okstra 0.186.7 → 0.187.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (541) hide show
  1. package/README.md +1 -1
  2. package/dist/cli-registry.d.mts +88 -1
  3. package/dist/cli-registry.mjs +68 -111
  4. package/dist/cli-registry.mjs.map +1 -1
  5. package/dist/commands/execute/render-bundle.mjs +0 -1
  6. package/dist/commands/execute/render-bundle.mjs.map +1 -1
  7. package/dist/commands/execute/run.mjs +8 -3
  8. package/dist/commands/execute/run.mjs.map +1 -1
  9. package/dist/commands/lifecycle/check-project.mjs +1 -14
  10. package/dist/commands/lifecycle/check-project.mjs.map +1 -1
  11. package/dist/commands/lifecycle/config.mjs +38 -40
  12. package/dist/commands/lifecycle/config.mjs.map +1 -1
  13. package/dist/commands/lifecycle/doctor.d.mts +22 -7
  14. package/dist/commands/lifecycle/doctor.mjs +77 -49
  15. package/dist/commands/lifecycle/doctor.mjs.map +1 -1
  16. package/dist/commands/lifecycle/install.d.mts +12 -10
  17. package/dist/commands/lifecycle/install.mjs +104 -39
  18. package/dist/commands/lifecycle/install.mjs.map +1 -1
  19. package/dist/commands/lifecycle/paths.mjs +8 -3
  20. package/dist/commands/lifecycle/paths.mjs.map +1 -1
  21. package/dist/commands/lifecycle/preflight.mjs +2 -1
  22. package/dist/commands/lifecycle/preflight.mjs.map +1 -1
  23. package/dist/commands/lifecycle/setup.mjs +22 -36
  24. package/dist/commands/lifecycle/setup.mjs.map +1 -1
  25. package/dist/commands/lifecycle/uninstall.d.mts +4 -2
  26. package/dist/commands/lifecycle/uninstall.mjs +59 -15
  27. package/dist/commands/lifecycle/uninstall.mjs.map +1 -1
  28. package/dist/commands/memory/memory.mjs +7 -1
  29. package/dist/commands/memory/memory.mjs.map +1 -1
  30. package/dist/lib/helper-scripts.d.mts +1 -1
  31. package/dist/lib/helper-scripts.mjs +10 -18
  32. package/dist/lib/helper-scripts.mjs.map +1 -1
  33. package/dist/lib/host-config.d.mts +72 -0
  34. package/dist/lib/host-config.mjs +404 -0
  35. package/dist/lib/host-config.mjs.map +1 -0
  36. package/dist/lib/host-registry-client.d.mts +2 -2
  37. package/dist/lib/host-registry-client.mjs +0 -3
  38. package/dist/lib/host-registry-client.mjs.map +1 -1
  39. package/dist/lib/install-assets.d.mts +1 -0
  40. package/dist/lib/install-assets.mjs +4 -0
  41. package/dist/lib/install-assets.mjs.map +1 -1
  42. package/dist/lib/proc.d.mts +2 -0
  43. package/dist/lib/proc.mjs +12 -0
  44. package/dist/lib/proc.mjs.map +1 -1
  45. package/dist/lib/python-command.d.mts +2 -0
  46. package/dist/lib/python-command.mjs +52 -0
  47. package/dist/lib/python-command.mjs.map +1 -0
  48. package/dist/lib/python-helper.d.mts +2 -5
  49. package/dist/lib/python-helper.mjs +3 -52
  50. package/dist/lib/python-helper.mjs.map +1 -1
  51. package/dist/lib/runtime-payload.d.mts +23 -0
  52. package/dist/lib/runtime-payload.mjs +57 -0
  53. package/dist/lib/runtime-payload.mjs.map +1 -0
  54. package/dist/lib/types.d.mts +24 -13
  55. package/docs/architecture/storage-model.md +21 -5
  56. package/docs/architecture.md +37 -36
  57. package/docs/cli.md +55 -76
  58. package/docs/coding-rules.md +295 -0
  59. package/docs/container.md +10 -36
  60. package/docs/contributor-change-matrix.md +1 -1
  61. package/docs/for-ai/skills/okstra-code-review.md +0 -1
  62. package/docs/for-ai/skills/okstra-container-build.md +8 -41
  63. package/docs/for-ai/skills/okstra-inspect.md +4 -4
  64. package/docs/for-ai/skills/okstra-manager.md +2 -2
  65. package/docs/for-ai/skills/okstra-rollup.md +0 -1
  66. package/docs/for-ai/skills/okstra-run.md +3 -3
  67. package/docs/for-ai/skills/okstra-user-response.md +0 -1
  68. package/docs/performance-improvement-plan-v2.md +1 -1
  69. package/docs/project-structure-overview.md +62 -62
  70. package/docs/task-process/README.md +3 -3
  71. package/docs/task-process/common-flow.md +1 -1
  72. package/docs/task-process/error-analysis.md +3 -3
  73. package/docs/task-process/implementation-planning.md +3 -1
  74. package/docs/task-process/release-handoff.md +1 -1
  75. package/package.json +3 -2
  76. package/runtime/BUILD.json +2 -2
  77. package/runtime/agents/workers/claude-worker.md +6 -11
  78. package/runtime/agents/workers/report-writer-worker.md +1 -1
  79. package/runtime/bin/lib/okstra/cli.sh +4 -0
  80. package/runtime/bin/lib/okstra/globals.sh +2 -0
  81. package/runtime/bin/lib/okstra/interactive.sh +26 -173
  82. package/runtime/bin/lib/okstra/project-resolver.sh +23 -61
  83. package/runtime/bin/lib/okstra/usage.sh +3 -3
  84. package/runtime/bin/okstra-compact-reminder.sh +1 -5
  85. package/runtime/bin/okstra-error-log.py +19 -2
  86. package/runtime/bin/okstra-import-check.py +26 -0
  87. package/runtime/bin/okstra-inject-report-index.py +5 -4
  88. package/runtime/bin/okstra-provider-exec.py +22 -17
  89. package/runtime/bin/okstra-render-final-report.py +16 -6
  90. package/runtime/bin/okstra-render-report-views.py +25 -18
  91. package/runtime/bin/okstra-report-translate.py +30 -18
  92. package/runtime/bin/okstra-spawn-followups.py +4 -0
  93. package/runtime/bin/okstra-token-usage.py +3 -0
  94. package/runtime/bin/okstra.sh +4 -2
  95. package/runtime/bin/okstra_bootstrap.py +58 -0
  96. package/runtime/prompts/coding-preflight/overview.md +1 -1
  97. package/runtime/prompts/duties/planning-worker.md +1 -1
  98. package/runtime/prompts/launch.template.md +35 -9
  99. package/runtime/prompts/lead/convergence.md +96 -87
  100. package/runtime/prompts/lead/okstra-lead-contract.md +63 -12
  101. package/runtime/prompts/lead/plan-body-verification.md +65 -46
  102. package/runtime/prompts/lead/report-writer.md +34 -4
  103. package/runtime/prompts/lead/team-contract.md +4 -1
  104. package/runtime/prompts/profiles/_clarification-recommendation.md +2 -1
  105. package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
  106. package/runtime/prompts/profiles/_common-contract.md +3 -3
  107. package/runtime/prompts/profiles/_coverage-critic.md +1 -1
  108. package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
  109. package/runtime/prompts/profiles/_implementation-executor.md +3 -1
  110. package/runtime/prompts/profiles/_implementation-verifier.md +16 -2
  111. package/runtime/prompts/profiles/error-analysis.md +4 -3
  112. package/runtime/prompts/profiles/final-verification.md +2 -2
  113. package/runtime/prompts/profiles/implementation-planning.md +22 -17
  114. package/runtime/prompts/profiles/implementation.md +3 -2
  115. package/runtime/prompts/profiles/requirements-discovery.md +2 -1
  116. package/runtime/prompts/wizard/prompts.ko.json +29 -8
  117. package/runtime/python/okstra_ctl/__init__.py +6 -27
  118. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +4 -5
  119. package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +21 -2
  120. package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +3 -12
  121. package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +40 -22
  122. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +14 -21
  123. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +37 -20
  124. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +86 -14
  125. package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +12 -12
  126. package/runtime/python/okstra_ctl/adapters/runtime/cmux.py +4 -7
  127. package/runtime/python/okstra_ctl/agent/__init__.py +1 -0
  128. package/runtime/python/okstra_ctl/{agent_activity.py → agent/activity.py} +12 -1
  129. package/runtime/python/okstra_ctl/{agent_invocation.py → agent/invocation.py} +34 -5
  130. package/runtime/python/okstra_ctl/agent/prompt_cli/__init__.py +19 -0
  131. package/runtime/python/okstra_ctl/agent/prompt_cli/__main__.py +11 -0
  132. package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +215 -0
  133. package/runtime/python/okstra_ctl/agent/prompt_cli/dynamic_verifier.py +155 -0
  134. package/runtime/python/okstra_ctl/agent/prompt_cli/emit.py +66 -0
  135. package/runtime/python/okstra_ctl/agent/prompt_cli/inputs.py +138 -0
  136. package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +408 -0
  137. package/runtime/python/okstra_ctl/agent/prompt_cli/results.py +197 -0
  138. package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +107 -0
  139. package/runtime/python/okstra_ctl/analysis_packet.py +47 -4
  140. package/runtime/python/okstra_ctl/approval_decisions.py +260 -8
  141. package/runtime/python/okstra_ctl/assignment_resolver.py +0 -12
  142. package/runtime/python/okstra_ctl/attempt_evidence.py +12 -24
  143. package/runtime/python/okstra_ctl/blocking_checks.py +251 -0
  144. package/runtime/python/okstra_ctl/brief_frontmatter.py +0 -7
  145. package/runtime/python/okstra_ctl/clarification_items/__init__.py +102 -0
  146. package/runtime/python/okstra_ctl/clarification_items/carry.py +224 -0
  147. package/runtime/python/okstra_ctl/clarification_items/dispositions.py +135 -0
  148. package/runtime/python/okstra_ctl/clarification_items/parsing.py +253 -0
  149. package/runtime/python/okstra_ctl/clarification_items/rows.py +115 -0
  150. package/runtime/python/okstra_ctl/clarification_items/scan.py +215 -0
  151. package/runtime/python/okstra_ctl/clarification_items/sidecars.py +211 -0
  152. package/runtime/python/okstra_ctl/cmux.py +38 -31
  153. package/runtime/python/okstra_ctl/code_review_target.py +163 -1
  154. package/runtime/python/okstra_ctl/conformance.py +23 -0
  155. package/runtime/python/okstra_ctl/container.py +38 -421
  156. package/runtime/python/okstra_ctl/context_cost.py +16 -2
  157. package/runtime/python/okstra_ctl/contract_graph_cli.py +7 -0
  158. package/runtime/python/okstra_ctl/convergence.py +101 -15
  159. package/runtime/python/okstra_ctl/convergence_critic_prompt.py +345 -0
  160. package/runtime/python/okstra_ctl/convergence_engine.py +382 -70
  161. package/runtime/python/okstra_ctl/convergence_store.py +5 -29
  162. package/runtime/python/okstra_ctl/design_prep.py +108 -12
  163. package/runtime/python/okstra_ctl/design_snapshot.py +11 -1
  164. package/runtime/python/okstra_ctl/design_surfaces.py +29 -2
  165. package/runtime/python/okstra_ctl/dispatch_core.py +102 -25
  166. package/runtime/python/okstra_ctl/dispatch_state.py +268 -39
  167. package/runtime/python/okstra_ctl/doctor_cli.py +48 -0
  168. package/runtime/python/okstra_ctl/domain/worker_exec.py +10 -5
  169. package/runtime/python/okstra_ctl/domain/worker_presentation.py +21 -1
  170. package/runtime/python/okstra_ctl/domain/worker_stream.py +52 -21
  171. package/runtime/python/okstra_ctl/domain/write_policy.py +219 -0
  172. package/runtime/python/okstra_ctl/entrypoints/hosts.py +9 -2
  173. package/runtime/python/okstra_ctl/error_log_core.py +1 -1
  174. package/runtime/python/okstra_ctl/error_report.py +14 -0
  175. package/runtime/python/okstra_ctl/error_zip.py +18 -2
  176. package/runtime/python/okstra_ctl/execution_identity.py +100 -7
  177. package/runtime/python/okstra_ctl/execution_manifest.py +67 -20
  178. package/runtime/python/okstra_ctl/execution_mutation_audit.py +90 -8
  179. package/runtime/python/okstra_ctl/final_report_paths.py +17 -0
  180. package/runtime/python/okstra_ctl/final_report_schema.py +90 -16
  181. package/runtime/python/okstra_ctl/git_reconcile.py +22 -2
  182. package/runtime/python/okstra_ctl/handoff.py +24 -1
  183. package/runtime/python/okstra_ctl/ids.py +6 -16
  184. package/runtime/python/okstra_ctl/implementation_direction.py +142 -33
  185. package/runtime/python/okstra_ctl/implementation_options.py +25 -11
  186. package/runtime/python/okstra_ctl/implementation_outcome.py +5 -2
  187. package/runtime/python/okstra_ctl/improvement_lenses.py +0 -14
  188. package/runtime/python/okstra_ctl/incremental_carry.py +69 -8
  189. package/runtime/python/okstra_ctl/incremental_scope.py +173 -12
  190. package/runtime/python/okstra_ctl/index.py +2 -2
  191. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +48 -8
  192. package/runtime/python/okstra_ctl/interactive_cli.py +223 -0
  193. package/runtime/python/okstra_ctl/json_boundary.py +10 -0
  194. package/runtime/python/okstra_ctl/listing.py +0 -91
  195. package/runtime/python/okstra_ctl/locks.py +5 -19
  196. package/runtime/python/okstra_ctl/log_report.py +14 -0
  197. package/runtime/python/okstra_ctl/manager_cli.py +22 -3
  198. package/runtime/python/okstra_ctl/manager_launch.py +4 -8
  199. package/runtime/python/okstra_ctl/material.py +2 -2
  200. package/runtime/python/okstra_ctl/migrate.py +23 -1
  201. package/runtime/python/okstra_ctl/model_cli.py +19 -5
  202. package/runtime/python/okstra_ctl/model_discovery.py +46 -47
  203. package/runtime/python/okstra_ctl/model_io/__init__.py +1 -0
  204. package/runtime/python/okstra_ctl/model_io/lines.py +163 -0
  205. package/runtime/python/okstra_ctl/model_io/references.py +370 -0
  206. package/runtime/python/okstra_ctl/model_io/renderers.py +498 -0
  207. package/runtime/python/okstra_ctl/model_io_cli.py +30 -945
  208. package/runtime/python/okstra_ctl/model_pool.py +10 -6
  209. package/runtime/python/okstra_ctl/models.py +8 -8
  210. package/runtime/python/okstra_ctl/mutation_recovery.py +5 -63
  211. package/runtime/python/okstra_ctl/next_phase.py +121 -69
  212. package/runtime/python/okstra_ctl/pane_reclaim.py +23 -0
  213. package/runtime/python/okstra_ctl/pane_title.py +11 -8
  214. package/runtime/python/okstra_ctl/path_hints.py +36 -11
  215. package/runtime/python/okstra_ctl/paths.py +76 -8
  216. package/runtime/python/okstra_ctl/plan_items.py +132 -16
  217. package/runtime/python/okstra_ctl/plan_items_cli.py +486 -36
  218. package/runtime/python/okstra_ctl/plan_run_root.py +1 -1
  219. package/runtime/python/okstra_ctl/plan_validate_cli.py +51 -0
  220. package/runtime/python/okstra_ctl/plan_verify_cli.py +80 -0
  221. package/runtime/python/okstra_ctl/prepare_error.py +20 -0
  222. package/runtime/python/okstra_ctl/prior_planning.py +157 -0
  223. package/runtime/python/okstra_ctl/profile_show.py +18 -1
  224. package/runtime/python/okstra_ctl/project_setup_cli.py +140 -0
  225. package/runtime/python/okstra_ctl/recap.py +22 -1
  226. package/runtime/python/okstra_ctl/reconcile.py +84 -0
  227. package/runtime/python/okstra_ctl/registry/host_registry.py +18 -1
  228. package/runtime/python/okstra_ctl/render.py +98 -32
  229. package/runtime/python/okstra_ctl/render_final_report.py +3 -7
  230. package/runtime/python/okstra_ctl/report_assembly.py +297 -7
  231. package/runtime/python/okstra_ctl/report_contract.py +30 -7
  232. package/runtime/python/okstra_ctl/report_finalize.py +206 -15
  233. package/runtime/python/okstra_ctl/report_html/common.py +2 -2
  234. package/runtime/python/okstra_ctl/report_html/render.py +8 -12
  235. package/runtime/python/okstra_ctl/report_language.py +8 -4
  236. package/runtime/python/okstra_ctl/report_narrative.py +90 -3
  237. package/runtime/python/okstra_ctl/report_projections.py +8 -2
  238. package/runtime/python/okstra_ctl/report_synthesis_packet.py +258 -1
  239. package/runtime/python/okstra_ctl/report_translation.py +2 -35
  240. package/runtime/python/okstra_ctl/report_views.py +4 -22
  241. package/runtime/python/okstra_ctl/resolve_task_key.py +13 -0
  242. package/runtime/python/okstra_ctl/rollup.py +13 -0
  243. package/runtime/python/okstra_ctl/run.py +639 -138
  244. package/runtime/python/okstra_ctl/run_audit.py +13 -0
  245. package/runtime/python/okstra_ctl/run_index_row.py +0 -12
  246. package/runtime/python/okstra_ctl/seeding.py +21 -11
  247. package/runtime/python/okstra_ctl/sequence.py +1 -1
  248. package/runtime/python/okstra_ctl/session.py +64 -14
  249. package/runtime/python/okstra_ctl/set_work_status.py +18 -0
  250. package/runtime/python/okstra_ctl/stage_integrate.py +15 -1
  251. package/runtime/python/okstra_ctl/stage_ledger.py +1 -1
  252. package/runtime/python/okstra_ctl/stage_map.py +57 -11
  253. package/runtime/python/okstra_ctl/stage_map_cli.py +83 -0
  254. package/runtime/python/okstra_ctl/stage_map_view.py +65 -0
  255. package/runtime/python/okstra_ctl/stage_targets.py +3 -12
  256. package/runtime/python/okstra_ctl/task_list_cli.py +138 -0
  257. package/runtime/python/okstra_ctl/task_show_cli.py +66 -0
  258. package/runtime/python/okstra_ctl/task_target.py +4 -16
  259. package/runtime/python/okstra_ctl/team.py +49 -2
  260. package/runtime/python/okstra_ctl/time_report.py +17 -2
  261. package/runtime/python/okstra_ctl/usage_report.py +11 -0
  262. package/runtime/python/okstra_ctl/user_response.py +55 -218
  263. package/runtime/python/okstra_ctl/user_response_values.py +242 -0
  264. package/runtime/python/okstra_ctl/validation_contract.py +3 -0
  265. package/runtime/python/okstra_ctl/verification_target.py +68 -0
  266. package/runtime/python/okstra_ctl/wizard.py +457 -92
  267. package/runtime/python/okstra_ctl/worker_artifacts.py +75 -16
  268. package/runtime/python/okstra_ctl/worker_audit_check.py +22 -0
  269. package/runtime/python/okstra_ctl/worker_dispatch.py +21 -0
  270. package/runtime/python/okstra_ctl/worker_liveness.py +33 -0
  271. package/runtime/python/okstra_ctl/worker_prompt_body.py +15 -2
  272. package/runtime/python/okstra_ctl/worker_prompt_contract.py +98 -7
  273. package/runtime/python/okstra_ctl/worker_prompt_headers.py +24 -2
  274. package/runtime/python/okstra_ctl/worker_prompt_policy.py +18 -3
  275. package/runtime/python/okstra_ctl/worker_request.py +2 -1
  276. package/runtime/python/okstra_ctl/worker_runner.py +16 -9
  277. package/runtime/python/okstra_ctl/worker_state.py +16 -1
  278. package/runtime/python/okstra_ctl/workflow.py +18 -1
  279. package/runtime/python/okstra_ctl/worktree/__init__.py +92 -0
  280. package/runtime/python/okstra_ctl/worktree/cleanliness.py +89 -0
  281. package/runtime/python/okstra_ctl/worktree/decisions.py +127 -0
  282. package/runtime/python/okstra_ctl/worktree/git_ops.py +165 -0
  283. package/runtime/python/okstra_ctl/worktree/linking.py +249 -0
  284. package/runtime/python/okstra_ctl/worktree/naming.py +91 -0
  285. package/runtime/python/okstra_ctl/worktree/provision.py +385 -0
  286. package/runtime/python/okstra_ctl/worktree/sync_config.py +181 -0
  287. package/runtime/python/okstra_ctl/worktree_cli.py +75 -0
  288. package/runtime/python/okstra_ctl/worktree_lookup_cli.py +39 -0
  289. package/runtime/python/okstra_ctl/worktree_registry.py +7 -0
  290. package/runtime/python/okstra_ctl/worktree_status_cli.py +53 -0
  291. package/runtime/python/okstra_ctl/write_policy.py +90 -213
  292. package/runtime/python/okstra_project/__init__.py +0 -4
  293. package/runtime/python/okstra_project/dirs.py +2 -2
  294. package/runtime/python/okstra_project/phase_pointer.py +84 -0
  295. package/runtime/python/okstra_project/slug.py +24 -0
  296. package/runtime/python/okstra_project/state.py +22 -230
  297. package/runtime/python/okstra_token_usage/claude.py +9 -1
  298. package/runtime/python/okstra_token_usage/cli.py +3 -1
  299. package/runtime/python/okstra_token_usage/collect.py +71 -10
  300. package/runtime/python/okstra_token_usage/cursor.py +2 -0
  301. package/runtime/python/okstra_token_usage/grok.py +1 -5
  302. package/runtime/python/okstra_token_usage/paths.py +26 -0
  303. package/runtime/python/okstra_token_usage/pricing.py +19 -7
  304. package/runtime/schemas/convergence-critic-results-v1.0.schema.json +5 -0
  305. package/runtime/schemas/execution-manifest-v2.schema.json +10 -10
  306. package/runtime/schemas/final-report-v2.0.schema.json +9 -26
  307. package/runtime/schemas/final-report-v3.0.schema.json +173 -41
  308. package/runtime/schemas/report-narrative-v3.0.schema.json +3 -2
  309. package/runtime/skills/okstra-brief-gen/SKILL.md +35 -2
  310. package/runtime/skills/okstra-container-build/SKILL.md +16 -47
  311. package/runtime/skills/okstra-inspect/facets/history.md +1 -1
  312. package/runtime/skills/okstra-inspect/facets/status.md +7 -6
  313. package/runtime/skills/okstra-pr-gen/SKILL.md +1 -1
  314. package/runtime/skills/okstra-run/SKILL.md +7 -5
  315. package/runtime/templates/report-writer-prompt-preamble.md +1 -1
  316. package/runtime/templates/reports/brief.template.md +6 -2
  317. package/runtime/templates/reports/error-analysis-input.template.md +2 -0
  318. package/runtime/templates/reports/final-verification-input.template.md +2 -0
  319. package/runtime/templates/reports/implementation-input.template.md +7 -1
  320. package/runtime/templates/reports/implementation-planning-input.template.md +4 -0
  321. package/runtime/templates/reports/quick-input.template.md +2 -0
  322. package/runtime/templates/reports/release-handoff-input.template.md +6 -1
  323. package/runtime/templates/reports/report.js +20 -3
  324. package/runtime/templates/reports/schedule.template.md +2 -0
  325. package/runtime/templates/reports/settings.template.json +0 -10
  326. package/runtime/templates/reports/task-brief.template.md +3 -0
  327. package/runtime/templates/reports/user-response.template.md +7 -3
  328. package/runtime/validators/checks/fixtures-01.py +57 -0
  329. package/runtime/validators/checks/fixtures-02.py +565 -0
  330. package/runtime/validators/checks/runners-01.py +108 -0
  331. package/runtime/validators/checks/validate-assets-01.py +60 -0
  332. package/runtime/validators/checks/validate-prompt-metadata-01.py +261 -0
  333. package/runtime/validators/checks/validate-tasks-01.py +46 -0
  334. package/runtime/validators/checks/validate-tasks-02.py +85 -0
  335. package/runtime/validators/checks/validate-tasks-03.py +61 -0
  336. package/runtime/validators/checks/validate-tasks-04.py +118 -0
  337. package/runtime/validators/forbidden_actions.py +73 -3
  338. package/runtime/validators/lib/common.sh +5 -0
  339. package/runtime/validators/lib/fixtures.sh +7 -591
  340. package/runtime/validators/lib/paths.sh +13 -0
  341. package/runtime/validators/lib/runners.sh +6 -104
  342. package/runtime/validators/lib/summary.sh +1 -1
  343. package/runtime/validators/lib/validate-assets.sh +6 -56
  344. package/runtime/validators/lib/validate-prompt-metadata.sh +6 -257
  345. package/runtime/validators/lib/validate-tasks.sh +9 -294
  346. package/runtime/validators/validate-implementation-plan-stages.py +4 -4
  347. package/runtime/validators/validate-run.py +1369 -2654
  348. package/runtime/validators/validate-workflow.sh +56 -16
  349. package/runtime/validators/validate_improvement_report.py +2 -1
  350. package/runtime/validators/validate_session_conformance.py +295 -49
  351. package/dist/commands/execute/agent-prompt.d.mts +0 -1
  352. package/dist/commands/execute/agent-prompt.mjs +0 -24
  353. package/dist/commands/execute/agent-prompt.mjs.map +0 -1
  354. package/dist/commands/execute/codex-dispatch.d.mts +0 -3
  355. package/dist/commands/execute/codex-dispatch.mjs +0 -6
  356. package/dist/commands/execute/codex-dispatch.mjs.map +0 -1
  357. package/dist/commands/execute/codex-run.d.mts +0 -3
  358. package/dist/commands/execute/codex-run.mjs +0 -62
  359. package/dist/commands/execute/codex-run.mjs.map +0 -1
  360. package/dist/commands/execute/convergence.d.mts +0 -1
  361. package/dist/commands/execute/convergence.mjs +0 -38
  362. package/dist/commands/execute/convergence.mjs.map +0 -1
  363. package/dist/commands/execute/error-log.d.mts +0 -1
  364. package/dist/commands/execute/error-log.mjs +0 -18
  365. package/dist/commands/execute/error-log.mjs.map +0 -1
  366. package/dist/commands/execute/git-reconcile.d.mts +0 -1
  367. package/dist/commands/execute/git-reconcile.mjs +0 -30
  368. package/dist/commands/execute/git-reconcile.mjs.map +0 -1
  369. package/dist/commands/execute/handoff.d.mts +0 -1
  370. package/dist/commands/execute/handoff.mjs +0 -31
  371. package/dist/commands/execute/handoff.mjs.map +0 -1
  372. package/dist/commands/execute/incremental-carry.d.mts +0 -1
  373. package/dist/commands/execute/incremental-carry.mjs +0 -20
  374. package/dist/commands/execute/incremental-carry.mjs.map +0 -1
  375. package/dist/commands/execute/incremental-scope.d.mts +0 -1
  376. package/dist/commands/execute/incremental-scope.mjs +0 -29
  377. package/dist/commands/execute/incremental-scope.mjs.map +0 -1
  378. package/dist/commands/execute/integrate-stages.d.mts +0 -1
  379. package/dist/commands/execute/integrate-stages.mjs +0 -24
  380. package/dist/commands/execute/integrate-stages.mjs.map +0 -1
  381. package/dist/commands/execute/pane-title.d.mts +0 -1
  382. package/dist/commands/execute/pane-title.mjs +0 -20
  383. package/dist/commands/execute/pane-title.mjs.map +0 -1
  384. package/dist/commands/execute/plan-items.d.mts +0 -1
  385. package/dist/commands/execute/plan-items.mjs +0 -9
  386. package/dist/commands/execute/plan-items.mjs.map +0 -1
  387. package/dist/commands/execute/plan-validate.d.mts +0 -1
  388. package/dist/commands/execute/plan-validate.mjs +0 -68
  389. package/dist/commands/execute/plan-validate.mjs.map +0 -1
  390. package/dist/commands/execute/plan-verify.d.mts +0 -1
  391. package/dist/commands/execute/plan-verify.mjs +0 -43
  392. package/dist/commands/execute/plan-verify.mjs.map +0 -1
  393. package/dist/commands/execute/spawn-followups.d.mts +0 -1
  394. package/dist/commands/execute/spawn-followups.mjs +0 -22
  395. package/dist/commands/execute/spawn-followups.mjs.map +0 -1
  396. package/dist/commands/execute/team.d.mts +0 -3
  397. package/dist/commands/execute/team.mjs +0 -66
  398. package/dist/commands/execute/team.mjs.map +0 -1
  399. package/dist/commands/execute/token-usage.d.mts +0 -1
  400. package/dist/commands/execute/token-usage.mjs +0 -19
  401. package/dist/commands/execute/token-usage.mjs.map +0 -1
  402. package/dist/commands/execute/worker-audit-check.d.mts +0 -1
  403. package/dist/commands/execute/worker-audit-check.mjs +0 -34
  404. package/dist/commands/execute/worker-audit-check.mjs.map +0 -1
  405. package/dist/commands/execute/worker-dispatch.d.mts +0 -7
  406. package/dist/commands/execute/worker-dispatch.mjs +0 -64
  407. package/dist/commands/execute/worker-dispatch.mjs.map +0 -1
  408. package/dist/commands/execute/worker-state.d.mts +0 -1
  409. package/dist/commands/execute/worker-state.mjs +0 -28
  410. package/dist/commands/execute/worker-state.mjs.map +0 -1
  411. package/dist/commands/execute/worktree-lookup.d.mts +0 -1
  412. package/dist/commands/execute/worktree-lookup.mjs +0 -92
  413. package/dist/commands/execute/worktree-lookup.mjs.map +0 -1
  414. package/dist/commands/execute/worktree-status.d.mts +0 -1
  415. package/dist/commands/execute/worktree-status.mjs +0 -121
  416. package/dist/commands/execute/worktree-status.mjs.map +0 -1
  417. package/dist/commands/inspect/code-review.d.mts +0 -1
  418. package/dist/commands/inspect/code-review.mjs +0 -32
  419. package/dist/commands/inspect/code-review.mjs.map +0 -1
  420. package/dist/commands/inspect/container.d.mts +0 -1
  421. package/dist/commands/inspect/container.mjs +0 -25
  422. package/dist/commands/inspect/container.mjs.map +0 -1
  423. package/dist/commands/inspect/context-cost.d.mts +0 -1
  424. package/dist/commands/inspect/context-cost.mjs +0 -25
  425. package/dist/commands/inspect/context-cost.mjs.map +0 -1
  426. package/dist/commands/inspect/design-prep.d.mts +0 -1
  427. package/dist/commands/inspect/design-prep.mjs +0 -22
  428. package/dist/commands/inspect/design-prep.mjs.map +0 -1
  429. package/dist/commands/inspect/error-report.d.mts +0 -1
  430. package/dist/commands/inspect/error-report.mjs +0 -25
  431. package/dist/commands/inspect/error-report.mjs.map +0 -1
  432. package/dist/commands/inspect/error-zip.d.mts +0 -1
  433. package/dist/commands/inspect/error-zip.mjs +0 -24
  434. package/dist/commands/inspect/error-zip.mjs.map +0 -1
  435. package/dist/commands/inspect/log-report.d.mts +0 -1
  436. package/dist/commands/inspect/log-report.mjs +0 -26
  437. package/dist/commands/inspect/log-report.mjs.map +0 -1
  438. package/dist/commands/inspect/model-io.d.mts +0 -1
  439. package/dist/commands/inspect/model-io.mjs +0 -25
  440. package/dist/commands/inspect/model-io.mjs.map +0 -1
  441. package/dist/commands/inspect/profile-show.d.mts +0 -1
  442. package/dist/commands/inspect/profile-show.mjs +0 -28
  443. package/dist/commands/inspect/profile-show.mjs.map +0 -1
  444. package/dist/commands/inspect/recap.d.mts +0 -1
  445. package/dist/commands/inspect/recap.mjs +0 -30
  446. package/dist/commands/inspect/recap.mjs.map +0 -1
  447. package/dist/commands/inspect/resolve-task-key.d.mts +0 -1
  448. package/dist/commands/inspect/resolve-task-key.mjs +0 -24
  449. package/dist/commands/inspect/resolve-task-key.mjs.map +0 -1
  450. package/dist/commands/inspect/rollup.d.mts +0 -1
  451. package/dist/commands/inspect/rollup.mjs +0 -25
  452. package/dist/commands/inspect/rollup.mjs.map +0 -1
  453. package/dist/commands/inspect/run-audit.d.mts +0 -1
  454. package/dist/commands/inspect/run-audit.mjs +0 -25
  455. package/dist/commands/inspect/run-audit.mjs.map +0 -1
  456. package/dist/commands/inspect/set-work-status.d.mts +0 -1
  457. package/dist/commands/inspect/set-work-status.mjs +0 -29
  458. package/dist/commands/inspect/set-work-status.mjs.map +0 -1
  459. package/dist/commands/inspect/stage-map.d.mts +0 -1
  460. package/dist/commands/inspect/stage-map.mjs +0 -131
  461. package/dist/commands/inspect/stage-map.mjs.map +0 -1
  462. package/dist/commands/inspect/task-list.d.mts +0 -1
  463. package/dist/commands/inspect/task-list.mjs +0 -149
  464. package/dist/commands/inspect/task-list.mjs.map +0 -1
  465. package/dist/commands/inspect/task-show.d.mts +0 -1
  466. package/dist/commands/inspect/task-show.mjs +0 -108
  467. package/dist/commands/inspect/task-show.mjs.map +0 -1
  468. package/dist/commands/inspect/time-report.d.mts +0 -1
  469. package/dist/commands/inspect/time-report.mjs +0 -24
  470. package/dist/commands/inspect/time-report.mjs.map +0 -1
  471. package/dist/commands/inspect/usage-report.d.mts +0 -1
  472. package/dist/commands/inspect/usage-report.mjs +0 -23
  473. package/dist/commands/inspect/usage-report.mjs.map +0 -1
  474. package/dist/commands/inspect/user-response.d.mts +0 -1
  475. package/dist/commands/inspect/user-response.mjs +0 -35
  476. package/dist/commands/inspect/user-response.mjs.map +0 -1
  477. package/dist/commands/inspect/worker-liveness.d.mts +0 -1
  478. package/dist/commands/inspect/worker-liveness.mjs +0 -45
  479. package/dist/commands/inspect/worker-liveness.mjs.map +0 -1
  480. package/dist/commands/lifecycle/contract-check.d.mts +0 -1
  481. package/dist/commands/lifecycle/contract-check.mjs +0 -18
  482. package/dist/commands/lifecycle/contract-check.mjs.map +0 -1
  483. package/dist/commands/lifecycle/migrate.d.mts +0 -1
  484. package/dist/commands/lifecycle/migrate.mjs +0 -30
  485. package/dist/commands/lifecycle/migrate.mjs.map +0 -1
  486. package/dist/commands/lifecycle/model.d.mts +0 -1
  487. package/dist/commands/lifecycle/model.mjs +0 -22
  488. package/dist/commands/lifecycle/model.mjs.map +0 -1
  489. package/dist/commands/manager.d.mts +0 -3
  490. package/dist/commands/manager.mjs +0 -50
  491. package/dist/commands/manager.mjs.map +0 -1
  492. package/dist/commands/report/agent-activity.d.mts +0 -1
  493. package/dist/commands/report/agent-activity.mjs +0 -20
  494. package/dist/commands/report/agent-activity.mjs.map +0 -1
  495. package/dist/commands/report/approval-decision.d.mts +0 -1
  496. package/dist/commands/report/approval-decision.mjs +0 -21
  497. package/dist/commands/report/approval-decision.mjs.map +0 -1
  498. package/dist/commands/report/design-snapshot.d.mts +0 -1
  499. package/dist/commands/report/design-snapshot.mjs +0 -19
  500. package/dist/commands/report/design-snapshot.mjs.map +0 -1
  501. package/dist/commands/report/finalize.d.mts +0 -4
  502. package/dist/commands/report/finalize.mjs +0 -64
  503. package/dist/commands/report/finalize.mjs.map +0 -1
  504. package/dist/commands/report/inject-report-index.d.mts +0 -1
  505. package/dist/commands/report/inject-report-index.mjs +0 -21
  506. package/dist/commands/report/inject-report-index.mjs.map +0 -1
  507. package/dist/commands/report/render-final-report.d.mts +0 -1
  508. package/dist/commands/report/render-final-report.mjs +0 -23
  509. package/dist/commands/report/render-final-report.mjs.map +0 -1
  510. package/dist/commands/report/render-views.d.mts +0 -1
  511. package/dist/commands/report/render-views.mjs +0 -27
  512. package/dist/commands/report/render-views.mjs.map +0 -1
  513. package/dist/commands/report/translate.d.mts +0 -1
  514. package/dist/commands/report/translate.mjs +0 -33
  515. package/dist/commands/report/translate.mjs.map +0 -1
  516. package/runtime/bin/lib/okstra/tmux-pane.sh +0 -40
  517. package/runtime/bin/lib/okstra-ctl/cmd-batch.sh +0 -59
  518. package/runtime/bin/lib/okstra-ctl/cmd-list.sh +0 -35
  519. package/runtime/bin/lib/okstra-ctl/cmd-open.sh +0 -36
  520. package/runtime/bin/lib/okstra-ctl/cmd-projects.sh +0 -26
  521. package/runtime/bin/lib/okstra-ctl/cmd-reconcile.sh +0 -29
  522. package/runtime/bin/lib/okstra-ctl/cmd-reindex.sh +0 -38
  523. package/runtime/bin/lib/okstra-ctl/cmd-rerun.sh +0 -345
  524. package/runtime/bin/lib/okstra-ctl/cmd-show.sh +0 -27
  525. package/runtime/bin/lib/okstra-ctl/cmd-tail.sh +0 -92
  526. package/runtime/bin/lib/okstra-ctl/main.sh +0 -41
  527. package/runtime/bin/lib/okstra-ctl/prepare.sh +0 -31
  528. package/runtime/bin/lib/okstra-ctl/usage.sh +0 -23
  529. package/runtime/bin/okstra-central.sh +0 -152
  530. package/runtime/bin/okstra-incremental-carry.py +0 -10
  531. package/runtime/bin/okstra-incremental-scope.py +0 -10
  532. package/runtime/bin/okstra-team-reconcile.sh +0 -36
  533. package/runtime/python/okstra_ctl/agent_prompt_cli.py +0 -1156
  534. package/runtime/python/okstra_ctl/batch.py +0 -60
  535. package/runtime/python/okstra_ctl/clarification_items.py +0 -1050
  536. package/runtime/python/okstra_ctl/container_registry.py +0 -72
  537. package/runtime/python/okstra_ctl/improvement_assignment.py +0 -61
  538. package/runtime/python/okstra_ctl/resolver.py +0 -54
  539. package/runtime/python/okstra_ctl/team_reconcile.py +0 -275
  540. package/runtime/python/okstra_ctl/tmux.py +0 -134
  541. package/runtime/python/okstra_ctl/worktree.py +0 -1099
