@event4u/agent-config 12.1.0 → 14.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +164 -186
  3. package/README.md +1 -1
  4. package/dist/agent-src/commands/cost/report.md +23 -0
  5. package/dist/agent-src/commands/evals/roadmap.json +8 -0
  6. package/dist/agent-src/commands/fix/ci.md +39 -1
  7. package/dist/agent-src/commands/pr/create.md +65 -4
  8. package/dist/agent-src/commands/roadmap/next.md +28 -5
  9. package/dist/agent-src/commands/work.md +16 -0
  10. package/dist/agent-src/contexts/contracts/command-suggestion-flow.md +47 -0
  11. package/dist/agent-src/contexts/execution/contract-decision-sheet.md +94 -0
  12. package/dist/agent-src/contexts/execution/plan-confidence-gate.md +30 -0
  13. package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +21 -8
  14. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +55 -8
  15. package/dist/agent-src/contexts/execution/subagent-response-contract.md +42 -0
  16. package/dist/agent-src/contexts/execution/subagent-routing.md +148 -33
  17. package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +29 -0
  18. package/dist/agent-src/contexts/subagent-configuration.md +69 -7
  19. package/dist/agent-src/rules/autonomous-execution.md +1 -1
  20. package/dist/agent-src/rules/command-suggestion-policy.md +33 -0
  21. package/dist/agent-src/rules/design-fidelity.md +1 -1
  22. package/dist/agent-src/rules/design-review-after-ui-write.md +36 -14
  23. package/dist/agent-src/rules/evaluator-independence.md +27 -5
  24. package/dist/agent-src/rules/git-history-discipline.md +5 -1
  25. package/dist/agent-src/rules/missing-skill-recovery.md +89 -0
  26. package/dist/agent-src/rules/ui-audit-gate.md +34 -12
  27. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +33 -0
  28. package/dist/agent-src/scripts/gate_execute.ts +395 -0
  29. package/dist/agent-src/scripts/resume_probe.ts +422 -0
  30. package/dist/agent-src/scripts/roadmap_gates.ts +138 -4
  31. package/dist/agent-src/scripts/update_roadmap_progress.ts +70 -4
  32. package/dist/agent-src/skills/ai-council/SKILL.md +61 -0
  33. package/dist/agent-src/skills/authz-review/SKILL.md +4 -0
  34. package/dist/agent-src/skills/design-system-capture/SKILL.md +4 -0
  35. package/dist/agent-src/skills/design-system-capture/references/snapshot-preference-order.md +77 -0
  36. package/dist/agent-src/skills/experiment-loop/SKILL.md +116 -0
  37. package/dist/agent-src/skills/experiment-loop/evals/output-schema.yml +27 -0
  38. package/dist/agent-src/skills/experiment-loop/evals/triggers.json +17 -0
  39. package/dist/agent-src/skills/experiment-loop/references/pivot-ladder.md +54 -0
  40. package/dist/agent-src/skills/experiment-loop/references/protocol.md +61 -0
  41. package/dist/agent-src/skills/experiment-loop/references/register-format.md +66 -0
  42. package/dist/agent-src/skills/merge-conflicts/SKILL.md +4 -0
  43. package/dist/agent-src/skills/prompt-optimizer/SKILL.md +17 -1
  44. package/dist/agent-src/skills/roadmap-management/SKILL.md +2 -2
  45. package/dist/agent-src/skills/roadmap-writing/SKILL.md +5 -5
  46. package/dist/agent-src/skills/subagent-orchestration/schemas/team-review-status.json +8 -3
  47. package/dist/agent-src/skills/systematic-debugging/SKILL.md +4 -0
  48. package/dist/agent-src/skills/threat-modeling/SKILL.md +3 -0
  49. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  50. package/dist/agent-src/templates/roadmaps.md +43 -3
  51. package/dist/cli/registry.js +1 -0
  52. package/dist/cli/registry.js.map +1 -1
  53. package/dist/cli-delegate/{chunk-K34QTSD5.js → chunk-7PZ4FSZB.js} +368 -191
  54. package/dist/cli-delegate/{chunk-55WZN5VF.js → chunk-EVTY7ISR.js} +2 -2
  55. package/dist/cli-delegate/{chunk-O52B4F5S.js → chunk-F3YUF7SA.js} +1 -1
  56. package/dist/cli-delegate/{chunk-LKZ5DOYW.js → chunk-XURJ4AQC.js} +11 -3
  57. package/dist/cli-delegate/cmd_conformance.js +224 -104
  58. package/dist/cli-delegate/cmd_doctor.js +2 -2
  59. package/dist/cli-delegate/cmd_export.js +2 -2
  60. package/dist/cli-delegate/cmd_handoff.js +1 -1
  61. package/dist/cli-delegate/cmd_prune.js +2 -2
  62. package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
  63. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  64. package/dist/cli-delegate/cmd_sync.js +2 -2
  65. package/dist/cli-delegate/cmd_uninstall.js +2 -2
  66. package/dist/cli-delegate/cmd_validate.js +2 -2
  67. package/dist/discovery/deprecation-report.md +1 -1
  68. package/dist/discovery/discovery-manifest.json +73 -30
  69. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  70. package/dist/discovery/discovery-manifest.summary.md +3 -3
  71. package/dist/discovery/dormancy-report.md +1 -1
  72. package/dist/discovery/orphan-report.md +1 -1
  73. package/dist/discovery/packs.json +7 -5
  74. package/dist/discovery/trust-report.md +3 -3
  75. package/dist/discovery/workspaces.json +6 -4
  76. package/dist/hooks/dispatch.js +2653 -1365
  77. package/dist/install/install.mjs +12 -4
  78. package/dist/mcp/registry-manifest.json +3 -3
  79. package/dist/mcp/server.json +2 -2
  80. package/dist/mcp/server.mjs +2679 -887
  81. package/dist/router.json +1 -1
  82. package/dist/scripts/condense.js +1 -1
  83. package/dist/scripts/condense.js.map +1 -1
  84. package/dist/server/schemas/settings.js +2 -2
  85. package/dist/server/schemas/settings.js.map +1 -1
  86. package/dist/ui/assets/{index-B0aMJnuz.js → index-C1pBRm1f.js} +2 -2
  87. package/dist/ui/assets/index-C1pBRm1f.js.map +1 -0
  88. package/dist/ui/index.html +1 -1
  89. package/docs/guidelines/design-fidelity-mechanics.md +21 -0
  90. package/docs/guidelines/design-handover-extraction.md +87 -0
  91. package/docs/guidelines/prompt-templates.md +24 -1
  92. package/package.json +1 -1
  93. package/src/agent-src/scripts/archive_completed_roadmaps.ts +33 -0
  94. package/src/agent-src/scripts/gate_execute.ts +395 -0
  95. package/src/agent-src/scripts/resume_probe.ts +422 -0
  96. package/src/agent-src/scripts/roadmap_gates.ts +138 -4
  97. package/src/agent-src/scripts/update_roadmap_progress.ts +70 -4
  98. package/src/config/ci-local-parity.yml +13 -0
  99. package/src/config/evaluator-budgets.json +9 -8
  100. package/src/config/gate-coverage.yml +140 -12
  101. package/src/config/gate-violation-baselines.json +9 -3
  102. package/src/config/hook-token-budget.json +8 -1
  103. package/src/config/rule-activation-census.json +41 -0
  104. package/src/config/rule-stub-ceilings.json +52 -0
  105. package/src/scripts/_dispatch.bash +4 -0
  106. package/src/scripts/_lib/bench_ab_complexity.ts +439 -0
  107. package/src/scripts/_lib/bench_ab_safety_tier.ts +142 -0
  108. package/src/scripts/_lib/bench_ab_scoring_v2.ts +28 -3
  109. package/src/scripts/_lib/bench_ab_search_adherence.ts +423 -0
  110. package/src/scripts/_lib/bench_ab_size_claim.ts +206 -0
  111. package/src/scripts/_lib/bench_ab_workspace.ts +111 -0
  112. package/src/scripts/_lib/evaluator_contract.ts +159 -0
  113. package/src/scripts/_lib/gh_transient.ts +80 -0
  114. package/src/scripts/_lib/release_material.ts +16 -5
  115. package/src/scripts/_lib/review_independence.ts +111 -0
  116. package/src/scripts/_lib/session_register.ts +14 -0
  117. package/src/scripts/_lib/skill_catalogue.ts +278 -5
  118. package/src/scripts/_lib/skill_catalogue_series.ts +411 -0
  119. package/src/scripts/_lib/tier_budget_routing.ts +31 -331
  120. package/src/scripts/_lib/turn_end_refusals.ts +482 -0
  121. package/src/scripts/ai_council/_py_json.ts +96 -0
  122. package/src/scripts/ai_council/cli_call_budget.ts +357 -0
  123. package/src/scripts/ai_council/clients.ts +101 -164
  124. package/src/scripts/ai_council/config.ts +25 -11
  125. package/src/scripts/ai_council/probe_store.ts +196 -0
  126. package/src/scripts/ai_council/qualification.ts +425 -0
  127. package/src/scripts/ai_council/qualification_wiring.ts +243 -0
  128. package/src/scripts/ai_council/quorum_wiring.ts +211 -0
  129. package/src/scripts/ai_team/team_dispatch.ts +40 -6
  130. package/src/scripts/bench_ab_v2_complexity.ts +252 -0
  131. package/src/scripts/bench_ab_v2_run.ts +3 -42
  132. package/src/scripts/bench_ab_v2_safety.ts +296 -0
  133. package/src/scripts/bench_ab_v2_search.ts +343 -0
  134. package/src/scripts/bench_ab_v2_stats.ts +191 -0
  135. package/src/scripts/build_archive_index.ts +383 -0
  136. package/src/scripts/capture_skill_catalogue.ts +369 -4
  137. package/src/scripts/check_evaluator_schema.ts +249 -0
  138. package/src/scripts/check_pr_ci_current.ts +409 -0
  139. package/src/scripts/check_review_schema.ts +195 -0
  140. package/src/scripts/check_rule_activation_census.ts +451 -0
  141. package/src/scripts/check_rule_stub_ceiling.ts +1128 -0
  142. package/src/scripts/check_source_size_budget.ts +428 -0
  143. package/src/scripts/check_suppression_hygiene.ts +5 -1
  144. package/src/scripts/condense.ts +1 -1
  145. package/src/scripts/cost/backfill_rates.mjs +298 -0
  146. package/src/scripts/cost/budget.mjs +12 -32
  147. package/src/scripts/cost_summary.ts +25 -0
  148. package/src/scripts/council_cli.ts +141 -191
  149. package/src/scripts/dispatch_r2_reviewer.ts +8 -1
  150. package/src/scripts/external_sources_denylist.json +1 -0
  151. package/src/scripts/hook_manifest.yaml +99 -6
  152. package/src/scripts/hooks/concern_registry.ts +6 -0
  153. package/src/scripts/hooks/dispatch_hook.ts +43 -1
  154. package/src/scripts/hooks/interruption_ledger_hook.ts +256 -0
  155. package/src/scripts/hooks/ship_diff_volume_hook.ts +180 -0
  156. package/src/scripts/hooks/skill_route_hook.ts +226 -0
  157. package/src/scripts/hooks/turn_end_gate_hook.ts +119 -26
  158. package/src/scripts/hooks/ui_route_nudge_hook.ts +6 -3
  159. package/src/scripts/hooks_doctor.ts +112 -0
  160. package/src/scripts/interruption_report.ts +443 -0
  161. package/src/scripts/lint_design_antipattern_parity.ts +40 -6
  162. package/src/scripts/lint_evidence_artifacts.ts +359 -0
  163. package/src/scripts/lint_harvest_provenance.ts +35 -1
  164. package/src/scripts/lint_roadmap_blockers.ts +65 -0
  165. package/src/scripts/lint_roadmap_complexity.ts +7 -1
  166. package/src/scripts/lint_rule_skill_pack_reach.ts +43 -0
  167. package/src/scripts/lint_skill_router_head.ts +21 -0
  168. package/src/scripts/mcp_server/consumer_tool_catalog.json +26 -3
  169. package/src/scripts/mcp_server/tool_catalog_source.ts +6 -24
  170. package/src/scripts/mcp_server/tools.ts +87 -0
  171. package/src/scripts/release.ts +43 -58
  172. package/src/scripts/routing_doctor.ts +50 -13
  173. package/src/scripts/rule_activation_census.ts +292 -0
  174. package/src/scripts/schemas/command.schema.json +1 -1
  175. package/src/scripts/schemas/evaluator-output.schema.json +60 -0
  176. package/src/scripts/schemas/review-findings.schema.json +31 -0
  177. package/src/scripts/schemas/skill.schema.json +18 -1
  178. package/src/scripts/self_review_gate.ts +6 -0
  179. package/src/scripts/session_register_hook.ts +30 -1
  180. package/src/scripts/sync_pr_branch.ts +271 -0
  181. package/src/scripts/test_council_qualification.ts +213 -0
  182. package/src/scripts/trigger_coverage.ts +224 -3
  183. package/src/server/schemas/settings.ts +2 -2
  184. package/dist/ui/assets/index-B0aMJnuz.js.map +0 -1
  185. package/src/config/budget-routing.json +0 -7
