sillyspec 3.28.13 → 3.29.1

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/package.json +1 -1
  2. package/src/blast-surface.js +168 -0
  3. package/src/ceremony-tier.js +359 -0
  4. package/src/change-risk-profile.js +72 -199
  5. package/src/config-schema.js +22 -1
  6. package/src/decisions-io.js +32 -0
  7. package/src/design-facts.js +1 -0
  8. package/src/doctor-diagnostics.js +119 -1
  9. package/src/index.js +92 -5
  10. package/src/knowledge-stats.js +25 -0
  11. package/src/modules.js +28 -0
  12. package/src/prefill.js +491 -0
  13. package/src/probe7-anchor-check.js +8 -6
  14. package/src/quick-gate-profile.js +8 -4
  15. package/src/quick-session-owner.js +63 -0
  16. package/src/redlines.js +195 -0
  17. package/src/review-dispatch.js +473 -1
  18. package/src/review-material-pack.js +265 -0
  19. package/src/review-tier.js +185 -41
  20. package/src/run/archive-distill.js +6 -0
  21. package/src/run/command.js +158 -87
  22. package/src/run/complete-handlers.js +82 -1
  23. package/src/run/complete.js +90 -3
  24. package/src/run/gate-snapshot.js +30 -1
  25. package/src/run/gates.js +301 -19
  26. package/src/run/prompt.js +423 -21
  27. package/src/run/quick-audit.js +12 -2
  28. package/src/run/shared.js +36 -0
  29. package/src/run/verify-quality-scan.js +15 -7
  30. package/src/scope-audit.js +8 -1
  31. package/src/span-risk-surface.js +127 -0
  32. package/src/stage-contract-spec.js +2 -2
  33. package/src/stage-contract.js +102 -81
  34. package/src/stage-review.js +26 -2
  35. package/src/stages/brainstorm-auto.js +1 -1
  36. package/src/stages/brainstorm.js +35 -19
  37. package/src/stages/execute.js +11 -3
  38. package/src/stages/plan.js +45 -24
  39. package/src/stages/scan.js +1 -1
  40. package/src/stages/verify.js +7 -7
  41. package/src/taskcard.js +3 -0
  42. package/src/verify-postcheck.js +375 -20
  43. package/src/verify-probes.js +194 -10
  44. package/src/worktree-apply.js +1 -1
  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
@@ -9,7 +9,9 @@ import { existsSync, readdirSync, readFileSync, writeFileSync } from 'fs'
9
9
  import { join, basename } from 'path'
10
10
  import { safeGit } from './git-helper.js'
11
11
  import { nowWallClock } from './datetime.js'
12
- import { detectChangeRisk, checkIntegrationEvidence, VERIFICATION_NEEDS, RISK_LEVEL_CAUSES } from './change-risk-profile.js'
12
+ import { resolveChangeRisk, extractExplicitRiskLevel, checkIntegrationEvidence, VERIFICATION_NEEDS, RISK_LEVEL_CAUSES } from './change-risk-profile.js'
13
+ import { loadBlastDeclarationsAllProjects } from './blast-surface.js'
14
+ import { parseFileChangeListDetailed } from './change-list.js'
13
15
  import { parseEvidenceSlots } from './verify-facts-schema.js'
14
16
  import { IR_STRICT_SINCE } from './constants.js'
15
17
  import { SCAN_REQUIRED_DOCS, AUXILIARY_STAGES } from './constants.js'
@@ -24,7 +26,6 @@ import { discoverModuleIndex } from './decision-distill.js'
24
26
  import { appendKnowledgeHit } from './knowledge-hits.js'
25
27
  // design.md 文件清单解析(2026-09-17-pass-cap-semantics task-02 事实③):change-list 是纯 fs
26
28
  // 叶子模块(design-facts / scope-audit / verify-probes 等同款直连惯例),静态引入无环。
27
- import { parseFileChangeListDetailed } from './change-list.js'
28
29
 
29
30
  /**
30
31
  * 校验结果
@@ -299,6 +300,21 @@ function validateScanOutputs(cwd, changeName, context = {}) {
299
300
  /**
300
301
  * brainstorm 完成校验:检查四件套规范文件是否生成
301
302
  */
303
+ /**
304
+ * 变更目录级声明面判级(2026-09-19-ceremony-pricing-five-cuts task-03 / D-008):
305
+ * design 文件变更清单(change-list 单一真相源解析,双形态标题)× blast 声明(多项目并集,
306
+ * 调用方无 project 语境)+ design frontmatter explicit(只压 tier 不豁免 evidence,D-009)。
307
+ * 词表散文判级(detectChangeRisk)退役后的 stage-contract 四消费点统一入口。
308
+ */
309
+ function resolveChangeRiskForChangeDir(changeDir, specBase) {
310
+ const designPath = join(changeDir, 'design.md')
311
+ let designText = ''
312
+ try { designText = readFileSync(designPath, 'utf8') } catch { designText = '' }
313
+ const declaredFiles = parseFileChangeListDetailed(designPath, { keepSillyspecDocs: true }).map(e => e.path)
314
+ const blastDeclarations = loadBlastDeclarationsAllProjects({ specBase })
315
+ return resolveChangeRisk({ files: declaredFiles, blastDeclarations, explicitRiskLevel: designText ? extractExplicitRiskLevel(designText) : null })
316
+ }
317
+
302
318
  function validateBrainstormOutputs(cwd, changeName, context = {}) {
303
319
  const { specRoot } = context
304
320
  const changesRoot = specRoot ? join(specRoot, 'changes') : join(cwd, '.sillyspec', 'changes')
@@ -376,23 +392,22 @@ function validateBrainstormOutputs(cwd, changeName, context = {}) {
376
392
  }
377
393
  }
378
394
 
