@event4u/agent-config 12.0.0 → 13.0.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 (148) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +273 -0
  3. package/README.md +1 -1
  4. package/agents/templates/.ai-council.yml.example +46 -3
  5. package/dist/agent-src/commands/cost/report.md +23 -0
  6. package/dist/agent-src/contexts/execution/subagent-routing.md +148 -33
  7. package/dist/agent-src/contexts/model-recommendations.md +6 -4
  8. package/dist/agent-src/contexts/subagent-configuration.md +69 -7
  9. package/dist/agent-src/rules/autonomous-execution.md +1 -1
  10. package/dist/agent-src/rules/design-fidelity.md +1 -1
  11. package/dist/agent-src/rules/design-review-after-ui-write.md +10 -5
  12. package/dist/agent-src/rules/evaluator-independence.md +27 -5
  13. package/dist/agent-src/rules/git-history-discipline.md +5 -1
  14. package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
  15. package/dist/agent-src/rules/missing-skill-recovery.md +89 -0
  16. package/dist/agent-src/rules/ui-audit-gate.md +9 -4
  17. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +33 -0
  18. package/dist/agent-src/scripts/roadmap_gates.ts +30 -2
  19. package/dist/agent-src/scripts/update_roadmap_progress.ts +47 -1
  20. package/dist/agent-src/skills/authz-review/SKILL.md +4 -0
  21. package/dist/agent-src/skills/design-system-capture/SKILL.md +4 -0
  22. package/dist/agent-src/skills/design-system-capture/references/snapshot-preference-order.md +77 -0
  23. package/dist/agent-src/skills/merge-conflicts/SKILL.md +4 -0
  24. package/dist/agent-src/skills/prompt-optimizer/SKILL.md +17 -1
  25. package/dist/agent-src/skills/roadmap-writing/SKILL.md +11 -11
  26. package/dist/agent-src/skills/systematic-debugging/SKILL.md +4 -0
  27. package/dist/agent-src/skills/threat-modeling/SKILL.md +3 -0
  28. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  29. package/dist/agent-src/templates/roadmaps.md +41 -3
  30. package/dist/cli/registry.js +1 -0
  31. package/dist/cli/registry.js.map +1 -1
  32. package/dist/cli-delegate/{chunk-NN4KW4HZ.js → chunk-3B72LSZL.js} +123 -2338
  33. package/dist/cli-delegate/{chunk-55WZN5VF.js → chunk-EVTY7ISR.js} +2 -2
  34. package/dist/cli-delegate/chunk-N7FEST6D.js +3895 -0
  35. package/dist/cli-delegate/{chunk-L565YFXH.js → chunk-XURJ4AQC.js} +566 -298
  36. package/dist/cli-delegate/cmd_conformance.js +4 -4
  37. package/dist/cli-delegate/cmd_doctor.js +2 -2
  38. package/dist/cli-delegate/cmd_export.js +2 -2
  39. package/dist/cli-delegate/cmd_handoff.js +1 -1
  40. package/dist/cli-delegate/cmd_prune.js +2 -2
  41. package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
  42. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  43. package/dist/cli-delegate/cmd_sync.js +2 -2
  44. package/dist/cli-delegate/cmd_uninstall.js +2 -2
  45. package/dist/cli-delegate/cmd_validate.js +2 -2
  46. package/dist/discovery/deprecation-report.md +1 -1
  47. package/dist/discovery/discovery-manifest.json +44 -23
  48. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  49. package/dist/discovery/discovery-manifest.summary.md +3 -3
  50. package/dist/discovery/dormancy-report.md +1 -1
  51. package/dist/discovery/orphan-report.md +1 -1
  52. package/dist/discovery/packs.json +6 -5
  53. package/dist/discovery/trust-report.md +2 -2
  54. package/dist/discovery/workspaces.json +5 -4
  55. package/dist/hooks/dispatch.js +10625 -8541
  56. package/dist/install/install.mjs +754 -484
  57. package/dist/mcp/registry-manifest.json +3 -3
  58. package/dist/mcp/server.json +2 -2
  59. package/dist/mcp/server.mjs +1302 -167
  60. package/dist/router.json +1 -1
  61. package/dist/scripts/_lib/model_tier.js +9 -0
  62. package/dist/scripts/_lib/model_tier.js.map +1 -1
  63. package/dist/scripts/condense.js +50 -0
  64. package/dist/scripts/condense.js.map +1 -1
  65. package/dist/server/schemas/settings.js +2 -2
  66. package/dist/server/schemas/settings.js.map +1 -1
  67. package/docs/MIGRATION.md +20 -1
  68. package/docs/guidelines/design-fidelity-mechanics.md +21 -0
  69. package/docs/guidelines/design-handover-extraction.md +87 -0
  70. package/docs/guidelines/prompt-templates.md +24 -1
  71. package/package.json +1 -1
  72. package/src/agent-src/scripts/archive_completed_roadmaps.ts +33 -0
  73. package/src/agent-src/scripts/roadmap_gates.ts +30 -2
  74. package/src/agent-src/scripts/update_roadmap_progress.ts +47 -1
  75. package/src/config/evaluator-budgets.json +9 -8
  76. package/src/config/gate-coverage.yml +63 -11
  77. package/src/config/gate-violation-baselines.json +12 -0
  78. package/src/config/hook-token-budget.json +15 -2
  79. package/src/config/preamble-payload-budget.json +28 -0
  80. package/src/scripts/_dispatch.bash +10 -0
  81. package/src/scripts/_lib/bench_ab_complexity.ts +439 -0
  82. package/src/scripts/_lib/bench_ab_safety_tier.ts +142 -0
  83. package/src/scripts/_lib/bench_ab_scoring_v2.ts +28 -3
  84. package/src/scripts/_lib/bench_ab_search_adherence.ts +423 -0
  85. package/src/scripts/_lib/bench_ab_size_claim.ts +206 -0
  86. package/src/scripts/_lib/bench_ab_workspace.ts +111 -0
  87. package/src/scripts/_lib/git_common_dir.ts +173 -0
  88. package/src/scripts/_lib/model_tier.ts +9 -0
  89. package/src/scripts/_lib/release_highlights.ts +108 -7
  90. package/src/scripts/_lib/skill_catalogue.ts +1115 -0
  91. package/src/scripts/_lib/tier_budget_routing.ts +31 -331
  92. package/src/scripts/ai_council/clients.ts +197 -18
  93. package/src/scripts/ai_council/config.ts +16 -1
  94. package/src/scripts/ai_council/low_impact.ts +14 -1
  95. package/src/scripts/ai_council/orchestrator.ts +14 -1
  96. package/src/scripts/ai_council/transport_resolver.ts +32 -2
  97. package/src/scripts/bench_ab_v2_complexity.ts +252 -0
  98. package/src/scripts/bench_ab_v2_run.ts +3 -42
  99. package/src/scripts/bench_ab_v2_safety.ts +296 -0
  100. package/src/scripts/bench_ab_v2_search.ts +343 -0
  101. package/src/scripts/bench_ab_v2_stats.ts +191 -0
  102. package/src/scripts/build_archive_index.ts +383 -0
  103. package/src/scripts/capture_skill_catalogue.ts +279 -317
  104. package/src/scripts/check_branch_freshness.ts +7 -1
  105. package/src/scripts/check_release_published.ts +6 -2
  106. package/src/scripts/check_release_trunk_sync.ts +18 -2
  107. package/src/scripts/check_source_size_budget.ts +428 -0
  108. package/src/scripts/check_suppression_hygiene.ts +5 -1
  109. package/src/scripts/condense.ts +50 -1
  110. package/src/scripts/cost/backfill_rates.mjs +298 -0
  111. package/src/scripts/cost/budget.mjs +12 -32
  112. package/src/scripts/cost_summary.ts +25 -0
  113. package/src/scripts/dispatch_r2_reviewer.ts +15 -5
  114. package/src/scripts/evidence_report.ts +15 -4
  115. package/src/scripts/external_sources_denylist.json +1 -0
  116. package/src/scripts/hook_manifest.yaml +75 -15
  117. package/src/scripts/hooks/concern_registry.ts +4 -0
  118. package/src/scripts/hooks/orchestration_record_hook.ts +21 -0
  119. package/src/scripts/hooks/skill_route_hook.ts +226 -0
  120. package/src/scripts/hooks/tool_result_bytes_hook.ts +219 -0
  121. package/src/scripts/hooks/ui_route_nudge_hook.ts +6 -3
  122. package/src/scripts/hot_context_hook.ts +13 -1
  123. package/src/scripts/install.ts +129 -0
  124. package/src/scripts/janitor.ts +77 -2
  125. package/src/scripts/lint_design_antipattern_parity.ts +40 -6
  126. package/src/scripts/lint_harvest_provenance.ts +35 -1
  127. package/src/scripts/lint_plan_risk_register.ts +22 -9
  128. package/src/scripts/lint_roadmap_blockers.ts +109 -6
  129. package/src/scripts/lint_rule_skill_pack_reach.ts +43 -0
  130. package/src/scripts/lint_scheduled_deprecations.ts +738 -0
  131. package/src/scripts/lint_skill_router_head.ts +21 -0
  132. package/src/scripts/mcp_server/consumer_tool_catalog.json +26 -3
  133. package/src/scripts/mcp_server/tool_catalog_source.ts +6 -24
  134. package/src/scripts/mcp_server/tools.ts +87 -0
  135. package/src/scripts/migration_status.ts +17 -1
  136. package/src/scripts/probe_review_binding_drift.ts +731 -0
  137. package/src/scripts/release.ts +100 -6
  138. package/src/scripts/routing_doctor.ts +50 -13
  139. package/src/scripts/schemas/command.schema.json +3 -3
  140. package/src/scripts/schemas/skill.schema.json +20 -2
  141. package/src/scripts/schemas/subagent.schema.json +2 -2
  142. package/src/scripts/session_eol_report.ts +81 -0
  143. package/src/scripts/trigger_coverage.ts +224 -3
  144. package/src/scripts/workspace_doctor.ts +464 -0
  145. package/src/scripts/worktree_cleanup_check.ts +2 -1
  146. package/src/server/schemas/settings.ts +2 -2
  147. package/dist/cli-delegate/chunk-AYKQGIBG.js +0 -1594
  148. package/src/config/budget-routing.json +0 -7
