@event4u/agent-config 14.22.0 → 14.23.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 (111) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +159 -380
  5. package/README.md +1 -1
  6. package/dist/agent-src/commands/pr/merge.md +47 -38
  7. package/dist/agent-src/commands/roadmap/next.md +8 -0
  8. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +12 -0
  9. package/dist/agent-src/rules/notes-first-reasoning.md +36 -25
  10. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +106 -4
  11. package/dist/agent-src/skills/code-intelligence/SKILL.md +57 -1
  12. package/dist/agent-src/skills/git-workflow/references/push-closes-its-loop.md +9 -0
  13. package/dist/agent-src/skills/tailwind-engineer/SKILL.md +52 -2
  14. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  15. package/dist/cli/registry.js +1 -1
  16. package/dist/cli-delegate/archive_completed_roadmaps.js +52 -4
  17. package/dist/cli-delegate/{chunk-TGIPZBCW.js → chunk-2KKXJW25.js} +22 -8
  18. package/dist/cli-delegate/{chunk-4FS4WRXP.js → chunk-IAI3XL43.js} +15 -3
  19. package/dist/cli-delegate/{chunk-BIXEDQPZ.js → chunk-KCNTUSPI.js} +1 -1
  20. package/dist/cli-delegate/cmd_export.js +2 -2
  21. package/dist/cli-delegate/cmd_handoff.js +20 -12
  22. package/dist/cli-delegate/cmd_prune.js +2 -2
  23. package/dist/cli-delegate/cmd_session_recycle.js +3 -13
  24. package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
  25. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  26. package/dist/cli-delegate/cmd_sync.js +2 -2
  27. package/dist/cli-delegate/cmd_uninstall.js +2 -2
  28. package/dist/cli-delegate/cmd_validate.js +2 -2
  29. package/dist/discovery/deprecation-report.md +1 -1
  30. package/dist/discovery/discovery-manifest.json +8 -8
  31. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  32. package/dist/discovery/discovery-manifest.summary.md +1 -1
  33. package/dist/discovery/dormancy-report.md +1 -1
  34. package/dist/discovery/orphan-report.md +1 -1
  35. package/dist/discovery/packs.json +2 -2
  36. package/dist/discovery/trust-report.md +1 -1
  37. package/dist/discovery/workspaces.json +2 -2
  38. package/dist/hooks/dispatch.js +880 -289
  39. package/dist/install/install.mjs +15 -3
  40. package/dist/mcp/registry-manifest.json +2 -2
  41. package/dist/mcp/server.json +2 -2
  42. package/dist/mcp/server.mjs +5458 -2453
  43. package/dist/scripts/_lib/lean_projection_mode.js +108 -0
  44. package/dist/scripts/_lib/router_match.js +243 -0
  45. package/dist/scripts/_lib/rule_injection.js +216 -0
  46. package/dist/scripts/condense.js +20 -20
  47. package/dist/scripts/project_thin_rules.js +139 -19
  48. package/dist/server/schemas/settings.js +16 -3
  49. package/docs/MIGRATION.md +56 -0
  50. package/docs/contracts/settings-classes.md +39 -7
  51. package/docs/guidelines/agent-infra/notes-horizon-mechanics.md +90 -0
  52. package/llms.txt +1 -1
  53. package/package.json +1 -1
  54. package/src/agent-src/scripts/archive_completed_roadmaps.ts +106 -4
  55. package/src/config/agent-settings.template.yml +47 -1
  56. package/src/config/continuity-surface.json +7 -0
  57. package/src/config/evaluator-budgets.json +4 -4
  58. package/src/config/gate-violation-baselines.json +2 -2
  59. package/src/config/hook-token-budget.json +7 -6
  60. package/src/config/packed-binary-manifest.json +1 -1
  61. package/src/config/routing-coverage-seed.json +9 -2
  62. package/src/scripts/_cli/cmd_session_recycle.ts +2 -25
  63. package/src/scripts/_cli/handoff_sessions.ts +14 -44
  64. package/src/scripts/_lib/continuity_slot.ts +295 -0
  65. package/src/scripts/_lib/continuity_writer.ts +253 -0
  66. package/src/scripts/_lib/hook_settings.ts +85 -0
  67. package/src/scripts/_lib/lean_projection_mode.ts +127 -0
  68. package/src/scripts/_lib/packed_binary_predicate.ts +37 -0
  69. package/src/scripts/_lib/recycle_envelope_paths.ts +109 -21
  70. package/src/scripts/_lib/regression_neighbourhood.ts +159 -22
  71. package/src/scripts/_lib/session_eol.ts +44 -0
  72. package/src/scripts/_lib/session_index_trust.ts +346 -0
  73. package/src/scripts/_lib/standing_bound_ratchet.ts +154 -0
  74. package/src/scripts/_lib/vendored_grammar_upstream.ts +280 -0
  75. package/src/scripts/audit_mcp_tools.ts +69 -25
  76. package/src/scripts/build_mcp_catalog.ts +24 -1
  77. package/src/scripts/check_host_tree_parity.ts +291 -0
  78. package/src/scripts/check_preamble_payload_budget.ts +136 -7
  79. package/src/scripts/check_rule_projection_integrity.ts +102 -2
  80. package/src/scripts/ci_settle.ts +205 -5
  81. package/src/scripts/code_graph/build.ts +79 -0
  82. package/src/scripts/code_graph/cli.ts +258 -7
  83. package/src/scripts/code_graph/detect.ts +38 -0
  84. package/src/scripts/code_graph/query.ts +25 -2
  85. package/src/scripts/code_graph/sqlite_store.ts +16 -1
  86. package/src/scripts/code_graph/types.ts +52 -3
  87. package/src/scripts/code_graph/validate.ts +3 -1
  88. package/src/scripts/code_graph/verbs.ts +590 -0
  89. package/src/scripts/condense.ts +20 -20
  90. package/src/scripts/handoff_context_hook.ts +39 -1
  91. package/src/scripts/hook_manifest.json +1 -1
  92. package/src/scripts/hook_manifest.yaml +24 -1
  93. package/src/scripts/hooks/block_speaking_inbox_dir.ts +182 -16
  94. package/src/scripts/hooks/code_graph_context_hook.ts +7 -18
  95. package/src/scripts/hooks/push_settle_hook.ts +3 -2
  96. package/src/scripts/hooks/rule_inject_hook.ts +107 -20
  97. package/src/scripts/hooks/session_eol_hook.ts +120 -2
  98. package/src/scripts/hot_context_hook.ts +62 -7
  99. package/src/scripts/lint_roadmap_blockers.ts +164 -1
  100. package/src/scripts/mcp_server/consumer_tool_catalog.json +152 -2
  101. package/src/scripts/mcp_server/graph_tools.ts +294 -0
  102. package/src/scripts/mcp_server/path_util.ts +31 -0
  103. package/src/scripts/mcp_server/tool_catalog_source.ts +7 -1
  104. package/src/scripts/mcp_server/tools.ts +3 -13
  105. package/src/scripts/model_rule_injection.ts +61 -4
  106. package/src/scripts/project_thin_rules.ts +149 -16
  107. package/src/scripts/report_standing_payload_by_host.ts +650 -0
  108. package/src/scripts/schemas/agent-settings.schema.json +7 -1
  109. package/src/scripts/session_memory_index.ts +81 -8
  110. package/src/server/schemas/settings.ts +23 -4
  111. package/src/vendor/grammars/README.md +27 -2
@@ -195,8 +195,23 @@ rule_loading_tier: __RULE_LOADING_TIER__
195
195
  # validate with `task bench:ab:live` before flipping, and
196
196
  # keep `task trigger-coverage` at 100%. One-flip revert:
197
197
  # set eager-all + `task generate-tools`.
198
+ # delivery = the `thin` projection PLUS the `rule-inject` hook concern, which
199
+ # delivers a rule body back on trigger match. SHIPPED DEFAULT for
200
+ # Claude Code since ADR-267. Measured over the frozen routing-matrix
201
+ # corpus: 616/616 byte-equal deliveries, 101/101 labelled rules
202
+ # reachable, 0 of 212 near-misses fired. Rollback is this one
203
+ # setting: `mode: eager-all` + `task generate-tools`.
204
+ #
205
+ # `hosts` scopes WHICH hosts are thinned. Every host outside the list receives
206
+ # exactly what `eager-all` writes, asserted byte-for-byte in CI by
207
+ # `check_host_tree_parity`. The default has one member because Claude Code is
208
+ # the only host that both binds the concern's slots and acts on its verdict
209
+ # (docs/enforcement-by-host.md); thinning a host with no bound slot would remove
210
+ # a body and put nothing in its place. Only `claude-code`, `cursor` and `cline`
211
+ # are thinnable at all - any other id is dropped with a warning.
198
212
  lean_projection:
199
- mode: eager-all
213
+ mode: delivery
214
+ hosts: [claude-code]
200
215
 
201
216
  # --- Telegraph condensation (output-side) ---
202
217
  #
@@ -1093,6 +1108,37 @@ commands:
1093
1108
  ui_paths: []
1094
1109
  api_paths: []
1095
1110
 
1111
+ # --- Continuity ---
1112
+ #
1113
+ # Automatic session-continuity production. The continuity record is the one
1114
+ # artefact a successor session resumes from; see
1115
+ # docs/contracts/continuity-record-slot.md for the slot's capacity policy and
1116
+ # docs/contracts/continuity-rollback.md for what each switch here undoes.
1117
+ continuity:
1118
+ # Deterministic continuity-record writer at session end
1119
+ # (road-to-continuity-writer-activation Phase 1). When `on`, the session-eol
1120
+ # concern writes the `continuity_record` capsule variant on Stop for a
1121
+ # SUBSTANTIVE session that has claimed a roadmap — every field computed from
1122
+ # on-disk state, no model spend, nothing shelled out. Default OFF: while the
1123
+ # manual writer (`agent-config session:recycle`) is still the normal path,
1124
+ # arming this by default would put a second producer on it before the parity
1125
+ # evidence is in.
1126
+ # off = (default) no automatic record; the tree behaves as it does today.
1127
+ # on = write the record at session end.
1128
+ auto_record: "off"
1129
+
1130
+ # Deterministic run-checkpoint production at session end
1131
+ # (road-to-continuity-writer-activation Phase 1.4). When `on`, a session
1132
+ # above the recycle threshold and inside a running roadmap contract leaves
1133
+ # agents/runtime/state/checkpoints/<run>.json behind, so a killed session can
1134
+ # be resumed by the watcher rather than by bookkeeping. Default ON: this is
1135
+ # the behaviour the tree already had, and the switch exists so an operator can
1136
+ # disable checkpointing WITHOUT disabling continuity writing or the recycle
1137
+ # advisory.
1138
+ # on = (default) write the checkpoint.
1139
+ # off = no checkpoint; every other session-end handler is unaffected.
1140
+ run_checkpoints: "on"
1141
+
1096
1142
  # --- Memory ---
1097
1143
  #
1098
1144
  # Engineering memory consolidation behaviour. Memory is entirely
@@ -504,6 +504,13 @@
504
504
  "disposition": "counted",
505
505
  "locus": "src/domains/meta/chat-history/import/command.md:77",
506
506
  "reason": "A numbered-table session picker plus a resume offer \u2014 a second, competing answer to \"which prior session do I continue from\". On the owner-authorised retirement set of 2026-09-06."
507
+ },
508
+ {
509
+ "id": "session-index-latch",
510
+ "axis": "persistent_continuity_artefacts",
511
+ "disposition": "excluded",
512
+ "locus": "src/scripts/_lib/session_index_trust.ts:251",
513
+ "reason": "A duplicate-suppression latch, not a record. It holds one file per session under agents/runtime/state/session-index-latch/ whose entire content is a claim timestamp, and nothing reads it to restore anything — a successor session that finds it learns only that THIS session already received the memory index, which is the opposite of resumable state. It exists because `session_start` can fire more than once for one session (resume, host reconnect, compaction boundary on some hosts) and two injections double a fixed cost while presenting two corpora as one session memory. Excluded on the same reasoning as schema:session-eol-state: the writer own bookkeeping, not a record a successor reads. Deleting the directory costs a session nothing but a possible second injection."
507
514
  }
508
515
  ]
509
516
  }
@@ -65,11 +65,11 @@
65
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. 99 -> 100 on 2026-08-19: the `decision:memo` verb (road-to-long-horizon-execution Phase 2.2, sequencing UOTL Phase 4.3 -- the decision-memo channel) registers in the same PR that adds the surface, per this record's own contract. It exists because the question ladder gained a rung between \"halt and ask\" and \"decide silently\": a resolution the run makes without contacting the operator is only legitimate if it is reviewable afterwards, and there was no surface that wrote such a record with a fixed shape. Write-and-list only, local-only (agents/runtime/ is gitignored), and it gates nothing -- the locked decision classes are refused at the config schema, never in the ledger. 100 -> 101 in the same PR: `run:supervise` (Phase 3.1 -- the out-of-process watcher for runs whose session died with open steps left). It is the operator-facing half of session continuity: nothing anywhere had the job of noticing that a claimed run stopped moving, so a roadmap 40% done and a roadmap finished looked identical from outside the dead session. Report-only by default and it NEVER merges, pushes or closes -- the auto-merge the reference design carries is a named rejection in the roadmap's harvest section, not an unbuilt feature. Re-measured twice via the pinned method on the unchanged tree: 101/101. 101 → 102 on 2026-08-22: the `adr:effective` verb (road-to-evidence-based-adr-governance step 2.2 — the effective-state projection over one decision record) registers in the same PR that adds the surface, per this record's own contract. It exists because current truth about an ADR was only reconstructable from linear prose: an agent had to read the body, then every amendment, and decide for itself which clauses still stand. The verb reports status, the Decision verbatim, clauses the record's own amendments superseded, active amendments, the three descriptive axes and the review-trigger state. It AUTHORIZES NOTHING — Phase 2 of that roadmap is titled \"surfacing only; no authority\" and its AC-7 forbids any authority consequence, so the verb reports an absent axis as absent (`— (no provenance axis)`, `— (ungraded)`, `absent → evidence`) and an unresolvable trigger as `indeterminate` rather than inferring one. Re-measured twice via the pinned method on the unchanged tree: 102/102. 102 → 104 on 2026-08-22: the `council:grant-billing` + `council:revoke-billing` verbs (road-to-billing-cliff-gate Phase 2 — the run-scoped billing grant that lets `ai_council.fallback.api_on_quota: 'ask'` be answered once per run instead of once per seat) register in the same PR that adds the surface, per this record's own contract. The two are a pair by construction rather than by convenience: a grant that can be issued and not revoked would outlive its run, which is the failure the run-scoped TTL exists to prevent. Re-measured via the pinned method: 104. 104 -> 106 on 2026-08-23: the `ui:audit` + `ui:render` verbs (road-to-frontend-power E2.1 and E3.1 -- the two Class-A commands that give the frontend obligations an artefact a gate can read and a render a verdict can cite) register in the same PR that adds the surface, per this record's own contract. The two are a pair by construction rather than by convenience: `ui-audit-gate`'s enforced_by moved from `none` to a carrier in the same change, and that carrier reports `verification: degraded` whenever the render artefact is absent -- so an audit verb without a render verb would ship a gate that can never reach `verified`. Both are Class A under ADR-124: they terminate, their whole state is a rebuildable directory under agents/runtime/state/, and ui:render closes chromium in a `finally` on every path with no --serve, watch or cache for a resident browser to hide in. Re-measured via the pinned method on the unchanged tree: 106. 106 → 107 on 2026-08-23: the `roadmap:context` verb (road-to-roadmap-situational-awareness — the single live probe that answers what else is in flight before a roadmap is picked) registers in the same PR that adds the surface, per this record's own contract. Its branch measured 105 against a base of 104 and this branch measured 106 against the same base for `ui:audit` + `ui:render`; the merge makes it 107, re-measured on the merged tree via the pinned method (registry enumeration): 107. 107 → 108 on 2026-08-26: the `roadmap:set-step` verb (road-to-skill-ecosystem-runtime-enforcement Phase 6 — the single sanctioned writer of a checkbox glyph) registers in the same PR that adds the surface, per this record's own contract. It exists because the glyph is written today by whatever edits the file, and a concurrent writer destroys work SILENTLY: the loser of an overwrite receives no error. The verb carries an advisory lock, a line-anchored edit that cannot span entries even in principle, a step-count invariant asserted against the LIVE pre-write file rather than an in-memory snapshot, and a post-write survival check. Re-measured via the pinned method (registry enumeration) on the unchanged tree: 108. 108 → 109 on 2026-08-26: the `stubs:due` verb (road-to-inbox-harvest-2026-08-f-owner-decision-queue step 2.1 — the read-only reader over `agents/roadmaps/stubs/` frontmatter) registers in the same PR that adds the surface, per this record's own contract. It exists because step 1.2 backfills a `review_by:` date into 77 parked files, and an AI council (2026-08-26, 2/2) made the ordering a RULE rather than a preference: dates without a reader are strictly worse than no dates, because a stale date certifies attention nobody paid. The verb is read-only by construction — it authors nothing under `agents/roadmaps/stubs/`, so `check_no_stub_inventory_table` stays green and unmodified, and `roadmap:progress` consumes only its two COUNTS for the dashboard header (two integers, no rows, nothing for the deleted index tables to grow back from). Re-measured via the pinned method (registry enumeration) on the unchanged tree: 109. 109 → 110 on 2026-09-03: the `hooks:effect` verb (road-to-wired-instruments Phase 1.1 — the CLI entry for `hook_effect_doctor`) registers in the same PR that adds the surface, per this record's own contract. It exists because the probe and its five-state vocabulary were built, tested and reachable from nothing: `grep -rn 'hook_effect_doctor|hook_effect_probe' src Taskfile.yml .github` returned only the two modules themselves, and a closed roadmap recorded the wiring as landed at `src/scripts/hooks_doctor.ts:88`, a line that is the closing brace of a filesystem helper in a file with zero references to the probe. Deliberately NOT merged into `hooks:doctor`: that verb reads the MANIFEST (what is declared) and this one reads what happened (what took effect), and the probe's own header names merging them as the thing to avoid — a declaration report and an execution report are different instruments, and merging them makes the cheap one expensive. Read-only apart from a scratch root it removes in a `finally`. Re-measured via the pinned method (registry enumeration) on the unchanged tree: 110."
66
66
  },