@@ -180,11 +180,11 @@ class RoadmapStats {
180
180
  }
181
181
 
182
182
  get open_blockers(): Blocker[] {
183
- return this.blockers.filter((b) => b.status !== 'resolved');
183
+ return this.blockers.filter((b) => !blocker_is_resolved(b));
184
184
  }
185
185
 
186
186
  get resolved_blockers(): Blocker[] {
187
- return this.blockers.filter((b) => b.status === 'resolved');
187
+ return this.blockers.filter((b) => blocker_is_resolved(b));
188
188
  }
189
189
 
190
190
  get done(): number {
@@ -365,6 +365,49 @@ interface Blocker {
365
365
  * costs one enum entry and turns a silent swallow into a rendered field.
366
366
  */
367
367
  question: string;
368
+ /**
369
+ * Gate taxonomy: `0` auto-run · `1` budget-preauthorized · `2`
370
+ * consent-once · `3` human-only. Empty string when the entry declares
371
+ * none, and an absent class READS AS 3 at every consumer.
372
+ *
373
+ * The default is the safe end on purpose. An agent that inferred the class
374
+ * at runtime could promote a Hard-Floor gate into something it executes;
375
+ * authoring it into the file means a misclassification is a reviewed edit,
376
+ * and `lint_roadmap_blockers` refuses a runnable class that names no
377
+ * command.
378
+ */
379
+ blockerClass: string;
380
+ /** The command a class-0/1 entry is resolved by running. */
381
+ run: string;
382
+ /** Spend estimate for class 1 — recorded, never enforced by the lint. */
383
+ budget: string;
384
+ }
385
+
386
+ /**
387
+ * Is this blocker settled?
388
+ *
389
+ * A PREFIX test, not equality, and the difference was measured: authors write
390
+ * the resolution into the status line — `**Status:** RESOLVED 2026-08-17 —
391
+ * **option (b)**, narrowed to two rules` — and an equality check matches
392
+ * neither `open` nor `resolved`, so the entry falls through to open. Found
393
+ * 2026-08-17 in `road-to-mixed-trigger-activation-cost`: a decision taken two
394
+ * days earlier was still being rendered by `agent-config gates` as a live one
395
+ * the reader owed an answer to, and it inflated every blocker count in the
396
+ * dashboard by one.
397
+ *
398
+ * `lint_roadmap_blockers` already read the same field as a prefix
399
+ * (`/^-\s*\*\*Status:\*\*\s*resolved/i`), so the two surfaces disagreed about
400
+ * what "resolved" means — the lint exempted the entry from its decidability
401
+ * ratchet while the dashboard counted it open.
402
+ */
403
+ function blocker_is_resolved(b: Blocker): boolean {
404
+ return /^resolved\b/.test(_strip(b.status).toLowerCase());
405
+ }
406
+
407
+ /** Class of a blocker, with the absent-field default applied. */
408
+ function blocker_class(b: Blocker): '0' | '1' | '2' | '3' {
409
+ const c = _strip(b.blockerClass).split(/[\s—–-]/)[0] as string;
410
+ return c === '0' || c === '1' || c === '2' ? c : '3';
368
411
  }
369
412
 
370
413
  // Strip fenced code blocks before blocker detection — a roadmap that shows the
@@ -380,8 +423,23 @@ function _stripFencedCode(text: string): string {
380
423
  return text.replace(FENCED_CODE_RE, (m) => '\n'.repeat(_splitlines(m).length));
381
424
  }
382
425
 
383
- const BLOCKER_FIELD_RE =
384
- /^-[ \t]*\*\*(Status|Owner|Blocks|Resolved when|What to do|Recommendation|If you do nothing|Question):\*\*/i;
426
+ /**
427
+ * Terminator for a blocker field's continuation lines — ANY `- **Label:**`
428
+ * bullet, not a closed list of known labels.
429
+ *
430
+ * It used to enumerate eight names, which silently diverged from the contract
431
+ * the two call sites below already document ("up to the next `- **Field:**`
432
+ * marker"): a bullet whose label was not on the list did not terminate, so the
433
+ * PREVIOUS field kept absorbing it and the dashboard rendered the two as one
434
+ * run-on sentence. Measured when `- **Options:**` and `- **Side finding:**`
435
+ * first appeared in a blocker — a reader saw the side finding as part of
436
+ * `Resolved when`, i.e. the generator changed what a field meant.
437
+ *
438
+ * A closed list is the wrong shape for a terminator: every future field name
439
+ * would have to be added here to avoid corrupting the field above it, and the
440
+ * failure is silent in generated output nobody edits by hand.
441
+ */
442
+ const BLOCKER_FIELD_RE = /^-[ \t]*\*\*[^*]+:\*\*/;
385
443
 
386
444
  /** Strip an inline `<!-- comment -->` and trim. */
387
445
  function _stripComment(s: string): string {
@@ -493,6 +551,9 @@ function parse_blockers(raw_text: string): Blocker[] {
493
551
  recommendation: _blockerField(body, 'Recommendation') ?? '',
494
552
  ifNothing: _blockerField(body, 'If you do nothing') ?? '',
495
553
  question: _blockerField(body, 'Question') ?? '',
554
+ blockerClass: _blockerField(body, 'Class') ?? '',
555
+ run: _blockerField(body, 'Run') ?? '',
556
+ budget: _blockerField(body, 'Budget') ?? '',
496
557
  });
497
558
  }
