sillyspec 3.27.1 → 3.27.5

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 (224) hide show
  1. package/.claude/skills/sillyspec-execute/SKILL.md +8 -0
  2. package/.claude/skills/sillyspec-quick/SKILL.md +2 -1
  3. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-archive/SKILL.md +87 -0
  4. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-auto/SKILL.md +93 -0
  5. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
  6. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-commit/SKILL.md +67 -0
  7. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-continue/SKILL.md +47 -0
  8. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
  9. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-execute/SKILL.md +214 -0
  10. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-explore/SKILL.md +102 -0
  11. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-export/SKILL.md +57 -0
  12. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-init/SKILL.md +177 -0
  13. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
  14. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-plan/SKILL.md +156 -0
  15. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-quick/SKILL.md +116 -0
  16. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-resume/SKILL.md +70 -0
  17. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-scan/SKILL.md +79 -0
  18. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-state/SKILL.md +64 -0
  19. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-status/SKILL.md +54 -0
  20. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-verify/SKILL.md +87 -0
  21. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-workspace/SKILL.md +175 -0
  22. package/.claude/worktrees/sillyspec-data-model-research/.gitattributes +2 -0
  23. package/.claude/worktrees/sillyspec-data-model-research/.husky/pre-push +20 -0
  24. package/.claude/worktrees/sillyspec-data-model-research/README.md +203 -0
  25. package/.claude/worktrees/sillyspec-data-model-research/SKILL.md +91 -0
  26. package/.claude/worktrees/sillyspec-data-model-research/bin/sillyspec.js +2 -0
  27. package/.claude/worktrees/sillyspec-data-model-research/logo.jpg +0 -0
  28. package/.claude/worktrees/sillyspec-data-model-research/package-lock.json +977 -0
  29. package/.claude/worktrees/sillyspec-data-model-research/package.json +39 -0
  30. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -0
  31. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
  32. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/favicon.jpg +0 -0
  33. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/index.html +16 -0
  34. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/logo.jpg +0 -0
  35. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/prototype-dashboard.html +836 -0
  36. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/prototype-overview.html +256 -0
  37. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/package.json +25 -0
  38. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/executor.js +95 -0
  39. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/index.js +679 -0
  40. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/parser.js +526 -0
  41. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/watcher.js +357 -0
  42. package/.claude/worktrees/sillyspec-data-model-research/src/agent-session-log.js +803 -0
  43. package/.claude/worktrees/sillyspec-data-model-research/src/change-list.js +290 -0
  44. package/.claude/worktrees/sillyspec-data-model-research/src/change-risk-profile.js +270 -0
  45. package/.claude/worktrees/sillyspec-data-model-research/src/check-primitives.js +65 -0
  46. package/.claude/worktrees/sillyspec-data-model-research/src/classify-change.js +109 -0
  47. package/.claude/worktrees/sillyspec-data-model-research/src/commit-suggest.js +202 -0
  48. package/.claude/worktrees/sillyspec-data-model-research/src/config-cat.js +88 -0
  49. package/.claude/worktrees/sillyspec-data-model-research/src/config-schema.js +328 -0
  50. package/.claude/worktrees/sillyspec-data-model-research/src/constants.js +97 -0
  51. package/.claude/worktrees/sillyspec-data-model-research/src/contract-matrix.js +480 -0
  52. package/.claude/worktrees/sillyspec-data-model-research/src/datetime.js +20 -0
  53. package/.claude/worktrees/sillyspec-data-model-research/src/db-engine.js +55 -0
  54. package/.claude/worktrees/sillyspec-data-model-research/src/db.js +381 -0
  55. package/.claude/worktrees/sillyspec-data-model-research/src/decision-distill.js +517 -0
  56. package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/backends/local-agent.js +155 -0
  57. package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/backends/sillyhub-mcp.js +245 -0
  58. package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/probe.js +246 -0
  59. package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/strategy.js +121 -0
  60. package/.claude/worktrees/sillyspec-data-model-research/src/docs-check.js +825 -0
  61. package/.claude/worktrees/sillyspec-data-model-research/src/docs-debt.js +393 -0
  62. package/.claude/worktrees/sillyspec-data-model-research/src/docs-gate.js +107 -0
  63. package/.claude/worktrees/sillyspec-data-model-research/src/doctor-diagnostics.js +1099 -0
  64. package/.claude/worktrees/sillyspec-data-model-research/src/endpoint-extractor.js +420 -0
  65. package/.claude/worktrees/sillyspec-data-model-research/src/foreign-declared.js +78 -0
  66. package/.claude/worktrees/sillyspec-data-model-research/src/fs-atomic.js +73 -0
  67. package/.claude/worktrees/sillyspec-data-model-research/src/git-helper.js +150 -0
  68. package/.claude/worktrees/sillyspec-data-model-research/src/hooks/claude-pre-tool-use.cjs +130 -0
  69. package/.claude/worktrees/sillyspec-data-model-research/src/hooks/worktree-guard.js +892 -0
  70. package/.claude/worktrees/sillyspec-data-model-research/src/index.js +2798 -0
  71. package/.claude/worktrees/sillyspec-data-model-research/src/init.js +626 -0
  72. package/.claude/worktrees/sillyspec-data-model-research/src/knowledge-match.js +231 -0
  73. package/.claude/worktrees/sillyspec-data-model-research/src/local-detect.js +128 -0
  74. package/.claude/worktrees/sillyspec-data-model-research/src/local-register.js +104 -0
  75. package/.claude/worktrees/sillyspec-data-model-research/src/machine-interface.js +543 -0
  76. package/.claude/worktrees/sillyspec-data-model-research/src/migrate.js +127 -0
  77. package/.claude/worktrees/sillyspec-data-model-research/src/module-impact.js +166 -0
  78. package/.claude/worktrees/sillyspec-data-model-research/src/module-resolve.js +222 -0
  79. package/.claude/worktrees/sillyspec-data-model-research/src/modules.js +580 -0
  80. package/.claude/worktrees/sillyspec-data-model-research/src/progress/change-registry.js +336 -0
  81. package/.claude/worktrees/sillyspec-data-model-research/src/progress/consistency-doctor.js +355 -0
  82. package/.claude/worktrees/sillyspec-data-model-research/src/progress/shared.js +36 -0
  83. package/.claude/worktrees/sillyspec-data-model-research/src/progress/stage-machine.js +594 -0
  84. package/.claude/worktrees/sillyspec-data-model-research/src/progress/step-store.js +265 -0
  85. package/.claude/worktrees/sillyspec-data-model-research/src/progress.js +1304 -0
  86. package/.claude/worktrees/sillyspec-data-model-research/src/quick-recommend.js +115 -0
  87. package/.claude/worktrees/sillyspec-data-model-research/src/quicklog.js +852 -0
  88. package/.claude/worktrees/sillyspec-data-model-research/src/review-tier.js +77 -0
  89. package/.claude/worktrees/sillyspec-data-model-research/src/run/command.js +1595 -0
  90. package/.claude/worktrees/sillyspec-data-model-research/src/run/complete-handlers.js +1552 -0
  91. package/.claude/worktrees/sillyspec-data-model-research/src/run/complete.js +1259 -0
  92. package/.claude/worktrees/sillyspec-data-model-research/src/run/concurrent-detect.js +156 -0
  93. package/.claude/worktrees/sillyspec-data-model-research/src/run/gates.js +1084 -0
  94. package/.claude/worktrees/sillyspec-data-model-research/src/run/multi-repo-context.js +208 -0
  95. package/.claude/worktrees/sillyspec-data-model-research/src/run/next.js +128 -0
  96. package/.claude/worktrees/sillyspec-data-model-research/src/run/prompt.js +843 -0
  97. package/.claude/worktrees/sillyspec-data-model-research/src/run/quick-audit.js +186 -0
  98. package/.claude/worktrees/sillyspec-data-model-research/src/run/scan-profile.js +269 -0
  99. package/.claude/worktrees/sillyspec-data-model-research/src/run/shared.js +1562 -0
  100. package/.claude/worktrees/sillyspec-data-model-research/src/run/stage.js +584 -0
  101. package/.claude/worktrees/sillyspec-data-model-research/src/run.js +23 -0
  102. package/.claude/worktrees/sillyspec-data-model-research/src/scan-diff.js +388 -0
  103. package/.claude/worktrees/sillyspec-data-model-research/src/scan-postcheck.js +512 -0
  104. package/.claude/worktrees/sillyspec-data-model-research/src/scan-staleness.js +95 -0
  105. package/.claude/worktrees/sillyspec-data-model-research/src/setup.js +414 -0
  106. package/.claude/worktrees/sillyspec-data-model-research/src/sillyhub-mcp/client.js +597 -0
  107. package/.claude/worktrees/sillyspec-data-model-research/src/sillyhub-mcp/config.js +62 -0
  108. package/.claude/worktrees/sillyspec-data-model-research/src/spec-dir-typo.js +50 -0
  109. package/.claude/worktrees/sillyspec-data-model-research/src/spec-sync.js +358 -0
  110. package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract-engine.js +171 -0
  111. package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract-spec.js +550 -0
  112. package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract.js +889 -0
  113. package/.claude/worktrees/sillyspec-data-model-research/src/stage-review.js +597 -0
  114. package/.claude/worktrees/sillyspec-data-model-research/src/stages/archive.js +199 -0
  115. package/.claude/worktrees/sillyspec-data-model-research/src/stages/brainstorm-auto.js +236 -0
  116. package/.claude/worktrees/sillyspec-data-model-research/src/stages/brainstorm.js +617 -0
  117. package/.claude/worktrees/sillyspec-data-model-research/src/stages/cmd-existence.js +145 -0
  118. package/.claude/worktrees/sillyspec-data-model-research/src/stages/doctor.js +540 -0
  119. package/.claude/worktrees/sillyspec-data-model-research/src/stages/execute.js +1275 -0
  120. package/.claude/worktrees/sillyspec-data-model-research/src/stages/explore.js +43 -0
  121. package/.claude/worktrees/sillyspec-data-model-research/src/stages/index.js +29 -0
  122. package/.claude/worktrees/sillyspec-data-model-research/src/stages/knowledge.js +514 -0
  123. package/.claude/worktrees/sillyspec-data-model-research/src/stages/plan-postcheck.js +1344 -0
  124. package/.claude/worktrees/sillyspec-data-model-research/src/stages/plan.js +650 -0
  125. package/.claude/worktrees/sillyspec-data-model-research/src/stages/quick.js +167 -0
  126. package/.claude/worktrees/sillyspec-data-model-research/src/stages/scan.js +608 -0
  127. package/.claude/worktrees/sillyspec-data-model-research/src/stages/status.js +64 -0
  128. package/.claude/worktrees/sillyspec-data-model-research/src/stages/verify.js +246 -0
  129. package/.claude/worktrees/sillyspec-data-model-research/src/sync.js +1470 -0
  130. package/.claude/worktrees/sillyspec-data-model-research/src/task-review.js +1498 -0
  131. package/.claude/worktrees/sillyspec-data-model-research/src/taskcard.js +210 -0
  132. package/.claude/worktrees/sillyspec-data-model-research/src/verify-postcheck.js +1718 -0
  133. package/.claude/worktrees/sillyspec-data-model-research/src/verify-probes.js +277 -0
  134. package/.claude/worktrees/sillyspec-data-model-research/src/version.js +19 -0
  135. package/.claude/worktrees/sillyspec-data-model-research/src/workflow.js +600 -0
  136. package/.claude/worktrees/sillyspec-data-model-research/src/workspace.js +125 -0
  137. package/.claude/worktrees/sillyspec-data-model-research/src/worktree-apply.js +1446 -0
  138. package/.claude/worktrees/sillyspec-data-model-research/src/worktree-deps.js +576 -0
  139. package/.claude/worktrees/sillyspec-data-model-research/src/worktree.js +1715 -0
  140. package/.claude/worktrees/sillyspec-data-model-research/templates/claude-instruction.md +23 -0
  141. package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/taskcard-rules.md +28 -0
  142. package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/testcase-design.md +12 -0
  143. package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/verify-probes.md +32 -0
  144. package/.claude/worktrees/sillyspec-data-model-research/templates/workflows/archive-impact.yaml +79 -0
  145. package/.claude/worktrees/sillyspec-data-model-research/templates/workflows/scan-docs.yaml +132 -0
  146. package/README.md +1 -1
  147. package/eval/README.md +86 -0
  148. package/eval/eval.config.json +28 -0
  149. package/eval/lib/adapters/cli.mjs +63 -0
  150. package/eval/lib/adapters/dryrun.mjs +27 -0
  151. package/eval/lib/config.mjs +82 -0
  152. package/eval/lib/db.mjs +76 -0
  153. package/eval/lib/report.mjs +120 -0
  154. package/eval/lib/runner.mjs +157 -0
  155. package/eval/lib/tasks.mjs +73 -0
  156. package/eval/lib/util.mjs +107 -0
  157. package/eval/lib/workdir.mjs +30 -0
  158. package/eval/run.mjs +83 -0
  159. package/eval/tasks/001-slugify/files/package.json +6 -0
  160. package/eval/tasks/001-slugify/solution/src/slugify.mjs +26 -0
  161. package/eval/tasks/001-slugify/task.json +6 -0
  162. package/eval/tasks/001-slugify/verify.mjs +28 -0
  163. package/eval/tasks/002-each-day/files/package.json +6 -0
  164. package/eval/tasks/002-each-day/files/src/dates.mjs +4 -0
  165. package/eval/tasks/002-each-day/solution/src/dates.mjs +30 -0
  166. package/eval/tasks/002-each-day/task.json +6 -0
  167. package/eval/tasks/002-each-day/verify.mjs +31 -0
  168. package/eval/tasks/003-word-report/files/data/notes.txt +5 -0
  169. package/eval/tasks/003-word-report/files/package.json +6 -0
  170. package/eval/tasks/003-word-report/solution/bin/report.mjs +36 -0
  171. package/eval/tasks/003-word-report/task.json +6 -0
  172. package/eval/tasks/003-word-report/verify.mjs +38 -0
  173. package/package.json +4 -2
  174. package/src/agent-session-log.js +803 -0
  175. package/src/change-list.js +22 -2
  176. package/src/change-risk-profile.js +83 -14
  177. package/src/config-schema.js +18 -3
  178. package/src/contract-matrix.js +153 -8
  179. package/src/datetime.js +20 -0
  180. package/src/decision-distill.js +517 -0
  181. package/src/dispatch/backends/local-agent.js +4 -4
  182. package/src/docs-check.js +227 -0
  183. package/src/docs-debt.js +187 -24
  184. package/src/doctor-diagnostics.js +102 -2
  185. package/src/foreign-declared.js +78 -0
  186. package/src/index.js +99 -10
  187. package/src/init.js +9 -7
  188. package/src/knowledge-match.js +107 -6
  189. package/src/local-register.js +7 -1
  190. package/src/module-resolve.js +12 -1
  191. package/src/modules.js +2 -1
  192. package/src/plan-adopt-waves.js +126 -0
  193. package/src/quicklog.js +61 -7
  194. package/src/run/command.js +139 -20
  195. package/src/run/complete-handlers.js +48 -19
  196. package/src/run/complete.js +43 -3
  197. package/src/run/gates.js +87 -22
  198. package/src/run/prompt.js +84 -42
  199. package/src/run/quick-audit.js +28 -0
  200. package/src/run/shared.js +176 -11
  201. package/src/run/stage.js +23 -0
  202. package/src/run.js +2 -2
  203. package/src/scan-postcheck.js +30 -17
  204. package/src/stage-contract-spec.js +2 -1
  205. package/src/stage-contract.js +68 -4
  206. package/src/stages/archive.js +53 -12
  207. package/src/stages/brainstorm-auto.js +7 -0
  208. package/src/stages/brainstorm.js +11 -0
  209. package/src/stages/doctor.js +157 -1
  210. package/src/stages/execute.js +33 -6
  211. package/src/stages/plan-postcheck.js +79 -18
  212. package/src/stages/plan.js +4 -0
  213. package/src/stages/quick.js +6 -1
  214. package/src/stages/verify.js +31 -14
  215. package/src/sync.js +94 -2
  216. package/src/task-review.js +2 -2
  217. package/src/taskcard-placeholders.js +21 -0
  218. package/src/taskcard.js +10 -1
  219. package/src/verify-postcheck.js +315 -13
  220. package/src/verify-probes.js +46 -4
  221. package/src/worktree-apply.js +184 -7
  222. package/src/worktree-deps.js +69 -0
  223. package/src/worktree.js +201 -58
  224. package/templates/prompts/taskcard-rules.md +1 -0
