sillyspec 3.29.5 → 3.31.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-auto/SKILL.md +1 -1
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +2 -2
- package/.claude/skills/sillyspec-commit/SKILL.md +1 -1
- package/.claude/skills/sillyspec-flow/SKILL.md +57 -0
- package/.claude/skills/sillyspec-quick/SKILL.md +7 -105
- package/AGENTS.md +38 -20
- package/SKILL.md +9 -3
- package/assets/command-cards/flow.md +32 -0
- package/assets/command-cards/run-brainstorm.md +1 -1
- package/assets/command-cards/run-quick.md +8 -25
- package/package.json +2 -1
- package/packages/dashboard/server/executor.js +2 -1
- package/packages/dashboard/server/parser.js +7 -7
- package/r7-test-env/README-R7-TEST.md +93 -0
- package/r7-test-env/demo-project/lib/calc.js +6 -0
- package/r7-test-env/demo-project/package.json +1 -0
- package/r7-test-env/demo-project/test.js +10 -0
- package/r7-test-env/prompt-R7-SMOKE.md +41 -0
- package/r7-test-env/sillyspec-r7.cmd +3 -0
- package/round4/account.mjs +18 -5
- package/round4/b1-analyze-task08.mjs +40 -0
- package/round4/b1-b1-target-validation.md +32 -0
- package/round4/b3a-prompt-audit-result.md +66 -0
- package/round4/b3a-prompt-audit.mjs +89 -0
- package/round4/optimization-plan.md +165 -0
- package/round4/r4-final-report.html +347 -0
- package/round4/r5-scenario-matrix.md +46 -0
- package/src/change-list.js +9 -0
- package/src/classify-change.js +6 -0
- package/src/command-cards.js +2 -1
- package/src/commit-guard.js +2 -0
- package/src/config-schema.js +64 -12
- package/src/db.js +35 -4
- package/src/decision-distill.js +9 -3
- package/src/docs-check.js +1 -1
- package/src/doctor-diagnostics.js +44 -2
- package/src/flow-draft.js +748 -0
- package/src/flow-parity.js +236 -0
- package/src/flow-review.js +184 -0
- package/src/flow.js +1492 -0
- package/src/foreign-declared.js +17 -1
- package/src/fr-index.js +396 -22
- package/src/fs-atomic.js +5 -1
- package/src/git-helper.js +2 -2
- package/src/greenfield-bootstrap.js +98 -0
- package/src/handoff.js +120 -7
- package/src/hunk-attribution.js +191 -0
- package/src/index.js +570 -16
- package/src/knowledge-classify.js +47 -1
- package/src/knowledge-digest.js +162 -0
- package/src/knowledge-quicklog.js +246 -0
- package/src/machine-draft.js +128 -0
- package/src/machine-interface.js +107 -5
- package/src/mcp-server.js +1 -0
- package/src/module-resolve.js +3 -1
- package/src/preview-progress.js +160 -0
- package/src/progress/change-registry.js +11 -3
- package/src/progress/shared.js +3 -2
- package/src/progress.js +56 -13
- package/src/quicklog.js +44 -6
- package/src/redomain.js +147 -0
- package/src/review-material-pack.js +204 -1
- package/src/run/archive-distill.js +4 -2
- package/src/run/bg-sync.js +67 -0
- package/src/run/command.js +123 -10
- package/src/run/complete-handlers.js +327 -72
- package/src/run/complete.js +350 -71
- package/src/run/gate-snapshot-ledger.js +213 -0
- package/src/run/gate-snapshot.js +250 -34
- package/src/run/gates.js +370 -233
- package/src/run/green-cache.js +123 -0
- package/src/run/junction-rm.js +70 -0
- package/src/run/next.js +14 -0
- package/src/run/prompt.js +185 -55
- package/src/run/quick-audit.js +185 -12
- package/src/run/shared.js +120 -3
- package/src/run/stage.js +182 -216
- package/src/run/test-ledger.js +264 -0
- package/src/run/verify-quality-scan.js +84 -7
- package/src/scope-audit.js +6 -1
- package/src/sentinel-assertions.js +114 -0
- package/src/setup.js +3 -3
- package/src/spec-sync.js +41 -1
- package/src/stage-contract.js +26 -8
- package/src/stage-review-checklist.js +2 -0
- package/src/stage-review.js +7 -2
- package/src/stage-templates.js +157 -0
- package/src/stages/archive.js +2 -0
- package/src/stages/brainstorm.js +24 -106
- package/src/stages/execute.js +221 -84
- package/src/stages/knowledge.js +74 -1
- package/src/stages/plan-postcheck.js +352 -1
- package/src/stages/plan.js +23 -1
- package/src/stages/quick.js +10 -2
- package/src/stages/verify.js +4 -2
- package/src/sync.js +81 -23
- package/src/task-done.js +181 -0
- package/src/task-review.js +8 -55
- package/src/test-bindings.js +549 -0
- package/src/timeline.js +218 -0
- package/src/ui-visual.js +175 -0
- package/src/verify-draft.js +214 -0
- package/src/verify-postcheck.js +860 -158
- package/src/verify-probes.js +59 -3
- package/src/watcher.js +1031 -0
- package/src/worktree-apply.js +44 -4
- package/src/worktree-deps.js +22 -8
- package/src/worktree-policy.js +42 -0
- package/src/worktree.js +102 -15
- package/templates/agents-instruction.md +37 -18
|
@@ -1337,12 +1337,22 @@ export function validatePlanFeasibility(changeDir, projectRoot = null) {
|
|
|
1337
1337
|
}
|
|
1338
1338
|
|
|
1339
1339
|
// 3. allowed_paths 文件存在或父目录存在(仅在 projectRoot 提供时检查)
|
|
1340
|
+
// F2/F3(R5 对撞实证 2026-09-21,round5/r5-collision-ehs-attribution.md P9/N4):
|
|
1341
|
+
// ① NEW: 前缀 = 声明待建文件(taskcard.js:129 已定合法格式),plan 时不存在是常态,
|
|
1342
|
+
// 照查必假阳性(实证:plan --done 三连卡壳 + agent 读 CLI 源码考古)——跳过;
|
|
1343
|
+
// ② repo≠main 的跨仓 task 路径相对他仓根,用主仓 projectRoot 查 100% 不存在——整卡跳过;
|
|
1344
|
+
// ③ 主仓真缺失仍告警,但文案附逃生通道(教 NEW: 前缀与 repo: 字段),消灭「读源码弄懂
|
|
1345
|
+
// 校验规则」的 meta 开销。
|
|
1340
1346
|
if (projectRoot && allowedPaths.length > 0) {
|
|
1347
|
+
const repoField = parseFrontmatterScalar(content, 'repo')
|
|
1348
|
+
const crossRepo = repoField != null && repoField !== 'main'
|
|
1341
1349
|
for (const p of allowedPaths) {
|
|
1350
|
+
if (p.startsWith('NEW:')) continue // 待建文件:存在性检查语义不适用
|
|
1351
|
+
if (crossRepo) continue // 跨仓路径按对应仓根声明,主仓根下查不到是常态(由该仓自己的执行/verify 把关)
|
|
1342
1352
|
const fullPath = pJoin(projectRoot, p)
|
|
1343
1353
|
const parentDir = pJoin(fullPath, '..')
|
|
1344
1354
|
if (!existsSync(fullPath) && !existsSync(parentDir)) {
|
|
1345
|
-
warnings.push(`${taskId}: allowed_paths 中的 ${p}
|
|
1355
|
+
warnings.push(`${taskId}: allowed_paths 中的 ${p} 文件和父目录都不存在——若为本次待建文件请改写为 NEW:${p};跨仓 task 路径须配 repo: <仓别名> 字段按对应仓根书写(此项主仓存在性检查不适用)`)
|
|
1346
1356
|
}
|
|
1347
1357
|
}
|
|
1348
1358
|
}
|
|
@@ -1621,6 +1631,322 @@ export function assessWaveStructure({ existingWaves, topoWaves, depMap, wavePath
|
|
|
1621
1631
|
return { mergeablePairs, error, advisory }
|
|
1622
1632
|
}
|
|
1623
1633
|
|
|
1634
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1635
|
+
// 并批 advisory(B-③ / FR-01,2026-09-21-r5-efficiency-batch1 task-01)
|
|
1636
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1637
|
+
|
|
1638
|
+
/** task id 归一补零(`task-1` → `task-01`,与卡片/注册表/Wave 引用口径一致,坑 wave-ref-unpadded 家族) */
|
|
1639
|
+
function padTaskRef(id) {
|
|
1640
|
+
return String(id).trim().replace(/^task-(\d+)$/i, (_, n) => `task-${n.padStart(2, '0')}`)
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
/**
|
|
1644
|
+
* 解析并批成员串:`task-01+task-02` / `task-01,task-02` / `01+02`(depends_on 式数字简写)。
|
|
1645
|
+
* 混合分隔符(+ + , , ; ;)通吃;返回补零 id 数组(无法解析的 token 丢弃)。
|
|
1646
|
+
*/
|
|
1647
|
+
function parseBatchMemberList(text) {
|
|
1648
|
+
if (text == null) return []
|
|
1649
|
+
return String(text)
|
|
1650
|
+
.split(/[++,,;;]/)
|
|
1651
|
+
.map(s => s.trim().replace(/['"`]/g, ''))
|
|
1652
|
+
.filter(Boolean)
|
|
1653
|
+
.map(tok => {
|
|
1654
|
+
const m = tok.match(/^(?:task-)?(\d+)$/i)
|
|
1655
|
+
return m ? `task-${m[1].padStart(2, '0')}` : null
|
|
1656
|
+
})
|
|
1657
|
+
.filter(Boolean)
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
/**
|
|
1661
|
+
* 解析 plan.md Wave 段任务分组 + 批标注行(行扫描口径同 parseTaskWavesFromPlan /
|
|
1662
|
+
* execute.js parseWavesFromPlan:`## Wave N` 进段、非 Wave 标题退出段)。
|
|
1663
|
+
*
|
|
1664
|
+
* 批标注行两形态(规范形态由 plan.js stepGeneratePlan prompt 钉死):
|
|
1665
|
+
* ① `> batch: task-01+task-02`——Wave 段内 blockquote 行,**不替代** `- task-XX` 纯 ID
|
|
1666
|
+
* 引用行(execute 只按纯 ID 行收任务,批标注行对既有解析器全部惰性——零阻断面);
|
|
1667
|
+
* ② `- task-01 + task-02(batch)`——列表项多 ID `+` 拼接(agent 即兴写法,语义无歧义宽收)。
|
|
1668
|
+
*
|
|
1669
|
+
* @param {string} planMdText
|
|
1670
|
+
* @returns {Array<{waveNo:number, tasks:string[], groups:string[][], batchIntent:boolean, clean:boolean}>}
|
|
1671
|
+
* tasks 补零任务 id(纯 ID 行 + 旧 checkbox 行兼容收,同既有双正则口径)
|
|
1672
|
+
* groups 批标注解析出的成员组(未与 Wave 任务对账,对账归主函数)
|
|
1673
|
+
* batchIntent 有任何并批意图信号(可解析标注 / 含 batch/并批/同批 字样的存疑行)→ 判定①不再提示
|
|
1674
|
+
* clean 无存疑 task 列表行、无 <2 成员的标注 → 判定②才评估(宁缺勿假)
|
|
1675
|
+
*/
|
|
1676
|
+
function parseBatchWavesFromPlanText(planMdText) {
|
|
1677
|
+
const waves = []
|
|
1678
|
+
let cur = null
|
|
1679
|
+
for (const line of String(planMdText || '').replace(/\r\n?/g, '\n').split('\n')) {
|
|
1680
|
+
const wm = line.match(/^#+\s*Wave\s+(\d+)/i)
|
|
1681
|
+
if (wm) {
|
|
1682
|
+
cur = { waveNo: parseInt(wm[1], 10), tasks: [], groups: [], batchIntent: false, clean: true }
|
|
1683
|
+
waves.push(cur)
|
|
1684
|
+
continue
|
|
1685
|
+
}
|
|
1686
|
+
if (/^#+\s/.test(line)) { cur = null; continue } // 非 Wave 标题退出当前段(同既有解析守卫)
|
|
1687
|
+
if (!cur) continue
|
|
1688
|
+
// 批标注形态①:`> batch: task-01+task-02`
|
|
1689
|
+
const bm = line.match(/^>\s*batch\s*[::]\s*(.+)$/i)
|
|
1690
|
+
if (bm) {
|
|
1691
|
+
cur.batchIntent = true
|
|
1692
|
+
const members = parseBatchMemberList(bm[1])
|
|
1693
|
+
if (members.length >= 2) cur.groups.push(members)
|
|
1694
|
+
else cur.clean = false
|
|
1695
|
+
continue
|
|
1696
|
+
}
|
|
1697
|
+
// 批标注形态②:`- task-01 + task-02(batch)`(多 ID `+` 拼接的列表项)
|
|
1698
|
+
const lb = line.match(/^[-*]\s+(task-\d+(?:\s*[++]\s*task-\d+)+)\s*(?:[((]\s*batch\s*[))])?\s*$/i)
|
|
1699
|
+
if (lb) {
|
|
1700
|
+
cur.batchIntent = true
|
|
1701
|
+
const members = parseBatchMemberList(lb[1])
|
|
1702
|
+
if (members.length >= 2) cur.groups.push(members)
|
|
1703
|
+
else cur.clean = false
|
|
1704
|
+
continue
|
|
1705
|
+
}
|
|
1706
|
+
// 纯 ID 引用行(新契约)与旧 checkbox 行(同 parseTaskWavesFromPlan 双正则口径)
|
|
1707
|
+
const tm = line.match(/^[-*]\s+(?:\[[ x]\]\s*)?(task-\d+)\s*[::\s]*$/i)
|
|
1708
|
+
|| line.match(/^[-*]\s*\[[ x]\]\s*(task-\d+)\b/i)
|
|
1709
|
+
if (tm) { cur.tasks.push(padTaskRef(tm[1])); continue }
|
|
1710
|
+
// 含 task id 但不匹配任何已知形态的列表行 → 并批信息存疑(判定②跳过;含并批字样则
|
|
1711
|
+
// 同时视为并批意图挡①)。说明文字等非列表行忽略,不参与任务集合与并批判定。
|
|
1712
|
+
if (/^[-*]\s+.*task-\d+/i.test(line)) {
|
|
1713
|
+
cur.clean = false
|
|
1714
|
+
if (/batch|并批|同批/i.test(line)) cur.batchIntent = true
|
|
1715
|
+
continue
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
return waves
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
/**
|
|
1722
|
+
* 汇总单卡并批判定要件(纯函数,复用本文件既有解析器先例——parseTargetFiles / parseAllowedPaths /
|
|
1723
|
+
* parseDependsOn / parseTaskContracts / parseTaskFrontmatter):
|
|
1724
|
+
* fileSet 文件面(parseTargetFiles 优先;缺失回退 parseAllowedPaths——存量卡兼容;两者皆无 → null 不可判)
|
|
1725
|
+
* deps depends_on(补零)
|
|
1726
|
+
* expectsProviders expects_from 的 provider 任务集(补零;Wave 内命中 = 契约链)
|
|
1727
|
+
* batchMembers 卡 frontmatter `batch:` 字段成员(数组成员或 `task-01+task-02` 串皆收;<2 成员不供信息)
|
|
1728
|
+
*
|
|
1729
|
+
* M3(2026-09-21-r5-efficiency-batch2 task-03)起导出:execute.js buildWavePrompt 推荐分组
|
|
1730
|
+
* 与本文件 checkBatchAdvisory 共用此 facts 收集(单一实现无二源——导出是复用需要,非新逻辑)。
|
|
1731
|
+
*/
|
|
1732
|
+
export function collectCardBatchFacts(content) {
|
|
1733
|
+
const text = String(content || '')
|
|
1734
|
+
let fileSet = null
|
|
1735
|
+
const tf = parseTargetFiles(text)
|
|
1736
|
+
if (!tf.missing && tf.entries.length > 0) {
|
|
1737
|
+
fileSet = new Set(tf.entries.map(e => e.path))
|
|
1738
|
+
} else {
|
|
1739
|
+
const ap = parseAllowedPaths(text)
|
|
1740
|
+
if (ap.length > 0) fileSet = new Set(ap)
|
|
1741
|
+
}
|
|
1742
|
+
const deps = parseDependsOn(text).map(padTaskRef)
|
|
1743
|
+
const { expectsFrom } = parseTaskContracts(text)
|
|
1744
|
+
const expectsProviders = Object.keys(expectsFrom || {}).map(padTaskRef)
|
|
1745
|
+
let batchMembers = null
|
|
1746
|
+
try {
|
|
1747
|
+
const parsed = parseTaskFrontmatter(text)
|
|
1748
|
+
if (parsed.ok && parsed.fm && parsed.fm.batch != null) {
|
|
1749
|
+
const raw = Array.isArray(parsed.fm.batch) ? parsed.fm.batch : [parsed.fm.batch]
|
|
1750
|
+
const uniq = [...new Set(raw.flatMap(v => parseBatchMemberList(String(v))))]
|
|
1751
|
+
if (uniq.length >= 2) batchMembers = uniq
|
|
1752
|
+
}
|
|
1753
|
+
} catch { /* 坏卡不供并批信息(fail-open,advisory 面不抛错) */ }
|
|
1754
|
+
return { fileSet, deps, expectsProviders, batchMembers }
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
/**
|
|
1758
|
+
* recommendWaveGroups —— M3 PLAN 粒度派发默认化的推荐分组纯函数(FR-03 / D-003@v1,
|
|
1759
|
+
* 2026-09-21-r5-efficiency-batch2 task-03)。按第 1 批三条件(allowed_paths/target_files
|
|
1760
|
+
* 两两正交 / 组内无 provides-expects_from 契约链(含 depends_on,双向)/ 组 ≤3)贪心预计算
|
|
1761
|
+
* 分组;checkBatchAdvisory 判定①文案与 execute.js buildWavePrompt 派发段共用本函数(无二源)。
|
|
1762
|
+
*
|
|
1763
|
+
* 护栏不变式(与 checkBatchAdvisory 两判定同口径:并批后整 Wave 批数 ≥ min(3, 任务数)):
|
|
1764
|
+
* 并批预算 = n − min(3,n),每把一个 task 并入已有组消耗 1 预算,预算耗尽后余 task 独立成组;
|
|
1765
|
+
* n≤3 时预算为 0 恒全单例(任何并批必违反 min(3,N)=N——推荐不得自触判定②)。
|
|
1766
|
+
* files=null(卡缺失/字段全无,正交不可判)→ 恒单例(宁缺勿假)。
|
|
1767
|
+
*
|
|
1768
|
+
* 确定性:按输入序贪心(先开组后补组、组满 3 或不相容即让位),同输入恒同输出;
|
|
1769
|
+
* 非数组输入返回 [](纯机械容错,不 throw)。
|
|
1770
|
+
*
|
|
1771
|
+
* @param {Array<{id:string, files:Iterable<string>|null, deps?:string[], expectsProviders?:string[]}>} tasks
|
|
1772
|
+
* id 接受未补零形态(内部 padTaskRef 归一);deps/expectsProviders 缺省视为无链。
|
|
1773
|
+
* @returns {string[][]} 全量 partition(含单例组,组内按输入序)——调用方以
|
|
1774
|
+
* groups.some(g => g.length >= 2) 判定有无推荐(无则零注入/零文案)。
|
|
1775
|
+
*/
|
|
1776
|
+
export function recommendWaveGroups(tasks) {
|
|
1777
|
+
if (!Array.isArray(tasks)) return []
|
|
1778
|
+
const list = tasks.map(t => ({
|
|
1779
|
+
id: padTaskRef(t?.id ?? ''),
|
|
1780
|
+
files: t?.files == null ? null : new Set([...t.files].map(String)),
|
|
1781
|
+
deps: new Set((t?.deps || []).map(padTaskRef)),
|
|
1782
|
+
expects: new Set((t?.expectsProviders || []).map(padTaskRef)),
|
|
1783
|
+
}))
|
|
1784
|
+
const n = list.length
|
|
1785
|
+
let budget = n - Math.min(3, n) // 并批预算:分组后批数 = n - 已消耗预算 ≥ min(3,n) 恒成立
|
|
1786
|
+
const chainBetween = (a, b) =>
|
|
1787
|
+
a.deps.has(b.id) || b.deps.has(a.id) || a.expects.has(b.id) || b.expects.has(a.id)
|
|
1788
|
+
const filesDisjoint = (a, b) => {
|
|
1789
|
+
for (const p of a.files) if (b.files.has(p)) return false
|
|
1790
|
+
return true
|
|
1791
|
+
}
|
|
1792
|
+
const groups = []
|
|
1793
|
+
for (const t of list) {
|
|
1794
|
+
let placed = false
|
|
1795
|
+
if (t.files != null && budget > 0) {
|
|
1796
|
+
for (const g of groups) {
|
|
1797
|
+
if (g.length >= 3) continue // 帽值:组 ≤3
|
|
1798
|
+
if (g.some(m => m.files == null || !filesDisjoint(m, t) || chainBetween(m, t))) continue
|
|
1799
|
+
g.push(t)
|
|
1800
|
+
budget -= 1
|
|
1801
|
+
placed = true
|
|
1802
|
+
break
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
if (!placed) groups.push([t])
|
|
1806
|
+
}
|
|
1807
|
+
return groups.map(g => g.map(t => t.id))
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
/**
|
|
1811
|
+
* checkBatchAdvisory —— plan 并批默认的 postcheck 机械提醒(B-③ / FR-01,warning 级 advisory)。
|
|
1812
|
+
*
|
|
1813
|
+
* 两判定(同口径不变式:并批后整 Wave 批数 ≥ min(3, 该 Wave 任务数)):
|
|
1814
|
+
* ① batch_orthogonal_unbundled:Wave 任务两两文件正交(无共享 target_files——缺失回退
|
|
1815
|
+
* allowed_paths 判、无 Wave 内 depends_on 链、无 Wave 内 expects_from 契约链)且无任何
|
|
1816
|
+
* 并批信号(plan.md 批标注行 / task 卡 batch 字段)→ 提示默认并批(2–4 任务/批)。
|
|
1817
|
+
* 阈值 N≥4:N≤3 时任何并批都违反护栏 min(3,N)=N(未并批已是唯一合规形态),提示只会
|
|
1818
|
+
* 制造 ①↔② 打架的 nag 环;B-③ 实证收益场景(R4-S-F)为 5 任务级。
|
|
1819
|
+
* ② wave_inflight_below_floor:有**可解析且无冲突**的并批信息,批数(多成员组数 + 未分组
|
|
1820
|
+
* 单任务数)< min(3, 任务数) → 护栏缺口提示(墙钟回退风险,warning 可显式接受)。
|
|
1821
|
+
* 解析不出 / 组间冲突 / 越界(成员不在本 Wave)→ 跳过(宁缺勿假,绝不猜批数)。
|
|
1822
|
+
*
|
|
1823
|
+
* @param {object} opts
|
|
1824
|
+
* @param {string} opts.tasksMdText tasks.md 注册表文本(任务唯一真相——声明集非空时过滤 Wave
|
|
1825
|
+
* 段里未声明的陈旧引用,防虚增任务数扰动两判定;空/无声明则不过滤)
|
|
1826
|
+
* @param {string} opts.planMdText plan.md 文本(Wave 段分组 + 批标注行)
|
|
1827
|
+
* @param {object|Map<string,string>} [opts.taskCards] taskId → 卡片全文(Record 或 Map 皆收)
|
|
1828
|
+
* @returns {Array<{level:'warning', code:'batch_orthogonal_unbundled'|'wave_inflight_below_floor', message:string}>}
|
|
1829
|
+
* 恒为数组(0..n 条,单次调用多 Wave 可各出一条);调用方只准并入 warnings 通道——
|
|
1830
|
+
* 红线:绝不进 errors(门禁阻断语义零变化)。
|
|
1831
|
+
*/
|
|
1832
|
+
export function checkBatchAdvisory({ tasksMdText, planMdText, taskCards } = {}) {
|
|
1833
|
+
const advisories = []
|
|
1834
|
+
const waves = parseBatchWavesFromPlanText(planMdText)
|
|
1835
|
+
if (waves.length === 0) return advisories
|
|
1836
|
+
|
|
1837
|
+
// 注册表过滤(2026-08-20-task-truth-unify:tasks.md 是任务唯一真相)
|
|
1838
|
+
const declared = new Set()
|
|
1839
|
+
for (const line of String(tasksMdText || '').replace(/\r\n?/g, '\n').split('\n')) {
|
|
1840
|
+
const m = line.match(/^[-*]\s*\[[ xX]\]\s*(task-\d+)\b/i)
|
|
1841
|
+
if (m) declared.add(padTaskRef(m[1]))
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
const cards = new Map()
|
|
1845
|
+
const cardEntries = taskCards instanceof Map
|
|
1846
|
+
? [...taskCards.entries()]
|
|
1847
|
+
: (taskCards && typeof taskCards === 'object' ? Object.entries(taskCards) : [])
|
|
1848
|
+
for (const [id, content] of cardEntries) cards.set(padTaskRef(id), collectCardBatchFacts(content))
|
|
1849
|
+
|
|
1850
|
+
for (const w of waves) {
|
|
1851
|
+
const waveTasks = [...new Set(w.tasks)].filter(t => declared.size === 0 || declared.has(t))
|
|
1852
|
+
const n = waveTasks.length
|
|
1853
|
+
if (n < 2) continue
|
|
1854
|
+
const waveSet = new Set(waveTasks)
|
|
1855
|
+
|
|
1856
|
+
// ── 并批信息对账:plan 批标注 × 卡 batch 字段 → 组。越界(成员不在本 Wave——跨 Wave /
|
|
1857
|
+
// 幻觉引用)或组间冲突(同任务进两组)→ 本 Wave ②跳过(宁缺勿假);此时必有并批
|
|
1858
|
+
// 意图信号(组来自批标注行或卡 batch 字段),①也不再提示。──
|
|
1859
|
+
let conflicted = false
|
|
1860
|
+
const rawGroups = []
|
|
1861
|
+
const seenKeys = new Set()
|
|
1862
|
+
const pushGroup = (members) => {
|
|
1863
|
+
const key = [...members].sort().join('+')
|
|
1864
|
+
if (seenKeys.has(key)) return // 同组多卡重复声明 / 双源同组 → 去重
|
|
1865
|
+
seenKeys.add(key)
|
|
1866
|
+
rawGroups.push(members)
|
|
1867
|
+
}
|
|
1868
|
+
for (const members of w.groups) {
|
|
1869
|
+
if (members.some(m => !waveSet.has(m))) { conflicted = true; continue }
|
|
1870
|
+
pushGroup(members)
|
|
1871
|
+
}
|
|
1872
|
+
let cardBatchIntent = false
|
|
1873
|
+
for (const t of waveTasks) {
|
|
1874
|
+
const c = cards.get(t)
|
|
1875
|
+
if (!c || !c.batchMembers) continue
|
|
1876
|
+
cardBatchIntent = true
|
|
1877
|
+
if (c.batchMembers.some(m => !waveSet.has(m))) { conflicted = true; continue }
|
|
1878
|
+
pushGroup(c.batchMembers)
|
|
1879
|
+
}
|
|
1880
|
+
const owner = new Set()
|
|
1881
|
+
let overlap = false
|
|
1882
|
+
for (const g of rawGroups) {
|
|
1883
|
+
for (const t of g) {
|
|
1884
|
+
if (owner.has(t)) { overlap = true; break }
|
|
1885
|
+
owner.add(t)
|
|
1886
|
+
}
|
|
1887
|
+
if (overlap) break
|
|
1888
|
+
}
|
|
1889
|
+
if (conflicted || overlap) continue
|
|
1890
|
+
|
|
1891
|
+
const batchIntent = w.batchIntent || cardBatchIntent
|
|
1892
|
+
const batchCount = rawGroups.length + (n - owner.size)
|
|
1893
|
+
const floor = Math.min(3, n)
|
|
1894
|
+
|
|
1895
|
+
// ── 判定①:文件正交但全未并批 ──
|
|
1896
|
+
if (!batchIntent && n >= 4) {
|
|
1897
|
+
const facts = waveTasks.map(t => cards.get(t) || null)
|
|
1898
|
+
const knowable = facts.every(f => f && f.fileSet)
|
|
1899
|
+
const chainFree = facts.every(f => f
|
|
1900
|
+
&& !f.deps.some(d => waveSet.has(d))
|
|
1901
|
+
&& !f.expectsProviders.some(p => waveSet.has(p)))
|
|
1902
|
+
let fileDisjoint = true
|
|
1903
|
+
if (knowable && chainFree) {
|
|
1904
|
+
outer:
|
|
1905
|
+
for (let i = 0; i < facts.length; i++) {
|
|
1906
|
+
for (let j = i + 1; j < facts.length; j++) {
|
|
1907
|
+
for (const p of facts[i].fileSet) {
|
|
1908
|
+
if (facts[j].fileSet.has(p)) { fileDisjoint = false; break outer }
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
if (knowable && chainFree && fileDisjoint) {
|
|
1914
|
+
// M3(batch2 task-03):判定①附推荐分组清单——同一纯函数 recommendWaveGroups
|
|
1915
|
+
// 无二源(该分支内任务两两正交无链,分组差异只来自帽值与批数护栏预算)。
|
|
1916
|
+
const recGroups = recommendWaveGroups(waveTasks.map((t, i) => ({
|
|
1917
|
+
id: t,
|
|
1918
|
+
files: facts[i].fileSet,
|
|
1919
|
+
deps: facts[i].deps,
|
|
1920
|
+
expectsProviders: facts[i].expectsProviders,
|
|
1921
|
+
})))
|
|
1922
|
+
const recLine = recGroups.some(g => g.length >= 2)
|
|
1923
|
+
? `;CLI 预计算推荐分组:${recGroups.map(g => `[${g.join(',')}]`).join(' / ')}`
|
|
1924
|
+
: ''
|
|
1925
|
+
advisories.push({
|
|
1926
|
+
level: 'warning',
|
|
1927
|
+
code: 'batch_orthogonal_unbundled',
|
|
1928
|
+
message: `Wave ${w.waveNo} 内 ${n} 个任务文件两两正交(无共享 target_files、无 depends_on / provides-expects_from 契约链)` +
|
|
1929
|
+
`但全部未并批——默认并批可摊平 execute 扇出的重复上下文重建(B-③):2–4 任务/批,` +
|
|
1930
|
+
`Wave 段内加批标注行(如 "> batch: task-01+task-02",不替代 - task-XX 纯 ID 引用行),` +
|
|
1931
|
+
`并批后整 Wave 批数保持 ≥ min(3, ${n})${recLine}`,
|
|
1932
|
+
})
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
// ── 判定②:批数护栏缺口(解析不出并批信息时跳过——宁缺勿假)──
|
|
1937
|
+
if (batchIntent && w.clean && rawGroups.length > 0 && batchCount < floor) {
|
|
1938
|
+
advisories.push({
|
|
1939
|
+
level: 'warning',
|
|
1940
|
+
code: 'wave_inflight_below_floor',
|
|
1941
|
+
message: `Wave ${w.waveNo} 并批后批数 ${batchCount} < min(3, ${n})(该 Wave 任务数)——` +
|
|
1942
|
+
`在飞子代理数低于护栏下限,有墙钟回退风险;建议拆为 ≥ ${floor} 批(如 5 任务 → 2+2+1 三批而非 3+2 两批)。` +
|
|
1943
|
+
`护栏为 warning 提示可显式接受:S-F 型小任务(token 优先于墙钟)可保持现批并在 plan.md 注明接受`,
|
|
1944
|
+
})
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
return advisories
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1624
1950
|
/**
|
|
1625
1951
|
* plan_level 第二把尺子(不接管,仅 warning):design 文件清单数 / 模块跨度 / task 数
|
|
1626
1952
|
* 与声明档位比对。fail-open(design/map 读取失败 → null 零输出)。
|
|
@@ -1854,6 +2180,31 @@ export async function executePlanPostcheck(context) {
|
|
|
1854
2180
|
}
|
|
1855
2181
|
} catch { /* fail-open */ }
|
|
1856
2182
|
|
|
2183
|
+
// ── 1h. 并批 advisory(B-③ / FR-01,warning 不阻断——R5 效率批 task-01)──
|
|
2184
|
+
// 正交未并批提示 + 批数护栏提示,只进 warnings 输出通道(printSectionWarnings 先例),
|
|
2185
|
+
// 绝不进 failures/errors——红线:门禁阻断语义零变化。fail-open:读取/解析异常整体跳过。
|
|
2186
|
+
try {
|
|
2187
|
+
const batchAdvPlanPath = pJoin(changeDir, 'plan.md')
|
|
2188
|
+
if (existsSync(batchAdvPlanPath)) {
|
|
2189
|
+
const batchAdvTasksMdPath = pJoin(changeDir, 'tasks.md')
|
|
2190
|
+
const batchAdvTasksDir = pJoin(changeDir, 'tasks')
|
|
2191
|
+
const batchAdvCards = {}
|
|
2192
|
+
if (existsSync(batchAdvTasksDir)) {
|
|
2193
|
+
for (const f of readdirSync(batchAdvTasksDir).filter(f => /^task-\d+\.md$/.test(f))) {
|
|
2194
|
+
const cardContent = readFileSync(pJoin(batchAdvTasksDir, f), 'utf8')
|
|
2195
|
+
const cardId = parseTaskId(cardContent, f)
|
|
2196
|
+
if (cardId) batchAdvCards[cardId] = cardContent
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
const batchAdvisories = checkBatchAdvisory({
|
|
2200
|
+
tasksMdText: existsSync(batchAdvTasksMdPath) ? readFileSync(batchAdvTasksMdPath, 'utf8') : '',
|
|
2201
|
+
planMdText: readFileSync(batchAdvPlanPath, 'utf8'),
|
|
2202
|
+
taskCards: batchAdvCards,
|
|
2203
|
+
})
|
|
2204
|
+
printSectionWarnings('并批', batchAdvisories.map(a => `[${a.code}] ${a.message}`))
|
|
2205
|
+
}
|
|
2206
|
+
} catch { /* fail-open:advisory 面任何异常不阻断 */ }
|
|
2207
|
+
|
|
1857
2208
|
// ── 聚合输出:一轮 --done 暴露全部失败项(坑6③)──
|
|
1858
2209
|
if (failures.length > 0) {
|
|
1859
2210
|
console.error(`\n❌ plan postcheck 失败(${failures.length} 类问题,已全部列出——一次修复后重跑,无需逐个迭代):`)
|
package/src/stages/plan.js
CHANGED
|
@@ -154,6 +154,23 @@ const stepGeneratePlan = {
|
|
|
154
154
|
> tasks.md 格式:\`- [ ] task-01: 一句话任务名\`(execute 从这里解析任务清单)
|
|
155
155
|
> plan.md Wave 段格式:\`- task-01\`(纯 ID 引用,execute 据此分组)
|
|
156
156
|
|
|
157
|
+
> **拆分粒度纪律(D-006@v1 / FR-11,R7 实证:18 卡细拆给反仪式变更预付协议税)**:
|
|
158
|
+
> - **默认「实现+单测同卡」**——测试与实现放同一任务卡(TDD 同任务),不单独开「测试卡」「验收 Wave」;纯验收类动作(跑全量回归)并入相关实现卡的 acceptance/verify。
|
|
159
|
+
> - **禁止仪式卡**:「纯接线」「纯 module-map 录入」「纯全量回归绿」不独立成卡——此类收尾动作写进相关实现卡的 implementation 与 acceptance(几行接线并入实现,module-map 录入并入该片最后一张实现卡)。
|
|
160
|
+
> - **「同 Wave 文件不相交」语义澄清**:该约束防并行子代理同文件互写(调度约束),**不要求**把测试/接线/录入拆成独立任务——同一张卡携带多个文件(实现+测试+module-map)完全合法,约束只作用于 Wave 内**多任务之间**的 allowed_paths 交集。
|
|
161
|
+
> - 拆分对齐「可独立验收的能力边界」而非「工序步骤」——一个能力(实现+接线+单测+登记)通常=一张卡;协议成本与任务卡数线性挂钩,卡片数是税。
|
|
162
|
+
|
|
163
|
+
> **并批默认(B-③,同 Wave 文件正交任务默认并批——摊平 execute 扇出的重复上下文重建,R4-S-F 实证 5 任务 2 批收益)**:
|
|
164
|
+
> - 文件正交(无共享 target_files、无 provides/expects_from 契约链、无 depends_on 依赖)的同 Wave 任务默认并批 2–4 任务/批;批标注写在 Wave 段内独立行(\`> batch: task-01+task-02\`,一段多行即多批),**不替代** \`- task-XX\` 纯 ID 引用行(execute 只按纯 ID 行收任务,漏写 ID 行 = 任务不进 execute);任务数 ≤3 的 Wave 不并批(任何并批都违反下条批数护栏)
|
|
165
|
+
> - 并批后整 Wave 批数 ≥ min(3, 该 Wave 任务数)(N≥3 即至少 3 批——批大小上界由此不变式约束:5 任务 → 2+2+1 三批而非 3+2 两批),保 Wave 在飞子代理数 ≥3、防墙钟回退
|
|
166
|
+
> - 并批不跨风险级(P0 不与 P2 混批,批内风险级不一致时按最高级拆批);plan-postcheck 的并批提示是 warning 级护栏——S-F 型小任务(token 优先于墙钟)可显式接受(在 plan.md 相应 Wave 段注明「接受并批护栏提示」即放行,不阻断)
|
|
167
|
+
|
|
168
|
+
> **执行模式声明(execution_mode,M4——2026-09-23 R8 对撞后缺省翻转:main 直写为缺省)**:
|
|
169
|
+
> - plan.md frontmatter 加 \`execution_mode: main | dispatch\` 键,**缺省不写 = main**(主代理直写);翻转依据=对撞双实证派发税(R7:直写进码 7′ vs 派发 70′;R8:同规模任务 execute 80′(6 子代理+编排)vs 单上下文进码 14′,3.6×——子代理冷启动上下文重建 + 伪并行 + 小任务全额派发开销)
|
|
170
|
+
> - 仅当满足判据才显式声明 \`execution_mode: dispatch\`:**任务真可并行**(Wave 内任务文件正交且墙钟可压缩)× **单任务规模大**(预估 >30min)× **上下文需分片**(单会话将撞压缩线);不满足或拿不准一律缺省 main(误用 main 只损失并行收益,不损失任何防线与审查;误用 dispatch 白付 2-3× 墙钟与 token)
|
|
171
|
+
> - main 下 execute Wave 步渲染直写指引(逐任务:读卡→worktree 内实现→每任务 commit→锚点→review write→下一任务),派发段/子代理工作目录段/并发帽段/推荐分组段全抑制;worktree 隔离/写入守卫/review.json/verify 门禁与 dispatch 完全一致(只换执行宿主,不换防线)
|
|
172
|
+
> - 直写只免「实现」派发(2026-09-23 R9 教训):Stage Review(tier=independent)仍派独立子代理——降级自审损失审查实效(R8 基线实证审查抓过真缺口);测试跑批输出落文件只取摘要——长输出全文进上下文是单会话 token 累积大头(R9 verify 段 13.8M 输入的大头来源)
|
|
173
|
+
|
|
157
174
|
---
|
|
158
175
|
|
|
159
176
|
#### plan_level = none
|
|
@@ -187,6 +204,7 @@ plan_level: none
|
|
|
187
204
|
\`\`\`markdown
|
|
188
205
|
---
|
|
189
206
|
plan_level: light
|
|
207
|
+
execution_mode: main # dispatch=子代理派发(判据见「执行模式声明」;缺省 main 直写——R7/R8 对撞实证)
|
|
190
208
|
---
|
|
191
209
|
|
|
192
210
|
# 轻量计划(Light Plan):<需求简述>
|
|
@@ -226,6 +244,7 @@ light 计划的约束:
|
|
|
226
244
|
\`\`\`markdown
|
|
227
245
|
---
|
|
228
246
|
plan_level: full
|
|
247
|
+
execution_mode: main # dispatch=子代理派发(判据见「执行模式声明」;缺省 main 直写——R7/R8 对撞实证)
|
|
229
248
|
---
|
|
230
249
|
|
|
231
250
|
# 实现计划(Plan)
|
|
@@ -350,7 +369,6 @@ plan.md 审查通过后、进入 execute 前,若 plan_level=full(跨模块/
|
|
|
350
369
|
tier: {REVIEW_TIER}({REVIEW_TIER_REASON})
|
|
351
370
|
- tier=self(ceremony 档 S0/S1):当前 agent 直接执行下方审查清单——S0=CLI 清单核验;S1=CLI 清单核验+定向探针抽查(对最高风险条目定向读源码验证)。轻仪是风险定价的正常形态而非偷懒豁免,清单机械项一条不省
|
|
352
371
|
- tier=independent(ceremony 档 S2/S3):必须用 Agent tool 启动独立的计划审查子代理(独立上下文,不共享你生成 plan 时的分析与倾向),由子代理执行下方审查清单并输出 review.json——S2=单轮;S3=两轮独立评审(视角互补,第二轮聚焦首轮未决项)
|
|
353
|
-
宿主环境无 Agent tool 可用(调用报 Unknown agent / Available agents: none)→ 不卡死(降级兜底,仅评审通道全不可用时):主代理切换为审查者角色自审替代,reviewerNotes 首行记录「降级:环境无子代理可用」,逐条结论附源码锚点(file:line 或 grep/read 证据)补偿独立性。
|
|
354
372
|
- 仪式按 risk 计价,plan_level 仅编排:agent 自报 plan_level=full 而 CLI 判档 S0/S1 时,CLI 强制轻仪执行并在注入文案留审计痕(强制轻仪说明行随 tier 注入)——不因「计划写得完整」进入 independent×2,勿自行升仪对抗定价
|
|
355
373
|
|
|
356
374
|
### 审查清单(读取 plan.md 的 plan_level,逐条核对)
|
|
@@ -363,6 +381,7 @@ ${REVIEW_CHECKLISTS.plan.map((item) => '- [ ] ' + item).join('\n')}
|
|
|
363
381
|
prompt 要点:
|
|
364
382
|
1. **材料包是评审基准面**:checklist 逐条只对包作答;包外文件可定向查证但须列明(禁全量扫读);包不足以作答→cannot_verify+requiredEvidence 列缺件。plan.md 本体可读(审查对象),design.md 只读包内 digest+点名锚(不是全文)。
|
|
365
383
|
2. 执行上方审查清单,每条给 pass/gap/fail + 证据
|
|
384
|
+
> 🚪 **评审豁免(2026-09-26-review-unsupervised-exit)**:若本会话环境**无嵌套派发能力**(无 Agent/Task 类子代理工具)——不产 review.json、**不做自审表演**;改为在变更目录写一行声明文件 review-unsupervised.md(内容含 unsupervised 字样+时间+一句环境说明),Stage Review 门见声明即放行并留痕(声明随归档;Task Review 层已退役 2026-09-26-task-review-retire)。有派发能力时本豁免不适用(真独立评审仍优待)。
|
|
366
385
|
3. 输出 review.json(CLI Stage Review Gate 将硬校验,契约如下 —— schema + 完整示例 + docHash 算法,照抄改值):
|
|
367
386
|
{REVIEW_JSON_CONTRACT}
|
|
368
387
|
4. verdict=fail 时在 reviewerNotes 写明阻断项
|
|
@@ -449,9 +468,12 @@ related_tests: # 可选。当本 task 改动会导致
|
|
|
449
468
|
|
|
450
469
|
⚠️ 生成卡片前先确认 plan.md 已满足(否则下一步 postcheck 会硬拦,导致返工重编号/重分 Wave):
|
|
451
470
|
- **共享文件须分 Wave**:若多个 task 的 allowed_path 含同一文件,plan.md 必须把它们分到不同「## Wave N」(同 Wave 共享文件会被 execute 强制并行,子代理互相覆盖;postcheck 拦同 Wave 共享)
|
|
471
|
+
(注意:该约束只限制 Wave 内**多任务之间**的文件交集——同一张卡携带实现+测试+module-map 多文件合法,勿为规避此约束把测试/接线拆成独立卡,见步骤 2 拆分粒度纪律)
|
|
452
472
|
- **task id 从 1 连续**:task-01、task-02、task-03… 不能跳号或重号(postcheck 校验 id 连续性,gap 会拦)
|
|
453
473
|
这两条是跨 task 全局约束,子代理只写单卡看不到全局——你(主 agent)分派子代理前必须先在 plan.md 里确认 Wave 划分与编号正确。
|
|
454
474
|
|
|
475
|
+
卡片粒度对齐 plan.md/tasks.md 的任务数——plan 未拆出的收尾动作不为仪式另立卡。
|
|
476
|
+
|
|
455
477
|
⚠️ **TaskCard 必备字段(缺一 postcheck 直接阻断):** id、title(英文)、**title_zh(中文标题,必填)**、allowed_paths、goal、implementation、acceptance、verify、constraints。骨架已由 taskcard CLI 预生成(含 title_zh 占位),子代理只需 Edit 填值,不要删除任何必备字段。
|
|
456
478
|
|
|
457
479
|
可选字段 **target_files**(本 task 计划要改动的文件清单;骨架已预置 \`target_files: []\` 占位行,按任务计划 Edit 填值,无明确文件级意图保留 []):✅ 精确仓根相对路径 \`src/foo.js\`、计划新建文件 \`NEW:src/bar.js\`;❌ \`src/**\`(glob)、\`src/dir/\`(目录前缀)、绝对路径。语义区分:target_files=计划要动的具体文件,allowed_paths=权限范围。
|
package/src/stages/quick.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const definition = {
|
|
2
2
|
name: 'quick',
|
|
3
|
-
title: '
|
|
4
|
-
description: '
|
|
3
|
+
title: '快速任务(已退役,仅存量在途会话收尾)',
|
|
4
|
+
description: 'quick 通道已退役(v3.31.0 起拒绝新会话);新工作走轻量变更 flow start',
|
|
5
5
|
auxiliary: true,
|
|
6
6
|
steps: [
|
|
7
7
|
{
|
|
@@ -61,6 +61,7 @@ step 3 --done 审计会把 src 核心文件(CLI / 状态机 / 注入框架等
|
|
|
61
61
|
2. 写代码完成任务
|
|
62
62
|
3. 如涉及逻辑变更,建议写单元测试验证(不强制,纯配置/文档/小改动可跳过);写测试时按下方「测试用例设计」检查
|
|
63
63
|
4. **不要做无谓的全量编译/构建。** 但——跑针对性的单元测试始终允许且鼓励(见 step3);若本任务就是修测试/构建/基础设施本身,跑测试/构建即验收,应当跑,不算"编译"
|
|
64
|
+
5. **长输出跑批纪律(token 减负)**:全量/多文件测试的输出重定向落文件(如 \`> .runtime/test-out.log 2>&1\`),上下文只回看尾部摘要与失败段(\`tail -50\` / \`grep -E "FAIL|✗|Error"\`)——长输出全文进上下文是 token 累积大头(R9 实证单会话 verify 段 13.8M 输入的大头来源)
|
|
64
65
|
|
|
65
66
|
{{include: testcase-design}}
|
|
66
67
|
|
|
@@ -86,6 +87,13 @@ step 3 --done 审计会把 src 核心文件(CLI / 状态机 / 注入框架等
|
|
|
86
87
|
### 📌 收尾确认
|
|
87
88
|
- 本步骤是最后一步,完成后 quick 会话即结束(--change 传递见首步 sessionId 说明)
|
|
88
89
|
|
|
90
|
+
### 📚 收口输出的转达义务(知识可见性契约,2026-09-23)
|
|
91
|
+
\`--done\` 收口输出里若出现以下任一块,**必须在你的最终回复中原文转达用户**(CLI 输出用户不可见,你的转述是用户看到它们的唯一通道):
|
|
92
|
+
- 「📚/⚠️ 知识收件箱」横幅——uncategorized 待审知识清单(标题 + 条数原样带出);
|
|
93
|
+
- 「📚 待归类提议」——本条 quick 的归类建议(归类与否由用户裁);
|
|
94
|
+
- 「📚 quick 资产尾」——蒸馏出的 FR/决策资产及去处。
|
|
95
|
+
未出现则不提及(不得编造)。
|
|
96
|
+
|
|
89
97
|
### ⚠️ 结果摘要模板(必填,CLI 会校验结构)
|
|
90
98
|
|
|
91
99
|
🔧 **推荐四参数形式**(CLI 自动合成结构化模板,杜绝嵌套冒号/缺字段/截断规则踩坑):
|
package/src/stages/verify.js
CHANGED
|
@@ -59,6 +59,8 @@ export const definition = {
|
|
|
59
59
|
name: '加载规范并锚定',
|
|
60
60
|
prompt: `加载规范文件并确认。
|
|
61
61
|
|
|
62
|
+
> 💡 **长会话税提示(2026-09-26-verify-gate-restrictfiles,R18 实证)**:若本会话已跨 brainstorm/plan/execute 多阶段(上下文已重),建议**新开会话跑 verify**(sillyspec run verify --change <名> 续跑,上下文重置)——verify 段轮均成本是 brainstorm 段的 3-4 倍,主因是每轮重读全历史;新会话只带工件进场,同动作成本立降。
|
|
63
|
+
|
|
62
64
|
### 操作
|
|
63
65
|
1. 读取 proposal.md、design.md、tasks.md、requirements.md、plan.md
|
|
64
66
|
2. 如果存在 decisions.md,必须读取并提取所有当前版本 D-xxx@vN 决策 ID
|
|
@@ -73,7 +75,7 @@ export const definition = {
|
|
|
73
75
|
6. 标注每个文件的存在/不存在状态
|
|
74
76
|
|
|
75
77
|
### Execute Evidence 传递检查
|
|
76
|
-
7. 检查 verify-required-evidence.json
|
|
78
|
+
7. 检查 verify-required-evidence.json 是否存在(历史兼容读:该文件由退役前的 execute Task Review Gate 写入,2026-09-26-task-review-retire 起停写——在场则消费,缺席=无 cannot_verify 任务)
|
|
77
79
|
- 路径:变更目录下的 verify-required-evidence.json
|
|
78
80
|
- 文件 schema:\`{ items: [{ task, verdict, evidence: string[] }] }\`——顶层是 \`items\`,每项的 \`evidence\` 是字符串数组(不是 \`requiredEvidence\` 键)
|
|
79
81
|
- 如果存在 → 逐项读取 \`items\`,对每个 cannot_verify 任务逐条核对其 \`evidence\` 数组是否已满足
|
|
@@ -100,7 +102,7 @@ export const definition = {
|
|
|
100
102
|
},
|
|
101
103
|
{
|
|
102
104
|
name: '逐项检查任务',
|
|
103
|
-
prompt: `对照 tasks.md
|
|
105
|
+
prompt: `对照 tasks.md(任务注册表唯一真相)检查每个任务完成状态。勾选由 agent 在任务完成(实现+测试绿+wt-commit)时手动写入(2026-09-26 Task Review 退役起,同 thin 工作单元语义),本阶段只读对照、不改勾。
|
|
104
106
|
|
|
105
107
|
### 勾选状态(CLI 注入,勿手数)
|
|
106
108
|
{TASKS_CHECKBOX}
|