agentic-flow-kit 1.7.15 → 1.8.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 (206) hide show
  1. package/README.md +50 -8
  2. package/modules/hosts/claude/commands/wf-build.md +1 -1
  3. package/modules/hosts/claude/commands/wf-design.md +5 -4
  4. package/modules/hosts/claude/commands/wf-maintain.md +1 -1
  5. package/modules/hosts/claude/commands/wf-new-task.md +47 -25
  6. package/modules/hosts/claude/commands/wf-plan.md +1 -1
  7. package/modules/hosts/claude/commands/wf-review.md +2 -2
  8. package/modules/hosts/claude/commands/wf-sync-hosts.md +7 -13
  9. package/modules/hosts/claude/commands/wf-test.md +2 -2
  10. package/modules/hosts/codex/commands/wf-build.md +1 -1
  11. package/modules/hosts/codex/commands/wf-design.md +5 -4
  12. package/modules/hosts/codex/commands/wf-maintain.md +1 -1
  13. package/modules/hosts/codex/commands/wf-new-task.md +47 -25
  14. package/modules/hosts/codex/commands/wf-plan.md +1 -1
  15. package/modules/hosts/codex/commands/wf-review.md +2 -2
  16. package/modules/hosts/codex/commands/wf-sync-hosts.md +7 -13
  17. package/modules/hosts/codex/commands/wf-test.md +2 -2
  18. package/modules/hosts/cursor/commands/wf-build.md +1 -1
  19. package/modules/hosts/cursor/commands/wf-design.md +5 -4
  20. package/modules/hosts/cursor/commands/wf-maintain.md +1 -1
  21. package/modules/hosts/cursor/commands/wf-new-task.md +47 -25
  22. package/modules/hosts/cursor/commands/wf-plan.md +1 -1
  23. package/modules/hosts/cursor/commands/wf-review.md +2 -2
  24. package/modules/hosts/cursor/commands/wf-sync-hosts.md +7 -13
  25. package/modules/hosts/cursor/commands/wf-test.md +2 -2
  26. package/modules/hosts/opencode/commands/wf-build.md +1 -1
  27. package/modules/hosts/opencode/commands/wf-design.md +5 -4
  28. package/modules/hosts/opencode/commands/wf-maintain.md +1 -1
  29. package/modules/hosts/opencode/commands/wf-new-task.md +47 -25
  30. package/modules/hosts/opencode/commands/wf-plan.md +1 -1
  31. package/modules/hosts/opencode/commands/wf-review.md +2 -2
  32. package/modules/hosts/opencode/commands/wf-sync-hosts.md +7 -13
  33. package/modules/hosts/opencode/commands/wf-test.md +2 -2
  34. package/modules/hosts/trae/commands/wf-build.md +1 -1
  35. package/modules/hosts/trae/commands/wf-design.md +5 -4
  36. package/modules/hosts/trae/commands/wf-maintain.md +1 -1
  37. package/modules/hosts/trae/commands/wf-new-task.md +47 -25
  38. package/modules/hosts/trae/commands/wf-plan.md +1 -1
  39. package/modules/hosts/trae/commands/wf-review.md +2 -2
  40. package/modules/hosts/trae/commands/wf-sync-hosts.md +7 -13
  41. package/modules/hosts/trae/commands/wf-test.md +2 -2
  42. package/package.json +1 -1
  43. package/src/cli.mjs +47 -4
  44. package/src/doctor.mjs +48 -3
  45. package/src/executor.mjs +262 -0
  46. package/src/executor.test.mjs +295 -0
  47. package/src/fresh-init.test.mjs +1 -1
  48. package/src/init.mjs +27 -3
  49. package/src/profiles.mjs +10 -3
  50. package/src/render.mjs +3 -3
  51. package/src/rule-budget.test.mjs +2 -2
  52. package/src/stack-profile.test.mjs +6 -0
  53. package/src/sync-hosts.mjs +37 -43
  54. package/src/sync-hosts.test.mjs +67 -56
  55. package/src/validate.mjs +6 -1
  56. package/src/validate.test.mjs +1 -1
  57. package/templates/.gitattributes +12 -1
  58. package/templates/AGENTS.md +3 -3
  59. package/templates/_agents/commands/build.md +1 -1
  60. package/templates/_agents/commands/design.md +5 -4
  61. package/templates/_agents/commands/maintain.md +1 -1
  62. package/templates/_agents/commands/new-task.md +47 -25
  63. package/templates/_agents/commands/plan.md +1 -1
  64. package/templates/_agents/commands/review.md +2 -2
  65. package/templates/_agents/commands/sync-hosts.md +7 -13
  66. package/templates/_agents/commands/test.md +2 -2
  67. package/templates/_agents/control-surfaces.txt +1 -1
  68. package/templates/_agents/engine-lock.json +334 -0
  69. package/templates/_agents/hooks/check-index-incremental.mjs +48 -0
  70. package/templates/_agents/hooks/check-pairing-incremental.mjs +66 -0
  71. package/templates/_agents/hooks/check-wiki-ledger.mjs +30 -0
  72. package/templates/_agents/lane-surfaces.txt +5 -2
  73. package/templates/_agents/level-hints.txt +30 -0
  74. package/templates/_agents/loop/papercut-debt/DESIGN.md +112 -0
  75. package/templates/_agents/mechanism-registry.md +27 -0
  76. package/templates/_agents/rule-budgets.txt +2 -2
  77. package/templates/_agents/scripts/agg-delegations.cjs +21 -3
  78. package/templates/_agents/scripts/agg-delegations.test.mjs +5 -3
  79. package/templates/_agents/scripts/agg-gate-stats.mjs +3 -3
  80. package/templates/_agents/scripts/agg-judgments.mjs +80 -0
  81. package/templates/_agents/scripts/agg-judgments.test.mjs +45 -0
  82. package/templates/_agents/scripts/archive-sweep.mjs +233 -0
  83. package/templates/_agents/scripts/archive-sweep.test.mjs +129 -0
  84. package/templates/_agents/scripts/check-attribution.mjs +37 -0
  85. package/templates/_agents/scripts/check-convergence.mjs +119 -0
  86. package/templates/_agents/scripts/check-engine-integrity.mjs +59 -127
  87. package/templates/_agents/scripts/check-engine-integrity.test.mjs +33 -45
  88. package/templates/_agents/scripts/check-funnel.mjs +56 -0
  89. package/templates/_agents/scripts/check-hygiene.mjs +5 -5
  90. package/templates/_agents/scripts/check-index-incremental.test.mjs +73 -0
  91. package/templates/_agents/scripts/check-lane-surface.mjs +74 -25
  92. package/templates/_agents/scripts/check-lane-surface.test.mjs +71 -5
  93. package/templates/_agents/scripts/check-ledger-invariant.mjs +427 -78
  94. package/templates/_agents/scripts/check-ledger-invariant.test.mjs +438 -4
  95. package/templates/_agents/scripts/check-loop-rev.test.mjs +13 -8
  96. package/templates/_agents/scripts/check-loop-unit.test.mjs +8 -0
  97. package/templates/_agents/scripts/check-loop.mjs +264 -75
  98. package/templates/_agents/scripts/check-loop.test.mjs +587 -61
  99. package/templates/_agents/scripts/check-metric-claims.mjs +4 -2
  100. package/templates/_agents/scripts/check-prejudgment.mjs +59 -0
  101. package/templates/_agents/scripts/check-registry.mjs +45 -0
  102. package/templates/_agents/scripts/check-scope.mjs +96 -0
  103. package/templates/_agents/scripts/check-scope.test.mjs +96 -0
  104. package/templates/_agents/scripts/check-scripts-syntax.mjs +61 -0
  105. package/templates/_agents/scripts/check-scripts-syntax.test.mjs +72 -0
  106. package/templates/_agents/scripts/check-spec-verif.mjs +47 -0
  107. package/templates/_agents/scripts/compact-ledgers.mjs +97 -0
  108. package/templates/_agents/scripts/compact-ledgers.test.mjs +162 -0
  109. package/templates/_agents/scripts/confirm-doc.mjs +136 -31
  110. package/templates/_agents/scripts/confirm-doc.test.mjs +180 -4
  111. package/templates/_agents/scripts/doctor.test.mjs +44 -1
  112. package/templates/_agents/scripts/edit-face-check.mjs +5 -3
  113. package/templates/_agents/scripts/edit-face-check.test.mjs +5 -4
  114. package/templates/_agents/scripts/fill-intent.mjs +7 -5
  115. package/templates/_agents/scripts/fill-intent.test.mjs +19 -1
  116. package/templates/_agents/scripts/fill-plan.mjs +10 -6
  117. package/templates/_agents/scripts/fill-plan.test.mjs +19 -3
  118. package/templates/_agents/scripts/fill-spec.mjs +6 -3
  119. package/templates/_agents/scripts/fill-spec.test.mjs +12 -4
  120. package/templates/_agents/scripts/fixture-scripts.mjs +153 -0
  121. package/templates/_agents/scripts/fixture-scripts.test.mjs +112 -0
  122. package/templates/_agents/scripts/gate-checklist.mjs +10 -9
  123. package/templates/_agents/scripts/gate-checklist.test.mjs +2 -11
  124. package/templates/_agents/scripts/gate-seg.mjs +1 -1
  125. package/templates/_agents/scripts/gen-effective-policy.mjs +107 -0
  126. package/templates/_agents/scripts/gen-effective-policy.test.mjs +100 -0
  127. package/templates/_agents/scripts/gen-verify-evidence.mjs +107 -0
  128. package/templates/_agents/scripts/gen-verify-evidence.test.mjs +84 -0
  129. package/templates/_agents/scripts/gen-workflow-dashboard.mjs +16 -14
  130. package/templates/_agents/scripts/gen-workflow-dashboard.test.mjs +2 -1
  131. package/templates/_agents/scripts/gen-workflow-metrics.mjs +12 -36
  132. package/templates/_agents/scripts/git-core.mjs +45 -0
  133. package/templates/_agents/scripts/git-merge-jsonl.mjs +63 -0
  134. package/templates/_agents/scripts/git-merge-jsonl.test.mjs +125 -0
  135. package/templates/_agents/scripts/judgment-ledger.mjs +65 -0
  136. package/templates/_agents/scripts/judgment-ledger.test.mjs +129 -0
  137. package/templates/_agents/scripts/kb-search.mjs +19 -4
  138. package/templates/_agents/scripts/kb-search.test.mjs +33 -0
  139. package/templates/_agents/scripts/lane-surface-audit.mjs +143 -0
  140. package/templates/_agents/scripts/lane-surface-audit.test.mjs +93 -0
  141. package/templates/_agents/scripts/ledger-doc-chain.test.mjs +166 -0
  142. package/templates/_agents/scripts/ledger-loader.mjs +108 -0
  143. package/templates/_agents/scripts/ledger-loader.test.mjs +131 -0
  144. package/templates/_agents/scripts/ledger-prune.mjs +96 -0
  145. package/templates/_agents/scripts/ledger-prune.test.mjs +120 -0
  146. package/templates/_agents/scripts/ledger-shards.mjs +307 -0
  147. package/templates/_agents/scripts/ledger-shards.test.mjs +339 -0
  148. package/templates/_agents/scripts/ledger-ts-monotonic.test.mjs +135 -0
  149. package/templates/_agents/scripts/level-criteria.mjs +231 -0
  150. package/templates/_agents/scripts/level-criteria.test.mjs +281 -0
  151. package/templates/_agents/scripts/papercut-debt-controller.mjs +141 -0
  152. package/templates/_agents/scripts/papercut-debt-controller.test.mjs +127 -0
  153. package/templates/_agents/scripts/papercut-debt-sensor.mjs +170 -0
  154. package/templates/_agents/scripts/papercut-debt-sensor.test.mjs +146 -0
  155. package/templates/_agents/scripts/pipeline-run.mjs +10 -24
  156. package/templates/_agents/scripts/pipeline-run.test.mjs +22 -42
  157. package/templates/_agents/scripts/policy.mjs +75 -0
  158. package/templates/_agents/scripts/resolve-merge-conflicts.mjs +110 -0
  159. package/templates/_agents/scripts/resolve-merge-conflicts.test.mjs +87 -0
  160. package/templates/_agents/scripts/rewrite-file.mjs +133 -0
  161. package/templates/_agents/scripts/rewrite-file.test.mjs +148 -0
  162. package/templates/_agents/scripts/rule-budget.mjs +83 -0
  163. package/templates/_agents/scripts/run-tests.mjs +99 -0
  164. package/templates/_agents/scripts/run-tests.test.mjs +105 -0
  165. package/templates/_agents/scripts/solidify-task.test.mjs +1 -1
  166. package/templates/_agents/scripts/stage-gates.mjs +38 -21
  167. package/templates/_agents/scripts/trust-mode.mjs +4 -4
  168. package/templates/_agents/scripts/trust-mode.test.mjs +1 -1
  169. package/templates/_agents/scripts/verify.mjs +135 -58
  170. package/templates/_agents/scripts/verify.test.mjs +156 -47
  171. package/templates/_agents/scripts/wf-journal.mjs +8 -4
  172. package/templates/_agents/scripts/wf-journal.test.mjs +1 -1
  173. package/templates/_agents/scripts/wiki-search.test.mjs +5 -6
  174. package/templates/_agents/scripts/workflow-board-server.test.mjs +2 -2
  175. package/templates/_agents/scripts/workflow-enums.test.mjs +14 -5
  176. package/templates/_agents/scripts/workflows-check.mjs +1 -1
  177. package/templates/_agents/scripts-test-exempt.txt +1 -0
  178. package/templates/_agents/skills/papercut-fix/SKILL.md +60 -0
  179. package/templates/_agents/skills/papercut-fix/references/response-template.md +81 -0
  180. package/templates/_agents/workflow-enums.txt +8 -1
  181. package/templates/_agents/workflows/pipeline-closing.md +4 -5
  182. package/templates/_githooks/pre-commit +109 -120
  183. package/templates/_githooks/pre-merge-commit +27 -23
  184. package/templates/_githooks/pre-push +7 -6
  185. package/templates/_github/workflows/kit-ci.yml +4 -3
  186. package/templates/workflow/README.md +23 -5
  187. package/templates/workflow/incidents/_TEMPLATE.md +4 -1
  188. package/templates/workflow/intents/_TEMPLATE.md +4 -2
  189. package/templates/workflow/papercuts.md +2 -0
  190. package/templates/workflow/plans/_TEMPLATE.md +10 -5
  191. package/templates/workflow/regression-checklist.md +1 -0
  192. package/templates/workflow/specs/_TEMPLATE.md +17 -1
  193. package/modules/hosts/claude/commands/wf-source-sync-check.md +0 -89
  194. package/modules/hosts/codex/commands/wf-source-sync-check.md +0 -89
  195. package/modules/hosts/cursor/commands/wf-source-sync-check.md +0 -89
  196. package/modules/hosts/opencode/commands/wf-source-sync-check.md +0 -89
  197. package/modules/hosts/trae/commands/wf-source-sync-check.md +0 -90
  198. package/templates/_agents/commands/source-sync-check.md +0 -103
  199. package/templates/_agents/hooks/check-pairing-incremental.sh +0 -43
  200. package/templates/_agents/hooks/check-wiki-ledger.sh +0 -18
  201. package/templates/_agents/scripts/check-ledger.mjs +0 -109
  202. package/templates/_agents/scripts/check-ledger.test.mjs +0 -199
  203. package/templates/_agents/scripts/rule-budget.sh +0 -74
  204. package/templates/_agents/scripts/source-sync-check.mjs +0 -156
  205. package/templates/_agents/scripts/source-sync-check.test.mjs +0 -286
  206. package/templates/_agents/workflows/source-sync-repair.md +0 -30
