@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,517 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* lint_rule_law_section — every routed rule states its law in one section short
|
|
4
|
+
* enough to stand on its own.
|
|
5
|
+
*
|
|
6
|
+
* THE DEFECT. Measured 2026-10-01 over `dist/router.json`'s 106 tier rules:
|
|
7
|
+
* 71 carry a heading matching `Iron Law`, 35 do not — among them
|
|
8
|
+
* `runtime-safety` and `tool-safety`, which govern tool grants and execution
|
|
9
|
+
* handlers. Several of the 35 state a law in bold text or a bare fence instead,
|
|
10
|
+
* so 35 is an upper bound on what is genuinely missing, not a count of
|
|
11
|
+
* lawless rules. Nothing asserted either way: law sections were an authoring
|
|
12
|
+
* habit, never a state.
|
|
13
|
+
*
|
|
14
|
+
* That matters now because `project_thin_rules` ships a class of rules as a
|
|
15
|
+
* stub plus their own law section. A rule with no law section cannot be in that
|
|
16
|
+
* class, and a rule whose law section is 5,387 characters defeats the point of
|
|
17
|
+
* a stub. This gate holds three lines so the projector can rely on them.
|
|
18
|
+
*
|
|
19
|
+
* WHAT IT ASSERTS, three axes, each with its own ratchet:
|
|
20
|
+
*
|
|
21
|
+
* 1. PRESENCE (`--axis presence`). Every routed tier rule has a law section, or
|
|
22
|
+
* is named in the `missing` baseline. SHRINK-ONLY: an id may leave that list
|
|
23
|
+
* freely, and a routed rule that is not on it and has no law section fails.
|
|
24
|
+
* A NEW routed rule therefore cannot be born lawless.
|
|
25
|
+
*
|
|
26
|
+
* 2. LAW CEILING (`--axis ceiling`). Target 1,200 characters, REPORTED and
|
|
27
|
+
* never failing; hard 2,000, failing. A rule over the hard ceiling today is
|
|
28
|
+
* named in `law_exceptions` with an owner and a review date, and may only
|
|
29
|
+
* shrink — its recorded size is a ceiling, not a licence.
|
|
30
|
+
*
|
|
31
|
+
* 3. BODY CEILING (`--axis body-ceiling`). 8,000 characters of
|
|
32
|
+
* comment-stripped body, the unit a full runtime delivery carries, so it is
|
|
33
|
+
* priced in the same unit the installed-layer report uses. Today's larger
|
|
34
|
+
* rules are named in `body_exceptions`, shrink-only on the same terms.
|
|
35
|
+
*
|
|
36
|
+
* WHY THE TARGET IS REPORTED AND NOT ENFORCED. A rule shortened to fit a number
|
|
37
|
+
* is the failure this roadmap says it will not cause. 1,200 is where the
|
|
38
|
+
* distribution sits (median 511, p90 1,787) and a rule between target and hard
|
|
39
|
+
* ceiling is a rule to look at, not a rule to cut. Only 2,000 — above p90, so
|
|
40
|
+
* no rule is truncated by it — refuses.
|
|
41
|
+
*
|
|
42
|
+
* WHY SCOPED TO TIER RULES. "Routed" means present in `router.json`'s `tier_1`
|
|
43
|
+
* or `tier_2`. The nine kernel rules are excluded and the exclusion is a fact
|
|
44
|
+
* about this repository, not a judgement: kernel rule files are agent-write-
|
|
45
|
+
* denied, and two of them (`agent-authority`, `scope-control`) carry no Iron-Law
|
|
46
|
+
* heading today, so a gate that included them would be a gate no agent could
|
|
47
|
+
* ever make green. They are counted and printed under `kernel (reported)` so
|
|
48
|
+
* the exclusion is visible rather than silent, and `project_thin_rules` keeps
|
|
49
|
+
* the kernel full-bodied anyway — it never ships a kernel stub, so the stub-law
|
|
50
|
+
* question does not arise there.
|
|
51
|
+
*
|
|
52
|
+
* WHAT IT DOES NOT ASSERT. Not that the extracted section is the rule's real
|
|
53
|
+
* obligation — a heading match is structural and checkable, "this sentence is
|
|
54
|
+
* the obligation" is not; the `# obligation: line N` marker and
|
|
55
|
+
* `report_obligation_carriers` own that. Not that a rule at its recorded
|
|
56
|
+
* exception is correct, only that it is not worse. And not a cross-commit
|
|
57
|
+
* ratchet: this gate reads one tree, so "shrink-only" means the tree sits at or
|
|
58
|
+
* under what is committed, and a silently LOWERED baseline is a question the
|
|
59
|
+
* diff answers, not this gate. Saying so is the point.
|
|
60
|
+
*
|
|
61
|
+
* Exit codes: 0 clean, 1 violations, 2 usage error, 3 internal error.
|
|
62
|
+
*/
|
|
63
|
+
import * as fs from 'node:fs';
|
|
64
|
+
import * as os from 'node:os';
|
|
65
|
+
import * as path from 'node:path';
|
|
66
|
+
import process from 'node:process';
|
|
67
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
68
|
+
|
|
69
|
+
import { asOf } from './_lib/as_of.js';
|
|
70
|
+
import { measureRuleFile, type RuleLawMeasure } from './_lib/rule_law_section.js';
|
|
71
|
+
import { DeadScopeError, reportScanned } from './_lib/scan_scope.js';
|
|
72
|
+
|
|
73
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
74
|
+
export const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
75
|
+
|
|
76
|
+
export const CONFIG_REL = 'src/config/rule-law-ceilings.json';
|
|
77
|
+
export const RULES_REL = 'src/rules';
|
|
78
|
+
export const ROUTER_REL = 'dist/router.json';
|
|
79
|
+
|
|
80
|
+
/** Reported, never failing — a rule above it is a rule to look at. */
|
|
81
|
+
export const LAW_TARGET_CHARS = 1_200;
|
|
82
|
+
/** Refused. Above p90, so no rule in the distribution is truncated by it. */
|
|
83
|
+
export const LAW_HARD_CHARS = 2_000;
|
|
84
|
+
/** Refused. The unit a full runtime delivery carries. */
|
|
85
|
+
export const BODY_HARD_CHARS = 8_000;
|
|
86
|
+
|
|
87
|
+
export interface SizeException {
|
|
88
|
+
/** Recorded size at adoption. The rule may sit at or under it, never above. */
|
|
89
|
+
chars: number;
|
|
90
|
+
/** Who answers for this rule staying oversized. */
|
|
91
|
+
owner: string;
|
|
92
|
+
/** `YYYY-MM-DD` the exception is due for review. */
|
|
93
|
+
review: string;
|
|
94
|
+
/** Why this rule cannot state its law under the ceiling today. */
|
|
95
|
+
reason: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface LawConfig {
|
|
99
|
+
_comment?: string;
|
|
100
|
+
measured_at_commit?: string;
|
|
101
|
+
/** Routed rules with no law section. Shrink-only: ids may leave, never join. */
|
|
102
|
+
missing: string[];
|
|
103
|
+
/** Rules whose law exceeds {@link LAW_HARD_CHARS}. */
|
|
104
|
+
law_exceptions: Record<string, SizeException>;
|
|
105
|
+
/** Rules whose body exceeds {@link BODY_HARD_CHARS}. */
|
|
106
|
+
body_exceptions: Record<string, SizeException>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export type Axis = 'presence' | 'ceiling' | 'body-ceiling' | 'all';
|
|
110
|
+
|
|
111
|
+
export interface Finding {
|
|
112
|
+
id: string;
|
|
113
|
+
axis: Exclude<Axis, 'all'>;
|
|
114
|
+
kind: string;
|
|
115
|
+
detail: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface LintResult {
|
|
119
|
+
findings: Finding[];
|
|
120
|
+
/** Rules at or above the soft target but under the hard ceiling. */
|
|
121
|
+
overTarget: Array<{ id: string; chars: number }>;
|
|
122
|
+
measures: RuleLawMeasure[];
|
|
123
|
+
kernelReported: Array<{ id: string; sections: number }>;
|
|
124
|
+
scanned: number;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Routed tier rule ids, sorted — `tier_1` + `tier_2`, kernel excluded. */
|
|
128
|
+
export function routedRuleIds(root = REPO_ROOT): string[] {
|
|
129
|
+
const data = JSON.parse(fs.readFileSync(path.join(root, ROUTER_REL), 'utf-8')) as Record<
|
|
130
|
+
string,
|
|
131
|
+
unknown
|
|
132
|
+
>;
|
|
133
|
+
const ids: string[] = [];
|
|
134
|
+
for (const tier of ['tier_1', 'tier_2']) {
|
|
135
|
+
const entries = data[tier];
|
|
136
|
+
if (!Array.isArray(entries)) continue;
|
|
137
|
+
for (const e of entries) ids.push(String((e as Record<string, unknown>).id));
|
|
138
|
+
}
|
|
139
|
+
return [...new Set(ids)].sort();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function kernelRuleIds(root = REPO_ROOT): string[] {
|
|
143
|
+
const data = JSON.parse(fs.readFileSync(path.join(root, ROUTER_REL), 'utf-8')) as Record<
|
|
144
|
+
string,
|
|
145
|
+
unknown
|
|
146
|
+
>;
|
|
147
|
+
const kernel = data.kernel;
|
|
148
|
+
return Array.isArray(kernel) ? kernel.map((x) => String(x)).sort() : [];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function readConfig(root = REPO_ROOT): LawConfig {
|
|
152
|
+
const p = path.join(root, CONFIG_REL);
|
|
153
|
+
const raw = JSON.parse(fs.readFileSync(p, 'utf-8')) as Partial<LawConfig>;
|
|
154
|
+
return {
|
|
155
|
+
missing: Array.isArray(raw.missing) ? raw.missing : [],
|
|
156
|
+
law_exceptions: raw.law_exceptions ?? {},
|
|
157
|
+
body_exceptions: raw.body_exceptions ?? {},
|
|
158
|
+
measured_at_commit: raw.measured_at_commit ?? '',
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Measure every routed rule that has a file. */
|
|
163
|
+
export function measureRouted(root = REPO_ROOT): RuleLawMeasure[] {
|
|
164
|
+
const out: RuleLawMeasure[] = [];
|
|
165
|
+
for (const id of routedRuleIds(root)) {
|
|
166
|
+
const p = path.join(root, RULES_REL, `${id}.md`);
|
|
167
|
+
if (!fs.existsSync(p)) continue;
|
|
168
|
+
out.push(measureRuleFile(id, p));
|
|
169
|
+
}
|
|
170
|
+
return out;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function lint(root = REPO_ROOT, axis: Axis = 'all'): LintResult {
|
|
174
|
+
const cfg = readConfig(root);
|
|
175
|
+
const measures = measureRouted(root);
|
|
176
|
+
const findings: Finding[] = [];
|
|
177
|
+
const overTarget: Array<{ id: string; chars: number }> = [];
|
|
178
|
+
const baselined = new Set(cfg.missing);
|
|
179
|
+
const want = (a: Exclude<Axis, 'all'>): boolean => axis === 'all' || axis === a;
|
|
180
|
+
|
|
181
|
+
for (const m of measures) {
|
|
182
|
+
if (want('presence') && m.law === null && !baselined.has(m.id)) {
|
|
183
|
+
findings.push({
|
|
184
|
+
id: m.id,
|
|
185
|
+
axis: 'presence',
|
|
186
|
+
kind: 'no-law-section',
|
|
187
|
+
detail:
|
|
188
|
+
'routed rule has no heading matching `Iron Law` — state the obligation under ' +
|
|
189
|
+
`one, or add the id to \`missing\` in ${CONFIG_REL} (that list is shrink-only, ` +
|
|
190
|
+
'so a new routed rule may not join it)',
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
if (want('ceiling') && m.law !== null) {
|
|
194
|
+
const exc = cfg.law_exceptions[m.id];
|
|
195
|
+
if (exc === undefined) {
|
|
196
|
+
if (m.lawChars > LAW_HARD_CHARS) {
|
|
197
|
+
findings.push({
|
|
198
|
+
id: m.id,
|
|
199
|
+
axis: 'ceiling',
|
|
200
|
+
kind: 'law-over-hard-ceiling',
|
|
201
|
+
detail:
|
|
202
|
+
`law section is ${m.lawChars} chars, over the ${LAW_HARD_CHARS} hard ceiling — ` +
|
|
203
|
+
'move the discussion under it behind `load_context`, or record a dated ' +
|
|
204
|
+
`exception with an owner in ${CONFIG_REL}`,
|
|
205
|
+
});
|
|
206
|
+
} else if (m.lawChars > LAW_TARGET_CHARS) {
|
|
207
|
+
overTarget.push({ id: m.id, chars: m.lawChars });
|
|
208
|
+
}
|
|
209
|
+
} else if (m.lawChars > exc.chars) {
|
|
210
|
+
findings.push({
|
|
211
|
+
id: m.id,
|
|
212
|
+
axis: 'ceiling',
|
|
213
|
+
kind: 'law-exception-grew',
|
|
214
|
+
detail:
|
|
215
|
+
`law section is ${m.lawChars} chars, above its recorded exception of ` +
|
|
216
|
+
`${exc.chars} (owner ${exc.owner}, review ${exc.review}) — an exception is ` +
|
|
217
|
+
'a ceiling, not a licence; it may only shrink',
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (want('body-ceiling')) {
|
|
222
|
+
const exc = cfg.body_exceptions[m.id];
|
|
223
|
+
if (exc === undefined) {
|
|
224
|
+
if (m.bodyChars > BODY_HARD_CHARS) {
|
|
225
|
+
findings.push({
|
|
226
|
+
id: m.id,
|
|
227
|
+
axis: 'body-ceiling',
|
|
228
|
+
kind: 'body-over-hard-ceiling',
|
|
229
|
+
detail:
|
|
230
|
+
`body is ${m.bodyChars} chars, over the ${BODY_HARD_CHARS} hard ceiling — ` +
|
|
231
|
+
'move history and mechanism discussion into a context file the rule names ' +
|
|
232
|
+
`in \`load_context\`, or record a dated exception in ${CONFIG_REL}`,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
} else if (m.bodyChars > exc.chars) {
|
|
236
|
+
findings.push({
|
|
237
|
+
id: m.id,
|
|
238
|
+
axis: 'body-ceiling',
|
|
239
|
+
kind: 'body-exception-grew',
|
|
240
|
+
detail:
|
|
241
|
+
`body is ${m.bodyChars} chars, above its recorded exception of ${exc.chars} ` +
|
|
242
|
+
`(owner ${exc.owner}, review ${exc.review}) — it may only shrink`,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// A baseline entry for a rule that now HAS a law section is debt that was
|
|
249
|
+
// paid and never collected. Reported as a finding, because a stale
|
|
250
|
+
// suppression is how a ratchet hardens into configuration.
|
|
251
|
+
if (want('presence')) {
|
|
252
|
+
const haveLaw = new Set(measures.filter((m) => m.law !== null).map((m) => m.id));
|
|
253
|
+
const routed = new Set(measures.map((m) => m.id));
|
|
254
|
+
for (const id of cfg.missing) {
|
|
255
|
+
if (haveLaw.has(id)) {
|
|
256
|
+
findings.push({
|
|
257
|
+
id,
|
|
258
|
+
axis: 'presence',
|
|
259
|
+
kind: 'stale-baseline-entry',
|
|
260
|
+
detail: `now has a law section — remove it from \`missing\` in ${CONFIG_REL}`,
|
|
261
|
+
});
|
|
262
|
+
} else if (!routed.has(id)) {
|
|
263
|
+
findings.push({
|
|
264
|
+
id,
|
|
265
|
+
axis: 'presence',
|
|
266
|
+
kind: 'stale-baseline-entry',
|
|
267
|
+
detail: `is not a routed rule any more — remove it from \`missing\` in ${CONFIG_REL}`,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const kernelReported = kernelRuleIds(root).map((id) => {
|
|
274
|
+
const p = path.join(root, RULES_REL, `${id}.md`);
|
|
275
|
+
return { id, sections: fs.existsSync(p) ? measureRuleFile(id, p).sections : 0 };
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
findings.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : a.kind < b.kind ? -1 : 1));
|
|
279
|
+
overTarget.sort((a, b) => b.chars - a.chars);
|
|
280
|
+
return { findings, overTarget, measures, kernelReported, scanned: measures.length };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** Rebuild the config from the current tree. Never raises an existing ceiling. */
|
|
284
|
+
export function writeBaseline(root = REPO_ROOT, today: string): string {
|
|
285
|
+
const prev = fs.existsSync(path.join(root, CONFIG_REL))
|
|
286
|
+
? readConfig(root)
|
|
287
|
+
: { missing: [], law_exceptions: {}, body_exceptions: {} };
|
|
288
|
+
const measures = measureRouted(root);
|
|
289
|
+
const missing = measures.filter((m) => m.law === null).map((m) => m.id).sort();
|
|
290
|
+
const law_exceptions: Record<string, SizeException> = {};
|
|
291
|
+
const body_exceptions: Record<string, SizeException> = {};
|
|
292
|
+
for (const m of measures) {
|
|
293
|
+
if (m.law !== null && m.lawChars > LAW_HARD_CHARS) {
|
|
294
|
+
const old = prev.law_exceptions[m.id];
|
|
295
|
+
law_exceptions[m.id] = {
|
|
296
|
+
chars: old === undefined ? m.lawChars : Math.min(old.chars, m.lawChars),
|
|
297
|
+
owner: old?.owner ?? 'agent-config-maintainer',
|
|
298
|
+
review: old?.review ?? today,
|
|
299
|
+
reason: old?.reason ?? 'recorded at adoption; not yet reviewed',
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
if (m.bodyChars > BODY_HARD_CHARS) {
|
|
303
|
+
const old = prev.body_exceptions[m.id];
|
|
304
|
+
body_exceptions[m.id] = {
|
|
305
|
+
chars: old === undefined ? m.bodyChars : Math.min(old.chars, m.bodyChars),
|
|
306
|
+
owner: old?.owner ?? 'agent-config-maintainer',
|
|
307
|
+
review: old?.review ?? today,
|
|
308
|
+
reason: old?.reason ?? 'recorded at adoption; not yet reviewed',
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const cfg = {
|
|
313
|
+
_comment:
|
|
314
|
+
'Per-rule law-section and body ceilings (road-to-rule-laws-that-can-stand Phase 1). ' +
|
|
315
|
+
'SHRINK-ONLY on every axis: an id may LEAVE `missing`, never join it; a recorded ' +
|
|
316
|
+
'exception is a ceiling the rule may fall below and never rise above. Raising one to ' +
|
|
317
|
+
'clear a red is the config-weakening move this repo blocks by construction — ' +
|
|
318
|
+
'`--write-baseline` refuses to raise, it only re-records the smaller of old and ' +
|
|
319
|
+
'current. A rule at its exception is not thereby correct, only not worse. Regenerate ' +
|
|
320
|
+
'with `lint_rule_law_section --write-baseline`; never hand-edit the counts, but DO ' +
|
|
321
|
+
'hand-edit `owner`, `review` and `reason` — those are the review contract and the ' +
|
|
322
|
+
'generator only seeds them.',
|
|
323
|
+
measured_at_commit: prev.measured_at_commit ?? '',
|
|
324
|
+
missing,
|
|
325
|
+
law_exceptions,
|
|
326
|
+
body_exceptions,
|
|
327
|
+
};
|
|
328
|
+
const text = JSON.stringify(cfg, null, 4) + '\n';
|
|
329
|
+
fs.writeFileSync(path.join(root, CONFIG_REL), text, 'utf-8');
|
|
330
|
+
return text;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
interface Args {
|
|
334
|
+
axis: Axis;
|
|
335
|
+
quiet: boolean;
|
|
336
|
+
writeBaseline: boolean;
|
|
337
|
+
root: string;
|
|
338
|
+
selfTest: boolean;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const USAGE =
|
|
342
|
+
'usage: lint_rule_law_section [--axis presence|ceiling|body-ceiling|all] [--quiet] ' +
|
|
343
|
+
'[--write-baseline] [--root DIR] [--self-test]\n';
|
|
344
|
+
|
|
345
|
+
function parseArgs(argv: string[]): Args {
|
|
346
|
+
const out: Args = { axis: 'all', quiet: false, writeBaseline: false, root: REPO_ROOT, selfTest: false };
|
|
347
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
348
|
+
const a = argv[i] as string;
|
|
349
|
+
if (a === '-h' || a === '--help') {
|
|
350
|
+
process.stdout.write(USAGE);
|
|
351
|
+
process.exit(0);
|
|
352
|
+
} else if (a === '--quiet') {
|
|
353
|
+
out.quiet = true;
|
|
354
|
+
} else if (a === '--write-baseline') {
|
|
355
|
+
out.writeBaseline = true;
|
|
356
|
+
} else if (a === '--self-test') {
|
|
357
|
+
out.selfTest = true;
|
|
358
|
+
} else if (a === '--axis') {
|
|
359
|
+
const v = argv[i + 1];
|
|
360
|
+
if (v === undefined) {
|
|
361
|
+
process.stderr.write(USAGE);
|
|
362
|
+
process.exit(2);
|
|
363
|
+
}
|
|
364
|
+
out.axis = v as Axis;
|
|
365
|
+
i += 1;
|
|
366
|
+
} else if (a.startsWith('--axis=')) {
|
|
367
|
+
out.axis = a.slice('--axis='.length) as Axis;
|
|
368
|
+
} else if (a === '--root') {
|
|
369
|
+
const v = argv[i + 1];
|
|
370
|
+
if (v === undefined) {
|
|
371
|
+
process.stderr.write(USAGE);
|
|
372
|
+
process.exit(2);
|
|
373
|
+
}
|
|
374
|
+
out.root = path.resolve(v);
|
|
375
|
+
i += 1;
|
|
376
|
+
} else if (a.startsWith('--root=')) {
|
|
377
|
+
out.root = path.resolve(a.slice('--root='.length));
|
|
378
|
+
} else {
|
|
379
|
+
process.stderr.write(USAGE);
|
|
380
|
+
process.stderr.write(`lint_rule_law_section: error: unrecognized argument: ${a}\n`);
|
|
381
|
+
process.exit(2);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (!['presence', 'ceiling', 'body-ceiling', 'all'].includes(out.axis)) {
|
|
385
|
+
process.stderr.write(USAGE);
|
|
386
|
+
process.stderr.write(`lint_rule_law_section: error: unknown axis: ${out.axis}\n`);
|
|
387
|
+
process.exit(2);
|
|
388
|
+
}
|
|
389
|
+
return out;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Prove the gate's reading changes its verdict, not just that it read
|
|
394
|
+
* something. A `scanned:` floor says a corpus was inspected; only a rejecting
|
|
395
|
+
* case says the inspection decides anything.
|
|
396
|
+
*/
|
|
397
|
+
export function selfTest(write: (s: string) => void = (s) => process.stdout.write(s)): number {
|
|
398
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'law-selftest-'));
|
|
399
|
+
try {
|
|
400
|
+
fs.mkdirSync(path.join(tmp, 'src', 'rules'), { recursive: true });
|
|
401
|
+
fs.mkdirSync(path.join(tmp, 'src', 'config'), { recursive: true });
|
|
402
|
+
fs.mkdirSync(path.join(tmp, 'dist'), { recursive: true });
|
|
403
|
+
fs.writeFileSync(
|
|
404
|
+
path.join(tmp, ROUTER_REL),
|
|
405
|
+
JSON.stringify({ kernel: [], tier_1: [{ id: 'lawless' }, { id: 'lawful' }], tier_2: [] }),
|
|
406
|
+
);
|
|
407
|
+
fs.writeFileSync(
|
|
408
|
+
path.join(tmp, CONFIG_REL),
|
|
409
|
+
JSON.stringify({ missing: [], law_exceptions: {}, body_exceptions: {} }),
|
|
410
|
+
);
|
|
411
|
+
fs.writeFileSync(path.join(tmp, 'src/rules/lawless.md'), '---\nx: 1\n---\n\n# Lawless\n\nProse.\n');
|
|
412
|
+
fs.writeFileSync(
|
|
413
|
+
path.join(tmp, 'src/rules/lawful.md'),
|
|
414
|
+
'---\nx: 1\n---\n\n# Lawful\n\n## The Iron Law\n\nNEVER DO THE THING.\n',
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
const rejecting = lint(tmp, 'presence');
|
|
418
|
+
const accepting = (() => {
|
|
419
|
+
fs.writeFileSync(
|
|
420
|
+
path.join(tmp, CONFIG_REL),
|
|
421
|
+
JSON.stringify({ missing: ['lawless'], law_exceptions: {}, body_exceptions: {} }),
|
|
422
|
+
);
|
|
423
|
+
return lint(tmp, 'presence');
|
|
424
|
+
})();
|
|
425
|
+
const ok =
|
|
426
|
+
rejecting.findings.length === 1 &&
|
|
427
|
+
rejecting.findings[0]?.kind === 'no-law-section' &&
|
|
428
|
+
accepting.findings.length === 0;
|
|
429
|
+
write(
|
|
430
|
+
ok
|
|
431
|
+
? '✅ self-test: 1 rejecting case, 1 accepting case — the reading decides the verdict\n'
|
|
432
|
+
: `❌ self-test FAILED: rejecting=${rejecting.findings.length} accepting=${accepting.findings.length}\n`,
|
|
433
|
+
);
|
|
434
|
+
return ok ? 0 : 1;
|
|
435
|
+
} finally {
|
|
436
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export function main(argv: string[] | null = null): number {
|
|
441
|
+
const args = parseArgs(argv ?? process.argv.slice(2));
|
|
442
|
+
if (args.selfTest) {
|
|
443
|
+
return selfTest();
|
|
444
|
+
}
|
|
445
|
+
if (args.writeBaseline) {
|
|
446
|
+
const today = asOf().toISOString().slice(0, 10);
|
|
447
|
+
writeBaseline(args.root, today);
|
|
448
|
+
process.stdout.write(`wrote ${CONFIG_REL}\n`);
|
|
449
|
+
return 0;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
let res: LintResult;
|
|
453
|
+
try {
|
|
454
|
+
res = lint(args.root, args.axis);
|
|
455
|
+
} catch (exc) {
|
|
456
|
+
process.stderr.write(`❌ lint_rule_law_section: ${(exc as Error).message}\n`);
|
|
457
|
+
return 3;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Outside the `--quiet` guard on purpose: CI passes `--quiet`, and a gate
|
|
461
|
+
// whose count disappears under the flag reads as silent to the coverage
|
|
462
|
+
// census.
|
|
463
|
+
try {
|
|
464
|
+
reportScanned({
|
|
465
|
+
gate: 'lint_rule_law_section',
|
|
466
|
+
scanned: res.scanned,
|
|
467
|
+
units: 'routed rule(s)',
|
|
468
|
+
roots: [RULES_REL, ROUTER_REL],
|
|
469
|
+
});
|
|
470
|
+
} catch (exc) {
|
|
471
|
+
if (exc instanceof DeadScopeError) {
|
|
472
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
473
|
+
return 1;
|
|
474
|
+
}
|
|
475
|
+
throw exc;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (!args.quiet) {
|
|
479
|
+
const withLaw = res.measures.filter((m) => m.law !== null).length;
|
|
480
|
+
process.stdout.write(
|
|
481
|
+
`law sections: ${withLaw}/${res.measures.length} routed rules; ` +
|
|
482
|
+
`kernel reported separately (${res.kernelReported.filter((k) => k.sections > 0).length}/` +
|
|
483
|
+
`${res.kernelReported.length} carry one, write-denied so never failed here)\n`,
|
|
484
|
+
);
|
|
485
|
+
for (const t of res.overTarget) {
|
|
486
|
+
process.stdout.write(
|
|
487
|
+
` over target (${LAW_TARGET_CHARS}), not a failure: ${t.id} — ${t.chars} chars\n`,
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (res.findings.length > 0) {
|
|
493
|
+
for (const f of res.findings) {
|
|
494
|
+
process.stderr.write(`❌ ${f.id} [${f.axis}/${f.kind}] ${f.detail}\n`);
|
|
495
|
+
}
|
|
496
|
+
process.stderr.write(`❌ lint_rule_law_section: ${res.findings.length} finding(s)\n`);
|
|
497
|
+
return 1;
|
|
498
|
+
}
|
|
499
|
+
if (!args.quiet) {
|
|
500
|
+
process.stdout.write('✅ lint_rule_law_section clean\n');
|
|
501
|
+
}
|
|
502
|
+
return 0;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function isCliEntry(): boolean {
|
|
506
|
+
if (process.argv[1] === undefined) return false;
|
|
507
|
+
if (import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) return true;
|
|
508
|
+
try {
|
|
509
|
+
return fs.realpathSync(fileURLToPath(import.meta.url)) === fs.realpathSync(path.resolve(process.argv[1]));
|
|
510
|
+
} catch {
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (isCliEntry()) {
|
|
516
|
+
process.exit(main());
|
|
517
|
+
}
|