@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
@@ -52,6 +52,23 @@
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-09-03, on the change that adds `check_agent_artifact_location`.
56
+ # Recomputed on this tree rather than incremented: `count_gate_scripts()` returns
57
+ # **313** and `grep -c '^ - id:'` returns **84** (83 before this row), so
58
+ # 84/313 = 26.8 %. The script count rose by three since the 2026-08-31 block
59
+ # below without any recount in between — the same drift every block here keeps
60
+ # describing, and the reason this one is a new block rather than an edit to that
61
+ # one: that block measured a tree that no longer exists.
62
+
63
+ # RECOUNTED 2026-08-31, on the change that adds `lint_promotion_paths` — and the
64
+ # 2026-08-30 block above was already stale when this row landed, in BOTH numbers
65
+ # rather than only in the one this change moves. Recomputed on this tree:
66
+ # `count_gate_scripts()` returns **310** and `grep -c '^ - id:'` returns **81**
67
+ # (80 before this row), so 81/310 = 26.1 %. The script count rose without any
68
+ # recount, which is the drift the blocks below keep describing; it is recorded
69
+ # here rather than folded into the 2026-08-30 prose, because that block is a
70
+ # measurement of a tree that no longer exists.
71
+ #
55
72
  # RECOUNTED ON A MERGE, 2026-08-30 — the fifth time, and the same shape as the
56
73
  # 2026-08-26 blocks below: two sides each correct for their own tree (~24 % from
57
74
  # the branch, ~25 % from main) meeting in one number. Recomputed rather than
@@ -391,6 +408,24 @@ gates:
391
408
  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
409
  status: enforced
393
410
  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."
411
+ - id: lint_carrier_integrity
412
+ argv: []
413
+ min_scanned: 600
414
+ 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."
415
+ status: enforced
416
+ canary:
417
+ class: contract-violation
418
+ path: agents/roadmaps/archive/zz-canary-carrier-integrity.md
419
+ content: |
420
+ ---
421
+ complexity: bounded
422
+ ---
423
+ # Canary planted by check_gate_coverage --canary; never ships.
424
+
425
+ ## Phase 1 — a phase
426
+
427
+ - [~] **1.1 A step carried to a receiver that does not exist.**
428
+ <!-- deferred-resolution: carried-to=road-to-zz-canary-receiver-that-does-not-exist -->
394
429
  - id: lint_skill_trigger_corpus
395
430
  argv: []
396
431
  min_scanned: 70
@@ -738,7 +773,7 @@ gates:
738
773
 
739
774
  - id: check_requirements_trace
740
775
  argv: []
741
- min_scanned: 2
776
+ min_scanned: 1
742
777
  corpus: "roadmap files at the top level of agents/roadmaps/ (25 at registration, 2026-08-22; 9 live on 2026-08-23, mid-drain). The floor tracks a corpus that shrinks by design, so completing and archiving a roadmap lowers it — it is never satisfied by un-archiving finished work. LOWERED 10 -> 2 on 2026-08-23. The 15 -> 10 move earlier the same day already recorded this shape and under-shot it: eight further PRs were open at that moment, each one titled `roadmap: complete <slug>` and each archiving exactly one top-level file, so the estate this floor tracks was already committed to landing at one or two. A floor of 10 against a corpus heading for 2 does not detect anything — it reds on every remaining merge in the drain, which is the gate firing on the drain WORKING, and the note directly above says so in its own words. 2 is chosen as one above the only reading this floor exists to catch: a scan whose root moved reports 0, never 2, so the collapse signal survives the move intact. What is given up is the middle band, and it was never load-bearing here — this gate is a LISTING (see no_canary_reason: `main()` returns 0 on every path), so the floor is its only failure mode and a partial-corpus read is not something it could act on anyway. Revisit-if: the drain ends and the estate stabilises above 2 — re-derive the floor from what the estate is then expected to hold, and never from the live count, which reds on the next archive by construction."
743
778
  status: enforced
744
779
  no_canary_reason: >-
@@ -2321,3 +2356,178 @@ gates:
2321
2356
  DISCRIMINATION: `--self-test`, 9 cases, 4 rejecting. The canary above
2322
2357
  documents the violating shape for a reader and is honestly NOT the
2323
2358
  discriminator, because a created file lands outside a four-name watchlist.
