@event4u/agent-config 16.0.0 → 16.2.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 (382) 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 +289 -140
  5. package/CONTRIBUTING.md +9 -0
  6. package/README.md +2 -2
  7. package/dist/agent-src/commands/analyze/inbox.md +26 -0
  8. package/dist/agent-src/commands/analyze/roadmap-repos.md +27 -0
  9. package/dist/agent-src/commands/challenge-me/closure.md +178 -0
  10. package/dist/agent-src/commands/challenge-me.md +9 -2
  11. package/dist/agent-src/commands/council/default.md +25 -5
  12. package/dist/agent-src/commands/feature/plan.md +28 -1
  13. package/dist/agent-src/commands/feature/roadmap.md +28 -1
  14. package/dist/agent-src/commands/implement-ticket.md +27 -0
  15. package/dist/agent-src/commands/jira-ticket.md +27 -0
  16. package/dist/agent-src/commands/pr/create.md +29 -0
  17. package/dist/agent-src/commands/pr/merge.md +60 -9
  18. package/dist/agent-src/commands/roadmap/ai-council.md +7 -1
  19. package/dist/agent-src/commands/roadmap/create.md +74 -27
  20. package/dist/agent-src/commands/roadmap/materialize.md +27 -0
  21. package/dist/agent-src/commands/roadmap/process-full.md +123 -18
  22. package/dist/agent-src/commands/team.md +22 -0
  23. package/dist/agent-src/commands/work.md +4 -0
  24. package/dist/agent-src/contexts/authority/scope-growth-ownership.md +80 -0
  25. package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +47 -0
  26. package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +44 -3
  27. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +19 -23
  28. package/dist/agent-src/contexts/execution/decision-closure-execution.md +93 -0
  29. package/dist/agent-src/contexts/execution/fix-loop-ladder.md +108 -0
  30. package/dist/agent-src/contexts/execution/plan-confidence-gate.md +4 -2
  31. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +75 -6
  32. package/dist/agent-src/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
  33. package/dist/agent-src/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
  34. package/dist/agent-src/guidelines/design-fidelity-routing.md +29 -0
  35. package/dist/agent-src/rules/autonomous-execution.md +15 -8
  36. package/dist/agent-src/rules/delegation-policy.md +23 -45
  37. package/dist/agent-src/rules/design-fidelity.md +32 -37
  38. package/dist/agent-src/rules/evaluator-independence.md +43 -49
  39. package/dist/agent-src/rules/test-first.md +41 -0
  40. package/dist/agent-src/rules/ui-audit-gate.md +1 -2
  41. package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
  42. package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +3 -3
  43. package/dist/agent-src/skills/ai-council/SKILL.md +11 -3
  44. package/dist/agent-src/skills/api-design/data/manifest.json +3 -3
  45. package/dist/agent-src/skills/database/data/manifest.json +4 -4
  46. package/dist/agent-src/skills/database/data/query-tuning.csv +3 -3
  47. package/dist/agent-src/skills/design-review/SKILL.md +80 -4
  48. package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
  49. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +30 -5
  50. package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
  51. package/dist/agent-src/skills/experiment-loop/SKILL.md +31 -1
  52. package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
  53. package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
  54. package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
  55. package/dist/agent-src/skills/storybook-workshop/SKILL.md +42 -3
  56. package/dist/agent-src/skills/threat-modeling/SKILL.md +9 -4
  57. package/dist/agent-src/skills/threat-modeling/data/manifest.json +4 -4
  58. package/dist/agent-src/skills/ui-component-architect/SKILL.md +23 -0
  59. package/dist/agent-src/skills/verify-repair-loop/SKILL.md +37 -3
  60. package/dist/agent-src/templates/agent-settings.md +1 -1
  61. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  62. package/dist/agent-src/templates/roadmaps.md +218 -0
  63. package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
  64. package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
  65. package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
  66. package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  67. package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
  68. package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  69. package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  70. package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
  71. package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  72. package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  73. package/dist/cli/python/knowledge_ingest.js +1 -1
  74. package/dist/cli/python/workspace_drive.js +1 -1
  75. package/dist/cli-delegate/check_update_banner.js +40 -2
  76. package/dist/cli-delegate/chunk-4E2W63DA.js +335 -0
  77. package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-4XFP2Y3E.js} +1 -1
  78. package/dist/cli-delegate/chunk-5R7HOLAF.js +99 -0
  79. package/dist/cli-delegate/{chunk-IVBPRCQO.js → chunk-6KFEC3G7.js} +1 -1
  80. package/dist/cli-delegate/{chunk-2TAQ2K4V.js → chunk-6KM6ORRY.js} +55 -386
  81. package/dist/cli-delegate/{chunk-AK6HODNQ.js → chunk-7XGBX6ZH.js} +973 -555
  82. package/dist/cli-delegate/{chunk-2XY7ILBT.js → chunk-FC75PGJE.js} +42 -2
  83. package/dist/cli-delegate/{chunk-55ACLDUE.js → chunk-FP36SWJD.js} +587 -141
  84. package/dist/cli-delegate/{chunk-3FKR64R5.js → chunk-IGD6ERX6.js} +62 -7
  85. package/dist/cli-delegate/{chunk-GYC3VRYN.js → chunk-JNMVLWLR.js} +99 -14
  86. package/dist/cli-delegate/{chunk-H32DGUJH.js → chunk-P6ZHP5W2.js} +1 -1
  87. package/dist/cli-delegate/{chunk-57PN4GYJ.js → chunk-RGNIWTEA.js} +1 -1
  88. package/dist/cli-delegate/chunk-ROYYTEZU.js +39 -0
  89. package/dist/cli-delegate/{chunk-2DK2VJWG.js → chunk-WGXTVKCQ.js} +20 -6
  90. package/dist/cli-delegate/chunk-YDPILCOL.js +152 -0
  91. package/dist/cli-delegate/{chunk-QJWAUMJW.js → chunk-YJ72UIAK.js} +730 -15
  92. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  93. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  94. package/dist/cli-delegate/cmd_conformance.js +985 -831
  95. package/dist/cli-delegate/cmd_doctor.js +7 -5
  96. package/dist/cli-delegate/cmd_explain.js +2 -2
  97. package/dist/cli-delegate/cmd_export.js +9 -7
  98. package/dist/cli-delegate/cmd_fleet.js +4 -2
  99. package/dist/cli-delegate/cmd_handoff.js +7 -7
  100. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  101. package/dist/cli-delegate/cmd_memory_get.js +1 -1
  102. package/dist/cli-delegate/cmd_migrate.js +2 -2
  103. package/dist/cli-delegate/cmd_packs_active.js +2 -2
  104. package/dist/cli-delegate/cmd_preflight.js +4 -2
  105. package/dist/cli-delegate/cmd_prune.js +10 -8
  106. package/dist/cli-delegate/cmd_route_audit.js +3 -3
  107. package/dist/cli-delegate/cmd_route_explain.js +1 -1
  108. package/dist/cli-delegate/cmd_session_recycle.js +4 -4
  109. package/dist/cli-delegate/cmd_settings_get.js +28 -2
  110. package/dist/cli-delegate/cmd_settings_migrate.js +9 -7
  111. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  112. package/dist/cli-delegate/cmd_sync.js +10 -8
  113. package/dist/cli-delegate/cmd_uninstall.js +16 -13
  114. package/dist/cli-delegate/cmd_update.js +1 -1
  115. package/dist/cli-delegate/cmd_upgrade.js +1 -1
  116. package/dist/cli-delegate/cmd_validate.js +12 -10
  117. package/dist/cli-delegate/cmd_versions.js +1 -1
  118. package/dist/discovery/deprecation-report.md +1 -1
  119. package/dist/discovery/discovery-manifest.json +90 -43
  120. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  121. package/dist/discovery/discovery-manifest.summary.md +4 -4
  122. package/dist/discovery/dormancy-report.md +1 -1
  123. package/dist/discovery/orphan-report.md +1 -1
  124. package/dist/discovery/packs.json +10 -8
  125. package/dist/discovery/trust-report.md +3 -3
  126. package/dist/discovery/workspaces.json +8 -6
  127. package/dist/hooks/dispatch.js +7682 -2772
  128. package/dist/hooks/dispatch.sha256 +1 -0
  129. package/dist/install/claudeRuleRewrite.js +14 -3
  130. package/dist/install/conflict.js +60 -10
  131. package/dist/install/conflictTracker.js +218 -0
  132. package/dist/install/fsPrimitives.js +54 -0
  133. package/dist/install/install.mjs +2200 -1559
  134. package/dist/install/manifestFiles.js +259 -0
  135. package/dist/install/preflight.js +14 -2
  136. package/dist/install/preserve.js +157 -0
  137. package/dist/install/recordedOwnership.js +132 -0
  138. package/dist/install/txlog.js +11 -2
  139. package/dist/mcp/registry-manifest.json +3 -3
  140. package/dist/mcp/server.json +2 -2
  141. package/dist/mcp/server.mjs +3066 -1470
  142. package/dist/router.json +1 -1
  143. package/dist/scripts/_lib/agent_settings.js +3 -2
  144. package/dist/scripts/_lib/fs_atomic.js +167 -0
  145. package/dist/scripts/_lib/installed_tools.js +460 -0
  146. package/dist/scripts/_lib/lean_projection_mode.js +3 -2
  147. package/dist/scripts/_lib/settings_renamed_keys.js +78 -0
  148. package/dist/server/routes/install.js +8 -0
  149. package/dist/server/routes/wizard.js +9 -3
  150. package/dist/server/schemas/settings.js +40 -5
  151. package/dist/shared/missionExecution.js +97 -0
  152. package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
  153. package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
  154. package/dist/ui/index.html +2 -2
  155. package/docs/MIGRATION.md +103 -0
  156. package/docs/contracts/settings-classes.md +28 -7
  157. package/docs/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
  158. package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
  159. package/docs/guidelines/design-fidelity-routing.md +29 -0
  160. package/package.json +4 -4
  161. package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
  162. package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
  163. package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
  164. package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  165. package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
  166. package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  167. package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  168. package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
  169. package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  170. package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  171. package/src/config/agent-settings.template.yml +168 -27
  172. package/src/config/auto-rule-budget.json +16 -0
  173. package/src/config/ci-local-parity.yml +36 -5
  174. package/src/config/continuity-surface.json +23 -2
  175. package/src/config/gate-coverage.yml +377 -13
  176. package/src/config/gate-violation-baselines.json +10 -6
  177. package/src/config/hook-latency-budget.json +21 -0
  178. package/src/config/hook-token-budget.json +4 -2
  179. package/src/config/loop-surfaces.yaml +215 -0
  180. package/src/config/metric-registry.yml +114 -0
  181. package/src/config/placeholder-drift-budget.json +10 -0
  182. package/src/config/platform-anchor.json +3 -2
  183. package/src/config/release-gate-locality.yml +11 -0
  184. package/src/config/routing-coverage-seed.json +33 -5
  185. package/src/config/rule-enforcement-baseline.json +0 -1
  186. package/src/install/claudeRuleRewrite.ts +16 -2
  187. package/src/install/conflict.ts +65 -9
  188. package/src/install/conflictTracker.ts +299 -0
  189. package/src/install/fsPrimitives.ts +54 -0
  190. package/src/install/manifestFiles.ts +312 -0
  191. package/src/install/preflight.ts +16 -2
  192. package/src/install/preserve.ts +205 -0
  193. package/src/install/recordedOwnership.ts +149 -0
  194. package/src/install/txlog.ts +11 -2
  195. package/src/install/types.ts +19 -4
  196. package/src/scripts/_cli/cmd_conformance.ts +88 -15
  197. package/src/scripts/_cli/cmd_doctor.ts +7 -20
  198. package/src/scripts/_cli/cmd_route_explain.ts +9 -2
  199. package/src/scripts/_cli/cmd_settings_get.ts +44 -0
  200. package/src/scripts/_cli/doctor_execution.ts +173 -0
  201. package/src/scripts/_cli/doctor_network_posture.ts +226 -0
  202. package/src/scripts/_lib/activation_payload.ts +247 -0
  203. package/src/scripts/_lib/agent_settings.ts +4 -2
  204. package/src/scripts/_lib/authority_path.ts +102 -0
  205. package/src/scripts/_lib/body_portable.ts +102 -0
  206. package/src/scripts/_lib/cascade_base.ts +94 -0
  207. package/src/scripts/_lib/cc_transcript.ts +132 -1
  208. package/src/scripts/_lib/changelog_eras.ts +7 -2
  209. package/src/scripts/_lib/concern_sla_window.ts +200 -0
  210. package/src/scripts/_lib/continuation_ladder.ts +149 -2
  211. package/src/scripts/_lib/council_transport.ts +129 -0
  212. package/src/scripts/_lib/delivery_ready.ts +112 -0
  213. package/src/scripts/_lib/forge_protection.ts +381 -0
  214. package/src/scripts/_lib/forge_reader.ts +558 -0
  215. package/src/scripts/_lib/hook_platform_keys.ts +27 -0
  216. package/src/scripts/_lib/host_env_write.ts +111 -0
  217. package/src/scripts/_lib/json_python_parity.ts +127 -0
  218. package/src/scripts/_lib/lean_projection_mode.ts +14 -3
  219. package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
  220. package/src/scripts/_lib/loop_surfaces.ts +382 -0
  221. package/src/scripts/_lib/mission_record.ts +190 -0
  222. package/src/scripts/_lib/obligation_frequency.ts +217 -0
  223. package/src/scripts/_lib/obligations.ts +513 -0
  224. package/src/scripts/_lib/orchestration_record.ts +11 -20
  225. package/src/scripts/_lib/probe_inputs.ts +271 -0
  226. package/src/scripts/_lib/read_surface_scan.ts +322 -0
  227. package/src/scripts/_lib/release_highlights.ts +201 -11
  228. package/src/scripts/_lib/release_holds.ts +493 -0
  229. package/src/scripts/_lib/release_migration_gate.ts +184 -0
  230. package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
  231. package/src/scripts/_lib/rides_along.ts +102 -0
  232. package/src/scripts/_lib/settings_renamed_keys.ts +82 -0
  233. package/src/scripts/_lib/shell_write_shapes.ts +108 -0
  234. package/src/scripts/_lib/structural_hiding.ts +622 -0
  235. package/src/scripts/_lib/test_delta.ts +166 -0
  236. package/src/scripts/_lib/test_provenance.ts +171 -0
  237. package/src/scripts/_lib/turn_end_refusals.ts +525 -4
  238. package/src/scripts/_lib/turn_end_transcript.ts +216 -0
  239. package/src/scripts/_lib/typed_op_grant.ts +246 -0
  240. package/src/scripts/_lib/typed_op_watch.ts +185 -0
  241. package/src/scripts/_lib/verification_command.ts +324 -0
  242. package/src/scripts/_lib/verification_evidence.ts +647 -0
  243. package/src/scripts/_lib/verify_clause.ts +280 -0
  244. package/src/scripts/ai_council/config.ts +8 -11
  245. package/src/scripts/ai_council/fallback_config.ts +5 -3
  246. package/src/scripts/ai_council/ownership_classes.ts +118 -0
  247. package/src/scripts/ai_team/team_dispatch.ts +4 -0
  248. package/src/scripts/ask_block_census.ts +285 -15
  249. package/src/scripts/before_complete_hook.ts +340 -23
  250. package/src/scripts/bench_hook_latency.ts +412 -5
  251. package/src/scripts/cache_realization_report.ts +61 -1
  252. package/src/scripts/check_argument_hint.ts +287 -0
  253. package/src/scripts/check_claims.ts +125 -1
  254. package/src/scripts/check_enforcement_coverage.ts +38 -9
  255. package/src/scripts/check_enforcement_matrix.ts +866 -0
  256. package/src/scripts/check_finding_dispositions.ts +22 -3
  257. package/src/scripts/check_gate_reachability.ts +173 -10
  258. package/src/scripts/check_generator_sync.ts +1025 -0
  259. package/src/scripts/check_host_docs_digest.ts +786 -0
  260. package/src/scripts/check_host_format_column.ts +559 -0
  261. package/src/scripts/check_input_references.ts +228 -0
  262. package/src/scripts/check_inputs_parity.ts +235 -0
  263. package/src/scripts/check_kernel_edit_ratified.ts +47 -5
  264. package/src/scripts/check_kill_switch_table.ts +294 -0
  265. package/src/scripts/check_placeholder_drift.ts +221 -0
  266. package/src/scripts/check_preamble_payload_budget.ts +75 -2
  267. package/src/scripts/check_read_surface_coverage.ts +515 -0
  268. package/src/scripts/check_release_highlights.ts +33 -1
  269. package/src/scripts/check_release_holds.ts +568 -0
  270. package/src/scripts/check_routing_coverage.ts +378 -11
  271. package/src/scripts/check_rule_projection_integrity.ts +22 -12
  272. package/src/scripts/check_static_parity.ts +17 -0
  273. package/src/scripts/check_test_delta.ts +294 -0
  274. package/src/scripts/check_test_weakening.ts +299 -0
  275. package/src/scripts/ci_settle.ts +145 -19
  276. package/src/scripts/closure_scan.ts +447 -0
  277. package/src/scripts/compile_hook_manifest.ts +102 -18
  278. package/src/scripts/council_record_shape.ts +209 -0
  279. package/src/scripts/external_sources_denylist.json +29 -4
  280. package/src/scripts/hook_manifest.json +1 -1
  281. package/src/scripts/hook_manifest.yaml +127 -11
  282. package/src/scripts/hooks/block_config_weakening.ts +229 -4
  283. package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
  284. package/src/scripts/hooks/block_plumbing_writes.ts +314 -0
  285. package/src/scripts/hooks/bundle_integrity.ts +291 -0
  286. package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
  287. package/src/scripts/hooks/code_graph_context_hook.ts +1 -2
  288. package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
  289. package/src/scripts/hooks/concern_registry.ts +6 -0
  290. package/src/scripts/hooks/concern_timings.ts +94 -0
  291. package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
  292. package/src/scripts/hooks/design_pass_hook.ts +221 -5
  293. package/src/scripts/hooks/design_slop_hook.ts +1 -2
  294. package/src/scripts/hooks/dispatch_hook.ts +25 -19
  295. package/src/scripts/hooks/edit_shape_hook.ts +1 -2
  296. package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
  297. package/src/scripts/hooks/evidence_independence.ts +97 -2
  298. package/src/scripts/hooks/exit_codes.ts +128 -0
  299. package/src/scripts/hooks/host_lowering.json +1 -0
  300. package/src/scripts/hooks/host_lowering.ts +128 -3
  301. package/src/scripts/hooks/host_lowering.yaml +262 -46
  302. package/src/scripts/hooks/host_semantics.ts +30 -2
  303. package/src/scripts/hooks/injection_budget.ts +9 -2
  304. package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
  305. package/src/scripts/hooks/journal_record_hook.ts +1 -2
  306. package/src/scripts/hooks/memory_recall_hook.ts +539 -0
  307. package/src/scripts/hooks/obligation_settle_hook.ts +328 -0
  308. package/src/scripts/hooks/one_question_per_ask_hook.ts +25 -4
  309. package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
  310. package/src/scripts/hooks/permission_gate.ts +1 -2
  311. package/src/scripts/hooks/push_settle_hook.ts +1 -3
  312. package/src/scripts/hooks/replay_hook.ts +2 -1
  313. package/src/scripts/hooks/reread_guard_hook.ts +1 -2
  314. package/src/scripts/hooks/review_baseline_hook.ts +1 -2
  315. package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
  316. package/src/scripts/hooks/rule_inject_hook.ts +55 -4
  317. package/src/scripts/hooks/run_continuation_hook.ts +30 -35
  318. package/src/scripts/hooks/skill_route_hook.ts +1 -2
  319. package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
  320. package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
  321. package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
  322. package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
  323. package/src/scripts/hooks/table_fingerprint.ts +49 -0
  324. package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
  325. package/src/scripts/hooks/telemetry_usage_hook.ts +1 -2
  326. package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
  327. package/src/scripts/hooks/turn_end_gate_hook.ts +337 -324
  328. package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
  329. package/src/scripts/hooks_doctor.ts +85 -0
  330. package/src/scripts/hooks_status.ts +19 -0
  331. package/src/scripts/install.ts +109 -210
  332. package/src/scripts/janitor.ts +7 -0
  333. package/src/scripts/lint_decision_classes.ts +464 -0
  334. package/src/scripts/lint_deny_text.ts +419 -0
  335. package/src/scripts/lint_exit_codes.ts +262 -0
  336. package/src/scripts/lint_hook_manifest.ts +66 -0
  337. package/src/scripts/lint_major_migration_sections.ts +450 -0
  338. package/src/scripts/lint_plan_risk_register.ts +47 -3
  339. package/src/scripts/lint_roadmap_blockers.ts +139 -12
  340. package/src/scripts/lint_roadmap_producers.ts +202 -0
  341. package/src/scripts/lint_skill_descriptions.ts +1 -1
  342. package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
  343. package/src/scripts/mcp_server/prompts.ts +142 -1
  344. package/src/scripts/measure_rule_budget.ts +101 -5
  345. package/src/scripts/measure_skill_ranker_baseline.ts +305 -51
  346. package/src/scripts/measure_turn_end_gate.ts +193 -0
  347. package/src/scripts/memory_lookup.ts +1 -1
  348. package/src/scripts/new_roadmap.ts +37 -0
  349. package/src/scripts/orchestration_record.ts +0 -5
  350. package/src/scripts/prepack-check.mjs +53 -0
  351. package/src/scripts/release.ts +14 -98
  352. package/src/scripts/release_drill.ts +267 -1
  353. package/src/scripts/release_publication.ts +64 -1
  354. package/src/scripts/report_enforcement_drift.ts +566 -0
  355. package/src/scripts/report_host_injection_effect.ts +3 -11
  356. package/src/scripts/report_invocation_surface.ts +383 -0
  357. package/src/scripts/report_obligation_carriers.ts +699 -0
  358. package/src/scripts/report_obligation_writer_split.ts +236 -0
  359. package/src/scripts/report_skill_menu_census.ts +208 -0
  360. package/src/scripts/report_standing_payload_by_host.ts +26 -152
  361. package/src/scripts/roadmap_verify_share.ts +213 -0
  362. package/src/scripts/schemas/agent-settings.schema.json +23 -0
  363. package/src/scripts/schemas/command.schema.json +48 -0
  364. package/src/scripts/schemas/review-findings.schema.json +4 -0
  365. package/src/scripts/schemas/skill.schema.json +44 -0
  366. package/src/scripts/scope_growth.ts +219 -0
  367. package/src/scripts/self_review_gate.ts +359 -10
  368. package/src/scripts/semantic_parity_census.ts +314 -0
  369. package/src/scripts/session_eol_report.ts +54 -1
  370. package/src/scripts/sweep_dead_scan_roots.ts +1 -1
  371. package/src/scripts/trigger_eval_grandfather.json +0 -3
  372. package/src/scripts/trigger_eval_rotation.ts +122 -27
  373. package/src/scripts/ui_conformance_probe.ts +774 -0
  374. package/src/scripts/update_prices.ts +85 -3
  375. package/src/scripts/write_bundle_digest.mjs +74 -0
  376. package/src/server/schemas/settings.ts +62 -7
  377. package/src/shared/missionExecution.ts +126 -0
  378. package/src/templates/consumer-settings/ONBOARDING.md +1 -0
  379. package/dist/cli-delegate/chunk-523HCFYO.js +0 -90
  380. package/dist/ui/assets/index-D0jaCRPM.js +0 -29
  381. package/dist/ui/assets/index-jSfMW8_t.css +0 -1
  382. /package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-7RQP4UDH.js} +0 -0
