sillyspec 3.32.9 → 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 (176) 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-flow/SKILL.md +1 -1
  5. package/.claude/skills/sillyspec-plan/SKILL.md +156 -156
  6. package/.github/workflows/ci.yml +58 -0
  7. package/.husky/pre-push +7 -6
  8. package/AGENTS.md +1 -1
  9. package/assets/command-cards/flow.md +1 -1
  10. package/eval/eval.config.json +3 -3
  11. package/eval/lib/config.mjs +6 -3
  12. package/eval/lib/util.mjs +16 -3
  13. package/eval/lib/workdir.mjs +4 -1
  14. package/eval/results/eval.db +0 -0
  15. package/eval/results/logs/2026-10-15T12-37-22-986Z-004-phase-stats-armA/prompt.txt +10 -0
  16. package/eval/results/logs/2026-10-15T12-37-22-986Z-004-phase-stats-armA/stderr.txt +0 -0
  17. package/eval/results/logs/2026-10-15T12-37-22-986Z-004-phase-stats-armA/stdout.txt +1 -0
  18. package/eval/results/logs/2026-10-15T12-45-37-124Z-004-phase-stats-armB/prompt.txt +10 -0
  19. package/eval/results/logs/2026-10-15T12-45-37-124Z-004-phase-stats-armB/stderr.txt +0 -0
  20. package/eval/results/logs/2026-10-15T12-45-37-124Z-004-phase-stats-armB/stdout.txt +1 -0
  21. package/eval/results/logs/2026-10-15T12-56-19-259Z-004-phase-stats-armA/prompt.txt +10 -0
  22. package/eval/results/logs/2026-10-15T12-56-19-259Z-004-phase-stats-armA/stderr.txt +0 -0
  23. package/eval/results/logs/2026-10-15T12-56-19-259Z-004-phase-stats-armA/stdout.txt +1 -0
  24. package/eval/results/logs/2026-10-15T13-00-05-625Z-004-phase-stats-armB/prompt.txt +10 -0
  25. package/eval/results/logs/2026-10-15T13-04-51-003Z-004-phase-stats-armA/prompt.txt +10 -0
  26. package/eval/results/logs/2026-10-15T13-04-51-003Z-004-phase-stats-armA/stderr.txt +0 -0
  27. package/eval/results/logs/2026-10-15T13-04-51-003Z-004-phase-stats-armA/stdout.txt +1 -0
  28. package/eval/results/logs/2026-10-15T13-06-25-271Z-004-phase-stats-armB/prompt.txt +10 -0
  29. package/eval/results/logs/2026-10-15T13-06-25-271Z-004-phase-stats-armB/stderr.txt +0 -0
  30. package/eval/results/logs/2026-10-15T13-06-25-271Z-004-phase-stats-armB/stdout.txt +1 -0
  31. package/eval/results/logs/2026-10-15T13-24-38-308Z-004-phase-stats-armB/prompt.txt +10 -0
  32. package/eval/results/logs/2026-10-15T13-24-38-308Z-004-phase-stats-armB/stderr.txt +0 -0
  33. package/eval/results/logs/2026-10-15T13-24-38-308Z-004-phase-stats-armB/stdout.txt +1 -0
  34. package/eval/results/logs/2026-10-15T14-50-01-950Z-005-orders-export-armA/prompt.txt +8 -0
  35. package/eval/results/logs/2026-10-15T14-50-01-950Z-005-orders-export-armA/stderr.txt +0 -0
  36. package/eval/results/logs/2026-10-15T14-50-01-950Z-005-orders-export-armA/stdout.txt +1 -0
  37. package/eval/results/logs/2026-10-15T14-51-56-316Z-005-orders-export-armB/prompt.txt +8 -0
  38. package/eval/results/logs/2026-10-15T14-51-56-316Z-005-orders-export-armB/stderr.txt +0 -0
  39. package/eval/results/logs/2026-10-15T14-51-56-316Z-005-orders-export-armB/stdout.txt +1 -0
  40. package/eval/results/workdirs/001-slugify__armA/package.json +6 -0
  41. package/eval/results/workdirs/001-slugify__armA/src/slugify.mjs +26 -0
  42. package/eval/results/workdirs/001-slugify__armB/package.json +6 -0
  43. package/eval/results/workdirs/001-slugify__armB/src/slugify.mjs +26 -0
  44. package/eval/results/workdirs/002-each-day__armA/package.json +6 -0
  45. package/eval/results/workdirs/002-each-day__armA/src/dates.mjs +30 -0
  46. package/eval/results/workdirs/002-each-day__armB/DRY_RUN_WRONG_ANSWER.mjs +1 -0
  47. package/eval/results/workdirs/002-each-day__armB/package.json +6 -0
  48. package/eval/results/workdirs/002-each-day__armB/src/dates.mjs +4 -0
  49. package/eval/results/workdirs/003-word-report__armA/bin/report.mjs +36 -0
  50. package/eval/results/workdirs/003-word-report__armA/data/notes.txt +5 -0
  51. package/eval/results/workdirs/003-word-report__armA/package.json +6 -0
  52. package/eval/results/workdirs/003-word-report__armB/bin/report.mjs +36 -0
  53. package/eval/results/workdirs/003-word-report__armB/data/notes.txt +5 -0
  54. package/eval/results/workdirs/003-word-report__armB/package.json +6 -0
  55. package/eval/results/workdirs/004-phase-stats__armA/data/events.log +16 -0
  56. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/flow.md +44 -0
  57. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-archive.md +27 -0
  58. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-brainstorm.md +34 -0
  59. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-execute.md +30 -0
  60. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-plan.md +28 -0
  61. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-verify.md +30 -0
  62. package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/status.md +28 -0
  63. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-archive/SKILL.md +87 -0
  64. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-auto/SKILL.md +54 -0
  65. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
  66. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-commit/SKILL.md +67 -0
  67. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-continue/SKILL.md +17 -0
  68. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
  69. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-execute/SKILL.md +222 -0
  70. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-explore/SKILL.md +107 -0
  71. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-export/SKILL.md +57 -0
  72. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-flow/SKILL.md +62 -0
  73. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-init/SKILL.md +177 -0
  74. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-knowledge/SKILL.md +40 -0
  75. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-plan/SKILL.md +156 -0
  76. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-propose/SKILL.md +16 -0
  77. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-quick/SKILL.md +116 -0
  78. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-resume/SKILL.md +21 -0
  79. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-scan/SKILL.md +79 -0
  80. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-state/SKILL.md +25 -0
  81. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-status/SKILL.md +54 -0
  82. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-verify/SKILL.md +87 -0
  83. package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-workspace/SKILL.md +33 -0
  84. package/eval/results/workdirs/004-phase-stats__armB/AGENTS.md +45 -0
  85. package/eval/results/workdirs/004-phase-stats__armB/CLAUDE.md +3 -0
  86. package/eval/results/workdirs/004-phase-stats__armB/data/events.log +16 -0
  87. package/eval/results/workdirs/run-001-slugify__armA/package.json +6 -0
  88. package/eval/results/workdirs/run-001-slugify__armA/src/slugify.mjs +26 -0
  89. package/eval/results/workdirs/run-001-slugify__armB/package.json +6 -0
  90. package/eval/results/workdirs/run-001-slugify__armB/src/slugify.mjs +26 -0
  91. package/eval/results/workdirs/run-002-each-day__armA/package.json +6 -0
  92. package/eval/results/workdirs/run-002-each-day__armA/src/dates.mjs +30 -0
  93. package/eval/results/workdirs/run-002-each-day__armB/DRY_RUN_WRONG_ANSWER.mjs +1 -0
  94. package/eval/results/workdirs/run-002-each-day__armB/package.json +6 -0
  95. package/eval/results/workdirs/run-002-each-day__armB/src/dates.mjs +4 -0
  96. package/eval/results/workdirs/run-003-word-report__armA/bin/report.mjs +36 -0
  97. package/eval/results/workdirs/run-003-word-report__armA/data/notes.txt +5 -0
  98. package/eval/results/workdirs/run-003-word-report__armA/package.json +6 -0
  99. package/eval/results/workdirs/run-003-word-report__armB/bin/report.mjs +36 -0
  100. package/eval/results/workdirs/run-003-word-report__armB/data/notes.txt +5 -0
  101. package/eval/results/workdirs/run-003-word-report__armB/package.json +6 -0
  102. package/eval/results/workdirs/run-004-phase-stats__armA/bin/phase-stats.mjs +37 -0
  103. package/eval/results/workdirs/run-004-phase-stats__armA/data/events.log +16 -0
  104. package/eval/results/workdirs/run-004-phase-stats__armB/DRY_RUN_WRONG_ANSWER.mjs +1 -0
  105. package/eval/results/workdirs/run-004-phase-stats__armB/data/events.log +16 -0
  106. package/eval/results/workdirs/run-005-orders-export__armA/bin/export-orders.mjs +57 -0
  107. package/eval/results/workdirs/run-005-orders-export__armA/data/.gitattributes +1 -0
  108. package/eval/results/workdirs/run-005-orders-export__armA/data/orders.csv +9 -0
  109. package/eval/results/workdirs/run-005-orders-export__armA/out/summary.json +1 -0
  110. package/eval/results/workdirs/run-005-orders-export__armB/bin/export-orders.mjs +57 -0
  111. package/eval/results/workdirs/run-005-orders-export__armB/data/.gitattributes +1 -0
  112. package/eval/results/workdirs/run-005-orders-export__armB/data/orders.csv +9 -0
  113. package/eval/results/workdirs/run-005-orders-export__armB/out/summary.json +1 -0
  114. package/eval/tasks/004-phase-stats/files/data/events.log +16 -0
  115. package/eval/tasks/004-phase-stats/solution/bin/phase-stats.mjs +37 -0
  116. package/eval/tasks/004-phase-stats/task.json +6 -0
  117. package/eval/tasks/004-phase-stats/verify.mjs +38 -0
  118. package/eval/tasks/005-orders-export/files/data/.gitattributes +1 -0
  119. package/eval/tasks/005-orders-export/files/data/orders.csv +9 -0
  120. package/eval/tasks/005-orders-export/solution/bin/export-orders.mjs +57 -0
  121. package/eval/tasks/005-orders-export/task.json +6 -0
  122. package/eval/tasks/005-orders-export/verify.mjs +28 -0
  123. package/package.json +1 -1
  124. package/quicklog/tmp-fix-review.mjs +21 -0
  125. package/src/ceremony-tier.js +2 -1
  126. package/src/commit-suggest.js +45 -4
  127. package/src/config-schema.js +14 -0
  128. package/src/decision-distill.js +106 -24
  129. package/src/decision-redomain.js +148 -0
  130. package/src/decision-renumber.js +157 -0
  131. package/src/design-facts.js +3 -3
  132. package/src/docs-check.js +30 -15
  133. package/src/docs-debt.js +6 -5
  134. package/src/doctor-diagnostics.js +3 -1
  135. package/src/flow-parity.js +28 -4
  136. package/src/flow.js +76 -13
  137. package/src/forensics.js +236 -0
  138. package/src/fr-index.js +17 -2
  139. package/src/hooks/worktree-guard.js +3 -3
  140. package/src/index.js +41 -6
  141. package/src/knowledge-digest.js +23 -6
  142. package/src/knowledge-graph.js +95 -18
  143. package/src/knowledge-match.js +34 -9
  144. package/src/knowledge-stats.js +12 -13
  145. package/src/knowledge-vector.js +98 -7
  146. package/src/machine-interface.js +26 -0
  147. package/src/module-impact.js +2 -5
  148. package/src/modules.js +31 -0
  149. package/src/quicklog.js +6 -1
  150. package/src/run/archive-distill.js +3 -0
  151. package/src/run/command.js +3 -3
  152. package/src/run/complete-handlers.js +8 -3
  153. package/src/run/complete.js +113 -44
  154. package/src/run/gates.js +100 -103
  155. package/src/run/lint-ownership.js +37 -0
  156. package/src/run/prompt.js +43 -28
  157. package/src/run/stage.js +1 -1
  158. package/src/run/verify-quality-scan.js +10 -1
  159. package/src/sentinel-assertions.js +43 -1
  160. package/src/spec-sync.js +81 -2
  161. package/src/stage-contract.js +26 -3
  162. package/src/stage-review.js +46 -12
  163. package/src/stage-templates.js +2 -0
  164. package/src/stages/brainstorm.js +76 -21
  165. package/src/stages/knowledge.js +56 -5
  166. package/src/stages/plan-postcheck.js +13 -2
  167. package/src/stages/plan.js +19 -0
  168. package/src/sync.js +28 -2
  169. package/src/task-tick.js +12 -0
  170. package/src/test-bindings.js +32 -3
  171. package/src/timeline.js +60 -19
  172. package/src/ui-visual.js +119 -4
  173. package/src/verify-postcheck.js +166 -18
  174. package/src/verify-probes.js +61 -18
  175. package/src/worktree-apply.js +26 -2
  176. package/templates/agents-instruction.md +1 -1
@@ -58,7 +58,7 @@ export const definition = {
58
58
  - **明显 small**(满足:单上下文可吞吐的纯执行类——改文案/修 bug/样式调整/配置微调,预计无 Wave 编排/上下文分片需求)→ 输出:「此变更规模较小,建议直接走轻量变更」+ 一句依据,给出可照抄建议命令(--input 引号内换行合法):
59
59
 
60
60
  \`\`\`
61
- sillyspec flow start --change <YYYY-MM-DD-名> --input "<动机与背景>
61
+ sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句中文概括(首行即变更标题);随后动机与背景
62
62
 
63
63
  成功标准:
64
64
  - <可验证标准>"
@@ -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
 
@@ -188,22 +203,38 @@ sillyspec flow start --change <YYYY-MM-DD-名> --input "<动机与背景>
188
203
  - evidence: <文件路径/代码位置/用户回答轮次>(源码引用写仓根相对全路径+行号,如 src/stage-contract.js:789——裸文件名在 docs-check 层1 靠 basename 全仓扫描找候选,找不到候选或关键词窗口不匹配即失效,到 pre-push 才拦,2026-09-19 实证 64 处返工)
189
204
  - 故障面: <本决策引入的新失败模式>(可选,type=architecture 时建议填写)
190
205
  - 退役判据: <出现什么信号时简化/删除本机制>(可选,type=architecture 时建议填写)
206
+ - 否决理由: <一句话核心劣势>(status=rejected 时必填——distill 入库前置要求,缺失则该条目不进知识库防复潮通道)
207
+ - 复潮条件: <何种信号下可重议本方案>(status=rejected 时必填)
191
208
  \`\`\`
192
209
 
193
210
  - 幂等:写入前按 D-xxx@vN 查重,已存在的不重复追加;后续修正走新版本 D-001@v2 + supersedes(见 Design Grill 版本规则)
211
+ - **agent 自行决定的 P1/P2**(见执行规则分层):source 用 code(查证得来)或 docs(文档依据),answer 标注「我的判断:…」——不写成用户同意过的,用户事后可否决
212
+ - **「只有用户能答且改变产物」的项**:不落 pending 占位条目;先在收尾一轮问出答案,再按 source: user 落盘
194
213
 
195
214
  ### 铁律 — 不要自问自答 / 等待用户
196
215
  - 这是人机协作步骤:提出问题后必须暂停等用户回答,**不要在输出里模拟用户回答然后说"需求已明确"**(命令由 CLI 在下方注入)
197
216
  - 需要拆分或批量模式时:列出方案并暂停等用户确认
198
217
  - 不需要追问、无歧义、需求已清晰时:可正常完成本步骤(不必强行 --wait)。但「正常完成」的前提是**输出一份可否决的确认清单**——在输出里明确列出「已确认需求点清单」(逐条),让用户能一眼看到并喊停;未与用户确认过任何需求点、纯凭自身判断的场景,不得仅凭「我觉得清晰」直接放行(这是把默认共识伪装成共识的逃逸口)
218
+ - **确认清单逐条带来源标记(两态)**:每条需求点标 \`[用户]\`(用户原话/回答得来)或 \`[推断]\`(agent 推断)——\`[推断]\` 即待用户点头项;未与用户确认过的推断点**不得混入已确认清单**(要么在收尾一轮问掉转 \`[用户]\`,要么显式留在「待确认」区)
199
219
  - 2-3 轮问答即进入方案讨论;达到 maxWaitRounds=8 后必须总结已确认内容与剩余风险,不要无限追问
200
220
 
221
+ ### 自欺逃逸口对照表
222
+ 要输出「本步完成」「可以直接进方案讨论」之前,先对表自检一遍:
223
+
224
+ | 念头 | 现实 |
225
+ |---|---|
226
+ | "我觉得已经够清晰了" | 清晰不等于用户确认过——没确认过的点进清单必须标 \`[推断]\`,并留待收尾一轮问掉 |
227
+ | "这步这么简单,跳过吧" | 简单变复杂的路只有一条:跳过的步骤 |
228
+ | "用户说『听起来不错』" | 认可的是你展示过的东西——你没写出来的描述与你的推断,都不算批准 |
229
+
201
230
  ### 输出
202
231
  需求理解摘要(用户确认的需求点列表)+ 拆分/批量结论(如适用)+ 本步新增落盘的 D-xxx@vN 清单 + 剩余风险(如有)
203
232
 
233
+ 确认清单每条带来源标记 \`[用户]\` / \`[推断]\`;\`[推断]\` 项在收尾一轮问掉或显式留在「待确认」区——两者都不带 \`[用户]\` 标记时不得计入「已确认」。
234
+
204
235
  ### 注意
205
236
  - 第一次进入此步骤时,按 A→D 顺序处理;有原型先分析,再探索,再判规模,再澄清
206
- - 用户通过 \`--continue --answer "回答"\` 回答后,本步骤会再次执行,此时检查是否需要追问或可以结束;再次执行时输出只需增量汇报(新结论 + 新落盘决策),已落盘内容不要重复输出`,
237
+ - 用户通过 \`--continue --answer "回答"\` 回答后,本步骤会再次执行,此时检查是否需要追问或可以结束;再次执行时输出只需增量汇报(**新回放块 + 新追问 + 新落盘决策**),已确认且未被纠正的内容不重复回放、已落盘内容不重复输出`,
207
238
  outputHint: '需求理解与决策落盘清单',
208
239
  optional: false
209
240
  },
@@ -249,7 +280,8 @@ sillyspec flow start --change <YYYY-MM-DD-名> --input "<动机与背景>
249
280
  4. 分段设计含新机制时同上检索(固定动作):设计的分段内容引入新机制词(例词为举例非机制,不构成封闭清单)时,先跑 \`sillyspec knowledge search --query "<机制词>"\`,命中条目必读(尤其 status=rejected 的防复潮条目)再定稿设计
250
281
  5. 确认变更名(格式:\`YYYY-MM-DD-<简短描述>\`,例如 \`2026-05-13-user-auth\`——CLI 已强制:新建/改名不带日期前缀直接 exit 2)
251
282
  6. 按「HTML 原型分级」三选一必填:生成原型 / 登记复用既有原型 / 写出跳过原因(见下)
252
- 7. 暂停等待用户确认或修改意见
283
+ 7. 展示时附一节「**我替你做的决定**」(2026-10-15-brainstorm-interaction-refine FR-04):从 \`decisions.md\` 摘出 \`source\` 非 user 的条目(代码/文档查证得来的 + agent 自行决定的 P1/P2),逐条列 D-xxx@vN + 一句话决定 + 一句依据——让用户边读方案边核这些点,不要埋在决策文件里让他自己去翻;无此类条目时写「无(全部决定均来自你的选择)」
284
+ 8. 暂停等待用户确认或修改意见
253
285
 
254
286
  ### HTML 原型分级(按改动程度,不是"是否碰前端文件")
255
287
  判定对象是**改造后的界面相对现状的变化程度**:
@@ -270,10 +302,10 @@ sillyspec flow start --change <YYYY-MM-DD-名> --input "<动机与背景>
270
302
  - 纯后端逻辑/CLI/配置/文档,无任何界面变化
271
303
 
272
304
  ### 生成要求(定稿原型=真码产物,不是手绘图)
273
- 入口文件保存到 \`{SPEC_ROOT}/changes/<change-name>/prototype-<名称>.html\`(目录不存在则先创建;该命名是 execute 注入契约,不改)。真码五要素:
274
- - **与实现同方言**:用项目真实前端栈构建——组件库/设计 token/样式从项目源码或 node_modules 真实引入(例:React 项目挂真实组件而非仿制标签、Vue 项目用真实 SFC;例词非封闭清单),不手搓仿制样式
275
- - **可复跑**:能被本项目工具链跑起来(dev server / 组件文档页 / 构建产物均可,入口 HTML 从本地 node_modules 引真实库也行)——先查 scan 文档与现有前端代码就近发现项目原型管线,已有则复用,没有则搭最小入口并留在项目惯例位置
276
- - **高保真=组件即实现原料**:布局/组件/交互按真实效果呈现,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 实证零声明)
277
309
  - **手绘单文件(自造样式)仅限一次性粗选对比,不得作为定稿原型**(frontend-apple-style 实证:手绘定稿后每条意见都要反向翻译回真实代码);粗选定了方向,升级真码再收口
278
310
  - **例外与降级**:项目本身即单文件 HTML 方言(纯静态站)时单文件即真码,不适用手绘限定;无法同方言真码化(如目标平台无 HTML 运行面)时显式声明降级与理由请用户裁决留痕——静默降级会被收口「UI 视觉证据」探针拦下
279
311
 
@@ -282,7 +314,7 @@ sillyspec flow start --change <YYYY-MM-DD-名> --input "<动机与背景>
282
314
  - 原型生成、复用登记或跳过声明必须在暂停**之前**完成(复用登记=原型文件已按 \`prototype-<名称>.html\` 落在变更目录)——暂停是本步骤最后动作,用户确认后直接进入写 design.md,不会回头补
283
315
 
284
316
  ### 输出
285
- 完整设计方案 + 变更名 + 原型文件路径 或 复用声明(来源+覆盖面+登记后路径)或 跳过原因(三选一必填;跳过时写明分级依据,用户可当场否决要求补)
317
+ 完整设计方案 + 变更名 + 原型文件路径 或 复用声明(来源+覆盖面+登记后路径)或 跳过原因(三选一必填;跳过时写明分级依据,用户可当场否决要求补) + **「我替你做的决定」节**(source≠user 的 D-xxx@vN 逐条;无则写明)
286
318
 
287
319
  ### 注意
288
320
  - 不要一次输出大段文字,按模块/Phase 分段
@@ -476,6 +508,20 @@ ${REVIEW_CHECKLISTS.brainstorm.slice(3).map((item) => '- ' + item).join('\n')}
476
508
  - P0/P1 未决项必须进入 Unresolved Blockers,不能带着进入 plan。
477
509
  4. 用户回答后,更新 design.md 和 decisions.md;如果推翻旧决策,新增版本 D-xxx@v2,而不是覆盖 D-xxx@v1。
478
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
+
479
525
  ### decisions.md 版本规则
480
526
  \`\`\`markdown
481
527
  ## D-001@v2: 缓存异常时的 fallback 语义
@@ -491,6 +537,8 @@ ${REVIEW_CHECKLISTS.brainstorm.slice(3).map((item) => '- ' + item).join('\n')}
491
537
  - evidence: design.md §3/§7, src/cache/...
492
538
  - 故障面: <本决策引入的新失败模式>(可选,type=architecture 时建议填写)
493
539
  - 退役判据: <出现什么信号时简化/删除本机制>(可选,type=architecture 时建议填写)
540
+ - 否决理由: <一句话核心劣势>(status=rejected 时必填——distill 入库前置要求,缺失则该条目不进知识库防复潮通道)
541
+ - 复潮条件: <何种信号下可重议本方案>(status=rejected 时必填)
494
542
  \`\`\`
495
543
 
496
544
  ### 输出格式
@@ -510,6 +558,12 @@ status: passed | needs-user-input | blocked | skipped
510
558
  | needs_thinking | N | 需要用户判断 |
511
559
  | unresolved | N | 真正设计漏洞 |
512
560
 
561
+ ## 实现者视角追问
562
+ (四要素口径;无重要问题写 "nothing important left")
563
+ | ID | 问题 | 为什么答案会改变产物 | 分堆(已答/我的判断/需用户) |
564
+ |---|---|---|---|
565
+ | B-001 | ... | ... | ... |
566
+
513
567
  ## Unresolved Blockers
514
568
  | ID | priority | 问题 | 阻塞原因 | 下一步 |
515
569
  |---|---|---|---|---|
@@ -517,6 +571,7 @@ status: passed | needs-user-input | blocked | skipped
517
571
 
518
572
  ### 铁律 — 等待用户
519
573
  - 发现 P0/P1 结构性矛盾且需要用户判断时,暂停等用户(命令由 CLI 在下方注入)
574
+ - 「只有用户能答且会改变产物」的问题(本步交叉审查 + 实现者视角追问合计)攒成**一轮**问完即止,不做第二轮
520
575
  - 用户显式跳过时必须在 decisions.md 记录 accepted risk;P0/P1 skip 仍必须写入 Unresolved Blockers
521
576
  - 完成前必须确认没有 P0/P1 unresolved blocker,否则不能进入 plan。`,
522
577
  outputHint: 'Design Grill 交叉审查结果',
@@ -110,8 +110,16 @@ export async function cmdSearch(dir, args, opts = {}) {
110
110
  // 复用 knowledge-match 引擎(三层 hybrid:路由→平台向量→本地词片,2026-09-29-knowledge-vector-recall)
111
111
  const result = await matchKnowledgeHybrid(knowledgeDir, query, { cwd: dir })
112
112
 
113
+ // 向量开关发现性提示(2026-10-15-vector-switch-discoverability):平台已连接+开关未开
114
+ // (缺省关)→ hint 键(vectorSwitchHint 单源,flow 注入零命中行同款文案);其余空串零输出。
115
+ let vectorHint = ''
116
+ try {
117
+ const { vectorSwitchHint } = await import('../knowledge-vector.js')
118
+ vectorHint = vectorSwitchHint(dir)
119
+ } catch { /* 提示 fail-open */ }
120
+
113
121
  if (!result.matched) {
114
- output(true, { query, matches: [] })
122
+ output(true, { query, matches: [], ...(vectorHint ? { hint: vectorHint } : {}) })
115
123
  return
116
124
  }
117
125
 
@@ -139,7 +147,7 @@ export async function cmdSearch(dir, args, opts = {}) {
139
147
  }
140
148
  })
141
149
 
142
- output(true, { query, matches })
150
+ output(true, { query, matches, ...(vectorHint ? { hint: vectorHint } : {}) })
143
151
  }
144
152
 
145
153
  // ── inspect ──
@@ -610,7 +618,8 @@ export async function cmdKnowledge(args, dir, opts = {}) {
610
618
  ' digest 治理信号卡(rot/伪域/坏绑定——纯读)',
611
619
  ' stats 覆盖率/死条目统计(纯读)',
612
620
  ' refresh 重扫 scan 文档重写 generated/ 区(**写盘**——先 validate 再用)',
613
- ' propose / classify / inbox / graph / stats',
621
+ ' propose / classify / inbox / graph / stats / renumber',
622
+ ' renumber [--dry-run|--apply] 存量决策号迁移为全局号(dry-run 先行;apply 写盘幂等)',
614
623
  '',
615
624
  '写盘面子命令(refresh/propose/classify)先跑同名只读命令确认现状;探索用法先看本帮助。',
616
625
  ]
@@ -693,11 +702,53 @@ export async function cmdKnowledge(args, dir, opts = {}) {
693
702
  }
694
703
  return
695
704
  }
705
+ case 'renumber': {
706
+ // 存量决策号一次性迁移(2026-10-14-decision-legacy-renumber):--dry-run 打印计划,
707
+ // --apply 执行重写(快照指纹校验 + 原子写;重跑幂等——原编号在场节段零触碰)。
708
+ const specBase = opts.specDir || join(dir, '.sillyspec')
709
+ const rest = args.slice(1)
710
+ const apply = rest.includes('--apply')
711
+ try {
712
+ const { planRenumber, applyRenumberPlan } = await import('../decision-renumber.js')
713
+ const plan = planRenumber(join(specBase, 'knowledge'))
714
+ const st = plan.stats
715
+ console.log(`🗺️ 决策号迁移计划:legacy ${st.legacySections} 节段|fanout ${st.fanoutGroups} 组(${st.fanoutCopies} 副本→单一身份)|发号 ${st.mintedNumbers}|无变更条目 ${st.noChangeEntries}(逐节独立发号)|epoch 零触碰 ${st.untouchedEpoch}`)
716
+ for (const [dom, n] of Object.entries(st.perDomainStart)) console.log(` 域 ${dom}:号段自 ${String(n).padStart(3, '0')} 起`)
717
+ if (!apply) {
718
+ console.log('(--dry-run 形态——加 --apply 执行重写;git 可回退)')
719
+ output(true, { subcommand: 'renumber', dryRun: true, ...st })
720
+ return
721
+ }
722
+ if (plan.files.every((f) => !f.ops || f.ops.length === 0)) {
723
+ console.log('✅ 无可迁移节段(幂等重跑——零变化)')
724
+ output(true, { subcommand: 'renumber', filesWritten: 0, ...st })
725
+ return
726
+ }
727
+ let filesWritten = 0
728
+ for (const fe of plan.files) {
729
+ if (!fe.ops || !fe.ops.length) continue
730
+ const p = join(specBase, 'knowledge', fe.file)
731
+ const cur = readFileSync(p, 'utf8')
732
+ if (cur !== fe.content) {
733
+ console.error(`❌ 快照漂移:${fe.file} 在计划后被并行修改——中止不覆盖(重跑重新计划)`)
734
+ process.exitCode = 1
735
+ return
736
+ }
737
+ writeAtomicSync(p, applyRenumberPlan(new Map([[fe.file, cur]]), plan).get(fe.file))
738
+ filesWritten++
739
+ }
740
+ console.log(`✅ 迁移完成:${filesWritten} 文件重写、${st.mintedNumbers} 号铸出(epoch 形态)——重跑 --apply 验证零变化;平台侧随后 platform sync 推送`)
741
+ output(true, { subcommand: 'renumber', filesWritten, ...st })
742
+ } catch (e) {
743
+ output(false, {}, { code: 'renumber_failed', message: String((e && e.message) || e) })
744
+ }
745
+ return
746
+ }
696
747
  default:
697
748
  output(false, {}, {
698
749
  code: 'unknown_subcommand',
699
- subcommand: subCommand,
700
- available: ['search', 'inspect', 'validate', 'refresh', 'propose', 'classify', 'inbox', 'stats', 'digest', 'graph'], // dump 随 graph 路由面(2026-10-09-graph-dump-layout)
750
+ subCommand: subCommand,
751
+ available: ['search', 'inspect', 'validate', 'refresh', 'propose', 'classify', 'inbox', 'stats', 'digest', 'graph', 'renumber'], // dump 随 graph 路由面(2026-10-09-graph-dump-layout)
701
752
  })
702
753
  }
703
754
  }
@@ -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,末列是状态列,勿追加其他列)。
package/src/sync.js CHANGED
@@ -273,6 +273,32 @@ function combineSignals(external, timeoutMs = REQUEST_TIMEOUT_MS) {
273
273
  return external ? AbortSignal.any([timeoutSignal, external]) : timeoutSignal;
274
274
  }
275
275
 
276
+ /**
277
+ * 非 2xx 回显折叠(坑 probe-noise-and-commit-suggest-quality 坑C,2026-10-15 消费仓实证:
278
+ * 409 冲突响应体含整段 platform_progress 大对象,text.slice(0,200) 密排回显挂在每条 CLI 命令
279
+ * 尾部刷屏)。结构化冲突体折叠为一行摘要(conflict 标识 + 关键时间戳/变更字段,不回显大
280
+ * 对象);其他 JSON 体打键名摘要;非 JSON/解析失败退回截断原文(既有形态零回归)。
281
+ */
282
+ export function formatSyncHttpErrorLine(method, url, status, text) {
283
+ const raw = String(text || '');
284
+ let body = null;
285
+ try { body = JSON.parse(raw); } catch { body = null; }
286
+ const m = method || 'GET';
287
+ if (body && typeof body === 'object') {
288
+ if (body.conflict === true) {
289
+ const parts = ['conflict'];
290
+ if (body.change) parts.push(`change=${body.change}`);
291
+ if (typeof body.last_pushed_at === 'string') parts.push(`last_pushed_at=${body.last_pushed_at}`);
292
+ if (body.base_ts !== undefined) parts.push(`base_ts=${body.base_ts}`);
293
+ if (body.reason) parts.push(`reason=${String(body.reason).slice(0, 80)}`);
294
+ return `[sync] ${m} ${url} → ${status} ${parts.join(' ')}(冲突响应体已折叠为一行)`;
295
+ }
296
+ const keys = Object.keys(body).slice(0, 6).join(',');
297
+ return `[sync] ${m} ${url} → ${status} ${keys ? `json(${keys})` : '(空响应体)'}`;
298
+ }
299
+ return `[sync] ${m} ${url} → ${status} ${raw.slice(0, 200)}`;
300
+ }
301
+
276
302
  async function fetchJson(url, options = {}) {
277
303
  const signal = combineSignals(options.signal);
278
304
  // noMute 是本模块的闸门开关不是 fetch 参数,先剥掉再透传
@@ -281,7 +307,7 @@ async function fetchJson(url, options = {}) {
281
307
  const res = await fetch(url, { ...init, signal });
282
308
  if (!res.ok) {
283
309
  const text = await res.text().catch(() => '');
284
- const line = `[sync] ${options.method || 'GET'} ${url} → ${res.status} ${text.slice(0, 200)}`;
310
+ const line = formatSyncHttpErrorLine(options.method, url, res.status, text);
285
311
  // 连接类失败(404=端点未就绪 / 5xx)走跨进程噪音闸:平台未就绪期每条命令的自动
286
312
  // 同步都撞同一批失败,重复刷屏淹没门禁输出(见 sync-noise.js 模块头)。409 冲突与
287
313
  // 其余 4xx 业务态每轮都该看见,不走闸。用户显式命令(connect health ping)传 noMute。
@@ -320,7 +346,7 @@ async function fetchJsonWithStatus(url, options = {}) {
320
346
  try { body = JSON.parse(text); } catch { body = null; }
321
347
  }
322
348
  if (!res.ok) {
323
- const line = `[sync] ${options.method || 'GET'} ${url} → ${res.status} ${text.slice(0, 200)}`;
349
+ const line = formatSyncHttpErrorLine(options.method, url, res.status, text);
324
350
  if (!noMute && isConnectionClassStatus(res.status)) syncConnectionWarn(line);
325
351
  else console.warn(line);
326
352
  }
package/src/task-tick.js CHANGED
@@ -145,6 +145,18 @@ export async function runTaskTick({ changeName, cwd, taskId, specBase, evidence
145
145
  ...(ev.text ? { evidence: ev.text.slice(0, 300) } : {}), // 事件面截 300 字符(全文在 task-evidence.jsonl)
146
146
  }) + '\n', 'utf8')
147
147
  } catch { /* 事件 best-effort:写失败翻格不受影响(节奏门退回采样判) */ }
148
+ // 侧车勾选留痕(2026-10-14-timeline-ticktime-anchor-scope):{ts,task,source} 追加进 change 目录
149
+ // 的 task-ticks.jsonl——勾选时刻的事实源(timeline 任务面精确时刻),落 change 目录随归档进 git
150
+ // 跨机可读,不随 .runtime 机本位/观测盲窗蒸发(2026-10-14-deploy-release-notes 实证:盲窗内
151
+ // 勾选两个记录面全空)。事件面不带任务 id(节奏门只读 checked N→M),id 口径以本侧车为权威。
152
+ // best-effort:写失败仅告警,翻格不受影响(timeline 退事件流推断)。
153
+ try {
154
+ appendFileSync(join(changesRoot, changeName, 'task-ticks.jsonl'), JSON.stringify({
155
+ ts: Date.now(), task: normalizeTaskId(taskId) || taskId, source: 'task-tick',
156
+ }) + '\n', 'utf8')
157
+ } catch (e) {
158
+ console.warn(`⚠️ 勾选留痕写失败(task-ticks.jsonl:${e && e.code ? e.code : e})——翻格不受影响,timeline 勾选时刻退事件流推断`)
159
+ }
148
160
  // 工件即时重推(2026-10-07-allticked-gate-docs-resync):翻格即任务面变更——顺带触发
149
161
  // spec-sync,平台不再停留 start 时点快照(真实会话实证:改写后平台恒显初稿)。best-effort。
150
162
  try {
@@ -18,7 +18,7 @@
18
18
  */
19
19
  import { createHash } from 'crypto'
20
20
  import { existsSync, readFileSync, readdirSync } from 'fs'
21
- import { join } from 'path'
21
+ import { dirname, join } from 'path'
22
22
  import { writeAtomicSync } from './fs-atomic.js'
23
23
  import { gitQuiet } from './git-helper.js'
24
24
  import { quicklogSidecarPath } from './quicklog.js'
@@ -193,9 +193,38 @@ export function readChangeTrace(changeDir) {
193
193
  } catch { return [] }
194
194
  }
195
195
 
196
- /** 写 trace(幂等:序列化字节不变跳写;原子:writeAtomicSync) */
196
+ /** 写 trace(幂等:序列化字节不变跳写;原子:writeAtomicSync)
197
+ *
198
+ * 幽灵绑定过滤(2026-10-15-zcode-engine-appserver 归档期实证,跨仓事故驱动修复):
199
+ * machine 行的 tests 条目若在任一候选根(变更目录上溯项目根 / process.cwd())都不存在——
200
+ * 多为卡片文本误提的不存在路径(如 verify 命令笔误被 deps 推断/绑定扫描捕获)——丢弃该
201
+ * 条目并 console.warn 留痕;agent 行不动(修理工/用户绑定如实保留);行内条目全被滤光则
202
+ * 保留原行不滤(交由 apply/verify 的悬空闸门裁决,不在写侧静默删行)。
203
+ */
197
204
  export function writeChangeTrace(changeDir, changeName, rows) {
198
- const normalized = rows.map(normalizeRow)
205
+ const projectRoots = (() => {
206
+ const cands = []
207
+ let d = changeDir
208
+ for (let i = 0; i < 3; i++) { d = dirname(d); cands.push(d.replace(/\\/g, '/')) }
209
+ cands.push(process.cwd().replace(/\\/g, '/'))
210
+ return [...new Set(cands)]
211
+ })()
212
+ const _existsSomewhere = (f) => projectRoots.some(root => existsSync(join(root, f)))
213
+ let _ghostDropped = []
214
+ const _filtered = rows.map(r => {
215
+ if (r.discovery === 'agent') return r
216
+ const files = (r.tests || []).map(t => testAnchorFile(t))
217
+ const ghosts = files.filter(f => !_existsSomewhere(f))
218
+ if (ghosts.length === 0) return r
219
+ const ghostKeys = new Set(ghosts)
220
+ _ghostDropped.push(...ghosts.map(g => `${r.row_id}: ${g}`))
221
+ const remaining = (r.tests || []).filter(t => !ghostKeys.has(testAnchorFile(t)))
222
+ return remaining.length > 0 ? { ...r, tests: remaining } : r
223
+ })
224
+ if (_ghostDropped.length > 0) {
225
+ console.warn('⚠️ [test-bindings] 幽灵绑定条目过滤:' + _ghostDropped.length + ' 个不存在的 tests 路径已从 machine 行丢弃(agent 行不动):\n ' + _ghostDropped.slice(0, 5).join('\n '))
226
+ }
227
+ const normalized = _filtered.map(normalizeRow)
199
228
  const payload = JSON.stringify({
200
229
  schemaVersion: 1, change: changeName, rows: normalized,
201
230
  }, null, 2) + '\n'