@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
@@ -0,0 +1,370 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Composition gate for the self-hosted hook bundle: ONE YAML reader, and a
4
+ * byte ceiling.
5
+ *
6
+ * WHY THIS EXISTS — measured, not hypothetical.
7
+ *
8
+ * Every concern is inlined into one `dist/hooks/dispatch.js` that every hook
9
+ * event loads, so a dependency added for ONE concern is paid by all of them on
10
+ * every dispatch. On 2026-10-01 the bundle carried two YAML parsers: `yaml`,
11
+ * pulled by six modules including the dispatcher itself, and `js-yaml`, pulled
12
+ * by one call site in `block_config_weakening.ts` — 95,846 bytes, 6.13 % of a
13
+ * 1,564,211-byte bundle. Beside it, `host_lowering.ts`'s own header said the
14
+ * `yaml` reader was "the ONLY remaining YAML reader on that path".
15
+ *
16
+ * Nothing in this tree could see either fact. The freshness gate compares
17
+ * mtimes, the content gate compares digests, and neither has an opinion about
18
+ * what is INSIDE the bundle. So the second parser reached a release and was
19
+ * found by an external rescore counting bytes — a review finding where it
20
+ * should have been a red check. This gate is that red check.
21
+ *
22
+ * WHAT IT CHECKS
23
+ *
24
+ * 1. At most `max_yaml_packages` YAML packages contribute bytes to the
25
+ * bundle. The package set is {@link YAML_PACKAGES} — a closed list, so a
26
+ * third parser arriving under a name nobody listed is NOT caught here, and
27
+ * that limit is stated rather than implied.
28
+ * 2. The bundle's byte count is at or under `max_bytes`.
29
+ *
30
+ * Both bounds live in `src/config/hook-bundle-budget.json`, never in this file:
31
+ * a number a reviewer has to read a script to find is a number nobody reviews.
32
+ *
33
+ * WHY IT BUILDS RATHER THAN READS. `dist/hooks/` is untracked, so a gate that
34
+ * read the bundle on disk would be local-only like `check_hook_bundle_content`
35
+ * — and the thing being bounded is what the SOURCE produces, which is a
36
+ * property of the commit rather than of a machine. So this builds its own
37
+ * bundle with `--metafile` and measures that. It runs identically in CI and
38
+ * locally, and it never touches the live bundle.
39
+ *
40
+ * The esbuild invocation is NOT duplicated here: it is read from
41
+ * `package.json`'s `build:hooks` script with only `--outfile=` rewritten, via
42
+ * the same `rewriteOutfile` helper `rebuild_hook_bundle` and
43
+ * `check_hook_bundle_content` use, so the flag set has exactly one home. The
44
+ * trailing `&& node src/scripts/write_bundle_digest.mjs` is stripped — this
45
+ * gate must not write a sidecar for a probe bundle nobody executes.
46
+ *
47
+ * WHAT IT DOES NOT DO. It says nothing about LATENCY. A smaller bundle is not
48
+ * a measured speed-up; `src/config/hook-latency-budget.json` owns that axis and
49
+ * no number here is derived from it. It says nothing about the install or MCP
50
+ * bundles, which carry their own module graphs.
51
+ *
52
+ * Usage:
53
+ * ./scripts-run src/scripts/check_hook_bundle_composition
54
+ * ./scripts-run src/scripts/check_hook_bundle_composition --metafile <path>
55
+ * ./scripts-run src/scripts/check_hook_bundle_composition --self-test
56
+ *
57
+ * Exit codes: 0 = within budget · 1 = over the ceiling or a second reader ·
58
+ * 2 = the build or the budget file failed.
59
+ */
60
+ // ledger-exempt: single-artefact transaction — one bundle build yielding one
61
+ // aggregate verdict. The per-source accounting lives in
62
+ // check_hook_bundle_freshness, which carries the ledger.
63
+ import { spawnSync } from 'node:child_process';
64
+ import * as fs from 'node:fs';
65
+ import * as os from 'node:os';
66
+ import * as path from 'node:path';
67
+ import { fileURLToPath, pathToFileURL } from 'node:url';
68
+
69
+ import { runGateCli, runSelfTest } from './_lib/gate_self_test.js';
70
+ import { reportScanned } from './_lib/scan_scope.js';
71
+ import { buildScript } from './check_hook_bundle_content.js';
72
+ import { rewriteOutfile } from './rebuild_hook_bundle.js';
73
+
74
+ const _HERE = path.dirname(fileURLToPath(import.meta.url));
75
+ export const REPO_ROOT = path.resolve(_HERE, '..', '..');
76
+ export const SELF_REL = path.join('src', 'scripts', 'check_hook_bundle_composition.ts');
77
+ export const BUDGET_REL = path.join('src', 'config', 'hook-bundle-budget.json');
78
+ const LIVE_REL = path.join('dist', 'hooks', 'dispatch.js');
79
+
80
+ /**
81
+ * The YAML readers this tree has ever carried, by package name.
82
+ *
83
+ * A CLOSED LIST, and the consequence is worth stating: a fourth parser arriving
84
+ * under a name not listed here contributes bytes the ceiling catches and the
85
+ * parser cap does not. The alternative — treating any package whose name
86
+ * contains "yaml" as a reader — would match a YAML-adjacent utility that is not
87
+ * a parser, and a cap that fires on the wrong package is worse than one that
88
+ * misses an unlisted one, because the first teaches people to raise the cap.
89
+ */
90
+ export const YAML_PACKAGES: readonly string[] = ['yaml', 'js-yaml', 'yaml-js', 'yamljs'];
91
+
92
+ export interface Budget {
93
+ readonly maxBytes: number;
94
+ readonly maxYamlPackages: number;
95
+ }
96
+
97
+ export interface Analysis {
98
+ /** Bytes of the bundle esbuild produced. */
99
+ readonly bytes: number;
100
+ /** Modules that contributed to it — the unit `scanned:` reports. */
101
+ readonly modules: number;
102
+ /** Bytes in the output, summed per owning npm package. */
103
+ readonly packageBytes: ReadonlyMap<string, number>;
104
+ /** Listed YAML packages contributing more than zero bytes, sorted. */
105
+ readonly yamlPackages: readonly string[];
106
+ }
107
+
108
+ /**
109
+ * The npm package a bundled module path belongs to, or `null` for this tree's
110
+ * own sources.
111
+ *
112
+ * Reads the INNERMOST `node_modules/` segment: a transitively nested copy
113
+ * (`node_modules/a/node_modules/yaml/…`) is still `yaml`'s bytes, and
114
+ * attributing it to `a` would let a second parser hide one level down.
115
+ */
116
+ export function owningPackage(modulePath: string): string | null {
117
+ const marker = 'node_modules/';
118
+ const at = modulePath.lastIndexOf(marker);
119
+ if (at < 0) return null;
120
+ const parts = modulePath.slice(at + marker.length).split('/');
121
+ const head = parts[0] ?? '';
122
+ if (head === '') return null;
123
+ return head.startsWith('@') ? `${head}/${parts[1] ?? ''}` : head;
124
+ }
125
+
126
+ /** Parse an esbuild metafile and attribute its one entry-point output. */
127
+ export function analyzeMetafile(json: string): Analysis {
128
+ const meta = JSON.parse(json) as {
129
+ outputs?: Record<string, { entryPoint?: string; bytes?: number; inputs?: Record<string, { bytesInOutput?: number }> }>;
130
+ };
131
+ const outputs = Object.values(meta.outputs ?? {});
132
+ const out = outputs.find((o) => typeof o.entryPoint === 'string');
133
+ if (out === undefined) {
134
+ throw new Error(
135
+ 'metafile carries no entry-point output — refusing to report a composition for a build that produced nothing',
136
+ );
137
+ }
138
+ const packageBytes = new Map<string, number>();
139
+ const inputs = out.inputs ?? {};
140
+ for (const [p, v] of Object.entries(inputs)) {
141
+ const pkg = owningPackage(p);
142
+ if (pkg === null) continue;
143
+ packageBytes.set(pkg, (packageBytes.get(pkg) ?? 0) + (v.bytesInOutput ?? 0));
144
+ }
145
+ const yamlPackages = YAML_PACKAGES.filter((p) => (packageBytes.get(p) ?? 0) > 0).sort();
146
+ return {
147
+ bytes: out.bytes ?? 0,
148
+ modules: Object.keys(inputs).length,
149
+ packageBytes,
150
+ yamlPackages,
151
+ };
152
+ }
153
+
154
+ /** Read the committed budget. Throws rather than defaulting — a guessed ceiling bounds nothing. */
155
+ export function readBudget(root: string): Budget {
156
+ const abs = path.join(root, BUDGET_REL);
157
+ const raw = JSON.parse(fs.readFileSync(abs, 'utf-8')) as Record<string, unknown>;
158
+ const maxBytes = raw['max_bytes'];
159
+ const maxYaml = raw['max_yaml_packages'];
160
+ if (typeof maxBytes !== 'number' || !Number.isInteger(maxBytes) || maxBytes <= 0) {
161
+ throw new Error(`${BUDGET_REL}: max_bytes must be a positive integer, got ${JSON.stringify(maxBytes)}`);
162
+ }
163
+ if (typeof maxYaml !== 'number' || !Number.isInteger(maxYaml) || maxYaml < 1) {
164
+ throw new Error(
165
+ `${BUDGET_REL}: max_yaml_packages must be an integer >= 1, got ${JSON.stringify(maxYaml)}`,
166
+ );
167
+ }
168
+ return { maxBytes, maxYamlPackages: maxYaml };
169
+ }
170
+
171
+ /** Findings, one line each. Empty means within budget. */
172
+ export function verdict(a: Analysis, budget: Budget): string[] {
173
+ const findings: string[] = [];
174
+ if (a.yamlPackages.length > budget.maxYamlPackages) {
175
+ const detail = a.yamlPackages
176
+ .map((p) => `${p} (${String(a.packageBytes.get(p) ?? 0)} B)`)
177
+ .join(', ');
178
+ findings.push(
179
+ `${String(a.yamlPackages.length)} YAML readers in the bundle, cap is ` +
180
+ `${String(budget.maxYamlPackages)}: ${detail}. Every dispatch loads all of them. ` +
181
+ 'Move the odd caller onto the reader the dispatcher already uses.',
182
+ );
183
+ }
184
+ if (a.bytes > budget.maxBytes) {
185
+ findings.push(
186
+ `bundle is ${String(a.bytes)} bytes, ceiling is ${String(budget.maxBytes)} ` +
187
+ `(over by ${String(a.bytes - budget.maxBytes)}). Shrink the bundle, or raise ` +
188
+ `max_bytes in ${BUDGET_REL} WITH an entry in its raise_log naming what was added.`,
189
+ );
190
+ }
191
+ return findings;
192
+ }
193
+
194
+ /**
195
+ * Build the hook bundle to a throwaway path and return its metafile JSON.
196
+ *
197
+ * Throws on any failure rather than returning a sentinel: a composition read off
198
+ * a build that did not happen is the silent-green this gate exists to replace.
199
+ */
200
+ export function buildMetafile(root: string): string {
201
+ const script = buildScript(root);
202
+ if (script === null) {
203
+ throw new Error('package.json has no `build:hooks` script — refusing to guess the flag set');
204
+ }
205
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'hook-bundle-composition-'));
206
+ const outFile = path.join(dir, 'dispatch.js');
207
+ const metaFile = path.join(dir, 'meta.json');
208
+ try {
209
+ const rewritten = rewriteOutfile(script, outFile);
210
+ if (rewritten === null) {
211
+ throw new Error(`\`build:hooks\` no longer writes ${LIVE_REL} — refusing to build to a guessed path`);
212
+ }
213
+ // Strip the digest sidecar step: it would write `dispatch.sha256` beside
214
+ // a probe bundle nobody executes, and on failure it would mask the build
215
+ // error with a file-not-found.
216
+ const esbuildOnly = rewritten.split('&& node ')[0] ?? rewritten;
217
+ const built = spawnSync('sh', ['-c', `${esbuildOnly.trim()} --metafile=${metaFile}`], {
218
+ cwd: root,
219
+ encoding: 'utf-8',
220
+ maxBuffer: 64 * 1024 * 1024,
221
+ env: {
222
+ ...process.env,
223
+ PATH: `${path.join(root, 'node_modules', '.bin')}:${process.env['PATH'] ?? ''}`,
224
+ },
225
+ });
226
+ if (built.status !== 0 || !fs.existsSync(metaFile)) {
227
+ throw new Error(`esbuild failed (exit ${String(built.status)})\n${built.stderr ?? ''}`);
228
+ }
229
+ return fs.readFileSync(metaFile, 'utf-8');
230
+ } finally {
231
+ fs.rmSync(dir, { recursive: true, force: true });
232
+ }
233
+ }
234
+
235
+ function selfTest(): number {
236
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'hook-bundle-composition-selftest-'));
237
+ const write = (name: string, body: unknown): string => {
238
+ const p = path.join(dir, name);
239
+ fs.writeFileSync(p, JSON.stringify(body));
240
+ return p;
241
+ };
242
+ const meta = (bytes: number, inputs: Record<string, number>): unknown => ({
243
+ inputs: {},
244
+ outputs: {
245
+ 'dist/hooks/dispatch.js': {
246
+ entryPoint: 'src/scripts/hooks/dispatch_entry.ts',
247
+ bytes,
248
+ inputs: Object.fromEntries(
249
+ Object.entries(inputs).map(([k, v]) => [k, { bytesInOutput: v }]),
250
+ ),
251
+ },
252
+ },
253
+ });
254
+ const budget = readBudget(REPO_ROOT);
255
+ const clean = write('clean.json', meta(budget.maxBytes, { 'node_modules/yaml/dist/index.js': 10 }));
256
+ const twoReaders = write(
257
+ 'two-readers.json',
258
+ meta(1000, {
259
+ 'node_modules/yaml/dist/index.js': 600,
260
+ 'node_modules/js-yaml/dist/js-yaml.mjs': 400,
261
+ }),
262
+ );
263
+ const oneOver = write('one-over.json', meta(budget.maxBytes + 1, { 'src/x.ts': 1 }));
264
+ const empty = write('empty.json', { inputs: {}, outputs: {} });
265
+
266
+ const drive = (fixture: string): number =>
267
+ runGateCli(REPO_ROOT, SELF_REL, ['--metafile', fixture], REPO_ROOT);
268
+
269
+ try {
270
+ return runSelfTest({
271
+ gate: 'check_hook_bundle_composition',
272
+ minCases: 4,
273
+ minRejectCases: 3,
274
+ cases: [
275
+ {
276
+ name: 'one YAML reader, exactly at the ceiling → accepted',
277
+ expect: 'accept',
278
+ run: () => drive(clean),
279
+ },
280
+ {
281
+ name: 'a second YAML reader → rejected',
282
+ expect: 'reject',
283
+ run: () => drive(twoReaders),
284
+ },
285
+ {
286
+ name: 'one byte over the ceiling → rejected',
287
+ expect: 'reject',
288
+ run: () => drive(oneOver),
289
+ },
290
+ {
291
+ name: 'a metafile with no entry-point output → rejected, never reported as 0 bytes',
292
+ expect: 'reject',
293
+ run: () => drive(empty),
294
+ },
295
+ ],
296
+ });
297
+ } finally {
298
+ fs.rmSync(dir, { recursive: true, force: true });
299
+ }
300
+ }
301
+
302
+ export function main(argv: readonly string[] = process.argv.slice(2), root: string = REPO_ROOT): number {
303
+ if (argv.includes('--self-test')) {
304
+ return selfTest();
305
+ }
306
+ const at = argv.indexOf('--metafile');
307
+ const fixture = at >= 0 ? argv[at + 1] : undefined;
308
+
309
+ let budget: Budget;
310
+ try {
311
+ budget = readBudget(root);
312
+ } catch (e) {
313
+ process.stderr.write(`check_hook_bundle_composition: ${String(e instanceof Error ? e.message : e)}\n`);
314
+ return 2;
315
+ }
316
+
317
+ let json: string;
318
+ try {
319
+ json = fixture === undefined ? buildMetafile(root) : fs.readFileSync(fixture, 'utf-8');
320
+ } catch (e) {
321
+ process.stderr.write(`check_hook_bundle_composition: ${String(e instanceof Error ? e.message : e)}\n`);
322
+ return 2;
323
+ }
324
+
325
+ let analysis: Analysis;
326
+ try {
327
+ analysis = analyzeMetafile(json);
328
+ } catch (e) {
329
+ process.stderr.write(`check_hook_bundle_composition: ${String(e instanceof Error ? e.message : e)}\n`);
330
+ return 1;
331
+ }
332
+
333
+ const findings = verdict(analysis, budget);
334
+ // Reported on BOTH paths, red included: a census that goes quiet exactly
335
+ // when the gate fires is the shape `reportScanned` exists to prevent.
336
+ reportScanned({
337
+ gate: 'check_hook_bundle_composition',
338
+ scanned: analysis.modules,
339
+ units: 'bundled module(s)',
340
+ roots: [LIVE_REL],
341
+ });
342
+
343
+ if (findings.length === 0) {
344
+ process.stdout.write(
345
+ `✅ hook bundle: ${String(analysis.bytes)} B / ${String(budget.maxBytes)} B ceiling, ` +
346
+ `${String(analysis.yamlPackages.length)} YAML reader(s) ` +
347
+ `[${analysis.yamlPackages.join(', ')}], ${String(analysis.modules)} modules\n`,
348
+ );
349
+ return 0;
350
+ }
351
+ process.stderr.write('❌ hook bundle composition is out of budget\n');
352
+ for (const f of findings) process.stderr.write(` - ${f}\n`);
353
+ process.stderr.write(` Budget: ${BUDGET_REL}\n`);
354
+ return 1;
355
+ }
356
+
357
+ function _isCliEntry(): boolean {
358
+ const argv1 = process.argv[1];
359
+ if (argv1 === undefined) return false;
360
+ if (import.meta.url === pathToFileURL(path.resolve(argv1)).href) return true;
361
+ try {
362
+ return fs.realpathSync(fileURLToPath(import.meta.url)) === fs.realpathSync(path.resolve(argv1));
363
+ } catch {
364
+ return false;
365
+ }
366
+ }
367
+
368
+ if (_isCliEntry()) {
369
+ process.exit(main());
370
+ }
@@ -30,12 +30,14 @@
30
30
  *
