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
@@ -0,0 +1,119 @@
1
+ // check-convergence.mjs — 检查 23:判据级收敛对账(2026-10-13 B6b-2 自 Shipyard.Material
2
+ // check-loop-sections/s23-convergence-audit.mjs 回灌;段体判据逐字对齐,出处沿革留痕在包源仓 git
3
+ // 历史与 CHANGELOG——装户径不引实例路径)
4
+ // 背景(convergence-audit,参照 GitHub spec-kit converge 机理「完成声明不是证据」):
5
+ // spec 的验证承诺此前只落在叙述节(检查 21 对节内引用做存在性扫描),没有逐判据 × 逐证据的机器对账。
6
+ // 本段把「验收判据」表(workflow/specs/_TEMPLATE.md 承重节)变成机器输入:L2/L3 done spec(锚后)逐行解析
7
+ // 判据 → 证据。状态列自报「覆盖」而证据无效 → 仍判未覆盖。
8
+ // 两档(convergence-hard):
9
+ // advisory(有 convergenceSince、无 hard 键,或日期 < convergenceHardSince):证据 = 存在的路径,或命令白名单声明。WARN。
10
+ // hard(日期 ≥ convergenceHardSince):路径仍只认存在,不读正文、不判与判据是否相关(判断题留给新会话复核);
11
+ // 命令必须是全量门实际跑的那几个,且 workflow/verify-evidence.md 有不早于本 spec 日期的全绿行。
12
+ // 答不了(空、路径不存在、命令不在全量门、没有对应全绿行)= 未覆盖,HARD-BLOCK。人工走查且证据为空仍合法。
13
+ // 无「验收判据」节或表无数据行 → 两档都是 WARN「收敛无判据」。
14
+ // 存量豁免:日期 < convergenceSince → 跳过。缺键(v1–v9)→ 整段跳过。缺 hard 键(v1–v10)→ 不升 hard。
15
+ // kit 适配(源仓→kit 逐条):① 命令白名单收 kit 全量门口径——verify.mjs 全量门实际跑的就是
16
+ // `npm test` 与 `node .agents/scripts/verify.mjs` 自身(verify.mjs 编排七门、测试面走 run-tests 套件),
17
+ // 源仓的 dotnet build / npm run build / verify-ui 等实例门随 instance-ref 门修法剔除;② hard 档解锁序
18
+ // 文案对齐 kit:verify.mjs 全绿尾部自动重生成 verify-evidence.md(gen-verify-evidence 单源),
19
+ // 不再单独点名 gen-verify-evidence 步骤。
20
+ import fs from 'node:fs';
21
+ import path from 'node:path';
22
+
23
+ // advisory 档的声明白名单(文本命中即认)。hard 档不再凭命中放行。命令面 = kit 全量门口径(见头注)。
24
+ const CMD_RE = /npm\s+(run\s+)?test|verify\.mjs/;
25
+ // 引用切分:每一处引用单独取出,防「npm test 与路径同格」被整格误判。长短形态都认。
26
+ const CITE_RE = /npm\s+(?:run\s+)?test|verify\.mjs/g;
27
+ // verify.mjs 全绿行(verifications.jsonl 绿凭证)实际覆盖的命令。不在此集的白名单命令没有对应绿记录。
28
+ const GATE_CMD = new Set(['npm test', 'npm run test', 'verify.mjs']);
29
+ const PATH_RE = /[\w.@\-\\/]+\.(mjs|js|ts|tsx|cs|md|json|html|sql)/g;
30
+ const SEC_RE = /^##\s+.*验收判据/;
31
+ // 与 gen-verify-evidence.mjs 数据行同形态(kit 六列:短 SHA | ISO 时间 | 门数 | 套件计数 | 绑定单 | runId)。
32
+ // 只取日期,不复述门数。
33
+ const EVIDENCE_ROW_RE = /^\|\s*`[0-9a-f]{7,40}`\s*\|\s*([^|]+?)\s*\|/;
34
+
35
+ export function run(ctx) {
36
+ const { ROOT, WF, docFiles, fmGet, linesOf, addedDateOf, kitPolicy, warnings, blockers } = ctx;
37
+ // --- 23. 判据级收敛对账(锚 convergenceSince / convergenceHardSince)---
38
+ function greenEvidenceDates() {
39
+ const dates = [];
40
+ let text = '';
41
+ try { text = fs.readFileSync(path.join(ROOT, WF, 'verify-evidence.md'), 'utf8'); } catch { return dates; }
42
+ for (const raw of text.split(/\r?\n/)) {
43
+ const m = raw.trim().match(EVIDENCE_ROW_RE);
44
+ if (!m) continue;
45
+ const day = m[1].trim().slice(0, 10);
46
+ if (/^\d{4}-\d{2}-\d{2}$/.test(day)) dates.push(day);
47
+ }
48
+ return dates;
49
+ }
50
+
51
+ // 命令格:每一处引用都须落在全量门里,且有不早于 spec 日期的全绿行。解析不出引用 → 不放行。
52
+ // 两侧日期同须 UTC 口径(台账渲染 UTC ts;spec「日期」fill-spec 默认 UTC,手写见 _TEMPLATE.md 验收判据注):
53
+ // 本地时区手写日期会在 UTC+8 0–8 点窗与全绿行错位一天——fail-closed,不误放。
54
+ function commandCovered(ev, specDate, greenDates) {
55
+ const cites = ev.match(CITE_RE) || [];
56
+ if (!cites.length) return false;
57
+ const fresh = greenDates.some((g) => g >= specDate);
58
+ return cites.every((c) => GATE_CMD.has(c) && fresh);
59
+ }
60
+
61
+ {
62
+ const anchor = kitPolicy.convergenceSince;
63
+ const hardAnchor = kitPolicy.convergenceHardSince;
64
+ if (anchor) {
65
+ const greenDates = greenEvidenceDates();
66
+ const isPlaceholder = (s) => !s || /^<.*>$/.test(s);
67
+ for (const spec of docFiles('specs')) {
68
+ const st = fmGet(spec, '状态');
69
+ const lvl = fmGet(spec, '级别');
70
+ if (st !== 'done' || !['L2', 'L3'].includes(lvl)) continue;
71
+ // 生效日锚:frontmatter「日期」优先(fill-spec 必写),缺则 git 首加日 / 文件名前缀(同检查 12 退化序)
72
+ let d = fmGet(spec, '日期') || addedDateOf(spec);
73
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(d)) d = /^\d{4}-\d{2}-\d{2}/.test(path.basename(spec)) ? path.basename(spec).slice(0, 10) : '';
74
+ if (!d || d < anchor) continue; // 锚前存量不回填
75
+ const hard = !!(hardAnchor && d >= hardAnchor);
76
+ const lines = linesOf(spec) || [];
77
+ const secLines = [];
78
+ let inSec = false;
79
+ for (const l of lines) {
80
+ if (SEC_RE.test(l)) { inSec = true; continue; }
81
+ if (inSec && /^##\s+/.test(l)) { inSec = false; continue; }
82
+ if (inSec) secLines.push(l);
83
+ }
84
+ // 解析表数据行(表头才是数据边界——沿用 agg-delegations splitTables 口径;分隔行/表头行/占位行跳过)
85
+ const rows = secLines
86
+ .filter((l) => /^\|.*\|\s*$/.test(l))
87
+ .filter((l) => !/^\|[\s:|-]+\|\s*$/.test(l))
88
+ .map((l) => l.split('|').slice(1, -1).map((c) => c.trim().replace(/^`|`$/g, '')))
89
+ .filter((cells) => cells.length >= 3)
90
+ .filter((cells) => !cells[1].startsWith('证据(')) // 表头行
91
+ .filter((cells) => !isPlaceholder(cells[0])); // 模板占位行
92
+ const base = path.basename(spec);
93
+ if (!rows.length) {
94
+ warnings.push(`- [WARN 收敛无判据] ${base} 无「验收判据」表或表为空——L2/L3 done spec 须逐条登记可验判据(检查 23,模板 workflow/specs/_TEMPLATE.md)`);
95
+ continue;
96
+ }
97
+ const missed = [];
98
+ for (const [crit, ev, stat] of rows.map((c) => [c[0], c[1], (c[2] || '').trim()])) {
99
+ if (stat === '人工' && isPlaceholder(ev)) continue; // 人工走查判据:空证据合法
100
+ let ok = false;
101
+ if (!isPlaceholder(ev)) {
102
+ if (hard && CMD_RE.test(ev)) ok = commandCovered(ev, d, greenDates);
103
+ else if (CMD_RE.test(ev)) ok = true; // advisory:命令白名单认声明
104
+ else {
105
+ const cands = ev.match(PATH_RE) || [];
106
+ ok = cands.some((p) => fs.existsSync(path.join(ROOT, p)));
107
+ }
108
+ }
109
+ if (!ok) missed.push(`${crit}→${ev || '(空)'}`);
110
+ }
111
+ if (missed.length) {
112
+ const detail = `${base} ${rows.length} 条判据中 ${missed.length} 条证据无效(${missed.join('; ')})`;
113
+ if (hard) blockers.push(`- [收敛未覆盖] ${detail}——状态列自报不作数;命令须是全量门(npm test / verify.mjs)且 workflow/verify-evidence.md 有不早于本 spec 日期的全绿行;路径只认存在(检查 23)。若本 spec 已标 done:先标后补会与本门互为前提(verify 门序含 check-loop)——解锁序:状态临时改回非 done → 跑 node .agents/scripts/verify.mjs 全量门(全绿尾部自动重生成 workflow/verify-evidence.md 全绿行)→ 再标 done`);
114
+ else warnings.push(`- [WARN 收敛未覆盖] ${detail}——状态列自报不作数,证据须存在或为验证命令(检查 23)`);
115
+ }
116
+ }
117
+ }
118
+ }
119
+ }
@@ -114,7 +114,10 @@ const hardUnits = (rel, text) => {
114
114
  const count = (re) => (text.match(re) || []).length;
115
115
  if (/\.m?c?js$/.test(rel) && !rel.endsWith('.test.mjs')) return count(/blockers\.push\(/g) + count(/process\.exit\(1\)/g);
116
116
  if (/^(\.githooks|templates\/_githooks)\//.test(rel) || /\.sh$/.test(rel) || rel.endsWith('local-pre-commit')) {
117
- return count(/\bexit\s+1\b/g);
117
+ // 硬拦单位数 = 钩子内可阻断提交的退出点。2026-10-13 门禁去 sh 化(B2):无扩展名钩子改 CommonJS 后
118
+ // 硬拦形态从 sh 的 `exit 1` 变为 process.exit(非零字面/状态表达式)——两种形态并计,sh 侧计数不变
119
+ // (sh 无 process.exit;exit(0) 成功路径不计)。此为改判据基线的合法演进(本文件头口径),与 CHANGELOG 同批。
120
+ return count(/\bexit\s+1\b/g) + count(/process\.exit\((?!0\b)/g);
118
121
  }
119
122
  if (rel.endsWith('.yml')) return count(/node [^\s]*scripts\/[^\s]*\.(mjs|cjs|sh)/g); // CI 里每道门的调用
120
123
  if (/\.(txt|json)$/.test(rel)) {
@@ -0,0 +1,56 @@
1
+ // check-funnel.mjs — 检查 26:闭环漏斗「协议前」占比动作阈值 [warning](2026-10-13 B6b-2 自
2
+ // Shipyard.Material check-loop-sections/s26-funnel-action.mjs 回灌;段体判据逐字对齐,出处沿革留痕
3
+ // 在包源仓 git 历史与 CHANGELOG——装户径不引实例路径)
4
+ // 背景(funnel-action-threshold):workflow/metrics.md 闭环漏斗早就在记「协议前」(收口却无确认链)
5
+ // 件数,但该数据只有观测没有动作——门禁零触发有退役阈值、指标超额有对账,唯独「闭环被绕开」这一项
6
+ // 无任何阈值,等于默许可绕。本段补动作面:漏斗表里日历相邻两月协议前占比均 >30% → WARN 并要求一轮归因。
7
+ // 判据口径:
8
+ // - 占比 = 协议前 / 收口;收口 = 0 的月份无占比(0/0 不判),只按有分母的月参与相邻比对;
9
+ // - 「相邻」= nextMonth(前一月) === 当前月(行已按月升序)——单月波动(批量迁移 / 集中补录)不触发;
10
+ // - 阈值 30%:源仓与生成器 gen-workflow-metrics.mjs 的 LEGACY_RATIO_LIMIT 同值(单源两侧);
11
+ // kit 的 gen-workflow-metrics 不渲染占比单元格(裸整数形态),故 0.30 单点在本文件——若日后给
12
+ // 生成器加 ⚠️ 标记面,须恢复「同值单源两侧 + 夹具钉住」纪律。
13
+ // 纯 WARN advisory(沿 §25 先例):这是月度节奏的过程事实,不是每次提交的正确性事实——
14
+ // 拿它做 hard-block 会把月度问题钉成每日拦路,逼人绕的正是门自己。升格判据见 mechanism-registry §26 行。
15
+ // 数据源是生成物(gen-workflow-metrics.mjs 单源写入),单元格形如 `79(45%) ⚠️` 或存量裸 `79`——
16
+ // 解析只取首个整数,两种形态都认(生成器升级当月不会整表重算,混合形态必须可读)。
17
+ // ROOT 由 check-loop 供给:--rev 模式下 ROOT 指向该提交的临时 worktree,故本段在 pre-push --rev
18
+ // 与工作区两种模式下读的都是同一棵树。
19
+ import fs from 'node:fs';
20
+ import path from 'node:path';
21
+
22
+ export function run(ctx) {
23
+ const { ROOT, WF, warnings } = ctx;
24
+ // --- 26. 闭环漏斗「协议前」占比动作阈值 [warning] ---
25
+ const RATIO_LIMIT = 0.30;
26
+ const metricsPath = path.join(ROOT, WF, 'metrics.md');
27
+ if (!fs.existsSync(metricsPath)) return; // 装户/新仓无快照 = 未启用该观测,静默跳过(沿 lane-surfaces「缺失即跳过」惯例)
28
+ const rows = [];
29
+ let inFunnel = false;
30
+ for (const line of fs.readFileSync(metricsPath, 'utf8').split(/\r?\n/)) {
31
+ if (/^##\s+闭环漏斗/.test(line)) { inFunnel = true; continue; }
32
+ if (inFunnel && /^##\s+/.test(line)) break; // 漏斗段到下一节为止
33
+ if (!inFunnel) continue;
34
+ const m = line.match(/^\|\s*(\d{4}-\d{2})\s*\|\s*(\d+)\s*\|\s*(\d+)\s*\|\s*(\d+)/);
35
+ if (!m) continue; // 表头/分隔行
36
+ rows.push({ month: m[1], closed: Number(m[2]), legacy: Number(m[4]) });
37
+ }
38
+ if (rows.length < 2) return;
39
+ rows.sort((a, b) => a.month.localeCompare(b.month));
40
+ const ratioOf = (r) => (r.closed > 0 ? r.legacy / r.closed : null);
41
+ const nextMonthKey = (ym) => {
42
+ const [y, m] = ym.split('-').map(Number);
43
+ return m === 12 ? `${y + 1}-01` : `${y}-${String(m + 1).padStart(2, '0')}`;
44
+ };
45
+ for (let i = 1; i < rows.length; i++) {
46
+ const prev = rows[i - 1];
47
+ const cur = rows[i];
48
+ if (nextMonthKey(prev.month) !== cur.month) continue;
49
+ const ra = ratioOf(prev);
50
+ const rb = ratioOf(cur);
51
+ if (ra === null || rb === null) continue;
52
+ if (ra > RATIO_LIMIT && rb > RATIO_LIMIT) {
53
+ warnings.push(`- [WARN 漏斗协议前超标] ${prev.month} ${(ra * 100).toFixed(0)}% → ${cur.month} ${(rb * 100).toFixed(0)}%(阈值 ${(RATIO_LIMIT * 100).toFixed(0)}%,相邻两月)——闭环在被系统性绕开:跑一轮归因(哪一环在逼人绕路:确认门太重 / 级别判错 / 批量豁免滥用?),结论记 workflow/papercuts.md 或立 incident,并在 mechanism-registry 月度对账登记处置`);
54
+ }
55
+ }
56
+ }
@@ -26,7 +26,7 @@ import { spawnSync } from 'node:child_process';
26
26
  import { gateSeg, finishSegs, makeCollector, appendSegs } from './gate-seg.mjs';
27
27
 
28
28
  // 门禁 ROI 插桩(2026-10-08 gate-roi-metrics):本模块承载的检查 2/9/11/12/13 同样需要计入门禁成本
29
- // 统计——尤其检查 11(调两个生成器 --check 的 spawn 子进程)与检查 13(调 sh rule-budget.sh)是耗时大户,
29
+ // 统计——尤其检查 11(调两个生成器 --check 的 spawn 子进程)与检查 13(调 rule-budget.mjs)是耗时大户,
30
30
  // 不插桩就等于把最贵的两段排除在度量之外。collector 由 check-loop 传入(它持有全局 warnings/blockers),
31
31
  // 未传时跳过插桩(纯函数直测场景零副作用)。判据单源见 gate-seg.mjs。
32
32
 
@@ -43,7 +43,7 @@ export function runCheckHygiene(ctx) {
43
43
  // --- 2. 模板字段占位符残留 [warning](排除运行时文件名格式与协议样板,口径沿 sh 版)---
44
44
  markGate('2', '模板字段占位符残留');
45
45
  {
46
- const phRe = /YYYY-MM-DD|<主题>|<日期 主题>|L0 \/ L1 \/ L2 \/ L3|draft \/ approved \/ done|open \/ fixed \/ closed/;
46
+ const phRe = /YYYY-MM-DD|<主题>|<日期 主题>|L1 \/ L2 \/ L3|draft \/ approved \/ done|open \/ fixed \/ closed/;
47
47
  const boilerRe = /\.\.\/specs\/[A-Za-z0-9-]*\.md|写明如何满足|防复发验证|_YYYY-MM-DD\.|format\('YYYY-MM-DD'\)|value-format="YYYY-MM-DD"|确认结果:approved(YYYY-MM-DD 用户对话内确认);done(YYYY-MM-DD 关单,随入口文档置终态)/;
48
48
  // plan 确认节样板豁免(2026-10-03 plan-confirm-boiler):plan 模板「确认与复核」节自带
49
49
  // 「确认结果:approved(YYYY-MM-DD 用户对话内确认);done(YYYY-MM-DD 关单,随入口文档置终态)」
@@ -145,13 +145,13 @@ export function runCheckHygiene(ctx) {
145
145
  }
146
146
  }
147
147
 
148
- // --- 13. 常驻面体积预算 [warning](判定单源 rule-budget.sh;无 sh 环境静默跳过——advisory,pre-commit 硬拦兜底)---
148
+ // --- 13. 常驻面体积预算 [warning](判定单源 rule-budget.mjs——2026-10-13 自 .sh 移植 node,恒可跑)---
149
149
  markGate('13', '常驻面体积预算');
150
150
  {
151
- const rb = path.join(ROOT, '.agents', 'scripts', 'rule-budget.sh');
151
+ const rb = path.join(ROOT, '.agents', 'scripts', 'rule-budget.mjs');
152
152
  const budgets = path.join(ROOT, '.agents', 'rule-budgets.txt');
153
153
  if (fs.existsSync(rb) && fs.existsSync(budgets)) {
154
- const r = spawnSync('sh', [rb, '--all'], { cwd: ROOT, encoding: 'utf8' });
154
+ const r = spawnSync(process.execPath, [rb, '--all'], { cwd: ROOT, encoding: 'utf8' });
155
155
  if (!r.error && r.status !== 0) {
156
156
  const head = `${r.stdout || ''}${r.stderr || ''}`.split('\n').slice(0, 5).map((l) => ' ' + l).join('\n');
157
157
  warnings.push(`- [WARN 常驻面超限] 常驻面体积超预算(先删除或下沉被取代条目再增——一进一出):\n${head}`);
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ // check-index-incremental.mjs:未触及 workflow 文档则跳过;触及且 INDEX 与磁盘不一致则 exit 1;重生成后 exit 0。
3
+ import { spawnSync } from 'node:child_process';
4
+ import fs from 'node:fs';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import { fileURLToPath, pathToFileURL } from 'node:url';
8
+
9
+ const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
10
+ const HOOK = path.join(SCRIPT_DIR, '..', 'hooks', 'check-index-incremental.mjs');
11
+ const GEN = path.join(SCRIPT_DIR, 'gen-workflow-index.mjs');
12
+ const { stagedTouchesIndex } = await import(pathToFileURL(HOOK).href);
13
+
14
+ let pass = 0;
15
+ let fail = 0;
16
+ const check = (desc, cond, detail = '') => {
17
+ if (cond) { pass++; console.log(`PASS ${desc}`); }
18
+ else { fail++; console.log(`FAIL ${desc}${detail ? `\n${detail}` : ''}`); }
19
+ };
20
+
21
+ check('路径:四目录 md 与 INDEX.md 触发', stagedTouchesIndex([
22
+ 'workflow/plans/2026-10-12-a.md',
23
+ 'README.md',
24
+ ]));
25
+ check('路径:归档 md 触发', stagedTouchesIndex(['workflow/archive/2026-10/plans/2026-10-01-a.md']));
26
+ check('路径:INDEX.md 触发', stagedTouchesIndex(['workflow/INDEX.md']));
27
+ check('路径:delegations 与引擎脚本不触发', !stagedTouchesIndex([
28
+ 'workflow/delegations.md',
29
+ '.agents/scripts/gen-workflow-index.mjs',
30
+ 'backend/x.cs',
31
+ ]));
32
+
33
+ const git = (cwd, args) => spawnSync('git', ['-c', 'core.hooksPath=', ...args], { cwd, encoding: 'utf8' });
34
+ const runHook = (cwd) => spawnSync(process.execPath, [HOOK], { cwd, encoding: 'utf8' });
35
+
36
+ function initRepo() {
37
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'index-incremental-'));
38
+ git(root, ['init']);
39
+ git(root, ['config', 'user.email', 'index-test@example.com']);
40
+ git(root, ['config', 'user.name', 'index-test']);
41
+ for (const d of ['intents', 'specs', 'plans', 'incidents']) fs.mkdirSync(path.join(root, 'workflow', d), { recursive: true });
42
+ fs.writeFileSync(path.join(root, 'workflow', 'plans', '2026-10-01-a.md'), '---\n状态: done\n级别: L1\n日期: 2026-10-01\n---\n# PLAN — 甲\n', 'utf8');
43
+ fs.writeFileSync(path.join(root, 'README.md'), 'x\n', 'utf8');
44
+ return root;
45
+ }
46
+
47
+ {
48
+ const root = initRepo();
49
+ git(root, ['add', 'README.md']);
50
+ const r = runHook(root);
51
+ check('未暂存 workflow 文档 → exit 0', r.status === 0, `${r.status}\n${r.stdout}${r.stderr}`);
52
+ fs.rmSync(root, { recursive: true, force: true });
53
+ }
54
+ {
55
+ const root = initRepo();
56
+ const gen = spawnSync(process.execPath, [GEN], { cwd: root, encoding: 'utf8' });
57
+ check('夹具生成 INDEX exit 0', gen.status === 0, gen.stderr);
58
+ git(root, ['add', 'workflow']);
59
+ const ok = runHook(root);
60
+ check('INDEX 与磁盘一致 → exit 0', ok.status === 0, `${ok.stdout}${ok.stderr}`);
61
+ fs.writeFileSync(path.join(root, 'workflow', 'plans', '2026-10-01-a.md'), '---\n状态: approved\n级别: L1\n日期: 2026-10-01\n---\n# PLAN — 甲\n', 'utf8');
62
+ git(root, ['add', 'workflow/plans/2026-10-01-a.md']);
63
+ const blocked = runHook(root);
64
+ check('状态变了但 INDEX 未重生成 → exit 1', blocked.status === 1, `${blocked.stdout}${blocked.stderr}`);
65
+ const regen = spawnSync(process.execPath, [GEN], { cwd: root, encoding: 'utf8' });
66
+ git(root, ['add', 'workflow/INDEX.md']);
67
+ const again = runHook(root);
68
+ check('重生成并暂存 INDEX 后 → exit 0', regen.status === 0 && again.status === 0, `${regen.stderr}${again.stdout}${again.stderr}`);
69
+ fs.rmSync(root, { recursive: true, force: true });
70
+ }
71
+
72
+ console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
73
+ if (fail) process.exit(1);
@@ -8,7 +8,7 @@
8
8
  // 检查 A「触达面判低」:暂存 diff 命中项目 L2 触达面(.agents/lane-surfaces.txt,ERE 逐行;
9
9
  // # 注释与空行忽略;文件缺失或清单为空 = 检查跳过,装户无感)时——
10
10
  // 存在活跃 L2/L3 入口 → 放行(归因歧义为已知边界:机器不猜「哪个入口在干这批改动」);
11
- // 活跃入口全为 L0/L1(含级别缺失)→ hard 拦,点名最新 L0/L1 入口 + 三选修法;
11
+ // 活跃入口全为 L1(含级别缺失)→ hard 拦,点名最新 L1 入口 + 三选修法(2026-10-13 L0 退役,低泳道=L1 专属);
12
12
  // 无任何活跃入口 → 仅 advisory(docs / bootstrap 豁免类改动的合法形态)。
13
13
  // 活跃 = intents draft/approved + incidents open/fixed(incident≡intent 等价口径)。
14
14
  // experiment/* 分支降级 advisory(泳道哲学:PoC 免闭环纪律,转正由 pre-push 加固门兜底)。
@@ -17,7 +17,7 @@
17
17
  // 标记是加固门覆盖率的前提且补写零成本——advisory 免的是纪律负担,不免标记本身。
18
18
  // 判据原则(2026-09-28 台账批量代录事故教训):只用一手事实——暂存文件清单、
19
19
  // 当前分支名、工作区 frontmatter 字段;不用时间戳 / 字符串相等反推过程事实。
20
- // 边界:git 调用一律 -c core.quotepath=off(非 ASCII 路径教训,同 check-pairing-incremental.sh);
20
+ // 边界:git 调用一律 -c core.quotepath=off(非 ASCII 路径教训,同 check-pairing-incremental.mjs);
21
21
  // frontmatter 读取容忍 CRLF(显式按 \r?\n 切分);detached HEAD 分支不可判 → A 按 strict、
22
22
  // B 跳过(沿 pairing 门先例);配置行正则语法错误 → fail-closed(防静默空洞)。
23
23
  // 已知边界:commit-time 门禁,CI / pre-push 复跑面无对应全量检查(push 时刻无暂存 diff 一手事实,
@@ -44,7 +44,7 @@ const base = (root, { level = 'L1', status = 'approved', extra = null } = {}) =>
44
44
  w(root, 'workflow/intents/2026-09-28-old-l1.md', intent('x', `状态: ${status}\n级别: ${level}\n日期: 2026-09-28\n模块: pipeline`));
45
45
  if (extra) w(root, extra[0], intent('y', extra[1]));
46
46
  };
47
- const FM_L0_NEW = '状态: draft\n级别: L0\n日期: 2026-10-01\n模块: ui';
47
+ const FM_LOW_NEW = '状态: draft\n级别: L1\n日期: 2026-10-01\n模块: ui';
48
48
 
49
49
  // --- S1:命中 + 活跃 L2 存在 → 豁免放行 ---
50
50
  {
@@ -53,7 +53,7 @@ const FM_L0_NEW = '状态: draft\n级别: L0\n日期: 2026-10-01\n模块: ui';
53
53
  const r = run(d, { staged: ['templates/_agents/scripts/x.mjs'] });
54
54
  check('S1 命中面+活跃 L2 入口 → 放行且无触达面判低输出', r.code === 0 && !r.out.includes('触达面判低'), r.out);
55
55
  }
56
- // --- S2:命中 + 活跃全为 L0/L1 → 阻断 ---
56
+ // --- S2:命中 + 活跃全为 L1(低泳道)→ 阻断 ---
57
57
  {
58
58
  const d = mkfix();
59
59
  base(d);
@@ -63,12 +63,12 @@ const FM_L0_NEW = '状态: draft\n级别: L0\n日期: 2026-10-01\n模块: ui';
63
63
  && r.out.includes('workflow/intents/2026-09-28-old-l1.md') && r.out.includes('修法'),
64
64
  r.out);
65
65
  }
66
- // --- S2b:多个 L0/L1 活跃 → 点名最新(日期排序)---
66
+ // --- S2b:多个低泳道(L1)活跃 → 点名最新(日期排序)---
67
67
  {
68
68
  const d = mkfix();
69
- base(d, { extra: ['workflow/intents/2026-10-01-new-l0.md', FM_L0_NEW] });
69
+ base(d, { extra: ['workflow/intents/2026-10-01-new-low.md', FM_LOW_NEW] });
70
70
  const r = run(d, { staged: ['templates/t.md'] });
71
- check('S2b 多活跃 L0/L1 → 关联入口取最新日期件', r.code === 1 && r.out.includes('2026-10-01-new-l0') && r.out.includes('共 2 件'), r.out);
71
+ check('S2b 多活跃 L1 → 关联入口取最新日期件', r.code === 1 && r.out.includes('2026-10-01-new-low') && r.out.includes('共 2 件'), r.out);
72
72
  }
73
73
  // --- S2c:experiment 分支 → 检查 A 降级 advisory ---
74
74
  {