@@ -154,6 +154,13 @@ AC-011: 不涉及业务规则/产品范围/默认行为/用户可见行为变更
154
154
  #### decisions.md(必填,写入变更根目录 \`decisions.md\`)
155
155
  记录所有决策:
156
156
  \`\`\`markdown
157
+ ---
158
+ author: <git-user>
159
+ created_at: <now-datetime>
160
+ ---
161
+
162
+ # 决策记录(Decisions)
163
+
157
164
  ## D-001@v1: 决策短标题
158
165
  - type: architecture | boundary | compatibility | ...
159
166
  - priority: P0 | P1 | P2
@@ -46,6 +46,7 @@ export const definition = {
46
46
  - 有匹配模板 → 询问是否基于模板
47
47
  - 无相关内容 → 跳过,不输出
48
48
  8. 查询知识库:\`cat {SPEC_ROOT}/knowledge/INDEX.md 2>/dev/null\`(无索引则跳过)——按需求关键词命中条目时读取对应知识文件;known-issues/已知坑要在方案期就规避,不要等 execute 的命中报告才发现
49
+ - decisions 库路由:INDEX.md \`## Decisions\` 段的路由行(\`decisions/<域>.md\`,由归档时决策提炼幂等维护)命中时,读取对应决策文件——rejected 条目须看到否决理由:除非复潮条件明确满足,否则不重新提出该方案(防复潮);确要复潮时在本变更 decisions.md 记新版本条目(D-xxx@vN+1)说明依据。CLI 预注入的否决决策提示(无命中则为空):{DECISION_HITS}
49
50
 
50
51
  ### 模块匹配方法
51
52
  读取 _module-map.yaml 后,根据用户描述的需求关键词,匹配相关模块:
@@ -339,6 +340,11 @@ design.md 第一行标题必须用中文:# 设计文档(Design)— <变更
339
340
  - 每条记录必须有稳定版本 ID:D-001@v1、D-002@v1 ...
340
341
  - 若后续 Design Grill 修正该决策,新记录使用 D-001@v2,并写明 supersedes: D-001@v1
341
342
  - 每条记录必须包含:type、status、source、question、answer、normalized_requirement、impacts、evidence、priority
343
+ - 九字段之外可另加四个可选字段,按需填写、不强制全填(旧格式决策记录缺这些字段不受影响):
344
+ - 锚点:决策落点的主文件,格式 <src 路径>:<行号或符号>(status=confirmed 时必填)
345
+ - 模块域:决策涉及的模块 ID,取自 \`{SPEC_ROOT}/docs/<project>/modules/_module-map.yaml\`,可多个、用逗号分隔
346
+ - 否决理由:一句话说明为何否决(status=rejected 时必填)
347
+ - 复潮条件:什么前提下可重新考虑该决策(status=rejected 时必填)
342
348
  - 长期术语只在 archive/scan 时再提升到 \`{SPEC_ROOT}/docs/<project>/glossary.md\`
343
349
  4. 格式自检(只查章节齐全;语义一致性/可行性/YAGNI 不在本步查,交给下一步 Design Grill 独立审查):
344
350
  - design.md 含全部必填章节(背景/设计目标/非目标/总体方案/文件变更清单/接口定义/风险登记)
@@ -566,6 +572,11 @@ created_at: <now-datetime>
566
572
 
567
573
  ### decisions.md 格式要求(仅在有 Grill/重大决策时生成)
568
574
  \`\`\`markdown
575
+ ---
576
+ author: <git-user>
577
+ created_at: <now-datetime>
578
+ ---
579
+
569
580
  # 决策记录(Decisions)
570
581
 
571
582
  ## D-001@v1: 决策短标题
@@ -327,9 +327,155 @@ timeout 5 which docker 2>/dev/null && echo "✅ Docker 可用" || echo "ℹ️ D
327
327
  outputHint: '模块文档健康状态',
328
328
  optional: false
329
329
  },
330
+ {
331
+ name: '决策待复核检查',
332
+ prompt: `检查决策知识库(knowledge/decisions/)的待复核状态(advisory,不阻断)。
333
+
334
+ ### 操作
335
+ 运行 docs-check 决策规则族(锚点存在性 + behind 阈值复核,task: 2026-08-23-adopt-harness-practices):
336
+
337
+ \`\`\`bash
338
+ # 定位 sillyspec 源码根:sillyspec 本仓 dogfood 优先用仓内源码(全局安装版本可能落后于本检查项);
339
+ # 其余场景经 bin 符号链接解析到安装包根;均不可得时降级跳过
340
+ SRC_ROOT=""
341
+ if grep -q '"name": *"sillyspec"' package.json 2>/dev/null && [ -f "src/docs-check.js" ]; then
342
+ SRC_ROOT="$PWD"
343
+ else
344
+ BIN=$(command -v sillyspec)
345
+ if [ -n "$BIN" ]; then
346
+ SRC_ROOT=$(node -e 'const fs=require("fs");const p=fs.realpathSync(process.argv[1]).replace(/\\\\/g,"/");const i=p.lastIndexOf("/bin/sillyspec.js");console.log(i===-1?"":p.slice(0,i));' "$BIN")
347
+ fi
348
+ fi
349
+ if [ -z "$SRC_ROOT" ]; then
350
+ echo "ℹ️ 无法定位 sillyspec 源码(非 sillyspec 仓且未安装 sillyspec 命令),跳过决策待复核检查"
351
+ else
352
+ node --input-type=module -e '
353
+ import { readFileSync, existsSync } from "node:fs";
354
+ import { pathToFileURL } from "node:url";
355
+ let specBase = process.argv[2];
356
+ if (existsSync(".sillyspec-platform.json")) {
357
+ try { const p = JSON.parse(readFileSync(".sillyspec-platform.json", "utf8")); if (p.specRoot) specBase = p.specRoot; } catch {}
358
+ }
359
+ const mod = await import(pathToFileURL(process.argv[1] + "/docs-check.js"));
360
+ const r = await mod.runDecisionRules({ projectRoot: process.cwd(), specBase: specBase });
361
+ if (r.empty) {
362
+ console.log("ℹ️ 决策库未初始化(" + specBase + "/knowledge/decisions/ 不存在)——冷启动空库,变更归档时 decision-distill 自动积累(R-02)");
363
+ } else if (r.findings.length === 0) {
364
+ console.log("✅ 决策待复核:无信号(" + r.implemented + "/" + r.entries + " 条 implemented,behind 阈值 " + r.threshold + ")");
365
+ } else {
366
+ console.log("⚠️ 决策待复核清单(" + r.findings.length + " 条,advisory 不阻断——D-003):");
367
+ for (const f of r.findings) console.log(" - " + f.message);
368
+ }
369
+ if (r.exempted.length > 0) console.log("ℹ️ 另有 " + r.exempted.length + " 条经 known_failures decisions.* 键豁免: " + r.exempted.map(e => e.key).join(", "));
370
+ ' "$SRC_ROOT/src" "$PWD/.sillyspec" || echo "⚠️ 决策规则执行失败(降级跳过,不影响其他检查项)"
371
+ fi
372
+ \`\`\`
373
+
374
+ 同步骤追加 CLI 版本漂移检测(D-002 并入既有检查段不新增步骤;D-004 git/version 双轨;advisory 不阻断)——检测全局安装的 sillyspec 流程引擎与当前仓源码是否脱节:
375
+
376
+ \`\`\`bash
377
+ # 安装根独立解析:command -v sillyspec → realpath → 向上找 name=sillyspec 的 package.json
378
+ # 勿复用上方 SRC_ROOT——sillyspec 仓场景它被 dogfood 分支强制指向当前仓,比较恒等(Grill 实证陷阱)
379
+ node --input-type=module -e '
380
+ import { execFileSync } from "node:child_process";
381
+ import { existsSync, readFileSync, realpathSync } from "node:fs";
382
+ import { dirname, join } from "node:path";
383
+
384
+ const run = (cmd, args) => { try { return execFileSync(cmd, args, { encoding: "utf8", timeout: 8000, stdio: ["ignore", "pipe", "pipe"] }).trim(); } catch { return ""; } };
385
+ const pkgOf = (dir) => { try { return JSON.parse(readFileSync(join(dir, "package.json"), "utf8")); } catch { return null; } };
386
+ const short = (h) => h.slice(0, 7);
387
+
388
+ try {
389
+ const cur = pkgOf(process.cwd());
390
+ if (!cur || cur.name !== "sillyspec") process.exit(0); // 当前仓非 sillyspec(消费项目)→ 静默跳过
391
+ const bin = run("sh", ["-c", "command -v sillyspec"]);
392
+ if (!bin) process.exit(0); // 无全局安装 → 静默跳过
393
+ let installRoot = "";
394
+ try {
395
+ let dir = dirname(realpathSync(bin));
396
+ for (;;) {
397
+ const p = pkgOf(dir);
398
+ if (p && p.name === "sillyspec") { installRoot = dir; break; }
399
+ const up = dirname(dir);
400
+ if (up === dir) break;
401
+ dir = up;
402
+ }
403
+ } catch {}
404
+ if (!installRoot) { console.log("⚠️ CLI 版本漂移检测降级:sillyspec 命令存在但未定位到安装根(" + bin + "),跳过比较,不阻断其余检查项"); process.exit(0); }
405
+ if (installRoot === process.cwd()) process.exit(0); // 安装根=当前仓(npm link 自身)→ 恒等不误报
406
+
407
+ // 双轨比较(D-004):
408
+ // git 轨——安装根有 .git(开发态:npm link / file: 链接)→ rev-parse HEAD 双仓 + remote origin 归一化同源判定
409
+ // version 兜底轨——安装根无 .git(安装态:registry 安装 / npm i -g . 拷贝,npm 恒排除 .git)→ 比较双仓 package.json version
410
+ // 盲区声明(R-04):同 version 不同 commit 的源码热改不检测——version 轨只在版本号变更时感知;
411
+ // git 轨管开发态、version 轨管安装态,两轨并集覆盖主流形态
412
+ const inst = pkgOf(installRoot) || {};
413
+ const curVer = cur.version || "?";
414
+ const instVer = inst.version || "?";
415
+ const warn = (detail) => console.log("⚠️ CLI 版本漂移:" + detail + "——全局安装的流程引擎与当前仓源码脱节,归档等流程行为可能滞后(2026-08-23 归档实证踩坑:五步版 archive 驱动六步定义,旧引擎驱动新定义),建议同步(安装根 git pull 或在 sillyspec 仓 npm i -g .)后重跑流程(advisory,不阻断)");
416
+
417
+ if (existsSync(join(installRoot, ".git"))) {
418
+ // remote 归一化:https/ssh 归一、.git 后缀剥离、host 小写——同源才可比较(异源 fork 无从判定 → 静默)
419
+ const normRemote = (u) => {
420
+ if (!u) return "";
421
+ let s = u.trim();
422
+ if (s.indexOf("://") === -1) {
423
+ const scp = s.match(/^([^@\\/]+@)([^:\\/]+):(.+)$/); // scp 形态 git@host:owner/repo.git
424
+ if (scp) s = "https://" + scp[2] + "/" + scp[3];
425
+ }
426
+ s = s.replace(/^ssh:\\/\\//, "https://").replace(/^git:\\/\\//, "https://");
427
+ const m = s.match(/^[a-zA-Z][a-zA-Z0-9+.-]*:\\/\\/(.*)$/);
428
+ const rest = m ? m[1] : s;
429
+ const slash = rest.indexOf("/");
430
+ let host = slash === -1 ? rest : rest.slice(0, slash);
431
+ const tail = slash === -1 ? "" : rest.slice(slash);
432
+ const at = host.indexOf("@");
433
+ if (at !== -1) host = host.slice(at + 1); // 剥 userinfo(ssh://git@… / https://token@…)
434
+ let out = "https://" + host.toLowerCase() + tail;
435
+ out = out.replace(/\\/+$/, "");
436
+ if (out.endsWith(".git")) out = out.slice(0, -4);
437
+ return out;
438
+ };
439
+ const curHead = run("git", ["-C", process.cwd(), "rev-parse", "HEAD"]);
440
+ const instHead = run("git", ["-C", installRoot, "rev-parse", "HEAD"]);
441
+ if (!curHead || !instHead) { console.log("⚠️ CLI 版本漂移检测降级:git 轨 rev-parse 失败(当前仓=" + (curHead ? "ok" : "fail") + " 安装根=" + (instHead ? "ok" : "fail") + "),跳过比较,不阻断其余检查项"); process.exit(0); }
442
+ const curRemote = normRemote(run("git", ["-C", process.cwd(), "remote", "get-url", "origin"]));
443
+ const instRemote = normRemote(run("git", ["-C", installRoot, "remote", "get-url", "origin"]));
444
+ if (curRemote && instRemote && curRemote !== instRemote) process.exit(0); // 非同源 → 静默
445
+ if (curHead !== instHead) warn("全局安装 " + installRoot + "(version " + instVer + " / commit " + short(instHead) + ")与当前仓(version " + curVer + " / HEAD " + short(curHead) + ")不一致");
446
+ // 同 commit → 一致静默
447
+ } else if (instVer !== curVer) {
448
+ warn("全局安装 " + installRoot + "(version " + instVer + ")与当前仓(version " + curVer + ")不一致(安装根无 .git,走 version 兜底轨)");
449
+ // 同 version → 一致静默(同 version 不同 commit 热改盲区见上方 R-04 注释)
450
+ }
451
+ } catch (e) {
452
+ console.log("⚠️ CLI 版本漂移检测降级:" + (e && e.message ? e.message : String(e)) + "(不阻断其余检查项)");
453
+ }
454
+ ' || echo "⚠️ CLI 版本漂移检测降级:脚本执行失败(不阻断其余检查项)"
455
+ \`\`\`
456
+
457
+ ### 输出格式
458
+ \`\`\`
459
+ 📋 决策待复核
460
+ ✅ 决策待复核:无信号(3/5 条 implemented,behind 阈值 10)
461
+ ⚠️ 决策待复核清单(1 条,advisory 不阻断——D-003):
462
+ - 「D-012@v1」决策待复核:锚定模块 docs-consistency 在最近确认 a1b2c3d 后源码已前进 15 commit,超阈值 10
463
+ ℹ️ 决策库未初始化 —— 冷启动空库(R-02)
464
+
465
+ ⚠️ CLI 版本漂移:全局安装 /path/to/sillyspec(version 3.26.0 / commit a1b2c3d)与当前仓(version 3.27.3 / HEAD e4f5a6b)不一致——全局安装的流程引擎与当前仓源码脱节,建议同步后重跑流程(仅漂移时输出;一致/非 sillyspec 仓/无全局安装/安装根=当前仓均静默)
466
+ \`\`\`
467
+
468
+ ### 注意
469
+ - 本检查项为 advisory(只 ⚠️ 不 ❌),不作为修复阻断项
470
+ - 待复核的复核再确认由用户完成:确认决策仍成立后,把条目「最近确认」更新为当前 HEAD——不要自动改写
471
+ - 全程只读,不修改 knowledge/decisions/ 下任何文件
472
+ - CLI 版本漂移检测(同步骤追加段,D-002/D-004)同为 advisory:漂移时一行警告;一致、非 sillyspec 仓消费场景、无全局安装、安装根=当前仓(npm link 自身)均静默;探测失败降级单行,不阻断其余检查项`,
473
+ outputHint: '决策待复核清单',
474
+ optional: false
475
+ },
330
476
  {
331
477
  name: '汇总报告',
332
- prompt: `汇总前四步的所有检查结果,生成最终的自检报告。
478
+ prompt: `汇总前面各步的所有检查结果,生成最终的自检报告。
333
479
 
334
480
  ### 输出格式
335
481
  \`\`\`
@@ -350,6 +496,12 @@ timeout 5 which docker 2>/dev/null && echo "✅ Docker 可用" || echo "ℹ️ D
350
496
  ## 外部依赖
351
497
  ✅ Context7 MCP — 已配置
352
498
  ⚠️ grep.app — 不可达
499
+
500
+ ## 决策待复核(advisory)
501
+ ⚠️ 「D-012@v1」— 锚定模块源码 behind 15 超阈值 10(复核后更新「最近确认」)
502
+
503
+ ## CLI 版本漂移(advisory)
504
+ ⚠️ 全局安装 sillyspec(version 3.26.0 / commit a1b2c3d)与当前仓(version 3.27.3 / HEAD e4f5a6b)不一致 — 同步后重跑流程
353
505
  \`\`\`
354
506
 
355
507
  ### 要求
@@ -370,8 +522,12 @@ timeout 5 which docker 2>/dev/null && echo "✅ Docker 可用" || echo "ℹ️ D
370
522
  - 孤儿目录 → 确认后 \`rm -rf .sillyspec/changes/<目录名>\`
371
523
  - Maven 私服不可达 → 检查 VPN、settings.xml 配置、私服状态
372
524
  - Git remote 不可达 → 检查网络、SSH key 或凭证
525
+ - CLI 版本漂移(全局安装落后于当前仓)→ 在 sillyspec 仓执行 \`npm i -g .\` 重新全局安装(或安装根 git pull),同步后重跑流程
373
526
  - 工具未安装 → 给出安装命令(如 \`brew install maven\`)
374
527
 
528
+ **状态错乱补 postmortem(advisory,不强制):**
529
+ 若本次自检检出状态错乱或不一致(sillyspec.db 阶段状态与实际产出不符、孤儿目录、僵尸 quick 会话等)并已修复,建议补一条轻量 postmortem 记录进 QUICKLOG(走 quick 流程或既有条目正文核对),根因块内按列表行写四子字段:\`- 现象:\`(错乱表现)、\`- 根因:\`(深层原因)、\`- 护栏:\`(防再犯措施)、\`- 证据:\`(可追溯路径——\`sillyspec agent-log --json\` 输出的本地会话日志 jsonl 路径、相关变更 review.json、verify-result.md)。护栏结论经人工确认后归入 \`.sillyspec/knowledge/known-issues.md\`——走既有 knowledge 追加链路(先入 knowledge/uncategorized.md,经知识整理确认后归类),不新建链路不新建命令。
530
+
375
531
  每条建议格式:
376
532
  \`\`\`
377
533
  💡 修复:<问题描述>
@@ -11,6 +11,9 @@ import { parseRepo } from './plan-postcheck.js'
11
11
  // parseTaskRegistry(注册表同口径)——双向引用均为函数声明 + 调用时解引(无模块初始化期
12
12
  // 交叉求值),ESM live binding 下安全;两边都是纯读函数,无副作用序问题。
13
13
  import { resolveChangeModuleCards } from '../module-resolve.js'
14
+ // 决策锚点触碰事实(2026-08-24-decision-touch-cli-drift task-01,W-A 主渲染点 D-003):
15
+ // changedFiles 口径与 {DOCS_DEBT} 现算同源(collectExecuteChangedFiles 唯一实现,勿双写)
16
+ import { collectExecuteChangedFiles, computeDecisionTouches, renderDecisionTouchFacts } from '../docs-debt.js'
14
17
 
15
18
  /**
16
19
  * 任务注册表解析(2026-08-20-task-truth-unify D-001@v1:tasks.md 唯一任务真相)。
@@ -913,20 +916,43 @@ ${indexLines}
913
916
  }
914
917
  } catch { /* 热区提取 best-effort:失败不注入 */ }
915
918
 
919
+ // ── 决策锚点触碰事实(W-A 主渲染点,D-003):changedFiles 口径与 run/prompt.js {DOCS_DEBT}
920
+ // 现算同源——collectExecuteChangedFiles(porcelain 未提交 ∪ baseline..HEAD)唯一实现,勿双写。
921
+ // 单过流程前缀第 4 步渲染 {DOCS_DEBT} 时 changedFiles 恒空,Wave 步(重入/续跑时已含前置
922
+ // Wave 已提交变更)才是触碰事实的主呈现时机。advisory:无触碰 section=''(模板字节不变,
923
+ // 零输出零阻断);≤5 条截断(R-05)。best-effort:失败不注入不阻断。
924
+ let decisionTouchSection = ''
925
+ try {
926
+ if (changeDir) {
927
+ const dtSpecBase = path.dirname(path.dirname(changeDir))
928
+ const { changedFiles } = collectExecuteChangedFiles({
929
+ specBase: dtSpecBase,
930
+ changeName: path.basename(changeDir),
931
+ cwd: options.cwd || process.cwd(),
932
+ })
933
+ const dtFacts = renderDecisionTouchFacts(computeDecisionTouches(changedFiles, path.join(dtSpecBase, 'knowledge')).touches)
934
+ if (dtFacts) {
935
+ decisionTouchSection = `\n### 决策锚点触碰(CLI 算,advisory——改到锚定文件时复核对应决策)\n\n${dtFacts}\n`
936
+ }
937
+ }
938
+ } catch { /* 触碰事实 best-effort:失败不注入不阻断 */ }
939
+
916
940
  // 子代理要点 4 / Wave 开始前第 1 条:模块表 / 热区存在时换成「按注入内容执行」版文案,
