okstra 0.186.7 → 0.187.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 (541) hide show
  1. package/README.md +1 -1
  2. package/dist/cli-registry.d.mts +88 -1
  3. package/dist/cli-registry.mjs +68 -111
  4. package/dist/cli-registry.mjs.map +1 -1
  5. package/dist/commands/execute/render-bundle.mjs +0 -1
  6. package/dist/commands/execute/render-bundle.mjs.map +1 -1
  7. package/dist/commands/execute/run.mjs +8 -3
  8. package/dist/commands/execute/run.mjs.map +1 -1
  9. package/dist/commands/lifecycle/check-project.mjs +1 -14
  10. package/dist/commands/lifecycle/check-project.mjs.map +1 -1
  11. package/dist/commands/lifecycle/config.mjs +38 -40
  12. package/dist/commands/lifecycle/config.mjs.map +1 -1
  13. package/dist/commands/lifecycle/doctor.d.mts +22 -7
  14. package/dist/commands/lifecycle/doctor.mjs +77 -49
  15. package/dist/commands/lifecycle/doctor.mjs.map +1 -1
  16. package/dist/commands/lifecycle/install.d.mts +12 -10
  17. package/dist/commands/lifecycle/install.mjs +104 -39
  18. package/dist/commands/lifecycle/install.mjs.map +1 -1
  19. package/dist/commands/lifecycle/paths.mjs +8 -3
  20. package/dist/commands/lifecycle/paths.mjs.map +1 -1
  21. package/dist/commands/lifecycle/preflight.mjs +2 -1
  22. package/dist/commands/lifecycle/preflight.mjs.map +1 -1
  23. package/dist/commands/lifecycle/setup.mjs +22 -36
  24. package/dist/commands/lifecycle/setup.mjs.map +1 -1
  25. package/dist/commands/lifecycle/uninstall.d.mts +4 -2
  26. package/dist/commands/lifecycle/uninstall.mjs +59 -15
  27. package/dist/commands/lifecycle/uninstall.mjs.map +1 -1
  28. package/dist/commands/memory/memory.mjs +7 -1
  29. package/dist/commands/memory/memory.mjs.map +1 -1
  30. package/dist/lib/helper-scripts.d.mts +1 -1
  31. package/dist/lib/helper-scripts.mjs +10 -18
  32. package/dist/lib/helper-scripts.mjs.map +1 -1
  33. package/dist/lib/host-config.d.mts +72 -0
  34. package/dist/lib/host-config.mjs +404 -0
  35. package/dist/lib/host-config.mjs.map +1 -0
  36. package/dist/lib/host-registry-client.d.mts +2 -2
  37. package/dist/lib/host-registry-client.mjs +0 -3
  38. package/dist/lib/host-registry-client.mjs.map +1 -1
  39. package/dist/lib/install-assets.d.mts +1 -0
  40. package/dist/lib/install-assets.mjs +4 -0
  41. package/dist/lib/install-assets.mjs.map +1 -1
  42. package/dist/lib/proc.d.mts +2 -0
  43. package/dist/lib/proc.mjs +12 -0
  44. package/dist/lib/proc.mjs.map +1 -1
  45. package/dist/lib/python-command.d.mts +2 -0
  46. package/dist/lib/python-command.mjs +52 -0
  47. package/dist/lib/python-command.mjs.map +1 -0
  48. package/dist/lib/python-helper.d.mts +2 -5
  49. package/dist/lib/python-helper.mjs +3 -52
  50. package/dist/lib/python-helper.mjs.map +1 -1
  51. package/dist/lib/runtime-payload.d.mts +23 -0
  52. package/dist/lib/runtime-payload.mjs +57 -0
  53. package/dist/lib/runtime-payload.mjs.map +1 -0
  54. package/dist/lib/types.d.mts +24 -13
  55. package/docs/architecture/storage-model.md +21 -5
  56. package/docs/architecture.md +37 -36
  57. package/docs/cli.md +55 -76
  58. package/docs/coding-rules.md +295 -0
  59. package/docs/container.md +10 -36
  60. package/docs/contributor-change-matrix.md +1 -1
  61. package/docs/for-ai/skills/okstra-code-review.md +0 -1
  62. package/docs/for-ai/skills/okstra-container-build.md +8 -41
  63. package/docs/for-ai/skills/okstra-inspect.md +4 -4
  64. package/docs/for-ai/skills/okstra-manager.md +2 -2
  65. package/docs/for-ai/skills/okstra-rollup.md +0 -1
  66. package/docs/for-ai/skills/okstra-run.md +3 -3
  67. package/docs/for-ai/skills/okstra-user-response.md +0 -1
  68. package/docs/performance-improvement-plan-v2.md +1 -1
  69. package/docs/project-structure-overview.md +62 -62
  70. package/docs/task-process/README.md +3 -3
  71. package/docs/task-process/common-flow.md +1 -1
  72. package/docs/task-process/error-analysis.md +3 -3
  73. package/docs/task-process/implementation-planning.md +3 -1
  74. package/docs/task-process/release-handoff.md +1 -1
  75. package/package.json +3 -2
  76. package/runtime/BUILD.json +2 -2
  77. package/runtime/agents/workers/claude-worker.md +6 -11
  78. package/runtime/agents/workers/report-writer-worker.md +1 -1
  79. package/runtime/bin/lib/okstra/cli.sh +4 -0
  80. package/runtime/bin/lib/okstra/globals.sh +2 -0
  81. package/runtime/bin/lib/okstra/interactive.sh +26 -173
  82. package/runtime/bin/lib/okstra/project-resolver.sh +23 -61
  83. package/runtime/bin/lib/okstra/usage.sh +3 -3
  84. package/runtime/bin/okstra-compact-reminder.sh +1 -5
  85. package/runtime/bin/okstra-error-log.py +19 -2
  86. package/runtime/bin/okstra-import-check.py +26 -0
  87. package/runtime/bin/okstra-inject-report-index.py +5 -4
  88. package/runtime/bin/okstra-provider-exec.py +22 -17
  89. package/runtime/bin/okstra-render-final-report.py +16 -6
  90. package/runtime/bin/okstra-render-report-views.py +25 -18
  91. package/runtime/bin/okstra-report-translate.py +30 -18
  92. package/runtime/bin/okstra-spawn-followups.py +4 -0
  93. package/runtime/bin/okstra-token-usage.py +3 -0
  94. package/runtime/bin/okstra.sh +4 -2
  95. package/runtime/bin/okstra_bootstrap.py +58 -0
  96. package/runtime/prompts/coding-preflight/overview.md +1 -1
  97. package/runtime/prompts/duties/planning-worker.md +1 -1
  98. package/runtime/prompts/launch.template.md +35 -9
  99. package/runtime/prompts/lead/convergence.md +96 -87
  100. package/runtime/prompts/lead/okstra-lead-contract.md +63 -12
  101. package/runtime/prompts/lead/plan-body-verification.md +65 -46
  102. package/runtime/prompts/lead/report-writer.md +34 -4
  103. package/runtime/prompts/lead/team-contract.md +4 -1
  104. package/runtime/prompts/profiles/_clarification-recommendation.md +2 -1
  105. package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
  106. package/runtime/prompts/profiles/_common-contract.md +3 -3
  107. package/runtime/prompts/profiles/_coverage-critic.md +1 -1
  108. package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
  109. package/runtime/prompts/profiles/_implementation-executor.md +3 -1
  110. package/runtime/prompts/profiles/_implementation-verifier.md +16 -2
  111. package/runtime/prompts/profiles/error-analysis.md +4 -3
  112. package/runtime/prompts/profiles/final-verification.md +2 -2
  113. package/runtime/prompts/profiles/implementation-planning.md +22 -17
  114. package/runtime/prompts/profiles/implementation.md +3 -2
  115. package/runtime/prompts/profiles/requirements-discovery.md +2 -1
  116. package/runtime/prompts/wizard/prompts.ko.json +29 -8
  117. package/runtime/python/okstra_ctl/__init__.py +6 -27
  118. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +4 -5
  119. package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +21 -2
  120. package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +3 -12
  121. package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +40 -22
  122. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +14 -21
  123. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +37 -20
  124. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +86 -14
  125. package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +12 -12
  126. package/runtime/python/okstra_ctl/adapters/runtime/cmux.py +4 -7
  127. package/runtime/python/okstra_ctl/agent/__init__.py +1 -0
  128. package/runtime/python/okstra_ctl/{agent_activity.py → agent/activity.py} +12 -1
  129. package/runtime/python/okstra_ctl/{agent_invocation.py → agent/invocation.py} +34 -5
  130. package/runtime/python/okstra_ctl/agent/prompt_cli/__init__.py +19 -0
  131. package/runtime/python/okstra_ctl/agent/prompt_cli/__main__.py +11 -0
  132. package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +215 -0
  133. package/runtime/python/okstra_ctl/agent/prompt_cli/dynamic_verifier.py +155 -0
  134. package/runtime/python/okstra_ctl/agent/prompt_cli/emit.py +66 -0
  135. package/runtime/python/okstra_ctl/agent/prompt_cli/inputs.py +138 -0
  136. package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +408 -0
  137. package/runtime/python/okstra_ctl/agent/prompt_cli/results.py +197 -0
  138. package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +107 -0
  139. package/runtime/python/okstra_ctl/analysis_packet.py +47 -4
  140. package/runtime/python/okstra_ctl/approval_decisions.py +260 -8
  141. package/runtime/python/okstra_ctl/assignment_resolver.py +0 -12
  142. package/runtime/python/okstra_ctl/attempt_evidence.py +12 -24
  143. package/runtime/python/okstra_ctl/blocking_checks.py +251 -0
  144. package/runtime/python/okstra_ctl/brief_frontmatter.py +0 -7
  145. package/runtime/python/okstra_ctl/clarification_items/__init__.py +102 -0
  146. package/runtime/python/okstra_ctl/clarification_items/carry.py +224 -0
  147. package/runtime/python/okstra_ctl/clarification_items/dispositions.py +135 -0
  148. package/runtime/python/okstra_ctl/clarification_items/parsing.py +253 -0
  149. package/runtime/python/okstra_ctl/clarification_items/rows.py +115 -0
  150. package/runtime/python/okstra_ctl/clarification_items/scan.py +215 -0
  151. package/runtime/python/okstra_ctl/clarification_items/sidecars.py +211 -0
  152. package/runtime/python/okstra_ctl/cmux.py +38 -31
  153. package/runtime/python/okstra_ctl/code_review_target.py +163 -1
  154. package/runtime/python/okstra_ctl/conformance.py +23 -0
  155. package/runtime/python/okstra_ctl/container.py +38 -421
  156. package/runtime/python/okstra_ctl/context_cost.py +16 -2
  157. package/runtime/python/okstra_ctl/contract_graph_cli.py +7 -0
  158. package/runtime/python/okstra_ctl/convergence.py +101 -15
  159. package/runtime/python/okstra_ctl/convergence_critic_prompt.py +345 -0
  160. package/runtime/python/okstra_ctl/convergence_engine.py +382 -70
  161. package/runtime/python/okstra_ctl/convergence_store.py +5 -29
  162. package/runtime/python/okstra_ctl/design_prep.py +108 -12
  163. package/runtime/python/okstra_ctl/design_snapshot.py +11 -1
  164. package/runtime/python/okstra_ctl/design_surfaces.py +29 -2
  165. package/runtime/python/okstra_ctl/dispatch_core.py +102 -25
  166. package/runtime/python/okstra_ctl/dispatch_state.py +268 -39
  167. package/runtime/python/okstra_ctl/doctor_cli.py +48 -0
  168. package/runtime/python/okstra_ctl/domain/worker_exec.py +10 -5
  169. package/runtime/python/okstra_ctl/domain/worker_presentation.py +21 -1
  170. package/runtime/python/okstra_ctl/domain/worker_stream.py +52 -21
  171. package/runtime/python/okstra_ctl/domain/write_policy.py +219 -0
  172. package/runtime/python/okstra_ctl/entrypoints/hosts.py +9 -2
  173. package/runtime/python/okstra_ctl/error_log_core.py +1 -1
  174. package/runtime/python/okstra_ctl/error_report.py +14 -0
  175. package/runtime/python/okstra_ctl/error_zip.py +18 -2
  176. package/runtime/python/okstra_ctl/execution_identity.py +100 -7
  177. package/runtime/python/okstra_ctl/execution_manifest.py +67 -20
  178. package/runtime/python/okstra_ctl/execution_mutation_audit.py +90 -8
  179. package/runtime/python/okstra_ctl/final_report_paths.py +17 -0
  180. package/runtime/python/okstra_ctl/final_report_schema.py +90 -16
  181. package/runtime/python/okstra_ctl/git_reconcile.py +22 -2
  182. package/runtime/python/okstra_ctl/handoff.py +24 -1
  183. package/runtime/python/okstra_ctl/ids.py +6 -16
  184. package/runtime/python/okstra_ctl/implementation_direction.py +142 -33
  185. package/runtime/python/okstra_ctl/implementation_options.py +25 -11
  186. package/runtime/python/okstra_ctl/implementation_outcome.py +5 -2
  187. package/runtime/python/okstra_ctl/improvement_lenses.py +0 -14
  188. package/runtime/python/okstra_ctl/incremental_carry.py +69 -8
  189. package/runtime/python/okstra_ctl/incremental_scope.py +173 -12
  190. package/runtime/python/okstra_ctl/index.py +2 -2
  191. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +48 -8
  192. package/runtime/python/okstra_ctl/interactive_cli.py +223 -0
  193. package/runtime/python/okstra_ctl/json_boundary.py +10 -0
  194. package/runtime/python/okstra_ctl/listing.py +0 -91
  195. package/runtime/python/okstra_ctl/locks.py +5 -19
  196. package/runtime/python/okstra_ctl/log_report.py +14 -0
  197. package/runtime/python/okstra_ctl/manager_cli.py +22 -3
  198. package/runtime/python/okstra_ctl/manager_launch.py +4 -8
  199. package/runtime/python/okstra_ctl/material.py +2 -2
  200. package/runtime/python/okstra_ctl/migrate.py +23 -1
  201. package/runtime/python/okstra_ctl/model_cli.py +19 -5
  202. package/runtime/python/okstra_ctl/model_discovery.py +46 -47
  203. package/runtime/python/okstra_ctl/model_io/__init__.py +1 -0
  204. package/runtime/python/okstra_ctl/model_io/lines.py +163 -0
  205. package/runtime/python/okstra_ctl/model_io/references.py +370 -0
  206. package/runtime/python/okstra_ctl/model_io/renderers.py +498 -0
  207. package/runtime/python/okstra_ctl/model_io_cli.py +30 -945
  208. package/runtime/python/okstra_ctl/model_pool.py +10 -6
  209. package/runtime/python/okstra_ctl/models.py +8 -8
  210. package/runtime/python/okstra_ctl/mutation_recovery.py +5 -63
  211. package/runtime/python/okstra_ctl/next_phase.py +121 -69
  212. package/runtime/python/okstra_ctl/pane_reclaim.py +23 -0
  213. package/runtime/python/okstra_ctl/pane_title.py +11 -8
  214. package/runtime/python/okstra_ctl/path_hints.py +36 -11
  215. package/runtime/python/okstra_ctl/paths.py +76 -8
  216. package/runtime/python/okstra_ctl/plan_items.py +132 -16
  217. package/runtime/python/okstra_ctl/plan_items_cli.py +486 -36
  218. package/runtime/python/okstra_ctl/plan_run_root.py +1 -1
  219. package/runtime/python/okstra_ctl/plan_validate_cli.py +51 -0
  220. package/runtime/python/okstra_ctl/plan_verify_cli.py +80 -0
  221. package/runtime/python/okstra_ctl/prepare_error.py +20 -0
  222. package/runtime/python/okstra_ctl/prior_planning.py +157 -0
  223. package/runtime/python/okstra_ctl/profile_show.py +18 -1
  224. package/runtime/python/okstra_ctl/project_setup_cli.py +140 -0
  225. package/runtime/python/okstra_ctl/recap.py +22 -1
  226. package/runtime/python/okstra_ctl/reconcile.py +84 -0
  227. package/runtime/python/okstra_ctl/registry/host_registry.py +18 -1
  228. package/runtime/python/okstra_ctl/render.py +98 -32
  229. package/runtime/python/okstra_ctl/render_final_report.py +3 -7
  230. package/runtime/python/okstra_ctl/report_assembly.py +297 -7
  231. package/runtime/python/okstra_ctl/report_contract.py +30 -7
  232. package/runtime/python/okstra_ctl/report_finalize.py +206 -15
  233. package/runtime/python/okstra_ctl/report_html/common.py +2 -2
  234. package/runtime/python/okstra_ctl/report_html/render.py +8 -12
  235. package/runtime/python/okstra_ctl/report_language.py +8 -4
  236. package/runtime/python/okstra_ctl/report_narrative.py +90 -3
  237. package/runtime/python/okstra_ctl/report_projections.py +8 -2
  238. package/runtime/python/okstra_ctl/report_synthesis_packet.py +258 -1
  239. package/runtime/python/okstra_ctl/report_translation.py +2 -35
  240. package/runtime/python/okstra_ctl/report_views.py +4 -22
  241. package/runtime/python/okstra_ctl/resolve_task_key.py +13 -0
  242. package/runtime/python/okstra_ctl/rollup.py +13 -0
  243. package/runtime/python/okstra_ctl/run.py +639 -138
  244. package/runtime/python/okstra_ctl/run_audit.py +13 -0
  245. package/runtime/python/okstra_ctl/run_index_row.py +0 -12
  246. package/runtime/python/okstra_ctl/seeding.py +21 -11
  247. package/runtime/python/okstra_ctl/sequence.py +1 -1
  248. package/runtime/python/okstra_ctl/session.py +64 -14
  249. package/runtime/python/okstra_ctl/set_work_status.py +18 -0
  250. package/runtime/python/okstra_ctl/stage_integrate.py +15 -1
  251. package/runtime/python/okstra_ctl/stage_ledger.py +1 -1
  252. package/runtime/python/okstra_ctl/stage_map.py +57 -11
  253. package/runtime/python/okstra_ctl/stage_map_cli.py +83 -0
  254. package/runtime/python/okstra_ctl/stage_map_view.py +65 -0
  255. package/runtime/python/okstra_ctl/stage_targets.py +3 -12
  256. package/runtime/python/okstra_ctl/task_list_cli.py +138 -0
  257. package/runtime/python/okstra_ctl/task_show_cli.py +66 -0
  258. package/runtime/python/okstra_ctl/task_target.py +4 -16
  259. package/runtime/python/okstra_ctl/team.py +49 -2
  260. package/runtime/python/okstra_ctl/time_report.py +17 -2
  261. package/runtime/python/okstra_ctl/usage_report.py +11 -0
  262. package/runtime/python/okstra_ctl/user_response.py +55 -218
  263. package/runtime/python/okstra_ctl/user_response_values.py +242 -0
  264. package/runtime/python/okstra_ctl/validation_contract.py +3 -0
  265. package/runtime/python/okstra_ctl/verification_target.py +68 -0
  266. package/runtime/python/okstra_ctl/wizard.py +457 -92
  267. package/runtime/python/okstra_ctl/worker_artifacts.py +75 -16
  268. package/runtime/python/okstra_ctl/worker_audit_check.py +22 -0
  269. package/runtime/python/okstra_ctl/worker_dispatch.py +21 -0
  270. package/runtime/python/okstra_ctl/worker_liveness.py +33 -0
  271. package/runtime/python/okstra_ctl/worker_prompt_body.py +15 -2
  272. package/runtime/python/okstra_ctl/worker_prompt_contract.py +98 -7
  273. package/runtime/python/okstra_ctl/worker_prompt_headers.py +24 -2
  274. package/runtime/python/okstra_ctl/worker_prompt_policy.py +18 -3
  275. package/runtime/python/okstra_ctl/worker_request.py +2 -1
  276. package/runtime/python/okstra_ctl/worker_runner.py +16 -9
  277. package/runtime/python/okstra_ctl/worker_state.py +16 -1
  278. package/runtime/python/okstra_ctl/workflow.py +18 -1
  279. package/runtime/python/okstra_ctl/worktree/__init__.py +92 -0
  280. package/runtime/python/okstra_ctl/worktree/cleanliness.py +89 -0
  281. package/runtime/python/okstra_ctl/worktree/decisions.py +127 -0
  282. package/runtime/python/okstra_ctl/worktree/git_ops.py +165 -0
  283. package/runtime/python/okstra_ctl/worktree/linking.py +249 -0
  284. package/runtime/python/okstra_ctl/worktree/naming.py +91 -0
  285. package/runtime/python/okstra_ctl/worktree/provision.py +385 -0
  286. package/runtime/python/okstra_ctl/worktree/sync_config.py +181 -0
  287. package/runtime/python/okstra_ctl/worktree_cli.py +75 -0
  288. package/runtime/python/okstra_ctl/worktree_lookup_cli.py +39 -0
  289. package/runtime/python/okstra_ctl/worktree_registry.py +7 -0
  290. package/runtime/python/okstra_ctl/worktree_status_cli.py +53 -0
  291. package/runtime/python/okstra_ctl/write_policy.py +90 -213
  292. package/runtime/python/okstra_project/__init__.py +0 -4
  293. package/runtime/python/okstra_project/dirs.py +2 -2
  294. package/runtime/python/okstra_project/phase_pointer.py +84 -0
  295. package/runtime/python/okstra_project/slug.py +24 -0
  296. package/runtime/python/okstra_project/state.py +22 -230
  297. package/runtime/python/okstra_token_usage/claude.py +9 -1
  298. package/runtime/python/okstra_token_usage/cli.py +3 -1
  299. package/runtime/python/okstra_token_usage/collect.py +71 -10
  300. package/runtime/python/okstra_token_usage/cursor.py +2 -0
  301. package/runtime/python/okstra_token_usage/grok.py +1 -5
  302. package/runtime/python/okstra_token_usage/paths.py +26 -0
  303. package/runtime/python/okstra_token_usage/pricing.py +19 -7
  304. package/runtime/schemas/convergence-critic-results-v1.0.schema.json +5 -0
  305. package/runtime/schemas/execution-manifest-v2.schema.json +10 -10
  306. package/runtime/schemas/final-report-v2.0.schema.json +9 -26
  307. package/runtime/schemas/final-report-v3.0.schema.json +173 -41
  308. package/runtime/schemas/report-narrative-v3.0.schema.json +3 -2
  309. package/runtime/skills/okstra-brief-gen/SKILL.md +35 -2
  310. package/runtime/skills/okstra-container-build/SKILL.md +16 -47
  311. package/runtime/skills/okstra-inspect/facets/history.md +1 -1
  312. package/runtime/skills/okstra-inspect/facets/status.md +7 -6
  313. package/runtime/skills/okstra-pr-gen/SKILL.md +1 -1
  314. package/runtime/skills/okstra-run/SKILL.md +7 -5
  315. package/runtime/templates/report-writer-prompt-preamble.md +1 -1
  316. package/runtime/templates/reports/brief.template.md +6 -2
  317. package/runtime/templates/reports/error-analysis-input.template.md +2 -0
  318. package/runtime/templates/reports/final-verification-input.template.md +2 -0
  319. package/runtime/templates/reports/implementation-input.template.md +7 -1
  320. package/runtime/templates/reports/implementation-planning-input.template.md +4 -0
  321. package/runtime/templates/reports/quick-input.template.md +2 -0
  322. package/runtime/templates/reports/release-handoff-input.template.md +6 -1
  323. package/runtime/templates/reports/report.js +20 -3
  324. package/runtime/templates/reports/schedule.template.md +2 -0
  325. package/runtime/templates/reports/settings.template.json +0 -10
  326. package/runtime/templates/reports/task-brief.template.md +3 -0
  327. package/runtime/templates/reports/user-response.template.md +7 -3
  328. package/runtime/validators/checks/fixtures-01.py +57 -0
  329. package/runtime/validators/checks/fixtures-02.py +565 -0
  330. package/runtime/validators/checks/runners-01.py +108 -0
  331. package/runtime/validators/checks/validate-assets-01.py +60 -0
  332. package/runtime/validators/checks/validate-prompt-metadata-01.py +261 -0
  333. package/runtime/validators/checks/validate-tasks-01.py +46 -0
  334. package/runtime/validators/checks/validate-tasks-02.py +85 -0
  335. package/runtime/validators/checks/validate-tasks-03.py +61 -0
  336. package/runtime/validators/checks/validate-tasks-04.py +118 -0
  337. package/runtime/validators/forbidden_actions.py +73 -3
  338. package/runtime/validators/lib/common.sh +5 -0
  339. package/runtime/validators/lib/fixtures.sh +7 -591
  340. package/runtime/validators/lib/paths.sh +13 -0
  341. package/runtime/validators/lib/runners.sh +6 -104
  342. package/runtime/validators/lib/summary.sh +1 -1
  343. package/runtime/validators/lib/validate-assets.sh +6 -56
  344. package/runtime/validators/lib/validate-prompt-metadata.sh +6 -257
  345. package/runtime/validators/lib/validate-tasks.sh +9 -294
  346. package/runtime/validators/validate-implementation-plan-stages.py +4 -4
  347. package/runtime/validators/validate-run.py +1369 -2654
  348. package/runtime/validators/validate-workflow.sh +56 -16
  349. package/runtime/validators/validate_improvement_report.py +2 -1
  350. package/runtime/validators/validate_session_conformance.py +295 -49
  351. package/dist/commands/execute/agent-prompt.d.mts +0 -1
  352. package/dist/commands/execute/agent-prompt.mjs +0 -24
  353. package/dist/commands/execute/agent-prompt.mjs.map +0 -1
  354. package/dist/commands/execute/codex-dispatch.d.mts +0 -3
  355. package/dist/commands/execute/codex-dispatch.mjs +0 -6
  356. package/dist/commands/execute/codex-dispatch.mjs.map +0 -1
  357. package/dist/commands/execute/codex-run.d.mts +0 -3
  358. package/dist/commands/execute/codex-run.mjs +0 -62
  359. package/dist/commands/execute/codex-run.mjs.map +0 -1
  360. package/dist/commands/execute/convergence.d.mts +0 -1
  361. package/dist/commands/execute/convergence.mjs +0 -38
  362. package/dist/commands/execute/convergence.mjs.map +0 -1
  363. package/dist/commands/execute/error-log.d.mts +0 -1
  364. package/dist/commands/execute/error-log.mjs +0 -18
  365. package/dist/commands/execute/error-log.mjs.map +0 -1
  366. package/dist/commands/execute/git-reconcile.d.mts +0 -1
  367. package/dist/commands/execute/git-reconcile.mjs +0 -30
  368. package/dist/commands/execute/git-reconcile.mjs.map +0 -1
  369. package/dist/commands/execute/handoff.d.mts +0 -1
  370. package/dist/commands/execute/handoff.mjs +0 -31
  371. package/dist/commands/execute/handoff.mjs.map +0 -1
  372. package/dist/commands/execute/incremental-carry.d.mts +0 -1
  373. package/dist/commands/execute/incremental-carry.mjs +0 -20
  374. package/dist/commands/execute/incremental-carry.mjs.map +0 -1
  375. package/dist/commands/execute/incremental-scope.d.mts +0 -1
  376. package/dist/commands/execute/incremental-scope.mjs +0 -29
  377. package/dist/commands/execute/incremental-scope.mjs.map +0 -1
  378. package/dist/commands/execute/integrate-stages.d.mts +0 -1
  379. package/dist/commands/execute/integrate-stages.mjs +0 -24
  380. package/dist/commands/execute/integrate-stages.mjs.map +0 -1
  381. package/dist/commands/execute/pane-title.d.mts +0 -1
  382. package/dist/commands/execute/pane-title.mjs +0 -20
  383. package/dist/commands/execute/pane-title.mjs.map +0 -1
  384. package/dist/commands/execute/plan-items.d.mts +0 -1
  385. package/dist/commands/execute/plan-items.mjs +0 -9
  386. package/dist/commands/execute/plan-items.mjs.map +0 -1
  387. package/dist/commands/execute/plan-validate.d.mts +0 -1
  388. package/dist/commands/execute/plan-validate.mjs +0 -68
  389. package/dist/commands/execute/plan-validate.mjs.map +0 -1
  390. package/dist/commands/execute/plan-verify.d.mts +0 -1
  391. package/dist/commands/execute/plan-verify.mjs +0 -43
  392. package/dist/commands/execute/plan-verify.mjs.map +0 -1
  393. package/dist/commands/execute/spawn-followups.d.mts +0 -1
  394. package/dist/commands/execute/spawn-followups.mjs +0 -22
  395. package/dist/commands/execute/spawn-followups.mjs.map +0 -1
  396. package/dist/commands/execute/team.d.mts +0 -3
  397. package/dist/commands/execute/team.mjs +0 -66
  398. package/dist/commands/execute/team.mjs.map +0 -1
  399. package/dist/commands/execute/token-usage.d.mts +0 -1
  400. package/dist/commands/execute/token-usage.mjs +0 -19
  401. package/dist/commands/execute/token-usage.mjs.map +0 -1
  402. package/dist/commands/execute/worker-audit-check.d.mts +0 -1
  403. package/dist/commands/execute/worker-audit-check.mjs +0 -34
  404. package/dist/commands/execute/worker-audit-check.mjs.map +0 -1
  405. package/dist/commands/execute/worker-dispatch.d.mts +0 -7
  406. package/dist/commands/execute/worker-dispatch.mjs +0 -64
  407. package/dist/commands/execute/worker-dispatch.mjs.map +0 -1
  408. package/dist/commands/execute/worker-state.d.mts +0 -1
  409. package/dist/commands/execute/worker-state.mjs +0 -28
  410. package/dist/commands/execute/worker-state.mjs.map +0 -1
  411. package/dist/commands/execute/worktree-lookup.d.mts +0 -1
  412. package/dist/commands/execute/worktree-lookup.mjs +0 -92
  413. package/dist/commands/execute/worktree-lookup.mjs.map +0 -1
  414. package/dist/commands/execute/worktree-status.d.mts +0 -1
  415. package/dist/commands/execute/worktree-status.mjs +0 -121
  416. package/dist/commands/execute/worktree-status.mjs.map +0 -1
  417. package/dist/commands/inspect/code-review.d.mts +0 -1
  418. package/dist/commands/inspect/code-review.mjs +0 -32
  419. package/dist/commands/inspect/code-review.mjs.map +0 -1
  420. package/dist/commands/inspect/container.d.mts +0 -1
  421. package/dist/commands/inspect/container.mjs +0 -25
  422. package/dist/commands/inspect/container.mjs.map +0 -1
  423. package/dist/commands/inspect/context-cost.d.mts +0 -1
  424. package/dist/commands/inspect/context-cost.mjs +0 -25
  425. package/dist/commands/inspect/context-cost.mjs.map +0 -1
  426. package/dist/commands/inspect/design-prep.d.mts +0 -1
  427. package/dist/commands/inspect/design-prep.mjs +0 -22
  428. package/dist/commands/inspect/design-prep.mjs.map +0 -1
  429. package/dist/commands/inspect/error-report.d.mts +0 -1
  430. package/dist/commands/inspect/error-report.mjs +0 -25
  431. package/dist/commands/inspect/error-report.mjs.map +0 -1
  432. package/dist/commands/inspect/error-zip.d.mts +0 -1
  433. package/dist/commands/inspect/error-zip.mjs +0 -24
  434. package/dist/commands/inspect/error-zip.mjs.map +0 -1
  435. package/dist/commands/inspect/log-report.d.mts +0 -1
  436. package/dist/commands/inspect/log-report.mjs +0 -26
  437. package/dist/commands/inspect/log-report.mjs.map +0 -1
  438. package/dist/commands/inspect/model-io.d.mts +0 -1
  439. package/dist/commands/inspect/model-io.mjs +0 -25
  440. package/dist/commands/inspect/model-io.mjs.map +0 -1
  441. package/dist/commands/inspect/profile-show.d.mts +0 -1
  442. package/dist/commands/inspect/profile-show.mjs +0 -28
  443. package/dist/commands/inspect/profile-show.mjs.map +0 -1
  444. package/dist/commands/inspect/recap.d.mts +0 -1
  445. package/dist/commands/inspect/recap.mjs +0 -30
  446. package/dist/commands/inspect/recap.mjs.map +0 -1
  447. package/dist/commands/inspect/resolve-task-key.d.mts +0 -1
  448. package/dist/commands/inspect/resolve-task-key.mjs +0 -24
  449. package/dist/commands/inspect/resolve-task-key.mjs.map +0 -1
  450. package/dist/commands/inspect/rollup.d.mts +0 -1
  451. package/dist/commands/inspect/rollup.mjs +0 -25
  452. package/dist/commands/inspect/rollup.mjs.map +0 -1
  453. package/dist/commands/inspect/run-audit.d.mts +0 -1
  454. package/dist/commands/inspect/run-audit.mjs +0 -25
  455. package/dist/commands/inspect/run-audit.mjs.map +0 -1
  456. package/dist/commands/inspect/set-work-status.d.mts +0 -1
  457. package/dist/commands/inspect/set-work-status.mjs +0 -29
  458. package/dist/commands/inspect/set-work-status.mjs.map +0 -1
  459. package/dist/commands/inspect/stage-map.d.mts +0 -1
  460. package/dist/commands/inspect/stage-map.mjs +0 -131
  461. package/dist/commands/inspect/stage-map.mjs.map +0 -1
  462. package/dist/commands/inspect/task-list.d.mts +0 -1
  463. package/dist/commands/inspect/task-list.mjs +0 -149
  464. package/dist/commands/inspect/task-list.mjs.map +0 -1
  465. package/dist/commands/inspect/task-show.d.mts +0 -1
  466. package/dist/commands/inspect/task-show.mjs +0 -108
  467. package/dist/commands/inspect/task-show.mjs.map +0 -1
  468. package/dist/commands/inspect/time-report.d.mts +0 -1
  469. package/dist/commands/inspect/time-report.mjs +0 -24
  470. package/dist/commands/inspect/time-report.mjs.map +0 -1
  471. package/dist/commands/inspect/usage-report.d.mts +0 -1
  472. package/dist/commands/inspect/usage-report.mjs +0 -23
  473. package/dist/commands/inspect/usage-report.mjs.map +0 -1
  474. package/dist/commands/inspect/user-response.d.mts +0 -1
  475. package/dist/commands/inspect/user-response.mjs +0 -35
  476. package/dist/commands/inspect/user-response.mjs.map +0 -1
  477. package/dist/commands/inspect/worker-liveness.d.mts +0 -1
  478. package/dist/commands/inspect/worker-liveness.mjs +0 -45
  479. package/dist/commands/inspect/worker-liveness.mjs.map +0 -1
  480. package/dist/commands/lifecycle/contract-check.d.mts +0 -1
  481. package/dist/commands/lifecycle/contract-check.mjs +0 -18
  482. package/dist/commands/lifecycle/contract-check.mjs.map +0 -1
  483. package/dist/commands/lifecycle/migrate.d.mts +0 -1
  484. package/dist/commands/lifecycle/migrate.mjs +0 -30
  485. package/dist/commands/lifecycle/migrate.mjs.map +0 -1
  486. package/dist/commands/lifecycle/model.d.mts +0 -1
  487. package/dist/commands/lifecycle/model.mjs +0 -22
  488. package/dist/commands/lifecycle/model.mjs.map +0 -1
  489. package/dist/commands/manager.d.mts +0 -3
  490. package/dist/commands/manager.mjs +0 -50
  491. package/dist/commands/manager.mjs.map +0 -1
  492. package/dist/commands/report/agent-activity.d.mts +0 -1
  493. package/dist/commands/report/agent-activity.mjs +0 -20
  494. package/dist/commands/report/agent-activity.mjs.map +0 -1
  495. package/dist/commands/report/approval-decision.d.mts +0 -1
  496. package/dist/commands/report/approval-decision.mjs +0 -21
  497. package/dist/commands/report/approval-decision.mjs.map +0 -1
  498. package/dist/commands/report/design-snapshot.d.mts +0 -1
  499. package/dist/commands/report/design-snapshot.mjs +0 -19
  500. package/dist/commands/report/design-snapshot.mjs.map +0 -1
  501. package/dist/commands/report/finalize.d.mts +0 -4
  502. package/dist/commands/report/finalize.mjs +0 -64
  503. package/dist/commands/report/finalize.mjs.map +0 -1
  504. package/dist/commands/report/inject-report-index.d.mts +0 -1
  505. package/dist/commands/report/inject-report-index.mjs +0 -21
  506. package/dist/commands/report/inject-report-index.mjs.map +0 -1
  507. package/dist/commands/report/render-final-report.d.mts +0 -1
  508. package/dist/commands/report/render-final-report.mjs +0 -23
  509. package/dist/commands/report/render-final-report.mjs.map +0 -1
  510. package/dist/commands/report/render-views.d.mts +0 -1
  511. package/dist/commands/report/render-views.mjs +0 -27
  512. package/dist/commands/report/render-views.mjs.map +0 -1
  513. package/dist/commands/report/translate.d.mts +0 -1
  514. package/dist/commands/report/translate.mjs +0 -33
  515. package/dist/commands/report/translate.mjs.map +0 -1
  516. package/runtime/bin/lib/okstra/tmux-pane.sh +0 -40
  517. package/runtime/bin/lib/okstra-ctl/cmd-batch.sh +0 -59
  518. package/runtime/bin/lib/okstra-ctl/cmd-list.sh +0 -35
  519. package/runtime/bin/lib/okstra-ctl/cmd-open.sh +0 -36
  520. package/runtime/bin/lib/okstra-ctl/cmd-projects.sh +0 -26
  521. package/runtime/bin/lib/okstra-ctl/cmd-reconcile.sh +0 -29
  522. package/runtime/bin/lib/okstra-ctl/cmd-reindex.sh +0 -38
  523. package/runtime/bin/lib/okstra-ctl/cmd-rerun.sh +0 -345
  524. package/runtime/bin/lib/okstra-ctl/cmd-show.sh +0 -27
  525. package/runtime/bin/lib/okstra-ctl/cmd-tail.sh +0 -92
  526. package/runtime/bin/lib/okstra-ctl/main.sh +0 -41
  527. package/runtime/bin/lib/okstra-ctl/prepare.sh +0 -31
  528. package/runtime/bin/lib/okstra-ctl/usage.sh +0 -23
  529. package/runtime/bin/okstra-central.sh +0 -152
  530. package/runtime/bin/okstra-incremental-carry.py +0 -10
  531. package/runtime/bin/okstra-incremental-scope.py +0 -10
  532. package/runtime/bin/okstra-team-reconcile.sh +0 -36
  533. package/runtime/python/okstra_ctl/agent_prompt_cli.py +0 -1156
  534. package/runtime/python/okstra_ctl/batch.py +0 -60
  535. package/runtime/python/okstra_ctl/clarification_items.py +0 -1050
  536. package/runtime/python/okstra_ctl/container_registry.py +0 -72
  537. package/runtime/python/okstra_ctl/improvement_assignment.py +0 -61
  538. package/runtime/python/okstra_ctl/resolver.py +0 -54
  539. package/runtime/python/okstra_ctl/team_reconcile.py +0 -275
  540. package/runtime/python/okstra_ctl/tmux.py +0 -134
  541. package/runtime/python/okstra_ctl/worktree.py +0 -1099
