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
@@ -1,6 +1,7 @@
1
1
  """Validated JSON loading and atomic persistence for convergence artifacts."""
2
2
  from __future__ import annotations
3
3
 
4
+ import hashlib
4
5
  from pathlib import Path
5
6
  from typing import Any, Mapping
6
7
 
@@ -13,8 +14,6 @@ from .execution_manifest import (
13
14
  )
14
15
  from .final_report_schema import load_named_schema
15
16
  from .json_boundary import JsonBoundaryError, load_owned_object, write_owned_object_atomic
16
- from .worker_request import verifier_extra_dirs
17
- from .write_policy import build_invocation_write_contract
18
17
 
19
18
 
20
19
  DYNAMIC_VERIFIER_SOURCE_ROLES = frozenset({
@@ -107,19 +106,13 @@ def reserve_dynamic_verifier(
107
106
  *,
108
107
  input_digest: str,
109
108
  invocation_ref: str | None = None,
110
- artifact_paths: tuple[Path, ...],
111
- worktree: Path | None = None,
112
109
  ) -> tuple[RoleExecution, Invocation]:
113
110
  """Reserve one provider-neutral verifier identity for a logical round.
114
111
 
115
- `worktree` 는 이 런의 워커가 실제로 서는 루트다. 디스패치가 정책을 다시
116
- 계산할 때 쓰는 값(`dispatch_core._canonical_write_contract` 이 job 의
117
- worktree 를 넘긴다)과 같아야 한다. 여기서 None 으로 고정하면 예약된 정책의
118
- `sourcePolicy.allowedRoot` 는 프로젝트 루트, 디스패치가 계산한 정책은 스테이지
119
- 워크트리가 되어 writePolicyDigest 가 갈리고, 같은 invocationRef 가
120
- `invocationRef drift` 로 거부된다 — 워크트리를 쓰는 런(implementation stage)
121
- 에서만 나타나고 워크트리가 없는 런(implementation-planning)에서는 안 나타나
122
- 버전 문제로 보이기 쉽다.
112
+ 예약은 정체성만 잡는다. 쓰기 계약은 디스패치가 attempt 를 열 때 한 번만
113
+ 계산해 그 attempt 행에 적는다 — 예약이 같은 값을 두 번째로 계산하던 동안,
114
+ 두 계산의 입력(worktree)이 갈리면 같은 invocationRef 가 `invocationRef
115
+ drift` 로 거부됐다.
123
116
  """
124
117
  if round_number < 1:
125
118
  raise ExecutionManifestError("dynamic verifier round must be positive")
@@ -156,20 +149,6 @@ def reserve_dynamic_verifier(
156
149
  raise ExecutionManifestError(
157
150
  "dynamic verifier has no runner write capability"
158
151
  )
159
- try:
160
- policy, enforcement = build_invocation_write_contract(
161
- role="verifier",
162
- project_root=project_root,
163
- worktree=worktree,
164
- artifact_paths=artifact_paths,
165
- maximum_precision=capability.max_boundary_precision,
166
- auxiliary_roots=verifier_extra_dirs("verifier"),
167
- validated_auxiliary_roots=verifier_extra_dirs("verifier"),
168
- )
169
- except (OSError, ValueError) as exc:
170
- raise ExecutionManifestError(
171
- f"dynamic verifier write policy is invalid: {exc}"
172
- ) from exc
173
152
  return Invocation(
174
153
  invocation_ref=selected_ref,
175
154
  participant_ref=verifier.participant_ref,
@@ -180,9 +159,6 @@ def reserve_dynamic_verifier(
180
159
  dispatch_kind=f"reverify-r{round_number}",
181
160
  round=round_number,
182
161
  input_digest=input_digest,
183
- write_policy=policy.to_payload(),
184
- write_policy_digest=policy.digest,
185
- write_enforcement=enforcement.to_payload(),
186
162
  )
187
163
 
188
164
  return reserve_derived_role_invocation(
@@ -53,6 +53,9 @@ _PLANNING_DATA_RE = re.compile(
53
53
  )
54
54
  _REQUEST_STATUSES = {"provisional", "blocked"}
55
55
  _INPUT_DECISIONS = {"accept-draft", "modify-draft", "reject-draft", "defer"}
56
+ _RECORDED_REQUEST_RE = re.compile(
57
+ r"^design-prep-requests/design-prep-request-(?P<token>[0-9]+)-(?P<item>.+)\.md$"
58
+ )
56
59
  _INPUT_FILENAME_RE = re.compile(
57
60
  r"^design-prep-input-(?P<seq>\d+)-(?P<item>PREP-\d{3})-"
58
61
  r"r(?P<revision>\d{3,})-(?P<input_id>[0-9a-f-]{36})\.md$"
@@ -322,12 +325,34 @@ def _request_target(
322
325
  "design-prep-requests/"
323
326
  f"design-prep-request-{planning_seq}-{item_id}.md"
324
327
  )
325
- if item.get("requestPath") != expected_relative:
328
+ request_dir = data_path.parent.parent / "design-prep-requests"
329
+ recorded = item.get("requestPath")
330
+ if recorded == expected_relative:
331
+ target = (data_path.parent.parent / expected_relative).resolve()
332
+ elif isinstance(recorded, str) and recorded.startswith("design-prep-requests/"):
333
+ target = (data_path.parent.parent / recorded).resolve()
334
+ match = _RECORDED_REQUEST_RE.fullmatch(recorded)
335
+ same_item = bool(match and match.group("item") == item_id)
336
+ stages = {
337
+ stage for stage in (item.get("stageRefs") or []) if isinstance(stage, int)
338
+ }
339
+ token_ok = bool(match) and int(match.group("token")) in stages
340
+ if target.is_file() and same_item:
341
+ pass
342
+ elif same_item and token_ok:
343
+ # 스냅샷은 stage 번호를 파일명에 쓴다. 파일이 아직 없어도 그 경로가
344
+ # 정본이다.
345
+ pass
346
+ else:
347
+ raise DesignPrepError(
348
+ f"item {item_id} must use canonical requestPath {expected_relative}"
349
+ )
350
+ else:
326
351
  raise DesignPrepError(
327
352
  f"item {item_id} must use canonical requestPath {expected_relative}"
328
353
  )
329
- request_dir = data_path.parent.parent / "design-prep-requests"
330
- target = (data_path.parent.parent / expected_relative).resolve()
354
+ # resolve 하지 않은 request_dir 과 비교한다. 디렉터리가 바깥을 가리키는
355
+ # 심링크면 target.parent 가 바깥이 되어 탈출을 잡는다.
331
356
  if target.parent != request_dir:
332
357
  raise DesignPrepError(f"requestPath escapes design-prep-requests: {target}")
333
358
  return target
@@ -358,13 +383,54 @@ def _render_request(
358
383
  return target, _complete_request(body, assessment, request_content).encode("utf-8")
359
384
 
360
385
 
361
- def _require_matching_request(target: Path, expected: bytes, conflict: str) -> None:
386
+ # 요청의 내용이 아니라 "어느 리포트가 이 요청을 실었는가" 에 딸린 프론트매터 줄.
387
+ # `request-content-fingerprint` 가 여기 드는 이유는 그 해시의 입력
388
+ # (`_render_request_body_without_fingerprints`) 이 `source-report` 줄을 포함하기
389
+ # 때문이다 — 리포트가 바뀌면 지문도 같이 흔들린다.
390
+ _REPORT_BOUND_REQUEST_FIELDS = ("source-report:", "request-content-fingerprint:")
391
+
392
+
393
+ def _request_identity(raw: bytes) -> bytes:
394
+ """발행 리포트에 딸린 줄을 뺀 요청 신원.
395
+
396
+ Stage Ledger 규칙은 `done` stage 의 본문을 다음 계획 run 으로 그대로
397
+ 이월하도록 요구하고, 그러면 같은 PREP 항목이 매 run 마다 다시 탐지된다.
398
+ 렌더된 요청은 `source-report` 한 줄만 달라지므로, 발행 리포트까지 신원에
399
+ 넣으면 한 태스크의 **두 번째 계획 run 부터 조립이 항상 막힌다** — 요청
400
+ 파일은 태스크 단위 디렉터리에 누적되고 이월은 선택이 아니기 때문이다.
401
+
402
+ 내용의 동일성은 남는 것들이 그대로 덮는다: `assessment-fingerprint` 는
403
+ 항목의 의미 필드만 해시하고(`assessment_fingerprint`), 본문 10개 절은 통째로
404
+ 비교된다. 즉 요청이 실제로 달라지면 여기서 여전히 걸린다.
405
+ """
406
+ try:
407
+ text = raw.decode("utf-8")
408
+ except UnicodeError:
409
+ # 신원을 계산할 수 없는 파일이다. 원본 바이트로 비교하게 두어 다르다고
410
+ # 판정되게 한다 — 읽을 수 없는 파일을 같다고 볼 근거가 없다.
411
+ return raw
412
+ return "\n".join(
413
+ line
414
+ for line in text.splitlines()
415
+ if not line.startswith(_REPORT_BOUND_REQUEST_FIELDS)
416
+ ).encode("utf-8")
417
+
418
+
419
+ def _request_conflicts(target: Path, expected: bytes, conflict: str) -> str | None:
420
+ """Return a one-line conflict description, or ``None`` when the file matches."""
362
421
  try:
363
422
  existing = target.read_bytes()
364
423
  except OSError as exc:
365
424
  raise DesignPrepError(f"cannot read existing request {target}: {exc}") from exc
366
- if existing != expected:
367
- raise DesignPrepError(f"refuses to overwrite {conflict}: {target}")
425
+ if _request_identity(existing) != _request_identity(expected):
426
+ return f"refuses to overwrite {conflict}: {target}"
427
+ return None
428
+
429
+
430
+ def _require_matching_request(target: Path, expected: bytes, conflict: str) -> None:
431
+ reason = _request_conflicts(target, expected, conflict)
432
+ if reason is not None:
433
+ raise DesignPrepError(reason)
368
434
 
369
435
 
370
436
  def _write_request_no_clobber(target: Path, encoded: bytes) -> None:
@@ -397,16 +463,32 @@ def materialize_design_prep_requests(plan_data_path: Path) -> list[Path]:
397
463
  planning_seq = _planning_seq(data_path)
398
464
  report_language = _report_language(data)
399
465
  items = _design_preparation_items(data)
400
- written: list[Path] = []
401
- for item in items:
402
- if item.get("status") not in _REQUEST_STATUSES:
403
- continue
404
- target, encoded = _render_request(
466
+ # 충돌은 한 항목씩 알리지 않는다. 요청 12개가 모두 옛 리포트의 파일과 부딪히면
467
+ # 첫 건에서 멈추는 구현은 사용자에게 12회 왕복을 시키고, 그때마다 이미 쓴
468
+ # 앞쪽 파일이 남아 상태가 절반만 진행된다. 전부 렌더해 전부 검사한 뒤 쓴다.
469
+ rendered = [
470
+ _render_request(
405
471
  data_path=data_path,
406
472
  planning_seq=planning_seq,
407
473
  report_language=report_language,
408
474
  item=item,
409
475
  )
476
+ for item in items
477
+ if item.get("status") in _REQUEST_STATUSES
478
+ ]
479
+ conflicts = [
480
+ reason
481
+ for target, encoded in rendered
482
+ if target.exists()
483
+ and (reason := _request_conflicts(target, encoded, "different request"))
484
+ ]
485
+ if conflicts:
486
+ raise DesignPrepError(
487
+ f"{len(conflicts)} design-prep request(s) conflict with existing files: "
488
+ + "; ".join(conflicts)
489
+ )
490
+ written: list[Path] = []
491
+ for target, encoded in rendered:
410
492
  _write_request_no_clobber(target, encoded)
411
493
  written.append(target)
412
494
  return written
@@ -1367,8 +1449,22 @@ def _discover_planning_reports(project_root: Path) -> list[Path]:
1367
1449
  return sorted(project_root.glob(pattern))
1368
1450
 
1369
1451
 
1452
+ _CLI_EPILOG = r"""Usage:
1453
+ okstra design-prep list [--project-root <path>] [--report <path>]
1454
+ okstra design-prep show --report <path> [--item PREP-001]
1455
+ okstra design-prep write --report <path> --item PREP-001 \
1456
+ --decision <accept-draft|modify-draft|reject-draft|defer> \
1457
+ [--overrides <json-object>] [--notes <text>] --confirmed
1458
+ """
1459
+ _CLI_DESCRIPTION = "Review AI-prepared implementation design inputs."
1460
+
1461
+
1370
1462
  def _build_parser() -> argparse.ArgumentParser:
1371
- parser = argparse.ArgumentParser(prog="design-prep")
1463
+ parser = argparse.ArgumentParser(
1464
+ description=_CLI_DESCRIPTION,
1465
+ epilog=_CLI_EPILOG,
1466
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1467
+ prog="okstra design-prep")
1372
1468
  subparsers = parser.add_subparsers(dest="command", required=True)
1373
1469
  list_parser = subparsers.add_parser("list")
1374
1470
  list_parser.add_argument("--project-root", default="")
@@ -114,8 +114,18 @@ def write_snapshot(narrative_path: Path, output_path: Path) -> dict[str, Any]:
114
114
  return snapshot
115
115
 
116
116
 
117
+ _CLI_EPILOG = r"""Usage:
118
+ okstra design-snapshot --narrative <report-narrative.md> --output <design-preparation.json>
119
+ """
120
+ _CLI_DESCRIPTION = "Write the detector-owned planning snapshot."
121
+
122
+
117
123
  def main(argv: list[str] | None = None) -> int:
118
- parser = argparse.ArgumentParser(prog="okstra design-snapshot")
124
+ parser = argparse.ArgumentParser(
125
+ description=_CLI_DESCRIPTION,
126
+ epilog=_CLI_EPILOG,
127
+ formatter_class=argparse.RawDescriptionHelpFormatter,
128
+ prog="okstra design-snapshot")
119
129
  parser.add_argument("--narrative", type=Path, required=True)
120
130
  parser.add_argument("--output", type=Path, required=True)
121
131
  args = parser.parse_args(argv)
@@ -110,6 +110,34 @@ def _phrase_matches(text: str, phrase: str) -> bool:
110
110
  return re.search(rf"(?<![\w-]){re.escape(phrase)}(?![\w-])", text) is not None
111
111
 
112
112
 
113
+ SELECTED_DIRECTION_CONTRACT = "selected-direction"
114
+
115
+
116
+ def _planned_file_structure(planning: Mapping[str, Any]) -> list[Any]:
117
+ """설계 표면 탐지가 읽는 "이 계획이 건드리는 파일" 목록.
118
+
119
+ 계획서에는 계약이 둘 있고 그 목록의 자리가 다르다. 선택 방향 계약은
120
+ `directionRealization.fileStructure`, 레거시 후보비교 계약은 추천 후보의
121
+ `optionCandidates[].fileStructure` 다.
122
+
123
+ 분기가 이 함수 안이 아니라 호출부 한 곳(`plan_items._extract_prep_items`)에만
124
+ 있던 동안, 같은 탐지기를 부르는 나머지 세 호출부(`design_snapshot`,
125
+ `report_projections`, `validate-run`)는 선택 방향 계획을 레거시 모양으로 읽고
126
+ "recommended option '' is missing or ambiguous" 로 실패했다. 계획은 멀쩡한데
127
+ 최종 검증만 막혔다.
128
+ """
129
+ if planning.get("planningContract") == SELECTED_DIRECTION_CONTRACT:
130
+ realization = planning.get("directionRealization")
131
+ if not isinstance(realization, Mapping):
132
+ raise DesignSurfaceError(
133
+ "selected-direction plan has no directionRealization object"
134
+ )
135
+ rows = realization.get("fileStructure")
136
+ return list(rows) if isinstance(rows, list) else []
137
+ rows = _selected_candidate(planning).get("fileStructure")
138
+ return list(rows) if isinstance(rows, list) else []
139
+
140
+
113
141
  def _selected_candidate(planning: Mapping[str, Any]) -> Mapping[str, Any]:
114
142
  recommended = _normalise((planning.get("recommendedOption") or {}).get("name"))
115
143
  candidates = [
@@ -321,8 +349,7 @@ def detect_design_surfaces(
321
349
  value=step.get(field),
322
350
  ):
323
351
  grouped.setdefault((stage_number, kind), []).append(evidence)
324
- selected = _selected_candidate(planning)
325
- for row in selected.get("fileStructure") or []:
352
+ for row in _planned_file_structure(planning):
326
353
  if not isinstance(row, Mapping):
327
354
  continue
328
355
  path = str(row.get("path") or "")
@@ -27,6 +27,7 @@ from .dispatch_state import (
27
27
  load_json_object as _load_json_object,
28
28
  link_agent_dispatch_result as _link_agent_dispatch_result,
29
29
  missing_completion_paths as _missing_completion_paths,
30
+ _plan_verify_result_aliases,
30
31
  mutate_team_state as _mutate_team_state,
31
32
  require_string as _require_string,
32
33
  resolve_project_path as _resolve_project_path,
@@ -54,7 +55,7 @@ from .assignment_resolver import (
54
55
  from .application.open_worker import open_worker
55
56
  from .domain.host import HostSessionContext
56
57
  from .domain.provider import HostModelBinding, ServedModelAttestation
57
- from .domain.worker_exec import SERVED_MODEL_MISMATCH_EXIT_CODE
58
+ from .domain.worker_exec import NO_RESULT_EXIT_CODE, SERVED_MODEL_MISMATCH_EXIT_CODE
58
59
  from .domain.worker_runtime import (
59
60
  SURFACE_CMUX_PANE,
60
61
  EnvironmentBlocked,
@@ -90,7 +91,7 @@ from .initial_prompt_materialization import (
90
91
  PromptDeliveryMode,
91
92
  materialize_initial_prompts,
92
93
  )
93
- from .agent_invocation import AgentInvocationError, materialize_retry_invocation
94
+ from .agent.invocation import AgentInvocationError, materialize_retry_invocation
94
95
  from .path_hints import hydrate_active_run_context
95
96
  from .schema_excerpt import (
96
97
  bundle_excerpt_path,
@@ -102,6 +103,7 @@ from .worker_audit_ledger import (
102
103
  check_worker_results_audit,
103
104
  parse_worker_result_name,
104
105
  )
106
+ from .worker_artifacts import worker_provider_id
105
107
  from .worker_prompt_body import REPORT_WRITER_WORKER_ID
106
108
  from .worker_prompt_headers import (
107
109
  WorkerPromptHeaderError,
@@ -121,6 +123,7 @@ from .write_policy import (
121
123
  WritePolicy,
122
124
  build_invocation_write_contract,
123
125
  planned_paths_from_run_manifest,
126
+ task_root_from_run_manifest,
124
127
  write_enforcement_from_payload,
125
128
  write_policy_from_payload,
126
129
  )
@@ -914,24 +917,32 @@ def _select_workers(
914
917
  options,
915
918
  )
916
919
  supported = set(options.supported_worker_wrappers)
920
+ state = team_state or {}
921
+
922
+ def _wrapper_supported(worker_id: str) -> bool:
923
+ return _worker_provider(state, worker_id, options) in supported
924
+
917
925
  selected = (
918
926
  list(requested_workers)
919
927
  if requested_workers
920
928
  else [
921
929
  worker
922
930
  for worker in recommended
923
- if worker in supported and worker != REPORT_WRITER_WORKER_ID
931
+ if _wrapper_supported(worker) and worker != REPORT_WRITER_WORKER_ID
924
932
  ]
925
933
  )
926
934
  unknown = [worker for worker in selected if worker not in recommended]
927
935
  if unknown:
928
936
  raise DispatchError("requested worker(s) are not in this run roster: " + ", ".join(unknown))
929
- unsupported = [worker for worker in selected if worker not in supported]
937
+ unsupported = [worker for worker in selected if not _wrapper_supported(worker)]
930
938
  if unsupported:
931
939
  allowed = ", ".join(sorted(supported))
932
940
  raise DispatchError(
933
941
  f"unsupported {options.unsupported_worker_label} worker(s): "
934
- + ", ".join(unsupported)
942
+ + ", ".join(
943
+ f"{worker} (provider {_worker_provider(state, worker, options)})"
944
+ for worker in unsupported
945
+ )
935
946
  + f" (dispatcher supports: {allowed})"
936
947
  )
937
948
  return selected
@@ -1162,13 +1173,11 @@ def _is_cli_wrapper_assignment(
1162
1173
  options: _BuildOptions,
1163
1174
  ) -> bool:
1164
1175
  state = _worker_state(team_state, worker_id)
1165
- provider = _string_value(state.get("provider")) or _provider_for_worker(
1166
- worker_id,
1167
- options,
1168
- )
1169
1176
  return (
1170
1177
  _string_value(state.get("runner")) or BACKEND_CLI_WRAPPER
1171
- ) == BACKEND_CLI_WRAPPER and provider in options.supported_worker_wrappers
1178
+ ) == BACKEND_CLI_WRAPPER and _worker_provider(
1179
+ team_state, worker_id, options
1180
+ ) in options.supported_worker_wrappers
1172
1181
 
1173
1182
 
1174
1183
  def _runner_for_worker(team_state: Mapping[str, Any], worker_id: str) -> str:
@@ -1203,6 +1212,7 @@ def _job_from_roster_worker(
1203
1212
  completion_paths=_completion_paths(
1204
1213
  fact.worker_id, result_path, manifest, project_root
1205
1214
  ),
1215
+ result_aliases=_plan_verify_result_aliases(result_path, manifest),
1206
1216
  worktree_path=_worktree_path(manifest, active_context),
1207
1217
  role=fact.role,
1208
1218
  idle_timeout_seconds=options.idle_timeout_seconds,
@@ -1484,26 +1494,28 @@ def _persisted_write_contract(
1484
1494
  if not job.has_execution_identity:
1485
1495
  return None
1486
1496
  manifest = read_execution_manifest(plan.manifest_path)
1487
- invocation = next(
1497
+ # 계약은 attempt 소유다 — 재시도는 attempt 마다 다른 프롬프트 파생 파일을
1498
+ # 쓰므로, invocation 으로 찾으면 1회차의 허용 경로를 2회차에 물린다.
1499
+ attempt = next(
1488
1500
  (
1489
- row for row in manifest.invocations
1490
- if row.invocation_ref == job.invocation_ref
1501
+ row for row in manifest.attempts
1502
+ if row.invocation_ref == job.invocation_ref and row.attempt == job.attempt
1491
1503
  ),
1492
1504
  None,
1493
1505
  )
1494
- if invocation is None:
1495
- raise DispatchError("worker invocation write policy was not persisted")
1506
+ if attempt is None:
1507
+ raise DispatchError("worker attempt write policy was not persisted")
1496
1508
  try:
1497
1509
  enforcement = write_enforcement_from_payload(
1498
- invocation.write_enforcement
1510
+ attempt.write_enforcement
1499
1511
  )
1500
1512
  policy = replace(
1501
- write_policy_from_payload(invocation.write_policy),
1513
+ write_policy_from_payload(attempt.write_policy),
1502
1514
  maximum_boundary_precision=enforcement.boundary_precision,
1503
1515
  )
1504
1516
  except (TypeError, ValueError) as exc:
1505
1517
  raise DispatchError(f"persisted worker write policy is invalid: {exc}") from exc
1506
- if policy.digest != invocation.write_policy_digest:
1518
+ if policy.digest != attempt.write_policy_digest:
1507
1519
  raise DispatchError("persisted worker write policy digest changed")
1508
1520
  return policy, enforcement
1509
1521
 
@@ -1555,9 +1567,6 @@ def _record_execution_attempt(plan: DispatchPlan, job: WorkerJob) -> None:
1555
1567
  dispatch_kind=job.dispatch_kind,
1556
1568
  round=_dispatch_round(job.dispatch_kind),
1557
1569
  input_digest=job.prompt_digest,
1558
- write_policy=write_policy.to_payload(),
1559
- write_policy_digest=write_policy.digest,
1560
- write_enforcement=write_enforcement.to_payload(),
1561
1570
  ),
1562
1571
  Attempt(
1563
1572
  invocation_ref=job.invocation_ref,
@@ -1569,6 +1578,9 @@ def _record_execution_attempt(plan: DispatchPlan, job: WorkerJob) -> None:
1569
1578
  error_path=None,
1570
1579
  change_summary={},
1571
1580
  evidence_seal_ref=None,
1581
+ write_policy=write_policy.to_payload(),
1582
+ write_policy_digest=write_policy.digest,
1583
+ write_enforcement=write_enforcement.to_payload(),
1572
1584
  ),
1573
1585
  task_key=task_key,
1574
1586
  )
@@ -1603,6 +1615,9 @@ def _canonical_write_contract(
1603
1615
  project_root=plan.project_root,
1604
1616
  worktree=worktree,
1605
1617
  artifact_paths=artifacts,
1618
+ task_root=task_root_from_run_manifest(
1619
+ plan.project_root, plan.manifest
1620
+ ),
1606
1621
  maximum_precision=capability.max_boundary_precision,
1607
1622
  planned_paths=planned[0],
1608
1623
  planned_paths_declared=planned[1],
@@ -2573,6 +2588,7 @@ def _dispatch_record(
2573
2588
  "modelExecutionValue": job.model_execution_value,
2574
2589
  "wrapperPath": str(job.wrapper_path),
2575
2590
  "completionPaths": [str(path) for path in job.completion_paths],
2591
+ "resultAliases": [str(path) for path in job.result_aliases],
2576
2592
  "worktreePath": job.worktree_path,
2577
2593
  }
2578
2594
  if job.invocation_id:
@@ -2713,12 +2729,14 @@ def _skip_reasons(
2713
2729
  if worker_id not in set(selected)
2714
2730
  }
2715
2731
  supported = set(options.supported_worker_wrappers)
2732
+ state = team_state or {}
2716
2733
  return {
2717
2734
  worker_id: (
2718
2735
  "skipped by worker dispatch default: worker is not supported by this dispatcher"
2719
2736
  )
2720
2737
  for worker_id in recommended
2721
- if worker_id not in set(selected) and worker_id not in supported
2738
+ if worker_id not in set(selected)
2739
+ and _worker_provider(state, worker_id, options) not in supported
2722
2740
  }
2723
2741
 
2724
2742
 
@@ -2857,7 +2875,7 @@ def _retry_details(job: WorkerJob, attempt: int, outcome: WorkerOutcome) -> dict
2857
2875
  def _outcome_from_status(record: Mapping[str, Any], status) -> WorkerOutcome:
2858
2876
  return WorkerOutcome(
2859
2877
  returncode=status.exit_code if status.exit_code is not None else 1,
2860
- missing_completion_paths=tuple(path for path in _record_completion_paths(record) if not path.is_file()),
2878
+ missing_completion_paths=_record_missing_completion_paths(record),
2861
2879
  pane_id=_string_value(record.get("paneId")),
2862
2880
  status_sidecar_path=status.path,
2863
2881
  timeout=status.timeout,
@@ -2925,8 +2943,43 @@ def _record_completion_paths(record: Mapping[str, Any]) -> list[Path]:
2925
2943
  return [Path(path) for path in _string_list(record.get("completionPaths"))]
2926
2944
 
2927
2945
 
2946
+ def _record_missing_completion_paths(record: Mapping[str, Any]) -> tuple[Path, ...]:
2947
+ aliases = {Path(path) for path in _string_list(record.get("resultAliases"))}
2948
+ result_path = Path(_string_value(record.get("resultPath")))
2949
+ worker_result = Path(_string_value(record.get("workerResultPath")))
2950
+ missing: list[Path] = []
2951
+ for path in _record_completion_paths(record):
2952
+ if path.is_file():
2953
+ continue
2954
+ if path in {result_path, worker_result} and any(alias.is_file() for alias in aliases):
2955
+ continue
2956
+ missing.append(path)
2957
+ return tuple(missing)
2958
+
2959
+
2928
2960
  def _should_retry(outcome: WorkerOutcome, attempt: int) -> bool:
2929
- return outcome.returncode == 0 and bool(outcome.missing_completion_paths) and attempt < MAX_WORKER_ATTEMPTS
2961
+ """결과가 없으면 재시도한다 — 기준은 종료 코드가 아니라 산출물이다.
2962
+
2963
+ 종전에는 `returncode == 0` 을 요구했다. 그래서 시작하자마자 죽은 CLI 는
2964
+ 재시도를 **한 번도** 못 받았고, 150초를 태우고 0 으로 끝난 CLI 만 받았다.
2965
+ 비용 순서가 거꾸로였다.
2966
+
2967
+ 실측(fontsninja-v3-site `dev-10341`, grok 워커 13회 디스패치): 세션 생성이
2968
+ 거부돼 1.6~4.7초 만에 코드 1 로 죽은 경우가 4회다. 그중
2969
+ `requirements-discovery-002` 는 그것이 1차 시도였는데 재시도가 없어 분석자
2970
+ 한 명이 그대로 사라졌고, 바로 다음 디스패치(`-003`)는 222초를 정상 수행했다.
2971
+ 같은 명령을 지금 돌리면 동시 3회까지 전부 성공한다 — 조건은 분 단위로
2972
+ 풀리는 일시적인 것이고, 재시도 비용은 2초다.
2973
+
2974
+ 산출물이 이미 나왔으면 종료 코드가 무엇이든 재시도하지 않는다. 일이 끝났기
2975
+ 때문이다. 타임아웃도 재시도하지 않는다 — 그것은 예산을 다 쓴 실패라
2976
+ 재시도가 같은 예산을 한 번 더 쓴다.
2977
+ """
2978
+ if outcome.timeout:
2979
+ return False
2980
+ if attempt >= MAX_WORKER_ATTEMPTS:
2981
+ return False
2982
+ return bool(outcome.missing_completion_paths)
2930
2983
 
2931
2984
 
2932
2985
  def _job_for_next_attempt(job: WorkerJob) -> WorkerJob:
@@ -2946,6 +2999,8 @@ def _job_for_next_attempt(job: WorkerJob) -> WorkerJob:
2946
2999
  def _failure_reason(outcome: WorkerOutcome) -> str:
2947
3000
  if outcome.timeout:
2948
3001
  return "worker wrapper timed out"
3002
+ if outcome.returncode == NO_RESULT_EXIT_CODE:
3003
+ return "CLI ended cleanly without emitting a result event"
2949
3004
  if outcome.returncode != 0:
2950
3005
  return f"wrapper exited with code {outcome.returncode}"
2951
3006
  if outcome.missing_completion_paths:
@@ -2982,6 +3037,28 @@ def _resolve_wrapper(provider: str, workspace_root: Path, options: _BuildOptions
2982
3037
  raise DispatchError(f"{script} not found (searched: {', '.join(str(c) for c in candidates)})")
2983
3038
 
2984
3039
 
3040
+ def _worker_provider(
3041
+ team_state: Mapping[str, Any],
3042
+ worker_id: str,
3043
+ options: _BuildOptions | None = None,
3044
+ ) -> str:
3045
+ """이 슬롯이 실제로 실행할 provider.
3046
+
3047
+ 로스터의 worker_id 는 provider 이름이 아니다. 한 provider 가 두 역할을 맡으면
3048
+ `grok-implementer` · `grok-verifier` 처럼 역할 접미사가 붙기 때문이다. 지원 여부는
3049
+ 항상 이 해석을 거친 provider 로 판정한다 — id 를 provider 집합과 직접 비교하면
3050
+ 역할 접미사가 붙은 슬롯이 전부 미지원으로 떨어진다.
3051
+ """
3052
+ workers = team_state.get("workers")
3053
+ recorded = ""
3054
+ if isinstance(workers, list):
3055
+ for worker in workers:
3056
+ if isinstance(worker, Mapping) and worker.get("workerId") == worker_id:
3057
+ recorded = _string_value(worker.get("provider"))
3058
+ break
3059
+ return recorded or _provider_for_worker(worker_id, options)
3060
+
3061
+
2985
3062
  def _provider_for_worker(
2986
3063
  worker_id: str,
2987
3064
  options: _BuildOptions | None = None,
@@ -2992,7 +3069,7 @@ def _provider_for_worker(
2992
3069
  return configured
2993
3070
  if worker_id == REPORT_WRITER_WORKER_ID:
2994
3071
  return "claude"
2995
- return worker_id
3072
+ return worker_provider_id(worker_id)
2996
3073
 
2997
3074
 
2998
3075