sillyspec 3.20.3 → 3.20.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 (225) hide show
  1. package/.claude/skills/sillyspec-archive/SKILL.md +21 -17
  2. package/.claude/skills/sillyspec-auto/SKILL.md +83 -77
  3. package/.claude/skills/sillyspec-brainstorm/SKILL.md +44 -17
  4. package/.claude/skills/sillyspec-commit/SKILL.md +106 -105
  5. package/.claude/skills/sillyspec-continue/SKILL.md +45 -44
  6. package/.claude/skills/sillyspec-doctor/SKILL.md +31 -22
  7. package/.claude/skills/sillyspec-execute/SKILL.md +30 -17
  8. package/.claude/skills/sillyspec-explore/SKILL.md +109 -96
  9. package/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
  10. package/.claude/skills/sillyspec-plan/SKILL.md +21 -52
  11. package/.claude/skills/sillyspec-propose/SKILL.md +21 -17
  12. package/.claude/skills/sillyspec-quick/SKILL.md +21 -17
  13. package/.claude/skills/sillyspec-resume/SKILL.md +68 -111
  14. package/.claude/skills/sillyspec-scan/SKILL.md +21 -17
  15. package/.claude/skills/sillyspec-state/SKILL.md +54 -54
  16. package/.claude/skills/sillyspec-status/SKILL.md +21 -17
  17. package/.claude/skills/sillyspec-verify/SKILL.md +21 -17
  18. package/.claude/skills/sillyspec-workspace/SKILL.md +157 -149
  19. package/.husky/pre-push +13 -0
  20. package/CLAUDE.md +18 -0
  21. package/README.md +198 -186
  22. package/SKILL.md +90 -87
  23. package/bin/sillyspec.js +2 -2
  24. package/docs/brainstorm-plan-contract.md +64 -0
  25. package/docs/plan-execute-contract.md +123 -0
  26. package/docs/platform-scan-protocol.md +298 -0
  27. package/docs/revision-mode.md +115 -0
  28. package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +99 -0
  29. package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +218 -0
  30. package/docs/sillyspec/file-lifecycle/stage-artifacts.md +167 -0
  31. package/docs/sillyspec/file-lifecycle/storage-and-state.md +148 -0
  32. package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +211 -0
  33. package/docs/sillyspec/file-lifecycle.md +131 -0
  34. package/docs/workflow-contract-regression.md +106 -0
  35. package/docs/worktree-isolation.md +252 -0
  36. package/package.json +40 -34
  37. package/packages/dashboard/dist/assets/{index-D1EVTLmc.js → index-Bq_Z2hne.js} +7446 -7446
  38. package/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
  39. package/packages/dashboard/dist/index.html +16 -16
  40. package/packages/dashboard/dist/prototype-dashboard.html +836 -0
  41. package/packages/dashboard/dist/prototype-overview.html +256 -0
  42. package/packages/dashboard/index.html +15 -15
  43. package/packages/dashboard/package-lock.json +2384 -2384
  44. package/packages/dashboard/package.json +25 -25
  45. package/packages/dashboard/public/prototype-dashboard.html +836 -0
  46. package/packages/dashboard/public/prototype-overview.html +256 -0
  47. package/packages/dashboard/server/executor.js +86 -86
  48. package/packages/dashboard/server/index.js +588 -508
  49. package/packages/dashboard/server/parser.js +526 -458
  50. package/packages/dashboard/server/watcher.js +350 -342
  51. package/packages/dashboard/src/App.vue +558 -325
  52. package/packages/dashboard/src/components/ActionBar.vue +93 -84
  53. package/packages/dashboard/src/components/CommandPalette.vue +96 -92
  54. package/packages/dashboard/src/components/DetailPanel.vue +137 -137
  55. package/packages/dashboard/src/components/DocPreview.vue +105 -8
  56. package/packages/dashboard/src/components/DocTree.vue +75 -19
  57. package/packages/dashboard/src/components/HResizeHandle.vue +48 -0
  58. package/packages/dashboard/src/components/LogStream.vue +65 -65
  59. package/packages/dashboard/src/components/PipelineStage.vue +95 -75
  60. package/packages/dashboard/src/components/PipelineView.vue +156 -129
  61. package/packages/dashboard/src/components/ProjectCard.vue +187 -0
  62. package/packages/dashboard/src/components/ProjectList.vue +210 -210
  63. package/packages/dashboard/src/components/ProjectOverview.vue +113 -139
  64. package/packages/dashboard/src/components/StageBadge.vue +67 -53
  65. package/packages/dashboard/src/components/StepCard.vue +94 -89
  66. package/packages/dashboard/src/components/VResizeHandle.vue +61 -0
  67. package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -48
  68. package/packages/dashboard/src/components/detail/GitDetail.vue +61 -61
  69. package/packages/dashboard/src/components/detail/TechDetail.vue +43 -43
  70. package/packages/dashboard/src/composables/useDashboard.js +192 -164
  71. package/packages/dashboard/src/composables/useKeyboard.js +119 -119
  72. package/packages/dashboard/src/composables/useLayout.js +131 -0
  73. package/packages/dashboard/src/composables/useWebSocket.js +129 -129
  74. package/packages/dashboard/src/main.js +8 -8
  75. package/packages/dashboard/src/style.css +132 -132
  76. package/packages/dashboard/vite.config.js +18 -18
  77. package/src/brainstorm-postcheck.js +158 -0
  78. package/src/change-list.js +52 -0
  79. package/src/change-risk-profile.js +352 -0
  80. package/src/classify-change.js +73 -0
  81. package/src/constants.js +70 -0
  82. package/src/contract-matrix.js +278 -0
  83. package/src/db.js +201 -0
  84. package/src/endpoint-extractor.js +315 -0
  85. package/src/hooks/claude-pre-tool-use.cjs +125 -0
  86. package/src/hooks/worktree-guard.js +761 -0
  87. package/src/index.js +922 -252
  88. package/src/init.js +470 -375
  89. package/src/knowledge-match.js +130 -0
  90. package/src/migrate.js +117 -117
  91. package/src/modules.js +482 -0
  92. package/src/progress.js +1734 -508
  93. package/src/run.js +3465 -652
  94. package/src/scan-postcheck.js +387 -0
  95. package/src/setup.js +398 -460
  96. package/src/stage-contract.js +700 -0
  97. package/src/stages/archive.js +160 -54
  98. package/src/stages/brainstorm-auto.js +229 -0
  99. package/src/stages/brainstorm.js +645 -239
  100. package/src/stages/doctor.js +365 -312
  101. package/src/stages/execute.js +634 -264
  102. package/src/stages/explore.js +34 -0
  103. package/src/stages/index.js +29 -35
  104. package/src/stages/knowledge.js +498 -0
  105. package/src/stages/plan-postcheck.js +518 -0
  106. package/src/stages/plan.js +582 -279
  107. package/src/stages/propose.js +174 -115
  108. package/src/stages/quick.js +102 -63
  109. package/src/stages/scan.js +558 -141
  110. package/src/stages/status.js +65 -65
  111. package/src/stages/verify.js +322 -135
  112. package/src/sync.js +497 -0
  113. package/src/task-review.js +346 -0
  114. package/src/workflow.js +785 -0
  115. package/src/worktree-apply.js +549 -0
  116. package/src/worktree-deps.js +185 -0
  117. package/src/worktree.js +982 -0
  118. package/templates/workflows/archive-impact.yaml +79 -0
  119. package/templates/workflows/scan-docs.yaml +132 -0
  120. package/test/brainstorm-plan-contract.test.mjs +273 -0
  121. package/test/check-syntax.mjs +26 -0
  122. package/test/cli-top-level-aliases.test.mjs +174 -0
  123. package/test/contract-artifacts.test.mjs +323 -0
  124. package/test/decision-supersede.test.mjs +277 -0
  125. package/test/knowledge-match.test.mjs +231 -0
  126. package/test/plan-execute-contract.test.mjs +357 -0
  127. package/test/plan-optimization.test.mjs +572 -0
  128. package/test/platform-artifacts.test.mjs +190 -0
  129. package/test/platform-failure-samples.test.mjs +199 -0
  130. package/test/platform-recovery-chain.test.mjs +179 -0
  131. package/test/platform-recovery.test.mjs +167 -0
  132. package/test/platform-scan-p0.test.mjs +175 -0
  133. package/test/revision-v1.test.mjs +1145 -0
  134. package/test/run-sanitize-project-name.test.mjs +51 -0
  135. package/test/run-scan-postcheck-fail.test.mjs +64 -0
  136. package/test/run-scan-project-parse.test.mjs +200 -0
  137. package/test/run-tests.mjs +48 -0
  138. package/test/runtime-cleanup-keeps-worktree.test.mjs +107 -0
  139. package/test/scan-docs-yaml-placeholders.test.mjs +84 -0
  140. package/test/scan-knowledge.test.mjs +175 -0
  141. package/test/scan-paths.test.mjs +68 -0
  142. package/test/scan-postcheck-project-priority.test.mjs +85 -0
  143. package/test/scan-postcheck.test.mjs +197 -0
  144. package/test/scan-workflow-anyfailed-block.test.mjs +52 -0
  145. package/test/spec-dir.test.mjs +206 -0
  146. package/test/stage-contract-failed-post-check.test.mjs +102 -0
  147. package/test/stage-contract.test.mjs +299 -0
  148. package/test/stage-definitions.test.mjs +39 -0
  149. package/test/wait-gates.test.mjs +501 -0
  150. package/test/workflow-spec-base.test.mjs +142 -0
  151. package/test/worktree-deps-provision.test.mjs +148 -0
  152. package/test/worktree-guard.test.mjs +136 -0
  153. package/test/worktree-native-overlay.test.mjs +188 -0
  154. package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +0 -97
  155. package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +0 -51
  156. package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +0 -29
  157. package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +0 -34
  158. package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +0 -13
  159. package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +0 -43
  160. package/.sillyspec/changes/auto-mode/design.md +0 -50
  161. package/.sillyspec/changes/auto-mode/proposal.md +0 -19
  162. package/.sillyspec/changes/auto-mode/requirements.md +0 -21
  163. package/.sillyspec/changes/auto-mode/tasks.md +0 -7
  164. package/.sillyspec/changes/brainstorm-archive/2026-04-05-dashboard-design.md +0 -206
  165. package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +0 -199
  166. package/.sillyspec/changes/dashboard/design.md +0 -219
  167. package/.sillyspec/changes/dashboard/design.md.braindraft +0 -206
  168. package/.sillyspec/changes/run-command-design/design.md +0 -1230
  169. package/.sillyspec/changes/unified-docs-design/design.md +0 -199
  170. package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
  171. package/.sillyspec/knowledge/INDEX.md +0 -8
  172. package/.sillyspec/knowledge/uncategorized.md +0 -3
  173. package/.sillyspec/plans/2026-04-05-dashboard.md +0 -737
  174. package/.sillyspec/projects/sillyspec.yaml +0 -3
  175. package/dist/steps/brainstorm/01-load-context.md +0 -30
  176. package/dist/steps/brainstorm/02-reuse-check.md +0 -6
  177. package/dist/steps/brainstorm/03-prototype-analysis.md +0 -11
  178. package/dist/steps/brainstorm/04-module-split.md +0 -23
  179. package/dist/steps/brainstorm/05-dialog-explore.md +0 -8
  180. package/dist/steps/brainstorm/06-propose-approaches.md +0 -3
  181. package/dist/steps/brainstorm/07-present-design.md +0 -3
  182. package/dist/steps/brainstorm/08-write-design.md +0 -21
  183. package/dist/steps/brainstorm/09-self-review.md +0 -15
  184. package/dist/steps/brainstorm/10-user-confirm.md +0 -3
  185. package/dist/steps/brainstorm/11-output-spec.md +0 -7
  186. package/dist/steps/brainstorm/manifest.yaml +0 -26
  187. package/dist/steps/execute/01-load-context.md +0 -41
  188. package/dist/steps/execute/02-scan-conventions.md +0 -47
  189. package/dist/steps/execute/03-skill-mcp.md +0 -19
  190. package/dist/steps/execute/04-assign-task.md +0 -22
  191. package/dist/steps/execute/04b-prompt-template.md +0 -54
  192. package/dist/steps/execute/05-write-test.md +0 -7
  193. package/dist/steps/execute/06-write-code.md +0 -8
  194. package/dist/steps/execute/07-run-test.md +0 -26
  195. package/dist/steps/execute/08-fix-issues.md +0 -28
  196. package/dist/steps/execute/09-next-task.md +0 -33
  197. package/dist/steps/execute/manifest.yaml +0 -28
  198. package/dist/steps/plan/01-load-context.md +0 -22
  199. package/dist/steps/plan/02-anchor-confirm.md +0 -1
  200. package/dist/steps/plan/03-expand-tasks.md +0 -33
  201. package/dist/steps/plan/04-mark-order.md +0 -15
  202. package/dist/steps/plan/05-e2e-planning.md +0 -17
  203. package/dist/steps/plan/06-self-check.md +0 -16
  204. package/dist/steps/plan/07-save.md +0 -1
  205. package/dist/steps/plan/manifest.yaml +0 -18
  206. package/dist/steps/scan/01-env-detect.md +0 -51
  207. package/dist/steps/scan/02-tech-stack.md +0 -16
  208. package/dist/steps/scan/03-conventions.md +0 -16
  209. package/dist/steps/scan/04-structure.md +0 -19
  210. package/dist/steps/scan/05-quality.md +0 -18
  211. package/dist/steps/scan/06-complete.md +0 -49
  212. package/dist/steps/scan/manifest.yaml +0 -16
  213. package/dist/steps/verify/01-load-specs.md +0 -28
  214. package/dist/steps/verify/02-check-tasks.md +0 -1
  215. package/dist/steps/verify/03-check-design.md +0 -6
  216. package/dist/steps/verify/04-run-tests.md +0 -7
  217. package/dist/steps/verify/05-e2e-tests.md +0 -27
  218. package/dist/steps/verify/05b-e2e-fix.md +0 -33
  219. package/dist/steps/verify/06-code-quality.md +0 -25
  220. package/dist/steps/verify/07-lint-check.md +0 -27
  221. package/dist/steps/verify/08-output-report.md +0 -14
  222. package/dist/steps/verify/manifest.yaml +0 -22
  223. package/packages/dashboard/dist/assets/index-DGe8CqeP.css +0 -1
  224. package/src/derive.js +0 -147
  225. package/src/step.js +0 -543
