agentic-flow-kit 1.7.16 → 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 (160) hide show
  1. package/README.md +11 -7
  2. package/modules/hosts/claude/commands/wf-build.md +1 -1
  3. package/modules/hosts/claude/commands/wf-maintain.md +1 -1
  4. package/modules/hosts/claude/commands/wf-new-task.md +9 -8
  5. package/modules/hosts/claude/commands/wf-plan.md +1 -1
  6. package/modules/hosts/claude/commands/wf-review.md +1 -1
  7. package/modules/hosts/claude/commands/wf-test.md +1 -1
  8. package/modules/hosts/codex/commands/wf-build.md +1 -1
  9. package/modules/hosts/codex/commands/wf-maintain.md +1 -1
  10. package/modules/hosts/codex/commands/wf-new-task.md +9 -8
  11. package/modules/hosts/codex/commands/wf-plan.md +1 -1
  12. package/modules/hosts/codex/commands/wf-review.md +1 -1
  13. package/modules/hosts/codex/commands/wf-test.md +1 -1
  14. package/modules/hosts/cursor/commands/wf-build.md +1 -1
  15. package/modules/hosts/cursor/commands/wf-maintain.md +1 -1
  16. package/modules/hosts/cursor/commands/wf-new-task.md +9 -8
  17. package/modules/hosts/cursor/commands/wf-plan.md +1 -1
  18. package/modules/hosts/cursor/commands/wf-review.md +1 -1
  19. package/modules/hosts/cursor/commands/wf-test.md +1 -1
  20. package/modules/hosts/opencode/commands/wf-build.md +1 -1
  21. package/modules/hosts/opencode/commands/wf-maintain.md +1 -1
  22. package/modules/hosts/opencode/commands/wf-new-task.md +9 -8
  23. package/modules/hosts/opencode/commands/wf-plan.md +1 -1
  24. package/modules/hosts/opencode/commands/wf-review.md +1 -1
  25. package/modules/hosts/opencode/commands/wf-test.md +1 -1
  26. package/modules/hosts/trae/commands/wf-build.md +1 -1
  27. package/modules/hosts/trae/commands/wf-maintain.md +1 -1
  28. package/modules/hosts/trae/commands/wf-new-task.md +9 -8
  29. package/modules/hosts/trae/commands/wf-plan.md +1 -1
  30. package/modules/hosts/trae/commands/wf-review.md +1 -1
  31. package/modules/hosts/trae/commands/wf-test.md +1 -1
  32. package/package.json +1 -1
  33. package/src/cli.mjs +6 -2
  34. package/src/doctor.mjs +11 -2
  35. package/src/executor.mjs +25 -8
  36. package/src/executor.test.mjs +4 -1
  37. package/src/fresh-init.test.mjs +1 -1
  38. package/src/init.mjs +20 -3
  39. package/src/profiles.mjs +7 -3
  40. package/src/rule-budget.test.mjs +2 -2
  41. package/src/stack-profile.test.mjs +6 -0
  42. package/src/validate.mjs +6 -1
  43. package/src/validate.test.mjs +1 -1
  44. package/templates/.gitattributes +4 -1
  45. package/templates/AGENTS.md +2 -2
  46. package/templates/_agents/commands/build.md +1 -1
  47. package/templates/_agents/commands/maintain.md +1 -1
  48. package/templates/_agents/commands/new-task.md +9 -8
  49. package/templates/_agents/commands/plan.md +1 -1
  50. package/templates/_agents/commands/review.md +1 -1
  51. package/templates/_agents/commands/test.md +1 -1
  52. package/templates/_agents/engine-lock.json +130 -42
  53. package/templates/_agents/hooks/check-index-incremental.mjs +48 -0
  54. package/templates/_agents/hooks/check-pairing-incremental.mjs +66 -0
  55. package/templates/_agents/hooks/check-wiki-ledger.mjs +30 -0
  56. package/templates/_agents/lane-surfaces.txt +4 -1
  57. package/templates/_agents/level-hints.txt +1 -1
  58. package/templates/_agents/loop/papercut-debt/DESIGN.md +112 -0
  59. package/templates/_agents/mechanism-registry.md +27 -0
  60. package/templates/_agents/rule-budgets.txt +2 -2
  61. package/templates/_agents/scripts/agg-delegations.cjs +21 -3
  62. package/templates/_agents/scripts/agg-gate-stats.mjs +3 -3
  63. package/templates/_agents/scripts/agg-judgments.mjs +80 -0
  64. package/templates/_agents/scripts/agg-judgments.test.mjs +45 -0
  65. package/templates/_agents/scripts/archive-sweep.mjs +233 -0
  66. package/templates/_agents/scripts/archive-sweep.test.mjs +129 -0
  67. package/templates/_agents/scripts/check-attribution.mjs +37 -0
  68. package/templates/_agents/scripts/check-convergence.mjs +119 -0
  69. package/templates/_agents/scripts/check-engine-integrity.mjs +4 -1
  70. package/templates/_agents/scripts/check-funnel.mjs +56 -0
  71. package/templates/_agents/scripts/check-hygiene.mjs +5 -5
  72. package/templates/_agents/scripts/check-index-incremental.test.mjs +73 -0
  73. package/templates/_agents/scripts/check-lane-surface.mjs +2 -2
  74. package/templates/_agents/scripts/check-lane-surface.test.mjs +5 -5
  75. package/templates/_agents/scripts/check-ledger-invariant.mjs +403 -152
  76. package/templates/_agents/scripts/check-ledger-invariant.test.mjs +433 -3
  77. package/templates/_agents/scripts/check-loop-rev.test.mjs +8 -5
  78. package/templates/_agents/scripts/check-loop-unit.test.mjs +8 -0
  79. package/templates/_agents/scripts/check-loop.mjs +215 -34
  80. package/templates/_agents/scripts/check-loop.test.mjs +570 -49
  81. package/templates/_agents/scripts/check-prejudgment.mjs +59 -0
  82. package/templates/_agents/scripts/check-registry.mjs +45 -0
  83. package/templates/_agents/scripts/check-scope.mjs +96 -0
  84. package/templates/_agents/scripts/check-scope.test.mjs +96 -0
  85. package/templates/_agents/scripts/check-scripts-syntax.mjs +61 -0
  86. package/templates/_agents/scripts/check-scripts-syntax.test.mjs +72 -0
  87. package/templates/_agents/scripts/check-spec-verif.mjs +47 -0
  88. package/templates/_agents/scripts/compact-ledgers.mjs +3 -3
  89. package/templates/_agents/scripts/confirm-doc.mjs +114 -38
  90. package/templates/_agents/scripts/confirm-doc.test.mjs +180 -4
  91. package/templates/_agents/scripts/edit-face-check.mjs +5 -3
  92. package/templates/_agents/scripts/fill-intent.mjs +5 -3
  93. package/templates/_agents/scripts/fill-intent.test.mjs +19 -1
  94. package/templates/_agents/scripts/fill-plan.mjs +10 -6
  95. package/templates/_agents/scripts/fill-plan.test.mjs +19 -3
  96. package/templates/_agents/scripts/fill-spec.mjs +6 -3
  97. package/templates/_agents/scripts/fill-spec.test.mjs +12 -4
  98. package/templates/_agents/scripts/fixture-scripts.mjs +2 -2
  99. package/templates/_agents/scripts/fixture-scripts.test.mjs +6 -1
  100. package/templates/_agents/scripts/gate-checklist.mjs +6 -0
  101. package/templates/_agents/scripts/gate-seg.mjs +1 -1
  102. package/templates/_agents/scripts/gen-effective-policy.mjs +107 -0
  103. package/templates/_agents/scripts/gen-effective-policy.test.mjs +100 -0
  104. package/templates/_agents/scripts/gen-verify-evidence.mjs +107 -0
  105. package/templates/_agents/scripts/gen-verify-evidence.test.mjs +84 -0
  106. package/templates/_agents/scripts/gen-workflow-dashboard.mjs +8 -3
  107. package/templates/_agents/scripts/gen-workflow-dashboard.test.mjs +2 -1
  108. package/templates/_agents/scripts/gen-workflow-metrics.mjs +3 -3
  109. package/templates/_agents/scripts/git-core.mjs +45 -0
  110. package/templates/_agents/scripts/judgment-ledger.mjs +65 -0
  111. package/templates/_agents/scripts/judgment-ledger.test.mjs +129 -0
  112. package/templates/_agents/scripts/kb-search.mjs +19 -4
  113. package/templates/_agents/scripts/kb-search.test.mjs +33 -0
  114. package/templates/_agents/scripts/lane-surface-audit.mjs +143 -0
  115. package/templates/_agents/scripts/lane-surface-audit.test.mjs +93 -0
  116. package/templates/_agents/scripts/ledger-shards.mjs +307 -0
  117. package/templates/_agents/scripts/ledger-shards.test.mjs +339 -0
  118. package/templates/_agents/scripts/level-criteria.mjs +9 -10
  119. package/templates/_agents/scripts/level-criteria.test.mjs +7 -5
  120. package/templates/_agents/scripts/papercut-debt-controller.mjs +141 -0
  121. package/templates/_agents/scripts/papercut-debt-controller.test.mjs +127 -0
  122. package/templates/_agents/scripts/papercut-debt-sensor.mjs +170 -0
  123. package/templates/_agents/scripts/papercut-debt-sensor.test.mjs +146 -0
  124. package/templates/_agents/scripts/pipeline-run.mjs +6 -22
  125. package/templates/_agents/scripts/pipeline-run.test.mjs +22 -42
  126. package/templates/_agents/scripts/policy.mjs +28 -0
  127. package/templates/_agents/scripts/resolve-merge-conflicts.mjs +110 -0
  128. package/templates/_agents/scripts/resolve-merge-conflicts.test.mjs +87 -0
  129. package/templates/_agents/scripts/rewrite-file.mjs +133 -0
  130. package/templates/_agents/scripts/rewrite-file.test.mjs +148 -0
  131. package/templates/_agents/scripts/rule-budget.mjs +83 -0
  132. package/templates/_agents/scripts/run-tests.mjs +99 -0
  133. package/templates/_agents/scripts/run-tests.test.mjs +105 -0
  134. package/templates/_agents/scripts/solidify-task.test.mjs +1 -1
  135. package/templates/_agents/scripts/stage-gates.mjs +34 -12
  136. package/templates/_agents/scripts/trust-mode.mjs +4 -4
  137. package/templates/_agents/scripts/trust-mode.test.mjs +1 -1
  138. package/templates/_agents/scripts/verify.mjs +135 -58
  139. package/templates/_agents/scripts/verify.test.mjs +156 -47
  140. package/templates/_agents/scripts/wf-journal.mjs +8 -4
  141. package/templates/_agents/scripts/workflow-board-server.test.mjs +2 -2
  142. package/templates/_agents/scripts/workflow-enums.test.mjs +14 -5
  143. package/templates/_agents/scripts-test-exempt.txt +1 -0
  144. package/templates/_agents/skills/papercut-fix/SKILL.md +60 -0
  145. package/templates/_agents/skills/papercut-fix/references/response-template.md +81 -0
  146. package/templates/_agents/workflow-enums.txt +8 -1
  147. package/templates/_githooks/pre-commit +110 -90
  148. package/templates/_githooks/pre-merge-commit +27 -23
  149. package/templates/_githooks/pre-push +4 -3
  150. package/templates/_github/workflows/kit-ci.yml +4 -3
  151. package/templates/workflow/README.md +14 -7
  152. package/templates/workflow/incidents/_TEMPLATE.md +4 -1
  153. package/templates/workflow/intents/_TEMPLATE.md +4 -2
  154. package/templates/workflow/papercuts.md +2 -0
  155. package/templates/workflow/plans/_TEMPLATE.md +10 -5
  156. package/templates/workflow/regression-checklist.md +1 -0
  157. package/templates/workflow/specs/_TEMPLATE.md +17 -1
  158. package/templates/_agents/hooks/check-pairing-incremental.sh +0 -43
  159. package/templates/_agents/hooks/check-wiki-ledger.sh +0 -18
  160. package/templates/_agents/scripts/rule-budget.sh +0 -74
