@event4u/agent-config 14.6.0 → 14.7.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +260 -7
- package/README.md +1 -1
- package/dist/agent-src/commands/analyze/inbox.md +264 -10
- package/dist/agent-src/commands/analyze.md +1 -1
- package/dist/agent-src/commands/roadmap/process-full.md +116 -30
- package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +31 -0
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +135 -104
- package/dist/agent-src/rules/context-hygiene.md +2 -0
- package/dist/agent-src/rules/decision-revisit-gate.md +3 -0
- package/dist/agent-src/rules/fix-what-you-see.md +108 -0
- package/dist/agent-src/rules/no-roadmap-references.md +0 -5
- package/dist/agent-src/rules/recurring-criticism.md +110 -0
- package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
- package/dist/agent-src/rules/rule-type-governance.md +0 -2
- package/dist/agent-src/rules/self-repair-loop.md +17 -0
- package/dist/agent-src/rules/skill-quality.md +0 -2
- package/dist/agent-src/rules/source-confidentiality.md +37 -7
- package/dist/agent-src/scripts/archival_sweep.ts +117 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +19 -3
- package/dist/agent-src/scripts/gate_budget.ts +282 -0
- package/dist/agent-src/scripts/gate_execute.ts +72 -31
- package/dist/agent-src/scripts/roadmap_gates.ts +74 -3
- package/dist/agent-src/scripts/update_roadmap_progress.ts +251 -7
- package/dist/agent-src/skills/ai-council/SKILL.md +48 -807
- package/dist/agent-src/skills/ai-council/references/advanced-modes.md +320 -0
- package/dist/agent-src/skills/ai-council/references/cost-and-redaction.md +99 -0
- package/dist/agent-src/skills/ai-council/references/execution-modes.md +162 -0
- package/dist/agent-src/skills/ai-council/references/output-and-synthesis.md +125 -0
- package/dist/agent-src/skills/ai-council/references/procedure.md +143 -0
- package/dist/agent-src/skills/quality-tools/SKILL.md +34 -326
- package/dist/agent-src/skills/quality-tools/references/js-ts-tools.md +113 -0
- package/dist/agent-src/skills/quality-tools/references/php-tools.md +222 -0
- package/dist/agent-src/skills/roadmap-management/SKILL.md +15 -409
- package/dist/agent-src/skills/roadmap-management/references/archival.md +226 -0
- package/dist/agent-src/skills/roadmap-management/references/authoring.md +107 -0
- package/dist/agent-src/skills/roadmap-management/references/dashboard.md +47 -0
- package/dist/agent-src/skills/roadmap-management/references/lifecycle.md +58 -0
- package/dist/agent-src/skills/skill-writing/SKILL.md +34 -570
- package/dist/agent-src/skills/skill-writing/references/procedure.md +275 -0
- package/dist/agent-src/skills/skill-writing/references/read-only-scripts.md +18 -0
- package/dist/agent-src/skills/skill-writing/references/section-patterns.md +298 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +45 -2
- package/dist/agent-src/templates/scripts/telemetry/flush_sender.mjs +163 -0
- package/dist/agent-src/templates/scripts/telemetry/remote.ts +160 -7
- package/dist/agent-src/templates/scripts/telemetry/transport.ts +169 -0
- package/dist/cli/registry.js +1 -1
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/chunk-2GNWGMAL.js +172 -0
- package/dist/cli-delegate/{chunk-RLN73PR5.js → chunk-4VR4ORQ6.js} +10 -7
- package/dist/cli-delegate/chunk-6EOCNQAM.js +402 -0
- package/dist/cli-delegate/{chunk-XURJ4AQC.js → chunk-74E3TKWV.js} +381 -280
- package/dist/cli-delegate/{chunk-3UCJWMMP.js → chunk-7F3VYSM2.js} +4 -4
- package/dist/cli-delegate/{chunk-BKO2AZ7Z.js → chunk-B2ERU2VA.js} +1 -1
- package/dist/cli-delegate/{chunk-NXFWFAYV.js → chunk-G23AT6GD.js} +1 -0
- package/dist/cli-delegate/{chunk-63WSP6YZ.js → chunk-GJ5BVHXM.js} +72 -165
- package/dist/cli-delegate/{chunk-P6LQDIWK.js → chunk-JZ4NR2TW.js} +338 -0
- package/dist/cli-delegate/chunk-MWRD5YOH.js +100 -0
- package/dist/cli-delegate/{chunk-GRRPEPRN.js → chunk-T7L2QK3K.js} +24 -5
- package/dist/cli-delegate/{chunk-EVTY7ISR.js → chunk-YM4GZDJG.js} +9 -1
- package/dist/cli-delegate/cmd_conformance.js +459 -709
- package/dist/cli-delegate/cmd_doctor.js +6 -6
- package/dist/cli-delegate/cmd_explain.js +1 -1
- package/dist/cli-delegate/cmd_export.js +7 -6
- package/dist/cli-delegate/cmd_handoff.js +6 -6
- package/dist/cli-delegate/cmd_packs_active.js +1 -1
- package/dist/cli-delegate/cmd_preflight.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +7 -6
- package/dist/cli-delegate/cmd_session_recycle.js +840 -30
- package/dist/cli-delegate/cmd_settings_migrate.js +7 -6
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +7 -6
- package/dist/cli-delegate/cmd_uninstall.js +9 -7
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +7 -6
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +68 -25
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -4
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +10 -8
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +8 -6
- package/dist/hooks/dispatch.js +2901 -2053
- package/dist/install/claudePathsPlan.js +187 -0
- package/dist/install/claudePathsPlan.js.map +1 -0
- package/dist/install/hostLayerFingerprint.js +136 -0
- package/dist/install/hostLayerFingerprint.js.map +1 -0
- package/dist/install/install.mjs +553 -425
- package/dist/install/partitionEligibility.js +276 -0
- package/dist/install/partitionEligibility.js.map +1 -0
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1744 -1191
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_src.js +41 -0
- package/dist/scripts/_lib/agent_src.js.map +1 -1
- package/dist/scripts/_lib/install_layout.js +83 -0
- package/dist/scripts/_lib/install_layout.js.map +1 -0
- package/dist/scripts/_lib/installed_lock.js +444 -0
- package/dist/scripts/_lib/installed_lock.js.map +1 -0
- package/dist/scripts/condense.js +13 -164
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/schemas/settings.js +4 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/contracts/settings-classes.md +12 -6
- package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +8 -3
- package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +26 -0
- package/docs/guidelines/agent-infra/memory-access.md +17 -0
- package/docs/guidelines/agent-infra/recurring-criticism-mechanics.md +96 -0
- package/package.json +1 -1
- package/src/agent-src/scripts/archival_sweep.ts +117 -0
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +19 -3
- package/src/agent-src/scripts/gate_budget.ts +282 -0
- package/src/agent-src/scripts/gate_execute.ts +72 -31
- package/src/agent-src/scripts/roadmap_gates.ts +74 -3
- package/src/agent-src/scripts/update_roadmap_progress.ts +251 -7
- package/src/agent-src/templates/scripts/telemetry/flush_sender.mjs +163 -0
- package/src/agent-src/templates/scripts/telemetry/remote.ts +160 -7
- package/src/agent-src/templates/scripts/telemetry/transport.ts +169 -0
- package/src/config/agent-settings.template.yml +27 -2
- package/src/config/ci-local-parity.yml +18 -0
- package/src/config/estate-count-budget.json +339 -3
- package/src/config/gate-violation-baselines.json +41 -41
- package/src/config/rule-activation-census.json +14 -6
- package/src/config/rule-stub-ceilings.json +19 -6
- package/src/install/claudePathsPlan.ts +196 -0
- package/src/install/hostLayerFingerprint.ts +145 -0
- package/src/install/partitionEligibility.ts +325 -0
- package/src/scripts/_cli/cmd_session_recycle.ts +9 -1
- package/src/scripts/_dispatch.bash +6 -1
- package/src/scripts/_lib/ac_heading.ts +30 -0
- package/src/scripts/_lib/agent_src.ts +42 -0
- package/src/scripts/_lib/bench_ab_pinned_repo.ts +162 -0
- package/src/scripts/_lib/bench_ab_workspace.ts +10 -1
- package/src/scripts/_lib/envelope_grounding.ts +38 -8
- package/src/scripts/_lib/installed_lock.ts +50 -5
- package/src/scripts/_lib/md_section.ts +129 -0
- package/src/scripts/_lib/scope_guard.sh +26 -3
- package/src/scripts/_lib/self_repair.ts +19 -0
- package/src/scripts/_lib/self_repair_class_b.ts +171 -0
- package/src/scripts/ai_council/cli_help.ts +55 -0
- package/src/scripts/before_complete_hook.ts +153 -28
- package/src/scripts/bench_ab_v2_complexity.ts +27 -7
- package/src/scripts/check_references.ts +60 -0
- package/src/scripts/check_release_published.ts +32 -11
- package/src/scripts/check_single_delivery.ts +59 -15
- package/src/scripts/ci_settle.ts +170 -0
- package/src/scripts/condense.ts +20 -170
- package/src/scripts/council_cli.ts +3 -3
- package/src/scripts/dispatch_r2_reviewer.ts +6 -14
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +77 -14
- package/src/scripts/hooks/block_no_verify.ts +61 -10
- package/src/scripts/hooks/block_unauthorized_git.ts +1 -1
- package/src/scripts/hooks/concern_registry.ts +4 -0
- package/src/scripts/hooks/rtk_wrap_hook.ts +8 -3
- package/src/scripts/hooks/run_continuation_hook.ts +8 -5
- package/src/scripts/hooks/ship_diff_volume_hook.ts +81 -15
- package/src/scripts/hooks/source_first_gate_hook.ts +464 -0
- package/src/scripts/hooks/state_io.ts +782 -20
- package/src/scripts/hooks/subagent_ledger_hook.ts +44 -15
- package/src/scripts/hooks/telemetry_flush_hook.ts +139 -0
- package/src/scripts/hooks/telemetry_self_repair.ts +128 -0
- package/src/scripts/hooks/telemetry_usage_hook.ts +22 -4
- package/src/scripts/hooks/turn_end_gate_hook.ts +81 -15
- package/src/scripts/install.ts +16 -11
- package/src/scripts/language_mirror_hook.ts +561 -25
- package/src/scripts/lint_framework_leakage_allowlist.json +2 -2
- package/src/scripts/lint_plan_risk_register.ts +4 -3
- package/src/scripts/lint_skill_router_head.ts +4 -4
- package/src/scripts/mcp_server/tools.ts +2 -2
- package/src/scripts/new_roadmap.ts +136 -0
- package/src/scripts/pack_mcp_content.ts +20 -5
- package/src/scripts/release.ts +37 -38
- package/src/scripts/report_layer_overlap.ts +6 -0
- package/src/scripts/self_repair_hook.ts +37 -5
- package/src/scripts/session_register_hook.ts +11 -0
- package/src/scripts/skill_usage_collect.ts +11 -1
- package/src/scripts/skill_usage_report.ts +278 -8
- package/src/scripts/sync_pr_branch.ts +7 -0
- package/src/server/schemas/settings.ts +8 -0
- package/dist/cli-delegate/chunk-EJITJBJX.js +0 -172
- package/dist/cli-delegate/chunk-PZWRPRWP.js +0 -38
- /package/dist/cli-delegate/{chunk-RYNADUFG.js → chunk-LWTYTX6F.js} +0 -0
|
@@ -1,66 +1,66 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$comment": "Violation ratchet for gates whose dead scan root was repaired. A repair REVEALS pre-existing debt; it does not create it. Each entry records the count revealed at repair time
|
|
2
|
+
"$comment": "Violation ratchet for gates whose dead scan root was repaired. A repair REVEALS pre-existing debt; it does not create it. Each entry records the count revealed at repair time \u2014 the gate fails only when its count RISES. Lowering a count is a normal commit; raising one is a defect. Entries expire: a baseline that has not dropped within 56 days of `landed` (or of `reaffirmed.date`) fails its gate, because a number that never moves is suppression with extra steps. Mechanism + rationale: src/scripts/_lib/gate_baseline.ts. Decision: AI council 2026-08-02, roadmap road-to-gates-that-can-fail blocker `dead-gate-finding-triage`.",
|
|
3
3
|
"gates": {
|
|
4
|
-
"ci-parity:local-only": {
|
|
5
|
-
"count": 166,
|
|
6
|
-
"landed": "2026-08-13",
|
|
7
|
-
"note": "Gates that `task ci` runs and no workflow does — the direction this manifest's own header calls the one that let real defects merge (16 stale index rows reached the trunk behind a local-only check-index). Revealed, not created, by repairing the checker's extraction: check_ci_local_parity built its CI-side set by regexing RAW workflow text for `task <name>` and expanding each closure, comments included. Several workflow comments contain the literal string `task ci` while stating that no workflow invokes it, so `ci` was read as an invocation and its full 247-gate closure entered the 'runs in CI' set. The count was therefore 0 BY CONSTRUCTION, and the prose documenting the gap was what suppressed it. Measured at the repair: CI-side 273 → 106, undeclared_local_only 0 → 167 of 247 local gates (68%). Baselined rather than hard-failed because 167 findings nobody can clear in the change that reveals them is the gate-that-lands-as-N-instant-blockers failure this repository has refused before; shrink-only keeps the property that matters, namely that a NEW gate registered in `task ci` with no workflow reds immediately. Lowering happens by wiring gates into workflows or declaring them under `local_only:` with a reason. Note the punchline, because it bounds the risk of this entry: check_ci_local_parity ITSELF runs in no workflow, so it is its own finding and this ratchet has no remote reach either — clearing the backlog is what gives it teeth. DRAINED BY ONE, 2026-08-13: check_ci_local_parity itself was wired into consistency.yml, so it left the set it exists to report. That is the whole shape of a legitimate drain here — the gate stops being local-only because a workflow now runs it, not because the declaration list grew. The floor in gate-coverage.yml moved in the same change and for the same reason: wiring two gates raised the parsed population 357 -> 360 (109 CI + 251 local). The intermediate 359 was the reading after the CI-side wiring alone and before check_gate_coverage joined the local `ci` chain; it was committed here by mistake and is corrected rather than left as a second number for the same measurement.",
|
|
8
|
-
"falsifier": "./scripts-run src/scripts/check_ci_local_parity"
|
|
9
|
-
},
|
|
10
|
-
"check_no_new_legacy_path:hardcoded-scan-roots": {
|
|
11
|
-
"count": 47,
|
|
12
|
-
"landed": "2026-08-13",
|
|
13
|
-
"note": "Lines under src/scripts/**/*.{ts,mts,mjs} that pass the retired `.agent-src.uncondensed` directory into `path.join(...)` — a filesystem path CONSTRUCTED against the tree ADR-051 abandoned, which is the exact shape of all 14 dead gates. Deliberately narrow so the number is signal, not noise: 236 raw mentions under src/scripts → 147 string-literal lines → 67 path-constructing lines when the pass was written, after exempting src/scripts/_lib/agent_src.ts (the shared resolver, whose job IS owning the constant — gates that route through it survived the move). Detectors holding the literal as data to match, strip, or print are not counted, and no prose, docs, tests or fixtures are read: that full-tree shape was measured at 44 files / 213 hits and rejected as the wrong lint (see the note in check_no_new_legacy_path.ts::find_offenders). Recorded at 58 rather than 67 because Phase 1's scan-root repairs were landing on this branch while the pass was being written and had already removed 9 — a baseline above the truth silently permits new violations. This count goes to ZERO as the remaining repairs land; every repair is a lowering commit. Lowered 56→51 by the road-to-renewal-foundation Phase 1 dead-root sweep (check_always_budget, check_portability, check_references, lint_framework_leakage, lint_agents_md). Lowered 51→49 by the gate-hardening adoption PR: skill_collision_clusters and skill_overlap were repointed at the live skills tree via the shared resolver — two gates that had been reading the retired container and scoring 0 of 288 skills. Lowered 49→48 by road-to-rule-delivery-integrity P2.2: score_skill_selection was repointed at the live skills tree via the shared resolver. It is the same shape as the two gates lowered before it — a scorer reading the retired container and scoring 0 of 288 skills — and here the consequence was sharper than a dead gate: the roadmap's pre-registered selection-accuracy criteria would have been compared against a baseline of silent zeros. Lowered 48→47 on 2026-08-13, and this one is a catch-up rather than a repair: the pass reported 47 against a baseline of 48, so a gain had already landed without the ratchet following it. Attributed to 455160160 (fix(evals): resolve the eval orchestrator against the live skills tree), the most recent repair in this branch's base that removes a path-constructing line — SKILLS_ROOT repointed through _lib/agent_src.ts::SRC_SKILLS(). A loose ratchet is not harmless: it silently permits one new violation to be added back without any gate noticing, which is the shape this entry exists to prevent."
|
|
14
|
-
},
|
|
15
4
|
"audit_user_type_axis": {
|
|
16
5
|
"count": 1,
|
|
17
6
|
"landed": "2026-08-02",
|
|
18
|
-
"note": "One orphan user-type value: `legal`. Five legal-review-prep skills (contract-review, dpa-review, nda-triage, legal-intake-triage, legal-practice-profile) declare `recommended_for_user_types: [legal]`, but there is no `user-types/legal.yml`
|
|
19
|
-
},
|
|
20
|
-
"lint_handoffs": {
|
|
21
|
-
"count": 18,
|
|
22
|
-
"landed": "2026-08-02",
|
|
23
|
-
"note": "All 18 are `handoff_tier_mismatch`: a senior-tier skill links to a peer whose frontmatter carries no `tier` (reported as tier='unset') — e.g. build-buy-partner → adr-create, customer-research → po-discovery, gated-reach → threat-modeling. This is a tier-metadata backfill backlog on the LINKED-TO skills, not a broken link. The one genuinely dangling link revealed by the same repair (competitive-positioning → a non-existent ../analyze-reference-repo/SKILL.md; it is a command, not a skill) was FIXED in the repairing commit rather than baselined. Clearing this entry means backfilling `tier` on the linked-to skills, or deciding that `tier: unset` on a link target is not a violation and narrowing the rule."
|
|
7
|
+
"note": "One orphan user-type value: `legal`. Five legal-review-prep skills (contract-review, dpa-review, nda-triage, legal-intake-triage, legal-practice-profile) declare `recommended_for_user_types: [legal]`, but there is no `user-types/legal.yml` \u2014 the pack shipped an axis value with no user-type definition behind it, and the dead scan root hid it (the audit reported used=0 orphans=0 while reading zero skills; it now reads 288 and reports used=8). Clearing this entry is a product call, not a copy edit: either author `user-types/legal.yml` (tagline, recommended packs, install-path hint) or drop `legal` from those five skills. Not decided here because both options change shipped pack behaviour."
|
|
24
8
|
},
|
|
25
|
-
"
|
|
26
|
-
"count":
|
|
27
|
-
"landed": "2026-08-
|
|
28
|
-
"note": "
|
|
29
|
-
"falsifier": "./scripts-run src/scripts/lint_roadmap_blockers --quiet"
|
|
30
|
-
},
|
|
31
|
-
"lint_pack_boundaries": {
|
|
32
|
-
"count": 337,
|
|
33
|
-
"landed": "2026-08-02",
|
|
34
|
-
"note": "Markdown links between source artefacts that cross a pack boundary the source pack's `requires` graph does not reach — revealed when the scan root moved off the deleted packages/ tree to the live corpus (0 -> 753 artefacts scanned). 82 distinct pack pairs across 131 source files; largest single class is engineering-base -> meta (73), skills in a consumer-installable pack citing this package's own maintenance skills. Each is a link that dangles for a consumer who installs the source pack without the target's. Judged real drift rather than an over-strict gate because it is long-tailed — top bucket 22%, no single systematic cause — unlike the lint_artefact_frontmatter case, where one required shape had 0-of-288 adoption. Fix per finding: add the edge to packs.yml requires, retarget the link, or move the artefact."
|
|
9
|
+
"check_depth_budget": {
|
|
10
|
+
"count": 4,
|
|
11
|
+
"landed": "2026-08-06",
|
|
12
|
+
"note": "Four depth files exceed the 16,000-char per-file ceiling: rule-body-migration-inventory.md (23,782), design-antipatterns.md (20,707), roadmap-process-loop.md (20,360), orchestration-telemetry.md (16,155). Recorded rather than fixed because splitting four large reference documents is unrelated to the change that introduced the ceiling, and a gate that lands as four instant blockers is the shape this repository has already recorded as a failure. Shrink-only from here: a FIFTH over-ceiling file reds. roadmap-process-loop.md is the file that motivated the gate \u2014 it states 'Size budget: <= 4,000 chars' in its own header and is 5x over it, which is what a declared budget with nothing measuring it decays into. The ceiling is a GROWTH RATCHET, not a measured quality threshold: the 7,308-trajectory study behind the 3,500-token skill ceiling (ADR-217) was taken on procedural always-co-loaded skills, and this layer is declarative and loaded one or two files at a time. Replace the number when someone measures comprehension on THIS layer."
|
|
35
13
|
},
|
|
36
14
|
"check_gate_completeness": {
|
|
37
15
|
"count": 215,
|
|
38
16
|
"landed": "2026-08-17",
|
|
39
|
-
"note": "Ledger adoption ratchet. Gates registered under the CI task roots that carry neither per-target completeness accounting (`_lib/gate_ledger.ts`) nor a `// ledger-exempt:` marker. This is deliberately NOT a sweep: road-to-skill-ecosystem-gate-integrity Phase 1 adopts exactly three gates (lint_framework_leakage, check_references, lint_roadmap_complexity) plus the checker itself to prove the interface, and the remainder migrates as gates are touched. Shrink-only from here
|
|
17
|
+
"note": "Ledger adoption ratchet. Gates registered under the CI task roots that carry neither per-target completeness accounting (`_lib/gate_ledger.ts`) nor a `// ledger-exempt:` marker. This is deliberately NOT a sweep: road-to-skill-ecosystem-gate-integrity Phase 1 adopts exactly three gates (lint_framework_leakage, check_references, lint_roadmap_complexity) plus the checker itself to prove the interface, and the remainder migrates as gates are touched. Shrink-only from here \u2014 the number may not grow, so a NEW gate must adopt or exempt. Lowered 216 -> 215 on 2026-08-17 by discharging the obligation three NEW arrivals had incurred, which is the standing rule being enforced rather than a drain: the registered population grew 244 -> 258 and three of the fourteen arrivals adopted nothing \u2014 `build_archive_index`, `check_gate_coverage`, `check_pr_ci_current`. The reading was 218 against this baseline of 216, and the missing arithmetic is that one PRE-existing gate (`lint_rule_skill_pack_reach`) had adopted in the meantime: 216 + 3 - 1 = 218. All three adopted a real ledger. `check_pr_ci_current` first took a reasoned exemption claiming no target population; the completion review refuted it from the file itself \u2014 the check rows ARE a population, and rows whose `state` is not a string were being dropped before any verdict ran, so a dropped row could be the failing one. The exemption was withdrawn and the ledger adopted, counting kept rows as read, dropped rows as skips, and rows a pre-row verdict never reached as out of scope. The denominator in this note was 14 stale (it said 244) and is now expressed without one, because a count pinned to a population that grows weekly rots faster than the count itself. Note what this lowering does NOT buy: headroom is zero either way, so the next un-adopted arrival reds this gate immediately \u2014 which is the intended behaviour and the reason the number is not padded. The refreshed `landed` is load-bearing rather than bookkeeping: `checkRatchet` returns `improved` BEFORE it evaluates staleness, so an entry left at 216 while the tree measures 215 would never expire at any age (pinned by tests/lib/gate_baseline.test.ts, which asserts `improved` at landed + STALE_AFTER_DAYS + 400 days) \u2014 the greenest available state and the worst, since it switches off the anti-fossilization clause exactly when someone has done real work.",
|
|
40
18
|
"falsifier": "./scripts-run src/scripts/check_gate_completeness"
|
|
41
19
|
},
|
|
42
|
-
"
|
|
43
|
-
"count":
|
|
44
|
-
"landed": "2026-08-
|
|
45
|
-
"note": "
|
|
20
|
+
"check_no_new_legacy_path:hardcoded-scan-roots": {
|
|
21
|
+
"count": 47,
|
|
22
|
+
"landed": "2026-08-13",
|
|
23
|
+
"note": "Lines under src/scripts/**/*.{ts,mts,mjs} that pass the retired `.agent-src.uncondensed` directory into `path.join(...)` \u2014 a filesystem path CONSTRUCTED against the tree ADR-051 abandoned, which is the exact shape of all 14 dead gates. Deliberately narrow so the number is signal, not noise: 236 raw mentions under src/scripts \u2192 147 string-literal lines \u2192 67 path-constructing lines when the pass was written, after exempting src/scripts/_lib/agent_src.ts (the shared resolver, whose job IS owning the constant \u2014 gates that route through it survived the move). Detectors holding the literal as data to match, strip, or print are not counted, and no prose, docs, tests or fixtures are read: that full-tree shape was measured at 44 files / 213 hits and rejected as the wrong lint (see the note in check_no_new_legacy_path.ts::find_offenders). Recorded at 58 rather than 67 because Phase 1's scan-root repairs were landing on this branch while the pass was being written and had already removed 9 \u2014 a baseline above the truth silently permits new violations. This count goes to ZERO as the remaining repairs land; every repair is a lowering commit. Lowered 56\u219251 by the road-to-renewal-foundation Phase 1 dead-root sweep (check_always_budget, check_portability, check_references, lint_framework_leakage, lint_agents_md). Lowered 51\u219249 by the gate-hardening adoption PR: skill_collision_clusters and skill_overlap were repointed at the live skills tree via the shared resolver \u2014 two gates that had been reading the retired container and scoring 0 of 288 skills. Lowered 49\u219248 by road-to-rule-delivery-integrity P2.2: score_skill_selection was repointed at the live skills tree via the shared resolver. It is the same shape as the two gates lowered before it \u2014 a scorer reading the retired container and scoring 0 of 288 skills \u2014 and here the consequence was sharper than a dead gate: the roadmap's pre-registered selection-accuracy criteria would have been compared against a baseline of silent zeros. Lowered 48\u219247 on 2026-08-13, and this one is a catch-up rather than a repair: the pass reported 47 against a baseline of 48, so a gain had already landed without the ratchet following it. Attributed to 455160160 (fix(evals): resolve the eval orchestrator against the live skills tree), the most recent repair in this branch's base that removes a path-constructing line \u2014 SKILLS_ROOT repointed through _lib/agent_src.ts::SRC_SKILLS(). A loose ratchet is not harmless: it silently permits one new violation to be added back without any gate noticing, which is the shape this entry exists to prevent."
|
|
24
|
+
},
|
|
25
|
+
"check_source_size_budget": {
|
|
26
|
+
"count": 19427,
|
|
27
|
+
"landed": "2026-08-20",
|
|
28
|
+
"note": "Lowered 19,570 -> 19,427 on 2026-08-20 by road-to-single-delivery Phase 5, and the -143 is a LOWERING THROUGH EXTRACTION in the sense this note's own convention requires -- reproducible with `git stash && npx tsx src/scripts/check_source_size_budget.ts`. The branch had to WIRE a feature into the two worst files here: the delivery partition (ADR-236) needs a decision point in condense.ts (2,885) and a fingerprint stamp in install.ts (5,461), and a first pass added 191 lines above the ceiling -- a straight regression this gate correctly refused. The fix was not to shrink the comments until the number fit. Three extractions carried it: the partition decision, its memo, its fail-safe ordering and its printed reason went to src/install/partitionEligibility.ts (325 lines, under the ceiling, contributing nothing here); the fingerprint itself to hostLayerFingerprint.ts; and then the one that produced the surplus -- the whole Claude `paths:` emission plan (_claude_paths_plan, _has_non_path_trigger, derive_trigger_globs, _is_unresolved_placeholder, _escape_claude_bracket, _expanded_pattern_count, the budget constant and the plan type, 169 lines) moved to src/install/claudePathsPlan.ts. That last move is not opportunistic: Phase 5.1 removed the path triggers from four package-only rules so they load unconditionally, which is a change ABOUT that plan, and the plan is host-activation-key logic that belongs beside emit_host_rules_cli.ts rather than inside the orchestrator that calls it. condense.ts ends at 2,735 -- 150 lines BELOW where the branch found it, while carrying four new call sites. Every consumer that imported the moved symbols from condense.ts still can: they are re-exported, so the extraction is invisible to callers and visible to this ratchet. Previous entry: lowered 19,626 -> 19,611 on 2026-08-17 by the release-gh-transient branch, same shape (retry loop and transient classifier extracted to _lib/gh_transient.ts, leaving release.ts a 20-line wrapper).",
|
|
29
|
+
"falsifier": "./scripts-run src/scripts/check_source_size_budget --quiet"
|
|
30
|
+
},
|
|
31
|
+
"ci-parity:local-only": {
|
|
32
|
+
"count": 165,
|
|
33
|
+
"landed": "2026-08-13",
|
|
34
|
+
"note": "Gates that `task ci` runs and no workflow does \u2014 the direction this manifest's own header calls the one that let real defects merge (16 stale index rows reached the trunk behind a local-only check-index). Revealed, not created, by repairing the checker's extraction: check_ci_local_parity built its CI-side set by regexing RAW workflow text for `task <name>` and expanding each closure, comments included. Several workflow comments contain the literal string `task ci` while stating that no workflow invokes it, so `ci` was read as an invocation and its full 247-gate closure entered the 'runs in CI' set. The count was therefore 0 BY CONSTRUCTION, and the prose documenting the gap was what suppressed it. Measured at the repair: CI-side 273 \u2192 106, undeclared_local_only 0 \u2192 167 of 247 local gates (68%). Baselined rather than hard-failed because 167 findings nobody can clear in the change that reveals them is the gate-that-lands-as-N-instant-blockers failure this repository has refused before; shrink-only keeps the property that matters, namely that a NEW gate registered in `task ci` with no workflow reds immediately. Lowering happens by wiring gates into workflows or declaring them under `local_only:` with a reason. Note the punchline, because it bounds the risk of this entry: check_ci_local_parity ITSELF runs in no workflow, so it is its own finding and this ratchet has no remote reach either \u2014 clearing the backlog is what gives it teeth. DRAINED BY ONE, 2026-08-13: check_ci_local_parity itself was wired into consistency.yml, so it left the set it exists to report. That is the whole shape of a legitimate drain here \u2014 the gate stops being local-only because a workflow now runs it, not because the declaration list grew. The floor in gate-coverage.yml moved in the same change and for the same reason: wiring two gates raised the parsed population 357 -> 360 (109 CI + 251 local). The intermediate 359 was the reading after the CI-side wiring alone and before check_gate_coverage joined the local `ci` chain; it was committed here by mistake and is corrected rather than left as a second number for the same measurement. WALKED DOWN 166 -> 165 on 2026-08-20 (road-to-session-closeout 5.2): check_ci_local_parity reported `ci-parity:local-only: 165 violation(s), baseline 166 - ratchet is loose` and asked for exactly this, so the gain cannot be silently given back. The measurement is the gate's own output on the merged tree, not a branch. What is NOT done here is the classification the same step asks for: 165 gates each need a recorded class (belongs-in-CI / developer-aid / dead), which is per-gate judgement, and migrating them would red the trunk on the landing day - the step forbids it for that reason.",
|
|
35
|
+
"falsifier": "./scripts-run src/scripts/check_ci_local_parity"
|
|
46
36
|
},
|
|
47
37
|
"gate-self-test:registered-non-adopters": {
|
|
48
38
|
"count": 24,
|
|
49
39
|
"landed": "2026-08-06",
|
|
50
|
-
"note": "Self-test adoption ratchet, expressed as a shrink-only NON-adopter count. 24 of the 32 gates registered enforced in gate-coverage.yml carry neither a `--self-test` (`_lib/gate_self_test.ts`) nor a `// self-test-exempt: <reason>` marker, so nothing proves they DISCRIMINATE
|
|
40
|
+
"note": "Self-test adoption ratchet, expressed as a shrink-only NON-adopter count. 24 of the 32 gates registered enforced in gate-coverage.yml carry neither a `--self-test` (`_lib/gate_self_test.ts`) nor a `// self-test-exempt: <reason>` marker, so nothing proves they DISCRIMINATE \u2014 an enforced `scanned:` floor proves a gate read something, only a self-test proves the reading changes the verdict. Deliberately NOT an adoption column ratcheted upward: check_gate_coverage.ts's own report_hardening_ratchet rejects that shape in this same file (\"grades the solution instead of the problem\"), and the sibling adoption ratchet (check_gate_completeness) is already a non-adopter count. Population is the manifest, not every script under src/scripts: padding the manifest to move this number would mean adding a floor per row. Shrink-only from here \u2014 a NEW registered gate must adopt or exempt. Migration mechanism, same as the ledger ratchet above: gates adopt as they are touched, not in a sweep \u2014 the eight current adopters were each added alongside a real change to that gate. The 56-day non-stagnation clause applies unchanged, so a count parked here fails on its own.",
|
|
51
41
|
"falsifier": "./scripts-run src/scripts/check_gate_coverage"
|
|
52
42
|
},
|
|
43
|
+
"lint_handoffs": {
|
|
44
|
+
"count": 18,
|
|
45
|
+
"landed": "2026-08-02",
|
|
46
|
+
"note": "All 18 are `handoff_tier_mismatch`: a senior-tier skill links to a peer whose frontmatter carries no `tier` (reported as tier='unset') \u2014 e.g. build-buy-partner \u2192 adr-create, customer-research \u2192 po-discovery, gated-reach \u2192 threat-modeling. This is a tier-metadata backfill backlog on the LINKED-TO skills, not a broken link. The one genuinely dangling link revealed by the same repair (competitive-positioning \u2192 a non-existent ../analyze-reference-repo/SKILL.md; it is a command, not a skill) was FIXED in the repairing commit rather than baselined. Clearing this entry means backfilling `tier` on the linked-to skills, or deciding that `tier: unset` on a link target is not a violation and narrowing the rule."
|
|
47
|
+
},
|
|
48
|
+
"lint_pack_boundaries": {
|
|
49
|
+
"count": 337,
|
|
50
|
+
"landed": "2026-08-02",
|
|
51
|
+
"note": "Markdown links between source artefacts that cross a pack boundary the source pack's `requires` graph does not reach \u2014 revealed when the scan root moved off the deleted packages/ tree to the live corpus (0 -> 753 artefacts scanned). 82 distinct pack pairs across 131 source files; largest single class is engineering-base -> meta (73), skills in a consumer-installable pack citing this package's own maintenance skills. Each is a link that dangles for a consumer who installs the source pack without the target's. Judged real drift rather than an over-strict gate because it is long-tailed \u2014 top bucket 22%, no single systematic cause \u2014 unlike the lint_artefact_frontmatter case, where one required shape had 0-of-288 adoption. Fix per finding: add the edge to packs.yml requires, retarget the link, or move the artefact."
|
|
52
|
+
},
|
|
53
|
+
"lint_roadmap_blockers:decidability": {
|
|
54
|
+
"count": 12,
|
|
55
|
+
"landed": "2026-08-21",
|
|
56
|
+
"note": "Open blocker entries that are not yet DECIDABLE by the person who owns them \u2014 missing `Recommendation:` (which option and why), missing `If you do nothing:` (the cost of the non-decision), or a `What to do:` carrying no command, path or enumerated option set. Revealed by adding the decidability half to lint_roadmap_blockers, not created by it: the five original fields describe a situation and never made it actionable, so a blocker could pass the contract while handing the analysis back to the person least equipped to redo it. Measured at introduction: 36 open entries, ALL 36 undecidable, and 14 of the 46 total entries carry no command/path/option at all in `What to do`. Baselined rather than hard-failed because 36 findings nobody can clear in the change that reveals them is the gate-that-lands-as-N-instant-blockers failure this repository has refused before \u2014 the same reasoning as ci-parity:local-only above. First measured at 34 rather than 36 because the two blockers on the branch that introduced this (`ac3-false-positive-reading`, `publish-side-marker-invariant`) were rewritten to the new standard and then closed in the same change; a baseline above the truth silently permits new violations. Re-measured at 38 when the branch was re-pointed at current main, and the +4 is population growth rather than a loosening: main gained the four `road-to-inbox-harvest-2026-08-d-*` roadmaps (context-ledger, runtime-skill-routing, scheduled-deprecation, top-band-model-economy), each carrying one open blocker authored before this requirement existed. That is the honest number for the population this gate now scans \u2014 recording 34 against a 38-entry tree would have failed the gate on its own introducing commit and taught the next reader to distrust it. Lowering happens one blocker at a time, and every roadmap touched for other reasons is a chance to lower it. Lowered 38\u219237 by the scheduled-deprecation PR, which is exactly that shape: code-graph-removal-authorisation was rewritten to the new standard because the roadmap was being touched anyway. Its `If you do nothing:` was one of the few that was not the common no-cost answer \u2014 the release gate that PR ships would have refused the next major cut while the blocker stayed open. Recorded in the past tense because the maintainer then RESOLVED that blocker on the trunk by withdrawing the commitment, so nothing refuses a cut today. The lowering stands on the rewrite, not on the consequence, and the sentence is corrected rather than deleted because a baseline note arguing from a retracted state is how a ratchet's rationale rots. The 56-day expiry is the point, not a hazard: if this number has not moved in two months, the fields are not being written and the gate should fail until someone decides whether the requirement is real. Lowered 37\u219234 when the scheduled-deprecation branch merged current main: three blockers were resolved or rewritten on the trunk in the meantime, so the drop is a genuine drain plus a merge, not a loosening. Re-tightened here rather than left loose because a ratchet that lags its own tree permits violations back in silently. Lowered 34\u219229 on 2026-08-16 by rewriting six entries to the new standard (raw-capture-needs-host-env, telemetry-sample-size, repo-admin-and-usage, benchmark-spend, measurement-a-no-per-arm-builder-tier, measurement-b-no-renderable-lane-pair) in a PR whose only purpose was the drain \u2014 the first lowering not taken on the back of another change, which the note above says is the usual shape. Two findings from that pass are worth keeping. The tree measured 35 against a baseline of 34 BEFORE the drain, i.e. the ratchet was already red on main and nobody knew: this gate runs in `task ci`, no workflow invokes `task ci`, and the local run that would have shown it was read through a `tail`, which hid every row above the last six. And four of the six `If you do nothing:` answers are honestly that nothing degrades \u2014 that is the true answer for a held measurement, and writing it is what makes the entry decidable, so the field earns its place by being cheap to answer truthfully rather than by always carrying a cost. Lowered 29 \u2192 27 on 2026-08-16 by the merge of this branch and the trunk drain above, and the arithmetic matters more than the delta: both sides rewrote entries from the same tree of 35 independently \u2014 the trunk six (recorded above), this branch two (deferred-finding-decision-reopen, spent-inbox-artifacts-await-deletion, rewritten because road-to-inbox-harvest-residuals was being touched anyway). The sets are disjoint, so the merged tree measures 27 and the gate itself reported the slack. Tightened rather than banked: 29 against a 27-entry tree would absorb the next two undecidable blockers without anything noticing, which is the lag this note has now corrected twice. Lowered 27 \u2192 26 on 2026-08-16 by rewriting `picktier-wire-or-archive` (road-to-inbox-harvest-2026-08-d-top-band-model-economy), and this one is the usual shape the note describes rather than the dedicated-drain shape: the roadmap was being worked for its own Step 4.1 and the entry was rewritten because it was open on the desk. Worth recording for what writing the two fields SURFACED, which is the argument for the requirement: `Recommendation:` forced the question 'which option, and why' and the honest answer turned out not to be one of the three options at all \u2014 the entry defers to a converged council verdict, and the council cannot reach quorum (re-measured 0 of 2 the same day), so the actionable next step is a one-line fix to a user-global config file outside this repository. Five fields had described that situation accurately for two weeks without anyone noticing that the named blocker was infrastructure rather than judgement. Lowered 26 \u2192 22 on 2026-08-18 by road-to-estate-drawdown Step 0.1, which needed the two fields for a different reason and therefore drained six entries as a by-product: the consolidated decision sheet renders a DEFAULT plus that default's provenance per user-owned decision, and 7 of the 21 live entries recorded no `Recommendation:` to derive one from. Six were rewritten to the new standard (skill-activation-window, b-convergence-machine, maintainer-blind-ratings, real-orchestration-usage, human-gated-live-trigger-eval, manual-rubric-rater), each marked `agent-drafted` in the field itself so the sheet can label the provenance rather than presenting a drafted default beside a maintainer-recorded one as the same kind of statement. The seventh, `road-to-gated-reach-followup`'s legacy `> Blocked until \u2026` note, is NOT counted here and was not rewritten: it is a blocked-until note rather than a `### blocker:` entry, so it has no field to carry, and converting it is a roadmap restructure this change did not take. **The tree was RED at 28 against this baseline of 26 before the drain, and again nobody knew** \u2014 the same failure the 34\u219229 paragraph above records, one gate cycle later: two entries had been added on the trunk without the fields, and this gate runs in `task ci`, which no workflow invokes. That is the second recorded instance of this ratchet being red on main undetected, which is an argument about where it runs rather than about its number. `human-gated-live-trigger-eval` also gained a command in `What to do:` (`rule_trigger_eval`, verified to exist at src/scripts/) because the field carried a path-less instruction; the other five needed only the two decidability fields. Lowered 22 -> 20 on 2026-08-19 by road-to-estate-drawdown Phase 2 triage batch 1, and RE-MEASURED after two merges of origin/main rather than carried over: the trunk gained roadmaps in between (the long-horizon pair and the council-fallback set), and the live count over the merged tree is still 20 across 39 roadmaps. Six roadmaps moved to later/, and two of them carried an undecidable open blocker each. The gain is a side effect of parking rather than of drafting a recommendation, so it is recorded as such \u2014 the two entries are unchanged and still undecidable where they sit; they are simply outside the active tree this ratchet scans. Walked down because the gate reported itself loose, and a baseline above the measurement is headroom a later change can spend with nothing objecting. Lowered 20 -> 17 on 2026-08-20 by the road-to-frontend-skill-application closeout, and the shape is the usual one this note describes: the roadmap was being closed for its own reasons and its three open blockers were on the desk anyway. Two (ui-corpus-has-no-ui, enforcement-evidence) left the population by being RESOLVED rather than rewritten, which is the cleanest way to lower this number and the only one that does not add prose; the third (ui-session-capture-window) was rewritten to the new standard. Worth recording what writing the two fields surfaced, since that is the standing argument for the requirement: on ui-corpus-has-no-ui, `Recommendation:` forced the question \"which option, and why\" and the honest answer was that the entry's own premise was false \u2014 it asserted the question could not be answered without a human naming a consumer store, and the stores were already on the machine, readable by the command the entry itself printed. Five fields had described that situation accurately for a week without anyone noticing the blocker was a measurement nobody had run. Tightened rather than banked because 20 against a 17-entry tree is headroom the next three undecidable blockers would spend with nothing objecting, which is the lag the paragraphs above have now corrected three times. Walked 17 -> 12 at the drain/road-to-frontend-skill-application merge, 2026-08-21, on the gate's own loose-ratchet report. Re-measured against the merged tree rather than attributed: sibling drain PRs made entries decidable in parallel, so this branch's share is not separable here and is not claimed. Banked at the measurement rather than left as headroom a later change could spend.",
|
|
57
|
+
"falsifier": "./scripts-run src/scripts/lint_roadmap_blockers --quiet"
|
|
58
|
+
},
|
|
53
59
|
"lint_settings_classes:derivable-surface": {
|
|
54
60
|
"count": 83,
|
|
55
61
|
"landed": "2026-08-12",
|
|
56
|
-
"note": "Anti-regrowth ratchet for the settings disposition axis. 83 of 134 template leaves are classified `derivable`
|
|
62
|
+
"note": "Anti-regrowth ratchet for the settings disposition axis. 83 of 134 template leaves are classified `derivable` \u2014 the mechanism can decide from the situation, so the key should not exist \u2014 and each names the replacement that would decide instead (`lint_settings_classes` rejects the bare label, which is what keeps the class from becoming a synonym for \"inconvenient to keep\"). This is a DELETION QUEUE, not 83 deletions: a row stays until the mechanism it names actually exists, and deleting a key ahead of its mechanism is a silently-changed default rather than a simplification. Shrink-only from here, which is the anti-regrowth half \u2014 a NEW key classified `derivable` raises the number and reds, so a key whose answer the situation already carries cannot be added at all. The 56-day non-stagnation clause is the other half and is wanted here rather than tolerated: a queue that never drains is a stated direction nobody is walking. The queue opened at 88 of 140 on 2026-08-12 and drained to 83 of 134 the same day: six keys had no reader whatsoever (telegraph.speak_scope, chat_history.max_size_kb, chat_history.on_overflow, quality.wait_for_remote_ci, screenshots.data_bearing_gate, legal_review_prep.consented_at), five of them `derivable` and one a `consent` row reclassified because a published-egress confirmation is a Hard Floor no setting may lift. An unread key is the only deletion that cannot silently change a default, which is why that batch went first. The classification itself is docs/contracts/settings-classes.md.",
|
|
57
63
|
"falsifier": "./scripts-run src/scripts/lint_settings_classes"
|
|
58
|
-
},
|
|
59
|
-
"check_source_size_budget": {
|
|
60
|
-
"count": 19571,
|
|
61
|
-
"landed": "2026-08-19",
|
|
62
|
-
"note": "Lowered 19,626 → 19,611 on 2026-08-17 by the release-gh-transient branch, and it is a LOWERING through extraction, never a raise — the −15 is against the trunk value, reproducible with `git stash && npx tsx src/scripts/check_source_size_budget.ts`. That branch hardened `release.ts` against GitHub's 5xx responses, which is exactly the kind of change that grows an already over-ceiling file: release.ts sits at 2,819 and carries 1,319 of this total. The retry loop, the transient classifier and the argv elider therefore went into a NEW module (`_lib/gh_transient.ts`, 74 lines, under the ceiling and contributing nothing here), leaving release.ts a 20-line wrapper. The payback came from the five call sites the wrapper replaced: each was a `run(['gh', …], {check, capture})` block spread over up to 17 lines, and each collapses to one `gh([…])` call. The ordering is the lesson and it matches the two entries below: extract, then measure — a wrapper written inline would have added ~20 lines and turned a hardening commit into a ratchet regression. Second lesson, learned the hard way on this same branch: pin the baseline ONCE, at the end. Two intermediate pins (19,606 then 19,612) made subsequent legitimate edits read as `N new` against numbers that had never been on the trunk. Lowered 19,611 -> 19,604 on 2026-08-19 by the long-horizon branch, and it is again a LOWERING through extraction rather than a raise. Worth recording because the branch is the first case the gate met that it had no answer for: two roadmaps of new behaviour whose footprint landed in three files that were ALREADY 3,962 lines over the ceiling before the work started, so the initial reading was +759 new. The council (2/2 seats, 2026-08-19) split between extracting and escalating and agreed on two negatives -- raising the baseline contradicts the gate's own words, and a preparatory refactor PR creates no headroom because lowering the baseline by N and then adding N back trips the gate at the same number. What closed it was seven extractions, each a cohesive unit moved UNCHANGED: mid_flight_fallback, fallback_config, response_render, py_parity, _lib/council_fallback_posture, _lib/council_fallback_wiring, _lib/council_settings_block (+ _lib/map_to_object, which had to move so the settings block could stop importing back into council_cli). Four duplicated call sites collapsed on the way -- the split/ask branch appeared three times and the projected-spend comparison twice, which is how a metered retry could have come to gate on different rules than the round it ran inside. The lesson the previous entry states holds and is now tested at scale: extract, then measure, and pin ONCE at the end -- an intermediate de-duplication briefly RAISED the total by 5, because in an over-ceiling file a shared helper's own docblock can cost more than the duplication it removes. Lowered 19,604 -> 19,571 on 2026-08-19, and this entry records a RED GATE before it records a lowering. The pin at 19,604 was taken at the end of the long-horizon extraction pass; the R2 completion review of that very branch then produced fixes that grew orchestrator.ts by 53 lines, and the gate went red at 19,656 -- 52 new, against a note in this file that claimed a lowering the tree no longer showed. The review caught it, not the author, which is the argument for reviewing a fix pass rather than only the work it fixes. Repaired the way this file's own text demands: by extracting, never by re-pinning. The spend gate came out whole into ai_council/spend_gate.ts -- CostBudget (the ceilings), _breach (the decision), OverrunEvent (what a crossing hands the operator) and the _total_usd arithmetic all three share, re-exported so no caller changed. Cohesive on its own terms: nothing in it calls a provider or knows what a round is. The refusal path of the unmetered escalation moved to mid_flight_fallback.refuseUnmeteredEscalation in the same pass, next to the runGatedRetry it mirrors. Second lesson, and it is the reason the first attempt only bought 5 lines: moving code INTO another over-ceiling file buys nothing, so check which files are actually over the line before choosing a destination -- the same shape as the note below where de-duplication briefly RAISED the total by 5.",
|
|
63
|
-
"falsifier": "./scripts-run src/scripts/check_source_size_budget --quiet"
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
}
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_comment": "Rule-activation ratchet baseline (road-to-mixed-trigger-activation-cost step 4.1). Two axes: the scoped/mixed ID SETS are pinned by identity, and the unconditional token total may only walk DOWN. Raising a number here to clear a failing check is the config-weakening move this repo blocks by construction \u2014 every raise needs a baseline_history entry whose reason is a real sentence. Regenerate with `check_rule_activation_census --write-baseline`; never hand-edit the counts.",
|
|
3
|
-
"measured_at_commit": "
|
|
3
|
+
"measured_at_commit": "unrecorded",
|
|
4
4
|
"tokens_exact": true,
|
|
5
|
-
"unconditional_tokens":
|
|
5
|
+
"unconditional_tokens": 111012,
|
|
6
6
|
"scoped_ids": [
|
|
7
7
|
"design-review-after-ui-write",
|
|
8
|
-
"no-roadmap-references",
|
|
9
8
|
"roadmap-progress-sync",
|
|
10
|
-
"rule-type-governance",
|
|
11
|
-
"skill-quality",
|
|
12
|
-
"source-confidentiality",
|
|
13
9
|
"source-of-truth",
|
|
14
10
|
"ui-audit-gate"
|
|
15
11
|
],
|
|
@@ -33,9 +29,21 @@
|
|
|
33
29
|
"settings-ask-protocol"
|
|
34
30
|
],
|
|
35
31
|
"baseline_history": [
|
|
32
|
+
{
|
|
33
|
+
"date": "2026-08-20",
|
|
34
|
+
"reason": "Re-anchored WITH an authorized change, not to clear a red. road-to-single-delivery Phase 5.1: the four package-only rules that carried `paths:` -- no-roadmap-references, rule-type-governance, skill-quality, source-confidentiality -- had their path triggers REMOVED, so the emitter now writes no `paths:` and all four load unconditionally. Scoped 8 -> 4; unconditional corpus 108130 -> the value above, a RISE of 2882 exact-BPE tokens. The rise is the point, not a regression: ADR-236 partitions the two delivery layers, which removes each rule's unscoped global twin, and ADR-227:79-80 records that a path-scoped rule is not re-injected after `/compact` -- so leaving them scoped would let four obligations, three of them Iron Laws, vanish mid-session with nothing left to reload them. AI council 2026-08-20, 2/2 convergent (anthropic/claude-sonnet-4-5 + openai/codex-default, two rounds, blind peer review) on this option over keeping the scope, keeping an unscoped global copy, and splitting the four by CI-gate coverage. Two independent decisive arguments: the obligations govern AUTHORING decisions, which happen before any file exists for a path trigger to match (so path-scoping fails at greenfield creation, compaction aside), and rule-type-governance has NO deterministic gate at all, so for that one there is no compensating control whatsoever. Cost in context: the same change removes 96584 tokens of doubled standing prose per session (210485 -> 113901 received, overlap 113 rules / 93411 tokens -> 0), so this +2882 is 3.0 % of what the partition returns. The figure moved twice and both moves are recorded rather than smoothed: an early revision claimed 1754 tokens from a chars/4 proxy, which understated the true BPE cost by about half (measured +3512); collapsing four duplicated rationale sections into one brought it to +3156; and check_rule_stub_ceiling then refused the three short pointers outright -- a migrated stub is held at its pointer's size, so prose added there is prose in the wrong place by that gate's contract. Removing those three notes landed the final +2882, with the single shared record living in source-confidentiality, which is not a migrated stub and has no ceiling."
|
|
35
|
+
},
|
|
36
36
|
{
|
|
37
37
|
"date": "2026-08-17",
|
|
38
38
|
"reason": "Re-anchored WITH the authorized change, not to clear a red. road-to-mixed-trigger-activation-cost Phase 2, amendment option (b) narrowed to the two path-dominant per-edit rules: `design-review-after-ui-write` and `ui-audit-gate` had their `component` and `design token` keyword triggers removed as an authoring decision, so the emitter restores `paths:` for both. Scoped 6 -> 8, mixed 19 -> 17, unconditional corpus 107194 -> the value above. The direction is DOWN on the weight axis and the identity move is the authorized one; the maintainer asked for this entry in the same commit so the ratchet documents its own sanctioned change instead of reporting it as drift."
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"date": "2026-08-20",
|
|
42
|
+
"reason": "Re-anchored WITH an authorized addition, not to clear a red: the new `recurring-criticism` rule plus the reconciling edits to `self-repair-loop` and `decision-revisit-gate`. The cost is structural, not sloppy authoring \u2014 the rule fires on a recurrence phrase in the USER PROMPT, and on Claude the emitter writes no `paths:` at all once any non-path trigger is present, so a prompt-triggered rule is unconditional by construction. There is no scoped variant of this obligation to choose instead: a path-scoped version would never see the prompt that triggers it, which is the whole mechanism. The body was already cut once for this reason (~45 percent, depth moved to guideline:agent-infra/recurring-criticism-mechanics, which is read on demand and costs nothing at session start); what remains is the Iron Law, the three-outcome table that is the anti-sycophancy guard, and the honest-enforcement paragraph. Trimming further would remove the reasoning the rule itself marks as the clause most likely to be trimmed. Delta 3575 tokens against a 2000 allowance, standing cost per session, stated rather than hidden."
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"date": "2026-08-20",
|
|
46
|
+
"reason": "Raised 103,265 -> 106,386 (+3,121) for one new rule, fix-what-you-see, added on maintainer instruction after a session identified a red check's cause, named the file's author, and handed it back unfixed. The rule permits exactly two dispositions -- fix it, or ship a tracked follow-up roadmap in the same change -- and removes ownership and note-only as escapes from active-remediation's ladder. WHAT THIS RE-ANCHOR EXPOSES, and it is the reason the entry is this long: the rule ships type auto rather than always because check_always_budget has two characters of headroom and its cap may only move down. That switch cleared the always gate and did NOT reduce the cost, because this census counts a rule as standing payload unless it is path-SCOPED, and a red CI check has no file pattern to scope on. So the two gates disagree about what a keyword-triggered rule costs, and the auto downgrade moved the same 3,121 tokens from a budget that refuses them into one that only asks for a sentence. Stated rather than exploited: the honest reading is that this rule costs standing payload whatever its type says, and the always-vs-auto choice bought delivery breadth, not weight."
|
|
39
47
|
}
|
|
40
48
|
]
|
|
41
49
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
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
|
|
3
|
-
"measured_at_commit": "
|
|
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
|
+
"measured_at_commit": "955378e83",
|
|
4
4
|
"tokens_exact": true,
|
|
5
5
|
"ceilings": {
|
|
6
6
|
"active-remediation": 1601,
|
|
@@ -14,9 +14,8 @@
|
|
|
14
14
|
"cli-output-handling": 83,
|
|
15
15
|
"code-comment-discipline": 692,
|
|
16
16
|
"commit-conventions": 136,
|
|
17
|
-
"context-hygiene":
|
|
17
|
+
"context-hygiene": 2470,
|
|
18
18
|
"copilot-routing": 128,
|
|
19
|
-
"decision-revisit-gate": 545,
|
|
20
19
|
"design-fidelity": 2245,
|
|
21
20
|
"devcontainer-routing": 128,
|
|
22
21
|
"docker-commands": 79,
|
|
@@ -38,7 +37,7 @@
|
|
|
38
37
|
"provider-lifecycle-discipline": 472,
|
|
39
38
|
"reviewer-awareness": 131,
|
|
40
39
|
"roadmap-ci-steps-policy": 564,
|
|
41
|
-
"roadmap-progress-sync":
|
|
40
|
+
"roadmap-progress-sync": 2185,
|
|
42
41
|
"rule-type-governance": 87,
|
|
43
42
|
"skill-improvement-trigger": 81,
|
|
44
43
|
"skill-quality": 78,
|
|
@@ -54,7 +53,21 @@
|
|
|
54
53
|
"rule": "roadmap-progress-sync",
|
|
55
54
|
"from": 1424,
|
|
56
55
|
"to": 2117,
|
|
57
|
-
"reason": "Iron Law 3 gained the preservation test that decides WHO resolves a deferred item
|
|
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
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"date": "2026-08-20",
|
|
60
|
+
"rule": "context-hygiene",
|
|
61
|
+
"from": 2447,
|
|
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) \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
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"date": "2026-08-20",
|
|
67
|
+
"rule": "roadmap-progress-sync",
|
|
68
|
+
"from": 2117,
|
|
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 \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."
|
|
58
71
|
}
|
|
59
72
|
]
|
|
60
73
|
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Claude Code `paths:` emission plan — which globs a rule projects, and which
|
|
3
|
+
* it deliberately does not.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from `condense.ts` on 2026-08-20 (road-to-single-delivery Phase 5).
|
|
6
|
+
* Two reasons, and the second is the one that made it worth doing now.
|
|
7
|
+
*
|
|
8
|
+
* It belongs here on its own merits: this is host-activation-key logic, the same
|
|
9
|
+
* subject as `emit_host_rules_cli.ts` beside it, and `condense.ts` is the
|
|
10
|
+
* orchestrator that calls it rather than the place it lives.
|
|
11
|
+
*
|
|
12
|
+
* And it pays for the phase that moved it. Phase 5.1 removed the path triggers
|
|
13
|
+
* from four package-only rules so they load unconditionally — a change ABOUT
|
|
14
|
+
* this plan — and wiring the delivery partition into `condense.ts` and
|
|
15
|
+
* `install.ts` added lines to two files already past the 1,500-line source
|
|
16
|
+
* ceiling, where `check_source_size_budget` counts every added line as a
|
|
17
|
+
* violation. That gate's own convention is that the total moves down through
|
|
18
|
+
* extraction and never up through a baseline raise, so the extraction is the fix
|
|
19
|
+
* rather than a favour: the plan lands in a module under the ceiling, and the
|
|
20
|
+
* ratchet sees a net improvement instead of a regression.
|
|
21
|
+
*
|
|
22
|
+
* Side-effect-free, no CLI entry, no `process.exit`.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Derive host-native activation globs from a rule's `triggers:` frontmatter
|
|
27
|
+
* (road-to-request-scoped-rule-load Phase 2). `file_pattern` triggers map
|
|
28
|
+
* verbatim; `path_prefix` triggers map to `<prefix>**`. Keyword / phrase /
|
|
29
|
+
* command triggers produce no glob — those rules stay
|
|
30
|
+
* description-activated (Cursor Agent-Requested / Windsurf model_decision).
|
|
31
|
+
*/
|
|
32
|
+
export function derive_trigger_globs(meta: Record<string, unknown>): string[] {
|
|
33
|
+
const triggers = meta['triggers'];
|
|
34
|
+
if (!Array.isArray(triggers)) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
const globs: string[] = [];
|
|
38
|
+
for (const t of triggers) {
|
|
39
|
+
if (t === null || typeof t !== 'object' || Array.isArray(t)) continue;
|
|
40
|
+
const obj = t as Record<string, unknown>;
|
|
41
|
+
if (typeof obj['file_pattern'] === 'string' && obj['file_pattern']) {
|
|
42
|
+
globs.push(obj['file_pattern']);
|
|
43
|
+
} else if (typeof obj['path_prefix'] === 'string' && obj['path_prefix']) {
|
|
44
|
+
const prefix = obj['path_prefix'] as string;
|
|
45
|
+
globs.push(prefix.endsWith('/') ? `${prefix}**` : `${prefix}**`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// De-dup, keep declaration order (deterministic output).
|
|
49
|
+
return [...new Set(globs)];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A brace group the host expands, versus an agent-config placeholder that only
|
|
54
|
+
* LOOKS like one.
|
|
55
|
+
*
|
|
56
|
+
* `path_prefix: "{module_root}/"` in `roadmap-ci-steps-policy` is resolved by
|
|
57
|
+
* this suite from `modules.root_paths`. Emitted verbatim into a host `paths:`
|
|
58
|
+
* list it is a brace group with no comma, which the host expands to the literal
|
|
59
|
+
* string `{module_root}` — a pattern that matches no file. The rule would then
|
|
60
|
+
* reach the model **never** instead of on-demand, and nothing would say so: the
|
|
61
|
+
* probed contract records this exact degradation as silent
|
|
62
|
+
* (`claude-code-rules-dir-contract.md` § constraints).
|
|
63
|
+
*
|
|
64
|
+
* Dropping the pattern is fail-safe in the one direction that matters. A rule
|
|
65
|
+
* left with no pattern at all carries no `paths:` key and loads
|
|
66
|
+
* unconditionally — the status quo, not a regression. A rule carrying a
|
|
67
|
+
* no-op pattern is invisible.
|
|
68
|
+
*
|
|
69
|
+
* The discriminator is the comma, not a placeholder allowlist: `{ts,tsx}` is a
|
|
70
|
+
* real alternation, `{module_root}` cannot be one.
|
|
71
|
+
*/
|
|
72
|
+
export function _is_unresolved_placeholder(pattern: string): boolean {
|
|
73
|
+
for (const m of pattern.matchAll(/\{([^}]*)\}/g)) {
|
|
74
|
+
if (!(m[1] as string).includes(',')) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* `[` opens a bracket expression for the host. An unparseable one makes that
|
|
83
|
+
* pattern match nothing while the rule's other patterns keep working — so a
|
|
84
|
+
* literal `[` in a trigger is escaped rather than trusted.
|
|
85
|
+
*/
|
|
86
|
+
export function _escape_claude_bracket(pattern: string): string {
|
|
87
|
+
return pattern.replace(/\[/g, '\\[');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Expanded-pattern cost of one glob: the product of its brace alternations.
|
|
92
|
+
* The host budgets a rule's whole `paths:` list at 1,000 expanded patterns and
|
|
93
|
+
* 4 MiB, and a list over budget is used UNEXPANDED — braces then match
|
|
94
|
+
* literally, i.e. nothing. Counting keeps the emitter under the cliff instead
|
|
95
|
+
* of discovering it as a silent no-op.
|
|
96
|
+
*/
|
|
97
|
+
export function _expanded_pattern_count(pattern: string): number {
|
|
98
|
+
let n = 1;
|
|
99
|
+
for (const m of pattern.matchAll(/\{([^}]*)\}/g)) {
|
|
100
|
+
n *= (m[1] as string).split(',').length;
|
|
101
|
+
}
|
|
102
|
+
return n;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Host-documented budget for one rule's whole `paths:` list. */
|
|
106
|
+
export const CLAUDE_PATHS_PATTERN_BUDGET = 1000;
|
|
107
|
+
|
|
108
|
+
export interface ClaudePathsPlan {
|
|
109
|
+
/** Patterns to emit under `paths:`, in declaration order. */
|
|
110
|
+
globs: string[];
|
|
111
|
+
/** Patterns deliberately not emitted, with the reason a reader can act on. */
|
|
112
|
+
dropped: { pattern: string; reason: 'unresolved-placeholder' | 'over-budget' | 'mixed-triggers' }[];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Does this rule declare any trigger that is NOT path-shaped?
|
|
117
|
+
*
|
|
118
|
+
* Load-bearing for `_claude_paths_plan` below, because one `triggers:` list
|
|
119
|
+
* means two different things on two hosts. Cursor and Windsurf treat globs as
|
|
120
|
+
* ADDITIVE — a rule with empty globs still reaches the agent through its
|
|
121
|
+
* description (see `_emit_cursor_mdc`'s own comment). Claude Code treats
|
|
122
|
+
* `paths:` as EXCLUSIVE: with it the rule loads on a path match; without it the
|
|
123
|
+
* rule loads unconditionally. The emitter fed both semantics from one list, so
|
|
124
|
+
* a rule that declared keywords *and* one path glob silently became
|
|
125
|
+
* path-gated-only on Claude.
|
|
126
|
+
*/
|
|
127
|
+
export function _has_non_path_trigger(meta: Record<string, unknown>): boolean {
|
|
128
|
+
const triggers = meta['triggers'];
|
|
129
|
+
if (!Array.isArray(triggers)) return false;
|
|
130
|
+
for (const t of triggers) {
|
|
131
|
+
if (t === null || typeof t !== 'object' || Array.isArray(t)) continue;
|
|
132
|
+
const obj = t as Record<string, unknown>;
|
|
133
|
+
if (typeof obj['keyword'] === 'string' && obj['keyword']) return true;
|
|
134
|
+
if (typeof obj['phrase'] === 'string' && obj['phrase']) return true;
|
|
135
|
+
}
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Derive the host's `paths:` list for one rule, with every drop recorded.
|
|
141
|
+
*
|
|
142
|
+
* Kernel / always-apply rules get an EMPTY list on purpose: absent `paths:` is
|
|
143
|
+
* how this host says "load unconditionally", which is exactly what an Iron-Law
|
|
144
|
+
* rule needs. Scoping one would be a correctness regression dressed as a byte
|
|
145
|
+
* saving — and the same contract notes path-scoped rules are not re-injected
|
|
146
|
+
* after `/compact`, so an obligation that must survive compaction cannot be
|
|
147
|
+
* path-scoped at all.
|
|
148
|
+
*/
|
|
149
|
+
export function _claude_paths_plan(meta: Record<string, unknown>): ClaudePathsPlan {
|
|
150
|
+
const always_apply = Boolean(meta['alwaysApply'] || meta['type'] === 'always');
|
|
151
|
+
if (always_apply) {
|
|
152
|
+
return { globs: [], dropped: [] };
|
|
153
|
+
}
|
|
154
|
+
const globs: string[] = [];
|
|
155
|
+
const dropped: ClaudePathsPlan['dropped'] = [];
|
|
156
|
+
|
|
157
|
+
// A rule that ALSO declares keyword / phrase triggers gets no `paths:` at
|
|
158
|
+
// all, and therefore keeps loading unconditionally on Claude Code.
|
|
159
|
+
//
|
|
160
|
+
// Emitting `paths:` here would narrow the rule to a path match and discard
|
|
161
|
+
// every keyword the author wrote, because this host reads the list as the
|
|
162
|
+
// whole gate rather than as one way in. Measured before this guard: 25 of
|
|
163
|
+
// 110 projected rules carried `paths:` and 19 of them also carried keyword
|
|
164
|
+
// triggers — `design-fidelity` lost 21 of them, in exactly the pasted-
|
|
165
|
+
// screenshot and capability-URL handovers its own routing section names as
|
|
166
|
+
// the classes it exists to catch. Nothing in the tree recorded a decision
|
|
167
|
+
// to make that trade, which is why it is treated as a defect.
|
|
168
|
+
//
|
|
169
|
+
// The drop is REPORTED rather than silent, so a rule whose obligation
|
|
170
|
+
// really is path-bound is visible as a candidate for removing its keyword
|
|
171
|
+
// triggers — the narrowing then becomes an authoring decision instead of an
|
|
172
|
+
// emitter side effect. Cursor and Windsurf are untouched: globs are
|
|
173
|
+
// additive there, so the same rule keeps both routes.
|
|
174
|
+
if (_has_non_path_trigger(meta)) {
|
|
175
|
+
for (const raw of derive_trigger_globs(meta)) {
|
|
176
|
+
dropped.push({ pattern: raw, reason: 'mixed-triggers' });
|
|
177
|
+
}
|
|
178
|
+
return { globs, dropped };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let spent = 0;
|
|
182
|
+
for (const raw of derive_trigger_globs(meta)) {
|
|
183
|
+
if (_is_unresolved_placeholder(raw)) {
|
|
184
|
+
dropped.push({ pattern: raw, reason: 'unresolved-placeholder' });
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const cost = _expanded_pattern_count(raw);
|
|
188
|
+
if (spent + cost > CLAUDE_PATHS_PATTERN_BUDGET) {
|
|
189
|
+
dropped.push({ pattern: raw, reason: 'over-budget' });
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
spent += cost;
|
|
193
|
+
globs.push(_escape_claude_bracket(raw));
|
|
194
|
+
}
|
|
195
|
+
return { globs, dropped };
|
|
196
|
+
}
|