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
@@ -84,6 +84,16 @@ def _write_json(path: Path, payload: dict) -> None:
84
84
  _write_text(path, json.dumps(payload, indent=2, ensure_ascii=False) + "\n")
85
85
 
86
86
 
87
+ def _execution_identity(ctx: Mapping[str, object]) -> dict:
88
+ raw = ctx.get("EXECUTION_IDENTITY_JSON")
89
+ if not isinstance(raw, str) or not raw.strip():
90
+ return {}
91
+ payload = json.loads(raw)
92
+ if not isinstance(payload, dict):
93
+ raise TokenRenderError("EXECUTION_IDENTITY_JSON must contain an object")
94
+ return payload
95
+
96
+
87
97
  def _lead_runtime(ctx: dict) -> str:
88
98
  return ctx.get("LEAD_RUNTIME", "") or "claude-code"
89
99
 
@@ -377,10 +387,14 @@ def _agent_contract(ctx: dict) -> dict:
377
387
  return parsed
378
388
 
379
389
 
380
- def _executor_contract(ctx: dict) -> dict:
390
+ def _executor_contract(ctx: dict) -> dict | None:
381
391
  runner = ctx.get("EXECUTOR_RUNNER", "")
382
392
  dispatch_mode = ctx.get("EXECUTOR_DISPATCH_MODE", "")
383
393
  host_model_value = ctx.get("EXECUTOR_HOST_MODEL_VALUE") or None
394
+ if not runner and not ctx.get("EXECUTOR_PROVIDER"):
395
+ if ctx.get("TASK_TYPE") == "implementation":
396
+ raise ValueError("implementation executor assignment is required")
397
+ return None
384
398
  expected_mode = {
385
399
  "native-session": "host-native",
386
400
  "cli-wrapper": "worker-dispatch",
@@ -1326,8 +1340,14 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
1326
1340
  latest_team_state_relative = pointers["latestTeamStatePath"]
1327
1341
  latest_resume_command_relative = pointers["latestResumeCommandPath"]
1328
1342
  convergence_block = _build_convergence_block(ctx)
1343
+ execution_identity = _execution_identity(ctx)
1344
+ existing_v2 = (
1345
+ existing.get("schemaVersion") == "2.0"
1346
+ and existing.get("executionIdentityVersion") == 2
1347
+ )
1348
+ writes_v2 = execution_identity.get("executionIdentityVersion") == 2
1329
1349
  payload = {
1330
- "schemaVersion": "1.0",
1350
+ "schemaVersion": "2.0" if existing_v2 or writes_v2 else "1.0",
1331
1351
  "projectId": ctx.get("PROJECT_ID", ""),
1332
1352
  "taskGroup": ctx.get("TASK_GROUP", ""),
1333
1353
  "taskId": ctx.get("TASK_ID", ""),
@@ -1487,6 +1507,12 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
1487
1507
  "sessionId": ctx.get("CLAUDE_SESSION_ID", ""),
1488
1508
  "resumeCommandPath": ctx.get("CLAUDE_RESUME_COMMAND_RELATIVE_PATH", ""),
1489
1509
  }
1510
+ if existing_v2 or writes_v2:
1511
+ existing_index = existing.get("roleExecutionIndex")
1512
+ payload["executionIdentityVersion"] = 2
1513
+ payload["roleExecutionIndex"] = (
1514
+ list(existing_index) if isinstance(existing_index, list) else []
1515
+ )
1490
1516
  _write_json(path, payload)
1491
1517
 
1492
1518
 
@@ -1608,8 +1634,9 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
1608
1634
  for s in str(ctx.get("CONCURRENT_RUN_STAGES", "") or "").split(",")
1609
1635
  if s.strip().isdigit()
1610
1636
  ]
1637
+ execution_identity = _execution_identity(ctx)
1611
1638
  payload = {
1612
- "schemaVersion": "1.0",
1639
+ "schemaVersion": execution_identity.get("schemaVersion", "1.0"),
1613
1640
  "okstraVersion": ctx.get("OKSTRA_VERSION", ""),
1614
1641
  "projectId": ctx.get("PROJECT_ID", ""),
1615
1642
  "taskGroup": ctx.get("TASK_GROUP", ""),
@@ -1783,6 +1810,7 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
1783
1810
  )
1784
1811
  if _lead_runtime(ctx) == "claude-code":
1785
1812
  payload["claudeSessionId"] = ctx.get("CLAUDE_SESSION_ID", "")
1813
+ payload.update(execution_identity)
1786
1814
  _guard_invocation_manifest_rewrite(existing_run_manifest, payload)
1787
1815
  _write_json(Path(run_manifest_path), payload)
1788
1816
 
@@ -1795,6 +1823,11 @@ _MODEL_ASSIGNMENT_KEYS = {
1795
1823
  "hostRuntime",
1796
1824
  "hostModelValue",
1797
1825
  }
1826
+ _V2_INVOCATION_IDENTITY_KEYS = {
1827
+ "participantRef",
1828
+ "roleExecutionRef",
1829
+ "dutyId",
1830
+ }
1798
1831
  _AGENT_CONTRACT_KEYS = {
1799
1832
  "schemaVersion",
1800
1833
  "dutyRootPath",
@@ -1860,11 +1893,8 @@ def _validate_invocation_manifest(payload: Mapping[str, object]) -> None:
1860
1893
  or not assignments
1861
1894
  ):
1862
1895
  raise ValueError("incomplete invocation manifest")
1863
- if any(
1864
- not isinstance(item, Mapping) or set(item) != _MODEL_ASSIGNMENT_KEYS
1865
- for item in assignments.values()
1866
- ):
1867
- raise ValueError("incomplete invocation manifest assignments")
1896
+ _invocation_manifest_identity_version(payload)
1897
+ _validate_invocation_assignment_shapes(assignments)
1868
1898
  _validate_compatibility_projections(payload, assignments)