67
67
  "mcp_public_tool_count": {
68
- "max": 20,
69
- "last_measured": 20,
70
- "last_measured_at": "2026-08-17",
68
+ "max": 25,
69
+ "last_measured": 25,
70
+ "last_measured_at": "2026-09-09",
71
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
+ "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. 20 → 25 on 2026-09-09 (release 14.23.0 gate). The +5 are `graph_impact`, `graph_tests_for`, `graph_dead`, `graph_query` and `graph_path` — 9452f29b4 (feat(mcp): five graph tools, the pinned install hint, and the skill description; road-to-a-graph-that-is-shipped Phase 4.1), which is on main. They are the agent-facing readers of the code graph: the roadmap Risk-Register rank 1 is \"the install grows and the graph is still unread\", and an agent that has to shell out to reach a verb will not, so the tools ARE the mitigation rather than a convenience. Same shape as every entry above — this RECORDS growth that already shipped through its own reviewed PR; it does not authorize new surface. The registering PR did not move this budget, so the gate did its job again: the breach warned on main and BLOCKED on the release ref, which is the conscious-decision point rather than a silently raised cap. The move is +25%, over the 10% creep rule, and that is the intended reading: five tools in one commit is a surface decision, not creep. Re-measured three times via the pinned method on a freshly built dist/mcp/server.mjs: 25/25/25, the returned name set differing from the 20-tool set by exactly the five graph_* entries. Headroom stays zero by design — the next tool that leaves STUB_TOOLS must move this number in its own PR."
73
73
  }
74
74
  }
75
75
  }
@@ -34,9 +34,9 @@
34
34
  "note": "The nine active non-draft roadmaps that predate the `relates:` requirement (road-to-agentic-engineering-assurance, road-to-component-library-lifecycle, road-to-council-evidence-integrity, road-to-frontend-fidelity-calibration, road-to-monorepo-scope-and-detection, road-to-observability-plate, road-to-role-scoped-spawn-profiles, road-to-skill-delivery-over-mcp, road-to-standing-payload-diet). This is a NEW obligation rather than a repaired scan root, and the ratchet is the point: `depends:` shipped in the template with nothing requiring it and measured 0 adoption, so requiring `relates:` retroactively across the estate would be an owner-reserved write with no analysis behind the edges it would have to invent (held as step 4.3, deferred). Clearing an entry here is one roadmap at a time: run `agent-config roadmap:context` on it, write the rows the probe surfaces, and lower the count."
35
35
  },
