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
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env node
2
+ // compact-ledgers.test.mjs — 台账分片写入与 compact 归并 fixture 套件(2026-10-12 ledger-shard-write)
3
+ // 覆盖:分片写入定位 + 跨来源链衔接 / compact 默认 dry-run 不动盘 / 幂等 / 前缀不变式(既有行保序)/
4
+ // 读取失败中止 / 分片历史扫描与 compact 删除豁免 / 行未并入时仍拦丢行。
5
+ import fs from 'node:fs';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
8
+ import { fileURLToPath, pathToFileURL } from 'node:url';
9
+ import { spawnSync } from 'node:child_process';
10
+
11
+ const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
12
+ const { compactLedgers } = await import(pathToFileURL(path.join(SCRIPT_DIR, 'compact-ledgers.mjs')).href);
13
+ const { appendLedger } = await import(pathToFileURL(path.join(SCRIPT_DIR, 'confirm-doc.mjs')).href);
14
+ const { ledgerChainHash } = await import(pathToFileURL(path.join(SCRIPT_DIR, 'policy.mjs')).href);
15
+ // 夹具闭包(2026-10-12 fixture-closure-rootfix):整闭包自动跟随,不再手写依赖枚举(漏拷 = 假绿)
16
+ const { copyScriptClosure } = await import(pathToFileURL(path.join(SCRIPT_DIR, 'fixture-scripts.mjs')).href);
17
+
18
+ let pass = 0, fail = 0;
19
+ const check = (name, ok, detail = '') => {
20
+ if (ok) { pass++; console.log(` ok ${name}`); }
21
+ else { fail++; console.error(`FAIL ${name}${detail ? ' — ' + detail : ''}`); }
22
+ };
23
+
24
+ const DOC_A = 'workflow/intents/2026-10-01-a.md';
25
+ const DOC_B = 'workflow/intents/2026-10-01-b.md';
26
+ const baseEntry = (doc, stage = 'approved') => ({
27
+ ts: new Date().toISOString(), doc, stage, fingerprint: 'a'.repeat(64), source: 'tty',
28
+ });
29
+ // --shard 依赖 doc 链口径(policyVersion ≥ 8):分片与主台账是不同文件,只有 doc 链跨来源接续。
30
+ // 装 kit.json 的 policyVersion=8 来启用,与真实装户升级后的形态一致。
31
+ const enableDocChain = (root) => {
32
+ fs.mkdirSync(path.join(root, '.agents'), { recursive: true });
33
+ fs.writeFileSync(path.join(root, '.agents', 'kit.json'), JSON.stringify({ policyVersion: 8 }), 'utf8');
34
+ };
35
+
36
+ // ============ 场景 1:--shard 写入定位到分片,且跨来源链接得上 ============
37
+ {
38
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'shard-write-'));
39
+ enableDocChain(root);
40
+ // 先用主台账写一行(模拟该 doc 的历史行在主台账)
41
+ appendLedger(root, baseEntry(DOC_A));
42
+ const mainPath = path.join(root, '.agents', 'confirmations.jsonl');
43
+ check('S1 默认写主台账', fs.existsSync(mainPath));
44
+
45
+ // 再对同一 doc 用 --shard 写一行:前驱必须接主台账那行,而不是从 '' 重启
46
+ appendLedger(root, baseEntry(DOC_A, 'done'), { shard: true });
47
+ const shardPath = path.join(root, '.agents', 'confirmations.d', '2026-10-01-a.jsonl');
48
+ check('S1 --shard 写入分片文件(按 doc 基名命名)', fs.existsSync(shardPath));
49
+
50
+ const mainRows = fs.readFileSync(mainPath, 'utf8').trim().split('\n').map((l) => JSON.parse(l));
51
+ const shardRows = fs.readFileSync(shardPath, 'utf8').trim().split('\n').map((l) => JSON.parse(l));
52
+ check('S1 分片行的 prevHash 接主台账同 doc 行(跨来源衔接,链不断)',
53
+ shardRows[0].prevHash === mainRows[0].hash, `${shardRows[0].prevHash} vs ${mainRows[0].hash}`);
54
+ // 行内重算仍一致(doc 链前提)
55
+ const { hash, prevHash, ...rest } = shardRows[0];
56
+ check('S1 分片行行内哈希可重算', ledgerChainHash(rest, prevHash) === hash);
57
+ }
58
+
59
+ // ============ 场景 2:compact 默认 dry-run 不动盘 ============
60
+ {
61
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'shard-dry-'));
62
+ appendLedger(root, baseEntry(DOC_A));
63
+ appendLedger(root, baseEntry(DOC_B), { shard: true });
64
+ const mainPath = path.join(root, '.agents', 'confirmations.jsonl');
65
+ const before = fs.readFileSync(mainPath, 'utf8');
66
+ const shardPath = path.join(root, '.agents', 'confirmations.d', '2026-10-01-b.jsonl');
67
+
68
+ const r = compactLedgers(root); // 默认 execute:false
69
+ check('S2 dry-run 标记 skipped 且给出并入行数', r.ok && r.skipped && r.reason === 'dry-run' && r.added === 1, JSON.stringify(r));
70
+ check('S2 dry-run 主台账字节不变', fs.readFileSync(mainPath, 'utf8') === before);
71
+ check('S2 dry-run 分片仍在', fs.existsSync(shardPath));
72
+ }
73
+
74
+ // ============ 场景 3:--execute 归并 + 前缀不变式 ============
75
+ {
76
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'shard-exec-'));
77
+ appendLedger(root, baseEntry(DOC_A));
78
+ const mainPath = path.join(root, '.agents', 'confirmations.jsonl');
79
+ const before = fs.readFileSync(mainPath, 'utf8');
80
+ appendLedger(root, baseEntry(DOC_B), { shard: true });
81
+
82
+ const r = compactLedgers(root, { execute: true });
83
+ const after = fs.readFileSync(mainPath, 'utf8');
84
+ check('S3 execute 归并成功并计入 1 行', r.ok && !r.skipped && r.added === 1, JSON.stringify(r));
85
+ check('S3 既有行严格保序在前(append-only 前缀不变式)', after.startsWith(before), JSON.stringify(after.slice(0, 40)));
86
+ check('S3 分片已清理', !fs.existsSync(path.join(root, '.agents', 'confirmations.d', '2026-10-01-b.jsonl')));
87
+ }
88
+
89
+ // ============ 场景 4:幂等(重跑不重复追加)============
90
+ {
91
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'shard-idem-'));
92
+ enableDocChain(root);
93
+ appendLedger(root, baseEntry(DOC_A));
94
+ appendLedger(root, baseEntry(DOC_B), { shard: true });
95
+ compactLedgers(root, { execute: true });
96
+ const mainPath = path.join(root, '.agents', 'confirmations.jsonl');
97
+ const after1 = fs.readFileSync(mainPath, 'utf8');
98
+ const mergedLine = after1.trim().split('\n').pop(); // 归并时实际追加的那一行(含 prevHash/hash)
99
+
100
+ // 把**同一行原文**再放进分片重跑(模拟重复归并同批内容)——行文本逐字相同才会被去重
101
+ fs.mkdirSync(path.join(root, '.agents', 'confirmations.d'), { recursive: true });
102
+ fs.writeFileSync(path.join(root, '.agents', 'confirmations.d', '2026-10-01-b.jsonl'), mergedLine + '\n', 'utf8');
103
+ const r = compactLedgers(root, { execute: true });
104
+ const after2 = fs.readFileSync(mainPath, 'utf8');
105
+ check('S4 重复归并不重复追加(并集按整行去重)', r.ok && r.added === 0 && after1 === after2, `added=${r.added}`);
106
+ }
107
+
108
+ // ============ 场景 5:无分片 / 空分片 → 跳过且不报错 ============
109
+ {
110
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'shard-none-'));
111
+ const r = compactLedgers(root);
112
+ check('S5 无分片目录 → 跳过且 ok', r.ok && r.skipped);
113
+ }
114
+
115
+ // ============ 场景 6:分片历史扫描 + compact 删除豁免(真 git)============
116
+ {
117
+ const d = fs.mkdtempSync(path.join(os.tmpdir(), 'shard-hist-'));
118
+ const git = (...args) => spawnSync('git', args, { cwd: d, encoding: 'utf8' });
119
+ git('init', '-q', '.'); git('config', 'user.email', 't@t.io'); git('config', 'user.name', 't');
120
+ git('config', 'core.autocrlf', 'false');
121
+ fs.mkdirSync(path.join(d, '.agents', 'scripts'), { recursive: true });
122
+ copyScriptClosure(path.join(d, '.agents', 'scripts'), ['check-ledger-invariant.mjs']);
123
+ const mkDoc = (rel) => {
124
+ const p = path.join(d, ...rel.split('/'));
125
+ fs.mkdirSync(path.dirname(p), { recursive: true });
126
+ fs.writeFileSync(p, '---\n状态: approved\n---\n# doc\n', 'utf8');
127
+ };
128
+ const rowObj = (doc) => JSON.stringify({ ts: '2026-10-01T00:00:00Z', doc, stage: 'approved', fingerprint: 'a'.repeat(64), source: 'tty' });
129
+ const runGate = () => spawnSync(process.execPath, ['.agents/scripts/check-ledger-invariant.mjs'], { cwd: d, encoding: 'utf8' });
130
+
131
+ mkDoc(DOC_A); mkDoc(DOC_B);
132
+ // 基线:主台账一行 + 分片一行(分片持有 B 的行)
133
+ fs.mkdirSync(path.join(d, '.agents', 'confirmations.d'), { recursive: true });
134
+ fs.writeFileSync(path.join(d, '.agents', 'confirmations.jsonl'), rowObj(DOC_A) + '\n', 'utf8');
135
+ fs.writeFileSync(path.join(d, '.agents', 'confirmations.d', '2026-10-01-b.jsonl'), rowObj(DOC_B) + '\n', 'utf8');
136
+ git('add', '-A'); git('commit', '-qm', 'base with shard');
137
+ let r = runGate();
138
+ check('S6 分片入库且未删 → 历史扫描放行', r.status === 0, `exit=${r.status} ${r.stderr}`);
139
+
140
+ // compact 归并后删分片:删除豁免应认领
141
+ fs.writeFileSync(path.join(d, '.agents', 'confirmations.jsonl'), rowObj(DOC_A) + '\n' + rowObj(DOC_B) + '\n', 'utf8');
142
+ fs.rmSync(path.join(d, '.agents', 'confirmations.d', '2026-10-01-b.jsonl'));
143
+ git('add', '-A'); git('commit', '-qm', 'compact merged');
144
+ r = runGate();
145
+ check('S6 compact 归并删分片 → 删除豁免认领,exit 0', r.status === 0, `exit=${r.status} ${r.stderr}`);
146
+ check('S6 豁免记录点名 compact 归并形态', /compact 归并形态/.test(r.stderr), r.stderr.slice(0, 200));
147
+
148
+ // 未归并就删分片(丢行)→ 仍 hard 拦。用**从未进过主台账**的独立 doc C,
149
+ // 否则它的行已在主台账里(早先 compact 并过),删除豁免会正确地放行——那正是豁免该有的行为。
150
+ const DOC_C = 'workflow/intents/2026-10-01-c.md';
151
+ mkDoc(DOC_C);
152
+ fs.writeFileSync(path.join(d, '.agents', 'confirmations.d', '2026-10-01-c.jsonl'), rowObj(DOC_C) + '\n', 'utf8');
153
+ git('add', '-A'); git('commit', '-qm', 'add shard c');
154
+ fs.rmSync(path.join(d, '.agents', 'confirmations.d', '2026-10-01-c.jsonl'));
155
+ git('add', '-A'); git('commit', '-qm', 'drop shard c without merge');
156
+ r = runGate();
157
+ check('S6 未归并即删分片(丢行)→ exit 1 hard 拦', r.status === 1, `exit=${r.status}`);
158
+ check('S6 报文点名「分片删除丢行」', /分片删除丢行/.test(r.stderr), r.stderr.slice(0, 300));
159
+ }
160
+
161
+ console.log(`\n[compact-ledgers.test] PASS ${pass} / FAIL ${fail}`);
162
+ process.exit(fail ? 1 : 0);
@@ -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,10 +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(放弃态:取代/取消,留指纹与台账)
30
+ // node .agents/scripts/confirm-doc.mjs <doc...> --no-shard(回退主台账 .agents/confirmations.jsonl:
31
+ // 分片已是默认写入口径,环境变量 CONFIRM_SHARDED=0 同效;里程碑可选跑 compact-ledgers.mjs 归并分片)
28
32
  // 多文档一次传入:TTY 模式天然逐份过目键入;--delegated 默认一次仅一份(2026-09-27