@@ -118,12 +118,16 @@ class ModelPool:
118
118
  )
119
119
 
120
120
  def list_catalog(self, *, role: str | None = None) -> tuple[ResolvedModel, ...]:
121
- canonical_role = self._normalize_role(role) if role else None
122
- return tuple(
123
- model
124
- for model in self._models.values()
125
- if self._supports_role(model, canonical_role)
126
- )
121
+ """Every registered row, selectable or not — the listing marks which.
122
+
123
+ Filtering this by availability made it identical to `list()` whenever a
124
+ role was given, so a hidden model became invisible in the one surface
125
+ meant to explain why it is hidden. `role` is still validated so an
126
+ unknown role fails here exactly as it does in `list()`.
127
+ """
128
+ if role:
129
+ self._normalize_role(role)
130
+ return tuple(self._models.values())
127
131
 
128
132
  def resolve(self, model_ref: str) -> ResolvedModel:
129
133
  try:
@@ -50,12 +50,6 @@ def lead_launch_spec(provider: str) -> LeadLaunchSpec:
50
50
  return spec.lead_launch
51
51
 
52
52
 
53
- def bare_lead_launch_argv(provider: str) -> list[str]:
54
- """Start this provider CLI with only the flags required by Okstra."""
55
- launch = lead_launch_spec(provider)
56
- return [launch.executable, *launch.sandbox_waiver]
57
-
58
-
59
53
  def lead_launch_argv(
60
54
  provider: str,
61
55
  *,
@@ -160,11 +154,17 @@ def default_model(role: str) -> Optional[str]:
160
154
 
161
155
 
162
156
  def catalog_pricing(provider: str) -> dict:
163
- """Selectable-model pricing as input, cached-input, output USD per million."""
157
+ """Catalog pricing as input, cached-input, output USD per million.
158
+
159
+ Every registered row, not only the selectable ones: a model hidden from the
160
+ picker is still what past runs were billed at, and dropping its rate here
161
+ silently re-prices that history through the coarser substring fallback in
162
+ `okstra_token_usage.pricing`.
163
+ """
164
164
  provider_id = provider.strip().lower()
165
165
  return {
166
166
  model.model_ref.model_id: model.pricing
167
- for model in _model_pool().list()
167
+ for model in _model_pool().list_catalog()
168
168
  if model.provider_id == provider_id and model.pricing is not None
169
169
  }
170
170
 
@@ -6,8 +6,11 @@ from pathlib import Path
6
6
  from typing import Any, Literal, Mapping, Sequence
7
7
 
8
8
  from .attempt_evidence import AttemptEvidenceSeal, _sha
9
- from .dispatch_state import build_dispatch_id
10
- from .execution_identity import ExecutionManifestError
9
+ from .execution_identity import build_dispatch_id
10
+ from .execution_identity import ( # noqa: F401 — MutationRecoveryDecision 재노출
11
+ ExecutionManifestError,
12
+ MutationRecoveryDecision,
13
+ )
11
14
  from .execution_manifest import (
12
15
  _replace_manifest,
13
16
  _write_manifest_over_existing,
@@ -39,67 +42,6 @@ class MutationRecoveryInput:
39
42
  current_git_chain_digest: str
40
43
 
41
44
 
42
- @dataclass(frozen=True)
43
- class MutationRecoveryDecision:
44
- recovery_ref: str
45
- source_invocation_ref: str
46
- source_attempt_ref: str
47
- source_evidence_seal_ref: str | None
48
- original_baseline_digest: str
49
- terminal_source_diff_digest: str
50
- terminal_git_chain_digest: str
51
- verifier_role_execution_ref: str | None
52
- verifier_invocation_ref: str | None
53
- recovered_evidence_refs: tuple[Mapping[str, Any], ...]
54
- recovered_out_of_plan_edits: tuple[Mapping[str, Any], ...]
55
- unmet_obligations: tuple[str, ...]
56
- decision: Literal["carry-forward", "reject", "cleanup-confirmed"]
57
- reason: str = ""
58
-
59
- def to_payload(self) -> dict[str, Any]:
60
- return {
61
- "recoveryRef": self.recovery_ref,
62
- "sourceInvocationRef": self.source_invocation_ref,
63
- "sourceAttemptRef": self.source_attempt_ref,
64
- "sourceEvidenceSealRef": self.source_evidence_seal_ref,
65
- "originalBaselineDigest": self.original_baseline_digest,
66
- "terminalSourceDiffDigest": self.terminal_source_diff_digest,
67
- "terminalGitChainDigest": self.terminal_git_chain_digest,
68
- "verifierRoleExecutionRef": self.verifier_role_execution_ref,
69
- "verifierInvocationRef": self.verifier_invocation_ref,
70
- "recoveredEvidenceRefs": [dict(row) for row in self.recovered_evidence_refs],
71
- "recoveredOutOfPlanEdits": [
72
- dict(row) for row in self.recovered_out_of_plan_edits
73
- ],
74
- "unmetObligations": list(self.unmet_obligations),
75
- "decision": self.decision,
76
- "reason": self.reason,
77
- }
78
-
79
- @classmethod
80
- def from_payload(cls, payload: Mapping[str, Any]) -> "MutationRecoveryDecision":
81
- return cls(
82
- recovery_ref=str(payload["recoveryRef"]),
83
- source_invocation_ref=str(payload["sourceInvocationRef"]),
84
- source_attempt_ref=str(payload["sourceAttemptRef"]),
85
- source_evidence_seal_ref=payload.get("sourceEvidenceSealRef"),
86
- original_baseline_digest=str(payload.get("originalBaselineDigest") or ""),
87
- terminal_source_diff_digest=str(
88
- payload.get("terminalSourceDiffDigest") or ""
89
- ),
90
- terminal_git_chain_digest=str(payload.get("terminalGitChainDigest") or ""),
91
- verifier_role_execution_ref=payload.get("verifierRoleExecutionRef"),
92
- verifier_invocation_ref=payload.get("verifierInvocationRef"),
93
- recovered_evidence_refs=tuple(payload.get("recoveredEvidenceRefs") or ()),
94
- recovered_out_of_plan_edits=tuple(
95
- payload.get("recoveredOutOfPlanEdits") or ()
96
- ),
97
- unmet_obligations=tuple(payload.get("unmetObligations") or ()),
98
- decision=str(payload["decision"]),
99
- reason=str(payload.get("reason") or ""),
100
- )
101
-
102
-
103
45
  def _seal_for(manifest, seal_ref: str | None) -> AttemptEvidenceSeal | None:
104
46
  if not seal_ref:
105
47
  return None
@@ -1,7 +1,8 @@
1
1
  """다음 Phase 포인터의 shape · 승격 · 투영 SSOT.
2
2
 
3
- 포인터는 `{phase, status, rationale}` 구조체다. 저작은 리드(report-writer)가
4
- 하고, Phase 7 검증기가 리포트의 Phase 라우팅과 대조해 어긋나면 정정한다.
3
+ 포인터는 `{phase, status, rationale}` 구조체다. 값은 리포트의 라우팅 필드에서
4
+ 투영된다 리드가 구조체를 직접 쓰는 표면은 없고, 매니페스트에 있던 값과
5
+ 대조하지도 않는다. 리드가 통제하는 것은 리포트의 라우팅 필드뿐이다.
5
6
  이 모듈은 세 가지만 소유한다 — 구조체 판별, 구형 문자열 승격, 리포트 투영.
6
7
  다음 phase 를 계산하는 다른 알고리즘을 다시 만들지 않는다.
7
8
  """
@@ -14,25 +15,26 @@ from okstra_ctl.clarification_items import (
14
15
  progress_blocking_ids,
15
16
  )
16
17
 
17
- STATUS_READY = "ready"
18
- STATUS_PENDING = "pending"
19
- STATUS_BLOCKED = "blocked"
20
- STATUS_TERMINAL = "terminal"
21
18
 
22
- POINTER_STATUSES = frozenset(
23
- {STATUS_READY, STATUS_PENDING, STATUS_BLOCKED, STATUS_TERMINAL}
19
+ # 포인터 값 자체(구조체·판별·승격)는 하위 계층이 소유한다. 읽기측 상태 접근자
20
+ # okstra_project.state 매니페스트를 돌려줄 때마다 승격을 해야 하는데, 그 값
21
+ # 타입이 여기 있으면 okstra_ctl 패키지 __init__ 을 통해 순환이 된다. 근거는
22
+ # okstra_project/phase_pointer.py 의 모듈 도크스트링.
23
+ #
24
+ # 이름을 그대로 재노출하므로 next_phase.make / .promote / .STATUS_* 를 쓰는
25
+ # 기존 호출부는 바뀌지 않는다.
26
+ from okstra_project.phase_pointer import ( # noqa: F401 — 의도적 재노출
27
+ LEGACY_SENTINELS as _LEGACY_SENTINELS,
28
+ POINTER_STATUSES,
29
+ STATUS_BLOCKED,
30
+ STATUS_PENDING,
31
+ STATUS_READY,
32
+ STATUS_TERMINAL,
33
+ is_pointer,
34
+ make,
35
+ promote,
24
36
  )
25
37
 
26
- # 구형 문자열 포인터가 쓰던 센티널 → status. 이 표는 승격에만 쓰이며
27
- # 새 포인터를 만드는 데는 절대 쓰지 않는다.
28
- _LEGACY_SENTINELS = {
29
- "": STATUS_PENDING,
30
- "unknown": STATUS_PENDING,
31
- "pending-routing-decision": STATUS_PENDING,
32
- "pending-release-handoff": STATUS_PENDING,
33
- "done-or-follow-up": STATUS_TERMINAL,
34
- }
35
-
36
38
  # 라우팅 필드가 없어 항상 terminal 인 task-type.
37
39
  _TERMINAL_TASK_TYPES = frozenset({"release-handoff"})
38
40
 
@@ -90,49 +92,6 @@ _FINAL_VERIFICATION_PHASE_ALIASES = {
90
92
  }
91
93
 
92
94
 
93
- def make(
94
- phase: str = "", status: str = STATUS_PENDING, rationale: str = ""
95
- ) -> dict[str, str]:
96
- """포인터 구조체 하나를 만든다. status 가 허용 집합 밖이면 거부한다."""
97
- if status not in POINTER_STATUSES:
98
- raise ValueError(f"unknown next-phase status: {status!r}")
99
- return {"phase": phase, "status": status, "rationale": rationale}
100
-
101
-
102
- def is_pointer(value: Any) -> bool:
103
- """value 가 유효한 포인터 구조체인지."""
104
- return (
105
- isinstance(value, Mapping)
106
- and isinstance(value.get("phase"), str)
107
- and value.get("status") in POINTER_STATUSES
108
- and isinstance(value.get("rationale"), str)
109
- )
110
-
111
-
112
- def promote(value: Any) -> dict[str, str]:
113
- """구형 문자열·None 을 포인터로 올린다.
114
-
115
- 이미 포인터인 입력은 같은 값을 담은 새 dict 로 돌려준다 — 입력을 그대로
116
- 돌려주지 않는다. 호출부가 반환값을 그 자리에서 변형해도 manifest 원본이
117
- 오염되지 않아야 하기 때문이다.
118
-
119
- pre-v1 무호환 원칙의 의도적 예외다. 근거는 업그레이드를 가로지르는
120
- 진행 중 태스크가 영구히 막히는 기존 사례 — ADR-0004 Consequences.
121
- """
122
- if is_pointer(value):
123
- return {
124
- "phase": value["phase"],
125
- "status": value["status"],
126
- "rationale": value["rationale"],
127
- }
128
- if not isinstance(value, str):
129
- return make()
130
- text = value.strip()
131
- if text in _LEGACY_SENTINELS:
132
- return make(status=_LEGACY_SENTINELS[text])
133
- return make(phase=text, status=STATUS_READY)
134
-
135
-
136
95
  def autofill_task_type(manifest: Mapping[str, Any]) -> str:
137
96
  """매니페스트의 포인터에서 바로 실행 가능한 task-type 을 뽑는다.
138
97
 
@@ -157,7 +116,15 @@ def project(report_data: Mapping[str, Any]) -> dict[str, str]:
157
116
  task_type = str(header.get("taskType") or "")
158
117
 
159
118
  if task_type in _TERMINAL_TASK_TYPES:
160
- return make(status=STATUS_TERMINAL)
119
+ # release-handoff 의 라우팅 칸은 자유 문자열이다. phase 로 삼을 값은
120
+ # 없지만(생애주기가 여기서 끝난다) 그 문장은 태스크가 왜 끝났는지
121
+ # 말하므로 근거 자리에 싣는다.
122
+ return make(
123
+ status=STATUS_TERMINAL,
124
+ rationale=_text(
125
+ _block(report_data, "releaseHandoff").get("routingRecommendation")
126
+ ),
127
+ )
161
128
  if task_type in _SIDETRACK_TASK_TYPES:
162
129
  return make(status=STATUS_PENDING)
163
130
 
@@ -181,6 +148,11 @@ def _block(report_data: Mapping[str, Any], key: str) -> Mapping[str, Any]:
181
148
  return block if isinstance(block, Mapping) else {}
182
149
 
183
150
 
151
+ def _text(value: Any) -> str:
152
+ """문자열이면 다듬어 돌려주고, 아니면 결측으로 취급한다."""
153
+ return value.strip() if isinstance(value, str) else ""
154
+
155
+
184
156
  def _from_nested(
185
157
  report_data: Mapping[str, Any],
186
158
  block_key: str,
@@ -193,27 +165,79 @@ def _from_nested(
193
165
  강제하면 dict repr 같은 것이 phase 이름 자리에 들어앉고, status 는 ready 가
194
166
  되어 소비자에게 "지금 시작 가능한 phase" 로 읽힌다. 결측은 pending 에서
195
167
  무해하게 멈추지만 이쪽은 존재하지 않는 phase 로 소비자를 보낸다.
168
+
169
+ 같은 라우팅 객체의 `rationale` 을 포인터에 함께 싣는다. 그 문장은 리드가
170
+ "왜 다음이 이 phase 인가" 를 쓴 자리고, 스키마가 네 phase 전부에서 필수로
171
+ 요구한다. 여기서 안 실으면 포인터에 남는 것은 phase 이름 하나뿐이라, 다음
172
+ 작업을 추천받는 사람이 근거 없이 이름만 본다.
173
+
174
+ 대상이 없으면 근거도 버린다 — 목적지를 설명하는 문장이므로 목적지 없이
175
+ 남으면 무엇을 설명하는지 알 수 없다.
196
176
  """
197
177
  routing = _block(report_data, block_key).get(routing_key)
198
178
  target = ""
179
+ rationale = ""
199
180
  if isinstance(routing, Mapping):
200
- raw = routing.get(target_key)
201
- target = raw.strip() if isinstance(raw, str) else ""
181
+ target = _text(routing.get(target_key))
182
+ rationale = _text(routing.get("rationale"))
202
183
  if not target:
203
184
  return make(status=STATUS_PENDING)
204
- return make(phase=target, status=STATUS_READY)
185
+ return make(phase=target, status=STATUS_READY, rationale=rationale)
205
186
 
206
187
 
207
188
  def _from_nested_routing(report_data: Mapping[str, Any], key: str) -> dict[str, str]:
208
189
  return _from_nested(report_data, key, "routing", "nextTaskType")
209
190
 
210
191
 
192
+ # 차단된 비교가 "그래서 뭘 하라는 것인가" 를 쓰는 칸. 스키마가 필수로 요구한다
193
+ # (`ImplementationOptionSelectionUserNarrative.required`).
194
+ _SELECTION_GUIDANCE_CAP = 400
195
+
196
+
197
+ def _selection_guidance(selection: Mapping[str, Any]) -> str:
198
+ """`userNarrative.selectionGuidance` 의 첫 문단.
199
+
200
+ 전체는 여러 문단이고 포인터의 근거는 한 줄로 인용되는 자리다. 첫 문단이
201
+ "이 run 에서 고를 것이 없다 / 무엇을 갖춰 다시 돌려라" 를 말한다.
202
+ """
203
+ narrative = selection.get("userNarrative")
204
+ if not isinstance(narrative, Mapping):
205
+ return ""
206
+ guidance = narrative.get("selectionGuidance")
207
+ if not isinstance(guidance, Mapping):
208
+ return ""
209
+ text = _text(guidance.get("text"))
210
+ if not text:
211
+ return ""
212
+ first = text.split("\n", 1)[0].strip()
213
+ if len(first) > _SELECTION_GUIDANCE_CAP:
214
+ first = first[:_SELECTION_GUIDANCE_CAP].rstrip() + "…"
215
+ return first
216
+
217
+
211
218
  def _from_option_selection(report_data: Mapping[str, Any]) -> dict[str, str]:
212
219
  # 이 phase 의 routing 은 문자열 enum 이다. 이웃 두 phase 가 쓰는 중첩 dict 가
213
220
  # 잘못 들어오면 결측과 똑같이 취급한다 — str() 로 강제하면 dict repr 이 phase
214
221
  # 이름이 되어 ready 로 나간다.
215
- raw = _block(report_data, "implementationOptionSelection").get("routing")
222
+ selection = _block(report_data, "implementationOptionSelection")
223
+ raw = selection.get("routing")
216
224
  routing = raw.strip() if isinstance(raw, str) else ""
225
+ if routing == "blocked":
226
+ # 차단은 목적지가 없다. 근거까지 비우면 포인터에 남는 것이 "막혔다" 뿐이고,
227
+ # 소비자에게 남는 유일한 행동은 같은 phase 를 그대로 다시 돌리는 것이다.
228
+ # 그 재실행이 같은 이유로 또 차단되면 사용자는 이유를 한 번도 못 본 채
229
+ # 같은 자리를 돈다. 이웃 분기들은 이미 근거를 싣는다 — `_from_nested` 는
230
+ # 라우팅의 `rationale`, `_from_planning` 은 승인 차단 사유,
231
+ # release-handoff 는 `routingRecommendation`.
232
+ return make(status=STATUS_BLOCKED, rationale=_selection_guidance(selection))
233
+ if routing == "pending-direction-selection":
234
+ # 이 상태는 **성공**이다 — 비교가 끝났고 사용자가 방향을 고르면 된다.
235
+ # 그런데 근거가 비면 closeout 표에 `pending` 행이 없어 "Otherwise →
236
+ # /okstra-inspect status" 로 떨어지고, 사용자는 끝난 phase 를 들여다보라는
237
+ # 안내를 받는다. 실측(dev-10341): 1회차가 IO-001·IO-002 를 내고 여기서
238
+ # 멈췄는데 안내가 없어 같은 phase 가 세 번 더 돌았고, 그동안 워커가
239
+ # 무너지며 결과가 0건으로 나빠졌다.
240
+ return make(status=STATUS_PENDING, rationale=_direction_selection_reason(selection))
217
241
  if routing in _OPTION_SELECTION_NON_PHASE:
218
242
  return make(status=_OPTION_SELECTION_NON_PHASE[routing])
219
243
  if not routing:
@@ -221,6 +245,32 @@ def _from_option_selection(report_data: Mapping[str, Any]) -> dict[str, str]:
221
245
  return make(phase=routing, status=STATUS_READY)
222
246
 
223
247
 
248
+ def _direction_selection_reason(selection: Mapping[str, Any]) -> str:
249
+ """비교가 끝난 run 이 사용자에게 남기는 다음 행동.
250
+
251
+ 후보 id 를 이름으로 말한다 — 고르라는 말만 있고 고를 것의 이름이 없으면
252
+ 사용자는 리포트를 열어 목록을 찾아야 한다.
253
+ """
254
+ options = selection.get("rankedOptions")
255
+ ids = [
256
+ _text(row.get("id"))
257
+ for row in options
258
+ if isinstance(row, Mapping) and _text(row.get("id"))
259
+ ] if isinstance(options, list) else []
260
+ if not ids:
261
+ return ""
262
+ listed = ", ".join(f"`{item}`" for item in ids)
263
+ recommended = _text(selection.get("recommendedOptionId"))
264
+ head = f"비교가 끝났습니다. 후보 {listed} 중 하나를 방향으로 고르세요"
265
+ head += f" (권장 `{recommended}`)." if recommended else "."
266
+ return (
267
+ f"{head} 고르는 자리는 계획 단계의 위저드입니다 — `/okstra-run` 으로 "
268
+ "`implementation-planning` 을 시작하면 이 후보들이 선택지로 나옵니다. "
269
+ "이 phase 를 다시 돌리지 마세요: 비교는 이미 끝났고, 재실행은 같은 "
270
+ "비교를 처음부터 다시 합니다."
271
+ )
272
+
273
+
224
274
  def _unresolved_approval_ids(report_data: Mapping[str, Any]) -> list[str]:
225
275
  return progress_blocking_ids(
226
276
  report_data.get("clarificationItems"),
@@ -279,8 +329,8 @@ def _from_planning(report_data: Mapping[str, Any]) -> dict[str, str]:
279
329
  outcome = str(planning.get("outcome") or "")
280
330
  if outcome == "direction-invalidated":
281
331
  return make(phase="implementation-option-selection", status=STATUS_READY)
282
- if outcome != "plan-ready":
283
- return make(status=STATUS_PENDING)
332
+ # 후보 비교 계획은 outcome 칸이 없다. 열린 승인 차단과 차단 게이트만
333
+ # 구현을 막는다. 칸이 없다고 pending 으로 두면 종료 안내가 inspect 로 간다.
284
334
  blocked_reason = _planning_approval_block_reason(report_data, planning)
285
335
  if blocked_reason:
286
336
  return make(status=STATUS_BLOCKED, rationale=blocked_reason)
@@ -294,7 +344,9 @@ def _from_target(report_data: Mapping[str, Any], key: str) -> dict[str, str]:
294
344
  def _from_final_verification(report_data: Mapping[str, Any]) -> dict[str, str]:
295
345
  pointer = _from_target(report_data, "finalVerification")
296
346
  if pointer["phase"] == "done":
297
- return make(status=STATUS_TERMINAL)
347
+ # `done` 은 phase 가 아니라 생애주기 종료다. 근거는 그대로 남긴다 —
348
+ # 태스크를 끝내도 되는 이유가 사용자에게 가장 필요한 문장이다.
349
+ return make(status=STATUS_TERMINAL, rationale=pointer["rationale"])
298
350
  if pointer["phase"] in _FINAL_VERIFICATION_PHASE_ALIASES:
299
351
  return make(
300
352
  phase=_FINAL_VERIFICATION_PHASE_ALIASES[pointer["phase"]],
@@ -66,11 +66,34 @@ def in_flight_run_dirs(project_root: Path) -> list[Path]:
66
66
  return out
67
67
 
68
68
 
69
+ def cwd_from_hook_payload(raw: str) -> str:
70
+ """훅이 stdin 으로 넘긴 JSON 에서 `cwd` 만 꺼낸다.
71
+
72
+ 입력은 훅이 주는 것이라 형태를 보장할 수 없다. 파싱에 실패하거나 키가
73
+ 없으면 빈 문자열을 돌려준다 — 호출부(okstra-compact-reminder.sh)가 그때
74
+ 자신의 $PWD 로 대체한다. 이 함수는 호출부의 폴백을 켜는 신호를 내는 것이
75
+ 역할이므로 빈 문자열이 곧 신호다.
76
+ """
77
+ import json
78
+
79
+ try:
80
+ payload = json.loads(raw)
81
+ except (ValueError, TypeError):
82
+ return ""
83
+ if not isinstance(payload, dict):
84
+ return ""
85
+ value = payload.get("cwd", "")
86
+ return value if isinstance(value, str) else ""
87
+
88
+
69
89
  def main(argv: list[str]) -> int:
70
90
  if len(argv) == 2 and argv[0] == "--in-flight-run-dirs-for":
71
91
  for run_dir in in_flight_run_dirs(Path(argv[1])):
72
92
  print(run_dir)
73
93
  return 0
94
+ if len(argv) == 1 and argv[0] == "--cwd-from-hook-stdin":
95
+ print(cwd_from_hook_payload(sys.stdin.read()))
96
+ return 0
74
97
  return 1
75
98
 
76
99
 
@@ -115,19 +115,22 @@ def release_pane_title(
115
115
  return PaneTitleLease(**{**lease.__dict__, "status": "released"})
116
116
 
117
117
 
118
- def cleanup_pane_title(
119
- lease: PaneTitleLease, backend: PaneTitleBackend
120
- ) -> PaneTitleLease:
121
- if lease.status == "not-applicable":
122
- return lease
123
- backend.set_title(lease.pane_id, lease.previous_title)
124
- return PaneTitleLease(**{**lease.__dict__, "status": "cleaned"})
118
+ _CLI_EPILOG = r"""Usage:
119
+ okstra pane-title acquire --execution-label <label> [--pane-id <id>]
120
+ okstra pane-title release
121
+ okstra pane-title cleanup
122
+ """
123
+ _CLI_DESCRIPTION = "Lease pane titles from stored execution labels."
125
124
 
126
125
 
127
126
  def main(argv: list[str] | None = None) -> int:
128
127
  import argparse
129
128
 
130
- parser = argparse.ArgumentParser(prog="okstra pane-title")
129
+ parser = argparse.ArgumentParser(
130
+ description=_CLI_DESCRIPTION,
131
+ epilog=_CLI_EPILOG,
132
+ formatter_class=argparse.RawDescriptionHelpFormatter,
133
+ prog="okstra pane-title")
131
134
  parser.add_argument("action", choices=("acquire", "release", "cleanup"))
132
135
  parser.add_argument("--execution-label", default="")
133
136
  parser.add_argument("--pane-id", default="")
@@ -12,8 +12,13 @@ from okstra_project.dirs import (
12
12
  okstra_home,
13
13
  )
14
14
 
15
- from .paths import runs_dir_of, task_manifest_file, task_timeline_file
16
- from .worker_artifacts import artifacts_from_context
15
+ from .paths import (
16
+ runs_dir_of,
17
+ task_manifest_file,
18
+ task_qa_dir,
19
+ task_timeline_file,
20
+ )
21
+ from .worker_artifacts import worker_artifact_paths
17
22
 
18
23
  RUN_CONTEXT_KIND = "run-context"
19
24
  RUN_CONTEXT_SCHEMA_VERSION = "2.0"
@@ -101,7 +106,7 @@ def hydrate_active_run_context(payload: Mapping[str, Any]) -> dict[str, Any]:
101
106
  "agentContract": dict(_mapping(payload.get("agentContract"))),
102
107
  "instructionSet": _hydrate_active_instruction_set(payload, ctx),
103
108
  "workers": _hydrate_active_workers(payload, ctx),
104
- "errorLogs": _hydrate_active_error_logs(ctx),
109
+ "errorLogs": _hydrate_active_error_logs(payload, ctx),
105
110
  "runtimeResources": {
106
111
  "codingPreflightDir": ctx.get("OKSTRA_CODING_PREFLIGHT_DIR", ""),
107
112
  "workerPreamblePathByAudience": {
@@ -255,19 +260,36 @@ def _hydrate_active_worker(
255
260
  ) -> dict[str, Any]:
256
261
  worker_id = str(worker.get("workerId", ""))
257
262
  hydrated = dict(worker)
258
- paths = artifacts_from_context(ctx, relative=True).get(worker_id, {})
259
- hydrated["promptPath"] = paths.get("promptPath", "")
260
- hydrated["resultPath"] = paths.get("resultPath", "")
263
+ paths = worker_artifact_paths(ctx, worker_id, relative=True)
264
+ hydrated["promptPath"] = paths["promptPath"]
265
+ hydrated["resultPath"] = paths["resultPath"]
261
266
  return hydrated
262
267
 
263
268
 
264
- def _hydrate_active_error_logs(ctx: Mapping[str, str]) -> dict[str, Any]:
265
- artifacts = artifacts_from_context(ctx, relative=True)
269
+ def _hydrate_active_error_logs(
270
+ payload: Mapping[str, Any],
271
+ ctx: Mapping[str, str],
272
+ ) -> dict[str, Any]:
273
+ """사이드카 맵의 키는 이 런의 로스터다.
274
+
275
+ 복원된 ctx 에는 로스터가 없다 — 경로 힌트만 담기기 때문이다. 그래서 압축
276
+ payload 가 들고 있는 `workers[]` 행에서 id 를 읽는다. provider registry 목록으로
277
+ 되돌리면 실행자와 검증자가 provider 를 공유할 때 생기는 접미사 id
278
+ (`claude-2`)의 사이드카 경로가 통째로 빠진다.
279
+ """
280
+ workers = payload.get("workers")
281
+ worker_ids = [
282
+ str(worker.get("workerId", ""))
283
+ for worker in (workers if isinstance(workers, list) else [])
284
+ if isinstance(worker, Mapping) and str(worker.get("workerId", ""))
285
+ ]
266
286
  return {
267
287
  "runErrorsLogPath": ctx.get("RUN_ERRORS_LOG_RELATIVE_PATH", ""),
268
288
  "sidecarsByWorkerId": {
269
- worker_id: paths["errorsSidecarPath"]
270
- for worker_id, paths in artifacts.items()
289
+ worker_id: worker_artifact_paths(
290
+ ctx, worker_id, relative=True
291
+ )["errorsSidecarPath"]
292
+ for worker_id in worker_ids
271
293
  },
272
294
  }
273
295
 
@@ -357,7 +379,7 @@ def _task_path_set(task_root: Path) -> dict[str, Path]:
357
379
  "task_index": task_root / "task-index.md",
358
380
  "instruction_set": instruction_set,
359
381
  "analysis_packet": instruction_set / "analysis-packet.md",
360
- "task_qa": task_root / "qa",
382
+ "task_qa": task_qa_dir(task_root),
361
383
  "runs_dir": runs_dir_of(task_root),
362
384
  "history_dir": history_dir,
363
385
  "timeline_file": task_timeline_file(task_root),
@@ -443,6 +465,7 @@ def _run_files(
443
465
  "approval_decisions": run_state / f"approval-decisions-{task_type_segment}-{sequences['state']}.json",
444
466
  "design_preparation": run_state / f"design-preparation-{task_type_segment}-{sequences['state']}.json",
445
467
  "plan_body_verification": run_state / f"plan-body-verification-{task_type_segment}-{sequences['state']}.json",
468
+ "incremental_decision": run_state / f"incremental-decision-{task_type_segment}-{sequences['state']}.json",
446
469
  "run_errors_log": run_logs / f"errors-{task_type_segment}-{sequences['state']}.jsonl",
447
470
  "claude_worker_errors_sidecar": worker_results / f"claude-worker-errors{suffixes['worker_results']}.json",
448
471
  "codex_worker_errors_sidecar": worker_results / f"codex-worker-errors{suffixes['worker_results']}.json",
@@ -578,6 +601,7 @@ def _absolute_worker_fields(paths: Mapping[str, Any]) -> dict[str, str]:
578
601
  "APPROVAL_DECISIONS_PATH": str(paths["approval_decisions"]),
579
602
  "DESIGN_PREPARATION_PATH": str(paths["design_preparation"]),
580
603
  "PLAN_BODY_VERIFICATION_STATE_PATH": str(paths["plan_body_verification"]),
604
+ "INCREMENTAL_DECISION_STATE_PATH": str(paths["incremental_decision"]),
581
605
  "CLAUDE_WORKER_ERRORS_SIDECAR_PATH": str(paths["claude_worker_errors_sidecar"]),
582
606
  "CODEX_WORKER_ERRORS_SIDECAR_PATH": str(paths["codex_worker_errors_sidecar"]),
583
607
  "ANTIGRAVITY_WORKER_ERRORS_SIDECAR_PATH": str(paths["antigravity_worker_errors_sidecar"]),
@@ -682,6 +706,7 @@ def _relative_file_fields(project_root: Path, paths: Mapping[str, Any]) -> dict[
682
706
  "APPROVAL_DECISIONS_RELATIVE_PATH": _rel(project_root, paths["approval_decisions"]),
683
707
  "DESIGN_PREPARATION_RELATIVE_PATH": _rel(project_root, paths["design_preparation"]),
684
708
  "PLAN_BODY_VERIFICATION_STATE_RELATIVE_PATH": _rel(project_root, paths["plan_body_verification"]),
709
+ "INCREMENTAL_DECISION_STATE_RELATIVE_PATH": _rel(project_root, paths["incremental_decision"]),
685
710
  "RUN_ERRORS_LOG_RELATIVE_PATH": _rel(project_root, paths["run_errors_log"]),
686
711
  "CLAUDE_WORKER_ERRORS_SIDECAR_RELATIVE_PATH": _rel(project_root, paths["claude_worker_errors_sidecar"]),
687
712
  "CODEX_WORKER_ERRORS_SIDECAR_RELATIVE_PATH": _rel(project_root, paths["codex_worker_errors_sidecar"]),