sillyspec 3.28.12 → 3.29.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.
Files changed (47) hide show
  1. package/.claude/skills/sillyspec-execute/SKILL.md +222 -223
  2. package/.claude/skills/sillyspec-plan/SKILL.md +156 -156
  3. package/.claude/skills/sillyspec-quick/SKILL.md +8 -8
  4. package/.claude/skills/sillyspec-verify/SKILL.md +87 -91
  5. package/package.json +1 -1
  6. package/src/blast-surface.js +168 -0
  7. package/src/ceremony-tier.js +359 -0
  8. package/src/change-risk-profile.js +75 -202
  9. package/src/config-schema.js +21 -0
  10. package/src/decision-distill.js +32 -25
  11. package/src/decisions-io.js +32 -0
  12. package/src/design-facts.js +1 -0
  13. package/src/doctor-diagnostics.js +148 -1
  14. package/src/fr-index.js +359 -0
  15. package/src/index.js +92 -5
  16. package/src/knowledge-stats.js +124 -2
  17. package/src/modules.js +28 -0
  18. package/src/prefill.js +491 -0
  19. package/src/probe7-anchor-check.js +8 -6
  20. package/src/quick-gate-profile.js +8 -4
  21. package/src/quick-session-owner.js +63 -0
  22. package/src/review-dispatch.js +473 -1
  23. package/src/review-material-pack.js +265 -0
  24. package/src/review-tier.js +185 -41
  25. package/src/run/archive-distill.js +35 -0
  26. package/src/run/command.js +158 -87
  27. package/src/run/complete-handlers.js +82 -1
  28. package/src/run/complete.js +37 -1
  29. package/src/run/gates.js +301 -19
  30. package/src/run/prompt.js +454 -21
  31. package/src/run/shared.js +36 -0
  32. package/src/run/verify-quality-scan.js +14 -6
  33. package/src/scope-audit.js +8 -1
  34. package/src/span-risk-surface.js +127 -0
  35. package/src/stage-contract-spec.js +2 -2
  36. package/src/stage-contract.js +172 -98
  37. package/src/stage-review.js +26 -2
  38. package/src/stages/brainstorm.js +34 -16
  39. package/src/stages/execute.js +10 -2
  40. package/src/stages/plan.js +29 -12
  41. package/src/stages/verify.js +7 -7
  42. package/src/taskcard.js +3 -0
  43. package/src/verify-postcheck.js +375 -20
  44. package/src/verify-probes.js +964 -30
  45. package/src/worktree.js +1 -1
  46. package/templates/prompts/taskcard-rules.md +1 -0
  47. package/templates/prompts/verify-probes.md +1 -1
package/src/run/gates.js CHANGED
@@ -3,7 +3,13 @@
3
3
  *
4
4
  * 阶段完成校验 gate 级联 + execute deps 硬门 + 完成回滚(自洽叶子模块,仅被 completeStep 调用):
5
5
  * - runStageCompletionGates:runValidators → verify-test 对账 → Plan→Execute contract →
6
- * Stage Review Gate → Execute Task Review Gate,任一失败走 rollbackCompletionAndReturn
6
+ * Stage Review Gate → Execute Task Review Gate,任一失败走 rollbackCompletionAndReturn;
7
+ * 级联末位追仪式档位摩擦升档检查点(2026-09-18-ceremony-risk-pricing task-03:读
8
+ * friction-ledger 累计账 → escalateByFriction → .runtime/ceremony-tier-<change>.json
9
+ * 迁移留痕,只升不降、fail-soft 不阻断完成);verify 检查族末位接 ceremony 双跑收口第一
10
+ * 出口(runCeremonyDualRunCheck:声明档 vs 实际 diff 事实档,mismatch=errors 硬 flag 阻断,
11
+ * task-04 接线 / execute 评审 FAIL 修复);升档检查块后 fire-and-forget 影子审查派发
12
+ * (dispatchShadowReview,task-06 接线 / execute 评审 GAP 修复,只记账不阻断)
7
13
  * - enforceDepsGate:execute depsStatus 不达标且非 wave 级 opt-out 时阻断 --done(exit 1)
8
14
  * - rollbackCompletionAndReturn / rollbackStageCompletion:统一回滚 stage/step 状态 + 落盘 + sync
