sillyspec 3.32.9 → 3.33.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-brainstorm/SKILL.md +114 -114
- package/.claude/skills/sillyspec-execute/SKILL.md +222 -222
- package/.claude/skills/sillyspec-explore/SKILL.md +107 -107
- package/.claude/skills/sillyspec-flow/SKILL.md +1 -1
- package/.claude/skills/sillyspec-plan/SKILL.md +156 -156
- package/.github/workflows/ci.yml +58 -0
- package/.husky/pre-push +7 -6
- package/AGENTS.md +1 -1
- package/assets/command-cards/flow.md +1 -1
- package/eval/eval.config.json +3 -3
- package/eval/lib/config.mjs +6 -3
- package/eval/lib/util.mjs +16 -3
- package/eval/lib/workdir.mjs +4 -1
- package/eval/results/eval.db +0 -0
- package/eval/results/logs/2026-10-15T12-37-22-986Z-004-phase-stats-armA/prompt.txt +10 -0
- package/eval/results/logs/2026-10-15T12-37-22-986Z-004-phase-stats-armA/stderr.txt +0 -0
- package/eval/results/logs/2026-10-15T12-37-22-986Z-004-phase-stats-armA/stdout.txt +1 -0
- package/eval/results/logs/2026-10-15T12-45-37-124Z-004-phase-stats-armB/prompt.txt +10 -0
- package/eval/results/logs/2026-10-15T12-45-37-124Z-004-phase-stats-armB/stderr.txt +0 -0
- package/eval/results/logs/2026-10-15T12-45-37-124Z-004-phase-stats-armB/stdout.txt +1 -0
- package/eval/results/logs/2026-10-15T12-56-19-259Z-004-phase-stats-armA/prompt.txt +10 -0
- package/eval/results/logs/2026-10-15T12-56-19-259Z-004-phase-stats-armA/stderr.txt +0 -0
- package/eval/results/logs/2026-10-15T12-56-19-259Z-004-phase-stats-armA/stdout.txt +1 -0
- package/eval/results/logs/2026-10-15T13-00-05-625Z-004-phase-stats-armB/prompt.txt +10 -0
- package/eval/results/logs/2026-10-15T13-04-51-003Z-004-phase-stats-armA/prompt.txt +10 -0
- package/eval/results/logs/2026-10-15T13-04-51-003Z-004-phase-stats-armA/stderr.txt +0 -0
- package/eval/results/logs/2026-10-15T13-04-51-003Z-004-phase-stats-armA/stdout.txt +1 -0
- package/eval/results/logs/2026-10-15T13-06-25-271Z-004-phase-stats-armB/prompt.txt +10 -0
- package/eval/results/logs/2026-10-15T13-06-25-271Z-004-phase-stats-armB/stderr.txt +0 -0
- package/eval/results/logs/2026-10-15T13-06-25-271Z-004-phase-stats-armB/stdout.txt +1 -0
- package/eval/results/logs/2026-10-15T13-24-38-308Z-004-phase-stats-armB/prompt.txt +10 -0
- package/eval/results/logs/2026-10-15T13-24-38-308Z-004-phase-stats-armB/stderr.txt +0 -0
- package/eval/results/logs/2026-10-15T13-24-38-308Z-004-phase-stats-armB/stdout.txt +1 -0
- package/eval/results/logs/2026-10-15T14-50-01-950Z-005-orders-export-armA/prompt.txt +8 -0
- package/eval/results/logs/2026-10-15T14-50-01-950Z-005-orders-export-armA/stderr.txt +0 -0
- package/eval/results/logs/2026-10-15T14-50-01-950Z-005-orders-export-armA/stdout.txt +1 -0
- package/eval/results/logs/2026-10-15T14-51-56-316Z-005-orders-export-armB/prompt.txt +8 -0
- package/eval/results/logs/2026-10-15T14-51-56-316Z-005-orders-export-armB/stderr.txt +0 -0
- package/eval/results/logs/2026-10-15T14-51-56-316Z-005-orders-export-armB/stdout.txt +1 -0
- package/eval/results/workdirs/001-slugify__armA/package.json +6 -0
- package/eval/results/workdirs/001-slugify__armA/src/slugify.mjs +26 -0
- package/eval/results/workdirs/001-slugify__armB/package.json +6 -0
- package/eval/results/workdirs/001-slugify__armB/src/slugify.mjs +26 -0
- package/eval/results/workdirs/002-each-day__armA/package.json +6 -0
- package/eval/results/workdirs/002-each-day__armA/src/dates.mjs +30 -0
- package/eval/results/workdirs/002-each-day__armB/DRY_RUN_WRONG_ANSWER.mjs +1 -0
- package/eval/results/workdirs/002-each-day__armB/package.json +6 -0
- package/eval/results/workdirs/002-each-day__armB/src/dates.mjs +4 -0
- package/eval/results/workdirs/003-word-report__armA/bin/report.mjs +36 -0
- package/eval/results/workdirs/003-word-report__armA/data/notes.txt +5 -0
- package/eval/results/workdirs/003-word-report__armA/package.json +6 -0
- package/eval/results/workdirs/003-word-report__armB/bin/report.mjs +36 -0
- package/eval/results/workdirs/003-word-report__armB/data/notes.txt +5 -0
- package/eval/results/workdirs/003-word-report__armB/package.json +6 -0
- package/eval/results/workdirs/004-phase-stats__armA/data/events.log +16 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/flow.md +44 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-archive.md +27 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-brainstorm.md +34 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-execute.md +30 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-plan.md +28 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/run-verify.md +30 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/commands/sillyspec/status.md +28 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-archive/SKILL.md +87 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-auto/SKILL.md +54 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-commit/SKILL.md +67 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-continue/SKILL.md +17 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-execute/SKILL.md +222 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-explore/SKILL.md +107 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-export/SKILL.md +57 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-flow/SKILL.md +62 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-init/SKILL.md +177 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-knowledge/SKILL.md +40 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-plan/SKILL.md +156 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-propose/SKILL.md +16 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-quick/SKILL.md +116 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-resume/SKILL.md +21 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-scan/SKILL.md +79 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-state/SKILL.md +25 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-status/SKILL.md +54 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-verify/SKILL.md +87 -0
- package/eval/results/workdirs/004-phase-stats__armB/.claude/skills/sillyspec-workspace/SKILL.md +33 -0
- package/eval/results/workdirs/004-phase-stats__armB/AGENTS.md +45 -0
- package/eval/results/workdirs/004-phase-stats__armB/CLAUDE.md +3 -0
- package/eval/results/workdirs/004-phase-stats__armB/data/events.log +16 -0
- package/eval/results/workdirs/run-001-slugify__armA/package.json +6 -0
- package/eval/results/workdirs/run-001-slugify__armA/src/slugify.mjs +26 -0
- package/eval/results/workdirs/run-001-slugify__armB/package.json +6 -0
- package/eval/results/workdirs/run-001-slugify__armB/src/slugify.mjs +26 -0
- package/eval/results/workdirs/run-002-each-day__armA/package.json +6 -0
- package/eval/results/workdirs/run-002-each-day__armA/src/dates.mjs +30 -0
- package/eval/results/workdirs/run-002-each-day__armB/DRY_RUN_WRONG_ANSWER.mjs +1 -0
- package/eval/results/workdirs/run-002-each-day__armB/package.json +6 -0
- package/eval/results/workdirs/run-002-each-day__armB/src/dates.mjs +4 -0
- package/eval/results/workdirs/run-003-word-report__armA/bin/report.mjs +36 -0
- package/eval/results/workdirs/run-003-word-report__armA/data/notes.txt +5 -0
- package/eval/results/workdirs/run-003-word-report__armA/package.json +6 -0
- package/eval/results/workdirs/run-003-word-report__armB/bin/report.mjs +36 -0
- package/eval/results/workdirs/run-003-word-report__armB/data/notes.txt +5 -0
- package/eval/results/workdirs/run-003-word-report__armB/package.json +6 -0
- package/eval/results/workdirs/run-004-phase-stats__armA/bin/phase-stats.mjs +37 -0
- package/eval/results/workdirs/run-004-phase-stats__armA/data/events.log +16 -0
- package/eval/results/workdirs/run-004-phase-stats__armB/DRY_RUN_WRONG_ANSWER.mjs +1 -0
- package/eval/results/workdirs/run-004-phase-stats__armB/data/events.log +16 -0
- package/eval/results/workdirs/run-005-orders-export__armA/bin/export-orders.mjs +57 -0
- package/eval/results/workdirs/run-005-orders-export__armA/data/.gitattributes +1 -0
- package/eval/results/workdirs/run-005-orders-export__armA/data/orders.csv +9 -0
- package/eval/results/workdirs/run-005-orders-export__armA/out/summary.json +1 -0
- package/eval/results/workdirs/run-005-orders-export__armB/bin/export-orders.mjs +57 -0
- package/eval/results/workdirs/run-005-orders-export__armB/data/.gitattributes +1 -0
- package/eval/results/workdirs/run-005-orders-export__armB/data/orders.csv +9 -0
- package/eval/results/workdirs/run-005-orders-export__armB/out/summary.json +1 -0
- package/eval/tasks/004-phase-stats/files/data/events.log +16 -0
- package/eval/tasks/004-phase-stats/solution/bin/phase-stats.mjs +37 -0
- package/eval/tasks/004-phase-stats/task.json +6 -0
- package/eval/tasks/004-phase-stats/verify.mjs +38 -0
- package/eval/tasks/005-orders-export/files/data/.gitattributes +1 -0
- package/eval/tasks/005-orders-export/files/data/orders.csv +9 -0
- package/eval/tasks/005-orders-export/solution/bin/export-orders.mjs +57 -0
- package/eval/tasks/005-orders-export/task.json +6 -0
- package/eval/tasks/005-orders-export/verify.mjs +28 -0
- package/package.json +1 -1
- package/quicklog/tmp-fix-review.mjs +21 -0
- package/src/ceremony-tier.js +2 -1
- package/src/commit-suggest.js +45 -4
- package/src/config-schema.js +14 -0
- package/src/decision-distill.js +106 -24
- package/src/decision-redomain.js +148 -0
- package/src/decision-renumber.js +157 -0
- package/src/design-facts.js +3 -3
- package/src/docs-check.js +30 -15
- package/src/docs-debt.js +6 -5
- package/src/doctor-diagnostics.js +3 -1
- package/src/flow-parity.js +28 -4
- package/src/flow.js +76 -13
- package/src/forensics.js +236 -0
- package/src/fr-index.js +17 -2
- package/src/hooks/worktree-guard.js +3 -3
- package/src/index.js +41 -6
- package/src/knowledge-digest.js +23 -6
- package/src/knowledge-graph.js +95 -18
- 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/module-impact.js +2 -5
- package/src/modules.js +31 -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 +100 -103
- package/src/run/lint-ownership.js +37 -0
- package/src/run/prompt.js +43 -28
- package/src/run/stage.js +1 -1
- package/src/run/verify-quality-scan.js +10 -1
- package/src/sentinel-assertions.js +43 -1
- package/src/spec-sync.js +81 -2
- package/src/stage-contract.js +26 -3
- package/src/stage-review.js +46 -12
- package/src/stage-templates.js +2 -0
- package/src/stages/brainstorm.js +76 -21
- package/src/stages/knowledge.js +56 -5
- package/src/stages/plan-postcheck.js +13 -2
- package/src/stages/plan.js +19 -0
- package/src/sync.js +28 -2
- package/src/task-tick.js +12 -0
- package/src/test-bindings.js +32 -3
- package/src/timeline.js +60 -19
- package/src/ui-visual.js +119 -4
- package/src/verify-postcheck.js +166 -18
- package/src/verify-probes.js +61 -18
- package/src/worktree-apply.js +26 -2
- package/templates/agents-instruction.md +1 -1
package/src/quicklog.js
CHANGED
|
@@ -124,13 +124,18 @@ export function sanitizeDesc(description) {
|
|
|
124
124
|
* 「需求:/动机:」类标签前缀,空白折叠单行,超长截断加 …;空输入返回 ''(调用方兜底
|
|
125
125
|
* change 名)。agent 在 --input 首行写一句中文概括即成标题(flow start 横幅有指引),
|
|
126
126
|
* --title 显式覆盖一切推导。
|
|
127
|
+
* 段头行防御(2026-10-15-title-label-skip):过门格式示例曾被读成段头形态——agent 把
|
|
128
|
+
* 「动机与背景:」独立成行照抄,白名单无复合词致整行成标题(进度库 6 变更实证)。两道
|
|
129
|
+
* 防御:白名单补复合词(动机与背景/背景与动机);任意短段头行(≤12 字非冒号内容+行尾
|
|
130
|
+
* 冒号,如「变更背景:」「详细说明:」)整行跳过取次非空行——冒号后无内容不成标题。
|
|
127
131
|
*/
|
|
128
132
|
export function deriveChangeTitle(input, max = 50) {
|
|
129
133
|
for (const l of String(input || '').replace(/\r\n/g, '\n').split('\n')) {
|
|
130
134
|
const t = l.trim()
|
|
131
135
|
if (!t) continue
|
|
132
136
|
if (/^成功标准\s*[::]/.test(t)) break
|
|
133
|
-
|
|
137
|
+
if (/^[^::]{1,12}[::]$/.test(t)) continue
|
|
138
|
+
const stripped = t.replace(/^(?:需求|动机|背景|任务|标题|动机与背景|背景与动机)\s*[::]\s*/, '').trim()
|
|
134
139
|
if (!stripped) continue
|
|
135
140
|
const one = stripped.replace(/\s+/g, ' ')
|
|
136
141
|
return one.length > max ? one.slice(0, max) + '…' : one
|
|
@@ -39,6 +39,9 @@ export async function executeArchiveDistill({ cwd, specBase, changeName }) {
|
|
|
39
39
|
console.warn(`\n⚠️ rejected 决策缺否决理由/复潮条件(needsWait)——这些条目未写盘,其余条目照常提炼。`)
|
|
40
40
|
console.warn(` 裁决指引(确认归档 --confirm 前处理):`)
|
|
41
41
|
console.warn(` ① 补录:把缺失字段补进 ${join(changeDir, 'decisions.md')} 后重跑提炼(幂等)——`)
|
|
42
|
+
console.warn(` 可复制补丁样例(在对应 D-xxx@vN 条目末尾追加两行,字段名与值之间用冒号+空格):`)
|
|
43
|
+
console.warn(` - 否决理由: <一句话核心劣势>`)
|
|
44
|
+
console.warn(` - 复潮条件: <何种信号下可重议本方案>`)
|
|
42
45
|
console.warn(` node --input-type=module -e "import { distillIntoKnowledge } from '<sillyspec 源码路径>/src/decision-distill.js'; console.log(JSON.stringify(distillIntoKnowledge(${JSON.stringify(changeDir)}, ${JSON.stringify(knowledgeRoot)}, ${JSON.stringify(headHash)}), null, 2))"`)
|
|
43
46
|
console.warn(` ② 跳过:经用户裁决不入库该条,并在「确认归档」--output 注记跳过原因。`)
|
|
44
47
|
}
|
package/src/run/command.js
CHANGED
|
@@ -232,7 +232,7 @@ export async function ensureStageSteps(progress, stageName, cwd, specDir = null)
|
|
|
232
232
|
function emitReadonlyEmptyGuide() {
|
|
233
233
|
console.log('ℹ️ 未找到进度数据(只读查询不建变更)')
|
|
234
234
|
console.log(' → 可开始轻量变更(--input 引号内换行合法,下例可照抄):')
|
|
235
|
-
console.log(' sillyspec flow start --change <YYYY-MM-DD-名> --input "
|
|
235
|
+
console.log(' sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句中文概括(首行即变更标题);随后动机与背景')
|
|
236
236
|
console.log('')
|
|
237
237
|
console.log(' 成功标准:')
|
|
238
238
|
console.log(' - <可验证标准>"')
|
|
@@ -858,7 +858,7 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
|
|
|
858
858
|
quickFallbackUsed = true
|
|
859
859
|
} else {
|
|
860
860
|
console.log(' 本仓无近期 quick 会话记录(current marker 与 quick-sessions 均空)——新工作请走轻量变更(--input 引号内换行合法,可照抄):')
|
|
861
|
-
console.log(' sillyspec flow start --change <YYYY-MM-DD-名> --input "
|
|
861
|
+
console.log(' sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句中文概括(首行即变更标题);随后动机与背景')
|
|
862
862
|
console.log('')
|
|
863
863
|
console.log(' 成功标准:')
|
|
864
864
|
console.log(' - <可验证标准>"')
|
|
@@ -2076,7 +2076,7 @@ async function runAutoMode(pm, progress, cwd, flags, changeName, platformOpts =
|
|
|
2076
2076
|
if (classification.mode === 'quick') {
|
|
2077
2077
|
console.log(`📊 auto 模式分类:${classification.reason} 判为小变更`)
|
|
2078
2078
|
console.log(` 小变更请走轻量变更(quick 通道已退役,v3.30.0 起拒绝新会话;--input 引号内换行合法,可照抄):`)
|
|
2079
|
-
console.log(' sillyspec flow start --change <YYYY-MM-DD-名> --input "
|
|
2079
|
+
console.log(' sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句中文概括(首行即变更标题);随后动机与背景')
|
|
2080
2080
|
console.log('')
|
|
2081
2081
|
console.log(' 成功标准:')
|
|
2082
2082
|
console.log(' - <可验证标准>"')
|
|
@@ -816,9 +816,14 @@ export async function bakeArchiveTimeline({ cwd, specBase, changeName, destDir,
|
|
|
816
816
|
const { readWatcherEvents, watcherEventsPath } = await import('../watcher.js')
|
|
817
817
|
const res = readWatcherEvents({ runtimeRoot, change: changeName })
|
|
818
818
|
if (!res.exists) return { ok: true, skipped: true, reason: `无事件流(${watcherEventsPath(runtimeRoot, changeName)})` }
|
|
819
|
-
|
|
819
|
+
// 侧车透传(2026-10-14-timeline-ticktime-anchor-scope):destDir 内 task-ticks.jsonl 已随归档包
|
|
820
|
+
// ——烤制面读包内侧车(与 tasks.md 同源同路径形态),精确勾选时刻跨机可读;锚收敛 changeName
|
|
821
|
+
// 进解析(消息不含变更名的提交不锚任务行)。
|
|
822
|
+
const { parseTaskLines, renderBakedTimeline, resolveCommitAnchors, parseTickLog, readBirthTs } = await import('../timeline.js')
|
|
820
823
|
let tasks = null
|
|
821
824
|
try { tasks = parseTaskLines(readRawImpl(join(destDir, 'tasks.md'), 'utf8')) } catch { /* 任务面缺失照烤(渲染层有降级段) */ }
|
|
825
|
+
let tickLog = []
|
|
826
|
+
try { tickLog = parseTickLog(readRawImpl(join(destDir, 'task-ticks.jsonl'), 'utf8')) } catch { /* 侧车缺失照烤(时刻退推断面) */ }
|
|
822
827
|
let tier = null
|
|
823
828
|
try { const m = /^tier:\s*(\S+)/m.exec(readRawImpl(join(destDir, 'flow-state.yaml'), 'utf8')); if (m) tier = m[1] } catch { /* 头脑风暴预段等无 flow-state 按未知 */ }
|
|
824
829
|
const look = gitLook || ((hash) => {
|
|
@@ -829,12 +834,12 @@ export async function bakeArchiveTimeline({ cwd, specBase, changeName, destDir,
|
|
|
829
834
|
const sep2 = rest.indexOf('\x1f')
|
|
830
835
|
return { hash: out.slice(0, sep), dateISO: rest.slice(0, sep2), message: rest.slice(sep2 + 1) }
|
|
831
836
|
})
|
|
832
|
-
const anchors = resolveCommitAnchors(res.events, look)
|
|
837
|
+
const anchors = resolveCommitAnchors(res.events, look, changeName)
|
|
833
838
|
let eventsRaw = null
|
|
834
839
|
try { eventsRaw = readRawImpl(watcherEventsPath(runtimeRoot, changeName), 'utf8') } catch { /* 副本缺源只烤渲染面 */ }
|
|
835
840
|
const oversize = eventsRaw != null && Buffer.byteLength(eventsRaw, 'utf8') > eventsCopyMaxBytes
|
|
836
841
|
// 评审 P3-1 清偿:副本未落盘(读源失败或超帽)时头注记必须显式「未随包」,不虚报在场
|
|
837
|
-
const timelineMd = renderBakedTimeline({ change: changeName, events: res.events, tasks, anchors, birthTs: readBirthTs(destDir), tier, bakedAtIso: nowIso || new Date().toISOString(), eventsCopySkipped: eventsRaw == null || oversize })
|
|
842
|
+
const timelineMd = renderBakedTimeline({ change: changeName, events: res.events, tasks, anchors, birthTs: readBirthTs(destDir), tier, bakedAtIso: nowIso || new Date().toISOString(), eventsCopySkipped: eventsRaw == null || oversize, tickLog })
|
|
838
843
|
writeImpl(join(destDir, 'timeline.md'), timelineMd)
|
|
839
844
|
const files = ['timeline.md']
|
|
840
845
|
if (eventsRaw != null && !oversize) {
|
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
|
@@ -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 补生成;已有可复跑 HTML 原型则登记复用:复制进变更目录并命名 prototype-<名称>.html,execute
|
|
227
|
+
console.warn(` ⚠️ design.md 命中前端文件类型(${feExts.join('/')})但变更目录无 prototype-*.html——轻微样式改动可忽略;布局/交互流程级变化应补原型(sillyspec run brainstorm --reopen 回到「分段展示设计」,或直接让 agent 补生成;已有可复跑 HTML 原型则登记复用:复制进变更目录并命名 prototype-<名称>.html,execute 按此命名注入;本仓形态未在 local.yaml ui_prototype 登记的,收口前补登记——form: real-code 或 single-html+豁免 ruling)`)
|
|
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 收口)。──
|
|
@@ -1304,18 +1397,11 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
1304
1397
|
// 快照含基线债文件时恒败,agent 被逼范围外清偿或逃生口)。失败输出提及文件 × 本变更
|
|
1305
1398
|
// 文件集零交集 → 存量债 advisory 降档放行(债非本变更引入,硬拦只是制造范围外功);
|
|
1306
1399
|
// 有交集 / 输出无可识别路径(unattributable,纯配置错误类)→ 维持硬拦(保守)。──
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
lintOwnership = triageLintOwnership({ failureFiles: lintCheck.failureFiles || [], changeFiles: lintScopeFiles })
|
|
1313
|
-
} catch { /* 鉴定链异常维持硬拦(保守) */ }
|
|
1314
|
-
if (lintOwnership && lintOwnership.verdict === 'pre-existing') {
|
|
1315
|
-
console.warn('\n⚠️ Verify lint 实测失败,但归属鉴定为 HEAD 存量债(失败提及文件与本变更文件集零交集)——降档 advisory 不阻断 verify 完成:')
|
|
1316
|
-
console.warn(` 存量债文件:${(lintCheck.failureFiles || []).join('、') || '(见上方输出)'}`)
|
|
1317
|
-
console.warn(' 处置建议:单独 quick 机械清偿(顺手清债解锁全仓 lint 门),或本变更确认无关时设 SILLYSPEC_VERIFY_LINT_GATE=advisory(审计留痕)。债随基线存在,与本变更交付无关。')
|
|
1318
|
-
} else {
|
|
1400
|
+
// 2026-10-15-lint-triage-path-unify:判定与文案收敛到 ./lint-ownership.js(与 noAI 质量
|
|
1401
|
+
// 扫描路径同源——原两路径分叉见消费仓坑档案 verify-lint-triage-path-divergence.md)。
|
|
1402
|
+
const { lintOwnershipDowngrade } = await import('./lint-ownership.js')
|
|
1403
|
+
const lintAdvisoryDowngraded = await lintOwnershipDowngrade({ lintCheck, cwd, changeName, specBase })
|
|
1404
|
+
if (!lintAdvisoryDowngraded) {
|
|
1319
1405
|
console.error('\n❌ verify 阶段被阻断:CLI 亲自实测 lint 失败(agent 的 lint 自报告与实测不符时以实测为准)。')
|
|
1320
1406
|
if (usedSnapInfo) {
|
|
1321
1407
|
try { const { printSnapshotFailureHint } = await import('./gate-snapshot.js')
|
|
@@ -1339,7 +1425,7 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
1339
1425
|
}
|
|
1340
1426
|
console.error(' 修复后重跑 --done(进度不丢);确认要跳过实测请设 SILLYSPEC_VERIFY_LINT_GATE=advisory(审计留痕)。')
|
|
1341
1427
|
return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts, { type: 'gate_rollback', detail: 'verify-lint' })
|
|
1342
|
-
} //
|
|
1428
|
+
} // 硬拦分支结束(降档放行时直接落到后续 parity/删除探针——rollback 语义不变)
|
|
1343
1429
|
}
|
|
1344
1430
|
// 契约 parity 对账:扫前端 API 调用 vs execute 提取的 provider endpoint artifact。
|
|
1345
1431
|
// 接线自 contract-matrix pipeline(verifyApiParity 的 CLI 入口)。
|
|
@@ -1351,95 +1437,6 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
1351
1437
|
const { runVerifyDeletionCheck, printVerifyDeletionCheck } = await import('../verify-postcheck.js')
|
|
1352
1438
|
const deletionCheck = runVerifyDeletionCheck({ cwd, specBase, changeName })
|
|
1353
1439
|
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
1440
|
// ── 行号漂移自动重锚(2026-09-09 §7-6:quick --done 已接,verify/archive 收尾补接)──
|
|
1444
1441
|
// 本变更 diff 中的 .md 文档经 autoReanchorDocRefs 定点重锚(fixable 唯一/优选命中才改 +
|
|
1445
1442
|
// 同口径回执),消灭「src 改动平移行号 → 下次 docs gate 拦」的人工往返。挂 verify 收尾即
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lint 归属鉴定降档(共享 helper)——2026-10-15-lint-triage-path-unify。
|
|
3
|
+
*
|
|
4
|
+
* 背景(消费仓坑档案 docs/sillyspec/verify-lint-triage-path-divergence.md,2026-10-15
|
|
5
|
+
* -dsh-engine-acp verify 实证):降档判定原先只实现在 gates.js 的 verify --done 拦截面;
|
|
6
|
+
* verify step6「noAI 质量扫描」(verify-quality-scan.js)只做 shouldBlockVerifyLint 二值
|
|
7
|
+
* 判定直接 throw——同一失败两路径行为分叉(step6 throw / --done 降档)。本模块把判定与
|
|
8
|
+
* 提示文案收敛为单一来源,两路径同接线。
|
|
9
|
+
*
|
|
10
|
+
* 语义(R4-S-F 实证口径,与 gates.js 原内联实现等价):
|
|
11
|
+
* - pre-existing(失败提及文件 × 本变更文件集零交集)→ 打印降档 warning,返回 true(放行);
|
|
12
|
+
* - owned(有交集)/ unattributable(输出无可识别路径)/ 鉴定链异常 → 返回 false
|
|
13
|
+
* (维持硬拦,保守——无法排除本变更责任时宁可拦不可漏)。
|
|
14
|
+
*
|
|
15
|
+
* @param {{lintCheck?: {failureFiles?: string[]}, cwd: string, changeName: string, specBase: string, importer?: () => Promise<any>}} input
|
|
16
|
+
* importer 可注入(测试用);缺省动态加载 ../verify-postcheck.js(triageLintOwnership +
|
|
17
|
+
* resolveVerifyChangedFiles——运行时 import 避免模块加载环)。
|
|
18
|
+
* @returns {Promise<boolean>} true=降档放行(advisory),false=维持硬拦。
|
|
19
|
+
*/
|
|
20
|
+
export async function lintOwnershipDowngrade({ lintCheck, cwd, changeName, specBase, importer }) {
|
|
21
|
+
const load = importer || (async () => import('../verify-postcheck.js'))
|
|
22
|
+
let lintOwnership = null
|
|
23
|
+
try {
|
|
24
|
+
const { triageLintOwnership, resolveVerifyChangedFiles } = await load()
|
|
25
|
+
const lintScopeFiles = (resolveVerifyChangedFiles(cwd, changeName, null, { includeWorkingTree: true, specBase }) || [])
|
|
26
|
+
.map((f) => String(f).replace(/\\/g, '/')).filter((f) => !f.startsWith('.sillyspec/'))
|
|
27
|
+
lintOwnership = triageLintOwnership({ failureFiles: (lintCheck && lintCheck.failureFiles) || [], changeFiles: lintScopeFiles })
|
|
28
|
+
} catch { /* 鉴定链异常维持硬拦(保守) */ }
|
|
29
|
+
if (lintOwnership && lintOwnership.verdict === 'pre-existing') {
|
|
30
|
+
const failureFiles = (lintCheck && lintCheck.failureFiles) || []
|
|
31
|
+
console.warn('\n⚠️ Verify lint 实测失败,但归属鉴定为 HEAD 存量债(失败提及文件与本变更文件集零交集)——降档 advisory 不阻断 verify 推进:')
|
|
32
|
+
console.warn(` 存量债文件:${failureFiles.join('、') || '(见上方输出)'}`)
|
|
33
|
+
console.warn(' 处置建议:单独清偿(顺手清债解锁全仓 lint 门),或本变更确认无关时设 SILLYSPEC_VERIFY_LINT_GATE=advisory(审计留痕)。债随基线存在,与本变更交付无关。')
|
|
34
|
+
return true
|
|
35
|
+
}
|
|
36
|
+
return false
|
|
37
|
+
}
|