@@ -1,264 +1,634 @@
1
- import { existsSync, readFileSync } from 'fs'
2
- import path from 'path'
3
-
4
- export const definition = {
5
- name: 'execute',
6
- title: '波次执行',
7
- description: '子代理并行 + 强制 TDD + 两阶段审查',
8
- steps: [] // 动态构建,由 buildExecuteSteps() 生成
9
- }
10
-
11
- // 固定前缀步骤定义
12
- const fixedPrefix = [
13
- {
14
- name: '状态检查',
15
- prompt: `检查当前状态,确认可以执行 execute。
16
-
17
- ### 操作
18
- 1. 运行 \`sillyspec progress show\`
19
- 2. 确认 currentStage 为 execute
20
- 3. 如果不是 检查是否有未完成的 tasks.md
21
- 4. 确认执行范围($ARGUMENTS 指定 wave/task 或全部)
22
-
23
- ### 输出
24
- 当前状态 + 执行范围确认`,
25
- outputHint: '当前状态 + 执行范围',
26
- optional: false
27
- },
28
- {
29
- name: '加载上下文',
30
- prompt: `加载计划、设计和代码库上下文。
31
-
32
- ### 操作
33
- 1. 读取 tasks.md(执行计划)
34
- 2. 读取 design.md(技术方案)
35
- 3. 读取 CONVENTIONS.md、ARCHITECTURE.md
36
- 4. 读取 local.yaml(构建命令)
37
- 5. 加载 CODEBASE-OVERVIEW.md
38
-
39
- ### 输出
40
- 已加载的上下文摘要`,
41
- outputHint: '上下文摘要',
42
- optional: false
43
- },
44
- {
45
- name: '确认执行范围',
46
- prompt: `解析任务,确认执行范围和确认模式。
47
-
48
- ### 操作
49
- 1. plan 中解析 Wave 分组和任务列表
50
- 2. 根据任务描述关键词为每个 Task 建议模型:
51
- - 架构/复杂推理 最强模型
52
- - 常规实现 中等模型
53
- - 简单修改 快速模型
54
- - 文档/写作 写作模型
55
- 3. 用户在 tasks.md 中的 [model:xxx] 标签优先
56
- 4. 询问用户执行确认频率:
57
- - 每个 Wave 确认 每个 Wave 完成后展示结果
58
- - AI 自主判断 BLOCKED 或计划外变更时才询问
59
- - 全自动 — 全部自动执行
60
- 5. 查询知识库:读取 \`.sillyspec/knowledge/INDEX.md\`,根据 Task 关键词匹配
61
-
62
- ### 输出
63
- Wave 分组 + 模型分配 + 确认模式 + 知识库匹配结果
64
-
65
- ### 注意
66
- - 默认推荐"每个 Wave 确认"`,
67
- outputHint: 'Wave 分组 + 模型分配',
68
- optional: false
69
- }
70
- ]
71
-
72
- // 固定后缀步骤定义
73
- const fixedSuffix = [
74
- {
75
- name: '知识库审阅',
76
- prompt: `检查本轮执行产生的新知识。
77
-
78
- ### 操作
79
- 1. 检查 \`.sillyspec/knowledge/uncategorized.md\` 中待确认条目
80
- 2. 如有 → 提示用户审阅
81
- 3. 用户确认后改为 [已确认],可归类到专题文件
82
-
83
- ### 输出
84
- 新知识条目数量 + 审阅提示(或"无新知识")`,
85
- outputHint: '知识条目数量',
86
- optional: true
87
- },
88
- {
89
- name: '完成确认',
90
- prompt: `所有任务完成后的收尾。
91
-
92
- ### 操作
93
- 1. 询问用户下一步:
94
- - 验证 → sillyspec run verify
95
- - 归档 → /sillyspec:archive
96
- - 继续开发
97
- 2. 提示 git add 暂存变更
98
-
99
- ### 输出
100
- 用户选择 + 下一步命令
101
-
102
- ### 注意
103
- - 完成后运行 \`sillyspec run execute --done\` 即可自动推进阶段`,
104
- outputHint: '用户选择',
105
- optional: false
106
- }
107
- ]
108
-
109
- /**
110
- * 从 plan 文件解析 Wave 分组
111
- */
112
- function parseWavesFromPlan(planContent) {
113
- const waves = []
114
- const lines = planContent.split('\n')
115
- let currentWave = null
116
- let currentTask = null
117
-
118
- for (const line of lines) {
119
- const waveMatch = line.match(/^#+\s*Wave\s+(\d+)/i)
120
- if (waveMatch) {
121
- currentWave = { index: parseInt(waveMatch[1]), tasks: [] }
122
- currentTask = null
123
- waves.push(currentWave)
124
- continue
125
- }
126
-
127
- if (!currentWave) continue
128
-
129
- const taskMatch = line.match(/^[-*]\s*\[[ x]\]\s*(.+)/)
130
- if (taskMatch) {
131
- currentTask = {
132
- name: taskMatch[1].trim(),
133
- file: '',
134
- steps: '',
135
- reference: ''
136
- }
137
- // 兼容旧格式:任务名后跟 (文件路径)
138
- const fileMatch = taskMatch[1].match(/\(([^)]+)\)$/)
139
- if (fileMatch) {
140
- currentTask.file = fileMatch[1]
141
- currentTask.name = taskMatch[1].replace(/\([^)]+\)$/, '').trim()
142
- }
143
- currentWave.tasks.push(currentTask)
144
- continue
145
- }
146
-
147
- // 解析子行信息(修改/参考/步骤)
148
- if (currentTask) {
149
- const modMatch = line.match(/^\s+-\s*修改:\s*(.+)/)
150
- if (modMatch) { currentTask.file = modMatch[1].trim(); continue }
151
-
152
- const refMatch = line.match(/^\s+-\s*参考:\s*(.+)/)
153
- if (refMatch) { currentTask.reference = refMatch[1].trim(); continue }
154
-
155
- const stepMatch = line.match(/^\s+-\s*步骤:/)
156
- if (stepMatch) { currentTask.steps = line.replace(/^\s+-\s*步骤:\s*/, '').trim(); continue }
157
-
158
- // 步骤续行(数字开头的子步骤)
159
- if (currentTask.steps && line.match(/^\s+\d+\./)) {
160
- currentTask.steps += '\n' + line.trim()
161
- }
162
- }
163
- }
164
-
165
- return waves
166
- }
167
-
168
- /**
169
- * 为 Wave 生成 prompt
170
- */
171
- function buildWavePrompt(wave, waveIndex, changeDir) {
172
- const taskList = wave.tasks.map((t, ti) => {
173
- const taskNum = String(t.index || (ti + 1)).padStart(2, '0')
174
- const taskFile = changeDir ? `${changeDir}/tasks/task-${taskNum}.md` : ''
175
- const taskFileExists = taskFile && existsSync(taskFile)
176
- let s = `- [ ] ${t.name}`
177
- if (t.file) s += ` (${t.file})`
178
- if (taskFileExists) {
179
- try {
180
- const taskContent = readFileSync(taskFile, 'utf8').trim()
181
- s += `
182
- \n### 📋 任务蓝图(task-${taskNum}.md)\n${taskContent}`
183
- } catch (e) {
184
- s += `
185
- \n### 📋 任务蓝图(task-${taskNum}.md)\n⚠️ 读取失败: ${e.message}`
186
- }
187
- }
188
- if (t.reference) s += `\n 参考: ${t.reference}`
189
- if (t.steps) s += `\n 步骤: ${t.steps}`
190
- return s
191
- }).join('\n')
192
- const { join } = path
193
- const hasTaskBlueprints = changeDir && existsSync(join(changeDir, 'tasks'))
194
- const taskBlueprintRule = hasTaskBlueprints
195
- ? '每个任务有独立的 task-N.md 蓝图——只做蓝图里写的事,不要实现蓝图之外的功能。如果蓝图有问题,**停下来反馈**,不要自己改。问题归因:实现困难 → task 蓝图没写好 → plan 没做好 → design 有缺陷。'
196
- : '如果发现 plan 不合理,**停下来反馈**,不要自己改方案。问题归因:实现困难 → plan 没做好 → design 有缺陷。'
197
- return `## Wave ${waveIndex}: 执行以下任务
198
-
199
- ### Wave 开始前
200
- 1. 读取 design.md 的「编码铁律」章节(如果存在),严格遵守
201
- 2. 读取 plan.md 了解全局任务划分和依赖关系
202
- 2. 确认本 Wave 的输入/输出契约(前置 Wave 产出了什么,本 Wave 需要消费什么)
203
- 3. 检查前置 Wave 的产出是否完整(文件是否存在、测试是否通过)
204
- 4. **上下文分层加载**:
205
- - 🔥 热上下文:design.md 编码铁律 + 当前 Wave 任务(必须加载)
206
- - 🌡️ 温上下文:CONVENTIONS.md + ARCHITECTURE.md(需要时加载)
207
- - ❄️ 冷上下文:其他变更的 design.md、历史 plan.md(不要主动加载,除非明确需要)
208
-
209
- ### 本 Wave 任务
210
- ${taskList}
211
-
212
- ### 执行要求
213
- 1. 按任务顺序执行,同一 Wave 内任务可并行
214
- 2. 铁律:先读后写、grep 确认方法存在、不编造、TDD
215
- 3. **禁止发散思维**:你是代码搬运工,严格按任务描述执行,不增不减不改。${taskBlueprintRule}
216
- 4. **Reverse Sync**:发现 Bug 或实现与 design.md/task-N.md 不一致时,先检查是代码错了还是文档有遗漏,有遗漏则先修文档再修代码。
217
- 3. **不要频繁编译!** 编译很慢,只在以下情况运行:
218
- - 写了大量代码后需要验证语法正确性
219
- - 最后一个 Wave 完成后做一次全量编译验证
220
- - 用户明确要求编译时
221
- 4. 单个任务完成后只跑**对应模块的单元测试**(TDD 绿灯确认),不要跑全量编译
222
- 5. 每个任务完成后:
223
- - 勾选 task-N.md 中的验收标准 checkbox
224
- - 勾选 plan.md / tasks.md 中对应任务的 checkbox
225
- - 记录改动文件和测试结果
226
- 6. 遇到 BLOCKED → 记录原因,选择:重试/跳过/停止
227
-
228
- ### 完成后
229
- 运行 sillyspec run execute --done --input "用户原始反馈" --output "Wave ${waveIndex} 结果摘要"`
230
- }
231
-
232
- /**
233
- * 动态构建 execute 步骤列表
234
- * @param {string|null} planFilePath - plan 文件路径,null 则用默认 3 Wave
235
- * @returns {Array} 步骤列表
236
- */
237
- export function buildExecuteSteps(planFilePath = null) {
238
- let waves
239
- let changeDir = null
240
-
241
- if (planFilePath && existsSync(planFilePath)) {
242
- const planContent = readFileSync(planFilePath, 'utf8')
243
- waves = parseWavesFromPlan(planContent)
244
- // planFilePath 推导 changeDir: .sillyspec/changes/<name>/plan.md
245
- changeDir = path.dirname(planFilePath)
246
- }
247
-
248
- // 如果没解析出 Wave,生成默认 3 个
249
- if (!waves || waves.length === 0) {
250
- waves = []
251
- for (let i = 1; i <= 3; i++) {
252
- waves.push({ index: i, tasks: [{ name: `默认任务 ${i}`, file: 'TBD' }] })
253
- }
254
- }
255
-
256
- const waveSteps = waves.map((wave, i) => ({
257
- name: `Wave ${i + 1} 执行`,
258
- prompt: buildWavePrompt(wave, i + 1, changeDir),
259
- outputHint: `Wave ${i + 1} 执行结果`,
260
- optional: false
261
- }))
262
-
263
- return [...fixedPrefix, ...waveSteps, ...fixedSuffix]
264
- }
1
+ import { existsSync, readFileSync } from 'fs'
2
+ import path from 'path'
3
+
4
+ /**
5
+ * 校验 plan.md 是否满足 execute 执行契约
6
+ * @param {string} planContent - plan.md 文件内容
7
+ * @returns {{ ok: boolean, errors: string[], warnings: string[], tasks: object[], waves: object[] }}
8
+ */
9
+ export function validatePlanForExecute(planContent) {
10
+ const errors = []
11
+ const warnings = []
12
+
13
+ if (!planContent || !planContent.trim()) {
14
+ return { ok: false, errors: ['plan.md 内容为空'], warnings, tasks: [], waves: [] }
15
+ }
16
+
17
+ const waves = parseWavesFromPlan(planContent)
18
+
19
+ // 收集所有 task
20
+ const allTasks = []
21
+ for (const wave of waves) {
22
+ for (const task of wave.tasks) {
23
+ allTasks.push(task)
24
+ }
25
+ }
26
+
27
+ // 检查 1: 至少有一个 checkbox task
28
+ if (allTasks.length === 0) {
29
+ errors.push('plan.md 中没有找到 checkbox task(格式: "- [ ] task-XX: 任务名")')
30
+ return { ok: false, errors, warnings, tasks: allTasks, waves }
31
+ }
32
+
33
+ // 检查 2: task id 唯一性
34
+ const idCounts = {}
35
+ for (const task of allTasks) {
36
+ if (task.index != null) {
37
+ const key = `task-${task.index}`
38
+ idCounts[key] = (idCounts[key] || 0) + 1
39
+ }
40
+ }
41
+ for (const [id, count] of Object.entries(idCounts)) {
42
+ if (count > 1) {
43
+ errors.push(`task id 重复: ${id} 出现 ${count} 次`)
44
+ }
45
+ }
46
+
47
+ // 检查 3: task id 连续性(从 1 开始)
48
+ const ids = allTasks
49
+ .map(t => t.index)
50
+ .filter(i => i != null)
51
+ .sort((a, b) => a - b)
52
+ if (ids.length > 0) {
53
+ const expected = Array.from({ length: ids.length }, (_, i) => ids[0] + i)
54
+ // 只检查以 task-01 起始的情况(常见模式)
55
+ if (ids[0] === 1) {
56
+ for (let i = 0; i < ids.length; i++) {
57
+ if (ids[i] !== i + 1) {
58
+ errors.push(`task id 不连续: 期望 task-${String(i + 1).padStart(2, '0')}, 实际 task-${String(ids[i]).padStart(2, '0')}`)
59
+ break
60
+ }
61
+ }
62
+ }
63
+ }
64
+
65
+ // 检查 4: task name 非空
66
+ for (const task of allTasks) {
67
+ if (!task.name || !task.name.trim()) {
68
+ errors.push(`task-${String(task.index || '?').padStart(2, '0')}: 任务名为空`)
69
+ }
70
+ }
71
+
72
+ // 检查 5: task 无 id 的 warning(不限制只在有 id 时检查)
73
+ for (const wave of waves) {
74
+ for (const task of wave.tasks) {
75
+ if (task.index == null) {
76
+ warnings.push(`Wave ${wave.index}: task "${task.name}" 没有 task id(建议格式 task-XX: 名称)`)
77
+ }
78
+ }
79
+ }
80
+
81
+ return { ok: errors.length === 0, errors, warnings, tasks: allTasks, waves }
82
+ }
83
+
84
+ export const definition = {
85
+ name: 'execute',
86
+ title: '波次执行',
87
+ description: '子代理并行 + 强制 TDD + 两阶段审查',
88
+ steps: [] // 动态构建,由 buildExecuteSteps() 生成
89
+ }
90
+
91
+ // 固定前缀步骤定义
92
+ const fixedPrefix = [
93
+ {
94
+ name: '状态检查',
95
+ prompt: `检查当前状态,确认可以执行 execute。
96
+
97
+ ### 操作
98
+ 1. 运行 \`sillyspec progress show\`
99
+ 2. 确认 currentStage 为 execute
100
+ 3. 如果不是 → 检查是否有未完成的 tasks.md
101
+ 4. 确认执行范围($ARGUMENTS 指定 wave/task 或全部)
102
+
103
+ ### 输出
104
+ 当前状态 + 执行范围确认`,
105
+ outputHint: '当前状态 + 执行范围',
106
+ optional: false
107
+ },
108
+ {
109
+ name: '加载上下文',
110
+ prompt: `加载计划、设计和代码库上下文。
111
+
112
+ ### 操作
113
+ 1. 读取 tasks.md(执行计划)
114
+ 2. 读取 design.md(技术方案)
115
+ 3. 读取 CONVENTIONS.md、ARCHITECTURE.md
116
+ 4. 读取 local.yaml(构建命令)
117
+ 5. 加载 CODEBASE-OVERVIEW.md
118
+
119
+ ### 模块文档加载
120
+ 6. 读取 \`.sillyspec/docs/<project>/modules/_module-map.yaml\`(不存在则跳过以下步骤)
121
+ 7. 根据 plan.md 中的任务文件路径匹配 _module-map.yaml 中的模块
122
+ 8. 读取匹配到的 \`.sillyspec/docs/<project>/modules/<module>.md\`
123
+ 9. 实现代码时遵循模块文档中描述的接口约定、数据流和依赖关系
124
+ 10. **利用模块索引快速定位源码**:
125
+ - 用 entrypoints 字段直接找到模块对外 API 的源码位置
126
+ - 用 main_symbols 字段找到核心类/函数的定义位置
127
+ - 子代理优先读模块卡片理解语义,再读 entrypoints/main_symbols 对应的源码
128
+
129
+ ### 符号影响面扩展检查
130
+ 11. **符号影响面扫描**(Critical — execute 前必做):
131
+ - 读取所有 tasks/task-NN.md,提取每个任务涉及的修改文件
132
+ - 对每个修改文件,检查是否涉及以下变更类型:
133
+ - class 构造函数参数变更(新增/删除/修改参数)
134
+ - 接口(interface)定义变更
135
+ - DTO / 类型定义变更
136
+ - API client 方法签名变更
137
+ - 函数/方法签名变更(参数增删改)
138
+ - 如果涉及上述变更类型,执行调用点搜索:
139
+ \`\`\`bash
140
+ rg "new ClassName\(" src/
141
+ rg "ClassName\(" src/
142
+ rg "methodName\(" src/
143
+ rg "import.*from.*filePath" src/
144
+ \`\`\`
145
+ - 将搜索到的调用点与 plan.md 和 tasks/task-NN.md 的 allowed_paths 对比
146
+ - **发现调用点不在任何 task 的 allowed_paths 中 → 直接阻断 execute**
147
+ - 报告:列出每个受影响符号、调用点位置、是否在任务范围内
148
+ - 如果调用点不在范围内但任务明确写了"不改原因",记录但不阻断
149
+
150
+ ### 输出
151
+ 已加载的上下文摘要(含模块文档 + 源码锚点)`,
152
+ outputHint: '上下文摘要',
153
+ optional: false
154
+ },
155
+ {
156
+ name: '确认 worktree 路径',
157
+ prompt: `确认当前 worktree 状态,提取隔离路径。
158
+
159
+ ### 操作
160
+ 1. 运行 \`sillyspec worktree meta <change-name>\` 读取 meta.json
161
+ 2. 从输出中提取 worktreePath、branch、mode 字段
162
+ 3. 确认 worktree 目录存在(如果是 worktree/native-worktree 模式)
163
+
164
+ ### 铁律
165
+ - **worktree 已由 CLI 在 execute 阶段启动时自动创建,不要自行创建或跳过**
166
+ - **后续所有子代理的 cwd 必须设为该 worktree 路径**
167
+ - 如果 meta.json 不存在(说明创建失败),停止并报错
168
+ - **不要自行检查 git dirty/uncommitted 状态来判断是否可以进入 worktree,CLI 已自动处理**
169
+
170
+ ### 输出
171
+ worktree 路径 + 分支名 + 模式
172
+
173
+ ### 完成后执行
174
+ sillyspec run execute --done --output "worktree 路径 + 分支名 + 模式"`,
175
+ outputHint: 'worktree 路径 + 分支名 + 模式',
176
+ optional: false
177
+ },
178
+ {
179
+ name: '确认执行范围',
180
+ prompt: `解析任务,确认执行范围和确认模式。
181
+
182
+ ### 操作
183
+ 1. plan 中解析 Wave 分组和任务列表
184
+ 2. 根据任务描述关键词为每个 Task 建议模型:
185
+ - 架构/复杂推理 最强模型
186
+ - 常规实现 → 中等模型
187
+ - 简单修改 → 快速模型
188
+ - 文档/写作 写作模型
189
+ 3. 用户在 tasks.md 中的 [model:xxx] 标签优先
190
+ 4. 读取 \`--confirm-mode\` 参数(由 CLI 传入,不需要询问用户):
191
+ - wave — 每个 Wave 完成后展示结果(默认)
192
+ - task 每个 Task 完成后展示结果
193
+ - auto 全部自动执行
194
+ 5. 查询知识库:读取 \`.sillyspec/knowledge/INDEX.md\`,根据 Task 关键词匹配
195
+
196
+ ### 知识命中报告
197
+ {KNOWLEDGE_HIT_REPORT}
198
+
199
+ 如上所示的知识条目与本次任务相关。请阅读这些条目以获取项目约定和已知模式。
200
+ 如无命中条目(Status: no matches),跳过本节。
201
+
202
+ ### 铁律
203
+ - **不要询问用户确认频率**,确认模式由 CLI \`--confirm-mode\` 参数决定
204
+ - 如果未检测到 \`--confirm-mode\`,默认使用 wave 模式`,
205
+ outputHint: 'Wave 分组 + 模型分配',
206
+ optional: false
207
+ }
208
+ ]
209
+
210
+ // 全局验收步骤定义
211
+ const acceptanceSteps = [
212
+ {
213
+ name: '对照设计检查',
214
+ mode: 'acceptance',
215
+ prompt: `对照 design.md 检查所有实现是否与设计一致。
216
+
217
+ ### 执行方式
218
+ 本步骤由当前 agent 汇总执行,不需要为每个检查项启动独立子代理。
219
+ 如需深入验证某个模块,可启动单个 QA 子代理统一处理。
220
+
221
+ ### 操作
222
+ 1. 读取 design.md(技术方案)
223
+ 2. 逐一对照 design.md 中的设计要点与实际代码实现
224
+ 3. 检查接口签名、数据结构、模块划分是否一致
225
+ 4. 记录偏差项(偏差 ≠ 错误,可能是合理的实现调整)
226
+
227
+ ### 输出
228
+ 检查清单:每项设计要点的实现状态 ✅/⚠️/❌ + 偏差说明`,
229
+ outputHint: '设计对照检查清单',
230
+ optional: false
231
+ },
232
+ {
233
+ name: '运行测试',
234
+ mode: 'acceptance',
235
+ prompt: `运行所有测试,验证代码质量。
236
+
237
+ ### 执行方式
238
+ 本步骤由当前 agent 执行,不需要启动独立子代理。
239
+
240
+ ### 操作
241
+ 1. 读取 local.yaml 获取构建和测试命令
242
+ 2. 运行测试套件(单元测试、集成测试)
243
+ 3. 运行 lint 检查
244
+ 4. 如果有测试失败 分析原因,标注是代码问题还是测试本身的问题
245
+ 5. 汇总测试结果
246
+
247
+ ### 输出
248
+ 测试结果摘要:通过/失败/跳过数量 + 失败项分析`,
249
+ outputHint: '测试结果摘要',
250
+ optional: false
251
+ },
252
+ {
253
+ name: '代码审查',
254
+ mode: 'acceptance',
255
+ prompt: `对本次变更进行代码审查。
256
+
257
+ ### 执行方式
258
+ 本步骤由当前 agent 或一个 QA agent 汇总执行,不需要为每个文件启动独立子代理。
259
+
260
+ ### 操作
261
+ 1. 检查 git diff 查看所有变更
262
+ 2. 审查要点:
263
+ - 代码风格是否符合 CONVENTIONS.md
264
+ - 是否有明显的 bug 或安全漏洞
265
+ - 是否有未处理的 TODO/FIXME
266
+ - 错误处理是否完善
267
+ - 是否有冗余代码或可简化的逻辑
268
+ 3. 对照 ARCHITECTURE.md 检查架构合规性
269
+
270
+ ### 输出
271
+ 审查结果:问题列表(严重程度 + 建议修复方式)+ 总体评价`,
272
+ outputHint: '代码审查结果',
273
+ optional: true
274
+ }
275
+ ]
276
+
277
+ // 固定后缀步骤定义
278
+ const fixedSuffix = [
279
+ {
280
+ name: '知识库审阅',
281
+ prompt: `检查本轮执行产生的新知识。
282
+
283
+ ### 操作
284
+ 1. 检查 \.sillyspec/knowledge/uncategorized.md\` 中待确认条目
285
+ 2. 如有 → 提示用户审阅
286
+ 3. 用户确认后改为 [已确认],可归类到专题文件
287
+
288
+ ### 输出
289
+ 新知识条目数量 + 审阅提示(或"无新知识")`,
290
+ outputHint: '知识条目数量',
291
+ optional: true
292
+ },
293
+ {
294
+ name: '完成确认',
295
+ prompt: `所有任务完成后的收尾。
296
+
297
+ 先检查当前 worktree 的隔离模式:
298
+ \`\`\`bash
299
+ node -e "import('./src/worktree.js').then(w => { const wm = new w.WorktreeManager(); const m = wm.getMeta('<change-name>'); console.log(m ? JSON.stringify({mode: m.mode, path: m.worktreePath}) : 'no meta'); })"
300
+ \`\`\`
301
+
302
+ ### 操作(mode = worktree,SillySpec 创建的隔离 worktree)
303
+
304
+ **自动审计流程(不需要用户确认代码):**
305
+
306
+ 1. 运行 \`sillyspec worktree assess <change-name>\` 自动风险审计
307
+ 2. 系统自动检查:
308
+ - patch --check 是否通过
309
+ - 变更是否在 allowed_paths 内
310
+ - 主工作区 baseline 是否变化
311
+ - 是否有高风险文件(lockfile/migration/配置/入口)
312
+ - diff 规模是否异常
313
+ 3. 输出 Apply Decision:
314
+
315
+ \`\`\`
316
+ Worktree Apply Decision
317
+ ────────────────────────
318
+ Decision: SAFE | WARNING | BLOCKED
319
+ Changed files: N
320
+ Additions: +N Deletions: -N
321
+ Risky files: none | <list>
322
+ Action: auto-applied | blocked
323
+ \`\`\`
324
+
325
+ 4. **SAFE** → 自动 \`sillyspec worktree apply <change-name>\` + cleanup
326
+ 5. **WARNING** → 自动 apply(有警告但不阻断)+ cleanup
327
+ 6. **BLOCKED** → 不 apply,输出原因,提示用户检查:
328
+ - \`sillyspec worktree diff <change-name>\` 查看具体变更
329
+ - \`sillyspec worktree cleanup <change-name>\` 丢弃
330
+ 7. 建议下一步:\`sillyspec run verify\`
331
+
332
+ ### 操作(mode = native-worktree,用户已有的 linked worktree)
333
+ 1. 同上自动审计流程
334
+ 2. SAFE/WARNING → \`sillyspec worktree apply <change-name>\`
335
+ 3. **不要运行 cleanup**
336
+ 4. 输出 Worktree: kept
337
+ 5. 建议下一步:\`sillyspec run verify\`
338
+
339
+ ### 操作(mode = in-place-fallback,降级模式无隔离目录)
340
+ 1. 展示本次执行摘要(\`git diff\` 查看变更)
341
+ 2. 跳过 apply 和 cleanup
342
+ 3. 输出 Worktree: none
343
+ 4. 建议下一步:\`sillyspec run verify\`
344
+
345
+ ### 输出
346
+ Apply Decision + 下一步建议
347
+
348
+ ### 注意
349
+ - 完成后运行 \`sillyspec run execute --done\` 即可自动推进阶段`,
350
+ outputHint: 'apply 结果',
351
+ optional: false
352
+ }
353
+ ]
354
+
355
+ /**
356
+ * 从 plan 文件解析 Wave 分组
357
+ */
358
+ function parseWavesFromPlan(planContent) {
359
+ const waves = []
360
+ const lines = planContent.split('\n')
361
+ let currentWave = null
362
+ let currentTask = null
363
+
364
+ for (const line of lines) {
365
+ const waveMatch = line.match(/^#+\s*Wave\s+(\d+)/i)
366
+ if (waveMatch) {
367
+ currentWave = { index: parseInt(waveMatch[1]), tasks: [] }
368
+ currentTask = null
369
+ waves.push(currentWave)
370
+ continue
371
+ }
372
+
373
+ // 遇到任何非 Wave 的标题行(## 自检、## 备注 等)时退出当前 Wave 段,
374
+ // 避免「## 自检」段里的 - [x] checkbox 被误当 task 定义解析(导致
375
+ // Contract 校验报 task id 重复/不连续)。详见 docs/sillyspec/plan-postcheck-self-check-checkbox-false-dup.md
376
+ if (/^#+\s/.test(line)) {
377
+ currentWave = null
378
+ currentTask = null
379
+ continue
380
+ }
381
+
382
+ if (!currentWave) continue
383
+
384
+ const taskMatch = line.match(/^[-*]\s*\[[ x]\]\s*(.+)/)
385
+ if (taskMatch) {
386
+ const taskNoMatch = taskMatch[1].match(/\btask-(\d+)\b/i)
387
+ currentTask = {
388
+ index: taskNoMatch ? parseInt(taskNoMatch[1], 10) : null,
389
+ name: taskMatch[1].trim(),
390
+ file: '',
391
+ steps: '',
392
+ reference: ''
393
+ }
394
+ // 兼容旧格式:任务名后跟 (文件路径)
395
+ const fileMatch = taskMatch[1].match(/\(([^)]+)\)$/)
396
+ if (fileMatch) {
397
+ currentTask.file = fileMatch[1]
398
+ currentTask.name = taskMatch[1].replace(/\([^)]+\)$/, '').trim()
399
+ }
400
+ currentWave.tasks.push(currentTask)
401
+ continue
402
+ }
403
+
404
+ // 解析子行信息(修改/参考/步骤)
405
+ if (currentTask) {
406
+ const modMatch = line.match(/^\s+-\s*修改:\s*(.+)/)
407
+ if (modMatch) { currentTask.file = modMatch[1].trim(); continue }
408
+
409
+ const refMatch = line.match(/^\s+-\s*参考:\s*(.+)/)
410
+ if (refMatch) { currentTask.reference = refMatch[1].trim(); continue }
411
+
412
+ const stepMatch = line.match(/^\s+-\s*步骤:/)
413
+ if (stepMatch) { currentTask.steps = line.replace(/^\s+-\s*步骤:\s*/, '').trim(); continue }
414
+
415
+ // 步骤续行(数字开头的子步骤)
416
+ if (currentTask.steps && line.match(/^\s+\d+\./)) {
417
+ currentTask.steps += '\n' + line.trim()
418
+ }
419
+ }
420
+ }
421
+
422
+ return waves
423
+ }
424
+
425
+ /**
426
+ * 为 Wave 生成 prompt(强制子代理执行)
427
+ */
428
+ function buildWavePrompt(wave, waveIndex, changeDir, worktreePath) {
429
+ // ── Contract Matrix:检查是否有 provider/consumer 契约需要注入 ──
430
+ let contractInjection = ''
431
+ if (changeDir) {
432
+ try {
433
+ const { buildContractMatrix, buildConsumerInjection } = require('../contract-matrix.js')
434
+ const planFile = path.join(changeDir, 'plan.md')
435
+ if (existsSync(planFile)) {
436
+ const planContent = readFileSync(planFile, 'utf8')
437
+ const contracts = buildContractMatrix(planContent, changeDir)
438
+ if (contracts.length > 0) {
439
+ // 收集本 wave 所有 task 的注入内容
440
+ const waveTasks = wave.tasks.map((t, ti) => {
441
+ const num = String(t.index || (ti + 1)).padStart(2, '0')
442
+ return `task-${num}`
443
+ })
444
+ const relevantContracts = contracts.filter(c => waveTasks.includes(c.consumer))
445
+ if (relevantContracts.length > 0) {
446
+ contractInjection = `
447
+ ### API Contract Matrix
448
+ 本 Wave 存在前端/后端跨 task 契约:
449
+ ${relevantContracts.map(c => `- **${c.consumer}** 消费 **${c.provider}** 产出的 API`).join('\n')}
450
+ `
451
+ // 为每个 consumer task 生成详细注入
452
+ for (const taskName of waveTasks) {
453
+ const injection = buildConsumerInjection(changeDir, join(changeDir, '..', '..'), taskName, contracts)
454
+ if (injection) {
455
+ contractInjection += `
456
+ ### 子代理 ${taskName} 的契约注入
457
+ 为 ${taskName} 启动子代理时,在子代理 prompt 末尾追加以下内容:
458
+
459
+ <contract-injection>
460
+ ${injection}
461
+ </contract-injection>
462
+ `
463
+ }
464
+ }
465
+ }
466
+ }
467
+ }
468
+ } catch {}
469
+ }
470
+
471
+ // 构建任务摘要(不再内联完整蓝图,减少上下文污染)
472
+ const taskSummary = wave.tasks.map((t, ti) => {
473
+ const taskNum = String(t.index || (ti + 1)).padStart(2, '0')
474
+ const taskRelPath = changeDir
475
+ ? `.sillyspec/changes/${path.basename(changeDir)}/tasks/task-${taskNum}.md`
476
+ : `task-${taskNum}.md`
477
+ const fileInfo = t.file ? ` (${t.file})` : ''
478
+ return `task-${taskNum}: ${t.name}${fileInfo} → ${taskRelPath}`
479
+ }).join('\n')
480
+
481
+ const taskList = wave.tasks.map((t, ti) => {
482
+ const taskNum = String(t.index || (ti + 1)).padStart(2, '0')
483
+ let s = `- [ ] ${t.name}`
484
+ if (t.file) s += ` (${t.file})`
485
+ return s
486
+ }).join('\n')
487
+
488
+ const worktreeSection = (worktreePath)
489
+ ? `
490
+ ### 工作目录(必须严格遵守)
491
+
492
+ 调用 Task 工具启动子代理时,**workdir 参数是强制必传的**。
493
+ 不传 workdir 会导致子代理把文件写到主工作区而非 worktree,破坏隔离。
494
+
495
+ \`\`\`json
496
+ {
497
+ "subagent_type": "general",
498
+ "workdir": "${worktreePath}",
499
+ "prompt": "在此编写任务描述..."
500
+ }
501
+ \`\`\`
502
+
503
+ ### 注意
504
+ 蓝图文件(tasks.md / design.md / proposal.md / requirements.md)在主工作区 .sillyspec/changes/<change>/ 下,它们可能不在 worktree 中。读取蓝图时使用主工作区路径,不要拼接到 worktree 路径下。
505
+ `
506
+ : ''
507
+
508
+ return `## Wave ${waveIndex}: 执行以下任务
509
+
510
+ ## 执行方式(必须严格遵守)
511
+
512
+ **每个任务必须由独立子代理执行,你不要自己写代码。**
513
+
514
+ 你的角色是调度者 + 审查者:
515
+ 1. 为每个任务启动一个子代理(Agent tool),同 Wave 内可并行
516
+ 2. 子代理完成后审查结果
517
+ 3. 勾选 plan.md 中的 checkbox
518
+ 4. 记录改动文件和测试结果
519
+
520
+ ${worktreeSection}
521
+ ### 任务摘要(按需读取完整蓝图)
522
+ 为每个任务启动子代理时,**只需告知任务目标和蓝图文件路径,让子代理按需读取**:
523
+
524
+ ${taskSummary}
525
+
526
+ 子代理 prompt 要点:
527
+ 1. 任务目标(简短描述)
528
+ 2. 蓝图文件路径(让子代理自行读取详情)
529
+ 3. 编码铁律:先读后写、TDD、不编造方法、只做蓝图里写的事、遵守边界处理规则、不超出 allowed_paths
530
+ 4. 如存在模块文档(.sillyspec/docs/*/modules/),按需读取涉及模块的 <module>.md 参考接口约定和数据流
531
+
532
+ ### Wave 开始前
533
+ 1. 读取 design.md 的「编码铁律」章节(如果存在),严格遵守
534
+ 2. 读取 plan.md 了解全局任务划分和依赖关系
535
+ 3. 确认本 Wave 的输入/输出契约(前置 Wave 产出了什么,本 Wave 需要消费什么)
536
+ 4. 检查前置 Wave 的产出是否完整(文件是否存在、测试是否通过)
537
+ 5. **上下文分层加载**:
538
+ - 🔥 热上下文:design.md 编码铁律 + 当前 Wave 任务(必须加载)
539
+ - 🌡️ 温上下文:CONVENTIONS.md + ARCHITECTURE.md(需要时加载)
540
+ - ❄️ 冷上下文:其他变更的 design.md、历史 plan.md(不要主动加载,除非明确需要)
541
+ ${contractInjection}
542
+ ### 本 Wave 任务
543
+ ${taskList}
544
+
545
+ ### 调度要求
546
+ 1. **同一 Wave 内的任务必须并行启动子代理,禁止串行等待。** Wave 的定义就是"无依赖、可并行",不要自行分析依赖关系。如果有依赖应该在 plan.md 的不同 Wave 中。
547
+ 2. **Reverse Sync**:子代理报告实现与 design.md 不一致时,先检查是代码错了还是文档有遗漏
548
+ 3. **不要频繁编译!** 编译很慢,只在以下情况运行:
549
+ - 写了大量代码后需要验证语法正确性
550
+ - 最后一个 Wave 完成后做一次全量编译验证
551
+ - 用户明确要求编译时
552
+ 4. 每个任务完成后:
553
+ - **先写 review.json 再勾选 checkbox**(见下方 Task Review Gate)
554
+ - 记录改动文件和测试结果
555
+ 5. 遇到 BLOCKED → 记录原因,选择:重试/跳过/停止
556
+
557
+ ### Task Review Gate(必须执行,不可跳过)
558
+
559
+ 每个子代理完成后、勾选 checkbox **之前**,你必须创建 task review。
560
+
561
+ **操作步骤:**
562
+ 1. 读取当前 task 的 git diff(从 task 开始到完成的变更)
563
+ 2. 对照 plan.md 中该 task 的描述和 tasks/task-XX.md(如果存在)检查实现是否符合要求
564
+ 3. 写入 review.json 文件
565
+ 4. **只有 review.json 写入成功后,才允许勾选 plan.md 中的 checkbox**
566
+
567
+ **review.json 路径:**
568
+
569
+ task-XX 对应:.sillyspec/.runtime/execute-runs/{EXECUTE_RUN_ID}/tasks/task-XX/review.json
570
+
571
+ 本 execute run 的固定 ID 是:{EXECUTE_RUN_ID}
572
+ **所有 task 的 review.json 必须使用这个 ID,不要自行创建新目录。**
573
+
574
+ **review.json 必填字段:**
575
+
576
+ { "schemaVersion": 1, "task": "task-XX", "base": "<git-base-commit>", "head": "<git-head-commit>",
577
+ "changedFiles": ["src/foo.js"], "specVerdict": "pass|fail|cannot_verify",
578
+ "qualityVerdict": "pass|fail|cannot_verify", "reviewerNotes": "评审说明",
579
+ "requiredEvidence": [] }
580
+
581
+ **评审铁律:**
582
+ - 不信任 implementer 自报结果,对照 diff 和 task brief 验证
583
+ - 只看当前 task 的 diff,不做全仓库漫游审查
584
+ - \`cannot_verify\` 只在确实无法验证且有待补充证据时使用,且 requiredEvidence 必须非空
585
+ - \`sillyspec run execute --done\` 会校验所有 task 的 review.json,缺失或 fail 会阻断完成
586
+
587
+ ### 完成后
588
+ 1. 为每个后端 router task,扫描变更文件提取 API 端点 artifact:
589
+ - 在变更文件中搜索所有 router 注册路径(@router.get/post/put/delete)
590
+ - 将端点清单写入 .sillyspec/.runtime/contract-artifacts/<task-name>/endpoints.json
591
+ - 格式: { "task": "task-XX", "type": "backend_endpoints", "endpoints": [{ "method": "GET", "path": "/api/ppm/xxx" }] }
592
+ 2. 运行 sillyspec run execute --done --input "用户原始反馈" --output "Wave ${waveIndex} 结果摘要"`
593
+ }
594
+
595
+ /**
596
+ * 动态构建 execute 步骤列表
597
+ * @param {string|null} planFilePath - plan 文件路径,null 则用默认 3 Wave
598
+ * @param {{ worktreePath?: string, noWorktree?: boolean }} options
599
+ * @returns {Array} 步骤列表
600
+ */
601
+ export function buildExecuteSteps(planFilePath = null, options = {}) {
602
+ const noWorktree = !!options.noWorktree
603
+ let waves
604
+ let changeDir = null
605
+
606
+ if (planFilePath && existsSync(planFilePath)) {
607
+ const planContent = readFileSync(planFilePath, 'utf8')
608
+ // Plan → Execute 契约由 plan 阶段完成时的 postcheck 把关(run.js completeStep),
609
+ // 此处只负责解析 waves,避免 buildExecuteSteps 与进程退出耦合。
610
+ waves = parseWavesFromPlan(planContent)
611
+ changeDir = path.dirname(planFilePath)
612
+ }
613
+
614
+ // 没解析出 Wave(plan 不存在或不含可识别 task)→ 默认 3 Wave(向后兼容)
615
+ if (!waves || waves.length === 0) {
616
+ waves = []
617
+ for (let i = 1; i <= 3; i++) {
618
+ waves.push({ index: i, tasks: [{ name: `默认任务 ${i}`, file: 'TBD' }] })
619
+ }
620
+ }
621
+
622
+ // 尝试获取 worktree 路径(可能由前缀步骤创建)
623
+ const worktreePath = options.worktreePath || null
624
+
625
+ const waveSteps = waves.map((wave, i) => ({
626
+ name: `Wave ${i + 1} 执行`,
627
+ mode: 'implementation',
628
+ prompt: buildWavePrompt(wave, i + 1, changeDir, worktreePath),
629
+ outputHint: `Wave ${i + 1} 执行结果`,
630
+ optional: false
631
+ }))
632
+
633
+ return [...fixedPrefix, ...waveSteps, ...acceptanceSteps, ...fixedSuffix]
634
+ }