9
15
  * - isCurrentWaveAllNoDepsVerify:enforceDepsGate 的私有 helper(wave 级 no_deps_verify opt-out 判定)
@@ -31,6 +37,15 @@ import { detectConcurrentChanges, formatConcurrentWarning, detectCommittedDrift,
31
37
  import { stageRegistry } from '../stages/index.js'
32
38
  import { normalizeTaskId } from '../taskcard.js'
33
39
  import { recordFrictionEvent } from '../friction-tally.js'
40
+ import { readFrictionLedger } from '../friction-ledger.js'
41
+ import { computeCeremonyTier, escalateByFriction, applyDeclarationCatchUp, CEREMONY_TIERS } from '../ceremony-tier.js'
42
+ import { loadBlastDeclarations } from '../blast-surface.js'
43
+ import { loadSpanRiskPatterns } from '../span-risk-surface.js'
44
+ import { resolveChangeRisk } from '../change-risk-profile.js'
45
+ import { withFileLock } from '../quicklog.js'
46
+ import { extractExplicitRiskLevel } from '../change-risk-profile.js'
47
+ import { parseFileChangeListDetailed } from '../change-list.js'
48
+ import { parseModuleMapSimple } from '../modules.js'
34
49
 
35
50
  /**
36
51
  * 从任务注册表(tasks.md)提取全部 task id(task-XX)——符号影响面覆盖度校验用。
@@ -565,6 +580,162 @@ function rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx,
565
580
  triggerSync(cwd, changeName, platformOpts)
566
581
  return { stageCompleted: false, currentIdx, nextPendingIdx: currentIdx }
567
582
  }
583
+
584
+ // ════════ 仪式档位摩擦升档(2026-09-18-ceremony-risk-pricing task-03 / D-008)════════
585
+ // 依据:design.md「总体方案 Phase 1 friction 段」+「生命周期契约表」。四阶段完成门(brainstorm/
586
+ // plan/execute/verify)各钉一个升档检查点:读 friction-ledger 按 change 过滤的累计账(禁读
587
+ // friction-tally——其 consumeFrictionHint 消费即删 + verify 收尾清零,读它会给 verify 门引入
588
+ // 隐式次序依赖,plan 全局硬约束 1),escalateByFriction 超阈 min(S3, tier+1) 只升不降,迁移记录
589
+ // 追加写 .runtime/ceremony-tier-<change>.json。档位文件是纯派生数据(与 friction-ledger 同目录
590
+ // 同生命周期,archive 时随 runtime 清理,不进 git);读改写整段持 withFileLock(先例 complete.js
591
+ // .tasks.md.lock / friction-tally.js tally+.lock)+ writeAtomicSync 原子写(先例 fs-atomic.js /
592
+ // local-register.js .local.yaml.lock),多会话并发下「只升不降」不变量成立。
593
+
594
+ /** 档位状态文件路径(runtimeRoot 下 per-change;命名对齐 apply-pathspec-<change>.txt 先例) */
595
+ function ceremonyTierFilePath(runtimeRoot, changeName) {
596
+ return join(runtimeRoot, `ceremony-tier-${changeName}.json`)
597
+ }
598
+
599
+ /**
600
+ * 读档位文件:缺失 / 坏 JSON / 非对象 / tier 不在 S0~S3 序 → null(档位是派生数据,坏档按无档
601
+ * 重定价,不修复不报错——容忍立场同 readFrictionLedger)。
602
+ */
603
+ function readCeremonyTierDoc(path) {
604
+ try {
605
+ const doc = JSON.parse(readFileSync(path, 'utf8'))
606
+ if (!doc || typeof doc !== 'object' || Array.isArray(doc)) return null
607
+ if (!CEREMONY_TIERS.includes(doc.tier)) return null
608
+ return doc
609
+ } catch {
610
+ return null
611
+ }
612
+ }
613
+
614
+ /** 落档位文件:writeAtomicSync 原子写(tmp+rename,读者见旧版或新版不见半截),JSON 两空格
615
+ * 缩进(friction-ledger 落盘同口径);调用方须已持 tier 文件锁。 */
616
+ function writeCeremonyTierDoc(path, doc) {
617
+ mkdirSync(dirname(path), { recursive: true })
618
+ writeAtomicSync(path, JSON.stringify(doc, null, 2))
619
+ }
620
+
621
+ /**
622
+ * 开跑定价 / 声明追赶重定价共用重算体(design 生命周期契约表「无 → 初始档」+ D-003 追赶):
623
+ * blast = resolveChangeRisk 声明面判级(declaredFiles × _module-map.yaml 顶层 blast 段
624
+ * + local 只升合并,经 loadBlastDeclarations 装载;explicit frontmatter 只压 tier
625
+ * 不豁免 evidence,D-008/D-009——词表散文判级随 2026-09-19-ceremony-pricing-five-cuts
626
+ * 退役)→ computeCeremonyTier blastTier 直入。design.md 缺失 → declaredFiles 空 →
627
+ * blast S1(声明面缺失按无命中,不缺省不拦截)。
628
+ * span = design 文件变更清单(readDesignOwnFiles 委托 change-list 单一真相源,双形态标题)
629
+ * + _module-map.yaml 跨模块口径(moduleIndex 缺失/空 → 该维跳过不缺省不拦截)。
630
+ * frictionCounts 不进重算——摩擦轴归 escalateByFriction 检查点(超阈升一档封顶语义),
631
+ * 两处重复计价会让 transitions 审计口径失真。
632
+ * eventContext='initial' → reasons 追加「初始档」事件行(档位首见);'catchup' → 不追加
633
+ * (追赶事件行由 applyDeclarationCatchUp 落,防语义错位与 reasons 随每道门膨胀)。
634
+ */
635
+ function computeInitialCeremonyTierDoc({ cwd, specBase, platformOpts, progress, changeName, projectName, stageName, eventContext = 'initial' }) {
636
+ const changeDir = resolveChangeDir(cwd, progress, platformOpts?.specRoot)
637
+ const designContent = changeDir && existsSync(join(changeDir, 'design.md')) ? readFileSync(join(changeDir, 'design.md'), 'utf8') : ''
638
+ const explicitRiskLevel = designContent ? extractExplicitRiskLevel(designContent) : null
639
+ const declaredFiles = readDesignOwnFiles(specBase, changeName)
640
+ const { declarations } = loadBlastDeclarations({ specBase, project: projectName })
641
+ const risk = resolveChangeRisk({ files: declaredFiles, blastDeclarations: declarations, explicitRiskLevel })
642
+ // span 轴声明面装载(2026-09-19-span-risk-pattern-migration task-03):与 :639 blast 装载同
643
+ // specBase/projectName 上下文——_module-map.yaml 顶层 span_risk 段;无声明项目 → 空表(span
644
+ // 模式维关闭,禁回退内置表,D-003;引擎默认 [] 同效,显式装载与其他声明面入参同风格)
645
+ const spanRiskPatterns = loadSpanRiskPatterns({ specBase, project: projectName })
646
+ let moduleIndex = null
647
+ const mapPath = join(specBase, 'docs', projectName, 'modules', '_module-map.yaml')
648
+ if (existsSync(mapPath)) {
649
+ try { moduleIndex = parseModuleMapSimple(readFileSync(mapPath, 'utf8')) || null } catch { moduleIndex = null }
650
+ }
651
+ const priced = computeCeremonyTier({ blastTier: risk.tier, declaredFiles, moduleIndex, spanRiskPatterns })
652
+ const auditLines = []
653
+ if (risk.hitPrefixes.length > 0) {
654
+ auditLines.push(`声明危险面命中:${risk.hitPrefixes.join('、')}(evidenceRequired=${risk.evidenceRequired})`)
655
+ }
656
+ if (risk.explicit) {
657
+ auditLines.push(`explicit risk_level 应用(tier 覆盖 → ${risk.tier};evidenceRequired 不受豁免,D-009)`)
658
+ }
659
+ const reasons = [...priced.reasons, ...auditLines]
660
+ if (eventContext === 'initial') {
661
+ reasons.push(`初始档(开跑定价事件:${stageName} 完成门首见该 change 无档位文件,按当时可得的 blast/span 信号定价——design 生命周期契约表「无 → 初始档」行)`)
662
+ }
663
+ return { tier: priced.tier, components: priced.components, reasons, transitions: [] }
664
+ }
665
+
666
+ /**
667
+ * 阶段门摩擦升档检查点(runStageCompletionGates 四完成门共用;升档不阻断本次 --done,
668
+ * 下一阶段才生效)。流程:读 ledger 累计账 → 无档先落初始档 → escalateByFriction →
669
+ * escalated 时追加 transitions 迁移记录写回。全程持锁(tierPath+'.lock',读改写一整段),
670
+ * 「只升不降」由三重保证:escalateByFriction 数学上不降 + 锁内串行化 + 写前对磁盘更高档
671
+ * 拒绝降档写回(reasons 留痕)。fail-soft 由调用方兜底(本函数抛出即检查失败,不碰完成状态)。
672
+ * @returns {Promise<{escalated: boolean, from: string, to: string, frictionCounts: object,
673
+ * downgradeRejected?: boolean, tierFilePath: string}>}
674
+ */
675
+ async function escalateCeremonyTierAtGate({ cwd, specBase, platformOpts, progress, changeName, projectName, stageName }) {
676
+ const effectiveSpecBase = platformOpts?.specRoot || specBase
677
+ const runtimeRoot = resolveRuntimeRoot(platformOpts, specBase)
678
+ // ledger 按 change 过滤的累计账(merge-by-change 后单 change 单条目);三键超集透传给
679
+ // escalateByFriction——引擎只消费 gate_rollback/review_rejected 两键,verify_run_failed 容忍
680
+ const entry = readFrictionLedger(runtimeRoot).find((e) => e.change === changeName) || null
681
+ const rawCounts = entry && entry.counts && typeof entry.counts === 'object' && !Array.isArray(entry.counts) ? entry.counts : {}
682
+ const frictionCounts = {
683
+ gate_rollback: Number(rawCounts.gate_rollback) || 0,
684
+ review_rejected: Number(rawCounts.review_rejected) || 0,
685
+ verify_run_failed: Number(rawCounts.verify_run_failed) || 0,
686
+ }
687
+ const tierPath = ceremonyTierFilePath(runtimeRoot, changeName)
688
+ const result = await withFileLock(tierPath + '.lock', async () => {
689
+ // 读档无文件(或坏档)→ 先落初始档(开跑定价事件);档位在场 → 声明追赶重定价
690
+ // (D-003:按当前声明面重算 → applyDeclarationCatchUp 三分支——无摩擦可升可降/摩擦
691
+ // 地板不退 → 写回;追赶不记 transitions,摩擦价随后由 escalate 即时兑现,同锁串行)
692
+ let doc = readCeremonyTierDoc(tierPath)
693
+ if (!doc) {
694
+ doc = computeInitialCeremonyTierDoc({ cwd, specBase: effectiveSpecBase, platformOpts, progress, changeName, projectName, stageName })
695
+ writeCeremonyTierDoc(tierPath, doc)
696
+ } else {
697
+ const recomputed = computeInitialCeremonyTierDoc({ cwd, specBase: effectiveSpecBase, platformOpts, progress, changeName, projectName, stageName, eventContext: 'catchup' })
698
+ const caught = applyDeclarationCatchUp({ currentDoc: doc, recomputedTier: recomputed.tier, recomputedComponents: recomputed.components, recomputedReasons: recomputed.reasons, frictionCounts })
699
+ // 降档保护由 applyDeclarationCatchUp 三分支承担(无摩擦整档换可升可降是 D-003 的立意;
700
+ // 有摩擦地板托底)——此处不再叠加「拒绝降档写回」(与追赶语义矛盾,防误伤)。
701
+ doc = caught
702
+ writeCeremonyTierDoc(tierPath, doc)
703
+ }
704
+ const esc = escalateByFriction(doc.tier, frictionCounts)
705
+ if (!esc.escalated) {
706
+ return { escalated: false, from: doc.tier, to: doc.tier, frictionCounts, tierFilePath: tierPath }
707
+ }
708
+ // 只升不降兜底:锁内读改写已串行化正常并发,此处对「磁盘档高于本次算得档」的锁外写入
709
+ // (stale 偷锁极端窗口 / 手工改档)拒绝降档写回,reasons 留痕、不追加 transitions(迁移没发生)
710
+ const disk = readCeremonyTierDoc(tierPath)
711
+ const diskTier = disk ? disk.tier : doc.tier
712
+ if (CEREMONY_TIERS.indexOf(diskTier) > CEREMONY_TIERS.indexOf(esc.tier)) {
713
+ const kept = disk
714
+ kept.reasons = [
715
+ ...(Array.isArray(kept.reasons) ? kept.reasons : []),
716
+ `拒绝降档写入(磁盘已 ${diskTier},本次按摩擦算得 ${esc.tier}——只升不降不变量)`,
717
+ ]
718
+ writeCeremonyTierDoc(tierPath, kept)
719
+ return { escalated: false, from: diskTier, to: diskTier, frictionCounts, downgradeRejected: true, tierFilePath: tierPath }
720
+ }
721
+ const transition = {
722
+ at: new Date().toISOString(),
723
+ from: doc.tier,
724
+ to: esc.tier,
725
+ frictionCounts: { gate_rollback: frictionCounts.gate_rollback, review_rejected: frictionCounts.review_rejected },
726
+ }
727
+ doc.tier = esc.tier
728
+ doc.components = { ...(doc.components || {}), friction: esc.tier }
729
+ doc.reasons = [
730
+ ...(Array.isArray(doc.reasons) ? doc.reasons : []),
731
+ `friction 升档(${transition.from}→${esc.tier}:ledger 累计 gate_rollback=${frictionCounts.gate_rollback}、review_rejected=${frictionCounts.review_rejected} 超阈,min(S3, tier+1))`,
732
+ ]
733
+ doc.transitions = [...(Array.isArray(doc.transitions) ? doc.transitions : []), transition]
734
+ writeCeremonyTierDoc(tierPath, doc)
735
+ return { escalated: true, from: transition.from, to: esc.tier, frictionCounts, tierFilePath: tierPath }
736
+ })
737
+ return result
738
+ }
568
739
  /**
569
740
  * 阶段完成校验 gate 级联(从 completeStep 抽出,行为保持)。仅当所有步骤确实标记为 completed 时
570
741
  * 由 completeStep 调用。顺序:runValidators → verify-test 对账 → Plan→Execute contract →
@@ -672,6 +843,24 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
672
843
  } catch { /* 预检失败不阻断 plan 完成 */ }