917
941
  // 否则保留原文(零回归——无 map / 无 design 的项目 prompt 不变)
918
942
  const moduleDocPoint = moduleSection
919
943
  ? '4. **模块卡分级**:把上方「模块卡分级」表中该 task 命中的卡路径写进子代理 prompt(细卡整读;粗大卡只读「契约摘要/注意事项/定位」节)——勿让子代理按目录漫游或整读根层大卡'
920
- : '4. 如存在模块文档(.sillyspec/docs/*/modules/),按需读取涉及模块的 <module>.md 参考接口约定和数据流'
944
+ : '4. 如存在模块文档({SPEC_ROOT}/docs/*/modules/),按需读取涉及模块的 <module>.md 参考接口约定和数据流——读主仓路径(CLI 已替换为绝对路径),不要读 worktree 副本'
921
945
  const waveStartItem1 = hasDesignHotzone
922
946
  ? '1. 「非目标」「兼容策略」两节已由 CLI 提取附在上方「design.md 热区」——**不要再整读 design.md**;需要其余章节时按热区下方行号索引精准读取(Read offset/limit)。确保子代理不超范围、不破坏旧逻辑'
923
947
  : '1. 读取 design.md 的「非目标」与「兼容策略」章节(如存在),确保子代理不超范围、不破坏旧逻辑'