@@ -13,8 +13,10 @@
13
13
  // 落态记账——免 TTY、免逐份问答,但台账行如实记 source:"chat-delegated" + quote:<原话>,
14
14
  // 永不伪装 TTY 行。调用纪律(AI 须先取得用户对话内明确确认)由 AGENTS.md 确认门条款承载,
15
15
  // quote 原话入账供事后对质。
16
- // 台账:.agents/confirmations.jsonl(入 git、追加式)——每行 {ts, doc, stage, fingerprint(64位), prev,
17
- // source:"tty"|"chat-delegated", quote?(仅委托行), batch, seq, of}。
16
+ // 台账:默认分片 .agents/confirmations.d/<doc基名>.jsonl(2026-10-13 ledger-shard-default,
17
+ // 协议对齐 Shipyard 参考实现;--no-shard / CONFIRM_SHARDED=0 回退主台账 .agents/confirmations.jsonl)
18
+ // ——两者入 git、追加式。每行 {ts, doc, stage, fingerprint(64位), prev, source:"tty"|"chat-delegated",
19
+ // quote?(仅委托行), batch, seq, of}。
18
20
  // batch/seq/of(2026-09-28 batch-ledger-audit 新增,纯增字段、向后兼容、历史行不回填):
19
21
  // 记「本次进程调用落了几份态」这一**写入时确定已知的事实**——batch 为本次调用生成的短随机串
20
22
  // (无时间语义、无全局唯一要求,仅需单台账内不碰撞),seq 为该次调用内件序(从 1),of 为本次
@@ -23,12 +25,12 @@
23
25
  // 指纹:内容 CRLF 归一 → 剔除「确认指纹:」行(防自引用)→ sha256;frontmatter 存前 16 位,台账存全量。
24
26
  // 用法:node .agents/scripts/confirm-doc.mjs <workflow/intents|x.md> [<doc2>...] [--root <仓库根>]
25
27
  // node .agents/scripts/confirm-doc.mjs <doc...> --delegated "<用户对话原话>"(委托代录)
26
- // node .agents/scripts/confirm-doc.mjs <doc...> --delegated "<原话>" --batch(协作道批量:L0/L1 多份一次代录)
28
+ // node .agents/scripts/confirm-doc.mjs <doc...> --delegated "<原话>" --batch(协作道批量:L1 多份一次代录)
27
29
  // node .agents/scripts/confirm-doc.mjs <doc...> --to superseded|cancelled(放弃态:取代/取消,留指纹与台账)
28
- // node .agents/scripts/confirm-doc.mjs <doc...> --shard(分片写入:多 worktree 并行时各写各的
29
- // .agents/confirmations.d/<doc>.jsonl,避免同文件并发覆盖;里程碑跑 compact-ledgers.mjs 归并)
30
+ // node .agents/scripts/confirm-doc.mjs <doc...> --no-shard(回退主台账 .agents/confirmations.jsonl:
31
+ // 分片已是默认写入口径,环境变量 CONFIRM_SHARDED=0 同效;里程碑可选跑 compact-ledgers.mjs 归并分片)
30
32
  // 多文档一次传入:TTY 模式天然逐份过目键入;--delegated 默认一次仅一份(2026-09-27
31
- // confirm-gate-one-per-call:多份并录曾系统性塌掉 build.md「逐件确认」三道门),L0/L1 协作道可
33
+ // confirm-gate-one-per-call:多份并录曾系统性塌掉 build.md「逐件确认」三道门),L1 协作道可
32
34
  // `--batch` 多份一次代录(2026-09-30 hybrid-governance-risk-lanes):逐份读「级别」,L2/L3 逐份拒绝
33
35
  // (防御道不批量);台账行带 `brief:true` 简洁审计标记(quote 原话仍入账供对质),check-loop 检查 15
34
36
  // 并录审计对全行带标记的批次豁免「确认并录」告警。--auto 一次仍仅一份(自治放行不批量)。
@@ -40,8 +42,8 @@ import fs from 'node:fs';
40
42
  import path from 'node:path';
41
43
  import readline from 'node:readline';
42
44
  import { createHash, randomBytes } from 'node:crypto';
43
- import { spawnSync } from 'node:child_process';
44
- import { readLedgerEntries } from './ledger-loader.mjs';
45
+ import { execFileSync, spawnSync } from 'node:child_process';
46
+ import { readLedgerEntries, readLedgerRows } from './ledger-loader.mjs';
45
47
  import { confirmGateFor, clarifyGateFor, doneGateFor, laneOfEntry, laneOfDoc } from './stage-gates.mjs';
46
48
  import { loadKitPolicy, hasFreshVerifyLine, ledgerChainHash, ledgerDocChainActive, ledgerDocChainPrev, scopeDigest } from './policy.mjs';
47
49
 
@@ -158,10 +160,12 @@ export function applyTransition(text, targetStage, fingerprint16) {
158
160
  // 最后一个带 hash 行**(ledgerDocChainPrev),不再取物理末行。理由见 POLICIES v8 注释:物理链隐含
159
161
  // 「单写者顺序追加」前提,而并发 append / git 并集合并 / 分片归并三种**正常协作**都会打断它。
160
162
  // 缺键(v1-v7)→ 走旧物理链口径,存量零变化。
161
- // shard(2026-10-12 ledger-shard-write):多 worktree 并行时两个进程同时 append 同一台账文件会互相覆盖
162
- // (各自读到同一末行 → 同 prevHash → 后写者覆盖前写者)。--shard 把本行写入分片
163
- // `.agents/confirmations.d/<doc基名>.jsonl`,各 worktree 各写各的、互不冲突;里程碑跑 compact-ledgers.mjs
164
- // 归并回主台账。**默认仍写主台账**——分片是有意的并发开关,不设则零影响。
163
+ // shard(2026-10-12 ledger-shard-write;2026-10-13 ledger-shard-default 升为默认):多 worktree 并行时
164
+ // 两个进程同时 append 同一台账文件会互相覆盖(各自读到同一末行 → 同 prevHash → 后写者覆盖前写者)。
165
+ // 分片把本行写入 `.agents/confirmations.d/<doc基名>.jsonl`,各 worktree 各写各的、互不冲突。
166
+ // **默认写分片**(CLI 层;--no-shard / CONFIRM_SHARDED=0 显式回退主台账)——doc 链未激活
167
+ // (policyVersion < 8)时 CLI 层自动回退主台账(见 isMain 内 ledger-shard-default 注释),
168
+ // 存量装户升级后行为零变化。函数层契约不变:shard 显式传入,默认 false。
165
169
  export function appendLedger(root, entry, { shard = false } = {}) {
166
170
  const p = shard && entry.doc
167
171
  ? path.join(root, '.agents', 'confirmations.d', `${path.basename(entry.doc, '.md')}.jsonl`)
@@ -208,8 +212,8 @@ if (isMain) {
208
212
  let delegatedQuote = null; // null = TTY 模式;字符串 = 委托代录的用户对话原话
209
213
  let toTarget = null; // null = 默认前向跳转;superseded|cancelled = 放弃态显式目标
210
214
  let autoMode = false; // AI 自治模式(信任等级授权,2026-09-30 ai-autonomy-trust)
211
- let batchMode = false; // 协作道批量代录(--batch,2026-09-30 hybrid-governance-risk-lanes:仅 --delegated + L0/L1)
212
- let shardWrite = false; // 分片写入(--shard,2026-10-12 ledger-shard-write):多 worktree 并行时各写各的分片,避免互相覆盖
215
+ let batchMode = false; // 协作道批量代录(--batch,2026-09-30 hybrid-governance-risk-lanes:仅 --delegated + L1)
216
+ let shardWrite = process.env.CONFIRM_SHARDED !== '0'; // 分片默认写入口径(2026-10-13 ledger-shard-default):CONFIRM_SHARDED=0 显式回退主台账
213
217
  const docs = [];
214
218
  for (let i = 0; i < argv.length; i++) {
215
219
  if (argv[i] === '--root') root = path.resolve(argv[++i]);
@@ -218,19 +222,34 @@ if (isMain) {
218
222
  else if (argv[i] === '--auto') autoMode = true;
219
223
  else if (argv[i] === '--batch') batchMode = true;
220
224
  else if (argv[i] === '--shard') shardWrite = true;
225
+ else if (argv[i] === '--no-shard') shardWrite = false;
221
226
  else docs.push(argv[i].replace(/\\/g, '/'));
222
227
  }
223
228
  if (toTarget !== null && !ABANDON_TARGETS.includes(toTarget)) {
224
229
  console.error(`用法:--to 仅接受 superseded|cancelled(现「${toTarget}」)——cancelled 自 draft/approved/open/fixed;superseded 自 approved/done/fixed/closed(未确认态只能 cancelled;终态间仅允许 done/closed→superseded)`);
225
230
  process.exit(1);
226
231
  }
227
- // --shard 与 doc 链口径绑定(2026-10-12 ledger-shard-write):分片与主台账分属不同文件,
228
- // 只有 doc 链能跨来源接续;旧物理链口径下分片行的 prevHash 会与主台账末行失配 → 提交即断链。
229
- // 故缺锚时**参数期直接拒**(友好报错),而不是让用户写完台账再被门禁拦。
230
- if (shardWrite && !ledgerDocChainActive(loadKitPolicy(root))) {
231
- console.error('❌ --shard 需要台账 doc 链口径(policy.ledgerDocChainSince),当前 policyVersion 无此键。\n 原因:分片与主台账是不同文件,只有 doc 链能跨来源接续;旧物理链口径下分片行会与主台账断链。\n 修法:在 .agents/kit.json 设 policyVersion: 8(或更高的支持版本)后重试。');
232
+ // 分片默认写入口径(2026-10-13 ledger-shard-default,对齐 Shipyard 参考实现):
233
+ // 确认行默认写 `.agents/confirmations.d/<doc基名>.jsonl`——多 worktree / 多人并行天然各写各的,
234
+ // 互不覆盖;跨分支合并走 .gitattributes 的 union driver(confirmations.d/*.jsonl merge=jsonl-union),
235
+ // 里程碑可选跑 compact-ledgers.mjs 收敛碎片。--no-shard / CONFIRM_SHARDED=0 显式回退主台账。
236
+ // doc 链仍是分片的前提(2026-10-12 ledger-shard-write 口径保留):分片与主台账分属不同文件,
237
+ // 只有 doc 链能跨来源接续;旧物理链口径下分片行的 prevHash 会与主台账末行失配 → 提交即断链。故:
238
+ // 显式 --shard + doc 链未激活 → 参数期直接拒(友好报错,原口径不变);
239
+ // 默认分片 + doc 链未激活 → 自动回退主台账并出 ℹ️——存量装户(policyVersion < 8)升级模板后
240
+ // 行为零变化,分片默认仅对 doc 链已激活的装户生效;升级法:.agents/kit.json 设 policyVersion: 8。
241
+ if (argv.includes('--shard') && argv.includes('--no-shard')) {
242
+ console.error('用法:--shard 与 --no-shard 互斥——分片已是默认写入口径,显式回退主台账只需 --no-shard');
232
243
  process.exit(1);
233
244
  }
245
+ if (shardWrite && !ledgerDocChainActive(loadKitPolicy(root))) {
246
+ if (argv.includes('--shard')) {
247
+ console.error('❌ --shard 需要台账 doc 链口径(policy.ledgerDocChainSince),当前 policyVersion 无此键。\n 原因:分片与主台账是不同文件,只有 doc 链能跨来源接续;旧物理链口径下分片行会与主台账断链。\n 修法:在 .agents/kit.json 设 policyVersion: 8(或更高的支持版本)后重试,或改用 --no-shard 显式写主台账。');
248
+ process.exit(1);
249
+ }
250
+ shardWrite = false; // 存量装户回退:分片默认仅对 doc 链已激活(policyVersion ≥ 8)的装户生效
251
+ console.error('ℹ️ 分片默认写入口径需 doc 链口径(policy.ledgerDocChainSince),当前 policyVersion 未激活——本次写主台账 .agents/confirmations.jsonl。升级法:.agents/kit.json 设 policyVersion: 8。');
252
+ }
234
253
 
235
254
  const delegated = delegatedQuote !== null;
236
255
  if (delegated && autoMode) {
@@ -247,7 +266,7 @@ if (shardWrite && !ledgerDocChainActive(loadKitPolicy(root))) {
247
266
  }
248
267
  // AI 自治门(2026-09-30 ai-autonomy-trust;2026-09-30 hybrid-governance-explore-hardening 加隐式形态):
249
268
  // 读 .agents/trust-mode.json。除显式 --auto 外,Trusted(level=2)下的非 TTY 单份调用在 TTY 门处
250
- // 免旗标自动转入本分支(见「Trusted 自动泳道」)——L0/L1 门 / incidents 排除 / 逐阶段与 done 门照常执行。
269
+ // 免旗标自动转入本分支(见「Trusted 自动泳道」)——L1 门 / incidents 排除 / 逐阶段与 done 门照常执行。
251
270
  let trustConfig = null;
252
271
  if (autoMode) {
253
272
  try {
@@ -265,7 +284,7 @@ if (shardWrite && !ledgerDocChainActive(loadKitPolicy(root))) {
265
284
  console.error('❌ AI 自治模式一次仅接受一份文档(与 --delegated 同口径,逐件放行)');
266
285
  process.exit(1);
267
286
  }
268
- console.error(`⚠️ AI 自治模式(Trust L${trustConfig.level}):仅限 L0/L1 文档;Level 1 只放行 draft→approved,Level 2 放行至 done`);
287
+ console.error(`⚠️ AI 自治模式(Trust L${trustConfig.level}):仅限 L1 文档;Level 1 只放行 draft→approved,Level 2 放行至 done`);
269
288
  }
270
289
  if (delegated && !String(delegatedQuote).trim()) {
271
290
  console.error('用法:--delegated 须带用户对话原话(node .agents/scripts/confirm-doc.mjs <doc...> --delegated "<用户原话>")——原话入台账供事后对质,不可缺省');
@@ -286,7 +305,7 @@ if (shardWrite && !ledgerDocChainActive(loadKitPolicy(root))) {
286
305
  if (!delegated && !autoMode && !ttyForced && (!process.stdin.isTTY || !process.stdout.isTTY)) {
287
306
  // Trusted 自动泳道(2026-09-30 hybrid-governance-explore-hardening):trust-mode.json 为 Trusted
288
307
  // (enabled 且 level=2)时,非 TTY 调用(AI 会话)对文档**免手工触发**(无需 --delegated 原话 /
289
- // --auto 旗标)自动转入 AI 自治放行——单份、非放弃态才可隐式(与 --auto 同口径,L0/L1 门与
308
+ // --auto 旗标)自动转入 AI 自治放行——单份、非放弃态才可隐式(与 --auto 同口径,L1 门与
290
309
  // incidents 排除由主循环自治门照常执行);不满足则维持原拒绝(fail-closed,Standard/Strict 不隐式)。
291
310
  let implicitTrust = null;
292
311
  try {
@@ -294,8 +313,8 @@ if (shardWrite && !ledgerDocChainActive(loadKitPolicy(root))) {
294
313
  } catch { implicitTrust = { enabled: false, level: 0 }; }
295
314
  if (implicitTrust.enabled && implicitTrust.level === 2 && docs.length === 1 && !toTarget) {
296
315
  autoMode = true;
297
- trustConfig = implicitTrust; // 主循环自治门(L0/L1 与 incidents 排除)直读本配置——顶部 if (autoMode) 块先于 TTY 门执行,须在此补装
298
- console.error('⚠️ Trusted 自动泳道(隐式):单份 L0/L1 文档按 AI 自治放行(Level 2 全闭环,台账 source=ai-auto-trust-L2);L2/L3 与 incidents 仍须人工确认');
316
+ trustConfig = implicitTrust; // 主循环自治门(L1 与 incidents 排除)直读本配置——顶部 if (autoMode) 块先于 TTY 门执行,须在此补装
317
+ console.error('⚠️ Trusted 自动泳道(隐式):单份 L1 文档按 AI 自治放行(Level 2 全闭环,台账 source=ai-auto-trust-L2);L2/L3 与 incidents 仍须人工确认');
299
318
  } else {
300
319
  console.error('确认门须由用户在终端亲手运行(node .agents/scripts/confirm-doc.mjs <workflow/文档>...)——AI 会话内不可代确认;用户对话内明确确认后可用 --delegated "<用户原话>" 委托代录(台账如实记来源)');
301
320
  process.exit(1);
@@ -311,14 +330,14 @@ if (shardWrite && !ledgerDocChainActive(loadKitPolicy(root))) {
311
330
  // 单文档默认(2026-09-27 confirm-gate-one-per-call):delegated 没有TTY「逐份过目」的天然机制——
312
331
  // 多文档并录曾系统性塌掉三道阶段门(build.md「逐件确认不得并作一次」)。逐件调用:一次一份、
313
332
  // 每次带当次用户原话。TTY 形态不受限(用户亲手逐份过目键入,天然逐件)。
314
- // 协作道批量(2026-09-30 hybrid-governance-risk-lanes):L0/L1 可 `--batch` 多份一次代录——
333
+ // 协作道批量(2026-09-30 hybrid-governance-risk-lanes):L1 可 `--batch` 多份一次代录——
315
334
  // 逐份读「级别」,L2/L3 逐份拒绝(见主循环协作道门);台账行带 brief:true,检查 15 据此豁免并录告警。
316
335
  if (docs.length > 1) {
317
336
  if (!batchMode) {
318
- console.error(`❌ --delegated 一次仅接受一份文档(现 ${docs.length} 份:${docs.join(' ')})——逐件确认口径(build.md):每份一次调用、每次带当次用户原话;L0/L1 协作道批量可加 --batch`);
337
+ console.error(`❌ --delegated 一次仅接受一份文档(现 ${docs.length} 份:${docs.join(' ')})——逐件确认口径(build.md):每份一次调用、每次带当次用户原话;L1 协作道批量可加 --batch`);
319
338
  process.exit(1);
320
339
  }
321
- console.error(` 批量代录(--batch):${docs.length} 份,仅受理 L0/L1(协作道,异步审计兜底);L2/L3 逐份拒绝`);
340
+ console.error(` 批量代录(--batch):${docs.length} 份,仅受理 L1(协作道,异步审计兜底);L2/L3 逐份拒绝`);
322
341
  }
323
342
  }
324
343
  // 调用事实(2026-09-28 batch-ledger-audit):本次进程调用的批次标识与件序——
@@ -434,7 +453,7 @@ if (shardWrite && !ledgerDocChainActive(loadKitPolicy(root))) {
434
453
  // TTY 形态同样需要(事后对质问的是「放行时盘面上哪些文件在动」,与谁点的确认无关)。
435
454
  const scope = machineScope(root, doc);
436
455
  // AI 自治放行分支(2026-09-30 ai-autonomy-trust):
437
- // ① 仅 L0/L1(读 frontmatter「级别」,缺级别按不可自治处理——fail-closed);
456
+ // ① 仅 L1(读 frontmatter「级别」,缺级别按不可自治处理——fail-closed);
438
457
  // ② Level 1:仅 draft→approved;Level 2:draft→approved 与 approved→done;
439
458
  // ③ incidents 永不自治(open→fixed/fixed→closed 必须人工)。
440
459
  if (autoMode) {
@@ -443,16 +462,18 @@ if (shardWrite && !ledgerDocChainActive(loadKitPolicy(root))) {
443
462
  ? (target === 'approved' || target === 'done')
444
463
  : target === 'approved';
445
464
  if (!isLowLevel || !stageAllowed || doc.startsWith('workflow/incidents/')) {
446
- console.error(`❌ ${doc} 未过 AI 自治门(级别「${lvl || '缺失'}」/ 泳道「${lane}」/ 目标态「${target}」/ 类型):Level ${trustConfig.level} 仅授权泳道 low(intent 两字段一致 L0/L1;suspect 须先对齐「级别/risk_level」)的 ${trustConfig.level === 2 ? 'approved/done' : 'approved'} 且不含 incidents`);
465
+ console.error(`❌ ${doc} 未过 AI 自治门(级别「${lvl || '缺失'}」/ 泳道「${lane}」/ 目标态「${target}」/ 类型):Level ${trustConfig.level} 仅授权泳道 low(intent 级别 L1;suspect 须先对齐「级别/risk_level」)的 ${trustConfig.level === 2 ? 'approved/done' : 'approved'} 且不含 incidents`);
447
466
  refused++;
448
467
  continue;
449
468
  }
450
469
  fs.writeFileSync(abs, applyTransition(text, target, fp.slice(0, 16)));
470
+ // shard 走第三参 options(不进 entry):appendLedger(root, entry, { shard }) 的契约是 options 形参,
471
+ // 写进 entry 会让旗标混入台账行数据且分片开关失效(2026-10-13 ledger-shard-default 修复的存量缺陷)。
451
472
  appendLedger(root, {
452
- shard: shardWrite, ts: new Date().toISOString(), doc, stage: target, fingerprint: fp, prev: st,
473
+ ts: new Date().toISOString(), doc, stage: target, fingerprint: fp, prev: st,
453
474
  source: `ai-auto-trust-L${trustConfig.level}`, quote: `AI 自治放行 (Trust Level ${trustConfig.level})`,
454
475
  batch, seq, of: docs.length, scope,
455
- });
476
+ }, { shard: shardWrite });
456
477
  console.log(`✓ ${doc} ${st} → ${target}(指纹 ${fp.slice(0, 16)},AI 自治已记账:source=ai-auto-trust-L${trustConfig.level})`);
457
478
  confirmed++;
458
479
  seq++;
@@ -460,27 +481,27 @@ if (shardWrite && !ledgerDocChainActive(loadKitPolicy(root))) {
460
481
  }
461
482
  if (delegated) {
462
483
  // 协作道门(2026-09-30 hybrid-governance-risk-lanes;2026-10-02 caliber-convergence 起单源泳道):
463
- // --batch 批量仅受理泳道 low(intent 两字段一致 L0/L1;suspect 拒)——high/suspect 逐份拒绝落账;
484
+ // --batch 批量仅受理泳道 low(intent 级别 L1;suspect 拒)——high/suspect 逐份拒绝落账;
464
485
  // 不带 --batch 的单份代录不限级别(既有口径不变)。
465
486
  const low = lane === 'low';
466
487
  if (batchMode && !low) {
467
- console.error(`❌ ${doc} 泳道「${lane}」(级别「${lvl || '缺失'}」)不在协作道——--batch 批量代录仅限两字段一致的 L0/L1;suspect(级别/risk_level 缺失、非法或分歧)须先对齐两字段,L2/L3 防御道须逐份确认(本份未落账、未写盘)`);
488
+ console.error(`❌ ${doc} 泳道「${lane}」(级别「${lvl || '缺失'}」)不在协作道——--batch 批量代录仅限级别 L1;suspect(级别/risk_level 缺失、非法或分歧)须先对齐两字段,L2/L3 防御道须逐份确认(本份未落账、未写盘)`);
468
489
  refused++;
469
490
  continue;
470
491
  }
471
492
  fs.writeFileSync(abs, applyTransition(text, target, fp.slice(0, 16)));
472
493
  appendLedger(root, {
473
- shard: shardWrite, ts: new Date().toISOString(), doc, stage: target, fingerprint: fp, prev: st,
494
+ ts: new Date().toISOString(), doc, stage: target, fingerprint: fp, prev: st,
474
495
  source: 'chat-delegated', quote: String(delegatedQuote),
475
496
  // 调用事实(batch-ledger-audit):seq=本次调用内件序(从 1),of=本次调用总份数
476
497
  batch, seq, of: docs.length,
477
498
  // 改动面事实(A1 干净版):quote 降级为「人类可读摘要」,分辨力改由 scope(机器派生)+ fingerprint
478
499
  // (文档内容)承担——「不复用同句」纪律据此退役(复用一句摘要不再削弱任何判据)。
479
500
  scope,
480
- // 简洁审计标记(hybrid-governance-risk-lanes):L0/L1 协作道行带 brief(quote 仍入账供对质),
501
+ // 简洁审计标记(hybrid-governance-risk-lanes):L1 协作道行带 brief(quote 仍入账供对质),
481
502
  // 检查 15 并录审计对全 brief 批次豁免「确认并录」告警
482
503
  ...(low ? { brief: true } : {}),
483
- });
504
+ }, { shard: shardWrite });
484
505
  console.log(low
485
506
  ? `✓ ${doc} ${st} → ${target} [${lvl} 协作道/异步审计](指纹 ${fp.slice(0, 16)})`
486
507
  : `✓ ${doc} ${st} → ${target}(指纹 ${fp.slice(0, 16)},代录已记账:source=chat-delegated)`);
@@ -500,15 +521,70 @@ if (shardWrite && !ledgerDocChainActive(loadKitPolicy(root))) {
500
521
  }
501
522
  fs.writeFileSync(abs, applyTransition(text, target, fp.slice(0, 16)));
502
523
  appendLedger(root, {
503
- shard: shardWrite, ts: new Date().toISOString(), doc, stage: target, fingerprint: fp, prev: st,
524
+ ts: new Date().toISOString(), doc, stage: target, fingerprint: fp, prev: st,
504
525
  source: 'tty',
505
526
  batch, seq, of: docs.length, // 调用事实(形态无关:TTY 多文档天然逐件过目,of>1 不构成违规)
506
- });
527
+ }, { shard: shardWrite });
507
528
  console.log(`✓ ${doc} → ${target}(指纹 ${fp.slice(0, 16)},台账已记)`);
508
529
  confirmed++;
509
530
  seq++;
510
531
  }
511
532
  if (rl) rl.close();
533
+ // --- 终态件滚动归档(2026-10-13 archive-on-close;B9 自 Shipyard.Material 回灌,行为边界逐字对齐)---
534
+ // 关单即归档:本进程刚落终态的文档,其同名家族若已全终态且超窗、无活跃引用 → git mv 进
535
+ // workflow/archive/<家族最晚终态月>/{类型}/。动机:archive-sweep 是手工工具、无人跑,活跃目录
536
+ // 无限膨胀(源仓实测 885 篇、733 篇终态零引用——留痕在包源仓 git 历史与 CHANGELOG);挂进确认门
537
+ // 后活跃目录恒定。判定单源 = archive-sweep.analyze(禁第二套口径);归档单位 = 同名家族
538
+ // (check-loop 检查 1 的结构配对是家族双向在场审计,单篇归档会造配对断裂——源仓首批返工实证)。
539
+ // 时序:**台账行已写、状态已落盘之后**才 mv——check-loop 检查 15 的内容绑定按台账 doc 路径读
540
+ // 四目录直下文件,归档后该路径不在 buildDocList() 扫描面内,绑定检查自然跳过(归档件不再受
541
+ // 内容绑定约束,与既有归档批次同口径)。失败一律 fail-open:归档是卫生动作,不得回滚已完成的
542
+ // 确认(**归档失败不阻断关单**——与源仓语义逐字一致)。启用锚(照搬源仓,无 policy 锚):
543
+ // ① 仓库已建立归档约定(workflow/archive/ 存在)——真实装户仓有,临时 fixture 无,避免测试夹具
544
+ // 被静默搬走(也避免在未采纳归档的仓库里凭空造目录);② env 逃生门 CONFIRM_DOC_NO_ARCHIVE。
545
+ const archiveRoot = path.join(root, 'workflow', 'archive');
546
+ if (confirmed > 0 && !process.env.CONFIRM_DOC_NO_ARCHIVE && fs.existsSync(archiveRoot)) {
547
+ try {
548
+ const { analyze, buildDocList, loadLastTouch, parseStatus, TERMINAL, ACTIVE } = await import('./archive-sweep.mjs');
549
+ // 台账终态锚:kit 读侧单源 = readLedgerRows(主台账+分片透明合并;源仓走
550
+ // loadAllConfirmations——「每 doc 终态行最大日期」的 max 判据对去重口径无语义差,
551
+ // 见 archive-sweep.loadLedgerTs 同款注释)。CONFIRM_DOC_TEST_NOW 仅测试注入,生产不设。
552
+ const ledgerTs = {};
553
+ for (const e of readLedgerRows(root)) {
554
+ if (TERMINAL.has(e.stage) && e.doc) {
555
+ const d = (e.ts || '').slice(0, 10);
556
+ if (d && (!ledgerTs[e.doc] || d > ledgerTs[e.doc])) ledgerTs[e.doc] = d;
557
+ }
558
+ }
559
+ const allDocs = buildDocList(root);
560
+ // 窗口口径:本次确认的文档**刚**落终态,其台账 ts = 今日 → 21 天窗口必然拦下(窗口的用途是
561
+ // 「防归档仍在编辑的在途件」,而刚过确认门的终态件按定义已定稿,窗口对它冗余)。故对本进程
562
+ // 确认过的家族用 windowDays=-1(判据 `floor((today-tTime)/864e5) <= windowDays` 恒 false = 窗口
563
+ // 关闭;注意 0 不等于关闭——0 表示「同日也算未满窗」);未涉及的家族不在此次归档范围(touched
564
+ // 过滤),历史积压仍走 archive-sweep 手工批次 + 21 天窗口。
565
+ const r = analyze({
566
+ docs: allDocs,
567
+ ledgerTs,
568
+ lastTouch: loadLastTouch(root),
569
+ activeTexts: allDocs.filter((d) => ACTIVE.has(parseStatus(d.text))).map((d) => d.text),
570
+ windowDays: -1,
571
+ });
572
+ // 只归档「本次确认涉及的家族」——不越权清理历史积压(积压走 archive-sweep 手工批次)
573
+ const touched = new Set(docs.map((d) => path.basename(d)));
574
+ const mine = r.candidates.filter((x) => touched.has(x.file));
575
+ if (mine.length) {
576
+ for (const x of mine) {
577
+ const destDir = path.join(root, 'workflow', 'archive', x.month, x.dir);
578
+ fs.mkdirSync(destDir, { recursive: true });
579
+ execFileSync('git', ['mv', x.rel, path.join('workflow', 'archive', x.month, x.dir, x.file)], { cwd: root });
580
+ }
581
+ const fams = new Set(mine.map((x) => x.file));
582
+ console.log(`📦 归档 ${mine.length} 篇 / ${fams.size} 族 → workflow/archive/<终态月>/(家族原子;活跃目录不再累积终态件)`);
583
+ }
584
+ } catch (err) {
585
+ console.error(`⚠️ 归档跳过(fail-open,不影响已完成的确认):${err.message}`);
586
+ }
587
+ }
512
588
  console.log(`\n完成:确认 ${confirmed} 份 / 跳过 ${docs.length - confirmed - refused} 份 / 拒绝 ${refused} 份${delegated ? '(委托代录)' : ''}`);
513
589
  process.exit(refused ? 2 : 0);
514
590
  }
@@ -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);
@@ -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,详见上方输出)');
@@ -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';
@@ -41,10 +41,12 @@ const SECTIONS = [
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 === '历史教训/防复发') {