@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.
Files changed (332) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +89 -237
  5. package/README.md +14 -12
  6. package/dist/agent-src/commands/analyze/inbox.md +1 -1
  7. package/dist/agent-src/commands/analyze/roadmap-repos.md +1 -1
  8. package/dist/agent-src/commands/bug/fix.md +17 -2
  9. package/dist/agent-src/commands/feature/plan.md +1 -1
  10. package/dist/agent-src/commands/feature/roadmap.md +1 -1
  11. package/dist/agent-src/commands/fix/ci.md +1 -1
  12. package/dist/agent-src/commands/implement-ticket.md +1 -1
  13. package/dist/agent-src/commands/jira-ticket.md +1 -1
  14. package/dist/agent-src/commands/roadmap/create.md +1 -1
  15. package/dist/agent-src/commands/roadmap/materialize.md +1 -1
  16. package/dist/agent-src/commands/roadmap/process-full.md +4 -4
  17. package/dist/agent-src/commands/roadmap/resolve-blockers.md +361 -0
  18. package/dist/agent-src/commands/roadmap/triage-parked.md +325 -0
  19. package/dist/agent-src/commands/roadmap.md +18 -4
  20. package/dist/agent-src/commands/tests/create.md +17 -3
  21. package/dist/agent-src/commands/tests/execute.md +22 -5
  22. package/dist/agent-src/contexts/authority/decision-revisit-gate-enforcement.md +43 -0
  23. package/dist/agent-src/contexts/communication/rules-auto/design-fidelity-enforcement.md +24 -0
  24. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +6 -6
  25. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +32 -2
  26. package/dist/agent-src/contexts/execution/session-canary-enforcement-history.md +67 -0
  27. package/dist/agent-src/contexts/execution/toolchain-resolver.md +101 -7
  28. package/dist/agent-src/rules/active-remediation.md +1 -1
  29. package/dist/agent-src/rules/autonomous-execution.md +1 -0
  30. package/dist/agent-src/rules/broken-access-control.md +1 -0
  31. package/dist/agent-src/rules/command-suggestion-policy.md +1 -0
  32. package/dist/agent-src/rules/context-hygiene.md +1 -1
  33. package/dist/agent-src/rules/decision-revisit-gate.md +16 -33
  34. package/dist/agent-src/rules/design-fidelity.md +13 -16
  35. package/dist/agent-src/rules/doc-screenshot-hygiene.md +1 -0
  36. package/dist/agent-src/rules/domain-safety-disclaimer.md +1 -0
  37. package/dist/agent-src/rules/domain-safety-pii.md +1 -0
  38. package/dist/agent-src/rules/domain-safety-retention.md +1 -0
  39. package/dist/agent-src/rules/engineering-safety-floor.md +1 -0
  40. package/dist/agent-src/rules/evaluator-independence.md +3 -2
  41. package/dist/agent-src/rules/fast-path-marker-visibility.md +1 -0
  42. package/dist/agent-src/rules/finance-safety-floor.md +1 -0
  43. package/dist/agent-src/rules/git-history-discipline.md +1 -0
  44. package/dist/agent-src/rules/image-likeness-and-rights.md +1 -0
  45. package/dist/agent-src/rules/legal-safety-floor.md +3 -2
  46. package/dist/agent-src/rules/lethal-trifecta-guard.md +1 -0
  47. package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
  48. package/dist/agent-src/rules/media-governance-routing.md +14 -17
  49. package/dist/agent-src/rules/persona-governance.md +2 -2
  50. package/dist/agent-src/rules/provider-lifecycle-discipline.md +1 -1
  51. package/dist/agent-src/rules/question-not-instruction.md +1 -0
  52. package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
  53. package/dist/agent-src/rules/runtime-safety.md +1 -0
  54. package/dist/agent-src/rules/secret-vcs-guard.md +1 -0
  55. package/dist/agent-src/rules/security-sensitive-stop.md +3 -2
  56. package/dist/agent-src/rules/session-canary.md +8 -62
  57. package/dist/agent-src/rules/settings-ask-protocol.md +1 -0
  58. package/dist/agent-src/rules/source-confidentiality.md +1 -0
  59. package/dist/agent-src/rules/strategy-safety-floor.md +1 -0
  60. package/dist/agent-src/rules/tool-safety.md +1 -0
  61. package/dist/agent-src/rules/untrusted-input-defense.md +1 -0
  62. package/dist/agent-src/rules/user-interrupt-priority.md +1 -0
  63. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
  64. package/dist/agent-src/scripts/deferral_carry.ts +339 -0
  65. package/dist/agent-src/scripts/update_roadmap_progress.ts +4 -1
  66. package/dist/agent-src/skills/api-design/data/manifest.json +2 -2
  67. package/dist/agent-src/skills/api-testing/SKILL.md +17 -0
  68. package/dist/agent-src/skills/code-intelligence/SKILL.md +14 -2
  69. package/dist/agent-src/skills/design-system-capture/references/import-procedure.md +1 -1
  70. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +2 -2
  71. package/dist/agent-src/skills/fe-design/SKILL.md +1 -1
  72. package/dist/agent-src/skills/iconography/SKILL.md +1 -1
  73. package/dist/agent-src/skills/js-library-packaging/SKILL.md +2 -2
  74. package/dist/agent-src/skills/monorepo-workspace/SKILL.md +1 -1
  75. package/dist/agent-src/skills/quality-tools/SKILL.md +11 -1
  76. package/dist/agent-src/skills/quality-tools/references/go-tools.md +76 -0
  77. package/dist/agent-src/skills/quality-tools/references/python-tools.md +104 -0
  78. package/dist/agent-src/skills/review-routing/SKILL.md +2 -2
  79. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
  80. package/dist/agent-src/skills/storybook-workshop/SKILL.md +3 -3
  81. package/dist/agent-src/skills/tailwind-engineer/SKILL.md +1 -1
  82. package/dist/agent-src/skills/test-case-discovery/SKILL.md +7 -0
  83. package/dist/agent-src/skills/test-driven-development/SKILL.md +17 -0
  84. package/dist/agent-src/skills/test-performance/SKILL.md +19 -1
  85. package/dist/agent-src/skills/ui-component-architect/SKILL.md +2 -2
  86. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  87. package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +48 -6
  88. package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
  89. package/dist/cli/registry.js +1 -1
  90. package/dist/cli-delegate/archive_completed_roadmaps.js +92 -12
  91. package/dist/cli-delegate/check_update_banner.js +12 -1
  92. package/dist/cli-delegate/chunk-523HCFYO.js +90 -0
  93. package/dist/cli-delegate/{chunk-M2DPGFZ4.js → chunk-6V6TA25H.js} +22 -152
  94. package/dist/cli-delegate/chunk-7IQE3D5S.js +191 -0
  95. package/dist/cli-delegate/{chunk-6KFEC3G7.js → chunk-CQWX336L.js} +1 -1
  96. package/dist/cli-delegate/chunk-DEIV7XP7.js +108 -0
  97. package/dist/cli-delegate/chunk-EEHDXSYD.js +2040 -0
  98. package/dist/cli-delegate/{chunk-P6ZHP5W2.js → chunk-GKE7ZVFA.js} +4 -4
  99. package/dist/cli-delegate/chunk-JQOM7TTM.js +1201 -0
  100. package/dist/cli-delegate/{chunk-IGD6ERX6.js → chunk-JQVYKIZA.js} +10 -0
  101. package/dist/cli-delegate/{chunk-FC75PGJE.js → chunk-JY2I2DXV.js} +12 -1
  102. package/dist/cli-delegate/chunk-KB5FZ655.js +208 -0
  103. package/dist/cli-delegate/{chunk-7RQP4UDH.js → chunk-KR6EM23A.js} +235 -1455
  104. package/dist/cli-delegate/{chunk-LTYVIY76.js → chunk-LIYTLH6O.js} +1 -1
  105. package/dist/cli-delegate/{chunk-YJ72UIAK.js → chunk-NQ7WT43P.js} +1186 -244
  106. package/dist/cli-delegate/chunk-NXEILF2K.js +2160 -0
  107. package/dist/cli-delegate/{chunk-5R7HOLAF.js → chunk-NYX3L2PZ.js} +1 -1
  108. package/dist/cli-delegate/{chunk-4E2W63DA.js → chunk-OKJUIAPS.js} +62 -1
  109. package/dist/cli-delegate/{chunk-QAJS5YWY.js → chunk-PHD7OOLE.js} +2 -1
  110. package/dist/cli-delegate/{chunk-RGNIWTEA.js → chunk-QLDHIPWR.js} +1 -1
  111. package/dist/cli-delegate/{chunk-7XGBX6ZH.js → chunk-RQNNW7IF.js} +1314 -560
  112. package/dist/cli-delegate/{chunk-4XFP2Y3E.js → chunk-S4UMZNES.js} +1 -1
  113. package/dist/cli-delegate/{chunk-6KM6ORRY.js → chunk-S4YWNRKO.js} +24 -8
  114. package/dist/cli-delegate/{chunk-FP36SWJD.js → chunk-TRI6DQZ6.js} +2781 -340
  115. package/dist/cli-delegate/chunk-ULQRUR23.js +208 -0
  116. package/dist/cli-delegate/{chunk-NUHLI2RX.js → chunk-YCU3TIXH.js} +2 -0
  117. package/dist/cli-delegate/chunk-ZBZV2G5R.js +210 -0
  118. package/dist/cli-delegate/{chunk-WGXTVKCQ.js → chunk-ZUZKZYCN.js} +5 -3
  119. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  120. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  121. package/dist/cli-delegate/cmd_conformance.js +1167 -234
  122. package/dist/cli-delegate/cmd_doctor.js +13 -6
  123. package/dist/cli-delegate/cmd_doctor_neighbours.js +23 -0
  124. package/dist/cli-delegate/cmd_explain.js +5 -5
  125. package/dist/cli-delegate/cmd_export.js +11 -8
  126. package/dist/cli-delegate/cmd_fleet.js +3 -3
  127. package/dist/cli-delegate/cmd_handoff.js +10 -7
  128. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  129. package/dist/cli-delegate/cmd_memory_get.js +3 -1
  130. package/dist/cli-delegate/cmd_migrate.js +2 -2
  131. package/dist/cli-delegate/cmd_packs_active.js +3 -3
  132. package/dist/cli-delegate/cmd_preflight.js +6 -6
  133. package/dist/cli-delegate/cmd_prune.js +11 -8
  134. package/dist/cli-delegate/cmd_refresh.js +2 -1
  135. package/dist/cli-delegate/cmd_route_audit.js +8 -6
  136. package/dist/cli-delegate/cmd_route_explain.js +4 -4
  137. package/dist/cli-delegate/cmd_session_recycle.js +4 -3
  138. package/dist/cli-delegate/cmd_settings_get.js +1 -1
  139. package/dist/cli-delegate/cmd_settings_migrate.js +11 -8
  140. package/dist/cli-delegate/cmd_settings_set.js +4 -4
  141. package/dist/cli-delegate/cmd_sync.js +11 -8
  142. package/dist/cli-delegate/cmd_uninstall.js +11 -8
  143. package/dist/cli-delegate/cmd_update.js +1 -1
  144. package/dist/cli-delegate/cmd_upgrade.js +3 -2
  145. package/dist/cli-delegate/cmd_validate.js +11 -8
  146. package/dist/cli-delegate/cmd_versions.js +1 -1
  147. package/dist/cli-delegate/deferral_carry.js +16 -0
  148. package/dist/cli-delegate/gate_execute.js +2 -2
  149. package/dist/cli-delegate/roadmap_gates.js +5 -5
  150. package/dist/cli-delegate/update_roadmap_progress.js +1 -1
  151. package/dist/discovery/deprecation-report.md +1 -1
  152. package/dist/discovery/discovery-manifest.json +127 -75
  153. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  154. package/dist/discovery/discovery-manifest.summary.md +3 -3
  155. package/dist/discovery/dormancy-report.md +1 -1
  156. package/dist/discovery/orphan-report.md +1 -1
  157. package/dist/discovery/packs.json +7 -5
  158. package/dist/discovery/trust-report.md +2 -2
  159. package/dist/discovery/workspaces.json +6 -4
  160. package/dist/hooks/dispatch.js +5357 -6656
  161. package/dist/hooks/dispatch.sha256 +1 -1
  162. package/dist/install/claudePathsPlan.js +47 -6
  163. package/dist/install/claudeRuleRewrite.js +7 -0
  164. package/dist/install/emit_host_rules_cli.js +39 -36
  165. package/dist/install/globalRuleScope.js +87 -0
  166. package/dist/install/host_rule_ownership.js +160 -0
  167. package/dist/install/install.mjs +2080 -1024
  168. package/dist/install/installThinLayer.js +261 -0
  169. package/dist/install/installedRuleLinks.js +176 -0
  170. package/dist/install/wizard-plan.js +24 -0
  171. package/dist/mcp/registry-manifest.json +3 -3
  172. package/dist/mcp/server.json +2 -2
  173. package/dist/mcp/server.mjs +31965 -27154
  174. package/dist/scripts/_lib/agent_settings.js +11 -0
  175. package/dist/scripts/_lib/claude_settings_hooks.js +10 -8
  176. package/dist/scripts/_lib/installed_layer.js +378 -0
  177. package/dist/scripts/_lib/lean_projection_mode.js +218 -15
  178. package/dist/scripts/_lib/rule_consequence_class.js +45 -0
  179. package/dist/scripts/_lib/rule_injection.js +67 -10
  180. package/dist/scripts/_lib/rule_law_section.js +122 -0
  181. package/dist/scripts/_lib/thin_rules.js +458 -0
  182. package/dist/scripts/_lib/windsurf_render.js +60 -0
  183. package/dist/scripts/condense.js +14 -26
  184. package/dist/scripts/project_thin_rules.js +14 -294
  185. package/dist/server/schemas/settings.js +6 -0
  186. package/dist/shared/skillRanking.js +74 -13
  187. package/docs/contracts/settings-classes.md +7 -5
  188. package/package.json +1 -1
  189. package/src/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
  190. package/src/agent-src/scripts/deferral_carry.ts +339 -0
  191. package/src/agent-src/scripts/update_roadmap_progress.ts +4 -1
  192. package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +48 -6
  193. package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
  194. package/src/config/agent-settings.template.yml +62 -22
  195. package/src/config/continuity-surface.json +7 -0
  196. package/src/config/evaluator-budgets.json +4 -4
  197. package/src/config/gate-coverage.yml +96 -3
  198. package/src/config/gate-violation-baselines.json +17 -9
  199. package/src/config/hook-bundle-budget.json +25 -0
  200. package/src/config/hook-token-budget.json +6 -1
  201. package/src/config/host-instruction-limits.json +33 -0
  202. package/src/config/routing-coverage-seed.json +24 -3
  203. package/src/config/rule-activation-census.json +7 -3
  204. package/src/config/rule-consequence-class.json +161 -0
  205. package/src/config/rule-law-ceilings.json +99 -0
  206. package/src/install/claudePathsPlan.ts +46 -4
  207. package/src/install/claudeRuleRewrite.ts +10 -1
  208. package/src/install/emit_host_rules_cli.ts +43 -34
  209. package/src/install/globalRuleScope.ts +109 -0
  210. package/src/install/host_rule_ownership.ts +170 -0
  211. package/src/install/installThinLayer.ts +312 -0
  212. package/src/install/installedRuleLinks.ts +220 -0
  213. package/src/install/wizard-plan.ts +24 -0
  214. package/src/scripts/_cli/cmd_doctor.ts +23 -71
  215. package/src/scripts/_cli/cmd_doctor_neighbours.ts +293 -0
  216. package/src/scripts/_dispatch.bash +16 -2
  217. package/src/scripts/_lib/agent_settings.ts +11 -0
  218. package/src/scripts/_lib/claude_settings_hooks.ts +10 -8
  219. package/src/scripts/_lib/estate_offsets.ts +241 -0
  220. package/src/scripts/_lib/exemption_shape.ts +143 -0
  221. package/src/scripts/_lib/fnv.ts +23 -0
  222. package/src/scripts/_lib/foreign_scan.ts +106 -0
  223. package/src/scripts/_lib/gitignore_block.ts +314 -0
  224. package/src/scripts/_lib/global_deploy_inventory.ts +32 -0
  225. package/src/scripts/_lib/graph_feeder_record.ts +214 -0
  226. package/src/scripts/_lib/hook_settings.ts +73 -0
  227. package/src/scripts/_lib/host_capability.ts +40 -19
  228. package/src/scripts/_lib/host_hook_merge.ts +165 -0
  229. package/src/scripts/_lib/installed_layer.ts +538 -0
  230. package/src/scripts/_lib/json_merge.ts +82 -0
  231. package/src/scripts/_lib/json_pointers.ts +93 -11
  232. package/src/scripts/_lib/lean_projection_mode.ts +275 -15
  233. package/src/scripts/_lib/machine_wake.ts +16 -3
  234. package/src/scripts/_lib/neighbour_census.ts +760 -0
  235. package/src/scripts/_lib/neighbour_scan.ts +122 -0
  236. package/src/scripts/_lib/neighbour_tool_use.ts +189 -0
  237. package/src/scripts/_lib/prompt_shape.ts +46 -0
  238. package/src/scripts/_lib/release_migration_gate.ts +19 -3
  239. package/src/scripts/_lib/reserved_name_sweep.ts +73 -0
  240. package/src/scripts/_lib/review_coverage.ts +134 -0
  241. package/src/scripts/_lib/rule_consequence_class.ts +71 -0
  242. package/src/scripts/_lib/rule_injection.ts +130 -10
  243. package/src/scripts/_lib/rule_law_section.ts +147 -0
  244. package/src/scripts/_lib/rule_layer_overlap.ts +58 -0
  245. package/src/scripts/_lib/skill_origin.ts +254 -0
  246. package/src/scripts/_lib/structured_ask.ts +64 -21
  247. package/src/scripts/_lib/thin_rules.ts +540 -0
  248. package/src/scripts/_lib/touched_file_quality.ts +434 -0
  249. package/src/scripts/_lib/turn_end_refusals.ts +77 -2
  250. package/src/scripts/_lib/windsurf_render.ts +63 -0
  251. package/src/scripts/ai_council/cli_call_budget.ts +13 -5
  252. package/src/scripts/ai_council/clients.ts +26 -57
  253. package/src/scripts/ai_council/config.ts +1 -1
  254. package/src/scripts/ai_council/vendor_defaults.ts +45 -0
  255. package/src/scripts/audit_skill_overlap.ts +261 -0
  256. package/src/scripts/before_complete_hook.ts +243 -12
  257. package/src/scripts/bench_touched_file_quality.ts +238 -0
  258. package/src/scripts/check_enforcement_matrix.ts +377 -20
  259. package/src/scripts/check_estate_count.ts +130 -153
  260. package/src/scripts/check_finding_dispositions.ts +116 -4
  261. package/src/scripts/check_hook_bundle_composition.ts +370 -0
  262. package/src/scripts/check_kernel_edit_ratified.ts +49 -11
  263. package/src/scripts/check_memory.ts +7 -1
  264. package/src/scripts/check_release_highlights.ts +10 -0
  265. package/src/scripts/check_release_pr_shape.ts +38 -15
  266. package/src/scripts/check_standing_rule_delivery.ts +33 -13
  267. package/src/scripts/check_verify_expectation_delta.ts +521 -0
  268. package/src/scripts/code_graph/detect.ts +127 -7
  269. package/src/scripts/code_graph/foreign.ts +194 -0
  270. package/src/scripts/code_graph/query.ts +217 -4
  271. package/src/scripts/code_graph/types.ts +26 -2
  272. package/src/scripts/code_graph/validate.ts +3 -1
  273. package/src/scripts/code_graph/verbs.ts +103 -0
  274. package/src/scripts/compile_hook_manifest.ts +33 -1
  275. package/src/scripts/condense.ts +14 -27
  276. package/src/scripts/estate_exemption_shape.ts +202 -0
  277. package/src/scripts/generate_host_cost_table.ts +1 -1
  278. package/src/scripts/hook_manifest.json +1 -1
  279. package/src/scripts/hook_manifest.yaml +150 -9
  280. package/src/scripts/hooks/block_config_weakening.ts +67 -2
  281. package/src/scripts/hooks/block_plumbing_writes.ts +29 -1
  282. package/src/scripts/hooks/code_graph_context_hook.ts +252 -23
  283. package/src/scripts/hooks/concern_failure_policy.ts +280 -0
  284. package/src/scripts/hooks/dispatch_hook.ts +52 -52
  285. package/src/scripts/hooks/exit_codes.ts +2 -1
  286. package/src/scripts/hooks/host_lowering.json +1 -1
  287. package/src/scripts/hooks/host_lowering.ts +19 -1
  288. package/src/scripts/hooks/one_question_per_ask_hook.ts +41 -25
  289. package/src/scripts/hooks/payload_stub.ts +10 -1
  290. package/src/scripts/hooks/rule_inject_hook.ts +757 -63
  291. package/src/scripts/hooks/table_fingerprint.ts +21 -0
  292. package/src/scripts/hooks/telemetry_usage_hook.ts +64 -8
  293. package/src/scripts/hooks/turn_end_gate_hook.ts +84 -0
  294. package/src/scripts/hooks_status.ts +112 -0
  295. package/src/scripts/injection_scan_hook.ts +7 -0
  296. package/src/scripts/install.ts +72 -185
  297. package/src/scripts/installed_layer_report.ts +103 -0
  298. package/src/scripts/language_mirror_hook.ts +15 -6
  299. package/src/scripts/learning_sidecar.ts +119 -8
  300. package/src/scripts/lint_hook_manifest.ts +53 -0
  301. package/src/scripts/lint_major_migration_sections.ts +156 -26
  302. package/src/scripts/lint_pack_boundaries.ts +63 -5
  303. package/src/scripts/lint_rule_law_section.ts +517 -0
  304. package/src/scripts/lint_workflow_security.ts +161 -5
  305. package/src/scripts/mcp_server/consumer_tool_catalog.json +53 -0
  306. package/src/scripts/mcp_server/graph_tools.ts +72 -3
  307. package/src/scripts/measure_routing_signal.ts +54 -16
  308. package/src/scripts/measure_skill_ranker_baseline.ts +216 -8
  309. package/src/scripts/memory_learn_hook.ts +91 -6
  310. package/src/scripts/model_rule_injection.ts +52 -5
  311. package/src/scripts/project_thin_rules.ts +34 -327
  312. package/src/scripts/report_evidence_temperature.ts +657 -0
  313. package/src/scripts/report_installed_rule_links.ts +157 -0
  314. package/src/scripts/report_label_agreement.ts +233 -0
  315. package/src/scripts/report_obligation_mechanism.ts +230 -0
  316. package/src/scripts/report_single_token_triggers.ts +277 -0
  317. package/src/scripts/report_skill_ranker_confusion.ts +442 -0
  318. package/src/scripts/report_standing_payload_by_host.ts +188 -15
  319. package/src/scripts/report_verification_record_coverage.ts +306 -0
  320. package/src/scripts/rule_activation_census.ts +5 -2
  321. package/src/scripts/rule_trigger_eval.ts +9 -9
  322. package/src/scripts/schemas/review-findings.schema.json +3 -2
  323. package/src/scripts/schemas/rule.schema.json +5 -0
  324. package/src/scripts/self_review_gate.ts +62 -7
  325. package/src/scripts/skill_tools/score_skill_relevance.ts +290 -25
  326. package/src/scripts/sweep_skill_ranker_signals.ts +129 -0
  327. package/src/scripts/sync_gitignore.ts +29 -235
  328. package/src/scripts/trigger_eval_grandfather.json +0 -5
  329. package/src/server/schemas/settings.ts +10 -0
  330. package/src/shared/skillRanking.ts +117 -11
  331. package/dist/cli-delegate/chunk-JNMVLWLR.js +0 -4457
  332. 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 the sections MIGRATION.md carries.
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 a heading
178
- * exists, failed when it does not.
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
- if (migration.has(s.version)) {
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(target, `no docs/MIGRATION.md section for ${s.version}`);
204
- findings.push({ section: s });
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
- if (migrationVersions(migrationText).has(target)) return null;
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
- `docs/MIGRATION.md has no "## " heading naming ${target}. Add one before the cut — ` +
234
- 'a section stating that the change asks nothing of a consumer satisfies this, and is ' +
235
- 'the right answer when it is true. What is not acceptable is silence.'
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 exists',
384
+ name: 'the same major passes once the heading and the index row exist',
297
385
  expect: 'accept',
298
386
  run: () =>
299
- fixture('present', changelog('17.0.0', ['**x:** drop y']), [
300
- '## 17.0.0 — what to do',
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('wrong-version', changelog('17.0.0', ['**x:** drop y']), [
308
- '## 16.0.0 — what to do',
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('arrow', changelog('17.0.0', ['**x:** drop y']), [
316
- '## 16.x → 17.0.0 — what to do',
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
- `${MIGRATION_PATH} carries no "## " heading naming it.\n` +
419
- ` Add a section for ${f.section.version}. A section stating that the change\n` +
420
- ' asks nothing of a consumer satisfies this and is the right answer when it\n' +
421
- ' is true — the requirement is a stated answer, never a procedure.\n',
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 · `core` · declared `requires`), the per-violation text and
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(source_pack: string, target_pack: string, requires: string[]): boolean {
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 === ALWAYS_INSTALLED) {
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,