@event4u/agent-config 16.1.0 → 16.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (467) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +98 -257
  5. package/README.md +14 -12
  6. package/dist/agent-src/commands/analyze/inbox.md +1 -1
  7. package/dist/agent-src/commands/analyze/roadmap-repos.md +1 -1
  8. package/dist/agent-src/commands/bug/fix.md +17 -2
  9. package/dist/agent-src/commands/feature/plan.md +1 -1
  10. package/dist/agent-src/commands/feature/roadmap.md +1 -1
  11. package/dist/agent-src/commands/fix/ci.md +1 -1
  12. package/dist/agent-src/commands/implement-ticket.md +1 -1
  13. package/dist/agent-src/commands/jira-ticket.md +1 -1
  14. package/dist/agent-src/commands/roadmap/create.md +1 -1
  15. package/dist/agent-src/commands/roadmap/materialize.md +1 -1
  16. package/dist/agent-src/commands/roadmap/process-full.md +5 -5
  17. package/dist/agent-src/commands/roadmap/resolve-blockers.md +361 -0
  18. package/dist/agent-src/commands/roadmap/triage-parked.md +325 -0
  19. package/dist/agent-src/commands/roadmap.md +18 -4
  20. package/dist/agent-src/commands/tests/create.md +17 -3
  21. package/dist/agent-src/commands/tests/execute.md +22 -5
  22. package/dist/agent-src/commands/work.md +4 -0
  23. package/dist/agent-src/contexts/authority/decision-revisit-gate-enforcement.md +43 -0
  24. package/dist/agent-src/contexts/communication/rules-auto/design-fidelity-enforcement.md +24 -0
  25. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +6 -6
  26. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +4 -8
  27. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +32 -2
  28. package/dist/agent-src/contexts/execution/session-canary-enforcement-history.md +67 -0
  29. package/dist/agent-src/contexts/execution/toolchain-resolver.md +101 -7
  30. package/dist/agent-src/rules/active-remediation.md +1 -1
  31. package/dist/agent-src/rules/autonomous-execution.md +1 -0
  32. package/dist/agent-src/rules/broken-access-control.md +1 -0
  33. package/dist/agent-src/rules/command-suggestion-policy.md +1 -0
  34. package/dist/agent-src/rules/context-hygiene.md +1 -1
  35. package/dist/agent-src/rules/decision-revisit-gate.md +16 -33
  36. package/dist/agent-src/rules/design-fidelity.md +13 -16
  37. package/dist/agent-src/rules/doc-screenshot-hygiene.md +1 -0
  38. package/dist/agent-src/rules/domain-safety-disclaimer.md +1 -0
  39. package/dist/agent-src/rules/domain-safety-pii.md +1 -0
  40. package/dist/agent-src/rules/domain-safety-retention.md +1 -0
  41. package/dist/agent-src/rules/engineering-safety-floor.md +1 -0
  42. package/dist/agent-src/rules/evaluator-independence.md +3 -2
  43. package/dist/agent-src/rules/fast-path-marker-visibility.md +1 -0
  44. package/dist/agent-src/rules/finance-safety-floor.md +1 -0
  45. package/dist/agent-src/rules/git-history-discipline.md +1 -0
  46. package/dist/agent-src/rules/image-likeness-and-rights.md +1 -0
  47. package/dist/agent-src/rules/legal-safety-floor.md +3 -2
  48. package/dist/agent-src/rules/lethal-trifecta-guard.md +1 -0
  49. package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
  50. package/dist/agent-src/rules/media-governance-routing.md +14 -17
  51. package/dist/agent-src/rules/persona-governance.md +2 -2
  52. package/dist/agent-src/rules/provider-lifecycle-discipline.md +1 -1
  53. package/dist/agent-src/rules/question-not-instruction.md +1 -0
  54. package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
  55. package/dist/agent-src/rules/runtime-safety.md +1 -0
  56. package/dist/agent-src/rules/secret-vcs-guard.md +1 -0
  57. package/dist/agent-src/rules/security-sensitive-stop.md +3 -2
  58. package/dist/agent-src/rules/session-canary.md +8 -62
  59. package/dist/agent-src/rules/settings-ask-protocol.md +1 -0
  60. package/dist/agent-src/rules/source-confidentiality.md +1 -0
  61. package/dist/agent-src/rules/strategy-safety-floor.md +1 -0
  62. package/dist/agent-src/rules/tool-safety.md +1 -0
  63. package/dist/agent-src/rules/ui-audit-gate.md +1 -2
  64. package/dist/agent-src/rules/untrusted-input-defense.md +1 -0
  65. package/dist/agent-src/rules/user-interrupt-priority.md +1 -0
  66. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
  67. package/dist/agent-src/scripts/deferral_carry.ts +339 -0
  68. package/dist/agent-src/scripts/update_roadmap_progress.ts +4 -1
  69. package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
  70. package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +2 -2
  71. package/dist/agent-src/skills/api-design/data/manifest.json +2 -2
  72. package/dist/agent-src/skills/api-testing/SKILL.md +17 -0
  73. package/dist/agent-src/skills/code-intelligence/SKILL.md +14 -2
  74. package/dist/agent-src/skills/design-review/SKILL.md +30 -4
  75. package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
  76. package/dist/agent-src/skills/design-system-capture/references/import-procedure.md +1 -1
  77. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +18 -3
  78. package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
  79. package/dist/agent-src/skills/fe-design/SKILL.md +1 -1
  80. package/dist/agent-src/skills/iconography/SKILL.md +1 -1
  81. package/dist/agent-src/skills/js-library-packaging/SKILL.md +2 -2
  82. package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
  83. package/dist/agent-src/skills/monorepo-workspace/SKILL.md +1 -1
  84. package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
  85. package/dist/agent-src/skills/quality-tools/SKILL.md +11 -1
  86. package/dist/agent-src/skills/quality-tools/references/go-tools.md +76 -0
  87. package/dist/agent-src/skills/quality-tools/references/python-tools.md +104 -0
  88. package/dist/agent-src/skills/review-routing/SKILL.md +2 -2
  89. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
  90. package/dist/agent-src/skills/storybook-workshop/SKILL.md +3 -3
  91. package/dist/agent-src/skills/tailwind-engineer/SKILL.md +1 -1
  92. package/dist/agent-src/skills/test-case-discovery/SKILL.md +7 -0
  93. package/dist/agent-src/skills/test-driven-development/SKILL.md +17 -0
  94. package/dist/agent-src/skills/test-performance/SKILL.md +19 -1
  95. package/dist/agent-src/skills/ui-component-architect/SKILL.md +25 -2
  96. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  97. package/dist/agent-src/templates/roadmaps.md +30 -0
  98. package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  99. package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +302 -21
  100. package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  101. package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  102. package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
  103. package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
  104. package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  105. package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  106. package/dist/cli/python/knowledge_ingest.js +1 -1
  107. package/dist/cli/python/workspace_drive.js +1 -1
  108. package/dist/cli/registry.js +1 -1
  109. package/dist/cli-delegate/archive_completed_roadmaps.js +92 -12
  110. package/dist/cli-delegate/check_update_banner.js +12 -1
  111. package/dist/cli-delegate/chunk-523HCFYO.js +90 -0
  112. package/dist/cli-delegate/{chunk-M2DPGFZ4.js → chunk-6V6TA25H.js} +22 -152
  113. package/dist/cli-delegate/chunk-7IQE3D5S.js +191 -0
  114. package/dist/cli-delegate/{chunk-6KFEC3G7.js → chunk-CQWX336L.js} +1 -1
  115. package/dist/cli-delegate/chunk-DEIV7XP7.js +108 -0
  116. package/dist/cli-delegate/chunk-EEHDXSYD.js +2040 -0
  117. package/dist/cli-delegate/{chunk-P6ZHP5W2.js → chunk-GKE7ZVFA.js} +4 -4
  118. package/dist/cli-delegate/chunk-JQOM7TTM.js +1201 -0
  119. package/dist/cli-delegate/{chunk-UNCRJBT3.js → chunk-JQVYKIZA.js} +41 -0
  120. package/dist/cli-delegate/{chunk-FC75PGJE.js → chunk-JY2I2DXV.js} +12 -1
  121. package/dist/cli-delegate/chunk-KB5FZ655.js +208 -0
  122. package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-KR6EM23A.js} +235 -1455
  123. package/dist/cli-delegate/{chunk-LTYVIY76.js → chunk-LIYTLH6O.js} +1 -1
  124. package/dist/cli-delegate/chunk-NQ7WT43P.js +2729 -0
  125. package/dist/cli-delegate/chunk-NXEILF2K.js +2160 -0
  126. package/dist/cli-delegate/{chunk-5R7HOLAF.js → chunk-NYX3L2PZ.js} +1 -1
  127. package/dist/cli-delegate/{chunk-4E2W63DA.js → chunk-OKJUIAPS.js} +62 -1
  128. package/dist/cli-delegate/{chunk-QAJS5YWY.js → chunk-PHD7OOLE.js} +2 -1
  129. package/dist/cli-delegate/{chunk-RGNIWTEA.js → chunk-QLDHIPWR.js} +1 -1
  130. package/dist/cli-delegate/{chunk-HDVVGPQW.js → chunk-RQNNW7IF.js} +1405 -567
  131. package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-S4UMZNES.js} +1 -1
  132. package/dist/cli-delegate/{chunk-6KM6ORRY.js → chunk-S4YWNRKO.js} +24 -8
  133. package/dist/cli-delegate/{chunk-GJCBB2UG.js → chunk-TRI6DQZ6.js} +3054 -325
  134. package/dist/cli-delegate/chunk-ULQRUR23.js +208 -0
  135. package/dist/cli-delegate/{chunk-NUHLI2RX.js → chunk-YCU3TIXH.js} +2 -0
  136. package/dist/cli-delegate/chunk-ZBZV2G5R.js +210 -0
  137. package/dist/cli-delegate/{chunk-WGXTVKCQ.js → chunk-ZUZKZYCN.js} +5 -3
  138. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  139. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  140. package/dist/cli-delegate/cmd_conformance.js +1862 -809
  141. package/dist/cli-delegate/cmd_doctor.js +13 -6
  142. package/dist/cli-delegate/cmd_doctor_neighbours.js +23 -0
  143. package/dist/cli-delegate/cmd_explain.js +5 -5
  144. package/dist/cli-delegate/cmd_export.js +11 -8
  145. package/dist/cli-delegate/cmd_fleet.js +3 -3
  146. package/dist/cli-delegate/cmd_handoff.js +10 -7
  147. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  148. package/dist/cli-delegate/cmd_memory_get.js +3 -1
  149. package/dist/cli-delegate/cmd_migrate.js +2 -2
  150. package/dist/cli-delegate/cmd_packs_active.js +3 -3
  151. package/dist/cli-delegate/cmd_preflight.js +6 -6
  152. package/dist/cli-delegate/cmd_prune.js +11 -8
  153. package/dist/cli-delegate/cmd_refresh.js +2 -1
  154. package/dist/cli-delegate/cmd_route_audit.js +8 -6
  155. package/dist/cli-delegate/cmd_route_explain.js +4 -4
  156. package/dist/cli-delegate/cmd_session_recycle.js +4 -3
  157. package/dist/cli-delegate/cmd_settings_get.js +1 -1
  158. package/dist/cli-delegate/cmd_settings_migrate.js +11 -8
  159. package/dist/cli-delegate/cmd_settings_set.js +4 -4
  160. package/dist/cli-delegate/cmd_sync.js +11 -8
  161. package/dist/cli-delegate/cmd_uninstall.js +11 -8
  162. package/dist/cli-delegate/cmd_update.js +1 -1
  163. package/dist/cli-delegate/cmd_upgrade.js +3 -2
  164. package/dist/cli-delegate/cmd_validate.js +11 -8
  165. package/dist/cli-delegate/cmd_versions.js +1 -1
  166. package/dist/cli-delegate/deferral_carry.js +16 -0
  167. package/dist/cli-delegate/gate_execute.js +2 -2
  168. package/dist/cli-delegate/roadmap_gates.js +5 -5
  169. package/dist/cli-delegate/update_roadmap_progress.js +1 -1
  170. package/dist/discovery/deprecation-report.md +1 -1
  171. package/dist/discovery/discovery-manifest.json +132 -80
  172. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  173. package/dist/discovery/discovery-manifest.summary.md +3 -3
  174. package/dist/discovery/dormancy-report.md +1 -1
  175. package/dist/discovery/orphan-report.md +1 -1
  176. package/dist/discovery/packs.json +7 -5
  177. package/dist/discovery/trust-report.md +2 -2
  178. package/dist/discovery/workspaces.json +6 -4
  179. package/dist/hooks/dispatch.js +7972 -5186
  180. package/dist/hooks/dispatch.sha256 +1 -0
  181. package/dist/install/claudePathsPlan.js +47 -6
  182. package/dist/install/claudeRuleRewrite.js +7 -0
  183. package/dist/install/emit_host_rules_cli.js +39 -36
  184. package/dist/install/globalRuleScope.js +87 -0
  185. package/dist/install/host_rule_ownership.js +160 -0
  186. package/dist/install/install.mjs +2226 -1055
  187. package/dist/install/installThinLayer.js +261 -0
  188. package/dist/install/installedRuleLinks.js +176 -0
  189. package/dist/install/wizard-plan.js +24 -0
  190. package/dist/mcp/registry-manifest.json +3 -3
  191. package/dist/mcp/server.json +2 -2
  192. package/dist/mcp/server.mjs +40913 -34865
  193. package/dist/scripts/_lib/agent_settings.js +11 -0
  194. package/dist/scripts/_lib/claude_settings_hooks.js +10 -8
  195. package/dist/scripts/_lib/installed_layer.js +378 -0
  196. package/dist/scripts/_lib/lean_projection_mode.js +218 -15
  197. package/dist/scripts/_lib/rule_consequence_class.js +45 -0
  198. package/dist/scripts/_lib/rule_injection.js +67 -10
  199. package/dist/scripts/_lib/rule_law_section.js +122 -0
  200. package/dist/scripts/_lib/thin_rules.js +458 -0
  201. package/dist/scripts/_lib/windsurf_render.js +60 -0
  202. package/dist/scripts/condense.js +14 -26
  203. package/dist/scripts/project_thin_rules.js +14 -294
  204. package/dist/server/schemas/settings.js +29 -0
  205. package/dist/shared/skillRanking.js +74 -13
  206. package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
  207. package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
  208. package/dist/ui/index.html +2 -2
  209. package/docs/contracts/settings-classes.md +10 -6
  210. package/package.json +4 -4
  211. package/src/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
  212. package/src/agent-src/scripts/deferral_carry.ts +339 -0
  213. package/src/agent-src/scripts/update_roadmap_progress.ts +4 -1
  214. package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  215. package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +302 -21
  216. package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  217. package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  218. package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
  219. package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
  220. package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  221. package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  222. package/src/config/agent-settings.template.yml +91 -22
  223. package/src/config/ci-local-parity.yml +18 -0
  224. package/src/config/continuity-surface.json +9 -2
  225. package/src/config/evaluator-budgets.json +4 -4
  226. package/src/config/gate-coverage.yml +327 -3
  227. package/src/config/gate-violation-baselines.json +21 -9
  228. package/src/config/hook-bundle-budget.json +25 -0
  229. package/src/config/hook-latency-budget.json +21 -0
  230. package/src/config/hook-token-budget.json +8 -3
  231. package/src/config/host-instruction-limits.json +33 -0
  232. package/src/config/metric-registry.yml +114 -0
  233. package/src/config/placeholder-drift-budget.json +10 -0
  234. package/src/config/routing-coverage-seed.json +38 -3
  235. package/src/config/rule-activation-census.json +7 -3
  236. package/src/config/rule-consequence-class.json +161 -0
  237. package/src/config/rule-law-ceilings.json +99 -0
  238. package/src/install/claudePathsPlan.ts +46 -4
  239. package/src/install/claudeRuleRewrite.ts +10 -1
  240. package/src/install/emit_host_rules_cli.ts +43 -34
  241. package/src/install/globalRuleScope.ts +109 -0
  242. package/src/install/host_rule_ownership.ts +170 -0
  243. package/src/install/installThinLayer.ts +312 -0
  244. package/src/install/installedRuleLinks.ts +220 -0
  245. package/src/install/wizard-plan.ts +24 -0
  246. package/src/scripts/_cli/cmd_doctor.ts +28 -90
  247. package/src/scripts/_cli/cmd_doctor_neighbours.ts +293 -0
  248. package/src/scripts/_cli/doctor_execution.ts +100 -17
  249. package/src/scripts/_cli/doctor_network_posture.ts +226 -0
  250. package/src/scripts/_dispatch.bash +16 -2
  251. package/src/scripts/_lib/agent_settings.ts +11 -0
  252. package/src/scripts/_lib/body_portable.ts +102 -0
  253. package/src/scripts/_lib/cc_transcript.ts +132 -1
  254. package/src/scripts/_lib/claude_settings_hooks.ts +10 -8
  255. package/src/scripts/_lib/concern_sla_window.ts +200 -0
  256. package/src/scripts/_lib/estate_offsets.ts +241 -0
  257. package/src/scripts/_lib/exemption_shape.ts +143 -0
  258. package/src/scripts/_lib/fnv.ts +23 -0
  259. package/src/scripts/_lib/foreign_scan.ts +106 -0
  260. package/src/scripts/_lib/forge_protection.ts +114 -0
  261. package/src/scripts/_lib/forge_reader.ts +558 -0
  262. package/src/scripts/_lib/gitignore_block.ts +314 -0
  263. package/src/scripts/_lib/global_deploy_inventory.ts +32 -0
  264. package/src/scripts/_lib/graph_feeder_record.ts +214 -0
  265. package/src/scripts/_lib/hook_settings.ts +73 -0
  266. package/src/scripts/_lib/host_capability.ts +40 -19
  267. package/src/scripts/_lib/host_env_write.ts +111 -0
  268. package/src/scripts/_lib/host_hook_merge.ts +165 -0
  269. package/src/scripts/_lib/installed_layer.ts +538 -0
  270. package/src/scripts/_lib/json_merge.ts +82 -0
  271. package/src/scripts/_lib/json_pointers.ts +93 -11
  272. package/src/scripts/_lib/lean_projection_mode.ts +275 -15
  273. package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
  274. package/src/scripts/_lib/machine_wake.ts +16 -3
  275. package/src/scripts/_lib/neighbour_census.ts +760 -0
  276. package/src/scripts/_lib/neighbour_scan.ts +122 -0
  277. package/src/scripts/_lib/neighbour_tool_use.ts +189 -0
  278. package/src/scripts/_lib/obligations.ts +119 -8
  279. package/src/scripts/_lib/probe_inputs.ts +271 -0
  280. package/src/scripts/_lib/prompt_shape.ts +46 -0
  281. package/src/scripts/_lib/read_surface_scan.ts +322 -0
  282. package/src/scripts/_lib/release_highlights.ts +2 -1
  283. package/src/scripts/_lib/release_migration_gate.ts +19 -3
  284. package/src/scripts/_lib/reserved_name_sweep.ts +73 -0
  285. package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
  286. package/src/scripts/_lib/review_coverage.ts +134 -0
  287. package/src/scripts/_lib/rule_consequence_class.ts +71 -0
  288. package/src/scripts/_lib/rule_injection.ts +130 -10
  289. package/src/scripts/_lib/rule_law_section.ts +147 -0
  290. package/src/scripts/_lib/rule_layer_overlap.ts +58 -0
  291. package/src/scripts/_lib/shell_write_shapes.ts +108 -0
  292. package/src/scripts/_lib/skill_origin.ts +254 -0
  293. package/src/scripts/_lib/structural_hiding.ts +622 -0
  294. package/src/scripts/_lib/structured_ask.ts +64 -21
  295. package/src/scripts/_lib/thin_rules.ts +540 -0
  296. package/src/scripts/_lib/touched_file_quality.ts +434 -0
  297. package/src/scripts/_lib/turn_end_refusals.ts +600 -4
  298. package/src/scripts/_lib/turn_end_transcript.ts +216 -0
  299. package/src/scripts/_lib/verification_command.ts +324 -0
  300. package/src/scripts/_lib/verification_evidence.ts +647 -0
  301. package/src/scripts/_lib/verify_clause.ts +280 -0
  302. package/src/scripts/_lib/windsurf_render.ts +63 -0
  303. package/src/scripts/ai_council/cli_call_budget.ts +13 -5
  304. package/src/scripts/ai_council/clients.ts +26 -57
  305. package/src/scripts/ai_council/config.ts +1 -1
  306. package/src/scripts/ai_council/vendor_defaults.ts +45 -0
  307. package/src/scripts/ai_team/team_dispatch.ts +4 -0
  308. package/src/scripts/audit_skill_overlap.ts +261 -0
  309. package/src/scripts/before_complete_hook.ts +573 -25
  310. package/src/scripts/bench_hook_latency.ts +412 -5
  311. package/src/scripts/bench_touched_file_quality.ts +238 -0
  312. package/src/scripts/cache_realization_report.ts +61 -1
  313. package/src/scripts/check_argument_hint.ts +287 -0
  314. package/src/scripts/check_claims.ts +125 -1
  315. package/src/scripts/check_enforcement_coverage.ts +38 -9
  316. package/src/scripts/check_enforcement_matrix.ts +621 -22
  317. package/src/scripts/check_estate_count.ts +130 -153
  318. package/src/scripts/check_finding_dispositions.ts +138 -7
  319. package/src/scripts/check_generator_sync.ts +71 -0
  320. package/src/scripts/check_hook_bundle_composition.ts +370 -0
  321. package/src/scripts/check_host_docs_digest.ts +786 -0
  322. package/src/scripts/check_host_format_column.ts +559 -0
  323. package/src/scripts/check_input_references.ts +228 -0
  324. package/src/scripts/check_inputs_parity.ts +235 -0
  325. package/src/scripts/check_kernel_edit_ratified.ts +89 -9
  326. package/src/scripts/check_kill_switch_table.ts +294 -0
  327. package/src/scripts/check_memory.ts +7 -1
  328. package/src/scripts/check_placeholder_drift.ts +221 -0
  329. package/src/scripts/check_read_surface_coverage.ts +515 -0
  330. package/src/scripts/check_release_highlights.ts +10 -0
  331. package/src/scripts/check_release_holds.ts +2 -1
  332. package/src/scripts/check_release_pr_shape.ts +38 -15
  333. package/src/scripts/check_routing_coverage.ts +378 -11
  334. package/src/scripts/check_standing_rule_delivery.ts +33 -13
  335. package/src/scripts/check_static_parity.ts +17 -0
  336. package/src/scripts/check_verify_expectation_delta.ts +521 -0
  337. package/src/scripts/ci_settle.ts +145 -19
  338. package/src/scripts/closure_scan.ts +115 -1
  339. package/src/scripts/code_graph/detect.ts +127 -7
  340. package/src/scripts/code_graph/foreign.ts +194 -0
  341. package/src/scripts/code_graph/query.ts +217 -4
  342. package/src/scripts/code_graph/types.ts +26 -2
  343. package/src/scripts/code_graph/validate.ts +3 -1
  344. package/src/scripts/code_graph/verbs.ts +103 -0
  345. package/src/scripts/compile_hook_manifest.ts +90 -14
  346. package/src/scripts/condense.ts +14 -27
  347. package/src/scripts/estate_exemption_shape.ts +202 -0
  348. package/src/scripts/external_sources_denylist.json +29 -4
  349. package/src/scripts/generate_host_cost_table.ts +1 -1
  350. package/src/scripts/hook_manifest.json +1 -1
  351. package/src/scripts/hook_manifest.yaml +192 -14
  352. package/src/scripts/hooks/block_config_weakening.ts +294 -4
  353. package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
  354. package/src/scripts/hooks/block_plumbing_writes.ts +342 -0
  355. package/src/scripts/hooks/bundle_integrity.ts +291 -0
  356. package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
  357. package/src/scripts/hooks/code_graph_context_hook.ts +253 -25
  358. package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
  359. package/src/scripts/hooks/concern_failure_policy.ts +280 -0
  360. package/src/scripts/hooks/concern_registry.ts +2 -0
  361. package/src/scripts/hooks/concern_timings.ts +94 -0
  362. package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
  363. package/src/scripts/hooks/design_pass_hook.ts +100 -9
  364. package/src/scripts/hooks/design_slop_hook.ts +1 -2
  365. package/src/scripts/hooks/dispatch_hook.ts +77 -71
  366. package/src/scripts/hooks/edit_shape_hook.ts +1 -2
  367. package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
  368. package/src/scripts/hooks/evidence_independence.ts +1 -2
  369. package/src/scripts/hooks/exit_codes.ts +129 -0
  370. package/src/scripts/hooks/host_lowering.json +1 -0
  371. package/src/scripts/hooks/host_lowering.ts +146 -3
  372. package/src/scripts/hooks/host_lowering.yaml +262 -46
  373. package/src/scripts/hooks/host_semantics.ts +30 -2
  374. package/src/scripts/hooks/injection_budget.ts +9 -2
  375. package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
  376. package/src/scripts/hooks/journal_record_hook.ts +1 -2
  377. package/src/scripts/hooks/memory_recall_hook.ts +1 -2
  378. package/src/scripts/hooks/obligation_settle_hook.ts +151 -5
  379. package/src/scripts/hooks/one_question_per_ask_hook.ts +43 -6
  380. package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
  381. package/src/scripts/hooks/payload_stub.ts +10 -1
  382. package/src/scripts/hooks/permission_gate.ts +1 -2
  383. package/src/scripts/hooks/push_settle_hook.ts +1 -3
  384. package/src/scripts/hooks/replay_hook.ts +2 -1
  385. package/src/scripts/hooks/reread_guard_hook.ts +1 -2
  386. package/src/scripts/hooks/review_baseline_hook.ts +1 -2
  387. package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
  388. package/src/scripts/hooks/rule_inject_hook.ts +765 -68
  389. package/src/scripts/hooks/run_continuation_hook.ts +13 -15
  390. package/src/scripts/hooks/skill_route_hook.ts +1 -2
  391. package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
  392. package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
  393. package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
  394. package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
  395. package/src/scripts/hooks/table_fingerprint.ts +70 -0
  396. package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
  397. package/src/scripts/hooks/telemetry_usage_hook.ts +65 -10
  398. package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
  399. package/src/scripts/hooks/turn_end_gate_hook.ts +421 -324
  400. package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
  401. package/src/scripts/hooks_status.ts +112 -0
  402. package/src/scripts/injection_scan_hook.ts +7 -0
  403. package/src/scripts/install.ts +75 -189
  404. package/src/scripts/installed_layer_report.ts +103 -0
  405. package/src/scripts/language_mirror_hook.ts +15 -6
  406. package/src/scripts/learning_sidecar.ts +119 -8
  407. package/src/scripts/lint_deny_text.ts +419 -0
  408. package/src/scripts/lint_exit_codes.ts +262 -0
  409. package/src/scripts/lint_hook_manifest.ts +108 -0
  410. package/src/scripts/lint_major_migration_sections.ts +156 -26
  411. package/src/scripts/lint_pack_boundaries.ts +63 -5
  412. package/src/scripts/lint_roadmap_blockers.ts +78 -12
  413. package/src/scripts/lint_rule_law_section.ts +517 -0
  414. package/src/scripts/lint_skill_descriptions.ts +1 -1
  415. package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
  416. package/src/scripts/lint_workflow_security.ts +161 -5
  417. package/src/scripts/mcp_server/consumer_tool_catalog.json +53 -0
  418. package/src/scripts/mcp_server/graph_tools.ts +72 -3
  419. package/src/scripts/mcp_server/prompts.ts +142 -1
  420. package/src/scripts/measure_routing_signal.ts +54 -16
  421. package/src/scripts/measure_skill_ranker_baseline.ts +515 -53
  422. package/src/scripts/measure_turn_end_gate.ts +193 -0
  423. package/src/scripts/memory_learn_hook.ts +91 -6
  424. package/src/scripts/memory_lookup.ts +1 -1
  425. package/src/scripts/model_rule_injection.ts +52 -5
  426. package/src/scripts/prepack-check.mjs +53 -0
  427. package/src/scripts/project_thin_rules.ts +34 -327
  428. package/src/scripts/release_publication.ts +13 -1
  429. package/src/scripts/report_evidence_temperature.ts +657 -0
  430. package/src/scripts/report_installed_rule_links.ts +157 -0
  431. package/src/scripts/report_invocation_surface.ts +383 -0
  432. package/src/scripts/report_label_agreement.ts +233 -0
  433. package/src/scripts/report_obligation_carriers.ts +699 -0
  434. package/src/scripts/report_obligation_mechanism.ts +230 -0
  435. package/src/scripts/report_obligation_writer_split.ts +236 -0
  436. package/src/scripts/report_single_token_triggers.ts +277 -0
  437. package/src/scripts/report_skill_menu_census.ts +208 -0
  438. package/src/scripts/report_skill_ranker_confusion.ts +442 -0
  439. package/src/scripts/report_standing_payload_by_host.ts +188 -15
  440. package/src/scripts/report_verification_record_coverage.ts +306 -0
  441. package/src/scripts/roadmap_verify_share.ts +213 -0
  442. package/src/scripts/rule_activation_census.ts +5 -2
  443. package/src/scripts/rule_trigger_eval.ts +9 -9
  444. package/src/scripts/schemas/command.schema.json +44 -0
  445. package/src/scripts/schemas/review-findings.schema.json +7 -2
  446. package/src/scripts/schemas/rule.schema.json +5 -0
  447. package/src/scripts/schemas/skill.schema.json +44 -0
  448. package/src/scripts/self_review_gate.ts +421 -17
  449. package/src/scripts/semantic_parity_census.ts +314 -0
  450. package/src/scripts/session_eol_report.ts +54 -1
  451. package/src/scripts/skill_tools/score_skill_relevance.ts +290 -25
  452. package/src/scripts/sweep_dead_scan_roots.ts +1 -1
  453. package/src/scripts/sweep_skill_ranker_signals.ts +129 -0
  454. package/src/scripts/sync_gitignore.ts +29 -235
  455. package/src/scripts/trigger_eval_grandfather.json +0 -7
  456. package/src/scripts/trigger_eval_rotation.ts +122 -27
  457. package/src/scripts/ui_conformance_probe.ts +156 -4
  458. package/src/scripts/update_prices.ts +85 -3
  459. package/src/scripts/write_bundle_digest.mjs +74 -0
  460. package/src/server/schemas/settings.ts +41 -0
  461. package/src/shared/skillRanking.ts +117 -11
  462. package/src/templates/consumer-settings/ONBOARDING.md +1 -0
  463. package/dist/cli-delegate/chunk-FXYUYU4N.js +0 -4416
  464. package/dist/cli-delegate/chunk-QJWAUMJW.js +0 -1072
  465. package/dist/cli-delegate/chunk-YDPILCOL.js +0 -152
  466. package/dist/ui/assets/index-DH1xc62v.js +0 -29
  467. package/dist/ui/assets/index-jSfMW8_t.css +0 -1