36
36
  "check_source_size_budget": {
37
- "count": 17973,
37
+ "count": 17963,
38
38
  "landed": "2026-09-08",
39
- "note": "Lowered 17,974 -> 17,973 on 2026-09-08 by drain/topology-c1. The branch adds a live-path emit to council_cli.ts, which sits 2,454 lines PAST the 1500 cap so every line there is one unit, and paid its own way down rather than raising the baseline, in the way this entry's doctrine prescribes. The twelve-line `all_responses` assembly (a spread, three conditionals and a push) moved out of council_cli.ts into ai_council/deliberation_metrics.ts::runCallsFrom, 353 lines and therefore UNDER the cap, so the six stage labels and their prose cost nothing. It moved on merit rather than only on arithmetic: 'which calls did this run make, and at which stage' is that module's own subject, and the CLI held the list only as a flat array with the stage boundaries implicit in the order -- both readings now come out of one walk, so the billing total and the attribution can never be computed over different sets. council_cli.ts 3,955 -> 3,954 net, after the two call lines, the import and the one payload key the feature needed. LOCAL READING, ADMISSIBLE ON THIS ENTRY'S OWN DISCRIMINATOR: the gate walks only SOURCE_ROOTS=['src'] counting excess over the 1500-line ceiling, and `git status --porcelain -uall -- src/` reports the two new src/ files staged and nothing untracked, so a clean CI checkout of this commit measures the same 17,973. If CI reads anything else, CI's number is the correct one and this note is the record of why. Carried in this commit because tests/scripts/check_source_size_budget.test.ts asserts baseline == live and therefore BLOCKS where the gate only advises. PRIOR ENTRY: Lowered 18,016 -> 17,974 on 2026-09-07 by fix/single-delivery-unconditional. SETTLED FIGURE, and it supersedes an intra-session 17,962 this same branch committed earlier — read the direction against MAIN, which is what the ratchet protects: main is 18,016, the branch leaves 17,974, net -42. The 17,962 was this branch's own low before a neutral review returned twelve findings; fixing them added a real mechanism to mcp_server and condense.ts (the flat-wrapper carriage gate the review showed was missing, plus its four-skip predicate), and condense.ts sits ~1,215 lines PAST the cap so each of those lines is one unit. Paid down first, in the way this entry's doctrine prescribes: _suggestSkillForTaskHandler and its 18-line docstring moved out of mcp_server/tools.ts (2,079 -> 2,025) into mcp_server/suggest_skill.ts, 72 lines and UNDER the cap, so its prose costs nothing. The residual +12 against the intra-session low is the gate's own output on the settled tree and is recorded rather than engineered away, because the alternative was extracting _nested_command_subpath and its two callers out of condense.ts - which needs MODULE_STATE, _relativeToPosix and _domains_command_logical passed in, a wider diff than the finding warrants and one no review asked for. LOCAL READING, ADMISSIBLE ON THIS ENTRY'S OWN DISCRIMINATOR: the gate walks only SOURCE_ROOTS=['src'] counting excess over the 1500-line ceiling, and `git status --porcelain -uall -- src/` reports the three new src/ files staged and nothing untracked, so a clean CI checkout of this commit measures the same 17,974. If CI reads anything else, CI's number is the correct one and this note is the record of why. Carried in this commit because tests/scripts/check_source_size_budget.test.ts asserts baseline == live and therefore BLOCKS where the gate only advises. PRIOR ENTRY: MERGE RECONCILIATION 2026-09-07, 18,061/18,017 -> 18,016. drain/admissible-council-seats and fix/release-obligation-answerable each lowered from the SAME 18,062 base in parallel — main to 18,061 (net -1: curlJsonPost, the seat-status helpers and the per-seat config parsing moved out of the three over-ceiling council files) and the branch to 18,017 (net -45: the /dev/tty prompt helpers moved out of release.ts into _lib/tty_prompt.ts, 76 lines and UNDER the cap, taking release.ts 1871 -> 1826). Taking either side alone is wrong in a different direction, which is the reconciliation doctrine this entry already carried from 2026-09-03: main's value silently discards the branch's earned -45, and the branch's value RAISES the baseline against main and weakens the gate. The committed number is neither — it is the gate's own output on the MERGED tree, where both reductions apply, and it equals the arithmetic (18,062 - 45 - 1 = 18,016) rather than being reconciled by it. LOCAL READING, ADMISSIBLE ON THIS ENTRY'S OWN DISCRIMINATOR: the gate walks only SOURCE_ROOTS=['src'] counting excess over the 1500-line ceiling, and `git status --porcelain -uall -- src/` reports ZERO untracked files, so a clean CI checkout of this commit measures the same 18,016. If CI reads anything else, CI's number is the correct one and this note is the record of why. Carried in the merge commit because tests/scripts/check_source_size_budget.test.ts asserts baseline == live and therefore BLOCKS where the gate only advises. PRIOR ENTRY: Lowered 18,062 -> 18,061 on 2026-09-07 by drain/admissible-council-seats. The branch's own additions to the three over-ceiling files (ai_council/config.ts, council_cli.ts, ai_council/clients.ts) came in at +96 excess and were paid down to -1 rather than baselined, in the four ways this entry's doctrine prescribes: `curlJsonPost` moved out of clients.ts into ai_council/api_transport.ts (UNDER the cap, so the prose and the three new provider shims cost nothing); the seat-status helpers moved out of council_cli.ts into ai_council/status_surface.ts, taking an orphaned doc comment with them; the three new per-seat config fields' parsing moved out of config.ts into ai_council/seat_policy.ts::parseSeatFields; and three prose blocks that the branch's own transport repair made FACTUALLY FALSE (the `openai` / `google-genai` Python-SDK claims, and a `_curlJsonPost` pointer to a function that had moved) were corrected and shortened in the same change. LOCAL READING, ADMISSIBLE ON THIS ENTRY'S OWN DISCRIMINATOR: the gate walks only SOURCE_ROOTS=['src'] counting excess over the 1500-line ceiling, and `git status --porcelain -uall -- src/` reports zero untracked files with the five new src/ files staged, so a clean CI checkout of this commit measures the same 18,061. If CI reads anything else, CI's number is the correct one and this note is the record of why. Carried here because tests/scripts/check_source_size_budget.test.ts:166 asserts baseline == live and therefore blocks where the gate only advises -- the lowering commit must carry its own baseline update. Lowered 18,068 -> 18,062 on 2026-09-06 by drain/host-enforcement-truth. Collapsing the five per-host binding constants in install.ts into src/scripts/hooks/host_lowering.yaml removed 29 lines from a file ~3,900 past the 1500 cap, where every line costs one excess; the journal's surface column and its extracted migration helper gave 23 back. Lowered 18,245 -> 18,230 on 2026-09-02 by drain/publication-integrity-option-a. Phase 2 Option A moved the authoring instruction out of the generator: release.ts, which sits ~530 lines PAST the 1500 cap so every line there costs one excess, loses the comment emission, a 16-line HEAD_DEFAULT doc block and its alias const, and gains a shorter writer docstring. The doc block moved to _lib/release_material.ts, which is 289 lines and therefore UNDER the cap, so the same prose now costs nothing. Net -15. The branch first came in at +8 and paid its own way down rather than raising the baseline.",
39
+ "note": "Lowered 17,973 -> 17,963 on 2026-09-08 by drain/graph-shipped-close. The branch registers five code-graph MCP tools (road-to-a-graph-that-is-shipped 4.1). Their five records and schemas are ~250 lines, and putting them in mcp_server/tools.ts -- which sits ~500 lines PAST the 1500 cap, so every line there is one unit -- would have cost ~250 excess. They live in mcp_server/graph_tools.ts instead (~280 lines, UNDER the cap, so they and their prose cost nothing), and tools.ts spreads them into ALLOWLIST in two lines: one import and one `...GRAPH_TOOLS,`. Those two were paid for rather than baselined, in the way this entry's doctrine prescribes: `_strip` and `_resolvePath` -- a whitespace trim and a tolerant realpath, generic by nature -- moved out of tools.ts into mcp_server/path_util.ts, also under the cap. They are imported under their original underscore names, so all 28 call sites are unchanged and a two-function move does not read as a sweep. tools.ts 2,025 -> 2,015: -12 from the extraction, +2 from the registration, net -10 for a branch that ADDED a feature. CORRECTED 2026-09-08 after an independent review checked the figure against the tree -- this sentence read '2,025 -> 2,013, net -12, against the +2 the registration needed', which states an intermediate file length as the final one and leaves the arithmetic (-12) and the conclusion (-10) describing different trees. `git show origin/main:...tools.ts | wc -l` is 2025 and the working tree is 2015. The TOTAL, 17,963, was always the gate's own output and is unchanged. Every other file this branch touches or creates -- code_graph/verbs.ts, code_graph/cli.ts, code_graph/build.ts, code_graph/detect.ts, code_graph/query.ts, code_graph/sqlite_store.ts, code_graph/types.ts, _lib/regression_neighbourhood.ts -- is under the 1500 ceiling before and after, so it contributes zero excess whatever it grew by. LOCAL READING, ADMISSIBLE ON THIS ENTRY'S OWN DISCRIMINATOR: the gate walks only SOURCE_ROOTS=['src'] counting excess over the 1500-line ceiling, and `git status --porcelain -uall -- src/` reports the two new src/ files staged and nothing untracked, so a clean CI checkout of this commit measures the same 17,963. If CI reads anything else, CI's number is the correct one and this note is the record of why. Carried in this commit because tests/scripts/check_source_size_budget.test.ts asserts baseline == live and therefore BLOCKS where the gate only advises. PRIOR ENTRY: Lowered 17,974 -> 17,973 on 2026-09-08 by drain/topology-c1. The branch adds a live-path emit to council_cli.ts, which sits 2,454 lines PAST the 1500 cap so every line there is one unit, and paid its own way down rather than raising the baseline, in the way this entry's doctrine prescribes. The twelve-line `all_responses` assembly (a spread, three conditionals and a push) moved out of council_cli.ts into ai_council/deliberation_metrics.ts::runCallsFrom, 353 lines and therefore UNDER the cap, so the six stage labels and their prose cost nothing. It moved on merit rather than only on arithmetic: 'which calls did this run make, and at which stage' is that module's own subject, and the CLI held the list only as a flat array with the stage boundaries implicit in the order -- both readings now come out of one walk, so the billing total and the attribution can never be computed over different sets. council_cli.ts 3,955 -> 3,954 net, after the two call lines, the import and the one payload key the feature needed. LOCAL READING, ADMISSIBLE ON THIS ENTRY'S OWN DISCRIMINATOR: the gate walks only SOURCE_ROOTS=['src'] counting excess over the 1500-line ceiling, and `git status --porcelain -uall -- src/` reports the two new src/ files staged and nothing untracked, so a clean CI checkout of this commit measures the same 17,973. If CI reads anything else, CI's number is the correct one and this note is the record of why. Carried in this commit because tests/scripts/check_source_size_budget.test.ts asserts baseline == live and therefore BLOCKS where the gate only advises. PRIOR ENTRY: Lowered 18,016 -> 17,974 on 2026-09-07 by fix/single-delivery-unconditional. SETTLED FIGURE, and it supersedes an intra-session 17,962 this same branch committed earlier — read the direction against MAIN, which is what the ratchet protects: main is 18,016, the branch leaves 17,974, net -42. The 17,962 was this branch's own low before a neutral review returned twelve findings; fixing them added a real mechanism to mcp_server and condense.ts (the flat-wrapper carriage gate the review showed was missing, plus its four-skip predicate), and condense.ts sits ~1,215 lines PAST the cap so each of those lines is one unit. Paid down first, in the way this entry's doctrine prescribes: _suggestSkillForTaskHandler and its 18-line docstring moved out of mcp_server/tools.ts (2,079 -> 2,025) into mcp_server/suggest_skill.ts, 72 lines and UNDER the cap, so its prose costs nothing. The residual +12 against the intra-session low is the gate's own output on the settled tree and is recorded rather than engineered away, because the alternative was extracting _nested_command_subpath and its two callers out of condense.ts - which needs MODULE_STATE, _relativeToPosix and _domains_command_logical passed in, a wider diff than the finding warrants and one no review asked for. LOCAL READING, ADMISSIBLE ON THIS ENTRY'S OWN DISCRIMINATOR: the gate walks only SOURCE_ROOTS=['src'] counting excess over the 1500-line ceiling, and `git status --porcelain -uall -- src/` reports the three new src/ files staged and nothing untracked, so a clean CI checkout of this commit measures the same 17,974. If CI reads anything else, CI's number is the correct one and this note is the record of why. Carried in this commit because tests/scripts/check_source_size_budget.test.ts asserts baseline == live and therefore BLOCKS where the gate only advises. PRIOR ENTRY: MERGE RECONCILIATION 2026-09-07, 18,061/18,017 -> 18,016. drain/admissible-council-seats and fix/release-obligation-answerable each lowered from the SAME 18,062 base in parallel — main to 18,061 (net -1: curlJsonPost, the seat-status helpers and the per-seat config parsing moved out of the three over-ceiling council files) and the branch to 18,017 (net -45: the /dev/tty prompt helpers moved out of release.ts into _lib/tty_prompt.ts, 76 lines and UNDER the cap, taking release.ts 1871 -> 1826). Taking either side alone is wrong in a different direction, which is the reconciliation doctrine this entry already carried from 2026-09-03: main's value silently discards the branch's earned -45, and the branch's value RAISES the baseline against main and weakens the gate. The committed number is neither — it is the gate's own output on the MERGED tree, where both reductions apply, and it equals the arithmetic (18,062 - 45 - 1 = 18,016) rather than being reconciled by it. LOCAL READING, ADMISSIBLE ON THIS ENTRY'S OWN DISCRIMINATOR: the gate walks only SOURCE_ROOTS=['src'] counting excess over the 1500-line ceiling, and `git status --porcelain -uall -- src/` reports ZERO untracked files, so a clean CI checkout of this commit measures the same 18,016. If CI reads anything else, CI's number is the correct one and this note is the record of why. Carried in the merge commit because tests/scripts/check_source_size_budget.test.ts asserts baseline == live and therefore BLOCKS where the gate only advises. PRIOR ENTRY: Lowered 18,062 -> 18,061 on 2026-09-07 by drain/admissible-council-seats. The branch's own additions to the three over-ceiling files (ai_council/config.ts, council_cli.ts, ai_council/clients.ts) came in at +96 excess and were paid down to -1 rather than baselined, in the four ways this entry's doctrine prescribes: `curlJsonPost` moved out of clients.ts into ai_council/api_transport.ts (UNDER the cap, so the prose and the three new provider shims cost nothing); the seat-status helpers moved out of council_cli.ts into ai_council/status_surface.ts, taking an orphaned doc comment with them; the three new per-seat config fields' parsing moved out of config.ts into ai_council/seat_policy.ts::parseSeatFields; and three prose blocks that the branch's own transport repair made FACTUALLY FALSE (the `openai` / `google-genai` Python-SDK claims, and a `_curlJsonPost` pointer to a function that had moved) were corrected and shortened in the same change. LOCAL READING, ADMISSIBLE ON THIS ENTRY'S OWN DISCRIMINATOR: the gate walks only SOURCE_ROOTS=['src'] counting excess over the 1500-line ceiling, and `git status --porcelain -uall -- src/` reports zero untracked files with the five new src/ files staged, so a clean CI checkout of this commit measures the same 18,061. If CI reads anything else, CI's number is the correct one and this note is the record of why. Carried here because tests/scripts/check_source_size_budget.test.ts:166 asserts baseline == live and therefore blocks where the gate only advises -- the lowering commit must carry its own baseline update. Lowered 18,068 -> 18,062 on 2026-09-06 by drain/host-enforcement-truth. Collapsing the five per-host binding constants in install.ts into src/scripts/hooks/host_lowering.yaml removed 29 lines from a file ~3,900 past the 1500 cap, where every line costs one excess; the journal's surface column and its extracted migration helper gave 23 back. Lowered 18,245 -> 18,230 on 2026-09-02 by drain/publication-integrity-option-a. Phase 2 Option A moved the authoring instruction out of the generator: release.ts, which sits ~530 lines PAST the 1500 cap so every line there costs one excess, loses the comment emission, a 16-line HEAD_DEFAULT doc block and its alias const, and gains a shorter writer docstring. The doc block moved to _lib/release_material.ts, which is 289 lines and therefore UNDER the cap, so the same prose now costs nothing. Net -15. The branch first came in at +8 and paid its own way down rather than raising the baseline.",
40
40
  "reason": "Lowered 18,230 -> 18,209 on 2026-09-03 by drain/artifact-location. `doctor --strict` needed 16 lines of integration wiring in cmd_doctor.ts, which sits ~2,100 lines PAST the 1500 cap so every line there costs one excess. The branch first came in at +137 and paid its own way down rather than raising the baseline: the strict semantics, threshold, kill switch, payload helper and argv fragments moved to _cli/_doctor_strict.ts (~160 lines, under the cap, costs nothing), taking it to +15; then the 37-line drift renderer moved to _cli/_doctor_emit.ts. Net -21. The renderer was chosen deliberately: pure rendering, two dependencies, no argparse involvement and therefore none of the byte-parity risk the parser surface carries against the retired Python implementation; nothing outside cmd_doctor.ts imported it. An AI council on the residual split (a: raise by 15 / d: descope the feature); neither was taken, because a parallel branch demonstrated the same day that paying down is achievable in this codebase, which is the evidence the split lacked. This gate's own test comment prescribes it: a commit that splits a god-file lowers the excess and must carry the lowered baseline. MERGE RECONCILIATION 2026-09-03, 18,209 -> 18,205: drain/wired-instruments and drain/artifact-location each lowered from the SAME 18,230 base in parallel -- the branch to 18,226 (net -4, run_continuation_hook.ts's termination ladder moved to _lib/continuation_ladder.ts and runtime_journal.ts's prose to _lib/outcome_vocabularies.ts, both under the cap) and main to 18,209 (net -21, the doctor --strict split above). Taking either side alone is wrong in a different direction: main's value silently discards the branch's earned -4, and the branch's value RAISES the baseline against main and weakens the gate. The committed number is neither -- it is the gate's own output on the merged tree, where both reductions apply. THIS LOCAL READING IS ADMISSIBLE HERE, and the discriminator matters because this file's ci-parity:local-only entry records a 165 -> 164 lowering that was reverted for exactly the opposite case: that count depends on how the environment resolves the projection surface, whereas this gate walks only SOURCE_ROOTS = ['src'] (check_source_size_budget.ts:88) counting excess lines over the 1500 ceiling, and `git status --porcelain -uall -- src/` reported ZERO untracked files, so a clean CI checkout of this commit measures the same 18,205. If CI reads anything else, CI's number is the correct one and this note is the record of why. LOWERED 18,205 -> 18,193 on 2026-09-03 by fix/release-curation-ask. The change adds a fifth publication guard, and its own additions landed at +10 excess: release.ts 2,008 -> 2,018, every line of which is past the 1500 cap. Paid down rather than baselined, in the two ways this gate's doctrine prescribes. The guard's rationale (~13 lines of docstring, including why prMerged is a parameter rather than a call-site if) lives in release_publication.ts, 915 lines and therefore UNDER the cap, so the prose costs nothing; and the two pure text helpers _splitlines and _rstrip moved out of release.ts into release_env.ts (333 lines, also under the cap), taking release.ts to 1,996 -- 12 BELOW where it started. The branch came in at +10 and leaves at -12. Recorded while moving them: _splitlines is duplicated a third and fourth time at adoption_report.ts:106 and readme_linter.ts:418; collapsing all three onto one definition touches three modules and was left out of this change rather than done silently. LOWERED 18,193 -> 18,180 on 2026-09-03 by fix/release-branch-reuse. The step-1 branch decision -- twenty lines of if/else in release.ts -- became one call to checkout_release_branch in release_publication.ts, which is 1,019 lines and therefore UNDER the 1500 cap, so the twenty lines plus a ~35-line rationale now cost nothing. release.ts 1,996 -> 1,983, every one of those lines being past the cap. The drill grew 765 -> 918 for two new scenarios and the snapshot/restore that keeps them from writing the real CHANGELOG, and it is under the cap too. A change that adds a feature and lowers the excess is the shape this gate's doctrine asks for; the alternative was a +13 raise for logic that had no business in the god-file. LOWERED 18,180 -> 18,068 on 2026-09-05 by fix/release-gate-producer-parity. The change wires the governance-mix obligation into the writer and the two local guards and adds a release-gate locality registry; its own additions to release.ts landed at +19 excess, every one of those lines past the 1500 cap. Paid down rather than baselined, in the way this entry's doctrine prescribes: the 132-line test-count-trend unit (_TEST_COUNT_LINE_RE, _TEST_LIST_MAX_BUFFER, _count_from_list_result, _count_tests_current, _previous_test_count_from_changelog, _render_test_trend_line) moved from release.ts into release_publication.ts, which is under the cap and where it now belongs on merit rather than only on arithmetic -- the same diff makes the missing `Tests: N` footer a section-level publication blocker, so the writer that emits the footer and the guard that refuses its absence sit in one module. release.ts 1,983 -> 1,871. All four names are re-exported from release.ts, so tests/scripts/release.test.ts is unchanged. LOCAL READING, ADMISSIBLE ON THIS ENTRY'S OWN DISCRIMINATOR: the gate walks only SOURCE_ROOTS=['src'] counting excess over the ceiling, and `git status --porcelain -uall -- src/` reports ZERO untracked files with the two new src/ files staged, so a clean CI checkout of this commit measures the same 18,068. If CI reads anything else, CI's number is the correct one and this note is the record of why."
41
41
  },
