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
@@ -42,11 +42,15 @@ from dataclasses import dataclass
42
42
  from pathlib import Path
43
43
  from typing import Any, Callable, Mapping, Sequence
44
44
 
45
- from .agent_activity import ActivityProjectionError, project_agent_activity
45
+ from .agent.activity import ActivityProjectionError, project_agent_activity
46
46
  from .report_contract import apply_execution_roles
47
47
  from .report_assembly import ReportAssemblyError, assemble_report
48
48
  from .dispatch_state import DispatchError, link_agent_dispatch_result
49
- from .final_report_paths import final_report_data_path, final_report_markdown_path
49
+ from .final_report_paths import (
50
+ final_report_data_path,
51
+ final_report_markdown_path,
52
+ sidecar_source_rel,
53
+ )
50
54
  from .paths import task_dir, task_manifest_file
51
55
  from .release_gate import release_handoff_allowed
52
56
  from .json_boundary import JsonBoundaryError, load_owned_object, write_owned_object_atomic
@@ -57,6 +61,16 @@ from .stage_targets import (
57
61
  )
58
62
  from .session import observe_lead_session
59
63
 
64
+ # 포인터 값 타입만 쓴다. `okstra_project.phase_pointer` 는 okstra 안의
65
+ # 어떤 것도 import 하지 않는 leaf 라 순환이 없다 — 그 모듈 도크스트링.
66
+ from okstra_project.phase_pointer import (
67
+ STATUS_BLOCKED,
68
+ STATUS_PENDING,
69
+ STATUS_READY,
70
+ STATUS_TERMINAL,
71
+ promote as promote_next_phase,
72
+ )
73
+
60
74
 
61
75
  STEP_PROJECT_ACTIVITY = "project-activity"
62
76
  STEP_CHECK_SOURCE = "check-source"
@@ -120,13 +134,6 @@ def resolve_optional_path(project_root: Path, value: Any) -> Path | None:
120
134
  return resolve_project_path(project_root, value)
121
135
 
122
136
 
123
- def project_relative_path(project_root: Path, path: Path) -> str:
124
- try:
125
- return path.relative_to(project_root).as_posix()
126
- except ValueError:
127
- return str(path)
128
-
129
-
130
137
  def task_group(manifest: Mapping[str, Any]) -> str:
131
138
  value = string_value(manifest.get("taskGroup"))
132
139
  if value:
@@ -337,8 +344,11 @@ def build_commands(ctx: FinalizeContext) -> list[tuple[str, list[str]]]:
337
344
  ctx.task_type,
338
345
  "--seq",
339
346
  ctx.seq,
347
+ # 사이드카가 되짚는 기준은 태스크 루트다. 프로젝트 루트
348
+ # 기준으로 넘기던 동안 HTML Export 가 만든 사이드카는
349
+ # 방향 선택 게이트를 통과할 수 없었다.
340
350
  "--source-report",
341
- project_relative_path(ctx.project_root, markdown_path),
351
+ sidecar_source_rel(markdown_path),
342
352
  ],
343
353
  ),
344
354
  (
@@ -491,9 +501,47 @@ def run_finalize(
491
501
  steps.append(step_payload(name, command, result))
492
502
  if result.returncode != 0 and not first_failure:
493
503
  first_failure = f"{name} failed with exit code {result.returncode}"
494
- if first_failure:
495
- return {"ok": False, "reason": first_failure, "steps": steps}
496
- return {"ok": True, "reason": "", "steps": steps}
504
+ pointer, pointer_error = _recorded_next_phase(ctx)
505
+ payload: dict[str, Any] = {
506
+ "ok": not first_failure,
507
+ "reason": first_failure,
508
+ "steps": steps,
509
+ "nextRecommendedPhase": pointer,
510
+ }
511
+ if pointer_error:
512
+ payload["nextRecommendedPhaseError"] = pointer_error
513
+ # closeout 이 실제로 건넬 명령. 표를 리드의 기억에 맡기지 않는다.
514
+ payload["nextCommand"] = closeout_command(payload)
515
+ return payload
516
+
517
+
518
+ def _recorded_next_phase(ctx: FinalizeContext) -> tuple[dict[str, str], str]:
519
+ """`validate-run` 이 방금 태스크 매니페스트에 쓴 다음 phase 포인터.
520
+
521
+ 리드의 closeout 표는 이 포인터의 `status` / `rationale` 로 분기하는데,
522
+ 포인터는 리드가 쓰는 필드가 아니고(`prompts/lead/report-writer.md`
523
+ "Nobody writes this pointer by hand"), 이 시퀀스의 결과에도 실리지
524
+ 않았다. 값 없이 표만 받은 리드에게 남는 행동은 상태를 늘어놓고 턴을
525
+ 끝내는 것뿐이다.
526
+
527
+ 여기서 다시 계산하지 않는다 — 투영은 `validate-run` 의
528
+ `update_workflow_metadata` 가 하고, 이 함수는 그것이 파일에 남긴 값을
529
+ 읽어 옮기기만 한다. `--only` 로 `validate-run` 을 건너뛴 호출에서는
530
+ 이전 run 이 남긴 값이 그대로 읽히므로, 이번 시퀀스가 그 단계를 돌았는지는
531
+ `steps` 로 판단한다.
532
+
533
+ 읽기가 실패하면 값을 지어내지 않고 사유를 함께 돌려준다.
534
+ """
535
+ try:
536
+ manifest = load_owned_object(
537
+ ctx.task_manifest_path, artifact="task-manifest"
538
+ )
539
+ except (JsonBoundaryError, OSError) as exc:
540
+ return promote_next_phase(None), f"{ctx.task_manifest_path}: {exc}"
541
+ workflow = manifest.get("workflow")
542
+ if not isinstance(workflow, Mapping):
543
+ return promote_next_phase(None), ""
544
+ return promote_next_phase(workflow.get("nextRecommendedPhase")), ""
497
545
 
498
546
 
499
547
  def _run_finalize_step(
@@ -529,6 +577,7 @@ def _run_project_activity(
529
577
  try:
530
578
  if ctx.report_contract_version == "3.0":
531
579
  assembled = assemble_report(ctx.project_root, ctx.manifest_path)
580
+ _point_task_manifest_at_report(ctx)
532
581
  count = len(assembled.get("agentActivity") or [])
533
582
  else:
534
583
  rows = project_agent_activity(
@@ -556,6 +605,34 @@ def write_execution_roles(ctx: FinalizeContext) -> None:
556
605
  write_owned_object_atomic(ctx.data_path, updated, artifact="final report record")
557
606
 
558
607
 
608
+ def _point_task_manifest_at_report(ctx: FinalizeContext) -> None:
609
+ """게시한 data.json 을 latestReport 로 올린다.
610
+
611
+ render-only prepare 는 기존 포인터를 보존한다. 조립이 새 리포트를 썼는데도
612
+ task-manifest 가 예전 seq 를 가리키면 검사가 통과해도 상태가 004 에 남는다.
613
+ """
614
+ path = ctx.task_manifest_path
615
+ if not path.is_file() or not ctx.data_path.is_file():
616
+ return
617
+ try:
618
+ payload = load_owned_object(path, artifact="task manifest")
619
+ except (OSError, JsonBoundaryError):
620
+ return
621
+ if not isinstance(payload, Mapping):
622
+ return
623
+ try:
624
+ relative = ctx.data_path.resolve().relative_to(
625
+ ctx.project_root.resolve()
626
+ ).as_posix()
627
+ except ValueError:
628
+ relative = str(ctx.data_path)
629
+ if payload.get("latestReportRecordPath") == relative:
630
+ return
631
+ updated = dict(payload)
632
+ updated["latestReportRecordPath"] = relative
633
+ write_owned_object_atomic(path, updated, artifact="task manifest")
634
+
635
+
559
636
  def _link_lead_result_for_validation(ctx: FinalizeContext) -> None:
560
637
  """Link the lead-authored synthesis artifact before Phase 7 validation."""
561
638
  manifest = _load_manifest(ctx.manifest_path)
@@ -566,8 +643,11 @@ def _link_lead_result_for_validation(ctx: FinalizeContext) -> None:
566
643
  lead_dispatches = [
567
644
  row for row in (team_state.get("agentDispatches") or [])
568
645
  if isinstance(row, Mapping)
569
- and row.get("audience") == "lead"
570
- and row.get("workerId") == "lead"
646
+ and (
647
+ row.get("audience") == "lead"
648
+ or row.get("workerId") == "lead"
649
+ or str(row.get("assignmentRef") or "") in {"lead", "lead/lead"}
650
+ )
571
651
  ]
572
652
  if len(lead_dispatches) != 1:
573
653
  return
@@ -621,8 +701,41 @@ def _context_from_args(args: argparse.Namespace) -> FinalizeContext:
621
701
  )
622
702
 
623
703
 
704
+ _CLI_EPILOG = r"""Usage:
705
+ okstra report-finalize --project-root <dir> --run-manifest <path> \
706
+ --report <final-report-<task-type>-<seq>.md> [--team-state <path>]
707
+
708
+ Runs the six Phase 7 steps in their contractual order against one final-report:
709
+
710
+ 1. project-activity project the run's activity events into the data.json
711
+ 2. check-source verify the data.json is the English SSOT
712
+ 3. token-usage substitute real token/cost numbers into the data.json
713
+ 4. render-views write the schema-v2 task-specific *.html sibling; v1
714
+ keeps the legacy conditional interactive view
715
+ 5. spawn-followups turn section 4 rows into task stubs
716
+ 6. validate-run validate the finished run artifacts
717
+
718
+ Every step is idempotent, so re-running after a fixed failure is safe. The
719
+ sequence stops at the first non-zero exit and reports which step failed, except
720
+ token-usage: its input is the lead session log, so a failure there does not
721
+ take the html, follow-ups and validation after it down with it. Whenever a step
722
+ exits non-zero the command prints the --only flags that resume the sequence
723
+ from the earliest failing step — follow those rather than rerunning the failing
724
+ step alone, which would leave the html rendered before the tokens landed.
725
+
726
+ This is the same code path the Codex lead adapter runs automatically after its
727
+ report-writer completes, so a Claude-led and a Codex-led run finalize
728
+ identically.
729
+
730
+ --workspace-root is owned by this command.
731
+ """
732
+
733
+
624
734
  def _parser() -> argparse.ArgumentParser:
625
735
  parser = argparse.ArgumentParser(
736
+ prog="okstra report-finalize",
737
+ epilog=_CLI_EPILOG,
738
+ formatter_class=argparse.RawDescriptionHelpFormatter,
626
739
  description="Run the Phase 7 report post-processing sequence.",
627
740
  )
628
741
  parser.add_argument("--project-root", required=True)
@@ -691,6 +804,80 @@ def _recovery_step_names(
691
804
  return []
692
805
 
693
806
 
807
+ def closeout_command(result: Mapping[str, Any]) -> dict[str, str]:
808
+ """이 run 이 사용자에게 건네야 할 명령.
809
+
810
+ `prompts/launch.template.md` "User closeout (BLOCKING)" 의 표를 코드로
811
+ 내린 것이다. 그 표는 문서에만 있었고, 표가 분기하는 포인터는 리드에게
812
+ 도착하지도 않았다 — 규칙이 있고 집행이 없는 자리였다. 여기서 판정하면
813
+ 리드가 표를 기억해 따라갈 필요가 없고, 리드가 침묵해도 명령은
814
+ `report-finalize` 출력에 남는다.
815
+
816
+ 산문을 해석하지 않는다. `blocked` / `pending` 의 근거 문장은 이미 무엇을
817
+ 실행할지 말하고 있으므로(`okstra_ctl.next_phase` 의 승인 차단 근거,
818
+ `validators/validate-run.py` 의 검증 실패 근거), 그 문장을 그대로 인용할
819
+ 자리로 넘기고 명령 칸은 비운다. 근거에서 명령을 다시 뽑아내는 것은
820
+ 기록된 값의 재구현이다.
821
+
822
+ 돌려주는 것은 `command` 와 `note` 두 칸이다. `command` 가 비면 근거
823
+ 문장이 그 자리를 대신한다.
824
+ """
825
+ failed_validate = any(
826
+ step.get("name") == STEP_VALIDATE_RUN and step.get("exitCode") != 0
827
+ for step in (result.get("steps") or [])
828
+ if isinstance(step, Mapping)
829
+ )
830
+ if failed_validate:
831
+ return {
832
+ "command": "/okstra-run",
833
+ "note": "validate-run failed — name the blocking cause in one line, "
834
+ "then re-run this same phase",
835
+ }
836
+ pointer = result.get("nextRecommendedPhase")
837
+ if not isinstance(pointer, Mapping):
838
+ return {"command": "/okstra-inspect status", "note": "no pointer recorded"}
839
+ status = str(pointer.get("status") or "").strip()
840
+ phase = str(pointer.get("phase") or "").strip()
841
+ rationale = str(pointer.get("rationale") or "").strip()
842
+ if status == STATUS_READY and phase:
843
+ return {"command": f"/okstra-run → {phase}", "note": ""}
844
+ if status == STATUS_TERMINAL:
845
+ return {
846
+ "command": "",
847
+ "note": "the lifecycle ends here — say the task is finished, name any "
848
+ "follow-up tasks this run registered, and do not send the user "
849
+ "to /okstra-inspect",
850
+ }
851
+ if status in (STATUS_BLOCKED, STATUS_PENDING) and rationale:
852
+ return {
853
+ "command": "",
854
+ "note": "quote the rationale above and issue the command it names",
855
+ }
856
+ return {"command": "/okstra-inspect status", "note": ""}
857
+
858
+
859
+ def _next_phase_summary_lines(result: Mapping[str, Any]) -> list[str]:
860
+ """다음 phase 포인터와 그것이 정하는 명령을 사람이 읽는 줄로."""
861
+ pointer = result.get("nextRecommendedPhase")
862
+ if not isinstance(pointer, Mapping):
863
+ return []
864
+ lines = [
865
+ f"next phase status: {pointer.get('status') or '-'}",
866
+ f"next phase: {pointer.get('phase') or '-'}",
867
+ ]
868
+ rationale = str(pointer.get("rationale") or "").strip()
869
+ if rationale:
870
+ lines.append(f"next phase rationale: {rationale}")
871
+ error = str(result.get("nextRecommendedPhaseError") or "").strip()
872
+ if error:
873
+ lines.append(f"next phase pointer unreadable: {error}")
874
+ closeout = closeout_command(result)
875
+ lines.append(f"next command: {closeout['command'] or '-'}")
876
+ if closeout["note"]:
877
+ lines.append(f"next command note: {closeout['note']}")
878
+ return lines
879
+
880
+
694
881
  def main(argv: Sequence[str] | None = None) -> int:
695
882
  args = _parser().parse_args(argv)
696
883
  try:
@@ -708,6 +895,10 @@ def main(argv: Sequence[str] | None = None) -> int:
708
895
  print("finalize steps:", file=sys.stderr)
709
896
  for line in _step_summary_lines(result, order):
710
897
  print(line, file=sys.stderr)
898
+ # closeout 이 분기하는 값. JSON 을 파싱하지 않고 훑는 리드에게도 보이도록
899
+ # 요약 줄로 한 번 더 낸다.
900
+ for line in _next_phase_summary_lines(result):
901
+ print(line, file=sys.stderr)
711
902
  if not result["ok"]:
712
903
  print(f"error: {result['reason']}", file=sys.stderr)
713
904
  recovery = _recovery_step_names(result, order)
@@ -68,8 +68,8 @@ def _own_section_ids(data: dict, omitted_fields: tuple[str, ...] = ()) -> set[st
68
68
 
69
69
  The ledger is the fallback home for a cited row, so it must not claim an id
70
70
  that has one — two elements with the same anchor send half the links to the
71
- wrong place. `crossVerification.consensus` numbers its rows `C-001` in some
72
- runs, exactly where a clarification lives.
71
+ wrong place. `crossVerification.consensus` numbers its rows `CV-001`, a
72
+ namespace of its own, so a `C-NNN` in the report has exactly one home.
73
73
 
74
74
  `omitted_fields` names task-block fields the HTML template does not render,
75
75
  so their ids do not count as anchored.
@@ -69,25 +69,21 @@ def _report_meta(data: dict, run_meta: HtmlRunMeta) -> dict[str, object]:
69
69
 
70
70
 
71
71
  def _localize(data: dict, data_path: Path) -> tuple[dict, str]:
72
- """Apply the translation sidecar for this report's language, if there is one.
73
-
74
- The data.json is the English SSOT and is never rewritten — the sidecar is
75
- read here and overlaid onto an in-memory copy, so only this HTML document
76
- speaks the reader's language. A pointer the sidecar left untranslated
77
- renders in English; that keeps a half-finished translation readable, and
78
- the counts are printed because a silently half-empty sidecar otherwise
79
- ships looking finished.
72
+ """이 리포트 언어의 번역 사이드카를 메모리 복사본에 덮어쓴다.
73
+
74
+ data.json 은 영어 정본이고 고치지 않는다. 사람이 읽는 HTML 만 독자 언어다.
75
+ 사이드카가 빠진 포인터는 영어로 남긴다. 사이드카 파일 자체가 없으면
76
+ 크롬만 번역되고 본문은 영어로 나가므로 렌더를 거부한다.
80
77
  """
81
78
  lang = str((data.get("meta") or {}).get("reportLanguage") or "en")
82
79
  if lang == "en":
83
80
  return data, lang
84
81
  sidecar_file = translation_sidecar_path(data_path, lang)
85
82
  if not sidecar_file.is_file():
86
- sys.stdout.write(
87
- f"note: no {lang} translation sidecar at {sidecar_file.name}; "
88
- "rendering the English source\n"
83
+ raise HtmlRenderError(
84
+ f"no {lang} translation sidecar at {sidecar_file.name}; "
85
+ "human HTML cannot fall back to the English source"
89
86
  )
90
- return data, lang
91
87
  payload = load_owned_object(sidecar_file, artifact="translation sidecar")
92
88
  strings = payload.get("strings")
93
89
  if not isinstance(strings, dict):
@@ -1,8 +1,12 @@
1
- """Report-language resolution for the report-writer prompt.
1
+ """Report-language resolution — the one place the precedence lives.
2
2
 
3
- Both dispatchers stamp `**Report Language:**` into the report-writer prompt, so
4
- the precedence (project config → global config → inferred from the task brief)
5
- lives here rather than in whichever dispatcher happened to need it first.
3
+ Two surfaces need the answer and must agree: the `**Report Language:**` line
4
+ both dispatchers stamp into the report-writer prompt, and `reportLanguage` in
5
+ the run manifest, which report assembly copies into `meta.reportLanguage` and
6
+ the HTML renderer reads. When only the prompt called this, a project could ask
7
+ for `ko`, get a Korean-instructed writer, and still ship an English report.
8
+
9
+ Precedence: project config → global config → inferred from the task brief.
6
10
  """
7
11
  from __future__ import annotations
8
12
 
@@ -38,8 +38,25 @@ class _Node:
38
38
  self.children: list[_Node] = []
39
39
 
40
40
 
41
+ _NARRATIVE_SCHEMA_RELATIVE = ("schemas", "report-narrative-v3.0.schema.json")
42
+
43
+
41
44
  def _narrative_schema_path() -> Path:
42
- return Path(__file__).resolve().parents[2] / "schemas" / "report-narrative-v3.0.schema.json"
45
+ """schemas 를 실제로 가진 루트에서 해소한다.
46
+
47
+ 부모 개수를 세면 체크아웃에서만 맞는다 — 설치본은 패키지가
48
+ `~/.okstra/lib/python/` 이고 schemas 는 `~/.okstra/schemas/` 라 한 단계
49
+ 어긋나서, 설치본으로 실행할 때 서사 파싱이 스키마 없음으로 죽었다.
50
+ """
51
+ from .paths import find_asset_root
52
+
53
+ root = find_asset_root(_NARRATIVE_SCHEMA_RELATIVE)
54
+ if root is None:
55
+ raise NarrativeContractError(
56
+ "could not locate report-narrative-v3.0.schema.json. Set OKSTRA_HOME "
57
+ "or run from a checkout that contains schemas/."
58
+ )
59
+ return root.joinpath(*_NARRATIVE_SCHEMA_RELATIVE)
43
60
 
44
61
 
45
62
  def _narrative_schema() -> dict[str, Any]:
@@ -50,6 +67,72 @@ def _allowed_top_level() -> frozenset[str]:
50
67
  return frozenset(_narrative_schema().get("properties", {}))
51
68
 
52
69
 
70
+ # 완성 리포트 스키마가 작성자 소유 블록 안에서 required 로 거는 기계 소유 필드.
71
+ # `writer_owned_data` 가 떼어내고 `_NESTED_FORBIDDEN` 이 저작을 막는 바로 그
72
+ # 이름들이라, 값 제약을 그대로 쓰면 정상 서사가 전부 "필수 필드 없음" 으로
73
+ # 깨진다. 앞의 둘은 `_NESTED_FORBIDDEN` 에서 파생시켜 두 목록이 갈라지지
74
+ # 않게 한다.
75
+ _MACHINE_OWNED_NESTED = frozenset(
76
+ {path.rsplit(".", 1)[-1] for path in _NESTED_FORBIDDEN} | {"designSurfaceCoverage"}
77
+ )
78
+
79
+
80
+ def _without_machine_owned_required(node: Any, *, negated: bool = False) -> Any:
81
+ """`required` 목록에서 기계 소유 필드 이름만 뺀 스키마 사본.
82
+
83
+ `not` 아래는 건드리지 않는다. 거기서 `required: [designPreparation]` 은
84
+ "이 필드가 있으면 안 된다" 는 뜻이고 서사에는 원래 없으니 이미 만족한다.
85
+ 이름을 빼면 `not: {required: []}` 가 되는데, 빈 `required` 는 모든 객체가
86
+ 만족하므로 그 `not` 은 항상 거짓이 된다 — 통과해야 할 분기를 통과 불가로
87
+ 뒤집는다. 실제로 `direction-invalidated` 계획 분기가 그렇게 죽었다.
88
+ """
89
+ if isinstance(node, list):
90
+ return [_without_machine_owned_required(item, negated=negated) for item in node]
91
+ if not isinstance(node, dict):
92
+ return node
93
+ result: dict[str, Any] = {}
94
+ for key, value in node.items():
95
+ if key == "required" and isinstance(value, list) and not negated:
96
+ result[key] = [
97
+ name for name in value if name not in _MACHINE_OWNED_NESTED
98
+ ]
99
+ else:
100
+ result[key] = _without_machine_owned_required(
101
+ value, negated=negated or key == "not"
102
+ )
103
+ return result
104
+
105
+
106
+ def _writer_owned_schema(schema: Mapping[str, Any]) -> dict[str, Any]:
107
+ """작성자가 쓴 서사에 걸 값 제약 — 완성 리포트 스키마에서 잘라 온다.
108
+
109
+ `report-narrative-v3.0.schema.json` 은 최상위 **이름** 허용목록이고 값은
110
+ 전부 `{"type": "object"}` 스텁이다. 그 스텁으로만 검증하면 enum 밖 값과
111
+ 빠진 필수 필드가 서사 단계를 통과하고, Phase 7 조립이 완성 리포트 스키마에
112
+ 부딪힐 때에야 거절된다 — 작성기 워커가 이미 끝난 뒤라 되돌릴 방법이 없다.
113
+ 실측(2026-08-26, stage 10 run 002): `verdictCard.direction` 에 enum 에 없는
114
+ `proceed` 가, `humanSummary` 에 필수 `whyItMatters` 가 빠진 채로 서사가
115
+ 통과했고 `report-finalize` 의 `project-activity` 가 exit 1 로 떨어졌다.
116
+
117
+ 그래서 이름 소유권은 서사 스키마에서, 값 제약은 넘겨받은 완성 리포트
118
+ 스키마에서 가져온다. 두 벌을 만들지 않으므로 드리프트가 생기지 않는다.
119
+ 서사는 부분 문서다 — 최상위 `required` 는 싣지 않는다. 이 run 의 task
120
+ 블록만 있고 나머지 task-type 블록과 기계 소유 최상위 필드는 없기 때문이다.
121
+ """
122
+ allowed = _allowed_top_level()
123
+ properties = {
124
+ key: value
125
+ for key, value in schema.get("properties", {}).items()
126
+ if key in allowed
127
+ }
128
+ return {
129
+ "type": "object",
130
+ "additionalProperties": False,
131
+ "properties": _without_machine_owned_required(properties),
132
+ "$defs": _without_machine_owned_required(schema.get("$defs", {})),
133
+ }
134
+
135
+
53
136
  def writer_owned_data(data: Mapping[str, Any]) -> dict[str, Any]:
54
137
  """완성 리포트에서 보고서 작성자 소유 필드만 복사한다."""
55
138
  owned = {
@@ -331,7 +414,11 @@ def _parse_object(node: _Node, schema_node: Any, index: SchemaIndex, path: str)
331
414
 
332
415
 
333
416
  def parse_narrative(markdown: str, schema: Mapping[str, Any]) -> dict[str, Any]:
334
- """Markdown을 작성자 소유 자료로 읽고 소유권 표면을 검증한다."""
417
+ """Markdown을 작성자 소유 자료로 읽고, 소유권 표면과 값 제약을 검증한다.
418
+
419
+ `schema` 는 완성 리포트 스키마다. 이름·타입 해석과 값 검증이 모두 그
420
+ 한 벌에서 나온다(`_writer_owned_schema`).
421
+ """
335
422
  root = _parse_tree(markdown)
336
423
  index = SchemaIndex(schema)
337
424
  result = _parse_object(root, schema, index, "")
@@ -345,7 +432,7 @@ def parse_narrative(markdown: str, schema: Mapping[str, Any]) -> dict[str, Any]:
345
432
  f"owner=report-writer cannot author fields: {labels}; "
346
433
  f"writer-owned top-level fields: {owned}"
347
434
  )
348
- errors = validate(result, _narrative_schema())
435
+ errors = validate(result, _writer_owned_schema(schema))
349
436
  if errors:
350
437
  raise NarrativeContractError("; ".join(errors))
351
438
  return result
@@ -168,6 +168,9 @@ def _worker_details(team_state: Mapping[str, Any]) -> list[dict[str, Any]]:
168
168
  usage = worker.get("usage") if isinstance(worker.get("usage"), Mapping) else {}
169
169
  row = {"label": _text(worker.get("role") or worker.get("workerId"), "Worker")}
170
170
  _populate_usage(row, usage)
171
+ # 작업 시간은 executionStatus 칸이다. workerDetails 스키마는
172
+ # durationMs 를 허용하지 않아 조립이 additionalProperties 로 거절한다.
173
+ row.pop("durationMs", None)
171
174
  for key in ("totalTokens", "cacheReadTokens", "billableTokens", "costUsd", "cliTotalTokens", "cliCostUsd"):
172
175
  row.setdefault(key, None)
173
176
  result.append(row)
@@ -231,6 +234,9 @@ def project_design(
231
234
  def _round_history(state: Mapping[str, Any]) -> dict[str, Any]:
232
235
  if not (state.get("config") or {}).get("enabled", True):
233
236
  return {"disabled": True}
237
+ skip_reason = state.get("round2SkippedReason", "not-skipped")
238
+ if skip_reason == "auto-disabled":
239
+ skip_reason = "single-analyser-only"
234
240
  rows = []
235
241
  for row in state.get("roundHistory") or []:
236
242
  if not isinstance(row, Mapping):
@@ -243,7 +249,7 @@ def _round_history(state: Mapping[str, Any]) -> dict[str, Any]:
243
249
  "dispatches": json.dumps(row.get("dispatches") or [], ensure_ascii=False),
244
250
  "skippedWorkers": json.dumps(row.get("skippedWorkers") or [], ensure_ascii=False),
245
251
  })
246
- return {"rounds": rows, "round2SkippedReason": state.get("round2SkippedReason", "not-skipped")}
252
+ return {"rounds": rows, "round2SkippedReason": skip_reason}
247
253
 
248
254
 
249
255
  def _source_items(finding: Mapping[str, Any]) -> list[str]:
@@ -265,7 +271,7 @@ def project_convergence(state: Mapping[str, Any]) -> dict[str, Any]:
265
271
  ticket_id = _text(ticket_ids[0] if ticket_ids else "unknown", "unknown")
266
272
  if finding.get("classification") in {"full-consensus", "partial-consensus"}:
267
273
  row = {
268
- "id": f"C-{len(consensus) + 1:03d}",
274
+ "id": f"CV-{len(consensus) + 1:03d}",
269
275
  "ticketId": ticket_id,
270
276
  "statement": _text(finding.get("summary"), "Converged finding"),
271
277
  "sourceItems": _source_items(finding),