okstra 0.174.0 → 0.176.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) 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/{src → dist}/commands/execute/team.mjs +29 -36
  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/{src → dist}/commands/execute/worktree-status.mjs +52 -46
  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/{src → dist}/commands/report/finalize.mjs +25 -32
  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 +6 -2
  232. package/docs/architecture.md +29 -20
  233. package/docs/cli.md +40 -16
  234. package/docs/contributor-change-matrix.md +2 -2
  235. package/docs/for-ai/skills/okstra-inspect.md +2 -3
  236. package/docs/for-ai/skills/okstra-rollup.md +1 -0
  237. package/docs/project-structure-overview.md +72 -62
  238. package/docs/task-process/README.md +12 -8
  239. package/docs/task-process/common-flow.md +13 -16
  240. package/docs/task-process/error-analysis.md +9 -10
  241. package/docs/task-process/final-verification.md +7 -7
  242. package/docs/task-process/implementation-planning.md +9 -9
  243. package/docs/task-process/implementation.md +6 -6
  244. package/docs/task-process/release-handoff.md +8 -7
  245. package/docs/task-process/requirements-discovery.md +8 -8
  246. package/package.json +11 -5
  247. package/runtime/BUILD.json +2 -2
  248. package/runtime/agents/workers/claude-worker.md +2 -1
  249. package/runtime/agents/workers/report-writer-worker.md +3 -1
  250. package/runtime/agents/workers/translator-worker.md +3 -1
  251. package/runtime/bin/lib/okstra/cli.sh +12 -0
  252. package/runtime/bin/lib/okstra/globals.sh +3 -0
  253. package/runtime/bin/lib/okstra/interactive.sh +12 -6
  254. package/runtime/bin/lib/okstra/usage.sh +7 -6
  255. package/runtime/bin/okstra-error-log.py +27 -0
  256. package/runtime/bin/okstra-provider-exec.py +217 -5
  257. package/runtime/bin/okstra-spawn-followups.py +4 -2
  258. package/runtime/bin/okstra.sh +10 -0
  259. package/runtime/prompts/launch.template.md +2 -2
  260. package/runtime/prompts/lead/context-loader.md +1 -2
  261. package/runtime/prompts/lead/convergence.md +27 -18
  262. package/runtime/prompts/lead/okstra-lead-contract.md +6 -5
  263. package/runtime/prompts/lead/plan-body-verification.md +10 -3
  264. package/runtime/prompts/lead/report-writer.md +15 -1
  265. package/runtime/prompts/lead/team-contract.md +17 -13
  266. package/runtime/prompts/profiles/_implementation-deliverable.md +1 -1
  267. package/runtime/prompts/profiles/_implementation-executor.md +0 -3
  268. package/runtime/prompts/profiles/_implementation-verifier.md +3 -7
  269. package/runtime/prompts/profiles/change-impact-analysis.md +20 -0
  270. package/runtime/prompts/profiles/error-analysis.md +27 -1
  271. package/runtime/prompts/profiles/feature-analysis.md +20 -0
  272. package/runtime/prompts/profiles/final-verification.md +20 -1
  273. package/runtime/prompts/profiles/implementation-option-selection.md +20 -0
  274. package/runtime/prompts/profiles/implementation-planning.md +26 -0
  275. package/runtime/prompts/profiles/implementation.md +19 -0
  276. package/runtime/prompts/profiles/improvement-discovery.md +21 -1
  277. package/runtime/prompts/profiles/project-analysis.md +20 -0
  278. package/runtime/prompts/profiles/release-handoff.md +4 -0
  279. package/runtime/prompts/profiles/requirements-discovery.md +27 -1
  280. package/runtime/prompts/wizard/prompts.ko.json +35 -0
  281. package/runtime/python/okstra_ctl/adapters/dispatch/cli_wrapper.py +8 -0
  282. package/runtime/python/okstra_ctl/adapters/dispatch/cmux.py +8 -0
  283. package/runtime/python/okstra_ctl/adapters/dispatch/native_team.py +13 -0
  284. package/runtime/python/okstra_ctl/adapters/hosts/antigravity/adapter.py +2 -2
  285. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +25 -5
  286. package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +2 -2
  287. package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +4 -1
  288. package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +2 -2
  289. package/runtime/python/okstra_ctl/adapters/hosts/kimi/adapter.py +2 -2
  290. package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +79 -11
  291. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +95 -25
  292. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +59 -14
  293. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +55 -9
  294. package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +51 -12
  295. package/runtime/python/okstra_ctl/adapters/runtime/__init__.py +1 -0
  296. package/runtime/python/okstra_ctl/adapters/runtime/assembly.py +27 -0
  297. package/runtime/python/okstra_ctl/adapters/runtime/cli_wrapper.py +49 -0
  298. package/runtime/python/okstra_ctl/adapters/runtime/cmux.py +74 -0
  299. package/runtime/python/okstra_ctl/agent_activity.py +5 -1
  300. package/runtime/python/okstra_ctl/agent_invocation.py +608 -14
  301. package/runtime/python/okstra_ctl/agent_prompt_cli.py +249 -50
  302. package/runtime/python/okstra_ctl/application/open_worker.py +29 -0
  303. package/runtime/python/okstra_ctl/application/resolve_assignment.py +53 -12
  304. package/runtime/python/okstra_ctl/assignment_environment.py +102 -0
  305. package/runtime/python/okstra_ctl/assignment_resolver.py +790 -0
  306. package/runtime/python/okstra_ctl/attempt_evidence.py +342 -0
  307. package/runtime/python/okstra_ctl/convergence.py +409 -3
  308. package/runtime/python/okstra_ctl/convergence_engine.py +248 -15
  309. package/runtime/python/okstra_ctl/convergence_store.py +170 -1
  310. package/runtime/python/okstra_ctl/dispatch_core.py +1204 -155
  311. package/runtime/python/okstra_ctl/dispatch_state.py +696 -11
  312. package/runtime/python/okstra_ctl/doctor.py +14 -0
  313. package/runtime/python/okstra_ctl/domain/host.py +34 -1
  314. package/runtime/python/okstra_ctl/domain/provider.py +212 -6
  315. package/runtime/python/okstra_ctl/domain/role.py +125 -0
  316. package/runtime/python/okstra_ctl/domain/wizard/interaction.py +18 -0
  317. package/runtime/python/okstra_ctl/domain/worker_exec.py +34 -1
  318. package/runtime/python/okstra_ctl/domain/worker_role.py +13 -3
  319. package/runtime/python/okstra_ctl/domain/worker_runtime.py +44 -0
  320. package/runtime/python/okstra_ctl/domain/worker_stream.py +24 -0
  321. package/runtime/python/okstra_ctl/error_log_write.py +9 -0
  322. package/runtime/python/okstra_ctl/error_report.py +21 -1
  323. package/runtime/python/okstra_ctl/execution_identity.py +529 -0
  324. package/runtime/python/okstra_ctl/execution_manifest.py +1281 -0
  325. package/runtime/python/okstra_ctl/execution_mutation_audit.py +619 -0
  326. package/runtime/python/okstra_ctl/implementation_outcome.py +21 -5
  327. package/runtime/python/okstra_ctl/index.py +6 -2
  328. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +43 -3
  329. package/runtime/python/okstra_ctl/legacy_model_selection.py +578 -0
  330. package/runtime/python/okstra_ctl/manager_sync.py +4 -1
  331. package/runtime/python/okstra_ctl/model_cli.py +278 -0
  332. package/runtime/python/okstra_ctl/model_defaults.py +46 -0
  333. package/runtime/python/okstra_ctl/model_discovery.py +26 -7
  334. package/runtime/python/okstra_ctl/model_pool.py +501 -0
  335. package/runtime/python/okstra_ctl/models.py +47 -27
  336. package/runtime/python/okstra_ctl/mutation_recovery.py +293 -0
  337. package/runtime/python/okstra_ctl/next_phase.py +236 -0
  338. package/runtime/python/okstra_ctl/pane_title.py +151 -0
  339. package/runtime/python/okstra_ctl/ports/host_model.py +48 -17
  340. package/runtime/python/okstra_ctl/ports/worker_dispatch.py +4 -0
  341. package/runtime/python/okstra_ctl/ports/worker_runtime.py +26 -0
  342. package/runtime/python/okstra_ctl/recap.py +4 -1
  343. package/runtime/python/okstra_ctl/registry/provider_registry.py +8 -4
  344. package/runtime/python/okstra_ctl/render.py +124 -57
  345. package/runtime/python/okstra_ctl/render_final_report.py +19 -0
  346. package/runtime/python/okstra_ctl/report_contract.py +39 -0
  347. package/runtime/python/okstra_ctl/report_finalize.py +16 -0
  348. package/runtime/python/okstra_ctl/report_html/render.py +1 -0
  349. package/runtime/python/okstra_ctl/role_requirements.py +210 -0
  350. package/runtime/python/okstra_ctl/rollup.py +4 -1
  351. package/runtime/python/okstra_ctl/run.py +1210 -258
  352. package/runtime/python/okstra_ctl/run_index_row.py +7 -1
  353. package/runtime/python/okstra_ctl/stage_fix_carry.py +17 -1
  354. package/runtime/python/okstra_ctl/team.py +14 -18
  355. package/runtime/python/okstra_ctl/time_report.py +38 -4
  356. package/runtime/python/okstra_ctl/usage_identity.py +86 -0
  357. package/runtime/python/okstra_ctl/usage_report.py +15 -3
  358. package/runtime/python/okstra_ctl/wizard.py +1449 -66
  359. package/runtime/python/okstra_ctl/worker_audit_ledger.py +68 -65
  360. package/runtime/python/okstra_ctl/worker_prompt_body.py +18 -2
  361. package/runtime/python/okstra_ctl/worker_prompt_contract.py +40 -9
  362. package/runtime/python/okstra_ctl/worker_prompt_headers.py +16 -1
  363. package/runtime/python/okstra_ctl/worker_request.py +7 -1
  364. package/runtime/python/okstra_ctl/worker_runner.py +130 -5
  365. package/runtime/python/okstra_ctl/workers.py +7 -5
  366. package/runtime/python/okstra_ctl/workflow.py +18 -32
  367. package/runtime/python/okstra_ctl/worktree.py +3 -3
  368. package/runtime/python/okstra_ctl/worktree_registry.py +5 -4
  369. package/runtime/python/okstra_ctl/write_policy.py +571 -0
  370. package/runtime/python/okstra_project/state.py +54 -6
  371. package/runtime/schemas/convergence-groups-v2.0.schema.json +128 -0
  372. package/runtime/schemas/execution-manifest-v2.schema.json +340 -0
  373. package/runtime/schemas/final-report-v2.0.schema.json +68 -5
  374. package/runtime/skills/okstra-inspect/facets/recap.md +2 -0
  375. package/runtime/skills/okstra-inspect/facets/report.md +1 -1
  376. package/runtime/skills/okstra-inspect/facets/status.md +15 -13
  377. package/runtime/skills/okstra-rollup/SKILL.md +1 -0
  378. package/runtime/skills/okstra-run/SKILL.md +14 -14
  379. package/runtime/templates/implementation-worker-preamble.md +3 -18
  380. package/runtime/templates/project-docs/task-index.template.md +0 -1
  381. package/runtime/templates/report-writer-prompt-preamble.md +2 -0
  382. package/runtime/templates/reports/final-report-v2.template.md +7 -1
  383. package/runtime/templates/reports/html/base.template.html +21 -0
  384. package/runtime/templates/reports/html/i18n/en.json +4 -0
  385. package/runtime/templates/reports/html/i18n/ko.json +4 -0
  386. package/runtime/templates/reports/html/macros/forms.html +5 -4
  387. package/runtime/templates/reports/html/tasks/final-verification.template.html +1 -1
  388. package/runtime/templates/reports/html/tasks/implementation.template.html +1 -1
  389. package/runtime/templates/worker-prompt-preamble.md +6 -35
  390. package/runtime/validators/lib/fixtures.sh +49 -16
  391. package/runtime/validators/validate-run.py +486 -134
  392. package/runtime/validators/validate_session_conformance.py +25 -7
  393. package/src/cli-registry.mjs +0 -539
  394. package/src/commands/execute/codex-dispatch.mjs +0 -10
  395. package/src/commands/execute/plan-items.mjs +0 -9
  396. package/src/commands/execute/plan-validate.mjs +0 -68
  397. package/src/commands/execute/run.mjs +0 -208
  398. package/src/commands/execute/wizard.mjs +0 -167
  399. package/src/commands/inspect/stage-map.mjs +0 -98
  400. package/src/commands/inspect/task-list.mjs +0 -97
  401. package/src/commands/inspect/task-show.mjs +0 -100
  402. package/src/commands/lifecycle/check-project.mjs +0 -187
  403. package/src/commands/lifecycle/config.mjs +0 -385
  404. package/src/commands/lifecycle/doctor.mjs +0 -308
  405. package/src/commands/lifecycle/install.mjs +0 -1170
  406. package/src/commands/lifecycle/paths.mjs +0 -104
  407. package/src/commands/lifecycle/preflight.mjs +0 -125
  408. package/src/commands/lifecycle/setup.mjs +0 -333
  409. package/src/commands/lifecycle/uninstall.mjs +0 -279
  410. package/src/commands/memory/memory.mjs +0 -524
  411. package/src/commands/pr/pr.mjs +0 -261
  412. package/src/lib/host-registry-client.mjs +0 -176
  413. package/src/lib/paths.mjs +0 -86
  414. package/src/lib/proc.mjs +0 -31
  415. package/src/lib/python-helper.mjs +0 -142
  416. package/src/lib/runtime-manifest.mjs +0 -70
  417. package/src/lib/skill-catalog.mjs +0 -53
  418. package/src/lib/version.mjs +0 -20
  419. /package/{src → dist}/commands/pr/default.md +0 -0