@@ -7,6 +7,8 @@
7
7
  // L2/L3 时同名 spec)已确认;未过 → exit 2 + 未落盘未记账(S19-S22)
8
8
  // ⑧ done 前置门(2026-09-30 confirm-gate-approved-history):→done 须 git 历史已留 approved 态
9
9
  // (先 done 后提交 / 两跳同批提交 → 拒绝);非 git / legacy / 存量豁免声明 → 跳过(S25-S29)
10
+ // ⑨ 关单即归档(2026-10-13 archive-on-close,B9 自源仓回灌):终态家族即时 git mv 进 archive、
11
+ // 未涉及家族不越权清理、无归档约定不启用、CONFIRM_DOC_NO_ARCHIVE 逃生门(S43-S45)
10
12
  // 用法:node templates/_agents/scripts/confirm-doc.test.mjs(npm test 随跑)
11
13
  import fs from 'node:fs';
12
14
  import os from 'node:os';
@@ -615,17 +617,17 @@ const gitHead = (root) => {
615
617
  fs.rmSync(root, { recursive: true, force: true });
616
618
  }
617
619
 
618
- // ---- S30【协作道批量】--batch:delegated 多份 L0/L1 一次代录 → 全部落态 + 台账 brief:true(2026-09-30 hybrid-governance-risk-lanes)----
620
+ // ---- S30【协作道批量】--batch:delegated 多份 L1 一次代录 → 全部落态 + 台账 brief:true(2026-09-30 hybrid-governance-risk-lanes)----
619
621
  {
620
622
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'confirm-bt1-'));
621
623
  fs.mkdirSync(path.join(root, 'workflow', 'intents'), { recursive: true });
622
624
  fs.writeFileSync(path.join(root, 'workflow', 'intents', '2026-09-30-bt1.md'), '---\n状态: draft\n级别: L1\n日期: 2026-09-30\n---\n# I1\n');
623
- fs.writeFileSync(path.join(root, 'workflow', 'intents', '2026-09-30-bt2.md'), '---\n状态: draft\n级别: L0\n日期: 2026-09-30\n---\n# I2\n');
625
+ fs.writeFileSync(path.join(root, 'workflow', 'intents', '2026-09-30-bt2.md'), '---\n状态: draft\n级别: L1\n日期: 2026-09-30\n---\n# I2\n');
624
626
  const r = spawnSync(process.execPath, [CLI, 'workflow/intents/2026-09-30-bt1.md', 'workflow/intents/2026-09-30-bt2.md', '--delegated', '批量放行', '--batch'], { cwd: root, encoding: 'utf8' });
625
627
  const led = readLedgerOf(root);
626
628
  const st1 = fs.readFileSync(path.join(root, 'workflow', 'intents', '2026-09-30-bt1.md'), 'utf8');
627
629
  const st2 = fs.readFileSync(path.join(root, 'workflow', 'intents', '2026-09-30-bt2.md'), 'utf8');
