okstra 0.186.7 → 0.187.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (541) hide show
  1. package/README.md +1 -1
  2. package/dist/cli-registry.d.mts +88 -1
  3. package/dist/cli-registry.mjs +68 -111
  4. package/dist/cli-registry.mjs.map +1 -1
  5. package/dist/commands/execute/render-bundle.mjs +0 -1
  6. package/dist/commands/execute/render-bundle.mjs.map +1 -1
  7. package/dist/commands/execute/run.mjs +8 -3
  8. package/dist/commands/execute/run.mjs.map +1 -1
  9. package/dist/commands/lifecycle/check-project.mjs +1 -14
  10. package/dist/commands/lifecycle/check-project.mjs.map +1 -1
  11. package/dist/commands/lifecycle/config.mjs +38 -40
  12. package/dist/commands/lifecycle/config.mjs.map +1 -1
  13. package/dist/commands/lifecycle/doctor.d.mts +22 -7
  14. package/dist/commands/lifecycle/doctor.mjs +77 -49
  15. package/dist/commands/lifecycle/doctor.mjs.map +1 -1
  16. package/dist/commands/lifecycle/install.d.mts +12 -10
  17. package/dist/commands/lifecycle/install.mjs +104 -39
  18. package/dist/commands/lifecycle/install.mjs.map +1 -1
  19. package/dist/commands/lifecycle/paths.mjs +8 -3
  20. package/dist/commands/lifecycle/paths.mjs.map +1 -1
  21. package/dist/commands/lifecycle/preflight.mjs +2 -1
  22. package/dist/commands/lifecycle/preflight.mjs.map +1 -1
  23. package/dist/commands/lifecycle/setup.mjs +22 -36
  24. package/dist/commands/lifecycle/setup.mjs.map +1 -1
  25. package/dist/commands/lifecycle/uninstall.d.mts +4 -2
  26. package/dist/commands/lifecycle/uninstall.mjs +59 -15
  27. package/dist/commands/lifecycle/uninstall.mjs.map +1 -1
  28. package/dist/commands/memory/memory.mjs +7 -1
  29. package/dist/commands/memory/memory.mjs.map +1 -1
  30. package/dist/lib/helper-scripts.d.mts +1 -1
  31. package/dist/lib/helper-scripts.mjs +10 -18
  32. package/dist/lib/helper-scripts.mjs.map +1 -1
  33. package/dist/lib/host-config.d.mts +72 -0
  34. package/dist/lib/host-config.mjs +404 -0
  35. package/dist/lib/host-config.mjs.map +1 -0
  36. package/dist/lib/host-registry-client.d.mts +2 -2
  37. package/dist/lib/host-registry-client.mjs +0 -3
  38. package/dist/lib/host-registry-client.mjs.map +1 -1
  39. package/dist/lib/install-assets.d.mts +1 -0
  40. package/dist/lib/install-assets.mjs +4 -0
  41. package/dist/lib/install-assets.mjs.map +1 -1
  42. package/dist/lib/proc.d.mts +2 -0
  43. package/dist/lib/proc.mjs +12 -0
  44. package/dist/lib/proc.mjs.map +1 -1
  45. package/dist/lib/python-command.d.mts +2 -0
  46. package/dist/lib/python-command.mjs +52 -0
  47. package/dist/lib/python-command.mjs.map +1 -0
  48. package/dist/lib/python-helper.d.mts +2 -5
  49. package/dist/lib/python-helper.mjs +3 -52
  50. package/dist/lib/python-helper.mjs.map +1 -1
  51. package/dist/lib/runtime-payload.d.mts +23 -0
  52. package/dist/lib/runtime-payload.mjs +57 -0
  53. package/dist/lib/runtime-payload.mjs.map +1 -0
  54. package/dist/lib/types.d.mts +24 -13
  55. package/docs/architecture/storage-model.md +21 -5
  56. package/docs/architecture.md +37 -36
  57. package/docs/cli.md +55 -76
  58. package/docs/coding-rules.md +295 -0
  59. package/docs/container.md +10 -36
  60. package/docs/contributor-change-matrix.md +1 -1
  61. package/docs/for-ai/skills/okstra-code-review.md +0 -1
  62. package/docs/for-ai/skills/okstra-container-build.md +8 -41
  63. package/docs/for-ai/skills/okstra-inspect.md +4 -4
  64. package/docs/for-ai/skills/okstra-manager.md +2 -2
  65. package/docs/for-ai/skills/okstra-rollup.md +0 -1
  66. package/docs/for-ai/skills/okstra-run.md +3 -3
  67. package/docs/for-ai/skills/okstra-user-response.md +0 -1
  68. package/docs/performance-improvement-plan-v2.md +1 -1
  69. package/docs/project-structure-overview.md +62 -62
  70. package/docs/task-process/README.md +3 -3
  71. package/docs/task-process/common-flow.md +1 -1
  72. package/docs/task-process/error-analysis.md +3 -3
  73. package/docs/task-process/implementation-planning.md +3 -1
  74. package/docs/task-process/release-handoff.md +1 -1
  75. package/package.json +3 -2
  76. package/runtime/BUILD.json +2 -2
  77. package/runtime/agents/workers/claude-worker.md +6 -11
  78. package/runtime/agents/workers/report-writer-worker.md +1 -1
  79. package/runtime/bin/lib/okstra/cli.sh +4 -0
  80. package/runtime/bin/lib/okstra/globals.sh +2 -0
  81. package/runtime/bin/lib/okstra/interactive.sh +26 -173
  82. package/runtime/bin/lib/okstra/project-resolver.sh +23 -61
  83. package/runtime/bin/lib/okstra/usage.sh +3 -3
  84. package/runtime/bin/okstra-compact-reminder.sh +1 -5
  85. package/runtime/bin/okstra-error-log.py +19 -2
  86. package/runtime/bin/okstra-import-check.py +26 -0
  87. package/runtime/bin/okstra-inject-report-index.py +5 -4
  88. package/runtime/bin/okstra-provider-exec.py +22 -17
  89. package/runtime/bin/okstra-render-final-report.py +16 -6
  90. package/runtime/bin/okstra-render-report-views.py +25 -18
  91. package/runtime/bin/okstra-report-translate.py +30 -18
  92. package/runtime/bin/okstra-spawn-followups.py +4 -0
  93. package/runtime/bin/okstra-token-usage.py +3 -0
  94. package/runtime/bin/okstra.sh +4 -2
  95. package/runtime/bin/okstra_bootstrap.py +58 -0
  96. package/runtime/prompts/coding-preflight/overview.md +1 -1
  97. package/runtime/prompts/duties/planning-worker.md +1 -1
  98. package/runtime/prompts/launch.template.md +35 -9
  99. package/runtime/prompts/lead/convergence.md +96 -87
  100. package/runtime/prompts/lead/okstra-lead-contract.md +63 -12
  101. package/runtime/prompts/lead/plan-body-verification.md +65 -46
  102. package/runtime/prompts/lead/report-writer.md +34 -4
  103. package/runtime/prompts/lead/team-contract.md +4 -1
  104. package/runtime/prompts/profiles/_clarification-recommendation.md +2 -1
  105. package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
  106. package/runtime/prompts/profiles/_common-contract.md +3 -3
  107. package/runtime/prompts/profiles/_coverage-critic.md +1 -1
  108. package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
  109. package/runtime/prompts/profiles/_implementation-executor.md +3 -1
  110. package/runtime/prompts/profiles/_implementation-verifier.md +16 -2
  111. package/runtime/prompts/profiles/error-analysis.md +4 -3
  112. package/runtime/prompts/profiles/final-verification.md +2 -2
  113. package/runtime/prompts/profiles/implementation-planning.md +22 -17
  114. package/runtime/prompts/profiles/implementation.md +3 -2
  115. package/runtime/prompts/profiles/requirements-discovery.md +2 -1
  116. package/runtime/prompts/wizard/prompts.ko.json +29 -8
  117. package/runtime/python/okstra_ctl/__init__.py +6 -27
  118. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +4 -5
  119. package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +21 -2
  120. package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +3 -12
  121. package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +40 -22
  122. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +14 -21
  123. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +37 -20
  124. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +86 -14
  125. package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +12 -12
  126. package/runtime/python/okstra_ctl/adapters/runtime/cmux.py +4 -7
  127. package/runtime/python/okstra_ctl/agent/__init__.py +1 -0
  128. package/runtime/python/okstra_ctl/{agent_activity.py → agent/activity.py} +12 -1
  129. package/runtime/python/okstra_ctl/{agent_invocation.py → agent/invocation.py} +34 -5
  130. package/runtime/python/okstra_ctl/agent/prompt_cli/__init__.py +19 -0
  131. package/runtime/python/okstra_ctl/agent/prompt_cli/__main__.py +11 -0
  132. package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +215 -0
  133. package/runtime/python/okstra_ctl/agent/prompt_cli/dynamic_verifier.py +155 -0
  134. package/runtime/python/okstra_ctl/agent/prompt_cli/emit.py +66 -0
  135. package/runtime/python/okstra_ctl/agent/prompt_cli/inputs.py +138 -0
  136. package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +408 -0
  137. package/runtime/python/okstra_ctl/agent/prompt_cli/results.py +197 -0
  138. package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +107 -0
  139. package/runtime/python/okstra_ctl/analysis_packet.py +47 -4
  140. package/runtime/python/okstra_ctl/approval_decisions.py +260 -8
  141. package/runtime/python/okstra_ctl/assignment_resolver.py +0 -12
  142. package/runtime/python/okstra_ctl/attempt_evidence.py +12 -24
  143. package/runtime/python/okstra_ctl/blocking_checks.py +251 -0
  144. package/runtime/python/okstra_ctl/brief_frontmatter.py +0 -7
  145. package/runtime/python/okstra_ctl/clarification_items/__init__.py +102 -0
  146. package/runtime/python/okstra_ctl/clarification_items/carry.py +224 -0
  147. package/runtime/python/okstra_ctl/clarification_items/dispositions.py +135 -0
  148. package/runtime/python/okstra_ctl/clarification_items/parsing.py +253 -0
  149. package/runtime/python/okstra_ctl/clarification_items/rows.py +115 -0
  150. package/runtime/python/okstra_ctl/clarification_items/scan.py +215 -0
  151. package/runtime/python/okstra_ctl/clarification_items/sidecars.py +211 -0
  152. package/runtime/python/okstra_ctl/cmux.py +38 -31
  153. package/runtime/python/okstra_ctl/code_review_target.py +163 -1
  154. package/runtime/python/okstra_ctl/conformance.py +23 -0
  155. package/runtime/python/okstra_ctl/container.py +38 -421
  156. package/runtime/python/okstra_ctl/context_cost.py +16 -2
  157. package/runtime/python/okstra_ctl/contract_graph_cli.py +7 -0
  158. package/runtime/python/okstra_ctl/convergence.py +101 -15
  159. package/runtime/python/okstra_ctl/convergence_critic_prompt.py +345 -0
  160. package/runtime/python/okstra_ctl/convergence_engine.py +382 -70
  161. package/runtime/python/okstra_ctl/convergence_store.py +5 -29
  162. package/runtime/python/okstra_ctl/design_prep.py +108 -12
  163. package/runtime/python/okstra_ctl/design_snapshot.py +11 -1
  164. package/runtime/python/okstra_ctl/design_surfaces.py +29 -2
  165. package/runtime/python/okstra_ctl/dispatch_core.py +102 -25
  166. package/runtime/python/okstra_ctl/dispatch_state.py +268 -39
  167. package/runtime/python/okstra_ctl/doctor_cli.py +48 -0
  168. package/runtime/python/okstra_ctl/domain/worker_exec.py +10 -5
  169. package/runtime/python/okstra_ctl/domain/worker_presentation.py +21 -1
  170. package/runtime/python/okstra_ctl/domain/worker_stream.py +52 -21
  171. package/runtime/python/okstra_ctl/domain/write_policy.py +219 -0
  172. package/runtime/python/okstra_ctl/entrypoints/hosts.py +9 -2
  173. package/runtime/python/okstra_ctl/error_log_core.py +1 -1
  174. package/runtime/python/okstra_ctl/error_report.py +14 -0
  175. package/runtime/python/okstra_ctl/error_zip.py +18 -2
  176. package/runtime/python/okstra_ctl/execution_identity.py +100 -7
  177. package/runtime/python/okstra_ctl/execution_manifest.py +67 -20
  178. package/runtime/python/okstra_ctl/execution_mutation_audit.py +90 -8
  179. package/runtime/python/okstra_ctl/final_report_paths.py +17 -0
  180. package/runtime/python/okstra_ctl/final_report_schema.py +90 -16
  181. package/runtime/python/okstra_ctl/git_reconcile.py +22 -2
  182. package/runtime/python/okstra_ctl/handoff.py +24 -1
  183. package/runtime/python/okstra_ctl/ids.py +6 -16
  184. package/runtime/python/okstra_ctl/implementation_direction.py +142 -33
  185. package/runtime/python/okstra_ctl/implementation_options.py +25 -11
  186. package/runtime/python/okstra_ctl/implementation_outcome.py +5 -2
  187. package/runtime/python/okstra_ctl/improvement_lenses.py +0 -14
  188. package/runtime/python/okstra_ctl/incremental_carry.py +69 -8
  189. package/runtime/python/okstra_ctl/incremental_scope.py +173 -12
  190. package/runtime/python/okstra_ctl/index.py +2 -2
  191. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +48 -8
  192. package/runtime/python/okstra_ctl/interactive_cli.py +223 -0
  193. package/runtime/python/okstra_ctl/json_boundary.py +10 -0
  194. package/runtime/python/okstra_ctl/listing.py +0 -91
  195. package/runtime/python/okstra_ctl/locks.py +5 -19
  196. package/runtime/python/okstra_ctl/log_report.py +14 -0
  197. package/runtime/python/okstra_ctl/manager_cli.py +22 -3
  198. package/runtime/python/okstra_ctl/manager_launch.py +4 -8
  199. package/runtime/python/okstra_ctl/material.py +2 -2
  200. package/runtime/python/okstra_ctl/migrate.py +23 -1
  201. package/runtime/python/okstra_ctl/model_cli.py +19 -5
  202. package/runtime/python/okstra_ctl/model_discovery.py +46 -47
  203. package/runtime/python/okstra_ctl/model_io/__init__.py +1 -0
  204. package/runtime/python/okstra_ctl/model_io/lines.py +163 -0
  205. package/runtime/python/okstra_ctl/model_io/references.py +370 -0
  206. package/runtime/python/okstra_ctl/model_io/renderers.py +498 -0
  207. package/runtime/python/okstra_ctl/model_io_cli.py +30 -945
  208. package/runtime/python/okstra_ctl/model_pool.py +10 -6
  209. package/runtime/python/okstra_ctl/models.py +8 -8
  210. package/runtime/python/okstra_ctl/mutation_recovery.py +5 -63
  211. package/runtime/python/okstra_ctl/next_phase.py +121 -69
  212. package/runtime/python/okstra_ctl/pane_reclaim.py +23 -0
  213. package/runtime/python/okstra_ctl/pane_title.py +11 -8
  214. package/runtime/python/okstra_ctl/path_hints.py +36 -11
  215. package/runtime/python/okstra_ctl/paths.py +76 -8
  216. package/runtime/python/okstra_ctl/plan_items.py +132 -16
  217. package/runtime/python/okstra_ctl/plan_items_cli.py +486 -36
  218. package/runtime/python/okstra_ctl/plan_run_root.py +1 -1
  219. package/runtime/python/okstra_ctl/plan_validate_cli.py +51 -0
  220. package/runtime/python/okstra_ctl/plan_verify_cli.py +80 -0
  221. package/runtime/python/okstra_ctl/prepare_error.py +20 -0
  222. package/runtime/python/okstra_ctl/prior_planning.py +157 -0
  223. package/runtime/python/okstra_ctl/profile_show.py +18 -1
  224. package/runtime/python/okstra_ctl/project_setup_cli.py +140 -0
  225. package/runtime/python/okstra_ctl/recap.py +22 -1
  226. package/runtime/python/okstra_ctl/reconcile.py +84 -0
  227. package/runtime/python/okstra_ctl/registry/host_registry.py +18 -1
  228. package/runtime/python/okstra_ctl/render.py +98 -32
  229. package/runtime/python/okstra_ctl/render_final_report.py +3 -7
  230. package/runtime/python/okstra_ctl/report_assembly.py +297 -7
  231. package/runtime/python/okstra_ctl/report_contract.py +30 -7
  232. package/runtime/python/okstra_ctl/report_finalize.py +206 -15
  233. package/runtime/python/okstra_ctl/report_html/common.py +2 -2
  234. package/runtime/python/okstra_ctl/report_html/render.py +8 -12
  235. package/runtime/python/okstra_ctl/report_language.py +8 -4
  236. package/runtime/python/okstra_ctl/report_narrative.py +90 -3
  237. package/runtime/python/okstra_ctl/report_projections.py +8 -2
  238. package/runtime/python/okstra_ctl/report_synthesis_packet.py +258 -1
  239. package/runtime/python/okstra_ctl/report_translation.py +2 -35
  240. package/runtime/python/okstra_ctl/report_views.py +4 -22
  241. package/runtime/python/okstra_ctl/resolve_task_key.py +13 -0
  242. package/runtime/python/okstra_ctl/rollup.py +13 -0
  243. package/runtime/python/okstra_ctl/run.py +639 -138
  244. package/runtime/python/okstra_ctl/run_audit.py +13 -0
  245. package/runtime/python/okstra_ctl/run_index_row.py +0 -12
  246. package/runtime/python/okstra_ctl/seeding.py +21 -11
  247. package/runtime/python/okstra_ctl/sequence.py +1 -1
  248. package/runtime/python/okstra_ctl/session.py +64 -14
  249. package/runtime/python/okstra_ctl/set_work_status.py +18 -0
  250. package/runtime/python/okstra_ctl/stage_integrate.py +15 -1
  251. package/runtime/python/okstra_ctl/stage_ledger.py +1 -1
  252. package/runtime/python/okstra_ctl/stage_map.py +57 -11
  253. package/runtime/python/okstra_ctl/stage_map_cli.py +83 -0
  254. package/runtime/python/okstra_ctl/stage_map_view.py +65 -0
  255. package/runtime/python/okstra_ctl/stage_targets.py +3 -12
  256. package/runtime/python/okstra_ctl/task_list_cli.py +138 -0
  257. package/runtime/python/okstra_ctl/task_show_cli.py +66 -0
  258. package/runtime/python/okstra_ctl/task_target.py +4 -16
  259. package/runtime/python/okstra_ctl/team.py +49 -2
  260. package/runtime/python/okstra_ctl/time_report.py +17 -2
  261. package/runtime/python/okstra_ctl/usage_report.py +11 -0
  262. package/runtime/python/okstra_ctl/user_response.py +55 -218
  263. package/runtime/python/okstra_ctl/user_response_values.py +242 -0
  264. package/runtime/python/okstra_ctl/validation_contract.py +3 -0
  265. package/runtime/python/okstra_ctl/verification_target.py +68 -0
  266. package/runtime/python/okstra_ctl/wizard.py +457 -92
  267. package/runtime/python/okstra_ctl/worker_artifacts.py +75 -16
  268. package/runtime/python/okstra_ctl/worker_audit_check.py +22 -0
  269. package/runtime/python/okstra_ctl/worker_dispatch.py +21 -0
  270. package/runtime/python/okstra_ctl/worker_liveness.py +33 -0
  271. package/runtime/python/okstra_ctl/worker_prompt_body.py +15 -2
  272. package/runtime/python/okstra_ctl/worker_prompt_contract.py +98 -7
  273. package/runtime/python/okstra_ctl/worker_prompt_headers.py +24 -2
  274. package/runtime/python/okstra_ctl/worker_prompt_policy.py +18 -3
  275. package/runtime/python/okstra_ctl/worker_request.py +2 -1
  276. package/runtime/python/okstra_ctl/worker_runner.py +16 -9
  277. package/runtime/python/okstra_ctl/worker_state.py +16 -1
  278. package/runtime/python/okstra_ctl/workflow.py +18 -1
  279. package/runtime/python/okstra_ctl/worktree/__init__.py +92 -0
  280. package/runtime/python/okstra_ctl/worktree/cleanliness.py +89 -0
  281. package/runtime/python/okstra_ctl/worktree/decisions.py +127 -0
  282. package/runtime/python/okstra_ctl/worktree/git_ops.py +165 -0
  283. package/runtime/python/okstra_ctl/worktree/linking.py +249 -0
  284. package/runtime/python/okstra_ctl/worktree/naming.py +91 -0
  285. package/runtime/python/okstra_ctl/worktree/provision.py +385 -0
  286. package/runtime/python/okstra_ctl/worktree/sync_config.py +181 -0
  287. package/runtime/python/okstra_ctl/worktree_cli.py +75 -0
  288. package/runtime/python/okstra_ctl/worktree_lookup_cli.py +39 -0
  289. package/runtime/python/okstra_ctl/worktree_registry.py +7 -0
  290. package/runtime/python/okstra_ctl/worktree_status_cli.py +53 -0
  291. package/runtime/python/okstra_ctl/write_policy.py +90 -213
  292. package/runtime/python/okstra_project/__init__.py +0 -4
  293. package/runtime/python/okstra_project/dirs.py +2 -2
  294. package/runtime/python/okstra_project/phase_pointer.py +84 -0
  295. package/runtime/python/okstra_project/slug.py +24 -0
  296. package/runtime/python/okstra_project/state.py +22 -230
  297. package/runtime/python/okstra_token_usage/claude.py +9 -1
  298. package/runtime/python/okstra_token_usage/cli.py +3 -1
  299. package/runtime/python/okstra_token_usage/collect.py +71 -10
  300. package/runtime/python/okstra_token_usage/cursor.py +2 -0
  301. package/runtime/python/okstra_token_usage/grok.py +1 -5
  302. package/runtime/python/okstra_token_usage/paths.py +26 -0
  303. package/runtime/python/okstra_token_usage/pricing.py +19 -7
  304. package/runtime/schemas/convergence-critic-results-v1.0.schema.json +5 -0
  305. package/runtime/schemas/execution-manifest-v2.schema.json +10 -10
  306. package/runtime/schemas/final-report-v2.0.schema.json +9 -26
  307. package/runtime/schemas/final-report-v3.0.schema.json +173 -41
  308. package/runtime/schemas/report-narrative-v3.0.schema.json +3 -2
  309. package/runtime/skills/okstra-brief-gen/SKILL.md +35 -2
  310. package/runtime/skills/okstra-container-build/SKILL.md +16 -47
  311. package/runtime/skills/okstra-inspect/facets/history.md +1 -1
  312. package/runtime/skills/okstra-inspect/facets/status.md +7 -6
  313. package/runtime/skills/okstra-pr-gen/SKILL.md +1 -1
  314. package/runtime/skills/okstra-run/SKILL.md +7 -5
  315. package/runtime/templates/report-writer-prompt-preamble.md +1 -1
  316. package/runtime/templates/reports/brief.template.md +6 -2
  317. package/runtime/templates/reports/error-analysis-input.template.md +2 -0
  318. package/runtime/templates/reports/final-verification-input.template.md +2 -0
  319. package/runtime/templates/reports/implementation-input.template.md +7 -1
  320. package/runtime/templates/reports/implementation-planning-input.template.md +4 -0
  321. package/runtime/templates/reports/quick-input.template.md +2 -0
  322. package/runtime/templates/reports/release-handoff-input.template.md +6 -1
  323. package/runtime/templates/reports/report.js +20 -3
  324. package/runtime/templates/reports/schedule.template.md +2 -0
  325. package/runtime/templates/reports/settings.template.json +0 -10
  326. package/runtime/templates/reports/task-brief.template.md +3 -0
  327. package/runtime/templates/reports/user-response.template.md +7 -3
  328. package/runtime/validators/checks/fixtures-01.py +57 -0
  329. package/runtime/validators/checks/fixtures-02.py +565 -0
  330. package/runtime/validators/checks/runners-01.py +108 -0
  331. package/runtime/validators/checks/validate-assets-01.py +60 -0
  332. package/runtime/validators/checks/validate-prompt-metadata-01.py +261 -0
  333. package/runtime/validators/checks/validate-tasks-01.py +46 -0
  334. package/runtime/validators/checks/validate-tasks-02.py +85 -0
  335. package/runtime/validators/checks/validate-tasks-03.py +61 -0
  336. package/runtime/validators/checks/validate-tasks-04.py +118 -0
  337. package/runtime/validators/forbidden_actions.py +73 -3
  338. package/runtime/validators/lib/common.sh +5 -0
  339. package/runtime/validators/lib/fixtures.sh +7 -591
  340. package/runtime/validators/lib/paths.sh +13 -0
  341. package/runtime/validators/lib/runners.sh +6 -104
  342. package/runtime/validators/lib/summary.sh +1 -1
  343. package/runtime/validators/lib/validate-assets.sh +6 -56
  344. package/runtime/validators/lib/validate-prompt-metadata.sh +6 -257
  345. package/runtime/validators/lib/validate-tasks.sh +9 -294
  346. package/runtime/validators/validate-implementation-plan-stages.py +4 -4
  347. package/runtime/validators/validate-run.py +1369 -2654
  348. package/runtime/validators/validate-workflow.sh +56 -16
  349. package/runtime/validators/validate_improvement_report.py +2 -1
  350. package/runtime/validators/validate_session_conformance.py +295 -49
  351. package/dist/commands/execute/agent-prompt.d.mts +0 -1
  352. package/dist/commands/execute/agent-prompt.mjs +0 -24
  353. package/dist/commands/execute/agent-prompt.mjs.map +0 -1
  354. package/dist/commands/execute/codex-dispatch.d.mts +0 -3
  355. package/dist/commands/execute/codex-dispatch.mjs +0 -6
  356. package/dist/commands/execute/codex-dispatch.mjs.map +0 -1
  357. package/dist/commands/execute/codex-run.d.mts +0 -3
  358. package/dist/commands/execute/codex-run.mjs +0 -62
  359. package/dist/commands/execute/codex-run.mjs.map +0 -1
  360. package/dist/commands/execute/convergence.d.mts +0 -1
  361. package/dist/commands/execute/convergence.mjs +0 -38
  362. package/dist/commands/execute/convergence.mjs.map +0 -1
  363. package/dist/commands/execute/error-log.d.mts +0 -1
  364. package/dist/commands/execute/error-log.mjs +0 -18
  365. package/dist/commands/execute/error-log.mjs.map +0 -1
  366. package/dist/commands/execute/git-reconcile.d.mts +0 -1
  367. package/dist/commands/execute/git-reconcile.mjs +0 -30
  368. package/dist/commands/execute/git-reconcile.mjs.map +0 -1
  369. package/dist/commands/execute/handoff.d.mts +0 -1
  370. package/dist/commands/execute/handoff.mjs +0 -31
  371. package/dist/commands/execute/handoff.mjs.map +0 -1
  372. package/dist/commands/execute/incremental-carry.d.mts +0 -1
  373. package/dist/commands/execute/incremental-carry.mjs +0 -20
  374. package/dist/commands/execute/incremental-carry.mjs.map +0 -1
  375. package/dist/commands/execute/incremental-scope.d.mts +0 -1
  376. package/dist/commands/execute/incremental-scope.mjs +0 -29
  377. package/dist/commands/execute/incremental-scope.mjs.map +0 -1
  378. package/dist/commands/execute/integrate-stages.d.mts +0 -1
  379. package/dist/commands/execute/integrate-stages.mjs +0 -24
  380. package/dist/commands/execute/integrate-stages.mjs.map +0 -1
  381. package/dist/commands/execute/pane-title.d.mts +0 -1
  382. package/dist/commands/execute/pane-title.mjs +0 -20
  383. package/dist/commands/execute/pane-title.mjs.map +0 -1
  384. package/dist/commands/execute/plan-items.d.mts +0 -1
  385. package/dist/commands/execute/plan-items.mjs +0 -9
  386. package/dist/commands/execute/plan-items.mjs.map +0 -1
  387. package/dist/commands/execute/plan-validate.d.mts +0 -1
  388. package/dist/commands/execute/plan-validate.mjs +0 -68
  389. package/dist/commands/execute/plan-validate.mjs.map +0 -1
  390. package/dist/commands/execute/plan-verify.d.mts +0 -1
  391. package/dist/commands/execute/plan-verify.mjs +0 -43
  392. package/dist/commands/execute/plan-verify.mjs.map +0 -1
  393. package/dist/commands/execute/spawn-followups.d.mts +0 -1
  394. package/dist/commands/execute/spawn-followups.mjs +0 -22
  395. package/dist/commands/execute/spawn-followups.mjs.map +0 -1
  396. package/dist/commands/execute/team.d.mts +0 -3
  397. package/dist/commands/execute/team.mjs +0 -66
  398. package/dist/commands/execute/team.mjs.map +0 -1
  399. package/dist/commands/execute/token-usage.d.mts +0 -1
  400. package/dist/commands/execute/token-usage.mjs +0 -19
  401. package/dist/commands/execute/token-usage.mjs.map +0 -1
  402. package/dist/commands/execute/worker-audit-check.d.mts +0 -1
  403. package/dist/commands/execute/worker-audit-check.mjs +0 -34
  404. package/dist/commands/execute/worker-audit-check.mjs.map +0 -1
  405. package/dist/commands/execute/worker-dispatch.d.mts +0 -7
  406. package/dist/commands/execute/worker-dispatch.mjs +0 -64
  407. package/dist/commands/execute/worker-dispatch.mjs.map +0 -1
  408. package/dist/commands/execute/worker-state.d.mts +0 -1
  409. package/dist/commands/execute/worker-state.mjs +0 -28
  410. package/dist/commands/execute/worker-state.mjs.map +0 -1
  411. package/dist/commands/execute/worktree-lookup.d.mts +0 -1
  412. package/dist/commands/execute/worktree-lookup.mjs +0 -92
  413. package/dist/commands/execute/worktree-lookup.mjs.map +0 -1
  414. package/dist/commands/execute/worktree-status.d.mts +0 -1
  415. package/dist/commands/execute/worktree-status.mjs +0 -121
  416. package/dist/commands/execute/worktree-status.mjs.map +0 -1
  417. package/dist/commands/inspect/code-review.d.mts +0 -1
  418. package/dist/commands/inspect/code-review.mjs +0 -32
  419. package/dist/commands/inspect/code-review.mjs.map +0 -1
  420. package/dist/commands/inspect/container.d.mts +0 -1
  421. package/dist/commands/inspect/container.mjs +0 -25
  422. package/dist/commands/inspect/container.mjs.map +0 -1
  423. package/dist/commands/inspect/context-cost.d.mts +0 -1
  424. package/dist/commands/inspect/context-cost.mjs +0 -25
  425. package/dist/commands/inspect/context-cost.mjs.map +0 -1
  426. package/dist/commands/inspect/design-prep.d.mts +0 -1
  427. package/dist/commands/inspect/design-prep.mjs +0 -22
  428. package/dist/commands/inspect/design-prep.mjs.map +0 -1
  429. package/dist/commands/inspect/error-report.d.mts +0 -1
  430. package/dist/commands/inspect/error-report.mjs +0 -25
  431. package/dist/commands/inspect/error-report.mjs.map +0 -1
  432. package/dist/commands/inspect/error-zip.d.mts +0 -1
  433. package/dist/commands/inspect/error-zip.mjs +0 -24
  434. package/dist/commands/inspect/error-zip.mjs.map +0 -1
  435. package/dist/commands/inspect/log-report.d.mts +0 -1
  436. package/dist/commands/inspect/log-report.mjs +0 -26
  437. package/dist/commands/inspect/log-report.mjs.map +0 -1
  438. package/dist/commands/inspect/model-io.d.mts +0 -1
  439. package/dist/commands/inspect/model-io.mjs +0 -25
  440. package/dist/commands/inspect/model-io.mjs.map +0 -1
  441. package/dist/commands/inspect/profile-show.d.mts +0 -1
  442. package/dist/commands/inspect/profile-show.mjs +0 -28
  443. package/dist/commands/inspect/profile-show.mjs.map +0 -1
  444. package/dist/commands/inspect/recap.d.mts +0 -1
  445. package/dist/commands/inspect/recap.mjs +0 -30
  446. package/dist/commands/inspect/recap.mjs.map +0 -1
  447. package/dist/commands/inspect/resolve-task-key.d.mts +0 -1
  448. package/dist/commands/inspect/resolve-task-key.mjs +0 -24
  449. package/dist/commands/inspect/resolve-task-key.mjs.map +0 -1
  450. package/dist/commands/inspect/rollup.d.mts +0 -1
  451. package/dist/commands/inspect/rollup.mjs +0 -25
  452. package/dist/commands/inspect/rollup.mjs.map +0 -1
  453. package/dist/commands/inspect/run-audit.d.mts +0 -1
  454. package/dist/commands/inspect/run-audit.mjs +0 -25
  455. package/dist/commands/inspect/run-audit.mjs.map +0 -1
  456. package/dist/commands/inspect/set-work-status.d.mts +0 -1
  457. package/dist/commands/inspect/set-work-status.mjs +0 -29
  458. package/dist/commands/inspect/set-work-status.mjs.map +0 -1
  459. package/dist/commands/inspect/stage-map.d.mts +0 -1
  460. package/dist/commands/inspect/stage-map.mjs +0 -131
  461. package/dist/commands/inspect/stage-map.mjs.map +0 -1
  462. package/dist/commands/inspect/task-list.d.mts +0 -1
  463. package/dist/commands/inspect/task-list.mjs +0 -149
  464. package/dist/commands/inspect/task-list.mjs.map +0 -1
  465. package/dist/commands/inspect/task-show.d.mts +0 -1
  466. package/dist/commands/inspect/task-show.mjs +0 -108
  467. package/dist/commands/inspect/task-show.mjs.map +0 -1
  468. package/dist/commands/inspect/time-report.d.mts +0 -1
  469. package/dist/commands/inspect/time-report.mjs +0 -24
  470. package/dist/commands/inspect/time-report.mjs.map +0 -1
  471. package/dist/commands/inspect/usage-report.d.mts +0 -1
  472. package/dist/commands/inspect/usage-report.mjs +0 -23
  473. package/dist/commands/inspect/usage-report.mjs.map +0 -1
  474. package/dist/commands/inspect/user-response.d.mts +0 -1
  475. package/dist/commands/inspect/user-response.mjs +0 -35
  476. package/dist/commands/inspect/user-response.mjs.map +0 -1
  477. package/dist/commands/inspect/worker-liveness.d.mts +0 -1
  478. package/dist/commands/inspect/worker-liveness.mjs +0 -45
  479. package/dist/commands/inspect/worker-liveness.mjs.map +0 -1
  480. package/dist/commands/lifecycle/contract-check.d.mts +0 -1
  481. package/dist/commands/lifecycle/contract-check.mjs +0 -18
  482. package/dist/commands/lifecycle/contract-check.mjs.map +0 -1
  483. package/dist/commands/lifecycle/migrate.d.mts +0 -1
  484. package/dist/commands/lifecycle/migrate.mjs +0 -30
  485. package/dist/commands/lifecycle/migrate.mjs.map +0 -1
  486. package/dist/commands/lifecycle/model.d.mts +0 -1
  487. package/dist/commands/lifecycle/model.mjs +0 -22
  488. package/dist/commands/lifecycle/model.mjs.map +0 -1
  489. package/dist/commands/manager.d.mts +0 -3
  490. package/dist/commands/manager.mjs +0 -50
  491. package/dist/commands/manager.mjs.map +0 -1
  492. package/dist/commands/report/agent-activity.d.mts +0 -1
  493. package/dist/commands/report/agent-activity.mjs +0 -20
  494. package/dist/commands/report/agent-activity.mjs.map +0 -1
  495. package/dist/commands/report/approval-decision.d.mts +0 -1
  496. package/dist/commands/report/approval-decision.mjs +0 -21
  497. package/dist/commands/report/approval-decision.mjs.map +0 -1
  498. package/dist/commands/report/design-snapshot.d.mts +0 -1
  499. package/dist/commands/report/design-snapshot.mjs +0 -19
  500. package/dist/commands/report/design-snapshot.mjs.map +0 -1
  501. package/dist/commands/report/finalize.d.mts +0 -4
  502. package/dist/commands/report/finalize.mjs +0 -64
  503. package/dist/commands/report/finalize.mjs.map +0 -1
  504. package/dist/commands/report/inject-report-index.d.mts +0 -1
  505. package/dist/commands/report/inject-report-index.mjs +0 -21
  506. package/dist/commands/report/inject-report-index.mjs.map +0 -1
  507. package/dist/commands/report/render-final-report.d.mts +0 -1
  508. package/dist/commands/report/render-final-report.mjs +0 -23
  509. package/dist/commands/report/render-final-report.mjs.map +0 -1
  510. package/dist/commands/report/render-views.d.mts +0 -1
  511. package/dist/commands/report/render-views.mjs +0 -27
  512. package/dist/commands/report/render-views.mjs.map +0 -1
  513. package/dist/commands/report/translate.d.mts +0 -1
  514. package/dist/commands/report/translate.mjs +0 -33
  515. package/dist/commands/report/translate.mjs.map +0 -1
  516. package/runtime/bin/lib/okstra/tmux-pane.sh +0 -40
  517. package/runtime/bin/lib/okstra-ctl/cmd-batch.sh +0 -59
  518. package/runtime/bin/lib/okstra-ctl/cmd-list.sh +0 -35
  519. package/runtime/bin/lib/okstra-ctl/cmd-open.sh +0 -36
  520. package/runtime/bin/lib/okstra-ctl/cmd-projects.sh +0 -26
  521. package/runtime/bin/lib/okstra-ctl/cmd-reconcile.sh +0 -29
  522. package/runtime/bin/lib/okstra-ctl/cmd-reindex.sh +0 -38
  523. package/runtime/bin/lib/okstra-ctl/cmd-rerun.sh +0 -345
  524. package/runtime/bin/lib/okstra-ctl/cmd-show.sh +0 -27
  525. package/runtime/bin/lib/okstra-ctl/cmd-tail.sh +0 -92
  526. package/runtime/bin/lib/okstra-ctl/main.sh +0 -41
  527. package/runtime/bin/lib/okstra-ctl/prepare.sh +0 -31
  528. package/runtime/bin/lib/okstra-ctl/usage.sh +0 -23
  529. package/runtime/bin/okstra-central.sh +0 -152
  530. package/runtime/bin/okstra-incremental-carry.py +0 -10
  531. package/runtime/bin/okstra-incremental-scope.py +0 -10
  532. package/runtime/bin/okstra-team-reconcile.sh +0 -36
  533. package/runtime/python/okstra_ctl/agent_prompt_cli.py +0 -1156
  534. package/runtime/python/okstra_ctl/batch.py +0 -60
  535. package/runtime/python/okstra_ctl/clarification_items.py +0 -1050
  536. package/runtime/python/okstra_ctl/container_registry.py +0 -72
  537. package/runtime/python/okstra_ctl/improvement_assignment.py +0 -61
  538. package/runtime/python/okstra_ctl/resolver.py +0 -54
  539. package/runtime/python/okstra_ctl/team_reconcile.py +0 -275
  540. package/runtime/python/okstra_ctl/tmux.py +0 -134
  541. package/runtime/python/okstra_ctl/worktree.py +0 -1099