@@ -2,7 +2,10 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  from dataclasses import dataclass
5
- from typing import Protocol
5
+ from typing import Literal, Protocol
6
+
7
+ from okstra_ctl.domain.provider import HostModelBinding
8
+ from okstra_ctl.domain.worker_exec import WorkerWriteCapability
6
9
 
7
10
 
8
11
  class HostModelBindingError(ValueError):
@@ -13,48 +16,76 @@ class HostModelBindingError(ValueError):
13
16
  class HostModelBindingRequest:
14
17
  host_runtime: str
15
18
  provider: str
16
- model: str
19
+ model_ref: str
20
+ version_kind: Literal["pinned", "channel"]
17
21
  model_execution_value: str
18
22
  runner: str
23
+ resolved_execution_value: str | None = None
24
+ worker_write_capability: WorkerWriteCapability | None = None
25
+
26
+ @property
27
+ def execution_value(self) -> str:
28
+ return self.resolved_execution_value or self.model_execution_value
19
29
 
20
30
 
21
31
  class HostModelBindingPort(Protocol):
22
- def resolve(self, request: HostModelBindingRequest) -> str | None: ...
32
+ def resolve(self, request: HostModelBindingRequest) -> HostModelBinding: ...
33
+
34
+
35
+ def _cli_binding(request: HostModelBindingRequest) -> HostModelBinding:
36
+ return HostModelBinding(
37
+ runner=request.runner,
38
+ catalog_execution_value=request.model_execution_value,
39
+ resolved_execution_value=request.execution_value,
40
+ host_model_value=None,
41
+ binding_fidelity="exact",
42
+ worker_write_capability=request.worker_write_capability,
43
+ )
23
44
 
