@event4u/agent-config 14.13.0 → 14.14.1

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 (138) 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 +138 -376
  5. package/README.md +1 -1
  6. package/dist/agent-src/commands/analyze/inbox.md +36 -0
  7. package/dist/agent-src/commands/pr/create.md +16 -1
  8. package/dist/agent-src/contexts/execution/terminal-states.md +25 -4
  9. package/dist/agent-src/rules/code-comment-discipline.md +6 -0
  10. package/dist/agent-src/rules/missing-skill-recovery.md +2 -2
  11. package/dist/agent-src/rules/token-budget-discipline.md +2 -2
  12. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +6 -6
  13. package/dist/agent-src/scripts/guarded_baseline.ts +22 -7
  14. package/dist/agent-src/scripts/stubs_due.ts +118 -2
  15. package/dist/agent-src/scripts/update_roadmap_progress.ts +8 -8
  16. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  17. package/dist/cli/registry.js +1 -0
  18. package/dist/cli-delegate/{chunk-ILMUPLGW.js → chunk-7RXIKF62.js} +159 -86
  19. package/dist/cli-delegate/cmd_conformance.js +39 -2
  20. package/dist/cli-delegate/cmd_doctor.js +1 -1
  21. package/dist/discovery/deprecation-report.md +1 -1
  22. package/dist/discovery/discovery-manifest.json +7 -7
  23. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  24. package/dist/discovery/discovery-manifest.summary.md +1 -1
  25. package/dist/discovery/dormancy-report.md +1 -1
  26. package/dist/discovery/orphan-report.md +1 -1
  27. package/dist/discovery/packs.json +2 -2
  28. package/dist/discovery/trust-report.md +1 -1
  29. package/dist/discovery/workspaces.json +2 -2
  30. package/dist/hooks/dispatch.js +1696 -812
  31. package/dist/mcp/registry-manifest.json +2 -2
  32. package/dist/mcp/server.json +2 -2
  33. package/dist/mcp/server.mjs +297 -115
  34. package/docs/guidelines/agent-infra/guarded-baseline.md +1 -1
  35. package/docs/guidelines/code-clarity.md +18 -0
  36. package/package.json +1 -1
  37. package/src/agent-src/scripts/archive_completed_roadmaps.ts +6 -6
  38. package/src/agent-src/scripts/guarded_baseline.ts +22 -7
  39. package/src/agent-src/scripts/stubs_due.ts +118 -2
  40. package/src/agent-src/scripts/update_roadmap_progress.ts +8 -8
  41. package/src/config/assurance-capability-registry.json +1 -1
  42. package/src/config/estate-count-budget.json +20 -1
  43. package/src/config/evaluator-budgets.json +4 -4
  44. package/src/config/gate-coverage.yml +211 -1
  45. package/src/config/gate-reachability-exemptions.json +1 -0
  46. package/src/config/gate-violation-baselines.json +9 -3
  47. package/src/config/preamble-payload-budget.json +19 -11
  48. package/src/config/rule-stub-ceilings.json +14 -7
  49. package/src/config/web-launch-readiness.json +4 -2
  50. package/src/scripts/_cli/_doctor_emit.ts +71 -0
  51. package/src/scripts/_cli/_doctor_strict.ts +163 -0
  52. package/src/scripts/_cli/cmd_doctor.ts +16 -37
  53. package/src/scripts/_dispatch.bash +11 -4
  54. package/src/scripts/_lib/activation_ladder.ts +47 -3
  55. package/src/scripts/_lib/activation_receipt_producer.ts +341 -0
  56. package/src/scripts/_lib/adr_frontmatter.ts +26 -0
  57. package/src/scripts/_lib/branch_convergence.ts +186 -0
  58. package/src/scripts/_lib/candidate_pair_delta.ts +349 -0
  59. package/src/scripts/_lib/carrier_status.ts +118 -0
  60. package/src/scripts/_lib/cascade_stage_enumeration.ts +92 -0
  61. package/src/scripts/_lib/context_observation.ts +132 -0
  62. package/src/scripts/_lib/continuation_ladder.ts +188 -0
  63. package/src/scripts/_lib/corpus_manifest.ts +726 -0
  64. package/src/scripts/_lib/delivery_arm_experiment.ts +18 -2
  65. package/src/scripts/_lib/estate_carrier_cases.ts +72 -0
  66. package/src/scripts/_lib/evaluation_cascade.ts +452 -0
  67. package/src/scripts/_lib/evolution_roi.ts +562 -0
  68. package/src/scripts/_lib/experiment_binding.ts +95 -0
  69. package/src/scripts/_lib/harness_evolution_guards.ts +10 -0
  70. package/src/scripts/_lib/ignored_blocker.ts +8 -1
  71. package/src/scripts/_lib/lexical_shortlist.ts +215 -0
  72. package/src/scripts/_lib/llm_candidate_proposer.ts +461 -0
  73. package/src/scripts/_lib/llm_proposer_transport.ts +153 -0
  74. package/src/scripts/_lib/outcome_envelope.ts +6 -1
  75. package/src/scripts/_lib/outcome_vocabularies.ts +135 -5
  76. package/src/scripts/_lib/pathology_archive.ts +393 -0
  77. package/src/scripts/_lib/promotion_capability.ts +315 -0
  78. package/src/scripts/_lib/promotion_evidence.ts +414 -0
  79. package/src/scripts/_lib/promotion_review.ts +294 -0
  80. package/src/scripts/_lib/prompt_shape.ts +92 -0
  81. package/src/scripts/_lib/release_highlights.ts +79 -1
  82. package/src/scripts/_lib/release_material.ts +125 -2
  83. package/src/scripts/_lib/repeated_failure.ts +19 -2
  84. package/src/scripts/_lib/routing_corpus.ts +307 -0
  85. package/src/scripts/_lib/routing_index_input.ts +104 -0
  86. package/src/scripts/_lib/rule_injection.ts +22 -1
  87. package/src/scripts/_lib/runtime_journal.ts +11 -4
  88. package/src/scripts/_lib/semantic_noop.ts +167 -0
  89. package/src/scripts/activation_receipt.ts +258 -0
  90. package/src/scripts/ai_council/cross_exam.ts +128 -0
  91. package/src/scripts/ai_council/explain_route.ts +157 -0
  92. package/src/scripts/ai_council/information_gain.ts +293 -0
  93. package/src/scripts/ai_council/leakage_corpus.ts +385 -0
  94. package/src/scripts/ai_council/leakage_patterns.ts +453 -0
  95. package/src/scripts/ai_council/minority_retention.ts +171 -0
  96. package/src/scripts/ai_council/recouncil_savings.ts +299 -0
  97. package/src/scripts/ai_council/replay_route.ts +223 -0
  98. package/src/scripts/ai_council/reviewer_assignment.ts +279 -0
  99. package/src/scripts/ai_council/routing_training_row.ts +197 -0
  100. package/src/scripts/ai_council/synthesis_strategy.ts +159 -0
  101. package/src/scripts/check_adr_frontmatter.ts +4 -9
  102. package/src/scripts/check_agent_artifact_location.ts +337 -0
  103. package/src/scripts/check_artefact_count_messaging.ts +74 -8
  104. package/src/scripts/check_enforcement_coverage.ts +40 -1
  105. package/src/scripts/check_estate_count.ts +205 -24
  106. package/src/scripts/check_release_highlights.ts +113 -14
  107. package/src/scripts/check_roadmap_trackable.ts +17 -2
  108. package/src/scripts/check_web_launch_readiness.ts +310 -20
  109. package/src/scripts/corpus_manifest.ts +235 -0
  110. package/src/scripts/evolution_lab.ts +234 -9
  111. package/src/scripts/generate_capability_matrix.ts +16 -9
  112. package/src/scripts/grade_target_readiness.ts +221 -2
  113. package/src/scripts/hook_effect_doctor.ts +47 -4
  114. package/src/scripts/hook_manifest.json +1 -1
  115. package/src/scripts/hook_manifest.yaml +28 -6
  116. package/src/scripts/hooks/comment_discipline_hook.ts +146 -0
  117. package/src/scripts/hooks/concern_registry.ts +2 -0
  118. package/src/scripts/hooks/run_continuation_hook.ts +78 -88
  119. package/src/scripts/hooks/session_eol_hook.ts +12 -1
  120. package/src/scripts/hooks/turn_end_gate_hook.ts +9 -1
  121. package/src/scripts/install-hooks.sh +18 -0
  122. package/src/scripts/language_mirror_hook.ts +14 -2
  123. package/src/scripts/lint_carrier_integrity.ts +820 -0
  124. package/src/scripts/lint_code_comments.ts +748 -0
  125. package/src/scripts/lint_plan_risk_register.ts +39 -19
  126. package/src/scripts/lint_promotion_paths.ts +779 -0
  127. package/src/scripts/llm_propose.ts +256 -0
  128. package/src/scripts/measure_delivery_sets.ts +361 -0
  129. package/src/scripts/measure_routing_signal.ts +307 -0
  130. package/src/scripts/model_rule_injection.ts +19 -3
  131. package/src/scripts/probe_council_retention.ts +495 -0
  132. package/src/scripts/release.ts +21 -37
  133. package/src/scripts/release_drill.ts +199 -0
  134. package/src/scripts/release_env.ts +35 -0
  135. package/src/scripts/release_publication.ts +142 -0
  136. package/src/scripts/roadmap_context.ts +15 -0
  137. package/src/scripts/skill_scout.ts +633 -0
  138. package/src/scripts/sync_pr_branch.ts +521 -42
@@ -34,6 +34,6 @@ Tooling, all of it in `guarded_baseline.ts` and enforced by both consumers:
34
34
  | Reported **separately**: a `## 🛡️ Guarded baselines` dashboard section plus a per-step stderr line | `update_roadmap_progress` |
35
35
  | **Rejected** — exit 1, on both `--check` and a plain regen — when `red_proof` is absent, when `category` is absent or illegal, when there is no evidence block, or when the annotation sits on anything but `- [ ]` | `update_roadmap_progress` |
36
36
  | Treated as **incomplete**: archival refused and the reason named | `archive_completed_roadmaps` |
37
- | Marked **stale** once a path-shaped `recheck_when` trigger resolves in the tree; a bare symbol trigger is reported as not machine-checkable rather than as not-stale | `guardedBaselineStaleness` |
37
+ | Marked **stale** once a path-shaped `recheck_when` trigger resolves in the tree. A trigger carrying **no** path token at all is reported as not machine-checkable rather than as not-stale; a trigger carrying at least one path token is decided by that path and is **not** reported, even when a companion symbol token sits beside it — a checked trigger that looks unchecked is the mirror of the failure the report exists to prevent (measured 2026-09-01: 3 of 4 reported lines were already decidable) | `guardedBaselineStaleness` |
38
38
 
