sillyspec 3.28.0 → 3.28.3
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 -93
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -114
- package/.claude/skills/sillyspec-continue/SKILL.md +17 -47
- package/.claude/skills/sillyspec-execute/SKILL.md +222 -214
- package/.claude/skills/sillyspec-knowledge/SKILL.md +40 -270
- package/.claude/skills/sillyspec-plan/SKILL.md +156 -156
- package/.claude/skills/sillyspec-propose/SKILL.md +16 -87
- package/.claude/skills/sillyspec-resume/SKILL.md +21 -70
- package/.claude/skills/sillyspec-state/SKILL.md +25 -64
- package/.claude/skills/sillyspec-workspace/SKILL.md +33 -175
- package/package.json +1 -1
- package/src/archive-delta.js +462 -0
- package/src/constants.js +6 -0
- package/src/design-facts.js +418 -0
- package/src/dispatch/backends/sillyhub-mcp.js +17 -5
- package/src/docs-check.js +197 -34
- package/src/docs-migrate.js +124 -0
- package/src/doctor-diagnostics.js +11 -2
- package/src/endpoint-baseline.js +109 -0
- package/src/git-helper.js +31 -0
- package/src/index.js +381 -22
- package/src/modules.js +41 -12
- package/src/progress/change-registry.js +39 -0
- package/src/progress/consistency-doctor.js +124 -5
- package/src/progress/stage-machine.js +13 -2
- package/src/progress.js +5 -0
- package/src/quicklog.js +51 -0
- package/src/run/command.js +199 -5
- package/src/run/complete-handlers.js +60 -4
- package/src/run/complete.js +105 -6
- package/src/run/gates.js +353 -2
- package/src/run/progress-confirm.js +66 -0
- package/src/run/prompt.js +193 -15
- package/src/run/quick-audit.js +12 -5
- package/src/run/scan-profile.js +117 -7
- package/src/run/shared.js +22 -6
- package/src/run/stage.js +14 -1
- package/src/scan-diff.js +54 -1
- package/src/scan-facts.js +173 -0
- package/src/scan-postcheck.js +114 -3
- package/src/spec-sync.js +19 -2
- package/src/stage-review.js +62 -3
- package/src/stages/archive.js +5 -5
- package/src/stages/brainstorm-auto.js +2 -1
- package/src/stages/brainstorm.js +12 -20
- package/src/stages/execute.js +19 -21
- package/src/stages/plan-postcheck.js +194 -1
- package/src/stages/plan.js +8 -3
- package/src/stages/quick.js +4 -2
- package/src/stages/scan.js +44 -102
- package/src/stages/verify.js +18 -18
- package/src/task-review.js +200 -0
- package/src/taskcard.js +8 -1
- package/src/verify-postcheck.js +771 -29
- package/src/verify-probes.js +89 -10
- package/src/workflow.js +30 -0
- package/templates/agents-instruction.md +1 -1
- package/templates/prompts/taskcard-rules.md +5 -0
- package/templates/workflows/scan-docs.yaml +6 -0
- package/.claude/skills/verify-per-user/SKILL.md +0 -6
package/src/run/gates.js
CHANGED
|
@@ -666,6 +666,61 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
666
666
|
const { runVerifyRequiredEvidenceCheck, printVerifyRequiredEvidenceCheck } = await import('../verify-postcheck.js')
|
|
667
667
|
const evidenceCheck = runVerifyRequiredEvidenceCheck({ cwd, specBase, changeName })
|
|
668
668
|
printVerifyRequiredEvidenceCheck(evidenceCheck)
|
|
669
|
+
// ── target_files 声明 ↔ 实际改动 对账(task-05 / ir-stage-p3a:②ERROR 阻断 / ③WARNING 放行)──
|
|
670
|
+
// 「计划落空」(task 卡声明没做)此前全盲区:review.json changedFiles 是 agent 手写不能当
|
|
671
|
+
// 事实源,execute 的 scope creep 只有 symbol-impact 试图抓。reconcileTargetFiles 用 git 三源
|
|
672
|
+
// 口径亲自取 actual,与 plan 阶段 task 卡 target_files 声明做三类差集:②类(声明未做)在场
|
|
673
|
+
// 即阻断——verify 的语义是「按计划交付且客观核验」,声明没做不能盖章归档;③类(做了没
|
|
674
|
+
// 声明=scope creep)启发式噪音面大(并行 WIP 剔除后仍可能有工具产物),WARNING 放行留审计;
|
|
675
|
+
// skipped/degraded(无 task 卡 / 全无声明 / git 不可用)降级不误红(存量变更零红门禁)。
|
|
676
|
+
// 阻断形态照 runVerifyTestCheck 先例:逐条列 task + path + 修复提示 + rollbackCompletionAndReturn。
|
|
677
|
+
const { reconcileTargetFiles, isStrictChange } = await import('../verify-postcheck.js')
|
|
678
|
+
const reconcileRuntimeRoot = resolveRuntimeRoot(platformOpts, specBase)
|
|
679
|
+
const reconcileCheck = reconcileTargetFiles({
|
|
680
|
+
cwd, specBase, changeName,
|
|
681
|
+
// runtimeRoot 口径与上方 parity 对账同源(B3 apply-pathspec 兜底源在此根下;平台模式下
|
|
682
|
+
// specBase 与 .runtime 分离,不传会读不到兜底清单 → 假降级)
|
|
683
|
+
runtimeRoot: reconcileRuntimeRoot,
|
|
684
|
+
// IR 严格档(2026-09-07-ir-hardening D-003@v1):主仓卡全零声明 → strictViolation → ERROR
|
|
685
|
+
strictMode: isStrictChange({ pm, cwd, changeName }),
|
|
686
|
+
})
|
|
687
|
+
const reconcileEnvelope = buildReconcileTargetFilesEnvelope(reconcileCheck)
|
|
688
|
+
const reconcileBlocked = printReconcileTargetFilesCheck(reconcileCheck, reconcileEnvelope)
|
|
689
|
+
// 结果落盘(design Wave 2 承诺:对齐 .runtime/verify-runs/<ts>/ 先例)——五状态全落(含
|
|
690
|
+
// 放行态),fail-soft 不影响 gate 判定;②类阻断回执同样留档供平台/审计消费
|
|
691
|
+
writeReconcileRunResult({ runtimeRoot: reconcileRuntimeRoot, changeName, envelope: reconcileEnvelope, result: reconcileCheck })
|
|
692
|
+
if (reconcileBlocked) {
|
|
693
|
+
return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts)
|
|
694
|
+
}
|
|
695
|
+
// ── verify-result.md 探针预填段一致性抽查(task-03 / ir-stage-p3b D-002@v1 方案A)──
|
|
696
|
+
// 「#### 探针 N」机械预填段(verify-probes --init 生成)长在 agent 可编辑的正文里,其防篡改
|
|
697
|
+
// 基准是正文锚点而非 verify-facts.json(删底稿绕不过防护)。checkProbeConsistency 重跑只读
|
|
698
|
+
// 探针与正文锚点对账,分级(D-003):probe1/probe6 不符 = ERROR → 阻断回滚(正文预填段疑似
|
|
699
|
+
// 被篡改);probe3/probe5 环境敏感维度 + R-06 HEAD 前进降级 = WARNING → 放行告警(drift);
|
|
700
|
+
// skipped(存量旧格式报告/无 changeName)/degraded(重跑异常 fail-soft)→ 放行提示。
|
|
701
|
+
// 接线形态照上方 P3a reconcile 先例:envelope + print(ERROR 时 console.error 逐条 mismatch +
|
|
702
|
+
// supportedFixes)+ verify-runs 落盘 + rollbackCompletionAndReturn。
|
|
703
|
+
const { checkProbeConsistency } = await import('../verify-postcheck.js')
|
|
704
|
+
const probeCheck = checkProbeConsistency({
|
|
705
|
+
// 四参与 reconcile 同源取值:cwd/specBase/changeName 原样透传;runtimeRoot 复用上方
|
|
706
|
+
// reconcileRuntimeRoot(resolveRuntimeRoot(platformOpts, specBase))——checkProbeConsistency
|
|
707
|
+
// 的锚点产物实际都在 changeDir,此参为统一传参签名对齐预留,口径一处定义不二算
|
|
708
|
+
cwd, specBase, changeName,
|
|
709
|
+
runtimeRoot: reconcileRuntimeRoot,
|
|
710
|
+
// IR 严格档(2026-09-07-ir-hardening D-002@v1):created_at ≥ IR_STRICT_SINCE 的变更
|
|
711
|
+
// 探针子节全缺 = ERROR(存量 skip 语义零变化);fail-open(读不到 → false 落豁免)
|
|
712
|
+
strictMode: isStrictChange({ pm, cwd, changeName }),
|
|
713
|
+
})
|
|
714
|
+
const probeEnvelope = buildProbeConsistencyEnvelope(probeCheck)
|
|
715
|
+
const probeBlocked = printProbeConsistencyCheck(probeCheck, probeEnvelope)
|
|
716
|
+
// 结果落盘(对齐 writeReconcileRunResult 先例):独立文件 probe-consistency-result.json——
|
|
717
|
+
// 与 reconcile-result.json 同 gate 运行同目录组织(verify-runs/<ts>/),但不并入后者:
|
|
718
|
+
// reconcile-result.json 的 schema 已被 P3a 消费方锁定,塞探针字段(读改写/扩参)只会引入
|
|
719
|
+
// 两检查间耦合;各写各的回执、按文件名订阅更干净。fail-soft 不影响 gate 判定
|
|
720
|
+
writeProbeConsistencyRunResult({ runtimeRoot: reconcileRuntimeRoot, changeName, envelope: probeEnvelope, result: probeCheck })
|
|
721
|
+
if (probeBlocked) {
|
|
722
|
+
return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts)
|
|
723
|
+
}
|
|
669
724
|
// ── module-impact 死信探针(blocking,债单 D-1/D-5):更新结果表 pending/待办行 → 阻断 verify ──
|
|
670
725
|
// 与 archive 移动前校验(extractPendingDocSyncRows)同一口径,把死信号从 archive 提前到 verify:
|
|
671
726
|
// agent 在 verify 阶段就须完成文档同步并回填 done/skipped,而非拖到归档被拦(修复 perf-remediation
|
|
@@ -726,7 +781,7 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
726
781
|
if (['brainstorm', 'plan', 'execute'].includes(stageName)) {
|
|
727
782
|
try {
|
|
728
783
|
const { classifyReviewTier } = await import('../review-tier.js')
|
|
729
|
-
const {
|
|
784
|
+
const { validateStageReviewWithAutoRefresh, getLatestStageReviewRunId, printStageReviewResult, generateStageReviewRunId, stageReviewMarkerPath } = await import('../stage-review.js')
|
|
730
785
|
const effectiveSpecBase = platformOpts?.specRoot || specBase
|
|
731
786
|
const reviewChangeDir = resolveChangeDir(cwd, progress, platformOpts?.specRoot)
|
|
732
787
|
const designPath = reviewChangeDir ? join(reviewChangeDir, 'design.md') : null
|
|
@@ -758,7 +813,16 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
|
|
|
758
813
|
: stageName === 'plan' ? 'plan'
|
|
759
814
|
: 'acceptance'
|
|
760
815
|
const searchDirs = [effectiveSpecBase, reviewChangeDir, cwd].filter(Boolean)
|
|
761
|
-
|
|
816
|
+
// docHash 失配自动刷新(坑 stage-review-refresh-hash-manual-forget,2026-09-04 ③):
|
|
817
|
+
// 主文档(design/plan.md)改版后忘跑 --refresh-hash 不再拦一轮——gate 机械重算放行
|
|
818
|
+
// (verdict 保留 + reviewerNotes 审计行 + 控制台留痕);主文档缺失/schema/verdict=fail
|
|
819
|
+
// 不在自动刷新范围,照常 fail-closed。
|
|
820
|
+
const gated = validateStageReviewWithAutoRefresh({ stage: stageName, reviewType, runtimeRoot, reviewRunId, searchDirs, autoRefresh: true })
|
|
821
|
+
if (gated.autoRefreshed) {
|
|
822
|
+
console.warn(`\n🔄 stage review docHash 失配已自动刷新(主文档改版后机械重算,verdict/checklist 保留——结论是否仍适用于新文档需人工确认)`)
|
|
823
|
+
console.warn(` 已就地更新: ${gated.refreshedPath}`)
|
|
824
|
+
}
|
|
825
|
+
const reviewResult = gated.result
|
|
762
826
|
printStageReviewResult(reviewResult, { stage: stageName, reviewRunId, runtimeRoot, changeName })
|
|
763
827
|
if (!reviewResult.ok) {
|
|
764
828
|
return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts)
|
|
@@ -1169,6 +1233,293 @@ export function filterStaleBaselineOverlap(overlap, dirtySet, foreignDeclared) {
|
|
|
1169
1233
|
return (overlap || []).filter(f => dirtySet?.has(f) && !(foreignDeclared?.has(f)))
|
|
1170
1234
|
}
|
|
1171
1235
|
|
|
1236
|
+
/**
|
|
1237
|
+
* target_files 对账结果 → 诊断信封(task-05 接线配套)。
|
|
1238
|
+
*
|
|
1239
|
+
* verify 块既有五项检查是「单结果对象 + print 函数 + 状态判定」形态(runVerifyTestCheck 先例),
|
|
1240
|
+
* 无 scan-postcheck 那种 checks 数组聚合结构,故信封不进数组、字段命名对齐 scan-postcheck 的
|
|
1241
|
+
* checks 条目(name/severity/detail/evidence/supportedFixes)——机器可路由口径跨模块一致。
|
|
1242
|
+
* severity:②类阻断=error(产物级阻断而非流程崩溃,不取 critical);③类与降级=warning;ok=info。
|
|
1243
|
+
* evidence 携带三类清单计数 + actual 形态与源明细(③类误报排查先看 sources 归因)。
|
|
1244
|
+
* code:五状态→四 code 的机器路由键(reconcile_missing_declared / reconcile_undeclared_file /
|
|
1245
|
+
* reconcile_skipped / reconcile_ok,design 接口定义),写盘与平台消费按 code 分支。
|
|
1246
|
+
*
|
|
1247
|
+
* @param {object} r reconcileTargetFiles 返回值
|
|
1248
|
+
* @returns {{ code: string, name: string, severity: string, detail: string, evidence: object, supportedFixes: string[] }}
|
|
1249
|
+
*/
|
|
1250
|
+
export function buildReconcileTargetFilesEnvelope(r) {
|
|
1251
|
+
const severity = r.status === 'missing_declared' ? 'error'
|
|
1252
|
+
: (r.status === 'ok' ? 'info' : 'warning')
|
|
1253
|
+
// 修复提示按状态分级给「可执行」动作:②类=干活或改声明;③类=补声明或回退;降级=指引补声明
|
|
1254
|
+
const supportedFixes = []
|
|
1255
|
+
if (r.status === 'missing_declared') {
|
|
1256
|
+
supportedFixes.push('完成声明的工作:把上方清单中 task 卡 target_files 声明的文件真正改出来(NEW: 声明的新建文件要先创建)')
|
|
1257
|
+
supportedFixes.push('或修正声明:编辑 tasks/task-NN.md 的 target_files,移除不再交付的路径(改声明须与该 task 的 review 结论一致,不得为过门而删声明)')
|
|
1258
|
+
} else if (r.status === 'undeclared') {
|
|
1259
|
+
supportedFixes.push('补声明:把上方清单文件补进对应 task 卡的 target_files(存在的文件直接写仓根相对路径)')
|
|
1260
|
+
supportedFixes.push('或回退越权改动:不属于任何 task 交付的文件应 revert(并行会话声明文件已被剔除,剩余即本变更产出)')
|
|
1261
|
+
} else if (r.status === 'skipped' || r.status === 'degraded') {
|
|
1262
|
+
supportedFixes.push('新 task 卡请声明 target_files(每条为仓根相对精确文件路径,不存在的文件加 NEW: 前缀);存量卡无声明/跨仓卡跳过属预期,无需补')
|
|
1263
|
+
}
|
|
1264
|
+
return {
|
|
1265
|
+
// code 机器路由键(审查 gap 回流,design 接口定义):五状态→四 code(skipped/degraded 同
|
|
1266
|
+
// 一个 reconcile_skipped——两者对消费方语义一致:本次无对账结论),供平台/上游按 code 分支
|
|
1267
|
+
code: r.status === 'missing_declared' ? 'reconcile_missing_declared'
|
|
1268
|
+
: r.status === 'undeclared' ? 'reconcile_undeclared_file'
|
|
1269
|
+
: (r.status === 'ok' ? 'reconcile_ok' : 'reconcile_skipped'),
|
|
1270
|
+
name: 'target_files_reconcile',
|
|
1271
|
+
severity,
|
|
1272
|
+
detail: `target_files 声明 ↔ 实际改动对账:status=${r.status}` + (r.skipReason ? `(${r.skipReason})` : ''),
|
|
1273
|
+
evidence: {
|
|
1274
|
+
matched_count: r.matched.length,
|
|
1275
|
+
missing_declared_count: r.missing.length,
|
|
1276
|
+
undeclared_count: r.undeclared.length,
|
|
1277
|
+
form: r.form,
|
|
1278
|
+
sources: r.sources,
|
|
1279
|
+
},
|
|
1280
|
+
supportedFixes,
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
/**
|
|
1285
|
+
* target_files 对账结果落盘 .runtime/verify-runs/<ts>/reconcile-result.json(审查 gap 回流)。
|
|
1286
|
+
*
|
|
1287
|
+
* design「总体方案 Wave 2」承诺「结果落盘对齐 .runtime/verify-runs/<ts>/ 先例」——console 输出
|
|
1288
|
+
* 跨进程即失忆,落盘留审计痕迹供 SillyHub/平台消费。目录命名对齐 verify-postcheck.js
|
|
1289
|
+
* writeRunResult 先例(ts=UTC 紧凑 YYYYMMDDHHmmss,与 test-result.json 同目录组织)。
|
|
1290
|
+
* fail-soft:写失败只 console.error 留痕不阻断 gate(落盘是审计附件不是证据源,与
|
|
1291
|
+
* writeRunResult 的 best-effort 同口径);五状态全落(放行态也要留档——对账跳过/降级同样
|
|
1292
|
+
* 是审计事实,下次回看才知道为什么没有差集)。
|
|
1293
|
+
*
|
|
1294
|
+
* @param {{ runtimeRoot: string, changeName: string|null, envelope: object, result: object }} opts
|
|
1295
|
+
* @returns {string|null} 成功返回落盘路径;失败返回 null
|
|
1296
|
+
*/
|
|
1297
|
+
export function writeReconcileRunResult({ runtimeRoot, changeName, envelope, result }) {
|
|
1298
|
+
try {
|
|
1299
|
+
const ts = new Date().toISOString().slice(0, 19).replace(/[-T:]/g, '')
|
|
1300
|
+
const runDir = join(runtimeRoot, 'verify-runs', ts)
|
|
1301
|
+
mkdirSync(runDir, { recursive: true })
|
|
1302
|
+
const resultPath = join(runDir, 'reconcile-result.json')
|
|
1303
|
+
// 落盘字段 snake_case 对齐 writeRunResult 的 test-result.json 机器消费口径(信封内存字段
|
|
1304
|
+
// 保持 camelCase 不动,写盘这层做一次映射)
|
|
1305
|
+
writeFileSync(resultPath, JSON.stringify({
|
|
1306
|
+
change: changeName,
|
|
1307
|
+
code: envelope.code,
|
|
1308
|
+
severity: envelope.severity,
|
|
1309
|
+
status: result.status,
|
|
1310
|
+
evidence: envelope.evidence,
|
|
1311
|
+
supported_fixes: envelope.supportedFixes,
|
|
1312
|
+
matched: result.matched,
|
|
1313
|
+
missing: result.missing,
|
|
1314
|
+
undeclared: result.undeclared,
|
|
1315
|
+
notes: result.notes,
|
|
1316
|
+
form: result.form,
|
|
1317
|
+
sources: result.sources,
|
|
1318
|
+
ran_at: new Date().toISOString(),
|
|
1319
|
+
}, null, 2) + '\n')
|
|
1320
|
+
console.log(`📄 target_files 对账结果已写入: ${resultPath}`)
|
|
1321
|
+
return resultPath
|
|
1322
|
+
} catch (e) {
|
|
1323
|
+
console.error(`⚠️ target_files 对账结果落盘失败(不影响 verify gate): ${e.message}`)
|
|
1324
|
+
return null
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
/**
|
|
1329
|
+
* 打印 target_files 对账结果(task-05 接线配套的打印层)。
|
|
1330
|
+
*
|
|
1331
|
+
* reconcileTargetFiles 是纯函数无配套 print(verify-postcheck 导出消费、打印归接线方),本函数
|
|
1332
|
+
* 即其在 verify 块的打印层:②类阻断明细照 runVerifyTestCheck 的失败输出形态(状态行 + 逐条
|
|
1333
|
+
* task + path + 修复行),③类/skipped/degraded 走 console.warn(advisory 形态照
|
|
1334
|
+
* printVerifyRequiredEvidenceCheck 先例)。返回 true = ②类在场应阻断(调用方据此走
|
|
1335
|
+
* rollbackCompletionAndReturn,与 testCheck.status==='failed' 分支同形)。
|
|
1336
|
+
*
|
|
1337
|
+
* @param {object} r reconcileTargetFiles 返回值
|
|
1338
|
+
* @param {object} [envelope] 预构造信封(缺省现算;显式传入便于调用方先断言再打印)
|
|
1339
|
+
* @returns {boolean} true = 阻断(status='missing_declared')
|
|
1340
|
+
*/
|
|
1341
|
+
export function printReconcileTargetFilesCheck(r, envelope = buildReconcileTargetFilesEnvelope(r)) {
|
|
1342
|
+
// ②类(声明未做=计划落空):ERROR 阻断
|
|
1343
|
+
if (r.status === 'missing_declared') {
|
|
1344
|
+
console.error(`\n❌ target_files 对账阻断:${r.missing.length} 条「声明未做」(②类,计划落空)——声明的交付文件在实际改动中不存在。`)
|
|
1345
|
+
for (const m of r.missing) {
|
|
1346
|
+
console.error(` - ${m.task}: ${m.path}${m.isNew ? '(NEW: 声明新建,文件未创建)' : ''}`)
|
|
1347
|
+
}
|
|
1348
|
+
console.error(` evidence: matched=${envelope.evidence.matched_count} missing=${envelope.evidence.missing_declared_count} undeclared=${envelope.evidence.undeclared_count}`)
|
|
1349
|
+
for (const fix of envelope.supportedFixes) console.error(` 修复:${fix}`)
|
|
1350
|
+
console.error(` 对账口径 actual=${(r.sources || []).join(' + ') || 'n/a'};修复后重新完成 verify。`)
|
|
1351
|
+
return true
|
|
1352
|
+
}
|
|
1353
|
+
// ③类(做了没声明=scope creep):WARNING 放行,suspectTask 尽力归因随行列出
|
|
1354
|
+
if (r.status === 'undeclared') {
|
|
1355
|
+
console.warn(`\n⚠️ target_files 对账发现 ${r.undeclared.length} 个实际改动未在任何 task 卡声明(③类 scope creep,advisory 不阻断):`)
|
|
1356
|
+
for (const u of r.undeclared.slice(0, 20)) {
|
|
1357
|
+
console.warn(` - ${u.path}${u.suspectTask ? `(疑似 ${u.suspectTask} 的改动——该 task 的 review.json changedFiles 提及)` : ''}`)
|
|
1358
|
+
}
|
|
1359
|
+
if (r.undeclared.length > 20) console.warn(` …还有 ${r.undeclared.length - 20} 个`)
|
|
1360
|
+
console.warn(` evidence: matched=${envelope.evidence.matched_count} missing=${envelope.evidence.missing_declared_count} undeclared=${envelope.evidence.undeclared_count}`)
|
|
1361
|
+
for (const fix of envelope.supportedFixes) console.warn(` 提示:${fix}`)
|
|
1362
|
+
console.warn(` 对账口径 actual=${(r.sources || []).join(' + ') || 'n/a'}(③类误报先核对 sources 与并行会话剔除提示)。`)
|
|
1363
|
+
return false
|
|
1364
|
+
}
|
|
1365
|
+
// IR 严格档(2026-09-07-ir-hardening D-003):strictViolation → ERROR 阻断(与②类同处置)
|
|
1366
|
+
if (r.status === 'skipped' && r.strictViolation) {
|
|
1367
|
+
console.error(`
|
|
1368
|
+
❌ target_files 对账阻断:${r.skipReason}`)
|
|
1369
|
+
console.error(` ${r.strictViolation.message}`)
|
|
1370
|
+
console.error(` 信封 code: ${r.strictViolation.code}`)
|
|
1371
|
+
return true
|
|
1372
|
+
}
|
|
1373
|
+
// 降级(skipped/degraded):WARNING 放行,skipReason 一句话(存量卡/git 不可用,不误红)
|
|
1374
|
+
if (r.status === 'skipped' || r.status === 'degraded') {
|
|
1375
|
+
console.warn(`\n⚠️ target_files 对账${r.status === 'degraded' ? '降级' : '跳过'}(不阻断):${r.skipReason}`)
|
|
1376
|
+
return false
|
|
1377
|
+
}
|
|
1378
|
+
// ok:①交集全落地
|
|
1379
|
+
console.log(`\n✅ target_files 对账通过:${r.matched.length} 个声明文件全部落地${r.form ? `(actual 形态=${r.form})` : ''}`)
|
|
1380
|
+
for (const n of (r.notes || [])) console.log(` ℹ️ ${n}`)
|
|
1381
|
+
return false
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
/**
|
|
1385
|
+
* 探针一致性抽查结果 → 诊断信封(task-03 接线配套,形态对齐 buildReconcileTargetFilesEnvelope)。
|
|
1386
|
+
*
|
|
1387
|
+
* code 路由键(task-02 JSDoc 接线约定):mismatch+error → probe_consistency_mismatch(阻断,
|
|
1388
|
+
* 正文预填段疑似被篡改);mismatch+warning → probe_consistency_drift(probe3/probe5 环境敏感
|
|
1389
|
+
* 维度 + R-06 HEAD 前进降级,放行告警);skipped/degraded → probe_consistency_skipped(放行
|
|
1390
|
+
* 提示——两态对消费方语义一致:本次无对账结论,照 P3a 五状态→四 code 的合流先例);ok →
|
|
1391
|
+
* probe_consistency_ok。severity 分级照 reconcile 先例:阻断=error、非阻断非 ok=warning、ok=info。
|
|
1392
|
+
* evidence 携带 mismatch 计数(总/error/warning)+ subsections 在场性(task-02 返回值的诊断字段,
|
|
1393
|
+
* 判别子 D-003 排查先看它——「哪个探针子节在场」决定走逐探针对账还是 prefill 整段缺失分支)。
|
|
1394
|
+
*
|
|
1395
|
+
* @param {object} r checkProbeConsistency 返回值
|
|
1396
|
+
* @returns {{ code: string, name: string, severity: string, detail: string, evidence: object, supportedFixes: string[] }}
|
|
1397
|
+
*/
|
|
1398
|
+
export function buildProbeConsistencyEnvelope(r) {
|
|
1399
|
+
const blocked = r.status === 'mismatch' && r.severity === 'error'
|
|
1400
|
+
const drifted = r.status === 'mismatch' && !blocked
|
|
1401
|
+
// IR 严格档独立 code(2026-09-07-ir-hardening D-002):首条 mismatch 携带
|
|
1402
|
+
// probe_prefill_missing_strict 时路由专用 code(平台/上游按 code 分支可区分
|
|
1403
|
+
// 「篡改预填段」与「从未生成预填段」两类 ERROR)
|
|
1404
|
+
const strictMissing = blocked
|
|
1405
|
+
&& Array.isArray(r.mismatches) && r.mismatches.length > 0
|
|
1406
|
+
&& r.mismatches[0] && r.mismatches[0].code === 'probe_prefill_missing_strict'
|
|
1407
|
+
const supportedFixes = []
|
|
1408
|
+
if (blocked) {
|
|
1409
|
+
supportedFixes.push('如实重生成:跑 `sillyspec verify-probes --change <变更名> --init` 重新落盘机械预填段,再基于新预填段如实补写正文结论(删改预填段过门是 Step7 防篡改纪律红线)')
|
|
1410
|
+
supportedFixes.push('若重生成后仍不符:核对 mismatch 的 expected(CLI 重跑指标)与 actual(正文锚点值)——确属环境变化(worktree 存活态/测试布局)时在 verify-result.md 注明差异原因')
|
|
1411
|
+
} else if (drifted) {
|
|
1412
|
+
supportedFixes.push('环境敏感维度漂移(probe3 测试布局 / probe5 扫描根 / probe6 HEAD 前进):跑 `sillyspec verify-probes --change <变更名> --init` 刷新预填段对齐当前环境(不阻断,留审计)')
|
|
1413
|
+
} else if (r.status === 'skipped' || r.status === 'degraded') {
|
|
1414
|
+
supportedFixes.push('新变更走 `sillyspec verify-probes --change <变更名> --init` 生成带探针预填段的骨架后再写结论;存量旧格式报告/无 changeName 跳过属预期,无需补')
|
|
1415
|
+
}
|
|
1416
|
+
return {
|
|
1417
|
+
// code 机器路由键(task-02 JSDoc):四状态→四 code,供平台/上游按 code 分支
|
|
1418
|
+
code: strictMissing ? 'probe_prefill_missing_strict'
|
|
1419
|
+
: blocked ? 'probe_consistency_mismatch'
|
|
1420
|
+
: drifted ? 'probe_consistency_drift'
|
|
1421
|
+
: (r.status === 'ok' ? 'probe_consistency_ok' : 'probe_consistency_skipped'),
|
|
1422
|
+
name: 'probe_consistency',
|
|
1423
|
+
severity: blocked ? 'error' : (r.status === 'ok' ? 'info' : 'warning'),
|
|
1424
|
+
detail: `verify-result.md 探针预填段一致性抽查:status=${r.status}`
|
|
1425
|
+
+ (r.status === 'mismatch' ? `(${(r.mismatches || []).length} 项不符,severity=${r.severity})` : '')
|
|
1426
|
+
+ (r.skipReason ? `(${r.skipReason})` : ''),
|
|
1427
|
+
evidence: {
|
|
1428
|
+
mismatch_count: (r.mismatches || []).length,
|
|
1429
|
+
error_count: (r.mismatches || []).filter(m => m && m.severity === 'error').length,
|
|
1430
|
+
warning_count: (r.mismatches || []).filter(m => m && m.severity === 'warning').length,
|
|
1431
|
+
subsections: r.subsections ?? null,
|
|
1432
|
+
},
|
|
1433
|
+
supportedFixes,
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
/**
|
|
1438
|
+
* 探针一致性抽查结果落盘 .runtime/verify-runs/<ts>/probe-consistency-result.json(task-03)。
|
|
1439
|
+
*
|
|
1440
|
+
* 形态选型(二选一取独立文件):与 reconcile-result.json 同 gate 运行、同目录组织
|
|
1441
|
+
* (verify-runs/<ts>/,ts=UTC 紧凑 YYYYMMDDHHmmss 对齐 writeRunResult 先例),但不并入后者——
|
|
1442
|
+
* reconcile-result.json 的 schema 已被 P3a 消费方锁定,并入(读改写/扩参)引入两检查间耦合;
|
|
1443
|
+
* 独立文件各写各的回执,平台/审计按文件名订阅(与 test-result.json 同一目录约定)。
|
|
1444
|
+
* fail-soft:写失败只 console.error 留痕不阻断 gate(对齐 writeReconcileRunResult);四状态全落
|
|
1445
|
+
* (放行态同样留档——跳过/降级是审计事实,下次回看才知道为什么没有对账结论)。
|
|
1446
|
+
*
|
|
1447
|
+
* @param {{ runtimeRoot: string, changeName: string|null, envelope: object, result: object }} opts
|
|
1448
|
+
* @returns {string|null} 成功返回落盘路径;失败返回 null
|
|
1449
|
+
*/
|
|
1450
|
+
export function writeProbeConsistencyRunResult({ runtimeRoot, changeName, envelope, result }) {
|
|
1451
|
+
try {
|
|
1452
|
+
const ts = new Date().toISOString().slice(0, 19).replace(/[-T:]/g, '')
|
|
1453
|
+
const runDir = join(runtimeRoot, 'verify-runs', ts)
|
|
1454
|
+
mkdirSync(runDir, { recursive: true })
|
|
1455
|
+
const resultPath = join(runDir, 'probe-consistency-result.json')
|
|
1456
|
+
// 落盘字段 snake_case 对齐 reconcile-result.json 机器消费口径(信封内存字段保持 camelCase
|
|
1457
|
+
// 不动,写盘这层做一次映射);mismatches 逐条落盘供平台按 probe/severity 细读
|
|
1458
|
+
writeFileSync(resultPath, JSON.stringify({
|
|
1459
|
+
change: changeName,
|
|
1460
|
+
code: envelope.code,
|
|
1461
|
+
severity: envelope.severity,
|
|
1462
|
+
status: result.status,
|
|
1463
|
+
result_severity: result.severity ?? null,
|
|
1464
|
+
evidence: envelope.evidence,
|
|
1465
|
+
supported_fixes: envelope.supportedFixes,
|
|
1466
|
+
mismatches: result.mismatches || [],
|
|
1467
|
+
skip_reason: result.skipReason ?? null,
|
|
1468
|
+
subsections: result.subsections ?? null,
|
|
1469
|
+
ran_at: new Date().toISOString(),
|
|
1470
|
+
}, null, 2) + '\n')
|
|
1471
|
+
console.log(`📄 探针一致性抽查结果已写入: ${resultPath}`)
|
|
1472
|
+
return resultPath
|
|
1473
|
+
} catch (e) {
|
|
1474
|
+
console.error(`⚠️ 探针一致性抽查结果落盘失败(不影响 verify gate): ${e.message}`)
|
|
1475
|
+
return null
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
/**
|
|
1480
|
+
* 打印探针一致性抽查结果(task-03 接线配套的打印层,阻断语义同 printReconcileTargetFilesCheck)。
|
|
1481
|
+
*
|
|
1482
|
+
* ERROR 级 mismatch:console.error 状态行 + 逐条 mismatch(probe/expected/actual/note,reconcile
|
|
1483
|
+
* 先例同形)+ evidence 计数 + supportedFixes,返回 true(调用方据此走 rollbackCompletionAndReturn);
|
|
1484
|
+
* WARNING 级 mismatch:console.warn 同形明细返回 false 放行;skipped/degraded:一句 skipReason 放行
|
|
1485
|
+
* (对齐 reconcile 降级分支形态);ok:静默返回 false(task-02 JSDoc 接线约定「'ok' → 静默」——
|
|
1486
|
+
* 正文锚点全符是常态,不刷存在感)。
|
|
1487
|
+
*
|
|
1488
|
+
* @param {object} r checkProbeConsistency 返回值
|
|
1489
|
+
* @param {object} [envelope] 预构造信封(缺省现算;显式传入便于调用方先断言再打印)
|
|
1490
|
+
* @returns {boolean} true = 阻断(status='mismatch' 且 severity='error')
|
|
1491
|
+
*/
|
|
1492
|
+
export function printProbeConsistencyCheck(r, envelope = buildProbeConsistencyEnvelope(r)) {
|
|
1493
|
+
// ERROR 级 mismatch:正文预填段与 CLI 重跑事实不符,疑似篡改 → 阻断
|
|
1494
|
+
if (r.status === 'mismatch' && r.severity === 'error') {
|
|
1495
|
+
console.error(`\n❌ 探针一致性抽查阻断:${(r.mismatches || []).length} 项与 CLI 重跑事实不符(其中 ERROR 级 ${envelope.evidence.error_count} 项)——verify-result.md 探针预填段疑似被篡改。`)
|
|
1496
|
+
for (const m of (r.mismatches || [])) {
|
|
1497
|
+
console.error(` - ${m.probe}: 重跑 ${JSON.stringify(m.expected)} vs 正文 ${JSON.stringify(m.actual)}(${m.severity})——${m.note}`)
|
|
1498
|
+
}
|
|
1499
|
+
console.error(` evidence: mismatches=${envelope.evidence.mismatch_count}(error=${envelope.evidence.error_count} warning=${envelope.evidence.warning_count})subsections=${JSON.stringify(envelope.evidence.subsections)}`)
|
|
1500
|
+
for (const fix of envelope.supportedFixes) console.error(` 修复:${fix}`)
|
|
1501
|
+
console.error(` 对比基准是正文锚点(删 verify-facts.json 绕不过防护);如实重生成预填段后重新完成 verify。`)
|
|
1502
|
+
return true
|
|
1503
|
+
}
|
|
1504
|
+
// WARNING 级 mismatch:环境敏感维度漂移 → 放行告警
|
|
1505
|
+
if (r.status === 'mismatch') {
|
|
1506
|
+
console.warn(`\n⚠️ 探针一致性抽查发现 ${(r.mismatches || []).length} 项漂移(环境敏感维度,advisory 不阻断):`)
|
|
1507
|
+
for (const m of (r.mismatches || [])) {
|
|
1508
|
+
console.warn(` - ${m.probe}: 重跑 ${JSON.stringify(m.expected)} vs 正文 ${JSON.stringify(m.actual)}——${m.note}`)
|
|
1509
|
+
}
|
|
1510
|
+
console.warn(` evidence: mismatches=${envelope.evidence.mismatch_count}(error=${envelope.evidence.error_count} warning=${envelope.evidence.warning_count})`)
|
|
1511
|
+
for (const fix of envelope.supportedFixes) console.warn(` 提示:${fix}`)
|
|
1512
|
+
return false
|
|
1513
|
+
}
|
|
1514
|
+
// 降级(skipped/degraded):放行,skipReason 一句话(存量旧报告/重跑异常,不误红)
|
|
1515
|
+
if (r.status === 'skipped' || r.status === 'degraded') {
|
|
1516
|
+
console.warn(`\n⚠️ 探针一致性抽查${r.status === 'degraded' ? '降级' : '跳过'}(不阻断):${r.skipReason}`)
|
|
1517
|
+
return false
|
|
1518
|
+
}
|
|
1519
|
+
// ok:①正文锚点与重跑全符——静默放行
|
|
1520
|
+
return false
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1172
1523
|
/**
|
|
1173
1524
|
* verify 服务进程回收(verify --done 收尾调用)。
|
|
1174
1525
|
*
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* progressConfirm — 主流程 Step1「进度确认」的 noAI 动作(brainstorm / execute / verify)
|
|
3
|
+
* (2026-09-07 noAI 主流程首批;机制先例:scan 系 noAI 步 + planPostcheck,见 run/stage.js noAI 分支)
|
|
4
|
+
*
|
|
5
|
+
* noAI 化依据(step1 职责在历次加固后已全部机械化):
|
|
6
|
+
* - 阶段路由:`run <stage>` 显式调用即写 currentStage(run/stage.js D-003@v1),
|
|
7
|
+
* 阶段顺序违规由 checkTransition 拦截——原 prompt 的「阶段不符则停止」检查已残留化
|
|
8
|
+
* ({PROGRESS_SNAPSHOT} 注入文案本就写着「CLI 已路由到本阶段」);
|
|
9
|
+
* - 进度摘要:快照三行(变更/阶段/步骤位置)由 CLI 注入,agent 的「输出当前进度摘要」
|
|
10
|
+
* 只是把注入内容复述一遍再 --done——整轮往返零判断增量;
|
|
11
|
+
* - brainstorm 变更名检查:自动名在创建时 CLI 已打印 rename 提示(run/command.js),
|
|
12
|
+
* 此处机械复检一次模式命中;命中只打印 ⚠️ 建议(noAI 步的 console 输出仍在 CLI 工具
|
|
13
|
+
* 结果里,agent 可见、可在后续步骤与用户交互改名)——不 throw、不阻塞推进。
|
|
14
|
+
* - execute 执行范围:$ARGUMENTS 是会话层信息,CLI 不掺入;本步只补 tasks 勾选计数
|
|
15
|
+
* (readPlanCheckboxStatus 既有机器事实),范围以用户在会话中给出的为准。
|
|
16
|
+
* - verify 测试提示:原 prompt 首段的「CLI 在 --done 统一跑测试」要点改为 console 提示。
|
|
17
|
+
*
|
|
18
|
+
* 输出纪律:只 console 打印(noAI 步不写 step output,与 scan 系动作一致);
|
|
19
|
+
* 纯确认步无失败语义,不 throw。
|
|
20
|
+
*/
|
|
21
|
+
import { join } from 'node:path'
|
|
22
|
+
|
|
23
|
+
/** brainstorm 自动生成变更名的模式(run/command.js:1083 `${date}-new-change-${hex}`) */
|
|
24
|
+
const AUTO_CHANGE_NAME_RE = /^\d{4}-\d{2}-\d{2}-new-change-[a-f0-9]+$/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 执行「进度确认」noAI 动作:打印进度快照 + 阶段专属机械提示。
|
|
28
|
+
*
|
|
29
|
+
* @param {object} opts
|
|
30
|
+
* @param {string} opts.stageName - brainstorm / execute / verify
|
|
31
|
+
* @param {string} opts.cwd
|
|
32
|
+
* @param {object} opts.stageData - progress.stages[stageName](steps 数组)
|
|
33
|
+
* @param {string|null} opts.changeName
|
|
34
|
+
* @param {object|null} [opts.pm] - ProgressManager(execute 档 tasks 勾选计数用;缺省跳过该段)
|
|
35
|
+
*/
|
|
36
|
+
export function executeProgressConfirm({ stageName, cwd, stageData, changeName, pm = null }) {
|
|
37
|
+
const steps = Array.isArray(stageData?.steps) ? stageData.steps : []
|
|
38
|
+
const done = steps.filter(s => s.status === 'completed').length
|
|
39
|
+
console.log('📊 进度快照(CLI 注入,无需跑 progress show):')
|
|
40
|
+
console.log(` - 变更:${changeName || '(未知)'}`)
|
|
41
|
+
console.log(` - 阶段:${stageName}(CLI 已路由到本阶段)`)
|
|
42
|
+
console.log(` - 步骤:${done}/${steps.length} 已完成;本步已由 CLI 自动确认`)
|
|
43
|
+
|
|
44
|
+
if (stageName === 'brainstorm' && changeName && AUTO_CHANGE_NAME_RE.test(changeName)) {
|
|
45
|
+
console.log('')
|
|
46
|
+
console.log(`⚠️ 变更名「${changeName}」疑似自动生成——建议与用户确认语义化名称后执行:`)
|
|
47
|
+
console.log(` sillyspec change-rename ${changeName} <新名称>(勿用 mv,会漏改进度库引用)`)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (stageName === 'execute' && pm && changeName) {
|
|
51
|
+
try {
|
|
52
|
+
const changeDir = join(pm._getSpecDir(cwd), 'changes', changeName)
|
|
53
|
+
const plan = pm.readPlanCheckboxStatus(changeDir)
|
|
54
|
+
if (plan && plan.total > 0) {
|
|
55
|
+
console.log(` - 任务勾选:tasks ${plan.checked}/${plan.total}(执行范围以用户会话指定为准,未指定则全部 Wave)`)
|
|
56
|
+
}
|
|
57
|
+
} catch { /* 勾选计数 fail-soft:plan.md 缺失/解析失败不阻塞确认步 */ }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (stageName === 'verify') {
|
|
61
|
+
console.log('')
|
|
62
|
+
console.log('💡 `run verify` 只下发指令不跑测试——全量测试由 CLI 在最后 --done 统一执行')
|
|
63
|
+
console.log(' (local.yaml 的 commands.test,同步对账可能耗时较长);后续「运行测试和质量扫描」')
|
|
64
|
+
console.log(' 步骤无需手动跑全量,只做 lint/静态检查 + 可选快速冒烟。')
|
|
65
|
+
}
|
|
66
|
+
}
|