okstra 0.186.7 → 0.187.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (541) hide show
  1. package/README.md +1 -1
  2. package/dist/cli-registry.d.mts +88 -1
  3. package/dist/cli-registry.mjs +68 -111
  4. package/dist/cli-registry.mjs.map +1 -1
  5. package/dist/commands/execute/render-bundle.mjs +0 -1
  6. package/dist/commands/execute/render-bundle.mjs.map +1 -1
  7. package/dist/commands/execute/run.mjs +8 -3
  8. package/dist/commands/execute/run.mjs.map +1 -1
  9. package/dist/commands/lifecycle/check-project.mjs +1 -14
  10. package/dist/commands/lifecycle/check-project.mjs.map +1 -1
  11. package/dist/commands/lifecycle/config.mjs +38 -40
  12. package/dist/commands/lifecycle/config.mjs.map +1 -1
  13. package/dist/commands/lifecycle/doctor.d.mts +22 -7
  14. package/dist/commands/lifecycle/doctor.mjs +77 -49
  15. package/dist/commands/lifecycle/doctor.mjs.map +1 -1
  16. package/dist/commands/lifecycle/install.d.mts +12 -10
  17. package/dist/commands/lifecycle/install.mjs +104 -39
  18. package/dist/commands/lifecycle/install.mjs.map +1 -1
  19. package/dist/commands/lifecycle/paths.mjs +8 -3
  20. package/dist/commands/lifecycle/paths.mjs.map +1 -1
  21. package/dist/commands/lifecycle/preflight.mjs +2 -1
  22. package/dist/commands/lifecycle/preflight.mjs.map +1 -1
  23. package/dist/commands/lifecycle/setup.mjs +22 -36
  24. package/dist/commands/lifecycle/setup.mjs.map +1 -1
  25. package/dist/commands/lifecycle/uninstall.d.mts +4 -2
  26. package/dist/commands/lifecycle/uninstall.mjs +59 -15
  27. package/dist/commands/lifecycle/uninstall.mjs.map +1 -1
  28. package/dist/commands/memory/memory.mjs +7 -1
  29. package/dist/commands/memory/memory.mjs.map +1 -1
  30. package/dist/lib/helper-scripts.d.mts +1 -1
  31. package/dist/lib/helper-scripts.mjs +10 -18
  32. package/dist/lib/helper-scripts.mjs.map +1 -1
  33. package/dist/lib/host-config.d.mts +72 -0
  34. package/dist/lib/host-config.mjs +404 -0
  35. package/dist/lib/host-config.mjs.map +1 -0
  36. package/dist/lib/host-registry-client.d.mts +2 -2
  37. package/dist/lib/host-registry-client.mjs +0 -3
  38. package/dist/lib/host-registry-client.mjs.map +1 -1
  39. package/dist/lib/install-assets.d.mts +1 -0
  40. package/dist/lib/install-assets.mjs +4 -0
  41. package/dist/lib/install-assets.mjs.map +1 -1
  42. package/dist/lib/proc.d.mts +2 -0
  43. package/dist/lib/proc.mjs +12 -0
  44. package/dist/lib/proc.mjs.map +1 -1
  45. package/dist/lib/python-command.d.mts +2 -0
  46. package/dist/lib/python-command.mjs +52 -0
  47. package/dist/lib/python-command.mjs.map +1 -0
  48. package/dist/lib/python-helper.d.mts +2 -5
  49. package/dist/lib/python-helper.mjs +3 -52
  50. package/dist/lib/python-helper.mjs.map +1 -1
  51. package/dist/lib/runtime-payload.d.mts +23 -0
  52. package/dist/lib/runtime-payload.mjs +57 -0
  53. package/dist/lib/runtime-payload.mjs.map +1 -0
  54. package/dist/lib/types.d.mts +24 -13
  55. package/docs/architecture/storage-model.md +21 -5
  56. package/docs/architecture.md +37 -36
  57. package/docs/cli.md +55 -76
  58. package/docs/coding-rules.md +295 -0
  59. package/docs/container.md +10 -36
  60. package/docs/contributor-change-matrix.md +1 -1
  61. package/docs/for-ai/skills/okstra-code-review.md +0 -1
  62. package/docs/for-ai/skills/okstra-container-build.md +8 -41
  63. package/docs/for-ai/skills/okstra-inspect.md +4 -4
  64. package/docs/for-ai/skills/okstra-manager.md +2 -2
  65. package/docs/for-ai/skills/okstra-rollup.md +0 -1
  66. package/docs/for-ai/skills/okstra-run.md +3 -3
  67. package/docs/for-ai/skills/okstra-user-response.md +0 -1
  68. package/docs/performance-improvement-plan-v2.md +1 -1
  69. package/docs/project-structure-overview.md +62 -62
  70. package/docs/task-process/README.md +3 -3
  71. package/docs/task-process/common-flow.md +1 -1
  72. package/docs/task-process/error-analysis.md +3 -3
  73. package/docs/task-process/implementation-planning.md +3 -1
  74. package/docs/task-process/release-handoff.md +1 -1
  75. package/package.json +3 -2
  76. package/runtime/BUILD.json +2 -2
  77. package/runtime/agents/workers/claude-worker.md +6 -11
  78. package/runtime/agents/workers/report-writer-worker.md +1 -1
  79. package/runtime/bin/lib/okstra/cli.sh +4 -0
  80. package/runtime/bin/lib/okstra/globals.sh +2 -0
  81. package/runtime/bin/lib/okstra/interactive.sh +26 -173
  82. package/runtime/bin/lib/okstra/project-resolver.sh +23 -61
  83. package/runtime/bin/lib/okstra/usage.sh +3 -3
  84. package/runtime/bin/okstra-compact-reminder.sh +1 -5
  85. package/runtime/bin/okstra-error-log.py +19 -2
  86. package/runtime/bin/okstra-import-check.py +26 -0
  87. package/runtime/bin/okstra-inject-report-index.py +5 -4
  88. package/runtime/bin/okstra-provider-exec.py +22 -17
  89. package/runtime/bin/okstra-render-final-report.py +16 -6
  90. package/runtime/bin/okstra-render-report-views.py +25 -18
  91. package/runtime/bin/okstra-report-translate.py +30 -18
  92. package/runtime/bin/okstra-spawn-followups.py +4 -0
  93. package/runtime/bin/okstra-token-usage.py +3 -0
  94. package/runtime/bin/okstra.sh +4 -2
  95. package/runtime/bin/okstra_bootstrap.py +58 -0
  96. package/runtime/prompts/coding-preflight/overview.md +1 -1
  97. package/runtime/prompts/duties/planning-worker.md +1 -1
  98. package/runtime/prompts/launch.template.md +35 -9
  99. package/runtime/prompts/lead/convergence.md +96 -87
  100. package/runtime/prompts/lead/okstra-lead-contract.md +63 -12
  101. package/runtime/prompts/lead/plan-body-verification.md +65 -46
  102. package/runtime/prompts/lead/report-writer.md +34 -4
  103. package/runtime/prompts/lead/team-contract.md +4 -1
  104. package/runtime/prompts/profiles/_clarification-recommendation.md +2 -1
  105. package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
  106. package/runtime/prompts/profiles/_common-contract.md +3 -3
  107. package/runtime/prompts/profiles/_coverage-critic.md +1 -1
  108. package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
  109. package/runtime/prompts/profiles/_implementation-executor.md +3 -1
  110. package/runtime/prompts/profiles/_implementation-verifier.md +16 -2
  111. package/runtime/prompts/profiles/error-analysis.md +4 -3
  112. package/runtime/prompts/profiles/final-verification.md +2 -2
  113. package/runtime/prompts/profiles/implementation-planning.md +22 -17
  114. package/runtime/prompts/profiles/implementation.md +3 -2
  115. package/runtime/prompts/profiles/requirements-discovery.md +2 -1
  116. package/runtime/prompts/wizard/prompts.ko.json +29 -8
  117. package/runtime/python/okstra_ctl/__init__.py +6 -27
  118. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +4 -5
  119. package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +21 -2
  120. package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +3 -12
  121. package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +40 -22
  122. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +14 -21
  123. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +37 -20
  124. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +86 -14
  125. package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +12 -12
  126. package/runtime/python/okstra_ctl/adapters/runtime/cmux.py +4 -7
  127. package/runtime/python/okstra_ctl/agent/__init__.py +1 -0
  128. package/runtime/python/okstra_ctl/{agent_activity.py → agent/activity.py} +12 -1
  129. package/runtime/python/okstra_ctl/{agent_invocation.py → agent/invocation.py} +34 -5
  130. package/runtime/python/okstra_ctl/agent/prompt_cli/__init__.py +19 -0
  131. package/runtime/python/okstra_ctl/agent/prompt_cli/__main__.py +11 -0
  132. package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +215 -0
  133. package/runtime/python/okstra_ctl/agent/prompt_cli/dynamic_verifier.py +155 -0
  134. package/runtime/python/okstra_ctl/agent/prompt_cli/emit.py +66 -0
  135. package/runtime/python/okstra_ctl/agent/prompt_cli/inputs.py +138 -0
  136. package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +408 -0
  137. package/runtime/python/okstra_ctl/agent/prompt_cli/results.py +197 -0
  138. package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +107 -0
  139. package/runtime/python/okstra_ctl/analysis_packet.py +47 -4
  140. package/runtime/python/okstra_ctl/approval_decisions.py +260 -8
  141. package/runtime/python/okstra_ctl/assignment_resolver.py +0 -12
  142. package/runtime/python/okstra_ctl/attempt_evidence.py +12 -24
  143. package/runtime/python/okstra_ctl/blocking_checks.py +251 -0
  144. package/runtime/python/okstra_ctl/brief_frontmatter.py +0 -7
  145. package/runtime/python/okstra_ctl/clarification_items/__init__.py +102 -0
  146. package/runtime/python/okstra_ctl/clarification_items/carry.py +224 -0
  147. package/runtime/python/okstra_ctl/clarification_items/dispositions.py +135 -0
  148. package/runtime/python/okstra_ctl/clarification_items/parsing.py +253 -0
  149. package/runtime/python/okstra_ctl/clarification_items/rows.py +115 -0
  150. package/runtime/python/okstra_ctl/clarification_items/scan.py +215 -0
  151. package/runtime/python/okstra_ctl/clarification_items/sidecars.py +211 -0
  152. package/runtime/python/okstra_ctl/cmux.py +38 -31
  153. package/runtime/python/okstra_ctl/code_review_target.py +163 -1
  154. package/runtime/python/okstra_ctl/conformance.py +23 -0
  155. package/runtime/python/okstra_ctl/container.py +38 -421
  156. package/runtime/python/okstra_ctl/context_cost.py +16 -2
  157. package/runtime/python/okstra_ctl/contract_graph_cli.py +7 -0
  158. package/runtime/python/okstra_ctl/convergence.py +101 -15
  159. package/runtime/python/okstra_ctl/convergence_critic_prompt.py +345 -0
  160. package/runtime/python/okstra_ctl/convergence_engine.py +382 -70
  161. package/runtime/python/okstra_ctl/convergence_store.py +5 -29
  162. package/runtime/python/okstra_ctl/design_prep.py +108 -12
  163. package/runtime/python/okstra_ctl/design_snapshot.py +11 -1
  164. package/runtime/python/okstra_ctl/design_surfaces.py +29 -2
  165. package/runtime/python/okstra_ctl/dispatch_core.py +102 -25
  166. package/runtime/python/okstra_ctl/dispatch_state.py +268 -39
  167. package/runtime/python/okstra_ctl/doctor_cli.py +48 -0
  168. package/runtime/python/okstra_ctl/domain/worker_exec.py +10 -5
  169. package/runtime/python/okstra_ctl/domain/worker_presentation.py +21 -1
  170. package/runtime/python/okstra_ctl/domain/worker_stream.py +52 -21
  171. package/runtime/python/okstra_ctl/domain/write_policy.py +219 -0
  172. package/runtime/python/okstra_ctl/entrypoints/hosts.py +9 -2
  173. package/runtime/python/okstra_ctl/error_log_core.py +1 -1
  174. package/runtime/python/okstra_ctl/error_report.py +14 -0
  175. package/runtime/python/okstra_ctl/error_zip.py +18 -2
  176. package/runtime/python/okstra_ctl/execution_identity.py +100 -7
  177. package/runtime/python/okstra_ctl/execution_manifest.py +67 -20
  178. package/runtime/python/okstra_ctl/execution_mutation_audit.py +90 -8
  179. package/runtime/python/okstra_ctl/final_report_paths.py +17 -0
  180. package/runtime/python/okstra_ctl/final_report_schema.py +90 -16
  181. package/runtime/python/okstra_ctl/git_reconcile.py +22 -2
  182. package/runtime/python/okstra_ctl/handoff.py +24 -1
  183. package/runtime/python/okstra_ctl/ids.py +6 -16
  184. package/runtime/python/okstra_ctl/implementation_direction.py +142 -33
  185. package/runtime/python/okstra_ctl/implementation_options.py +25 -11
  186. package/runtime/python/okstra_ctl/implementation_outcome.py +5 -2
  187. package/runtime/python/okstra_ctl/improvement_lenses.py +0 -14
  188. package/runtime/python/okstra_ctl/incremental_carry.py +69 -8
  189. package/runtime/python/okstra_ctl/incremental_scope.py +173 -12
  190. package/runtime/python/okstra_ctl/index.py +2 -2
  191. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +48 -8
  192. package/runtime/python/okstra_ctl/interactive_cli.py +223 -0
  193. package/runtime/python/okstra_ctl/json_boundary.py +10 -0
  194. package/runtime/python/okstra_ctl/listing.py +0 -91
  195. package/runtime/python/okstra_ctl/locks.py +5 -19
  196. package/runtime/python/okstra_ctl/log_report.py +14 -0
  197. package/runtime/python/okstra_ctl/manager_cli.py +22 -3
  198. package/runtime/python/okstra_ctl/manager_launch.py +4 -8
  199. package/runtime/python/okstra_ctl/material.py +2 -2
  200. package/runtime/python/okstra_ctl/migrate.py +23 -1
  201. package/runtime/python/okstra_ctl/model_cli.py +19 -5
  202. package/runtime/python/okstra_ctl/model_discovery.py +46 -47
  203. package/runtime/python/okstra_ctl/model_io/__init__.py +1 -0
  204. package/runtime/python/okstra_ctl/model_io/lines.py +163 -0
  205. package/runtime/python/okstra_ctl/model_io/references.py +370 -0
  206. package/runtime/python/okstra_ctl/model_io/renderers.py +498 -0
  207. package/runtime/python/okstra_ctl/model_io_cli.py +30 -945
  208. package/runtime/python/okstra_ctl/model_pool.py +10 -6
  209. package/runtime/python/okstra_ctl/models.py +8 -8
  210. package/runtime/python/okstra_ctl/mutation_recovery.py +5 -63
  211. package/runtime/python/okstra_ctl/next_phase.py +121 -69
  212. package/runtime/python/okstra_ctl/pane_reclaim.py +23 -0
  213. package/runtime/python/okstra_ctl/pane_title.py +11 -8
  214. package/runtime/python/okstra_ctl/path_hints.py +36 -11
  215. package/runtime/python/okstra_ctl/paths.py +76 -8
  216. package/runtime/python/okstra_ctl/plan_items.py +132 -16
  217. package/runtime/python/okstra_ctl/plan_items_cli.py +486 -36
  218. package/runtime/python/okstra_ctl/plan_run_root.py +1 -1
  219. package/runtime/python/okstra_ctl/plan_validate_cli.py +51 -0
  220. package/runtime/python/okstra_ctl/plan_verify_cli.py +80 -0
  221. package/runtime/python/okstra_ctl/prepare_error.py +20 -0
  222. package/runtime/python/okstra_ctl/prior_planning.py +157 -0
  223. package/runtime/python/okstra_ctl/profile_show.py +18 -1
  224. package/runtime/python/okstra_ctl/project_setup_cli.py +140 -0
  225. package/runtime/python/okstra_ctl/recap.py +22 -1
  226. package/runtime/python/okstra_ctl/reconcile.py +84 -0
  227. package/runtime/python/okstra_ctl/registry/host_registry.py +18 -1
  228. package/runtime/python/okstra_ctl/render.py +98 -32
  229. package/runtime/python/okstra_ctl/render_final_report.py +3 -7
  230. package/runtime/python/okstra_ctl/report_assembly.py +297 -7
  231. package/runtime/python/okstra_ctl/report_contract.py +30 -7
  232. package/runtime/python/okstra_ctl/report_finalize.py +206 -15
  233. package/runtime/python/okstra_ctl/report_html/common.py +2 -2
  234. package/runtime/python/okstra_ctl/report_html/render.py +8 -12
  235. package/runtime/python/okstra_ctl/report_language.py +8 -4
  236. package/runtime/python/okstra_ctl/report_narrative.py +90 -3
  237. package/runtime/python/okstra_ctl/report_projections.py +8 -2
  238. package/runtime/python/okstra_ctl/report_synthesis_packet.py +258 -1
  239. package/runtime/python/okstra_ctl/report_translation.py +2 -35
  240. package/runtime/python/okstra_ctl/report_views.py +4 -22
  241. package/runtime/python/okstra_ctl/resolve_task_key.py +13 -0
  242. package/runtime/python/okstra_ctl/rollup.py +13 -0
  243. package/runtime/python/okstra_ctl/run.py +639 -138
  244. package/runtime/python/okstra_ctl/run_audit.py +13 -0
  245. package/runtime/python/okstra_ctl/run_index_row.py +0 -12
  246. package/runtime/python/okstra_ctl/seeding.py +21 -11
  247. package/runtime/python/okstra_ctl/sequence.py +1 -1
  248. package/runtime/python/okstra_ctl/session.py +64 -14
  249. package/runtime/python/okstra_ctl/set_work_status.py +18 -0
  250. package/runtime/python/okstra_ctl/stage_integrate.py +15 -1
  251. package/runtime/python/okstra_ctl/stage_ledger.py +1 -1
  252. package/runtime/python/okstra_ctl/stage_map.py +57 -11
  253. package/runtime/python/okstra_ctl/stage_map_cli.py +83 -0
  254. package/runtime/python/okstra_ctl/stage_map_view.py +65 -0
  255. package/runtime/python/okstra_ctl/stage_targets.py +3 -12
  256. package/runtime/python/okstra_ctl/task_list_cli.py +138 -0
  257. package/runtime/python/okstra_ctl/task_show_cli.py +66 -0
  258. package/runtime/python/okstra_ctl/task_target.py +4 -16
  259. package/runtime/python/okstra_ctl/team.py +49 -2
  260. package/runtime/python/okstra_ctl/time_report.py +17 -2
  261. package/runtime/python/okstra_ctl/usage_report.py +11 -0
  262. package/runtime/python/okstra_ctl/user_response.py +55 -218
  263. package/runtime/python/okstra_ctl/user_response_values.py +242 -0
  264. package/runtime/python/okstra_ctl/validation_contract.py +3 -0
  265. package/runtime/python/okstra_ctl/verification_target.py +68 -0
  266. package/runtime/python/okstra_ctl/wizard.py +457 -92
  267. package/runtime/python/okstra_ctl/worker_artifacts.py +75 -16
  268. package/runtime/python/okstra_ctl/worker_audit_check.py +22 -0
  269. package/runtime/python/okstra_ctl/worker_dispatch.py +21 -0
  270. package/runtime/python/okstra_ctl/worker_liveness.py +33 -0
  271. package/runtime/python/okstra_ctl/worker_prompt_body.py +15 -2
  272. package/runtime/python/okstra_ctl/worker_prompt_contract.py +98 -7
  273. package/runtime/python/okstra_ctl/worker_prompt_headers.py +24 -2
  274. package/runtime/python/okstra_ctl/worker_prompt_policy.py +18 -3
  275. package/runtime/python/okstra_ctl/worker_request.py +2 -1
  276. package/runtime/python/okstra_ctl/worker_runner.py +16 -9
  277. package/runtime/python/okstra_ctl/worker_state.py +16 -1
  278. package/runtime/python/okstra_ctl/workflow.py +18 -1
  279. package/runtime/python/okstra_ctl/worktree/__init__.py +92 -0
  280. package/runtime/python/okstra_ctl/worktree/cleanliness.py +89 -0
  281. package/runtime/python/okstra_ctl/worktree/decisions.py +127 -0
  282. package/runtime/python/okstra_ctl/worktree/git_ops.py +165 -0
  283. package/runtime/python/okstra_ctl/worktree/linking.py +249 -0
  284. package/runtime/python/okstra_ctl/worktree/naming.py +91 -0
  285. package/runtime/python/okstra_ctl/worktree/provision.py +385 -0
  286. package/runtime/python/okstra_ctl/worktree/sync_config.py +181 -0
  287. package/runtime/python/okstra_ctl/worktree_cli.py +75 -0
  288. package/runtime/python/okstra_ctl/worktree_lookup_cli.py +39 -0
  289. package/runtime/python/okstra_ctl/worktree_registry.py +7 -0
  290. package/runtime/python/okstra_ctl/worktree_status_cli.py +53 -0
  291. package/runtime/python/okstra_ctl/write_policy.py +90 -213
  292. package/runtime/python/okstra_project/__init__.py +0 -4
  293. package/runtime/python/okstra_project/dirs.py +2 -2
  294. package/runtime/python/okstra_project/phase_pointer.py +84 -0
  295. package/runtime/python/okstra_project/slug.py +24 -0
  296. package/runtime/python/okstra_project/state.py +22 -230
  297. package/runtime/python/okstra_token_usage/claude.py +9 -1
  298. package/runtime/python/okstra_token_usage/cli.py +3 -1
  299. package/runtime/python/okstra_token_usage/collect.py +71 -10
  300. package/runtime/python/okstra_token_usage/cursor.py +2 -0
  301. package/runtime/python/okstra_token_usage/grok.py +1 -5
  302. package/runtime/python/okstra_token_usage/paths.py +26 -0
  303. package/runtime/python/okstra_token_usage/pricing.py +19 -7
  304. package/runtime/schemas/convergence-critic-results-v1.0.schema.json +5 -0
  305. package/runtime/schemas/execution-manifest-v2.schema.json +10 -10
  306. package/runtime/schemas/final-report-v2.0.schema.json +9 -26
  307. package/runtime/schemas/final-report-v3.0.schema.json +173 -41
  308. package/runtime/schemas/report-narrative-v3.0.schema.json +3 -2
  309. package/runtime/skills/okstra-brief-gen/SKILL.md +35 -2
  310. package/runtime/skills/okstra-container-build/SKILL.md +16 -47
  311. package/runtime/skills/okstra-inspect/facets/history.md +1 -1
  312. package/runtime/skills/okstra-inspect/facets/status.md +7 -6
  313. package/runtime/skills/okstra-pr-gen/SKILL.md +1 -1
  314. package/runtime/skills/okstra-run/SKILL.md +7 -5
  315. package/runtime/templates/report-writer-prompt-preamble.md +1 -1
  316. package/runtime/templates/reports/brief.template.md +6 -2
  317. package/runtime/templates/reports/error-analysis-input.template.md +2 -0
  318. package/runtime/templates/reports/final-verification-input.template.md +2 -0
  319. package/runtime/templates/reports/implementation-input.template.md +7 -1
  320. package/runtime/templates/reports/implementation-planning-input.template.md +4 -0
  321. package/runtime/templates/reports/quick-input.template.md +2 -0
  322. package/runtime/templates/reports/release-handoff-input.template.md +6 -1
  323. package/runtime/templates/reports/report.js +20 -3
  324. package/runtime/templates/reports/schedule.template.md +2 -0
  325. package/runtime/templates/reports/settings.template.json +0 -10
  326. package/runtime/templates/reports/task-brief.template.md +3 -0
  327. package/runtime/templates/reports/user-response.template.md +7 -3
  328. package/runtime/validators/checks/fixtures-01.py +57 -0
  329. package/runtime/validators/checks/fixtures-02.py +565 -0
  330. package/runtime/validators/checks/runners-01.py +108 -0
  331. package/runtime/validators/checks/validate-assets-01.py +60 -0
  332. package/runtime/validators/checks/validate-prompt-metadata-01.py +261 -0
  333. package/runtime/validators/checks/validate-tasks-01.py +46 -0
  334. package/runtime/validators/checks/validate-tasks-02.py +85 -0
  335. package/runtime/validators/checks/validate-tasks-03.py +61 -0
  336. package/runtime/validators/checks/validate-tasks-04.py +118 -0
  337. package/runtime/validators/forbidden_actions.py +73 -3
  338. package/runtime/validators/lib/common.sh +5 -0
  339. package/runtime/validators/lib/fixtures.sh +7 -591
  340. package/runtime/validators/lib/paths.sh +13 -0
  341. package/runtime/validators/lib/runners.sh +6 -104
  342. package/runtime/validators/lib/summary.sh +1 -1
  343. package/runtime/validators/lib/validate-assets.sh +6 -56
  344. package/runtime/validators/lib/validate-prompt-metadata.sh +6 -257
  345. package/runtime/validators/lib/validate-tasks.sh +9 -294
  346. package/runtime/validators/validate-implementation-plan-stages.py +4 -4
  347. package/runtime/validators/validate-run.py +1369 -2654
  348. package/runtime/validators/validate-workflow.sh +56 -16
  349. package/runtime/validators/validate_improvement_report.py +2 -1
  350. package/runtime/validators/validate_session_conformance.py +295 -49
  351. package/dist/commands/execute/agent-prompt.d.mts +0 -1
  352. package/dist/commands/execute/agent-prompt.mjs +0 -24
  353. package/dist/commands/execute/agent-prompt.mjs.map +0 -1
  354. package/dist/commands/execute/codex-dispatch.d.mts +0 -3
  355. package/dist/commands/execute/codex-dispatch.mjs +0 -6
  356. package/dist/commands/execute/codex-dispatch.mjs.map +0 -1
  357. package/dist/commands/execute/codex-run.d.mts +0 -3
  358. package/dist/commands/execute/codex-run.mjs +0 -62
  359. package/dist/commands/execute/codex-run.mjs.map +0 -1
  360. package/dist/commands/execute/convergence.d.mts +0 -1
  361. package/dist/commands/execute/convergence.mjs +0 -38
  362. package/dist/commands/execute/convergence.mjs.map +0 -1
  363. package/dist/commands/execute/error-log.d.mts +0 -1
  364. package/dist/commands/execute/error-log.mjs +0 -18
  365. package/dist/commands/execute/error-log.mjs.map +0 -1
  366. package/dist/commands/execute/git-reconcile.d.mts +0 -1
  367. package/dist/commands/execute/git-reconcile.mjs +0 -30
  368. package/dist/commands/execute/git-reconcile.mjs.map +0 -1
  369. package/dist/commands/execute/handoff.d.mts +0 -1
  370. package/dist/commands/execute/handoff.mjs +0 -31
  371. package/dist/commands/execute/handoff.mjs.map +0 -1
  372. package/dist/commands/execute/incremental-carry.d.mts +0 -1
  373. package/dist/commands/execute/incremental-carry.mjs +0 -20
  374. package/dist/commands/execute/incremental-carry.mjs.map +0 -1
  375. package/dist/commands/execute/incremental-scope.d.mts +0 -1
  376. package/dist/commands/execute/incremental-scope.mjs +0 -29
  377. package/dist/commands/execute/incremental-scope.mjs.map +0 -1
  378. package/dist/commands/execute/integrate-stages.d.mts +0 -1
  379. package/dist/commands/execute/integrate-stages.mjs +0 -24
  380. package/dist/commands/execute/integrate-stages.mjs.map +0 -1
  381. package/dist/commands/execute/pane-title.d.mts +0 -1
  382. package/dist/commands/execute/pane-title.mjs +0 -20
  383. package/dist/commands/execute/pane-title.mjs.map +0 -1
  384. package/dist/commands/execute/plan-items.d.mts +0 -1
  385. package/dist/commands/execute/plan-items.mjs +0 -9
  386. package/dist/commands/execute/plan-items.mjs.map +0 -1
  387. package/dist/commands/execute/plan-validate.d.mts +0 -1
  388. package/dist/commands/execute/plan-validate.mjs +0 -68
  389. package/dist/commands/execute/plan-validate.mjs.map +0 -1
  390. package/dist/commands/execute/plan-verify.d.mts +0 -1
  391. package/dist/commands/execute/plan-verify.mjs +0 -43
  392. package/dist/commands/execute/plan-verify.mjs.map +0 -1
  393. package/dist/commands/execute/spawn-followups.d.mts +0 -1
  394. package/dist/commands/execute/spawn-followups.mjs +0 -22
  395. package/dist/commands/execute/spawn-followups.mjs.map +0 -1
  396. package/dist/commands/execute/team.d.mts +0 -3
  397. package/dist/commands/execute/team.mjs +0 -66
  398. package/dist/commands/execute/team.mjs.map +0 -1
  399. package/dist/commands/execute/token-usage.d.mts +0 -1
  400. package/dist/commands/execute/token-usage.mjs +0 -19
  401. package/dist/commands/execute/token-usage.mjs.map +0 -1
  402. package/dist/commands/execute/worker-audit-check.d.mts +0 -1
  403. package/dist/commands/execute/worker-audit-check.mjs +0 -34
  404. package/dist/commands/execute/worker-audit-check.mjs.map +0 -1
  405. package/dist/commands/execute/worker-dispatch.d.mts +0 -7
  406. package/dist/commands/execute/worker-dispatch.mjs +0 -64
  407. package/dist/commands/execute/worker-dispatch.mjs.map +0 -1
  408. package/dist/commands/execute/worker-state.d.mts +0 -1
  409. package/dist/commands/execute/worker-state.mjs +0 -28
  410. package/dist/commands/execute/worker-state.mjs.map +0 -1
  411. package/dist/commands/execute/worktree-lookup.d.mts +0 -1
  412. package/dist/commands/execute/worktree-lookup.mjs +0 -92
  413. package/dist/commands/execute/worktree-lookup.mjs.map +0 -1
  414. package/dist/commands/execute/worktree-status.d.mts +0 -1
  415. package/dist/commands/execute/worktree-status.mjs +0 -121
  416. package/dist/commands/execute/worktree-status.mjs.map +0 -1
  417. package/dist/commands/inspect/code-review.d.mts +0 -1
  418. package/dist/commands/inspect/code-review.mjs +0 -32
  419. package/dist/commands/inspect/code-review.mjs.map +0 -1
  420. package/dist/commands/inspect/container.d.mts +0 -1
  421. package/dist/commands/inspect/container.mjs +0 -25
  422. package/dist/commands/inspect/container.mjs.map +0 -1
  423. package/dist/commands/inspect/context-cost.d.mts +0 -1
  424. package/dist/commands/inspect/context-cost.mjs +0 -25
  425. package/dist/commands/inspect/context-cost.mjs.map +0 -1
  426. package/dist/commands/inspect/design-prep.d.mts +0 -1
  427. package/dist/commands/inspect/design-prep.mjs +0 -22
  428. package/dist/commands/inspect/design-prep.mjs.map +0 -1
  429. package/dist/commands/inspect/error-report.d.mts +0 -1
  430. package/dist/commands/inspect/error-report.mjs +0 -25
  431. package/dist/commands/inspect/error-report.mjs.map +0 -1
  432. package/dist/commands/inspect/error-zip.d.mts +0 -1
  433. package/dist/commands/inspect/error-zip.mjs +0 -24
  434. package/dist/commands/inspect/error-zip.mjs.map +0 -1
  435. package/dist/commands/inspect/log-report.d.mts +0 -1
  436. package/dist/commands/inspect/log-report.mjs +0 -26
  437. package/dist/commands/inspect/log-report.mjs.map +0 -1
  438. package/dist/commands/inspect/model-io.d.mts +0 -1
  439. package/dist/commands/inspect/model-io.mjs +0 -25
  440. package/dist/commands/inspect/model-io.mjs.map +0 -1
  441. package/dist/commands/inspect/profile-show.d.mts +0 -1
  442. package/dist/commands/inspect/profile-show.mjs +0 -28
  443. package/dist/commands/inspect/profile-show.mjs.map +0 -1
  444. package/dist/commands/inspect/recap.d.mts +0 -1
  445. package/dist/commands/inspect/recap.mjs +0 -30
  446. package/dist/commands/inspect/recap.mjs.map +0 -1
  447. package/dist/commands/inspect/resolve-task-key.d.mts +0 -1
  448. package/dist/commands/inspect/resolve-task-key.mjs +0 -24
  449. package/dist/commands/inspect/resolve-task-key.mjs.map +0 -1
  450. package/dist/commands/inspect/rollup.d.mts +0 -1
  451. package/dist/commands/inspect/rollup.mjs +0 -25
  452. package/dist/commands/inspect/rollup.mjs.map +0 -1
  453. package/dist/commands/inspect/run-audit.d.mts +0 -1
  454. package/dist/commands/inspect/run-audit.mjs +0 -25
  455. package/dist/commands/inspect/run-audit.mjs.map +0 -1
  456. package/dist/commands/inspect/set-work-status.d.mts +0 -1
  457. package/dist/commands/inspect/set-work-status.mjs +0 -29
  458. package/dist/commands/inspect/set-work-status.mjs.map +0 -1
  459. package/dist/commands/inspect/stage-map.d.mts +0 -1
  460. package/dist/commands/inspect/stage-map.mjs +0 -131
  461. package/dist/commands/inspect/stage-map.mjs.map +0 -1
  462. package/dist/commands/inspect/task-list.d.mts +0 -1
  463. package/dist/commands/inspect/task-list.mjs +0 -149
  464. package/dist/commands/inspect/task-list.mjs.map +0 -1
  465. package/dist/commands/inspect/task-show.d.mts +0 -1
  466. package/dist/commands/inspect/task-show.mjs +0 -108
  467. package/dist/commands/inspect/task-show.mjs.map +0 -1
  468. package/dist/commands/inspect/time-report.d.mts +0 -1
  469. package/dist/commands/inspect/time-report.mjs +0 -24
  470. package/dist/commands/inspect/time-report.mjs.map +0 -1
  471. package/dist/commands/inspect/usage-report.d.mts +0 -1
  472. package/dist/commands/inspect/usage-report.mjs +0 -23
  473. package/dist/commands/inspect/usage-report.mjs.map +0 -1
  474. package/dist/commands/inspect/user-response.d.mts +0 -1
  475. package/dist/commands/inspect/user-response.mjs +0 -35
  476. package/dist/commands/inspect/user-response.mjs.map +0 -1
  477. package/dist/commands/inspect/worker-liveness.d.mts +0 -1
  478. package/dist/commands/inspect/worker-liveness.mjs +0 -45
  479. package/dist/commands/inspect/worker-liveness.mjs.map +0 -1
  480. package/dist/commands/lifecycle/contract-check.d.mts +0 -1
  481. package/dist/commands/lifecycle/contract-check.mjs +0 -18
  482. package/dist/commands/lifecycle/contract-check.mjs.map +0 -1
  483. package/dist/commands/lifecycle/migrate.d.mts +0 -1
  484. package/dist/commands/lifecycle/migrate.mjs +0 -30
  485. package/dist/commands/lifecycle/migrate.mjs.map +0 -1
  486. package/dist/commands/lifecycle/model.d.mts +0 -1
  487. package/dist/commands/lifecycle/model.mjs +0 -22
  488. package/dist/commands/lifecycle/model.mjs.map +0 -1
  489. package/dist/commands/manager.d.mts +0 -3
  490. package/dist/commands/manager.mjs +0 -50
  491. package/dist/commands/manager.mjs.map +0 -1
  492. package/dist/commands/report/agent-activity.d.mts +0 -1
  493. package/dist/commands/report/agent-activity.mjs +0 -20
  494. package/dist/commands/report/agent-activity.mjs.map +0 -1
  495. package/dist/commands/report/approval-decision.d.mts +0 -1
  496. package/dist/commands/report/approval-decision.mjs +0 -21
  497. package/dist/commands/report/approval-decision.mjs.map +0 -1
  498. package/dist/commands/report/design-snapshot.d.mts +0 -1
  499. package/dist/commands/report/design-snapshot.mjs +0 -19
  500. package/dist/commands/report/design-snapshot.mjs.map +0 -1
  501. package/dist/commands/report/finalize.d.mts +0 -4
  502. package/dist/commands/report/finalize.mjs +0 -64
  503. package/dist/commands/report/finalize.mjs.map +0 -1
  504. package/dist/commands/report/inject-report-index.d.mts +0 -1
  505. package/dist/commands/report/inject-report-index.mjs +0 -21
  506. package/dist/commands/report/inject-report-index.mjs.map +0 -1
  507. package/dist/commands/report/render-final-report.d.mts +0 -1
  508. package/dist/commands/report/render-final-report.mjs +0 -23
  509. package/dist/commands/report/render-final-report.mjs.map +0 -1
  510. package/dist/commands/report/render-views.d.mts +0 -1
  511. package/dist/commands/report/render-views.mjs +0 -27
  512. package/dist/commands/report/render-views.mjs.map +0 -1
  513. package/dist/commands/report/translate.d.mts +0 -1
  514. package/dist/commands/report/translate.mjs +0 -33
  515. package/dist/commands/report/translate.mjs.map +0 -1
  516. package/runtime/bin/lib/okstra/tmux-pane.sh +0 -40
  517. package/runtime/bin/lib/okstra-ctl/cmd-batch.sh +0 -59
  518. package/runtime/bin/lib/okstra-ctl/cmd-list.sh +0 -35
  519. package/runtime/bin/lib/okstra-ctl/cmd-open.sh +0 -36
  520. package/runtime/bin/lib/okstra-ctl/cmd-projects.sh +0 -26
  521. package/runtime/bin/lib/okstra-ctl/cmd-reconcile.sh +0 -29
  522. package/runtime/bin/lib/okstra-ctl/cmd-reindex.sh +0 -38
  523. package/runtime/bin/lib/okstra-ctl/cmd-rerun.sh +0 -345
  524. package/runtime/bin/lib/okstra-ctl/cmd-show.sh +0 -27
  525. package/runtime/bin/lib/okstra-ctl/cmd-tail.sh +0 -92
  526. package/runtime/bin/lib/okstra-ctl/main.sh +0 -41
  527. package/runtime/bin/lib/okstra-ctl/prepare.sh +0 -31
  528. package/runtime/bin/lib/okstra-ctl/usage.sh +0 -23
  529. package/runtime/bin/okstra-central.sh +0 -152
  530. package/runtime/bin/okstra-incremental-carry.py +0 -10
  531. package/runtime/bin/okstra-incremental-scope.py +0 -10
  532. package/runtime/bin/okstra-team-reconcile.sh +0 -36
  533. package/runtime/python/okstra_ctl/agent_prompt_cli.py +0 -1156
  534. package/runtime/python/okstra_ctl/batch.py +0 -60
  535. package/runtime/python/okstra_ctl/clarification_items.py +0 -1050
  536. package/runtime/python/okstra_ctl/container_registry.py +0 -72
  537. package/runtime/python/okstra_ctl/improvement_assignment.py +0 -61
  538. package/runtime/python/okstra_ctl/resolver.py +0 -54
  539. package/runtime/python/okstra_ctl/team_reconcile.py +0 -275
  540. package/runtime/python/okstra_ctl/tmux.py +0 -134
  541. package/runtime/python/okstra_ctl/worktree.py +0 -1099
