okstra 0.186.7 → 0.188.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 (546) 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 +107 -40
  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 +39 -38
  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 +47 -22
  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/adapters/cmux.md +3 -3
  100. package/runtime/prompts/lead/convergence.md +99 -88
  101. package/runtime/prompts/lead/okstra-lead-contract.md +63 -12
  102. package/runtime/prompts/lead/plan-body-verification.md +65 -46
  103. package/runtime/prompts/lead/report-writer.md +35 -5
  104. package/runtime/prompts/lead/team-contract.md +4 -1
  105. package/runtime/prompts/profiles/_clarification-recommendation.md +2 -1
  106. package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
  107. package/runtime/prompts/profiles/_common-contract.md +3 -3
  108. package/runtime/prompts/profiles/_coverage-critic.md +1 -1
  109. package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
  110. package/runtime/prompts/profiles/_implementation-executor.md +3 -1
  111. package/runtime/prompts/profiles/_implementation-verifier.md +16 -2
  112. package/runtime/prompts/profiles/error-analysis.md +4 -3
  113. package/runtime/prompts/profiles/final-verification.md +2 -2
  114. package/runtime/prompts/profiles/implementation-planning.md +22 -17
  115. package/runtime/prompts/profiles/implementation.md +3 -2
  116. package/runtime/prompts/profiles/requirements-discovery.md +2 -1
  117. package/runtime/prompts/wizard/prompts.ko.json +29 -8
  118. package/runtime/python/okstra_ctl/__init__.py +6 -27
  119. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +4 -5
  120. package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +21 -2
  121. package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +3 -12
  122. package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +63 -22
  123. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +20 -21
  124. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +37 -20
  125. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +86 -14
  126. package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +12 -12
  127. package/runtime/python/okstra_ctl/adapters/runtime/cmux.py +4 -7
  128. package/runtime/python/okstra_ctl/agent/__init__.py +1 -0
  129. package/runtime/python/okstra_ctl/{agent_activity.py → agent/activity.py} +16 -2
  130. package/runtime/python/okstra_ctl/{agent_invocation.py → agent/invocation.py} +34 -5
  131. package/runtime/python/okstra_ctl/agent/prompt_cli/__init__.py +19 -0
  132. package/runtime/python/okstra_ctl/agent/prompt_cli/__main__.py +11 -0
  133. package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +228 -0
  134. package/runtime/python/okstra_ctl/agent/prompt_cli/dynamic_verifier.py +155 -0
  135. package/runtime/python/okstra_ctl/agent/prompt_cli/emit.py +66 -0
  136. package/runtime/python/okstra_ctl/agent/prompt_cli/inputs.py +138 -0
  137. package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +605 -0
  138. package/runtime/python/okstra_ctl/agent/prompt_cli/results.py +197 -0
  139. package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +107 -0
  140. package/runtime/python/okstra_ctl/analysis_packet.py +47 -4
  141. package/runtime/python/okstra_ctl/approval_decisions.py +287 -10
  142. package/runtime/python/okstra_ctl/assignment_resolver.py +0 -12
  143. package/runtime/python/okstra_ctl/attempt_evidence.py +12 -24
  144. package/runtime/python/okstra_ctl/blocking_checks.py +251 -0
  145. package/runtime/python/okstra_ctl/brief_frontmatter.py +0 -7
  146. package/runtime/python/okstra_ctl/clarification_items/__init__.py +102 -0
  147. package/runtime/python/okstra_ctl/clarification_items/carry.py +224 -0
  148. package/runtime/python/okstra_ctl/clarification_items/dispositions.py +135 -0
  149. package/runtime/python/okstra_ctl/clarification_items/parsing.py +253 -0
  150. package/runtime/python/okstra_ctl/clarification_items/rows.py +115 -0
  151. package/runtime/python/okstra_ctl/clarification_items/scan.py +215 -0
  152. package/runtime/python/okstra_ctl/clarification_items/sidecars.py +211 -0
  153. package/runtime/python/okstra_ctl/cmux.py +38 -31
  154. package/runtime/python/okstra_ctl/code_review_target.py +163 -1
  155. package/runtime/python/okstra_ctl/conformance.py +23 -0
  156. package/runtime/python/okstra_ctl/container.py +38 -421
  157. package/runtime/python/okstra_ctl/context_cost.py +32 -4
  158. package/runtime/python/okstra_ctl/contract_graph_cli.py +7 -0
  159. package/runtime/python/okstra_ctl/convergence.py +107 -16
  160. package/runtime/python/okstra_ctl/convergence_critic_prompt.py +345 -0
  161. package/runtime/python/okstra_ctl/convergence_engine.py +382 -70
  162. package/runtime/python/okstra_ctl/convergence_store.py +5 -29
  163. package/runtime/python/okstra_ctl/design_prep.py +108 -12
  164. package/runtime/python/okstra_ctl/design_snapshot.py +11 -1
  165. package/runtime/python/okstra_ctl/design_surfaces.py +29 -2
  166. package/runtime/python/okstra_ctl/dispatch_core.py +236 -49
  167. package/runtime/python/okstra_ctl/dispatch_state.py +417 -41
  168. package/runtime/python/okstra_ctl/doctor_cli.py +48 -0
  169. package/runtime/python/okstra_ctl/domain/worker_exec.py +10 -5
  170. package/runtime/python/okstra_ctl/domain/worker_presentation.py +28 -1
  171. package/runtime/python/okstra_ctl/domain/worker_stream.py +52 -21
  172. package/runtime/python/okstra_ctl/domain/write_policy.py +219 -0
  173. package/runtime/python/okstra_ctl/entrypoints/hosts.py +9 -2
  174. package/runtime/python/okstra_ctl/error_log_core.py +1 -1
  175. package/runtime/python/okstra_ctl/error_report.py +14 -0
  176. package/runtime/python/okstra_ctl/error_zip.py +18 -2
  177. package/runtime/python/okstra_ctl/exact_coverage.py +5 -0
  178. package/runtime/python/okstra_ctl/execution_identity.py +100 -7
  179. package/runtime/python/okstra_ctl/execution_manifest.py +67 -20
  180. package/runtime/python/okstra_ctl/execution_mutation_audit.py +90 -8
  181. package/runtime/python/okstra_ctl/final_report_paths.py +17 -0
  182. package/runtime/python/okstra_ctl/final_report_schema.py +319 -17
  183. package/runtime/python/okstra_ctl/git_reconcile.py +22 -2
  184. package/runtime/python/okstra_ctl/handoff.py +24 -1
  185. package/runtime/python/okstra_ctl/ids.py +6 -16
  186. package/runtime/python/okstra_ctl/implementation_direction.py +142 -33
  187. package/runtime/python/okstra_ctl/implementation_options.py +32 -13
  188. package/runtime/python/okstra_ctl/implementation_outcome.py +5 -2
  189. package/runtime/python/okstra_ctl/improvement_lenses.py +0 -14
  190. package/runtime/python/okstra_ctl/incremental_carry.py +69 -8
  191. package/runtime/python/okstra_ctl/incremental_scope.py +173 -12
  192. package/runtime/python/okstra_ctl/index.py +2 -2
  193. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +48 -8
  194. package/runtime/python/okstra_ctl/interactive_cli.py +223 -0
  195. package/runtime/python/okstra_ctl/json_boundary.py +10 -0
  196. package/runtime/python/okstra_ctl/listing.py +0 -91
  197. package/runtime/python/okstra_ctl/locks.py +5 -19
  198. package/runtime/python/okstra_ctl/log_report.py +14 -0
  199. package/runtime/python/okstra_ctl/manager_cli.py +22 -3
  200. package/runtime/python/okstra_ctl/manager_launch.py +4 -8
  201. package/runtime/python/okstra_ctl/material.py +2 -2
  202. package/runtime/python/okstra_ctl/migrate.py +23 -1
  203. package/runtime/python/okstra_ctl/model_cli.py +19 -5
  204. package/runtime/python/okstra_ctl/model_discovery.py +46 -47
  205. package/runtime/python/okstra_ctl/model_io/__init__.py +1 -0
  206. package/runtime/python/okstra_ctl/model_io/lines.py +163 -0
  207. package/runtime/python/okstra_ctl/model_io/references.py +370 -0
  208. package/runtime/python/okstra_ctl/model_io/renderers.py +498 -0
  209. package/runtime/python/okstra_ctl/model_io_cli.py +30 -945
  210. package/runtime/python/okstra_ctl/model_pool.py +10 -6
  211. package/runtime/python/okstra_ctl/models.py +8 -8
  212. package/runtime/python/okstra_ctl/mutation_recovery.py +5 -63
  213. package/runtime/python/okstra_ctl/next_phase.py +121 -69
  214. package/runtime/python/okstra_ctl/pane_reclaim.py +23 -0
  215. package/runtime/python/okstra_ctl/pane_title.py +11 -8
  216. package/runtime/python/okstra_ctl/path_hints.py +36 -11
  217. package/runtime/python/okstra_ctl/paths.py +76 -8
  218. package/runtime/python/okstra_ctl/plan_items.py +132 -16
  219. package/runtime/python/okstra_ctl/plan_items_cli.py +486 -36
  220. package/runtime/python/okstra_ctl/plan_run_root.py +1 -1
  221. package/runtime/python/okstra_ctl/plan_validate_cli.py +51 -0
  222. package/runtime/python/okstra_ctl/plan_verify_cli.py +80 -0
  223. package/runtime/python/okstra_ctl/prepare_error.py +20 -0
  224. package/runtime/python/okstra_ctl/prior_planning.py +157 -0
  225. package/runtime/python/okstra_ctl/profile_show.py +18 -1
  226. package/runtime/python/okstra_ctl/project_setup_cli.py +140 -0
  227. package/runtime/python/okstra_ctl/recap.py +22 -1
  228. package/runtime/python/okstra_ctl/reconcile.py +84 -0
  229. package/runtime/python/okstra_ctl/registry/host_registry.py +18 -1
  230. package/runtime/python/okstra_ctl/render.py +98 -32
  231. package/runtime/python/okstra_ctl/render_final_report.py +4 -8
  232. package/runtime/python/okstra_ctl/report_assembly.py +297 -7
  233. package/runtime/python/okstra_ctl/report_contract.py +30 -7
  234. package/runtime/python/okstra_ctl/report_finalize.py +213 -16
  235. package/runtime/python/okstra_ctl/report_html/common.py +2 -2
  236. package/runtime/python/okstra_ctl/report_html/render.py +8 -12
  237. package/runtime/python/okstra_ctl/report_language.py +8 -4
  238. package/runtime/python/okstra_ctl/report_narrative.py +220 -34
  239. package/runtime/python/okstra_ctl/report_projections.py +8 -2
  240. package/runtime/python/okstra_ctl/report_synthesis_packet.py +393 -1
  241. package/runtime/python/okstra_ctl/report_translation.py +2 -35
  242. package/runtime/python/okstra_ctl/report_view_artifacts.py +42 -2
  243. package/runtime/python/okstra_ctl/report_views.py +4 -22
  244. package/runtime/python/okstra_ctl/resolve_task_key.py +13 -0
  245. package/runtime/python/okstra_ctl/rollup.py +13 -0
  246. package/runtime/python/okstra_ctl/run.py +639 -138
  247. package/runtime/python/okstra_ctl/run_audit.py +13 -0
  248. package/runtime/python/okstra_ctl/run_index_row.py +0 -12
  249. package/runtime/python/okstra_ctl/seeding.py +21 -11
  250. package/runtime/python/okstra_ctl/sequence.py +1 -1
  251. package/runtime/python/okstra_ctl/session.py +64 -14
  252. package/runtime/python/okstra_ctl/set_work_status.py +18 -0
  253. package/runtime/python/okstra_ctl/stage_integrate.py +15 -1
  254. package/runtime/python/okstra_ctl/stage_ledger.py +1 -1
  255. package/runtime/python/okstra_ctl/stage_map.py +57 -11
  256. package/runtime/python/okstra_ctl/stage_map_cli.py +83 -0
  257. package/runtime/python/okstra_ctl/stage_map_view.py +65 -0
  258. package/runtime/python/okstra_ctl/stage_targets.py +3 -12
  259. package/runtime/python/okstra_ctl/task_list_cli.py +138 -0
  260. package/runtime/python/okstra_ctl/task_show_cli.py +66 -0
  261. package/runtime/python/okstra_ctl/task_target.py +4 -16
  262. package/runtime/python/okstra_ctl/team.py +49 -2
  263. package/runtime/python/okstra_ctl/time_report.py +17 -2
  264. package/runtime/python/okstra_ctl/usage_report.py +11 -0
  265. package/runtime/python/okstra_ctl/user_response.py +55 -218
  266. package/runtime/python/okstra_ctl/user_response_values.py +242 -0
  267. package/runtime/python/okstra_ctl/validation_contract.py +3 -0
  268. package/runtime/python/okstra_ctl/verdict_blocks.py +28 -8
  269. package/runtime/python/okstra_ctl/verification_target.py +68 -0
  270. package/runtime/python/okstra_ctl/wizard.py +457 -92
  271. package/runtime/python/okstra_ctl/worker_artifacts.py +75 -16
  272. package/runtime/python/okstra_ctl/worker_audit_check.py +22 -0
  273. package/runtime/python/okstra_ctl/worker_dispatch.py +21 -0
  274. package/runtime/python/okstra_ctl/worker_liveness.py +33 -0
  275. package/runtime/python/okstra_ctl/worker_prompt_body.py +15 -2
  276. package/runtime/python/okstra_ctl/worker_prompt_contract.py +98 -7
  277. package/runtime/python/okstra_ctl/worker_prompt_headers.py +24 -2
  278. package/runtime/python/okstra_ctl/worker_prompt_policy.py +18 -3
  279. package/runtime/python/okstra_ctl/worker_request.py +2 -1
  280. package/runtime/python/okstra_ctl/worker_runner.py +58 -17
  281. package/runtime/python/okstra_ctl/worker_state.py +16 -1
  282. package/runtime/python/okstra_ctl/workflow.py +18 -1
  283. package/runtime/python/okstra_ctl/worktree/__init__.py +92 -0
  284. package/runtime/python/okstra_ctl/worktree/cleanliness.py +89 -0
  285. package/runtime/python/okstra_ctl/worktree/decisions.py +127 -0
  286. package/runtime/python/okstra_ctl/worktree/git_ops.py +165 -0
  287. package/runtime/python/okstra_ctl/worktree/linking.py +249 -0
  288. package/runtime/python/okstra_ctl/worktree/naming.py +91 -0
  289. package/runtime/python/okstra_ctl/worktree/provision.py +385 -0
  290. package/runtime/python/okstra_ctl/worktree/sync_config.py +181 -0
  291. package/runtime/python/okstra_ctl/worktree_cli.py +75 -0
  292. package/runtime/python/okstra_ctl/worktree_lookup_cli.py +39 -0
  293. package/runtime/python/okstra_ctl/worktree_registry.py +7 -0
  294. package/runtime/python/okstra_ctl/worktree_status_cli.py +53 -0
  295. package/runtime/python/okstra_ctl/write_policy.py +90 -213
  296. package/runtime/python/okstra_project/__init__.py +0 -4
  297. package/runtime/python/okstra_project/dirs.py +2 -2
  298. package/runtime/python/okstra_project/phase_pointer.py +84 -0
  299. package/runtime/python/okstra_project/slug.py +24 -0
  300. package/runtime/python/okstra_project/state.py +22 -230
  301. package/runtime/python/okstra_token_usage/antigravity.py +50 -12
  302. package/runtime/python/okstra_token_usage/claude.py +9 -1
  303. package/runtime/python/okstra_token_usage/cli.py +3 -1
  304. package/runtime/python/okstra_token_usage/collect.py +171 -36
  305. package/runtime/python/okstra_token_usage/cursor.py +2 -0
  306. package/runtime/python/okstra_token_usage/grok.py +1 -5
  307. package/runtime/python/okstra_token_usage/paths.py +26 -0
  308. package/runtime/python/okstra_token_usage/pricing.py +23 -8
  309. package/runtime/schemas/convergence-critic-results-v1.0.schema.json +5 -0
  310. package/runtime/schemas/execution-manifest-v2.schema.json +10 -10
  311. package/runtime/schemas/final-report-v2.0.schema.json +9 -26
  312. package/runtime/schemas/final-report-v3.0.schema.json +173 -41
  313. package/runtime/schemas/report-narrative-v3.0.schema.json +3 -2
  314. package/runtime/skills/okstra-brief-gen/SKILL.md +35 -2
  315. package/runtime/skills/okstra-container-build/SKILL.md +16 -47
  316. package/runtime/skills/okstra-inspect/facets/history.md +1 -1
  317. package/runtime/skills/okstra-inspect/facets/status.md +7 -6
  318. package/runtime/skills/okstra-pr-gen/SKILL.md +1 -1
  319. package/runtime/skills/okstra-run/SKILL.md +7 -5
  320. package/runtime/templates/report-writer-prompt-preamble.md +3 -1
  321. package/runtime/templates/reports/brief.template.md +6 -2
  322. package/runtime/templates/reports/error-analysis-input.template.md +2 -0
  323. package/runtime/templates/reports/final-verification-input.template.md +2 -0
  324. package/runtime/templates/reports/implementation-input.template.md +7 -1
  325. package/runtime/templates/reports/implementation-planning-input.template.md +4 -0
  326. package/runtime/templates/reports/quick-input.template.md +2 -0
  327. package/runtime/templates/reports/release-handoff-input.template.md +6 -1
  328. package/runtime/templates/reports/report.js +20 -3
  329. package/runtime/templates/reports/schedule.template.md +2 -0
  330. package/runtime/templates/reports/settings.template.json +0 -10
  331. package/runtime/templates/reports/task-brief.template.md +3 -0
  332. package/runtime/templates/reports/user-response.template.md +7 -3
  333. package/runtime/validators/checks/fixtures-01.py +57 -0
  334. package/runtime/validators/checks/fixtures-02.py +565 -0
  335. package/runtime/validators/checks/runners-01.py +108 -0
  336. package/runtime/validators/checks/validate-assets-01.py +60 -0
  337. package/runtime/validators/checks/validate-prompt-metadata-01.py +261 -0
  338. package/runtime/validators/checks/validate-tasks-01.py +46 -0
  339. package/runtime/validators/checks/validate-tasks-02.py +85 -0
  340. package/runtime/validators/checks/validate-tasks-03.py +61 -0
  341. package/runtime/validators/checks/validate-tasks-04.py +118 -0
  342. package/runtime/validators/forbidden_actions.py +73 -3
  343. package/runtime/validators/lib/common.sh +5 -0
  344. package/runtime/validators/lib/fixtures.sh +7 -591
  345. package/runtime/validators/lib/paths.sh +13 -0
  346. package/runtime/validators/lib/runners.sh +6 -104
  347. package/runtime/validators/lib/summary.sh +1 -1
  348. package/runtime/validators/lib/validate-assets.sh +6 -56
  349. package/runtime/validators/lib/validate-prompt-metadata.sh +6 -257
  350. package/runtime/validators/lib/validate-tasks.sh +9 -294
  351. package/runtime/validators/validate-implementation-plan-stages.py +4 -4
  352. package/runtime/validators/validate-run.py +1363 -2643
  353. package/runtime/validators/validate-workflow.sh +56 -16
  354. package/runtime/validators/validate_improvement_report.py +2 -1
  355. package/runtime/validators/validate_session_conformance.py +295 -49
  356. package/dist/commands/execute/agent-prompt.d.mts +0 -1
  357. package/dist/commands/execute/agent-prompt.mjs +0 -24
  358. package/dist/commands/execute/agent-prompt.mjs.map +0 -1
  359. package/dist/commands/execute/codex-dispatch.d.mts +0 -3
  360. package/dist/commands/execute/codex-dispatch.mjs +0 -6
  361. package/dist/commands/execute/codex-dispatch.mjs.map +0 -1
  362. package/dist/commands/execute/codex-run.d.mts +0 -3
  363. package/dist/commands/execute/codex-run.mjs +0 -62
  364. package/dist/commands/execute/codex-run.mjs.map +0 -1
  365. package/dist/commands/execute/convergence.d.mts +0 -1
  366. package/dist/commands/execute/convergence.mjs +0 -38
  367. package/dist/commands/execute/convergence.mjs.map +0 -1
  368. package/dist/commands/execute/error-log.d.mts +0 -1
  369. package/dist/commands/execute/error-log.mjs +0 -18
  370. package/dist/commands/execute/error-log.mjs.map +0 -1
  371. package/dist/commands/execute/git-reconcile.d.mts +0 -1
  372. package/dist/commands/execute/git-reconcile.mjs +0 -30
  373. package/dist/commands/execute/git-reconcile.mjs.map +0 -1
  374. package/dist/commands/execute/handoff.d.mts +0 -1
  375. package/dist/commands/execute/handoff.mjs +0 -31
  376. package/dist/commands/execute/handoff.mjs.map +0 -1
  377. package/dist/commands/execute/incremental-carry.d.mts +0 -1
  378. package/dist/commands/execute/incremental-carry.mjs +0 -20
  379. package/dist/commands/execute/incremental-carry.mjs.map +0 -1
  380. package/dist/commands/execute/incremental-scope.d.mts +0 -1
  381. package/dist/commands/execute/incremental-scope.mjs +0 -29
  382. package/dist/commands/execute/incremental-scope.mjs.map +0 -1
  383. package/dist/commands/execute/integrate-stages.d.mts +0 -1
  384. package/dist/commands/execute/integrate-stages.mjs +0 -24
  385. package/dist/commands/execute/integrate-stages.mjs.map +0 -1
  386. package/dist/commands/execute/pane-title.d.mts +0 -1
  387. package/dist/commands/execute/pane-title.mjs +0 -20
  388. package/dist/commands/execute/pane-title.mjs.map +0 -1
  389. package/dist/commands/execute/plan-items.d.mts +0 -1
  390. package/dist/commands/execute/plan-items.mjs +0 -9
  391. package/dist/commands/execute/plan-items.mjs.map +0 -1
  392. package/dist/commands/execute/plan-validate.d.mts +0 -1
  393. package/dist/commands/execute/plan-validate.mjs +0 -68
  394. package/dist/commands/execute/plan-validate.mjs.map +0 -1
  395. package/dist/commands/execute/plan-verify.d.mts +0 -1
  396. package/dist/commands/execute/plan-verify.mjs +0 -43
  397. package/dist/commands/execute/plan-verify.mjs.map +0 -1
  398. package/dist/commands/execute/spawn-followups.d.mts +0 -1
  399. package/dist/commands/execute/spawn-followups.mjs +0 -22
  400. package/dist/commands/execute/spawn-followups.mjs.map +0 -1
  401. package/dist/commands/execute/team.d.mts +0 -3
  402. package/dist/commands/execute/team.mjs +0 -66
  403. package/dist/commands/execute/team.mjs.map +0 -1
  404. package/dist/commands/execute/token-usage.d.mts +0 -1
  405. package/dist/commands/execute/token-usage.mjs +0 -19
  406. package/dist/commands/execute/token-usage.mjs.map +0 -1
  407. package/dist/commands/execute/worker-audit-check.d.mts +0 -1
  408. package/dist/commands/execute/worker-audit-check.mjs +0 -34
  409. package/dist/commands/execute/worker-audit-check.mjs.map +0 -1
  410. package/dist/commands/execute/worker-dispatch.d.mts +0 -7
  411. package/dist/commands/execute/worker-dispatch.mjs +0 -64
  412. package/dist/commands/execute/worker-dispatch.mjs.map +0 -1
  413. package/dist/commands/execute/worker-state.d.mts +0 -1
  414. package/dist/commands/execute/worker-state.mjs +0 -28
  415. package/dist/commands/execute/worker-state.mjs.map +0 -1
  416. package/dist/commands/execute/worktree-lookup.d.mts +0 -1
  417. package/dist/commands/execute/worktree-lookup.mjs +0 -92
  418. package/dist/commands/execute/worktree-lookup.mjs.map +0 -1
  419. package/dist/commands/execute/worktree-status.d.mts +0 -1
  420. package/dist/commands/execute/worktree-status.mjs +0 -121
  421. package/dist/commands/execute/worktree-status.mjs.map +0 -1
  422. package/dist/commands/inspect/code-review.d.mts +0 -1
  423. package/dist/commands/inspect/code-review.mjs +0 -32
  424. package/dist/commands/inspect/code-review.mjs.map +0 -1
  425. package/dist/commands/inspect/container.d.mts +0 -1
  426. package/dist/commands/inspect/container.mjs +0 -25
  427. package/dist/commands/inspect/container.mjs.map +0 -1
  428. package/dist/commands/inspect/context-cost.d.mts +0 -1
  429. package/dist/commands/inspect/context-cost.mjs +0 -25
  430. package/dist/commands/inspect/context-cost.mjs.map +0 -1
  431. package/dist/commands/inspect/design-prep.d.mts +0 -1
  432. package/dist/commands/inspect/design-prep.mjs +0 -22
  433. package/dist/commands/inspect/design-prep.mjs.map +0 -1
  434. package/dist/commands/inspect/error-report.d.mts +0 -1
  435. package/dist/commands/inspect/error-report.mjs +0 -25
  436. package/dist/commands/inspect/error-report.mjs.map +0 -1
  437. package/dist/commands/inspect/error-zip.d.mts +0 -1
  438. package/dist/commands/inspect/error-zip.mjs +0 -24
  439. package/dist/commands/inspect/error-zip.mjs.map +0 -1
  440. package/dist/commands/inspect/log-report.d.mts +0 -1
  441. package/dist/commands/inspect/log-report.mjs +0 -26
  442. package/dist/commands/inspect/log-report.mjs.map +0 -1
  443. package/dist/commands/inspect/model-io.d.mts +0 -1
  444. package/dist/commands/inspect/model-io.mjs +0 -25
  445. package/dist/commands/inspect/model-io.mjs.map +0 -1
  446. package/dist/commands/inspect/profile-show.d.mts +0 -1
  447. package/dist/commands/inspect/profile-show.mjs +0 -28
  448. package/dist/commands/inspect/profile-show.mjs.map +0 -1
  449. package/dist/commands/inspect/recap.d.mts +0 -1
  450. package/dist/commands/inspect/recap.mjs +0 -30
  451. package/dist/commands/inspect/recap.mjs.map +0 -1
  452. package/dist/commands/inspect/resolve-task-key.d.mts +0 -1
  453. package/dist/commands/inspect/resolve-task-key.mjs +0 -24
  454. package/dist/commands/inspect/resolve-task-key.mjs.map +0 -1
  455. package/dist/commands/inspect/rollup.d.mts +0 -1
  456. package/dist/commands/inspect/rollup.mjs +0 -25
  457. package/dist/commands/inspect/rollup.mjs.map +0 -1
  458. package/dist/commands/inspect/run-audit.d.mts +0 -1
  459. package/dist/commands/inspect/run-audit.mjs +0 -25
  460. package/dist/commands/inspect/run-audit.mjs.map +0 -1
  461. package/dist/commands/inspect/set-work-status.d.mts +0 -1
  462. package/dist/commands/inspect/set-work-status.mjs +0 -29
  463. package/dist/commands/inspect/set-work-status.mjs.map +0 -1
  464. package/dist/commands/inspect/stage-map.d.mts +0 -1
  465. package/dist/commands/inspect/stage-map.mjs +0 -131
  466. package/dist/commands/inspect/stage-map.mjs.map +0 -1
  467. package/dist/commands/inspect/task-list.d.mts +0 -1
  468. package/dist/commands/inspect/task-list.mjs +0 -149
  469. package/dist/commands/inspect/task-list.mjs.map +0 -1
  470. package/dist/commands/inspect/task-show.d.mts +0 -1
  471. package/dist/commands/inspect/task-show.mjs +0 -108
  472. package/dist/commands/inspect/task-show.mjs.map +0 -1
  473. package/dist/commands/inspect/time-report.d.mts +0 -1
  474. package/dist/commands/inspect/time-report.mjs +0 -24
  475. package/dist/commands/inspect/time-report.mjs.map +0 -1
  476. package/dist/commands/inspect/usage-report.d.mts +0 -1
  477. package/dist/commands/inspect/usage-report.mjs +0 -23
  478. package/dist/commands/inspect/usage-report.mjs.map +0 -1
  479. package/dist/commands/inspect/user-response.d.mts +0 -1
  480. package/dist/commands/inspect/user-response.mjs +0 -35
  481. package/dist/commands/inspect/user-response.mjs.map +0 -1
  482. package/dist/commands/inspect/worker-liveness.d.mts +0 -1
  483. package/dist/commands/inspect/worker-liveness.mjs +0 -45
  484. package/dist/commands/inspect/worker-liveness.mjs.map +0 -1
  485. package/dist/commands/lifecycle/contract-check.d.mts +0 -1
  486. package/dist/commands/lifecycle/contract-check.mjs +0 -18
  487. package/dist/commands/lifecycle/contract-check.mjs.map +0 -1
  488. package/dist/commands/lifecycle/migrate.d.mts +0 -1
  489. package/dist/commands/lifecycle/migrate.mjs +0 -30
  490. package/dist/commands/lifecycle/migrate.mjs.map +0 -1
  491. package/dist/commands/lifecycle/model.d.mts +0 -1
  492. package/dist/commands/lifecycle/model.mjs +0 -22
  493. package/dist/commands/lifecycle/model.mjs.map +0 -1
  494. package/dist/commands/manager.d.mts +0 -3
  495. package/dist/commands/manager.mjs +0 -50
  496. package/dist/commands/manager.mjs.map +0 -1
  497. package/dist/commands/report/agent-activity.d.mts +0 -1
  498. package/dist/commands/report/agent-activity.mjs +0 -20
  499. package/dist/commands/report/agent-activity.mjs.map +0 -1
  500. package/dist/commands/report/approval-decision.d.mts +0 -1
  501. package/dist/commands/report/approval-decision.mjs +0 -21
  502. package/dist/commands/report/approval-decision.mjs.map +0 -1
  503. package/dist/commands/report/design-snapshot.d.mts +0 -1
  504. package/dist/commands/report/design-snapshot.mjs +0 -19
  505. package/dist/commands/report/design-snapshot.mjs.map +0 -1
  506. package/dist/commands/report/finalize.d.mts +0 -4
  507. package/dist/commands/report/finalize.mjs +0 -64
  508. package/dist/commands/report/finalize.mjs.map +0 -1
  509. package/dist/commands/report/inject-report-index.d.mts +0 -1
  510. package/dist/commands/report/inject-report-index.mjs +0 -21
  511. package/dist/commands/report/inject-report-index.mjs.map +0 -1
  512. package/dist/commands/report/render-final-report.d.mts +0 -1
  513. package/dist/commands/report/render-final-report.mjs +0 -23
  514. package/dist/commands/report/render-final-report.mjs.map +0 -1
  515. package/dist/commands/report/render-views.d.mts +0 -1
  516. package/dist/commands/report/render-views.mjs +0 -27
  517. package/dist/commands/report/render-views.mjs.map +0 -1
  518. package/dist/commands/report/translate.d.mts +0 -1
  519. package/dist/commands/report/translate.mjs +0 -33
  520. package/dist/commands/report/translate.mjs.map +0 -1
  521. package/runtime/bin/lib/okstra/tmux-pane.sh +0 -40
  522. package/runtime/bin/lib/okstra-ctl/cmd-batch.sh +0 -59
  523. package/runtime/bin/lib/okstra-ctl/cmd-list.sh +0 -35
  524. package/runtime/bin/lib/okstra-ctl/cmd-open.sh +0 -36
  525. package/runtime/bin/lib/okstra-ctl/cmd-projects.sh +0 -26
  526. package/runtime/bin/lib/okstra-ctl/cmd-reconcile.sh +0 -29
  527. package/runtime/bin/lib/okstra-ctl/cmd-reindex.sh +0 -38
  528. package/runtime/bin/lib/okstra-ctl/cmd-rerun.sh +0 -345
  529. package/runtime/bin/lib/okstra-ctl/cmd-show.sh +0 -27
  530. package/runtime/bin/lib/okstra-ctl/cmd-tail.sh +0 -92
  531. package/runtime/bin/lib/okstra-ctl/main.sh +0 -41
  532. package/runtime/bin/lib/okstra-ctl/prepare.sh +0 -31
  533. package/runtime/bin/lib/okstra-ctl/usage.sh +0 -23
  534. package/runtime/bin/okstra-central.sh +0 -152
  535. package/runtime/bin/okstra-incremental-carry.py +0 -10
  536. package/runtime/bin/okstra-incremental-scope.py +0 -10
  537. package/runtime/bin/okstra-team-reconcile.sh +0 -36
  538. package/runtime/python/okstra_ctl/agent_prompt_cli.py +0 -1156
  539. package/runtime/python/okstra_ctl/batch.py +0 -60
  540. package/runtime/python/okstra_ctl/clarification_items.py +0 -1050
  541. package/runtime/python/okstra_ctl/container_registry.py +0 -72
  542. package/runtime/python/okstra_ctl/improvement_assignment.py +0 -61
  543. package/runtime/python/okstra_ctl/resolver.py +0 -54
  544. package/runtime/python/okstra_ctl/team_reconcile.py +0 -275
  545. package/runtime/python/okstra_ctl/tmux.py +0 -134
  546. package/runtime/python/okstra_ctl/worktree.py +0 -1099
@@ -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"})
@@ -103,10 +127,23 @@ def _ledger(path: Path, task_key: str, task_type: str, run_seq: str) -> dict[str
103
127
  expected = (task_key, task_type, run_seq)
104
128
  actual = (ledger.get("taskKey"), ledger.get("taskType"), ledger.get("runSeq"))
105
129
  if actual != expected:
106
- raise ApprovalDecisionError("approval ledger identity does not match this run")
130
+ # 원장은 `runSeq` 를 세 자리 문자열로 적는다. 정수 `4` 를 넘긴 호출은
131
+ # 원장을 덤프해 보기 전까지 무엇이 달랐는지 알 수 없었다.
132
+ raise ApprovalDecisionError(
133
+ "approval ledger identity does not match this run: ledger has "
134
+ f"taskKey={actual[0]!r} taskType={actual[1]!r} runSeq={actual[2]!r}; "
135
+ f"this call passed taskKey={expected[0]!r} taskType={expected[1]!r} "
136
+ f"runSeq={expected[2]!r}"
137
+ )
107
138
  return ledger
108
139
 
109
140
 
141
+ def normalize_run_seq(value: str) -> str:
142
+ """`4` → `004`. 원장과 run 산출물은 세 자리 0 패딩 문자열을 쓴다."""
143
+ text = str(value).strip()
144
+ return text.zfill(3) if text.isdigit() else text
145
+
146
+
110
147
  def _validate_option_set(
111
148
  options: Sequence[DecisionOption], classification: str,
112
149
  recommended_disposition: str,
@@ -127,6 +164,23 @@ def _validate_option_set(
127
164
  )
128
165
 
129
166
 
167
+ def _reject_off_enum(value: str, field: str, definition: str) -> None:
168
+ """스키마 밖 값을 원장 진입 지점에서 거절한다.
169
+
170
+ 종전에는 두 provenance 필드가 비어 있지 않기만 하면 통과했다. 리포트 조립이
171
+ 스키마로 잡아 내기는 하지만 그때는 이미 레코드를 디스크에 쓴 뒤라, 잘못된 값이
172
+ 원장과 리포트 양쪽에 남아 이후의 모든 읽기(`okstra user-response`)를 막았다.
173
+ """
174
+ if not value:
175
+ return
176
+ allowed = _schema_enum(definition)
177
+ if value not in allowed:
178
+ raise ApprovalDecisionError(
179
+ f"invalid {field}: {value!r}. Allowed values: "
180
+ + ", ".join(sorted(allowed))
181
+ )
182
+
183
+
130
184
  def _decision_row(
131
185
  *, clarification_id: str, ticket_id: str, statement: str,
132
186
  expected_form: str, classification: str, origin: str,
@@ -135,6 +189,10 @@ def _decision_row(
135
189
  ) -> dict[str, Any]:
136
190
  if classification not in CLASSIFICATIONS:
137
191
  raise ApprovalDecisionError(f"invalid classification: {classification}")
192
+ _reject_off_enum(origin, "origin", "ClarificationOrigin")
193
+ _reject_off_enum(
194
+ user_confirmation, "user_confirmation", "ClarificationUserConfirmation"
195
+ )
138
196
  if recommended_disposition not in DISPOSITIONS:
139
197
  raise ApprovalDecisionError(
140
198
  f"invalid recommended_disposition: {recommended_disposition}"
@@ -235,7 +293,18 @@ def _options_from_args(args: argparse.Namespace) -> tuple[DecisionOption, ...]:
235
293
  args.option_added_work, args.option_direction_change,
236
294
  )
237
295
  if len({len(values) for values in fields}) != 1:
238
- raise ApprovalDecisionError("every repeated option field needs the same count")
296
+ names = (
297
+ "role", "answer", "rationale", "disposition", "reach",
298
+ "scope-effect", "added-work", "direction-change",
299
+ )
300
+ counts = ", ".join(
301
+ f"--option-{name}={len(values)}" for name, values in zip(names, fields)
302
+ )
303
+ raise ApprovalDecisionError(
304
+ "every repeated option field needs the same count — repeat each "
305
+ f"--option-* flag once per option, including the ones --help marks "
306
+ f"optional: {counts}"
307
+ )
239
308
  return tuple(
240
309
  DecisionOption(role, answer, rationale, disposition, reach,
241
310
  tuple(effect.split(",")) if effect else (), added, direction)
@@ -260,8 +329,21 @@ def _add_open_arguments(parser: argparse.ArgumentParser) -> None:
260
329
  parser.add_argument(f"--option-{flag}", action="append", default=[])
261
330
 
262
331
 
332
+ _CLI_EPILOG = r"""Usage:
333
+ okstra approval-decision open --ledger <path> [decision fields]
334
+ okstra approval-decision resolve --ledger <path> --clarification-id <C-NNN> [resolution fields]
335
+ okstra approval-decision carry --ledger <path> --from-responses <clarification-response.md> --clarification-id <C-NNN> [--clarification-id <C-NNN> ...]
336
+ okstra approval-decision carry --ledger <path> --source-ledger <path> --source-run-ref <ref> --clarification-id <C-NNN>
337
+ """
338
+ _CLI_DESCRIPTION = "Record lead-owned approval inputs."
339
+
340
+
263
341
  def _parser() -> argparse.ArgumentParser:
264
- parser = argparse.ArgumentParser(prog="okstra approval-decision")
342
+ parser = argparse.ArgumentParser(
343
+ description=_CLI_DESCRIPTION,
344
+ epilog=_CLI_EPILOG,
345
+ formatter_class=argparse.RawDescriptionHelpFormatter,
346
+ prog="okstra approval-decision")
265
347
  commands = parser.add_subparsers(dest="command", required=True)
266
348
  opened = commands.add_parser("open")
267
349
  opened.add_argument("--ledger", type=Path, required=True)
@@ -275,9 +357,38 @@ def _parser() -> argparse.ArgumentParser:
275
357
  resolved.add_argument("--check-ref", action="append", default=[])
276
358
  carried = commands.add_parser("carry")
277
359
  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)
360
+ source = carried.add_mutually_exclusive_group(required=True)
361
+ source.add_argument(
362
+ "--from-responses",
363
+ type=Path,
364
+ help=(
365
+ "attached user-responses bundle (`instruction-set/"
366
+ "clarification-response.md`). It is the source of truth for the "
367
+ "answer; the question's own fields come from the report each "
368
+ "response section names. Task-level and cumulative, so no prior "
369
+ "run seq has to be located."
370
+ ),
371
+ )
372
+ source.add_argument(
373
+ "--source-ledger",
374
+ type=Path,
375
+ help="a prior run's approval ledger, when it is still on disk",
376
+ )
377
+ carried.add_argument(
378
+ "--source-run-ref",
379
+ default="",
380
+ help=(
381
+ "provenance recorded on the carried row. Required with "
382
+ "--source-ledger; with --from-responses it defaults to the "
383
+ "response section the answer came from."
384
+ ),
385
+ )
386
+ carried.add_argument(
387
+ "--clarification-id",
388
+ action="append",
389
+ required=True,
390
+ help="repeat to carry several ids in one call",
391
+ )
281
392
  return parser
282
393
 
283
394
 
@@ -285,19 +396,181 @@ def _open_from_args(args: argparse.Namespace) -> None:
285
396
  values = vars(args).copy()
286
397
  values.pop("command")
287
398
  values["ledger_path"] = values.pop("ledger")
399
+ values["run_seq"] = normalize_run_seq(values["run_seq"])
288
400
  for key in tuple(values):
289
401
  if key.startswith("option_"):
290
402
  values.pop(key)
291
403
  open_decision(**values, options=_options_from_args(args))
292
404
 
293
405
 
406
+ # `kind: decision` 인 행은 스키마가 옵션 메뉴를 요구하므로 함께 가져온다.
407
+ # `resolution` 은 가져오지 않는다 — 그 `checkRefs` 는 답을 기록한 run 의 활동
408
+ # 행이고, 이월 행은 `answered` 로 실린다.
409
+ _QUESTION_FIELDS = (
410
+ "id", "ticketId", "kind", "statement", "expectedForm", "blocks", "origin",
411
+ "options",
412
+ )
413
+
414
+
415
+ def _responses_index(path: Path) -> dict[str, tuple[Any, str, str, str]]:
416
+ """묶음 파일의 답을 ``{id: (entry, 구간 이름, 리포트 기록 이름, task-type)}`` 로.
417
+
418
+ 같은 id 를 여러 run 이 답했으면 뒤 구간이 이긴다 — 묶음은 시간순으로 쌓이고
419
+ 최신 답이 정본이다.
420
+ """
421
+ try:
422
+ text = path.read_text(encoding="utf-8")
423
+ except OSError as exc:
424
+ raise ApprovalDecisionError(f"cannot read {path}: {exc}") from exc
425
+ sections = attached_response_sections(text)
426
+ if not sections:
427
+ raise ApprovalDecisionError(
428
+ f"not an attached user-responses bundle (no `## user-response-*.md` "
429
+ f"section): {path}"
430
+ )
431
+ index: dict[str, tuple[Any, str, str, str]] = {}
432
+ for name, body in sections:
433
+ report = response_source_report(body)
434
+ task_type = _frontmatter_task_type(body)
435
+ for entry in parse_user_response_entries(body):
436
+ index[entry.response_id] = (entry, name, report, task_type)
437
+ return index
438
+
439
+
440
+ def _frontmatter_task_type(section_body: str) -> str:
441
+ match = re.search(r"^task-type:\s*(\S.*?)\s*$", section_body, re.MULTILINE)
442
+ return match.group(1) if match else ""
443
+
444
+
445
+ def _prior_question_row(
446
+ responses_path: Path, clarification_id: str, report_name: str, task_type: str,
447
+ ) -> dict[str, Any]:
448
+ """질문 메타데이터의 출처는 그 질문을 낸 리포트다.
449
+
450
+ 묶음 파일은 답만 담는다. `ticketId`·`statement`·`expectedForm` 은 스키마가
451
+ 요구하는데 묶음에 없으므로, 지어내는 대신 구간 frontmatter 가 지목한
452
+ 리포트에서 읽는다. 그 리드가 seq 를 찾아 헤매지 않게 하는 것이 이 경로의
453
+ 요점이다.
454
+ """
455
+ if not report_name or not task_type:
456
+ raise ApprovalDecisionError(
457
+ f"{clarification_id}: its response section names no source report; "
458
+ "carry it with --source-ledger instead"
459
+ )
460
+ task_root = responses_path.resolve().parent.parent
461
+ report_path = (
462
+ task_root / "runs" / task_type / "reports" / Path(report_name).name
463
+ )
464
+ if not report_path.is_file():
465
+ raise ApprovalDecisionError(
466
+ f"{clarification_id}: source report not found: {report_path}"
467
+ )
468
+ try:
469
+ data = load_owned_object(report_path, artifact="prior final report")
470
+ except (JsonBoundaryError, OSError) as exc:
471
+ raise ApprovalDecisionError(f"cannot read {report_path}: {exc}") from exc
472
+ rows = data.get("clarificationItems") if isinstance(data, Mapping) else None
473
+ match = next(
474
+ (
475
+ row for row in (rows or [])
476
+ if isinstance(row, Mapping) and row.get("id") == clarification_id
477
+ ),
478
+ None,
479
+ )
480
+ if match is None:
481
+ raise ApprovalDecisionError(
482
+ f"{clarification_id}: not defined in {report_path.name}"
483
+ )
484
+ row = {key: match[key] for key in _QUESTION_FIELDS if key in match}
485
+ if isinstance(row.get("options"), list):
486
+ row["options"] = [
487
+ _contract_v3_option(option, clarification_id)
488
+ for option in row["options"]
489
+ if isinstance(option, Mapping)
490
+ ]
491
+ if "approvalContext" in match:
492
+ row["approval"] = match["approvalContext"]
493
+ return row
494
+
495
+
496
+ _REACH_TOKENS = ("in-repo", "cross-repo")
497
+
498
+
499
+ def _contract_v3_option(option: Mapping[str, Any], clarification_id: str) -> dict[str, Any]:
500
+ """계약 2.0 의 `scopeImpact` 를 3.0 의 `reach` + `scopeEffects` 로 되돌린다.
501
+
502
+ 두 열거는 정확히 분할 관계다 — 2.0 의 토큰 집합
503
+ `{in-repo, cross-repo, new-schema, deferrable}` 이 3.0 에서 `reach`
504
+ (`in-repo|cross-repo`, 정확히 하나)와 `scopeEffects`(나머지)로 갈렸다.
505
+ 그래서 이 변환은 값을 지어내지 않는다. 이미 3.0 모양이면 그대로 둔다.
506
+ """
507
+ row = dict(option)
508
+ if "reach" in row:
509
+ row.pop("scopeImpact", None)
510
+ return row
511
+ tokens = row.pop("scopeImpact", None)
512
+ if not isinstance(tokens, list):
513
+ raise ApprovalDecisionError(
514
+ f"{clarification_id}: an option states neither `reach` nor "
515
+ "`scopeImpact`; carry it with --source-ledger instead"
516
+ )
517
+ reach = [token for token in tokens if token in _REACH_TOKENS]
518
+ if len(reach) != 1:
519
+ raise ApprovalDecisionError(
520
+ f"{clarification_id}: an option's `scopeImpact` names {len(reach)} "
521
+ "reach token(s); exactly one is needed to state `reach`"
522
+ )
523
+ row["reach"] = reach[0]
524
+ effects = [token for token in tokens if token not in _REACH_TOKENS]
525
+ if effects:
526
+ row["scopeEffects"] = effects
527
+ return row
528
+
529
+
530
+ def _carry_from_responses(args: argparse.Namespace) -> None:
531
+ index = _responses_index(args.from_responses)
532
+ for clarification_id in args.clarification_id:
533
+ found = index.get(clarification_id)
534
+ if found is None:
535
+ raise ApprovalDecisionError(
536
+ f"clarification not answered in {args.from_responses}: "
537
+ f"{clarification_id}"
538
+ )
539
+ entry, section, report_name, task_type = found
540
+ if not entry.value.strip():
541
+ raise ApprovalDecisionError(
542
+ f"{clarification_id}: its answer in {section} has no Value"
543
+ )
544
+ decision = _prior_question_row(
545
+ args.from_responses, clarification_id, report_name, task_type
546
+ )
547
+ # 답이 정본인 자리는 여기다. `resolutionInput` 은 쓰지 않는다 —
548
+ # 그 모양은 `checkRefs` 로 이번 run 의 활동 행을 요구하는데, 이월된
549
+ # 답에는 이번 run 의 활동이 없다(`report_assembly` 의 이월 분기).
550
+ decision["userConfirmation"] = "asked-and-answered"
551
+ decision["userInput"] = entry.value
552
+ carry_decision(
553
+ args.ledger,
554
+ source_run_ref=args.source_run_ref or section,
555
+ decision=decision,
556
+ )
557
+
558
+
294
559
  def _carry_from_args(args: argparse.Namespace) -> None:
295
560
  source = _read_ledger(args.source_ledger)
296
561
  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])
562
+ for clarification_id in args.clarification_id:
563
+ matches = [
564
+ row for row in rows
565
+ if isinstance(row, dict) and row.get("id") == clarification_id
566
+ ]
567
+ if len(matches) != 1:
568
+ raise ApprovalDecisionError(
569
+ f"source clarification not found: {clarification_id}"
570
+ )
571
+ carry_decision(
572
+ args.ledger, source_run_ref=args.source_run_ref, decision=matches[0]
573
+ )
301
574
 
302
575
 
303
576
  def _run(args: argparse.Namespace) -> None:
@@ -309,7 +582,11 @@ def _run(args: argparse.Namespace) -> None:
309
582
  user_text=args.user_text, user_response_ref=args.user_response_ref,
310
583
  check_refs=args.check_ref,
311
584
  )
585
+ elif args.from_responses:
586
+ _carry_from_responses(args)
312
587
  else:
588
+ if not args.source_run_ref:
589
+ raise ApprovalDecisionError("--source-ledger requires --source-run-ref")
313
590
  _carry_from_args(args)
314
591
 
315
592
 
@@ -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(