@@ -8,8 +8,10 @@ provider's model or billing rule.
8
8
 
9
9
  ## Inputs
10
10
 
11
- - The sub-task's declared `model_tier` (`lite | medium | high | inherit`,
12
- per [`model-recommendations`](../model-recommendations.md)).
11
+ - The sub-task's declared `model_tier` (`lite | medium | high | frontier |
12
+ inherit`, per [`model-recommendations`](../model-recommendations.md)).
13
+ `frontier` (ADR-232) is declarable and opt-in; it is never the resolution of
14
+ `inherit` — see the top-band invariant below.
13
15
  - `subagents.downshift`, `subagents.quota_arbitrage`, `subagents.model_map`
14
16
  (see [`auto-orchestration-activation`](auto-orchestration-activation.md)).
15
17
  - The host-capability manifest's `separate_quota_pool`
@@ -27,43 +29,154 @@ THE SAME MINUS THE QUOTA WIN.
27
29
  1. **Downshift off** (`downshift: false`) → every sub-task runs on the session
28
30
  tier. No routing change.
29
31
  2. **Downshift on** → a sub-task runs on its declared tier; an `inherit` task
30
- runs on the session tier. The tier resolves to a model alias via
31
- `model_map`; an empty entry means "use the tier's runtime default" — never a
32
- baked-in provider model name.
32
+ runs on the session tier, **bounded by the top-band invariant below**. The
33
+ tier resolves to a model alias via `model_map`; an empty entry means "use
34
+ the tier's runtime default" — never a baked-in provider model name.
33
35
  3. **Quota arbitrage** → prefer the **separate** quota pool for the sub-task
34
36
  **only when** `subagents.quota_arbitrage == true` **and**
35
37
  `manifest.separate_quota_pool == true`. Otherwise the shared pool is used and
36
38
  the tier/model choice is unchanged. This is the only place the
37
39
  "Sonnet-has-its-own-allowance" idea lives, and it lives as a runtime-detected
38
40
  flag, never as portable prose.
39
- 4. **Budget routing** (design:
40
- [`budget-routing` contract](../../../../docs/contracts/budget-routing.md)) →
41
- AFTER the tier resolves per 1–3, the budget relation via `pickTier`
42
- (`src/scripts/_lib/tier_budget_routing.ts`) would apply: cheapest
43
- classifier-adequate tier WITH available budget; that tier exhausted or
44
- cooling → next tier up with budget; all unavailable → session model +
45
- surfaced one-line notice. Work is never blocked to save money. Before
46
- the dispatch is created, acquire the atomic reserve
47
- (`acquireBudgetPermit`; check state via `node src/scripts/cost/budget.mjs
48
- tier <t>`); a 429/quota error trips `tripCooldown` for that tier and
49
- falls back per the relation — never a retry loop.
50
- `subagents.budget_routing` was removed as a settings key (always-on
51
- orchestration, road-to-always-on-orchestration Phase 1: no per-layer
52
- on/off switch survives) — `pickTier` has NO production caller today, so
53
- this point documents the DESIGNED relation, not a currently wired one;
54
- wiring it is a later phase of that roadmap (council-side
55
- `cli_call_budget`/`cost_budget` are the caps that replace the settings
56
- gate). Every budget-routed dispatch, once wired, would emit one
57
- `orchestration_record` line (tier + provenance-tagged token delta) so
58
- realized savings are measured, not asserted.
41
+ 4. **Budget routing — ARCHIVED 2026-08-16, there is no fourth step.** This
42
+ position used to describe a budget relation applied after the tier resolves
43
+ per 1–3: cheapest classifier-adequate tier WITH available budget, an atomic
44
+ pre-dispatch reserve, a cool-down on quota errors. None of it exists any
45
+ more. A converged AI-council verdict (anthropic + openai, 2 of 2) archived
46
+ `pickTier` and the permit lifecycle, because the `routing_switch` they
47
+ required lost its only source when `subagents.budget_routing` was deliberately
48
+ deleted by always-on orchestration — so wiring meant inventing a replacement
49
+ for a removed category — and because `session_tier` sits non-null in 0 of 327
50
+ records, leaving the saving unmeasurable in principle. Migration record and
51
+ the union revisit-if:
52
+ [`budget-routing` contract](../../../../docs/contracts/budget-routing.md).
53
+ **Tier resolution therefore ends at step 3.** What survives of the module is
54
+ `TIER_ORDER` and `readCooldowns`, read by `routing:doctor` as a diagnostic —
55
+ monitoring, not routing, and note that nothing writes the cool-down file any
56
+ more, which is why the doctor reports that state as *unavailable, no
57
+ producer* rather than as a measured "not cooling".
58
+
59
+ ### The top-band invariant (binding)
60
+
61
+ ```
62
+ UNDER A TOP-BAND SESSION, `inherit` RESOLVES TO AT MOST `high`.
63
+ A SLICE THAT GENUINELY NEEDS THE SESSION'S OWN BAND DECLARES IT.
64
+ ONE TOP-BAND CONTEXT PER TASK — THE ORCHESTRATOR'S. NEVER A SECOND BY DEFAULT.
65
+ ```
66
+
67
+ Read as one line: an undeclared slice under a top-band session runs at most
68
+ `high`, never the session's own band.
69
+
70
+ `inherit` means *model-agnostic*, not *whatever the session happens to cost*.
71
+ Left unbounded it is the one declaration that silently buys the most expensive
72
+ band available, for exactly the slices whose authors judged the model choice
73
+ not to matter — the weakest possible reason to spend the most.
74
+
75
+ **The clamp is `high`, not the top mapped tier, and the distinction is not
76
+ cosmetic.** ADR-232 added `frontier` to the tier→model map, so the top *mapped*
77
+ rung is now `frontier`; clamping there would clamp nothing. The bound is the
78
+ top **generally-recommended** band, which is what
79
+ [`model-recommendations`](../model-recommendations.md) already states from the
80
+ other side — its `frontier` row reads *"Never the resolution of `inherit`"*.
81
+ This clause is that same rule written where the resolution actually happens.
82
+
83
+ Consequences, stated so the cost stays visible rather than removed:
84
+
85
+ - A slice that needs `frontier` **declares `model_tier: frontier`** — a value
86
+ § Inputs above lists as declarable, precisely so this escape hatch is legal
87
+ rather than instructed-but-forbidden. The declaration is the whole point:
88
+ the spend becomes an authored decision with a name on it instead of a
89
+ default nobody chose.
90
+ - The clamp changes only the **undeclared** case. Every explicit
91
+ `model_tier:` is honoured verbatim, upward included.
92
+ - A session running *below* `high` is untouched — `inherit` still resolves to
93
+ the session tier. "At most" is a ceiling, never a floor, and this clause
94
+ never promotes a slice.
95
+ - A **declared `frontier` implementer** keeps its band, and its judge is
96
+ governed by [`subagent-configuration`](../subagent-configuration.md)
97
+ § The judge cap — which handles that case explicitly rather than leaving
98
+ the reviewer weaker than the author.
99
+
100
+ **Honest scope.** This is a policy clause in a context file, so it binds the
101
+ agent that reads it and nothing else; no gate re-derives an `inherit`
102
+ resolution at dispatch time. `enforced_by: none`, deliberately — the same
103
+ boundary the safety-floor rules state for their own obligations.
104
+
105
+ ### The non-escalation floor (binding)
106
+
107
+ ```
108
+ A SLICE WHOSE DISPATCH OVERHEAD EXCEEDS THE SAVING FROM DOWNSHIFTING
109
+ STAYS IN-SESSION. SPLITTING WORK WHOSE OVERHEAD EATS ITS OWN SAVING
110
+ IS THE ANTI-GOAL, NOT THE MECHANISM WORKING.
111
+ ```
112
+
113
+ Every dispatch pays a fixed cost the in-session path does not: the slice's
114
+ prompt is re-sent to a fresh context, the return is read back, and the
115
+ orchestrator verifies it. Below some slice size that overhead exceeds
116
+ whatever the cheaper tier saved, and the delegation is a net loss dressed as
117
+ an optimisation.
118
+
119
+ **The floor is a rule, not a number — and the reason is measured, not
120
+ assumed.** The step that authored this clause expected the same-band spawn
121
+ distribution to supply the number. It cannot, and the corpus says so
122
+ precisely. Over the full 327-record `orchestration` corpus (2026-07 plus
123
+ 2026-08):
124
+
125
+ | Field the saving model needs | Non-null records |
126
+ |---|---:|
127
+ | `dispatch_tokens` | 40 |
128
+ | `tier_chosen` | 1 |
129
+ | `session_tier` | **0** |
130
+
131
+ The three must co-occur on one dispatch to model a reduction, so the usable
132
+ intersection is **zero**. This is not a small sample — it is an empty one, and
133
+ `orchestration_savings_report.ts` already prints exactly that conclusion
134
+ (`MODELED cost reduction: n/a (needs dispatch_tokens + session_tier +
135
+ tier_chosen on a dispatch)`). `token_delta` does not rescue it: the hook
136
+ writes it as a constant `0` with provenance `estimated`, deliberately, because
137
+ no in-session counterfactual exists on disk to difference against.
138
+
139
+ So the floor binds as a judgement with a named direction rather than a
140
+ threshold:
141
+
142
+ - **Default to in-session** when the slice is small enough that stating it
143
+ costs about as much as doing it — a single-file edit, one grep, one
144
+ question with a known answer.
145
+ - **Delegate** when the slice carries its own reading (a file set to sweep, an
146
+ independent verification, a search whose output you do not want in context)
147
+ — there the returned conclusion is the saving, and it is not in tokens.
148
+ - **Never delegate to hit a target.** A dispatch count is not an outcome.
149
+
150
+ *Revisit-if:* a single dispatch record carries `dispatch_tokens`,
151
+ `session_tier` and `tier_chosen` together. At that point the floor is
152
+ computable and this clause should be replaced by the number, not amended
153
+ toward it. Filling `session_tier` is the blocking gap — it exists in the
154
+ schema and on the manual `orchestration_record` CLI path (`--session-tier`),
155
+ and the hook that produced all 327 records never sets it.
59
156
 
60
157
  **Cache trade-off (road-to-cache-economy Phase 4).** A tier downshift changes
61
- the sub-task's model, and the prompt cache is keyed by `(model, prefix)` — so
62
- a downshifted leg forfeits its model-scoped cache reads AND splits a cohort's
63
- shared prefix into two caches (downshifted legs vs. session-tier legs). The
64
- per-call model saving and the forfeited cache-read saving pull in opposite
65
- directions; which wins is a measured question, not a default this policy
66
- resolves — see
158
+ the sub-task's model, and the prompt cache is keyed by `(model, prefix)`. The
159
+ concern was that a downshifted leg forfeits its model-scoped cache reads AND
160
+ splits a cohort's shared prefix into two caches (downshifted legs vs.
161
+ session-tier legs).
162
+
163
+ **Measured 2026-08-16 —
164
+ [`downshift-vs-cache.md`](../../../../agents/evidence/analysis/downshift-vs-cache.md),
165
+ 611 subagent legs / 16,612 calls.** The first half does not hold: a subagent
166
+ leg's **first call realizes 2.8 % cache read**, so it starts cold and does not
167
+ inherit the session's cache — a downshift forfeits a cache the leg never had.
168
+ The cache is not lost, it is created on the other model and then read for the
169
+ rest of the leg (median 18 calls per leg, ~96.9 % read share). The second half
170
+ is **not measured** — nothing sizes cohort prefix sharing — but it is bounded
171
+ above: prefix splitting is a write-side cost, and writes are **3.1 %** of
172
+ subagent billable input against a 96.9 % read surface. Even attributing every
173
+ write to splitting, the two sit about an order of magnitude apart, in favour of
174
+ the downshift, so the direction does not depend on the unmeasured fraction.
175
+
176
+ This resolves the direction; it does not license downshifting as a target. The
177
+ cold start is paid per leg regardless of tier, which is precisely what the
178
+ non-escalation floor above bounds. Analogous downgrade/cache coupling on the
179
+ council side: see
67
180
  [`docs/contracts/ai-council-config.md`](../../../../docs/contracts/ai-council-config.md)
68
181
  § `model_downgrade` for the analogous downgrade/cache coupling this suite
69
182
  already ships on the council side.
@@ -80,8 +193,10 @@ rule, or context asserts "model X is free".
80
193
  [`src/scripts/_lib/subagent_routing.ts`](../../../../src/scripts/_lib/subagent_routing.ts)
81
194
  (`resolveSubagentRouting`), covered by
82
195
  [`tests/scripts/_lib_subagent_routing.test.ts`](../../../../tests/scripts/_lib_subagent_routing.test.ts).
83
- Budget layer: [`src/scripts/_lib/tier_budget_routing.ts`](../../../../src/scripts/_lib/tier_budget_routing.ts)
84
- (`pickTier`, `acquireBudgetPermit`, cool-downs), covered by
196
+ Tier vocabulary + cool-down state:
197
+ [`src/scripts/_lib/tier_budget_routing.ts`](../../../../src/scripts/_lib/tier_budget_routing.ts)
198
+ (`TIER_ORDER`, `readCooldowns` — the budget decision layer that used to live
199
+ there is archived, see step 4), covered by
85
200
  [`tests/scripts/tier_budget_routing.test.ts`](../../../../tests/scripts/tier_budget_routing.test.ts);
86
201
  live state: `agent-config routing:doctor` (orchestration section).
87
202
 
@@ -1,15 +1,17 @@
1
1
  # Model Recommendations
2
2
 
3
3
  Loaded by the `model-recommendation` rule. Expresses recommendations as
4
- **vendor-neutral capability tiers** (`lite | medium | high`, ADR-035), never
5
- vendor model names. Each agent resolves a tier to its own best model in that
6
- band; the package's only tier→model mapping is the Claude generator's
7
- (`high→opus`, `medium→sonnet`, `lite→haiku`).
4
+ **vendor-neutral capability tiers** (`lite | medium | high | frontier`,
5
+ ADR-035 as amended by ADR-232), never vendor model names. Each agent resolves a
6
+ tier to its own best model in that band; the package's only tier→model mapping
7
+ is the Claude generator's (`frontier→fable`, `high→opus`, `medium→sonnet`,
8
+ `lite→haiku`).
8
9
 
9
10
  ## Tier Profiles
10
11
 
11
12
  | Tier | Band | Claude resolution | Use for |
12
13
  |---|---|---|---|
14
+ | `frontier` | above the strongest generally-recommended band | Fable | **Opt-in, expected to stay rare.** Declare it only where the work genuinely needs capability beyond `high` — the point of the band is that such work *says so* rather than inheriting the session's model silently. Never the resolution of `inherit` |
13
15
  | `high` | strongest reasoning | Opus | Architecture, refactoring, complex debugging, code review, design decisions, multi-file changes, large-scale analysis / research / planning |
14
16
  | `medium` | balanced daily | Sonnet (latest) | Daily coding, known-cause bug fixes, tests, single-layer features, quality checks, config, documentation, implementation after a `high`-tier plan |
15
17
  | `lite` | fastest / cheapest | Haiku | Trivial mechanical edits — formatting, one-line config, rote renames, boilerplate stamping |
@@ -15,21 +15,83 @@ Loaded by the `subagent-orchestration` skill and the `/do-and-judge`,
15
15
  ## Model tier ladder
16
16
 
17
17
  The "one tier up" fallback walks this ladder. Session model is the
18
- starting point; judge picks the tier above.
18
+ starting point; judge picks the tier above. The ladder is written in
19
+ **bands**, not vendor models — the package keeps exactly one tier→model
20
+ mapping and it lives in
21
+ [`model-recommendations`](model-recommendations.md) (ADR-035 § 3). A
22
+ second per-vendor ladder here would be the two-clocks drift ADR-232 was
23
+ careful not to create.
19
24
 
20
25
  ```
