okstra 0.186.7 → 0.188.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (546) hide show
  1. package/README.md +1 -1
  2. package/dist/cli-registry.d.mts +88 -1
  3. package/dist/cli-registry.mjs +68 -111
  4. package/dist/cli-registry.mjs.map +1 -1
  5. package/dist/commands/execute/render-bundle.mjs +0 -1
  6. package/dist/commands/execute/render-bundle.mjs.map +1 -1
  7. package/dist/commands/execute/run.mjs +8 -3
  8. package/dist/commands/execute/run.mjs.map +1 -1
  9. package/dist/commands/lifecycle/check-project.mjs +1 -14
  10. package/dist/commands/lifecycle/check-project.mjs.map +1 -1
  11. package/dist/commands/lifecycle/config.mjs +38 -40
  12. package/dist/commands/lifecycle/config.mjs.map +1 -1
  13. package/dist/commands/lifecycle/doctor.d.mts +22 -7
  14. package/dist/commands/lifecycle/doctor.mjs +77 -49
  15. package/dist/commands/lifecycle/doctor.mjs.map +1 -1
  16. package/dist/commands/lifecycle/install.d.mts +12 -10
  17. package/dist/commands/lifecycle/install.mjs +107 -40
  18. package/dist/commands/lifecycle/install.mjs.map +1 -1
  19. package/dist/commands/lifecycle/paths.mjs +8 -3
  20. package/dist/commands/lifecycle/paths.mjs.map +1 -1
  21. package/dist/commands/lifecycle/preflight.mjs +2 -1
  22. package/dist/commands/lifecycle/preflight.mjs.map +1 -1
  23. package/dist/commands/lifecycle/setup.mjs +22 -36
  24. package/dist/commands/lifecycle/setup.mjs.map +1 -1
  25. package/dist/commands/lifecycle/uninstall.d.mts +4 -2
  26. package/dist/commands/lifecycle/uninstall.mjs +59 -15
  27. package/dist/commands/lifecycle/uninstall.mjs.map +1 -1
  28. package/dist/commands/memory/memory.mjs +7 -1
  29. package/dist/commands/memory/memory.mjs.map +1 -1
  30. package/dist/lib/helper-scripts.d.mts +1 -1
  31. package/dist/lib/helper-scripts.mjs +10 -18
  32. package/dist/lib/helper-scripts.mjs.map +1 -1
  33. package/dist/lib/host-config.d.mts +72 -0
  34. package/dist/lib/host-config.mjs +404 -0
  35. package/dist/lib/host-config.mjs.map +1 -0
  36. package/dist/lib/host-registry-client.d.mts +2 -2
  37. package/dist/lib/host-registry-client.mjs +0 -3
  38. package/dist/lib/host-registry-client.mjs.map +1 -1
  39. package/dist/lib/install-assets.d.mts +1 -0
  40. package/dist/lib/install-assets.mjs +4 -0
  41. package/dist/lib/install-assets.mjs.map +1 -1
  42. package/dist/lib/proc.d.mts +2 -0
  43. package/dist/lib/proc.mjs +12 -0
  44. package/dist/lib/proc.mjs.map +1 -1
  45. package/dist/lib/python-command.d.mts +2 -0
  46. package/dist/lib/python-command.mjs +52 -0
  47. package/dist/lib/python-command.mjs.map +1 -0
  48. package/dist/lib/python-helper.d.mts +2 -5
  49. package/dist/lib/python-helper.mjs +3 -52
  50. package/dist/lib/python-helper.mjs.map +1 -1
  51. package/dist/lib/runtime-payload.d.mts +23 -0
  52. package/dist/lib/runtime-payload.mjs +57 -0
  53. package/dist/lib/runtime-payload.mjs.map +1 -0
  54. package/dist/lib/types.d.mts +24 -13
  55. package/docs/architecture/storage-model.md +21 -5
  56. package/docs/architecture.md +39 -38
  57. package/docs/cli.md +55 -76
  58. package/docs/coding-rules.md +295 -0
  59. package/docs/container.md +10 -36
  60. package/docs/contributor-change-matrix.md +1 -1
  61. package/docs/for-ai/skills/okstra-code-review.md +0 -1
  62. package/docs/for-ai/skills/okstra-container-build.md +8 -41
  63. package/docs/for-ai/skills/okstra-inspect.md +4 -4
  64. package/docs/for-ai/skills/okstra-manager.md +2 -2
  65. package/docs/for-ai/skills/okstra-rollup.md +0 -1
  66. package/docs/for-ai/skills/okstra-run.md +3 -3
  67. package/docs/for-ai/skills/okstra-user-response.md +0 -1
  68. package/docs/performance-improvement-plan-v2.md +1 -1
  69. package/docs/project-structure-overview.md +62 -62
  70. package/docs/task-process/README.md +3 -3
  71. package/docs/task-process/common-flow.md +1 -1
  72. package/docs/task-process/error-analysis.md +3 -3
  73. package/docs/task-process/implementation-planning.md +3 -1
  74. package/docs/task-process/release-handoff.md +1 -1
  75. package/package.json +3 -2
  76. package/runtime/BUILD.json +2 -2
  77. package/runtime/agents/workers/claude-worker.md +6 -11
  78. package/runtime/agents/workers/report-writer-worker.md +1 -1
  79. package/runtime/bin/lib/okstra/cli.sh +4 -0
  80. package/runtime/bin/lib/okstra/globals.sh +2 -0
  81. package/runtime/bin/lib/okstra/interactive.sh +26 -173
  82. package/runtime/bin/lib/okstra/project-resolver.sh +23 -61
  83. package/runtime/bin/lib/okstra/usage.sh +3 -3
  84. package/runtime/bin/okstra-compact-reminder.sh +1 -5
  85. package/runtime/bin/okstra-error-log.py +19 -2
  86. package/runtime/bin/okstra-import-check.py +26 -0
  87. package/runtime/bin/okstra-inject-report-index.py +5 -4
  88. package/runtime/bin/okstra-provider-exec.py +22 -17
  89. package/runtime/bin/okstra-render-final-report.py +16 -6
  90. package/runtime/bin/okstra-render-report-views.py +47 -22
  91. package/runtime/bin/okstra-report-translate.py +30 -18
  92. package/runtime/bin/okstra-spawn-followups.py +4 -0
  93. package/runtime/bin/okstra-token-usage.py +3 -0
  94. package/runtime/bin/okstra.sh +4 -2
  95. package/runtime/bin/okstra_bootstrap.py +58 -0
  96. package/runtime/prompts/coding-preflight/overview.md +1 -1
  97. package/runtime/prompts/duties/planning-worker.md +1 -1
  98. package/runtime/prompts/launch.template.md +35 -9
  99. package/runtime/prompts/lead/adapters/cmux.md +3 -3
  100. package/runtime/prompts/lead/convergence.md +99 -88
  101. package/runtime/prompts/lead/okstra-lead-contract.md +63 -12
  102. package/runtime/prompts/lead/plan-body-verification.md +65 -46
  103. package/runtime/prompts/lead/report-writer.md +35 -5
  104. package/runtime/prompts/lead/team-contract.md +4 -1
  105. package/runtime/prompts/profiles/_clarification-recommendation.md +2 -1
  106. package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
  107. package/runtime/prompts/profiles/_common-contract.md +3 -3
  108. package/runtime/prompts/profiles/_coverage-critic.md +1 -1
  109. package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
  110. package/runtime/prompts/profiles/_implementation-executor.md +3 -1
  111. package/runtime/prompts/profiles/_implementation-verifier.md +16 -2
  112. package/runtime/prompts/profiles/error-analysis.md +4 -3
  113. package/runtime/prompts/profiles/final-verification.md +2 -2
  114. package/runtime/prompts/profiles/implementation-planning.md +22 -17
  115. package/runtime/prompts/profiles/implementation.md +3 -2
  116. package/runtime/prompts/profiles/requirements-discovery.md +2 -1
  117. package/runtime/prompts/wizard/prompts.ko.json +29 -8
  118. package/runtime/python/okstra_ctl/__init__.py +6 -27
  119. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +4 -5
  120. package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +21 -2
  121. package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +3 -12
  122. package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +63 -22
  123. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +20 -21
  124. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +37 -20
  125. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +86 -14
  126. package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +12 -12
  127. package/runtime/python/okstra_ctl/adapters/runtime/cmux.py +4 -7
  128. package/runtime/python/okstra_ctl/agent/__init__.py +1 -0
  129. package/runtime/python/okstra_ctl/{agent_activity.py → agent/activity.py} +16 -2
  130. package/runtime/python/okstra_ctl/{agent_invocation.py → agent/invocation.py} +34 -5
  131. package/runtime/python/okstra_ctl/agent/prompt_cli/__init__.py +19 -0
  132. package/runtime/python/okstra_ctl/agent/prompt_cli/__main__.py +11 -0
  133. package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +228 -0
  134. package/runtime/python/okstra_ctl/agent/prompt_cli/dynamic_verifier.py +155 -0
  135. package/runtime/python/okstra_ctl/agent/prompt_cli/emit.py +66 -0
  136. package/runtime/python/okstra_ctl/agent/prompt_cli/inputs.py +138 -0
  137. package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +605 -0
  138. package/runtime/python/okstra_ctl/agent/prompt_cli/results.py +197 -0
  139. package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +107 -0
  140. package/runtime/python/okstra_ctl/analysis_packet.py +47 -4
  141. package/runtime/python/okstra_ctl/approval_decisions.py +287 -10
  142. package/runtime/python/okstra_ctl/assignment_resolver.py +0 -12
  143. package/runtime/python/okstra_ctl/attempt_evidence.py +12 -24
  144. package/runtime/python/okstra_ctl/blocking_checks.py +251 -0
  145. package/runtime/python/okstra_ctl/brief_frontmatter.py +0 -7
  146. package/runtime/python/okstra_ctl/clarification_items/__init__.py +102 -0
  147. package/runtime/python/okstra_ctl/clarification_items/carry.py +224 -0
  148. package/runtime/python/okstra_ctl/clarification_items/dispositions.py +135 -0
  149. package/runtime/python/okstra_ctl/clarification_items/parsing.py +253 -0
  150. package/runtime/python/okstra_ctl/clarification_items/rows.py +115 -0
  151. package/runtime/python/okstra_ctl/clarification_items/scan.py +215 -0
  152. package/runtime/python/okstra_ctl/clarification_items/sidecars.py +211 -0
  153. package/runtime/python/okstra_ctl/cmux.py +38 -31
  154. package/runtime/python/okstra_ctl/code_review_target.py +163 -1
  155. package/runtime/python/okstra_ctl/conformance.py +23 -0
  156. package/runtime/python/okstra_ctl/container.py +38 -421
  157. package/runtime/python/okstra_ctl/context_cost.py +32 -4
  158. package/runtime/python/okstra_ctl/contract_graph_cli.py +7 -0
  159. package/runtime/python/okstra_ctl/convergence.py +107 -16
  160. package/runtime/python/okstra_ctl/convergence_critic_prompt.py +345 -0
  161. package/runtime/python/okstra_ctl/convergence_engine.py +382 -70
  162. package/runtime/python/okstra_ctl/convergence_store.py +5 -29
  163. package/runtime/python/okstra_ctl/design_prep.py +108 -12
  164. package/runtime/python/okstra_ctl/design_snapshot.py +11 -1
  165. package/runtime/python/okstra_ctl/design_surfaces.py +29 -2
  166. package/runtime/python/okstra_ctl/dispatch_core.py +236 -49
  167. package/runtime/python/okstra_ctl/dispatch_state.py +417 -41
  168. package/runtime/python/okstra_ctl/doctor_cli.py +48 -0
  169. package/runtime/python/okstra_ctl/domain/worker_exec.py +10 -5
  170. package/runtime/python/okstra_ctl/domain/worker_presentation.py +28 -1
  171. package/runtime/python/okstra_ctl/domain/worker_stream.py +52 -21
  172. package/runtime/python/okstra_ctl/domain/write_policy.py +219 -0
  173. package/runtime/python/okstra_ctl/entrypoints/hosts.py +9 -2
  174. package/runtime/python/okstra_ctl/error_log_core.py +1 -1
  175. package/runtime/python/okstra_ctl/error_report.py +14 -0
  176. package/runtime/python/okstra_ctl/error_zip.py +18 -2
  177. package/runtime/python/okstra_ctl/exact_coverage.py +5 -0
  178. package/runtime/python/okstra_ctl/execution_identity.py +100 -7
  179. package/runtime/python/okstra_ctl/execution_manifest.py +67 -20
  180. package/runtime/python/okstra_ctl/execution_mutation_audit.py +90 -8
  181. package/runtime/python/okstra_ctl/final_report_paths.py +17 -0
  182. package/runtime/python/okstra_ctl/final_report_schema.py +319 -17
  183. package/runtime/python/okstra_ctl/git_reconcile.py +22 -2
  184. package/runtime/python/okstra_ctl/handoff.py +24 -1
  185. package/runtime/python/okstra_ctl/ids.py +6 -16
  186. package/runtime/python/okstra_ctl/implementation_direction.py +142 -33
  187. package/runtime/python/okstra_ctl/implementation_options.py +32 -13
  188. package/runtime/python/okstra_ctl/implementation_outcome.py +5 -2
  189. package/runtime/python/okstra_ctl/improvement_lenses.py +0 -14
  190. package/runtime/python/okstra_ctl/incremental_carry.py +69 -8
  191. package/runtime/python/okstra_ctl/incremental_scope.py +173 -12
  192. package/runtime/python/okstra_ctl/index.py +2 -2
  193. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +48 -8
  194. package/runtime/python/okstra_ctl/interactive_cli.py +223 -0
  195. package/runtime/python/okstra_ctl/json_boundary.py +10 -0
  196. package/runtime/python/okstra_ctl/listing.py +0 -91
  197. package/runtime/python/okstra_ctl/locks.py +5 -19
  198. package/runtime/python/okstra_ctl/log_report.py +14 -0
  199. package/runtime/python/okstra_ctl/manager_cli.py +22 -3
  200. package/runtime/python/okstra_ctl/manager_launch.py +4 -8
  201. package/runtime/python/okstra_ctl/material.py +2 -2
  202. package/runtime/python/okstra_ctl/migrate.py +23 -1
  203. package/runtime/python/okstra_ctl/model_cli.py +19 -5
  204. package/runtime/python/okstra_ctl/model_discovery.py +46 -47
  205. package/runtime/python/okstra_ctl/model_io/__init__.py +1 -0
  206. package/runtime/python/okstra_ctl/model_io/lines.py +163 -0
  207. package/runtime/python/okstra_ctl/model_io/references.py +370 -0
  208. package/runtime/python/okstra_ctl/model_io/renderers.py +498 -0
  209. package/runtime/python/okstra_ctl/model_io_cli.py +30 -945
  210. package/runtime/python/okstra_ctl/model_pool.py +10 -6
  211. package/runtime/python/okstra_ctl/models.py +8 -8
  212. package/runtime/python/okstra_ctl/mutation_recovery.py +5 -63
  213. package/runtime/python/okstra_ctl/next_phase.py +121 -69
  214. package/runtime/python/okstra_ctl/pane_reclaim.py +23 -0
  215. package/runtime/python/okstra_ctl/pane_title.py +11 -8
  216. package/runtime/python/okstra_ctl/path_hints.py +36 -11
  217. package/runtime/python/okstra_ctl/paths.py +76 -8
  218. package/runtime/python/okstra_ctl/plan_items.py +132 -16
  219. package/runtime/python/okstra_ctl/plan_items_cli.py +486 -36
  220. package/runtime/python/okstra_ctl/plan_run_root.py +1 -1
  221. package/runtime/python/okstra_ctl/plan_validate_cli.py +51 -0
  222. package/runtime/python/okstra_ctl/plan_verify_cli.py +80 -0
  223. package/runtime/python/okstra_ctl/prepare_error.py +20 -0
  224. package/runtime/python/okstra_ctl/prior_planning.py +157 -0
  225. package/runtime/python/okstra_ctl/profile_show.py +18 -1
  226. package/runtime/python/okstra_ctl/project_setup_cli.py +140 -0
  227. package/runtime/python/okstra_ctl/recap.py +22 -1
  228. package/runtime/python/okstra_ctl/reconcile.py +84 -0
  229. package/runtime/python/okstra_ctl/registry/host_registry.py +18 -1
  230. package/runtime/python/okstra_ctl/render.py +98 -32
  231. package/runtime/python/okstra_ctl/render_final_report.py +4 -8
  232. package/runtime/python/okstra_ctl/report_assembly.py +297 -7
  233. package/runtime/python/okstra_ctl/report_contract.py +30 -7
  234. package/runtime/python/okstra_ctl/report_finalize.py +213 -16
  235. package/runtime/python/okstra_ctl/report_html/common.py +2 -2
  236. package/runtime/python/okstra_ctl/report_html/render.py +8 -12
  237. package/runtime/python/okstra_ctl/report_language.py +8 -4
  238. package/runtime/python/okstra_ctl/report_narrative.py +220 -34
  239. package/runtime/python/okstra_ctl/report_projections.py +8 -2
  240. package/runtime/python/okstra_ctl/report_synthesis_packet.py +393 -1
  241. package/runtime/python/okstra_ctl/report_translation.py +2 -35
  242. package/runtime/python/okstra_ctl/report_view_artifacts.py +42 -2
  243. package/runtime/python/okstra_ctl/report_views.py +4 -22
  244. package/runtime/python/okstra_ctl/resolve_task_key.py +13 -0
  245. package/runtime/python/okstra_ctl/rollup.py +13 -0
  246. package/runtime/python/okstra_ctl/run.py +639 -138
  247. package/runtime/python/okstra_ctl/run_audit.py +13 -0
  248. package/runtime/python/okstra_ctl/run_index_row.py +0 -12
  249. package/runtime/python/okstra_ctl/seeding.py +21 -11
  250. package/runtime/python/okstra_ctl/sequence.py +1 -1
  251. package/runtime/python/okstra_ctl/session.py +64 -14
  252. package/runtime/python/okstra_ctl/set_work_status.py +18 -0
  253. package/runtime/python/okstra_ctl/stage_integrate.py +15 -1
  254. package/runtime/python/okstra_ctl/stage_ledger.py +1 -1
  255. package/runtime/python/okstra_ctl/stage_map.py +57 -11
  256. package/runtime/python/okstra_ctl/stage_map_cli.py +83 -0
  257. package/runtime/python/okstra_ctl/stage_map_view.py +65 -0
  258. package/runtime/python/okstra_ctl/stage_targets.py +3 -12
  259. package/runtime/python/okstra_ctl/task_list_cli.py +138 -0
  260. package/runtime/python/okstra_ctl/task_show_cli.py +66 -0
  261. package/runtime/python/okstra_ctl/task_target.py +4 -16
  262. package/runtime/python/okstra_ctl/team.py +49 -2
  263. package/runtime/python/okstra_ctl/time_report.py +17 -2
  264. package/runtime/python/okstra_ctl/usage_report.py +11 -0
  265. package/runtime/python/okstra_ctl/user_response.py +55 -218
  266. package/runtime/python/okstra_ctl/user_response_values.py +242 -0
  267. package/runtime/python/okstra_ctl/validation_contract.py +3 -0
  268. package/runtime/python/okstra_ctl/verdict_blocks.py +28 -8
  269. package/runtime/python/okstra_ctl/verification_target.py +68 -0
  270. package/runtime/python/okstra_ctl/wizard.py +457 -92
  271. package/runtime/python/okstra_ctl/worker_artifacts.py +75 -16
  272. package/runtime/python/okstra_ctl/worker_audit_check.py +22 -0
  273. package/runtime/python/okstra_ctl/worker_dispatch.py +21 -0
  274. package/runtime/python/okstra_ctl/worker_liveness.py +33 -0
  275. package/runtime/python/okstra_ctl/worker_prompt_body.py +15 -2
  276. package/runtime/python/okstra_ctl/worker_prompt_contract.py +98 -7
  277. package/runtime/python/okstra_ctl/worker_prompt_headers.py +24 -2
  278. package/runtime/python/okstra_ctl/worker_prompt_policy.py +18 -3
  279. package/runtime/python/okstra_ctl/worker_request.py +2 -1
  280. package/runtime/python/okstra_ctl/worker_runner.py +58 -17
  281. package/runtime/python/okstra_ctl/worker_state.py +16 -1
  282. package/runtime/python/okstra_ctl/workflow.py +18 -1
  283. package/runtime/python/okstra_ctl/worktree/__init__.py +92 -0
  284. package/runtime/python/okstra_ctl/worktree/cleanliness.py +89 -0
  285. package/runtime/python/okstra_ctl/worktree/decisions.py +127 -0
  286. package/runtime/python/okstra_ctl/worktree/git_ops.py +165 -0
  287. package/runtime/python/okstra_ctl/worktree/linking.py +249 -0
  288. package/runtime/python/okstra_ctl/worktree/naming.py +91 -0
  289. package/runtime/python/okstra_ctl/worktree/provision.py +385 -0
  290. package/runtime/python/okstra_ctl/worktree/sync_config.py +181 -0
  291. package/runtime/python/okstra_ctl/worktree_cli.py +75 -0
  292. package/runtime/python/okstra_ctl/worktree_lookup_cli.py +39 -0
  293. package/runtime/python/okstra_ctl/worktree_registry.py +7 -0
  294. package/runtime/python/okstra_ctl/worktree_status_cli.py +53 -0
  295. package/runtime/python/okstra_ctl/write_policy.py +90 -213
  296. package/runtime/python/okstra_project/__init__.py +0 -4
  297. package/runtime/python/okstra_project/dirs.py +2 -2
  298. package/runtime/python/okstra_project/phase_pointer.py +84 -0
  299. package/runtime/python/okstra_project/slug.py +24 -0
  300. package/runtime/python/okstra_project/state.py +22 -230
  301. package/runtime/python/okstra_token_usage/antigravity.py +50 -12
  302. package/runtime/python/okstra_token_usage/claude.py +9 -1
  303. package/runtime/python/okstra_token_usage/cli.py +3 -1
  304. package/runtime/python/okstra_token_usage/collect.py +171 -36
  305. package/runtime/python/okstra_token_usage/cursor.py +2 -0
  306. package/runtime/python/okstra_token_usage/grok.py +1 -5
  307. package/runtime/python/okstra_token_usage/paths.py +26 -0
  308. package/runtime/python/okstra_token_usage/pricing.py +23 -8
  309. package/runtime/schemas/convergence-critic-results-v1.0.schema.json +5 -0
  310. package/runtime/schemas/execution-manifest-v2.schema.json +10 -10
  311. package/runtime/schemas/final-report-v2.0.schema.json +9 -26
  312. package/runtime/schemas/final-report-v3.0.schema.json +173 -41
  313. package/runtime/schemas/report-narrative-v3.0.schema.json +3 -2
  314. package/runtime/skills/okstra-brief-gen/SKILL.md +35 -2
  315. package/runtime/skills/okstra-container-build/SKILL.md +16 -47
  316. package/runtime/skills/okstra-inspect/facets/history.md +1 -1
  317. package/runtime/skills/okstra-inspect/facets/status.md +7 -6
  318. package/runtime/skills/okstra-pr-gen/SKILL.md +1 -1
  319. package/runtime/skills/okstra-run/SKILL.md +7 -5
  320. package/runtime/templates/report-writer-prompt-preamble.md +3 -1
  321. package/runtime/templates/reports/brief.template.md +6 -2
  322. package/runtime/templates/reports/error-analysis-input.template.md +2 -0
  323. package/runtime/templates/reports/final-verification-input.template.md +2 -0
  324. package/runtime/templates/reports/implementation-input.template.md +7 -1
  325. package/runtime/templates/reports/implementation-planning-input.template.md +4 -0
  326. package/runtime/templates/reports/quick-input.template.md +2 -0
  327. package/runtime/templates/reports/release-handoff-input.template.md +6 -1
  328. package/runtime/templates/reports/report.js +20 -3
  329. package/runtime/templates/reports/schedule.template.md +2 -0
  330. package/runtime/templates/reports/settings.template.json +0 -10
  331. package/runtime/templates/reports/task-brief.template.md +3 -0
  332. package/runtime/templates/reports/user-response.template.md +7 -3
  333. package/runtime/validators/checks/fixtures-01.py +57 -0
  334. package/runtime/validators/checks/fixtures-02.py +565 -0
  335. package/runtime/validators/checks/runners-01.py +108 -0
  336. package/runtime/validators/checks/validate-assets-01.py +60 -0
  337. package/runtime/validators/checks/validate-prompt-metadata-01.py +261 -0
  338. package/runtime/validators/checks/validate-tasks-01.py +46 -0
  339. package/runtime/validators/checks/validate-tasks-02.py +85 -0
  340. package/runtime/validators/checks/validate-tasks-03.py +61 -0
  341. package/runtime/validators/checks/validate-tasks-04.py +118 -0
  342. package/runtime/validators/forbidden_actions.py +73 -3
  343. package/runtime/validators/lib/common.sh +5 -0
  344. package/runtime/validators/lib/fixtures.sh +7 -591
  345. package/runtime/validators/lib/paths.sh +13 -0
  346. package/runtime/validators/lib/runners.sh +6 -104
  347. package/runtime/validators/lib/summary.sh +1 -1
  348. package/runtime/validators/lib/validate-assets.sh +6 -56
  349. package/runtime/validators/lib/validate-prompt-metadata.sh +6 -257
  350. package/runtime/validators/lib/validate-tasks.sh +9 -294
  351. package/runtime/validators/validate-implementation-plan-stages.py +4 -4
  352. package/runtime/validators/validate-run.py +1363 -2643
  353. package/runtime/validators/validate-workflow.sh +56 -16
  354. package/runtime/validators/validate_improvement_report.py +2 -1
  355. package/runtime/validators/validate_session_conformance.py +295 -49
  356. package/dist/commands/execute/agent-prompt.d.mts +0 -1
  357. package/dist/commands/execute/agent-prompt.mjs +0 -24
  358. package/dist/commands/execute/agent-prompt.mjs.map +0 -1
  359. package/dist/commands/execute/codex-dispatch.d.mts +0 -3
  360. package/dist/commands/execute/codex-dispatch.mjs +0 -6
  361. package/dist/commands/execute/codex-dispatch.mjs.map +0 -1
  362. package/dist/commands/execute/codex-run.d.mts +0 -3
  363. package/dist/commands/execute/codex-run.mjs +0 -62
  364. package/dist/commands/execute/codex-run.mjs.map +0 -1
  365. package/dist/commands/execute/convergence.d.mts +0 -1
  366. package/dist/commands/execute/convergence.mjs +0 -38
  367. package/dist/commands/execute/convergence.mjs.map +0 -1
  368. package/dist/commands/execute/error-log.d.mts +0 -1
  369. package/dist/commands/execute/error-log.mjs +0 -18
  370. package/dist/commands/execute/error-log.mjs.map +0 -1
  371. package/dist/commands/execute/git-reconcile.d.mts +0 -1
  372. package/dist/commands/execute/git-reconcile.mjs +0 -30
  373. package/dist/commands/execute/git-reconcile.mjs.map +0 -1
  374. package/dist/commands/execute/handoff.d.mts +0 -1
  375. package/dist/commands/execute/handoff.mjs +0 -31
  376. package/dist/commands/execute/handoff.mjs.map +0 -1
  377. package/dist/commands/execute/incremental-carry.d.mts +0 -1
  378. package/dist/commands/execute/incremental-carry.mjs +0 -20
  379. package/dist/commands/execute/incremental-carry.mjs.map +0 -1
  380. package/dist/commands/execute/incremental-scope.d.mts +0 -1
  381. package/dist/commands/execute/incremental-scope.mjs +0 -29
  382. package/dist/commands/execute/incremental-scope.mjs.map +0 -1
  383. package/dist/commands/execute/integrate-stages.d.mts +0 -1
  384. package/dist/commands/execute/integrate-stages.mjs +0 -24
  385. package/dist/commands/execute/integrate-stages.mjs.map +0 -1
  386. package/dist/commands/execute/pane-title.d.mts +0 -1
  387. package/dist/commands/execute/pane-title.mjs +0 -20
  388. package/dist/commands/execute/pane-title.mjs.map +0 -1
  389. package/dist/commands/execute/plan-items.d.mts +0 -1
  390. package/dist/commands/execute/plan-items.mjs +0 -9
  391. package/dist/commands/execute/plan-items.mjs.map +0 -1
  392. package/dist/commands/execute/plan-validate.d.mts +0 -1
  393. package/dist/commands/execute/plan-validate.mjs +0 -68
  394. package/dist/commands/execute/plan-validate.mjs.map +0 -1
  395. package/dist/commands/execute/plan-verify.d.mts +0 -1
  396. package/dist/commands/execute/plan-verify.mjs +0 -43
  397. package/dist/commands/execute/plan-verify.mjs.map +0 -1
  398. package/dist/commands/execute/spawn-followups.d.mts +0 -1
  399. package/dist/commands/execute/spawn-followups.mjs +0 -22
  400. package/dist/commands/execute/spawn-followups.mjs.map +0 -1
  401. package/dist/commands/execute/team.d.mts +0 -3
  402. package/dist/commands/execute/team.mjs +0 -66
  403. package/dist/commands/execute/team.mjs.map +0 -1
  404. package/dist/commands/execute/token-usage.d.mts +0 -1
  405. package/dist/commands/execute/token-usage.mjs +0 -19
  406. package/dist/commands/execute/token-usage.mjs.map +0 -1
  407. package/dist/commands/execute/worker-audit-check.d.mts +0 -1
  408. package/dist/commands/execute/worker-audit-check.mjs +0 -34
  409. package/dist/commands/execute/worker-audit-check.mjs.map +0 -1
  410. package/dist/commands/execute/worker-dispatch.d.mts +0 -7
  411. package/dist/commands/execute/worker-dispatch.mjs +0 -64
  412. package/dist/commands/execute/worker-dispatch.mjs.map +0 -1
  413. package/dist/commands/execute/worker-state.d.mts +0 -1
  414. package/dist/commands/execute/worker-state.mjs +0 -28
  415. package/dist/commands/execute/worker-state.mjs.map +0 -1
  416. package/dist/commands/execute/worktree-lookup.d.mts +0 -1
  417. package/dist/commands/execute/worktree-lookup.mjs +0 -92
  418. package/dist/commands/execute/worktree-lookup.mjs.map +0 -1
  419. package/dist/commands/execute/worktree-status.d.mts +0 -1
  420. package/dist/commands/execute/worktree-status.mjs +0 -121
  421. package/dist/commands/execute/worktree-status.mjs.map +0 -1
  422. package/dist/commands/inspect/code-review.d.mts +0 -1
  423. package/dist/commands/inspect/code-review.mjs +0 -32
  424. package/dist/commands/inspect/code-review.mjs.map +0 -1
  425. package/dist/commands/inspect/container.d.mts +0 -1
  426. package/dist/commands/inspect/container.mjs +0 -25
  427. package/dist/commands/inspect/container.mjs.map +0 -1
  428. package/dist/commands/inspect/context-cost.d.mts +0 -1
  429. package/dist/commands/inspect/context-cost.mjs +0 -25
  430. package/dist/commands/inspect/context-cost.mjs.map +0 -1
  431. package/dist/commands/inspect/design-prep.d.mts +0 -1
  432. package/dist/commands/inspect/design-prep.mjs +0 -22
  433. package/dist/commands/inspect/design-prep.mjs.map +0 -1
  434. package/dist/commands/inspect/error-report.d.mts +0 -1
  435. package/dist/commands/inspect/error-report.mjs +0 -25
  436. package/dist/commands/inspect/error-report.mjs.map +0 -1
  437. package/dist/commands/inspect/error-zip.d.mts +0 -1
  438. package/dist/commands/inspect/error-zip.mjs +0 -24
  439. package/dist/commands/inspect/error-zip.mjs.map +0 -1
  440. package/dist/commands/inspect/log-report.d.mts +0 -1
  441. package/dist/commands/inspect/log-report.mjs +0 -26
  442. package/dist/commands/inspect/log-report.mjs.map +0 -1
  443. package/dist/commands/inspect/model-io.d.mts +0 -1
  444. package/dist/commands/inspect/model-io.mjs +0 -25
  445. package/dist/commands/inspect/model-io.mjs.map +0 -1
  446. package/dist/commands/inspect/profile-show.d.mts +0 -1
  447. package/dist/commands/inspect/profile-show.mjs +0 -28
  448. package/dist/commands/inspect/profile-show.mjs.map +0 -1
  449. package/dist/commands/inspect/recap.d.mts +0 -1
  450. package/dist/commands/inspect/recap.mjs +0 -30
  451. package/dist/commands/inspect/recap.mjs.map +0 -1
  452. package/dist/commands/inspect/resolve-task-key.d.mts +0 -1
  453. package/dist/commands/inspect/resolve-task-key.mjs +0 -24
  454. package/dist/commands/inspect/resolve-task-key.mjs.map +0 -1
  455. package/dist/commands/inspect/rollup.d.mts +0 -1
  456. package/dist/commands/inspect/rollup.mjs +0 -25
  457. package/dist/commands/inspect/rollup.mjs.map +0 -1
  458. package/dist/commands/inspect/run-audit.d.mts +0 -1
  459. package/dist/commands/inspect/run-audit.mjs +0 -25
  460. package/dist/commands/inspect/run-audit.mjs.map +0 -1
  461. package/dist/commands/inspect/set-work-status.d.mts +0 -1
  462. package/dist/commands/inspect/set-work-status.mjs +0 -29
  463. package/dist/commands/inspect/set-work-status.mjs.map +0 -1
  464. package/dist/commands/inspect/stage-map.d.mts +0 -1
  465. package/dist/commands/inspect/stage-map.mjs +0 -131
  466. package/dist/commands/inspect/stage-map.mjs.map +0 -1
  467. package/dist/commands/inspect/task-list.d.mts +0 -1
  468. package/dist/commands/inspect/task-list.mjs +0 -149
  469. package/dist/commands/inspect/task-list.mjs.map +0 -1
  470. package/dist/commands/inspect/task-show.d.mts +0 -1
  471. package/dist/commands/inspect/task-show.mjs +0 -108
  472. package/dist/commands/inspect/task-show.mjs.map +0 -1
  473. package/dist/commands/inspect/time-report.d.mts +0 -1
  474. package/dist/commands/inspect/time-report.mjs +0 -24
  475. package/dist/commands/inspect/time-report.mjs.map +0 -1
  476. package/dist/commands/inspect/usage-report.d.mts +0 -1
  477. package/dist/commands/inspect/usage-report.mjs +0 -23
  478. package/dist/commands/inspect/usage-report.mjs.map +0 -1
  479. package/dist/commands/inspect/user-response.d.mts +0 -1
  480. package/dist/commands/inspect/user-response.mjs +0 -35
  481. package/dist/commands/inspect/user-response.mjs.map +0 -1
  482. package/dist/commands/inspect/worker-liveness.d.mts +0 -1
  483. package/dist/commands/inspect/worker-liveness.mjs +0 -45
  484. package/dist/commands/inspect/worker-liveness.mjs.map +0 -1
  485. package/dist/commands/lifecycle/contract-check.d.mts +0 -1
  486. package/dist/commands/lifecycle/contract-check.mjs +0 -18
  487. package/dist/commands/lifecycle/contract-check.mjs.map +0 -1
  488. package/dist/commands/lifecycle/migrate.d.mts +0 -1
  489. package/dist/commands/lifecycle/migrate.mjs +0 -30
  490. package/dist/commands/lifecycle/migrate.mjs.map +0 -1
  491. package/dist/commands/lifecycle/model.d.mts +0 -1
  492. package/dist/commands/lifecycle/model.mjs +0 -22
  493. package/dist/commands/lifecycle/model.mjs.map +0 -1
  494. package/dist/commands/manager.d.mts +0 -3
  495. package/dist/commands/manager.mjs +0 -50
  496. package/dist/commands/manager.mjs.map +0 -1
  497. package/dist/commands/report/agent-activity.d.mts +0 -1
  498. package/dist/commands/report/agent-activity.mjs +0 -20
  499. package/dist/commands/report/agent-activity.mjs.map +0 -1
  500. package/dist/commands/report/approval-decision.d.mts +0 -1
  501. package/dist/commands/report/approval-decision.mjs +0 -21
  502. package/dist/commands/report/approval-decision.mjs.map +0 -1
  503. package/dist/commands/report/design-snapshot.d.mts +0 -1
  504. package/dist/commands/report/design-snapshot.mjs +0 -19
  505. package/dist/commands/report/design-snapshot.mjs.map +0 -1
  506. package/dist/commands/report/finalize.d.mts +0 -4
  507. package/dist/commands/report/finalize.mjs +0 -64
  508. package/dist/commands/report/finalize.mjs.map +0 -1
  509. package/dist/commands/report/inject-report-index.d.mts +0 -1
  510. package/dist/commands/report/inject-report-index.mjs +0 -21
  511. package/dist/commands/report/inject-report-index.mjs.map +0 -1
  512. package/dist/commands/report/render-final-report.d.mts +0 -1
  513. package/dist/commands/report/render-final-report.mjs +0 -23
  514. package/dist/commands/report/render-final-report.mjs.map +0 -1
  515. package/dist/commands/report/render-views.d.mts +0 -1
  516. package/dist/commands/report/render-views.mjs +0 -27
  517. package/dist/commands/report/render-views.mjs.map +0 -1
  518. package/dist/commands/report/translate.d.mts +0 -1
  519. package/dist/commands/report/translate.mjs +0 -33
  520. package/dist/commands/report/translate.mjs.map +0 -1
  521. package/runtime/bin/lib/okstra/tmux-pane.sh +0 -40
  522. package/runtime/bin/lib/okstra-ctl/cmd-batch.sh +0 -59
  523. package/runtime/bin/lib/okstra-ctl/cmd-list.sh +0 -35
  524. package/runtime/bin/lib/okstra-ctl/cmd-open.sh +0 -36
  525. package/runtime/bin/lib/okstra-ctl/cmd-projects.sh +0 -26
  526. package/runtime/bin/lib/okstra-ctl/cmd-reconcile.sh +0 -29
  527. package/runtime/bin/lib/okstra-ctl/cmd-reindex.sh +0 -38
  528. package/runtime/bin/lib/okstra-ctl/cmd-rerun.sh +0 -345
  529. package/runtime/bin/lib/okstra-ctl/cmd-show.sh +0 -27
  530. package/runtime/bin/lib/okstra-ctl/cmd-tail.sh +0 -92
  531. package/runtime/bin/lib/okstra-ctl/main.sh +0 -41
  532. package/runtime/bin/lib/okstra-ctl/prepare.sh +0 -31
  533. package/runtime/bin/lib/okstra-ctl/usage.sh +0 -23
  534. package/runtime/bin/okstra-central.sh +0 -152
  535. package/runtime/bin/okstra-incremental-carry.py +0 -10
  536. package/runtime/bin/okstra-incremental-scope.py +0 -10
  537. package/runtime/bin/okstra-team-reconcile.sh +0 -36
  538. package/runtime/python/okstra_ctl/agent_prompt_cli.py +0 -1156
  539. package/runtime/python/okstra_ctl/batch.py +0 -60
  540. package/runtime/python/okstra_ctl/clarification_items.py +0 -1050
  541. package/runtime/python/okstra_ctl/container_registry.py +0 -72
  542. package/runtime/python/okstra_ctl/improvement_assignment.py +0 -61
  543. package/runtime/python/okstra_ctl/resolver.py +0 -54
  544. package/runtime/python/okstra_ctl/team_reconcile.py +0 -275
  545. package/runtime/python/okstra_ctl/tmux.py +0 -134
  546. package/runtime/python/okstra_ctl/worktree.py +0 -1099
