sillyspec 3.33.0 → 3.33.2
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-flow/SKILL.md +18 -10
- package/.github/workflows/ci.yml +11 -3
- package/AGENTS.md +3 -3
- package/README.md +2 -2
- package/assets/command-cards/flow.md +9 -5
- package/package.json +1 -1
- package/src/archive-delta.js +4 -3
- package/src/change-delete.js +6 -5
- package/src/contract-matrix.js +2 -1
- package/src/cross-placement.js +5 -1
- package/src/cross-repo-reconcile.js +4 -1
- package/src/db.js +8 -2
- package/src/decision-distill.js +22 -8
- package/src/decision-values.js +34 -0
- package/src/decisions-io.js +5 -2
- package/src/deleted-ledger.js +2 -1
- package/src/doctor-diagnostics.js +8 -3
- package/src/flow-draft.js +17 -10
- package/src/flow-parity.js +93 -5
- package/src/flow-review-pack.js +183 -0
- package/src/flow-review.js +45 -3
- package/src/flow.js +365 -109
- 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 +62 -21
- package/src/init.js +7 -30
- package/src/knowledge-hits.js +2 -1
- package/src/knowledge-vector.js +2 -1
- 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 +14 -12
- 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 +102 -23
- package/src/run/gates.js +32 -8
- package/src/run/prompt.js +6 -5
- 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 +11 -5
- package/src/runtime-paths.js +189 -0
- package/src/scan-refresh.js +4 -3
- package/src/scope-audit.js +13 -7
- package/src/sentinel-assertions.js +37 -0
- package/src/spec-sync.js +38 -22
- package/src/stage-contract.js +24 -2
- package/src/stage-review.js +3 -2
- package/src/stage-templates.js +3 -2
- package/src/stages/brainstorm.js +6 -5
- 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 +45 -1
- package/src/validate-artifacts.js +2 -1
- package/src/verify-draft.js +2 -1
- package/src/verify-postcheck.js +11 -10
- package/src/verify-probes.js +157 -52
- package/src/watcher.js +15 -9
- package/src/worktree-apply.js +2 -1
- package/src/write-audit.js +2 -1
- package/templates/agents-instruction.md +3 -3
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, carriedFiles = [], 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
|
}
|
|
@@ -537,7 +543,6 @@ export function createGateSnapshot({ cwd, files, sourceRoot = null, skipImportSm
|
|
|
537
543
|
// 是共享运行时面:.runtime/(sqlite 锁/在途 marker)与 quicklog/(跨会话共享账本);
|
|
538
544
|
// local.yaml 由下方 cfg 段显式复制(单一来源,不经 overlay)。
|
|
539
545
|
const overlayRoot = sourceRoot || cwd
|
|
540
|
-
let overlaid = 0
|
|
541
546
|
// 双写一致性(2026-09-20 红线机检变更实证:主 agent 在主仓直写而 execute 已建 worktree 时,
|
|
542
547
|
// overlay 源=worktree 拿陈旧内容盖掉快照 HEAD 的新版——快照内 import 找不到新导出→门禁假红)。
|
|
543
548
|
// 祖先判据(fr-index-l2 二次实证升级):基线优先取 merge-base 的每文件祖先内容
|
|
@@ -562,9 +567,12 @@ export function createGateSnapshot({ cwd, files, sourceRoot = null, skipImportSm
|
|
|
562
567
|
}
|
|
563
568
|
try { return cmpText(readFileSync(join(snapshotRoot, rel))) } catch { return null }
|
|
564
569
|
}
|
|
565
|
-
|
|
570
|
+
// 单文件 overlay(判定面 files 与携带面 carriedFiles 共用,2026-10-20-gate-snapshot-
|
|
571
|
+
// carried-deps 提取):源=overlayRoot;worktree 模式经三态判定(陈旧取主仓/正常取
|
|
572
|
+
// worktree/分叉取 worktree 血统 M2);源侧不存在返回 false(已删/不在场不拷)。
|
|
573
|
+
const overlayOne = (f) => {
|
|
566
574
|
const isSillyspecRuntime = typeof f === 'string' && (f.startsWith('.sillyspec/.runtime/') || f.startsWith('.sillyspec/quicklog/') || f === '.sillyspec/local.yaml' || f === '.sillyspec/.sillyspec-platform.json')
|
|
567
|
-
if (typeof f !== 'string' || f.includes('..') || f.startsWith('/') || isSillyspecRuntime)
|
|
575
|
+
if (typeof f !== 'string' || f.includes('..') || f.startsWith('/') || isSillyspecRuntime) return false
|
|
568
576
|
let src = join(overlayRoot, f)
|
|
569
577
|
if (sourceRoot && sourceRoot !== cwd) {
|
|
570
578
|
const wtPath = join(sourceRoot, f), cwdPath = join(cwd, f)
|
|
@@ -589,11 +597,26 @@ export function createGateSnapshot({ cwd, files, sourceRoot = null, skipImportSm
|
|
|
589
597
|
}
|
|
590
598
|
}
|
|
591
599
|
}
|
|
592
|
-
if (!existsSync(src))
|
|
600
|
+
if (!existsSync(src)) return false
|
|
593
601
|
const dst = join(snapshotRoot, f)
|
|
594
602
|
mkdirSync(dirname(dst), { recursive: true })
|
|
595
603
|
copyFileSync(src, dst)
|
|
596
|
-
|
|
604
|
+
return true
|
|
605
|
+
}
|
|
606
|
+
let overlaid = 0
|
|
607
|
+
for (const f of files) { if (overlayOne(f)) overlaid++ }
|
|
608
|
+
|
|
609
|
+
// 携带面 overlay(2026-10-20-gate-snapshot-carried-deps,坑 verify-gate-…-three-defects
|
|
610
|
+
// 缺陷二):他者声明文件从判定面剔除但物理携带进快照——快照复现本变更实测时真实的依赖
|
|
611
|
+
// 环境(worktree 模式≈worktree 自洽态;in-place≈主仓混合树)。坑文场景:本变更文件 import
|
|
612
|
+
// 他者未提交导出(quick 的 stripPathPrefix)→ 原快照装 HEAD 旧版必 TypeError 假红,携带后
|
|
613
|
+
// 自洽。判定面(files/overlaid)不含携带件——归属语义不变,假红可经控制台清单归因到他者。
|
|
614
|
+
const carriedList = []
|
|
615
|
+
if (Array.isArray(carriedFiles) && carriedFiles.length > 0) {
|
|
616
|
+
for (const f of carriedFiles) {
|
|
617
|
+
if (files.includes(f)) continue // 判定面已含(own 声明优先级高于他者)——不重复拷
|
|
618
|
+
if (overlayOne(f)) carriedList.push(f)
|
|
619
|
+
}
|
|
597
620
|
}
|
|
598
621
|
|
|
599
622
|
// 环境目录链接(坑 gate-snapshot-env-mismatch,2026-09-12 驾驭第十三批③,用户实证:
|
|
@@ -625,6 +648,7 @@ export function createGateSnapshot({ cwd, files, sourceRoot = null, skipImportSm
|
|
|
625
648
|
// 回退主仓现行为(主仓口径可能被并行脏文件污染,但环境真实;两害取轻 + 回退原因可见)。
|
|
626
649
|
const envMissing = envDirsLinked(cwd, snapshotRoot)
|
|
627
650
|
if (envMissing.length > 0) {
|
|
651
|
+
setFail(`环境目录链接不完整(${envMissing.join('、')} 在主仓存在、快照内缺失)——快照作废回退主仓实测`)
|
|
628
652
|
console.warn(`⚠️ 门禁快照环境不完整(${envMissing.join('、')} 在主仓存在、快照内缺失)——快照作废回退主仓实测(宁可主仓口径不可假沙箱硬挂;回退后失败先做污染归属鉴定)`)
|
|
629
653
|
// 与正常 cleanup/失败 catch 同一清理体:双清确认才销账(该早退曾残留旧式 --quiet
|
|
630
654
|
// 清理与无重试 rmSync——独立审查 P1-1 根因未除尽的漏网面)
|
|
@@ -699,7 +723,7 @@ export function createGateSnapshot({ cwd, files, sourceRoot = null, skipImportSm
|
|
|
699
723
|
// HEAD 健版保证 import 图闭合。新增文件(HEAD 无版)无法回退 → 醒目 warn 保留。
|
|
700
724
|
// 全链 fail-open:找不到解释器/基建异常只 warn 不作废快照(主场景是 Python 项目,
|
|
701
725
|
// JS/TS 的 vitest load 冒烟成本高留后续)。
|
|
702
|
-
const overlaidPyFiles = files.filter(f => typeof f === 'string' && f.endsWith('.py')
|
|
726
|
+
const overlaidPyFiles = [...files, ...carriedList].filter(f => typeof f === 'string' && f.endsWith('.py')
|
|
703
727
|
&& !f.startsWith('.sillyspec/')
|
|
704
728
|
&& existsSync(join(snapshotRoot, f)))
|
|
705
729
|
if (overlaidPyFiles.length > 0 && !skipImportSmoke) {
|
|
@@ -720,10 +744,13 @@ export function createGateSnapshot({ cwd, files, sourceRoot = null, skipImportSm
|
|
|
720
744
|
if (dirRemoved && worktreeCleaned) unregisterGateSnapshot({ runtimeRoot, snapshotRoot })
|
|
721
745
|
} catch { /* 双失败已由 cleanupSnapshot 吞掉;条目保留 */ }
|
|
722
746
|
}
|
|
723
|
-
return { snapshotRoot, cleanup, overlaid, pinnedCacheLinks }
|
|
747
|
+
return { snapshotRoot, cleanup, overlaid, pinnedCacheLinks, carriedFiles: carriedList, carried: carriedList.length }
|
|
724
748
|
} catch (e) {
|
|
725
749
|
// 基建失败(worktree add 拒绝/磁盘满/…):尽力清理后回退主仓;销账同走双清确认
|
|
726
|
-
// (D-004@v2
|
|
750
|
+
// (D-004@v2:清不掉的条目保留待回收——它不是悬空条目,是待回收记录)。
|
|
751
|
+
// 异常细节不再静默(坑 snapshot-fallback-reason-blind):failRef 带原因 + warn 落日志。
|
|
752
|
+
setFail(`快照基建异常: ${e && e.message ? e.message : e}`)
|
|
753
|
+
try { console.warn(`⚠️ 门禁快照基建失败(回退主仓实测): ${e && e.message ? e.message : e}`) } catch { /* 输出失败不影响回退 */ }
|
|
727
754
|
if (snapshotRoot) {
|
|
728
755
|
try {
|
|
729
756
|
const { dirRemoved, worktreeCleaned } = cleanupSnapshot({ snapshotRoot, cwd })
|
|
@@ -856,8 +883,15 @@ export function envDirsLinked(cwd, snapshotRoot) {
|
|
|
856
883
|
*/
|
|
857
884
|
export function printSnapshotFailureHint(snapInfo, { offEnv = 'SILLYSPEC_VERIFY_GATE_SNAPSHOT_OFF' } = {}) {
|
|
858
885
|
if (!snapInfo || !snapInfo.snapshotRoot) return
|
|
859
|
-
console.error(` 🔬 本次实测执行于隔离快照:${snapInfo.snapshotRoot}(HEAD + 本变更 ${snapInfo.changeFileCount ?? '?'} 个文件${snapInfo.sourceRoot ? ',overlay 自 worktree' : ''})`)
|
|
860
|
-
|
|
886
|
+
console.error(` 🔬 本次实测执行于隔离快照:${snapInfo.snapshotRoot}(HEAD + 本变更 ${snapInfo.changeFileCount ?? '?'} 个文件${snapInfo.sourceRoot ? ',overlay 自 worktree' : ''}${snapInfo.carriedFiles && snapInfo.carriedFiles.length > 0 ? `,携带 ${snapInfo.carriedFiles.length} 个他者声明文件(依赖自洽面)` : ''})`)
|
|
887
|
+
// 2026-10-20-gate-snapshot-carried-deps 评审 F3 清偿:携带面在场时他者文件**在**疑点内
|
|
888
|
+
// (原「并行污染不在疑点内」只对未声明脏文件成立——携带件是声明过的他者内容)。
|
|
889
|
+
if (snapInfo.carriedFiles && snapInfo.carriedFiles.length > 0) {
|
|
890
|
+
const preview = snapInfo.carriedFiles.slice(0, 5).join('、')
|
|
891
|
+
console.error(` 失败疑点排查顺序:① 本变更文件自身的真实失败(最常见)→ 修代码;② 携带的他者声明文件坏半态(${snapInfo.carriedFiles.length > 5 ? `${preview} 等` : preview}——他者变更在途内容,非本变更面)→ 核对他者变更状态或 SNAPSHOT_OFF 对照;③ 快照环境差异(monorepo 依赖布局/环境目录链接缺失)→ 设 ${offEnv}=1 回退主仓复跑对照——主仓过而快照挂即环境问题。`)
|
|
892
|
+
} else {
|
|
893
|
+
console.error(` 失败疑点排查顺序:① 本变更文件自身的真实失败(最常见)→ 修代码;② 快照环境差异(monorepo 依赖布局/环境目录链接缺失)→ 设 ${offEnv}=1 回退主仓复跑对照——主仓过而快照挂即环境问题;③ 并行污染已被快照隔离,不在疑点内。`)
|
|
894
|
+
}
|
|
861
895
|
}
|
|
862
896
|
|
|
863
897
|
/**
|
|
@@ -865,12 +899,14 @@ export function printSnapshotFailureHint(snapInfo, { offEnv = 'SILLYSPEC_VERIFY_
|
|
|
865
899
|
* main 工作区,多会话并发任何人的 WIP 都能弄红别人的门)。等价「--worktree 定向跑」:
|
|
866
900
|
* overlay 文件集 = resolveVerifyChangedFiles(worktree-aware:含 merge-base 补齐与
|
|
867
901
|
* working-tree),overlay 源 = 本变更 worktree 根(meta 感知;in-place 退主仓 cwd)——
|
|
868
|
-
*
|
|
869
|
-
*
|
|
902
|
+
* 并行会话**未声明**的在途脏文件物理不进快照(隔离核心);他者**显式声明**的文件以携带面
|
|
903
|
+
* 物理进快照(2026-10-20-gate-snapshot-carried-deps:依赖自洽——判定/归因面仍剔除)。
|
|
904
|
+
* 变更文档(changes/<name>/**,test_strategy 的 module-impact.md 等消费)从主仓 specBase 随快照复制。
|
|
870
905
|
* @param {{ cwd: string, changeName: string, specBase: string, platformOpts?: object }} opts
|
|
871
906
|
* @returns {Promise<{snapshotRoot: string, cleanup: () => void, overlaid: number, changeFileCount: number}|null>}
|
|
872
907
|
*/
|
|
873
|
-
export async function createVerifyGateSnapshot({ cwd, changeName, specBase, platformOpts = {} }) {
|
|
908
|
+
export async function createVerifyGateSnapshot({ cwd, changeName, specBase, platformOpts = {}, failRef = null }) {
|
|
909
|
+
const setFail = (reason) => { if (failRef && typeof failRef === 'object') failRef.reason = reason }
|
|
874
910
|
try {
|
|
875
911
|
const { resolveVerifyChangedFiles } = await import('../verify-postcheck.js')
|
|
876
912
|
const { resolveRuntimeRoot } = await import('./shared.js')
|
|
@@ -879,19 +915,35 @@ export async function createVerifyGateSnapshot({ cwd, changeName, specBase, plat
|
|
|
879
915
|
let verifyRuntimeRoot = null
|
|
880
916
|
try { verifyRuntimeRoot = resolveRuntimeRoot(platformOpts, specBase) } catch { verifyRuntimeRoot = null }
|
|
881
917
|
const { splitOwnVsForeignDiffFiles } = await import('../foreign-declared.js')
|
|
882
|
-
|
|
918
|
+
// 变更文件集为空三分成因区分(坑 snapshot-fallback-reason-blind):null=git 不可用/解析
|
|
919
|
+
// 失败;[]=HEAD 无 diff;他者声明过滤清空在下方单独计——三者处置同为回退主仓,但排查
|
|
920
|
+
// 出路完全不同,笼统「变更文件集为空」曾把 pnpm 布局拒建也误标成它。
|
|
921
|
+
const rawChangeFiles = resolveVerifyChangedFiles(cwd, changeName, null, {
|
|
883
922
|
specBase, includeWorkingTree: true,
|
|
884
|
-
})
|
|
885
|
-
|
|
923
|
+
})
|
|
924
|
+
let changeFiles = rawChangeFiles || []
|
|
925
|
+
if (changeFiles.length === 0) {
|
|
926
|
+
setFail(rawChangeFiles === null
|
|
927
|
+
? '变更文件集为空(git 不可用/解析失败——非 git 仓或 git 异常,无从定向 overlay)'
|
|
928
|
+
: '变更文件集为空(HEAD 无 diff——改动可能已全部提交,无可隔离面)')
|
|
929
|
+
return null // 无变更文件集 → 无从定向,回退主仓
|
|
930
|
+
}
|
|
886
931
|
// 他者声明归属过滤(坑 verify-reconcile-foreign-wip 同款):in-place 模式 working-tree
|
|
887
932
|
// 并入后主仓全部在途文件进场——他者活跃变更显式声明的文件(quick --files / 他者 design
|
|
888
|
-
//
|
|
933
|
+
// 清单)从**判定面**剔除,只留本变更相关面(无主文件保留,fail-closed 口径与 probe6 一致)。
|
|
934
|
+
// 2026-10-20-gate-snapshot-carried-deps(坑 verify-gate-…-three-defects 缺陷二):被剔
|
|
935
|
+
// 文件改以「携带面」物理进快照(carriedFiles)——快照依赖自洽,判定/归属语义不变。
|
|
936
|
+
let carriedForeignFiles = []
|
|
889
937
|
try {
|
|
890
938
|
const { foreign } = splitOwnVsForeignDiffFiles(cwd, changeName, changeFiles, { specBase, runtimeRoot: verifyRuntimeRoot })
|
|
891
939
|
if (foreign.length > 0) {
|
|
892
940
|
const foreignSet = new Set(foreign.map(x => x.file))
|
|
941
|
+
carriedForeignFiles = changeFiles.filter(f => foreignSet.has(f))
|
|
893
942
|
changeFiles = changeFiles.filter(f => !foreignSet.has(f))
|
|
894
|
-
if (changeFiles.length === 0)
|
|
943
|
+
if (changeFiles.length === 0) {
|
|
944
|
+
setFail(`变更文件集为空(${foreign.length} 个 diff 文件全部被他者声明过滤——并行会话声明面覆盖了本变更全部在途文件)`)
|
|
945
|
+
return null
|
|
946
|
+
}
|
|
895
947
|
}
|
|
896
948
|
} catch { /* 过滤失败退全量(fail-closed:宁可多 overlay 不漏本变更文件) */ }
|
|
897
949
|
|
|
@@ -920,8 +972,16 @@ export async function createVerifyGateSnapshot({ cwd, changeName, specBase, plat
|
|
|
920
972
|
}
|
|
921
973
|
} catch { /* meta 读取失败 → 主仓 cwd 源(files 已含本变更 working-tree 改动) */ }
|
|
922
974
|
|
|
923
|
-
const snap = createGateSnapshot({ cwd, files: changeFiles, sourceRoot, mergeBase: snapMergeBase, runtimeRoot: verifyRuntimeRoot })
|
|
924
|
-
if (!snap)
|
|
975
|
+
const snap = createGateSnapshot({ cwd, files: changeFiles, carriedFiles: carriedForeignFiles, sourceRoot, mergeBase: snapMergeBase, runtimeRoot: verifyRuntimeRoot, failRef })
|
|
976
|
+
if (!snap) {
|
|
977
|
+
// createGateSnapshot 已把具体原因写进同一 failRef;无 reason(旧形态兜底)补笼统口径
|
|
978
|
+
if (failRef && typeof failRef === 'object' && !failRef.reason) setFail('快照基建不可用(createGateSnapshot 返回 null,无更细原因)')
|
|
979
|
+
return null
|
|
980
|
+
}
|
|
981
|
+
if (snap.carriedFiles && snap.carriedFiles.length > 0) {
|
|
982
|
+
const preview = snap.carriedFiles.slice(0, 5).join('、')
|
|
983
|
+
console.log(`📦 快照携带 ${snap.carriedFiles.length} 个他者声明文件(依赖自洽面,不进判定/归因面;假红先核对他者变更)${snap.carriedFiles.length > 5 ? `:${preview} 等` : `:${preview}`}`)
|
|
984
|
+
}
|
|
925
985
|
|
|
926
986
|
// 变更文档随快照:module-impact.md(test_strategy=evidence-auto 消费)/tasks.md 等
|
|
927
987
|
// 在 .sillyspec(gitignore)不进 HEAD,从主仓 specBase 复制 changes/<name>/**
|
|
@@ -940,7 +1000,26 @@ export async function createVerifyGateSnapshot({ cwd, changeName, specBase, plat
|
|
|
940
1000
|
} catch { /* 文档复制失败不连坐快照(test_strategy 降级 module 集仍可跑) */ }
|
|
941
1001
|
|
|
942
1002
|
return { ...snap, changeFileCount: changeFiles.length, sourceRoot }
|
|
943
|
-
} catch {
|
|
944
|
-
|
|
1003
|
+
} catch (e) {
|
|
1004
|
+
// 任一链路异常 → 回退主仓现行为。异常细节不再静默(坑 snapshot-fallback-reason-blind):
|
|
1005
|
+
// failRef 带原因 + warn 落日志,排查不再靠 stdout 考古。
|
|
1006
|
+
setFail(`快照链路异常: ${e && e.message ? e.message : e}`)
|
|
1007
|
+
try { console.warn(`⚠️ 门禁快照链路异常(回退主仓实测): ${e && e.stack ? e.stack.split('\n')[0] : e}`) } catch { /* 输出失败不影响回退 */ }
|
|
1008
|
+
return null
|
|
945
1009
|
}
|
|
946
1010
|
}
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* 快照口径观测字段(坑 snapshot-fallback-reason-blind,FR-04 落盘面):--done 实测门把
|
|
1014
|
+
* used_snapshot 与降级原因经 runVerifyTestCheck 的 observability 通道带进 test-result.json
|
|
1015
|
+
* extra(对齐 2026-10-09 复用观测先例)。快照在场时记录随快照销毁(无可降级事实);
|
|
1016
|
+
* 回退主仓时记录恰好落在主仓 .runtime——降级证据存活在主仓,与用途一致。
|
|
1017
|
+
* @param {boolean} used 快照是否真实在场
|
|
1018
|
+
* @param {{ reason?: string }|null} failRef createVerifyGateSnapshot 的出参(可 null)
|
|
1019
|
+
* @returns {{ used_snapshot: boolean, snapshot_fallback_reason?: string }}
|
|
1020
|
+
*/
|
|
1021
|
+
export function snapshotObservability(used, failRef) {
|
|
1022
|
+
if (used) return { used_snapshot: true }
|
|
1023
|
+
const reason = failRef && failRef.reason ? String(failRef.reason) : 'createVerifyGateSnapshot 返回 null(变更文件集为空 / 快照基建不可用)'
|
|
1024
|
+
return { used_snapshot: false, snapshot_fallback_reason: reason }
|
|
1025
|
+
}
|
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'
|
|
@@ -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
|
-
console.log(`🧪 Verify 实测隔离快照(根:${verifyGateSnap.snapshotRoot}):HEAD + 本变更 ${verifyGateSnap.changeFileCount} 个文件${verifyGateSnap.sourceRoot ? '(overlay 自 worktree——本变更分支内容定向跑)' : ''}
|
|
1111
|
+
console.log(`🧪 Verify 实测隔离快照(根:${verifyGateSnap.snapshotRoot}):HEAD + 本变更 ${verifyGateSnap.changeFileCount} 个文件${verifyGateSnap.sourceRoot ? '(overlay 自 worktree——本变更分支内容定向跑)' : ''}${verifyGateSnap.carriedFiles && verifyGateSnap.carriedFiles.length > 0 ? ` + 携带 ${verifyGateSnap.carriedFiles.length} 个他者声明文件(依赖自洽,不进判定面)` : ''}(并行会话未声明脏文件不参与门判定)`)
|
|
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 同逻辑(逐行扫描)。
|
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'
|
|
@@ -868,7 +869,7 @@ export async function renderPreflightFailures({ stageName, stepName, cwd, specBa
|
|
|
868
869
|
export function shouldInjectFullContext({ changeName, stageName, runtimeRoot } = {}) {
|
|
869
870
|
try {
|
|
870
871
|
if (!runtimeRoot || !changeName || !stageName) return { full: true }
|
|
871
|
-
const ledgerPath =
|
|
872
|
+
const ledgerPath = runtimeStatePath(runtimeRoot, `prompt-inject-${changeName}.json`)
|
|
872
873
|
if (!existsSync(ledgerPath)) return { full: true }
|
|
873
874
|
const ledger = JSON.parse(readFileSync(ledgerPath, 'utf8'))
|
|
874
875
|
const rec = (ledger && ledger.stages && typeof ledger.stages === 'object') ? ledger.stages[stageName] : null
|
|
@@ -906,7 +907,7 @@ function renderPromptInjectSummaryLine({ firstStep, digest, specBase, projectNam
|
|
|
906
907
|
* @param {{ runtimeRoot: string, changeName: string, stageName: string, firstStep: number, digest: string }} args
|
|
907
908
|
*/
|
|
908
909
|
async function recordPromptInjectLedger({ runtimeRoot, changeName, stageName, firstStep, digest }) {
|
|
909
|
-
const ledgerPath =
|
|
910
|
+
const ledgerPath = runtimeStatePath(runtimeRoot, `prompt-inject-${changeName}.json`)
|
|
910
911
|
await withFileLock(ledgerPath + '.lock', () => {
|
|
911
912
|
let ledger = null
|
|
912
913
|
try { ledger = JSON.parse(readFileSync(ledgerPath, 'utf8')) } catch { ledger = null }
|
|
@@ -1428,7 +1429,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1428
1429
|
// 注入结果同步落 runtime JSON(与既有 KNOWLEDGE_HIT_REPORT 落盘口径一致)
|
|
1429
1430
|
const decRuntimeDir = join(decSpecBase, '.runtime')
|
|
1430
1431
|
mkdirSync(decRuntimeDir, { recursive: true })
|
|
1431
|
-
writeAtomicSync(
|
|
1432
|
+
writeAtomicSync(runtimeStatePath(decRuntimeDir, 'decision-hits.json'), JSON.stringify({ matched: decResult.matched, decisionHits: decResult.decisionHits || [] }, null, 2) + '\n')
|
|
1432
1433
|
} catch (e) {
|
|
1433
1434
|
substitute(/\{DECISION_HITS\}/g, `[decisions] 决策命中检测异常(${e.message}),跳过`)
|
|
1434
1435
|
}
|
|
@@ -1495,7 +1496,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1495
1496
|
)
|
|
1496
1497
|
// 写入 runtime JSON(既有机制照旧,新旧遥测共存)
|
|
1497
1498
|
mkdirSync(runtimeDir, { recursive: true })
|
|
1498
|
-
writeAtomicSync(
|
|
1499
|
+
writeAtomicSync(runtimeStatePath(runtimeDir, 'knowledge-hit-report.json'), JSON.stringify(knowledgeInjection.json, null, 2) + '\n')
|
|
1499
1500
|
} catch (e) {
|
|
1500
1501
|
substitute(/\{KNOWLEDGE_HIT_REPORT\}/g, 'Status: no matches (error: ' + e.message + ')')
|
|
1501
1502
|
}
|
|
@@ -1552,7 +1553,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
|
|
|
1552
1553
|
let runId = ''
|
|
1553
1554
|
const execSpecBase = resolvePromptSpecBase(platformOpts, cwd)
|
|
1554
1555
|
const runtimeRoot = resolveRuntimeRoot(platformOpts, execSpecBase)
|
|
1555
|
-
const runIdFile =
|
|
1556
|
+
const runIdFile = runtimeStatePath(runtimeRoot, `current-execute-run-id-${changeName}`)
|
|
1556
1557
|
try {
|
|
1557
1558
|
if (existsSync(runIdFile)) {
|
|
1558
1559
|
const c = readFileSync(runIdFile, 'utf8').trim()
|
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 || '')
|