21
- haiku → sonnet → opus
26
+ lite → medium → high
22
27
  ```
23
28
 
24
- If the session runs on **opus**, judge stays on opus (no higher tier
25
- available). If the session runs on **sonnet**, judge defaults to opus.
26
- If the session runs on **haiku**, judge defaults to sonnet.
29
+ If the session runs at **`high`**, judge stays at `high`. If the session
30
+ runs at **`medium`**, judge defaults to `high`. If the session runs at
31
+ **`lite`**, judge defaults to `medium`.
32
+
33
+ ### The judge cap (binding)
34
+
35
+ ```
36
+ THE ONE-TIER-UP ESCALATION STOPS AT `high` — IT NEVER CLIMBS INTO `frontier`.
37
+ A JUDGE IS NEVER BELOW ITS OWN IMPLEMENTER: A DECLARED `frontier`
38
+ IMPLEMENTER KEEPS A `frontier` JUDGE.
39
+ A SATURATED LADDER ESCALATES CROSS-VENDOR VIA THE COUNCIL —
40
+ NEVER INTO A SECOND SAME-VENDOR TOP-BAND AGENT.
41
+ ```
42
+
43
+ Two clauses that are easy to collapse into one and must not be. The cap
44
+ binds the **escalation**, not the floor:
45
+
46
+ | Implementer band | Judge band | Why |
47
+ |---|---|---|
48
+ | `lite` | `medium` | one tier up |
49
+ | `medium` | `high` | one tier up |
50
+ | `high` | `high` | escalation capped — never climbs into `frontier` |
51
+ | `frontier` | **`frontier`** | the floor wins: a judge is never below its author |
52
+
53
+ `frontier` (ADR-232) sits above `high` in the vocabulary, so a naive
54
+ "one tier up" would resolve a `high` implementer's judge to `frontier`
55
+ and buy the most expensive band available for every verification pass.
56
+ The cap forecloses that — `frontier` is opt-in by declaration only.
57
+
58
+ **But the cap must not invert the ladder it protects.** Reading it as a
59
+ flat ceiling would give a declared `frontier` implementer a `high`
60
+ judge, i.e. a reviewer strictly *weaker* than the author — worse than
61
+ the same-band case the one-tier-up rule exists to avoid, and a direct
62
+ contradiction of "never downshifts its judge below what the verify
63
+ contract requires" below. So `frontier` reaches the judge only where
64
+ the implementer already declared it, never by escalation from `high`.
65
+
66
+ **What to do when the ladder saturates.** At `high` and at `frontier`
67
+ the judge is same-band — the reviewer is no stronger than the author,
68
+ which is the condition the one-tier-up rule existed to avoid. The
69
+ answer is *different vendor*, not *bigger model*:
70
+
71
+ - Route the saturation case through
72
+ [`ai-council`](../skills/ai-council/SKILL.md), which polls models
73
+ outside the host session (Anthropic + OpenAI) and is therefore
74
+ genuinely independent of the implementer's framing.
75
+ - This is the escalation **instead of** a second top-band context, not
76
+ in addition to one. Two same-vendor top-band agents on one task cost
77
+ twice and correlate their blind spots; a second vendor costs less and
78
+ disagrees for real reasons.
79
+ - Cost boundary unchanged: the council is a network call and is
80
+ therefore never automatic. It is what the saturation case escalates
81
+ *to* when a second opinion is wanted, not a step every `high` task
82
+ takes ([`ai-council`](../skills/ai-council/SKILL.md) § Do NOT use).
83
+
84
+ **Honest scope.** This is policy prose in a context file — it binds the
85
+ agent that reads it and no gate re-derives a judge tier at dispatch
86
+ time. `enforced_by: none`, stated rather than implied.
27
87
 
28
88
  **Downshift never touches the judge.** When cost-aware routing runs an
29
89
  implementer on a downshifted tier (per the category → tier defaults in
30
90
  [`model-recommendations § Subagent Category → Tier Defaults`](model-recommendations.md)),
31
- the judge still resolves one tier above the IMPLEMENTER's resolved tier —
32
- so a `lite` implementer gets at least a `medium` judge. Downshifting an
91
+ the judge still resolves one tier above the IMPLEMENTER's resolved tier,
92
+ **with the escalation capped at `high` per the judge cap above** — so a
93
+ `lite` implementer gets at least a `medium` judge, and no *downshifted*
94
+ implementer escalates its judge into `frontier`. Downshifting an
33
95
  implementer never downshifts its judge below what the verify contract
34
96
  requires, and a judge is never `lite`.
35
97
 
@@ -67,7 +67,7 @@ ALLOWLIST > 20 ENTRIES IN ONE SESSION = THE LINTER IS WRONG.
67
67
  STOP. PROPOSE LINTER REDESIGN OR REMOVAL. DO NOT EXPAND THE ALLOWLIST FURTHER.
68
68
  ```
69
69
 
70
- Crossing 20 entries counts as the 3rd validation-target failure — fix the tool shape, not the list. Enforced at tool-call time by the `block-config-weakening` PreToolUse guard (`src/scripts/hooks/block_config_weakening.ts`) **on the three hosts that have a `pre_tool_use` slot at all** — augment, claude, cowork: it counts allowlist entries added per session, warns from 5, and blocks past 20. On cursor, cline, windsurf, gemini and copilot the guard has nowhere to bind, so there the cap is model-carried and "enforced at tool-call time" is not a claim this rule can make. Run `agent-config hooks:status` to see which slots are bound on the host you are actually on — the sibling rules `git-history-discipline` and `evaluator-independence` qualify the identical slot, and an unqualified claim here would read as a guarantee the manifest does not give. Baselines and budget thresholds warn only — a rising count there may be a legitimate ratchet reset, which the edit alone cannot distinguish. Verify with the narrowest tool that proves the target green (a single `curl` / Playwright spec for HTTP/UI, the test runner with a `--filter`, an `xdebug` step-through) — never a meta-pipeline as per-iteration probe. Failed-attempt detail, suppression-sweep equivalents, probe-efficiency detail, and adaptive effort & stop (RDP): [`autonomy-mechanics § Validation-loop budget`](../contexts/execution/autonomy-mechanics.md).
70
+ Crossing 20 entries counts as the 3rd validation-target failure — fix the tool shape, not the list. Enforced at tool-call time by the `block-config-weakening` PreToolUse guard (`src/scripts/hooks/block_config_weakening.ts`) **on `claude`, the one host that both binds `pre_tool_use` and honours a deny**: it counts allowlist entries added per session, warns from 5, and blocks past 20. It is *bound* on augment and cowork as well, and ignored there — `host_semantics.ts` verifies claude alone, and both trampolines discard dispatcher output and `exit 0` unconditionally. Everywhere else the cap is model-carried and "enforced at tool-call time" is not a claim this rule can make. **Corrected 2026-08-17 in both directions:** this sentence used to certify augment, claude and cowork as the enforcing set (an over-claim of two hosts) and to explain the rest with "the guard has nowhere to bind" (false for cursor, cline and gemini, whose native pre-tool events `native_event_aliases` already maps onto `pre_tool_use` — unbound, not unbindable; only windsurf and copilot have no pre-tool surface). The four states are tabulated once in [`hook-architecture-v1 § Which hosts carry pre_tool_use`](../docs/contracts/hook-architecture-v1.md). Run `agent-config hooks:status` to see which slots are bound on the host you are actually on — the sibling rules `git-history-discipline` and `evaluator-independence` qualify the identical slot, and an unqualified claim here would read as a guarantee the manifest does not give. Baselines and budget thresholds warn only — a rising count there may be a legitimate ratchet reset, which the edit alone cannot distinguish. Verify with the narrowest tool that proves the target green (a single `curl` / Playwright spec for HTTP/UI, the test runner with a `--filter`, an `xdebug` step-through) — never a meta-pipeline as per-iteration probe. Failed-attempt detail, suppression-sweep equivalents, probe-efficiency detail, and adaptive effort & stop (RDP): [`autonomy-mechanics § Validation-loop budget`](../contexts/execution/autonomy-mechanics.md).
71
71
 