628
- check('S30 --batch:2 份 L0/L1 一次代录 → 全部 approved + 台账 brief:true / of=2 / seq 递增',
630
+ check('S30 --batch:2 份 L1 一次代录 → 全部 approved + 台账 brief:true / of=2 / seq 递增',
629
631
  r.status === 0 && led.length === 2
630
632
  && led.every((e) => e.brief === true && e.source === 'chat-delegated' && e.of === 2 && e.stage === 'approved')
631
633
  && led[0].seq === 1 && led[1].seq === 2
@@ -694,7 +696,7 @@ const gitHead = (root) => {
694
696
  fs.rmSync(root, { recursive: true, force: true });
695
697
  }
696
698
 
697
- // ---- S33 简洁审计:单份 delegated L0/L1(无 --batch)→ 台账 brief:true + 紧凑输出;L2 份不带 brief ----
699
+ // ---- S33 简洁审计:单份 delegated L1(无 --batch)→ 台账 brief:true + 紧凑输出;L2 份不带 brief ----
698
700
  {
699
701
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'confirm-bt4-'));
700
702
  fs.mkdirSync(path.join(root, 'workflow', 'intents'), { recursive: true });
@@ -880,5 +882,179 @@ const writeTrust = (root, cfg) => {
880
882
  fs.rmSync(root, { recursive: true, force: true });
881
883
  }
882
884
 
885
+ // ---- S42 分片默认写入口径(2026-10-13 ledger-shard-default):CLI 默认写分片 / 两种回退 / 存量回退 / 显式拒 ----
886
+ {
887
+ const mkFixture = (withV8) => {
888
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'confirm-shard-'));
889
+ const docP = path.join(root, 'workflow', 'intents');
890
+ fs.mkdirSync(docP, { recursive: true });
891
+ fs.writeFileSync(path.join(docP, '2026-10-13-s.md'), '---\n状态: draft\n级别: L1\n---\n# I\n');
892
+ if (withV8) {
893
+ fs.mkdirSync(path.join(root, '.agents'), { recursive: true });
894
+ fs.writeFileSync(path.join(root, '.agents', 'kit.json'), JSON.stringify({ policyVersion: 8 }), 'utf8');
895
+ }
896
+ return root;
897
+ };
898
+ const shardPathOf = (root) => path.join(root, '.agents', 'confirmations.d', '2026-10-13-s.jsonl');
899
+ const mainPathOf = (root) => path.join(root, '.agents', 'confirmations.jsonl');
900
+ const readRows = (p) => (fs.existsSync(p) ? fs.readFileSync(p, 'utf8').split('\n').filter(Boolean).map((l) => JSON.parse(l)) : []);
901
+ const spawnConfirm = (root, args, env = {}) => spawnSync(process.execPath, [CLI, 'workflow/intents/2026-10-13-s.md', '--delegated', '可以', ...args], { cwd: root, encoding: 'utf8', env: { ...process.env, ...env } });
902
+
903
+ // S42① 默认(无旗标)+ doc 链已激活(policyVersion 8)→ 写分片,主台账零行
904
+ {
905
+ const root = mkFixture(true);
906
+ const r = spawnConfirm(root, []);
907
+ check('S42① 默认写分片:exit 0 + 行落 confirmations.d/<doc基名>.jsonl + 主台账零行',
908
+ r.status === 0 && readRows(shardPathOf(root)).length === 1
909
+ && readRows(shardPathOf(root))[0].source === 'chat-delegated'
910
+ && readRows(mainPathOf(root)).length === 0,
911
+ JSON.stringify({ status: r.status, stderr: r.stderr }));
912
+ fs.rmSync(root, { recursive: true, force: true });
913
+ }
914
+ // S42② --no-shard → 显式回退主台账
915
+ {
916
+ const root = mkFixture(true);
917
+ const r = spawnConfirm(root, ['--no-shard']);
918
+ check('S42② --no-shard 回退主台账:exit 0 + 行落 confirmations.jsonl + 分片不存在',
919
+ r.status === 0 && readRows(mainPathOf(root)).length === 1 && !fs.existsSync(shardPathOf(root)),
920
+ JSON.stringify({ status: r.status, stderr: r.stderr }));
921
+ fs.rmSync(root, { recursive: true, force: true });
922
+ }
923
+ // S42③ CONFIRM_SHARDED=0 → 环境变量回退主台账
924
+ {
925
+ const root = mkFixture(true);
926
+ const r = spawnConfirm(root, [], { CONFIRM_SHARDED: '0' });
927
+ check('S42③ CONFIRM_SHARDED=0 回退主台账:exit 0 + 行落 confirmations.jsonl + 分片不存在',
928
+ r.status === 0 && readRows(mainPathOf(root)).length === 1 && !fs.existsSync(shardPathOf(root)),
929
+ JSON.stringify({ status: r.status, stderr: r.stderr }));
930
+ fs.rmSync(root, { recursive: true, force: true });
931
+ }
932
+ // S42④ 存量装户(无 kit.json → policyVersion 1,doc 链未激活)默认 → 自动回退主台账 + ℹ️ 升级指引
933
+ {
934
+ const root = mkFixture(false);
935
+ const r = spawnConfirm(root, []);
936
+ check('S42④ 存量装户默认:自动回退主台账(exit 0 + 行落 confirmations.jsonl)+ stderr 带 policyVersion: 8 升级指引 + 分片不存在',
937
+ r.status === 0 && readRows(mainPathOf(root)).length === 1
938
+ && /policyVersion: 8/.test(r.stderr) && !fs.existsSync(shardPathOf(root)),
939
+ JSON.stringify({ status: r.status, stderr: r.stderr }));
940
+ fs.rmSync(root, { recursive: true, force: true });
941
+ }
942
+ // S42⑤ 存量装户显式 --shard → 参数期直接拒(原口径不变)
943
+ {
944
+ const root = mkFixture(false);
945
+ const r = spawnConfirm(root, ['--shard']);
946
+ check('S42⑤ 显式 --shard 缺 doc 链锚:exit 1 + 报错带修法 + 零落账(文档/台账均未动)',
947
+ r.status === 1 && /--shard 需要台账 doc 链口径/.test(r.stderr)
948
+ && !fs.existsSync(shardPathOf(root)) && !fs.existsSync(mainPathOf(root)),
949
+ JSON.stringify({ status: r.status, stderr: r.stderr }));
950
+ fs.rmSync(root, { recursive: true, force: true });
951
+ }
952
+ // S42⑥ --shard 与 --no-shard 互斥 → 用法错误
953
+ {
954
+ const root = mkFixture(true);
955
+ const r = spawnConfirm(root, ['--shard', '--no-shard']);
956
+ check('S42⑥ --shard 与 --no-shard 互斥:exit 1 + 用法提示 + 零落账',
957
+ r.status === 1 && /互斥/.test(r.stderr) && !fs.existsSync(shardPathOf(root)) && !fs.existsSync(mainPathOf(root)),
958
+ JSON.stringify({ status: r.status, stderr: r.stderr }));
959
+ fs.rmSync(root, { recursive: true, force: true });
960
+ }
961
+ }
962
+
963
+ // ---- S43 关单即归档(2026-10-13 archive-on-close;B9 自源仓回灌随件测试):终态家族超窗 → git mv 进 archive ----
964
+ // 需真实 git 仓库(归档走 git mv);家族原子:intent+plan 同进同退。月份断言锚定「实时今月」——
965
+ // 与源仓同口径(台账终态锚 = 实时 now,归档月 = 家族最晚终态月),跨月运行需随今月更新断言。
966
+ {
967
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'confirm-arch-'));
968
+ const git = (...a) => spawnSync(process.platform === 'win32' ? 'git.exe' : 'git', a, { cwd: root, encoding: 'utf8' });
969
+ git('init', '-q');
970
+ git('config', 'user.email', 't@t');
971
+ git('config', 'user.name', 't');
972
+ const ip = path.join(root, 'workflow', 'intents');
973
+ const pp = path.join(root, 'workflow', 'plans');
974
+ fs.mkdirSync(ip, { recursive: true });
975
+ fs.mkdirSync(pp, { recursive: true });
976
+ fs.mkdirSync(path.join(root, 'workflow', 'archive'), { recursive: true }); // 归档约定在场(启用条件)
977
+ // 家族:intent(本次关单)+ plan(已 done 且正文变更在 30 天前 → 超窗)
978
+ fs.writeFileSync(path.join(ip, '2026-09-01-fam.md'), '---\n状态: approved\n级别: L1\n确认指纹: aaaa1111aaaa1111\n---\n# I\n\n## 验收标准(可测试)\n- [x] 通过(证据:测试全绿)\n', 'utf8');
979
+ fs.writeFileSync(path.join(pp, '2026-09-01-fam.md'), '---\n状态: done\n级别: L1\n---\n# P\n\n## 改动方案\n- 正文\n', 'utf8');
980
+ git('add', '-A');
981
+ spawnSync(process.platform === 'win32' ? 'git.exe' : 'git', ['commit', '-q', '-m', 'c1'], { cwd: root, env: { ...process.env, GIT_AUTHOR_DATE: '2026-09-01T10:00:00+08:00', GIT_COMMITTER_DATE: '2026-09-01T10:00:00+08:00' } });
982
+ const r = spawnSync(process.execPath, [CLI, 'workflow/intents/2026-09-01-fam.md', '--delegated', '可以'],
983
+ { cwd: root, encoding: 'utf8', env: { ...process.env, CONFIRM_DOC_TEST_NOW: '2026-10-13' } });
984
+ const intentArchived = fs.existsSync(path.join(root, 'workflow', 'archive', '2026-10', 'intents', '2026-09-01-fam.md'));
985
+ const planArchived = fs.existsSync(path.join(root, 'workflow', 'archive', '2026-10', 'plans', '2026-09-01-fam.md'));
986
+ check('S43 关单即归档:终态家族整族 git mv 进 archive(intent+plan 同批)',
987
+ r.status === 0 && intentArchived && planArchived && /📦 归档/.test(r.stdout),
988
+ JSON.stringify({ status: r.status, stdout: String(r.stdout).slice(-300), stderr: String(r.stderr).slice(-200), intentArchived, planArchived }));
989
+ check('S43b 归档后活跃目录不再留该家族', !fs.existsSync(path.join(ip, '2026-09-01-fam.md')) && !fs.existsSync(path.join(pp, '2026-09-01-fam.md')));
990
+ fs.rmSync(root, { recursive: true, force: true });
991
+ }
992
+
993
+ // ---- S44 归档边界:本次确认的家族即时归档(窗口对其冗余);未涉及的家族不越权清理 ----
994
+ {
995
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'confirm-arch2-'));
996
+ const git = (...a) => spawnSync(process.platform === 'win32' ? 'git.exe' : 'git', a, { cwd: root, encoding: 'utf8' });
997
+ git('init', '-q');
998
+ git('config', 'user.email', 't@t');
999
+ git('config', 'user.name', 't');
1000
+ const ip = path.join(root, 'workflow', 'intents');
1001
+ fs.mkdirSync(ip, { recursive: true });
1002
+ fs.mkdirSync(path.join(root, 'workflow', 'archive'), { recursive: true }); // 归档约定在场(启用条件)
1003
+ // 本次确认的家族(刚落终态 → 即时归档,窗口不适用)
1004
+ fs.writeFileSync(path.join(ip, '2026-10-12-fresh.md'), '---\n状态: approved\n级别: L1\n确认指纹: bbbb2222bbbb2222\n---\n# I\n\n## 验收标准(可测试)\n- [x] 通过(证据:测试全绿)\n', 'utf8');
1005
+ // 未涉及的家族(同为终态、超窗、零引用——但不在本次确认范围,须留守)
1006
+ fs.writeFileSync(path.join(ip, '2026-09-01-other.md'), '---\n状态: done\n级别: L1\n---\n# I2\n\n## 验收标准(可测试)\n- [x] 通过(证据:测试全绿)\n', 'utf8');
1007
+ git('add', '-A');
1008
+ spawnSync(process.platform === 'win32' ? 'git.exe' : 'git', ['commit', '-q', '-m', 'c1'], { cwd: root, env: { ...process.env, GIT_AUTHOR_DATE: '2026-10-12T10:00:00+08:00', GIT_COMMITTER_DATE: '2026-10-12T10:00:00+08:00' } });
1009
+ const r = spawnSync(process.execPath, [CLI, 'workflow/intents/2026-10-12-fresh.md', '--delegated', '可以'],
1010
+ { cwd: root, encoding: 'utf8', env: { ...process.env, CONFIRM_DOC_TEST_NOW: '2026-10-13' } });
1011
+ check('S44 本次确认家族即时归档(窗口对刚过确认门的终态件冗余)',
1012
+ r.status === 0 && fs.existsSync(path.join(root, 'workflow', 'archive', '2026-10', 'intents', '2026-10-12-fresh.md')) && /📦 归档/.test(r.stdout),
1013
+ JSON.stringify({ status: r.status, stdout: String(r.stdout).slice(-200), stderr: String(r.stderr).slice(-200) }));
1014
+ check('S44b 未涉及的家族不越权清理(历史积压走 archive-sweep 手工批次)',
1015
+ fs.existsSync(path.join(ip, '2026-09-01-other.md')),
1016
+ JSON.stringify({ exists: fs.existsSync(path.join(ip, '2026-09-01-other.md')) }));
1017
+ fs.rmSync(root, { recursive: true, force: true });
1018
+ }
1019
+
1020
+ // ---- S45 归档约定缺席 → 不启用(未采纳归档的仓库不凭空造目录);env 逃生门 CONFIRM_DOC_NO_ARCHIVE ----
1021
+ {
1022
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'confirm-arch3-'));
1023
+ const git = (...a) => spawnSync(process.platform === 'win32' ? 'git.exe' : 'git', a, { cwd: root, encoding: 'utf8' });
1024
+ git('init', '-q');
1025
+ git('config', 'user.email', 't@t');
1026
+ git('config', 'user.name', 't');
1027
+ const ip = path.join(root, 'workflow', 'intents');
1028
+ fs.mkdirSync(ip, { recursive: true });
1029
+ fs.writeFileSync(path.join(ip, '2026-09-01-noconv.md'), '---\n状态: approved\n级别: L1\n确认指纹: cccc3333cccc3333\n---\n# I\n\n## 验收标准(可测试)\n- [x] 通过(证据:测试全绿)\n', 'utf8');
1030
+ git('add', '-A');
1031
+ spawnSync(process.platform === 'win32' ? 'git.exe' : 'git', ['commit', '-q', '-m', 'c1'], { cwd: root, env: { ...process.env, GIT_AUTHOR_DATE: '2026-09-01T10:00:00+08:00', GIT_COMMITTER_DATE: '2026-09-01T10:00:00+08:00' } });
1032
+ const r = spawnSync(process.execPath, [CLI, 'workflow/intents/2026-09-01-noconv.md', '--delegated', '可以'],
1033
+ { cwd: root, encoding: 'utf8', env: { ...process.env, CONFIRM_DOC_TEST_NOW: '2026-10-13' } });
1034
+ check('S45 无 workflow/archive/ 约定 → 不归档、不造目录',
1035
+ r.status === 0 && fs.existsSync(path.join(ip, '2026-09-01-noconv.md')) && !fs.existsSync(path.join(root, 'workflow', 'archive')) && !/📦 归档/.test(r.stdout),
1036
+ JSON.stringify({ status: r.status, stdout: String(r.stdout).slice(-200) }));
1037
+ // S45b 有归档约定 + CONFIRM_DOC_NO_ARCHIVE=1 → 逃生门生效(关单不归档)
1038
+ const root2 = fs.mkdtempSync(path.join(os.tmpdir(), 'confirm-arch4-'));
1039
+ const git2 = (...a) => spawnSync(process.platform === 'win32' ? 'git.exe' : 'git', a, { cwd: root2, encoding: 'utf8' });
1040
+ git2('init', '-q');
1041
+ git2('config', 'user.email', 't@t');
1042
+ git2('config', 'user.name', 't');
1043
+ const ip2 = path.join(root2, 'workflow', 'intents');
1044
+ fs.mkdirSync(ip2, { recursive: true });
1045
+ fs.mkdirSync(path.join(root2, 'workflow', 'archive'), { recursive: true });
1046
+ fs.writeFileSync(path.join(ip2, '2026-09-01-optout.md'), '---\n状态: approved\n级别: L1\n确认指纹: dddd4444dddd4444\n---\n# I\n\n## 验收标准(可测试)\n- [x] 通过(证据:测试全绿)\n', 'utf8');
1047
+ git2('add', '-A');
1048
+ spawnSync(process.platform === 'win32' ? 'git.exe' : 'git', ['commit', '-q', '-m', 'c1'], { cwd: root2, env: { ...process.env, GIT_AUTHOR_DATE: '2026-09-01T10:00:00+08:00', GIT_COMMITTER_DATE: '2026-09-01T10:00:00+08:00' } });
1049
+ const r2 = spawnSync(process.execPath, [CLI, 'workflow/intents/2026-09-01-optout.md', '--delegated', '可以'],
1050
+ { cwd: root2, encoding: 'utf8', env: { ...process.env, CONFIRM_DOC_TEST_NOW: '2026-10-13', CONFIRM_DOC_NO_ARCHIVE: '1' } });
1051
+ check('S45b CONFIRM_DOC_NO_ARCHIVE 逃生门:关单成功且不归档(状态已置 done)',
1052
+ r2.status === 0 && /状态: done/.test(fs.readFileSync(path.join(ip2, '2026-09-01-optout.md'), 'utf8'))
1053
+ && !fs.existsSync(path.join(root2, 'workflow', 'archive', 'intents')) && !/📦 归档/.test(r2.stdout),
1054
+ JSON.stringify({ status: r2.status, stdout: String(r2.stdout).slice(-200) }));
1055
+ fs.rmSync(root, { recursive: true, force: true });
1056
+ fs.rmSync(root2, { recursive: true, force: true });
1057
+ }
1058
+
883
1059
  console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
