@event4u/agent-config 14.7.0 → 14.9.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 +175 -390
- package/README.md +1 -1
- package/agents/templates/.ai-council.yml.example +34 -4
- package/dist/agent-src/commands/evals/git-pr-merge.json +13 -0
- package/dist/agent-src/commands/evals/roadmap.json +17 -1
- package/dist/agent-src/commands/pr/create.md +14 -2
- package/dist/agent-src/commands/pr/merge.md +383 -0
- package/dist/agent-src/commands/review/changes.md +95 -14
- package/dist/agent-src/commands/roadmap/process-full.md +157 -3
- package/dist/agent-src/commands/roadmap.md +1 -1
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +46 -36
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +37 -2
- package/dist/agent-src/contexts/execution/host-capability-manifest.md +77 -0
- package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +100 -2
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +154 -2
- package/dist/agent-src/contexts/execution/subagent-response-contract.md +80 -0
- package/dist/agent-src/rules/artifact-drafting-protocol.md +5 -1
- package/dist/agent-src/rules/decision-revisit-gate.md +65 -33
- package/dist/agent-src/rules/recurring-criticism.md +1 -1
- package/dist/agent-src/rules/roadmap-progress-sync.md +19 -0
- package/dist/agent-src/rules/source-confidentiality.md +1 -1
- package/dist/agent-src/rules/untrusted-input-defense.md +9 -3
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +7 -4
- package/dist/agent-src/scripts/dashboard_mode.ts +212 -0
- package/dist/agent-src/scripts/update_roadmap_progress.ts +47 -48
- package/dist/agent-src/skills/adr-create/SKILL.md +124 -5
- package/dist/agent-src/skills/code-review/evals/evals.json +87 -46
- package/dist/agent-src/skills/decision-record/SKILL.md +61 -3
- package/dist/agent-src/skills/decision-review/SKILL.md +65 -0
- package/dist/agent-src/skills/humanizer/SKILL.md +39 -0
- package/dist/agent-src/skills/humanizer/evals/strip_fixtures.json +57 -0
- package/dist/agent-src/skills/humanizer/references/fixtures.md +54 -0
- package/dist/agent-src/skills/judge-spec-compliance/SKILL.md +154 -0
- package/dist/agent-src/skills/judge-synthesis/SKILL.md +45 -0
- package/dist/agent-src/skills/merge-conflicts/SKILL.md +26 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +18 -18
- package/dist/agent-src/skills/security/SKILL.md +8 -0
- package/dist/agent-src/skills/security-audit/SKILL.md +12 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +28 -2
- package/dist/cli/registry.js +3 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-FBKL6JWT.js → chunk-77GR3VD4.js} +68 -43
- package/dist/cli-delegate/{chunk-JZ4NR2TW.js → chunk-7PRE6ZV2.js} +1 -1
- package/dist/cli-delegate/{chunk-UEZ7D35A.js → chunk-CYH5SP6M.js} +4 -23
- package/dist/cli-delegate/{chunk-Q26HT4QT.js → chunk-E46KPN6K.js} +251 -150
- package/dist/cli-delegate/{chunk-74E3TKWV.js → chunk-H4VIVD4C.js} +5 -5
- package/dist/cli-delegate/chunk-I3EIN2YG.js +36 -0
- package/dist/cli-delegate/{chunk-4VR4ORQ6.js → chunk-LPPWO5CE.js} +4 -3
- package/dist/cli-delegate/{chunk-7F3VYSM2.js → chunk-PLSG6UL5.js} +14 -14
- package/dist/cli-delegate/cmd_adr_effective.js +1162 -0
- package/dist/cli-delegate/cmd_conformance.js +101 -25
- package/dist/cli-delegate/cmd_converge.js +3 -3
- package/dist/cli-delegate/cmd_doctor.js +7 -7
- package/dist/cli-delegate/cmd_export.js +6 -6
- package/dist/cli-delegate/cmd_handoff.js +7 -6
- package/dist/cli-delegate/cmd_memory_get.js +2 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_preflight.js +5 -5
- package/dist/cli-delegate/cmd_prune.js +6 -6
- package/dist/cli-delegate/cmd_route_audit.js +6 -5
- package/dist/cli-delegate/cmd_route_explain.js +3 -3
- package/dist/cli-delegate/cmd_session_recycle.js +3 -3
- package/dist/cli-delegate/cmd_settings_get.js +4 -4
- package/dist/cli-delegate/cmd_settings_migrate.js +6 -6
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +6 -6
- package/dist/cli-delegate/cmd_uninstall.js +6 -6
- package/dist/cli-delegate/cmd_update.js +3 -3
- package/dist/cli-delegate/cmd_upgrade.js +3 -3
- package/dist/cli-delegate/cmd_validate.js +6 -6
- package/dist/cli-delegate/cmd_versions.js +3 -3
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +83 -30
- 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 +11 -9
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +9 -7
- package/dist/hooks/dispatch.js +1295 -812
- package/dist/install/globalRuleLayers.js +169 -0
- package/dist/install/globalRuleLayers.js.map +1 -0
- package/dist/install/partitionEligibility.js +156 -0
- package/dist/install/partitionEligibility.js.map +1 -1
- package/dist/install/ruleLayerPartition.js +157 -0
- package/dist/install/ruleLayerPartition.js.map +1 -0
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1358 -805
- package/dist/scripts/_lib/claude_plugin.js +162 -0
- package/dist/scripts/_lib/claude_plugin.js.map +1 -0
- package/dist/scripts/_lib/environment_detector.js +397 -0
- package/dist/scripts/_lib/environment_detector.js.map +1 -0
- package/dist/scripts/_lib/prune_empty_dirs.js +85 -0
- package/dist/scripts/_lib/prune_empty_dirs.js.map +1 -0
- package/dist/scripts/_lib/spawn_env.js +129 -0
- package/dist/scripts/_lib/spawn_env.js.map +1 -0
- package/dist/scripts/ai_council/modes.js +199 -0
- package/dist/scripts/ai_council/modes.js.map +1 -0
- package/dist/scripts/ai_council/transport_resolver.js +418 -0
- package/dist/scripts/ai_council/transport_resolver.js.map +1 -0
- package/dist/scripts/condense.js +52 -89
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/routes/wizard.js +3 -2
- package/dist/server/routes/wizard.js.map +1 -1
- package/dist/ui/assets/index-Cxgy63kz.js +26 -0
- package/dist/ui/assets/index-Cxgy63kz.js.map +1 -0
- package/dist/ui/index.html +1 -1
- package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +45 -1
- package/docs/guidelines/agent-infra/false-green.md +47 -1
- package/docs/guidelines/agent-infra/traceability-field-mechanics.md +50 -0
- package/docs/guidelines/agent-infra/untrusted-input-spotlighting.md +17 -0
- package/package.json +7 -3
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +7 -4
- package/src/agent-src/scripts/dashboard_mode.ts +212 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +47 -48
- package/src/config/ci-local-parity.yml +21 -0
- package/src/config/estate-count-budget.json +13 -471
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +458 -12
- package/src/config/gate-violation-baselines.json +18 -8
- package/src/config/gitignore-block.txt +15 -2
- package/src/config/hook-latency-budget.json +5 -0
- package/src/config/publish-surface.json +60 -0
- package/src/config/requirements-trace-budget.json +32 -0
- package/src/config/rule-stub-ceilings.json +25 -4
- package/src/install/globalRuleLayers.ts +197 -0
- package/src/install/partitionEligibility.ts +177 -0
- package/src/install/ruleLayerPartition.ts +212 -0
- package/src/scripts/_cli/cmd_adr_effective.ts +484 -0
- package/src/scripts/_dispatch.bash +18 -0
- package/src/scripts/_lib/adr_frontmatter.test.ts +365 -0
- package/src/scripts/_lib/adr_frontmatter.ts +459 -0
- package/src/scripts/_lib/base_tree.ts +175 -0
- package/src/scripts/_lib/billing_grant.ts +165 -0
- package/src/scripts/_lib/billing_grant_cli.ts +123 -0
- package/src/scripts/_lib/council_fallback_posture.ts +53 -6
- package/src/scripts/_lib/council_fallback_wiring.ts +61 -1
- package/src/scripts/_lib/envelope_grounding.ts +11 -1
- package/src/scripts/_lib/host_capability.ts +59 -5
- package/src/scripts/_lib/layer_overlap_notice.ts +11 -2
- package/src/scripts/_lib/prune_empty_dirs.ts +80 -0
- package/src/scripts/_lib/quota_parked.ts +133 -0
- package/src/scripts/_lib/retrieval_sanitize.ts +62 -0
- package/src/scripts/_lib/review_telemetry.ts +196 -0
- package/src/scripts/_lib/subagent_response.ts +92 -0
- package/src/scripts/adr/evidence_census.ts +899 -0
- package/src/scripts/adr/regenerate_index.ts +25 -38
- package/src/scripts/adr_cite_check.ts +540 -39
- package/src/scripts/ai_council/clients.ts +12 -1
- package/src/scripts/ai_council/config.ts +72 -115
- package/src/scripts/ai_council/fallback_config.ts +19 -7
- package/src/scripts/ai_council/mid_flight_fallback.ts +88 -7
- package/src/scripts/ai_council/orchestrator.ts +2 -2
- package/src/scripts/ai_council/py_format.ts +124 -0
- package/src/scripts/ai_council/transport_resolver.ts +65 -2
- package/src/scripts/audit_adr_coverage.ts +134 -21
- package/src/scripts/bench_hook_latency.ts +204 -0
- package/src/scripts/build_archive_index.ts +34 -0
- package/src/scripts/check_adr_frontmatter.ts +574 -39
- package/src/scripts/check_council_pin_staleness.ts +300 -0
- package/src/scripts/check_estate_count.ts +420 -293
- package/src/scripts/check_gate_coverage.ts +91 -0
- package/src/scripts/check_hook_bundle_content.ts +219 -0
- package/src/scripts/check_new_adr_evidence.ts +529 -0
- package/src/scripts/check_no_stub_inventory_table.ts +292 -0
- package/src/scripts/check_pack_size.ts +234 -1
- package/src/scripts/check_publish_surface.ts +297 -0
- package/src/scripts/check_references.ts +18 -0
- package/src/scripts/check_requirements_trace.ts +378 -0
- package/src/scripts/check_rule_layer_partition.ts +531 -0
- package/src/scripts/check_secret_leak.ts +166 -12
- package/src/scripts/check_single_delivery.ts +137 -4
- package/src/scripts/check_standing_rule_delivery.ts +8 -2
- package/src/scripts/code_graph/cli.ts +44 -5
- package/src/scripts/code_graph/extract.ts +87 -0
- package/src/scripts/condense.ts +61 -91
- package/src/scripts/council_cli.ts +12 -16
- package/src/scripts/detect_ai_tells.ts +87 -0
- package/src/scripts/dispatch_r2_reviewer.ts +42 -0
- package/src/scripts/external_sources_denylist.json +4 -1
- package/src/scripts/generate_capabilities_index.ts +41 -0
- package/src/scripts/git_authorization_hook.ts +54 -4
- package/src/scripts/hooks/block_unauthorized_git.ts +41 -3
- package/src/scripts/hooks/dispatch_hook.ts +154 -187
- package/src/scripts/hooks/dispatch_issues.ts +38 -19
- package/src/scripts/hooks/fallback_yaml.ts +66 -0
- package/src/scripts/hooks/host_semantics.ts +21 -0
- package/src/scripts/hooks/injection_budget.ts +13 -3
- package/src/scripts/hooks/py_json_dumps.ts +103 -0
- package/src/scripts/hooks/run_continuation_hook.ts +3 -1
- package/src/scripts/hooks/state_io.ts +88 -1
- package/src/scripts/hooks/stdin_failure_policy.ts +96 -0
- package/src/scripts/hooks/subagent_ledger_hook.ts +76 -11
- package/src/scripts/injection_scan_hook.ts +257 -15
- package/src/scripts/lint_adr_sweep_routing.ts +365 -0
- package/src/scripts/lint_hook_manifest.ts +49 -0
- package/src/scripts/lint_provenance_vocabulary.ts +472 -4
- package/src/scripts/lint_workflow_security.ts +53 -10
- package/src/scripts/lint_workflow_security_allowlist.json +2 -18
- package/src/scripts/report_envelope_rate.ts +166 -0
- package/src/scripts/review_axis_report.ts +218 -0
- package/src/scripts/routing_doctor.ts +5 -1
- package/src/scripts/run_supervise.ts +46 -1
- package/src/scripts/schemas/evals.schema.json +5 -0
- package/src/scripts/sync_pr_branch.ts +170 -20
- package/dist/cli/commands/commands.candidates.test.js +0 -257
- package/dist/cli/commands/commands.candidates.test.js.map +0 -1
- package/dist/cli/commands/profiles.test.js +0 -88
- package/dist/cli/commands/profiles.test.js.map +0 -1
- package/dist/cli/commands/recordTriggerEval.test.js +0 -113
- package/dist/cli/commands/recordTriggerEval.test.js.map +0 -1
- package/dist/cli/configRoot.test.js +0 -97
- package/dist/cli/configRoot.test.js.map +0 -1
- package/dist/cli/mcp/content.test.js +0 -54
- package/dist/cli/mcp/content.test.js.map +0 -1
- package/dist/cli/mcp/dispatch.test.js +0 -108
- package/dist/cli/mcp/dispatch.test.js.map +0 -1
- package/dist/server/writeRoot.test.js +0 -91
- package/dist/server/writeRoot.test.js.map +0 -1
- package/dist/shared/capabilities.test.js +0 -39
- package/dist/shared/capabilities.test.js.map +0 -1
- package/dist/ui/assets/index-C1pBRm1f.js +0 -26
- package/dist/ui/assets/index-C1pBRm1f.js.map +0 -1
- package/dist/cli-delegate/{chunk-PPQO4NGR.js → chunk-6RJA2F5A.js} +3 -3
- package/dist/cli-delegate/{chunk-6EOCNQAM.js → chunk-E4XIZDIB.js} +0 -0
- package/dist/cli-delegate/{chunk-GJ5BVHXM.js → chunk-PMEKGDED.js} +3 -3
|
@@ -23,9 +23,9 @@
|
|
|
23
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
24
|
},
|
|
25
25
|
"check_source_size_budget": {
|
|
26
|
-
"count":
|
|
27
|
-
"landed": "2026-08-
|
|
28
|
-
"note": "Lowered 19,
|
|
26
|
+
"count": 19363,
|
|
27
|
+
"landed": "2026-08-22",
|
|
28
|
+
"note": "Lowered 19,395 -> 19,363 on 2026-08-22 by road-to-council-seat-selection, and the -32 is the residue of a LOWERING THROUGH EXTRACTION that had to pay for its own feature first. The branch adds the verified_at member stamp and the check_council_pin_staleness gate, whose 74 new lines landed in two files already far above the ceiling -- ai_council/config.ts (+63, at 2,272) and ai_council/clients.ts (+11, at 2,862). This gate refused it, correctly, and the debt could not be paid out of the additions themselves: of the 63 only a ~48-line validation block is a movable unit, and the 11 are a comment on a one-line constant. So the payment came from the cleanest pre-existing unit in the same file -- the already-labelled, already-self-contained 'Python-format helpers' section (114 lines) moved whole to ai_council/py_format.ts, which config.ts now imports. Net -32 past zero rather than exactly to it, because the section is a section and splitting it to hit a number would leave a worse file than moving it.",
|
|
29
29
|
"falsifier": "./scripts-run src/scripts/check_source_size_budget --quiet"
|
|
30
30
|
},
|
|
31
31
|
"ci-parity:local-only": {
|
|
@@ -50,11 +50,11 @@
|
|
|
50
50
|
"landed": "2026-08-02",
|
|
51
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
52
|
},
|
|
53
|
-
"
|
|
54
|
-
"count":
|
|
55
|
-
"landed": "2026-08-
|
|
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/
|
|
53
|
+
"lint_provenance_vocabulary:permanence-language": {
|
|
54
|
+
"count": 5,
|
|
55
|
+
"landed": "2026-08-22",
|
|
56
|
+
"note": "Permanence language in an ADR record's load-bearing positions -- its `# ` title, its `decision:` frontmatter slug, its `## Decision` section, or an `Addendum` heading. Revealed by adding rule 4 to lint_provenance_vocabulary, not created by it: ADR-240 section 6 states the doctrine (`forever`, `permanently`, `never revisit`, `never reconsider` and their kin do not belong in a mechanism decision's title, slug or Decision section) and the corpus had five records violating it on the day the rule landed. Baselined rather than hard-failed because a gate that lands as five instant blockers is the shape this repository has already refused (see ci-parity:local-only and lint_roadmap_blockers:decidability above), and every one of the five is a content decision rather than a copy edit: ADR-208 (slug `dist-agent-src-keep-forever`, title \"kept forever\", Decision \"KEEP -- permanently\") is the standing self-contradiction ADR-240 cites, since lines 10-11 of its own frontmatter carry the conditions under which it should be reopened; ADR-107:37 and ADR-108:5/12/24/29 are owner PURPOSE statements (\"open-source forever\", \"now or in the future\", \"liability disclaimer is permanent\") whose fix is the escape ADR-240 names -- record `authority_basis: owner_intent` -- which is an owner ruling, not an agent edit; ADR-122:77/154 and ADR-124:229 assert a permanently-off default whose honest form is a default-off setting plus a reopen condition. The rule is deliberately SCOPED and the scoping is the gate: the same word list applied to whole files matches many more, and the large majority are legitimate. The comparison figure is given with its method because 'unscoped' has two readings and an earlier version of this note picked one without saying which, giving 41 lines across 30 records which reproduces neither way: the bare word list over both ADR surfaces matches 45 lines in 26 records, and the word list PLUS the lint's own line filters (code fences, quoted spans, table rows, in-sentence negation, attributive `permanent`, hyphen compounds) but WITHOUT position scoping matches 29 in 17. Scoping to the four load-bearing positions takes it to 11 in 5. The 29-to-11 step is what the scoping buys; the 45 is what a naive grep would report -- an `## Alternatives considered` entry rejecting a permanence option (ADR-105:97, ADR-137:148-151, ADR-212:133), a `## Consequences` line reporting one (ADR-136:90, ADR-208:73, ADR-216:278/298), a `## Context` recounting one (ADR-042:36, ADR-232:34), a quoted open question (ADR-201:342/343) and a table rationale cell bounding a failure mode (ADR-074:41, ADR-225:64) are all describing a permanence claim rather than making one. Shrink-only from here, which is the property that matters: a NEW record asserting permanence in any of the four positions reds this gate immediately, and headroom is zero. Lowering happens one record at a time -- convert an owner purpose statement to `authority_basis: owner_intent`, or restate a mechanism decision with the reopen condition it already carries. The 56-day expiry is the point rather than a hazard: if this number has not moved in two months, the doctrine is not being applied and the gate should fail until someone decides whether it is real.\n\nWALKED 11 -> 5 on 2026-08-22 by road-to-evidence-based-adr-governance 4.3, and the split is exactly the one this note predicted: the six AGENT-EXECUTABLE hits are fixed and the five OWNER-RESERVED ones are untouched. ADR-208's slug is now `dist-agent-src-is-the-shipped-projection-tree`, its title says \"until its `review_trigger` fires\", and its Decision reads \"KEEP, until `review_trigger` (a), (b) or (c) fires\" -- the escape ADR-240 section 6 names, taken against reopen conditions the record's own frontmatter already carried, which is why it needed no new judgement. ADR-122:77/154 and ADR-124:229 asserted a permanently-off default; each now states default-off plus the reopen condition its own measurement supplies (for ADR-122, a re-run clearing both locked thresholds with the FP control held; for ADR-124, the category limit behind the null being shown not to hold). Those two carry no `review_trigger` at all -- both predate the 2026-07-25 requirement -- so the condition was derived from the measurement in the record rather than restated from frontmatter, and that difference is recorded because it is the weaker of the two moves. The FILE NAME of ADR-208 is deliberately unchanged: no gate couples the `decision:` slug to the filename (regenerate_index reads the slug only as a fallback when `decision:` is absent), and renaming would rewrite eight inbound references, two of them inside frozen review-input diffs and one inside an archived roadmap whose path was correct when written -- rewriting history to fix a name, which is the trade this tree already refused for out-of-directory decision records. The remaining five are ADR-107:37 and ADR-108:5/12/24/29, owner PURPOSE statements whose fix is `authority_basis: owner_intent`; they are routed to stubs/road-to-owner-authority-decisions.md and are not agent-editable. Zero headroom still holds for the four load-bearing positions.",
|
|
57
|
+
"falsifier": "./scripts-run src/scripts/lint_provenance_vocabulary"
|
|
58
58
|
},
|
|
59
59
|
"lint_settings_classes:derivable-surface": {
|
|
60
60
|
"count": 83,
|
|
@@ -62,5 +62,15 @@
|
|
|
62
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.",
|
|
63
63
|
"falsifier": "./scripts-run src/scripts/lint_settings_classes"
|
|
64
64
|
}
|
|
65
|
+
},
|
|
66
|
+
"retired": {
|
|
67
|
+
"lint_roadmap_blockers:decidability": {
|
|
68
|
+
"count": 0,
|
|
69
|
+
"landed": "2026-08-22",
|
|
70
|
+
"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.\n\nWALKED 12 -> 1 on 2026-08-22 by resolving the two `road-to-evidence-based-adr-governance` blockers (`owner-autonomy-batch`, `authority-coupling-decision`), which were undecidable in exactly the way this ratchet names: each handed a Hard-Floor question to the reader without a landed outcome. Both now carry one. The single remaining violation is `road-to-drain-commands:395` (`merge-authority`), which PR #1517 resolves and archives \u2014 so the honest count for THIS tree is 1 and it is expected to reach 0 once that lands. It is not lowered to 0 here, because a baseline must describe the tree it ships with rather than the tree someone expects next. Lowered 1 -> 0 on 2026-08-22, and the prediction the previous entry made is what closed it: the single remaining violation was `road-to-drain-commands:395` (`merge-authority`), and PR #1517 archived that roadmap, taking the entry out of the scanned population. Nothing was rewritten to reach zero -- the record left the active tree with its blocker RESOLVED, which is the cleanest of the two ways this number goes down and the only one that adds no prose. Zero is now the ceiling: any new open blocker missing `Recommendation:`, `If you do nothing:` or an actionable `What to do:` reds this gate immediately, with no headroom at all.",
|
|
71
|
+
"falsifier": "./scripts-run src/scripts/lint_roadmap_blockers --quiet",
|
|
72
|
+
"retired_on": "2026-08-22",
|
|
73
|
+
"retired_because": "Walked to 0 and therefore removed from `gates`: an entry recording no debt is what gate_baseline.test.ts refuses, and a missing entry is not weaker \u2014 evaluateGate treats an absent baseline as ok only when the live count is 0, which is the same ceiling the note below asked for. Kept here because the note is dated history, not commentary; nothing reads this key at runtime."
|
|
74
|
+
}
|
|
65
75
|
}
|
|
66
76
|
}
|
|
@@ -2,8 +2,21 @@
|
|
|
2
2
|
# install). After the Phase-5 migration the entire `.augment/`, `.claude/`,
|
|
3
3
|
# `.cursor/` trees live under `~/.event4u/agent-config/`; consumers carry
|
|
4
4
|
# only `agents/overrides/`. Pre-Phase-5 entries kept as a defence-in-depth
|
|
5
|
-
# ignore list for legacy installs that have not migrated yet
|
|
6
|
-
#
|
|
5
|
+
# ignore list for legacy installs that have not migrated yet.
|
|
6
|
+
#
|
|
7
|
+
# CORRECTED 2026-08-21 (road-to-merge-hotspot-drawdown 2.2): this header used to
|
|
8
|
+
# end "— Phase 5 unstages them on first migrate run", and NOTHING DOES. Verified
|
|
9
|
+
# on the live tree: `cmd_migrate.ts` refreshes this block
|
|
10
|
+
# (`_update_gitignore`, :619-647) and performs no git index operation of any
|
|
11
|
+
# kind, and no executable path anywhere in the repo runs `git rm --cached` —
|
|
12
|
+
# `check_tracked_but_ignored.ts:80` and `check_no_local_settings_committed.ts:85`
|
|
13
|
+
# only PRINT the command, and `/sync-gitignore:fix` states in as many words that
|
|
14
|
+
# git-ops are user-owned and must not be run automatically. So an entry added
|
|
15
|
+
# here ignores a path for the future and leaves an already-committed file
|
|
16
|
+
# tracked; a consumer who has committed it must unstage it themselves. The false
|
|
17
|
+
# claim mattered because a consumer-rollout plan was built on it as precedent:
|
|
18
|
+
# an auto-unstage would be a new mechanism AND a deliberate reversal of the
|
|
19
|
+
# user-owned-git-ops design, not an extension of something already shipped.
|
|
7
20
|
.augment/skills/
|
|
8
21
|
.augment/commands/
|
|
9
22
|
.augment/guidelines/
|
|
@@ -49,6 +49,11 @@
|
|
|
49
49
|
"tool_calls": 10,
|
|
50
50
|
"observe_only": true,
|
|
51
51
|
"p50_ci": null,
|
|
52
|
+
"observe_only_decision": "b-per-turn-composite-bar option (b) — council 2026-08-20, 2/2 quorum, both seats convergent: \"mark (pre + post) x 10 + ups + stop observe-only for one release, then derive an absolute ceiling and a pathology net from that distribution.\" Option (a), adopting the source draft's p50 <= 1.5 s, was refused because no run in this tree produced that number and an invented bar on a summed metric is the flappiest gate available. Option (c), declining the composite, was refused because it leaves D-1 permanently unmeasurable, which is the defect itself.",
|
|
53
|
+
"observe_only_since_version": "14.6.0",
|
|
54
|
+
"arming_procedure": "1. Collect the printed composite from every CI gate run across one release cycle — the bench prints it on every run, gated or not. 2. Set p50_ci to an ABSOLUTE cap derived from that distribution, never a creep window: shared CI runners flap, which is why regression_gate.max_regression_pct is already 200. 3. Keep the pathology net separate from the cap, so one spike is caught as a pathology rather than by lowering the cap. 4. Set observe_only to false — no code change is needed to arm it.",
|
|
55
|
+
"arming_precondition": "At least 10 CI gate readings of the composite, from at least 2 distinct runner sessions. Below that the estimator cannot separate code cost from runner load: the transport-isolation cell added for b-payload-read-parse-dominates read 44-157% at n=12 and 69-74% at n=50 on ONE machine, and a bar set from single-digit readings would carry that same instability.",
|
|
56
|
+
"_arming_precondition_honesty": "The 10-readings / 2-sessions floor is a STATED MINIMUM, not a derived one — it is the point below which the n=12 instability above was directly observed on a sibling metric in this same roadmap. Revisit-if: a release cycle produces the readings and their spread demands a wider floor, or the composite turns out to be dominated by one slot, in which case that slot's own cap is the better gate and this row stays observe-only permanently with that recorded.",
|
|
52
57
|
"_observe_only_note": "While observe_only is true the bench PRINTS the composite on every run, including the CI gate run, and never fails on it. Setting p50_ci to a number and observe_only to false is the whole of step 4.2 — no code change is needed to arm it. Do not arm it from a single run: the composite sums four slot measurements and multiplies two of them by tool_calls, so it accumulates runner variance faster than any single slot (risk 5), which is why the same absolute-cap-plus-pathology-net posture the slots use applies here too."
|
|
53
58
|
},
|
|
54
59
|
"regression_gate": {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "GENERATED by src/scripts/check_publish_surface.ts --write. Do not hand-edit. It records the resolved publish surface so a change to what ships is a diff a reviewer reads. `files[]` is an ALLOWLIST and it overrides .npmignore for anything under an included root — measured, after an attempt to strip compiled test artefacts via .npmignore had no effect because dist/ is an included root. That is why `negations` exists and why `npmignore_shadowed` is reported: a pattern the live payload contradicts is present and not doing what its author expected.",
|
|
3
|
+
"roots": [
|
|
4
|
+
"dist/agent-src/",
|
|
5
|
+
".augment-plugin/",
|
|
6
|
+
".claude-plugin/",
|
|
7
|
+
"src/config/",
|
|
8
|
+
"agents/templates/",
|
|
9
|
+
"dist/",
|
|
10
|
+
"docs/guidelines/",
|
|
11
|
+
"docs/contracts/persona-schema.md",
|
|
12
|
+
"docs/contracts/provider-lifecycle.md",
|
|
13
|
+
"docs/contracts/settings-classes.md",
|
|
14
|
+
"src/scripts/",
|
|
15
|
+
"src/server/schemas/",
|
|
16
|
+
"src/server/io/yamlIO.ts",
|
|
17
|
+
"src/shared/",
|
|
18
|
+
"src/install/",
|
|
19
|
+
"src/agent-src/scripts/",
|
|
20
|
+
"src/agent-src/templates/scripts/",
|
|
21
|
+
"src/templates/",
|
|
22
|
+
"tests/fixtures/hooks/",
|
|
23
|
+
"AGENTS.md",
|
|
24
|
+
"CHANGELOG.md",
|
|
25
|
+
"CONTRIBUTING.md",
|
|
26
|
+
"LICENSE",
|
|
27
|
+
"MIGRATION.md",
|
|
28
|
+
"README.md",
|
|
29
|
+
"llms.txt"
|
|
30
|
+
],
|
|
31
|
+
"negations": [
|
|
32
|
+
"!dist/**/*.test.js",
|
|
33
|
+
"!dist/**/*.test.js.map",
|
|
34
|
+
"!dist/**/*.spec.js",
|
|
35
|
+
"!dist/**/*.spec.js.map"
|
|
36
|
+
],
|
|
37
|
+
"npmignore": [
|
|
38
|
+
".idea/",
|
|
39
|
+
".vscode/",
|
|
40
|
+
".phpunit.cache/",
|
|
41
|
+
"**/__pycache__/",
|
|
42
|
+
"**/*.pyc",
|
|
43
|
+
"*.swp",
|
|
44
|
+
"*.swo",
|
|
45
|
+
"*~",
|
|
46
|
+
"tmp/",
|
|
47
|
+
"agents/",
|
|
48
|
+
".agent-settings",
|
|
49
|
+
".DS_Store",
|
|
50
|
+
"Thumbs.db",
|
|
51
|
+
".git/",
|
|
52
|
+
".gitignore",
|
|
53
|
+
"composer.lock",
|
|
54
|
+
"vendor/"
|
|
55
|
+
],
|
|
56
|
+
"npmignore_shadowed": [
|
|
57
|
+
"agents/"
|
|
58
|
+
],
|
|
59
|
+
"shadowed_measured_in": "built tree (payload carries dist/cli/**)"
|
|
60
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Growth-only ratchet over the LIVE population of the requirement-to-acceptance-to-evidence inventory (src/scripts/check_requirements_trace.ts). MEASURED AT REGISTRATION, NEVER A TARGET. What each number counts: `unlinked` is a declared trace row with no evidence ref at all; `unresolved` is an evidence ref that does not resolve at the CURRENT head; `dangling_ac` is an `[AC:<id>]` annotation on a verify line with no matching acceptance_id in the same file; `malformed` is an id that is not the reused claim-ledger kebab slug. All four may only walk DOWN. Population is `live` — the active corpus only. The fixture population (synthetic inputs that test the reader) and any frozen cohort (which may include archived files) are deliberately NOT in scope here: a maintainer-authored fixture is evidence about the reader rather than about uptake, and a count over archived roadmaps cannot fall, which would make the ratchet meaningless. Note the interaction the inventory's own header states: a completed roadmap can move from resolved to unresolved with no roadmap edit, because refs are evaluated at head. That is the intended reading and it means this ratchet can red on a deletion elsewhere in the tree — which is the point, not a defect.",
|
|
3
|
+
"schema_version": 1,
|
|
4
|
+
"owner": "maintainer",
|
|
5
|
+
"review_by": "2027-08-22",
|
|
6
|
+
"registered_at": "2026-08-22",
|
|
7
|
+
"population": "live",
|
|
8
|
+
"baseline": {
|
|
9
|
+
"unlinked": 0,
|
|
10
|
+
"unresolved": 0,
|
|
11
|
+
"dangling_ac": 0,
|
|
12
|
+
"malformed": 0
|
|
13
|
+
},
|
|
14
|
+
"measured_at_registration": {
|
|
15
|
+
"corpus": 25,
|
|
16
|
+
"files_declaring": 1,
|
|
17
|
+
"rows": 3,
|
|
18
|
+
"linked": 3,
|
|
19
|
+
"refs": 7,
|
|
20
|
+
"note": "One file declares, and it is this mechanism's own roadmap — dogfooded per its step 0.3 before any other roadmap was asked to carry the fields. Adoption is therefore 1 of 25 and is NOT evidence of uptake: a mechanism's author using it is the floor, not a signal. The three roadmaps step 2.1 originally named for dogfooding were all archived by sibling pull requests in the same drain run before any window opened, which is why the population model above exists at all."
|
|
21
|
+
},
|
|
22
|
+
"baseline_history": [
|
|
23
|
+
{
|
|
24
|
+
"at": "2026-08-22",
|
|
25
|
+
"unlinked": 0,
|
|
26
|
+
"unresolved": 0,
|
|
27
|
+
"dangling_ac": 0,
|
|
28
|
+
"malformed": 0,
|
|
29
|
+
"why": "Registered at the measured live reading on the commit that shipped the inventory. All four are zero because exactly one file declares and its refs all resolve — which is a fact about a corpus of one declarer, not a clean bill over 25 roadmaps. Shrink-only from here: a new trace row with no evidence ref, a ref that stops resolving, a dangling [AC:] annotation or a non-slug id each red this immediately, with no headroom. The honest limit is that a ratchet at zero over a single declarer cannot distinguish compliance from non-adoption, which is exactly the distinction the roadmap's own falsifier set is written to keep separate."
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
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": "
|
|
3
|
+
"measured_at_commit": "05eeb5ca3",
|
|
4
4
|
"tokens_exact": true,
|
|
5
5
|
"ceilings": {
|
|
6
6
|
"active-remediation": 1601,
|
|
7
7
|
"analysis-skill-routing": 76,
|
|
8
8
|
"architecture": 955,
|
|
9
|
-
"artifact-drafting-protocol":
|
|
9
|
+
"artifact-drafting-protocol": 680,
|
|
10
10
|
"augment-edit-discipline": 137,
|
|
11
11
|
"autonomous-execution": 1633,
|
|
12
12
|
"brand-consistency": 133,
|
|
@@ -37,14 +37,14 @@
|
|
|
37
37
|
"provider-lifecycle-discipline": 472,
|
|
38
38
|
"reviewer-awareness": 131,
|
|
39
39
|
"roadmap-ci-steps-policy": 564,
|
|
40
|
-
"roadmap-progress-sync":
|
|
40
|
+
"roadmap-progress-sync": 2479,
|
|
41
41
|
"rule-type-governance": 87,
|
|
42
42
|
"skill-improvement-trigger": 81,
|
|
43
43
|
"skill-quality": 78,
|
|
44
44
|
"slash-command-routing-policy": 77,
|
|
45
45
|
"symfony-routing": 129,
|
|
46
46
|
"ui-audit-gate": 1280,
|
|
47
|
-
"untrusted-input-defense":
|
|
47
|
+
"untrusted-input-defense": 934,
|
|
48
48
|
"upstream-proposal": 76
|
|
49
49
|
},
|
|
50
50
|
"history": [
|
|
@@ -68,6 +68,27 @@
|
|
|
68
68
|
"from": 2117,
|
|
69
69
|
"to": 2185,
|
|
70
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
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"date": "2026-08-20",
|
|
74
|
+
"rule": "artifact-drafting-protocol",
|
|
75
|
+
"from": 611,
|
|
76
|
+
"to": 680,
|
|
77
|
+
"reason": "Three lines plus a pointer extension, +69 tokens, for a new contract field that governs what a run may author without asking: an artifact discovered AFTER an accepted execution contract halts by default, and `late_artifacts: auto-research` is the only declared value that continues, capped at three per run. The five-step auto-research procedure, the reasoning for the default and its reversibility argument all went to the pointer target (artifact-drafting-protocol-mechanics gains a Late artifacts section) rather than here, which is what held the growth to a bullet. The residue is obligation surface and cannot live in the guideline: this rule's own batch-mode bullet already tells the reader that a mid-run artifact triggers the full interactive protocol, so a reader who never opens the guideline would conclude that a declared value cannot change it. Named default rather than described behaviour, because the default direction is the decision -- AI council 2/2 (anthropic + openai) chose `halt` over `auto-research` on the autonomy-defaults-sheet fork of road-to-user-out-of-the-loop, record agents/evidence/council/drain-blocker-dispositions-a.md."
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"date": "2026-08-20",
|
|
81
|
+
"rule": "roadmap-progress-sync",
|
|
82
|
+
"from": 2185,
|
|
83
|
+
"to": 2479,
|
|
84
|
+
"reason": "One subsection, +294 tokens, for the `deferred_policy` contract field: a declared policy removes the synchronous options ROUND at closure without moving any row of the preservation test's routing table. It is obligation surface and it cannot live anywhere else. Not in the pointer target -- roadmap-progress-mechanics.md sits at 16,039 chars against check_depth_budget's 16,000-char ceiling, and that gate is a count ratchet whose next failure is a FIFTH over-ceiling file, so prose pushed there is the one move that turns a warn into a red. Not in the contract context either: Iron Law 3 is the gate being amended, and a reader who learns from roadmap-execution-contract that a field exists but never reads this rule would not know that `cancel-with-memo` still stops for the user. The three-row table is the delta in full; the reasoning, both alternatives and the reversibility argument went to the contract context's own decision comment. Condensed once before this raise (2509 -> 2411) by cutting a paragraph that restated the preservation test. AI council 2/2 (anthropic + openai) on the autonomy-defaults-sheet fork of road-to-user-out-of-the-loop, record agents/evidence/council/drain-blocker-dispositions-a.md. RE-BASED on merge with origin/main, 2026-08-20: this entry was authored against a base of 2117 and raised to 2411. main had independently raised the same ceiling 2117 -> 2185 for its own reason, so the merged tree measures 2479 and the honest arithmetic for THIS raise is 2185 -> 2479 -- the same +294 for the same subsection, re-based rather than re-argued. Recorded rather than silently regenerated because the gate's own failure text names a history-versus-ceilings disagreement as the signature of a hand-edited count, and a merge that quietly adopted one side's numbers would produce exactly that signature with nothing saying why."
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"date": "2026-08-22",
|
|
88
|
+
"rule": "untrusted-input-defense",
|
|
89
|
+
"from": 845,
|
|
90
|
+
"to": 934,
|
|
91
|
+
"reason": "Six lines, +89 tokens, correcting a claim the section itself had made false. It said no content-scanning backstop existed and that a future one would be the first candidate to change enforced_by; injection_scan_hook.ts now exists, so the first half is false, and the second was always wrong because a warn-only hook cannot refuse and therefore cannot enforce. Every sentence left carries one of those claims. The measurement half -- slot, 15 channels, 99.00 percent recall, 0.85 percent false positives, the manifest keys -- did move to the pointer target, which is what cut the overshoot from +194 to +89; what stays is the rule's own enforcement disclosure, and a stub ceiling that forbids correcting a false statement about the rule's own enforcement is the wrong constraint applied to the right file."
|
|
71
92
|
}
|
|
72
93
|
]
|
|
73
94
|
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where each host's GLOBAL rule layer lives on disk, and whether it carries a
|
|
3
|
+
* given set of rules.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this module exists
|
|
6
|
+
*
|
|
7
|
+
* ADR-236 partitions rules between the global and the project layer: an artefact
|
|
8
|
+
* scoped to this package alone stays project-local, everything else is delivered
|
|
9
|
+
* globally and withheld from the project tree. `partitionEligibility.ts` decides
|
|
10
|
+
* WHETHER the partition is on, by verifying the claude host layer against
|
|
11
|
+
* `installed.lock`. Its own docstring then draws the only conclusion that
|
|
12
|
+
* evidence supports — it "says nothing about `~/.cursor`", so withholding a
|
|
13
|
+
* cursor artefact on the strength of a claude fingerprint "would deliver it
|
|
14
|
+
* nowhere".
|
|
15
|
+
*
|
|
16
|
+
* That reasoning is right and its scope was too narrow for rules. The rule
|
|
17
|
+
* filter sits in `condense.ts::_scoped_rule_basenames()`, which is per-RUN and
|
|
18
|
+
* not per-DIRECTORY, so it already withholds from `.clinerules` on a claude
|
|
19
|
+
* fingerprint — the exact extrapolation the docstring refuses. Measured
|
|
20
|
+
* 2026-08-22 in a freshly generated worktree with the partition active:
|
|
21
|
+
*
|
|
22
|
+
* ```
|
|
23
|
+
* .claude/rules 13 files, 13 package-only, 0 global-only
|
|
24
|
+
* .clinerules 14 files, 13 package-only, 0 global-only
|
|
25
|
+
* .cursor/rules 126 files, 26 package-only, 100 global-only
|
|
26
|
+
* .windsurf/rules 113 files, 13 package-only, 100 global-only
|
|
27
|
+
* .augment/rules 118 files, 15 package-only, 103 global-only
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* Two hosts partition on borrowed evidence; three do not partition at all. This
|
|
31
|
+
* module supplies the evidence per host so neither case has to be guessed:
|
|
32
|
+
* {@link globalRuleLayerNames} reads the host's own global directory, and
|
|
33
|
+
* {@link hostLayerCarries} answers the one question a withhold depends on.
|
|
34
|
+
*
|
|
35
|
+
* ## Why the paths are written out rather than imported
|
|
36
|
+
*
|
|
37
|
+
* `USER_SCOPE_PATHS` in `src/scripts/install.ts` is the authority for a host's
|
|
38
|
+
* global scope ROOT, and it is the right authority. It is not imported here for
|
|
39
|
+
* two reasons: `install.ts` is a multi-thousand-line CLI entry point that pulls
|
|
40
|
+
* in the whole installer graph, and a scope root is not a rules path — cline's
|
|
41
|
+
* root already IS its rules directory (`~/Documents/Cline/Rules/`) while every
|
|
42
|
+
* other host appends a subdirectory. So the mapping is stated once here and
|
|
43
|
+
* PINNED against `USER_SCOPE_PATHS` by `tests/install/global_rule_layers.test.ts`,
|
|
44
|
+
* which fails if a root moves. A pinned restatement drifts loudly; an import
|
|
45
|
+
* would have cost the bundle.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
import * as fs from 'node:fs';
|
|
49
|
+
import * as os from 'node:os';
|
|
50
|
+
import * as path from 'node:path';
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Tool id → its global rule directory, relative to the user's home.
|
|
54
|
+
*
|
|
55
|
+
* Keys are the ids `USER_SCOPE_PATHS` and `TOOL_SIGNALS` use, so a row here is
|
|
56
|
+
* traceable to the installer's own scope table rather than invented.
|
|
57
|
+
*
|
|
58
|
+
* The five hosts below are the ones that receive a projected rule TREE. A host
|
|
59
|
+
* whose rules arrive as a single concatenated file (`.windsurfrules`,
|
|
60
|
+
* `GEMINI.md`, copilot instructions) has no directory to compare and is
|
|
61
|
+
* deliberately absent: {@link globalRuleLayerPath} returns null for it, which
|
|
62
|
+
* reads as "no evidence" and keeps the full projection.
|
|
63
|
+
*/
|
|
64
|
+
export const GLOBAL_RULE_DIRS: Readonly<Record<string, string>> = {
|
|
65
|
+
'claude-code': '.claude/rules',
|
|
66
|
+
cursor: '.cursor/rules',
|
|
67
|
+
augment: '.augment/rules',
|
|
68
|
+
// Windsurf's scope root is `~/.codeium/windsurf/`, not `~/.windsurf/`.
|
|
69
|
+
windsurf: '.codeium/windsurf/rules',
|
|
70
|
+
// Cline's scope root IS the rules directory — no `rules/` suffix.
|
|
71
|
+
cline: 'Documents/Cline/Rules',
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Project rule directory → the tool id whose global layer covers it.
|
|
76
|
+
*
|
|
77
|
+
* Five entries, because five directories receive a projected rule TREE. Two are
|
|
78
|
+
* symlink trees (`condense.ts::TOOL_DIRS`); three are written by their own
|
|
79
|
+
* emitters — `generate_cursor_mdc_rules`, `generate_windsurf_modern_rules`, and
|
|
80
|
+
* `project_to_augment`. The `.cursor/rules` row covers both the `.mdc` files and
|
|
81
|
+
* the legacy `.md` symlinks that share that directory.
|
|
82
|
+
*
|
|
83
|
+
* It lives here rather than in either consumer so the gate that reports a
|
|
84
|
+
* duplicate and the emitters that must stop creating one read the same list. A
|
|
85
|
+
* sixth host directory added to one and not the other is exactly how a gate goes
|
|
86
|
+
* green over a tree nobody checks.
|
|
87
|
+
*/
|
|
88
|
+
export const PROJECT_RULE_DIRS: Readonly<Record<string, string>> = {
|
|
89
|
+
'.claude/rules': 'claude-code',
|
|
90
|
+
'.cursor/rules': 'cursor',
|
|
91
|
+
'.clinerules': 'cline',
|
|
92
|
+
'.windsurf/rules': 'windsurf',
|
|
93
|
+
'.augment/rules': 'augment',
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/** The tool id whose global layer covers a project rule directory, or null. */
|
|
97
|
+
export function toolIdForProjectRuleDir(dir: string): string | null {
|
|
98
|
+
return PROJECT_RULE_DIRS[dir] ?? null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Every tool id that has a global rule directory this module can read. */
|
|
102
|
+
export function hostsWithGlobalRuleLayer(): readonly string[] {
|
|
103
|
+
return Object.keys(GLOBAL_RULE_DIRS).sort();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Absolute path to a host's global rule directory, or null when the host has no
|
|
108
|
+
* directory-shaped global layer.
|
|
109
|
+
*
|
|
110
|
+
* Null is a real answer and not an error: it means this module cannot supply
|
|
111
|
+
* evidence for that host, and every caller must read that as "do not withhold".
|
|
112
|
+
*/
|
|
113
|
+
export function globalRuleLayerPath(
|
|
114
|
+
toolId: string,
|
|
115
|
+
home: string = process.env['HOME'] ?? os.homedir(),
|
|
116
|
+
): string | null {
|
|
117
|
+
const rel = GLOBAL_RULE_DIRS[toolId];
|
|
118
|
+
return rel === undefined ? null : path.join(home, rel);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The rule basenames a host's global layer actually holds, normalised to `.md`.
|
|
123
|
+
*
|
|
124
|
+
* Returns null when the directory does not exist or cannot be read — the
|
|
125
|
+
* absent-tolerant contract Phase 1.1 requires. Normalisation matters because
|
|
126
|
+
* Cursor's tree is `.mdc`: comparing raw filenames would report every rule as
|
|
127
|
+
* missing there and turn the partition off for the one host whose duplication is
|
|
128
|
+
* largest.
|
|
129
|
+
*/
|
|
130
|
+
export function globalRuleLayerNames(
|
|
131
|
+
toolId: string,
|
|
132
|
+
home: string = process.env['HOME'] ?? os.homedir(),
|
|
133
|
+
): readonly string[] | null {
|
|
134
|
+
const dir = globalRuleLayerPath(toolId, home);
|
|
135
|
+
if (dir === null) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
let entries: string[];
|
|
139
|
+
try {
|
|
140
|
+
entries = fs.readdirSync(dir);
|
|
141
|
+
} catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
return entries
|
|
145
|
+
.filter((f) => f.endsWith('.md') || f.endsWith('.mdc'))
|
|
146
|
+
.map((f) => (f.endsWith('.mdc') ? `${f.slice(0, -4)}.md` : f))
|
|
147
|
+
.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** What {@link hostLayerCarries} concluded, and on what evidence. */
|
|
151
|
+
export interface CarriesVerdict {
|
|
152
|
+
/** True only when every requested name is present in the host's layer. */
|
|
153
|
+
readonly carries: boolean;
|
|
154
|
+
/** Absolute path read, or null when the host has no directory layer. */
|
|
155
|
+
readonly layerPath: string | null;
|
|
156
|
+
/** Names asked for that the layer does not hold. Empty when `carries`. */
|
|
157
|
+
readonly missing: readonly string[];
|
|
158
|
+
/** Why the verdict is what it is — for the caller's own reporting. */
|
|
159
|
+
readonly reason: 'carries' | 'no-layer-for-host' | 'layer-absent' | 'missing-names';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Does this host's global layer carry every rule that would be withheld from its
|
|
164
|
+
* project directory?
|
|
165
|
+
*
|
|
166
|
+
* This is the property `personaPartition`'s docstring states as the precondition
|
|
167
|
+
* for any withhold — "withholding is only safe once the surviving layer is known
|
|
168
|
+
* to carry what is withheld" — asked per host instead of inferred from claude.
|
|
169
|
+
*
|
|
170
|
+
* Fails safe in all three negative directions: a host with no directory layer, a
|
|
171
|
+
* directory that is absent, and a directory missing even one name all return
|
|
172
|
+
* `carries: false`, so the caller keeps the full projection. An empty
|
|
173
|
+
* `ruleNames` is vacuously carried, which is correct — withholding nothing needs
|
|
174
|
+
* no evidence.
|
|
175
|
+
*/
|
|
176
|
+
export function hostLayerCarries(
|
|
177
|
+
toolId: string,
|
|
178
|
+
ruleNames: readonly string[],
|
|
179
|
+
home: string = process.env['HOME'] ?? os.homedir(),
|
|
180
|
+
): CarriesVerdict {
|
|
181
|
+
const layerPath = globalRuleLayerPath(toolId, home);
|
|
182
|
+
if (layerPath === null) {
|
|
183
|
+
return { carries: false, layerPath: null, missing: [...ruleNames], reason: 'no-layer-for-host' };
|
|
184
|
+
}
|
|
185
|
+
const have = globalRuleLayerNames(toolId, home);
|
|
186
|
+
if (have === null) {
|
|
187
|
+
return { carries: false, layerPath, missing: [...ruleNames], reason: 'layer-absent' };
|
|
188
|
+
}
|
|
189
|
+
const present = new Set(have);
|
|
190
|
+
const missing = ruleNames.filter((n) => !present.has(n));
|
|
191
|
+
return {
|
|
192
|
+
carries: missing.length === 0,
|
|
193
|
+
layerPath,
|
|
194
|
+
missing,
|
|
195
|
+
reason: missing.length === 0 ? 'carries' : 'missing-names',
|
|
196
|
+
};
|
|
197
|
+
}
|