31
31
  * Gated: a diff touching a kernel rule under `src/rules/` (the nine of
32
32
  * `_lib/kernel_rules.ts`), a governance hook (`src/scripts/hooks/block_*.ts`),
33
- * a hook-plumbing SOURCE (`hook_manifest.yaml`, `host_lowering.yaml`, a
34
- * `*-dispatcher.sh`, either hook budget file — see `PLUMBING_SOURCE_RE`),
35
- * or this gate itself — so it cannot be weakened without its own record. Such
36
- * a diff must carry a ratification artifact under
37
- * the ratifications directory with `verdict: ratified`, valid per the
38
- * ratification-artifact contract.
33
+ * hook plumbing — the YAML sources `hook_manifest.yaml` and
34
+ * `host_lowering.yaml`, their compiled siblings `hook_manifest.json` and
35
+ * `host_lowering.json`, the dispatcher `hooks/dispatch_hook.ts`, the
36
+ * kernel-rule list `_lib/kernel_rules.ts`, a `*-dispatcher.sh`, either hook
37
+ * budget file (see `PLUMBING_SOURCE_RE`) — or this gate itself, so it cannot
38
+ * be weakened without its own record. Such a diff must carry a ratification
39
+ * artifact under the ratifications directory with `verdict: ratified`, valid
40
+ * per the ratification-artifact contract.
39
41
  *