498
559
  }
@@ -527,6 +588,9 @@ function parse_blockers(raw_text: string): Blocker[] {
527
588
  recommendation: '',
528
589
  ifNothing: '',
529
590
  question: '',
591
+ blockerClass: '',
592
+ run: '',
593
+ budget: '',
530
594
  });
531
595
  }
532
596
  return blockers;
@@ -1172,6 +1236,8 @@ export {
1172
1236
  is_roadmap_candidate,
1173
1237
  count_checkboxes,
1174
1238
  parse_blockers,
1239
+ blocker_class,
1240
+ blocker_is_resolved,
1175
1241
  blocker_needs_user,
1176
1242
  parse_roadmap,
1177
1243
  bar,
@@ -184,6 +184,19 @@ ci_only:
184
184
  # worth remote enforcement, OR it should simply be added to a workflow. Prefer
185
185
  # adding it: a local-only gate is the direction that let real defects merge.
186
186
  local_only:
187
+ - id: check_pr_ci_current
188
+ class: pre-push-scoped
189
+ reason: >
190
+ Compares the local branch tip against the open PR head and against the SHA
191
+ the PR's checks actually ran on. In CI that comparison is tautological by
192
+ construction — a workflow runs ON the PR head, so local, PR head and checks
193
+ head are the same commit and the gate can only ever pass. Its whole subject
194
+ is the window BEFORE a push (unpushed work, a green that ran against the
195
+ pre-fix commit, a PR head the checkout cannot reach because Update branch
196
+ was pressed), and that window does not exist inside a run. Declared rather
197
+ than wired: there is no remote equivalent to write, which is the distinction
198
+ this list exists to record. Sibling `check_branch_freshness` is a no-op in
199
+ CI for the same reason — the forge owns mergeability there.
187
200
  - id: roadmap_progress_hook
188
201
  class: git-hook-scoped
189
202
  reason: >
@@ -58,17 +58,18 @@
58
58
  "deterministic": false
59
59
  },