@@ -1,6 +1,6 @@
1
1
  """Read-side state accessors over okstra's on-disk authority files.
2
2
 
3
- Skills, okstra.sh, and okstra-ctl all read the same files through this module
3
+ Skills and okstra.sh read the same files through this module
4
4
  so identity / discovery values are derived from disk on every call instead of
5
5
  being passed via process environment. This keeps concurrent claude-code skill
6
6
  invocations isolated — each call reads the authoritative state at the moment
@@ -34,6 +34,13 @@ from .dirs import (
34
34
  TASK_MANIFEST_FILENAME,
35
35
  TASKS_RELATIVE,
36
36
  )
37
+ # 포인터 승격은 순수 값 연산이라 하위 계층이 소유한다. 예전에는 함수 본문에서
38
+ # okstra_ctl.next_phase 를 지연 import 했다 — okstra_ctl 패키지 __init__ 이
39
+ # 이 모듈의 slugify 를 도로 끌어와 순환이 됐기 때문이다.
40
+ from .phase_pointer import promote as promote_phase_pointer
41
+ # slug 규칙은 leaf 가 소유한다 — okstra_ctl 이 이 모듈을 거치지 않고 규칙만
42
+ # 가져갈 수 있어야 패키지 __init__ 을 통한 순환이 생기지 않는다.
43
+ from .slug import slugify # noqa: F401 — 재노출: 기존 호출부가 state 에서 끌어온다
37
44
 
38
45
 
39
46
  class StateError(Exception):
@@ -53,31 +60,6 @@ def _load_json(path: Path) -> Optional[dict]:
53
60
  raise StateError(f"failed to parse {path}: {exc}") from exc
54
61
 
55
62
 
56
- def _next_phase():
57
- """`okstra_ctl.next_phase` 를 호출 시점에 가져온다.
58
-
59
- 모듈 최상단에서 import 하면 순환이 된다 — `okstra_ctl/__init__.py` 가
60
- `.ids` 를 거쳐 이 모듈의 `slugify` 를 도로 import 하기 때문에, 여기서
61
- okstra_ctl 을 먼저 끌어오면 아직 정의되지 않은 이름을 찾다가 ImportError 로
62
- 죽는다. 같은 파일의 `_reconcile_task_root_best_effort` 도 같은 이유로 지연
63
- import 를 쓴다.
64
- """
65
- from okstra_ctl import next_phase
66
-
67
- return next_phase
68
-
69
-
70
- def slugify(value: str) -> str:
71
- """task-group / task-id segment 를 디스크 경로용 slug 로 정규화한다.
72
-
73
- 소문자화 + 알파넘 외 문자 → '-' 로 collapse.
74
- okstra.sh 의 path-resolve.sh slug 규칙과 일치해야 한다.
75
- """
76
- value = (value or "").lower()
77
- value = re.sub(r"[^a-z0-9]+", "-", value).strip("-")
78
- return value
79
-
80
-
81
63
  def read_task_catalog(project_root: Path) -> list[dict]:
82
64
  """project-level task-catalog.json 을 list[dict] 로 돌려준다.