40
42
  * Every kernel diff is gated, not only the authority-EXPANDING ones.
41
43
  * ADR-268 § 4 makes the artifact required for authority-expanding edits. Which
@@ -141,13 +143,49 @@ const GOVERNANCE_HOOK_RE = /^src\/scripts\/hooks\/block_[a-z0-9_]+\.ts$/;
141
143
  * and every property the gate already had — base-revision code, in-repo quorum
142
144
  * policy, the whole diff as its unit — carries over unchanged.
143
145
  *
144
- * Sources only. `dist/hooks/dispatch.js` and `hooks/hooks.json` are BUILD
145
- * OUTPUTS with no legitimate hand edit at all, so they are refused at tool-call
146
- * time by `block_plumbing_writes.ts` rather than ratified here — a record for a
147
- * file whose every hand edit is illegitimate would be a record of a mistake.
146
+ * `dist/hooks/dispatch.js` and `hooks/hooks.json` are BUILD OUTPUTS that no
147
+ * reader in this repository opens — they exist only inside an installed
148
+ * consumer tree — and they have no legitimate hand edit at all, so they are
149
+ * refused at tool-call time by `block_plumbing_writes.ts` rather than ratified
150
+ * here: a record for a file whose every hand edit is illegitimate would be a
151
+ * record of a mistake.
152
+ *
153
+ * road-to-blocking-severities 2.1 WIDENS this from "sources only" to
154
+ * "everything the dispatcher serves at runtime", and the distinction above is
155
+ * why the two compiled tables land here while the two build outputs do not.
156
+ * `src/scripts/hook_manifest.json` and `src/scripts/hooks/host_lowering.json`
157
+ * are committed in THIS tree and are READ IN PREFERENCE to the YAML they were
158
+ * compiled from — `dispatch_hook._load_yaml` and `host_lowering.resolveTable`
159
+ * both serve the compiled body whenever its fingerprint matches. A diff that
160
+ * edits only the JSON therefore changes what every host is told about which
161
+ * concern runs and which slot can refuse, while the gated YAML beside it is
162
+ * untouched. That is the same authority change as editing the source, reached
163
+ * one file later, and before this step it carried no record.
164
+ *
165
+ * Two further files join for the same reason rather than by analogy:
166
+ *
167
+ * - `src/scripts/hooks/dispatch_hook.ts` is the dispatcher itself. Every
168
+ * concern's verdict passes through it, so an edit here can suppress a
169
+ * refusal without touching the manifest that declares it.
170
+ * - `src/scripts/_lib/kernel_rules.ts` is the list of the nine kernel rules.
171
+ * THIS GATE resolves `is_kernel_rule` through it, so a diff removing a name
172
+ * from that list removes a rule from this gate's own reach — the one edit
173
+ * that could quietly narrow the gate while every other path kept working.
174
+ *
175
+ * Both are also denied at tool-call time, `dispatch_hook.ts` through the
176
+ * plumbing guard's source set and `kernel_rules.ts` through the kernel-rule
177
+ * write guard, so the pairing of deny-plus-record matches what the YAML sources
178
+ * already had.
179
+ *
180
+ * The compiled tables are REGENERATED, not hand-edited, by
181
+ * `./scripts-run src/scripts/compile_hook_manifest --table manifest|host-lowering`,
182
+ * which runs as a script rather than as an edit tool call and so is not what
183
+ * either mechanism refuses. A regeneration lands in the same diff as the YAML
184
+ * change that caused it, which already requires a record; the case this closes
185
+ * is the diff where only the JSON moves.
148
186
  */
