@event4u/agent-config 14.13.0 → 14.14.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 (101) 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 +116 -387
  5. package/dist/agent-src/commands/analyze/inbox.md +36 -0
  6. package/dist/agent-src/rules/code-comment-discipline.md +6 -0
  7. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +6 -6
  8. package/dist/agent-src/scripts/guarded_baseline.ts +22 -7
  9. package/dist/agent-src/scripts/stubs_due.ts +118 -2
  10. package/dist/agent-src/scripts/update_roadmap_progress.ts +8 -8
  11. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  12. package/dist/cli-delegate/cmd_conformance.js +38 -1
  13. package/dist/discovery/deprecation-report.md +1 -1
  14. package/dist/discovery/discovery-manifest.json +4 -4
  15. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  16. package/dist/discovery/discovery-manifest.summary.md +1 -1
  17. package/dist/discovery/dormancy-report.md +1 -1
  18. package/dist/discovery/orphan-report.md +1 -1
  19. package/dist/discovery/packs.json +2 -2
  20. package/dist/discovery/trust-report.md +1 -1
  21. package/dist/discovery/workspaces.json +2 -2
  22. package/dist/hooks/dispatch.js +1438 -711
  23. package/dist/mcp/registry-manifest.json +2 -2
  24. package/dist/mcp/server.json +2 -2
  25. package/dist/mcp/server.mjs +112 -17
  26. package/docs/guidelines/agent-infra/guarded-baseline.md +1 -1
  27. package/docs/guidelines/code-clarity.md +18 -0
  28. package/package.json +1 -1
  29. package/src/agent-src/scripts/archive_completed_roadmaps.ts +6 -6
  30. package/src/agent-src/scripts/guarded_baseline.ts +22 -7
  31. package/src/agent-src/scripts/stubs_due.ts +118 -2
  32. package/src/agent-src/scripts/update_roadmap_progress.ts +8 -8
  33. package/src/config/estate-count-budget.json +20 -1
  34. package/src/config/gate-coverage.yml +150 -0
  35. package/src/config/gate-violation-baselines.json +8 -3
  36. package/src/config/preamble-payload-budget.json +19 -11
  37. package/src/config/rule-stub-ceilings.json +14 -7
  38. package/src/scripts/_dispatch.bash +0 -4
  39. package/src/scripts/_lib/activation_ladder.ts +47 -3
  40. package/src/scripts/_lib/activation_receipt_producer.ts +341 -0
  41. package/src/scripts/_lib/adr_frontmatter.ts +26 -0
  42. package/src/scripts/_lib/candidate_pair_delta.ts +349 -0
  43. package/src/scripts/_lib/carrier_status.ts +118 -0
  44. package/src/scripts/_lib/cascade_stage_enumeration.ts +92 -0
  45. package/src/scripts/_lib/corpus_manifest.ts +726 -0
  46. package/src/scripts/_lib/delivery_arm_experiment.ts +18 -2
  47. package/src/scripts/_lib/estate_carrier_cases.ts +72 -0
  48. package/src/scripts/_lib/evaluation_cascade.ts +452 -0
  49. package/src/scripts/_lib/evolution_roi.ts +562 -0
  50. package/src/scripts/_lib/harness_evolution_guards.ts +10 -0
  51. package/src/scripts/_lib/lexical_shortlist.ts +215 -0
  52. package/src/scripts/_lib/llm_candidate_proposer.ts +461 -0
  53. package/src/scripts/_lib/llm_proposer_transport.ts +153 -0
  54. package/src/scripts/_lib/pathology_archive.ts +393 -0
  55. package/src/scripts/_lib/promotion_capability.ts +315 -0
  56. package/src/scripts/_lib/promotion_evidence.ts +414 -0
  57. package/src/scripts/_lib/promotion_review.ts +294 -0
  58. package/src/scripts/_lib/prompt_shape.ts +92 -0
  59. package/src/scripts/_lib/release_highlights.ts +70 -1
  60. package/src/scripts/_lib/release_material.ts +125 -2
  61. package/src/scripts/_lib/routing_corpus.ts +307 -0
  62. package/src/scripts/_lib/routing_index_input.ts +104 -0
  63. package/src/scripts/_lib/rule_injection.ts +22 -1
  64. package/src/scripts/_lib/semantic_noop.ts +167 -0
  65. package/src/scripts/activation_receipt.ts +258 -0
  66. package/src/scripts/ai_council/cross_exam.ts +128 -0
  67. package/src/scripts/ai_council/explain_route.ts +157 -0
  68. package/src/scripts/ai_council/information_gain.ts +293 -0
  69. package/src/scripts/ai_council/leakage_corpus.ts +385 -0
  70. package/src/scripts/ai_council/leakage_patterns.ts +453 -0
  71. package/src/scripts/ai_council/minority_retention.ts +171 -0
  72. package/src/scripts/ai_council/recouncil_savings.ts +299 -0
  73. package/src/scripts/ai_council/replay_route.ts +223 -0
  74. package/src/scripts/ai_council/reviewer_assignment.ts +279 -0
  75. package/src/scripts/ai_council/routing_training_row.ts +197 -0
  76. package/src/scripts/ai_council/synthesis_strategy.ts +159 -0
  77. package/src/scripts/check_adr_frontmatter.ts +4 -9
  78. package/src/scripts/check_estate_count.ts +205 -24
  79. package/src/scripts/check_release_highlights.ts +96 -13
  80. package/src/scripts/check_roadmap_trackable.ts +17 -2
  81. package/src/scripts/corpus_manifest.ts +235 -0
  82. package/src/scripts/evolution_lab.ts +181 -8
  83. package/src/scripts/hook_manifest.json +1 -1
  84. package/src/scripts/hook_manifest.yaml +28 -6
  85. package/src/scripts/hooks/comment_discipline_hook.ts +146 -0
  86. package/src/scripts/hooks/concern_registry.ts +2 -0
  87. package/src/scripts/hooks/turn_end_gate_hook.ts +9 -1
  88. package/src/scripts/language_mirror_hook.ts +14 -2
  89. package/src/scripts/lint_carrier_integrity.ts +820 -0
  90. package/src/scripts/lint_code_comments.ts +643 -0
  91. package/src/scripts/lint_plan_risk_register.ts +39 -19
  92. package/src/scripts/lint_promotion_paths.ts +779 -0
  93. package/src/scripts/llm_propose.ts +256 -0
  94. package/src/scripts/measure_delivery_sets.ts +361 -0
  95. package/src/scripts/measure_routing_signal.ts +307 -0
  96. package/src/scripts/model_rule_injection.ts +19 -3
  97. package/src/scripts/probe_council_retention.ts +495 -0
  98. package/src/scripts/release.ts +21 -37
  99. package/src/scripts/release_drill.ts +143 -0
  100. package/src/scripts/release_env.ts +35 -0
  101. package/src/scripts/release_publication.ts +84 -0
@@ -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,
@@ -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
  }
@@ -52,6 +52,15 @@
52
52
  # CI-identical argv, and 75 of those 78 are listed below.** The manifest is
53
53
  # complete for every gate that can carry a floor, covering ~25 % of the population.
54
54
  #
55
+ # RECOUNTED 2026-08-31, on the change that adds `lint_promotion_paths` — and the
56
+ # 2026-08-30 block above was already stale when this row landed, in BOTH numbers
57
+ # rather than only in the one this change moves. Recomputed on this tree:
58
+ # `count_gate_scripts()` returns **310** and `grep -c '^ - id:'` returns **81**
59
+ # (80 before this row), so 81/310 = 26.1 %. The script count rose without any
60
+ # recount, which is the drift the blocks below keep describing; it is recorded
61
+ # here rather than folded into the 2026-08-30 prose, because that block is a
62
+ # measurement of a tree that no longer exists.
63
+ #
55
64
  # RECOUNTED ON A MERGE, 2026-08-30 — the fifth time, and the same shape as the
56
65
  # 2026-08-26 blocks below: two sides each correct for their own tree (~24 % from
57
66
  # the branch, ~25 % from main) meeting in one number. Recomputed rather than
@@ -391,6 +400,24 @@ gates:
391
400
  corpus: "every agents/roadmaps/*.md, counted as FILES rather than as declarations. THE FLOOR WAS RETIRED ON 2026-08-30 AND REPLACED WITH AN ENUMERATION ASSERTION IN THE LINTER ITSELF (AI council, anthropic + openai, convergent on option b). It sat at 5 over a population under deliberate drawdown to ZERO, so it went red the day the drain succeeded — 6 active roadmaps to 4 in one afternoon — and the only available move was lowering the number again, a treadmill ending at a floor of 0, which is no floor. What guards the scope now is `lint_consolidation_lineage` refusing outright when a declared root is missing, is not a directory, or cannot be enumerated; that check lives in the PRODUCTION linter rather than here on purpose, because an independent existsSync in this gate would observe a directory without proving the linter enumerated it. Honest limit, named rather than implied: it catches a missing or renamed root, not a glob narrowed to match nothing, not enumeration in the wrong working directory, and not roadmaps moved into nested directories the walk no longer traverses. Today none of them carries a `consolidates:` field, so the finding count is legitimately zero while the scanned count is not — that split is the point: a declaration-count floor would be a zero-check wearing a ratchet's name, and would go green the moment the scan root moved. The floor sits at 5 rather than near the present 12 because the active estate is under deliberate drawdown (one-in-one-out plus an explicit drain), so a falling count is expected work rather than a collapse; below 5 the estate would be smaller than any point in its recorded history. NO CANARY, deliberately: the council decision that admitted this gate (2026-08-27, 2/2 convergent, E1=b/E2=report) fixed it in REPORT mode, so it exits 0 on findings by design and a planted violation could not turn it red. Its ability to go red is proven instead by `--self-test`, which the same decision required: six declaration spellings must parse identically, all four finding types must fire, and a complete folder must stay silent."
392
401
  status: enforced
393
402
  no_canary_reason: "A report-mode gate cannot be turned red by a planted file. The council decision that admitted this gate (2026-08-27, 2/2 convergent, E1=b/E2=report) fixed it in report mode deliberately: the four measured failures come from a high-noise drafting context and do not establish precision on the tracked estate, so blocking is earned by tracked-scope evidence rather than assumed. A canary here would either be planted and ignored (proving nothing) or force `--strict` into CI, which would decide E2 by wiring instead of by evidence. The negative control is `--self-test` instead, required by the same decision and stronger than a single planted file: six declaration spellings must parse to an identical parent set, all four finding types must fire, and a complete folder must stay silent — the third arm being what stops a checker that returned every code unconditionally from passing the second. Revisit together with E2: when the tracked half is promoted to blocking, it owes a canary."
403
+ - id: lint_carrier_integrity
404
+ argv: []
405
+ min_scanned: 600
406
+ corpus: "every roadmap file under agents/roadmaps/archive/ and agents/roadmaps/skipped/ — 656 at the time of writing, and the count only rises because archival is one-way. The floor sits at 600 rather than near 656 because a reading below it means the walk lost a directory, not that roadmaps were un-archived. The walk direction is the design: a `[~]` carry is validated once, when the parent is archived, by a sweep that then never looks at the pair again, so the receiver can be deleted afterwards with nothing going red. Starting from the archived parent — which names its destination by slug and which nothing rewrites — makes whole-file deletion detectable on the first pass, with no registry to maintain. TWO CLASSES, and the split decides the tier: `broken-destination` (deleted, renamed, re-parented, skipped, or archived with open steps) is a HARD failure at zero with no baseline, because every member had a recorded home and lost it; `unannotated` (a bare `[~]` with no annotation at all) is ratcheted at 243 in gate-violation-baselines.json, all of it predating the annotation contract by up to three months."
407
+ status: enforced
408
+ canary:
409
+ class: contract-violation
410
+ path: agents/roadmaps/archive/zz-canary-carrier-integrity.md
411
+ content: |
412
+ ---
413
+ complexity: bounded
414
+ ---
415
+ # Canary planted by check_gate_coverage --canary; never ships.
416
+
417
+ ## Phase 1 — a phase
418
+
419
+ - [~] **1.1 A step carried to a receiver that does not exist.**
420
+ <!-- deferred-resolution: carried-to=road-to-zz-canary-receiver-that-does-not-exist -->
394
421
  - id: lint_skill_trigger_corpus
395
422
  argv: []
396
423
  min_scanned: 70
@@ -2321,3 +2348,126 @@ gates:
2321
2348
  DISCRIMINATION: `--self-test`, 9 cases, 4 rejecting. The canary above
2322
2349
  documents the violating shape for a reader and is honestly NOT the
2323
2350
  discriminator, because a created file lands outside a four-name watchlist.