884
1060
  process.exit(fail ? 1 : 0);
@@ -32,7 +32,7 @@ if (!fs.existsSync(SRC)) {
32
32
  console.error(`doctor.test.mjs:SKIP——未找到 ${SRC}(装户环境无包源 src/;本套件仅在包源仓库/dogfooding 跑 npm test 时有意义)`);
33
33
  process.exit(0);
34
34
  }
35
- const { checkOwnedDrift, checkAdapterDrift, checkLedgerCoverage, checkDelegationSnapshotFreshness, checkGateAnchors, checkTemplateDrift, gateTrustOf, GATE_TRUST_LEVELS, GATE_TRUST_DEFAULT } = await import(pathToFileURL(SRC).href);
35
+ const { checkOwnedDrift, checkAdapterDrift, checkLedgerCoverage, checkDelegationSnapshotFreshness, checkGateAnchors, checkTemplateDrift, checkLevelHints, gateTrustOf, GATE_TRUST_LEVELS, GATE_TRUST_DEFAULT } = await import(pathToFileURL(SRC).href);
36
36
 
37
37
  let pass = 0;
38
38
  let fail = 0;
@@ -723,5 +723,48 @@ const GATE_BODY = '// gate v1\n';
723
723
  }
724
724
  }
725
725
 
726
+ // --- 场景 37-40:checkLevelHints(2026-10-13 hint-doctor)---
727
+ // level-hints 配置此前**只**在 flow-kit route 时才校验,而 route 是可选调用 → 写错的清单能静默存活。
728
+ // 本组直测三态:旧装户无判据单源 → skipped;配置合法(含默认空)→ ok;非法档位/ERE → ok=false。
729
+ {
730
+ const mkHintsRoot = (hintsText) => {
731
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'doctor-hints-'));
732
+ fs.mkdirSync(path.join(root, '.agents', 'scripts'), { recursive: true });
733
+ fs.copyFileSync(path.join(ROOT, 'templates/_agents/scripts/level-criteria.mjs'),
734
+ path.join(root, '.agents/scripts/level-criteria.mjs'));
735
+ if (hintsText !== null) fs.writeFileSync(path.join(root, '.agents/level-hints.txt'), hintsText);
736
+ return root;
737
+ };
738
+
739
+ {
740
+ const r = mkHintsRoot('L2 ^src/contracts/\nL3 ^src/db/migrations/.*\\.sql$\n');
741
+ const out = checkLevelHints(r);
742
+ check('场景 37:合法 hints → ok 且报条数与档位',
743
+ out.skipped === false && out.ok === true && /2 条有效/.test(out.note) && /L2/.test(out.note) && /L3/.test(out.note),
744
+ JSON.stringify(out));
745
+ fs.rmSync(r, { recursive: true, force: true });
746
+ }
747
+ {
748
+ const r = mkHintsRoot(null); // 缺文件=默认空=纯语义定级
749
+ const out = checkLevelHints(r);
750
+ check('场景 38:清单缺失(默认空)→ ok 且明示未配置', out.ok === true && out.skipped === false && out.note.length > 0, JSON.stringify(out));
751
+ fs.rmSync(r, { recursive: true, force: true });
752
+ }
753
+ {
754
+ const r = mkHintsRoot('L3 ^src/db/[bad(\n'); // 非法 ERE
755
+ const out = checkLevelHints(r);
756
+ check('场景 39:非法 ERE → ok=false 且带清单路径与行号',
757
+ out.ok === false && out.skipped === false && /level-hints/.test(out.detail) && /第 \d+ 行/.test(out.detail), JSON.stringify(out));
758
+ fs.rmSync(r, { recursive: true, force: true });
759
+ }
760
+ {
761
+ const r = fs.mkdtempSync(path.join(os.tmpdir(), 'doctor-hints-old-')); // 无判据单源=旧装户
762
+ fs.mkdirSync(path.join(r, '.agents'), { recursive: true });
763
+ const out = checkLevelHints(r);
764
+ check('场景 40:无 level-criteria.mjs(1.7.15 前旧装户)→ skipped 不产出告警', out.skipped === true, JSON.stringify(out));
765
+ fs.rmSync(r, { recursive: true, force: true });
766
+ }
767
+ }
768
+
726
769
  console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
