Skip to content

Commit 28c5aa0

Browse files
committed
feat(visible-output): dialogue-primary closeout B1 classifiers
Add classifyDialogueNarrativeSample for completion/analysis closeout narrative, treat report-link-only analysis delivery as link-only-thin, and sync Skill/prompt/instructions plus unit tests. Lifecycle wiring is B2.
1 parent 30cde4b commit 28c5aa0

11 files changed

Lines changed: 208 additions & 10 deletions

‎changelogs/unreleased.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
- **GlobalOnly 全局 adapter 本地刷新 CLI(R1a)**:新增 `devcodex global-adapters apply [--dry-run] [--json]` 与 `npm run global-adapters:apply*`,从当前包根复用 `applyGlobalHostConfig` 刷新用户级五宿主 adapter(不 pack/不 publish)。nextStep/PC5/audit/V8/doctor/help/website intro 按源码 vs 已发布分流;次选 R1b `npm install -g .`、R2 pack+tarball、R3 `npm update -g`;bare `devcodex update` 仅 workspace。`package.json` files 已纳入 `global-adapters-cli` / guidance 模块,避免 pack 后 require 断裂。**Profile 01/02/04/05/06/07 已同步 R1a~R4**(PI-173)。任务:`bugs/GlobalOnly刷全局副本命令口径漂移修复`。
99
- **R1a 复审收口**:`host-parity-scorecard` repairSteps / recommendedEntry / userVisibleSummary 接入 `describeGlobalAdapterRefresh*`(源码 → `global-adapters apply`,已发布 → `npm -g`);`global-adapters apply` 在主机事务已提交但 Grok 集成失败时返回 `GLOBAL_ADAPTERS_HOSTS_COMMITTED_GROK_FAILED` + `partialState`;npm pack `files` 不再包含 `test-global-adapters-cli.js`。
10+
- **Dialogue-Primary Closeout B1**:新增 `classifyDialogueNarrativeSample` / `hasReadableNarrativeSnippet`(完成态/分析收敛须对话内可读叙事);`classifyAnalysisArtifactDeliverySample` 对「仅报告链接」返回 `link-only-thin`;同步 user-visible-output-contract、report、13-analyze、report-analysis prompt、V102 锚点与单测。lifecycle 接线留 B2。需求:`完成态对话内可读收口` CP2。
1011
- **文档站依赖审计加固**:Rspress 提升到 2.0.18,`brace-expansion` 提升到 5.0.8;`test:audit` 改为 fail-closed 结构化审计。当前唯一例外为仅影响 unstable RSC 的 `GHSA-qwww-vcr4-c8h2`,本站静态运行面禁止 RSC 标记,例外于 2026-08-15 到期;任何新 advisory、依赖链变化、RSC 使用或到期都会重新阻断。
1112
- **Grok 兼容导入去重**:Grok 原生会话兼容加载 `~/.claude/settings.json` 时,DevCodex Claude adapter 识别 Grok 的 camelCase payload 与 snake_case 事件并安全跳过重复 lifecycle;Grok 专用 plugin 保持唯一生命周期 owner,消除 `user_prompt_submit` / `stop` hook 报错和双重状态写入。
1213
- **五宿主全局 adapter 运行态修复**:`status`/`doctor` 从 receipt/files 假就绪升级为 `configured/adapterReady/contractStatus/nativeStatus/operationalState/ready/issues` 分层;五个可执行 adapter 均增加有界契约探针,只有原生 CLI 深探针通过才可标记 operational ready。doctor 增加原生版本与 Grok list/inspect、installed Hook contract、MCP initialize。Grok plugin source 改为用户级 runtime + cwd workspace 发现,同名受管 identity 经官方 CLI drain-all 后收敛到唯一 canonical;未知来源 mutation 前阻断,首次安装与多 identity 失败均保留/恢复原注册状态,受管恢复来源可继续收敛。Copilot CLI 按官方用户级目录部署 instructions、Hooks、MCP 与 Skills;Copilot IDE 仍保持独立 instruction-fallback ceiling。

‎hooks/_runtime/visible-output-contract.cjs‎

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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(/https?:\/\/\S+/gi, ' ')
809+
.replace(/`[^`]+`/g, ' ')
810+
}
811+
812+
function hasUserNarrativeOverride(text) {
813+
return /user-override|用户要求只要路径|不要摘要|只要路径|全文贴对话|跳过对话摘要/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 => !/^(?:详见|见)?报告|路径[::]|reports\//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+
/DevCodexVisibleEnvelopeV1\s*·\s*(?:completion-check|final-result)/i.test(textSample) ||
862+
/###\s*DevCodex\s*·\s*(?:完成检查|执行结果)/i.test(textSample) ||
863+
/🛡️\s*DEV\s*模式\s*\|\s*合规检查/i.test(textSample) ||
864+
/(?:完成检查|completion-check|CompletionEvidenceGate|final-result)/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,

‎instructions/13-analyze.instructions.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ version: 1.15.3
7474
> ⚠️ 三项验证通过 **PCV-3** 统一完成(而非每轮实时补充);最终用户面结论中必须可见,不得仅在报告文件中满足。
7575
> ⛔ 缺少任意一项验证均导致 SC1 不通过。
7676
77+
### 对话内可读收口(Dialogue-Primary)
78+
79+
- analyze / 方案审阅收敛时,用户面必须是**可读摘要 + 报告链接**;禁止仅报告链接或「详见报告」宣称完成(`link-only-thin` / `classifyDialogueNarrativeSample`)。
80+
- 长论证进 `reports/**`;对话内至少:结果/结论一句 + ≥1 实质要点 + 推荐结论(如适用)。
81+
- 默认不要引导用户打开预览软件阅读报告;路径供归档与深读。
82+
7783
### 推荐结论(必须显式)
7884

7985
- analyze 报告或用户面结论出现多个可执行建议、多个后续路径或方案对比时,必须输出 `推荐结论` 或 `推荐方案`。

‎instructions/16-report.instructions.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ reports/<子目录>/<agent>/YYYYMMDD/NN--<简述>.md
104104
- 每条建议/问题必须附五项验证(合理性 + 可实施性 + 收益 + 验证状态 + 影响范围)— 与 [`17-compliance.instructions.md`](./17-compliance.instructions.md) §1 输出验证保持一致
105105
- 报告写入后必须执行二次验证(V1~V6,见 `17-compliance.instructions.md`)
106106
- dev/fix/self-fix 宣告完成时,最终回复必须投影 `FinalValidationSummaryV1` 或等价短矩阵:权威验证命令与 exitCode、runId 或关键计数、WorkspaceSyncStatus、dirty boundary、release action boundary;若声明 commit,必须追加 post-commit replay。长日志留在报告中,用户面不得只写“全绿 / 已通过 / 详见报告”。
107+
- **Dialogue-Primary**:完成态最终回复还须含可读叙事(结果 + 要点),见 `user-visible-output-contract` 的 `classifyDialogueNarrativeSample`;分析收敛禁止报告链接 alone(`link-only-thin`)。
107108
- 跨会话/未完成/多批次任务不得缺少 `ContextHandoffCard`;SUMMARY 只能作为索引,不得替代交接卡
108109
- 报告必须登记到 `ArtifactDeliveryManifestV1`,最终用户面由 `UserFacingArtifactSetV1` 投影为语义链接(详见 [`02-output-paths.instructions.md`](./02-output-paths.instructions.md) §产物路径输出格式):
109110
```markdown

‎prompts/report-analysis.prompt.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ applyTo: .devcodex/**/reports/analysis/**
3636

3737
## §3 分析结论
3838

39+
### §3.0 用户可见收口(对话内主读)
40+
41+
> 最终回复(非仅本报告文件)须让用户**不打开报告源码**也能读懂:结果一句话 + ≥1 要点 + 本报告链接。
42+
> 禁止仅链接 /「详见报告」;`classifyAnalysisArtifactDeliverySample` 对无叙事的链接判 `link-only-thin`。
43+
3944
### §3.1 推荐结论
4045

4146
> 多建议、多路径或技术选型时必须填写;无后续动作时写“推荐:无后续动作”并说明原因。

‎scripts/lib/optimization-backlog-evidence.js‎

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@
33
/**
44
* PF-168 / PF-169 / PF-170 — evidence honesty for optimization backlog,
55
* analysis artifact delivery, and residual optimization lists.
6+
* DPC: analysis delivery requires readable narrative + report link (link-only-thin).
67
*/
78

9+
const {
10+
hasReadableNarrativeSnippet
11+
} = require('../../hooks/_runtime/visible-output-contract.cjs')
12+
813
const EVIDENCE_GRADE = /证据等级|evidence\s*grade|证据档|A\s*\/\s*B\s*\/\s*C|[((][ABC][))]|\b[ABC]\s*级/i
914
const REPRO_CMD = /可复现|npm run|node scripts\/|exitCode|验证命令|command:/i
1015
const UNVERIFIED = /待验证|UNVERIFIED|未验证|⚠️/i
@@ -37,17 +42,21 @@ function classifyOptimizationBacklogEvidenceSample(sample) {
3742

3843
/**
3944
* @param {string} sample
40-
* @returns {'ready'|'chat-only'|'not-analysis-delivery'}
45+
* @returns {'ready'|'chat-only'|'link-only-thin'|'not-analysis-delivery'}
4146
*/
4247
function classifyAnalysisArtifactDeliverySample(sample) {
4348
const text = String(sample || '')
4449
if (!ANALYSIS_CLAIM.test(text) && !/analyze|audit 结论|方案对比/i.test(text)) {
4550
return 'not-analysis-delivery'
4651
}
47-
if (REPORT_LINK.test(text) || /reports\/analysis|reports\/audit/i.test(text)) return 'ready'
48-
// Long analysis body without report path
49-
if (text.length > 400 && !REPORT_LINK.test(text)) return 'chat-only'
50-
if (ANALYSIS_CLAIM.test(text) && !REPORT_LINK.test(text)) return 'chat-only'
52+
const hasReport = REPORT_LINK.test(text) || /reports\/analysis|reports\/audit/i.test(text)
53+
if (!hasReport) {
54+
if (text.length > 400) return 'chat-only'
55+
if (ANALYSIS_CLAIM.test(text)) return 'chat-only'
56+
return 'chat-only'
57+
}
58+
// DPC-009: report link alone is not enough — need dialogue-readable narrative
59+
if (!hasReadableNarrativeSnippet(text)) return 'link-only-thin'
5160
return 'ready'
5261
}
5362

‎scripts/lib/validate-visible-output-controls.js‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ function buildVisibleOutputControlChecks(ctx) {
3030
requireAnchors('skills/review-checklist/SKILL.md', ['ReviewExecutionPlanV1', 'ReviewEvidenceReceiptV1', 'ReviewStateSnapshotV1'])
3131
requireAnchors('skills/report/SKILL.md', ['ArtifactDeliveryManifestV1', 'UserFacingArtifactSetV1', 'FinalValidationSummaryV1'])
3232
requireAnchors('skills/compliance/SKILL.md', ['FinalValidationSummaryV1', 'DevModeCompletionCheckDetailGate'])
33-
requireAnchors('skills/user-visible-output-contract/SKILL.md', ['ArtifactAnchorProjectionV1', 'ArtifactAnchorProjectionGate', 'FinalValidationSummaryGate', 'classifyFinalValidationSummarySample'])
33+
requireAnchors('skills/user-visible-output-contract/SKILL.md', ['ArtifactAnchorProjectionV1', 'ArtifactAnchorProjectionGate', 'FinalValidationSummaryGate', 'classifyFinalValidationSummarySample', 'classifyDialogueNarrativeSample', 'Dialogue-Primary'])
3434
requireAnchors('prompts/precheck-status.prompt.md', ['DevCodexVisibleEnvelopeV1', 'PC0~PC7'])
35-
requireAnchors('hooks/_runtime/visible-output-contract.cjs', ['ArtifactAnchorV1', 'projectArtifactAnchorsFromManifest', 'analyzeFinalValidationSummarySample', 'classifyFinalValidationSummarySample'])
35+
requireAnchors('hooks/_runtime/visible-output-contract.cjs', ['ArtifactAnchorV1', 'projectArtifactAnchorsFromManifest', 'analyzeFinalValidationSummarySample', 'classifyFinalValidationSummarySample', 'classifyDialogueNarrativeSample', 'hasReadableNarrativeSnippet'])
3636
requireAnchors('hooks/_runtime/lifecycle-visible-reply.cjs', ['finalValidationSummaryStatus', 'DevModeCompletionCheckDetailGate'])
3737

3838
try {

‎scripts/test-optimization-backlog-evidence.js‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,20 @@ assert.strictEqual(
3030
classifyAnalysisArtifactDeliverySample('技术方案分析完成。总体合理。' + '细节。'.repeat(200)),
3131
'chat-only'
3232
)
33-
// PF-169 positive
33+
// PF-169 positive: report link + readable narrative
3434
assert.strictEqual(
3535
classifyAnalysisArtifactDeliverySample(
36-
'方案审阅结论:合理。详细报告:[04--审阅.md](./reports/analysis/grok/20260721/04--审阅.md)'
36+
'方案审阅结论:合理,因边界清晰且与仓库实现一致。详细报告:[04--审阅.md](./reports/analysis/grok/20260721/04--审阅.md)'
3737
),
3838
'ready'
3939
)
40+
// DPC-009: report link without readable narrative → link-only-thin
41+
assert.strictEqual(
42+
classifyAnalysisArtifactDeliverySample(
43+
'分析完成。[报告](./reports/analysis/grok/20260721/04--审阅.md)'
44+
),
45+
'link-only-thin'
46+
)
4047

4148
// PF-170 negative residual without benefit
4249
assert.strictEqual(

‎scripts/test-visible-output-contract.js‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@ const {
1515
createLinkCapabilityDecision,
1616
createVisibleEnvelope,
1717
analyzeFinalValidationSummarySample,
18+
analyzeDialogueNarrativeSample,
1819
projectArtifactAnchorsFromManifest,
1920
projectUserFacingArtifactSet,
2021
renderVisibleEnvelope,
22+
classifyDialogueNarrativeSample,
2123
classifyFinalValidationSummarySample,
24+
hasReadableNarrativeSnippet,
2225
shouldUseCompact
2326
} = require('../hooks/_runtime/visible-output-contract.cjs')
2427
const ROOT = path.resolve(__dirname, '..')
@@ -347,6 +350,36 @@ assert.strictEqual(classifyFinalValidationSummarySample([
347350
'commit abc1234'
348351
].join('\n')), 'post-commit-replay')
349352

353+
// DPC: dialogue-primary narrative (readable closeout without opening report)
354+
assert.strictEqual(classifyDialogueNarrativeSample('随便聊聊'), 'not-claimed')
355+
assert.strictEqual(
356+
classifyDialogueNarrativeSample([
357+
'### DevCodex · 完成检查',
358+
'详见报告:[r](reports/x.md)',
359+
'`DevCodexVisibleEnvelopeV1 · completion-check · PASS · ' + 'e'.repeat(64) + '`'
360+
].join('\n')),
361+
'narrative-missing'
362+
)
363+
assert.strictEqual(
364+
classifyDialogueNarrativeSample([
365+
'### DevCodex · 完成检查',
366+
'## 结果',
367+
'已完成对话内可读收口 B1:叙事分类器与 link-only-thin 已落地。',
368+
'## 要点',
369+
'- 扩展既有 FVS/PF-169,未新建平行 Gate',
370+
'- 用户默认可在对话内读懂结论',
371+
'`DevCodexVisibleEnvelopeV1 · completion-check · PASS · ' + 'f'.repeat(64) + '`'
372+
].join('\n')),
373+
'present'
374+
)
375+
assert.strictEqual(
376+
classifyDialogueNarrativeSample('分析完成。user-override:用户要求只要路径。'),
377+
'waived'
378+
)
379+
assert.ok(hasReadableNarrativeSnippet('方案审阅结论:合理,因边界清晰且可验证。'))
380+
assert.ok(!hasReadableNarrativeSnippet('[报告](reports/a.md)'))
381+
assert.strictEqual(analyzeDialogueNarrativeSample('分析完成。').classification, 'narrative-missing')
382+
350383
// F-009 / PF-177: production consumer (lifecycle-visible-reply) must surface missing path column
351384
{
352385
const { buildLifecycleVisibleReplyUtils } = require('../hooks/_runtime/lifecycle-visible-reply.cjs')

‎skills/report/SKILL.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ CLI/MCP surface。
146146
- 控制面报告若出现新增探针、黄色偏离或部署同步,必须单独写出部署同步证据与其他证据来源,不能只在摘要里带过
147147
- 报告末尾引用本次会话记忆路径
148148
- 回复末尾由 `user-visible-output-contract` 输出“完成交付文件”:每项使用语义 displayName、purposeText、userAction,并强制 **路径列**(默认 workspace-relative portable,见 PF-175 路径列规则),按 decision→result→evidence→optional 顺序。`ArtifactLinkSet` 只作兼容投影;Rich clickable 不在路径列外重复 `绝对路径:` 行,只有用户要求、链接失败、工作区外、歧义或无法定位时路径列/fallback 用绝对路径(详见 [`02-output-paths.instructions.md`](../../instructions/02-output-paths.instructions.md))
149+
- **Dialogue-Primary Closeout(对话内可读收口)**:完成态 / analyze·audit 收敛的**阅读主入口是最终回复**,不是打开报告源码。最终回复须含叙事最小包(结果一句话 + ≥1 实质要点)+(完成态)`FinalValidationSummary` + 唯一步 + 交付清单;报告仍强制落盘(chat 豁免)。禁止仅链接/`详见报告`/纯矩阵收口;analyze 有报告链接但无可读结论 → `link-only-thin`(`classifyAnalysisArtifactDeliverySample`)。userAction 默认「深读时打开归档报告」,禁止默认「请用 Typora/浏览器预览」。机检:`classifyDialogueNarrativeSample` / `hasReadableNarrativeSnippet`
149150

150151
## 行数与拆分
151152

0 commit comments

Comments
 (0)