379
- // ── 风险判级提前提示(坑 risk-first-use-opaque,2026-08-24 用户实证)──
380
- // verify --done 的 risk gate 既往只在流程末段暴露,首次使用者撞墙后才知道 frontmatter
381
- // risk_level 覆盖通道。design --done 即按同款 detectChangeRisk 预判一次(此时 plan.md 尚未
382
- // 产出,verify 时会连 plan 一起重判、级别可能上浮——本提示 advisory 不作为定论):
383
- // 高危关键词命中 / 存在否定抑制 → 透出判级结果 + 覆盖指引,把机制可见性提前到 design 阶段。
395
+ // ── 风险判级提前提示(坑 risk-first-use-opaque,2026-08-24;2026-09-19-ceremony-pricing-five-cuts
396
+ // task-03 改声明面口径)── verify --done 的证据门既往只在流程末段暴露,首次使用者撞墙后
397
+ // 才知道出路。design --done 即按「design 文件清单 × 项目声明危险面」预判一次(advisory
398
+ // 不作定论):evidence:true 命中 → 透出证据要求 + 出路;explicit 已声明 → 透出「压档不豁免
399
+ // 证据」(D-009——旧显式短路连证据门一起免的洞已收口)。
384
400
  {
385
- const profile = detectChangeRisk({ designContent: content })
386
- if (['integration-critical', 'deployment-critical'].includes(profile.level) && !profile.explicit) {
401
+ const riskPreview = resolveChangeRiskForChangeDir(changeDir, context.specRoot || specRoot || join(cwd, '.sillyspec'))
402
+ if (riskPreview.evidenceRequired && !riskPreview.explicit) {
387
403
  warnings.push(
388
- `[risk] 本次 design 判级 ${profile.level}(命中关键词:${profile.triggers.join('、')})——verify --done 将强制真实集成证据门控。` +
389
- `若属关键词误伤(实际未触碰 daemon/session/启动入口),现在就在 design.md frontmatter 加 risk_level: <真实等级>(如 unit-sufficient)显式覆盖,免得到 verify 阶段才发现而返工。`
404
+ `[risk] 本次 design 声明文件面命中项目声明危险面(${riskPreview.hitPrefixes.join('、')},evidence:true)——verify --done 将强制真实集成证据门控。` +
405
+ `若认为不该要求集成证据:改 _module-map.yaml 顶层 blast 段(git 可见);design frontmatter risk_level 只压仪式档、不豁免证据要求(D-009)。`
390
406
  )
391
407
  }
392
- if ((profile.suppressedTriggers || []).length > 0) {
408
+ if (riskPreview.evidenceRequired && riskPreview.explicit) {
393
409
  warnings.push(
394
- `[risk] 关键词命中 ${profile.suppressedTriggers.join('、')} 已被同句否定语境抑制,当前判级 ${profile.level}。` +
395
- `若实际涉及对应改动,请在 design.md frontmatter 声明高危级(抑制可审计,防静默降级逃证据门控)。`
410
+ `[risk] 声明文件面命中 evidence:true 危险面(${riskPreview.hitPrefixes.join('、')}),且 frontmatter 已显式 risk_level——注意:显式声明压仪式档(tier=${riskPreview.tier}),但集成证据要求不被豁免(D-009)。`
396
411
  )
397
412
  }
398
413
  }
@@ -655,40 +670,26 @@ function validateVerifyOutputs(cwd, changeName, context = {}) {
655
670
  }
656
671
 
657
672
  // ── P0: Change Risk Gate — 核心功能缺少真实集成验证时 FAIL ──
658
- const changeRiskProfile = detectChangeRisk({
659
- designContent: readIfExists(join(changeDir, 'design.md')),
660
- planContent: readIfExists(join(changeDir, 'plan.md')),
661
- })
673
+ // 2026-09-19-ceremony-pricing-five-cuts task-03:声明面口径——design 文件清单 × blast 声明,
674
+ // evidenceRequired 驱动证据门(D-009:explicit 只压仪式档不豁免证据)。
675
+ const changeRiskProfile = resolveChangeRiskForChangeDir(changeDir, context.specRoot || specRoot || join(cwd, '.sillyspec'))
662
676
  const conclusion = conclusionStr // 槽优先解析结果复用(此前同输入重复扫两遍)
663
- // ── 否定抑制审计(坑 risk-negation-blindness)──
664
- // 关键词命中被同句否定语境抑制而不参与判级时必须透出:抑制是把双刃剑(「本次不新增
665
- // daemon」真豁免 vs 靠堆否定措辞静默降级逃证据门控),这里让每次抑制可见可核对;
666
- // frontmatter risk_level 仍是双向权威覆盖通道。
667
- if ((changeRiskProfile.suppressedTriggers || []).length > 0) {
668
- warnings.push(
669
- `[${changeRiskProfile.level}] 风险判级:关键词命中 ${changeRiskProfile.suppressedTriggers.join(', ')} 已被同句否定语境(不/未/无/避免…)抑制,未参与判级。` +
670
- `若本变更实际涉及跨进程/状态机/启动入口改动,请在 design.md frontmatter 加 risk_level: integration-critical(或 deployment-critical)显式声明或提供真实集成证据——抑制可审计,不许用来静默降级。`
671
- )
672
- }
673
- if (['integration-critical', 'deployment-critical'].includes(changeRiskProfile.level)) {
674
- // 关键词误伤早期引导(坑2,FR-02):命中高危关键词且无 frontmatter risk_level 时,
675
- // 无条件透出 frontmatter 覆盖指引(不依赖 conclusion / evidence),让 agent 早期即可
676
- // 判断是否属误判并显式覆盖,而非撞到 evidence gate 末尾才发现出路③。含否定语境的
677
- // 命中已在 detectChangeRisk 内被同句否定抑制(见 change-risk-profile.js NEGATION_CUES),
678
- // 此处命中的都是无否定语境的真命中;仍误伤的走 frontmatter 显式覆盖通道。
677
+ if (changeRiskProfile.evidenceRequired) {
678
+ // 声明面命中披露 + 出路(旧关键词误伤引导随词表退役改写):命中 evidence:true 声明路径
679
+ // → 证据门强制;出路=改 map(git 可见)或提供真实证据;risk_level 压档不豁免证据。
679
680
  if (!changeRiskProfile.explicit) {
680
681
  warnings.push(
681
- `[${changeRiskProfile.level}] 本次变更被关键词判级(命中:${changeRiskProfile.triggers.join(', ')})。` +
682
- `若属关键词误伤(实际未触碰 daemon/session/启动入口/跨进程),可在 design.md frontmatter 加 risk_level: <真实等级>(如 unit-sufficient)显式覆盖后重跑。`
682
+ `[${changeRiskProfile.level}] 本次变更声明文件面命中项目声明危险面(evidence:true:${changeRiskProfile.hitPrefixes.join(', ')})。` +
683
+ `若认为不该要求集成证据:改 _module-map.yaml 顶层 blast 段(git 可见);design frontmatter risk_level 只压仪式档、不豁免证据要求(D-009)。`
683
684
  )
684
685
  }
685
- // ── 显式 risk_level 豁免洞分层(D-002@v1,2026-09-17-pass-cap-semantics task-02)──
686
- // ① explicit + 降级 unit-sufficient/contract-required:不进本 critical 块(上方 level 门),
687
- // PASS WITH NOTES 维持免证据——关键词误伤逃生保留;
688
- // ② explicit 且仍 integration/deployment-critical + PASS WITH NOTES:必须携带结构化 handover
689
- // (facts.handover 有效行;blocking 级计入封顶口径同 validatePassEligibility 条件②)或齐全
690
- // 集成证据,二选一——无 handover 即挂证据门(checkIntegrationEvidence 不过 → error);
691
- // ③ 非显式(关键词判级)判定式一字不动(防 PASS WITH NOTES 绕证据门控)。
686
+ // ── 显式 risk_level 分层(D-002@v1 延续 + D-009 修订,2026-09-19-ceremony-pricing-five-cuts)──
687
+ // ① explicit 与否不豁免证据(D-009):evidence:true 命中即进本块——explicit 低档声明不再
688
+ // 是证据逃生门(旧「豁免级免证据」洞已收口,出路=改 map);
689
+ // ② explicit + PASS WITH NOTES:必须携带结构化 handover(facts.handover 有效行;blocking 级
690
+ // 计入封顶口径同 validatePassEligibility 条件②)或齐全集成证据,二选一——无 handover 即挂
691
+ // 证据门(checkIntegrationEvidence 不过 → error);
692
+ // ③ 非显式(声明面判级)判定式一字不动(防 PASS WITH NOTES 绕证据门控)。
692
693
  const notesExplicitCritical = conclusion === 'PASS WITH NOTES' && changeRiskProfile.explicit
693
694
  const handoverRows = notesExplicitCritical ? countFactsHandoverItems(changeDir) : 0
694
695
  const requiresEvidence = conclusion === 'PASS'
@@ -764,10 +765,11 @@ function validateVerifyOutputs(cwd, changeName, context = {}) {
764
765
  ` 缺失项(需在 verify-result.md 如实提供并满足):${evidenceCheck.errors.join('; ')}\n` +
765
766
  ` 每项要提供什么:${needs}\n` +
766
767
  ` 风险判级原因:${cause}\n` +
767
- ` 命中触发词:${changeRiskProfile.triggers.join(', ')}\n` +
768
+ ` 命中声明危险面(evidence:true):${changeRiskProfile.hitPrefixes.join(', ') || '(无)'}\n` +
768
769
  ` 出路:① 补全上述缺失的真实集成证据(真实启动 daemon/backend、集成测试、运行日志)后保持 PASS;` +
769
770
  `或 ② 如实改结论 FAIL(承认端到端未验,留待部署后补);` +
770
- `或 ③ 若判级是关键词误伤(实际并未触碰 daemon/session/启动入口),在 design.md frontmatter 加 risk_level: <真实等级>(如 unit-sufficient)显式覆盖后重跑。` +
771
+ `或 ③ 若认为该路径不该要求集成证据,改 _module-map.yaml 顶层 blast 段的 evidence 声明(git 可见)——` +
772
+ `design frontmatter risk_level 只压仪式档、不豁免证据要求(D-009)。` +
771
773
  `仅改结论文案/措辞蹭字面关键词会被对账。`
772
774
  )
773
775
  }
@@ -782,11 +784,11 @@ function validateVerifyOutputs(cwd, changeName, context = {}) {
782
784
  // ============ 探针 7 验收×测试覆盖矩阵门禁(2026-09-14-acceptance-test-matrix FR-02) ============
783
785
  // 段格式与 src/verify-probes.js renderProbe7Lines 骨架渲染字面同源(禁第二套解析文法):
784
786
  // 段标题「#### 探针 7:验收×测试覆盖矩阵」(全/半角冒号皆认);每 task 前 **task-NN** 锚行;
785
- // 五列表 | acceptance 条目 | 归属测试文件 | 关键词命中 | 判定 | 证据 |;判定槽 <待填:四选一>、
787
+ // 五列表 | acceptance 条目 | 归属测试文件 | 关键词命中 | 判定 | 证据 |;判定槽 <待填:五选一>、
786
788
  // 证据槽 <TODO>;列表防御行(卡无 acceptance…)与不适用行无槽不计。
787
789
 
788
- /** 判定列四枚举白名单(骨架口径注记字面同源) */
789
- const MATRIX_VERDICT_WHITELIST = new Set(['covered', 'partial', 'uncovered', 'non-testable'])
790
+ /** 判定列五枚举白名单(骨架口径注记字面同源;covered-service=service 层承接,2026-09-19-api-matrix-service-coverage) */
791
+ const MATRIX_VERDICT_WHITELIST = new Set(['covered', 'covered-service', 'partial', 'uncovered', 'non-testable'])
790
792
  /** 证据列未填占位(骨架字面同源) */
791
793
  const MATRIX_EVIDENCE_TODO = '<TODO>'
792
794
 
@@ -823,11 +825,12 @@ function matrixEvidenceHasAnchor(evidence) {
823
825
  }
824
826
 
825
827
  /**
826
- * 行级证据口径:covered/partial 须非 TODO 且含测试锚点;non-testable 须非 TODO 且非空
827
- * (一句话理由);uncovered 无证据要求;判定未填(不在三枚举内)的行只计 unfilled 不重复计证据。
828
+ * 行级证据口径:covered/covered-service/partial 须非 TODO 且含测试锚点;non-testable 须非 TODO
829
+ * 且非空(一句话理由);uncovered 无证据要求;判定未填(不在需证据枚举内)的行只计 unfilled 不重复
830
+ * 计证据(covered-service 与 covered 同口径:证据即测试锚点三形态,2026-09-19-api-matrix-service-coverage)。
828
831
  */
829
832
  function matrixEvidenceMissing(verdict, evidence) {
830
- if (verdict !== 'covered' && verdict !== 'partial' && verdict !== 'non-testable') return false
833
+ if (verdict !== 'covered' && verdict !== 'covered-service' && verdict !== 'partial' && verdict !== 'non-testable') return false
831
834
  const e = String(evidence || '').trim()
832
835
  if (e === '' || e === MATRIX_EVIDENCE_TODO || e.startsWith('<待填')) return true
833
836
  if (verdict === 'non-testable') return false // 非 TODO 且非空即合规(理由一句话)
@@ -925,15 +928,15 @@ function validateAcceptanceMatrix(cwd, changeName, context = {}) {
925
928
  if (matrix.unfilled > 0) {
926
929
  const list = matrix.rows.filter(r => r.unfilled).map(rowLabel).join(';')
927
930
  errors.push(
928
- `探针 7 验收×测试覆盖矩阵有 ${matrix.unfilled} 行判定未填(四选一 covered/partial/uncovered/non-testable):${list}。` +
929
- `修复:编辑 verify-result.md 探针 7 段,把 <待填:四选一> 替换为判定值。`
931
+ `探针 7 验收×测试覆盖矩阵有 ${matrix.unfilled} 行判定未填(五选一 covered/covered-service/partial/uncovered/non-testable):${list}。` +
932
+ `修复:编辑 verify-result.md 探针 7 段,把 <待填:五选一> 替换为判定值。`
930
933
  )
931
934
  }
932
935
  if (matrix.missingEvidence > 0) {
933
936
  const list = matrix.rows.filter(r => r.evidenceMissing).map(rowLabel).join(';')
934
937
  errors.push(
935
938
  `探针 7 验收×测试覆盖矩阵有 ${matrix.missingEvidence} 行证据缺失` +
936
- `(covered/partial 证据须含测试锚点:\`.test.\` 文件名 / file:line / 反引号包裹的测试名;non-testable 证据须写一句理由):${list}。` +
939
+ `(covered/covered-service/partial 证据须含测试锚点:\`.test.\` 文件名 / file:line / 反引号包裹的测试名;non-testable 证据须写一句理由):${list}。` +
937
940
  `修复:在证据列补测试锚点(如 \`test/foo.test.mjs\` 或 \`src/x.js:42\`)或 non-testable 理由。`
938
941
  )
939
942
  }
@@ -985,11 +988,13 @@ function validateAcceptanceMatrix(cwd, changeName, context = {}) {
985
988
  // 与探针 7(验收×测试承接面)并排互补的独立对账门(design §4,D-009 非目标 / R-07 口径注记
986
989
  // 互指):探针 7 管「每条 acceptance 由哪些测试承接」,本矩阵管「design 接口段每个端点由哪些
987
990
  // 验证用例/冒烟步骤覆盖」——fail-closed 记账,机械封住「接口层零派生」的 P1 缺陷面:
988
- // - covered 记账:分子=判定 covered 的端点行;有效分母=N−non-testable 行数(N=解析端点数
989
- // 或声明数,D-005);分子<有效分母或解析面有未覆盖端点 → error 逐条列缺覆盖端点
991
+ // - covered 记账:分子=判定 covered+covered-service 的端点行(covered-service 计分子——
992
+ // service 层承接是已完成、覆盖层不同;另有 advisory 单独计数承接面);有效分母=N−non-testable
993
+ // 行数(N=解析端点数或声明数,D-005);分子<有效分母或解析面有未覆盖端点 → error 逐条列缺覆盖端点
990
994
  // - 锚点五形态(design §4 Grill #10):design接口表#<METHOD /path> 解析级(须命中
991
995
  // facts.apiFace 解析产出集,防空指);权限矩阵[...]/契约表@.../DDL@.../载荷@... 形态级
992
- // 存在即认;covered/partial 行缺锚点 → error(missingEvidence 口径同 probe7)
996
+ // 存在即认;covered/partial 行缺锚点 → error(missingEvidence 口径同 probe7);
997
+ // covered-service 行只须测试锚点三形态(matrixEvidenceHasAnchor 同源,2026-09-19-api-matrix-service-coverage)
993
998
  // - 移交联动(probe7 条件④同款形态):partial/uncovered 端点行>0 且 facts.handover 零
994
999
  // 有效行 → error;blocking 是否封顶归 validatePassEligibility 条件②(④管有去向/②管去向级)
995
1000
  // - 探索行(uncovered+[探索] 标记)与消费端子行(两空格缩进 ↳ 前缀)不进分母分子
@@ -1093,7 +1098,7 @@ function apiEvidenceHasAnchorForm(evidence) {
1093
1098
  * facts?: object|null, // verify-facts.json(handover 联动用,缺 null)
1094
1099
  * factsExpected?: boolean, // false(存量未跑管线)→ 兼容 ok 不误伤
1095
1100
  * strict?: boolean, // isIrStrictVerifyChange(段缺失分层用)
1096
- * riskLevel?: string|null, // detectChangeRisk().level
1101
+ * riskLevel?: string|null, // resolveChangeRisk().level(声明面判级兼容字段)
1097
1102
  * changeName?: string,
1098
1103
  * permSectionText?: string, // design.md 权限段(段头含 权限/角色 的段合并文本,壳内提取)
1099
1104
  * designText?: string, // design.md 全文(写端点豁免行级检测用)
@@ -1172,13 +1177,22 @@ export function judgeApiCoverageMatrix(args) {
1172
1177
  const unfilledRows = judgedRows.filter(r => r.unfilled)
1173
1178
  if (unfilledRows.length > 0) {
1174
1179
  errors.push(
1175
- `接口验证覆盖矩阵有 ${unfilledRows.length} 行判定未填(四选一 covered/partial/uncovered/non-testable):${unfilledRows.map(rowLabel).join(';')}。` +
1176
- `修复:编辑 verify-result.md 接口验证覆盖矩阵段,把 <待填:四选一> 替换为判定值。`
1180
+ `接口验证覆盖矩阵有 ${unfilledRows.length} 行判定未填(五选一 covered/covered-service/partial/uncovered/non-testable):${unfilledRows.map(rowLabel).join(';')}。` +
1181
+ `修复:编辑 verify-result.md 接口验证覆盖矩阵段,把 <待填:五选一> 替换为判定值。`
1177
1182
  )
1178
1183
  }
1179
1184
  const parseSet = new Set(endpoints.map(e => `${e.method} ${e.path}`))
1180
1185
  const anchorViolations = []
1181
1186
  for (const r of judgedRows) {
1187
+ if (r.verdict === 'covered-service') {
1188
+ // covered-service(service 层承接):证据即测试本身,只校验测试锚点三形态
1189
+ // (matrixEvidenceHasAnchor 同源口径,禁第二套解析文法);不做 design接口表# 解析级
1190
+ // 核对与五形态校验(不要求 design 侧锚点)——校验后 continue,不走 covered/partial 分支。
1191
+ if (!matrixEvidenceHasAnchor(r.evidence)) {
1192
+ anchorViolations.push(`${rowLabel(r)}:covered-service 证据缺测试锚点(\`.test.\` 文件名 / file:line / 反引号包裹的测试名)`)
1193
+ }
1194
+ continue
1195
+ }
1182
1196
  if (r.verdict !== 'covered' && r.verdict !== 'partial') continue
1183
1197
  const e = String(r.evidence || '')
1184
1198
  // design接口表# 锚点解析级核对(防空指):提取的 METHOD /path 须全部命中解析产出集
@@ -1192,7 +1206,7 @@ export function judgeApiCoverageMatrix(args) {
1192
1206
  if (anchorViolations.length > 0) {
1193
1207
  errors.push(
1194
1208
  `接口验证覆盖矩阵有 ${anchorViolations.length} 项证据锚点缺失/空指` +
1195
- `(covered/partial 证据须含锚点五形态之一:design接口表#METHOD /path(须命中 design 接口段解析面)/ 权限矩阵[角色×动作] / 契约表@行标识 / DDL@列名 / 载荷@构造点路径):${anchorViolations.join(';')}。` +
1209
+ `(covered/partial 证据须含锚点五形态之一:design接口表#METHOD /path(须命中 design 接口段解析面)/ 权限矩阵[角色×动作] / 契约表@行标识 / DDL@列名 / 载荷@构造点路径;covered-service 证据须含测试锚点三形态之一:\`.test.\` 文件名 / file:line / 反引号包裹的测试名):${anchorViolations.join(';')}。` +
1196
1210
  `修复:在证据列补真实锚点(design接口表# 锚点须与 design.md 接口段端点一致,防编造端点)。`
1197
1211
  )
1198
1212
  }
@@ -1205,17 +1219,19 @@ export function judgeApiCoverageMatrix(args) {
1205
1219
  )
1206
1220
  }
1207
1221
 
1208
- // ── covered 记账(D-005/D-008):分子=covered 端点行;有效分母=N−non-testable 行数;
1209
- // N=解析端点数,解析零行按声明数(declaredRow 优先——矩阵实态,facts.declared 兜底)。
1210
- // 缺覆盖清单解析集驱动:解析面端点 − covered 行命中 − non-testable 行命中(partial/
1211
- // uncovered/unfilled 端点行不在 covered 命中集即自然进清单;探索行不进分母分子——其
1212
- // uncovered 判定使之天然不计分子,且不计入 non-testable 扣减)。──
1222
+ // ── covered 记账(D-005/D-008):分子=covered+covered-service 端点行(covered-service 并入
1223
+ // 分子满足覆盖等式,service 层承接是「已完成、覆盖层不同」非「未完成待移交」);有效分母=
1224
+ // N−non-testable 行数;N=解析端点数,解析零行按声明数(declaredRow 优先——矩阵实态,
1225
+ // facts.declared 兜底)。缺覆盖清单解析集驱动:解析面端点 − covered/covered-service 行命中
1226
+ // − non-testable 行命中(partial/uncovered/unfilled 端点行不在命中集即自然进清单;探索行
1227
+ // 不进分母分子——其 uncovered 判定使之天然不计分子,且不计入 non-testable 扣减)。──
1213
1228
  if (!zeroFace) {
1214
1229
  const N = endpoints.length > 0 ? endpoints.length : (declaredFromRow != null ? declaredFromRow : (declared != null ? declared : 0))
1215
1230
  const nonTestableCount = rows.filter(r => r.verdict === 'non-testable').length
1216
1231
  const validDenominator = Math.max(0, N - nonTestableCount)
1217
- const coveredCount = rows.filter(r => r.verdict === 'covered').length
1218
- const coveredSet = new Set(rows.filter(r => r.verdict === 'covered' && r.method && r.path).map(r => `${r.method} ${r.path}`))
1232
+ const serviceCoveredCount = rows.filter(r => r.verdict === 'covered-service').length
1233
+ const coveredCount = rows.filter(r => r.verdict === 'covered' || r.verdict === 'covered-service').length
1234
+ const coveredSet = new Set(rows.filter(r => (r.verdict === 'covered' || r.verdict === 'covered-service') && r.method && r.path).map(r => `${r.method} ${r.path}`))
1219
1235
  const nonTestSet = new Set(rows.filter(r => r.verdict === 'non-testable' && r.method && r.path).map(r => `${r.method} ${r.path}`))
1220
1236
  const missingEndpoints = endpoints.filter(e => {
1221
1237
  const k = `${e.method} ${e.path}`
@@ -1224,10 +1240,17 @@ export function judgeApiCoverageMatrix(args) {
1224
1240
  if (coveredCount < validDenominator || missingEndpoints.length > 0) {
1225
1241
  const list = missingEndpoints.length > 0
1226
1242
  ? missingEndpoints.map(e => `${e.method} ${e.path}`).join(';')
1227
- : `声明 ${N} 端点而 covered 端点行仅 ${coveredCount}(解析零行降级,无法逐条列端点——按声明行拆出每端点行填判定)`
1243
+ : `声明 ${N} 端点而 covered/covered-service 端点行仅 ${coveredCount}(解析零行降级,无法逐条列端点——按声明行拆出每端点行填判定)`
1228
1244
  errors.push(
1229
- `接口验证覆盖矩阵覆盖不足:有效分母 ${validDenominator}(N=${N} − non-testable ${nonTestableCount}),covered 分子 ${coveredCount}——缺覆盖端点:${list}。` +
1230
- `修复:补验证用例/冒烟步骤后把端点行改 covered 并填五形态锚点;不适用端点改 non-testable 并写一句理由;确未覆盖的走「## 移交项(结构化)」承载并保持 partial/uncovered(FR-04)。`
1245
+ `接口验证覆盖矩阵覆盖不足:有效分母 ${validDenominator}(N=${N} − non-testable ${nonTestableCount}),covered+covered-service 分子 ${coveredCount}——缺覆盖端点:${list}。` +
1246
+ `修复:补验证用例/冒烟步骤后把端点行改 covered 并填五形态锚点;端点行为由 service 层测试锁定的改 covered-service 并填测试锚点(\`.test.\` / file:line / 反引号);不适用端点改 non-testable 并写一句理由;确未覆盖的走「## 移交项(结构化)」承载并保持 partial/uncovered(FR-04)。`
1247
+ )
1248
+ }
1249
+ // covered-service 承接 advisory(D-001/R-01 第一版,只进 warnings 不进 errors):端点级与
1250
+ // 间接覆盖在矩阵统计上可区分——先 advisory 观察滥用面,不设占比上限不阻断。
1251
+ if (serviceCoveredCount > 0) {
1252
+ warnings.push(
1253
+ `[advisory] ${serviceCoveredCount} 端点由 service 层测试承接(非端点级)——端点级与间接覆盖在矩阵统计上可区分,承接占比先 advisory 观察(D-001/R-01,不阻断)`
1231
1254
  )
1232
1255
  }
1233
1256
  // 声明与解析并存以解析为准并注记(骨架 :1573 漂移信号同款口径,数据面 facts.apiFace)
@@ -1340,7 +1363,7 @@ function extractPermissionMatrixText(designMd) {
1340
1363
  * - factsExpected=false(存量未跑管线,resolveFactsExpected 同源口径)→ no-op 零行为
1341
1364
  * - verify-facts.json(apiFace/consumerHints/handover,缺失容 null——fail-closed 归纯函数)
1342
1365
  * - 矩阵段 MD 槽解析(X-05 防篡改锚点:锚定当前文档实态)
1343
- * - detectChangeRisk 判级(与 validatePassEligibility 壳同款 design/plan 双文件输入)
1366
+ * - resolveChangeRisk 声明面判级(design 文件清单 × blast 声明 + explicit,task-03)
1344
1367
  * - design.md 权限矩阵段提取(fs 只读,壳非纯函数)
1345
1368
  */
1346
1369
  export function validateApiCoverageMatrix(cwd, changeName, context = {}) {
@@ -1358,10 +1381,9 @@ export function validateApiCoverageMatrix(cwd, changeName, context = {}) {
1358
1381
 
1359
1382
  const facts = readFactsForEligibility(changeDir)
1360
1383
  const designText = readIfExists(join(changeDir, 'design.md'))
1361
- const riskProfile = detectChangeRisk({
1362
- designContent: designText,
1363
- planContent: readIfExists(join(changeDir, 'plan.md')),
1364
- })
1384
+ // 2026-09-19-ceremony-pricing-five-cuts task-03:声明面判级(design 文件清单 × blast 声明 +
1385
+ // explicit;level 为五级词兼容字段——evidenceRequired → integration-critical)
1386
+ const riskProfile = resolveChangeRiskForChangeDir(changeDir, context.specRoot || specRoot || join(cwd, '.sillyspec'))
1365
1387
  return judgeApiCoverageMatrix({
1366
1388
  matrix: extractApiCoverageMatrixSlots(readFileSync(verifyResultPath, 'utf8')),
1367
1389
  apiFace: facts && facts.apiFace && typeof facts.apiFace === 'object' ? facts.apiFace : null,
@@ -1618,7 +1640,7 @@ function collectDbScriptCandidates({ cwd, changeName, specRoot, changeDir }) {
1618
1640
  * PASS 封顶 validator 注册壳(与 validateAcceptanceMatrix 同三参签名同构,D-010:注册进
1619
1641
  * contracts.verify.validators 即覆盖 gates / machine-interface 等全部 runValidators 调用方,
1620
1642
  * gates.js 零改动)。壳内取数组装:verify-result.md 结论(槽优先,legacy 回退)、
1621
- * verify-facts.json(缺失容 null)、detectChangeRisk 判级、事实③文件集;判定调
1643
+ * verify-facts.json(缺失容 null)、resolveChangeRisk 声明面判级、事实③文件集;判定调
1622
1644
  * evaluatePassEligibility 纯函数。
1623
1645
  */
1624
1646
  export function validatePassEligibility(cwd, changeName, context = {}) {
@@ -1638,10 +1660,9 @@ export function validatePassEligibility(cwd, changeName, context = {}) {
1638
1660
  conclusion,
1639
1661
  facts,
1640
1662
  factsExpected: isIrStrictVerifyChange(changeDir) || facts !== null,
1641
- changeRiskProfile: detectChangeRisk({
1642
- designContent: readIfExists(join(changeDir, 'design.md')),
1643
- planContent: readIfExists(join(changeDir, 'plan.md')),
1644
- }),
1663
+ // 2026-09-19-ceremony-pricing-five-cuts task-03:声明面判级(level 兼容字段驱动 evaluatePassEligibility
1664
+ // 的 critical 判定——evidenceRequired → integration-critical,D-009 证据不被 explicit 豁免)
1665
+ changeRiskProfile: resolveChangeRiskForChangeDir(changeDir, context.specRoot || specRoot || join(cwd, '.sillyspec')),
1645
1666
  changeName,
1646
1667
  dbScriptCandidates: collectDbScriptCandidates({ cwd, changeName, specRoot, changeDir }),
1647
1668
  })
@@ -445,7 +445,7 @@ export function getLatestStageReviewRunId(runtimeRoot, stage, changeName) {
445
445
  * @param {string} changeName - 变更名(跨变更过滤锚)
446
446
  * @returns {{ priorRunId: string, verdicts: string, openFindings: string[], passItems: string[], notesPreview: string }|null}
447
447
  */
448
- export function collectSameStagePriorReview(runtimeRoot, stage, changeName) {
448
+ export async function collectSameStagePriorReview(runtimeRoot, stage, changeName) {
449
449
  if (!runtimeRoot || !stage || !changeName) return null
450
450
  const dir = join(runtimeRoot, 'stage-reviews')
451
451
  if (!existsSync(dir)) return null
@@ -490,11 +490,27 @@ export function collectSameStagePriorReview(runtimeRoot, stage, changeName) {
490
490
  openFindings.push(`[verdict-fail] verdict=fail 但 checklist 无明细——以 reviewerNotes 为准:${notes || '(无 notes)'}`)
491
491
  }
492
492
  if (openFindings.length === 0 && passItems.length === 0) return null
493
+ // fixDiff 腿(QA Gap 2 修正,design Wave 2.4 兑现):复审唯一基准面的另一半——上一轮之后
494
+ // 主文档的修复 diff 摘要(git diff --stat 半边,主文档相对 HEAD 的未提交改动;纯函数侧
495
+ // 不再追多提交窗口——复审场景主文档几乎总在工作树)。fail 时留空串不阻断渲染。
496
+ let fixDiff = ''
497
+ try {
498
+ const metaPath = join(runtimeRoot, '..', 'changes', changeName)
499
+ const docCandidates = ['design.md', 'plan.md', 'proposal.md']
500
+ for (const doc of docCandidates) {
501
+ const dp = join(metaPath, doc)
502
+ if (!existsSync(dp)) continue
503
+ const { safeGit } = await import('./git-helper.js')
504
+ fixDiff = String(safeGit(join(runtimeRoot, '..', '..'), ['diff', '--stat', 'HEAD', '--', dp], { trim: false }) || '')
505
+ if (fixDiff.trim()) break
506
+ }
507
+ } catch { /* fixDiff best-effort:无 git/无 diff → 空串(渲染体仍成立) */ }
493
508
  return {
494
509
  priorRunId: latest.runId,
495
510
  verdicts: `spec=${latest.review.specVerdict}, quality=${latest.review.qualityVerdict}`,
496
511
  openFindings,
497
512
  passItems,
513
+ fixDiff: fixDiff.trim().slice(0, 2000),
498
514
  notesPreview: String(latest.review.reviewerNotes || '').replace(/\s+/g, ' ').slice(0, 160),
499
515
  }
500
516
  }
@@ -513,17 +529,25 @@ export function collectSameStagePriorReview(runtimeRoot, stage, changeName) {
513
529
  export function renderPriorRoundFindingsMd(collected, cap = 15) {
514
530
  if (!collected) return ''
515
531
  const L = []
516
- L.push(`**同阶段上一轮审查结论(run ${collected.priorRunId},verdict ${collected.verdicts})——本次为复审,以增量为主,不重演全量审查**:`)
532
+ // 2026-09-19-review-material-pack D-002/Grill 交叉点 1:建议语→排他语——本块是再审的
533
+ // **唯一基准面**(fixDiff 由采集端并入 collected.openFindings 的证据行,渲染体扩展、
534
+ // 占位符机制零改动)。子代理服从必读清单而非回灌块的教训:排他语必须明写「唯一」。
535
+ L.push(`**同阶段上一轮审查结论(run ${collected.priorRunId},verdict ${collected.verdicts})——本材料是本轮复审的唯一基准面:只核验下列未决项的修复状态与本次改版新增面,不重演全量审查(全量重读=重做已付的评审)**:`)
517
536
  if (collected.openFindings.length > 0) {
518
537
  const shown = collected.openFindings.slice(0, cap)
519
538
  L.push(`- **上一轮未决问题(逐项核验修复状态:已修给证据、未解决必须如实再次 fail——漏放行=假通过)**(${shown.length}/${collected.openFindings.length} 条):`)
520
539
  for (const f of shown) L.push(` - ${f}`)
521
540
  }
541
+ if (collected.fixDiff && String(collected.fixDiff).trim()) {
542
+ L.push('- **上一轮后的修复 diff 摘要(唯一基准面的证据半边——核验修复是否真实落地)**:')
543
+ L.push('```diff\n' + String(collected.fixDiff) + '\n```')
544
+ }
522
545
  if (collected.passItems.length > 0) {
523
546
  const shown = collected.passItems.slice(0, cap)
524
547
  L.push(`- **上一轮已实证 pass 面(勿重复报告/重验——除非本次修复改动明确触及该面才定向复查)**(${shown.length}/${collected.passItems.length} 条):`)
525
548
  for (const p of shown) L.push(` - ${p}`)
526
549
  }
550
+ L.push(`- **包外查证口径**:本基准面之外的文件可定向查证但须在 reviewerNotes 列明查证清单;禁全量扫读仓。材料不足以核验某未决项→该项 cannot_verify+列缺件,不得靠全量重读自救。`)
527
551
  return `\n${L.join('\n')}\n`
528
552
  }
529
553
 
@@ -106,7 +106,7 @@ AC-002: 未修改数据库 schema
106
106
  AC-003: 未涉及鉴权/权限
107
107
  AC-004: 未扩大 allowed_paths
108
108
  AC-005: 未引入新外部依赖
109
- AC-006: 未修改核心模块(workflow/daemon/session/lifecycle)
109
+ AC-006: 未修改核心模块(模块索引中被多方依赖或承担关键流程的模块)
110
110
  AC-007: 已有项目约定可复用
111
111
  AC-008: 不影响向后兼容
112
112
  AC-009: 不涉及数据迁移
@@ -76,7 +76,10 @@ export const definition = {
76
76
  maxWaitRounds: 8,
77
77
  waitReason: '等待用户回答需求问题或澄清',
78
78
  waitOptions: ['回答见--answer', '信息够了,进入方案讨论'],
79
- prompt: `通过对话探索需求细节、分析原型(如有)、判断拆分/批量、并对歧义点做需求澄清 Grill。本步骤合并了原有的对话探索、原型分析、需求范围评估、需求澄清 Grill 四个环节——按下方 A→D 顺序按需执行。提问遵守「一次只问一个」(见 D 节追问策略):必须问的问题逐个问,能查代码/文档解决的不问;不要把多个歧义打包成一屏清单一次抛给用户(用户记不住、答不全,反而漏掉关键约束);也不要为凑数把单个问题拖成多轮。
79
+ prompt: `### ⚡ 薄跑判定(P0-3,2026-09-20 对撞实验驱动——需求已收敛来源走全仪探索是纯税)
80
+ **需求来源已收敛**(满足任一:--input 显式声明承接/已澄清/方案已定;fork 或关联会话已多轮澄清过本需求,你已能不问自答全部 P0 歧义;任务书/评审结论直接给定方案与边界)→ 本步可**薄跑**:A-D 节按已收敛事实直书结论(歧义零清单、范围直裁、决策若已有则引用源会话不重问),一次性完成不再多轮追问。薄跑不降质量底线:范围结论与决策落盘照常,仅省「明知故问」的交互轮。Grill(Step 7)与规范文件(Step 8)**不适用薄跑、永不省**。
81
+
82
+ 通过对话探索需求细节、分析原型(如有)、判断拆分/批量、并对歧义点做需求澄清 Grill。本步骤合并了原有的对话探索、原型分析、需求范围评估、需求澄清 Grill 四个环节——按下方 A→D 顺序按需执行。提问遵守「一次只问一个」(见 D 节追问策略):必须问的问题逐个问,能查代码/文档解决的不问;不要把多个歧义打包成一屏清单一次抛给用户(用户记不住、答不全,反而漏掉关键约束);也不要为凑数把单个问题拖成多轮。
80
83
 
81
84
  ### A. 原型/设计图分析(如用户提供)
82
85
  如果用户提供了截图、图片或 HTML 原型,先分析提取:
@@ -172,7 +175,7 @@ export const definition = {
172
175
  - answer: <用户确认或代码查证结果>
173
176
  - normalized_requirement: <可测试的约束>
174
177
  - impacts: [FR-?, task-?, verify-?]
175
- - evidence: <文件路径/代码位置/用户回答轮次>
178
+ - evidence: <文件路径/代码位置/用户回答轮次>(源码引用写仓根相对全路径+行号,如 src/stage-contract.js:789——裸文件名在 docs-check 层1 靠 basename 全仓扫描找候选,找不到候选或关键词窗口不匹配即失效,到 pre-push 才拦,2026-09-19 实证 64 处返工)
176
179
  - 故障面: <本决策引入的新失败模式>(可选,type=architecture 时建议填写)
177
180
  - 退役判据: <出现什么信号时简化/删除本机制>(可选,type=architecture 时建议填写)
178
181
  \`\`\`
@@ -271,6 +274,11 @@ export const definition = {
271
274
  },
272
275
  {
273
276
  name: '写设计文档并自审',
277
+ // 前置失败清单声明(2026-09-18-preflight-slimming task-04,D-001@v1):本步产出 design.md,
278
+ // prompt 渲染时 renderPreflightFailures 按 run/prompt.js PREFLIGHT_VALIDATORS 映射表只读快跑
279
+ // 声明的 validator 子集(design-file-list=design.md 文件清单幻觉路径快查;
280
+ // four-piece-rules=brainstorm 四件套规则轻查),无失败/异常恒空串零注入。
281
+ preflightValidators: ['design-file-list', 'four-piece-rules'],
274
282
  prompt: `撰写 design 文档并进行 AI 自审。
275
283
 
276
284
  ### 文件标题规则(sillyhub 平台解析识别用)
@@ -292,12 +300,12 @@ design.md 第一行标题必须用中文:# 设计文档(Design)— <变更
292
300
 
293
301
  **路径存在性核验(NEW: 前缀铁律,2026-09-17 用户反馈⑥-①前移)**:清单条目在「生成规范文件」步 \`--done\` 门禁会逐一核验仓内存在性——**计划新建的文件必须在路径前加 \`NEW:\` 前缀**(冒号后不加空格,\`NEW:src/foo.js\` 合法、\`NEW: src/foo.js\` 属书写错误);未加前缀的新建路径会以 \`design_file_ref_invalid\`(幻觉路径/书写错误)在末步被拦,返工一轮。已有文件写仓根相对正斜杠路径,禁绝对路径/glob。
294
302
  **字段数据流标注**(避免新增字段到 execute 才发现没透传):当清单含「新增/修改对外字段、接口、DTO、响应体、事件 payload、配置键」时,对应行「说明」列必须交代 producer→consumer 数据流——产出方(producer)→ 每跳流转/归一化点(序列化/反序列化/字段映射/默认值兜底)→ 消费方(consumer)。漏标 = 字段在某跳 dormant(声明了却没透传),到 execute/verify 才暴露,属设计层缺口。
295
- - 示例:「修改 daemon.ts:新增 budget_tokens,producer=backend/api-types.ts → ws_hub envelope 透传 → daemon.ts normalizeUsage(snake→camel)→ consumer=runtime/lease.ts 写 lease」
303
+ - 示例:「修改 api/server.ts:新增 quota_remaining 字段,producer=服务端 types/api.ts → 消息 envelope 透传 → 客户端 normalize(snake→camel)→ consumer=runtime/quota.ts 写入」
296
304
  - 仅改内部实现、无对外字段变动时,说明列照常写「新增 xx 方法」即可,无需数据流。
297
305
 
298
306
  **跨仓变更写法**(本次变更涉及 local.yaml repos: 注册的其他仓时必读;纯主仓变更跳过):
299
- - 清单**按仓分段**:每个仓一个小节,标题格式固定 \`## <repo-key> 仓变更\`(如 \`## sub-grid-security 仓变更\`;可加编号前缀或(跨仓,X1-X4)这类括号备注后缀,其余后缀/写法解析不了)。plan-postcheck 文件覆盖对账按此段头解析仓归属,写成「前端仓 / backend」等其他标题 → 对账不上且会点名段头格式错误
300
- - 段内路径相对**该仓根**写(\`src/routes/x.js\`);❌ 禁止带仓库名前缀(\`sub-grid-security/src/routes/x.js\`)、❌ 禁止绝对路径/盘符路径(\`C:/repo/src/...\`)——后续 TaskCard 的 allowed_paths 与 review 对账(\`git -C <仓根> diff\` 产仓根相对路径)都用同一口径,前缀/绝对路径永不命中
307
+ - 清单**按仓分段**:每个仓一个小节,标题格式固定 \`## <repo-key> 仓变更\`(如 \`## user-service 仓变更\`;可加编号前缀或(跨仓,X1-X4)这类括号备注后缀,其余后缀/写法解析不了)。plan-postcheck 文件覆盖对账按此段头解析仓归属,写成「前端仓 / backend」等其他标题 → 对账不上且会点名段头格式错误
308
+ - 段内路径相对**该仓根**写(\`src/routes/x.js\`);❌ 禁止带仓库名前缀(\`<repo-key>/src/routes/x.js\`)、❌ 禁止绝对路径/盘符路径(\`C:/repo/src/...\`)——后续 TaskCard 的 allowed_paths 与 review 对账(\`git -C <仓根> diff\` 产仓根相对路径)都用同一口径,前缀/绝对路径永不命中
301
309
  - 主仓文件放在第一个段头之前(无段头区域 = main),或单独写 \`## main 仓变更\` 段
302
310
  - repo-key 必须是 local.yaml repos: 段已注册的键(未注册先跑 \`sillyspec local register-repo <key> <仓根路径>\`,main 隐式不用注册)
303
311
 
@@ -381,11 +389,11 @@ design.md 文件路径 + 自审结果
381
389
  ### 定位
382
390
  这是设计完成后的质量门,不是需求探索。目标不是继续发散,而是找出 design.md 内部、四件套之间、文档与外部约束之间的结构性矛盾。
383
391
 
384
- ### 审查执行方式(CLI 按变更规模判定,占位符由 run.js 注入)
392
+ ### 审查执行方式(CLI 按 ceremony_tier 风险定价,占位符由 run.js 注入)
385
393
  tier: {REVIEW_TIER}({REVIEW_TIER_REASON})
386
- - tier=self:当前 agent 直接执行下方交叉审查(小变更)
387
- - tier=independent:必须用 Agent tool 启动一个独立的设计审查子代理(独立上下文,不共享你的分析与倾向),子代理按下方"交叉审查模型"审查 design.md 并输出 review.json。review.json 产物契约(CLI Stage Review Gate 将硬校验,schema + 完整示例 + docHash 算法如下,照抄改值):
388
- 宿主环境无 Agent tool 可用(调用报 Unknown agent / Available agents: none)→ 不卡死:主代理切换为审查者角色自审替代,reviewerNotes 首行记录「降级:环境无子代理可用」,逐条结论附源码锚点(file:line 或 grep/read 证据)补偿独立性。
394
+ - tier=self(ceremony 档 S0/S1):当前 agent 按「仪式档位菜单」执行对应档轻仪交叉审查(S0=CLI 清单核验 / S1=CLI 清单核验+定向探针抽查)——轻仪是风险定价的正常形态而非降级,清单机械项一条不省
395
+ - tier=independent(ceremony 档 S2/S3):必须用 Agent tool 启动一个独立的设计审查子代理(独立上下文,不共享你的分析与倾向),子代理按下方"交叉审查模型"审查 design.md 并输出 review.json——S2=独立评审×1;S3=两轮独立评审(两轮独立子代理交叉,第二轮聚焦首轮未决项与新增面)。review.json 产物契约(CLI Stage Review Gate 将硬校验,schema + 完整示例 + docHash 算法如下,照抄改值):
396
+ 宿主环境无 Agent tool 可用(调用报 Unknown agent / Available agents: none)→ 不卡死(降级兜底,仅评审通道全不可用时):主代理切换为审查者角色自审替代,reviewerNotes 首行记录「降级:环境无子代理可用」,逐条结论附源码锚点(file:line 或 grep/read 证据)补偿独立性。
389
397
  {PRIOR_REVIEW_FACTS}
390
398
  {REVIEW_JSON_CONTRACT}
391
399
  子代理只产出 review + Unresolved Blockers,**是否调用 sillyspec run brainstorm --wait 仍由你(主 agent)根据其 verdict 决定**(子代理不直接操作 CLI 状态机)。
@@ -393,23 +401,27 @@ tier: {REVIEW_TIER}({REVIEW_TIER_REASON})
393
401
  - 时间盒收敛:审查子代理的调研是收敛动作不是发散——必读材料读完即逐条出结论并落盘 review.json;仅个别结论存疑时定向补证,禁止循环扩大核验面(连续读文件 10+ 次仍零结论 = 立即停止扩展、基于已读材料收敛)。
394
402
  - 写通道降级:子代理写操作持续被平台拒绝(session not in running turn 类故障)→ 重试 ≤3 次即停,把完整审查结论(含 review.json 全文)作为最终文本回传主代理,由主代理代为落盘并在 reviewerNotes 首行留痕「代落盘:子代理写通道故障」;禁止长时间空转重试。
395
403
 
404
+ ### 仪式档位菜单(ceremony_tier 按 risk 客观定价——注入的 tier 行已标注当前档)
405
+ - S0:CLI 清单核验——按下方交叉审查模型/交叉点清单机械逐项核对(零 token 发散),结论逐条附证据锚点,无需独立子代理
406
+ - S1:CLI 清单核验 + 定向探针抽查——机械项全查之外,对最高风险的 1-2 个交叉点做定向源码探针(读相关实现验证设计假设),无需独立子代理
407
+ - S2:独立评审×1——一个独立审查子代理完整执行下方交叉审查模型并产出 review.json
408
+ - S3:两轮独立评审 + Grill 深查——两个独立审查子代理分别审查(视角互补:一个查结构性矛盾/一致性,一个查可行性与外部约束),首轮未决项进第二轮复核;对 P0 歧义逐一压力测试(Grill 深查)
409
+ 当前档以注入的 ceremony_tier 行为准(run.js 注入 {REVIEW_TIER} 时随附菜单并标注);档位由 blast/span/friction 三轴客观计算,agent 自报只可升不可降——按当前档如实执行,不自行升仪或降仪。S2/S3 评审通道全不可用时按上方降级兜底执行并留 degraded 痕(reviewerNotes「降级:环境无子代理可用」);S0/S1 的 self 是档位定价的正常形态,不是降级。
410
+
396
411
  ### 默认行为
397
412
  1. 默认必须执行一次交叉审查;不要让用户凭主观判断决定"要不要 Grill"。
398
413
  2. 只有以下情况可以轻量跳过,并必须记录原因:
399
414
  - 用户明确要求 no-grill / 显式跳过
400
415
  - 文档是一页以内、单模块、无状态流转、无 schema/API/兼容策略变更
401
- - plan_level 明确为 none,且只改 1-2 个文件
416
+ - ceremony 档为 S0(客观定价已是最低仪式档)且只改 1-2 个文件——S0 的 CLI 清单核验仍要执行,跳过的只是发散深查
402
417
  3. 即使跳过,也要输出"Design Grill skipped"和原因,不能静默跳过。
403
418
 
404
- ### 输入材料
405
- 1. 必须读取完整 \`{SPEC_ROOT}/changes/<change-name>/design.md\`
406
- 2. 读取 proposal.md、requirements.md、tasks.md、decisions.md(如存在)
407
- 3. 读取 scan/module docs:
408
- - \`{SPEC_ROOT}/docs/<project>/scan/ARCHITECTURE.md\`
409
- - \`{SPEC_ROOT}/docs/<project>/scan/CONVENTIONS.md\`
410
- - \`{SPEC_ROOT}/docs/<project>/modules/_module-map.yaml\`
411
- - 命中的模块文档
412
- 4. 按 design.md 文件变更清单读取相关源码、测试、配置、schema 或样例数据;矛盾经常藏在设计与外部约束交叉处,素材宁可多读,不要只读摘要。
419
+ ### 输入材料(材料包基准面——CLI 注入素材半边+主代理补点名半边,2026-09-19-review-material-cli-wiring)
420
+ 1. **评审材料包(基准面——checklist 逐条只对本包作答)**:CLI 已机械组装注入下方(src/review-material-pack.js assembleStageReviewMaterials('grill-first'):design 要点 digest+文件清单);「五个交叉点」节留位——派发前由你补齐五点(每点 title+file:line 锚,如需源码片段按同文件 extractSnippets 口径附),留空则评审者按 cannot_verify 列缺件。tier=independent 时把补完的整包贴进子代理派发 prompt:
421
+ {REVIEW_MATERIALS}
422
+ 2. 包外文件可按需定向查证(列明查过的文件),**禁止全量扫读仓**——矛盾经常藏在交叉处,定向读那一处,不读整个仓。
423
+ 3. **自检首项(先于一切审查项)**:材料包是否足以逐条作答下方审查清单;不足以作答→cannot_verify+在 requiredEvidence 列缺件(缺什么列什么——这是包质量问题不是你的问题),不要靠全量扫读自救。
424
+ 4. decisions.md 全文可读(决策条目是审查对象本体,不属于扫读面)。
413
425
 
414
426
  ### 交叉审查模型
415
427
  按三层检查并输出 cross-check matrix:
@@ -479,6 +491,10 @@ status: passed | needs-user-input | blocked | skipped
479
491
  name: '生成规范文件',
480
492
  // 重命名自「用户确认并生成规范文件」(去确认门控:设计已在「分段展示设计」步确认过,末步再问纯冗余),老进度 completed 由迁移逻辑承接
481
493
  migratedFrom: ['用户确认并生成规范文件'],
494
+ // 前置失败清单声明(2026-09-18-preflight-slimming task-04,D-001@v1):本步 --done 消费
495
+ // brainstorm.design.* 门禁(含 design-file-list 文件清单核验与四件套规则)——事前清单与
496
+ // 事后门禁同源(validator 子集映射见 run/prompt.js PREFLIGHT_VALIDATORS 注释)。
497
+ preflightValidators: ['design-file-list', 'four-piece-rules'],
482
498
  prompt: `按变更规模生成规范文件并给出实现路径建议(设计已在前置步骤确认过,本步不再暂停等确认,生成后展示摘要即可)。
483
499
 
484
500
  ### 规模评估(展示前先做)