2359
+
2360
+ - id: lint_promotion_paths
2361
+ argv: ["--quiet"]
2362
+ min_scanned: 1800
2363
+ 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."
2364
+ status: enforced
2365
+ canary:
2366
+ class: contract-violation
2367
+ path: src/scripts/__canary_promotion_bypass__.ts
2368
+ content: |
2369
+ import { parseCandidateRecord } from './_lib/candidate_record.js';
2370
+ const promoted = { ...parseCandidateRecord, lifecycle: 'promoted' };
2371
+ fs.writeFileSync(path.join(REPO_ROOT, 'src', 'rules', 'x.md'), promoted);
2372
+ note: >
2373
+ road-to-harness-promotion-bridge — discharge route 1 of the carried
2374
+ blocking condition. The condition covers "a verb, a state transition into
2375
+ `promoted`, or any write into `src/` derived from a candidate", and the AI
2376
+ council of 2026-08-31 ruled it NOT DISCHARGED because only the first two
2377
+ were gated. This gate inventories all three limbs across the whole
2378
+ authored tree and fails when a promotion-capable write bypasses the single
2379
+ guarded capability in `src/scripts/_lib/promotion_capability.ts`.
2380
+
2381
+ THE CAPABILITY IS UNOBTAINABLE WHILE `blocker: merge-authority` IS OPEN,
2382
+ and R0 proves that by CALLING it rather than by reading it — a text match
2383
+ would pass over a refusal edited into a no-op. Sensitivity of R0 was
2384
+ observed: neutralising the refusal makes the gate red with
2385
+ "capability obtainable while the blocker is open".
2386
+
2387
+ ANTI-VACUITY IS THE POINT, not a bonus. The condition's own text says a
2388
+ check over a population of zero discharges nothing, so three floors are
2389
+ asserted separately — 400 files, 4 candidate-derived modules, 10
2390
+ filesystem-write sites inside them — each via `assertScanned`, each exiting
2391
+ 2 rather than green. Live values at baseline: 2812 / 16 / 66.
2392
+
2393
+ THE CANARY IS A GENUINE DISCRIMINATOR here: the corpus is a tree walk, so
2394
+ a planted file is in scope and a create-only plant exercises the real red
2395
+ path on all three limbs at once. `--self-test` adds 15 cases (9 rejecting)
2396
+ covering the forms a single plant cannot reach — the interface-declaration
2397
+ accepting pole, a two-argument `assertTransition`, a candidate-derived
2398
+ write into a CLONE's `src/` (out of scope: a clone is a candidate's own
2399
+ sandbox, already gated by `bench_ab_integrity`'s allowed-delta-path check),
2400
+ a candidate-derived READ of `src/`, and prose in a block comment.
2401
+
2402
+ TWO DEFECTS WERE FOUND BY RUNNING IT, both pinned as cases: a substitution
2403
+ that rewrote identifiers INSIDE string literals (`cand` in `'ac-cand-'`),
2404
+ and an expansion that DESTROYED the `REPO_ROOT` token the rule keys on
2405
+ because `REPO_ROOT` is itself a `const`. The first produced a false
2406
+ positive on a tmpdir removal; the second silently dropped the R3 half of a
2407
+ planted three-limb bypass, which is the failure direction that matters.
2408
+
2409
+ - id: lint_code_comments
2410
+ argv: ["--base", "origin/main"]
2411
+ min_scanned: 0
2412
+ corpus: >
2413
+ the source LINES a change adds, measured against the base ref — a DIFF,
2414
+ not a tree walk. The floor is 0 deliberately: a docs-only or config-only
2415
+ change legitimately carries no source file, and a floor above 0 would red
2416
+ exactly those commits. The scan-scope failure this class exists to catch
2417
+ cannot occur here, because there is no committed path list that can go
2418
+ stale — the root is `git diff`, and git answers for its own range.
2419
+
2420
+ LINES, not files, since 2026-09-02. Selecting the file set from the diff
2421
+ while scanning each file whole made every edit to an old file carry that
2422
+ file's whole comment history: PR #1813 added no flagged comment and was
2423
+ refused on eleven legacy lines in four files it had touched for unrelated
2424
+ reasons. An untracked file still counts whole — every line in it is new,
2425
+ which is also what keeps the canary below a genuine discriminator.
2426
+ status: enforced
2427
+ canary:
2428
+ class: contract-violation
2429
+ path: src/scripts/__canary_comment_discipline__.ts
2430
+ content: |
2431
+ // Hier stehen nur die Werte, die Bedeutung tragen und nicht mehr.
2432
+ export const canary = 1;
2433
+ note: >
2434
+ The carrier `code-comment-discipline` never had, and the one the "Code
2435
+ comments English" clause of `language-and-tone` claimed via a `.md`-only
2436
+ validator (`check_md_language.ts:175` rejects every other path).
2437
+
2438
+ THE CANARY IS A GENUINE DISCRIMINATOR: the corpus is a diff, a newly
2439
+ created file is in it by construction, and the planted content trips the
2440
+ `de-comment` class on two function words with no umlaut — the
2441
+ transliterated form the real incident was written in, not the easy case.
2442
+
2443
+ DISCRIMINATION IS ALSO PINNED BY FIXTURE, in both directions, because a
2444
+ language heuristic is only usable if its near-misses are known:
2445
+ `tests/scripts/lint_code_comments.test.ts` asserts silence on an umlauted
2446
+ proper name, a non-ASCII sample value inside a code span, a `Ü2` step
2447
+ label, and a German user quotation spanning two comment lines — the four
2448
+ shapes this repository's own hooks actually contain. Measured over 402
2449
+ source files here: zero findings. Measured over the 45 changed files of
2450
+ the consumer repository that prompted the gate: 530.
2451
+
2452
+ SENSITIVITY WAS OBSERVED, not assumed: neutralising the German predicate
2453
+ turns 4 of the 20 table cases red and restoring it turns them green. (The
2454
+ earlier wording said "4 of the 22" and described the inline vitest corpus
2455
+ that the fixture-table refactor replaced; a completion review caught the
2456
+ stale denominator.)
2457
+
2458
+ THE GATE ALSO CARRIES ITS OWN `--self-test` (20 cases, 11 rejecting),
2459
+ which drives the real binary rather than the imported classifier — argv
2460
+ parsing, scan-scope assertion and exit codes included. It does NOT reach a
2461
+ consumer checkout, and the earlier claim that it did was withdrawn after a
2462
+ completion review: `runGateCli` spawns `<repoRoot>/node_modules/.bin/tsx`
2463
+ and both `tsx` and `vitest` are devDependencies, `dist/scripts/` carries no
2464
+ compiled twin, and `scripts-run` is not in `files[]` — so where the suite
2465
+ is absent the self-test cannot run either, and where it runs the suite is
2466
+ present. What it buys is real and narrower: it exercises the CLI half that
2467
+ the vitest file cannot reach, and CI runs the gate only in diff mode,
2468
+ which exits 0 on an empty diff. It is ONE table, not a second corpus: the cases
2469
+ live in `COMMENT_CASES`, exported by the gate, and the vitest file
2470
+ enumerates the same export against `scanText` while the self-test
2471
+ enumerates it against the CLI. A second table over one classifier would
2472
+ drift, and the copy nobody runs locally drifts first. Floors are set at
2473
+ 16/8 against a real 20/11, so deleting a case to reach green fails and a
2474
+ deliberate shrink shows up as a floor edit in the diff. Sensitivity
2475
+ re-observed on the self-test the same way: neutralising the German
2476
+ predicate turns exactly 4 of the 11 rejecting cases red (the three
2477
+ German-comment shapes and the reasonless escape) while the report and
2478
+ provenance cases stay green, and restoring it returns 20/20. The block-comment
2479
+ continuation and JSX cases arrived with the two blind spots fixed on
2480
+ `origin/main` and were folded into the same table on merge, so both surfaces
2481
+ cover them rather than the vitest file alone.
2482
+
2483
+ NOTE ON THE ADOPTION RATCHET, stated because the number does not move:
2484
+ `gate-self-test:registered-non-adopters` counts manifest rows that are
2485
+ `enforced` AND carry `min_scanned >= 1`. This row's floor is 0 for the
2486
+ reason the corpus field gives, so it was never in that population and
2487
+ the count stays at 24 either side of this change. The self-test is
2488
+ therefore adopted on its merits rather than to clear a ratchet.
2489
+
2490
+ - id: check_agent_artifact_location
2491
+ argv: []
2492
+ min_scanned: 3000
2493
+ corpus: >
2494
+ every `.md` file the whole-tree walk VISITS, not the roadmap-shaped ones it
2495
+ matches. Zero matches is the normal clean state — the repository is
2496
+ expected to have no misplaced roadmap — so only the unfiltered walk can
2497
+ tell "nothing misplaced" from "nothing read", which is the exact
2498
+ distinction this manifest exists to enforce.
2499
+
2500
+ The floor is 3,000 against a real 3,866 on the tree carrying this row.
2501
+ Set well below the true corpus because `docs/archive/` and the roadmap
2502
+ archive legitimately grow and shrink, and far above a collapse: the failure
2503
+ mode this guards is the walk root moving, which would take the count to
2504
+ single digits rather than to 3,000.
2505
+
2506
+ The walk skips `.git`, `node_modules`, `dist/` and the per-tool projection
2507
+ trees (`.claude/`, `.cursor/`, `.augment/`, `.clinerules/`, `.agents/`).
2508
+ Those are generated copies, so a finding there is a finding about `src/`
2509
+ reported twice — excluded from the corpus deliberately, which is why the
2510
+ count is 3,866 rather than the full tree's markdown population.
2511
+
2512
+ SELF-TEST ADOPTED. `--self-test` runs 5 cases, 2 rejecting, against the
2513
+ real CLI over throwaway trees: a roadmap-shaped file under
2514
+ `docs/roadmaps/` and one at the tree root must RED; the byte-identical
2515
+ file under `agents/roadmaps/` and under `agents/roadmaps/later/` must stay
2516
+ GREEN, and so must prose that carries frontmatter and a checkbox but no
2517
+ `## Phase` heading. Both polarities, which is what the originating
2518
+ roadmap's step 1.1 required in as many words: "Both directions, or the
2519
+ detector's polarity is untested."
2520
+ canary:
2521
+ class: contract-violation
2522
+ path: docs/roadmaps/zz-canary-artifact-location.md
2523
+ content: |
2524
+ ---
2525
+ complexity: lightweight
2526
+ status: ready
2527
+ ---
2528
+ # Canary planted by check_gate_coverage --canary; never ships.
2529
+
2530
+ ## Phase 1 - the planted phase
2531
+
2532
+ - [ ] **1.1 A planted step.** Carries all three shape signals so the
2533
+ gate must report it from a directory that is not the roadmap root.
@@ -16,6 +16,7 @@
16
16
  "mcp:glama-drift-check": "Queries an external registry — network-dependent and flaky by construction. Would run against a recorded snapshot instead of a live query.",
17
17
  "migration-verify": "Verifies a physical move during a migration; there is no migration in flight. Would run during one.",
18
18
  "value": "Renders a dashboard; the render is the product, not a check. Nothing would make it a gate.",
19
+ "push-ready": "NOT A GATE — a developer-invoked helper that MUTATES the tree: it merges the base set in, regenerates derived files, and hands the push back to the contributor. It is gate-shaped only because step 5 calls `check_branch_freshness`, which IS wired to CI on its own. Wiring this target would make CI perform merges, which is the exact thing `road-to-cascading-base-integration` 2.2 forbids the pre-push hook from doing one layer down. Nothing would make it a gate; the checks inside it are wired separately.",
19
20
  "value:lint": "Lints that dashboard, which is generated and regenerated on demand. Would run if the dashboard became a committed artefact a drift could break."
20
21
  }
21
22
  }
@@ -34,9 +34,10 @@
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": 18205,
38
+ "landed": "2026-09-03",
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
+ "reason": "Lowered 18,230 -> 18,209 on 2026-09-03 by drain/artifact-location. `doctor --strict` needed 16 lines of integration wiring in cmd_doctor.ts, which sits ~2,100 lines PAST the 1500 cap so every line there costs one excess. The branch first came in at +137 and paid its own way down rather than raising the baseline: the strict semantics, threshold, kill switch, payload helper and argv fragments moved to _cli/_doctor_strict.ts (~160 lines, under the cap, costs nothing), taking it to +15; then the 37-line drift renderer moved to _cli/_doctor_emit.ts. Net -21. The renderer was chosen deliberately: pure rendering, two dependencies, no argparse involvement and therefore none of the byte-parity risk the parser surface carries against the retired Python implementation; nothing outside cmd_doctor.ts imported it. An AI council on the residual split (a: raise by 15 / d: descope the feature); neither was taken, because a parallel branch demonstrated the same day that paying down is achievable in this codebase, which is the evidence the split lacked. This gate's own test comment prescribes it: a commit that splits a god-file lowers the excess and must carry the lowered baseline. MERGE RECONCILIATION 2026-09-03, 18,209 -> 18,205: drain/wired-instruments and drain/artifact-location each lowered from the SAME 18,230 base in parallel -- the branch to 18,226 (net -4, run_continuation_hook.ts's termination ladder moved to _lib/continuation_ladder.ts and runtime_journal.ts's prose to _lib/outcome_vocabularies.ts, both under the cap) and main to 18,209 (net -21, the doctor --strict split above). Taking either side alone is wrong in a different direction: main's value silently discards the branch's earned -4, and the branch's value RAISES the baseline against main and weakens the gate. The committed number is neither -- it is the gate's own output on the merged tree, where both reductions apply. THIS LOCAL READING IS ADMISSIBLE HERE, and the discriminator matters because this file's ci-parity:local-only entry records a 165 -> 164 lowering that was reverted for exactly the opposite case: that count depends on how the environment resolves the projection surface, whereas this gate walks only SOURCE_ROOTS = ['src'] (check_source_size_budget.ts:88) counting excess lines over the 1500 ceiling, and `git status --porcelain -uall -- src/` reported ZERO untracked files, so a clean CI checkout of this commit measures the same 18,205. If CI reads anything else, CI's number is the correct one and this note is the record of why."
40
41
  },
41
42
  "ci-parity:local-only": {
42
43
  "count": 165,
@@ -83,6 +84,11 @@
83
84
  "landed": "2026-08-30",
84
85
  "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
86
  "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."
87
+ },
88
+ "lint_carrier_integrity": {
89
+ "count": 243,
90
+ "landed": "2026-09-02",
91
+ "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
92
  }
87
93
  },
88
94
  "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
  }
@@ -168,7 +168,7 @@
168
168
  "ceiling_enforced_by": "tests/scripts/web_launch_readiness_config.test.ts asserts checks.length < 50. Currently 7."
169
169
  },
170
170
  "regions": {
171
- "_comment": "Region axis (road-to-web-launch-readiness 2.3). Separate from site_types because it answers a different question: site_types decides WHETHER a check applies, regions decides HOW SEVERELY. A DE-targeted site owes Impressum and Datenschutz by law (TMG 5 / DSGVO Art. 13), so `required-legal-pages` is critical there and situational elsewhere -- the same check, a different tier. Modelled as a tier ESCALATION rather than as a second applies_to list, because a legal page is not more or less applicable in Germany, it is more or less optional.",
171
+ "_comment": "Region axis (road-to-web-launch-readiness 2.3). LEGAL-ROW CONTRACT: any row here or in `checks` whose `why` names a statute MUST carry `authority` (the citation and where it can be read) and `review_by` (the ISO date it must be re-read by); `authority` and `review_by` travel together, so a citation moved out of prose into `authority` stays dated; and a lapsed `review_by` FAILS the loader rather than warning, because a date nothing reads is a comment. Enforced by legalRowViolations() in src/scripts/check_web_launch_readiness.ts, which loadConfig() calls before returning. Separate from site_types because it answers a different question: site_types decides WHETHER a check applies, regions decides HOW SEVERELY. A DE-targeted site owes an Impressum and a Datenschutzerklaerung by law -- the citations sit in the escalation's `authority` field, dated, rather than in prose, which is what the `ddg-citation-authority` decision of 2026-09-03 settled -- so `required-legal-pages` is critical there and situational elsewhere -- the same check, a different tier. Modelled as a tier ESCALATION rather than as a second applies_to list, because a legal page is not more or less applicable in Germany, it is more or less optional.",
172
172
  "values": [
173
173
  "de",
174
174
  "eu",
@@ -181,7 +181,9 @@
181
181
  "check": "required-legal-pages",
182
182
  "region": "de",
183
183
  "to": "critical",
184
- "why": "TMG 5 obliges an Impressum and DSGVO Art. 13 a Datenschutzerklaerung for a DE-targeted commercial site. Abmahnung risk is real and immediate, which is what separates critical from situational here."
184
+ "why": "A DE-targeted commercial site owes a published imprint and a privacy notice by law; the two citations and the date they must be re-read by live in this row's `authority` field, not in this sentence. Abmahnung risk is real and immediate, which is what separates critical from situational here.",
185
+ "authority": "Impressum: DDG § 5 (Digitale-Dienste-Gesetz), gesetze-im-internet.de/ddg -- in force since 2024-05-14, superseding the identically-numbered provision of the TMG, which is why the pre-2026-09-03 form of this row cited dead law: it was written on 2026-08-25, twenty-seven months after that supersession. Datenschutzerklaerung: DSGVO Art. 13, Regulation (EU) 2016/679, consolidated text at eur-lex.europa.eu. Both are cited here as provenance a reader can re-check, never as counsel: src/rules/legal-safety-floor.md governs how this package may speak about law, and `review_by` below is the date these two must be read again.",
186
+ "review_by": "2027-09-03"
185
187
  }
186
188
  ]
