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
@@ -16,9 +16,10 @@ from dataclasses import asdict, dataclass
16
16
  from pathlib import Path
17
17
 
18
18
  from okstra_ctl.final_report_paths import final_report_data_path
19
+ from okstra_ctl.paths import infer_project_root
19
20
  from okstra_ctl.stage_citations import cited_stage_numbers
20
21
  from okstra_ctl.stage_targets import downstream_stage_closure
21
- from okstra_ctl.json_boundary import load_owned_object
22
+ from okstra_ctl.json_boundary import load_owned_object, write_owned_object_atomic
22
23
 
23
24
  CUTOFF_RATIO = 0.5
24
25
 
@@ -27,6 +28,7 @@ CUTOFF_RATIO = 0.5
27
28
  # which is what made a re-run's "no impacted stages resolved" unreadable as
28
29
  # either a structural judgement or a mapping the lead never made.
29
30
  DECLARED_FULL_PREFIX = "declared structural change:"
31
+ DECLARED_CARRY_ALL_PREFIX = "declared additive change:"
30
32
 
31
33
  # CLI-to-lead signal only: an answered id traced to no stage and the user has
32
34
  # not named `--impacted` stages. Never recorded as incrementalDecision (schema
@@ -54,7 +56,7 @@ class UserReverifyScope:
54
56
  The lead still runs `okstra incremental-scope`; this only says which of the
55
57
  CLI's inputs the user pinned. `auto` pins nothing.
56
58
  """
57
- mode: str # "auto" | "full" | "stages"
59
+ mode: str # "auto" | "full" | "carry-all" | "stages"
58
60
  stages: list[int]
59
61
 
60
62
 
@@ -62,10 +64,18 @@ class ReverifyScopeError(ValueError):
62
64
  """The `--reverify-scope` value is not one of the three accepted forms."""
63
65
 
64
66
 
67
+ # 위저드가 "이월만 하고 재검증은 없음" 을 고를 때 쓰는 핀. 직전 stage 가 전부
68
+ # `done` 이고 이번 재실행이 stage 를 **추가만** 하는 형태에서는 지정할 stage 번호가
69
+ # 없다 — 그래서 `stages` 핀으로 표현할 수 없고, `full` 을 고르면 이월해도 되는
70
+ # stage 를 전부 다시 교차검증하는 비용을 사용자가 치른다.
71
+ CARRY_ALL_SCOPE = "carry-all"
72
+
73
+
65
74
  def parse_user_reverify_scope(raw: str) -> UserReverifyScope:
66
75
  """`--reverify-scope` → the user's pinned scope.
67
76
 
68
- Accepts exactly `""` / `auto`, `full`, or a comma-separated stage list.
77
+ Accepts exactly `""` / `auto`, `full`, `carry-all`, or a comma-separated
78
+ stage list.
69
79
  Anything else raises rather than degrading to `auto`: a typo silently read
70
80
  as "let the lead decide" would drop a full-re-verification request the user
71
81
  made on purpose, and the run would look like it honoured it.
@@ -73,13 +83,13 @@ def parse_user_reverify_scope(raw: str) -> UserReverifyScope:
73
83
  value = (raw or "").strip()
74
84
  if not value or value == "auto":
75
85
  return UserReverifyScope("auto", [])
76
- if value == "full":
77
- return UserReverifyScope("full", [])
86
+ if value in ("full", CARRY_ALL_SCOPE):
87
+ return UserReverifyScope(value, [])
78
88
  tokens = [token.strip() for token in value.split(",") if token.strip()]
79
89
  if not tokens or not all(token.isdigit() for token in tokens):
80
90
  raise ReverifyScopeError(
81
- f"--reverify-scope must be 'auto', 'full', or a stage-number list "
82
- f"(e.g. '2,3'); got {raw!r}"
91
+ f"--reverify-scope must be 'auto', 'full', {CARRY_ALL_SCOPE!r}, or a "
92
+ f"stage-number list (e.g. '2,3'); got {raw!r}"
83
93
  )
84
94
  return UserReverifyScope("stages", sorted({int(token) for token in tokens}))
85
95
 
@@ -369,6 +379,57 @@ def decide_scope(
369
379
  )
370
380
 
371
381
 
382
+ def decision_record(
383
+ decision: IncrementalDecision,
384
+ *,
385
+ prev_data: str,
386
+ ) -> dict:
387
+ """디스크에 남길 판정. narrative 의 `incrementalDecision` 과 같은 camelCase.
388
+
389
+ 작성자가 리포트에 적을 필드명과 다르게 두면 옮겨 적는 사람이 매핑을 한 번
390
+ 더 해야 하고, 그 매핑이 이 판정을 전달하는 유일한 경로였다.
391
+ """
392
+ return {
393
+ "schemaVersion": "1.0",
394
+ "mode": decision.mode,
395
+ "reverifyStages": list(decision.reverify_stages),
396
+ "carryStages": list(decision.carry_stages),
397
+ "reason": decision.reason,
398
+ "prevDataPath": prev_data,
399
+ }
400
+
401
+
402
+ def _record_path(run_manifest: Path) -> Path:
403
+ """매니페스트가 이 run 의 판정 레코드로 지목한 경로.
404
+
405
+ 경로를 CLI 인자로 받지 않는 이유가 이 함수다 — 판정을 쓰는 쪽과 읽는 쪽이
406
+ 각자 경로를 조립하면 그 조립이 어긋날 자리가 생기고, 어긋나면 읽는 쪽은
407
+ "판정이 없다"(= full)로 읽는다. 매니페스트 한 곳이 답한다.
408
+ """
409
+ manifest = load_owned_object(run_manifest, artifact="run manifest")
410
+ value = manifest.get("incrementalDecisionPath")
411
+ if not isinstance(value, str) or not value:
412
+ raise ValueError(
413
+ "run manifest has no incrementalDecisionPath; re-render the run "
414
+ "bundle with a runtime that ships it"
415
+ )
416
+ candidate = Path(value)
417
+ if candidate.is_absolute():
418
+ return candidate
419
+ # 매니페스트의 경로 필드는 프로젝트 상대다. 부모를 고정 횟수로 세지 않는다
420
+ # — 매니페스트는 `.okstra/tasks/<group>/<id>/runs/<type>/manifests/` 아래에
421
+ # 있고 그 깊이를 손으로 세면 계층이 하나 바뀔 때 조용히 어긋난다.
422
+ return infer_project_root(run_manifest.resolve()) / candidate
423
+
424
+
425
+ def _persist(decision: IncrementalDecision, args) -> None:
426
+ write_owned_object_atomic(
427
+ _record_path(Path(args.run_manifest)),
428
+ decision_record(decision, prev_data=args.prev_data),
429
+ artifact="incremental scope decision",
430
+ )
431
+
432
+
372
433
  def _preview_result(args) -> dict:
373
434
  """`preview_link_availability` over CLI args, degrading to full on bad input.
374
435
 
@@ -394,6 +455,42 @@ def _preview_result(args) -> dict:
394
455
  }
395
456
 
396
457
 
458
+ def _carry_all_decision(args, declared: str) -> IncrementalDecision:
459
+ """이전 stage 전부를 그대로 이월하고 아무것도 재검증하지 않는 판정.
460
+
461
+ base ref 는 여전히 본다 — 브랜치가 움직였으면 이전 stage 본문이 "그대로"
462
+ 가 아니고, 그때 이월은 사실이 아닌 주장이 된다.
463
+ """
464
+ if not args.prev_base_sha or args.prev_base_sha != args.cur_base_sha:
465
+ return IncrementalDecision(
466
+ "full", [], [],
467
+ "the branch this plan was written against has moved on "
468
+ f"({args.prev_base_sha or 'unrecorded'} -> "
469
+ f"{args.cur_base_sha or 'unrecorded'}), so the prior stages cannot "
470
+ "be carried as written even though this run only adds to them",
471
+ )
472
+ try:
473
+ data = load_owned_object(
474
+ Path(args.prev_data), artifact="incremental scope report"
475
+ )
476
+ stages = parse_stage_graph(data)
477
+ except (OSError, ValueError, KeyError, TypeError) as exc:
478
+ return IncrementalDecision(
479
+ "full", [], [], f"invalid incremental-scope input: {exc}",
480
+ )
481
+ carry = sorted({stage for stage, _ in stages})
482
+ if not carry:
483
+ return IncrementalDecision(
484
+ UNRESOLVED_MODE, [], [],
485
+ "--carry-all-reason was given but the prior report has no Stage Map "
486
+ "row to carry; check --prev-data",
487
+ )
488
+ return IncrementalDecision(
489
+ "incremental", [], carry,
490
+ f"{DECLARED_CARRY_ALL_PREFIX} {declared}",
491
+ )
492
+
493
+
397
494
  def _decision_for_run(args) -> IncrementalDecision:
398
495
  """SHA·폐포·컷오프 판정. unlinked 는 full 로 강등하지 않는다."""
399
496
  data = load_owned_object(
@@ -419,8 +516,10 @@ def _decision_for_run(args) -> IncrementalDecision:
419
516
  [],
420
517
  [],
421
518
  f"{', '.join(unlinked)} trace(s) to no stage in the prior report — "
422
- "pass --impacted with the stage numbers those answers affect, or "
423
- "--full-reason for a structural change",
519
+ "pass --impacted with the stage numbers those answers affect, "
520
+ "--full-reason for a structural change, or --carry-all-reason when "
521
+ "this run only adds stages and every prior stage carries forward "
522
+ "as written",
424
523
  )
425
524
  impacted.update(linked)
426
525
  unknown_stages = impacted - {stage for stage, _ in stages}
@@ -435,9 +534,39 @@ def _decision_for_run(args) -> IncrementalDecision:
435
534
  )
436
535
 
437
536
 
537
+ _CLI_EPILOG = r"""Usage:
538
+ okstra incremental-scope --prev-data <path> --cur-base-sha <sha> \
539
+ --prev-base-sha <sha> [--impacted <csv>] [--option <name>]
540
+ okstra incremental-scope --preview --prev-data <path> \
541
+ --answered-clarifications <csv>
542
+
543
+ Prints a JSON decision {mode, reverify_stages, carry_stages, reason} to stdout.
544
+
545
+ `--preview` prints {wouldForceFull, unlinkedIds, reason} instead and needs no
546
+ base SHA. It answers only the link half — whether the answered ids trace to a
547
+ stage in the prior report — so it can run before `render-bundle` fixes a
548
+ worktree base commit, while the decision to start is still reversible.
549
+ `wouldForceFull: false` is not a promise of `incremental`: the base-ref check
550
+ still runs at decision time.
551
+ """
552
+ _CLI_DESCRIPTION = "Decide the re-verify vs carry-forward scope for a clarification re-run."
553
+
554
+
438
555
  def main(argv: list[str]) -> int:
439
- ap = argparse.ArgumentParser(prog="okstra incremental-scope")
556
+ ap = argparse.ArgumentParser(
557
+ description=_CLI_DESCRIPTION,
558
+ epilog=_CLI_EPILOG,
559
+ formatter_class=argparse.RawDescriptionHelpFormatter,
560
+ prog="okstra incremental-scope")
440
561
  ap.add_argument("--prev-data", required=True, help="prior run final-report data.json")
562
+ ap.add_argument(
563
+ "--run-manifest",
564
+ default="",
565
+ help="this run's manifest. Required unless --preview: the decision is "
566
+ "written to the record it names (incrementalDecisionPath) so the "
567
+ "report writer's authoring contract and `incremental-carry` read "
568
+ "the same values instead of re-typed CSVs",
569
+ )
441
570
  # Not required: `--preview` answers the link half before a worktree base
442
571
  # commit exists, which is the whole reason that mode is there.
443
572
  ap.add_argument("--cur-base-sha", default="")
@@ -464,21 +593,49 @@ def main(argv: list[str]) -> int:
464
593
  "recommended approach. Forces full and records the judgement, so it "
465
594
  "is distinguishable from a re-run that simply resolved no stage",
466
595
  )
596
+ ap.add_argument(
597
+ "--carry-all-reason",
598
+ default="",
599
+ help="declare that this run only ADDS to the prior plan — every prior "
600
+ "stage carries forward as written and nothing is re-verified. The "
601
+ "mirror of --full-reason: without it a run that appends a stage has "
602
+ "no answer to give, because pinning any prior stage as impacted "
603
+ "makes `incremental-carry` demand it from a snapshot that no longer "
604
+ "holds it",
605
+ )
467
606
  args = ap.parse_args(argv)
468
607
 
469
608
  if args.preview:
470
609
  print(json.dumps(_preview_result(args), ensure_ascii=False))
471
610
  return 0
472
611
 
612
+ if not args.run_manifest.strip():
613
+ ap.error("--run-manifest is required unless --preview")
614
+
473
615
  declared = args.full_reason.strip()
474
616
  if declared:
475
617
  # The lead's structural judgement is exactly what the back-trace cannot
476
618
  # make, so a successful trace must not override it.
477
- print(json.dumps(asdict(IncrementalDecision(
619
+ decision = IncrementalDecision(
478
620
  "full", [], [], f"{DECLARED_FULL_PREFIX} {declared}",
479
- )), ensure_ascii=False))
621
+ )
622
+ _persist(decision, args)
623
+ print(json.dumps(asdict(decision), ensure_ascii=False))
480
624
  return 0
481
625
 
626
+ additive = args.carry_all_reason.strip()
627
+ if additive:
628
+ # 추가만 하는 run 은 되짚기로 표현할 수 없다. 이전 stage 중 어느 것도
629
+ # 영향받지 않았다는 것은 `--impacted` 로 못 쓰고(빈 값은 "좁힐 수
630
+ # 없음" 으로 읽힌다), 아무 stage 나 찍으면 `incremental-carry` 가 그
631
+ # stage 를 현재 스냅샷에서 찾다가 거절한다 — done 이라 이번 서술문에
632
+ # 없기 때문이다. 그래서 판단을 선언으로 받는다.
633
+ decision = _carry_all_decision(args, additive)
634
+ if decision.mode != UNRESOLVED_MODE:
635
+ _persist(decision, args)
636
+ print(json.dumps(asdict(decision), ensure_ascii=False))
637
+ return 0 if decision.mode != UNRESOLVED_MODE else 1
638
+
482
639
  try:
483
640
  decision = _decision_for_run(args)
484
641
  # Every bad input degrades to a full re-verification rather than raising:
@@ -491,6 +648,10 @@ def main(argv: list[str]) -> int:
491
648
  decision = IncrementalDecision(
492
649
  "full", [], [], f"invalid incremental-scope input: {exc}",
493
650
  )
651
+ # `unresolved` 는 판정이 아니라 리드에게 되묻는 신호이고 리포트 스키마의
652
+ # enum 에도 없다. 레코드로 남기면 소비자가 그것을 판정으로 읽는다.
653
+ if decision.mode != UNRESOLVED_MODE:
654
+ _persist(decision, args)
494
655
  print(json.dumps(asdict(decision), ensure_ascii=False))
495
656
  return 0
496
657
 
@@ -58,7 +58,7 @@ def record_start(home: Path, *, project_id: str, project_root: str,
58
58
  final_status_rel=final_status_rel,
59
59
  execution_manifest_path=execution_manifest_path,
60
60
  role_execution_refs=role_execution_refs)
61
- # okstra-ctl 가 미리 'reserving' 예약 row 를 작성했을 수 있다(P1-2). 같은
61
+ # 앞선 prepare 가 미리 'reserving' 예약 row 를 작성했을 수 있다(P1-2). 같은
62
62
  # runId 의 예약 row 가 있으면 update; 없으면 append.
63
63
  existing_rows = read_jsonl(home / "active.jsonl")
64
64
  had_reservation = any(r.get("runId") == run_id for r in existing_rows)
@@ -68,7 +68,7 @@ def record_start(home: Path, *, project_id: str, project_root: str,
68
68
  remove_jsonl_row(home / "active.jsonl",
69
69
  lambda r: r.get("runId") == run_id)
70
70
  # 동일 runId 가 이미 recent.jsonl 에 있으면(예: 매니페스트 직후 사용자가
71
- # 처음 okstra-ctl 을 실행해 자동 backfill 이 같은 terminal run 을 미리
71
+ # 자동 backfill 이 같은 terminal run 을 미리
72
72
  # ingest 한 경우) 중복 append 를 방지하기 위해 기존 row 를 제거하고 새로
73
73
  # 쓴다(replace). 그렇지 않으면 동일 runId 가 두 줄로 남아 prefix 해석이
74
74
  # 모호해지고 runCount 도 재증가한다.
@@ -11,7 +11,7 @@ from pathlib import Path
11
11
  from typing import Any, Callable, Literal, Mapping, Sequence
12
12
 
13
13
  from .final_report_paths import final_report_data_path
14
- from .agent_invocation import (
14
+ from .agent.invocation import (
15
15
  AgentInstruction,
16
16
  AgentInstructionSource,
17
17
  AgentInvocationError,
@@ -377,14 +377,18 @@ def _prepare_prompt_item(
377
377
  except InitialPromptMaterializationError:
378
378
  raise
379
379
  except Exception as exc:
380
+ leaked = ""
380
381
  if temp_path is not None:
381
382
  try:
382
383
  temp_path.unlink(missing_ok=True)
383
- except OSError:
384
- pass
384
+ except OSError as cleanup_exc:
385
+ # 정리 실패를 버리면 남은 .tmp 파일이 원인 없이 쌓인다. 원 오류를
386
+ # 가리지 않도록 던지지 않고 메시지에 실어 보낸다.
387
+ leaked = (f"; the staging file {temp_path} could not be removed "
388
+ f"({cleanup_exc})")
385
389
  raise InitialPromptMaterializationError(
386
390
  "render_failed",
387
- f"cannot render initial prompt for {item.worker.worker_id}: {exc}",
391
+ f"cannot render initial prompt for {item.worker.worker_id}: {exc}{leaked}",
388
392
  ) from exc
389
393
  if temp_path is None:
390
394
  raise InitialPromptMaterializationError(
@@ -590,6 +594,7 @@ def _prompt_body(
590
594
  worker.worker_id,
591
595
  worker.model,
592
596
  plan,
597
+ _optional_approved_plan_path(context) if plan.include_approved_plan else "",
593
598
  )
594
599
 
595
600
 
@@ -601,10 +606,7 @@ def _report_writer_packet_source(
601
606
  context: _MaterializationContext,
602
607
  plan: PromptPlan,
603
608
  ) -> AgentInstructionSource | None:
604
- if (
605
- plan.audience != "report-writer"
606
- or not uses_report_contract_v3(context.manifest)
607
- ):
609
+ if not _report_writer_reads_a_packet(context, plan):
608
610
  return None
609
611
  _, markdown_path = report_synthesis_packet_paths(
610
612
  _report_writer_narrative_path(context)
@@ -615,6 +617,25 @@ def _report_writer_packet_source(
615
617
  )
616
618
 
617
619
 
620
+ def _report_writer_reads_a_packet(
621
+ context: "_MaterializationContext",
622
+ plan: PromptPlan,
623
+ ) -> bool:
624
+ """report-writer 의 `## Inputs` 가 합성 묶음 한 줄로 접히는가.
625
+
626
+ ADR-0023 이후 report-writer 는 합성 묶음 하나만 받는다 — clarification
627
+ 응답을 포함한 개별 경로는 열거되지 않는다. contract v3 가 아닌 매니페스트는
628
+ 아직 옛 목록(`report_writer_input_lines`)을 쓰고 그 목록에는 clarification
629
+ 줄이 있다. 즉 이 답은 정적 plan 플래그가 아니라 이 run 의 계약 버전에
630
+ 달려 있고, 본문을 조립한 쪽과 그 본문을 검사하는 쪽이 같은 술어를 써야
631
+ 한다 — 갈리면 clarification 을 받은 planning 재실행이 Phase 6 에서
632
+ `existing_prompt_invalid` 로 막힌다.
633
+ """
634
+ return plan.audience == "report-writer" and uses_report_contract_v3(
635
+ context.manifest
636
+ )
637
+
638
+
618
639
  def _materialize_report_writer_packet(
619
640
  context: _MaterializationContext,
620
641
  plan: PromptPlan,
@@ -993,6 +1014,7 @@ def _validate_existing_clarification_metadata(
993
1014
  enumerated = (
994
1015
  item.plan.audience in _MATERIALIZABLE_AUDIENCES
995
1016
  and not item.plan.packet_only
1017
+ and not _report_writer_reads_a_packet(context, item.plan)
996
1018
  )
997
1019
  input_values = _backticked_line_values(
998
1020
  _markdown_section(text, "## Inputs"),
@@ -1253,6 +1275,9 @@ def _result_paths(
1253
1275
  worker_result = _required_string(state, "resultPath")
1254
1276
  if worker_id != REPORT_WRITER_WORKER_ID:
1255
1277
  return worker_result, None
1278
+ if uses_report_contract_v3(context.manifest):
1279
+ narrative = report_narrative_path(context.project_root, context.manifest)
1280
+ return _project_relative(context.project_root, narrative), worker_result
1256
1281
  expected_report = _required_string(context.manifest, "expectedReportRecordPath")
1257
1282
  return str(final_report_data_path(Path(expected_report))), worker_result
1258
1283
 
@@ -1336,6 +1361,21 @@ def _required_approved_plan_path(context: _MaterializationContext) -> str:
1336
1361
  return str(_resolve_input_path(context.project_root, Path(value)))
1337
1362
 
1338
1363
 
1364
+ def _optional_approved_plan_path(context: _MaterializationContext) -> str:
1365
+ """The approved plan path when this run recorded one, otherwise empty.
1366
+
1367
+ `_required_approved_plan_path` speaks for implementation, where a missing
1368
+ plan is a contract violation. An acceptance verifier enumerates the same
1369
+ file as an input, and a run prepared without one still has to produce a
1370
+ prompt — so the absence is reported by the empty line being dropped, not by
1371
+ refusing to materialize.
1372
+ """
1373
+ try:
1374
+ return _required_approved_plan_path(context)
1375
+ except InitialPromptMaterializationError:
1376
+ return ""
1377
+
1378
+
1339
1379
  def _required_stage_number(context: _MaterializationContext) -> str:
1340
1380
  value = _string_value(_active_run_field(context, "stage"))
1341
1381
  if not value:
@@ -0,0 +1,223 @@
1
+ """`okstra.sh` 대화형 입력 경로가 쓰는 조회 헬퍼들.
2
+
3
+ 이 여섯 갈래는 원래 `scripts/lib/okstra/interactive.sh` 의 파이썬 heredoc 안에
4
+ 있었다(docs/coding-rules.md R1 위반). 셸 문자열 안에 있는 동안에는 import 도
5
+ 테스트도 할 수 없었다. 로직과 출력 형식은 그대로 옮겼다 — 호출부의 파서를
6
+ 건드리지 않기 위해서다.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import json
12
+ import os
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ from . import next_phase
17
+ from .ids import slugify_task_segment
18
+ from .implementation_outcome import reconcile_implementation_outcome
19
+ from .json_boundary import JsonBoundaryError, load_owned_object
20
+ from .paths import RunRef, task_dir
21
+ from .run_context import latest_run_inputs
22
+
23
+
24
+ def _resolve_task_root(args: argparse.Namespace) -> int:
25
+ """`OK\\t<abs>` 또는 `MISSING` + 진단 줄들.
26
+
27
+ task root 의 slug 경로 구성은 `paths.task_dir` 이 단일 기준점이다. 예전에
28
+ 이 코드는 slugify 와 `.okstra/tasks/<slug>/<slug>` 구조를 자체 재구현해
29
+ 규칙이 바뀌면 조용히 어긋날 수 있었다.
30
+ """
31
+ project_root = Path(args.project_root)
32
+ requested_key = f"{args.project_id}:{args.task_group}:{args.task_id}"
33
+ requested_key_ci = requested_key.lower()
34
+
35
+ candidates: list[str] = []
36
+ catalog_keys: list[str] = []
37
+
38
+ catalog_path = project_root / ".okstra" / "discovery" / "task-catalog.json"
39
+ if catalog_path.is_file():
40
+ try:
41
+ catalog = load_owned_object(catalog_path, artifact="task catalog")
42
+ except JsonBoundaryError as exc:
43
+ sys.stderr.write(f"resolve_task_root: failed to parse {catalog_path}: {exc}\n")
44
+ catalog = None
45
+ if isinstance(catalog, dict):
46
+ catalog_project_id = catalog.get("projectId")
47
+ for entry in catalog.get("tasks") or []:
48
+ if not isinstance(entry, dict):
49
+ continue
50
+ entry_key = entry.get("taskKey") or ""
51
+ if not isinstance(entry_key, str) or not entry_key:
52
+ continue
53
+ belongs = (
54
+ catalog_project_id == args.project_id
55
+ or entry.get("projectId") == args.project_id
56
+ or entry_key.startswith(f"{args.project_id}:")
57
+ )
58
+ if belongs:
59
+ catalog_keys.append(entry_key)
60
+ if entry_key.lower() != requested_key_ci:
61
+ continue
62
+ rel = entry.get("taskRootPath") or entry.get("taskRoot")
63
+ if isinstance(rel, str) and rel:
64
+ abs_path = (
65
+ Path(rel) if os.path.isabs(rel)
66
+ else (project_root / rel).resolve()
67
+ )
68
+ if abs_path.is_dir():
69
+ print(f"OK\t{abs_path}")
70
+ return 0
71
+ candidates.append(str(abs_path))
72
+
73
+ slug_path = task_dir(project_root, args.task_group, args.task_id)
74
+ if slug_path.is_dir():
75
+ print(f"OK\t{slug_path}")
76
+ return 0
77
+ candidates.append(str(slug_path))
78
+
79
+ print("MISSING")
80
+ print(f"REQUESTED\t{requested_key}")
81
+ for candidate in candidates:
82
+ print(f"TRIED\t{candidate}")
83
+ for key in sorted(set(catalog_keys)):
84
+ print(f"AVAILABLE\t{key}")
85
+ return 0
86
+
87
+
88
+ def _reconcile_outcome(args: argparse.Namespace) -> int:
89
+ """구현 결과 화해. 편의 기능이라 실패해도 run 을 끊지 않는다."""
90
+ try:
91
+ reconcile_implementation_outcome(Path(args.project_root), Path(args.task_root))
92
+ except Exception as exc: # noqa: BLE001 - 아래에서 신호를 남긴다
93
+ # expected-miss: 화해는 편의 기능이고 호출부(okstra.sh)는 `set -e` 아래
94
+ # 있다. 여기서 던지면 평범한 run 이 통째로 중단된다. 대신 stderr 로
95
+ # 남겨 삼킨 사실이 보이게 한다.
96
+ sys.stderr.write(f"reconcile-outcome: skipped ({exc})\n")
97
+ return 0
98
+
99
+
100
+ def _autofill(args: argparse.Namespace) -> int:
101
+ """`BRIEF=<v>` / `TYPE=<v>` 두 줄. 매니페스트를 못 읽으면 빈 값."""
102
+ try:
103
+ data = load_owned_object(Path(args.manifest), artifact="task manifest")
104
+ except JsonBoundaryError as exc:
105
+ sys.stderr.write(f"autofill_from_manifest: failed to parse {args.manifest}: {exc}\n")
106
+ return 0
107
+
108
+ brief = ""
109
+ task_type = ""
110
+ if args.need_brief:
111
+ brief = (data.get("taskBriefPath") or "").strip()
112
+ if args.need_type:
113
+ # 포인터 판정은 파이썬이 갖는다 — `ready` 가 아니면 빈 문자열이라
114
+ # 셸이 센티널 목록을 따로 들고 있을 이유가 없다.
115
+ task_type = next_phase.autofill_task_type(data)
116
+ print(f"BRIEF={brief}")
117
+ print(f"TYPE={task_type}")
118
+ return 0
119
+
120
+
121
+ def _latest_report(args: argparse.Namespace) -> int:
122
+ """`<report>\\t<task_type>` 한 줄. 없으면 아무것도 내지 않는다."""
123
+ types = (
124
+ (args.task_type,) if args.task_type
125
+ else ("error-analysis", "requirements-discovery")
126
+ )
127
+ ref = RunRef.latest_under(args.task_root, types)
128
+ if ref is not None:
129
+ sys.stdout.write(f"{ref.report}\t{ref.task_type}")
130
+ return 0
131
+
132
+
133
+ def _manifest_brief(args: argparse.Namespace) -> int:
134
+ """매니페스트의 `taskBriefPath` 한 줄."""
135
+ try:
136
+ data = load_owned_object(Path(args.manifest), artifact="task manifest")
137
+ except JsonBoundaryError as exc:
138
+ sys.stderr.write(f"manifest-brief: failed to parse {args.manifest}: {exc}\n")
139
+ return 1
140
+ print((data.get("taskBriefPath") or "").strip())
141
+ return 0
142
+
143
+
144
+ def _resume_args(args: argparse.Namespace) -> int:
145
+ """직전 run 의 인자들을 NUL 로 구분해 흘린다(셸의 `read -d ''` 용).
146
+
147
+ `default` 와 빈 값은 phase 기본값을 뜻하므로 넘기지 않는다 — 명시 플래그로
148
+ 고정하면 다음 run 이 기본값 변경을 못 받는다.
149
+ """
150
+ inputs = latest_run_inputs(
151
+ args.project_root, args.task_group, args.task_id,
152
+ phase_segment=slugify_task_segment(args.task_type),
153
+ )
154
+
155
+ def emit(flag: str, value: str) -> None:
156
+ if value:
157
+ sys.stdout.write(flag + "\0" + value + "\0")
158
+
159
+ def model(value: object) -> str:
160
+ return value if isinstance(value, str) and value not in ("", "default") else ""
161
+
162
+ workers = inputs.get("workers")
163
+ if isinstance(workers, list):
164
+ workers = ",".join(w for w in workers if isinstance(w, str))
165
+ elif not isinstance(workers, str):
166
+ workers = ""
167
+ emit("--workers", workers)
168
+ emit("--lead-model", model(inputs.get("leadModel")))
169
+ emit("--claude-model", model(inputs.get("claudeModel")))
170
+ emit("--codex-model", model(inputs.get("codexModel")))
171
+ emit("--antigravity-model", model(inputs.get("antigravityModel")))
172
+ emit("--report-writer-model", model(inputs.get("reportWriterModel")))
173
+ directive = inputs.get("directive")
174
+ emit("--directive", directive if isinstance(directive, str) else "")
175
+ related = inputs.get("relatedTasks")
176
+ emit("--related-tasks", related if isinstance(related, str) else "")
177
+ return 0
178
+
179
+
180
+ def main(argv: list[str] | None = None) -> int:
181
+ parser = argparse.ArgumentParser(prog="okstra interactive")
182
+ sub = parser.add_subparsers(dest="op", required=True)
183
+
184
+ resolve = sub.add_parser("resolve-task-root")
185
+ resolve.add_argument("--project-root", required=True)
186
+ resolve.add_argument("--project-id", required=True)
187
+ resolve.add_argument("--task-group", required=True)
188
+ resolve.add_argument("--task-id", required=True)
189
+ resolve.set_defaults(handler=_resolve_task_root)
190
+
191
+ reconcile = sub.add_parser("reconcile-outcome")
192
+ reconcile.add_argument("--project-root", required=True)
193
+ reconcile.add_argument("--task-root", required=True)
194
+ reconcile.set_defaults(handler=_reconcile_outcome)
195
+
196
+ autofill = sub.add_parser("autofill")
197
+ autofill.add_argument("--manifest", required=True)
198
+ autofill.add_argument("--need-brief", action="store_true")
199
+ autofill.add_argument("--need-type", action="store_true")
200
+ autofill.set_defaults(handler=_autofill)
201
+
202
+ latest = sub.add_parser("latest-report")
203
+ latest.add_argument("--task-root", required=True)
204
+ latest.add_argument("--task-type", default="")
205
+ latest.set_defaults(handler=_latest_report)
206
+
207
+ brief = sub.add_parser("manifest-brief")
208
+ brief.add_argument("--manifest", required=True)
209
+ brief.set_defaults(handler=_manifest_brief)
210
+
211
+ resume = sub.add_parser("resume-args")
212
+ resume.add_argument("--project-root", required=True)
213
+ resume.add_argument("--task-group", required=True)
214
+ resume.add_argument("--task-id", required=True)
215
+ resume.add_argument("--task-type", required=True)
216
+ resume.set_defaults(handler=_resume_args)
217
+
218
+ args = parser.parse_args(argv)
219
+ return args.handler(args)
220
+
221
+
222
+ if __name__ == "__main__":
223
+ raise SystemExit(main())
@@ -163,6 +163,16 @@ def external_claude_team_json_source(path: Path) -> ExternalJsonSource:
163
163
  return _issue_external_source(path, "Claude Code")
164
164
 
165
165
 
166
+ def external_codex_catalog_json_source(
167
+ path: Path, codex_home: Path
168
+ ) -> ExternalJsonSource:
169
+ """호출자가 지정한 codex home의 모델 카탈로그 캐시만 승인한다."""
170
+ expected = codex_home / "models_cache.json"
171
+ if Path(os.path.abspath(path)) != Path(os.path.abspath(expected)) or path.is_symlink():
172
+ raise JsonBoundaryError(path, "codex model catalog", "outside codex home")
173
+ return _issue_external_source(path, "codex CLI")
174
+
175
+
166
176
  def external_tool_json_source(path: Path, workspace_root: Path) -> ExternalJsonSource:
167
177
  """외부 도구가 workspace의 비-Okstra 경로에 쓴 보고서만 승인한다."""
168
178
  try: