@event4u/agent-config 9.14.0 → 9.15.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 (276) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +110 -112
  3. package/README.md +8 -0
  4. package/dist/agent-src/commands/agents/audit.md +7 -7
  5. package/dist/agent-src/commands/agents/optimize.md +1 -1
  6. package/dist/agent-src/commands/analyze/reference-repo.md +1 -1
  7. package/dist/agent-src/commands/chat-history.md +1 -0
  8. package/dist/agent-src/commands/context.md +1 -0
  9. package/dist/agent-src/commands/council.md +1 -1
  10. package/dist/agent-src/commands/ghostwriter.md +1 -1
  11. package/dist/agent-src/commands/memory.md +2 -1
  12. package/dist/agent-src/commands/module.md +1 -0
  13. package/dist/agent-src/commands/optimize/agents-dir.md +1 -1
  14. package/dist/agent-src/commands/optimize/deep.md +5 -2
  15. package/dist/agent-src/commands/optimize/skills.md +5 -5
  16. package/dist/agent-src/commands/override.md +1 -0
  17. package/dist/agent-src/commands/prediction-pool.md +4 -4
  18. package/dist/agent-src/commands/research/deep.md +1 -1
  19. package/dist/agent-src/commands/roadmap.md +2 -1
  20. package/dist/agent-src/commands/rule-compliance-audit.md +1 -1
  21. package/dist/agent-src/commands/tdd.md +1 -0
  22. package/dist/agent-src/commands/tests.md +1 -0
  23. package/dist/agent-src/commands/upstream-contribute.md +5 -5
  24. package/dist/agent-src/commands/video/from-script.md +1 -1
  25. package/dist/agent-src/commands/video/from-song.md +1 -1
  26. package/dist/agent-src/commands/video/scene.md +1 -1
  27. package/dist/agent-src/commands/video/stitch.md +1 -1
  28. package/dist/agent-src/commands/video/storyboard.md +1 -1
  29. package/dist/agent-src/commands/worktree/create.md +2 -1
  30. package/dist/agent-src/contexts/augment-infrastructure.md +1 -1
  31. package/dist/agent-src/contexts/contracts/artifact-engagement-flow.md +5 -5
  32. package/dist/agent-src/contexts/contracts/command-suggestion-flow.md +3 -3
  33. package/dist/agent-src/contexts/contracts/emergency-triage-block.md +2 -2
  34. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +4 -3
  35. package/dist/agent-src/contexts/execution/subagent-routing.md +19 -0
  36. package/dist/agent-src/contexts/override-system.md +1 -1
  37. package/dist/agent-src/ghostwriter/fictional-fixture-v1.md +1 -1
  38. package/dist/agent-src/rules/augment-edit-discipline.md +1 -1
  39. package/dist/agent-src/rules/autonomous-execution.md +2 -1
  40. package/dist/agent-src/rules/brand-consistency.md +1 -0
  41. package/dist/agent-src/rules/brand-source-of-truth.md +1 -0
  42. package/dist/agent-src/rules/code-comment-discipline.md +2 -0
  43. package/dist/agent-src/rules/communication-through-line.md +1 -0
  44. package/dist/agent-src/rules/content-quoting-floor.md +1 -0
  45. package/dist/agent-src/rules/cross-source-consistency.md +0 -1
  46. package/dist/agent-src/rules/downstream-changes.md +1 -0
  47. package/dist/agent-src/rules/external-code-graph-interop.md +1 -0
  48. package/dist/agent-src/rules/external-reference-deep-dive.md +1 -0
  49. package/dist/agent-src/rules/fast-path-marker-visibility.md +1 -0
  50. package/dist/agent-src/rules/finance-safety-floor.md +0 -1
  51. package/dist/agent-src/rules/icon-consistency.md +1 -0
  52. package/dist/agent-src/rules/image-likeness-and-rights.md +1 -0
  53. package/dist/agent-src/rules/improve-before-implement.md +1 -1
  54. package/dist/agent-src/rules/invite-challenge.md +1 -0
  55. package/dist/agent-src/rules/lethal-trifecta-guard.md +1 -0
  56. package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
  57. package/dist/agent-src/rules/markdown-safe-codeblocks.md +1 -0
  58. package/dist/agent-src/rules/media-governance-routing.md +4 -3
  59. package/dist/agent-src/rules/media-sync-ground-truth.md +1 -0
  60. package/dist/agent-src/rules/no-attribution-footers.md +1 -0
  61. package/dist/agent-src/rules/no-decorative-emojis-in-git-surfaces.md +1 -0
  62. package/dist/agent-src/rules/no-pr-progress-comments.md +1 -0
  63. package/dist/agent-src/rules/onboarding-gate.md +1 -0
  64. package/dist/agent-src/rules/output-discipline.md +1 -0
  65. package/dist/agent-src/rules/prefer-enums-over-literals.md +1 -0
  66. package/dist/agent-src/rules/preservation-guard.md +1 -0
  67. package/dist/agent-src/rules/question-not-instruction.md +1 -0
  68. package/dist/agent-src/rules/roadmap-ci-steps-policy.md +1 -0
  69. package/dist/agent-src/rules/role-mode-adherence.md +1 -0
  70. package/dist/agent-src/rules/runtime-safety.md +1 -0
  71. package/dist/agent-src/rules/security-sensitive-stop.md +1 -0
  72. package/dist/agent-src/rules/session-canary.md +9 -2
  73. package/dist/agent-src/rules/source-confidentiality.md +1 -0
  74. package/dist/agent-src/rules/source-of-truth.md +1 -0
  75. package/dist/agent-src/rules/spreadsheet-source-quality.md +1 -0
  76. package/dist/agent-src/rules/think-before-action.md +1 -0
  77. package/dist/agent-src/rules/token-budget-discipline.md +1 -0
  78. package/dist/agent-src/rules/token-efficiency.md +1 -0
  79. package/dist/agent-src/rules/token-optimizer-maintenance.md +5 -5
  80. package/dist/agent-src/rules/tool-safety.md +1 -0
  81. package/dist/agent-src/rules/untrusted-input-defense.md +2 -0
  82. package/dist/agent-src/rules/user-interaction.md +1 -0
  83. package/dist/agent-src/rules/user-interrupt-priority.md +1 -0
  84. package/dist/agent-src/skills/adr-create/SKILL.md +1 -1
  85. package/dist/agent-src/skills/check-refs/SKILL.md +1 -1
  86. package/dist/agent-src/skills/competitive-positioning/SKILL.md +1 -1
  87. package/dist/agent-src/skills/condense-memory/SKILL.md +1 -1
  88. package/dist/agent-src/skills/context-authoring/SKILL.md +1 -1
  89. package/dist/agent-src/skills/copilot-agents-optimization/SKILL.md +1 -1
  90. package/dist/agent-src/skills/description-assist/SKILL.md +1 -1
  91. package/dist/agent-src/skills/github-ci/SKILL.md +1 -0
  92. package/dist/agent-src/skills/guideline-writing/SKILL.md +1 -1
  93. package/dist/agent-src/skills/lint-skills/SKILL.md +1 -1
  94. package/dist/agent-src/skills/readme-reviewer/SKILL.md +1 -1
  95. package/dist/agent-src/skills/roadmap-management/SKILL.md +8 -1
  96. package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
  97. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
  98. package/dist/agent-src/skills/skill-writing/SKILL.md +4 -4
  99. package/dist/agent-src/skills/token-optimizer/SKILL.md +4 -4
  100. package/dist/agent-src/skills/upstream-contribute/SKILL.md +6 -6
  101. package/dist/agent-src/skills/using-git-worktrees/SKILL.md +25 -0
  102. package/dist/agent-src/templates/AGENTS.md +1 -1
  103. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  104. package/dist/agent-src/templates/command.md +1 -1
  105. package/dist/agent-src/templates/copilot-instructions.md +3 -3
  106. package/dist/agent-src/templates/rule.md +2 -2
  107. package/dist/agent-src/templates/scripts/work_engine/orchestration.ts +1 -1
  108. package/dist/agent-src/templates/skill-archive-note.md +5 -5
  109. package/dist/agent-src/templates/skill.md +1 -1
  110. package/dist/cli/agent-config.js +66 -19
  111. package/dist/cli/agent-config.js.map +1 -1
  112. package/dist/cli/main.js +9 -4
  113. package/dist/cli/main.js.map +1 -1
  114. package/dist/cli/python/workspace_drive.js +2 -0
  115. package/dist/cli/python/workspace_drive.js.map +1 -1
  116. package/dist/cli/python/workspace_drive_health.js +2 -0
  117. package/dist/cli/python/workspace_drive_health.js.map +1 -1
  118. package/dist/cli/python/workspace_hosts.js +2 -0
  119. package/dist/cli/python/workspace_hosts.js.map +1 -1
  120. package/dist/cli/python/workspace_sessions.js +2 -0
  121. package/dist/cli/python/workspace_sessions.js.map +1 -1
  122. package/dist/cli/registry.js +1 -0
  123. package/dist/cli/registry.js.map +1 -1
  124. package/dist/cli-delegate/{chunk-A3HJ6NTI.js → chunk-63WSP6YZ.js} +2 -1
  125. package/dist/cli-delegate/{chunk-EJYIGZ3T.js → chunk-BKO2AZ7Z.js} +1 -1
  126. package/dist/cli-delegate/{chunk-GCVFLBP2.js → chunk-H574KOUH.js} +338 -494
  127. package/dist/cli-delegate/chunk-NXFWFAYV.js +339 -0
  128. package/dist/cli-delegate/{chunk-TEGJU2JX.js → chunk-ZXXJLL5K.js} +2 -2
  129. package/dist/cli-delegate/cmd_conformance.js +29 -23
  130. package/dist/cli-delegate/cmd_doctor.js +3 -3
  131. package/dist/cli-delegate/cmd_export.js +4 -4
  132. package/dist/cli-delegate/cmd_preflight.js +2 -2
  133. package/dist/cli-delegate/cmd_prune.js +4 -4
  134. package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
  135. package/dist/cli-delegate/cmd_sync.js +4 -4
  136. package/dist/cli-delegate/cmd_uninstall.js +4 -4
  137. package/dist/cli-delegate/cmd_validate.js +4 -4
  138. package/dist/discovery/deprecation-report.md +1 -1
  139. package/dist/discovery/discovery-manifest.json +127 -95
  140. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  141. package/dist/discovery/discovery-manifest.summary.md +1 -1
  142. package/dist/discovery/orphan-report.md +1 -1
  143. package/dist/discovery/packs.json +2 -2
  144. package/dist/discovery/trust-report.md +1 -1
  145. package/dist/discovery/workspaces.json +2 -2
  146. package/dist/hooks/dispatch.js +729 -430
  147. package/dist/install/install.mjs +52 -11
  148. package/dist/install/rule_scope.js +18 -2
  149. package/dist/install/rule_scope.js.map +1 -1
  150. package/dist/mcp/registry-manifest.json +2 -2
  151. package/dist/mcp/server.json +2 -2
  152. package/dist/mcp/server.mjs +518 -411
  153. package/dist/router.json +1 -1
  154. package/dist/scripts/_lib/claude_settings_hooks.js +30 -5
  155. package/dist/scripts/_lib/claude_settings_hooks.js.map +1 -1
  156. package/dist/scripts/_lib/scoped_projection.js +223 -0
  157. package/dist/scripts/_lib/scoped_projection.js.map +1 -0
  158. package/dist/scripts/_lib/surface_tiers.js +125 -0
  159. package/dist/scripts/_lib/surface_tiers.js.map +1 -0
  160. package/dist/scripts/_lib/token_count.js +166 -0
  161. package/dist/scripts/_lib/token_count.js.map +1 -0
  162. package/dist/scripts/_lib/trigger_eval_floors.js +8 -0
  163. package/dist/scripts/_lib/trigger_eval_floors.js.map +1 -1
  164. package/dist/scripts/condense.js +139 -9
  165. package/dist/scripts/condense.js.map +1 -1
  166. package/dist/scripts/project_thin_rules.js +450 -0
  167. package/dist/scripts/project_thin_rules.js.map +1 -0
  168. package/dist/server/routes/workspace.js +2 -0
  169. package/dist/server/routes/workspace.js.map +1 -1
  170. package/dist/server/schemas/settings.js +9 -3
  171. package/dist/server/schemas/settings.js.map +1 -1
  172. package/dist/ui/assets/index-DJY_vQVn.js +40 -0
  173. package/dist/ui/assets/index-DJY_vQVn.js.map +1 -0
  174. package/dist/ui/index.html +1 -1
  175. package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +1 -1
  176. package/package.json +11 -11
  177. package/src/agent-src/templates/AGENTS.md +1 -1
  178. package/src/agent-src/templates/scripts/work_engine/orchestration.ts +1 -1
  179. package/src/config/agent-settings.template.yml +59 -11
  180. package/src/config/ci-local-parity.yml +8 -0
  181. package/src/config/discovery/unassigned-artefacts.yml +23 -25
  182. package/src/config/evaluator-budgets.json +35 -5
  183. package/src/config/gate-coverage.yml +147 -0
  184. package/src/config/gate-violation-baselines.json +25 -0
  185. package/src/install/rule_scope.ts +27 -2
  186. package/src/scripts/_cli/cmd_conformance.ts +13 -24
  187. package/src/scripts/_dispatch.bash +10 -0
  188. package/src/scripts/_lib/claude_settings_hooks.ts +30 -4
  189. package/src/scripts/_lib/gate_baseline.ts +213 -0
  190. package/src/scripts/_lib/scoped_projection.ts +50 -0
  191. package/src/scripts/_lib/tier_budget_routing.ts +234 -0
  192. package/src/scripts/_lib/tool_adapter_registry.ts +8 -1
  193. package/src/scripts/_lib/trigger_eval_floors.ts +9 -0
  194. package/src/scripts/_lib/trigger_routers.ts +17 -5
  195. package/src/scripts/adr/regenerate_index.ts +4 -3
  196. package/src/scripts/ai-video/lib/parse-blueprint.sh +1 -1
  197. package/src/scripts/ai_council/compile_corpus.ts +1 -1
  198. package/src/scripts/audit_cloud_compatibility.ts +37 -2
  199. package/src/scripts/audit_initial_context.ts +53 -19
  200. package/src/scripts/audit_likelihood.ts +31 -2
  201. package/src/scripts/audit_user_type_axis.ts +58 -11
  202. package/src/scripts/bench_hook_latency.ts +172 -41
  203. package/src/scripts/build_mcp_catalog.ts +12 -1
  204. package/src/scripts/check_always_budget.ts +122 -11
  205. package/src/scripts/check_augment_description_cap.ts +24 -3
  206. package/src/scripts/check_ci_strict_superset.ts +150 -0
  207. package/src/scripts/check_cluster_patterns.ts +100 -0
  208. package/src/scripts/check_consumer_scope_flip.ts +26 -4
  209. package/src/scripts/check_evaluator_budgets.ts +198 -19
  210. package/src/scripts/check_gate_coverage.ts +337 -1
  211. package/src/scripts/check_generator_output_coverage.ts +2 -1
  212. package/src/scripts/check_iron_law_prominence.ts +1 -1
  213. package/src/scripts/check_kernel_rule_bundle.ts +1 -1
  214. package/src/scripts/check_no_new_legacy_path.ts +167 -3
  215. package/src/scripts/check_pack_size.ts +18 -4
  216. package/src/scripts/check_portability.ts +37 -7
  217. package/src/scripts/check_references.ts +19 -3
  218. package/src/scripts/check_release_pr_shape.ts +13 -0
  219. package/src/scripts/check_token_regression.ts +9 -2
  220. package/src/scripts/condense.ts +155 -12
  221. package/src/scripts/cost/budget.mjs +63 -2
  222. package/src/scripts/evaluator_umbrella.sh +34 -1
  223. package/src/scripts/generate_capability_matrix.ts +8 -0
  224. package/src/scripts/generate_router_coverage_from_matrix.ts +155 -0
  225. package/src/scripts/hook_manifest.yaml +17 -3
  226. package/src/scripts/hooks/block_config_weakening.ts +337 -0
  227. package/src/scripts/hooks/concern_registry.ts +2 -0
  228. package/src/scripts/hooks/probe_throttle.ts +75 -0
  229. package/src/scripts/install-hooks.sh +24 -2
  230. package/src/scripts/install.ts +2 -2
  231. package/src/scripts/lint_agents_md.ts +9 -3
  232. package/src/scripts/lint_archived_skills.ts +1 -1
  233. package/src/scripts/lint_artefact_frontmatter.ts +185 -37
  234. package/src/scripts/lint_command_routing.ts +21 -52
  235. package/src/scripts/lint_command_verbs.ts +47 -28
  236. package/src/scripts/lint_context_spine_usage.ts +38 -12
  237. package/src/scripts/lint_framework_leakage.ts +25 -6
  238. package/src/scripts/lint_handoffs.ts +49 -2
  239. package/src/scripts/lint_media_policy_linkage.ts +62 -30
  240. package/src/scripts/lint_namespace.ts +92 -7
  241. package/src/scripts/lint_new_skill_gate.ts +39 -31
  242. package/src/scripts/lint_originality_shingles.ts +7 -1
  243. package/src/scripts/lint_override_kernel_guard.ts +21 -3
  244. package/src/scripts/lint_pack_boundaries.ts +461 -91
  245. package/src/scripts/lint_pack_dependencies.ts +24 -57
  246. package/src/scripts/lint_persona_governance.ts +1 -1
  247. package/src/scripts/lint_role_experiences.ts +34 -5
  248. package/src/scripts/lint_trigger_precision.ts +142 -0
  249. package/src/scripts/mcp_server/consumer_tool_catalog.json +12 -201
  250. package/src/scripts/mcp_server/tools.ts +1 -1
  251. package/src/scripts/prepack-check.mjs +40 -1
  252. package/src/scripts/prepack_router_targets.d.mts +7 -0
  253. package/src/scripts/prepack_router_targets.mjs +93 -0
  254. package/src/scripts/print_required_checks.ts +72 -18
  255. package/src/scripts/profile_staleness_hook.ts +12 -0
  256. package/src/scripts/record_evaluator_measurements.mjs +105 -0
  257. package/src/scripts/recruit_preflight.sh +1 -1
  258. package/src/scripts/release.ts +105 -1
  259. package/src/scripts/router_target_paths.d.mts +4 -0
  260. package/src/scripts/router_target_paths.mjs +69 -0
  261. package/src/scripts/router_telemetry.ts +52 -1
  262. package/src/scripts/routing_doctor.ts +485 -0
  263. package/src/scripts/rule_trigger_eval.ts +403 -0
  264. package/src/scripts/schemas/rule.schema.json +5 -1
  265. package/src/scripts/session_canary_hook.ts +96 -5
  266. package/src/scripts/skill_linter.ts +9 -28
  267. package/src/scripts/smoke/skills.sh +1 -1
  268. package/src/scripts/sweep_dead_scan_roots.ts +356 -10
  269. package/src/scripts/trigger_coverage.ts +13 -2
  270. package/src/scripts/update_counts.ts +1 -1
  271. package/src/scripts/validate_telegraph_carveouts.ts +1 -1
  272. package/src/scripts/wrapper_freshness_hook.ts +14 -0
  273. package/src/server/schemas/settings.ts +19 -5
  274. package/dist/cli-delegate/chunk-TJIAM5FW.js +0 -133
  275. package/dist/ui/assets/index-BpSQt5vm.js +0 -40
  276. package/dist/ui/assets/index-BpSQt5vm.js.map +0 -1
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Budget-aware tier routing (road-to-tested-routing Phase 7).
3
+ *
4
+ * Deterministic decision layer for `docs/contracts/budget-routing.md`:
5
+ * pick the cheapest CLASSIFIER-ADEQUATE tier that has available budget;
6
+ * cheap exhausted but a stronger tier funded → route up, never block;
7
+ * everything exhausted or unreadable → session model + notice (fail-open).
8
+ *
9
+ * Two hard mechanism requirements from the contract:
10
+ * - `acquireBudgetPermit` is an ATOMIC reserve — O_EXCL lock file, then
11
+ * sum + append-pending in one critical section. Check-then-spend races
12
+ * two concurrent dispatches past the ceiling (council finding
13
+ * 2026-08-03).
14
+ * - `tripCooldown` / cool-down state — a 429/quota error pauses a tier
15
+ * (default 60 min) instead of retry-looping; `pickTier` skips cooling
16
+ * tiers via the same relation.
17
+ *
18
+ * Pure decision logic is I/O-free; only the permit/cool-down helpers
19
+ * touch the filesystem (ledger + state under agents/cost-tracking/).
20
+ */
21
+ import * as fs from 'node:fs';
22
+ import * as path from 'node:path';
23
+
24
+ export type BudgetTier = 'cheap' | 'medium' | 'strong';
25
+
26
+ /** Ordered cheapest-first — the relation walks this order upward. */
27
+ export const TIER_ORDER: readonly BudgetTier[] = ['cheap', 'medium', 'strong'];
28
+
29
+ export interface TierBudgetState {
30
+ /** USD ceiling from cost.budgets.per_tier.<tier>; null = no tier cap. */
31
+ ceiling_usd: number | null;
32
+ /** Ledger-summed spend for this tier in the ceiling's window (USD). */
33
+ spent_usd: number;
34
+ /** Epoch ms until which this tier is cooling after a quota error; 0 = live. */
35
+ cooldown_until_ms: number;
36
+ }
37
+
38
+ export type BudgetRoutingSwitch = 'ask' | 'auto' | 'off';
39
+
40
+ export interface PickTierInputs {
41
+ /** Cheapest tier the classifier marked adequate (quality floor). */
42
+ adequate_tier: BudgetTier;
43
+ /** Per-tier budget state, complete over TIER_ORDER. */
44
+ budgets: Record<BudgetTier, TierBudgetState>;
45
+ /** subagents.budget_routing setting. */
46
+ routing_switch: BudgetRoutingSwitch;
47
+ /** Estimated cost of the dispatch (USD) for the availability check. */
48
+ estimated_cost_usd: number;
49
+ /** Clock injection — never Date.now() in the decision path. */
50
+ now_ms: number;
51
+ }
52
+
53
+ export interface PickTierDecision {
54
+ /** 'tier' → dispatch on `tier`; 'session' → run on the session model. */
55
+ route: 'tier' | 'session';
56
+ tier: BudgetTier | null;
57
+ /** 'proceed' | 'ask' (switch=ask and a budget-motivated downshift) */
58
+ action: 'proceed' | 'ask';
59
+ /** Non-empty when the relation degraded (exhausted / cooling / off). */
60
+ notice: string;
61
+ reason: string;
62
+ }
63
+
64
+ function tierAvailable(
65
+ s: TierBudgetState,
66
+ est: number,
67
+ now: number,
68
+ ): { ok: boolean; why: string } {
69
+ if (s.cooldown_until_ms > now) {
70
+ return { ok: false, why: 'cooling after quota error' };
71
+ }
72
+ if (s.ceiling_usd === null) {
73
+ return { ok: true, why: 'no tier cap' };
74
+ }
75
+ if (s.spent_usd + est > s.ceiling_usd) {
76
+ return { ok: false, why: `ceiling ${s.ceiling_usd} reached (spent ${s.spent_usd})` };
77
+ }
78
+ return { ok: true, why: 'within ceiling' };
79
+ }
80
+
81
+ /** The binding relation from docs/contracts/budget-routing.md. */
82
+ export function pickTier(inp: PickTierInputs): PickTierDecision {
83
+ if (inp.routing_switch === 'off') {
84
+ return {
85
+ route: 'session',
86
+ tier: null,
87
+ action: 'proceed',
88
+ notice: '',
89
+ reason: 'budget_routing off — session model, today\'s behavior',
90
+ };
91
+ }
92
+ const startIdx = TIER_ORDER.indexOf(inp.adequate_tier);
93
+ const skipped: string[] = [];
94
+ for (let i = startIdx; i < TIER_ORDER.length; i += 1) {
95
+ const tier = TIER_ORDER[i] as BudgetTier;
96
+ const avail = tierAvailable(inp.budgets[tier], inp.estimated_cost_usd, inp.now_ms);
97
+ if (avail.ok) {
98
+ const degraded = i > startIdx;
99
+ return {
100
+ route: 'tier',
101
+ tier,
102
+ action: inp.routing_switch === 'ask' ? 'ask' : 'proceed',
103
+ notice: degraded
104
+ ? `cheapest adequate tier unavailable (${skipped.join('; ')}) — routed up to ${tier}`
105
+ : '',
106
+ reason: `${tier}: ${avail.why}${degraded ? '' : ' — cheapest adequate tier'}`,
107
+ };
108
+ }
109
+ skipped.push(`${tier}: ${avail.why}`);
110
+ }
111
+ return {
112
+ route: 'session',
113
+ tier: null,
114
+ action: 'proceed',
115
+ notice: `all tier budgets unavailable (${skipped.join('; ')}) — session model, work not blocked`,
116
+ reason: 'fail-open: budget machinery degrades savings, never work',
117
+ };
118
+ }
119
+
120
+ // ── Atomic permit + cool-down (filesystem side) ────────────────────────────
121
+
122
+ export const RESERVE_FILE = 'tier-reserves.jsonl';
123
+ export const COOLDOWN_FILE = 'tier-cooldowns.json';
124
+ export const DEFAULT_COOLDOWN_MS = 60 * 60 * 1000;
125
+
126
+ interface ReserveEntry {
127
+ ts_ms: number;
128
+ tier: BudgetTier;
129
+ est_usd: number;
130
+ status: 'pending';
131
+ }
132
+
133
+ /**
134
+ * Atomic budget reserve: O_EXCL lock → sum ledger+reserves → append a
135
+ * pending entry → release. Returns granted=false (never throws) when the
136
+ * ceiling would be crossed or the lock cannot be taken quickly — the
137
+ * caller falls back per the relation (fail-open at the DECISION layer,
138
+ * conservative at the SPEND layer).
139
+ */
140
+ export function acquireBudgetPermit(options: {
141
+ tracking_dir: string;
142
+ tier: BudgetTier;
143
+ estimated_cost_usd: number;
144
+ ceiling_usd: number | null;
145
+ spent_usd: number;
146
+ now_ms: number;
147
+ }): { granted: boolean; reason: string } {
148
+ const dir = options.tracking_dir;
149
+ fs.mkdirSync(dir, { recursive: true });
150
+ const lock = path.join(dir, `${RESERVE_FILE}.lock`);
151
+ let fd: number;
152
+ try {
153
+ fd = fs.openSync(lock, 'wx');
154
+ } catch {
155
+ return { granted: false, reason: 'reserve lock busy — treat tier as unavailable this instant' };
156
+ }
157
+ try {
158
+ const reservePath = path.join(dir, RESERVE_FILE);
159
+ let reserved = 0;
160
+ if (fs.existsSync(reservePath)) {
161
+ for (const line of fs.readFileSync(reservePath, 'utf-8').split('\n')) {
162
+ if (!line.trim()) continue;
163
+ try {
164
+ const e = JSON.parse(line) as ReserveEntry;
165
+ if (e.tier === options.tier && e.status === 'pending') {
166
+ reserved += e.est_usd;
167
+ }
168
+ } catch {
169
+ // tolerate a torn line — reserve file is advisory spend state
170
+ }
171
+ }
172
+ }
173
+ if (
174
+ options.ceiling_usd !== null &&
175
+ options.spent_usd + reserved + options.estimated_cost_usd > options.ceiling_usd
176
+ ) {
177
+ return {
178
+ granted: false,
179
+ reason: `ceiling ${options.ceiling_usd} would be crossed (spent ${options.spent_usd} + reserved ${reserved})`,
180
+ };
181
+ }
182
+ const entry: ReserveEntry = {
183
+ ts_ms: options.now_ms,
184
+ tier: options.tier,
185
+ est_usd: options.estimated_cost_usd,
186
+ status: 'pending',
187
+ };
188
+ fs.appendFileSync(reservePath, `${JSON.stringify(entry)}\n`, 'utf-8');
189
+ return { granted: true, reason: 'reserved' };
190
+ } finally {
191
+ fs.closeSync(fd);
192
+ fs.rmSync(lock, { force: true });
193
+ }
194
+ }
195
+
196
+ /** Record a quota/429 error: the tier cools down; pickTier skips it. */
197
+ export function tripCooldown(options: {
198
+ tracking_dir: string;
199
+ tier: BudgetTier;
200
+ now_ms: number;
201
+ cooldown_ms?: number;
202
+ }): number {
203
+ const until = options.now_ms + (options.cooldown_ms ?? DEFAULT_COOLDOWN_MS);
204
+ const p = path.join(options.tracking_dir, COOLDOWN_FILE);
205
+ let state: Record<string, number> = {};
206
+ try {
207
+ state = JSON.parse(fs.readFileSync(p, 'utf-8')) as Record<string, number>;
208
+ } catch {
209
+ state = {};
210
+ }
211
+ state[options.tier] = until;
212
+ fs.mkdirSync(options.tracking_dir, { recursive: true });
213
+ fs.writeFileSync(p, `${JSON.stringify(state, null, 2)}\n`, 'utf-8');
214
+ return until;
215
+ }
216
+
217
+ /** Read the cool-down map (missing/torn file → all tiers live). */
218
+ export function readCooldowns(tracking_dir: string): Record<BudgetTier, number> {
219
+ const empty: Record<BudgetTier, number> = { cheap: 0, medium: 0, strong: 0 };
220
+ try {
221
+ const raw = JSON.parse(
222
+ fs.readFileSync(path.join(tracking_dir, COOLDOWN_FILE), 'utf-8'),
223
+ ) as Record<string, unknown>;
224
+ for (const t of TIER_ORDER) {
225
+ const v = raw[t];
226
+ if (typeof v === 'number' && Number.isFinite(v)) {
227
+ empty[t] = v;
228
+ }
229
+ }
230
+ return empty;
231
+ } catch {
232
+ return empty;
233
+ }
234
+ }
@@ -31,7 +31,14 @@ export const ADAPTER_REGISTRY: readonly AdapterEntry[] = [
31
31
  {
32
32
  tool: 'Claude Code',
33
33
  kind: 'native',
34
- roots: ['.claude/rules', '.claude/skills', '.claude/agents', '.claude/personas', '.claude/user-types'],
34
+ roots: [
35
+ '.claude/rules',
36
+ '.claude/skills',
37
+ '.claude/commands',
38
+ '.claude/agents',
39
+ '.claude/personas',
40
+ '.claude/user-types',
41
+ ],
35
42
  generator_tag: 'claude-code',
36
43
  },
37
44
  { tool: 'Cursor', kind: 'native', roots: ['.cursor/rules', '.cursor/commands'], generator_tag: 'cursor' },
@@ -29,3 +29,12 @@ export const DOMAIN_FLOORS: Readonly<Record<string, TriggerEvalFloor>> = {
29
29
  export function floor_for(skill: string | null | undefined): TriggerEvalFloor {
30
30
  return (skill !== null && skill !== undefined && DOMAIN_FLOORS[skill]) || DEFAULT_FLOOR;
31
31
  }
32
+
33
+ /**
34
+ * Advisory floor for the rules-mode canary leg (`rule_trigger_eval.ts`,
35
+ * road-to-tested-routing Phase 4). Deliberately loose: it is a drift canary
36
+ * over a ~40-case derived suite spanning ~97 rules, not a per-rule quality
37
+ * gate — ratchet only after live baseline data exists. A breach fails the
38
+ * scheduled canary job only, never a PR.
39
+ */
40
+ export const RULES_MODE_FLOOR: TriggerEvalFloor = { minRecall: 0.5, minPrecision: 0.5 };
@@ -42,14 +42,20 @@ export interface FetchRouterOpts {
42
42
  model?: string;
43
43
  apiKey?: string;
44
44
  fetchImpl?: FetchImpl;
45
+ /**
46
+ * Prompt header prepended to the catalogue (default: the skill-routing
47
+ * header). The rules-mode eval (`rule_trigger_eval.ts`) injects its own
48
+ * header here so it reuses this transport code unchanged.
49
+ */
50
+ promptHeader?: string;
45
51
  }
46
52
 
47
53
  const MAX_TOKENS = 256;
48
54
 
49
55
  /** Build the system prompt the same way AnthropicRouter does. */
50
- function buildSystemPrompt(skills: SkillMeta[]): string {
56
+ function buildSystemPrompt(skills: SkillMeta[], header: string = ROUTING_PROMPT_HEADER): string {
51
57
  const catalogue = skills.map((s) => `- ${s.name} :: ${s.description}`).join('\n');
52
- return ROUTING_PROMPT_HEADER + catalogue + '\n';
58
+ return header + catalogue + '\n';
53
59
  }
54
60
 
55
61
  function requireKey(name: string, apiKey: string | undefined): string {
@@ -97,11 +103,13 @@ export class OpenAiRouter implements TriggerRouter {
97
103
  private _model: string;
98
104
  private _apiKey: string;
99
105
  private _fetch: FetchImpl;
106
+ private _promptHeader: string | undefined;
100
107
 
101
108
  constructor(opts: FetchRouterOpts = {}) {
102
109
  this._model = opts.model ?? 'gpt-4o-mini';
103
110
  this._apiKey = requireKey('OpenAiRouter', opts.apiKey);
104
111
  this._fetch = opts.fetchImpl ?? (globalThis.fetch as unknown as FetchImpl);
112
+ this._promptHeader = opts.promptHeader;
105
113
  }
106
114
 
107
115
  route(_query: string, _skills: SkillMeta[]): [string[], number, number] {
@@ -109,7 +117,7 @@ export class OpenAiRouter implements TriggerRouter {
109
117
  }
110
118
 
111
119
  async routeAsync(query: string, skills: SkillMeta[]): Promise<[string[], number, number]> {
112
- const systemPrompt = buildSystemPrompt(skills);
120
+ const systemPrompt = buildSystemPrompt(skills, this._promptHeader);
113
121
  const body = JSON.stringify({
114
122
  model: this._model,
115
123
  max_tokens: MAX_TOKENS,
@@ -156,12 +164,14 @@ export class GeminiRouter implements TriggerRouter {
156
164
  private _model: string;
157
165
  private _apiKey: string;
158
166
  private _fetch: FetchImpl;
167
+ private _promptHeader: string | undefined;
159
168
 
160
169
  constructor(opts: FetchRouterOpts = {}) {
161
170
  // flash, NOT pro: pro burns ~150 "thinking" tokens on trivial prompts.
162
171
  this._model = opts.model ?? 'gemini-2.5-flash';
163
172
  this._apiKey = requireKey('GeminiRouter', opts.apiKey);
164
173
  this._fetch = opts.fetchImpl ?? (globalThis.fetch as unknown as FetchImpl);
174
+ this._promptHeader = opts.promptHeader;
165
175
  }
166
176
 
167
177
  route(_query: string, _skills: SkillMeta[]): [string[], number, number] {
@@ -169,7 +179,7 @@ export class GeminiRouter implements TriggerRouter {
169
179
  }
170
180
 
171
181
  async routeAsync(query: string, skills: SkillMeta[]): Promise<[string[], number, number]> {
172
- const systemPrompt = buildSystemPrompt(skills);
182
+ const systemPrompt = buildSystemPrompt(skills, this._promptHeader);
173
183
  const url =
174
184
  `https://generativelanguage.googleapis.com/v1beta/models/` +
175
185
  `${this._model}:generateContent?key=${this._apiKey}`;
@@ -221,11 +231,13 @@ export class AnthropicFetchRouter implements TriggerRouter {
221
231
  private _model: string;
222
232
  private _apiKey: string;
223
233
  private _fetch: FetchImpl;
234
+ private _promptHeader: string | undefined;
224
235
 
225
236
  constructor(opts: FetchRouterOpts = {}) {
226
237
  this._model = opts.model ?? 'claude-haiku-4-5-20251001';
227
238
  this._apiKey = requireKey('AnthropicFetchRouter', opts.apiKey);
228
239
  this._fetch = opts.fetchImpl ?? (globalThis.fetch as unknown as FetchImpl);
240
+ this._promptHeader = opts.promptHeader;
229
241
  }
230
242
 
231
243
  route(_query: string, _skills: SkillMeta[]): [string[], number, number] {
@@ -233,7 +245,7 @@ export class AnthropicFetchRouter implements TriggerRouter {
233
245
  }
234
246
 
235
247
  async routeAsync(query: string, skills: SkillMeta[]): Promise<[string[], number, number]> {
236
- const systemPrompt = buildSystemPrompt(skills);
248
+ const systemPrompt = buildSystemPrompt(skills, this._promptHeader);
237
249
  const body = JSON.stringify({
238
250
  model: this._model,
239
251
  max_tokens: MAX_TOKENS,
@@ -8,8 +8,9 @@
8
8
  * Ported from the retired Python `src/scripts/adr/regenerate_index.py` (ADR-200).
9
9
  * The CLI contract is pinned — `--dir` (default `docs/adr/`),
10
10
  * `--check`, exit codes (0 / 1 / 2), the stdout/stderr split, byte-identical
11
- * messages, AND byte-identical generated `INDEX.md`. No behaviour changes —
12
- * historical quirks preserved (consumers pin the exact behaviour).
11
+ * messages. One deliberate deviation from the Python output: the INDEX.md
12
+ * header names this TypeScript generator, not the retired `.py` (the ported
13
+ * self-misdescription was itself a defect — ADR-hygiene roadmap, Phase 1).
13
14
  */
14
15
  import * as fs from 'node:fs';
15
16
  import * as path from 'node:path';
@@ -244,7 +245,7 @@ export function render(num: Row[], leg: Row[], outside: OutsideRow[] = []): stri
244
245
  const out = [
245
246
  '# ADR Index',
246
247
  '',
247
- '_Auto-generated by `scripts/adr/regenerate_index.py`. Do not edit._',
248
+ '_Auto-generated by `src/scripts/adr/regenerate_index.ts`. Do not edit._',
248
249
  '',
249
250
  ];
250
251
  if (num.length === 0 && leg.length === 0 && outside.length === 0) {
@@ -4,7 +4,7 @@
4
4
  # stdout. Pure POSIX-compatible bash (no associative arrays, runs
5
5
  # on macOS bash 3.2); jq required for JSON safety.
6
6
  #
7
- # Schema: .agent-src.uncondensed/skills/scene-expander/scene-blueprint.schema.yaml
7
+ # Schema: src/skills/scene-expander/scene-blueprint.schema.yaml
8
8
  # Contract: scripts/media/lib/adapter-contract.md
9
9
  #
10
10
  # Usage:
@@ -10,7 +10,7 @@
10
10
  * human-authored source-of-truth for PR review. This script reads it
11
11
  * through the hardened {@link parse_corpus_strict} parser and writes a YAML
12
12
  * lockfile that becomes the **runtime** source-of-truth. The pattern mirrors
13
- * `dist/agent-src/` vs `.agent-src.uncondensed/`: human edits Markdown,
13
+ * `dist/agent-src/` vs `src/`: human edits Markdown,
14
14
  * `task consistency` enforces lockfile parity via the same
15
15
  * `git diff --quiet` gate.
16
16
  *
@@ -22,6 +22,8 @@ import * as path from 'node:path';
22
22
  import process from 'node:process';
23
23
  import { fileURLToPath, pathToFileURL } from 'node:url';
24
24
 
25
+ import { DeadScopeError, assertScanned } from './_lib/scan_scope.js';
26
+
25
27
  type Json = unknown;
26
28
  type JsonObject = Record<string, Json>;
27
29
 
@@ -29,9 +31,25 @@ const _HERE = fileURLToPath(import.meta.url);
29
31
 
30
32
  // src/scripts/audit_cloud_compatibility.ts → parents[2] of the .py file = repo root.
31
33
  export const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
32
- export const SOURCE = path.join(ROOT, '.agent-src.uncondensed');
33
34
  export const SCAN_DIRS = ['skills', 'rules', 'commands', 'guidelines'];
34
35
 
36
+ /**
37
+ * Where each artefact kind actually lives.
38
+ *
39
+ * Every kind used to be `SOURCE/<kind>` under one pre-ADR-051 container. All
40
+ * four resolved to a missing directory after the move, so `scan()` returned []
41
+ * and the audit emitted `"total": 0` with every histogram empty — a cloud-
42
+ * compatibility report over zero artefacts. The four kinds now have four
43
+ * unrelated homes (commands are not a directory at all but one `command.md`
44
+ * leaf per verb), so the mapping is explicit rather than a join.
45
+ */
46
+ export const KIND_ROOTS: Readonly<Record<string, string>> = {
47
+ skills: path.join(ROOT, 'src', 'skills'),
48
+ rules: path.join(ROOT, 'src', 'rules'),
49
+ commands: path.join(ROOT, 'src', 'domains'),
50
+ guidelines: path.join(ROOT, 'docs', 'guidelines'),
51
+ };
52
+
35
53
  // --- patterns (global so findall returns every non-overlapping match) ---------
36
54
  const SCRIPT_RE = /scripts\/[a-z_]+\.(?:py|sh)|python3\s+scripts\/|bash\s+scripts\/|\.\/scripts\//g;
37
55
  const TASK_RE = /`task\s+[a-z][a-z0-9-]+`/g;
@@ -357,7 +375,7 @@ interface Row extends ClassifyEvidence {
357
375
  export function scan(): Row[] {
358
376
  const rows: Row[] = [];
359
377
  for (const sub of SCAN_DIRS) {
360
- const base = path.join(SOURCE, sub);
378
+ const base = KIND_ROOTS[sub] as string;
361
379
  if (!_isDir(base)) {
362
380
  continue;
363
381
  }
@@ -565,6 +583,23 @@ export function main(argv: string[] | null = null): number {
565
583
  }
566
584
 
567
585
  const rows = scan();
586
+ // Scope assertion: a cloud-compat report over an empty corpus is not "no
587
+ // cloud-incompatible artefacts", it is a report about nothing — the exact
588
+ // `"total": 0` output this audit produced while its roots were dead.
589
+ try {
590
+ assertScanned({
591
+ gate: 'audit_cloud_compatibility',
592
+ scanned: rows.length,
593
+ units: 'artefact(s)',
594
+ roots: SCAN_DIRS.map((k) => _relativeToPosix(KIND_ROOTS[k] as string, ROOT)),
595
+ });
596
+ } catch (exc) {
597
+ if (!(exc instanceof DeadScopeError)) {
598
+ throw exc;
599
+ }
600
+ process.stderr.write(`❌ ${exc.message}\n`);
601
+ return 2;
602
+ }
568
603
  const summary = summarize(rows);
569
604
 
570
605
  if (args.details) {
@@ -59,9 +59,22 @@ const MONOLITH_TOOLS = ['.windsurfrules'];
59
59
  // costing?" question as the rule/description surfaces, for the MCP slice. The
60
60
  // source-of-truth catalog is the one shipped by this package's MCP server.
61
61
  const MCP_CATALOG = path.join(REPO_ROOT, 'src', 'scripts', 'mcp_server', 'consumer_tool_catalog.json');
62
- // This package ships a single MCP server; the accounting is keyed by server so
63
- // a multi-server consumer surface generalises without a shape change.
62
+ // The catalog is served by TWO transports with two different tool lists, so
63
+ // pricing it once produced a number no client ever loads:
64
+ //
65
+ // - stdio (`src/scripts/mcp_server/`) registers only the implemented tools
66
+ // (`REGISTRY` in mcp_server/tools.ts) — the 12 discovery stubs are
67
+ // deliberately unregistered (ADR-132), so a local client sees 19.
68
+ // - worker (`internal/workers/mcp/`) maps EVERY catalog entry into
69
+ // `tools/list` (`stubs.ts::listTools`), tagging stubs `_meta.stub` and
70
+ // answering calls with the not_implemented envelope — so it sees 31.
71
+ //
72
+ // Pricing all 31 under one label reported a hybrid client that cannot exist,
73
+ // and made the over-subscription flag fire on a count no local install pays.
74
+ // Keying by transport keeps the per-server shape and makes each number belong
75
+ // to a real client. `implemented_on` in the catalog is what distinguishes them.
64
76
  const MCP_SERVER_NAME = 'agent-config';
77
+ const MCP_TRANSPORTS = ['stdio', 'worker'] as const;
65
78
  // Soft advisory ceiling on tool count per server. "Few used" cannot be known at
66
79
  // audit time (no runtime call data), so over-subscription is a count-based
67
80
  // heuristic: many tools => maintainer should review which earn their cost.
@@ -396,24 +409,40 @@ export function mcp_tool_schemas(): Record<string, McpServer> {
396
409
  return {};
397
410
  }
398
411
  const tools = (catalog.tools as unknown[]) ?? [];
399
- const priced: McpPriced[] = tools
400
- .filter((t): t is Record<string, unknown> => typeof t === 'object' && t !== null && !Array.isArray(t))
401
- .map((t) => _measure_tool_schema(t));
402
- // priced.sort(key=lambda r: (-int(r["tokens_gpt"]), str(r["name"])))
403
- priced.sort(
404
- (a, b) => b.tokens_gpt - a.tokens_gpt || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0),
412
+ const records = tools.filter(
413
+ (t): t is Record<string, unknown> => typeof t === 'object' && t !== null && !Array.isArray(t),
405
414
  );
406
- const server: McpServer = {
407
- tool_count: priced.length,
408
- chars: priced.reduce((acc, r) => acc + r.chars, 0),
409
- tokens_gpt: priced.reduce((acc, r) => acc + r.tokens_gpt, 0),
410
- tokens_claude: priced.reduce((acc, r) => acc + r.tokens_claude, 0),
411
- tokens_gpt_exact: priced.length > 0 ? priced.every((r) => Boolean(r.tokens_gpt_exact)) : true,
412
- over_subscription: priced.length > MCP_OVERSUBSCRIPTION_TOOL_CAP,
413
- oversubscription_cap: MCP_OVERSUBSCRIPTION_TOOL_CAP,
414
- tools: priced.map((r) => ({ name: r.name, tokens_gpt: r.tokens_gpt, chars: r.chars })),
415
+
416
+ /** Tools a given transport actually returns from `tools/list`. */
417
+ const served = (transport: string): Record<string, unknown>[] => {
418
+ // The Worker serves the whole catalog (stubs included, flagged
419
+ // `_meta.stub`); stdio serves only what `implemented_on` names.
420
+ if (transport === 'worker') return records;
421
+ return records.filter(
422
+ (t) => Array.isArray(t.implemented_on) && (t.implemented_on as unknown[]).includes(transport),
423
+ );
415
424
  };
416
- return { [MCP_SERVER_NAME]: server };
425
+
426
+ const out: Record<string, McpServer> = {};
427
+ for (const transport of MCP_TRANSPORTS) {
428
+ const priced: McpPriced[] = served(transport).map((t) => _measure_tool_schema(t));
429
+ // priced.sort(key=lambda r: (-int(r["tokens_gpt"]), str(r["name"])))
430
+ priced.sort(
431
+ (a, b) => b.tokens_gpt - a.tokens_gpt || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0),
432
+ );
433
+ out[`${MCP_SERVER_NAME} (${transport})`] = {
434
+ tool_count: priced.length,
435
+ chars: priced.reduce((acc, r) => acc + r.chars, 0),
436
+ tokens_gpt: priced.reduce((acc, r) => acc + r.tokens_gpt, 0),
437
+ tokens_claude: priced.reduce((acc, r) => acc + r.tokens_claude, 0),
438
+ tokens_gpt_exact:
439
+ priced.length > 0 ? priced.every((r) => Boolean(r.tokens_gpt_exact)) : true,
440
+ over_subscription: priced.length > MCP_OVERSUBSCRIPTION_TOOL_CAP,
441
+ oversubscription_cap: MCP_OVERSUBSCRIPTION_TOOL_CAP,
442
+ tools: priced.map((r) => ({ name: r.name, tokens_gpt: r.tokens_gpt, chars: r.chars })),
443
+ };
444
+ }
445
+ return out;
417
446
  }
418
447
 
419
448
  export function build(): Record<string, unknown> {
@@ -678,7 +707,12 @@ export function main(argv: string[] | null = null): number {
678
707
  const rf = rfVals.length > 0 ? (rfVals[0] as Measure) : ({} as Measure);
679
708
  const dc = data.description_catalog as Record<string, Measure>;
680
709
  const mcp = (data.mcp_tool_schemas as Record<string, McpServer>) ?? {};
681
- const mcp_gpt = Object.values(mcp).reduce((acc, s) => acc + s.tokens_gpt, 0);
710
+ // MAX, not sum: the entries are two TRANSPORTS of the same server, and
711
+ // a client connects over exactly one of them. Summing would bill the
712
+ // 19 implemented tools twice and gate on a client that cannot exist —
713
+ // the same conflation the per-transport split exists to remove. The
714
+ // budget is what the worst-case single client actually loads.
715
+ const mcp_gpt = Object.values(mcp).reduce((acc, s) => Math.max(acc, s.tokens_gpt), 0);
682
716
  const checks: Record<string, number> = {
683
717
  'rules.gpt': rf.tokens_gpt ?? 0,
684
718
  // The always-SCANNED skill descriptions (258 source skills) — the
@@ -30,6 +30,8 @@ import * as fs from 'node:fs';
30
30
  import * as path from 'node:path';
31
31
  import { fileURLToPath, pathToFileURL } from 'node:url';
32
32
 
33
+ import { DeadScopeError, assertScanned } from './_lib/scan_scope.js';
34
+
33
35
  const _HERE = fileURLToPath(import.meta.url);
34
36
  // src/scripts/audit_likelihood.py → parent.parent.parent == repo root.
35
37
  export const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
@@ -38,9 +40,20 @@ export const AUDIT_JSON = path.join(REPORT_DIR, 'auto-rules-audit.json');
38
40
  export const AUDIT_MD = path.join(REPORT_DIR, 'auto-rules-audit.md');
39
41
  export const LIKELIHOOD_JSON = path.join(REPORT_DIR, 'auto-rules-likelihood.json');
40
42
 
43
+ /**
44
+ * The corpus a rule's trigger terms are scored against.
45
+ *
46
+ * The first two globs named the pre-ADR-051 source container, so skills and
47
+ * commands — by far the largest and most trigger-dense part of the corpus —
48
+ * contributed nothing. The audit still printed a score for every rule, derived
49
+ * from contexts + guidelines alone; that biases every score downward and makes
50
+ * the low-likelihood flag unreliable in the direction that matters (false
51
+ * "nothing triggers this"). Commands are now one `command.md` leaf per verb
52
+ * under `src/domains`.
53
+ */
41
54
  const CORPUS_GLOBS: readonly string[] = [
42
- '.agent-src.uncondensed/skills/**/SKILL.md',
43
- '.agent-src.uncondensed/commands/**/*.md',
55
+ 'src/skills/**/SKILL.md',
56
+ 'src/domains/**/command.md',
44
57
  'agents/settings/contexts/**/*.md',
45
58
  'docs/guidelines/**/*.md',
46
59
  ];
@@ -403,6 +416,22 @@ export function main(): number {
403
416
  }
404
417
  const rules: Json[] = JSON.parse(fs.readFileSync(AUDIT_JSON, 'utf-8'))['rules'];
405
418
  const corpus = build_corpus();
419
+ // Scope assertion: an empty corpus scores every rule as low-likelihood
420
+ // while looking exactly like a completed audit.
421
+ try {
422
+ assertScanned({
423
+ gate: 'audit_likelihood',
424
+ scanned: corpus.size,
425
+ units: 'distinct corpus token(s)',
426
+ roots: CORPUS_GLOBS,
427
+ });
428
+ } catch (exc) {
429
+ if (!(exc instanceof DeadScopeError)) {
430
+ throw exc;
431
+ }
432
+ process.stderr.write(`❌ ${exc.message}\n`);
433
+ return 2;
434
+ }
406
435
  const scores = rules.map((r) => score(r, corpus));
407
436
  fs.writeFileSync(
408
437
  LIKELIHOOD_JSON,