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
@@ -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
  )
@@ -6,7 +6,7 @@ import json
6
6
  from collections.abc import Mapping
7
7
  from dataclasses import dataclass, field
8
8
  from pathlib import Path
9
- from typing import Any
9
+ from typing import Any, Literal
10
10
 
11
11
  from .domain.provider import HostModelBinding, ServedModelAttestation
12
12
  from .domain.worker_exec import WorkerWriteCapability
@@ -116,9 +116,6 @@ class Invocation:
116
116
  dispatch_kind: str
117
117
  round: int
118
118
  input_digest: str
119
- write_policy: Mapping[str, Any]
120
- write_policy_digest: str
121
- write_enforcement: Mapping[str, Any]
122
119
 
123
120
  def to_payload(self) -> dict[str, Any]:
124
121
  return {
@@ -131,14 +128,19 @@ class Invocation:
131
128
  "dispatchKind": self.dispatch_kind,
132
129
  "round": self.round,
133
130
  "inputDigest": self.input_digest,
134
- "writePolicy": dict(self.write_policy),
135
- "writePolicyDigest": self.write_policy_digest,
136
- "writeEnforcement": dict(self.write_enforcement),
137
131
  }
138
132
 
139
133
 
140
134
  @dataclass(frozen=True)
141
135
  class Attempt:
136
+ """One dispatch of a logical invocation, with the write contract it ran under.
137
+
138
+ 쓰기 계약이 invocation 이 아니라 여기 있는 이유: 계약의 artifactPolicy 는 그
139
+ 시도가 실제로 쓰는 프롬프트 파생 파일(`...-attempt-2.log` 등)을 이름으로
140
+ 담는다. attempt 마다 다른 값이므로 attempt-불변인 invocation 행에 두면 같은
141
+ invocationRef 의 2회차가 `invocationRef drift` 로 거절된다.
142
+ """
143
+
142
144
  invocation_ref: str
143
145
  attempt: int
144
146
  started_at: str
@@ -148,6 +150,9 @@ class Attempt:
148
150
  error_path: str | None
149
151
  change_summary: Mapping[str, Any]
150
152
  evidence_seal_ref: str | None
153
+ write_policy: Mapping[str, Any]
154
+ write_policy_digest: str
155
+ write_enforcement: Mapping[str, Any]
151
156
 
152
157
  def to_payload(self) -> dict[str, Any]:
153
158
  return {
@@ -160,6 +165,9 @@ class Attempt:
160
165
  "errorPath": self.error_path,
161
166
  "changeSummary": dict(self.change_summary),
162
167
  "evidenceSealRef": self.evidence_seal_ref,
168
+ "writePolicy": dict(self.write_policy),
169
+ "writePolicyDigest": self.write_policy_digest,
170
+ "writeEnforcement": dict(self.write_enforcement),
163
171
  }
164
172
 
165
173
 