673
844
  }
674
845
 
846
+ // ── 预填注未删 advisory(2026-09-18-artifact-prefill task-03 / FR-03 / D-003@v1 门禁梯度·
847
+ // 轻档):brainstorm/plan --done 时对白名单槽宿主文件(design.md + tasks/task-*.md)查
848
+ // 未删预填注——注在场 = 未确认(预填≠结论),提示核对后删注(删注=确认动作)。advisory
849
+ // 不阻断(忘删注=未确认提示,task 卡 constraints「旧路径零新硬门」);error 档挂 verify
850
+ // 收尾(归档前注清零)。检测单点 = verify-probes 探针面 runProbe10PrefillNoteClearance
851
+ // (探针 10 --init 预填与 verify 收尾 error 门同一实现,不二算)。已知误报面:散文引用
852
+ // 注字面量(如本变更 design.md 协议描述)会命中——warning 文案注明核对语义,不追求
853
+ // 字面区分。──
854
+ if (['brainstorm', 'plan'].includes(stageName) && changeName) {
855
+ try {
856
+ const { runProbe10PrefillNoteClearance } = await import('../verify-probes.js')
857
+ const noteAdvisory = runProbe10PrefillNoteClearance({ specBase, changeName })
858
+ if (noteAdvisory.unclearedFiles.length > 0) {
859
+ console.warn(`\n⚠️ 预填注未清(白名单槽未确认,${noteAdvisory.unclearedFiles.length} 处:${noteAdvisory.unclearedFiles.join('、')})——核对后删注;归档前注清零为 error 门(散文含注字面量会命中——核对是否真有未确认预填槽)`)
860
+ }
861
+ } catch { /* advisory 失败不阻断完成(fail-soft,facade 预检先例同款) */ }
862
+ }
863
+
675
864
  // verify 产物校验通过 + 结论非 FAIL(否则上面已阻断)。
