sillyspec 3.26.11 → 3.26.14
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 +1 -1
- package/.claude/skills/sillyspec-execute/SKILL.md +1 -1
- package/.claude/skills/sillyspec-knowledge/SKILL.md +1 -1
- package/.claude/skills/sillyspec-plan/SKILL.md +5 -5
- package/.claude/skills/sillyspec-quick/SKILL.md +5 -1
- package/README.md +7 -5
- package/SKILL.md +5 -4
- package/package.json +1 -1
- package/packages/dashboard/server/executor.js +10 -1
- package/packages/dashboard/server/index.js +108 -46
- package/src/config-schema.js +3 -0
- package/src/contract-matrix.js +22 -13
- package/src/db.js +24 -4
- package/src/dispatch/probe.js +99 -63
- package/src/docs-check.js +58 -13
- package/src/docs-debt.js +6 -2
- package/src/docs-gate.js +1 -0
- package/src/doctor-diagnostics.js +208 -21
- package/src/fs-atomic.js +3 -1
- package/src/git-helper.js +7 -4
- package/src/hooks/worktree-guard.js +8 -2
- package/src/index.js +200 -27
- package/src/init.js +3 -2
- package/src/local-detect.js +9 -1
- package/src/modules.js +7 -5
- package/src/progress/stage-machine.js +589 -578
- package/src/progress/step-store.js +1 -1
- package/src/progress.js +187 -30
- package/src/quicklog.js +47 -16
- package/src/run/command.js +88 -46
- package/src/run/complete-handlers.js +27 -15
- package/src/run/complete.js +1186 -1121
- package/src/run/concurrent-detect.js +3 -1
- package/src/run/gates.js +172 -25
- package/src/run/prompt.js +17 -9
- package/src/run/scan-profile.js +35 -23
- package/src/run/shared.js +164 -63
- package/src/run/stage.js +41 -1
- package/src/setup.js +8 -10
- package/src/sillyhub-mcp/client.js +74 -6
- package/src/spec-dir-typo.js +3 -24
- package/src/spec-sync.js +43 -8
- package/src/stage-contract.js +12 -38
- package/src/stage-review.js +40 -1
- package/src/stages/archive.js +4 -3
- package/src/stages/brainstorm-auto.js +1 -1
- package/src/stages/brainstorm.js +27 -13
- package/src/stages/doctor.js +29 -16
- package/src/stages/execute.js +170 -154
- package/src/stages/knowledge.js +18 -3
- package/src/stages/plan-postcheck.js +72 -37
- package/src/stages/plan.js +70 -48
- package/src/stages/quick.js +3 -1
- package/src/stages/verify.js +1 -1
- package/src/sync.js +339 -225
- package/src/task-review.js +281 -64
- package/src/taskcard.js +178 -0
- package/src/verify-postcheck.js +48 -35
- package/src/worktree-apply.js +58 -34
- package/src/worktree-deps.js +120 -20
- package/src/worktree.js +55 -9
- package/.npmrc-publish +0 -2
|
@@ -344,8 +344,14 @@ function parseTaskWavesFromPlan(planPath) {
|
|
|
344
344
|
const wm = line.match(/^#+\s*Wave\s+(\d+)/i)
|
|
345
345
|
if (wm) { currentWave = parseInt(wm[1], 10); continue }
|
|
346
346
|
if (/^#+\s/.test(line)) { currentWave = null; continue } // 非 Wave 标题退出当前 Wave 段
|
|
347
|
-
|
|
348
|
-
|
|
347
|
+
// 2026-08-20-task-truth-unify:Wave 段下为纯 ID 引用行(- task-XX);旧 checkbox 行同收
|
|
348
|
+
// (旧变更读侧兼容——新格式由 validatePlanForExecute 拦旧形态,此处不重复把关)
|
|
349
|
+
const tm = line.match(/^[-*]\s+(?:\[[ x]\]\s*)?(task-\d+)\s*[::\s]*$/i)
|
|
350
|
+
|| line.match(/^[-*]\s*\[[ x]\]\s*(task-\d+)\b/i)
|
|
351
|
+
// id 归一补零(与卡片/注册表 task-01 口径一致,坑 wave-ref-unpadded):plan.md 写
|
|
352
|
+
// `- task-1` 时裸用捕获原文做 key,永远匹配不到卡片 id task-01 → 被判「未列入任何
|
|
353
|
+
// Wave」,同 Wave 共享文件检测静默跳过。execute.js parseWavesFromPlan 已补零,对齐。
|
|
354
|
+
if (tm && currentWave !== null) map.set(String(tm[1]).replace(/^task-(\d+)$/i, (_, n) => `task-${n.padStart(2, '0')}`), currentWave)
|
|
349
355
|
}
|
|
350
356
|
return map
|
|
351
357
|
}
|
|
@@ -452,38 +458,46 @@ export function validateBlueprintConsistency(changeDir) {
|
|
|
452
458
|
}
|
|
453
459
|
}
|
|
454
460
|
|
|
455
|
-
//
|
|
456
|
-
//
|
|
457
|
-
// 不受 execute 审计 → 零失败信号漏做。口径与
|
|
458
|
-
// (`- [ ] task-NN` checkbox
|
|
461
|
+
// 任务声明 ↔ tasks/ 卡片双向对账(债单 D-2b;2026-08-20-task-truth-unify:声明源迁 tasks.md 注册表):
|
|
462
|
+
// 注册表列 17 任务只出 12 卡(尾部文档同步类任务漏卡)时,无卡任务不进 Wave、
|
|
463
|
+
// 不受 execute 审计 → 零失败信号漏做。口径与 execute.js parseTaskRegistry 一致
|
|
464
|
+
// (`- [ ] task-NN` checkbox 行;file 命名 task-NN.md + 卡内 id task-NN 均取,缺一即漏卡)。
|
|
459
465
|
const planMdPath = pJoin(changeDir, 'plan.md')
|
|
460
|
-
|
|
461
|
-
|
|
466
|
+
const declaredIds = new Set()
|
|
467
|
+
const tasksMdPath = pJoin(changeDir, 'tasks.md')
|
|
468
|
+
if (existsSync(tasksMdPath)) {
|
|
469
|
+
for (const line of readFileSync(tasksMdPath, 'utf8').split('\n')) {
|
|
470
|
+
const m = line.match(/^[-*]\s*\[[ x]\]\s*(task-\d+)\b/i)
|
|
471
|
+
if (m) declaredIds.add(m[1].toLowerCase())
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (declaredIds.size === 0 && existsSync(planMdPath)) {
|
|
475
|
+
// 兼容读侧:tasks.md 缺失/无声明时回退解析 plan.md 旧 checkbox 声明(旧归档变更)
|
|
462
476
|
for (const line of readFileSync(planMdPath, 'utf8').split('\n')) {
|
|
463
477
|
const m = line.match(/^[-*]\s*\[[ x]\]\s*(task-\d+)\b/i)
|
|
464
478
|
if (m) declaredIds.add(m[1].toLowerCase())
|
|
465
479
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
)
|
|
482
|
-
|
|
480
|
+
}
|
|
481
|
+
if (declaredIds.size > 0) {
|
|
482
|
+
const tprRule = getRule('plan.task-plan-reconciliation')
|
|
483
|
+
const cardIds = new Set([...taskInfo.keys()].map(id => id.toLowerCase()))
|
|
484
|
+
const missing = [...declaredIds].filter(id => !cardIds.has(id))
|
|
485
|
+
if (missing.length > 0) {
|
|
486
|
+
errors.push(
|
|
487
|
+
tprRule.data.messageMissingCards
|
|
488
|
+
.replaceAll('${declared}', declaredIds.size)
|
|
489
|
+
.replaceAll('${missing}', missing.join(', '))
|
|
490
|
+
)
|
|
491
|
+
}
|
|
492
|
+
const orphans = [...cardIds].filter(id => !declaredIds.has(id))
|
|
493
|
+
if (orphans.length > 0) {
|
|
494
|
+
errors.push(
|
|
495
|
+
tprRule.data.messageOrphanCards.replaceAll('${orphans}', orphans.join(', '))
|
|
496
|
+
)
|
|
483
497
|
}
|
|
484
|
-
// declaredIds.size === 0:plan.md 无 task checkbox 声明(极端格式)→ 跳过对账,
|
|
485
|
-
// 由 validatePlanArtifacts/validatePlanForExecute 把关,不在此误伤。
|
|
486
498
|
}
|
|
499
|
+
// declaredIds.size === 0:注册表无 task 声明(极端格式)→ 跳过对账,
|
|
500
|
+
// 由 validatePlanArtifacts/validatePlanForExecute 把关,不在此误伤。
|
|
487
501
|
|
|
488
502
|
// 拓扑排序 + 循环依赖
|
|
489
503
|
const depMap = new Map()
|
|
@@ -603,16 +617,19 @@ export function parseLocalYamlModules(yamlText) {
|
|
|
603
617
|
const modules = {}
|
|
604
618
|
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
605
619
|
const line = lines[i]
|
|
606
|
-
const entry = line.match(/^
|
|
620
|
+
const entry = line.match(/^([ \t]+)([A-Za-z0-9_.\-]+):\s*(.*)$/)
|
|
607
621
|
if (!entry) {
|
|
608
622
|
// 遇到新的顶层 key(行首非空格且非注释)→ modules 块结束
|
|
609
623
|
if (line.length > 0 && !line.startsWith(' ') && !line.startsWith('#') && line.trim() !== '') break
|
|
610
624
|
continue
|
|
611
625
|
}
|
|
612
|
-
|
|
626
|
+
// 条目缩进放宽为「任意非零空白」(坑 modules-indent-hardcoded):此前硬编码恰 2 空格,
|
|
627
|
+
// 4 空格/Tab 缩进的条目不匹配且不触发块结束 → 被静默跳过 → modules 丢失 →
|
|
628
|
+
// validateScriptCommands 退化为只查根 package.json,monorepo 子包命令被误报不存在。
|
|
629
|
+
const rest = entry[3] || ''
|
|
613
630
|
// path 值:带引号或不带引号(flow mapping),取第一个匹配
|
|
614
631
|
const pathMatch = rest.match(/path:\s*"([^"]+)"/) || rest.match(/path:\s*([^,\s}]+)/)
|
|
615
|
-
if (pathMatch) modules[entry[
|
|
632
|
+
if (pathMatch) modules[entry[2]] = { path: pathMatch[1] }
|
|
616
633
|
}
|
|
617
634
|
return Object.keys(modules).length > 0 ? modules : null
|
|
618
635
|
}
|
|
@@ -658,9 +675,14 @@ export function validateTaskCommands(changeDir, projectRoot, modules = null) {
|
|
|
658
675
|
continue // 非法 YAML 由 feasibility 处理,不重复
|
|
659
676
|
}
|
|
660
677
|
|
|
661
|
-
// 合并 verify + implementation
|
|
662
|
-
|
|
663
|
-
|
|
678
|
+
// 合并 verify + implementation 文本(命令可能出现在任一字段)。
|
|
679
|
+
// 字段两形态都收(坑 taskcard-block-list-noop):规范格式(taskcard.js 骨架 / plan 模板)的
|
|
680
|
+
// verify/implementation 是 YAML 块列表 → js-yaml 解析为数组;此前只收 string,规范卡片
|
|
681
|
+
// 恒落空 → 「命令存在性校验」(hard-error)对合规 TaskCard 整体 no-op,只有违规写成
|
|
682
|
+
// 纯标量的卡片才被校验。数组按行 join 回文本,与标量同管道。
|
|
683
|
+
const toCmdText = (v) => Array.isArray(v) ? v.map(String).join('\n') : (typeof v === 'string' ? v : '')
|
|
684
|
+
const verifyText = toCmdText(fm.verify)
|
|
685
|
+
const implText = toCmdText(fm.implementation)
|
|
664
686
|
const text = `${verifyText}\n${implText}`
|
|
665
687
|
if (!text.trim()) continue
|
|
666
688
|
|
|
@@ -915,7 +937,15 @@ export function validatePlanFeasibility(changeDir, projectRoot = null) {
|
|
|
915
937
|
const content = readFileSync(pJoin(tasksDir, file), 'utf8')
|
|
916
938
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/)
|
|
917
939
|
if (!fmMatch) {
|
|
918
|
-
|
|
940
|
+
// 漏闭合 --- 高频坑细化:只报「缺少 YAML frontmatter」不具体,用户须逐轮试错。
|
|
941
|
+
// 有开头 --- 无闭合 / 完全无 --- 分开报并附修复动作(manifest messageFrontmatter 保持
|
|
942
|
+
// 基础句,细化文案内联——同 title_zh 内联先例)。骨架命令从源头保闭合 + LF。
|
|
943
|
+
const firstLine = (content.split('\n')[0] || '').trim()
|
|
944
|
+
errors.push(
|
|
945
|
+
firstLine === '---'
|
|
946
|
+
? `${file}: frontmatter 未闭合——有开头 --- 但缺少结尾 ---。修复:补一行 --- 闭合(frontmatter 以 --- 行开始并以 --- 行结束),或 sillyspec taskcard <change> --task <task-id> 重新生成安全骨架后填充`
|
|
947
|
+
: `${fsRule.data.messageFrontmatter.replaceAll('${id}', file)}——文件必须以 --- 行开始。修复:sillyspec taskcard <change> --task <task-id> 生成安全骨架后填充`
|
|
948
|
+
)
|
|
919
949
|
continue
|
|
920
950
|
}
|
|
921
951
|
const fm = fmMatch[1]
|
|
@@ -1028,8 +1058,10 @@ export function validatePlanFeasibility(changeDir, projectRoot = null) {
|
|
|
1028
1058
|
if (allTaskIds.length > 0) {
|
|
1029
1059
|
const nums = allTaskIds.map(id => {
|
|
1030
1060
|
const m = id.match(/task-(\d+)/i)
|
|
1031
|
-
return m ? parseInt(m[1]) : null
|
|
1061
|
+
return m ? parseInt(m[1], 10) : null
|
|
1032
1062
|
}).filter(n => n !== null).sort((a, b) => a - b)
|
|
1063
|
+
// nums[0]!==1 时跳过 = 兼容旧变更编号不从 1 起(契约同 validatePlanForExecute 检查 3,
|
|
1064
|
+
// test/plan-execute-contract.test.mjs Case 10 钉死,勿改)
|
|
1033
1065
|
if (nums.length > 0 && nums[0] === 1) {
|
|
1034
1066
|
for (let i = 0; i < nums.length; i++) {
|
|
1035
1067
|
if (nums[i] !== i + 1) {
|
|
@@ -1261,9 +1293,12 @@ export async function executePlanPostcheck(context) {
|
|
|
1261
1293
|
currentWaveTasks = null
|
|
1262
1294
|
continue
|
|
1263
1295
|
}
|
|
1264
|
-
|
|
1296
|
+
// 2026-08-20-task-truth-unify:Wave 段下纯 ID 引用行(- task-NN);旧 checkbox 行同收。
|
|
1297
|
+
// 编号补零对齐卡片口径(坑 wave-ref-unpadded,同 parseTaskWavesFromPlan)
|
|
1298
|
+
const tm = line.match(/^[-*]\s+(?:\[[ x]\]\s*)?task-(\d+)\s*[::\s]*$/i)
|
|
1299
|
+
|| line.match(/^[-*]\s*\[[ x]\]\s*task-(\d+)/i)
|
|
1265
1300
|
if (tm && currentWaveTasks) {
|
|
1266
|
-
currentWaveTasks.push(`task-${tm[1]}`)
|
|
1301
|
+
currentWaveTasks.push(`task-${String(tm[1]).padStart(2, '0')}`)
|
|
1267
1302
|
}
|
|
1268
1303
|
}
|
|
1269
1304
|
if (currentWaveTasks) existingWaves.push(currentWaveTasks)
|
package/src/stages/plan.js
CHANGED
|
@@ -134,7 +134,8 @@ const stepGeneratePlan = {
|
|
|
134
134
|
1. 读取 plan.md frontmatter 的 \`plan_level:\` 字段(上一步已落盘为持久锚点;文件不存在或无该字段时回退读上一步输出的分类结果)
|
|
135
135
|
2. 读取 tasks.md 和 design.md 了解需求范围
|
|
136
136
|
3. 按 plan_level 选择对应模板输出
|
|
137
|
-
4.
|
|
137
|
+
4. **写回任务清单**:把展开后的任务清单写回 tasks.md(checkbox 行 \`- [ ] task-XX: 一句话任务名\`,可附 \`[model:xxx]\`/\`(depends_on: task-01,02)\` 行内标注)。**写回规则(D-002@v1)**:保留 frontmatter/中文标题/所有非 task-XX 行(quick 挂载的 ql-xxx 勾选行、注记等逐行保留),仅重写 task-XX checkbox 行集合——防摧毁 quick 挂载条目
|
|
138
|
+
5. 保存 plan.md(审查在下一步"审查计划"独立进行,不在本步自审——避免生成与自审同一次输出);frontmatter 保留 plan_level 字段不删。**plan.md Wave 段下任务一律纯 ID 引用行**(\`- task-XX\`,不重抄任务名——任务名唯一真相在 tasks.md,重抄即双写漂移)
|
|
138
139
|
|
|
139
140
|
---
|
|
140
141
|
|
|
@@ -151,17 +152,15 @@ plan_level: none
|
|
|
151
152
|
<一句话说明判定理由>
|
|
152
153
|
|
|
153
154
|
## 建议直接 execute
|
|
154
|
-
直接进入 execute
|
|
155
|
-
|
|
156
|
-
## Tasks
|
|
157
|
-
- [ ] task-01: 按用户需求完成小范围明确修改
|
|
155
|
+
直接进入 execute 阶段完成最小任务(任务清单见 tasks.md)。
|
|
158
156
|
|
|
159
157
|
## 验收
|
|
160
158
|
- 修改范围符合用户需求
|
|
161
159
|
- 不引入额外无关变更
|
|
162
160
|
- 必要测试或检查通过
|
|
163
161
|
\`\`\`
|
|
164
|
-
|
|
162
|
+
同时把最小任务写入 tasks.md:\`- [ ] task-01: 按用户需求完成小范围明确修改\`(同样遵守写回规则)。
|
|
163
|
+
**注意:** 任务 checkbox 只在 tasks.md(\`- [ ] task-XX:\` 格式,execute 阶段从 tasks.md 解析任务、按 plan.md Wave 引用分组)。
|
|
165
164
|
|
|
166
165
|
---
|
|
167
166
|
|
|
@@ -181,11 +180,6 @@ plan_level: light
|
|
|
181
180
|
## 范围
|
|
182
181
|
- 涉及的文件/模块清单
|
|
183
182
|
|
|
184
|
-
## Tasks
|
|
185
|
-
- [ ] task-01: ...(覆盖:FR-01, D-001@v1)
|
|
186
|
-
- [ ] task-02: ...
|
|
187
|
-
- [ ] task-03: ...
|
|
188
|
-
|
|
189
183
|
## 验收
|
|
190
184
|
- 具体可验证的验收条目
|
|
191
185
|
|
|
@@ -194,6 +188,7 @@ plan_level: light
|
|
|
194
188
|
|---|---|---|
|
|
195
189
|
| D-001@v1 | task-01 | AC-01 |
|
|
196
190
|
\`\`\`
|
|
191
|
+
任务清单一律写 tasks.md(本步操作 4 写回;light 级 plan.md **无任务区**——execute 自动把注册表合成为单个隐式 Wave 串行执行)。
|
|
197
192
|
|
|
198
193
|
light 计划的约束:
|
|
199
194
|
- **禁止**生成 Mermaid 图
|
|
@@ -201,10 +196,10 @@ light 计划的约束:
|
|
|
201
196
|
- **禁止**泛泛风险分析(如"需要充分测试")
|
|
202
197
|
- **禁止**放实现细节(函数签名、代码示例)
|
|
203
198
|
- 来源/目标直接引用已有文档,不重新生成
|
|
204
|
-
- 如果存在 decisions.md,所有当前版本 D-xxx@vN
|
|
199
|
+
- 如果存在 decisions.md,所有当前版本 D-xxx@vN 必须在覆盖矩阵中出现(CLI 只校验 D-xxx@vN ID 字面出现在 plan.md,warning 不阻断;矩阵结构供人类追溯,CLI 不校验 D→FR→task 映射完整性)
|
|
205
200
|
- 如果存在 P0/P1 unresolved blocker,不生成 plan.md
|
|
206
|
-
-
|
|
207
|
-
-
|
|
201
|
+
- 任务清单(tasks.md)控制在 10 条以内
|
|
202
|
+
- 任务名使用 tasks.md checkbox 格式(\`- [ ] task-XX:\`),plan.md 不放任务行
|
|
208
203
|
|
|
209
204
|
---
|
|
210
205
|
|
|
@@ -226,11 +221,11 @@ plan_level: full
|
|
|
226
221
|
> 技术不确定性高时才需要 Spike。无不确定性则跳过此节。
|
|
227
222
|
|
|
228
223
|
## Wave 1(并行,无依赖)
|
|
229
|
-
-
|
|
230
|
-
-
|
|
224
|
+
- task-01
|
|
225
|
+
- task-02
|
|
231
226
|
|
|
232
227
|
## Wave 2(依赖 Wave 1)
|
|
233
|
-
-
|
|
228
|
+
- task-03
|
|
234
229
|
|
|
235
230
|
## 任务总表
|
|
236
231
|
| 编号 | 任务 | Wave | 优先级 | 依赖 | 覆盖 FR/D | 说明 |
|
|
@@ -243,9 +238,11 @@ plan_level: full
|
|
|
243
238
|
task-01 → task-03(最长路径,决定最短交付周期)
|
|
244
239
|
|
|
245
240
|
## 全局验收标准
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
241
|
+
1. 所有单元测试通过
|
|
242
|
+
2. 集成敏感 task(路由/layout/跨进程装配)建议加集成冒烟验收——组件单测全绿 ≠ 集成正确
|
|
243
|
+
3. (brownfield)未配置新功能时行为不变
|
|
244
|
+
|
|
245
|
+
> 验收结论不落在 plan.md(此段是验收清单,plan.md 不做执行态文件)——逐项核验结果由 verify 阶段写入 verify-result.md(PASS/FAIL 结论与证据);task 级验收对照 TaskCard frontmatter 的 acceptance 字段。
|
|
249
246
|
|
|
250
247
|
## 覆盖矩阵(如存在 decisions.md)
|
|
251
248
|
| ID | 覆盖任务 | 验收证据 |
|
|
@@ -257,7 +254,7 @@ full 计划的约束:
|
|
|
257
254
|
- **禁止**估时(任务总表不含估时列)
|
|
258
255
|
- **禁止**泛泛风险分析("需要充分测试"类废话转为具体验收条目)
|
|
259
256
|
- Mermaid 依赖关系图**仅当依赖关系非平凡时生成**(线性依赖或全并行时不生成)
|
|
260
|
-
- **Wave
|
|
257
|
+
- **Wave 段 ID 引用行必须保留**(\`- task-XX\` 纯 ID、无 checkbox 无任务名——机器解析依赖;任务名唯一真相在 tasks.md,plan.md 重抄即双写漂移)
|
|
261
258
|
- plan.md 包含 Wave 分组 + 任务总表 + 关键路径 + 全局验收标准,**不放实现细节**
|
|
262
259
|
- 如果存在 decisions.md,plan.md 建议包含当前版本 D-xxx@vN/FR-xxx 覆盖矩阵(CLI 只校验 D-xxx@vN/FR-xxx ID 字面出现在 plan.md,warning 不阻断;矩阵的 D→FR→task 映射完整性供人类追溯,CLI 不校验——勿以为画了矩阵就被结构校验)
|
|
263
260
|
- 如果存在 P0/P1 unresolved blocker,不生成 plan.md,输出阻塞清单
|
|
@@ -284,11 +281,12 @@ full 计划的约束:
|
|
|
284
281
|
---
|
|
285
282
|
|
|
286
283
|
### 通用操作(所有级别)
|
|
287
|
-
1. 读取 tasks.md
|
|
284
|
+
1. 读取 tasks.md 获取任务列表(注册表唯一真相)
|
|
288
285
|
2. 读取 design.md 获取文件变更清单
|
|
289
286
|
3. 读取 plan.md frontmatter 的 \`plan_level:\` 字段(持久锚点;缺失时回退上一步输出)
|
|
290
287
|
4. 按对应级别模板生成内容
|
|
291
288
|
5. 保存到变更目录下的 plan.md(路径格式:\`{SPEC_ROOT}/changes/<change-name>/plan.md\`,其中 <change-name> 是变更目录名,直接使用,不加子目录。正确路径示例:\`{SPEC_ROOT}/changes/2026-05-28-agent-log-streaming/plan.md\`)
|
|
289
|
+
6. 写回 tasks.md(本步操作 4 的写回规则:仅重写 task-XX checkbox 行集合,保留非 task-XX 行)
|
|
292
290
|
**plan_level 为 none 时生成最小 plan.md(占位),不生成完整蓝图。**
|
|
293
291
|
|
|
294
292
|
---
|
|
@@ -392,6 +390,7 @@ execute/verify 阶段会按实际代码变更更新此文档;archive 阶段会
|
|
|
392
390
|
* 在保留 plan 任务拆分完整性的同时减少子代理调用数量。
|
|
393
391
|
*/
|
|
394
392
|
export function buildCoordinatorStep(changeDir, taskNames) {
|
|
393
|
+
const changeName = path.basename(changeDir)
|
|
395
394
|
const taskList = taskNames.map(t => {
|
|
396
395
|
const num = t.num
|
|
397
396
|
return `- task-${num}: ${t.name}`
|
|
@@ -484,8 +483,10 @@ ${taskList}
|
|
|
484
483
|
## 操作
|
|
485
484
|
1. 读取 ${changeDir}/design.md 和 ${changeDir}/plan.md 了解整体上下文
|
|
486
485
|
2. 读取本 batch 涉及的相关源文件
|
|
487
|
-
3. 为 batch 中的**每一个 task
|
|
488
|
-
|
|
486
|
+
3. 为 batch 中的**每一个 task** 先运行 CLI 生成 Windows 安全骨架(在项目根目录执行;编号/标题自动取自 plan.md,author/created_at 自动填写):
|
|
487
|
+
sillyspec taskcard ${changeName} --task <本 batch 任务号,逗号分隔,如 task-01,task-02>
|
|
488
|
+
4. 用 Edit tool 逐卡填充骨架占位符(allowed_paths/goal/implementation/acceptance/verify/constraints 等)。**禁止用 Write 整文件重写**——手写整卡是 CRLF 行尾/漏闭合 --- /漏硬校验字段三类 postcheck 拒绝的根源,骨架 + Edit 从源头消灭
|
|
489
|
+
5. 骨架字段含义与可选字段(provides/expects_from/related_tests,按需插进 frontmatter)参考下述模板:
|
|
489
490
|
|
|
490
491
|
${taskcardTemplate}
|
|
491
492
|
|
|
@@ -498,7 +499,7 @@ ${taskcardTemplate}
|
|
|
498
499
|
|
|
499
500
|
## 完成标志
|
|
500
501
|
- 本 batch 的每个 task-N.md 都已写入 ${changeDir}/tasks/
|
|
501
|
-
- 每个文件非空且 frontmatter
|
|
502
|
+
- 每个文件非空且 frontmatter 完整(保持骨架的 --- 闭合与 LF 行尾)
|
|
502
503
|
\`\`\`
|
|
503
504
|
|
|
504
505
|
## 验收(生成后自查,不另开步骤)
|
|
@@ -558,21 +559,25 @@ export const fixedSuffix = [] // postcheck 是动态生成的(需要 changeDir
|
|
|
558
559
|
// ═══════════════════════════════════════════════════════════════
|
|
559
560
|
|
|
560
561
|
/**
|
|
561
|
-
*
|
|
562
|
+
* 解析任务清单内容获取任务数量(2026-08-20-task-truth-unify:源迁 tasks.md 注册表;
|
|
563
|
+
* 传入内容为注册表内容,plan.md 回退场景由调用方决定传谁)
|
|
562
564
|
*/
|
|
563
|
-
function parseTaskCount(
|
|
564
|
-
if (!
|
|
565
|
-
|
|
565
|
+
function parseTaskCount(registryContent) {
|
|
566
|
+
if (!registryContent || typeof registryContent !== 'string') return 0
|
|
567
|
+
// [ xX] + i(坑 taskcount-uppercase-x):parseTaskNames / gates.js extractTaskIdsFromRegistry
|
|
568
|
+
// 均收大写 [X],此处漏收 → 全大写勾选的注册表计数 0 → 误走「无任务」分支丢 coordinator 步
|
|
569
|
+
const matches = registryContent.match(/^[-*]\s*\[[ xX]\]\s*task-\d+/gim)
|
|
566
570
|
return matches ? matches.length : 0
|
|
567
571
|
}
|
|
568
572
|
|
|
569
573
|
/**
|
|
570
|
-
*
|
|
574
|
+
* 解析任务名列表(源=tasks.md 注册表;taskcard.js 复用,口径须同源)
|
|
575
|
+
* export 供 taskcard.js 复用(sillyspec taskcard 从 checkbox 行自动带出编号/标题)
|
|
571
576
|
*/
|
|
572
|
-
function parseTaskNames(
|
|
573
|
-
// 返回 [{name, num}]
|
|
577
|
+
export function parseTaskNames(registryContent) {
|
|
578
|
+
// 返回 [{name, num}]:保留原始编号,避免 buildCoordinatorStep 按 i+1 重编号与 execute 原始编号错位
|
|
574
579
|
const tasks = []
|
|
575
|
-
const lines =
|
|
580
|
+
const lines = String(registryContent || '').split('\n')
|
|
576
581
|
for (const line of lines) {
|
|
577
582
|
const m = line.match(/^[-*]\s*\[[ x]\]\s*task-(\d+):\s*(.+)/i)
|
|
578
583
|
if (m) tasks.push({ num: String(m[1]).padStart(2, '0'), name: m[2].trim() })
|
|
@@ -580,6 +585,18 @@ function parseTaskNames(planContent) {
|
|
|
580
585
|
return tasks
|
|
581
586
|
}
|
|
582
587
|
|
|
588
|
+
/**
|
|
589
|
+
* 读变更目录的任务注册表内容(tasks.md 唯一真相;缺失回退 plan.md——旧归档变更兼容读侧)
|
|
590
|
+
*/
|
|
591
|
+
export function readTaskRegistryContent(changeDir) {
|
|
592
|
+
if (!changeDir) return null
|
|
593
|
+
const tasksFile = path.join(changeDir, 'tasks.md')
|
|
594
|
+
if (existsSync(tasksFile)) return readFileSync(tasksFile, 'utf8')
|
|
595
|
+
const planFile = path.join(changeDir, 'plan.md')
|
|
596
|
+
if (existsSync(planFile)) return readFileSync(planFile, 'utf8')
|
|
597
|
+
return null
|
|
598
|
+
}
|
|
599
|
+
|
|
583
600
|
/**
|
|
584
601
|
* 动态构建 plan 步骤列表
|
|
585
602
|
* 新架构:3 个 LLM 步骤 + 1 个 noAI 步骤 = 4 阶段
|
|
@@ -591,15 +608,17 @@ function parseTaskNames(planContent) {
|
|
|
591
608
|
export function buildPlanSteps(changeDir = null, planContent = null) {
|
|
592
609
|
let taskCount = 0
|
|
593
610
|
|
|
594
|
-
//
|
|
595
|
-
|
|
611
|
+
// 尝试从任务注册表解析任务数(tasks.md 唯一真相;2026-08-20-task-truth-unify 起
|
|
612
|
+
// plan.md Wave 段为纯 ID 引用行,checkbox 计数须读 tasks.md。planContent 入参兼容
|
|
613
|
+
// 旧调用(视为注册表内容);changeDir 兜底读 tasks.md → plan.md)
|
|
614
|
+
if (changeDir) {
|
|
615
|
+
const registryContent = readTaskRegistryContent(changeDir)
|
|
616
|
+
if (registryContent != null) taskCount = parseTaskCount(registryContent)
|
|
617
|
+
} else if (planContent) {
|
|
596
618
|
taskCount = parseTaskCount(planContent)
|
|
597
|
-
} else if (changeDir) {
|
|
598
|
-
const planFile = path.join(changeDir, 'plan.md')
|
|
599
|
-
if (existsSync(planFile)) {
|
|
600
|
-
taskCount = parseTaskCount(readFileSync(planFile, 'utf8'))
|
|
601
|
-
}
|
|
602
619
|
}
|
|
620
|
+
// 兜底:注册表计数 0(写回前空窗/骨架未落)时回退入参 planContent 计数,防误走「无任务」分支
|
|
621
|
+
if (taskCount === 0 && planContent) taskCount = parseTaskCount(planContent)
|
|
603
622
|
|
|
604
623
|
// 没有任务数则用固定步骤(兼容旧流程,无蓝图步骤无 postcheck)
|
|
605
624
|
if (taskCount === 0) {
|
|
@@ -607,15 +626,18 @@ export function buildPlanSteps(changeDir = null, planContent = null) {
|
|
|
607
626
|
return [...fixedPrefix, ...postcheck]
|
|
608
627
|
}
|
|
609
628
|
|
|
610
|
-
//
|
|
629
|
+
// 解析任务名(源与计数同口径,坑 plan-tasknames-empty-under-registry-contract):新契约
|
|
630
|
+
// (2026-08-20-task-truth-unify)下 plan.md Wave 段是纯 ID 引用行(无 checkbox 无任务名),
|
|
631
|
+
// 此前只从 planContent/plan.md 解析 → taskNames 恒空 → coordinator 步骤任务清单空白,
|
|
632
|
+
// 主 agent 只能自行读 tasks.md 兜底易漏派。tasks.md 优先(与 readTaskRegistryContent 同序)。
|
|
611
633
|
let taskNames = []
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
634
|
+
let namesSource = planContent || null
|
|
635
|
+
if (changeDir) {
|
|
636
|
+
const registryContent = readTaskRegistryContent(changeDir)
|
|
637
|
+
if (registryContent != null) namesSource = registryContent
|
|
638
|
+
}
|
|
639
|
+
if (namesSource) {
|
|
640
|
+
taskNames = parseTaskNames(namesSource)
|
|
619
641
|
}
|
|
620
642
|
|
|
621
643
|
// 生成协调器步骤(TaskCard 生成)+ postcheck
|
package/src/stages/quick.js
CHANGED
|
@@ -16,7 +16,7 @@ export const definition = {
|
|
|
16
16
|
step 3 --done 审计会把 src 核心文件(CLI / 状态机 / 注入框架等危险清单)、新增文件、删除文件判为危险变更并拦截。若你**预判**本 quick 的修复目标本身就是这类文件,重新启动 quick 时带上 --force-baseline(受保护/危险文件)/ --allow-new(新增文件)/ --allow-delete(删除文件)预声明——flag 会持久化进会话守卫,后续 --done 直接生效,省一轮拦截重跑。不预带也可:step 3 拦截后按提示带 flag 重跑 --done 同样解锁。
|
|
17
17
|
|
|
18
18
|
### 🧭 文件预声明(多 agent 并发仓库推荐)
|
|
19
|
-
--done 审计按「时间窗 diff」归属文件:quick 窗口内**并行会话**改的文件也会被算进本会话的 QUICKLOG「文件:」行。多个 agent 同时操作本仓库时,启动 quick 建议带 \`--files <a.js,b.js>\` 声明预计要改的文件——声明即归属:文件行只落声明命中的文件,窗口内未声明脏文件以「审计:」行落盘追溯(不混入、不静默丢)。不预判也行:--done 后按 QUICKLOG
|
|
19
|
+
--done 审计按「时间窗 diff」归属文件:quick 窗口内**并行会话**改的文件也会被算进本会话的 QUICKLOG「文件:」行。多个 agent 同时操作本仓库时,启动 quick 建议带 \`--files <a.js,b.js>\` 声明预计要改的文件——声明即归属:文件行只落声明命中的文件,窗口内未声明脏文件以「审计:」行落盘追溯(不混入、不静默丢)。不预判也行:--done 后按 QUICKLOG 核对说明手动精修文件行兜底。中途发现要改声明外文件(如新写的测试),带新增文件恢复会话即追加边界(追加不替换):\`sillyspec run quick --files <新增文件> --change <sessionId>\`。
|
|
20
20
|
|
|
21
21
|
### 操作
|
|
22
22
|
1. 检查关联变更(\`<linked-changes>\`,逗号分隔的变更名列表;显示「(无)」= 不关联变更),确定记录方式
|
|
@@ -37,6 +37,8 @@ step 3 --done 审计会把 src 核心文件(CLI / 状态机 / 注入框架等
|
|
|
37
37
|
- 在 \`{SPEC_ROOT}/quicklog/QUICKLOG-<git-user>.md\` 写入「进行中」条目(含关联变更与预估文件)
|
|
38
38
|
- 对每个关联变更 \`<c>\`:在 \`{SPEC_ROOT}/changes/<c>/tasks.md\` 追加未勾选 task \`- [ ] <quicklog-id> <任务描述>\`
|
|
39
39
|
|
|
40
|
+
> 💡 启动时带 \`--input "<一句话任务描述>"\` 可让条目从第一分钟就有语义标题(平台「快速修复」列表默认隐藏进行中的「(quick 任务)」占位条目);未带时标题要到 step 3 --done 才从「需求:」回填。若你启动时漏带且任务较长,建议按 CLI 启动警告指引重启会话补带。
|
|
41
|
+
|
|
40
42
|
**你不要创建或修改任何 QUICKLOG 的条目骨架 / tasks.md 记录**——ql-ID 分配、「进行中」条目、状态翻转、task 勾选、轮转全由 CLI 接管,你无需手写。<quicklog-id> 可用于 design.md / plan.md / archive / 模块变更索引引用。
|
|
41
43
|
|
|
42
44
|
> ℹ️ step 3 --done 时,CLI 已落盘结构化 QUICKLOG 条目(标题从「需求:」提取——届时写一句语义化短标题,规则见 step3 结果摘要模板;正文四字段分行、文件行可用 --file-notes 写多行括注)。事后只需按需核对,详见 step 3「QUICKLOG 正文核对」。
|
package/src/stages/verify.js
CHANGED
|
@@ -98,7 +98,7 @@ export const definition = {
|
|
|
98
98
|
},
|
|
99
99
|
{
|
|
100
100
|
name: '逐项检查任务',
|
|
101
|
-
prompt: `对照 tasks.md
|
|
101
|
+
prompt: `对照 tasks.md(任务注册表唯一真相)检查每个任务完成状态。勾选由 execute 双路写入(agent 按 review gate 手动勾 + CLI autoCheckPlanFromReviews 机器勾选器按 review.json 自动勾),本阶段只读对照、不改勾。
|
|
102
102
|
|
|
103
103
|
### 操作
|
|
104
104
|
对每个 checkbox:
|