@@ -62,8 +62,11 @@ 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 { isVerificationCommand, mightBeVerification } from "./_lib/verification_command.js";
69
+ import { runnerOf } from "./_lib/verification_evidence.js";
67
70
 
68
71
  // NOTE: the Python docstring says `agents/runtime/state/`, but the code
69
72
  // constant is `agents/state/`. Replicated verbatim — latent docstring/code
@@ -144,28 +147,6 @@ export const COMMAND_TOOLS: ReadonlySet<string> = new Set([
144
147
  "RunShellCommand", // Cursor
145
148
  ]);
146
149
 
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
150
  type StateDict = Record<string, unknown>;
170
151
 
171
152
  /** Python datetime.now(timezone.utc).isoformat(timespec="seconds"). */
@@ -193,6 +174,11 @@ function _empty_state(): StateDict {
193
174
  // has to survive it: the measured failure is a completion claim in a LATER
194
175
  // turn than the poll it rests on.
195
176
  ci_last: null,
177
+ // Round 8 — see `_reset_turn`. Present in the empty state so a freshly
178
+ // created file has the same shape as one that has seen a turn boundary;
179
+ // a reader must never have to distinguish "no runs yet" from "key absent".
180
+ verification_runs: [],
181
+ edits_this_turn: 0,
196
182
  checked_at: _now(),
197
183
  };
198
184
  }
@@ -230,7 +216,7 @@ function _is_verification(command: string): boolean {
230
216
  // settle is genuine, which is what the FC-3b guard below decides. Before this
231
217
  // change a CI poll counted for nothing at all, so nothing observed the
232
218
  // difference between "settled" and "never started".
233
- return _VERIFICATION_RE.test(command) || isCiPoll(command);
219
+ return isVerificationCommand(command) || isCiPoll(command);
234
220
  }
235
221
 
236
222
  /**
@@ -334,11 +320,207 @@ function _extract_output(payload: StateDict): string | null {
334
320
  return null;
335
321
  }
336
322
 
323
+ /**
324
+ * Tool names that CHANGE the tree, across the platforms this concern sees.
325
+ *
326
+ * The first four mirror `_EDIT_TOOLS` in the turn-end gate exactly; the rest are
327
+ * the same three tools under the names Augment, Cline and Cursor give them. The
328
+ * gate reads a Claude transcript and can afford four names; this recorder runs
329
+ * on every platform the manifest binds `post_tool_use` on, so it needs all of
330
+ * them — a missed edit name means `edits_this_turn` undercounts and a
331
+ * verification run looks LATER in the sequence than it was, which is the one
332
+ * direction that could clear an unverified edit.
333
+ */
334
+ const EDIT_TOOLS: ReadonlySet<string> = new Set([
335
+ "Edit",
336
+ "Write",
337
+ "MultiEdit",
338
+ "NotebookEdit",
339
+ "str-replace-editor",
340
+ "save-file",
341
+ "str_replace_editor",
342
+ "create_file",
343
+ "write_to_file",
344
+ "replace_in_file",
345
+ "apply_diff",
346
+ "edit_file",
347
+ ]);
348
+
349
+ /**
350
+ * How many run records one turn may keep.
351
+ *
352
+ * The state file is read by a stop-slot hook on every turn, so it is on the
353
+ * latency path and cannot grow without a ceiling. The NEWEST are kept because
354
+ * the only question asked of them is whether a passing run follows the LAST
355
+ * edit, which is always answered by the tail.
356
+ */
357
+ export const MAX_VERIFICATION_RUNS_PER_TURN = 24;
358
+
359
+ /**
360
+ * Characters of tool output retained per stream per record.
361
+ *
362
+ * CHARACTERS, not bytes, and the name is kept for its existing importers: the
363
+ * slice is applied to a JS string. Multibyte output can therefore exceed this
364
+ * many bytes, which is a bound on the state file rather than a promise about
365
+ * it, and 4,096 characters is far more than any runner's summary needs.
366
+ */
367
+ export const RUN_OUTPUT_TAIL_BYTES = 4096;
368
+
369
+ /**
370
+ * Apply the per-turn cap, keeping the newest AND the earliest failing record.
371
+ *
372
+ * Two detectors read this array and they ask opposite-ended questions. Detector
373
+ * C asks whether a pass follows the last edit — always answered by the tail.
374
+ * Detector F asks whether a red preceded the green — answered by the head. A
375
+ * plain `slice(-N)` serves the first and can silently drop the evidence the
376
+ * second needs, turning a turn that did honest red-green work into a
377
+ * `no_red_evidence` refusal once the run count passes the cap.
378
+ */
379
+ function _cap_runs(runs: unknown[]): unknown[] {
380
+ if (runs.length <= MAX_VERIFICATION_RUNS_PER_TURN) return runs;
381
+ const tail = runs.slice(-(MAX_VERIFICATION_RUNS_PER_TURN - 1));
382
+ const head = runs.find((r) => {
383
+ if (r === null || typeof r !== "object" || Array.isArray(r)) return false;
384
+ const rec = r as StateDict;
385
+ const code = rec["exit_code"];
386
+ return typeof code === "number" && code !== 0;
387
+ });
388
+ if (head === undefined || tail.includes(head)) {
389
+ return runs.slice(-MAX_VERIFICATION_RUNS_PER_TURN);
390
+ }
391
+ return [head, ...tail];
392
+ }
393
+
394
+ /**
395
+ * The prefix a host puts on a FAILED shell result when it carries no exit field.
396
+ *
397
+ * Claude Code's Bash tool does exactly this: a failing call's whole tool
398
+ * response is the string `Error: Exit code 1\n<output>`. Measured over 1,077
399
+ * object-shaped and 11 string-shaped tool results in this machine's own
400
+ * transcripts (2026-09-29): every string-shaped result carried this prefix, and
401
+ * no shape of either kind carried any of the numeric field names below.
402
+ */
403
+ const _ERROR_EXIT_PREFIX = /^Error:\s*Exit code\s*(\d+)/i;
404
+
405
+ /** The status a post-tool payload actually reports, and HOW it reported it. */
406
+ interface ExitReading {
407
+ readonly code: number | null;
408
+ /** Provenance, recorded so a reader can tell an observed code from a derived one. */
409
+ readonly source: "field" | "error_prefix" | "response_shape" | null;
410
+ /** The host said the call was interrupted — a kill, not a verdict on the work. */
411
+ readonly interrupted: boolean;
412
+ }
413
+
414
+ const _NO_EXIT: ExitReading = { code: null, source: null, interrupted: false };
415
+
416
+ function _numeric_exit_field(obj: StateDict): number | null {
417
+ for (const key of ["exit_code", "exitCode", "returncode", "returnCode", "status_code"]) {
418
+ const v = obj[key];
419
+ if (typeof v === "number" && Number.isFinite(v)) return Math.trunc(v);
420
+ if (typeof v === "string" && v.trim() !== "" && Number.isFinite(Number(v))) {
421
+ return Math.trunc(Number(v));
422
+ }
423
+ }
424
+ return null;
425
+ }
426
+
427
+ /**
428
+ * What the host said about how the command ended.
429
+ *
430
+ * `null` is a REAL value and is never normalised to 0: several hosts surface no
431
+ * exit status at all, and writing 0 for "the host said nothing" would
432
+ * manufacture the strongest possible evidence out of silence. The classifier's
433
+ * `exit_code_unavailable` verdict exists so that gap stays visible.
434
+ *
435
+ * THREE SOURCES, in descending directness, and the third is the one that took a
436
+ * measurement rather than a guess.
437
+ *
438
+ * · `field` — a numeric exit field, wherever a host provides one.
439
+ * · `error_prefix` — the `Error: Exit code N` string form above. This is a
440
+ * real non-zero code, stated by the host in the only place it states it.
441
+ * · `response_shape` — an OBJECT response carrying `interrupted: false`
442
+ * alongside a `stdout` or `stderr` key. On Claude Code that is the success
443
+ * form, and the discriminator is deliberately the PRESENCE of
444
+ * `interrupted: false` rather than the ABSENCE of an error prefix: a
445
+ * positive signal from the host, not an inference from silence. A shape
446
+ * that carries neither reads as `null` and stays an instrument gap.
447
+ *
448
+ * Without the second and third readings this recorder wrote `exit_code: null`
449
+ * for every command on the one host that binds the turn-end gate, so the whole
450
+ * record path was inert — found by an independent review of the branch that
451
+ * introduced it, against 1,077 real tool results.
452
+ */
453
+ function _extract_exit_reading(payload: StateDict): ExitReading {
454
+ const direct = _numeric_exit_field(payload);
455
+ if (direct !== null) return { code: direct, source: "field", interrupted: false };
456
+
457
+ for (const key of ["tool_response", "toolResponse", "result", "output"]) {
458
+ const v = payload[key];
459
+ if (typeof v === "string") {
460
+ const m = _ERROR_EXIT_PREFIX.exec(v);
461
+ if (m?.[1] !== undefined) {
462
+ return { code: Number(m[1]), source: "error_prefix", interrupted: false };
463
+ }
464
+ continue;
465
+ }
466
+ if (v === null || typeof v !== "object" || Array.isArray(v)) continue;
467
+ const obj = v as StateDict;
468
+ const nested = _numeric_exit_field(obj);
469
+ if (nested !== null) return { code: nested, source: "field", interrupted: false };
470
+ if (obj["interrupted"] === true) return { code: null, source: null, interrupted: true };
471
+ const hasStream = typeof obj["stdout"] === "string" || typeof obj["stderr"] === "string";
472
+ if (obj["interrupted"] === false && hasStream) {
473
+ return { code: 0, source: "response_shape", interrupted: false };
474
+ }
475
+ }
476
+ return _NO_EXIT;
477
+ }
478
+
479
+ /**
480
+ * The command's stdout and stderr as REAL strings, never as a stringified blob.
481
+ *
482
+ * Separate from `_extract_output` above, which feeds the pre-existing `vacuous`
483
+ * counters and whose behavior must not move: that one returns
484
+ * `JSON.stringify(v)` for an object response, and every parser in
485
+ * `verification_evidence.ts` is line-anchored (`/^[ \t]*Tests:?.../m` and
486
+ * friends). JSON escapes a newline as the two characters `\` and `n`, so a
487
+ * stringified response can never match any of them — the classifier was blind
488
+ * to every summary on the host's success shape. Found by the same review.
489
+ */
490
+ function _extract_run_streams(payload: StateDict): { stdout: string; stderr: string } {
491
+ for (const key of ["tool_response", "toolResponse", "result", "output"]) {
492
+ const v = payload[key];
493
+ if (typeof v === "string") return { stdout: v, stderr: "" };
494
+ if (v === null || typeof v !== "object" || Array.isArray(v)) continue;
495
+ const obj = v as StateDict;
496
+ const out = obj["stdout"] ?? obj["output"];
497
+ const err = obj["stderr"];
498
+ if (typeof out === "string" || typeof err === "string") {
499
+ return {
500
+ stdout: typeof out === "string" ? out : "",
501
+ stderr: typeof err === "string" ? err : "",
502
+ };
503
+ }
504
+ }
505
+ const topOut = payload["stdout"] ?? payload["output"];
506
+ const topErr = payload["stderr"];
507
+ return {
508
+ stdout: typeof topOut === "string" ? topOut : "",
509
+ stderr: typeof topErr === "string" ? topErr : "",
510
+ };
511
+ }
512
+
337
513
  function _reset_turn(state: StateDict, session_id: string): StateDict {
338
514
  state["session_id"] = session_id || state["session_id"] || "";
339
515
  state["turn_started_at"] = _now();
340
516
  state["verifications_this_turn"] = 0;
341
517
  state["verified_this_turn"] = false;
518
+ // Round 8 — the run records the stop gate reads instead of the command text,
519
+ // and the edit counter that places them in the turn's sequence. Both are
520
+ // TURN-scoped: a run from the previous turn demonstrably did not exercise this
521
+ // turn's edits, which is the freshness argument `verify-before-complete` makes.
522
+ state["verification_runs"] = [];
523
+ state["edits_this_turn"] = 0;
342
524
  // FC-3b turn-scoped counters: a CI settle must be witnessed within the same
343
525
  // turn that claims it, so the in-flight observation does not survive a turn.
344
526
  state["ci_saw_pending"] = false;
@@ -386,6 +568,11 @@ function _update(state: StateDict, event: string, envelope: StateDict): StateDic
386
568
  state = _reset_turn(state, session_id);
387
569
  } else if (event === "post_tool_use") {
388
570
  const [tool, cmd] = _extract_command(pl);
571
+ // Counted BEFORE the verification branch, so a run recorded in this same
572
+ // event is placed after the edits that preceded it and never after itself.
573
+ if (tool !== null && EDIT_TOOLS.has(tool)) {
574
+ state["edits_this_turn"] = _asInt(state["edits_this_turn"]) + 1;
575
+ }
389
576
  if (cmd && _is_verification(cmd)) {
390
577
  const output = _extract_output(pl);
391
578
  // No readable output → the guard has nothing to judge, so behaviour is
@@ -439,6 +626,7 @@ function _update(state: StateDict, event: string, envelope: StateDict): StateDic
439
626
  vacuous,
440
627
  counted: counts,
441
628
  };
629
+
442
630
  if (counts) {
443
631
  state["verifications_this_turn"] = _asInt(state["verifications_this_turn"]) + 1;
444
632
  state["verifications_this_session"] =
@@ -448,6 +636,67 @@ function _update(state: StateDict, event: string, envelope: StateDict): StateDic
448
636
  state["nonevidence_this_turn"] = _asInt(state["nonevidence_this_turn"]) + 1;
449
637
  }
450
638
  }
