@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
@@ -62,8 +62,19 @@ import {
62
62
  prune_stale_session_states,
63
63
  session_state_file,
64
64
  update_json_under_lock,
65
+ owns_session_state,
65
66
  } from "./hooks/state_io.js";
66
67
  import { readHookStdin } from "./hooks/hook_stdin.js";
68
+ import { hookSectionValue } from "./_lib/hook_settings.js";
69
+ import { isVerificationCommand, mightBeVerification } from "./_lib/verification_command.js";
70
+ import {
71
+ advisoryLine,
72
+ runTouchedFileQuality,
73
+ touchedFilesAtStop,
74
+ type QualityRun,
75
+ } from "./_lib/touched_file_quality.js";
76
+ import { runnerOf } from "./_lib/verification_evidence.js";
77
+ import { resolve_toolchain } from "../agent-src/templates/scripts/work_engine/stack/runner.js";
67
78
 
68
79
  // NOTE: the Python docstring says `agents/runtime/state/`, but the code
69
80
  // constant is `agents/state/`. Replicated verbatim — latent docstring/code
@@ -144,28 +155,6 @@ export const COMMAND_TOOLS: ReadonlySet<string> = new Set([
144
155
  "RunShellCommand", // Cursor
145
156
  ]);
146
157
 
147
- // Permissive verification-command pattern. Observability — false positives
148
- // are cheaper than false negatives. Word-boundary anchored on common
149
- // shell separators so chained commands (`task sync && task ci`) match.
150
- //
151
- // Python re flags: IGNORECASE. The pattern uses no Python-specific syntax;
152
- // it ports verbatim. The leading `(?:^|[\s;&|`(])` class includes backtick.
153
- const _VERIFICATION_RE = new RegExp(
154
- "(?:^|[\\s;&|`(])(" +
155
- "task\\s+(?:ci|test|tests|lint|check|qa|phpstan|rector|ecs|pest|pytest)" +
156
- "|(?:\\./|\\.venv/bin/|vendor/bin/)?(?:pest|phpunit|phpstan|psalm|rector|ecs)\\b" +
157
- "|(?:python3?|\\.venv/bin/python3?)\\s+-m\\s+pytest" +
158
- "|pytest\\b" +
159
- "|(?:npm|pnpm|yarn|bun)\\s+(?:run\\s+)?(?:test|check|lint|typecheck|tsc)" +
160
- "|cargo\\s+(?:test|check|clippy)" +
161
- "|go\\s+test" +
162
- "|make\\s+(?:test|check|lint)" +
163
- "|composer\\s+(?:test|check|lint|phpstan)" +
164
- "|(?:php\\s+)?artisan\\s+test" +
165
- ")",
166
- "i",
167
- );
168
-
169
158
  type StateDict = Record<string, unknown>;
170
159
 
171
160
  /** Python datetime.now(timezone.utc).isoformat(timespec="seconds"). */
@@ -193,10 +182,92 @@ function _empty_state(): StateDict {
193
182
  // has to survive it: the measured failure is a completion claim in a LATER
194
183
  // turn than the poll it rests on.
195
184
  ci_last: null,
185
+ // Round 8 — see `_reset_turn`. Present in the empty state so a freshly
186
+ // created file has the same shape as one that has seen a turn boundary;
187
+ // a reader must never have to distinguish "no runs yet" from "key absent".
188
+ verification_runs: [],
189
+ edits_this_turn: 0,
190
+ // NOTE — `quality_runs` and `quality_files_source` are deliberately ABSENT
191
+ // here, against the convention `verification_runs` above follows.
192
+ //
193
+ // That convention ("present in the empty state so a reader never has to tell
194
+ // 'no runs yet' from 'key absent'") is the right default and it loses to a
195
+ // stronger one: the roadmap's first acceptance criterion is that with
196
+ // `touched_file_quality: off` the record is BYTE-IDENTICAL to the base ref.
197
+ // Seeding two keys would break that for every install that never opted in,
198
+ // and "the default-off feature changed my state file" is exactly the kind of
199
+ // silent shape drift a consumer cannot audit. The convention's cost here is
200
+ // zero, because the only reader of these keys is 2.1's analysis, which counts
201
+ // stops — and a stop with no key is a stop where the pass did not run, which
202
+ // is a distinction it needs rather than one it must not make.
196
203
  checked_at: _now(),
197
204
  };
198
205
  }
199
206
 
