sillyspec 3.29.4 → 3.31.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-auto/SKILL.md +1 -1
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +2 -2
- package/.claude/skills/sillyspec-commit/SKILL.md +1 -1
- package/.claude/skills/sillyspec-flow/SKILL.md +57 -0
- package/.claude/skills/sillyspec-quick/SKILL.md +7 -105
- package/AGENTS.md +38 -20
- package/SKILL.md +9 -3
- package/assets/command-cards/flow.md +32 -0
- package/assets/command-cards/run-archive.md +26 -0
- package/assets/command-cards/run-brainstorm.md +33 -0
- package/assets/command-cards/run-execute.md +29 -0
- package/assets/command-cards/run-plan.md +27 -0
- package/assets/command-cards/run-quick.md +17 -0
- package/assets/command-cards/run-verify.md +29 -0
- package/assets/command-cards/status.md +27 -0
- package/package.json +2 -1
- package/packages/dashboard/server/executor.js +2 -1
- package/packages/dashboard/server/parser.js +7 -7
- package/r7-test-env/README-R7-TEST.md +93 -0
- package/r7-test-env/demo-project/lib/calc.js +6 -0
- package/r7-test-env/demo-project/package.json +1 -0
- package/r7-test-env/demo-project/test.js +10 -0
- package/r7-test-env/prompt-R7-SMOKE.md +41 -0
- package/r7-test-env/sillyspec-r7.cmd +3 -0
- package/round4/account.mjs +98 -0
- package/round4/b1-analyze-task08.mjs +40 -0
- package/round4/b1-b1-target-validation.md +32 -0
- package/round4/b3a-prompt-audit-result.md +66 -0
- package/round4/b3a-prompt-audit.mjs +89 -0
- package/round4/brief-autocompact.md +34 -0
- package/round4/brief-session-replay.md +50 -0
- package/round4/deep-read-synthesis.md +38 -0
- package/round4/extract-full.mjs +38 -0
- package/round4/extract-timeline.mjs +43 -0
- package/round4/find-sessions.mjs +13 -0
- package/round4/full-r4-l.md +4707 -0
- package/round4/full-r4-o-l.md +4300 -0
- package/round4/full-r4-os.md +1681 -0
- package/round4/full-r4-sf.md +4255 -0
- package/round4/full-r4-sq.md +1734 -0
- package/round4/gate-audit-relay-receipt.md +30 -0
- package/round4/gate-value-audit.md +52 -0
- package/round4/optimization-plan.md +165 -0
- package/round4/prereg.md +67 -0
- package/round4/prompt-R4-L.md +63 -0
- package/round4/prompt-R4-O-L.md +59 -0
- package/round4/prompt-R4-O-S.md +56 -0
- package/round4/prompt-R4-S-F.md +60 -0
- package/round4/prompt-R4-S-Q.md +66 -0
- package/round4/r4-final-report.html +347 -0
- package/round4/r4-os-timeline.txt +297 -0
- package/round4/r4-sf-timeline.txt +738 -0
- package/round4/r4-sq-timeline.txt +322 -0
- package/round4/r4l-timeline.txt +793 -0
- package/round4/r4ol-timeline.txt +729 -0
- package/round4/r4sf-timeline.txt +852 -0
- package/round4/r5-scenario-matrix.md +46 -0
- package/round4/result-S.md +171 -0
- package/round4/result-final.md +46 -0
- package/round4/result-quality.md +63 -0
- package/src/change-list.js +9 -0
- package/src/classify-change.js +6 -0
- package/src/command-cards.js +74 -0
- package/src/commit-guard.js +2 -0
- package/src/config-schema.js +64 -12
- package/src/contract-matrix.js +26 -5
- package/src/db.js +35 -4
- package/src/decision-distill.js +9 -3
- package/src/diagnostic-codes.js +2 -1
- package/src/docs-check.js +1 -1
- package/src/doctor-diagnostics.js +44 -2
- package/src/flow-draft.js +748 -0
- package/src/flow-parity.js +236 -0
- package/src/flow-review.js +184 -0
- package/src/flow.js +1492 -0
- package/src/foreign-declared.js +17 -1
- package/src/fr-index.js +396 -22
- package/src/fs-atomic.js +5 -1
- package/src/git-helper.js +2 -2
- package/src/greenfield-bootstrap.js +98 -0
- package/src/handoff.js +120 -7
- package/src/hunk-attribution.js +191 -0
- package/src/index.js +602 -21
- package/src/init.js +25 -8
- package/src/knowledge-classify.js +47 -1
- package/src/knowledge-digest.js +162 -0
- package/src/knowledge-quicklog.js +246 -0
- package/src/machine-draft.js +128 -0
- package/src/machine-interface.js +194 -7
- package/src/mcp-server.js +1 -0
- package/src/module-impact.js +51 -22
- package/src/module-resolve.js +3 -1
- package/src/preview-progress.js +160 -0
- package/src/progress/change-registry.js +11 -3
- package/src/progress/shared.js +3 -2
- package/src/progress.js +56 -13
- package/src/quicklog.js +44 -6
- package/src/redomain.js +147 -0
- package/src/review-material-pack.js +204 -1
- package/src/run/archive-distill.js +4 -2
- package/src/run/bg-sync.js +67 -0
- package/src/run/command.js +127 -10
- package/src/run/complete-handlers.js +363 -73
- package/src/run/complete.js +356 -74
- package/src/run/gate-snapshot-ledger.js +213 -0
- package/src/run/gate-snapshot.js +260 -38
- package/src/run/gates.js +482 -268
- package/src/run/green-cache.js +123 -0
- package/src/run/junction-rm.js +70 -0
- package/src/run/next.js +14 -0
- package/src/run/prompt.js +185 -55
- package/src/run/quick-audit.js +213 -14
- package/src/run/shared.js +123 -4
- package/src/run/stage.js +182 -216
- package/src/run/test-ledger.js +264 -0
- package/src/run/verify-quality-scan.js +84 -7
- package/src/scope-audit.js +6 -1
- package/src/sentinel-assertions.js +114 -0
- package/src/setup.js +3 -3
- package/src/spec-sync.js +41 -1
- package/src/stage-contract.js +26 -8
- package/src/stage-review-checklist.js +2 -0
- package/src/stage-review.js +7 -2
- package/src/stage-templates.js +157 -0
- package/src/stages/archive.js +2 -0
- package/src/stages/brainstorm.js +24 -106
- package/src/stages/execute.js +222 -85
- package/src/stages/knowledge.js +74 -1
- package/src/stages/plan-postcheck.js +385 -20
- package/src/stages/plan.js +23 -1
- package/src/stages/quick.js +10 -2
- package/src/stages/verify.js +4 -2
- package/src/sync.js +81 -23
- package/src/task-done.js +181 -0
- package/src/task-review.js +8 -55
- package/src/taskcard-frontmatter.js +57 -0
- package/src/test-bindings.js +549 -0
- package/src/timeline.js +218 -0
- package/src/ui-visual.js +175 -0
- package/src/verify-draft.js +214 -0
- package/src/verify-postcheck.js +919 -159
- package/src/verify-probes.js +103 -25
- package/src/watcher.js +1031 -0
- package/src/worktree-apply.js +44 -4
- package/src/worktree-deps.js +22 -8
- package/src/worktree-policy.js +42 -0
- package/src/worktree.js +102 -15
- package/templates/agents-instruction.md +37 -18
package/src/contract-matrix.js
CHANGED
|
@@ -542,11 +542,16 @@ export function verifyApiParity(specBase, scanRoot, runtimeRoot, changeName = nu
|
|
|
542
542
|
// worktree,scanRoot(主仓 cwd)里是旧版本——读旧版会漏新调用/多已删调用。
|
|
543
543
|
let frontendCalls = []
|
|
544
544
|
let frontendScope = 'full-repo'
|
|
545
|
+
// 变更文件面一次解析两用(quick-C,2026-09-20 报告问题 C):前端调用收窄 + unused 分层的
|
|
546
|
+
// change-relevant 判定共消费;解析失败 → null(前端回退全仓、分层回退 artifact 口径)
|
|
547
|
+
const parityChanged = changeName
|
|
548
|
+
? (() => { try { return _resolveDiffFilesForParity(specBase, scanRoot, changeName, runtimeRoot) } catch { return null } })()
|
|
549
|
+
: null
|
|
545
550
|
const frontendRoot = (wt && wt.gitDir !== scanRoot) ? wt.gitDir : scanRoot
|
|
546
551
|
const frontendRootLabel = frontendRoot === scanRoot ? 'scan-root' : 'worktree'
|
|
547
552
|
if (changeName) {
|
|
548
553
|
try {
|
|
549
|
-
const changed =
|
|
554
|
+
const changed = parityChanged
|
|
550
555
|
if (changed && changed.length > 0) {
|
|
551
556
|
const changedSet = new Set(changed.map(f => f.replace(/\\/g, '/')))
|
|
552
557
|
frontendCalls = scanFrontendApiCalls(frontendRoot).filter(c => {
|
|
@@ -647,12 +652,28 @@ export function verifyApiParity(specBase, scanRoot, runtimeRoot, changeName = nu
|
|
|
647
652
|
return liveKeys.has(key) || liveEndpoints.length === 0
|
|
648
653
|
})
|
|
649
654
|
// unused 分层(坑 probe5-unused-stock-noise,2026-09-15 EHS 生产实证:490 个 unused 全是
|
|
650
|
-
// urgent 等他模块存量端点——全仓 live 扫描 ×
|
|
651
|
-
//
|
|
652
|
-
//
|
|
655
|
+
// urgent 等他模块存量端点——全仓 live 扫描 × 本变更对账的口径噪音刷屏)。本变更相关判定
|
|
656
|
+
// 两级(quick-C,2026-09-20 报告问题 C:artifact 集口径被整仓 baseline 击穿——execute 起点
|
|
657
|
+
// 拍的全仓端点基线让 artifactKeys ⊇ 全仓、418 存量端点全误标「本变更端点前端未调用」):
|
|
658
|
+
// ①有变更文件面(_resolveDiffFilesForParity 同款链,前端收窄复用同一次解析)→ 判「端点定义
|
|
659
|
+
// 源文件 ∈ 本变更 diff 面」(源串反斜杠归一 + artifact 任务目录前缀形态,双向 endsWith——
|
|
660
|
+
// 与前端调用收窄同款匹配);
|
|
661
|
+
// ②面不可得(CLI contractScan 无 changeName / diff 兜底链全空)→ 回退 artifact 端点集口径;
|
|
662
|
+
// ③无 artifact(非契约流)→ 回退全列零回归。
|
|
653
663
|
const artifactKeys = new Set(allProviderEndpoints.map(e => `${e.method} ${e.path}`))
|
|
654
664
|
const hasArtifacts = allProviderEndpoints.length > 0
|
|
655
|
-
const unusedChangeRelevant =
|
|
665
|
+
const unusedChangeRelevant = parityChanged && parityChanged.length > 0
|
|
666
|
+
? narrowedUnused.filter(u => {
|
|
667
|
+
// diffApiParity 的 unused 项定义源字段是 providerFile(endpoint-extractor.js:492),
|
|
668
|
+
// 兼容直调方可能给的 source 形态
|
|
669
|
+
const src = String(u.providerFile || u.source || '').replace(/\\/g, '/')
|
|
670
|
+
if (!src) return false
|
|
671
|
+
return parityChanged.some(cf => {
|
|
672
|
+
const c = String(cf).replace(/\\/g, '/')
|
|
673
|
+
return src === c || src.endsWith('/' + c) || c.endsWith('/' + src) || src.endsWith(c) || c.endsWith(src)
|
|
674
|
+
})
|
|
675
|
+
})
|
|
676
|
+
: (hasArtifacts ? narrowedUnused.filter(u => artifactKeys.has(`${u.method} ${u.path}`)) : narrowedUnused)
|
|
656
677
|
const unusedStockCount = narrowedUnused.length - unusedChangeRelevant.length
|
|
657
678
|
|
|
658
679
|
const ok = missingBackend.length === 0
|
package/src/db.js
CHANGED
|
@@ -7,7 +7,7 @@ import { dirname } from 'path';
|
|
|
7
7
|
// 每次 new ProgressManager 都过 init,靠版本戳跳过建表省开销)。
|
|
8
8
|
// node:sqlite(DatabaseSync)是原生 SQLite 引擎,打开即持久化(不像 sql.js 纯内存需整库 export 落盘),
|
|
9
9
|
// _createSchema 内 DDL 直接落盘,无需额外 _save。
|
|
10
|
-
const DB_SCHEMA_VERSION =
|
|
10
|
+
const DB_SCHEMA_VERSION = 8;
|
|
11
11
|
|
|
12
12
|
// SQLITE_BUSY 应用层有限重试(R-08 / NFR-03):WAL 单写者模型,并发写第二者在
|
|
13
13
|
// busy_timeout=5000(init PRAGMA)后抛 SQLITE_BUSY。busy_timeout 已在引擎层处理大部分等待;
|
|
@@ -252,7 +252,7 @@ export class DB {
|
|
|
252
252
|
CREATE TABLE IF NOT EXISTS project (
|
|
253
253
|
id INTEGER PRIMARY KEY DEFAULT 1,
|
|
254
254
|
name TEXT NOT NULL,
|
|
255
|
-
schema_version INTEGER DEFAULT
|
|
255
|
+
schema_version INTEGER DEFAULT 8,
|
|
256
256
|
created_at TEXT NOT NULL,
|
|
257
257
|
updated_at TEXT NOT NULL
|
|
258
258
|
)
|
|
@@ -263,7 +263,7 @@ export class DB {
|
|
|
263
263
|
CREATE TABLE IF NOT EXISTS changes (
|
|
264
264
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
265
265
|
name TEXT UNIQUE NOT NULL,
|
|
266
|
-
current_stage TEXT DEFAULT '
|
|
266
|
+
current_stage TEXT DEFAULT 'brainstorm',
|
|
267
267
|
status TEXT DEFAULT 'active',
|
|
268
268
|
no_worktree INTEGER DEFAULT 0,
|
|
269
269
|
created_at TEXT NOT NULL,
|
|
@@ -285,6 +285,8 @@ export class DB {
|
|
|
285
285
|
status TEXT DEFAULT 'pending',
|
|
286
286
|
started_at TEXT,
|
|
287
287
|
completed_at TEXT,
|
|
288
|
+
authority TEXT NOT NULL DEFAULT 'cli',
|
|
289
|
+
preview_evidence TEXT,
|
|
288
290
|
UNIQUE(change_id, stage)
|
|
289
291
|
)
|
|
290
292
|
`);
|
|
@@ -298,7 +300,8 @@ export class DB {
|
|
|
298
300
|
status TEXT DEFAULT 'pending',
|
|
299
301
|
output TEXT,
|
|
300
302
|
completed_at TEXT,
|
|
301
|
-
ordering INTEGER NOT NULL DEFAULT 0
|
|
303
|
+
ordering INTEGER NOT NULL DEFAULT 0,
|
|
304
|
+
authority TEXT NOT NULL DEFAULT 'cli'
|
|
302
305
|
)
|
|
303
306
|
`);
|
|
304
307
|
|
|
@@ -335,6 +338,13 @@ export class DB {
|
|
|
335
338
|
this.db.exec('CREATE INDEX IF NOT EXISTS idx_stages_change ON stages(change_id)');
|
|
336
339
|
this.db.exec('CREATE INDEX IF NOT EXISTS idx_steps_stage ON steps(stage_id)');
|
|
337
340
|
|
|
341
|
+
// Migration: authority 双轨列(watcher 预览账本,幂等——ADD COLUMN NOT NULL DEFAULT 存量即盖章)
|
|
342
|
+
this._migrateAddColumn('stages', 'authority', "TEXT NOT NULL DEFAULT 'cli'");
|
|
343
|
+
this._migrateAddColumn('stages', 'preview_evidence', 'TEXT');
|
|
344
|
+
this._migrateAddColumn('steps', 'authority', "TEXT NOT NULL DEFAULT 'cli'");
|
|
345
|
+
// 预览账本(2026-09-23-watcher-preview-progress):authority 探测与 GC 走此复合索引
|
|
346
|
+
this.db.exec('CREATE INDEX IF NOT EXISTS idx_stages_authority ON stages(change_id, stage, authority)');
|
|
347
|
+
|
|
338
348
|
// Migration: add isolation columns to changes table (idempotent)
|
|
339
349
|
this._migrateAddColumn('changes', 'isolation_status', 'TEXT');
|
|
340
350
|
this._migrateAddColumn('changes', 'isolation_mode', 'TEXT');
|
|
@@ -378,6 +388,27 @@ export class DB {
|
|
|
378
388
|
// change-registry 首建/claimChangeOwner(task-02 接线会话标识);消费方=assertChangeOwnership
|
|
379
389
|
// (task-02)+ serializeForSync 平台同步投影。幂等 ALTER(列存在跳过,v5 先例同款)。
|
|
380
390
|
this._migrateAddColumn('changes', 'owner_session', 'TEXT');
|
|
391
|
+
|
|
392
|
+
// v8(2026-09-27-change-birth-stage-brainstorm):出生阶段 'scan'→'brainstorm' 的存量数据迁移。
|
|
393
|
+
// 旧版 changes 行出生 current_stage='scan'(db.js DDL 默认值 + progress.js 两处 INSERT),
|
|
394
|
+
// thin/quick 等不进主流程的变更全生命周期显示「🔍 代码扫描」误导(scan 是 auxiliary,
|
|
395
|
+
// shared.js MAIN_FLOW_ORDER 从不含它)。只改写「出生默认未被真跑过」的行:active 且
|
|
396
|
+
// (stages.scan='pending' 或根本没有 stages.scan 行——registerChange 出生的行不带 stages 行,
|
|
397
|
+
// 评审 P3-2 缺口)——setStage('scan') 置 in-progress,真在跑/已跑完 scan 的行不受影响。
|
|
398
|
+
// 同步刷 last_local_modified_ts(与 pm._touchLocalModified 同语义):否则平台 pull 见本地
|
|
399
|
+
// 无脏度会静默 import,把平台的旧 'scan' 导回来(platform-sync-pull 冲突保留本地现状)。
|
|
400
|
+
// 幂等:改写后条件不再命中,重跑零效果。strftime 对齐 toISOString 形态(T 分隔 + ms + Z)。
|
|
401
|
+
this.db.exec(`
|
|
402
|
+
UPDATE changes
|
|
403
|
+
SET current_stage = 'brainstorm',
|
|
404
|
+
last_local_modified_ts = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
405
|
+
WHERE current_stage = 'scan'
|
|
406
|
+
AND status = 'active'
|
|
407
|
+
AND (
|
|
408
|
+
id IN (SELECT change_id FROM stages WHERE stage = 'scan' AND status = 'pending')
|
|
409
|
+
OR NOT EXISTS (SELECT 1 FROM stages WHERE change_id = changes.id AND stage = 'scan')
|
|
410
|
+
)
|
|
411
|
+
`);
|
|
381
412
|
}
|
|
382
413
|
|
|
383
414
|
/**
|
package/src/decision-distill.js
CHANGED
|
@@ -186,8 +186,11 @@ function normalizeModules(moduleIndex) {
|
|
|
186
186
|
return moduleIndex.modules ? moduleIndex.modules : moduleIndex
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
/** _module-map.yaml 子集解析(仅模块 id → paths/core_files;不复用 modules.js
|
|
190
|
-
|
|
189
|
+
/** _module-map.yaml 子集解析(仅模块 id → paths/core_files/doc;不复用 modules.js 以保零依赖纯函数)。
|
|
190
|
+
* 2026-09-23 资产三小件③:增 doc 标量捕获(` doc: modules/<id>.md`——模块卡路径,归档文档
|
|
191
|
+
* 认领 advisory 消费)+ 导出(complete-handlers 复用同解析源,跨项目扫描需项目归属故不走
|
|
192
|
+
* discoverModuleIndex 的合并口径)。既有消费方只读 paths/core_files,增字段零行为差。 */
|
|
193
|
+
export function parseModulePathsSubset(content) {
|
|
191
194
|
const modules = {}
|
|
192
195
|
let cur = null
|
|
193
196
|
let key = null
|
|
@@ -202,6 +205,8 @@ function parseModulePathsSubset(content) {
|
|
|
202
205
|
}
|
|
203
206
|
const block = line.match(/^ (paths|core_files):$/)
|
|
204
207
|
if (block) { key = block[1]; modules[cur][key] = modules[cur][key] || []; continue }
|
|
208
|
+
const doc = line.match(/^ doc: (.+)$/)
|
|
209
|
+
if (doc) { modules[cur].doc = doc[1].trim().replace(/^['"]|['"]$/g, ''); key = null; continue }
|
|
205
210
|
const item = line.match(/^ - (.+)$/)
|
|
206
211
|
if (item && key) modules[cur][key].push(item[1].trim().replace(/^['"]|['"]$/g, ''))
|
|
207
212
|
}
|
|
@@ -224,10 +229,11 @@ export function discoverModuleIndex(knowledgeRoot) {
|
|
|
224
229
|
if (!existsSync(mapPath)) continue
|
|
225
230
|
const sub = parseModulePathsSubset(readFileSync(mapPath, 'utf8'))
|
|
226
231
|
for (const [id, m] of Object.entries(sub)) {
|
|
227
|
-
if (!merged[id]) merged[id] = { paths: m.paths || [], core_files: m.core_files || [] }
|
|
232
|
+
if (!merged[id]) merged[id] = { paths: m.paths || [], core_files: m.core_files || [], ...(m.doc ? { doc: m.doc } : {}) }
|
|
228
233
|
else {
|
|
229
234
|
merged[id].paths = [...new Set([...(merged[id].paths || []), ...(m.paths || [])])]
|
|
230
235
|
merged[id].core_files = [...new Set([...(merged[id].core_files || []), ...(m.core_files || [])])]
|
|
236
|
+
if (!merged[id].doc && m.doc) merged[id].doc = m.doc
|
|
231
237
|
}
|
|
232
238
|
}
|
|
233
239
|
}
|
package/src/diagnostic-codes.js
CHANGED
|
@@ -76,7 +76,7 @@ export const DIAGNOSTIC_CODES = Object.freeze({
|
|
|
76
76
|
},
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
-
/** gate check id →
|
|
79
|
+
/** gate check id → 失败码(七 check 全覆盖;表外 id 返回 undefined,调用方不挂键)。 */
|
|
80
80
|
const CHECK_ID_TO_CODE = Object.freeze({
|
|
81
81
|
artifacts: 'artifacts_invalid',
|
|
82
82
|
'design-file-list': 'design_file_ref_invalid',
|
|
@@ -84,6 +84,7 @@ const CHECK_ID_TO_CODE = Object.freeze({
|
|
|
84
84
|
'execute-evidence': 'execute_evidence_unchanged',
|
|
85
85
|
'task-reviews': 'task_reviews_invalid',
|
|
86
86
|
'verify-test': 'verify_test_failed',
|
|
87
|
+
'verify-lint': 'verify_lint_failed',
|
|
87
88
|
});
|
|
88
89
|
|
|
89
90
|
/**
|
package/src/docs-check.js
CHANGED
|
@@ -60,7 +60,7 @@ function _rdExists(absPath) {
|
|
|
60
60
|
export function createHeadReader(projectRoot, ref = 'HEAD') {
|
|
61
61
|
const root = pathResolve(projectRoot)
|
|
62
62
|
const git = (args) => execFileSync('git', ['-C', root, ...args], {
|
|
63
|
-
encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
|
|
63
|
+
encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true,
|
|
64
64
|
})
|
|
65
65
|
let toplevel
|
|
66
66
|
try { toplevel = git(['rev-parse', '--show-toplevel']).trim() } catch { return null }
|
|
@@ -31,6 +31,7 @@ import { gitQuiet } from './git-helper.js'
|
|
|
31
31
|
import jsYaml from 'js-yaml';
|
|
32
32
|
import { pruneTimestampedEntries } from './runtime-hygiene.js';
|
|
33
33
|
import { CHECK_SEVERITY } from './constants.js';
|
|
34
|
+
import { readGateSnapshotLedger, selectStaleSnapshots } from './run/gate-snapshot-ledger.js'
|
|
34
35
|
import { checkPlatformManaged, isSelfReferentialSpecRoot, PLATFORM_MANAGED_FILENAME, QUICK_SID_RE, resolveRuntimeRoot } from './run/shared.js';
|
|
35
36
|
import { readFrictionLedger } from './friction-ledger.js';
|
|
36
37
|
// FR 索引消费(2026-09-18-fr-index-l1 L1,D-007):D14 第四检查——epoch 后归档的索引在场+取代完整。
|
|
@@ -1430,6 +1431,34 @@ export function renderDoctorSummary(diagnostics) {
|
|
|
1430
1431
|
import * as _wtm from './worktree.js'
|
|
1431
1432
|
function require_wtm() { return _wtm }
|
|
1432
1433
|
|
|
1434
|
+
/**
|
|
1435
|
+
* 门禁快照泄漏探测(task-04,2026-09-24-gate-snapshot-lifecycle / FR-03 / D-007@v1):
|
|
1436
|
+
* 账本内「守卫通过 ∧ 超 TTL(env/24h 单源)∧ pid 明确死」的条目即失活残留——门禁快照
|
|
1437
|
+
* (%TEMP%/sillyspec-gate-*)在进程被杀/Windows 删除失败时必然泄漏且此前零可见面。
|
|
1438
|
+
* 只读:只报不修(零修复),warning 级不阻断(doctor --json 的 overall_status 与退出码
|
|
1439
|
+
* 沿用现行全局口径)。runtimeRoot 缺失/账本损坏→ok(fail-open 零误报)。
|
|
1440
|
+
*/
|
|
1441
|
+
export function detectGateSnapshotLeak({ runtimeRoot = null, now = Date.now(), staleHours, isProcessAlive } = {}) {
|
|
1442
|
+
if (!runtimeRoot) {
|
|
1443
|
+
return { name: 'gate_snapshot_leak', label: '门禁快照泄漏', pass: true, severity: null, findings: [], safe_actions: [] }
|
|
1444
|
+
}
|
|
1445
|
+
let leaked = []
|
|
1446
|
+
try {
|
|
1447
|
+
const { stale } = selectStaleSnapshots(readGateSnapshotLedger(runtimeRoot), { now, staleHours, isProcessAlive })
|
|
1448
|
+
leaked = stale.map((e) => ({ root: e.snapshotRoot, ageHours: Math.max(0, Math.round((now - e.createdAt) / 3600000)) }))
|
|
1449
|
+
} catch { leaked = [] }
|
|
1450
|
+
return {
|
|
1451
|
+
name: 'gate_snapshot_leak',
|
|
1452
|
+
label: '门禁快照泄漏',
|
|
1453
|
+
pass: leaked.length === 0,
|
|
1454
|
+
severity: leaked.length > 0 ? CHECK_SEVERITY.WARNING : null,
|
|
1455
|
+
findings: leaked.length > 0
|
|
1456
|
+
? leaked.map((x) => `残留快照 ${x.root}(账龄约 ${x.ageHours}h,超 TTL 且登记进程已失活——下个门禁建快照时自动回收,也可 rm -rf 后 git worktree prune)`)
|
|
1457
|
+
: [],
|
|
1458
|
+
safe_actions: [],
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1433
1462
|
export async function runDoctorDiagnostics({ cwd }) {
|
|
1434
1463
|
const pointer = resolvePointer(cwd);
|
|
1435
1464
|
const multiDb = detectMultiDb(cwd, pointer);
|
|
@@ -1464,7 +1493,20 @@ export async function runDoctorDiagnostics({ cwd }) {
|
|
|
1464
1493
|
// task-06(2026-09-18-ceremony-risk-pricing / FR-04 / R-04):影子对照——轻/重 catch 差异报告 +
|
|
1465
1494
|
// 转正判据计数(纯增量挂载,只读不阻断;无影子数据跳过不误报)
|
|
1466
1495
|
const ceremonyShadow = detectCeremonyShadowComparison(cwd, authoritySpecDir)
|
|
1467
|
-
|
|
1496
|
+
// task-04(2026-09-24-gate-snapshot-lifecycle / FR-03 / D-007@v1):门禁快照泄漏
|
|
1497
|
+
// (%TEMP%/sillyspec-gate-* 崩溃残留——warning 级只读可见面;阈值与 create 前扫共用
|
|
1498
|
+
// env SILLYSPEC_GATE_SNAPSHOT_STALE_HOURS/24h 单源,顶层 doctor 无 --stale-hours 参数面)。
|
|
1499
|
+
// runtimeRoot 与 quick/verify 落账本处同源:平台指针 runtimeRoot 优先(否则 specBase/.runtime),
|
|
1500
|
+
// 否则平台模式下账本写别处、doctor 恒读空(独立审查 P1-3)
|
|
1501
|
+
let gateLeakRuntimeRoot = null
|
|
1502
|
+
try {
|
|
1503
|
+
gateLeakRuntimeRoot = resolveRuntimeRoot(
|
|
1504
|
+
{ runtimeRoot: pointer.present && pointer.runtimeRoot && existsSync(pointer.runtimeRoot) ? pointer.runtimeRoot : null },
|
|
1505
|
+
authoritySpecDir || join(cwd, '.sillyspec'),
|
|
1506
|
+
)
|
|
1507
|
+
} catch { gateLeakRuntimeRoot = null }
|
|
1508
|
+
const gateSnapshotLeak = detectGateSnapshotLeak({ runtimeRoot: gateLeakRuntimeRoot })
|
|
1509
|
+
const dimensions = [multiDb, pointerHealth, changesSplit, changeDb, executeMismatch, docBloat, repoNativeChain, lifecycleDoc, worktreeHealth, buildEnv, mcpEndpoints, applyManifestDrift, selfMaintenanceTax, archiveIntegrity, ceremonyShadow, gateSnapshotLeak];
|
|
1468
1510
|
|
|
1469
1511
|
return {
|
|
1470
1512
|
dimensions,
|
|
@@ -1697,7 +1739,7 @@ export async function dumpDb({ dbPath, cwd }) {
|
|
|
1697
1739
|
const schemaVersion = sv.length ? sv[0].schema_version : null;
|
|
1698
1740
|
const changes = rows('SELECT name, current_stage, status, created_at, last_active FROM changes ORDER BY last_active DESC')
|
|
1699
1741
|
.map((r) => ({ name: r.name, current_stage: r.current_stage, status: r.status, created_at: r.created_at, last_active: r.last_active }));
|
|
1700
|
-
const stages = rows(
|
|
1742
|
+
const stages = rows(`SELECT c.name, s.stage, s.status, s.started_at, s.completed_at FROM stages s JOIN changes c ON s.change_id = c.id WHERE (s.authority = 'cli' OR s.authority IS NULL) ORDER BY c.name, s.stage`)
|
|
1701
1743
|
.map((r) => ({ change: r.name, stage: r.stage, status: r.status, started_at: r.started_at, completed_at: r.completed_at }));
|
|
1702
1744
|
return writeDump({ ok: true, meta, schema_version: schemaVersion, changes, stages }, authoritySpecDir);
|
|
1703
1745
|
} catch (e) {
|