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,66 @@
1
+ #!/usr/bin/env node
2
+ // 闭环配对(增量版):本次暂存的 intents/ incidents/ 入口文档必须已有同名 plan(L2/L3 另须同名 spec)
3
+ // 目的:把 check-loop「配对断裂」hard-block 的发现时机从 pre-push 提前到 commit
4
+ // (papercut 2026-09-15:maintain.md 只写「incident≡intent 免另立 intent」,未把配 plan 写成显式步骤,
5
+ // 按文档字面执行三次 commit 全绿、push 才爆红,只能后补;
6
+ // 2026-09-27 audit-gate-hardening 补齐 spec 档——此前只查 plan,L2/L3 缺 spec 仍要拖到 push 才爆红)
7
+ // 口径与 check-loop 检查 1 同款分档:入口文档(intents/ incidents/ 直下 .md)↔ workflow/plans/ 同名,
8
+ // L2/L3 级别另须 workflow/specs/ 同名;同 commit 携带 plan/spec 合法(检查的是工作区文件存在,staged 新建同样命中)。
9
+ // 自 sh 版移植 node(2026-10-08 门禁去 sh 化第二批;2026-10-13 自 Shipyard.Material 参考实现回灌)——语义逐条对齐。
10
+ // 已知边界(沿 sh 版):legacy 豁免与级别读取均基于工作区文件而非暂存 blob——add 后又改工作区时
11
+ // 可能误豁免/误拦(逐文件取 blob 的 fork 成本大于收益,接受)。
12
+ // git 调用带 `-c core.quotepath=off`:quotepath 默认 true 时非 ASCII 路径被引号转义,
13
+ // workflow/ 前缀匹配失配 → 门禁静默跳过(2026-09-27 gate-coverage 教训)。
14
+ import { spawnSync } from 'node:child_process';
15
+ import fs from 'node:fs';
16
+ import path from 'node:path';
17
+ import process from 'node:process';
18
+
19
+ const git = (args) => {
20
+ const r = spawnSync('git', args, { encoding: 'utf8' });
21
+ return { ok: !r.error && r.status === 0, out: (r.stdout || '').trim() };
22
+ };
23
+ const root = git(['rev-parse', '--show-toplevel']);
24
+ if (!root.ok) process.exit(1);
25
+ process.chdir(root.out);
26
+
27
+ const staged = git(['-c', 'core.quotepath=off', 'diff', '--cached', '--name-only', '--diff-filter=ACMR']);
28
+ if (!staged.ok) process.exit(1);
29
+ const entries = staged.out.split('\n').filter((f) => /^workflow\/(intents|incidents)\/[^/]+\.md$/.test(f) && !f.endsWith('_TEMPLATE.md'));
30
+
31
+ let fail = 0;
32
+ const block = (lines) => { console.log('闭环配对(增量)— BLOCK:'); for (const l of lines) console.log(' ' + l); fail = 1; };
33
+
34
+ for (const f of entries) {
35
+ // 闭环引擎启用前的回填件带「流程: legacy」豁免配对检查(与 check-loop 同口径)
36
+ let legacy = false, lvl = '';
37
+ try {
38
+ for (const line of fs.readFileSync(f, 'utf8').split(/\r?\n/)) {
39
+ if (line.startsWith('流程: legacy')) legacy = true;
40
+ const m = /^级别:\s*(.*)$/.exec(line);
41
+ if (m && lvl === '') lvl = m[1].trim();
42
+ if (legacy && lvl !== '') break;
43
+ }
44
+ } catch { /* 读不到按未豁免处理(沿 sh 版 grep 失败即不豁免的语义) */ }
45
+ if (legacy) continue;
46
+ const base = path.basename(f, '.md');
47
+ if (!fs.existsSync(path.join('workflow', 'plans', `${base}.md`))) {
48
+ block([
49
+ `入口缺同名 plan: ${f}(应在 workflow/plans/${base}.md)`,
50
+ 'L1 用 fill-plan.mjs --level L1(入口/改动方案/约束与风险/验证计划/验收标准),不另立 intent',
51
+ ]);
52
+ }
53
+ if (lvl === 'L2' || lvl === 'L3') {
54
+ if (!fs.existsSync(path.join('workflow', 'specs', `${base}.md`))) {
55
+ block([
56
+ `入口缺同名 spec: ${f}(级别 ${lvl} 须三件套,应在 workflow/specs/${base}.md)`,
57
+ '与 pre-push check-loop 检查 1 同口径——先起草 spec 走 design.md,再提交',
58
+ ]);
59
+ }
60
+ }
61
+ }
62
+
63
+ if (fail !== 0) {
64
+ console.error('pre-commit: 入口文档配对不全(plan / L2-L3 spec),提交已阻止(与 pre-push check-loop 同口径,发现时机提前到 commit)');
65
+ }
66
+ process.exit(fail);
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ // wiki 台账一致性(增量版):本次暂存触及 wiki/ 时跑 verify 三方校验(磁盘 ↔ INDEX ↔ 看板 DATA)
3
+ // 目的:给「登记动作」装触发点——verify 此前无任何钩子调用,只能靠「触及 wiki 时手跑」的文字纪律
4
+ // (incident 2026-09-16-wiki-drift-and-verifier-gaps 认定的根因);本脚本把发现时机提前到 commit。
5
+ // 口径与 .agents/scripts/verify-wiki-consistency.mjs 一致:活层新增/删除/移动未登记、数据维护批次目录未入台账即拦。
6
+ // 自 sh 版移植 node(2026-10-08 门禁去 sh 化第二批;2026-10-13 自 Shipyard.Material 参考实现回灌)——语义逐条对齐。
7
+ // 注意 git 调用带 `-c core.quotepath=off`:quotepath 默认 true 时中文路径被转义加引号,^wiki/ 前缀
8
+ // 匹配失配 → 门禁静默跳过(2026-09-27 gate-coverage 教训)。
9
+ import { spawnSync } from 'node:child_process';
10
+ import path from 'node:path';
11
+ import process from 'node:process';
12
+
13
+ const git = (args) => {
14
+ const r = spawnSync('git', args, { encoding: 'utf8' });
15
+ return { ok: !r.error && r.status === 0, out: (r.stdout || '').trim() };
16
+ };
17
+ const root = git(['rev-parse', '--show-toplevel']);
18
+ if (!root.ok) process.exit(1);
19
+ process.chdir(root.out);
20
+
21
+ const touched = git(['-c', 'core.quotepath=off', 'diff', '--cached', '--name-only']);
22
+ if (!touched.ok || !touched.out.split('\n').some((f) => f.startsWith('wiki/'))) process.exit(0);
23
+
24
+ const r = spawnSync(process.execPath, [path.join('.agents', 'scripts', 'verify-wiki-consistency.mjs')], { stdio: 'inherit' });
25
+ if (r.status !== 0) {
26
+ console.log('wiki 台账(增量)— BLOCK:');
27
+ console.log(' 先跑 node .agents/scripts/gen-wiki-board.mjs 补 INDEX/看板(或补 wiki/数据维护/README.md 登记行),再原路重试提交');
28
+ process.exit(1);
29
+ }
30
+ process.exit(0);
@@ -3,9 +3,12 @@
3
3
  # # 注释与空行忽略;本文件缺失或清单为空 = 检查跳过(装户无感升级)。
