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
@@ -0,0 +1,107 @@
1
+ """materialize 를 요청한 신원이 이 run 이 발급한 것인지 확인한다.
2
+
3
+ run manifest 는 어떤 role 이 어떤 assignment 로 실행되는지를 이미 적어 두었다.
4
+ CLI 인자가 그것과 어긋나면 감사 기록이 실행과 달라지므로 거절한다. 이 검사가
5
+ 없으면 매니페스트에 없는 role 로 프롬프트를 찍어 낼 수 있다.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from typing import Any, Mapping
10
+
11
+ from ...worker_prompt_policy import (
12
+ CRITIC_DUTY_BY_ASSIGNMENT_SEGMENT,
13
+ resolve_prompt_plan_for_manifest,
14
+ )
15
+ from .inputs import AgentPromptCliError
16
+
17
+
18
+ def _validate_run_identity(
19
+ manifest: Mapping[str, Any],
20
+ *,
21
+ worker_id: str,
22
+ dispatch_kind: str,
23
+ assignment_ref: str,
24
+ audience: str,
25
+ ) -> None:
26
+ expected: str
27
+ if assignment_ref == "lead":
28
+ expected = "lead"
29
+ if worker_id != "lead":
30
+ raise AgentPromptCliError("lead assignment requires worker ID 'lead'")
31
+ elif assignment_ref == "translator":
32
+ expected = "translator"
33
+ if worker_id != "translator":
34
+ raise AgentPromptCliError("translator assignment requires worker ID 'translator'")
35
+ elif assignment_ref.startswith("critic/"):
36
+ scope = assignment_ref.split("/", 1)[1]
37
+ expected = CRITIC_DUTY_BY_ASSIGNMENT_SEGMENT.get(scope, "")
38
+ if not expected or dispatch_kind != "critic":
39
+ raise AgentPromptCliError("critic assignment identity is invalid")
40
+ # provider 대조는 여기 두지 않는다. critic 의 `worker_id` 는 배정 참조의
41
+ # 마지막 마디(`scope` / `acceptance`)이고 provider 이름이 아니다
42
+ # (`render.py` 의 로스터 행이 `assignment_ref.rsplit("/", 1)[-1]` 로 만든다).
43
+ # 종전에 있던 `provider != worker_id` 검사는 두 어휘를 비교해서 늘 참이 되는
44
+ # 형태였고, 설정을 run manifest 에서 찾다 못 찾아 조용히 건너뛰었기에
45
+ # 드러나지 않았다. 정본에서 읽게 고치면 모든 critic 디스패치가 거부된다.
46
+ else:
47
+ try:
48
+ expected = resolve_prompt_plan_for_manifest(
49
+ manifest=manifest,
50
+ worker_id=worker_id,
51
+ dispatch_kind=dispatch_kind,
52
+ ).duty_audience
53
+ except ValueError as exc:
54
+ raise AgentPromptCliError(str(exc)) from exc
55
+ if audience != expected and _manifest_issued_role_execution(
56
+ manifest, audience=audience, assignment_ref=assignment_ref
57
+ ):
58
+ # The prompt plan names a worker's role by worker id, so on an
59
+ # implementation run the executor's provider resolves to the
60
+ # executor role no matter which role execution is being targeted.
61
+ # But the manifest issues one role execution per role, and a
62
+ # provider serving as both executor and verifier gets two — a
63
+ # normal roster, since the verifier contract accepts reusing the
64
+ # executor's model behind its own session. Refusing the second
65
+ # audience made a role execution okstra had itself issued
66
+ # undispatchable, which costs the run an independent verifier.
67
+ expected = audience
68
+ if audience != expected:
69
+ raise AgentPromptCliError(
70
+ f"audience {audience!r} does not match required audience {expected!r}"
71
+ )
72
+
73
+
74
+ def _manifest_issued_role_execution(
75
+ manifest: Mapping[str, Any],
76
+ *,
77
+ audience: str,
78
+ assignment_ref: str,
79
+ ) -> bool:
80
+ """Whether this run issued a role execution for that audience's role.
81
+
82
+ Keyed on the manifest's own rows, not on what the prompt plan infers from
83
+ a worker id: the question is whether okstra created the execution being
84
+ targeted, and only the manifest answers that.
85
+ """
86
+ from ...domain.role import RoleCatalogError, role_for_duty
87
+
88
+ try:
89
+ role = role_for_duty(audience)
90
+ except RoleCatalogError:
91
+ return False
92
+ assignments = manifest.get("invocationAssignments")
93
+ assignment = (
94
+ assignments.get(assignment_ref) if isinstance(assignments, Mapping) else None
95
+ )
96
+ provider = (
97
+ assignment.get("provider") if isinstance(assignment, Mapping) else None
98
+ )
99
+ if not provider:
100
+ return False
101
+ executions = manifest.get("roleExecutions")
102
+ return any(
103
+ isinstance(row, Mapping)
104
+ and row.get("role") == role
105
+ and row.get("provider") == provider
106
+ for row in (executions if isinstance(executions, list) else [])
107
+ )
@@ -67,7 +67,7 @@ WORKER_PROFILE_SECTIONS_BY_TASK_TYPE = {
67
67
  "Brief consumption",
68
68
  "Worker planning procedure",
69
69
  "Pre-planning context exploration",
70
- "Design principles applied when scoring options",
70
+ "Design principles applied to realization review",
71
71
  ),
72
72
  "final-verification": (
73
73
  "Worker verification procedure",
@@ -102,8 +102,17 @@ def build_analysis_packet(
102
102
  fix_history_text: str = "",
103
103
  stage_ledger_json: str = "",
104
104
  stage_ledger_notice: str = "",
105
+ prior_planning_summary: str = "",
105
106
  ) -> str:
106
- """Return the primary compact input for Claude/Codex/Antigravity analysers."""
107
+ """Return the primary compact input for Claude/Codex/Antigravity analysers.
108
+
109
+ `fix_history_text`, `stage_ledger_json` / `stage_ledger_notice`, and
110
+ `prior_planning_summary` are pre-rendered by their own modules
111
+ (`fix_cycles`, `stage_ledger`, `prior_planning`) because each is assembled
112
+ from disk state this file does not own. Each arrives already narrowed, and
113
+ an empty string means the caller found nothing to carry — the matching
114
+ block is then omitted rather than rendered saying "none".
115
+ """
107
116
  brief_text = task_brief_path.read_text(encoding="utf-8")
108
117
  profile_text = analysis_profile_path.read_text(encoding="utf-8")
109
118
  reference_text = _read_optional(reference_expectations_path)
@@ -126,6 +135,7 @@ def build_analysis_packet(
126
135
  parts.extend(_fix_history_block(fix_history_text))
127
136
  parts.extend(_stage_ledger_block(stage_ledger_json))
128
137
  parts.extend(_stage_ledger_unavailable_block(stage_ledger_notice))
138
+ parts.extend(_prior_planning_block(prior_planning_summary))
129
139
  parts.extend(_clarification_block(clarification_text))
130
140
  parts.extend(_directive_block(directive))
131
141
  body = "\n".join(part.rstrip() for part in parts).rstrip() + "\n"
@@ -334,8 +344,11 @@ def _stage_ledger_block(stage_ledger_json: str) -> list[str]:
334
344
  "authority is the latter.",
335
345
  "",
336
346
  "A `planDivergence` entry means the two plans disagree about a",
337
- "completed stage. Treat it as a blocker for any new stage number and",
338
- "report it; do not resolve it by choosing one of the two yourself.",
347
+ "completed stage, or that the plan the completed stages were built",
348
+ "against could not be read so that comparison never ran. Treat either",
349
+ "as a blocker for any new stage number and report it; do not resolve",
350
+ "it by choosing one of the two yourself. Completed stages built",
351
+ "against an earlier plan are not a divergence.",
339
352
  "",
340
353
  "```json",
341
354
  stage_ledger_json.strip(),
@@ -371,6 +384,36 @@ def _stage_ledger_unavailable_block(notice: str) -> list[str]:
371
384
  ]
372
385
 
373
386
 
387
+ def _prior_planning_block(prior_planning_summary: str) -> list[str]:
388
+ """직전 계획 run 의 맥락. 직전 run 이 없으면 블록 자체가 없다.
389
+
390
+ 머리글이 이 블록의 용도를 스스로 말한다. 그러지 않으면 워커는 표에 실린
391
+ 계획을 승인된 것으로 읽고 그대로 옮겨 적는다 — 이 블록이 실리는 run 은
392
+ 직전 계획이 **승인에 도달하지 못해** 다시 도는 run 이므로 그 독해는 틀렸다.
393
+ """
394
+ if not prior_planning_summary.strip():
395
+ return []
396
+ return [
397
+ "",
398
+ "## Prior Planning Run",
399
+ "",
400
+ "What this task's previous implementation-planning run recorded. It is",
401
+ "here so that stages the previous plan already settled stay textually",
402
+ "stable across the re-run, and so settled analysis is not derived from",
403
+ "the brief a second time under new `P-*` numbers.",
404
+ "",
405
+ "It is NOT an approved plan and NOT an instruction to copy. The",
406
+ "previous run did not reach approval — that is why this run exists —",
407
+ "so every line below is still open to revision. Where the brief, the",
408
+ "directive, or a clarification answer disagrees with it, they win, and",
409
+ "a stage you are changing is rewritten rather than carried. Read the",
410
+ "report at the path below for anything this summary leaves out.",
411
+ "",
412
+ prior_planning_summary.strip(),
413
+ "",
414
+ ]
415
+
416
+
374
417
  def _clarification_block(clarification_text: str) -> list[str]:
375
418
  if not clarification_text.strip():
376
419
  return []
@@ -6,12 +6,36 @@ import json
6
6
  import re
7
7
  import sys
8
8
  from copy import deepcopy
9
+ from functools import lru_cache
9
10
  from dataclasses import dataclass
10
11
  from pathlib import Path
11
12
  from typing import Any, Mapping, Sequence
12
13
 
14
+ from .clarification_items.sidecars import (
15
+ attached_response_sections,
16
+ response_source_report,
17
+ )
18
+ from .user_response_values import parse_user_response_entries
13
19
  from .convergence_store import write_json_atomic
20
+ from .final_report_schema import load_schema_version
14
21
  from .json_boundary import JsonBoundaryError, load_owned_object
22
+ from .report_contract import CURRENT_REPORT_SCHEMA_VERSION
23
+
24
+
25
+ @lru_cache(maxsize=None)
26
+ def _schema_enum(definition: str) -> frozenset[str]:
27
+ """리포트 스키마의 ``$defs`` enum 을 그대로 읽는다.
28
+
29
+ 이 원장의 행은 그대로 final-report 의 ``clarificationItems`` 가 된다. 값 목록을
30
+ 여기에 다시 타이핑하면 스키마와 따로 흐르므로 정본에서 읽는다.
31
+ """
32
+ schema = load_schema_version(CURRENT_REPORT_SCHEMA_VERSION)
33
+ values = (schema.get("$defs") or {}).get(definition, {}).get("enum")
34
+ if not isinstance(values, list) or not values:
35
+ raise ApprovalDecisionError(
36
+ f"report schema has no enum for {definition!r}"
37
+ )
38
+ return frozenset(str(value) for value in values)
15
39
 
16
40
 
17
41
  DISPOSITIONS = frozenset({"select", "accept-risk", "request-revision", "reject"})
@@ -127,6 +151,23 @@ def _validate_option_set(
127
151
  )
128
152
 
129
153
 
154
+ def _reject_off_enum(value: str, field: str, definition: str) -> None:
155
+ """스키마 밖 값을 원장 진입 지점에서 거절한다.
156
+
157
+ 종전에는 두 provenance 필드가 비어 있지 않기만 하면 통과했다. 리포트 조립이
158
+ 스키마로 잡아 내기는 하지만 그때는 이미 레코드를 디스크에 쓴 뒤라, 잘못된 값이
159
+ 원장과 리포트 양쪽에 남아 이후의 모든 읽기(`okstra user-response`)를 막았다.
160
+ """
161
+ if not value:
162
+ return
163
+ allowed = _schema_enum(definition)
164
+ if value not in allowed:
165
+ raise ApprovalDecisionError(
166
+ f"invalid {field}: {value!r}. Allowed values: "
167
+ + ", ".join(sorted(allowed))
168
+ )
169
+
170
+
130
171
  def _decision_row(
131
172
  *, clarification_id: str, ticket_id: str, statement: str,
132
173
  expected_form: str, classification: str, origin: str,
@@ -135,6 +176,10 @@ def _decision_row(
135
176
  ) -> dict[str, Any]:
136
177
  if classification not in CLASSIFICATIONS:
137
178
  raise ApprovalDecisionError(f"invalid classification: {classification}")
179
+ _reject_off_enum(origin, "origin", "ClarificationOrigin")
180
+ _reject_off_enum(
181
+ user_confirmation, "user_confirmation", "ClarificationUserConfirmation"
182
+ )
138
183
  if recommended_disposition not in DISPOSITIONS:
139
184
  raise ApprovalDecisionError(
140
185
  f"invalid recommended_disposition: {recommended_disposition}"
@@ -260,8 +305,21 @@ def _add_open_arguments(parser: argparse.ArgumentParser) -> None:
260
305
  parser.add_argument(f"--option-{flag}", action="append", default=[])
261
306
 
262
307
 
308
+ _CLI_EPILOG = r"""Usage:
309
+ okstra approval-decision open --ledger <path> [decision fields]
310
+ okstra approval-decision resolve --ledger <path> --clarification-id <C-NNN> [resolution fields]
311
+ okstra approval-decision carry --ledger <path> --from-responses <clarification-response.md> --clarification-id <C-NNN> [--clarification-id <C-NNN> ...]
312
+ okstra approval-decision carry --ledger <path> --source-ledger <path> --source-run-ref <ref> --clarification-id <C-NNN>
313
+ """
314
+ _CLI_DESCRIPTION = "Record lead-owned approval inputs."
315
+
316
+
263
317
  def _parser() -> argparse.ArgumentParser:
264
- parser = argparse.ArgumentParser(prog="okstra approval-decision")
318
+ parser = argparse.ArgumentParser(
319
+ description=_CLI_DESCRIPTION,
320
+ epilog=_CLI_EPILOG,
321
+ formatter_class=argparse.RawDescriptionHelpFormatter,
322
+ prog="okstra approval-decision")
265
323
  commands = parser.add_subparsers(dest="command", required=True)
266
324
  opened = commands.add_parser("open")
267
325
  opened.add_argument("--ledger", type=Path, required=True)
@@ -275,9 +333,38 @@ def _parser() -> argparse.ArgumentParser:
275
333
  resolved.add_argument("--check-ref", action="append", default=[])
276
334
  carried = commands.add_parser("carry")
277
335
  carried.add_argument("--ledger", type=Path, required=True)
278
- carried.add_argument("--source-ledger", type=Path, required=True)
279
- carried.add_argument("--source-run-ref", required=True)
280
- carried.add_argument("--clarification-id", required=True)
336
+ source = carried.add_mutually_exclusive_group(required=True)
337
+ source.add_argument(
338
+ "--from-responses",
339
+ type=Path,
340
+ help=(
341
+ "attached user-responses bundle (`instruction-set/"
342
+ "clarification-response.md`). It is the source of truth for the "
343
+ "answer; the question's own fields come from the report each "
344
+ "response section names. Task-level and cumulative, so no prior "
345
+ "run seq has to be located."
346
+ ),
347
+ )
348
+ source.add_argument(
349
+ "--source-ledger",
350
+ type=Path,
351
+ help="a prior run's approval ledger, when it is still on disk",
352
+ )
353
+ carried.add_argument(
354
+ "--source-run-ref",
355
+ default="",
356
+ help=(
357
+ "provenance recorded on the carried row. Required with "
358
+ "--source-ledger; with --from-responses it defaults to the "
359
+ "response section the answer came from."
360
+ ),
361
+ )
362
+ carried.add_argument(
363
+ "--clarification-id",
364
+ action="append",
365
+ required=True,
366
+ help="repeat to carry several ids in one call",
367
+ )
281
368
  return parser
282
369
 
283
370
 
@@ -291,13 +378,174 @@ def _open_from_args(args: argparse.Namespace) -> None:
291
378
  open_decision(**values, options=_options_from_args(args))
292
379
 
293
380
 
381
+ # `kind: decision` 인 행은 스키마가 옵션 메뉴를 요구하므로 함께 가져온다.
382
+ # `resolution` 은 가져오지 않는다 — 그 `checkRefs` 는 답을 기록한 run 의 활동
383
+ # 행이고, 이월 행은 `answered` 로 실린다.
384
+ _QUESTION_FIELDS = (
385
+ "id", "ticketId", "kind", "statement", "expectedForm", "blocks", "origin",
386
+ "options",
387
+ )
388
+
389
+
390
+ def _responses_index(path: Path) -> dict[str, tuple[Any, str, str, str]]:
391
+ """묶음 파일의 답을 ``{id: (entry, 구간 이름, 리포트 기록 이름, task-type)}`` 로.
392
+
393
+ 같은 id 를 여러 run 이 답했으면 뒤 구간이 이긴다 — 묶음은 시간순으로 쌓이고
394
+ 최신 답이 정본이다.
395
+ """
396
+ try:
397
+ text = path.read_text(encoding="utf-8")
398
+ except OSError as exc:
399
+ raise ApprovalDecisionError(f"cannot read {path}: {exc}") from exc
400
+ sections = attached_response_sections(text)
401
+ if not sections:
402
+ raise ApprovalDecisionError(
403
+ f"not an attached user-responses bundle (no `## user-response-*.md` "
404
+ f"section): {path}"
405
+ )
406
+ index: dict[str, tuple[Any, str, str, str]] = {}
407
+ for name, body in sections:
408
+ report = response_source_report(body)
409
+ task_type = _frontmatter_task_type(body)
410
+ for entry in parse_user_response_entries(body):
411
+ index[entry.response_id] = (entry, name, report, task_type)
412
+ return index
413
+
414
+
415
+ def _frontmatter_task_type(section_body: str) -> str:
416
+ match = re.search(r"^task-type:\s*(\S.*?)\s*$", section_body, re.MULTILINE)
417
+ return match.group(1) if match else ""
418
+
419
+
420
+ def _prior_question_row(
421
+ responses_path: Path, clarification_id: str, report_name: str, task_type: str,
422
+ ) -> dict[str, Any]:
423
+ """질문 메타데이터의 출처는 그 질문을 낸 리포트다.
424
+
425
+ 묶음 파일은 답만 담는다. `ticketId`·`statement`·`expectedForm` 은 스키마가
426
+ 요구하는데 묶음에 없으므로, 지어내는 대신 구간 frontmatter 가 지목한
427
+ 리포트에서 읽는다. 그 리드가 seq 를 찾아 헤매지 않게 하는 것이 이 경로의
428
+ 요점이다.
429
+ """
430
+ if not report_name or not task_type:
431
+ raise ApprovalDecisionError(
432
+ f"{clarification_id}: its response section names no source report; "
433
+ "carry it with --source-ledger instead"
434
+ )
435
+ task_root = responses_path.resolve().parent.parent
436
+ report_path = (
437
+ task_root / "runs" / task_type / "reports" / Path(report_name).name
438
+ )
439
+ if not report_path.is_file():
440
+ raise ApprovalDecisionError(
441
+ f"{clarification_id}: source report not found: {report_path}"
442
+ )
443
+ try:
444
+ data = load_owned_object(report_path, artifact="prior final report")
445
+ except (JsonBoundaryError, OSError) as exc:
446
+ raise ApprovalDecisionError(f"cannot read {report_path}: {exc}") from exc
447
+ rows = data.get("clarificationItems") if isinstance(data, Mapping) else None
448
+ match = next(
449
+ (
450
+ row for row in (rows or [])
451
+ if isinstance(row, Mapping) and row.get("id") == clarification_id
452
+ ),
453
+ None,
454
+ )
455
+ if match is None:
456
+ raise ApprovalDecisionError(
457
+ f"{clarification_id}: not defined in {report_path.name}"
458
+ )
459
+ row = {key: match[key] for key in _QUESTION_FIELDS if key in match}
460
+ if isinstance(row.get("options"), list):
461
+ row["options"] = [
462
+ _contract_v3_option(option, clarification_id)
463
+ for option in row["options"]
464
+ if isinstance(option, Mapping)
465
+ ]
466
+ if "approvalContext" in match:
467
+ row["approval"] = match["approvalContext"]
468
+ return row
469
+
470
+
471
+ _REACH_TOKENS = ("in-repo", "cross-repo")
472
+
473
+
474
+ def _contract_v3_option(option: Mapping[str, Any], clarification_id: str) -> dict[str, Any]:
475
+ """계약 2.0 의 `scopeImpact` 를 3.0 의 `reach` + `scopeEffects` 로 되돌린다.
476
+
477
+ 두 열거는 정확히 분할 관계다 — 2.0 의 토큰 집합
478
+ `{in-repo, cross-repo, new-schema, deferrable}` 이 3.0 에서 `reach`
479
+ (`in-repo|cross-repo`, 정확히 하나)와 `scopeEffects`(나머지)로 갈렸다.
480
+ 그래서 이 변환은 값을 지어내지 않는다. 이미 3.0 모양이면 그대로 둔다.
481
+ """
482
+ row = dict(option)
483
+ if "reach" in row:
484
+ row.pop("scopeImpact", None)
485
+ return row
486
+ tokens = row.pop("scopeImpact", None)
487
+ if not isinstance(tokens, list):
488
+ raise ApprovalDecisionError(
489
+ f"{clarification_id}: an option states neither `reach` nor "
490
+ "`scopeImpact`; carry it with --source-ledger instead"
491
+ )
492
+ reach = [token for token in tokens if token in _REACH_TOKENS]
493
+ if len(reach) != 1:
494
+ raise ApprovalDecisionError(
495
+ f"{clarification_id}: an option's `scopeImpact` names {len(reach)} "
496
+ "reach token(s); exactly one is needed to state `reach`"
497
+ )
498
+ row["reach"] = reach[0]
499
+ effects = [token for token in tokens if token not in _REACH_TOKENS]
500
+ if effects:
501
+ row["scopeEffects"] = effects
502
+ return row
503
+
504
+
505
+ def _carry_from_responses(args: argparse.Namespace) -> None:
506
+ index = _responses_index(args.from_responses)
507
+ for clarification_id in args.clarification_id:
508
+ found = index.get(clarification_id)
509
+ if found is None:
510
+ raise ApprovalDecisionError(
511
+ f"clarification not answered in {args.from_responses}: "
512
+ f"{clarification_id}"
513
+ )
514
+ entry, section, report_name, task_type = found
515
+ if not entry.value.strip():
516
+ raise ApprovalDecisionError(
517
+ f"{clarification_id}: its answer in {section} has no Value"
518
+ )
519
+ decision = _prior_question_row(
520
+ args.from_responses, clarification_id, report_name, task_type
521
+ )
522
+ # 답이 정본인 자리는 여기다. `resolutionInput` 은 쓰지 않는다 —
523
+ # 그 모양은 `checkRefs` 로 이번 run 의 활동 행을 요구하는데, 이월된
524
+ # 답에는 이번 run 의 활동이 없다(`report_assembly` 의 이월 분기).
525
+ decision["userConfirmation"] = "asked-and-answered"
526
+ decision["userInput"] = entry.value
527
+ carry_decision(
528
+ args.ledger,
529
+ source_run_ref=args.source_run_ref or section,
530
+ decision=decision,
531
+ )
532
+
533
+
294
534
  def _carry_from_args(args: argparse.Namespace) -> None:
295
535
  source = _read_ledger(args.source_ledger)
296
536
  rows = source.get("activeClarifications") or []
297
- matches = [row for row in rows if isinstance(row, dict) and row.get("id") == args.clarification_id]
298
- if len(matches) != 1:
299
- raise ApprovalDecisionError(f"source clarification not found: {args.clarification_id}")
300
- carry_decision(args.ledger, source_run_ref=args.source_run_ref, decision=matches[0])
537
+ for clarification_id in args.clarification_id:
538
+ matches = [
539
+ row for row in rows
540
+ if isinstance(row, dict) and row.get("id") == clarification_id
541
+ ]
542
+ if len(matches) != 1:
543
+ raise ApprovalDecisionError(
544
+ f"source clarification not found: {clarification_id}"
545
+ )
546
+ carry_decision(
547
+ args.ledger, source_run_ref=args.source_run_ref, decision=matches[0]
548
+ )
301
549
 
302
550
 
303
551
  def _run(args: argparse.Namespace) -> None:
@@ -309,7 +557,11 @@ def _run(args: argparse.Namespace) -> None:
309
557
  user_text=args.user_text, user_response_ref=args.user_response_ref,
310
558
  check_refs=args.check_ref,
311
559
  )
560
+ elif args.from_responses:
561
+ _carry_from_responses(args)
312
562
  else:
563
+ if not args.source_run_ref:
564
+ raise ApprovalDecisionError("--source-ledger requires --source-run-ref")
313
565
  _carry_from_args(args)
314
566
 
315
567
 
@@ -91,18 +91,6 @@ class AssignmentContext:
91
91
  environment: AssignmentEnvironment
92
92
 
93
93
 
94
- @dataclass(frozen=True)
95
- class RoleCountSelection:
96
- role: str
97
- count: int
98
-
99
-
100
- @dataclass(frozen=True)
101
- class RoleModelSelection:
102
- role: str
103
- model_refs: tuple[str, ...]
104
-
105
-
106
94
  @dataclass(frozen=True)
107
95
  class RoleInstance:
108
96
  role: str
@@ -3,13 +3,17 @@ from __future__ import annotations
3
3
 
4
4
  import hashlib
5
5
  import json
6
- from dataclasses import dataclass
6
+ from dataclasses import dataclass, replace
7
7
  from datetime import datetime, timezone
8
8
  from pathlib import Path
9
9
  from typing import Any, Mapping, Sequence
10
10
 
11
11
  from .domain.worker_stream import host_event_row
12
- from .execution_identity import Attempt, ExecutionManifestError
12
+ from .execution_identity import (
13
+ Attempt,
14
+ ExecutionManifestError,
15
+ MutationRecoveryDecision,
16
+ )
13
17
  from .execution_manifest import read_execution_manifest
14
18
  from .execution_manifest import _replace_manifest, _write_manifest_over_existing
15
19
  from .jsonl import append_jsonl
@@ -221,15 +225,10 @@ def finalize_attempt(
221
225
  raise AttemptEvidenceError("attempt to seal is unknown")
222
226
  existing = manifest.attempts[index]
223
227
  if terminal.interrupted_before_record:
224
- closed = Attempt(
225
- invocation_ref=existing.invocation_ref,
226
- attempt=existing.attempt,
227
- started_at=existing.started_at,
228
+ closed = replace(
229
+ existing,
228
230
  finished_at=_utc_now(),
229
231
  status="evidence-unsealed",
230
- result_path=existing.result_path,
231
- error_path=existing.error_path,
232
- change_summary=dict(existing.change_summary),
233
232
  evidence_seal_ref=None,
234
233
  )
235
234
  attempts = list(manifest.attempts)
@@ -252,15 +251,10 @@ def finalize_attempt(
252
251
  if not _same_observation(existing_seal, terminal):
253
252
  raise AttemptEvidenceError("attempt already sealed with different observation")
254
253
  return existing_seal
255
- closed = Attempt(
256
- invocation_ref=existing.invocation_ref,
257
- attempt=existing.attempt,
258
- started_at=existing.started_at,
254
+ closed = replace(
255
+ existing,
259
256
  finished_at=existing.finished_at or _utc_now(),
260
257
  status=terminal.status,
261
- result_path=existing.result_path,
262
- error_path=existing.error_path,
263
- change_summary=dict(existing.change_summary),
264
258
  evidence_seal_ref=seal.seal_ref,
265
259
  )
266
260
  attempts = list(manifest.attempts)
@@ -285,7 +279,6 @@ def reject_unsealed_attempt(
285
279
  task_key: str,
286
280
  ):
287
281
  """Close an unsealed attempt and record a cleanup-only reject."""
288
- from .mutation_recovery import MutationRecoveryDecision
289
282
 
290
283
  invocation_ref, attempt_number = _split_attempt_ref(attempt_ref)
291
284
  with task_mutex(task_key):
@@ -301,15 +294,10 @@ def reject_unsealed_attempt(
301
294
  if index is None:
302
295
  raise AttemptEvidenceError("attempt to reject is unknown")
303
296
  existing = manifest.attempts[index]
304
- closed = Attempt(
305
- invocation_ref=existing.invocation_ref,
306
- attempt=existing.attempt,
307
- started_at=existing.started_at,
297
+ closed = replace(
298
+ existing,
308
299
  finished_at=existing.finished_at or _utc_now(),
309
300
  status="evidence-unsealed",
310
- result_path=existing.result_path,
311
- error_path=existing.error_path,
312
- change_summary=dict(existing.change_summary),
313
301
  evidence_seal_ref=None,
314
302
  )
315
303
  decision = MutationRecoveryDecision(