924
948
 
925
- // 构建任务摘要(不再内联完整蓝图,减少上下文污染)
949
+ // 构建任务摘要(不再内联完整蓝图,减少上下文污染)。
950
+ // 任务卡路径用 {SPEC_ROOT} 占位符(坑 worktree-spec-artifact-misplace):子代理 cwd=worktree 时
951
+ // 相对路径会解析到 worktree 的 .sillyspec 副本;占位符经 CLI 替换为主仓绝对路径。
926
952
  const taskSummary = wave.tasks.map((t, ti) => {
927
953
  const taskNum = String(t.index || (ti + 1)).padStart(2, '0')
928
954
  const taskRelPath = changeDir
929
- ? `.sillyspec/changes/${path.basename(changeDir)}/tasks/task-${taskNum}.md`
955
+ ? `{SPEC_ROOT}/changes/${path.basename(changeDir)}/tasks/task-${taskNum}.md`
930
956
  : `task-${taskNum}.md`
931
957
  const fileInfo = t.file ? ` (${t.file})` : ''
932
958
  return `task-${taskNum}: ${t.name}${fileInfo} → ${taskRelPath}`
@@ -1025,7 +1051,7 @@ ${workdirLines}
1025
1051
  \`\`\`
1026
1052
 
1027
1053
  ### 注意
1028
- 蓝图文件(tasks.md / design.md / proposal.md / requirements.md / tasks/task-XX.md)在主工作区 .sillyspec/changes/<change>/ 下,它们不在跨仓仓也不在 worktree 中。读取蓝图时使用主工作区路径,不要拼接到 worktree / 跨仓仓路径下。
1054
+ 蓝图文件(tasks.md / design.md / proposal.md / requirements.md / tasks/task-XX.md)在主工作区 {SPEC_ROOT}/changes/<change>/ 下(CLI 已替换为主仓绝对路径),它们不在跨仓仓也不在 worktree 中。读取蓝图时使用主工作区路径,不要拼接到 worktree / 跨仓仓路径下。
1029
1055
  `
1030
1056
  } else {
1031
1057
  worktreeSection = (worktreePath)
@@ -1044,7 +1070,7 @@ ${workdirLines}
1044
1070
  \`\`\`
1045
1071
 
1046
1072
  ### 注意
1047
- 蓝图文件(tasks.md / design.md / proposal.md / requirements.md)在主工作区 .sillyspec/changes/<change>/ 下,它们可能不在 worktree 中。读取蓝图时使用主工作区路径,不要拼接到 worktree 路径下。
1073
+ 蓝图文件(tasks.md / design.md / proposal.md / requirements.md)在主工作区 {SPEC_ROOT}/changes/<change>/ 下(CLI 已替换为主仓绝对路径),它们可能不在 worktree 中。读取蓝图时使用主工作区路径,不要拼接到 worktree 路径下;同理,spec 流程产物(module-impact.md / knowledge 条目 / 模块文档)只写主仓 {SPEC_ROOT},绝不写进 worktree 副本。
1048
1074
  `
1049
1075
  : ''
1050
1076
  }
@@ -1114,7 +1140,7 @@ ${taskSummary}
1114
1140
 
1115
1141
  {{include: testcase-design}}
1116
1142
 
1117
- ${designHotzone}
1143
+ ${designHotzone}${decisionTouchSection}
1118
1144
  ### Wave 开始前
1119
1145
  ${waveStartItem1}
1120
1146
  2. 读取 plan.md 了解全局任务划分和依赖关系
@@ -1143,6 +1169,7 @@ ${taskList}
1143
1169
  - 最后一个 Wave 完成后做一次全量编译验证
1144
1170
  - 用户明确要求编译时
1145
1171
  4. 每个任务完成后:
1172
+ - **在 worktree 内 git add -A && git commit -m "<task-NN 摘要>"**(坑 subagent-uncommitted-newfile-apply3way,2026-08-22 实证:纯新增文件不 commit 时 apply 的 git apply --3way 报 "does not exist in index" 直接炸——未 commit 的新文件不在 base commit 也不在 index,patch 生成取不到。commit 后 base..HEAD diff 完整、apply 顺畅,review.json 的 head 也有真实锚点)
1146
1173
  - **先写 review.json 再勾选 checkbox**(见下方 Task Review Gate)
1147
1174
  - **既跑 lint check 也跑 formatter**:凡变更涉及的源码跑项目的 lint 检查 **和** 格式化(如 \`ruff format\` / \`prettier --write\`),不要只跑 check——只 check 不 format 会把格式问题留到 commit 时被 pre-commit hook 拦截(worktree 内二进制可能缺失,先 \`which <bin>\` 确认,缺则 \`uv tool install\` / \`uv sync\`)
1148
1175
  - 记录改动文件和测试结果
@@ -19,6 +19,7 @@ import { tmpdir } from 'os'
19
19
  import jsYaml from 'js-yaml'
20
20
  import { parseFileChangeList, pathMatches } from '../change-list.js'
21
21
  import { getRule } from '../stage-contract-spec.js'
22
+ import { TASKCARD_PLACEHOLDERS } from '../taskcard-placeholders.js'
22
23
  import { validateScriptCommands } from './cmd-existence.js'
23
24
 
24
25
  // ═══════════════════════════════════════════════════════════════
@@ -173,7 +174,12 @@ export function parseHeadCommit(content) {
173
174
  export function parseRepoRegistry(yamlText) {
174
175
  const reg = new Map()
175
176
  if (!yamlText) return reg
176
- const lines = yamlText.split('\n')
177
+ // CRLF 归一(坑 register-repo-crlf-idempotent-loop,2026-08-23 实证:Windows 下 agent Write
178
+ // 工具/编辑器写出的 local.yaml 带 \r——条目正则 `(.*)$` 的 `.` 不匹配 \r、`$` 又要求真串尾,
179
+ // 行尾残留 \r 时整条失配 → 空 Map → MultiRepoContext fail-closed 报「未注册」→ register-repo
180
+ // 幂等跳过不落盘 → 死循环。同文件 parseAllowedPaths/parseDependsOn/parseBaseCommit 均有此
181
+ // 归一,本函数是唯一缺口)
182
+ const lines = String(yamlText).replace(/\r\n?/g, '\n').split('\n')
177
183
  let startIdx = -1
178
184
  for (let i = 0; i < lines.length; i++) {
179
185
  if (/^repos:\s*(?:#.*)?$/.test(lines[i])) { startIdx = i; break }
@@ -356,6 +362,28 @@ function parseTaskWavesFromPlan(planPath) {
356
362
  return map
357
363
  }
358
364
 
365
+ /**
366
+ * 收集任务卡 depends_on → Map<taskId, deps>(Wave 重排 / plan-adopt-waves 共用)。
367
+ * taskId 优先取卡内 frontmatter id:(与文件名解耦——文件名 task-01 但 id 改写过时以 id 为准)。
368
+ * @param {string} tasksDir - changes/<name>/tasks 目录
369
+ * @returns {Map<string, string[]>}
370
+ */
371
+ export function collectTaskDepMap(tasksDir) {
372
+ const depMap = new Map()
373
+ if (!existsSync(tasksDir)) return depMap
374
+ for (const file of readdirSync(tasksDir).filter(f => /^task-\d+\.md$/.test(f))) {
375
+ const content = readFileSync(pJoin(tasksDir, file), 'utf8')
376
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/)
377
+ let taskId = file.replace('.md', '')
378
+ if (fmMatch) {
379
+ const idMatch = fmMatch[1].match(/^id:\s*(.+)/m)
380
+ if (idMatch) taskId = idMatch[1].trim()
381
+ }
382
+ depMap.set(taskId, parseDependsOn(content))
383
+ }
384
+ return depMap
385
+ }
386
+
359
387
  /**
360
388
  * 本地一致性校验器
361
389
  * @param {string} changeDir - 变更目录
@@ -448,7 +476,9 @@ export function validateBlueprintConsistency(changeDir) {
448
476
  if (ts.length > 1) {
449
477
  errors.push(
450
478
  `路径 ${p} 被 Wave ${w} 内 ${ts.length} 个 task 修改: ${ts.join(', ')} — 同 Wave 任务 execute 强制并行(execute.js:603),` +
451
- `子代理会互相覆盖该文件。解法:把它们拆到不同 Wave(串行)。`
479
+ `子代理会互相覆盖该文件。解法:把它们拆到不同 Wave(串行)。` +
480
+ `(注:quick 阶段对同文件并发只提示不阻断——那是轻量流程无并行子代理、最坏后果 git 可分离;` +
481
+ `此处硬拦是因并行覆盖不可自动恢复,两阶段宽严不同是设计使然)`
452
482
  )
453
483
  }
454
484
  }
@@ -1000,6 +1030,22 @@ export function validatePlanFeasibility(changeDir, projectRoot = null) {
1000
1030
  if (!hasVerify) errors.push(fsRule.data.messageVerify.replaceAll('${id}', taskId || file))
1001
1031
  if (!hasConstraints) errors.push(fsRule.data.messageConstraints.replaceAll('${id}', taskId || file))
1002
1032
 
1033
+ // 5b. 骨架占位符拦截(坑 taskcard-placeholder-slip,2026-08-24 用户实证:task 卡留成
1034
+ // 空骨架、九字段「存在性」全过,直到人工审计才发现)。占位标记是 taskcard.js 生成的
1035
+ // 封闭集合(TASKCARD_PLACEHOLDERS 同源导出),精确匹配;verify 字段的占位命令刻意
1036
+ // 不拦(可能是真实 tsc 命令)。占位符视同缺字段,与 9 字段缺失同级 error。
1037
+ // 先剥 HTML 注释再匹配——骨架尾部注释自身罗列了占位标记清单(填充指引),不剥会把
1038
+ // 已填充卡的注释误判成占位残留。
1039
+ const contentNoComments = content.replace(/<!--[\s\S]*?-->/g, '')
1040
+ const placeholderHits = TASKCARD_PLACEHOLDERS.filter(p => contentNoComments.includes(p.marker))
1041
+ if (placeholderHits.length > 0) {
1042
+ errors.push(
1043
+ (fsRule.data.messagePlaceholder || '${id}: task 卡仍是未填充的生成骨架')
1044
+ .replaceAll('${id}', taskId || file)
1045
+ .replaceAll('${fields}', placeholderHits.map(p => p.field).join('、'))
1046
+ )
1047
+ }
1048
+
1003
1049
  // 6. acceptance best-effort 字段 grep(D-05 软约束,warning 不阻断)
1004
1050
  // 从 acceptance 文本提取 snake_case/camelCase 标识符,grep allowed_paths 指向的源文件;
1005
1051
  // 找不到 → warning(给 LLM 审查提线索,不阻断 execute)。宁漏不噪:只取 snake/camel,
@@ -1246,18 +1292,7 @@ export async function executePlanPostcheck(context) {
1246
1292
  const tasksDir = pJoin(changeDir, 'tasks')
1247
1293
  if (existsSync(tasksDir)) {
1248
1294
  const taskFiles = readdirSync(tasksDir).filter(f => /^task-\d+\.md$/.test(f))
1249
- const depMap = new Map()
1250
-
1251
- for (const file of taskFiles) {
1252
- const content = readFileSync(pJoin(tasksDir, file), 'utf8')
1253
- const fmMatch = content.match(/^---\n([\s\S]*?)\n---/)
1254
- let taskId = file.replace('.md', '')
1255
- if (fmMatch) {
1256
- const idMatch = fmMatch[1].match(/^id:\s*(.+)/m)
1257
- if (idMatch) taskId = idMatch[1].trim()
1258
- }
1259
- depMap.set(taskId, parseDependsOn(content))
1260
- }
1295
+ const depMap = collectTaskDepMap(tasksDir)
1261
1296
 
1262
1297
  const { waves, error: topoError } = topoSortWaves(depMap)
1263
1298
  if (topoError) {
@@ -1270,8 +1305,10 @@ export async function executePlanPostcheck(context) {
1270
1305
  console.log(` Wave ${i + 1}: ${wave.join(', ')}`)
1271
1306
  })
1272
1307
 
1273
- // 比较 plan.md 现有 Wave 分组
1274
- if (waves.length > 1 && taskFiles.length > 1) {
1308
+ // 比较 plan.md 现有 Wave 分组 + 依赖方向硬校验(坑 wave-dep-direction-unchecked,
1309
+ // 2026-08-24 用户反馈二期:主控手排 Wave 与拓扑比对只警告不阻断,而真正的正确性违规
1310
+ // ——depends_on 落同 Wave / 后置 Wave——此前完全没有校验)
1311
+ if (taskFiles.length > 0) {
1275
1312
  const planPath = pJoin(changeDir, 'plan.md')
1276
1313
  if (existsSync(planPath)) {
1277
1314
  const planContent = readFileSync(planPath, 'utf8')
@@ -1303,6 +1340,30 @@ export async function executePlanPostcheck(context) {
1303
1340
  }
1304
1341
  if (currentWaveTasks) existingWaves.push(currentWaveTasks)
1305
1342
 
1343
+ // ── 依赖方向硬校验 ──
1344
+ // Wave 契约(execute.js Wave 要点 1):同一 Wave 的子代理强制并行;有依赖的 task
1345
+ // 必须落在其依赖的后置 Wave。同 Wave / 后置 Wave 引用都是正确性违规(并行破坏依赖
1346
+ // / 顺序颠倒),硬拦;仅分组数差异(手工过度串行化)合法,保留下方 warning。
1347
+ const waveOf = new Map()
1348
+ existingWaves.forEach((ws, i) => { for (const t of ws) waveOf.set(t, i) })
1349
+ const directionViolations = []
1350
+ for (const [taskId, deps] of depMap) {
1351
+ const w = waveOf.get(taskId)
1352
+ if (w === undefined) continue // 卡片未列入任何 Wave → 由 validatePlanForExecute 覆盖校验兜底
1353
+ for (const dep of deps) {
1354
+ const dw = waveOf.get(dep)
1355
+ if (dw === undefined) continue // 依赖不存在的 task → feasibility depends_on 引用校验兜底
1356
+ if (dw === w) directionViolations.push(`${taskId} depends_on ${dep},但两者同在 Wave ${w + 1}(同 Wave 强制并行,依赖被破坏)`)
1357
+ else if (dw > w) directionViolations.push(`${taskId} depends_on ${dep},但 ${dep} 在后置 Wave ${dw + 1}(顺序颠倒)`)
1358
+ }
1359
+ }
1360
+ if (directionViolations.length > 0) {
1361
+ console.error(`\n❌ Wave 依赖方向违规(${directionViolations.length} 处):`)
1362
+ for (const v of directionViolations) console.error(` ${v}`)
1363
+ console.error(' 解法:sillyspec plan-adopt-waves --change <变更名> 一键按 depends_on 拓扑重排 plan.md Wave 段,或手动调整分组')
1364
+ throw new Error('planPostcheck: Wave 依赖方向违规(depends_on 同 Wave / 后置 Wave)——' + directionViolations.join(';'))
1365
+ }
1366
+
1306
1367
  const sameStructure = waves.length === existingWaves.length &&
1307
1368
  waves.every((w, i) => {
1308
1369
  const a = [...w].sort().join(',')
@@ -1313,8 +1374,8 @@ export async function executePlanPostcheck(context) {
1313
1374
  if (sameStructure) {
1314
1375
  console.log(' ✅ Wave 分组与拓扑排序一致,无需更新 plan.md')
1315
1376
  } else {
1316
- console.log(' ⚠️ Wave 分组与拓扑排序不一致,建议手动调整 plan.md')
1317
- console.log(' 拓扑排序建议的 Wave 分组见上方')
1377
+ console.log(' ⚠️ Wave 分组与拓扑排序不一致(依赖方向已校验合法——手工比拓扑更细的串行化是安全做法,可保持现状)')
1378
+ console.log(' 标准分组见上方 📊;如需对齐:sillyspec plan-adopt-waves --change <变更名>(重排 plan.md Wave 段并同步任务总表 W 列)')
1318
1379
  }
1319
1380
  }
1320
1381
  }
@@ -473,6 +473,9 @@ ${taskList}
473
473
  \`\`\`
474
474
  你是一个专注的 TaskCard 生成器。你的任务是为指定 batch 生成全部 TaskCard。
475
475
 
476
+ ## ⚠️ 格式形态(第一眼必读,坑 taskcard-body-section-rework)
477
+ TaskCard 的契约字段全部在 **frontmatter**(首对 --- 包裹的 YAML 键值对:id/title/title_zh/allowed_paths/goal/...)——**不是 body 章节**(## 标题下的段落)。goal 是 frontmatter 的 \`goal: >\` 多行标量、implementation/acceptance/verify/constraints 是 frontmatter 的列表项;写成 body 章节(\`## goal\` / \`## Goal\` 等)会三组校验全挂返工。先跑骨架命令再 Edit 填充,骨架即正确形态。
478
+
476
479
  ## 输入
477
480
  - 变更目录:${changeDir}
478
481
  - 当前时间:<now-datetime>(frontmatter 的 created_at 使用此值)
@@ -507,6 +510,7 @@ ${taskcardTemplate}
507
510
  - 所有 task 都被某个 batch 覆盖、无遗漏、无重复
508
511
  - frontmatter 字段分两组对照(与 plan-postcheck / taskcard-rules.md 同源,三处清单以此为准):
509
512
  - **硬校验 9 字段**(缺失 plan-postcheck 直接报错阻断):id、title、title_zh、allowed_paths、goal、implementation、acceptance、verify、constraints
513
+ - **占位符硬拦**(骨架占位值未替换视同缺字段,plan --done 报错阻断):FR-XX、D-XXX、src/example/file.ts、一句话说明这个 task、具体步骤 1、可验证的验收条件 1、边界约束 1
510
514
  - **规范约定 5 字段**(应填但缺失只影响规范性,不阻断):author、created_at、priority、depends_on、blocks
511
515
  - 每个 task 总长度 20~40 行
512
516
  - **一致性自查**:
@@ -66,6 +66,9 @@ step 3 --done 审计会把 src 核心文件(CLI / 状态机 / 注入框架等
66
66
  ### 输出
67
67
  实现摘要 + 修改文件列表
68
68
 
69
+ ### 📌 末步预告:结果摘要四字段(step3 --done 硬校验,缺一被拒)
70
+ 下一步(step3)--done 必须给四字段结果摘要(推荐 \`--req/--cause/--solution/--result\` 四参数,CLI 自动合成;兼容 \`--output "需求:… 根因:… 方案:… 结果:…"\`)。其中「结果:验证情况(测试数 / lint / typecheck / 部署状态)」的素材在**本步**产生——实现摘要里请顺手记下具体验证数据(如「3 个单测通过、lint 0 告警」),step3 直接引用,避免到收尾时回忆不起来或被硬校验打回重跑。
71
+
69
72
  ### 铁律
70
73
  - 不要修改无关文件
71
74
  - 不要编造不存在的 CLI 子命令
@@ -97,7 +100,9 @@ sillyspec run quick --done --change <id> --req "一句话语义化短标题(
97
100
  结果:验证情况(测试数 / lint / typecheck / 部署状态)
98
101
  \`\`\`
99
102
 
100
- ⚠️ **「需求:」(= --req)就是 QUICKLOG 条目标题**——CLI 提取该字段、截到首个标点(,。;,;)、超 80 字再截断。写成完整需求长句会被截成语义不完整的状语前半段(如「在 X 时,…」标题只剩「在 X 时」),事后还得手改。一句话说清改了什么(如「登录限流修复——INCR 计数误清」),需求背景放「根因:/方案:」字段。旧形式正文内避免嵌套全角冒号(标签后紧接嵌套冒号会被拆分判定缺字段)——这正是四参数形式消灭的事故面。
103
+ ⚠️ **「需求:」(= --req)就是 QUICKLOG 条目标题**——CLI 提取该字段、截到首个标点(,。;,;)、超 80 字再截断。写成完整需求长句会被截成语义不完整的状语前半段(如「在 X 时,…」标题只剩「在 X 时」),事后还得手改。一句话说清改了什么(如「登录限流修复——INCR 计数误清」),需求背景放「根因:/方案:」字段。单行旧形式的风险仅在「正文里再次出现顶层标签字样」——需求:/根因:/方案:/结果: 字样前导恰是空白或句末标点(。;!?)时,会被边界扫描误当真实字段边界(错位/缺字段被拒)——这正是四参数形式消灭的事故面。注意区分:根因块内以列表行形态出现的嵌套子字段(\`- 现象:\` \`- 根因:\` \`- 护栏:\` \`- 证据:\`)是**合法形态**——「- 」前缀不构成顶层标签、不参与拆分判定,顶层四字段边界不受影响(postmortem 用法见下方可选提示)。
104
+
105
+ > 💡 **可选(非必填)**——根因字段属 postmortem 场景(踩坑修复、值得留档复盘)时,可改用多行 \`--output\` 形式,在根因块内按列表行补四子字段:\`根因:\` 行后换行接 \`- 现象:…\`(出了什么问题)、\`- 根因:…\`(深层原因)、\`- 护栏:…\`(防再犯措施)、\`- 证据:…\`(可追溯路径,如 sillyspec agent-log --json 输出的会话日志 jsonl 路径、review.json、verify-result.md)。该列表行形态为合法格式(顶层标签边界不受影响,详见上方说明);常规场景仍推荐四参数形式。
101
106
 
102
107
  ### 📎 文件括注 --file-notes(可选,省事后手改文件行)
103
108
  若希望 QUICKLOG「文件:」行直接落盘为多行带括注(而非单行逗号路径),\`--done\` 时加 \`--file-notes\`,CLI 落盘为多行 \`- path(括注)\`,免去事后手改: