@@ -798,6 +798,109 @@ function unique(items) {
798798 return result
799799}
800800
801+ /**
802+ * Dialogue-Primary Closeout (DPC): readable narrative without opening the report.
803+ * Shared helper — intentionally lenient (DPC-013).
804+ */
805+ function stripMarkdownLinks ( text ) {
806+ return String ( text || '' )
807+ . replace ( / \[ [ ^ \] ] * \] \( [ ^ ) ] + \) / g, ' ' )
808+ . replace ( / h t t p s ? : \/ \/ \S + / gi, ' ' )
809+ . replace ( / ` [ ^ ` ] + ` / g, ' ' )
810+ }
811+
812+ function hasUserNarrativeOverride ( text ) {
813+ return / u s e r - o v e r r i d e | 用 户 要 求 只 要 路 径 | 不 要 摘 要 | 只 要 路 径 | 全 文 贴 对 话 | 跳 过 对 话 摘 要 / i. test ( String ( text || '' ) )
814+ }
815+
816+ function hasReadableNarrativeSnippet ( sample ) {
817+ const text = String ( sample || '' )
818+ if ( ! text . trim ( ) ) return false
819+ if ( hasUserNarrativeOverride ( text ) ) return true
820+
821+ const plain = stripMarkdownLinks ( text ) . replace ( / \r \n / g, '\n' )
822+ const resultSignal =
823+ / (?: ^ | \n ) \s * (?: # { 1 , 3 } \s * ) ? (?: 结 果 | 结 论 | 推 荐 | 执 行 结 果 | 分 析 结 论 ) \s * [: : ] / im. test ( plain ) ||
824+ / (?: 结 果 | 结 论 | 推 荐 ) \s * [: : ] .{ 8 , } / m. test ( plain ) ||
825+ / (?: 本 次 完 成 | 做 成 了 | 方 案 审 阅 结 论 | 分 析 完 成 | 收 口 结 论 ) / . test ( plain ) ||
826+ / (?: 推 荐 \s * [: : ] | 推 荐 方 案 | 推 荐 结 论 ) / . test ( plain )
827+
828+ const bulletLines = plain
829+ . split ( '\n' )
830+ . map ( line => line . trim ( ) )
831+ . filter ( line => / ^ [ - * • ] | \d + [ . ) 、 ] / . test ( line ) )
832+ . map ( line => line . replace ( / ^ [ - * • ] \s * | \d + [ . ) 、 ] \s * / , '' ) . trim ( ) )
833+ . filter ( line => line . length >= 6 )
834+ . filter ( line => ! / ^ (?: 详 见 | 见 ) ? 报 告 | 路 径 [: : ] | r e p o r t s \/ / i. test ( line ) )
835+
836+ const substantiveBullet = bulletLines . length >= 1
837+ const reasonInSentence =
838+ / (?: 因 为 | 原 因 | 关 键 | 包 括 | 变 更 | 结 论 ) .{ 6 , } / . test ( plain ) &&
839+ plain . replace ( / \s + / g, '' ) . length >= 20
840+
841+ // Single short verdict with substance (e.g. "方案审阅结论:合理,因边界清晰。")
842+ const shortVerdict =
843+ / (?: 结 论 | 合 理 | 可 行 | 通 过 | 完 成 ) [: : , , \s ] .{ 4 , } / . test ( plain ) &&
844+ plain . replace ( / \s + / g, '' ) . length >= 12
845+
846+ if ( substantiveBullet ) return true
847+ if ( resultSignal && ( reasonInSentence || shortVerdict || plain . replace ( / \s + / g, '' ) . length >= 16 ) ) return true
848+ if ( shortVerdict && plain . replace ( / \s + / g, '' ) . length >= 16 ) return true
849+ return false
850+ }
851+
852+ /**
853+ * Classify dialogue-primary narrative for final-result / completion / analyze closeout.
854+ * @returns {'not-claimed'|'waived'|'present'|'narrative-missing' }
855+ */
856+ function analyzeDialogueNarrativeSample ( sample , options = { } ) {
857+ const textSample = String ( sample || '' )
858+ const forceClaimed = options . forceClaimed === true
859+ const claimedCloseout =
860+ forceClaimed ||
861+ / D e v C o d e x V i s i b l e E n v e l o p e V 1 \s * · \s * (?: c o m p l e t i o n - c h e c k | f i n a l - r e s u l t ) / i. test ( textSample ) ||
862+ / # # # \s * D e v C o d e x \s * · \s * (?: 完 成 检 查 | 执 行 结 果 ) / i. test ( textSample ) ||
863+ / 🛡 ️ \s * D E V \s * 模 式 \s * \| \s * 合 规 检 查 / i. test ( textSample ) ||
864+ / (?: 完 成 检 查 | c o m p l e t i o n - c h e c k | C o m p l e t i o n E v i d e n c e G a t e | f i n a l - r e s u l t ) / i. test ( textSample ) ||
865+ / (?: 分 析 完 成 | 审 阅 结 论 | 方 案 审 阅 | 收 敛 交 付 | 执 行 结 果 | 推 荐 结 论 | 本 次 完 成 | 已 完 成 交 付 ) / i. test ( textSample )
866+
867+ if ( ! textSample . trim ( ) || ! claimedCloseout ) {
868+ return {
869+ schemaVersion : 'DialogueNarrativeAnalysisV1' ,
870+ claimed : false ,
871+ status : 'not-claimed' ,
872+ classification : 'not-claimed' ,
873+ hasNarrative : false ,
874+ waived : false
875+ }
876+ }
877+
878+ if ( hasUserNarrativeOverride ( textSample ) ) {
879+ return {
880+ schemaVersion : 'DialogueNarrativeAnalysisV1' ,
881+ claimed : true ,
882+ status : 'verified-present' ,
883+ classification : 'waived' ,
884+ hasNarrative : true ,
885+ waived : true
886+ }
887+ }
888+
889+ const hasNarrative = hasReadableNarrativeSnippet ( textSample )
890+ return {
891+ schemaVersion : 'DialogueNarrativeAnalysisV1' ,
892+ claimed : true ,
893+ status : hasNarrative ? 'verified-present' : 'verified-missing' ,
894+ classification : hasNarrative ? 'present' : 'narrative-missing' ,
895+ hasNarrative,
896+ waived : false
897+ }
898+ }
899+
900+ function classifyDialogueNarrativeSample ( sample , options = { } ) {
901+ return analyzeDialogueNarrativeSample ( sample , options ) . classification
902+ }
903+
801904/**
802905 * DevModeCompletionCheckDetailGate / FinalValidationSummaryV1 free-text classifier.
803906 *
@@ -973,9 +1076,13 @@ module.exports = {
9731076 createLinkCapabilityDecision,
9741077 createVisibleEnvelope,
9751078 analyzeFinalValidationSummarySample,
1079+ analyzeDialogueNarrativeSample,
9761080 buildSimpleGovernanceFastPathDecision,
9771081 classifyArtifactPathColumnSample,
1082+ classifyDialogueNarrativeSample,
9781083 classifyFinalValidationSummarySample,
1084+ hasReadableNarrativeSnippet,
1085+ hasUserNarrativeOverride,
9791086 digest,
9801087 isSemanticDisplayName,
9811088 portableTarget,
0 commit comments