sillyspec 3.32.8 → 3.32.10
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-brainstorm/SKILL.md +2 -2
- package/.claude/skills/sillyspec-execute/SKILL.md +2 -2
- package/.claude/skills/sillyspec-flow/SKILL.md +1 -1
- package/.claude/skills/sillyspec-plan/SKILL.md +2 -2
- package/AGENTS.md +5 -4
- package/assets/command-cards/flow.md +9 -4
- package/package.json +1 -1
- package/src/change-list.js +13 -0
- package/src/commit-guard.js +70 -2
- package/src/commit-suggest.js +45 -4
- package/src/config-schema.js +14 -0
- package/src/cross-repo-reconcile.js +54 -1
- package/src/decision-distill.js +77 -21
- package/src/decision-renumber.js +157 -0
- package/src/deleted-ledger.js +75 -0
- package/src/design-facts.js +5 -3
- package/src/dispatch/backends/local-agent.js +1 -1
- package/src/dispatch/backends/sillyhub-mcp.js +1 -1
- package/src/docs-check.js +30 -15
- package/src/docs-debt.js +3 -2
- package/src/doctor-diagnostics.js +3 -1
- package/src/flow-draft.js +24 -2
- package/src/flow-parity.js +32 -2
- package/src/flow.js +427 -69
- package/src/fr-index.js +15 -1
- package/src/hooks/worktree-guard.js +3 -3
- package/src/index.js +22 -10
- package/src/knowledge-digest.js +23 -6
- package/src/knowledge-graph.js +92 -14
- package/src/knowledge-match.js +34 -9
- package/src/knowledge-stats.js +12 -13
- package/src/knowledge-vector.js +98 -7
- package/src/machine-interface.js +26 -0
- package/src/progress/change-registry.js +7 -0
- package/src/progress.js +67 -0
- package/src/quicklog.js +6 -1
- package/src/redomain.js +27 -0
- package/src/route-hindsight.js +34 -2
- package/src/run/archive-distill.js +3 -0
- package/src/run/command.js +40 -44
- package/src/run/complete-handlers.js +8 -3
- package/src/run/complete.js +129 -63
- package/src/run/gates.js +94 -90
- package/src/run/prompt.js +13 -3
- package/src/run/shared.js +19 -0
- package/src/run/stage.js +1 -1
- package/src/scope-audit.js +2 -2
- package/src/sentinel-assertions.js +43 -1
- package/src/stage-contract.js +26 -3
- package/src/stage-review.js +44 -11
- package/src/stage-templates.js +2 -0
- package/src/stages/brainstorm.js +24 -12
- package/src/stages/execute.js +1 -1
- package/src/stages/explore.js +3 -5
- package/src/stages/knowledge.js +56 -5
- package/src/sync.js +98 -2
- package/src/task-tick.js +12 -0
- package/src/timeline.js +60 -19
- package/src/verify-postcheck.js +83 -6
- package/src/verify-probes.js +61 -18
- package/templates/agents-instruction.md +5 -4
- package/templates/prompts/testcase-design.md +5 -0
- package/src/stages/brainstorm-auto.js +0 -286
package/src/run/complete.js
CHANGED
|
@@ -22,22 +22,19 @@ import { writeAtomicSync } from '../fs-atomic.js'
|
|
|
22
22
|
import { gitQuiet } from '../git-helper.js'
|
|
23
23
|
import { writeCloseTraceArtifacts, projectTraceFaceRows } from '../flow-parity.js'
|
|
24
24
|
import { withFileLock } from '../quicklog.js'
|
|
25
|
-
import { triggerSync, WAIT_MARKER_RE, getStageSteps, formatWaitOptions, resolveRuntimeRoot, getOrCreateMultiRepoContext, resolveChangeDir, writePlatformDocsPointer } from './shared.js'
|
|
25
|
+
import { triggerSync, WAIT_MARKER_RE, getStageSteps, formatWaitOptions, resolveRuntimeRoot, getOrCreateMultiRepoContext, resolveChangeDir, writePlatformDocsPointer, isRetiredAutoBrainstormTable, printRetiredAutoBrainstormRefusal } from './shared.js'
|
|
26
26
|
import { isExplicitReviewWrite, collectWorktreeChangedFiles } from '../task-review.js'
|
|
27
27
|
|
|
28
|
-
// auto
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
async function getStageStepsAutoAware(stageName, cwd, progress, specDir) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (!isAutoTable) return steps
|
|
39
|
-
const { definition: brainstormAutoDef } = await import('../stages/brainstorm-auto.js')
|
|
40
|
-
return brainstormAutoDef.steps
|
|
28
|
+
// 退役自动道守门(2026-10-14-brainstorm-auto-retire):brainstorm-auto 4 步表已随自动道删除。
|
|
29
|
+
// 2026-09-08 双轨互踩 bug 的兼容面(complete 路径感知 auto 表用 auto 定义推进)就此退役——
|
|
30
|
+
// 存量 auto 表变更在此明确拒绝(不静默重种清零进度);--reset/--reopen 走 resetStage 内联
|
|
31
|
+
// 重种主模式表 = 恢复出路。
|
|
32
|
+
async function getStageStepsAutoAware(stageName, cwd, progress, specDir, changeName = null) {
|
|
33
|
+
if (stageName === 'brainstorm' && isRetiredAutoBrainstormTable(progress?.stages?.brainstorm?.steps)) {
|
|
34
|
+
printRetiredAutoBrainstormRefusal(changeName)
|
|
35
|
+
process.exit(2)
|
|
36
|
+
}
|
|
37
|
+
return getStageSteps(stageName, cwd, progress, specDir)
|
|
41
38
|
}
|
|
42
39
|
|
|
43
40
|
import { executeScanPreflight, executeScanPostcheck, computeScanProfile, executeScanDetectProjects, executeScanResumeCheck, executeScanFinalize } from './scan-profile.js'
|
|
@@ -337,7 +334,7 @@ export async function completeStep(pm, progress, stageName, cwd, outputText, inp
|
|
|
337
334
|
}
|
|
338
335
|
|
|
339
336
|
// ── requiresWait 硬门控 ──
|
|
340
|
-
const defStepsForCurrent = await getStageStepsAutoAware(stageName, cwd, progress, platformOpts?.specRoot || null)
|
|
337
|
+
const defStepsForCurrent = await getStageStepsAutoAware(stageName, cwd, progress, platformOpts?.specRoot || null, changeName)
|
|
341
338
|
// def↔DB 一致性守卫(坑 execute-step-table-drift,2026-08-20 实证):runCommand 入口已按名
|
|
342
339
|
// 重播种(ensureStageSteps),但同一命令进程内 plan.md 若再次被改(或绕过 runCommand 直调
|
|
343
340
|
// completeStep 的路径),def 与 DB 步数错位会让 currentIdx 的门控/prompt 施加到错误的步骤上
|
|
@@ -496,25 +493,26 @@ export async function completeStep(pm, progress, stageName, cwd, outputText, inp
|
|
|
496
493
|
.filter((h) => !_hits.some((x) => x.file === h.file && x.id === h.id && (x.change || '') === (h.change || '')))
|
|
497
494
|
_hits.push(..._viaImpact)
|
|
498
495
|
} catch { /* 图构建/键解析 fail-soft——词面命中面不受影响 */ }
|
|
499
|
-
// 已回应不重弹:decisions.md
|
|
500
|
-
//
|
|
496
|
+
// 已回应不重弹:decisions.md 某行同时含「命中 id+域文件名(或命中条目变更名)」(小白鼠
|
|
497
|
+
// 回应形态「unmapped.md D-001@v1」)→ 视为已甄别过,静默。行级共现(2026-10-14-decision-id-p2-hardening
|
|
498
|
+
// 收紧:文件级共现时本变更自身同号 id + 正文常见词 backend/unmapped 撞车会静默吞掉他变更命中)。
|
|
501
499
|
const _answered = _dm
|
|
502
500
|
? (h) => {
|
|
503
501
|
const _dom = String(h.file || '').replace(/^decisions\//, '').replace(/\.md$/, '')
|
|
504
|
-
return _dm.includes(h.id) &&
|
|
502
|
+
return _dm.split(/\r?\n/).some((line) => line.includes(h.id) && (line.includes(_dom) || (h.change && line.includes(h.change))))
|
|
505
503
|
}
|
|
506
504
|
: () => false
|
|
507
505
|
const _echoHits = _hits.filter((h) => !_answered(h))
|
|
508
506
|
const _entries = km.matched ? (km.entries || []) : []
|
|
509
507
|
if (_echoHits.length > 0 || _entries.length > 0) {
|
|
510
|
-
const { deathPathNote } = await import('../knowledge-match.js')
|
|
508
|
+
const { deathPathNote, decisionChangeQualifier } = await import('../knowledge-match.js')
|
|
511
509
|
console.warn(`\n⚠️ [knowledge-gate] 方案/决策知识命中(设计时点防复潮提示,warn 不阻断):`)
|
|
512
510
|
for (const h of _echoHits.slice(0, 5)) {
|
|
513
511
|
const _note = h.status === 'rejected'
|
|
514
512
|
? ` 否决理由:${h.reason || '(未记录)'}`
|
|
515
513
|
: h.deathPath ? ` ⚰️死路注记:${deathPathNote(h.reason)}` : ''
|
|
516
514
|
const _via = h.viaImpact ? ` 🧭impact 可达(键:${h.impactKey})` : ''
|
|
517
|
-
console.warn(` - ${h.id} ${h.title}(${h.file})status=${h.status || '?'}${_note}${_via}`)
|
|
515
|
+
console.warn(` - ${h.id} ${h.title}(${h.file}|${decisionChangeQualifier(h.change)})status=${h.status || '?'}${_note}${_via}`)
|
|
518
516
|
}
|
|
519
517
|
for (const e of _entries.slice(0, 3)) {
|
|
520
518
|
const _base = e.anchor ? `${e.file}#${e.anchor}` : e.file
|
|
@@ -676,40 +674,9 @@ export async function completeStep(pm, progress, stageName, cwd, outputText, inp
|
|
|
676
674
|
// 勾选回归 agent 手动(完成=实现+测试绿+wt-commit 即勾,同 thin 工作单元语义),假勾防线由
|
|
677
675
|
// detectExecuteBatchFinish 内 checkExecuteCodeEvidence 代码证据核验 + verify 测试对账承担。
|
|
678
676
|
if (stageName === 'execute' && changeName) {
|
|
679
|
-
// 自动勾选(full-autopilot-parity,R21 thin
|
|
680
|
-
//
|
|
681
|
-
|
|
682
|
-
try {
|
|
683
|
-
const _tasksMdPath = join(specBase, 'changes', changeName, 'tasks.md')
|
|
684
|
-
if (existsSync(_tasksMdPath)) {
|
|
685
|
-
const _runIdFile = join(resolveRuntimeRoot(platformOpts, specBase), `current-execute-run-id-${changeName}`)
|
|
686
|
-
let _tickCommits = ''
|
|
687
|
-
try {
|
|
688
|
-
if (existsSync(_runIdFile)) {
|
|
689
|
-
_tickCommits = gitQuiet(cwd, ['log', '--format=%B%x1e', 'HEAD~20..HEAD']) || ''
|
|
690
|
-
}
|
|
691
|
-
} catch {}
|
|
692
|
-
if (!_tickCommits) _tickCommits = gitQuiet(cwd, ['log', '--format=%B%x1e', '-20']) || ''
|
|
693
|
-
if (_tickCommits) {
|
|
694
|
-
const _texts = String(_tickCommits).split('\x1e').map(x => x.trim()).filter(Boolean).join('\n')
|
|
695
|
-
const _evidenced = new Set()
|
|
696
|
-
for (const m of _texts.matchAll(/task-(\d{1,2})/gi)) _evidenced.add(String(Number(m[1])).padStart(2, '0'))
|
|
697
|
-
if (_evidenced.size > 0) {
|
|
698
|
-
let _md = readFileSync(_tasksMdPath, 'utf8')
|
|
699
|
-
let _ticked = 0
|
|
700
|
-
for (const nn of _evidenced) {
|
|
701
|
-
const re = new RegExp(`^- \\[ \\](.*task-${nn}[:\\s])`, 'm')
|
|
702
|
-
if (re.test(_md)) { _md = _md.replace(re, `- [x]$1`); _ticked++ }
|
|
703
|
-
}
|
|
704
|
-
if (_ticked > 0) {
|
|
705
|
-
const { writeAtomicSync } = await import('../fs-atomic.js')
|
|
706
|
-
writeAtomicSync(_tasksMdPath, _md)
|
|
707
|
-
console.log(`✅ [自动勾选] ${_ticked} 个任务有提交证据但未勾——已机器代勾(full-autopilot-parity)`)
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
} catch { /* auto-tick fail-soft */ }
|
|
677
|
+
// 自动勾选(full-autopilot-parity,R21 thin 实证迁移):代勾判据/实现见
|
|
678
|
+
// autoTickTasksFromCommitSubjects(subject token × allowed_paths 实现事实锚)。
|
|
679
|
+
await autoTickTasksFromCommitSubjects({ changeName, cwd, specBase, platformOpts })
|
|
713
680
|
const _bf = await detectExecuteBatchFinish({ pm, stageName, changeName, cwd, specBase, platformOpts, steps })
|
|
714
681
|
if (_bf.batched && _bf.aligned > 0) {
|
|
715
682
|
console.log(`\n🚀 execute 批量完成:plan 全勾 + 代码核验通过,一次性补完 ${_bf.aligned} 个剩余 step → 进入阶段完成分支`)
|
|
@@ -1028,7 +995,7 @@ export async function completeStep(pm, progress, stageName, cwd, outputText, inp
|
|
|
1028
995
|
}
|
|
1029
996
|
}
|
|
1030
997
|
|
|
1031
|
-
const defSteps = await getStageStepsAutoAware(stageName, cwd, progress, platformOpts?.specRoot || null)
|
|
998
|
+
const defSteps = await getStageStepsAutoAware(stageName, cwd, progress, platformOpts?.specRoot || null, changeName)
|
|
1032
999
|
console.log(`✅ Step ${currentIdx + 1}/${steps.length} 完成:${steps[currentIdx].name}\n`)
|
|
1033
1000
|
|
|
1034
1001
|
// Workflow post_check(W6 Step6 抽至 complete-handlers.js handleWorkflowPostCheck)
|
|
@@ -1535,8 +1502,10 @@ export function shouldAutoCheckTask(r, endToEnd, ctx = null, changeName = '') {
|
|
|
1535
1502
|
if (isExplicitReviewWrite(r.review)) return true
|
|
1536
1503
|
|
|
1537
1504
|
// W2 task-04 草稿零 diff 守卫(FR-03)
|
|
1538
|
-
//
|
|
1539
|
-
|
|
1505
|
+
// review 为自动草稿时,要求 changedFiles 非空且实测 diff 非空。ctx 缺席(直写/主仓形态
|
|
1506
|
+
// buildDraftContext 返 null)同样保守不勾——原「ctx=null 直接放行」使 cannot_verify 草稿
|
|
1507
|
+
// 无任何实现核验即被代勾(坑 wt-commit-auto-tick-unimplemented-tasks 连带,2026-10-15)。
|
|
1508
|
+
if (r.review?.reviewerNotes && r.review.reviewerNotes.includes('auto-generated draft')) {
|
|
1540
1509
|
const taskId = r.review.task || 'task-NN'
|
|
1541
1510
|
const unlockCmd = `sillyspec review write${changeName ? ` --change ${changeName}` : ' --change <变更名>'} --task ${taskId} --spec pass --quality pass --force --changed-files <文件>`
|
|
1542
1511
|
const changedFiles = r.review.changedFiles || []
|
|
@@ -1544,8 +1513,8 @@ export function shouldAutoCheckTask(r, endToEnd, ctx = null, changeName = '') {
|
|
|
1544
1513
|
console.warn(`⚠️ 草稿 review changedFiles 为空,跳过自动勾选(task=${taskId})——解锁:${unlockCmd}(纯验证任务用 --changed-files "" + --evidence "<验证说明>")`)
|
|
1545
1514
|
return false
|
|
1546
1515
|
}
|
|
1547
|
-
if (!ctx.gitDir || !ctx.base || !ctx.head) {
|
|
1548
|
-
console.warn(`⚠️ ctx
|
|
1516
|
+
if (!ctx || !ctx.gitDir || !ctx.base || !ctx.head) {
|
|
1517
|
+
console.warn(`⚠️ ctx 缺席或不完整(gitDir=${ctx && ctx.gitDir}, base=${ctx && ctx.base}, head=${ctx && ctx.head}),跳过草稿 diff 校验,保守不勾(task=${taskId})——解锁:${unlockCmd}`)
|
|
1549
1518
|
return false
|
|
1550
1519
|
}
|
|
1551
1520
|
// 预取路径:一次全量 diff 文件集内存归属(autoCheckPlanFromReviews/批量路径已 prefetch)
|
|
@@ -1575,6 +1544,86 @@ export function shouldAutoCheckTask(r, endToEnd, ctx = null, changeName = '') {
|
|
|
1575
1544
|
|
|
1576
1545
|
return true
|
|
1577
1546
|
}
|
|
1547
|
+
/**
|
|
1548
|
+
* execute --done 的 full-autopilot 代勾(独立函数供测试直驱;completeStep execute 分支调用)。
|
|
1549
|
+
*
|
|
1550
|
+
* 实现事实锚(坑 wt-commit-auto-tick-unimplemented-tasks,2026-10-15 消费仓两次实证:原按
|
|
1551
|
+
* %B 提交全文提 token——task-01 提交正文提及 task-02/03/04 即全被误勾,批量完成判定与续跑
|
|
1552
|
+
* 跳过双失真):证据源收敛为提交 subject 行(%s)的 task-NN token,且任务卡 allowed_paths
|
|
1553
|
+
* 在场时要求「提及该 task 的提交触及面 × allowed_paths」交叠(parseAllowedPaths +
|
|
1554
|
+
* pathMatches,与草稿归属同口径);无卡面/无 allowed_paths 退化为仅 subject token。无锚不勾。
|
|
1555
|
+
* fail-soft(异常零行为)。
|
|
1556
|
+
* @param {{ changeName: string, cwd: string, specBase: string, platformOpts?: object }} opts
|
|
1557
|
+
*/
|
|
1558
|
+
export async function autoTickTasksFromCommitSubjects({ changeName, cwd, specBase, platformOpts = {} }) {
|
|
1559
|
+
try {
|
|
1560
|
+
const _tasksMdPath = join(specBase, 'changes', changeName, 'tasks.md')
|
|
1561
|
+
if (!existsSync(_tasksMdPath)) return { ticked: 0, skippedNoAnchor: 0 }
|
|
1562
|
+
const _runIdFile = join(resolveRuntimeRoot(platformOpts, specBase), `current-execute-run-id-${changeName}`)
|
|
1563
|
+
let _tickLog = ''
|
|
1564
|
+
try {
|
|
1565
|
+
if (existsSync(_runIdFile)) {
|
|
1566
|
+
_tickLog = gitQuiet(cwd, ['log', '--name-only', '--format=%x1e%s', 'HEAD~20..HEAD'], { trim: false }) || ''
|
|
1567
|
+
}
|
|
1568
|
+
} catch {}
|
|
1569
|
+
if (!_tickLog) _tickLog = gitQuiet(cwd, ['log', '--name-only', '--format=%x1e%s', '-20'], { trim: false }) || ''
|
|
1570
|
+
if (_tickLog) {
|
|
1571
|
+
// 逐提交解析:首行 = subject(token 证据面——正文散文提及不是实现证据),其余行 = 该提交触及文件
|
|
1572
|
+
const _evidenced = new Map()
|
|
1573
|
+
for (const _chunk of String(_tickLog).split('\x1e')) {
|
|
1574
|
+
const _lines = String(_chunk || '').split('\n').map(x => x.trim()).filter(Boolean)
|
|
1575
|
+
if (_lines.length === 0) continue
|
|
1576
|
+
for (const m of _lines[0].matchAll(/task-(\d{1,2})/gi)) {
|
|
1577
|
+
const nn = String(Number(m[1])).padStart(2, '0')
|
|
1578
|
+
if (!_evidenced.has(nn)) _evidenced.set(nn, new Set())
|
|
1579
|
+
for (const f of _lines.slice(1)) _evidenced.get(nn).add(f)
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
if (_evidenced.size > 0) {
|
|
1583
|
+
// 任务卡 allowed_paths 索引(卡面读取失败 → 无卡面口径:仅 subject token)
|
|
1584
|
+
const _allowedByTask = new Map()
|
|
1585
|
+
let _pathMatches = null
|
|
1586
|
+
try {
|
|
1587
|
+
const { parseAllowedPaths } = await import('../stages/plan-postcheck.js')
|
|
1588
|
+
;({ pathMatches: _pathMatches } = await import('../change-list.js'))
|
|
1589
|
+
const _tasksDir = join(specBase, 'changes', changeName, 'tasks')
|
|
1590
|
+
for (const nn of _evidenced.keys()) {
|
|
1591
|
+
const _cardPath = join(_tasksDir, `task-${nn}.md`)
|
|
1592
|
+
if (existsSync(_cardPath)) {
|
|
1593
|
+
const _paths = parseAllowedPaths(readFileSync(_cardPath, 'utf8'))
|
|
1594
|
+
.map(p => String(p).replace(/^NEW:\s*/, '').trim()).filter(Boolean)
|
|
1595
|
+
if (_paths.length > 0) _allowedByTask.set(nn, _paths)
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
} catch { /* 卡面读取失败 → 无锚退化为仅 subject token(与无卡面同口径) */ }
|
|
1599
|
+
let _md = readFileSync(_tasksMdPath, 'utf8')
|
|
1600
|
+
let _ticked = 0
|
|
1601
|
+
let _skippedNoAnchor = 0
|
|
1602
|
+
for (const [nn, _files] of _evidenced) {
|
|
1603
|
+
const _allowed = _allowedByTask.get(nn)
|
|
1604
|
+
if (_allowed && _pathMatches && ![..._files].some(f => _allowed.some(p => _pathMatches(f, p)))) {
|
|
1605
|
+
_skippedNoAnchor++
|
|
1606
|
+
continue // subject 提及但触及面与声明面零交叠——无实现事实锚不勾
|
|
1607
|
+
}
|
|
1608
|
+
const re = new RegExp(`^- \\[ \\](.*task-${nn}[:\\s])`, 'm')
|
|
1609
|
+
if (re.test(_md)) { _md = _md.replace(re, `- [x]$1`); _ticked++ }
|
|
1610
|
+
}
|
|
1611
|
+
if (_ticked > 0) {
|
|
1612
|
+
const { writeAtomicSync } = await import('../fs-atomic.js')
|
|
1613
|
+
writeAtomicSync(_tasksMdPath, _md)
|
|
1614
|
+
console.log(`✅ [自动勾选] ${_ticked} 个任务有实现证据(提交 subject token${_allowedByTask.size > 0 ? ' × allowed_paths 交叠' : ''})但未勾——已机器代勾(full-autopilot-parity)`)
|
|
1615
|
+
}
|
|
1616
|
+
if (_skippedNoAnchor > 0) {
|
|
1617
|
+
console.log(`ℹ️ [自动勾选] ${_skippedNoAnchor} 个 task 号有 subject token 但触及面与 allowed_paths 零交叠——不代勾(无实现事实锚),完成时请人工核对勾选`)
|
|
1618
|
+
}
|
|
1619
|
+
return { ticked: _ticked, skippedNoAnchor: _skippedNoAnchor }
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
return { ticked: 0, skippedNoAnchor: 0 }
|
|
1623
|
+
} catch { /* auto-tick fail-soft */ }
|
|
1624
|
+
return { ticked: 0, skippedNoAnchor: 0 }
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1578
1627
|
export async function autoCheckPlanFromReviews({ stageName, changeName, cwd, platformOpts }) {
|
|
1579
1628
|
if (stageName !== 'execute' || !changeName) {
|
|
1580
1629
|
return { autoChecked: false, checkedCount: 0, skippedCount: 0 }
|
|
@@ -1716,7 +1765,7 @@ export function detectVerifyBatchFinish({ changeName, specBase, steps }) {
|
|
|
1716
1765
|
}
|
|
1717
1766
|
}
|
|
1718
1767
|
|
|
1719
|
-
async function detectExecuteBatchFinish({ pm, stageName, changeName, cwd, specBase, platformOpts, steps }) { if (stageName !== 'execute' || !changeName) return { batched: false, aligned: 0 }
|
|
1768
|
+
export async function detectExecuteBatchFinish({ pm, stageName, changeName, cwd, specBase, platformOpts, steps }) { if (stageName !== 'execute' || !changeName) return { batched: false, aligned: 0 }
|
|
1720
1769
|
try {
|
|
1721
1770
|
const changeDir = join(specBase, 'changes', changeName)
|
|
1722
1771
|
const { total: planTotal, checked: planChecked } = pm.readPlanCheckboxStatus(changeDir)
|
|
@@ -1750,6 +1799,23 @@ async function detectExecuteBatchFinish({ pm, stageName, changeName, cwd, specBa
|
|
|
1750
1799
|
// → 每个 task 进 blockedTasks → 批量完成静默失效
|
|
1751
1800
|
const runtimeRoot = resolveRuntimeRoot(platformOpts, specBase)
|
|
1752
1801
|
const runIdFile = join(runtimeRoot, `current-execute-run-id-${changeName}`)
|
|
1802
|
+
// ── 缺席草稿自动补齐(坑 task-review-retired-archive-mismatch,2026-10-15 归档门口实证)──
|
|
1803
|
+
// Task Review 层退役(2026-09-26)后 execute 批量完成不再产 per-task review.json,但本函数
|
|
1804
|
+
// 下方复核循环与 archive step2 完成度判定(真相源=review.json verdict)仍以它在场为前提——
|
|
1805
|
+
// 正常走完五阶段的变更到归档门口必然全量「review.json 缺失」。退役时 generateTaskReviewDrafts
|
|
1806
|
+
// 只摘了调用点(函数本体与 backfill-reviews 手动通道都在):此处对缺失面重新接线——机械
|
|
1807
|
+
// 幂等(已存在不覆盖人工/子代理 verdict),失败 fail-open 不阻断批量(最坏退回修复前形态,
|
|
1808
|
+
// 下方复核循环照旧拦)。挂在 runId marker 读取之前:run 缺失时生成器建 run + 写 marker,
|
|
1809
|
+
// 下方循环一次读盘即见。
|
|
1810
|
+
try {
|
|
1811
|
+
const { generateTaskReviewDrafts } = await import('../task-review.js')
|
|
1812
|
+
const draftOut = await generateTaskReviewDrafts({ changeName, cwd, platformOpts })
|
|
1813
|
+
if (draftOut && draftOut.generated > 0) {
|
|
1814
|
+
console.log(`📝 [批量完成前置] 自动补 ${draftOut.generated} 份 per-task review 草稿(机械幂等;archive 完成度真相源在场——草稿 verdict=cannot_verify 仍由 verify 兑证/人工复核升级,summarizeTaskCompletion 单列可见)`)
|
|
1815
|
+
}
|
|
1816
|
+
} catch (e) {
|
|
1817
|
+
console.warn(`⚠️ per-task 草稿自动补齐异常(fail-open,批量完成继续走既有复核循环): ${(e && e.message) || e}`)
|
|
1818
|
+
}
|
|
1753
1819
|
const blockedTasks = []
|
|
1754
1820
|
const { readReview, isValidExecuteRunId } = await import('../task-review.js')
|
|
1755
1821
|
// runId marker 对全部 task 恒定:循环外一次读盘(原实现逐 task 重复 readFileSync 同一文件)
|
|
@@ -1888,7 +1954,7 @@ export async function waitStep(pm, progress, stageName, cwd, outputText, waitRea
|
|
|
1888
1954
|
|
|
1889
1955
|
// maxWaitRounds 硬上限:达到后拒绝继续 --wait
|
|
1890
1956
|
const currentStep = stageData.steps[currentIdx]
|
|
1891
|
-
const defSteps = await getStageStepsAutoAware(stageName, cwd, progress, platformOpts?.specRoot || null)
|
|
1957
|
+
const defSteps = await getStageStepsAutoAware(stageName, cwd, progress, platformOpts?.specRoot || null, changeName)
|
|
1892
1958
|
const stepDef = defSteps?.[currentIdx] || {}
|
|
1893
1959
|
const maxWaitRounds = currentStep.maxWaitRounds ?? stepDef.maxWaitRounds
|
|
1894
1960
|
const currentWaitRound = currentStep.waitRound || 0
|
|
@@ -2038,7 +2104,7 @@ export async function continueStep(pm, progress, stageName, cwd, answer, options
|
|
|
2038
2104
|
} else {
|
|
2039
2105
|
currentIdx = waitingSteps[0].idx
|
|
2040
2106
|
}
|
|
2041
|
-
const defSteps = await getStageStepsAutoAware(stageName, cwd, progress, platformOpts?.specRoot || null)
|
|
2107
|
+
const defSteps = await getStageStepsAutoAware(stageName, cwd, progress, platformOpts?.specRoot || null, changeName)
|
|
2042
2108
|
const currentStepDef = defSteps?.[currentIdx] || {}
|
|
2043
2109
|
const currentStep = stageData.steps[currentIdx]
|
|
2044
2110
|
const isRepeatableWait = currentStepDef.repeatableWait === true || currentStep.repeatableWait === true
|
|
@@ -2247,7 +2313,7 @@ export async function skipStep(pm, progress, stageName, cwd, changeName, platfor
|
|
|
2247
2313
|
console.log(`⚠️ Step "${steps[currentIdx].name}" 此前被门控阻断(blocked),本次 --skip 将跳过该步——若门控条件已修复,更宜直接 --done 推进。`)
|
|
2248
2314
|
}
|
|
2249
2315
|
|
|
2250
|
-
const defSteps = await getStageStepsAutoAware(stageName, cwd, progress, platformOpts?.specRoot || null)
|
|
2316
|
+
const defSteps = await getStageStepsAutoAware(stageName, cwd, progress, platformOpts?.specRoot || null, changeName)
|
|
2251
2317
|
const stepDef = defSteps ? defSteps[currentIdx] : null
|
|
2252
2318
|
if (stepDef && !stepDef.optional) {
|
|
2253
2319
|
console.error(`❌ 步骤 "${steps[currentIdx].name}" 不可跳过`)
|
package/src/run/gates.js
CHANGED
|
@@ -224,7 +224,7 @@ export function warnMissingUiPrototype(stageName, changeName, currentStepName, s
|
|
|
224
224
|
const design = readFileSync(designPath, 'utf8')
|
|
225
225
|
const feExts = [...new Set((design.match(/\.(vue|svelte|astro|tsx|jsx|css|scss|sass|less|styl|html)\b/gi) || []).map(s => s.replace('.', '').toLowerCase()))]
|
|
226
226
|
if (feExts.length === 0) return
|
|
227
|
-
console.warn(` ⚠️ design.md 命中前端文件类型(${feExts.join('/')})但变更目录无 prototype-*.html——轻微样式改动可忽略;布局/交互流程级变化应补原型(sillyspec run brainstorm --reopen 回到「分段展示设计」,或直接让 agent
|
|
227
|
+
console.warn(` ⚠️ design.md 命中前端文件类型(${feExts.join('/')})但变更目录无 prototype-*.html——轻微样式改动可忽略;布局/交互流程级变化应补原型(sillyspec run brainstorm --reopen 回到「分段展示设计」,或直接让 agent 补生成;已有可复跑 HTML 原型则登记复用:复制进变更目录并命名 prototype-<名称>.html,execute 按此命名注入)`)
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
/**
|
|
@@ -958,6 +958,99 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
958
958
|
writeVerifyGatePointer({ runtimeRoot: reconcileRuntimeRoot, changeName, blocked: true, note: 'target_files ②类(missing_declared)阻断' })
|
|
959
959
|
docGateFailures.push({ type: 'gate_rollback', detail: 'verify-contract', label: 'target_files 对账阻断(②类「声明未做」——计划落空,明细见上)' })
|
|
960
960
|
}
|
|
961
|
+
// ── 探针一致性 / draft 篡改门禁 / ceremony 双跑三门(坑 verify-close-gate-staggered-exposure,
|
|
962
|
+
// 2026-10-15 消费仓四轮 --done 实证:三门原在实测门之后逐个 fail-fast——每轮先付 195s
|
|
963
|
+
// 实测再见一个新文档问题,四轮纯往返约 20 分钟。三门数据面(facts 已于 runValidators 前
|
|
964
|
+
// backfill、verify-result.md 正文/sidecar、ceremony tier 文件、git diff)与实测结果零数据
|
|
965
|
+
// 依赖——照 required-evidence/reconcile 先例前移并入 docGateFailures 收集(R16 聚合一次
|
|
966
|
+
// 全列),失败统一在检查点 A 回滚(进度不丢,修复后重跑 --done)。──
|
|
967
|
+
// 探针一致性(task-03 / ir-stage-p3b D-002@v1 方案A):「#### 探针 N」机械预填段长在 agent
|
|
968
|
+
// 可编辑的正文里,其防篡改基准是正文锚点而非 verify-facts.json(删底稿绕不过防护)。
|
|
969
|
+
// checkProbeConsistency 重跑只读探针与正文锚点对账,分级(D-003):probe1/probe6 不符 =
|
|
970
|
+
// ERROR → 收集阻断(正文预填段疑似被篡改);probe3/probe5 环境敏感维度 + R-06 HEAD 前进
|
|
971
|
+
// 降级 = WARNING → 放行告警(drift);skipped/degraded → 放行提示。
|
|
972
|
+
// 接线形态照 P3a reconcile 先例:envelope + print(ERROR 时 console.error 逐条 mismatch +
|
|
973
|
+
// supportedFixes)+ verify-runs 落盘 + docGateFailures。
|
|
974
|
+
const { checkProbeConsistency } = await import('../verify-postcheck.js') // isStrictChange 复用本块 reconcile 段既有 import
|
|
975
|
+
const probeCheck = checkProbeConsistency({
|
|
976
|
+
// 四参与 reconcile 同源取值:cwd/specBase/changeName 原样透传;runtimeRoot 复用上方
|
|
977
|
+
// reconcileRuntimeRoot(resolveRuntimeRoot(platformOpts, specBase))——checkProbeConsistency
|
|
978
|
+
// 的锚点产物实际都在 changeDir,此参为统一传参签名对齐预留,口径一处定义不二算
|
|
979
|
+
cwd, specBase, changeName,
|
|
980
|
+
runtimeRoot: reconcileRuntimeRoot,
|
|
981
|
+
// IR 严格档(2026-09-07-ir-hardening D-002@v1):created_at ≥ IR_STRICT_SINCE 的变更
|
|
982
|
+
// 探针子节全缺 = ERROR(存量 skip 语义零变化);fail-open(读不到 → false 落豁免)
|
|
983
|
+
strictMode: isStrictChange({ pm, cwd, changeName }),
|
|
984
|
+
})
|
|
985
|
+
const probeEnvelope = buildProbeConsistencyEnvelope(probeCheck)
|
|
986
|
+
const probeBlocked = printProbeConsistencyCheck(probeCheck, probeEnvelope)
|
|
987
|
+
// 探针7 covered 证据锚点 advisory(坑 probe7-covered-anchor-missing,2026-09-15 复盘:7 行
|
|
988
|
+
// covered 证据缺 file:line 被审查打回——预填说明已载明口径,机器在 --done 时点再拦一道,
|
|
989
|
+
// 省一轮人工往返)。advisory 不阻断;独立模块零环(verify-probes 是多会话冲突面,不碰)。
|
|
990
|
+
try {
|
|
991
|
+
const { checkProbe7AnchorCoverage } = await import('../probe7-anchor-check.js')
|
|
992
|
+
const p7ReportPath = join(specBase, 'changes', changeName, 'verify-result.md')
|
|
993
|
+
if (existsSync(p7ReportPath)) {
|
|
994
|
+
const p7Anchor = checkProbe7AnchorCoverage(readFileSync(p7ReportPath, 'utf8'))
|
|
995
|
+
if (p7Anchor.applicable && p7Anchor.missingAnchors.length > 0) {
|
|
996
|
+
console.warn(`\n⚠️ 探针7 锚点校验:${p7Anchor.missingAnchors.length}/${p7Anchor.coveredRows} 行判定为 covered 但证据列缺 file:line / \`.test.\` 文件名 / 反引号路径锚点(advisory 不阻断,审查会要求回补):`)
|
|
997
|
+
for (const m of p7Anchor.missingAnchors.slice(0, 10)) {
|
|
998
|
+
console.warn(` - ${m.task}:${m.acceptance || '(空 acceptance)'}——证据当前为「${m.evidence || '(空)'}」,应给 \`测试文件路径:行号\` 首命中锚点、\`.test.\` 测试文件名或反引号包裹的路径/测试名(行号可省)`)
|
|
999
|
+
}
|
|
1000
|
+
if (p7Anchor.missingAnchors.length > 10) console.warn(` …还有 ${p7Anchor.missingAnchors.length - 10} 行`)
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
} catch { /* 锚点校验异常 fail-soft 不影响 gate */ }
|
|
1004
|
+
// 结果落盘(对齐 writeReconcileRunResult 先例):独立文件 probe-consistency-result.json——
|
|
1005
|
+
// 与 reconcile-result.json 同 gate 运行同目录组织(verify-runs/<ts>/),但不并入后者:
|
|
1006
|
+
// reconcile-result.json 的 schema 已被 P3a 消费方锁定,塞探针字段(读改写/扩参)只会引入
|
|
1007
|
+
// 两检查间耦合;各写各的回执、按文件名订阅更干净。fail-soft 不影响 gate 判定
|
|
1008
|
+
writeProbeConsistencyRunResult({ runtimeRoot: reconcileRuntimeRoot, changeName, envelope: probeEnvelope, result: probeCheck })
|
|
1009
|
+
if (probeBlocked) {
|
|
1010
|
+
writeVerifyGatePointer({ runtimeRoot: reconcileRuntimeRoot, changeName, blocked: true, note: '探针一致性抽查 error 阻断' })
|
|
1011
|
+
docGateFailures.push({ type: 'gate_rollback', detail: 'verify-contract', label: '探针一致性抽查 error 阻断(明细见上)' })
|
|
1012
|
+
}
|
|
1013
|
+
// ── verify 填槽制篡改门禁(r5l 方案3 / 评审护栏#2 承重件,v2 件二)──
|
|
1014
|
+
// verify-probes --init --draft 生成的机器预填段(任务完成度/风险等级/测试结果/决策追踪)带
|
|
1015
|
+
// sha256 指纹标记 + .runtime sidecar 台账;--done 对账:标记被删 / 内容哈希失配 → 收集阻断
|
|
1016
|
+
// (heredoc 整份重写的典型形态——Write 可用与否均免疫,机制修法不做生成侧劝说)。修改机器
|
|
1017
|
+
// 段唯一通道 verify-probes --amend-draft(重锚 + amendment 审计)。无 sidecar(未用 draft /
|
|
1018
|
+
// 存量变更)→ not-applicable 零红;门禁自身异常 fail-soft(对齐探针抽查 degraded 语义)。
|
|
1019
|
+
try {
|
|
1020
|
+
const { checkDraftIntegrity } = await import('../verify-draft.js')
|
|
1021
|
+
const draftCheck = checkDraftIntegrity({
|
|
1022
|
+
mdPath: join(specBase, 'changes', changeName, 'verify-result.md'),
|
|
1023
|
+
changeName, runtimeRoot: reconcileRuntimeRoot,
|
|
1024
|
+
})
|
|
1025
|
+
if (draftCheck.applicable && !draftCheck.ok) {
|
|
1026
|
+
console.error(`\n❌ verify-result.md 机器预填段篡改门禁:${draftCheck.violations.length} 处失配——draft 预填段被整份重写或标记被删/被手工重锚。`)
|
|
1027
|
+
for (const v of draftCheck.violations) console.error(` - ${v}`)
|
|
1028
|
+
console.error(' 修复:还原机器段原文(AGENT 槽——结论枚举/移交项/审查叙述——不受本门限制);确要修改机器段,跑 sillyspec verify-probes --change ' + changeName + ' --amend-draft 留痕重锚后重新 --done。')
|
|
1029
|
+
writeVerifyGatePointer({ runtimeRoot: reconcileRuntimeRoot, changeName, blocked: true, note: 'draft 篡改门禁阻断' })
|
|
1030
|
+
docGateFailures.push({ type: 'gate_rollback', detail: 'verify-contract', label: 'verify-result.md 机器预填段篡改门禁(明细见上)' })
|
|
1031
|
+
}
|
|
1032
|
+
} catch { /* 门禁异常 fail-soft(不是篡改,对齐探针 degraded 放行) */ }
|
|
1033
|
+
// ── ceremony 双跑收口·第一出口(2026-09-18-ceremony-risk-pricing task-04 / FR-03 / D-003;
|
|
1034
|
+
// execute 评审 FAIL 项补接线):声明档(.runtime/ceremony-tier-<change>.json 开跑定价)vs
|
|
1035
|
+
// 实际 diff 重跑 blast+span 的事实档,mismatch(severity=error,懒 agent 低报)汇入收集
|
|
1036
|
+
// 硬 flag 面(照 reconcile/probe 先例检查点 A 统一回滚,修复后重跑 --done 进度不丢)。
|
|
1037
|
+
// 检查/记账/种子收敛在 runCeremonyDualRunCheck 单点(archive 出口 complete-handlers.js
|
|
1038
|
+
// runArchiveCeremonyDualRunExit 同款消费);skipped(档位文件缺失=存量变更零红门禁)/
|
|
1039
|
+
// degraded(git 不可用降级)零噪音放行;整体 fail-soft——检查自身异常降级放行(异常
|
|
1040
|
+
// 不是低报,对齐 archive 出口同款处置)。specBase/runtimeRoot 与四邻检查同源。──
|
|
1041
|
+
try {
|
|
1042
|
+
const { runCeremonyDualRunCheck, printCeremonyDualRunCheck } = await import('../verify-postcheck.js')
|
|
1043
|
+
const ceremonyCheck = await runCeremonyDualRunCheck({
|
|
1044
|
+
cwd, specBase, changeName,
|
|
1045
|
+
runtimeRoot: reconcileRuntimeRoot,
|
|
1046
|
+
})
|
|
1047
|
+
printCeremonyDualRunCheck(ceremonyCheck)
|
|
1048
|
+
if (ceremonyCheck.status === 'mismatch' && ceremonyCheck.severity === 'error') {
|
|
1049
|
+
docGateFailures.push({ type: 'gate_rollback', detail: 'verify-contract', label: 'ceremony 双跑 mismatch(声明档低报,明细见上)' })
|
|
1050
|
+
}
|
|
1051
|
+
} catch (e) {
|
|
1052
|
+
console.warn(`⚠️ Ceremony 双跑对账异常降级放行(不阻断 verify,fail-soft): ${(e && e.message) || e}`)
|
|
1053
|
+
}
|
|
961
1054
|
}
|
|
962
1055
|
// ── 收集模式检查点 A(verify):纯文档门全清后才进入实测门(test/lint/parity/probe 保持
|
|
963
1056
|
// fail-fast 不变)。brainstorm/plan/execute 不走本检查点(其实测面为空,检查点 B 收口)。──
|
|
@@ -1351,95 +1444,6 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
1351
1444
|
const { runVerifyDeletionCheck, printVerifyDeletionCheck } = await import('../verify-postcheck.js')
|
|
1352
1445
|
const deletionCheck = runVerifyDeletionCheck({ cwd, specBase, changeName })
|
|
1353
1446
|
printVerifyDeletionCheck(deletionCheck)
|
|
1354
|
-
// ── verify-result.md 探针预填段一致性抽查(task-03 / ir-stage-p3b D-002@v1 方案A)──
|
|
1355
|
-
// 「#### 探针 N」机械预填段(verify-probes --init 生成)长在 agent 可编辑的正文里,其防篡改
|
|
1356
|
-
// 基准是正文锚点而非 verify-facts.json(删底稿绕不过防护)。checkProbeConsistency 重跑只读
|
|
1357
|
-
// 探针与正文锚点对账,分级(D-003):probe1/probe6 不符 = ERROR → 阻断回滚(正文预填段疑似
|
|
1358
|
-
// 被篡改);probe3/probe5 环境敏感维度 + R-06 HEAD 前进降级 = WARNING → 放行告警(drift);
|
|
1359
|
-
// skipped(存量旧格式报告/无 changeName)/degraded(重跑异常 fail-soft)→ 放行提示。
|
|
1360
|
-
// 接线形态照上方 P3a reconcile 先例:envelope + print(ERROR 时 console.error 逐条 mismatch +
|
|
1361
|
-
// supportedFixes)+ verify-runs 落盘 + rollbackCompletionAndReturn。
|
|
1362
|
-
const { checkProbeConsistency, isStrictChange } = await import('../verify-postcheck.js')
|
|
1363
|
-
const probeCheck = checkProbeConsistency({
|
|
1364
|
-
// 四参与 reconcile 同源取值:cwd/specBase/changeName 原样透传;runtimeRoot 复用上方
|
|
1365
|
-
// reconcileRuntimeRoot(resolveRuntimeRoot(platformOpts, specBase))——checkProbeConsistency
|
|
1366
|
-
// 的锚点产物实际都在 changeDir,此参为统一传参签名对齐预留,口径一处定义不二算
|
|
1367
|
-
cwd, specBase, changeName,
|
|
1368
|
-
runtimeRoot: reconcileRuntimeRoot,
|
|
1369
|
-
// IR 严格档(2026-09-07-ir-hardening D-002@v1):created_at ≥ IR_STRICT_SINCE 的变更
|
|
1370
|
-
// 探针子节全缺 = ERROR(存量 skip 语义零变化);fail-open(读不到 → false 落豁免)
|
|
1371
|
-
strictMode: isStrictChange({ pm, cwd, changeName }),
|
|
1372
|
-
})
|
|
1373
|
-
const probeEnvelope = buildProbeConsistencyEnvelope(probeCheck)
|
|
1374
|
-
const probeBlocked = printProbeConsistencyCheck(probeCheck, probeEnvelope)
|
|
1375
|
-
// 探针7 covered 证据锚点 advisory(坑 probe7-covered-anchor-missing,2026-09-15 复盘:7 行
|
|
1376
|
-
// covered 证据缺 file:line 被审查打回——预填说明已载明口径,机器在 --done 时点再拦一道,
|
|
1377
|
-
// 省一轮人工往返)。advisory 不阻断;独立模块零环(verify-probes 是多会话冲突面,不碰)。
|
|
1378
|
-
try {
|
|
1379
|
-
const { checkProbe7AnchorCoverage } = await import('../probe7-anchor-check.js')
|
|
1380
|
-
const p7ReportPath = join(specBase, 'changes', changeName, 'verify-result.md')
|
|
1381
|
-
if (existsSync(p7ReportPath)) {
|
|
1382
|
-
const p7Anchor = checkProbe7AnchorCoverage(readFileSync(p7ReportPath, 'utf8'))
|
|
1383
|
-
if (p7Anchor.applicable && p7Anchor.missingAnchors.length > 0) {
|
|
1384
|
-
console.warn(`\n⚠️ 探针7 锚点校验:${p7Anchor.missingAnchors.length}/${p7Anchor.coveredRows} 行判定为 covered 但证据列缺 file:line / \`.test.\` 文件名 / 反引号路径锚点(advisory 不阻断,审查会要求回补):`)
|
|
1385
|
-
for (const m of p7Anchor.missingAnchors.slice(0, 10)) {
|
|
1386
|
-
console.warn(` - ${m.task}:${m.acceptance || '(空 acceptance)'}——证据当前为「${m.evidence || '(空)'}」,应给 \`测试文件路径:行号\` 首命中锚点、\`.test.\` 测试文件名或反引号包裹的路径/测试名(行号可省)`)
|
|
1387
|
-
}
|
|
1388
|
-
if (p7Anchor.missingAnchors.length > 10) console.warn(` …还有 ${p7Anchor.missingAnchors.length - 10} 行`)
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
|
-
} catch { /* 锚点校验异常 fail-soft 不影响 gate */ }
|
|
1392
|
-
// 结果落盘(对齐 writeReconcileRunResult 先例):独立文件 probe-consistency-result.json——
|
|
1393
|
-
// 与 reconcile-result.json 同 gate 运行同目录组织(verify-runs/<ts>/),但不并入后者:
|
|
1394
|
-
// reconcile-result.json 的 schema 已被 P3a 消费方锁定,塞探针字段(读改写/扩参)只会引入
|
|
1395
|
-
// 两检查间耦合;各写各的回执、按文件名订阅更干净。fail-soft 不影响 gate 判定
|
|
1396
|
-
writeProbeConsistencyRunResult({ runtimeRoot: reconcileRuntimeRoot, changeName, envelope: probeEnvelope, result: probeCheck })
|
|
1397
|
-
if (probeBlocked) {
|
|
1398
|
-
writeVerifyGatePointer({ runtimeRoot: reconcileRuntimeRoot, changeName, blocked: true, note: '探针一致性抽查 error 阻断' })
|
|
1399
|
-
return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts, { type: 'gate_rollback', detail: 'verify-contract' })
|
|
1400
|
-
}
|
|
1401
|
-
// ── verify 填槽制篡改门禁(r5l 方案3 / 评审护栏#2 承重件,v2 件二)──
|
|
1402
|
-
// verify-probes --init --draft 生成的机器预填段(任务完成度/风险等级/测试结果/决策追踪)带
|
|
1403
|
-
// sha256 指纹标记 + .runtime sidecar 台账;--done 对账:标记被删 / 内容哈希失配 → 阻断回滚
|
|
1404
|
-
// (heredoc 整份重写的典型形态——Write 可用与否均免疫,机制修法不做生成侧劝说)。修改机器
|
|
1405
|
-
// 段唯一通道 verify-probes --amend-draft(重锚 + amendment 审计)。无 sidecar(未用 draft /
|
|
1406
|
-
// 存量变更)→ not-applicable 零红;门禁自身异常 fail-soft(对齐探针抽查 degraded 语义)。
|
|
1407
|
-
try {
|
|
1408
|
-
const { checkDraftIntegrity } = await import('../verify-draft.js')
|
|
1409
|
-
const draftCheck = checkDraftIntegrity({
|
|
1410
|
-
mdPath: join(specBase, 'changes', changeName, 'verify-result.md'),
|
|
1411
|
-
changeName, runtimeRoot: reconcileRuntimeRoot,
|
|
1412
|
-
})
|
|
1413
|
-
if (draftCheck.applicable && !draftCheck.ok) {
|
|
1414
|
-
console.error(`\n❌ verify-result.md 机器预填段篡改门禁:${draftCheck.violations.length} 处失配——draft 预填段被整份重写或标记被删/被手工重锚。`)
|
|
1415
|
-
for (const v of draftCheck.violations) console.error(` - ${v}`)
|
|
1416
|
-
console.error(' 修复:还原机器段原文(AGENT 槽——结论枚举/移交项/审查叙述——不受本门限制);确要修改机器段,跑 sillyspec verify-probes --change ' + changeName + ' --amend-draft 留痕重锚后重新 --done。')
|
|
1417
|
-
writeVerifyGatePointer({ runtimeRoot: reconcileRuntimeRoot, changeName, blocked: true, note: 'draft 篡改门禁阻断' })
|
|
1418
|
-
return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts, { type: 'gate_rollback', detail: 'verify-contract' })
|
|
1419
|
-
}
|
|
1420
|
-
} catch { /* 门禁异常 fail-soft(不是篡改,对齐探针 degraded 放行) */ }
|
|
1421
|
-
// ── ceremony 双跑收口·第一出口(2026-09-18-ceremony-risk-pricing task-04 / FR-03 / D-003;
|
|
1422
|
-
// execute 评审 FAIL 项补接线):声明档(.runtime/ceremony-tier-<change>.json 开跑定价)vs
|
|
1423
|
-
// 实际 diff 重跑 blast+span 的事实档,mismatch(severity=error,懒 agent 低报)汇入 verify
|
|
1424
|
-
// errors 硬 flag 面——阻断回滚(照 reconcile/probe 先例 rollbackCompletionAndReturn,修复后
|
|
1425
|
-
// 重跑 --done 进度不丢)。检查/记账/种子收敛在 runCeremonyDualRunCheck 单点(archive 出口
|
|
1426
|
-
// complete-handlers.js runArchiveCeremonyDualRunExit 同款消费);skipped(档位文件缺失=存量
|
|
1427
|
-
// 变更零红门禁)/degraded(git 不可用降级)零噪音放行;整体 fail-soft——检查自身异常降级
|
|
1428
|
-
// 放行(异常不是低报,对齐 archive 出口同款处置)。specBase/runtimeRoot 与四邻检查同源
|
|
1429
|
-
// (reconcileRuntimeRoot=resolveRuntimeRoot(platformOpts, specBase),口径一处定义不二算)。──
|
|
1430
|
-
try {
|
|
1431
|
-
const { runCeremonyDualRunCheck, printCeremonyDualRunCheck } = await import('../verify-postcheck.js')
|
|
1432
|
-
const ceremonyCheck = await runCeremonyDualRunCheck({
|
|
1433
|
-
cwd, specBase, changeName,
|
|
1434
|
-
runtimeRoot: reconcileRuntimeRoot,
|
|
1435
|
-
})
|
|
1436
|
-
printCeremonyDualRunCheck(ceremonyCheck)
|
|
1437
|
-
if (ceremonyCheck.status === 'mismatch' && ceremonyCheck.severity === 'error') {
|
|
1438
|
-
return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts, { type: 'gate_rollback', detail: 'verify-contract' })
|
|
1439
|
-
}
|
|
1440
|
-
} catch (e) {
|
|
1441
|
-
console.warn(`⚠️ Ceremony 双跑对账异常降级放行(不阻断 verify,fail-soft): ${(e && e.message) || e}`)
|
|
1442
|
-
}
|
|
1443
1447
|
// ── 行号漂移自动重锚(2026-09-09 §7-6:quick --done 已接,verify/archive 收尾补接)──
|
|
1444
1448
|
// 本变更 diff 中的 .md 文档经 autoReanchorDocRefs 定点重锚(fixable 唯一/优选命中才改 +
|
|
1445
1449
|
// 同口径回执),消灭「src 改动平移行号 → 下次 docs gate 拦」的人工往返。挂 verify 收尾即
|
package/src/run/prompt.js
CHANGED
|
@@ -1390,7 +1390,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1390
1390
|
if (stageName === 'brainstorm' && promptText.includes('{DECISION_HITS}')) {
|
|
1391
1391
|
try {
|
|
1392
1392
|
const { matchKnowledgeHybrid } = await import('../knowledge-vector.js')
|
|
1393
|
-
const { deathPathNote } = await import('../knowledge-match.js')
|
|
1393
|
+
const { deathPathNote, decisionChangeQualifier } = await import('../knowledge-match.js')
|
|
1394
1394
|
const decSpecBase = resolvePromptSpecBase(platformOpts, cwd)
|
|
1395
1395
|
const decKnowledgeDir = join(decSpecBase, 'knowledge')
|
|
1396
1396
|
// taskContext:changeName(brainstorm Step2 时 tasks.md 尚未生成,变更名是唯一稳定任务
|
|
@@ -1402,10 +1402,10 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1402
1402
|
let decInjected = ''
|
|
1403
1403
|
if (rejectedHits.length > 0) {
|
|
1404
1404
|
const lines = [
|
|
1405
|
-
'⚠️ 否决决策/死路注记提示(历史已否决或已记死路,防复潮)——下列决策此前已被否决或已记死路。看到理由后,除非复潮条件明确满足,不要在本次方案中重新提出;若认为复潮条件已满足,须在本变更 decisions.md 记录新版本条目(D-xxx@vN+1
|
|
1405
|
+
'⚠️ 否决决策/死路注记提示(历史已否决或已记死路,防复潮)——下列决策此前已被否决或已记死路。看到理由后,除非复潮条件明确满足,不要在本次方案中重新提出;若认为复潮条件已满足,须在本变更 decisions.md 记录新版本条目(D-xxx@vN+1)说明依据;跨变更引用他条目须带变更名限定(变更名#D-xxx@vN——同号不同变更互不相干):'
|
|
1406
1406
|
]
|
|
1407
1407
|
for (const h of rejectedHits.slice(0, 5)) {
|
|
1408
|
-
lines.push(`- ${h.id} ${h.title}(${h.file})${h.status === 'rejected' ? '' : ' ⚰️死路注记'}`)
|
|
1408
|
+
lines.push(`- ${h.id} ${h.title}(${h.file}|${decisionChangeQualifier(h.change)})${h.status === 'rejected' ? '' : ' ⚰️死路注记'}`)
|
|
1409
1409
|
lines.push(` - ${h.status === 'rejected' ? '否决理由' : '死路'}:${h.status === 'rejected' ? (h.reason || '(未记录)') : deathPathNote(h.reason)}`)
|
|
1410
1410
|
lines.push(` - 复潮条件:${h.revisitWhen || '(未记录)'}`)
|
|
1411
1411
|
}
|
|
@@ -2247,6 +2247,16 @@ ${maskVolatileForGuide(guideTemplate)}
|
|
|
2247
2247
|
}
|
|
2248
2248
|
console.log('<!--SS-META:' + JSON.stringify(meta) + '-->')
|
|
2249
2249
|
}
|
|
2250
|
+
|
|
2251
|
+
// 尾部全文锚(2026-10-14-guide-anchor-adopt-proto,sess_fb51d988 实证):首见全量渲染可能被
|
|
2252
|
+
// harness 工具结果截断保尾——长动态注入段(模块欠账/知识命中等)把决策关键节(原型分级/生成
|
|
2253
|
+
// 要求等)顶出可见窗口,agent 只见铁律半句就临场发挥。末尾一行带 step-guides 全文路径,截断后
|
|
2254
|
+
// 仍能 Read 回完整规则(complete.js task-08 锚定行的补全:带路径,不只报位置)。复入分支顶部
|
|
2255
|
+
// 已有 📄 行,但其动态附录同样可能很长把顶部顶掉——尾部统一再锚一次。guide 关闭/--json 劫持/
|
|
2256
|
+
// 文件不在场时零输出(stdout 确定性测试面不变)。
|
|
2257
|
+
if (guideEnabled && !jsonHijacked && existsSync(guideFile)) {
|
|
2258
|
+
console.log(`📄 指引全文锚:${guideFile}(本输出被截断或上下文丢失时 Read 此文件拿回完整步骤规则——决策关键节都在内)`)
|
|
2259
|
+
}
|
|
2250
2260
|
}
|
|
2251
2261
|
/**
|
|
2252
2262
|
* 替换 prompt 文本中的路径根占位符 {SPEC_ROOT}/{DOCS_ROOT}/{PROJECTS_ROOT}/
|
package/src/run/shared.js
CHANGED
|
@@ -2308,6 +2308,25 @@ export function _clearMultiRepoCtxCache(cwd, changeName) {
|
|
|
2308
2308
|
}
|
|
2309
2309
|
|
|
2310
2310
|
|
|
2311
|
+
/**
|
|
2312
|
+
* 退役自动道步骤表形态判定(2026-10-14-brainstorm-auto-retire):brainstorm 4 步 auto 表
|
|
2313
|
+
*(首名「进度确认与上下文加载」——与主模式首步「进度确认」字面可区分)。检测到时调用方必须
|
|
2314
|
+
* 报退役指引并退出:防止 ensureStageSteps 把 auto 表判「漂移」静默重种清零存量进度。
|
|
2315
|
+
* --reset/--reopen 走 resetStage 内联重种(不经此判定)——即指引中的恢复出路。
|
|
2316
|
+
*/
|
|
2317
|
+
export function isRetiredAutoBrainstormTable(steps) {
|
|
2318
|
+
return Array.isArray(steps) && steps.length === 4
|
|
2319
|
+
&& steps[0]?.name === '进度确认与上下文加载'
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
/** 退役拒绝文案(单一真相源:ensureStageSteps / getStageStepsAutoAware / ensureAutoStage 三入口共用)。 */
|
|
2323
|
+
export function printRetiredAutoBrainstormRefusal(changeName) {
|
|
2324
|
+
console.error(`⛔ 该变更的 brainstorm 步骤表来自已退役的自动道(4 步 auto 表,brainstorm-auto 已于 2026-10-14 删除——其设计产物流程无原型分级/真码口径,且双轨步骤表机制易互踩)。`)
|
|
2325
|
+
console.error(` 处置:① sillyspec run brainstorm --change ${changeName || '<变更名>'} --reset(重种主模式 8 步表从头开始——brainstorm 尚未收口时适用)② sillyspec doctor(诊断后处置)。`)
|
|
2326
|
+
console.error(` 进度未被修改(拒绝不写盘,不静默重种)。`)
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
|
|
2311
2330
|
/**
|
|
2312
2331
|
* 获取阶段的步骤定义(execute 需要动态构建)
|
|
2313
2332
|
*/
|
package/src/run/stage.js
CHANGED
|
@@ -417,7 +417,7 @@ export async function runStage(pm, progress, stageName, cwd, changeName, skipApp
|
|
|
417
417
|
// 进程内直调 runCommand 的入口(测试/编程调用)与 --change 指向不存在会话的形态。
|
|
418
418
|
console.error(`❌ quick 通道已退役,无法启动 quick 会话(若带 --change 则该会话不存在或已清理)。`)
|
|
419
419
|
console.error(` 新工作请走轻量变更(--input 引号内换行合法,可照抄):`)
|
|
420
|
-
console.error(' sillyspec flow start --change <YYYY-MM-DD-名> --input "
|
|
420
|
+
console.error(' sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句中文概括(首行即变更标题);随后动机与背景')
|
|
421
421
|
console.error('')
|
|
422
422
|
console.error(' 成功标准:')
|
|
423
423
|
console.error(' - <可验证标准>"')
|
package/src/scope-audit.js
CHANGED
|
@@ -1329,7 +1329,7 @@ async function computeFullFlowAudit({ cwd, specBase, changeName, platformOpts, c
|
|
|
1329
1329
|
* { key, repoPath, anchor, totals, degraded, degradedReason, patch?, patchSha256? };
|
|
1330
1330
|
* notes 为调用方 note 链的增量行(跨仓计数行 + 降级行)。
|
|
1331
1331
|
*/
|
|
1332
|
-
export function reconcileCrossRepoPlan({ cwd, specBase, runtimeRoot, changeName, planEntries, collectPatch = false }) {
|
|
1332
|
+
export function reconcileCrossRepoPlan({ cwd, specBase, runtimeRoot, changeName, planEntries, collectPatch = false, crossBaselines = null }) {
|
|
1333
1333
|
const rows = []
|
|
1334
1334
|
const repos = []
|
|
1335
1335
|
const notes = []
|
|
@@ -1344,7 +1344,7 @@ export function reconcileCrossRepoPlan({ cwd, specBase, runtimeRoot, changeName,
|
|
|
1344
1344
|
let repoActual = null
|
|
1345
1345
|
let repoFailure = null
|
|
1346
1346
|
try {
|
|
1347
|
-
repoActual = collectRepoActual({ repoKey, specBase, cwd, runtimeRoot, changeName })
|
|
1347
|
+
repoActual = collectRepoActual({ repoKey, specBase, cwd, runtimeRoot, changeName, crossBaselines })
|
|
1348
1348
|
} catch (e) {
|
|
1349
1349
|
repoFailure = e && e.message ? String(e.message).split('\n')[0] : String(e)
|
|
1350
1350
|
}
|