24
45
 
25
46
  class FailClosedHostModelBindingPort:
26
47
  def __init__(self, host_runtime: str) -> None:
27
48
  self._host_runtime = host_runtime
28
49
 
29
- def resolve(self, request: HostModelBindingRequest) -> str | None:
50
+ def resolve(self, request: HostModelBindingRequest) -> HostModelBinding:
30
51
  if request.runner == "cli-wrapper":
31
- return None
52
+ return _cli_binding(request)
32
53
  raise HostModelBindingError(
33
54
  "unsupported host model: "
34
55
  f"host={self._host_runtime!r}, provider={request.provider!r}, "
35
- f"model={request.model_execution_value!r}"
56
+ f"model={request.execution_value!r}"
36
57
  )
37
58
 
38
59
 
39
60
  class NativeExecutionValueHostModelBindingPort:
40
- """Explicit policy for hosts whose native API accepts provider CLI values."""
61
+ """Binding for hosts whose native API accepts provider execution values."""
41
62
 
42
63
  def __init__(self, host_runtime: str, native_provider: str) -> None:
43
64
  self._host_runtime = host_runtime
44
65
  self._native_provider = native_provider
45
66
 
46
- def resolve(self, request: HostModelBindingRequest) -> str | None:
67
+ def resolve(self, request: HostModelBindingRequest) -> HostModelBinding:
47
68
  if request.runner == "cli-wrapper":