42
42
  "ci-parity:local-only": {
@@ -30,13 +30,14 @@
30
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
31
  "skill-route": 512,
32
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",
33
- "rule-inject": 20480,
34
- "rule-inject_reason": "road-to-trigger-delivered-rule-bodies 1.1: this concern delivers rule BODIES, not a pointer line, so its ceiling is the per-prompt token cap converted to bytes rather than a measured emission - 5,000 exact-BPE tokens (the p90 of the matched-body-token distribution over tests/eval/routing-matrix, rounded up to 500, reproduced by `model_rule_injection --corpus tests/eval/routing-matrix`) at ~4 bytes/token. Registered ABOVE the 4,096-byte user_prompt_submit and 2,048-byte pre_tool_use slot sums ON PURPOSE, and the mismatch is the finding rather than an oversight: the concern is default-OFF and emits ZERO bytes under every shipped setting, so no slot sum is breached today. The run that flips `lean_projection.mode: delivery` is the run that must move those two slot rows - that is the activation charge step 1.7 defers, and it is recorded here so the flip cannot be taken without meeting it."
33
+ "rule-inject": 16384,
34
+ "rule-inject_reason": "LOWERED 20480 -> 16384 on 2026-09-08, R2 finding 3. THE OLD PAIR WAS TWO STATISTICS IN TWO UNITS, not one decision: this row and `hooks/rule_inject_hook.ts::CAP_BYTES` carried the p90 of the matched-body TOKEN distribution rounded to 500 tok and converted at ~4 B/tok (5,000 tok -> 20,480 B), while `per_slot_sum_caps_bytes.user_prompt_submit` carries the p90 gate-open FIRE SIZE in bytes rounded up to 512 (16,384 B) \u2014 the activation charge owner ruling E2 specifies for the flipping run. One concern licensed 25% above the whole slot's registered sum, on a slot carrying 12 other concerns, made the overrun DESIGNED IN: measured max fire was 20,406 B and 38 of 330 corpus fires exceeded 16,384 B. Reconciled DOWNWARD, never by raising the slot: the charge is the owner-specified number and the concern now conforms to it instead of exceeding it. MEASURED COST of the lowering, over the frozen `tests/eval/routing-matrix` corpus with the command path included (330 fires): p90 16,865 -> 14,507 B, max 20,406 -> 16,348 B, fires truncated 33 -> 45, rule bodies dropped to fit the cap 63 -> 88. So 25 further bodies are withheld on the top-decile fires, where five or more rules matched at once \u2014 a delivery reduction, stated rather than implied, and the endpoints do not measure it (`b-recall` scores `matchTierRules`, not `selectForInjection`). WHAT ENFORCES WHAT, because the finding correctly asked: `CAP_BYTES` is enforced at emission by `selectForInjection`, which drops whole bodies to stay under it, so a fire is TRUNCATED and never over-emitted. The per-slot sums here are an AUTHORING-time control read by `bench_hook_injection`; the only runtime enforcement is `per_turn_aggregate_bytes.ceiling_bytes` in `hooks/injection_budget.ts`, which drops lowest-severity advisories and does not read a per-slot row. And the bench cannot see a real fire from this concern at all: it runs each concern under AGENT_CONFIG_REPLAY=1, which re-imposes the settings gate, and this repository carries no `.agent-settings.yml`, so the concern measures 0 B there. The CROSS-CONCERN residue is therefore still unbounded by any acting gate \u2014 it is now bounded in the authoring record rather than guaranteed to breach, which is a smaller claim than closure and is the honest one."
35
35
  },