149
187
  const PLUMBING_SOURCE_RE =
150
- /^(?:src\/scripts\/hook_manifest\.yaml|src\/scripts\/hooks\/host_lowering\.yaml|src\/scripts\/hooks\/[a-z0-9-]+-dispatcher\.sh|src\/config\/hook-(?:token|latency)-budget\.json)$/;
188
+ /^(?:src\/scripts\/hook_manifest\.(?:yaml|json)|src\/scripts\/hooks\/host_lowering\.(?:yaml|json)|src\/scripts\/hooks\/dispatch_hook\.ts|src\/scripts\/_lib\/kernel_rules\.ts|src\/scripts\/hooks\/[a-z0-9-]+-dispatcher\.sh|src\/config\/hook-(?:token|latency)-budget\.json)$/;
151
189
 
152
190
  /**
153
191
  * A kernel rule, in the source tree or in any projection.
@@ -607,7 +607,13 @@ function _checkAppendOnly(base: string | null, findings: Finding[]): void {
607
607
  '--no-color',
608
608
  ref,
609
609
  '--',
610
- 'agents/memory/intake/',
610
+ // The JSONL drops, not the directory. `INTAKE_GLOB`, this module's
611
+ // docstring and every fixture in the suite all scope this check to
612
+ // `*.jsonl`; asking git for the directory made it govern the tracked
613
+ // skeleton too, so editing the README that documents the append-only
614
+ // rule was reported as a violation of it. `:(glob)` is explicit rather
615
+ // than relying on the default pathspec magic.
616
+ ':(glob)agents/memory/intake/*.jsonl',
611
617
  ]);
612
618
  if (rc !== 0) {
613
619
  findings.push(new Finding(INTAKE_GLOB, 0, 'warning', `append-only: git diff failed vs ${ref}`));
@@ -79,6 +79,7 @@ import {
79
79
  CURATED_HEAD_INSTRUCTION,
80
80
  extract_changelog_section,
81
81
  } from './_lib/release_material.js';
82
+ import { coverageLimitationNote, ledgerCoverageLabel } from './_lib/review_coverage.js';
82
83
  import { assertScanned, assertWatchlistResolves, DeadScopeError } from './_lib/scan_scope.js';
83
84
 
84
85
  const _HERE = fileURLToPath(import.meta.url);
@@ -339,6 +340,15 @@ export function main(argv: readonly string[]): number {
339
340
  const contradictions = highlight_contradictions(curated, derived);
340
341
  if (contradictions.length === 0) {
341
342
  process.stdout.write(`✅ curated head plausible for ${version} (span ${from}..${to})\n`);
343
+ // How much of the change the self-review read, printed where the head's
344
+ // Known-limitations field is decided. Partial coverage IS a known
345
+ // limitation of the record, and the ledger was the only place it was
346
+ // written down. A label, not a floor — the exit code above is already
347
+ // fixed at 0 by the time this runs, deliberately.
348
+ const covNote = coverageLimitationNote(ledgerCoverageLabel(REPO_ROOT, version));
349
+ if (covNote !== null) {
350
+ process.stdout.write(`${covNote}\n`);
351
+ }
342
352
  // Risk 2 of the roadmap that made `Known limitations` derivable, paid
343
353
  // where it lands rather than in a comment: a self-declared residual is
344
354
  // only detectable when somebody WROTE IT DOWN, so a derived `_none_`
@@ -45,6 +45,16 @@ const ALLOWLIST_GLOBS = [
45
45
  // be able to carry the dispositions of its own self-review findings; the
46
46
  // finding-dispositions gate is red until blocking ones are recorded here.
47
47
  'agents/evidence/release-findings/*.json',
48
+ // Evidence-temperature census — `taskfiles/content.yml` § release-prepare
49
+ // writes one per release, so without this entry the pipeline generates a
50
+ // file its own shape step refuses. Digit classes rather than `?`: fnmatch
51
+ // `?` compiles to `.` under the `s` flag, so it matches any character AND
52
+ // crosses `/` — `????-??-??` admits `evidence-temperature-x/yz-ab-cd.md`,
53
+ // which `[0-9]` cannot. Two reports can appear in one release PR, and
54
+ // both are in shape: `--resume` re-runs release-prepare, so a release
55
+ // carried across UTC midnight writes a second census. What that one
56
+ // contains is the census's business, not this gate's.
57
+ 'agents/evidence/analysis/evidence-temperature-[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9].md', // code-comment-allow provenance-comment -- the glob IS this gate's operand, not where the code came from
48
58
  // Project-settings template pin — bumped by release.ts set_template_pin and
49
59
  // its regenerated dist twin (kept in lockstep with package.json.version).
50
60
  'src/agent-src/templates/agents/agent-project-settings.example.yml',
@@ -52,16 +62,26 @@ const ALLOWLIST_GLOBS = [
52
62
  ] as const;
53
63
 
54
64
  /**
55
- * Translate a Python `fnmatch` shell pattern to a RegExp, mirroring
56
- * `fnmatch.translate`. fnmatch is case-sensitive on POSIX (fnmatchcase
57
- * semantics on the normalized path); `*` and `?` do NOT cross path
58
- * separators is NOT a property of fnmatch — `*` matches everything
59
- * including `/`. The allowlist relies on that (e.g. nested-file rejection
60
- * in the test works because none of the globs end with the file).
65
+ * Translate a Python `fnmatch` shell pattern to a RegExp, following
66
+ * `fnmatch.translate` for the constructs the allowlist actually uses: `*` and
67
+ * `[0-9]`. No entry uses `?`, and bracket handling beyond that one class is
68
+ * unverified against CPython — see the note at the bracket branch.
69
+ *
70
+ * Matching is case-sensitive, as `fnmatchcase` on a normalized path.
71
+ *
72
+ * THE PROPERTY THE WILDCARD ENTRIES TURN ON — the literal paths are unaffected:
73
+ * in fnmatch, `*` and `?` DO cross path separators.
74
+ * `*` becomes `.*` and `?` becomes `.`, both under the `s` flag, so each matches
75
+ * `/` like any other character. Glob syntax elsewhere (shells, .gitignore) stops
76
+ * `*` at a segment boundary; this does not. So the pack entry admits any depth
77
+ * beneath `src/packs/`, and the census entry uses digit classes precisely
78
+ * because they cannot match a separator. Both sides are pinned in
79
+ * `check_release_pr_shape.test.ts`.
61
80
  */
