sillyspec 3.20.2 → 3.20.4

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 (133) hide show
  1. package/.claude/skills/sillyspec-archive/SKILL.md +21 -21
  2. package/.claude/skills/sillyspec-auto/SKILL.md +83 -83
  3. package/.claude/skills/sillyspec-brainstorm/SKILL.md +44 -44
  4. package/.claude/skills/sillyspec-commit/SKILL.md +106 -106
  5. package/.claude/skills/sillyspec-continue/SKILL.md +45 -45
  6. package/.claude/skills/sillyspec-doctor/SKILL.md +31 -31
  7. package/.claude/skills/sillyspec-execute/SKILL.md +30 -30
  8. package/.claude/skills/sillyspec-explore/SKILL.md +109 -109
  9. package/.claude/skills/sillyspec-knowledge/SKILL.md +269 -269
  10. package/.claude/skills/sillyspec-plan/SKILL.md +21 -21
  11. package/.claude/skills/sillyspec-propose/SKILL.md +21 -21
  12. package/.claude/skills/sillyspec-quick/SKILL.md +21 -21
  13. package/.claude/skills/sillyspec-resume/SKILL.md +68 -68
  14. package/.claude/skills/sillyspec-scan/SKILL.md +21 -21
  15. package/.claude/skills/sillyspec-state/SKILL.md +54 -54
  16. package/.claude/skills/sillyspec-status/SKILL.md +21 -21
  17. package/.claude/skills/sillyspec-verify/SKILL.md +21 -21
  18. package/.claude/skills/sillyspec-workspace/SKILL.md +157 -157
  19. package/.husky/pre-push +13 -13
  20. package/CLAUDE.md +18 -18
  21. package/README.md +198 -188
  22. package/SKILL.md +90 -91
  23. package/bin/sillyspec.js +2 -2
  24. package/docs/brainstorm-plan-contract.md +64 -64
  25. package/docs/plan-execute-contract.md +123 -123
  26. package/docs/platform-scan-protocol.md +298 -298
  27. package/docs/revision-mode.md +115 -115
  28. package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +99 -99
  29. package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +218 -218
  30. package/docs/sillyspec/file-lifecycle/stage-artifacts.md +167 -167
  31. package/docs/sillyspec/file-lifecycle/storage-and-state.md +148 -148
  32. package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +211 -193
  33. package/docs/sillyspec/file-lifecycle.md +125 -125
  34. package/docs/workflow-contract-regression.md +106 -106
  35. package/docs/worktree-isolation.md +252 -252
  36. package/package.json +40 -40
  37. package/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -7446
  38. package/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -1
  39. package/packages/dashboard/dist/index.html +16 -16
  40. package/packages/dashboard/index.html +15 -15
  41. package/packages/dashboard/package-lock.json +2384 -2384
  42. package/packages/dashboard/package.json +25 -25
  43. package/packages/dashboard/server/executor.js +86 -86
  44. package/packages/dashboard/server/index.js +588 -588
  45. package/packages/dashboard/server/parser.js +526 -526
  46. package/packages/dashboard/server/watcher.js +344 -344
  47. package/packages/dashboard/src/App.vue +558 -558
  48. package/packages/dashboard/src/components/ActionBar.vue +93 -93
  49. package/packages/dashboard/src/components/CommandPalette.vue +96 -96
  50. package/packages/dashboard/src/components/DetailPanel.vue +137 -137
  51. package/packages/dashboard/src/components/LogStream.vue +65 -65
  52. package/packages/dashboard/src/components/PipelineStage.vue +95 -95
  53. package/packages/dashboard/src/components/PipelineView.vue +156 -156
  54. package/packages/dashboard/src/components/ProjectList.vue +210 -210
  55. package/packages/dashboard/src/components/StageBadge.vue +67 -67
  56. package/packages/dashboard/src/components/StepCard.vue +94 -94
  57. package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -48
  58. package/packages/dashboard/src/components/detail/GitDetail.vue +61 -61
  59. package/packages/dashboard/src/components/detail/TechDetail.vue +43 -43
  60. package/packages/dashboard/src/composables/useDashboard.js +170 -170
  61. package/packages/dashboard/src/composables/useKeyboard.js +119 -119
  62. package/packages/dashboard/src/composables/useWebSocket.js +129 -129
  63. package/packages/dashboard/src/main.js +8 -8
  64. package/packages/dashboard/src/style.css +132 -132
  65. package/packages/dashboard/vite.config.js +18 -18
  66. package/src/brainstorm-postcheck.js +158 -158
  67. package/src/change-list.js +52 -52
  68. package/src/change-risk-profile.js +352 -352
  69. package/src/classify-change.js +73 -73
  70. package/src/constants.js +70 -70
  71. package/src/contract-matrix.js +278 -278
  72. package/src/db.js +201 -201
  73. package/src/endpoint-extractor.js +315 -315
  74. package/src/hooks/claude-pre-tool-use.cjs +125 -125
  75. package/src/hooks/worktree-guard.js +653 -653
  76. package/src/index.js +922 -900
  77. package/src/init.js +431 -431
  78. package/src/knowledge-match.js +130 -130
  79. package/src/migrate.js +117 -117
  80. package/src/modules.js +482 -482
  81. package/src/progress.js +1734 -1734
  82. package/src/run.js +3465 -3358
  83. package/src/scan-postcheck.js +387 -383
  84. package/src/setup.js +398 -398
  85. package/src/stage-contract.js +700 -700
  86. package/src/stages/archive.js +160 -160
  87. package/src/stages/brainstorm-auto.js +229 -229
  88. package/src/stages/brainstorm.js +645 -645
  89. package/src/stages/doctor.js +365 -365
  90. package/src/stages/execute.js +625 -625
  91. package/src/stages/explore.js +34 -34
  92. package/src/stages/index.js +29 -29
  93. package/src/stages/knowledge.js +498 -498
  94. package/src/stages/plan-postcheck.js +511 -513
  95. package/src/stages/plan.js +582 -582
  96. package/src/stages/propose.js +174 -174
  97. package/src/stages/quick.js +82 -82
  98. package/src/stages/scan.js +558 -558
  99. package/src/stages/status.js +65 -65
  100. package/src/stages/verify.js +322 -322
  101. package/src/sync.js +497 -497
  102. package/src/task-review.js +346 -346
  103. package/src/workflow.js +785 -785
  104. package/src/worktree-apply.js +549 -549
  105. package/src/worktree-deps.js +185 -0
  106. package/src/worktree.js +982 -932
  107. package/templates/workflows/archive-impact.yaml +79 -79
  108. package/templates/workflows/scan-docs.yaml +132 -132
  109. package/test/brainstorm-plan-contract.test.mjs +273 -273
  110. package/test/check-syntax.mjs +26 -26
  111. package/test/contract-artifacts.test.mjs +323 -323
  112. package/test/decision-supersede.test.mjs +277 -277
  113. package/test/knowledge-match.test.mjs +231 -231
  114. package/test/plan-execute-contract.test.mjs +330 -330
  115. package/test/plan-optimization.test.mjs +572 -572
  116. package/test/platform-artifacts.test.mjs +166 -166
  117. package/test/platform-failure-samples.test.mjs +199 -199
  118. package/test/platform-recovery-chain.test.mjs +167 -167
  119. package/test/platform-recovery.test.mjs +136 -136
  120. package/test/platform-scan-p0.test.mjs +168 -168
  121. package/test/revision-v1.test.mjs +1145 -1145
  122. package/test/run-scan-project-parse.test.mjs +200 -200
  123. package/test/run-tests.mjs +48 -48
  124. package/test/scan-knowledge.test.mjs +175 -175
  125. package/test/scan-paths.test.mjs +68 -68
  126. package/test/scan-postcheck.test.mjs +197 -197
  127. package/test/spec-dir.test.mjs +206 -206
  128. package/test/stage-contract.test.mjs +299 -299
  129. package/test/stage-definitions.test.mjs +39 -39
  130. package/test/wait-gates.test.mjs +496 -496
  131. package/test/worktree-deps-provision.test.mjs +148 -0
  132. package/test/worktree-guard.test.mjs +71 -71
  133. package/test/worktree-native-overlay.test.mjs +188 -188
