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
@@ -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 一手事实,
@@ -30,6 +30,7 @@ import path from 'node:path';
30
30
  import { spawnSync } from 'node:child_process';
31
31
  import { laneOfEntry, laneOfDoc } from './stage-gates.mjs';
32
32
  import { loadKitPolicy } from './policy.mjs';
33
+ import { loadLevelHints, hintLevelFor, rankOf, maxLevel } from './level-criteria.mjs';
33
34
 
34
35
  const argv = process.argv.slice(2);
35
36
  const argOf = (k) => {
@@ -126,32 +127,80 @@ const collectActive = () => {
126
127
  const blocks = [];
127
128
  const advisories = [];
128
129
 
130
+ // --- 检查 A0:level-hints 路径下限对账(2026-10-13 hints-backstop)----------------
131
+ // 背景:此前「就高不就低」只在 flow-kit route 输出 advisory,而提交面判低门只认 lane-surfaces.txt
132
+ // → 两份清单各管一头、**无机器对账**:项目在 hints 里登记了「改这些面至少 L2/L3」,提交面却可能
133
+ // 靠一个 L1 活跃入口放行(判低有机可乘)。本段把 hints 接进提交面:
134
+ // ① hints 命中 → 要求存在**级别 ≥ 命中档**的活跃入口(不只「有个 high 入口」),否则 hard 拦;
135
+ // ② hints 已登记高风险面、lane-surfaces.txt 却缺失/为空 → advisory 提示「意图面登记了、提交面无兜底」。
136
+ // fail-closed:hints 语法错 → hard 拦(写错配置比没配置危险,见 level-criteria parseLevelHints)。
137
+ const hintsState = (() => {
138
+ try {
139
+ const { hints } = loadLevelHints(ROOT);
140
+ return { hints, invalid: null };
141
+ } catch (e) {
142
+ return { hints: [], invalid: e.message };
143
+ }
144
+ })();
145
+
146
+ if (hintsState.invalid) {
147
+ blocks.push([
148
+ '[level-hints 配置错误] .agents/level-hints.txt 非法(档位只收 L2/L3;ERE 不得带行内注释)——配置面坏了整条兜底不可信:',
149
+ ...String(hintsState.invalid).split('\n').map((l) => ` ${l}`),
150
+ ' 修法:修正清单后重试(fail-closed,防静默空洞——写错配置比没配置危险:没配置会提示,写错会误判档位)',
151
+ ].join('\n'));
152
+ }
153
+
154
+ // 命中判定复用判据单源的 hintLevelFor(2026-10-13 executor-review):此前本文件手写了一遍
155
+ // 「路径归一化 + 多条命中取高档」,与 level-criteria.mjs:104 的实现同形但独立演进——
156
+ // 双源漂移是本仓硬规则(doctor 段就写着「不复刻解析器」),一旦归一化口径调整,
157
+ // route 会判 L1 而本门拦 L3,且没有任何测试能发现。
158
+ const hintHits = hintsState.hints.length
159
+ ? STAGED
160
+ .map((p) => ({ p, lv: hintLevelFor(hintsState.hints, [p]).level }))
161
+ .filter((x) => x.lv)
162
+ : [];
163
+ const hintLevel = hintHits.reduce((acc, x) => maxLevel(acc, x.lv), '');
164
+
129
165
  // --- 检查 A:触达面判低 ---
130
166
  const patterns = loadPatterns();
167
+
168
+ if (hintLevel && !patterns.length) {
169
+ advisories.push(
170
+ `[level-hints 无提交面兜底] .agents/level-hints.txt 登记了 ${hintHits.length} 个命中(${hintHits.slice(0, 5).map((x) => `${x.p}→${x.lv}`).join(' ')})——`
171
+ + '但 .agents/lane-surfaces.txt 缺失或为空,触达面判低检查整体跳过:意图面登记了高风险面,提交面却无兜底',
172
+ );
173
+ }
131
174
  const hits = patterns.length ? STAGED.filter((p) => patterns.some((re) => re.test(p))) : [];
132
- if (hits.length) {
175
+ // 本次提交触达到的**最低应然档位**:lane-surfaces 命中隐含 L2(沿原口径),hints 命中按登记档取。
176
+ // 混档取高——与 level-criteria maxLevel 同一口径(就高不就低在提交面同样成立)。
177
+ const requiredLevel = maxLevel(hits.length ? 'L2' : '', hintLevel);
178
+ if (requiredLevel) {
133
179
  const active = collectActive();
134
- const hasHigh = active.some((e) => e.lane === 'high');
135
- if (!hasHigh) {
136
- const low = active.filter((e) => e.lane !== 'high'); // low + suspect 均拦(suspect 不作豁免依据)
137
- if (low.length) {
138
- const newest = [...low].sort((a, b) => (b.date || '').localeCompare(a.date || '') || b.rel.localeCompare(a.rel))[0];
139
- const fixVerb = newest.lane === 'suspect'
140
- ? '① 对齐该入口「级别」与「risk_level」两字段至一致的 L2/L3(fill-intent 双写口径;现两值缺失/非法/分歧)并补同名 spec(.agents/commands/design.md)'
141
- : '① 升级该入口「级别」与「risk_level」两字段至 L2/L3 并补同名 spec(.agents/commands/design.md)';
142
- const text = [
143
- `[触达面判低] 暂存命中 L2 触达面,但活跃入口无泳道 high(存在 low/suspect):`,
144
- ` 命中: ${hits.join(' ')}`,
145
- ` 关联入口: ${newest.rel}(级别 ${newest.level || '缺失'}/risk_level ${newest.risk || '缺失'}→ 泳道 ${newest.lane}${newest.date ? `,${newest.date}` : ''};活跃非 high 共 ${low.length} 件)`,
146
- ` 修法(三选一): ${fixVerb}`,
147
- ` ② 该入口已完成则关单(done)或显式放弃(superseded/cancelled)——不留活跃残留`,
148
- ` ③ 面清单误配则修 .agents/lane-surfaces.txt`,
149
- ].join('\n');
150
- if (isExperiment) advisories.push(`(advisory·experiment 泳道,不阻断;转正由 pre-push 加固门兜底)\n${text}`);
151
- else blocks.push(text);
152
- } else {
153
- advisories.push(`[触达面判低](advisory)暂存命中 L2 触达面(${hits.length} 个文件),但当前无任何活跃入口——若非 docs / bootstrap 豁免类改动(.agents/commands/new-task.md §intent 豁免),请先立入口文档`);
154
- }
180
+ const requiredRank = rankOf(requiredLevel) ?? 1;
181
+ // 入口是否够格:级别 rank ≥ 应然档。**suspect(级别/risk_level 缺失、非法或分歧)一律不够格**——
182
+ // 沿用原口径「suspect 不作豁免依据」:分歧(L2/L1)这类看似够档的入口,机器无法确认它真罩着这批改动。
183
+ const enough = (e) => e.lane !== 'suspect' && (rankOf(e.level) ?? -1) >= requiredRank;
184
+ const insufficient = active.filter((e) => !enough(e));
185
+ if (insufficient.length) {
186
+ const newest = [...insufficient].sort((a, b) => (b.date || '').localeCompare(a.date || '') || b.rel.localeCompare(a.rel))[0];
187
+ const hitList = [...hits.map((p) => `${p}(触达面)`), ...hintHits.map((x) => `${x.p}(hints→${x.lv})`)];
188
+ const fixVerb = newest.lane === 'suspect'
189
+ ? `① 对齐该入口「级别」与「risk_level」两字段至一致的 ${requiredLevel}(fill-intent 双写口径;现两值缺失/非法/分歧)并补同名 spec(.agents/commands/design.md)`
190
+ : `① 升级该入口「级别」与「risk_level」两字段至 ${requiredLevel} 及以上并补同名 spec(.agents/commands/design.md)`;
191
+ const text = [
192
+ `[触达面判低] 暂存命中高风险面,但活跃入口无 ${requiredLevel} 及以上(存在更低档/low/suspect):`,
193
+ ` 命中: ${hitList.join(' ')}`,
194
+ ` 关联入口: ${newest.rel}(级别 ${newest.level || '缺失'}/risk_level ${newest.risk || '缺失'}→ 泳道 ${newest.lane}${newest.date ? `,${newest.date}` : ''};活跃不足共 ${insufficient.length} 件)`,
195
+ ...(hintLevel ? [' 判据:.agents/level-hints.txt 登记的路径下限——就高不就低在提交面同样成立(此前只 advisory,无门)'] : []),
196
+ ` 修法(三选一): ${fixVerb}`,
197
+ ` ② 该入口已完成则关单(done)或显式放弃(superseded/cancelled)——不留活跃残留`,
198
+ ` ③ 清单误配则修 .agents/lane-surfaces.txt${hintLevel ? ' 或 .agents/level-hints.txt' : ''}`,
199
+ ].join('\n');
200
+ if (isExperiment) advisories.push(`(advisory·experiment 泳道,不阻断;转正由 pre-push 加固门兜底)\n${text}`);
201
+ else blocks.push(text);
202
+ } else if (!active.length) {
203
+ advisories.push(`[触达面判低](advisory)暂存命中高风险面(${hits.length + hintHits.length} 个文件,应然 ${requiredLevel}),但当前无任何活跃入口——若非 docs / bootstrap 豁免类改动(.agents/commands/new-task.md §intent 豁免),请先立入口文档`);
155
204
  }
156
205
  }
157
206
 
@@ -176,7 +225,7 @@ if (isExperiment) {
176
225
  for (const a of advisories) console.error(`泳道完整性(增量)— advisory:\n${a}\n`);
177
226
  if (blocks.length) {
178
227
  for (const b of blocks) console.error(`泳道完整性(增量)— BLOCK:\n${b}\n`);
179
- console.error('pre-commit: 泳道完整性未通过,提交已阻止(判据见 .agents/commands/new-task.md §级别判断)');
228
+ console.error('pre-commit: 泳道完整性未通过,提交已阻止(判据单源见 .agents/scripts/level-criteria.mjs)');
180
229
  process.exit(1);
181
230
  }
182
231
  process.exit(0);
@@ -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
  {
@@ -200,6 +200,72 @@ const FM_L0_NEW = '状态: draft\n级别: L0\n日期: 2026-10-01\n模块: ui';
200
200
  const r = run(d, { staged: ['ok/a.ts'] });
201
201
  check('配置含非法 ERE → fail-closed 阻断并指出坏行', r.code === 1 && r.out.includes('[面清单语法错误]') && r.out.includes('[invalid('), r.out);
202
202
  }
203
+ // --- S-hints(2026-10-13 hints-backstop):level-hints 接入提交面判低 ---
204
+ // 此前「就高不就低」只在 flow-kit route 输出 advisory,提交面只认 lane-surfaces → 两份清单无机器对账。
205
+ {
206
+ // ① hints 命中 L3,但活跃入口只有 L2 → 判低必须拦(此前「有个 high 入口」就放行)
207
+ const d = mkfix();
208
+ base(d, { level: 'L2' });
209
+ w(d, '.agents/level-hints.txt', 'L3 ^src/db/\n');
210
+ const r = run(d, { staged: ['src/db/schema.sql'] });
211
+ check('S-hints hints 命中 L3 + 活跃仅 L2 → 阻断(要求入口 ≥ L3,不只「有个 high」)',
212
+ r.code === 1 && r.out.includes('[触达面判低]') && r.out.includes('src/db/schema.sql')
213
+ && r.out.includes('L3') && r.out.includes('level-hints.txt'), r.out);
214
+
215
+ // ② 升到 L3 后放行
216
+ const d2 = mkfix();
217
+ base(d2, { level: 'L3' });
218
+ w(d2, '.agents/level-hints.txt', 'L3 ^src/db/\n');
219
+ const r2 = run(d2, { staged: ['src/db/schema.sql'] });
220
+ check('S-hints 入口升到 L3 → 放行', r2.code === 0 && !r2.out.includes('[触达面判低]'), r2.out);
221
+
222
+ // ③ 无 hints(默认空)→ 不产生任何新阻断面(旧装户零影响)
223
+ const d3 = mkfix();
224
+ base(d3, { level: 'L1' });
225
+ const r3 = run(d3, { staged: ['src/db/schema.sql'] });
226
+ check('S-hints 未配 hints → 不触发新判定(默认空=纯语义定级)', !r3.out.includes('[触达面判低]'), r3.out);
227
+
228
+ // ④ hints 命中但无任何活跃入口 → advisory(与检查 A 同口径,豁免 docs/bootstrap 类)
229
+ const d4 = mkfix();
230
+ w(d4, '.agents/lane-surfaces.txt', '^templates/\n');
231
+ w(d4, '.agents/level-hints.txt', 'L2 ^src/db/\n');
232
+ const r4 = run(d4, { staged: ['src/db/x.sql'] });
233
+ check('S-hints 命中但无活跃入口 → advisory 不阻断',
234
+ r4.code === 0 && r4.out.includes('触达面判低') && r4.out.includes('advisory'), r4.out);
235
+
236
+ // ⑤ hints 语法错 → fail-closed(写错配置比没配置危险)
237
+ const d5 = mkfix();
238
+ base(d5, { level: 'L2' });
239
+ w(d5, '.agents/level-hints.txt', 'L3 [bad(\n');
240
+ const r5 = run(d5, { staged: ['ok/a.ts'] });
241
+ check('S-hints 非法 hints → fail-closed 阻断并带路径行号',
242
+ r5.code === 1 && r5.out.includes('[level-hints 配置错误]') && r5.out.includes('level-hints.txt'), r5.out);
243
+
244
+ // ⑥ 两份清单交叉对账:hints 登记了高风险面、lane-surfaces 为空 → advisory 点名「提交面无兜底」
245
+ const d6 = mkfix();
246
+ w(d6, '.agents/level-hints.txt', 'L2 ^src/db/\n');
247
+ const r6 = run(d6, { staged: ['src/db/x.sql'] });
248
+ check('S-hints hints 已登记但触达面清单空 → advisory 点名无兜底',
249
+ r6.code === 0 && r6.out.includes('level-hints 无提交面兜底'), r6.out);
250
+
251
+ // ⑦ L0 档 hints → 配置错误 fail-closed(2026-10-13 executor-review 实测 Major)
252
+ // 原实现接受 `L0 ^docs/`,提交面判低门当真 → 纯文档提交被硬拦,修法还让人「升级至 L0 及以上」
253
+ const d7 = mkfix();
254
+ base(d7, { level: 'L1' });
255
+ w(d7, '.agents/level-hints.txt', 'L0 ^docs/\n');
256
+ const r7 = run(d7, { staged: ['docs/readme.md'] });
257
+ check('S-hints L0 档 hints → fail-closed 阻断(不拿低档当门槛)',
258
+ r7.code === 1 && r7.out.includes('[level-hints 配置错误]') && r7.out.includes('L2 或 L3'), r7.out);
259
+
260
+ // ⑧ 路径归一化口径与判据单源一致(复用 hintLevelFor,不再各自复刻)
261
+ // 同一文件同时命中 hints 与触达面 → 判据里点名两处来源;Windows 反斜杠写法也要能命中
262
+ const d8 = mkfix();
263
+ base(d8, { level: 'L3' });
264
+ w(d8, '.agents/level-hints.txt', 'L3 ^src/db/\n');
265
+ const r8 = run(d8, { staged: ['src\\db\\a.sql'] });
266
+ check('S-hints 反斜杠路径经单源归一后仍命中(复用 hintLevelFor)',
267
+ r8.code === 0 && !r8.out.includes('[触达面判低]'), r8.out);
268
+ }
203
269
  // --- git 真仓端到端:走生产发现路径(quotepath=off + diff --cached)---
204
270
  {
205
271
  const d = mkfix();