@@ -1,72 +0,0 @@
1
- """Per-task container registry — flock 보조 인덱스.
2
-
3
- docker 라벨은 컨테이너 존재 여부의 기준값(SSOT)이지만 tmux session/pane id 나
4
- findings 파일 경로 같은 운영 메타데이터는 추적하지 못한다. 이 모듈은 task root
5
- 하위 ``container/registry.json`` 에 그런 보조 정보를 가벼운 dict 로 보관한다.
6
-
7
- flock + tmp+``os.replace`` 원자 쓰기 idiom 은 ``json_registry`` 의 공용
8
- Interface 를 사용하고, lock/registry 경로만 ``paths.container_paths()`` 로
9
- 해소해 전역 ``~/.okstra/worktrees/registry.json`` 과 분리한다. dict 구조는
10
- ``{"services": {<svc>: {session_name, pane_id, findings_path}}}`` 한 가지뿐 —
11
- worktree_registry 의 dataclass 는 복제하지 않는다.
12
- """
13
- from __future__ import annotations
14
-
15
- from pathlib import Path
16
-
17
- from .json_registry import load_registry_json, registry_lock, save_registry_json
18
- from . import paths
19
-
20
-
21
- def _registry_lock(lock_path: Path):
22
- """``container/registry.json.lock`` 에 대한 배타 flock."""
23
- return registry_lock(lock_path)
24
-
25
-
26
- def _load(registry_path: Path) -> dict:
27
- data = load_registry_json(registry_path, lambda: {"services": {}})
28
- data.setdefault("services", {})
29
- return data
30
-
31
-
32
- def _save(registry_path: Path, data: dict) -> None:
33
- save_registry_json(registry_path, data)
34
-
35
-
36
- def reserve(
37
- project_root: Path, group: str, task_id: str, service: str,
38
- *, session_name: str, pane_id: str, findings_path: str,
39
- ) -> None:
40
- """``service`` 의 tmux session/pane/findings 메타데이터를 등록한다."""
41
- cp = paths.container_paths(project_root, group, task_id)
42
- with _registry_lock(cp["registry_lock"]):
43
- data = _load(cp["registry"])
44
- data["services"][service] = {
45
- "session_name": session_name,
46
- "pane_id": pane_id,
47
- "findings_path": findings_path,
48
- }
49
- _save(cp["registry"], data)
50
-
51
-
52
- def lookup(project_root: Path, group: str, task_id: str) -> dict:
53
- """현재 registry dict 를 돌려준다. 파일 부재 시 ``{"services": {}}``."""
54
- cp = paths.container_paths(project_root, group, task_id)
55
- with _registry_lock(cp["registry_lock"]):
56
- return _load(cp["registry"])
57
-
58
-
59
- def release(project_root: Path, group: str, task_id: str, service: str) -> None:
60
- """``service`` 항목 하나만 제거한다(없으면 no-op)."""
61
- cp = paths.container_paths(project_root, group, task_id)
62
- with _registry_lock(cp["registry_lock"]):
63
- data = _load(cp["registry"])
64
- data["services"].pop(service, None)
65
- _save(cp["registry"], data)
66
-
67
-
68
- def clear(project_root: Path, group: str, task_id: str) -> None:
69
- """모든 service 항목을 비운다."""
70
- cp = paths.container_paths(project_root, group, task_id)
71
- with _registry_lock(cp["registry_lock"]):
72
- _save(cp["registry"], {"services": {}})
@@ -1,61 +0,0 @@
1
- """Assign improvement-discovery primary passes from the resolved roster."""
2
- from __future__ import annotations
3
-
4
- from collections.abc import Mapping
5
-
6
-
7
- def assign_primary_lenses(
8
- worker_ids: list[str],
9
- priority_lenses: list[str],
10
- ) -> dict[str, str]:
11
- """Round-robin resolved worker instances over resolved priority lenses."""
12
- if not worker_ids:
13
- raise ValueError("worker IDs must not be empty")
14
- if not priority_lenses:
15
- raise ValueError("priority lenses must not be empty")
16
- if len(set(worker_ids)) != len(worker_ids):
17
- raise ValueError("worker IDs must be unique")
18
- return {
19
- worker_id: priority_lenses[index % len(priority_lenses)]
20
- for index, worker_id in enumerate(worker_ids)
21
- }
22
-
23
-
24
- def validate_primary_lens_assignments(
25
- assignments: Mapping[str, str],
26
- worker_ids: list[str],
27
- priority_lenses: list[str],
28
- ) -> list[str]:
29
- """Require every selected analyser once at its roster-order lens."""
30
- errors: list[str] = []
31
- if not worker_ids:
32
- return ["resolved analyser worker IDs must not be empty"]
33
- if not priority_lenses:
34
- return ["resolved priority lenses must not be empty"]
35
- try:
36
- expected = assign_primary_lenses(worker_ids, priority_lenses)
37
- except ValueError as exc:
38
- return [str(exc)]
39
- if list(assignments) != worker_ids:
40
- errors.append(
41
- "assignment order must match resolved analyser worker order: "
42
- + ", ".join(worker_ids)
43
- )
44
- for worker_id in worker_ids:
45
- if worker_id not in assignments:
46
- errors.append(f"missing worker assignment: {worker_id}")
47
- for worker_id, lens in assignments.items():
48
- if worker_id not in expected:
49
- errors.append(f"extra worker assignment: {worker_id}")
50
- continue
51
- if lens not in priority_lenses:
52
- errors.append(
53
- f"{worker_id}: primary lens {lens!r} is outside resolved priority lenses"
54
- )
55
- continue
56
- if lens != expected[worker_id]:
57
- errors.append(
58
- f"{worker_id}: expected primary lens {expected[worker_id]!r}, "
59
- f"got {lens!r}"
60
- )
61
- return errors
@@ -1,54 +0,0 @@
1
- """run id 해석."""
2
- from __future__ import annotations
3
-
4
- from pathlib import Path
5
- from typing import List
6
-
7
- from .listing import list_runs
8
-
9
-
10
- class ResolveError(Exception):
11
- """runId 해석 실패. candidates 에 후보 목록 포함."""
12
-
13
- def __init__(self, message: str, candidates: List[str]) -> None:
14
- super().__init__(message)
15
- self.candidates = candidates
16
-
17
-
18
- def _all_run_ids(home: Path) -> List[str]:
19
- rows = list_runs(home, include_archive=True)
20
- return [r["runId"] for r in rows]
21
-
22
-
23
- def resolve_run_id(home: Path, query: str) -> str:
24
- """prefix substring 매칭 규칙으로 runId 해석.
25
- 1. 정확 일치 → 즉시 반환.
26
- 2. endswith 일치 1건 → 반환.
27
- 3. substring 일치 1건 → 반환.
28
- 그 외(0건/다수) → ResolveError.
29
- """
30
- ids = _all_run_ids(home)
31
- if query in ids:
32
- return query
33
- ends = [i for i in ids if i.endswith(query)]
34
- if len(ends) == 1:
35
- return ends[0]
36
- if len(ends) > 1:
37
- raise ResolveError(f"ambiguous (endswith): {query!r}", ends)
38
- subs = [i for i in ids if query in i]
39
- if len(subs) == 1:
40
- return subs[0]
41
- if len(subs) > 1:
42
- raise ResolveError(f"ambiguous (substring): {query!r}", subs)
43
- raise ResolveError(f"no run matches {query!r}", [])
44
-
45
-
46
- def resolve_last(home: Path, *, project: str, task_group: str) -> str:
47
- """가장 최근 runId 를 반환. 매칭 없으면 ResolveError."""
48
- rows = list_runs(home, project=project, task_group=task_group,
49
- include_archive=True, limit=1)
50
- if not rows:
51
- raise ResolveError(
52
- f"no run found for project={project} task_group={task_group}", []
53
- )
54
- return rows[0]["runId"]
@@ -1,275 +0,0 @@
1
- """Stale team-member reconciliation for run-end teardown.
2
-
3
- A Claude Code team member clears its own ``isActive`` flag in
4
- ``~/.claude/teams/<team>/config.json`` when its ``Agent()`` dispatch returns, so
5
- by Phase 7 every worker is normally already inactive. The one failure mode is a
6
- member whose tmux pane died WITHOUT clearing the flag (killed mid-turn): it stays
7
- ``isActive: true`` forever and keeps showing as a live member that re-sending
8
- ``shutdown_request`` cannot clear — the addressee is already gone.
9
-
10
- This module reconciles exactly that case: a member with ``isActive`` truthy
11
- whose recorded tmux pane is no longer live is flipped to inactive. It NEVER
12
- touches a member whose pane is still live, the lead, or a member with no
13
- recorded pane — those are left for graceful shutdown.
14
-
15
- CC v2.1.178 removed ``TeamDelete``: the implicit team ends with the session and
16
- run-end teardown only dismisses teammates via ``shutdown_request``. Reconcile
17
- keeps the roster honest so a dismissed-but-dead member does not linger as a
18
- live pill — see okstra-lead-contract.md "Run-scoped worker-resource lifecycle"
19
- for neutral cleanup ownership and the selected Claude adapter's
20
- "Run-scoped resource lifecycle" for this provider-specific reconciliation.
21
-
22
- Roster resolution (``--project-root``) exists because ``team-state.lead.sessionId``
23
- is a phase-3 snapshot: Claude Code re-issues the session id across resume /
24
- compaction mid-run, so the live on-disk roster often sits under a DIFFERENT
25
- ``session-*`` dir than the snapshot names. Resolving by the CURRENT live session
26
- (the newest jsonl under the encoded-cwd projects dir) is what makes split-pane
27
- teardown find the teammates it must dismiss.
28
- """
29
- from __future__ import annotations
30
-
31
- import json
32
- import os
33
- import subprocess
34
- import sys
35
- import tempfile
36
- from pathlib import Path
37
-
38
- from okstra_ctl.session import resolve_inproc_lead_session_id
39
- from okstra_ctl.json_boundary import (
40
- JsonBoundaryError,
41
- external_claude_team_json_source,
42
- load_external_json,
43
- )
44
-
45
-
46
- def live_pane_ids() -> tuple[bool, set[str]]:
47
- """Return ``(tmux_available, live_pane_ids)``.
48
-
49
- ``tmux_available`` is False when no tmux server is reachable; the caller must
50
- then skip reconciliation rather than treat every pane as dead.
51
- """
52
- try:
53
- out = subprocess.run(
54
- ["tmux", "list-panes", "-a", "-F", "#{pane_id}"],
55
- capture_output=True, text=True,
56
- )
57
- except FileNotFoundError:
58
- return False, set()
59
- if out.returncode != 0:
60
- return False, set()
61
- return True, {ln.strip() for ln in out.stdout.splitlines() if ln.strip()}
62
-
63
-
64
- def reconcile_members(config: dict, live_panes: set[str]) -> list[str]:
65
- """Flip dead-pane stale-active members to inactive in-place.
66
-
67
- Returns the names of members that were flipped. A member qualifies only when
68
- it is not the lead, ``isActive`` is truthy, it has a recorded ``tmuxPaneId``,
69
- and that pane is not among ``live_panes``.
70
- """
71
- lead = config.get("leadAgentId")
72
- flipped: list[str] = []
73
- for member in config.get("members", []):
74
- if member.get("agentId") == lead:
75
- continue
76
- pane = (member.get("tmuxPaneId") or "").strip()
77
- if not member.get("isActive") or not pane:
78
- continue
79
- if pane not in live_panes:
80
- member["isActive"] = False
81
- flipped.append(member.get("name", member.get("agentId", "?")))
82
- return flipped
83
-
84
-
85
- def dismissible_members(config: dict) -> list[str]:
86
- """Names of every non-lead member — the set the lead may ``shutdown_request``.
87
-
88
- The lead further filters by confirmed-complete (result path present) before
89
- dismissing; this just tells it which teammates the resolved roster holds.
90
- """
91
- lead = config.get("leadAgentId")
92
- names: list[str] = []
93
- for member in config.get("members", []):
94
- if member.get("agentId") == lead:
95
- continue
96
- names.append(member.get("name", member.get("agentId", "?")))
97
- return names
98
-
99
-
100
- def _team_config_for_session(session_id: str) -> Path:
101
- """``~/.claude/teams/session-<prefix>/config.json`` for a session id.
102
-
103
- The harness names the team dir with the session id's first segment (the
104
- 8-hex stem before the first dash), never the ``teamName`` label.
105
- """
106
- prefix = session_id.split("-", 1)[0]
107
- return Path.home() / ".claude" / "teams" / f"session-{prefix}" / "config.json"
108
-
109
-
110
- def _live_team_config(project_root: Path) -> Path | None:
111
- """Resolve the CURRENT live session's team config, or None when absent.
112
-
113
- Robust to the mid-run session-id drift that strands the phase-3
114
- ``lead.sessionId`` snapshot: keys off the live session, not the snapshot.
115
- """
116
- sid = resolve_inproc_lead_session_id(project_root)
117
- if not sid:
118
- return None
119
- cand = _team_config_for_session(sid)
120
- return cand if cand.is_file() else None
121
-
122
-
123
- def _config_path(team_arg: str) -> Path:
124
- """Resolve a team name, a team dir, or a config.json path to the config file."""
125
- p = Path(team_arg)
126
- if p.name == "config.json":
127
- return p
128
- if p.is_dir():
129
- return p / "config.json"
130
- return Path.home() / ".claude" / "teams" / team_arg / "config.json"
131
-
132
-
133
- def _atomic_write(path: Path, text: str) -> None:
134
- """Replace ``path`` atomically — config.json is the harness's roster source
135
- of truth, so a partial write on crash must never be observable."""
136
- fd, tmp = tempfile.mkstemp(dir=str(path.parent), prefix=".reconcile-")
137
- try:
138
- with os.fdopen(fd, "w") as handle:
139
- handle.write(text)
140
- os.replace(tmp, path)
141
- except BaseException:
142
- try:
143
- os.unlink(tmp)
144
- except OSError:
145
- pass
146
- raise
147
-
148
-
149
- def _reconcile_config(cfg_path: Path, dry_run: bool, print_members: bool) -> int:
150
- """Read, reconcile, and (unless dry-run) write back one config.json.
151
-
152
- When ``print_members`` is set, also emit one ``dismissible-member: <name>``
153
- line per non-lead member so the lead knows whom to ``shutdown_request``.
154
- """
155
- try:
156
- # 외부 입력: Claude Code가 소유하는 team roster 설정이다.
157
- config = load_external_json(
158
- external_claude_team_json_source(cfg_path),
159
- artifact="Claude team configuration",
160
- )
161
- except JsonBoundaryError as exc:
162
- print(f"team-reconcile: cannot read team config at {cfg_path}: {exc}",
163
- file=sys.stderr)
164
- return 1
165
-
166
- available, live = live_pane_ids()
167
- if not available:
168
- print("team-reconcile: tmux unavailable — skipped (no flip)")
169
- else:
170
- flipped = reconcile_members(config, live)
171
- if flipped and dry_run:
172
- print("team-reconcile (dry-run): would deactivate " + ", ".join(flipped))
173
- elif flipped:
174
- _atomic_write(cfg_path, json.dumps(config, indent=2, ensure_ascii=False) + "\n")
175
- print(f"team-reconcile: deactivated {len(flipped)} stale member(s): "
176
- + ", ".join(flipped))
177
- else:
178
- print("team-reconcile: no stale-active members")
179
-
180
- if print_members:
181
- for name in dismissible_members(config):
182
- print(f"dismissible-member: {name}")
183
- return 0
184
-
185
-
186
- def _session_id_from_stdin() -> str:
187
- """Extract ``session_id`` from a SessionEnd hook's JSON payload on stdin.
188
-
189
- Returns "" when stdin is a terminal (manual invocation), empty, or not the
190
- expected JSON — the caller then no-ops. The hook must never block or fail a
191
- session teardown.
192
- """
193
- if sys.stdin.isatty():
194
- return ""
195
- try:
196
- raw = sys.stdin.read()
197
- except OSError:
198
- return ""
199
- if not raw.strip():
200
- return ""
201
- try:
202
- data = json.loads(raw)
203
- except json.JSONDecodeError:
204
- return ""
205
- sid = data.get("session_id") or data.get("sessionId") or ""
206
- return sid if isinstance(sid, str) else ""
207
-
208
-
209
- def _run_resolve_and_reconcile(project_root: str, fallback_team: str, dry_run: bool) -> int:
210
- """``--project-root`` mode: resolve the live roster, reconcile, list members."""
211
- cfg = _live_team_config(Path(project_root))
212
- if cfg is None and fallback_team:
213
- fb = _config_path(fallback_team)
214
- cfg = fb if fb.is_file() else None
215
- if cfg is None:
216
- print("team-reconcile: no live roster for this session — nothing to dismiss")
217
- return 0
218
- print(f"team-reconcile: roster resolved at {cfg}")
219
- return _reconcile_config(cfg, dry_run, print_members=True)
220
-
221
-
222
- def _run_session_end(dry_run: bool) -> int:
223
- """``--session-end`` mode: reconcile the ending session's roster from the
224
- SessionEnd hook payload. Safety net so a lead that skipped run-end teardown
225
- still leaves no dead-pane member lingering as a live pill."""
226
- sid = _session_id_from_stdin()
227
- if not sid:
228
- return 0
229
- cfg = _team_config_for_session(sid)
230
- if not cfg.is_file():
231
- return 0
232
- return _reconcile_config(cfg, dry_run, print_members=False)
233
-
234
-
235
- def _parse_argv(argv: list[str]) -> tuple[bool, str, str, bool, list[str]]:
236
- dry_run = False
237
- project_root = ""
238
- fallback_team = ""
239
- session_end = False
240
- rest: list[str] = []
241
- it = iter(argv)
242
- for arg in it:
243
- if arg in ("--list", "--dry-run"):
244
- dry_run = True
245
- elif arg == "--project-root":
246
- project_root = next(it, "")
247
- elif arg == "--fallback-team":
248
- fallback_team = next(it, "")
249
- elif arg == "--session-end":
250
- session_end = True
251
- else:
252
- rest.append(arg)
253
- return dry_run, project_root, fallback_team, session_end, rest
254
-
255
-
256
- def main(argv: list[str]) -> int:
257
- dry_run, project_root, fallback_team, session_end, rest = _parse_argv(argv)
258
-
259
- if session_end:
260
- return _run_session_end(dry_run)
261
- if project_root:
262
- return _run_resolve_and_reconcile(project_root, fallback_team, dry_run)
263
-
264
- if len(rest) != 1:
265
- print("usage: okstra-team-reconcile.sh [--list] "
266
- "(<team-name|team-dir|config.json> | --project-root <dir> "
267
- "[--fallback-team <label>] | --session-end)",
268
- file=sys.stderr)
269
- return 2
270
-
271
- cfg_path = _config_path(rest[0])
272
- if not cfg_path.is_file():
273
- print(f"team-reconcile: no team config at {cfg_path}", file=sys.stderr)
274
- return 1
275
- return _reconcile_config(cfg_path, dry_run, print_members=False)
@@ -1,134 +0,0 @@
1
- """tmux command helpers for okstra-owned container and rerun sessions.
2
-
3
- Worker dispatch does not come through here. Workers land in a cmux surface or,
4
- outside cmux, in a cli-wrapper subprocess — neither owns a tmux pane. What is
5
- left is the two places that use tmux purely as a way to hold a long-lived
6
- background process: the container watcher/tail panes and `cmd-rerun.sh`'s
7
- detached spawn.
8
- """
9
- from __future__ import annotations
10
-
11
- import shlex
12
- import shutil
13
- import subprocess
14
- from typing import Optional, Sequence
15
-
16
-
17
- # container watcher/tail pane 전용 태그. 이 태그가 붙은 pane 은 세션 종료 후에도
18
- # 생존한다 — watcher/tail 의 "세션 후 생존" 불변식이다. 예전에는 SessionEnd 의
19
- # 태그 스캔이 다른 태그만 본다는 사실이 그 생존을 지탱했지만, 지금은 그 스캔과
20
- # 훅과 스크립트 자체가 없어 pane 을 세션 경계에서 회수하는 주체가 아예 없다. 회수는 `down` / `stop-watcher` 의 스코프 reap 뿐이다.
21
- CONTAINER_TAG_OPTION = "@okstra_container_run"
22
-
23
-
24
- def _shell_quote(s: str) -> str:
25
- """POSIX shell 안전 인용. shlex.quote 가 모든 메타문자를 처리한다."""
26
- return shlex.quote(s)
27
-
28
-
29
- def build_tmux_command(*, session_name: str, cwd: str, run_seq: int,
30
- argv: list, okstra_script: str,
31
- extra_env: Optional[dict] = None) -> list:
32
- """tmux new-session 명령을 list 형태로 반환."""
33
- env_prefix = f"OKSTRA_RUN_SEQ_OVERRIDE={run_seq}"
34
- if extra_env:
35
- for k, v in extra_env.items():
36
- env_prefix += f" {k}={_shell_quote(str(v))}"
37
- inner = (f"{env_prefix} {_shell_quote(okstra_script)} "
38
- + " ".join(_shell_quote(a) for a in argv))
39
- return ["tmux", "new-session", "-d", "-s", session_name, "-c", cwd, inner]
40
-
41
-
42
- def tmux_available() -> bool:
43
- if shutil.which("tmux") is None:
44
- return False
45
- try:
46
- result = run_tmux(["display-message", "-p", "#{version}"], timeout=3)
47
- except (OSError, subprocess.SubprocessError):
48
- return False
49
- return result.returncode == 0
50
-
51
-
52
- def run_tmux(
53
- args: Sequence[str], *, timeout: int = 10
54
- ) -> subprocess.CompletedProcess[str]:
55
- return subprocess.run(
56
- ["tmux", *args],
57
- capture_output=True,
58
- text=True,
59
- timeout=timeout,
60
- check=False,
61
- )
62
-
63
-
64
- def new_detached_session(
65
- session_name: str, cwd: str, first_cmd: str | None = None
66
- ) -> str:
67
- """detached tmux 세션을 띄우고 첫 pane id 를 반환한다.
68
-
69
- cmd-rerun.sh 의 detached spawn 패턴을 python 으로 옮긴 것
70
- (`tmux new-session -d -s <name> -c <cwd> [cmd]`). `-P -F #{pane_id}` 로
71
- 생성된 pane id 를 받아 이후 split_container_pane 의 `-t` 대상으로 쓴다.
72
- first_cmd 미지정 시 기본 셸이 첫 pane 을 점유한다 — `true` 처럼 즉시 끝나는
73
- 명령을 주면 단일-윈도우 세션이 split 전에 무너질 수 있으므로(remain-on-exit
74
- off), holder pane 은 오래 사는 명령이어야 한다.
75
- """
76
- args = ["new-session", "-d", "-s", session_name, "-c", cwd,
77
- "-P", "-F", "#{pane_id}"]
78
- if first_cmd:
79
- args.append(first_cmd)
80
- result = run_tmux(args)
81
- if result.returncode != 0:
82
- raise RuntimeError(result.stderr.strip() or "tmux new-session failed")
83
- return result.stdout.strip()
84
-
85
-
86
- def split_container_pane(
87
- *,
88
- session_pane: Optional[str],
89
- cwd: str,
90
- command: str,
91
- title: str,
92
- scope_value: str,
93
- kind: str,
94
- ) -> Optional[str]:
95
- """container watcher/tail pane 을 split 하고 container 전용 태그만 부착한다.
96
-
97
- reap 가 스캔하는 trace 태그는 절대 부착하지 않는다 — CONTAINER_TAG_OPTION 으로
98
- 직접 set-option 한다. session_pane 가 빈값/None(tmux 미사용 경로)이면 raise
99
- 없이 None 반환(degrade).
100
- """
101
- if not session_pane:
102
- return None
103
- result = run_tmux(
104
- ["split-window", "-h", "-P", "-F", "#{pane_id}",
105
- "-c", cwd, "-t", session_pane, command]
106
- )
107
- if result.returncode != 0:
108
- raise RuntimeError(result.stderr.strip() or "tmux split-window failed")
109
- pane_id = result.stdout.strip()
110
- set_pane_title(pane_id, title)
111
- tag_container_pane(pane_id, scope_value)
112
- return pane_id
113
-
114
-
115
- def tag_container_pane(pane_id: str, scope_value: str) -> None:
116
- """container 전용 태그(CONTAINER_TAG_OPTION)만 부착한다.
117
-
118
- reap 가 스캔하는 trace 태그는 절대 거치지 않는다 — 이 태그가 붙은 pane 은
119
- 세션 종료 후에도 생존하고 `down`/`stop-watcher` 의 스코프 reap 로만
120
- 회수된다. holder pane 도 이 태그로 묶어 회수 대상에 포함한다."""
121
- run_tmux(["set-option", "-p", "-t", pane_id, CONTAINER_TAG_OPTION, scope_value])
122
-
123
-
124
- def set_pane_title(pane_id: str, title: str) -> None:
125
- result = run_tmux(["select-pane", "-t", pane_id, "-T", title])
126
- if result.returncode != 0:
127
- raise RuntimeError(result.stderr.strip() or "tmux select-pane failed")
128
-
129
-
130
- def kill_pane(pane_id: str) -> None:
131
- try:
132
- run_tmux(["kill-pane", "-t", pane_id])
133
- except (OSError, subprocess.SubprocessError):
134
- return