60
60
  "cli_help_command_count": {
61
- "max": 98,
62
- "last_measured": 98,
63
- "last_measured_at": "2026-08-15",
61
+ "max": 99,
62
+ "last_measured": 99,
63
+ "last_measured_at": "2026-08-17",
64
64
  "method": "registry enumeration — count of `{ name: '...', disposition` entries in src/cli/registry.ts (NOT --help prose parsing; methods historically disagreed 74 vs 76 vs 79)",
65
- "baseline_note": "80 → 81 on 2026-08-03 (release 9.15.0 gate). This RECORDS growth that already shipped through its own reviewed PR; it does not authorize new surface. The +1 is f02db54c2 (fix(cli): register routing:doctor in the CLI registry) — the command existed, only its registry entry was missing, and the registering PR did not move this budget. The gate did its job again: breach WARNS on main and BLOCKS on release, which is the conscious-decision point rather than a silently raised cap. Re-measured 2026-08-03 via the pinned method: 81. Headroom stays zero by design — the next registry addition must move this number in its own PR. 81 → 82 on 2026-08-04: the `handoff` subcommand (road-to-agent-handoff-resume Phase 4) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 82. 82 → 84 on 2026-08-04: the `route:explain` + `route:audit` verbs (routing-correctness validation surface) register in the same PR that adds the surface. Re-measured via the pinned method: 84. 84 → 85 on 2026-08-05: the `settings:set` verb (road-to-zero-ceremony-settings Phase 2 — the one agent-reachable settings writer) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 85. 85 → 86 on 2026-08-06: the `conformance:behavior` verb (road-to-agent-behavior-conformance Phase 5 — replays the local transcript store through the four mechanised conformance checks) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 86. 86 → 88 on 2026-08-07: the `sessions:list` + `sessions:claim` verbs (road-to-parallel-session-coordination — the shell surface of the shared session register, needed because /roadmap:next is command markdown and can only reach the register through a shell call) register in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 88. 88 → 89 on 2026-08-08: the `council:status` verb registers in the same PR that adds the surface, per this record's own contract. It exists because an agent in a consumer project announced \"kein Council konfiguriert (keine .agent-settings.yml)\" and substituted a weaker path while the council was configured user-globally the whole time — there was no verb that answered availability, so the only route was an inference from a filename, and the intuitive filename is the one location ADR-104 guarantees is never consulted. Re-measured via the pinned method: 89. 89 → 91 on 2026-08-08: the `self-repair:status` + `self-repair:release` verbs (the queued-defect reader and the single Hard-Floor-gated outward step of the self-repair loop — the loop is deliberately unable to publish unless a human runs the release command) register in the same PR that adds the surface, per this record's own contract. Landed alongside the +1 council:status move in the same branch. Re-measured via the pinned method: 91. 91 → 94 on 2026-08-08: the `packs:active`, `settings:get` and `mcp:available` verbs (road-to-capability-answerability Phase 2 — the three capability probes for the gaps that had none) register in the same PR that adds the surface, per this record's own contract. Each answers a question that was previously only inferable from a filename: which packs are active and from which file, what a setting resolves to and which file set it, and what is declared in mcp.json versus what can actually launch. 94 -> 95 in the same PR: `brand:status` (Phase 2.4 — brand-layer presence, which the rule depended on and no surface could answer). Re-measured twice via the pinned method on the unchanged tree: 95/95. 95 → 96 on 2026-08-10: the `session:recycle` verb (road-to-token-economy-recycling Phase 2.2 — the deterministic producer of the main-session recycle envelope; the recycle advisory names this exact verb, so it must exist as a shell surface) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 96. 96 -> 97 on 2026-08-10: the `gates` verb registers in the same PR that adds the surface, per this record's own contract. It exists because the blocker fields the dashboard already parses (Owner, What to do, Resolved when — CI-enforced by lint_roadmap_blockers) had no owner-filtered reader: the dashboard counts the open blockers across a 600-line file and links each to a per-roadmap anchor, so \"which of these are mine\" was a manual read of every anchor. The same inference-from-a-filename shape as the council:status entry above, one layer up. This branch and the session:recycle branch each measured 96 independently and landed on the same number for different verbs; the merge makes it 97, re-measured twice via the pinned method on the merged tree: 97/97. 97 -> 98 on 2026-08-15: the `workspace:doctor` verb (road-to-inbox-harvest-2026-08-c-workspace-identity Phase 3 — the read-only report over the new `workspaceIdentity()` resolver) registers in the same PR that adds the surface, per this record's own contract. It exists for the same inference-from-a-filename shape as the council:status and gates entries above, one layer down: the tree answered \"where am I\" in eight ad-hoc places across six files, and the 12.0.0 span shipped the same worktree misclassification twice, so no verb could be asked which checkout is the main one. Read-only by construction — the roadmap's Non-goals put worktree disposal out of scope and its Risk 3 names \"the pressure read becomes a disposal path by accident\" as the failure to design against, so the command carries no --fix and no --prune. Re-measured twice via the pinned method on the unchanged tree: 98/98."
65
+ "baseline_note": "80 → 81 on 2026-08-03 (release 9.15.0 gate). This RECORDS growth that already shipped through its own reviewed PR; it does not authorize new surface. The +1 is f02db54c2 (fix(cli): register routing:doctor in the CLI registry) — the command existed, only its registry entry was missing, and the registering PR did not move this budget. The gate did its job again: breach WARNS on main and BLOCKS on release, which is the conscious-decision point rather than a silently raised cap. Re-measured 2026-08-03 via the pinned method: 81. Headroom stays zero by design — the next registry addition must move this number in its own PR. 81 → 82 on 2026-08-04: the `handoff` subcommand (road-to-agent-handoff-resume Phase 4) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 82. 82 → 84 on 2026-08-04: the `route:explain` + `route:audit` verbs (routing-correctness validation surface) register in the same PR that adds the surface. Re-measured via the pinned method: 84. 84 → 85 on 2026-08-05: the `settings:set` verb (road-to-zero-ceremony-settings Phase 2 — the one agent-reachable settings writer) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 85. 85 → 86 on 2026-08-06: the `conformance:behavior` verb (road-to-agent-behavior-conformance Phase 5 — replays the local transcript store through the four mechanised conformance checks) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 86. 86 → 88 on 2026-08-07: the `sessions:list` + `sessions:claim` verbs (road-to-parallel-session-coordination — the shell surface of the shared session register, needed because /roadmap:next is command markdown and can only reach the register through a shell call) register in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 88. 88 → 89 on 2026-08-08: the `council:status` verb registers in the same PR that adds the surface, per this record's own contract. It exists because an agent in a consumer project announced \"kein Council konfiguriert (keine .agent-settings.yml)\" and substituted a weaker path while the council was configured user-globally the whole time — there was no verb that answered availability, so the only route was an inference from a filename, and the intuitive filename is the one location ADR-104 guarantees is never consulted. Re-measured via the pinned method: 89. 89 → 91 on 2026-08-08: the `self-repair:status` + `self-repair:release` verbs (the queued-defect reader and the single Hard-Floor-gated outward step of the self-repair loop — the loop is deliberately unable to publish unless a human runs the release command) register in the same PR that adds the surface, per this record's own contract. Landed alongside the +1 council:status move in the same branch. Re-measured via the pinned method: 91. 91 → 94 on 2026-08-08: the `packs:active`, `settings:get` and `mcp:available` verbs (road-to-capability-answerability Phase 2 — the three capability probes for the gaps that had none) register in the same PR that adds the surface, per this record's own contract. Each answers a question that was previously only inferable from a filename: which packs are active and from which file, what a setting resolves to and which file set it, and what is declared in mcp.json versus what can actually launch. 94 -> 95 in the same PR: `brand:status` (Phase 2.4 — brand-layer presence, which the rule depended on and no surface could answer). Re-measured twice via the pinned method on the unchanged tree: 95/95. 95 → 96 on 2026-08-10: the `session:recycle` verb (road-to-token-economy-recycling Phase 2.2 — the deterministic producer of the main-session recycle envelope; the recycle advisory names this exact verb, so it must exist as a shell surface) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 96. 96 -> 97 on 2026-08-10: the `gates` verb registers in the same PR that adds the surface, per this record's own contract. It exists because the blocker fields the dashboard already parses (Owner, What to do, Resolved when — CI-enforced by lint_roadmap_blockers) had no owner-filtered reader: the dashboard counts the open blockers across a 600-line file and links each to a per-roadmap anchor, so \"which of these are mine\" was a manual read of every anchor. The same inference-from-a-filename shape as the council:status entry above, one layer up. This branch and the session:recycle branch each measured 96 independently and landed on the same number for different verbs; the merge makes it 97, re-measured twice via the pinned method on the merged tree: 97/97. 97 -> 98 on 2026-08-15: the `workspace:doctor` verb (road-to-inbox-harvest-2026-08-c-workspace-identity Phase 3 — the read-only report over the new `workspaceIdentity()` resolver) registers in the same PR that adds the surface, per this record's own contract. It exists for the same inference-from-a-filename shape as the council:status and gates entries above, one layer down: the tree answered \"where am I\" in eight ad-hoc places across six files, and the 12.0.0 span shipped the same worktree misclassification twice, so no verb could be asked which checkout is the main one. Read-only by construction — the roadmap's Non-goals put worktree disposal out of scope and its Risk 3 names \"the pressure read becomes a disposal path by accident\" as the failure to design against, so the command carries no --fix and no --prune. Re-measured twice via the pinned method on the unchanged tree: 98/98. 98 -> 99 on 2026-08-17: the `council:quota` verb (road-to-council-quota-accounting-truth Phase 2) registers in the same PR that adds the surface, per this record's own contract. Exactly the inference-from-a-filename shape as the council:status entry above, one layer down: `cmd_quota` had existed in council_cli.ts and been reachable only via `scripts-run`, so the documented recovery path for an exhausted shared counter -- read the usage, reset one provider -- did not resolve through `agent-config`, and an operator debugging a quota failure met `unknown command`. The verb is read-only apart from the pre-existing `--reset <provider>`, which already required `--confirm`. Re-measured twice via the pinned method on the unchanged tree: 99/99."
66
66
  },
67
67
  "mcp_public_tool_count": {
68
- "max": 19,
69
- "last_measured": 19,
70
- "last_measured_at": "2026-08-02",
71
- "method": "tools/list length on the stdio server (post-ADR-132: implemented tools only)"
68
+ "max": 20,
69
+ "last_measured": 20,
70
+ "last_measured_at": "2026-08-17",
71
+ "method": "tools/list length on the stdio server (post-ADR-132: implemented tools only)",
72
+ "baseline_note": "19 → 20 on 2026-08-17 (release 13.0.0 gate). The +1 is 866bd9861 (feat(mcp): recover what the host dropped — real ranker tool + a route to it, road-to-inbox-harvest-2026-08-d-runtime-skill-routing Phase 4): `suggest_skill_for_task` was promoted from a discovery STUB to a real ALLOWLIST entry and removed from STUB_TOOLS. Worth stating precisely, because the consumer-visible NAME did not grow — the stub already published that name and its `task` + `limit` input schema, which the promotion carried over verbatim. What grew is the count of tools tools/list actually returns, which is exactly what this metric measures post-ADR-132: implemented tools only. Same shape as the cli_help_command_count history above — this RECORDS growth that already shipped through its own reviewed PR (866bd9861 is on main), it does not authorize new surface. The registering PR did not move this budget, so the gate did its job: the breach warned on main and blocked on the release ref, which is the conscious-decision point rather than a silently raised cap. Re-measured twice via the pinned method on a freshly built dist/mcp/server.mjs: 20/20. Headroom stays zero by design — the next tool that leaves STUB_TOOLS must move this number in its own PR."
72
73
  }