72
72
  Body migrated to `contexts/execution/autonomy-mechanics.md` (per P4 of `road-to-kernel-and-router.md`) — opt-in detection summary, task-scope shapes table, N=3 mechanics, allowlist-antipattern detail, probe efficiency, adaptive effort (RDP).
73
73
  Trigger-set above activates this routing on demand, independent of the discipline profile (ADR-110).
@@ -171,7 +171,7 @@ introduced. The row that would have caught it is
171
171
  and it exists only because a review asked for it after the trigger had already
172
172
  shipped. Write that row first next time; it is the cheap half.
173
173
 
174
- Body migrated to [`guideline:design-fidelity-mechanics`](../docs/guidelines/design-fidelity-mechanics.md) (per P4 of `road-to-kernel-and-router.md`) — surgical visual edits (targeted-edit vs redesign-trigger discipline, stable anchors), asset & imagery discipline (owned-asset path, third-party delivery is self-hosted by default, real-imagery-as-proof, iconography floor, no unrequested filler), deviation-surfacing shape, failure-mode catalog, `daf-*` fixtures.
174
+ Body migrated to [`guideline:design-fidelity-mechanics`](../docs/guidelines/design-fidelity-mechanics.md) (per P4 of `road-to-kernel-and-router.md`) — URL / live-page handover (extraction into the `design-system.json` contract before the first UI write, the retrieval order, the lock boundary), surgical visual edits (targeted-edit vs redesign-trigger discipline, stable anchors), asset & imagery discipline (owned-asset path, third-party delivery is self-hosted by default, real-imagery-as-proof, iconography floor, no unrequested filler), deviation-surfacing shape, failure-mode catalog, `daf-*` fixtures.
175
175
  Trigger-set above activates this routing on demand, independent of the discipline profile (ADR-110).
