okstra 0.186.7 → 0.188.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (546) hide show
  1. package/README.md +1 -1
  2. package/dist/cli-registry.d.mts +88 -1
  3. package/dist/cli-registry.mjs +68 -111
  4. package/dist/cli-registry.mjs.map +1 -1
  5. package/dist/commands/execute/render-bundle.mjs +0 -1
  6. package/dist/commands/execute/render-bundle.mjs.map +1 -1
  7. package/dist/commands/execute/run.mjs +8 -3
  8. package/dist/commands/execute/run.mjs.map +1 -1
  9. package/dist/commands/lifecycle/check-project.mjs +1 -14
  10. package/dist/commands/lifecycle/check-project.mjs.map +1 -1
  11. package/dist/commands/lifecycle/config.mjs +38 -40
  12. package/dist/commands/lifecycle/config.mjs.map +1 -1
  13. package/dist/commands/lifecycle/doctor.d.mts +22 -7
  14. package/dist/commands/lifecycle/doctor.mjs +77 -49
  15. package/dist/commands/lifecycle/doctor.mjs.map +1 -1
  16. package/dist/commands/lifecycle/install.d.mts +12 -10
  17. package/dist/commands/lifecycle/install.mjs +107 -40
  18. package/dist/commands/lifecycle/install.mjs.map +1 -1
  19. package/dist/commands/lifecycle/paths.mjs +8 -3
  20. package/dist/commands/lifecycle/paths.mjs.map +1 -1
  21. package/dist/commands/lifecycle/preflight.mjs +2 -1
  22. package/dist/commands/lifecycle/preflight.mjs.map +1 -1
  23. package/dist/commands/lifecycle/setup.mjs +22 -36
  24. package/dist/commands/lifecycle/setup.mjs.map +1 -1
  25. package/dist/commands/lifecycle/uninstall.d.mts +4 -2
  26. package/dist/commands/lifecycle/uninstall.mjs +59 -15
  27. package/dist/commands/lifecycle/uninstall.mjs.map +1 -1
  28. package/dist/commands/memory/memory.mjs +7 -1
  29. package/dist/commands/memory/memory.mjs.map +1 -1
  30. package/dist/lib/helper-scripts.d.mts +1 -1
  31. package/dist/lib/helper-scripts.mjs +10 -18
  32. package/dist/lib/helper-scripts.mjs.map +1 -1
  33. package/dist/lib/host-config.d.mts +72 -0
  34. package/dist/lib/host-config.mjs +404 -0
  35. package/dist/lib/host-config.mjs.map +1 -0
  36. package/dist/lib/host-registry-client.d.mts +2 -2
  37. package/dist/lib/host-registry-client.mjs +0 -3
  38. package/dist/lib/host-registry-client.mjs.map +1 -1
  39. package/dist/lib/install-assets.d.mts +1 -0
  40. package/dist/lib/install-assets.mjs +4 -0
  41. package/dist/lib/install-assets.mjs.map +1 -1
  42. package/dist/lib/proc.d.mts +2 -0
  43. package/dist/lib/proc.mjs +12 -0
  44. package/dist/lib/proc.mjs.map +1 -1
  45. package/dist/lib/python-command.d.mts +2 -0
  46. package/dist/lib/python-command.mjs +52 -0
  47. package/dist/lib/python-command.mjs.map +1 -0
  48. package/dist/lib/python-helper.d.mts +2 -5
  49. package/dist/lib/python-helper.mjs +3 -52
  50. package/dist/lib/python-helper.mjs.map +1 -1
  51. package/dist/lib/runtime-payload.d.mts +23 -0
  52. package/dist/lib/runtime-payload.mjs +57 -0
  53. package/dist/lib/runtime-payload.mjs.map +1 -0
  54. package/dist/lib/types.d.mts +24 -13
  55. package/docs/architecture/storage-model.md +21 -5
  56. package/docs/architecture.md +39 -38
  57. package/docs/cli.md +55 -76
  58. package/docs/coding-rules.md +295 -0
  59. package/docs/container.md +10 -36
  60. package/docs/contributor-change-matrix.md +1 -1
  61. package/docs/for-ai/skills/okstra-code-review.md +0 -1
  62. package/docs/for-ai/skills/okstra-container-build.md +8 -41
  63. package/docs/for-ai/skills/okstra-inspect.md +4 -4
  64. package/docs/for-ai/skills/okstra-manager.md +2 -2
  65. package/docs/for-ai/skills/okstra-rollup.md +0 -1
  66. package/docs/for-ai/skills/okstra-run.md +3 -3
  67. package/docs/for-ai/skills/okstra-user-response.md +0 -1
  68. package/docs/performance-improvement-plan-v2.md +1 -1
  69. package/docs/project-structure-overview.md +62 -62
  70. package/docs/task-process/README.md +3 -3
  71. package/docs/task-process/common-flow.md +1 -1
  72. package/docs/task-process/error-analysis.md +3 -3
  73. package/docs/task-process/implementation-planning.md +3 -1
  74. package/docs/task-process/release-handoff.md +1 -1
  75. package/package.json +3 -2
  76. package/runtime/BUILD.json +2 -2
  77. package/runtime/agents/workers/claude-worker.md +6 -11
  78. package/runtime/agents/workers/report-writer-worker.md +1 -1
  79. package/runtime/bin/lib/okstra/cli.sh +4 -0
  80. package/runtime/bin/lib/okstra/globals.sh +2 -0
  81. package/runtime/bin/lib/okstra/interactive.sh +26 -173
  82. package/runtime/bin/lib/okstra/project-resolver.sh +23 -61
  83. package/runtime/bin/lib/okstra/usage.sh +3 -3
  84. package/runtime/bin/okstra-compact-reminder.sh +1 -5
  85. package/runtime/bin/okstra-error-log.py +19 -2
  86. package/runtime/bin/okstra-import-check.py +26 -0
  87. package/runtime/bin/okstra-inject-report-index.py +5 -4
  88. package/runtime/bin/okstra-provider-exec.py +22 -17
  89. package/runtime/bin/okstra-render-final-report.py +16 -6
  90. package/runtime/bin/okstra-render-report-views.py +47 -22
  91. package/runtime/bin/okstra-report-translate.py +30 -18
  92. package/runtime/bin/okstra-spawn-followups.py +4 -0
  93. package/runtime/bin/okstra-token-usage.py +3 -0
  94. package/runtime/bin/okstra.sh +4 -2
  95. package/runtime/bin/okstra_bootstrap.py +58 -0
  96. package/runtime/prompts/coding-preflight/overview.md +1 -1
  97. package/runtime/prompts/duties/planning-worker.md +1 -1
  98. package/runtime/prompts/launch.template.md +35 -9
  99. package/runtime/prompts/lead/adapters/cmux.md +3 -3
  100. package/runtime/prompts/lead/convergence.md +99 -88
  101. package/runtime/prompts/lead/okstra-lead-contract.md +63 -12
  102. package/runtime/prompts/lead/plan-body-verification.md +65 -46
  103. package/runtime/prompts/lead/report-writer.md +35 -5
  104. package/runtime/prompts/lead/team-contract.md +4 -1
  105. package/runtime/prompts/profiles/_clarification-recommendation.md +2 -1
  106. package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
  107. package/runtime/prompts/profiles/_common-contract.md +3 -3
  108. package/runtime/prompts/profiles/_coverage-critic.md +1 -1
  109. package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
  110. package/runtime/prompts/profiles/_implementation-executor.md +3 -1
  111. package/runtime/prompts/profiles/_implementation-verifier.md +16 -2
  112. package/runtime/prompts/profiles/error-analysis.md +4 -3
  113. package/runtime/prompts/profiles/final-verification.md +2 -2
  114. package/runtime/prompts/profiles/implementation-planning.md +22 -17
  115. package/runtime/prompts/profiles/implementation.md +3 -2
  116. package/runtime/prompts/profiles/requirements-discovery.md +2 -1
  117. package/runtime/prompts/wizard/prompts.ko.json +29 -8
  118. package/runtime/python/okstra_ctl/__init__.py +6 -27
  119. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +4 -5
  120. package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +21 -2
  121. package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +3 -12
  122. package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +63 -22
  123. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +20 -21
  124. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +37 -20
  125. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +86 -14
  126. package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +12 -12
  127. package/runtime/python/okstra_ctl/adapters/runtime/cmux.py +4 -7
  128. package/runtime/python/okstra_ctl/agent/__init__.py +1 -0
  129. package/runtime/python/okstra_ctl/{agent_activity.py → agent/activity.py} +16 -2
  130. package/runtime/python/okstra_ctl/{agent_invocation.py → agent/invocation.py} +34 -5
  131. package/runtime/python/okstra_ctl/agent/prompt_cli/__init__.py +19 -0
  132. package/runtime/python/okstra_ctl/agent/prompt_cli/__main__.py +11 -0
  133. package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +228 -0
  134. package/runtime/python/okstra_ctl/agent/prompt_cli/dynamic_verifier.py +155 -0
  135. package/runtime/python/okstra_ctl/agent/prompt_cli/emit.py +66 -0
  136. package/runtime/python/okstra_ctl/agent/prompt_cli/inputs.py +138 -0
  137. package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +605 -0
  138. package/runtime/python/okstra_ctl/agent/prompt_cli/results.py +197 -0
  139. package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +107 -0
  140. package/runtime/python/okstra_ctl/analysis_packet.py +47 -4
  141. package/runtime/python/okstra_ctl/approval_decisions.py +287 -10
  142. package/runtime/python/okstra_ctl/assignment_resolver.py +0 -12
  143. package/runtime/python/okstra_ctl/attempt_evidence.py +12 -24
  144. package/runtime/python/okstra_ctl/blocking_checks.py +251 -0
  145. package/runtime/python/okstra_ctl/brief_frontmatter.py +0 -7
  146. package/runtime/python/okstra_ctl/clarification_items/__init__.py +102 -0
  147. package/runtime/python/okstra_ctl/clarification_items/carry.py +224 -0
  148. package/runtime/python/okstra_ctl/clarification_items/dispositions.py +135 -0
  149. package/runtime/python/okstra_ctl/clarification_items/parsing.py +253 -0
  150. package/runtime/python/okstra_ctl/clarification_items/rows.py +115 -0
  151. package/runtime/python/okstra_ctl/clarification_items/scan.py +215 -0
  152. package/runtime/python/okstra_ctl/clarification_items/sidecars.py +211 -0
  153. package/runtime/python/okstra_ctl/cmux.py +38 -31
  154. package/runtime/python/okstra_ctl/code_review_target.py +163 -1
  155. package/runtime/python/okstra_ctl/conformance.py +23 -0
  156. package/runtime/python/okstra_ctl/container.py +38 -421
  157. package/runtime/python/okstra_ctl/context_cost.py +32 -4
  158. package/runtime/python/okstra_ctl/contract_graph_cli.py +7 -0
  159. package/runtime/python/okstra_ctl/convergence.py +107 -16
  160. package/runtime/python/okstra_ctl/convergence_critic_prompt.py +345 -0
  161. package/runtime/python/okstra_ctl/convergence_engine.py +382 -70
  162. package/runtime/python/okstra_ctl/convergence_store.py +5 -29
  163. package/runtime/python/okstra_ctl/design_prep.py +108 -12
  164. package/runtime/python/okstra_ctl/design_snapshot.py +11 -1
  165. package/runtime/python/okstra_ctl/design_surfaces.py +29 -2
  166. package/runtime/python/okstra_ctl/dispatch_core.py +236 -49
  167. package/runtime/python/okstra_ctl/dispatch_state.py +417 -41
  168. package/runtime/python/okstra_ctl/doctor_cli.py +48 -0
  169. package/runtime/python/okstra_ctl/domain/worker_exec.py +10 -5
  170. package/runtime/python/okstra_ctl/domain/worker_presentation.py +28 -1
  171. package/runtime/python/okstra_ctl/domain/worker_stream.py +52 -21
  172. package/runtime/python/okstra_ctl/domain/write_policy.py +219 -0
  173. package/runtime/python/okstra_ctl/entrypoints/hosts.py +9 -2
  174. package/runtime/python/okstra_ctl/error_log_core.py +1 -1
  175. package/runtime/python/okstra_ctl/error_report.py +14 -0
  176. package/runtime/python/okstra_ctl/error_zip.py +18 -2
  177. package/runtime/python/okstra_ctl/exact_coverage.py +5 -0
  178. package/runtime/python/okstra_ctl/execution_identity.py +100 -7
  179. package/runtime/python/okstra_ctl/execution_manifest.py +67 -20
  180. package/runtime/python/okstra_ctl/execution_mutation_audit.py +90 -8
  181. package/runtime/python/okstra_ctl/final_report_paths.py +17 -0
  182. package/runtime/python/okstra_ctl/final_report_schema.py +319 -17
  183. package/runtime/python/okstra_ctl/git_reconcile.py +22 -2
  184. package/runtime/python/okstra_ctl/handoff.py +24 -1
  185. package/runtime/python/okstra_ctl/ids.py +6 -16
  186. package/runtime/python/okstra_ctl/implementation_direction.py +142 -33
  187. package/runtime/python/okstra_ctl/implementation_options.py +32 -13
  188. package/runtime/python/okstra_ctl/implementation_outcome.py +5 -2
  189. package/runtime/python/okstra_ctl/improvement_lenses.py +0 -14
  190. package/runtime/python/okstra_ctl/incremental_carry.py +69 -8
  191. package/runtime/python/okstra_ctl/incremental_scope.py +173 -12
  192. package/runtime/python/okstra_ctl/index.py +2 -2
  193. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +48 -8
  194. package/runtime/python/okstra_ctl/interactive_cli.py +223 -0
  195. package/runtime/python/okstra_ctl/json_boundary.py +10 -0
  196. package/runtime/python/okstra_ctl/listing.py +0 -91
  197. package/runtime/python/okstra_ctl/locks.py +5 -19
  198. package/runtime/python/okstra_ctl/log_report.py +14 -0
  199. package/runtime/python/okstra_ctl/manager_cli.py +22 -3
  200. package/runtime/python/okstra_ctl/manager_launch.py +4 -8
  201. package/runtime/python/okstra_ctl/material.py +2 -2
  202. package/runtime/python/okstra_ctl/migrate.py +23 -1
  203. package/runtime/python/okstra_ctl/model_cli.py +19 -5
  204. package/runtime/python/okstra_ctl/model_discovery.py +46 -47
  205. package/runtime/python/okstra_ctl/model_io/__init__.py +1 -0
  206. package/runtime/python/okstra_ctl/model_io/lines.py +163 -0
  207. package/runtime/python/okstra_ctl/model_io/references.py +370 -0
  208. package/runtime/python/okstra_ctl/model_io/renderers.py +498 -0
  209. package/runtime/python/okstra_ctl/model_io_cli.py +30 -945
  210. package/runtime/python/okstra_ctl/model_pool.py +10 -6
  211. package/runtime/python/okstra_ctl/models.py +8 -8
  212. package/runtime/python/okstra_ctl/mutation_recovery.py +5 -63
  213. package/runtime/python/okstra_ctl/next_phase.py +121 -69
  214. package/runtime/python/okstra_ctl/pane_reclaim.py +23 -0
  215. package/runtime/python/okstra_ctl/pane_title.py +11 -8
  216. package/runtime/python/okstra_ctl/path_hints.py +36 -11
  217. package/runtime/python/okstra_ctl/paths.py +76 -8
  218. package/runtime/python/okstra_ctl/plan_items.py +132 -16
  219. package/runtime/python/okstra_ctl/plan_items_cli.py +486 -36
  220. package/runtime/python/okstra_ctl/plan_run_root.py +1 -1
  221. package/runtime/python/okstra_ctl/plan_validate_cli.py +51 -0
  222. package/runtime/python/okstra_ctl/plan_verify_cli.py +80 -0
  223. package/runtime/python/okstra_ctl/prepare_error.py +20 -0
  224. package/runtime/python/okstra_ctl/prior_planning.py +157 -0
  225. package/runtime/python/okstra_ctl/profile_show.py +18 -1
  226. package/runtime/python/okstra_ctl/project_setup_cli.py +140 -0
  227. package/runtime/python/okstra_ctl/recap.py +22 -1
  228. package/runtime/python/okstra_ctl/reconcile.py +84 -0
  229. package/runtime/python/okstra_ctl/registry/host_registry.py +18 -1
  230. package/runtime/python/okstra_ctl/render.py +98 -32
  231. package/runtime/python/okstra_ctl/render_final_report.py +4 -8
  232. package/runtime/python/okstra_ctl/report_assembly.py +297 -7
  233. package/runtime/python/okstra_ctl/report_contract.py +30 -7
  234. package/runtime/python/okstra_ctl/report_finalize.py +213 -16
  235. package/runtime/python/okstra_ctl/report_html/common.py +2 -2
  236. package/runtime/python/okstra_ctl/report_html/render.py +8 -12
  237. package/runtime/python/okstra_ctl/report_language.py +8 -4
  238. package/runtime/python/okstra_ctl/report_narrative.py +220 -34
  239. package/runtime/python/okstra_ctl/report_projections.py +8 -2
  240. package/runtime/python/okstra_ctl/report_synthesis_packet.py +393 -1
  241. package/runtime/python/okstra_ctl/report_translation.py +2 -35
  242. package/runtime/python/okstra_ctl/report_view_artifacts.py +42 -2
  243. package/runtime/python/okstra_ctl/report_views.py +4 -22
  244. package/runtime/python/okstra_ctl/resolve_task_key.py +13 -0
  245. package/runtime/python/okstra_ctl/rollup.py +13 -0
  246. package/runtime/python/okstra_ctl/run.py +639 -138
  247. package/runtime/python/okstra_ctl/run_audit.py +13 -0
  248. package/runtime/python/okstra_ctl/run_index_row.py +0 -12
  249. package/runtime/python/okstra_ctl/seeding.py +21 -11
  250. package/runtime/python/okstra_ctl/sequence.py +1 -1
  251. package/runtime/python/okstra_ctl/session.py +64 -14
  252. package/runtime/python/okstra_ctl/set_work_status.py +18 -0
  253. package/runtime/python/okstra_ctl/stage_integrate.py +15 -1
  254. package/runtime/python/okstra_ctl/stage_ledger.py +1 -1
  255. package/runtime/python/okstra_ctl/stage_map.py +57 -11
  256. package/runtime/python/okstra_ctl/stage_map_cli.py +83 -0
  257. package/runtime/python/okstra_ctl/stage_map_view.py +65 -0
  258. package/runtime/python/okstra_ctl/stage_targets.py +3 -12
  259. package/runtime/python/okstra_ctl/task_list_cli.py +138 -0
  260. package/runtime/python/okstra_ctl/task_show_cli.py +66 -0
  261. package/runtime/python/okstra_ctl/task_target.py +4 -16
  262. package/runtime/python/okstra_ctl/team.py +49 -2
  263. package/runtime/python/okstra_ctl/time_report.py +17 -2
  264. package/runtime/python/okstra_ctl/usage_report.py +11 -0
  265. package/runtime/python/okstra_ctl/user_response.py +55 -218
  266. package/runtime/python/okstra_ctl/user_response_values.py +242 -0
  267. package/runtime/python/okstra_ctl/validation_contract.py +3 -0
  268. package/runtime/python/okstra_ctl/verdict_blocks.py +28 -8
  269. package/runtime/python/okstra_ctl/verification_target.py +68 -0
  270. package/runtime/python/okstra_ctl/wizard.py +457 -92
  271. package/runtime/python/okstra_ctl/worker_artifacts.py +75 -16
  272. package/runtime/python/okstra_ctl/worker_audit_check.py +22 -0
  273. package/runtime/python/okstra_ctl/worker_dispatch.py +21 -0
  274. package/runtime/python/okstra_ctl/worker_liveness.py +33 -0
  275. package/runtime/python/okstra_ctl/worker_prompt_body.py +15 -2
  276. package/runtime/python/okstra_ctl/worker_prompt_contract.py +98 -7
  277. package/runtime/python/okstra_ctl/worker_prompt_headers.py +24 -2
  278. package/runtime/python/okstra_ctl/worker_prompt_policy.py +18 -3
  279. package/runtime/python/okstra_ctl/worker_request.py +2 -1
  280. package/runtime/python/okstra_ctl/worker_runner.py +58 -17
  281. package/runtime/python/okstra_ctl/worker_state.py +16 -1
  282. package/runtime/python/okstra_ctl/workflow.py +18 -1
  283. package/runtime/python/okstra_ctl/worktree/__init__.py +92 -0
  284. package/runtime/python/okstra_ctl/worktree/cleanliness.py +89 -0
  285. package/runtime/python/okstra_ctl/worktree/decisions.py +127 -0
  286. package/runtime/python/okstra_ctl/worktree/git_ops.py +165 -0
  287. package/runtime/python/okstra_ctl/worktree/linking.py +249 -0
  288. package/runtime/python/okstra_ctl/worktree/naming.py +91 -0
  289. package/runtime/python/okstra_ctl/worktree/provision.py +385 -0
  290. package/runtime/python/okstra_ctl/worktree/sync_config.py +181 -0
  291. package/runtime/python/okstra_ctl/worktree_cli.py +75 -0
  292. package/runtime/python/okstra_ctl/worktree_lookup_cli.py +39 -0
  293. package/runtime/python/okstra_ctl/worktree_registry.py +7 -0
  294. package/runtime/python/okstra_ctl/worktree_status_cli.py +53 -0
  295. package/runtime/python/okstra_ctl/write_policy.py +90 -213
  296. package/runtime/python/okstra_project/__init__.py +0 -4
  297. package/runtime/python/okstra_project/dirs.py +2 -2
  298. package/runtime/python/okstra_project/phase_pointer.py +84 -0
  299. package/runtime/python/okstra_project/slug.py +24 -0
  300. package/runtime/python/okstra_project/state.py +22 -230
  301. package/runtime/python/okstra_token_usage/antigravity.py +50 -12
  302. package/runtime/python/okstra_token_usage/claude.py +9 -1
  303. package/runtime/python/okstra_token_usage/cli.py +3 -1
  304. package/runtime/python/okstra_token_usage/collect.py +171 -36
  305. package/runtime/python/okstra_token_usage/cursor.py +2 -0
  306. package/runtime/python/okstra_token_usage/grok.py +1 -5
  307. package/runtime/python/okstra_token_usage/paths.py +26 -0
  308. package/runtime/python/okstra_token_usage/pricing.py +23 -8
  309. package/runtime/schemas/convergence-critic-results-v1.0.schema.json +5 -0
  310. package/runtime/schemas/execution-manifest-v2.schema.json +10 -10
  311. package/runtime/schemas/final-report-v2.0.schema.json +9 -26
  312. package/runtime/schemas/final-report-v3.0.schema.json +173 -41
  313. package/runtime/schemas/report-narrative-v3.0.schema.json +3 -2
  314. package/runtime/skills/okstra-brief-gen/SKILL.md +35 -2
  315. package/runtime/skills/okstra-container-build/SKILL.md +16 -47
  316. package/runtime/skills/okstra-inspect/facets/history.md +1 -1
  317. package/runtime/skills/okstra-inspect/facets/status.md +7 -6
  318. package/runtime/skills/okstra-pr-gen/SKILL.md +1 -1
  319. package/runtime/skills/okstra-run/SKILL.md +7 -5
  320. package/runtime/templates/report-writer-prompt-preamble.md +3 -1
  321. package/runtime/templates/reports/brief.template.md +6 -2
  322. package/runtime/templates/reports/error-analysis-input.template.md +2 -0
  323. package/runtime/templates/reports/final-verification-input.template.md +2 -0
  324. package/runtime/templates/reports/implementation-input.template.md +7 -1
  325. package/runtime/templates/reports/implementation-planning-input.template.md +4 -0
  326. package/runtime/templates/reports/quick-input.template.md +2 -0
  327. package/runtime/templates/reports/release-handoff-input.template.md +6 -1
  328. package/runtime/templates/reports/report.js +20 -3
  329. package/runtime/templates/reports/schedule.template.md +2 -0
  330. package/runtime/templates/reports/settings.template.json +0 -10
  331. package/runtime/templates/reports/task-brief.template.md +3 -0
  332. package/runtime/templates/reports/user-response.template.md +7 -3
  333. package/runtime/validators/checks/fixtures-01.py +57 -0
  334. package/runtime/validators/checks/fixtures-02.py +565 -0
  335. package/runtime/validators/checks/runners-01.py +108 -0
  336. package/runtime/validators/checks/validate-assets-01.py +60 -0
  337. package/runtime/validators/checks/validate-prompt-metadata-01.py +261 -0
  338. package/runtime/validators/checks/validate-tasks-01.py +46 -0
  339. package/runtime/validators/checks/validate-tasks-02.py +85 -0
  340. package/runtime/validators/checks/validate-tasks-03.py +61 -0
  341. package/runtime/validators/checks/validate-tasks-04.py +118 -0
  342. package/runtime/validators/forbidden_actions.py +73 -3
  343. package/runtime/validators/lib/common.sh +5 -0
  344. package/runtime/validators/lib/fixtures.sh +7 -591
  345. package/runtime/validators/lib/paths.sh +13 -0
  346. package/runtime/validators/lib/runners.sh +6 -104
  347. package/runtime/validators/lib/summary.sh +1 -1
  348. package/runtime/validators/lib/validate-assets.sh +6 -56
  349. package/runtime/validators/lib/validate-prompt-metadata.sh +6 -257
  350. package/runtime/validators/lib/validate-tasks.sh +9 -294
  351. package/runtime/validators/validate-implementation-plan-stages.py +4 -4
  352. package/runtime/validators/validate-run.py +1363 -2643
  353. package/runtime/validators/validate-workflow.sh +56 -16
  354. package/runtime/validators/validate_improvement_report.py +2 -1
  355. package/runtime/validators/validate_session_conformance.py +295 -49
  356. package/dist/commands/execute/agent-prompt.d.mts +0 -1
  357. package/dist/commands/execute/agent-prompt.mjs +0 -24
  358. package/dist/commands/execute/agent-prompt.mjs.map +0 -1
  359. package/dist/commands/execute/codex-dispatch.d.mts +0 -3
  360. package/dist/commands/execute/codex-dispatch.mjs +0 -6
  361. package/dist/commands/execute/codex-dispatch.mjs.map +0 -1
  362. package/dist/commands/execute/codex-run.d.mts +0 -3
  363. package/dist/commands/execute/codex-run.mjs +0 -62
  364. package/dist/commands/execute/codex-run.mjs.map +0 -1
  365. package/dist/commands/execute/convergence.d.mts +0 -1
  366. package/dist/commands/execute/convergence.mjs +0 -38
  367. package/dist/commands/execute/convergence.mjs.map +0 -1
  368. package/dist/commands/execute/error-log.d.mts +0 -1
  369. package/dist/commands/execute/error-log.mjs +0 -18
  370. package/dist/commands/execute/error-log.mjs.map +0 -1
  371. package/dist/commands/execute/git-reconcile.d.mts +0 -1
  372. package/dist/commands/execute/git-reconcile.mjs +0 -30
  373. package/dist/commands/execute/git-reconcile.mjs.map +0 -1
  374. package/dist/commands/execute/handoff.d.mts +0 -1
  375. package/dist/commands/execute/handoff.mjs +0 -31
  376. package/dist/commands/execute/handoff.mjs.map +0 -1
  377. package/dist/commands/execute/incremental-carry.d.mts +0 -1
  378. package/dist/commands/execute/incremental-carry.mjs +0 -20
  379. package/dist/commands/execute/incremental-carry.mjs.map +0 -1
  380. package/dist/commands/execute/incremental-scope.d.mts +0 -1
  381. package/dist/commands/execute/incremental-scope.mjs +0 -29
  382. package/dist/commands/execute/incremental-scope.mjs.map +0 -1
  383. package/dist/commands/execute/integrate-stages.d.mts +0 -1
  384. package/dist/commands/execute/integrate-stages.mjs +0 -24
  385. package/dist/commands/execute/integrate-stages.mjs.map +0 -1
  386. package/dist/commands/execute/pane-title.d.mts +0 -1
  387. package/dist/commands/execute/pane-title.mjs +0 -20
  388. package/dist/commands/execute/pane-title.mjs.map +0 -1
  389. package/dist/commands/execute/plan-items.d.mts +0 -1
  390. package/dist/commands/execute/plan-items.mjs +0 -9
  391. package/dist/commands/execute/plan-items.mjs.map +0 -1
  392. package/dist/commands/execute/plan-validate.d.mts +0 -1
  393. package/dist/commands/execute/plan-validate.mjs +0 -68
  394. package/dist/commands/execute/plan-validate.mjs.map +0 -1
  395. package/dist/commands/execute/plan-verify.d.mts +0 -1
  396. package/dist/commands/execute/plan-verify.mjs +0 -43
  397. package/dist/commands/execute/plan-verify.mjs.map +0 -1
  398. package/dist/commands/execute/spawn-followups.d.mts +0 -1
  399. package/dist/commands/execute/spawn-followups.mjs +0 -22
  400. package/dist/commands/execute/spawn-followups.mjs.map +0 -1
  401. package/dist/commands/execute/team.d.mts +0 -3
  402. package/dist/commands/execute/team.mjs +0 -66
  403. package/dist/commands/execute/team.mjs.map +0 -1
  404. package/dist/commands/execute/token-usage.d.mts +0 -1
  405. package/dist/commands/execute/token-usage.mjs +0 -19
  406. package/dist/commands/execute/token-usage.mjs.map +0 -1
  407. package/dist/commands/execute/worker-audit-check.d.mts +0 -1
  408. package/dist/commands/execute/worker-audit-check.mjs +0 -34
  409. package/dist/commands/execute/worker-audit-check.mjs.map +0 -1
  410. package/dist/commands/execute/worker-dispatch.d.mts +0 -7
  411. package/dist/commands/execute/worker-dispatch.mjs +0 -64
  412. package/dist/commands/execute/worker-dispatch.mjs.map +0 -1
  413. package/dist/commands/execute/worker-state.d.mts +0 -1
  414. package/dist/commands/execute/worker-state.mjs +0 -28
  415. package/dist/commands/execute/worker-state.mjs.map +0 -1
  416. package/dist/commands/execute/worktree-lookup.d.mts +0 -1
  417. package/dist/commands/execute/worktree-lookup.mjs +0 -92
  418. package/dist/commands/execute/worktree-lookup.mjs.map +0 -1
  419. package/dist/commands/execute/worktree-status.d.mts +0 -1
  420. package/dist/commands/execute/worktree-status.mjs +0 -121
  421. package/dist/commands/execute/worktree-status.mjs.map +0 -1
  422. package/dist/commands/inspect/code-review.d.mts +0 -1
  423. package/dist/commands/inspect/code-review.mjs +0 -32
  424. package/dist/commands/inspect/code-review.mjs.map +0 -1
  425. package/dist/commands/inspect/container.d.mts +0 -1
  426. package/dist/commands/inspect/container.mjs +0 -25
  427. package/dist/commands/inspect/container.mjs.map +0 -1
  428. package/dist/commands/inspect/context-cost.d.mts +0 -1
  429. package/dist/commands/inspect/context-cost.mjs +0 -25
  430. package/dist/commands/inspect/context-cost.mjs.map +0 -1
  431. package/dist/commands/inspect/design-prep.d.mts +0 -1
  432. package/dist/commands/inspect/design-prep.mjs +0 -22
  433. package/dist/commands/inspect/design-prep.mjs.map +0 -1
  434. package/dist/commands/inspect/error-report.d.mts +0 -1
  435. package/dist/commands/inspect/error-report.mjs +0 -25
  436. package/dist/commands/inspect/error-report.mjs.map +0 -1
  437. package/dist/commands/inspect/error-zip.d.mts +0 -1
  438. package/dist/commands/inspect/error-zip.mjs +0 -24
  439. package/dist/commands/inspect/error-zip.mjs.map +0 -1
  440. package/dist/commands/inspect/log-report.d.mts +0 -1
  441. package/dist/commands/inspect/log-report.mjs +0 -26
  442. package/dist/commands/inspect/log-report.mjs.map +0 -1
  443. package/dist/commands/inspect/model-io.d.mts +0 -1
  444. package/dist/commands/inspect/model-io.mjs +0 -25
  445. package/dist/commands/inspect/model-io.mjs.map +0 -1
  446. package/dist/commands/inspect/profile-show.d.mts +0 -1
  447. package/dist/commands/inspect/profile-show.mjs +0 -28
  448. package/dist/commands/inspect/profile-show.mjs.map +0 -1
  449. package/dist/commands/inspect/recap.d.mts +0 -1
  450. package/dist/commands/inspect/recap.mjs +0 -30
  451. package/dist/commands/inspect/recap.mjs.map +0 -1
  452. package/dist/commands/inspect/resolve-task-key.d.mts +0 -1
  453. package/dist/commands/inspect/resolve-task-key.mjs +0 -24
  454. package/dist/commands/inspect/resolve-task-key.mjs.map +0 -1
  455. package/dist/commands/inspect/rollup.d.mts +0 -1
  456. package/dist/commands/inspect/rollup.mjs +0 -25
  457. package/dist/commands/inspect/rollup.mjs.map +0 -1
  458. package/dist/commands/inspect/run-audit.d.mts +0 -1
  459. package/dist/commands/inspect/run-audit.mjs +0 -25
  460. package/dist/commands/inspect/run-audit.mjs.map +0 -1
  461. package/dist/commands/inspect/set-work-status.d.mts +0 -1
  462. package/dist/commands/inspect/set-work-status.mjs +0 -29
  463. package/dist/commands/inspect/set-work-status.mjs.map +0 -1
  464. package/dist/commands/inspect/stage-map.d.mts +0 -1
  465. package/dist/commands/inspect/stage-map.mjs +0 -131
  466. package/dist/commands/inspect/stage-map.mjs.map +0 -1
  467. package/dist/commands/inspect/task-list.d.mts +0 -1
  468. package/dist/commands/inspect/task-list.mjs +0 -149
  469. package/dist/commands/inspect/task-list.mjs.map +0 -1
  470. package/dist/commands/inspect/task-show.d.mts +0 -1
  471. package/dist/commands/inspect/task-show.mjs +0 -108
  472. package/dist/commands/inspect/task-show.mjs.map +0 -1
  473. package/dist/commands/inspect/time-report.d.mts +0 -1
  474. package/dist/commands/inspect/time-report.mjs +0 -24
  475. package/dist/commands/inspect/time-report.mjs.map +0 -1
  476. package/dist/commands/inspect/usage-report.d.mts +0 -1
  477. package/dist/commands/inspect/usage-report.mjs +0 -23
  478. package/dist/commands/inspect/usage-report.mjs.map +0 -1
  479. package/dist/commands/inspect/user-response.d.mts +0 -1
  480. package/dist/commands/inspect/user-response.mjs +0 -35
  481. package/dist/commands/inspect/user-response.mjs.map +0 -1
  482. package/dist/commands/inspect/worker-liveness.d.mts +0 -1
  483. package/dist/commands/inspect/worker-liveness.mjs +0 -45
  484. package/dist/commands/inspect/worker-liveness.mjs.map +0 -1
  485. package/dist/commands/lifecycle/contract-check.d.mts +0 -1
  486. package/dist/commands/lifecycle/contract-check.mjs +0 -18
  487. package/dist/commands/lifecycle/contract-check.mjs.map +0 -1
  488. package/dist/commands/lifecycle/migrate.d.mts +0 -1
  489. package/dist/commands/lifecycle/migrate.mjs +0 -30
  490. package/dist/commands/lifecycle/migrate.mjs.map +0 -1
  491. package/dist/commands/lifecycle/model.d.mts +0 -1
  492. package/dist/commands/lifecycle/model.mjs +0 -22
  493. package/dist/commands/lifecycle/model.mjs.map +0 -1
  494. package/dist/commands/manager.d.mts +0 -3
  495. package/dist/commands/manager.mjs +0 -50
  496. package/dist/commands/manager.mjs.map +0 -1
  497. package/dist/commands/report/agent-activity.d.mts +0 -1
  498. package/dist/commands/report/agent-activity.mjs +0 -20
  499. package/dist/commands/report/agent-activity.mjs.map +0 -1
  500. package/dist/commands/report/approval-decision.d.mts +0 -1
  501. package/dist/commands/report/approval-decision.mjs +0 -21
  502. package/dist/commands/report/approval-decision.mjs.map +0 -1
  503. package/dist/commands/report/design-snapshot.d.mts +0 -1
  504. package/dist/commands/report/design-snapshot.mjs +0 -19
  505. package/dist/commands/report/design-snapshot.mjs.map +0 -1
  506. package/dist/commands/report/finalize.d.mts +0 -4
  507. package/dist/commands/report/finalize.mjs +0 -64
  508. package/dist/commands/report/finalize.mjs.map +0 -1
  509. package/dist/commands/report/inject-report-index.d.mts +0 -1
  510. package/dist/commands/report/inject-report-index.mjs +0 -21
  511. package/dist/commands/report/inject-report-index.mjs.map +0 -1
  512. package/dist/commands/report/render-final-report.d.mts +0 -1
  513. package/dist/commands/report/render-final-report.mjs +0 -23
  514. package/dist/commands/report/render-final-report.mjs.map +0 -1
  515. package/dist/commands/report/render-views.d.mts +0 -1
  516. package/dist/commands/report/render-views.mjs +0 -27
  517. package/dist/commands/report/render-views.mjs.map +0 -1
  518. package/dist/commands/report/translate.d.mts +0 -1
  519. package/dist/commands/report/translate.mjs +0 -33
  520. package/dist/commands/report/translate.mjs.map +0 -1
  521. package/runtime/bin/lib/okstra/tmux-pane.sh +0 -40
  522. package/runtime/bin/lib/okstra-ctl/cmd-batch.sh +0 -59
  523. package/runtime/bin/lib/okstra-ctl/cmd-list.sh +0 -35
  524. package/runtime/bin/lib/okstra-ctl/cmd-open.sh +0 -36
  525. package/runtime/bin/lib/okstra-ctl/cmd-projects.sh +0 -26
  526. package/runtime/bin/lib/okstra-ctl/cmd-reconcile.sh +0 -29
  527. package/runtime/bin/lib/okstra-ctl/cmd-reindex.sh +0 -38
  528. package/runtime/bin/lib/okstra-ctl/cmd-rerun.sh +0 -345
  529. package/runtime/bin/lib/okstra-ctl/cmd-show.sh +0 -27
  530. package/runtime/bin/lib/okstra-ctl/cmd-tail.sh +0 -92
  531. package/runtime/bin/lib/okstra-ctl/main.sh +0 -41
  532. package/runtime/bin/lib/okstra-ctl/prepare.sh +0 -31
  533. package/runtime/bin/lib/okstra-ctl/usage.sh +0 -23
  534. package/runtime/bin/okstra-central.sh +0 -152
  535. package/runtime/bin/okstra-incremental-carry.py +0 -10
  536. package/runtime/bin/okstra-incremental-scope.py +0 -10
  537. package/runtime/bin/okstra-team-reconcile.sh +0 -36
  538. package/runtime/python/okstra_ctl/agent_prompt_cli.py +0 -1156
  539. package/runtime/python/okstra_ctl/batch.py +0 -60
  540. package/runtime/python/okstra_ctl/clarification_items.py +0 -1050
  541. package/runtime/python/okstra_ctl/container_registry.py +0 -72
  542. package/runtime/python/okstra_ctl/improvement_assignment.py +0 -61
  543. package/runtime/python/okstra_ctl/resolver.py +0 -54
  544. package/runtime/python/okstra_ctl/team_reconcile.py +0 -275
  545. package/runtime/python/okstra_ctl/tmux.py +0 -134
  546. package/runtime/python/okstra_ctl/worktree.py +0 -1099
@@ -0,0 +1,165 @@
1
+ """worktree 가 쓰는 git 명령 한 겹.
2
+
3
+ `_git` 은 `check=False` 라서 호출부가 returncode 를 보고 판단한다. 이 층은
4
+ okstra 의 규칙을 하나도 모른다 — 브랜치가 존재하는지, HEAD 가 무엇인지,
5
+ 머지가 충돌했는지 같은 git 사실만 답한다. okstra 가 무엇을 무시하기로 했는지는
6
+ `cleanliness` 가, 무엇을 옮기기로 했는지는 `sync_config` 가 안다.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import subprocess
11
+ from pathlib import Path
12
+
13
+
14
+ def _git(cwd: Path, *args: str) -> subprocess.CompletedProcess:
15
+ return subprocess.run(
16
+ ["git", "-C", str(cwd), *args],
17
+ capture_output=True, text=True, check=False,
18
+ )
19
+
20
+
21
+ def _is_inside_non_main_worktree(project_root: Path) -> bool:
22
+ """True iff project_root is inside a git worktree that is NOT the
23
+ repository's main checkout. Detection rule: `--git-dir` (per-worktree
24
+ .git pointer) differs from `--git-common-dir` (shared object store).
25
+ """
26
+ common = _git(project_root, "rev-parse", "--git-common-dir")
27
+ per_tree = _git(project_root, "rev-parse", "--git-dir")
28
+ if common.returncode != 0 or per_tree.returncode != 0:
29
+ return False
30
+ common_abs = (project_root / common.stdout.strip()).resolve()
31
+ per_tree_abs = (project_root / per_tree.stdout.strip()).resolve()
32
+ return common_abs != per_tree_abs
33
+
34
+
35
+ def is_git_work_tree(project_root: Path) -> bool:
36
+ """project_root 가 git work tree 내부인지 판정하는 public git-introspection seam.
37
+
38
+ git 미설치(FileNotFoundError) 를 포함한 모든 실패는 False — 호출자(migrate
39
+ 등)가 non-git 레이아웃으로 안전하게 폴백할 수 있도록 한다. 과거 migrate.py
40
+ 가 같은 판정을 자체 구현(`--show-toplevel`)했는데 이 seam 으로 통합한다."""
41
+ try:
42
+ res = _git(project_root, "rev-parse", "--is-inside-work-tree")
43
+ except (OSError, FileNotFoundError):
44
+ return False
45
+ return res.returncode == 0 and res.stdout.strip() == "true"
46
+
47
+
48
+ def _branch_exists(project_root: Path, branch: str) -> bool:
49
+ res = _git(project_root, "rev-parse", "--verify", "--quiet", f"refs/heads/{branch}")
50
+ return res.returncode == 0
51
+
52
+
53
+ def _branch_checkout_path(project_root: Path, branch: str) -> str:
54
+ """The worktree that currently has *branch* checked out, or "" when none does.
55
+
56
+ `git branch -D` refuses to delete a checked-out branch, so an existing-branch
57
+ error that only recommends `-D` is unfollowable whenever the branch is the
58
+ main checkout's HEAD. Callers use this to name the extra step instead.
59
+ """
60
+ res = _git(project_root, "worktree", "list", "--porcelain")
61
+ if res.returncode != 0:
62
+ return ""
63
+ current = ""
64
+ for line in res.stdout.splitlines():
65
+ if line.startswith("worktree "):
66
+ current = line[len("worktree "):].strip()
67
+ elif line.strip() == f"branch refs/heads/{branch}":
68
+ return current
69
+ return ""
70
+
71
+
72
+ def _branch_exists_message(project_root: Path, branch: str, kind: str) -> str:
73
+ """Existing-branch error text whose remedy is actually runnable."""
74
+ checkout = _branch_checkout_path(project_root, branch)
75
+ if checkout:
76
+ return (
77
+ f"{kind} worktree branch already exists: {branch}, and it is checked "
78
+ f"out at {checkout}. `git branch -D {branch}` will refuse while it is "
79
+ f"checked out — switch that checkout to another branch first "
80
+ f"(`git -C {checkout} switch <other-branch>`), then delete it, or "
81
+ "choose a different work-category before retrying."
82
+ )
83
+ return (
84
+ f"{kind} worktree branch already exists: {branch}. "
85
+ f"Delete it (`git branch -D {branch}`) or choose a different "
86
+ "work-category before retrying."
87
+ )
88
+
89
+
90
+ def _head_sha(cwd: Path) -> str:
91
+ res = _git(cwd, "rev-parse", "HEAD")
92
+ if res.returncode != 0:
93
+ return ""
94
+ return res.stdout.strip()
95
+
96
+
97
+ def _resolve_commit_sha(cwd: Path, ref: str) -> str:
98
+ """Resolve a user-supplied ref (branch, tag, short/long SHA) to a full
99
+ commit SHA in `cwd`. Returns empty string when the ref is not resolvable
100
+ so the caller can raise a contextual error.
101
+ """
102
+ res = _git(cwd, "rev-parse", "--verify", "--quiet", f"{ref}^{{commit}}")
103
+ if res.returncode != 0:
104
+ return ""
105
+ return res.stdout.strip()
106
+
107
+
108
+ def main_worktree_path(project_root: Path) -> Path:
109
+ """Locate the repository's MAIN worktree (the original checkout).
110
+
111
+ `git worktree list --porcelain` lists worktrees in a stable order
112
+ where the first `worktree <path>` block is the main checkout.
113
+ Falls back to `project_root` if parsing fails — caller still gets
114
+ a working path, sync-dir links just point at the caller's tree
115
+ (the prior behaviour).
116
+ """
117
+ res = _git(project_root, "worktree", "list", "--porcelain")
118
+ if res.returncode != 0:
119
+ return project_root
120
+ for line in res.stdout.splitlines():
121
+ if line.startswith("worktree "):
122
+ return Path(line[len("worktree "):].strip())
123
+ return project_root
124
+
125
+
126
+ def is_ancestor(cwd, commit: str, head: str) -> bool:
127
+ """True iff `commit` is an ancestor of `head` (both non-empty)."""
128
+ if not commit or not head:
129
+ return False
130
+ return _git(Path(cwd), "merge-base", "--is-ancestor", commit, head).returncode == 0
131
+
132
+
133
+ class MergeError(RuntimeError):
134
+ """`git merge` 가 내용 충돌이 아닌 사유로 실패했을 때(추적되지 않은 파일
135
+ 덮어쓰기, 진행 중인 머지 잔존, 없는 브랜치 등) 던진다. 충돌 파일이 0개인
136
+ 실패를 '충돌'로 둔갑시키지 않고 git stderr 를 그대로 노출해 호출자가 올바른
137
+ 복구 경로로 안내하도록 한다."""
138
+
139
+
140
+ def merge_branch(cwd, branch: str, *, no_ff: bool) -> list[str] | None:
141
+ """`branch` 를 cwd 의 현재 HEAD 에 머지한다. 성공이면 None, 내용 충돌이면
142
+ abort 후 충돌 파일 목록을 반환한다(최소 1개). 충돌이 아닌 머지 실패는
143
+ MergeError 로 던진다 — 충돌 0개를 충돌로 보고하지 않기 위함. 호출자가 충돌
144
+ 목록을 HandoffConflict/IntegrateError 등 적절한 예외로 변환한다 — 머지·
145
+ 충돌감지·abort 시퀀스의 단일 지점."""
146
+ args = ["merge", "--no-ff", "--no-edit", branch] if no_ff \
147
+ else ["merge", "--no-edit", branch]
148
+ res = _git(Path(cwd), *args)
149
+ if res.returncode == 0:
150
+ return None
151
+ conflicts = _git(Path(cwd), "diff", "--name-only",
152
+ "--diff-filter=U").stdout.split()
153
+ _git(Path(cwd), "merge", "--abort")
154
+ if not conflicts:
155
+ raise MergeError((res.stderr or res.stdout).strip()
156
+ or f"git merge {branch} failed (exit={res.returncode})")
157
+ return conflicts
158
+
159
+
160
+ def remove_worktree_force(cwd, worktree_path) -> subprocess.CompletedProcess:
161
+ """워크트리를 강제 제거한다. provision_*_worktree 가 워크트리에 .okstra·
162
+ synced 디렉터리/파일 symlink(untracked) 를 깔기 때문에 plain remove 는 그
163
+ 잔여물 때문에 git 이 거부한다 — dirty 게이트로 실사용자 변경을 막은 뒤
164
+ --force 로 okstra 잔여물만 정리하는 단일 지점."""
165
+ return _git(Path(cwd), "worktree", "remove", "--force", str(worktree_path))
@@ -0,0 +1,249 @@
1
+ """메인 체크아웃의 공유 상태를 새 worktree 안에 재현한다.
2
+
3
+ `sync_config` 가 고른 목록을 실제로 심링크(또는 스냅샷 복사)하는 층이다. 방식이
4
+ 세 가지이고 이유가 각각 다르다 — 디렉터리 통째 심링크는 새로 생기는 최상위
5
+ 항목이 메인으로 가게 하고, 자식별 심링크는 git 이 심링크된 디렉터리를 파일
6
+ 하나로 보는 탓에 `.claude` 가 `?? .claude` 로 뜨는 것을 막으며, 스냅샷 복사는
7
+ 쓰기 핸들을 공유하지 않으려는 픽스처용이다.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ import shutil
13
+ import stat
14
+ from pathlib import Path
15
+
16
+ from ..seeding import (
17
+ SettingsLinkError,
18
+ ensure_project_settings_symlink,
19
+ )
20
+ from .sync_config import (
21
+ CHILD_LINKED_SYNC_DIRS,
22
+ _resolve_snapshot_files,
23
+ _resolve_sync_dirs,
24
+ _resolve_sync_files,
25
+ )
26
+
27
+
28
+ def _link_sync_dirs(source_root: Path, worktree_path: Path) -> list[str]:
29
+ """Symlink each configured dir from `source_root` (the MAIN
30
+ worktree) into the new worktree.
31
+
32
+ Skip rules:
33
+ - Source missing in `source_root` → silently skipped.
34
+ - Target path already exists in worktree (e.g. tracked content
35
+ checked out by `git worktree add`) → skipped to avoid clobbering
36
+ version-controlled files.
37
+ - Parent directories are created as needed for nested entries.
38
+
39
+ Entries listed in `CHILD_LINKED_SYNC_DIRS` are materialised as a real
40
+ directory of per-child symlinks instead (see that constant).
41
+
42
+ Returns a list of human-readable notes (one per linked entry) so the
43
+ caller can include them in the provisioning note.
44
+ """
45
+ notes: list[str] = []
46
+ for rel in _resolve_sync_dirs(source_root):
47
+ src = (source_root / rel).resolve()
48
+ if not src.exists():
49
+ continue
50
+ dst = worktree_path / rel
51
+ if dst.exists() or dst.is_symlink():
52
+ continue
53
+ dst.parent.mkdir(parents=True, exist_ok=True)
54
+ if rel in CHILD_LINKED_SYNC_DIRS and src.is_dir():
55
+ _link_dir_children(src, dst)
56
+ notes.append(rel)
57
+ continue
58
+ try:
59
+ os.symlink(src, dst)
60
+ except FileExistsError:
61
+ continue
62
+ notes.append(rel)
63
+ return notes
64
+
65
+
66
+ def _link_dir_children(src: Path, dst: Path) -> None:
67
+ """Create `dst` as a real directory whose entries symlink to `src`'s
68
+ children, so git sees the same directory shape it sees in the main
69
+ checkout (`CHILD_LINKED_SYNC_DIRS`).
70
+
71
+ Each link points at the MAIN checkout's child rather than its resolved
72
+ target, so a child that is itself a symlink (e.g. `.claude/settings.local.json`
73
+ → `~/.okstra/templates/settings.local.json`) keeps following whatever the
74
+ main checkout currently points at. An unreadable source or a child that
75
+ cannot be linked degrades to "that child is absent in this worktree" —
76
+ provisioning is not worth failing over host config.
77
+ """
78
+ dst.mkdir(parents=True, exist_ok=True)
79
+ try:
80
+ children = sorted(src.iterdir())
81
+ except OSError:
82
+ return
83
+ for child in children:
84
+ link = dst / child.name
85
+ if link.exists() or link.is_symlink():
86
+ continue
87
+ try:
88
+ os.symlink(child, link)
89
+ except OSError:
90
+ continue
91
+
92
+
93
+ def _link_sync_files(source_root: Path, worktree_path: Path) -> list[str]:
94
+ """File-level counterpart to `_link_sync_dirs` (FU-V2).
95
+
96
+ Same skip rules: missing source → skipped silently; pre-existing dst
97
+ (tracked content checked out by `git worktree add`) → skipped to avoid
98
+ clobbering. Symlinks (not copies) keep secrets out of duplicate files —
99
+ the link points back to the MAIN worktree's `.env` so rotating the file
100
+ there is reflected in every active task without re-provisioning.
101
+ """
102
+ notes: list[str] = []
103
+ for rel in _resolve_sync_files(source_root):
104
+ src = (source_root / rel).resolve()
105
+ if not src.exists() or not src.is_file():
106
+ continue
107
+ dst = worktree_path / rel
108
+ if dst.exists() or dst.is_symlink():
109
+ continue
110
+ dst.parent.mkdir(parents=True, exist_ok=True)
111
+ try:
112
+ os.symlink(src, dst)
113
+ except FileExistsError:
114
+ continue
115
+ notes.append(rel)
116
+ return notes
117
+
118
+
119
+ def sync_file_key_names(source_root: Path) -> dict[str, list[str]]:
120
+ """공유되는 sync 파일마다 그 안의 **키 이름**만 모은다 (값은 절대 읽지 않는다).
121
+
122
+ `.env` 는 gitignore 대상이라 브랜치에 속하지 않고, `_link_sync_files` 가 메인
123
+ 체크아웃의 것을 각 worktree 에 심링크로 건다. 값 회전은 그 설계가 노린 것이지만
124
+ **키 이름 변경**은 아직 옛 이름을 읽는 모든 브랜치를 한꺼번에 깨뜨린다 — 코드는
125
+ 브랜치별로 격리되지만 `.env` 는 격리되지 않기 때문이다. 그 사고를 알아보려면
126
+ 프로비저닝 시점의 이름 집합을 남겨야 한다.
127
+
128
+ `KEY=` 꼴이 아닌 파일은 빈 목록이 되어 드리프트 판정에서 자연히 빠진다 —
129
+ sync 파일 목록은 프로젝트가 바꿀 수 있으므로 `.env` 형식을 전제하지 않는다.
130
+ """
131
+ keys: dict[str, list[str]] = {}
132
+ for rel in _resolve_sync_files(source_root):
133
+ src = (source_root / rel).resolve()
134
+ if not src.is_file():
135
+ continue
136
+ try:
137
+ text = src.read_text(encoding="utf-8")
138
+ except (OSError, UnicodeDecodeError):
139
+ continue
140
+ found = []
141
+ for line in text.splitlines():
142
+ stripped = line.strip().removeprefix("export ").lstrip()
143
+ if not stripped or stripped.startswith("#"):
144
+ continue
145
+ name, separator, _ = stripped.partition("=")
146
+ name = name.strip()
147
+ if separator and name and all(
148
+ ch.isalnum() or ch == "_" for ch in name
149
+ ):
150
+ found.append(name)
151
+ keys[rel] = sorted(set(found))
152
+ return keys
153
+
154
+
155
+ def describe_sync_key_drift(
156
+ recorded: object, current: dict[str, list[str]]
157
+ ) -> str:
158
+ """프로비저닝 때 기록한 키 이름 집합과 지금을 대조해 한 줄로 알린다.
159
+
160
+ 기록이 없으면(그 전에 만들어진 worktree) 빈 문자열 — 알 수 없는 것을 사고로
161
+ 보고하지 않는다. 차단이 아니라 알림인 이유는 키 추가가 정상 작업이기 때문이다.
162
+ """
163
+ if not isinstance(recorded, dict):
164
+ return ""
165
+ parts: list[str] = []
166
+ for rel in sorted(set(recorded) | set(current)):
167
+ before = set(recorded.get(rel) or [])
168
+ after = set(current.get(rel) or [])
169
+ removed = sorted(before - after)
170
+ added = sorted(after - before)
171
+ if not removed and not added:
172
+ continue
173
+ detail = []
174
+ if removed:
175
+ detail.append(f"gone: {', '.join(removed)}")
176
+ if added:
177
+ detail.append(f"new: {', '.join(added)}")
178
+ parts.append(f"{rel} ({'; '.join(detail)})")
179
+ if not parts:
180
+ return ""
181
+ return (
182
+ "shared-env drift — " + "; ".join(parts) + ". This file is symlinked "
183
+ "from the main checkout into every task worktree, so a renamed key "
184
+ "breaks every branch still reading the old name at once, and it shows "
185
+ "up as verifier sessions failing to boot while the executor passes. "
186
+ "Key names only; values are never recorded."
187
+ )
188
+
189
+
190
+ def _seed_worktree_settings_symlink(worktree_path: Path) -> None:
191
+ """Seed `.claude/settings.local.json` in the worker worktree so dispatched
192
+ Claude / codex / antigravity sessions inherit the okstra read-only / write
193
+ allowlist. Mirrors the main-project seeding done in `run.py` — needed
194
+ because `_link_sync_dirs` skips `.claude/` whenever `git worktree add`
195
+ already materialised the directory (e.g. tracked `.claude/handoff-*.md`).
196
+ Failures degrade to stderr warning so worktree provisioning still
197
+ succeeds.
198
+ """
199
+ try:
200
+ link = ensure_project_settings_symlink(project_root=worktree_path)
201
+ except SettingsLinkError as exc:
202
+ print(
203
+ f"okstra-settings: failed to seed worker worktree symlink at "
204
+ f"{worktree_path / '.claude/settings.local.json'} — worker dispatch "
205
+ f"may be blocked by Claude Code permissions. ({exc})",
206
+ file=__import__("sys").stderr,
207
+ )
208
+ return
209
+ if link is None:
210
+ print(
211
+ "okstra-settings: ~/.okstra/templates/settings.local.json missing — "
212
+ "re-run 'npx okstra@latest install' (0.14.0+) to provision the "
213
+ "symlink target.",
214
+ file=__import__("sys").stderr,
215
+ )
216
+
217
+
218
+ def _copy_snapshot_files(source_root: Path, worktree_path: Path) -> list[str]:
219
+ """Copy fixture files from MAIN → task worktree as read-only snapshots
220
+ (FU-V3).
221
+
222
+ Unlike `_link_sync_files`, this materialises a fresh on-disk copy and
223
+ chmods it to `0o444` so the verifier can read the same rows the
224
+ executor saw without sharing a writable handle that would corrupt the
225
+ main worktree's copy. Skip rules mirror the symlink helpers (missing
226
+ source → skipped silently; pre-existing dst → skipped to avoid
227
+ clobbering tracked content).
228
+ """
229
+ notes: list[str] = []
230
+ for rel in _resolve_snapshot_files(source_root):
231
+ src = (source_root / rel).resolve()
232
+ if not src.exists() or not src.is_file():
233
+ continue
234
+ dst = worktree_path / rel
235
+ if dst.exists() or dst.is_symlink():
236
+ continue
237
+ dst.parent.mkdir(parents=True, exist_ok=True)
238
+ shutil.copy2(src, dst)
239
+ try:
240
+ os.chmod(dst, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
241
+ except OSError:
242
+ # chmod failure (exotic filesystems, root-squashed mounts) does
243
+ # not invalidate the snapshot itself — it just means the read-
244
+ # only contract is best-effort. Surface in notes so the operator
245
+ # can audit if a verifier later mutates the file.
246
+ notes.append(f"{rel} (rw-fallback)")
247
+ continue
248
+ notes.append(rel)
249
+ return notes
@@ -0,0 +1,91 @@
1
+ """task-key 를 worktree 경로와 브랜치 이름으로 옮긴다.
2
+
3
+ 경로도 브랜치도 파일 시스템과 git 참조 양쪽에서 안전한 문자만 남겨야 하고,
4
+ 브랜치는 work-category 별 namespace 아래로 들어간다. 순수 문자열 계산이라
5
+ 디스크도 git 도 보지 않는다 — 그래서 provision 전에 미리 보여 줄 수 있다.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from pathlib import Path
10
+
11
+ from okstra_project.dirs import okstra_home
12
+
13
+ from ..ids import _safe_fs_segment
14
+
15
+
16
+ # Work-category → branch namespace (slash-prefixed). Mirrors the values
17
+ # accepted by `--work-category` (bugfix / feature / refactor / ops /
18
+ # improvement); `feature` and `improvement` share the `feature/` namespace,
19
+ # and any unset/unrecognised category falls back to `task/`.
20
+ _WORK_CATEGORY_NAMESPACE = {
21
+ "feature": "feature",
22
+ "improvement": "feature",
23
+ "bugfix": "fix",
24
+ "refactor": "refactor",
25
+ "ops": "ops",
26
+ }
27
+
28
+
29
+ def _safe_segment(value: str) -> str:
30
+ """Sanitise a single path/branch segment.
31
+
32
+ Forbidden chars (`/`, `:`, spaces, anything outside `[a-z0-9-]`)
33
+ are collapsed to `-`. Empty result becomes `_` so we never create
34
+ an empty path component. Delegates to the canonical slugifier in
35
+ `ids.py` to stay in lock-step with run-id / manifest segmentation.
36
+ """
37
+ return _safe_fs_segment(value)
38
+
39
+
40
+ def _work_category_namespace(work_category: str) -> str:
41
+ key = (work_category or "").strip().lower()
42
+ return _WORK_CATEGORY_NAMESPACE.get(key, "task")
43
+
44
+
45
+ def compute_worktree_path(
46
+ *,
47
+ project_id: str,
48
+ task_group_segment: str,
49
+ task_id_segment: str,
50
+ stage_number: Optional[int] = None,
51
+ group_id: Optional[str] = None,
52
+ ) -> Path:
53
+ """Pure path computation. One worktree dir per task-key, or per
54
+ `<task-key>/stage-<N>` when stage_number is given (implementation
55
+ stage isolation). Uses `OKSTRA_HOME` when set (test hook), else
56
+ `~/.okstra`."""
57
+ if stage_number is not None and group_id is not None:
58
+ raise ValueError("stage_number and group_id are mutually exclusive")
59
+ base = okstra_home()
60
+ path = (
61
+ base / "worktrees"
62
+ / _safe_segment(project_id)
63
+ / _safe_segment(task_group_segment)
64
+ / _safe_segment(task_id_segment)
65
+ )
66
+ if stage_number is not None:
67
+ path = path / f"stage-{stage_number}"
68
+ if group_id is not None:
69
+ path = path / f"group-{group_id}"
70
+ return path
71
+
72
+
73
+ def compute_branch_name(
74
+ *,
75
+ work_category: str,
76
+ task_id_segment: str,
77
+ stage_number: Optional[int] = None,
78
+ group_id: Optional[str] = None,
79
+ ) -> str:
80
+ """One branch per task-key as `<namespace>/<task-id>`, or
81
+ `<namespace>/<task-id>-s<N>` for an implementation stage worktree. The
82
+ namespace is a controlled constant so its slash is preserved; only the
83
+ task-id segment is sanitised."""
84
+ if stage_number is not None and group_id is not None:
85
+ raise ValueError("stage_number and group_id are mutually exclusive")
86
+ name = f"{_work_category_namespace(work_category)}/{_safe_segment(task_id_segment)}"
87
+ if stage_number is not None:
88
+ name = f"{name}-s{stage_number}"
89
+ if group_id is not None:
90
+ name = f"{name}-{group_id}"
91
+ return name