207
+ /** The three values `hooks.verify_before_complete.touched_file_quality` may take. */
208
+ export type TouchedFileQualityMode = "off" | "shadow" | "warn";
209
+
210
+ /**
211
+ * The configured mode, defaulting to `off`.
212
+ *
213
+ * FAIL-CLOSED ON THE FLAG, FAIL-OPEN ON THE READ, exactly as
214
+ * `hookSectionEnabled` does: an unreadable settings file, a missing key, or a
215
+ * value outside the enum all resolve to `off`. A typo must never turn a pass
216
+ * ON — `warn` emits a line the operator did not ask for, and `shadow` spawns
217
+ * processes, so an unrecognised string is the one case where guessing is
218
+ * strictly worse than doing nothing.
219
+ */
220
+ export function readTouchedFileQualityMode(consumer_root: string): TouchedFileQualityMode {
221
+ const raw = hookSectionValue(consumer_root, "verify_before_complete", "touched_file_quality");
222
+ return raw === "shadow" || raw === "warn" ? raw : "off";
223
+ }
224
+
225
+ /**
226
+ * Run the shadow quality pass for this stop, or return nothing.
227
+ *
228
+ * Kept OUTSIDE the state lock on purpose. `update_json_under_lock` holds a lock
229
+ * around its mutator, and a mutator that spawns `tsc` would hold it for the
230
+ * whole typecheck — every concurrent concern writing this session's state would
231
+ * block behind a linter. The pass runs first, the result is handed to `_update`
232
+ * as data, and the lock stays as short as it was.
233
+ */
234
+ export function collectTouchedFileQuality(
235
+ consumer_root: string,
236
+ session_id: string,
237
+ ): { runs: QualityRun[]; source: string } | null {
238
+ let touched: { files: readonly string[]; source: string };
239
+ try {
240
+ touched = touchedFilesAtStop(consumer_root, session_id);
241
+ } catch {
242
+ return null;
243
+ }
244
+ if (touched.files.length === 0) {
245
+ // Nothing to measure. Recorded as an empty run list with its source, not as
246
+ // silence: 2.1 counts "stops with edits" against "stops with at least one
247
+ // entry", and a stop that produced no entry has to be distinguishable from
248
+ // a stop where the pass never ran.
249
+ return { runs: [], source: touched.source };
250
+ }
251
+ let commands: readonly string[] = [];
252
+ try {
253
+ commands = resolve_toolchain(consumer_root).quality;
254
+ } catch {
255
+ commands = [];
256
+ }
257
+ try {
258
+ return {
259
+ runs: runTouchedFileQuality({
260
+ root: consumer_root,
261
+ commands,
262
+ files: touched.files,
263
+ }),
264
+ source: touched.source,
265
+ };
266
+ } catch {
267
+ return null;
268
+ }
269
+ }
270
+
200
271
 
201
272
  /** Return [tool_name, command_text] from a tool-event payload. */