@@ -0,0 +1,647 @@
1
+ /**
2
+ * Verification evidence — what a run RECORD says, never what a command SAYS.
3
+ *
4
+ * The defect this module exists to close: the turn-end gate's detector C asks
5
+ * whether a verification command appeared in the transcript after the last
6
+ * edit, and answers that by matching the command TEXT against a regex. `echo
7
+ * test` matches it — the word `test` is in the pattern — so a turn can change
8
+ * production code, echo a word, and end. Nothing in the estate read whether the
9
+ * command ran, what it exited with, or whether it discovered a single test.
10
+ *
11
+ * So the unit here is a RECORD, not a string: a command, an exit code, and the
12
+ * tails of what it printed. The classifier is pure — no fs, no spawn, no clock,
13
+ * no process state — because its verdict is quoted into a refusal and a refusal
14
+ * that depends on the machine it ran on cannot be reproduced by the person it
15
+ * refused.
16
+ *
17
+ * WHY `INVALID_RUN` CARRIES A REASON AND NOT A BOOLEAN.
18
+ * Two of the reasons say the INSTRUMENT failed (`exit_code_unavailable`,
19
+ * `unreadable_record`) and five say the RUN failed. Only the second kind may
20
+ * ever refuse a turn: a host that omits an exit code has told us nothing about
21
+ * the operator's work, and refusing on it would punish the turn for the
22
+ * recorder's gap. `INSTRUMENT_GAP_REASONS` below is that split, exported so the
23
+ * consumer reads the distinction rather than re-deriving it from a comment.
24
+ *
25
+ * WHY EXIT 0 WITH NO PARSABLE SUMMARY IS A PASS.
26
+ * `tsc --noEmit`, `eslint` and `phpstan` print nothing on success — silence is
27
+ * the Unix convention for it. A classifier demanding a test summary would
28
+ * reclassify the most common green signal in this repository as invalid, and
29
+ * would refuse every consumer whose runner this module has never heard of.
30
+ * Exit 0 from a command the verification set admits IS the machine-readable
31
+ * signal; the parsers below only ever make the verdict SHARPER (a summary
32
+ * reporting failures overrides a zero exit, and a summary reporting zero tests
33
+ * overrides it too).
34
+ */
35
+
36
+ import { isVerificationCommand } from './verification_command.js';
37
+
38
+
39
+ export type InvalidRunReason =
40
+ | 'zero_tests_discovered'
41
+ | 'nonzero_exit_without_test_failure'
42
+ | 'fixture_or_load_failure'
43
+ | 'unreadable_record'
44
+ | 'not_a_verification_command'
45
+ | 'exit_code_unavailable'
46
+ | 'timeout_or_killed';
47
+
48
+ export type VerificationVerdict =
49
+ | { readonly kind: 'PASS_EVIDENCE_OK' }
50
+ | { readonly kind: 'FAIL_EVIDENCE' }
51
+ | { readonly kind: 'INVALID_RUN'; readonly reason: InvalidRunReason };
52
+
53
+ /**
54
+ * One observed run of one command.
55
+ *
56
+ * `exit_code: null` is a real and distinct value from `0`: it means the host
57
+ * did not surface one, which is a fact about the host. Conflating it with
58
+ * success is the single most dangerous shortcut available here, so the type
59
+ * makes it impossible to write by accident.
60
+ */
61
+ export interface RunRecord {
62
+ readonly command: string;
63
+ readonly exit_code: number | null;
64
+ readonly stdout_tail?: string;
65
+ readonly stderr_tail?: string;
66
+ readonly runner?: string;
67
+ /** The host reported the call interrupted — a kill, never a verdict on the work. */
68
+ readonly interrupted?: boolean;
69
+ /** How the recorder obtained `exit_code`. Advisory; nothing branches on it. */
70
+ readonly exit_source?: 'field' | 'error_prefix' | 'response_shape' | null;
71
+ }
72
+
73
+ /**
74
+ * Reasons that describe a gap in the INSTRUMENT rather than a failure of the run.
75
+ *
76
+ * A consumer deciding whether to refuse reads this set; it never decides from
77
+ * the reason string directly. Risk 1 of the parent roadmap is exactly this:
78
+ * "a passing run classifies INVALID_RUN" when the parser has not met the
79
+ * consumer's runner, and the mitigation is that an instrument gap is logged as
80
+ * a finding and never refused on.
81
+ */
82
+ export const INSTRUMENT_GAP_REASONS: ReadonlySet<InvalidRunReason> = new Set<InvalidRunReason>([
83
+ 'exit_code_unavailable',
84
+ 'unreadable_record',
85
+ ]);
86
+
87
+ /** Whether this verdict leaves the turn unrefusable — a pass, or a gap in the instrument. */
88
+ export function isInstrumentGap(verdict: VerificationVerdict): boolean {
89
+ return verdict.kind === 'INVALID_RUN' && INSTRUMENT_GAP_REASONS.has(verdict.reason);
90
+ }
91
+
92
+ /**
93
+ * Executables that cannot verify anything, whatever words follow them.
94
+ *
95
+ * A BLOCK list and not an allow list, and the direction is the whole argument.
96
+ * `not_a_verification_command` is a REFUSING reason: a command wrongly landing
97
+ * in it refuses honest work, which is the failure that teaches an operator to
98
+ * switch the gate off. An allow list would put every runner this module has
99
+ * never met — `bash scripts/test.sh`, `./run-tests.sh`, a consumer's in-house
100
+ * wrapper — into that refusing bucket on day one. So the rejection is narrow:
101
+ * only executables that demonstrably assert nothing are named, and everything
102
+ * unknown is given the benefit of its exit code.
103
+ *
104
+ * `git` is here because `git log`, `git show` and `git diff` read history; the
105
+ * one git subcommand anybody could call verification is `git bisect run`, which
106
+ * verifies by invoking a different command that gets its own record.
107
+ */
108
+ const NON_VERIFYING_EXECUTABLES: ReadonlySet<string> = new Set([
109
+ ':', 'awk', 'basename', 'cat', 'cd', 'chmod', 'cp', 'curl', 'cut', 'date', 'dirname',
110
+ 'echo', 'exit', 'export', 'false', 'find', 'git', 'grep', 'head', 'less', 'ln', 'ls',
111
+ 'mkdir', 'more', 'mv', 'open', 'printf', 'pwd', 'realpath', 'rg', 'rm', 'sed', 'sleep',
112
+ 'sort', 'source', 'tail', 'tee', 'touch', 'tr', 'true', 'type', 'uniq', 'wc', 'wget',
113
+ 'which', 'yes',
114
+ ]);
115
+
116
+ /** Wrappers that prefix a real command without being one. */
117
+ const COMMAND_PREFIXES: ReadonlySet<string> = new Set([
118
+ 'bunx', 'command', 'env', 'exec', 'nice', 'nohup', 'npx', 'sudo', 'time',
119
+ ]);
120
+
121
+ /** The executable a single command segment actually runs, lower-cased and unpathed. */
122
+ function leadingExecutable(segment: string): string | null {
123
+ for (const raw of segment.trim().split(/\s+/)) {
124
+ if (raw === '') continue;
125
+ if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(raw)) continue; // FOO=bar prefix
126
+ const base = (raw.split('/').pop() ?? raw).replace(/\.exe$/i, '').toLowerCase();
127
+ if (base === '') continue;
128
+ if (COMMAND_PREFIXES.has(base)) continue;
129
+ return base;
130
+ }
131
+ return null;
132
+ }
133
+
134
+ /**
135
+ * Whether any segment of this command line could have verified something.
136
+ *
137
+ * The selector in `verification_command.ts` answers "is this worth recording",
138
+ * and it matches `echo test` because the word `test` is in its pattern. That is
139
+ * the defect the record path exists to close, so this cannot delegate to it
140
+ * alone: a segment whose executable cannot assert anything is skipped BEFORE
141
+ * the selector is consulted, and only the surviving segments may qualify.
142
+ */
143
+ function canVerify(command: string): boolean {
144
+ for (const segment of command.split(/&&|\|\||[;|\n]/)) {
145
+ if (segment.trim() === '') continue;
146
+ const exe = leadingExecutable(segment);
147
+ if (exe !== null && NON_VERIFYING_EXECUTABLES.has(exe)) continue;
148
+ if (isVerificationCommand(segment)) return true;
149
+ }
150
+ return false;
151
+ }
152
+
153
+ /**
154
+ * Exit codes a shell reports for a process the kernel killed.
155
+ *
156
+ * 128 + SIGKILL(9) and 128 + SIGTERM(15). A test runner the CI timeout reaped
157
+ * printed whatever it had got through; its partial output can easily contain a
158
+ * passing summary for the files it reached, which is why a kill is checked
159
+ * BEFORE the summary is trusted and never after.
160
+ */
161
+ const KILLED_EXIT_CODES: ReadonlySet<number> = new Set([137, 143]);
162
+
163
+ /**
164
+ * Output shapes that mean the suite never ran, as distinct from running clean.
165
+ *
166
+ * Deliberately separate from `isVacuousOutput` in the recorder: that one asks
167
+ * whether a whole output blob proves nothing, line by line, and answers `false`
168
+ * for empty output. This one asks whether a specific "the loader died" marker
169
+ * is present anywhere, which is a different question with a different default.
170
+ */
171
+ const LOAD_FAILURE_PATTERNS: readonly RegExp[] = [
172
+ /\bfailed to load\b/i,
173
+ /\berror(?:s)? collecting\b/i,
174
+ /\bcollection error\b/i,
175
+ /\bcannot find module\b/i,
176
+ /\bmodule not found\b/i,
177
+ /\bunable to load\b/i,
178
+ /\btransform failed\b/i,
179
+ /\bconftest\.py\b.*\berror\b/i,
180
+ /\bcould not open input file\b/i,
181
+ /\bfatal error\b/i,
182
+ ];
183
+
184
+ /** A parsed test summary. `total === null` means the runner did not state one. */
185
+ interface Summary {
186
+ readonly passed: number;
187
+ readonly failed: number;
188
+ readonly total: number | null;
189
+ }
190
+
191
+ function _num(m: RegExpMatchArray | null): number {
192
+ return m?.[1] === undefined ? 0 : Number(m[1]);
193
+ }
194
+
195
+ /**
196
+ * vitest: `Tests 3 failed | 812 passed (815)`; jest: `Tests: 1 failed, 2 passed, 3 total`.
197
+ *
198
+ * One parser for both because both put the counts on a line whose label is
199
+ * `Tests`, and splitting them would mean two places to keep a count regex
200
+ * correct. The separators differ (`|` vs `,`) and neither parser cares.
201
+ */
202
+ function parseVitestOrJest(text: string): Summary | null {
203
+ const line = /^[ \t]*Tests:?[ \t]+(.+)$/m.exec(text);
204
+ if (line?.[1] === undefined) return null;
205
+ const body = line[1];
206
+ const failed = _num(/(\d+)\s+failed/i.exec(body));
207
+ const passed = _num(/(\d+)\s+passed/i.exec(body));
208
+ const totalMatch = /\((\d+)\)\s*$/.exec(body.trim()) ?? /(\d+)\s+total/i.exec(body);
209
+ const total = totalMatch?.[1] === undefined ? null : Number(totalMatch[1]);
210
+ return { passed, failed, total };
211
+ }
212
+
213
+ /**
214
+ * pytest's `-q` tail: `3 passed, 1 failed in 0.12s`, or `no tests ran in 0.01s`.
215
+ *
216
+ * `error` is counted as a failure rather than as its own axis: a collection
217
+ * error and an assertion failure are the same answer to "did this run prove the
218
+ * change works", and the separate `fixture_or_load_failure` reason already
219
+ * carries the case where the distinction matters.
220
+ */
221
+ function parsePytest(text: string): Summary | null {
222
+ if (/\bno tests ran\b/i.test(text)) return { passed: 0, failed: 0, total: 0 };
223
+ const line = /^[= ]*((?:\d+\s+(?:passed|failed|error|errors|skipped|xfailed|xpassed|deselected)[,\s]*)+)(?:in\s+[\d.]+s)?[= ]*$/im.exec(
224
+ text,
225
+ );
226
+ if (line?.[1] === undefined) return null;
227
+ const body = line[1];
228
+ const passed = _num(/(\d+)\s+passed/i.exec(body));
229
+ const failed =
230
+ _num(/(\d+)\s+failed/i.exec(body)) + _num(/(\d+)\s+errors?\b/i.exec(body));
231
+ const skipped = _num(/(\d+)\s+skipped/i.exec(body));
232
+ return { passed, failed, total: passed + failed + skipped };
233
+ }
234
+
235
+ /**
236
+ * phpunit (`OK (15 tests, 30 assertions)`, `Tests: 15, Assertions: 30, Failures: 1.`)
237
+ * and pest (`Tests: 2 failed, 10 passed (24 assertions)`).
238
+ *
239
+ * Pest's line is handled by `parseVitestOrJest` above — it wears the same
240
+ * `Tests:` label with `failed`/`passed` words — so only the two phpunit shapes
241
+ * need their own reading here.
242
+ */
243
+ function parsePhpunit(text: string): Summary | null {
244
+ if (/\bno tests executed\b/i.test(text)) return { passed: 0, failed: 0, total: 0 };
245
+ const ok = /\bOK\s*\((\d+)\s+tests?/i.exec(text);
246
+ if (ok?.[1] !== undefined) {
247
+ const n = Number(ok[1]);
248
+ return { passed: n, failed: 0, total: n };
249
+ }
250
+ const counted = /^[ \t]*Tests:\s*(\d+),\s*Assertions:\s*\d+(.*)$/m.exec(text);
251
+ if (counted?.[1] === undefined) return null;
252
+ const total = Number(counted[1]);
253
+ const rest = counted[2] ?? '';
254
+ const failed =
255
+ _num(/Failures:\s*(\d+)/i.exec(rest)) +
256
+ _num(/Errors:\s*(\d+)/i.exec(rest)) +
257
+ _num(/Risky:\s*(\d+)/i.exec(rest));
258
+ return { passed: Math.max(total - failed, 0), failed, total };
259
+ }
260
+
261
+ /**
262
+ * TAP: a `1..N` plan with `ok` / `not ok` lines, or a `# fail N` trailer.
263
+ *
264
+ * The plan is authoritative for the total because a truncated stream can carry
265
+ * fewer result lines than it promised — and a stream that promised five results
266
+ * and delivered two is not a clean run, which the total/seen mismatch below
267
+ * lets the caller see as a non-pass.
268
+ */
269
+ function parseTap(text: string): Summary | null {
270
+ const plan = /^1\.\.(\d+)\s*$/m.exec(text);
271
+ const okLines = text.match(/^ok\b/gim)?.length ?? 0;
272
+ const notOkLines = text.match(/^not ok\b/gim)?.length ?? 0;
273
+ const trailerFail = /^#\s*fail\s+(\d+)\s*$/im.exec(text);
274
+ if (plan?.[1] === undefined && okLines === 0 && notOkLines === 0) return null;
275
+ const total = plan?.[1] === undefined ? okLines + notOkLines : Number(plan[1]);
276
+ const failed = trailerFail?.[1] === undefined ? notOkLines : Number(trailerFail[1]);
277
+ return { passed: okLines, failed, total };
278
+ }
279
+
280
+ /**
281
+ * Every parser, tried in turn; the first that recognises the text wins.
282
+ *
283
+ * Order matters only between phpunit and vitest/jest, and only because pest
284
+ * writes a `Tests:` line that the vitest parser reads correctly — so phpunit's
285
+ * stricter `Tests: <n>, Assertions:` shape is tried first and declines when the
286
+ * line is pest's.
287
+ */
288
+ export function parseSummary(text: string): Summary | null {
289
+ return parsePhpunit(text) ?? parseVitestOrJest(text) ?? parsePytest(text) ?? parseTap(text);
290
+ }
291
+
292
+ function output(record: RunRecord): string {
293
+ return `${record.stdout_tail ?? ''}\n${record.stderr_tail ?? ''}`;
294
+ }
295
+
296
+ /**
297
+ * Classify one run record.
298
+ *
299
+ * The order of the checks IS the policy and is not interchangeable:
300
+ *
301
+ * 1. an unreadable record says nothing about the run;
302
+ * 2. a command that cannot verify anything is never evidence, whatever it
303
+ * exited with — this is the `echo test` case the regex path admits;
304
+ * 3. a missing exit code is the host's gap, reported as one;
305
+ * 4. a killed process is checked BEFORE its output, because partial output
306
+ * from a reaped runner can carry a passing summary for the files it
307
+ * reached;
308
+ * 5. a load failure is checked before the summary, because a suite that never
309
+ * loaded can still print a summary of the zero tests it ran;
310
+ * 6. the summary then sharpens or overrides the exit code;
311
+ * 7. and only then does a bare exit code decide.
312
+ */
313
+ export function classifyRun(record: unknown): VerificationVerdict {
314
+ if (typeof record !== 'object' || record === null || Array.isArray(record)) {
315
+ return { kind: 'INVALID_RUN', reason: 'unreadable_record' };
316
+ }
317
+ const r = record as Partial<RunRecord>;
318
+ if (typeof r.command !== 'string' || r.command.trim() === '') {
319
+ return { kind: 'INVALID_RUN', reason: 'unreadable_record' };
320
+ }
321
+ if (!canVerify(r.command)) {
322
+ return { kind: 'INVALID_RUN', reason: 'not_a_verification_command' };
323
+ }
324
+ if (
325
+ r.exit_code !== null
326
+ && r.exit_code !== undefined
327
+ && (typeof r.exit_code !== 'number' || !Number.isFinite(r.exit_code))
328
+ ) {
329
+ return { kind: 'INVALID_RUN', reason: 'unreadable_record' };
330
+ }
331
+
332
+ const text = output(r as RunRecord);
333
+ const summary = parseSummary(text);
334
+
335
+ // A KILL is read before anything the process printed: a reaped runner's
336
+ // partial output can carry a passing summary for the files it reached. A
337
+ // summary that reports FAILURES still wins, because those failures happened.
338
+ if (r.interrupted === true || (typeof r.exit_code === 'number' && KILLED_EXIT_CODES.has(r.exit_code))) {
339
+ if (summary !== null && summary.failed > 0) return { kind: 'FAIL_EVIDENCE' };
340
+ return { kind: 'INVALID_RUN', reason: 'timeout_or_killed' };
341
+ }
342
+
343
+ // A RUN THAT PRINTED ITS OWN FAILURES IS A FAILURE, exit code or not. This
344
+ // sits above the missing-exit-code gap deliberately, and the order was the
345
+ // other way round until an independent review measured what it cost: on
346
+ // Claude Code the success shape carries no exit field at all, so a failing
347
+ // run whose summary said `2 failed` classified `exit_code_unavailable`,
348
+ // became an instrument gap, and ended the turn normally. The evidence of
349
+ // failure was in the record, was parseable, and was thrown away. A host's
350
+ // silence about an exit status tells us nothing about the operator's work;
351
+ // a runner's own failure count tells us plenty.
352
+ if (summary !== null && summary.failed > 0) return { kind: 'FAIL_EVIDENCE' };
353
+
354
+ if (r.exit_code === null || r.exit_code === undefined) {
355
+ return { kind: 'INVALID_RUN', reason: 'exit_code_unavailable' };
356
+ }
357
+
358
+ // A LOAD FAILURE, and only where it is not contradicted by a clean summary.
359
+ // The patterns are broad on purpose — `fatal error`, `cannot find module` —
360
+ // and a passing run that merely PRINTS one is common: vitest echoes a
361
+ // `stderr |` block verbatim, so any test exercising an error path can carry
362
+ // the words. Checking this above a summary reporting passes refused honest
363
+ // work, which is the expensive direction for a gate that can block a turn.
364
+ // The case the original ordering was defending — "a suite that never loaded
365
+ // can still print a summary of the zero tests it ran" — is already covered
366
+ // by `zero_tests_discovered` below, and by the `passed === 0` clause here.
367
+ if (
368
+ (summary === null || summary.passed === 0)
369
+ && LOAD_FAILURE_PATTERNS.some((re) => re.test(text))
370
+ ) {
371
+ return { kind: 'INVALID_RUN', reason: 'fixture_or_load_failure' };
372
+ }
373
+
374
+ if (summary !== null) {
375
+ if (summary.failed > 0) return { kind: 'FAIL_EVIDENCE' };
376
+ if (summary.total === 0 || (summary.total === null && summary.passed === 0)) {
377
+ return { kind: 'INVALID_RUN', reason: 'zero_tests_discovered' };
378
+ }
379
+ if (summary.total !== null && summary.total > 0 && summary.passed === 0) {
380
+ return { kind: 'INVALID_RUN', reason: 'zero_tests_discovered' };
381
+ }
382
+ if (r.exit_code !== 0) {
383
+ return { kind: 'INVALID_RUN', reason: 'nonzero_exit_without_test_failure' };
384
+ }
385
+ return { kind: 'PASS_EVIDENCE_OK' };
386
+ }
387
+
388
+ if (r.exit_code !== 0) {
389
+ return { kind: 'INVALID_RUN', reason: 'nonzero_exit_without_test_failure' };
390
+ }
391
+ return { kind: 'PASS_EVIDENCE_OK' };
392
+ }
393
+
394
+ /**
395
+ * The runner a command names, or `other`.
396
+ *
397
+ * Advisory only — nothing in `classifyRun` branches on it. It exists so a
398
+ * finding can say which parser was in play when a verdict is disputed, which is
399
+ * the difference between "the classifier is wrong" and "the classifier has
400
+ * never met this runner".
401
+ */
402
+ export function runnerOf(command: string): string {
403
+ for (const [name, re] of [
404
+ ['vitest', /\bvitest\b/i],
405
+ ['jest', /\bjest\b/i],
406
+ ['pytest', /\bpytest\b/i],
407
+ ['phpunit', /\bphpunit\b/i],
408
+ ['pest', /\bpest\b/i],
409
+ ['go', /\bgo\s+test\b/i],
410
+ ['cargo', /\bcargo\s+test\b/i],
411
+ ] as const) {
412
+ if (re.test(command)) return name;
413
+ }
414
+ return 'other';
415
+ }
416
+
417
+ /**
418
+ * One turn's recorded runs, as the recorder left them.
419
+ *
420
+ * `edits_this_turn` is the recorder's OWN total, not a count taken from a
421
+ * transcript. Both numbers in the placement comparison below therefore come
422
+ * from the same counter in the same file, so the comparison cannot be thrown
423
+ * off by a transcript reader and the recorder disagreeing about what an edit is.
424
+ */
425
+ export interface TurnRunState {
426
+ readonly runs: readonly unknown[];
427
+ readonly edits_this_turn: number;
428
+ }
429
+
430
+ /**
431
+ * What the records say about the turn, and whether they may be refused on.
432
+ *
433
+ * Three fields rather than a boolean because the three answers are genuinely
434
+ * different: a pass allows, a failure refuses, and a gap in the INSTRUMENT
435
+ * allows while saying so. Collapsing the third into either of the others is
436
+ * Risk 1 of the plan realised — "a passing run classifies INVALID_RUN" and the
437
+ * turn is refused for the recorder's shortcoming.
438
+ */
439
+ export interface RecordReading {
440
+ /** A `PASS_EVIDENCE_OK` record placed after the turn's last edit. */
441
+ readonly passed: boolean;
442
+ /** A `FAIL_EVIDENCE` record placed after the turn's last edit. */
443
+ readonly failed: boolean;
444
+ /** Nothing judgeable, and the reason is the recorder's — never refuse on this. */
445
+ readonly instrumentGap: boolean;
446
+ /** Every non-passing verdict in the window, for the finding text. Advisory. */
447
+ readonly reasons: readonly InvalidRunReason[];
448
+ }
449
+
450
+ /**
451
+ * Where in the turn's edit sequence a record sits, or `null` when unplaceable.
452
+ *
453
+ * `after_edits` is the recorder's edit counter as it stood when the command ran.
454
+ * A record carrying no number cannot be placed at all, and an unplaceable record
455
+ * is treated below as an instrument gap rather than as either evidence — which
456
+ * is the direction that under-refuses.
457
+ */
458
+ function placeRecord(raw: unknown): number | null {
459
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return null;
460
+ const v = (raw as Record<string, unknown>)['after_edits'];
461
+ if (typeof v !== 'number' || !Number.isFinite(v)) return null;
462
+ return v;
463
+ }
464
+
465
+ /**
466
+ * Read the turn's records for evidence that its LAST edit was verified.
467
+ *
468
+ * A record qualifies only when `after_edits >= edits_this_turn` — nothing was
469
+ * edited after the command ran. That is the same freshness clause the transcript
470
+ * path applies by scanning only after the last edit, and keeping it is the
471
+ * difference between this being a stricter reading of the same rule and being a
472
+ * different, weaker rule wearing the same name.
473
+ */
474
+ export function readRunEvidence(state: TurnRunState): RecordReading {
475
+ const reasons: InvalidRunReason[] = [];
476
+ let passed = false;
477
+ let failed = false;
478
+ let gap = false;
479
+
480
+ for (const raw of state.runs) {
481
+ const placed = placeRecord(raw);
482
+ if (placed === null) {
483
+ gap = true;
484
+ reasons.push('unreadable_record');
485
+ continue;
486
+ }
487
+ if (placed < state.edits_this_turn) continue; // an edit followed this run
488
+ const verdict = classifyRun(raw);
489
+ if (verdict.kind === 'PASS_EVIDENCE_OK') {
490
+ passed = true;
491
+ continue;
492
+ }
493
+ if (verdict.kind === 'FAIL_EVIDENCE') {
494
+ failed = true;
495
+ continue;
496
+ }
497
+ reasons.push(verdict.reason);
498
+ if (isInstrumentGap(verdict)) gap = true;
499
+ }
500
+
501
+ // A pass or a failure is a reading of the RUN, and either one settles the
502
+ // question; the gap only matters when nothing judgeable was found at all.
503
+ return { passed, failed, instrumentGap: gap && !passed && !failed, reasons };
504
+ }
505
+
506
+ /**
507
+ * The test files a command names, or the empty string when it names none.
508
+ *
509
+ * The key groups records so that a red and a green about the SAME target can be
510
+ * paired. A command with no explicit target — `npx vitest run`, `task test` —
511
+ * gets the empty key, which is its own group: a whole-suite red followed by a
512
+ * whole-suite green is a legitimate red-then-green, and pairing it with a
513
+ * single-file run would claim a sequence nobody observed.
514
+ *
515
+ * Sorted and joined so argument ORDER cannot split one target set into two
516
+ * groups.
517
+ */
518
+ export function testTargetKey(command: string): string {
519
+ const targets = new Set<string>();
520
+ for (const token of command.split(/\s+/)) {
521
+ const bare = token.replace(/^['"]|['"]$/g, '');
522
+ if (/[./]\w*(?:test|spec)\w*\.[a-z]+$/i.test(bare) || /(?:Test|Spec)\.php$/.test(bare)) {
523
+ targets.add(bare);
524
+ }
525
+ }
526
+ return [...targets].sort().join('|');
527
+ }
528
+
529
+ /**
530
+ * Whether the turn's records show a red run turning green over the same target.
531
+ *
532
+ * WHY THIS IS EVIDENCE AND A NEW TEST FILE ALONE IS NOT. Detector F's escape
533
+ * hatch is "a test file was touched somewhere in the turn", which a file
534
+ * containing `it('works', () => expect(true).toBe(true))` satisfies. A test
535
+ * never seen red has unknown sensitivity: it may assert nothing the change
536
+ * could break. A red-then-green pair over one target is the cheapest observable
537
+ * proof that the assertion discriminates — it failed, the code changed, it
538
+ * passed.
539
+ *
540
+ * ORDER COMES FROM THE ARRAY, not from a timestamp. The recorder appends, so
541
+ * index order IS run order, and it survives a host whose clock is coarse or
542
+ * whose records share a second. Only records at or after the turn's edit total
543
+ * count for the GREEN half, for the same freshness reason `readRunEvidence`
544
+ * applies; the RED half is deliberately unbounded, because a red necessarily
545
+ * predates the edit that fixed it.
546
+ */
547
+ export function hasRedThenGreen(state: TurnRunState): boolean {
548
+ let reddenedAnywhere = false;
549
+ const reddened = new Set<string>();
550
+ for (let i = 0; i < state.runs.length; i += 1) {
551
+ const raw = state.runs[i];
552
+ const verdict = classifyRun(raw);
553
+ const key = testTargetKey(
554
+ typeof raw === 'object' && raw !== null && !Array.isArray(raw)
555
+ ? String((raw as Record<string, unknown>)['command'] ?? '')
556
+ : '',
557
+ );
558
+ if (RED_VERDICTS(verdict)) {
559
+ reddened.add(key);
560
+ reddenedAnywhere = true;
561
+ continue;
562
+ }
563
+ if (verdict.kind !== 'PASS_EVIDENCE_OK') continue;
564
+ const placed = placeRecord(raw);
565
+ if (placed === null || placed < state.edits_this_turn) continue;
566
+ // A WHOLE-SUITE green (the empty key) settles any reddened target: it is
567
+ // strictly stronger evidence than a single-file green, and "red on the
568
+ // file, then the whole suite" is the common honest sequence. A targeted
569
+ // green still only settles its own target.
570
+ if (key === '' ? reddenedAnywhere : reddened.has(key)) return true;
571
+ }
572
+ return false;
573
+ }
574
+
575
+ /**
576
+ * Which verdicts count as the RED half of a red-then-green pair.
577
+ *
578
+ * Deliberately wider than `FAIL_EVIDENCE`, and the canonical TDD first red is
579
+ * why: a new test importing a module that does not exist yet prints `Cannot
580
+ * find module`, which classifies `fixture_or_load_failure`. Requiring a parsed
581
+ * failure COUNT for the red half would refuse exactly the discipline this
582
+ * detector exists to encourage — including the shape of work that introduced
583
+ * this module.
584
+ *
585
+ * The red half needs to show the target NOT PASSING, which is a weaker bar than
586
+ * showing how many assertions failed. The green half carries the placement and
587
+ * freshness checks, so the pair is still anchored to the turn's last edit.
588
+ * `exit_code_unavailable` and `unreadable_record` are excluded: those say the
589
+ * instrument failed, not that the target did.
590
+ */
591
+ function RED_VERDICTS(verdict: VerificationVerdict): boolean {
592
+ if (verdict.kind === 'FAIL_EVIDENCE') return true;
593
+ if (verdict.kind !== 'INVALID_RUN') return false;
594
+ return (
595
+ verdict.reason === 'fixture_or_load_failure'
596
+ || verdict.reason === 'nonzero_exit_without_test_failure'
597
+ || verdict.reason === 'zero_tests_discovered'
598
+ );
599
+ }
600
+
601
+ /**
602
+ * The refusal text for a record-path finding.
603
+ *
604
+ * It lives HERE, beside the verdicts it names, rather than in the detector that
605
+ * emits it: this module owns the vocabulary (`not_a_verification_command`,
606
+ * `zero_tests_discovered`, …), so a sentence quoting one of those words has to
607
+ * change whenever the vocabulary does. Splitting the two is how a refusal comes
608
+ * to name a reason the classifier stopped producing.
609
+ *
610
+ * It names the VERDICT rather than restating the rule, because the operator's
611
+ * next action differs per reason: a failed run means fix the code, a zero-test
612
+ * run means the filter matched nothing, and `not_a_verification_command` means
613
+ * the thing that ran could not have checked anything.
614
+ */
615
+ export function describeRecordFinding(
616
+ failed: boolean,
617
+ reasons: readonly InvalidRunReason[],
618
+ ): string {
619
+ const tail =
620
+ ' (verify-before-complete: no verification command run in this message → '
621
+ + 'you cannot claim it passes)';
622
+ if (failed) {
623
+ return (
624
+ 'this turn changed a file and its verification run FAILED — a recorded '
625
+ + 'run after the last edit reports failing tests' + tail
626
+ );
627
+ }
628
+ if (reasons.length === 0) {
629
+ return (
630
+ 'this turn changed a file and no verification run was recorded after '
631
+ + 'the last edit' + tail
632
+ );
633
+ }
634
+ return (
635
+ 'this turn changed a file and no recorded run after the last edit proves '
636
+ + `anything — the runs seen classify as ${[...new Set(reasons)].join(', ')}`
637
+ + tail
638
+ );
639
+ }
640
+
641
+ /** The refusal text when a new test file arrived with no red it passed. */
642
+ export const NO_RED_EVIDENCE_REASON =
643
+ 'a completion claim over production code this turn changed, with a test '
644
+ + 'file touched but `no_red_evidence` — no recorded run shows that test '
645
+ + 'target failing and then passing after the last edit. A test never seen '
646
+ + 'red has unknown sensitivity: run it against the unfixed code, watch it '
647
+ + 'fail for the intended reason, then fix and re-run';