@event4u/agent-config 16.2.0 → 16.3.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/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +89 -237
- package/README.md +14 -12
- package/dist/agent-src/commands/analyze/inbox.md +1 -1
- package/dist/agent-src/commands/analyze/roadmap-repos.md +1 -1
- package/dist/agent-src/commands/bug/fix.md +17 -2
- package/dist/agent-src/commands/feature/plan.md +1 -1
- package/dist/agent-src/commands/feature/roadmap.md +1 -1
- package/dist/agent-src/commands/fix/ci.md +1 -1
- package/dist/agent-src/commands/implement-ticket.md +1 -1
- package/dist/agent-src/commands/jira-ticket.md +1 -1
- package/dist/agent-src/commands/roadmap/create.md +1 -1
- package/dist/agent-src/commands/roadmap/materialize.md +1 -1
- package/dist/agent-src/commands/roadmap/process-full.md +4 -4
- package/dist/agent-src/commands/roadmap/resolve-blockers.md +361 -0
- package/dist/agent-src/commands/roadmap/triage-parked.md +325 -0
- package/dist/agent-src/commands/roadmap.md +18 -4
- package/dist/agent-src/commands/tests/create.md +17 -3
- package/dist/agent-src/commands/tests/execute.md +22 -5
- package/dist/agent-src/contexts/authority/decision-revisit-gate-enforcement.md +43 -0
- package/dist/agent-src/contexts/communication/rules-auto/design-fidelity-enforcement.md +24 -0
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +6 -6
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +32 -2
- package/dist/agent-src/contexts/execution/session-canary-enforcement-history.md +67 -0
- package/dist/agent-src/contexts/execution/toolchain-resolver.md +101 -7
- package/dist/agent-src/rules/active-remediation.md +1 -1
- package/dist/agent-src/rules/autonomous-execution.md +1 -0
- package/dist/agent-src/rules/broken-access-control.md +1 -0
- package/dist/agent-src/rules/command-suggestion-policy.md +1 -0
- package/dist/agent-src/rules/context-hygiene.md +1 -1
- package/dist/agent-src/rules/decision-revisit-gate.md +16 -33
- package/dist/agent-src/rules/design-fidelity.md +13 -16
- package/dist/agent-src/rules/doc-screenshot-hygiene.md +1 -0
- package/dist/agent-src/rules/domain-safety-disclaimer.md +1 -0
- package/dist/agent-src/rules/domain-safety-pii.md +1 -0
- package/dist/agent-src/rules/domain-safety-retention.md +1 -0
- package/dist/agent-src/rules/engineering-safety-floor.md +1 -0
- package/dist/agent-src/rules/evaluator-independence.md +3 -2
- package/dist/agent-src/rules/fast-path-marker-visibility.md +1 -0
- package/dist/agent-src/rules/finance-safety-floor.md +1 -0
- package/dist/agent-src/rules/git-history-discipline.md +1 -0
- package/dist/agent-src/rules/image-likeness-and-rights.md +1 -0
- package/dist/agent-src/rules/legal-safety-floor.md +3 -2
- package/dist/agent-src/rules/lethal-trifecta-guard.md +1 -0
- package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
- package/dist/agent-src/rules/media-governance-routing.md +14 -17
- package/dist/agent-src/rules/persona-governance.md +2 -2
- package/dist/agent-src/rules/provider-lifecycle-discipline.md +1 -1
- package/dist/agent-src/rules/question-not-instruction.md +1 -0
- package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
- package/dist/agent-src/rules/runtime-safety.md +1 -0
- package/dist/agent-src/rules/secret-vcs-guard.md +1 -0
- package/dist/agent-src/rules/security-sensitive-stop.md +3 -2
- package/dist/agent-src/rules/session-canary.md +8 -62
- package/dist/agent-src/rules/settings-ask-protocol.md +1 -0
- package/dist/agent-src/rules/source-confidentiality.md +1 -0
- package/dist/agent-src/rules/strategy-safety-floor.md +1 -0
- package/dist/agent-src/rules/tool-safety.md +1 -0
- package/dist/agent-src/rules/untrusted-input-defense.md +1 -0
- package/dist/agent-src/rules/user-interrupt-priority.md +1 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
- package/dist/agent-src/scripts/deferral_carry.ts +339 -0
- package/dist/agent-src/scripts/update_roadmap_progress.ts +4 -1
- package/dist/agent-src/skills/api-design/data/manifest.json +2 -2
- package/dist/agent-src/skills/api-testing/SKILL.md +17 -0
- package/dist/agent-src/skills/code-intelligence/SKILL.md +14 -2
- package/dist/agent-src/skills/design-system-capture/references/import-procedure.md +1 -1
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +2 -2
- package/dist/agent-src/skills/fe-design/SKILL.md +1 -1
- package/dist/agent-src/skills/iconography/SKILL.md +1 -1
- package/dist/agent-src/skills/js-library-packaging/SKILL.md +2 -2
- package/dist/agent-src/skills/monorepo-workspace/SKILL.md +1 -1
- package/dist/agent-src/skills/quality-tools/SKILL.md +11 -1
- package/dist/agent-src/skills/quality-tools/references/go-tools.md +76 -0
- package/dist/agent-src/skills/quality-tools/references/python-tools.md +104 -0
- package/dist/agent-src/skills/review-routing/SKILL.md +2 -2
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
- package/dist/agent-src/skills/storybook-workshop/SKILL.md +3 -3
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +1 -1
- package/dist/agent-src/skills/test-case-discovery/SKILL.md +7 -0
- package/dist/agent-src/skills/test-driven-development/SKILL.md +17 -0
- package/dist/agent-src/skills/test-performance/SKILL.md +19 -1
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +2 -2
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +48 -6
- package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
- package/dist/cli/registry.js +1 -1
- package/dist/cli-delegate/archive_completed_roadmaps.js +92 -12
- package/dist/cli-delegate/check_update_banner.js +12 -1
- package/dist/cli-delegate/chunk-523HCFYO.js +90 -0
- package/dist/cli-delegate/{chunk-M2DPGFZ4.js → chunk-6V6TA25H.js} +22 -152
- package/dist/cli-delegate/chunk-7IQE3D5S.js +191 -0
- package/dist/cli-delegate/{chunk-6KFEC3G7.js → chunk-CQWX336L.js} +1 -1
- package/dist/cli-delegate/chunk-DEIV7XP7.js +108 -0
- package/dist/cli-delegate/chunk-EEHDXSYD.js +2040 -0
- package/dist/cli-delegate/{chunk-P6ZHP5W2.js → chunk-GKE7ZVFA.js} +4 -4
- package/dist/cli-delegate/chunk-JQOM7TTM.js +1201 -0
- package/dist/cli-delegate/{chunk-IGD6ERX6.js → chunk-JQVYKIZA.js} +10 -0
- package/dist/cli-delegate/{chunk-FC75PGJE.js → chunk-JY2I2DXV.js} +12 -1
- package/dist/cli-delegate/chunk-KB5FZ655.js +208 -0
- package/dist/cli-delegate/{chunk-7RQP4UDH.js → chunk-KR6EM23A.js} +235 -1455
- package/dist/cli-delegate/{chunk-LTYVIY76.js → chunk-LIYTLH6O.js} +1 -1
- package/dist/cli-delegate/{chunk-YJ72UIAK.js → chunk-NQ7WT43P.js} +1186 -244
- package/dist/cli-delegate/chunk-NXEILF2K.js +2160 -0
- package/dist/cli-delegate/{chunk-5R7HOLAF.js → chunk-NYX3L2PZ.js} +1 -1
- package/dist/cli-delegate/{chunk-4E2W63DA.js → chunk-OKJUIAPS.js} +62 -1
- package/dist/cli-delegate/{chunk-QAJS5YWY.js → chunk-PHD7OOLE.js} +2 -1
- package/dist/cli-delegate/{chunk-RGNIWTEA.js → chunk-QLDHIPWR.js} +1 -1
- package/dist/cli-delegate/{chunk-7XGBX6ZH.js → chunk-RQNNW7IF.js} +1314 -560
- package/dist/cli-delegate/{chunk-4XFP2Y3E.js → chunk-S4UMZNES.js} +1 -1
- package/dist/cli-delegate/{chunk-6KM6ORRY.js → chunk-S4YWNRKO.js} +24 -8
- package/dist/cli-delegate/{chunk-FP36SWJD.js → chunk-TRI6DQZ6.js} +2781 -340
- package/dist/cli-delegate/chunk-ULQRUR23.js +208 -0
- package/dist/cli-delegate/{chunk-NUHLI2RX.js → chunk-YCU3TIXH.js} +2 -0
- package/dist/cli-delegate/chunk-ZBZV2G5R.js +210 -0
- package/dist/cli-delegate/{chunk-WGXTVKCQ.js → chunk-ZUZKZYCN.js} +5 -3
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +1 -1
- package/dist/cli-delegate/cmd_conformance.js +1167 -234
- package/dist/cli-delegate/cmd_doctor.js +13 -6
- package/dist/cli-delegate/cmd_doctor_neighbours.js +23 -0
- package/dist/cli-delegate/cmd_explain.js +5 -5
- package/dist/cli-delegate/cmd_export.js +11 -8
- package/dist/cli-delegate/cmd_fleet.js +3 -3
- package/dist/cli-delegate/cmd_handoff.js +10 -7
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_memory_get.js +3 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +3 -3
- package/dist/cli-delegate/cmd_preflight.js +6 -6
- package/dist/cli-delegate/cmd_prune.js +11 -8
- package/dist/cli-delegate/cmd_refresh.js +2 -1
- package/dist/cli-delegate/cmd_route_audit.js +8 -6
- package/dist/cli-delegate/cmd_route_explain.js +4 -4
- package/dist/cli-delegate/cmd_session_recycle.js +4 -3
- package/dist/cli-delegate/cmd_settings_get.js +1 -1
- package/dist/cli-delegate/cmd_settings_migrate.js +11 -8
- package/dist/cli-delegate/cmd_settings_set.js +4 -4
- package/dist/cli-delegate/cmd_sync.js +11 -8
- package/dist/cli-delegate/cmd_uninstall.js +11 -8
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +3 -2
- package/dist/cli-delegate/cmd_validate.js +11 -8
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/cli-delegate/deferral_carry.js +16 -0
- package/dist/cli-delegate/gate_execute.js +2 -2
- package/dist/cli-delegate/roadmap_gates.js +5 -5
- package/dist/cli-delegate/update_roadmap_progress.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +127 -75
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +7 -5
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +6 -4
- package/dist/hooks/dispatch.js +5357 -6656
- package/dist/hooks/dispatch.sha256 +1 -1
- package/dist/install/claudePathsPlan.js +47 -6
- package/dist/install/claudeRuleRewrite.js +7 -0
- package/dist/install/emit_host_rules_cli.js +39 -36
- package/dist/install/globalRuleScope.js +87 -0
- package/dist/install/host_rule_ownership.js +160 -0
- package/dist/install/install.mjs +2080 -1024
- package/dist/install/installThinLayer.js +261 -0
- package/dist/install/installedRuleLinks.js +176 -0
- package/dist/install/wizard-plan.js +24 -0
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +31965 -27154
- package/dist/scripts/_lib/agent_settings.js +11 -0
- package/dist/scripts/_lib/claude_settings_hooks.js +10 -8
- package/dist/scripts/_lib/installed_layer.js +378 -0
- package/dist/scripts/_lib/lean_projection_mode.js +218 -15
- package/dist/scripts/_lib/rule_consequence_class.js +45 -0
- package/dist/scripts/_lib/rule_injection.js +67 -10
- package/dist/scripts/_lib/rule_law_section.js +122 -0
- package/dist/scripts/_lib/thin_rules.js +458 -0
- package/dist/scripts/_lib/windsurf_render.js +60 -0
- package/dist/scripts/condense.js +14 -26
- package/dist/scripts/project_thin_rules.js +14 -294
- package/dist/server/schemas/settings.js +6 -0
- package/dist/shared/skillRanking.js +74 -13
- package/docs/contracts/settings-classes.md +7 -5
- package/package.json +1 -1
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
- package/src/agent-src/scripts/deferral_carry.ts +339 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +4 -1
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +48 -6
- package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
- package/src/config/agent-settings.template.yml +62 -22
- package/src/config/continuity-surface.json +7 -0
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +96 -3
- package/src/config/gate-violation-baselines.json +17 -9
- package/src/config/hook-bundle-budget.json +25 -0
- package/src/config/hook-token-budget.json +6 -1
- package/src/config/host-instruction-limits.json +33 -0
- package/src/config/routing-coverage-seed.json +24 -3
- package/src/config/rule-activation-census.json +7 -3
- package/src/config/rule-consequence-class.json +161 -0
- package/src/config/rule-law-ceilings.json +99 -0
- package/src/install/claudePathsPlan.ts +46 -4
- package/src/install/claudeRuleRewrite.ts +10 -1
- package/src/install/emit_host_rules_cli.ts +43 -34
- package/src/install/globalRuleScope.ts +109 -0
- package/src/install/host_rule_ownership.ts +170 -0
- package/src/install/installThinLayer.ts +312 -0
- package/src/install/installedRuleLinks.ts +220 -0
- package/src/install/wizard-plan.ts +24 -0
- package/src/scripts/_cli/cmd_doctor.ts +23 -71
- package/src/scripts/_cli/cmd_doctor_neighbours.ts +293 -0
- package/src/scripts/_dispatch.bash +16 -2
- package/src/scripts/_lib/agent_settings.ts +11 -0
- package/src/scripts/_lib/claude_settings_hooks.ts +10 -8
- package/src/scripts/_lib/estate_offsets.ts +241 -0
- package/src/scripts/_lib/exemption_shape.ts +143 -0
- package/src/scripts/_lib/fnv.ts +23 -0
- package/src/scripts/_lib/foreign_scan.ts +106 -0
- package/src/scripts/_lib/gitignore_block.ts +314 -0
- package/src/scripts/_lib/global_deploy_inventory.ts +32 -0
- package/src/scripts/_lib/graph_feeder_record.ts +214 -0
- package/src/scripts/_lib/hook_settings.ts +73 -0
- package/src/scripts/_lib/host_capability.ts +40 -19
- package/src/scripts/_lib/host_hook_merge.ts +165 -0
- package/src/scripts/_lib/installed_layer.ts +538 -0
- package/src/scripts/_lib/json_merge.ts +82 -0
- package/src/scripts/_lib/json_pointers.ts +93 -11
- package/src/scripts/_lib/lean_projection_mode.ts +275 -15
- package/src/scripts/_lib/machine_wake.ts +16 -3
- package/src/scripts/_lib/neighbour_census.ts +760 -0
- package/src/scripts/_lib/neighbour_scan.ts +122 -0
- package/src/scripts/_lib/neighbour_tool_use.ts +189 -0
- package/src/scripts/_lib/prompt_shape.ts +46 -0
- package/src/scripts/_lib/release_migration_gate.ts +19 -3
- package/src/scripts/_lib/reserved_name_sweep.ts +73 -0
- package/src/scripts/_lib/review_coverage.ts +134 -0
- package/src/scripts/_lib/rule_consequence_class.ts +71 -0
- package/src/scripts/_lib/rule_injection.ts +130 -10
- package/src/scripts/_lib/rule_law_section.ts +147 -0
- package/src/scripts/_lib/rule_layer_overlap.ts +58 -0
- package/src/scripts/_lib/skill_origin.ts +254 -0
- package/src/scripts/_lib/structured_ask.ts +64 -21
- package/src/scripts/_lib/thin_rules.ts +540 -0
- package/src/scripts/_lib/touched_file_quality.ts +434 -0
- package/src/scripts/_lib/turn_end_refusals.ts +77 -2
- package/src/scripts/_lib/windsurf_render.ts +63 -0
- package/src/scripts/ai_council/cli_call_budget.ts +13 -5
- package/src/scripts/ai_council/clients.ts +26 -57
- package/src/scripts/ai_council/config.ts +1 -1
- package/src/scripts/ai_council/vendor_defaults.ts +45 -0
- package/src/scripts/audit_skill_overlap.ts +261 -0
- package/src/scripts/before_complete_hook.ts +243 -12
- package/src/scripts/bench_touched_file_quality.ts +238 -0
- package/src/scripts/check_enforcement_matrix.ts +377 -20
- package/src/scripts/check_estate_count.ts +130 -153
- package/src/scripts/check_finding_dispositions.ts +116 -4
- package/src/scripts/check_hook_bundle_composition.ts +370 -0
- package/src/scripts/check_kernel_edit_ratified.ts +49 -11
- package/src/scripts/check_memory.ts +7 -1
- package/src/scripts/check_release_highlights.ts +10 -0
- package/src/scripts/check_release_pr_shape.ts +38 -15
- package/src/scripts/check_standing_rule_delivery.ts +33 -13
- package/src/scripts/check_verify_expectation_delta.ts +521 -0
- package/src/scripts/code_graph/detect.ts +127 -7
- package/src/scripts/code_graph/foreign.ts +194 -0
- package/src/scripts/code_graph/query.ts +217 -4
- package/src/scripts/code_graph/types.ts +26 -2
- package/src/scripts/code_graph/validate.ts +3 -1
- package/src/scripts/code_graph/verbs.ts +103 -0
- package/src/scripts/compile_hook_manifest.ts +33 -1
- package/src/scripts/condense.ts +14 -27
- package/src/scripts/estate_exemption_shape.ts +202 -0
- package/src/scripts/generate_host_cost_table.ts +1 -1
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +150 -9
- package/src/scripts/hooks/block_config_weakening.ts +67 -2
- package/src/scripts/hooks/block_plumbing_writes.ts +29 -1
- package/src/scripts/hooks/code_graph_context_hook.ts +252 -23
- package/src/scripts/hooks/concern_failure_policy.ts +280 -0
- package/src/scripts/hooks/dispatch_hook.ts +52 -52
- package/src/scripts/hooks/exit_codes.ts +2 -1
- package/src/scripts/hooks/host_lowering.json +1 -1
- package/src/scripts/hooks/host_lowering.ts +19 -1
- package/src/scripts/hooks/one_question_per_ask_hook.ts +41 -25
- package/src/scripts/hooks/payload_stub.ts +10 -1
- package/src/scripts/hooks/rule_inject_hook.ts +757 -63
- package/src/scripts/hooks/table_fingerprint.ts +21 -0
- package/src/scripts/hooks/telemetry_usage_hook.ts +64 -8
- package/src/scripts/hooks/turn_end_gate_hook.ts +84 -0
- package/src/scripts/hooks_status.ts +112 -0
- package/src/scripts/injection_scan_hook.ts +7 -0
- package/src/scripts/install.ts +72 -185
- package/src/scripts/installed_layer_report.ts +103 -0
- package/src/scripts/language_mirror_hook.ts +15 -6
- package/src/scripts/learning_sidecar.ts +119 -8
- package/src/scripts/lint_hook_manifest.ts +53 -0
- package/src/scripts/lint_major_migration_sections.ts +156 -26
- package/src/scripts/lint_pack_boundaries.ts +63 -5
- package/src/scripts/lint_rule_law_section.ts +517 -0
- package/src/scripts/lint_workflow_security.ts +161 -5
- package/src/scripts/mcp_server/consumer_tool_catalog.json +53 -0
- package/src/scripts/mcp_server/graph_tools.ts +72 -3
- package/src/scripts/measure_routing_signal.ts +54 -16
- package/src/scripts/measure_skill_ranker_baseline.ts +216 -8
- package/src/scripts/memory_learn_hook.ts +91 -6
- package/src/scripts/model_rule_injection.ts +52 -5
- package/src/scripts/project_thin_rules.ts +34 -327
- package/src/scripts/report_evidence_temperature.ts +657 -0
- package/src/scripts/report_installed_rule_links.ts +157 -0
- package/src/scripts/report_label_agreement.ts +233 -0
- package/src/scripts/report_obligation_mechanism.ts +230 -0
- package/src/scripts/report_single_token_triggers.ts +277 -0
- package/src/scripts/report_skill_ranker_confusion.ts +442 -0
- package/src/scripts/report_standing_payload_by_host.ts +188 -15
- package/src/scripts/report_verification_record_coverage.ts +306 -0
- package/src/scripts/rule_activation_census.ts +5 -2
- package/src/scripts/rule_trigger_eval.ts +9 -9
- package/src/scripts/schemas/review-findings.schema.json +3 -2
- package/src/scripts/schemas/rule.schema.json +5 -0
- package/src/scripts/self_review_gate.ts +62 -7
- package/src/scripts/skill_tools/score_skill_relevance.ts +290 -25
- package/src/scripts/sweep_skill_ranker_signals.ts +129 -0
- package/src/scripts/sync_gitignore.ts +29 -235
- package/src/scripts/trigger_eval_grandfather.json +0 -5
- package/src/server/schemas/settings.ts +10 -0
- package/src/shared/skillRanking.ts +117 -11
- package/dist/cli-delegate/chunk-JNMVLWLR.js +0 -4457
- package/dist/cli-delegate/chunk-YDPILCOL.js +0 -152
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Composition gate for the self-hosted hook bundle: ONE YAML reader, and a
|
|
4
|
+
* byte ceiling.
|
|
5
|
+
*
|
|
6
|
+
* WHY THIS EXISTS — measured, not hypothetical.
|
|
7
|
+
*
|
|
8
|
+
* Every concern is inlined into one `dist/hooks/dispatch.js` that every hook
|
|
9
|
+
* event loads, so a dependency added for ONE concern is paid by all of them on
|
|
10
|
+
* every dispatch. On 2026-10-01 the bundle carried two YAML parsers: `yaml`,
|
|
11
|
+
* pulled by six modules including the dispatcher itself, and `js-yaml`, pulled
|
|
12
|
+
* by one call site in `block_config_weakening.ts` — 95,846 bytes, 6.13 % of a
|
|
13
|
+
* 1,564,211-byte bundle. Beside it, `host_lowering.ts`'s own header said the
|
|
14
|
+
* `yaml` reader was "the ONLY remaining YAML reader on that path".
|
|
15
|
+
*
|
|
16
|
+
* Nothing in this tree could see either fact. The freshness gate compares
|
|
17
|
+
* mtimes, the content gate compares digests, and neither has an opinion about
|
|
18
|
+
* what is INSIDE the bundle. So the second parser reached a release and was
|
|
19
|
+
* found by an external rescore counting bytes — a review finding where it
|
|
20
|
+
* should have been a red check. This gate is that red check.
|
|
21
|
+
*
|
|
22
|
+
* WHAT IT CHECKS
|
|
23
|
+
*
|
|
24
|
+
* 1. At most `max_yaml_packages` YAML packages contribute bytes to the
|
|
25
|
+
* bundle. The package set is {@link YAML_PACKAGES} — a closed list, so a
|
|
26
|
+
* third parser arriving under a name nobody listed is NOT caught here, and
|
|
27
|
+
* that limit is stated rather than implied.
|
|
28
|
+
* 2. The bundle's byte count is at or under `max_bytes`.
|
|
29
|
+
*
|
|
30
|
+
* Both bounds live in `src/config/hook-bundle-budget.json`, never in this file:
|
|
31
|
+
* a number a reviewer has to read a script to find is a number nobody reviews.
|
|
32
|
+
*
|
|
33
|
+
* WHY IT BUILDS RATHER THAN READS. `dist/hooks/` is untracked, so a gate that
|
|
34
|
+
* read the bundle on disk would be local-only like `check_hook_bundle_content`
|
|
35
|
+
* — and the thing being bounded is what the SOURCE produces, which is a
|
|
36
|
+
* property of the commit rather than of a machine. So this builds its own
|
|
37
|
+
* bundle with `--metafile` and measures that. It runs identically in CI and
|
|
38
|
+
* locally, and it never touches the live bundle.
|
|
39
|
+
*
|
|
40
|
+
* The esbuild invocation is NOT duplicated here: it is read from
|
|
41
|
+
* `package.json`'s `build:hooks` script with only `--outfile=` rewritten, via
|
|
42
|
+
* the same `rewriteOutfile` helper `rebuild_hook_bundle` and
|
|
43
|
+
* `check_hook_bundle_content` use, so the flag set has exactly one home. The
|
|
44
|
+
* trailing `&& node src/scripts/write_bundle_digest.mjs` is stripped — this
|
|
45
|
+
* gate must not write a sidecar for a probe bundle nobody executes.
|
|
46
|
+
*
|
|
47
|
+
* WHAT IT DOES NOT DO. It says nothing about LATENCY. A smaller bundle is not
|
|
48
|
+
* a measured speed-up; `src/config/hook-latency-budget.json` owns that axis and
|
|
49
|
+
* no number here is derived from it. It says nothing about the install or MCP
|
|
50
|
+
* bundles, which carry their own module graphs.
|
|
51
|
+
*
|
|
52
|
+
* Usage:
|
|
53
|
+
* ./scripts-run src/scripts/check_hook_bundle_composition
|
|
54
|
+
* ./scripts-run src/scripts/check_hook_bundle_composition --metafile <path>
|
|
55
|
+
* ./scripts-run src/scripts/check_hook_bundle_composition --self-test
|
|
56
|
+
*
|
|
57
|
+
* Exit codes: 0 = within budget · 1 = over the ceiling or a second reader ·
|
|
58
|
+
* 2 = the build or the budget file failed.
|
|
59
|
+
*/
|
|
60
|
+
// ledger-exempt: single-artefact transaction — one bundle build yielding one
|
|
61
|
+
// aggregate verdict. The per-source accounting lives in
|
|
62
|
+
// check_hook_bundle_freshness, which carries the ledger.
|
|
63
|
+
import { spawnSync } from 'node:child_process';
|
|
64
|
+
import * as fs from 'node:fs';
|
|
65
|
+
import * as os from 'node:os';
|
|
66
|
+
import * as path from 'node:path';
|
|
67
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
68
|
+
|
|
69
|
+
import { runGateCli, runSelfTest } from './_lib/gate_self_test.js';
|
|
70
|
+
import { reportScanned } from './_lib/scan_scope.js';
|
|
71
|
+
import { buildScript } from './check_hook_bundle_content.js';
|
|
72
|
+
import { rewriteOutfile } from './rebuild_hook_bundle.js';
|
|
73
|
+
|
|
74
|
+
const _HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
75
|
+
export const REPO_ROOT = path.resolve(_HERE, '..', '..');
|
|
76
|
+
export const SELF_REL = path.join('src', 'scripts', 'check_hook_bundle_composition.ts');
|
|
77
|
+
export const BUDGET_REL = path.join('src', 'config', 'hook-bundle-budget.json');
|
|
78
|
+
const LIVE_REL = path.join('dist', 'hooks', 'dispatch.js');
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The YAML readers this tree has ever carried, by package name.
|
|
82
|
+
*
|
|
83
|
+
* A CLOSED LIST, and the consequence is worth stating: a fourth parser arriving
|
|
84
|
+
* under a name not listed here contributes bytes the ceiling catches and the
|
|
85
|
+
* parser cap does not. The alternative — treating any package whose name
|
|
86
|
+
* contains "yaml" as a reader — would match a YAML-adjacent utility that is not
|
|
87
|
+
* a parser, and a cap that fires on the wrong package is worse than one that
|
|
88
|
+
* misses an unlisted one, because the first teaches people to raise the cap.
|
|
89
|
+
*/
|
|
90
|
+
export const YAML_PACKAGES: readonly string[] = ['yaml', 'js-yaml', 'yaml-js', 'yamljs'];
|
|
91
|
+
|
|
92
|
+
export interface Budget {
|
|
93
|
+
readonly maxBytes: number;
|
|
94
|
+
readonly maxYamlPackages: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface Analysis {
|
|
98
|
+
/** Bytes of the bundle esbuild produced. */
|
|
99
|
+
readonly bytes: number;
|
|
100
|
+
/** Modules that contributed to it — the unit `scanned:` reports. */
|
|
101
|
+
readonly modules: number;
|
|
102
|
+
/** Bytes in the output, summed per owning npm package. */
|
|
103
|
+
readonly packageBytes: ReadonlyMap<string, number>;
|
|
104
|
+
/** Listed YAML packages contributing more than zero bytes, sorted. */
|
|
105
|
+
readonly yamlPackages: readonly string[];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The npm package a bundled module path belongs to, or `null` for this tree's
|
|
110
|
+
* own sources.
|
|
111
|
+
*
|
|
112
|
+
* Reads the INNERMOST `node_modules/` segment: a transitively nested copy
|
|
113
|
+
* (`node_modules/a/node_modules/yaml/…`) is still `yaml`'s bytes, and
|
|
114
|
+
* attributing it to `a` would let a second parser hide one level down.
|
|
115
|
+
*/
|
|
116
|
+
export function owningPackage(modulePath: string): string | null {
|
|
117
|
+
const marker = 'node_modules/';
|
|
118
|
+
const at = modulePath.lastIndexOf(marker);
|
|
119
|
+
if (at < 0) return null;
|
|
120
|
+
const parts = modulePath.slice(at + marker.length).split('/');
|
|
121
|
+
const head = parts[0] ?? '';
|
|
122
|
+
if (head === '') return null;
|
|
123
|
+
return head.startsWith('@') ? `${head}/${parts[1] ?? ''}` : head;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Parse an esbuild metafile and attribute its one entry-point output. */
|
|
127
|
+
export function analyzeMetafile(json: string): Analysis {
|
|
128
|
+
const meta = JSON.parse(json) as {
|
|
129
|
+
outputs?: Record<string, { entryPoint?: string; bytes?: number; inputs?: Record<string, { bytesInOutput?: number }> }>;
|
|
130
|
+
};
|
|
131
|
+
const outputs = Object.values(meta.outputs ?? {});
|
|
132
|
+
const out = outputs.find((o) => typeof o.entryPoint === 'string');
|
|
133
|
+
if (out === undefined) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
'metafile carries no entry-point output — refusing to report a composition for a build that produced nothing',
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
const packageBytes = new Map<string, number>();
|
|
139
|
+
const inputs = out.inputs ?? {};
|
|
140
|
+
for (const [p, v] of Object.entries(inputs)) {
|
|
141
|
+
const pkg = owningPackage(p);
|
|
142
|
+
if (pkg === null) continue;
|
|
143
|
+
packageBytes.set(pkg, (packageBytes.get(pkg) ?? 0) + (v.bytesInOutput ?? 0));
|
|
144
|
+
}
|
|
145
|
+
const yamlPackages = YAML_PACKAGES.filter((p) => (packageBytes.get(p) ?? 0) > 0).sort();
|
|
146
|
+
return {
|
|
147
|
+
bytes: out.bytes ?? 0,
|
|
148
|
+
modules: Object.keys(inputs).length,
|
|
149
|
+
packageBytes,
|
|
150
|
+
yamlPackages,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Read the committed budget. Throws rather than defaulting — a guessed ceiling bounds nothing. */
|
|
155
|
+
export function readBudget(root: string): Budget {
|
|
156
|
+
const abs = path.join(root, BUDGET_REL);
|
|
157
|
+
const raw = JSON.parse(fs.readFileSync(abs, 'utf-8')) as Record<string, unknown>;
|
|
158
|
+
const maxBytes = raw['max_bytes'];
|
|
159
|
+
const maxYaml = raw['max_yaml_packages'];
|
|
160
|
+
if (typeof maxBytes !== 'number' || !Number.isInteger(maxBytes) || maxBytes <= 0) {
|
|
161
|
+
throw new Error(`${BUDGET_REL}: max_bytes must be a positive integer, got ${JSON.stringify(maxBytes)}`);
|
|
162
|
+
}
|
|
163
|
+
if (typeof maxYaml !== 'number' || !Number.isInteger(maxYaml) || maxYaml < 1) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`${BUDGET_REL}: max_yaml_packages must be an integer >= 1, got ${JSON.stringify(maxYaml)}`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
return { maxBytes, maxYamlPackages: maxYaml };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Findings, one line each. Empty means within budget. */
|
|
172
|
+
export function verdict(a: Analysis, budget: Budget): string[] {
|
|
173
|
+
const findings: string[] = [];
|
|
174
|
+
if (a.yamlPackages.length > budget.maxYamlPackages) {
|
|
175
|
+
const detail = a.yamlPackages
|
|
176
|
+
.map((p) => `${p} (${String(a.packageBytes.get(p) ?? 0)} B)`)
|
|
177
|
+
.join(', ');
|
|
178
|
+
findings.push(
|
|
179
|
+
`${String(a.yamlPackages.length)} YAML readers in the bundle, cap is ` +
|
|
180
|
+
`${String(budget.maxYamlPackages)}: ${detail}. Every dispatch loads all of them. ` +
|
|
181
|
+
'Move the odd caller onto the reader the dispatcher already uses.',
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
if (a.bytes > budget.maxBytes) {
|
|
185
|
+
findings.push(
|
|
186
|
+
`bundle is ${String(a.bytes)} bytes, ceiling is ${String(budget.maxBytes)} ` +
|
|
187
|
+
`(over by ${String(a.bytes - budget.maxBytes)}). Shrink the bundle, or raise ` +
|
|
188
|
+
`max_bytes in ${BUDGET_REL} WITH an entry in its raise_log naming what was added.`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
return findings;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Build the hook bundle to a throwaway path and return its metafile JSON.
|
|
196
|
+
*
|
|
197
|
+
* Throws on any failure rather than returning a sentinel: a composition read off
|
|
198
|
+
* a build that did not happen is the silent-green this gate exists to replace.
|
|
199
|
+
*/
|
|
200
|
+
export function buildMetafile(root: string): string {
|
|
201
|
+
const script = buildScript(root);
|
|
202
|
+
if (script === null) {
|
|
203
|
+
throw new Error('package.json has no `build:hooks` script — refusing to guess the flag set');
|
|
204
|
+
}
|
|
205
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'hook-bundle-composition-'));
|
|
206
|
+
const outFile = path.join(dir, 'dispatch.js');
|
|
207
|
+
const metaFile = path.join(dir, 'meta.json');
|
|
208
|
+
try {
|
|
209
|
+
const rewritten = rewriteOutfile(script, outFile);
|
|
210
|
+
if (rewritten === null) {
|
|
211
|
+
throw new Error(`\`build:hooks\` no longer writes ${LIVE_REL} — refusing to build to a guessed path`);
|
|
212
|
+
}
|
|
213
|
+
// Strip the digest sidecar step: it would write `dispatch.sha256` beside
|
|
214
|
+
// a probe bundle nobody executes, and on failure it would mask the build
|
|
215
|
+
// error with a file-not-found.
|
|
216
|
+
const esbuildOnly = rewritten.split('&& node ')[0] ?? rewritten;
|
|
217
|
+
const built = spawnSync('sh', ['-c', `${esbuildOnly.trim()} --metafile=${metaFile}`], {
|
|
218
|
+
cwd: root,
|
|
219
|
+
encoding: 'utf-8',
|
|
220
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
221
|
+
env: {
|
|
222
|
+
...process.env,
|
|
223
|
+
PATH: `${path.join(root, 'node_modules', '.bin')}:${process.env['PATH'] ?? ''}`,
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
if (built.status !== 0 || !fs.existsSync(metaFile)) {
|
|
227
|
+
throw new Error(`esbuild failed (exit ${String(built.status)})\n${built.stderr ?? ''}`);
|
|
228
|
+
}
|
|
229
|
+
return fs.readFileSync(metaFile, 'utf-8');
|
|
230
|
+
} finally {
|
|
231
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function selfTest(): number {
|
|
236
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'hook-bundle-composition-selftest-'));
|
|
237
|
+
const write = (name: string, body: unknown): string => {
|
|
238
|
+
const p = path.join(dir, name);
|
|
239
|
+
fs.writeFileSync(p, JSON.stringify(body));
|
|
240
|
+
return p;
|
|
241
|
+
};
|
|
242
|
+
const meta = (bytes: number, inputs: Record<string, number>): unknown => ({
|
|
243
|
+
inputs: {},
|
|
244
|
+
outputs: {
|
|
245
|
+
'dist/hooks/dispatch.js': {
|
|
246
|
+
entryPoint: 'src/scripts/hooks/dispatch_entry.ts',
|
|
247
|
+
bytes,
|
|
248
|
+
inputs: Object.fromEntries(
|
|
249
|
+
Object.entries(inputs).map(([k, v]) => [k, { bytesInOutput: v }]),
|
|
250
|
+
),
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
const budget = readBudget(REPO_ROOT);
|
|
255
|
+
const clean = write('clean.json', meta(budget.maxBytes, { 'node_modules/yaml/dist/index.js': 10 }));
|
|
256
|
+
const twoReaders = write(
|
|
257
|
+
'two-readers.json',
|
|
258
|
+
meta(1000, {
|
|
259
|
+
'node_modules/yaml/dist/index.js': 600,
|
|
260
|
+
'node_modules/js-yaml/dist/js-yaml.mjs': 400,
|
|
261
|
+
}),
|
|
262
|
+
);
|
|
263
|
+
const oneOver = write('one-over.json', meta(budget.maxBytes + 1, { 'src/x.ts': 1 }));
|
|
264
|
+
const empty = write('empty.json', { inputs: {}, outputs: {} });
|
|
265
|
+
|
|
266
|
+
const drive = (fixture: string): number =>
|
|
267
|
+
runGateCli(REPO_ROOT, SELF_REL, ['--metafile', fixture], REPO_ROOT);
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
return runSelfTest({
|
|
271
|
+
gate: 'check_hook_bundle_composition',
|
|
272
|
+
minCases: 4,
|
|
273
|
+
minRejectCases: 3,
|
|
274
|
+
cases: [
|
|
275
|
+
{
|
|
276
|
+
name: 'one YAML reader, exactly at the ceiling → accepted',
|
|
277
|
+
expect: 'accept',
|
|
278
|
+
run: () => drive(clean),
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: 'a second YAML reader → rejected',
|
|
282
|
+
expect: 'reject',
|
|
283
|
+
run: () => drive(twoReaders),
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
name: 'one byte over the ceiling → rejected',
|
|
287
|
+
expect: 'reject',
|
|
288
|
+
run: () => drive(oneOver),
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
name: 'a metafile with no entry-point output → rejected, never reported as 0 bytes',
|
|
292
|
+
expect: 'reject',
|
|
293
|
+
run: () => drive(empty),
|
|
294
|
+
},
|
|
295
|
+
],
|
|
296
|
+
});
|
|
297
|
+
} finally {
|
|
298
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function main(argv: readonly string[] = process.argv.slice(2), root: string = REPO_ROOT): number {
|
|
303
|
+
if (argv.includes('--self-test')) {
|
|
304
|
+
return selfTest();
|
|
305
|
+
}
|
|
306
|
+
const at = argv.indexOf('--metafile');
|
|
307
|
+
const fixture = at >= 0 ? argv[at + 1] : undefined;
|
|
308
|
+
|
|
309
|
+
let budget: Budget;
|
|
310
|
+
try {
|
|
311
|
+
budget = readBudget(root);
|
|
312
|
+
} catch (e) {
|
|
313
|
+
process.stderr.write(`check_hook_bundle_composition: ${String(e instanceof Error ? e.message : e)}\n`);
|
|
314
|
+
return 2;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
let json: string;
|
|
318
|
+
try {
|
|
319
|
+
json = fixture === undefined ? buildMetafile(root) : fs.readFileSync(fixture, 'utf-8');
|
|
320
|
+
} catch (e) {
|
|
321
|
+
process.stderr.write(`check_hook_bundle_composition: ${String(e instanceof Error ? e.message : e)}\n`);
|
|
322
|
+
return 2;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
let analysis: Analysis;
|
|
326
|
+
try {
|
|
327
|
+
analysis = analyzeMetafile(json);
|
|
328
|
+
} catch (e) {
|
|
329
|
+
process.stderr.write(`check_hook_bundle_composition: ${String(e instanceof Error ? e.message : e)}\n`);
|
|
330
|
+
return 1;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const findings = verdict(analysis, budget);
|
|
334
|
+
// Reported on BOTH paths, red included: a census that goes quiet exactly
|
|
335
|
+
// when the gate fires is the shape `reportScanned` exists to prevent.
|
|
336
|
+
reportScanned({
|
|
337
|
+
gate: 'check_hook_bundle_composition',
|
|
338
|
+
scanned: analysis.modules,
|
|
339
|
+
units: 'bundled module(s)',
|
|
340
|
+
roots: [LIVE_REL],
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
if (findings.length === 0) {
|
|
344
|
+
process.stdout.write(
|
|
345
|
+
`✅ hook bundle: ${String(analysis.bytes)} B / ${String(budget.maxBytes)} B ceiling, ` +
|
|
346
|
+
`${String(analysis.yamlPackages.length)} YAML reader(s) ` +
|
|
347
|
+
`[${analysis.yamlPackages.join(', ')}], ${String(analysis.modules)} modules\n`,
|
|
348
|
+
);
|
|
349
|
+
return 0;
|
|
350
|
+
}
|
|
351
|
+
process.stderr.write('❌ hook bundle composition is out of budget\n');
|
|
352
|
+
for (const f of findings) process.stderr.write(` - ${f}\n`);
|
|
353
|
+
process.stderr.write(` Budget: ${BUDGET_REL}\n`);
|
|
354
|
+
return 1;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function _isCliEntry(): boolean {
|
|
358
|
+
const argv1 = process.argv[1];
|
|
359
|
+
if (argv1 === undefined) return false;
|
|
360
|
+
if (import.meta.url === pathToFileURL(path.resolve(argv1)).href) return true;
|
|
361
|
+
try {
|
|
362
|
+
return fs.realpathSync(fileURLToPath(import.meta.url)) === fs.realpathSync(path.resolve(argv1));
|
|
363
|
+
} catch {
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (_isCliEntry()) {
|
|
369
|
+
process.exit(main());
|
|
370
|
+
}
|
|
@@ -30,12 +30,14 @@
|
|
|
30
30
|
*
|
|
31
31
|
* Gated: a diff touching a kernel rule under `src/rules/` (the nine of
|
|
32
32
|
* `_lib/kernel_rules.ts`), a governance hook (`src/scripts/hooks/block_*.ts`),
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
33
|
+
* hook plumbing — the YAML sources `hook_manifest.yaml` and
|
|
34
|
+
* `host_lowering.yaml`, their compiled siblings `hook_manifest.json` and
|
|
35
|
+
* `host_lowering.json`, the dispatcher `hooks/dispatch_hook.ts`, the
|
|
36
|
+
* kernel-rule list `_lib/kernel_rules.ts`, a `*-dispatcher.sh`, either hook
|
|
37
|
+
* budget file (see `PLUMBING_SOURCE_RE`) — or this gate itself, so it cannot
|
|
38
|
+
* be weakened without its own record. Such a diff must carry a ratification
|
|
39
|
+
* artifact under the ratifications directory with `verdict: ratified`, valid
|
|
40
|
+
* per the ratification-artifact contract.
|
|
39
41
|
*
|
|
40
42
|
* Every kernel diff is gated, not only the authority-EXPANDING ones.
|
|
41
43
|
* ADR-268 § 4 makes the artifact required for authority-expanding edits. Which
|
|
@@ -141,13 +143,49 @@ const GOVERNANCE_HOOK_RE = /^src\/scripts\/hooks\/block_[a-z0-9_]+\.ts$/;
|
|
|
141
143
|
* and every property the gate already had — base-revision code, in-repo quorum
|
|
142
144
|
* policy, the whole diff as its unit — carries over unchanged.
|
|
143
145
|
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
146
|
+
* `dist/hooks/dispatch.js` and `hooks/hooks.json` are BUILD OUTPUTS that no
|
|
147
|
+
* reader in this repository opens — they exist only inside an installed
|
|
148
|
+
* consumer tree — and they have no legitimate hand edit at all, so they are
|
|
149
|
+
* refused at tool-call time by `block_plumbing_writes.ts` rather than ratified
|
|
150
|
+
* here: a record for a file whose every hand edit is illegitimate would be a
|
|
151
|
+
* record of a mistake.
|
|
152
|
+
*
|
|
153
|
+
* road-to-blocking-severities 2.1 WIDENS this from "sources only" to
|
|
154
|
+
* "everything the dispatcher serves at runtime", and the distinction above is
|
|
155
|
+
* why the two compiled tables land here while the two build outputs do not.
|
|
156
|
+
* `src/scripts/hook_manifest.json` and `src/scripts/hooks/host_lowering.json`
|
|
157
|
+
* are committed in THIS tree and are READ IN PREFERENCE to the YAML they were
|
|
158
|
+
* compiled from — `dispatch_hook._load_yaml` and `host_lowering.resolveTable`
|
|
159
|
+
* both serve the compiled body whenever its fingerprint matches. A diff that
|
|
160
|
+
* edits only the JSON therefore changes what every host is told about which
|
|
161
|
+
* concern runs and which slot can refuse, while the gated YAML beside it is
|
|
162
|
+
* untouched. That is the same authority change as editing the source, reached
|
|
163
|
+
* one file later, and before this step it carried no record.
|
|
164
|
+
*
|
|
165
|
+
* Two further files join for the same reason rather than by analogy:
|
|
166
|
+
*
|
|
167
|
+
* - `src/scripts/hooks/dispatch_hook.ts` is the dispatcher itself. Every
|
|
168
|
+
* concern's verdict passes through it, so an edit here can suppress a
|
|
169
|
+
* refusal without touching the manifest that declares it.
|
|
170
|
+
* - `src/scripts/_lib/kernel_rules.ts` is the list of the nine kernel rules.
|
|
171
|
+
* THIS GATE resolves `is_kernel_rule` through it, so a diff removing a name
|
|
172
|
+
* from that list removes a rule from this gate's own reach — the one edit
|
|
173
|
+
* that could quietly narrow the gate while every other path kept working.
|
|
174
|
+
*
|
|
175
|
+
* Both are also denied at tool-call time, `dispatch_hook.ts` through the
|
|
176
|
+
* plumbing guard's source set and `kernel_rules.ts` through the kernel-rule
|
|
177
|
+
* write guard, so the pairing of deny-plus-record matches what the YAML sources
|
|
178
|
+
* already had.
|
|
179
|
+
*
|
|
180
|
+
* The compiled tables are REGENERATED, not hand-edited, by
|
|
181
|
+
* `./scripts-run src/scripts/compile_hook_manifest --table manifest|host-lowering`,
|
|
182
|
+
* which runs as a script rather than as an edit tool call and so is not what
|
|
183
|
+
* either mechanism refuses. A regeneration lands in the same diff as the YAML
|
|
184
|
+
* change that caused it, which already requires a record; the case this closes
|
|
185
|
+
* is the diff where only the JSON moves.
|
|
148
186
|
*/
|
|
149
187
|
const PLUMBING_SOURCE_RE =
|
|
150
|
-
/^(?:src\/scripts\/hook_manifest\.yaml|src\/scripts\/hooks\/host_lowering\.yaml|src\/scripts\/hooks\/[a-z0-9-]+-dispatcher\.sh|src\/config\/hook-(?:token|latency)-budget\.json)$/;
|
|
188
|
+
/^(?:src\/scripts\/hook_manifest\.(?:yaml|json)|src\/scripts\/hooks\/host_lowering\.(?:yaml|json)|src\/scripts\/hooks\/dispatch_hook\.ts|src\/scripts\/_lib\/kernel_rules\.ts|src\/scripts\/hooks\/[a-z0-9-]+-dispatcher\.sh|src\/config\/hook-(?:token|latency)-budget\.json)$/;
|
|
151
189
|
|
|
152
190
|
/**
|
|
153
191
|
* A kernel rule, in the source tree or in any projection.
|
|
@@ -607,7 +607,13 @@ function _checkAppendOnly(base: string | null, findings: Finding[]): void {
|
|
|
607
607
|
'--no-color',
|
|
608
608
|
ref,
|
|
609
609
|
'--',
|
|
610
|
-
'
|
|
610
|
+
// The JSONL drops, not the directory. `INTAKE_GLOB`, this module's
|
|
611
|
+
// docstring and every fixture in the suite all scope this check to
|
|
612
|
+
// `*.jsonl`; asking git for the directory made it govern the tracked
|
|
613
|
+
// skeleton too, so editing the README that documents the append-only
|
|
614
|
+
// rule was reported as a violation of it. `:(glob)` is explicit rather
|
|
615
|
+
// than relying on the default pathspec magic.
|
|
616
|
+
':(glob)agents/memory/intake/*.jsonl',
|
|
611
617
|
]);
|
|
612
618
|
if (rc !== 0) {
|
|
613
619
|
findings.push(new Finding(INTAKE_GLOB, 0, 'warning', `append-only: git diff failed vs ${ref}`));
|
|
@@ -79,6 +79,7 @@ import {
|
|
|
79
79
|
CURATED_HEAD_INSTRUCTION,
|
|
80
80
|
extract_changelog_section,
|
|
81
81
|
} from './_lib/release_material.js';
|
|
82
|
+
import { coverageLimitationNote, ledgerCoverageLabel } from './_lib/review_coverage.js';
|
|
82
83
|
import { assertScanned, assertWatchlistResolves, DeadScopeError } from './_lib/scan_scope.js';
|
|
83
84
|
|
|
84
85
|
const _HERE = fileURLToPath(import.meta.url);
|
|
@@ -339,6 +340,15 @@ export function main(argv: readonly string[]): number {
|
|
|
339
340
|
const contradictions = highlight_contradictions(curated, derived);
|
|
340
341
|
if (contradictions.length === 0) {
|
|
341
342
|
process.stdout.write(`✅ curated head plausible for ${version} (span ${from}..${to})\n`);
|
|
343
|
+
// How much of the change the self-review read, printed where the head's
|
|
344
|
+
// Known-limitations field is decided. Partial coverage IS a known
|
|
345
|
+
// limitation of the record, and the ledger was the only place it was
|
|
346
|
+
// written down. A label, not a floor — the exit code above is already
|
|
347
|
+
// fixed at 0 by the time this runs, deliberately.
|
|
348
|
+
const covNote = coverageLimitationNote(ledgerCoverageLabel(REPO_ROOT, version));
|
|
349
|
+
if (covNote !== null) {
|
|
350
|
+
process.stdout.write(`${covNote}\n`);
|
|
351
|
+
}
|
|
342
352
|
// Risk 2 of the roadmap that made `Known limitations` derivable, paid
|
|
343
353
|
// where it lands rather than in a comment: a self-declared residual is
|
|
344
354
|
// only detectable when somebody WROTE IT DOWN, so a derived `_none_`
|
|
@@ -45,6 +45,16 @@ const ALLOWLIST_GLOBS = [
|
|
|
45
45
|
// be able to carry the dispositions of its own self-review findings; the
|
|
46
46
|
// finding-dispositions gate is red until blocking ones are recorded here.
|
|
47
47
|
'agents/evidence/release-findings/*.json',
|
|
48
|
+
// Evidence-temperature census — `taskfiles/content.yml` § release-prepare
|
|
49
|
+
// writes one per release, so without this entry the pipeline generates a
|
|
50
|
+
// file its own shape step refuses. Digit classes rather than `?`: fnmatch
|
|
51
|
+
// `?` compiles to `.` under the `s` flag, so it matches any character AND
|
|
52
|
+
// crosses `/` — `????-??-??` admits `evidence-temperature-x/yz-ab-cd.md`,
|
|
53
|
+
// which `[0-9]` cannot. Two reports can appear in one release PR, and
|
|
54
|
+
// both are in shape: `--resume` re-runs release-prepare, so a release
|
|
55
|
+
// carried across UTC midnight writes a second census. What that one
|
|
56
|
+
// contains is the census's business, not this gate's.
|
|
57
|
+
'agents/evidence/analysis/evidence-temperature-[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9].md', // code-comment-allow provenance-comment -- the glob IS this gate's operand, not where the code came from
|
|
48
58
|
// Project-settings template pin — bumped by release.ts set_template_pin and
|
|
49
59
|
// its regenerated dist twin (kept in lockstep with package.json.version).
|
|
50
60
|
'src/agent-src/templates/agents/agent-project-settings.example.yml',
|
|
@@ -52,16 +62,26 @@ const ALLOWLIST_GLOBS = [
|
|
|
52
62
|
] as const;
|
|
53
63
|
|
|
54
64
|
/**
|
|
55
|
-
* Translate a Python `fnmatch` shell pattern to a RegExp,
|
|
56
|
-
* `fnmatch.translate
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
65
|
+
* Translate a Python `fnmatch` shell pattern to a RegExp, following
|
|
66
|
+
* `fnmatch.translate` for the constructs the allowlist actually uses: `*` and
|
|
67
|
+
* `[0-9]`. No entry uses `?`, and bracket handling beyond that one class is
|
|
68
|
+
* unverified against CPython — see the note at the bracket branch.
|
|
69
|
+
*
|
|
70
|
+
* Matching is case-sensitive, as `fnmatchcase` on a normalized path.
|
|
71
|
+
*
|
|
72
|
+
* THE PROPERTY THE WILDCARD ENTRIES TURN ON — the literal paths are unaffected:
|
|
73
|
+
* in fnmatch, `*` and `?` DO cross path separators.
|
|
74
|
+
* `*` becomes `.*` and `?` becomes `.`, both under the `s` flag, so each matches
|
|
75
|
+
* `/` like any other character. Glob syntax elsewhere (shells, .gitignore) stops
|
|
76
|
+
* `*` at a segment boundary; this does not. So the pack entry admits any depth
|
|
77
|
+
* beneath `src/packs/`, and the census entry uses digit classes precisely
|
|
78
|
+
* because they cannot match a separator. Both sides are pinned in
|
|
79
|
+
* `check_release_pr_shape.test.ts`.
|
|
61
80
|
*/
|
|
62
81
|
function _fnmatchToRegExp(pat: string): RegExp {
|
|
63
|
-
//
|
|
64
|
-
//
|
|
82
|
+
// `*` → `.*`, `?` → `.`, a bracket class kept, everything else escaped.
|
|
83
|
+
// CPython does more inside a class (hyphen and set-operator escaping, `(?!)`
|
|
84
|
+
// for an empty one) and this does not — see the bracket branch below.
|
|
65
85
|
let i = 0;
|
|
66
86
|
const n = pat.length;
|
|
67
87
|
let res = '';
|
|
@@ -87,12 +107,13 @@ function _fnmatchToRegExp(pat: string): RegExp {
|
|
|
87
107
|
res += '\\[';
|
|
88
108
|
} else {
|
|
89
109
|
let stuff = pat.slice(i, j);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
110
|
+
// The two arms this replaces were byte-identical, so the branch
|
|
111
|
+
// implemented nothing. Bracket handling here is checked against
|
|
112
|
+
// `[0-9]`, the only class the allowlist uses; anything else is
|
|
113
|
+
// untested against CPython, whose behaviour also varies by
|
|
114
|
+
// version. Add a bracket glob and verify it rather than assuming
|
|
115
|
+
// this mirrors `fnmatch.translate` beyond that one case.
|
|
116
|
+
stuff = stuff.replace(/\\/g, '\\\\');
|
|
96
117
|
i = j + 1;
|
|
97
118
|
if (stuff.startsWith('!')) {
|
|
98
119
|
stuff = '^' + stuff.slice(1);
|
|
@@ -105,7 +126,9 @@ function _fnmatchToRegExp(pat: string): RegExp {
|
|
|
105
126
|
res += c.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
106
127
|
}
|
|
107
128
|
}
|
|
108
|
-
// Python
|
|
129
|
+
// Full match with dotAll. Python spells the same thing `(?s:%s)\Z`; in JS
|
|
130
|
+
// `$` without the `m` flag does not match before a trailing newline, so
|
|
131
|
+
// `^(?:…)$` is equivalent rather than merely similar.
|
|
109
132
|
return new RegExp(`^(?:${res})$`, 's');
|
|
110
133
|
}
|
|
111
134
|
|
|
@@ -31,24 +31,44 @@
|
|
|
31
31
|
* Exit codes: `0` under cap · `1` over cap or an unstated cap raise · `2` usage
|
|
32
32
|
* error · `3` dead scan scope.
|
|
33
33
|
*
|
|
34
|
-
*
|
|
34
|
+
* THIS IS DELIBERATELY NOT A CI GATE, AND THE REASON IS NARROWER THAN IT WAS.
|
|
35
35
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* **neither** layer exists
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
36
|
+
* **Corrected 2026-10-05 (step 0.1 of
|
|
37
|
+
* `road-to-a-rule-carrier-that-works-outside-the-repo`).** This section used to
|
|
38
|
+
* say "no CI workflow performs a user-scope install, so on a fresh runner
|
|
39
|
+
* **neither** layer exists", and then concluded that making the measurement
|
|
40
|
+
* CI-observable "needs the `InstructionsLoaded` record committed from a real
|
|
41
|
+
* session, which is a different piece of work". The first clause describes a
|
|
42
|
+
* workflow inventory; the second turns it into a capability claim, and that
|
|
43
|
+
* step does not hold. A runner CAN stage a user-scope install: the installer
|
|
44
|
+
* reads `HOME` from the environment, and
|
|
45
|
+
* `tests/install/global_install_hooks_smoke.test.ts` already drives
|
|
46
|
+
* `src/scripts/install --global --tools=claude-code` against a `mkdtemp` HOME
|
|
47
|
+
* and asserts the tree it produced. Nothing about the `InstructionsLoaded`
|
|
48
|
+
* event is required to observe the FILESYSTEM half, which is what this script's
|
|
49
|
+
* `filesystem` input reads.
|
|
45
50
|
*
|
|
46
|
-
* What
|
|
51
|
+
* What stays true, and is the real reason this is not in `gate-coverage.yml`:
|
|
52
|
+
* **this gate measures THIS machine**, by design. `main()` resolves
|
|
53
|
+
* `os.homedir()` and `REPO_ROOT`, and the question it answers — "how much is
|
|
54
|
+
* the developer in front of me actually loading" — is about a machine, not
|
|
55
|
+
* about a commit. A CI run of it would measure the runner, which is a tree
|
|
56
|
+
* nobody works in, so the number would be real and uninteresting. The
|
|
57
|
+
* `measureStandingDelivery` function underneath takes both directories as
|
|
58
|
+
* PARAMETERS and is therefore already temp-HOME-capable; what is missing is a
|
|
59
|
+
* reason to point it at a runner, not a way to.
|
|
60
|
+
*
|
|
61
|
+
* The honest cost is unchanged: the duplicate-delivery defect is not
|
|
47
62
|
* mechanically caught for anyone who never runs this locally. The enforced layer
|
|
48
63
|
* is the installer gate (`install.ts::_gate_rule_layer_overlap`), which refuses
|
|
49
64
|
* to CREATE the doubled state; this script measures a state that already exists.
|
|
50
|
-
*
|
|
51
|
-
*
|
|
65
|
+
*
|
|
66
|
+
* `_lib/installed_layer.ts` is the step-0.1 library that takes the same reading
|
|
67
|
+
* per host rule directory with every input as a parameter, and
|
|
68
|
+
* `installed_layer_report.ts` is its report. The two measure different things —
|
|
69
|
+
* that one counts characters after the frontmatter and block-comment strip,
|
|
70
|
+
* this one counts tokens over whole files against a cap — and neither number
|
|
71
|
+
* converts into the other.
|
|
52
72
|
*/
|
|
53
73
|
|
|
54
74
|
import * as fs from 'node:fs';
|