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.
- package/.claude/skills/sillyspec-archive/SKILL.md +21 -21
- package/.claude/skills/sillyspec-auto/SKILL.md +83 -83
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +44 -44
- package/.claude/skills/sillyspec-commit/SKILL.md +106 -106
- package/.claude/skills/sillyspec-continue/SKILL.md +45 -45
- package/.claude/skills/sillyspec-doctor/SKILL.md +31 -31
- package/.claude/skills/sillyspec-execute/SKILL.md +30 -30
- package/.claude/skills/sillyspec-explore/SKILL.md +109 -109
- package/.claude/skills/sillyspec-knowledge/SKILL.md +269 -269
- package/.claude/skills/sillyspec-plan/SKILL.md +21 -21
- package/.claude/skills/sillyspec-propose/SKILL.md +21 -21
- package/.claude/skills/sillyspec-quick/SKILL.md +21 -21
- package/.claude/skills/sillyspec-resume/SKILL.md +68 -68
- package/.claude/skills/sillyspec-scan/SKILL.md +21 -21
- package/.claude/skills/sillyspec-state/SKILL.md +54 -54
- package/.claude/skills/sillyspec-status/SKILL.md +21 -21
- package/.claude/skills/sillyspec-verify/SKILL.md +21 -21
- package/.claude/skills/sillyspec-workspace/SKILL.md +157 -157
- package/.husky/pre-push +13 -13
- package/CLAUDE.md +18 -18
- package/README.md +198 -188
- package/SKILL.md +90 -91
- package/bin/sillyspec.js +2 -2
- package/docs/brainstorm-plan-contract.md +64 -64
- package/docs/plan-execute-contract.md +123 -123
- package/docs/platform-scan-protocol.md +298 -298
- package/docs/revision-mode.md +115 -115
- package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +99 -99
- package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +218 -218
- package/docs/sillyspec/file-lifecycle/stage-artifacts.md +167 -167
- package/docs/sillyspec/file-lifecycle/storage-and-state.md +148 -148
- package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +211 -193
- package/docs/sillyspec/file-lifecycle.md +125 -125
- package/docs/workflow-contract-regression.md +106 -106
- package/docs/worktree-isolation.md +252 -252
- package/package.json +40 -40
- package/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -7446
- package/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -1
- package/packages/dashboard/dist/index.html +16 -16
- package/packages/dashboard/index.html +15 -15
- package/packages/dashboard/package-lock.json +2384 -2384
- package/packages/dashboard/package.json +25 -25
- package/packages/dashboard/server/executor.js +86 -86
- package/packages/dashboard/server/index.js +588 -588
- package/packages/dashboard/server/parser.js +526 -526
- package/packages/dashboard/server/watcher.js +344 -344
- package/packages/dashboard/src/App.vue +558 -558
- package/packages/dashboard/src/components/ActionBar.vue +93 -93
- package/packages/dashboard/src/components/CommandPalette.vue +96 -96
- package/packages/dashboard/src/components/DetailPanel.vue +137 -137
- package/packages/dashboard/src/components/LogStream.vue +65 -65
- package/packages/dashboard/src/components/PipelineStage.vue +95 -95
- package/packages/dashboard/src/components/PipelineView.vue +156 -156
- package/packages/dashboard/src/components/ProjectList.vue +210 -210
- package/packages/dashboard/src/components/StageBadge.vue +67 -67
- package/packages/dashboard/src/components/StepCard.vue +94 -94
- package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -48
- package/packages/dashboard/src/components/detail/GitDetail.vue +61 -61
- package/packages/dashboard/src/components/detail/TechDetail.vue +43 -43
- package/packages/dashboard/src/composables/useDashboard.js +170 -170
- package/packages/dashboard/src/composables/useKeyboard.js +119 -119
- package/packages/dashboard/src/composables/useWebSocket.js +129 -129
- package/packages/dashboard/src/main.js +8 -8
- package/packages/dashboard/src/style.css +132 -132
- package/packages/dashboard/vite.config.js +18 -18
- package/src/brainstorm-postcheck.js +158 -158
- package/src/change-list.js +52 -52
- package/src/change-risk-profile.js +352 -352
- package/src/classify-change.js +73 -73
- package/src/constants.js +70 -70
- package/src/contract-matrix.js +278 -278
- package/src/db.js +201 -201
- package/src/endpoint-extractor.js +315 -315
- package/src/hooks/claude-pre-tool-use.cjs +125 -125
- package/src/hooks/worktree-guard.js +653 -653
- package/src/index.js +922 -900
- package/src/init.js +431 -431
- package/src/knowledge-match.js +130 -130
- package/src/migrate.js +117 -117
- package/src/modules.js +482 -482
- package/src/progress.js +1734 -1734
- package/src/run.js +3465 -3358
- package/src/scan-postcheck.js +387 -383
- package/src/setup.js +398 -398
- package/src/stage-contract.js +700 -700
- package/src/stages/archive.js +160 -160
- package/src/stages/brainstorm-auto.js +229 -229
- package/src/stages/brainstorm.js +645 -645
- package/src/stages/doctor.js +365 -365
- package/src/stages/execute.js +625 -625
- package/src/stages/explore.js +34 -34
- package/src/stages/index.js +29 -29
- package/src/stages/knowledge.js +498 -498
- package/src/stages/plan-postcheck.js +511 -513
- package/src/stages/plan.js +582 -582
- package/src/stages/propose.js +174 -174
- package/src/stages/quick.js +82 -82
- package/src/stages/scan.js +558 -558
- package/src/stages/status.js +65 -65
- package/src/stages/verify.js +322 -322
- package/src/sync.js +497 -497
- package/src/task-review.js +346 -346
- package/src/workflow.js +785 -785
- package/src/worktree-apply.js +549 -549
- package/src/worktree-deps.js +185 -0
- package/src/worktree.js +982 -932
- package/templates/workflows/archive-impact.yaml +79 -79
- package/templates/workflows/scan-docs.yaml +132 -132
- package/test/brainstorm-plan-contract.test.mjs +273 -273
- package/test/check-syntax.mjs +26 -26
- package/test/contract-artifacts.test.mjs +323 -323
- package/test/decision-supersede.test.mjs +277 -277
- package/test/knowledge-match.test.mjs +231 -231
- package/test/plan-execute-contract.test.mjs +330 -330
- package/test/plan-optimization.test.mjs +572 -572
- package/test/platform-artifacts.test.mjs +166 -166
- package/test/platform-failure-samples.test.mjs +199 -199
- package/test/platform-recovery-chain.test.mjs +167 -167
- package/test/platform-recovery.test.mjs +136 -136
- package/test/platform-scan-p0.test.mjs +168 -168
- package/test/revision-v1.test.mjs +1145 -1145
- package/test/run-scan-project-parse.test.mjs +200 -200
- package/test/run-tests.mjs +48 -48
- package/test/scan-knowledge.test.mjs +175 -175
- package/test/scan-paths.test.mjs +68 -68
- package/test/scan-postcheck.test.mjs +197 -197
- package/test/spec-dir.test.mjs +206 -206
- package/test/stage-contract.test.mjs +299 -299
- package/test/stage-definitions.test.mjs +39 -39
- package/test/wait-gates.test.mjs +496 -496
- package/test/worktree-deps-provision.test.mjs +148 -0
- package/test/worktree-guard.test.mjs +71 -71
- package/test/worktree-native-overlay.test.mjs +188 -188
package/src/stages/execute.js
CHANGED
|
@@ -1,625 +1,625 @@
|
|
|
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
|
-
if (!currentWave) continue
|
|
374
|
-
|
|
375
|
-
const taskMatch = line.match(/^[-*]\s*\[[ x]\]\s*(.+)/)
|
|
376
|
-
if (taskMatch) {
|
|
377
|
-
const taskNoMatch = taskMatch[1].match(/\btask-(\d+)\b/i)
|
|
378
|
-
currentTask = {
|
|
379
|
-
index: taskNoMatch ? parseInt(taskNoMatch[1], 10) : null,
|
|
380
|
-
name: taskMatch[1].trim(),
|
|
381
|
-
file: '',
|
|
382
|
-
steps: '',
|
|
383
|
-
reference: ''
|
|
384
|
-
}
|
|
385
|
-
// 兼容旧格式:任务名后跟 (文件路径)
|
|
386
|
-
const fileMatch = taskMatch[1].match(/\(([^)]+)\)$/)
|
|
387
|
-
if (fileMatch) {
|
|
388
|
-
currentTask.file = fileMatch[1]
|
|
389
|
-
currentTask.name = taskMatch[1].replace(/\([^)]+\)$/, '').trim()
|
|
390
|
-
}
|
|
391
|
-
currentWave.tasks.push(currentTask)
|
|
392
|
-
continue
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// 解析子行信息(修改/参考/步骤)
|
|
396
|
-
if (currentTask) {
|
|
397
|
-
const modMatch = line.match(/^\s+-\s*修改:\s*(.+)/)
|
|
398
|
-
if (modMatch) { currentTask.file = modMatch[1].trim(); continue }
|
|
399
|
-
|
|
400
|
-
const refMatch = line.match(/^\s+-\s*参考:\s*(.+)/)
|
|
401
|
-
if (refMatch) { currentTask.reference = refMatch[1].trim(); continue }
|
|
402
|
-
|
|
403
|
-
const stepMatch = line.match(/^\s+-\s*步骤:/)
|
|
404
|
-
if (stepMatch) { currentTask.steps = line.replace(/^\s+-\s*步骤:\s*/, '').trim(); continue }
|
|
405
|
-
|
|
406
|
-
// 步骤续行(数字开头的子步骤)
|
|
407
|
-
if (currentTask.steps && line.match(/^\s+\d+\./)) {
|
|
408
|
-
currentTask.steps += '\n' + line.trim()
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
return waves
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* 为 Wave 生成 prompt(强制子代理执行)
|
|
418
|
-
*/
|
|
419
|
-
function buildWavePrompt(wave, waveIndex, changeDir, worktreePath) {
|
|
420
|
-
// ── Contract Matrix:检查是否有 provider/consumer 契约需要注入 ──
|
|
421
|
-
let contractInjection = ''
|
|
422
|
-
if (changeDir) {
|
|
423
|
-
try {
|
|
424
|
-
const { buildContractMatrix, buildConsumerInjection } = require('../contract-matrix.js')
|
|
425
|
-
const planFile = path.join(changeDir, 'plan.md')
|
|
426
|
-
if (existsSync(planFile)) {
|
|
427
|
-
const planContent = readFileSync(planFile, 'utf8')
|
|
428
|
-
const contracts = buildContractMatrix(planContent, changeDir)
|
|
429
|
-
if (contracts.length > 0) {
|
|
430
|
-
// 收集本 wave 所有 task 的注入内容
|
|
431
|
-
const waveTasks = wave.tasks.map((t, ti) => {
|
|
432
|
-
const num = String(t.index || (ti + 1)).padStart(2, '0')
|
|
433
|
-
return `task-${num}`
|
|
434
|
-
})
|
|
435
|
-
const relevantContracts = contracts.filter(c => waveTasks.includes(c.consumer))
|
|
436
|
-
if (relevantContracts.length > 0) {
|
|
437
|
-
contractInjection = `
|
|
438
|
-
### API Contract Matrix
|
|
439
|
-
本 Wave 存在前端/后端跨 task 契约:
|
|
440
|
-
${relevantContracts.map(c => `- **${c.consumer}** 消费 **${c.provider}** 产出的 API`).join('\n')}
|
|
441
|
-
`
|
|
442
|
-
// 为每个 consumer task 生成详细注入
|
|
443
|
-
for (const taskName of waveTasks) {
|
|
444
|
-
const injection = buildConsumerInjection(changeDir, join(changeDir, '..', '..'), taskName, contracts)
|
|
445
|
-
if (injection) {
|
|
446
|
-
contractInjection += `
|
|
447
|
-
### 子代理 ${taskName} 的契约注入
|
|
448
|
-
为 ${taskName} 启动子代理时,在子代理 prompt 末尾追加以下内容:
|
|
449
|
-
|
|
450
|
-
<contract-injection>
|
|
451
|
-
${injection}
|
|
452
|
-
</contract-injection>
|
|
453
|
-
`
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
} catch {}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// 构建任务摘要(不再内联完整蓝图,减少上下文污染)
|
|
463
|
-
const taskSummary = wave.tasks.map((t, ti) => {
|
|
464
|
-
const taskNum = String(t.index || (ti + 1)).padStart(2, '0')
|
|
465
|
-
const taskRelPath = changeDir
|
|
466
|
-
? `.sillyspec/changes/${path.basename(changeDir)}/tasks/task-${taskNum}.md`
|
|
467
|
-
: `task-${taskNum}.md`
|
|
468
|
-
const fileInfo = t.file ? ` (${t.file})` : ''
|
|
469
|
-
return `task-${taskNum}: ${t.name}${fileInfo} → ${taskRelPath}`
|
|
470
|
-
}).join('\n')
|
|
471
|
-
|
|
472
|
-
const taskList = wave.tasks.map((t, ti) => {
|
|
473
|
-
const taskNum = String(t.index || (ti + 1)).padStart(2, '0')
|
|
474
|
-
let s = `- [ ] ${t.name}`
|
|
475
|
-
if (t.file) s += ` (${t.file})`
|
|
476
|
-
return s
|
|
477
|
-
}).join('\n')
|
|
478
|
-
|
|
479
|
-
const worktreeSection = (worktreePath)
|
|
480
|
-
? `
|
|
481
|
-
### 工作目录(必须严格遵守)
|
|
482
|
-
|
|
483
|
-
调用 Task 工具启动子代理时,**workdir 参数是强制必传的**。
|
|
484
|
-
不传 workdir 会导致子代理把文件写到主工作区而非 worktree,破坏隔离。
|
|
485
|
-
|
|
486
|
-
\`\`\`json
|
|
487
|
-
{
|
|
488
|
-
"subagent_type": "general",
|
|
489
|
-
"workdir": "${worktreePath}",
|
|
490
|
-
"prompt": "在此编写任务描述..."
|
|
491
|
-
}
|
|
492
|
-
\`\`\`
|
|
493
|
-
|
|
494
|
-
### 注意
|
|
495
|
-
蓝图文件(tasks.md / design.md / proposal.md / requirements.md)在主工作区 .sillyspec/changes/<change>/ 下,它们可能不在 worktree 中。读取蓝图时使用主工作区路径,不要拼接到 worktree 路径下。
|
|
496
|
-
`
|
|
497
|
-
: ''
|
|
498
|
-
|
|
499
|
-
return `## Wave ${waveIndex}: 执行以下任务
|
|
500
|
-
|
|
501
|
-
## 执行方式(必须严格遵守)
|
|
502
|
-
|
|
503
|
-
**每个任务必须由独立子代理执行,你不要自己写代码。**
|
|
504
|
-
|
|
505
|
-
你的角色是调度者 + 审查者:
|
|
506
|
-
1. 为每个任务启动一个子代理(Agent tool),同 Wave 内可并行
|
|
507
|
-
2. 子代理完成后审查结果
|
|
508
|
-
3. 勾选 plan.md 中的 checkbox
|
|
509
|
-
4. 记录改动文件和测试结果
|
|
510
|
-
|
|
511
|
-
${worktreeSection}
|
|
512
|
-
### 任务摘要(按需读取完整蓝图)
|
|
513
|
-
为每个任务启动子代理时,**只需告知任务目标和蓝图文件路径,让子代理按需读取**:
|
|
514
|
-
|
|
515
|
-
${taskSummary}
|
|
516
|
-
|
|
517
|
-
子代理 prompt 要点:
|
|
518
|
-
1. 任务目标(简短描述)
|
|
519
|
-
2. 蓝图文件路径(让子代理自行读取详情)
|
|
520
|
-
3. 编码铁律:先读后写、TDD、不编造方法、只做蓝图里写的事、遵守边界处理规则、不超出 allowed_paths
|
|
521
|
-
4. 如存在模块文档(.sillyspec/docs/*/modules/),按需读取涉及模块的 <module>.md 参考接口约定和数据流
|
|
522
|
-
|
|
523
|
-
### Wave 开始前
|
|
524
|
-
1. 读取 design.md 的「编码铁律」章节(如果存在),严格遵守
|
|
525
|
-
2. 读取 plan.md 了解全局任务划分和依赖关系
|
|
526
|
-
3. 确认本 Wave 的输入/输出契约(前置 Wave 产出了什么,本 Wave 需要消费什么)
|
|
527
|
-
4. 检查前置 Wave 的产出是否完整(文件是否存在、测试是否通过)
|
|
528
|
-
5. **上下文分层加载**:
|
|
529
|
-
- 🔥 热上下文:design.md 编码铁律 + 当前 Wave 任务(必须加载)
|
|
530
|
-
- 🌡️ 温上下文:CONVENTIONS.md + ARCHITECTURE.md(需要时加载)
|
|
531
|
-
- ❄️ 冷上下文:其他变更的 design.md、历史 plan.md(不要主动加载,除非明确需要)
|
|
532
|
-
${contractInjection}
|
|
533
|
-
### 本 Wave 任务
|
|
534
|
-
${taskList}
|
|
535
|
-
|
|
536
|
-
### 调度要求
|
|
537
|
-
1. **同一 Wave 内的任务必须并行启动子代理,禁止串行等待。** Wave 的定义就是"无依赖、可并行",不要自行分析依赖关系。如果有依赖应该在 plan.md 的不同 Wave 中。
|
|
538
|
-
2. **Reverse Sync**:子代理报告实现与 design.md 不一致时,先检查是代码错了还是文档有遗漏
|
|
539
|
-
3. **不要频繁编译!** 编译很慢,只在以下情况运行:
|
|
540
|
-
- 写了大量代码后需要验证语法正确性
|
|
541
|
-
- 最后一个 Wave 完成后做一次全量编译验证
|
|
542
|
-
- 用户明确要求编译时
|
|
543
|
-
4. 每个任务完成后:
|
|
544
|
-
- **先写 review.json 再勾选 checkbox**(见下方 Task Review Gate)
|
|
545
|
-
- 记录改动文件和测试结果
|
|
546
|
-
5. 遇到 BLOCKED → 记录原因,选择:重试/跳过/停止
|
|
547
|
-
|
|
548
|
-
### Task Review Gate(必须执行,不可跳过)
|
|
549
|
-
|
|
550
|
-
每个子代理完成后、勾选 checkbox **之前**,你必须创建 task review。
|
|
551
|
-
|
|
552
|
-
**操作步骤:**
|
|
553
|
-
1. 读取当前 task 的 git diff(从 task 开始到完成的变更)
|
|
554
|
-
2. 对照 plan.md 中该 task 的描述和 tasks/task-XX.md(如果存在)检查实现是否符合要求
|
|
555
|
-
3. 写入 review.json 文件
|
|
556
|
-
4. **只有 review.json 写入成功后,才允许勾选 plan.md 中的 checkbox**
|
|
557
|
-
|
|
558
|
-
**review.json 路径:**
|
|
559
|
-
|
|
560
|
-
task-XX 对应:.sillyspec/.runtime/execute-runs/{EXECUTE_RUN_ID}/tasks/task-XX/review.json
|
|
561
|
-
|
|
562
|
-
本 execute run 的固定 ID 是:{EXECUTE_RUN_ID}
|
|
563
|
-
**所有 task 的 review.json 必须使用这个 ID,不要自行创建新目录。**
|
|
564
|
-
|
|
565
|
-
**review.json 必填字段:**
|
|
566
|
-
|
|
567
|
-
{ "schemaVersion": 1, "task": "task-XX", "base": "<git-base-commit>", "head": "<git-head-commit>",
|
|
568
|
-
"changedFiles": ["src/foo.js"], "specVerdict": "pass|fail|cannot_verify",
|
|
569
|
-
"qualityVerdict": "pass|fail|cannot_verify", "reviewerNotes": "评审说明",
|
|
570
|
-
"requiredEvidence": [] }
|
|
571
|
-
|
|
572
|
-
**评审铁律:**
|
|
573
|
-
- 不信任 implementer 自报结果,对照 diff 和 task brief 验证
|
|
574
|
-
- 只看当前 task 的 diff,不做全仓库漫游审查
|
|
575
|
-
- \`cannot_verify\` 只在确实无法验证且有待补充证据时使用,且 requiredEvidence 必须非空
|
|
576
|
-
- \`sillyspec run execute --done\` 会校验所有 task 的 review.json,缺失或 fail 会阻断完成
|
|
577
|
-
|
|
578
|
-
### 完成后
|
|
579
|
-
1. 为每个后端 router task,扫描变更文件提取 API 端点 artifact:
|
|
580
|
-
- 在变更文件中搜索所有 router 注册路径(@router.get/post/put/delete)
|
|
581
|
-
- 将端点清单写入 .sillyspec/.runtime/contract-artifacts/<task-name>/endpoints.json
|
|
582
|
-
- 格式: { "task": "task-XX", "type": "backend_endpoints", "endpoints": [{ "method": "GET", "path": "/api/ppm/xxx" }] }
|
|
583
|
-
2. 运行 sillyspec run execute --done --input "用户原始反馈" --output "Wave ${waveIndex} 结果摘要"`
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
/**
|
|
587
|
-
* 动态构建 execute 步骤列表
|
|
588
|
-
* @param {string|null} planFilePath - plan 文件路径,null 则用默认 3 Wave
|
|
589
|
-
* @param {{ worktreePath?: string, noWorktree?: boolean }} options
|
|
590
|
-
* @returns {Array} 步骤列表
|
|
591
|
-
*/
|
|
592
|
-
export function buildExecuteSteps(planFilePath = null, options = {}) {
|
|
593
|
-
const noWorktree = !!options.noWorktree
|
|
594
|
-
let waves
|
|
595
|
-
let changeDir = null
|
|
596
|
-
|
|
597
|
-
if (planFilePath && existsSync(planFilePath)) {
|
|
598
|
-
const planContent = readFileSync(planFilePath, 'utf8')
|
|
599
|
-
// Plan → Execute 契约由 plan 阶段完成时的 postcheck 把关(run.js completeStep),
|
|
600
|
-
// 此处只负责解析 waves,避免 buildExecuteSteps 与进程退出耦合。
|
|
601
|
-
waves = parseWavesFromPlan(planContent)
|
|
602
|
-
changeDir = path.dirname(planFilePath)
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
// 没解析出 Wave(plan 不存在或不含可识别 task)→ 默认 3 Wave(向后兼容)
|
|
606
|
-
if (!waves || waves.length === 0) {
|
|
607
|
-
waves = []
|
|
608
|
-
for (let i = 1; i <= 3; i++) {
|
|
609
|
-
waves.push({ index: i, tasks: [{ name: `默认任务 ${i}`, file: 'TBD' }] })
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
// 尝试获取 worktree 路径(可能由前缀步骤创建)
|
|
614
|
-
const worktreePath = options.worktreePath || null
|
|
615
|
-
|
|
616
|
-
const waveSteps = waves.map((wave, i) => ({
|
|
617
|
-
name: `Wave ${i + 1} 执行`,
|
|
618
|
-
mode: 'implementation',
|
|
619
|
-
prompt: buildWavePrompt(wave, i + 1, changeDir, worktreePath),
|
|
620
|
-
outputHint: `Wave ${i + 1} 执行结果`,
|
|
621
|
-
optional: false
|
|
622
|
-
}))
|
|
623
|
-
|
|
624
|
-
return [...fixedPrefix, ...waveSteps, ...acceptanceSteps, ...fixedSuffix]
|
|
625
|
-
}
|
|
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
|
+
if (!currentWave) continue
|
|
374
|
+
|
|
375
|
+
const taskMatch = line.match(/^[-*]\s*\[[ x]\]\s*(.+)/)
|
|
376
|
+
if (taskMatch) {
|
|
377
|
+
const taskNoMatch = taskMatch[1].match(/\btask-(\d+)\b/i)
|
|
378
|
+
currentTask = {
|
|
379
|
+
index: taskNoMatch ? parseInt(taskNoMatch[1], 10) : null,
|
|
380
|
+
name: taskMatch[1].trim(),
|
|
381
|
+
file: '',
|
|
382
|
+
steps: '',
|
|
383
|
+
reference: ''
|
|
384
|
+
}
|
|
385
|
+
// 兼容旧格式:任务名后跟 (文件路径)
|
|
386
|
+
const fileMatch = taskMatch[1].match(/\(([^)]+)\)$/)
|
|
387
|
+
if (fileMatch) {
|
|
388
|
+
currentTask.file = fileMatch[1]
|
|
389
|
+
currentTask.name = taskMatch[1].replace(/\([^)]+\)$/, '').trim()
|
|
390
|
+
}
|
|
391
|
+
currentWave.tasks.push(currentTask)
|
|
392
|
+
continue
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// 解析子行信息(修改/参考/步骤)
|
|
396
|
+
if (currentTask) {
|
|
397
|
+
const modMatch = line.match(/^\s+-\s*修改:\s*(.+)/)
|
|
398
|
+
if (modMatch) { currentTask.file = modMatch[1].trim(); continue }
|
|
399
|
+
|
|
400
|
+
const refMatch = line.match(/^\s+-\s*参考:\s*(.+)/)
|
|
401
|
+
if (refMatch) { currentTask.reference = refMatch[1].trim(); continue }
|
|
402
|
+
|
|
403
|
+
const stepMatch = line.match(/^\s+-\s*步骤:/)
|
|
404
|
+
if (stepMatch) { currentTask.steps = line.replace(/^\s+-\s*步骤:\s*/, '').trim(); continue }
|
|
405
|
+
|
|
406
|
+
// 步骤续行(数字开头的子步骤)
|
|
407
|
+
if (currentTask.steps && line.match(/^\s+\d+\./)) {
|
|
408
|
+
currentTask.steps += '\n' + line.trim()
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return waves
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* 为 Wave 生成 prompt(强制子代理执行)
|
|
418
|
+
*/
|
|
419
|
+
function buildWavePrompt(wave, waveIndex, changeDir, worktreePath) {
|
|
420
|
+
// ── Contract Matrix:检查是否有 provider/consumer 契约需要注入 ──
|
|
421
|
+
let contractInjection = ''
|
|
422
|
+
if (changeDir) {
|
|
423
|
+
try {
|
|
424
|
+
const { buildContractMatrix, buildConsumerInjection } = require('../contract-matrix.js')
|
|
425
|
+
const planFile = path.join(changeDir, 'plan.md')
|
|
426
|
+
if (existsSync(planFile)) {
|
|
427
|
+
const planContent = readFileSync(planFile, 'utf8')
|
|
428
|
+
const contracts = buildContractMatrix(planContent, changeDir)
|
|
429
|
+
if (contracts.length > 0) {
|
|
430
|
+
// 收集本 wave 所有 task 的注入内容
|
|
431
|
+
const waveTasks = wave.tasks.map((t, ti) => {
|
|
432
|
+
const num = String(t.index || (ti + 1)).padStart(2, '0')
|
|
433
|
+
return `task-${num}`
|
|
434
|
+
})
|
|
435
|
+
const relevantContracts = contracts.filter(c => waveTasks.includes(c.consumer))
|
|
436
|
+
if (relevantContracts.length > 0) {
|
|
437
|
+
contractInjection = `
|
|
438
|
+
### API Contract Matrix
|
|
439
|
+
本 Wave 存在前端/后端跨 task 契约:
|
|
440
|
+
${relevantContracts.map(c => `- **${c.consumer}** 消费 **${c.provider}** 产出的 API`).join('\n')}
|
|
441
|
+
`
|
|
442
|
+
// 为每个 consumer task 生成详细注入
|
|
443
|
+
for (const taskName of waveTasks) {
|
|
444
|
+
const injection = buildConsumerInjection(changeDir, join(changeDir, '..', '..'), taskName, contracts)
|
|
445
|
+
if (injection) {
|
|
446
|
+
contractInjection += `
|
|
447
|
+
### 子代理 ${taskName} 的契约注入
|
|
448
|
+
为 ${taskName} 启动子代理时,在子代理 prompt 末尾追加以下内容:
|
|
449
|
+
|
|
450
|
+
<contract-injection>
|
|
451
|
+
${injection}
|
|
452
|
+
</contract-injection>
|
|
453
|
+
`
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
} catch {}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// 构建任务摘要(不再内联完整蓝图,减少上下文污染)
|
|
463
|
+
const taskSummary = wave.tasks.map((t, ti) => {
|
|
464
|
+
const taskNum = String(t.index || (ti + 1)).padStart(2, '0')
|
|
465
|
+
const taskRelPath = changeDir
|
|
466
|
+
? `.sillyspec/changes/${path.basename(changeDir)}/tasks/task-${taskNum}.md`
|
|
467
|
+
: `task-${taskNum}.md`
|
|
468
|
+
const fileInfo = t.file ? ` (${t.file})` : ''
|
|
469
|
+
return `task-${taskNum}: ${t.name}${fileInfo} → ${taskRelPath}`
|
|
470
|
+
}).join('\n')
|
|
471
|
+
|
|
472
|
+
const taskList = wave.tasks.map((t, ti) => {
|
|
473
|
+
const taskNum = String(t.index || (ti + 1)).padStart(2, '0')
|
|
474
|
+
let s = `- [ ] ${t.name}`
|
|
475
|
+
if (t.file) s += ` (${t.file})`
|
|
476
|
+
return s
|
|
477
|
+
}).join('\n')
|
|
478
|
+
|
|
479
|
+
const worktreeSection = (worktreePath)
|
|
480
|
+
? `
|
|
481
|
+
### 工作目录(必须严格遵守)
|
|
482
|
+
|
|
483
|
+
调用 Task 工具启动子代理时,**workdir 参数是强制必传的**。
|
|
484
|
+
不传 workdir 会导致子代理把文件写到主工作区而非 worktree,破坏隔离。
|
|
485
|
+
|
|
486
|
+
\`\`\`json
|
|
487
|
+
{
|
|
488
|
+
"subagent_type": "general",
|
|
489
|
+
"workdir": "${worktreePath}",
|
|
490
|
+
"prompt": "在此编写任务描述..."
|
|
491
|
+
}
|
|
492
|
+
\`\`\`
|
|
493
|
+
|
|
494
|
+
### 注意
|
|
495
|
+
蓝图文件(tasks.md / design.md / proposal.md / requirements.md)在主工作区 .sillyspec/changes/<change>/ 下,它们可能不在 worktree 中。读取蓝图时使用主工作区路径,不要拼接到 worktree 路径下。
|
|
496
|
+
`
|
|
497
|
+
: ''
|
|
498
|
+
|
|
499
|
+
return `## Wave ${waveIndex}: 执行以下任务
|
|
500
|
+
|
|
501
|
+
## 执行方式(必须严格遵守)
|
|
502
|
+
|
|
503
|
+
**每个任务必须由独立子代理执行,你不要自己写代码。**
|
|
504
|
+
|
|
505
|
+
你的角色是调度者 + 审查者:
|
|
506
|
+
1. 为每个任务启动一个子代理(Agent tool),同 Wave 内可并行
|
|
507
|
+
2. 子代理完成后审查结果
|
|
508
|
+
3. 勾选 plan.md 中的 checkbox
|
|
509
|
+
4. 记录改动文件和测试结果
|
|
510
|
+
|
|
511
|
+
${worktreeSection}
|
|
512
|
+
### 任务摘要(按需读取完整蓝图)
|
|
513
|
+
为每个任务启动子代理时,**只需告知任务目标和蓝图文件路径,让子代理按需读取**:
|
|
514
|
+
|
|
515
|
+
${taskSummary}
|
|
516
|
+
|
|
517
|
+
子代理 prompt 要点:
|
|
518
|
+
1. 任务目标(简短描述)
|
|
519
|
+
2. 蓝图文件路径(让子代理自行读取详情)
|
|
520
|
+
3. 编码铁律:先读后写、TDD、不编造方法、只做蓝图里写的事、遵守边界处理规则、不超出 allowed_paths
|
|
521
|
+
4. 如存在模块文档(.sillyspec/docs/*/modules/),按需读取涉及模块的 <module>.md 参考接口约定和数据流
|
|
522
|
+
|
|
523
|
+
### Wave 开始前
|
|
524
|
+
1. 读取 design.md 的「编码铁律」章节(如果存在),严格遵守
|
|
525
|
+
2. 读取 plan.md 了解全局任务划分和依赖关系
|
|
526
|
+
3. 确认本 Wave 的输入/输出契约(前置 Wave 产出了什么,本 Wave 需要消费什么)
|
|
527
|
+
4. 检查前置 Wave 的产出是否完整(文件是否存在、测试是否通过)
|
|
528
|
+
5. **上下文分层加载**:
|
|
529
|
+
- 🔥 热上下文:design.md 编码铁律 + 当前 Wave 任务(必须加载)
|
|
530
|
+
- 🌡️ 温上下文:CONVENTIONS.md + ARCHITECTURE.md(需要时加载)
|
|
531
|
+
- ❄️ 冷上下文:其他变更的 design.md、历史 plan.md(不要主动加载,除非明确需要)
|
|
532
|
+
${contractInjection}
|
|
533
|
+
### 本 Wave 任务
|
|
534
|
+
${taskList}
|
|
535
|
+
|
|
536
|
+
### 调度要求
|
|
537
|
+
1. **同一 Wave 内的任务必须并行启动子代理,禁止串行等待。** Wave 的定义就是"无依赖、可并行",不要自行分析依赖关系。如果有依赖应该在 plan.md 的不同 Wave 中。
|
|
538
|
+
2. **Reverse Sync**:子代理报告实现与 design.md 不一致时,先检查是代码错了还是文档有遗漏
|
|
539
|
+
3. **不要频繁编译!** 编译很慢,只在以下情况运行:
|
|
540
|
+
- 写了大量代码后需要验证语法正确性
|
|
541
|
+
- 最后一个 Wave 完成后做一次全量编译验证
|
|
542
|
+
- 用户明确要求编译时
|
|
543
|
+
4. 每个任务完成后:
|
|
544
|
+
- **先写 review.json 再勾选 checkbox**(见下方 Task Review Gate)
|
|
545
|
+
- 记录改动文件和测试结果
|
|
546
|
+
5. 遇到 BLOCKED → 记录原因,选择:重试/跳过/停止
|
|
547
|
+
|
|
548
|
+
### Task Review Gate(必须执行,不可跳过)
|
|
549
|
+
|
|
550
|
+
每个子代理完成后、勾选 checkbox **之前**,你必须创建 task review。
|
|
551
|
+
|
|
552
|
+
**操作步骤:**
|
|
553
|
+
1. 读取当前 task 的 git diff(从 task 开始到完成的变更)
|
|
554
|
+
2. 对照 plan.md 中该 task 的描述和 tasks/task-XX.md(如果存在)检查实现是否符合要求
|
|
555
|
+
3. 写入 review.json 文件
|
|
556
|
+
4. **只有 review.json 写入成功后,才允许勾选 plan.md 中的 checkbox**
|
|
557
|
+
|
|
558
|
+
**review.json 路径:**
|
|
559
|
+
|
|
560
|
+
task-XX 对应:.sillyspec/.runtime/execute-runs/{EXECUTE_RUN_ID}/tasks/task-XX/review.json
|
|
561
|
+
|
|
562
|
+
本 execute run 的固定 ID 是:{EXECUTE_RUN_ID}
|
|
563
|
+
**所有 task 的 review.json 必须使用这个 ID,不要自行创建新目录。**
|
|
564
|
+
|
|
565
|
+
**review.json 必填字段:**
|
|
566
|
+
|
|
567
|
+
{ "schemaVersion": 1, "task": "task-XX", "base": "<git-base-commit>", "head": "<git-head-commit>",
|
|
568
|
+
"changedFiles": ["src/foo.js"], "specVerdict": "pass|fail|cannot_verify",
|
|
569
|
+
"qualityVerdict": "pass|fail|cannot_verify", "reviewerNotes": "评审说明",
|
|
570
|
+
"requiredEvidence": [] }
|
|
571
|
+
|
|
572
|
+
**评审铁律:**
|
|
573
|
+
- 不信任 implementer 自报结果,对照 diff 和 task brief 验证
|
|
574
|
+
- 只看当前 task 的 diff,不做全仓库漫游审查
|
|
575
|
+
- \`cannot_verify\` 只在确实无法验证且有待补充证据时使用,且 requiredEvidence 必须非空
|
|
576
|
+
- \`sillyspec run execute --done\` 会校验所有 task 的 review.json,缺失或 fail 会阻断完成
|
|
577
|
+
|
|
578
|
+
### 完成后
|
|
579
|
+
1. 为每个后端 router task,扫描变更文件提取 API 端点 artifact:
|
|
580
|
+
- 在变更文件中搜索所有 router 注册路径(@router.get/post/put/delete)
|
|
581
|
+
- 将端点清单写入 .sillyspec/.runtime/contract-artifacts/<task-name>/endpoints.json
|
|
582
|
+
- 格式: { "task": "task-XX", "type": "backend_endpoints", "endpoints": [{ "method": "GET", "path": "/api/ppm/xxx" }] }
|
|
583
|
+
2. 运行 sillyspec run execute --done --input "用户原始反馈" --output "Wave ${waveIndex} 结果摘要"`
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* 动态构建 execute 步骤列表
|
|
588
|
+
* @param {string|null} planFilePath - plan 文件路径,null 则用默认 3 Wave
|
|
589
|
+
* @param {{ worktreePath?: string, noWorktree?: boolean }} options
|
|
590
|
+
* @returns {Array} 步骤列表
|
|
591
|
+
*/
|
|
592
|
+
export function buildExecuteSteps(planFilePath = null, options = {}) {
|
|
593
|
+
const noWorktree = !!options.noWorktree
|
|
594
|
+
let waves
|
|
595
|
+
let changeDir = null
|
|
596
|
+
|
|
597
|
+
if (planFilePath && existsSync(planFilePath)) {
|
|
598
|
+
const planContent = readFileSync(planFilePath, 'utf8')
|
|
599
|
+
// Plan → Execute 契约由 plan 阶段完成时的 postcheck 把关(run.js completeStep),
|
|
600
|
+
// 此处只负责解析 waves,避免 buildExecuteSteps 与进程退出耦合。
|
|
601
|
+
waves = parseWavesFromPlan(planContent)
|
|
602
|
+
changeDir = path.dirname(planFilePath)
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// 没解析出 Wave(plan 不存在或不含可识别 task)→ 默认 3 Wave(向后兼容)
|
|
606
|
+
if (!waves || waves.length === 0) {
|
|
607
|
+
waves = []
|
|
608
|
+
for (let i = 1; i <= 3; i++) {
|
|
609
|
+
waves.push({ index: i, tasks: [{ name: `默认任务 ${i}`, file: 'TBD' }] })
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// 尝试获取 worktree 路径(可能由前缀步骤创建)
|
|
614
|
+
const worktreePath = options.worktreePath || null
|
|
615
|
+
|
|
616
|
+
const waveSteps = waves.map((wave, i) => ({
|
|
617
|
+
name: `Wave ${i + 1} 执行`,
|
|
618
|
+
mode: 'implementation',
|
|
619
|
+
prompt: buildWavePrompt(wave, i + 1, changeDir, worktreePath),
|
|
620
|
+
outputHint: `Wave ${i + 1} 执行结果`,
|
|
621
|
+
optional: false
|
|
622
|
+
}))
|
|
623
|
+
|
|
624
|
+
return [...fixedPrefix, ...waveSteps, ...acceptanceSteps, ...fixedSuffix]
|
|
625
|
+
}
|