@event4u/agent-config 9.14.0 → 9.16.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 (287) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +118 -99
  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/mcp/content.js +1 -1
  115. package/dist/cli/mcp/content.js.map +1 -1
  116. package/dist/cli/python/workspace_drive.js +2 -0
  117. package/dist/cli/python/workspace_drive.js.map +1 -1
  118. package/dist/cli/python/workspace_drive_health.js +2 -0
  119. package/dist/cli/python/workspace_drive_health.js.map +1 -1
  120. package/dist/cli/python/workspace_hosts.js +2 -0
  121. package/dist/cli/python/workspace_hosts.js.map +1 -1
  122. package/dist/cli/python/workspace_sessions.js +2 -0
  123. package/dist/cli/python/workspace_sessions.js.map +1 -1
  124. package/dist/cli/registry.js +1 -0
  125. package/dist/cli/registry.js.map +1 -1
  126. package/dist/cli-delegate/{chunk-A3HJ6NTI.js → chunk-63WSP6YZ.js} +2 -1
  127. package/dist/cli-delegate/{chunk-EJYIGZ3T.js → chunk-BKO2AZ7Z.js} +1 -1
  128. package/dist/cli-delegate/{chunk-GCVFLBP2.js → chunk-H574KOUH.js} +338 -494
  129. package/dist/cli-delegate/chunk-NXFWFAYV.js +339 -0
  130. package/dist/cli-delegate/{chunk-TEGJU2JX.js → chunk-ZXXJLL5K.js} +2 -2
  131. package/dist/cli-delegate/cmd_conformance.js +29 -23
  132. package/dist/cli-delegate/cmd_doctor.js +3 -3
  133. package/dist/cli-delegate/cmd_export.js +4 -4
  134. package/dist/cli-delegate/cmd_preflight.js +2 -2
  135. package/dist/cli-delegate/cmd_prune.js +4 -4
  136. package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
  137. package/dist/cli-delegate/cmd_sync.js +4 -4
  138. package/dist/cli-delegate/cmd_uninstall.js +4 -4
  139. package/dist/cli-delegate/cmd_validate.js +4 -4
  140. package/dist/discovery/deprecation-report.md +1 -1
  141. package/dist/discovery/discovery-manifest.json +127 -95
  142. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  143. package/dist/discovery/discovery-manifest.summary.md +1 -1
  144. package/dist/discovery/orphan-report.md +1 -1
  145. package/dist/discovery/packs.json +2 -2
  146. package/dist/discovery/trust-report.md +1 -1
  147. package/dist/discovery/workspaces.json +2 -2
  148. package/dist/hooks/dispatch.js +729 -430
  149. package/dist/install/install.mjs +52 -11
  150. package/dist/install/rule_scope.js +18 -2
  151. package/dist/install/rule_scope.js.map +1 -1
  152. package/dist/mcp/registry-manifest.json +2 -2
  153. package/dist/mcp/server.json +2 -2
  154. package/dist/mcp/server.mjs +518 -411
  155. package/dist/router.json +1 -1
  156. package/dist/scripts/_lib/claude_settings_hooks.js +30 -5
  157. package/dist/scripts/_lib/claude_settings_hooks.js.map +1 -1
  158. package/dist/scripts/_lib/scoped_projection.js +223 -0
  159. package/dist/scripts/_lib/scoped_projection.js.map +1 -0
  160. package/dist/scripts/_lib/surface_tiers.js +125 -0
  161. package/dist/scripts/_lib/surface_tiers.js.map +1 -0
  162. package/dist/scripts/_lib/token_count.js +166 -0
  163. package/dist/scripts/_lib/token_count.js.map +1 -0
  164. package/dist/scripts/_lib/trigger_eval_floors.js +8 -0
  165. package/dist/scripts/_lib/trigger_eval_floors.js.map +1 -1
  166. package/dist/scripts/condense.js +139 -9
  167. package/dist/scripts/condense.js.map +1 -1
  168. package/dist/scripts/project_thin_rules.js +450 -0
  169. package/dist/scripts/project_thin_rules.js.map +1 -0
  170. package/dist/server/routes/workspace.js +3 -1
  171. package/dist/server/routes/workspace.js.map +1 -1
  172. package/dist/server/schemas/settings.js +9 -3
  173. package/dist/server/schemas/settings.js.map +1 -1
  174. package/dist/shared/userMd/utils.js +1 -1
  175. package/dist/shared/userMd/utils.js.map +1 -1
  176. package/dist/ui/assets/index-DmR0mbk4.js +26 -0
  177. package/dist/ui/assets/index-DmR0mbk4.js.map +1 -0
  178. package/dist/ui/index.html +1 -1
  179. package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +1 -1
  180. package/package.json +14 -14
  181. package/src/agent-src/templates/AGENTS.md +1 -1
  182. package/src/agent-src/templates/scripts/work_engine/orchestration.ts +1 -1
  183. package/src/config/agent-settings.template.yml +61 -11
  184. package/src/config/budget-routing.json +7 -0
  185. package/src/config/ci-local-parity.yml +8 -0
  186. package/src/config/discovery/unassigned-artefacts.yml +23 -25
  187. package/src/config/evaluator-budgets.json +35 -5
  188. package/src/config/gate-coverage.yml +147 -0
  189. package/src/config/gate-violation-baselines.json +25 -0
  190. package/src/install/rule_scope.ts +27 -2
  191. package/src/scripts/_cli/cmd_conformance.ts +13 -24
  192. package/src/scripts/_dispatch.bash +10 -0
  193. package/src/scripts/_lib/claude_settings_hooks.ts +30 -4
  194. package/src/scripts/_lib/gate_baseline.ts +213 -0
  195. package/src/scripts/_lib/scoped_projection.ts +50 -0
  196. package/src/scripts/_lib/tier_budget_routing.ts +360 -0
  197. package/src/scripts/_lib/tool_adapter_registry.ts +8 -1
  198. package/src/scripts/_lib/trigger_eval_floors.ts +9 -0
  199. package/src/scripts/_lib/trigger_routers.ts +17 -5
  200. package/src/scripts/adr/regenerate_index.ts +4 -3
  201. package/src/scripts/ai-video/lib/parse-blueprint.sh +1 -1
  202. package/src/scripts/ai_council/compile_corpus.ts +1 -1
  203. package/src/scripts/anchor_scoring_run.ts +1 -1
  204. package/src/scripts/audit_cloud_compatibility.ts +37 -2
  205. package/src/scripts/audit_initial_context.ts +53 -19
  206. package/src/scripts/audit_likelihood.ts +31 -2
  207. package/src/scripts/audit_user_type_axis.ts +58 -11
  208. package/src/scripts/bench_hook_latency.ts +172 -41
  209. package/src/scripts/bench_persona_placebo.ts +1 -1
  210. package/src/scripts/bench_quality_run.ts +1 -1
  211. package/src/scripts/build_mcp_catalog.ts +12 -1
  212. package/src/scripts/check_always_budget.ts +122 -11
  213. package/src/scripts/check_augment_description_cap.ts +24 -3
  214. package/src/scripts/check_ci_strict_superset.ts +150 -0
  215. package/src/scripts/check_cli_registry_budget_sync.ts +168 -0
  216. package/src/scripts/check_cluster_patterns.ts +100 -0
  217. package/src/scripts/check_consumer_scope_flip.ts +26 -4
  218. package/src/scripts/check_evaluator_budgets.ts +198 -19
  219. package/src/scripts/check_gate_coverage.ts +338 -2
  220. package/src/scripts/check_generator_output_coverage.ts +2 -1
  221. package/src/scripts/check_iron_law_prominence.ts +1 -1
  222. package/src/scripts/check_kernel_rule_bundle.ts +1 -1
  223. package/src/scripts/check_no_new_legacy_path.ts +167 -3
  224. package/src/scripts/check_pack_size.ts +18 -4
  225. package/src/scripts/check_portability.ts +37 -7
  226. package/src/scripts/check_references.ts +19 -3
  227. package/src/scripts/check_release_pr_shape.ts +13 -0
  228. package/src/scripts/check_token_quality_golden.ts +1 -1
  229. package/src/scripts/check_token_regression.ts +9 -2
  230. package/src/scripts/condense.ts +155 -12
  231. package/src/scripts/cost/budget.mjs +77 -2
  232. package/src/scripts/evaluator_umbrella.sh +34 -1
  233. package/src/scripts/generate_capability_matrix.ts +8 -0
  234. package/src/scripts/generate_router_coverage_from_matrix.ts +155 -0
  235. package/src/scripts/hook_manifest.yaml +17 -3
  236. package/src/scripts/hooks/block_config_weakening.ts +337 -0
  237. package/src/scripts/hooks/concern_registry.ts +2 -0
  238. package/src/scripts/hooks/probe_throttle.ts +75 -0
  239. package/src/scripts/install-hooks.sh +24 -2
  240. package/src/scripts/install.ts +2 -2
  241. package/src/scripts/lint_agents_md.ts +9 -3
  242. package/src/scripts/lint_archived_skills.ts +1 -1
  243. package/src/scripts/lint_artefact_frontmatter.ts +185 -37
  244. package/src/scripts/lint_command_routing.ts +21 -52
  245. package/src/scripts/lint_command_verbs.ts +47 -28
  246. package/src/scripts/lint_context_spine_usage.ts +38 -12
  247. package/src/scripts/lint_framework_leakage.ts +25 -6
  248. package/src/scripts/lint_handoffs.ts +49 -2
  249. package/src/scripts/lint_media_policy_linkage.ts +62 -30
  250. package/src/scripts/lint_namespace.ts +92 -7
  251. package/src/scripts/lint_new_skill_gate.ts +39 -31
  252. package/src/scripts/lint_originality_shingles.ts +7 -1
  253. package/src/scripts/lint_override_kernel_guard.ts +21 -3
  254. package/src/scripts/lint_pack_boundaries.ts +461 -91
  255. package/src/scripts/lint_pack_dependencies.ts +24 -57
  256. package/src/scripts/lint_persona_governance.ts +1 -1
  257. package/src/scripts/lint_role_experiences.ts +34 -5
  258. package/src/scripts/lint_trigger_precision.ts +142 -0
  259. package/src/scripts/mcp_server/consumer_tool_catalog.json +12 -201
  260. package/src/scripts/mcp_server/tools.ts +1 -1
  261. package/src/scripts/prepack-check.mjs +40 -1
  262. package/src/scripts/prepack_router_targets.d.mts +7 -0
  263. package/src/scripts/prepack_router_targets.mjs +93 -0
  264. package/src/scripts/print_required_checks.ts +72 -18
  265. package/src/scripts/profile_staleness_hook.ts +12 -0
  266. package/src/scripts/record_evaluator_measurements.mjs +105 -0
  267. package/src/scripts/recruit_preflight.sh +1 -1
  268. package/src/scripts/release.ts +214 -7
  269. package/src/scripts/router_target_paths.d.mts +4 -0
  270. package/src/scripts/router_target_paths.mjs +69 -0
  271. package/src/scripts/router_telemetry.ts +52 -1
  272. package/src/scripts/routing_doctor.ts +560 -0
  273. package/src/scripts/rule_trigger_eval.ts +403 -0
  274. package/src/scripts/schemas/rule.schema.json +5 -1
  275. package/src/scripts/session_canary_hook.ts +96 -5
  276. package/src/scripts/skill_linter.ts +9 -28
  277. package/src/scripts/smoke/skills.sh +1 -1
  278. package/src/scripts/sweep_dead_scan_roots.ts +356 -10
  279. package/src/scripts/trigger_coverage.ts +13 -2
  280. package/src/scripts/update_counts.ts +1 -1
  281. package/src/scripts/validate_telegraph_carveouts.ts +1 -1
  282. package/src/scripts/wrapper_freshness_hook.ts +14 -0
  283. package/src/server/schemas/settings.ts +19 -5
  284. package/src/shared/userMd/utils.ts +1 -1
  285. package/dist/cli-delegate/chunk-TJIAM5FW.js +0 -133
  286. package/dist/ui/assets/index-BpSQt5vm.js +0 -40
  287. package/dist/ui/assets/index-BpSQt5vm.js.map +0 -1