676
865
  // 再由 CLI 亲自执行 local.yaml 的测试命令,与 verify-result.md 的自报告对账:
677
866
  // 自报告 PASS 但实测失败 → 阻断(防止"文案通过"绕过验证)。
@@ -935,6 +1124,28 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
935
1124
  if (probeBlocked) {
936
1125
  return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts, { type: 'gate_rollback', detail: 'verify-contract' })
937
1126
  }
1127
+ // ── ceremony 双跑收口·第一出口(2026-09-18-ceremony-risk-pricing task-04 / FR-03 / D-003;
1128
+ // execute 评审 FAIL 项补接线):声明档(.runtime/ceremony-tier-<change>.json 开跑定价)vs
1129
+ // 实际 diff 重跑 blast+span 的事实档,mismatch(severity=error,懒 agent 低报)汇入 verify
1130
+ // errors 硬 flag 面——阻断回滚(照 reconcile/probe 先例 rollbackCompletionAndReturn,修复后
1131
+ // 重跑 --done 进度不丢)。检查/记账/种子收敛在 runCeremonyDualRunCheck 单点(archive 出口
1132
+ // complete-handlers.js runArchiveCeremonyDualRunExit 同款消费);skipped(档位文件缺失=存量
1133
+ // 变更零红门禁)/degraded(git 不可用降级)零噪音放行;整体 fail-soft——检查自身异常降级
1134
+ // 放行(异常不是低报,对齐 archive 出口同款处置)。specBase/runtimeRoot 与四邻检查同源
1135
+ // (reconcileRuntimeRoot=resolveRuntimeRoot(platformOpts, specBase),口径一处定义不二算)。──
1136
+ try {
1137
+ const { runCeremonyDualRunCheck, printCeremonyDualRunCheck } = await import('../verify-postcheck.js')
1138
+ const ceremonyCheck = await runCeremonyDualRunCheck({
1139
+ cwd, specBase, changeName,
1140
+ runtimeRoot: reconcileRuntimeRoot,
1141
+ })
1142
+ printCeremonyDualRunCheck(ceremonyCheck)
1143
+ if (ceremonyCheck.status === 'mismatch' && ceremonyCheck.severity === 'error') {
1144
+ return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts, { type: 'gate_rollback', detail: 'verify-contract' })
1145
+ }
1146
+ } catch (e) {
1147
+ console.warn(`⚠️ Ceremony 双跑对账异常降级放行(不阻断 verify,fail-soft): ${(e && e.message) || e}`)
1148
+ }
938
1149
  // ── 行号漂移自动重锚(2026-09-09 §7-6:quick --done 已接,verify/archive 收尾补接)──
939
1150
  // 本变更 diff 中的 .md 文档经 autoReanchorDocRefs 定点重锚(fixable 唯一/优选命中才改 +
940
1151
  // 同口径回执),消灭「src 改动平移行号 → 下次 docs gate 拦」的人工往返。挂 verify 收尾即
@@ -971,6 +1182,25 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
971
1182
  }
972
1183
  }
973
1184
  }
1185
+ // ── 预填注清零 error 门(归档前最后一道;2026-09-18-artifact-prefill task-03 / FR-03 /
1186
+ // D-003@v1 门禁梯度·error 档):白名单槽宿主文件(design.md + tasks/task-*.md)仍含
1187
+ // 未删预填注 → 阻断 verify 完成(verify 完成是 archive 前置——「归档前注清零」的落点;
1188
+ // brainstorm/plan --done 的 advisory 在此收口为硬门,注在场=预填未确认)。检测单点 =
1189
+ // verify-probes 探针面 runProbe10PrefillNoteClearance(探针 10 --init 预填与本门同一
1190
+ // 实现,不二算)。已知误报面:散文引用注字面量会命中——修法是核对后删注/改写散文
1191
+ // 措辞,不是绕门。──
1192
+ try {
1193
+ const { runProbe10PrefillNoteClearance } = await import('../verify-probes.js')
1194
+ const noteClearance = runProbe10PrefillNoteClearance({ specBase, changeName })
1195
+ if (noteClearance.unclearedFiles.length > 0) {
1196
+ console.error(`\n❌ verify 阶段被阻断:预填注清零校验未过(${noteClearance.unclearedFiles.length} 个文件的白名单槽仍含未删预填注——预填≠结论,注在场=未确认):`)
1197
+ for (const f of noteClearance.unclearedFiles) console.error(` - ${f}`)
1198
+ console.error(' 修复:逐槽核对预填值后删除行尾「(预填:核对后删本注)」注(删注=确认动作);散文含注字面量的命中改写该处措辞。清零后重新完成 verify(进度不丢)。')
1199
+ return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts, { type: 'gate_rollback', detail: 'verify-contract' })
1200
+ }
1201
+ } catch (e) {
1202
+ console.warn(`⚠️ 预填注清零校验异常(降级放行,fail-soft——异常不是未清零,ceremony 双跑同款处置): ${(e && e.message) || e}`)
1203
+ }
974
1204
  // ── verify 服务进程回收(坑 verify-service-process-leak;坑 verify-pids-cross-session-kill
