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
@@ -7,60 +7,10 @@ from pathlib import Path
7
7
  from typing import List, Optional
8
8
 
9
9
  from .paths import resolve_under_root
10
- from .json_boundary import JsonBoundaryError, load_owned_object
11
10
  from .reconcile import _parse_iso
12
11
  from .run_index_row import read_run_index
13
12
 
14
13
 
15
- def list_projects(home: Path, *, only_active: bool = False,
16
- sort_by: str = "recent") -> List[dict]:
17
- """projects/<id>/meta.json 을 모두 읽어 정렬된 목록을 반환."""
18
- base = home / "projects"
19
- if not base.is_dir():
20
- return []
21
- items: List[dict] = []
22
- for child in base.iterdir():
23
- meta_file = child / "meta.json"
24
- if not meta_file.is_file():
25
- continue
26
- try:
27
- meta = load_owned_object(meta_file, artifact="project metadata")
28
- except JsonBoundaryError:
29
- # 동시 쓰기/이전 크래시로 깨진 meta.json 하나가 전체 listing 을
30
- # 죽이지 않도록 해당 프로젝트만 건너뛴다(다른 JSON 리더와 동일 정책).
31
- continue
32
- # projectRoot 가 누락/빈 문자열이면 Path("").is_dir() 이 cwd 를 가리켜
33
- # True 가 되므로(missing 으로 잡히지 않음), 빈 값은 명시적으로 missing
34
- # 처리한다.
35
- proj_root = meta.get("projectRoot") or ""
36
- meta["missing"] = (not proj_root) or (not Path(proj_root).is_dir())
37
- if only_active and int(meta.get("activeCount", 0)) <= 0:
38
- continue
39
- items.append(meta)
40
- if sort_by == "name":
41
- items.sort(key=lambda m: m.get("projectId", ""))
42
- elif sort_by == "count":
43
- items.sort(key=lambda m: int(m.get("runCount", 0)), reverse=True)
44
- else:
45
- items.sort(key=lambda m: m.get("lastRunAt", ""), reverse=True)
46
- return items
47
-
48
-
49
- def format_projects_table(items: List[dict]) -> str:
50
- """projects 테이블 텍스트 포맷팅."""
51
- rows = [("PROJECT-ID", "RUNS", "ACTIVE", "LAST-RUN", "ROOT")]
52
- for it in items:
53
- root = it.get("projectRoot", "") + (" (MISSING)" if it.get("missing") else "")
54
- rows.append((it.get("projectId", ""), str(it.get("runCount", 0)),
55
- str(it.get("activeCount", 0)), it.get("lastRunAt", ""),
56
- root))
57
- widths = [max(len(r[i]) for r in rows) for i in range(5)]
58
- lines = []
59
- for r in rows:
60
- lines.append(" ".join(c.ljust(widths[i]) for i, c in enumerate(r)))
61
- return "\n".join(lines) + "\n"
62
-
63
-
64
14
  def _parse_since(value: str) -> Optional[datetime]:
65
15
  """--since 파싱. 빈 문자열은 None(필터 없음). 형식 위반은 ValueError 로
66
16
  fail-fast 한다. 이전엔 잘못된 값(`7days`)이 silently None 으로 강등되어
@@ -133,47 +83,6 @@ def list_runs(home: Path, *, project: str = "all", task_group: str = "all",
133
83
  return out
134
84
 
135
85
 
136
- def format_runs_table(rows: List[dict]) -> str:
137
- header = ("RUN-ID", "STATUS", "STARTED", "TASK-TYPE")
138
- body = [(r.get("runId", ""), r.get("status", ""),
139
- r.get("startedAt", ""), r.get("taskType", "")) for r in rows]
140
- table = [header] + body
141
- widths = [max(len(t[i]) for t in table) for i in range(4)]
142
- lines = []
143
- for t in table:
144
- lines.append(" ".join(c.ljust(widths[i]) for i, c in enumerate(t)))
145
- return "\n".join(lines) + "\n"
146
-
147
-
148
- def find_row_by_run_id(home: Path, run_id: str) -> Optional[dict]:
149
- """active+recent+archive 를 모두 뒤져 runId 가 일치하는 최신 row 반환."""
150
- rows = list_runs(home, include_archive=True)
151
- for r in rows:
152
- if r.get("runId") == run_id:
153
- return r
154
- return None
155
-
156
-
157
- def format_show(row: dict) -> str:
158
- inv = row.get("invocationFile", "")
159
- return (
160
- f"runId : {row.get('runId')}\n"
161
- f"projectId : {row.get('projectId')}\n"
162
- f"projectRoot : {row.get('projectRoot')}\n"
163
- f"taskKey : {row.get('taskGroup')}/{row.get('taskId')}\n"
164
- f"taskType : {row.get('taskType')}\n"
165
- f"status : {row.get('status')}\n"
166
- f"startedAt : {row.get('startedAt')}\n"
167
- f"finishedAt : {row.get('finishedAt')}\n"
168
- f"workers : {','.join(row.get('workers', []))}\n"
169
- f"leadModel : {row.get('leadModel')}\n"
170
- f"validation : {row.get('validation')}\n"
171
- f"runDirRel : {row.get('runDirRel')}\n"
172
- f"finalReportRecordRel : {row.get('finalReportRecordRel') or row.get('finalReportRel')}\n"
173
- f"invocationFile : {inv}\n"
174
- )
175
-
176
-
177
86
  def absolute_final_report_path(row: dict) -> Optional[Path]:
178
87
  from .final_report_paths import index_report_record_rel
179
88
 
@@ -1,4 +1,4 @@
1
- """중앙 락과 task-lock 파일명. 다른 okstra_ctl 모듈에 의존하지 않는다."""
1
+ """중앙 락과 task-key 단위 mutex. 다른 okstra_ctl 모듈에 의존하지 않는다."""
2
2
  from __future__ import annotations
3
3
 
4
4
  import contextlib
@@ -9,9 +9,10 @@ from .ids import _escape_segment_for_join, _safe_fs_segment
9
9
 
10
10
  @contextlib.contextmanager
11
11
  def central_lock(home):
12
- """~/.okstra/.lock 위에 fcntl LOCK_EX. okstra-central.sh 의
13
- okstra_central_with_lock 과 같은 파일을 사용하므로 record_start 와
14
- 상호 직렬화된다.
12
+ """~/.okstra/.lock 위에 fcntl LOCK_EX.
13
+
14
+ 중앙 인덱스(active.jsonl·recent.jsonl)를 쓰는 모든 경로가 이 한 파일을
15
+ 거치므로 record_start·reconcile·reserve 가 서로 직렬화된다.
15
16
  """
16
17
  from pathlib import Path as _Path
17
18
  home_p = _Path(home)
@@ -57,18 +58,3 @@ def worktree_provision_mutex(home, project_id: str, task_group: str,
57
58
  yield
58
59
  finally:
59
60
  fcntl.flock(f.fileno(), fcntl.LOCK_UN)
60
-
61
-
62
- def task_lock_filename(project_id: str, task_group: str, task_id: str,
63
- task_type: str) -> str:
64
- """task-key + task-type 단위 mutex 파일명. ~/.okstra/.locks/ 아래에 위치한다.
65
- okstra 의 RUN_DIR 가 task-type 별로 분리되므로 mutex 도 같은 입자에 둔다.
66
- 각 세그먼트는 먼저 fs-safe 슬러그로 정규화해 `/` 나 `..` 가 .locks 디렉터리
67
- 밖으로 mutex 를 escape 시키는 것을 막고, 이어서 `-` 를 `--` 로 escape 한
68
- 뒤 `-` 로 조인해 슬래시 경계를 보존한다(('p','feature-8','email','x') 와
69
- ('p','feature','8-email','x') 가 같은 파일명으로 매핑되어 mutex 가 공유
70
- 되는 문제 방지).
71
- """
72
- parts = [_escape_segment_for_join(_safe_fs_segment(s))
73
- for s in (project_id, task_group, task_id, task_type)]
74
- return "-".join(parts) + ".lock"
@@ -140,8 +140,22 @@ def render_result_text(result: dict) -> str:
140
140
  return "".join(parts)
141
141
 
142
142
 
143
+ _CLI_EPILOG = r"""Usage:
144
+ okstra log-report [--project-root <dir>] [--cwd <dir>] [--top <N>] [--json]
145
+
146
+ Output: JSON { ok, projectRoot, logsRoot, topLargest[], perTask[], totals }.
147
+ Scans <project-root>/.okstra/tasks/**/runs/*/prompts/*.log. Each entry reports
148
+ the wrapper transcript as transcriptBytes and its sibling prompt Markdown as
149
+ promptBytes; sizeBytes remains the transcript-size compatibility field. Sizes
150
+ are raw bytes and mtimes are epoch seconds — the caller formats KB/MB and ages.
151
+ Read-only: it never deletes logs (cleanup commands stay in the skill).
152
+ """
153
+
154
+
143
155
  def main(argv: list[str] | None = None) -> int:
144
156
  parser = argparse.ArgumentParser(
157
+ epilog=_CLI_EPILOG,
158
+ formatter_class=argparse.RawDescriptionHelpFormatter,
145
159
  prog="okstra log-report",
146
160
  description="Inventory wrapper sidecar logs by size and task (read-only).")
147
161
  parser.add_argument("--project-root", default="")
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
 
4
4
  import argparse
5
5
  import json
6
- import os
7
6
  import sys
8
7
  from pathlib import Path
9
8
 
@@ -146,8 +145,29 @@ def _parse_child_specs(values: list[str], task_group: str, default_task_id: str)
146
145
  return specs
147
146
 
148
147
 
148
+ _CLI_EPILOG = r"""Usage:
149
+ okstra manager init --manager-id <id>
150
+ okstra manager discover-projects
151
+ okstra manager new project --manager-id <id> --project-id <id> --project-root <path> [--role <role>] [--tag <tag>]
152
+ okstra manager new task-group --manager-id <id> --task-group <task-group>
153
+ okstra manager new task --manager-id <id> --task-group <task-group> --task-id <task-id> [--task <project-id:task-group:task-id>]
154
+ okstra manager task status --manager-id <id> --task-group <task-group> --task-id <task-id>
155
+ okstra manager task sync --manager-id <id> --task-group <task-group> --task-id <task-id>
156
+ okstra manager task assign --manager-id <id> --task-group <task-group> --task-id <task-id> --project-id <project-id>
157
+ okstra manager task note --manager-id <id> --task-group <task-group> --task-id <task-id> --scope <shared|project> --body <text>
158
+ okstra manager task run --manager-id <id> --project-id <project-id> --task-group <task-group> --task-id <task-id>
159
+
160
+ --workspace-root is owned by this command.
161
+ """
162
+ _CLI_DESCRIPTION = "Manage cross-project okstra tasks."
163
+
164
+
149
165
  def _parser() -> argparse.ArgumentParser:
150
- parser = argparse.ArgumentParser(prog="okstra manager")
166
+ parser = argparse.ArgumentParser(
167
+ description=_CLI_DESCRIPTION,
168
+ epilog=_CLI_EPILOG,
169
+ formatter_class=argparse.RawDescriptionHelpFormatter,
170
+ prog="okstra manager")
151
171
  parser.add_argument("--json", action="store_true")
152
172
  parser.add_argument("--workspace-root", default="")
153
173
  sub = parser.add_subparsers(dest="command", required=True)
@@ -299,7 +319,6 @@ def main(argv: list[str] | None = None) -> int:
299
319
  args.task_id,
300
320
  args.project_id,
301
321
  child_task_id=args.child_task_id,
302
- env=os.environ,
303
322
  ),
304
323
  args.json,
305
324
  )
@@ -2,7 +2,6 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  from pathlib import Path
5
- from typing import Mapping
6
5
 
7
6
  from okstra_ctl.jsonl import append_jsonl, read_jsonl
8
7
 
@@ -19,13 +18,12 @@ from .manager_store import ManagerError, _now_iso, _read_json, _read_json_defaul
19
18
 
20
19
 
21
20
  WORKER_DISPATCH_BACKEND = "subagent"
21
+ # child lead 는 언제나 subagent 로 뜬다. 예전에는 `$TMUX` 가 잡히면
22
+ # `tmux-child-lead` 를 골랐지만 tmux 백엔드가 사라져 분기가 남지 않았다.
23
+ CHILD_LEAD_BACKEND = "subagent-child-lead"
22
24
  LAUNCH_STATUS_PREPARED = "prepared"
23
25
 
24
26
 
25
- def select_child_lead_backend(env: Mapping[str, str]) -> str:
26
- return "tmux-child-lead" if str(env.get("TMUX") or "").strip() else "subagent-child-lead"
27
-
28
-
29
27
  def _find_project(projects: dict, project_id: str) -> dict:
30
28
  for project in projects.get("projects", []):
31
29
  if isinstance(project, dict) and project.get("projectId") == project_id:
@@ -189,7 +187,6 @@ def build_launch_packet(
189
187
  project_id: str,
190
188
  *,
191
189
  child_task_id: str | None = None,
192
- env: Mapping[str, str] | None = None,
193
190
  now: str | None = None,
194
191
  ) -> dict:
195
192
  manifest = _read_json(task_manifest_path(home, manager_id, task_group, task_id))
@@ -204,7 +201,6 @@ def build_launch_packet(
204
201
  target_task_key = _target_task_key(project_id, task_group, child_task_id_value)
205
202
  _validate_child_task_key(child, target_task_key)
206
203
  project_root = _validated_project_root(project)
207
- backend = select_child_lead_backend(env or {})
208
204
  created_at = now or _now_iso()
209
205
  context_path = child_context_path(home, manager_id, task_group, task_id, project_id, child_task_id_value)
210
206
  context_path.parent.mkdir(parents=True, exist_ok=True)
@@ -224,7 +220,7 @@ def build_launch_packet(
224
220
  "taskId": task_id,
225
221
  "projectId": project_id,
226
222
  "taskKey": target_task_key,
227
- "backend": backend,
223
+ "backend": CHILD_LEAD_BACKEND,
228
224
  "workerDispatchBackend": WORKER_DISPATCH_BACKEND,
229
225
  "projectRoot": project_root,
230
226
  "contextPath": str(context_path),
@@ -11,7 +11,7 @@ import json
11
11
  from pathlib import Path
12
12
  from typing import Optional
13
13
 
14
- from .json_boundary import load_owned_object
14
+ from .json_boundary import JsonBoundaryError, load_owned_object
15
15
 
16
16
 
17
17
  def build_analysis_material(brief_path: Path, directive: str = "") -> str:
@@ -43,7 +43,7 @@ def resolve_related_tasks(
43
43
  existing = manifest.get("relatedTasks") or []
44
44
  if not isinstance(existing, list):
45
45
  existing = []
46
- except Exception:
46
+ except JsonBoundaryError:
47
47
  existing = []
48
48
  provided = [v.strip() for v in (raw_related or "").split(",") if v.strip()]
49
49
  seen: set[str] = set()
@@ -389,6 +389,24 @@ def _replace_in_project_rows(
389
389
  return updated, changed
390
390
 
391
391
 
392
+ _CLI_EPILOG = r"""Dry-run by default:
393
+ prints the migration plan as JSON and exits without touching anything.
394
+
395
+ Usage:
396
+ okstra migrate [--apply] [--cwd <dir>] [--quiet]
397
+
398
+ Options:
399
+ --apply Execute the migration (git mv + .gitignore + okstra-home
400
+ registry row updates). Without it, preview only.
401
+ --cwd Project root to migrate. Default: current directory.
402
+ --quiet Single-line JSON output.
403
+
404
+ Exits 1 when the migration is refused (.okstra/ already exists, or no
405
+ legacy .project-docs/okstra/ found).
406
+ """
407
+ _CLI_DESCRIPTION = "Move legacy .project-docs/okstra/ to .okstra/ (one-shot)."
408
+
409
+
392
410
  def main(argv: Optional[list[str]] = None) -> int:
393
411
  """`python3 -m okstra_ctl.migrate [--apply] [--cwd <dir>] [--quiet]`.
394
412
 
@@ -399,7 +417,11 @@ def main(argv: Optional[list[str]] = None) -> int:
399
417
  import argparse
400
418
  import sys
401
419
 
402
- p = argparse.ArgumentParser(prog="okstra migrate")
420
+ p = argparse.ArgumentParser(
421
+ description=_CLI_DESCRIPTION,
422
+ epilog=_CLI_EPILOG,
423
+ formatter_class=argparse.RawDescriptionHelpFormatter,
424
+ prog="okstra migrate")
403
425
  p.add_argument("--apply", action="store_true",
404
426
  help="실제 실행 (기본: dry-run 미리보기)")
405
427
  p.add_argument("--cwd", default=".",
@@ -142,7 +142,6 @@ def _catalog_row(model, adapter, role: str | None) -> dict[str, Any]:
142
142
  }
143
143
  if not model.selectable:
144
144
  row["reason"] = "not selectable"
145
- return row
146
145
  if role != "leader":
147
146
  return row
148
147
  if model.provider_id != adapter.descriptor.native_provider_id:
@@ -165,9 +164,10 @@ def _catalog_row(model, adapter, role: str | None) -> dict[str, Any]:
165
164
  # resolution actually does — it accepts the model and falls back to
166
165
  # `cli-wrapper` — leaving no way to find out why a lead was not native.
167
166
  row["leaderRunner"] = "cli-wrapper"
168
- row["reason"] = (
169
- f"native-session unavailable ({exc}); the lead runs via cli-wrapper"
170
- )
167
+ row["reason"] = "; ".join(filter(None, (
168
+ row["reason"],
169
+ f"native-session unavailable ({exc}); the lead runs via cli-wrapper",
170
+ )))
171
171
  else:
172
172
  row["leaderRunner"] = "native-session"
173
173
  return row
@@ -234,8 +234,22 @@ def _write_json_atomic(path: Path, payload: dict[str, Any]) -> None:
234
234
  write_owned_object_atomic(path, payload, artifact="model configuration")
235
235
 
236
236
 
237
+ _CLI_EPILOG = r"""Usage:
238
+ okstra model list [--role <role>] [--host <host>] [--json]
239
+ okstra model default set <role> <modelRef[,modelRef...]> --scope project|global [--cwd <dir>]
240
+ okstra model default unset <role> --scope project|global [--cwd <dir>]
241
+
242
+ Listing and default writes never send an inference call.
243
+ """
244
+ _CLI_DESCRIPTION = "List catalog models and manage role defaults."
245
+
246
+
237
247
  def main(argv: list[str] | None = None) -> int:
238
- parser = argparse.ArgumentParser(prog="okstra model")
248
+ parser = argparse.ArgumentParser(
249
+ description=_CLI_DESCRIPTION,
250
+ epilog=_CLI_EPILOG,
251
+ formatter_class=argparse.RawDescriptionHelpFormatter,
252
+ prog="okstra model")
239
253
  sub = parser.add_subparsers(dest="command", required=True)
240
254
  listed = sub.add_parser("list")
241
255
  listed.add_argument("--role")
@@ -12,6 +12,12 @@ import subprocess
12
12
  from functools import lru_cache
13
13
  from pathlib import Path
14
14
 
15
+ from .json_boundary import (
16
+ JsonBoundaryError,
17
+ external_codex_catalog_json_source,
18
+ load_external_json,
19
+ )
20
+
15
21
  # Per-role reasoning-effort policy for CLIs that bake effort into the model
16
22
  # name (agy). Deterministic — no per-run improvisation.
17
23
  # `critic` is intentionally absent: an opt-in critic reuses its worker's
@@ -30,37 +36,16 @@ def role_effort(role: str) -> str:
30
36
  return ROLE_EFFORT.get(role, _DEFAULT_EFFORT)
31
37
 
32
38
 
33
- # agy lists a tier per model, but the listed slug is not always the model it
34
- # serves: on agy 1.1.10 a `gemini-3.1-pro-high` session identifies itself as
35
- # "Gemini 3.6 Flash", while `gemini-3.1-pro-low` correctly identifies as
36
- # "Gemini 3.1 Pro". The substitution is not visible anywhere in the dispatch —
37
- # `agy models` lists the slug and the run exits 0 — so it surfaces only as a
38
- # verifier that never refutes anything: measured over one 63-item plan-body
39
- # prompt, the high slug returned all-AGREE in 64s while the low slug spent 328s
40
- # and raised three DISAGREEs, one of them the defect claude and codex both
41
- # caught that round. Only the `high` tier is affected, so a role asking for any
42
- # other effort still resolves normally (and a tier agy does not offer still
43
- # hard-fails below).
44
- _UNTRUSTED_HIGH_TIER_EXECUTIONS = frozenset({"gemini-3.1-pro"})
45
-
46
-
47
- def _dispatch_effort(execution: str, role: str) -> str:
48
- effort = role_effort(role).lower()
49
- if effort == "high" and execution in _UNTRUSTED_HIGH_TIER_EXECUTIONS:
50
- return "low"
51
- return effort
52
-
53
-
54
- def dispatch_tier_suffixes() -> frozenset[str]:
55
- """Every tier suffix dispatch can append to an agy execution value.
39
+ def agy_tier_suffixes() -> frozenset[str]:
40
+ """agy 가 모델 식별자에 담는 티어 어휘.
56
41
 
57
- The provider serves back the suffixed identity it was given, so reading
58
- that identity means undoing exactly this set — not guessing at whatever
59
- trails the last hyphen. `low` is here because `_dispatch_effort` demotes
60
- the untrusted high tier to it.
42
+ 서빙 식별자에서 티어를 벗겨 카탈로그 id 로 되돌릴 때 이 집합만 벗긴다 —
43
+ 마지막 하이픈 뒤를 무엇이든 벗기면 카탈로그에 없는 id 를 만들어 낸다.
44
+ 이 집합은 okstra 의 역할별 effort 정책(`ROLE_EFFORT`)이 아니라 agy 의
45
+ 어휘다. 둘은 다르다: 정책이 지금 붙이는 것은 `high` 뿐이지만, 관측값에는
46
+ 다른 티어가 실려 올 수 있다.
61
47
  """
62
- efforts = {value.lower() for value in (*ROLE_EFFORT.values(), _DEFAULT_EFFORT)}
63
- return frozenset(efforts | {"low"})
48
+ return frozenset({"high", "medium", "low"})
64
49
 
65
50
 
66
51
  @lru_cache(maxsize=1)
@@ -84,28 +69,42 @@ def agy_models(agy_bin: str = "agy") -> tuple[str, ...]:
84
69
  return tuple(model_id for model_id in ids if model_id)
85
70
 
86
71
 
87
- def _codex_config_path() -> Path:
88
- return Path.home() / ".codex" / "config.toml"
72
+ def _codex_home() -> Path:
73
+ return Path.home() / ".codex"
89
74
 
90
75
 
91
- def codex_availability(config_path: Path | None = None) -> tuple[str, ...]:
92
- """Keys under [tui.model_availability_nux] in codex config, or ()."""
93
- path = config_path or _codex_config_path()
76
+ def codex_availability(codex_home: Path | None = None) -> tuple[str, ...]:
77
+ """Model slugs the codex CLI last fetched from the provider, or ().
78
+
79
+ The earlier source, `[tui.model_availability_nux]` in `config.toml`, is a
80
+ per-model onboarding-notice ledger, not an account's model list: measured
81
+ 2026-08-26 that section held `gpt-5.5` and `gpt-5.6-sol` while this machine's
82
+ session history recorded 2072 turns on `gpt-5.6-terra` and 130 on
83
+ `gpt-5.6-luna`. Gating dispatch on it rejected models the CLI runs.
84
+
85
+ `visibility` is not filtered. It governs the CLI's own model picker, not
86
+ whether `--model <slug>` is accepted -- `codex-auto-review` is `hide` and is
87
+ dispatched by okstra today.
88
+ """
89
+ home = codex_home or _codex_home()
94
90
  try:
95
- lines = path.read_text(encoding="utf-8").splitlines()
96
- except OSError:
91
+ payload = load_external_json(
92
+ external_codex_catalog_json_source(home / "models_cache.json", home),
93
+ artifact="codex model catalog",
94
+ )
95
+ except JsonBoundaryError:
96
+ # No cache yet, or one this process may not read. Returning () turns the
97
+ # gate off rather than blocking dispatch, which is what every other
98
+ # discovery failure in this module does.
99
+ return ()
100
+ models = payload.get("models") if isinstance(payload, dict) else None
101
+ if not isinstance(models, list):
97
102
  return ()
98
103
  out: list[str] = []
99
- in_section = False
100
- for raw in lines:
101
- line = raw.strip()
102
- if line.startswith("[") and line.endswith("]"):
103
- in_section = line == "[tui.model_availability_nux]"
104
- continue
105
- if in_section and "=" in line:
106
- key = line.split("=", 1)[0].strip().strip('"')
107
- if key:
108
- out.append(key)
104
+ for entry in models:
105
+ slug = entry.get("slug") if isinstance(entry, dict) else None
106
+ if isinstance(slug, str) and slug.strip():
107
+ out.append(slug.strip())
109
108
  return tuple(out)
110
109
 
111
110
 
@@ -150,7 +149,7 @@ def normalize_antigravity_execution(
150
149
  # (`gemini-3.1-pro-high`) and rejects the bare slug with "requires --effort"
151
150
  # — a flag the wrapper never sends. So the suffixed form is the only
152
151
  # dispatchable spelling, including on the un-verified fallback below.
153
- tiered = f"{execution}-{_dispatch_effort(execution, role)}"
152
+ tiered = f"{execution}-{role_effort(role).lower()}"
154
153
  if not available:
155
154
  return tiered # discovery unavailable → dispatch anyway, wrapper still guards
156
155
  if execution in available:
@@ -0,0 +1 @@
1
+ """`okstra model-io` 의 구현. 진입점은 `..model_io_cli` 다."""
@@ -0,0 +1,163 @@
1
+ """읽은 값을 고정 Markdown 줄로 바꾼다.
2
+
3
+ 이 층은 파일을 열지 않는다 — 이미 읽힌 매핑을 받아 문자열만 만든다. 그래서
4
+ 참조 해석과 독립이고, 서식을 고칠 때 경로 규칙을 다시 읽을 필요가 없다.
5
+ `_provider_models` 만 예외적으로 거절하지만, 그것도 디스크가 아니라 받은
6
+ 매핑 안의 모순을 보는 것이다.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+ from typing import Any, Mapping
12
+
13
+ from ..fixed_text import line as _line, scalar as _value
14
+ from ..json_boundary import JsonBoundaryError
15
+
16
+
17
+ def _mapping(payload: Mapping[str, Any], key: str) -> Mapping[str, Any]:
18
+ value = payload.get(key)
19
+ return value if isinstance(value, Mapping) else {}
20
+
21
+
22
+ def _project_policy_lines(project: Mapping[str, Any]) -> str:
23
+ packs = project.get("reviewRulePacks")
24
+ pack_lines = (
25
+ [
26
+ _line("Review rule pack", path)
27
+ for path in packs
28
+ if isinstance(path, str)
29
+ ]
30
+ if isinstance(packs, list)
31
+ else []
32
+ )
33
+ qa_lines: list[str] = []
34
+ qa_commands = project.get("qaCommands")
35
+ if isinstance(qa_commands, Mapping):
36
+ for category, entries in sorted(qa_commands.items()):
37
+ if not isinstance(entries, list):
38
+ continue
39
+ for entry in entries:
40
+ if not isinstance(entry, Mapping):
41
+ continue
42
+ qa_lines.append(
43
+ f"- QA `{_value(category)}` / `{_value(entry.get('label'))}`: "
44
+ f"`{_value(entry.get('cmd'))}`\n"
45
+ )
46
+ return (
47
+ "\n## Project Review Rule Packs\n\n"
48
+ + ("".join(pack_lines) or "- None\n")
49
+ + "\n## Project QA Commands\n\n"
50
+ + ("".join(qa_lines) or "- None\n")
51
+ )
52
+
53
+
54
+ def _assignment_line(prefix: str, assignment: Mapping[str, Any], identifier: object = None) -> str:
55
+ label = prefix if identifier is None else f"{prefix} `{_value(identifier)}`"
56
+ return (
57
+ f"- {label}: role `{_value(assignment.get('role'))}`; "
58
+ f"provider `{_value(assignment.get('provider'))}`; "
59
+ f"model `{_value(assignment.get('model'))}`\n"
60
+ )
61
+
62
+
63
+ def _worker_assignment_lines(run: Mapping[str, Any]) -> str:
64
+ assignments = run.get("workerAssignments")
65
+ if not isinstance(assignments, list):
66
+ return "- None\n"
67
+ lines = [
68
+ _assignment_line("Worker", assignment, assignment.get("workerId"))
69
+ for assignment in assignments
70
+ if isinstance(assignment, Mapping)
71
+ ]
72
+ return "".join(lines) or "- None\n"
73
+
74
+
75
+ def _worker_prompt_lines(run: Mapping[str, Any]) -> str:
76
+ paths = run.get("workerPromptPathByWorkerId")
77
+ if not isinstance(paths, Mapping):
78
+ return "- None\n"
79
+ lines = [
80
+ f"- Worker prompt `{_value(worker_id)}`: `{_value(path)}`\n"
81
+ for worker_id, path in sorted(paths.items(), key=lambda item: _value(item[0]))
82
+ ]
83
+ return "".join(lines) or "- None\n"
84
+
85
+
86
+ def _next_phase_lines(workflow: Mapping[str, Any]) -> str:
87
+ pointer = _mapping(workflow, "nextRecommendedPhase")
88
+ return (
89
+ _line("Next phase", pointer.get("phase"))
90
+ + _line("Next phase status", pointer.get("status"))
91
+ + _line("Next phase rationale", pointer.get("rationale"))
92
+ )
93
+
94
+
95
+ def _status_overview_text(rows: list[dict[str, object]]) -> str:
96
+ blocks = [_line("Task count", len(rows))]
97
+ for row in rows:
98
+ blocks.extend((
99
+ "\n## Task\n\n",
100
+ _line("Task key", row.get("taskKey")),
101
+ _line("Task group", row.get("taskGroup")),
102
+ _line("Task type", row.get("taskType")),
103
+ _line("Latest run status", row.get("latestRunStatus")),
104
+ _line("Updated at", row.get("updatedAt")),
105
+ _line("Latest run manifest", row.get("latestRunManifestPath")),
106
+ _line("Work category", row.get("workCategory")),
107
+ _line("Current status", row.get("currentStatus")),
108
+ _line("Current phase", row.get("currentPhase")),
109
+ _line("Current phase state", row.get("currentPhaseState")),
110
+ _line("Next phase", row.get("nextPhase")),
111
+ _line("Next phase status", row.get("nextPhaseStatus")),
112
+ _line("Next phase rationale", row.get("nextPhaseRationale")),
113
+ _line("Awaiting approval", row.get("awaitingApproval")),
114
+ _line("Latest report", row.get("latestReportRecordPath")),
115
+ _line("Latest resume command", row.get("latestResumeCommandPath")),
116
+ _line("Work status", row.get("workStatus")),
117
+ ))
118
+ return "# Okstra Status Input\n\n" + "".join(blocks)
119
+
120
+
121
+ def _history_overview_text(rows: list[dict[str, object]], limit: int) -> str:
122
+ shown = rows[:limit]
123
+ blocks = [
124
+ _line("Task count", len(rows)),
125
+ _line("Shown task count", len(shown)),
126
+ _line("Remaining task count", max(0, len(rows) - len(shown))),
127
+ ]
128
+ for row in shown:
129
+ blocks.extend((
130
+ "\n## Task\n\n", _line("Task key", row.get("taskKey")),
131
+ _line("Task group", row.get("taskGroup")),
132
+ _line("Task type", row.get("taskType")),
133
+ _line("Current status", row.get("currentStatus")),
134
+ _line("Latest run status", row.get("latestRunStatus")),
135
+ _line("Last run", row.get("lastRun")),
136
+ _line("Latest report", row.get("latestReportRecordPath")),
137
+ _line("Updated at", row.get("updatedAt")),
138
+ _line("Latest run manifest", row.get("latestRunManifestPath")),
139
+ ))
140
+ return "# Okstra History Input\n\n" + "".join(blocks)
141
+
142
+
143
+ def _csv(value: object) -> str:
144
+ return ",".join(str(item) for item in value) if isinstance(value, list) else ""
145
+
146
+
147
+ def _provider_models(run: Mapping[str, Any]) -> dict[str, str]:
148
+ values: dict[str, set[str]] = {}
149
+ assignments = run.get("workerAssignments")
150
+ if isinstance(assignments, list):
151
+ for assignment in assignments:
152
+ if not isinstance(assignment, Mapping):
153
+ continue
154
+ provider = assignment.get("provider")
155
+ model = assignment.get("model")
156
+ if isinstance(provider, str) and isinstance(model, str) and model:
157
+ values.setdefault(provider, set()).add(model)
158
+ for provider, models in values.items():
159
+ if len(models) > 1:
160
+ raise JsonBoundaryError(
161
+ Path("workerAssignments"), "run manifest", f"conflicting models for provider {provider}"
162
+ )
163
+ return {provider: next(iter(models)) for provider, models in values.items()}