@@ -8,7 +8,7 @@
8
8
  * same finding text, same scan scope and file-walk order. No behaviour
9
9
  * changes; latent bugs are replicated and flagged as divergence candidates.
10
10
  *
11
- * Scans dist/agent-src/ and .agent-src.uncondensed/ for project-specific
11
+ * Scans src/ and dist/agent-src/ for project-specific
12
12
  * references that violate package portability (the package must work in ANY
13
13
  * project).
14
14
  *
@@ -271,10 +271,25 @@ const ALLOWLIST: string[] = [
271
271
  'package repository', // "package repository" concept
272
272
  'src/scripts/mcp_server/', // MCP server module path (road-to-mcp-server.md Phase 1)
273
273
  String.raw`scripts\.mcp_server`, // MCP server Python module entrypoint
274
+ // ── Revealed 2026-08-02 when `src/` entered SCAN_DIRS (dead-root repair,
275
+ // road-to-renewal-foundation Phase 1). Both live under `src/templates/`,
276
+ // which is never projected into `dist/agent-src/`, so this gate had never
277
+ // seen them. Neither is a CONSUMER-project leak — they are the package's
278
+ // own published identifiers, which a consumer must type verbatim:
279
+ String.raw`plugin install agent-conf@`, // real marketplace install command
280
+ String.raw`\.event4u-bridge\.yml`, // real shipped anchor filename (src/scripts/install.ts, lint_agents_layout.ts)
274
281
  ];
275
282
 
276
- // Directories to scan (only package files, not project-specific agents/)
277
- const SCAN_DIRS = ['dist/agent-src', '.agent-src.uncondensed'];
283
+ // Directories to scan (only package files, not project-specific agents/).
284
+ //
285
+ // DEAD-ROOT REPAIR (road-to-renewal-foundation Phase 1): the second entry was
286
+ // `.agent-src.uncondensed`, deleted by ADR-051. The loops below `continue` on a
287
+ // missing directory, so half this gate's declared corpus — the whole authoring
288
+ // tree — was silently skipped. A project identifier authored in `src/` was only
289
+ // ever caught once a condense run projected it into `dist/agent-src/`, i.e. one
290
+ // step later than the gate claims to catch it. `src/` is the source of truth
291
+ // now and is scanned directly.
292
+ const SCAN_DIRS = ['src', 'dist/agent-src'];
278
293
 
279
294
  // Additional root-level files shipped by the package that must also stay
280
295
  // portable. These are read by agents working on the package itself and —
@@ -608,11 +623,20 @@ function check_cli_invocations(filepath: string): Violation[] {
608
623
  return violations;
609
624
  }
610
625
 
611
- function scan_all(root: string): { violations: Violation[]; detected: string[] } {
626
+ function scan_all(root: string): {
627
+ violations: Violation[];
628
+ detected: string[];
629
+ scanned: number;
630
+ } {
612
631
  const { patterns, detected } = _compile_patterns(root);
613
632
  const forbidden = _compile_forbidden_patterns();
614
633
  const allowlist = _compile_allowlist();
615
634
  const violations: Violation[] = [];
635
+ // Distinct files actually read, for the gate-coverage contract
636
+ // (src/config/gate-coverage.yml): a gate that inspected nothing has not
637
+ // passed. Counted as a set because the layer-3/4 passes revisit files the
638
+ // layer-1 pass already read.
639
+ const scannedFiles = new Set<string>();
616
640
 
617
641
  // Layer 1 + 2: full package content
618
642
  for (const scanDir of SCAN_DIRS) {
@@ -621,6 +645,7 @@ function scan_all(root: string): { violations: Violation[]; detected: string[] }
621
645
  continue;
622
646
  }
623
647
  for (const f of _rglobMdSorted(d)) {
648
+ scannedFiles.add(f);
624
649
  violations.push(...check_file(f, [...patterns, ...forbidden], allowlist));
625
650
  }
626
651
  }
@@ -629,6 +654,7 @@ function scan_all(root: string): { violations: Violation[]; detected: string[] }
629
654
  for (const rel of SCAN_ROOT_FILES) {
630
655
  const f = path.join(root, rel);
631
656
  if (_isFile(f)) {
657
+ scannedFiles.add(f);
632
658
  violations.push(...check_file(f, forbidden, allowlist));
633
659
  }
634
660
  }
@@ -664,7 +690,7 @@ function scan_all(root: string): { violations: Violation[]; detected: string[] }
664
690
  }
665
691
  }
666
692
 
667
- return { violations, detected };
693
+ return { violations, detected, scanned: scannedFiles.size };
668
694
  }
669
695
 
670
696
  function format_text(violations: Violation[], detected: string[]): string {
@@ -816,7 +842,7 @@ function parseArgs(argv: readonly string[]): ParsedArgs {
816
842
  function main(): number {
817
843
  const args = parseArgs(process.argv.slice(2));
818
844
 
819
- let result: { violations: Violation[]; detected: string[] };
845
+ let result: { violations: Violation[]; detected: string[]; scanned: number };
820
846
  try {
821
847
  result = scan_all(args.root);
822
848
  } catch (e) {
@@ -825,7 +851,11 @@ function main(): number {
825
851
  return 3;
826
852
  }
827
853
 
828
- const { violations, detected } = result;
854
+ const { violations, detected, scanned } = result;
855
+ // gate-coverage contract (src/config/gate-coverage.yml): one machine-readable
856
+ // count on stderr so `check_gate_coverage` can assert this gate read a real
857
+ // corpus. It scanned a deleted root for months and still exited 0.
858
+ process.stderr.write(`scanned: ${String(scanned)}\n`);
829
859
  if (args.format === 'json') {
830
860
  const payload = { detected, violations };
831
861
  process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
@@ -452,9 +452,12 @@ function _extract_personas_frontmatter(text: string): Array<[number, string]> {
452
452
 
453
453
  function _find_suggestion(p: string, root: string): string {
454
454
  const name = path.basename(p);
455
+ // DEAD-ROOT REPAIR (road-to-renewal-foundation Phase 1): the middle entry
456
+ // was `.agent-src.uncondensed`, deleted by ADR-051. `src/` is the source of
457
+ // truth, so a file that exists only there produced no suggestion at all.
455
458
  for (const d of [
456
459
  path.join(root, 'dist/agent-src'),
457
- path.join(root, '.agent-src.uncondensed'),
460
+ path.join(root, 'src'),
458
461
  path.join(root, 'agents'),
459
462
  ]) {
460
463
  if (_exists(d)) {
@@ -586,10 +589,15 @@ function check_file(filepath: string, artifacts: Artifacts, root: string): Broke
586
589
  } else {
587
590
  // Strip leading ./ and try with prefixes
588
591
  const ref = rawRef.replace(/^[./]+/, '');
592
+ // DEAD-ROOT REPAIR: `.agent-src.uncondensed` (ADR-051) never
593
+ // resolved, so a reference that only exists under the source of
594
+ // truth was reported BROKEN. `src/` and its category roots
595
+ // replace it.
589
596
  for (const prefix of [
590
597
  root,
591
598
  path.join(root, 'dist/agent-src'),
592
- path.join(root, '.agent-src.uncondensed'),
599
+ path.join(root, 'src'),
600
+ path.join(root, 'src/agent-src'),
593
601
  ]) {
594
602
  if (_exists(path.join(prefix, ref))) {
595
603
  resolved = true;
@@ -605,7 +613,7 @@ function check_file(filepath: string, artifacts: Artifacts, root: string): Broke
605
613
  const rel = rawRef.slice('.augment/'.length);
606
614
  for (const prefix of [
607
615
  path.join(root, 'dist/agent-src'),
608
- path.join(root, '.agent-src.uncondensed'),
616
+ path.join(root, 'src/agent-src'),
609
617
  ]) {
610
618
  if (_exists(path.join(prefix, rel))) {
611
619
  resolved = true;
@@ -774,6 +782,10 @@ function check_memory_yaml(filepath: string, artifacts: Artifacts, root: string)
774
782
  function scan_all(root: string): BrokenRef[] {
775
783
  const artifacts = collect_artifacts(root);
776
784
  const broken: BrokenRef[] = [];
785
+ // gate-coverage contract (src/config/gate-coverage.yml): a machine-readable
786
+ // count of files actually read, so `check_gate_coverage` can assert this
787
+ // gate saw a real corpus rather than a moved root.
788
+ let scannedFiles = 0;
777
789
  for (const scanDir of SCAN_DIRS) {
778
790
  const d = path.join(root, scanDir);
779
791
  if (!_exists(d)) {
@@ -785,18 +797,22 @@ function scan_all(root: string): BrokenRef[] {
785
797
  if (SKIP_DIRS.some((skip) => f.startsWith(path.join(root, skip)))) {
786
798
  continue;
787
799
  }
800
+ scannedFiles += 1;
788
801
  broken.push(...check_file(f, artifacts, root));
789
802
  }
790
803
  }
791
804
  const memoryDir = path.join(root, MEMORY_YAML_ROOT);
792
805
  if (_isDir(memoryDir)) {
793
806
  for (const f of _rglob(memoryDir, '.yml').sort()) {
807
+ scannedFiles += 1;
794
808
  broken.push(...check_memory_yaml(f, artifacts, root));
795
809
  }
796
810
  for (const f of _rglob(memoryDir, '.yaml').sort()) {
811
+ scannedFiles += 1;
797
812
  broken.push(...check_memory_yaml(f, artifacts, root));
798
813
  }
799
814
  }
815
+ process.stderr.write(`scanned: ${String(scannedFiles)}\n`);
800
816
  return broken;
801
817
  }
802
818
 
@@ -24,6 +24,9 @@ const _HERE = fileURLToPath(import.meta.url);
24
24
 
25
25
  const ALLOWLIST_GLOBS = [
26
26
  'package.json',
27
+ // Lockfile version fields — bumped by release.ts set_lockfile_version in
28
+ // lockstep with package.json (road-to-gates-that-can-fail Phase 5).
29
+ 'package-lock.json',
27
30
  'CHANGELOG.md',
28
31
  '.claude-plugin/marketplace.json',
29
32
  'src/packs/*/pack.yaml',
@@ -131,6 +134,16 @@ function check(files: readonly string[]): number {
131
134
  for (const f of bad) {
132
135
  process.stdout.write(`OUT-OF-SHAPE: ${f}\n`);
133
136
  }
137
+ process.stdout.write(
138
+ '\n' +
139
+ 'A release PR may only contain the version-bump allowlist — the heavy\n' +
140
+ 'test matrix skips on release/* heads, so code must not ride a release PR\n' +
141
+ '(docs/contracts/release-pr-gating.md § Mid-release fixes).\n' +
142
+ 'Fix: land the files above on main via their own PR, then\n' +
143
+ ' git checkout release/X.Y.Z && git merge origin/main && git push\n' +
144
+ '— their release-PR diff becomes empty and this check goes green.\n' +
145
+ 'Then resume with: task release -- --resume --yes\n',
146
+ );
134
147
  return 1;
135
148
  }
136
149
  process.stdout.write(
@@ -41,7 +41,7 @@ import * as path from 'node:path';
41
41
  import process from 'node:process';
42
42
  import { fileURLToPath, pathToFileURL } from 'node:url';
43
43
 
44
- import yaml from 'js-yaml';
44
+ import * as yaml from 'js-yaml';
45
45
 
46
46
  const _HERE = fileURLToPath(import.meta.url);
47
47
  const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
@@ -17,7 +17,9 @@
17
17
  * - thin_rule_load — thin_projection.thin_gpt
18
18
  * - skill_descriptions — description_catalog.skills_core_source.tokens_gpt
19
19
  * - command_descriptions — description_catalog.commands_core_source.tokens_gpt
20
- * - mcp_schemas — Σ mcp_tool_schemas[*].tokens_gpt
20
+ * - mcp_schemas — max mcp_tool_schemas[*].tokens_gpt (per-transport;
21
+ * a client connects over ONE transport, so Σ would
22
+ * double-bill the tools both transports serve)
21
23
  *
22
24
  * A baseline captured under a different `token_method` (e.g. proxy → exact)
23
25
  * is a legitimate reset, not a regression — the gate WARNS and asks for a
@@ -74,10 +76,15 @@ function _num(obj: unknown, ...keys: string[]): number {
74
76
 
75
77
  /** Extract the flat gated-metric map from a projection-cost report. */
76
78
  function extract_metrics(report: Json): Metrics {
79
+ // MAX, not Σ. The report keys one entry per SERVER-TRANSPORT pair, and a
80
+ // client connects over exactly one transport of a given server — summing
81
+ // `agent-config (stdio)` and `agent-config (worker)` would bill the 19
82
+ // shared tools twice and read as a regression the moment the transports were
83
+ // split apart. The gated metric is what the worst-case single client loads.
77
84
  const mcp = (report['mcp_tool_schemas'] as Json) ?? {};
78
85
  let mcp_total = 0;
79
86
  for (const server of Object.values(mcp)) {
80
- mcp_total += _num(server, 'tokens_gpt');
87
+ mcp_total = Math.max(mcp_total, _num(server, 'tokens_gpt'));
81
88
  }
82
89
  return {
83
90
  token_method: String(report['token_method'] ?? 'unknown'),
@@ -35,15 +35,23 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
35
35
  import * as YAML from 'yaml';
36
36
 
37
37
  import {
38
+ _domains_command_logical,
38
39
  artefact_roots as _agent_src_artefact_roots,
39
40
  iter_all_sources as _agent_src_iter_all_sources,
40
41
  resolve_logical as _agent_src_resolve_logical,
41
42
  strip_source_prefix,
42
43
  } from './_lib/agent_src.js';
44
+ // Import-safety note: `project_thin_rules` guards its CLI entry
45
+ // (`_isCliEntry()` before `process.exit(main())`), so importing it here is
46
+ // side-effect-free. condense.ts is bundled into the installer, where a bare
47
+ // top-level `process.exit` would fire at consumer runtime — the documented
48
+ // bundled-CLI-entry-guard landmine. Verified before wiring, not assumed.
49
+ import { build_thin } from './project_thin_rules.js';
43
50
  import { build_claude_hook_matrix } from './_lib/claude_settings_hooks.js';
44
51
  import { is_claude_builtin_name } from './_lib/claude_builtin_names.js';
45
52
  import { project_settings_path, load_agent_settings } from './_lib/agent_settings.js';
46
53
  import { rule_is_compile_enabled } from './_lib/compile_time_toggles.js';
54
+ import { resolve_rule_pack_scope } from './_lib/scoped_projection.js';
47
55
  import { info, success, flush_summary, resolve_level } from './_lib/script_output.js';
48
56
  import {
49
57
  TIER_TO_CLAUDE_MODEL as _TIER_TO_CLAUDE_MODEL,
@@ -987,13 +995,16 @@ export { _HRR_BANNER_MARKER };
987
995
  // (rule-router contract § Schema v2). Untagged rules fail safe: they ship.
988
996
  // ADR-040: projection-time filtering only, no runtime resolver.
989
997
 
990
- function _read_projection_list(key: string): string[] | null {
998
+ function _read_projection_raw(key: string): unknown {
991
999
  const data = load_agent_settings({ project_path: MODULE_STATE.SETTINGS_FILE });
992
1000
  const proj = data['projection'];
993
- const value =
994
- typeof proj === 'object' && proj !== null && !Array.isArray(proj)
995
- ? (proj as Record<string, unknown>)[key]
996
- : null;
1001
+ return typeof proj === 'object' && proj !== null && !Array.isArray(proj)
1002
+ ? (proj as Record<string, unknown>)[key]
1003
+ : null;
1004
+ }
1005
+
1006
+ function _read_projection_list(key: string): string[] | null {
1007
+ const value = _read_projection_raw(key);
997
1008
  if (Array.isArray(value) && value.length > 0) {
998
1009
  return value.map((v) => String(v));
999
1010
  }
@@ -1004,8 +1015,28 @@ function _read_rule_workspaces(): string[] | null {
1004
1015
  return _read_projection_list('rule_workspaces');
1005
1016
  }
1006
1017
 
1018
+ /** `runtime.active_packs` overlay, empty when unset (fresh-install default). */
1019
+ function _read_runtime_active_packs(): string[] {
1020
+ const data = load_agent_settings({ project_path: MODULE_STATE.SETTINGS_FILE });
1021
+ const rt = data['runtime'];
1022
+ const value =
1023
+ typeof rt === 'object' && rt !== null && !Array.isArray(rt)
1024
+ ? (rt as Record<string, unknown>)['active_packs']
1025
+ : null;
1026
+ return Array.isArray(value) ? value.map((v) => String(v)) : [];
1027
+ }
1028
+
1029
+ /**
1030
+ * Pack scope for the rule layer. `rule_packs: auto` derives the active-pack
1031
+ * set (the same one the skill/command prune uses) instead of a hand-typed id
1032
+ * list; anything else keeps the historical list-or-inactive semantics.
1033
+ */
1007
1034
  function _read_rule_packs(): string[] | null {
1008
- return _read_projection_list('rule_packs');
1035
+ return resolve_rule_pack_scope(
1036
+ _read_projection_raw('rule_packs'),
1037
+ MODULE_STATE.PROJECT_ROOT,
1038
+ _read_runtime_active_packs(),
1039
+ );
1009
1040
  }
1010
1041
 
1011
1042
  /**
@@ -1044,11 +1075,19 @@ export function generate_rule_symlinks(): number {
1044
1075
 
1045
1076
  let thin_files: Record<string, string> | null = null;
1046
1077
  if (_lean_projection_mode() === 'thin') {
1047
- // The `project_thin_rules` twin is not ported (out of scope — thin mode
1048
- // is opt-in and not exercised by golden parity). Throw a clear error if
1049
- // a consumer actually enables it, mirroring an unmet import.
1050
- throw new Error(
1051
- 'lean_projection.mode=thin requires project_thin_rules (not ported in condense.ts)',
1078
+ // DEAD-SWITCH REPAIR (road-to-renewal-foundation Phase 2). This branch
1079
+ // used to THROW: the port was skipped as "out of scope, not exercised by
1080
+ // golden parity", which left a documented, settings-selectable mode that
1081
+ // could only ever crash. `build_thin` was ported and has been present the
1082
+ // whole time — the wiring was the only thing missing.
1083
+ //
1084
+ // Scope note: this repairs the SWITCH only. The default stays
1085
+ // `eager-all`. Flipping it is parked behind the thin-projection honest
1086
+ // null (thin win-rate 36.2% < the 48% pre-registered threshold), and
1087
+ // nothing here disturbs that verdict — but a mode that throws cannot
1088
+ // even be re-measured, which is why the repair earns its place alone.
1089
+ thin_files = Object.fromEntries(
1090
+ build_thin(MODULE_STATE.RULES_SOURCE, _read_rule_workspaces()),
1052
1091
  );
1053
1092
  }
1054
1093
 
@@ -1146,6 +1185,9 @@ function _WINDSURF_WORKFLOWS_DIR(): string {
1146
1185
  function _CURSOR_COMMANDS_DIR(): string {
1147
1186
  return path.join(MODULE_STATE.PROJECT_ROOT, '.cursor', 'commands');
1148
1187
  }
1188
+ function _CLAUDE_COMMANDS_DIR(): string {
1189
+ return path.join(MODULE_STATE.PROJECT_ROOT, '.claude', 'commands');
1190
+ }
1149
1191
 
1150
1192
  function _parse_frontmatter(content: string): [Record<string, unknown>, string] {
1151
1193
  if (!content.startsWith('---')) {
@@ -1566,6 +1608,92 @@ export function extract_description_from_md(content: string): string {
1566
1608
  return '';
1567
1609
  }
1568
1610
 
1611
+ /**
1612
+ * The `commands/<sub>` path of a command source file, or `null` when it is a
1613
+ * FLAT command (`commands/<name>.md`).
1614
+ *
1615
+ * A nested path is what Claude Code renders as `/cluster:sub`. Flat command
1616
+ * FILES are not registered by Claude Code (probed ≤ 2.1.204 — see the
1617
+ * flat-command mitigation in `install.ts`), which is why flat commands keep a
1618
+ * skill wrapper and nested ones do not need one.
1619
+ */
1620
+ function _nested_command_subpath(source_file: string): string | null {
1621
+ const rel = _relativeToPosix(source_file, MODULE_STATE.PROJECT_ROOT);
1622
+ const logical = _domains_command_logical(rel);
1623
+ if (logical === null || !logical.startsWith('commands/')) {
1624
+ return null;
1625
+ }
1626
+ const sub = logical.slice('commands/'.length).replace(/\.md$/, '');
1627
+ return sub.includes('/') ? sub : null;
1628
+ }
1629
+
1630
+ /**
1631
+ * Project-scope `.claude/commands/<cluster>/<sub>.md` — the colon form.
1632
+ *
1633
+ * Before this, the package repo's own `.claude/` carried NO commands
1634
+ * directory: every command reached Claude Code as a hyphen-named skill
1635
+ * wrapper in `.claude/skills/`, while the colon form existed only in the
1636
+ * user-global tree written by `install.ts`. Every clustered command was
1637
+ * therefore listed TWICE for anyone with a global install
1638
+ * (`roadmap-process-full` AND `roadmap:process-full`), costing 4,214 GPT tok
1639
+ * of always-loaded catalog for zero added reach — measured 2026-08-02.
1640
+ *
1641
+ * Emitting the nested form here lets `generate_claude_commands` stop wrapping
1642
+ * the clustered commands: one listing, same reachability, and reachability no
1643
+ * longer depends on a global deploy having run. Claude Code dedupes project
1644
+ * and user scope by name, so the two copies of `/cluster:sub` collapse.
1645
+ *
1646
+ * ADR-003 (colon canonical for clusters) and ADR-044 (flat commands stay
1647
+ * hyphenated) both hold: flat commands are untouched and keep their wrapper.
1648
+ */
1649
+ export function generate_claude_project_commands(
1650
+ active_command_slugs: ReadonlySet<string> | null = null,
1651
+ ): number {
1652
+ if (!_isDir(path.join(MODULE_STATE.PROJECT_ROOT, 'src', 'domains'))) {
1653
+ return 0;
1654
+ }
1655
+ const nested: Array<[string, string]> = [];
1656
+ for (const [source_file, slug] of iterCommands()) {
1657
+ if (active_command_slugs !== null && !active_command_slugs.has(slug)) {
1658
+ continue;
1659
+ }
1660
+ const sub = _nested_command_subpath(source_file);
1661
+ if (sub !== null) {
1662
+ nested.push([source_file, sub]);
1663
+ }
1664
+ }
1665
+
1666
+ const target_root = _CLAUDE_COMMANDS_DIR();
1667
+ const valid = new Set(nested.map(([, sub]) => `${sub}.md`));
1668
+ // Sweep stale links first: a renamed or deleted command must not linger as
1669
+ // a dangling `/cluster:sub`.
1670
+ if (_exists(target_root)) {
1671
+ for (const item of _rglobSorted(target_root, '*.md')) {
1672
+ const rel = _relativeToPosix(item, target_root);
1673
+ if (!valid.has(rel)) {
1674
+ fs.unlinkSync(item);
1675
+ }
1676
+ }
1677
+ }
1678
+
1679
+ let count = 0;
1680
+ for (const [source_file, sub] of nested) {
1681
+ const link = path.join(target_root, `${sub}.md`);
1682
+ _mkdirp(path.dirname(link));
1683
+ if (_existsOrSymlink(link)) {
1684
+ fs.unlinkSync(link);
1685
+ }
1686
+ // path.relative, not a hand-counted `../` run: the nesting depth
1687
+ // varies (2 and 3 segments both occur) and an off-by-one here writes
1688
+ // a dangling link that still LOOKS right in `ls -l`.
1689
+ const rel_target = path.relative(path.dirname(link), source_file);
1690
+ fs.symlinkSync(rel_target, link);
1691
+ count += 1;
1692
+ }
1693
+ info(` ✅ Linked ${count} \`.claude/commands/**/*.md\` files (colon form)`);
1694
+ return count;
1695
+ }
1696
+
1569
1697
  export function generate_claude_commands(active_command_slugs: ReadonlySet<string> | null = null): number {
1570
1698
  if (!_isDir(path.join(MODULE_STATE.PROJECT_ROOT, 'src', 'domains'))) {
1571
1699
  process.stderr.write(' ⚠️ src/domains/ not found — skipping commands\n');
@@ -1597,6 +1725,15 @@ export function generate_claude_commands(active_command_slugs: ReadonlySet<strin
1597
1725
  skipped += 1;
1598
1726
  continue;
1599
1727
  }
1728
+ // A clustered command already reaches Claude Code as `/cluster:sub`
1729
+ // from `.claude/commands/` (generate_claude_project_commands). A
1730
+ // hyphen wrapper on top would be a second listing of the same command
1731
+ // — the double-listing this de-duplicates. Flat commands still need
1732
+ // the wrapper: Claude Code does not register flat command FILES.
1733
+ if (_nested_command_subpath(source_file) !== null) {
1734
+ skipped += 1;
1735
+ continue;
1736
+ }
1600
1737
  // Never claim a `/name` that is a Claude Code built-in command or
1601
1738
  // bundled skill — a same-name entry shadows the built-in (e.g. a
1602
1739
  // `review` entry hides Claude Code's own /review). Nested
@@ -2144,6 +2281,11 @@ function _generate_tools_inner(
2144
2281
  generate_gemini_md();
2145
2282
  }
2146
2283
  const skills = _tool_active('claude-code') ? generate_claude_skills(skill_names) : 0;
2284
+ // Colon form first: `generate_claude_commands` skips every command this
2285
+ // one emitted, so the ordering is what keeps the two from double-listing.
2286
+ const claude_cmds = _tool_active('claude-code')
2287
+ ? generate_claude_project_commands(cmd_slugs)
2288
+ : 0;
2147
2289
  const commands = _tool_active('claude-code') ? generate_claude_commands(cmd_slugs) : 0;
2148
2290
  const subagents = _tool_active('claude-code') ? generate_claude_subagents() : 0;
2149
2291
  const subagent_contexts = generate_subagent_host_contexts();
@@ -2157,7 +2299,8 @@ function _generate_tools_inner(
2157
2299
  const windsurf_wf = _tool_active('windsurf') ? generate_windsurf_workflows(cmd_slugs) : 0;
2158
2300
  const summary =
2159
2301
  `✅ generate-tools — rules=${rules} skills=${skills} ` +
2160
- `commands=${commands} subagents=${subagents} subagent_contexts=${subagent_contexts} plugin_cmd_skills=${plugin_cmd_skills} ` +
2302
+ `claude_commands=${claude_cmds} command_skills=${commands} ` +
2303
+ `subagents=${subagents} subagent_contexts=${subagent_contexts} plugin_cmd_skills=${plugin_cmd_skills} ` +
2161
2304
  `plugin_hooks=${plugin_hooks} ` +
2162
2305
  `personas=${personas} user_types=${user_types} ` +
2163
2306
  `cursor_mdc=${cursor_mdc} windsurf_rules=${windsurf_modern} ` +
@@ -10,7 +10,7 @@
10
10
  // Env: BUDGET_STORE, BUDGET_CONFIG, BUDGET_PERIOD={today|week|month|all}, BUDGET_QUIET=1
11
11
 
12
12
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
13
- import { dirname } from 'node:path';
13
+ import { dirname, join } from 'node:path';
14
14
 
15
15
  const STORE = process.env.BUDGET_STORE || 'agents/cost-tracking/sessions.jsonl';
16
16
  const CONFIG = process.env.BUDGET_CONFIG || 'agents/cost-tracking/budget.json';
@@ -198,14 +198,89 @@ function cmdCheck() {
198
198
  if (hardStop) process.exit(1);
199
199
  }
200
200
 
201
+ // --- Per-tier budget state (docs/contracts/budget-routing.md) -------------
202
+ // v1 window for per-tier ceilings: rolling 24h for SPEND (ledger entries
203
+ // tagged `tier:`). Pending reserves from tier-reserves.jsonl count only
204
+ // within the shared reserve TTL — reserves are race protection, not spend
205
+ // accounting. The TTL comes from src/config/budget-routing.json, the SAME
206
+ // source acquireBudgetPermit reads, so the two readers cannot diverge
207
+ // (external review 2026-08-03, Finding 1b).
208
+
209
+ function loadReserveTtlMs() {
210
+ try {
211
+ const cfgPath = join(dirname(new URL(import.meta.url).pathname), '..', '..', 'config', 'budget-routing.json');
212
+ const raw = JSON.parse(readFileSync(cfgPath, 'utf-8'));
213
+ if (Number.isFinite(raw.reserve_ttl_ms)) return raw.reserve_ttl_ms;
214
+ } catch { /* fall through to the safe default below */ }
215
+ return 600000;
216
+ }
217
+
218
+ function loadPerTierCeilings() {
219
+ const out = { cheap: null, medium: null, strong: null };
220
+ if (!existsSync(SETTINGS)) return out;
221
+ let inPerTier = false;
222
+ for (const line of readFileSync(SETTINGS, 'utf-8').split('\n')) {
223
+ if (/^ {4}per_tier:/.test(line)) { inPerTier = true; continue; }
224
+ if (inPerTier) {
225
+ const m = line.match(/^ {6}(cheap|medium|strong):\s*([0-9.]+|null)\s*(#.*)?$/);
226
+ if (m) {
227
+ out[m[1]] = m[2] === 'null' ? null : Number(m[2]);
228
+ continue;
229
+ }
230
+ if (/^ {0,5}\S/.test(line)) inPerTier = false;
231
+ }
232
+ }
233
+ return out;
234
+ }
235
+
236
+ function cmdTier(rest) {
237
+ const tier = rest[0];
238
+ if (!['cheap', 'medium', 'strong'].includes(tier)) {
239
+ console.error('usage: budget.mjs tier {cheap|medium|strong}');
240
+ process.exit(2);
241
+ }
242
+ const dayMs = 24 * 3600 * 1000;
243
+ const now = Date.now();
244
+ const inWindow = (ts) => ts && (now - new Date(ts).getTime()) < dayMs;
245
+ const spent = loadSessions()
246
+ .filter((s) => s.tier === tier && inWindow(s.capturedAt || s.endedAt || s.ts))
247
+ .reduce((acc, s) => acc + (Number(s.total_cost_usd ?? s.cost_usd ?? 0) || 0), 0);
248
+ let reserved = 0;
249
+ const reserveTtl = loadReserveTtlMs();
250
+ const reservePath = join(dirname(STORE), 'tier-reserves.jsonl');
251
+ if (existsSync(reservePath)) {
252
+ for (const line of readFileSync(reservePath, 'utf-8').split('\n')) {
253
+ if (!line.trim()) continue;
254
+ try {
255
+ const e = JSON.parse(line);
256
+ if (e.tier === tier && e.status === 'pending' && (now - e.ts_ms) < reserveTtl) {
257
+ reserved += Number(e.est_usd || 0) || 0;
258
+ }
259
+ } catch { /* skip malformed line */ }
260
+ }
261
+ }
262
+ const ceiling = loadPerTierCeilings()[tier];
263
+ const available = ceiling === null ? true : spent + reserved < ceiling;
264
+ console.log(JSON.stringify({
265
+ tier,
266
+ window: 'rolling-24h',
267
+ reserve_ttl_ms: reserveTtl,
268
+ ceiling_usd: ceiling,
269
+ spent_usd: spent,
270
+ reserved_usd: reserved,
271
+ available,
272
+ }));
273
+ }
274
+
201
275
  function main() {
202
276
  const [cmd, ...rest] = process.argv.slice(2);
203
277
  switch (cmd) {
204
278
  case 'set': return cmdSet(rest);
205
279
  case 'get': return cmdGet();
206
280
  case 'check': return cmdCheck();
281
+ case 'tier': return cmdTier(rest);
207
282
  default:
208
- console.error('usage: budget.mjs {set <usd>|get|check}');
283
+ console.error('usage: budget.mjs {set <usd>|get|check|tier <cheap|medium|strong>}');
209
284
  process.exit(2);
210
285
  }
211
286
  }
@@ -23,8 +23,12 @@
23
23
  # 9. npx smoke first-touch paths (--version, --help, mcp initialize)
24
24
  # 6-8 feed src/scripts/check_evaluator_budgets.ts against
25
25
  # src/config/evaluator-budgets.json (owner + review date lint-enforced).
26
+ # 8b. record publish with UMBRELLA_RECORD=1, writes the measurement set to
27
+ # agents/evidence/metrics/evaluator-measurements.json so
28
+ # drift is visible in a diff the day it lands
26
29
  #
27
30
  # Usage: bash src/scripts/evaluator_umbrella.sh [--skip-pack <tarball>]
31
+ # UMBRELLA_RECORD=1 bash src/scripts/evaluator_umbrella.sh # nightly
28
32
  set -euo pipefail
29
33
 
30
34
  ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
@@ -166,9 +170,38 @@ console.log(JSON.stringify(m, null, 1));' \
166
170
  "$MEASURE_TMP" "$UNPACKED_BYTES" "$NM_MB" "$DEP_COUNT" "$CLI_MS" "$MCP_MS" "$CLI_COUNT" "$MCP_TOOLS" \
167
171
  "$COLD_START_MS" "$COLD_START_CONDITIONS"
168
172
 
169
- step "budget gate (absolute + >regression_pct creep)"
173
+ # The budget gate runs BEFORE the record is refreshed, so the comparison is
174
+ # fresh-vs-committed. Refreshing first would make every drift check trivially
175
+ # green (road-to-gates-that-can-fail Phase 5).
176
+ step "budget gate (absolute + >regression_pct creep + drift vs the committed record)"
170
177
  ./scripts-run src/scripts/check_evaluator_budgets --measurements "$MEASURE_TMP"
171
178
 
179
+ # ------------------------------------------------- 8b. publish the record
180
+ # With UMBRELLA_RECORD=1 (the nightly sets it) the measurement set is written
181
+ # to a COMMITTED artifact, so budget drift is visible the day it lands instead
182
+ # of at the next release — `cli_help_command_count` drifted 74 → 80 against a
183
+ # value frozen at 79 with nobody seeing it. It lives under agents/evidence/,
184
+ # the repo's committed-evidence tree, and NOT under src/config/, which ships:
185
+ # this is maintainer evidence with nightly churn, not consumer runtime config,
186
+ # and keeping it out of the tarball also keeps evaluator-budgets.json (the
187
+ # policy) reviewable on its own. Sorted keys + fixed indent so a moved number
188
+ # is a one-line diff.
189
+ if [[ "${UMBRELLA_RECORD:-0}" == "1" ]]; then
190
+ step "publish the measurement record (agents/evidence/metrics/)"
191
+ RECORD_PATH="$ROOT/agents/evidence/metrics/evaluator-measurements.json"
192
+ mkdir -p "$(dirname "$RECORD_PATH")"
193
+ RUN_URL=""
194
+ if [[ -n "${GITHUB_REPOSITORY:-}" && -n "${GITHUB_RUN_ID:-}" ]]; then
195
+ RUN_URL="${GITHUB_SERVER_URL:-https://github.com}/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
196
+ fi
197
+ node src/scripts/record_evaluator_measurements.mjs \
198
+ --measurements "$MEASURE_TMP" \
199
+ --out "$RECORD_PATH" \
200
+ --sha "$(git rev-parse HEAD 2>/dev/null || echo '')" \
201
+ --run-url "$RUN_URL" \
202
+ --conditions "$COLD_START_CONDITIONS"
203
+ fi
204
+
172
205
  # ------------------------------------------------------------- 9. npx smoke
173
206
  step "first-touch smoke (installed bin)"
174
207
  "$CLI_BIN" --version >/dev/null
@@ -109,6 +109,14 @@ const _FN_SPEC: Record<string, FnSpec> = {
109
109
  artifact: 'commands',
110
110
  cells: { 'claude-code': 'native', augment: 'native' },
111
111
  },
112
+ // The colon form (`/cluster:sub`) in the project tree. Same artifact and
113
+ // the same native cell as the hyphen wrapper above — the two split the
114
+ // command population between them (nested here, flat there); this is not
115
+ // an added surface.
116
+ generate_claude_project_commands: {
117
+ artifact: 'commands',
118
+ cells: { 'claude-code': 'native' },
119
+ },
112
120
  generate_claude_subagents: {
113
121
  artifact: 'subagents',
114
122
  cells: { 'claude-code': 'native' },