@event4u/agent-config 16.1.0 → 16.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (467) 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 +98 -257
  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 +5 -5
  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/commands/work.md +4 -0
  23. package/dist/agent-src/contexts/authority/decision-revisit-gate-enforcement.md +43 -0
  24. package/dist/agent-src/contexts/communication/rules-auto/design-fidelity-enforcement.md +24 -0
  25. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +6 -6
  26. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +4 -8
  27. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +32 -2
  28. package/dist/agent-src/contexts/execution/session-canary-enforcement-history.md +67 -0
  29. package/dist/agent-src/contexts/execution/toolchain-resolver.md +101 -7
  30. package/dist/agent-src/rules/active-remediation.md +1 -1
  31. package/dist/agent-src/rules/autonomous-execution.md +1 -0
  32. package/dist/agent-src/rules/broken-access-control.md +1 -0
  33. package/dist/agent-src/rules/command-suggestion-policy.md +1 -0
  34. package/dist/agent-src/rules/context-hygiene.md +1 -1
  35. package/dist/agent-src/rules/decision-revisit-gate.md +16 -33
  36. package/dist/agent-src/rules/design-fidelity.md +13 -16
  37. package/dist/agent-src/rules/doc-screenshot-hygiene.md +1 -0
  38. package/dist/agent-src/rules/domain-safety-disclaimer.md +1 -0
  39. package/dist/agent-src/rules/domain-safety-pii.md +1 -0
  40. package/dist/agent-src/rules/domain-safety-retention.md +1 -0
  41. package/dist/agent-src/rules/engineering-safety-floor.md +1 -0
  42. package/dist/agent-src/rules/evaluator-independence.md +3 -2
  43. package/dist/agent-src/rules/fast-path-marker-visibility.md +1 -0
  44. package/dist/agent-src/rules/finance-safety-floor.md +1 -0
  45. package/dist/agent-src/rules/git-history-discipline.md +1 -0
  46. package/dist/agent-src/rules/image-likeness-and-rights.md +1 -0
  47. package/dist/agent-src/rules/legal-safety-floor.md +3 -2
  48. package/dist/agent-src/rules/lethal-trifecta-guard.md +1 -0
  49. package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
  50. package/dist/agent-src/rules/media-governance-routing.md +14 -17
  51. package/dist/agent-src/rules/persona-governance.md +2 -2
  52. package/dist/agent-src/rules/provider-lifecycle-discipline.md +1 -1
  53. package/dist/agent-src/rules/question-not-instruction.md +1 -0
  54. package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
  55. package/dist/agent-src/rules/runtime-safety.md +1 -0
  56. package/dist/agent-src/rules/secret-vcs-guard.md +1 -0
  57. package/dist/agent-src/rules/security-sensitive-stop.md +3 -2
  58. package/dist/agent-src/rules/session-canary.md +8 -62
  59. package/dist/agent-src/rules/settings-ask-protocol.md +1 -0
  60. package/dist/agent-src/rules/source-confidentiality.md +1 -0
  61. package/dist/agent-src/rules/strategy-safety-floor.md +1 -0
  62. package/dist/agent-src/rules/tool-safety.md +1 -0
  63. package/dist/agent-src/rules/ui-audit-gate.md +1 -2
  64. package/dist/agent-src/rules/untrusted-input-defense.md +1 -0
  65. package/dist/agent-src/rules/user-interrupt-priority.md +1 -0
  66. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
  67. package/dist/agent-src/scripts/deferral_carry.ts +339 -0
  68. package/dist/agent-src/scripts/update_roadmap_progress.ts +4 -1
  69. package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
  70. package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +2 -2
  71. package/dist/agent-src/skills/api-design/data/manifest.json +2 -2
  72. package/dist/agent-src/skills/api-testing/SKILL.md +17 -0
  73. package/dist/agent-src/skills/code-intelligence/SKILL.md +14 -2
  74. package/dist/agent-src/skills/design-review/SKILL.md +30 -4
  75. package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
  76. package/dist/agent-src/skills/design-system-capture/references/import-procedure.md +1 -1
  77. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +18 -3
  78. package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
  79. package/dist/agent-src/skills/fe-design/SKILL.md +1 -1
  80. package/dist/agent-src/skills/iconography/SKILL.md +1 -1
  81. package/dist/agent-src/skills/js-library-packaging/SKILL.md +2 -2
  82. package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
  83. package/dist/agent-src/skills/monorepo-workspace/SKILL.md +1 -1
  84. package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
  85. package/dist/agent-src/skills/quality-tools/SKILL.md +11 -1
  86. package/dist/agent-src/skills/quality-tools/references/go-tools.md +76 -0
  87. package/dist/agent-src/skills/quality-tools/references/python-tools.md +104 -0
  88. package/dist/agent-src/skills/review-routing/SKILL.md +2 -2
  89. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
  90. package/dist/agent-src/skills/storybook-workshop/SKILL.md +3 -3
  91. package/dist/agent-src/skills/tailwind-engineer/SKILL.md +1 -1
  92. package/dist/agent-src/skills/test-case-discovery/SKILL.md +7 -0
  93. package/dist/agent-src/skills/test-driven-development/SKILL.md +17 -0
  94. package/dist/agent-src/skills/test-performance/SKILL.md +19 -1
  95. package/dist/agent-src/skills/ui-component-architect/SKILL.md +25 -2
  96. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  97. package/dist/agent-src/templates/roadmaps.md +30 -0
  98. package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  99. package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +302 -21
  100. package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  101. package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  102. package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
  103. package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
  104. package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  105. package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  106. package/dist/cli/python/knowledge_ingest.js +1 -1
  107. package/dist/cli/python/workspace_drive.js +1 -1
  108. package/dist/cli/registry.js +1 -1
  109. package/dist/cli-delegate/archive_completed_roadmaps.js +92 -12
  110. package/dist/cli-delegate/check_update_banner.js +12 -1
  111. package/dist/cli-delegate/chunk-523HCFYO.js +90 -0
  112. package/dist/cli-delegate/{chunk-M2DPGFZ4.js → chunk-6V6TA25H.js} +22 -152
  113. package/dist/cli-delegate/chunk-7IQE3D5S.js +191 -0
  114. package/dist/cli-delegate/{chunk-6KFEC3G7.js → chunk-CQWX336L.js} +1 -1
  115. package/dist/cli-delegate/chunk-DEIV7XP7.js +108 -0
  116. package/dist/cli-delegate/chunk-EEHDXSYD.js +2040 -0
  117. package/dist/cli-delegate/{chunk-P6ZHP5W2.js → chunk-GKE7ZVFA.js} +4 -4
  118. package/dist/cli-delegate/chunk-JQOM7TTM.js +1201 -0
  119. package/dist/cli-delegate/{chunk-UNCRJBT3.js → chunk-JQVYKIZA.js} +41 -0
  120. package/dist/cli-delegate/{chunk-FC75PGJE.js → chunk-JY2I2DXV.js} +12 -1
  121. package/dist/cli-delegate/chunk-KB5FZ655.js +208 -0
  122. package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-KR6EM23A.js} +235 -1455
  123. package/dist/cli-delegate/{chunk-LTYVIY76.js → chunk-LIYTLH6O.js} +1 -1
  124. package/dist/cli-delegate/chunk-NQ7WT43P.js +2729 -0
  125. package/dist/cli-delegate/chunk-NXEILF2K.js +2160 -0
  126. package/dist/cli-delegate/{chunk-5R7HOLAF.js → chunk-NYX3L2PZ.js} +1 -1
  127. package/dist/cli-delegate/{chunk-4E2W63DA.js → chunk-OKJUIAPS.js} +62 -1
  128. package/dist/cli-delegate/{chunk-QAJS5YWY.js → chunk-PHD7OOLE.js} +2 -1
  129. package/dist/cli-delegate/{chunk-RGNIWTEA.js → chunk-QLDHIPWR.js} +1 -1
  130. package/dist/cli-delegate/{chunk-HDVVGPQW.js → chunk-RQNNW7IF.js} +1405 -567
  131. package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-S4UMZNES.js} +1 -1
  132. package/dist/cli-delegate/{chunk-6KM6ORRY.js → chunk-S4YWNRKO.js} +24 -8
  133. package/dist/cli-delegate/{chunk-GJCBB2UG.js → chunk-TRI6DQZ6.js} +3054 -325
  134. package/dist/cli-delegate/chunk-ULQRUR23.js +208 -0
  135. package/dist/cli-delegate/{chunk-NUHLI2RX.js → chunk-YCU3TIXH.js} +2 -0
  136. package/dist/cli-delegate/chunk-ZBZV2G5R.js +210 -0
  137. package/dist/cli-delegate/{chunk-WGXTVKCQ.js → chunk-ZUZKZYCN.js} +5 -3
  138. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  139. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  140. package/dist/cli-delegate/cmd_conformance.js +1862 -809
  141. package/dist/cli-delegate/cmd_doctor.js +13 -6
  142. package/dist/cli-delegate/cmd_doctor_neighbours.js +23 -0
  143. package/dist/cli-delegate/cmd_explain.js +5 -5
  144. package/dist/cli-delegate/cmd_export.js +11 -8
  145. package/dist/cli-delegate/cmd_fleet.js +3 -3
  146. package/dist/cli-delegate/cmd_handoff.js +10 -7
  147. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  148. package/dist/cli-delegate/cmd_memory_get.js +3 -1
  149. package/dist/cli-delegate/cmd_migrate.js +2 -2
  150. package/dist/cli-delegate/cmd_packs_active.js +3 -3
  151. package/dist/cli-delegate/cmd_preflight.js +6 -6
  152. package/dist/cli-delegate/cmd_prune.js +11 -8
  153. package/dist/cli-delegate/cmd_refresh.js +2 -1
  154. package/dist/cli-delegate/cmd_route_audit.js +8 -6
  155. package/dist/cli-delegate/cmd_route_explain.js +4 -4
  156. package/dist/cli-delegate/cmd_session_recycle.js +4 -3
  157. package/dist/cli-delegate/cmd_settings_get.js +1 -1
  158. package/dist/cli-delegate/cmd_settings_migrate.js +11 -8
  159. package/dist/cli-delegate/cmd_settings_set.js +4 -4
  160. package/dist/cli-delegate/cmd_sync.js +11 -8
  161. package/dist/cli-delegate/cmd_uninstall.js +11 -8
  162. package/dist/cli-delegate/cmd_update.js +1 -1
  163. package/dist/cli-delegate/cmd_upgrade.js +3 -2
  164. package/dist/cli-delegate/cmd_validate.js +11 -8
  165. package/dist/cli-delegate/cmd_versions.js +1 -1
  166. package/dist/cli-delegate/deferral_carry.js +16 -0
  167. package/dist/cli-delegate/gate_execute.js +2 -2
  168. package/dist/cli-delegate/roadmap_gates.js +5 -5
  169. package/dist/cli-delegate/update_roadmap_progress.js +1 -1
  170. package/dist/discovery/deprecation-report.md +1 -1
  171. package/dist/discovery/discovery-manifest.json +132 -80
  172. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  173. package/dist/discovery/discovery-manifest.summary.md +3 -3
  174. package/dist/discovery/dormancy-report.md +1 -1
  175. package/dist/discovery/orphan-report.md +1 -1
  176. package/dist/discovery/packs.json +7 -5
  177. package/dist/discovery/trust-report.md +2 -2
  178. package/dist/discovery/workspaces.json +6 -4
  179. package/dist/hooks/dispatch.js +7972 -5186
  180. package/dist/hooks/dispatch.sha256 +1 -0
  181. package/dist/install/claudePathsPlan.js +47 -6
  182. package/dist/install/claudeRuleRewrite.js +7 -0
  183. package/dist/install/emit_host_rules_cli.js +39 -36
  184. package/dist/install/globalRuleScope.js +87 -0
  185. package/dist/install/host_rule_ownership.js +160 -0
  186. package/dist/install/install.mjs +2226 -1055
  187. package/dist/install/installThinLayer.js +261 -0
  188. package/dist/install/installedRuleLinks.js +176 -0
  189. package/dist/install/wizard-plan.js +24 -0
  190. package/dist/mcp/registry-manifest.json +3 -3
  191. package/dist/mcp/server.json +2 -2
  192. package/dist/mcp/server.mjs +40913 -34865
  193. package/dist/scripts/_lib/agent_settings.js +11 -0
  194. package/dist/scripts/_lib/claude_settings_hooks.js +10 -8
  195. package/dist/scripts/_lib/installed_layer.js +378 -0
  196. package/dist/scripts/_lib/lean_projection_mode.js +218 -15
  197. package/dist/scripts/_lib/rule_consequence_class.js +45 -0
  198. package/dist/scripts/_lib/rule_injection.js +67 -10
  199. package/dist/scripts/_lib/rule_law_section.js +122 -0
  200. package/dist/scripts/_lib/thin_rules.js +458 -0
  201. package/dist/scripts/_lib/windsurf_render.js +60 -0
  202. package/dist/scripts/condense.js +14 -26
  203. package/dist/scripts/project_thin_rules.js +14 -294
  204. package/dist/server/schemas/settings.js +29 -0
  205. package/dist/shared/skillRanking.js +74 -13
  206. package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
  207. package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
  208. package/dist/ui/index.html +2 -2
  209. package/docs/contracts/settings-classes.md +10 -6
  210. package/package.json +4 -4
  211. package/src/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
  212. package/src/agent-src/scripts/deferral_carry.ts +339 -0
  213. package/src/agent-src/scripts/update_roadmap_progress.ts +4 -1
  214. package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  215. package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +302 -21
  216. package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  217. package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  218. package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
  219. package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
  220. package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  221. package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  222. package/src/config/agent-settings.template.yml +91 -22
  223. package/src/config/ci-local-parity.yml +18 -0
  224. package/src/config/continuity-surface.json +9 -2
  225. package/src/config/evaluator-budgets.json +4 -4
  226. package/src/config/gate-coverage.yml +327 -3
  227. package/src/config/gate-violation-baselines.json +21 -9
  228. package/src/config/hook-bundle-budget.json +25 -0
  229. package/src/config/hook-latency-budget.json +21 -0
  230. package/src/config/hook-token-budget.json +8 -3
  231. package/src/config/host-instruction-limits.json +33 -0
  232. package/src/config/metric-registry.yml +114 -0
  233. package/src/config/placeholder-drift-budget.json +10 -0
  234. package/src/config/routing-coverage-seed.json +38 -3
  235. package/src/config/rule-activation-census.json +7 -3
  236. package/src/config/rule-consequence-class.json +161 -0
  237. package/src/config/rule-law-ceilings.json +99 -0
  238. package/src/install/claudePathsPlan.ts +46 -4
  239. package/src/install/claudeRuleRewrite.ts +10 -1
  240. package/src/install/emit_host_rules_cli.ts +43 -34
  241. package/src/install/globalRuleScope.ts +109 -0
  242. package/src/install/host_rule_ownership.ts +170 -0
  243. package/src/install/installThinLayer.ts +312 -0
  244. package/src/install/installedRuleLinks.ts +220 -0
  245. package/src/install/wizard-plan.ts +24 -0
  246. package/src/scripts/_cli/cmd_doctor.ts +28 -90
  247. package/src/scripts/_cli/cmd_doctor_neighbours.ts +293 -0
  248. package/src/scripts/_cli/doctor_execution.ts +100 -17
  249. package/src/scripts/_cli/doctor_network_posture.ts +226 -0
  250. package/src/scripts/_dispatch.bash +16 -2
  251. package/src/scripts/_lib/agent_settings.ts +11 -0
  252. package/src/scripts/_lib/body_portable.ts +102 -0
  253. package/src/scripts/_lib/cc_transcript.ts +132 -1
  254. package/src/scripts/_lib/claude_settings_hooks.ts +10 -8
  255. package/src/scripts/_lib/concern_sla_window.ts +200 -0
  256. package/src/scripts/_lib/estate_offsets.ts +241 -0
  257. package/src/scripts/_lib/exemption_shape.ts +143 -0
  258. package/src/scripts/_lib/fnv.ts +23 -0
  259. package/src/scripts/_lib/foreign_scan.ts +106 -0
  260. package/src/scripts/_lib/forge_protection.ts +114 -0
  261. package/src/scripts/_lib/forge_reader.ts +558 -0
  262. package/src/scripts/_lib/gitignore_block.ts +314 -0
  263. package/src/scripts/_lib/global_deploy_inventory.ts +32 -0
  264. package/src/scripts/_lib/graph_feeder_record.ts +214 -0
  265. package/src/scripts/_lib/hook_settings.ts +73 -0
  266. package/src/scripts/_lib/host_capability.ts +40 -19
  267. package/src/scripts/_lib/host_env_write.ts +111 -0
  268. package/src/scripts/_lib/host_hook_merge.ts +165 -0
  269. package/src/scripts/_lib/installed_layer.ts +538 -0
  270. package/src/scripts/_lib/json_merge.ts +82 -0
  271. package/src/scripts/_lib/json_pointers.ts +93 -11
  272. package/src/scripts/_lib/lean_projection_mode.ts +275 -15
  273. package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
  274. package/src/scripts/_lib/machine_wake.ts +16 -3
  275. package/src/scripts/_lib/neighbour_census.ts +760 -0
  276. package/src/scripts/_lib/neighbour_scan.ts +122 -0
  277. package/src/scripts/_lib/neighbour_tool_use.ts +189 -0
  278. package/src/scripts/_lib/obligations.ts +119 -8
  279. package/src/scripts/_lib/probe_inputs.ts +271 -0
  280. package/src/scripts/_lib/prompt_shape.ts +46 -0
  281. package/src/scripts/_lib/read_surface_scan.ts +322 -0
  282. package/src/scripts/_lib/release_highlights.ts +2 -1
  283. package/src/scripts/_lib/release_migration_gate.ts +19 -3
  284. package/src/scripts/_lib/reserved_name_sweep.ts +73 -0
  285. package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
  286. package/src/scripts/_lib/review_coverage.ts +134 -0
  287. package/src/scripts/_lib/rule_consequence_class.ts +71 -0
  288. package/src/scripts/_lib/rule_injection.ts +130 -10
  289. package/src/scripts/_lib/rule_law_section.ts +147 -0
  290. package/src/scripts/_lib/rule_layer_overlap.ts +58 -0
  291. package/src/scripts/_lib/shell_write_shapes.ts +108 -0
  292. package/src/scripts/_lib/skill_origin.ts +254 -0
  293. package/src/scripts/_lib/structural_hiding.ts +622 -0
  294. package/src/scripts/_lib/structured_ask.ts +64 -21
  295. package/src/scripts/_lib/thin_rules.ts +540 -0
  296. package/src/scripts/_lib/touched_file_quality.ts +434 -0
  297. package/src/scripts/_lib/turn_end_refusals.ts +600 -4
  298. package/src/scripts/_lib/turn_end_transcript.ts +216 -0
  299. package/src/scripts/_lib/verification_command.ts +324 -0
  300. package/src/scripts/_lib/verification_evidence.ts +647 -0
  301. package/src/scripts/_lib/verify_clause.ts +280 -0
  302. package/src/scripts/_lib/windsurf_render.ts +63 -0
  303. package/src/scripts/ai_council/cli_call_budget.ts +13 -5
  304. package/src/scripts/ai_council/clients.ts +26 -57
  305. package/src/scripts/ai_council/config.ts +1 -1
  306. package/src/scripts/ai_council/vendor_defaults.ts +45 -0
  307. package/src/scripts/ai_team/team_dispatch.ts +4 -0
  308. package/src/scripts/audit_skill_overlap.ts +261 -0
  309. package/src/scripts/before_complete_hook.ts +573 -25
  310. package/src/scripts/bench_hook_latency.ts +412 -5
  311. package/src/scripts/bench_touched_file_quality.ts +238 -0
  312. package/src/scripts/cache_realization_report.ts +61 -1
  313. package/src/scripts/check_argument_hint.ts +287 -0
  314. package/src/scripts/check_claims.ts +125 -1
  315. package/src/scripts/check_enforcement_coverage.ts +38 -9
  316. package/src/scripts/check_enforcement_matrix.ts +621 -22
  317. package/src/scripts/check_estate_count.ts +130 -153
  318. package/src/scripts/check_finding_dispositions.ts +138 -7
  319. package/src/scripts/check_generator_sync.ts +71 -0
  320. package/src/scripts/check_hook_bundle_composition.ts +370 -0
  321. package/src/scripts/check_host_docs_digest.ts +786 -0
  322. package/src/scripts/check_host_format_column.ts +559 -0
  323. package/src/scripts/check_input_references.ts +228 -0
  324. package/src/scripts/check_inputs_parity.ts +235 -0
  325. package/src/scripts/check_kernel_edit_ratified.ts +89 -9
  326. package/src/scripts/check_kill_switch_table.ts +294 -0
  327. package/src/scripts/check_memory.ts +7 -1
  328. package/src/scripts/check_placeholder_drift.ts +221 -0
  329. package/src/scripts/check_read_surface_coverage.ts +515 -0
  330. package/src/scripts/check_release_highlights.ts +10 -0
  331. package/src/scripts/check_release_holds.ts +2 -1
  332. package/src/scripts/check_release_pr_shape.ts +38 -15
  333. package/src/scripts/check_routing_coverage.ts +378 -11
  334. package/src/scripts/check_standing_rule_delivery.ts +33 -13
  335. package/src/scripts/check_static_parity.ts +17 -0
  336. package/src/scripts/check_verify_expectation_delta.ts +521 -0
  337. package/src/scripts/ci_settle.ts +145 -19
  338. package/src/scripts/closure_scan.ts +115 -1
  339. package/src/scripts/code_graph/detect.ts +127 -7
  340. package/src/scripts/code_graph/foreign.ts +194 -0
  341. package/src/scripts/code_graph/query.ts +217 -4
  342. package/src/scripts/code_graph/types.ts +26 -2
  343. package/src/scripts/code_graph/validate.ts +3 -1
  344. package/src/scripts/code_graph/verbs.ts +103 -0
  345. package/src/scripts/compile_hook_manifest.ts +90 -14
  346. package/src/scripts/condense.ts +14 -27
  347. package/src/scripts/estate_exemption_shape.ts +202 -0
  348. package/src/scripts/external_sources_denylist.json +29 -4
  349. package/src/scripts/generate_host_cost_table.ts +1 -1
  350. package/src/scripts/hook_manifest.json +1 -1
  351. package/src/scripts/hook_manifest.yaml +192 -14
  352. package/src/scripts/hooks/block_config_weakening.ts +294 -4
  353. package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
  354. package/src/scripts/hooks/block_plumbing_writes.ts +342 -0
  355. package/src/scripts/hooks/bundle_integrity.ts +291 -0
  356. package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
  357. package/src/scripts/hooks/code_graph_context_hook.ts +253 -25
  358. package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
  359. package/src/scripts/hooks/concern_failure_policy.ts +280 -0
  360. package/src/scripts/hooks/concern_registry.ts +2 -0
  361. package/src/scripts/hooks/concern_timings.ts +94 -0
  362. package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
  363. package/src/scripts/hooks/design_pass_hook.ts +100 -9
  364. package/src/scripts/hooks/design_slop_hook.ts +1 -2
  365. package/src/scripts/hooks/dispatch_hook.ts +77 -71
  366. package/src/scripts/hooks/edit_shape_hook.ts +1 -2
  367. package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
  368. package/src/scripts/hooks/evidence_independence.ts +1 -2
  369. package/src/scripts/hooks/exit_codes.ts +129 -0
  370. package/src/scripts/hooks/host_lowering.json +1 -0
  371. package/src/scripts/hooks/host_lowering.ts +146 -3
  372. package/src/scripts/hooks/host_lowering.yaml +262 -46
  373. package/src/scripts/hooks/host_semantics.ts +30 -2
  374. package/src/scripts/hooks/injection_budget.ts +9 -2
  375. package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
  376. package/src/scripts/hooks/journal_record_hook.ts +1 -2
  377. package/src/scripts/hooks/memory_recall_hook.ts +1 -2
  378. package/src/scripts/hooks/obligation_settle_hook.ts +151 -5
  379. package/src/scripts/hooks/one_question_per_ask_hook.ts +43 -6
  380. package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
  381. package/src/scripts/hooks/payload_stub.ts +10 -1
  382. package/src/scripts/hooks/permission_gate.ts +1 -2
  383. package/src/scripts/hooks/push_settle_hook.ts +1 -3
  384. package/src/scripts/hooks/replay_hook.ts +2 -1
  385. package/src/scripts/hooks/reread_guard_hook.ts +1 -2
  386. package/src/scripts/hooks/review_baseline_hook.ts +1 -2
  387. package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
  388. package/src/scripts/hooks/rule_inject_hook.ts +765 -68
  389. package/src/scripts/hooks/run_continuation_hook.ts +13 -15
  390. package/src/scripts/hooks/skill_route_hook.ts +1 -2
  391. package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
  392. package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
  393. package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
  394. package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
  395. package/src/scripts/hooks/table_fingerprint.ts +70 -0
  396. package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
  397. package/src/scripts/hooks/telemetry_usage_hook.ts +65 -10
  398. package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
  399. package/src/scripts/hooks/turn_end_gate_hook.ts +421 -324
  400. package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
  401. package/src/scripts/hooks_status.ts +112 -0
  402. package/src/scripts/injection_scan_hook.ts +7 -0
  403. package/src/scripts/install.ts +75 -189
  404. package/src/scripts/installed_layer_report.ts +103 -0
  405. package/src/scripts/language_mirror_hook.ts +15 -6
  406. package/src/scripts/learning_sidecar.ts +119 -8
  407. package/src/scripts/lint_deny_text.ts +419 -0
  408. package/src/scripts/lint_exit_codes.ts +262 -0
  409. package/src/scripts/lint_hook_manifest.ts +108 -0
  410. package/src/scripts/lint_major_migration_sections.ts +156 -26
  411. package/src/scripts/lint_pack_boundaries.ts +63 -5
  412. package/src/scripts/lint_roadmap_blockers.ts +78 -12
  413. package/src/scripts/lint_rule_law_section.ts +517 -0
  414. package/src/scripts/lint_skill_descriptions.ts +1 -1
  415. package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
  416. package/src/scripts/lint_workflow_security.ts +161 -5
  417. package/src/scripts/mcp_server/consumer_tool_catalog.json +53 -0
  418. package/src/scripts/mcp_server/graph_tools.ts +72 -3
  419. package/src/scripts/mcp_server/prompts.ts +142 -1
  420. package/src/scripts/measure_routing_signal.ts +54 -16
  421. package/src/scripts/measure_skill_ranker_baseline.ts +515 -53
  422. package/src/scripts/measure_turn_end_gate.ts +193 -0
  423. package/src/scripts/memory_learn_hook.ts +91 -6
  424. package/src/scripts/memory_lookup.ts +1 -1
  425. package/src/scripts/model_rule_injection.ts +52 -5
  426. package/src/scripts/prepack-check.mjs +53 -0
  427. package/src/scripts/project_thin_rules.ts +34 -327
  428. package/src/scripts/release_publication.ts +13 -1
  429. package/src/scripts/report_evidence_temperature.ts +657 -0
  430. package/src/scripts/report_installed_rule_links.ts +157 -0
  431. package/src/scripts/report_invocation_surface.ts +383 -0
  432. package/src/scripts/report_label_agreement.ts +233 -0
  433. package/src/scripts/report_obligation_carriers.ts +699 -0
  434. package/src/scripts/report_obligation_mechanism.ts +230 -0
  435. package/src/scripts/report_obligation_writer_split.ts +236 -0
  436. package/src/scripts/report_single_token_triggers.ts +277 -0
  437. package/src/scripts/report_skill_menu_census.ts +208 -0
  438. package/src/scripts/report_skill_ranker_confusion.ts +442 -0
  439. package/src/scripts/report_standing_payload_by_host.ts +188 -15
  440. package/src/scripts/report_verification_record_coverage.ts +306 -0
  441. package/src/scripts/roadmap_verify_share.ts +213 -0
  442. package/src/scripts/rule_activation_census.ts +5 -2
  443. package/src/scripts/rule_trigger_eval.ts +9 -9
  444. package/src/scripts/schemas/command.schema.json +44 -0
  445. package/src/scripts/schemas/review-findings.schema.json +7 -2
  446. package/src/scripts/schemas/rule.schema.json +5 -0
  447. package/src/scripts/schemas/skill.schema.json +44 -0
  448. package/src/scripts/self_review_gate.ts +421 -17
  449. package/src/scripts/semantic_parity_census.ts +314 -0
  450. package/src/scripts/session_eol_report.ts +54 -1
  451. package/src/scripts/skill_tools/score_skill_relevance.ts +290 -25
  452. package/src/scripts/sweep_dead_scan_roots.ts +1 -1
  453. package/src/scripts/sweep_skill_ranker_signals.ts +129 -0
  454. package/src/scripts/sync_gitignore.ts +29 -235
  455. package/src/scripts/trigger_eval_grandfather.json +0 -7
  456. package/src/scripts/trigger_eval_rotation.ts +122 -27
  457. package/src/scripts/ui_conformance_probe.ts +156 -4
  458. package/src/scripts/update_prices.ts +85 -3
  459. package/src/scripts/write_bundle_digest.mjs +74 -0
  460. package/src/server/schemas/settings.ts +41 -0
  461. package/src/shared/skillRanking.ts +117 -11
  462. package/src/templates/consumer-settings/ONBOARDING.md +1 -0
  463. package/dist/cli-delegate/chunk-FXYUYU4N.js +0 -4416
  464. package/dist/cli-delegate/chunk-QJWAUMJW.js +0 -1072
  465. package/dist/cli-delegate/chunk-YDPILCOL.js +0 -152
  466. package/dist/ui/assets/index-DH1xc62v.js +0 -29
  467. package/dist/ui/assets/index-jSfMW8_t.css +0 -1
@@ -0,0 +1,342 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * PreToolUse guard: refuse a hand edit to a hook BUILD OUTPUT.
4
+ *
5
+ * road-to-a-kernel-that-guards-its-plumbing 1.2 — the other half of a
6
+ * deliberate split, and the split is the whole design:
7
+ *
8
+ * · plumbing SOURCES (`hook_manifest.yaml`, `host_lowering.yaml`, the
9
+ * `*-dispatcher.sh` trampolines, both hook budget files) are edited
10
+ * legitimately all the time. They carry a RECORD — the ADR-268 § 4
11
+ * ratification gate, path set extended by step 1.1 — not a deny.
12
+ * · plumbing BUILD OUTPUTS have no legitimate hand edit at all. They carry a
13
+ * DENY, which is this file.
14
+ *
15
+ * One mechanism per file class. A record for a file whose every hand edit is a
16
+ * mistake would be a record of a mistake; a deny on a file people edit on
17
+ * purpose would be a wedge.
18
+ *
19
+ * WHAT IS REFUSED.
20
+ *
21
+ * - `dist/hooks/dispatch.js` — the esbuild bundle (`npm run build:hooks`).
22
+ * Every concern the dispatcher runs is inlined here. A hand edit survives
23
+ * until the next build, reaches every dispatch in the meantime, and is
24
+ * invisible in a source review.
25
+ * - `hooks/hooks.json` — the host binding file, written by `condense.ts` via
26
+ * `task sync`. A hand edit here silently unbinds a guard.
27
+ * - `src/scripts/hook_manifest.json` and `src/scripts/hooks/host_lowering.json`
28
+ * — the compiled plumbing tables, written by `compile_hook_manifest`
29
+ * (road-to-blocking-severities 2.2). These are the ones the RUNTIME actually
30
+ * reads: `dispatch_hook._load_yaml` and `host_lowering.resolveTable` serve
31
+ * the compiled body in preference to the YAML whenever its fingerprint
32
+ * matches, so an edited table is what every host is told about which concern
33
+ * runs and which slot can refuse, while the reviewed YAML beside it still
34
+ * says something else.
35
+ *
36
+ * All four are matched under any parent directory, because a consumer install
37
+ * carries them under its own project root rather than under this repository's.
38
+ *
39
+ * WHAT STILL PASSES, and why that is not a hole.
40
+ *
41
+ * `npm run build:hooks` and `task sync` are the legitimate writers, and both
42
+ * reach these files through a build tool rather than through a shell write
43
+ * shape: the command string carries no redirect into the path, no in-place
44
+ * `sed`, no `tee`/`mv`/`cp` naming it. `shellWriteTarget` matches shapes, not
45
+ * effects, so the builds pass and a hand edit does not. Stated here because it
46
+ * is the property that makes this guard safe to bind `fail_closed`.
47
+ *
48
+ * KNOWN OPEN, stated rather than implied.
49
+ *
50
+ * A write whose target never appears as a path-shaped token — built inside an
51
+ * interpreter's own argument, as in `python3 -c "open('hooks/hooks.json','w')…"`
52
+ * — carries none of the recognised shapes and is NOT detected. This is the same
53
+ * residual `block_kernel_rule_writes.ts` declares for itself and the same
54
+ * boundary: a guard, not write mediation. The honest claim is that the cheap
55
+ * and the accidental paths are closed, not that the determined one is.
56
+ *
57
+ * "Through an interpreter" does not by itself mean undetected, and the tests
58
+ * pin both directions: `sh -c 'printf x > hooks/hooks.json'` IS refused,
59
+ * because the redirect survives the tokenizer inside the quoted argument.
60
+ *
61
+ * MANIFEST DECLARATION.
62
+ *
63
+ * `fail_closed: true`, `severity: blocking`. The fail-closed guarantee is
64
+ * about a DETECTED write: a malformed envelope with no target at all allows,
65
+ * exactly as the kernel guard does, because a guard that blocks what it cannot
66
+ * read turns a shape defect into a wedged session.
67
+ *
68
+ * No agent-accessible kill switch — deliberately, and it is also what
69
+ * `lint_deny_text` (step 1.3) requires of every blocking concern's deny text.
70
+ * The legitimate change is to rebuild the file from its source.
71
+ *
72
+ * Exit codes (docs/contracts/hook-architecture-v1.md):
73
+ * 0 — allow
74
+ * 1 — block
75
+ */
76
+
77
+ import * as fs from 'node:fs';
78
+ import * as path from 'node:path';
79
+ import { fileURLToPath, pathToFileURL } from 'node:url';
80
+
81
+ import { shellWriteTarget } from '../_lib/shell_write_shapes.js';
82
+ import { EDIT_TOOLS } from '../minimal_safe_diff_hook.js';
83
+ import { COMMAND_TOOLS } from '../before_complete_hook.js';
84
+ import { readHookStdin } from './hook_stdin.js';
85
+
86
+ const _HERE = fileURLToPath(import.meta.url);
87
+
88
+ type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue };
89
+ type JsonObject = { [k: string]: JsonValue };
90
+
91
+ function _isObject(v: JsonValue | undefined): v is JsonObject {
92
+ return v !== null && v !== undefined && typeof v === 'object' && !Array.isArray(v);
93
+ }
94
+
95
+ /**
96
+ * The generated plumbing artifacts, as trailing path segments.
97
+ *
98
+ * A trailing match rather than a repo-relative one because the same two files
99
+ * exist under every consumer project root and under this repository's, and a
100
+ * guard anchored to one tree would be silent in the other.
101
+ */
102
+ export const PLUMBING_BUILD_OUTPUTS: readonly string[] = [
103
+ 'dist/hooks/dispatch.js',
104
+ 'hooks/hooks.json',
105
+ 'src/scripts/hook_manifest.json',
106
+ 'src/scripts/hooks/host_lowering.json',
107
+ ];
108
+
109
+ /**
110
+ * Where each output comes from, printed in the deny so the reader is told what
111
+ * to run instead of only what not to do.
112
+ *
113
+ * road-to-blocking-severities 2.2 added the two compiled tables. They are the
114
+ * same KIND of file as the first two — written by a build, never by hand — and
115
+ * until this step they were refused by nothing, which mattered more here than
116
+ * it would have for an ordinary generated artifact: `dispatch_hook._load_yaml`
117
+ * and `host_lowering.resolveTable` both serve the compiled body IN PREFERENCE
118
+ * to the YAML whenever its fingerprint matches, so an edited JSON is what every
119
+ * host is told about which concern runs and which slot can refuse.
120
+ *
121
+ * Their YAML sources stay OUT of this set and keep carrying a ratification
122
+ * record instead — a hand edit there is legitimate and is exactly how the
123
+ * plumbing is maintained. The split is the one the guard's header already
124
+ * draws: a deny for files with no legitimate hand edit, a record for files
125
+ * whose hand edits are the point.
126
+ */
127
+ const REGENERATED_BY: Readonly<Record<string, string>> = {
128
+ 'dist/hooks/dispatch.js': 'npm run build:hooks',
129
+ 'hooks/hooks.json': 'task sync',
130
+ 'src/scripts/hook_manifest.json':
131
+ './scripts-run src/scripts/compile_hook_manifest --table manifest',
132
+ 'src/scripts/hooks/host_lowering.json':
133
+ './scripts-run src/scripts/compile_hook_manifest --table host-lowering',
134
+ };
135
+
136
+ // Keys across platforms that carry the tool's target file path.
137
+ const _PATH_KEYS: readonly string[] = [
138
+ 'file_path',
139
+ 'path',
140
+ 'target_file',
141
+ 'filename',
142
+ 'filePath',
143
+ 'notebook_path',
144
+ ];
145
+
146
+ /** Normalize separators; strip a leading "./" run so segment checks are robust. */
147
+ function _normalize(p: string): string {
148
+ return p.replace(/\\/g, '/').replace(/^(\.\/)+/, '');
149
+ }
150
+
151
+ /**
152
+ * The build output `filePath` names, or `null`.
153
+ *
154
+ * Two matching rules, because the guard has two kinds of input and only one of
155
+ * them carries enough information to be exact:
156
+ *
157
+ * · a RELATIVE path is matched EXACTLY, after a leading `./` or `../` run is
158
+ * stripped. `dist/hooks/dispatch.js` and `./hooks/hooks.json` are the
159
+ * governed files; `docs/hooks/hooks.json` is a different file that happens
160
+ * to end the same way, and refusing it would be an over-block on a file
161
+ * nothing generates.
162
+ * · an ABSOLUTE path is matched on its trailing segments, because the guard
163
+ * does not know where the project root is and a host that sends absolute
164
+ * paths — Claude Code sends every `file_path` absolute — would otherwise
165
+ * escape it entirely. A fail-open on the main path is the worse of the two
166
+ * errors.
167
+ *
168
+ * The residual is named rather than hidden: an absolute path ending in
169
+ * `…/hooks/hooks.json` under an unrelated parent is refused too. That is the
170
+ * conservative direction for a two-file guard, and the fix if it ever bites is
171
+ * to relativise against the envelope's `workspace_root`.
172
+ *
173
+ * A bare basename (`dispatch.js`) never matches under either rule — it is not
174
+ * evidence that the governed file is the target.
175
+ */
176
+ export function targets_plumbing_output(filePath: string): string | null {
177
+ if (!filePath) {
178
+ return null;
179
+ }
180
+ const normalized = _normalize(filePath);
181
+ const absolute = normalized.startsWith('/') || /^[A-Za-z]:\//.test(normalized);
182
+ const segments = normalized.split('/').filter((s) => s.length > 0 && s !== '.');
183
+ const relevant = absolute ? segments : segments.filter((s) => s !== '..');
184
+
185
+ for (const output of PLUMBING_BUILD_OUTPUTS) {
186
+ const want = output.split('/');
187
+ if (absolute) {
188
+ if (relevant.length < want.length) {
189
+ continue;
190
+ }
191
+ const tail = relevant.slice(relevant.length - want.length);
192
+ if (tail.every((s, i) => s === want[i])) {
193
+ return output;
194
+ }
195
+ continue;
196
+ }
197
+ if (relevant.length === want.length && relevant.every((s, i) => s === want[i])) {
198
+ return output;
199
+ }
200
+ }
201
+ return null;
202
+ }
203
+
204
+ /** Best-effort (toolName, candidate target paths) read off a PreToolUse envelope. */
205
+ function _extract(envelope: JsonObject): { tool: string; paths: string[] } {
206
+ const payload = _isObject(envelope['payload']) ? envelope['payload'] : envelope;
207
+ const nameVal =
208
+ payload['tool_name'] ??
209
+ payload['toolName'] ??
210
+ payload['tool'] ??
211
+ envelope['tool_name'] ??
212
+ envelope['tool'];
213
+ const tool = typeof nameVal === 'string' ? nameVal : '';
214
+
215
+ const ti = _isObject(payload['tool_input'])
216
+ ? payload['tool_input']
217
+ : _isObject(envelope['tool_input'])
218
+ ? envelope['tool_input']
219
+ : null;
220
+
221
+ const paths: string[] = [];
222
+ if (ti !== null) {
223
+ for (const key of _PATH_KEYS) {
224
+ const v = ti[key];
225
+ if (typeof v === 'string' && v) {
226
+ paths.push(v);
227
+ }
228
+ }
229
+ }
230
+ return { tool, paths };
231
+ }
232
+
233
+ /** Best-effort read of a shell command off a PreToolUse envelope. */
234
+ function _extract_command(envelope: JsonObject): string | null {
235
+ const payload = _isObject(envelope['payload']) ? envelope['payload'] : envelope;
236
+ const ti = _isObject(payload['tool_input'])
237
+ ? payload['tool_input']
238
+ : _isObject(envelope['tool_input'])
239
+ ? envelope['tool_input']
240
+ : null;
241
+ for (const src of [ti, payload]) {
242
+ if (src === null) {
243
+ continue;
244
+ }
245
+ const v = src['command'];
246
+ if (typeof v === 'string' && v) {
247
+ return v;
248
+ }
249
+ }
250
+ return null;
251
+ }
252
+
253
+ /** Return (blocked, reason) for one PreToolUse envelope. Pure — no I/O. */
254
+ export function check_envelope(envelope: JsonObject): [boolean, string] {
255
+ const { tool, paths } = _extract(envelope);
256
+ if (!tool) {
257
+ return [false, ''];
258
+ }
259
+ const deny = (output: string): [boolean, string] => [
260
+ true,
261
+ `${output} is a build output, not a source — regenerate it with ` +
262
+ `\`${REGENERATED_BY[output] ?? 'the build that writes it'}\`. ` +
263
+ 'See docs/contracts/hook-architecture-v1.md § Plumbing.',
264
+ ];
265
+ if (EDIT_TOOLS.has(tool)) {
266
+ for (const p of paths) {
267
+ const output = targets_plumbing_output(p);
268
+ if (output !== null) {
269
+ return deny(output);
270
+ }
271
+ }
272
+ return [false, ''];
273
+ }
274
+ if (COMMAND_TOOLS.has(tool)) {
275
+ const cmd = _extract_command(envelope);
276
+ if (cmd !== null) {
277
+ const output = shellWriteTarget(cmd, targets_plumbing_output);
278
+ if (output !== null) {
279
+ return deny(output);
280
+ }
281
+ }
282
+ }
283
+ return [false, ''];
284
+ }
285
+
286
+ function _asObject(v: JsonValue | undefined): JsonObject | null {
287
+ return _isObject(v) ? v : null;
288
+ }
289
+
290
+ export function main(): number {
291
+ const raw = readHookStdin();
292
+ let envelope: JsonObject = {};
293
+ if (raw.trim()) {
294
+ try {
295
+ const obj = JSON.parse(raw) as JsonValue;
296
+ envelope = _asObject(obj) ?? {};
297
+ } catch {
298
+ // Malformed envelope — no target to decide about; allow. The
299
+ // fail-closed guarantee applies to a DETECTED write.
300
+ return 0;
301
+ }
302
+ }
303
+
304
+ const [blocked, reason] = check_envelope(envelope);
305
+ if (blocked) {
306
+ process.stderr.write(
307
+ `block-plumbing-writes: BLOCKED — ${reason}\n` +
308
+ ' This file is generated. Edit the source it is built from, then rebuild.\n' +
309
+ ' Contract: docs/contracts/hook-architecture-v1.md\n',
310
+ );
311
+ return 1; // EXIT_BLOCK
312
+ }
313
+
314
+ return 0; // EXIT_ALLOW
315
+ }
316
+
317
+ // Bundle-safety: never auto-run when inlined into an esbuild bundle, where
318
+ // every module shares the bundle's `import.meta.url`.
319
+ declare const __AGENT_CONFIG_BUNDLE__: boolean | undefined;
320
+ function _isCliEntry(): boolean {
321
+ if (typeof __AGENT_CONFIG_BUNDLE__ !== 'undefined' && __AGENT_CONFIG_BUNDLE__) {
322
+ return false;
323
+ }
324
+ if (process.argv[1] === undefined) {
325
+ return false;
326
+ }
327
+ const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
328
+ if (import.meta.url === argvUrl) {
329
+ return true;
330
+ }
331
+ try {
332
+ const here = fs.realpathSync(fileURLToPath(import.meta.url));
333
+ const argv = fs.realpathSync(path.resolve(process.argv[1]));
334
+ return here === argv;
335
+ } catch {
336
+ return false;
337
+ }
338
+ }
339
+
340
+ if (_isCliEntry() || (typeof __AGENT_CONFIG_BUNDLE__ === 'undefined' && process.argv[1] === _HERE)) {
341
+ process.exitCode = main();
342
+ }
@@ -0,0 +1,291 @@
1
+ import { createHash } from 'node:crypto';
2
+ import * as fs from 'node:fs';
3
+ import * as os from 'node:os';
4
+ import * as path from 'node:path';
5
+
6
+ import { fix_hint, log_dispatch_issue } from './dispatch_issues.js';
7
+ import { EXIT_BLOCK } from './exit_codes.js';
8
+ import { emitFor } from './host_semantics.js';
9
+
10
+ /**
11
+ * bundle_integrity — does the dispatcher's own bundle still match what the
12
+ * build produced?
13
+ *
14
+ * Step 3.1 of `road-to-a-kernel-that-guards-its-plumbing`. `dist/hooks/
15
+ * dispatch.js` is a generated file with no legitimate hand edit, and until
16
+ * this landed an edit to it survived until the next build, reached every
17
+ * dispatch meanwhile, and was invisible in a source review.
18
+ * `check_hook_bundle_content` catches that in CI and `block_plumbing_writes`
19
+ * refuses the edit at tool-call time; neither of them is looking at the bundle
20
+ * the process is EXECUTING at the moment it runs a blocking guard.
21
+ *
22
+ * Three verdicts, and the gap between two of them is the whole design:
23
+ *
24
+ * - `ok` — the bundle hashes to the sidecar.
25
+ * - `mismatch` — it does not. The caller refuses on a slot carrying a blocking
26
+ * concern and warns otherwise.
27
+ * - `unverifiable` — there is no sidecar, or it cannot be read or parsed.
28
+ *
29
+ * `unverifiable` NEVER refuses, and that is a decision rather than an
30
+ * oversight. A consumer install from a package built before the sidecar
31
+ * existed has no sidecar; so does any tree where `dist/` was produced by
32
+ * something other than `npm run build:hooks`. Refusing there would convert a
33
+ * missing optional artifact into a total outage of the agent's tooling, which
34
+ * is the failure `hook-architecture-v1.md` already rules on one level up when
35
+ * it fixes a MISSING dispatcher as a silent allow even for a `fail_closed`
36
+ * concern. An absent control is not a tripped control.
37
+ *
38
+ * This is not a signature. Anyone who can rewrite the bundle can rewrite the
39
+ * sidecar beside it, and saying otherwise would be the coverage inflation this
40
+ * roadmap exists to remove. What it catches is the hand edit and the partial
41
+ * write — the cases where somebody changed one file and not the other.
42
+ */
43
+
44
+ /** What a verification produced. */
45
+ export type IntegrityVerdict =
46
+ | { state: 'ok'; sha: string }
47
+ | { state: 'mismatch'; expected: string; actual: string }
48
+ | { state: 'unverifiable'; reason: string };
49
+
50
+ /**
51
+ * The cheap identity of a file — what a later dispatch compares instead of
52
+ * re-hashing 1.5 MB.
53
+ *
54
+ * Size AND mtime, never mtime alone: a rebuild that produces a byte-identical
55
+ * bundle bumps mtime and would force a needless re-hash (harmless), while a
56
+ * same-size edit at the same mtime is the one pair this cannot see. That pair
57
+ * needs a deliberate timestamp forge, which is a strictly harder act than the
58
+ * hand edit this defends against, and the honest statement is that the cache
59
+ * trades that case for 0.0101 ms per dispatch against 0.470 ms.
60
+ *
61
+ * 0.0101 ms is the WHOLE cached path measured — reading the stamp back plus
62
+ * the stat — not the stat alone, which is 0.0010 ms. The first version of this
63
+ * comment quoted the stat, which described one of the two syscalls the
64
+ * dispatcher actually pays.
65
+ */
66
+ export interface BundleStamp {
67
+ sha: string;
68
+ size: number;
69
+ mtimeMs: number;
70
+ }
71
+
72
+ /** Read the expected digest out of a `shasum -a 256` style sidecar. */
73
+ export function parseSidecar(text: string): string | null {
74
+ // `<64 hex> <filename>` — the two-space form `shasum` emits. Tolerant of
75
+ // any run of whitespace and of a trailing newline, because an operator who
76
+ // regenerates the file by hand should not be punished for one space.
77
+ const m = /^([0-9a-f]{64})\s/u.exec(text.trim());
78
+ return m === null ? null : (m[1] as string);
79
+ }
80
+
81
+ /** Where the sidecar for a given bundle lives. */
82
+ export function sidecarPathFor(bundlePath: string): string {
83
+ return bundlePath.replace(/\.js$/u, '') + '.sha256';
84
+ }
85
+
86
+ /** SHA-256 of a file's bytes, or null when it cannot be read. */
87
+ export function hashFile(p: string): string | null {
88
+ try {
89
+ return createHash('sha256').update(fs.readFileSync(p)).digest('hex');
90
+ } catch {
91
+ return null;
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Compare a bundle against its sidecar.
97
+ *
98
+ * Pure apart from the two reads, so the caller's caching and the verdict logic
99
+ * can be tested without a dispatch.
100
+ */
101
+ export function verifyBundle(bundlePath: string): IntegrityVerdict {
102
+ const sidecar = sidecarPathFor(bundlePath);
103
+ let expected: string | null;
104
+ try {
105
+ expected = parseSidecar(fs.readFileSync(sidecar, 'utf-8'));
106
+ } catch {
107
+ return { state: 'unverifiable', reason: `no sidecar at ${path.basename(sidecar)}` };
108
+ }
109
+ if (expected === null) {
110
+ return { state: 'unverifiable', reason: `sidecar at ${path.basename(sidecar)} is malformed` };
111
+ }
112
+ const actual = hashFile(bundlePath);
113
+ if (actual === null) {
114
+ return { state: 'unverifiable', reason: `bundle at ${path.basename(bundlePath)} is unreadable` };
115
+ }
116
+ return actual === expected ? { state: 'ok', sha: actual } : { state: 'mismatch', expected, actual };
117
+ }
118
+
119
+ /** The cheap identity of the bundle right now, or null when it is not there. */
120
+ export function stampOf(bundlePath: string, sha: string): BundleStamp | null {
121
+ try {
122
+ const st = fs.statSync(bundlePath);
123
+ return { sha, size: st.size, mtimeMs: Math.floor(st.mtimeMs) };
124
+ } catch {
125
+ return null;
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Is a cached stamp still describing the file on disk?
131
+ *
132
+ * Returns false whenever the answer is not a confident yes — an unreadable
133
+ * file re-verifies rather than reusing a stamp that may describe something
134
+ * else.
135
+ */
136
+ export function stampMatches(bundlePath: string, cached: BundleStamp | null | undefined): boolean {
137
+ if (cached === null || cached === undefined) return false;
138
+ try {
139
+ const st = fs.statSync(bundlePath);
140
+ return st.size === cached.size && Math.floor(st.mtimeMs) === cached.mtimeMs;
141
+ } catch {
142
+ return false;
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Where the once-per-bundle stamp is cached.
148
+ *
149
+ * The step wrote "caches `{sha, size, mtime}` in session state"; this uses the
150
+ * OS temp dir instead, and the deviation is deliberate. Session state under
151
+ * `agents/runtime/state/` is not written in replay mode — which is the mode
152
+ * `bench_hook_latency` runs in — so a session-state cache would be skipped in
153
+ * exactly the harness that has to measure the cached path, and the step's own
154
+ * verify line asks for `bench_hook_latency --gate` green with a per-dispatch
155
+ * cost of a stat call. It also means a host that never emits `session_start`
156
+ * would get no cache and no check at all.
157
+ *
158
+ * A temp-dir cache is content-addressed by the bundle's own path, so it needs
159
+ * no session identity, self-heals on the first dispatch of any session, works
160
+ * in a consumer install, and degrades to "verify every time" when the temp dir
161
+ * is unwritable — slower, never wrong.
162
+ */
163
+ export function stampCachePath(bundlePath: string, tmpDir: string): string {
164
+ const key = createHash('sha256').update(bundlePath).digest('hex').slice(0, 16);
165
+ return path.join(tmpDir, `agent-config-bundle-integrity-${key}.json`);
166
+ }
167
+
168
+ /** Read a cached stamp, or null when there is not a usable one. */
169
+ export function readStamp(cachePath: string): BundleStamp | null {
170
+ try {
171
+ const raw = JSON.parse(fs.readFileSync(cachePath, 'utf-8')) as Partial<BundleStamp>;
172
+ if (
173
+ typeof raw.sha === 'string' &&
174
+ typeof raw.size === 'number' &&
175
+ typeof raw.mtimeMs === 'number'
176
+ ) {
177
+ return { sha: raw.sha, size: raw.size, mtimeMs: raw.mtimeMs };
178
+ }
179
+ } catch {
180
+ // Absent, unreadable or malformed — all three mean "no cache", which
181
+ // costs a re-verification and never a wrong answer.
182
+ }
183
+ return null;
184
+ }
185
+
186
+ /** Persist a stamp. Failure is silent: a cache that cannot be written is a cache miss. */
187
+ export function writeStamp(cachePath: string, stamp: BundleStamp): void {
188
+ try {
189
+ fs.writeFileSync(cachePath, JSON.stringify(stamp), 'utf-8');
190
+ } catch {
191
+ /* a cache is an optimisation, never a control */
192
+ }
193
+ }
194
+
195
+ /**
196
+ * The whole check, cache included — hash once per bundle identity, stat after.
197
+ *
198
+ * Returns the verdict plus whether this call had to do the expensive part, so
199
+ * a caller (and a test) can assert the cache actually caches rather than
200
+ * trusting that it does.
201
+ */
202
+ export function verifyBundleCached(
203
+ bundlePath: string,
204
+ tmpDir: string,
205
+ ): { verdict: IntegrityVerdict; hashed: boolean } {
206
+ const cachePath = stampCachePath(bundlePath, tmpDir);
207
+ const cached = readStamp(cachePath);
208
+ if (stampMatches(bundlePath, cached) && cached !== null) {
209
+ return { verdict: { state: 'ok', sha: cached.sha }, hashed: false };
210
+ }
211
+ const verdict = verifyBundle(bundlePath);
212
+ if (verdict.state === 'ok') {
213
+ const stamp = stampOf(bundlePath, verdict.sha);
214
+ if (stamp !== null) writeStamp(cachePath, stamp);
215
+ }
216
+ // A `mismatch` is deliberately NOT cached. Caching it would be caching a
217
+ // refusal, and the next legitimate rebuild has to be able to clear it
218
+ // without anyone knowing a cache existed.
219
+ return { verdict, hashed: true };
220
+ }
221
+
222
+ /** What the dispatcher should do about the verdict it just got. */
223
+ export type IntegrityAction =
224
+ | { action: 'allow' }
225
+ | { action: 'warn'; detail: string }
226
+ | { action: 'block'; detail: string };
227
+
228
+ /**
229
+ * Turn a verdict into a dispatch decision.
230
+ *
231
+ * Separated from the dispatcher so the SEVERITY half of the rule is testable
232
+ * without a dispatch — and because `dispatch_hook.ts` sits against the
233
+ * 1,500-line source budget, which refused the inline version.
234
+ *
235
+ * `hasBlockingConcern` is the discriminator rather than the slot name: a slot
236
+ * carrying only advisory concerns could not have refused anything anyway, so
237
+ * blocking there would let a stale digest wedge a chain with no authority to
238
+ * begin with.
239
+ */
240
+ export function integrityAction(
241
+ verdict: IntegrityVerdict,
242
+ hasBlockingConcern: boolean,
243
+ ): IntegrityAction {
244
+ if (verdict.state !== 'mismatch') {
245
+ // `ok` and `unverifiable` are both allow, and they are both SILENT.
246
+ // See the header for why an absent sidecar is not a tripped control.
247
+ return { action: 'allow' };
248
+ }
249
+ const detail =
250
+ `dist/hooks/dispatch.js does not match its build digest ` +
251
+ `(expected ${verdict.expected.slice(0, 12)}…, got ${verdict.actual.slice(0, 12)}…). ` +
252
+ `The bundle is generated by \`npm run build:hooks\` and has no legitimate ` +
253
+ `hand edit; rebuild to clear this.`;
254
+ return hasBlockingConcern ? { action: 'block', detail } : { action: 'warn', detail };
255
+ }
256
+
257
+ /**
258
+ * The whole gate, side effects included — verify, log, and decide the exit.
259
+ *
260
+ * Returns an exit code when the dispatch must stop, `null` when it continues.
261
+ * It lives here rather than inline in `dispatch_hook.ts` for two reasons, and
262
+ * the second is the load-bearing one: that file sits against the 1,500-line
263
+ * source budget and `check_source_size_budget` refused the inline version, and
264
+ * a gate whose logging and emission are inside the dispatcher can only be
265
+ * tested by running a dispatch.
266
+ */
267
+ export function runBundleIntegrityGate(
268
+ bundlePath: string,
269
+ platform: string,
270
+ event: string,
271
+ hasBlockingConcern: boolean,
272
+ tmpDir: string = os.tmpdir(),
273
+ ): number | null {
274
+ const { verdict } = verifyBundleCached(bundlePath, tmpDir);
275
+ const act = integrityAction(verdict, hasBlockingConcern);
276
+ if (act.action === 'allow') return null;
277
+ const line = `plumbing-integrity: ${act.detail}`;
278
+ try {
279
+ log_dispatch_issue(process.cwd(), 'dispatcher', 'execution_failed', line, fix_hint());
280
+ } catch {
281
+ /* observability never breaks the hook */
282
+ }
283
+ if (act.action === 'warn') {
284
+ process.stderr.write(`dispatch_hook: ${line}\n`);
285
+ return null;
286
+ }
287
+ const em = emitFor(platform, event, 'block', [line], EXIT_BLOCK);
288
+ if (em.stdout) process.stdout.write(em.stdout);
289
+ if (em.stderr) process.stderr.write(em.stderr);
290
+ return em.exit;
291
+ }
@@ -84,10 +84,9 @@ import path from 'node:path';
84
84
  import { fileURLToPath, pathToFileURL } from 'node:url';
85
85
 
86
86
  import { readHookStdin } from './hook_stdin.js';
87
+ import { EXIT_ALLOW, EXIT_WARN } from './exit_codes.js';
87
88
 
88
89
  const SETTINGS_FILE = '.agent-settings.yml';
89
- const EXIT_ALLOW = 0;
90
- const EXIT_WARN = 2;
91
90
 
92
91
  type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue };
93
92
  type JsonObject = { [k: string]: JsonValue };