1869
1899
  for key in (
1870
1900
  item
@@ -1901,13 +1931,17 @@ def _validate_compatibility_projections(
1901
1931
  workers = payload.get("workerAssignments")
1902
1932
  if not isinstance(lead, Mapping) or not isinstance(workers, list):
1903
1933
  raise ValueError("incomplete invocation manifest compatibility projection")
1904
- if _model_projection(lead) != assignments.get("lead"):
1934
+ if _model_projection(lead) != _model_projection_from_assignment(
1935
+ assignments.get("lead")
1936
+ ):
1905
1937
  raise ValueError("immutable invocation manifest lead projection drift")
1906
1938
  for worker in workers:
1907
1939
  if not isinstance(worker, Mapping):
1908
1940
  raise ValueError("incomplete invocation manifest worker projection")
1909
1941
  reference = f"initial/{worker.get('workerId', '')}"
1910
- if _model_projection(worker) != assignments.get(reference):
1942
+ if _model_projection(worker) != _model_projection_from_assignment(
1943
+ assignments.get(reference)
1944
+ ):
1911
1945
  raise ValueError("immutable invocation manifest worker projection drift")
1912
1946
 
1913
1947
 
@@ -1915,6 +1949,40 @@ def _model_projection(value: Mapping[str, object]) -> dict[str, object]:
1915
1949
  return {key: value.get(key) for key in _MODEL_ASSIGNMENT_KEYS}
1916
1950
 
1917
1951
 
1952
+ def _model_projection_from_assignment(value: object) -> dict[str, object] | None:
1953
+ if not isinstance(value, Mapping) or not _MODEL_ASSIGNMENT_KEYS.issubset(value):
1954
+ return None
1955
+ return _model_projection(value)
1956
+
1957
+
1958
+ def _invocation_manifest_identity_version(payload: Mapping[str, object]) -> int:
1959
+ schema = payload.get("schemaVersion")
1960
+ identity = payload.get("executionIdentityVersion")
1961
+ if schema == "2.0" and identity == 2:
1962
+ return 2
1963
+ if schema == "2.0" or identity is not None:
1964
+ raise ValueError("invocation manifest mixes v1 and v2 execution identity")
1965
+ if schema not in (None, 1, "1", "1.0"):
1966
+ raise ValueError("unsupported invocation manifest execution identity version")
1967
+ return 1
1968
+
1969
+
1970
+ def _validate_invocation_assignment_shapes(
1971
+ assignments: Mapping[str, object],
1972
+ ) -> None:
1973
+ allowed = {frozenset(_MODEL_ASSIGNMENT_KEYS)}
1974
+ for item in assignments.values():
1975
+ if not isinstance(item, Mapping):
1976
+ raise ValueError("incomplete invocation manifest assignments")
1977
+ keys = set(item)
1978
+ if keys & _V2_INVOCATION_IDENTITY_KEYS:
1979
+ raise ValueError(
1980
+ "invocation assignment duplicates v2 execution identity"
1981
+ )
1982
+ if frozenset(keys) not in allowed:
1983
+ raise ValueError("incomplete invocation manifest assignments")
1984
+
1985
+
1918
1986
  def render_timeline(timeline_path: str, ctx: dict) -> None:
1919
1987
  task_manifest_path = Path(ctx.get("TASK_MANIFEST_PATH", ""))
1920
1988
  task_manifest = {}
@@ -12,10 +12,10 @@ columns in the Execution Status table, omitted §4 phase-continuation
12
12
  rows, ad-hoc ``## Index`` sections. Routing everything through one
13
13
  template + schema cuts those failure modes to zero.
14
14
 
15
- For schema-v1 compatibility, the top-of-report ``## Index`` remains a
16
- deterministic post-render section built by ``_inject_index_and_anchors``.
17
- Schema v2 keeps its compact fixed AI-handoff order and does not inject the
18
- legacy reader index; the task-specific HTML provides the human navigation.
15
+ Rendering never injects a reader index: the AI-handoff markdown keeps its
16
+ compact fixed order and the task-specific HTML provides human navigation.
17
+ ``_inject_index_and_anchors`` survives as a standalone pass over an already
18
+ written markdown file, driven by ``scripts/okstra-inject-report-index.py``.
19
19
 
20
20
  Phase 7 mutation flow: ``okstra-token-usage.py --substitute-data`` fills
21
21
  the ``tokenUsage`` and ``executionStatus[].totalTokens`` etc. cells in
@@ -53,7 +53,11 @@ from okstra_ctl.i18n import I18nError, SUPPORTED_LANGS, load_dictionary, make_ji
53
53
  from okstra_ctl.md_table import UNESCAPED_PIPE_RE, to_cell_text
54
54
  from okstra_ctl.models import UnknownModelError, resolve_model_metadata
55
55
  from okstra_ctl.paths import find_asset_root
56
- from okstra_ctl.report_contract import TASK_TYPE_DATA_PROPERTY, markdown_template_for
56
+ from okstra_ctl.report_contract import (
57
+ CURRENT_REPORT_SCHEMA_VERSION,
58
+ TASK_TYPE_DATA_PROPERTY,
59
+ markdown_template_for,
60
+ )
57
61
  from okstra_ctl.report_markdown import ReportSections
58
62
  from okstra_ctl.schema_excerpt import excerpt_cut_from_version
59
63
  from okstra_ctl.seeding import installed_version
@@ -61,10 +65,9 @@ from okstra_ctl.usage_cells import format_duration_ms, format_int, format_usd
61
65
 
62
66
 
63
67
  TEMPLATE_BY_SCHEMA_VERSION = {
64
- "1.0": ("templates", "reports", "final-report.template.md"),
65
68
  "2.0": ("templates", "reports", "final-report-v2.template.md"),
66
69
  }
67
- DEFAULT_TEMPLATE_REL = TEMPLATE_BY_SCHEMA_VERSION["1.0"]
70
+ DEFAULT_TEMPLATE_REL = TEMPLATE_BY_SCHEMA_VERSION[CURRENT_REPORT_SCHEMA_VERSION]
68
71
 
69
72
  TASK_DELIVERABLE_TITLES = {
70
73
  "requirements-discovery": "Requirements Discovery",
@@ -563,6 +566,8 @@ def _ai_markdown_context(data: dict, schema: dict | None) -> dict:
563
566
  context["md_rest"] = sections.rest
564
567
  context["md_has"] = sections.has
565
568
  context["md_claim"] = sections.mark_rendered
569
+ context["executionRolesTable"] = render_execution_roles_markdown(data)
570
+ context["executionIdentityVersion"] = data.get("executionIdentityVersion")
566
571
  return context
567
572
 
568
573
 
@@ -620,6 +625,23 @@ _OPTIONAL_ANALYSIS_DEFAULTS = (
620
625
  )
621
626
 
622
627
 
628
+ def render_execution_roles_markdown(data: dict) -> str:
629
+ roles = data.get("executionRoles") or []
630
+ if data.get("executionIdentityVersion") != 2:
631
+ return "legacy execution roles"
632
+ lines = ["| Role | Label | Status |", "|---|---|---|"]
633
+ for row in roles:
634
+ lines.append(
635
+ f"| {row.get('role', '')} | {row.get('executionLabel', '')} | "
636
+ f"{row.get('status', '')} |"
637
+ )
638
+ return "\n".join(lines)
639
+
640
+
641
+ def render_execution_roles_html(data: dict) -> str:
642
+ return render_execution_roles_markdown(data)
643
+
644
+
623
645
  def _with_optional_defaults(data: dict) -> dict:
624
646
  """Render context with schema-optional fields filled in.
625
647
 
@@ -666,14 +688,7 @@ def render(
666
688
 
667
689
  try:
668
690
  template = env.get_template(template_path.name)
669
- context = (
670
- _ai_markdown_context(data, schema)
671
- if data.get("schemaVersion") == "2.0"
672
- else _with_optional_defaults(data)
673
- )
674
- rendered = template.render(**context)
675
- if data.get("schemaVersion") == "1.0":
676
- rendered = _inject_index_and_anchors(rendered, dictionary)
691
+ rendered = template.render(**_ai_markdown_context(data, schema))
677
692
  return _ventilate_prose(rendered)
678
693
  except I18nError as exc:
679
694
  raise FinalReportRenderError(
@@ -689,10 +704,10 @@ def find_default_template(start: Path | None = None) -> Path:
689
704
  """Locate the bundled final-report template.
690
705
 
691
706
  Resolution order:
692
- 1. ``$OKSTRA_HOME/templates/reports/final-report.template.md`` (installed runtime).
693
- 2. ``<repo>/templates/reports/final-report.template.md`` (in-repo dev runs).
707
+ 1. ``$OKSTRA_HOME/templates/reports/final-report-v2.template.md`` (installed runtime).
708
+ 2. ``<repo>/templates/reports/final-report-v2.template.md`` (in-repo dev runs).
694
709
  Repo root is detected by walking up from this file until a
695
- ``templates/reports/final-report.template.md`` exists.
710
+ ``templates/reports/final-report-v2.template.md`` exists.
696
711
 
697
712
  Raises ``FinalReportRenderError`` if neither path is present.
698
713
  """
@@ -701,7 +716,7 @@ def find_default_template(start: Path | None = None) -> Path:
701
716
  return root.joinpath(*DEFAULT_TEMPLATE_REL)
702
717
 
703
718
  raise FinalReportRenderError(
704
- "could not locate final-report.template.md. Set OKSTRA_HOME or "
719
+ "could not locate final-report-v2.template.md. Set OKSTRA_HOME or "
705
720
  "run from a checkout that contains templates/reports/."
706
721
  )
707
722
 
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
 
4
4
 
5
5
  CURRENT_REPORT_SCHEMA_VERSION = "2.0"
6
- LEGACY_REPORT_SCHEMA_VERSION = "1.0"
7
6
 
8
7
  PUBLIC_REPORT_TASK_TYPES = (
9
8
  "requirements-discovery",
@@ -171,6 +170,45 @@ def html_template_for(task_type: str) -> str:
171
170
  raise ValueError(f"no task-specific html template for: {task_type}") from exc
172
171
 
173
172
 
173
+ def execution_roles_from_manifest(manifest: dict) -> list[dict]:
174
+ """Copy the exact RoleExecution set. Do not invent rows."""
175
+ if int(manifest.get("executionIdentityVersion") or 0) != 2:
176
+ return []
177
+ roles = []
178
+ for row in manifest.get("roleExecutions") or []:
179
+ if not isinstance(row, dict):
180
+ continue
181
+ roles.append({
182
+ "roleExecutionRef": row.get("roleExecutionRef"),
183
+ "role": row.get("role"),
184
+ "provider": row.get("provider"),
185
+ "modelRef": row.get("modelRef"),
186
+ "ordinal": row.get("ordinal"),
187
+ "executionLabel": row.get("executionLabel"),
188
+ "status": row.get("status"),
189
+ })
190
+ return roles
191
+
192
+
193
+ def apply_execution_roles(data: dict, manifest: dict) -> dict:
194
+ """Copy the manifest RoleExecution set onto a final-report payload."""
195
+ updated = dict(data)
196
+ if int(manifest.get("executionIdentityVersion") or 0) != 2:
197
+ return updated
198
+ updated["executionIdentityVersion"] = 2
199
+ updated["executionRoles"] = execution_roles_from_manifest(manifest)
200
+ return updated
201
+
202
+
203
+ def assert_execution_roles_match_manifest(report: dict, manifest: dict) -> None:
204
+ report_refs = {row.get("roleExecutionRef") for row in report.get("executionRoles") or []}
205
+ manifest_refs = {
206
+ row.get("roleExecutionRef") for row in manifest.get("roleExecutions") or []
207
+ }
208
+ if report_refs != manifest_refs:
209
+ raise ValueError("report executionRoles must equal manifest roleExecutions")
210
+
211
+
174
212
  def markdown_template_for(task_type: str) -> str:
175
213
  try:
176
214
  return TASK_TYPE_MARKDOWN_TEMPLATE[task_type]
@@ -6,6 +6,18 @@ substitution, html view rendering, follow-up task spawning, and run validation.
6
6
  The order is load-bearing — rendering before substitution ships `--` token
7
7
  cells, and validating before rendering trips the report-views contract.
8
8
 
9
+ `token-usage` is the one step whose failure does not stop the sequence, which
10
+ deliberately accepts that first state: its input is the lead session log, so a
11
+ refusal there would otherwise delete every artifact the later steps produce.
12
+ The run does not pass in that state. `validators/validate-run.py` re-collects
13
+ when the recorded usage is all zeros against an `unavailable` session source
14
+ (`_needs_token_autofix`) and refuses the run rather than ship zeroed counts
15
+ (`accuracy-failed`); a legacy v1 report is caught earlier still, by its
16
+ unsubstituted `{{...}}` placeholders, which a v2 report never carries because
17
+ its numeric cells are `null` until this step fills them. Substituting the
18
+ tokens on a later retry then leaves the already-rendered html stale for
19
+ `validators/validate-report-views.py`.
20
+
9
21
  The translation sidecar is NOT one of these steps. `render-views` overlays it,
10
22
  so a non-English run dispatches the translator before this sequence starts —
11
23
  after verifying the data.json is English, which is why `check-source` is also
@@ -26,9 +38,11 @@ from pathlib import Path
26
38
  from typing import Any, Callable, Mapping, Sequence
27
39
 
28
40
  from .agent_activity import ActivityProjectionError, project_agent_activity
41
+ from .report_contract import apply_execution_roles
29
42
  from .dispatch_state import DispatchError, link_agent_dispatch_result
30
43
  from .final_report_paths import final_report_data_path, final_report_markdown_path
31
44
  from .paths import task_dir, task_manifest_file
45
+ from .session import observe_lead_session
32
46
 
33
47
 
34
48
  STEP_PROJECT_ACTIVITY = "project-activity"
@@ -354,7 +368,14 @@ def run_finalize(
354
368
  before_step: Callable[[str], None] | None = None,
355
369
  only: Sequence[str] | None = None,
356
370
  ) -> dict[str, Any]:
357
- """Run the Phase 7 steps in order, stopping at the first non-zero exit.
371
+ """Run the Phase 7 steps in contractual order.
372
+
373
+ A non-zero exit stops the sequence, with one exception: ``token-usage``
374
+ defers. Its input is the lead session log — state outside this run — so a
375
+ refusal there is not evidence that the artifacts after it are unwritable.
376
+ The deferred failure becomes the result's ``reason`` when nothing later
377
+ fails, and yields to any later failure, which is the step that actually
378
+ blocked the run.
358
379
 
359
380
  ``before_step`` fires immediately before each step is spawned, letting an
360
381
  adapter settle state the step will read (the Codex adapter marks the report
@@ -366,7 +387,9 @@ def run_finalize(
366
387
  step at full token and wall-clock cost.
367
388
  """
368
389
  steps: list[dict[str, Any]] = []
390
+ deferred = ""
369
391
  try:
392
+ write_execution_roles(ctx)
370
393
  commands = build_commands(ctx)
371
394
  except FinalizeError as exc:
372
395
  return {"ok": False, "reason": str(exc), "steps": steps}
@@ -426,14 +449,38 @@ def run_finalize(
426
449
  )
427
450
  steps.append(step_payload(name, command, result))
428
451
  if result.returncode != 0:
429
- return {
430
- "ok": False,
431
- "reason": f"{name} failed with exit code {result.returncode}",
432
- "steps": steps,
433
- }
452
+ failure = f"{name} failed with exit code {result.returncode}"
453
+ # Token collection reads the lead session log, which lives outside
454
+ # this run. One refusal there (`grandTotalTokens=0`) took the whole
455
+ # run's output with it: html never rendered, follow-ups never
456
+ # spawned, and `validate-run` then blocked on `report-views:
457
+ # missing html artifact`. So this step alone defers instead of
458
+ # stopping the sequence. It hides nothing — the non-zero exit stays
459
+ # in `steps`, `ok` stays False, and the closing `validate-run`
460
+ # still refuses the run.
461
+ if name == STEP_TOKEN_USAGE:
462
+ deferred = failure
463
+ continue
464
+ return {"ok": False, "reason": failure, "steps": steps}
465
+ if deferred:
466
+ return {"ok": False, "reason": deferred, "steps": steps}
434
467
  return {"ok": True, "reason": "", "steps": steps}
435
468
 
436
469
 
470
+ def write_execution_roles(ctx: FinalizeContext) -> None:
471
+ """Write the exact manifest role set onto the final-report data.json."""
472
+ if not ctx.data_path.is_file() or not ctx.manifest_path.is_file():
473
+ return
474
+ manifest = _load_manifest(ctx.manifest_path)
475
+ data = _load_manifest(ctx.data_path)
476
+ updated = apply_execution_roles(dict(data), dict(manifest))
477
+ if updated == data:
478
+ return
479
+ tmp = ctx.data_path.with_suffix(ctx.data_path.suffix + ".tmp")
480
+ tmp.write_text(json.dumps(updated, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
481
+ tmp.replace(ctx.data_path)
482
+
483
+
437
484
  def _link_lead_result_for_validation(ctx: FinalizeContext) -> None:
438
485
  """Link the lead-authored synthesis artifact before Phase 7 validation."""
439
486
  manifest = _load_manifest(ctx.manifest_path)
@@ -546,6 +593,29 @@ def _step_summary_lines(result: Mapping[str, Any]) -> list[str]:
546
593
  return lines
547
594
 
548
595
 
596
+ def _recovery_step_names(result: Mapping[str, Any]) -> list[str]:
597
+ """The steps a retry has to re-run: every step from the earliest failure on.
598
+
599
+ Naming only the failed steps would prescribe half a recovery. `token-usage`
600
+ defers, so `render-views` already wrote an html view from unsubstituted
601
+ data; substituting the tokens on a retry leaves that view stale
602
+ (`validators/validate-report-views.py` checks `source-sha256` against the
603
+ md body). And a sequence that stopped early never reached `validate-run`,
604
+ which is the step that decides whether the run is shippable. Resuming from
605
+ the earliest failure redoes both while still skipping the prefix that
606
+ succeeded — the saving `--only` exists for.
607
+ """
608
+ failed = {
609
+ string_value(step.get("name"))
610
+ for step in (result.get("steps") or [])
611
+ if step.get("exitCode") != 0
612
+ }
613
+ for index, name in enumerate(STEP_ORDER):
614
+ if name in failed:
615
+ return list(STEP_ORDER[index:])
616
+ return []
617
+
618
+
549
619
  def main(argv: Sequence[str] | None = None) -> int:
550
620
  args = _parser().parse_args(argv)
551
621
  try:
@@ -553,6 +623,10 @@ def main(argv: Sequence[str] | None = None) -> int:
553
623
  except FinalizeError as exc:
554
624
  print(f"error: {exc}", file=sys.stderr)
555
625
  return 2
626
+ # Phase 7 is the last boundary the lead crosses, and the last chance to add
627
+ # the generations resume and compaction split it into. It runs ahead of the
628
+ # sequence because the token-usage step below reads `leadSessionIds`.
629
+ observe_lead_session(ctx.project_root, ctx.team_state_path)
556
630
  result = run_finalize(ctx, only=args.only or None)
557
631
  print(json.dumps(result, indent=2, ensure_ascii=False))
558
632
  print("finalize steps:", file=sys.stderr)
@@ -560,10 +634,11 @@ def main(argv: Sequence[str] | None = None) -> int:
560
634
  print(line, file=sys.stderr)
561
635
  if not result["ok"]:
562
636
  print(f"error: {result['reason']}", file=sys.stderr)
563
- if result.get("steps"):
637
+ recovery = _recovery_step_names(result)
638
+ if recovery:
639
+ flags = " ".join(f"--only {name}" for name in recovery)
564
640
  print(
565
- "retry just the failing step with "
566
- f"`--only {result['steps'][-1].get('name')}`",
641
+ f"resume the sequence from the earliest failure with `{flags}`",
567
642
  file=sys.stderr,
568
643
  )
569
644
  return 1
@@ -155,6 +155,7 @@ def render_v2_html_view(
155
155
  # Every task type ends with the same run-cost section, so it is bound
156
156
  # here rather than in ten view models that would each rebuild it.
157
157
  "runUsage": run_usage(data),
158
+ "executionRoles": data.get("executionRoles") or [],
158
159
  "css": (root / "html/assets/base.css").read_text(encoding="utf-8"),
159
160
  "js": response_js + "\n" + base_js,
160
161
  }