@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
|
@@ -52,6 +52,33 @@ const DEFAULT_MANIFEST = path.join(
|
|
|
52
52
|
const HOOKS_DIR = path.join(REPO_ROOT, "src", "scripts", "hooks");
|
|
53
53
|
const DEFAULT_HOST_LOWERING = path.join(HOOKS_DIR, "host_lowering.yaml");
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* What a concern does to a turn — the closed set `effect:` draws from.
|
|
57
|
+
*
|
|
58
|
+
* Read by the neighbour census, which needs to say what sharing an event with
|
|
59
|
+
* a foreign entry actually risks. A foreign entry's effect is `unknown` unless
|
|
60
|
+
* its output shape has been observed: no host exposes another package's hook
|
|
61
|
+
* execution, so the honest default is that the neighbour's effect cannot be
|
|
62
|
+
* read off its registration.
|
|
63
|
+
*
|
|
64
|
+
* - `permission` can refuse the thing it fires on
|
|
65
|
+
* - `context` puts text in front of the model
|
|
66
|
+
* - `verification` checks a claim or a shape and reports what it found
|
|
67
|
+
* - `telemetry` records state, a ledger, a counter
|
|
68
|
+
* - `memory` reads or writes the memory layer
|
|
69
|
+
* - `notification` tells the human something and changes nothing else
|
|
70
|
+
* - `formatting` reshapes a command or a generated file
|
|
71
|
+
*/
|
|
72
|
+
export const CONCERN_EFFECTS: ReadonlySet<string> = new Set([
|
|
73
|
+
"permission",
|
|
74
|
+
"context",
|
|
75
|
+
"verification",
|
|
76
|
+
"telemetry",
|
|
77
|
+
"memory",
|
|
78
|
+
"notification",
|
|
79
|
+
"formatting",
|
|
80
|
+
]);
|
|
81
|
+
|
|
55
82
|
// Canonical event vocabulary — keep in lock-step with
|
|
56
83
|
// docs/contracts/hook-architecture-v1.md and dispatch_hook.EVENT_VOCABULARY.
|
|
57
84
|
const EVENT_VOCABULARY: ReadonlySet<string> = new Set([
|
|
@@ -159,6 +186,32 @@ function _check_concerns(manifest: YamlObject, errors: string[]): Set<string> {
|
|
|
159
186
|
if (!_isFile(path.join(REPO_ROOT, script))) {
|
|
160
187
|
errors.push(`concerns.${name}: script not found at '${script}'`);
|
|
161
188
|
}
|
|
189
|
+
// `effect:` — what this concern DOES to a turn, from a closed set. It is
|
|
190
|
+
// what lets the neighbour census say "a foreign entry shares this event
|
|
191
|
+
// with something of ours that can refuse" rather than only "two entries".
|
|
192
|
+
// Required and enum-checked, which is a stricter rule than the optional
|
|
193
|
+
// keys below for one reason: a concern with no effect, or with a
|
|
194
|
+
// misspelled one, would silently drop out of every double-gate comparison
|
|
195
|
+
// — the warning would stay quiet and read as "no conflict", which is the
|
|
196
|
+
// wrong answer rather than a missing one. There is no unknown-key guard
|
|
197
|
+
// on this mapping, so nothing else would catch `effects:`.
|
|
198
|
+
const effect = spec["effect"];
|
|
199
|
+
if (typeof effect !== "string" || !CONCERN_EFFECTS.has(effect)) {
|
|
200
|
+
errors.push(
|
|
201
|
+
`concerns.${name}: 'effect' must be one of ` +
|
|
202
|
+
`${[...CONCERN_EFFECTS].sort().join(" | ")} (got ${JSON.stringify(effect)})`,
|
|
203
|
+
);
|
|
204
|
+
} else if ((spec["severity"] === "blocking") !== (effect === "permission")) {
|
|
205
|
+
// The two fields answer the same question from different sides, so a
|
|
206
|
+
// disagreement is a mistake in one of them, never a shade of meaning:
|
|
207
|
+
// `blocking` is the dispatcher's word for "this can refuse" and
|
|
208
|
+
// `permission` is the census's.
|
|
209
|
+
errors.push(
|
|
210
|
+
`concerns.${name}: 'effect: ${effect}' contradicts ` +
|
|
211
|
+
`'severity: ${String(spec["severity"])}' — a concern that can refuse ` +
|
|
212
|
+
`is 'permission', and only those are 'blocking'`,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
162
215
|
// `tools:` — the optional per-concern tool filter the dispatcher applies
|
|
163
216
|
// in-process (`_concern_matches_tool`). Validated because the failure mode
|
|
164
217
|
// is silent: the dispatcher fails toward RUNNING the concern on anything
|
|
@@ -53,6 +53,23 @@ const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
|
53
53
|
|
|
54
54
|
export const GATE = 'lint_major_migration_sections';
|
|
55
55
|
export const MIGRATION_PATH = path.join('docs', 'MIGRATION.md');
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The consumer-facing index of what every major broke.
|
|
59
|
+
*
|
|
60
|
+
* The SECOND obligation this gate carries, and it is the same obligation
|
|
61
|
+
* pointed at a different reader. `docs/MIGRATION.md` answers "what do I have to
|
|
62
|
+
* do"; this table answers "what happened, and when" — it is the first thing
|
|
63
|
+
* somebody planning an upgrade across several majors opens, because it is the
|
|
64
|
+
* only place the breaks are side by side.
|
|
65
|
+
*
|
|
66
|
+
* It stopped at 9.0.0 while the archived changelogs carried 10.0.0 through
|
|
67
|
+
* 16.0.0. Seven majors with no row, and nothing in the tree could see it: the
|
|
68
|
+
* forward half (`lint_scheduled_deprecations`) looks at commitments, the
|
|
69
|
+
* backward half looked only at `MIGRATION.md`, and the index was guarded by
|
|
70
|
+
* nobody. The source that reported it counted the report as its twentieth.
|
|
71
|
+
*/
|
|
72
|
+
export const BREAKING_INDEX_PATH = 'BREAKING_CHANGES.md';
|
|
56
73
|
const CHANGELOG_PATH = 'CHANGELOG.md';
|
|
57
74
|
const ARCHIVE_DIR = path.join('docs', 'archive');
|
|
58
75
|
|
|
@@ -145,8 +162,37 @@ export function migrationVersions(text: string): Set<string> {
|
|
|
145
162
|
return out;
|
|
146
163
|
}
|
|
147
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Versions the breaking-changes index carries a ROW for.
|
|
167
|
+
*
|
|
168
|
+
* Reads the FIRST cell only, and that is the load-bearing choice rather than a
|
|
169
|
+
* parsing convenience. The live table's Migration cells end in pointers like
|
|
170
|
+
* "See [CHANGELOG 8.0.0](CHANGELOG.md)", so a whole-line scan would hand the
|
|
171
|
+
* index rows it does not have — and would do it in the direction that makes the
|
|
172
|
+
* gate green, which is the direction a reader cannot detect.
|
|
173
|
+
*
|
|
174
|
+
* The bold spelling the table happens to use is not required: a row is a row.
|
|
175
|
+
*/
|
|
176
|
+
export function indexVersions(text: string): Set<string> {
|
|
177
|
+
const out = new Set<string>();
|
|
178
|
+
for (const line of text.split('\n')) {
|
|
179
|
+
const trimmed = line.trim();
|
|
180
|
+
if (!trimmed.startsWith('|')) continue;
|
|
181
|
+
const first = trimmed.slice(1).split('|')[0];
|
|
182
|
+
if (first === undefined) continue;
|
|
183
|
+
for (const m of first.matchAll(/(?<![\d.])(\d+)\.0\.0(?![\d.])/g)) {
|
|
184
|
+
out.add(`${m[1] as string}.0.0`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return out;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Which of the two obligations a major failed. Never empty on a finding. */
|
|
191
|
+
export type MissingKind = 'migration' | 'index';
|
|
192
|
+
|
|
148
193
|
export interface Finding {
|
|
149
194
|
readonly section: MajorSection;
|
|
195
|
+
readonly missing: readonly MissingKind[];
|
|
150
196
|
}
|
|
151
197
|
|
|
152
198
|
/** Read every changelog file under `root`, newest-era file first. */
|
|
@@ -171,15 +217,23 @@ export interface EvaluateResult {
|
|
|
171
217
|
}
|
|
172
218
|
|
|
173
219
|
/**
|
|
174
|
-
* Compare the majors found against
|
|
220
|
+
* Compare the majors found against BOTH records a breaking major owes.
|
|
175
221
|
*
|
|
176
222
|
* Every section reaches exactly one ledger outcome: out of scope below the
|
|
177
|
-
* floor, out of scope with no BREAKING entries, completed when
|
|
178
|
-
*
|
|
223
|
+
* floor, out of scope with no BREAKING entries, completed when both records
|
|
224
|
+
* name it, failed when either does not.
|
|
225
|
+
*
|
|
226
|
+
* The two are checked together and reported apart. Together, because they are
|
|
227
|
+
* one obligation — "a major that broke something is written down where a
|
|
228
|
+
* consumer looks" — and splitting it across two gates would mean two copies of
|
|
229
|
+
* `findMajorSections` free to disagree about what a major even is. Apart,
|
|
230
|
+
* because a finding that says only "17.0.0 is undocumented" leaves the reader
|
|
231
|
+
* to discover which of two files to open.
|
|
179
232
|
*/
|
|
180
233
|
export function evaluate(
|
|
181
234
|
sections: readonly MajorSection[],
|
|
182
235
|
migration: ReadonlySet<string>,
|
|
236
|
+
index: ReadonlySet<string>,
|
|
183
237
|
ledger: GateLedger,
|
|
184
238
|
): EvaluateResult {
|
|
185
239
|
const findings: Finding[] = [];
|
|
@@ -196,12 +250,24 @@ export function evaluate(
|
|
|
196
250
|
continue;
|
|
197
251
|
}
|
|
198
252
|
inScope += 1;
|
|
199
|
-
|
|
253
|
+
const missing: MissingKind[] = [];
|
|
254
|
+
if (!migration.has(s.version)) missing.push('migration');
|
|
255
|
+
if (!index.has(s.version)) missing.push('index');
|
|
256
|
+
if (missing.length === 0) {
|
|
200
257
|
ledger.complete(target);
|
|
201
258
|
continue;
|
|
202
259
|
}
|
|
203
|
-
ledger.fail(
|
|
204
|
-
|
|
260
|
+
ledger.fail(
|
|
261
|
+
target,
|
|
262
|
+
missing
|
|
263
|
+
.map((k) =>
|
|
264
|
+
k === 'migration'
|
|
265
|
+
? `no ${MIGRATION_PATH} section for ${s.version}`
|
|
266
|
+
: `no ${BREAKING_INDEX_PATH} row for ${s.version}`,
|
|
267
|
+
)
|
|
268
|
+
.join('; '),
|
|
269
|
+
);
|
|
270
|
+
findings.push({ section: s, missing });
|
|
205
271
|
}
|
|
206
272
|
return { findings, sections: [...sections], inScope };
|
|
207
273
|
}
|
|
@@ -220,6 +286,7 @@ export function pendingMajorFinding(
|
|
|
220
286
|
target: string,
|
|
221
287
|
changelogEntry: string,
|
|
222
288
|
migrationText: string,
|
|
289
|
+
indexText: string,
|
|
223
290
|
): string | null {
|
|
224
291
|
const m = /^(\d+)\.0\.0$/.exec(target.trim());
|
|
225
292
|
if (m === null) return null;
|
|
@@ -227,12 +294,19 @@ export function pendingMajorFinding(
|
|
|
227
294
|
const breaking = sections.reduce((n, s) => n + s.breaking, 0);
|
|
228
295
|
if (breaking === 0) return null;
|
|
229
296
|
if (Number(m[1]) < FLOOR_MAJOR) return null;
|
|
230
|
-
|
|
297
|
+
const owed: string[] = [];
|
|
298
|
+
if (!migrationVersions(migrationText).has(target)) {
|
|
299
|
+
owed.push(`${MIGRATION_PATH} has no "## " heading naming ${target}`);
|
|
300
|
+
}
|
|
301
|
+
if (!indexVersions(indexText).has(target)) {
|
|
302
|
+
owed.push(`${BREAKING_INDEX_PATH} has no row naming ${target}`);
|
|
303
|
+
}
|
|
304
|
+
if (owed.length === 0) return null;
|
|
231
305
|
return (
|
|
232
306
|
`the ${target} entry carries ${String(breaking)} BREAKING CHANGES entr(y/ies) and ` +
|
|
233
|
-
|
|
234
|
-
'
|
|
235
|
-
'
|
|
307
|
+
`${owed.join(', and ')}. Add them before the cut — a section stating that the change ` +
|
|
308
|
+
'asks nothing of a consumer satisfies the first, and is the right answer when it is ' +
|
|
309
|
+
'true. What is not acceptable is silence.'
|
|
236
310
|
);
|
|
237
311
|
}
|
|
238
312
|
|
|
@@ -264,14 +338,28 @@ function selfTest(): number {
|
|
|
264
338
|
'',
|
|
265
339
|
].join('\n');
|
|
266
340
|
|
|
341
|
+
const indexTable = (versions: readonly string[]): string =>
|
|
342
|
+
[
|
|
343
|
+
'# Breaking Changes',
|
|
344
|
+
'',
|
|
345
|
+
'## Breaking changes by major',
|
|
346
|
+
'',
|
|
347
|
+
'| Version | Date | What broke | Migration |',
|
|
348
|
+
'|---|---|---|---|',
|
|
349
|
+
...versions.map((v) => `| **${v}** | 2026-01-01 | something | do a thing |`),
|
|
350
|
+
'',
|
|
351
|
+
].join('\n');
|
|
352
|
+
|
|
267
353
|
const fixture = (
|
|
268
354
|
name: string,
|
|
269
355
|
changelogText: string,
|
|
270
356
|
migrationHeadings: readonly string[],
|
|
357
|
+
indexRows: readonly string[] = [],
|
|
271
358
|
): number => {
|
|
272
359
|
const dir = path.join(root, name);
|
|
273
360
|
fs.mkdirSync(path.join(dir, 'docs'), { recursive: true });
|
|
274
361
|
fs.writeFileSync(path.join(dir, 'CHANGELOG.md'), changelogText, 'utf8');
|
|
362
|
+
fs.writeFileSync(path.join(dir, BREAKING_INDEX_PATH), indexTable(indexRows), 'utf8');
|
|
275
363
|
fs.writeFileSync(
|
|
276
364
|
path.join(dir, 'docs', 'MIGRATION.md'),
|
|
277
365
|
['# Migration Guide', '', ...migrationHeadings.flatMap((h) => [h, '', 'body', ''])].join(
|
|
@@ -293,28 +381,48 @@ function selfTest(): number {
|
|
|
293
381
|
// one half proves the refusal fires, the other that the heading is
|
|
294
382
|
// what clears it, so neither can be satisfied by a gate that always
|
|
295
383
|
// reds or always passes.
|
|
296
|
-
name: 'the same major passes once the heading
|
|
384
|
+
name: 'the same major passes once the heading and the index row exist',
|
|
297
385
|
expect: 'accept',
|
|
298
386
|
run: () =>
|
|
299
|
-
fixture(
|
|
300
|
-
'
|
|
301
|
-
|
|
387
|
+
fixture(
|
|
388
|
+
'present',
|
|
389
|
+
changelog('17.0.0', ['**x:** drop y']),
|
|
390
|
+
['## 17.0.0 — what to do'],
|
|
391
|
+
['17.0.0'],
|
|
392
|
+
),
|
|
302
393
|
},
|
|
303
394
|
{
|
|
304
395
|
name: 'a heading naming a DIFFERENT major does not satisfy the requirement',
|
|
305
396
|
expect: 'reject',
|
|
306
397
|
run: () =>
|
|
307
|
-
fixture(
|
|
308
|
-
'
|
|
398
|
+
fixture(
|
|
399
|
+
'wrong-version',
|
|
400
|
+
changelog('17.0.0', ['**x:** drop y']),
|
|
401
|
+
['## 16.0.0 — what to do'],
|
|
402
|
+
['17.0.0'],
|
|
403
|
+
),
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
// The index half of the same pair. Section present, row absent —
|
|
407
|
+
// so this case can only pass by checking the index, and the
|
|
408
|
+
// 'present' case above can only pass by accepting a correct one.
|
|
409
|
+
name: 'a major with a migration section but no index row is refused',
|
|
410
|
+
expect: 'reject',
|
|
411
|
+
run: () =>
|
|
412
|
+
fixture('no-index-row', changelog('17.0.0', ['**x:** drop y']), [
|
|
413
|
+
'## 17.0.0 — what to do',
|
|
309
414
|
]),
|
|
310
415
|
},
|
|
311
416
|
{
|
|
312
417
|
name: 'an arrow-form heading naming the version satisfies it',
|
|
313
418
|
expect: 'accept',
|
|
314
419
|
run: () =>
|
|
315
|
-
fixture(
|
|
316
|
-
'
|
|
317
|
-
|
|
420
|
+
fixture(
|
|
421
|
+
'arrow',
|
|
422
|
+
changelog('17.0.0', ['**x:** drop y']),
|
|
423
|
+
['## 16.x → 17.0.0 — what to do'],
|
|
424
|
+
['17.0.0'],
|
|
425
|
+
),
|
|
318
426
|
},
|
|
319
427
|
{
|
|
320
428
|
name: 'a major BELOW the floor is forgiven',
|
|
@@ -336,6 +444,10 @@ function selfTest(): number {
|
|
|
336
444
|
const dir = path.join(root, 'no-changelog');
|
|
337
445
|
fs.mkdirSync(path.join(dir, 'docs'), { recursive: true });
|
|
338
446
|
fs.writeFileSync(path.join(dir, 'docs', 'MIGRATION.md'), '# Migration Guide\n', 'utf8');
|
|
447
|
+
// The index IS present, so the refusal below can only be the
|
|
448
|
+
// dead-scan-root one. Without it this case would reject on a
|
|
449
|
+
// missing file and stop testing what it is named for.
|
|
450
|
+
fs.writeFileSync(path.join(dir, BREAKING_INDEX_PATH), indexTable([]), 'utf8');
|
|
339
451
|
return runGateCli(repo, script, ['--root', dir, '--quiet'], repo);
|
|
340
452
|
},
|
|
341
453
|
},
|
|
@@ -381,6 +493,15 @@ export function main(argv: readonly string[]): number {
|
|
|
381
493
|
return 1;
|
|
382
494
|
}
|
|
383
495
|
const migration = migrationVersions(fs.readFileSync(migrationFile, 'utf-8'));
|
|
496
|
+
// An absent index reds rather than skipping the index half. A missing
|
|
497
|
+
// comparand that silently narrows the comparison is how a gate reports
|
|
498
|
+
// "nothing wrong" about a question it stopped asking.
|
|
499
|
+
const indexFile = path.join(root, BREAKING_INDEX_PATH);
|
|
500
|
+
if (!fs.existsSync(indexFile)) {
|
|
501
|
+
process.stderr.write(`❌ ${GATE}: ${BREAKING_INDEX_PATH} not found under ${root}\n`);
|
|
502
|
+
return 1;
|
|
503
|
+
}
|
|
504
|
+
const index = indexVersions(fs.readFileSync(indexFile, 'utf-8'));
|
|
384
505
|
|
|
385
506
|
const sources = changelogSources(root);
|
|
386
507
|
const sections: MajorSection[] = [];
|
|
@@ -389,7 +510,7 @@ export function main(argv: readonly string[]): number {
|
|
|
389
510
|
}
|
|
390
511
|
|
|
391
512
|
const ledger = new GateLedger(GATE);
|
|
392
|
-
const result = evaluate(sections, migration, ledger);
|
|
513
|
+
const result = evaluate(sections, migration, index, ledger);
|
|
393
514
|
const tally = ledger.finalize();
|
|
394
515
|
|
|
395
516
|
try {
|
|
@@ -400,7 +521,7 @@ export function main(argv: readonly string[]): number {
|
|
|
400
521
|
gate: GATE,
|
|
401
522
|
scanned: tally.planned,
|
|
402
523
|
units: 'major changelog section(s)',
|
|
403
|
-
roots: [CHANGELOG_PATH, ARCHIVE_DIR],
|
|
524
|
+
roots: [CHANGELOG_PATH, ARCHIVE_DIR, MIGRATION_PATH, BREAKING_INDEX_PATH],
|
|
404
525
|
});
|
|
405
526
|
} catch (exc) {
|
|
406
527
|
if (exc instanceof DeadScopeError) {
|
|
@@ -412,13 +533,22 @@ export function main(argv: readonly string[]): number {
|
|
|
412
533
|
|
|
413
534
|
if (result.findings.length > 0) {
|
|
414
535
|
for (const f of result.findings) {
|
|
536
|
+
const owed = f.missing
|
|
537
|
+
.map((k) =>
|
|
538
|
+
k === 'migration'
|
|
539
|
+
? `${MIGRATION_PATH} carries no "## " heading naming it`
|
|
540
|
+
: `${BREAKING_INDEX_PATH} carries no row naming it`,
|
|
541
|
+
)
|
|
542
|
+
.join(', and ');
|
|
415
543
|
process.stderr.write(
|
|
416
544
|
`❌ ${GATE}: ${f.section.version} ships ${String(f.section.breaking)} BREAKING ` +
|
|
417
545
|
`CHANGES entr(y/ies) (${f.section.source}:${String(f.section.line)}) and ` +
|
|
418
|
-
`${
|
|
419
|
-
` Add
|
|
420
|
-
' asks nothing of a consumer satisfies
|
|
421
|
-
' is true — the requirement is a stated answer, never a
|
|
546
|
+
`${owed}.\n` +
|
|
547
|
+
` Add what is missing for ${f.section.version}. A migration section stating\n` +
|
|
548
|
+
' that the change asks nothing of a consumer satisfies that half and is the\n' +
|
|
549
|
+
' right answer when it is true — the requirement is a stated answer, never a\n' +
|
|
550
|
+
` procedure. The ${BREAKING_INDEX_PATH} row is one line in the by-major table,\n` +
|
|
551
|
+
' linking the changelog section it came from.\n',
|
|
422
552
|
);
|
|
423
553
|
}
|
|
424
554
|
return 1;
|
|
@@ -429,7 +559,7 @@ export function main(argv: readonly string[]): number {
|
|
|
429
559
|
process.stdout.write(
|
|
430
560
|
`✅ ${GATE}: ${String(tally.planned)} major section(s) read, ` +
|
|
431
561
|
`${String(result.inScope)} in scope (major >= ${String(FLOOR_MAJOR)} with BREAKING ` +
|
|
432
|
-
'CHANGES), each with a migration section' +
|
|
562
|
+
'CHANGES), each with a migration section and an index row' +
|
|
433
563
|
(result.inScope === 0
|
|
434
564
|
? ' — NONE in scope this run, so no comparison ran. Green here means\n' +
|
|
435
565
|
' "nothing to compare", not "the comparison passed".\n'
|
|
@@ -34,10 +34,19 @@
|
|
|
34
34
|
* this gate now matches that definition.
|
|
35
35
|
* - **BROKEN deliberately:** exit code 2 is new (dead scan scope). 0 and 1
|
|
36
36
|
* keep their meanings.
|
|
37
|
+
* - **BROKEN deliberately (2026-10-01):** the always-installed set is the
|
|
38
|
+
* hard-coded `core` PLUS every pack flagged `always_on: true`. The original
|
|
39
|
+
* knew only `core`, so it reported a violation for a target the resolver
|
|
40
|
+
* provably does install — the same defect shape as the direct-vs-transitive
|
|
41
|
+
* `requires` comparison above. `config/packs.resolve_active_packs` seeds the
|
|
42
|
+
* active set with the always-on packs before expanding any selection, and
|
|
43
|
+
* `docs/contracts/capability-packs.md` § Always-on packs states such a pack
|
|
44
|
+
* "cannot be deselected". One-way: a link OUT of an always-on pack into a
|
|
45
|
+
* gated one still dangles and is still reported.
|
|
37
46
|
* - **STILL HOLDS:** the CLI surface (`--format text|json`, `--quiet`, `-h`),
|
|
38
47
|
* the link regex and its fragment/query stripping, link-resolution semantics
|
|
39
48
|
* (external / absolute / out-of-tree targets ignored), the allow rule
|
|
40
|
-
* (same pack ·
|
|
49
|
+
* (same pack · always-installed · declared `requires`), the per-violation text and
|
|
41
50
|
* JSON shapes, the stdout/stderr split, and exit 0 clean / 1 violations.
|
|
42
51
|
*
|
|
43
52
|
* ## Where the `requires` graph is read from
|
|
@@ -115,6 +124,47 @@ const LINK_RE = /\[[^\]]*\]\(([^)#?]+)(?:[#?][^)]*)?\)/g;
|
|
|
115
124
|
/** A pack every install carries — a link into it can never dangle. */
|
|
116
125
|
const ALWAYS_INSTALLED = 'core';
|
|
117
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Packs every projection carries, so a link INTO one can never dangle.
|
|
129
|
+
*
|
|
130
|
+
* `core` is the hard-coded floor. Beyond it, `always_on: true` in the pack
|
|
131
|
+
* vocabulary means exactly this: `config/packs.resolve_active_packs` seeds the
|
|
132
|
+
* active set with every always-on pack BEFORE expanding the selected profile's
|
|
133
|
+
* `requires` closure, and `docs/contracts/capability-packs.md` § Always-on
|
|
134
|
+
* packs states the pack "is never gated by the selected profile, workspace, or
|
|
135
|
+
* pack set, and it cannot be deselected".
|
|
136
|
+
*
|
|
137
|
+
* Reading only `core` was the same class of defect this file's header already
|
|
138
|
+
* records for the direct-vs-transitive `requires` comparison: the gate reported
|
|
139
|
+
* a violation for a target the resolver provably does install. The direction is
|
|
140
|
+
* one-way — a link OUT of an always-on pack into a gated one still dangles and
|
|
141
|
+
* is still a violation.
|
|
142
|
+
*/
|
|
143
|
+
function _always_installed_packs(): Set<string> {
|
|
144
|
+
const out = new Set<string>([ALWAYS_INSTALLED]);
|
|
145
|
+
if (_exists(VOCAB)) {
|
|
146
|
+
const vocab = parseYaml(fs.readFileSync(VOCAB, 'utf-8'), { version: '1.1' });
|
|
147
|
+
if (Array.isArray(vocab)) {
|
|
148
|
+
for (const entry of vocab) {
|
|
149
|
+
if (entry !== null && typeof entry === 'object' && !Array.isArray(entry)) {
|
|
150
|
+
const e = entry as Record<string, unknown>;
|
|
151
|
+
if (typeof e['id'] === 'string' && e['always_on'] === true) {
|
|
152
|
+
out.add(e['id']);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
for (const dir of _pack_home_dirs()) {
|
|
159
|
+
const meta = _load_pack_meta(dir);
|
|
160
|
+
if (meta['always_on'] === true) {
|
|
161
|
+
const id = typeof meta['id'] === 'string' ? meta['id'] : path.basename(dir);
|
|
162
|
+
out.add(id);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return out;
|
|
166
|
+
}
|
|
167
|
+
|
|
118
168
|
type ViolationKind =
|
|
119
169
|
/** Link crosses into a pack the source pack does not require. */
|
|
120
170
|
| 'cross-pack'
|
|
@@ -479,11 +529,16 @@ function _scan_file(p: string): string[] {
|
|
|
479
529
|
return out;
|
|
480
530
|
}
|
|
481
531
|
|
|
482
|
-
function _is_allowed(
|
|
532
|
+
function _is_allowed(
|
|
533
|
+
source_pack: string,
|
|
534
|
+
target_pack: string,
|
|
535
|
+
requires: string[],
|
|
536
|
+
always_installed: ReadonlySet<string> = new Set([ALWAYS_INSTALLED]),
|
|
537
|
+
): boolean {
|
|
483
538
|
if (source_pack === target_pack) {
|
|
484
539
|
return true;
|
|
485
540
|
}
|
|
486
|
-
if (target_pack
|
|
541
|
+
if (always_installed.has(target_pack)) {
|
|
487
542
|
return true;
|
|
488
543
|
}
|
|
489
544
|
return (requires || []).includes(target_pack);
|
|
@@ -501,10 +556,11 @@ function _link_allowed(
|
|
|
501
556
|
source_packs: readonly string[],
|
|
502
557
|
target_packs: readonly string[],
|
|
503
558
|
closureOf: (pack: string) => Set<string>,
|
|
559
|
+
always_installed: ReadonlySet<string> = new Set([ALWAYS_INSTALLED]),
|
|
504
560
|
): boolean {
|
|
505
561
|
return source_packs.every((s) => {
|
|
506
562
|
const reach = [...closureOf(s)];
|
|
507
|
-
return target_packs.some((t) => _is_allowed(s, t, reach));
|
|
563
|
+
return target_packs.some((t) => _is_allowed(s, t, reach, always_installed));
|
|
508
564
|
});
|
|
509
565
|
}
|
|
510
566
|
|
|
@@ -597,6 +653,7 @@ function main(argv?: readonly string[]): number {
|
|
|
597
653
|
}
|
|
598
654
|
|
|
599
655
|
const pack_requires = _pack_requires();
|
|
656
|
+
const always_installed = _always_installed_packs();
|
|
600
657
|
const closureCache = new Map<string, Set<string>>();
|
|
601
658
|
const closureOf = (pack: string): Set<string> => {
|
|
602
659
|
let c = closureCache.get(pack);
|
|
@@ -635,7 +692,7 @@ function main(argv?: readonly string[]): number {
|
|
|
635
692
|
if (target_packs === undefined) {
|
|
636
693
|
continue; // docs/, scripts/, root files, unassigned — not pack-scoped
|
|
637
694
|
}
|
|
638
|
-
if (_link_allowed(src_packs, target_packs, closureOf)) {
|
|
695
|
+
if (_link_allowed(src_packs, target_packs, closureOf, always_installed)) {
|
|
639
696
|
continue;
|
|
640
697
|
}
|
|
641
698
|
violations.push({
|
|
@@ -743,6 +800,7 @@ export {
|
|
|
743
800
|
_build_artefact_index,
|
|
744
801
|
_resolve_link,
|
|
745
802
|
_scan_file,
|
|
803
|
+
_always_installed_packs,
|
|
746
804
|
_is_allowed,
|
|
747
805
|
_link_allowed,
|
|
748
806
|
_load_pack_meta,
|