48
- return None
49
- if (
50
- request.runner != "native-session"
51
- or request.host_runtime != self._host_runtime
52
- or request.provider != self._native_provider
53
- or not request.model_execution_value.strip()
54
- ):
69
+ return _cli_binding(request)
70
+ if not self._supports_native(request):
55
71
  raise HostModelBindingError(
56
72
  "unsupported host model: "
57
73
  f"host={request.host_runtime!r}, provider={request.provider!r}, "
58
- f"model={request.model_execution_value!r}"
74
+ f"model={request.execution_value!r}"
59
75
  )
60
- return request.model_execution_value
76
+ return HostModelBinding(
77
+ runner=request.runner,
78
+ catalog_execution_value=request.model_execution_value,
79
+ resolved_execution_value=request.execution_value,
80
+ host_model_value=request.execution_value,
81
+ binding_fidelity="exact",
82
+ worker_write_capability=request.worker_write_capability,
83
+ )
84
+
85
+ def _supports_native(self, request: HostModelBindingRequest) -> bool:
86
+ return (
87
+ request.runner == "native-session"
88
+ and request.host_runtime == self._host_runtime
89
+ and request.provider == self._native_provider
90
+ and bool(request.execution_value.strip())
91
+ )
@@ -5,6 +5,8 @@ from dataclasses import dataclass
5
5
  from pathlib import Path
6
6
  from typing import Literal, Protocol
7
7
 
8
+ from okstra_ctl.domain.worker_exec import WorkerWriteCapability
9
+
8
10
 
9
11
  @dataclass(frozen=True)
10
12
  class WorkerDispatchRequest:
@@ -30,3 +32,5 @@ class WorkerDispatchPlan:
30
32
 
31
33
  class WorkerDispatchPort(Protocol):
32
34
  def build_plan(self, request: WorkerDispatchRequest) -> WorkerDispatchPlan: ...