@@ -0,0 +1,48 @@
1
+ """`okstra doctor` 가 부르는 진단 프로젝션.
2
+
3
+ 진단 로직 자체는 `okstra_ctl.doctor` 에 있다. 이 모듈은 그것을 CLI 경계로
4
+ 노출하기만 한다 — 원래 `src/commands/lifecycle/doctor.mts` 의 문자열 리터럴
5
+ 안에 있던 두 스니펫을 옮긴 것이다(docs/coding-rules.md R1 위반).
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+ import json
11
+
12
+ from .doctor import model_pool_diagnostics, phase_diagnostics
13
+
14
+
15
+ def main(argv: list[str] | None = None) -> int:
16
+ parser = argparse.ArgumentParser(prog="okstra doctor")
17
+ sub = parser.add_subparsers(dest="op", required=True)
18
+
19
+ pool = sub.add_parser("model-pool")
20
+ pool.add_argument("--host-runtime", default="claude-code")
21
+ pool.add_argument("--cwd", required=True)
22
+
23
+ phase = sub.add_parser("phase")
24
+ phase.add_argument("--phase", required=True)
25
+ phase.add_argument("--cwd", required=True)
26
+ phase.add_argument("--workspace-root", required=True)
27
+ # `--home` 은 사용자 홈이다. 에이전트 설치 루트가 `<home>/.claude/agents` 라서
28
+ # okstra 홈(~/.okstra)을 넘기면 모든 worker-agent 점검이 없는 경로를 짚는다.
29
+ phase.add_argument("--home", required=True)
30
+ phase.add_argument("--host-runtime", default="")
31
+
32
+ args = parser.parse_args(argv)
33
+ if args.op == "model-pool":
34
+ payload = model_pool_diagnostics(host_runtime=args.host_runtime, cwd=args.cwd)
35
+ else:
36
+ payload = phase_diagnostics(
37
+ args.phase,
38
+ cwd=args.cwd,
39
+ workspace_root=args.workspace_root,
40
+ home=args.home,
41
+ host_runtime=args.host_runtime,
42
+ )
43
+ print(json.dumps(payload, ensure_ascii=False))
44
+ return 0
45
+
46
+
47
+ if __name__ == "__main__":
48
+ raise SystemExit(main())
@@ -12,9 +12,12 @@ from pathlib import Path
12
12
  from typing import Literal, Protocol, runtime_checkable
13
13
 
14
14
  from .worker_presentation import Presentation
15
- from ..write_policy import WriteEnforcement, WritePolicy
15
+ from .write_policy import WriteEnforcement, WritePolicy
16
16
 
17
17
  SERVED_MODEL_MISMATCH_EXIT_CODE = 78
18
+ # JSON 이벤트 스트림이 result 이벤트 없이 깨끗하게(exit 0) 끝난 run.
19
+ # CLI 가 성공을 주장했지만 답을 내지 않았다 — sysexits 의 EX_PROTOCOL.
20
+ NO_RESULT_EXIT_CODE = 76
18
21
 
19
22
 
20
23
  @dataclass(frozen=True)
@@ -22,10 +25,12 @@ class ExecutionPolicy:
22
25
  """How a non-interactive worker is allowed to act.