29
- // confirm-gate-one-per-call:多份并录曾系统性塌掉 build.md「逐件确认」三道门),L0/L1 协作道可
33
+ // confirm-gate-one-per-call:多份并录曾系统性塌掉 build.md「逐件确认」三道门),L1 协作道可
30
34
  // `--batch` 多份一次代录(2026-09-30 hybrid-governance-risk-lanes):逐份读「级别」,L2/L3 逐份拒绝
31
35
  // (防御道不批量);台账行带 `brief:true` 简洁审计标记(quote 原话仍入账供对质),check-loop 检查 15
32
36
  // 并录审计对全行带标记的批次豁免「确认并录」告警。--auto 一次仍仅一份(自治放行不批量)。
@@ -38,9 +42,10 @@ import fs from 'node:fs';
38
42
  import path from 'node:path';
39
43
  import readline from 'node:readline';
40
44
  import { createHash, randomBytes } from 'node:crypto';
41
- import { spawnSync } from 'node:child_process';
45
+ import { execFileSync, spawnSync } from 'node:child_process';
46
+ import { readLedgerEntries, readLedgerRows } from './ledger-loader.mjs';
42
47
  import { confirmGateFor, clarifyGateFor, doneGateFor, laneOfEntry, laneOfDoc } from './stage-gates.mjs';
43
- import { loadKitPolicy, hasFreshVerifyLine, ledgerChainHash, scopeDigest } from './policy.mjs';
48
+ import { loadKitPolicy, hasFreshVerifyLine, ledgerChainHash, ledgerDocChainActive, ledgerDocChainPrev, scopeDigest } from './policy.mjs';
44
49
 
45
50
  // hasFreshVerify(root, doc):读 <root>/.agents/verifications.jsonl 判 24h 窗口绿行(判定单源
46
51
  // policy.hasFreshVerifyLine)。只读,台账缺失/不可读一律 false(=无凭证,advisory 层 fail-open)。
@@ -148,22 +153,40 @@ export function applyTransition(text, targetStage, fingerprint16) {
148
153
  }
149
154
 
150
155
  // appendLedger(root, entry):追加一行 JSON(目录不存在自动建;只 append 不重写)
151
- // 哈希链(2026-10-09 engine-quality-round2 D):每行追加 prevHash(末行 hash,末行无 hash/空文件 → '')
152
- // 与 hash(ledgerChainHash 单源计算,check-loop 15 同函数验链)——「就地改/删中间行」必留断链痕迹;
153
- // 历史行零回填(无 hash 行后链重启),整文件重写不可机器防(边界声明见 policy.ledgerChainHash)。
154
- export function appendLedger(root, entry) {
155
- const p = path.join(root, '.agents', 'confirmations.jsonl');
156
+ // 哈希链(2026-10-09 engine-quality-round2 D):每行追加 prevHash 与 hash(ledgerChainHash 单源计算,
157
+ // check-loop 15 同函数验链)——「就地改/删中间行」必留断链痕迹;历史行零回填(无 hash 行后链重启),
158
+ // 整文件重写不可机器防(边界声明见 policy.ledgerChainHash)。
159
+ // 链域(2026-10-12 ledger-doc-chain):policy.ledgerDocChainSince 存在且合法 → prevHash 取**同行 doc 的
160
+ // 最后一个带 hash 行**(ledgerDocChainPrev),不再取物理末行。理由见 POLICIES v8 注释:物理链隐含
161
+ // 「单写者顺序追加」前提,而并发 append / git 并集合并 / 分片归并三种**正常协作**都会打断它。
162
+ // 缺键(v1-v7)→ 走旧物理链口径,存量零变化。
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。
169
+ export function appendLedger(root, entry, { shard = false } = {}) {
170
+ const p = shard && entry.doc
171
+ ? path.join(root, '.agents', 'confirmations.d', `${path.basename(entry.doc, '.md')}.jsonl`)
172
+ : path.join(root, '.agents', 'confirmations.jsonl');
156
173
  fs.mkdirSync(path.dirname(p), { recursive: true });
157
174
  let prevHash = '';
158
175
  try {
159
- if (fs.existsSync(p)) {
176
+ if (ledgerDocChainActive(loadKitPolicy(root))) {
177
+ // doc 链取前驱走**台账视图**(主台账 + 分片)而非单个文件:--shard 写入时,该 doc 的历史行
178
+ // 很可能在主台账里,只读分片文件会从 '' 重启 → 链断裂。视图取法天然跨来源衔接。
179
+ const rows = readLedgerEntries(root).map((e) => e.row);
180
+ prevHash = ledgerDocChainPrev(rows, entry.doc);
181
+ } else if (fs.existsSync(p)) {
182
+ // 旧物理链口径:取**本次写入目标文件**的末行 hash(v1-v7 存量行为,逐字不变)
160
183
  const lines = fs.readFileSync(p, 'utf8').split(/\r?\n/).filter((l) => l.trim());
161
184
  if (lines.length) {
162
185
  const last = JSON.parse(lines[lines.length - 1]);
163
186
  prevHash = last && typeof last.hash === 'string' ? last.hash : '';
164
187
  }
165
188
  }
166
- } catch { /* 末行不可解析 → prevHash ''(链从本行重启;断裂由 check-loop 15 出账) */ }
189
+ } catch { /* 不可解析 → prevHash ''(链从本行重启;断裂由 check-loop 15 出账) */ }
167
190
  const { hash: _h, prevHash: _p, ...rest } = entry;
168
191
  const row = { ...rest, prevHash, hash: ledgerChainHash(rest, prevHash) };
169
192
  fs.appendFileSync(p, JSON.stringify(row) + '\n');
@@ -189,7 +212,8 @@ if (isMain) {
189
212
  let delegatedQuote = null; // null = TTY 模式;字符串 = 委托代录的用户对话原话
190
213
  let toTarget = null; // null = 默认前向跳转;superseded|cancelled = 放弃态显式目标
191
214
  let autoMode = false; // AI 自治模式(信任等级授权,2026-09-30 ai-autonomy-trust)
192
- let batchMode = false; // 协作道批量代录(--batch,2026-09-30 hybrid-governance-risk-lanes:仅 --delegated + L0/L1)
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 显式回退主台账
193
217
  const docs = [];
194
218
  for (let i = 0; i < argv.length; i++) {
195
219
  if (argv[i] === '--root') root = path.resolve(argv[++i]);
@@ -197,12 +221,36 @@ if (isMain) {
197
221
  else if (argv[i] === '--to') toTarget = argv[++i] ?? '';
198
222
  else if (argv[i] === '--auto') autoMode = true;
199
223
  else if (argv[i] === '--batch') batchMode = true;
224
+ else if (argv[i] === '--shard') shardWrite = true;
225
+ else if (argv[i] === '--no-shard') shardWrite = false;
200
226
  else docs.push(argv[i].replace(/\\/g, '/'));
201
227
  }
202
228
  if (toTarget !== null && !ABANDON_TARGETS.includes(toTarget)) {
203
229
  console.error(`用法:--to 仅接受 superseded|cancelled(现「${toTarget}」)——cancelled 自 draft/approved/open/fixed;superseded 自 approved/done/fixed/closed(未确认态只能 cancelled;终态间仅允许 done/closed→superseded)`);
204
230
  process.exit(1);
205
231
  }
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');
243
+ process.exit(1);
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
+ }
253
+
206
254
  const delegated = delegatedQuote !== null;
207
255
  if (delegated && autoMode) {
208
256
  console.error('❌ --auto 与 --delegated 互斥:自治模式是用户预先授权的机器放行,委托代录是单次对话确认');
@@ -218,7 +266,7 @@ if (isMain) {
218
266
  }
219
267
  // AI 自治门(2026-09-30 ai-autonomy-trust;2026-09-30 hybrid-governance-explore-hardening 加隐式形态):
220
268
  // 读 .agents/trust-mode.json。除显式 --auto 外,Trusted(level=2)下的非 TTY 单份调用在 TTY 门处
221
- // 免旗标自动转入本分支(见「Trusted 自动泳道」)——L0/L1 门 / incidents 排除 / 逐阶段与 done 门照常执行。
269
+ // 免旗标自动转入本分支(见「Trusted 自动泳道」)——L1 门 / incidents 排除 / 逐阶段与 done 门照常执行。
222
270
  let trustConfig = null;
223
271
  if (autoMode) {
224
272
  try {
@@ -236,7 +284,7 @@ if (isMain) {
236
284
  console.error('❌ AI 自治模式一次仅接受一份文档(与 --delegated 同口径,逐件放行)');
237
285
  process.exit(1);
238
286
  }
239
- 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`);
240
288
  }
241
289
  if (delegated && !String(delegatedQuote).trim()) {
242
290
  console.error('用法:--delegated 须带用户对话原话(node .agents/scripts/confirm-doc.mjs <doc...> --delegated "<用户原话>")——原话入台账供事后对质,不可缺省');
@@ -257,7 +305,7 @@ if (isMain) {
257
305
  if (!delegated && !autoMode && !ttyForced && (!process.stdin.isTTY || !process.stdout.isTTY)) {
258
306
  // Trusted 自动泳道(2026-09-30 hybrid-governance-explore-hardening):trust-mode.json 为 Trusted
259
307
  // (enabled 且 level=2)时,非 TTY 调用(AI 会话)对文档**免手工触发**(无需 --delegated 原话 /
260
- // --auto 旗标)自动转入 AI 自治放行——单份、非放弃态才可隐式(与 --auto 同口径,L0/L1 门与
308
+ // --auto 旗标)自动转入 AI 自治放行——单份、非放弃态才可隐式(与 --auto 同口径,L1 门与
261
309
  // incidents 排除由主循环自治门照常执行);不满足则维持原拒绝(fail-closed,Standard/Strict 不隐式)。
262
310
  let implicitTrust = null;
263
311
  try {
@@ -265,8 +313,8 @@ if (isMain) {
265
313
  } catch { implicitTrust = { enabled: false, level: 0 }; }
266
314
  if (implicitTrust.enabled && implicitTrust.level === 2 && docs.length === 1 && !toTarget) {
267
315
  autoMode = true;
268
- trustConfig = implicitTrust; // 主循环自治门(L0/L1 与 incidents 排除)直读本配置——顶部 if (autoMode) 块先于 TTY 门执行,须在此补装
269
- 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 仍须人工确认');
270
318
  } else {
271
319
  console.error('确认门须由用户在终端亲手运行(node .agents/scripts/confirm-doc.mjs <workflow/文档>...)——AI 会话内不可代确认;用户对话内明确确认后可用 --delegated "<用户原话>" 委托代录(台账如实记来源)');
272
320
  process.exit(1);
@@ -282,14 +330,14 @@ if (isMain) {
282
330
  // 单文档默认(2026-09-27 confirm-gate-one-per-call):delegated 没有TTY「逐份过目」的天然机制——
283
331
  // 多文档并录曾系统性塌掉三道阶段门(build.md「逐件确认不得并作一次」)。逐件调用:一次一份、
284
332
  // 每次带当次用户原话。TTY 形态不受限(用户亲手逐份过目键入,天然逐件)。
285
- // 协作道批量(2026-09-30 hybrid-governance-risk-lanes):L0/L1 可 `--batch` 多份一次代录——
333
+ // 协作道批量(2026-09-30 hybrid-governance-risk-lanes):L1 可 `--batch` 多份一次代录——
286
334
  // 逐份读「级别」,L2/L3 逐份拒绝(见主循环协作道门);台账行带 brief:true,检查 15 据此豁免并录告警。
287
335
  if (docs.length > 1) {
288
336
  if (!batchMode) {
289
- console.error(`❌ --delegated 一次仅接受一份文档(现 ${docs.length} 份:${docs.join(' ')})——逐件确认口径(build.md):每份一次调用、每次带当次用户原话;L0/L1 协作道批量可加 --batch`);
337
+ console.error(`❌ --delegated 一次仅接受一份文档(现 ${docs.length} 份:${docs.join(' ')})——逐件确认口径(build.md):每份一次调用、每次带当次用户原话;L1 协作道批量可加 --batch`);
290
338
  process.exit(1);
291
339
  }
292
- console.error(` 批量代录(--batch):${docs.length} 份,仅受理 L0/L1(协作道,异步审计兜底);L2/L3 逐份拒绝`);
340
+ console.error(` 批量代录(--batch):${docs.length} 份,仅受理 L1(协作道,异步审计兜底);L2/L3 逐份拒绝`);
293
341
  }
294
342
  }
295
343
  // 调用事实(2026-09-28 batch-ledger-audit):本次进程调用的批次标识与件序——
@@ -405,7 +453,7 @@ if (isMain) {
405
453
  // TTY 形态同样需要(事后对质问的是「放行时盘面上哪些文件在动」,与谁点的确认无关)。
406
454
  const scope = machineScope(root, doc);
407
455
  // AI 自治放行分支(2026-09-30 ai-autonomy-trust):
408
- // ① 仅 L0/L1(读 frontmatter「级别」,缺级别按不可自治处理——fail-closed);
456
+ // ① 仅 L1(读 frontmatter「级别」,缺级别按不可自治处理——fail-closed);
409
457
  // ② Level 1:仅 draft→approved;Level 2:draft→approved 与 approved→done;
410
458
  // ③ incidents 永不自治(open→fixed/fixed→closed 必须人工)。
411
459
  if (autoMode) {
@@ -414,16 +462,18 @@ if (isMain) {
414
462
  ? (target === 'approved' || target === 'done')
415
463
  : target === 'approved';
416
464
  if (!isLowLevel || !stageAllowed || doc.startsWith('workflow/incidents/')) {
417
- 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`);
418
466
  refused++;
419
467
  continue;
420
468
  }
421
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 修复的存量缺陷)。
422
472
  appendLedger(root, {
423
473
  ts: new Date().toISOString(), doc, stage: target, fingerprint: fp, prev: st,
424
474
  source: `ai-auto-trust-L${trustConfig.level}`, quote: `AI 自治放行 (Trust Level ${trustConfig.level})`,
425
475
  batch, seq, of: docs.length, scope,
426
- });
476
+ }, { shard: shardWrite });
427
477
  console.log(`✓ ${doc} ${st} → ${target}(指纹 ${fp.slice(0, 16)},AI 自治已记账:source=ai-auto-trust-L${trustConfig.level})`);
428
478
  confirmed++;
429
479
  seq++;
@@ -431,11 +481,11 @@ if (isMain) {
431
481
  }
432
482
  if (delegated) {
433
483
  // 协作道门(2026-09-30 hybrid-governance-risk-lanes;2026-10-02 caliber-convergence 起单源泳道):
434
- // --batch 批量仅受理泳道 low(intent 两字段一致 L0/L1;suspect 拒)——high/suspect 逐份拒绝落账;
484
+ // --batch 批量仅受理泳道 low(intent 级别 L1;suspect 拒)——high/suspect 逐份拒绝落账;
435
485
  // 不带 --batch 的单份代录不限级别(既有口径不变)。
436
486
  const low = lane === 'low';
437
487
  if (batchMode && !low) {
438
- 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 防御道须逐份确认(本份未落账、未写盘)`);
439
489
  refused++;
440
490
  continue;
441
491
  }
@@ -448,10 +498,10 @@ if (isMain) {
448
498
  // 改动面事实(A1 干净版):quote 降级为「人类可读摘要」,分辨力改由 scope(机器派生)+ fingerprint
449
499
  // (文档内容)承担——「不复用同句」纪律据此退役(复用一句摘要不再削弱任何判据)。
450
500
  scope,
451
- // 简洁审计标记(hybrid-governance-risk-lanes):L0/L1 协作道行带 brief(quote 仍入账供对质),
501
+ // 简洁审计标记(hybrid-governance-risk-lanes):L1 协作道行带 brief(quote 仍入账供对质),
452
502
  // 检查 15 并录审计对全 brief 批次豁免「确认并录」告警
453
503
  ...(low ? { brief: true } : {}),
454
- });
504
+ }, { shard: shardWrite });
455
505
  console.log(low
456
506
  ? `✓ ${doc} ${st} → ${target} [${lvl} 协作道/异步审计](指纹 ${fp.slice(0, 16)})`
457
507
  : `✓ ${doc} ${st} → ${target}(指纹 ${fp.slice(0, 16)},代录已记账:source=chat-delegated)`);
@@ -471,15 +521,70 @@ if (isMain) {
471
521
  }
472
522
  fs.writeFileSync(abs, applyTransition(text, target, fp.slice(0, 16)));
473
523
  appendLedger(root, {
474
- ts: new Date().toISOString(), doc, stage: target, fingerprint: fp, prev: st,
524
+ ts: new Date().toISOString(), doc, stage: target, fingerprint: fp, prev: st,
475
525
  source: 'tty',
476
526
  batch, seq, of: docs.length, // 调用事实(形态无关:TTY 多文档天然逐件过目,of>1 不构成违规)
477
- });
527
+ }, { shard: shardWrite });
478
528
  console.log(`✓ ${doc} → ${target}(指纹 ${fp.slice(0, 16)},台账已记)`);
479
529
  confirmed++;
480
530
  seq++;
481
531
  }
482
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
+ }
483
588
  console.log(`\n完成:确认 ${confirmed} 份 / 跳过 ${docs.length - confirmed - refused} 份 / 拒绝 ${refused} 份${delegated ? '(委托代录)' : ''}`);
484
589
  process.exit(refused ? 2 : 0);
485
590
  }