73
74
  }
74
75
  }
@@ -40,21 +40,37 @@
40
40
  # visible, or it repeats the sin it exists to catch.
41
41
  # canary optional mutation recipe (Phase 7) — see CANARY below
42
42
  #
43
- # COVERAGE — THE HONEST DENOMINATOR (re-measured 2026-08-13)
43
+ # COVERAGE — THE HONEST DENOMINATOR (re-measured 2026-08-17)
44
44
  # ----------------------------------------------------------
45
- # 259 `lint_*` / `check_*` / `audit_*` scripts live under `src/scripts/`
45
+ # 266 `lint_*` / `check_*` / `audit_*` scripts live under `src/scripts/`
46
46
  # (the count is computed, not asserted — `check_gate_coverage.count_gate_scripts`).
47
- # **36 of them emit the machine-readable `scanned: <N>` line under their
48
- # CI-identical argv, and all 36 are listed below.** The manifest is therefore
49
- # complete with respect to its own contract, and covers ~14 % of the population.
50
- # (The 2026-08-05 reading said "32 … and all 31 are listed"; the 2026-08-10
51
- # reading said "33 … and all 33" while 34 entries were listed; the 2026-08-13
52
- # reading said "257 … 35 of them" and both halves were correct at the time.
53
- # Both halves are RECOUNTED here on 2026-08-15: 259 is what
54
- # `count_gate_scripts()` returns on the tree that carries this change — the
55
- # reading was 258 with `lint_scheduled_deprecations` absent — and 36 is
47
+ # **41 of them emit the machine-readable `scanned: <N>` line under their
48
+ # CI-identical argv, and 40 of those 41 are listed below.** The manifest is
49
+ # therefore complete with respect to its own contract for every gate that can
50
+ # carry a floor, and covers ~15 % of the population.
51
+ #
52
+ # The one emitter deliberately NOT listed is `lint_evidence_artifacts`, for the
53
+ # same reason `check_rule_projection_integrity` is not (see the paragraph
54
+ # below): it is diff-scoped, so it legitimately emits `scanned: 0` on any
55
+ # branch that adds no evidence artifact, and `min_scanned: 0` is the
56
+ # false-count shape this file rejects. Its ledger half IS discharged — it
57
+ # adopts `_lib/gate_ledger.ts` and `_lib/scan_scope.ts`, so
58
+ # `check_gate_completeness` counts it as an adopter.
59
+ #
60
+ # Counting emitters rather than rows is the point of stating both numbers: the
61
+ # 2026-08-05 and 2026-08-10 readings below were miscounts precisely because one
62
+ # number was updated and the other was not.
63
+ # (The 2026-08-05 reading said "32 … and all 31 are listed" and the 2026-08-10
64
+ # reading said "33 … and all 33" while 34 entries were listed — those two are
65
+ # the MISCOUNTS this paragraph exists to warn about, and they are listed here
66
+ # precisely because they were wrong, not correct-at-the-time. The 2026-08-13
67
+ # reading ("257 … 35 of them") and the 2026-08-15 reading ("259 … 36 of them")
68
+ # were both correct when written.
69
+ # Both halves are RECOUNTED here on 2026-08-16: 260 is what
70
+ # `count_gate_scripts()` returns on the tree that carries this change, and 37 is
56
71
  # `grep -c '^ - id:'` on this file. Neither number was derived by adding one to
57
- # the previous prose, which is the move the note below forbids.)
72
+ # the previous prose, which is the move the note below forbids — and the
73
+ # temptation was live, because this change does add exactly one of each.)
58
74
  #
59
75
  # A 2026-08-13 revision of this paragraph first wrote 256, derived as "255 before
60
76
  # this change plus the one it adds" — inside the sentence claiming to have
@@ -166,6 +182,99 @@ gates:
166
182
  the Taskfile parse degrades and the population silently collapses —
167
183
  cannot pass.
168
184
 
185
+ - id: check_rule_activation_census
186
+ argv: []
187
+ min_scanned: 100
188
+ corpus: "rule files under src/rules/ (117 at baseline)"
189
+ status: enforced
190
+ note: >
191
+ Floor set below the live count so a legitimate rule retirement does not red
192
+ the gate that watches activation. It is still far above a collapse: if the
193
+ rule root moved, this gate's own assertScanned fires first and the manifest
194
+ row is the second net. The gate exists because commit 33c7c20 moved nineteen
195
+ rules from path-scoped to always-on with nothing watching, so a version of
196
+ it that silently scanned zero would be the same failure twice.
197
+
198
+ - id: check_rule_stub_ceiling
199
+ argv: []
200
+ min_scanned: 100
201
+ corpus: "rule files under src/rules/ (117 at baseline; 44 carry a migration pointer)"
202
+ status: enforced
203
+ note: >
204
+ Scans the whole rule root and reports that count, not the 44 migrated rules
205
+ it ratchets — the denominator is what proves the root still exists, and a
206
+ floor on the migrated subset would fall as rules legitimately finish
207
+ migrating. Floor set below the live 117 so a rule retirement does not red
208
+ the gate. The gate's own assertScanned fires first if the root moves; this
209
+ row is the second net. It exists because the sibling census pins the corpus
210
+ total with a 2,000-token drift allowance, which is green while one rule
211
+ grows and another shrinks — measured at adoption, the corpus sat 411 tokens
212
+ above its pinned baseline with that gate green.
213
+ canary:
214
+ class: contract-violation
215
+ path: src/rules/zz-canary-stub-probe.md
216
+ content: |
217
+ ---
218
+ type: "auto"
219
+ ---
220
+
221
+ # ZZ canary probe
222
+
223
+ Body migrated to `guideline:zz-canary-target-that-does-not-exist`.
224
+ # A created rule is the create-only shape this gate is built for: it carries
225
+ # a migration pointer whose target resolves nowhere AND has no ceiling in the
226
+ # baseline, so it trips two independent axes. Without a recipe the gate would
227
+ # ship permanently UNPROVEN — a green run over a real corpus is not evidence
228
+ # that the reading changes the verdict, which is the whole point of the
229
+ # canary. The gate also carries a `--self-test` with seven rejecting cases;
230
+ # the two prove different things and neither replaces the other.
231
+
232
+ - id: check_review_schema
233
+ argv: []
234
+ min_scanned: 1
235
+ corpus: "committed review-findings ledgers under agents/evidence/release-findings/ (1 at baseline)"
236
+ status: enforced
237
+ canary:
238
+ class: contract-violation
239
+ path: agents/evidence/release-findings/zz-canary-probe.json
240
+ content: |
241
+ {
242
+ "schema_version": 1,
243
+ "release": "0.0.0-canary",
244
+ "review_independence": "same-family",
245
+ "acceptance_status": "accepted",
246
+ "assurance": "independent",
247
+ "reviewers": ["anthropic", "anthropic"],
248
+ "findings": []
249
+ }
250
+ note: >
251
+ Floor is 1 because the corpus genuinely holds one ledger today, and a
252
+ floor of 0 would let the directory empty out silently — the shape this
253
+ manifest exists to refuse. The canary plants the exact defect the gate was
254
+ built for: a same-family reviewer set claiming `accepted`. It must red.
255
+
256
+ - id: check_evaluator_schema
257
+ argv: []
258
+ min_scanned: 5
259
+ corpus: "registered evaluator x recorded verifier fixture pairs (6 at baseline)"
260
+ status: enforced
261
+ canary:
262
+ class: contract-violation
263
+ path: tests/fixtures/evaluator-output/lint_output_slop.degraded.stdout
264
+ content: |
265
+ planted canary: output with no clean sentinel and no file:line rows
266
+ note: >
267
+ The corpus is (evaluator x fixture) pairs, not evaluators, because the
268
+ mapping is what can be wrong and each flavour exercises a different branch
269
+ of it. The floor sits below the live count so retiring one fixture flavour
270
+ does not red the change that retires it, but above 3 so a corpus that
271
+ silently collapsed to green-only cannot pass — a green-only corpus cannot
272
+ distinguish a working adapter from one that hardcodes success, which is
273
+ the whole reason red and degraded fixtures are committed alongside.
274
+ The canary plants a `degraded` stdout with no matching `.exit`, so the
275
+ adapter reads exit 0 with an unparseable body: expected `unreadable`,
276
+ asserted `present` by the green expectation table -> the gate must fail.
277
+
169
278
  - id: check_suppression_hygiene