975
1205
  // 见 reapVerifyServices 注释)──
976
1206
  try {
@@ -1217,6 +1447,69 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
1217
1447
  return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts, { type: 'review_rejected', detail: 'task-review' })
1218
1448
  }
1219
1449
  }
1450
+
1451
+ // ── 仪式档位摩擦升档检查点(task-03 / D-008,design 生命周期契约表「阶段门升档」行)──
1452
+ // 级联末位(所有阻断门通过、本次 --done 即将成立)执行:读 friction-ledger 累计账 →
1453
+ // escalateByFriction 超阈 min(S3, tier+1) → 迁移记录写 .runtime/ceremony-tier-<change>.json
1454
+ // (withFileLock+原子写、只升不降;读档无文件先落初始档=开跑定价事件)。升档不阻断本次
1455
+ // 完成——下一阶段才生效,warning 披露 from→to 与摩擦计数审计行;未超阈静默通过不加噪。
1456
+ // fail-soft:升档检查是旁路数据面,任何异常绝不阻断完成(对齐 friction-ledger fail-soft 红线)。
1457
+ if (['brainstorm', 'plan', 'execute', 'verify'].includes(stageName) && changeName) {
1458
+ try {
1459
+ const escTier = await escalateCeremonyTierAtGate({ cwd, specBase, platformOpts, progress, changeName, projectName, stageName })
1460
+ if (escTier.escalated) {
1461
+ let shownPath = escTier.tierFilePath
1462
+ try {
1463
+ const rp = relative(cwd, escTier.tierFilePath)
1464
+ if (rp) shownPath = rp
1465
+ } catch { /* Windows 跨盘符 relative 抛错 → 保留绝对路径 */ }
1466
+ console.warn(`\n⚠️ 摩擦升档 ${escTier.from}→${escTier.to}(friction-ledger 累计 gate_rollback=${escTier.frictionCounts.gate_rollback}、review_rejected=${escTier.frictionCounts.review_rejected} 超阈,min(S3, tier+1))——不阻断本次完成,下一阶段起生效(档位文件 ${shownPath} 已迁移留痕)`)
1467
+ }
1468
+ } catch (e) {
1469
+ console.warn(`⚠️ 仪式档位摩擦升档检查异常(不阻断完成,本次跳过): ${e && e.message ? e.message : e}`)
1470
+ }
1471
+ }
1472
+
1473
+ // ── 影子期后台静默派发触发点(2026-09-18-ceremony-risk-pricing task-06 / Phase 3;
1474
+ // execute 评审 GAP 项补接线):升档检查块后 fire-and-forget(dispatchShadowReview JSDoc
1475
+ // 建议挂点),S0/S1 轻档变更在 brainstorm/plan/execute 完成时向平台派重仪式影子审查做轻/重
1476
+ // 对照——只记账不阻断(FR-04:影子结论只落 stage-reviews-shadow/ 命名空间,函数内部已隔离,
1477
+ // 绝不写主线 marker、不进 gate/verify/archive 判定面)。前置校验(档位∈S0/S1、ceremony.shadow
1478
+ // 开关、幂等闸、平台 client 可用性)全在函数内部,不满足 → skip 留痕对照账(零噪音)。
1479
+ // client 供给面(P3-1 收口,ql-20260918-006):复用 review-dispatch 命令先例(index.js:1273)
1480
+ // 懒加载 SillyHubMcpClient 注入——构造只读 local.yaml 配置不联网;未配平台的仓在函数内
1481
+ // probe no-config 仍走 skip 留痕(合理:影子只在配了平台的仓积累对照数据)。specBase 平台
1482
+ // 接管优先(与上方 Stage Review Gate effectiveSpecBase 同口径)。异常双保险:函数自身
1483
+ // catch-all 永不抛 + 外层 try-catch/promise.catch 吞掉(影子是旁路数据面,任何异常绝不影响
1484
+ // 门判定与完成)。──
1485
+ if (['brainstorm', 'plan', 'execute'].includes(stageName) && changeName) {
1486
+ try {
1487
+ const shadowSpecBase = platformOpts?.specRoot || specBase
1488
+ Promise.all([
1489
+ import('../review-dispatch.js'),
1490
+ import('../sillyhub-mcp/client.js'),
1491
+ ])
1492
+ .then(async ([{ dispatchShadowReview }, { SillyHubMcpClient }]) => {
1493
+ // 构造失败(local.yaml 损坏等)传 undefined → 函数内部 skip 留痕,不报错不阻断
1494
+ let shadowClient;
1495
+ try {
1496
+ shadowClient = new SillyHubMcpClient({ cwd });
1497
+ } catch {
1498
+ shadowClient = undefined;
1499
+ }
1500
+ return dispatchShadowReview({
1501
+ mode: 'create', cwd, specBase: shadowSpecBase, changeName, stage: stageName,
1502
+ client: shadowClient,
1503
+ });
1504
+ })
1505
+ .then((r) => {
1506
+ if (r && r.ok && !r.skipped && r.missionId) {
1507
+ console.log(`\nℹ️ 影子审查已后台派发(mission ${r.missionId},fire-and-forget 只记账不阻断——回收随主线检查点 status 模式或 doctor 影子对照维度)`)
1508
+ }
1509
+ })
1510
+ .catch(() => { /* fire-and-forget:影子链路异常吞掉,绝不向主线传播 */ })
1511
+ } catch { /* 动态 import 触发失败静默(影子是旁路数据面) */ }
1512
+ }
1220
1513
  return null