35
+
36
+ def worker_write_capability(self) -> WorkerWriteCapability: ...
@@ -0,0 +1,26 @@
1
+ """Port for opening, closing, and signalling a worker execution surface."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Protocol
5
+
6
+ from ..domain.worker_runtime import (
7
+ ProgressEvent,
8
+ RuntimeHandle,
9
+ WorkerSpawnRequest,
10
+ )
11
+
12
+
13
+ class WorkerRuntimePort(Protocol):
14
+ surface: str
15
+
16
+ def spawn(self, request: WorkerSpawnRequest) -> RuntimeHandle: ...
17
+
18
+ def close(self, handle: RuntimeHandle) -> None: ...
19
+
20
+ def wait(self, handle: RuntimeHandle) -> int: ...
21
+
22
+ def terminate(self, handle: RuntimeHandle) -> None: ...
23
+
24
+ def notify(self, event: ProgressEvent) -> None: ...
25
+
26
+ def restore_lead(self) -> None: ...
@@ -11,6 +11,7 @@ import json
11
11
  import sys
12
12
  from pathlib import Path
13
13
 
14
+ from okstra_ctl import next_phase
14
15
  from okstra_ctl.clarification_items import sidecar_answers, user_response_sidecars
15
16
  from okstra_ctl.ids import slugify_task_segment
16
17
  from okstra_ctl.incremental_scope import preview_link_availability_for_report
@@ -108,7 +109,9 @@ def assemble_recap(task_root: Path, project_root: Path) -> dict:
108
109
  "fromPhase": prev_phase,
109
110
  "toPhase": cur,
110
111
  "lastCompletedPhase": snap.get("lastCompletedPhase", ""),
111
- "nextRecommendedPhase": snap.get("nextRecommendedPhase", ""),
112
+ "nextRecommendedPhase": next_phase.promote(
113
+ snap.get("nextRecommendedPhase")
114
+ ),
112
115
  "reportPath": run.get("reportPath", ""),
113
116
  })
114
117
  prev_phase = cur
@@ -46,7 +46,7 @@ class ProviderRegistry:
46
46
  return tuple(
47
47
  provider_id
48
48
  for provider_id, spec in self._providers.items()
49
- if role in spec.supported_roles
49
+ if spec.supports_role(role)
50
50
  )
51
51
 
52
52
  @property
@@ -72,10 +72,14 @@ def _registry_for_roots(
72
72
  ) -> ProviderRegistry:
73
73
  discovered = ProviderRegistry.from_roots((bundled_root, user_root)).providers
74
74
  bundled_order = ("claude", "antigravity", "codex", "grok", "kimi")
75
- ordered = {provider_id: discovered[provider_id] for provider_id in bundled_order}
75
+ ordered = {
76
+ provider_id: discovered[provider_id]
77
+ for provider_id in bundled_order
78
+ if provider_id in discovered
79
+ }
76
80
  ordered.update(
77
- (provider_id, spec)
78
- for provider_id, spec in discovered.items()
81
+ (provider_id, discovered[provider_id])
82
+ for provider_id in sorted(discovered)
79
83
  if provider_id not in ordered
80
84
  )
81
85
  return ProviderRegistry(ordered)
@@ -30,6 +30,7 @@ from okstra_project.dirs import TASK_MANIFEST_FILENAME, OKSTRA_DIR_NAME, project
30
30
  # render_task_manifest 가 동일한 리스트/딕셔너리를 로컬에 중복 정의했는데,
31
31
  # 이는 silent drift 위험이 있어 SSOT import 로 통합한다.
32
32
  from . import fix_cycles
33
+ from . import next_phase
33
34
  from .analysis_inputs import ANALYSIS_TASK_TYPES
34
35
  from .application.resolve_assignment import (
35
36
  resolve_assignment_runner,
@@ -46,7 +47,7 @@ from .registry.provider_registry import default_provider_registry
46
47
  from .path_hints import compact_active_run_context, hydrate_run_context
47
48
  from .paths import task_timeline_file
48
49
  from .worker_artifacts import artifacts_from_context
49
- from .workflow import DEFAULT_NEXT_PHASE, PHASE_SEQUENCE
50
+ from .workflow import PHASE_SEQUENCE
50
51
 
51
52
 
52
53
  class TokenRenderError(Exception):
@@ -84,6 +85,16 @@ def _write_json(path: Path, payload: dict) -> None:
84
85
  _write_text(path, json.dumps(payload, indent=2, ensure_ascii=False) + "\n")
85
86
 
86
87
 
88
+ def _execution_identity(ctx: Mapping[str, object]) -> dict:
89
+ raw = ctx.get("EXECUTION_IDENTITY_JSON")
90
+ if not isinstance(raw, str) or not raw.strip():
91
+ return {}
92
+ payload = json.loads(raw)
93
+ if not isinstance(payload, dict):
94
+ raise TokenRenderError("EXECUTION_IDENTITY_JSON must contain an object")
95
+ return payload
96
+
97
+
87
98
  def _lead_runtime(ctx: dict) -> str:
88
99
  return ctx.get("LEAD_RUNTIME", "") or "claude-code"
89
100
 
@@ -377,10 +388,14 @@ def _agent_contract(ctx: dict) -> dict:
377
388
  return parsed
378
389
 
379
390
 
380
- def _executor_contract(ctx: dict) -> dict:
391
+ def _executor_contract(ctx: dict) -> dict | None:
381
392
  runner = ctx.get("EXECUTOR_RUNNER", "")
382
393
  dispatch_mode = ctx.get("EXECUTOR_DISPATCH_MODE", "")
383
394
  host_model_value = ctx.get("EXECUTOR_HOST_MODEL_VALUE") or None
395
+ if not runner and not ctx.get("EXECUTOR_PROVIDER"):
396
+ if ctx.get("TASK_TYPE") == "implementation":
397
+ raise ValueError("implementation executor assignment is required")
398
+ return None
384
399
  expected_mode = {
385
400
  "native-session": "host-native",
386
401
  "cli-wrapper": "worker-dispatch",
@@ -549,9 +564,10 @@ def _active_workflow(ctx: dict) -> dict:
549
564
  "currentPhase": ctx.get("WORKFLOW_CURRENT_PHASE", ""),
550
565
  "currentPhaseState": ctx.get("WORKFLOW_CURRENT_PHASE_STATE", ""),
551
566
  "lastCompletedPhase": ctx.get("WORKFLOW_LAST_COMPLETED_PHASE", ""),
552
- "nextRecommendedPhase": ctx.get("WORKFLOW_NEXT_RECOMMENDED_PHASE", ""),
567
+ "nextRecommendedPhase": next_phase.promote(
568
+ ctx.get("WORKFLOW_NEXT_RECOMMENDED_PHASE")
569
+ ),
553
570
  "awaitingApproval": ctx.get("WORKFLOW_AWAITING_APPROVAL", "false") == "true",
554
- "routingStatus": ctx.get("WORKFLOW_ROUTING_STATUS", ""),
555
571
  "allowedOutputs": ctx.get("PHASE_ALLOWED_OUTPUTS", ""),
556
572
  "forbiddenActions": ctx.get("PHASE_FORBIDDEN_ACTIONS", ""),
557
573
  }
@@ -903,15 +919,14 @@ def render_task_catalog_discovery(output_path: str, ctx: dict) -> None:
903
919
  "lastCompletedPhase": (workflow or {}).get("lastCompletedPhase", "")
904
920
  if isinstance(workflow, dict)
905
921
  else "",
906
- "nextRecommendedPhase": (workflow or {}).get("nextRecommendedPhase", "")
907
- if isinstance(workflow, dict)
908
- else "",
922
+ "nextRecommendedPhase": next_phase.promote(
923
+ (workflow or {}).get("nextRecommendedPhase")
924
+ if isinstance(workflow, dict)
925
+ else None
926
+ ),
909
927
  "awaitingApproval": (workflow or {}).get("awaitingApproval", False)
910
928
  if isinstance(workflow, dict)
911
929
  else False,
912
- "routingStatus": (workflow or {}).get("routingStatus", "")
913
- if isinstance(workflow, dict)
914
- else "",
915
930
  "taskRootPath": s(manifest, "taskRootPath") or rel(task_root),
916
931
  "taskManifestPath": s(manifest, "taskManifestPath")
917
932
  or rel(manifest_path),
@@ -1177,27 +1192,31 @@ def _derive_phase_states(existing_workflow: dict, ctx: dict) -> tuple[dict, str,
1177
1192
 
1178
1193
 
1179
1194
  def _derive_next_recommended_phase(
1180
- current_phase: str, current_phase_state: str, existing_workflow: dict, ctx: dict
1181
- ) -> str:
1182
- """canonical next phase 를 deterministic 하게 결정한다.
1183
-
1184
- `current_phase` 가 terminal-success(`completed`) 가 아니면 그 자리에 머무르도록
1185
- recommend 한다. `prepared` / `in-progress` / `blocked` / `error` /
1186
- `contract-violated` 상태에서 다음 phase 로 미리 advance 하면 wizard(와 사람)
1187
- 가 현재 phase 를 끝난 것으로 오인한다. (Historical bug: implementation 이
1188
- `prepared` 상태로 provision 됐을 때 wizard 가 `final-verification` 을 기본
1189
- task_type 으로 추천했다.)"""
1190
- canonical_next = DEFAULT_NEXT_PHASE.get(current_phase) or ctx.get(
1191
- "WORKFLOW_NEXT_RECOMMENDED_PHASE", "unknown"
1195
+ existing_workflow: dict, current_phase_state: str
1196
+ ) -> dict:
1197
+ """prepare 시점의 포인터를 만든다.
1198
+
1199
+ 대상 phase 는 리드가 정한다. prepare 는 그 값을 보존하고 진행 가능 여부만
1200
+ 내린다 — 실행이 아직 안 끝났는데 다음을 제안하면 위저드가 현재 phase 를
1201
+ 끝난 것으로 오인한다. (기록된 사례: implementation 이 `prepared` 로
1202
+ 준비됐을 때 위저드가 `final-verification` 을 기본 task_type 으로 추천했다.)
1203
+
1204
+ 현재 phase 가 `completed` 면 그 오인이 성립하지 않으므로 포인터를 승격만 해서
1205
+ 그대로 돌려준다. 내리는 대상도 `ready` 하나뿐이다 — `pending` · `blocked` ·
1206
+ `terminal` 은 애초에 착수를 부르지 않는 상태라 내려봐야 얻는 것이 없고,
1207
+ 리드가 판단해 적은 `blocked`(사람이 봐야 하는 상태)와 `terminal`(생애주기
1208
+ 종료)을 `pending`("아직 아무도 정하지 않음")으로 지워버린다.
1209
+ """
1210
+ pointer = next_phase.promote(existing_workflow.get("nextRecommendedPhase"))
1211
+ if current_phase_state == "completed":
1212
+ return pointer
1213
+ if pointer["status"] != next_phase.STATUS_READY:
1214
+ return pointer
1215
+ return next_phase.make(
1216
+ phase=pointer["phase"],
1217
+ status=next_phase.STATUS_PENDING,
1218
+ rationale=pointer["rationale"],
1192
1219
  )
1193
- existing_next = existing_workflow.get("nextRecommendedPhase") or ""
1194
- if current_phase_state not in {"completed"}:
1195
- # 아직 끝나지 않음 — 같은 phase 를 추천한다. sequence 상 더 앞을 가리키는
1196
- # stale existing_next 는 무시하고, 같은 current_phase 를 가리키는 값만 존중.
1197
- return current_phase
1198
- if existing_next and existing_next != current_phase:
1199
- return existing_next
1200
- return canonical_next
1201
1220
 
1202
1221
 
1203
1222
  def _derive_latest_pointers(existing: dict, ctx: dict, current_report_relative: str) -> dict:
@@ -1306,14 +1325,11 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
1306
1325
  "WORKFLOW_WORK_CATEGORY", "unknown"
1307
1326
  )
1308
1327
  next_recommended_phase = _derive_next_recommended_phase(
1309
- current_phase, current_phase_state, existing_workflow, ctx
1328
+ existing_workflow, current_phase_state
1310
1329
  )
1311
1330
  last_completed_phase = existing_workflow.get("lastCompletedPhase") or ctx.get(
1312
1331
  "WORKFLOW_LAST_COMPLETED_PHASE", ""
1313
1332
  )
1314
- routing_status = existing_workflow.get("routingStatus") or ctx.get(
1315
- "WORKFLOW_ROUTING_STATUS", "not-applicable"
1316
- )
1317
1333
  awaiting_approval = existing_workflow.get("awaitingApproval")
1318
1334
  if not isinstance(awaiting_approval, bool):
1319
1335
  awaiting_approval = ctx.get("WORKFLOW_AWAITING_APPROVAL", "false") == "true"
@@ -1326,8 +1342,14 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
1326
1342
  latest_team_state_relative = pointers["latestTeamStatePath"]
1327
1343
  latest_resume_command_relative = pointers["latestResumeCommandPath"]
1328
1344
  convergence_block = _build_convergence_block(ctx)
1345
+ execution_identity = _execution_identity(ctx)
1346
+ existing_v2 = (
1347
+ existing.get("schemaVersion") == "2.0"
1348
+ and existing.get("executionIdentityVersion") == 2
1349
+ )
1350
+ writes_v2 = execution_identity.get("executionIdentityVersion") == 2
1329
1351
  payload = {
1330
- "schemaVersion": "1.0",
1352
+ "schemaVersion": "2.0" if existing_v2 or writes_v2 else "1.0",
1331
1353
  "projectId": ctx.get("PROJECT_ID", ""),
1332
1354
  "taskGroup": ctx.get("TASK_GROUP", ""),
1333
1355
  "taskId": ctx.get("TASK_ID", ""),
@@ -1379,7 +1401,6 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
1379
1401
  "lastCompletedPhase": last_completed_phase,
1380
1402
  "nextRecommendedPhase": next_recommended_phase,
1381
1403
  "awaitingApproval": awaiting_approval,
1382
- "routingStatus": routing_status,
1383
1404
  "lastSafeCheckpoint": last_safe_checkpoint,
1384
1405
  },
1385
1406
  "artifacts": {
@@ -1487,6 +1508,12 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
1487
1508
  "sessionId": ctx.get("CLAUDE_SESSION_ID", ""),
1488
1509
  "resumeCommandPath": ctx.get("CLAUDE_RESUME_COMMAND_RELATIVE_PATH", ""),
1489
1510
  }
1511
+ if existing_v2 or writes_v2:
1512
+ existing_index = existing.get("roleExecutionIndex")
1513
+ payload["executionIdentityVersion"] = 2
1514
+ payload["roleExecutionIndex"] = (
1515
+ list(existing_index) if isinstance(existing_index, list) else []
1516
+ )
1490
1517
  _write_json(path, payload)
1491
1518
 
1492
1519
 
@@ -1608,8 +1635,9 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
1608
1635
  for s in str(ctx.get("CONCURRENT_RUN_STAGES", "") or "").split(",")
1609
1636
  if s.strip().isdigit()
1610
1637
  ]
1638
+ execution_identity = _execution_identity(ctx)
1611
1639
  payload = {
1612
- "schemaVersion": "1.0",
1640
+ "schemaVersion": execution_identity.get("schemaVersion", "1.0"),
1613
1641
  "okstraVersion": ctx.get("OKSTRA_VERSION", ""),
1614
1642
  "projectId": ctx.get("PROJECT_ID", ""),
1615
1643
  "taskGroup": ctx.get("TASK_GROUP", ""),
@@ -1719,16 +1747,13 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
1719
1747
  "lastCompletedPhase": workflow.get(
1720
1748
  "lastCompletedPhase", ctx.get("WORKFLOW_LAST_COMPLETED_PHASE", "")
1721
1749
  ),
1722
- "nextRecommendedPhase": workflow.get(
1723
- "nextRecommendedPhase", ctx.get("WORKFLOW_NEXT_RECOMMENDED_PHASE", "")
1750
+ "nextRecommendedPhase": next_phase.promote(
1751
+ workflow.get("nextRecommendedPhase")
1724
1752
  ),
1725
1753
  "awaitingApproval": workflow.get(
1726
1754
  "awaitingApproval",
1727
1755
  ctx.get("WORKFLOW_AWAITING_APPROVAL", "false") == "true",
1728
1756
  ),
1729
- "routingStatus": workflow.get(
1730
- "routingStatus", ctx.get("WORKFLOW_ROUTING_STATUS", "")
1731
- ),
1732
1757
  "lastSafeCheckpoint": workflow.get("lastSafeCheckpoint", {}),
1733
1758
  },
1734
1759
  "teamContract": {
@@ -1783,6 +1808,7 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
1783
1808
  )
1784
1809
  if _lead_runtime(ctx) == "claude-code":
1785
1810
  payload["claudeSessionId"] = ctx.get("CLAUDE_SESSION_ID", "")
1811
+ payload.update(execution_identity)
1786
1812
  _guard_invocation_manifest_rewrite(existing_run_manifest, payload)
1787
1813
  _write_json(Path(run_manifest_path), payload)
1788
1814
 
@@ -1795,6 +1821,11 @@ _MODEL_ASSIGNMENT_KEYS = {
1795
1821
  "hostRuntime",
1796
1822
  "hostModelValue",
1797
1823
  }
1824
+ _V2_INVOCATION_IDENTITY_KEYS = {
1825
+ "participantRef",
1826
+ "roleExecutionRef",
1827
+ "dutyId",
1828
+ }
1798
1829
  _AGENT_CONTRACT_KEYS = {
1799
1830
  "schemaVersion",
1800
1831
  "dutyRootPath",
@@ -1860,11 +1891,8 @@ def _validate_invocation_manifest(payload: Mapping[str, object]) -> None:
1860
1891
  or not assignments
1861
1892
  ):
1862
1893
  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")
1894
+ _invocation_manifest_identity_version(payload)
1895
+ _validate_invocation_assignment_shapes(assignments)
1868
1896
  _validate_compatibility_projections(payload, assignments)
1869
1897
  for key in (
1870
1898
  item
@@ -1901,13 +1929,17 @@ def _validate_compatibility_projections(
1901
1929
  workers = payload.get("workerAssignments")
1902
1930
  if not isinstance(lead, Mapping) or not isinstance(workers, list):
1903
1931
  raise ValueError("incomplete invocation manifest compatibility projection")
1904
- if _model_projection(lead) != assignments.get("lead"):
1932
+ if _model_projection(lead) != _model_projection_from_assignment(
1933
+ assignments.get("lead")
1934
+ ):
1905
1935
  raise ValueError("immutable invocation manifest lead projection drift")
1906
1936
  for worker in workers:
1907
1937
  if not isinstance(worker, Mapping):
1908
1938
  raise ValueError("incomplete invocation manifest worker projection")
1909
1939
  reference = f"initial/{worker.get('workerId', '')}"
1910
- if _model_projection(worker) != assignments.get(reference):
1940
+ if _model_projection(worker) != _model_projection_from_assignment(
1941
+ assignments.get(reference)
1942
+ ):
1911
1943
  raise ValueError("immutable invocation manifest worker projection drift")
1912
1944
 
1913
1945
 
@@ -1915,6 +1947,40 @@ def _model_projection(value: Mapping[str, object]) -> dict[str, object]:
1915
1947
  return {key: value.get(key) for key in _MODEL_ASSIGNMENT_KEYS}
1916
1948
 
1917
1949
 
1950
+ def _model_projection_from_assignment(value: object) -> dict[str, object] | None:
1951
+ if not isinstance(value, Mapping) or not _MODEL_ASSIGNMENT_KEYS.issubset(value):
1952
+ return None
1953
+ return _model_projection(value)
1954
+
1955
+
1956
+ def _invocation_manifest_identity_version(payload: Mapping[str, object]) -> int:
1957
+ schema = payload.get("schemaVersion")
1958
+ identity = payload.get("executionIdentityVersion")
1959
+ if schema == "2.0" and identity == 2:
1960
+ return 2
1961
+ if schema == "2.0" or identity is not None:
1962
+ raise ValueError("invocation manifest mixes v1 and v2 execution identity")
1963
+ if schema not in (None, 1, "1", "1.0"):
1964
+ raise ValueError("unsupported invocation manifest execution identity version")
1965
+ return 1
1966
+
1967
+
1968
+ def _validate_invocation_assignment_shapes(
1969
+ assignments: Mapping[str, object],
1970
+ ) -> None:
1971
+ allowed = {frozenset(_MODEL_ASSIGNMENT_KEYS)}
1972
+ for item in assignments.values():
1973
+ if not isinstance(item, Mapping):
1974
+ raise ValueError("incomplete invocation manifest assignments")
1975
+ keys = set(item)
1976
+ if keys & _V2_INVOCATION_IDENTITY_KEYS:
1977
+ raise ValueError(
1978
+ "invocation assignment duplicates v2 execution identity"
1979
+ )
1980
+ if frozenset(keys) not in allowed:
1981
+ raise ValueError("incomplete invocation manifest assignments")
1982
+
1983
+
1918
1984
  def render_timeline(timeline_path: str, ctx: dict) -> None:
1919
1985
  task_manifest_path = Path(ctx.get("TASK_MANIFEST_PATH", ""))
1920
1986
  task_manifest = {}
@@ -1983,9 +2049,10 @@ def render_timeline(timeline_path: str, ctx: dict) -> None:
1983
2049
  "currentPhaseState": workflow.get("currentPhaseState", ""),
1984
2050
  "phaseStates": workflow.get("phaseStates", {}),
1985
2051
  "lastCompletedPhase": workflow.get("lastCompletedPhase", ""),
1986
- "nextRecommendedPhase": workflow.get("nextRecommendedPhase", ""),
2052
+ "nextRecommendedPhase": next_phase.promote(
2053
+ workflow.get("nextRecommendedPhase")
2054
+ ),
1987
2055
  "awaitingApproval": workflow.get("awaitingApproval", False),
1988
- "routingStatus": workflow.get("routingStatus", ""),
1989
2056
  "lastSafeCheckpoint": workflow.get("lastSafeCheckpoint", {}),
1990
2057
  },
1991
2058
  }
@@ -2171,15 +2238,15 @@ def render_task_index(template_path: str, output_path: str, ctx: dict) -> None:
2171
2238
  "lastCompletedPhase", ctx.get("WORKFLOW_LAST_COMPLETED_PHASE", "")
2172
2239
  )
2173
2240
  or "--",
2174
- "{{WORKFLOW_NEXT_RECOMMENDED_PHASE}}": workflow.get(
2175
- "nextRecommendedPhase", ctx.get("WORKFLOW_NEXT_RECOMMENDED_PHASE", "")
2176
- ),
2241
+ # 토큰은 사람이 읽는 한 줄이라 문자열이어야 한다. 포인터의 `status` 는
2242
+ # 같은 페이지의 다른 줄이 아니라 소비자 로직이 읽는다.
2243
+ "{{WORKFLOW_NEXT_RECOMMENDED_PHASE}}": next_phase.promote(
2244
+ workflow.get("nextRecommendedPhase")
2245
+ )["phase"]
2246
+ or "--",
2177
2247
  "{{WORKFLOW_AWAITING_APPROVAL}}": "yes"
2178
2248
  if workflow.get("awaitingApproval", False)
2179
2249
  else "no",
2180
- "{{WORKFLOW_ROUTING_STATUS}}": workflow.get(
2181
- "routingStatus", ctx.get("WORKFLOW_ROUTING_STATUS", "")
2182
- ),
2183
2250
  "{{WORKFLOW_PHASE_STATE_LINES}}": "\n".join(phase_state_lines),
2184
2251
  "{{WORKFLOW_LAST_SAFE_CHECKPOINT_LINES}}": "\n".join(checkpoint_lines),
2185
2252
  "{{FIX_CYCLES_SUMMARY}}": _fix_cycles_index_line(task_manifest),
@@ -566,6 +566,8 @@ def _ai_markdown_context(data: dict, schema: dict | None) -> dict:
566
566
  context["md_rest"] = sections.rest
567
567
  context["md_has"] = sections.has
568
568
  context["md_claim"] = sections.mark_rendered
569
+ context["executionRolesTable"] = render_execution_roles_markdown(data)
570
+ context["executionIdentityVersion"] = data.get("executionIdentityVersion")
569
571
  return context
570
572
 
571
573
 
@@ -623,6 +625,23 @@ _OPTIONAL_ANALYSIS_DEFAULTS = (
623
625
  )
624
626
 
625
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
+
626
645
  def _with_optional_defaults(data: dict) -> dict:
627
646
  """Render context with schema-optional fields filled in.