187
189
  }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * The drift renderer, moved out of `cmd_doctor.ts`.
3
+ *
4
+ * WHY IT MOVED, because a reader will otherwise take this for a drive-by
5
+ * refactor. `cmd_doctor.ts` sits thousands of lines past the 1,500-line
6
+ * threshold `check_source_size_budget` measures, so every line added to it
7
+ * counts as new excess — and that ratchet turns one way, with its own test
8
+ * asserting `baseline == live` in BOTH directions.
9
+ *
10
+ * `doctor --strict` needed fifteen lines of integration wiring in that file
11
+ * that cannot live anywhere else: an import, two option fields, four argv-table
12
+ * splices, a validator call, both exit paths, and the payload attachment. So
13
+ * the choice was to raise the ratchet or to pay it down. The ratchet's own test
14
+ * comment answers that: a commit which splits a god-file lowers the excess and
15
+ * must carry the lowered baseline. Paying down is the response it was designed
16
+ * for; raising it is what the gate calls a defect.
17
+ *
18
+ * THIS function was chosen, and the choice is not arbitrary. It is pure
19
+ * rendering with two dependencies — a print sink and the tag id — no argparse
20
+ * involvement, and therefore none of the byte-parity risk the parser surface
21
+ * carries against the retired Python implementation. Nothing outside
22
+ * `cmd_doctor.ts` imported it; `cmd_prune.ts` has a same-named local of its own
23
+ * and is untouched. Behaviour is identical and the export surface is preserved
24
+ * by re-export.
25
+ */
26
+
27
+ type Dict = Record<string, unknown>;
28
+
29
+ const PACKAGE_TAG_ID = 'event4u/agent-config';
30
+
31
+ function print(line = ''): void {
32
+ process.stdout.write(`${line}\n`);
33
+ }
34
+
35
+ export function _emit_text(
36
+ project_root: string,
37
+ missing: Dict[],
38
+ modified: Dict[],
39
+ foreign: Dict[],
40
+ tag_drift: Dict[],
41
+ ): void {
42
+ const total = missing.length + modified.length + foreign.length + tag_drift.length;
43
+ if (total === 0) {
44
+ print(`✅ doctor: manifest matches filesystem under ${project_root}`);
45
+ return;
46
+ }
47
+ print(`⚠️ doctor: ${total} drift item(s) under ${project_root}`);
48
+ const groups: ReadonlyArray<[string, Dict[]]> = [
49
+ ['missing', missing],
50
+ ['modified', modified],
51
+ ['foreign', foreign],
52
+ ['tag-drift', tag_drift],
53
+ ];
54
+ for (const [label, items] of groups) {
55
+ if (items.length === 0) {
56
+ continue;
57
+ }
58
+ print(`\n ${label} (${items.length}):`);
59
+ for (const it of items) {
60
+ const tool = (it['tool'] as string) || '?';
61
+ print(` · [${tool}] ${it['path']}`);
62
+ if (label === 'tag-drift') {
63
+ const found = (it['found'] as string) || '(missing)';
64
+ const expected = 'expected' in it ? it['expected'] : PACKAGE_TAG_ID;
65
+ print(` expected: ${expected}`);
66
+ print(` found: ${found}`);
67
+ }
68
+ print(` fix: ${it['fix']}`);
69
+ }
70
+ }
71
+ }