639
+
640
+ // The RUN record, written beside `last_verification` and gated on a
641
+ // DIFFERENT, wider selector — the one the turn-end gate's detector C uses.
642
+ //
643
+ // Two selectors is a smell, so here is why it is not one — and the reason
644
+ // was rewritten 2026-09-30, because the version standing here was half
645
+ // wrong. It claimed the narrow selector protected `verifications_this_turn`
646
+ // AND the FC-3b `ci_last` discrimination. `ci_last` is written inside the
647
+ // nested `isCiPoll` branch above, reached through the second disjunct of
648
+ // `_is_verification` whatever the first one says, so no widening of the
649
+ // first could ever have moved it; and nothing branches on the counter.
650
+ // Both are now pinned by tests rather than asserted in a comment.
651
+ //
652
+ // What survives is a real distinction, and it is about ERROR COST, not
653
+ // about two spellings of one idea. `isVerificationCommand` answers *did
654
+ // this verify* — a false positive there clears an unverified edit, so it is
655
+ // head-anchored. `mightBeVerification` answers *is this worth writing down*
656
+ // — and the run record decides nothing by itself, since `classifyRun` is
657
+ // what judges it. So the record path wants every command the gate might
658
+ // read, including the ones the classifier will then reject —
659
+ // `not_a_verification_command` is a verdict the gate needs to SEE, and a
660
+ // recorder that filtered those out would leave the gate unable to tell "the
661
+ // turn ran `echo test`" from "the turn ran nothing at all".
662
+ //
663
+ // `after_edits` is the edit counter as it stood when the command ran. The
664
+ // reader compares it against the number of edits the turn made in total:
665
+ // equal or greater means nothing was edited afterwards. That is an ORDINAL
666
+ // comparison needing no clock, so it survives a host whose timestamps are
667
+ // coarse or absent, and it degrades toward under-refusing when the reader's
668
+ // own count is short.
669
+ if (cmd && mightBeVerification(cmd)) {
670
+ const streams = _extract_run_streams(pl);
671
+ const exit = _extract_exit_reading(pl);
672
+ const runs = Array.isArray(state["verification_runs"])
673
+ ? [...(state["verification_runs"] as unknown[])]
674
+ : [];
675
+ runs.push({
676
+ command: cmd.slice(0, 512),
677
+ tool,
678
+ exit_code: exit.code,
679
+ // Provenance, not decoration: `response_shape` is a code this recorder
680
+ // DERIVED from the host's success form, and a reader disputing a verdict
681
+ // needs to know which of the three readings produced it.
682
+ exit_source: exit.source,
683
+ interrupted: exit.interrupted,
684
+ stdout_tail: streams.stdout.slice(-RUN_OUTPUT_TAIL_BYTES),
685
+ // Written, and previously not: `RunRecord.stderr_tail` was declared and
686
+ // read by the classifier's `output()` while nothing populated it, so
687
+ // stderr reached a parser only by accident inside a stringified blob.
688
+ stderr_tail: streams.stderr.slice(-RUN_OUTPUT_TAIL_BYTES),
689
+ runner: runnerOf(cmd),
690
+ after_edits: _asInt(state["edits_this_turn"]),
691
+ at: _now(),
692
+ });
693
+ // The NEWEST are kept for detector C, which asks whether a pass follows
694
+ // the last edit. Detector F asks a question the head answers — was there a
695
+ // red before the green — so the cap keeps the FIRST failing record too,
696
+ // ahead of the tail, rather than letting a chatty turn drop the red half
697
+ // of its own red-then-green pair.
698
+ state["verification_runs"] = _cap_runs(runs);
699
+ }
451
700
  } else if (event === "stop") {
452
701
  state["last_stop_at"] = _now();
453
702
  }
