sillyspec 3.19.2 → 3.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
- package/package.json +1 -1
- package/src/index.js +59 -0
- package/src/run.js +64 -22
- package/src/stage-contract.js +14 -1
- package/src/stages/execute.js +36 -26
- package/src/stages/knowledge.js +498 -0
- package/src/stages/plan-postcheck.js +513 -0
- package/src/stages/plan.js +152 -346
- package/src/worktree-apply.js +141 -0
- package/test/decision-supersede.test.mjs +277 -0
- package/test/plan-optimization.test.mjs +572 -0
package/src/stages/plan.js
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'fs'
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'fs'
|
|
2
2
|
import path from 'path'
|
|
3
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
|
+
|
|
4
14
|
/**
|
|
5
15
|
* 校验 design.md 是否满足 plan 执行契约
|
|
6
16
|
* 第一版是轻量 markdown 结构检查,不强 schema。
|
|
@@ -66,15 +76,28 @@ export const definition = {
|
|
|
66
76
|
steps: null // 动态生成
|
|
67
77
|
}
|
|
68
78
|
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
// ═══════════════════════════════════════════════════════════════
|
|
80
|
+
// 第 1 步(LLM):复杂度分类 + 上下文加载(合并原 ①②③④)
|
|
81
|
+
// ═══════════════════════════════════════════════════════════════
|
|
82
|
+
|
|
83
|
+
const stepClassify = {
|
|
84
|
+
id: 'classify',
|
|
85
|
+
name: '复杂度分类与上下文加载',
|
|
86
|
+
prompt: `在生成计划之前,先加载上下文并判定本次需求的复杂度等级(plan_level)。
|
|
74
87
|
|
|
75
88
|
### 操作
|
|
76
|
-
1.
|
|
77
|
-
2.
|
|
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)
|
|
78
101
|
|
|
79
102
|
### 分级规则
|
|
80
103
|
判定 plan_level 为 none 时,需**同时满足**以下所有条件:
|
|
@@ -118,72 +141,27 @@ needs_parallel_execution: true | false
|
|
|
118
141
|
needs_human_review: true | false
|
|
119
142
|
\`\`\`
|
|
120
143
|
|
|
121
|
-
|
|
122
|
-
outputHint: '复杂度分类结果',
|
|
123
|
-
optional: false
|
|
124
|
-
},
|
|
125
|
-
{
|
|
126
|
-
name: '状态检查',
|
|
127
|
-
prompt: `检查当前状态,确认可以执行 plan。
|
|
128
|
-
|
|
129
|
-
### 操作
|
|
130
|
-
1. 运行 \`sillyspec progress show\`
|
|
131
|
-
2. 确认 currentStage 为 "plan"
|
|
132
|
-
|
|
133
|
-
### 输出
|
|
134
|
-
当前状态摘要`,
|
|
135
|
-
outputHint: '状态摘要',
|
|
136
|
-
optional: false
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
name: '加载上下文',
|
|
140
|
-
prompt: `加载所有规范文件和代码库上下文。
|
|
141
|
-
|
|
142
|
-
### 操作
|
|
143
|
-
1. 读取 CODEBASE-OVERVIEW.md + 各子项目上下文
|
|
144
|
-
2. 读取 proposal.md、design.md、requirements.md、tasks.md
|
|
145
|
-
3. 如果存在 decisions.md,必须读取并提取所有当前版本 D-xxx@vN 决策 ID
|
|
146
|
-
- 如果发现 priority=P0/P1 且 status=unresolved/blocking 的决策,停止生成计划,要求先回到 brainstorm 的 Design Grill 修正
|
|
147
|
-
- 如果发现 superseded 决策,只引用最新版本,不引用旧版本
|
|
148
|
-
4. 读取 CONVENTIONS.md、ARCHITECTURE.md、STACK.md
|
|
149
|
-
5. 读取 local.yaml 获取构建/测试命令
|
|
150
|
-
|
|
151
|
-
### 模块文档加载
|
|
152
|
-
6. 读取 \`.sillyspec/docs/<project>/modules/_module-map.yaml\`(不存在则跳过以下步骤)
|
|
153
|
-
7. 根据 design.md 的文件变更清单匹配 _module-map.yaml 中的模块
|
|
154
|
-
8. 读取匹配到的 \`.sillyspec/docs/<project>/modules/<module>.md\`
|
|
155
|
-
9. 将模块文档作为制定计划的上下文,确保计划符合模块当前设计
|
|
156
|
-
10. **利用模块依赖关系辅助分析**:
|
|
157
|
-
- 用 depends_on 判断哪些模块会被间接影响
|
|
158
|
-
- 用 used_by 判断变更会不会影响下游模块
|
|
159
|
-
- 将依赖关系纳入 Wave 分组决策(依赖同一模块的任务尽量同 Wave)
|
|
160
|
-
- 如果变更涉及多个有依赖关系的模块,在 plan.md 的任务总表中标注模块依赖
|
|
144
|
+
然后列出已加载的文件清单(含 decisions.md 当前版本/未决项状态、模块文档 + 模块依赖关系摘要)。
|
|
161
145
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
name: '锚定确认',
|
|
169
|
-
prompt: `确认已读取的文件。
|
|
146
|
+
分类完成后,继续进入下一步。`,
|
|
147
|
+
outputHint: '复杂度分类结果 + 文件清单',
|
|
148
|
+
optional: false
|
|
149
|
+
}
|
|
170
150
|
|
|
171
|
-
|
|
172
|
-
|
|
151
|
+
// ═══════════════════════════════════════════════════════════════
|
|
152
|
+
// 第 2 步(LLM):生成分级计划 + 自检(合并原 ⑤⑥)
|
|
153
|
+
// ═══════════════════════════════════════════════════════════════
|
|
173
154
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
},
|
|
179
|
-
{
|
|
180
|
-
name: '按复杂度生成分级计划',
|
|
181
|
-
prompt: `根据「复杂度分类」步骤的 plan_level 结果,按对应级别生成计划。
|
|
155
|
+
const stepGeneratePlan = {
|
|
156
|
+
id: 'generate_plan',
|
|
157
|
+
name: '生成分级计划与自检',
|
|
158
|
+
prompt: `根据上一步的 plan_level 结果,按对应级别生成计划,然后立即自检。
|
|
182
159
|
|
|
183
160
|
### 操作
|
|
184
161
|
1. 读取上一步输出的 plan_level 分类结果
|
|
185
162
|
2. 读取 tasks.md 和 design.md 了解需求范围
|
|
186
163
|
3. 按 plan_level 选择对应模板输出
|
|
164
|
+
4. 生成后立即自检(见下方自检清单)
|
|
187
165
|
|
|
188
166
|
---
|
|
189
167
|
|
|
@@ -247,7 +225,7 @@ plan_level: light
|
|
|
247
225
|
light 计划的约束:
|
|
248
226
|
- **禁止**生成 Mermaid 图
|
|
249
227
|
- **禁止**估时
|
|
250
|
-
-
|
|
228
|
+
- **禁止**泛泛风险分析(如"需要充分测试")
|
|
251
229
|
- **禁止**放实现细节(函数签名、代码示例)
|
|
252
230
|
- 来源/目标直接引用已有文档,不重新生成
|
|
253
231
|
- 如果存在 decisions.md,所有当前版本 D-xxx@vN 必须在 Tasks 或覆盖矩阵中出现
|
|
@@ -339,16 +317,10 @@ full 计划的约束:
|
|
|
339
317
|
5. 保存到变更目录下的 plan.md(路径格式:\`.sillyspec/changes/<change-name>/plan.md\`,其中 <change-name> 是变更目录名,直接使用,不加子目录。正确路径示例:\`.sillyspec/changes/2026-05-28-agent-log-streaming/plan.md\`)
|
|
340
318
|
**plan_level 为 none 时生成最小 plan.md(占位),不生成完整蓝图。**
|
|
341
319
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
optional: false
|
|
346
|
-
},
|
|
347
|
-
{
|
|
348
|
-
name: '自检总览',
|
|
349
|
-
prompt: `根据 plan_level 检查对应的计划质量。
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
### 自检(生成后立即执行,不另开步骤)
|
|
350
323
|
|
|
351
|
-
### 操作
|
|
352
324
|
读取上一步的 plan_level 分类结果,按级别执行对应的自检:
|
|
353
325
|
|
|
354
326
|
#### plan_level = none
|
|
@@ -390,213 +362,18 @@ plan_level + 计划内容(none 级别输出建议操作)`,
|
|
|
390
362
|
- [ ] 没有泛泛风险分析(如"需要充分测试")
|
|
391
363
|
|
|
392
364
|
### 输出
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
optional: false
|
|
397
|
-
}
|
|
398
|
-
]
|
|
399
|
-
|
|
400
|
-
// 固定后缀步骤
|
|
401
|
-
export const fixedSuffix = [
|
|
402
|
-
{
|
|
403
|
-
name: '重排 Wave(基于 depends_on)',
|
|
404
|
-
prompt: `根据蓝图的 depends_on 字段重排 Wave 分组,更新 plan.md。
|
|
405
|
-
|
|
406
|
-
### 操作
|
|
407
|
-
1. 读取所有 tasks/task-NN.md 的 frontmatter,提取每个任务的 depends_on 列表
|
|
408
|
-
2. 拓扑排序:无依赖的任务 → Wave 1,依赖 Wave 1 的 → Wave 2,依此类推
|
|
409
|
-
3. 检查是否存在循环依赖,如有则报错暂停
|
|
410
|
-
4. 用重排结果更新 plan.md:
|
|
411
|
-
- Wave 分组(含 checkbox 列表)
|
|
412
|
-
- 任务总表的 Wave 列
|
|
413
|
-
- 依赖关系图(Mermaid)
|
|
414
|
-
- 关键路径
|
|
415
|
-
5. 如果 Wave 分组与原始 plan.md 一致,只需确认一致即可,不需要重写
|
|
416
|
-
|
|
417
|
-
### 规则
|
|
418
|
-
- **Wave 是执行单元,同 Wave 内任务必须无依赖(可并行)**
|
|
419
|
-
- 有 depends_on 关系的任务必须在不同的 Wave
|
|
420
|
-
- depends_on 为空的任务放 Wave 1
|
|
421
|
-
- 取决于拓扑排序的最大深度决定 Wave 编号
|
|
422
|
-
|
|
423
|
-
### 输出
|
|
424
|
-
重排后的 Wave 分组摘要(如果与原 plan.md 一致则说明一致)`,
|
|
425
|
-
outputHint: 'Wave 重排结果',
|
|
426
|
-
optional: false
|
|
427
|
-
},
|
|
428
|
-
{
|
|
429
|
-
name: '审查一致性',
|
|
430
|
-
prompt: `审查所有 task-N.md 的一致性。
|
|
431
|
-
|
|
432
|
-
### 操作
|
|
433
|
-
1. 读取所有 tasks/task-NN.md
|
|
434
|
-
2. 检查:
|
|
435
|
-
- 文件路径有没有冲突(两个任务改同一个文件)
|
|
436
|
-
- 依赖关系和 plan.md 的 Wave 分组是否一致
|
|
437
|
-
- 验收标准和 plan.md 的全局标准是否矛盾
|
|
438
|
-
- 接口定义是否自洽
|
|
439
|
-
5. **生产接线路径检查**(Critical):
|
|
440
|
-
- 读取 design.md,搜索以下关键词:
|
|
441
|
-
- 注入 / inject / 构造 / constructor / 初始化 / init / 启动路径 / startup / main / cli / entrypoint / daemon start / bootstrap
|
|
442
|
-
- 如果命中,提取提到的具体文件名(如 "在 cli.ts 中"、"main.ts 中实例化"、"Daemon 构造函数")
|
|
443
|
-
- 收集所有 tasks/task-NN.md 的 frontmatter 中 \`allowed_paths\` 列表
|
|
444
|
-
- 检查:design 中提到的生产入口文件是否在某个 task 的 allowed_paths 中?
|
|
445
|
-
- 如果 design 提到了入口文件但所有 task 的 allowed_paths 都不含该文件:
|
|
446
|
-
- **这是 plan contract 失败**
|
|
447
|
-
- 列出具体矛盾:\`design says: X 实例化 Y,但 allowed_paths 不含 Z\`
|
|
448
|
-
- **不自动修复**,暂停等待用户决定
|
|
449
|
-
- 如果 design 明确说"不需要改入口文件"并给出了理由,视为通过
|
|
450
|
-
- 调用:\`sillyspec run plan --wait --reason "生产接线路径检查失败" --options "修改 allowed_paths,修改 design,确认不需要改入口" --output "矛盾清单"\`
|
|
451
|
-
6. **符号影响面检查**(Critical):
|
|
452
|
-
- 对每个 task 涉及的文件,检查是否修改了构造函数、接口、DTO、API client 方法签名
|
|
453
|
-
- 如果是,搜索所有调用点:
|
|
454
|
-
\`\`\`bash
|
|
455
|
-
rg "new <ClassName>" src/
|
|
456
|
-
rg "<methodName>" src/ --type ts --type js
|
|
457
|
-
\`\`\`
|
|
458
|
-
- 对比调用点与 plan.md/task 的 allowed_paths
|
|
459
|
-
- 发现调用点不在任务范围内 → **plan contract 失败**
|
|
460
|
-
- 调用:\`sillyspec run plan --wait --reason "符号影响面检查发现遗漏调用点" --options "扩展 allowed_paths,添加新任务,确认不需要改" --output "遗漏调用点清单"\`
|
|
461
|
-
3. 发现问题 → 列出问题清单,暂停等待用户决定
|
|
462
|
-
- 调用:\`sillyspec run plan --wait --reason "审查发现一致性问题" --options "自动修复,手动修复,忽略并继续" --output "问题清单"\`
|
|
463
|
-
- **绝对禁止**:自己决定修复方向然后自动修复
|
|
464
|
-
4. 无问题 → 正常完成
|
|
465
|
-
|
|
466
|
-
### 输出
|
|
467
|
-
一致性审查结果`,
|
|
468
|
-
outputHint: '审查结果',
|
|
469
|
-
optional: false
|
|
470
|
-
},
|
|
471
|
-
{
|
|
472
|
-
name: '保存并更新进度',
|
|
473
|
-
prompt: `确认所有文件已保存,更新进度。
|
|
474
|
-
|
|
475
|
-
### 操作
|
|
476
|
-
1. 确认 plan.md 和所有 tasks/task-NN.md 已存在
|
|
477
|
-
|
|
478
|
-
### 输出
|
|
479
|
-
文件列表 + 下一步命令`,
|
|
480
|
-
outputHint: '文件列表',
|
|
481
|
-
optional: false
|
|
482
|
-
}
|
|
483
|
-
]
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* 解析 plan.md 获取任务数量
|
|
487
|
-
*/
|
|
488
|
-
function parseTaskCount(planContent) {
|
|
489
|
-
if (!planContent || typeof planContent !== 'string') return 0
|
|
490
|
-
const matches = planContent.match(/^[-*]\s*\[[ x]\]\s*task-\d+/gm)
|
|
491
|
-
return matches ? matches.length : 0
|
|
365
|
+
plan_level + 计划内容 + 自检结果(一次输出)`,
|
|
366
|
+
outputHint: '计划内容 + 自检结果',
|
|
367
|
+
optional: false
|
|
492
368
|
}
|
|
493
369
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
function buildTaskPrompt(taskNum, taskName, changeDir) {
|
|
498
|
-
const num = String(taskNum).padStart(2, '0')
|
|
499
|
-
return `编写任务蓝图 tasks/task-${num}.md
|
|
500
|
-
|
|
501
|
-
当前时间:<now-datetime>(frontmatter 的 created_at 使用此值)
|
|
502
|
-
当前用户:<git-user>(frontmatter 的 author 使用此值)
|
|
503
|
-
|
|
504
|
-
### 任务
|
|
505
|
-
${taskName}
|
|
506
|
-
|
|
507
|
-
### 文件路径
|
|
508
|
-
\`.sillyspec/changes/<change-name>/tasks/task-${num}.md\`
|
|
509
|
-
|
|
510
|
-
### 格式要求(必须严格遵守)
|
|
511
|
-
\`\`\`markdown
|
|
512
|
-
---
|
|
513
|
-
id: task-${num}
|
|
514
|
-
title: ${taskName}
|
|
515
|
-
priority: P0
|
|
516
|
-
estimated_hours: N
|
|
517
|
-
depends_on: []
|
|
518
|
-
blocks: []
|
|
519
|
-
requirement_ids: [FR-01]
|
|
520
|
-
decision_ids: [D-001@v1]
|
|
521
|
-
allowed_paths:
|
|
522
|
-
- 允许修改的路径范围
|
|
523
|
-
---
|
|
524
|
-
|
|
525
|
-
# task-${num}: ${taskName}
|
|
526
|
-
|
|
527
|
-
## 修改文件(必填)
|
|
528
|
-
- 精确到文件路径,列出所有需要新增或修改的文件
|
|
529
|
-
|
|
530
|
-
## 覆盖来源
|
|
531
|
-
- Requirements: FR-xx(来自 requirements.md)
|
|
532
|
-
- Decisions: D-xxx@vN(如存在 decisions.md)
|
|
533
|
-
|
|
534
|
-
## 实现要求
|
|
535
|
-
1. 具体做什么,写清楚
|
|
536
|
-
2. ...
|
|
537
|
-
|
|
538
|
-
## 接口定义(代码类任务必填)
|
|
539
|
-
写方法签名、数据结构、控制流伪代码。AI executor 应能照着直接编码。
|
|
540
|
-
|
|
541
|
-
## 边界处理(必填)
|
|
542
|
-
- null/空值行为
|
|
543
|
-
- 兼容旧行为(brownfield:未配置新功能时行为不变)
|
|
544
|
-
- 异常不静默吞掉(明确返回值或抛出)
|
|
545
|
-
- 不修改传入参数
|
|
546
|
-
- 歧义/冲突场景的处理策略
|
|
547
|
-
|
|
548
|
-
## 非目标(本任务不做的事)
|
|
549
|
-
- 明确列出边界,防止 scope creep
|
|
550
|
-
|
|
551
|
-
## 参考
|
|
552
|
-
- 已有代码可参考的模式
|
|
553
|
-
- 相关的 CONVENTIONS.md 条目
|
|
554
|
-
|
|
555
|
-
## TDD 步骤
|
|
556
|
-
1. 写 XxxTest,覆盖场景 A/B/C
|
|
557
|
-
2. 运行 <test-cmd> 确认测试失败
|
|
558
|
-
3. 实现 Xxx
|
|
559
|
-
4. 运行 <test-cmd> 确认测试通过
|
|
560
|
-
5. 运行全量测试确认无回退
|
|
561
|
-
(纯配置/文档类任务简化为:1. 实现 2. 验证)
|
|
562
|
-
|
|
563
|
-
## 验收标准
|
|
564
|
-
| # | 验证步骤 | 通过标准 |
|
|
565
|
-
|---|---|---|
|
|
566
|
-
| AC-01 | 具体操作 | 期望结果 |
|
|
567
|
-
| AC-02 | ... | ... |
|
|
568
|
-
\`\`\`
|
|
569
|
-
|
|
570
|
-
### frontmatter 元数据说明
|
|
571
|
-
- \`priority\`: P0(必须)/ P1(重要)/ P2(可选)
|
|
572
|
-
- \`estimated_hours\`: 预估工时,单个 task ≤ 8h
|
|
573
|
-
- \`depends_on\`: 依赖的前序 task 编号列表
|
|
574
|
-
- \`blocks\`: 被本 task 阻塞的后续 task 编号列表
|
|
575
|
-
- \`requirement_ids\`: 本任务覆盖的 FR-xxx 列表
|
|
576
|
-
- \`decision_ids\`: 本任务覆盖的当前版本 D-xxx@vN 列表;无 decisions.md 时可为空数组
|
|
577
|
-
- \`allowed_paths\`: AI executor 可以修改的文件路径范围(安全边界)
|
|
578
|
-
|
|
579
|
-
### 关键规则
|
|
580
|
-
- task-N.md 必须独立完整,execute 子代理只读这一个文件就能干活
|
|
581
|
-
- 不要依赖其他 task-N.md 的内容
|
|
582
|
-
- 接口定义写到"搬砖工照着做"的程度
|
|
583
|
-
- 边界处理至少覆盖 5 条规则
|
|
584
|
-
- 验收标准用表格格式,每条可点击验证,禁止"功能可演示"类笼统表述
|
|
585
|
-
- 如果存在 decisions.md,不允许丢失当前版本 D-xxx@vN;无法覆盖的 D-xxx@vN 必须写入非目标或剩余风险
|
|
586
|
-
- 写完后保存到文件
|
|
587
|
-
|
|
588
|
-
### 操作
|
|
589
|
-
1. 读取 design.md 和 plan.md 了解上下文
|
|
590
|
-
2. 读取相关源文件了解现有代码
|
|
591
|
-
3. 编写任务蓝图
|
|
592
|
-
4. 保存到 tasks/task-${num}.md
|
|
593
|
-
|
|
594
|
-
### 输出
|
|
595
|
-
任务蓝图内容摘要`
|
|
596
|
-
}
|
|
370
|
+
// ═══════════════════════════════════════════════════════════════
|
|
371
|
+
// 第 3 步(LLM):生成紧凑 TaskCard(子代理并行)
|
|
372
|
+
// ═══════════════════════════════════════════════════════════════
|
|
597
373
|
|
|
598
374
|
/**
|
|
599
|
-
*
|
|
375
|
+
* 构建紧凑 TaskCard 协调器步骤(单步,子代理并行写卡片)
|
|
376
|
+
* 每个 task 生成 20~40 行紧凑可执行卡片
|
|
600
377
|
*/
|
|
601
378
|
export function buildCoordinatorStep(changeDir, taskNames) {
|
|
602
379
|
const taskList = taskNames.map((name, i) => {
|
|
@@ -615,76 +392,54 @@ export function buildCoordinatorStep(changeDir, taskNames) {
|
|
|
615
392
|
|
|
616
393
|
操作:
|
|
617
394
|
1. 读取 ${changeDir}/design.md 和 ${changeDir}/plan.md 了解上下文
|
|
618
|
-
2.
|
|
619
|
-
|
|
620
|
-
ls .sillyspec/docs/*/modules/_module-map.yaml 2>/dev/null\
|
|
621
|
-
确认模块划分,读取涉及模块的 \
|
|
622
|
-
ls .sillyspec/docs/*/modules/*.md 2>/dev/null\
|
|
623
|
-
3. 读取相关源文件了解现有代码
|
|
624
|
-
4. 按以下格式编写任务蓝图并保存到 ${changeDir}/tasks/task-${num}.md:
|
|
395
|
+
2. 读取相关源文件了解现有代码
|
|
396
|
+
3. 生成紧凑 TaskCard(20~40 行),格式如下:
|
|
625
397
|
|
|
626
398
|
---
|
|
627
399
|
id: task-${num}
|
|
628
400
|
title: ${name}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
401
|
+
author: <git-user>
|
|
402
|
+
created_at: <now-datetime>
|
|
403
|
+
priority: P0
|
|
404
|
+
depends_on: []
|
|
405
|
+
blocks: []
|
|
633
406
|
requirement_ids: [FR-XX]
|
|
634
407
|
decision_ids: [D-XXX@vN]
|
|
635
408
|
allowed_paths:
|
|
636
|
-
-
|
|
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(如:不修改传入参数)
|
|
637
425
|
---
|
|
638
426
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
-
|
|
646
|
-
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
1. 具体做什么
|
|
650
|
-
|
|
651
|
-
## 接口定义(代码类任务必填)
|
|
652
|
-
方法签名、数据结构、控制流伪代码
|
|
653
|
-
|
|
654
|
-
## 边界处理(必填)
|
|
655
|
-
- null/空值行为
|
|
656
|
-
- 兼容旧行为(brownfield:未配置新功能时行为不变)
|
|
657
|
-
- 异常不静默吞掉(明确返回值或抛出)
|
|
658
|
-
- 不修改传入参数
|
|
659
|
-
- 歧义/冲突场景的处理策略
|
|
660
|
-
|
|
661
|
-
## 非目标(本任务不做的事)
|
|
662
|
-
- 明确边界,防止 scope creep
|
|
663
|
-
|
|
664
|
-
## 参考
|
|
665
|
-
- 可参考的模式
|
|
666
|
-
|
|
667
|
-
## TDD 步骤
|
|
668
|
-
1. 写测试 → 2. 确认失败 → 3. 写代码 → 4. 确认通过 → 5. 回归
|
|
669
|
-
|
|
670
|
-
## 验收标准
|
|
671
|
-
| # | 验证步骤 | 通过标准 |
|
|
672
|
-
|---|---|---|
|
|
673
|
-
| AC-01 | ... | ... |
|
|
674
|
-
|
|
675
|
-
关键规则:
|
|
676
|
-
- 必须独立完整,execute 子代理只读这一个文件就能干活
|
|
677
|
-
- 不要依赖其他 task-N.md 的内容
|
|
678
|
-
- 接口定义写到"搬砖工照着做"的程度
|
|
679
|
-
- 边界处理至少 5 条
|
|
680
|
-
- 验收标准用表格,禁止笼统表述
|
|
681
|
-
- 如果存在 decisions.md,不允许丢失当前版本 D-xxx@vN;无法覆盖的 D-xxx@vN 必须写入非目标或剩余风险
|
|
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 中标注
|
|
682
437
|
- 写完后用 Write tool 保存到文件
|
|
683
438
|
\`\`\``
|
|
684
439
|
}).join('\n\n')
|
|
685
440
|
|
|
686
441
|
|
|
687
|
-
const prompt = `为 plan.md
|
|
442
|
+
const prompt = `为 plan.md 中的每个任务生成紧凑 TaskCard。
|
|
688
443
|
|
|
689
444
|
## 任务清单
|
|
690
445
|
${taskList}
|
|
@@ -695,7 +450,7 @@ ${taskList}
|
|
|
695
450
|
|
|
696
451
|
## 执行方式(必须严格遵守)
|
|
697
452
|
|
|
698
|
-
**你必须使用 Agent tool
|
|
453
|
+
**你必须使用 Agent tool 启动子代理来写每个卡片,不要自己写。**
|
|
699
454
|
|
|
700
455
|
1. 确认 \`${changeDir}/tasks/\` 目录存在(不存在则创建)
|
|
701
456
|
2. 为每个任务启动一个独立子代理(Agent tool),可并行启动多个
|
|
@@ -708,20 +463,67 @@ ${taskList}
|
|
|
708
463
|
|
|
709
464
|
${subagentPrompts}
|
|
710
465
|
|
|
711
|
-
##
|
|
466
|
+
## 验收(生成后自查,不另开步骤)
|
|
712
467
|
- 每个 task-N.md 文件存在且非空
|
|
713
|
-
-
|
|
714
|
-
-
|
|
715
|
-
-
|
|
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
|
+
- 如发现矛盾,列出问题清单,不要自动修复`
|
|
716
475
|
|
|
717
476
|
return {
|
|
718
|
-
|
|
477
|
+
id: 'generate_blueprints',
|
|
478
|
+
name: '生成 TaskCard(子代理并行)',
|
|
719
479
|
prompt,
|
|
720
|
-
outputHint: '
|
|
480
|
+
outputHint: 'TaskCard 生成结果',
|
|
721
481
|
optional: false
|
|
722
482
|
}
|
|
723
483
|
}
|
|
724
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
|
+
|
|
725
527
|
/**
|
|
726
528
|
* 从 plan.md 解析任务名列表
|
|
727
529
|
*/
|
|
@@ -737,6 +539,8 @@ function parseTaskNames(planContent) {
|
|
|
737
539
|
|
|
738
540
|
/**
|
|
739
541
|
* 动态构建 plan 步骤列表
|
|
542
|
+
* 新架构:3 个 LLM 步骤 + 1 个 noAI 步骤 = 4 阶段
|
|
543
|
+
*
|
|
740
544
|
* @param {string|null} changeDir - 变更目录路径
|
|
741
545
|
* @param {string|null} planContent - plan.md 内容(可选,用于解析任务数)
|
|
742
546
|
* @returns {Array} 步骤列表
|
|
@@ -754,9 +558,10 @@ export function buildPlanSteps(changeDir = null, planContent = null) {
|
|
|
754
558
|
}
|
|
755
559
|
}
|
|
756
560
|
|
|
757
|
-
//
|
|
561
|
+
// 没有任务数则用固定步骤(兼容旧流程,无蓝图步骤无 postcheck)
|
|
758
562
|
if (taskCount === 0) {
|
|
759
|
-
|
|
563
|
+
const postcheck = changeDir ? [buildPostcheckStep(changeDir)] : []
|
|
564
|
+
return [...fixedPrefix, ...postcheck]
|
|
760
565
|
}
|
|
761
566
|
|
|
762
567
|
// 解析任务名
|
|
@@ -770,7 +575,8 @@ export function buildPlanSteps(changeDir = null, planContent = null) {
|
|
|
770
575
|
}
|
|
771
576
|
}
|
|
772
577
|
|
|
773
|
-
//
|
|
578
|
+
// 生成协调器步骤(TaskCard 生成)+ postcheck
|
|
774
579
|
const coordinatorStep = buildCoordinatorStep(changeDir, taskNames)
|
|
775
|
-
|
|
580
|
+
const postcheckStep = buildPostcheckStep(changeDir)
|
|
581
|
+
return [...fixedPrefix, coordinatorStep, postcheckStep]
|
|
776
582
|
}
|