sillyspec 3.32.10 → 3.33.1
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 +66 -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/archive-delta.js +4 -3
- package/src/ceremony-tier.js +2 -1
- package/src/change-delete.js +6 -5
- package/src/contract-matrix.js +2 -1
- package/src/cross-placement.js +5 -1
- package/src/db.js +8 -2
- package/src/decision-distill.js +29 -3
- package/src/decision-redomain.js +148 -0
- package/src/deleted-ledger.js +2 -1
- package/src/design-facts.js +3 -3
- package/src/docs-debt.js +3 -3
- package/src/doctor-diagnostics.js +8 -3
- package/src/flow-draft.js +2 -1
- package/src/flow-parity.js +10 -2
- package/src/flow.js +112 -63
- package/src/forensics.js +236 -0
- package/src/fr-index.js +2 -1
- package/src/friction-ledger.js +2 -1
- package/src/friction-tally.js +8 -4
- package/src/handoff.js +2 -1
- package/src/hooks/worktree-guard.js +5 -5
- package/src/index.js +75 -8
- package/src/knowledge-graph.js +3 -4
- package/src/knowledge-hits.js +2 -1
- package/src/knowledge-vector.js +2 -1
- package/src/module-impact.js +2 -5
- package/src/modules.js +31 -0
- package/src/progress/consistency-doctor.js +5 -2
- package/src/progress/stage-machine.js +6 -2
- package/src/progress.js +19 -8
- package/src/quicklog.js +194 -9
- package/src/review-dispatch.js +4 -3
- package/src/review-tier.js +2 -1
- package/src/route-hindsight.js +4 -3
- package/src/run/bg-sync.js +10 -3
- package/src/run/command.js +13 -11
- package/src/run/complete-handlers.js +40 -37
- package/src/run/complete.js +12 -8
- package/src/run/gate-snapshot-ledger.js +2 -1
- package/src/run/gate-snapshot.js +57 -11
- package/src/run/gates.js +38 -21
- package/src/run/lint-ownership.js +37 -0
- package/src/run/prompt.js +46 -30
- package/src/run/scan-profile.js +2 -1
- package/src/run/shared.js +109 -5
- package/src/run/stage.js +5 -4
- package/src/run/test-ledger.js +2 -1
- package/src/run/verify-quality-scan.js +21 -6
- package/src/runtime-paths.js +189 -0
- package/src/scan-refresh.js +4 -3
- package/src/scope-audit.js +7 -6
- package/src/sentinel-assertions.js +37 -0
- package/src/spec-sync.js +118 -23
- package/src/stage-contract.js +24 -2
- package/src/stage-review.js +5 -3
- package/src/stages/brainstorm.js +72 -21
- package/src/stages/plan-postcheck.js +13 -2
- package/src/stages/plan.js +19 -0
- package/src/sync-noise.js +3 -2
- package/src/sync.js +71 -15
- package/src/task-done.js +2 -1
- package/src/task-review.js +7 -5
- package/src/task-tick.js +23 -1
- package/src/test-bindings.js +32 -3
- package/src/ui-visual.js +119 -4
- package/src/validate-artifacts.js +2 -1
- package/src/verify-draft.js +2 -1
- package/src/verify-postcheck.js +128 -27
- package/src/verify-probes.js +157 -52
- package/src/watcher.js +15 -9
- package/src/worktree-apply.js +28 -3
- package/src/write-audit.js +2 -1
- package/templates/agents-instruction.md +1 -1
package/src/run/complete.js
CHANGED
|
@@ -17,12 +17,13 @@
|
|
|
17
17
|
* - node: join(path) + existsSync/readdirSync/readFileSync/mkdirSync/writeFileSync/appendFileSync/statSync(fs)
|
|
18
18
|
*/
|
|
19
19
|
import { join, isAbsolute, resolve } from 'node:path'
|
|
20
|
+
import { runtimeStatePath } from '../runtime-paths.js'
|
|
20
21
|
import { existsSync, readFileSync, mkdirSync, writeFileSync, appendFileSync } from 'node:fs'
|
|
21
22
|
import { writeAtomicSync } from '../fs-atomic.js'
|
|
22
23
|
import { gitQuiet } from '../git-helper.js'
|
|
23
24
|
import { writeCloseTraceArtifacts, projectTraceFaceRows } from '../flow-parity.js'
|
|
24
25
|
import { withFileLock } from '../quicklog.js'
|
|
25
|
-
import { triggerSync, WAIT_MARKER_RE, getStageSteps, formatWaitOptions, resolveRuntimeRoot, getOrCreateMultiRepoContext, resolveChangeDir, writePlatformDocsPointer, isRetiredAutoBrainstormTable, printRetiredAutoBrainstormRefusal } from './shared.js'
|
|
26
|
+
import { triggerSync, triggerSyncTerminalStrict, WAIT_MARKER_RE, getStageSteps, formatWaitOptions, resolveRuntimeRoot, getOrCreateMultiRepoContext, resolveChangeDir, writePlatformDocsPointer, isRetiredAutoBrainstormTable, printRetiredAutoBrainstormRefusal } from './shared.js'
|
|
26
27
|
import { isExplicitReviewWrite, collectWorktreeChangedFiles } from '../task-review.js'
|
|
27
28
|
|
|
28
29
|
// 退役自动道守门(2026-10-14-brainstorm-auto-retire):brainstorm-auto 4 步表已随自动道删除。
|
|
@@ -828,7 +829,10 @@ export async function completeStep(pm, progress, stageName, cwd, outputText, inp
|
|
|
828
829
|
|
|
829
830
|
// gate 全过:persist completed(task-01 移后)。此处到 _write 之间若崩,DB 仍 in-progress(内存已 completed 但未落盘),下次进 CLI 读 DB 即 in-progress,不产生"假 completed"。
|
|
830
831
|
pm._write(cwd, progress, changeName)
|
|
831
|
-
|
|
832
|
+
// 终态强校验(2026-10-17-archive-half-state-location-stuck-active):状态机驱动——仅
|
|
833
|
+
// DB status 已达 archived/deleted(archive 阶段完成即此态)走内联强校验,其余阶段
|
|
834
|
+
// 原样委托 triggerSync(后台化不变,非终态零行为变化)。见 run/shared.js 契约注释。
|
|
835
|
+
await triggerSyncTerminalStrict(cwd, changeName, platformOpts, { completion: true })
|
|
832
836
|
|
|
833
837
|
const total = steps.length
|
|
834
838
|
console.log(`✅ ${stageName} 阶段已完成(${total}/${total} 步)`)
|
|
@@ -843,7 +847,7 @@ export async function completeStep(pm, progress, stageName, cwd, outputText, inp
|
|
|
843
847
|
let stageLedger = null
|
|
844
848
|
let crossStage = false
|
|
845
849
|
try {
|
|
846
|
-
const ledgerPath = join(specBase, '.runtime', `stage-session-ledger-${changeName}.json`)
|
|
850
|
+
const ledgerPath = runtimeStatePath(join(specBase, '.runtime'), `stage-session-ledger-${changeName}.json`)
|
|
847
851
|
try { stageLedger = JSON.parse(readFileSync(ledgerPath, 'utf8')) } catch { /* 首阶段 */ }
|
|
848
852
|
const r = updateStageSessionLedger(stageLedger, process.env.SILLYSPEC_SESSION_ID, stageName)
|
|
849
853
|
stageLedger = r.ledger
|
|
@@ -1018,7 +1022,7 @@ export async function completeStep(pm, progress, stageName, cwd, outputText, inp
|
|
|
1018
1022
|
if (stageName === 'execute' && changeName && defSteps && defSteps[currentIdx] && defSteps[nextPendingIdx] &&
|
|
1019
1023
|
/Wave \d+ 执行/.test(defSteps[currentIdx].name || '') && /Wave \d+ 执行/.test(defSteps[nextPendingIdx].name || '')) {
|
|
1020
1024
|
try {
|
|
1021
|
-
const ledgerPath = join(specBase, '.runtime', `wave-session-ledger-${changeName}.json`)
|
|
1025
|
+
const ledgerPath = runtimeStatePath(join(specBase, '.runtime'), `wave-session-ledger-${changeName}.json`)
|
|
1022
1026
|
let ledger = null
|
|
1023
1027
|
try { ledger = JSON.parse(readFileSync(ledgerPath, 'utf8')) } catch { /* 首 Wave */ }
|
|
1024
1028
|
const { ledger: nextLedger, crossWaveAdvisory } = updateWaveSessionLedger(ledger, process.env.SILLYSPEC_SESSION_ID, defSteps[currentIdx].name)
|
|
@@ -1141,7 +1145,7 @@ async function printVerifyScopeDrift({ cwd, changeName, specBase, platformOpts,
|
|
|
1141
1145
|
snapshot = JSON.parse(readFileSync(join(specBase, 'changes', changeName, 'scope-audit.json'), 'utf8'))
|
|
1142
1146
|
} catch {
|
|
1143
1147
|
try {
|
|
1144
|
-
snapshot = JSON.parse(readFileSync(
|
|
1148
|
+
snapshot = JSON.parse(readFileSync(runtimeStatePath(resolveRuntimeRoot(platformOpts, specBase), `scope-audit-${changeName}.json`), 'utf8'))
|
|
1145
1149
|
} catch { /* 快照缺失/损坏 → 跳过对比只打当前 totals */ }
|
|
1146
1150
|
}
|
|
1147
1151
|
}
|
|
@@ -1559,7 +1563,7 @@ export async function autoTickTasksFromCommitSubjects({ changeName, cwd, specBas
|
|
|
1559
1563
|
try {
|
|
1560
1564
|
const _tasksMdPath = join(specBase, 'changes', changeName, 'tasks.md')
|
|
1561
1565
|
if (!existsSync(_tasksMdPath)) return { ticked: 0, skippedNoAnchor: 0 }
|
|
1562
|
-
const _runIdFile =
|
|
1566
|
+
const _runIdFile = runtimeStatePath(resolveRuntimeRoot(platformOpts, specBase), `current-execute-run-id-${changeName}`)
|
|
1563
1567
|
let _tickLog = ''
|
|
1564
1568
|
try {
|
|
1565
1569
|
if (existsSync(_runIdFile)) {
|
|
@@ -1640,7 +1644,7 @@ export async function autoCheckPlanFromReviews({ stageName, changeName, cwd, pla
|
|
|
1640
1644
|
// plan.md Wave 引用行无 checkbox,无处可勾)。tasks.md 存在性作前置。
|
|
1641
1645
|
const tasksMdPath = join(changeDir, 'tasks.md')
|
|
1642
1646
|
const runtimeRoot = resolveRuntimeRoot(platformOpts, specBaseLc)
|
|
1643
|
-
const runIdFile =
|
|
1647
|
+
const runIdFile = runtimeStatePath(runtimeRoot, `current-execute-run-id-${changeName}`)
|
|
1644
1648
|
if (!existsSync(tasksMdPath) || !existsSync(runIdFile)) {
|
|
1645
1649
|
return { autoChecked: false, checkedCount: 0, skippedCount: 0 }
|
|
1646
1650
|
}
|
|
@@ -1798,7 +1802,7 @@ export async function detectExecuteBatchFinish({ pm, stageName, changeName, cwd,
|
|
|
1798
1802
|
// 平台模式 runtimeRoot / worktree 漂移 specDriftAnchor 不透传时 marker 恒读不到
|
|
1799
1803
|
// → 每个 task 进 blockedTasks → 批量完成静默失效
|
|
1800
1804
|
const runtimeRoot = resolveRuntimeRoot(platformOpts, specBase)
|
|
1801
|
-
const runIdFile =
|
|
1805
|
+
const runIdFile = runtimeStatePath(runtimeRoot, `current-execute-run-id-${changeName}`)
|
|
1802
1806
|
// ── 缺席草稿自动补齐(坑 task-review-retired-archive-mismatch,2026-10-15 归档门口实证)──
|
|
1803
1807
|
// Task Review 层退役(2026-09-26)后 execute 批量完成不再产 per-task review.json,但本函数
|
|
1804
1808
|
// 下方复核循环与 archive step2 完成度判定(真相源=review.json verdict)仍以它在场为前提——
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* 异常面:全部 fail-open 吞掉(账本/回收任一异常零阻断,退现状残留)。
|
|
17
17
|
*/
|
|
18
18
|
import { execFileSync } from 'node:child_process'
|
|
19
|
+
import { runtimeStatePath } from '../runtime-paths.js'
|
|
19
20
|
import { existsSync, mkdirSync, readFileSync } from 'node:fs'
|
|
20
21
|
import { basename, dirname, join, resolve, sep } from 'node:path'
|
|
21
22
|
import { tmpdir } from 'node:os'
|
|
@@ -28,7 +29,7 @@ const STALE_HOURS_DEFAULT = 24
|
|
|
28
29
|
|
|
29
30
|
/** 账本文件路径(runtime 域:.sillyspec/.runtime/active-gate-snapshots.json) */
|
|
30
31
|
export function gateSnapshotLedgerPath(runtimeRoot) {
|
|
31
|
-
return
|
|
32
|
+
return runtimeStatePath(runtimeRoot, LEDGER_FILE)
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
/** 读账本(损坏/缺失/非数组 → 空数组,fail-open) */
|
package/src/run/gate-snapshot.js
CHANGED
|
@@ -494,7 +494,12 @@ export function applyGateSnapshotCopy(cwd, snapshotRoot) {
|
|
|
494
494
|
* @param {{ cwd: string, files: string[] }} opts cwd=主仓根;files=本会话变更文件(仓库根相对 POSIX)
|
|
495
495
|
* @returns {{ snapshotRoot: string, cleanup: () => void, reason?: string }|null} 失败返回 null(调用方回退主仓)
|
|
496
496
|
*/
|
|
497
|
-
export function createGateSnapshot({ cwd, files, sourceRoot = null, skipImportSmoke = false, mergeBase = null, runtimeRoot = null }) {
|
|
497
|
+
export function createGateSnapshot({ cwd, files, sourceRoot = null, skipImportSmoke = false, mergeBase = null, runtimeRoot = null, failRef = null }) {
|
|
498
|
+
// failRef 可选出参(坑 snapshot-fallback-reason-blind,2026-10-16 下游实证):所有失败分支
|
|
499
|
+
// 把结构化原因写进 failRef.reason——调用方(verify --done 实测门 / noAI 质量扫描)消费它
|
|
500
|
+
// 替换硬编码笼统文案,降级原因可直读(pnpm 布局拒建 ≠ 文件集为空 ≠ junction 失败)。
|
|
501
|
+
// 返回契约不变(失败恒 null);不传 failRef 行为与既有逐字节一致。
|
|
502
|
+
const setFail = (reason) => { if (failRef && typeof failRef === 'object') failRef.reason = reason }
|
|
498
503
|
let snapshotRoot = null
|
|
499
504
|
try {
|
|
500
505
|
// 前置:主仓须是 git 仓且有 HEAD(无 git 环境回退主仓现行为)
|
|
@@ -519,6 +524,7 @@ export function createGateSnapshot({ cwd, files, sourceRoot = null, skipImportSm
|
|
|
519
524
|
// 与其让沙箱报无关错误逼 advisory,不如布局命中即作废快照回退主仓(宁可主仓口径)。
|
|
520
525
|
const layoutHit = detectSymlinkStoreLayout(cwd)
|
|
521
526
|
if (layoutHit) {
|
|
527
|
+
setFail(`symlink-store 布局拒建(${layoutHit}):node_modules 符号链接 store 经 junction 跨根失效,实测必假败——设计性回退主仓实测`)
|
|
522
528
|
console.warn(`⚠️ 门禁快照对 ${layoutHit} 布局不可靠(node_modules 符号链接 store 经 junction 跨根失效,实测必假败)——跳过快照回退主仓实测;主仓失败再做污染归属鉴定`)
|
|
523
529
|
return null
|
|
524
530
|
}
|
|
@@ -625,6 +631,7 @@ export function createGateSnapshot({ cwd, files, sourceRoot = null, skipImportSm
|
|
|
625
631
|
// 回退主仓现行为(主仓口径可能被并行脏文件污染,但环境真实;两害取轻 + 回退原因可见)。
|
|
626
632
|
const envMissing = envDirsLinked(cwd, snapshotRoot)
|
|
627
633
|
if (envMissing.length > 0) {
|
|
634
|
+
setFail(`环境目录链接不完整(${envMissing.join('、')} 在主仓存在、快照内缺失)——快照作废回退主仓实测`)
|
|
628
635
|
console.warn(`⚠️ 门禁快照环境不完整(${envMissing.join('、')} 在主仓存在、快照内缺失)——快照作废回退主仓实测(宁可主仓口径不可假沙箱硬挂;回退后失败先做污染归属鉴定)`)
|
|
629
636
|
// 与正常 cleanup/失败 catch 同一清理体:双清确认才销账(该早退曾残留旧式 --quiet
|
|
630
637
|
// 清理与无重试 rmSync——独立审查 P1-1 根因未除尽的漏网面)
|
|
@@ -723,7 +730,10 @@ export function createGateSnapshot({ cwd, files, sourceRoot = null, skipImportSm
|
|
|
723
730
|
return { snapshotRoot, cleanup, overlaid, pinnedCacheLinks }
|
|
724
731
|
} catch (e) {
|
|
725
732
|
// 基建失败(worktree add 拒绝/磁盘满/…):尽力清理后回退主仓;销账同走双清确认
|
|
726
|
-
// (D-004@v2
|
|
733
|
+
// (D-004@v2:清不掉的条目保留待回收——它不是悬空条目,是待回收记录)。
|
|
734
|
+
// 异常细节不再静默(坑 snapshot-fallback-reason-blind):failRef 带原因 + warn 落日志。
|
|
735
|
+
setFail(`快照基建异常: ${e && e.message ? e.message : e}`)
|
|
736
|
+
try { console.warn(`⚠️ 门禁快照基建失败(回退主仓实测): ${e && e.message ? e.message : e}`) } catch { /* 输出失败不影响回退 */ }
|
|
727
737
|
if (snapshotRoot) {
|
|
728
738
|
try {
|
|
729
739
|
const { dirRemoved, worktreeCleaned } = cleanupSnapshot({ snapshotRoot, cwd })
|
|
@@ -870,7 +880,8 @@ export function printSnapshotFailureHint(snapInfo, { offEnv = 'SILLYSPEC_VERIFY_
|
|
|
870
880
|
* @param {{ cwd: string, changeName: string, specBase: string, platformOpts?: object }} opts
|
|
871
881
|
* @returns {Promise<{snapshotRoot: string, cleanup: () => void, overlaid: number, changeFileCount: number}|null>}
|
|
872
882
|
*/
|
|
873
|
-
export async function createVerifyGateSnapshot({ cwd, changeName, specBase, platformOpts = {} }) {
|
|
883
|
+
export async function createVerifyGateSnapshot({ cwd, changeName, specBase, platformOpts = {}, failRef = null }) {
|
|
884
|
+
const setFail = (reason) => { if (failRef && typeof failRef === 'object') failRef.reason = reason }
|
|
874
885
|
try {
|
|
875
886
|
const { resolveVerifyChangedFiles } = await import('../verify-postcheck.js')
|
|
876
887
|
const { resolveRuntimeRoot } = await import('./shared.js')
|
|
@@ -879,10 +890,19 @@ export async function createVerifyGateSnapshot({ cwd, changeName, specBase, plat
|
|
|
879
890
|
let verifyRuntimeRoot = null
|
|
880
891
|
try { verifyRuntimeRoot = resolveRuntimeRoot(platformOpts, specBase) } catch { verifyRuntimeRoot = null }
|
|
881
892
|
const { splitOwnVsForeignDiffFiles } = await import('../foreign-declared.js')
|
|
882
|
-
|
|
893
|
+
// 变更文件集为空三分成因区分(坑 snapshot-fallback-reason-blind):null=git 不可用/解析
|
|
894
|
+
// 失败;[]=HEAD 无 diff;他者声明过滤清空在下方单独计——三者处置同为回退主仓,但排查
|
|
895
|
+
// 出路完全不同,笼统「变更文件集为空」曾把 pnpm 布局拒建也误标成它。
|
|
896
|
+
const rawChangeFiles = resolveVerifyChangedFiles(cwd, changeName, null, {
|
|
883
897
|
specBase, includeWorkingTree: true,
|
|
884
|
-
})
|
|
885
|
-
|
|
898
|
+
})
|
|
899
|
+
let changeFiles = rawChangeFiles || []
|
|
900
|
+
if (changeFiles.length === 0) {
|
|
901
|
+
setFail(rawChangeFiles === null
|
|
902
|
+
? '变更文件集为空(git 不可用/解析失败——非 git 仓或 git 异常,无从定向 overlay)'
|
|
903
|
+
: '变更文件集为空(HEAD 无 diff——改动可能已全部提交,无可隔离面)')
|
|
904
|
+
return null // 无变更文件集 → 无从定向,回退主仓
|
|
905
|
+
}
|
|
886
906
|
// 他者声明归属过滤(坑 verify-reconcile-foreign-wip 同款):in-place 模式 working-tree
|
|
887
907
|
// 并入后主仓全部在途文件进场——他者活跃变更显式声明的文件(quick --files / 他者 design
|
|
888
908
|
// 清单)剔除,只留本变更相关面(无主文件保留,fail-closed 口径与 probe6 一致)。
|
|
@@ -891,7 +911,10 @@ export async function createVerifyGateSnapshot({ cwd, changeName, specBase, plat
|
|
|
891
911
|
if (foreign.length > 0) {
|
|
892
912
|
const foreignSet = new Set(foreign.map(x => x.file))
|
|
893
913
|
changeFiles = changeFiles.filter(f => !foreignSet.has(f))
|
|
894
|
-
if (changeFiles.length === 0)
|
|
914
|
+
if (changeFiles.length === 0) {
|
|
915
|
+
setFail(`变更文件集为空(${foreign.length} 个 diff 文件全部被他者声明过滤——并行会话声明面覆盖了本变更全部在途文件)`)
|
|
916
|
+
return null
|
|
917
|
+
}
|
|
895
918
|
}
|
|
896
919
|
} catch { /* 过滤失败退全量(fail-closed:宁可多 overlay 不漏本变更文件) */ }
|
|
897
920
|
|
|
@@ -920,8 +943,12 @@ export async function createVerifyGateSnapshot({ cwd, changeName, specBase, plat
|
|
|
920
943
|
}
|
|
921
944
|
} catch { /* meta 读取失败 → 主仓 cwd 源(files 已含本变更 working-tree 改动) */ }
|
|
922
945
|
|
|
923
|
-
const snap = createGateSnapshot({ cwd, files: changeFiles, sourceRoot, mergeBase: snapMergeBase, runtimeRoot: verifyRuntimeRoot })
|
|
924
|
-
if (!snap)
|
|
946
|
+
const snap = createGateSnapshot({ cwd, files: changeFiles, sourceRoot, mergeBase: snapMergeBase, runtimeRoot: verifyRuntimeRoot, failRef })
|
|
947
|
+
if (!snap) {
|
|
948
|
+
// createGateSnapshot 已把具体原因写进同一 failRef;无 reason(旧形态兜底)补笼统口径
|
|
949
|
+
if (failRef && typeof failRef === 'object' && !failRef.reason) setFail('快照基建不可用(createGateSnapshot 返回 null,无更细原因)')
|
|
950
|
+
return null
|
|
951
|
+
}
|
|
925
952
|
|
|
926
953
|
// 变更文档随快照:module-impact.md(test_strategy=evidence-auto 消费)/tasks.md 等
|
|
927
954
|
// 在 .sillyspec(gitignore)不进 HEAD,从主仓 specBase 复制 changes/<name>/**
|
|
@@ -940,7 +967,26 @@ export async function createVerifyGateSnapshot({ cwd, changeName, specBase, plat
|
|
|
940
967
|
} catch { /* 文档复制失败不连坐快照(test_strategy 降级 module 集仍可跑) */ }
|
|
941
968
|
|
|
942
969
|
return { ...snap, changeFileCount: changeFiles.length, sourceRoot }
|
|
943
|
-
} catch {
|
|
944
|
-
|
|
970
|
+
} catch (e) {
|
|
971
|
+
// 任一链路异常 → 回退主仓现行为。异常细节不再静默(坑 snapshot-fallback-reason-blind):
|
|
972
|
+
// failRef 带原因 + warn 落日志,排查不再靠 stdout 考古。
|
|
973
|
+
setFail(`快照链路异常: ${e && e.message ? e.message : e}`)
|
|
974
|
+
try { console.warn(`⚠️ 门禁快照链路异常(回退主仓实测): ${e && e.stack ? e.stack.split('\n')[0] : e}`) } catch { /* 输出失败不影响回退 */ }
|
|
975
|
+
return null
|
|
945
976
|
}
|
|
946
977
|
}
|
|
978
|
+
|
|
979
|
+
/**
|
|
980
|
+
* 快照口径观测字段(坑 snapshot-fallback-reason-blind,FR-04 落盘面):--done 实测门把
|
|
981
|
+
* used_snapshot 与降级原因经 runVerifyTestCheck 的 observability 通道带进 test-result.json
|
|
982
|
+
* extra(对齐 2026-10-09 复用观测先例)。快照在场时记录随快照销毁(无可降级事实);
|
|
983
|
+
* 回退主仓时记录恰好落在主仓 .runtime——降级证据存活在主仓,与用途一致。
|
|
984
|
+
* @param {boolean} used 快照是否真实在场
|
|
985
|
+
* @param {{ reason?: string }|null} failRef createVerifyGateSnapshot 的出参(可 null)
|
|
986
|
+
* @returns {{ used_snapshot: boolean, snapshot_fallback_reason?: string }}
|
|
987
|
+
*/
|
|
988
|
+
export function snapshotObservability(used, failRef) {
|
|
989
|
+
if (used) return { used_snapshot: true }
|
|
990
|
+
const reason = failRef && failRef.reason ? String(failRef.reason) : 'createVerifyGateSnapshot 返回 null(变更文件集为空 / 快照基建不可用)'
|
|
991
|
+
return { used_snapshot: false, snapshot_fallback_reason: reason }
|
|
992
|
+
}
|
package/src/run/gates.js
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* 不搬:ensureDepsFreshness(execute 入口 deps 自检,调用方 runStage 非 completeStep,归属未来 execute-handler)
|
|
29
29
|
*/
|
|
30
30
|
import { basename, join, relative, dirname } from 'node:path'
|
|
31
|
+
import { runtimeStatePath } from '../runtime-paths.js'
|
|
31
32
|
import { createHash } from 'node:crypto'
|
|
32
33
|
import { execFileSync } from 'node:child_process'
|
|
33
34
|
import { existsSync, readFileSync, readdirSync, statSync, mkdirSync, unlinkSync, writeFileSync } from 'node:fs'
|
|
@@ -224,7 +225,7 @@ export function warnMissingUiPrototype(stageName, changeName, currentStepName, s
|
|
|
224
225
|
const design = readFileSync(designPath, 'utf8')
|
|
225
226
|
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
227
|
if (feExts.length === 0) return
|
|
227
|
-
console.warn(` ⚠️ design.md 命中前端文件类型(${feExts.join('/')})但变更目录无 prototype-*.html——轻微样式改动可忽略;布局/交互流程级变化应补原型(sillyspec run brainstorm --reopen 回到「分段展示设计」,或直接让 agent 补生成;已有可复跑 HTML 原型则登记复用:复制进变更目录并命名 prototype-<名称>.html,execute
|
|
228
|
+
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
229
|
}
|
|
229
230
|
|
|
230
231
|
/**
|
|
@@ -444,8 +445,8 @@ export async function enforceDepsGate(stageName, cwd, changeName, step, steps, c
|
|
|
444
445
|
if (worktreeGone && changeName) {
|
|
445
446
|
try {
|
|
446
447
|
const runtimeRoot = resolveRuntimeRoot(platformOpts, specBase)
|
|
447
|
-
const legitimatelyEnded = existsSync(
|
|
448
|
-
|| existsSync(
|
|
448
|
+
const legitimatelyEnded = existsSync(runtimeStatePath(runtimeRoot, `apply-pathspec-${changeName}.txt`))
|
|
449
|
+
|| existsSync(runtimeStatePath(runtimeRoot, `execute-cleanup-${changeName}.json`))
|
|
449
450
|
if (legitimatelyEnded) {
|
|
450
451
|
console.log('ℹ️ worktree 已在 apply/execute 收尾时正当清理(凭据在 .runtime),deps 门放行')
|
|
451
452
|
return true
|
|
@@ -549,7 +550,7 @@ function rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx,
|
|
|
549
550
|
|
|
550
551
|
/** 档位状态文件路径(runtimeRoot 下 per-change;命名对齐 apply-pathspec-<change>.txt 先例) */
|
|
551
552
|
function ceremonyTierFilePath(runtimeRoot, changeName) {
|
|
552
|
-
return
|
|
553
|
+
return runtimeStatePath(runtimeRoot, `ceremony-tier-${changeName}.json`)
|
|
553
554
|
}
|
|
554
555
|
|
|
555
556
|
/**
|
|
@@ -1100,17 +1101,24 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
1100
1101
|
// 变更文档随快照(test_strategy 消费)。并行会话在途文件物理不进快照、不参与本门判定。
|
|
1101
1102
|
// 快照基建失败 → 回退主仓现行为(失败路径沿用 renderVerifyTestAttribution 归因提示)。
|
|
1102
1103
|
let verifyGateSnap = null
|
|
1104
|
+
let snapshotFailRef = null // createVerifyGateSnapshot 出参:结构化失败原因(FR-04,替代硬编码笼统文案)
|
|
1103
1105
|
if (!process.env.SILLYSPEC_VERIFY_GATE_SNAPSHOT_OFF) {
|
|
1104
1106
|
try {
|
|
1105
1107
|
const { createVerifyGateSnapshot } = await import('./gate-snapshot.js')
|
|
1106
|
-
|
|
1108
|
+
snapshotFailRef = {}
|
|
1109
|
+
verifyGateSnap = await createVerifyGateSnapshot({ cwd, changeName, specBase, platformOpts, failRef: snapshotFailRef })
|
|
1107
1110
|
if (verifyGateSnap) {
|
|
1108
1111
|
console.log(`🧪 Verify 实测隔离快照(根:${verifyGateSnap.snapshotRoot}):HEAD + 本变更 ${verifyGateSnap.changeFileCount} 个文件${verifyGateSnap.sourceRoot ? '(overlay 自 worktree——本变更分支内容定向跑)' : ''}(并行会话脏文件不参与门判定)`)
|
|
1109
1112
|
} else {
|
|
1110
|
-
// FR-02(2026-10-09-verify-reuse-friction):null 回退与异常回退同等可见 +
|
|
1113
|
+
// FR-02(2026-10-09-verify-reuse-friction):null 回退与异常回退同等可见 + 记账;
|
|
1114
|
+
// FR-04(2026-10-16-fix-downstream-tool-defects):reason 消费 failRef 结构化原因
|
|
1115
|
+
// (pnpm 布局拒建 / 文件集为空三分成因 / junction 失败 / 基建异常详情),不再笼统
|
|
1111
1116
|
try {
|
|
1112
1117
|
const { reportGateSnapshotFallback } = await import('./gate-snapshot.js')
|
|
1113
|
-
|
|
1118
|
+
const fbReason = snapshotFailRef && snapshotFailRef.reason
|
|
1119
|
+
? `createVerifyGateSnapshot 返回 null:${snapshotFailRef.reason}`
|
|
1120
|
+
: 'createVerifyGateSnapshot 返回 null(变更文件集为空 / 快照基建不可用)'
|
|
1121
|
+
if (typeof reportGateSnapshotFallback === 'function') await reportGateSnapshotFallback({ cwd, changeName, platformOpts, where: 'verify --done 实测门', reason: fbReason })
|
|
1114
1122
|
} catch { /* 报告失败不改变回退行为(含测试 mock 模块缺导出的形态) */ }
|
|
1115
1123
|
}
|
|
1116
1124
|
} catch (e) {
|
|
@@ -1120,6 +1128,22 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
1120
1128
|
} catch { /* 报告失败不改变回退行为(含测试 mock 模块缺导出的形态) */ }
|
|
1121
1129
|
}
|
|
1122
1130
|
}
|
|
1131
|
+
// FR-04 降级事实落盘:used_snapshot 与失败原因随 test-result.json extra 持久化(快照在场
|
|
1132
|
+
// 时记录随快照销毁;回退主仓时恰好落在主仓 .runtime——降级证据存活面与用途一致)。
|
|
1133
|
+
// 评审 P3-2 整改:OFF 开关是主动关闸,不得记成「基建故障/文件集为空」的笼统回退原因。
|
|
1134
|
+
let snapObsFields
|
|
1135
|
+
if (process.env.SILLYSPEC_VERIFY_GATE_SNAPSHOT_OFF) {
|
|
1136
|
+
snapObsFields = { used_snapshot: false, snapshot_fallback_reason: 'SILLYSPEC_VERIFY_GATE_SNAPSHOT_OFF=1 主动关闸(快照未尝试,非基建故障)' }
|
|
1137
|
+
} else if (verifyGateSnap) {
|
|
1138
|
+
snapObsFields = { used_snapshot: true }
|
|
1139
|
+
} else {
|
|
1140
|
+
try {
|
|
1141
|
+
const { snapshotObservability } = await import('./gate-snapshot.js')
|
|
1142
|
+
snapObsFields = snapshotObservability(false, snapshotFailRef)
|
|
1143
|
+
} catch {
|
|
1144
|
+
snapObsFields = { used_snapshot: false, snapshot_fallback_reason: snapshotFailRef && snapshotFailRef.reason ? String(snapshotFailRef.reason) : 'createVerifyGateSnapshot 返回 null(变更文件集为空 / 快照基建不可用)' }
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1123
1147
|
const gateCwd = verifyGateSnap ? verifyGateSnap.snapshotRoot : cwd
|
|
1124
1148
|
const gateSpecBase = verifyGateSnap ? join(verifyGateSnap.snapshotRoot, '.sillyspec') : specBase
|
|
1125
1149
|
let testCheck
|
|
@@ -1220,7 +1244,7 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
1220
1244
|
`质量扫描: ${reusableScan ? '命中(上层未消费)' : '指纹失配/无记录'}`,
|
|
1221
1245
|
`green-cache: ${greenHit ? '命中(上层未消费)' : '指纹失配/过期/无记录'}`,
|
|
1222
1246
|
].join(';')
|
|
1223
|
-
testCheck = runVerifyTestCheck({ cwd: gateCwd, specBase: gateSpecBase, changeName, ctx, observability: { fingerprint: _obsFingerprint, reuse_decision: { layer: 'real-run', hit: false, reason: _missReason } }, ...(Array.isArray(_restrict) && _restrict.length > 0 ? { restrictFiles: _restrict } : {}) })
|
|
1247
|
+
testCheck = runVerifyTestCheck({ cwd: gateCwd, specBase: gateSpecBase, changeName, ctx, observability: { fingerprint: _obsFingerprint, reuse_decision: { layer: 'real-run', hit: false, reason: _missReason }, ...snapObsFields }, ...(Array.isArray(_restrict) && _restrict.length > 0 ? { restrictFiles: _restrict } : {}) })
|
|
1224
1248
|
printVerifyTestCheck(testCheck)
|
|
1225
1249
|
// 自动绑定补全(full-autopilot-parity,R21 thin 迁移):测试已跑、test-result.json 已落盘,
|
|
1226
1250
|
// verify --done 收口时从测试结果自动补全 requirements.md 的空绑定槽——与 thin 同逻辑(逐行扫描)。
|
|
@@ -1397,18 +1421,11 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
1397
1421
|
// 快照含基线债文件时恒败,agent 被逼范围外清偿或逃生口)。失败输出提及文件 × 本变更
|
|
1398
1422
|
// 文件集零交集 → 存量债 advisory 降档放行(债非本变更引入,硬拦只是制造范围外功);
|
|
1399
1423
|
// 有交集 / 输出无可识别路径(unattributable,纯配置错误类)→ 维持硬拦(保守)。──
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
lintOwnership = triageLintOwnership({ failureFiles: lintCheck.failureFiles || [], changeFiles: lintScopeFiles })
|
|
1406
|
-
} catch { /* 鉴定链异常维持硬拦(保守) */ }
|
|
1407
|
-
if (lintOwnership && lintOwnership.verdict === 'pre-existing') {
|
|
1408
|
-
console.warn('\n⚠️ Verify lint 实测失败,但归属鉴定为 HEAD 存量债(失败提及文件与本变更文件集零交集)——降档 advisory 不阻断 verify 完成:')
|
|
1409
|
-
console.warn(` 存量债文件:${(lintCheck.failureFiles || []).join('、') || '(见上方输出)'}`)
|
|
1410
|
-
console.warn(' 处置建议:单独 quick 机械清偿(顺手清债解锁全仓 lint 门),或本变更确认无关时设 SILLYSPEC_VERIFY_LINT_GATE=advisory(审计留痕)。债随基线存在,与本变更交付无关。')
|
|
1411
|
-
} else {
|
|
1424
|
+
// 2026-10-15-lint-triage-path-unify:判定与文案收敛到 ./lint-ownership.js(与 noAI 质量
|
|
1425
|
+
// 扫描路径同源——原两路径分叉见消费仓坑档案 verify-lint-triage-path-divergence.md)。
|
|
1426
|
+
const { lintOwnershipDowngrade } = await import('./lint-ownership.js')
|
|
1427
|
+
const lintAdvisoryDowngraded = await lintOwnershipDowngrade({ lintCheck, cwd, changeName, specBase })
|
|
1428
|
+
if (!lintAdvisoryDowngraded) {
|
|
1412
1429
|
console.error('\n❌ verify 阶段被阻断:CLI 亲自实测 lint 失败(agent 的 lint 自报告与实测不符时以实测为准)。')
|
|
1413
1430
|
if (usedSnapInfo) {
|
|
1414
1431
|
try { const { printSnapshotFailureHint } = await import('./gate-snapshot.js')
|
|
@@ -1432,7 +1449,7 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
1432
1449
|
}
|
|
1433
1450
|
console.error(' 修复后重跑 --done(进度不丢);确认要跳过实测请设 SILLYSPEC_VERIFY_LINT_GATE=advisory(审计留痕)。')
|
|
1434
1451
|
return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts, { type: 'gate_rollback', detail: 'verify-lint' })
|
|
1435
|
-
} //
|
|
1452
|
+
} // 硬拦分支结束(降档放行时直接落到后续 parity/删除探针——rollback 语义不变)
|
|
1436
1453
|
}
|
|
1437
1454
|
// 契约 parity 对账:扫前端 API 调用 vs execute 提取的 provider endpoint artifact。
|
|
1438
1455
|
// 接线自 contract-matrix pipeline(verifyApiParity 的 CLI 入口)。
|
|
@@ -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
|
+
}
|
package/src/run/prompt.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* - loadModuleContextIndex/buildModuleContextInjection 内 require('fs'/'path') 改顶部静态 import
|
|
20
20
|
*/
|
|
21
21
|
import { basename, dirname, join } from 'node:path'
|
|
22
|
+
import { runtimeStatePath } from '../runtime-paths.js'
|
|
22
23
|
import { existsSync, readFileSync, mkdirSync, readdirSync, writeFileSync, rmSync } from 'node:fs'
|
|
23
24
|
import { createHash } from 'node:crypto'
|
|
24
25
|
import jsYaml from 'js-yaml'
|
|
@@ -68,6 +69,7 @@ const VOLATILE_PLACEHOLDER_RES = [
|
|
|
68
69
|
/<now-datetime>/g, /<now-timestamp>/g, /<now-date>/g, /<now-iso-datetime>/g, /<git-head-short>/g, /<quicklog-id>/g,
|
|
69
70
|
/{SCAN_STALENESS}/g, /{SCAN_FACTS}/g, /{QUICK_CONTEXT_DIGEST}/g, /{DECISION_HITS}/g, /{UI_VISUAL_GUIDANCE}/g,
|
|
70
71
|
/{KNOWLEDGE_HIT_REPORT}/g, /{DOCS_DEBT}/g, /{MODULE_RESOLVE_TABLE}/g, /{REVIEW_MATERIALS}/g,
|
|
72
|
+
/{REVIEW_TIER}/g, /{REVIEW_TIER_REASON}/g, /{REVIEW_JSON_CONTRACT}/g,
|
|
71
73
|
/{EXECUTE_RUN_ID}/g, /{STAGE_REVIEW_RUN_ID}/g, /{PROGRESS_SNAPSHOT}/g, /{TASK_COMPLETION_REPORT}/g,
|
|
72
74
|
/{WORKTREE_META}/g, /{WORKTREE_BASELINE_INFO}/g, /{TASKS_CHECKBOX}/g, /{GIT_DIRTY}/g,
|
|
73
75
|
/{SCOPE_AUDIT_TABLE}/g, /{FR_INDEX_DIGEST}/g, /{HANDOVER_SUMMARY}/g, /{PREFLIGHT_FAILURES}/g,
|
|
@@ -867,7 +869,7 @@ export async function renderPreflightFailures({ stageName, stepName, cwd, specBa
|
|
|
867
869
|
export function shouldInjectFullContext({ changeName, stageName, runtimeRoot } = {}) {
|
|
868
870
|
try {
|
|
869
871
|
if (!runtimeRoot || !changeName || !stageName) return { full: true }
|
|
870
|
-
const ledgerPath =
|
|
872
|
+
const ledgerPath = runtimeStatePath(runtimeRoot, `prompt-inject-${changeName}.json`)
|
|
871
873
|
if (!existsSync(ledgerPath)) return { full: true }
|
|
872
874
|
const ledger = JSON.parse(readFileSync(ledgerPath, 'utf8'))
|
|
873
875
|
const rec = (ledger && ledger.stages && typeof ledger.stages === 'object') ? ledger.stages[stageName] : null
|
|
@@ -905,7 +907,7 @@ function renderPromptInjectSummaryLine({ firstStep, digest, specBase, projectNam
|
|
|
905
907
|
* @param {{ runtimeRoot: string, changeName: string, stageName: string, firstStep: number, digest: string }} args
|
|
906
908
|
*/
|
|
907
909
|
async function recordPromptInjectLedger({ runtimeRoot, changeName, stageName, firstStep, digest }) {
|
|
908
|
-
const ledgerPath =
|
|
910
|
+
const ledgerPath = runtimeStatePath(runtimeRoot, `prompt-inject-${changeName}.json`)
|
|
909
911
|
await withFileLock(ledgerPath + '.lock', () => {
|
|
910
912
|
let ledger = null
|
|
911
913
|
try { ledger = JSON.parse(readFileSync(ledgerPath, 'utf8')) } catch { ledger = null }
|
|
@@ -1044,6 +1046,18 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1044
1046
|
promptText = promptText.split(token).join(v)
|
|
1045
1047
|
return promptText
|
|
1046
1048
|
}
|
|
1049
|
+
// 裸 split/join 注入点的收集通道(2026-10-16-step-guide-dyn-completeness):语义与
|
|
1050
|
+
// substituteSplitJoin 相同,供不以占位符正则批量替换的站点使用。
|
|
1051
|
+
// ⚠️ 纪律:凡向 promptText 注入运行期值的站点必须走本组三个助手之一——裸 `.split().join()`
|
|
1052
|
+
// 会让值漏进复入短输出的「动态注入段」附录(值不落盘、guide 掩码后无回读通道)。
|
|
1053
|
+
// 2026-10-15 消费仓实证:评审六值(tier/材料包/契约等)漏收,主代理被迫读引擎源码逆推档位、
|
|
1054
|
+
// 手建材料包、手拼 review.json 路径写错一层前缀再纠正——收集器注释「漏收才是坑」的兑现。
|
|
1055
|
+
const substituteRaw = (token, value) => {
|
|
1056
|
+
const v = String(value)
|
|
1057
|
+
dynSegments.push(v)
|
|
1058
|
+
promptText = promptText.split(token).join(v)
|
|
1059
|
+
return promptText
|
|
1060
|
+
}
|
|
1047
1061
|
// 替换 prompt 中的占位符
|
|
1048
1062
|
if (projectName && promptText.includes('<project>')) {
|
|
1049
1063
|
substitute(/<project>/g, projectName, true)
|
|
@@ -1415,7 +1429,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1415
1429
|
// 注入结果同步落 runtime JSON(与既有 KNOWLEDGE_HIT_REPORT 落盘口径一致)
|
|
1416
1430
|
const decRuntimeDir = join(decSpecBase, '.runtime')
|
|
1417
1431
|
mkdirSync(decRuntimeDir, { recursive: true })
|
|
1418
|
-
writeAtomicSync(
|
|
1432
|
+
writeAtomicSync(runtimeStatePath(decRuntimeDir, 'decision-hits.json'), JSON.stringify({ matched: decResult.matched, decisionHits: decResult.decisionHits || [] }, null, 2) + '\n')
|
|
1419
1433
|
} catch (e) {
|
|
1420
1434
|
substitute(/\{DECISION_HITS\}/g, `[decisions] 决策命中检测异常(${e.message}),跳过`)
|
|
1421
1435
|
}
|
|
@@ -1427,7 +1441,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1427
1441
|
// 替换空串零输出;fail-soft 异常单行说明,不留残留占位符。
|
|
1428
1442
|
if (stageName === 'brainstorm' && promptText.includes('{UI_VISUAL_GUIDANCE}')) {
|
|
1429
1443
|
try {
|
|
1430
|
-
const { detectUiTouch, buildUiGuidanceLines } = await import('../ui-visual.js')
|
|
1444
|
+
const { detectUiTouch, buildUiGuidanceLines, extractQuotedPaths } = await import('../ui-visual.js')
|
|
1431
1445
|
const uiSpecBase = resolvePromptSpecBase(platformOpts, cwd)
|
|
1432
1446
|
let corpus = changeName || ''
|
|
1433
1447
|
for (const name of ['proposal.md', 'requirements.md']) {
|
|
@@ -1435,7 +1449,10 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1435
1449
|
corpus += '\n' + readFileSync(join(uiSpecBase, 'changes', changeName, name), 'utf8')
|
|
1436
1450
|
} catch { /* 缺席跳过 */ }
|
|
1437
1451
|
}
|
|
1438
|
-
|
|
1452
|
+
// ui_prototype 形态注入(2026-10-15-ui-proto-form-config):语料路径参与子仓 override
|
|
1453
|
+
// 匹配(proposal/requirements 反引号路径粗提);无路径=仓级兜底。
|
|
1454
|
+
const guidancePaths = extractQuotedPaths(corpus)
|
|
1455
|
+
substitute(/\{UI_VISUAL_GUIDANCE\}/g, detectUiTouch(corpus) ? '\n' + buildUiGuidanceLines(uiSpecBase, guidancePaths).join('\n') + '\n' : '')
|
|
1439
1456
|
} catch (e) {
|
|
1440
1457
|
substitute(/\{UI_VISUAL_GUIDANCE\}/g, `[ui-guidance] UI 须知注入异常(${e.message}),跳过`)
|
|
1441
1458
|
}
|
|
@@ -1479,7 +1496,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1479
1496
|
)
|
|
1480
1497
|
// 写入 runtime JSON(既有机制照旧,新旧遥测共存)
|
|
1481
1498
|
mkdirSync(runtimeDir, { recursive: true })
|
|
1482
|
-
writeAtomicSync(
|
|
1499
|
+
writeAtomicSync(runtimeStatePath(runtimeDir, 'knowledge-hit-report.json'), JSON.stringify(knowledgeInjection.json, null, 2) + '\n')
|
|
1483
1500
|
} catch (e) {
|
|
1484
1501
|
substitute(/\{KNOWLEDGE_HIT_REPORT\}/g, 'Status: no matches (error: ' + e.message + ')')
|
|
1485
1502
|
}
|
|
@@ -1536,7 +1553,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1536
1553
|
let runId = ''
|
|
1537
1554
|
const execSpecBase = resolvePromptSpecBase(platformOpts, cwd)
|
|
1538
1555
|
const runtimeRoot = resolveRuntimeRoot(platformOpts, execSpecBase)
|
|
1539
|
-
const runIdFile =
|
|
1556
|
+
const runIdFile = runtimeStatePath(runtimeRoot, `current-execute-run-id-${changeName}`)
|
|
1540
1557
|
try {
|
|
1541
1558
|
if (existsSync(runIdFile)) {
|
|
1542
1559
|
const c = readFileSync(runIdFile, 'utf8').trim()
|
|
@@ -1676,22 +1693,21 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1676
1693
|
reviewMaterialsMd = await assembleStageReviewMaterials({ stage: matStage, cwd, changeName, specBase: tierSpecBase })
|
|
1677
1694
|
} catch { /* 组装 best-effort:失败零注入 */ }
|
|
1678
1695
|
}
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1696
|
+
// 全部走 substituteRaw(收集进 dynSegments——复入附录含评审六值;2026-10-16-step-guide-dyn-completeness)
|
|
1697
|
+
substituteRaw('{REVIEW_TIER}', ceremonyInjection.tierValue + ceremonyInjection.menuMd)
|
|
1698
|
+
substituteRaw('{REVIEW_TIER_REASON}', tier.reason)
|
|
1699
|
+
substituteRaw('{STAGE_REVIEW_RUN_ID}', reviewRunId)
|
|
1700
|
+
substituteRaw('{REVIEW_JSON_CONTRACT}', reviewContractMd)
|
|
1701
|
+
substituteRaw('{PRIOR_REVIEW_FACTS}', priorFactsMd)
|
|
1702
|
+
substituteRaw('{REVIEW_MATERIALS}', reviewMaterialsMd) // 三阶段材料包槽(CLI 组装注入,Gap 1 收口——此前恒空串靠主代理手组,机械保证断一半;主代理点名半边由模板补位指引接手)
|
|
1686
1703
|
} catch (e) {
|
|
1687
|
-
// 降级 self,避免 prompt
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
.split('{REVIEW_MATERIALS}').join('') // 降级分支同步 join(plan 审查提示:防占位符字面量残留)
|
|
1704
|
+
// 降级 self,避免 prompt 残留占位符(降级值同样收编进 dynSegments——附录如实呈现降级态)
|
|
1705
|
+
substituteRaw('{REVIEW_TIER}', 'self')
|
|
1706
|
+
substituteRaw('{REVIEW_TIER_REASON}', '分级异常降级 self: ' + e.message)
|
|
1707
|
+
substituteRaw('{STAGE_REVIEW_RUN_ID}', 'review-unknown')
|
|
1708
|
+
substituteRaw('{REVIEW_JSON_CONTRACT}', '(review 契约注入失败,按 schemaVersion=1 + reviewType + verdicts∈pass/fail/cannot_verify + reviewedFiles + docHash=主文档 sha256 产出)')
|
|
1709
|
+
substituteRaw('{PRIOR_REVIEW_FACTS}', '')
|
|
1710
|
+
substituteRaw('{REVIEW_MATERIALS}', '') // 降级分支同步 join(plan 审查提示:防占位符字面量残留)
|
|
1695
1711
|
}
|
|
1696
1712
|
}
|
|
1697
1713
|
|
|
@@ -1721,7 +1737,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1721
1737
|
} catch (e) {
|
|
1722
1738
|
metaInfo = '(注入失败:' + e.message + '——运行 sillyspec worktree meta 自查,meta.json 不存在则停止并报错)'
|
|
1723
1739
|
}
|
|
1724
|
-
|
|
1740
|
+
substituteRaw('{WORKTREE_META}', metaInfo)
|
|
1725
1741
|
}
|
|
1726
1742
|
|
|
1727
1743
|
// archive Step1「任务完成度检查」客观真相源注入:以 review.json verdict 算完成度,
|
|
@@ -1738,9 +1754,9 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1738
1754
|
const summary = tcrChangeDir
|
|
1739
1755
|
? summarizeTaskCompletion({ changeDir: tcrChangeDir, runtimeRoot: tcrRuntimeRoot, changeName })
|
|
1740
1756
|
: null
|
|
1741
|
-
|
|
1757
|
+
substituteRaw('{TASK_COMPLETION_REPORT}', summary ? summary.report : '(无法计算完成度:变更目录缺失)')
|
|
1742
1758
|
} catch (e) {
|
|
1743
|
-
|
|
1759
|
+
substituteRaw('{TASK_COMPLETION_REPORT}', '(完成度计算异常:' + e.message + ',请手动核对 plan.md 与 .runtime/execute-runs/*/tasks/task-NN/review.json)')
|
|
1744
1760
|
}
|
|
1745
1761
|
}
|
|
1746
1762
|
|
|
@@ -1781,7 +1797,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1781
1797
|
} catch (e) {
|
|
1782
1798
|
baselineInfo = '(worktree 基线注入失败:' + e.message + '——diff 对账前先 git branch -a 查 sillyspec/<change> 分支并用 git merge-base <main> <branch> 自查基点,勿用主仓 HEAD 当基点)'
|
|
1783
1799
|
}
|
|
1784
|
-
|
|
1800
|
+
substituteRaw('{WORKTREE_BASELINE_INFO}', baselineInfo)
|
|
1785
1801
|
}
|
|
1786
1802
|
|
|
1787
1803
|
// verify「运行测试和质量扫描」evidence-auto 推荐注入(task-12,FR-11 / D-005@v2):
|
|
@@ -1822,7 +1838,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1822
1838
|
} catch (e) {
|
|
1823
1839
|
eaInjected = `(evidence-auto 推荐注入失败:${e.message}——请读 .sillyspec/local.yaml 确认 test_strategy;--done 时 CLI 仍会按 resolveTestStrategy 实测对账,推荐不可用时可显式设 test_strategy: full/module)`
|
|
1824
1840
|
}
|
|
1825
|
-
|
|
1841
|
+
substituteRaw('{EVIDENCE_AUTO_RECOMMENDATION}', eaInjected)
|
|
1826
1842
|
}
|
|
1827
1843
|
|
|
1828
1844
|
// {ARCHIVE_IMPACT_AUDIT}(2026-09-09 ql-20260909-004):archive extract-module-impact 步的
|
|
@@ -1842,7 +1858,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1842
1858
|
} catch (e) {
|
|
1843
1859
|
auditBlock = '(三重核对机械预填失败:' + (e && e.message ? e.message : e) + '——回退手跑 git diff --name-only 比对)'
|
|
1844
1860
|
}
|
|
1845
|
-
|
|
1861
|
+
substituteRaw('{ARCHIVE_IMPACT_AUDIT}', auditBlock)
|
|
1846
1862
|
}
|
|
1847
1863
|
|
|
1848
1864
|
// {SCOPE_AUDIT_TABLE}(2026-09-10-change-scope-audit task-05,FR-03):archive「确认归档」步的
|
|
@@ -1865,7 +1881,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1865
1881
|
} catch (e) {
|
|
1866
1882
|
scopeTable = '(变更范围对账表注入失败:' + (e && e.message ? e.message : e) + '——回退手跑 sillyspec scope-audit --change <name> 查看后再确认归档,注入失败不阻断归档)'
|
|
1867
1883
|
}
|
|
1868
|
-
|
|
1884
|
+
substituteRaw('{SCOPE_AUDIT_TABLE}', scopeTable)
|
|
1869
1885
|
}
|
|
1870
1886
|
|
|
1871
1887
|
// {HANDOVER_SUMMARY}(2026-09-17-pass-cap-semantics task-04,FR-06 注入态):archive「确认
|
|
@@ -1901,7 +1917,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1901
1917
|
} catch (e) {
|
|
1902
1918
|
handoverBlock = '(移交项清单注入失败:' + (e && e.message ? e.message : e) + '——回退读 changes/<变更名>/verify-facts.json 的 handover 段核对后再确认归档,注入失败不阻断归档)'
|
|
1903
1919
|
}
|
|
1904
|
-
|
|
1920
|
+
substituteRaw('{HANDOVER_SUMMARY}', handoverBlock)
|
|
1905
1921
|
}
|
|
1906
1922
|
|
|
1907
1923
|
// 注入模块上下文(brainstorm/plan/execute 阶段 + quick 首步「理解任务」——刀①:quick
|
package/src/run/scan-profile.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* - executeScanPostcheck 原冗余动态 import('fs'/'path'/'child_process') 删除,改顶部静态(execSync 实际未直接用,纯遗留)
|
|
10
10
|
*/
|
|
11
11
|
import { join, basename, extname } from 'node:path'
|
|
12
|
+
import { runtimeStatePath } from '../runtime-paths.js'
|
|
12
13
|
import { existsSync, readdirSync, statSync, mkdirSync, writeFileSync, unlinkSync, readFileSync } from 'node:fs'
|
|
13
14
|
import { safeGit, resolveRuntimeRoot } from './shared.js'
|
|
14
15
|
import { SCAN_REQUIRED_DOCS } from '../constants.js'
|
|
@@ -332,7 +333,7 @@ export async function executeScanResumeCheck(cwd, platformOpts) {
|
|
|
332
333
|
// .runtime/last-delta.json(归档/delta 补跑写入的最近变更快照),14 天窗口内提示本轮
|
|
333
334
|
// scan 优先核对其涉及模块——advisory 不阻断不改变步骤结构;缺失/过期/解析失败静默。
|
|
334
335
|
try {
|
|
335
|
-
const sidecarPath =
|
|
336
|
+
const sidecarPath = runtimeStatePath(resolveRuntimeRoot(platformOpts, specBase), 'last-delta.json')
|
|
336
337
|
if (existsSync(sidecarPath)) {
|
|
337
338
|
const sidecar = JSON.parse(readFileSync(sidecarPath, 'utf8'))
|
|
338
339
|
const ageMs = Date.now() - Date.parse(sidecar.updatedAt || '')
|