4
4
  # 正则语法错误行 → 门禁 fail-closed 阻断(防静默空洞,修配置后重试)。
5
5
  # 语义:命中任一模式 = 本次改动触及「一处定义、多处消费」的契约 / 规则面(L2 触达面,
6
- # 判据见 .agents/commands/new-task.md §级别判断)。此时须有活跃(draft/approved / open/fixed)
7
- # L2/L3 入口罩着;活跃入口全为 L0/L1 会被 pre-commit「触达面判低」拦截(三选修法见门禁消息)。
6
+ # 判据单源见 .agents/scripts/level-criteria.mjs 的 L2_SURFACES)。此时须有活跃(draft/approved / open/fixed)
7
+ # L2/L3 入口罩着;活跃入口全为 L1 会被 pre-commit「触达面判低」拦截(三选修法见门禁消息)。
8
8
  # 无任何活跃入口时仅 advisory——docs / bootstrap 豁免类改动(new-task.md §intent 豁免)不受阻。
9
+ # 漏登面审计:node .agents/scripts/lane-surface-audit.mjs(advisory,恒 exit 0)——近窗口(默认 30 天)
10
+ # L2/L3 任务反复触及(≥3 个不同入口文档)却无文件被本清单命中的目录 = 候选漏登面,供人工评估补登;
11
+ # 月度手跑,改共享契约频繁期可随时跑(候选 ≠ 必登——高频触及 ≠ 契约面,机器不猜语义)。
9
12
  # 本仓默认面(引擎 / 契约面,行为承载目录——commands/roles 纯提示文本属 docs 级不列入;按项目自行改):
10
13
  ^templates/
11
14
  ^\.agents/(scripts|hooks)/
