okstra 0.173.0 → 0.175.1

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 (402) hide show
  1. package/README.md +18 -9
  2. package/bin/okstra +8 -4
  3. package/dist/cli-registry.d.mts +16 -0
  4. package/dist/cli-registry.mjs +560 -0
  5. package/dist/cli-registry.mjs.map +1 -0
  6. package/dist/commands/execute/agent-prompt.d.mts +1 -0
  7. package/{src → dist}/commands/execute/agent-prompt.mjs +10 -11
  8. package/dist/commands/execute/agent-prompt.mjs.map +1 -0
  9. package/dist/commands/execute/codex-dispatch.d.mts +3 -0
  10. package/dist/commands/execute/codex-dispatch.mjs +6 -0
  11. package/dist/commands/execute/codex-dispatch.mjs.map +1 -0
  12. package/dist/commands/execute/codex-run.d.mts +3 -0
  13. package/{src → dist}/commands/execute/codex-run.mjs +30 -37
  14. package/dist/commands/execute/codex-run.mjs.map +1 -0
  15. package/dist/commands/execute/convergence.d.mts +1 -0
  16. package/{src → dist}/commands/execute/convergence.mjs +15 -16
  17. package/dist/commands/execute/convergence.mjs.map +1 -0
  18. package/dist/commands/execute/error-log.d.mts +1 -0
  19. package/{src → dist}/commands/execute/error-log.mjs +2 -3
  20. package/dist/commands/execute/error-log.mjs.map +1 -0
  21. package/dist/commands/execute/git-reconcile.d.mts +1 -0
  22. package/{src → dist}/commands/execute/git-reconcile.mjs +10 -11
  23. package/dist/commands/execute/git-reconcile.mjs.map +1 -0
  24. package/dist/commands/execute/handoff.d.mts +1 -0
  25. package/{src → dist}/commands/execute/handoff.mjs +10 -11
  26. package/dist/commands/execute/handoff.mjs.map +1 -0
  27. package/dist/commands/execute/incremental-carry.d.mts +1 -0
  28. package/{src → dist}/commands/execute/incremental-carry.mjs +2 -3
  29. package/dist/commands/execute/incremental-carry.mjs.map +1 -0
  30. package/dist/commands/execute/incremental-scope.d.mts +1 -0
  31. package/{src → dist}/commands/execute/incremental-scope.mjs +2 -3
  32. package/dist/commands/execute/incremental-scope.mjs.map +1 -0
  33. package/dist/commands/execute/integrate-stages.d.mts +1 -0
  34. package/{src → dist}/commands/execute/integrate-stages.mjs +10 -11
  35. package/dist/commands/execute/integrate-stages.mjs.map +1 -0
  36. package/dist/commands/execute/pane-title.d.mts +1 -0
  37. package/dist/commands/execute/pane-title.mjs +20 -0
  38. package/dist/commands/execute/pane-title.mjs.map +1 -0
  39. package/dist/commands/execute/plan-items.d.mts +1 -0
  40. package/dist/commands/execute/plan-items.mjs +9 -0
  41. package/dist/commands/execute/plan-items.mjs.map +1 -0
  42. package/dist/commands/execute/plan-validate.d.mts +1 -0
  43. package/dist/commands/execute/plan-validate.mjs +68 -0
  44. package/dist/commands/execute/plan-validate.mjs.map +1 -0
  45. package/dist/commands/execute/plan-verify.d.mts +1 -0
  46. package/{src → dist}/commands/execute/plan-verify.mjs +13 -16
  47. package/dist/commands/execute/plan-verify.mjs.map +1 -0
  48. package/dist/commands/execute/render-bundle.d.mts +3 -0
  49. package/{src → dist}/commands/execute/render-bundle.mjs +65 -61
  50. package/dist/commands/execute/render-bundle.mjs.map +1 -0
  51. package/dist/commands/execute/run.d.mts +24 -0
  52. package/dist/commands/execute/run.mjs +196 -0
  53. package/dist/commands/execute/run.mjs.map +1 -0
  54. package/dist/commands/execute/spawn-followups.d.mts +1 -0
  55. package/{src → dist}/commands/execute/spawn-followups.mjs +6 -7
  56. package/dist/commands/execute/spawn-followups.mjs.map +1 -0
  57. package/dist/commands/execute/team.d.mts +3 -0
  58. package/dist/commands/execute/team.mjs +56 -0
  59. package/dist/commands/execute/team.mjs.map +1 -0
  60. package/dist/commands/execute/token-usage.d.mts +1 -0
  61. package/{src → dist}/commands/execute/token-usage.mjs +2 -3
  62. package/dist/commands/execute/token-usage.mjs.map +1 -0
  63. package/dist/commands/execute/wizard.d.mts +8 -0
  64. package/dist/commands/execute/wizard.mjs +191 -0
  65. package/dist/commands/execute/wizard.mjs.map +1 -0
  66. package/dist/commands/execute/worker-audit-check.d.mts +1 -0
  67. package/{src → dist}/commands/execute/worker-audit-check.mjs +11 -12
  68. package/dist/commands/execute/worker-audit-check.mjs.map +1 -0
  69. package/dist/commands/execute/worker-dispatch.d.mts +7 -0
  70. package/{src → dist}/commands/execute/worker-dispatch.mjs +34 -46
  71. package/dist/commands/execute/worker-dispatch.mjs.map +1 -0
  72. package/dist/commands/execute/worker-state.d.mts +1 -0
  73. package/{src → dist}/commands/execute/worker-state.mjs +15 -16
  74. package/dist/commands/execute/worker-state.mjs.map +1 -0
  75. package/dist/commands/execute/worktree-lookup.d.mts +1 -0
  76. package/{src → dist}/commands/execute/worktree-lookup.mjs +38 -37
  77. package/dist/commands/execute/worktree-lookup.mjs.map +1 -0
  78. package/dist/commands/execute/worktree-status.d.mts +1 -0
  79. package/dist/commands/execute/worktree-status.mjs +115 -0
  80. package/dist/commands/execute/worktree-status.mjs.map +1 -0
  81. package/dist/commands/inspect/code-review.d.mts +1 -0
  82. package/{src → dist}/commands/inspect/code-review.mjs +16 -19
  83. package/dist/commands/inspect/code-review.mjs.map +1 -0
  84. package/dist/commands/inspect/container.d.mts +1 -0
  85. package/{src → dist}/commands/inspect/container.mjs +11 -13
  86. package/dist/commands/inspect/container.mjs.map +1 -0
  87. package/dist/commands/inspect/context-cost.d.mts +1 -0
  88. package/{src → dist}/commands/inspect/context-cost.mjs +11 -13
  89. package/dist/commands/inspect/context-cost.mjs.map +1 -0
  90. package/dist/commands/inspect/design-prep.d.mts +1 -0
  91. package/{src → dist}/commands/inspect/design-prep.mjs +10 -11
  92. package/dist/commands/inspect/design-prep.mjs.map +1 -0
  93. package/dist/commands/inspect/error-report.d.mts +1 -0
  94. package/{src → dist}/commands/inspect/error-report.mjs +11 -13
  95. package/dist/commands/inspect/error-report.mjs.map +1 -0
  96. package/dist/commands/inspect/error-zip.d.mts +1 -0
  97. package/{src → dist}/commands/inspect/error-zip.mjs +11 -12
  98. package/dist/commands/inspect/error-zip.mjs.map +1 -0
  99. package/dist/commands/inspect/log-report.d.mts +1 -0
  100. package/{src → dist}/commands/inspect/log-report.mjs +11 -13
  101. package/dist/commands/inspect/log-report.mjs.map +1 -0
  102. package/dist/commands/inspect/profile-show.d.mts +1 -0
  103. package/{src → dist}/commands/inspect/profile-show.mjs +10 -11
  104. package/dist/commands/inspect/profile-show.mjs.map +1 -0
  105. package/dist/commands/inspect/recap.d.mts +1 -0
  106. package/{src → dist}/commands/inspect/recap.mjs +11 -13
  107. package/dist/commands/inspect/recap.mjs.map +1 -0
  108. package/dist/commands/inspect/resolve-task-key.d.mts +1 -0
  109. package/{src → dist}/commands/inspect/resolve-task-key.mjs +11 -13
  110. package/dist/commands/inspect/resolve-task-key.mjs.map +1 -0
  111. package/dist/commands/inspect/rollup.d.mts +1 -0
  112. package/{src → dist}/commands/inspect/rollup.mjs +11 -13
  113. package/dist/commands/inspect/rollup.mjs.map +1 -0
  114. package/dist/commands/inspect/run-audit.d.mts +1 -0
  115. package/{src → dist}/commands/inspect/run-audit.mjs +11 -12
  116. package/dist/commands/inspect/run-audit.mjs.map +1 -0
  117. package/dist/commands/inspect/set-work-status.d.mts +1 -0
  118. package/{src → dist}/commands/inspect/set-work-status.mjs +11 -13
  119. package/dist/commands/inspect/set-work-status.mjs.map +1 -0
  120. package/dist/commands/inspect/stage-map.d.mts +1 -0
  121. package/dist/commands/inspect/stage-map.mjs +110 -0
  122. package/dist/commands/inspect/stage-map.mjs.map +1 -0
  123. package/dist/commands/inspect/task-list.d.mts +1 -0
  124. package/dist/commands/inspect/task-list.mjs +103 -0
  125. package/dist/commands/inspect/task-list.mjs.map +1 -0
  126. package/dist/commands/inspect/task-show.d.mts +1 -0
  127. package/dist/commands/inspect/task-show.mjs +108 -0
  128. package/dist/commands/inspect/task-show.mjs.map +1 -0
  129. package/dist/commands/inspect/time-report.d.mts +1 -0
  130. package/{src → dist}/commands/inspect/time-report.mjs +11 -13
  131. package/dist/commands/inspect/time-report.mjs.map +1 -0
  132. package/dist/commands/inspect/usage-report.d.mts +1 -0
  133. package/{src → dist}/commands/inspect/usage-report.mjs +11 -13
  134. package/dist/commands/inspect/usage-report.mjs.map +1 -0
  135. package/dist/commands/inspect/user-response.d.mts +1 -0
  136. package/{src → dist}/commands/inspect/user-response.mjs +10 -11
  137. package/dist/commands/inspect/user-response.mjs.map +1 -0
  138. package/dist/commands/inspect/worker-liveness.d.mts +1 -0
  139. package/{src → dist}/commands/inspect/worker-liveness.mjs +11 -13
  140. package/dist/commands/inspect/worker-liveness.mjs.map +1 -0
  141. package/dist/commands/lifecycle/check-project.d.mts +21 -0
  142. package/dist/commands/lifecycle/check-project.mjs +175 -0
  143. package/dist/commands/lifecycle/check-project.mjs.map +1 -0
  144. package/dist/commands/lifecycle/config.d.mts +1 -0
  145. package/dist/commands/lifecycle/config.mjs +369 -0
  146. package/dist/commands/lifecycle/config.mjs.map +1 -0
  147. package/dist/commands/lifecycle/doctor.d.mts +10 -0
  148. package/dist/commands/lifecycle/doctor.mjs +317 -0
  149. package/dist/commands/lifecycle/doctor.mjs.map +1 -0
  150. package/dist/commands/lifecycle/install.d.mts +17 -0
  151. package/dist/commands/lifecycle/install.mjs +1077 -0
  152. package/dist/commands/lifecycle/install.mjs.map +1 -0
  153. package/dist/commands/lifecycle/migrate.d.mts +1 -0
  154. package/{src → dist}/commands/lifecycle/migrate.mjs +10 -11
  155. package/dist/commands/lifecycle/migrate.mjs.map +1 -0
  156. package/dist/commands/lifecycle/model.d.mts +1 -0
  157. package/dist/commands/lifecycle/model.mjs +22 -0
  158. package/dist/commands/lifecycle/model.mjs.map +1 -0
  159. package/dist/commands/lifecycle/paths.d.mts +1 -0
  160. package/dist/commands/lifecycle/paths.mjs +102 -0
  161. package/dist/commands/lifecycle/paths.mjs.map +1 -0
  162. package/dist/commands/lifecycle/preflight.d.mts +1 -0
  163. package/dist/commands/lifecycle/preflight.mjs +124 -0
  164. package/dist/commands/lifecycle/preflight.mjs.map +1 -0
  165. package/dist/commands/lifecycle/setup.d.mts +1 -0
  166. package/dist/commands/lifecycle/setup.mjs +297 -0
  167. package/dist/commands/lifecycle/setup.mjs.map +1 -0
  168. package/dist/commands/lifecycle/uninstall.d.mts +4 -0
  169. package/dist/commands/lifecycle/uninstall.mjs +262 -0
  170. package/dist/commands/lifecycle/uninstall.mjs.map +1 -0
  171. package/dist/commands/manager.d.mts +3 -0
  172. package/{src → dist}/commands/manager.mjs +25 -32
  173. package/dist/commands/manager.mjs.map +1 -0
  174. package/dist/commands/memory/memory.d.mts +1 -0
  175. package/dist/commands/memory/memory.mjs +569 -0
  176. package/dist/commands/memory/memory.mjs.map +1 -0
  177. package/dist/commands/pr/pr.d.mts +33 -0
  178. package/dist/commands/pr/pr.mjs +296 -0
  179. package/dist/commands/pr/pr.mjs.map +1 -0
  180. package/dist/commands/report/agent-activity.d.mts +1 -0
  181. package/{src → dist}/commands/report/agent-activity.mjs +11 -12
  182. package/dist/commands/report/agent-activity.mjs.map +1 -0
  183. package/dist/commands/report/finalize.d.mts +4 -0
  184. package/dist/commands/report/finalize.mjs +64 -0
  185. package/dist/commands/report/finalize.mjs.map +1 -0
  186. package/dist/commands/report/inject-report-index.d.mts +1 -0
  187. package/{src → dist}/commands/report/inject-report-index.mjs +6 -7
  188. package/dist/commands/report/inject-report-index.mjs.map +1 -0
  189. package/dist/commands/report/render-final-report.d.mts +1 -0
  190. package/{src → dist}/commands/report/render-final-report.mjs +6 -7
  191. package/dist/commands/report/render-final-report.mjs.map +1 -0
  192. package/dist/commands/report/render-views.d.mts +1 -0
  193. package/{src → dist}/commands/report/render-views.mjs +6 -7
  194. package/dist/commands/report/render-views.mjs.map +1 -0
  195. package/dist/commands/report/translate.d.mts +1 -0
  196. package/{src → dist}/commands/report/translate.mjs +6 -7
  197. package/dist/commands/report/translate.mjs.map +1 -0
  198. package/dist/lib/helper-scripts.d.mts +3 -0
  199. package/{src → dist}/lib/helper-scripts.mjs +11 -14
  200. package/dist/lib/helper-scripts.mjs.map +1 -0
  201. package/dist/lib/host-registry-client.d.mts +25 -0
  202. package/dist/lib/host-registry-client.mjs +152 -0
  203. package/dist/lib/host-registry-client.mjs.map +1 -0
  204. package/dist/lib/install-assets.d.mts +8 -0
  205. package/{src → dist}/lib/install-assets.mjs +17 -20
  206. package/dist/lib/install-assets.mjs.map +1 -0
  207. package/dist/lib/okstra-dirs.d.mts +4 -0
  208. package/{src → dist}/lib/okstra-dirs.mjs +3 -6
  209. package/dist/lib/okstra-dirs.mjs.map +1 -0
  210. package/dist/lib/paths.d.mts +4 -0
  211. package/dist/lib/paths.mjs +78 -0
  212. package/dist/lib/paths.mjs.map +1 -0
  213. package/dist/lib/proc.d.mts +3 -0
  214. package/dist/lib/proc.mjs +31 -0
  215. package/dist/lib/proc.mjs.map +1 -0
  216. package/dist/lib/python-helper.d.mts +8 -0
  217. package/dist/lib/python-helper.mjs +123 -0
  218. package/dist/lib/python-helper.mjs.map +1 -0
  219. package/dist/lib/runtime-manifest.d.mts +7 -0
  220. package/dist/lib/runtime-manifest.mjs +73 -0
  221. package/dist/lib/runtime-manifest.mjs.map +1 -0
  222. package/dist/lib/skill-catalog.d.mts +4 -0
  223. package/dist/lib/skill-catalog.mjs +50 -0
  224. package/dist/lib/skill-catalog.mjs.map +1 -0
  225. package/dist/lib/types.d.mts +129 -0
  226. package/dist/lib/types.mjs +2 -0
  227. package/dist/lib/types.mjs.map +1 -0
  228. package/dist/lib/version.d.mts +2 -0
  229. package/dist/lib/version.mjs +27 -0
  230. package/dist/lib/version.mjs.map +1 -0
  231. package/docs/architecture/storage-model.md +17 -3
  232. package/docs/architecture.md +12 -22
  233. package/docs/cli.md +29 -8
  234. package/docs/container.md +1 -1
  235. package/docs/contributor-change-matrix.md +3 -3
  236. package/docs/project-structure-overview.md +29 -20
  237. package/docs/task-process/README.md +3 -1
  238. package/docs/task-process/implementation-planning.md +2 -2
  239. package/docs/task-process/implementation.md +1 -1
  240. package/package.json +11 -5
  241. package/runtime/BUILD.json +2 -2
  242. package/runtime/agents/workers/claude-worker.md +3 -2
  243. package/runtime/agents/workers/report-writer-worker.md +3 -1
  244. package/runtime/agents/workers/translator-worker.md +3 -1
  245. package/runtime/bin/lib/okstra/cli.sh +12 -0
  246. package/runtime/bin/lib/okstra/globals.sh +4 -1
  247. package/runtime/bin/lib/okstra/usage.sh +4 -4
  248. package/runtime/bin/okstra-error-log.py +27 -0
  249. package/runtime/bin/okstra-provider-exec.py +245 -16
  250. package/runtime/bin/okstra-trace-cleanup.sh +58 -129
  251. package/runtime/bin/okstra.sh +10 -0
  252. package/runtime/prompts/lead/adapters/cmux.md +2 -0
  253. package/runtime/prompts/lead/convergence.md +27 -18
  254. package/runtime/prompts/lead/okstra-lead-contract.md +4 -2
  255. package/runtime/prompts/lead/plan-body-verification.md +13 -6
  256. package/runtime/prompts/lead/report-writer.md +6 -6
  257. package/runtime/prompts/lead/team-contract.md +1 -1
  258. package/runtime/prompts/profiles/_common-contract.md +2 -2
  259. package/runtime/prompts/profiles/_implementation-executor.md +2 -0
  260. package/runtime/prompts/profiles/_implementation-verifier.md +2 -2
  261. package/runtime/prompts/profiles/change-impact-analysis.md +16 -0
  262. package/runtime/prompts/profiles/error-analysis.md +21 -1
  263. package/runtime/prompts/profiles/feature-analysis.md +16 -0
  264. package/runtime/prompts/profiles/final-verification.md +16 -0
  265. package/runtime/prompts/profiles/implementation-option-selection.md +16 -0
  266. package/runtime/prompts/profiles/implementation-planning.md +32 -9
  267. package/runtime/prompts/profiles/implementation.md +17 -1
  268. package/runtime/prompts/profiles/improvement-discovery.md +16 -0
  269. package/runtime/prompts/profiles/project-analysis.md +16 -0
  270. package/runtime/prompts/profiles/release-handoff.md +5 -1
  271. package/runtime/prompts/profiles/requirements-discovery.md +20 -0
  272. package/runtime/prompts/wizard/prompts.ko.json +18 -0
  273. package/runtime/python/okstra_ctl/adapters/dispatch/__init__.py +1 -6
  274. package/runtime/python/okstra_ctl/adapters/dispatch/cli_wrapper.py +8 -0
  275. package/runtime/python/okstra_ctl/adapters/dispatch/cmux.py +8 -0
  276. package/runtime/python/okstra_ctl/adapters/dispatch/native_team.py +13 -0
  277. package/runtime/python/okstra_ctl/adapters/hosts/antigravity/adapter.py +2 -2
  278. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +25 -5
  279. package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +2 -2
  280. package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +4 -1
  281. package/runtime/python/okstra_ctl/adapters/hosts/external/relay.md +4 -4
  282. package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +2 -2
  283. package/runtime/python/okstra_ctl/adapters/hosts/kimi/adapter.py +2 -2
  284. package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +79 -11
  285. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +100 -25
  286. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +59 -14
  287. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +55 -9
  288. package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +51 -12
  289. package/runtime/python/okstra_ctl/agent_activity.py +5 -1
  290. package/runtime/python/okstra_ctl/agent_invocation.py +608 -14
  291. package/runtime/python/okstra_ctl/agent_prompt_cli.py +249 -50
  292. package/runtime/python/okstra_ctl/application/resolve_assignment.py +53 -12
  293. package/runtime/python/okstra_ctl/assignment_environment.py +102 -0
  294. package/runtime/python/okstra_ctl/assignment_resolver.py +790 -0
  295. package/runtime/python/okstra_ctl/attempt_evidence.py +342 -0
  296. package/runtime/python/okstra_ctl/conformance.py +68 -0
  297. package/runtime/python/okstra_ctl/convergence.py +409 -3
  298. package/runtime/python/okstra_ctl/convergence_engine.py +248 -15
  299. package/runtime/python/okstra_ctl/convergence_store.py +170 -1
  300. package/runtime/python/okstra_ctl/dispatch_core.py +1181 -102
  301. package/runtime/python/okstra_ctl/dispatch_state.py +835 -22
  302. package/runtime/python/okstra_ctl/doctor.py +16 -2
  303. package/runtime/python/okstra_ctl/domain/host.py +32 -1
  304. package/runtime/python/okstra_ctl/domain/provider.py +212 -6
  305. package/runtime/python/okstra_ctl/domain/role.py +125 -0
  306. package/runtime/python/okstra_ctl/domain/wizard/interaction.py +13 -0
  307. package/runtime/python/okstra_ctl/domain/worker_exec.py +39 -1
  308. package/runtime/python/okstra_ctl/domain/worker_role.py +13 -3
  309. package/runtime/python/okstra_ctl/domain/worker_stream.py +24 -0
  310. package/runtime/python/okstra_ctl/error_log_write.py +9 -0
  311. package/runtime/python/okstra_ctl/error_report.py +21 -1
  312. package/runtime/python/okstra_ctl/execution_identity.py +529 -0
  313. package/runtime/python/okstra_ctl/execution_manifest.py +1281 -0
  314. package/runtime/python/okstra_ctl/execution_mutation_audit.py +619 -0
  315. package/runtime/python/okstra_ctl/final_report_schema.py +5 -4
  316. package/runtime/python/okstra_ctl/index.py +6 -2
  317. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +43 -3
  318. package/runtime/python/okstra_ctl/legacy_model_selection.py +490 -0
  319. package/runtime/python/okstra_ctl/model_cli.py +278 -0
  320. package/runtime/python/okstra_ctl/model_defaults.py +46 -0
  321. package/runtime/python/okstra_ctl/model_discovery.py +26 -7
  322. package/runtime/python/okstra_ctl/model_pool.py +501 -0
  323. package/runtime/python/okstra_ctl/models.py +47 -27
  324. package/runtime/python/okstra_ctl/mutation_recovery.py +293 -0
  325. package/runtime/python/okstra_ctl/pane_reclaim.py +13 -22
  326. package/runtime/python/okstra_ctl/pane_title.py +151 -0
  327. package/runtime/python/okstra_ctl/ports/host_model.py +48 -17
  328. package/runtime/python/okstra_ctl/ports/worker_dispatch.py +4 -0
  329. package/runtime/python/okstra_ctl/registry/provider_registry.py +8 -4
  330. package/runtime/python/okstra_ctl/render.py +78 -10
  331. package/runtime/python/okstra_ctl/render_final_report.py +34 -19
  332. package/runtime/python/okstra_ctl/report_contract.py +39 -1
  333. package/runtime/python/okstra_ctl/report_finalize.py +84 -9
  334. package/runtime/python/okstra_ctl/report_html/render.py +1 -0
  335. package/runtime/python/okstra_ctl/role_requirements.py +217 -0
  336. package/runtime/python/okstra_ctl/run.py +1250 -260
  337. package/runtime/python/okstra_ctl/run_index_row.py +7 -1
  338. package/runtime/python/okstra_ctl/schema_excerpt.py +1 -1
  339. package/runtime/python/okstra_ctl/scope_provenance.py +1 -1
  340. package/runtime/python/okstra_ctl/session.py +69 -12
  341. package/runtime/python/okstra_ctl/team.py +51 -25
  342. package/runtime/python/okstra_ctl/time_report.py +38 -4
  343. package/runtime/python/okstra_ctl/tmux.py +19 -149
  344. package/runtime/python/okstra_ctl/usage_identity.py +86 -0
  345. package/runtime/python/okstra_ctl/usage_report.py +15 -3
  346. package/runtime/python/okstra_ctl/wizard.py +1253 -62
  347. package/runtime/python/okstra_ctl/worker_audit_ledger.py +68 -65
  348. package/runtime/python/okstra_ctl/worker_request.py +9 -1
  349. package/runtime/python/okstra_ctl/worker_runner.py +130 -5
  350. package/runtime/python/okstra_ctl/workers.py +7 -5
  351. package/runtime/python/okstra_ctl/worktree.py +69 -3
  352. package/runtime/python/okstra_ctl/write_policy.py +571 -0
  353. package/runtime/python/okstra_token_usage/cli.py +1 -1
  354. package/runtime/python/okstra_token_usage/collect.py +66 -6
  355. package/runtime/schemas/convergence-groups-v2.0.schema.json +128 -0
  356. package/runtime/schemas/execution-manifest-v2.schema.json +340 -0
  357. package/runtime/schemas/final-report-v2.0.schema.json +25 -0
  358. package/runtime/skills/okstra-run/SKILL.md +9 -9
  359. package/runtime/skills/okstra-setup/references/project-config.md +11 -0
  360. package/runtime/templates/report-writer-prompt-preamble.md +2 -0
  361. package/runtime/templates/reports/final-report-v2.template.md +7 -1
  362. package/runtime/templates/reports/html/base.template.html +21 -0
  363. package/runtime/templates/reports/html/i18n/en.json +4 -0
  364. package/runtime/templates/reports/html/i18n/ko.json +4 -0
  365. package/runtime/templates/reports/settings.template.json +0 -24
  366. package/runtime/templates/worker-prompt-preamble.md +6 -2
  367. package/runtime/validators/lib/fixtures.sh +98 -33
  368. package/runtime/validators/validate-implementation-plan-stages.py +63 -3
  369. package/runtime/validators/validate-run.py +443 -508
  370. package/runtime/validators/validate_session_conformance.py +26 -8
  371. package/runtime/bin/okstra-subagent-reclaim.sh +0 -26
  372. package/runtime/schemas/final-report-v1.0.schema.json +0 -6366
  373. package/runtime/templates/reports/final-report.template.md +0 -1258
  374. package/src/cli-registry.mjs +0 -532
  375. package/src/commands/execute/codex-dispatch.mjs +0 -10
  376. package/src/commands/execute/plan-items.mjs +0 -9
  377. package/src/commands/execute/plan-validate.mjs +0 -68
  378. package/src/commands/execute/run.mjs +0 -208
  379. package/src/commands/execute/team.mjs +0 -63
  380. package/src/commands/execute/wizard.mjs +0 -167
  381. package/src/commands/inspect/stage-map.mjs +0 -98
  382. package/src/commands/inspect/task-list.mjs +0 -97
  383. package/src/commands/inspect/task-show.mjs +0 -100
  384. package/src/commands/lifecycle/check-project.mjs +0 -187
  385. package/src/commands/lifecycle/config.mjs +0 -385
  386. package/src/commands/lifecycle/doctor.mjs +0 -308
  387. package/src/commands/lifecycle/install.mjs +0 -1172
  388. package/src/commands/lifecycle/paths.mjs +0 -104
  389. package/src/commands/lifecycle/preflight.mjs +0 -125
  390. package/src/commands/lifecycle/setup.mjs +0 -333
  391. package/src/commands/lifecycle/uninstall.mjs +0 -279
  392. package/src/commands/memory/memory.mjs +0 -524
  393. package/src/commands/pr/pr.mjs +0 -261
  394. package/src/commands/report/finalize.mjs +0 -64
  395. package/src/lib/host-registry-client.mjs +0 -176
  396. package/src/lib/paths.mjs +0 -86
  397. package/src/lib/proc.mjs +0 -31
  398. package/src/lib/python-helper.mjs +0 -142
  399. package/src/lib/runtime-manifest.mjs +0 -70
  400. package/src/lib/skill-catalog.mjs +0 -53
  401. package/src/lib/version.mjs +0 -20
  402. /package/{src → dist}/commands/pr/default.md +0 -0
@@ -1,45 +1,45 @@
1
1
  #!/usr/bin/env bash
2
2
  #
3
- # okstra-trace-cleanup.sh — close tmux panes created during okstra runs.
3
+ # okstra-trace-cleanup.sh — close the harness-owned worker panes of an okstra run.
4
4
  #
5
- # Worker-compute panes are tmux-pane backend siblings. Their dispatcher tags
6
- # each pane it owns with a pane-level user option (`@okstra_worker_run=<RUN_DIR>`),
7
- # so panes are found server-wide by tag — no tmux env var or pane-id registry is
8
- # needed, and the run-scoped tag keeps concurrent okstra runs from closing each
9
- # other's panes.
5
+ # The panes this closes are the teammate panes the HARNESS splits for dispatched
6
+ # worker agents. okstra does not create them and cannot tag them, so they are
7
+ # found by a title allowlist scoped to the lead's window. They accumulate until
8
+ # the pane budget runs out, and `shutdown_request` only idles the agent without
9
+ # freeing its pane — this script is the only thing that closes them, which is why
10
+ # the lead calls it at every round boundary and before every user gate.
10
11
  #
11
- # Trace panes were `tail -F` siblings the provider wrappers split, tagged
12
- # `@okstra_trace_run` / `@okstra_status`. Those wrappers are now four-line
13
- # entrypoints and worker progress renders into the worker's own pane, so
14
- # NOTHING SPAWNS A TRACE PANE and neither tag has a writer left. The trace
15
- # paths below still run and simply match nothing; `--reclaim-completed`, which
16
- # keys on `@okstra_status`, is inert for the same reason. Kept rather than
17
- # deleted because the hooks that call this script are already seeded on user
18
- # machines — retiring the trace machinery is a deliberate follow-up.
12
+ # Two tag-driven paths used to live here and both are gone:
19
13
  #