@@ -613,6 +862,74 @@ export function main(argv?: string[]): number {
613
862
  return run(_readStdin(), { consumer_root: process.cwd(), verbose: args.verbose });
614
863
  }
615
864
 
865
+ // ---------------------------------------------------------------------------
866
+ // The consumer side of `verification_runs`, living with its producer
867
+ // ---------------------------------------------------------------------------
868
+
869
+ /**
870
+ * Why the turn-end gate's reader sits HERE and not in the gate.
871
+ *
872
+ * The shape of `verification_runs` and `edits_this_turn` is this file's; the
873
+ * reader is the only consumer of that shape, and the gate already imported this
874
+ * module's path BUILDER rather than a path constant for exactly the reason that
875
+ * applies here too — "the consumer cannot read a path the producer does not
876
+ * write" is only true while the two agree, and separating them made the
877
+ * agreement invisible when the layout moved. Keeping the reader beside the
878
+ * writer makes a shape change a type error in one file instead of a silent
879
+ * disagreement across two.
880
+ *
881
+ * It also keeps the gate under its source-size ceiling without deleting
882
+ * anything, which is the move the ratchet asks for rather than a trim.
883
+ */
884
+ export interface TurnRunState {
885
+ readonly runs: readonly unknown[];
886
+ readonly edits_this_turn: number;
887
+ }
888
+
889
+ /**
890
+ * The turn's recorded verification runs, or `null` when the recorder is not live.
891
+ *
892
+ * WHY THE LIVENESS TEST IS `edits_this_turn >= 1` AND NOT "the file exists".
893
+ * The obvious predicate — a state file with a `verification_runs` key — is
894
+ * wrong in a way that would refuse honest work on entire platforms. That key is
895
+ * present in the recorder's EMPTY state, so a host whose `post_tool_use` slot
896
+ * the manifest does not bind still has a file carrying `[]`, written by the
897
+ * prompt and stop events alone. Reading that as "this turn ran nothing" would
898
+ * refuse every editing turn on such a host, whatever the operator actually ran.
899
+ *
900
+ * `edits_this_turn` is the one field only a `post_tool_use` event can raise. The
901
+ * detector reaches here having already found an edit in the transcript, so a
902
+ * recorder that saw none of this turn's tool events is exactly the case this
903
+ * returns `null` for — and `null` means the transcript path answers, which is
904
+ * the behavior that predates the record path.
905
+ *
906
+ * The ownership check is the same one detector D applies to `ci_last`, for the
907
+ * same reason: a FOREIGN file's passing record would vouch for a run this
908
+ * session never made.
909
+ */
910
+ export function readTurnRunState(
911
+ workspaceRoot: string,
912
+ session_id: string,
913
+ ): TurnRunState | null {
914
+ if (!has_stable_session_id(session_id)) return null;
915
+ try {
916
+ const raw = fs.readFileSync(path.join(workspaceRoot, statePathFor(session_id)), 'utf-8');
917
+ const decoded: unknown = JSON.parse(raw);
918
+ if (!owns_session_state(decoded, session_id)) return null;
919
+ if (typeof decoded !== 'object' || decoded === null || Array.isArray(decoded)) return null;
920
+ const state = decoded as Record<string, unknown>;
921
+ const runs = state['verification_runs'];
922
+ const edits = state['edits_this_turn'];
923
+ if (!Array.isArray(runs)) return null;
924
+ if (typeof edits !== 'number' || !Number.isFinite(edits) || edits < 1) return null;
925
+ return { runs: runs as readonly unknown[], edits_this_turn: edits };
926
+ } catch {
927
+ // Absent, unreadable or malformed — the recorder said nothing, so the
928
+ // transcript answers. Never a refusal of its own.
929
+ return null;
930
+ }
931
+ }
932
+
616
933
  // Bundle-safety: never auto-run when inlined into an esbuild bundle, where
617
934
  // every module shares the bundle's `import.meta.url` (see cmd_migrate.ts).
618
935
  declare const __AGENT_CONFIG_BUNDLE__: boolean | undefined;