@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
@@ -31,19 +31,23 @@
31
31
  * "per-host `enforced_by` resolved from the table, not from a host name" means
32
32
  * in practice.
33
33
  *
34
- * Once per session (a latch keyed by session id), so it cannot nag.
34
+ * Once per distinct SEARCH TARGET, capped at five lines per session (step 2.2),
35
+ * so it cannot nag. It was once-per-session until step 2.1 added `Bash`: with
36
+ * shell searches in scope a single line covered the first `grep` of a session
37
+ * and nothing after it, which is the opposite of the problem — the state that
38
+ * matters is the state at the question being asked.
35
39
  * fail_closed: false — every error path returns allow; a context line must
36
40
  * never break a tool call.
37
41
  */
42
+ import { createHash } from 'node:crypto';
38
43
  import fs from 'node:fs';
39
44
  import path from 'node:path';
40
45
  import { fileURLToPath, pathToFileURL } from 'node:url';
41
46
 
42
47
  import { type GraphState, graphState } from '../code_graph/detect.js';
43
48
  import { readHookStdin } from './hook_stdin.js';
49
+ import { EXIT_ALLOW, EXIT_WARN } from './exit_codes.js';
44
50
 
45
- const EXIT_ALLOW = 0;
46
- const EXIT_WARN = 2;
47
51
  const CODE_EXT = /\.(php|ts|tsx|mts|cts|js|jsx|mjs|cjs)$/i;
48
52
 
49
53
  type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue };
@@ -56,13 +60,127 @@ function isObject(v: unknown): v is JsonObject {
56
60
  interface ToolIntent {
57
61
  isSearch: boolean;
58
62
  isCodeRead: boolean;
63
+ /**
64
+ * What this call is ABOUT — a Grep/Glob pattern, a read path, or the search
65
+ * term of a shell search. The per-target latch key (step 2.2): the same
66
+ * question asked twice is one question, and a hook that answered it once
67
+ * has nothing to add the second time.
68
+ *
69
+ * Empty when the call is neither a search nor a code read, and never the
70
+ * raw command line of an arbitrary Bash call — see {@link bashSearchToken}.
71
+ */
72
+ target: string;
73
+ }
74
+
75
+ /**
76
+ * The shell search heads this hook recognises.
77
+ *
78
+ * A closed list on purpose. "Any Bash command might be a search" is the reading
79
+ * that makes the hook chatty — Risk-Register rank 2 — so a command whose head
80
+ * is not here is silent AND does not latch, exactly as a non-code `Read` is.
81
+ */
82
+ const SEARCH_HEADS = new Set(['grep', 'rg', 'ag', 'find', 'ack', 'fd']);
83
+
84
+ /**
85
+ * Short options that consume the NEXT word as their value.
86
+ *
87
+ * Without this the value is read as the search term: a completion review of
88
+ * step 2.1 measured `grep -A 5 foo` keying the latch on `"5"` and
89
+ * `rg -g *.ts alpha` keying on `"*.ts"` — which also silenced a second,
90
+ * genuinely different search behind the same glob. A latch keyed on an option's
91
+ * argument is worse than no latch: it collapses distinct questions onto one slot.
92
+ */
93
+ const VALUE_TAKING = new Set([
94
+ '-A', '-B', '-C', '-m', '-e', '-f', '-g', '-t', '-T', '-d', '--include',
95
+ '--exclude', '--glob', '--max-count', '--after-context', '--before-context',
96
+ '--context', '--regexp', '--file', '--type', '--maxdepth', '--depth',
97
+ ]);
98
+
99
+ /** Asking a tool for its own usage is not a search, and must not spend a slot. */
100
+ const NOT_A_SEARCH = new Set(['--help', '-h', '--version', '-V', '--usage']);
101
+
102
+ /** `find` carries its subject in `-name`, not in the first positional. */
103
+ const FIND_SUBJECT = new Set(['-name', '-iname', '-path', '-ipath', '-regex']);
104
+
105
+ /**
106
+ * The search term inside ONE pipeline segment, or `null`.
107
+ *
108
+ * Split out from {@link bashSearchToken} so a pipeline can be scanned segment
109
+ * by segment: `cat x | grep needle` is a search, and reading only the head of
110
+ * the whole command missed every one of them. The same review measured that.
111
+ */
112
+ function segmentSearchToken(words: readonly string[]): string | null {
113
+ if (words.length === 0) return null;
114
+ let i = 0;
115
+ let head = (words[0] as string).split('/').pop() as string;
116
+ if (head === 'git') {
117
+ // `git -C <path> grep <term>` is the form this tree's own token-efficiency
118
+ // rule mandates, and a `words[1] === 'grep'` test missed all of it. Scan
119
+ // past git's own options to the subcommand.
120
+ let j = 1;
121
+ while (j < words.length && (words[j] as string).startsWith('-')) {
122
+ j += VALUE_TAKING.has(words[j] as string) || words[j] === '-C' ? 2 : 1;
123
+ }
124
+ if (words[j] !== 'grep') return null;
125
+ head = 'git grep';
126
+ i = j + 1;
127
+ } else if (SEARCH_HEADS.has(head)) {
128
+ i = 1;
129
+ } else {
130
+ return null;
131
+ }
132
+ const rest = words.slice(i);
133
+ if (rest.some((w) => NOT_A_SEARCH.has(w))) return null;
134
+ if (head === 'find') {
135
+ const at = rest.findIndex((w) => FIND_SUBJECT.has(w));
136
+ if (at !== -1 && at + 1 < rest.length) return strip(rest[at + 1] as string);
137
+ }
138
+ for (let k = 0; k < rest.length; k += 1) {
139
+ const w = rest[k] as string;
140
+ if (w.startsWith('-')) {
141
+ if (VALUE_TAKING.has(w)) k += 1; // its value is not the subject
142
+ continue;
143
+ }
144
+ return strip(w);
145
+ }
146
+ return head;
147
+ }
148
+
149
+ function strip(w: string): string {
150
+ return w.replace(/^["']|["']$/g, '');
151
+ }
152
+
153
+ /**
154
+ * The thing a shell search is searching FOR, or `null` when the command is not
155
+ * a structure search at all.
156
+ *
157
+ * Reads EVERY pipeline segment, not just the first: `cat x | grep needle` and
158
+ * `… | rg symbol` are searches, and they are the common shape. The first
159
+ * segment that is a search wins, so one line yields one latch key.
160
+ *
161
+ * `git grep` is the one two-word head and is resolved past git's own options.
162
+ * Failing a positional subject the key is the command's head, which collapses a
163
+ * flag-only search onto one slot rather than leaking a command line into a
164
+ * state file — and the key is stored as a digest in any case
165
+ * ({@link latchKey}).
166
+ */
167
+ export function bashSearchToken(command: string): string | null {
168
+ for (const segment of command.split(/\||&&|;/)) {
169
+ const words = segment.trim().split(/\s+/).filter((w) => w !== '');
170
+ const hit = segmentSearchToken(words);
171
+ if (hit !== null) return hit;
172
+ }
173
+ return null;
59
174
  }
60
175
 
61
176
  /**
62
177
  * Best-effort read of the intercepted tool + its target from the envelope.
63
178
  *
64
- * Carried over from the nudge unchanged, and the manifest's `tools:` filter is
65
- * pinned against exactly this branch surface — Grep, Glob, Read.
179
+ * The manifest's `tools:` filter is pinned against exactly this branch surface
180
+ * — Grep, Glob, Read, Bash. `Bash` joined it in step 2.1 for the reason the
181
+ * step gives: the searches an agent actually runs in this tree are shell
182
+ * searches, so a matcher that saw only the structured search tools was watching
183
+ * the door nobody uses.
66
184
  */
67
185
  export function classifyTool(envelope: JsonObject): ToolIntent {
68
186
  const payload = isObject(envelope['payload']) ? envelope['payload'] : envelope;
@@ -72,13 +190,29 @@ export function classifyTool(envelope: JsonObject): ToolIntent {
72
190
  const ti = (isObject(payload['tool_input']) ? payload['tool_input'] : envelope['tool_input']) as
73
191
  | JsonObject
74
192
  | undefined;
75
- const isSearch = name === 'Grep' || name === 'Glob';
76
- let isCodeRead = false;
193
+ const silent: ToolIntent = { isSearch: false, isCodeRead: false, target: '' };
194
+
195
+ if (name === 'Grep' || name === 'Glob') {
196
+ const pat = isObject(ti) ? (ti['pattern'] ?? ti['query']) : undefined;
197
+ return { isSearch: true, isCodeRead: false, target: typeof pat === 'string' ? pat : name };
198
+ }
77
199
  if (name === 'Read' && isObject(ti)) {
78
200
  const fp = ti['file_path'] ?? ti['path'];
79
- if (typeof fp === 'string') isCodeRead = CODE_EXT.test(fp);
201
+ if (typeof fp === 'string' && CODE_EXT.test(fp)) {
202
+ return { isSearch: false, isCodeRead: true, target: fp };
203
+ }
204
+ return silent;
205
+ }
206
+ if (name === 'Bash' && isObject(ti)) {
207
+ const cmd = ti['command'];
208
+ if (typeof cmd !== 'string') return silent;
209
+ const token = bashSearchToken(cmd);
210
+ // Any other Bash command is silent AND does not latch: it consumed no
211
+ // slot, so the next real search still gets its line.
212
+ if (token === null) return silent;
213
+ return { isSearch: true, isCodeRead: false, target: token };
80
214
  }
81
- return { isSearch, isCodeRead };
215
+ return silent;
82
216
  }
83
217
 
84
218
  function sessionId(envelope: JsonObject): string {
@@ -89,33 +223,117 @@ function sessionId(envelope: JsonObject): string {
89
223
  function latchFile(root: string): string {
90
224
  return path.join(root, 'agents', 'runtime', 'state', 'code-graph-context.json');
91
225
  }
92
- function alreadySpoke(root: string, session: string): boolean {
226
+
227
+ /**
228
+ * At most this many context lines in one session (step 2.2).
229
+ *
230
+ * The cap is the chattiness bound, and it is the half that does not depend on
231
+ * the agent's behaviour: per-target alone is unbounded, because a session that
232
+ * greps thirty distinct symbols would hear thirty lines and the consumer would
233
+ * turn the hook off. Five is a stated default, not a measurement — a session
234
+ * that has been told the graph's state five times has been told.
235
+ */
236
+ export const MAX_CONTEXT_LINES_PER_SESSION = 5;
237
+
238
+ /** How many sessions the latch file retains before evicting the oldest. */
239
+ export const MAX_SESSIONS_LATCHED = 200;
240
+
241
+ /**
242
+ * What the latch file records for a target: a truncated digest, never the
243
+ * target itself.
244
+ *
245
+ * A council review of step 2.1 (2026-10-01, anthropic + openai) asked for one
246
+ * concrete thing before the shell tool was routed here — that command text is
247
+ * neither persisted nor carried into diagnostics. Routing on the shell tool
248
+ * makes the target a search TERM an operator typed, and a term can be a
249
+ * customer name, a token fragment or an internal hostname. Nothing downstream
250
+ * needs to read it back: the latch answers "have I spoken about this one", and
251
+ * equality over a digest answers that exactly as well.
252
+ *
253
+ * Not a security boundary and not claimed as one — a short digest of a short
254
+ * term is guessable by anyone who can enumerate terms. It is the same
255
+ * PII-exclusion-by-construction move `domain-safety-pii` § Surface 2 asks for:
256
+ * the file is shaped so the plaintext is not in it to leak.
257
+ */
258
+ export function latchKey(target: string): string {
259
+ return createHash('sha256').update(target).digest('hex').slice(0, 16);
260
+ }
261
+
262
+ /**
263
+ * Should this target get a line, given what this session already heard?
264
+ *
265
+ * Pure, so the two rules it encodes can be tested without a filesystem: a
266
+ * repeat is silent at any count, and a NEW target is silent once the cap is
267
+ * reached. Takes the target in the clear and hashes here, so no caller can
268
+ * forget to.
269
+ */
270
+ export function speaksFor(spoken: readonly string[], target: string): boolean {
271
+ if (spoken.includes(latchKey(target))) return false;
272
+ return spoken.length < MAX_CONTEXT_LINES_PER_SESSION;
273
+ }
274
+
275
+ function readLatch(root: string): Record<string, string[]> {
93
276
  try {
94
- const state = JSON.parse(fs.readFileSync(latchFile(root), 'utf-8')) as Record<string, boolean>;
95
- return state[session] === true;
277
+ const raw = JSON.parse(fs.readFileSync(latchFile(root), 'utf-8')) as Record<string, unknown>;
278
+ const out: Record<string, string[]> = {};
279
+ for (const [k, v] of Object.entries(raw)) {
280
+ // A pre-2.2 file stored `true` per session. Read it as "this session
281
+ // is done", which is what it meant — never as an empty list, which
282
+ // would hand an upgraded session a fresh budget it already spent.
283
+ if (Array.isArray(v)) out[k] = v.filter((x): x is string => typeof x === 'string');
284
+ else if (v === true) out[k] = Array.from({ length: MAX_CONTEXT_LINES_PER_SESSION }, (_, i) => `legacy:${String(i)}`);
285
+ }
286
+ return out;
96
287
  } catch {
97
- return false;
288
+ return {};
98
289
  }
99
290
  }
100
- function latch(root: string, session: string): void {
291
+
292
+ /**
293
+ * The cheap read-only half, so the `git status` probe behind `graphState` runs
294
+ * only for a call that could still produce a line (Risk-Register rank 3).
295
+ *
296
+ * Advisory only — {@link latchTarget} re-reads and remains the authority. A
297
+ * disagreement between the two can only come from a concurrent write, and it
298
+ * resolves toward silence, which is the safe direction for a context line.
299
+ */
300
+ export function wouldSpeak(root: string, session: string, target: string): boolean {
301
+ return speaksFor(readLatch(root)[session] ?? [], target);
302
+ }
303
+
304
+ /**
305
+ * Decide and record in one step, because the two must not drift apart: a reader
306
+ * that says yes and a writer that records something else is how a cap leaks.
307
+ * Returns whether the caller may speak.
308
+ */
309
+ export function latchTarget(root: string, session: string, target: string): boolean {
310
+ const state = readLatch(root);
311
+ const spoken = state[session] ?? [];
312
+ if (!speaksFor(spoken, target)) return false;
101
313
  try {
102
- const p = latchFile(root);
103
- let state: Record<string, boolean> = {};
104
- try {
105
- state = JSON.parse(fs.readFileSync(p, 'utf-8')) as Record<string, boolean>;
106
- } catch {
107
- /* fresh */
314
+ state[session] = [...spoken, latchKey(target)];
315
+ // Nothing prunes a finished session, so without this the file
316
+ // accumulated up to five digests per session id for the life of the
317
+ // repository — a review's finding, and the sibling instrument added in
318
+ // the same change caps itself and says why local-and-gitignored is not
319
+ // the same as free. Insertion order is the eviction order: the oldest
320
+ // session is the one least likely to speak again, and re-latching a
321
+ // dropped session costs one extra line, never a wrong answer.
322
+ const ids = Object.keys(state);
323
+ for (const old of ids.slice(0, Math.max(0, ids.length - MAX_SESSIONS_LATCHED))) {
324
+ if (old !== session) delete state[old];
108
325
  }
109
- state[session] = true;
326
+ const p = latchFile(root);
110
327
  fs.mkdirSync(path.dirname(p), { recursive: true });
111
328
  fs.writeFileSync(p, JSON.stringify(state));
112
329
  } catch {
113
330
  /* fail-open: a persistence failure must not break the tool call */
114
331
  }
332
+ return true;
115
333
  }
116
334
 
117
335
  /**
118
- * The three-state staleness token this hook reports.
336
+ * The four-state staleness token this hook reports.
119
337
  *
120
338
  * Re-exported, not defined here: Phase 3's verbs must print the same token, and
121
339
  * an engine module importing this hook to learn it would invert the dependency
@@ -140,6 +358,13 @@ export function contextLine(state: GraphState): string {
140
358
  'grep is the other path — say which one answered.'
141
359
  );
142
360
  }
361
+ if (state === 'edited') {
362
+ return (
363
+ 'code-graph: level with HEAD, but indexed files are edited in the working tree. ' +
364
+ 'Relationship answers miss anything written since the last build — ' +
365
+ '`agent-config code-graph refresh` picks the edits up, or use grep and say so.'
366
+ );
367
+ }
143
368
  const n = state.slice('behind:'.length);
144
369
  return (
145
370
  `code-graph: ${n} commit(s) behind. Relationship answers may miss anything newer — ` +
@@ -161,11 +386,14 @@ export function main(): number {
161
386
  const pr = envelope['workspace_root'] ?? envelope['project_root'];
162
387
  const root = typeof cwd === 'string' && cwd ? cwd : typeof pr === 'string' && pr ? pr : '.';
163
388
 
164
- const { isSearch, isCodeRead } = classifyTool(envelope);
389
+ const { isSearch, isCodeRead, target } = classifyTool(envelope);
165
390
  if (!isSearch && !isCodeRead) return EXIT_ALLOW;
166
391
 
167
392
  const session = sessionId(envelope);
168
- if (alreadySpoke(root, session)) return EXIT_ALLOW; // once per session
393
+ // Once per distinct target, at most five per session (2.2). Checked BEFORE
394
+ // `graphState`, because that call now runs a `git status` probe and a line
395
+ // this session will not emit must not pay for one.
396
+ if (!wouldSpeak(root, session, target)) return EXIT_ALLOW;
169
397
 
170
398
  let state: GraphState;
171
399
  try {
@@ -177,7 +405,7 @@ export function main(): number {
177
405
  // so a session that builds a graph mid-flight still gets the line once.
178
406
  if (state === 'absent') return EXIT_ALLOW;
179
407
 
180
- latch(root, session);
408
+ if (!latchTarget(root, session, target)) return EXIT_ALLOW;
181
409
  process.stdout.write(`${JSON.stringify({ decision: 'warn', reason: contextLine(state) })}\n`);
182
410
  return EXIT_WARN;
183
411
  }
@@ -47,9 +47,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
47
47
  import { isScannable, scanText, type CommentFinding } from '../lint_code_comments.js';
48
48
  import { unwrap, type JsonObject, type JsonValue } from './envelope.js';
49
49
  import { readHookStdin } from './hook_stdin.js';
50
-
51
- const EXIT_ALLOW = 0;
52
- const EXIT_WARN = 2;
50
+ import { EXIT_ALLOW, EXIT_WARN } from './exit_codes.js';
53
51
 
54
52
  /** Findings named in the advisory line. The rest are counted, never listed. */
55
53
  export const MAX_REPORTED = 3;
@@ -0,0 +1,280 @@
1
+ /**
2
+ * What a concern's DECLARED SEVERITY means when the concern produced no verdict.
3
+ *
4
+ * Extracted from `dispatch_hook.ts` on 2026-10-03, and not for tidiness.
5
+ * `check_source_size_budget` measured that file at exactly 1,500 lines on
6
+ * `main` — sitting precisely on the cap — and step 3.3's additions put it 203
7
+ * lines over. The ratchet turns one way, and the doctrine every entry in
8
+ * `gate-violation-baselines.json` records is that a MOVE gives the budget back
9
+ * where raising the baseline spends it. The same move
10
+ * `_lib/concern_sla_window.ts` made out of `bench_hook_latency.ts`.
11
+ *
12
+ * It is also the better boundary on its own terms. Everything here answers one
13
+ * question — what a concern's declaration permits and requires when the
14
+ * concern itself said nothing — and none of it is about RUNNING a concern,
15
+ * which is what the dispatcher does. `stdin_failure_policy.ts` is its sibling:
16
+ * the same question for a payload the dispatcher could not read.
17
+ *
18
+ * The dispatcher re-exports every symbol here, so callers and tests that
19
+ * imported them from `dispatch_hook.js` are unaffected.
20
+ */
21
+ import { EXIT_ALLOW, EXIT_BLOCK, EXIT_ERROR } from './exit_codes.js';
22
+
23
+ type JsonObject = Record<string, unknown>;
24
+
25
+ /**
26
+ * P0.2 (road-to-rule-coherence) — is this concern declared advisory?
27
+ *
28
+ * An advisory concern MUST never produce a BLOCK verdict on any host. Four
29
+ * PreToolUse concerns document themselves as advisory in prose
30
+ * (`design_slop_hook`: "FLAGS, NEVER A BLOCK") while the transport happily
31
+ * turned their WARN into a host-level deny. Prose is not enforcement: the
32
+ * manifest now declares severity and the dispatcher enforces the ceiling.
33
+ */
34
+ export function _is_advisory(concern: JsonObject): boolean {
35
+ return String(concern["severity"] ?? "").trim().toLowerCase() === "advisory";
36
+ }
37
+
38
+ /**
39
+ * Is this concern declared `severity: blocking`?
40
+ *
41
+ * The positive twin of `_is_advisory`, and NOT its negation. A concern whose
42
+ * `severity` is absent, misspelt or any third value is neither — and step 3.3
43
+ * below promotes a crash to a refusal only on an EXPLICIT `blocking`, so an
44
+ * undeclared concern keeps the historical fail-open rather than inheriting a
45
+ * refusal from a typo. `lint_hook_manifest` requires the key, so the middle
46
+ * case should not exist; this reads it as the safe direction anyway, because
47
+ * the cost of the two mistakes is not symmetric.
48
+ */
49
+ export function _is_blocking(concern: JsonObject): boolean {
50
+ return String(concern["severity"] ?? "").trim().toLowerCase() === "blocking";
51
+ }
52
+
53
+ /**
54
+ * What an `rc >= 3` (crash / could-not-decide) becomes — step 3.3 of
55
+ * `road-to-a-kernel-that-guards-its-plumbing`.
56
+ *
57
+ * WHAT CHANGED, AND WHAT AUTHORISES IT.
58
+ * Until this step the resolution read the `fail_closed:` FLAG: a crash blocked
59
+ * when the concern opted in and allowed otherwise. Three of nine
60
+ * `severity: blocking` concerns carried the flag, so six guards declared
61
+ * blocking in the manifest — the declaration that AUTHORISES a refusal, and the
62
+ * one `_is_advisory` already enforces as a ceiling — and then allowed the call
63
+ * through whenever they crashed. A guard that refuses when it works and permits
64
+ * when it breaks is not a guard: a crash is exactly the moment the check did
65
+ * not happen, so it is the moment the answer matters most.
66
+ *
67
+ * So `severity` now decides both directions. It is the ceiling (an advisory
68
+ * concern may never block) AND the floor (a blocking concern that could not
69
+ * decide refuses). `fail_closed:` is no longer consulted on this branch — all
70
+ * three concerns carrying it are `severity: blocking`, so no verdict moves
71
+ * because it stopped being read here.
72
+ *
73
+ * WHAT A SLOW CONCERN IS NOT, ON EITHER PATH.
74
+ * This branch resolves a concern that produced NO VERDICT. It is not a latency
75
+ * gate and no reading of `concern_sla_ms` reaches it. On the default in-process
76
+ * route a kill-timeout cannot preempt synchronous code, so an overrun there is
77
+ * a POST-HOC observation about a concern that did in fact answer — refusing it
78
+ * afterwards would deny a call whose guard passed, buying no enforcement while
79
+ * spending exactly the availability risk Risk 1 of the owning roadmap holds
80
+ * down. On the spawn route the timeout stays the historical 30 s, because the
81
+ * registered SLA measures in-process concern work and is three to fifteen times
82
+ * smaller than interpreter startup alone; `SPAWN_TIMEOUT_MS` carries that
83
+ * measurement and the probe that refused the substitution.
84
+ *
85
+ * So `sla_ms x 3` remains what the warn-only window made it: a number the bench
86
+ * prints beside each measured p95, gating nothing. Its readings are recorded
87
+ * with the owning plan and no count is repeated here — a review of an earlier
88
+ * revision found two different tallies in one diff, because a number had been
89
+ * pasted into an implementation comment while the record kept accumulating.
90
+ * The readings validate the bound as an OBSERVATION, which is the only thing
91
+ * this step asks of them.
92
+ *
93
+ * The discriminator for this branch is whether a VERDICT EXISTS. A crash and a
94
+ * killed spawn leave none, so they fail closed. A slow success leaves one, so
95
+ * it is honoured, however slow it was.
96
+ *
97
+ * THE ONE-RETRY ESCAPE, AND WHY THE STOP SLOT NEEDS IT.
98
+ * `turn-end-gate` and `run-continuation` are `severity: blocking` on `stop`,
99
+ * and `tests/hooks/concern_severity.test.ts` records the hazard in as many
100
+ * words: a turn-end gate that fails closed "does not degrade, it wedges the
101
+ * session". Neither carries `skip_on_refusal_retry`, so without this clause a
102
+ * DETERMINISTIC crash in either would refuse every Stop, indefinitely, with no
103
+ * escape the user can reach.
104
+ *
105
+ * So the promotion is spent ONCE. On the retry the host itself marks
106
+ * (`stop_hook_active`, read by `_is_refusal_retry`) a crash falls back to
107
+ * fail-open and the turn ends. The legitimate refusal path is untouched: a
108
+ * concern that DECIDED to refuse returns `EXIT_BLOCK` and never reaches this
109
+ * branch. What a broken concern loses is only the power to refuse forever on
110
+ * the strength of being broken.
111
+ *
112
+ * `pre_tool_use` is untouched by the clause — `_is_refusal_retry` is false on
113
+ * every event but `stop` — and that asymmetry is deliberate. A crashing
114
+ * tool-call guard refuses every attempt, which is the right answer there: the
115
+ * user can run a different command and nothing is wedged, whereas a turn that
116
+ * cannot end leaves no move at all.
117
+ */
118
+ export function _resolve_execution_failure(
119
+ concern: JsonObject,
120
+ refusal_retry: boolean,
121
+ ): number {
122
+ if (!_is_blocking(concern)) return EXIT_ALLOW;
123
+ if (refusal_retry) return EXIT_ALLOW;
124
+ return EXIT_BLOCK;
125
+ }
126
+
127
+ /**
128
+ * The spawn kill-timeout, and why `concern_sla_ms` is NOT what sets it.
129
+ *
130
+ * Step 3.3 of `road-to-a-kernel-that-guards-its-plumbing` reads "timeout for a
131
+ * blocking concern becomes `sla_ms x 3`". That clause is NOT landed, and the
132
+ * reason is a unit mismatch that is measurable rather than arguable.
133
+ *
134
+ * `concern_sla_ms` is derived from the per-concern `duration_ms` this file
135
+ * reports into the timings sink, which on the in-process route brackets
136
+ * `main_fn(argsList)` and nothing else — the concern's OWN work. The registered
137
+ * rows are 0.564 to 1.587 ms, so `sla_ms x 3` is 1.7 to 4.8 ms.
138
+ *
139
+ * A `spawnSync` timeout bounds something else entirely: fork, interpreter
140
+ * start, module graph load, and only then the same work. The bench's own
141
+ * control row measures the interpreter term alone at p95 17 ms on the 1 vCPU
142
+ * reference class and 26 ms on the GitHub runner — three to fifteen times the
143
+ * whole proposed bound, before the concern has run a line.
144
+ *
145
+ * Wiring it was tried on this branch and probed against the real dispatcher.
146
+ * With `AGENT_CONFIG_HOOKS_ISOLATED=1` on `claude/pre_tool_use`, all six
147
+ * blocking concerns returned `ETIMEDOUT`, left no verdict, and — resolved by
148
+ * `_resolve_execution_failure` — turned the dispatch into a deny: exit 2 on an
149
+ * ordinary `Read`. That is Risk 1 of the owning roadmap ("fail-closed wedges
150
+ * slow hosts") firing on every host rather than a slow one, reached through a
151
+ * documented escape hatch. The two changes are individually defensible and
152
+ * lethal together, which is exactly the shape a measured bound is supposed to
153
+ * prevent.
154
+ *
155
+ * So the bound stays where the window put it: an observation
156
+ * `src/scripts/_lib/concern_sla_window.ts` prints on every bench run, gating
157
+ * nothing. The spawn path keeps the historical 30 s. Re-wiring it needs a
158
+ * SPAWN-path measurement — which this tree does not have, because the sink it
159
+ * would come from records the in-process number — and not a second reading of
160
+ * the one registered here.
161
+ *
162
+ * `spawn_path_keeps_the_historical_timeout` in `dispatch_hook.test.ts` pins
163
+ * this: it dispatches a blocking concern whose NAME carries a registered SLA
164
+ * row down the spawn path and asserts the call is allowed. Under the bound it
165
+ * reds.
166
+ */
167
+ export const SPAWN_TIMEOUT_MS = 30000;
168
+
169
+ /**
170
+ * The signal name a killed child reports, or the honest placeholder.
171
+ *
172
+ * A SIGNALLED child has `status === null` and `signal` set, and reaches the
173
+ * dispatcher's spawn branch with no `proc.error` whenever the signal came from
174
+ * outside this process — an OOM kill, a SIGTERM from a supervisor, a crash in
175
+ * the child's own runtime. `proc.status ?? 0` read that as exit 0, i.e. ALLOW,
176
+ * which is a fail-OPEN at the one moment the error band exists to fail closed:
177
+ * the concern was killed, so it decided nothing. The coalescing default was
178
+ * harmless while the band meant fail-open for six of nine blocking concerns;
179
+ * with the severity resolution it is the difference between a guard that
180
+ * refuses when it is killed and one that waves the call through. Found by an
181
+ * independent review, 2026-10-03, latent since the spawn path was written.
182
+ *
183
+ * `null` for both fields is possible and is reported as an unknown signal
184
+ * rather than guessed at — a refusal whose cause reads "unknown" is still
185
+ * actionable, and one whose cause is invented is not.
186
+ */
187
+ export function signalLabel(signal: NodeJS.Signals | null): string {
188
+ return signal === null ? 'unknown signal' : signal;
189
+ }
190
+
191
+ /**
192
+ * What an operator is told when a blocking concern refuses without deciding.
193
+ *
194
+ * Printed where the refusal is produced rather than left in a source comment.
195
+ * Both review seats of 2026-10-03 named the same gap: on `pre_tool_use` a
196
+ * deterministically crashing blocking concern refuses every tool call, and the
197
+ * operator cannot discover the escape hatch from inside a wedged session.
198
+ * `stop` is bounded by the one-retry clause and needs no such line, so the
199
+ * caller prints this only where the denial can repeat.
200
+ */
201
+ export function noVerdictRefusalNotice(name: string): string {
202
+ return (
203
+ `dispatch_hook: '${name}' is severity: blocking and produced no ` +
204
+ `verdict, so this call is refused. If it keeps failing, ` +
205
+ `AGENT_CONFIG_HOOKS_ISOLATED=1 runs concerns out of process and ` +
206
+ `agents/runtime/state/dispatch-issues.jsonl names the cause.\n`
207
+ );
208
+ }
209
+
210
+ /**
211
+ * What a finished `spawnSync` actually said, in the vocabulary of verdicts.
212
+ *
213
+ * Three ways a spawned concern produces NO VERDICT, and until 2026-10-03 only
214
+ * two of them were recognised as such:
215
+ *
216
+ * - **`proc.error`** — an OSError, or the kill-timeout expiring. Always a
217
+ * no-verdict and always was.
218
+ * - **`status === null` with a signal** — the child was killed from outside
219
+ * this process: an OOM kill, a supervisor SIGTERM, an abort in the child's
220
+ * own runtime. `proc.status ?? 0` read this as exit 0, i.e. ALLOW, which is
221
+ * a fail-OPEN at the one moment the error band exists to fail closed. The
222
+ * coalescing default was harmless while the band meant fail-open for six of
223
+ * nine blocking concerns; with the severity resolution it is the difference
224
+ * between a guard that refuses when it is killed and one that waves the call
225
+ * through. Found by an independent review, latent since the spawn path was
226
+ * written.
227
+ * - **an exit code in the error band** — a child that simply exited 3. This
228
+ * logged nothing until step 3.3: `proc.error` covers the first case, the
229
+ * in-process route logs its own throw, and this fell between the two. That
230
+ * was survivable while the band meant fail-open; now it can produce a
231
+ * refusal, and a refusal whose cause is in no record is one nobody can act
232
+ * on.
233
+ *
234
+ * Returning a classification rather than writing the log here keeps this module
235
+ * free of I/O — the caller owns `log_dispatch_issue` and the workspace it needs,
236
+ * and this owns what the three cases MEAN.
237
+ */
238
+ export interface SpawnClassification {
239
+ /** The exit code the dispatcher should carry forward. */
240
+ readonly rc: number;
241
+ /** `execution_failed` detail to record, or `null` when there is nothing to record. */
242
+ readonly issueDetail: string | null;
243
+ /** True when the concern produced no verdict and its own stdout must be discarded. */
244
+ readonly noVerdict: boolean;
245
+ /** Prefix for the stderr line, or `null` to pass the child's stderr through unchanged. */
246
+ readonly stderrNote: string | null;
247
+ }
248
+
249
+ export function classifySpawnResult(
250
+ error: Error | undefined,
251
+ status: number | null,
252
+ signal: NodeJS.Signals | null,
253
+ ): SpawnClassification {
254
+ if (error !== undefined) {
255
+ const err = error as NodeJS.ErrnoException;
256
+ const typeName = err.code === 'ETIMEDOUT' ? 'TimeoutExpired' : err.name || 'OSError';
257
+ return {
258
+ rc: EXIT_ERROR,
259
+ issueDetail: `${typeName}: ${err.message}`,
260
+ noVerdict: true,
261
+ stderrNote: err.message,
262
+ };
263
+ }
264
+ if (status === null) {
265
+ const sig = signalLabel(signal);
266
+ return {
267
+ rc: EXIT_ERROR,
268
+ issueDetail: `concern was terminated by ${sig} without a verdict`,
269
+ noVerdict: true,
270
+ stderrNote: `terminated by ${sig}`,
271
+ };
272
+ }
273
+ return {
274
+ rc: status,
275
+ issueDetail:
276
+ status >= EXIT_ERROR ? `concern exited ${String(status)} without a verdict` : null,
277
+ noVerdict: false,
278
+ stderrNote: null,
279
+ };
280
+ }
@@ -52,6 +52,7 @@ import { main as gitAuthorizationMain } from '../git_authorization_hook.js';
52
52
  import { main as evidenceIndependenceMain } from './evidence_independence.js';
53
53
  import { main as blockNoVerifyMain } from './block_no_verify.js';
54
54
  import { main as blockKernelRuleWritesMain } from './block_kernel_rule_writes.js';
55
+ import { main as blockPlumbingWritesMain } from './block_plumbing_writes.js';
55
56
  import { main as blockConfigWeakeningMain } from './block_config_weakening.js';
56
57
  import { main as blockSpeakingInboxDirMain } from './block_speaking_inbox_dir.js';
57
58
  import { main as oneQuestionPerAskMain } from './one_question_per_ask_hook.js';
@@ -118,6 +119,7 @@ export const CONCERN_REGISTRY: Readonly<Record<string, ConcernMain>> = {
118
119
  'src/scripts/hooks/evidence_independence.ts': evidenceIndependenceMain as ConcernMain,
119
120
  'src/scripts/hooks/block_no_verify.ts': blockNoVerifyMain as ConcernMain,
120
121
  'src/scripts/hooks/block_kernel_rule_writes.ts': blockKernelRuleWritesMain as ConcernMain,
122
+ 'src/scripts/hooks/block_plumbing_writes.ts': blockPlumbingWritesMain as ConcernMain,
121
123
  'src/scripts/hooks/block_config_weakening.ts': blockConfigWeakeningMain as ConcernMain,
122
124
  'src/scripts/hooks/block_speaking_inbox_dir.ts': blockSpeakingInboxDirMain as ConcernMain,
123
125
  'src/scripts/hooks/one_question_per_ask_hook.ts': oneQuestionPerAskMain as ConcernMain,