@@ -606,23 +606,70 @@ function portableTarget(filePath, workspaceRoot) {
606606 return String ( filePath ) . replace ( / \\ / g, '/' )
607607}
608608
609- function renderArtifactItem ( item , capability , tier ) {
609+ /**
610+ * PF-175 ArtifactPathColumnGate: always expose a path cell.
611+ * Default = workspace-relative portable; absolute only when fallback/failed/outside.
612+ * Rich clickable may still use absolute href for openability; path cell stays portable unless absolutePathFallback.
613+ */
614+ function resolveArtifactPathCell ( item , capability ) {
610615 const portable = portableTarget ( item . canonicalPath , capability ?. workspaceRoot )
611- const target = tier === 'rich-markdown' && capability ?. mode === 'clickable'
612- ? String ( item . canonicalPath ) . replace ( / \\ / g, '/' )
616+ const absolute = String ( item . canonicalPath || '' ) . replace ( / \\ / g, '/' )
617+ const forceAbsolute = capability ?. absolutePathFallback === true ||
618+ capability ?. mode === 'failed' ||
619+ capability ?. targetRelation === 'outside-workspace'
620+ return {
621+ portable,
622+ absolute,
623+ pathCell : forceAbsolute ? absolute : portable ,
624+ forceAbsolute
625+ }
626+ }
627+
628+ function renderArtifactItem ( item , capability , tier ) {
629+ const { portable, absolute, pathCell, forceAbsolute } = resolveArtifactPathCell ( item , capability )
630+ const linkTarget = tier === 'rich-markdown' && capability ?. mode === 'clickable'
631+ ? absolute
613632 : portable
614- const suffix = ` — ${ item . purposeText } ;操作:${ item . userAction } `
633+ const purpose = text ( item . purposeText ) || '用途未标注'
634+ const action = text ( item . userAction ) || '查看'
635+ // Path column always present (PF-175); not the same as legacy bare absolute-only lists.
636+ const pathSuffix = `;路径:\`${ pathCell } \`;操作:${ action } `
615637 if ( tier === 'plain-text' || capability ?. mode === 'plain' || capability ?. mode === 'failed' ) {
616- const locator = capability ?. absolutePathFallback ? String ( item . canonicalPath ) . replace ( / \\ / g, '/' ) : portable
617- const fallback = capability ?. absolutePathFallback ? `;fallback:${ capability . fallbackReason } ` : ''
618- return `- ${ item . displayName } ${ suffix } [${ locator } ]${ fallback } `
638+ const fallback = capability ?. absolutePathFallback && capability ?. fallbackReason
639+ ? `;fallback:${ capability . fallbackReason } `
640+ : ''
641+ return `- ${ item . displayName } — ${ purpose } ${ pathSuffix } ${ fallback } `
642+ }
643+ const escapedTarget = / \s / . test ( linkTarget ) ? `<${ linkTarget } >` : linkTarget
644+ let line = `- [${ item . displayName } ](${ escapedTarget } ) — ${ purpose } ${ pathSuffix } `
645+ // Absolute line only when path cell is already absolute and reason must stay explicit for failed surfaces.
646+ if ( forceAbsolute && capability ?. absolutePathFallback && capability ?. fallbackReason ) {
647+ line += `\n 绝对路径:${ absolute } (${ capability . fallbackReason } )`
619648 }
620- const escapedTarget = / \s / . test ( target ) ? `<${ target } >` : target
621- let line = `- [${ item . displayName } ](${ escapedTarget } )${ suffix } `
622- if ( capability ?. absolutePathFallback ) line += `\n 绝对路径:${ item . canonicalPath } `
623649 return line
624650}
625651
652+ /**
653+ * Free-text delivery/CP table classifier (PF-175).
654+ * @returns {'not-claimed'|'present'|'missing-path-column'|'legacy-bare-path' }
655+ */
656+ function classifyArtifactPathColumnSample ( sample ) {
657+ const textSample = String ( sample || '' )
658+ if ( ! textSample . trim ( ) ) return 'not-claimed'
659+ // Legacy bare-path lists are a claim form even without allowed action headings.
660+ if ( / (?: 主 要 产 物 | 核 心 文 件 | 路 径 列 表 ) \s * [: : ] ? \s * \n (?: [ - * ] \s * ) ? (?: [ A - Z a - z ] : [ \\ / ] | \/ ) / . test ( textSample ) &&
661+ ! / 用 途 | 操 作 : | \| \s * 路 径 \s * \| / . test ( textSample ) ) {
662+ return 'legacy-bare-path'
663+ }
664+ const claimsDelivery = / 需 要 你 确 认 的 文 件 | 本 批 交 付 文 件 | 完 成 交 付 文 件 | 阻 断 证 据 | 交 付 表 | 确 认 文 件 / . test ( textSample )
665+ if ( ! claimsDelivery ) return 'not-claimed'
666+ const hasPathColumn =
667+ / 路 径 \s * [: : ] / . test ( textSample ) ||
668+ / \| \s * 路 径 \s * \| / . test ( textSample ) ||
669+ / 路 径 \s * \| \s * / . test ( textSample )
670+ return hasPathColumn ? 'present' : 'missing-path-column'
671+ }
672+
626673function renderVisibleEnvelope ( envelope , { tier = null , compact = false } = { } ) {
627674 const validContract = envelope ?. validation ?. valid === true
628675 if ( ! validContract ) envelope = invalidEnvelope ( envelope ?. validation ?. errors || [ 'envelope-invalid' ] )
@@ -685,9 +732,13 @@ module.exports = {
685732 createLinkCapabilityDecision,
686733 createVisibleEnvelope,
687734 buildSimpleGovernanceFastPathDecision,
735+ classifyArtifactPathColumnSample,
688736 digest,
689737 isSemanticDisplayName,
738+ portableTarget,
690739 projectUserFacingArtifactSet,
740+ renderArtifactItem,
741+ resolveArtifactPathCell,
691742 renderVisibleEnvelope,
692743 shouldUseCompact
693744}
0 commit comments