2351
+
2352
+ - id: lint_promotion_paths
2353
+ argv: ["--quiet"]
2354
+ min_scanned: 1800
2355
+ corpus: "every .ts file under src/ and tests/ (2812 at baseline). The floor is 1800, not 1: this gate's failure shape is a walk that stops at a renamed root or a skip-list that grows, and a partial walk certifies exactly the coverage it did not perform."
2356
+ status: enforced
2357
+ canary:
2358
+ class: contract-violation
2359
+ path: src/scripts/__canary_promotion_bypass__.ts
2360
+ content: |
2361
+ import { parseCandidateRecord } from './_lib/candidate_record.js';
2362
+ const promoted = { ...parseCandidateRecord, lifecycle: 'promoted' };
2363
+ fs.writeFileSync(path.join(REPO_ROOT, 'src', 'rules', 'x.md'), promoted);
2364
+ note: >
2365
+ road-to-harness-promotion-bridge — discharge route 1 of the carried
2366
+ blocking condition. The condition covers "a verb, a state transition into
2367
+ `promoted`, or any write into `src/` derived from a candidate", and the AI
2368
+ council of 2026-08-31 ruled it NOT DISCHARGED because only the first two
2369
+ were gated. This gate inventories all three limbs across the whole
2370
+ authored tree and fails when a promotion-capable write bypasses the single
2371
+ guarded capability in `src/scripts/_lib/promotion_capability.ts`.
2372
+
2373
+ THE CAPABILITY IS UNOBTAINABLE WHILE `blocker: merge-authority` IS OPEN,
2374
+ and R0 proves that by CALLING it rather than by reading it — a text match
2375
+ would pass over a refusal edited into a no-op. Sensitivity of R0 was
2376
+ observed: neutralising the refusal makes the gate red with
2377
+ "capability obtainable while the blocker is open".
2378
+
2379
+ ANTI-VACUITY IS THE POINT, not a bonus. The condition's own text says a
2380
+ check over a population of zero discharges nothing, so three floors are
2381
+ asserted separately — 400 files, 4 candidate-derived modules, 10
2382
+ filesystem-write sites inside them — each via `assertScanned`, each exiting
2383
+ 2 rather than green. Live values at baseline: 2812 / 16 / 66.
2384
+
2385
+ THE CANARY IS A GENUINE DISCRIMINATOR here: the corpus is a tree walk, so
2386
+ a planted file is in scope and a create-only plant exercises the real red
2387
+ path on all three limbs at once. `--self-test` adds 15 cases (9 rejecting)
2388
+ covering the forms a single plant cannot reach — the interface-declaration
2389
+ accepting pole, a two-argument `assertTransition`, a candidate-derived
2390
+ write into a CLONE's `src/` (out of scope: a clone is a candidate's own
2391
+ sandbox, already gated by `bench_ab_integrity`'s allowed-delta-path check),
2392
+ a candidate-derived READ of `src/`, and prose in a block comment.
2393
+
2394
+ TWO DEFECTS WERE FOUND BY RUNNING IT, both pinned as cases: a substitution
2395
+ that rewrote identifiers INSIDE string literals (`cand` in `'ac-cand-'`),
2396
+ and an expansion that DESTROYED the `REPO_ROOT` token the rule keys on
2397
+ because `REPO_ROOT` is itself a `const`. The first produced a false
2398
+ positive on a tmpdir removal; the second silently dropped the R3 half of a
2399
+ planted three-limb bypass, which is the failure direction that matters.
2400
+
2401
+ - id: lint_code_comments
2402
+ argv: ["--base", "origin/main"]
2403
+ min_scanned: 0
2404
+ corpus: >
2405
+ the source files a change touches, measured against the base ref — a DIFF,
2406
+ not a tree walk. The floor is 0 deliberately: a docs-only or config-only
2407
+ change legitimately carries no source file, and a floor above 0 would red
2408
+ exactly those commits. The scan-scope failure this class exists to catch
2409
+ cannot occur here, because there is no committed path list that can go
2410
+ stale — the root is `git diff`, and git answers for its own range.
2411
+ status: enforced
2412
+ canary:
2413
+ class: contract-violation
2414
+ path: src/scripts/__canary_comment_discipline__.ts
2415
+ content: |
2416
+ // Hier stehen nur die Werte, die Bedeutung tragen und nicht mehr.
2417
+ export const canary = 1;
2418
+ note: >
2419
+ The carrier `code-comment-discipline` never had, and the one the "Code
2420
+ comments English" clause of `language-and-tone` claimed via a `.md`-only
2421
+ validator (`check_md_language.ts:175` rejects every other path).
2422
+
2423
+ THE CANARY IS A GENUINE DISCRIMINATOR: the corpus is a diff, a newly
2424
+ created file is in it by construction, and the planted content trips the
2425
+ `de-comment` class on two function words with no umlaut — the
2426
+ transliterated form the real incident was written in, not the easy case.
2427
+
2428
+ DISCRIMINATION IS ALSO PINNED BY FIXTURE, in both directions, because a
2429
+ language heuristic is only usable if its near-misses are known:
2430
+ `tests/scripts/lint_code_comments.test.ts` asserts silence on an umlauted
2431
+ proper name, a non-ASCII sample value inside a code span, a `Ü2` step
2432
+ label, and a German user quotation spanning two comment lines — the four
2433
+ shapes this repository's own hooks actually contain. Measured over 402
2434
+ source files here: zero findings. Measured over the 45 changed files of
2435
+ the consumer repository that prompted the gate: 530.
2436
+
2437
+ SENSITIVITY WAS OBSERVED, not assumed: neutralising the German predicate
2438
+ turns 4 of the 20 table cases red and restoring it turns them green. (The
2439
+ earlier wording said "4 of the 22" and described the inline vitest corpus
2440
+ that the fixture-table refactor replaced; a completion review caught the
2441
+ stale denominator.)
2442
+
2443
+ THE GATE ALSO CARRIES ITS OWN `--self-test` (20 cases, 11 rejecting),
2444
+ which drives the real binary rather than the imported classifier — argv
2445
+ parsing, scan-scope assertion and exit codes included. It does NOT reach a
2446
+ consumer checkout, and the earlier claim that it did was withdrawn after a
2447
+ completion review: `runGateCli` spawns `<repoRoot>/node_modules/.bin/tsx`
2448
+ and both `tsx` and `vitest` are devDependencies, `dist/scripts/` carries no
2449
+ compiled twin, and `scripts-run` is not in `files[]` — so where the suite
2450
+ is absent the self-test cannot run either, and where it runs the suite is
2451
+ present. What it buys is real and narrower: it exercises the CLI half that
2452
+ the vitest file cannot reach, and CI runs the gate only in diff mode,
2453
+ which exits 0 on an empty diff. It is ONE table, not a second corpus: the cases
2454
+ live in `COMMENT_CASES`, exported by the gate, and the vitest file
2455
+ enumerates the same export against `scanText` while the self-test
2456
+ enumerates it against the CLI. A second table over one classifier would
2457
+ drift, and the copy nobody runs locally drifts first. Floors are set at
2458
+ 16/8 against a real 20/11, so deleting a case to reach green fails and a
2459
+ deliberate shrink shows up as a floor edit in the diff. Sensitivity
2460
+ re-observed on the self-test the same way: neutralising the German
2461
+ predicate turns exactly 4 of the 11 rejecting cases red (the three
2462
+ German-comment shapes and the reasonless escape) while the report and
2463
+ provenance cases stay green, and restoring it returns 20/20. The block-comment
2464
+ continuation and JSX cases arrived with the two blind spots fixed on
2465
+ `origin/main` and were folded into the same table on merge, so both surfaces
2466
+ cover them rather than the vitest file alone.
2467
+
2468
+ NOTE ON THE ADOPTION RATCHET, stated because the number does not move:
2469
+ `gate-self-test:registered-non-adopters` counts manifest rows that are
2470
+ `enforced` AND carry `min_scanned >= 1`. This row's floor is 0 for the
2471
+ reason the corpus field gives, so it was never in that population and
2472
+ the count stays at 24 either side of this change. The self-test is
2473
+ therefore adopted on its merits rather than to clear a ratchet.
@@ -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": 18246,
38
- "landed": "2026-08-30",
39
- "note": "Lowered 18,437 -> 18,246 ON THE MERGE, 2026-08-30, by MEASURING the merged tree rather than picking a side -- the method every prior entry here prescribes and the exact case several of them describe. Two branches of one drain run each paid their own way independently against different bases: road-to-turnaround-followups Phase 3 reached 18,437 by moving the claude-code post-copy reporting into src/install/claudeRuleRewrite.ts, and this branch reached 18,249 by moving the inline-findings concern, run_consensus_scoring and _serialise_responses into three modules under the ceiling. The merged tree measures 18,246, which is BELOW both -- neither side's number would have been correct, and keeping either would have silently given back the other's gain. Set to the EXACT live total because check_source_size_budget.test.ts asserts baseline == tree total in both directions. ONE CORRECTION CARRIED FORWARD BY HAND, because the merge would otherwise have dropped it: this branch's superseded entry had fixed a stale figure a completion review found in it -- it recorded orchestrator.ts at 2,067 lines where the tree measures 2,038 -- and that entry is gone now, taking both the false sentence and the record of its correction with it. The false sentence is what mattered and it is gone; the correction is restated here so a reader can see it was made rather than inferring the number was never wrong. `landed` refreshed deliberately: checkRatchet returns `improved` BEFORE it evaluates staleness, so an entry left at the old number while the tree measures lower would never expire at any age. Prior entry: Lowered 18,440 -> 18,437 on 2026-08-30 by road-to-turnaround-followups Phase 3, which had to pay its own way before it could pass. Calling the Claude host-form emitter from the installer charged lines to src/scripts/install.ts, which sits ~3,900 lines PAST the 1500-line threshold, so every added line counts. The fix was EXTRACTION, never a bump, and it went further than break-even: the new concern lives in src/install/claudeRuleRewrite.ts (far under the threshold and therefore free), and the claude-code flat-command WRAPPER reporting moved there with it. That reporting belongs there on merit rather than only on budget -- it is claude-code post-copy reporting exactly like the rule rewrite, the emitted messages are byte-identical, and the reviewer diffed them mechanically against HEAD~1 to confirm no guard changed scope. install.ts ends up SHORTER than before this feature. Set to the EXACT live total because check_source_size_budget.test.ts asserts baseline == tree total in BOTH directions -- a baseline left loose by even one line is a red test, not tolerated slack, and that is how this lowering surfaced rather than by inspection. NOTE ON METHOD, recorded because the first instinct here was wrong: a gate reporting its own ratchet loose is normally NOT evidence for lowering it -- only the enforcing environment's reading is, which is the lesson check_ci_local_parity taught when a local lowering met a CI count one higher. That caution does not transfer to THIS gate: its count is a line tally over tracked source, a property of the tree rather than of the environment, and its own test asserts equality, so leaving the baseline high is a red test rather than safe conservatism. `landed` refreshed deliberately: checkRatchet returns `improved` BEFORE it evaluates staleness, so an entry left at the old number while the tree measures lower would never expire at any age. Prior entry: Lowered 18,446 -> 18,440 on 2026-08-27 by fix/council-cost-truth, which paid its own way through EXTRACTION rather than a bump. Replacing three inline cost-accumulation loops in council_cli.ts with calls to sumBillableCost took that file 3,981 -> 3,975 lines, and it sits 2,475 lines PAST the 1500-line threshold, so all six removed lines count. The three new helpers -- sumBillableCost, isBillableResponse, allSeatsNonBillable -- landed in ai_council/pricing.ts, which goes 445 -> 551 lines and is therefore still FAR under the threshold: those 106 added lines are free. Net -6, which is exactly the gate's reading. pricing.ts is where they belong on merit, not only on budget: it already owns estimate_cost and the price table, so cost logic reading a price table has no other home. Set to the EXACT live total, because check_source_size_budget.test.ts asserts baseline == tree total -- and that is how this lowering surfaced: CI shard 2/4 red on both ubuntu and macOS with 'expected 18446 to be 18440', where the gate itself only advises. `landed` refreshed deliberately, not as bookkeeping: checkRatchet returns `improved` BEFORE it evaluates staleness, so an entry left at the old number while the tree measures lower would never expire at any age. Prior entry: Lowered 18,474 -> 18,446 on 2026-08-26 by road-to-consumer-repo-reality Phase 1, which had to pay its own way before it could pass. Wiring the three install-reach checks into cmd_doctor.ts charged +5 lines, because that file is ~2,150 lines PAST the 1500-line threshold and every line above it counts. The fix was extraction, never a bump: the two runtime-presence checks -- _check_python_runtime and _check_humanizer_runtime -- moved WHOLE into _lib/doctor_runtime_checks.ts. They are cohesive with each other and with nothing else in that file (each answers 'is a runtime present, and what happens when it is not'; neither reads the manifest, the drift sets, or the project root), and their one dependency, shutilWhich, is PASSED IN rather than imported -- it is defined in cmd_doctor.ts itself, so importing it back would have made the two modules circular, and a parameter is cheaper than moving a shared utility four other call sites already resolve from there. Thin wrappers keep the historical names and arity, so the runner map, the export block and the existing fallback test are untouched. cmd_doctor.ts goes 3,666 -> 3,638 lines. Set to the EXACT live total: check_source_size_budget.test.ts asserts baseline == tree total, so a baseline left loose by even one line is a RED TEST rather than tolerated slack -- which is how this lowering surfaced, in a neutral review of the branch rather than by inspection. `landed` refreshed deliberately: checkRatchet returns `improved` BEFORE it evaluates staleness, so an entry left at the old number while the tree measures lower would never expire at any age. Prior entry: Lowered 18,489 -> 18,474 on 2026-08-26 by road-to-internal-estate-fit Phase 2, which paid its own way and then some. De-duplicating SCOPE_DETECT_MANIFESTS and SCOPE_DETECT_AI_DIRS removed two byte-for-byte constant blocks from src/scripts/install.ts, which is 3,902 lines PAST the 1500-line threshold, so every removed line counts. One exported definition each now lives in src/install/detect.ts (far under the threshold and therefore free) and install.ts imports them. Net -15. Set to the EXACT live total, because check_source_size_budget.test.ts asserts baseline == tree total -- a baseline left loose by even one line is a red test, and that is how this lowering surfaced rather than by inspection: CI reported 'expected 18489 to be 18474'. `landed` refreshed deliberately, not as bookkeeping: checkRatchet returns `improved` BEFORE it evaluates staleness, so an entry left at the old number while the tree measures lower would never expire at any age. Lowered 18,511 -> 18,489 ON THE MERGE, 2026-08-26, by MEASURING the merged tree rather than picking a side — which is the method every prior entry here prescribes and the exact case several of them describe. Two branches of one drain run each paid their own way independently: road-to-skill-ecosystem-runtime-enforcement extracted the four runtime-wiring checks into _lib/runtime_wiring_checks.ts and the transcript reader into _lib/loop_guards, reaching 18,490 against a base of 18,512; road-to-skill-ecosystem-eval-integrity extracted the exact binary tests, the CPython erf port, wilcoxon and the float-parity rounder into _lib/paired_stats.ts, reaching 18,511 against the same base. The merged tree measures 18,489 — which is 18,512 - 22 - 1, i.e. the SUM of both gains, so picking either branch's number would have silently given back the other branch's. Set to the EXACT live total: check_source_size_budget.test.ts asserts baseline == tree total, so a baseline loose by even one line is a red test. Both derivations are preserved below rather than one being dropped, because each records a real extraction and the pair is the evidence for this number. Prior entry (runtime-enforcement branch): lowered 18,512 -> 18,490 on 2026-08-26. Prior entry (eval-integrity branch): lowered 18,512 -> 18,511 on 2026-08-26 by road-to-skill-ecosystem-eval-integrity Phase 2, which had to pay its own way before it could pass. Adding direction counts to `_paired_median_block` charged +24 lines, because bench_ab_v2_stats.ts was 25 lines PAST the 1500-line threshold and every line above it counts. The fix was extraction, never a bump: _lib/paired_stats.ts now holds mcnemar_exact, cohens_h, the CPython erf/normal-CDF port, wilcoxon, and the float-parity rounder those need. Every name is re-exported from bench_ab_v2_stats.ts, so `check_quality_regression`'s existing `wilcoxon` import and every other consumer resolve unchanged. bench_ab_v2_stats.ts goes 1525 -> 1329 lines, well under the threshold, so those lines are now free. Net -1 past zero rather than exactly to it, for the reason every prior entry here gives: the unit that pays is a unit. Set to the EXACT live total — check_source_size_budget.test.ts asserts baseline == tree total, so a baseline left loose by one line is a red test, and that is how this lowering surfaced rather than by inspection. `landed` refreshed deliberately: checkRatchet returns `improved` BEFORE it evaluates staleness, so an entry left at the old number while the tree measures lower would never expire at any age. Prior entry: lowered 18,561 -> 18,512 on 2026-08-24 by the same method every prior entry here prescribes — the merged tree MEASURED, never a side picked. main had reached 18,561; this branch (road-to-skill-delivery-over-mcp) carries its own extractions, and the merged total reads 18,512, a -49 gain that would have been silently given back by keeping main's number. Set to the EXACT live total, because check_source_size_budget.test.ts asserts baseline == tree total; a baseline loose by one line is a red test, and that is how this surfaced. Prior entry: lowered 18,566 -> 18,561 on 2026-08-24 by the method the two prior entries prescribe, for the same reason: independent gains met in a merge, so the merged tree was MEASURED rather than a side picked. main had reached 18,566 via road-to-skill-link-integrity-and-manifest-sync Phase 2; this branch had already paid its own extraction under road-to-trigger-delivered-rule-bodies 1.6 (condense.ts's inline mode literal moved whole to _lib/lean_projection_mode.ts, -5; the new module is 45 lines and therefore free). check_source_size_budget reports 18,561 on the merged tree, exactly 18,566 - 5, so picking either side's number would have given back the other's gain. Set to the EXACT live total: check_source_size_budget.test.ts asserts baseline == tree total, so a baseline loose by one line is a red test — which is how this lowering surfaced rather than by inspection. `landed` refreshed deliberately, not as bookkeeping: checkRatchet returns `improved` BEFORE it evaluates staleness, so an entry left at the old number while the tree measures lower would never expire at any age. Prior entry: lowered 18,574 -> 18,571 on 2026-08-23, and the -3 is the SUM of two independent lowerings-through-extraction that met in a merge, not a new reduction. main had already reached 18,573 via road-to-override-efficacy-proof (-1: _check_overrides moved whole to _cli/doctor_overrides.ts, -119, and the install-mode detector plus its marker constant to _cli/doctor_install_mode.ts, -14, both names forwarded with `export ... from`; net -1 past zero rather than exactly to it, because the unit that paid is a unit). This branch had reached 18,572 via road-to-council-evidence-integrity Phases 1-2 (-2: the two result bundles PeerReviewResult and ConsensusResult, -98, the two options interfaces, -19, and CouncilQuestion, -11, all PURE MOVES into ai_council/orchestrator_results.ts, with orchestrator.ts re-exporting every name; the options interfaces required a TYPE-ONLY back-import of OnOverrunCallback, legal and runtime-free since both directions are erased). The merge conflict was resolved by MEASURING the merged tree rather than picking a side: check_source_size_budget reports 18571 on it, which is exactly 18,574 - 1 - 2. Picking either branch's number would have silently given back the other branch's gain -- the failure main's own entry names one line above. Set to the EXACT live total: check_source_size_budget.test.ts asserts baseline == tree total, so a baseline left loose by even one line is a red test. Lowered 18,571 -> 18,566 on 2026-08-23 by road-to-skill-link-integrity-and-manifest-sync Phase 2, which had to pay its own way before it could pass: adding set_augment_manifest_version to release.ts charged +35 lines, because release.ts is 564 lines PAST the 1500-line threshold and every line above it counts. The fix was extraction, never a bump. Both manifest-version writers -- the new set_augment_manifest_version and its pre-existing twin set_marketplace_version -- moved whole into release_env.ts, the leaf that already owns both manifest paths AND jsonDumpsIndent, so neither function needed anything it did not already have there. release_env.ts is 298 lines, far under the threshold, so those lines are now free. release.ts re-exports set_marketplace_version so its existing test import keeps resolving; the new function is imported from release_env directly. Net -5 past zero rather than exactly to it, for the same reason main's entry gives: the unit that pays is a unit."
37
+ "count": 18230,
38
+ "landed": "2026-09-01",
39
+ "note": "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
  },
41
41
  "ci-parity:local-only": {
42
42
  "count": 165,
@@ -83,6 +83,11 @@
83
83
  "landed": "2026-08-30",
84
84
  "falsifier": "./scripts-run src/scripts/check_no_external_sources # the 'attribution shape' line names the current block count; the gate fails when it RISES above this entry",
85
85
  "note": "Lowered 243 -> 148 on 2026-08-30 by road-to-source-silence-cutover step 3.1. AI COUNCIL 2026-08-30, 2/2 seats present (anthropic + openai), option (a): NARROW the `source-header` class, do not delete it, and lower the baseline to the new measured total. REASON THE OLD NUMBER WAS WRONG, not merely high: the class flagged the VALUE of every `**Source:**` header that was not an ENC1 token or an opaque round id, so a correctly ANONYMISED header scored exactly like a leaking one. All 95 findings were read; not one named an external source in readable form, and six were flagging the anonymisation notice itself. One seat: the detector penalised correct behaviour. WHAT CHANGED: `sourceHeaderHits` now flags only a value carrying a readable identifier - an owner/repo slug, a domain, or an @scope/package - per the operational grammar in `readableIdentifierIn`, written out because a seat warned that 'readable slug' left unspecified 'merely moves subjective classification into code'. Both seats REFUSED option (c), deleting the class: a `**Source:**` header is where policy tells authors to record attribution and is therefore the highest-value place to keep looking. THE DELTA IS AUDITED: 148 = 127 tmp-quote + 21 repo-slug, and a full-tree recount removed exactly the 95 audited source-header findings and ADDED ZERO. The first run produced 150, on two middle-of-path segments (`uncondensed/rules`, `installer/src`) that read like slugs; both are pinned as fixtures. EVIDENCE: `tests/scripts/source_header_narrowing.test.ts` over three labeled fixtures under `tests/fixtures/source-headers/` - compliant (must not flag), internal-ref (must not flag), leaking (MUST flag, all three identifier shapes). The pre-narrowing predicate is kept as `legacySourceHeaderHits` and shadow-reported by the gate; it enforces nothing. ROLLBACK, as specified: revert the matcher and restore 243 if a readable identifier in a `**Source:**` fixture escapes detection, if the full-tree count is not reproducibly 148, or if the delta contains anything outside the audited source-header set. WHAT REMAINS IS REAL DEBT: 127 quoted speaking inbox-directory names and 21 repository slugs, 152 of them in `agents/roadmaps/archive/`. Paying it down is in-place redaction under ADR-250, which this change deliberately did not attempt. EXPIRY: 56 days from `landed`, i.e. 2026-10-25. HONEST LIMIT, added on the completion review that measured it: the narrowed `source-header` class now contributes ZERO of the 148 in-tree findings — 148 is 127 tmp-quote + 21 repo-slug — so IN THIS TREE it is operationally indistinguishable from option (c), deleting it, which both council seats refused. What separates them is the fixture corpus and nothing else, and the same review found the first corpus unrepresentative: it missed the most natural citation shape there is, a FILE inside an external repository (`somevendor/some-suite/skills/foo.md`), because the matcher judged a slug by the characters either side of it rather than by the whole token. The corpus now covers that shape in both directions, and the grammar tokenises. Nothing will notice if the class silently stops firing on real content — only the fixtures will, which is why they are the deliverable rather than a nicety."
86
+ },
87
+ "lint_carrier_integrity": {
88
+ "count": 243,
89
+ "landed": "2026-09-02",
90
+ "note": "243 pre-contract usages of `[~]` in archived roadmaps that carry no `deferred-resolution:` annotation. The annotation contract was settled by council on 2026-08-27; every file in this population predates it, the oldest by months (agents/roadmaps/archive/step-99-north-star-restructure.md:92 is 2026-05 and uses the glyph to mean 'partially green, not a regression'). They are NOT repaired: rewriting an archived roadmap's step semantics retroactively invents an intent nobody recorded, and the honest alternative glyph `[-]` is owner-reserved (archive_completed_roadmaps.ts:396). Shrink-only from here, one owner decision at a time. This ratchet covers ONLY the unannotated class; the broken-destination class -- a receiver deleted, renamed, re-parented, skipped, or archived with open steps -- is a hard failure at zero with no baseline, because deleting agents/roadmaps/road-to-council-topology-evidence-followups.md produces 38 of those and must red immediately rather than nudge a count. REMEDY WHEN THE 56-DAY NON-STAGNATION CLAUSE FIRES (from `landed`, i.e. 2026-10-28), named here because neither remedy the checker prints fits this population: \"fix the findings\" is ruled out by the sentence above -- rewriting an archived roadmap's step semantics retroactively invents an intent nobody recorded -- and \"lower the count\" is ruled out with it, since this population only shrinks when an owner decides one entry at a time. The remedy that fits is a `reaffirmed: {date, reason}` block on THIS entry (`_lib/gate_baseline.ts`), whose reason lands in the diff of the change that makes it: that is the path for debt which is genuinely unrepairable rather than merely unattended. A reaffirmation is not automatic. It asserts that the 243 were re-read and are still unrepairable, and if any of them has become repairable in the meantime the count goes down instead. Do not reaffirm at age 0 -- the clause exists so the question is asked once every 56 days, and answering it early answers nothing."
86
91
  }
87
92
  },
88
93
  "retired": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "_comment": "Per-spawn preamble payload ceiling (road-to-cache-economy Phase 3), unblocked by C-3 measuring 38.0% against a 15% bar. This is a RATCHET, not the roadmap's literal candidate numbers: those (median 40k / p95 50k) were anchored to an upstream ~37k baseline, while this package's own deterministic in-repo payload is ~102.5k tokens — a hard 40k gate would be red on the day it lands and would teach the reader to ignore it. The ratchet fails on GROWTH instead, so the number can only walk down, and the 40k/50k target stays recorded as the destination. Only the deterministic, in-repo buckets are gated: user-scope rules depend on what the developer happens to have installed globally and are therefore not CI-checkable.",
2
+ "_comment": "Per-spawn preamble payload ceiling (road-to-cache-economy Phase 3), unblocked by C-3 measuring 38.0% against a 15% bar. This is a RATCHET, not the roadmap's literal candidate numbers: those (median 40k / p95 50k) were anchored to an upstream ~37k baseline, while this package's own deterministic in-repo payload is ~102.5k tokens \u2014 a hard 40k gate would be red on the day it lands and would teach the reader to ignore it. The ratchet fails on GROWTH instead, so the number can only walk down, and the 40k/50k target stays recorded as the destination. Only the deterministic, in-repo buckets are gated: user-scope rules depend on what the developer happens to have installed globally and are therefore not CI-checkable.",
3
3
  "schema_version": 1,
4
4
  "registered_at": "2026-07-30",
5
5
  "owner": "maintainer",
@@ -12,11 +12,11 @@
12
12
  "CLAUDE.md hierarchy (project + project-local)"
13
13
  ],
14
14
  "excluded_buckets": [
15
- "user-scope rules (~/.claude/rules/*.md) — ALREADY COUNTED, inside the `project-scope rules` gated bucket; excluded here only to avoid double-counting. CORRECTED 2026-08-30 (road-to-agent-turnaround 4.2, AI council 2/2 over two rounds). The previous reason read \"machine-dependent, not CI-checkable\", and that was FALSE in both halves: the layer is written by this package's own installer out of dist/agent-src/rules/, and which rules reach the GLOBAL layer rather than the project one is decided by frontmatter (`workspaces:` naming the maintainer workspace), not by anything on the developer's machine. `preamble_byte_census.censusDeliveredRulePayload` computes it from the projection alone, reading nothing under ~/. Measured 2026-08-30: 119 projected − 15 package-only = 104 delivered, matching the installed file count exactly; the 1.5 % char gap against the machine reading is install drift. Round 1 of the council voted to GATE the bucket on the premise that its bytes were invisible to CI; round 2 was given the checker's bucket definitions, found the 104 are a SUBSET of the 119 the gate already measures in full, and chose (a′): correct the reason, add the reconciliation test, leave the baseline alone. Adding the bucket would have moved the baseline ~111k for zero additional delivered payload. What remains genuinely unreported by this GATE is the 104 / 13 / 2 destination split, which the CENSUS now prints — the council placed it there until distinct destinations need independent budget enforcement.",
16
- "tool definitions + dispatch prompt — no local source carries the request payload; the census reports it as an explicit residual",
17
- "personas (src/agent-src/personas, 36 files, ~37,181 tok) — measured ZERO standing cost 2026-08-22: .claude/personas/ is written by this repo's generators (5 in-repo references) and read by no host contract; the host's real subagent surface is .claude/agents/, which this package writes 1 file into. Reversal condition: a host publishing a personas contract, or a generator folding persona prose into an instruction file that IS read. Evidence: agents/evidence/analysis/uncounted-prose-corpora-load-semantics.md",
18
- "contexts (src/agent-src/contexts, 58 files, ~114,042 tok) — measured ON-DEMAND 2026-08-22: 0 rules declare load_context_eager: (17 declare the lazy load_context:), and rule.schema.json:49 documents eager as the variant that counts against the per-rule budget. This records that no eager declaration exists, NOT that eager loading would be free — nobody has declared one, so nobody has measured it. Evidence: agents/evidence/analysis/uncounted-prose-corpora-load-semantics.md",
19
- "skill bodies (src/skills/*/SKILL.md, 290 files, ~595,843 tok) — measured ON-DEMAND 2026-08-22: censusSkillsCatalog (preamble_byte_census.ts:216-234) builds the gated catalog bucket from `- name: description` per skill and reads no body byte, which is why 290 skills totalling ~595,843 tok report as ~14.4k standing. Corollary: the description cap is the WHOLE skill-side lever, not a fragment of it. Evidence: agents/evidence/analysis/uncounted-prose-corpora-load-semantics.md"
15
+ "user-scope rules (~/.claude/rules/*.md) \u2014 ALREADY COUNTED, inside the `project-scope rules` gated bucket; excluded here only to avoid double-counting. CORRECTED 2026-08-30 (road-to-agent-turnaround 4.2, AI council 2/2 over two rounds). The previous reason read \"machine-dependent, not CI-checkable\", and that was FALSE in both halves: the layer is written by this package's own installer out of dist/agent-src/rules/, and which rules reach the GLOBAL layer rather than the project one is decided by frontmatter (`workspaces:` naming the maintainer workspace), not by anything on the developer's machine. `preamble_byte_census.censusDeliveredRulePayload` computes it from the projection alone, reading nothing under ~/. Measured 2026-08-30: 119 projected \u2212 15 package-only = 104 delivered, matching the installed file count exactly; the 1.5 % char gap against the machine reading is install drift. Round 1 of the council voted to GATE the bucket on the premise that its bytes were invisible to CI; round 2 was given the checker's bucket definitions, found the 104 are a SUBSET of the 119 the gate already measures in full, and chose (a\u2032): correct the reason, add the reconciliation test, leave the baseline alone. Adding the bucket would have moved the baseline ~111k for zero additional delivered payload. What remains genuinely unreported by this GATE is the 104 / 13 / 2 destination split, which the CENSUS now prints \u2014 the council placed it there until distinct destinations need independent budget enforcement.",
16
+ "tool definitions + dispatch prompt \u2014 no local source carries the request payload; the census reports it as an explicit residual",
17
+ "personas (src/agent-src/personas, 36 files, ~37,181 tok) \u2014 measured ZERO standing cost 2026-08-22: .claude/personas/ is written by this repo's generators (5 in-repo references) and read by no host contract; the host's real subagent surface is .claude/agents/, which this package writes 1 file into. Reversal condition: a host publishing a personas contract, or a generator folding persona prose into an instruction file that IS read. Evidence: agents/evidence/analysis/uncounted-prose-corpora-load-semantics.md",
18
+ "contexts (src/agent-src/contexts, 58 files, ~114,042 tok) \u2014 measured ON-DEMAND 2026-08-22: 0 rules declare load_context_eager: (17 declare the lazy load_context:), and rule.schema.json:49 documents eager as the variant that counts against the per-rule budget. This records that no eager declaration exists, NOT that eager loading would be free \u2014 nobody has declared one, so nobody has measured it. Evidence: agents/evidence/analysis/uncounted-prose-corpora-load-semantics.md",
19
+ "skill bodies (src/skills/*/SKILL.md, 290 files, ~595,843 tok) \u2014 measured ON-DEMAND 2026-08-22: censusSkillsCatalog (preamble_byte_census.ts:216-234) builds the gated catalog bucket from `- name: description` per skill and reads no body byte, which is why 290 skills totalling ~595,843 tok report as ~14.4k standing. Corollary: the description cap is the WHOLE skill-side lever, not a fragment of it. Evidence: agents/evidence/analysis/uncounted-prose-corpora-load-semantics.md"
20
20
  ],
21
21
  "_bucket_classification_note": "All three additions above are EXCLUSIONS, not gated buckets, and that is the finding rather than a shortcut. A census bucket over a corpus measured at zero is a permanently green gate certifying nothing. The exclusions carry their measurement so the next pass that proposes one of these corpora as a token lever meets the number instead of the silence."
22
22
  },
@@ -30,7 +30,7 @@
30
30
  {
31
31
  "tokens": 102520,
32
32
  "at": "2026-07-31",
33
- "why": "free tightening: the measured tree came in 79 tok under the registered baseline, so the ratchet walks down to meet it. Not a claimed reduction — nothing was optimised; the point of a ratchet is that a lower measurement becomes the new ceiling instead of becoming unused headroom."
33
+ "why": "free tightening: the measured tree came in 79 tok under the registered baseline, so the ratchet walks down to meet it. Not a claimed reduction \u2014 nothing was optimised; the point of a ratchet is that a lower measurement becomes the new ceiling instead of becoming unused headroom."
34
34
  }
35
35
  ],
36
36
  "headroom_pct": 5,
@@ -71,20 +71,28 @@
71
71
  "measured_cold_start_median_tokens": 230930,
72
72
  "c3_scope_dedup_reduction_pct": 38,
73
73
  "c3_condition": "FIXTURE-ONLY as of 2026-07-31: the 38% was measured on a byte-identical two-scope fixture, and that condition is currently unreachable for every consumer because install.ts:2723/2725 stamp package:/source_path: into every installed rule while the in-repo projection stamps nothing. Aligning versions yields 0/110 twins, not 110/110. The figure is the mechanism's ceiling, NOT a realised saving, and this baseline therefore assumes no dedup. Reachability is the subject of road-to-dedup-reachability; run measure_scope_dedup for the live split.",
74
- "_comment": "The measured median comes from Claude Code transcripts (deduped by message.id + requestId) and is context for the ratchet, never the gated number — it moves with the developer's own session history."
74
+ "_comment": "The measured median comes from Claude Code transcripts (deduped by message.id + requestId) and is context for the ratchet, never the gated number \u2014 it moves with the developer's own session history."
75
75
  },
76
76
  "ci_delivery": {
77
77
  "workflow": ".github/workflows/standing-payload-delta.yml",
78
78
  "trigger": "pull_request",
79
79
  "posture": "blocking_on_ceiling_breach_with_grace_ceiling",
80
80
  "design_ceiling": 107646,
81
- "grace_ceiling": 138212,
82
- "grace_measured_at": "2026-08-24",
81
+ "grace_ceiling": 138273,
82
+ "grace_measured_at": "2026-09-02",
83
83
  "grace_end_date": "2026-11-10",
84
84
  "decision": "OPTION (b) of b-preamble-ci-placement: fold into the existing standing-payload-delta.yml rather than add a per-PR job or a scheduled main job. That workflow already computes the neighbouring number and already DECLINED to fail on the premise that this gate 'already fails on growth' -- a premise that was false, because nothing in .github/ ran this gate at all. Making it fail is the smallest change that removes the contradiction instead of adding a third surface. AI council 2026-08-24, 2/2 convergent, on a maintainer delegation.",
85
85
  "why_a_single_ci_run_is_enough": "The 'per-spawn' in this metric's name describes WHERE the cost is paid, not random sampling. The measurement is a deterministic census of revision-controlled inputs -- rules, the skill catalogue and CLAUDE.md, all read from the checkout -- so one CI run represents the checked-out revision exactly. Variation between two runs on the same commit would be a CHECKER DEFECT, never budget noise, and is asserted against by the repeatability test in tests/scripts/check_preamble_payload_budget.test.ts. Both council seats rejected the single-sample objection on this ground.",
86
86
  "why_a_grace_ceiling": "HEAD measures 138,212 against a design ceiling of 107,646 -- 28.4 % over. Making the gate blocking at the design ceiling would fail EVERY pull request from the moment it lands, which is a self-inflicted CI stop rather than a gate: nobody can reduce 30,566 tokens inside the PR that turns the gate on. The grace ceiling is set AT the measurement so growth beyond today reds immediately while today's tree passes, and it ratchets DOWN toward the design ceiling as reductions land. It may never move UP. It expires at the milestone-1 date, at which point the design ceiling applies.",
87
- "honest_limit": "Editing this workflow makes it FAIL; it does not make it REQUIRED. Whether a failing check blocks a merge is branch-protection configuration, which is a repo-admin action outside this change and outside an autonomous run. So the claim this record supports is 'the gate can now fail a PR', never 'the gate now blocks a merge'. Verify the latter with `gh api repos/:owner/:repo/rulesets` rather than by reading this file."
87
+ "honest_limit": "Editing this workflow makes it FAIL; it does not make it REQUIRED. Whether a failing check blocks a merge is branch-protection configuration, which is a repo-admin action outside this change and outside an autonomous run. So the claim this record supports is 'the gate can now fail a PR', never 'the gate now blocks a merge'. Verify the latter with `gh api repos/:owner/:repo/rulesets` rather than by reading this file.",
88
+ "grace_ceiling_history": [
89
+ {
90
+ "from": 138212,
91
+ "to": 138273,
92
+ "at": "2026-09-02",
93
+ "why": "code-comment-discipline gained three Iron Law lines: a comment naming where the code came from is evidence in the wrong place, provenance is not a WHY, and a roadmap cannot license a comment the rule forbids. +61 tok after two condensations \u2014 the section was first written at +546, moved to its pointer target (code-clarity.md) at +172, and folded into the existing Iron Law block rather than opening a second one at +61. What remains is obligation surface that preservation-guard forbids condensing further, and it cannot move to the guideline because a rule whose Iron Law is elsewhere is a rule nobody reads. The defect it closes was measured, not argued: 509 German comment lines and 21 report- or provenance-shaped comments across 41 of 45 changed source files in a consumer repository, with both governing rules holding an enforcement hole \u2014 one naming a .md-only validator for a source-file clause, the other naming no enforcer at all. This raises the GRACE ceiling, which is a dated window (expires 2026-11-10), not the design ceiling of 107646, which this tree passed long before this change."
94
+ }
95
+ ]
88
96
  },
89
97
  "status_2026_08_24": {
90
98
  "state": "at_risk",
@@ -1,5 +1,5 @@
1
1
  {
2
- "_comment": "Per-rule stub ceilings (road-to-rule-stub-projection Phase 2). One exact-BPE token ceiling per rule carrying a migration pointer. SHRINK-ONLY: a rule may fall below its ceiling freely, and may not rise above it. Raising a ceiling to clear a failing check is the config-weakening move this repo blocks by construction — every raise needs a `history` entry whose `reason` is a real sentence. The ceilings are a floor-plus-residue SNAPSHOT taken at adoption, not a target: a rule at its ceiling is not thereby correct, only not worse. Regenerate with `check_rule_stub_ceiling --write-baseline`; never hand-edit the counts.",
2
+ "_comment": "Per-rule stub ceilings (road-to-rule-stub-projection Phase 2). One exact-BPE token ceiling per rule carrying a migration pointer. SHRINK-ONLY: a rule may fall below its ceiling freely, and may not rise above it. Raising a ceiling to clear a failing check is the config-weakening move this repo blocks by construction \u2014 every raise needs a `history` entry whose `reason` is a real sentence. The ceilings are a floor-plus-residue SNAPSHOT taken at adoption, not a target: a rule at its ceiling is not thereby correct, only not worse. Regenerate with `check_rule_stub_ceiling --write-baseline`; never hand-edit the counts.",
3
3
  "measured_at_commit": "580cb1138",
4
4
  "tokens_exact": true,
5
5
  "ceilings": {
@@ -12,7 +12,7 @@
12
12
  "brand-consistency": 133,
13
13
  "broken-access-control": 743,
14
14
  "cli-output-handling": 83,
15
- "code-comment-discipline": 692,
15
+ "code-comment-discipline": 750,
16
16
  "commit-conventions": 136,
17
17
  "context-hygiene": 2470,
18
18
  "copilot-routing": 128,
@@ -53,21 +53,21 @@
53
53
  "rule": "roadmap-progress-sync",
54
54
  "from": 1424,
55
55
  "to": 2117,
56
- "reason": "Iron Law 3 gained the preservation test that decides WHO resolves a deferred item — a routing table plus the two council refinements that make it binding (a follow-up naming a roadmap that does not exist yet fails closed to the user; a council resolution is recorded at the item or it did not happen) and the residual limit both authoring seats named. This is obligation surface, not explanation: the table decides a route and the two refinements are the conditions under which the route is legal, so a reader who reaches only the pointer target has already made the decision. It could not move to the pointer target either — roadmap-progress-mechanics.md sits at the 16,000-char check_depth_budget ceiling in this same change, and pushing prose there reds a count ratchet whose whole job is refusing a fifth over-ceiling file. The section was condensed once before this raise (2201 -> 2117) by turning four prose paragraphs into bullets; further cutting would delete substance, which preservation-guard forbids."
56
+ "reason": "Iron Law 3 gained the preservation test that decides WHO resolves a deferred item \u2014 a routing table plus the two council refinements that make it binding (a follow-up naming a roadmap that does not exist yet fails closed to the user; a council resolution is recorded at the item or it did not happen) and the residual limit both authoring seats named. This is obligation surface, not explanation: the table decides a route and the two refinements are the conditions under which the route is legal, so a reader who reaches only the pointer target has already made the decision. It could not move to the pointer target either \u2014 roadmap-progress-mechanics.md sits at the 16,000-char check_depth_budget ceiling in this same change, and pushing prose there reds a count ratchet whose whole job is refusing a fifth over-ceiling file. The section was condensed once before this raise (2201 -> 2117) by turning four prose paragraphs into bullets; further cutting would delete substance, which preservation-guard forbids."
57
57
  },
58
58
  {
59
59
  "date": "2026-08-20",
60
60
  "rule": "context-hygiene",
61
61
  "from": 2447,
62
62
  "to": 2470,
63
- "reason": "One sentence, +23 tokens, and it is obligation surface rather than explanation: for CI, do not hand-write the wait loop. The mechanics went to the pointer target where this rule's own migration note already sends them (context-hygiene-mechanics gains the exit codes, the four not-settled states and the measured incident) — that move is what took the growth from +179 to +23, and the residue is the pointer itself, which cannot live in the guideline because a rule that does not name the helper is a rule nobody follows to it. The incident it encodes: a hand-written `until ! gh pr checks N | grep -q pending` exited when the API failed, because the error text contains no `pending`, and the session reported a settle that had not happened and retracted it in the next reply. The rule's neighbouring waiter law already says one waiter per condition; what it did not say is that the exit condition is the part that gets written wrong, which is why the helper is named here rather than described."
63
+ "reason": "One sentence, +23 tokens, and it is obligation surface rather than explanation: for CI, do not hand-write the wait loop. The mechanics went to the pointer target where this rule's own migration note already sends them (context-hygiene-mechanics gains the exit codes, the four not-settled states and the measured incident) \u2014 that move is what took the growth from +179 to +23, and the residue is the pointer itself, which cannot live in the guideline because a rule that does not name the helper is a rule nobody follows to it. The incident it encodes: a hand-written `until ! gh pr checks N | grep -q pending` exited when the API failed, because the error text contains no `pending`, and the session reported a settle that had not happened and retracted it in the next reply. The rule's neighbouring waiter law already says one waiter per condition; what it did not say is that the exit condition is the part that gets written wrong, which is why the helper is named here rather than described."
64
64
  },
65
65
  {
66
66
  "date": "2026-08-20",
67
67
  "rule": "roadmap-progress-sync",
68
68
  "from": 2117,
69
69
  "to": 2185,
70
- "reason": "One sentence in Iron Law 1, +68 tokens, and it is obligation surface rather than explanation: `roadmap:progress` now passes `--archive`, so the law's operational meaning changed — a complete roadmap is moved by the same run instead of reported to a human, and the two exclusions a reader must know before trusting that (`--check` never archives, the PostToolUse hook never archives) are exactly the part that cannot be deferred to a pointer. The prose was NOT moved to docs/guidelines/agent-infra/roadmap-progress-mechanics.md, which is where this rule's migration note sends its mechanics, because that file has roughly a hundred characters of headroom under the 16,000-char depth ceiling and check_depth_budget is shrink-only with four files already over it — a FIFTH over-ceiling file reds, so moving 400 characters there would have traded this gate for that one. The full contract, the council record and its recorded dissent live with the code in src/agent-src/scripts/archival_sweep.ts, which is the third home and the one with room."
70
+ "reason": "One sentence in Iron Law 1, +68 tokens, and it is obligation surface rather than explanation: `roadmap:progress` now passes `--archive`, so the law's operational meaning changed \u2014 a complete roadmap is moved by the same run instead of reported to a human, and the two exclusions a reader must know before trusting that (`--check` never archives, the PostToolUse hook never archives) are exactly the part that cannot be deferred to a pointer. The prose was NOT moved to docs/guidelines/agent-infra/roadmap-progress-mechanics.md, which is where this rule's migration note sends its mechanics, because that file has roughly a hundred characters of headroom under the 16,000-char depth ceiling and check_depth_budget is shrink-only with four files already over it \u2014 a FIFTH over-ceiling file reds, so moving 400 characters there would have traded this gate for that one. The full contract, the council record and its recorded dissent live with the code in src/agent-src/scripts/archival_sweep.ts, which is the third home and the one with room."
71
71
  },
72
72
  {
73
73
  "date": "2026-08-20",
@@ -95,7 +95,7 @@
95
95
  "rule": "design-fidelity",
96
96
  "from": 2245,
97
97
  "to": 2540,
98
- "reason": "One subsection, +295 tokens, for the second axis: artifact MATURITY is a property of the handed-over artifact, instruction MANDATE is a property of the prose, and `strict` on a self-declared wireframe is a structure mandate rather than a pixel mandate. It is an Iron Law plus the two discriminator clauses that decide it -- obligation surface, which this repository's rule/mechanics split puts in the rule by construction. The lookup half ALREADY went to the pointer target in the same change: the maturity-to-spec table and the pinned `daf-wireframe-near-miss` row live in design-fidelity-mechanics.md § Artifact maturity, and the rule links them. Pushing the remaining prose there too is the one move that is not available -- that migration is exactly what took the mechanics file from 15,265 to 16,524 chars past check_depth_budget's 16,000 ceiling, and paying for it required splitting the asset & imagery block out into its own guideline in this same commit series. A second push would re-red the ratchet the split just cleared. Raised rather than condensed because the two clauses that would compress are the ones that carry the decision: 'the discriminator reads the ARTEFACT, never the prose' and 'an undeclared artifact is treated as finished' -- drop either and the rule authorises the redesign it exists to prevent."
98
+ "reason": "One subsection, +295 tokens, for the second axis: artifact MATURITY is a property of the handed-over artifact, instruction MANDATE is a property of the prose, and `strict` on a self-declared wireframe is a structure mandate rather than a pixel mandate. It is an Iron Law plus the two discriminator clauses that decide it -- obligation surface, which this repository's rule/mechanics split puts in the rule by construction. The lookup half ALREADY went to the pointer target in the same change: the maturity-to-spec table and the pinned `daf-wireframe-near-miss` row live in design-fidelity-mechanics.md \u00a7 Artifact maturity, and the rule links them. Pushing the remaining prose there too is the one move that is not available -- that migration is exactly what took the mechanics file from 15,265 to 16,524 chars past check_depth_budget's 16,000 ceiling, and paying for it required splitting the asset & imagery block out into its own guideline in this same commit series. A second push would re-red the ratchet the split just cleared. Raised rather than condensed because the two clauses that would compress are the ones that carry the decision: 'the discriminator reads the ARTEFACT, never the prose' and 'an undeclared artifact is treated as finished' -- drop either and the rule authorises the redesign it exists to prevent."
99
99
  },
100
100
  {
101
101
  "date": "2026-08-23",
@@ -116,7 +116,7 @@
116
116
  "rule": "improve-before-implement",
117
117
  "from": 845,
118
118
  "to": 1385,
119
- "reason": "road-to-evidence-gated-change Phase 3 lands D5 in this rule: the existence question was inert on bug fixes and trivial changes, which are the classes that most often re-add a helper the tree already has. +540 tokens, and each of the four additions is obligation surface rather than lookup material — the narrowed exclusion (the three heavy checks stay excluded, the one cheap question does not) with the diff-decidable skip that keeps it cheap, the six named verdicts with negative evidence owed for `new`, the two clauses that keep the set from degrading into a ranking or into similarity-driven abstraction, and the recorded reason the gate is not routed through /tdd. Migrating any of it to the pointer target is the one move that is not available: docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md sits at 15,992 of the 16,000-char check_depth_budget ceiling — eight characters — and that gate is a shrink-only ratchet already carrying four baseline violations whose own note says a FIFTH over-ceiling file reds. Raised rather than condensed because the clauses that would compress are the ones carrying the decision: drop the heavy/cheap split and the question becomes a tax on the highest-frequency change class (risk-register row 3 of that roadmap), drop the negative-evidence clause and `new` becomes the default verdict again."
119
+ "reason": "road-to-evidence-gated-change Phase 3 lands D5 in this rule: the existence question was inert on bug fixes and trivial changes, which are the classes that most often re-add a helper the tree already has. +540 tokens, and each of the four additions is obligation surface rather than lookup material \u2014 the narrowed exclusion (the three heavy checks stay excluded, the one cheap question does not) with the diff-decidable skip that keeps it cheap, the six named verdicts with negative evidence owed for `new`, the two clauses that keep the set from degrading into a ranking or into similarity-driven abstraction, and the recorded reason the gate is not routed through /tdd. Migrating any of it to the pointer target is the one move that is not available: docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md sits at 15,992 of the 16,000-char check_depth_budget ceiling \u2014 eight characters \u2014 and that gate is a shrink-only ratchet already carrying four baseline violations whose own note says a FIFTH over-ceiling file reds. Raised rather than condensed because the clauses that would compress are the ones carrying the decision: drop the heavy/cheap split and the question becomes a tax on the highest-frequency change class (risk-register row 3 of that roadmap), drop the negative-evidence clause and `new` becomes the default verdict again."
120
120
  },
121
121
  {
122
122
  "date": "2026-08-27",
@@ -124,6 +124,13 @@
124
124
  "from": 680,
125
125
  "to": 690,
126
126
  "reason": "road-to-composition-before-creation Phase 3.3 makes Phase B's verdict a WRITTEN record rather than only a reasoned one, so the rule has to name the field that carries it. +74 tokens, 680 -> 754, and the split was deliberate: everything that is lookup material \u2014 the field's YAML shape, the four dispositions with their discriminators, why `none_found` must exist so an author is never pushed to invent an incumbent, why `create_separate` is the value no incumbent vocabulary can express, and what the lint can and cannot check \u2014 went to the pointer target (`docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md` \u00a7 Where the answer is written). What stayed is obligation surface and cannot be moved without the rule mandating a field it does not name: that the verdict is written into the artifact, the field's name, and the measurement behind it (1 of the 20 most recently added skills and rules carried a machine-readable record \u2014 `agents/evidence/analysis/authoring-search-record-sample-2026-08-27.md`). Net +10 (680 -> 690), and the drafting history is the part worth recording because the net hides it: the first draft added +248 (to 928) and FOUR shrink passes brought it back -- 928 -> 783 -> 754 -> 690 -- the last one forced by a different gate rather than chosen. An independent review read the earlier wording as contradicting the structured `from: 680, to: 690`; one seat called it cumbersome rather than wrong. Stated both ways now: initial draft +248, final net +10. `check_preamble_payload_budget` reds at 138,212 tokens -- its grace ceiling, set to the exact measured total on 2026-08-24 with zero slack and expiring 2026-11-10 -- and `origin/main` measures 138,195, so this branch had **17 tokens** of headroom for the whole rule payload, not 74. The addition was therefore folded into the existing Phase-B parenthetical instead of added as a paragraph, which is both cheaper and better placed: the field is now named where the phase is described. Measured after: 138,208, four under the ceiling. The ceiling here is the exact measured count with no slack, so the next growth is caught rather than absorbed."
127
+ },
128
+ {
129
+ "date": "2026-09-02",
130
+ "rule": "code-comment-discipline",
131
+ "from": 692,
132
+ "to": 750,
133
+ "reason": "Three Iron Law lines folded into the rule's existing block: a comment naming where the code came from is evidence in the wrong place, provenance is not a WHY, and a roadmap cannot license a comment this rule forbids. That is obligation surface \u2014 it decides what an author may write \u2014 and preservation-guard forbids condensing an Iron Law further. Everything explanatory went to the pointer target the rule already names: the measured case (509 German comment lines and 21 report- or provenance-shaped comments across 41 of 45 changed source files in a consumer repository, including a 101-line colour-analysis report inside a CSS file), the reason the rule's own keyword triggers miss the write, and the two enforcers. Three condensations took the growth from +546 to +58: move to the guideline, then fold into the existing Iron Law block rather than open a second section. The ceiling is set at the measured body rather than above it \u2014 headroom nobody has a use for is the spendable-surplus failure this file's own comment names."
127
134
  }
128
135
  ]
129
136
  }
@@ -764,10 +764,6 @@ cmd_roadmap_progress() {
764
764
  # The parked estate's front door — read-only, and deliberately so. It authors
765
765
  # nothing under `agents/roadmaps/stubs/`, which is what keeps it clear of the
766
766
  # 2026-08-21 council verdict that deleted the hand-maintained index there.
767
- cmd_stubs_due() {
768
- exec_ts "$PACKAGE_ROOT/src/scripts/stubs_due.ts" "$@"
769
- }
770
-
771
767
  cmd_roadmap_progress_check() {
772
768
  local script
773
769
  script="$(resolve_script "dist/agent-src/scripts/update_roadmap_progress.ts" ".augment/scripts/update_roadmap_progress.ts")"