sillyspec 3.29.5 → 3.30.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 +54 -54
- 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 +18 -116
- package/AGENTS.md +38 -20
- package/SKILL.md +9 -3
- package/assets/command-cards/flow.md +32 -0
- package/assets/command-cards/run-brainstorm.md +1 -1
- package/assets/command-cards/run-quick.md +8 -25
- 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 +18 -5
- 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/optimization-plan.md +165 -0
- package/round4/r4-final-report.html +347 -0
- package/round4/r5-scenario-matrix.md +46 -0
- package/src/change-list.js +9 -0
- package/src/classify-change.js +6 -0
- package/src/command-cards.js +2 -1
- package/src/commit-guard.js +2 -0
- package/src/config-schema.js +64 -12
- package/src/db.js +35 -4
- package/src/decision-distill.js +9 -3
- 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 +570 -16
- 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 +107 -5
- package/src/mcp-server.js +1 -0
- 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 +123 -10
- package/src/run/complete-handlers.js +327 -72
- package/src/run/complete.js +350 -71
- package/src/run/gate-snapshot-ledger.js +213 -0
- package/src/run/gate-snapshot.js +250 -34
- package/src/run/gates.js +370 -233
- 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 +185 -12
- package/src/run/shared.js +120 -3
- 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 +221 -84
- package/src/stages/knowledge.js +74 -1
- package/src/stages/plan-postcheck.js +352 -1
- 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/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 +860 -158
- package/src/verify-probes.js +59 -3
- 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/run/gates.js
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 阶段完成校验 gate 级联 + execute deps 硬门 + 完成回滚(自洽叶子模块,仅被 completeStep 调用):
|
|
5
5
|
* - runStageCompletionGates:runValidators → verify-test 对账 → Plan→Execute contract →
|
|
6
|
-
* Stage Review Gate
|
|
6
|
+
* Stage Review Gate,任一失败走 rollbackCompletionAndReturn(Execute Task Review Gate
|
|
7
|
+
* 已随 2026-09-26-task-review-retire 退役——R18 实证任务粒度评审在无独立评审者供给时
|
|
8
|
+
* 全降形式合规、实质拦截为零);
|
|
7
9
|
* 级联末位追仪式档位摩擦升档检查点(2026-09-18-ceremony-risk-pricing task-03:读
|
|
8
10
|
* friction-ledger 累计账 → escalateByFriction → .runtime/ceremony-tier-<change>.json
|
|
9
11
|
* 迁移留痕,只升不降、fail-soft 不阻断完成);verify 检查族末位接 ceremony 双跑收口第一
|
|
@@ -33,6 +35,37 @@ import { writeAtomicSync } from '../fs-atomic.js'
|
|
|
33
35
|
import { triggerSync, resolveChangeDir, resolveRuntimeRoot } from './shared.js'
|
|
34
36
|
import { runValidators } from '../stage-contract.js'
|
|
35
37
|
import { handleScanStageCompleted, handleExecuteWorktreeCleanup } from './complete-handlers.js'
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 评审豁免凭据(2026-09-26-review-unsupervised-exit,R18-full 实证驱动):无嵌套派发能力的
|
|
41
|
+
* 环境(子代理不能再派子代理)里,阶段评审只能降级自审——三份自审 review.json 全 PASS 零信息
|
|
42
|
+
* 量纯表演,门仍硬拦形式合规(R18-full 15 次拦截里 5 次是 review 面形式)。诚实出口:agent 写
|
|
43
|
+
* 变更目录 review-unsupervised.md(一行声明:环境无嵌套派发能力+时间,须含 unsupervised 字样)
|
|
44
|
+
* → Stage Review Gate / doctor align 门两点共吃(Task Review 层与 --done review.json 硬门
|
|
45
|
+
* 已随 2026-09-26-task-review-retire 退役):有效
|
|
46
|
+
* review.json ‖ 豁免声明二选一放行(warn+遥测,豁免率可观测);两凭据皆无照旧 fail-closed。
|
|
47
|
+
* 真独立评审(有派发能力)不受影响——豁免是声明式自曝,不是绕门。
|
|
48
|
+
*/
|
|
49
|
+
export function readReviewUnsupervisedWaiver(changeDir) {
|
|
50
|
+
try {
|
|
51
|
+
if (!changeDir) return null
|
|
52
|
+
const p = join(changeDir, 'review-unsupervised.md')
|
|
53
|
+
if (!existsSync(p)) return null
|
|
54
|
+
const t = readFileSync(p, 'utf8')
|
|
55
|
+
return /unsupervised/i.test(t) ? t.trim().slice(0, 200) : null
|
|
56
|
+
} catch {
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** 豁免放行的统一回显+遥测(调用点持 runtimeRoot/specBase)。 */
|
|
62
|
+
async function waiveWithTelemetry({ changeName, gate, specBase }) {
|
|
63
|
+
console.warn(`⚠️ [评审豁免] review-unsupervised.md 在场(${gate})——环境无嵌套派发能力,阶段评审降级留痕放行(不产自审 review.json 不表演);声明随归档留痕,可经遥测观测豁免率`)
|
|
64
|
+
try {
|
|
65
|
+
const { appendKnowledgeHit } = await import('../knowledge-hits.js')
|
|
66
|
+
appendKnowledgeHit(join(specBase, '.runtime'), { type: 'review-unsupervised-escape', change: changeName, gate, source: 'gates' })
|
|
67
|
+
} catch { /* 遥测 fail-soft */ }
|
|
68
|
+
}
|
|
36
69
|
import { detectConcurrentChanges, formatConcurrentWarning, detectCommittedDrift, formatCommittedDriftWarning } from './concurrent-detect.js'
|
|
37
70
|
import { stageRegistry } from '../stages/index.js'
|
|
38
71
|
import { normalizeTaskId } from '../taskcard.js'
|
|
@@ -235,8 +268,9 @@ function isCurrentWaveAllNoDepsVerify(stepName, changeDir) {
|
|
|
235
268
|
* 只读校验(不自动生成 marker/runId——恢复读场景缺失就是缺失,如实报告):
|
|
236
269
|
* 1. Stage Review:tier=independent 时须存在有效 execute stage review.json(verdict 非 fail);
|
|
237
270
|
* tier=self 放行(与正常 gate 分级一致)。
|
|
238
|
-
* 2. Task Review
|
|
239
|
-
*
|
|
271
|
+
* 2. Task Review 段已退役(2026-09-26-task-review-retire:R18 实证任务粒度评审在无嵌套
|
|
272
|
+
* 派发环境全降自审表演、实质拦截为零——豁免通道不足以止损,层整体退役;残存 review.json
|
|
273
|
+
* 由 task-review.js 保留导出作历史兼容读侧)。
|
|
240
274
|
*
|
|
241
275
|
* @param {{ cwd: string, changeName: string, specBase: string, platformOpts?: object }} opts
|
|
242
276
|
* @returns {Promise<boolean>} true=被门阻断(CLI 应放弃 align 并 exit 1);false=校验通过可继续
|
|
@@ -245,10 +279,16 @@ export async function enforceAlignExecuteReviewGate({ cwd, changeName, specBase,
|
|
|
245
279
|
const effectiveSpecBase = platformOpts?.specRoot || specBase
|
|
246
280
|
const runtimeRoot = resolveRuntimeRoot(platformOpts, effectiveSpecBase)
|
|
247
281
|
const reviewChangeDir = resolveChangeDir(cwd, { currentChange: changeName }, platformOpts?.specRoot || null)
|
|
282
|
+
// 豁免凭据(review-unsupervised-exit):无嵌套派发能力环境两段校验整体降级留痕放行
|
|
283
|
+
if (readReviewUnsupervisedWaiver(reviewChangeDir)) {
|
|
284
|
+
await waiveWithTelemetry({ changeName, gate: 'doctor-align', specBase: effectiveSpecBase })
|
|
285
|
+
console.warn(' ℹ️ align 前置 review 门整体豁免(unsupervised 声明在场)——继续对齐')
|
|
286
|
+
return false
|
|
287
|
+
}
|
|
248
288
|
const blocked = (msgs) => {
|
|
249
289
|
console.error('\n❌ doctor --align-execute-progress 前置 review 校验未过——execute 完成审计不能绕过:')
|
|
250
290
|
for (const m of msgs) console.error(' - ' + m)
|
|
251
|
-
console.error(' 解法:补齐上述 review 后重跑 align(stage review:sillyspec register-stage-review --change ' + changeName + ' --stage execute
|
|
291
|
+
console.error(' 解法:补齐上述 review 后重跑 align(stage review:sillyspec register-stage-review --change ' + changeName + ' --stage execute)')
|
|
252
292
|
return true
|
|
253
293
|
}
|
|
254
294
|
|
|
@@ -285,36 +325,10 @@ export async function enforceAlignExecuteReviewGate({ cwd, changeName, specBase,
|
|
|
285
325
|
return true
|
|
286
326
|
}
|
|
287
327
|
|
|
288
|
-
// 2. Task Review
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
if (!planPath || !existsSync(planPath)) {
|
|
293
|
-
return blocked(['plan.md 不存在,Task Review 无从校验(align 的 checkbox 判定依赖同目录,疑似路径解析异常)'])
|
|
294
|
-
}
|
|
295
|
-
const planContent = readFileSync(planPath, 'utf8')
|
|
296
|
-
const runIdFile = join(runtimeRoot, `current-execute-run-id-${changeName}`)
|
|
297
|
-
let executeRunId = ''
|
|
298
|
-
try {
|
|
299
|
-
if (existsSync(runIdFile)) {
|
|
300
|
-
const c = readFileSync(runIdFile, 'utf8').trim()
|
|
301
|
-
if (c && isValidExecuteRunId(c)) executeRunId = c
|
|
302
|
-
}
|
|
303
|
-
} catch {}
|
|
304
|
-
if (!executeRunId) {
|
|
305
|
-
executeRunId = resolveLatestExecuteRunIdWithTasks({ runtimeRoot, changeName }) || ''
|
|
306
|
-
}
|
|
307
|
-
if (!executeRunId) {
|
|
308
|
-
return blocked(['execute runId 无法定位(marker 缺失且 execute-runs/ 无含 review 的 run 目录)——15/N 份 task review 所在 run 不可寻,无法对账'])
|
|
309
|
-
}
|
|
310
|
-
const reviewResult = validateTaskReviews({ planContent, runtimeRoot, executeRunId, changeDir: reviewChangeDir, gitDir: cwd })
|
|
311
|
-
printReviewResult(reviewResult, { runtimeRoot, executeRunId })
|
|
312
|
-
if (!reviewResult.ok) return true // 报错明细已在 printReviewResult 输出
|
|
313
|
-
console.log('✅ align 前置 review 校验通过(stage review + task review 均齐备),继续对齐')
|
|
314
|
-
} catch (e) {
|
|
315
|
-
console.error('❌ align 前置 Task Review 校验异常,阻断对齐: ' + e.message)
|
|
316
|
-
return true
|
|
317
|
-
}
|
|
328
|
+
// 2. Task Review 段已退役(2026-09-26-task-review-retire)——原「所有 task review.json 齐备
|
|
329
|
+
// 且 verdict 非 fail」校验随层退役删除;task 粒度审计改由 execute 收口的代码证据核验
|
|
330
|
+
// (checkExecuteCodeEvidence)+ verify 测试对账承担。
|
|
331
|
+
console.log('✅ align 前置 review 校验通过(stage review;task review 段已退役),继续对齐')
|
|
318
332
|
return false
|
|
319
333
|
}
|
|
320
334
|
|
|
@@ -342,7 +356,7 @@ export async function enforceDepsGate(stageName, cwd, changeName, step, steps, c
|
|
|
342
356
|
|
|
343
357
|
// main 侧就绪后的跨仓 worktree deps 校验(坑 cross-repo-no-worktree-isolation):
|
|
344
358
|
// 跨仓仓供给失败同样无构建/测试能力(前端仓无 node_modules 建不了 build)。只校验已建
|
|
345
|
-
// worktree 的跨仓仓(legacy 直写模式无 meta
|
|
359
|
+
// worktree 的跨仓仓(legacy 直写模式无 meta,维持原语义)。
|
|
346
360
|
// 放行返回 true;跨仓未就绪置 blocked + exit(1)。
|
|
347
361
|
const crossCheckOrExit = async () => {
|
|
348
362
|
let crossFailed = []
|
|
@@ -446,85 +460,11 @@ export async function enforceDepsGate(stageName, cwd, changeName, step, steps, c
|
|
|
446
460
|
process.exit(1)
|
|
447
461
|
}
|
|
448
462
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
* review.json,要到收尾才暴露,用户被迫事后批量补。本门提前到每次 --done:校验 plan 里所有已勾
|
|
455
|
-
* task 的 review.json,缺字段/不存在/JSON 坏 → 置 step=blocked + exit(1),与 enforceDepsGate 同范式
|
|
456
|
-
* (阻断前经 persist 回调落盘 blocked)。
|
|
457
|
-
* 未勾 task 不校验(还没做)。平台模式/无 marker/无 plan 时放行(下游 Task Review Gate 兜底)。
|
|
458
|
-
*/
|
|
459
|
-
export async function enforceReviewJsonGate(stageName, cwd, changeName, step, steps, currentIdx, specBase, platformOpts, persist) {
|
|
460
|
-
if (stageName !== 'execute' || !changeName) return true
|
|
461
|
-
// head 锡点自动落盘(2026-08-21 审计项③,D-010 补对称):跨仓 task base_commit 已在派发时
|
|
462
|
-
// CLI 落盘,head_commit 此前靠主 agent 按 prompt 手跑 rev-parse 手写(漏抄/抄错炸 review gate)。
|
|
463
|
-
// 每次 --done 时机幂等补齐(已存在不覆盖)。best-effort:失败只 warn,不阻断 --done 主流程。
|
|
464
|
-
try {
|
|
465
|
-
const { stampCrossRepoHeadCommits } = await import('../stages/execute.js')
|
|
466
|
-
const stamped = await stampCrossRepoHeadCommits({ changeName, cwd, specBase, platformOpts })
|
|
467
|
-
if (stamped && stamped.stamped > 0) {
|
|
468
|
-
console.log(`📌 已自动落盘跨仓 task head_commit 锡点:${stamped.stampedTasks.join(', ')}(幂等,此后 --done 不再重复写)`)
|
|
469
|
-
}
|
|
470
|
-
} catch (e) {
|
|
471
|
-
console.warn(`⚠️ head_commit 锡点自动落盘失败(降级留给 agent 手写): ${e.message}`)
|
|
472
|
-
}
|
|
473
|
-
const runtimeRoot = resolveRuntimeRoot(platformOpts, specBase)
|
|
474
|
-
const runIdFile = join(runtimeRoot, `current-execute-run-id-${changeName}`)
|
|
475
|
-
const planPath = join(specBase, 'changes', changeName, 'plan.md')
|
|
476
|
-
if (!existsSync(runIdFile) || !existsSync(planPath)) return true
|
|
477
|
-
const { validateCheckedTaskReviews, resolveLatestExecuteRunIdWithTasks, isValidExecuteRunId } = await import('../task-review.js')
|
|
478
|
-
// existsSync 与 read 之间 marker 可能被并发 cleanup/归档删除(多 agent 场景):读失败按
|
|
479
|
-
//「marker 缺失」处理,走漂移兜底重定位,不让 ENOENT 冒成顶层 stack(与 :426-435 口径对齐)
|
|
480
|
-
let executeRunId = ''
|
|
481
|
-
try {
|
|
482
|
-
executeRunId = readFileSync(runIdFile, 'utf8').trim()
|
|
483
|
-
} catch {
|
|
484
|
-
console.warn('⚠️ execute run marker 读取失败(可能被并发清理),改扫真实 run 目录')
|
|
485
|
-
executeRunId = resolveLatestExecuteRunIdWithTasks({ runtimeRoot, changeName }) || ''
|
|
486
|
-
}
|
|
487
|
-
// marker 是 agent 可写内容:格式校验防注入/穿越,非法视为缺失走漂移兜底重定位
|
|
488
|
-
if (executeRunId && !isValidExecuteRunId(executeRunId)) {
|
|
489
|
-
console.warn(`⚠️ execute run marker 内容非法(期望 exec-YYYY-MM-DD-HHMMSS,实得 ${JSON.stringify(executeRunId.slice(0, 60))}),改扫真实 run 目录`)
|
|
490
|
-
executeRunId = ''
|
|
491
|
-
}
|
|
492
|
-
// 2026-08-20-task-truth-unify:已勾 task 清单源迁 tasks.md(勾选唯一落点);tasks.md 缺失
|
|
493
|
-
// 回退 planPath 内容(旧变更兼容读侧)。planPath 存在性已在上方把关。
|
|
494
|
-
const tasksRegistryPath = join(specBase, 'changes', changeName, 'tasks.md')
|
|
495
|
-
const planContent = existsSync(tasksRegistryPath)
|
|
496
|
-
? readFileSync(tasksRegistryPath, 'utf8')
|
|
497
|
-
: readFileSync(planPath, 'utf8')
|
|
498
|
-
// marker 读失败且重定位无果(无任何含 tasks/ 的 run):无 run 可校验,放行(下游 Task Review Gate 兜底)
|
|
499
|
-
if (!executeRunId) return true
|
|
500
|
-
// marker 漂移兜底(gate-atom-a 正确修法):marker 指向的 run 缺 tasks/(generateExecuteRunId 只写
|
|
501
|
-
// marker 不建目录,漂移后新 run 不继承旧 review)时,无视 marker 改扫 execute-runs/ 取 mtime 最新
|
|
502
|
-
// 且真正含 tasks/ 的 run,用其齐备的 review.json 校验,避免误报「review.json 不存在」。注意不能用
|
|
503
|
-
// resolveLatestExecuteRunId——它见 marker 非空即原样返回(不校验目录),恰是本场景要绕开的值。
|
|
504
|
-
if (executeRunId && !existsSync(join(runtimeRoot, 'execute-runs', executeRunId, 'tasks'))) {
|
|
505
|
-
const relocated = resolveLatestExecuteRunIdWithTasks({ runtimeRoot, changeName })
|
|
506
|
-
if (relocated && relocated !== executeRunId) {
|
|
507
|
-
console.warn(`⚠️ execute run marker 漂移:${executeRunId} 无 tasks/,改用真实含 review 的 run ${relocated}`)
|
|
508
|
-
executeRunId = relocated
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
const result = validateCheckedTaskReviews({ planContent, runtimeRoot, executeRunId })
|
|
512
|
-
if (result.ok) return true
|
|
513
|
-
if (steps && steps[currentIdx]) {
|
|
514
|
-
steps[currentIdx].status = 'blocked'
|
|
515
|
-
steps[currentIdx].blockReason = 'review.json 字段校验阻断:补齐后重试 --done(backfill-reviews --adopt 可代填 mechanics)'
|
|
516
|
-
}
|
|
517
|
-
console.error('❌ ── review.json 字段校验阻断(本次 --done 未完成,进度未推进)──')
|
|
518
|
-
console.error(' 已勾选 [x] 的 task review.json 不完整(铁律:勾 checkbox 前必须先写完整 review.json):')
|
|
519
|
-
for (const f of result.failures) {
|
|
520
|
-
const kindLabel = f.kind === 'missing' ? 'review.json 不存在' : (f.kind === 'parseError' ? 'JSON 解析失败' : '字段缺失')
|
|
521
|
-
console.error(` • ${f.taskId}(${kindLabel}): ${f.reviewPath}`)
|
|
522
|
-
for (const e of f.errors) console.error(` - ${e}`)
|
|
523
|
-
}
|
|
524
|
-
console.error(' 修复:mechanics 字段(base/head/changedFiles/schemaVersion)缺错可跑 sillyspec backfill-reviews --change ' + changeName + ' --adopt 一键代填(verdict 保留);verdict/证据缺失需人工补全后重跑 execute --done。')
|
|
525
|
-
if (persist) { try { await persist() } catch { /* 落盘失败不吞阻断语义 */ } }
|
|
526
|
-
process.exit(1)
|
|
527
|
-
}
|
|
463
|
+
// ── enforceReviewJsonGate 已退役(2026-09-26-task-review-retire)──
|
|
464
|
+
// 原「execute --done 时已勾 [x] task 的 review.json schema 硬门」(坑 review-json-field-gap)
|
|
465
|
+
// 随 Task Review 层整体退役删除:勾选语义回归 agent 手动(完成=实现+测试绿+wt-commit 即勾),
|
|
466
|
+
// review.json 不再是任何门的输入;validateCheckedTaskReviews 纯函数保留在 task-review.js
|
|
467
|
+
// (历史变更 doctor/回放兼容读侧,单元测试 review-json-field-gate.test.mjs 仍覆盖)。
|
|
528
468
|
|
|
529
469
|
/**
|
|
530
470
|
* 阶段完成校验失败时回滚状态。
|
|
@@ -564,7 +504,7 @@ export function rollbackStageCompletion(stageData, steps, currentIdx) {
|
|
|
564
504
|
* 阶段完成校验失败后的统一收尾:回滚 stage/step 状态 + 落盘 + sync + 返回「未完成」。
|
|
565
505
|
*
|
|
566
506
|
* completeStep 的各 gate(runValidators / verify-test 对账 / plan→execute contract /
|
|
567
|
-
* Stage Review
|
|
507
|
+
* Stage Review)失败时都走这套动作;原先每个失败分支手写重复
|
|
568
508
|
* ~7 次(含 lastActive 落盘 + triggerSync + return 结构),统一进来消坑,避免某分支
|
|
569
509
|
* 漏写 triggerSync / 写错 return 结构导致行为分裂。返回 nextPendingIdx=currentIdx,
|
|
570
510
|
* 让上层走「完成但不推进」分支,--done 被拒、agent 修复产物后重跑。
|
|
@@ -739,13 +679,21 @@ async function escalateCeremonyTierAtGate({ cwd, specBase, platformOpts, progres
|
|
|
739
679
|
/**
|
|
740
680
|
* 阶段完成校验 gate 级联(从 completeStep 抽出,行为保持)。仅当所有步骤确实标记为 completed 时
|
|
741
681
|
* 由 completeStep 调用。顺序:runValidators → verify-test 对账 → Plan→Execute contract →
|
|
742
|
-
* Stage Review Gate
|
|
682
|
+
* Stage Review Gate。任一 gate 失败 → rollbackCompletionAndReturn
|
|
743
683
|
* (统一回滚 + 返回 early-return 对象);全部通过 → 返回 null(completeStep 继续收尾)。
|
|
744
684
|
*
|
|
745
685
|
* @returns {{stageCompleted:false,currentIdx,nextPendingIdx:number}|null}
|
|
746
686
|
*/
|
|
747
687
|
export async function runStageCompletionGates({ stageName, cwd, changeName, platformOpts, specBase, progress, pm, stageData, steps, currentIdx, ctx = null }) {
|
|
748
688
|
const projectName = progress.project || basename(cwd)
|
|
689
|
+
// ── 纯文档门收集模式(R16 减负批次,2026-09-24)──
|
|
690
|
+
// 对撞 R15 实证「execute 收口连挡 4 次」:多门串行 fail-fast,每次 --done 只暴露一个门,
|
|
691
|
+
// 修复重跑才见下一个门。纯文档 blocking 门(validators / verify 死信与预填注 / Stage
|
|
692
|
+
// Review)改为收集:全部跑完 → 一次打印清单 → 统一 rollback 一次(detail 取首门,
|
|
693
|
+
// 进度库信号语义不变)。实测门(test/lint/parity/probe)保持文档面全清后才跑、内部 fail-fast
|
|
694
|
+
// 不变——贵的门只为真实代码问题付账。检查点 A=verify 实测门前;B=brainstorm/plan/execute
|
|
695
|
+
// Stage Review Gate 后(friction 升档前——升档语义=「所有阻断门通过」)。
|
|
696
|
+
const docGateFailures = []
|
|
749
697
|
// decisions.md header 自动补齐(坑 decisions-header-late-warning,2026-08-24 用户反馈二期):
|
|
750
698
|
// brainstorm step8 旧模板自带无 frontmatter 的 decisions 样例,存量变更照抄必缺 author/created_at,
|
|
751
699
|
// 拖到平台同步/后续环节才提示。gate 前幂等补机械字段(先例:autoCheckPlanFromReviews 同层写
|
|
@@ -818,8 +766,9 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
818
766
|
console.error(`\n 提示:修复缺失产物后重新完成此步骤(--skip-approval 只跳过阶段转换/审批检查,不能跳过产物校验)`)
|
|
819
767
|
// 产物校验失败必须阻断完成 —— 否则 validator 形同虚设,
|
|
820
768
|
// verify 会带着 FAIL/缺 verify-result.md 被 ✅ 标记完成(历史教训)。
|
|
821
|
-
//
|
|
822
|
-
|
|
769
|
+
// 收集模式(R16):不再直接 return——继续跑完其余纯文档门(review 等)后由检查点统一
|
|
770
|
+
// rollback,一次 --done 暴露全部缺失面(R15 连挡 4 次的解)。
|
|
771
|
+
docGateFailures.push({ type: 'gate_rollback', detail: 'validators', label: `产物契约校验未过(${contractResult.errors.length} 条,明细见上)` })
|
|
823
772
|
}
|
|
824
773
|
if (contractResult.warnings.length > 0) {
|
|
825
774
|
console.warn(`\n⚠️ 阶段 ${stageName} 校验警告:`)
|
|
@@ -879,7 +828,7 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
879
828
|
console.error(`\n❌ verify 阶段被阻断:module-impact.md「更新结果」表存在 ${pendingRows.length} 个未清 pending/待办项(死信)`)
|
|
880
829
|
for (const row of pendingRows) console.error(` - ${row}`)
|
|
881
830
|
console.error(' 文档同步是 verify 的收尾义务:请完成模块文档同步并回填状态为 done/skipped(说明原因),再重新完成 verify。')
|
|
882
|
-
|
|
831
|
+
docGateFailures.push({ type: 'gate_rollback', detail: 'verify-contract', label: `module-impact.md 存在 ${pendingRows.length} 个 pending/待办死信(明细见上)` })
|
|
883
832
|
}
|
|
884
833
|
}
|
|
885
834
|
}
|
|
@@ -895,11 +844,41 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
895
844
|
console.error(`\n❌ verify 阶段被阻断:预填注清零校验未过(${noteClearance.unclearedFiles.length} 个文件的白名单槽仍含未删预填注——预填≠结论,注在场=未确认):`)
|
|
896
845
|
for (const f of noteClearance.unclearedFiles) console.error(` - ${f}`)
|
|
897
846
|
console.error(' 修复:逐槽核对预填值后删除行尾「(预填:核对后删本注)」注(删注=确认动作);散文含注字面量的命中改写该处措辞。清零后重新完成 verify(进度不丢)。')
|
|
898
|
-
|
|
847
|
+
docGateFailures.push({ type: 'gate_rollback', detail: 'verify-contract', label: `预填注清零未过(${noteClearance.unclearedFiles.length} 文件,明细见上)` })
|
|
899
848
|
}
|
|
900
849
|
} catch (e) {
|
|
901
850
|
console.warn(`⚠️ 预填注清零校验异常(降级放行,fail-soft——异常不是未清零,ceremony 双跑同款处置): ${(e && e.message) || e}`)
|
|
902
851
|
}
|
|
852
|
+
|
|
853
|
+
// UI 视觉证据门·error 档(2026-09-27-ui-visual-guidance / 探针 12 分级门的 verify 收尾执法):
|
|
854
|
+
// 视觉降级无用户裁决留痕恒拦;ui_visual_gate=error 时缺 visual-evidence.md 亦拦(默认 warn
|
|
855
|
+
// 只警告不拦)。检测单点 = runUiVisualProbe(与 flow done probes 子步、verify-probes --init
|
|
856
|
+
// 同源,不二算)。fail-soft 同预填注门。
|
|
857
|
+
try {
|
|
858
|
+
const { runUiVisualProbe, readUiVisualGate, UI_EVIDENCE_FILENAME } = await import('../ui-visual.js')
|
|
859
|
+
const uiProbe = runUiVisualProbe({ changeDir: join(specBase, 'changes', changeName), gate: readUiVisualGate(specBase) })
|
|
860
|
+
if (uiProbe.level === 'error') {
|
|
861
|
+
console.error(`
|
|
862
|
+
❌ verify 阶段被阻断:UI 视觉证据门未过(${uiProbe.notes.join(';')})`)
|
|
863
|
+
console.error(` 修复:补渲染对照证据到 ${UI_EVIDENCE_FILENAME};降级项补「用户裁决」留痕段。证据应在执行时按 flow start「UI 变更执行须知」随手产生——收口只验在场。`)
|
|
864
|
+
docGateFailures.push({ type: 'gate_rollback', detail: 'verify-contract', label: 'UI 视觉证据门未过(探针 12 error 档,明细见上)' })
|
|
865
|
+
} else if (uiProbe.level === 'warn') {
|
|
866
|
+
console.warn(`
|
|
867
|
+
⚠️ UI 视觉证据(探针 12 warn 档):${uiProbe.notes.join(';')}`)
|
|
868
|
+
}
|
|
869
|
+
} catch (e) {
|
|
870
|
+
console.warn(`⚠️ UI 视觉证据探针异常(降级放行,fail-soft): ${(e && e.message) || e}`)
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// ── 收集模式检查点 A(verify):纯文档门全清后才进入实测门(test/lint/parity/probe 保持
|
|
875
|
+
// fail-fast 不变)。brainstorm/plan/execute 不走本检查点(其实测面为空,检查点 B 收口)。──
|
|
876
|
+
if (stageName === 'verify' && docGateFailures.length > 0) {
|
|
877
|
+
console.error(`\n❌ verify 收口被 ${docGateFailures.length} 道纯文档门拦截(一次性全列——全部修复后重跑一次 --done,不必逐门连撞):`)
|
|
878
|
+
docGateFailures.forEach((f, i) => console.error(` ${i + 1}. ${f.label}`))
|
|
879
|
+
console.error(` (各门明细见上方对应 ❌ 段落;实测门(test/lint)在文档面全清后才执行)`)
|
|
880
|
+
const firstFail = docGateFailures[0]
|
|
881
|
+
return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts, { type: firstFail.type, detail: firstFail.detail })
|
|
903
882
|
}
|
|
904
883
|
|
|
905
884
|
// verify 产物校验通过 + 结论非 FAIL(否则上面已阻断)。
|
|
@@ -952,16 +931,134 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
952
931
|
const gateCwd = verifyGateSnap ? verifyGateSnap.snapshotRoot : cwd
|
|
953
932
|
const gateSpecBase = verifyGateSnap ? join(verifyGateSnap.snapshotRoot, '.sillyspec') : specBase
|
|
954
933
|
let testCheck
|
|
955
|
-
|
|
934
|
+
// ── P2 三键账本复用(D-001@v1,2026-09-21-r5-efficiency-batch3 task-01):代码×测试面×环境
|
|
935
|
+
// 三键全等且有通过记录 → 免重跑(batch2 实证同码态全量被跑多遍)。fail-closed:键分量
|
|
936
|
+
// 不可得/键不等/无记录 → 真跑(走下方原路径,行为不变)。咨询异常整体按无账本(fail-open
|
|
937
|
+
// 不影响门禁)。优先级:P2 账本(三键精确)> P0-1 质量扫描复用 > 真跑。──
|
|
938
|
+
let ledgerReuse = null
|
|
939
|
+
// ── 账本停复用护栏(2026-09-24-fr-test-readside task-05,D-006@v1):trace 含 active 行
|
|
940
|
+
// 的变更残差并入跑面,v2 键(config+整目录面)不再描述实际执行——fail-closed 跳过
|
|
941
|
+
// consult/record(宁多跑一轮不误复用未含残差的旧绿;退役判据=账本键 v3 携 aggregate
|
|
942
|
+
// run plan)。空 trace/缺失 → 照用 v2(行为不变)。──
|
|
943
|
+
let traceHasActiveRows = false
|
|
944
|
+
try {
|
|
945
|
+
const { readChangeTrace } = await import('../test-bindings.js')
|
|
946
|
+
traceHasActiveRows = readChangeTrace(join(specBase, 'changes', changeName)).some(r => r.state === 'active')
|
|
947
|
+
if (traceHasActiveRows) console.log('ℹ️ 本变更含 active 测试绑定行——账本停复用(残差跑面静态不可键化,fail-closed)')
|
|
948
|
+
} catch { /* 读失败按无 trace(照用账本) */ }
|
|
949
|
+
if (!traceHasActiveRows) {
|
|
950
|
+
try {
|
|
951
|
+
const { consultTestLedger } = await import('./test-ledger.js')
|
|
952
|
+
const consult = consultTestLedger({
|
|
953
|
+
runtimeRoot: resolveRuntimeRoot(platformOpts, specBase),
|
|
954
|
+
changeName, projectRoot: gateCwd, testRoot: join(gateCwd, 'test'), specBase: gateSpecBase, cwd: gateCwd,
|
|
955
|
+
})
|
|
956
|
+
if (consult.reuse) ledgerReuse = consult
|
|
957
|
+
} catch { /* 账本咨询异常 → 真跑 */ }
|
|
958
|
+
}
|
|
959
|
+
if (ledgerReuse) {
|
|
960
|
+
testCheck = {
|
|
961
|
+
status: 'passed',
|
|
962
|
+
reason: `♻️ P2 三键账本复用(代码×测试面×环境全等;实测于 ${ledgerReuse.result.ranAt},耗时 ${Math.round((ledgerReuse.result.durationMs || 0) / 1000)}s)`,
|
|
963
|
+
command: `${ledgerReuse.runPlan?.[0]?.runner || 'npm test'} (ledger-reuse)`,
|
|
964
|
+
exitCode: 0, durationMs: 0, outputTail: null,
|
|
965
|
+
resultPath: null, strategy: ledgerReuse.result.strategy || null,
|
|
966
|
+
}
|
|
967
|
+
console.log(`\n♻️ Verify 测试对账:P2 账本复用(三键全等,免重跑;实测于 ${ledgerReuse.result.ranAt})`)
|
|
968
|
+
printVerifyTestCheck(testCheck)
|
|
969
|
+
} else if (reusableScan && reusableScan.testResult) {
|
|
956
970
|
testCheck = reusableScan.testResult
|
|
957
971
|
console.log(`\n♻️ Verify 测试对账:复用 noAI 质量扫描步的实测结果(代码指纹匹配,免重跑;实测于 ${reusableScan.ranAt || '本变更 verify 期间'}${testCheck.resultPath ? `,台账 ${testCheck.resultPath}` : ''})`)
|
|
958
972
|
printVerifyTestCheck(testCheck)
|
|
959
973
|
} else {
|
|
960
974
|
// 测试实测是同步 execSync,长套件可跑 2~10min 且中途无输出——先预告避免 agent 误判卡死
|
|
961
975
|
console.log(`\n⏳ Verify 测试对账:CLI 亲自执行 local.yaml 的 commands.test(同步,耗时可能较长,请等待…)`)
|
|
962
|
-
|
|
976
|
+
// restrictFiles 接线(2026-09-26-verify-gate-restrictfiles,评审 P2 留痕缺口的清偿):
|
|
977
|
+
// quick 门(quick-audit.js:584)传声明文件收窄模块选择,verify 门此前没传——变更全提交后
|
|
978
|
+
// verify --done 同形下文件面解析 0 命中可能假 skip(该实测没实测)。与 :1135 lint scope
|
|
979
|
+
// 同源口径(includeWorkingTree 含 worktree 未提交);空清单不传(restrict 空数组反而制造
|
|
980
|
+
// 假 skip——空=解析失败应走全量硬门,不降级)。
|
|
981
|
+
let _restrict = null
|
|
982
|
+
try {
|
|
983
|
+
const { resolveVerifyChangedFiles } = await import('../verify-postcheck.js')
|
|
984
|
+
_restrict = resolveVerifyChangedFiles(cwd, changeName, null, { includeWorkingTree: true, specBase: gateSpecBase })
|
|
985
|
+
} catch { /* 解析异常走全量(restrictFiles 不传) */ }
|
|
986
|
+
testCheck = runVerifyTestCheck({ cwd: gateCwd, specBase: gateSpecBase, changeName, ctx, ...(Array.isArray(_restrict) && _restrict.length > 0 ? { restrictFiles: _restrict } : {}) })
|
|
963
987
|
printVerifyTestCheck(testCheck)
|
|
988
|
+
// 自动绑定补全(full-autopilot-parity,R21 thin 迁移):测试已跑、test-result.json 已落盘,
|
|
989
|
+
// verify --done 收口时从测试结果自动补全 requirements.md 的空绑定槽——与 thin 同逻辑(逐行扫描)。
|
|
990
|
+
try {
|
|
991
|
+
const { verifyRequirementBindings } = await import('../flow-draft.js')
|
|
992
|
+
let _rb = verifyRequirementBindings({ changeDir: resolveChangeDir(cwd, { currentChange: changeName }, platformOpts?.specRoot) })
|
|
993
|
+
if (_rb.applicable && _rb.emptySlots.length > 0) {
|
|
994
|
+
const _runsDir = join(resolveRuntimeRoot(platformOpts, specBase), 'verify-runs')
|
|
995
|
+
let _testFiles = new Set()
|
|
996
|
+
if (existsSync(_runsDir)) {
|
|
997
|
+
for (const _rd of readdirSync(_runsDir).sort().reverse().slice(0, 3)) {
|
|
998
|
+
const _tr = join(_runsDir, _rd, 'test-result.json')
|
|
999
|
+
if (!existsSync(_tr)) continue
|
|
1000
|
+
try {
|
|
1001
|
+
const _j = JSON.parse(readFileSync(_tr, 'utf8'))
|
|
1002
|
+
// P1 修复:从 command 解析测试文件路径(匹配 test_*.py / *.test.ts 等)
|
|
1003
|
+
const _cmd = String(_j.command || '')
|
|
1004
|
+
for (const _fm of _cmd.matchAll(/([\w\/\\.-]+\.\w{1,5})/g)) {
|
|
1005
|
+
if (/test|spec/i.test(_fm[1])) _testFiles.add(_fm[1].replace(/\\/g, '/'))
|
|
1006
|
+
}
|
|
1007
|
+
if (_testFiles.size > 0) break
|
|
1008
|
+
} catch {}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
if (_testFiles.size > 0) {
|
|
1012
|
+
const _reqPath = join(resolveChangeDir(cwd, { currentChange: changeName }, platformOpts?.specRoot), 'requirements.md')
|
|
1013
|
+
const _lines = readFileSync(_reqPath, 'utf8').split('\n')
|
|
1014
|
+
const _bind = [..._testFiles].slice(0, 3).join('、')
|
|
1015
|
+
let _filled = 0
|
|
1016
|
+
for (let _li = 0; _li < _lines.length - 1; _li++) {
|
|
1017
|
+
if (/^<!--\s*AGENT:测试绑定/.test(_lines[_li]) && !_lines[_li + 1].trim()) {
|
|
1018
|
+
_lines[_li + 1] = _bind; _filled++; _li++
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
if (_filled > 0) {
|
|
1022
|
+
const { writeAtomicSync } = await import('../fs-atomic.js')
|
|
1023
|
+
writeAtomicSync(_reqPath, _lines.join('\n'))
|
|
1024
|
+
console.log(`✅ [自动绑定] ${_filled} 个空绑定槽已从测试结果补全(${_bind.slice(0, 60)})——agent 可覆盖(full-autopilot-parity)`)
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
} catch { /* auto-bind fail-soft */ }
|
|
1029
|
+
// P2 记账(fail-closed 层②:只有通过结果落账本——失败永不缓存,修复后重跑才能记)
|
|
1030
|
+
if (!traceHasActiveRows) {
|
|
1031
|
+
try {
|
|
1032
|
+
const { recordTestLedger } = await import('./test-ledger.js')
|
|
1033
|
+
if (testCheck && testCheck.status === 'passed') {
|
|
1034
|
+
recordTestLedger({
|
|
1035
|
+
runtimeRoot: resolveRuntimeRoot(platformOpts, specBase),
|
|
1036
|
+
changeName, projectRoot: gateCwd, testRoot: join(gateCwd, 'test'), specBase: gateSpecBase, cwd: gateCwd,
|
|
1037
|
+
result: { pass: true, total: testCheck.total ?? null, failedFiles: [], durationMs: testCheck.durationMs ?? null, strategy: testCheck.strategy ?? null },
|
|
1038
|
+
})
|
|
1039
|
+
}
|
|
1040
|
+
} catch { /* 记账异常不影响门禁(下次仍真跑) */ }
|
|
1041
|
+
}
|
|
964
1042
|
}
|
|
1043
|
+
// ── trace 晋升(2026-09-24-fr-test-bindings task-04,fr-test-binding §3.2/D-003@v1):
|
|
1044
|
+
// verify 门通过点按矩阵判定列晋升 candidate→active(covered/covered-service→active+
|
|
1045
|
+
// confirmed_by=agent;partial 留;uncovered/non-testable 删行)。fail-open:晋升异常不拦门。──
|
|
1046
|
+
try {
|
|
1047
|
+
const verifyMdForPromo = join(specBase, 'changes', changeName, 'verify-result.md')
|
|
1048
|
+
if (existsSync(verifyMdForPromo)) {
|
|
1049
|
+
const { extractAcceptanceMatrixSlots } = await import('../stage-contract.js')
|
|
1050
|
+
const { promoteTraceFromMatrix } = await import('../test-bindings.js')
|
|
1051
|
+
const slots = extractAcceptanceMatrixSlots(readFileSync(verifyMdForPromo, 'utf8'))
|
|
1052
|
+
if (slots.present && Array.isArray(slots.rows) && slots.rows.length > 0) {
|
|
1053
|
+
const promo = promoteTraceFromMatrix({
|
|
1054
|
+
changeDir: join(specBase, 'changes', changeName), changeName, matrixRows: slots.rows,
|
|
1055
|
+
})
|
|
1056
|
+
if (promo.promoted > 0 || promo.dropped > 0) {
|
|
1057
|
+
console.log(`🔗 绑定晋升:${promo.promoted} 行→active(confirmed_by=agent),${promo.dropped} 行删(uncovered/non-testable),${promo.kept} 行保持`)
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
} catch { /* 晋升 fail-open(不拦 verify 门,归档以 trace 现态为准) */ }
|
|
965
1062
|
// tests 段二次回填(2026-09-08-ir-verify-facts FR-03 次序:实测在 runValidators 之后,
|
|
966
1063
|
// tests 快照此刻才可得;不参与门禁——供 P3d 追溯)
|
|
967
1064
|
try {
|
|
@@ -1091,7 +1188,8 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
1091
1188
|
const deletionCheck = runVerifyDeletionCheck({ cwd, specBase, changeName })
|
|
1092
1189
|
printVerifyDeletionCheck(deletionCheck)
|
|
1093
1190
|
// ── required-evidence 对账(2026-09-08-ir-verify-facts FR-03:v2 起 cannot_verify 不闭环阻断)──
|
|
1094
|
-
// execute Task Review Gate
|
|
1191
|
+
// verify-required-evidence.json 由退役前的 execute Task Review Gate 写入
|
|
1192
|
+
// (2026-09-26-task-review-retire 起停写;在场则消费=历史变更兼容读,缺席=无 cannot_verify 任务)。
|
|
1095
1193
|
// v2 槽优先分类核验(FR-02):blocked = missing 无豁免或 satisfied 核验不过 → 阻断 verify 完成
|
|
1096
1194
|
// (此前 advisory 只查「提及」——死链不闭环);无槽存量 md 降级 legacy 子串对账(warning 不阻断)。
|
|
1097
1195
|
const { runVerifyRequiredEvidenceCheck, printVerifyRequiredEvidenceCheck, trackVerifyResultRegression } = await import('../verify-postcheck.js')
|
|
@@ -1199,6 +1297,26 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
1199
1297
|
writeVerifyGatePointer({ runtimeRoot: reconcileRuntimeRoot, changeName, blocked: true, note: '探针一致性抽查 error 阻断' })
|
|
1200
1298
|
return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts, { type: 'gate_rollback', detail: 'verify-contract' })
|
|
1201
1299
|
}
|
|
1300
|
+
// ── verify 填槽制篡改门禁(r5l 方案3 / 评审护栏#2 承重件,v2 件二)──
|
|
1301
|
+
// verify-probes --init --draft 生成的机器预填段(任务完成度/风险等级/测试结果/决策追踪)带
|
|
1302
|
+
// sha256 指纹标记 + .runtime sidecar 台账;--done 对账:标记被删 / 内容哈希失配 → 阻断回滚
|
|
1303
|
+
// (heredoc 整份重写的典型形态——Write 可用与否均免疫,机制修法不做生成侧劝说)。修改机器
|
|
1304
|
+
// 段唯一通道 verify-probes --amend-draft(重锚 + amendment 审计)。无 sidecar(未用 draft /
|
|
1305
|
+
// 存量变更)→ not-applicable 零红;门禁自身异常 fail-soft(对齐探针抽查 degraded 语义)。
|
|
1306
|
+
try {
|
|
1307
|
+
const { checkDraftIntegrity } = await import('../verify-draft.js')
|
|
1308
|
+
const draftCheck = checkDraftIntegrity({
|
|
1309
|
+
mdPath: join(specBase, 'changes', changeName, 'verify-result.md'),
|
|
1310
|
+
changeName, runtimeRoot: reconcileRuntimeRoot,
|
|
1311
|
+
})
|
|
1312
|
+
if (draftCheck.applicable && !draftCheck.ok) {
|
|
1313
|
+
console.error(`\n❌ verify-result.md 机器预填段篡改门禁:${draftCheck.violations.length} 处失配——draft 预填段被整份重写或标记被删/被手工重锚。`)
|
|
1314
|
+
for (const v of draftCheck.violations) console.error(` - ${v}`)
|
|
1315
|
+
console.error(' 修复:还原机器段原文(AGENT 槽——结论枚举/移交项/审查叙述——不受本门限制);确要修改机器段,跑 sillyspec verify-probes --change ' + changeName + ' --amend-draft 留痕重锚后重新 --done。')
|
|
1316
|
+
writeVerifyGatePointer({ runtimeRoot: reconcileRuntimeRoot, changeName, blocked: true, note: 'draft 篡改门禁阻断' })
|
|
1317
|
+
return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts, { type: 'gate_rollback', detail: 'verify-contract' })
|
|
1318
|
+
}
|
|
1319
|
+
} catch { /* 门禁异常 fail-soft(不是篡改,对齐探针 degraded 放行) */ }
|
|
1202
1320
|
// ── ceremony 双跑收口·第一出口(2026-09-18-ceremony-risk-pricing task-04 / FR-03 / D-003;
|
|
1203
1321
|
// execute 评审 FAIL 项补接线):声明档(.runtime/ceremony-tier-<change>.json 开跑定价)vs
|
|
1204
1322
|
// 实际 diff 重跑 blast+span 的事实档,mismatch(severity=error,懒 agent 低报)汇入 verify
|
|
@@ -1248,6 +1366,31 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
1248
1366
|
reapVerifyServices(platformOpts, specBase, changeName)
|
|
1249
1367
|
} catch (e) { console.warn(`⚠️ verify 服务进程回收异常(不阻断): ${e.message}`) }
|
|
1250
1368
|
|
|
1369
|
+
// ── 归档就绪度报告(P3 / FR-03 / D-003@batch3,task-04):构造归 buildArchiveReadinessReport
|
|
1370
|
+
// (本文件下方导出的可测函数),此处只渲染。全只读 fail-open;无发现零输出。──
|
|
1371
|
+
try {
|
|
1372
|
+
const report = await buildArchiveReadinessReport({ cwd, changeName, progress, specBase: platformOpts?.specRoot || specBase })
|
|
1373
|
+
if (report && (report.face.length > 0 || report.manifestGaps.length > 0 || report.impactUnlisted.length > 0 || report.agingHit)) {
|
|
1374
|
+
console.log('\n📋 归档就绪度报告(verify 收口前置——archive 三查预演,全只读):')
|
|
1375
|
+
if (report.face.length > 0) {
|
|
1376
|
+
console.log(' ⎿ 未-apply 交付面 ' + report.face.length + ' 个文件:' + report.face.slice(0, 5).join('、') + (report.face.length > 5 ? ' 等' : ''))
|
|
1377
|
+
console.log(' 建议 archive 前先 apply:sillyspec worktree apply ' + changeName)
|
|
1378
|
+
} else {
|
|
1379
|
+
console.log(' ⎿ 未-apply 交付面:0(干净)')
|
|
1380
|
+
}
|
|
1381
|
+
if (report.manifestGaps.length > 0) {
|
|
1382
|
+
console.log(' ⎿ manifest 缺行草拟【待确认——确认后自行补入 design.md §6,草拟不代写】:')
|
|
1383
|
+
for (const g of report.manifestGaps.slice(0, 5)) console.log(' | 修改 | ' + g + ' | 归档就绪度草拟(verify 收口前置探测) |')
|
|
1384
|
+
if (report.manifestGaps.length > 5) console.log(' … 共 ' + report.manifestGaps.length + ' 个')
|
|
1385
|
+
}
|
|
1386
|
+
if (report.impactUnlisted.length > 0) {
|
|
1387
|
+
console.log(' ⎿ module-impact 归因草拟【待确认——确认后补入未匹配/矩阵章节】:')
|
|
1388
|
+
for (const u of report.impactUnlisted.slice(0, 5)) console.log(' - `' + u + '` → 归因(共位测试/镜像/并行排除…)')
|
|
1389
|
+
if (report.impactUnlisted.length > 5) console.log(' … 共 ' + report.impactUnlisted.length + ' 个')
|
|
1390
|
+
}
|
|
1391
|
+
if (report.agingHit) console.log(' ⚠️ main 已前进且与交付面有交集(基线老化中——等待越久合并面越大,batch2 实证窗口期三方冲突):建议立即 apply')
|
|
1392
|
+
}
|
|
1393
|
+
} catch { /* 就绪度报告整体 fail-open:异常零输出不影响 verify 收尾 */ }
|
|
1251
1394
|
console.log('\n✅ 验证通过,下一步:sillyspec run archive')
|
|
1252
1395
|
}
|
|
1253
1396
|
|
|
@@ -1301,6 +1444,9 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
1301
1444
|
|
|
1302
1445
|
if (tier.tier === 'self') {
|
|
1303
1446
|
console.log('\nℹ️ Stage Review: ' + stageName + ' tier=self(' + tier.reason + '),已降级为当前 agent 自审,不强制独立子代理。')
|
|
1447
|
+
} else if (readReviewUnsupervisedWaiver(reviewChangeDir)) {
|
|
1448
|
+
// 豁免凭据(review-unsupervised-exit):无嵌套派发能力环境不强制独立审查也不接受自审表演
|
|
1449
|
+
await waiveWithTelemetry({ changeName, gate: 'stage-review:' + stageName, specBase: effectiveSpecBase })
|
|
1304
1450
|
} else {
|
|
1305
1451
|
let reviewRunId = getLatestStageReviewRunId(runtimeRoot, stageName, changeName)
|
|
1306
1452
|
if (!reviewRunId) {
|
|
@@ -1334,8 +1480,8 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
1334
1480
|
const reviewResult = gated.result
|
|
1335
1481
|
printStageReviewResult(reviewResult, { stage: stageName, reviewRunId, runtimeRoot, changeName })
|
|
1336
1482
|
if (!reviewResult.ok) {
|
|
1337
|
-
|
|
1338
|
-
}
|
|
1483
|
+
docGateFailures.push({ type: 'review_rejected', detail: 'stage-review', label: 'Stage Review 未过(stage-review,明细见上)' })
|
|
1484
|
+
} else {
|
|
1339
1485
|
// 降级自审留痕(2026-09-10 用户反馈①:PI agent 等宿主无 Agent tool,prompt 降级条款允许
|
|
1340
1486
|
// 当前 agent 自审产出 review.json)。放行但独立性折损必须可见:⚠️ 审计行区分降级 review
|
|
1341
1487
|
// 与真子代理 review(事后可审计),与 ⚖️/🔍 软归属同哲学——可见的折损优于静默的伪装。
|
|
@@ -1356,11 +1502,13 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
1356
1502
|
if (hasDelegatedWriteDisclosure(reviewResult.review)) {
|
|
1357
1503
|
console.warn(`\n⚠️ Stage Review 代落盘披露:${stageName} review 的 reviewerNotes 首行标记「代落盘」——review.json 由主代理代(写通道故障的)审查者落盘,独立性依赖审查者结论回传的忠实性,建议人工抽查关键结论与回传文本一致性。`)
|
|
1358
1504
|
}
|
|
1505
|
+
}
|
|
1359
1506
|
}
|
|
1360
1507
|
} catch (e) {
|
|
1361
|
-
// fail-closed:Gate
|
|
1508
|
+
// fail-closed:Gate 自身异常阻断完成,不静默放行
|
|
1509
|
+
//(收集模式:异常也入列,检查点 B 统一 rollback)
|
|
1362
1510
|
console.error('❌ Stage Review Gate 异常,阻断 ' + stageName + ' 完成: ' + e.message)
|
|
1363
|
-
|
|
1511
|
+
docGateFailures.push({ type: 'review_rejected', detail: 'stage-review', label: `Stage Review Gate 异常(${e.message})` })
|
|
1364
1512
|
}
|
|
1365
1513
|
}
|
|
1366
1514
|
|
|
@@ -1384,109 +1532,21 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
1384
1532
|
} catch { /* 提示失败不阻断 execute */ }
|
|
1385
1533
|
}
|
|
1386
1534
|
|
|
1387
|
-
// ── Execute Task Review Gate
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
const runtimeRoot = resolveRuntimeRoot(platformOpts, effectiveSpecBase)
|
|
1403
|
-
|
|
1404
|
-
// execute run id:从变更专属标记文件读取(agent 可写内容,格式校验防注入/穿越)
|
|
1405
|
-
const runIdFile = join(runtimeRoot, `current-execute-run-id-${changeName}`)
|
|
1406
|
-
let executeRunId = ''
|
|
1407
|
-
try {
|
|
1408
|
-
if (existsSync(runIdFile)) {
|
|
1409
|
-
const c = readFileSync(runIdFile, 'utf8').trim()
|
|
1410
|
-
const { isValidExecuteRunId } = await import('../task-review.js')
|
|
1411
|
-
if (c && !isValidExecuteRunId(c)) {
|
|
1412
|
-
console.warn(`⚠️ execute run marker 内容非法(期望 exec-YYYY-MM-DD-HHMMSS,实得 ${JSON.stringify(c.slice(0, 60))}),视为缺失回退扫描`)
|
|
1413
|
-
} else {
|
|
1414
|
-
executeRunId = c
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
} catch {}
|
|
1418
|
-
if (!executeRunId) {
|
|
1419
|
-
// marker 缺失:先扫描 execute-runs/ 既有目录找回真实 runId(与 getLatestStageReviewRunId
|
|
1420
|
-
// 目录扫描兜底同语义),避免 marker 丢失而 agent 已用旧 runId 落盘时,直接 generate 新 ID
|
|
1421
|
-
// 找不到旧 review、误判缺 review.json。仅当确实无既有 run 才 generate 新 ID 并落盘。
|
|
1422
|
-
const { generateExecuteRunId, resolveLatestExecuteRunId, stampExecuteRunChange, claimExecuteRunId } = await import('../task-review.js')
|
|
1423
|
-
executeRunId = resolveLatestExecuteRunId({ runtimeRoot, changeName }) || ''
|
|
1424
|
-
if (!executeRunId) {
|
|
1425
|
-
executeRunId = generateExecuteRunId(changeName)
|
|
1426
|
-
// 落盘(marker 缺失时 fallback 生成后写盘,保证后续 checkbox/gate 读到同一 ID)
|
|
1427
|
-
// D-001#1 fallback 写入点:mkdir execute-runs/<runId>/tasks 先于 marker(不变量:
|
|
1428
|
-
// marker 在则目录在)。不 try/catch——异常直穿外层 catch 走 fail-closed 阻断
|
|
1429
|
-
//(gate 自身写 run 目录失败不能静默放行完成)。
|
|
1430
|
-
// 排他认领(坑 exec-run-id-same-second-collision):并行会话同秒碰撞在认领处消解
|
|
1431
|
-
executeRunId = claimExecuteRunId(runtimeRoot, executeRunId)
|
|
1432
|
-
mkdirSync(join(runtimeRoot, 'execute-runs', executeRunId, 'tasks'), { recursive: true })
|
|
1433
|
-
writeAtomicSync(runIdFile, executeRunId + '\n')
|
|
1434
|
-
stampExecuteRunChange(runtimeRoot, executeRunId, changeName)
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
// git 真实性校验目录:worktree 存在则用 worktree(base/head commit 在其中),否则主仓库。
|
|
1439
|
-
// 跨仓支持(task-07 / design §6 gates 行 + D-013):
|
|
1440
|
-
// - 有 ctx(execute 启动入口 task-09 构造的 MultiRepoContext 实例)→ 用
|
|
1441
|
-
// ctx.resolve('main').gitDir 作为主仓 task 的校验 cwd。ctx 的 main entry 已在
|
|
1442
|
-
// _buildMainEntry 内统一编码 worktreePath/cwd + in-place-fallback 兜底(与 task-review.js:724
|
|
1443
|
-
// generateTaskReviewDrafts 的 in-place 逻辑同源),避免两处漂移。Task Review Gate 内部循环
|
|
1444
|
-
// 再按 review.repo ?? 'main' 从 ctx.resolve(repo).gitDir 切跨仓 task 的 gitDir(task-04 实现)。
|
|
1445
|
-
// - 无 ctx(ctx=null 缺省,单仓 / 旧调用链)→ 走原逻辑(meta.worktreePath 或 cwd),零回归。
|
|
1446
|
-
let reviewGitDir = cwd
|
|
1447
|
-
if (ctx) {
|
|
1448
|
-
const mainEntry = ctx.resolve('main')
|
|
1449
|
-
if (mainEntry?.gitDir) reviewGitDir = mainEntry.gitDir
|
|
1450
|
-
} else {
|
|
1451
|
-
try {
|
|
1452
|
-
const { WorktreeManager } = await import('../worktree.js')
|
|
1453
|
-
const wm = new WorktreeManager({ cwd })
|
|
1454
|
-
const meta = wm.getMeta(changeName)
|
|
1455
|
-
if (meta?.worktreePath && meta.mode !== 'in-place-fallback' && existsSync(meta.worktreePath)) {
|
|
1456
|
-
reviewGitDir = meta.worktreePath
|
|
1457
|
-
}
|
|
1458
|
-
} catch {}
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
const reviewResult = validateTaskReviews({ planContent, runtimeRoot, executeRunId, changeDir: planFile, gitDir: reviewGitDir, ctx })
|
|
1462
|
-
printReviewResult(reviewResult, { runtimeRoot, executeRunId })
|
|
1463
|
-
|
|
1464
|
-
if (!reviewResult.ok) {
|
|
1465
|
-
// Task review 校验失败,阻断 execute 完成
|
|
1466
|
-
// 检查是否存在 checkbox 已勾但 review 不通过的情况
|
|
1467
|
-
const uncheckedTasks = reviewResult.errors.filter(e => e.includes('缺少 review.json'))
|
|
1468
|
-
if (uncheckedTasks.length > 0) {
|
|
1469
|
-
console.error('\n⚠️ 部分任务已在 tasks.md 中勾选,但 review.json 不存在。')
|
|
1470
|
-
console.error(` 请取消勾选这些任务的 checkbox,或补充对应的 review.json(execute run ID: ${executeRunId})。`)
|
|
1471
|
-
}
|
|
1472
|
-
return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts, { type: 'review_rejected', detail: 'task-review' })
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
// cannot_verify 的 requiredEvidence 写入 change 目录,供 verify 阶段消费
|
|
1476
|
-
if (reviewResult.requiredEvidence.length > 0) {
|
|
1477
|
-
const evidencePath = writeVerifyRequiredEvidence(join(effectiveSpecBase, 'changes', changeName), reviewResult.requiredEvidence)
|
|
1478
|
-
if (evidencePath) {
|
|
1479
|
-
console.log(`📄 verify-required-evidence.json 已写入: ${evidencePath}`)
|
|
1480
|
-
console.log(' verify 阶段必须满足这些证据要求。')
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
}
|
|
1484
|
-
} catch (e) {
|
|
1485
|
-
// fail-closed:Gate 自身异常时不能默认放行,否则异常成了绕过评审的通道
|
|
1486
|
-
console.error(`❌ Task Review Gate 异常,阻断 execute 完成: ${e.message}`)
|
|
1487
|
-
console.error(' 请检查 review.json / plan.md 是否可读,修复后重新完成此步骤。')
|
|
1488
|
-
return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts, { type: 'review_rejected', detail: 'task-review' })
|
|
1489
|
-
}
|
|
1535
|
+
// ── Execute Task Review Gate 已退役(2026-09-26-task-review-retire)──
|
|
1536
|
+
// 原「所有 task 必须有 review.json 且 verdict 通过」块随层整体退役删除:R18 对撞实证该层在
|
|
1537
|
+
// 无嵌套派发环境全降自审表演(15 次拦截 5 次形式合规、实质拦截为零),前置豁免通道
|
|
1538
|
+
// (review-unsupervised-exit)已覆盖无派发环境。task 粒度审计由 execute 收口的代码证据核验
|
|
1539
|
+
// (checkExecuteCodeEvidence,detectExecuteBatchFinish 内)+ verify 测试对账承担;Stage Review
|
|
1540
|
+
// Gate(阶段粒度)保留。残存 review.json 走 task-review.js 保留导出(历史兼容读侧)。
|
|
1541
|
+
// ── 收集模式检查点 B(brainstorm/plan/execute):纯文档门(validators / Stage Review)
|
|
1542
|
+
// 全量收口——非空则一次打印清单、统一 rollback 一次(R15「连挡 4 次」的解;
|
|
1543
|
+
// detail 取首门,进度库信号语义不变)。verify 走检查点 A(其实测门在前)。──
|
|
1544
|
+
if (stageName !== 'verify' && docGateFailures.length > 0) {
|
|
1545
|
+
console.error(`\n❌ ${stageName} 收口被 ${docGateFailures.length} 道纯文档门拦截(一次性全列——全部修复后重跑一次 --done,不必逐门连撞):`)
|
|
1546
|
+
docGateFailures.forEach((f, i) => console.error(` ${i + 1}. ${f.label}`))
|
|
1547
|
+
console.error(` (各门明细见上方对应 ❌/⚠️ 段落)`)
|
|
1548
|
+
const firstFail = docGateFailures[0]
|
|
1549
|
+
return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts, { type: firstFail.type, detail: firstFail.detail })
|
|
1490
1550
|
}
|
|
1491
1551
|
|
|
1492
1552
|
// ── 仪式档位摩擦升档检查点(task-03 / D-008,design 生命周期契约表「阶段门升档」行)──
|
|
@@ -1762,6 +1822,27 @@ export async function completeStageGates({ stageName, cwd, changeName, platformO
|
|
|
1762
1822
|
}
|
|
1763
1823
|
}
|
|
1764
1824
|
|
|
1825
|
+
// ── full 流程绑定面补齐(2026-09-27-gate-face-binding-parity,R23-full 实证缺口)──
|
|
1826
|
+
// 绑定槽此前只在 thin flow start 收编时追加(flow.js ensureBindingSlots);full 流程
|
|
1827
|
+
// requirements 由 brainstorm 产出、无绑定面且不经过 flow start → test-trace 恒空 →
|
|
1828
|
+
// FR 入库永远无 tests 绑定,动态门 FR 回归源对 full 出身 FR 失效。此处单源复用
|
|
1829
|
+
// ensureBindingSlots(已有槽/无 FR 块均 no-op),verify --done 的 auto-bind 因槽在场闭环。
|
|
1830
|
+
// best-effort:追加失败只告警不阻断 brainstorm 收尾。
|
|
1831
|
+
if (stageName === 'brainstorm' && changeName) {
|
|
1832
|
+
try {
|
|
1833
|
+
const changeDir = join(specBase, 'changes', changeName)
|
|
1834
|
+
if (existsSync(join(changeDir, 'requirements.md'))) {
|
|
1835
|
+
const { ensureBindingSlots } = await import('../flow-draft.js')
|
|
1836
|
+
const b = ensureBindingSlots({ changeDir })
|
|
1837
|
+
if (b.appended) {
|
|
1838
|
+
console.log(`📌 追加测试绑定槽 ${b.slots} 枚(full 流程 requirements 无绑定面——plan/execute 写测试时作答,verify --done 将从实测结果自动补全空槽)`)
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
} catch (e) {
|
|
1842
|
+
console.warn(`⚠️ 绑定槽追加失败(best-effort 不阻断): ${(e && e.message) || e}`)
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1765
1846
|
// scan 平台 manifest + post-check(S1 平台受害者:noAI scanPostcheck 末步 / continueStep 现也走这里)
|
|
1766
1847
|
const _scanResult = await handleScanStageCompleted({ stageName, currentIdx, cwd, progress, pm, stageData, changeName, outputText, platformOpts })
|
|
1767
1848
|
if (_scanResult) return _scanResult
|
|
@@ -1801,7 +1882,7 @@ export async function completeStageGates({ stageName, cwd, changeName, platformO
|
|
|
1801
1882
|
pm._write(cwd, progress, changeName)
|
|
1802
1883
|
}
|
|
1803
1884
|
|
|
1804
|
-
// 阶段完成校验 gate 级联(runValidators → verify-test → Plan→Execute → Stage Review
|
|
1885
|
+
// 阶段完成校验 gate 级联(runValidators → verify-test → Plan→Execute → Stage Review)
|
|
1805
1886
|
if (settledCount === total && total > 0) {
|
|
1806
1887
|
const _gateEarlyReturn = await runStageCompletionGates({ stageName, cwd, changeName, platformOpts, specBase, progress, pm, stageData, steps, currentIdx, ctx })
|
|
1807
1888
|
if (_gateEarlyReturn) return _gateEarlyReturn
|
|
@@ -2240,7 +2321,7 @@ export function killProcessTree(pid, sig = 'SIGTERM') {
|
|
|
2240
2321
|
if (process.platform === 'win32') {
|
|
2241
2322
|
// /T = 连同子进程树(shell 包装下的 python/node 服务本体);/F = 强制(长驻服务常吞 SIGTERM 等价物)
|
|
2242
2323
|
try {
|
|
2243
|
-
execFileSync('taskkill', ['/PID', String(pidNum), '/T', '/F'], { stdio: 'ignore', timeout: 15000 })
|
|
2324
|
+
execFileSync('taskkill', ['/PID', String(pidNum), '/T', '/F'], { stdio: 'ignore', timeout: 15000, windowsHide: true })
|
|
2244
2325
|
return true
|
|
2245
2326
|
} catch (e) {
|
|
2246
2327
|
// taskkill 对不存在进程退出码 128(等价 ESRCH,已退出按回收静默);其余(拒绝访问等)上抛
|
|
@@ -2259,7 +2340,7 @@ export function killProcessTree(pid, sig = 'SIGTERM') {
|
|
|
2259
2340
|
/** ps 枚举直接子进程 PID(POSIX;失败返回空——杀不到子进程不阻断父进程回收) */
|
|
2260
2341
|
function listChildPids(parentPid) {
|
|
2261
2342
|
try {
|
|
2262
|
-
const out = execFileSync('ps', ['-axo', 'pid=,ppid='], { encoding: 'utf8', timeout: 5000 })
|
|
2343
|
+
const out = execFileSync('ps', ['-axo', 'pid=,ppid='], { encoding: 'utf8', timeout: 5000, windowsHide: true })
|
|
2263
2344
|
const children = []
|
|
2264
2345
|
for (const line of String(out).split('\n')) {
|
|
2265
2346
|
const m = line.trim().match(/^(\d+)\s+(\d+)$/)
|
|
@@ -2300,3 +2381,59 @@ export function reapVerifyServices(platformOpts, specBase, changeName) {
|
|
|
2300
2381
|
if (failedAll.length > 0) console.warn(`⚠️ 服务进程回收失败 ${failedAll.length} 个:${failedAll.join(', ')} — 手动处理:taskkill /PID <pid> /F(Windows)/ kill -9 <pid>`)
|
|
2301
2382
|
return { reaped: reapedTotal, receiptPath }
|
|
2302
2383
|
}
|
|
2384
|
+
|
|
2385
|
+
|
|
2386
|
+
/**
|
|
2387
|
+
* buildArchiveReadinessReport —— 归档就绪度报告构造(P3 / FR-03 / D-003@batch3,task-04)。
|
|
2388
|
+
* verify PASS 后、archive 前的三查前置(batch2 实证三查全压 archive 时段首查=15 回合主因)。
|
|
2389
|
+
* 纯读:①未-apply 交付面(applyWorktree checkOnly,含 ql-009②/ql-010 合并感知口径)②manifest
|
|
2390
|
+
* 缺行草拟集(面文件不在 design §6 清单——草拟不代写,agent 确认后自落)③module-impact 归因
|
|
2391
|
+
* 草拟集(面文件不在 module-impact.md 文本)④基线老化命中(main HEAD 前进过 worktree 基点
|
|
2392
|
+
* ∩ 前进文件与交付面有交集)。无 worktree(meta 缺/in-place/native)→ 全空(零噪音)。
|
|
2393
|
+
* fail-open:任一探测异常只置空该项,不抛(渲染层再兜一层)。
|
|
2394
|
+
*/
|
|
2395
|
+
export async function buildArchiveReadinessReport({ cwd, changeName, progress, specBase }) {
|
|
2396
|
+
const out = { face: [], manifestGaps: [], impactUnlisted: [], agingHit: false }
|
|
2397
|
+
try {
|
|
2398
|
+
const { WorktreeManager } = await import('../worktree.js')
|
|
2399
|
+
const wm = new WorktreeManager({ cwd })
|
|
2400
|
+
const meta = wm.getMeta(changeName)
|
|
2401
|
+
if (!meta || meta.mode === 'in-place-fallback' || meta.mode === 'native-worktree'
|
|
2402
|
+
|| !meta.worktreePath || !existsSync(meta.worktreePath)) return out
|
|
2403
|
+
const { applyWorktree } = await import('../worktree-apply.js')
|
|
2404
|
+
const probe = applyWorktree(changeName, { cwd, checkOnly: true })
|
|
2405
|
+
out.face = (probe && Array.isArray(probe.changedFiles)) ? probe.changedFiles : []
|
|
2406
|
+
if (out.face.length === 0) return out
|
|
2407
|
+
// ② manifest 缺行草拟
|
|
2408
|
+
try {
|
|
2409
|
+
const changeDir = resolveChangeDir(cwd, progress || {}, specBase)
|
|
2410
|
+
if (changeDir) {
|
|
2411
|
+
const designPath = join(changeDir, 'design.md')
|
|
2412
|
+
if (existsSync(designPath)) {
|
|
2413
|
+
const { parseFileChangeList } = await import('../change-list.js')
|
|
2414
|
+
const declared = parseFileChangeList(designPath, { keepSillyspecDocs: true })
|
|
2415
|
+
out.manifestGaps = out.face.filter(f => !declared.has(f))
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
} catch { /* 草拟失败置空该项 */ }
|
|
2419
|
+
// ③ module-impact 归因草拟
|
|
2420
|
+
try {
|
|
2421
|
+
const changeDir2 = resolveChangeDir(cwd, progress || {}, specBase)
|
|
2422
|
+
const miPath = changeDir2 ? join(changeDir2, 'module-impact.md') : null
|
|
2423
|
+
const miText = (miPath && existsSync(miPath)) ? readFileSync(miPath, 'utf8') : ''
|
|
2424
|
+
out.impactUnlisted = out.face.filter(f => !miText.includes(f))
|
|
2425
|
+
} catch { /* 同上 */ }
|
|
2426
|
+
// ④ 基线老化命中
|
|
2427
|
+
try {
|
|
2428
|
+
if (meta.baseHash) {
|
|
2429
|
+
const { gitQuiet } = await import('../git-helper.js')
|
|
2430
|
+
const advanced = gitQuiet(cwd, ['diff', '--name-only', meta.baseHash + '..HEAD'])
|
|
2431
|
+
if (advanced) {
|
|
2432
|
+
const advSet = new Set(advanced.split('\n').filter(Boolean))
|
|
2433
|
+
out.agingHit = out.face.some(f => advSet.has(f))
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
} catch { /* git 失败不提示 */ }
|
|
2437
|
+
} catch { /* 整体 fail-open → 全空 */ }
|
|
2438
|
+
return out
|
|
2439
|
+
}
|