1221
1514
  }
1222
1515
 
@@ -1360,28 +1653,17 @@ export function validateFileLocations(cwd, stageName, progress, changeName, spec
1360
1653
  */
1361
1654
  function readDesignOwnFiles(specBase, changeName) {
1362
1655
  if (!specBase || !changeName) return []
1363
- let content
1656
+ // 2026-09-19-ceremony-pricing-five-cuts task-03 / D-004:委托 change-list.js 单一真相源解析器
1657
+ // (此前本地重写的数字标题版只认「## 6.」——现行模板「## 文件变更清单」8 行解析成 0,span 轴瞎)。
1658
+ // parseFileChangeListDetailed:双形态标题(## 6. 文件变更清单 / ## 文件变更清单 含括注变体)
1659
+ // + 任何 ^## 标题关段 + 表格/分类列表双写法;keepSillyspecDocs=true 保持旧实现把 .sillyspec
1660
+ // 交付文档计入声明面的行为。
1364
1661
  try {
1365
- content = readFileSync(join(specBase, 'changes', changeName, 'design.md'), 'utf8')
1662
+ return parseFileChangeListDetailed(join(specBase, 'changes', changeName, 'design.md'), { keepSillyspecDocs: true })
1663
+ .map(e => e.path)
1366
1664
  } catch {
1367
1665
  return []
1368
1666
  }
1369
- const files = []
1370
- let inSection6 = false
1371
- for (const line of content.split('\n')) {
1372
- const heading = line.match(/^##\s+(\d+)\./)
1373
- if (heading) {
1374
- inSection6 = heading[1] === '6'
1375
- continue
1376
- }
1377
- if (!inSection6 || !line.startsWith('|')) continue
1378
- // 跳过表头分隔行(|---|---|)
1379
- if (/^\|[\s:|-]+\|?$/.test(line.trim())) continue
1380
- // 表格第 2 列反引号文件路径:`| 新增 | \`src/.../x.js\` | 说明 |`
1381
- const m = line.match(/^\|[^|]+\|\s*`([^`]+)`/)
1382
- if (m) files.push(m[1].trim())
1383
- }
1384
- return files
1385
1667
  }
1386
1668
 
1387
1669
  /**