176
176
 
177
177
  ## See also
@@ -114,11 +114,16 @@ still have no runtime consumer. A test — not a code path — keeps the two fro
114
114
  drifting: `ui_rule_triggers.test.ts` asserts every `file_pattern` declared here
115
115
  is accepted by that predicate.
116
116
 
117
- Warn-only, capped at two nudges per session, default-OFF, bound only on hosts
118
- carrying a `pre_tool_use` slot. A nudge that can be ignored is not enforcement,
119
- so `enforced_by:` stays `none` — it moves the day a mechanism can refuse, not
120
- the day a reminder appears. `agent-config hooks:status` answers whether the
121
- concern is bound on the host you are actually on.
117
+ Warn-only, capped at two nudges per session, default-OFF, and bound only in the
118
+ three `platforms:` rows that carry a `pre_tool_use` key — augment, claude,
119
+ cowork (**clarified 2026-08-17**: "hosts carrying a slot" read as a host
120
+ property and is a manifest one; three further hosts alias a native pre-tool
121
+ event with no binding, and only claude honours a deny — see
122
+ [`hook-architecture-v1 § Which hosts carry pre_tool_use`](../docs/contracts/hook-architecture-v1.md)).
123
+ The verdict is unchanged either way: a nudge that can be ignored is not
124
+ enforcement, so `enforced_by:` stays `none` — it moves the day a mechanism can
125
+ refuse, not the day a reminder appears. `agent-config hooks:status` answers
126
+ whether the concern is bound on the host you are actually on.
122
127
 
123
128
  What that leaves, and it is the useful part: the write side now has a named
124
129
  obligation and a route to the skill that discharges it, where before it had
@@ -95,13 +95,35 @@ Three limits, stated because they were measured. The pre-loaded-verdict list is
95
95
  a **phrase list**, so a paraphrase evades it — it catches recurrences of known
96
96
  steering wording, not steering as such. The turn boundary is the
97
97
  authorization ledger's `detected_at` stamp, because the envelope carries no turn
98
- id; with no ledger yet, the counter falls back to session scope. And the slot
99
- itself is not universal: `pre_tool_use` exists on **three** hosts — augment,
100
- claude, cowork. On cursor, cline, windsurf, gemini and copilot this guard has
101
- nowhere to bind, so items 1 and 4 join 2 and 3 as model-carried there. The
102
- frequency join in `check_enforcement_coverage.ts` reports exactly that set, and
98
+ id; with no ledger yet, the counter falls back to session scope. And the guard
99
+ does not deny everywhere it exists: `pre_tool_use` is **bound** on three hosts —
100
+ augment, claude, cowork — and only `claude` honours the deny. Everywhere else,
101
+ augment and cowork included, items 1 and 4 join 2 and 3 as model-carried.
103
102
  `agent-config hooks:status` answers it for the host you are on right now.
104
103
 
104
+ **Corrected 2026-08-17 — this paragraph was wrong on both sides of the line it
105
+ drew.** It said `pre_tool_use` "exists on three hosts" and that the guard has
106
+ "nowhere to bind" on the other five. Neither half survived a re-read of the
107
+ tree. **Downward:** the manifest's own `native_event_aliases` table already maps
108
+ `preToolUse` (cursor), `PreToolUse` (cline) and `BeforeTool` (gemini) onto
109
+ `pre_tool_use`, so there the guard is **unbound, not unbindable**; only windsurf
110
+ and copilot carry no pre-tool surface at all. **Upward, and worse:**
111
+ `host_semantics.ts` certifies **claude alone**, and the augment and cowork
112
+ trampolines discard dispatcher output and `exit 0` unconditionally — so on two
113
+ of the three "enforcing" hosts this guard runs and is then ignored. Nor may the
114
+ inverse be asserted downward: nothing records whether an unbound host's
115
+ pre-tool event can *deny*, and `severity: blocking` is a property of the concern
116
+ rather than of the host. The four states are tabulated once in
117
+ [`hook-architecture-v1 § Which hosts carry pre_tool_use`](../docs/contracts/hook-architecture-v1.md).
118
+
119
+ Stated at this length because this rule exists over a case where a process that
120
+ *looked* followed produced fabricated evidence. An unbacked reason for a real
121
+ gap and an unmeasured claim of enforcement are the same failure in a smaller
122
+ font, and this paragraph had shipped one of each. Note also what the frequency
123
+ join actually reports: `check_enforcement_coverage.ts` skips `fallback_only`
124
+ platforms, so its gap set is **four** — cursor, cline, windsurf, gemini — never
125
+ copilot, which is excluded by declaration rather than measured as a gap.
126
+
105
127
  Items 2 and 3 — an honestly chosen scope, and recording the prompt with the
106
128
  verdict — are **not** enforced by anything. A narrowed scope is not decidable
107
129
  from the prompt alone, and no validator reads the artefacts a verdict lands in.
@@ -84,4 +84,8 @@ Trigger-set above activates this routing on demand, independent of the disciplin
84
84
  - [`commit-policy`](commit-policy.md) — commits are the user's call; rewriting them is a stronger version of the same restriction.
85
85
  - [`token-efficiency`](token-efficiency.md) — Iron Law on burning the user's tokens for cosmetic gain.
86
86
  - [`skill:git-workflow`](../skills/git-workflow/SKILL.md) — Safe Squash-After-Push protocol and Divergent-State Recovery decision tree.
87
- - `git … --no-verify` / `core.hooksPath` overrides — deterministically blocked by the `block-no-verify` PreToolUse guard (`src/scripts/hooks/block_no_verify.ts`, registered in `hook_manifest.yaml`) **on the three hosts that have a `pre_tool_use` slot at all** — augment, claude, cowork. On cursor, cline, windsurf, gemini and copilot the guard has nowhere to bind, so on those hosts the Iron Laws above are model-carried and "deterministically blocked" is not a claim this rule can make. Surfaced by the frequency join in `check_enforcement_coverage.ts`, which reports the per-commit obligation as uncovered on exactly those platforms. To check the host you are on rather than trusting this sentence, run `agent-config hooks:status`.
87
+ - `git … --no-verify` / `core.hooksPath` overrides — deterministically blocked by the `block-no-verify` PreToolUse guard (`src/scripts/hooks/block_no_verify.ts`, registered in `hook_manifest.yaml`) **on `claude`, the one host that both binds `pre_tool_use` and honours a deny**. It is *bound* on augment and cowork too, and ignored there. Everywhere else the Iron Laws above are model-carried and "deterministically blocked" is not a claim this rule can make. To check the host you are on rather than trusting this sentence, run `agent-config hooks:status`.
88
+
89
+ **Corrected 2026-08-17, in both directions, because this line was wrong on each side.** It read "the guard has nowhere to bind" on cursor, cline, windsurf, gemini and copilot — refuted for three of the five by the manifest's own `native_event_aliases` table, which already maps `preToolUse`, `PreToolUse` and `BeforeTool` onto `pre_tool_use`: there the guard is **unbound, not unbindable**, and only windsurf and copilot carry no pre-tool surface. And it certified augment, claude and cowork as the enforcing set — `host_semantics.ts` verifies **claude alone**, while the augment and cowork trampolines discard dispatcher output and `exit 0` unconditionally, so a guard bound there runs and is then ignored. Both halves are the same failure: a host-capability claim this tree never established. The four states are tabulated once in [`hook-architecture-v1 § Which hosts carry pre_tool_use`](../docs/contracts/hook-architecture-v1.md).
90
+
91
+ The frequency join in `check_enforcement_coverage.ts` reports this obligation uncovered on **four** platforms — cursor, cline, windsurf, gemini. It skips `fallback_only` platforms before computing, so copilot never appears in its output and must not be quoted as if it did; copilot is uncovered for the separate reason that it has no hook surface at all.
@@ -4,6 +4,7 @@ tier: "1"
4
4
  description: "Writing/upstreaming low-impact-decisions corpus entries — non-bypassable privacy floor"
5
5
  triggers:
6
6
  - path_prefix: "agents/decisions/low-impact-decisions"
7
+ - path_prefix: "data/low-impact-decisions-seed"
7
8
  - keyword: "low-impact-decisions"
8
9
  - keyword: "low-impact corpus"
9
10
  - keyword: "learn-low-impact"