@@ -0,0 +1,30 @@
1
+ # 风险级别路径提示清单(单源消费方:templates/_agents/scripts/level-criteria.mjs)
2
+ #
3
+ # 用途:登记「哪些路径一碰就**至少**是 L2 / L3」。定级是**意图面**的语义判定(「一处定义、多处消费」),
4
+ # 机器判不了语义;但「路径下限」可以登记,避免明显高风险的改动被拍脑袋判成 L1。
5
+ # 与 lane-surfaces.txt 的分工:那是**提交面**的触达判据(改这些面须有活跃 L2/L3 入口),
6
+ # 本文件是**意图面**的档位提示(改这些面就该按更高档走)。两者不同档,各司其职。
7
+ #
8
+ # 格式:每行一条 `<档位> <ERE>`,ERE 为 grep -E 兼容正则,匹配仓库相对路径(/ 分隔)。
9
+ # 档位**只收 L2 / L3**:本清单登记的是「一碰就至少是中/高风险面」的下限。L1 不构成门槛(L0 已退役),
10
+ # 登记了只会让提交面判低门(L2 触达面门)对无关改动出手——那正是要避免的噪声。
11
+ # **ERE 与注释必须分两行**:写 `L3 ^a\.sql$ # 迁移 SQL` 会把 `# 迁移 SQL` 编进正则,
12
+ # 若含 $ 锚点该条就永不命中,且不报任何错(路径下限与提交面兜底同时静默失效)。
13
+ # 确需匹配路径里的 # 时写 \#。非法档位 / 行内注释 / 正则语法错 → **fail-loud 抛错**
14
+ # (写错配置比没配置危险:没配置会提示补,写错会误判)。
15
+ # # 注释与空行忽略;本文件缺失或清单为空 = 纯语义定级(判据面全程静默,不阻断)。
16
+ #
17
+ # 默认留空(刻意):本清单内容是**项目私有**的——按你自己的代码布局登记,照抄别人的清单等于没登记。
18
+ # 建议起步:从「改什么几乎必然是 L2/L3」的高频面挑 3~5 条先登记,跑一段时间再补。
19
+ #
20
+ # ---- 示例(取消注释并按项目实际路径改写;本仓为 node 包源,下例仅示意形态)----
21
+ # # 迁移 SQL(注释单独成行,不要接在 ERE 后面)
22
+ # L3 ^src/db/migrations/.*\.sql$
23
+ # # schema 定义
24
+ # L3 ^src/db/schema/
25
+ # # 认证与中间件管线 / DI 链
26
+ # L3 ^(src/(auth|middleware)/|src/.*\.(auth|di)\.(ts|js)$)
27
+ # # 共享契约(DTO / 接口签名)
28
+ # L2 ^src/contracts/
29
+ # # 编码权威(取号 / 命名规则生成器)
30
+ # L2 ^src/coding/
@@ -0,0 +1,112 @@
1
+ # papercut-debt 控制回路 — 设计(2026-10-07 拍板;B10a 自 Shipyard.Material 回灌)
2
+
3
+ > 本文件是回路的**设计单一真相源**。传感器 / 控制器 / 执行器技能 / 记忆文件均按此实现,改设计先改本文件。
4
+ > 范围:把 `workflow/papercuts.md` 的未升级存量债驱动到 0。执行器为**对话内手动触发**(kit 不为回路接线 CI/定时)。
5
+
6
+ ## 1. 设定点(Set point)
7
+
8
+ **`workflow/papercuts.md` 中未消债条目数 → 0。**
9
+
10
+ - **判定口径(单源,本文件)**:
11
+ - **已消债(settled)** = 该行含 `✅`(真修完)**或** 含 `⏸ 暂缓(日期):理由`(人读过、判失效)。
12
+ - **未消债(debt)** = 其余,即 `debt = 总数 − settled`。
13
+ - `⏸ 暂缓` **无理由者仍计债** —— 堵"标个符号清零"的路,迫使人真读一遍。
14
+ - **环境约束类(`[env]` 前缀)**:「位置」列以 `[env]` 开头(外层可带反引号包裹,传感器兼容)的行**不进债池**(`scan()` 不返回,控制器 import 债池数组天然不选),单列 `envPending`(仅未 settled 的)。清账两路径:环境前提复测消失 → `⏸ 暂缓(日期):前提已消失`;定性实为代码缺陷 → 去前缀转正回债池。理由:环境自愈不该由回路轮询发现(源仓首轮实证:pypi 恢复后自愈的环境条目,却占 debt 名额等人工定性一轮——留痕在包源仓 git 历史与 CHANGELOG)。
15
+ - 两类分开计数(`resolved` / `deferredReasoned`),避免"暂缓"冒充"修完"。
16
+ - 终态:全部条目 settled。**"暂缓"是合法终态,但必须有人写理由。**
17
+ - 基线:装户首次跑传感器时的 `debt` 读数即基线,随后每轮 `debt` 恰好 -1。装户侧不预填数字。
18
+
19
+ > **首轮修正(设计教训,单源保留)**:初版口径只有 `✅` 判债、`⏸` 不消债。后果是执行器走"判失效"分支(写 `⏸`)后 `debt` 不减,完成判据"debt 恰好 -1"永远达不到,且控制器下一轮仍选中同一条 → **回路空转**。现口径按上表,`settled` 由传感器单源判定、控制器 import 复用(两处各判一次必然漂移,见 §3 末)。
20
+
21
+ 为什么选它(三条依据):
22
+
23
+ 1. **计数客观**:`papercuts.md` 是人写的自由文本表格,但"是否已升级"是二值事实,解析成本低、复跑稳定。
24
+ 2. **改动面天然是文档**:每条 papercut 的"拟修"列指向的多是 `.agents/commands/`、`.agents/skills/`、`.githooks/` 下的提示文本与脚本 —— 与回路执行器边界(文档面)**天然对齐**,不需人为收窄。
25
+ 3. **不撞人工门**:`papercuts.md` 是普通台账,**不走 `confirm-doc` 状态机**(不同于 intent/spec/plan),所以回路可以无人值守跑而不会卡在人工确认点。
26
+
27
+ 不选的候选与理由属**装户实测数据**(源仓实例:回归清单未勾项、常驻面体积瘦身、check-loop 存量 warning——留痕在包源仓 git 历史与 CHANGELOG)。装户立回路时按同原则自评:单轮以小时计的、改 AI 行为指令的、性价比最低的候选不进本回路。
28
+
29
+ ## 2. 传感器(Sensor)
30
+
31
+ **脚本**:`.agents/scripts/papercut-debt-sensor.mjs`
32
+ **输出**:`--json` 时给结构化 JSON;默认给人读的中文摘要。退出码恒 0(**测量失败不该阻断回路**,但会显式打印 ERROR 行)。
33
+
34
+ 测量口径:
35
+
36
+ | 字段 | 含义 |
37
+ |---|---|
38
+ | `unresolved` | 未升级条目数组,每项含 `line` / `date` / `location` / `symptom` / `proposedFix` / `hits`(scope 由控制器 §3 判定,不在此列) |
39
+ | `resolved` | 已升级条目数 |
40
+ | `total` | 台账数据行总数(排除表头与分隔行) |
41
+ | `debt` | `unresolved.length` —— 设定点的当前误差 |
42
+
43
+ 关键设计约束:
44
+
45
+ - **零子进程**。不 spawn git / node。理由(源仓实测,留痕在包源仓 git 历史与 CHANGELOG):曾有托管 Node 环境 `spawnSync` 对**任意**可执行文件(含 node 自身)返回 `EBUSY`,任何依赖 git 历史的传感器在该环境不可用——已有 `check-loop.mjs` 就是这么崩的。零子进程设计保留:传感器不依赖子进程比依赖更稳,且免受环境回退影响。
46
+ - **CRLF 容忍**。台账检出为 CRLF(全局 `core.autocrlf=true`),按 `split(/\r?\n/)` 切行,**禁止依赖行尾格式**。
47
+ - **管道符安全**。单元格内可能含 `|`(代码片段),按"切分后多于 6 段则把中间段并回症状列"处理,取前 3 列 + 末列。
48
+ - **失效路径不阻塞**。对每条的"位置"列抽出形如 `.agents/…` / `wiki/…` 的路径并检查存在性;不存在只标 `stale: true`,不排除该条 —— 台账记的是历史事实,路径后来被重命名/删除是常态,不等于债已消。
49
+ - **分片感知**(kit 侧随 ledger-shards 落地):读侧走 `readLedgerText(root,'papercuts')`(主台账 ∪ `workflow/papercuts.d/*.md`),无分片时等价原读法——传感器零额外逻辑,口径不变。
50
+
51
+ ## 3. 控制器(Controller)
52
+
53
+ **脚本**:`.agents/scripts/papercut-debt-controller.mjs`
54
+ **输入**:`papercut-debt-sensor.mjs --json` 的输出(或自己调传感器)。
55
+ **输出**:本轮唯一目标(top-1),markdown 片段,直接拼进执行器提示词。
56
+
57
+ 排序键(**确定性,无随机、无 LLM 判断**):
58
+
59
+ 1. **命中次数降序** —— 台账文件头自己的规则:"同一文件命中第 2 次或用户点名时,升级为 docs 修复 commit"。`第 N 次命中` 从症状/拟修文本中正则抽取。
60
+ 2. **日期降序** —— 同命中次数下,日期新的优先(近期痛点更可能仍成立)。
61
+ 3. **原行序升序** —— 完全平手时按台账原始顺序,保证结果可复现。
62
+
63
+ **Scope gate(出选前过滤)**:`拟修` 明确指向 `backend/` `frontend/` `tests/` `wiki/` `sqlcmd` `db_tool`,或文本含 `数据库` / `DDL` / `DROP` 的条目,本轮跳过并列为 `skipped-out-of-scope`。记入输出,由用户决定是否授权。执行器只改文档面。指向业务代码或连库的条目超出授权。
64
+
65
+ **单轮只取 1 条。** L2/L3 要求独立复核,控制器不得一次给出 3 条。
66
+
67
+ **口径复用(硬约束)**:控制器**不得复制** `✅` / `⏸` / `hits` / `stale` 的任何判定正则,一律 `import { scan } from './papercut-debt-sensor.mjs'`。控制器只负责排序与 scope gate。理由(源仓首轮实证,留痕在包源仓 git 历史与 CHANGELOG):两处各判一次必然漂移——初版传感器按 `✅` 判债、控制器也按 `✅` 选目标,导致已 `⏸` 的条目被反复选中。
68
+
69
+ ## 4. 执行器(Actuator)
70
+
71
+ **技能**:`.agents/skills/papercut-fix/SKILL.md`(装副本随 `.agents/skills/` 分发)。
72
+ **触发**:你说"跑一轮 papercut 回路"。
73
+ **验证门**(改动后必跑,全部零 spawn 或纯本地):
74
+
75
+ ```bash
76
+ node .agents/scripts/check-loop.mjs # 闭环骨架(实证留痕在包源仓 git 历史与 CHANGELOG)
77
+ node .agents/scripts/papercut-debt-sensor.mjs --json # debt 应恰好 -1
78
+ ```
79
+
80
+ > **注意**:`verify.mjs` 默认不在本回路跑 —— 回路只动台账与技能文件(文档面),`verify.mjs` 含构建/测试(源码面,按装户技术栈),与 papercut 回路边界无关,回路收尾以传感器计数核验为准。需要关单凭证时直跑 `node .agents/scripts/verify.mjs`(全绿自动落 verifications.jsonl)。
81
+
82
+ **金样(golden patterns)**,已固化进技能:
83
+
84
+ - 台账写入**一律用 Edit 工具**(源仓实证 papercut:`node -e` 批量 replace 遇 CRLF 静默无操作、假落账——留痕在包源仓 git 历史与 CHANGELOG)。
85
+ - 写后**必 grep 回读验证**再报成功。
86
+ - 标 ✅ 时**追加**升级记录(日期 + commit),不删原"拟修"文本 —— 原文本是复发时的对照物。
87
+ - 改动 `.agents/commands/` 后必看 `.agents/rule-budgets.txt` 的体积上限(常驻面只增不减,一进一出;机器门 `node .agents/scripts/rule-budget.mjs --staged`)。
88
+
89
+ ## 5. 扰动与阻尼器(Disturbances / Dampener)
90
+
91
+ **扰动源**(通用形态):
92
+
93
+ - 用户并发提交:回路运行期间,用户可能修改同一文件。
94
+ - 生成物漂移 —— `workflow/INDEX.md`、`workflow/metrics.md`、`gen-wiki-board` 产物会随任何状态变更而重生成。
95
+ - 工具链升级 —— kit bump 会重写 `.agents/` 装副本,台账里的"拟修"可能已失效。
96
+
97
+ **阻尼器(已存在,不另建)**:`.githooks/pre-commit` 门禁(常驻面体积预算 `rule-budget --staged` 硬拦等)+ `pre-push` 的 `check-loop` hard-block。**新条目带 `⏸ 暂缓` 而无理由**、或同一行被并发改写压行,都会在这两道门上现形。回路**不新增任何门禁** —— 现有门禁已足够挡住"回路把台账改坏"。
98
+
99
+ ## 6. 人在环上(Human on the loop)
100
+
101
+ - **记忆文件**:装户自持(owned 面参考 `.agents/notes/`)——常驻反馈(永久范围排除、已知失效区、reviewer 意见)。每轮在控制器之后、提示词之前注入。**只写耐久规则,不写单轮日志**。
102
+ - **人工放行**:回路不自动 commit、不 push。产出 diff 后停止,等待用户明确放行。
103
+ - **`/iterate` 不实现**。追加范围或暂缓须由用户在对话中逐条给出,例如「修第 3 条」或「这条暂缓,理由 X」。
104
+
105
+ ## 7. 流控(Flow control)
106
+
107
+ **在途判据**:工作区存在本回路产生的未提交 diff。手动触发时同一工作区只跑一轮,不另绑远端评审流。`--dry-run` 只输出目标清单,不改文件。
108
+
109
+ ## 8. 历史阻塞与豁免(源仓实证,单源保留结论)
110
+
111
+ 1. **回路脚本落 `.agents/scripts/` 是否命中 L2 触达面**:不命中——`check-lane-surface.mjs` 在无活跃 L2/L3 入口时只出 advisory、exit 0 放行(**规则**:无活跃入口即放行)。**结论:不必为回路另立 intent**;若日后 `.agents/scripts/` 确有 L2 语义改动被拦,再按 `new-task.md` 立入口。实测过程留痕在包源仓 git 历史与 CHANGELOG。
112
+ 2. **依赖 git 历史的传感器在本机必崩**(EBUSY):以零子进程设计规避(§2),不做环境假设。源仓后来环境升级后该崩溃消失,但零子进程约束不回退——传感器不依赖子进程比依赖更稳。
@@ -0,0 +1,27 @@
1
+ # 机制使用量台账 (Mechanism Usage Registry)
2
+
3
+ > 门禁棘轮的出口:每个门禁机制的「触发统计口径 + 退役阈值 + 状态」单源登记于此。
4
+ > **台账的目的不是记录而是强制处置**:新增门禁机制须登记一行(一进一出,并点名它替代/合并的
5
+ > 既有机制,无替代则说明增量理由);机制退役时状态改「已退役」并在最近触发列留档;
6
+ > 低频机制按退役阈值降级。
7
+ > 列序固定:机制 | 门禁位置 | 触发统计口径 | 退役阈值 | 最近触发 | 状态 | 处置期限。
8
+ > 本表归装户自持运营(sync 永不覆盖,同 metric-claims.txt 口径);kit 引擎做 WARN 级格式校验
9
+ > (check-loop 检查 22:列数 / 状态枚举 / 待退役处置期限——advisory 不阻断,永不 hard),
10
+ > 对账纪律靠下方「维护纪律」人工执行;判定数据面由 check-loop 每次真实运行自动落账到
11
+ > `.agents/judgments.jsonl`(judgment-ledger,本地 gitignore),月度聚合经 agg-judgments.mjs 查账。
12
+
13
+ | 机制 | 门禁位置 | 触发统计口径 | 退役阈值 | 最近触发 | 状态 | 处置期限 |
14
+ |---|---|---|---|---|---|---|
15
+ | 确认指纹对账(示例行) | check-loop 检查 15 | hard「指纹不配对/确认内容漂移」计数(月,经 agg-judgments 查判定流水) | 不退役(防伪造确认主干) | 示例:建门留档见包源仓 git 历史与 CHANGELOG | 现役 | — |
16
+ | 触达面判低门(示例行) | check-lane-surface(pre-commit/CI 泳道完整性) | hard「触达面判低/产品契约面」计数(月) | 不退役(级别闭环主干) | 示例:在用 | 现役 | — |
17
+
18
+ > 以上两行为**示例行**,只示范登记口径,不参与装户对账;装户按自有机制增删行,
19
+ > 首次对账时可将示例行替换为真实运营数据。
20
+
21
+ ## 维护纪律
22
+
23
+ - **一进一出**:新增门禁机制必须同时在本表登记,并点名它替代/合并的既有机制(无替代则说明增量理由)。
24
+ - **月度对账**:每月初按「触发统计口径」列刷新计数(半自动:`node .agents/scripts/agg-judgments.mjs`
25
+ 查判定流水 `.agents/judgments.jsonl`——check-loop 每次真实运行自动落账,替代人工 grep 输出;
26
+ git log 统计口径不变);达到阈值的行改「待退役」并填处置期限。
27
+ - **退役动作**:状态改「已退役」当日删除机制代码 + 其测试 + 本表更新,同一提交完成(不留死代码)。
@@ -1,7 +1,7 @@
1
- # 常驻面体积预算(单源:check-loop.sh 检查 13(--all,WARN)与 .githooks/pre-commit(--staged,硬拦)共读)
1
+ # 常驻面体积预算(单源:check-hygiene 检查 13(--all,WARN)与 .githooks/pre-commit(--staged,硬拦)共读)
2
2
  # 格式:<路径或 glob> <上限字节>;路径以 / 结尾表示目录合计。上限 = 当前实测 + 余量。