@@ -1,582 +1,582 @@
1
- import { existsSync, readFileSync, readdirSync } from 'fs'
2
- import path from 'path'
3
-
4
- // 从 plan-postcheck.js 重导出(保持向后兼容)
5
- export {
6
- topoSortWaves,
7
- validateBlueprintConsistency,
8
- validatePlanArtifacts,
9
- validatePlanFeasibility
10
- } from './plan-postcheck.js'
11
-
12
- // 这些解析函数已迁移到 plan-postcheck.js,此处不再定义
13
-
14
- /**
15
- * 校验 design.md 是否满足 plan 执行契约
16
- * 第一版是轻量 markdown 结构检查,不强 schema。
17
- * @param {string} designContent - design.md 文件内容
18
- * @returns {{ ok: boolean, errors: string[], warnings: string[] }}
19
- */
20
- export function validateDesignForPlan(designContent) {
21
- const errors = []
22
- const warnings = []
23
-
24
- if (!designContent || !designContent.trim()) {
25
- return { ok: false, errors: ['design.md 内容为空'], warnings }
26
- }
27
-
28
- const lower = designContent.toLowerCase()
29
-
30
- // 检查 1: 必须包含目标/问题描述(error)
31
- const hasGoal = /(^|\n)#{2,}\s*.*(目标|goal|objective|背景|background|问题|problem|purpose|目的)/i.test(designContent)
32
- if (!hasGoal) {
33
- errors.push('design.md 缺少「目标/背景/问题描述」章节 — plan 需要知道要达成什么')
34
- }
35
-
36
- // 检查 2: 必须包含范围/scope(error)
37
- const hasScope = /(^|\n)#{2,}\s*.*(范围|scope|总体方案|方案|approach|solution|设计|design)/i.test(designContent)
38
- if (!hasScope) {
39
- errors.push('design.md 缺少「范围/总体方案/设计」章节 — plan 需要知道做什么和怎么做')
40
- }
41
-
42
- // 检查 3: 必须包含决策/方案选择(error)
43
- const hasDecisions = /(^|\n)#{2,}\s*.*(决策|decision|选择|choice|方案选择)/i.test(designContent)
44
- || /d-\d+@v\d+/i.test(designContent) // decisions.md 引用 ID
45
- || /decisions?\.md/i.test(designContent) // 引用 decisions.md
46
- if (!hasDecisions) {
47
- errors.push('design.md 缺少「决策/方案选择」— plan 需要基于明确的技术决策来拆分任务')
48
- }
49
-
50
- // 检查 4 (warning): 缺非目标/non-goals
51
- const hasNonGoals = /(^|\n)#{2,}\s*.*(非目标|non-goals?|不做|out of scope|不在范围)/i.test(designContent)
52
- if (!hasNonGoals) {
53
- warnings.push('design.md 缺少「非目标/Non-goals」— 建议明确不做什么,防止 scope creep')
54
- }
55
-
56
- // 检查 5 (warning): 缺约束/风险
57
- const hasConstraints = /(^|\n)#{2,}\s*.*(约束|constraint|限制|limitation|风险|risk|trade-?off)/i.test(designContent)
58
- if (!hasConstraints) {
59
- warnings.push('design.md 缺少「约束/风险/Trade-off」— 建议记录已知约束和风险')
60
- }
61
-
62
- // 检查 6 (warning): 缺文件变更清单
63
- const hasFileChanges = /文件变更|file change|变更清单|changed files/i.test(designContent)
64
- || /^\|\s*(新增|修改|删除|new|modify|delete|update)\s*\|/im.test(designContent)
65
- if (!hasFileChanges) {
66
- warnings.push('design.md 缺少「文件变更清单」— 建议列出预期改动的文件')
67
- }
68
-
69
- return { ok: errors.length === 0, errors, warnings }
70
- }
71
-
72
- export const definition = {
73
- name: 'plan',
74
- title: '实现计划',
75
- description: '编写实现计划 — 按 Wave 分组,每个任务独立文档',
76
- steps: null // 动态生成
77
- }
78
-
79
- // ═══════════════════════════════════════════════════════════════
80
- // 第 1 步(LLM):复杂度分类 + 上下文加载(合并原 ①②③④)
81
- // ═══════════════════════════════════════════════════════════════
82
-
83
- const stepClassify = {
84
- id: 'classify',
85
- name: '复杂度分类与上下文加载',
86
- prompt: `在生成计划之前,先加载上下文并判定本次需求的复杂度等级(plan_level)。
87
-
88
- ### 操作
89
- 1. 运行 \`sillyspec progress show\`,确认 currentStage 为 "plan"
90
- 2. 读取 CODEBASE-OVERVIEW.md + 各子项目上下文
91
- 3. 读取 proposal.md、design.md、requirements.md、tasks.md
92
- 4. 如果存在 decisions.md,必须读取并提取所有当前版本 D-xxx@vN 决策 ID
93
- - 如果发现 priority=P0/P1 且 status=unresolved/blocking 的决策,停止生成计划,要求先回到 brainstorm 的 Design Grill 修正
94
- - 如果发现 superseded 决策,只引用最新版本,不引用旧版本
95
- 5. 读取 CONVENTIONS.md、ARCHITECTURE.md、STACK.md
96
- 6. 读取 local.yaml 获取构建/测试命令
97
- 7. 读取 \`.sillyspec/docs/<project>/modules/_module-map.yaml\`(不存在则跳过)
98
- - 根据 design.md 的文件变更清单匹配模块
99
- - 读取匹配到的模块文档
100
- - 利用模块依赖关系辅助分析(depends_on / used_by)
101
-
102
- ### 分级规则
103
- 判定 plan_level 为 none 时,需**同时满足**以下所有条件:
104
- - 涉及文件 ≤ 2 个
105
- - 不跨模块(改动集中在单个模块内)
106
- - 无 schema / DB / manifest / local.yaml 变更
107
- - 无状态机 / workflow 状态流转变更
108
- - 无 source_root / spec_root / runtime_root 路径隔离规则变更
109
- - 无 validator / postcheck / agent 调度行为变更
110
- - 需求明确,无设计歧义
111
-
112
- 判定为 light(满足任一即升为 light):
113
- - 涉及 3-5 个文件
114
- - 涉及 prompt 行为变更
115
- - 涉及 validator / postcheck 逻辑
116
- - 涉及路径规则变更(但范围可控)
117
- - 涉及 schema/DB/状态机变更,但影响面可控
118
- - 需要明确验收标准来防止范围漂移
119
-
120
- 判定为 full(满足任一即升为 full):
121
- - 预计 8 个以上 task
122
- - 跨 3 个以上模块
123
- - 涉及 CLI + 平台 + DB 联动
124
- - 涉及 agent 调度 / worktree / isolation 逻辑
125
- - 涉及复杂状态恢复(checkpoint / resume)
126
- - 需要并行 sub-agent 执行
127
- - 需要人工审查设计方向
128
- - 涉及 worktree / baseline / sandbox 等基础设施
129
-
130
- ### 输出格式
131
- 在输出开头,以如下格式输出分类结果:
132
-
133
- \`\`\`
134
- plan_level: none | light | full
135
- reason: <一句话说明判定理由>
136
- estimated_files: <N>
137
- cross_module: true | false
138
- has_schema_change: true | false
139
- has_state_machine_change: true | false
140
- needs_parallel_execution: true | false
141
- needs_human_review: true | false
142
- \`\`\`
143
-
144
- 然后列出已加载的文件清单(含 decisions.md 当前版本/未决项状态、模块文档 + 模块依赖关系摘要)。
145
-
146
- 分类完成后,继续进入下一步。`,
147
- outputHint: '复杂度分类结果 + 文件清单',
148
- optional: false
149
- }
150
-
151
- // ═══════════════════════════════════════════════════════════════
152
- // 第 2 步(LLM):生成分级计划 + 自检(合并原 ⑤⑥)
153
- // ═══════════════════════════════════════════════════════════════
154
-
155
- const stepGeneratePlan = {
156
- id: 'generate_plan',
157
- name: '生成分级计划与自检',
158
- prompt: `根据上一步的 plan_level 结果,按对应级别生成计划,然后立即自检。
159
-
160
- ### 操作
161
- 1. 读取上一步输出的 plan_level 分类结果
162
- 2. 读取 tasks.md 和 design.md 了解需求范围
163
- 3. 按 plan_level 选择对应模板输出
164
- 4. 生成后立即自检(见下方自检清单)
165
-
166
- ---
167
-
168
- #### plan_level = none
169
- 生成最小 plan.md(占位文件,保持流程兼容),不生成完整蓝图。格式:
170
- \`\`\`markdown
171
- ---
172
- plan_level: none
173
- ---
174
-
175
- # 计划跳过
176
-
177
- ## 原因
178
- <一句话说明判定理由>
179
-
180
- ## 建议直接 execute
181
- 直接进入 execute 阶段完成下列最小任务。
182
-
183
- ## Tasks
184
- - [ ] task-01: 按用户需求完成小范围明确修改
185
-
186
- ## 验收
187
- - 修改范围符合用户需求
188
- - 不引入额外无关变更
189
- - 必要测试或检查通过
190
- \`\`\`
191
- **注意:** 所有 plan_level 都必须包含 \`- [ ] task-XX:\` 格式的 checkbox 任务,execute 阶段依赖此格式解析任务。
192
-
193
- ---
194
-
195
- #### plan_level = light
196
- 生成轻量 plan.md,保存到变更目录。只包含以下四部分:
197
-
198
- \`\`\`markdown
199
- ---
200
- plan_level: light
201
- ---
202
-
203
- # 轻量计划:<需求简述>
204
-
205
- ## 来源
206
- 直接引用 brainstorm 结论或用户原始需求,不重新扩写。
207
-
208
- ## 范围
209
- - 涉及的文件/模块清单
210
-
211
- ## Tasks
212
- - [ ] task-01: ...(覆盖:FR-01, D-001@v1)
213
- - [ ] task-02: ...
214
- - [ ] task-03: ...
215
-
216
- ## 验收
217
- - 具体可验证的验收条目
218
-
219
- ## 覆盖矩阵(如存在 decisions.md)
220
- | ID | 覆盖任务 | 验收证据 |
221
- |---|---|---|
222
- | D-001@v1 | task-01 | AC-01 |
223
- \`\`\`
224
-
225
- light 计划的约束:
226
- - **禁止**生成 Mermaid 图
227
- - **禁止**估时
228
- - **禁止**泛泛风险分析(如"需要充分测试")
229
- - **禁止**放实现细节(函数签名、代码示例)
230
- - 来源/目标直接引用已有文档,不重新生成
231
- - 如果存在 decisions.md,所有当前版本 D-xxx@vN 必须在 Tasks 或覆盖矩阵中出现
232
- - 如果存在 P0/P1 unresolved blocker,不生成 plan.md
233
- - 任务列表控制在 10 条以内
234
- - **任务必须使用 checkbox 格式**(\`- [ ] task-XX:\`),不要用纯编号列表(\`1. 2.\`),execute 阶段依赖此格式解析任务
235
-
236
- ---
237
-
238
- #### plan_level = full
239
- 生成完整 plan.md,保存到变更目录。格式如下:
240
-
241
- \`\`\`markdown
242
- ---
243
- plan_level: full
244
- ---
245
-
246
- # 实现计划
247
-
248
- ## Spike 前置验证(如需要)
249
- | Spike | 验证内容 | 不通过后果 |
250
- |---|---|---|
251
- | spike-01 | ... | task-XX 推翻重设计 |
252
-
253
- > 技术不确定性高时才需要 Spike。无不确定性则跳过此节。
254
-
255
- ## Wave 1(并行,无依赖)
256
- - [ ] task-01: 添加用户创建接口(覆盖:FR-01, D-001@v1)
257
- - [ ] task-02: 添加角色创建接口(覆盖:FR-02)
258
-
259
- ## Wave 2(依赖 Wave 1)
260
- - [ ] task-03: 用户创建接口联调
261
-
262
- ## 任务总表
263
- | 编号 | 任务 | Wave | 优先级 | 依赖 | 覆盖 FR/D | 说明 |
264
- |---|---|---|---|---|---|---|
265
- | task-01 | 添加用户创建接口 | W1 | P0 | — | FR-01, D-001@v1 | ... |
266
- | task-02 | 添加角色创建接口 | W1 | P0 | — | FR-02 | ... |
267
- | task-03 | 用户创建接口联调 | W2 | P0 | task-01,02 | FR-03 | ... |
268
-
269
- ## 关键路径
270
- task-01 → task-03(最长路径,决定最短交付周期)
271
-
272
- ## 全局验收标准
273
- - [ ] 所有单元测试通过
274
- - [ ] (brownfield)未配置新功能时行为不变
275
-
276
- ## 覆盖矩阵(如存在 decisions.md)
277
- | ID | 覆盖任务 | 验收证据 |
278
- |---|---|---|
279
- | D-001@v1 | task-01 | AC-01 |
280
- \`\`\`
281
-
282
- full 计划的约束:
283
- - **禁止**估时(任务总表不含估时列)
284
- - **禁止**泛泛风险分析("需要充分测试"类废话转为具体验收条目)
285
- - Mermaid 依赖关系图**仅当依赖关系非平凡时生成**(线性依赖或全并行时不生成)
286
- - **Wave 下的 checkbox 行必须保留**(execute 阶段解析依赖 \`- [ ] task-XX:\` 格式)
287
- - plan.md 包含 Wave 分组 + 任务总表 + 关键路径 + 全局验收标准,**不放实现细节**
288
- - 如果存在 decisions.md,plan.md 必须包含当前版本 D-xxx@vN/FR-xxx 覆盖矩阵
289
- - 如果存在 P0/P1 unresolved blocker,不生成 plan.md,输出阻塞清单
290
- - 实现细节写到后续的 tasks/task-NN.md 中
291
- - 每个任务编号格式:task-01、task-02 ...
292
- - 任务总表的优先级:P0(必须)/ P1(重要)/ P2(可选)
293
- - 总任务数控制在 15 个以内
294
-
295
- ### Spike 前置验证(仅 full)
296
- 当存在技术不确定性时,在 Wave 之前设计 Spike:
297
- - 涉及新技术栈/未经验证的集成 → 需要 Spike
298
- - 涉及安全隔离/性能瓶颈 → 需要 Spike
299
- - 纯业务逻辑/确定的技术方案 → 不需要 Spike
300
- - 每个 Spike 定义:验证内容 + 通过标准 + 不通过后果
301
-
302
- ### 批量模式指引(仅 full)
303
- 如果 design.md 或需求中包含批量特征(关键词:批量/模板/引擎/N个相似),按以下原则规划:
304
- - ❌ 不要列出每个实例作为独立任务
305
- - ❌ 不要在文档中嵌入数据
306
- - ✅ 设计通用架构,Wave 1 聚焦架构
307
- - ✅ 数据转换用脚本完成,单独一个 Wave
308
- - ✅ 总任务数控制在 10 个以内
309
-
310
- ---
311
-
312
- ### 通用操作(所有级别)
313
- 1. 读取 tasks.md 获取任务列表
314
- 2. 读取 design.md 获取文件变更清单
315
- 3. 读取上一步的 plan_level 分类结果
316
- 4. 按对应级别模板生成内容
317
- 5. 保存到变更目录下的 plan.md(路径格式:\`.sillyspec/changes/<change-name>/plan.md\`,其中 <change-name> 是变更目录名,直接使用,不加子目录。正确路径示例:\`.sillyspec/changes/2026-05-28-agent-log-streaming/plan.md\`)
318
- **plan_level 为 none 时生成最小 plan.md(占位),不生成完整蓝图。**
319
-
320
- ---
321
-
322
- ### 自检(生成后立即执行,不另开步骤)
323
-
324
- 读取上一步的 plan_level 分类结果,按级别执行对应的自检:
325
-
326
- #### plan_level = none
327
- - [ ] plan.md 文件存在且包含 plan_level: none
328
- - [ ] 给出了可操作的修改建议(2-5 条)
329
- - [ ] 不含 Wave、Mermaid、估时、任务总表、依赖关系等完整蓝图内容
330
- - [ ] 建议了直接 execute
331
- - [ ] 包含至少一个 \`- [ ] task-XX:\` 格式的 checkbox 任务(execute 解析依赖此格式)
332
-
333
- #### plan_level = light
334
- - [ ] 输出明确标注 plan_level: light
335
- - [ ] 有来源、范围、任务列表、验收标准四个部分
336
- - [ ] 来源直接引用已有文档,未重新扩写
337
- - [ ] 任务列表清晰且无实现细节
338
- - [ ] 任务使用 checkbox 格式(\`- [ ] task-XX:\`),不是纯编号列表
339
- - [ ] 验收标准具体可验证(非笼统表述)
340
- - [ ] 如果存在 decisions.md,所有当前版本 D-xxx@vN 在 plan.md 中可追踪
341
- - [ ] 不存在 P0/P1 unresolved blocker
342
- - [ ] 没有 Mermaid 图、估时、风险分析
343
- - [ ] 没有函数签名、代码示例等实现细节
344
- - [ ] plan.md 与 design.md 的文件变更清单一致
345
- - [ ] 包含至少一个 \`- [ ] task-XX:\` 格式的 checkbox 任务(execute 解析依赖此格式)
346
-
347
- #### plan_level = full
348
- - [ ] 每个 task 有编号(task-01、task-02 ...)
349
- - [ ] 每个 task 在 Wave 下有 checkbox(\`- [ ] task-XX:\` 格式,execute 解析依赖此格式)
350
- - [ ] 已标注 Wave 分组和依赖关系
351
- - [ ] 有任务总表(含优先级、依赖列,**无估时列**)
352
- - [ ] 有关键路径标注
353
- - [ ] 有全局验收标准
354
- - [ ] 如果存在 decisions.md,任务总表或覆盖矩阵覆盖全部当前版本 D-xxx@vN
355
- - [ ] 不存在 P0/P1 unresolved blocker
356
- - [ ] (brownfield)全局验收包含兼容性条款
357
- - [ ] 没有实现细节(接口定义、代码示例等不应该在 plan.md 里)
358
- - [ ] plan.md 与 design.md 的文件变更清单一致
359
- - [ ] 如果涉及构造函数/接口/DTO/client 方法变更,是否搜索了所有调用点并纳入任务范围?
360
- - [ ] 调用点搜索命令的输出是否记录在 plan.md 或 task-NN.md 中?
361
- - [ ] 如果有 Mermaid 图,依赖关系确实非平凡(非线性/非全并行)
362
- - [ ] 没有泛泛风险分析(如"需要充分测试")
363
-
364
- ### 输出
365
- plan_level + 计划内容 + 自检结果(一次输出)`,
366
- outputHint: '计划内容 + 自检结果',
367
- optional: false
368
- }
369
-
370
- // ═══════════════════════════════════════════════════════════════
371
- // 第 3 步(LLM):生成紧凑 TaskCard(子代理并行)
372
- // ═══════════════════════════════════════════════════════════════
373
-
374
- /**
375
- * 构建紧凑 TaskCard 协调器步骤(单步,子代理并行写卡片)
376
- * 每个 task 生成 20~40 行紧凑可执行卡片
377
- */
378
- export function buildCoordinatorStep(changeDir, taskNames) {
379
- const taskList = taskNames.map((name, i) => {
380
- const num = String(i + 1).padStart(2, '0')
381
- return `- task-${num}: ${name}`
382
- }).join('\n')
383
-
384
- const subagentPrompts = taskNames.map((name, i) => {
385
- const num = String(i + 1).padStart(2, '0')
386
- return `\`\`\`
387
- 任务编号:task-${num}
388
- 任务名称:${name}
389
- 文件路径:${changeDir}/tasks/task-${num}.md
390
- 当前时间:<now-datetime>(frontmatter 的 created_at 使用此值)
391
- 当前用户:<git-user>(frontmatter 的 author 使用此值)
392
-
393
- 操作:
394
- 1. 读取 ${changeDir}/design.md 和 ${changeDir}/plan.md 了解上下文
395
- 2. 读取相关源文件了解现有代码
396
- 3. 生成紧凑 TaskCard(20~40 行),格式如下:
397
-
398
- ---
399
- id: task-${num}
400
- title: ${name}
401
- author: <git-user>
402
- created_at: <now-datetime>
403
- priority: P0
404
- depends_on: []
405
- blocks: []
406
- requirement_ids: [FR-XX]
407
- decision_ids: [D-XXX@vN]
408
- allowed_paths:
409
- - frontend/src/lib/errors.ts
410
- goal: >
411
- 一句话说明这个 task 要做什么、为什么。
412
- implementation:
413
- - 具体步骤 1
414
- - 具体步骤 2
415
- - 具体步骤 3
416
- acceptance:
417
- - 可验证的验收条件 1
418
- - 可验证的验收条件 2
419
- - 可验证的验收条件 3
420
- verify:
421
- - cd frontend && pnpm exec tsc --noEmit
422
- constraints:
423
- - 边界约束 1(如:不加测试)
424
- - 边界约束 2(如:不修改传入参数)
425
- ---
426
-
427
- TaskCard 格式规则(必须严格遵守):
428
- - 总长度 20~40 行,不要写成长文档
429
- - frontmatter 只含必要字段,不加 estimated_hours
430
- - goal: 一句话,用 > 多行字符串
431
- - implementation: 列表,每条一个具体步骤
432
- - acceptance: 列表,每条可独立验证(不是表格)
433
- - verify: 列表,实际可执行的命令
434
- - constraints: 列表,明确边界(含 brownfield 兼容、异常处理)
435
- - 不需要:修改文件章节、覆盖来源章节、接口定义章节、TDD 步骤章节、参考章节
436
- - 如果存在 decisions.md,无法覆盖的 D-xxx@vN 在 constraints 中标注
437
- - 写完后用 Write tool 保存到文件
438
- \`\`\``
439
- }).join('\n\n')
440
-
441
-
442
- const prompt = `为 plan.md 中的每个任务生成紧凑 TaskCard。
443
-
444
- ## 任务清单
445
- ${taskList}
446
-
447
- ## 时间和用户
448
- 当前时间:<now-datetime>
449
- 当前用户:<git-user>
450
-
451
- ## 执行方式(必须严格遵守)
452
-
453
- **你必须使用 Agent tool 启动子代理来写每个卡片,不要自己写。**
454
-
455
- 1. 确认 \`${changeDir}/tasks/\` 目录存在(不存在则创建)
456
- 2. 为每个任务启动一个独立子代理(Agent tool),可并行启动多个
457
- 3. 每个子代理使用对应的 prompt(见下方模板)
458
- 4. 等待所有子代理完成
459
- 5. 验证每个 task-N.md 文件已生成且非空
460
-
461
- ### 子代理 prompt 模板
462
- 为每个任务使用以下 prompt 启动子代理:
463
-
464
- ${subagentPrompts}
465
-
466
- ## 验收(生成后自查,不另开步骤)
467
- - 每个 task-N.md 文件存在且非空
468
- - frontmatter 包含:id、title、author、created_at、priority、depends_on、blocks、allowed_paths
469
- - body 包含:goal、implementation、acceptance、verify、constraints
470
- - 每个 task 总长度 20~40 行
471
- - **一致性自查**:
472
- - allowed_paths 有无冲突
473
- - depends_on 与 plan.md Wave 分组是否一致
474
- - 如发现矛盾,列出问题清单,不要自动修复`
475
-
476
- return {
477
- id: 'generate_blueprints',
478
- name: '生成 TaskCard(子代理并行)',
479
- prompt,
480
- outputHint: 'TaskCard 生成结果',
481
- optional: false
482
- }
483
- }
484
-
485
- // ═══════════════════════════════════════════════════════════════
486
- // 第 4 步(noAI):Wave 重排 + 一致性校验 + 保存(合并原 ⑧⑨⑩,全代码化)
487
- // 核心逻辑已迁移到 plan-postcheck.js,此处只保留步骤定义
488
- // ═══════════════════════════════════════════════════════════════
489
-
490
- /**
491
- * noAI postcheck 步骤:Wave 重排 + 一致性校验 + 可行性校验 + 保存确认
492
- * 核心逻辑见 plan-postcheck.js
493
- */
494
- export function buildPostcheckStep(changeDir) {
495
- return {
496
- id: 'postcheck',
497
- name: 'Wave 重排与可行性校验',
498
- prompt: '', // noAI 步骤不需要 prompt
499
- outputHint: 'Wave 重排 + 校验结果',
500
- optional: false,
501
- noAI: true,
502
- _cliAction: 'planPostcheck'
503
- }
504
- }
505
-
506
- // ═══════════════════════════════════════════════════════════════
507
- // 向后兼容:导出 fixedPrefix / fixedSuffix(供 run.js 切片用)
508
- // ═══════════════════════════════════════════════════════════════
509
-
510
- export const fixedPrefix = [stepClassify, stepGeneratePlan]
511
-
512
- export const fixedSuffix = [] // postcheck 是动态生成的(需要 changeDir)
513
-
514
- // ═══════════════════════════════════════════════════════════════
515
- // 工具函数(保持导出兼容)
516
- // ═══════════════════════════════════════════════════════════════
517
-
518
- /**
519
- * 解析 plan.md 获取任务数量
520
- */
521
- function parseTaskCount(planContent) {
522
- if (!planContent || typeof planContent !== 'string') return 0
523
- const matches = planContent.match(/^[-*]\s*\[[ x]\]\s*task-\d+/gm)
524
- return matches ? matches.length : 0
525
- }
526
-
527
- /**
528
- * 从 plan.md 解析任务名列表
529
- */
530
- function parseTaskNames(planContent) {
531
- const names = []
532
- const lines = planContent.split('\n')
533
- for (const line of lines) {
534
- const m = line.match(/^[-*]\s*\[[ x]\]\s*task-\d+:\s*(.+)/i)
535
- if (m) names.push(m[1].trim())
536
- }
537
- return names
538
- }
539
-
540
- /**
541
- * 动态构建 plan 步骤列表
542
- * 新架构:3 个 LLM 步骤 + 1 个 noAI 步骤 = 4 阶段
543
- *
544
- * @param {string|null} changeDir - 变更目录路径
545
- * @param {string|null} planContent - plan.md 内容(可选,用于解析任务数)
546
- * @returns {Array} 步骤列表
547
- */
548
- export function buildPlanSteps(changeDir = null, planContent = null) {
549
- let taskCount = 0
550
-
551
- // 尝试从 plan.md 解析任务数
552
- if (planContent) {
553
- taskCount = parseTaskCount(planContent)
554
- } else if (changeDir) {
555
- const planFile = path.join(changeDir, 'plan.md')
556
- if (existsSync(planFile)) {
557
- taskCount = parseTaskCount(readFileSync(planFile, 'utf8'))
558
- }
559
- }
560
-
561
- // 没有任务数则用固定步骤(兼容旧流程,无蓝图步骤无 postcheck)
562
- if (taskCount === 0) {
563
- const postcheck = changeDir ? [buildPostcheckStep(changeDir)] : []
564
- return [...fixedPrefix, ...postcheck]
565
- }
566
-
567
- // 解析任务名
568
- let taskNames = []
569
- if (planContent) {
570
- taskNames = parseTaskNames(planContent)
571
- } else if (changeDir) {
572
- const planFile = path.join(changeDir, 'plan.md')
573
- if (existsSync(planFile)) {
574
- taskNames = parseTaskNames(readFileSync(planFile, 'utf8'))
575
- }
576
- }
577
-
578
- // 生成协调器步骤(TaskCard 生成)+ postcheck
579
- const coordinatorStep = buildCoordinatorStep(changeDir, taskNames)
580
- const postcheckStep = buildPostcheckStep(changeDir)
581
- return [...fixedPrefix, coordinatorStep, postcheckStep]
582
- }
1
+ import { existsSync, readFileSync, readdirSync } from 'fs'
2
+ import path from 'path'
3
+
4
+ // 从 plan-postcheck.js 重导出(保持向后兼容)
5
+ export {
6
+ topoSortWaves,
7
+ validateBlueprintConsistency,
8
+ validatePlanArtifacts,
9
+ validatePlanFeasibility
10
+ } from './plan-postcheck.js'
11
+
12
+ // 这些解析函数已迁移到 plan-postcheck.js,此处不再定义
13
+
14
+ /**
15
+ * 校验 design.md 是否满足 plan 执行契约
16
+ * 第一版是轻量 markdown 结构检查,不强 schema。
17
+ * @param {string} designContent - design.md 文件内容
18
+ * @returns {{ ok: boolean, errors: string[], warnings: string[] }}
19
+ */
20
+ export function validateDesignForPlan(designContent) {
21
+ const errors = []
22
+ const warnings = []
23
+
24
+ if (!designContent || !designContent.trim()) {
25
+ return { ok: false, errors: ['design.md 内容为空'], warnings }
26
+ }
27
+
28
+ const lower = designContent.toLowerCase()
29
+
30
+ // 检查 1: 必须包含目标/问题描述(error)
31
+ const hasGoal = /(^|\n)#{2,}\s*.*(目标|goal|objective|背景|background|问题|problem|purpose|目的)/i.test(designContent)
32
+ if (!hasGoal) {
33
+ errors.push('design.md 缺少「目标/背景/问题描述」章节 — plan 需要知道要达成什么')
34
+ }
35
+
36
+ // 检查 2: 必须包含范围/scope(error)
37
+ const hasScope = /(^|\n)#{2,}\s*.*(范围|scope|总体方案|方案|approach|solution|设计|design)/i.test(designContent)
38
+ if (!hasScope) {
39
+ errors.push('design.md 缺少「范围/总体方案/设计」章节 — plan 需要知道做什么和怎么做')
40
+ }
41
+
42
+ // 检查 3: 必须包含决策/方案选择(error)
43
+ const hasDecisions = /(^|\n)#{2,}\s*.*(决策|decision|选择|choice|方案选择)/i.test(designContent)
44
+ || /d-\d+@v\d+/i.test(designContent) // decisions.md 引用 ID
45
+ || /decisions?\.md/i.test(designContent) // 引用 decisions.md
46
+ if (!hasDecisions) {
47
+ errors.push('design.md 缺少「决策/方案选择」— plan 需要基于明确的技术决策来拆分任务')
48
+ }
49
+
50
+ // 检查 4 (warning): 缺非目标/non-goals
51
+ const hasNonGoals = /(^|\n)#{2,}\s*.*(非目标|non-goals?|不做|out of scope|不在范围)/i.test(designContent)
52
+ if (!hasNonGoals) {
53
+ warnings.push('design.md 缺少「非目标/Non-goals」— 建议明确不做什么,防止 scope creep')
54
+ }
55
+
56
+ // 检查 5 (warning): 缺约束/风险
57
+ const hasConstraints = /(^|\n)#{2,}\s*.*(约束|constraint|限制|limitation|风险|risk|trade-?off)/i.test(designContent)
58
+ if (!hasConstraints) {
59
+ warnings.push('design.md 缺少「约束/风险/Trade-off」— 建议记录已知约束和风险')
60
+ }
61
+
62
+ // 检查 6 (warning): 缺文件变更清单
63
+ const hasFileChanges = /文件变更|file change|变更清单|changed files/i.test(designContent)
64
+ || /^\|\s*(新增|修改|删除|new|modify|delete|update)\s*\|/im.test(designContent)
65
+ if (!hasFileChanges) {
66
+ warnings.push('design.md 缺少「文件变更清单」— 建议列出预期改动的文件')
67
+ }
68
+
69
+ return { ok: errors.length === 0, errors, warnings }
70
+ }
71
+
72
+ export const definition = {
73
+ name: 'plan',
74
+ title: '实现计划',
75
+ description: '编写实现计划 — 按 Wave 分组,每个任务独立文档',
76
+ steps: null // 动态生成
77
+ }
78
+
79
+ // ═══════════════════════════════════════════════════════════════
80
+ // 第 1 步(LLM):复杂度分类 + 上下文加载(合并原 ①②③④)
81
+ // ═══════════════════════════════════════════════════════════════
82
+
83
+ const stepClassify = {
84
+ id: 'classify',
85
+ name: '复杂度分类与上下文加载',
86
+ prompt: `在生成计划之前,先加载上下文并判定本次需求的复杂度等级(plan_level)。
87
+
88
+ ### 操作
89
+ 1. 运行 \`sillyspec progress show\`,确认 currentStage 为 "plan"
90
+ 2. 读取 CODEBASE-OVERVIEW.md + 各子项目上下文
91
+ 3. 读取 proposal.md、design.md、requirements.md、tasks.md
92
+ 4. 如果存在 decisions.md,必须读取并提取所有当前版本 D-xxx@vN 决策 ID
93
+ - 如果发现 priority=P0/P1 且 status=unresolved/blocking 的决策,停止生成计划,要求先回到 brainstorm 的 Design Grill 修正
94
+ - 如果发现 superseded 决策,只引用最新版本,不引用旧版本
95
+ 5. 读取 CONVENTIONS.md、ARCHITECTURE.md、STACK.md
96
+ 6. 读取 local.yaml 获取构建/测试命令
97
+ 7. 读取 \`.sillyspec/docs/<project>/modules/_module-map.yaml\`(不存在则跳过)
98
+ - 根据 design.md 的文件变更清单匹配模块
99
+ - 读取匹配到的模块文档
100
+ - 利用模块依赖关系辅助分析(depends_on / used_by)
101
+
102
+ ### 分级规则
103
+ 判定 plan_level 为 none 时,需**同时满足**以下所有条件:
104
+ - 涉及文件 ≤ 2 个
105
+ - 不跨模块(改动集中在单个模块内)
106
+ - 无 schema / DB / manifest / local.yaml 变更
107
+ - 无状态机 / workflow 状态流转变更
108
+ - 无 source_root / spec_root / runtime_root 路径隔离规则变更
109
+ - 无 validator / postcheck / agent 调度行为变更
110
+ - 需求明确,无设计歧义
111
+
112
+ 判定为 light(满足任一即升为 light):
113
+ - 涉及 3-5 个文件
114
+ - 涉及 prompt 行为变更
115
+ - 涉及 validator / postcheck 逻辑
116
+ - 涉及路径规则变更(但范围可控)
117
+ - 涉及 schema/DB/状态机变更,但影响面可控
118
+ - 需要明确验收标准来防止范围漂移
119
+
120
+ 判定为 full(满足任一即升为 full):
121
+ - 预计 8 个以上 task
122
+ - 跨 3 个以上模块
123
+ - 涉及 CLI + 平台 + DB 联动
124
+ - 涉及 agent 调度 / worktree / isolation 逻辑
125
+ - 涉及复杂状态恢复(checkpoint / resume)
126
+ - 需要并行 sub-agent 执行
127
+ - 需要人工审查设计方向
128
+ - 涉及 worktree / baseline / sandbox 等基础设施
129
+
130
+ ### 输出格式
131
+ 在输出开头,以如下格式输出分类结果:
132
+
133
+ \`\`\`
134
+ plan_level: none | light | full
135
+ reason: <一句话说明判定理由>
136
+ estimated_files: <N>
137
+ cross_module: true | false
138
+ has_schema_change: true | false
139
+ has_state_machine_change: true | false
140
+ needs_parallel_execution: true | false
141
+ needs_human_review: true | false
142
+ \`\`\`
143
+
144
+ 然后列出已加载的文件清单(含 decisions.md 当前版本/未决项状态、模块文档 + 模块依赖关系摘要)。
145
+
146
+ 分类完成后,继续进入下一步。`,
147
+ outputHint: '复杂度分类结果 + 文件清单',
148
+ optional: false
149
+ }
150
+
151
+ // ═══════════════════════════════════════════════════════════════
152
+ // 第 2 步(LLM):生成分级计划 + 自检(合并原 ⑤⑥)
153
+ // ═══════════════════════════════════════════════════════════════
154
+
155
+ const stepGeneratePlan = {
156
+ id: 'generate_plan',
157
+ name: '生成分级计划与自检',
158
+ prompt: `根据上一步的 plan_level 结果,按对应级别生成计划,然后立即自检。
159
+
160
+ ### 操作
161
+ 1. 读取上一步输出的 plan_level 分类结果
162
+ 2. 读取 tasks.md 和 design.md 了解需求范围
163
+ 3. 按 plan_level 选择对应模板输出
164
+ 4. 生成后立即自检(见下方自检清单)
165
+
166
+ ---
167
+
168
+ #### plan_level = none
169
+ 生成最小 plan.md(占位文件,保持流程兼容),不生成完整蓝图。格式:
170
+ \`\`\`markdown
171
+ ---
172
+ plan_level: none
173
+ ---
174
+
175
+ # 计划跳过
176
+
177
+ ## 原因
178
+ <一句话说明判定理由>
179
+
180
+ ## 建议直接 execute
181
+ 直接进入 execute 阶段完成下列最小任务。
182
+
183
+ ## Tasks
184
+ - [ ] task-01: 按用户需求完成小范围明确修改
185
+
186
+ ## 验收
187
+ - 修改范围符合用户需求
188
+ - 不引入额外无关变更
189
+ - 必要测试或检查通过
190
+ \`\`\`
191
+ **注意:** 所有 plan_level 都必须包含 \`- [ ] task-XX:\` 格式的 checkbox 任务,execute 阶段依赖此格式解析任务。
192
+
193
+ ---
194
+
195
+ #### plan_level = light
196
+ 生成轻量 plan.md,保存到变更目录。只包含以下四部分:
197
+
198
+ \`\`\`markdown
199
+ ---
200
+ plan_level: light
201
+ ---
202
+
203
+ # 轻量计划:<需求简述>
204
+
205
+ ## 来源
206
+ 直接引用 brainstorm 结论或用户原始需求,不重新扩写。
207
+
208
+ ## 范围
209
+ - 涉及的文件/模块清单
210
+
211
+ ## Tasks
212
+ - [ ] task-01: ...(覆盖:FR-01, D-001@v1)
213
+ - [ ] task-02: ...
214
+ - [ ] task-03: ...
215
+
216
+ ## 验收
217
+ - 具体可验证的验收条目
218
+
219
+ ## 覆盖矩阵(如存在 decisions.md)
220
+ | ID | 覆盖任务 | 验收证据 |
221
+ |---|---|---|
222
+ | D-001@v1 | task-01 | AC-01 |
223
+ \`\`\`
224
+
225
+ light 计划的约束:
226
+ - **禁止**生成 Mermaid 图
227
+ - **禁止**估时
228
+ - **禁止**泛泛风险分析(如"需要充分测试")
229
+ - **禁止**放实现细节(函数签名、代码示例)
230
+ - 来源/目标直接引用已有文档,不重新生成
231
+ - 如果存在 decisions.md,所有当前版本 D-xxx@vN 必须在 Tasks 或覆盖矩阵中出现
232
+ - 如果存在 P0/P1 unresolved blocker,不生成 plan.md
233
+ - 任务列表控制在 10 条以内
234
+ - **任务必须使用 checkbox 格式**(\`- [ ] task-XX:\`),不要用纯编号列表(\`1. 2.\`),execute 阶段依赖此格式解析任务
235
+
236
+ ---
237
+
238
+ #### plan_level = full
239
+ 生成完整 plan.md,保存到变更目录。格式如下:
240
+
241
+ \`\`\`markdown
242
+ ---
243
+ plan_level: full
244
+ ---
245
+
246
+ # 实现计划
247
+
248
+ ## Spike 前置验证(如需要)
249
+ | Spike | 验证内容 | 不通过后果 |
250
+ |---|---|---|
251
+ | spike-01 | ... | task-XX 推翻重设计 |
252
+
253
+ > 技术不确定性高时才需要 Spike。无不确定性则跳过此节。
254
+
255
+ ## Wave 1(并行,无依赖)
256
+ - [ ] task-01: 添加用户创建接口(覆盖:FR-01, D-001@v1)
257
+ - [ ] task-02: 添加角色创建接口(覆盖:FR-02)
258
+
259
+ ## Wave 2(依赖 Wave 1)
260
+ - [ ] task-03: 用户创建接口联调
261
+
262
+ ## 任务总表
263
+ | 编号 | 任务 | Wave | 优先级 | 依赖 | 覆盖 FR/D | 说明 |
264
+ |---|---|---|---|---|---|---|
265
+ | task-01 | 添加用户创建接口 | W1 | P0 | — | FR-01, D-001@v1 | ... |
266
+ | task-02 | 添加角色创建接口 | W1 | P0 | — | FR-02 | ... |
267
+ | task-03 | 用户创建接口联调 | W2 | P0 | task-01,02 | FR-03 | ... |
268
+
269
+ ## 关键路径
270
+ task-01 → task-03(最长路径,决定最短交付周期)
271
+
272
+ ## 全局验收标准
273
+ - [ ] 所有单元测试通过
274
+ - [ ] (brownfield)未配置新功能时行为不变
275
+
276
+ ## 覆盖矩阵(如存在 decisions.md)
277
+ | ID | 覆盖任务 | 验收证据 |
278
+ |---|---|---|
279
+ | D-001@v1 | task-01 | AC-01 |
280
+ \`\`\`
281
+
282
+ full 计划的约束:
283
+ - **禁止**估时(任务总表不含估时列)
284
+ - **禁止**泛泛风险分析("需要充分测试"类废话转为具体验收条目)
285
+ - Mermaid 依赖关系图**仅当依赖关系非平凡时生成**(线性依赖或全并行时不生成)
286
+ - **Wave 下的 checkbox 行必须保留**(execute 阶段解析依赖 \`- [ ] task-XX:\` 格式)
287
+ - plan.md 包含 Wave 分组 + 任务总表 + 关键路径 + 全局验收标准,**不放实现细节**
288
+ - 如果存在 decisions.md,plan.md 必须包含当前版本 D-xxx@vN/FR-xxx 覆盖矩阵
289
+ - 如果存在 P0/P1 unresolved blocker,不生成 plan.md,输出阻塞清单
290
+ - 实现细节写到后续的 tasks/task-NN.md 中
291
+ - 每个任务编号格式:task-01、task-02 ...
292
+ - 任务总表的优先级:P0(必须)/ P1(重要)/ P2(可选)
293
+ - 总任务数控制在 15 个以内
294
+
295
+ ### Spike 前置验证(仅 full)
296
+ 当存在技术不确定性时,在 Wave 之前设计 Spike:
297
+ - 涉及新技术栈/未经验证的集成 → 需要 Spike
298
+ - 涉及安全隔离/性能瓶颈 → 需要 Spike
299
+ - 纯业务逻辑/确定的技术方案 → 不需要 Spike
300
+ - 每个 Spike 定义:验证内容 + 通过标准 + 不通过后果
301
+
302
+ ### 批量模式指引(仅 full)
303
+ 如果 design.md 或需求中包含批量特征(关键词:批量/模板/引擎/N个相似),按以下原则规划:
304
+ - ❌ 不要列出每个实例作为独立任务
305
+ - ❌ 不要在文档中嵌入数据
306
+ - ✅ 设计通用架构,Wave 1 聚焦架构
307
+ - ✅ 数据转换用脚本完成,单独一个 Wave
308
+ - ✅ 总任务数控制在 10 个以内
309
+
310
+ ---
311
+
312
+ ### 通用操作(所有级别)
313
+ 1. 读取 tasks.md 获取任务列表
314
+ 2. 读取 design.md 获取文件变更清单
315
+ 3. 读取上一步的 plan_level 分类结果
316
+ 4. 按对应级别模板生成内容
317
+ 5. 保存到变更目录下的 plan.md(路径格式:\`.sillyspec/changes/<change-name>/plan.md\`,其中 <change-name> 是变更目录名,直接使用,不加子目录。正确路径示例:\`.sillyspec/changes/2026-05-28-agent-log-streaming/plan.md\`)
318
+ **plan_level 为 none 时生成最小 plan.md(占位),不生成完整蓝图。**
319
+
320
+ ---
321
+
322
+ ### 自检(生成后立即执行,不另开步骤)
323
+
324
+ 读取上一步的 plan_level 分类结果,按级别执行对应的自检:
325
+
326
+ #### plan_level = none
327
+ - [ ] plan.md 文件存在且包含 plan_level: none
328
+ - [ ] 给出了可操作的修改建议(2-5 条)
329
+ - [ ] 不含 Wave、Mermaid、估时、任务总表、依赖关系等完整蓝图内容
330
+ - [ ] 建议了直接 execute
331
+ - [ ] 包含至少一个 \`- [ ] task-XX:\` 格式的 checkbox 任务(execute 解析依赖此格式)
332
+
333
+ #### plan_level = light
334
+ - [ ] 输出明确标注 plan_level: light
335
+ - [ ] 有来源、范围、任务列表、验收标准四个部分
336
+ - [ ] 来源直接引用已有文档,未重新扩写
337
+ - [ ] 任务列表清晰且无实现细节
338
+ - [ ] 任务使用 checkbox 格式(\`- [ ] task-XX:\`),不是纯编号列表
339
+ - [ ] 验收标准具体可验证(非笼统表述)
340
+ - [ ] 如果存在 decisions.md,所有当前版本 D-xxx@vN 在 plan.md 中可追踪
341
+ - [ ] 不存在 P0/P1 unresolved blocker
342
+ - [ ] 没有 Mermaid 图、估时、风险分析
343
+ - [ ] 没有函数签名、代码示例等实现细节
344
+ - [ ] plan.md 与 design.md 的文件变更清单一致
345
+ - [ ] 包含至少一个 \`- [ ] task-XX:\` 格式的 checkbox 任务(execute 解析依赖此格式)
346
+
347
+ #### plan_level = full
348
+ - [ ] 每个 task 有编号(task-01、task-02 ...)
349
+ - [ ] 每个 task 在 Wave 下有 checkbox(\`- [ ] task-XX:\` 格式,execute 解析依赖此格式)
350
+ - [ ] 已标注 Wave 分组和依赖关系
351
+ - [ ] 有任务总表(含优先级、依赖列,**无估时列**)
352
+ - [ ] 有关键路径标注
353
+ - [ ] 有全局验收标准
354
+ - [ ] 如果存在 decisions.md,任务总表或覆盖矩阵覆盖全部当前版本 D-xxx@vN
355
+ - [ ] 不存在 P0/P1 unresolved blocker
356
+ - [ ] (brownfield)全局验收包含兼容性条款
357
+ - [ ] 没有实现细节(接口定义、代码示例等不应该在 plan.md 里)
358
+ - [ ] plan.md 与 design.md 的文件变更清单一致
359
+ - [ ] 如果涉及构造函数/接口/DTO/client 方法变更,是否搜索了所有调用点并纳入任务范围?
360
+ - [ ] 调用点搜索命令的输出是否记录在 plan.md 或 task-NN.md 中?
361
+ - [ ] 如果有 Mermaid 图,依赖关系确实非平凡(非线性/非全并行)
362
+ - [ ] 没有泛泛风险分析(如"需要充分测试")
363
+
364
+ ### 输出
365
+ plan_level + 计划内容 + 自检结果(一次输出)`,
366
+ outputHint: '计划内容 + 自检结果',
367
+ optional: false
368
+ }
369
+
370
+ // ═══════════════════════════════════════════════════════════════
371
+ // 第 3 步(LLM):生成紧凑 TaskCard(子代理并行)
372
+ // ═══════════════════════════════════════════════════════════════
373
+
374
+ /**
375
+ * 构建紧凑 TaskCard 协调器步骤(单步,子代理并行写卡片)
376
+ * 每个 task 生成 20~40 行紧凑可执行卡片
377
+ */
378
+ export function buildCoordinatorStep(changeDir, taskNames) {
379
+ const taskList = taskNames.map((name, i) => {
380
+ const num = String(i + 1).padStart(2, '0')
381
+ return `- task-${num}: ${name}`
382
+ }).join('\n')
383
+
384
+ const subagentPrompts = taskNames.map((name, i) => {
385
+ const num = String(i + 1).padStart(2, '0')
386
+ return `\`\`\`
387
+ 任务编号:task-${num}
388
+ 任务名称:${name}
389
+ 文件路径:${changeDir}/tasks/task-${num}.md
390
+ 当前时间:<now-datetime>(frontmatter 的 created_at 使用此值)
391
+ 当前用户:<git-user>(frontmatter 的 author 使用此值)
392
+
393
+ 操作:
394
+ 1. 读取 ${changeDir}/design.md 和 ${changeDir}/plan.md 了解上下文
395
+ 2. 读取相关源文件了解现有代码
396
+ 3. 生成紧凑 TaskCard(20~40 行),格式如下:
397
+
398
+ ---
399
+ id: task-${num}
400
+ title: ${name}
401
+ author: <git-user>
402
+ created_at: <now-datetime>
403
+ priority: P0
404
+ depends_on: []
405
+ blocks: []
406
+ requirement_ids: [FR-XX]
407
+ decision_ids: [D-XXX@vN]
408
+ allowed_paths:
409
+ - frontend/src/lib/errors.ts
410
+ goal: >
411
+ 一句话说明这个 task 要做什么、为什么。
412
+ implementation:
413
+ - 具体步骤 1
414
+ - 具体步骤 2
415
+ - 具体步骤 3
416
+ acceptance:
417
+ - 可验证的验收条件 1
418
+ - 可验证的验收条件 2
419
+ - 可验证的验收条件 3
420
+ verify:
421
+ - cd frontend && pnpm exec tsc --noEmit
422
+ constraints:
423
+ - 边界约束 1(如:不加测试)
424
+ - 边界约束 2(如:不修改传入参数)
425
+ ---
426
+
427
+ TaskCard 格式规则(必须严格遵守):
428
+ - 总长度 20~40 行,不要写成长文档
429
+ - frontmatter 只含必要字段,不加 estimated_hours
430
+ - goal: 一句话,用 > 多行字符串
431
+ - implementation: 列表,每条一个具体步骤
432
+ - acceptance: 列表,每条可独立验证(不是表格)
433
+ - verify: 列表,实际可执行的命令
434
+ - constraints: 列表,明确边界(含 brownfield 兼容、异常处理)
435
+ - 不需要:修改文件章节、覆盖来源章节、接口定义章节、TDD 步骤章节、参考章节
436
+ - 如果存在 decisions.md,无法覆盖的 D-xxx@vN 在 constraints 中标注
437
+ - 写完后用 Write tool 保存到文件
438
+ \`\`\``
439
+ }).join('\n\n')
440
+
441
+
442
+ const prompt = `为 plan.md 中的每个任务生成紧凑 TaskCard。
443
+
444
+ ## 任务清单
445
+ ${taskList}
446
+
447
+ ## 时间和用户
448
+ 当前时间:<now-datetime>
449
+ 当前用户:<git-user>
450
+
451
+ ## 执行方式(必须严格遵守)
452
+
453
+ **你必须使用 Agent tool 启动子代理来写每个卡片,不要自己写。**
454
+
455
+ 1. 确认 \`${changeDir}/tasks/\` 目录存在(不存在则创建)
456
+ 2. 为每个任务启动一个独立子代理(Agent tool),可并行启动多个
457
+ 3. 每个子代理使用对应的 prompt(见下方模板)
458
+ 4. 等待所有子代理完成
459
+ 5. 验证每个 task-N.md 文件已生成且非空
460
+
461
+ ### 子代理 prompt 模板
462
+ 为每个任务使用以下 prompt 启动子代理:
463
+
464
+ ${subagentPrompts}
465
+
466
+ ## 验收(生成后自查,不另开步骤)
467
+ - 每个 task-N.md 文件存在且非空
468
+ - frontmatter 包含:id、title、author、created_at、priority、depends_on、blocks、allowed_paths
469
+ - body 包含:goal、implementation、acceptance、verify、constraints
470
+ - 每个 task 总长度 20~40 行
471
+ - **一致性自查**:
472
+ - allowed_paths 有无冲突
473
+ - depends_on 与 plan.md Wave 分组是否一致
474
+ - 如发现矛盾,列出问题清单,不要自动修复`
475
+
476
+ return {
477
+ id: 'generate_blueprints',
478
+ name: '生成 TaskCard(子代理并行)',
479
+ prompt,
480
+ outputHint: 'TaskCard 生成结果',
481
+ optional: false
482
+ }
483
+ }
484
+
485
+ // ═══════════════════════════════════════════════════════════════
486
+ // 第 4 步(noAI):Wave 重排 + 一致性校验 + 保存(合并原 ⑧⑨⑩,全代码化)
487
+ // 核心逻辑已迁移到 plan-postcheck.js,此处只保留步骤定义
488
+ // ═══════════════════════════════════════════════════════════════
489
+
490
+ /**
491
+ * noAI postcheck 步骤:Wave 重排 + 一致性校验 + 可行性校验 + 保存确认
492
+ * 核心逻辑见 plan-postcheck.js
493
+ */
494
+ export function buildPostcheckStep(changeDir) {
495
+ return {
496
+ id: 'postcheck',
497
+ name: 'Wave 重排与可行性校验',
498
+ prompt: '', // noAI 步骤不需要 prompt
499
+ outputHint: 'Wave 重排 + 校验结果',
500
+ optional: false,
501
+ noAI: true,
502
+ _cliAction: 'planPostcheck'
503
+ }
504
+ }
505
+
506
+ // ═══════════════════════════════════════════════════════════════
507
+ // 向后兼容:导出 fixedPrefix / fixedSuffix(供 run.js 切片用)
508
+ // ═══════════════════════════════════════════════════════════════
509
+
510
+ export const fixedPrefix = [stepClassify, stepGeneratePlan]
511
+
512
+ export const fixedSuffix = [] // postcheck 是动态生成的(需要 changeDir)
513
+
514
+ // ═══════════════════════════════════════════════════════════════
515
+ // 工具函数(保持导出兼容)
516
+ // ═══════════════════════════════════════════════════════════════
517
+
518
+ /**
519
+ * 解析 plan.md 获取任务数量
520
+ */
521
+ function parseTaskCount(planContent) {
522
+ if (!planContent || typeof planContent !== 'string') return 0
523
+ const matches = planContent.match(/^[-*]\s*\[[ x]\]\s*task-\d+/gm)
524
+ return matches ? matches.length : 0
525
+ }
526
+
527
+ /**
528
+ * 从 plan.md 解析任务名列表
529
+ */
530
+ function parseTaskNames(planContent) {
531
+ const names = []
532
+ const lines = planContent.split('\n')
533
+ for (const line of lines) {
534
+ const m = line.match(/^[-*]\s*\[[ x]\]\s*task-\d+:\s*(.+)/i)
535
+ if (m) names.push(m[1].trim())
536
+ }
537
+ return names
538
+ }
539
+
540
+ /**
541
+ * 动态构建 plan 步骤列表
542
+ * 新架构:3 个 LLM 步骤 + 1 个 noAI 步骤 = 4 阶段
543
+ *
544
+ * @param {string|null} changeDir - 变更目录路径
545
+ * @param {string|null} planContent - plan.md 内容(可选,用于解析任务数)
546
+ * @returns {Array} 步骤列表
547
+ */
548
+ export function buildPlanSteps(changeDir = null, planContent = null) {
549
+ let taskCount = 0
550
+
551
+ // 尝试从 plan.md 解析任务数
552
+ if (planContent) {
553
+ taskCount = parseTaskCount(planContent)
554
+ } else if (changeDir) {
555
+ const planFile = path.join(changeDir, 'plan.md')
556
+ if (existsSync(planFile)) {
557
+ taskCount = parseTaskCount(readFileSync(planFile, 'utf8'))
558
+ }
559
+ }
560
+
561
+ // 没有任务数则用固定步骤(兼容旧流程,无蓝图步骤无 postcheck)
562
+ if (taskCount === 0) {
563
+ const postcheck = changeDir ? [buildPostcheckStep(changeDir)] : []
564
+ return [...fixedPrefix, ...postcheck]
565
+ }
566
+
567
+ // 解析任务名
568
+ let taskNames = []
569
+ if (planContent) {
570
+ taskNames = parseTaskNames(planContent)
571
+ } else if (changeDir) {
572
+ const planFile = path.join(changeDir, 'plan.md')
573
+ if (existsSync(planFile)) {
574
+ taskNames = parseTaskNames(readFileSync(planFile, 'utf8'))
575
+ }
576
+ }
577
+
578
+ // 生成协调器步骤(TaskCard 生成)+ postcheck
579
+ const coordinatorStep = buildCoordinatorStep(changeDir, taskNames)
580
+ const postcheckStep = buildPostcheckStep(changeDir)
581
+ return [...fixedPrefix, coordinatorStep, postcheckStep]
582
+ }