83
65
 
@@ -117,7 +99,7 @@ def read_task_manifest(task_root: Path) -> Optional[dict]:
117
99
  workflow = manifest.get("workflow")
118
100
  if not isinstance(workflow, dict):
119
101
  return manifest
120
- workflow["nextRecommendedPhase"] = _next_phase().promote(
102
+ workflow["nextRecommendedPhase"] = promote_phase_pointer(
121
103
  workflow.get("nextRecommendedPhase")
122
104
  )
123
105
  # routingStatus 는 포인터 status 로 흡수됐다 — 승격과 같은 자리에서 지운다.
@@ -143,11 +125,20 @@ def read_task_key(task_root: Path) -> str:
143
125
 
144
126
 
145
127
  def _reconcile_task_root_best_effort(project_root: Path, task_root: Path) -> None:
128
+ """카탈로그가 가리키는 task-root 를 정본 산출물로 치유한다(best-effort).
129
+
130
+ import 를 함수 본문에 두는 이유는 순환이 아니다 — 순환은 없다. 하위 계층인
131
+ okstra_project 가 상위 계층 okstra_ctl 을 import 시점에 끌어오지 않게 하려는
132
+ 것이다. 이 치유는 읽기측 해석의 부가 동작이므로, 실패해도 해석 자체는
133
+ 계속돼야 한다.
134
+ """
146
135
  try:
147
136
  from okstra_ctl.implementation_outcome import reconcile_implementation_outcome
148
137
 
149
138
  reconcile_implementation_outcome(project_root, task_root)
150
139
  except Exception:
140
+ # expected-miss: 치유는 부가 동작이다. 여기서 던지면 단순 조회가
141
+ # 실패한다. 무엇이 치유되지 않았는지는 다음 run 의 정합성 검사가 잡는다.
151
142
  return
152
143
 
153
144
 
@@ -195,7 +186,7 @@ def _entry_with_manifest_state(entry: dict, task_root: Path) -> dict:
195
186
  raw = workflow.get("nextRecommendedPhase")
196
187
  if raw is None:
197
188
  raw = entry.get("nextRecommendedPhase")
198
- out["nextRecommendedPhase"] = _next_phase().promote(raw)
189
+ out["nextRecommendedPhase"] = promote_phase_pointer(raw)
199
190
  out.update(derived_task_status(manifest))
200
191
  return out
201
192
 
@@ -350,7 +341,7 @@ def list_project_tasks(
350
341
  def resolve_task_identity(project_root: Path, task_key: str) -> dict:
351
342
  """task-key 한 줄로 manifest + 경로를 한 번에 반환.
352
343
 
353
- skill / okstra.sh / okstra-ctl 모두가 이 함수를 호출해 같은 dict 를 받는다.
344
+ skill 과 okstra.sh 모두가 이 함수를 호출해 같은 dict 를 받는다.
354
345
  StateError: task root 또는 manifest 없음.
355
346
  """
356
347
  task_root = find_task_root(project_root, task_key)
@@ -379,7 +370,7 @@ def resolve_task_identity(project_root: Path, task_key: str) -> dict:
379
370
  "taskType": manifest.get("taskType") or "",
380
371
  "currentPhase": workflow.get("currentPhase") or "",
381
372
  "currentPhaseState": workflow.get("currentPhaseState") or "",
382
- "nextRecommendedPhase": _next_phase().promote(
373
+ "nextRecommendedPhase": promote_phase_pointer(
383
374
  workflow.get("nextRecommendedPhase")
384
375
  ),
385
376
  "lastCompletedPhase": workflow.get("lastCompletedPhase") or "",
@@ -410,7 +401,7 @@ def task_read_side_snapshot(project_root: Path, task_key: str) -> dict:
410
401
  "currentPhase": workflow.get("currentPhase"),
411
402
  "currentPhaseState": workflow.get("currentPhaseState"),
412
403
  "lastCompletedPhase": workflow.get("lastCompletedPhase"),
413
- "nextRecommendedPhase": _next_phase().promote(
404
+ "nextRecommendedPhase": promote_phase_pointer(
414
405
  workflow.get("nextRecommendedPhase")
415
406
  ),
416
407
  "phaseStates": workflow.get("phaseStates"),
@@ -421,202 +412,3 @@ def task_read_side_snapshot(project_root: Path, task_key: str) -> dict:
421
412
  "modelAssignments": manifest.get("modelAssignments"),
422
413
  "latestRunPath": manifest.get("latestRunPath"),
423
414
  }
424
-
425
-
426
- def stage_map_read_side_snapshot(project_root: Path, task_key: str) -> dict:
427
- """stage-map inspect 용 read-side view: Stage Map + done 처리된 stage 번호.
428
-
429
- Stage Map 파싱과 planning run-root 위치를 caller 에게 노출하지 않는다 —
430
- Node inspect 가 okstra_ctl 의 private 심볼을 import 하거나 `runs/` 경로를
431
- 직접 조립하지 않도록 하는 어댑터다.
432
- """
433
- from okstra_ctl.consumers import read_stage_consumer_state
434
- from okstra_ctl.paths import RunRef
435
- from okstra_ctl.stage_map import (
436
- PlanningDetail,
437
- StageMapError,
438
- load_planning_detail,
439
- load_task_stage_map,
440
- merge_planning_detail,
441
- )
442
-
443
- identity = resolve_task_identity(project_root, task_key)
444
- task_root = Path(identity["taskRoot"])
445
- try:
446
- stage_snapshot = load_task_stage_map(task_root, identity["manifest"])
447
- detail = (
448
- load_planning_detail(Path(stage_snapshot.source_plan_path))
449
- if stage_snapshot.source_plan_path
450
- else PlanningDetail({}, {})
451
- )
452
- except StageMapError as exc:
453
- raise StateError(str(exc), stage=exc.code) from exc
454
- plan_run_root = RunRef.from_task_root(
455
- task_root, "implementation-planning"
456
- ).run_dir
457
- done: list[int] = []
458
- if plan_run_root.is_dir():
459
- done = sorted(
460
- read_stage_consumer_state(
461
- plan_run_root, recover_from_carry=True
462
- ).done_stages
463
- )
464
- return {
465
- "taskKey": identity["taskKey"],
466
- "taskRoot": identity["taskRoot"],
467
- "state": stage_snapshot.state,
468
- "sourcePlanPath": stage_snapshot.source_plan_path,
469
- "stages": merge_planning_detail(stage_snapshot.stages, detail),
470
- "doneStages": done,
471
- "planning": detail.task_narratives,
472
- }
473
-
474
-
475
- def code_review_target_snapshot(
476
- project_root: Path, task_key: str, stage: int
477
- ) -> dict:
478
- """stage 코드 리뷰가 무엇을 읽고 결과를 어디에 쓰는지 해소한다.
479
-
480
- stage_map_read_side_snapshot 과 같은 결: caller 는 registry 행도, run-root
481
- 레이아웃도, stage base 규칙도 보지 않는다. stage_targets 의
482
- StageTargetError 도 StateError 로 바꿔 던진다 — caller 가 그 예외를 잡으려고
483
- okstra_ctl 을 import 하지 않도록.
484
-
485
- base 는 stage worktree 를 만들 때 registry 행에 적힌 `base_ref` 다 — 그
486
- stage 가 실제로 갈라져 나온 커밋. 리뷰 시점에 규칙으로 다시 계산하면
487
- 표류한다: 다중의존 stage 는 오늘의 task-key worktree HEAD 를 받게 되어
488
- whole-task final-verification 이 stage 들을 병합한 뒤에는 base..head 구간이
489
- 비거나 뒤집히고, 단일의존 stage 는 선행 stage 가 재실행되면 어긋난다.
490
- """
491
- from okstra_ctl import code_review_paths, worktree_registry
492
- from okstra_ctl.stage_map import StageMapError, load_task_stage_map
493
-
494
- identity = resolve_task_identity(project_root, task_key)
495
- task_root = Path(identity["taskRoot"])
496
- try:
497
- stage_snapshot = load_task_stage_map(task_root, identity["manifest"])
498
- except StageMapError as exc:
499
- raise StateError(str(exc), stage=exc.code) from exc
500
- if stage_snapshot.state != "ready":
501
- raise StateError(
502
- "implementation-planning Stage Map is missing",
503
- stage="missing",
504
- )
505
- stages = stage_snapshot.stages
506
- selected = next((s for s in stages if s["stage_number"] == stage), None)
507
- if selected is None:
508
- raise StateError(
509
- f"stage {stage} is not in the Stage Map for {task_key}",
510
- stage="stage_missing",
511
- )
512
-
513
- coords = (identity["projectId"], identity["taskGroup"], identity["taskId"])
514
- stage_row = worktree_registry.get_stage_row(*coords, stage) or {}
515
- worktree_view = _stage_worktree_view(project_root, stage_row, stage)
516
- base_commit = stage_row.get("base_ref") or _legacy_stage_base_commit(
517
- identity, stages, selected
518
- )
519
- review_path, round_no = code_review_paths.next_stage_review(
520
- code_review_paths.stage_review_dir(
521
- project_root, identity["taskGroup"], identity["taskId"]
522
- ),
523
- stage,
524
- )
525
- return {
526
- "mode": "stage",
527
- "taskKey": identity["taskKey"],
528
- "taskRoot": identity["taskRoot"],
529
- "stage": stage,
530
- **worktree_view,
531
- "baseCommit": base_commit,
532
- "reviewPath": str(review_path),
533
- "round": round_no,
534
- }
535
-
536
-
537
- def _legacy_stage_base_commit(
538
- identity: dict, stages: list[dict], selected: dict
539
- ) -> str:
540
- """`base_ref` 를 남기지 않은 옛 registry 행의 base 를 규칙에서 되살린다.
541
-
542
- provision 이 base_ref 를 기록하기 전에 만들어진 stage 행에만 쓰인다. 규칙
543
- 자체는 stage_targets 가 소유하며 여기서 다시 유도하지 않는다.
544
- """
545
- from okstra_ctl import stage_targets, worktree_registry
546
- from okstra_ctl.paths import RunRef
547
-
548
- coords = (identity["projectId"], identity["taskGroup"], identity["taskId"])
549
- plan_run_root = RunRef.from_task_root(
550
- Path(identity["taskRoot"]), "implementation-planning"
551
- ).run_dir
552
- lifecycle = stage_targets.read_stage_lifecycle_snapshot(
553
- stages, plan_run_root, recover_from_carry=True
554
- )
555
- # The anchor and the multi-dep candidate base must come from the task-key
556
- # worktree HEAD, where stage work accumulates — not from the invocation
557
- # cwd, which may sit on an older or unrelated commit.
558
- task_entry = worktree_registry.lookup(*coords)
559
- task_worktree = Path(
560
- (task_entry.worktree_path if task_entry else "") or identity["projectRoot"]
561
- )
562
- anchor = worktree_registry.get_implementation_base(*coords) or ""
563
- try:
564
- return stage_targets.resolve_stage_base_commit(
565
- selected,
566
- lifecycle.done_rows,
567
- anchor_base_commit=anchor,
568
- candidate_base=_git_out(task_worktree, "rev-parse", "HEAD"),
569
- project_root=task_worktree,
570
- plan_run_root=plan_run_root,
571
- )
572
- except stage_targets.StageTargetError as exc:
573
- raise StateError(
574
- f"stage {selected['stage_number']} has no recorded base_ref and its "
575
- f"base could not be derived: {exc}",
576
- stage="stage_base_unresolved",
577
- ) from exc
578
-
579
-
580
- def _stage_worktree_view(project_root: Path, stage_row: dict, stage: int) -> dict:
581
- """한 stage 의 worktree 경로 / 브랜치 / head 커밋.
582
-
583
- 철거된 stage worktree 는 오류가 아니다: whole-task final-verification 이
584
- 디렉터리를 지워도 브랜치는 남고, 리뷰가 필요한 커밋은 그 브랜치에 그대로
585
- 있다.
586
- """
587
- branch = stage_row.get("branch") or ""
588
- if not branch:
589
- raise StateError(
590
- f"stage {stage} has no registry branch — review it in branch mode",
591
- stage="stage_branch_missing",
592
- )
593
- worktree_path = ""
594
- if stage_row.get("status") == "active" and stage_row.get("worktree_path"):
595
- candidate = Path(stage_row["worktree_path"])
596
- if candidate.is_dir():
597
- worktree_path = str(candidate)
598
- head_source = Path(worktree_path) if worktree_path else Path(project_root)
599
- head_ref = "HEAD" if worktree_path else branch
600
- head_commit = _git_out(head_source, "rev-parse", head_ref)
601
- if not head_commit:
602
- raise StateError(
603
- f"stage {stage} head could not be resolved: branch {branch!r} is "
604
- f"not readable from {head_source}",
605
- stage="stage_head_unresolved",
606
- )
607
- return {
608
- "worktreePath": worktree_path,
609
- "branch": branch,
610
- "headCommit": head_commit,
611
- }
612
-
613
-
614
- def _git_out(repo: Path, *args: str) -> str:
615
- """git 호출의 stdout. 실패는 빈 문자열 — 호출자가 부재를 판단한다."""
616
- result = subprocess.run(
617
- ["git", "-C", str(repo), *args],
618
- capture_output=True,
619
- text=True,
620
- check=False,
621
- )
622
- return result.stdout.strip() if result.returncode == 0 else ""
@@ -2,6 +2,7 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import json
5
+ import sys
5
6
  import re
6
7
  from datetime import datetime
7
8
  from pathlib import Path
@@ -158,8 +159,15 @@ def _advance_usage_scan(jsonl_path: Path, usage_state: dict) -> dict:
158
159
  if ev:
159
160
  tail_events.append(ev)
160
161
  break
161
- except OSError:
162
+ except FileNotFoundError:
163
+ # expected-miss: 세션 jsonl 은 열거된 뒤 사라질 수 있고(정리·회전), 아직
164
+ # 한 줄도 안 쓴 세션은 파일 자체가 없다. 읽을 것이 없는 것이 정상이다.
162
165
  pass
166
+ except OSError as exc:
167
+ # 파일은 있는데 못 읽은 것은 다르다. 읽다 만 상태로 반환하면 토큰 집계가
168
+ # 실제보다 적게 나오는데 리포트에는 정상 수치처럼 찍힌다.
169
+ print(f"token-usage: partial read of {jsonl_path} ({exc})",
170
+ file=sys.stderr)
163
171
  usage_state.update(offset=offset, events=events,
164
172
  agentName=agent_name, model=model)
165
173
  return {"events": events + tail_events,
@@ -14,7 +14,9 @@ from .report import (
14
14
 
15
15
 
16
16
  def main() -> int:
17
- parser = argparse.ArgumentParser(description=__doc__)
17
+ parser = argparse.ArgumentParser(
18
+ prog="okstra token-usage",
19
+ description=__doc__)
18
20
  parser.add_argument(
19
21
  "team_state",
20
22
  type=Path,
@@ -2,6 +2,7 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import json
5
+ import sys
5
6
  import os
6
7
  from datetime import datetime, timezone
7
8
  from pathlib import Path
@@ -113,22 +114,76 @@ def _aggregate_totals(items: list[dict]) -> dict:
113
114
  def run_artifact_suffix(team_state_path: Path) -> str | None:
114
115
  """``team-state-<task-type>-<seq>.json`` → ``<task-type>-<seq>``.
115
116
 
116
- 이 접미사로 *같은 run* 의 run-manifest / status 를 정확히 짚는다. task 디렉토리
117
- 한 곳에 여러 run(재시도·이전 phase·레거시 타임스탬프)의 산출물이 섞여 있어,
118
- glob 으로 아무거나 집으면 엉뚱한 run 의 시각을 쓰게 된다(관측: 가장 오래된
119
- 레거시 manifest 의 createdAt 을 집어 윈도우가 한 달로 벌어짐)."""
117
+ task 디렉토리 한 곳에 여러 run(재시도·이전 phase·레거시 타임스탬프)의 산출물이
118
+ 섞여 있어, glob 으로 아무거나 집으면 엉뚱한 run 의 시각을 쓰게 된다(관측: 가장
119
+ 오래된 레거시 manifest 의 createdAt 을 집어 윈도우가 한 달로 벌어짐).
120
+
121
+ **접미사가 곧 매니페스트 이름은 아니다.** okstra 는 카테고리별로 seq 를 따로
122
+ 매기므로 한 run 이 `manifests: 025` 와 `state: 023` 을 동시에 가질 수 있다.
123
+ run-manifest 는 `_run_manifest_for_team_state` 가 매니페스트의 `teamStatePath`
124
+ 역참조로 짚는다. status 산출물은 team-state 와 같은 카테고리라 이 접미사로
125
+ 계속 짚는다."""
120
126
  name = team_state_path.name
121
127
  if not (name.startswith("team-state-") and name.endswith(".json")):
122
128
  return None
123
129
  return name[len("team-state-"):-len(".json")]
124
130
 
125
131
 
126
- def _run_manifest_created_at(run_dir: Path, suffix: str) -> str | None:
127
- p = run_dir / "manifests" / f"run-manifest-{suffix}.json"
132
+ def _read_json_or_none(path: Path) -> dict | None:
128
133
  try:
129
- return json.loads(p.read_text()).get("createdAt")
134
+ data = json.loads(path.read_text())
130
135
  except (OSError, json.JSONDecodeError):
131
136
  return None
137
+ return data if isinstance(data, dict) else None
138
+
139
+
140
+ def _run_manifest_for_team_state(
141
+ run_dir: Path, suffix: str, team_state_path: Path
142
+ ) -> dict | None:
143
+ """이 team-state 가 속한 run 의 매니페스트.
144
+
145
+ 접미사가 곧 매니페스트 이름이라는 전제는 카테고리별 seq 가 갈리는 순간
146
+ 깨진다. 실측(2026-08-27, fontsninja-nlpvibe `nestjs-migration-…`): 한 run 의
147
+ `manifests` 가 025, `state`·`prompts`·`worker-results` 가 023, `reports` 가
148
+ 017 이었다. team-state 이름에서 뽑은 `023` 으로 매니페스트를 집으면 **12시간
149
+ 전 run** 의 `createdAt`(04:45:34Z) 이 창의 시작이 되고, 그 사이 같은 세션이
150
+ 돈 이전 run 들이 통째로 창에 들어온다 — `relax_start=False` 가 막으려던 바로
151
+ 그 오탐이다(그 run 에서 로스터에 없는 워커 5명이 이 run 의 위반으로 보고됐다).
152
+
153
+ 매니페스트는 자기 team-state 를 `teamStatePath` 에 적으므로 그것으로 맞춘다.
154
+ 맞는 것이 없으면 접미사 방식으로 돌아간다 — 그 필드가 없던 옛 매니페스트다.
155
+ """
156
+ direct = run_dir / "manifests" / f"run-manifest-{suffix}.json"
157
+ # `Path.glob` 는 디렉터리가 없어도 빈 결과를 낸다 — 감쌀 예외가 없다.
158
+ candidates = [
159
+ direct,
160
+ *sorted((run_dir / "manifests").glob("run-manifest-*.json")),
161
+ ]
162
+ for path in candidates:
163
+ data = _read_json_or_none(path)
164
+ if data is None:
165
+ continue
166
+ recorded = str(data.get("teamStatePath") or "")
167
+ if recorded and Path(recorded).name == team_state_path.name:
168
+ return data
169
+ return _read_json_or_none(direct)
170
+
171
+
172
+ def _created_at_by_suffix(run_dir: Path, suffix: str) -> str | None:
173
+ """접미사가 곧 매니페스트 이름인 조회.
174
+
175
+ **다른** run 을 접미사로 지목할 때만 쓴다(`_previous_run_end`). 이번 run 은
176
+ team-state 로 맞춰야 한다 — 두 접미사가 갈리는 run 이 실재한다.
177
+ """
178
+ data = _read_json_or_none(run_dir / "manifests" / f"run-manifest-{suffix}.json")
179
+ return None if data is None else data.get("createdAt")
180
+
181
+
182
+ def _run_manifest_created_at(
183
+ run_dir: Path, suffix: str, team_state_path: Path
184
+ ) -> str | None:
185
+ data = _run_manifest_for_team_state(run_dir, suffix, team_state_path)
186
+ return None if data is None else data.get("createdAt")
132
187
 
133
188
 
134
189
  def _run_end_estimate(run_dir: Path, suffix: str) -> str | None:
@@ -233,9 +288,15 @@ def _previous_run_end(run_dir: Path, suffix: str) -> str | None:
233
288
  ended_at = prev_state.get("runEndedAt")
234
289
  if ended_at:
235
290
  return str(ended_at)
236
- except (OSError, json.JSONDecodeError):
291
+ except OSError:
292
+ # expected-miss: 직전 run 이 team-state 를 남기지 않고 끝났으면 파일이
293
+ # 없다. 아래 두 추정 경로가 바로 그 경우를 위한 폴백이다.
237
294
  pass
238
- return _run_end_estimate(run_dir, prev) or _run_manifest_created_at(run_dir, prev)
295
+ except json.JSONDecodeError as exc:
296
+ # 파일은 있는데 깨진 것은 폴백이 상정한 상황이 아니다.
297
+ print(f"token-usage: team-state for the previous run ({prev}) is not "
298
+ f"valid JSON, estimating instead ({exc})", file=sys.stderr)
299
+ return _run_end_estimate(run_dir, prev) or _created_at_by_suffix(run_dir, prev)
239
300
 
240
301
 
241
302
  def resolve_run_window(
@@ -271,7 +332,7 @@ def resolve_run_window(
271
332
  if not suffix:
272
333
  return None, None
273
334
  run_dir = team_state_path.parent.parent
274
- since = _run_manifest_created_at(run_dir, suffix)
335
+ since = _run_manifest_created_at(run_dir, suffix, team_state_path)
275
336
  if relax_start:
276
337
  earliest = _earliest_lead_session_ts(state, team_state_path)
277
338
  if earliest and (since is None or earliest < since):
@@ -92,4 +92,6 @@ def save_cache(jsonl_path: Path, cache: dict) -> None:
92
92
  tmp.write_text(json.dumps(cache, ensure_ascii=False, separators=(",", ":")))
93
93
  os.replace(tmp, p)
94
94
  except OSError:
95
+ # expected-miss: 이 캐시는 jsonl 에서 언제든 다시 만들 수 있는 파생물이다.
96
+ # 저장에 실패해도 집계 결과가 달라지지 않으므로 없는 것이 정상 분기다.
95
97
  pass
@@ -11,7 +11,7 @@ import os
11
11
  from datetime import datetime, timezone
12
12
  from pathlib import Path
13
13
  from typing import Mapping
14
- from urllib.parse import quote, unquote
14
+ from urllib.parse import unquote
15
15
 
16
16
  from .jsonl_io import iter_jsonl
17
17
  from .paths import ts_in_window
@@ -132,10 +132,6 @@ def find_grok_sessions(
132
132
  return sorted(matches)
133
133
 
134
134
 
135
- def grok_session_dir_name(cwd: Path) -> str:
136
- return quote(str(cwd), safe="")
137
-
138
-
139
135
  def grok_session_is_non_interactive(updates_path: Path) -> bool:
140
136
  """워커 래퍼 세션은 prompt_context.is_non_interactive 가 true 이다."""
141
137
  context_path = updates_path.parent / "prompt_context.json"
@@ -72,3 +72,29 @@ def claude_project_dir(cwd: Path, projects_root: Path | None = None) -> Path:
72
72
  # `projects_root` 는 테스트/진단용 주입 시드 — 기본은 실제 ~/.claude/projects.
73
73
  encoded = "-" + str(cwd).strip("/").replace("/", "-")
74
74
  return (projects_root or CLAUDE_PROJECTS) / encoded
75
+
76
+
77
+ def find_session_jsonl(
78
+ session_id: str, cwd: Path, projects_root: Path | None = None
79
+ ) -> Path | None:
80
+ """세션 id 하나의 transcript 경로. 못 찾으면 None.
81
+
82
+ 프로젝트로 인코딩된 디렉터리를 먼저 보고, 없으면 projects_root 아래의
83
+ 모든 인코딩 디렉터리를 훑는다. 리드 세션의 cwd 가 대상 프로젝트 루트와
84
+ 다를 수 있기 때문이다 — `okstra preflight --cwd <다른 프로젝트>` 는
85
+ 지원되는 실행 방식이고, 그때 transcript 는 **리드 세션의 cwd** 로 인코딩된
86
+ 디렉터리에 놓인다. 프로젝트 디렉터리만 보면 실재하는 세션을 없다고 답한다.
87
+
88
+ 세션 id 는 전역 유일하므로 디렉터리를 넓혀도 다른 세션을 잘못 집지 않는다.
89
+ 파일 내용을 읽지 않는 경로 조회다.
90
+ """
91
+ if not session_id:
92
+ return None
93
+ direct = claude_project_dir(cwd, projects_root) / f"{session_id}.jsonl"
94
+ if direct.is_file():
95
+ return direct
96
+ root = projects_root or CLAUDE_PROJECTS
97
+ try:
98
+ return next(iter(sorted(root.glob(f"*/{session_id}.jsonl"))), None)
99
+ except OSError:
100
+ return None
@@ -8,14 +8,15 @@ provider:
8
8
  `claude-haiku-4-5-*`, `claude-3-5-sonnet-*`, `claude-3-5-haiku-*`,
9
9
  `claude-3-opus-*`, `claude-3-haiku-*`.
10
10
  * OpenAI / Codex — `gpt-5*`, `gpt-4o*`, `gpt-4*`.
11
- * Google / Gemini — `gemini-3.6-flash*`, `gemini-3.5-flash*`, `gemini-3.1-pro*`, `gemini-3-flash*`,
11
+ * Google / Gemini — `gemini-3.7-flash*`, `gemini-3.6-flash*`, `gemini-3.5-flash*`, `gemini-3.1-pro*`, `gemini-3-flash*`,
12
12
  `gemini-2.5-pro*`, `gemini-2.5-flash*`, `gemini-2.0-flash*`.
13
13
 
14
14
  Matching prefers the longest key that is a substring of the model id (most
15
15
  specific wins), so table ordering does not affect the result. Update when
16
16
  providers change list pricing.
17
17
 
18
- Sources (last verified 2026-08-03, public list prices, USD per 1M tokens):
18
+ Sources (last verified 2026-08-03 — Google table re-verified 2026-08-26,
19
+ public list prices, USD per 1M tokens):
19
20
  * Anthropic: https://www.anthropic.com/pricing
20
21
  * OpenAI: https://openai.com/api/pricing
21
22
  * Google: https://ai.google.dev/gemini-api/docs/pricing
@@ -74,9 +75,10 @@ CLAUDE_PRICING = {
74
75
  # Anthropic 1h ephemeral cache_creation multiplier on the base input rate.
75
76
  CLAUDE_CACHE_CREATE_1H_MULT = 2.0
76
77
 
77
- # Legacy / non-selectable codex prices. Selectable-model prices (the aliases the
78
- # picker offers) live in the model catalog and are merged in below via
79
- # `catalog_pricing("codex")`. Substring match order is now longest-key-first
78
+ # Codex prices for slugs the provider catalog no longer offers. Prices for
79
+ # registered models -- selectable or hidden -- live in the model catalog and are
80
+ # merged in below via `catalog_pricing("codex")`. Substring match order is
81
+ # longest-key-first
80
82
  # (see `_match_pricing`), so this dict's ordering no longer affects matching.
81
83
  # For models with no published cached-input rate (o1-pro, o3-pro), cached is set
82
84
  # equal to input as a conservative no-discount default.
@@ -84,6 +86,10 @@ _LEGACY_CODEX_PRICING = {
84
86
  # model substring -> (input USD/1M, cached_input USD/1M, output USD/1M).
85
87
 
86
88
  # GPT-5 series.
89
+ # gpt-5.6 was dropped from the provider catalog; its rate stays so past runs
90
+ # keep pricing. Substring matching means sol / terra / luna resolve here too
91
+ # -- the 5.6 family rate, not a per-variant price we have a source for.
92
+ "gpt-5.6": (5.00, 0.50, 30.0),
87
93
  "gpt-5.2-pro": (21.0, 2.10, 168.0),
88
94
  "gpt-5.1": (1.25, 0.125, 10.0),
89
95
  "gpt-5-mini": (0.25, 0.025, 2.00),
@@ -126,8 +132,14 @@ GEMINI_PRICING = {
126
132
  # forms appear in the wild, so include both for the new 3.x families.
127
133
 
128
134
  # Gemini 3 series.
129
- "3.6-flash": (1.50, 7.50),
130
- "3-6-flash": (1.50, 7.50),
135
+ # 3.7 Flash 와 3.6 Flash 는 2026-12-31 까지 도입 요율 $0.75/$3.75 이고
136
+ # 2027-01-01 에 표준 요율 $1.50/$7.50 으로 오른다(2026-08-26 확인). 이 표에는
137
+ # 시점 개념이 없으므로 지금 청구되는 값을 담는다 — 그날 이후 두 계열을
138
+ # (1.50, 7.50) 으로 갱신해야 한다.
139
+ "3.7-flash": (0.75, 3.75),
140
+ "3-7-flash": (0.75, 3.75),
141
+ "3.6-flash": (0.75, 3.75),
142
+ "3-6-flash": (0.75, 3.75),
131
143
  "3.5-flash": (1.50, 9.00),
132
144
  "3-5-flash": (1.50, 9.00),
133
145
  "3.1-pro": (2.00, 12.0),
@@ -31,6 +31,11 @@
31
31
  "items": { "type": "string", "pattern": "\\S" }
32
32
  },
33
33
  "originEvidence": { "type": "string", "pattern": "\\S" },
34
+ "duplicateOf": {
35
+ "type": "string",
36
+ "pattern": "\\S",
37
+ "description": "The existing finding id (`F-NNN`) this candidate overlaps. A declared duplicate is recorded and skips gap verification entirely; restating the finding instead costs one verification round to reject it. The shape stays free-form because `findingId` is free-form in convergence-groups-v2.0; `okstra convergence apply-critic-gaps` owns the check that the id names a finding the state actually carries."
38
+ },
34
39
  "severity": { "enum": ["critical", "major", "minor"] },
35
40
  "evidenceArtifacts": {
36
41
  "type": "array",
@@ -236,10 +236,7 @@
236
236
  "dutyId",
237
237
  "dispatchKind",
238
238
  "round",
239
- "inputDigest",
240
- "writePolicy",
241
- "writePolicyDigest",
242
- "writeEnforcement"
239
+ "inputDigest"
243
240
  ],
244
241
  "properties": {
245
242
  "invocationRef": {"type": "string", "pattern": "\\S"},
@@ -250,10 +247,7 @@
250
247
  "dutyId": {"type": "string", "pattern": "\\S"},
251
248
  "dispatchKind": {"type": "string", "pattern": "\\S"},
252
249
  "round": {"type": "integer", "minimum": 1},
253
- "inputDigest": {"$ref": "#/$defs/Digest"},
254
- "writePolicy": {"$ref": "#/$defs/WritePolicy"},
255
- "writePolicyDigest": {"$ref": "#/$defs/Digest"},
256
- "writeEnforcement": {"$ref": "#/$defs/WriteEnforcement"}
250
+ "inputDigest": {"$ref": "#/$defs/Digest"}
257
251
  },
258
252
  "additionalProperties": false
259
253
  },
@@ -268,7 +262,10 @@
268
262
  "resultPath",
269
263
  "errorPath",
270
264
  "changeSummary",
271
- "evidenceSealRef"
265
+ "evidenceSealRef",
266
+ "writePolicy",
267
+ "writePolicyDigest",
268
+ "writeEnforcement"
272
269
  ],
273
270
  "properties": {
274
271
  "invocationRef": {"type": "string", "pattern": "\\S"},
@@ -279,7 +276,10 @@
279
276
  "resultPath": {"type": ["string", "null"]},
280
277
  "errorPath": {"type": ["string", "null"]},
281
278
  "changeSummary": {"type": "object"},
282
- "evidenceSealRef": {"type": ["string", "null"]}
279
+ "evidenceSealRef": {"type": ["string", "null"]},
280
+ "writePolicy": {"$ref": "#/$defs/WritePolicy"},
281
+ "writePolicyDigest": {"$ref": "#/$defs/Digest"},
282
+ "writeEnforcement": {"$ref": "#/$defs/WriteEnforcement"}
283
283
  },
284
284
  "additionalProperties": false
285
285
  },