@@ -0,0 +1,89 @@
1
+ ---
2
+ type: "auto"
3
+ tier: "2a"
4
+ description: "A skill exists in the tree but not in the host's catalogue — ask for it by TASK via suggest_skill_for_task, never conclude it does not exist"
5
+ triggers:
6
+ - phrase: "skill not found"
7
+ - phrase: "no such skill"
8
+ - phrase: "skill does not exist"
9
+ - phrase: "kein passendes skill"
10
+ - phrase: "which skill"
11
+ - phrase: "welches skill"
12
+ applies_to_user_types:
13
+ - "developer"
14
+ - "maintainer"
15
+ self_contained: true
16
+ workspaces: [engineering]
17
+ packs: [meta]
18
+ # obligation: line 41
19
+ obligation_frequency: "per-turn"
20
+ ---
21
+
22
+ # Missing-Skill Recovery
23
+
24
+ The catalogue a host shows you is not the catalogue that exists. Hosts truncate,
25
+ and at least one truncates hard: measured 2026-08-16 on a default install, the
26
+ host published its own budget event stating it had stripped every description
27
+ and dropped **402 entries** from the model-visible skills list
28
+ (`agents/evidence/analysis/scoped-projection-host-delivery.md`). Scoping the
29
+ projection moved that to 330 and stripped the descriptions anyway.
30
+
31
+ 402 is the **host's own count against the host's own denominator**, which is not
32
+ this package's: the same install projects 297 skills, so the figure is a
33
+ magnitude, never a subtraction anyone can perform. What it establishes is that
34
+ most of the catalogue is not model-visible — which is all this rule needs.
35
+
36
+ So "I do not see a skill for this" is evidence about the **delivery**, never
37
+ about the tree.
38
+
39
+ ## The Iron Law
40
+
41
+ ```
42
+ A SKILL MISSING FROM THE HOST CATALOGUE IS NOT A SKILL THAT DOES NOT EXIST.
43
+ NEVER CONCLUDE "NO SKILL COVERS THIS" FROM THE LIST YOU WERE SHOWN.
44
+ ASK BY TASK — `suggest_skill_for_task` RANKS THE TREE, NOT THE CATALOGUE.
45
+ NEVER REBUILD A CAPABILITY BECAUSE ITS SKILL WAS NOT DELIVERED.
46
+ ```
47
+
48
+ ## What to do
49
+
50
+ 1. **Ask by task, not by name.** A name you cannot see is a name you cannot
51
+ type; a task description always works. The `suggest_skill_for_task` MCP tool
52
+ ranks `SKILL.md` frontmatter over the source tree and returns names, scores
53
+ and personas — no bodies, so a wrong rank costs one read.
54
+ 2. **Open the winner and check it yourself.** The ranker is deterministic
55
+ keyword scoring, not judgement. A high score is a pointer, not a verdict.
56
+ 3. **Say which source answered.** "The catalogue had no entry, the ranker
57
+ found `X`" is a different claim from "there is no skill for this", and a
58
+ reader cannot reconstruct which one you meant from silence.
59
+ 4. **If nothing scores, say that** — and proceed without a skill rather than
60
+ inventing one. A ranked empty list and an unreachable catalogue are
61
+ different answers; the tool distinguishes them (`status: no_catalogue`) and
62
+ so should you.
63
+
64
+ ## When NOT to fire
65
+
66
+ - The skill you need was delivered and is in front of you. This rule is for the
67
+ gap, not for routine skill selection.
68
+ - The task genuinely has no skill and you already checked by task. Checking
69
+ twice is the loop, not the discipline.
70
+ - A host that delivers its catalogue whole — the rule costs nothing there and
71
+ simply never has a gap to close.
72
+
73
+ ## Honest enforcement — `enforced_by: none`
74
+
75
+ Nothing can observe an agent concluding "no skill exists". The `skill-route`
76
+ concern injects ranked pointers on `user_prompt_submit` when its floor is
77
+ cleared, which covers the prompts where the ranker is confident and by
78
+ construction covers no others: it is silent on ~86 % of prompts by design. This
79
+ rule is the model-carried half for the rest, and it is stated as such rather
80
+ than implied to be caught by the concern.
81
+
82
+ ## See also
83
+
84
+ - [`code-intelligence`](../skills/code-intelligence/SKILL.md) — the same
85
+ shape for source code: query the index first, fall back, name the source.
86
+ - [`external-code-graph-interop`](external-code-graph-interop.md) — its rule-side
87
+ twin, and the model this rule's trigger set follows.
88
+ - `src/scripts/skill_tools/score_skill_relevance.ts` — the ranker itself, and
89
+ the one place its scoring is defined.
@@ -121,10 +121,15 @@ every `file_pattern` declared here is accepted by that predicate. The predicate
121
121
  is deliberately wider (it also covers `.css`, `.scss`, `.astro`), because a
122
122
  measurement denominator and a routing trigger are not the same population.
123
123
 
124
- Warn-only, capped at two per session, default-OFF, bound only where a
125
- `pre_tool_use` slot exists. A reminder that can be ignored is not a gate, so
126
- `enforced_by:` stays `none`. Run `agent-config hooks:status` for the host you
127
- are on rather than trusting this sentence.
124
+ Warn-only, capped at two per session, default-OFF, and bound only in the three
125
+ `platforms:` rows that carry a `pre_tool_use` key — augment, claude, cowork
126
+ (**clarified 2026-08-17**: "where a slot exists" read as a host property, and it
127
+ is a manifest one; three further hosts alias a native pre-tool event without a
128
+ binding, and only claude honours a deny at all — the four states are tabulated
129
+ in [`hook-architecture-v1 § Which hosts carry pre_tool_use`](../docs/contracts/hook-architecture-v1.md)).
130
+ None of that changes the verdict here: a reminder that can be ignored is not a
131
+ gate, so `enforced_by:` stays `none`. Run `agent-config hooks:status` for the
132
+ host you are on rather than trusting this sentence.
128
133
 
129
134
  ## Failure modes
130
135
 
@@ -403,6 +403,38 @@ function _regen_dashboard(root: string, dry_run: boolean): void {
403
403
  }
404
404
  }
405
405
 
406
+ /**
407
+ * Rebuild the archive index after a move, when the builder is present.
408
+ *
409
+ * The sweep is the one operation that changes what `agents/roadmaps/archive/`
410
+ * contains, so it is the one operation that can leave the generated index
411
+ * stale — and the drift gate on that index runs in the same PR the sweep runs
412
+ * in. Regenerating here keeps the two from disagreeing by construction rather
413
+ * than by remembering.
414
+ *
415
+ * `build_archive_index` is package-internal (`src/scripts/`), while this sweep
416
+ * is projected into consumer installs that carry no such script. Absent
417
+ * builder → nothing to regenerate, and that is a normal consumer state, not a
418
+ * failure. The spawn is best-effort for the same reason `_regen_dashboard` is:
419
+ * a sweep that archived correctly must not report failure because a
420
+ * regeneration could not run.
421
+ */
422
+ function _regen_archive_index(root: string, dry_run: boolean): void {
423
+ if (dry_run) {
424
+ return;
425
+ }
426
+ const script = path.join(root, 'src', 'scripts', 'build_archive_index.ts');
427
+ if (!_isFile(script)) {
428
+ return;
429
+ }
430
+ _runTwin(root, script);
431
+ for (const rel of ['agents/roadmaps/archive/INDEX.md', 'agents/roadmaps/archive/index.json']) {
432
+ if (_isFile(path.join(root, rel))) {
433
+ _run(['git', 'add', '--', rel], root);
434
+ }
435
+ }
436
+ }
437
+
406
438
  /** Spawn the dashboard generator twin (tsx) with cwd=root, mirroring `[sys.executable, script]`. */
407
439
  function _runTwin(root: string, script: string): void {
408
440
  // Python uses `sys.executable` — the interpreter already running this
@@ -491,6 +523,7 @@ function main(argv?: readonly string[]): number {
491
523
  return 0;
492
524
  }
493
525
  _regen_dashboard(root, ns.dry_run);
526
+ _regen_archive_index(root, ns.dry_run);
494
527
  const verb = ns.dry_run ? 'Would archive' : 'Archived';
495
528
  for (const rec of archived) {
496
529
  process.stdout.write(