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
@@ -14,7 +14,11 @@ from pathlib import Path
14
14
  from typing import Any, Mapping
15
15
 
16
16
  from .clarification_items import USER_INPUT_BLOCKS, progress_blocking_ids
17
- from .final_report_paths import final_report_data_path
17
+ from .final_report_paths import (
18
+ final_report_data_path,
19
+ final_report_markdown_path,
20
+ is_full_reading_copy_path,
21
+ )
18
22
  from .exact_coverage import ExactCoverageError, calculate_plan_exact_coverage
19
23
  from .final_report_schema import (
20
24
  SchemaError,
@@ -32,8 +36,12 @@ from .user_response import UserResponseError, parse_direction_selection
32
36
 
33
37
 
34
38
  _TASK_TYPE = "implementation-option-selection"
39
+ # 손잡이는 리포트 **기록**이다. 전체 열람본(`.md`)은 `2bcd575` 이후 완료 산출물이
40
+ # 아니라 요청 시 렌더라(`dispatch_state._required_worker_artifacts`) run 의 reports
41
+ # 디렉터리에 없다. `--approved-plan` 이 이미 같은 이유로 기록만 받는다
42
+ # (`final_report_paths.require_approved_plan_record`).
35
43
  _REPORT_RE = re.compile(
36
- r"^final-report-implementation-option-selection-(?P<seq>\d{3,})\.md$"
44
+ r"^final-report-implementation-option-selection-(?P<seq>\d{3,})\.data\.json$"
37
45
  )
38
46
  _FRONTMATTER_RE = re.compile(
39
47
  r"\A---[ \t]*\r?\n(?P<body>.*?)(?:\r?\n)---[ \t]*(?:\r?\n|\Z)",
@@ -138,13 +146,6 @@ def _selection_layout(report: Path) -> tuple[Path, str]:
138
146
  return task_root, match.group("seq")
139
147
 
140
148
 
141
- def _sibling_data_path(report: Path) -> Path:
142
- data_path = _strict_regular_file(final_report_data_path(report), "data")
143
- if data_path.parent != report.parent:
144
- raise DirectionSelectionError("data file must be a sibling of the report")
145
- return data_path
146
-
147
-
148
149
  def _parse_data(data_path: Path) -> tuple[dict[str, Any], bytes]:
149
150
  try:
150
151
  snapshot = load_owned_object_snapshot(
@@ -215,7 +216,7 @@ def _comparison_selection(
215
216
  task_root: Path,
216
217
  seq: str,
217
218
  expected_task_key: str,
218
- ) -> tuple[str, str, str, str, tuple[str, ...]]:
219
+ ) -> tuple[str, str, str, tuple[str, ...]]:
219
220
  sidecar = _strict_regular_file(
220
221
  report.parent.parent
221
222
  / "user-responses"
@@ -253,7 +254,6 @@ def _comparison_selection(
253
254
  return (
254
255
  _relative_source(sidecar, task_root, "selection evidence"),
255
256
  parsed.option_id,
256
- parsed.option_name,
257
257
  parsed.selection_note,
258
258
  constraints,
259
259
  )
@@ -261,7 +261,7 @@ def _comparison_selection(
261
261
 
262
262
  def _preselected_selection(
263
263
  selection: Mapping[str, Any],
264
- ) -> tuple[str, str, str, str, tuple[str, ...]]:
264
+ ) -> tuple[str, str, str, tuple[str, ...]]:
265
265
  if selection.get("routing") != "implementation-planning":
266
266
  raise DirectionSelectionError(
267
267
  "preselected direction routing must be implementation-planning"
@@ -276,12 +276,21 @@ def _preselected_selection(
276
276
  raise DirectionSelectionError(
277
277
  "preselected direction evidence and citation are required"
278
278
  )
279
- return f"{confirmation} ({citation})", option_id, "", "", ()
279
+ return f"{confirmation} ({citation})", option_id, "", ()
280
280
 
281
281
 
282
282
  def _selected_option(
283
- selection: Mapping[str, Any], option_id: str, option_name: str
283
+ selection: Mapping[str, Any], option_id: str
284
284
  ) -> tuple[Mapping[str, Any], str]:
285
+ """선택된 후보를 **id 로만** 특정한다.
286
+
287
+ 사이드카의 `Option-Name` 은 대조하지 않는다. HTML 열람본은 번역 오버레이를
288
+ 적용한 사본으로 렌더되므로(`report_html/render.py` `_localize`) Export 가
289
+ 적는 이름은 독자의 언어다 — `reportLanguage` 가 `en` 이 아닌 런에서 영어
290
+ 정본과의 바이트 비교는 통과할 수 있는 값이 하나도 없었다. id 는 번역되지
291
+ 않으므로 신원은 id 가 온전히 결정하고, 하류로 나가는 이름은 아래에서
292
+ 리포트 값으로 정규화된다.
293
+ """
285
294
  options = selection.get("rankedOptions")
286
295
  if not isinstance(options, list):
287
296
  raise DirectionSelectionError("ranked candidate list is missing")
@@ -297,10 +306,8 @@ def _selected_option(
297
306
  )
298
307
  except ValueError as exc:
299
308
  raise DirectionSelectionError(str(exc)) from exc
300
- if report_option_id != option_id or (
301
- option_name and report_option_name != option_name
302
- ):
303
- raise DirectionSelectionError("selected candidate name does not match report")
309
+ if report_option_id != option_id:
310
+ raise DirectionSelectionError("selected candidate id does not match report")
304
311
  coverage = option.get("coverageSummary")
305
312
  if not isinstance(coverage, Mapping) or coverage.get("coverageVerdict") != "exact":
306
313
  raise DirectionSelectionError("selected candidate must have exact coverage")
@@ -340,12 +347,26 @@ def resolve_selected_direction(
340
347
  report_path: Path,
341
348
  expected_task_key: str,
342
349
  ) -> SelectedDirection:
343
- """Validate a selection report in the documented fail-closed order."""
344
- report = _strict_regular_file(Path(report_path), "report")
345
- data_path = _sibling_data_path(report)
346
- task_root, seq = _selection_layout(report)
347
- report = validate_task_artifact_path(report, task_root, "report")
350
+ """Validate a selection report record in the documented fail-closed order.
351
+
352
+ 손잡이는 `.data.json` 이다. 전체 열람본은 어디서도 열리지 않는다 — 사이드카의
353
+ `source-report` 대조할 이름으로만 쓰인다. 그 파일을 실물로 요구하는 동안
354
+ 게이트는 통과할 있는 입력이 0개였고, 후보 비교가 끝나도 계획으로 넘어갈
355
+ 길이 없었다. 실측: fontsninja-v3-site 의 선택 리포트 3쌍 전부 열람본이 없다.
356
+ """
357
+ handle = lexical_absolute_path(Path(report_path))
358
+ if is_full_reading_copy_path(handle):
359
+ raise DirectionSelectionError(
360
+ "selected direction must be the report record (.data.json), not the "
361
+ f"full reading copy: {report_path}\n"
362
+ f" use: {final_report_data_path(handle)}"
363
+ )
364
+ data_path = _strict_regular_file(handle, "data")
365
+ task_root, seq = _selection_layout(data_path)
348
366
  data_path = validate_task_artifact_path(data_path, task_root, "data")
367
+ # 사이드카의 `source-report` 는 열람본 이름을 적는다. 그 파일을 열지는 않는다 —
368
+ # 대조할 이름일 뿐이다.
369
+ report = final_report_markdown_path(data_path)
349
370
  data, data_bytes = _parse_data(data_path)
350
371
  _validate_report_identity(data, expected_task_key)
351
372
  _validate_schema(data)
@@ -354,7 +375,7 @@ def resolve_selected_direction(
354
375
  raise DirectionSelectionError("implementation option selection data is missing")
355
376
  mode = str(selection.get("mode") or "")
356
377
  if mode == "candidate-comparison":
357
- evidence, option_id, option_name, note, constraints = _comparison_selection(
378
+ evidence, option_id, note, constraints = _comparison_selection(
358
379
  report=report,
359
380
  data_path=data_path,
360
381
  data_bytes=data_bytes,
@@ -363,14 +384,10 @@ def resolve_selected_direction(
363
384
  expected_task_key=expected_task_key,
364
385
  )
365
386
  elif mode == "preselected-validation":
366
- evidence, option_id, option_name, note, constraints = _preselected_selection(
367
- selection
368
- )
387
+ evidence, option_id, note, constraints = _preselected_selection(selection)
369
388
  else:
370
389
  raise DirectionSelectionError(f"unsupported selection mode: {mode}")
371
- option, normalized_option_name = _selected_option(
372
- selection, option_id, option_name
373
- )
390
+ option, normalized_option_name = _selected_option(selection, option_id)
374
391
  _validate_no_blockers(data, option)
375
392
  return SelectedDirection(
376
393
  mode=mode,
@@ -439,7 +456,7 @@ _PLAN_EXECUTION_FIELDS = (
439
456
  )
440
457
 
441
458
 
442
- def _load_selected_direction_snapshot(
459
+ def load_selected_direction_snapshot(
443
460
  snapshot_path: Path,
444
461
  ) -> tuple[SelectedDirectionSnapshot | None, list[str]]:
445
462
  path = lexical_absolute_path(Path(snapshot_path))
@@ -484,9 +501,16 @@ def validate_selected_direction_ref(
484
501
  "the selected-direction snapshot"
485
502
  )
486
503
  if ref.get("snapshotPath") != snapshot.relative_path:
504
+ # 기대값을 이름으로 댄다. `sourceReport` / `sourceDataSha256` / `optionId`
505
+ # 는 스냅샷 파일에 그대로 실려 있어 저자가 옮겨 적으면 되지만,
506
+ # `snapshotPath` 는 저자가 직접 만들어야 하고 그 기준점(태스크 루트)은
507
+ # 스키마에도 프롬프트에도 적혀 있지 않았다. 무엇과 다른지 말하지 않는
508
+ # 실패는 실행이 스스로 고칠 수 없다.
487
509
  failures.append(
488
510
  "selectedDirectionRef.snapshotPath is missing or does not match the "
489
- "actual selected-direction snapshot path"
511
+ "actual selected-direction snapshot path; expected the task-root "
512
+ f"relative {snapshot.relative_path!r}, got "
513
+ f"{ref.get('snapshotPath')!r}"
490
514
  )
491
515
  if ref.get("snapshotSha256") != snapshot.sha256:
492
516
  failures.append(
@@ -774,6 +798,89 @@ def _declared_coverage_summary_errors(
774
798
  ]
775
799
 
776
800
 
801
+ # stage 를 게이트하는 검증 행이 implementation run 안에서 실행 가능함을 강제하는
802
+ # 마커. 실측 실패값(dev-10341 VC-013/VC-014)에서 왔다: 수동 워크스루·배포 전제
803
+ # 문구가 stageRefs 를 단 채 실리면, implementation run 은 배포 금지·자격증명
804
+ # 부재라 그 행을 영원히 통과시킬 수 없고 코드 결함 0건인 stage 가 차단된다.
805
+ # 그런 검증은 브리프의 External Gates / final-verification 의 user-owned
806
+ # external QA 소속이다.
807
+ _MANUAL_VALIDATION_MARKERS = (
808
+ "by hand",
809
+ "manually",
810
+ "redeploy",
811
+ "deployment",
812
+ "preproduction",
813
+ "signed in",
814
+ )
815
+
816
+
817
+ def _stage_validation_executability_errors(
818
+ planning: Mapping[str, Any],
819
+ ) -> list[str]:
820
+ failures: list[str] = []
821
+ for row in planning.get("validationChecklist") or ():
822
+ if not isinstance(row, Mapping) or not row.get("stageRefs"):
823
+ continue
824
+ lowered = str(row.get("commandOrObservation") or "").lower()
825
+ matched = [
826
+ marker for marker in _MANUAL_VALIDATION_MARKERS if marker in lowered
827
+ ]
828
+ if matched:
829
+ failures.append(
830
+ f"validationChecklist {row.get('id')} gates stage(s) "
831
+ f"{list(row.get('stageRefs') or ())} but is not executable by the "
832
+ f"implementation run (marker(s): {matched}); move manual or "
833
+ "deployed-environment verification to the brief's External Gates "
834
+ "or final-verification's user-owned external QA"
835
+ )
836
+ return failures
837
+
838
+
839
+ def _is_config_only_path(path: str) -> bool:
840
+ name = posixpath.basename(posixpath.normpath(str(path).replace("\\", "/")))
841
+ if name == "Dockerfile" or name.startswith("Dockerfile."):
842
+ return True
843
+ if name.startswith("docker-compose") or name.startswith(".env"):
844
+ return True
845
+ return name.endswith((".yml", ".yaml"))
846
+
847
+
848
+ def _micro_stage_fold_errors(planning: Mapping[str, Any]) -> list[str]:
849
+ """설정 파일 전용 종속 stage 는 독립 stage 가 아니다 — 커밋으로만 나눈다.
850
+
851
+ 실측(dev-10341): 다른 stage 가 도입한 플래그를 Dockerfile/compose 2줄,
852
+ workflow 2줄로 전파하는 작업이 stage 2·3 을 각각 차지해 run 2회(회당
853
+ 30분+)와 검증 차단 1회를 낳았다. 값 전파는 도입 stage 의 스텝이고,
854
+ 분리가 필요하면 커밋을 나눈다. 첫 stage(의존 없음)는 예외다 — 설정만
855
+ 바꾸는 태스크 자체는 정당하다.
856
+ """
857
+ dependent: dict[int, bool] = {}
858
+ for row in planning.get("stageMap") or ():
859
+ if isinstance(row, Mapping) and isinstance(row.get("stage"), int):
860
+ raw = str(row.get("dependsOn") or "").strip().lower()
861
+ dependent[row["stage"]] = raw not in ("", "(none)", "none")
862
+ failures: list[str] = []
863
+ for stage in planning.get("stages") or ():
864
+ if not isinstance(stage, Mapping):
865
+ continue
866
+ number = stage.get("stage")
867
+ if not isinstance(number, int) or not dependent.get(number):
868
+ continue
869
+ paths = [
870
+ str(path)
871
+ for step in stage.get("stepwiseExecution") or ()
872
+ if isinstance(step, Mapping)
873
+ for path in step.get("plannedPaths") or ()
874
+ ]
875
+ if paths and all(_is_config_only_path(path) for path in paths):
876
+ failures.append(
877
+ f"stage {number} is a config-only propagation stage "
878
+ f"({sorted(set(paths))}); fold it into the stage that introduces "
879
+ "the value and split the work by commits, not stages"
880
+ )
881
+ return failures
882
+
883
+
777
884
  def _direction_invalidation_errors(planning: Mapping[str, Any]) -> list[str]:
778
885
  failures: list[str] = []
779
886
  invalidation = planning.get("directionInvalidation")
@@ -807,7 +914,7 @@ def validate_selected_direction_plan(
807
914
  return ["implementationPlanning is missing"]
808
915
  if planning.get("planningContract") != "selected-direction":
809
916
  return ["implementationPlanning.planningContract must be selected-direction"]
810
- snapshot, failures = _load_selected_direction_snapshot(snapshot_path)
917
+ snapshot, failures = load_selected_direction_snapshot(snapshot_path)
811
918
  if snapshot is None:
812
919
  return failures
813
920
  header = data.get("header")
@@ -840,4 +947,6 @@ def validate_selected_direction_plan(
840
947
  planning, brief_end_state_id_sequence(Path(brief_path))
841
948
  )
842
949
  )
950
+ failures.extend(_stage_validation_executability_errors(planning))
951
+ failures.extend(_micro_stage_fold_errors(planning))
843
952
  return failures
@@ -22,6 +22,12 @@ EVALUATION_CRITERIA = (
22
22
  "verification-cost",
23
23
  "rollout-cost",
24
24
  )
25
+ MIN_CRITERION_VALUE = 1
26
+ MAX_CRITERION_VALUE = 5
27
+ MIN_FEASIBLE_VOTES = 2
28
+ MAX_RANKED_OPTIONS = 3
29
+ CANDIDATE_COMPARISON_ROUTING = "pending-direction-selection"
30
+ NO_VALID_OPTIONS_ROUTING = "blocked"
25
31
 
26
32
  _FINGERPRINT_FIELDS = (
27
33
  "goal",
@@ -115,7 +121,9 @@ def _validate_criteria(data: Mapping[str, object], errors: list[str]) -> dict[st
115
121
  errors.append("evaluationCriteria must contain the fixed eight criteria in order")
116
122
  weights = _criterion_values(rows, "weight")
117
123
  if any(
118
- not isinstance(weight, int) or isinstance(weight, bool) or not 1 <= weight <= 5
124
+ not isinstance(weight, int)
125
+ or isinstance(weight, bool)
126
+ or not MIN_CRITERION_VALUE <= weight <= MAX_CRITERION_VALUE
119
127
  for weight in weights.values()
120
128
  ):
121
129
  errors.append("evaluationCriteria weights must be integers in 1..5")
@@ -177,14 +185,16 @@ def _validate_option_scores(
177
185
  errors.append(f"{option_id} criterionScores must contain the fixed eight criteria")
178
186
  scores = _criterion_values(score_rows, "score")
179
187
  if any(
180
- not isinstance(score, int) or isinstance(score, bool) or not 1 <= score <= 5
188
+ not isinstance(score, int)
189
+ or isinstance(score, bool)
190
+ or not MIN_CRITERION_VALUE <= score <= MAX_CRITERION_VALUE
181
191
  for score in scores.values()
182
192
  ):
183
193
  errors.append(f"{option_id} criterion score values must be integers in 1..5")
184
194
  values_valid = all(
185
195
  isinstance(score, int)
186
196
  and not isinstance(score, bool)
187
- and 1 <= score <= 5
197
+ and MIN_CRITERION_VALUE <= score <= MAX_CRITERION_VALUE
188
198
  for score in scores.values()
189
199
  )
190
200
  if (
@@ -223,7 +233,7 @@ def _validate_option_feasibility(
223
233
  isinstance(vote, Mapping) and vote.get("verdict") == "feasible"
224
234
  for vote in votes
225
235
  )
226
- if require_valid and feasible < 2:
236
+ if require_valid and feasible < MIN_FEASIBLE_VOTES:
227
237
  errors.append(f"{option_id} must have at least two feasible votes")
228
238
  if require_valid and option.get("safetyBlockers"):
229
239
  errors.append(f"{option_id} safetyBlockers must be empty")
@@ -231,7 +241,7 @@ def _validate_option_feasibility(
231
241
  errors.append(f"{option_id} unresolvedFeasibilityFacts must be empty")
232
242
  return (
233
243
  all_participated
234
- and feasible >= 2
244
+ and feasible >= MIN_FEASIBLE_VOTES
235
245
  and not option.get("safetyBlockers")
236
246
  and not option.get("unresolvedFeasibilityFacts")
237
247
  )
@@ -270,8 +280,10 @@ def _validate_displayed_options(
270
280
  options = [
271
281
  option for option in (raw_options or ()) if isinstance(option, Mapping)
272
282
  ]
273
- if len(options) > 3:
274
- errors.append("rankedOptions must display at most three valid options")
283
+ if len(options) > MAX_RANKED_OPTIONS:
284
+ errors.append(
285
+ f"rankedOptions must display at most {MAX_RANKED_OPTIONS} valid options"
286
+ )
275
287
  validity = {
276
288
  str(option.get("id")): _validate_candidate(
277
289
  option,
@@ -297,10 +309,10 @@ def _validate_option_count_and_routing(
297
309
  if not valid_candidates:
298
310
  if recommended is not None:
299
311
  errors.append("recommendedOptionId must be null when no valid options exist")
300
- if routing != "blocked":
312
+ if routing != NO_VALID_OPTIONS_ROUTING:
301
313
  errors.append("routing must be blocked only when no valid options exist")
302
314
  return
303
- if routing == "blocked":
315
+ if routing == NO_VALID_OPTIONS_ROUTING:
304
316
  errors.append("routing may be blocked only when no valid options exist")
305
317
  if not options:
306
318
  errors.append("recommendedOptionId must name the first ranked option")
@@ -409,7 +421,9 @@ def _validate_complete_ranking(
409
421
  and audit_validity.get(str(row.get("id")))
410
422
  and candidate_fingerprint(row) not in displayed_fingerprints
411
423
  )
412
- expected = rank_valid_options(valid, data.get("evaluationCriteria") or ())[:3]
424
+ expected = rank_valid_options(
425
+ valid, data.get("evaluationCriteria") or ()
426
+ )[:MAX_RANKED_OPTIONS]
413
427
  reported = tuple(str(option.get("id")) for option in options)
414
428
  if reported != expected:
415
429
  errors.append("rankedOptions must contain the top valid candidates in order")
@@ -427,7 +441,7 @@ def _validate_mode(
427
441
  if mode == "candidate-comparison":
428
442
  if direction is not None:
429
443
  errors.append("candidate-comparison must not set preselectedDirection")
430
- if options and data.get("routing") != "pending-direction-selection":
444
+ if options and data.get("routing") != CANDIDATE_COMPARISON_ROUTING:
431
445
  errors.append("candidate-comparison with options must await direction selection")
432
446
  return
433
447
  if len(options) != 1:
@@ -13,7 +13,7 @@ from .consumers import (
13
13
  )
14
14
  from . import next_phase
15
15
  from .paths import RunRef, task_manifest_file
16
- from .stage_map import StageMapError, load_task_stage_map
16
+ from .stage_map import StageMapError, load_latest_plan_stage_map
17
17
  from .workflow import PHASE_SEQUENCE
18
18
  from .json_boundary import JsonBoundaryError, load_owned_object, write_owned_object_atomic
19
19
 
@@ -38,8 +38,11 @@ def derive_implementation_outcome(task_root: Path) -> ImplementationOutcome:
38
38
  if not plan_run_root.is_dir():
39
39
  return ImplementationOutcome(completed=False, reason="implementation-planning run root missing")
40
40
 
41
+ # 어떤 stage 가 끝나야 구현이 끝났는지는 최신 계획이 답한다. carry 가
42
+ # 가리키는 계획으로 물으면, 계획 수정 뒤 덧붙은 stage 가 아직 남아 있는데도
43
+ # 옛 계획의 마지막 stage 에서 phase 를 완료로 승격한다 (ADR-0015/0016).
41
44
  try:
42
- stage_snapshot = load_task_stage_map(task_root, manifest)
45
+ stage_snapshot = load_latest_plan_stage_map(task_root)
43
46
  except StageMapError as exc:
44
47
  return ImplementationOutcome(
45
48
  completed=False, reason=f"{exc.code}: {exc.reason}"
@@ -30,17 +30,3 @@ SOURCE_WORKERS: tuple[str, ...] = ("claude", "codex", "antigravity")
30
30
  def is_valid_lens(value: str) -> bool:
31
31
  return value in LENSES
32
32
 
33
-
34
- def is_valid_lens_subset(values: list[str]) -> bool:
35
- if not (MIN_PRIORITY_LENSES <= len(values) <= MAX_PRIORITY_LENSES):
36
- return False
37
- if len(set(values)) != len(values):
38
- return False
39
- return all(v in LENSES for v in values)
40
-
41
-
42
- def is_within_candidate_cap(count: int, *, brief_cap: int | None) -> bool:
43
- cap = brief_cap if brief_cap is not None else DEFAULT_CANDIDATE_CAP
44
- if cap < 1 or cap > ABSOLUTE_CANDIDATE_CAP:
45
- return False
46
- return 1 <= count <= cap
@@ -423,8 +423,12 @@ def merge_v3_plan_state(
423
423
  reverify_stages: set[int],
424
424
  ) -> dict[str, Any]:
425
425
  """이전 판정 중 이월 스테이지 소유분과 본문이 같은 체크리스트를 복사한다."""
426
+ # 양쪽 다 작성자 소유 필드만 놓고 비교한다. 한쪽만 거르면 "작성자가
427
+ # 바꿨는가" 가 아니라 "assembly 소유 필드가 실렸는가" 를 재게 되고, 이월을
428
+ # **제대로** 복사한 narrative 일수록 반드시 걸린다 — 복사가 성공할수록
429
+ # 실패하는 검사가 된다.
426
430
  prev_stages = _writer_stage_rows(prev, snapshot="prior")
427
- cur_stages = _stage_rows(_planning(narrative), snapshot="current")
431
+ cur_stages = _writer_stage_rows(narrative, snapshot="current")
428
432
  _validate_stage_sets(prev_stages, cur_stages, carry_stages, reverify_stages)
429
433
  for stage in sorted(carry_stages):
430
434
  current = cur_stages.get(stage)
@@ -481,12 +485,15 @@ def merge_v3_plan_state(
481
485
  return state
482
486
 
483
487
 
484
- def _sync_prepared_dispatch_queue(state_path: Path, merged: Mapping[str, Any]) -> None:
488
+ def sync_prepared_dispatch_queue(state_path: Path, merged: Mapping[str, Any]) -> None:
485
489
  """이월 뒤 프롬프트 큐를 상태 파일과 맞춘다.
486
490
 
487
491
  `plan-items prompt` 는 prepare 가 쓴 `plan-items-*.json` 을 읽는다. 시드가
488
492
  그 파일을 먼저 쓰므로, 이월이 큐를 줄인 뒤에는 형제 파일을 같이 고쳐야
489
493
  워커가 이미 통과한 P-Val 을 다시 보지 않는다.
494
+
495
+ 직전 run 판정 이월(`plan_items_cli._carry_prior_run_verdicts`)도 같은 자리를
496
+ 맞춰야 한다 — 이월이 두 곳에 있으므로 이 동기화도 공개 이름을 쓴다.
490
497
  """
491
498
  name = state_path.name
492
499
  prefix = "plan-body-verification-"
@@ -512,6 +519,27 @@ def _sync_prepared_dispatch_queue(state_path: Path, merged: Mapping[str, Any]) -
512
519
  write_json_atomic(prepared, envelope)
513
520
 
514
521
 
522
+ def _decision_stages(path: str) -> tuple[set[int], set[int]]:
523
+ """판정 레코드가 말하는 두 stage 집합.
524
+
525
+ 리드가 stdout 을 보고 CSV 로 다시 적던 값이다. 그 재입력이 report writer 가
526
+ 받은 지시와 이 병합이 검사하는 범위를 갈라놓을 수 있던 자리이고, 갈리면
527
+ 이월 검사가 엉뚱한 stage 를 본다.
528
+ """
529
+ record = _load_data(path, option="--decision")
530
+ if record.get("mode") != "incremental":
531
+ raise CarryError(
532
+ f"--decision records mode {record.get('mode')!r}; a carry merge only "
533
+ "applies to an incremental decision"
534
+ )
535
+ try:
536
+ carry = {int(stage) for stage in record["carryStages"]}
537
+ reverify = {int(stage) for stage in record["reverifyStages"]}
538
+ except (KeyError, TypeError, ValueError) as exc:
539
+ raise CarryError(f"--decision has no usable stage sets: {exc}") from exc
540
+ return carry, reverify
541
+
542
+
515
543
  def _parse_stage_csv(value: str | None, *, option: str) -> set[int] | None:
516
544
  if value is None:
517
545
  return None
@@ -536,14 +564,39 @@ def _load_data(path: str, *, option: str) -> dict:
536
564
  return data
537
565
 
538
566
 
567
+ _CLI_EPILOG = r"""Usage:
568
+ okstra incremental-carry --prev-data <path> --cur-narrative <path> \
569
+ --state <path> --prev-seq <str> --carry-stages <csv> \
570
+ --reverify-stages <csv> --out-state <path>
571
+ okstra incremental-carry --prev-data <path> --cur-data <path> \
572
+ --prev-seq <str> [--carry-stages <csv> --reverify-stages <csv>] --out <path>
573
+
574
+ Exits non-zero on ownership, scope, or schema drift so the caller can fall back
575
+ to a full re-run.
576
+ """
577
+ _CLI_DESCRIPTION = "Merge carried-forward plan-item verdicts."
578
+
579
+
539
580
  def main(argv: list[str]) -> int:
540
- ap = argparse.ArgumentParser(prog="okstra incremental-carry")
581
+ ap = argparse.ArgumentParser(
582
+ description=_CLI_DESCRIPTION,
583
+ epilog=_CLI_EPILOG,
584
+ formatter_class=argparse.RawDescriptionHelpFormatter,
585
+ prog="okstra incremental-carry")
541
586
  ap.add_argument("--prev-data", required=True, help="prior run final-report data.json")
542
587
  current = ap.add_mutually_exclusive_group(required=True)
543
588
  current.add_argument("--cur-data", help="historical v2 current data.json")
544
589
  current.add_argument("--cur-narrative", help="contract v3 current narrative Markdown")
545
590
  ap.add_argument("--state", help="contract v3 convergence-owned plan state")
546
591
  ap.add_argument("--prev-seq", required=True, help="prior run seq, tagged onto carried items")
592
+ ap.add_argument(
593
+ "--decision",
594
+ default=None,
595
+ help="the `okstra incremental-scope` decision record (the run manifest's "
596
+ "incrementalDecisionPath). Supplies both stage sets, so they are the "
597
+ "same values the report writer's authoring contract was built from. "
598
+ "Cannot be combined with --carry-stages / --reverify-stages",
599
+ )
547
600
  ap.add_argument("--carry-stages", default=None, help="comma-separated carried stage numbers")
548
601
  ap.add_argument("--reverify-stages", default=None, help="comma-separated reverified stage numbers")
549
602
  ap.add_argument("--out", help="historical v2 merged data.json path")
@@ -552,10 +605,18 @@ def main(argv: list[str]) -> int:
552
605
 
553
606
  try:
554
607
  prev = _load_data(args.prev_data, option="--prev-data")
555
- carry_stages = _parse_stage_csv(args.carry_stages, option="--carry-stages")
556
- reverify_stages = _parse_stage_csv(
557
- args.reverify_stages, option="--reverify-stages",
558
- )
608
+ if args.decision is not None:
609
+ if args.carry_stages is not None or args.reverify_stages is not None:
610
+ raise CarryError(
611
+ "--decision already carries both stage sets; remove "
612
+ "--carry-stages / --reverify-stages"
613
+ )
614
+ carry_stages, reverify_stages = _decision_stages(args.decision)
615
+ else:
616
+ carry_stages = _parse_stage_csv(args.carry_stages, option="--carry-stages")
617
+ reverify_stages = _parse_stage_csv(
618
+ args.reverify_stages, option="--reverify-stages",
619
+ )
559
620
  if args.cur_narrative:
560
621
  if not args.state or not args.out_state or args.out:
561
622
  raise CarryError(
@@ -600,7 +661,7 @@ def main(argv: list[str]) -> int:
600
661
 
601
662
  try:
602
663
  write_json_atomic(output, merged)
603
- _sync_prepared_dispatch_queue(output, merged)
664
+ sync_prepared_dispatch_queue(output, merged)
604
665
  except OSError as exc:
605
666
  print(f"carry refused: --out could not be written: {exc}", file=sys.stderr)
606
667
  return 1