23
26
 
24
27
  Identical for every worker: nobody is at the keyboard to answer an approval
25
- prompt, so the gate has to be open and the boundary has to come from a
26
- sandbox instead. Declared once here rather than per provider, because a
27
- policy that lives in one adapter file per provider drifts into one policy
28
- per provider.
28
+ prompt, so the gate has to be open. No provider CLI enforces a sandbox
29
+ boundary any more, so `write_scope` is what the worker is told to write, not
30
+ what it is prevented from leaving — `write_policy` and `write_enforcement`
31
+ are what the run checks after the fact. Declared once here rather than per
32
+ provider, because a policy that lives in one adapter file per provider
33
+ drifts into one policy per provider.
29
34
  """
30
35
 
31
36
  auto_approve: bool
@@ -30,6 +30,8 @@ Sink = Callable[[str], str | None]
30
30
  Channel = Literal["stdout", "stderr"]
31
31
  SinkSpec = tuple[Channel, Sink]
32
32
  ObserveServedModel = Callable[[Mapping[str, Any]], str | None]
33
+ # 이벤트가 실어 온 토큰 사용량 스냅샷(공급자 어휘 그대로). 없으면 None.
34
+ ObserveUsage = Callable[[Mapping[str, Any]], Mapping[str, Any] | None]
33
35
 
34
36
  WORKER = "worker"
35
37
 
@@ -113,15 +115,33 @@ class JsonEvents:
113
115
 
114
116
  normalise: Normalise
115
117
  observe: ObserveServedModel
118
+ # 어떤 CLI 는 토큰 사용량을 홈 디렉터리의 트랜스크립트가 아니라 이 스트림에만
119
+ # 싣는다(agy 의 `result.usage`). 어댑터가 그 스냅샷을 읽는 법을 넘기면
120
+ # 러너가 마지막 값을 status 사이드카에 적어, 수집기가 로그를 다시 파싱하지
121
+ # 않아도 된다. 어느 CLI 가 그런지는 이 계층이 알 필요도, 알아서도 안 된다.
122
+ observe_usage: ObserveUsage | None = None
116
123
  # 어떤 CLI 의 streaming-json 은 문장이 아니라 토큰을 한 줄씩 보낸다. 붙이지
117
124
  # 않으면 pane 이 한 글자 한 줄이 된다. 기본은 끄고, 그런 CLI 의 어댑터가 켠다 —
118
125
  # 어느 CLI 가 그런지는 이 계층이 알 필요도, 알아서도 안 되는 사실이다.
119
126
  join_adjacent_text: bool = False
127
+ # 이 어휘에서 "정상 종결" 을 뜻하는 원시 이벤트. 기본은 Result 이벤트지만,
128
+ # 어떤 CLI 의 어휘에는 Result 가 없고 별도의 종결 이벤트가 있다 — 어댑터가
129
+ # 자기 어휘의 표지를 넘긴다. 어느 CLI 가 그런지는 이 계층이 알 필요도,
130
+ # 알아서도 안 되는 사실이다. 판정 없이는 그런 CLI 의 건강한 run 전부가
131
+ # 결과 부재로 강등된다.
132
+ is_conclusion: Callable[[Mapping[str, Any]], bool] | None = None
120
133
  _pending_text: list[str] = field(default_factory=list, repr=False, compare=False)
134
+ _saw_conclusion: list[bool] = field(
135
+ default_factory=list, repr=False, compare=False
136
+ )
121
137
 
122
138
  def merges_stderr(self) -> bool:
123
139
  return True
124
140
 
141
+ def concluded(self) -> bool:
142
+ """스트림이 이 어휘의 종결 표지로 끝났는가."""
143
+ return bool(self._saw_conclusion)
144
+
125
145
  def sinks(self, writer: TranscriptWriter) -> tuple[SinkSpec, ...]:
126
146
  def emit(line: str) -> str | None:
127
147
  stripped = line.strip()
@@ -138,6 +158,8 @@ class JsonEvents:
138
158
  if not isinstance(event, dict):
139
159
  return None
140
160
  self.observe(event)
161
+ if self.is_conclusion is not None and self.is_conclusion(event):
162
+ self._saw_conclusion.append(True)
141
163
  closing: str | None = None
142
164
  for entry in self.normalise(event):
143
165
  closing = self._write_entry(writer, entry) or closing
@@ -164,7 +186,12 @@ class JsonEvents:
164
186
  screen=format_live(entry),
165
187
  archive=format_log(entry),
166
188
  )
167
- return final_text(entry)
189
+ closing = final_text(entry)
190
+ if closing is not None:
191
+ # Result 이벤트는 어휘와 무관한 종결이다. 빈 결과 텍스트도 종결이므로
192
+ # closing_text 의 진릿값과 따로 기록한다.
193
+ self._saw_conclusion.append(True)
194
+ return closing
168
195
 
169
196
  def _publish_complete_lines(self, writer: TranscriptWriter) -> None:
170
197
  combined = "".join(self._pending_text)
@@ -12,9 +12,10 @@ to be worth a default here — ``content_block_events``, whose events are keyed
12
12
  provider, and an adapter that speaks something else says so in its own file.
13
13
 
14
14
  Screen output folds what the log keeps in full — a tool call becomes one line, a
15
- tool result becomes an outcome and a size. Thinking is dropped at normalisation,
16
- which is safe only because worker liveness is measured from stream arrival
17
- rather than from log writes (see the spec's 7.2).
15
+ tool result becomes an outcome, a size, and the first few rows of what came
16
+ back. Thinking is dropped at normalisation, which is safe only because worker
17
+ liveness is measured from stream arrival rather than from log writes (see the
18
+ spec's 7.2).
18
19
 
19
20
  Three projections of the same stream: ``format_live`` for the pane,
20
21
  ``format_log`` for the archive, and ``final_text`` for the caller that must
@@ -31,6 +32,14 @@ from typing import Any, Callable, Mapping
31
32
  # previous one off the top of a short pane.
32
33
  _MAX_SUMMARY = 120
33
34
 
35
+ # 화면이 받는 도구 결과 본문의 줄 수. 0 줄이던 동안 pane 에 남는 것은 성패와
36
+ # 바이트 수뿐이었다. 어떤 어댑터가 본문 대신 도구 이름표를 넘겨 모든 호출이
37
+ # 한 자릿수 바이트로 보이던 run 을, 화면만 보고는 아무도 판별하지 못했다.
38
+ # 결과 하나가 pane 을 다 차지하면 직전 호출이 위로 밀려 나가므로 앞부분만
39
+ # 보여주고, 나머지는 로그가 갖는다.
40
+ _LIVE_BODY_ROWS = 4
41
+ _BODY_INDENT = " "
42
+
34
43
 
35
44
  @dataclass(frozen=True)
36
45
  class Text:
@@ -58,11 +67,20 @@ class ToolResult:
58
67
  ``failed`` is None when the stream closed the tool step without reporting an
59
68
  outcome at all. Reading that absence as success would put a confident "ok"
60
69
  next to a command that failed.
70
+
71
+ ``body_describes_result`` marks a tool that reported *about* its result
72
+ instead of returning it — a file read that answers ``54 lines, 4293 bytes``
73
+ rather than the 4293 bytes. Without the mark the two are the same shape and
74
+ the size reads as the payload, so a 4293-byte read is archived as
75
+ ``← done (20 bytes)`` and a reader concludes the read came back nearly
76
+ empty. The provider that emits such a report says so; this layer only
77
+ renders it differently.
61
78
  """
62
79
 
63
80
  body: str
64
81
  size_bytes: int
65
82
  failed: bool | None = None
83
+ body_describes_result: bool = False
66
84
 
67
85
 
68
86
  @dataclass(frozen=True)
@@ -112,17 +130,6 @@ def host_event_row(
112
130
  return row
113
131
 
114
132
 
115
- def no_events(event: Mapping[str, Any]) -> tuple[StreamEvent, ...]:
116
- """The normaliser for a CLI that emits no events at all.
117
-
118
- A text CLI's output is already what a person reads, so the runner forwards
119
- it whole and never calls this. It exists so ``ExecCommand`` can default to
120
- a truthful "there is nothing here to normalise" instead of to some
121
- provider's schema.
122
- """
123
- return ()
124
-
125
-
126
133
  def content_block_events(event: Mapping[str, Any]) -> tuple[StreamEvent, ...]:
127
134
  """Normalise the stream shape keyed on ``type`` with ``message.content``.
128
135
 
@@ -157,7 +164,7 @@ def content_block_events(event: Mapping[str, Any]) -> tuple[StreamEvent, ...]:
157
164
 
158
165
 
159
166
  def format_live(event: StreamEvent) -> list[str]:
160
- return _rows(event, limit=_MAX_SUMMARY, include_body=False)
167
+ return _rows(event, limit=_MAX_SUMMARY, body_rows=_LIVE_BODY_ROWS)
161
168
 
162
169
 
163
170
  def format_log(event: StreamEvent) -> list[str]:
@@ -167,7 +174,7 @@ def format_log(event: StreamEvent) -> list[str]:
167
174
  the fact, by a person reconstructing why a worker failed and by
168
175
  ``okstra log-report``.
169
176
  """
170
- return _rows(event, limit=None, include_body=True)
177
+ return _rows(event, limit=None, body_rows=None)
171
178
 
172
179
 
173
180
  def final_text(event: StreamEvent) -> str | None:
@@ -175,17 +182,24 @@ def final_text(event: StreamEvent) -> str | None:
175
182
  return event.text if isinstance(event, Result) else None
176
183
 
177
184
 
178
- def _rows(event: StreamEvent, *, limit: int | None, include_body: bool) -> list[str]:
185
+ def _rows(event: StreamEvent, *, limit: int | None, body_rows: int | None) -> list[str]:
179
186
  if isinstance(event, Text):
180
187
  return _body_rows(event.body)
181
188
  if isinstance(event, ToolCall):
182
189
  head = f"→ {event.name}: {event.detail}" if event.detail else f"→ {event.name}"
183
190
  return [_truncate(head, limit)]
184
191
  if isinstance(event, ToolResult):
185
- rows = [f" ← {_outcome(event.failed)} ({event.size_bytes} bytes)"]
186
- if include_body:
187
- rows.extend(_body_rows(event.body))
188
- return rows
192
+ if event.body_describes_result:
193
+ # 크기를 싣지 않는다. 이 이벤트가 가진 숫자는 보고 문자열의 길이일
194
+ # 뿐이고(보고조차 없으면 0), 그것을 payload 자리에 놓는 것이 오독의
195
+ # 출처였다.
196
+ report = " ".join(event.body.split())
197
+ tail = (
198
+ f"the tool reported: {report}" if report else "the tool reported nothing"
199
+ )
200
+ return [_truncate(f" ← {_outcome(event.failed)} — no body returned; {tail}", limit)]
201
+ head = f" ← {_outcome(event.failed)} ({event.size_bytes} bytes)"
202
+ return [head, *_result_body(event.body, limit=limit, keep=body_rows)]
189
203
  if isinstance(event, Denial):
190
204
  return [_truncate(f"!! PERMISSION DENIED — {event.tool}: {event.reason}", limit)]
191
205
  # A Result is printed by the runner at the point the run ends, not woven
@@ -193,6 +207,23 @@ def _rows(event: StreamEvent, *, limit: int | None, include_body: bool) -> list[
193
207
  return []
194
208
 
195
209
 
210
+ def _result_body(body: str, *, limit: int | None, keep: int | None) -> list[str]:
211
+ """결과 본문을 몇 줄 보여줄지 정한다.
212
+
213
+ `keep is None` 이 기록의 몫이다 — 전부 남긴다. 화면은 앞 몇 줄만 받고,
214
+ 생략한 줄 수를 밝힌다. 밝히지 않으면 짧은 출력과 잘린 출력이 같은 모양이
215
+ 되어, 읽는 사람이 로그를 열 이유를 알 수 없다.
216
+ """
217
+ rows = _body_rows(body)
218
+ if keep is None:
219
+ return rows
220
+ shown = [_truncate(_BODY_INDENT + row, limit) for row in rows[:keep]]
221
+ dropped = len(rows) - keep
222
+ if dropped > 0:
223
+ shown.append(f"{_BODY_INDENT}… +{dropped} more line(s) — full body in the log")
224
+ return shown
225
+
226
+
196
227
  def _outcome(failed: bool | None) -> str:
197
228
  if failed is None:
198
229
  return "done"
@@ -0,0 +1,219 @@
1
+ """쓰기 정책 값 타입 · 직렬화 · 검증.
2
+
3
+ `..write_policy`(623줄) 안에 있던 값 코어를 도메인으로 내렸다. 그 모듈은
4
+ git 을 실행하고 run manifest 를 읽는 응용 로직인데, `domain/worker_exec.py` 가
5
+ `WritePolicy` / `WriteEnforcement` 타입 하나 때문에 그 응용 모듈을 최상위
6
+ import 하고 있었다 — 도메인이 응용을 부르는 역전이고, 그 탓에
7
+ `write_policy → path_hints` 가 함수 본문 지연 import 로 밀려나 있었다.
8
+
9
+ 값과 그 값의 정합성(직렬화·다이제스트·payload 검증)까지가 여기다.
10
+ 정책을 **만들거나**(build_*) **유도하는**(derive_*) 일은 응용에 남는다.
11
+ `..write_policy` 가 이 이름들을 재노출하므로 기존 호출부는 바뀌지 않는다.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import hashlib
16
+ import json
17
+ from collections.abc import Mapping, Sequence
18
+ from dataclasses import dataclass
19
+ from pathlib import Path, PurePosixPath
20
+ from typing import Any, Literal
21
+
22
+
23
+ BoundaryPrecision = Literal["exact-path", "directory-boundary", "none"]
24
+
25
+ _PRECISION_RANK = {"none": 0, "directory-boundary": 1, "exact-path": 2}
26
+
27
+ _POLICY_KEYS = {
28
+ "artifactPolicy",
29
+ "sourcePolicy",
30
+ "gitPolicy",
31
+ "auxiliaryPolicy",
32
+ }
33
+
34
+ _ENFORCEMENT_KEYS = {
35
+ "boundaryPrecision",
36
+ "mutationAudit",
37
+ "attribution",
38
+ "auditedRoots",
39
+ "observationCoverage",
40
+ "unobservedWriteSurfaces",
41
+ }
42
+
43
+ class WritePolicyError(ValueError):
44
+ """Raised when a write policy cannot be made canonical or trustworthy."""
45
+
46
+ @dataclass(frozen=True)
47
+ class WritePolicy:
48
+ artifact_policy: Mapping[str, Any]
49
+ source_policy: Mapping[str, Any]
50
+ git_policy: Mapping[str, Any]
51
+ auxiliary_policy: Mapping[str, Any]
52
+ maximum_boundary_precision: BoundaryPrecision = "none"
53
+
54
+ def to_payload(self) -> dict[str, Any]:
55
+ return {
56
+ "artifactPolicy": dict(self.artifact_policy),
57
+ "sourcePolicy": dict(self.source_policy),
58
+ "gitPolicy": dict(self.git_policy),
59
+ "auxiliaryPolicy": dict(self.auxiliary_policy),
60
+ }
61
+
62
+ @property
63
+ def digest(self) -> str:
64
+ return write_policy_digest(self.to_payload())
65
+
66
+ @property
67
+ def source_mode(self) -> str:
68
+ return str(self.source_policy["mode"])
69
+
70
+ @dataclass(frozen=True)
71
+ class WriteEnforcement:
72
+ boundary_precision: BoundaryPrecision
73
+ mutation_audit: Literal["none", "batch"]
74
+ attribution: Literal["call", "batch-unattributed"]
75
+ audited_roots: tuple[Path, ...]
76
+ observation_coverage: Literal["complete", "scoped", "partial"]
77
+ unobserved_write_surfaces: tuple[str, ...]
78
+
79
+ def to_payload(self) -> dict[str, Any]:
80
+ return {
81
+ "boundaryPrecision": self.boundary_precision,
82
+ "mutationAudit": self.mutation_audit,
83
+ "attribution": self.attribution,
84
+ "auditedRoots": [str(path) for path in self.audited_roots],
85
+ "observationCoverage": self.observation_coverage,
86
+ "unobservedWriteSurfaces": list(self.unobserved_write_surfaces),
87
+ }
88
+
89
+ def write_policy_from_payload(payload: Mapping[str, Any]) -> WritePolicy:
90
+ validate_write_policy_payload(payload)
91
+ return WritePolicy(
92
+ artifact_policy=dict(payload["artifactPolicy"]),
93
+ source_policy=dict(payload["sourcePolicy"]),
94
+ git_policy=dict(payload["gitPolicy"]),
95
+ auxiliary_policy=dict(payload["auxiliaryPolicy"]),
96
+ )
97
+
98
+ def write_enforcement_from_payload(
99
+ payload: Mapping[str, Any],
100
+ ) -> WriteEnforcement:
101
+ validate_write_enforcement_payload(payload)
102
+ return WriteEnforcement(
103
+ boundary_precision=payload["boundaryPrecision"],
104
+ mutation_audit=payload["mutationAudit"],
105
+ attribution=payload["attribution"],
106
+ audited_roots=tuple(Path(value) for value in payload["auditedRoots"]),
107
+ observation_coverage=payload["observationCoverage"],
108
+ unobserved_write_surfaces=tuple(payload["unobservedWriteSurfaces"]),
109
+ )
110
+
111
+ def write_policy_digest(payload: Mapping[str, Any]) -> str:
112
+ validate_write_policy_payload(payload)
113
+ encoded = json.dumps(
114
+ payload,
115
+ sort_keys=True,
116
+ separators=(",", ":"),
117
+ ensure_ascii=False,
118
+ ).encode("utf-8")
119
+ return "sha256:" + hashlib.sha256(encoded).hexdigest()
120
+
121
+ def validate_write_policy_payload(payload: Mapping[str, Any]) -> None:
122
+ if set(payload) != _POLICY_KEYS:
123
+ raise WritePolicyError("writePolicy must contain four canonical policy sections")
124
+ artifact = _mapping(payload.get("artifactPolicy"), "artifactPolicy")
125
+ source = _mapping(payload.get("sourcePolicy"), "sourcePolicy")
126
+ git = _mapping(payload.get("gitPolicy"), "gitPolicy")
127
+ auxiliary = _mapping(payload.get("auxiliaryPolicy"), "auxiliaryPolicy")
128
+ if set(artifact) != {"allowedRoot", "allowedPaths"}:
129
+ raise WritePolicyError("artifactPolicy is incomplete")
130
+ _absolute_text(artifact.get("allowedRoot"), "artifactPolicy.allowedRoot")
131
+ _relative_paths(artifact.get("allowedPaths", ()))
132
+ if source.get("mode") not in {"source-readonly", "project-mutation"}:
133
+ raise WritePolicyError("sourcePolicy mode is invalid")
134
+ if set(source) != {"mode", "allowedRoot", "plannedPaths", "protectedPaths"}:
135
+ raise WritePolicyError("sourcePolicy is incomplete")
136
+ _absolute_text(source.get("allowedRoot"), "sourcePolicy.allowedRoot")
137
+ _relative_paths(source.get("plannedPaths", ()))
138
+ _relative_paths(source.get("protectedPaths", ()))
139
+ if git.get("mode") not in {"disabled", "fast-forward-descendant-chain"}:
140
+ raise WritePolicyError("gitPolicy mode is invalid")
141
+ if git.get("mode") == "disabled" and set(git) != {"mode"}:
142
+ raise WritePolicyError("disabled gitPolicy must not grant Git paths")
143
+ required_git = {
144
+ "mode", "worktreeGitDir", "gitCommonDir", "expectedBaseCommit",
145
+ "allowedBranchRef", "commitMode",
146
+ }
147
+ if git.get("mode") != "disabled" and set(git) != required_git:
148
+ raise WritePolicyError("project mutation gitPolicy is incomplete")
149
+ if set(auxiliary) != {"scratchRoots", "generatedPaths", "externalRoots"}:
150
+ raise WritePolicyError("auxiliaryPolicy is incomplete")
151
+ _relative_paths(auxiliary.get("generatedPaths", ()))
152
+ for key in ("scratchRoots", "externalRoots"):
153
+ for value in _text_sequence(auxiliary.get(key), f"auxiliaryPolicy.{key}"):
154
+ _absolute_text(value, f"auxiliaryPolicy.{key}")
155
+
156
+ def validate_write_enforcement_payload(payload: Mapping[str, Any]) -> None:
157
+ if set(payload) != _ENFORCEMENT_KEYS:
158
+ raise WritePolicyError("writeEnforcement is incomplete")
159
+ boundary = payload.get("boundaryPrecision")
160
+ mutation_audit = payload.get("mutationAudit")
161
+ attribution = payload.get("attribution")
162
+ coverage = payload.get("observationCoverage")
163
+ if boundary not in _PRECISION_RANK:
164
+ raise WritePolicyError("writeEnforcement boundaryPrecision is invalid")
165
+ if mutation_audit not in {"none", "batch"}:
166
+ raise WritePolicyError("writeEnforcement mutationAudit is invalid")
167
+ if attribution not in {"call", "batch-unattributed"}:
168
+ raise WritePolicyError("writeEnforcement attribution is invalid")
169
+ if coverage not in {"complete", "scoped", "partial"}:
170
+ raise WritePolicyError("writeEnforcement observationCoverage is invalid")
171
+ if (boundary, mutation_audit, attribution) == ("exact-path", "none", "call"):
172
+ if coverage != "complete":
173
+ raise WritePolicyError("exact enforcement must have complete coverage")
174
+ elif (boundary, mutation_audit, attribution) == (
175
+ "none",
176
+ "none",
177
+ "batch-unattributed",
178
+ ):
179
+ if coverage != "partial":
180
+ raise WritePolicyError("host-session enforcement must be partial")
181
+ elif mutation_audit != "batch" or attribution != "batch-unattributed":
182
+ raise WritePolicyError("non-exact enforcement must use batch attribution")
183
+ elif boundary == "exact-path" or coverage == "complete":
184
+ raise WritePolicyError("batch enforcement cannot claim exact coverage")
185
+ for root in _text_sequence(payload.get("auditedRoots"), "auditedRoots"):
186
+ _absolute_text(root, "auditedRoots")
187
+ _text_sequence(payload.get("unobservedWriteSurfaces"), "unobservedWriteSurfaces")
188
+
189
+ def _relative_paths(value: object) -> tuple[str, ...]:
190
+ paths: list[str] = []
191
+ for raw in _text_sequence(value, "relative paths"):
192
+ candidate = PurePosixPath(raw)
193
+ if candidate.is_absolute() or not candidate.parts or ".." in candidate.parts:
194
+ raise WritePolicyError("policy paths must be normalized relative paths")
195
+ normalized = candidate.as_posix()
196
+ if normalized in {"", "."} or normalized != raw:
197
+ raise WritePolicyError("policy paths must be normalized relative paths")
198
+ paths.append(normalized)
199
+ if len(set(paths)) != len(paths):
200
+ raise WritePolicyError("policy paths must be unique")
201
+ return tuple(paths)
202
+
203
+ def _mapping(value: object, label: str) -> Mapping[str, Any]:
204
+ if not isinstance(value, Mapping):
205
+ raise WritePolicyError(f"{label} must be an object")
206
+ return value
207
+
208
+ def _text_sequence(value: object, label: str) -> tuple[str, ...]:
209
+ if not isinstance(value, Sequence) or isinstance(value, (str, bytes)):
210
+ raise WritePolicyError(f"{label} must be an array")
211
+ rows = tuple(value)
212
+ if any(not isinstance(row, str) or not row for row in rows):
213
+ raise WritePolicyError(f"{label} must contain non-empty strings")
214
+ return rows
215
+
216
+ def _absolute_text(value: object, label: str) -> str:
217
+ if not isinstance(value, str) or not value or not Path(value).is_absolute():
218
+ raise WritePolicyError(f"{label} must be an absolute path")
219
+ return value
@@ -9,7 +9,7 @@ from contextlib import contextmanager
9
9
  from dataclasses import asdict
10
10
  from pathlib import Path
11
11
 
12
- from ..agent_invocation import (
12
+ from ..agent.invocation import (
13
13
  AgentInvocationError,
14
14
  AgentModelAssignment,
15
15
  agent_model_assignment_from_payload,
@@ -113,7 +113,7 @@ def _build_parser() -> argparse.ArgumentParser:
113
113
  resolve.add_argument(
114
114
  "--capability",
115
115
  action="append",
116
- choices=("claude-skill-handoff", "tmux"),
116
+ choices=("claude-skill-handoff",),
117
117
  default=[],
118
118
  )
119
119
  launch = commands.add_parser("launch-plan")
@@ -200,9 +200,16 @@ def _probe_payload(
200
200
  status = "unknown" if "unavailable" in statuses else (
201
201
  "ready" if readiness.ok else "blocked"
202
202
  )
203
+ # A check may report that this session cannot host the lead while the run
204
+ # itself is fine. The caller needs the mode it must actually launch in, not
205
+ # the mode it asked about, so the probe answers with the resolved one.
206
+ entry_mode = (
207
+ "spawn-process" if "lead-must-spawn" in statuses else args.entry_mode
208
+ )
203
209
  return {
204
210
  "runtime": adapter.descriptor.id,
205
211
  "status": status,
212
+ "entryMode": entry_mode,
206
213
  "checks": list(readiness.checks),
207
214
  "relayContract": adapter.descriptor.relay_contract,
208
215
  }
@@ -37,7 +37,7 @@ def parse_records(paths: list[Path]) -> tuple[list[dict], int]:
37
37
  continue
38
38
  try:
39
39
  rec = json.loads(line)
40
- except Exception:
40
+ except (ValueError, TypeError):
41
41
  skipped += 1
42
42
  continue
43
43
  if isinstance(rec, dict):
@@ -210,8 +210,22 @@ def render_result_text(result: dict) -> str:
210
210
  return "".join(parts)
211
211
 
212
212
 
213
+ _CLI_EPILOG = r"""Usage:
214
+ okstra error-report <task-root>
215
+ okstra error-report <task-key> --project-root <dir>
216
+ okstra error-report <task-key> --cwd <dir>
217
+
218
+ Writes a timestamped markdown report under <task-root>/error-reports/ and
219
+ prints a JSON summary (counts by errorType / source / phase / agent) to stdout.
220
+ This is read-only against task state; it never mutates task artifacts.
221
+ """
222
+
223
+
213
224
  def main(argv: list[str] | None = None) -> int:
214
225
  parser = argparse.ArgumentParser(
226
+ prog="okstra error-report",
227
+ epilog=_CLI_EPILOG,
228
+ formatter_class=argparse.RawDescriptionHelpFormatter,
215
229
  description="Aggregate okstra-run error logs into a task-level report."
216
230
  )
217
231
  parser.add_argument("target", help="task root path or task-key")
@@ -19,7 +19,11 @@ from okstra_ctl.error_log_core import aggregate, parse_records
19
19
  from okstra_ctl.jsonl import read_jsonl
20
20
  from okstra_ctl.locks import central_lock
21
21
  from okstra_ctl.paths import okstra_home, resolve_under_root
22
- from okstra_ctl.json_boundary import load_owned_object, write_owned_object_atomic
22
+ from okstra_ctl.json_boundary import (
23
+ JsonBoundaryError,
24
+ load_owned_object,
25
+ write_owned_object_atomic,
26
+ )
23
27
 
24
28
 
25
29
  def run_dirs(home: Path) -> list[tuple[str, Path, dict]]:
@@ -282,7 +286,7 @@ def last_output_path(home: Path) -> str:
282
286
  "lastOutputPath", ""
283
287
  )
284
288
  )
285
- except Exception:
289
+ except JsonBoundaryError:
286
290
  return ""
287
291
 
288
292
 
@@ -335,8 +339,20 @@ def render_result_text(result: dict) -> str:
335
339
  return "".join(lines)
336
340
 
337
341
 
342
+ _CLI_EPILOG = r"""Usage:
343
+ okstra error-zip --out <path>
344
+
345
+ Walks the global run-index (~/.okstra/recent.jsonl + active.jsonl), collects
346
+ every target project's runs/*/logs/errors-*.jsonl, anonymizes records, and
347
+ writes a .zip (report.md + errors/anonymized.jsonl) to <path>. Read-only
348
+ against every target's .okstra/. Prints a JSON summary to stdout.
349
+ """
350
+
351
+
338
352
  def main(argv: list[str] | None = None) -> int:
339
353
  parser = argparse.ArgumentParser(
354
+ epilog=_CLI_EPILOG,
355
+ formatter_class=argparse.RawDescriptionHelpFormatter,
340
356
  prog="okstra error-zip",
341
357
  description="cross-project okstra 에러를 수집·익명화해 zip 으로 묶는다.",
342
358
  )
@@ -7,6 +7,11 @@ from dataclasses import dataclass
7
7
  from typing import Literal
8
8
 
9
9
 
10
+ # 판정 우선순위 — 여러 조건이 동시에 성립할 때 앞선 것이 이긴다. 저작 계약이
11
+ # 이 순서를 작성자에게 말하므로 `calculate_exact_coverage` 의 분기와 함께 간다.
12
+ COVERAGE_VERDICT_PRECEDENCE = ("contradicted", "under", "over", "exact")
13
+
14
+
10
15
  class ExactCoverageError(ValueError):
11
16
  """Raised when coverage input cannot define a valid comparison."""
12
17