36
36
  "per_slot_sum_caps_bytes": {
37
37
  "_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.",
38
38
  "session_start": 16384,
39
- "user_prompt_submit": 4096,
39
+ "user_prompt_submit": 16384,
40
+ "user_prompt_submit_reason": "RAISED 4096 -> 16384 on 2026-09-07 by road-to-delivery-for-every-host 3.2, under owner ruling E2, which assigns this move to the run that flips `lean_projection.mode: delivery` and is the activation charge the `rule-inject` row below defers to it. THE NUMBER IS MEASURED, NOT CHOSEN: 16,384 is the p90 gate-open fire size of the delivery concern over the frozen `tests/eval/routing-matrix` corpus rounded UP to 512 bytes, exactly as E2 specifies. Distribution over 318 gate-open fires: p50 6,674 B, p90 16,188 B, max 20,406 B. Reproduce it from `_lib/rule_injection.ts::matchTierRules` + `selectForInjection` at `hooks/rule_inject_hook.ts::CAP_BYTES` over that corpus. NOT config weakening, and the guard agrees by its own classification: `hooks/block_config_weakening.ts:96-98` classifies `*-budget.json` as `advisory`, so its decide() path returns `warn` and never a block — treat that warning as expected and documented. `pre_tool_use` is deliberately NOT raised alongside it: the concern's binding on that slot was REMOVED in the same change, so nothing new emits there and its 2,048 stands.",
40
41
  "pre_tool_use": 2048,
41
42
  "post_tool_use": 2048,
42
43
  "stop": 2048,
@@ -64,10 +65,10 @@
64
65
  "agent_error"
65
66
  ],