170
279
  argv: []
171
280
  min_scanned: 8
@@ -799,3 +908,22 @@ gates:
799
908
  `tests/scripts/lint_scheduled_deprecations.test.ts`, verified by mutation:
800
909
  widening the overdue threshold fails the overdue-by-construction case, and
801
910
  granting `allowEmpty` fails the zero-row case.
911
+
912
+ - id: check_source_size_budget
913
+ argv: []
914
+ min_scanned: 900
915
+ corpus: "TypeScript sources — src/**/*.ts excluding *.d.ts and *.test.ts, the same two kinds _lib/gate_population excludes (1,063 at baseline)"
916
+ status: enforced
917
+ # No `canary:` recipe, and the reason is the canary contract's own shape
918
+ # rather than a gap. The violation this gate detects is a file OVER 1,500
919
+ # lines, so a create-only recipe would have to inline 1,501 lines of content
920
+ # into this manifest — a plant larger than the manifest it lives in. It
921
+ # reports UNPROVEN rather than green, which is the honest state.
922
+ #
923
+ # Discrimination is proven instead by the gate's own `--self-test`, and it is
924
+ # stronger here than a single plant would be: six cases, three rejecting,
925
+ # covering a file one line over the ceiling, one line under, exactly at it
926
+ # (inclusive), a dead scan root, AND — the case a canary could not express —
927
+ # an already-listed file that GREW while the file COUNT stayed at one. That
928
+ # last case is what pins the metric as excess lines rather than a file count;
929
+ # a count-based ratchet passes it, and this gate must not.
@@ -23,9 +23,9 @@
23
23
  "note": "All 18 are `handoff_tier_mismatch`: a senior-tier skill links to a peer whose frontmatter carries no `tier` (reported as tier='unset') — e.g. build-buy-partner → adr-create, customer-research → po-discovery, gated-reach → threat-modeling. This is a tier-metadata backfill backlog on the LINKED-TO skills, not a broken link. The one genuinely dangling link revealed by the same repair (competitive-positioning → a non-existent ../analyze-reference-repo/SKILL.md; it is a command, not a skill) was FIXED in the repairing commit rather than baselined. Clearing this entry means backfilling `tier` on the linked-to skills, or deciding that `tier: unset` on a link target is not a violation and narrowing the rule."
24
24
  },
25
25
  "lint_roadmap_blockers:decidability": {
26
- "count": 34,
27
- "landed": "2026-08-15",
28
- "note": "Open blocker entries that are not yet DECIDABLE by the person who owns them — missing `Recommendation:` (which option and why), missing `If you do nothing:` (the cost of the non-decision), or a `What to do:` carrying no command, path or enumerated option set. Revealed by adding the decidability half to lint_roadmap_blockers, not created by it: the five original fields describe a situation and never made it actionable, so a blocker could pass the contract while handing the analysis back to the person least equipped to redo it. Measured at introduction: 36 open entries, ALL 36 undecidable, and 14 of the 46 total entries carry no command/path/option at all in `What to do`. Baselined rather than hard-failed because 36 findings nobody can clear in the change that reveals them is the gate-that-lands-as-N-instant-blockers failure this repository has refused before — the same reasoning as ci-parity:local-only above. First measured at 34 rather than 36 because the two blockers on the branch that introduced this (`ac3-false-positive-reading`, `publish-side-marker-invariant`) were rewritten to the new standard and then closed in the same change; a baseline above the truth silently permits new violations. Re-measured at 38 when the branch was re-pointed at current main, and the +4 is population growth rather than a loosening: main gained the four `road-to-inbox-harvest-2026-08-d-*` roadmaps (context-ledger, runtime-skill-routing, scheduled-deprecation, top-band-model-economy), each carrying one open blocker authored before this requirement existed. That is the honest number for the population this gate now scans — recording 34 against a 38-entry tree would have failed the gate on its own introducing commit and taught the next reader to distrust it. Lowering happens one blocker at a time, and every roadmap touched for other reasons is a chance to lower it. Lowered 38→37 by the scheduled-deprecation PR, which is exactly that shape: code-graph-removal-authorisation was rewritten to the new standard because the roadmap was being touched anyway. Its `If you do nothing:` was one of the few that was not the common no-cost answer — the release gate that PR ships would have refused the next major cut while the blocker stayed open. Recorded in the past tense because the maintainer then RESOLVED that blocker on the trunk by withdrawing the commitment, so nothing refuses a cut today. The lowering stands on the rewrite, not on the consequence, and the sentence is corrected rather than deleted because a baseline note arguing from a retracted state is how a ratchet's rationale rots. The 56-day expiry is the point, not a hazard: if this number has not moved in two months, the fields are not being written and the gate should fail until someone decides whether the requirement is real. Lowered 37→34 when the scheduled-deprecation branch merged current main: three blockers were resolved or rewritten on the trunk in the meantime, so the drop is a genuine drain plus a merge, not a loosening. Re-tightened here rather than left loose because a ratchet that lags its own tree permits violations back in silently.",
26
+ "count": 26,
27
+ "landed": "2026-08-16",
28
+ "note": "Open blocker entries that are not yet DECIDABLE by the person who owns them — missing `Recommendation:` (which option and why), missing `If you do nothing:` (the cost of the non-decision), or a `What to do:` carrying no command, path or enumerated option set. Revealed by adding the decidability half to lint_roadmap_blockers, not created by it: the five original fields describe a situation and never made it actionable, so a blocker could pass the contract while handing the analysis back to the person least equipped to redo it. Measured at introduction: 36 open entries, ALL 36 undecidable, and 14 of the 46 total entries carry no command/path/option at all in `What to do`. Baselined rather than hard-failed because 36 findings nobody can clear in the change that reveals them is the gate-that-lands-as-N-instant-blockers failure this repository has refused before — the same reasoning as ci-parity:local-only above. First measured at 34 rather than 36 because the two blockers on the branch that introduced this (`ac3-false-positive-reading`, `publish-side-marker-invariant`) were rewritten to the new standard and then closed in the same change; a baseline above the truth silently permits new violations. Re-measured at 38 when the branch was re-pointed at current main, and the +4 is population growth rather than a loosening: main gained the four `road-to-inbox-harvest-2026-08-d-*` roadmaps (context-ledger, runtime-skill-routing, scheduled-deprecation, top-band-model-economy), each carrying one open blocker authored before this requirement existed. That is the honest number for the population this gate now scans — recording 34 against a 38-entry tree would have failed the gate on its own introducing commit and taught the next reader to distrust it. Lowering happens one blocker at a time, and every roadmap touched for other reasons is a chance to lower it. Lowered 38→37 by the scheduled-deprecation PR, which is exactly that shape: code-graph-removal-authorisation was rewritten to the new standard because the roadmap was being touched anyway. Its `If you do nothing:` was one of the few that was not the common no-cost answer — the release gate that PR ships would have refused the next major cut while the blocker stayed open. Recorded in the past tense because the maintainer then RESOLVED that blocker on the trunk by withdrawing the commitment, so nothing refuses a cut today. The lowering stands on the rewrite, not on the consequence, and the sentence is corrected rather than deleted because a baseline note arguing from a retracted state is how a ratchet's rationale rots. The 56-day expiry is the point, not a hazard: if this number has not moved in two months, the fields are not being written and the gate should fail until someone decides whether the requirement is real. Lowered 37→34 when the scheduled-deprecation branch merged current main: three blockers were resolved or rewritten on the trunk in the meantime, so the drop is a genuine drain plus a merge, not a loosening. Re-tightened here rather than left loose because a ratchet that lags its own tree permits violations back in silently. Lowered 34→29 on 2026-08-16 by rewriting six entries to the new standard (raw-capture-needs-host-env, telemetry-sample-size, repo-admin-and-usage, benchmark-spend, measurement-a-no-per-arm-builder-tier, measurement-b-no-renderable-lane-pair) in a PR whose only purpose was the drain — the first lowering not taken on the back of another change, which the note above says is the usual shape. Two findings from that pass are worth keeping. The tree measured 35 against a baseline of 34 BEFORE the drain, i.e. the ratchet was already red on main and nobody knew: this gate runs in `task ci`, no workflow invokes `task ci`, and the local run that would have shown it was read through a `tail`, which hid every row above the last six. And four of the six `If you do nothing:` answers are honestly that nothing degrades — that is the true answer for a held measurement, and writing it is what makes the entry decidable, so the field earns its place by being cheap to answer truthfully rather than by always carrying a cost. Lowered 29 → 27 on 2026-08-16 by the merge of this branch and the trunk drain above, and the arithmetic matters more than the delta: both sides rewrote entries from the same tree of 35 independently — the trunk six (recorded above), this branch two (deferred-finding-decision-reopen, spent-inbox-artifacts-await-deletion, rewritten because road-to-inbox-harvest-residuals was being touched anyway). The sets are disjoint, so the merged tree measures 27 and the gate itself reported the slack. Tightened rather than banked: 29 against a 27-entry tree would absorb the next two undecidable blockers without anything noticing, which is the lag this note has now corrected twice. Lowered 27 → 26 on 2026-08-16 by rewriting `picktier-wire-or-archive` (road-to-inbox-harvest-2026-08-d-top-band-model-economy), and this one is the usual shape the note describes rather than the dedicated-drain shape: the roadmap was being worked for its own Step 4.1 and the entry was rewritten because it was open on the desk. Worth recording for what writing the two fields SURFACED, which is the argument for the requirement: `Recommendation:` forced the question 'which option, and why' and the honest answer turned out not to be one of the three options at all — the entry defers to a converged council verdict, and the council cannot reach quorum (re-measured 0 of 2 the same day), so the actionable next step is a one-line fix to a user-global config file outside this repository. Five fields had described that situation accurately for two weeks without anyone noticing that the named blocker was infrastructure rather than judgement.",
29
29
  "falsifier": "./scripts-run src/scripts/lint_roadmap_blockers --quiet"
30
30
  },