@@ -528,3 +536,88 @@ def _required_index_text(
528
536
  def _optional_text(value: object) -> str | None:
529
537
  text = str(value or "").strip()
530
538
  return text or None
539
+
540
+
541
+ # ---- 무거운 모듈에서 옮겨 온 값 타입 ------------------------------------
542
+ # 아래 둘은 dispatch_state(2,092줄) 와 mutation_recovery 안에 있었다. 값 하나가
543
+ # 필요한 모듈이 그 모듈 전체를 끌어오게 되어, 반대 방향은 함수 본문 지연
544
+ # import 로 밀려나 순환이 됐다. 원래 소유 모듈이 이름을 재노출한다.
545
+
546
+ def build_dispatch_id(invocation_id: str, attempt: int) -> str:
547
+ """The only place Python assembles a dispatch id.
548
+
549
+ Two generators drifted: this module pinned the attempt to 1 while
550
+ `dispatch_core` used the real one, so a re-dispatch took the first attempt's
551
+ id, team-state kept the same row twice, and `validate-run` failed that run
552
+ with `agent dispatch ID is duplicated`.
553
+
554
+ Prose mirrors of this shape live outside Python, where no guard reaches
555
+ them: `prompts/lead/convergence.md`, `prompts/lead/report-writer.md`,
556
+ `prompts/lead/plan-body-verification.md` and the five
557
+ `adapters/hosts/*/relay.md` each spell an `--dispatch-id` argument out for
558
+ the lead. They agree with this function only because
559
+ `record_verified_agent_dispatch` records the first attempt and no other;
560
+ changing that convention has to change them too.
561
+ """
562
+ return f"{invocation_id}:attempt-{attempt}"
563
+
564
+
565
+ @dataclass(frozen=True)
566
+ class MutationRecoveryDecision:
567
+ recovery_ref: str
568
+ source_invocation_ref: str
569
+ source_attempt_ref: str
570
+ source_evidence_seal_ref: str | None
571
+ original_baseline_digest: str
572
+ terminal_source_diff_digest: str
573
+ terminal_git_chain_digest: str
574
+ verifier_role_execution_ref: str | None
575
+ verifier_invocation_ref: str | None
576
+ recovered_evidence_refs: tuple[Mapping[str, Any], ...]
577
+ recovered_out_of_plan_edits: tuple[Mapping[str, Any], ...]
578
+ unmet_obligations: tuple[str, ...]
579
+ decision: Literal["carry-forward", "reject", "cleanup-confirmed"]
580
+ reason: str = ""
581
+
582
+ def to_payload(self) -> dict[str, Any]:
583
+ return {
584
+ "recoveryRef": self.recovery_ref,
585
+ "sourceInvocationRef": self.source_invocation_ref,
586
+ "sourceAttemptRef": self.source_attempt_ref,
587
+ "sourceEvidenceSealRef": self.source_evidence_seal_ref,
588
+ "originalBaselineDigest": self.original_baseline_digest,
589
+ "terminalSourceDiffDigest": self.terminal_source_diff_digest,
590
+ "terminalGitChainDigest": self.terminal_git_chain_digest,
591
+ "verifierRoleExecutionRef": self.verifier_role_execution_ref,
592
+ "verifierInvocationRef": self.verifier_invocation_ref,
593
+ "recoveredEvidenceRefs": [dict(row) for row in self.recovered_evidence_refs],
594
+ "recoveredOutOfPlanEdits": [
595
+ dict(row) for row in self.recovered_out_of_plan_edits
596
+ ],
597
+ "unmetObligations": list(self.unmet_obligations),
598
+ "decision": self.decision,
599
+ "reason": self.reason,
600
+ }
601
+
602
+ @classmethod
603
+ def from_payload(cls, payload: Mapping[str, Any]) -> "MutationRecoveryDecision":
604
+ return cls(
605
+ recovery_ref=str(payload["recoveryRef"]),
606
+ source_invocation_ref=str(payload["sourceInvocationRef"]),
607
+ source_attempt_ref=str(payload["sourceAttemptRef"]),
608
+ source_evidence_seal_ref=payload.get("sourceEvidenceSealRef"),
609
+ original_baseline_digest=str(payload.get("originalBaselineDigest") or ""),
610
+ terminal_source_diff_digest=str(
611
+ payload.get("terminalSourceDiffDigest") or ""
612
+ ),
613
+ terminal_git_chain_digest=str(payload.get("terminalGitChainDigest") or ""),
614
+ verifier_role_execution_ref=payload.get("verifierRoleExecutionRef"),
615
+ verifier_invocation_ref=payload.get("verifierInvocationRef"),
616
+ recovered_evidence_refs=tuple(payload.get("recoveredEvidenceRefs") or ()),
617
+ recovered_out_of_plan_edits=tuple(
618
+ payload.get("recoveredOutOfPlanEdits") or ()
619
+ ),
620
+ unmet_obligations=tuple(payload.get("unmetObligations") or ()),
621
+ decision=str(payload["decision"]),
622
+ reason=str(payload.get("reason") or ""),
623
+ )