3
3
  # 超限含义:常驻面只增不减——须先删除或下沉被取代条目再增(一进一出)。
4
- # 本文件会被 core.autocrlf 检出为 CRLF,读取方(rule-budget.sh)已显式去 \r,禁止依赖行尾格式。
4
+ # 本文件会被 core.autocrlf 检出为 CRLF,读取方(rule-budget.mjs)按 /\r?\n/ 切分天然容 \r,禁止依赖行尾格式。
5
5
  # 口径:预算比较用「索引 / HEAD 字节」(git cat-file,LF 归一);禁用工作区 wc -c 核对(CRLF 膨胀)。
6
6
  # 2026-10-06 backflow-loop-audit-remediation:6 份命令文档顶部加「装户边界」标注(build.md 实测 8386B
7
7
  # 超 8192 被拦)——单篇上限 8192→8704(与装户 Shipyard.Material 同值,512B 步进);目录合计未超不动。
@@ -2,6 +2,7 @@
2
2
  /* agg-delegations: 量化证据聚合
3
3
  * 读 workflow/delegations.md 两张结果表(按表头签名识别:委派表含「被委派方」列、自做表=日期+任务一句话;不依赖 ## 节标题)+ 扫 workflow/incidents/ 按月计数,
4
4
  * 输出各月指标(一次通过率 / 平均返工次数 / 主兜底占比 / **产出接受率**)、并发扩容门判定、可粘贴的月度快照行。
5
+ * 分片感知(2026-10-13 ledger-shard-ledgers 回灌):读侧文本 = 主台账 ∪ delegations.d/*.md,单源 = ledger-shards.mjs。
5
6
  * 结果取值:一次通过 | 返工×N | 返工×N(门禁噪声)| 主兜底 | 返工待修(未闭环,不计入率)。
6
7
  * 门禁噪声档(2026-10-08 selfmeasure-and-modularize):剥离「返工由门禁面而非设计面触发」的形态——
7
8
  * 扩容门第 3 项只对设计返工判 =0,门禁噪声返工单列观测。存量行零回填(旧四档解析逐条不变)。
@@ -95,15 +96,28 @@ function splitTables(text) {
95
96
  return tables;
96
97
  }
97
98
 
99
+ // 分片读侧文本(2026-10-13 ledger-shard-ledgers 回灌):单源 = ledger-shards.readLedgerText
100
+ // (主台账 ∪ delegations.d/*.md,月序即时间序)。.cjs 不能静态 import .mjs、require(esm) 需
101
+ // Node≥20.19(engines ≥18 不假设)——CLI 入口以 import() 装载后注入此变量再进 main;
102
+ // 库调用方(gen-workflow-dashboard)经 opts.text 显式注入合并文本。两者都缺位 → 回落主台账直读
103
+ // (与旧签名行为一致,不静默造数)。
104
+ let shardReadText = null;
98
105
  // readLedger:root 显式参数 + soft 模式(2026-10-08 workflow-dashboard 使能导出,判据单源复用——
99
106
  // 仪表盘 require 本函数取行,不重写表解析;缺省 root/无 soft 行为与原签名完全一致,main() 零变化)
100
107
  function readLedger(root = ROOT, opts = {}) {
101
108
  const ledger = path.join(root, 'workflow', 'delegations.md');
102
- if (!fs.existsSync(ledger)) {
109
+ let text;
110
+ if (typeof opts.text === 'string') {
111
+ text = opts.text; // 库调用方注入的合并文本(读侧单源 = ledger-shards.readLedgerText)
112
+ } else if (!fs.existsSync(ledger)) {
103
113
  if (opts.soft) return [];
104
114
  fail(`找不到 ${ledger}`);
115
+ } else if (shardReadText) {
116
+ try { text = shardReadText(root, 'delegations'); } // 分片感知读(fail-loud 包装:单分片读失败不静默吞)
117
+ catch (err) { if (opts.soft) return []; fail(`台账读取失败: ${ledger} (${err.message})`); }
118
+ } else {
119
+ text = fs.readFileSync(ledger, 'utf8');
105
120
  }
106
- const text = fs.readFileSync(ledger, 'utf8');
107
121
  const tables = splitTables(text);
108
122
  const delegated = parseTableRows(tables.delegated.join('\n')).map((c) => ({
109
123
  scope: '委派',
@@ -278,7 +292,11 @@ function main() {
278
292
 
279
293
  // isMain 守卫 + 纯函数导出(board-kb-p1:gateMonth / expansionVerdict 供测试 import,import 不执行 main)
280
294
  if (require.main === module) {
281
- main();
295
+ // 分片读侧装载(2026-10-13 ledger-shard-ledgers):动态 import 后进 main;装载失败 fail-loud
296
+ // (指标缺数会喂错扩容门,不静默退化主台账直读)。
297
+ import('./ledger-shards.mjs')
298
+ .then((m) => { shardReadText = m.readLedgerText; main(); })
299
+ .catch((err) => fail(`ledger-shards.mjs 装载失败: ${err.message}`));
282
300
  } else {
283
301
  module.exports = { parseResult, parseVerdict, metrics, gateMonth, expansionVerdict, fmtAccept, GATE, ACCEPT_MIN_SAMPLE, readLedger };
284
302
  }
@@ -8,10 +8,12 @@ import fs from 'node:fs';
8
8
  import os from 'node:os';
9
9
  import path from 'node:path';
10
10
  import { spawnSync } from 'node:child_process';
11
- import { fileURLToPath } from 'node:url';
11
+ import { fileURLToPath, pathToFileURL } from 'node:url';
12
12
 
13
13
  const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
14
- const SRC = path.join(SCRIPT_DIR, 'agg-delegations.cjs');
14
+ // 夹具闭包(2026-10-12 fixture-closure-rootfix):入口 agg-delegations.cjs 的传递依赖自动带入(当前无,
15
+ // 但日后加 import 不必回来改这里——漏拷 = 假绿)。
16
+ const { copyScriptClosure } = await import(pathToFileURL(path.join(SCRIPT_DIR, 'fixture-scripts.mjs')).href);
15
17
  const USAGE = '用法:node .agents/scripts/agg-delegations.test.mjs';
16
18
 
17
19
  let pass = 0;
@@ -26,7 +28,7 @@ const mkfix = (ledgerText) => {
26
28
  fs.mkdirSync(path.join(root, '.agents', 'scripts'), { recursive: true });
27
29
  fs.mkdirSync(path.join(root, 'workflow', 'incidents'), { recursive: true });
28
30
  fs.writeFileSync(path.join(root, 'workflow', 'delegations.md'), ledgerText, 'utf8');
29
- fs.copyFileSync(SRC, path.join(root, '.agents', 'scripts', 'agg-delegations.cjs'));
31
+ copyScriptClosure(path.join(root, '.agents', 'scripts'), ['agg-delegations.cjs']);
30
32
  return root;
31
33
  };
32
34
  const runAgg = (root) => spawnSync(process.execPath, [path.join(root, '.agents', 'scripts', 'agg-delegations.cjs')], { cwd: root, encoding: 'utf8' });
@@ -8,7 +8,7 @@
8
8
  // 门禁数与质量指标负相关,但此前无任何数据能回答「该加门还是该拆门」(metrics/DASHBOARD 全部指标
9
9
  // 100% 指向产出物、0 条指向门禁自身)。加门是焦虑驱动的唯一理性反应,因为拆门没有依据。
10
10
  //
11
- // 耗时口径**含子进程时间**(检查 11 调两个生成器 --check、检查 13 调 sh rule-budget.sh)——
11
+ // 耗时口径**含子进程时间**(检查 11 调两个生成器 --check、检查 13 调 rule-budget.mjs)——
12
12
  // 门禁真实成本本就要含子进程,不做剔除;输出中显式标注防误读为「纯计算耗时」。
13
13
  // 坏行容忍跳过(与 confirmations.jsonl / delegations 台账同容错口径)。
14
14
  // 用法:node .agents/scripts/agg-gate-stats.mjs [--days 30]
@@ -84,13 +84,13 @@ function main() {
84
84
  const { runs, list, noiseTop, dead, costTotal } = aggregate(rows, Date.now(), Number.isFinite(days) ? days : null);
85
85
 
86
86
  if (!runs || !list.length) {
87
- console.log('门禁 ROI:未采集——跑 `node .agents/scripts/check-loop.mjs --gate-stats` 后重试');
87
+ console.log('门禁 ROI:未采集——跑 `node .agents/scripts/check-loop.mjs --all --gate-stats` 后重试(--all 全段成本面;2026-10-13 分道后裸跑只记硬门段)');
88
88
  console.log('(数据落 .agents/cache/gate-stats.jsonl,该目录已 gitignore;首次采集后本表才有内容)');
89
89
  return;
90
90
  }
91
91
 
92
92
  console.log(`# 门禁 ROI 聚合(窗口 ${days} 天 / 有效样本 ${runs} 次运行)\n`);
93
- console.log('耗时口径**含子进程时间**(检查 11 调生成器 --check、检查 13 调 sh rule-budget.sh)——门禁真实成本,不做剔除。\n');
93
+ console.log('耗时口径**含子进程时间**(检查 11 调生成器 --check、检查 13 调 rule-budget.mjs)——门禁真实成本,不做剔除。\n');
94
94
  console.log('| 检查 | 名称 | 命中/运行 | 拦截 | 警告 | 累计耗时 | 均耗时 | 每次命中警告数 |');
95
95
  console.log('|------|------|-----------|------|------|----------|--------|----------------|');
96
96
  for (const a of list) {
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+ // agg-judgments.mjs — check-loop 判定流水月度聚合(2026-10-11 judgment-ledger)
3
+ // 用法:node .agents/scripts/agg-judgments.mjs [--month=YYYY-MM]
4
+ // 源:.agents/judgments.jsonl(check-loop.mjs 每次真实运行追加一行,本地 gitignore;
5
+ // 缺文件 = 尚无运行记录,提示后 exit 0——与 delegations 台账「必在」不同,这是渐积数据)。
6
+ // 消费:mechanism-registry 月度对账(.agents/mechanism-registry.md「触发统计口径」列的 WARN/BLOCK 计数)
7
+ // 与 advisory→hard 升格 / 退役判定的直接查账面——替代原「人工 grep check-loop 输出」。
8
+ // 测试:node .agents/scripts/agg-judgments.test.mjs(纯函数 aggFromRows 进程内断言)
9
+ import fs from 'node:fs';
10
+ import path from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+
13
+ const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
14
+ const ROOT = path.resolve(SCRIPT_DIR, '..', '..');
15
+ const LEDGER = path.join(ROOT, '.agents', 'judgments.jsonl');
16
+
17
+ const argMonth = (process.argv.find((a) => a.startsWith('--month=')) || '').split('=')[1] || null;
18
+
19
+ // aggFromRows(rows, month):按月×规则×级别聚合。rows = 已解析的台账行数组;
20
+ // month = 'YYYY-MM' 过滤(null = 全部)。输出 { byMonth: { 'YYYY-MM': { runs, rules: { rule: { WARN, BLOCK } } } } }。
21
+ export function aggFromRows(rows, month = null) {
22
+ const byMonth = {};
23
+ for (const row of rows || []) {
24
+ if (typeof row.ts !== 'string' || !/^\d{4}-\d{2}/.test(row.ts)) continue;
25
+ const ym = row.ts.slice(0, 7);
26
+ if (month && ym !== month) continue;
27
+ const b = (byMonth[ym] ||= { runs: 0, rules: {} });
28
+ b.runs++;
29
+ for (const r of Array.isArray(row.rules) ? row.rules : []) {
30
+ if (!r || typeof r.rule !== 'string') continue;
31
+ const cell = (b.rules[r.rule] ||= { WARN: 0, BLOCK: 0 });
32
+ if (r.level === 'WARN') cell.WARN += r.count || 0;
33
+ else if (r.level === 'BLOCK') cell.BLOCK += r.count || 0;
34
+ }
35
+ }
36
+ return { byMonth };
37
+ }
38
+
39
+ function readRows() {
40
+ if (!fs.existsSync(LEDGER)) return null;
41
+ const rows = [];
42
+ let bad = 0;
43
+ for (const line of fs.readFileSync(LEDGER, 'utf8').split(/\r?\n/)) {
44
+ if (!line.trim()) continue;
45
+ try {
46
+ rows.push(JSON.parse(line));
47
+ } catch {
48
+ bad++;
49
+ }
50
+ }
51
+ return { rows, bad };
52
+ }
53
+
54
+ function main() {
55
+ const read = readRows();
56
+ if (!read) {
57
+ console.error(`agg-judgments: ${path.relative(ROOT, LEDGER)} 不存在——尚无运行记录(check-loop 真实运行一次即有一行)`);
58
+ process.exit(0);
59
+ }
60
+ const { byMonth } = aggFromRows(read.rows, argMonth);
61
+ const months = Object.keys(byMonth).sort();
62
+ if (!months.length) {
63
+ console.log(`agg-judgments: ${argMonth || '全部月份'} 无记录(台账共 ${read.rows.length} 行)`);
64
+ return;
65
+ }
66
+ for (const ym of months) {
67
+ const b = byMonth[ym];
68
+ console.log(`📅 ${ym}|运行 ${b.runs} 次`);
69
+ const rules = Object.keys(b.rules).sort();
70
+ if (!rules.length) { console.log(' (无规则判定)'); continue; }
71
+ for (const rule of rules) {
72
+ const { WARN, BLOCK } = b.rules[rule];
73
+ console.log(` ${rule.padEnd(24)} WARN×${WARN} BLOCK×${BLOCK}`);
74
+ }
75
+ }
76
+ if (read.bad) console.error(`agg-judgments: 跳过坏行 ${read.bad}(JSON 解析失败,不中断聚合)`);
77
+ }
78
+
79
+ const isMain = process.argv[1] && process.argv[1].endsWith('agg-judgments.mjs');
80
+ if (isMain) main();
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ // agg-judgments.test.mjs — 判定流水聚合纯函数套件(2026-10-11 judgment-ledger)
3
+ // aggFromRows 进程内断言:按月分桶 / --month 过滤 / WARN·BLOCK 计数 / 坏 rules 字段容忍。
4
+ import { aggFromRows } from './agg-judgments.mjs';
5
+
6
+ let pass = 0;
7
+ let fail = 0;
8
+ const check = (desc, cond, detail = '') => {
9
+ if (cond) { pass++; console.log(`PASS ${desc}`); }
10
+ else { fail++; console.log(`FAIL ${desc}${detail ? '\n ' + detail : ''}`); }
11
+ };
12
+
13
+ const ROWS = [
14
+ { ts: '2026-10-01T01:00:00Z', mode: 'hard', rules: [{ rule: '三件套不全', level: 'WARN', count: 2 }] },
15
+ { ts: '2026-10-02T01:00:00Z', mode: 'hygiene', rules: [{ rule: '三件套不全', level: 'WARN', count: 1 }, { rule: '配对断裂', level: 'BLOCK', count: 1 }] },
16
+ { ts: '2026-09-30T01:00:00Z', mode: 'hard', rules: [{ rule: '归因缺字段', level: 'WARN', count: 3 }] },
17
+ { ts: '2026-10-03T01:00:00Z', mode: 'hard', rules: [] },
18
+ { ts: 'bad-ts', mode: 'hard', rules: [] },
19
+ { ts: '2026-10-04T01:00:00Z', mode: 'hard', rules: 'not-array' },
20
+ ];
21
+
22
+ // ---- S1 按月分桶 + 计数 ----
23
+ {
24
+ const { byMonth } = aggFromRows(ROWS);
25
+ check('S1 两个月桶(坏 ts 行不计)', Object.keys(byMonth).sort().join(',') === '2026-09,2026-10', JSON.stringify(Object.keys(byMonth)));
26
+ check('S1 2026-10 运行 4 次', byMonth['2026-10'].runs === 4, String(byMonth['2026-10'].runs));
27
+ check('S1 跨行同规则 WARN 累加(2+1=3)', byMonth['2026-10'].rules['三件套不全'].WARN === 3, JSON.stringify(byMonth['2026-10'].rules));
28
+ check('S1 BLOCK 计数独立', byMonth['2026-10'].rules['配对断裂'].BLOCK === 1);
29
+ check('S1 2026-09 归因缺字段 WARN×3', byMonth['2026-09'].rules['归因缺字段'].WARN === 3);
30
+ check('S1 空 rules 行只计 runs', byMonth['2026-10'].runs === 4 && byMonth['2026-10'].rules['配对断裂'].WARN === 0);
31
+ }
32
+
33
+ // ---- S2 month 过滤 ----
34
+ {
35
+ const { byMonth } = aggFromRows(ROWS, '2026-09');
36
+ check('S2 过滤单月只留 2026-09', Object.keys(byMonth).join(',') === '2026-09' && byMonth['2026-09'].runs === 1);
37
+ check('S2 过滤无命中 → 空桶', aggFromRows(ROWS, '2025-01').byMonth['2025-01'] === undefined);
38
+ }
39
+
40
+ // ---- S3 空入参 ----
41
+ check('S3 空行数组 → 空 byMonth', Object.keys(aggFromRows([]).byMonth).length === 0);
42
+ check('S3 null 入参不 throw', Object.keys(aggFromRows(null).byMonth).length === 0);
43
+
44
+ console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
45
+ process.exit(fail ? 1 : 0);