31
31
  "lint_pack_boundaries": {
@@ -55,6 +55,12 @@
55
55
  "landed": "2026-08-12",
56
56
  "note": "Anti-regrowth ratchet for the settings disposition axis. 83 of 134 template leaves are classified `derivable` — the mechanism can decide from the situation, so the key should not exist — and each names the replacement that would decide instead (`lint_settings_classes` rejects the bare label, which is what keeps the class from becoming a synonym for \"inconvenient to keep\"). This is a DELETION QUEUE, not 83 deletions: a row stays until the mechanism it names actually exists, and deleting a key ahead of its mechanism is a silently-changed default rather than a simplification. Shrink-only from here, which is the anti-regrowth half — a NEW key classified `derivable` raises the number and reds, so a key whose answer the situation already carries cannot be added at all. The 56-day non-stagnation clause is the other half and is wanted here rather than tolerated: a queue that never drains is a stated direction nobody is walking. The queue opened at 88 of 140 on 2026-08-12 and drained to 83 of 134 the same day: six keys had no reader whatsoever (telegraph.speak_scope, chat_history.max_size_kb, chat_history.on_overflow, quality.wait_for_remote_ci, screenshots.data_bearing_gate, legal_review_prep.consented_at), five of them `derivable` and one a `consent` row reclassified because a published-egress confirmation is a Hard Floor no setting may lift. An unread key is the only deletion that cannot silently change a default, which is why that batch went first. The classification itself is docs/contracts/settings-classes.md.",
57
57
  "falsifier": "./scripts-run src/scripts/lint_settings_classes"
58
+ },
59
+ "check_source_size_budget": {
60
+ "count": 19611,
61
+ "landed": "2026-08-17",
62
+ "note": "Lowered 19,626 → 19,611 on 2026-08-17 by the release-gh-transient branch, and it is a LOWERING through extraction, never a raise — the −15 is against the trunk value, reproducible with `git stash && npx tsx src/scripts/check_source_size_budget.ts`. That branch hardened `release.ts` against GitHub's 5xx responses, which is exactly the kind of change that grows an already over-ceiling file: release.ts sits at 2,819 and carries 1,319 of this total. The retry loop, the transient classifier and the argv elider therefore went into a NEW module (`_lib/gh_transient.ts`, 74 lines, under the ceiling and contributing nothing here), leaving release.ts a 20-line wrapper. The payback came from the five call sites the wrapper replaced: each was a `run(['gh', …], {check, capture})` block spread over up to 17 lines, and each collapses to one `gh([…])` call. The ordering is the lesson and it matches the two entries below: extract, then measure — a wrapper written inline would have added ~20 lines and turned a hardening commit into a ratchet regression. Second lesson, learned the hard way on this same branch: pin the baseline ONCE, at the end. Two intermediate pins (19,606 then 19,612) made subsequent legitimate edits read as `N new` against numbers that had never been on the trunk.",
63
+ "falsifier": "./scripts-run src/scripts/check_source_size_budget --quiet"
58
64
  }
59
65
  }
60
66
  }
@@ -27,7 +27,9 @@
27
27
  "reread-guard": 1024,
28
28
  "reread-guard_reason": "one advisory line naming the earlier read / ranged alternative (roadmap 6.1)",
29
29
  "session-eol": 1024,
30
- "session-eol_reason": "one recycle-advisory line, once per session past the committed threshold (road-to-token-economy-recycling 3.2); recording itself emits nothing"
30
+ "session-eol_reason": "one recycle-advisory line, once per session past the committed threshold (road-to-token-economy-recycling 3.2); recording itself emits nothing",
31
+ "skill-route": 512,
32
+ "skill-route_reason": "road-to-inbox-harvest-2026-08-d-runtime-skill-routing 2.1: one line carrying at most three skill ids and their scores - pointers, never bodies. Registered BELOW the 1024 default deliberately: this row is the ceiling that keeps it a pointer line, so a later change that starts injecting descriptions fails the build instead of quietly doubling a per-turn slot that already carries nine other concerns under a 4096-byte sum cap"
31
33
  },