@@ -0,0 +1,385 @@
1
+ """worktree 를 실제로 만든다 — task 하나에 하나, stage 하나에 하나.
2
+
3
+ 이 파일만 부수 효과를 낸다: 레지스트리 예약, worktree 추가, sync 링크 설치,
4
+ 실패 시 되감기. 나머지 층은 전부 이 두 함수가 쓰려고 나뉘어 있다.
5
+ `provision_task_worktree` 는 같은 task-key 로 다시 들어오면 기존 worktree 를
6
+ 그대로 돌려주고, `provision_stage_worktree` 는 base_commit 을 반드시 요구한다.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import sys
11
+ from dataclasses import dataclass
12
+ from pathlib import Path
13
+
14
+ from .. import worktree_registry
15
+ from .decisions import preview_worktree_decision, resolve_stage_worktree_decision
16
+ from .git_ops import (
17
+ _branch_exists,
18
+ _branch_exists_message,
19
+ _git,
20
+ _head_sha,
21
+ _resolve_commit_sha,
22
+ main_worktree_path,
23
+ remove_worktree_force,
24
+ )
25
+ from .linking import (
26
+ _copy_snapshot_files,
27
+ _link_sync_dirs,
28
+ _link_sync_files,
29
+ describe_sync_key_drift,
30
+ sync_file_key_names,
31
+ _seed_worktree_settings_symlink,
32
+ )
33
+ from .naming import _safe_segment
34
+
35
+
36
+ @dataclass
37
+ class WorktreeProvision:
38
+ """Result of `provision_task_worktree`.
39
+
40
+ status:
41
+ - "created": fresh worktree at `path` on `branch`
42
+ - "reused": registry already had this task-key; same path/branch
43
+ returned and no new `git worktree add` was executed
44
+ - "skipped-in-worktree": project_root is itself a non-main
45
+ worktree; the run reuses `project_root` and no new worktree is
46
+ materialised (registry NOT updated — that caller is already
47
+ isolated by virtue of its own worktree)
48
+ - "skipped-not-git": project_root has no `.git` (worktree path
49
+ cannot be provisioned; degrade gracefully)
50
+ """
51
+ status: str
52
+ path: str = "" # absolute path of the task worktree (or project_root when reused)
53
+ branch: str = "" # branch checked out in the worktree (empty when reused / not-git)
54
+ base_ref: str = "" # commit SHA the worktree was branched from (empty when not created)
55
+ note: str = "" # human-readable explanation, surfaced in team-state / manifests
56
+
57
+
58
+ def _sync_key_drift_note(main_root: Path, entry) -> str:
59
+ """재사용되는 worktree 에 붙일 공유 sync 파일 키 드리프트 한 줄.
60
+
61
+ 앞에 `; ` 를 붙인 형태로 돌려주므로 note 문자열에 그대로 이어 붙일 수 있고,
62
+ 드리프트가 없으면 빈 문자열이라 종전 note 와 바이트가 같다.
63
+ """
64
+ if entry is None:
65
+ return ""
66
+ message = describe_sync_key_drift(
67
+ entry.sync_file_keys, sync_file_key_names(main_root)
68
+ )
69
+ if not message:
70
+ return ""
71
+ print(f"okstra-worktree: {message}", file=sys.stderr)
72
+ return f"; {message}"
73
+
74
+
75
+ def provision_task_worktree(
76
+ *,
77
+ task_type: str,
78
+ project_root: Path,
79
+ project_id: str,
80
+ task_group_segment: str,
81
+ task_id_segment: str,
82
+ work_category: str,
83
+ base_ref: str = "",
84
+ require_base_ref: bool = False,
85
+ ) -> WorktreeProvision:
86
+ """Materialise (or reuse) the task worktree for this run.
87
+
88
+ First phase of a task-key creates the worktree on a new branch.
89
+ Subsequent phases of the same task-key look up the registry and
90
+ return the existing path + branch unchanged.
91
+
92
+ ``base_ref`` is the ref (branch name, tag, or commit SHA) to branch
93
+ the new worktree from on first phase. When empty, the main worktree's
94
+ current ``HEAD`` is used (legacy default; the CLI enforces a
95
+ non-empty value on first phase so callers go through the
96
+ AskUserQuestion menu in the okstra-run skill). Subsequent phases
97
+ ignore ``base_ref`` — the registered entry's base is reused.
98
+
99
+ Concurrency: callers must hold ``locks.worktree_provision_mutex`` for
100
+ this task-key (run.py's prepare flow does). The exists/branch pre-checks
101
+ and ``git worktree add`` here are not internally locked — only the
102
+ registry reserve row is — so unlocked concurrent calls race (TOCTOU).
103
+ flock is non-reentrant, hence the lock lives at the caller.
104
+
105
+ Raises:
106
+ RuntimeError when worktree creation fails (path clash on disk
107
+ that the registry does not know about, branch clash with a
108
+ different task-key, `git worktree add` non-zero). The caller
109
+ (`run.py`) catches and re-raises as PrepareError to keep a
110
+ single error surface.
111
+ """
112
+ decision = preview_worktree_decision(
113
+ project_root=project_root, project_id=project_id,
114
+ task_group_segment=task_group_segment, task_id_segment=task_id_segment,
115
+ work_category=work_category, base_ref=base_ref,
116
+ )
117
+
118
+ if decision.status == "skipped-not-git":
119
+ return WorktreeProvision(
120
+ status="skipped-not-git",
121
+ path=decision.path,
122
+ note=(
123
+ "worktree provisioning skipped: project_root is not inside a git "
124
+ "repository; task will operate directly on project_root"
125
+ ),
126
+ )
127
+
128
+ if decision.status == "skipped-in-worktree":
129
+ return WorktreeProvision(
130
+ status="skipped-in-worktree",
131
+ path=decision.path,
132
+ note=(
133
+ "worktree provisioning skipped: project_root is already inside a "
134
+ "non-main git worktree; task reuses the caller's worktree"
135
+ ),
136
+ )
137
+
138
+ safe_project = _safe_segment(project_id)
139
+ safe_group = _safe_segment(task_group_segment)
140
+ safe_task = _safe_segment(task_id_segment)
141
+
142
+ if decision.status == "reused":
143
+ worktree_registry.touch_phase(safe_project, safe_group, safe_task, task_type)
144
+ _seed_worktree_settings_symlink(Path(decision.path))
145
+ drift = _sync_key_drift_note(
146
+ main_worktree_path(project_root),
147
+ worktree_registry.lookup(safe_project, safe_group, safe_task),
148
+ )
149
+ return WorktreeProvision(
150
+ status="reused",
151
+ path=decision.path,
152
+ branch=decision.branch,
153
+ base_ref=decision.base_ref,
154
+ note=(
155
+ f"task worktree reused at {decision.path} on branch "
156
+ f"{decision.branch} (base {decision.base_ref[:12]}); phase {task_type}"
157
+ f"{drift}"
158
+ ),
159
+ )
160
+
161
+ # decision.status == "new" — proceed with creation
162
+ worktree_path = Path(decision.path)
163
+ branch = decision.branch
164
+
165
+ if worktree_path.exists():
166
+ raise RuntimeError(
167
+ f"task worktree path already exists but is not in the registry: "
168
+ f"{worktree_path}. Remove it with `git worktree remove <path>` "
169
+ "(or `rm -rf` if it is not a registered worktree) before retrying."
170
+ )
171
+ if _branch_exists(project_root, branch):
172
+ raise RuntimeError(_branch_exists_message(project_root, branch, "task"))
173
+
174
+ main_root = main_worktree_path(project_root)
175
+ requested_base = (base_ref or "").strip()
176
+ if not requested_base and require_base_ref:
177
+ raise RuntimeError(
178
+ "first-phase task worktree requires an explicit base ref; "
179
+ "pass `--base-ref <branch|tag|sha>` (or invoke through the "
180
+ "okstra-run skill which collects this interactively)"
181
+ )
182
+ if requested_base:
183
+ resolved_sha = _resolve_commit_sha(main_root, requested_base)
184
+ if not resolved_sha:
185
+ raise RuntimeError(
186
+ f"could not resolve base ref `{requested_base}` in main worktree "
187
+ f"({main_root}); ensure the branch/tag/SHA exists locally"
188
+ )
189
+ resolved_base_ref = resolved_sha
190
+ base_origin = requested_base
191
+ else:
192
+ resolved_base_ref = _head_sha(main_root)
193
+ if not resolved_base_ref:
194
+ raise RuntimeError(
195
+ "could not resolve HEAD sha in main worktree; cannot create task worktree"
196
+ )
197
+ base_origin = "HEAD"
198
+
199
+ worktree_path.parent.mkdir(parents=True, exist_ok=True)
200
+ res = _git(
201
+ main_root,
202
+ "worktree", "add", "-b", branch, str(worktree_path), resolved_base_ref,
203
+ )
204
+ if res.returncode != 0:
205
+ raise RuntimeError(
206
+ f"`git worktree add` failed (exit={res.returncode}): "
207
+ f"{(res.stderr or res.stdout).strip()}"
208
+ )
209
+
210
+ # Sync dirs sourced from the MAIN worktree so every task sees the
211
+ # same shared state regardless of which checkout invoked okstra.
212
+ linked = _link_sync_dirs(main_root, worktree_path)
213
+ linked_files = _link_sync_files(main_root, worktree_path)
214
+ sync_file_keys = sync_file_key_names(main_root)
215
+ snapshot_files = _copy_snapshot_files(main_root, worktree_path)
216
+ linked_parts: list[str] = []
217
+ if linked:
218
+ linked_parts.append(f"linked {', '.join(linked)}")
219
+ if linked_files:
220
+ linked_parts.append(f"linked-files {', '.join(linked_files)}")
221
+ if snapshot_files:
222
+ linked_parts.append(f"snapshot {', '.join(snapshot_files)}")
223
+ linked_suffix = ("; " + "; ".join(linked_parts)) if linked_parts else ""
224
+
225
+ try:
226
+ worktree_registry.reserve(
227
+ project_id=safe_project,
228
+ task_group=safe_group,
229
+ task_id=safe_task,
230
+ worktree_path=str(worktree_path),
231
+ branch=branch,
232
+ base_ref=resolved_base_ref,
233
+ phase=task_type,
234
+ sync_file_keys=sync_file_keys,
235
+ )
236
+ except RuntimeError:
237
+ # Roll back the on-disk worktree so the next attempt is not
238
+ # blocked by the lingering directory / branch.
239
+ remove_worktree_force(main_root, worktree_path)
240
+ _git(main_root, "branch", "-D", branch)
241
+ raise
242
+
243
+ _seed_worktree_settings_symlink(worktree_path)
244
+
245
+ base_label = (
246
+ f"{base_origin} @ {resolved_base_ref[:12]}"
247
+ if base_origin != "HEAD"
248
+ else f"HEAD @ {resolved_base_ref[:12]}"
249
+ )
250
+ return WorktreeProvision(
251
+ status="created",
252
+ path=str(worktree_path),
253
+ branch=branch,
254
+ base_ref=resolved_base_ref,
255
+ note=(
256
+ f"task worktree created at {worktree_path} on branch {branch} "
257
+ f"(base {base_label}; phase {task_type}){linked_suffix}"
258
+ ),
259
+ )
260
+
261
+
262
+ def provision_stage_worktree(
263
+ *,
264
+ project_root: Path,
265
+ project_id: str,
266
+ task_group_segment: str,
267
+ task_id_segment: str,
268
+ work_category: str,
269
+ stage_number: int,
270
+ base_commit: str,
271
+ ) -> WorktreeProvision:
272
+ """Materialise an isolated worktree for one implementation stage.
273
+
274
+ Unlike `provision_task_worktree` (one worktree per task-key shared
275
+ across phases), this provisions a per-stage worktree branched from
276
+ `base_commit` at `<task-key>/stage-<N>/` on branch `<prefix>-<task>-s<N>`.
277
+ The stage-key (`<task-key>#stage-<N>`) is reserved atomically through
278
+ `worktree_registry`; re-entry of the same stage-key returns the
279
+ existing entry. Branch / on-disk conflicts roll back the worktree
280
+ before re-raising so a retry is not blocked.
281
+
282
+ Concurrency: callers must hold ``locks.worktree_provision_mutex`` for
283
+ the task-key, acquired BEFORE the Stage Run Claim reads the registry
284
+ (run.py does) — otherwise two `--stage auto` runs can select the same
285
+ stage and the loser silently enters the winner's worktree via the
286
+ "reused" path. flock is non-reentrant, hence the lock lives at the
287
+ caller.
288
+ """
289
+ if not base_commit:
290
+ raise RuntimeError("provision_stage_worktree requires a base_commit")
291
+
292
+ decision = resolve_stage_worktree_decision(
293
+ project_id=project_id,
294
+ task_group_segment=task_group_segment,
295
+ task_id_segment=task_id_segment,
296
+ work_category=work_category,
297
+ stage_number=stage_number,
298
+ )
299
+ if decision.status == "reused":
300
+ drift = _sync_key_drift_note(
301
+ main_worktree_path(project_root),
302
+ worktree_registry.lookup(
303
+ _safe_segment(project_id),
304
+ _safe_segment(task_group_segment),
305
+ _safe_segment(task_id_segment),
306
+ stage_number=stage_number,
307
+ ),
308
+ )
309
+ return WorktreeProvision(
310
+ status="reused",
311
+ path=decision.path,
312
+ branch=decision.branch,
313
+ base_ref=decision.base_ref,
314
+ note=(
315
+ f"stage {stage_number} worktree reused at "
316
+ f"{decision.path} on branch {decision.branch} "
317
+ f"(base {decision.base_ref[:12]}){drift}"
318
+ ),
319
+ )
320
+
321
+ safe_project = _safe_segment(project_id)
322
+ safe_group = _safe_segment(task_group_segment)
323
+ safe_task = _safe_segment(task_id_segment)
324
+ worktree_path = Path(decision.path)
325
+ branch = decision.branch
326
+
327
+ if worktree_path.exists():
328
+ raise RuntimeError(
329
+ f"stage worktree path already exists but is not in the registry: "
330
+ f"{worktree_path}. Remove it before retrying."
331
+ )
332
+ if _branch_exists(project_root, branch):
333
+ raise RuntimeError(_branch_exists_message(project_root, branch, "stage"))
334
+
335
+ main_root = main_worktree_path(project_root)
336
+ resolved_sha = _resolve_commit_sha(main_root, base_commit)
337
+ if not resolved_sha:
338
+ raise RuntimeError(
339
+ f"could not resolve base_commit `{base_commit}` in main worktree "
340
+ f"({main_root}); ensure the commit exists locally"
341
+ )
342
+
343
+ worktree_path.parent.mkdir(parents=True, exist_ok=True)
344
+ res = _git(
345
+ main_root,
346
+ "worktree", "add", "-b", branch, str(worktree_path), resolved_sha,
347
+ )
348
+ if res.returncode != 0:
349
+ raise RuntimeError(
350
+ f"`git worktree add` failed (exit={res.returncode}): "
351
+ f"{(res.stderr or res.stdout).strip()}"
352
+ )
353
+
354
+ _link_sync_dirs(main_root, worktree_path)
355
+ _link_sync_files(main_root, worktree_path)
356
+ _copy_snapshot_files(main_root, worktree_path)
357
+ sync_file_keys = sync_file_key_names(main_root)
358
+
359
+ try:
360
+ worktree_registry.reserve(
361
+ project_id=safe_project,
362
+ task_group=safe_group,
363
+ task_id=safe_task,
364
+ worktree_path=str(worktree_path),
365
+ branch=branch,
366
+ base_ref=resolved_sha,
367
+ phase="implementation",
368
+ stage_number=stage_number,
369
+ sync_file_keys=sync_file_keys,
370
+ )
371
+ except RuntimeError:
372
+ remove_worktree_force(main_root, worktree_path)
373
+ _git(main_root, "branch", "-D", branch)
374
+ raise
375
+
376
+ _seed_worktree_settings_symlink(worktree_path)
377
+
378
+ return WorktreeProvision(
379
+ status="created", path=str(worktree_path),
380
+ branch=branch, base_ref=resolved_sha,
381
+ note=(
382
+ f"stage {stage_number} worktree created at {worktree_path} "
383
+ f"on branch {branch} (base {resolved_sha[:12]})"
384
+ ),
385
+ )
@@ -0,0 +1,181 @@
1
+ """worktree 로 옮겨 갈 경로 목록을 결정한다 — 그 목록을 쓰지는 않는다.
2
+
3
+ `git worktree add` 는 추적되지 않는 최상위 항목을 가져오지 않는다. 어떤
4
+ 디렉터리·파일이 메인 체크아웃에서 새 worktree 로 따라와야 하는지가 여기서
5
+ 정해진다. 우선순위는 세 단계 — 환경 변수, `project.json`, 내장 기본값 — 이고
6
+ 앞의 것이 뒤를 REPLACE 한다(병합이 아니다).
7
+
8
+ 이 층은 git 을 부르지 않고 심링크도 걸지 않는다. 목록만 답한다.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import os
13
+ from pathlib import Path
14
+ from typing import Optional
15
+
16
+ from okstra_project.dirs import project_json_path
17
+
18
+ from ..json_boundary import JsonBoundaryError, load_owned_object
19
+
20
+
21
+ # Project-root directories that hold okstra task state, ignored by git, or
22
+ # otherwise required for the executor to operate but NOT carried across by
23
+ # `git worktree add`. Each is symlinked from the MAIN worktree into the new
24
+ # worktree at provision time. Symlinks (not copies) so every task sees the
25
+ # live shared state and disk/CPU cost stays near zero; the trade-off is
26
+ # that any write through the link reaches the main worktree, which is
27
+ # acceptable because okstra writes only to paths it owns: its task-scoped
28
+ # subdirectory (e.g. `.okstra/tasks/<task-id>/runs/...`) and, for a
29
+ # branch-mode code review, `.project-docs/code-reviews/<branch>/`.
30
+ #
31
+ # Override precedence (most-specific first):
32
+ # 1. `OKSTRA_WORKTREE_SYNC_DIRS` env var — colon-separated list, REPLACES
33
+ # defaults. Empty string disables the feature entirely. One-off
34
+ # operator override.
35
+ # 2. `worktreeSyncDirs` array in `.okstra/project.json` —
36
+ # project-level config, persists across runs. Same semantics: array
37
+ # REPLACES defaults, empty array disables.
38
+ # 3. The built-in `DEFAULT_WORKTREE_SYNC_DIRS` below.
39
+ DEFAULT_WORKTREE_SYNC_DIRS: tuple[str, ...] = (
40
+ ".project-docs",
41
+ ".scratch",
42
+ "graphify-out",
43
+ ".claude",
44
+ )
45
+
46
+
47
+ # Sync dirs materialised as a REAL directory whose children are symlinked one
48
+ # by one, instead of a single symlink standing in for the whole directory.
49
+ #
50
+ # Why the split exists: git does not follow a symlink, so a symlinked directory
51
+ # is one *file* to it. A project that ignores host config by its contents
52
+ # (`.claude/*`) matches every child but never the bare `.claude` path, so the
53
+ # symlink lands in `git status` as
54
+ # `?? .claude` while the same directory is invisible in the main checkout. Any
55
+ # plan step asserting a clean worktree then fails on okstra's own provisioning.
56
+ # Linking the children instead reproduces the main checkout's shape, so
57
+ # whatever the project's ignore rules do there, they do here too.
58
+ #
59
+ # Only `.claude` qualifies. The other sync dirs are shared okstra state that
60
+ # okstra WRITES into, and a directory symlink is what makes a newly created
61
+ # top-level entry land in the main checkout rather than diverging inside the
62
+ # worktree. `.claude` is host configuration okstra reads; its one okstra write
63
+ # is the fixed `settings.local.json` child seeded by
64
+ # `_seed_worktree_settings_symlink`.
65
+ CHILD_LINKED_SYNC_DIRS: tuple[str, ...] = (".claude",)
66
+
67
+
68
+ # Project-root-relative FILES (not dirs) symlinked from MAIN → task worktree
69
+ # at provision time. Same symlink semantics as `DEFAULT_WORKTREE_SYNC_DIRS`:
70
+ # every task sees the live shared file. The split exists because the original
71
+ # `_link_sync_dirs` helper only walked directories — a `.env` (or any
72
+ # top-level file outside `.git`'s tracking) would otherwise be lost on every
73
+ # new worktree, blocking verifier dispatches that depend on environment-
74
+ # resolved secrets.
75
+ #
76
+ # Override precedence mirrors `DEFAULT_WORKTREE_SYNC_DIRS`:
77
+ # 1. `OKSTRA_WORKTREE_SYNC_FILES` env var (colon-separated, REPLACES).
78
+ # 2. `worktreeSyncFiles` array in `.okstra/project.json`.
79
+ # 3. The built-in `DEFAULT_WORKTREE_SYNC_FILES` below.
80
+ DEFAULT_WORKTREE_SYNC_FILES: tuple[str, ...] = (
81
+ ".env",
82
+ )
83
+
84
+
85
+ # Project-root-relative files COPIED (not symlinked) from MAIN → task worktree
86
+ # at provision time, then `chmod 0o444` so the task cannot mutate the
87
+ # snapshot. Used for live-mutating fixtures (e.g. `classifications.db`)
88
+ # where the verifier needs to reproduce accuracy SQL against the same rows
89
+ # the executor saw, without sharing the writable handle that would corrupt
90
+ # the main worktree's copy. Default is empty — okstra has no opinion about
91
+ # which fixtures any given project relies on; opt in per-project via
92
+ # `worktreeSnapshotFiles` in `project.json` (or `OKSTRA_WORKTREE_SNAPSHOT_FILES`
93
+ # for one-off operator override).
94
+ DEFAULT_WORKTREE_SNAPSHOT_FILES: tuple[str, ...] = ()
95
+
96
+
97
+ def _read_project_json_field(project_root: Path, field: str) -> Optional[tuple[str, ...]]:
98
+ """Read a string-array field from the project's okstra project.json.
99
+
100
+ Returns None if the field is absent or the file cannot be parsed (so
101
+ the caller falls back to defaults). Returns an empty tuple if the
102
+ field is explicitly an empty array (caller treats this as "disable").
103
+ A non-list value is treated as missing — we do not raise here because
104
+ field resolution must never block worktree provisioning.
105
+ """
106
+ target = project_json_path(project_root)
107
+ if not target.is_file():
108
+ return None
109
+ try:
110
+ data = load_owned_object(target, artifact="project config")
111
+ except (OSError, JsonBoundaryError):
112
+ return None
113
+ if not isinstance(data, dict):
114
+ return None
115
+ value = data.get(field)
116
+ if not isinstance(value, list):
117
+ return None
118
+ cleaned = tuple(
119
+ item.strip() for item in value
120
+ if isinstance(item, str) and item.strip()
121
+ )
122
+ return cleaned
123
+
124
+
125
+ def _resolve_entries(
126
+ *,
127
+ env_var: str,
128
+ project_field: str,
129
+ default: tuple[str, ...],
130
+ project_root: Optional[Path],
131
+ ) -> tuple[str, ...]:
132
+ """Generic resolver shared by sync-dirs / sync-files / snapshot-files.
133
+
134
+ Precedence: env var (colon-separated, REPLACES) → project.json field →
135
+ built-in default. An empty env value or empty JSON array disables the
136
+ feature (returns `()`).
137
+ """
138
+ raw = os.environ.get(env_var)
139
+ if raw is not None:
140
+ raw = raw.strip()
141
+ if not raw:
142
+ return ()
143
+ return tuple(part for part in (p.strip() for p in raw.split(":")) if part)
144
+ if project_root is not None:
145
+ from_project = _read_project_json_field(project_root, project_field)
146
+ if from_project is not None:
147
+ return from_project
148
+ return default
149
+
150
+
151
+ def _resolve_sync_dirs(project_root: Optional[Path] = None) -> tuple[str, ...]:
152
+ """Return the list of project-root-relative dirs to symlink into the
153
+ new worktree. Precedence: env var → project.json → built-in default.
154
+ See the comment above `DEFAULT_WORKTREE_SYNC_DIRS` for full semantics.
155
+ """
156
+ return _resolve_entries(
157
+ env_var="OKSTRA_WORKTREE_SYNC_DIRS",
158
+ project_field="worktreeSyncDirs",
159
+ default=DEFAULT_WORKTREE_SYNC_DIRS,
160
+ project_root=project_root,
161
+ )
162
+
163
+
164
+ def _resolve_sync_files(project_root: Optional[Path] = None) -> tuple[str, ...]:
165
+ """File-level counterpart to `_resolve_sync_dirs` (FU-V2)."""
166
+ return _resolve_entries(
167
+ env_var="OKSTRA_WORKTREE_SYNC_FILES",
168
+ project_field="worktreeSyncFiles",
169
+ default=DEFAULT_WORKTREE_SYNC_FILES,
170
+ project_root=project_root,
171
+ )
172
+
173
+
174
+ def _resolve_snapshot_files(project_root: Optional[Path] = None) -> tuple[str, ...]:
175
+ """Read-only snapshot file list (FU-V3)."""
176
+ return _resolve_entries(
177
+ env_var="OKSTRA_WORKTREE_SNAPSHOT_FILES",
178
+ project_field="worktreeSnapshotFiles",
179
+ default=DEFAULT_WORKTREE_SNAPSHOT_FILES,
180
+ project_root=project_root,
181
+ )
@@ -0,0 +1,75 @@
1
+ """`okstra worktree-lookup` / `okstra worktree-status` 의 핸들러.
2
+
3
+ 원래 `src/commands/execute/worktree-{lookup,status}.mts` 의 문자열 리터럴 안에
4
+ 있던 코드다(docs/coding-rules.md R1 위반). 로직은 그대로 옮겼다.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ from dataclasses import asdict
10
+ from pathlib import Path
11
+
12
+ from . import worktree_registry
13
+ from .ids import _safe_fs_segment
14
+ from .worktree import (
15
+ dirty_entries_excluding_okstra,
16
+ is_git_work_tree,
17
+ nested_worktree_excludes,
18
+ okstra_clean_gate_excludes,
19
+ )
20
+
21
+
22
+ def _entry_payload(entry) -> dict:
23
+ """registry 의 snake_case dataclass 를 CLI 의 camelCase 계약으로 옮긴다."""
24
+ d = asdict(entry)
25
+ return {
26
+ "taskKey": d.get("task_key"),
27
+ "projectId": d.get("project_id"),
28
+ "taskGroup": d.get("task_group"),
29
+ "taskId": d.get("task_id"),
30
+ "worktreePath": d.get("worktree_path"),
31
+ "branch": d.get("branch"),
32
+ "baseRef": d.get("base_ref"),
33
+ "createdAt": d.get("created_at"),
34
+ "lastPhase": d.get("last_phase"),
35
+ "status": d.get("status"),
36
+ }
37
+
38
+
39
+ def lookup(args: argparse.Namespace) -> int:
40
+ entry = worktree_registry.lookup(
41
+ _safe_fs_segment(args.project_id),
42
+ _safe_fs_segment(args.task_group),
43
+ _safe_fs_segment(args.task_id),
44
+ )
45
+ if entry is None:
46
+ print(json.dumps({"ok": True, "entry": None}))
47
+ return 0
48
+ print(json.dumps(
49
+ {"ok": True, "entry": _entry_payload(entry)},
50
+ ensure_ascii=False, default=str, indent=2,
51
+ ))
52
+ return 0
53
+
54
+
55
+ def status(args: argparse.Namespace) -> int:
56
+ path = Path(args.path).resolve()
57
+ if not path.is_dir():
58
+ print(json.dumps({"ok": False, "stage": "input",
59
+ "reason": f"not a directory: {path}"}))
60
+ return 2
61
+ if not is_git_work_tree(path):
62
+ print(json.dumps({"ok": False, "stage": "input",
63
+ "reason": f"not inside a git work tree: {path}"}))
64
+ return 2
65
+
66
+ entries = dirty_entries_excluding_okstra(path)
67
+ excluded = [*okstra_clean_gate_excludes(path), *nested_worktree_excludes(path)]
68
+ print(json.dumps({
69
+ "ok": True,
70
+ "path": str(path),
71
+ "clean": not entries,
72
+ "entries": entries,
73
+ "excluded": excluded,
74
+ }, ensure_ascii=False, indent=2))
75
+ return 1 if (args.check_clean and entries) else 0
@@ -0,0 +1,39 @@
1
+ """`okstra worktree-lookup` 의 진입점.
2
+
3
+ 핸들러는 [worktree_cli.lookup] 에 있다. 이 모듈은 자기 이름의 파서만 갖는다 —
4
+ `python3 -m` 대상 하나가 `okstra` 명령 하나에 대응해야 `--help` 의 prog 와
5
+ 사용법이 사용자가 실제로 치는 명령과 같아진다.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+
11
+ from .worktree_cli import lookup
12
+
13
+ _CLI_EPILOG = r"""Usage:
14
+ okstra worktree-lookup <project-id> <task-group> <task-id>
15
+
16
+ The three identifiers are slugified by the runtime before lookup, so
17
+ case/whitespace variants are tolerated.
18
+
19
+ Output: JSON with the registry entry (worktreePath, branch, baseRef,
20
+ status, lastPhase, createdAt). Emits {"ok": true, "entry": null} when
21
+ the task-key has no active worktree.
22
+ """
23
+
24
+
25
+ def main(argv: list[str] | None = None) -> int:
26
+ parser = argparse.ArgumentParser(
27
+ prog="okstra worktree-lookup",
28
+ description="Look up the registered worktree for a task-key.",
29
+ epilog=_CLI_EPILOG,
30
+ formatter_class=argparse.RawDescriptionHelpFormatter,
31
+ )
32
+ parser.add_argument("project_id", metavar="project-id")
33
+ parser.add_argument("task_group", metavar="task-group")
34
+ parser.add_argument("task_id", metavar="task-id")
35
+ return lookup(parser.parse_args(argv))
36
+
37
+
38
+ if __name__ == "__main__":
39
+ raise SystemExit(main())