66
67
  "excluded_slots_rationale": "session_start is excluded BY THE STEP: it is the one-shot restore slot that legitimately carries hot-context, handoff-context and the canary contract, and capping it against a per-turn number would evict exactly the payloads it exists to deliver. The other three are not per-turn events at all.",
67
- "ceiling_bytes": 47104,
68
- "ceiling_derivation": "NOT a fresh number, and deliberately so: it is the arithmetic consequence of the per_slot_sum_caps_bytes rows already registered above — 4096 + (2048 + 2048) x 10 + 2048. A tighter per-turn ceiling would contradict per-slot rows that are already the committed decision, and a looser one would let the composition exceed what every part was capped at. Tightening it is a decision about those rows, taken there, in a PR.",
68
+ "ceiling_bytes": 59392,
69
+ "ceiling_derivation": "NOT a fresh number, and deliberately so: it is the arithmetic consequence of the per_slot_sum_caps_bytes rows already registered above — 16384 + (2048 + 2048) x 10 + 2048. A tighter per-turn ceiling would contradict per-slot rows that are already the committed decision, and a looser one would let the composition exceed what every part was capped at. Tightening it is a decision about those rows, taken there, in a PR.",
69
70
  "gate_on_ceiling": false,
70
- "gate_on_ceiling_rationale": "The bench REPORTS the aggregate but does not red the build on it yet — the first-reading discipline the latency twin's observe_only flag encodes. Measured 2026-08-19 under the committed fixtures: ~1,142 B against a 47,104 B ceiling, i.e. the row is registered roughly forty times above any reading that exists. The reading MOVES between runs — 922, 1,140 and 1,142 B observed within one hour — and the mechanism is worth naming precisely rather than as 'flaky': the stop-slot end-review-nudge sizes its line from the branch's own mutation volume, and replay skips its writes but not its reads, so the number grows as the working branch grows. That is a pre-existing bench non-determinism this row surfaces rather than introduces, and it is the strongest single reason not to arm the ceiling against it. Arming a ceiling nobody has ever approached would gate on an untested inequality. Runtime enforcement is NOT gated by this flag and is always on: this flag governs CI, the ceiling governs the live session.",
71
+ "gate_on_ceiling_rationale": "The bench REPORTS the aggregate but does not red the build on it yet — the first-reading discipline the latency twin's observe_only flag encodes. Measured 2026-08-19 under the committed fixtures: ~1,142 B against the 47,104 B ceiling that applied then, i.e. the row is registered roughly forty times above any reading that exists. The ceiling has since become 59,392 B, following the `user_prompt_submit` slot raise above; the measurement is left at its original date rather than restated, and the headroom the sentence describes only widened. The reading MOVES between runs — 922, 1,140 and 1,142 B observed within one hour — and the mechanism is worth naming precisely rather than as 'flaky': the stop-slot end-review-nudge sizes its line from the branch's own mutation volume, and replay skips its writes but not its reads, so the number grows as the working branch grows. That is a pre-existing bench non-determinism this row surfaces rather than introduces, and it is the strongest single reason not to arm the ceiling against it. Arming a ceiling nobody has ever approached would gate on an untested inequality. Runtime enforcement is NOT gated by this flag and is always on: this flag governs CI, the ceiling governs the live session.",
71
72
  "honest_null_consequence": "If the census shows real turns sitting near the ceiling, the per-slot rows move first and this row follows from them. If real turns never approach it, the row is a bound that costs nothing and the finding is published at review date either way."
72
73
  },
73
74
  "injection_load_metric": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "_comment": "Every binary entry the published tarball is allowed to carry, one entry per file. Consulted by check_pack_size's binary-eligibility predicate; an entry is ALLOWED only when every mechanical condition in that predicate holds, so a row here is a claim the gate re-verifies rather than a permission it grants. Path-exact by construction: no wildcards, no directory grants, and a second row for the same path is a hard failure. AUTHORITY: AI council 2026-09-08, 2 seats, unanimous, deep tier — it reopened the `binary` class that f573d628c closed 24 hours earlier. `archive` is NOT reopened and this file may never carry one: that collision supplied zero evidence about archives.",
2
+ "_comment": "Every binary entry the published tarball is allowed to carry, one entry per file. Consulted by check_pack_size's binary-eligibility predicate; an entry is ALLOWED only when every mechanical condition in that predicate holds, so a row here is a claim the gate re-verifies rather than a permission it grants. Path-exact by construction: no wildcards, no directory grants, and a second row for the same path is a hard failure. AUTHORITY: AI council 2026-09-08, 2 seats, unanimous, deep tier — it reopened the `binary` class that f573d628c closed 24 hours earlier. `archive` is NOT reopened and this file may never carry one: that collision supplied zero evidence about archives. ANCHOR: every condition the predicate checks reads THIS file, so a commit that edits a row and its bytes together satisfies all of them. The check that anchors these bytes to something outside this repository is `src/scripts/_lib/vendored_grammar_upstream.ts`, exercised by `tests/scripts/vendored_grammar_upstream.test.ts`; the threat boundary between the two is written out in `packed_binary_predicate.ts`'s docblock.",
3
3
  "schema_version": 1,
4
4
  "entries": [
5
5
  {
@@ -9,11 +9,11 @@
9
9
  "skills": "src/skills/*/evals/triggers.json over src/skills/*/SKILL.md."
10
10
  },
11
11
  "seed": {
12
- "rules": 0.8952,
12
+ "rules": 0.9619,
13
13
  "skills": 0.3344
14
14
  },
15
15
  "seed_provenance": {
16
- "rules": "94 covered of 105 routed rules (tier-1 21/24, tier-2 73/81) on 2026-08-25.",
16
+ "rules": "101 covered of 105 routed rules on 2026-09-08; was 94 of 105 (tier-1 21/24, tier-2 73/81) on 2026-08-25.",
17
17
  "skills": "100 of 299 skills carry evals/triggers.json on 2026-08-30.",
18
18
  "finding": "The gap between the two seeds IS the roadmap defect D1, quantified. The rules surface is ~90% covered by a deterministic corpus that can fail a PR; the skills surface -- the one production routes on -- is 25%, covered only by a harness that is advisory only, never gating (rule_trigger_eval.ts:4)."
19
19
  },
@@ -38,6 +38,13 @@
38
38
  "from": 0.3144,
39
39
  "to": 0.3344,
40
40
  "reason": "road-to-governed-harness-evolution Phase 2.4 authored six wave-1 corpus files (94 -> 100 of 299), chosen by 2.2's inbound-confusion-edge ordering rather than picked. The seed rises so the gain cannot be given back silently; it may never be LOWERED. Surfaced by check_routing_coverage.test.ts, which asserts seed == live measurement in BOTH directions -- a seed left below the tree is a red test, not slack, which is the same asymmetry check_source_size_budget uses and for the same reason: nothing else would force the ratchet to follow a gain."
41
+ },
42
+ {
43
+ "date": "2026-09-08",
44
+ "scope": "rules",
45
+ "from": 0.8952,
46
+ "to": 0.9619,
47
+ "reason": "road-to-delivery-for-every-host authored seven routing-matrix fixtures -- council-availability, evaluator-independence, fix-what-you-see, missing-skill-recovery, playbook-precedence, recurring-criticism, self-repair-loop (94 -> 101 of 105 routed rules). First entry on the RULES scope; the three above are all skills. The seed rises because this file's own test asserts seed == live measurement in both directions, so the gain would otherwise red the suite for doing what the ratchet encourages. It may never be LOWERED."
41
48
  }
42
49
  ]
43
50
  }
@@ -33,8 +33,8 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
33
33
  import { ORIGIN_CWD_FALLBACK, resolve_project_root } from '../_lib/agent_settings.js';
34
34
  import {
35
35
  RECYCLE_ENVELOPE_MAX_BYTES,
36
- recycle_consumed_rel,
37
36
  recycle_envelope_rel,
37
+ resolvePredecessor,
38
38
  } from '../_lib/recycle_envelope_paths.js';
39
39
  import {
40
40
  CAPSULE_SCHEMA_VERSION,
@@ -84,30 +84,7 @@ export function templateEnvelope(): Record<string, unknown> {
84
84
  };
85
85
  }
86
86
 
87
- /**
88
- * Which session this one continues — read, never composed.
89
- *
90
- * The chain is derivable without any new artifact: when this session started,
91
- * its reader consumed the predecessor's record and MOVED it to this session's
92
- * consumed path (`recycle_consumed_rel`). So the file sitting there is, by
93
- * construction, the record this session resumed from, and its `session_id` is
94
- * the predecessor.
95
- *
96
- * Returns the explicit string `none` when there is nothing there. `none` is a
97
- * CLAIM — "this session starts a chain" — and is deliberately not an empty
98
- * value: a reader must be able to tell "no predecessor" from "nobody wrote the
99
- * field", because only the second one is a reason to go looking.
100
- */
101
- export function resolvePredecessor(projectRoot: string, sessionId: string | null): string {
102
- try {
103
- const consumed = path.join(projectRoot, recycle_consumed_rel(sessionId));
104
- const raw = JSON.parse(fs.readFileSync(consumed, 'utf-8')) as { session_id?: unknown };
105
- const prior = String(raw.session_id ?? '').trim();
106
- return prior === '' ? 'none' : prior;
107
- } catch {
108
- return 'none';
109
- }
110
- }
87
+ export { resolvePredecessor };
111
88
 
112
89
  interface ParsedArgv {
113
90
  ok: boolean;
@@ -32,7 +32,13 @@ import * as path from 'node:path';
32
32
 
33
33
  import { projectStoreSlug } from '../_lib/cc_transcript.js';
34
34
  import { derive_session_tag, list_sessions, read_entries } from '../chat_history.js';
35
- import { eolSessionKey, readEolCounters, type StoredEolCounters } from '../_lib/session_eol.js';
35
+ import {
36
+ eolSessionKey,
37
+ is_substantive,
38
+ readEolCounters,
39
+ type StoredEolCounters,
40
+ SUBSTANTIVE_TOKEN_FLOOR,
41
+ } from '../_lib/session_eol.js';
36
42
 
37
43
  export const DEFAULT_CAP = 15;
38
44
  /**
@@ -42,24 +48,16 @@ export const DEFAULT_CAP = 15;
42
48
  * on the observed store 10 of 217 sessions are filtered (~5%), so 3× covers
43
49
  * that comfortably while keeping the per-file scan bounded.
44
50
  */
51
+ /**
52
+ * Re-exported from `_lib/session_eol.ts`, where both now live so the Stop-path
53
+ * hook can reach them without importing this CLI module. Kept exported HERE
54
+ * because the resume picker is the surface every existing importer names.
55
+ */
56
+ export { is_substantive, SUBSTANTIVE_TOKEN_FLOOR };
57
+
45
58
  const FETCH_OVERSHOOT = 3;
46
59
  const SUMMARY_SNIPPET_CHARS = 120;
47
60
 
48
- /**
49
- * Committed substantive-content floor, in parsed transcript tokens (billable
50
- * input of the last main-chain assistant record, per `cc_transcript.ts`) — the
51
- * OR-arm for a session that produced real discussion without ever calling a
52
- * tool.
53
- *
54
- * Derivation (`agents/evidence/analysis/handoff-substantive-threshold.md`,
55
- * 217 sessions of the local Claude store, 2026-08-10): 206 of the 207 sessions
56
- * carrying an assistant record also carry a `tool_use` block, so this arm
57
- * serves the ~0.5 % tail and the hosts whose transcripts log no tool blocks at
58
- * all. 10 000 sits **25× below** the p10 of real working sessions (254 939),
59
- * so it cannot hide one, and above a single trivial exchange. Changing it is a
60
- * PR citing evidence, never a drive-by edit.
61
- */
62
- export const SUBSTANTIVE_TOKEN_FLOOR = 10_000;
63
61
 
64
62
  export type HandoffSource = 'chat-history' | 'claude-transcript' | 'codex-session';
65
63
 
@@ -454,34 +452,6 @@ function _sort_key(s: HandoffSession): string {
454
452
  return s.endedAt ?? s.startedAt ?? '';
455
453
  }
456
454
 
457
- /**
458
- * Does this session hold anything worth resuming?
459
- *
460
- * `≥ 1 assistant turn AND (≥ 1 tool call OR parsed tokens ≥ the committed
461
- * floor)`, read from the counts-only session-eol state.
462
- *
463
- * **Fail-open, deliberately** (Phase 1.2): absent, unreadable or mis-shaped
464
- * state LISTS the candidate rather than filtering it, and a `tool_calls` key
465
- * missing from a state file written before that counter existed reads as
466
- * *unknown*, never as zero. A wrongly listed candidate is noise the user
467
- * scrolls past; a wrongly hidden one is data loss they cannot even see.
468
- */
469
- export function is_substantive(counters: StoredEolCounters | null): boolean {
470
- if (counters === null) return true;
471
- // Every "unknown" below is a NUMBER test, not an `=== undefined` test.
472
- // Absent, null and NaN are all unknown, and each one reaches this code by
473
- // a real path: a state file written before the counter existed is absent,
474
- // `JSON.stringify` turns NaN into null on the way to disk, and a
475
- // half-written file yields a parseable object with missing keys. Reading
476
- // any of them as "counted zero" hides a session that did real work.
477
- if (!Number.isFinite(counters.assistant_records)) return true;
478
- if ((counters.assistant_records as number) < 1) return false;
479
- if (!Number.isFinite(counters.tool_calls)) return true;
480
- if ((counters.tool_calls as number) >= 1) return true;
481
- if (!Number.isFinite(counters.final_context_tokens)) return true;
482
- return (counters.final_context_tokens as number) >= SUBSTANTIVE_TOKEN_FLOOR;
483
- }
484
-
485
455
  function _derive_tag(id: string): string | null {
486
456
  try {
487
457
  return derive_session_tag(id);