@event4u/agent-config 16.1.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 +98 -257
- 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 +5 -5
- 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/commands/work.md +4 -0
- 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/autonomy-mechanics.md +4 -8
- 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/ui-audit-gate.md +1 -2
- 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/accessibility-auditor/data/aria-patterns.csv +1 -1
- package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +2 -2
- 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-review/SKILL.md +30 -4
- package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
- package/dist/agent-src/skills/design-system-capture/references/import-procedure.md +1 -1
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +18 -3
- package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
- 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/markitdown/SKILL.md +4 -0
- package/dist/agent-src/skills/monorepo-workspace/SKILL.md +1 -1
- package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
- 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 +25 -2
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +30 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +302 -21
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
- package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/dist/cli/python/knowledge_ingest.js +1 -1
- package/dist/cli/python/workspace_drive.js +1 -1
- 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-UNCRJBT3.js → chunk-JQVYKIZA.js} +41 -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-YUM5KURG.js → chunk-KR6EM23A.js} +235 -1455
- package/dist/cli-delegate/{chunk-LTYVIY76.js → chunk-LIYTLH6O.js} +1 -1
- package/dist/cli-delegate/chunk-NQ7WT43P.js +2729 -0
- 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-HDVVGPQW.js → chunk-RQNNW7IF.js} +1405 -567
- package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-S4UMZNES.js} +1 -1
- package/dist/cli-delegate/{chunk-6KM6ORRY.js → chunk-S4YWNRKO.js} +24 -8
- package/dist/cli-delegate/{chunk-GJCBB2UG.js → chunk-TRI6DQZ6.js} +3054 -325
- 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 +1862 -809
- 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 +132 -80
- 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 +7972 -5186
- package/dist/hooks/dispatch.sha256 +1 -0
- 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 +2226 -1055
- 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 +40913 -34865
- 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 +29 -0
- package/dist/shared/skillRanking.js +74 -13
- package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
- package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
- package/dist/ui/index.html +2 -2
- package/docs/contracts/settings-classes.md +10 -6
- package/package.json +4 -4
- 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/app_spec.ts +10 -4
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +302 -21
- package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
- package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/src/config/agent-settings.template.yml +91 -22
- package/src/config/ci-local-parity.yml +18 -0
- package/src/config/continuity-surface.json +9 -2
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +327 -3
- package/src/config/gate-violation-baselines.json +21 -9
- package/src/config/hook-bundle-budget.json +25 -0
- package/src/config/hook-latency-budget.json +21 -0
- package/src/config/hook-token-budget.json +8 -3
- package/src/config/host-instruction-limits.json +33 -0
- package/src/config/metric-registry.yml +114 -0
- package/src/config/placeholder-drift-budget.json +10 -0
- package/src/config/routing-coverage-seed.json +38 -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 +28 -90
- package/src/scripts/_cli/cmd_doctor_neighbours.ts +293 -0
- package/src/scripts/_cli/doctor_execution.ts +100 -17
- package/src/scripts/_cli/doctor_network_posture.ts +226 -0
- package/src/scripts/_dispatch.bash +16 -2
- package/src/scripts/_lib/agent_settings.ts +11 -0
- package/src/scripts/_lib/body_portable.ts +102 -0
- package/src/scripts/_lib/cc_transcript.ts +132 -1
- package/src/scripts/_lib/claude_settings_hooks.ts +10 -8
- package/src/scripts/_lib/concern_sla_window.ts +200 -0
- 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/forge_protection.ts +114 -0
- package/src/scripts/_lib/forge_reader.ts +558 -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_env_write.ts +111 -0
- 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/llm_proposer_transport.ts +14 -4
- 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/obligations.ts +119 -8
- package/src/scripts/_lib/probe_inputs.ts +271 -0
- package/src/scripts/_lib/prompt_shape.ts +46 -0
- package/src/scripts/_lib/read_surface_scan.ts +322 -0
- package/src/scripts/_lib/release_highlights.ts +2 -1
- package/src/scripts/_lib/release_migration_gate.ts +19 -3
- package/src/scripts/_lib/reserved_name_sweep.ts +73 -0
- package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
- 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/shell_write_shapes.ts +108 -0
- package/src/scripts/_lib/skill_origin.ts +254 -0
- package/src/scripts/_lib/structural_hiding.ts +622 -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 +600 -4
- package/src/scripts/_lib/turn_end_transcript.ts +216 -0
- package/src/scripts/_lib/verification_command.ts +324 -0
- package/src/scripts/_lib/verification_evidence.ts +647 -0
- package/src/scripts/_lib/verify_clause.ts +280 -0
- 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/ai_team/team_dispatch.ts +4 -0
- package/src/scripts/audit_skill_overlap.ts +261 -0
- package/src/scripts/before_complete_hook.ts +573 -25
- package/src/scripts/bench_hook_latency.ts +412 -5
- package/src/scripts/bench_touched_file_quality.ts +238 -0
- package/src/scripts/cache_realization_report.ts +61 -1
- package/src/scripts/check_argument_hint.ts +287 -0
- package/src/scripts/check_claims.ts +125 -1
- package/src/scripts/check_enforcement_coverage.ts +38 -9
- package/src/scripts/check_enforcement_matrix.ts +621 -22
- package/src/scripts/check_estate_count.ts +130 -153
- package/src/scripts/check_finding_dispositions.ts +138 -7
- package/src/scripts/check_generator_sync.ts +71 -0
- package/src/scripts/check_hook_bundle_composition.ts +370 -0
- package/src/scripts/check_host_docs_digest.ts +786 -0
- package/src/scripts/check_host_format_column.ts +559 -0
- package/src/scripts/check_input_references.ts +228 -0
- package/src/scripts/check_inputs_parity.ts +235 -0
- package/src/scripts/check_kernel_edit_ratified.ts +89 -9
- package/src/scripts/check_kill_switch_table.ts +294 -0
- package/src/scripts/check_memory.ts +7 -1
- package/src/scripts/check_placeholder_drift.ts +221 -0
- package/src/scripts/check_read_surface_coverage.ts +515 -0
- package/src/scripts/check_release_highlights.ts +10 -0
- package/src/scripts/check_release_holds.ts +2 -1
- package/src/scripts/check_release_pr_shape.ts +38 -15
- package/src/scripts/check_routing_coverage.ts +378 -11
- package/src/scripts/check_standing_rule_delivery.ts +33 -13
- package/src/scripts/check_static_parity.ts +17 -0
- package/src/scripts/check_verify_expectation_delta.ts +521 -0
- package/src/scripts/ci_settle.ts +145 -19
- package/src/scripts/closure_scan.ts +115 -1
- 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 +90 -14
- package/src/scripts/condense.ts +14 -27
- package/src/scripts/estate_exemption_shape.ts +202 -0
- package/src/scripts/external_sources_denylist.json +29 -4
- package/src/scripts/generate_host_cost_table.ts +1 -1
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +192 -14
- package/src/scripts/hooks/block_config_weakening.ts +294 -4
- package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
- package/src/scripts/hooks/block_plumbing_writes.ts +342 -0
- package/src/scripts/hooks/bundle_integrity.ts +291 -0
- package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
- package/src/scripts/hooks/code_graph_context_hook.ts +253 -25
- package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
- package/src/scripts/hooks/concern_failure_policy.ts +280 -0
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/hooks/concern_timings.ts +94 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
- package/src/scripts/hooks/design_pass_hook.ts +100 -9
- package/src/scripts/hooks/design_slop_hook.ts +1 -2
- package/src/scripts/hooks/dispatch_hook.ts +77 -71
- package/src/scripts/hooks/edit_shape_hook.ts +1 -2
- package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
- package/src/scripts/hooks/evidence_independence.ts +1 -2
- package/src/scripts/hooks/exit_codes.ts +129 -0
- package/src/scripts/hooks/host_lowering.json +1 -0
- package/src/scripts/hooks/host_lowering.ts +146 -3
- package/src/scripts/hooks/host_lowering.yaml +262 -46
- package/src/scripts/hooks/host_semantics.ts +30 -2
- package/src/scripts/hooks/injection_budget.ts +9 -2
- package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
- package/src/scripts/hooks/journal_record_hook.ts +1 -2
- package/src/scripts/hooks/memory_recall_hook.ts +1 -2
- package/src/scripts/hooks/obligation_settle_hook.ts +151 -5
- package/src/scripts/hooks/one_question_per_ask_hook.ts +43 -6
- package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
- package/src/scripts/hooks/payload_stub.ts +10 -1
- package/src/scripts/hooks/permission_gate.ts +1 -2
- package/src/scripts/hooks/push_settle_hook.ts +1 -3
- package/src/scripts/hooks/replay_hook.ts +2 -1
- package/src/scripts/hooks/reread_guard_hook.ts +1 -2
- package/src/scripts/hooks/review_baseline_hook.ts +1 -2
- package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
- package/src/scripts/hooks/rule_inject_hook.ts +765 -68
- package/src/scripts/hooks/run_continuation_hook.ts +13 -15
- package/src/scripts/hooks/skill_route_hook.ts +1 -2
- package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
- package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
- package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
- package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
- package/src/scripts/hooks/table_fingerprint.ts +70 -0
- package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
- package/src/scripts/hooks/telemetry_usage_hook.ts +65 -10
- package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
- package/src/scripts/hooks/turn_end_gate_hook.ts +421 -324
- package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
- package/src/scripts/hooks_status.ts +112 -0
- package/src/scripts/injection_scan_hook.ts +7 -0
- package/src/scripts/install.ts +75 -189
- 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_deny_text.ts +419 -0
- package/src/scripts/lint_exit_codes.ts +262 -0
- package/src/scripts/lint_hook_manifest.ts +108 -0
- package/src/scripts/lint_major_migration_sections.ts +156 -26
- package/src/scripts/lint_pack_boundaries.ts +63 -5
- package/src/scripts/lint_roadmap_blockers.ts +78 -12
- package/src/scripts/lint_rule_law_section.ts +517 -0
- package/src/scripts/lint_skill_descriptions.ts +1 -1
- package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
- 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/mcp_server/prompts.ts +142 -1
- package/src/scripts/measure_routing_signal.ts +54 -16
- package/src/scripts/measure_skill_ranker_baseline.ts +515 -53
- package/src/scripts/measure_turn_end_gate.ts +193 -0
- package/src/scripts/memory_learn_hook.ts +91 -6
- package/src/scripts/memory_lookup.ts +1 -1
- package/src/scripts/model_rule_injection.ts +52 -5
- package/src/scripts/prepack-check.mjs +53 -0
- package/src/scripts/project_thin_rules.ts +34 -327
- package/src/scripts/release_publication.ts +13 -1
- package/src/scripts/report_evidence_temperature.ts +657 -0
- package/src/scripts/report_installed_rule_links.ts +157 -0
- package/src/scripts/report_invocation_surface.ts +383 -0
- package/src/scripts/report_label_agreement.ts +233 -0
- package/src/scripts/report_obligation_carriers.ts +699 -0
- package/src/scripts/report_obligation_mechanism.ts +230 -0
- package/src/scripts/report_obligation_writer_split.ts +236 -0
- package/src/scripts/report_single_token_triggers.ts +277 -0
- package/src/scripts/report_skill_menu_census.ts +208 -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/roadmap_verify_share.ts +213 -0
- package/src/scripts/rule_activation_census.ts +5 -2
- package/src/scripts/rule_trigger_eval.ts +9 -9
- package/src/scripts/schemas/command.schema.json +44 -0
- package/src/scripts/schemas/review-findings.schema.json +7 -2
- package/src/scripts/schemas/rule.schema.json +5 -0
- package/src/scripts/schemas/skill.schema.json +44 -0
- package/src/scripts/self_review_gate.ts +421 -17
- package/src/scripts/semantic_parity_census.ts +314 -0
- package/src/scripts/session_eol_report.ts +54 -1
- package/src/scripts/skill_tools/score_skill_relevance.ts +290 -25
- package/src/scripts/sweep_dead_scan_roots.ts +1 -1
- 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 -7
- package/src/scripts/trigger_eval_rotation.ts +122 -27
- package/src/scripts/ui_conformance_probe.ts +156 -4
- package/src/scripts/update_prices.ts +85 -3
- package/src/scripts/write_bundle_digest.mjs +74 -0
- package/src/server/schemas/settings.ts +41 -0
- package/src/shared/skillRanking.ts +117 -11
- package/src/templates/consumer-settings/ONBOARDING.md +1 -0
- package/dist/cli-delegate/chunk-FXYUYU4N.js +0 -4416
- package/dist/cli-delegate/chunk-QJWAUMJW.js +0 -1072
- package/dist/cli-delegate/chunk-YDPILCOL.js +0 -152
- package/dist/ui/assets/index-DH1xc62v.js +0 -29
- package/dist/ui/assets/index-jSfMW8_t.css +0 -1
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a host actually loads, measured in the host's own unit (step 0.1 of
|
|
3
|
+
* `road-to-a-rule-carrier-that-works-outside-the-repo`).
|
|
4
|
+
*
|
|
5
|
+
* THE QUESTION NOTHING IN THIS TREE COULD ANSWER. Every existing figure for the
|
|
6
|
+
* installed layer counts raw bytes of whole files: `censusRuleDir` sums
|
|
7
|
+
* `statSync().size`, `charsAtRoot` does the same for a directory, and
|
|
8
|
+
* `check_preamble_payload_budget` reads both. A host does not load frontmatter
|
|
9
|
+
* as instruction — it is the routing surface, which the router has already
|
|
10
|
+
* read — and it does not render HTML comments, which are authoring scaffolding.
|
|
11
|
+
* So the number a reader wants when they ask "how much instruction is this
|
|
12
|
+
* consumer under" has never been computed here.
|
|
13
|
+
*
|
|
14
|
+
* THE NUMBER IS THEREFORE NOT COMPARABLE WITH ANY PUBLISHED BASELINE, and that
|
|
15
|
+
* is stated here rather than discovered by someone holding the two side by
|
|
16
|
+
* side. `chars` below is strictly smaller than the byte figures in
|
|
17
|
+
* `budgets.yml`, `rule-activation-census.json` or the payload ratchet, and the
|
|
18
|
+
* gap is not a constant — it is whatever that particular corpus spends on
|
|
19
|
+
* frontmatter and comments.
|
|
20
|
+
*
|
|
21
|
+
* ONE LIBRARY, FOUR CONSUMERS. The report CLI is the only one wired in this
|
|
22
|
+
* change (Phase 0 makes no behavior change), and the shape is the reason the
|
|
23
|
+
* other three named in step 0.1 — CI, `doctor`, the upgrade receipt — can use
|
|
24
|
+
* it without a second reader: every input is a PARAMETER. `home`, `projectRoot`
|
|
25
|
+
* and `manifestPath` are supplied by the caller, never read off the process, so
|
|
26
|
+
* a test, a temporary `HOME` and a real machine are the same call with
|
|
27
|
+
* different arguments. That is also what makes the step's "install into a
|
|
28
|
+
* temporary HOME" reachable at all, and it is the fact that refutes
|
|
29
|
+
* `check_standing_rule_delivery`'s header: a user-scope install IS observable
|
|
30
|
+
* from CI, because the installer takes `HOME` from the environment.
|
|
31
|
+
*
|
|
32
|
+
* WHAT `package_owned` AND `foreign` MEAN, because the honest answer is three
|
|
33
|
+
* valued and the report has two columns. `recordedOwnership` distinguishes
|
|
34
|
+
* `recorded-unchanged`, `recorded-modified` and `unknown`, where `unknown` is
|
|
35
|
+
* explicitly "this tree cannot say" rather than "foreign". The report folds the
|
|
36
|
+
* first two into `package_owned` — both are files the manifest claims — and
|
|
37
|
+
* reports the third as `foreign`, which therefore reads "not claimed by this
|
|
38
|
+
* install", never "written by someone else". A machine with no manifest reports
|
|
39
|
+
* every file foreign, which is the correct reading of no evidence and is why
|
|
40
|
+
* {@link InstalledLayerReport.manifest_present} is in the output.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
import * as fs from 'node:fs';
|
|
44
|
+
import * as path from 'node:path';
|
|
45
|
+
import { fileURLToPath } from 'node:url';
|
|
46
|
+
|
|
47
|
+
import {
|
|
48
|
+
GLOBAL_RULE_DIRS,
|
|
49
|
+
PROJECT_RULE_DIRS,
|
|
50
|
+
globalRuleLayerPath,
|
|
51
|
+
} from '../../install/globalRuleLayers.js';
|
|
52
|
+
import { NO_RECORDED_HASHES, readRecordedHashes } from '../../install/recordedOwnership.js';
|
|
53
|
+
import { ruleBody } from './rule_law_section.js';
|
|
54
|
+
|
|
55
|
+
/** How many files the per-directory ranking carries. Step 0.1 fixes it at 20. */
|
|
56
|
+
export const TOP_FILES = 20;
|
|
57
|
+
|
|
58
|
+
/** Which layer a directory belongs to — the two the host unions when it loads. */
|
|
59
|
+
export type LayerScope = 'global' | 'project';
|
|
60
|
+
|
|
61
|
+
/** One rule file, measured the way the host loads it. */
|
|
62
|
+
export interface RuleFileReading {
|
|
63
|
+
/** Basename, which is what a reader opening the directory sees. */
|
|
64
|
+
readonly file: string;
|
|
65
|
+
/** Characters AFTER the frontmatter and block-comment strip. */
|
|
66
|
+
readonly chars: number;
|
|
67
|
+
/** False when the file carries a `paths:` key — then the host loads it on a path match. */
|
|
68
|
+
readonly unconditional: boolean;
|
|
69
|
+
/** True when the installed-tools manifest records this path, in either state. */
|
|
70
|
+
readonly package_owned: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** One host rule directory, which is the unit step 0.1 asks the report to be in. */
|
|
74
|
+
export interface LayerReading {
|
|
75
|
+
/** Tool id, as `GLOBAL_RULE_DIRS` spells it. */
|
|
76
|
+
readonly host: string;
|
|
77
|
+
readonly scope: LayerScope;
|
|
78
|
+
readonly dir: string;
|
|
79
|
+
/** False when the directory does not exist — a real answer, never an error. */
|
|
80
|
+
readonly present: boolean;
|
|
81
|
+
readonly files: number;
|
|
82
|
+
readonly unconditional: number;
|
|
83
|
+
readonly chars: number;
|
|
84
|
+
readonly package_owned: number;
|
|
85
|
+
readonly foreign: number;
|
|
86
|
+
/**
|
|
87
|
+
* The same split as `package_owned` / `foreign`, in CHARACTERS.
|
|
88
|
+
*
|
|
89
|
+
* Both axes are kept because they answer different questions and the file
|
|
90
|
+
* counts alone cannot answer the one step 1.4 asks. "How many of the files
|
|
91
|
+
* does this install claim" is a reaping question; "how much of what the
|
|
92
|
+
* host loads does this install account for" is a budget question, and one
|
|
93
|
+
* 30,000-character rule against twenty 200-character ones makes the two
|
|
94
|
+
* readings disagree by an order of magnitude. The limit rows below are
|
|
95
|
+
* built from the character axis for exactly that reason.
|
|
96
|
+
*/
|
|
97
|
+
readonly package_owned_chars: number;
|
|
98
|
+
readonly foreign_chars: number;
|
|
99
|
+
/** The {@link TOP_FILES} largest, by the SAME measure as `chars`. */
|
|
100
|
+
readonly top: readonly RuleFileReading[];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The fraction of a published limit at which the report warns.
|
|
105
|
+
*
|
|
106
|
+
* 0.8 is step 1.4's number, not a tuned one. It is a WARNING threshold and
|
|
107
|
+
* never a gate: crossing it changes what the report says and nothing else, so
|
|
108
|
+
* no install fails here.
|
|
109
|
+
*/
|
|
110
|
+
export const LIMIT_WARN_FRACTION = 0.8;
|
|
111
|
+
|
|
112
|
+
/** What this tree can say about a host's published instruction budget. */
|
|
113
|
+
export type HostLimitKind = 'vendor-published' | 'reported' | 'unpublished';
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* One host, measured against its published limit — step 3.5's per-host row.
|
|
117
|
+
*
|
|
118
|
+
* The row is per HOST and not per directory, because the limit applies to what
|
|
119
|
+
* the host loads and the host unions its global and project layers. A host
|
|
120
|
+
* whose two directories are each comfortably under the limit can still be over
|
|
121
|
+
* it together, and a per-directory row would never show that.
|
|
122
|
+
*/
|
|
123
|
+
export interface HostLimitReading {
|
|
124
|
+
readonly host: string;
|
|
125
|
+
/** `null` when this tree records no limit — which fires no warning. */
|
|
126
|
+
readonly limit: number | null;
|
|
127
|
+
readonly kind: HostLimitKind;
|
|
128
|
+
/** Every scope for this host, summed. */
|
|
129
|
+
readonly chars: number;
|
|
130
|
+
readonly package_owned_chars: number;
|
|
131
|
+
readonly foreign_chars: number;
|
|
132
|
+
/** `chars / limit`, or `null` when there is no limit to be a fraction of. */
|
|
133
|
+
readonly fraction: number | null;
|
|
134
|
+
/**
|
|
135
|
+
* True only when a limit EXISTS and the combined total reaches
|
|
136
|
+
* {@link LIMIT_WARN_FRACTION} of it — 80 %, not 100 %. The field is an
|
|
137
|
+
* early warning, which is the whole point of a fraction below 1.
|
|
138
|
+
*
|
|
139
|
+
* An absent limit never warns. That is the conservative direction and it is
|
|
140
|
+
* deliberate: a warning derived from a limit nobody recorded would be a
|
|
141
|
+
* fabricated finding, which is worse than a silent row a reader can see is
|
|
142
|
+
* unmeasured.
|
|
143
|
+
*/
|
|
144
|
+
readonly warn: boolean;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface InstalledLayerReport {
|
|
148
|
+
readonly home: string;
|
|
149
|
+
readonly project_root: string;
|
|
150
|
+
/** `claude --version`, or `unknown`. Never fabricated. */
|
|
151
|
+
readonly host_version: string;
|
|
152
|
+
/** False when no installed-tools manifest resolved — then every file reads foreign. */
|
|
153
|
+
readonly manifest_present: boolean;
|
|
154
|
+
readonly layers: readonly LayerReading[];
|
|
155
|
+
/** One row per host, against its published limit. Step 3.5. */
|
|
156
|
+
readonly limits: readonly HostLimitReading[];
|
|
157
|
+
/** Every layer summed, which is what the host loads when it unions the two scopes. */
|
|
158
|
+
readonly totals: {
|
|
159
|
+
readonly files: number;
|
|
160
|
+
readonly unconditional: number;
|
|
161
|
+
readonly chars: number;
|
|
162
|
+
readonly package_owned: number;
|
|
163
|
+
readonly foreign: number;
|
|
164
|
+
readonly package_owned_chars: number;
|
|
165
|
+
readonly foreign_chars: number;
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Whether the host loads this file on every session rather than on a path match.
|
|
171
|
+
*
|
|
172
|
+
* `paths:` at the start of a line is the whole test, and it is the host's own
|
|
173
|
+
* convention rather than this package's: `claudeRuleRewrite.ts` states it from
|
|
174
|
+
* the emitting side — "with it a rule loads on a path match, without it the
|
|
175
|
+
* rule loads unconditionally". `rule_activation_census.projection_reading` asks
|
|
176
|
+
* the installed tree the same question with the same regex, so a file counted
|
|
177
|
+
* unconditional here is counted unconditional there.
|
|
178
|
+
*
|
|
179
|
+
* SOURCE-SIDE CLASSIFICATION IS A DIFFERENT QUESTION and deliberately not used.
|
|
180
|
+
* `classify_rule` reads `alwaysApply` and `type: always` to tell `always` from
|
|
181
|
+
* `unconditional`, which matters when you are deciding what to emit. This
|
|
182
|
+
* reads what WAS emitted, where the distinction has already been spent: an
|
|
183
|
+
* Iron-Law rule and an untriggered tier-2 rule both arrive without `paths:` and
|
|
184
|
+
* both load every session, which is the only property the host acts on.
|
|
185
|
+
*/
|
|
186
|
+
export function isUnconditional(text: string): boolean {
|
|
187
|
+
return !/^paths:/m.test(text);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Measure one directory.
|
|
192
|
+
*
|
|
193
|
+
* `.md` only, matching `censusRuleDir` and `topRulesByCost`, so the FILE count
|
|
194
|
+
* is comparable with the existing byte censuses even though the character count
|
|
195
|
+
* is not. Cursor's `.mdc` tree is therefore read as zero files, which is the
|
|
196
|
+
* same blind spot every other census in this tree has and is not fixed here —
|
|
197
|
+
* fixing it in one reader and not the others would make two counts that
|
|
198
|
+
* disagree, which is worse than one that is openly partial.
|
|
199
|
+
*/
|
|
200
|
+
export function readLayer(
|
|
201
|
+
host: string,
|
|
202
|
+
scope: LayerScope,
|
|
203
|
+
dir: string,
|
|
204
|
+
recorded: ReadonlyMap<string, string | null>,
|
|
205
|
+
): LayerReading {
|
|
206
|
+
const empty = {
|
|
207
|
+
files: 0,
|
|
208
|
+
unconditional: 0,
|
|
209
|
+
chars: 0,
|
|
210
|
+
package_owned: 0,
|
|
211
|
+
foreign: 0,
|
|
212
|
+
package_owned_chars: 0,
|
|
213
|
+
foreign_chars: 0,
|
|
214
|
+
top: [],
|
|
215
|
+
};
|
|
216
|
+
let names: string[];
|
|
217
|
+
try {
|
|
218
|
+
names = fs.readdirSync(dir);
|
|
219
|
+
} catch {
|
|
220
|
+
return { host, scope, dir, present: false, ...empty };
|
|
221
|
+
}
|
|
222
|
+
const readings: RuleFileReading[] = [];
|
|
223
|
+
for (const name of names.sort()) {
|
|
224
|
+
if (!name.endsWith('.md')) continue;
|
|
225
|
+
const abs = path.join(dir, name);
|
|
226
|
+
let text: string;
|
|
227
|
+
try {
|
|
228
|
+
const st = fs.statSync(abs);
|
|
229
|
+
if (!st.isFile()) continue;
|
|
230
|
+
text = fs.readFileSync(abs, 'utf-8');
|
|
231
|
+
} catch {
|
|
232
|
+
continue; // a file that cannot be read is not a file the host loads
|
|
233
|
+
}
|
|
234
|
+
// RECORDED-BUT-MODIFIED IS STILL RECORDED. The report's two columns
|
|
235
|
+
// answer "does this install claim the file"; `classifyOwnership` answers
|
|
236
|
+
// the finer question the upgrade path needs — whether the bytes still
|
|
237
|
+
// match — and folding that in here would give a column three meanings.
|
|
238
|
+
const owned = recorded.has(abs);
|
|
239
|
+
readings.push({
|
|
240
|
+
file: name,
|
|
241
|
+
// THE STRIP IS THE PROJECTOR'S OWN. `ruleBody` is what
|
|
242
|
+
// `project_thin_rules` and the delivery carrier both call, so "what
|
|
243
|
+
// a host loads" and "what a delivery carries" cannot drift into two
|
|
244
|
+
// spellings of the same measurement.
|
|
245
|
+
chars: ruleBody(text).length,
|
|
246
|
+
unconditional: isUnconditional(text),
|
|
247
|
+
package_owned: owned,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
const top = [...readings]
|
|
251
|
+
// RANKED BY THE SAME MEASURE AS THE TOTAL, which is why
|
|
252
|
+
// `preamble_byte_census.topRulesByCost` is not reused here: it ranks by
|
|
253
|
+
// `statSync().size`, so its list and this total would be in different
|
|
254
|
+
// units and the largest file by one could be absent from the other.
|
|
255
|
+
.sort((a, b) => b.chars - a.chars || a.file.localeCompare(b.file))
|
|
256
|
+
.slice(0, TOP_FILES);
|
|
257
|
+
return {
|
|
258
|
+
host,
|
|
259
|
+
scope,
|
|
260
|
+
dir,
|
|
261
|
+
present: true,
|
|
262
|
+
files: readings.length,
|
|
263
|
+
unconditional: readings.filter((r) => r.unconditional).length,
|
|
264
|
+
chars: readings.reduce((n, r) => n + r.chars, 0),
|
|
265
|
+
package_owned: readings.filter((r) => r.package_owned).length,
|
|
266
|
+
foreign: readings.filter((r) => !r.package_owned).length,
|
|
267
|
+
package_owned_chars: readings.reduce((n, r) => n + (r.package_owned ? r.chars : 0), 0),
|
|
268
|
+
foreign_chars: readings.reduce((n, r) => n + (r.package_owned ? 0 : r.chars), 0),
|
|
269
|
+
top,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** The shape of one entry in `src/config/host-instruction-limits.json`. */
|
|
274
|
+
interface RawHostLimit {
|
|
275
|
+
readonly limit?: unknown;
|
|
276
|
+
readonly kind?: unknown;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Where the limits live, derived from this module rather than from `cwd`.
|
|
281
|
+
*
|
|
282
|
+
* The two `..` hops climb from `src/scripts/_lib` to `src/`, which is where
|
|
283
|
+
* `config/` lives — NOT to the package root, though the comment said so until
|
|
284
|
+
* 2026-10-05. The result is correct and the arithmetic stated for it was not,
|
|
285
|
+
* which is the half a future reader would trust when changing the hops.
|
|
286
|
+
*
|
|
287
|
+
* `fileURLToPath`, NOT `new URL(...).pathname` — the claimed parity with
|
|
288
|
+
* `default_template_path()` was only half true until 2026-10-05. A `pathname`
|
|
289
|
+
* percent-encodes, so a package under a directory with a space resolved to
|
|
290
|
+
* `/Users/x/My%20Projects/...`, and on Windows it yields a leading-slash drive
|
|
291
|
+
* path. Either way the read fails, `loadHostInstructionLimits` swallows it and
|
|
292
|
+
* returns an empty map, and EVERY host then reads `unpublished` with `warn`
|
|
293
|
+
* false — the budget measurement degrading to "not measured" with no signal,
|
|
294
|
+
* which is the one failure mode this file's own limit table was written to
|
|
295
|
+
* avoid.
|
|
296
|
+
*/
|
|
297
|
+
export function defaultHostLimitsPath(): string {
|
|
298
|
+
return path.join(
|
|
299
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
300
|
+
'..',
|
|
301
|
+
'..',
|
|
302
|
+
'config',
|
|
303
|
+
'host-instruction-limits.json',
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Read the published-limit table.
|
|
309
|
+
*
|
|
310
|
+
* TOLERANT IN ONE DIRECTION ONLY. An unreadable or malformed file yields an
|
|
311
|
+
* EMPTY table, so every host reads `unpublished` and nothing warns — a reading
|
|
312
|
+
* this tree cannot stand behind must never become a warning it can. A present
|
|
313
|
+
* entry whose `limit` is not a positive finite number is treated the same way,
|
|
314
|
+
* because a `0` or a string would otherwise make every install look saturated.
|
|
315
|
+
*/
|
|
316
|
+
export function loadHostInstructionLimits(
|
|
317
|
+
file: string = defaultHostLimitsPath(),
|
|
318
|
+
): ReadonlyMap<string, { limit: number | null; kind: HostLimitKind }> {
|
|
319
|
+
const out = new Map<string, { limit: number | null; kind: HostLimitKind }>();
|
|
320
|
+
let raw: unknown;
|
|
321
|
+
try {
|
|
322
|
+
raw = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
323
|
+
} catch {
|
|
324
|
+
return out; // no table ⇒ no limits ⇒ no warnings
|
|
325
|
+
}
|
|
326
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return out;
|
|
327
|
+
for (const [host, v] of Object.entries(raw as Record<string, unknown>)) {
|
|
328
|
+
if (host.startsWith('_')) continue; // `_comment` and friends are not hosts
|
|
329
|
+
if (typeof v !== 'object' || v === null || Array.isArray(v)) continue;
|
|
330
|
+
const e = v as RawHostLimit;
|
|
331
|
+
const n = e.limit;
|
|
332
|
+
const limit = typeof n === 'number' && Number.isFinite(n) && n > 0 ? n : null;
|
|
333
|
+
const k = e.kind;
|
|
334
|
+
const kind: HostLimitKind =
|
|
335
|
+
limit === null
|
|
336
|
+
? 'unpublished'
|
|
337
|
+
: k === 'vendor-published' || k === 'reported'
|
|
338
|
+
? k
|
|
339
|
+
: 'reported'; // a cited figure with no stated kind is the weaker one
|
|
340
|
+
out.set(host, { limit, kind });
|
|
341
|
+
}
|
|
342
|
+
return out;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Fold the per-directory readings into one row per host, against its limit.
|
|
347
|
+
*
|
|
348
|
+
* Exported so a caller holding layers it built itself — the install receipt,
|
|
349
|
+
* which measures before and after — can produce the same rows without
|
|
350
|
+
* re-reading the tree.
|
|
351
|
+
*/
|
|
352
|
+
export function buildHostLimitRows(
|
|
353
|
+
layers: readonly LayerReading[],
|
|
354
|
+
limits: ReadonlyMap<string, { limit: number | null; kind: HostLimitKind }>,
|
|
355
|
+
): HostLimitReading[] {
|
|
356
|
+
const byHost = new Map<string, { chars: number; owned: number; foreign: number }>();
|
|
357
|
+
for (const l of layers) {
|
|
358
|
+
if (!l.present) continue;
|
|
359
|
+
const acc = byHost.get(l.host) ?? { chars: 0, owned: 0, foreign: 0 };
|
|
360
|
+
acc.chars += l.chars;
|
|
361
|
+
acc.owned += l.package_owned_chars;
|
|
362
|
+
acc.foreign += l.foreign_chars;
|
|
363
|
+
byHost.set(l.host, acc);
|
|
364
|
+
}
|
|
365
|
+
// EVERY host that has a directory gets a row, present or not — a host whose
|
|
366
|
+
// layer is absent reads 0 against its limit, which is a real answer and the
|
|
367
|
+
// one step 3.5 asks for ("one row per host with a published limit").
|
|
368
|
+
for (const l of layers) if (!byHost.has(l.host)) byHost.set(l.host, { chars: 0, owned: 0, foreign: 0 });
|
|
369
|
+
// AND every host the LIMITS table knows, whether or not a layer reached it.
|
|
370
|
+
// Rows were layer-driven while the requirement is limit-driven, so a limit
|
|
371
|
+
// recorded for a host the layer map does not list disappeared silently —
|
|
372
|
+
// the two sources agree today, which means they can only diverge in the
|
|
373
|
+
// direction the code did not cover.
|
|
374
|
+
for (const [host, e] of limits) if (e.limit !== null && !byHost.has(host)) {
|
|
375
|
+
byHost.set(host, { chars: 0, owned: 0, foreign: 0 });
|
|
376
|
+
}
|
|
377
|
+
const rows: HostLimitReading[] = [];
|
|
378
|
+
for (const host of [...byHost.keys()].sort()) {
|
|
379
|
+
const a = byHost.get(host) as { chars: number; owned: number; foreign: number };
|
|
380
|
+
const e = limits.get(host) ?? { limit: null, kind: 'unpublished' as HostLimitKind };
|
|
381
|
+
const fraction = e.limit === null ? null : a.chars / e.limit;
|
|
382
|
+
rows.push({
|
|
383
|
+
host,
|
|
384
|
+
limit: e.limit,
|
|
385
|
+
kind: e.kind,
|
|
386
|
+
chars: a.chars,
|
|
387
|
+
package_owned_chars: a.owned,
|
|
388
|
+
foreign_chars: a.foreign,
|
|
389
|
+
fraction,
|
|
390
|
+
warn: fraction !== null && fraction >= LIMIT_WARN_FRACTION,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
return rows;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export interface ReportOptions {
|
|
397
|
+
readonly home: string;
|
|
398
|
+
readonly projectRoot: string;
|
|
399
|
+
/** The installed-tools manifest. Absent or unreadable means no ownership evidence. */
|
|
400
|
+
readonly manifestPath?: string | null;
|
|
401
|
+
/** Supplied rather than shelled, so a fixture is not at the mercy of a CLI being on PATH. */
|
|
402
|
+
readonly hostVersion?: string;
|
|
403
|
+
/**
|
|
404
|
+
* The published-limit table. A parameter for the same reason `home` is: a
|
|
405
|
+
* fixture must be able to state the limit it is measuring against instead
|
|
406
|
+
* of inheriting whatever the shipped config happens to say today.
|
|
407
|
+
*/
|
|
408
|
+
readonly hostLimitsPath?: string | null;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Read every host rule directory in both scopes.
|
|
413
|
+
*
|
|
414
|
+
* BOTH SCOPES, BECAUSE THE HOST UNIONS THEM. A reading of the global layer
|
|
415
|
+
* alone would understate a consumer whose project carries its own rules, and
|
|
416
|
+
* the carrier's own scope filter reads the union for the same reason
|
|
417
|
+
* (`rule_layer_overlap.hostRuleLayerIds`).
|
|
418
|
+
*/
|
|
419
|
+
export function buildInstalledLayerReport(opts: ReportOptions): InstalledLayerReport {
|
|
420
|
+
const manifestPath = opts.manifestPath ?? null;
|
|
421
|
+
const recorded =
|
|
422
|
+
manifestPath === null
|
|
423
|
+
? NO_RECORDED_HASHES
|
|
424
|
+
: readRecordedHashes(manifestPath, opts.projectRoot);
|
|
425
|
+
const layers: LayerReading[] = [];
|
|
426
|
+
for (const host of Object.keys(GLOBAL_RULE_DIRS).sort()) {
|
|
427
|
+
const dir = globalRuleLayerPath(host, opts.home);
|
|
428
|
+
if (dir === null) continue;
|
|
429
|
+
layers.push(readLayer(host, 'global', dir, recorded));
|
|
430
|
+
}
|
|
431
|
+
for (const rel of Object.keys(PROJECT_RULE_DIRS).sort()) {
|
|
432
|
+
const host = PROJECT_RULE_DIRS[rel] as string;
|
|
433
|
+
layers.push(readLayer(host, 'project', path.join(opts.projectRoot, rel), recorded));
|
|
434
|
+
}
|
|
435
|
+
const totals = layers.reduce(
|
|
436
|
+
(acc, l) => ({
|
|
437
|
+
files: acc.files + l.files,
|
|
438
|
+
unconditional: acc.unconditional + l.unconditional,
|
|
439
|
+
chars: acc.chars + l.chars,
|
|
440
|
+
package_owned: acc.package_owned + l.package_owned,
|
|
441
|
+
foreign: acc.foreign + l.foreign,
|
|
442
|
+
package_owned_chars: acc.package_owned_chars + l.package_owned_chars,
|
|
443
|
+
foreign_chars: acc.foreign_chars + l.foreign_chars,
|
|
444
|
+
}),
|
|
445
|
+
{
|
|
446
|
+
files: 0,
|
|
447
|
+
unconditional: 0,
|
|
448
|
+
chars: 0,
|
|
449
|
+
package_owned: 0,
|
|
450
|
+
foreign: 0,
|
|
451
|
+
package_owned_chars: 0,
|
|
452
|
+
foreign_chars: 0,
|
|
453
|
+
},
|
|
454
|
+
);
|
|
455
|
+
const limits = buildHostLimitRows(
|
|
456
|
+
layers,
|
|
457
|
+
loadHostInstructionLimits(opts.hostLimitsPath ?? defaultHostLimitsPath()),
|
|
458
|
+
);
|
|
459
|
+
return {
|
|
460
|
+
home: opts.home,
|
|
461
|
+
project_root: opts.projectRoot,
|
|
462
|
+
host_version: opts.hostVersion ?? 'unknown',
|
|
463
|
+
manifest_present: manifestPath !== null && fs.existsSync(manifestPath),
|
|
464
|
+
layers,
|
|
465
|
+
limits,
|
|
466
|
+
totals,
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/** The report as lines, for a terminal. One directory per block. */
|
|
471
|
+
export function renderInstalledLayerReport(report: InstalledLayerReport): string[] {
|
|
472
|
+
const out: string[] = [];
|
|
473
|
+
out.push(`installed layer — host ${report.host_version}, HOME ${report.home}`);
|
|
474
|
+
out.push(`project ${report.project_root}`);
|
|
475
|
+
out.push(
|
|
476
|
+
report.manifest_present
|
|
477
|
+
? 'ownership: read from the installed-tools manifest'
|
|
478
|
+
: 'ownership: NO manifest resolved — every file reads foreign, which is no evidence rather than a finding',
|
|
479
|
+
);
|
|
480
|
+
for (const l of report.layers) {
|
|
481
|
+
if (!l.present) {
|
|
482
|
+
out.push(` ${l.host} (${l.scope}) — absent: ${l.dir}`);
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
out.push(
|
|
486
|
+
` ${l.host} (${l.scope}) — ${String(l.files)} files, ` +
|
|
487
|
+
`${String(l.unconditional)} unconditional, ${String(l.chars)} chars, ` +
|
|
488
|
+
`${String(l.package_owned)} package-owned / ${String(l.foreign)} foreign`,
|
|
489
|
+
);
|
|
490
|
+
for (const f of l.top) {
|
|
491
|
+
out.push(
|
|
492
|
+
` ${String(f.chars).padStart(7)} ${f.unconditional ? 'uncond' : 'scoped'} ${f.file}`,
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
const t = report.totals;
|
|
497
|
+
out.push(
|
|
498
|
+
` TOTAL — ${String(t.files)} files, ${String(t.unconditional)} unconditional, ` +
|
|
499
|
+
`${String(t.chars)} chars, ${String(t.package_owned)} package-owned / ${String(t.foreign)} foreign`,
|
|
500
|
+
);
|
|
501
|
+
out.push(...renderHostLimitRows(report.limits));
|
|
502
|
+
return out;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* The budget block — one row per host, against its published limit.
|
|
507
|
+
*
|
|
508
|
+
* Extracted so the REPORT and the INSTALL RECEIPT print the same bytes. They
|
|
509
|
+
* answer the same question at two moments, and a consumer comparing a receipt
|
|
510
|
+
* against a later report must not have to decide whether a wording difference
|
|
511
|
+
* means a measurement difference.
|
|
512
|
+
*/
|
|
513
|
+
export function renderHostLimitRows(rows: readonly HostLimitReading[]): string[] {
|
|
514
|
+
// ONE ROW PER HOST, against its published limit — steps 1.4 and 3.5. The
|
|
515
|
+
// combined figure is what the row is about: a consumer's own instruction
|
|
516
|
+
// files sit in the same budget as this package's, so a package-owned number
|
|
517
|
+
// alone would understate what the host is actually holding.
|
|
518
|
+
const out: string[] = [
|
|
519
|
+
'host instruction budgets — package-owned + foreign against the published limit',
|
|
520
|
+
];
|
|
521
|
+
for (const r of rows) {
|
|
522
|
+
const split =
|
|
523
|
+
`${String(r.package_owned_chars)} package-owned + ` +
|
|
524
|
+
`${String(r.foreign_chars)} foreign = ${String(r.chars)} chars`;
|
|
525
|
+
if (r.limit === null) {
|
|
526
|
+
out.push(` ${r.host} — ${split}; no published limit recorded — not measured`);
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
const pct = ((r.fraction as number) * 100).toFixed(1);
|
|
530
|
+
out.push(
|
|
531
|
+
` ${r.host} — ${split}; ${pct}% of ${String(r.limit)} (${r.kind})` +
|
|
532
|
+
(r.warn
|
|
533
|
+
? ` — WARNING: at or past ${String(Math.round(LIMIT_WARN_FRACTION * 100))}% of the published limit`
|
|
534
|
+
: ''),
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
return out;
|
|
538
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-shaped merge primitives shared by the installer's host-config writers.
|
|
3
|
+
*
|
|
4
|
+
* Moved verbatim out of `src/scripts/install.ts` (road-to-a-tree-that-keeps-its-
|
|
5
|
+
* neighbours Phase 1.1). The behaviour is unchanged — this is a module boundary,
|
|
6
|
+
* not a rewrite. Two reasons it earns its own file:
|
|
7
|
+
*
|
|
8
|
+
* - `install.ts` sits far above the `check_source_size_budget` ceiling, so every
|
|
9
|
+
* line added there raises the tree-wide excess. The hook-coexistence work needs
|
|
10
|
+
* to touch seven call sites in that file; paying for it with a MOVE keeps the
|
|
11
|
+
* ratchet green and leaves the file shorter than it was.
|
|
12
|
+
* - `host_hook_merge.ts` needs `deepcopy` and `jsonEqual` too, and importing them
|
|
13
|
+
* from the installer would be a cycle.
|
|
14
|
+
*
|
|
15
|
+
* `deepMerge` REPLACES arrays wholesale. That is correct for scalar and object
|
|
16
|
+
* keys and wrong for a host's `hooks.<event>` array, which is a shared list other
|
|
17
|
+
* packages also append to — that case is `host_hook_merge.mergeHookEvents`, and
|
|
18
|
+
* the installer routes `hooks` through it before calling this.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** True for a JSON object (not null, not an array). */
|
|
22
|
+
export function isPlainObject(v: unknown): v is Record<string, unknown> {
|
|
23
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** `copy.deepcopy` for JSON-shaped values. */
|
|
27
|
+
export function deepcopy<T>(v: T): T {
|
|
28
|
+
if (v === null || typeof v !== 'object') return v;
|
|
29
|
+
if (Array.isArray(v)) return v.map((x) => deepcopy(x)) as unknown as T;
|
|
30
|
+
const out: Record<string, unknown> = {};
|
|
31
|
+
for (const k of Object.keys(v as Record<string, unknown>)) {
|
|
32
|
+
out[k] = deepcopy((v as Record<string, unknown>)[k]);
|
|
33
|
+
}
|
|
34
|
+
return out as unknown as T;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Recursive object merge; `overlay` wins on every leaf.
|
|
39
|
+
*
|
|
40
|
+
* Arrays are replaced, never concatenated — see the module header for the one
|
|
41
|
+
* case where that is the wrong answer and who handles it instead.
|
|
42
|
+
*/
|
|
43
|
+
export function deepMerge(
|
|
44
|
+
base: Record<string, unknown>,
|
|
45
|
+
overlay: Record<string, unknown>,
|
|
46
|
+
): Record<string, unknown> {
|
|
47
|
+
const result = deepcopy(base);
|
|
48
|
+
for (const key of Object.keys(overlay)) {
|
|
49
|
+
const value = overlay[key];
|
|
50
|
+
if (
|
|
51
|
+
Object.prototype.hasOwnProperty.call(result, key) &&
|
|
52
|
+
isPlainObject(result[key]) &&
|
|
53
|
+
isPlainObject(value)
|
|
54
|
+
) {
|
|
55
|
+
result[key] = deepMerge(result[key] as Record<string, unknown>, value as Record<string, unknown>);
|
|
56
|
+
} else {
|
|
57
|
+
result[key] = deepcopy(value);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Deep structural equality for JSON-shaped values (Python dict `==`). */
|
|
64
|
+
export function jsonEqual(a: unknown, b: unknown): boolean {
|
|
65
|
+
if (a === b) return true;
|
|
66
|
+
if (typeof a !== typeof b) return false;
|
|
67
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
68
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
69
|
+
return a.every((v, i) => jsonEqual(v, b[i]));
|
|
70
|
+
}
|
|
71
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
72
|
+
const ka = Object.keys(a);
|
|
73
|
+
const kb = Object.keys(b);
|
|
74
|
+
if (ka.length !== kb.length) return false;
|
|
75
|
+
return ka.every(
|
|
76
|
+
(k) =>
|
|
77
|
+
Object.prototype.hasOwnProperty.call(b, k) &&
|
|
78
|
+
jsonEqual(a[k], (b as Record<string, unknown>)[k]),
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|