20
- # Two invocation shapes:
14
+ # - Worker-compute panes of the `tmux-pane` dispatch backend, tagged
15
+ # `@okstra_worker_run`. That backend was removed — a worker now gets a cmux
16
+ # surface or a cli-wrapper subprocess, and neither is a tmux pane okstra owns.
17
+ # - Trace panes, tagged `@okstra_trace_run` / `@okstra_status`, which the
18
+ # provider wrappers used to split as `tail -F` siblings. The wrappers became
19
+ # four-line entrypoints and worker progress renders into the worker's own
20
+ # pane, so nothing had spawned a trace pane for some time.
21
21
  #
22
- # --run-dir <RUN_DIR> Used by the LEAD between phases and at wrap-up. Closes
23
- # (a) trace panes tagged with this run's dir and
24
- # (b) worker-agent panes the harness gives to dispatched
25
- # subagents (`claude-worker` / `codex-worker` /
26
- # `antigravity-worker` / `report-writer-worker`), identified
27
- # by a title allowlist scoped to the LEAD's WINDOW. The
28
- # lead pane is read from `<RUN_DIR>/state/lead-pane.id`
29
- # (recorded once by the lead in its own foreground pane —
30
- # reliable even though Claude Code's Bash tool strips
31
- # `$TMUX`/`$TMUX_PANE`); it scopes the title scan and is
32
- # NEVER killed. Window scope keeps a second lead running
33
- # in another window of the same session out of range.
22
+ # With both writers gone the tag scan matched nothing, so it was removed along
23
+ # with the two modes that existed only to drive it (`--reap`, which the SessionEnd
24
+ # hook called, and `--reclaim-completed`, which `okstra-subagent-reclaim.sh` drove
25
+ # from SubagentStop / TaskCompleted). Those hooks are gone from the seeded
26
+ # settings too.
34
27
  #
35
- # --reap Used by the `SessionEnd` hook, where no single run-dir
36
- # applies. Closes every trace pane whose tag points under
37
- # `$CLAUDE_PROJECT_DIR/.okstra/` (or every tagged trace
38
- # pane if that env var is unset). Harness-owned
39
- # worker-agent panes are left to the harness.
28
+ # Invocation:
29
+ #
30
+ # --run-dir <RUN_DIR> Closes the run's worker-agent panes (`claude-worker` /
31
+ # `codex-worker` / `antigravity-worker` /
32
+ # `report-writer-worker` and the FleetView teammate
33
+ # prefixes), scoped to the LEAD's WINDOW. The lead pane is
34
+ # read from `<RUN_DIR>/state/lead-pane.id` (recorded once
35
+ # by the lead in its own foreground pane — reliable even
36
+ # though Claude Code's Bash tool strips `$TMUX` /
37
+ # `$TMUX_PANE`); it scopes the scan and is NEVER killed.
38
+ # Window scope keeps a second lead running in another
39
+ # window of the same session out of range.
40
40
  #
41
41
  # `--list` (alias `--dry-run`) prints `<pane_id>\t<pane_title>` per pane instead
42
- # of killing — only meaningful with `--run-dir`.
42
+ # of killing.
43
43
  #
44
44
  # `--keep <substr>` (repeatable) spares any pane whose current title contains
45
45
  # <substr>, in both the kill and the list set. Used to preserve an in-flight
@@ -50,7 +50,7 @@
50
50
 
51
51
  set -u
52
52
 
53
- # Trace-pane caller resolution helper (okstra_resolve_caller_pane) — see
53
+ # Caller-pane resolution helper (okstra_resolve_caller_pane) — see
54
54
  # lib/okstra/tmux-pane.sh. Used as the lead-pane fallback below so a missing /
55
55
  # stale lead-pane.id resolves to the pane THIS process actually runs in (via
56
56
  # ancestor-PID ↔ tmux pane_pid matching), never a foreign active-client pane.
@@ -58,43 +58,26 @@ set -u
58
58
  _clean_script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
59
59
  [ -r "$_clean_script_dir/lib/okstra/tmux-pane.sh" ] && . "$_clean_script_dir/lib/okstra/tmux-pane.sh"
60
60
 
61
- # --reclaim-completed shells out to `python3 -m okstra_ctl.pane_reclaim`. The
62
- # package is a bin-sibling in the repo layout (scripts/okstra_ctl) and under
63
- # $OKSTRA_HOME/lib/python in the installed layout. Put both on PYTHONPATH so the
64
- # import resolves regardless of where this script runs from.
65
- _okstra_home="${OKSTRA_HOME:-$HOME/.okstra}"
66
- export PYTHONPATH="${_clean_script_dir}:${_okstra_home}/lib/python${PYTHONPATH:+:$PYTHONPATH}"
67
-
68
61
  MODE="kill" # kill | list
69
- RECLAIM=0 # 1: trace pane 은 @okstra_status 가 완료(exited)일 때만 회수 (--reclaim-completed)
70
- REAP=0
71
62
  run_dir=""
72
63
  KEEP_PATTERNS=() # --keep <substr>: panes whose title contains substr are spared from kill/list
73
64
  while [[ $# -gt 0 ]]; do
74
65
  case "$1" in
75
- --list|--dry-run) MODE="list" ;;
76
- --reclaim-completed) RECLAIM=1 ;;
77
- --reap) REAP=1 ;;
66
+ --list|--dry-run) MODE="list" ;;
78
67
  --run-dir) shift; run_dir="${1-}" ;;
79
68
  --run-dir=*) run_dir="${1#--run-dir=}" ;;
80
69
  --keep) shift; KEEP_PATTERNS+=("${1-}") ;;
81
70
  --keep=*) KEEP_PATTERNS+=("${1#--keep=}") ;;
82
71
  -h|--help)
83
72
  cat <<'USAGE'
84
- usage: okstra-trace-cleanup.sh (--run-dir <RUN_DIR> [--list] [--reclaim-completed] [--keep <substr>]... | --reap)
73
+ usage: okstra-trace-cleanup.sh --run-dir <RUN_DIR> [--list] [--keep <substr>]...
85
74
 
86
- --run-dir okstra run directory; closes that run's trace + worker-agent panes.
87
- --list with --run-dir: print "<pane_id>\t<pane_title>" per pane; no kill.
75
+ --run-dir okstra run directory; closes that run's worker-agent panes.
76
+ --list print "<pane_id>\t<pane_title>" per pane; no kill.
88
77
  --dry-run alias for --list.
89
78
  --keep <substr> exclude any pane whose title contains <substr> from the
90
79
  kill/list set (repeatable). Used to spare an in-flight
91
80
  report-writer at a round boundary.
92
- --reclaim-completed with --run-dir: restrict trace panes to those whose
93
- @okstra_status sidecar is terminal (stage=exited); in-flight
94
- and teammate panes are preserved. Skips the title-allowlist
95
- teammate scan. Combinable with --list.
96
- --reap close every okstra trace pane under $CLAUDE_PROJECT_DIR/.okstra
97
- (SessionEnd hook; no single run-dir applies).
98
81
  USAGE
99
82
  exit 0 ;;
100
83
  *)
@@ -104,29 +87,16 @@ USAGE
104
87
  shift
105
88
  done
106
89
 
107
- if [[ "$REAP" -eq 0 && -z "$run_dir" ]]; then
108
- printf 'okstra-trace-cleanup.sh: --run-dir <RUN_DIR> (or --reap) is required\n' >&2
90
+ if [[ -z "$run_dir" ]]; then
91
+ printf 'okstra-trace-cleanup.sh: --run-dir <RUN_DIR> is required\n' >&2
109
92
  exit 2
110
93
  fi
111
94
 
112
- # Canonicalize paths used in tag string-compares. The wrappers tag panes with
113
- # `pwd -P` (symlink-resolved), so the scope paths must be resolved the same way
114
- # — else a symlinked component (e.g. macOS /tmp -> /private/tmp) makes the
115
- # compare miss. Fall back to the literal value if the dir does not resolve.
116
- _resolve() { (cd "$1" 2>/dev/null && pwd -P) || printf '%s' "$1"; }
117
- [[ -n "$run_dir" ]] && run_dir="$(_resolve "$run_dir")"
118
- project_dir=""
119
- [[ -n "${CLAUDE_PROJECT_DIR:-}" ]] && project_dir="$(_resolve "$CLAUDE_PROJECT_DIR")"
120
-
121
- # Lead pane. For a run, prefer the value the lead recorded in its own foreground
122
- # pane; fall back to the active-pane probe. Rejected if the recorded pane is
123
- # gone. For --reap there is no run state — probe the active pane, used only to
124
- # avoid killing whatever pane the reap runs from.
95
+ # Lead pane. Prefer the value the lead recorded in its own foreground pane; fall
96
+ # back to the active-pane probe. Rejected if the recorded pane is gone.
125
97
  lead_pane=""
126
- if [[ "$REAP" -eq 0 ]]; then
127
- lead_pane_file="$run_dir/state/lead-pane.id"
128
- [[ -r "$lead_pane_file" ]] && lead_pane="$(head -n1 "$lead_pane_file" 2>/dev/null || true)"
129
- fi
98
+ lead_pane_file="$run_dir/state/lead-pane.id"
99
+ [[ -r "$lead_pane_file" ]] && lead_pane="$(head -n1 "$lead_pane_file" 2>/dev/null || true)"
130
100
  if [[ -z "$lead_pane" ]] || ! tmux display-message -p -t "$lead_pane" '#{pane_id}' >/dev/null 2>&1; then
131
101
  if type okstra_resolve_caller_pane >/dev/null 2>&1; then
132
102
  lead_pane="$(okstra_resolve_caller_pane 2>/dev/null || true)"
@@ -135,17 +105,6 @@ if [[ -z "$lead_pane" ]] || ! tmux display-message -p -t "$lead_pane" '#{pane_id
135
105
  fi
136
106
  fi
137
107
 
138
- # Does a trace pane's tag belong to the set we are closing?
139
- _tag_in_scope() {
140
- local tag="$1"
141
- if [[ "$REAP" -eq 1 ]]; then
142
- [[ -z "$tag" ]] && return 1
143
- [[ -n "$project_dir" ]] && { [[ "$tag" == "$project_dir/"* ]]; return; }
144
- return 0 # no project scope available → reap every tagged trace pane
145
- fi
146
- [[ "$tag" == "$run_dir" ]]
147
- }
148
-
149
108
  _title_in_okstra_scope() {
150
109
  local title="$1"
151
110
  case "$title" in
@@ -168,7 +127,6 @@ _title_in_okstra_scope() {
168
127
  }
169
128
 
170
129
  # A collected pane whose current title contains any --keep substring is spared.
171
- # Applied at the final emit so both the tag scan and the title scan honour it.
172
130
  _keep_excluded() {
173
131
  local pid="$1" title pat
174
132
  (( ${#KEEP_PATTERNS[@]} )) || return 1
@@ -181,54 +139,25 @@ _keep_excluded() {
181
139
 
182
140
  collect_okstra_panes() {
183
141
  local -a panes=()
184
- local pid trace_tag worker_tag status_tag title
185
-
186
- # (1) Trace and worker-compute panes tagged in scope — found server-wide by
187
- # tag, so no tmux env var or pane-id registry is needed. Each `@okstra_*`
188
- # column carries a leading `x` sentinel (stripped after read): tmux's `-F`
189
- # drops an UNSET user option AND its adjacent tab, which would otherwise
190
- # shift later columns left (e.g. an empty worker tag stealing the status
191
- # value). The sentinel keeps every column present so positional parsing holds.
192
- # `#x` strip 은 실제 태그 값을 깎지 않는다: 모든 태그 값(trace_run/worker_run/
193
- # status)은 절대경로라 `/` 로 시작 → `x` 로 시작하는 일이 없어 sentinel 만 벗겨진다.
194
- while IFS=$'\t' read -r pid trace_tag worker_tag status_tag; do
195
- trace_tag="${trace_tag#x}"; worker_tag="${worker_tag#x}"; status_tag="${status_tag#x}"
142
+ local pid title
143
+
144
+ # Title-allowlisted worker-agent panes in the lead's WINDOW. `list-panes -t
145
+ # <pane>` (no `-s`, no `-a`) resolves the window containing that pane and lists
146
+ # only that window's panes. Split-pane teammates always land in the lead's
147
+ # window, so this catches all of THIS run's worker panes while a second okstra
148
+ # lead in another window of the same tmux session is left untouched. Skipped
149
+ # when the lead pane is unknown.
150
+ [[ -n "$lead_pane" ]] || return 0
151
+ while IFS=$'\t' read -r pid title; do
196
152
  [[ -n "$pid" ]] || continue
197
153
  [[ "$pid" == "$lead_pane" ]] && continue
198
- if _tag_in_scope "$trace_tag" || _tag_in_scope "$worker_tag"; then
199
- if [[ "$RECLAIM" -eq 1 ]]; then
200
- # reclaim 모드: 완료(stage=exited)된 worker 의 pane 만. status 태그가
201
- # 없거나(teammate/비-wrapper pane) 미완료면 보존.
202
- [[ -n "$status_tag" ]] || continue
203
- python3 -m okstra_ctl.pane_reclaim "$status_tag" || continue
204
- fi
154
+ if _title_in_okstra_scope "$title"; then
205
155
  panes+=("$pid")
206
156
  fi
207
- done < <(tmux list-panes -a \
208
- -F '#{pane_id}'$'\t''x#{@okstra_trace_run}'$'\t''x#{@okstra_worker_run}'$'\t''x#{@okstra_status}' \
209
- 2>/dev/null || true)
210
- # (2) Title-allowlisted worker-agent panes in the lead's WINDOW. Only for a
211
- # run (reap leaves these harness-owned panes to the harness). `list-panes -t
212
- # <pane>` (no `-s`, no `-a`) resolves the window containing that pane and
213
- # lists only that window's panes. Split-pane teammates always land in the
214
- # lead's window, so this catches all of THIS run's worker panes while a second
215
- # okstra lead in another window of the same tmux session — whose in-flight
216
- # worker panes are untagged and title-only — is left untouched. Skipped when
217
- # the lead pane is unknown. reclaim 모드는 teammate pane 을 회수하지 않으므로
218
- # (완료 판정 불가, trace-only) 이 스캔을 건너뛴다.
219
- if [[ "$REAP" -eq 0 && "$RECLAIM" -eq 0 && -n "$lead_pane" ]]; then
220
- while IFS=$'\t' read -r pid title; do
221
- [[ -n "$pid" ]] || continue
222
- [[ "$pid" == "$lead_pane" ]] && continue
223
- if _title_in_okstra_scope "$title"; then
224
- panes+=("$pid")
225
- fi
226
- done < <(tmux list-panes -t "$lead_pane" \
227
- -F '#{pane_id}'$'\t''#{pane_title}' 2>/dev/null || true)
228
- fi
157
+ done < <(tmux list-panes -t "$lead_pane" \
158
+ -F '#{pane_id}'$'\t''#{pane_title}' 2>/dev/null || true)
229
159
 
230
- # Dedupe — a live trace pane can match both the tag scan and the title scan.
231
- # Then drop any pane a --keep pattern spares (in-flight report-writer).
160
+ # Drop any pane a --keep pattern spares (in-flight report-writer).
232
161
  if (( ${#panes[@]} )); then
233
162
  printf '%s\n' "${panes[@]}" | awk 'NF && !seen[$0]++' | while IFS= read -r _pid; do
234
163
  _keep_excluded "$_pid" && continue
@@ -222,6 +222,16 @@ PY_ARGS=(
222
222
  [[ -n "${DIRECTIVE-}" ]] && PY_ARGS+=(--directive "$DIRECTIVE")
223
223
  [[ -n "${FIX_CYCLE-}" ]] && PY_ARGS+=(--fix-cycle "$FIX_CYCLE")
224
224
  [[ -n "${WORKERS_OVERRIDE-}" ]] && PY_ARGS+=(--workers "$WORKERS_OVERRIDE")
225
+ for role_count in "${ROLE_COUNTS[@]-}"; do
226
+ [[ -z "$role_count" ]] && continue
227
+ PY_ARGS+=(--role-count "$role_count")
228
+ done
229
+ for role_model in "${ROLE_MODELS[@]-}"; do
230
+ [[ -z "$role_model" ]] && continue
231
+ PY_ARGS+=(--role-model "$role_model")
232
+ done
233
+ [[ -n "${HOST_SESSION_CONTEXT_JSON-}" ]] && \
234
+ PY_ARGS+=(--host-session-context-json "$HOST_SESSION_CONTEXT_JSON")
225
235
  [[ -n "${LEAD_PROVIDER_OVERRIDE-}" ]] && PY_ARGS+=(--lead-provider "$LEAD_PROVIDER_OVERRIDE")
226
236
  [[ -n "${LEAD_MODEL_OVERRIDE-}" ]] && PY_ARGS+=(--lead-model "$LEAD_MODEL_OVERRIDE")
227
237
  [[ -n "${CLAUDE_MODEL_OVERRIDE-}" ]] && PY_ARGS+=(--claude-model "$CLAUDE_MODEL_OVERRIDE")
@@ -34,6 +34,8 @@ It overrides only the worker-dispatch portion of the selected host relay, not th
34
34
  | `record_lead_event` | Append progress and activity records to the manifest-provided `leadEventsPath`. Emit the matching `PROGRESS:` line and, when an activity record is required, the immediately following `ACTIVITY:` line from the same structured fields. |
35
35
  | `collect_usage` | Collect artifact/CLI-log-backed usage through the existing Okstra token-usage path; never substitute another runtime's session log. |
36
36
 
37
+ An `implementation` run calls `dispatch_worker` twice: once for the Executor, then — after `await_workers` settles it — once for the verifiers. That second call's `--workers` list must omit the Executor's worker ID: it is materialized as the Executor on every dispatch, so a batch still carrying it is refused again. A verifier started beside the Executor observes base HEAD instead of the stage diff, so a single batch holding both is refused by `scripts/okstra_ctl/dispatch_core.py` `_validate_implementation_phase_order`, `--dry-run` included.
38
+
37
39
  ## Pane placement is not yours to compute
38
40
 
39
41
  Okstra creates, sizes, labels, and closes every worker pane. Do not issue terminal-multiplexer commands of any kind — not to place a worker, not to resize the lead, not to reclaim a finished round. Pane geometry depends on the display, and a lead that recomputes it per run gets it wrong differently on every host.
@@ -84,7 +84,7 @@ Read the worker result files generated in Phase 4/5 and extract individual findi
84
84
  - Same semantics but disjoint ticket sets → separate groups (do NOT over-merge across tickets).
85
85
  - Only one worker confirms a finding → one single-source group.
86
86
  4. When grouping is ambiguous, prefer splitting over merging (avoid over-merging). Semantic matching, ticket-set equality, and evidence interpretation remain lead judgments; the engine does not perform fuzzy matching or decide whether evidence is credible.
87
- 5. Write `runs/<task-type>/state/convergence-groups-<task-type>-<seq>.json`. Each group carries its `ticketIds`, `originWorker`, `originEvidence`, `discoveredBy`, and every `<worker>:<item-id>` source in `sourceItems`. For analysis sidetracks where ticket tagging is not required, `ticketIds: []` is the canonical value; never synthesize `"unknown"` or another placeholder. `scripts/okstra_ctl/convergence_engine.py` and `schemas/convergence-groups-v1.0.schema.json` enforce the required array field and reject non-string or blank entries while allowing the empty array. When a live command or external read produced reproducible evidence, also include `evidenceArtifacts[]` with its `.okstra/` path, SHA-256 digest, command, and environment. The field is optional because historical or inaccessible evidence may not have a captured artifact. The lead and verifier MUST NOT infer live or external evidence from wording or keyword matching; they use the finding's explicit claim, provenance, and supplied artifacts. Include the resolved worker roster in order with functional `audience` values; do not derive scope from provider or model identity. The `audience` enum is a convergence role, not a phase label: every finding-producing worker uses `analysis` — an `implementation` run's verifiers included — and only the report author uses `report-writer`. There is no `implementation-verifier` audience here; map the verifier roster to `analysis`.
87
+ 5. Write `runs/<task-type>/state/convergence-groups-<task-type>-<seq>.json`. Each group carries its `ticketIds`, `originWorker`, `originEvidence`, `discoveredBy`, and every `<worker>:<item-id>` source in `sourceItems`. For analysis sidetracks where ticket tagging is not required, `ticketIds: []` is the canonical value; never synthesize `"unknown"` or another placeholder. `scripts/okstra_ctl/convergence_engine.py` and the version-selected convergence-groups schema enforce the required array field and reject non-string or blank entries while allowing the empty array. When a live command or external read produced reproducible evidence, also include `evidenceArtifacts[]` with its `.okstra/` path, SHA-256 digest, command, and environment. The field is optional because historical or inaccessible evidence may not have a captured artifact. The lead and verifier MUST NOT infer live or external evidence from wording or keyword matching; they use the finding's explicit claim, provenance, and supplied artifacts. Include the resolved worker roster in order with functional `audience` values; do not derive scope from provider or model identity. In a v2 run, set top-level `schemaVersion: "2.0"`, `executionIdentityVersion: 2`, and `runManifestPath` to the current run manifest's exact canonical project-relative path. Write the groups artifact under that same resolved run directory's `state/` directory. Every v2 worker row also carries the paired `participantRef` and `sourceRoleExecutionRef` from that run manifest's canonical role state. Set `sourceRoleExecutionRef` to the selected source `RoleExecution` row's `roleExecutionRef`, not that row's `sourceRoleExecutionRef` field; a static source row has null in the latter field. Copy `participantRef` from that same selected row. Never derive those references from the worker name, provider, model, or execution label. A legacy v1 document keeps `schemaVersion: "1.0"` and omits `executionIdentityVersion`, `runManifestPath`, and both worker reference fields. The `audience` enum is a convergence role, not a phase label: every finding-producing worker uses `analysis` and selects an `analyser`, `designer`, `planner`, or `verifier` source role. Only the report author uses `report-writer`, paired with a `report-writer` source role. There is no `implementation-verifier` audience here; map an implementation verifier to `analysis`.
88
88
  6. Do not write a queue or classification in this grouped-input artifact. `okstra convergence seed` classifies Round 0 by mode:
89
89
  - Collaborative mode: multi-source groups become `full-consensus` immediately; only single-source groups enter the working queue.
90
90
  - Adversarial mode: every finding enters the working queue regardless of source count. Semantic grouping merges provenance only; it does not decide a finding is reliable.
@@ -106,8 +106,8 @@ convergence-<task-type>-<seq>.json
106
106
 
107
107
  Follow this protocol exactly:
108
108
 
109
- 0. Two schemas describe what the reducer reads: `schemas/convergence-groups-v1.0.schema.json` feeds step 1's `seed --groups`, and `schemas/convergence-round-results-v1.0.schema.json` feeds step 4's `apply-round --results`. `schemas/convergence-critic-results-v1.0.schema.json` is a third shape but **not** a reducer input — it describes the critic worker's own result document. Step 6's `apply-critic-gaps --results` takes the coverage batch you assemble from those candidates plus each analyser's vote (`{schemaVersion, taskKey, mode, provider, modelExecutionValue, dispatches[], gaps[]}`, spelled out in §"Coverage critic pass" §"State"); feeding the critic document straight in is rejected, by design. `okstra convergence example --kind <groups|round-results|critic-results>` prints a deterministic valid instance of each and writes only JSON to stdout.
110
- 1. Run `okstra convergence seed --groups <groups> --work-state <work> --final-state <final> --migration-dir <state/migrations>`. A `reuse-final` action means validate the existing final and continue to Phase 6. `create-work`, `resume-work`, and `restart-round0` continue with planning.
109
+ 0. Version-selected schemas describe what the reducer reads: `schemas/convergence-groups-v1.0.schema.json` accepts only legacy groups, `schemas/convergence-groups-v2.0.schema.json` accepts only explicit v2 execution identity, and `schemas/convergence-round-results-v1.0.schema.json` feeds step 4's `apply-round --results`. `schemas/convergence-critic-results-v1.0.schema.json` is a fourth shape but **not** a reducer input — it describes the critic worker's own result document. Step 6's `apply-critic-gaps --results` takes the coverage batch you assemble from those candidates plus each analyser's vote (`{schemaVersion, taskKey, mode, provider, modelExecutionValue, dispatches[], gaps[]}`, spelled out in §"Coverage critic pass" §"State"); feeding the critic document straight in is rejected, by design. `okstra convergence example --kind <groups|round-results|critic-results>` prints a deterministic valid v1 instance of each and writes only JSON to stdout.
110
+ 1. Run `okstra convergence seed --groups <groups> --run-manifest <current-run-manifest> --work-state <work> --final-state <final> --migration-dir <state/migrations>` for a v2 worker roster. `--run-manifest` must be the exact current manifest named by the groups document's `runManifestPath`; a previous run from the same task is not interchangeable. Omit the flag for a legacy v1 roster. A `reuse-final` action means validate the existing final and continue to Phase 6. `create-work`, `resume-work`, and `restart-round0` continue with planning.
111
111
  2. Run `okstra convergence plan-round --work-state <work> --plan <round-plan>`. This is read-only with respect to the working state.
112
112
  3. When the plan action is `dispatch`, create exactly one reverify prompt for each `dispatches[]` row and dispatch it through the selected runtime adapter. Its findings are exactly that row's `findingIds`.
113
113
  4. Convert parsed verdicts and every terminal dispatch outcome into `convergence-round-<N>-results-<task-type>-<seq>.json`; then run `okstra convergence apply-round --work-state <work> --plan <round-plan> --results <round-results>`.
@@ -228,21 +228,30 @@ Design intent: one `counter-evidence` refute denies a claim consensus (it cannot
228
228
 
229
229
  For every finding reverify row and critic-gap verification row, first write a
230
230
  call-specific task-instructions file under the current run's `state/`
231
- directory. Then run `okstra agent-prompt materialize` with `--audience
232
- reverification-worker`, `--assignment-ref reverify/<workerId>`, the exact
233
- `--worker-id`, `--dispatch-kind reverify-r<N>`, and the authorized
234
- prompt/result/audit paths. The returned `promptPath` is the only body that may
235
- be dispatched; do not append role prose or reconstruct model headers after
236
- materialization.
237
-
238
- If the dispatch gate then rejects that prompt, fix the task-instructions file
239
- and re-run the same `materialize` call with `--replace-undispatched` — keep the
240
- `--invocation-id`. A published prompt is otherwise immutable, so without that
241
- flag the retry fails as `existing_invocation_conflict`; do NOT delete the
242
- reservation under `prompts/.agent-invocations` and do NOT mint a second
243
- invocation id to get around it, because both detach the audit chain from the
244
- call it describes. The flag is checked: once any dispatch row names this
245
- invocation, the prompt is history and the replacement is refused.
231
+ directory. For a v2 run, take `participantRef` and
232
+ `sourceRoleExecutionRef` from the canonical round-plan dispatch row or the
233
+ matching canonical working-state worker row. That stored reference is the
234
+ selected source `RoleExecution` row's `roleExecutionRef`, not that row's
235
+ `sourceRoleExecutionRef` field. Then run `okstra agent-prompt
236
+ materialize` with `--audience
237
+ reverification-worker`, `--assignment-ref reverify/<workerId>`,
238
+ `--source-role-execution-ref
239
+ <sourceRoleExecutionRef>`, the exact `--worker-id`, `--dispatch-kind
240
+ reverify-r<N>`, and the authorized prompt/result/audit paths. Do not derive the
241
+ source role execution from `workerId`, provider, model, or execution-label
242
+ text. A legacy v1 run omits `--source-role-execution-ref`; the materializer
243
+ keeps its legacy read-only identity resolution for that schema version. The
244
+ returned `promptPath` is the only body that may be dispatched; do not append
245
+ role prose or reconstruct model headers after materialization.
246
+
247
+ If the dispatch gate then rejects that prompt, preserve its prompt, metadata,
248
+ reservation, and append-only Invocation bytes. A v2 reverify correction uses a
249
+ fresh `--invocation-id` and fresh prompt/metadata path; the materializer rejects
250
+ `--replace-undispatched` for this dynamic identity path. A legacy v1 correction
251
+ may fix the task-instructions file and re-run the same `materialize` call with
252
+ `--replace-undispatched` while keeping the `--invocation-id`. Once any dispatch
253
+ row names the invocation, replacement is rejected even for v1 and the prompt is
254
+ history. Do not delete prompt, metadata, or reservation files by hand.
246
255
 
247
256
  Run `okstra agent-prompt verify --run-manifest <path> --metadata
248
257
  <metadataPath> --json` immediately before dispatch. A failed verification is a
@@ -44,7 +44,9 @@ Read-side inspection (`/okstra-inspect`) and scheduling (`/okstra-schedule-gen`)
44
44
 
45
45
  ## Core operating contract
46
46
 
47
- - The `lead` owns orchestration, convergence supervision, and final-report review/approval. It does not author the final-report file when `Report writer worker` is in the roster.
47
+ - The `leader` owns orchestration, convergence supervision, and final-report review/approval. It does not author the final-report file when `Report writer worker` is in the roster. `lead` is a compatibility alias for `leader` and must not be written on new artifacts.
48
+ - Dispatch consumes stored role executions, not provider-named worker IDs. Canonical roles are `leader`, `analyser`, `critic`, `designer`, `planner`, `implementer`, `verifier`, `report-writer`, and `translator`. `executor` is a compatibility alias for `implementer`.
49
+ - Pane titles and operational rows use the stored `executionLabel`. Do not rebuild that label from a provider name or model string.
48
50
  - Standard worker roles remain `Claude worker`, `Codex worker`, `Antigravity worker`, and `Report writer worker`; these are provider/functional-role identities, not lead-runtime primitives.
49
51
  - `Report writer worker`, when in the roster, is the **author** of the final-report file. Lead reviews the draft and may request a revision via a follow-up dispatch, but MUST NOT write the report itself as a "shortcut". The only legal lead-authored fallback is when a Report writer worker dispatch was actually attempted and recorded a terminal status of `error`/`timeout`/`not-run` with an explicit reason in team-state — see [report-writer](./report-writer.md) "Lead-authored fallback".
50
52
  - "Session resume", "team is no longer alive", and similar are NOT valid reasons to skip Report writer worker dispatch — see [report-writer](./report-writer.md) "Resume-safe dispatch".
@@ -181,7 +183,7 @@ For `--task-type implementation` runs, the task bundle additionally pins one of
181
183
  - `instruction-set/analysis-profile.md` — top "Executor binding" block (provider, display name, model, runner, and dispatch mode)
182
184
  - `runs/implementation/manifests/run-manifest-*.json` — `teamContract.executor` object (the same binding plus `appliesTo: "implementation"`)
183
185
 
184
- Lead MUST dispatch Edit/Write-bearing work only through that executor binding: use the host primitive with `hostModelValue` for `runner=native-session`, or `okstra worker-dispatch` with `modelExecutionValue` for `runner=cli-wrapper`. The other two providers still run as read-only verifiers in the same run; the executor's own provider is *also* dispatched separately as a verifier in a fresh session, so the diff is reviewed context-isolated. Session isolation is the primary self-review safeguard — same-model executor and same-provider verifier is acceptable in distinct sessions. A different model variant (e.g. executor=opus / Claude verifier=sonnet) is recommended but not mandatory.
186
+ Lead MUST dispatch Edit/Write-bearing work only through that executor binding: use the host primitive with `hostModelValue` for `runner=native-session`, or `okstra worker-dispatch` with `modelExecutionValue` for `runner=cli-wrapper`. The other providers in the roster still run as read-only verifiers in the same run; the executor's own provider does not, because its worker ID materializes as the executor on every dispatch — so the diff is reviewed context-isolated by the remaining verifiers. Session isolation is the primary self-review safeguard — a verifier reusing the executor's model variant is acceptable in a distinct session. A different model variant (e.g. executor=opus / Claude verifier=sonnet) is recommended but not mandatory.
185
187
 
186
188
  Executor is chosen at run-prep time via `--executor <claude|codex|antigravity>` (or `OKSTRA_DEFAULT_EXECUTOR`, fallback `claude`); the model used by the executor is taken from the corresponding worker model flag (`--claude-model` / `--codex-model` / `--antigravity-model`). For CLI-backed executors, the underlying file mutation happens inside the executor CLI's own auto-edit mode (e.g. `codex exec --sandbox workspace-write`), not through the lead runtime's `write_artifact` operation.
187
189
 
@@ -53,7 +53,7 @@ The shared Majority definition and the auto-disable rule (fewer than 2 analyser
53
53
 
54
54
  ## Plan-item extraction (Round 0 equivalent)
55
55
 
56
- From the report-writer's draft of `## 5.4 Implementation Plan Deliverables`, the lead creates the verification queue only through this sequence (see also `templates/reports/final-report.template.md` §5.5.9):
56
+ From the report-writer's draft of `## 5.4 Implementation Plan Deliverables`, the lead creates the verification queue only through this sequence (see also `templates/reports/final-report-v2.template.md` §5.5.9):
57
57
 
58
58
  ```text
59
59
  okstra plan-items extract --data <data.json> --output <state>/plan-items-....json
@@ -124,7 +124,7 @@ The verdict tokens `AGREE` / `DISAGREE` / `SUPPLEMENT` are reused, but their mea
124
124
  `P-Prep-S<stage>-<kind>` applies the same verdict tokens and adds these disposition checks:
125
125
 
126
126
  - `inline-contract`: AGREE only when the contract contains the kind-specific fields needed to implement the stage; a label or generic prose is insufficient.
127
- - `prep-item`: AGREE only when every referenced PREP item has a concrete `aiProposal`, correct `stageRefs`/`kind`, and state-specific safeguards required by `schemas/final-report-v1.0.schema.json`.
127
+ - `prep-item`: AGREE only when every referenced PREP item has a concrete `aiProposal`, correct `stageRefs`/`kind`, and state-specific safeguards required by `schemas/final-report-v2.0.schema.json`.
128
128
  - `not-applicable`: AGREE only when the rationale is consistent with the stage action; otherwise DISAGREE with `fixability` (`planner-fixable` when the plan can supply the missing contract, `needs-user-input` only for genuinely external facts).
129
129
  - A declared `blocked` item is not itself a plan-body failure. Missing or duplicate coverage, an empty proposal, a mismatched reference, or an unjustified disposition is a failure and receives `DISAGREE(<kind>)` with `fixability`.
130
130
 
@@ -202,7 +202,14 @@ Before each verifier call, write one task-instructions file under the current
202
202
  run's `state/` directory and run `okstra agent-prompt materialize` with
203
203
  `--audience reverification-worker`,
204
204
  `--assignment-ref reverify/<workerId>`, the exact `--worker-id`, and
205
- `--dispatch-kind reverify-r<N>`. Run `okstra agent-prompt verify` against the
205
+ `--dispatch-kind reverify-r<N>`. For a v2 run, select the verifier's canonical
206
+ source `RoleExecution` row from the run manifest's static role state. Use its
207
+ `participantRef`, and set `sourceRoleExecutionRef` to the selected source
208
+ `RoleExecution` row's `roleExecutionRef`, not that row's
209
+ `sourceRoleExecutionRef` field. Pass `--source-role-execution-ref
210
+ <sourceRoleExecutionRef>`. Do not derive that reference from `workerId`,
211
+ provider, model, or execution-label text. A legacy v1 run omits this flag. Run
212
+ `okstra agent-prompt verify` against the
206
213
  returned `metadataPath` before dispatch and use the returned `promptPath`
207
214
  without modification. Native-session calls use only `hostModelValue`; before
208
215
  the host primitive, run `okstra agent-prompt record-dispatch` with the project
@@ -254,7 +261,7 @@ round before any host or provider process starts.
254
261
 
255
262
  **How the corrective round is recorded.** The first prompt was dispatched, so it is immutable — `--replace-undispatched` refuses it, correctly. Materialize the correction under a NEW `--invocation-id` and a new prompt path. Before linking its result, retire the first attempt's link: `okstra agent-prompt reject-result --run-manifest <path> --dispatch-id <first dispatch id> --superseded-by <corrective dispatch id> --reason "<what was wrong with the returned result>"`. Without that step the corrective `link-result` fails with `agent result is already linked to another dispatch`, which is how a worker that ran for twenty minutes and wrote a good result ends up unrecordable. Nothing is deleted: the rejected link stays in `agentResultLinks` carrying `supersededBy` and `rejectionReason`, so the ledger shows both attempts and why the second exists.
256
263
 
257
- Then lead writes `runs/<task-type>/state/plan-body-verification-<task-type>-<seq>.json` (schema below), **appending this round** — one new `roundHistory[]` entry plus this round's votes on each verified item's `planItems[].rounds[]`. The file accumulates across rounds; it is never truncated to the latest one. After `okstra plan-verify` exits 0, lead sets that new round's `completedAt` to the current ISO 8601 UTC time exactly once; a prior round's `completedAt` is immutable. Lead then populates `### 5.5.9 Plan Body Verification` in the final report's data.json (`implementationPlanning.planBodyVerification`, schema `schemas/final-report-v1.0.schema.json`; template at `templates/reports/final-report.template.md`). The §5.5.9 body is **grouped by plan item**: `planItems[]`, each carrying its `id`, its plain-language `subject` (rendered as the item heading), an optional `sourceSection`, an optional `clarificationId` (the `C-<N>` this item blocks on when `majority-disagree`), and a `verdicts[]` list (`worker / verdict / breakageKind / note`) — one verdict row per worker under that item. The renderer prints three fixed legends (gate values, verdict tokens, breakage kinds a–f) so the reader can decode every cell without opening this spec. The older flat `#### Verdict details` table (`Plan item / Worker / …`, one row per plan-item × worker pair) is superseded by the grouped layout — it hid *what* each vote was about behind a bare `P-*` ID; the subject heading is the fix. The validator's `Plan Body Verification` + `Gate result:` substring checks still gate this section.
264
+ Then lead writes `runs/<task-type>/state/plan-body-verification-<task-type>-<seq>.json` (schema below), **appending this round** — one new `roundHistory[]` entry plus this round's votes on each verified item's `planItems[].rounds[]`. The file accumulates across rounds; it is never truncated to the latest one. After `okstra plan-verify` exits 0, lead sets that new round's `completedAt` to the current ISO 8601 UTC time exactly once; a prior round's `completedAt` is immutable. Lead then populates `### 5.5.9 Plan Body Verification` in the final report's data.json (`implementationPlanning.planBodyVerification`, schema `schemas/final-report-v2.0.schema.json`; template at `templates/reports/final-report-v2.template.md`). The §5.5.9 body is **grouped by plan item**: `planItems[]`, each carrying its `id`, its plain-language `subject` (rendered as the item heading), an optional `sourceSection`, an optional `clarificationId` (the `C-<N>` this item blocks on when `majority-disagree`), and a `verdicts[]` list (`worker / verdict / breakageKind / note`) — one verdict row per worker under that item. The renderer prints three fixed legends (gate values, verdict tokens, breakage kinds a–f) so the reader can decode every cell without opening this spec. The older flat `#### Verdict details` table (`Plan item / Worker / …`, one row per plan-item × worker pair) is superseded by the grouped layout — it hid *what* each vote was about behind a bare `P-*` ID; the subject heading is the fix. The validator's `Plan Body Verification` + `Gate result:` substring checks still gate this section.
258
265
  7. **Self-fix loop (one rewrite, targeting planner-fixable defects).** After round 1, lead may run one report-writer rewrite when at least one `majority-disagree` item has a majority of its `DISAGREE` verdicts at `fixability == planner-fixable`. The targeted re-verification after that rewrite is round 2. After round 2, stop automatic self-fix regardless of outcome. Classify every remaining item as `user-decision`, `noncritical-dissent`, or `correctness-critical`. A second automatic self-fix is a contract violation. The fixed order is initial verification → one planner self-fix → targeted re-verification → user gate.
259
266
  - **Group the targets by cause before instructing (BLOCKING).** Blocked items are usually several derivatives of one defect — one constant declared twice, one responsibility given two owners — and the coverage rows that cite them fail as a consequence, not independently. Lead MUST partition this round's targets into cause groups and instruct each group as **"remove this cause"**, naming the derivatives it accounts for. **Handing report-writer a bare item list is forbidden**: patched one at a time, each correction leaves the sibling sections still asserting the old value, so the next round re-finds the same family and the budget drains without converging. Record the partition in `planBodyVerification.selfFixGroups[]` (`round`, `causeSummary`, `itemIds`). One group per item is a legitimate outcome only when the items genuinely share no cause — recorded that way, it is a visible diagnosis rather than a skipped one. **Enforced:** `validators/validate-run.py` `_validate_self_fix_grouping` requires the partition, ties `selfFixRoundsApplied` to the highest recorded round, and fails any corrected item that belongs to no group.
260
267
  - lead instructs report-writer to rewrite the items in each cause group (NOT a full draft regeneration; procedure in [report-writer](./report-writer.md) §"Self-fix rewrite").
@@ -268,9 +275,9 @@ round before any host or provider process starts.
268
275
  - **Round completion.** A round is complete only after the renderer has run on the corrected data.json, lead has appended the round to the state file per step 6, lead has reconciled instructed groups against applied corrections — every `itemIds` entry either carries a `selfFixNote` or is still recorded as broken — **`okstra plan-verify --report <report>` exits 0** (step 5), and lead has then set that round's immutable `completedAt`. A round left with a non-zero exit carries its defect into the next round's inputs, which is how a mis-scored gate survives a whole self-fix budget. A round that was instructed but never rendered has not happened, and counting it inflates the budget that gates promotion. The state-file append is not optional bookkeeping: the next re-verification overwrites data.json's `planItems[].verdicts`, so a round that never reached `roundHistory[]` leaves no record anywhere of what it blocked on — which is the whole reason this file exists. **Enforced:** `validators/validate-run.py` `_validate_plan_body_state_rounds` requires one `roundHistory[]` entry per round `1..roundCount`, each carrying its own `gateResult` and cited by at least one item's `rounds[]`, and requires the file's `selfFixRoundsApplied` to match the report's. For a resolved correctness-critical user response, `_validate_target_round_causality` additionally requires the cited round's `completedAt` to be after every linked canonical `user-decision-required` event and no later than every linked canonical `user-decision-evaluated` event.
269
276
  - **Loop termination.** Lead — not the report-writer worker — records the round count in `planBodyVerification.selfFixRoundsApplied` at each round's end, and why the loop stopped in `planBodyVerification.selfFixStopReason`. The count must equal the highest `round` in `selfFixGroups[]`, so it is derivable from recorded work rather than self-reported. A user-directed correction does not consume the automatic self-fix limit, and a verification failure after that correction does not restart the automatic loop:
270
277
  - `all-resolved` — no planner-fixable `majority-disagree` item remains. Exit.
271
- - `no-progress` — the round resolved **zero** planner-fixable items relative to the previous round. Exit even with budget left: the same rewrite would repeat. Newly *introduced* defects count against progress, so a rewrite that trades one defect for another stops the loop rather than churning.
278
+ - `no-progress` — the round resolved **zero** planner-fixable items relative to the previous round. Exit even with budget left: the same rewrite would repeat. Newly *introduced* defects count against progress, so a rewrite that trades one defect for another stops the loop rather than churning. A round that re-targets only what the previous round left unresolved is this same conclusion reached one dispatch earlier — exit on it under this reason rather than paying for the round that proves it. **Enforced (advisory):** `validators/validate-run.py` `_detect_self_fix_recurrence` warns on that shape and names this stop reason.
272
279
  - `max-rounds-reached` — `selfFixRoundsApplied == selfFixMaxRounds`. Exit.
273
- - `cause-group-recurrence` — legacy read-only value for reports produced before activity contract v1. A new activity-contract-v1 run MUST NOT emit it because there is no second automatic self-fix round in which a cause group can recur. **Enforced:** `validators/validate-run.py` `_validate_activity_contract_plan_limits`.
280
+ - `cause-group-recurrence` — legacy read-only value for reports produced before activity contract v1. A new activity-contract-v1 run MUST NOT emit it because there is no second automatic self-fix round in which a cause group can recur. **Enforced:** `validators/validate-run.py` `_validate_activity_contract_plan_limits`. A legacy report that already carries it is still an *exhausted* loop, so step 8 may promote its surviving items: `_SELF_FIX_EXHAUSTED_REASONS` admits this value alongside `no-progress` / `max-rounds-reached`. Refusing it there left such a report with no exit at all — the loop may not run again, and the item may not be promoted either.
274
281
  - `not-attempted` — the loop never ran because no item qualified.
275
282
  The `no-progress` and `max-rounds-reached` exits are what make the loop terminate; `selfFixMaxRounds` alone is the backstop.
276
283
  - a `majority-disagree` item with a majority of its deciding `DISAGREE` votes at `needs-user-input` is NOT a self-fix target — after correctness-critical precedence, it goes straight to the next step as `user-decision` rather than generic `noncritical-dissent`. **Enforced:** `validators/validate-run.py` `_expected_approval_classification`.
@@ -261,17 +261,17 @@ For those v1 reports, the final report must contain section headings whose **lin
261
261
  | 7 | `Validation Checklist` | `### Validation Checklist (Korean gloss)` |
262
262
  | 8 | `Rollback` | `### Rollback Strategy (Korean gloss)` |
263
263
  | 9 | `Requirement Coverage` | `### Requirement Coverage (Korean gloss)` |
264
- | 10 | `Plan Body Verification` + `Gate result:` | `### Plan Body Verification (Korean gloss)` containing a `Gate result:` line — copy `templates/reports/final-report.template.md §5.5.9` verbatim. Validator checks both substrings. |
264
+ | 10 | `Plan Body Verification` + `Gate result:` | `### Plan Body Verification (Korean gloss)` containing a `Gate result:` line — copy `templates/reports/final-report-v2.template.md §5.5.9` verbatim. Validator checks both substrings. |
265
265
  | 11 | `Cross-Project Dependencies` | `### Cross-Project Dependencies (Korean gloss)` — `_(none)_` when the plan is single-project. |
266
266
  | 12 | `Decision Drafts` | `### Decision Drafts (Korean gloss)` |
267
267
 
268
268
  **Never author a `User Approval Request` section.** The approval gate moved to the YAML frontmatter `approved: true|false` field, and `validators/validate-run.py` fails any report containing a `## User Approval Request` block or a `### 5.5.8 User Approval Request` stub. This paragraph replaces an earlier row that listed the heading as required — following it produced a report the validator rejected.
269
269
 
270
- `Implementation Design Preparation` is NOT in the substring list: `templates/reports/final-report.template.md` §5.5.10 renders that heading from `implementationPlanning.designPreparation`, so it is enforced by the schema + renderer rather than by a heading scan.
270
+ `Implementation Design Preparation` is NOT in the substring list: `templates/reports/final-report-v2.template.md` §5.5.10 renders that heading from `implementationPlanning.designPreparation`, so it is enforced by the schema + renderer rather than by a heading scan.
271
271
 
272
272
  The English keyword is mandatory and the body of each section is written in English, like everything else you author — see "Writing Guidelines" below. For non-`implementation-planning` runs, omit this entire block — these headings are NOT validator-checked for other task-types.
273
273
 
274
- The final-report template `templates/reports/final-report.template.md` Section 5.5 already encodes this contract — copy that block verbatim and fill in.
274
+ The final-report template `templates/reports/final-report-v2.template.md` Section 5.5 already encodes this contract — copy that block verbatim and fill in.
275
275
 
276
276
  ### Self-fix rewrite (plan-body self-fix round)
277
277
 
@@ -305,7 +305,7 @@ When the run's `task-type` is `final-verification`, the report's `## 7. Final Ve
305
305
 
306
306
  For every other task-type, set the `Verdict Token` cell to `not-applicable`. Do NOT omit the row — the template renders it for all task-types and downstream tooling expects the field to exist.
307
307
 
308
- The final-report template `templates/reports/final-report.template.md` Section 7 already encodes this contract — copy that block verbatim and fill in.
308
+ The final-report template `templates/reports/final-report-v2.template.md` Section 7 already encodes this contract — copy that block verbatim and fill in.
309
309
 
310
310
  ### Release-handoff section contract (release-handoff runs only)
311
311
 
@@ -313,7 +313,7 @@ When the run's `task-type` is `release-handoff`, the final report MUST include S
313
313
 
314
314
  **Single-lead authorship (release-handoff only):** release-handoff has no worker roster. The lead authors the final-report file directly — there is no `Report writer worker` dispatch to perform in Phase 6, no resume-safe dispatch concern, and no mandatory worker-results file for a report-writer role. The rest of this contract's dispatch / resume / fallback machinery applies ONLY when `Report writer worker` is in the roster (i.e. every task-type other than `release-handoff`).
315
315
 
316
- The final-report template `templates/reports/final-report.template.md` Section 5.6 already encodes this contract — copy that block verbatim and fill in. For non-`release-handoff` runs, omit Section 5.6 entirely.
316
+ The final-report template `templates/reports/final-report-v2.template.md` Section 5.6 already encodes this contract — copy that block verbatim and fill in. For non-`release-handoff` runs, omit Section 5.6 entirely.
317
317
 
318
318
  ### Mandatory worker-result pointer and audit sidecar (BLOCKING)
319
319
 
@@ -337,7 +337,7 @@ The selected report-writer preamble defines that audit shape. **Enforced:** `val
337
337
 
338
338
  ### Main Body Section
339
339
 
340
- Section numbering follows `templates/reports/final-report.template.md` exactly — that file is the documentation SSOT for section names and ordering. For full body structure at authoring time, consult your run's **phase-stripped** `final-report-template.md` (the per-task-type instruction-set copy defined in Phase 6 dispatch item 10); the "copy that block verbatim" references below mean the §-block as it appears in that stripped copy, not a re-read of the full source.
340
+ Section numbering follows `templates/reports/final-report-v2.template.md` exactly — that file is the documentation SSOT for section names and ordering. For full body structure at authoring time, consult your run's **phase-stripped** `final-report-template.md` (the per-task-type instruction-set copy defined in Phase 6 dispatch item 10); the "copy that block verbatim" references below mean the §-block as it appears in that stripped copy, not a re-read of the full source.
341
341
 
342
342
  **Verdict Card (top-of-report, mandatory).** Render `## Verdict Card` between the report header and the (conditional) Approval block. Its `Verdict Token` and `Direction` cells MUST byte-match the corresponding cells in `## 7. Final Verdict`. Its `Next Step`, the final-verdict next step, and the first item of `## 3. Recommended Next Steps` MUST name the same route target, though the actionable command and prose need not be byte-identical. Divergence is `contract-violated`.
343
343
 
@@ -164,7 +164,7 @@ After each worker attempt returns (regardless of role), Lead MUST verify the can
164
164
  - The result file exists but its audit sidecar does not, at `runs/<task-type>/worker-results/<worker>-audit-<task-type>-<seq>.md`. Workers write both in the same step, so a result without a sidecar means the Reading Confirmation block — the only evidence the worker read its inputs — was never produced. `validate-run.py` fails the run on this at Phase 7 either way (`validate_worker_results_audit`); checking it here spends the existing one-retry budget while the role can still be re-dispatched, instead of surfacing hours later when the worker session is gone.
165
165
  - `okstra worker-audit-check --run-dir <runs/<task-type>/> --task-type <t> --seq <n> --worker <id>` exits 2 on a backticked `path:line` citation in the worker's result that has no matching Evidence read row in its audit sidecar. Run it the moment you collect each result. Phase 7 enforces the same rules from the same implementation (`okstra_ctl.worker_audit_ledger`), but by then the worker session is gone and the only remaining moves are editing the result yourself — which destroys the audit chain the ledger exists to provide — or ending the run `contract-violated`. While the session is alive, `SendMessage` to the worker so it corrects its own citation; that costs about a minute against a re-dispatch or a failed run.
166
166
 
167
- The same audit check parses command evidence from canonical rows such as `- Evidence command: {"command":"npm run check","cwd":"<project-root>","exitCode":0,"outputSummary":"all checks passed"}`. Workers record only commands that produced or verified a conclusion, not exploratory `rg`, `ls`, or file-opening commands. Environment variable values, tokens, credentials, and authorization headers are excluded. A malformed row or potential sensitive material is a contract failure returned by `okstra_ctl.worker_audit_ledger`; send the failure to the worker while its session is still available.
167
+ The same audit check parses command evidence from canonical rows such as `- Evidence command: {"command":"npm run check","cwd":"<project-root>","exitCode":0,"outputSummary":"all checks passed"}`. Workers record only commands that produced or verified a conclusion, not exploratory `rg`, `ls`, or file-opening commands. A malformed row is a contract failure returned by `okstra_ctl.worker_audit_ledger`; send the failure to the worker while its session is still available. Nothing scans these rows for secrets, so a row is never refused for how a setting is named — `AUTH_MODE=off` and `TOKEN_TTL=60` record verbatim. Do not tell a worker to drop or paraphrase a row on those grounds: a worker that cannot record the command it ran has no way to satisfy the evidence ledger that demands it.
168
168
 
169
169
  **One-retry policy:**
170
170
 
@@ -12,7 +12,7 @@ profile document.
12
12
  - **Phase 5.5 (convergence — peer review by workers)**: workers peer-review each other's findings across up to `effectiveMaxRounds` rounds; the lead mediates but does not vote. See `prompts/lead/convergence.md` for the round protocol (replay of findings, `AGREE` / `DISAGREE` / `SUPPLEMENT` verdicts), queue invariants, and final classification (`full-consensus` / `partial-consensus` / `contested` / `worker-unique`). For `requirements-discovery`, `error-analysis`, `implementation-option-selection`, `implementation-planning`, `project-analysis`, `feature-analysis`, and `change-impact-analysis` this phase runs in **adversarial mode** (`convergence.adversarial=true`): verifiers try to refute each finding against its cited evidence and the burden of proof sits on the claim — see that skill's §"Adversarial Verification Mode".
13
13
  - Do NOT conclude "no peer review happens" from the roster alone — every profile that lists ≥2 analyser workers runs convergence by default (`convergence.enabled=true` in `task-manifest.json`).
14
14
  - For a new `implementation-planning` run, the plan-body sequence is initial verification → one planner self-fix → targeted re-verification → user gate. The initial verification is round 1, the targeted re-verification is round 2, and a second automatic self-fix is a contract violation. A user-directed correction does not consume the automatic self-fix limit, and a verification failure after that correction does not restart the automatic loop.
15
- - **provider-unavailable fallback (tolerance).** A worker dispatch can fail to produce a result for two distinct reasons, and both take the same recovery path. (1) **Pane budget:** the dispatch is rejected with `no room for another tmux split` (or an equivalent teammate-pane creation failure). (2) **Sandbox CLI-start failure (non-tmux path):** an external CLI worker wrapper exits non-zero within seconds with empty stdout and its live-log shows `operation not permitted`. In either case the lead spends the one shared retry budget through the assignment's recorded runner. If the provider is still unavailable, record that terminal status and continue only under the convergence quorum rules; never replace it silently with a fixed provider or count a substitute as the original provider's vote. Completed external-CLI worker panes are reclaimed by the selected runtime adapter's resource lifecycle. (This is a prompt instruction, not a code-enforced gate.)
15
+ - **provider-unavailable fallback (tolerance).** A worker dispatch can fail to produce a result for two distinct reasons, and both take the same recovery path. (1) **Pane budget:** the dispatch is rejected with `no room for another tmux split` (or an equivalent teammate-pane creation failure) — this is the harness running out of room for its own teammate panes, not okstra placing a worker. (2) **Sandbox CLI-start failure:** an external CLI worker wrapper exits non-zero within seconds with empty stdout and its live-log shows `operation not permitted`. In either case the lead spends the one shared retry budget through the assignment's recorded runner. If the provider is still unavailable, record that terminal status and continue only under the convergence quorum rules; never replace it silently with a fixed provider or count a substitute as the original provider's vote. Completed external-CLI workers hold no pane of their own; the harness teammate panes are reclaimed by the selected runtime adapter's resource lifecycle. (This is a prompt instruction, not a code-enforced gate.)
16
16
  - Dual-audience final-report contract (shared):
17
17
  - data.json is the sole authored report artifact. AI handoff Markdown and human HTML are independently derived from it; neither derived artifact is the other's source.
18
18
  - User-facing information belongs in `humanSummary` and the selected task block's `userNarrative`; it must not exist only in Markdown. The HTML human main body explains the result with those fields plus task facts.
@@ -69,7 +69,7 @@ profile document.
69
69
  - `origin` — who raised it. `worker-finding` (an analyser or verifier reached it on its own evidence), `material-gap` (neither the brief nor the codebase answers it), or `lead-directed` (the lead's own judgment, **including anything the lead instructed a worker to raise**). A lead that seeds its conclusion into a worker prompt and then reports the worker's agreement as an independent finding has mislabelled the row; that shape is what let one run block on a question its own lead had authored.
70
70
  - `userConfirmation` — what happened before the row was written. `asked-and-answered`, `asked-awaiting` (asked, no answer yet), or `deferred-no-interactive-session` (this run had no user to ask). Record an answer in `userInput` and move `status` to `answered`.
71
71
  - Neither field is required once `status` is `answered` / `resolved` — the record lives in `userInput` by then.
72
- - **Legacy canonical column schema (must match `templates/reports/final-report.template.md` §1 exactly):** every `## 1. Clarification Items` table has exactly these 4 columns, in this order:
72
+ - **Legacy canonical column schema (must match `templates/reports/final-report-v2.template.md` §1 exactly):** every `## 1. Clarification Items` table has exactly these 4 columns, in this order:
73
73
  `| <record-meta> | Statement | Expected form | User input |` (the first header is the i18n `columns.recordMeta` label — `Record`).
74
74
  The five short fields (ID, Ticket ID, Kind, Blocks, Status) are stacked inside the single record-meta cell, one per line separated by `<br>`, in this fixed order (mirrors the §2.1 Primary-Evidence meta column):
75
75
 
@@ -94,6 +94,8 @@ persisted prompt lacks the heading `Coding-conventions preflight`
94
94
  - **local git operations only**: `git add`, `git commit`. Prefer small commits keyed to plan steps.
95
95
  - **Explicit-path staging only (BLOCKING).** Stage exactly the files a plan step touches by naming each path — `git add <path1> <path2>`. Never use the bulk forms `git add .`, `git add -A`, `git add -u`, or `git add --all`: they sweep in unrelated edits, stray build output, and editor scratch files. Never use `git add -f` (force) under any circumstance — `-f` is the *only* way a `.gitignore`d path reaches a commit, and a forced ignored file is always a defect. If `git add <path>` reports the path is ignored, that file does not belong in the commit; do not override it — leave it untracked.
96
96
  - **No ignored / okstra files in commits (BLOCKING).** Before every `git commit`, run `git diff --cached --name-only` and pipe the staged paths through `git check-ignore --stdin --no-index`; abort the commit if it reports **any** match — a staged path that `.gitignore` excludes means a bulk-add or `-f` leaked it in. The check explicitly covers `.okstra/` (and `.project-docs/` when the legacy symlink is present): `.okstra/**` is gitignored, and force-staging it onto the stage branch is the one way these verification artifacts reach the upstream PR. Conformance/qa evidence belongs in the carry sidecar and verifier result — committing it is never correct, even when a step's instructions seem to ask for it.
97
+ - **Clean-tree checks use `okstra worktree-status --check-clean` (BLOCKING).** The worktree you execute in carries okstra's own provisioning — `.okstra`, the synced entries (`.project-docs`, `.claude`, …), and the nested `stage-<N>/` worktree — so `git status --porcelain` is never empty here and its emptiness proves nothing about your stage. Use `okstra worktree-status --check-clean`: it asks the same question over source paths only and exits 1 when dirty, so it chains as an assertion. This applies to a check you write yourself AND to one you inherit: when a plan step's `command` cell asserts cleanliness with a bare `git status`, run the okstra command in its place and record the substitution in `Out-of-plan edits` — the step's intent was a clean stage, not a literal empty status.
98
+ - **Never read an `.okstra/` artifact from a git object (BLOCKING).** `.okstra/**` is gitignored and never committed (see the staging rules above), so `git cat-file -e <tag>:.okstra/…`, `git show <tag>:.okstra/…`, and every variant of that read fail by construction — the answer is never "commit it after all". When a plan step's command depends on such a read, the step is unsatisfiable as written: read the artifact from the working tree instead, record the deviation, and carry the artifact forward through the stage carry sidecar.
97
99
  - **Commit message format (mandatory)**: every commit message MUST follow Conventional Commits — `<type>(<scope>): <subject>` for the first line, optional body separated by a blank line, optional footer. Constraints:
98
100
  - `<type>` MUST be one of: `feat` / `fix` / `perf` / `revert` / `deps` / `docs` / `refactor` / `build` / `ci` / `chore` / `test`. When the repo is `release-please`-managed, this aligns the commit with a configured changelog section.
99
101
  - `<scope>` SHOULD be the plan step identifier or the primary module touched (e.g. `feat(report-writer): ...`). Omit the parentheses only when no meaningful scope applies.