sillyspec 3.32.10 → 3.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -114
  2. package/.claude/skills/sillyspec-execute/SKILL.md +222 -222
  3. package/.claude/skills/sillyspec-explore/SKILL.md +107 -107
  4. package/.claude/skills/sillyspec-plan/SKILL.md +156 -156
  5. package/.github/workflows/ci.yml +58 -0
  6. package/.husky/pre-push +7 -6
  7. package/eval/eval.config.json +3 -3
  8. package/eval/lib/config.mjs +6 -3
  9. package/eval/lib/util.mjs +16 -3
  10. package/eval/lib/workdir.mjs +4 -1
  11. package/eval/results/eval.db +0 -0
  12. package/eval/results/logs/2026-10-15T12-37-22-986Z-004-phase-stats-armA/prompt.txt +10 -0
  13. package/eval/results/logs/2026-10-15T12-37-22-986Z-004-phase-stats-armA/stderr.txt +0 -0
  14. package/eval/results/logs/2026-10-15T12-37-22-986Z-004-phase-stats-armA/stdout.txt +1 -0
  15. package/eval/results/logs/2026-10-15T12-45-37-124Z-004-phase-stats-armB/prompt.txt +10 -0
  16. package/eval/results/logs/2026-10-15T12-45-37-124Z-004-phase-stats-armB/stderr.txt +0 -0
  17. package/eval/results/logs/2026-10-15T12-45-37-124Z-004-phase-stats-armB/stdout.txt +1 -0
  18. package/eval/results/logs/2026-10-15T12-56-19-259Z-004-phase-stats-armA/prompt.txt +10 -0
  19. package/eval/results/logs/2026-10-15T12-56-19-259Z-004-phase-stats-armA/stderr.txt +0 -0
  20. package/eval/results/logs/2026-10-15T12-56-19-259Z-004-phase-stats-armA/stdout.txt +1 -0
  21. package/eval/results/logs/2026-10-15T13-00-05-625Z-004-phase-stats-armB/prompt.txt +10 -0
  22. package/eval/results/logs/2026-10-15T13-04-51-003Z-004-phase-stats-armA/prompt.txt +10 -0
  23. package/eval/results/logs/2026-10-15T13-04-51-003Z-004-phase-stats-armA/stderr.txt +0 -0
  24. package/eval/results/logs/2026-10-15T13-04-51-003Z-004-phase-stats-armA/stdout.txt +1 -0
  25. package/eval/results/logs/2026-10-15T13-06-25-271Z-004-phase-stats-armB/prompt.txt +10 -0
  26. package/eval/results/logs/2026-10-15T13-06-25-271Z-004-phase-stats-armB/stderr.txt +0 -0
  27. package/eval/results/logs/2026-10-15T13-06-25-271Z-004-phase-stats-armB/stdout.txt +1 -0
  28. package/eval/results/logs/2026-10-15T13-24-38-308Z-004-phase-stats-armB/prompt.txt +10 -0
  29. package/eval/results/logs/2026-10-15T13-24-38-308Z-004-phase-stats-armB/stderr.txt +0 -0
  30. package/eval/results/logs/2026-10-15T13-24-38-308Z-004-phase-stats-armB/stdout.txt +1 -0
  31. package/eval/results/logs/2026-10-15T14-50-01-950Z-005-orders-export-armA/prompt.txt +8 -0
  32. package/eval/results/logs/2026-10-15T14-50-01-950Z-005-orders-export-armA/stderr.txt +0 -0
  33. package/eval/results/logs/2026-10-15T14-50-01-950Z-005-orders-export-armA/stdout.txt +1 -0
  34. package/eval/results/logs/2026-10-15T14-51-56-316Z-005-orders-export-armB/prompt.txt +8 -0
  35. package/eval/results/logs/2026-10-15T14-51-56-316Z-005-orders-export-armB/stderr.txt +0 -0
  36. package/eval/results/logs/2026-10-15T14-51-56-316Z-005-orders-export-armB/stdout.txt +1 -0
  37. package/eval/results/workdirs/001-slugify__armA/package.json +6 -0
  38. package/eval/results/workdirs/001-slugify__armA/src/slugify.mjs +26 -0
  39. package/eval/results/workdirs/001-slugify__armB/package.json +6 -0
  40. package/eval/results/workdirs/001-slugify__armB/src/slugify.mjs +26 -0
  41. package/eval/results/workdirs/002-each-day__armA/package.json +6 -0
  42. package/eval/results/workdirs/002-each-day__armA/src/dates.mjs +30 -0
  43. package/eval/results/workdirs/002-each-day__armB/DRY_RUN_WRONG_ANSWER.mjs +1 -0
  44. package/eval/results/workdirs/002-each-day__armB/package.json +6 -0
  45. package/eval/results/workdirs/002-each-day__armB/src/dates.mjs +4 -0
  46. package/eval/results/workdirs/003-word-report__armA/bin/report.mjs +36 -0
  47. package/eval/results/workdirs/003-word-report__armA/data/notes.txt +5 -0
  48. package/eval/results/workdirs/003-word-report__armA/package.json +6 -0
  49. package/eval/results/workdirs/003-word-report__armB/bin/report.mjs +36 -0
  50. package/eval/results/workdirs/003-word-report__armB/data/notes.txt +5 -0
  51. package/eval/results/workdirs/003-word-report__armB/package.json +6 -0
  52. package/eval/results/workdirs/004-phase-stats__armA/data/events.log +16 -0
  53. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/flow.md +44 -0
  54. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-archive.md +27 -0
  55. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-brainstorm.md +34 -0
  56. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-execute.md +30 -0
  57. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-plan.md +28 -0
  58. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-verify.md +30 -0
  59. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/status.md +28 -0
  60. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-archive/SKILL.md +87 -0
  61. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-auto/SKILL.md +54 -0
  62. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
  63. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-commit/SKILL.md +67 -0
  64. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-continue/SKILL.md +17 -0
  65. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
  66. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-execute/SKILL.md +222 -0
  67. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-explore/SKILL.md +107 -0
  68. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-export/SKILL.md +57 -0
  69. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-flow/SKILL.md +62 -0
  70. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-init/SKILL.md +177 -0
  71. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-knowledge/SKILL.md +40 -0
  72. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-plan/SKILL.md +156 -0
  73. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-propose/SKILL.md +16 -0
  74. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-quick/SKILL.md +116 -0
  75. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-resume/SKILL.md +21 -0
  76. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-scan/SKILL.md +79 -0
  77. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-state/SKILL.md +25 -0
  78. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-status/SKILL.md +54 -0
  79. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-verify/SKILL.md +87 -0
  80. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-workspace/SKILL.md +33 -0
  81. package/eval/results/workdirs/004-phase-stats__armB/AGENTS.md +45 -0
  82. package/eval/results/workdirs/004-phase-stats__armB/CLAUDE.md +3 -0
  83. package/eval/results/workdirs/004-phase-stats__armB/data/events.log +16 -0
  84. package/eval/results/workdirs/run-001-slugify__armA/package.json +6 -0
  85. package/eval/results/workdirs/run-001-slugify__armA/src/slugify.mjs +26 -0
  86. package/eval/results/workdirs/run-001-slugify__armB/package.json +6 -0
  87. package/eval/results/workdirs/run-001-slugify__armB/src/slugify.mjs +26 -0
  88. package/eval/results/workdirs/run-002-each-day__armA/package.json +6 -0
  89. package/eval/results/workdirs/run-002-each-day__armA/src/dates.mjs +30 -0
  90. package/eval/results/workdirs/run-002-each-day__armB/DRY_RUN_WRONG_ANSWER.mjs +1 -0
  91. package/eval/results/workdirs/run-002-each-day__armB/package.json +6 -0
  92. package/eval/results/workdirs/run-002-each-day__armB/src/dates.mjs +4 -0
  93. package/eval/results/workdirs/run-003-word-report__armA/bin/report.mjs +36 -0
  94. package/eval/results/workdirs/run-003-word-report__armA/data/notes.txt +5 -0
  95. package/eval/results/workdirs/run-003-word-report__armA/package.json +6 -0
  96. package/eval/results/workdirs/run-003-word-report__armB/bin/report.mjs +36 -0
  97. package/eval/results/workdirs/run-003-word-report__armB/data/notes.txt +5 -0
  98. package/eval/results/workdirs/run-003-word-report__armB/package.json +6 -0
  99. package/eval/results/workdirs/run-004-phase-stats__armA/bin/phase-stats.mjs +37 -0
  100. package/eval/results/workdirs/run-004-phase-stats__armA/data/events.log +16 -0
  101. package/eval/results/workdirs/run-004-phase-stats__armB/DRY_RUN_WRONG_ANSWER.mjs +1 -0
  102. package/eval/results/workdirs/run-004-phase-stats__armB/data/events.log +16 -0
  103. package/eval/results/workdirs/run-005-orders-export__armA/bin/export-orders.mjs +57 -0
  104. package/eval/results/workdirs/run-005-orders-export__armA/data/.gitattributes +1 -0
  105. package/eval/results/workdirs/run-005-orders-export__armA/data/orders.csv +9 -0
  106. package/eval/results/workdirs/run-005-orders-export__armA/out/summary.json +1 -0
  107. package/eval/results/workdirs/run-005-orders-export__armB/bin/export-orders.mjs +57 -0
  108. package/eval/results/workdirs/run-005-orders-export__armB/data/.gitattributes +1 -0
  109. package/eval/results/workdirs/run-005-orders-export__armB/data/orders.csv +9 -0
  110. package/eval/results/workdirs/run-005-orders-export__armB/out/summary.json +1 -0
  111. package/eval/tasks/004-phase-stats/files/data/events.log +16 -0
  112. package/eval/tasks/004-phase-stats/solution/bin/phase-stats.mjs +37 -0
  113. package/eval/tasks/004-phase-stats/task.json +6 -0
  114. package/eval/tasks/004-phase-stats/verify.mjs +38 -0
  115. package/eval/tasks/005-orders-export/files/data/.gitattributes +1 -0
  116. package/eval/tasks/005-orders-export/files/data/orders.csv +9 -0
  117. package/eval/tasks/005-orders-export/solution/bin/export-orders.mjs +57 -0
  118. package/eval/tasks/005-orders-export/task.json +6 -0
  119. package/eval/tasks/005-orders-export/verify.mjs +28 -0
  120. package/package.json +1 -1
  121. package/quicklog/tmp-fix-review.mjs +21 -0
  122. package/src/ceremony-tier.js +2 -1
  123. package/src/decision-distill.js +29 -3
  124. package/src/decision-redomain.js +148 -0
  125. package/src/design-facts.js +3 -3
  126. package/src/docs-debt.js +3 -3
  127. package/src/flow-parity.js +10 -2
  128. package/src/flow.js +1 -1
  129. package/src/forensics.js +236 -0
  130. package/src/fr-index.js +2 -1
  131. package/src/index.js +34 -1
  132. package/src/knowledge-graph.js +3 -4
  133. package/src/module-impact.js +2 -5
  134. package/src/modules.js +31 -0
  135. package/src/run/gates.js +7 -14
  136. package/src/run/lint-ownership.js +37 -0
  137. package/src/run/prompt.js +40 -25
  138. package/src/run/verify-quality-scan.js +10 -1
  139. package/src/spec-sync.js +81 -2
  140. package/src/stage-review.js +2 -1
  141. package/src/stages/brainstorm.js +71 -20
  142. package/src/stages/plan-postcheck.js +13 -2
  143. package/src/stages/plan.js +19 -0
  144. package/src/test-bindings.js +32 -3
  145. package/src/ui-visual.js +119 -4
  146. package/src/verify-postcheck.js +117 -17
  147. package/src/worktree-apply.js +26 -2
package/src/run/prompt.js CHANGED
@@ -68,6 +68,7 @@ const VOLATILE_PLACEHOLDER_RES = [
68
68
  /<now-datetime>/g, /<now-timestamp>/g, /<now-date>/g, /<now-iso-datetime>/g, /<git-head-short>/g, /<quicklog-id>/g,
69
69
  /{SCAN_STALENESS}/g, /{SCAN_FACTS}/g, /{QUICK_CONTEXT_DIGEST}/g, /{DECISION_HITS}/g, /{UI_VISUAL_GUIDANCE}/g,
70
70
  /{KNOWLEDGE_HIT_REPORT}/g, /{DOCS_DEBT}/g, /{MODULE_RESOLVE_TABLE}/g, /{REVIEW_MATERIALS}/g,
71
+ /{REVIEW_TIER}/g, /{REVIEW_TIER_REASON}/g, /{REVIEW_JSON_CONTRACT}/g,
71
72
  /{EXECUTE_RUN_ID}/g, /{STAGE_REVIEW_RUN_ID}/g, /{PROGRESS_SNAPSHOT}/g, /{TASK_COMPLETION_REPORT}/g,
72
73
  /{WORKTREE_META}/g, /{WORKTREE_BASELINE_INFO}/g, /{TASKS_CHECKBOX}/g, /{GIT_DIRTY}/g,
73
74
  /{SCOPE_AUDIT_TABLE}/g, /{FR_INDEX_DIGEST}/g, /{HANDOVER_SUMMARY}/g, /{PREFLIGHT_FAILURES}/g,
@@ -1044,6 +1045,18 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
1044
1045
  promptText = promptText.split(token).join(v)
1045
1046
  return promptText
1046
1047
  }
1048
+ // 裸 split/join 注入点的收集通道(2026-10-16-step-guide-dyn-completeness):语义与
1049
+ // substituteSplitJoin 相同,供不以占位符正则批量替换的站点使用。
1050
+ // ⚠️ 纪律:凡向 promptText 注入运行期值的站点必须走本组三个助手之一——裸 `.split().join()`
1051
+ // 会让值漏进复入短输出的「动态注入段」附录(值不落盘、guide 掩码后无回读通道)。
1052
+ // 2026-10-15 消费仓实证:评审六值(tier/材料包/契约等)漏收,主代理被迫读引擎源码逆推档位、
1053
+ // 手建材料包、手拼 review.json 路径写错一层前缀再纠正——收集器注释「漏收才是坑」的兑现。
1054
+ const substituteRaw = (token, value) => {
1055
+ const v = String(value)
1056
+ dynSegments.push(v)
1057
+ promptText = promptText.split(token).join(v)
1058
+ return promptText
1059
+ }
1047
1060
  // 替换 prompt 中的占位符
1048
1061
  if (projectName && promptText.includes('<project>')) {
1049
1062
  substitute(/<project>/g, projectName, true)
@@ -1427,7 +1440,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
1427
1440
  // 替换空串零输出;fail-soft 异常单行说明,不留残留占位符。
1428
1441
  if (stageName === 'brainstorm' && promptText.includes('{UI_VISUAL_GUIDANCE}')) {
1429
1442
  try {
1430
- const { detectUiTouch, buildUiGuidanceLines } = await import('../ui-visual.js')
1443
+ const { detectUiTouch, buildUiGuidanceLines, extractQuotedPaths } = await import('../ui-visual.js')
1431
1444
  const uiSpecBase = resolvePromptSpecBase(platformOpts, cwd)
1432
1445
  let corpus = changeName || ''
1433
1446
  for (const name of ['proposal.md', 'requirements.md']) {
@@ -1435,7 +1448,10 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
1435
1448
  corpus += '\n' + readFileSync(join(uiSpecBase, 'changes', changeName, name), 'utf8')
1436
1449
  } catch { /* 缺席跳过 */ }
1437
1450
  }
1438
- substitute(/\{UI_VISUAL_GUIDANCE\}/g, detectUiTouch(corpus) ? '\n' + buildUiGuidanceLines().join('\n') + '\n' : '')
1451
+ // ui_prototype 形态注入(2026-10-15-ui-proto-form-config):语料路径参与子仓 override
1452
+ // 匹配(proposal/requirements 反引号路径粗提);无路径=仓级兜底。
1453
+ const guidancePaths = extractQuotedPaths(corpus)
1454
+ substitute(/\{UI_VISUAL_GUIDANCE\}/g, detectUiTouch(corpus) ? '\n' + buildUiGuidanceLines(uiSpecBase, guidancePaths).join('\n') + '\n' : '')
1439
1455
  } catch (e) {
1440
1456
  substitute(/\{UI_VISUAL_GUIDANCE\}/g, `[ui-guidance] UI 须知注入异常(${e.message}),跳过`)
1441
1457
  }
@@ -1676,22 +1692,21 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
1676
1692
  reviewMaterialsMd = await assembleStageReviewMaterials({ stage: matStage, cwd, changeName, specBase: tierSpecBase })
1677
1693
  } catch { /* 组装 best-effort:失败零注入 */ }
1678
1694
  }
1679
- promptText = promptText
1680
- .split('{REVIEW_TIER}').join(ceremonyInjection.tierValue + ceremonyInjection.menuMd)
1681
- .split('{REVIEW_TIER_REASON}').join(tier.reason)
1682
- .split('{STAGE_REVIEW_RUN_ID}').join(reviewRunId)
1683
- .split('{REVIEW_JSON_CONTRACT}').join(reviewContractMd)
1684
- .split('{PRIOR_REVIEW_FACTS}').join(priorFactsMd)
1685
- .split('{REVIEW_MATERIALS}').join(reviewMaterialsMd) // 三阶段材料包槽(CLI 组装注入,Gap 1 收口——此前恒空串靠主代理手组,机械保证断一半;主代理点名半边由模板补位指引接手)
1695
+ // 全部走 substituteRaw(收集进 dynSegments——复入附录含评审六值;2026-10-16-step-guide-dyn-completeness)
1696
+ substituteRaw('{REVIEW_TIER}', ceremonyInjection.tierValue + ceremonyInjection.menuMd)
1697
+ substituteRaw('{REVIEW_TIER_REASON}', tier.reason)
1698
+ substituteRaw('{STAGE_REVIEW_RUN_ID}', reviewRunId)
1699
+ substituteRaw('{REVIEW_JSON_CONTRACT}', reviewContractMd)
1700
+ substituteRaw('{PRIOR_REVIEW_FACTS}', priorFactsMd)
1701
+ substituteRaw('{REVIEW_MATERIALS}', reviewMaterialsMd) // 三阶段材料包槽(CLI 组装注入,Gap 1 收口——此前恒空串靠主代理手组,机械保证断一半;主代理点名半边由模板补位指引接手)
1686
1702
  } catch (e) {
1687
- // 降级 self,避免 prompt 残留占位符
1688
- promptText = promptText
1689
- .split('{REVIEW_TIER}').join('self')
1690
- .split('{REVIEW_TIER_REASON}').join('分级异常降级 self: ' + e.message)
1691
- .split('{STAGE_REVIEW_RUN_ID}').join('review-unknown')
1692
- .split('{REVIEW_JSON_CONTRACT}').join('(review 契约注入失败,按 schemaVersion=1 + reviewType + verdicts∈pass/fail/cannot_verify + reviewedFiles + docHash=主文档 sha256 产出)')
1693
- .split('{PRIOR_REVIEW_FACTS}').join('')
1694
- .split('{REVIEW_MATERIALS}').join('') // 降级分支同步 join(plan 审查提示:防占位符字面量残留)
1703
+ // 降级 self,避免 prompt 残留占位符(降级值同样收编进 dynSegments——附录如实呈现降级态)
1704
+ substituteRaw('{REVIEW_TIER}', 'self')
1705
+ substituteRaw('{REVIEW_TIER_REASON}', '分级异常降级 self: ' + e.message)
1706
+ substituteRaw('{STAGE_REVIEW_RUN_ID}', 'review-unknown')
1707
+ substituteRaw('{REVIEW_JSON_CONTRACT}', '(review 契约注入失败,按 schemaVersion=1 + reviewType + verdicts∈pass/fail/cannot_verify + reviewedFiles + docHash=主文档 sha256 产出)')
1708
+ substituteRaw('{PRIOR_REVIEW_FACTS}', '')
1709
+ substituteRaw('{REVIEW_MATERIALS}', '') // 降级分支同步 join(plan 审查提示:防占位符字面量残留)
1695
1710
  }
1696
1711
  }
1697
1712
 
@@ -1721,7 +1736,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
1721
1736
  } catch (e) {
1722
1737
  metaInfo = '(注入失败:' + e.message + '——运行 sillyspec worktree meta 自查,meta.json 不存在则停止并报错)'
1723
1738
  }
1724
- promptText = promptText.split('{WORKTREE_META}').join(metaInfo)
1739
+ substituteRaw('{WORKTREE_META}', metaInfo)
1725
1740
  }
1726
1741
 
1727
1742
  // archive Step1「任务完成度检查」客观真相源注入:以 review.json verdict 算完成度,
@@ -1738,9 +1753,9 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
1738
1753
  const summary = tcrChangeDir
1739
1754
  ? summarizeTaskCompletion({ changeDir: tcrChangeDir, runtimeRoot: tcrRuntimeRoot, changeName })
1740
1755
  : null
1741
- promptText = promptText.split('{TASK_COMPLETION_REPORT}').join(summary ? summary.report : '(无法计算完成度:变更目录缺失)')
1756
+ substituteRaw('{TASK_COMPLETION_REPORT}', summary ? summary.report : '(无法计算完成度:变更目录缺失)')
1742
1757
  } catch (e) {
1743
- promptText = promptText.split('{TASK_COMPLETION_REPORT}').join('(完成度计算异常:' + e.message + ',请手动核对 plan.md 与 .runtime/execute-runs/*/tasks/task-NN/review.json)')
1758
+ substituteRaw('{TASK_COMPLETION_REPORT}', '(完成度计算异常:' + e.message + ',请手动核对 plan.md 与 .runtime/execute-runs/*/tasks/task-NN/review.json)')
1744
1759
  }
1745
1760
  }
1746
1761
 
@@ -1781,7 +1796,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
1781
1796
  } catch (e) {
1782
1797
  baselineInfo = '(worktree 基线注入失败:' + e.message + '——diff 对账前先 git branch -a 查 sillyspec/<change> 分支并用 git merge-base <main> <branch> 自查基点,勿用主仓 HEAD 当基点)'
1783
1798
  }
1784
- promptText = promptText.split('{WORKTREE_BASELINE_INFO}').join(baselineInfo)
1799
+ substituteRaw('{WORKTREE_BASELINE_INFO}', baselineInfo)
1785
1800
  }
1786
1801
 
1787
1802
  // verify「运行测试和质量扫描」evidence-auto 推荐注入(task-12,FR-11 / D-005@v2):
@@ -1822,7 +1837,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
1822
1837
  } catch (e) {
1823
1838
  eaInjected = `(evidence-auto 推荐注入失败:${e.message}——请读 .sillyspec/local.yaml 确认 test_strategy;--done 时 CLI 仍会按 resolveTestStrategy 实测对账,推荐不可用时可显式设 test_strategy: full/module)`
1824
1839
  }
1825
- promptText = promptText.split('{EVIDENCE_AUTO_RECOMMENDATION}').join(eaInjected)
1840
+ substituteRaw('{EVIDENCE_AUTO_RECOMMENDATION}', eaInjected)
1826
1841
  }
1827
1842
 
1828
1843
  // {ARCHIVE_IMPACT_AUDIT}(2026-09-09 ql-20260909-004):archive extract-module-impact 步的
@@ -1842,7 +1857,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
1842
1857
  } catch (e) {
1843
1858
  auditBlock = '(三重核对机械预填失败:' + (e && e.message ? e.message : e) + '——回退手跑 git diff --name-only 比对)'
1844
1859
  }
1845
- promptText = promptText.split('{ARCHIVE_IMPACT_AUDIT}').join(auditBlock)
1860
+ substituteRaw('{ARCHIVE_IMPACT_AUDIT}', auditBlock)
1846
1861
  }
1847
1862
 
1848
1863
  // {SCOPE_AUDIT_TABLE}(2026-09-10-change-scope-audit task-05,FR-03):archive「确认归档」步的
@@ -1865,7 +1880,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
1865
1880
  } catch (e) {
1866
1881
  scopeTable = '(变更范围对账表注入失败:' + (e && e.message ? e.message : e) + '——回退手跑 sillyspec scope-audit --change <name> 查看后再确认归档,注入失败不阻断归档)'
1867
1882
  }
1868
- promptText = promptText.split('{SCOPE_AUDIT_TABLE}').join(scopeTable)
1883
+ substituteRaw('{SCOPE_AUDIT_TABLE}', scopeTable)
1869
1884
  }
1870
1885
 
1871
1886
  // {HANDOVER_SUMMARY}(2026-09-17-pass-cap-semantics task-04,FR-06 注入态):archive「确认
@@ -1901,7 +1916,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
1901
1916
  } catch (e) {
1902
1917
  handoverBlock = '(移交项清单注入失败:' + (e && e.message ? e.message : e) + '——回退读 changes/<变更名>/verify-facts.json 的 handover 段核对后再确认归档,注入失败不阻断归档)'
1903
1918
  }
1904
- promptText = promptText.split('{HANDOVER_SUMMARY}').join(handoverBlock)
1919
+ substituteRaw('{HANDOVER_SUMMARY}', handoverBlock)
1905
1920
  }
1906
1921
 
1907
1922
  // 注入模块上下文(brainstorm/plan/execute 阶段 + quick 首步「理解任务」——刀①:quick
@@ -798,13 +798,22 @@ export async function executeVerifyQualityScan({ cwd, specBase, changeName, plat
798
798
  storeQualityScan({ specBase, cwd, changeName, testResult: testCheck, lintResult: lintCheck, smokeResult, coverageCheck, usedSnapshot: Boolean(snap), missReason: _missReasonParts.join(';') || null, scopeDecision: { planned: !process.env.SILLYSPEC_VERIFY_GATE_SNAPSHOT_OFF, actual: Boolean(snap) } })
799
799
  const testFailed = testCheck.status === 'failed'
800
800
  const lintBlocked = shouldBlockVerifyLint(lintCheck)
801
+ // 2026-10-15-lint-triage-path-unify:lint 归属鉴定降档接线(与 gates --done 路径同源共享
802
+ // helper)——零交集存量债降档 advisory 不 throw;owned/unattributable/鉴定异常维持硬拦
803
+ // (保守)。仅在「lint 单独失败」时降档(testFailed 优先 throw——测试失败不作 lint 降档
804
+ // 陪葬判断)。原两路径分叉实证见消费仓坑档案 verify-lint-triage-path-divergence.md。
805
+ let lintAdvisoryDowngraded = false
806
+ if (lintBlocked && !testFailed) {
807
+ const { lintOwnershipDowngrade } = await import('./lint-ownership.js')
808
+ lintAdvisoryDowngraded = await lintOwnershipDowngrade({ lintCheck, cwd, changeName, specBase })
809
+ }
801
810
  // 摩擦计数(friction-signal-hint task-03):记录条件与 throw 条件**刻意解耦**——advisory 档
802
811
  // lint 失败(SILLYSPEC_VERIFY_LINT_GATE=advisory 时不 throw)同样是摩擦信号,计数不应随
803
812
  // 逃生阀漂移。recordFrictionEvent 自带静默降级,不影响本步推进/throw 语义。
804
813
  if (testFailed || lintCheck.status === 'failed') {
805
814
  await recordFrictionEvent({ cwd, changeName, platformOpts, type: 'verify_run_failed', detail: testFailed ? 'test' : 'lint' })
806
815
  }
807
- if (testFailed || lintBlocked) {
816
+ if (testFailed || (lintBlocked && !lintAdvisoryDowngraded)) {
808
817
  if (snap) {
809
818
  try { const { printSnapshotFailureHint } = await import('./gate-snapshot.js')
810
819
  printSnapshotFailureHint(snap) } catch { /* 提示失败不影响阻断 */ }
package/src/spec-sync.js CHANGED
@@ -19,6 +19,7 @@ import { createHash } from 'crypto';
19
19
  import { existsSync, readdirSync, readFileSync, statSync, mkdirSync, writeFileSync, unlinkSync } from 'fs';
20
20
  import { join, relative, sep } from 'path';
21
21
  import { bindSyncNoiseRoot, syncConnectionWarn, isConnectionClassStatus, noteSyncSuccess } from './sync-noise.js';
22
+ import { writeAtomicSync } from './fs-atomic.js';
22
23
 
23
24
  // 与 sillyhub-daemon/src/spec-sync.ts 共用排除口径(task-07 / FR-06 / D-008@v2):
24
25
  // .runtime(有点)/ runtime(无点)/ projects(本地环境文件)在顶层排除;
@@ -63,6 +64,61 @@ export function describeSyncError(err) {
63
64
  return err && err.message ? err.message : String(err);
64
65
  }
65
66
 
67
+ // ── 同步失败连续计数与响应体摘要(2026-10-16 生产索引损坏事故可观测性加固)──
68
+ // 事故:平台 422 业务错误只打状态码(真因 HTTP_422_SPEC_BUNDLE_INVALID+路径要手动重放请求
69
+ // 才拿到),且失败走「下次自动重试」无限静默循环——归档半态(本地已归档、平台仍「进行中」)
70
+ // 挂数小时无人知晓。两道护栏:①非 2xx 响应体解析进告警行;②按 label 连续失败计数持久化,
71
+ // 连续 ≥3 次升级 🚨 显式告警,任一次成功清零。
72
+ const FAIL_STREAK_FILE = join('.runtime', 'spec-sync-fail-streak.json');
73
+ const FAIL_STREAK_ESCALATE_AT = 3;
74
+
75
+ export function describeErrorBody(status, rawBody) {
76
+ if (!rawBody) return '';
77
+ try {
78
+ const j = JSON.parse(rawBody);
79
+ const code = j.code || `HTTP_${status}`;
80
+ const p = j.details && j.details.path ? ` path=${j.details.path}` : '';
81
+ return j.message ? `${code}${p}: ${j.message}` : rawBody.slice(0, 200);
82
+ } catch {
83
+ return rawBody.slice(0, 200);
84
+ }
85
+ }
86
+
87
+ export function readFailStreak(specRoot) {
88
+ try {
89
+ return JSON.parse(readFileSync(join(specRoot, FAIL_STREAK_FILE), 'utf8')) || {};
90
+ } catch {
91
+ return {};
92
+ }
93
+ }
94
+
95
+ export function bumpFailStreak(specRoot, label, detail) {
96
+ const all = readFailStreak(specRoot);
97
+ const cur = all[label] || { count: 0 };
98
+ cur.count += 1;
99
+ cur.lastAt = new Date().toISOString();
100
+ if (detail) cur.lastError = String(detail).slice(0, 300);
101
+ all[label] = cur;
102
+ try {
103
+ mkdirSync(join(specRoot, '.runtime'), { recursive: true });
104
+ writeAtomicSync(join(specRoot, FAIL_STREAK_FILE), JSON.stringify(all, null, 2) + '\n');
105
+ } catch {
106
+ // fail-soft:计数落盘失败只影响告警升级,不阻断同步主流程
107
+ }
108
+ return cur.count;
109
+ }
110
+
111
+ export function clearFailStreak(specRoot, label) {
112
+ const all = readFailStreak(specRoot);
113
+ if (!all[label]) return;
114
+ delete all[label];
115
+ try {
116
+ writeAtomicSync(join(specRoot, FAIL_STREAK_FILE), JSON.stringify(all, null, 2) + '\n');
117
+ } catch {
118
+ // fail-soft:残留计数只是少清一次,下次成功再清
119
+ }
120
+ }
121
+
66
122
  function _hashBuffer(buf) {
67
123
  return createHash('sha256').update(buf).digest('hex');
68
124
  }
@@ -640,16 +696,26 @@ export async function syncSpecTree(specRoot, platform, changeName, opts = {}) {
640
696
  // 8 分钟才被后续步骤的同步补上)。失败可见、成功不打扰。
641
697
  // 连接类失败(404=端点未就绪 / 5xx)走噪音闸(sync-noise.js):平台未就绪期每步
642
698
  // 自动同步重刷同一行,淹没门禁输出;其余状态码(4xx 业务态)每轮该看见。
643
- const line = `[spec-sync] 拉取清单失败 HTTP ${res.status}(文件树本次未同步,下次自动重试): ${logLabel}`;
699
+ const raw = await res.text().catch(() => '');
700
+ const detail = describeErrorBody(res.status, raw);
701
+ const failCount = bumpFailStreak(specRoot, logLabel, detail);
702
+ const line = `[spec-sync] 拉取清单失败 HTTP ${res.status}(文件树本次未同步,下次自动重试): ${logLabel}${detail ? ` —— ${detail}` : ''}`;
644
703
  if (isConnectionClassStatus(res.status)) syncConnectionWarn(line);
645
704
  else console.warn(line);
705
+ if (failCount >= FAIL_STREAK_ESCALATE_AT) {
706
+ console.warn(`🚨 [spec-sync] 已连续 ${failCount} 次同步失败——平台文件树可能持续落后(变更中心或仍显示「进行中」/旧内容): ${logLabel}`);
707
+ }
646
708
  return { synced: 0 };
647
709
  }
648
710
  const body = await res.json().catch(() => ({}));
649
711
  serverManifest = body.files || {};
650
712
  } catch (err) {
713
+ const failCount = bumpFailStreak(specRoot, logLabel, describeSyncError(err));
651
714
  const line = `[spec-sync] 拉取清单异常(文件树本次未同步,下次自动重试): ${logLabel}: ${describeSyncError(err)}`;
652
715
  syncConnectionWarn(line); // 网络/超时/熔断均连接类
716
+ if (failCount >= FAIL_STREAK_ESCALATE_AT) {
717
+ console.warn(`🚨 [spec-sync] 已连续 ${failCount} 次同步失败——平台文件树可能持续落后(变更中心或仍显示「进行中」/旧内容): ${logLabel}`);
718
+ }
653
719
  return { synced: 0 };
654
720
  }
655
721
 
@@ -691,6 +757,7 @@ export async function syncSpecTree(specRoot, platform, changeName, opts = {}) {
691
757
  if (ops.length === 0) {
692
758
  debugLog(`[spec-sync] 无差异,跳过同步: ${logLabel}`);
693
759
  clearStaleTombstoneMarkers(specRoot);
760
+ clearFailStreak(specRoot, logLabel);
694
761
  return { synced: 0 };
695
762
  }
696
763
 
@@ -706,9 +773,15 @@ export async function syncSpecTree(specRoot, platform, changeName, opts = {}) {
706
773
  signal: withSignal(30000),
707
774
  });
708
775
  if (!res.ok) {
709
- const line = `[spec-sync] 同步请求失败 HTTP ${res.status}(文件树本次未同步,下次自动重试): ${logLabel}`;
776
+ const raw = await res.text().catch(() => '');
777
+ const detail = describeErrorBody(res.status, raw);
778
+ const failCount = bumpFailStreak(specRoot, logLabel, detail);
779
+ const line = `[spec-sync] 同步请求失败 HTTP ${res.status}(文件树本次未同步,下次自动重试): ${logLabel}${detail ? ` —— ${detail}` : ''}`;
710
780
  if (isConnectionClassStatus(res.status)) syncConnectionWarn(line);
711
781
  else console.warn(line);
782
+ if (failCount >= FAIL_STREAK_ESCALATE_AT) {
783
+ console.warn(`🚨 [spec-sync] 已连续 ${failCount} 次同步失败——平台文件树可能持续落后(变更中心或仍显示「进行中」/旧内容): ${logLabel}`);
784
+ }
712
785
  return { synced: 0 };
713
786
  }
714
787
  const body = await res.json().catch(() => ({}));
@@ -780,6 +853,7 @@ export async function syncSpecTree(specRoot, platform, changeName, opts = {}) {
780
853
  writeBaseSnapshot(specRoot, localFiles);
781
854
  clearSpecConflictMarker(specRoot, conflictLabel);
782
855
  noteSyncSuccess();
856
+ clearFailStreak(specRoot, logLabel);
783
857
  console.log(`[spec-sync] 冲突已自动消解(0 个需人工裁决;本会话改动 ${ops.length - followers.length > 0 ? '已同步' : '无差异'}): ${conflictLabel}`);
784
858
  return { synced: Math.max(ops.length - followers.length, 0), conflict: false, autoResolved: followers.length };
785
859
  }
@@ -811,6 +885,7 @@ export async function syncSpecTree(specRoot, platform, changeName, opts = {}) {
811
885
  return { synced: 0, conflict: true, serverVersions: real, autoResolved: followers.length, conflictPath };
812
886
  }
813
887
  noteSyncSuccess();
888
+ clearFailStreak(specRoot, logLabel);
814
889
  console.log(`[spec-sync] 已同步 ${ops.length} 个文件变更: ${logLabel}`);
815
890
  // 本轮无冲突 = 此前的未决 spec 树冲突已不存在,清残留标记(否则 status 永久红标、
816
891
  // 横幅去重单行提示不消——坑 spec-sync-conflict-banner-spam)。全树入口
@@ -828,8 +903,12 @@ export async function syncSpecTree(specRoot, platform, changeName, opts = {}) {
828
903
  writeBaseSnapshot(specRoot, localFiles);
829
904
  return { synced: ops.length };
830
905
  } catch (err) {
906
+ const failCount = bumpFailStreak(specRoot, logLabel, describeSyncError(err));
831
907
  const line = `[spec-sync] 同步异常(文件树本次未同步,下次自动重试): ${logLabel}: ${describeSyncError(err)}`;
832
908
  syncConnectionWarn(line); // 网络/超时/熔断均连接类
909
+ if (failCount >= FAIL_STREAK_ESCALATE_AT) {
910
+ console.warn(`🚨 [spec-sync] 已连续 ${failCount} 次同步失败——平台文件树可能持续落后(变更中心或仍显示「进行中」/旧内容): ${logLabel}`);
911
+ }
833
912
  return { synced: 0 };
834
913
  }
835
914
  }
@@ -139,7 +139,8 @@ export function renderReviewJsonContract({ stage, changeDir, reviewRunId, tier,
139
139
  '',
140
140
  '> 路径:`{SPEC_ROOT}/.runtime/stage-reviews/' + stageLbl + '-' + runIdLbl + '/review.json`(本次 reviewType=' + reviewType + ',主审查文档=' + mainDocRel + ')',
141
141
  ...(changeDir
142
- ? ['> **本次完整绝对路径(直接复制给审查子代理,勿手拼 runId/漏阶段前缀——坑 stage-review-path-prefix 2026-10-10 实证:marker 只有裸 runId,子代理按 runId 直写漏「<stage>-」前缀即错位)**: `' + join(dirname(dirname(changeDir)), '.runtime', 'stage-reviews', stageLbl + '-' + runIdLbl, 'review.json') + '`']
142
+ ? ['> **本次完整绝对路径(直接复制给审查子代理——勿手拼、勿增删任何前缀)**: `' + join(dirname(dirname(changeDir)), '.runtime', 'stage-reviews', stageLbl + '-' + runIdLbl, 'review.json') + '`',
143
+ '> ⚠️ 坑 stage-review-path-prefix 双向实测:漏「<stage>-」前缀即错位(2026-10-10:marker 只有裸 runId,子代理按 runId 直写);**多加一层「review-」前缀同样错位**(2026-10-15 复发:手拼成 `<stage>-review-review-…`,空目录残渣+一轮纠正)。唯一可靠做法:整条路径直接粘贴。']
143
144
  : []),
144
145
  '',
145
146
  '### 必填字段(validateStageReviewSchema 硬校验,缺任一即 schema 失败阻断)',
@@ -89,7 +89,9 @@ sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句中文概括(
89
89
  prompt: `### ⚡ 轻量跑判定(P0-3,2026-09-20 对撞实验驱动——需求已收敛来源走全仪探索是纯税)
90
90
  **需求来源已收敛**(满足任一:--input 显式声明承接/已澄清/方案已定;fork 或关联会话已多轮澄清过本需求,你已能不问自答全部 P0 歧义;任务书/评审结论直接给定方案与边界)→ 本步可**轻量跑**:A-D 节按已收敛事实直书结论(歧义零清单、范围直裁、决策若已有则引用源会话不重问),一次性完成不再多轮追问。轻量跑不降质量底线:范围结论与决策落盘照常,仅省「明知故问」的交互轮。Grill(Step 7)与规范文件(Step 8)**不适用轻量跑、永不省**。
91
91
 
92
- 通过对话探索需求细节、分析原型(如有)、判断拆分/批量、并对歧义点做需求澄清 Grill。本步骤合并了原有的对话探索、原型分析、需求范围评估、需求澄清 Grill 四个环节——按下方 A→D 顺序按需执行。提问遵守「一次只问一个」(见 D 节追问策略):必须问的问题逐个问,能查代码/文档解决的不问;不要把多个歧义打包成一屏清单一次抛给用户(用户记不住、答不全,反而漏掉关键约束);也不要为凑数把单个问题拖成多轮。
92
+ 通过对话探索需求细节、分析原型(如有)、判断拆分/批量、并对歧义点做需求澄清 Grill。本步骤合并了原有的对话探索、原型分析、需求范围评估、需求澄清 Grill 四个环节——按下方 A→D 顺序按需执行。提问遵守「一次只问一个」(见 D 节追问策略——分层口径:P0 逐轮逐问、P1/P2 能自定的自定并标注、只有会改变产物的攒收尾一轮):必须问的问题逐个问,能查代码/文档解决的不问;不要把多个歧义打包成一屏清单一次抛给用户(用户记不住、答不全,反而漏掉关键约束);也不要为凑数把单个问题拖成多轮。
93
+
94
+ **规模重估(对话中如发现该升级,当场说)**:若对话或方案讨论中发现本次变更实际需要 Wave 并行编排、上下文分片或多阶段治理——超出当前档位(例:早期粗判的轻量面变成跨模块改造)——必须**当场向用户说明并给出升级建议**(如改走完整五阶段 / 带 \`--upgrade-thick\`),不静默按原档继续。档位仍由 CLI 客观定价(三轴)落值,你只能建议、不能自行升降档。
93
95
 
94
96
  ### A. 原型/设计图分析(如用户提供)
95
97
  如果用户提供了截图、图片或 HTML 原型,先分析提取:
@@ -106,8 +108,18 @@ sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句中文概括(
106
108
  2. **提出问题后必须暂停等待用户回答**,不要替用户回答
107
109
  3. 根据用户回答判断:信息够了 → 进入 C/D 或正常完成 / 需要追问 → 暂停等待下一个回答
108
110
  4. 探索顺序(按需):目的 → 约束 → 边界 → 成功标准
109
- 5. 多选题优于开放式问题
110
- 6. YAGNI — 砍掉不需要的功能
111
+ 5. **先挖为什么**:不只问「要什么」,要问出「为什么现在做、不做会怎样」(动机是后续替你补全没提的那一百个决定的依据);到本步出口仍说不清动机 → 不许进入「提出 2-3 种方案」,先把动机问出来
112
+ 6. **实现方式的选择权归谁**:技术栈/平台/语言/库/存储介质这类「怎么做」的选择,问清哪些是用户有强意见要谈的、哪些是交给你选的;你选的按决策记录规则落盘并把 answer 标注「我的判断」(不是用户同意过的)
113
+ 7. 多选题优于开放式问题;被问「你觉得呢/先猜一个」时给一个具体方案并标「我的判断」,不要空转追问
114
+ 8. YAGNI — 砍掉不需要的功能
115
+
116
+ ### B2. 分块回放(Playback——边理解边复述,与追问交替)
117
+ 每理解到一块就先回放一块,别把回放攒到最后:
118
+ - 回放块 200-300 字,复述「我听到的是」——**凡是猜测都标为猜测,只有用户说过的才写成用户说的**
119
+ - 回放块结尾固定问一句:「哪里不对或漏了什么?」
120
+ - 回放与追问交替进行直到对话结束:回放 → 用户纠正 → 下一块追问 → 下一块回放;最后一块覆盖剩余部分
121
+ - 再次进入本步(\`--continue --answer\` 后)时,回放只复述新增/被纠正的理解,已确认且未被纠正的部分不重复回放(防上下文膨胀)
122
+ - 回放块放「**回放:**」分节、追问放「**追问:**」分节——两者分节标注,用户可分别纠正(回答的是上一轮追问时不影响回放块的语义)
111
123
 
112
124
  ### C. 需求范围评估(拆分/批量判断)
113
125
  1. 根据分析结果判断复杂度。满足以下任意 2 条建议拆分:
@@ -155,19 +167,22 @@ sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句中文概括(
155
167
  - P1:影响用户场景、验收标准、错误处理、默认值
156
168
  - P2:文案、展示细节、低风险交互偏好
157
169
 
158
- **执行规则**:
170
+ **执行规则(分层——2026-10-15-brainstorm-interaction-refine FR-03)**:
159
171
  - P1/P2 歧义 0-2 个且无 P0:不进入 Grill 追问,在后续设计中内联处理并记录依据
160
- - P1/P2 歧义 >= 3 个:按优先级逐个澄清
161
- - 任意 P0 歧义:必须澄清;如果需要用户判断,暂停问一个问题
172
+ - P1/P2 歧义 >= 3 个:不逐个问——agent 能自行决定的**自行决定**(写进 decisions.md,answer 标注「我的判断」),剩下的攒成收尾一轮;只有「只有用户能答且答案会改变产物」的才问
173
+ - 任意 P0 歧义:必须澄清;逐轮逐问(一次只问一个),深度优先
174
+ - **收尾一轮(一次性问完)**:把「只有用户能答且答案会改变产物」的 P1/P2 项(含 Design Grill 回收的同类项)汇总成**一轮**问题,一次问完;问完即进入「提出 2-3 种方案」,不再追问、不做第二轮
175
+ - 可判定出口:进入「提出 2-3 种方案」前,P1/P2 未决项必须为 0,或已记录为「我的判断」落 decisions.md
162
176
  - 不要问用户"要不要 Grill"。由你根据歧义风险决定;只在需要业务判断/取舍时等待用户回答
163
177
 
164
- **追问策略**(进入澄清时):
165
- 1. **一次只问一个问题**:按 P0 → P1 → P2 顺序,深度优先处理最关键歧义
166
- 2. **能查代码就不问**:问题可由源码、scan 文档、模块文档回答时,先查证并给出结论
167
- 3. **术语碰撞立即指出**:用户用词与 glossary/代码实体/模块文档冲突时,当场说明冲突并要求选择 canonical term
168
- 4. **模糊词精化**:把"账户/任务/状态/会话/执行"这类多义词拆成明确实体或状态
169
- 5. **场景压力测试**:用具体 case 逼出边界(失败重试、部分成功、历史数据、权限不足、并发修改、兼容旧配置)
170
- 6. **前提挑战优先**:如果现有设计或代码已有简单路径,先说明"可能不该新增"
178
+ **追问策略**(进入澄清时,分层口径同上):
179
+ 1. **P0 逐轮逐问**:P0 一次只问一个,深度优先
180
+ 2. **P1/P2 能自定就自定**:能由源码/scan 文档/模块文档/既有决策确定,或能凭工程判断定且不改变产物的,直接定;按决策记录规则落 decisions.md 并在 answer 标注「我的判断」;只有真正需要用户判断/取舍的才等用户
181
+ 3. **能查代码就不问**:问题可由源码、scan 文档、模块文档回答时,先查证并给出结论
182
+ 4. **术语碰撞立即指出**:用户用词与 glossary/代码实体/模块文档冲突时,当场说明冲突并要求选择 canonical term
183
+ 5. **模糊词精化**:把"账户/任务/状态/会话/执行"这类多义词拆成明确实体或状态
184
+ 6. **场景压力测试**:用具体 case 逼出边界(失败重试、部分成功、历史数据、权限不足、并发修改、兼容旧配置)
185
+ 7. **前提挑战优先**:如果现有设计或代码已有简单路径,先说明"可能不该新增"
171
186
 
172
187
  **决策记录(增量落盘,防会话中断丢失)**:每解决一个有实现影响的问题(不要把闲聊都记录进去),**当场追加**写入 \`{SPEC_ROOT}/changes/<change-name>/decisions.md\`——不要只在对话里留草稿等「写设计文档」步统一落盘:会话随时可能中断,未落盘的决策无法跨会话恢复。落盘规则:
173
188
 
@@ -193,19 +208,33 @@ sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句中文概括(
193
208
  \`\`\`
194
209
 
195
210
  - 幂等:写入前按 D-xxx@vN 查重,已存在的不重复追加;后续修正走新版本 D-001@v2 + supersedes(见 Design Grill 版本规则)
211
+ - **agent 自行决定的 P1/P2**(见执行规则分层):source 用 code(查证得来)或 docs(文档依据),answer 标注「我的判断:…」——不写成用户同意过的,用户事后可否决
212
+ - **「只有用户能答且改变产物」的项**:不落 pending 占位条目;先在收尾一轮问出答案,再按 source: user 落盘
196
213
 
197
214
  ### 铁律 — 不要自问自答 / 等待用户
198
215
  - 这是人机协作步骤:提出问题后必须暂停等用户回答,**不要在输出里模拟用户回答然后说"需求已明确"**(命令由 CLI 在下方注入)
199
216
  - 需要拆分或批量模式时:列出方案并暂停等用户确认
200
217
  - 不需要追问、无歧义、需求已清晰时:可正常完成本步骤(不必强行 --wait)。但「正常完成」的前提是**输出一份可否决的确认清单**——在输出里明确列出「已确认需求点清单」(逐条),让用户能一眼看到并喊停;未与用户确认过任何需求点、纯凭自身判断的场景,不得仅凭「我觉得清晰」直接放行(这是把默认共识伪装成共识的逃逸口)
218
+ - **确认清单逐条带来源标记(两态)**:每条需求点标 \`[用户]\`(用户原话/回答得来)或 \`[推断]\`(agent 推断)——\`[推断]\` 即待用户点头项;未与用户确认过的推断点**不得混入已确认清单**(要么在收尾一轮问掉转 \`[用户]\`,要么显式留在「待确认」区)
201
219
  - 2-3 轮问答即进入方案讨论;达到 maxWaitRounds=8 后必须总结已确认内容与剩余风险,不要无限追问
202
220
 
221
+ ### 自欺逃逸口对照表
222
+ 要输出「本步完成」「可以直接进方案讨论」之前,先对表自检一遍:
223
+
224
+ | 念头 | 现实 |
225
+ |---|---|
226
+ | "我觉得已经够清晰了" | 清晰不等于用户确认过——没确认过的点进清单必须标 \`[推断]\`,并留待收尾一轮问掉 |
227
+ | "这步这么简单,跳过吧" | 简单变复杂的路只有一条:跳过的步骤 |
228
+ | "用户说『听起来不错』" | 认可的是你展示过的东西——你没写出来的描述与你的推断,都不算批准 |
229
+
203
230
  ### 输出
204
231
  需求理解摘要(用户确认的需求点列表)+ 拆分/批量结论(如适用)+ 本步新增落盘的 D-xxx@vN 清单 + 剩余风险(如有)
205
232
 
233
+ 确认清单每条带来源标记 \`[用户]\` / \`[推断]\`;\`[推断]\` 项在收尾一轮问掉或显式留在「待确认」区——两者都不带 \`[用户]\` 标记时不得计入「已确认」。
234
+
206
235
  ### 注意
207
236
  - 第一次进入此步骤时,按 A→D 顺序处理;有原型先分析,再探索,再判规模,再澄清
208
- - 用户通过 \`--continue --answer "回答"\` 回答后,本步骤会再次执行,此时检查是否需要追问或可以结束;再次执行时输出只需增量汇报(新结论 + 新落盘决策),已落盘内容不要重复输出`,
237
+ - 用户通过 \`--continue --answer "回答"\` 回答后,本步骤会再次执行,此时检查是否需要追问或可以结束;再次执行时输出只需增量汇报(**新回放块 + 新追问 + 新落盘决策**),已确认且未被纠正的内容不重复回放、已落盘内容不重复输出`,
209
238
  outputHint: '需求理解与决策落盘清单',
210
239
  optional: false
211
240
  },
@@ -251,7 +280,8 @@ sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句中文概括(
251
280
  4. 分段设计含新机制时同上检索(固定动作):设计的分段内容引入新机制词(例词为举例非机制,不构成封闭清单)时,先跑 \`sillyspec knowledge search --query "<机制词>"\`,命中条目必读(尤其 status=rejected 的防复潮条目)再定稿设计
252
281
  5. 确认变更名(格式:\`YYYY-MM-DD-<简短描述>\`,例如 \`2026-05-13-user-auth\`——CLI 已强制:新建/改名不带日期前缀直接 exit 2)
253
282
  6. 按「HTML 原型分级」三选一必填:生成原型 / 登记复用既有原型 / 写出跳过原因(见下)
254
- 7. 暂停等待用户确认或修改意见
283
+ 7. 展示时附一节「**我替你做的决定**」(2026-10-15-brainstorm-interaction-refine FR-04):从 \`decisions.md\` 摘出 \`source\` 非 user 的条目(代码/文档查证得来的 + agent 自行决定的 P1/P2),逐条列 D-xxx@vN + 一句话决定 + 一句依据——让用户边读方案边核这些点,不要埋在决策文件里让他自己去翻;无此类条目时写「无(全部决定均来自你的选择)」
284
+ 8. 暂停等待用户确认或修改意见
255
285
 
256
286
  ### HTML 原型分级(按改动程度,不是"是否碰前端文件")
257
287
  判定对象是**改造后的界面相对现状的变化程度**:
@@ -272,10 +302,10 @@ sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句中文概括(
272
302
  - 纯后端逻辑/CLI/配置/文档,无任何界面变化
273
303
 
274
304
  ### 生成要求(定稿原型=真码产物,不是手绘图)
275
- 入口文件保存到 \`{SPEC_ROOT}/changes/<change-name>/prototype-<名称>.html\`(目录不存在则先创建;该命名是 execute 注入契约,不改)。真码五要素:
276
- - **与实现同方言**:用项目真实前端栈构建——组件库/设计 token/样式从项目源码或 node_modules 真实引入(例:React 项目挂真实组件而非仿制标签、Vue 项目用真实 SFC;例词非封闭清单),不手搓仿制样式
277
- - **可复跑**:能被本项目工具链跑起来(dev server / 组件文档页 / 构建产物均可,入口 HTML 从本地 node_modules 引真实库也行)——先查 scan 文档与现有前端代码就近发现项目原型管线,已有则复用,没有则搭最小入口并留在项目惯例位置
278
- - **高保真=组件即实现原料**:布局/组件/交互按真实效果呈现,execute 阶段直接对照搬组件,不经过"看图重新翻译"
305
+ 入口文件保存到 \`{SPEC_ROOT}/changes/<change-name>/prototype-<名称>.html\`(目录不存在则先创建;该命名是 execute 注入契约,不改)。**本仓 local.yaml ui_prototype 段登记的形态约定优先于本节默认阶梯**(form: real-code | single-html;豁免须带 ruling 用户裁决留痕;多仓变更按触达子仓 repos override 优先——方案对比步须知会注入本仓登记态)。真码五要素:
306
+ - **与实现同方言**:用项目真实前端栈构建——组件库/设计 token/样式从项目源码或 node_modules 真实引入(例:React 项目挂真实组件而非仿制标签、Vue 项目用真实 SFC;例词非封闭清单),不手搓仿制样式;**把 token 数值抄进自定义 CSS 变量=复刻≠引入,仍属手搓仿制**(2026-10-15 knowledge-search 实证误读)
307
+ - **可复跑**:能被本项目工具链跑起来(dev server / 组件文档页 / 构建产物均可,入口 HTML 从本地 node_modules 引真实库也行)——先查 scan 文档与现有前端代码就近发现项目原型管线,已有则复用;**历史原型先例同为单文件不构成管线存在**(51 个手绘先例是惯例债不是证据);查无真码管线时**先单开一个轻量变更搭最小入口**(bootstrap 与业务变更分离),本变更按登记形态或降级声明执行
308
+ - **高保真=组件即实现原料,不是「看起来像」**:布局/组件/交互按真实效果呈现,execute 阶段直接对照搬组件,不经过"看图重新翻译";**生成档输出必带素材来源声明**(真值 token 源文件路径 / 组件来源 / 入口位置——缺声明=无源可查,2026-10-15 server-logs-panel 实证零声明)
279
309
  - **手绘单文件(自造样式)仅限一次性粗选对比,不得作为定稿原型**(frontend-apple-style 实证:手绘定稿后每条意见都要反向翻译回真实代码);粗选定了方向,升级真码再收口
280
310
  - **例外与降级**:项目本身即单文件 HTML 方言(纯静态站)时单文件即真码,不适用手绘限定;无法同方言真码化(如目标平台无 HTML 运行面)时显式声明降级与理由请用户裁决留痕——静默降级会被收口「UI 视觉证据」探针拦下
281
311
 
@@ -284,7 +314,7 @@ sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句中文概括(
284
314
  - 原型生成、复用登记或跳过声明必须在暂停**之前**完成(复用登记=原型文件已按 \`prototype-<名称>.html\` 落在变更目录)——暂停是本步骤最后动作,用户确认后直接进入写 design.md,不会回头补
285
315
 
286
316
  ### 输出
287
- 完整设计方案 + 变更名 + 原型文件路径 或 复用声明(来源+覆盖面+登记后路径)或 跳过原因(三选一必填;跳过时写明分级依据,用户可当场否决要求补)
317
+ 完整设计方案 + 变更名 + 原型文件路径 或 复用声明(来源+覆盖面+登记后路径)或 跳过原因(三选一必填;跳过时写明分级依据,用户可当场否决要求补) + **「我替你做的决定」节**(source≠user 的 D-xxx@vN 逐条;无则写明)
288
318
 
289
319
  ### 注意
290
320
  - 不要一次输出大段文字,按模块/Phase 分段
@@ -478,6 +508,20 @@ ${REVIEW_CHECKLISTS.brainstorm.slice(3).map((item) => '- ' + item).join('\n')}
478
508
  - P0/P1 未决项必须进入 Unresolved Blockers,不能带着进入 plan。
479
509
  4. 用户回答后,更新 design.md 和 decisions.md;如果推翻旧决策,新增版本 D-xxx@v2,而不是覆盖 D-xxx@v1。
480
510
 
511
+ ### 实现者视角追问(builder check——与上方交叉审查并行执行,不替代它)
512
+ 交叉审查问「文档自身矛盾吗」;本节问「照这份文档,我能建出它吗」。两问正交,都要做(tier=self 时你依次自问两遍并分节输出):
513
+ - **tier=independent(S2/S3)**:把下列四要素追加进审查子代理的派发 prompt(作为同一子代理的第二项任务,不额外计请求预算):
514
+ - 你是一位有才华的 [领域] 工程师,被交予本设计文档,要据此制定实现计划,**你联系不到写文档的人**
515
+ - 只报告你「必须被回答、否则得靠猜、而猜错会出事」的问题;每条附「为什么答案会改变产物」,按重要性排序
516
+ - 跳过:文档已显式留给实现者的部分、你自己能合理决定的、措辞挑刺
517
+ - 没有重要问题 → 回答 \`nothing important left\`
518
+ - 写成 review.json 的 \`reviewerNotes\` 一节:\`## 实现者视角追问\` + 逐条问题与理由(**不新增 schema 字段**——去向钉在现有 reviewerNotes,Stage Review Gate 照常读)
519
+ - **tier=self(S0/S1)**:以实现者身份快读一遍 design.md,同样三个跳过条件,输出「实现者视角追问」小节(可空 = nothing important left)
520
+ - **回收分三堆**(两档都适用):
521
+ 1. **文档/对话已答** → 把答案补进 design.md 对应章节,不改决策
522
+ 2. **minor(不改变产物的猜测点)** → 你自己定,按决策记录格式落 decisions.md 并标注「我的判断」
523
+ 3. **只有用户能答且会改变产物** → 进「对话式探索」的收尾一轮(一次性问完,不做第二轮);无法回退到前一步时挂 Unresolved Blockers
524
+
481
525
  ### decisions.md 版本规则
482
526
  \`\`\`markdown
483
527
  ## D-001@v2: 缓存异常时的 fallback 语义
@@ -514,6 +558,12 @@ status: passed | needs-user-input | blocked | skipped
514
558
  | needs_thinking | N | 需要用户判断 |
515
559
  | unresolved | N | 真正设计漏洞 |
516
560
 
561
+ ## 实现者视角追问
562
+ (四要素口径;无重要问题写 "nothing important left")
563
+ | ID | 问题 | 为什么答案会改变产物 | 分堆(已答/我的判断/需用户) |
564
+ |---|---|---|---|
565
+ | B-001 | ... | ... | ... |
566
+
517
567
  ## Unresolved Blockers
518
568
  | ID | priority | 问题 | 阻塞原因 | 下一步 |
519
569
  |---|---|---|---|---|
@@ -521,6 +571,7 @@ status: passed | needs-user-input | blocked | skipped
521
571
 
522
572
  ### 铁律 — 等待用户
523
573
  - 发现 P0/P1 结构性矛盾且需要用户判断时,暂停等用户(命令由 CLI 在下方注入)
574
+ - 「只有用户能答且会改变产物」的问题(本步交叉审查 + 实现者视角追问合计)攒成**一轮**问完即止,不做第二轮
524
575
  - 用户显式跳过时必须在 decisions.md 记录 accepted risk;P0/P1 skip 仍必须写入 Unresolved Blockers
525
576
  - 完成前必须确认没有 P0/P1 unresolved blocker,否则不能进入 plan。`,
526
577
  outputHint: 'Design Grill 交叉审查结果',
@@ -88,9 +88,14 @@ export function parseAllowedPaths(content) {
88
88
  // 剥首尾成对反引号(坑7 家族:`src/a.js` 整项以反引号开头时 js-yaml/正则路径带 ` 匹配失败报未覆盖;
89
89
  // 与 change-list.js 的 cell 归一化同语义——剥两端包裹引号,不动路径内部字符)
90
90
  return blockMatch[1].match(/[ \t]*-[ \t]+(.+)/g)?.map(s => {
91
- const raw = s.replace(/^[ \t]*-[ \t]+/, '').trim()
91
+ let raw = s.replace(/^[ \t]*-[ \t]+/, '').trim()
92
+ // 双横线归一化(2026-10-15-zcode-engine-appserver 归档期实证:手编辑残留 "- - path"
93
+ // 嵌套子弹,剥一层外子弹后仍带 "- " 前缀 → 幽灵路径进 allow 面 → apply 越权误拦)。
94
+ // 循环剥除连续子弹前缀("- " / "- " 等任意空白形态)。
95
+ while (/^-[ \t]+/.test(raw)) raw = raw.replace(/^-+[ \t]+/, '').trim()
92
96
  return raw.replace(/^`([^`]*)`$/, '$1').replace(/^['"]|['"]$/g, '').trim()
93
- }).filter(Boolean) || []
97
+ }).filter(Boolean)
98
+ .filter((v, i, arr) => arr.indexOf(v) === i) // 去重(同卡重复条目不产生重复 allow 项)
94
99
  }
95
100
  return []
96
101
  }
@@ -2308,6 +2313,12 @@ async function executePlanPostcheckRun(context, { autoAdoptBudget = 0 } = {}) {
2308
2313
  const miGen = await generatePlanModuleImpactFirstVersion(changeDir, cwd, specRoot)
2309
2314
  if (miGen.status === 'generated') {
2310
2315
  console.log(` ✅ module-impact.md 首版已由 CLI 自动生成(归类 ${miGen.matchedCount} 个文件,未匹配 ${miGen.unmatchedCount} 个;影响类型列由 execute/verify 回填)`)
2316
+ } else if (miGen.status === 'skipped' && (miGen.reason === 'no-module-map' || miGen.reason === 'no-declared-files')) {
2317
+ // 坑 plan-module-impact-autogen-missing(2026-10-14 实证:指引说勿手写等 CLI 生成,
2318
+ // 无 module-map/无声明清单时静默 skip → gate 报缺失空转两轮)。非 small 变更在此显式
2319
+ // 告知生成前提缺失 + 逃生口径,agent 不再靠 gate 撞墙才知道。
2320
+ console.warn(` ⚠️ module-impact.md 首版未能自动生成(${miGen.reason === 'no-module-map' ? '本仓无 .sillyspec/docs/<项目>/modules/_module-map.yaml 模块图' : 'design.md 文件变更清单 main 段无声明文件'})——gate 仍要求该文件在场`)
2321
+ console.warn(' 逃生口径:按 gate 模板手写骨架(章节标题逐字「## 模块影响矩阵」「## 未匹配文件」+「## 更新结果」表骨架 pending,execute/verify 回填),或先跑 sillyspec run scan 建模块图')
2311
2322
  }
2312
2323
 
2313
2324
  // ── 1e. 阶段完成产物校验(plan stage contract)──
@@ -219,6 +219,9 @@ execution_mode: main # dispatch=子代理派发(判据见「执行模式声
219
219
  ## 验收
220
220
  - 具体可验证的验收条目
221
221
 
222
+ ## Review Focus 盲区清单(隐含面)
223
+ - (≤5 条——上限不是配额,写不出就停;按最可能坑到人排序。每条 = 规格隐含但没有任何任务测试覆盖的输入/条件 + 合理人预期行为 + 承接落点 task-XX;写不出显式写「已检查,无」——不留空、不编造;泛泛风险("并发需注意"类)不算行)
224
+
222
225
  ## 覆盖矩阵(如存在 decisions.md)
223
226
  | ID | 覆盖任务 | 验收证据 |
224
227
  |---|---|---|
@@ -236,6 +239,7 @@ light 计划的约束:
236
239
  - 如果存在 P0/P1 unresolved blocker,不生成 plan.md
237
240
  - 任务清单(tasks.md)控制在 10 条以内
238
241
  - 任务名使用 tasks.md checkbox 格式(\`- [ ] task-XX:\`),plan.md 不放任务行
242
+ - **比例自检**(2026-10-15-plan-review-focus):计划产物与 spec/design 相称——整段可粘贴代码体=转录不是计划;用签名、测试断言与规格精确值承载决策,为实现者保留判断空间
239
243
 
240
244
  ---
241
245
 
@@ -285,6 +289,15 @@ task-01 → task-03(最长路径,决定最短交付周期)
285
289
 
286
290
  > 验收结论不落在 plan.md(此段是验收清单,plan.md 不做执行态文件)——逐项核验结果由 verify 阶段写入 verify-result.md(PASS/FAIL 结论与证据);task 级验收对照 TaskCard frontmatter 的 acceptance 字段。
287
291
 
292
+ ## Review Focus 盲区清单(隐含面)
293
+ 规格/设计隐含、但没有任何任务测试覆盖的输入/失效模式(≤5 条——上限不是配额,写不出第 4 条就停;按最可能坑到人排序;每行 = 具体输入或条件 + 可判定预期行为 + 承接落点;泛泛风险分析不算行):
294
+
295
+ | # | 输入/条件 | 合理人预期行为 | 承接落点 |
296
+ |---|---|---|---|
297
+ | 1 | ... | ... | task-XX(acceptance/测试) |
298
+
299
+ (写不出 → 写「已检查,无」一行——不留空、不编造。)
300
+
288
301
  ## 覆盖矩阵(如存在 decisions.md)
289
302
  | ID | 覆盖任务 | 验收证据 |
290
303
  |---|---|---|
@@ -294,6 +307,7 @@ task-01 → task-03(最长路径,决定最短交付周期)
294
307
  full 计划的约束:
295
308
  - **禁止**估时(任务总表不含估时列)
296
309
  - **禁止**泛泛风险分析("需要充分测试"类废话转为具体验收条目)
310
+ - **比例自检**(2026-10-15-plan-review-focus):计划产物(plan.md + TaskCard implementation)与 spec/design 相称——整段可粘贴代码体=转录不是计划;用签名、测试断言与规格精确值承载决策,为实现者保留判断空间
297
311
  - Mermaid 依赖关系图**仅当依赖关系非平凡时生成**(线性依赖或全并行时不生成)
298
312
  - **Wave 段 ID 引用行必须保留**(\`- task-XX\` 纯 ID、无 checkbox 无任务名——机器解析依赖;任务名唯一真相在 tasks.md,plan.md 重抄即双写漂移)
299
313
  - plan.md 包含 Wave 分组 + 任务总表 + 关键路径 + 全局硬约束(design 有跨 task 硬约束时逐字抄录)+ 全局验收标准,**不放实现细节**
@@ -390,6 +404,11 @@ prompt 要点:
390
404
  6. 写通道降级:写操作持续被平台拒绝(session not in running turn 类)→ 重试 ≤3 次即停,完整结论(含 review.json 全文)作为最终文本回传主代理代落盘,reviewerNotes 首行留痕「代落盘:子代理写通道故障」;禁止长时间空转重试。
391
405
  7. **审查经济学三律(2026-09-20 对撞实验驱动,同 brainstorm Grill 步)**:①请求预算硬钳——子代理请求上限 **10 次**(材料包基准面 + plan.md + ≤3 次定向补证),到达即出 verdict 禁「再确认一下」续读,派发 prompt 明示此预算;②FAIL 后增量复审——修复阻断项后 **resume 同一子代理**只发修复说明+diff 验证「阻断项解决否」,不重开全文审(64s vs 322s = 5 倍差实证),review.json checklist 追加增量复核行;原 agent 不可 resume 才新开(prompt 附首轮阻断项仍只验阻断项);③**并行化**——派发用 run_in_background: true,主代理立即继续 Step 4(生成 TaskCard),回收在即将 --done 本步骤前(pass → 完成 / fail → 增量复审 / 在跑 → 等待),Stage Review Gate 照常硬校验;④**审查只读纪律**——子代理工具面只读(Read/Grep/只读 Bash),禁止 Edit/Write 被审产物(design/plan/四件套/源码),唯一可写 review.json,发现问题写 checklist/blockers 由主代理修(2026-09-20 实证 Grill 越权改 design 15 处;③并行化下同文件双写竞态的前置防线,详见 brainstorm Grill 步全文)。
392
406
 
407
+ ### 补充复查(2026-10-15-plan-review-focus)
408
+ 1. **Review Focus 承接复查**:plan.md「Review Focus 盲区清单(隐含面)」每行是否落到拥有该代码的 task 的 acceptance/测试;空节必须是显式「已检查,无」而非跳过或编造低价值行凑数(≤5 条是上限不是配额)。
409
+ 2. **比例自检复核**:plan.md 与 TaskCard implementation 与 spec/design 相称——整段可粘贴代码体=转录不是计划;签名+测试断言+规格精确值承载决策足够。
410
+ 3. **共享面预扫留表**:每对共享 target_files / provides-expects_from 契约链 / 依赖边的任务逐对核「产出 vs 消费」一致、每张卡自洽(测试所指=代码所指、创建文件=后文触碰文件),核验结果以**表格**写进审查输出(共享对两任务一行、单卡自洽一行)——"scan is clean" 而无表格行 = 没扫过;plan-postcheck 硬校验的是字段对齐,此处核语义矛盾。
411
+
393
412
  ### module-impact.md 首版(scale≠small 时:CLI 自动生成,审查步勿手写)
394
413
  module-impact.md 首版**由 CLI 在本阶段 --done 时自动生成**——文件×模块归属按 _module-map.yaml 前缀匹配机械预填,章节含「## 模块影响矩阵」「## 未匹配文件」「## 更新结果」表骨架(每受影响模块一行 pending),影响类型列留 <!--TODO--> 由 execute/verify 按实际 diff 回填。已存在不覆盖。手写整份首版是历史返工根源(章节标题变体会被 archive contains_sections 硬拦),**本步不要手写**。
395
414
  仅当 --done 报 module-impact 缺失时(如无 module-map 的绿地项目生成降级失败),按 gate 报错提示手写兜底:两章节标题逐字用「## 模块影响矩阵」「## 未匹配文件」(未命中的文件归「未匹配文件」章节),并含「## 更新结果」表骨架(行:\`modules/<id>.md\`、\`_module-map.yaml\`,状态列 pending/skipped,末列是状态列,勿追加其他列)。