32
34
  "per_slot_sum_caps_bytes": {
33
35
  "_comment": "Cross-concern per-fire sum per slot (roadmap 3.1). session_start is one-time and may carry the restore payloads; per-turn slots stay tight.",
@@ -49,6 +51,11 @@
49
51
  "_comment": "Roadmap 5.3 + 6.4 registrations. These are BEHAVIOUR metrics (advisory adoption), not injection bytes - registered here because this file already carries the owner/review discipline for the same concerns.",
50
52
  "owner": "maintainer",
51
53
  "review_by": "2026-11-10",
54
+ "skill_route_pointer_rate": {
55
+ "definition": "road-to-inbox-harvest-2026-08-d-runtime-skill-routing 2.2: skill-route fires / user_prompt_submit events per session - the share of prompts where the deterministic ranker cleared its measured p90 floor and injected pointers. NO THRESHOLD IS COMMITTED, deliberately and by the house baseline-first rule: the TRIGGER is calibrated (top-1 >= 30/100, the p90 of 496 routing-matrix prompt lines, expected to fire on ~13.9%), the OUTCOME is not. Kill standard as for the sibling nudges: a nudge whose pointers are measurably ignored over the review window gets its floor raised or its line removed. Expected fire rate is the falsifier for the trigger half - a live rate far from 13.9% means the corpus is not representative of real prompts, which is a finding about the corpus and is published either way.",
56
+ "instrument": "rule-trips.json warn counters for the skill-route concern (fires) + conformance audits for whether the pointed-at skill was opened (adoption); HONEST GAP: no deterministic counter exists for 'the agent acted on the pointer' - the numerator is audit-carried and registered as such rather than implied to be automatic",
57
+ "threshold": "none committed before data - baseline first, threshold at review date (2026-11-10)"
58
+ },
52
59
  "full_rewrite_small_diff_rate": {
53
60
  "definition": "edit-shape fires / Write calls per session (the full-rewrite-with-small-diff rate). Kill criterion per the shipped nudge standard: a nudge whose verdicts are measurably ignored (adoption near zero over the review window, from conformance audits) gets its trigger tightened or the injected line removed.",
54
61
  "instrument": "rule-trips.json warn counters (the dispatcher already counts every warn per concern) + conformance audits for adoption"
@@ -0,0 +1,41 @@
1
+ {
2
+ "_comment": "Rule-activation ratchet baseline (road-to-mixed-trigger-activation-cost step 4.1). Two axes: the scoped/mixed ID SETS are pinned by identity, and the unconditional token total may only walk DOWN. Raising a number here to clear a failing check is the config-weakening move this repo blocks by construction \u2014 every raise needs a baseline_history entry whose reason is a real sentence. Regenerate with `check_rule_activation_census --write-baseline`; never hand-edit the counts.",
3
+ "measured_at_commit": "097ab6549",
4
+ "tokens_exact": true,
5
+ "unconditional_tokens": 103265,
6
+ "scoped_ids": [
7
+ "design-review-after-ui-write",
8
+ "no-roadmap-references",
9
+ "roadmap-progress-sync",
10
+ "rule-type-governance",
11
+ "skill-quality",
12
+ "source-confidentiality",
13
+ "source-of-truth",
14
+ "ui-audit-gate"
15
+ ],
16
+ "mixed_ids": [
17
+ "augment-edit-discipline",
18
+ "design-fidelity",
19
+ "doc-screenshot-hygiene",
20
+ "domain-adoption-policy",
21
+ "framework-neutrality-in-generic-skills",
22
+ "image-likeness-and-rights",
23
+ "laravel-translations",
24
+ "lethal-trifecta-guard",
25
+ "linked-projects-onboarding-gate",
26
+ "low-impact-corpus-privacy-floor",
27
+ "markdown-safe-codeblocks",
28
+ "onboarding-gate",
29
+ "persona-governance",
30
+ "php-coding",
31
+ "provider-lifecycle-discipline",
32
+ "roadmap-ci-steps-policy",
33
+ "settings-ask-protocol"
34
+ ],
35
+ "baseline_history": [
36
+ {
37
+ "date": "2026-08-17",
38
+ "reason": "Re-anchored WITH the authorized change, not to clear a red. road-to-mixed-trigger-activation-cost Phase 2, amendment option (b) narrowed to the two path-dominant per-edit rules: `design-review-after-ui-write` and `ui-audit-gate` had their `component` and `design token` keyword triggers removed as an authoring decision, so the emitter restores `paths:` for both. Scoped 6 -> 8, mixed 19 -> 17, unconditional corpus 107194 -> the value above. The direction is DOWN on the weight axis and the identity move is the authorized one; the maintainer asked for this entry in the same commit so the ratchet documents its own sanctioned change instead of reporting it as drift."
39
+ }
40
+ ]
41
+ }
@@ -0,0 +1,52 @@
1
+ {
2
+ "_comment": "Per-rule stub ceilings (road-to-rule-stub-projection Phase 2). One exact-BPE token ceiling per rule carrying a migration pointer. SHRINK-ONLY: a rule may fall below its ceiling freely, and may not rise above it. Raising a ceiling to clear a failing check is the config-weakening move this repo blocks by construction — every raise needs a `history` entry whose `reason` is a real sentence. The ceilings are a floor-plus-residue SNAPSHOT taken at adoption, not a target: a rule at its ceiling is not thereby correct, only not worse. Regenerate with `check_rule_stub_ceiling --write-baseline`; never hand-edit the counts.",
3
+ "measured_at_commit": "d8e2ee655",
4
+ "tokens_exact": true,
5
+ "ceilings": {
6
+ "active-remediation": 1601,
7
+ "analysis-skill-routing": 76,
8
+ "architecture": 955,
9
+ "artifact-drafting-protocol": 611,
10
+ "augment-edit-discipline": 137,
11
+ "autonomous-execution": 1633,
12
+ "brand-consistency": 133,
13
+ "broken-access-control": 743,
14
+ "cli-output-handling": 83,
15
+ "code-comment-discipline": 692,
16
+ "commit-conventions": 136,
17
+ "context-hygiene": 2447,
18
+ "copilot-routing": 128,
19
+ "decision-revisit-gate": 545,
20
+ "design-fidelity": 2245,
21
+ "devcontainer-routing": 128,
22
+ "docker-commands": 79,
23
+ "domain-adoption-policy": 497,
24
+ "framework-neutrality-in-generic-skills": 621,
25
+ "git-history-discipline": 1205,
26
+ "improve-before-implement": 845,
27
+ "laravel-routing": 126,
28
+ "laravel-translations": 82,
29
+ "legal-safety-floor": 1401,
30
+ "linked-projects-onboarding-gate": 172,
31
+ "minimal-safe-diff": 893,
32
+ "missing-tool-handling": 103,
33
+ "model-recommendation": 156,
34
+ "no-roadmap-references": 644,
35
+ "package-ci-checks": 74,
36
+ "persona-governance": 330,
37
+ "php-coding": 80,
38
+ "provider-lifecycle-discipline": 472,
39
+ "reviewer-awareness": 131,
40
+ "roadmap-ci-steps-policy": 564,
41
+ "roadmap-progress-sync": 1424,
42
+ "rule-type-governance": 87,
43
+ "skill-improvement-trigger": 81,
44
+ "skill-quality": 78,
45
+ "slash-command-routing-policy": 77,
46
+ "symfony-routing": 129,
47
+ "ui-audit-gate": 1280,
48
+ "untrusted-input-defense": 845,
49
+ "upstream-proposal": 76
50
+ },
51
+ "history": []
52
+ }
@@ -162,6 +162,9 @@ Tier 1 — power-user (release shape, audit, migration):
162
162
  Usage: council:render <responses.json>
163
163
  council:status Report whether an AI council is configured, and from where
164
164
  (no API call, no spend)
165
+ council:quota Show today's per-provider CLI-call usage against the
166
+ enforced cap; --reset <provider> --confirm clears one
167
+ (no API call, no spend)
165
168
  self-repair:status List queued agent-config defect records (read-only)
166
169
  self-repair:release Publish one defect record — a PR when the fix can be
167
170
  pushed, else an issue. Running it is your Hard-Floor
@@ -1443,6 +1446,7 @@ main() {
1443
1446
  council:run) cmd_council run "$@" ;;
1444
1447
  council:render) cmd_council render "$@" ;;
1445
1448
  council:status) cmd_council status "$@" ;;
1449
+ council:quota) cmd_council quota "$@" ;;
1446
1450
  self-repair:status) cmd_self_repair status "$@" ;;
1447
1451
  self-repair:release) cmd_self_repair release "$@" ;;
1448
1452
  update) cmd_update "$@" ;;