202
273
  function _extract_command(payload: StateDict): [string | null, string | null] {
@@ -230,7 +301,7 @@ function _is_verification(command: string): boolean {
230
301
  // settle is genuine, which is what the FC-3b guard below decides. Before this
231
302
  // change a CI poll counted for nothing at all, so nothing observed the
232
303
  // difference between "settled" and "never started".
233
- return _VERIFICATION_RE.test(command) || isCiPoll(command);
304
+ return isVerificationCommand(command) || isCiPoll(command);
234
305
  }
235
306
 
236
307
  /**
@@ -334,11 +405,303 @@ function _extract_output(payload: StateDict): string | null {
334
405
  return null;
335
406
  }
336
407
 
408
+ /**
409
+ * Tool names that CHANGE the tree, across the platforms this concern sees.
410
+ *
411
+ * The first four mirror `_EDIT_TOOLS` in the turn-end gate exactly; the rest are
412
+ * the same three tools under the names Augment, Cline and Cursor give them. The
413
+ * gate reads a Claude transcript and can afford four names; this recorder runs
414
+ * on every platform the manifest binds `post_tool_use` on, so it needs all of
415
+ * them — a missed edit name means `edits_this_turn` undercounts and a
416
+ * verification run looks LATER in the sequence than it was, which is the one
417
+ * direction that could clear an unverified edit.
418
+ */
419
+ const EDIT_TOOLS: ReadonlySet<string> = new Set([
420
+ "Edit",
421
+ "Write",
422
+ "MultiEdit",
423
+ "NotebookEdit",
424
+ "str-replace-editor",
425
+ "save-file",
426
+ "str_replace_editor",
427
+ "create_file",
428
+ "write_to_file",
429
+ "replace_in_file",
430
+ "apply_diff",
431
+ "edit_file",
432
+ ]);
433
+
434
+ /**
435
+ * How many run records one turn may keep.
436
+ *
437
+ * The state file is read by a stop-slot hook on every turn, so it is on the
438
+ * latency path and cannot grow without a ceiling. The NEWEST are kept because
439
+ * the only question asked of them is whether a passing run follows the LAST
440
+ * edit, which is always answered by the tail.
441
+ */
442
+ export const MAX_VERIFICATION_RUNS_PER_TURN = 24;
443
+
444
+ /**
445
+ * Characters of tool output retained per stream per record.
446
+ *
447
+ * CHARACTERS, not bytes, and the name is kept for its existing importers: the
448
+ * slice is applied to a JS string. Multibyte output can therefore exceed this
449
+ * many bytes, which is a bound on the state file rather than a promise about
450
+ * it, and 4,096 characters is far more than any runner's summary needs.
451
+ */
452
+ export const RUN_OUTPUT_TAIL_BYTES = 4096;
453
+
454
+ /**
455
+ * Apply the per-turn cap, keeping the newest AND the earliest failing record.
456
+ *
457
+ * Two detectors read this array and they ask opposite-ended questions. Detector
458
+ * C asks whether a pass follows the last edit — always answered by the tail.
459
+ * Detector F asks whether a red preceded the green — answered by the head. A
460
+ * plain `slice(-N)` serves the first and can silently drop the evidence the
461
+ * second needs, turning a turn that did honest red-green work into a
462
+ * `no_red_evidence` refusal once the run count passes the cap.
463
+ */
464
+ function _cap_runs(runs: unknown[]): unknown[] {
465
+ if (runs.length <= MAX_VERIFICATION_RUNS_PER_TURN) return runs;
466
+ const tail = runs.slice(-(MAX_VERIFICATION_RUNS_PER_TURN - 1));
467
+ const head = runs.find((r) => {
468
+ if (r === null || typeof r !== "object" || Array.isArray(r)) return false;
469
+ const rec = r as StateDict;
470
+ const code = rec["exit_code"];
471
+ return typeof code === "number" && code !== 0;
472
+ });
473
+ if (head === undefined || tail.includes(head)) {
474
+ return runs.slice(-MAX_VERIFICATION_RUNS_PER_TURN);
475
+ }
476
+ return [head, ...tail];
477
+ }
478
+
479
+ /**
480
+ * The prefix a host puts on a FAILED shell result when it carries no exit field.
481
+ *
482
+ * Claude Code's Bash tool does exactly this: a failing call's whole tool
483
+ * response is the string `Error: Exit code 1\n<output>`. Measured over 1,077
484
+ * object-shaped and 11 string-shaped tool results in this machine's own
485
+ * transcripts (2026-09-29): every string-shaped result carried this prefix, and
486
+ * no shape of either kind carried any of the numeric field names below.
487
+ */
488
+ const _ERROR_EXIT_PREFIX = /^Error:\s*Exit code\s*(\d+)/i;
489
+
490
+ /**
491
+ * The same statement without the `Error:` word, which is how the host words it
492
+ * on its FAILURE event.
493
+ *
494
+ * Measured 2026-10-01 on Claude Code 2.1.286: a failing `Bash` call fires
495
+ * `PostToolUseFailure`, whose envelope carries NO `tool_response` at all. The
496
+ * exit status is a top-level `error` string whose first line reads
497
+ * `Exit code N`, with the command's own diagnostics on the lines after it. The
498
+ * prefix above never matches that, so for as long as only the success event was
499
+ * bound this recorder could not have written a non-zero exit even once — which
500
+ * is exactly what 24 consecutive all-zero records on this host turned out to
501
+ * mean.
502
+ */
503
+ const _BARE_EXIT_PREFIX = /^\s*Exit code\s*(\d+)/i;
504
+
505
+ /** The status a post-tool payload actually reports, and HOW it reported it. */
506
+ interface ExitReading {
507
+ readonly code: number | null;
508
+ /** Provenance, recorded so a reader can tell an observed code from a derived one. */
509
+ readonly source: "field" | "error_prefix" | "response_shape" | null;
510
+ /** The host said the call was interrupted — a kill, not a verdict on the work. */
511
+ readonly interrupted: boolean;
512
+ }
513
+
514
+ const _NO_EXIT: ExitReading = { code: null, source: null, interrupted: false };
515
+
516
+ function _numeric_exit_field(obj: StateDict): number | null {
517
+ for (const key of ["exit_code", "exitCode", "returncode", "returnCode", "status_code"]) {
518
+ const v = obj[key];
519
+ if (typeof v === "number" && Number.isFinite(v)) return Math.trunc(v);
520
+ if (typeof v === "string" && v.trim() !== "" && Number.isFinite(Number(v))) {
521
+ return Math.trunc(Number(v));
522
+ }
523
+ }
524
+ return null;
525
+ }
526
+
527
+ /**
528
+ * What the host said about how the command ended.
529
+ *
530
+ * `null` is a REAL value and is never normalised to 0: several hosts surface no
531
+ * exit status at all, and writing 0 for "the host said nothing" would
532
+ * manufacture the strongest possible evidence out of silence. The classifier's
533
+ * `exit_code_unavailable` verdict exists so that gap stays visible.
534
+ *
535
+ * THREE SOURCES, in descending directness, and the third is the one that took a
536
+ * measurement rather than a guess.
537
+ *
538
+ * · `field` — a numeric exit field, wherever a host provides one.
539
+ * · `error_prefix` — the `Error: Exit code N` string form above. This is a
540
+ * real non-zero code, stated by the host in the only place it states it.
541
+ * · `response_shape` — an OBJECT response carrying `interrupted: false`
542
+ * alongside a `stdout` or `stderr` key. On Claude Code that is the success
543
+ * form, and the discriminator is deliberately the PRESENCE of
544
+ * `interrupted: false` rather than the ABSENCE of an error prefix: a
545
+ * positive signal from the host, not an inference from silence. A shape
546
+ * that carries neither reads as `null` and stays an instrument gap.
547
+ *
548
+ * Without the second and third readings this recorder wrote `exit_code: null`
549
+ * for every command on the one host that binds the turn-end gate, so the whole
550
+ * record path was inert — found by an independent review of the branch that
551
+ * introduced it, against 1,077 real tool results.
552
+ */
553
+ /**
554
+ * Did the HOST say this call failed, independently of anything in its text?
555
+ *
556
+ * Read from the native event name, which the host writes in two places and the
557
+ * agent in neither: `hook_event_name` inside the payload, and `native_event` on
558
+ * the dispatcher envelope. Either is authoritative; both are checked because a
559
+ * raw-payload invocation carries only the first and a synthesised envelope only
560
+ * the second.
561
+ *
562
+ * An explicit set of OBSERVED names, with a suffix match behind it as a declared
563
+ * fallback rather than as the primary rule. A reviewer called the suffix-only
564
+ * form a maintenance trap and was right about the direction: the set is what a
565
+ * reader checks against the manifest, and `PostToolUseFailure` is the one
566
+ * spelling this package has actually seen a host send.
567
+ *
568
+ * The fallback is kept, and keeping it is the deliberate half. A host whose
569
+ * failure event this tree has never observed is exactly the case where guessing
570
+ * wrong is cheapest in one direction and expensive in the other: a false
571
+ * positive costs a refusal to record a zero, a false negative costs a
572
+ * manufactured pass on a red run. So the set is the rule and the suffix is the
573
+ * safe default for a name nobody here has met yet.
574
+ */
575
+ export const FAILURE_EVENT_NAMES: ReadonlySet<string> = new Set(["PostToolUseFailure"]);
576
+
577
+ function _is_failure_event(payload: StateDict, envelope: StateDict): boolean {
578
+ for (const v of [payload["hook_event_name"], envelope["native_event"]]) {
579
+ if (typeof v !== "string" || v === "") continue;
580
+ if (FAILURE_EVENT_NAMES.has(v)) return true;
581
+ if (/failure$/i.test(v)) return true;
582
+ }
583
+ return false;
584
+ }
585
+
586
+ function _extract_exit_reading(payload: StateDict, failure_event = false): ExitReading {
587
+ // On an event the host itself named a FAILURE, a zero is never recorded —
588
+ // from any of the three readings. The host has already stated the call did not
589
+ // succeed, so a `0` arrived at by parsing its text would contradict the only
590
+ // authoritative thing in the envelope, and it would do so in the single most
591
+ // dangerous direction: manufacturing the strongest possible pass evidence out
592
+ // of a red run. `null` instead, which the classifier already treats as an
593
+ // instrument gap and never as a pass.
594
+ //
595
+ // Raised by an independent reviewer who asked what happens when a command's
596
+ // own error text begins `Exit code 0`. Nothing observed produces that, which
597
+ // is exactly why it is worth closing before something does.
598
+ const seal = (r: ExitReading): ExitReading =>
599
+ failure_event && r.code === 0 ? { code: null, source: null, interrupted: r.interrupted } : r;
600
+
601
+ const direct = _numeric_exit_field(payload);
602
+ if (direct !== null) return seal({ code: direct, source: "field", interrupted: false });
603
+
604
+ // The FAILURE envelope, read before the success keys because it carries none
605
+ // of them: no `tool_response`, the status in a top-level `error` string, and
606
+ // the interrupt flag spelled `is_interrupt` rather than the nested
607
+ // `interrupted` below. Interruption is checked FIRST — a killed call is a
608
+ // statement about the kill and not a verdict on the work, so a host that
609
+ // sends both must not have its kill read as a failing exit.
610
+ const interrupted_top = payload["is_interrupt"] === true;
611
+ const err = payload["error"];
612
+ if (typeof err === "string") {
613
+ if (interrupted_top) return { code: null, source: null, interrupted: true };
614
+ const bare = _BARE_EXIT_PREFIX.exec(err) ?? _ERROR_EXIT_PREFIX.exec(err);
615
+ if (bare?.[1] !== undefined) {
616
+ return seal({ code: Number(bare[1]), source: "error_prefix", interrupted: false });
617
+ }
618
+ }
619
+ if (interrupted_top) return { code: null, source: null, interrupted: true };
620
+
621
+ for (const key of ["tool_response", "toolResponse", "result", "output"]) {
622
+ const v = payload[key];
623
+ if (typeof v === "string") {
624
+ const m = _ERROR_EXIT_PREFIX.exec(v);
625
+ if (m?.[1] !== undefined) {
626
+ return seal({ code: Number(m[1]), source: "error_prefix", interrupted: false });
627
+ }
628
+ continue;
629
+ }
630
+ if (v === null || typeof v !== "object" || Array.isArray(v)) continue;
631
+ const obj = v as StateDict;
632
+ const nested = _numeric_exit_field(obj);
633
+ if (nested !== null) return seal({ code: nested, source: "field", interrupted: false });
634
+ if (obj["interrupted"] === true) return { code: null, source: null, interrupted: true };
635
+ const hasStream = typeof obj["stdout"] === "string" || typeof obj["stderr"] === "string";
636
+ if (obj["interrupted"] === false && hasStream) {
637
+ return seal({ code: 0, source: "response_shape", interrupted: false });
638
+ }
639
+ }
640
+ return _NO_EXIT;
641
+ }
642
+
643
+ /**
644
+ * The command's stdout and stderr as REAL strings, never as a stringified blob.
645
+ *
646
+ * Separate from `_extract_output` above, which feeds the pre-existing `vacuous`
647
+ * counters and whose behavior must not move: that one returns
648
+ * `JSON.stringify(v)` for an object response, and every parser in
649
+ * `verification_evidence.ts` is line-anchored (`/^[ \t]*Tests:?.../m` and
650
+ * friends). JSON escapes a newline as the two characters `\` and `n`, so a
651
+ * stringified response can never match any of them — the classifier was blind
652
+ * to every summary on the host's success shape. Found by the same review.
653
+ */
654
+ function _extract_run_streams(payload: StateDict): { stdout: string; stderr: string } {
655
+ for (const key of ["tool_response", "toolResponse", "result", "output"]) {
656
+ const v = payload[key];
657
+ if (typeof v === "string") return { stdout: v, stderr: "" };
658
+ if (v === null || typeof v !== "object" || Array.isArray(v)) continue;
659
+ const obj = v as StateDict;
660
+ const out = obj["stdout"] ?? obj["output"];
661
+ const err = obj["stderr"];
662
+ if (typeof out === "string" || typeof err === "string") {
663
+ return {
664
+ stdout: typeof out === "string" ? out : "",
665
+ stderr: typeof err === "string" ? err : "",
666
+ };
667
+ }
668
+ }
669
+ const topOut = payload["stdout"] ?? payload["output"];
670
+ const topErr = payload["stderr"];
671
+ if (typeof topOut === "string" || typeof topErr === "string") {
672
+ return {
673
+ stdout: typeof topOut === "string" ? topOut : "",
674
+ stderr: typeof topErr === "string" ? topErr : "",
675
+ };
676
+ }
677
+ // The failure envelope's one text field. Recorded as `stdout` because the
678
+ // host collapses both streams into it and says nothing about which was
679
+ // which — a recorder that split them would be inventing a fact — and because
680
+ // every parser in `verification_evidence` is line-anchored over stdout. The
681
+ // alternative, dropping it, would leave a red run recorded with its exit code
682
+ // and no reason, which is the half the reader actually needs.
683
+ const errText = payload["error"];
684
+ if (typeof errText === "string") return { stdout: errText, stderr: "" };
685
+ return { stdout: "", stderr: "" };
686
+ }
687
+
337
688
  function _reset_turn(state: StateDict, session_id: string): StateDict {
338
689
  state["session_id"] = session_id || state["session_id"] || "";
339
690
  state["turn_started_at"] = _now();
340
691
  state["verifications_this_turn"] = 0;
341
692
  state["verified_this_turn"] = false;
693
+ // Round 8 — the run records the stop gate reads instead of the command text,
694
+ // and the edit counter that places them in the turn's sequence. Both are
695
+ // TURN-scoped: a run from the previous turn demonstrably did not exercise this
696
+ // turn's edits, which is the freshness argument `verify-before-complete` makes.
697
+ state["verification_runs"] = [];
698
+ state["edits_this_turn"] = 0;
699
+ // TURN-scoped for the same reason as its neighbour: a quality verdict from
700
+ // the previous turn describes files this turn may already have changed, and a
701
+ // stale PASS is the direction that misleads. DELETED rather than emptied, so
702
+ // that a turn boundary restores the base-ref shape — see `_empty_state`.
703
+ delete state["quality_runs"];
704
+ delete state["quality_files_source"];
342
705
  // FC-3b turn-scoped counters: a CI settle must be witnessed within the same
343
706
  // turn that claims it, so the in-flight observation does not survive a turn.
344
707
  state["ci_saw_pending"] = false;
@@ -357,7 +720,19 @@ function _asInt(v: unknown): number {
357
720
  return Number.isFinite(n) ? Math.trunc(n) : 0;
358
721
  }
359
722
 
360
- function _update(state: StateDict, event: string, envelope: StateDict): StateDict {
723
+ /**
724
+ * Apply one event to the state.
725
+ *
726
+ * `quality` is the shadow pass's already-computed result, handed in rather than
727
+ * fetched: this function runs INSIDE the state lock, and a spawn in here would
728
+ * hold that lock for the duration of a typecheck.
729
+ */
730
+ function _update(
731
+ state: StateDict,
732
+ event: string,
733
+ envelope: StateDict,
734
+ quality: { runs: QualityRun[]; source: string } | null = null,
735
+ ): StateDict {
361
736
  const session_id = (envelope["session_id"] || state["session_id"] || "") as string;
362
737
  if (session_id && session_id !== state["session_id"]) {
363
738
  // Session boundary — reset session-scoped counters.
@@ -376,6 +751,16 @@ function _update(state: StateDict, event: string, envelope: StateDict): StateDic
376
751
  state = _reset_turn(state, session_id);
377
752
  }
378
753
 
754
+ // The host that wrote this file. Recorded because the instrument-gap reading
755
+ // is a PER-HOST question and the witness carried no answer to it: every
756
+ // coverage number had to be attributed by hand, and a file from an unknown
757
+ // host is indistinguishable from a host with nothing to report. Written on
758
+ // every update rather than once, so a file predating this field acquires it
759
+ // the first time the host touches it; until then a reader sees it absent,
760
+ // which is the honest reading and not a default.
761
+ const platform = envelope["platform"];
762
+ if (typeof platform === "string" && platform) state["platform"] = platform;
763
+
379
764
  let payload = envelope["payload"];
380
765
  if (!(typeof payload === "object" && payload !== null && !Array.isArray(payload))) {
381
766
  payload = {};
@@ -386,6 +771,11 @@ function _update(state: StateDict, event: string, envelope: StateDict): StateDic
386
771
  state = _reset_turn(state, session_id);
387
772
  } else if (event === "post_tool_use") {
388
773
  const [tool, cmd] = _extract_command(pl);
774
+ // Counted BEFORE the verification branch, so a run recorded in this same
775
+ // event is placed after the edits that preceded it and never after itself.
776
+ if (tool !== null && EDIT_TOOLS.has(tool)) {
777
+ state["edits_this_turn"] = _asInt(state["edits_this_turn"]) + 1;
778
+ }
389
779
  if (cmd && _is_verification(cmd)) {
390
780
  const output = _extract_output(pl);
391
781
  // No readable output → the guard has nothing to judge, so behaviour is
@@ -439,6 +829,7 @@ function _update(state: StateDict, event: string, envelope: StateDict): StateDic
439
829
  vacuous,
440
830
  counted: counts,
441
831
  };
832
+
442
833
  if (counts) {
443
834
  state["verifications_this_turn"] = _asInt(state["verifications_this_turn"]) + 1;
444
835
  state["verifications_this_session"] =
@@ -448,8 +839,73 @@ function _update(state: StateDict, event: string, envelope: StateDict): StateDic
448
839
  state["nonevidence_this_turn"] = _asInt(state["nonevidence_this_turn"]) + 1;
449
840
  }
450
841
  }
842
+
843
+ // The RUN record, written beside `last_verification` and gated on a
844
+ // DIFFERENT, wider selector — the one the turn-end gate's detector C uses.
845
+ //
846
+ // Two selectors is a smell, so here is why it is not one — and the reason
847
+ // was rewritten 2026-09-30, because the version standing here was half
848
+ // wrong. It claimed the narrow selector protected `verifications_this_turn`
849
+ // AND the FC-3b `ci_last` discrimination. `ci_last` is written inside the
850
+ // nested `isCiPoll` branch above, reached through the second disjunct of
851
+ // `_is_verification` whatever the first one says, so no widening of the
852
+ // first could ever have moved it; and nothing branches on the counter.
853
+ // Both are now pinned by tests rather than asserted in a comment.
854
+ //
855
+ // What survives is a real distinction, and it is about ERROR COST, not
856
+ // about two spellings of one idea. `isVerificationCommand` answers *did
857
+ // this verify* — a false positive there clears an unverified edit, so it is
858
+ // head-anchored. `mightBeVerification` answers *is this worth writing down*
859
+ // — and the run record decides nothing by itself, since `classifyRun` is
860
+ // what judges it. So the record path wants every command the gate might
861
+ // read, including the ones the classifier will then reject —
862
+ // `not_a_verification_command` is a verdict the gate needs to SEE, and a
863
+ // recorder that filtered those out would leave the gate unable to tell "the
864
+ // turn ran `echo test`" from "the turn ran nothing at all".
865
+ //
866
+ // `after_edits` is the edit counter as it stood when the command ran. The
867
+ // reader compares it against the number of edits the turn made in total:
868
+ // equal or greater means nothing was edited afterwards. That is an ORDINAL
869
+ // comparison needing no clock, so it survives a host whose timestamps are
870
+ // coarse or absent, and it degrades toward under-refusing when the reader's
871
+ // own count is short.
872
+ if (cmd && mightBeVerification(cmd)) {
873
+ const streams = _extract_run_streams(pl);
874
+ const exit = _extract_exit_reading(pl, _is_failure_event(pl, envelope));
875
+ const runs = Array.isArray(state["verification_runs"])
876
+ ? [...(state["verification_runs"] as unknown[])]
877
+ : [];
878
+ runs.push({
879
+ command: cmd.slice(0, 512),
880
+ tool,
881
+ exit_code: exit.code,
882
+ // Provenance, not decoration: `response_shape` is a code this recorder
883
+ // DERIVED from the host's success form, and a reader disputing a verdict
884
+ // needs to know which of the three readings produced it.
885
+ exit_source: exit.source,
886
+ interrupted: exit.interrupted,
887
+ stdout_tail: streams.stdout.slice(-RUN_OUTPUT_TAIL_BYTES),
888
+ // Written, and previously not: `RunRecord.stderr_tail` was declared and
889
+ // read by the classifier's `output()` while nothing populated it, so
890
+ // stderr reached a parser only by accident inside a stringified blob.
891
+ stderr_tail: streams.stderr.slice(-RUN_OUTPUT_TAIL_BYTES),
892
+ runner: runnerOf(cmd),
893
+ after_edits: _asInt(state["edits_this_turn"]),
894
+ at: _now(),
895
+ });
896
+ // The NEWEST are kept for detector C, which asks whether a pass follows
897
+ // the last edit. Detector F asks a question the head answers — was there a
898
+ // red before the green — so the cap keeps the FIRST failing record too,
899
+ // ahead of the tail, rather than letting a chatty turn drop the red half
900
+ // of its own red-then-green pair.
901
+ state["verification_runs"] = _cap_runs(runs);
902
+ }
451
903
  } else if (event === "stop") {
452
904
  state["last_stop_at"] = _now();
905
+ if (quality !== null) {
906
+ state["quality_runs"] = quality.runs;
907
+ state["quality_files_source"] = quality.source;
908
+ }
453
909
  }
454
910
 
455
911
  state["checked_at"] = _now();
@@ -511,6 +967,15 @@ export function run(
511
967
 
512
968
  const target = path.join(consumer_root, statePathFor(session_id));
513
969
 
970
+ // The shadow quality pass, computed BEFORE the lock (see `_update`'s header)
971
+ // and only at `stop`. `off` is the template default and the resolved value of
972
+ // every unreadable or unrecognised setting, so an install that never opted in
973
+ // spawns nothing, reads no manifest and writes no new field — the acceptance
974
+ // criterion is that its record is byte-identical to the base ref.
975
+ const quality_mode = event === "stop" ? readTouchedFileQualityMode(consumer_root) : "off";
976
+ const quality =
977
+ quality_mode === "off" ? null : collectTouchedFileQuality(consumer_root, session_id);
978
+
514
979
  // LOAD → UPDATE → PUBLISH under ONE lock, not three separate steps.
515
980
  //
516
981
  // This was `_load_state` / `_update` / `atomic_write_json`, which makes the
@@ -541,7 +1006,7 @@ export function run(
541
1006
  // treats a missing counter as 0 and the `=== true` guards treat a missing
542
1007
  // flag as false, so every assertion would still pass while `schema_version`
543
1008
  // and an explicit `ci_last: null` vanished from a freshly created file.
544
- state = _update({ ..._empty_state(), ...loaded } as StateDict, event, envelope);
1009
+ state = _update({ ..._empty_state(), ...loaded } as StateDict, event, envelope, quality);
545
1010
  return state;
546
1011
  });
547
1012
  if (outcome === "failed") {
@@ -562,6 +1027,21 @@ export function run(
562
1027
  );
563
1028
  }
564
1029
 
1030
+ // `warn` — ONE advisory line, and the exit code does not move.
1031
+ //
1032
+ // In this tree a warn is conventionally an exit of 2, and on the one host that
1033
+ // honours a deny that reads as a BLOCK. D3 of the roadmap settles it the other
1034
+ // way for this field: shadow → warn only, never a block, on the strength of
1035
+ // this hook's own contract ("the hook itself never blocks — it is
1036
+ // observability infra, not control flow"). So the line goes to stderr and the
1037
+ // function returns 0 on this path exactly as on every other.
1038
+ if (quality_mode === "warn" && quality !== null) {
1039
+ const line = advisoryLine(quality.runs);
1040
+ if (line !== null) {
1041
+ process.stderr.write(`${line}\n`);
1042
+ }
1043
+ }
1044
+
565
1045
  if (verbose) {
566
1046
  process.stderr.write(
567
1047
  `verify-before-complete-hook: event=${event} ` +
@@ -613,6 +1093,74 @@ export function main(argv?: string[]): number {
613
1093
  return run(_readStdin(), { consumer_root: process.cwd(), verbose: args.verbose });
614
1094
  }
615
1095
 
1096
+ // ---------------------------------------------------------------------------
1097
+ // The consumer side of `verification_runs`, living with its producer
1098
+ // ---------------------------------------------------------------------------
1099
+
1100
+ /**
1101
+ * Why the turn-end gate's reader sits HERE and not in the gate.
1102
+ *
1103
+ * The shape of `verification_runs` and `edits_this_turn` is this file's; the
1104
+ * reader is the only consumer of that shape, and the gate already imported this
1105
+ * module's path BUILDER rather than a path constant for exactly the reason that
1106
+ * applies here too — "the consumer cannot read a path the producer does not
1107
+ * write" is only true while the two agree, and separating them made the
1108
+ * agreement invisible when the layout moved. Keeping the reader beside the
1109
+ * writer makes a shape change a type error in one file instead of a silent
1110
+ * disagreement across two.
1111
+ *
1112
+ * It also keeps the gate under its source-size ceiling without deleting
1113
+ * anything, which is the move the ratchet asks for rather than a trim.
1114
+ */
1115
+ export interface TurnRunState {
1116
+ readonly runs: readonly unknown[];
1117
+ readonly edits_this_turn: number;
1118
+ }
1119
+
1120
+ /**
1121
+ * The turn's recorded verification runs, or `null` when the recorder is not live.
1122
+ *
1123
+ * WHY THE LIVENESS TEST IS `edits_this_turn >= 1` AND NOT "the file exists".
1124
+ * The obvious predicate — a state file with a `verification_runs` key — is
1125
+ * wrong in a way that would refuse honest work on entire platforms. That key is
1126
+ * present in the recorder's EMPTY state, so a host whose `post_tool_use` slot
1127
+ * the manifest does not bind still has a file carrying `[]`, written by the
1128
+ * prompt and stop events alone. Reading that as "this turn ran nothing" would
1129
+ * refuse every editing turn on such a host, whatever the operator actually ran.
1130
+ *
1131
+ * `edits_this_turn` is the one field only a `post_tool_use` event can raise. The
1132
+ * detector reaches here having already found an edit in the transcript, so a
1133
+ * recorder that saw none of this turn's tool events is exactly the case this
1134
+ * returns `null` for — and `null` means the transcript path answers, which is
1135
+ * the behavior that predates the record path.
1136
+ *
1137
+ * The ownership check is the same one detector D applies to `ci_last`, for the
1138
+ * same reason: a FOREIGN file's passing record would vouch for a run this
1139
+ * session never made.
1140
+ */
1141
+ export function readTurnRunState(
1142
+ workspaceRoot: string,
1143
+ session_id: string,
1144
+ ): TurnRunState | null {
1145
+ if (!has_stable_session_id(session_id)) return null;
1146
+ try {
1147
+ const raw = fs.readFileSync(path.join(workspaceRoot, statePathFor(session_id)), 'utf-8');
1148
+ const decoded: unknown = JSON.parse(raw);
1149
+ if (!owns_session_state(decoded, session_id)) return null;
1150
+ if (typeof decoded !== 'object' || decoded === null || Array.isArray(decoded)) return null;
1151
+ const state = decoded as Record<string, unknown>;
1152
+ const runs = state['verification_runs'];
1153
+ const edits = state['edits_this_turn'];
1154
+ if (!Array.isArray(runs)) return null;
1155
+ if (typeof edits !== 'number' || !Number.isFinite(edits) || edits < 1) return null;
1156
+ return { runs: runs as readonly unknown[], edits_this_turn: edits };
1157
+ } catch {
1158
+ // Absent, unreadable or malformed — the recorder said nothing, so the
1159
+ // transcript answers. Never a refusal of its own.
1160
+ return null;
1161
+ }
1162
+ }
1163
+
616
1164
  // Bundle-safety: never auto-run when inlined into an esbuild bundle, where
617
1165
  // every module shares the bundle's `import.meta.url` (see cmd_migrate.ts).
618
1166
  declare const __AGENT_CONFIG_BUNDLE__: boolean | undefined;