628
647
 
@@ -170,6 +170,45 @@ def html_template_for(task_type: str) -> str:
170
170
  raise ValueError(f"no task-specific html template for: {task_type}") from exc
171
171
 
172
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
+
173
212
  def markdown_template_for(task_type: str) -> str:
174
213
  try:
175
214
  return TASK_TYPE_MARKDOWN_TEMPLATE[task_type]
@@ -38,6 +38,7 @@ from pathlib import Path
38
38
  from typing import Any, Callable, Mapping, Sequence
39
39
 
40
40
  from .agent_activity import ActivityProjectionError, project_agent_activity
41
+ from .report_contract import apply_execution_roles
41
42
  from .dispatch_state import DispatchError, link_agent_dispatch_result
42
43
  from .final_report_paths import final_report_data_path, final_report_markdown_path
43
44
  from .paths import task_dir, task_manifest_file
@@ -388,6 +389,7 @@ def run_finalize(
388
389
  steps: list[dict[str, Any]] = []
389
390
  deferred = ""
390
391
  try:
392
+ write_execution_roles(ctx)
391
393
  commands = build_commands(ctx)
392
394
  except FinalizeError as exc:
393
395
  return {"ok": False, "reason": str(exc), "steps": steps}
@@ -465,6 +467,20 @@ def run_finalize(
465
467
  return {"ok": True, "reason": "", "steps": steps}
466
468
 
467
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
+
468
484
  def _link_lead_result_for_validation(ctx: FinalizeContext) -> None:
469
485
  """Link the lead-authored synthesis artifact before Phase 7 validation."""
470
486
  manifest = _load_manifest(ctx.manifest_path)
@@ -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
  }