62
81
  function _fnmatchToRegExp(pat: string): RegExp {
63
- // Mirror Python fnmatch.translate: `*` → `.*`, `?` → `.`, `[seq]` kept,
64
- // everything else escaped. The full pattern is anchored with `(?s:...)\Z`.
82
+ // `*` → `.*`, `?` → `.`, a bracket class kept, everything else escaped.
83
+ // CPython does more inside a class (hyphen and set-operator escaping, `(?!)`
84
+ // for an empty one) and this does not — see the bracket branch below.
65
85
  let i = 0;
66
86
  const n = pat.length;
67
87
  let res = '';
@@ -87,12 +107,13 @@ function _fnmatchToRegExp(pat: string): RegExp {
87
107
  res += '\\[';
88
108
  } else {
89
109
  let stuff = pat.slice(i, j);
90
- if (!stuff.includes('-')) {
91
- stuff = stuff.replace(/\\/g, '\\\\');
92
- } else {
93
- // Rare; not used by the allowlist. Keep simple escape.
94
- stuff = stuff.replace(/\\/g, '\\\\');
95
- }
110
+ // The two arms this replaces were byte-identical, so the branch
111
+ // implemented nothing. Bracket handling here is checked against
112
+ // `[0-9]`, the only class the allowlist uses; anything else is
113
+ // untested against CPython, whose behaviour also varies by
114
+ // version. Add a bracket glob and verify it rather than assuming
115
+ // this mirrors `fnmatch.translate` beyond that one case.
116
+ stuff = stuff.replace(/\\/g, '\\\\');
96
117
  i = j + 1;
97
118
  if (stuff.startsWith('!')) {
98
119
  stuff = '^' + stuff.slice(1);
@@ -105,7 +126,9 @@ function _fnmatchToRegExp(pat: string): RegExp {
105
126
  res += c.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
106
127
  }
107
128
  }
108
- // Python uses `(?s:%s)\Z` — dotAll + full match.
129
+ // Full match with dotAll. Python spells the same thing `(?s:%s)\Z`; in JS
130
+ // `$` without the `m` flag does not match before a trailing newline, so
131
+ // `^(?:…)$` is equivalent rather than merely similar.
109
132
  return new RegExp(`^(?:${res})$`, 's');
110
133
  }
111
134
 
@@ -31,24 +31,44 @@
31
31
  * Exit codes: `0` under cap · `1` over cap or an unstated cap raise · `2` usage
32
32
  * error · `3` dead scan scope.
33
33
  *
34
- * ## This is deliberately NOT a CI gate
34
+ * THIS IS DELIBERATELY NOT A CI GATE, AND THE REASON IS NARROWER THAN IT WAS.
35
35
  *
36
- * Both inputs are machine-local by construction. `.claude/rules/` is gitignored
37
- * (`.gitignore:123` — it is generated by `task sync` / `task generate-tools`),
38
- * and no CI workflow performs a user-scope install, so on a fresh runner
39
- * **neither** layer exists. Wired into `ci` it would either red on a dead scope
40
- * or, with the empty case allowed, sit permanently green while certifying a
41
- * property it cannot observe — the exact failure `src/config/gate-coverage.yml`
42
- * was written to end. It is therefore registered as a local / doctor-surface
43
- * check and is absent from `gate-coverage.yml`, whose contract is CI-identical
44
- * invocation.
36
+ * **Corrected 2026-10-05 (step 0.1 of
37
+ * `road-to-a-rule-carrier-that-works-outside-the-repo`).** This section used to
38
+ * say "no CI workflow performs a user-scope install, so on a fresh runner
39
+ * **neither** layer exists", and then concluded that making the measurement
40
+ * CI-observable "needs the `InstructionsLoaded` record committed from a real
41
+ * session, which is a different piece of work". The first clause describes a
42
+ * workflow inventory; the second turns it into a capability claim, and that
43
+ * step does not hold. A runner CAN stage a user-scope install: the installer
44
+ * reads `HOME` from the environment, and
45
+ * `tests/install/global_install_hooks_smoke.test.ts` already drives
46
+ * `src/scripts/install --global --tools=claude-code` against a `mkdtemp` HOME
47
+ * and asserts the tree it produced. Nothing about the `InstructionsLoaded`
48
+ * event is required to observe the FILESYSTEM half, which is what this script's
49
+ * `filesystem` input reads.
45
50
  *
46
- * What that costs, stated plainly: the duplicate-delivery defect is not
51
+ * What stays true, and is the real reason this is not in `gate-coverage.yml`:
52
+ * **this gate measures THIS machine**, by design. `main()` resolves
53
+ * `os.homedir()` and `REPO_ROOT`, and the question it answers — "how much is
54
+ * the developer in front of me actually loading" — is about a machine, not
55
+ * about a commit. A CI run of it would measure the runner, which is a tree
56
+ * nobody works in, so the number would be real and uninteresting. The
57
+ * `measureStandingDelivery` function underneath takes both directories as
58
+ * PARAMETERS and is therefore already temp-HOME-capable; what is missing is a
59
+ * reason to point it at a runner, not a way to.
60
+ *
61
+ * The honest cost is unchanged: the duplicate-delivery defect is not
47
62
  * mechanically caught for anyone who never runs this locally. The enforced layer
48
63
  * is the installer gate (`install.ts::_gate_rule_layer_overlap`), which refuses
49
64
  * to CREATE the doubled state; this script measures a state that already exists.
50
- * Making the measurement CI-observable needs the `InstructionsLoaded` record
51
- * committed from a real session, which is a different piece of work.
65
+ *
66
+ * `_lib/installed_layer.ts` is the step-0.1 library that takes the same reading
67
+ * per host rule directory with every input as a parameter, and
68
+ * `installed_layer_report.ts` is its report. The two measure different things —
69
+ * that one counts characters after the frontmatter and block-comment strip,
70
+ * this one counts tokens over whole files against a cap — and neither number
71
+ * converts into the other.
52
72
  */
53
73
 
54
74
  import * as fs from 'node:fs';