39
39
  Only verification against the real mechanism permits `[x]`. **A baseline that has not demonstrably gone RED is an ordinary open item** and must not carry the annotation at all — the sabotage-and-restore proof is the entry price, not a nice-to-have.
@@ -315,6 +315,24 @@ Behavioral baseline: `tests/code-comments/eval-fixtures.md`
315
315
  `ccd-untouched-preservation`, `ccd-explicit-teaching-override`); the decidable
316
316
  criteria are proven by `tests/scripts/code_comment_fixtures.test.ts`.
317
317
 
318
+ ### Evidence belongs in the roadmap that asked for it
319
+
320
+ Measured 2026-09-02 in a consumer repository: **509 German comment lines** and
321
+ **21 report- or provenance-shaped comments** across **41 of 45** changed source
322
+ files, the worst a 101-line colour-analysis report inside a CSS file. The cause
323
+ was a roadmap requiring proof for every checked step: the run read that as
324
+ *evidence should be documented* and wrote the measurements into the source too,
325
+ where the roadmap already carried them.
326
+
327
+ The provenance of a value is not a WHY, and a roadmap cannot license a comment
328
+ the rule forbids.
329
+
330
+ Two enforcers carry this because neither rule could fire on a write:
331
+ `code-comment-discipline` matches prompt keywords, and `language-and-tone`'s
332
+ English-comment clause named a `.md`-only validator. `lint_code_comments`
333
+ refuses on the diff; the `comment-discipline` concern reports on the text as
334
+ written. Neither replaces the discipline.
335
+
318
336
  ## See also
319
337
 
320
338
  - Language-specific anchors that link to this guideline:
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@event4u/agent-config",
3
3
  "mcpName": "io.github.event4u-app/agent-config",
4
- "version": "14.13.0",
4
+ "version": "14.14.1",
5
5
  "description": "Governed skills, rules and commands for AI coding tools (Claude Code, Cursor, Windsurf, Copilot) — every claim machine-checked, including the counts in these badges.",
6
6
  "license": "MIT",
7
7
  "private": false,
@@ -12,8 +12,8 @@
12
12
  * `urp.collect()` completion criterion, and exit code 0. snake_case kept.
13
13
  *
14
14
  * A roadmap that has reached `count_open == 0` and `count_deferred == 0` is
15
- * **complete**. This sweep moves it to `agents/roadmaps/archive/`, rewrites
16
- * inbound references (`agents/roadmaps/<x>.md` → `agents/roadmaps/archive/<x>.md`)
15
+ * **complete**. This sweep moves it to `agents/roadmaps/archive/`, rewrites code-comment-allow provenance-comment -- the sweep's own operand
16
+ * inbound references (`agents/roadmaps/<x>.md` → `agents/roadmaps/archive/<x>.md`) code-comment-allow provenance-comment -- the rewrite this sweep performs
17
17
  * across tracked files so links never break, and regenerates the dashboard.
18
18
  *
19
19
  * It replaces the old **merge-gate** (keep one item open + a manual post-merge
@@ -136,7 +136,7 @@ function _branch_touched_paths(root: string, base: string): Set<string> | null {
136
136
  *
137
137
  * Two classes, and the second is why this predicate exists at all:
138
138
  *
139
- * - `agents/evidence/**` — a committed record of what was true, or of what a
139
+ * - `agents/evidence/**` — a committed record of what was true, or of what a code-comment-allow provenance-comment -- the predicate's operand set
140
140
  * reviewer was shown, at one point in time. Retroactively editing it makes it
141
141
  * claim something that did not happen, which is the failure
142
142
  * `evaluator-independence` exists to prevent ("the prompt ships with the
@@ -146,7 +146,7 @@ function _branch_touched_paths(root: string, base: string): Set<string> | null {
146
146
  * AND makes `git apply` target a path the diff never created.
147
147
  *
148
148
  * Measured 2026-08-11: archiving one roadmap rewrote four frozen artifacts
149
- * under `agents/evidence/reviews/`, including the `diff.patch` headers of a
149
+ * under `agents/evidence/reviews/`, including the `diff.patch` headers of a code-comment-allow provenance-comment -- names what the predicate protects
150
150
  * recorded review input. Nothing caught it — the sweep reported it as
151
151
  * "4 ref(s) migrated", which reads like success.
152
152
  */
@@ -346,7 +346,7 @@ function _escapeRe(v: string): string {
346
346
  * bare `- <name>` item inside the `relates:` block, which is a declaration the
347
347
  * author made deliberately. A mention anywhere else in the file does not count.
348
348
  */
349
- function _relatesTo(text: string, slug: string): boolean {
349
+ export function _relatesTo(text: string, slug: string): boolean {
350
350
  const lines = text.split('\n');
351
351
  const start = lines.findIndex((l) => /^relates:/.test(l));
352
352
  if (start === -1) return false;
@@ -654,7 +654,7 @@ function _regen_dashboard(root: string, dry_run: boolean): void {
654
654
  /**
655
655
  * Rebuild the archive index after a move, when the builder is present.
656
656
  *
657
- * The sweep is the one operation that changes what `agents/roadmaps/archive/`
657
+ * The sweep is the one operation that changes what `agents/roadmaps/archive/` code-comment-allow provenance-comment -- the directory this function writes
658
658
  * contains, so it is the one operation that can leave the generated index
659
659
  * stale — and the drift gate on that index runs in the same PR the sweep runs
660
660
  * in. Regenerating here keeps the two from disagreeing by construction rather
@@ -226,9 +226,18 @@ export function guardedBaselineProblems(items: readonly GuardedBaselineItem[]):
226
226
  *
227
227
  * HONEST SCOPE. Only a PATH trigger is machine-checkable: a token containing `/`
228
228
  * is resolved against the repo root and stale iff it exists. A bare symbol name
229
- * cannot be decided from a path, so it is returned as `unverifiable` rather than
230
- * silently reading as "not stale" — an unchecked trigger that looks checked is
231
- * the failure this whole state exists to avoid.
229
+ * cannot be decided from a path, so a record carrying ONLY symbol tokens is
230
+ * returned as `unverifiable` rather than silently reading as "not stale" — an
231
+ * unchecked trigger that looks checked is the failure this whole state exists
232
+ * to avoid.
233
+ *
234
+ * A record whose trigger carries AT LEAST ONE path token is decided by that
235
+ * path and is NOT reported as unverifiable, even when a companion symbol token
236
+ * sits beside it. Reporting it would be the mirror of the failure above: a
237
+ * checked trigger that looks unchecked. Measured 2026-09-01 — three of the four
238
+ * lines the dashboard printed as "not machine-checkable" carried a path token
239
+ * and were therefore already decidable, which is noise that trains a reader to
240
+ * skip the section where the one genuinely undecidable trigger also lives.
232
241
  */
233
242
  export function guardedBaselineStaleness(
234
243
  items: readonly GuardedBaselineItem[],
@@ -241,16 +250,22 @@ export function guardedBaselineStaleness(
241
250
  if (trigger === '') {
242
251
  continue;
243
252
  }
244
- for (const token of trigger.split(/[\s,]+/).filter((t) => t !== '')) {
245
- if (!token.includes('/')) {
246
- unverifiable.push(`line ${it.line} (${it.label}): recheck_when \`${token}\``);
247
- } else if (fs.existsSync(path.join(repo_root, token))) {
253
+ const tokens = trigger.split(/[\s,]+/).filter((t) => t !== '');
254
+ const paths = tokens.filter((t) => t.includes('/'));
255
+ for (const token of paths) {
256
+ if (fs.existsSync(path.join(repo_root, token))) {
248
257
  stale.push(
249
258
  `line ${it.line} (${it.label}): recheck_when \`${token}\` now exists — ` +
250
259
  're-verify against the real mechanism',
251
260
  );
252
261
  }
253
262
  }
263
+ if (paths.length > 0) {
264
+ continue;
265
+ }
266
+ for (const token of tokens) {
267
+ unverifiable.push(`line ${it.line} (${it.label}): recheck_when \`${token}\``);
268
+ }
254
269
  }
255
270
  return { stale, unverifiable };
256
271
  }
@@ -66,6 +66,48 @@ export interface StubRecord {
66
66
  owner_decisions: number;
67
67
  overdue: boolean;
68
68
  days_overdue: number | null;
69
+ /** Frontmatter `design_validated:` — a citation, or null when absent. */
70
+ design_validated: string | null;
71
+ /** Frontmatter `capability_gap:` — `none` means the stub declares no gap. */
72
+ capability_gap: string | null;
73
+ /** Frontmatter `blocker_class:` — `estate` | `budget` | `product`. */
74
+ blocker_class: string | null;
75
+ /** Frontmatter `blocker_opened:` — ISO date the hold began, or null. */
76
+ blocker_opened: string | null;
77
+ /** Releases published since `blocker_opened`, or null when it is absent. */
78
+ releases_since_blocked: number | null;
79
+ /** All three membership conditions hold. See {@link BLOCKED_QUICKWIN_FIELDS}. */
80
+ blocked_quickwin: boolean;
81
+ }
82
+
83
+ /**
84
+ * The three frontmatter keys that decide fourth-bucket membership.
85
+ *
86
+ * Read as SCALARS, never as substrings of the body. A substring match over
87
+ * prose is what put a validated fix and a waiting preference into one bucket
88
+ * in the first place; doing it with three substrings instead of one would be
89
+ * worse, not better. A stub missing any of the three is not a member and falls
90
+ * through to the OWNER bucket exactly as before — absence is never a member.
91
+ */
92
+ export const BLOCKED_QUICKWIN_FIELDS = ['design_validated', 'capability_gap', 'blocker_class'] as const;
93
+
94
+ /** Blocker classes that make a stub a BLOCKED QUICK-WIN rather than a product wait. */
95
+ export const QUICKWIN_BLOCKER_CLASSES = ['estate', 'budget'] as const;
96
+
97
+ /**
98
+ * All three conditions, read from frontmatter only.
99
+ *
100
+ * A recorded design validation, an explicit `capability_gap: none`, and an open
101
+ * blocker that is an estate or budget decision rather than a product one.
102
+ */
103
+ export function is_blocked_quickwin(
104
+ design_validated: string | null,
105
+ capability_gap: string | null,
106
+ blocker_class: string | null,
107
+ ): boolean {
108
+ if (design_validated === null || design_validated === '') return false;
109
+ if (capability_gap !== 'none') return false;
110
+ return (QUICKWIN_BLOCKER_CLASSES as readonly string[]).includes(blocker_class ?? '');
69
111
  }
70
112
 
71
113
  function _isDir(p: string): boolean {
@@ -188,7 +230,48 @@ function _parseIso(s: string): Date | null {
188
230
 
189
231
  /** Read every stub and classify it. `today` is injectable so tests are stable. */
190
232
  export function scan(root: string, today: string): StubRecord[] {
191
- return scan_dir(path.join(root, STUB_DIR), today);
233
+ return scan_dir(path.join(root, STUB_DIR), today, release_dates(root));
234
+ }
235
+
236
+ /**
237
+ * Changelog files that carry released sections, newest era first.
238
+ *
239
+ * BOTH are read, and that is the whole point of the pair. The era split moved
240
+ * older releases into the archive, so a count taken over the current file alone
241
+ * silently reports zero for anything before the split — measured: 14.11.0 lives
242
+ * only in the archive, and grepping `CHANGELOG.md` for it returns nothing.
243
+ */
244
+ export const CHANGELOG_ERAS = ['CHANGELOG.md', 'docs/archive/CHANGELOG-pre-14.12.0.md'] as const;
245
+
246
+ /** `## [x.y.z](…) (YYYY-MM-DD)` — a released section heading with its date. */
247
+ const RELEASE_HEADING = /^## \[\d+\.\d+\.\d+\][^\n]*\((\d{4}-\d{2}-\d{2})\)\s*$/gm;
248
+
249
+ /** Every released section's date across both eras, unsorted. */
250
+ export function release_dates(root: string): string[] {
251
+ const out: string[] = [];
252
+ for (const rel of CHANGELOG_ERAS) {
253
+ let text: string;
254
+ try {
255
+ text = fs.readFileSync(path.join(root, rel), 'utf8');
256
+ } catch {
257
+ continue;
258
+ }
259
+ for (const m of text.matchAll(RELEASE_HEADING)) {
260
+ if (m[1] !== undefined) out.push(m[1]);
261
+ }
262
+ }
263
+ return out;
264
+ }
265
+
266
+ /**
267
+ * Releases published strictly AFTER the day the hold began.
268
+ *
269
+ * Strictly after, because a release cut on the same day cannot have carried a
270
+ * fix the hold was blocking — counting it would inflate the figure the
271
+ * council's falsifier is read against.
272
+ */
273
+ export function releases_since(opened: string, dates: readonly string[]): number {
274
+ return dates.filter((d) => d > opened).length;
192
275
  }
193
276
 
194
277
  /**
@@ -198,7 +281,11 @@ export function scan(root: string, today: string): StubRecord[] {
198
281
  * immediately re-join — a fragile round trip past exactly the directory both
199
282
  * callers care about.
200
283
  */
201
- export function scan_dir(base: string, today: string): StubRecord[] {
284
+ export function scan_dir(
285
+ base: string,
286
+ today: string,
287
+ releaseDates: readonly string[] = [],
288
+ ): StubRecord[] {
202
289
  if (!_isDir(base)) return [];
203
290
  const now = _parseIso(today);
204
291
  const out: StubRecord[] = [];
@@ -222,6 +309,10 @@ export function scan_dir(base: string, today: string): StubRecord[] {
222
309
  const review_by = _scalar(front, 'review_by');
223
310
  const reviewed_at = _scalar(front, 'reviewed_at');
224
311
  const probeVal = _scalar(front, 'probe');
312
+ const design_validated = _scalar(front, 'design_validated');
313
+ const capability_gap = _scalar(front, 'capability_gap');
314
+ const blocker_class = _scalar(front, 'blocker_class');
315
+ const blocker_opened = _scalar(front, 'blocker_opened');
225
316
  const shape: StubShape = text.includes(TRANSFER_MARKER) ? 'transfer' : 'orgmode';
226
317
  let overdue = false;
227
318
  let days_overdue: number | null = null;
@@ -242,6 +333,13 @@ export function scan_dir(base: string, today: string): StubRecord[] {
242
333
  owner_decisions: count_owner_decisions(text),
243
334
  overdue,
244
335
  days_overdue,
336
+ design_validated,
337
+ capability_gap,
338
+ blocker_class,
339
+ blocker_opened,
340
+ releases_since_blocked:
341
+ blocker_opened === null ? null : releases_since(blocker_opened, releaseDates),
342
+ blocked_quickwin: is_blocked_quickwin(design_validated, capability_gap, blocker_class),
245
343
  });
246
344
  }
247
345
  return out;
@@ -251,6 +349,8 @@ export interface Counts {
251
349
  overdue: number;
252
350
  owner_decisions: number;
253
351
  missing_review_by: number;
352
+ /** ADDITIVE 2026-09-01. Existing keys are unchanged in name, type and meaning. */
353
+ blocked_quickwin: number;
254
354
  total: number;
255
355
  }
256
356
 
@@ -259,6 +359,7 @@ export function counts(records: StubRecord[]): Counts {
259
359
  overdue: records.filter((r) => r.overdue).length,
260
360
  owner_decisions: records.reduce((s, r) => s + r.owner_decisions, 0),
261
361
  missing_review_by: records.filter((r) => r.review_by === null).length,
362
+ blocked_quickwin: records.filter((r) => r.blocked_quickwin).length,
262
363
  total: records.length,
263
364
  };
264
365
  }
@@ -328,6 +429,21 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
328
429
  }
329
430
  }
330
431
 
432
+ const quickwins = records.filter((r) => r.blocked_quickwin);
433
+ if (quickwins.length > 0) {
434
+ process.stdout.write(
435
+ `\n 🔓 ${quickwins.length} BLOCKED QUICK-WIN(S) — design validated, no capability gap,\n` +
436
+ ' held by an estate or budget decision rather than a product one:\n',
437
+ );
438
+ for (const r of quickwins) {
439
+ const since =
440
+ r.releases_since_blocked === null
441
+ ? 'no `blocker_opened:`'
442
+ : `${String(r.releases_since_blocked)} release(s) since ${r.blocker_opened}`;
443
+ process.stdout.write(` ${r.file} (${r.blocker_class}, ${since})\n`);
444
+ }
445
+ }
446
+
331
447
  const noProbe = records.filter((r) => !r.probe_none && !r.has_probe_heading);
332
448
  if (noProbe.length > 0) {
333
449
  process.stdout.write(
@@ -12,7 +12,7 @@
12
12
  * `archive_completed_roadmaps` can consume it the same way Python does. snake_case
13
13
  * is kept on the public surface.
14
14
  *
15
- * Scans every roadmap under `agents/roadmaps/` (excluding `archive/`, `skipped/`,
15
+ * Scans every roadmap under `agents/roadmaps/` (excluding `archive/`, `skipped/`, code-comment-allow provenance-comment -- the path is this script's operand, not where the code came from
16
16
  * `template.md`, `README.md`, `open-questions*.md`), counts checkbox states per
17
17
  * phase, and writes a dashboard at `agents/roadmaps-progress.md` (outside the
18
18
  * `roadmaps/` folder to keep it clean) with:
@@ -96,7 +96,7 @@ const EXCLUDE_DIRS: ReadonlySet<string> = new Set(['archive', 'skipped', 'stubs'
96
96
 
97
97
  // FRONTMATTER_RE = re.compile(r"\A---\n(.*?)\n---\s*\n", re.DOTALL)
98
98
  const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---[ \t\n\r\f\v]*\n/;
99
- const DRAFT_VALUES: ReadonlySet<string> = new Set(['draft']);
99
+ const UNSCHEDULED_VALUES: ReadonlySet<string> = new Set(['draft', 'carrier']);
100
100
 
101
101
  const MERGE_GATED_RE = /merge-gated/i;
102
102
  // PR_NUM_RE = re.compile(r"pr\s*[=#:]?\s*#?\s*(\d+)", re.IGNORECASE)
@@ -289,8 +289,8 @@ function _stripQuotes(s: string): string {
289
289
  return out;
290
290
  }
291
291
 
292
- function is_draft(fm: Record<string, string>): boolean {
293
- return DRAFT_VALUES.has((fm['status'] ?? '').toLowerCase());
292
+ function is_unscheduled(fm: Record<string, string>): boolean {
293
+ return UNSCHEDULED_VALUES.has((fm['status'] ?? '').toLowerCase());
294
294
  }
295
295
 
296
296
  /**
@@ -752,7 +752,7 @@ function collect(roadmap_root: string): RoadmapStats[] {
752
752
  continue;
753
753
  }
754
754
  const text = fs.readFileSync(p, { encoding: 'utf-8' });
755
- if (is_draft(parse_frontmatter(text))) {
755
+ if (is_unscheduled(parse_frontmatter(text))) {
756
756
  continue;
757
757
  }
758
758
  const stats = parse_roadmap(p, roadmap_root);
@@ -820,7 +820,7 @@ function collect_parked(roadmap_root: string): ParkedRoadmap[] {
820
820
  } catch {
821
821
  continue;
822
822
  }
823
- if (is_draft(parse_frontmatter(text))) {
823
+ if (is_unscheduled(parse_frontmatter(text))) {
824
824
  continue;
825
825
  }
826
826
  const cell = resume_cell(text);
@@ -1305,7 +1305,7 @@ function _parseArgs(argv: readonly string[]): Args {
1305
1305
  }
1306
1306
 
1307
1307
  /**
1308
- * When the default cwd carries no `agents/roadmaps/`, fall back to the git
1308
+ * When the default cwd carries no `agents/roadmaps/`, fall back to the git code-comment-allow provenance-comment -- the path is this script's operand, not where the code came from
1309
1309
  * toplevel — hook and IDE invocations often run from a subdirectory, and a
1310
1310
  * cwd-only resolution silently skipped the dashboard there. An explicit
1311
1311
  * `--repo-root` always wins; a cwd that has the directory is used as-is
@@ -1479,7 +1479,7 @@ export {
1479
1479
  TITLE_RE,
1480
1480
  FRONTMATTER_RE,
1481
1481
  parse_frontmatter,
1482
- is_draft,
1482
+ is_unscheduled,
1483
1483
  is_roadmap_candidate,
1484
1484
  count_checkboxes,
1485
1485
  parse_blockers,
@@ -6,7 +6,7 @@
6
6
  "registered_at": "2026-08-23",
7
7
  "grader": {
8
8
  "path": "src/scripts/grade_target_readiness.ts",
9
- "dimensions": 10,
9
+ "dimensions": 13,
10
10
  "knockouts": [
11
11
  "test-presence",
12
12
  "static-analysis",
@@ -7,7 +7,7 @@
7
7
  "review_by": "2027-08-18",
8
8
  "metric": {
9
9
  "basis": "src/scripts/check_estate_count.ts. The live side uses collect() from update_roadmap_progress — the same parser the dashboard is generated from, imported rather than re-implemented. The floor side runs the SAME functions over a scratch copy of the base ref's agents/roadmaps/, materialised by _lib/base_tree.ts (git ls-tree + git cat-file --batch; `git archive` cannot be used here because .gitattributes carries `/agents export-ignore` and would yield an empty, silently-zero tree). SKILL METRICS (added 2026-08-24): _lib/skill_estate.ts walks src/skills/*/SKILL.md under the SAME root, so the floor side measures the base ref's own skill tree with the same function. That needs a SECOND materialised subtree — materialiseSubtree takes one prefix — and the two are independent on purpose: a base ref with no src/skills (an old tag, a shallow clone) drops the skill metrics and leaves the roadmap metrics ratcheting.",
10
- "active_roadmaps": "non-draft roadmap candidates at the top level of agents/roadmaps/, as collect() counts them. This is exactly the number the dashboard header prints as 'N open roadmaps'.",
10
+ "active_roadmaps": "roadmap candidates at the top level of agents/roadmaps/ that are not drafts, as collect() counts them, PLUS the ones collect() drops for declaring status: carrier. NO LONGER EQUAL TO THE DASHBOARD HEADER'S 'N open roadmaps', and the divergence is deliberate: with carriers excluded, flipping one roadmap's frontmatter to status: carrier measured active_roadmaps 3 -> 2 with the gate printing 'estate within its ratchet', because a count below the floor is a drawdown and free. The floor is measured at the base ref, so that flip lowers the next change's floor permanently, the file cannot be flipped back without paying, and any roadmap can be laundered out of the count by adding one word. A reclassification is not a disposal, so the metric counts the file on both sides and a flip changes nothing. Same reasoning later_roadmaps and open_blockers already carry: estate all the same, and a number that falls on a relocation prints free tightening over a burial. Both directions hold - deleting a carrier is still a shrink that earns no offset (classifyDiff), and adding one is growth that takes the estate_growth_exempt claim path like any other new roadmap. Drafts stay excluded on both sides, unchanged.",
11
11
  "later_roadmaps": "roadmap-shaped *.md files under agents/roadmaps/later/ — the same is_roadmap_candidate name filter the active side uses, so later/README.md is not counted. Counted from the filesystem because parked roadmaps sit outside collect()'s corpus by design; they are estate all the same, and a drawdown that moved everything into later/ and called the number down would be the burial T1's quality anchor exists to prevent.",
12
12
  "open_blockers": "blockers whose Status does not start with `resolved`, summed over the active and later corpora.",
13
13
  "floor": "the same three metrics measured at the base ref (origin/main, else the merge commit's first parent). No base ref resolves -> no floor -> the gate FAILS rather than skipping: a shrink-only gate with no floor passes every possible tree. `--base <ref>` is the explicit escape.",
@@ -36,5 +36,24 @@
36
36
  "_comment": "T3. `applies_above_active: null` means UNCONDITIONAL. T3 phrases the condition as 'while the active count sits above target', and T1 says the target belongs to the maintainer — so rather than inventing a ceiling so the condition could be evaluated, null states that no ceiling is registered and the lint therefore applies. Registering a number here is how it gets a threshold; registering one above the live count is how it is turned off.",
37
37
  "applies_above_active": null,
38
38
  "exemption": "estate_offset_exempt: <reason> in the added roadmap's own frontmatter. The key lives in the file rather than in this config for the reason RATCHET_RESET_KEY gives for living inside its baseline JSON: the claim shows up in the diff of the change that makes it."
39
+ },
40
+ "provisional_promotion": {
41
+ "_comment": "Phase 3.1 + 3.2 of road-to-blocked-quickwin-visibility: the mechanically capped provisional-promotion path the 2026-08-24 estate council named as the alternative TO loosening its five-condition rule — SPECIFIED IN FULL HERE AND DECLINED. The five conditions are answered one sentence each under `five_conditions`; the declination and its reasoning are the four `declined_*` fields; the fallback that now governs each future occurrence is `fallback`. READ THE NULLS HERE THE OTHER WAY ROUND FROM THE SIBLING KEY: `one_in_one_out.applies_above_active: null` means UNCONDITIONAL — no ceiling is registered, so that lint applies to everything — while `max_live: null` and `expires_after_days: null` HERE mean NOT REGISTERED, PATH INACTIVE. The two nulls sit a few lines apart in one file and mean opposite things, so a reader who transfers the sibling's semantics reads a declined path as an uncapped one, which is the exact inversion of what this object records. `check_estate_count` reads this object and distinguishes THREE states so that an intentional declination can never be mistaken for a config nobody wrote: `declined` (specified, refused, inactive), absent (misconfiguration — a could-not-run, never a pass, same shape as the missing-`one_in_one_out` throw), and `registered` (both integers present, which is the owner's act and nobody else's). Half-registered — one integer, one null — throws rather than guessing which half was meant. READING IS NOT ACTIVATION: the gate grants no allowance in any of the three states, and wiring an allowance for the `registered` state is a separate change that needs the owner's authorization rather than this one; a gate that started granting headroom the moment someone typed a number would be the self-certification the 2026-08-24 verdict refused, arriving one commit later.",
42
+ "status": "declined",
43
+ "declined_on": "2026-09-01",
44
+ "declined_by": "AI council (drain run 15), 2/2 convergent — anthropic/claude-sonnet-4-5 + openai/codex-default, 2 rounds, depth deep, peer-review, blind chairman, quorum 2/2 present (needed 1), subscription transport, billable=0, $0.0000. The verdict was endorsed by the blind chairman as the anchor reasoning of the session.",
45
+ "declined_reason": "Registering either integer — including the `max_live: 1` bounded-trial variant — constitutes governance self-amendment: it extends the agent's own write authority over a recorded estate floor, and `decision-revisit-gate` expressly reserves that class to the owner and states that no delegation overrides it. `max_live: 1` limits magnitude, not the legal character of the authority change, so the bounded trial is the same act at a smaller scale rather than a smaller act. The strongest counter-argument, recorded because it is real: the owner's standing instruction for the run was unusually broad and could be read as the owner personally choosing the council as decision-maker for this question. That general language nevertheless loses to the narrower rule expressly covering delegation and self-amendment, which is why the decline is convergent rather than split.",
46
+ "fallback": "Each future occurrence of the deadlock falsifier remains blocked until explicit owner authorization for THAT occurrence. This is the behaviour that already obtained, now recorded as a decision rather than left as a gap: `stubs:due` reports the blocked quick-win in its own bucket and `releases_since_blocked` counts the condition against published releases, so the next occurrence is detected by a query rather than by a reviewer noticing for the ninth time — and neither surface promotes anything on its own.",
47
+ "five_conditions": {
48
+ "1_authorizing_rule": "An existing repository-authorized rule explicitly permits autonomous exemption — this key is that rule and the repository holds no other, so registering `max_live` and `expires_after_days` here as integers is the single act that would create the permission, which is precisely why a specification shipping with its own numbers filled in would be the self-certification the 2026-08-24 verdict refused.",
49
+ "2_objective_criteria": "That rule supplies objective eligibility criteria — the three blocked-quick-win frontmatter scalars `stubs:due` already reads (`design_validated` non-empty, `capability_gap: none`, `blocker_class: estate` or `budget`) plus a defect measurable in at least two released artefacts, every one of them a field or a count rather than a judgement.",
50
+ "3_repository_evidence": "Repository evidence proves those criteria satisfied — `agent-config stubs:due --json` computes all three scalars from frontmatter rather than from body prose, so a claim appears in a diff and is reviewable, and `releases_since_blocked` counts published releases across both CHANGELOG eras, so the release criterion is measured against artefacts outside the promoting branch and cannot be self-asserted by the diff that claims it.",
51
+ "4_mechanical_cap": "The exemption is mechanically capped or expiring — `max_live` bounds how many provisional promotions may be live at once and `expires_after_days` reverts an unmerged one to `agents/roadmaps/stubs/`, both read by `check_estate_count` from this file rather than enforced by anyone remembering.",
52
+ "5_no_priority_choice": "Applying it requires no choice among competing roadmap priorities — a provisional promotion offsets nothing, so no active roadmap is retired in exchange and the pick that both 2026-08-24 council seats refused to invent, because no mechanical evidence identifies the least valuable active roadmap, never arises."
53
+ },
54
+ "max_live": null,
55
+ "expires_after_days": null,
56
+ "owner": "maintainer",
57
+ "review_by": "2027-09-01"
39
58
  }
40
59
  }
@@ -58,11 +58,11 @@
58
58
  "deterministic": false
59
59
  },
60
60
  "cli_help_command_count": {
61
- "max": 109,
62
- "last_measured": 109,
63
- "last_measured_at": "2026-08-26",
61
+ "max": 110,
62
+ "last_measured": 110,
63
+ "last_measured_at": "2026-09-03",
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. 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."
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
68
  "max": 20,