727
770
  process.exit(fail ? 1 : 0);
@@ -4,7 +4,7 @@
4
4
  // 工作树编辑期零反馈(实测:常驻面重构砍掉阶段索引,到提交才被检查 7 报警)。本脚本把判定左移到编辑时。
5
5
  // 判定零复刻铁律:闭环判定**单源**复用 check-loop——以 CHECK_LOOP_ROOT=<root>(全扫模式,扫工作树
6
6
  // 非 HEAD)子进程消费其裁决,本脚本不实现任何门判定;唯一自算是常驻面字节预算 advisory(工作树
7
- // LF 字节 vs rule-budgets.txt 上限——提交门口径走 git index/HEAD(rule-budget.sh),两者判据不同面,
7
+ // LF 字节 vs rule-budgets.txt 上限——提交门口径走 git index/HEAD(rule-budget.mjs),两者判据不同面,
8
8
  // 此处为编辑时 advisory,冲突时以提交门为准)。
9
9
  // exit 语义:check-loop hard(exit≠0)→ 2;预算 advisory 超限 → 1;其余 → 0。
10
10
  // WARN **不进 exit**(打印明细供 AI/人对照)——绝对 WARN 计数会让带存量 advisory 的仓库恒非零
@@ -53,9 +53,11 @@ export function budgetAdvisory(files, budgetsText) {
53
53
  }
54
54
 
55
55
  // runCheck:工作树面 check-loop(CHECK_LOOP_ROOT 全扫)。返回 { code, output, ms }。
56
+ // --all 显式全量(2026-10-13 分道后裸跑=默认硬门):本工具的动机就是编辑期暴露卫生面 WARN
57
+ // (如阶段索引漂移——头部动机段),硬门子集满足不了,须显式声明全量。
56
58
  function runCheck(root, scriptDir) {
57
59
  const t0 = Date.now();
58
- const r = spawnSync(process.execPath, [path.join(scriptDir, 'check-loop.mjs')], {
60
+ const r = spawnSync(process.execPath, [path.join(scriptDir, 'check-loop.mjs'), '--all'], {
59
61
  cwd: root, encoding: 'utf8', windowsHide: true,
60
62
  env: { ...process.env, CHECK_LOOP_ROOT: root },
61
63
  });
@@ -84,7 +86,7 @@ export function main(root = process.cwd(), scriptDir = path.dirname(fileURLToPat
84
86
  if (/\[WARN /.test(line)) console.log(' ' + line.trim().slice(0, 200));
85
87
  }
86
88
  for (const { rel, limit, actual } of over) {
87
- console.log(` ⚠️ 预算 advisory:${rel} 工作树 ${actual}B > 上限 ${limit}B(编辑时口径;提交门以 rule-budget.sh 为准)`);
89
+ console.log(` ⚠️ 预算 advisory:${rel} 工作树 ${actual}B > 上限 ${limit}B(编辑时口径;提交门以 rule-budget.mjs 为准)`);
88
90
  }
89
91
  if (ck.code !== 0) {
90
92
  console.error('❌ 闭环判定 hard 未过——先修复再提交(判定单源 check-loop,详见上方输出)');
@@ -36,16 +36,17 @@ check('globToRe:目录前缀', globToRe('.agents/cache/').test('.agents/cache/
36
36
  }
37
37
 
38
38
  // ---- ② 端到端 fixture ----
39
- const SCRIPTS = ['check-loop.mjs', 'policy.mjs', 'stage-gates.mjs', 'check-metric-claims.mjs', 'check-hygiene.mjs', 'check-stage-index.mjs', 'gate-seg.mjs', 'workflow-enums.mjs', 'edit-face-check.mjs'];
39
+ // 夹具闭包(2026-10-12 fixture-closure-rootfix):此前手写 SCRIPTS 枚举,漏拷 ledger-loader / ledger-prune
40
+ // 就让 check-loop import 失败 → 门禁静默不跑 → **假绿**。改为按入口自动求传递闭包,新增依赖自动跟随。
41
+ const { copyScriptClosure, copyAgentsFiles } = await import('./fixture-scripts.mjs');
40
42
  // 引用完整性哑文件(检查 4/6/7 的存在性判据——避免 fixture 结构性 WARN 淹没断言面)
41
43
  const REF_COMMANDS = ['new-task', 'plan', 'design', 'build', 'test', 'deploy', 'maintain', 'review'];
42
44
  const REF_ROLES = ['implementer', 'independent-reviewer', 'ui-verifier'];
43
45
  function mkFixture({ breakStageIndex, overBudget }) {
44
46
  const fx = fs.mkdtempSync(path.join(os.tmpdir(), 'efc-'));
45
47
  fs.mkdirSync(path.join(fx, '.agents', 'scripts'), { recursive: true });
46
- for (const f of SCRIPTS) fs.copyFileSync(path.join(SRC, f), path.join(fx, '.agents', 'scripts', f));
47
- for (const f of ['kit.json', 'workflow-modules.txt', 'workflow-enums.txt'])
48
- if (fs.existsSync(path.join(SRC, '..', f))) fs.copyFileSync(path.join(SRC, '..', f), path.join(fx, '.agents', f));
48
+ copyScriptClosure(path.join(fx, '.agents', 'scripts'), ['edit-face-check.mjs', 'check-loop.mjs']);
49
+ copyAgentsFiles(path.join(fx, '.agents'), ['kit.json', 'workflow-modules.txt', 'workflow-enums.txt']);
49
50
  fs.mkdirSync(path.join(fx, 'workflow', 'intents'), { recursive: true });
50
51
  for (const d of ['specs', 'plans', 'incidents']) fs.mkdirSync(path.join(fx, 'workflow', d), { recursive: true });
51
52
  for (const c of REF_COMMANDS) { fs.mkdirSync(path.join(fx, '.agents', 'commands'), { recursive: true }); fs.writeFileSync(path.join(fx, '.agents', 'commands', `${c}.md`), `# ${c}\n`); }
@@ -1,5 +1,5 @@
1
1
  // flow-kit fill-intent:起草 intent 草稿(背景与问题/历史教训防复发/目标/非目标/约束/影响面/触达红线/验收标准 8 节正文 + 确认与复核 共 9 节)
2
- // 读 workflow/intents/_TEMPLATE.md 的节标题 → 输出符合 frontmatter 受限子集(6 字段,含 risk_level 风险泳道)的草稿到指定文件
2
+ // 读 workflow/intents/_TEMPLATE.md 的节标题 → 输出符合 frontmatter 受限子集(7 字段,含 risk_level 风险泳道与预判路由)的草稿到指定文件
3
3
  // 用法:node .agents/scripts/fill-intent.mjs --module pipeline --level L1 --topic "test" --output /tmp/x.md
4
4
  // 零依赖;CLI 解析用最简字符串匹配;不替代 AI 起草——本工具只输出模板与提示句
5
5
  import fs from 'node:fs';
@@ -33,18 +33,20 @@ const SECTIONS = [
33
33
  { title: '目标', hint: '- <可验证的目标>' },
34
34
  { title: '非目标(L1 微改动无实质内容可删本节,不硬填)', hint: '- <明确不做的,防止范围蔓延>' },
35
35
  { title: '约束(L1 微改动无实质内容可删本节,不硬填)', hint: '- <复用什么、不许动什么>' },
36
- { title: '影响面', hint: '- 模块:<词表之一,见 .agents/workflow-modules.txt>\n- 数据库:无 / <表名>(涉及 schema 变更 → L3;无 schema 的运行时 / 管线结构面亦为 L3,判据见 .agents/commands/new-task.md §级别判断)\n- 前端页面:<路由或组件,无则删本行>' },
37
- { title: '触达红线(对照 AGENTS.md,勾选仅标记触及范围;具体如何满足在同名 spec 或 Quick-Plan 中说明)', hint: '- [ ] <按本项目 AGENTS.md 红线逐行补;无则删本行>\n- [ ] 规则 / 契约变更(编码权威 / 共享契约 / 接口签名 / 既有参数语义 / 全局口径)→ 级别至少 L2\n- [ ] schema / 迁移 SQL / DI 链 / 认证与中间件管线 → 级别 L3\n\n> 触及任一红线即为 L2 / L3,必须立 ../specs/YYYY-MM-DD-<主题>.md 写明如何满足,方可起草 plan。' },
36
+ { title: '影响面', hint: '- 模块:<词表之一,见 .agents/workflow-modules.txt>\n- 数据库:无 / <表名>(涉及 schema 变更 → L3;无 schema 的运行时 / 管线结构面亦为 L3,判据单源见 .agents/scripts/level-criteria.mjs 的 L3_SURFACES)\n- 前端页面:<路由或组件,无则删本行>' },
37
+ { title: '触达红线(对照 AGENTS.md,勾选仅标记触及范围;具体如何满足在同名 spec 或 Quick-Plan 中说明)', hint: '- [ ] <按本项目 AGENTS.md 红线逐行补;无则删本行>\n- [ ] 触及判据单源 `.agents/scripts/level-criteria.mjs` 的 `L2_SURFACES` 触达面闭集 → 级别至少 L2\n- [ ] 触及同源的 `L3_SURFACES` 结构面闭集 → 级别 L3\n\n> 两条闭集只在该单源一处定义,**逐条对照单源勾选,不要凭记忆复述**(此处曾各写一份且条目不一致,判低即错判)。\n> 触及任一红线即为 L2 / L3,必须立 ../specs/YYYY-MM-DD-<主题>.md 写明如何满足,方可起草 plan。' },
38
38
  { title: '验收标准(可测试)', hint: '- [ ] <逐条可测试;写不出可测试判据 = 还没想清楚>\n\n> **闭环对账**:关单在 test 阶段(不依赖 deploy)。intent 置 done 前逐条勾验,每条补证据——`- [x] <判据>(证据:<commit SHA / 测试用例名 / 冒烟脚本输出>)`。\n> done 状态仍有未勾项会被 check-loop 拦截(2026-09-12 起新建 intent 为 hard-block,存量 intent 仅 warning 提示);勾选但缺「证据:」为 hard-block。' },
39
39
  ];
40
40
 
41
41
  // renderIntent:骨架渲染(纯函数)。kbHits(2026-10-09 engine-quality-round3 W2):kb-search 命中行
42
42
  // 数组(📄 行原文)——非空时替换「历史教训/防复发」节的检索占位行为实际命中列表(历史坑强制注入,
43
43
  // 手动路由与 pipeline-run 工单同权);空/缺省保留占位(fail-open 口径)。子进程调用在 main,本函数保持无 IO。
44
+ // 预判(2026-10-13 自 Shipyard §25 prejudgment-ledger 回灌):intents≡require 恒真、level 与「级别」双写
45
+ // 同值——机器可推导字段直出(与 B6b 路由预判一致性检查对账零 WARN);advisory 灰度,缺字段零告警。
44
46
  export function renderIntent({ topic, module, level, notes, date = TODAY, kbHits = [], kbFailNote = '' }) {
45
47
  if (!LEVELS.includes(level)) throw new Error('level 不在枚举 ' + LEVELS.join('|') + ':' + level);
46
- const fm = '---\n状态: draft\n级别: ' + level + '\nrisk_level: ' + level + '\n日期: ' + date + '\n模块: ' + module + '\n备注: ' + (notes || '<可选:附注自由文本,check-loop 不解析>') + '\n---\n';
47
- let body = '# INTENT — ' + topic + '\n\n<!-- 复制本模板为 YYYY-MM-DD-<主题>.md 后填写;plans/ 下同名文件与本文件配对 -->\n<!-- frontmatter 受限子集(2026-09-13):每行 键: 值;状态∈draft/approved/done/superseded/cancelled(严格枚举,附注写备注键);级别∈L0/L1/L2/L3;risk_level=级别(混合治理风险泳道选道:L0/L1 协作道异步审计 / L2/L3 防御道同步确认);check-loop 只扫 frontmatter 取机器字段,正文不再写状态/级别行 -->\n\n';
48
+ const fm = '---\n状态: draft\n级别: ' + level + '\n预判: kind=require level=' + level + '\nrisk_level: ' + level + '\n日期: ' + date + '\n模块: ' + module + '\n备注: ' + (notes || '<可选:附注自由文本,check-loop 不解析>') + '\n---\n';
49
+ let body = '# INTENT — ' + topic + '\n\n<!-- 复制本模板为 YYYY-MM-DD-<主题>.md 后填写;plans/ 下同名文件与本文件配对 -->\n<!-- frontmatter 受限子集(2026-09-13):每行 键: 值;状态∈draft/approved/done/superseded/cancelled(严格枚举,附注写备注键);级别∈L1/L2/L3(2026-10-13 L0 退役);risk_level=级别(混合治理风险泳道选道:L1 协作道异步审计 / L2/L3 防御道同步确认);check-loop 只扫 frontmatter 取机器字段,正文不再写状态/级别行 -->\n\n';
48
50
  for (const { title, hint } of SECTIONS) {
49
51
  let section = hint;
50
52
  if (title === '历史教训/防复发') {
@@ -71,13 +71,31 @@ function check(name, cond, detail = '') {
71
71
 
72
72
  // ---- 场景 8:risk_level 风险泳道字段(混合治理,= 级别,全枚举落 frontmatter)----
73
73
  {
74
- for (const level of ['L0', 'L1', 'L2', 'L3']) {
74
+ for (const level of ['L1', 'L2', 'L3']) { // L0 已退役——L0 现走 renderIntent 抛「level 不在枚举」(S8b)
75
75
  const { body } = renderIntent({ topic: 't', module: 'pipeline', level });
76
76
  const fm = body.match(/^---([\s\S]*?)---/)[1];
77
77
  check('S8 risk_level=' + level + ' 写入 frontmatter', new RegExp('^risk_level:\\s*' + level + '$', 'm').test(fm));
78
78
  }
79
79
  }
80
80
 
81
+ // ---- 场景 8b:L0 已退役(2026-10-13 自源仓收敛系列 1 回灌)→ renderIntent fail-loud ----
82
+ {
83
+ let threw = '';
84
+ try { renderIntent({ topic: 't', module: 'pipeline', level: 'L0' }); } catch (e) { threw = e.message; }
85
+ check('S8b level=L0 → 抛 level 不在枚举 L1|L2|L3', threw.includes('level 不在枚举 L1|L2|L3'), threw);
86
+ }
87
+
88
+ // ---- 场景 9:预判键 scaffold(2026-10-13 自 Shipyard §25 prejudgment-ledger 回灌)----
89
+ // intents≡require 恒真;level 与「级别」双写同值——与路由预判一致性检查对账零 WARN(advisory 灰度)
90
+ {
91
+ for (const level of ['L1', 'L2', 'L3']) { // L0 已退役——L0 现走 renderIntent 抛「level 不在枚举」(S8b)
92
+ const { body } = renderIntent({ topic: 't', module: 'pipeline', level });
93
+ const fm = body.match(/^---([\s\S]*?)---/)[1];
94
+ check('S9 预判=' + level + ' scaffold(kind=require level=级别)',
95
+ new RegExp('^预判:\\s*kind=require level=' + level + '$', 'm').test(fm));
96
+ }
97
+ }
98
+
81
99
 
82
100
  // ---- 历史坑注入 kbHits(2026-10-09 engine-quality-round3 W2)----
83
101
  {
@@ -1,7 +1,8 @@
1
1
  // flow-kit fill-plan:起草 plan 草稿(L1 Quick-Plan 三节 / L2-L3 完整 4 节)
2
2
  // 用法:node .agents/scripts/fill-plan.mjs --level L1 --topic "test" --output /tmp/x.md [--root <仓库根>]
3
3
  // L1:改动方案 + 约束与风险 + 验证计划(Quick-Plan 必填三节)
4
- // L2/L3:改动方案 + 任务拆解 + 执行顺序 + 验证计划
4
+ // L1 直达(2026-10-13 自 Shipyard 回灌):本 plan 即入口、不另立 intent;无同名 intent/incident 时起草门
5
+ // 放行(--level L1)。修复类仍可另立 incident。L2/L3:改动方案 + 任务拆解 + 执行顺序 + 验证计划
5
6
  // 起草门(2026-09-30 stage-gate-machine):output 落于工作区(路径含 workflow 段)时,须同主题入口已确认,
6
7
  // 且入口级别(取入口 frontmatter,非 --level)为 L2/L3 时同名 spec 已确认;未过 → 拒绝生成(exit 2)。
7
8
  import fs from 'node:fs';
@@ -27,14 +28,14 @@ function parseArgs(argv) {
27
28
  function fail(msg) { console.error('❌ ' + msg); process.exit(1); }
28
29
 
29
30
  const L1_SECTIONS = [
30
- { title: '改动方案', hint: '- <文件/组件>:<做什么;判据细节直接写进条目,如「L771 message.success 改『更新成功』」>' },
31
+ { title: '改动方案', hint: '- <文件/组件>:<做什么;判据细节直接写进条目,如「L771 message.success 改『更新成功』」>\n\n> build.md 确认的改动清单确认后回填本节落盘(对话留痕仅供当次)——改动面 + 任务勾选态 + git status = 会话重入三锚(2026-10-11 session-context)。' },
31
32
  { title: '约束与风险', hint: '- 约束:<对照 AGENTS.md 红线,说明如何满足;不许动什么>\n- 风险:<潜在影响 / 破坏性风险>' },
32
33
  { title: '验证计划', hint: '- 静态门:项目构建命令(+ 项目测试命令,测试项目就绪后)\n- 前端:cd frontend && npm run build(vue-tsc)\n- UI:.agents/commands/test.md(涉及页面改动必走,headless Chrome 实测)\n- L2 追加:<契约 / 规则面比对:编码结果抽样 / 接口契约断言 / 口径对账>\n- L3 追加:<含 schema 变更:备份 + 回滚 SQL 就绪后执行;仅运行时 / 管线:回退上一 release tag + 配置开关预案>' },
33
34
  ];
34
35
 
35
36
  const FULL_SECTIONS = [
36
37
  L1_SECTIONS[0],
37
- { title: '任务拆解(L2/L3 必填;L1 仅多文件多步骤时用,单任务微改动删本节)', hint: '1. <任务>\n - 判据:<怎样算完成;尽量对应一个测试或可复现操作>\n - 风险:低 / 中 / 高(<原因>)\n2. <任务>\n - 判据:…\n - 风险:…' },
38
+ { title: '任务拆解(L2/L3 必填;L1 仅多文件多步骤时用,单任务微改动删本节)', hint: '- [ ] 1. <任务>\n - 判据:<怎样算完成;尽量对应一个测试或可复现操作>\n - 风险:低 / 中 / 高(<原因>)\n - 留痕:(勾选时回填 commit SHA / 测试名 / 实测输出)\n- [ ] 2. <任务>\n - 判据:…\n - 风险:…\n - 留痕:…\n\n> 任务即勾选态(2026-10-11 session-context):完成一项立刻勾选并回填留痕;压缩 / 新会话续作先读本表勾选态 + git status 重建进度,禁凭记忆补勾。' },
38
39
  { title: '执行顺序(L2/L3 必填;L1 单文件微改动删本节)', hint: '<1 → 2 → 3;标注依赖关系>' },
39
40
  L1_SECTIONS[2],
40
41
  ];
@@ -43,7 +44,10 @@ export function renderPlan({ topic, level = 'L1' }) {
43
44
  if (!LEVELS.includes(level)) throw new Error('level 不在枚举 ' + LEVELS.join('|') + ':' + level);
44
45
  const sections = level === 'L1' ? L1_SECTIONS : FULL_SECTIONS;
45
46
  const fm = '---\n状态: draft\n级别: ' + level + '\n模块: pipeline\n---\n';
46
- let body = '# PLAN — ' + topic + '\n\n<!-- 与 intents/ 或 incidents/ 下同名入口文档配对;L2/L3 必须先有 ../specs/ 同名 spec 确认通过;AI 起草、用户对话内确认后开工 -->\n<!-- frontmatter 受限子集(2026-09-13):状态∈draft/approved/done/superseded/cancelled;级别∈L0/L1/L2/L3;正文不再写状态/级别行 -->\n<!-- L1 快车道 (Quick-Plan)(2026-09-30 更新):合并 Spec 与 Plan 核心。必填三节:[改动方案, 约束与风险, 验证计划]。仅多文件多步骤时保留任务拆解/执行顺序 -->\n\n对应入口:../intents/YYYY-MM-DD-<主题>.md 或 ../incidents/YYYY-MM-DD-<主题>.md(保留实际一项)\n对应 spec:../specs/YYYY-MM-DD-<主题>.md(L1 走快车道则省略)\n\n';
47
+ const entryLine = level === 'L1'
48
+ ? '对应入口:本 plan(L1 不再另立 intent)。修复类另保留 ../incidents/YYYY-MM-DD-<主题>.md\n对应 spec:省略(仅 L2/L3)\n\n'
49
+ : '对应入口:../intents/YYYY-MM-DD-<主题>.md 或 ../incidents/YYYY-MM-DD-<主题>.md(保留实际一项)\n对应 spec:../specs/YYYY-MM-DD-<主题>.md\n\n';
50
+ let body = '# PLAN — ' + topic + '\n\n<!-- 与 intents/ 或 incidents/ 下同名入口文档配对;L2/L3 必须先有 ../specs/ 同名 spec 确认通过;AI 起草、用户对话内确认后开工 -->\n<!-- frontmatter 受限子集(2026-09-13):状态∈draft/approved/done/superseded/cancelled;级别∈L1/L2/L3(2026-10-13 L0 退役);正文不再写状态/级别行 -->\n<!-- L1 快车道 (Quick-Plan)(2026-09-30 更新):合并 Spec 与 Plan 核心。必填三节:[改动方案, 约束与风险, 验证计划]。仅多文件多步骤时保留任务拆解/执行顺序。L1 直达(2026-10-13 自 Shipyard 回灌):本文件即入口,不另立 intent -->\n\n' + entryLine;
47
51
  for (const { title, hint } of sections) {
48
52
  body += '## ' + title + '\n\n' + hint + '\n\n';
49
53
  }
@@ -58,7 +62,7 @@ if (isMain) {
58
62
  if (!LEVELS.includes(a.level)) fail('level 必须在 ' + LEVELS.join('|') + ':' + a.level);
59
63
  const ws = resolveWorkspace(a.output, a.root);
60
64
  if (ws) {
61
- const gate = draftGateFor('plan', ws.root, ws.base);
65
+ const gate = draftGateFor('plan', ws.root, ws.base, { level: a.level });
62
66
  if (!gate.ok) {
63
67
  console.error('❌ 起草门未过(stage-gates):' + gate.reason);
64
68
  console.error(' ' + gate.hint + '——本份未生成(确认前置后重跑本命令)');
@@ -70,6 +74,6 @@ if (isMain) {
70
74
  const { body } = renderPlan(a);
71
75
  fs.mkdirSync(path.dirname(a.output), { recursive: true });
72
76
  fs.writeFileSync(a.output, body);
73
- console.log('✅ 已生成 plan 草稿:' + a.output + '(状态 draft / 级别 ' + a.level + ' / ' + (a.level === 'L1' ? 'Quick-Plan 三节' : '完整 4 节') + ')');
77
+ console.log('✅ 已生成 plan 草稿:' + a.output + '(状态 draft / 级别 ' + a.level + ' / ' + (a.level === 'L1' ? 'Quick-Plan 三节(plan 即入口)' : '完整 4 节') + ')');
74
78
  }
75
79
  export default renderPlan;
@@ -31,6 +31,10 @@ function check(name, cond, detail = '') {
31
31
  check('S2 L2 含节 ## 执行顺序', body.includes('## 执行顺序'));
32
32
  check('S2 L2 含节 ## 验证计划', body.includes('## 验证计划'));
33
33
  check('S2 L2 共 5 节(4 节 + 确认与复核)', sections.length === 5, '实际 ' + sections.length + ' 节');
34
+ // 任务即勾选态(2026-10-13 自 Shipyard 回灌 session-context)
35
+ check('S2 任务即勾选态:复选框编号形态', /- \[ \] 1\. <任务>/.test(body));
36
+ check('S2 任务即勾选态:留痕行', body.includes('留痕:(勾选时回填 commit SHA / 测试名 / 实测输出)'));
37
+ check('S2 任务即勾选态:三锚注释', body.includes('会话重入三锚'));
34
38
  }
35
39
 
36
40
  // ---- 场景 3:L3 与 L2 同结构 ----
@@ -86,10 +90,22 @@ const runTool = (root, base, level) => spawnSync(process.execPath,
86
90
  [CLI, '--topic', base, '--level', level, '--output', path.join(root, 'workflow', 'plans', `${base}.md`)],
87
91
  { cwd: root, encoding: 'utf8' });
88
92
  {
93
+ // S8(2026-10-13 自 Shipyard 回灌 L1 即入口):无同名入口 + --level L1 → plan 自己就是入口,放行生成;
94
+ // 对照 S8b:L2 无同名入口仍拒(直达语义只对 L1 开)。
89
95
  const root = mkws();
90
96
  const r = runTool(root, '2026-09-30-none', 'L1');
91
- check('S8 起草门:无同名入口 → exit 2 且零文件产出',
92
- r.status === 2 && /起草门未过/.test(r.stderr) && !fs.existsSync(path.join(root, 'workflow', 'plans', '2026-09-30-none.md')),
97
+ const f = path.join(root, 'workflow', 'plans', '2026-09-30-none.md');
98
+ check('S8 L1 即入口:无同名入口 + --level L1 → 放行生成(plan 即入口)',
99
+ r.status === 0 && fs.existsSync(f)
100
+ && fs.readFileSync(f, 'utf8').includes('对应入口:本 plan(L1 不再另立 intent)'),
101
+ JSON.stringify({ status: r.status, stderr: r.stderr }));
102
+ fs.rmSync(root, { recursive: true, force: true });
103
+ }
104
+ {
105
+ const root = mkws();
106
+ const r = runTool(root, '2026-09-30-none2', 'L2');
107
+ check('S8b 起草门:无同名入口 + --level L2 → exit 2 且零文件产出(直达仅限 L1)',
108
+ r.status === 2 && /起草门未过/.test(r.stderr) && !fs.existsSync(path.join(root, 'workflow', 'plans', '2026-09-30-none2.md')),
93
109
  JSON.stringify({ status: r.status, stderr: r.stderr }));
94
110
  fs.rmSync(root, { recursive: true, force: true });
95
111
  }
@@ -113,7 +129,7 @@ const runTool = (root, base, level) => spawnSync(process.execPath,
113
129
  fs.rmSync(root, { recursive: true, force: true });
114
130
  }
115
131
  {
116
- // S10b 探索泳道(2026-09-30 hybrid-governance-explore-hardening):L0/L1 draft intent → 放行 plan 起草
132
+ // S10b 探索泳道(2026-09-30 hybrid-governance-explore-hardening):L1 draft intent → 放行 plan 起草
117
133
  // (「先起草后确认」;收口由 check-loop --hardening 加固门兜底)
118
134
  const root = mkws({ 'workflow/intents/2026-09-30-exp.md': '---\n状态: draft\n级别: L1\nrisk_level: L1\n日期: 2026-09-30\n---\n# I\n' });
119
135
  const r = runTool(root, '2026-09-30-exp', 'L1');
@@ -1,4 +1,4 @@
1
- // flow-kit fill-spec:起草 spec 草稿(功能行为 / 数据流 / 系统改动 / 约束遵守映射 / 风险评估 共 5 节)
1
+ // flow-kit fill-spec:起草 spec 草稿(功能行为 / 数据流 / 系统改动 / 约束遵守映射 / 验收判据 / 风险评估 共 6 节)
2
2
  // 用法:node .agents/scripts/fill-spec.mjs --topic "test" --output /tmp/x.md [--root <仓库根>]
3
3
  // 起草门(2026-09-30 stage-gate-machine):output 落于工作区(路径含 workflow 段)时,须同主题入口已确认
4
4
  // (判定口径见 stage-gates.mjs);未过 → 拒绝生成(exit 2);仓外草稿(无 workflow 段)不校验。
@@ -30,15 +30,18 @@ const SECTIONS = [
30
30
  { title: '数据流', hint: '<请求→处理→持久化→回应的端到端路径;标出各环节涉及的服务 / 表 / 缓存 / 第三方调用>' },
31
31
  { title: '系统改动', hint: '<分模块列出本 spec 引入的所有改动;含新增 / 修改 / 删除;与 plan §改动面 一一对应>' },
32
32
  { title: '约束遵守映射', hint: '<逐条对照根 AGENTS.md 与目录级 AGENTS.md(如有)的红线 / 约束,说明本 spec 如何满足;逐条回应而非简单列举>' },
33
+ { title: '验收判据', hint: '<逐条登记可观察行为 × 证据(存在的文件路径,或全量门命令且 workflow/verify-evidence.md 有不早于本 spec 日期的全绿行)。人工走查状态写「人工」、证据留空(截图等图片路径不认;需留痕改存 .md 报告后引路径)。对账段不核对正文相关性>', table: true },
33
34
  { title: '风险评估', hint: '<列出本 spec 引入的风险点:数据 / 接口 / 性能 / 安全 / 回滚难度 / 用户影响;评估每个的等级与缓解措施>' },
34
35
  ];
35
36
 
36
37
  export function renderSpec({ topic, level = 'L2', date = TODAY }) {
37
38
  if (!LEVELS.includes(level)) throw new Error(`level 不在枚举 ${LEVELS.join('|')}:${level}`);
38
39
  const fm = `---\n状态: draft\n级别: ${level}\n日期: ${date}\n模块: pipeline\n备注: <spec 关联 intent / plan 的同名主题>\n---\n`;
39
- let body = `# SPEC — ${topic}\n\n<!-- 与 intents/ 下同名入口文档配对;L2/L3 必须先有 ../intents/YYYY-MM-DD-<主题>.md approved -->\n<!-- frontmatter 受限子集(2026-09-13):状态∈draft/approved/done/superseded/cancelled;级别∈L0/L1/L2/L3;正文不再写状态/级别行 -->\n\n`;
40
- for (const { title, hint } of SECTIONS) {
40
+ let body = `# SPEC — ${topic}\n\n<!-- 与 intents/ 下同名入口文档配对;L2/L3 必须先有 ../intents/YYYY-MM-DD-<主题>.md approved -->\n<!-- frontmatter 受限子集(2026-09-13):状态∈draft/approved/done/superseded/cancelled;级别∈L1/L2/L3(2026-10-13 L0 退役);正文不再写状态/级别行 -->\n\n`;
41
+ for (const { title, hint, table } of SECTIONS) {
41
42
  body += `## ${title}\n\n${hint}\n\n`;
43
+ // 验收判据是对账段(B6b 落地后编号以 check-loop.mjs 头部清单为准)的机器输入:直出表骨架(表头签名固定,作者只填行——占位行被检查豁免)
44
+ if (table) body += `| 判据(可观察行为) | 证据(测试文件 / 命令 / 证据文件) | 状态 |\n|---|---|---|\n| <行为 1> | <路径或命令> | <覆盖 / 未覆盖 / 人工> |\n\n`;
42
45
  }
43
46
  body += `## 确认与复核\n\n- 确认日期:\n- 复核:L3 必须独立复核(independent-reviewer,新会话);L2 推荐独立复核;L1 可省\n`;
44
47
  return { frontmatter: fm, body: fm + body, sections: SECTIONS.map((s) => s.title) };
@@ -12,12 +12,20 @@ function check(name, cond, detail = '') {
12
12
  else { failCount++; console.log('FAIL ' + name + (detail ? '——' + detail : '')); }
13
13
  }
14
14
 
15
- // ---- 场景 1:5 节正文齐 ----
15
+ // ---- 场景 1:6 节正文齐 ----
16
16
  {
17
17
  const { body } = renderSpec({ topic: 'test', level: 'L2' });
18
- const expectedSections = ['功能行为', '数据流', '系统改动', '约束遵守映射', '风险评估', '确认与复核'];
18
+ const expectedSections = ['功能行为', '数据流', '系统改动', '约束遵守映射', '验收判据', '风险评估', '确认与复核'];
19
19
  for (const s of expectedSections) check('S1 含节 ## ' + s, body.includes('## ' + s));
20
- check('S1 共 6 节标题', (body.match(/^##\s/gm) || []).length === 6);
20
+ check('S1 共 7 节标题', (body.match(/^##\s/gm) || []).length === 7);
21
+ }
22
+
23
+ // ---- 场景 1b:验收判据表骨架(2026-10-13 自 Shipyard 回灌——对账段机器输入,表头签名固定)----
24
+ {
25
+ const { body } = renderSpec({ topic: 't', level: 'L2' });
26
+ check('S1b 验收判据表头三列签名', body.includes('| 判据(可观察行为) | 证据(测试文件 / 命令 / 证据文件) | 状态 |'));
27
+ check('S1b 验收判据表分隔行', body.includes('|---|---|---|'));
28
+ check('S1b 验收判据占位行', body.includes('| <行为 1> | <路径或命令> | <覆盖 / 未覆盖 / 人工> |'));
21
29
  }
22
30
 
23
31
  // ---- 场景 2:frontmatter 含 5 字段 ----
@@ -94,7 +102,7 @@ const runTool = (root, base) => spawnSync(process.execPath,
94
102
  fs.rmSync(root, { recursive: true, force: true });
95
103
  }
96
104
  {
97
- // S9b 探索泳道(2026-09-30 hybrid-governance-explore-hardening):L0/L1 draft intent → 放行 spec 起草
105
+ // S9b 探索泳道(2026-09-30 hybrid-governance-explore-hardening):L1 draft intent → 放行 spec 起草
98
106
  // (「先起草后确认」;L2/L3 防御道维持入口确认前置——S9 照旧)
99
107
  const root = mkws({ entryFile: 'intents/2026-09-30-exp.md', entryBody: '---\n状态: draft\n级别: L1\nrisk_level: L1\n日期: 2026-09-30\n---\n# I\n' });
100
108
  const r = runTool(root, '2026-09-30-exp');