sillyspec 3.32.9 → 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 +1 -1
- package/assets/command-cards/flow.md +1 -1
- package/package.json +1 -1
- package/src/commit-suggest.js +45 -4
- package/src/config-schema.js +14 -0
- package/src/decision-distill.js +77 -21
- package/src/decision-renumber.js +157 -0
- package/src/docs-check.js +30 -15
- package/src/docs-debt.js +3 -2
- package/src/doctor-diagnostics.js +3 -1
- package/src/flow-parity.js +18 -2
- package/src/flow.js +75 -12
- package/src/fr-index.js +15 -1
- package/src/hooks/worktree-guard.js +3 -3
- package/src/index.js +7 -5
- 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/quicklog.js +6 -1
- package/src/run/archive-distill.js +3 -0
- package/src/run/command.js +3 -3
- package/src/run/complete-handlers.js +8 -3
- package/src/run/complete.js +113 -44
- package/src/run/gates.js +93 -89
- package/src/run/prompt.js +3 -3
- package/src/run/stage.js +1 -1
- 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 +5 -1
- package/src/stages/knowledge.js +56 -5
- package/src/sync.js +28 -2
- package/src/task-tick.js +12 -0
- package/src/timeline.js +60 -19
- package/src/verify-postcheck.js +49 -1
- package/src/verify-probes.js +61 -18
- package/templates/agents-instruction.md +1 -1
package/src/run/complete.js
CHANGED
|
@@ -493,25 +493,26 @@ export async function completeStep(pm, progress, stageName, cwd, outputText, inp
|
|
|
493
493
|
.filter((h) => !_hits.some((x) => x.file === h.file && x.id === h.id && (x.change || '') === (h.change || '')))
|
|
494
494
|
_hits.push(..._viaImpact)
|
|
495
495
|
} catch { /* 图构建/键解析 fail-soft——词面命中面不受影响 */ }
|
|
496
|
-
// 已回应不重弹:decisions.md
|
|
497
|
-
//
|
|
496
|
+
// 已回应不重弹:decisions.md 某行同时含「命中 id+域文件名(或命中条目变更名)」(小白鼠
|
|
497
|
+
// 回应形态「unmapped.md D-001@v1」)→ 视为已甄别过,静默。行级共现(2026-10-14-decision-id-p2-hardening
|
|
498
|
+
// 收紧:文件级共现时本变更自身同号 id + 正文常见词 backend/unmapped 撞车会静默吞掉他变更命中)。
|
|
498
499
|
const _answered = _dm
|
|
499
500
|
? (h) => {
|
|
500
501
|
const _dom = String(h.file || '').replace(/^decisions\//, '').replace(/\.md$/, '')
|
|
501
|
-
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))))
|
|
502
503
|
}
|
|
503
504
|
: () => false
|
|
504
505
|
const _echoHits = _hits.filter((h) => !_answered(h))
|
|
505
506
|
const _entries = km.matched ? (km.entries || []) : []
|
|
506
507
|
if (_echoHits.length > 0 || _entries.length > 0) {
|
|
507
|
-
const { deathPathNote } = await import('../knowledge-match.js')
|
|
508
|
+
const { deathPathNote, decisionChangeQualifier } = await import('../knowledge-match.js')
|
|
508
509
|
console.warn(`\n⚠️ [knowledge-gate] 方案/决策知识命中(设计时点防复潮提示,warn 不阻断):`)
|
|
509
510
|
for (const h of _echoHits.slice(0, 5)) {
|
|
510
511
|
const _note = h.status === 'rejected'
|
|
511
512
|
? ` 否决理由:${h.reason || '(未记录)'}`
|
|
512
513
|
: h.deathPath ? ` ⚰️死路注记:${deathPathNote(h.reason)}` : ''
|
|
513
514
|
const _via = h.viaImpact ? ` 🧭impact 可达(键:${h.impactKey})` : ''
|
|
514
|
-
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}`)
|
|
515
516
|
}
|
|
516
517
|
for (const e of _entries.slice(0, 3)) {
|
|
517
518
|
const _base = e.anchor ? `${e.file}#${e.anchor}` : e.file
|
|
@@ -673,40 +674,9 @@ export async function completeStep(pm, progress, stageName, cwd, outputText, inp
|
|
|
673
674
|
// 勾选回归 agent 手动(完成=实现+测试绿+wt-commit 即勾,同 thin 工作单元语义),假勾防线由
|
|
674
675
|
// detectExecuteBatchFinish 内 checkExecuteCodeEvidence 代码证据核验 + verify 测试对账承担。
|
|
675
676
|
if (stageName === 'execute' && changeName) {
|
|
676
|
-
// 自动勾选(full-autopilot-parity,R21 thin
|
|
677
|
-
//
|
|
678
|
-
|
|
679
|
-
try {
|
|
680
|
-
const _tasksMdPath = join(specBase, 'changes', changeName, 'tasks.md')
|
|
681
|
-
if (existsSync(_tasksMdPath)) {
|
|
682
|
-
const _runIdFile = join(resolveRuntimeRoot(platformOpts, specBase), `current-execute-run-id-${changeName}`)
|
|
683
|
-
let _tickCommits = ''
|
|
684
|
-
try {
|
|
685
|
-
if (existsSync(_runIdFile)) {
|
|
686
|
-
_tickCommits = gitQuiet(cwd, ['log', '--format=%B%x1e', 'HEAD~20..HEAD']) || ''
|
|
687
|
-
}
|
|
688
|
-
} catch {}
|
|
689
|
-
if (!_tickCommits) _tickCommits = gitQuiet(cwd, ['log', '--format=%B%x1e', '-20']) || ''
|
|
690
|
-
if (_tickCommits) {
|
|
691
|
-
const _texts = String(_tickCommits).split('\x1e').map(x => x.trim()).filter(Boolean).join('\n')
|
|
692
|
-
const _evidenced = new Set()
|
|
693
|
-
for (const m of _texts.matchAll(/task-(\d{1,2})/gi)) _evidenced.add(String(Number(m[1])).padStart(2, '0'))
|
|
694
|
-
if (_evidenced.size > 0) {
|
|
695
|
-
let _md = readFileSync(_tasksMdPath, 'utf8')
|
|
696
|
-
let _ticked = 0
|
|
697
|
-
for (const nn of _evidenced) {
|
|
698
|
-
const re = new RegExp(`^- \\[ \\](.*task-${nn}[:\\s])`, 'm')
|
|
699
|
-
if (re.test(_md)) { _md = _md.replace(re, `- [x]$1`); _ticked++ }
|
|
700
|
-
}
|
|
701
|
-
if (_ticked > 0) {
|
|
702
|
-
const { writeAtomicSync } = await import('../fs-atomic.js')
|
|
703
|
-
writeAtomicSync(_tasksMdPath, _md)
|
|
704
|
-
console.log(`✅ [自动勾选] ${_ticked} 个任务有提交证据但未勾——已机器代勾(full-autopilot-parity)`)
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
} catch { /* auto-tick fail-soft */ }
|
|
677
|
+
// 自动勾选(full-autopilot-parity,R21 thin 实证迁移):代勾判据/实现见
|
|
678
|
+
// autoTickTasksFromCommitSubjects(subject token × allowed_paths 实现事实锚)。
|
|
679
|
+
await autoTickTasksFromCommitSubjects({ changeName, cwd, specBase, platformOpts })
|
|
710
680
|
const _bf = await detectExecuteBatchFinish({ pm, stageName, changeName, cwd, specBase, platformOpts, steps })
|
|
711
681
|
if (_bf.batched && _bf.aligned > 0) {
|
|
712
682
|
console.log(`\n🚀 execute 批量完成:plan 全勾 + 代码核验通过,一次性补完 ${_bf.aligned} 个剩余 step → 进入阶段完成分支`)
|
|
@@ -1532,8 +1502,10 @@ export function shouldAutoCheckTask(r, endToEnd, ctx = null, changeName = '') {
|
|
|
1532
1502
|
if (isExplicitReviewWrite(r.review)) return true
|
|
1533
1503
|
|
|
1534
1504
|
// W2 task-04 草稿零 diff 守卫(FR-03)
|
|
1535
|
-
//
|
|
1536
|
-
|
|
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')) {
|
|
1537
1509
|
const taskId = r.review.task || 'task-NN'
|
|
1538
1510
|
const unlockCmd = `sillyspec review write${changeName ? ` --change ${changeName}` : ' --change <变更名>'} --task ${taskId} --spec pass --quality pass --force --changed-files <文件>`
|
|
1539
1511
|
const changedFiles = r.review.changedFiles || []
|
|
@@ -1541,8 +1513,8 @@ export function shouldAutoCheckTask(r, endToEnd, ctx = null, changeName = '') {
|
|
|
1541
1513
|
console.warn(`⚠️ 草稿 review changedFiles 为空,跳过自动勾选(task=${taskId})——解锁:${unlockCmd}(纯验证任务用 --changed-files "" + --evidence "<验证说明>")`)
|
|
1542
1514
|
return false
|
|
1543
1515
|
}
|
|
1544
|
-
if (!ctx.gitDir || !ctx.base || !ctx.head) {
|
|
1545
|
-
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}`)
|
|
1546
1518
|
return false
|
|
1547
1519
|
}
|
|
1548
1520
|
// 预取路径:一次全量 diff 文件集内存归属(autoCheckPlanFromReviews/批量路径已 prefetch)
|
|
@@ -1572,6 +1544,86 @@ export function shouldAutoCheckTask(r, endToEnd, ctx = null, changeName = '') {
|
|
|
1572
1544
|
|
|
1573
1545
|
return true
|
|
1574
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
|
+
|
|
1575
1627
|
export async function autoCheckPlanFromReviews({ stageName, changeName, cwd, platformOpts }) {
|
|
1576
1628
|
if (stageName !== 'execute' || !changeName) {
|
|
1577
1629
|
return { autoChecked: false, checkedCount: 0, skippedCount: 0 }
|
|
@@ -1713,7 +1765,7 @@ export function detectVerifyBatchFinish({ changeName, specBase, steps }) {
|
|
|
1713
1765
|
}
|
|
1714
1766
|
}
|
|
1715
1767
|
|
|
1716
|
-
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 }
|
|
1717
1769
|
try {
|
|
1718
1770
|
const changeDir = join(specBase, 'changes', changeName)
|
|
1719
1771
|
const { total: planTotal, checked: planChecked } = pm.readPlanCheckboxStatus(changeDir)
|
|
@@ -1747,6 +1799,23 @@ async function detectExecuteBatchFinish({ pm, stageName, changeName, cwd, specBa
|
|
|
1747
1799
|
// → 每个 task 进 blockedTasks → 批量完成静默失效
|
|
1748
1800
|
const runtimeRoot = resolveRuntimeRoot(platformOpts, specBase)
|
|
1749
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
|
+
}
|
|
1750
1819
|
const blockedTasks = []
|
|
1751
1820
|
const { readReview, isValidExecuteRunId } = await import('../task-review.js')
|
|
1752
1821
|
// runId marker 对全部 task 恒定:循环外一次读盘(原实现逐 task 重复 readFileSync 同一文件)
|
package/src/run/gates.js
CHANGED
|
@@ -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
|
}
|
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(' - <可验证标准>"')
|
|
@@ -165,6 +165,48 @@ export function detectBatchCheckCadence(events) {
|
|
|
165
165
|
return worst;
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
/**
|
|
169
|
+
* 侧车勾选突发检测(2026-10-14-tick-burst-sidecar-gate,纯函数):task-ticks.jsonl 的毫秒级
|
|
170
|
+
* 时戳按相邻间隔窗口分簇——CLI 逐格快连点(收口一把点的官方路径形态)在事件流里是单格精确跳,
|
|
171
|
+
* 10-07 采样去重后对 detectBatchCheckCadence 不可见(近期实证:6 格 2 秒内/4 格 1 秒内零检出);
|
|
172
|
+
* 侧车是 CLI tick 的事实源(随归档进 git、不受观测盲窗影响),收口节奏门回退消费本函数——
|
|
173
|
+
* CLI 与直接 Edit 两条路径同判。
|
|
174
|
+
* 语义:tickLog 同任务取最早(幂等重勾防御)+ 坏行跳过(task 形态/时戳有限性)+ 排序后按
|
|
175
|
+
* gap<=windowMs(缺省 60s,opts.windowMs 覆写)分簇;最大簇 size>=2 → 返回与事件流同形的
|
|
176
|
+
* batchTick 记录。from/to 是 CLI 勾序计数(Edit 翻格不记侧车,与事件流 checked N→M 计数空间
|
|
177
|
+
* 不同源)——resolveBatchTickAction 只消费跳幅(to-from)与 ts,语义等价;detail 带「侧车突发」
|
|
178
|
+
* 标注防口径混淆。fail-open:不足两格/空输入/全坏行 → null。
|
|
179
|
+
*/
|
|
180
|
+
export function detectTickBurst(tickLog, opts = {}) {
|
|
181
|
+
const windowMs = Number.isFinite(opts.windowMs) ? opts.windowMs : 60_000;
|
|
182
|
+
const byTask = new Map();
|
|
183
|
+
for (const r of Array.isArray(tickLog) ? tickLog : []) {
|
|
184
|
+
const task = String((r && r.task) || '').trim();
|
|
185
|
+
const ts = Number(r && r.ts);
|
|
186
|
+
if (!/^task-\d+$/.test(task) || !Number.isFinite(ts)) continue;
|
|
187
|
+
const prev = byTask.get(task);
|
|
188
|
+
if (prev === undefined || ts < prev) byTask.set(task, ts);
|
|
189
|
+
}
|
|
190
|
+
const ticks = [...byTask.values()].sort((a, b) => a - b);
|
|
191
|
+
if (ticks.length < 2) return null;
|
|
192
|
+
let worst = null;
|
|
193
|
+
let start = 0;
|
|
194
|
+
for (let i = 1; i <= ticks.length; i++) {
|
|
195
|
+
if (i < ticks.length && ticks[i] - ticks[i - 1] <= windowMs) continue;
|
|
196
|
+
const size = i - start;
|
|
197
|
+
if (size >= 2 && (!worst || size > worst.size)) {
|
|
198
|
+
worst = { size, from: start, to: i, ts: ticks[start], spanMs: ticks[i - 1] - ticks[start] };
|
|
199
|
+
}
|
|
200
|
+
start = i;
|
|
201
|
+
}
|
|
202
|
+
if (!worst) return null;
|
|
203
|
+
return {
|
|
204
|
+
from: worst.from, to: worst.to, size: worst.size, spanMs: worst.spanMs, ts: worst.ts,
|
|
205
|
+
source: 'tick-burst',
|
|
206
|
+
detail: `checked ${worst.from}→${worst.to}(task tick 侧车突发 ${worst.size} 格)`,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
168
210
|
/**
|
|
169
211
|
* 单拍勾选门决策(2026-09-29-batch-tick-gate,纯函数):检出单拍跳(batchTick 非 null)
|
|
170
212
|
* 时的收口动作裁决。镜像豁免分支退役(2026-10-07-thin-tasks-v3——任务面统一为 agent
|
|
@@ -193,4 +235,4 @@ export function resolveBatchTickAction({ batchTick, nonMirrorCount = null, allow
|
|
|
193
235
|
return { action: 'reject', reason: 'agent-batch-tick' };
|
|
194
236
|
}
|
|
195
237
|
|
|
196
|
-
export default { detectFakeCheckCompletion, detectBatchCheckCadence };
|
|
238
|
+
export default { detectFakeCheckCompletion, detectBatchCheckCadence, detectTickBurst };
|
package/src/stage-contract.js
CHANGED
|
@@ -1059,8 +1059,12 @@ const API_COVERAGE_HEADING_RE = /^## 接口验证覆盖矩阵/m
|
|
|
1059
1059
|
const API_ENDPOINT_CELL_RE = /(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+(\/[^\s|]+)/
|
|
1060
1060
|
/** 声明占位行(D-005 零解析降级侧,骨架「本变更接口面:N 端点(agent 声明)」字面同源) */
|
|
1061
1061
|
const API_DECLARED_ROW_RE = /本变更接口面[::]\s*(\d+)\s*端点/
|
|
1062
|
-
/** design接口表# 锚点提取(解析级校验用——防空指,Grill #10:非物理行号,METHOD /path
|
|
1063
|
-
|
|
1062
|
+
/** design接口表# 锚点提取(解析级校验用——防空指,Grill #10:非物理行号,METHOD /path 形态)。
|
|
1063
|
+
* 字符类左右括号对称排除(坑 verify-close-gate-staggered-exposure,2026-10-15 消费仓 node 直调
|
|
1064
|
+
* 实证:原只排右括号)不排左括号(——`GET /api/x(注记)` 的路径吞成 `/api/x(注记`,与解析面
|
|
1065
|
+
* 永不匹配 → 恒空指,报错文案却指向「design.md 未命中」误导排查方向)。中文(英文、全/半角
|
|
1066
|
+
* 四形与既有分隔符一并终止捕获。 */
|
|
1067
|
+
const API_ANCHOR_DESIGN_API_RE = /design接口表#\s*(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+(\/[^\s|,。;;()()]+)/g
|
|
1064
1068
|
/** 探索行标记(骨架文法注释字面同源:证据列含 [探索]) */
|
|
1065
1069
|
const API_EXPLORATION_MARK_RE = /\[探索\]/
|
|
1066
1070
|
|
|
@@ -1261,9 +1265,16 @@ export function judgeApiCoverageMatrix(args) {
|
|
|
1261
1265
|
}
|
|
1262
1266
|
}
|
|
1263
1267
|
if (anchorViolations.length > 0) {
|
|
1268
|
+
// 空指诊断(坑 verify-close-gate-staggered-exposure 建议③,2026-10-15 实证:报错只说「未命中
|
|
1269
|
+
// 解析面」不展示解析面实际内容——「锚点吞字」与「design 缺表」分不清,agent 被引去改 design
|
|
1270
|
+
// 文档白做功。附上 parseSet 实际端点集一眼对齐)。
|
|
1271
|
+
const parseFaceDesc = parseSet.size > 0
|
|
1272
|
+
? [...parseSet].join(';')
|
|
1273
|
+
: '(解析面为空——design.md 接口段未解析到端点,先表格化接口段再跑 verify-probes --init)'
|
|
1264
1274
|
errors.push(
|
|
1265
1275
|
`接口验证覆盖矩阵有 ${anchorViolations.length} 项证据锚点缺失/空指` +
|
|
1266
1276
|
`(covered/partial 证据须含锚点五形态之一,可复制样例:design接口表#POST /api/xx(# 后必须 METHOD /path 且命中 design 接口段解析面,仅表名/行号不计)/ 权限矩阵[admin×读] / 契约表@任务卡字段清单 / DDL@users.id / 载荷@e2e_body.json;covered-service 证据须含测试锚点三形态之一:\`.test.\` 文件名 / file:line / 反引号包裹的测试名):${anchorViolations.join(';')}。` +
|
|
1277
|
+
`design 接口段实际解析面端点集:${parseFaceDesc}——design接口表# 锚点路径须与之逐字一致。` +
|
|
1267
1278
|
`修复:在证据列补真实锚点(design接口表# 锚点须与 design.md 接口段端点一致,防编造端点)。`
|
|
1268
1279
|
)
|
|
1269
1280
|
}
|
|
@@ -1419,6 +1430,9 @@ function extractPermissionMatrixText(designMd) {
|
|
|
1419
1430
|
* - tasks/ 不存在(quick 会话/旧变更)或 verify-result.md 未落盘(中间步骤)→ no-op
|
|
1420
1431
|
* - factsExpected=false(存量未跑管线,resolveFactsExpected 同源口径)→ no-op 零行为
|
|
1421
1432
|
* - verify-facts.json(apiFace/consumerHints/handover,缺失容 null——fail-closed 归纯函数)
|
|
1433
|
+
* - context.apiFaceLive(可选,2026-10-14-gate-verify-apiface-preflight):gate/derive 预检侧
|
|
1434
|
+
* 以同一解析器实况解析 design.md 注入(与 --done backfill 产物同形同源,零落盘)——在场时
|
|
1435
|
+
* 覆写 facts.apiFace 作解析产出集(过期 facts 不得赢),未注入行为与现状完全一致
|
|
1422
1436
|
* - 矩阵段 MD 槽解析(X-05 防篡改锚点:锚定当前文档实态)
|
|
1423
1437
|
* - resolveChangeRisk 声明面判级(design 文件清单 × blast 声明 + explicit,task-03)
|
|
1424
1438
|
* - design.md 权限矩阵段提取(fs 只读,壳非纯函数)
|
|
@@ -1441,9 +1455,18 @@ export function validateApiCoverageMatrix(cwd, changeName, context = {}) {
|
|
|
1441
1455
|
// 2026-09-19-ceremony-pricing-five-cuts task-03:声明面判级(design 文件清单 × blast 声明 +
|
|
1442
1456
|
// explicit;level 为五级词兼容字段——evidenceRequired → integration-critical)
|
|
1443
1457
|
const riskProfile = resolveChangeRiskForChangeDir(changeDir, context.specRoot || specRoot || join(cwd, '.sillyspec'))
|
|
1458
|
+
// gate/derive 预检注入(2026-10-14-gate-verify-apiface-preflight FR-01/FR-02):facts.apiFace
|
|
1459
|
+
// 唯一生产者是 run verify --done 收尾前置 backfill(run/gates.js),gate 路径不回填且 --init
|
|
1460
|
+
// 刻意不落键 → 预检 parseSet 空/旧 → design接口表# 锚点恒空指(平台 6 轮 gate 实证)。调用方
|
|
1461
|
+
// (machine-interface runGate/derive)以同一解析器实况解析 design.md 经 context.apiFaceLive
|
|
1462
|
+
// 传入(本模块零 import verify-probes 纪律:注入不做函数调用)——与 --done backfill 产物同形
|
|
1463
|
+
// 同源,在场时必须覆写 facts(过期 facts 不得赢),未注入时行为与现状完全一致。
|
|
1464
|
+
const apiFace = context.apiFaceLive && typeof context.apiFaceLive === 'object'
|
|
1465
|
+
? context.apiFaceLive
|
|
1466
|
+
: (facts && facts.apiFace && typeof facts.apiFace === 'object' ? facts.apiFace : null)
|
|
1444
1467
|
return judgeApiCoverageMatrix({
|
|
1445
1468
|
matrix: extractApiCoverageMatrixSlots(readFileSync(verifyResultPath, 'utf8')),
|
|
1446
|
-
apiFace
|
|
1469
|
+
apiFace,
|
|
1447
1470
|
consumerHints: facts && facts.consumerHints && typeof facts.consumerHints === 'object' ? facts.consumerHints : null,
|
|
1448
1471
|
facts,
|
|
1449
1472
|
factsExpected: true,
|