okstra 0.186.7 → 0.188.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (546) hide show
  1. package/README.md +1 -1
  2. package/dist/cli-registry.d.mts +88 -1
  3. package/dist/cli-registry.mjs +68 -111
  4. package/dist/cli-registry.mjs.map +1 -1
  5. package/dist/commands/execute/render-bundle.mjs +0 -1
  6. package/dist/commands/execute/render-bundle.mjs.map +1 -1
  7. package/dist/commands/execute/run.mjs +8 -3
  8. package/dist/commands/execute/run.mjs.map +1 -1
  9. package/dist/commands/lifecycle/check-project.mjs +1 -14
  10. package/dist/commands/lifecycle/check-project.mjs.map +1 -1
  11. package/dist/commands/lifecycle/config.mjs +38 -40
  12. package/dist/commands/lifecycle/config.mjs.map +1 -1
  13. package/dist/commands/lifecycle/doctor.d.mts +22 -7
  14. package/dist/commands/lifecycle/doctor.mjs +77 -49
  15. package/dist/commands/lifecycle/doctor.mjs.map +1 -1
  16. package/dist/commands/lifecycle/install.d.mts +12 -10
  17. package/dist/commands/lifecycle/install.mjs +107 -40
  18. package/dist/commands/lifecycle/install.mjs.map +1 -1
  19. package/dist/commands/lifecycle/paths.mjs +8 -3
  20. package/dist/commands/lifecycle/paths.mjs.map +1 -1
  21. package/dist/commands/lifecycle/preflight.mjs +2 -1
  22. package/dist/commands/lifecycle/preflight.mjs.map +1 -1
  23. package/dist/commands/lifecycle/setup.mjs +22 -36
  24. package/dist/commands/lifecycle/setup.mjs.map +1 -1
  25. package/dist/commands/lifecycle/uninstall.d.mts +4 -2
  26. package/dist/commands/lifecycle/uninstall.mjs +59 -15
  27. package/dist/commands/lifecycle/uninstall.mjs.map +1 -1
  28. package/dist/commands/memory/memory.mjs +7 -1
  29. package/dist/commands/memory/memory.mjs.map +1 -1
  30. package/dist/lib/helper-scripts.d.mts +1 -1
  31. package/dist/lib/helper-scripts.mjs +10 -18
  32. package/dist/lib/helper-scripts.mjs.map +1 -1
  33. package/dist/lib/host-config.d.mts +72 -0
  34. package/dist/lib/host-config.mjs +404 -0
  35. package/dist/lib/host-config.mjs.map +1 -0
  36. package/dist/lib/host-registry-client.d.mts +2 -2
  37. package/dist/lib/host-registry-client.mjs +0 -3
  38. package/dist/lib/host-registry-client.mjs.map +1 -1
  39. package/dist/lib/install-assets.d.mts +1 -0
  40. package/dist/lib/install-assets.mjs +4 -0
  41. package/dist/lib/install-assets.mjs.map +1 -1
  42. package/dist/lib/proc.d.mts +2 -0
  43. package/dist/lib/proc.mjs +12 -0
  44. package/dist/lib/proc.mjs.map +1 -1
  45. package/dist/lib/python-command.d.mts +2 -0
  46. package/dist/lib/python-command.mjs +52 -0
  47. package/dist/lib/python-command.mjs.map +1 -0
  48. package/dist/lib/python-helper.d.mts +2 -5
  49. package/dist/lib/python-helper.mjs +3 -52
  50. package/dist/lib/python-helper.mjs.map +1 -1
  51. package/dist/lib/runtime-payload.d.mts +23 -0
  52. package/dist/lib/runtime-payload.mjs +57 -0
  53. package/dist/lib/runtime-payload.mjs.map +1 -0
  54. package/dist/lib/types.d.mts +24 -13
  55. package/docs/architecture/storage-model.md +21 -5
  56. package/docs/architecture.md +39 -38
  57. package/docs/cli.md +55 -76
  58. package/docs/coding-rules.md +295 -0
  59. package/docs/container.md +10 -36
  60. package/docs/contributor-change-matrix.md +1 -1
  61. package/docs/for-ai/skills/okstra-code-review.md +0 -1
  62. package/docs/for-ai/skills/okstra-container-build.md +8 -41
  63. package/docs/for-ai/skills/okstra-inspect.md +4 -4
  64. package/docs/for-ai/skills/okstra-manager.md +2 -2
  65. package/docs/for-ai/skills/okstra-rollup.md +0 -1
  66. package/docs/for-ai/skills/okstra-run.md +3 -3
  67. package/docs/for-ai/skills/okstra-user-response.md +0 -1
  68. package/docs/performance-improvement-plan-v2.md +1 -1
  69. package/docs/project-structure-overview.md +62 -62
  70. package/docs/task-process/README.md +3 -3
  71. package/docs/task-process/common-flow.md +1 -1
  72. package/docs/task-process/error-analysis.md +3 -3
  73. package/docs/task-process/implementation-planning.md +3 -1
  74. package/docs/task-process/release-handoff.md +1 -1
  75. package/package.json +3 -2
  76. package/runtime/BUILD.json +2 -2
  77. package/runtime/agents/workers/claude-worker.md +6 -11
  78. package/runtime/agents/workers/report-writer-worker.md +1 -1
  79. package/runtime/bin/lib/okstra/cli.sh +4 -0
  80. package/runtime/bin/lib/okstra/globals.sh +2 -0
  81. package/runtime/bin/lib/okstra/interactive.sh +26 -173
  82. package/runtime/bin/lib/okstra/project-resolver.sh +23 -61
  83. package/runtime/bin/lib/okstra/usage.sh +3 -3
  84. package/runtime/bin/okstra-compact-reminder.sh +1 -5
  85. package/runtime/bin/okstra-error-log.py +19 -2
  86. package/runtime/bin/okstra-import-check.py +26 -0
  87. package/runtime/bin/okstra-inject-report-index.py +5 -4
  88. package/runtime/bin/okstra-provider-exec.py +22 -17
  89. package/runtime/bin/okstra-render-final-report.py +16 -6
  90. package/runtime/bin/okstra-render-report-views.py +47 -22
  91. package/runtime/bin/okstra-report-translate.py +30 -18
  92. package/runtime/bin/okstra-spawn-followups.py +4 -0
  93. package/runtime/bin/okstra-token-usage.py +3 -0
  94. package/runtime/bin/okstra.sh +4 -2
  95. package/runtime/bin/okstra_bootstrap.py +58 -0
  96. package/runtime/prompts/coding-preflight/overview.md +1 -1
  97. package/runtime/prompts/duties/planning-worker.md +1 -1
  98. package/runtime/prompts/launch.template.md +35 -9
  99. package/runtime/prompts/lead/adapters/cmux.md +3 -3
  100. package/runtime/prompts/lead/convergence.md +99 -88
  101. package/runtime/prompts/lead/okstra-lead-contract.md +63 -12
  102. package/runtime/prompts/lead/plan-body-verification.md +65 -46
  103. package/runtime/prompts/lead/report-writer.md +35 -5
  104. package/runtime/prompts/lead/team-contract.md +4 -1
  105. package/runtime/prompts/profiles/_clarification-recommendation.md +2 -1
  106. package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
  107. package/runtime/prompts/profiles/_common-contract.md +3 -3
  108. package/runtime/prompts/profiles/_coverage-critic.md +1 -1
  109. package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
  110. package/runtime/prompts/profiles/_implementation-executor.md +3 -1
  111. package/runtime/prompts/profiles/_implementation-verifier.md +16 -2
  112. package/runtime/prompts/profiles/error-analysis.md +4 -3
  113. package/runtime/prompts/profiles/final-verification.md +2 -2
  114. package/runtime/prompts/profiles/implementation-planning.md +22 -17
  115. package/runtime/prompts/profiles/implementation.md +3 -2
  116. package/runtime/prompts/profiles/requirements-discovery.md +2 -1
  117. package/runtime/prompts/wizard/prompts.ko.json +29 -8
  118. package/runtime/python/okstra_ctl/__init__.py +6 -27
  119. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +4 -5
  120. package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +21 -2
  121. package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +3 -12
  122. package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +63 -22
  123. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +20 -21
  124. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +37 -20
  125. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +86 -14
  126. package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +12 -12
  127. package/runtime/python/okstra_ctl/adapters/runtime/cmux.py +4 -7
  128. package/runtime/python/okstra_ctl/agent/__init__.py +1 -0
  129. package/runtime/python/okstra_ctl/{agent_activity.py → agent/activity.py} +16 -2
  130. package/runtime/python/okstra_ctl/{agent_invocation.py → agent/invocation.py} +34 -5
  131. package/runtime/python/okstra_ctl/agent/prompt_cli/__init__.py +19 -0
  132. package/runtime/python/okstra_ctl/agent/prompt_cli/__main__.py +11 -0
  133. package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +228 -0
  134. package/runtime/python/okstra_ctl/agent/prompt_cli/dynamic_verifier.py +155 -0
  135. package/runtime/python/okstra_ctl/agent/prompt_cli/emit.py +66 -0
  136. package/runtime/python/okstra_ctl/agent/prompt_cli/inputs.py +138 -0
  137. package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +605 -0
  138. package/runtime/python/okstra_ctl/agent/prompt_cli/results.py +197 -0
  139. package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +107 -0
  140. package/runtime/python/okstra_ctl/analysis_packet.py +47 -4
  141. package/runtime/python/okstra_ctl/approval_decisions.py +287 -10
  142. package/runtime/python/okstra_ctl/assignment_resolver.py +0 -12
  143. package/runtime/python/okstra_ctl/attempt_evidence.py +12 -24
  144. package/runtime/python/okstra_ctl/blocking_checks.py +251 -0
  145. package/runtime/python/okstra_ctl/brief_frontmatter.py +0 -7
  146. package/runtime/python/okstra_ctl/clarification_items/__init__.py +102 -0
  147. package/runtime/python/okstra_ctl/clarification_items/carry.py +224 -0
  148. package/runtime/python/okstra_ctl/clarification_items/dispositions.py +135 -0
  149. package/runtime/python/okstra_ctl/clarification_items/parsing.py +253 -0
  150. package/runtime/python/okstra_ctl/clarification_items/rows.py +115 -0
  151. package/runtime/python/okstra_ctl/clarification_items/scan.py +215 -0
  152. package/runtime/python/okstra_ctl/clarification_items/sidecars.py +211 -0
  153. package/runtime/python/okstra_ctl/cmux.py +38 -31
  154. package/runtime/python/okstra_ctl/code_review_target.py +163 -1
  155. package/runtime/python/okstra_ctl/conformance.py +23 -0
  156. package/runtime/python/okstra_ctl/container.py +38 -421
  157. package/runtime/python/okstra_ctl/context_cost.py +32 -4
  158. package/runtime/python/okstra_ctl/contract_graph_cli.py +7 -0
  159. package/runtime/python/okstra_ctl/convergence.py +107 -16
  160. package/runtime/python/okstra_ctl/convergence_critic_prompt.py +345 -0
  161. package/runtime/python/okstra_ctl/convergence_engine.py +382 -70
  162. package/runtime/python/okstra_ctl/convergence_store.py +5 -29
  163. package/runtime/python/okstra_ctl/design_prep.py +108 -12
  164. package/runtime/python/okstra_ctl/design_snapshot.py +11 -1
  165. package/runtime/python/okstra_ctl/design_surfaces.py +29 -2
  166. package/runtime/python/okstra_ctl/dispatch_core.py +236 -49
  167. package/runtime/python/okstra_ctl/dispatch_state.py +417 -41
  168. package/runtime/python/okstra_ctl/doctor_cli.py +48 -0
  169. package/runtime/python/okstra_ctl/domain/worker_exec.py +10 -5
  170. package/runtime/python/okstra_ctl/domain/worker_presentation.py +28 -1
  171. package/runtime/python/okstra_ctl/domain/worker_stream.py +52 -21
  172. package/runtime/python/okstra_ctl/domain/write_policy.py +219 -0
  173. package/runtime/python/okstra_ctl/entrypoints/hosts.py +9 -2
  174. package/runtime/python/okstra_ctl/error_log_core.py +1 -1
  175. package/runtime/python/okstra_ctl/error_report.py +14 -0
  176. package/runtime/python/okstra_ctl/error_zip.py +18 -2
  177. package/runtime/python/okstra_ctl/exact_coverage.py +5 -0
  178. package/runtime/python/okstra_ctl/execution_identity.py +100 -7
  179. package/runtime/python/okstra_ctl/execution_manifest.py +67 -20
  180. package/runtime/python/okstra_ctl/execution_mutation_audit.py +90 -8
  181. package/runtime/python/okstra_ctl/final_report_paths.py +17 -0
  182. package/runtime/python/okstra_ctl/final_report_schema.py +319 -17
  183. package/runtime/python/okstra_ctl/git_reconcile.py +22 -2
  184. package/runtime/python/okstra_ctl/handoff.py +24 -1
  185. package/runtime/python/okstra_ctl/ids.py +6 -16
  186. package/runtime/python/okstra_ctl/implementation_direction.py +142 -33
  187. package/runtime/python/okstra_ctl/implementation_options.py +32 -13
  188. package/runtime/python/okstra_ctl/implementation_outcome.py +5 -2
  189. package/runtime/python/okstra_ctl/improvement_lenses.py +0 -14
  190. package/runtime/python/okstra_ctl/incremental_carry.py +69 -8
  191. package/runtime/python/okstra_ctl/incremental_scope.py +173 -12
  192. package/runtime/python/okstra_ctl/index.py +2 -2
  193. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +48 -8
  194. package/runtime/python/okstra_ctl/interactive_cli.py +223 -0
  195. package/runtime/python/okstra_ctl/json_boundary.py +10 -0
  196. package/runtime/python/okstra_ctl/listing.py +0 -91
  197. package/runtime/python/okstra_ctl/locks.py +5 -19
  198. package/runtime/python/okstra_ctl/log_report.py +14 -0
  199. package/runtime/python/okstra_ctl/manager_cli.py +22 -3
  200. package/runtime/python/okstra_ctl/manager_launch.py +4 -8
  201. package/runtime/python/okstra_ctl/material.py +2 -2
  202. package/runtime/python/okstra_ctl/migrate.py +23 -1
  203. package/runtime/python/okstra_ctl/model_cli.py +19 -5
  204. package/runtime/python/okstra_ctl/model_discovery.py +46 -47
  205. package/runtime/python/okstra_ctl/model_io/__init__.py +1 -0
  206. package/runtime/python/okstra_ctl/model_io/lines.py +163 -0
  207. package/runtime/python/okstra_ctl/model_io/references.py +370 -0
  208. package/runtime/python/okstra_ctl/model_io/renderers.py +498 -0
  209. package/runtime/python/okstra_ctl/model_io_cli.py +30 -945
  210. package/runtime/python/okstra_ctl/model_pool.py +10 -6
  211. package/runtime/python/okstra_ctl/models.py +8 -8
  212. package/runtime/python/okstra_ctl/mutation_recovery.py +5 -63
  213. package/runtime/python/okstra_ctl/next_phase.py +121 -69
  214. package/runtime/python/okstra_ctl/pane_reclaim.py +23 -0
  215. package/runtime/python/okstra_ctl/pane_title.py +11 -8
  216. package/runtime/python/okstra_ctl/path_hints.py +36 -11
  217. package/runtime/python/okstra_ctl/paths.py +76 -8
  218. package/runtime/python/okstra_ctl/plan_items.py +132 -16
  219. package/runtime/python/okstra_ctl/plan_items_cli.py +486 -36
  220. package/runtime/python/okstra_ctl/plan_run_root.py +1 -1
  221. package/runtime/python/okstra_ctl/plan_validate_cli.py +51 -0
  222. package/runtime/python/okstra_ctl/plan_verify_cli.py +80 -0
  223. package/runtime/python/okstra_ctl/prepare_error.py +20 -0
  224. package/runtime/python/okstra_ctl/prior_planning.py +157 -0
  225. package/runtime/python/okstra_ctl/profile_show.py +18 -1
  226. package/runtime/python/okstra_ctl/project_setup_cli.py +140 -0
  227. package/runtime/python/okstra_ctl/recap.py +22 -1
  228. package/runtime/python/okstra_ctl/reconcile.py +84 -0
  229. package/runtime/python/okstra_ctl/registry/host_registry.py +18 -1
  230. package/runtime/python/okstra_ctl/render.py +98 -32
  231. package/runtime/python/okstra_ctl/render_final_report.py +4 -8
  232. package/runtime/python/okstra_ctl/report_assembly.py +297 -7
  233. package/runtime/python/okstra_ctl/report_contract.py +30 -7
  234. package/runtime/python/okstra_ctl/report_finalize.py +213 -16
  235. package/runtime/python/okstra_ctl/report_html/common.py +2 -2
  236. package/runtime/python/okstra_ctl/report_html/render.py +8 -12
  237. package/runtime/python/okstra_ctl/report_language.py +8 -4
  238. package/runtime/python/okstra_ctl/report_narrative.py +220 -34
  239. package/runtime/python/okstra_ctl/report_projections.py +8 -2
  240. package/runtime/python/okstra_ctl/report_synthesis_packet.py +393 -1
  241. package/runtime/python/okstra_ctl/report_translation.py +2 -35
  242. package/runtime/python/okstra_ctl/report_view_artifacts.py +42 -2
  243. package/runtime/python/okstra_ctl/report_views.py +4 -22
  244. package/runtime/python/okstra_ctl/resolve_task_key.py +13 -0
  245. package/runtime/python/okstra_ctl/rollup.py +13 -0
  246. package/runtime/python/okstra_ctl/run.py +639 -138
  247. package/runtime/python/okstra_ctl/run_audit.py +13 -0
  248. package/runtime/python/okstra_ctl/run_index_row.py +0 -12
  249. package/runtime/python/okstra_ctl/seeding.py +21 -11
  250. package/runtime/python/okstra_ctl/sequence.py +1 -1
  251. package/runtime/python/okstra_ctl/session.py +64 -14
  252. package/runtime/python/okstra_ctl/set_work_status.py +18 -0
  253. package/runtime/python/okstra_ctl/stage_integrate.py +15 -1
  254. package/runtime/python/okstra_ctl/stage_ledger.py +1 -1
  255. package/runtime/python/okstra_ctl/stage_map.py +57 -11
  256. package/runtime/python/okstra_ctl/stage_map_cli.py +83 -0
  257. package/runtime/python/okstra_ctl/stage_map_view.py +65 -0
  258. package/runtime/python/okstra_ctl/stage_targets.py +3 -12
  259. package/runtime/python/okstra_ctl/task_list_cli.py +138 -0
  260. package/runtime/python/okstra_ctl/task_show_cli.py +66 -0
  261. package/runtime/python/okstra_ctl/task_target.py +4 -16
  262. package/runtime/python/okstra_ctl/team.py +49 -2
  263. package/runtime/python/okstra_ctl/time_report.py +17 -2
  264. package/runtime/python/okstra_ctl/usage_report.py +11 -0
  265. package/runtime/python/okstra_ctl/user_response.py +55 -218
  266. package/runtime/python/okstra_ctl/user_response_values.py +242 -0
  267. package/runtime/python/okstra_ctl/validation_contract.py +3 -0
  268. package/runtime/python/okstra_ctl/verdict_blocks.py +28 -8
  269. package/runtime/python/okstra_ctl/verification_target.py +68 -0
  270. package/runtime/python/okstra_ctl/wizard.py +457 -92
  271. package/runtime/python/okstra_ctl/worker_artifacts.py +75 -16
  272. package/runtime/python/okstra_ctl/worker_audit_check.py +22 -0
  273. package/runtime/python/okstra_ctl/worker_dispatch.py +21 -0
  274. package/runtime/python/okstra_ctl/worker_liveness.py +33 -0
  275. package/runtime/python/okstra_ctl/worker_prompt_body.py +15 -2
  276. package/runtime/python/okstra_ctl/worker_prompt_contract.py +98 -7
  277. package/runtime/python/okstra_ctl/worker_prompt_headers.py +24 -2
  278. package/runtime/python/okstra_ctl/worker_prompt_policy.py +18 -3
  279. package/runtime/python/okstra_ctl/worker_request.py +2 -1
  280. package/runtime/python/okstra_ctl/worker_runner.py +58 -17
  281. package/runtime/python/okstra_ctl/worker_state.py +16 -1
  282. package/runtime/python/okstra_ctl/workflow.py +18 -1
  283. package/runtime/python/okstra_ctl/worktree/__init__.py +92 -0
  284. package/runtime/python/okstra_ctl/worktree/cleanliness.py +89 -0
  285. package/runtime/python/okstra_ctl/worktree/decisions.py +127 -0
  286. package/runtime/python/okstra_ctl/worktree/git_ops.py +165 -0
  287. package/runtime/python/okstra_ctl/worktree/linking.py +249 -0
  288. package/runtime/python/okstra_ctl/worktree/naming.py +91 -0
  289. package/runtime/python/okstra_ctl/worktree/provision.py +385 -0
  290. package/runtime/python/okstra_ctl/worktree/sync_config.py +181 -0
  291. package/runtime/python/okstra_ctl/worktree_cli.py +75 -0
  292. package/runtime/python/okstra_ctl/worktree_lookup_cli.py +39 -0
  293. package/runtime/python/okstra_ctl/worktree_registry.py +7 -0
  294. package/runtime/python/okstra_ctl/worktree_status_cli.py +53 -0
  295. package/runtime/python/okstra_ctl/write_policy.py +90 -213
  296. package/runtime/python/okstra_project/__init__.py +0 -4
  297. package/runtime/python/okstra_project/dirs.py +2 -2
  298. package/runtime/python/okstra_project/phase_pointer.py +84 -0
  299. package/runtime/python/okstra_project/slug.py +24 -0
  300. package/runtime/python/okstra_project/state.py +22 -230
  301. package/runtime/python/okstra_token_usage/antigravity.py +50 -12
  302. package/runtime/python/okstra_token_usage/claude.py +9 -1
  303. package/runtime/python/okstra_token_usage/cli.py +3 -1
  304. package/runtime/python/okstra_token_usage/collect.py +171 -36
  305. package/runtime/python/okstra_token_usage/cursor.py +2 -0
  306. package/runtime/python/okstra_token_usage/grok.py +1 -5
  307. package/runtime/python/okstra_token_usage/paths.py +26 -0
  308. package/runtime/python/okstra_token_usage/pricing.py +23 -8
  309. package/runtime/schemas/convergence-critic-results-v1.0.schema.json +5 -0
  310. package/runtime/schemas/execution-manifest-v2.schema.json +10 -10
  311. package/runtime/schemas/final-report-v2.0.schema.json +9 -26
  312. package/runtime/schemas/final-report-v3.0.schema.json +173 -41
  313. package/runtime/schemas/report-narrative-v3.0.schema.json +3 -2
  314. package/runtime/skills/okstra-brief-gen/SKILL.md +35 -2
  315. package/runtime/skills/okstra-container-build/SKILL.md +16 -47
  316. package/runtime/skills/okstra-inspect/facets/history.md +1 -1
  317. package/runtime/skills/okstra-inspect/facets/status.md +7 -6
  318. package/runtime/skills/okstra-pr-gen/SKILL.md +1 -1
  319. package/runtime/skills/okstra-run/SKILL.md +7 -5
  320. package/runtime/templates/report-writer-prompt-preamble.md +3 -1
  321. package/runtime/templates/reports/brief.template.md +6 -2
  322. package/runtime/templates/reports/error-analysis-input.template.md +2 -0
  323. package/runtime/templates/reports/final-verification-input.template.md +2 -0
  324. package/runtime/templates/reports/implementation-input.template.md +7 -1
  325. package/runtime/templates/reports/implementation-planning-input.template.md +4 -0
  326. package/runtime/templates/reports/quick-input.template.md +2 -0
  327. package/runtime/templates/reports/release-handoff-input.template.md +6 -1
  328. package/runtime/templates/reports/report.js +20 -3
  329. package/runtime/templates/reports/schedule.template.md +2 -0
  330. package/runtime/templates/reports/settings.template.json +0 -10
  331. package/runtime/templates/reports/task-brief.template.md +3 -0
  332. package/runtime/templates/reports/user-response.template.md +7 -3
  333. package/runtime/validators/checks/fixtures-01.py +57 -0
  334. package/runtime/validators/checks/fixtures-02.py +565 -0
  335. package/runtime/validators/checks/runners-01.py +108 -0
  336. package/runtime/validators/checks/validate-assets-01.py +60 -0
  337. package/runtime/validators/checks/validate-prompt-metadata-01.py +261 -0
  338. package/runtime/validators/checks/validate-tasks-01.py +46 -0
  339. package/runtime/validators/checks/validate-tasks-02.py +85 -0
  340. package/runtime/validators/checks/validate-tasks-03.py +61 -0
  341. package/runtime/validators/checks/validate-tasks-04.py +118 -0
  342. package/runtime/validators/forbidden_actions.py +73 -3
  343. package/runtime/validators/lib/common.sh +5 -0
  344. package/runtime/validators/lib/fixtures.sh +7 -591
  345. package/runtime/validators/lib/paths.sh +13 -0
  346. package/runtime/validators/lib/runners.sh +6 -104
  347. package/runtime/validators/lib/summary.sh +1 -1
  348. package/runtime/validators/lib/validate-assets.sh +6 -56
  349. package/runtime/validators/lib/validate-prompt-metadata.sh +6 -257
  350. package/runtime/validators/lib/validate-tasks.sh +9 -294
  351. package/runtime/validators/validate-implementation-plan-stages.py +4 -4
  352. package/runtime/validators/validate-run.py +1363 -2643
  353. package/runtime/validators/validate-workflow.sh +56 -16
  354. package/runtime/validators/validate_improvement_report.py +2 -1
  355. package/runtime/validators/validate_session_conformance.py +295 -49
  356. package/dist/commands/execute/agent-prompt.d.mts +0 -1
  357. package/dist/commands/execute/agent-prompt.mjs +0 -24
  358. package/dist/commands/execute/agent-prompt.mjs.map +0 -1
  359. package/dist/commands/execute/codex-dispatch.d.mts +0 -3
  360. package/dist/commands/execute/codex-dispatch.mjs +0 -6
  361. package/dist/commands/execute/codex-dispatch.mjs.map +0 -1
  362. package/dist/commands/execute/codex-run.d.mts +0 -3
  363. package/dist/commands/execute/codex-run.mjs +0 -62
  364. package/dist/commands/execute/codex-run.mjs.map +0 -1
  365. package/dist/commands/execute/convergence.d.mts +0 -1
  366. package/dist/commands/execute/convergence.mjs +0 -38
  367. package/dist/commands/execute/convergence.mjs.map +0 -1
  368. package/dist/commands/execute/error-log.d.mts +0 -1
  369. package/dist/commands/execute/error-log.mjs +0 -18
  370. package/dist/commands/execute/error-log.mjs.map +0 -1
  371. package/dist/commands/execute/git-reconcile.d.mts +0 -1
  372. package/dist/commands/execute/git-reconcile.mjs +0 -30
  373. package/dist/commands/execute/git-reconcile.mjs.map +0 -1
  374. package/dist/commands/execute/handoff.d.mts +0 -1
  375. package/dist/commands/execute/handoff.mjs +0 -31
  376. package/dist/commands/execute/handoff.mjs.map +0 -1
  377. package/dist/commands/execute/incremental-carry.d.mts +0 -1
  378. package/dist/commands/execute/incremental-carry.mjs +0 -20
  379. package/dist/commands/execute/incremental-carry.mjs.map +0 -1
  380. package/dist/commands/execute/incremental-scope.d.mts +0 -1
  381. package/dist/commands/execute/incremental-scope.mjs +0 -29
  382. package/dist/commands/execute/incremental-scope.mjs.map +0 -1
  383. package/dist/commands/execute/integrate-stages.d.mts +0 -1
  384. package/dist/commands/execute/integrate-stages.mjs +0 -24
  385. package/dist/commands/execute/integrate-stages.mjs.map +0 -1
  386. package/dist/commands/execute/pane-title.d.mts +0 -1
  387. package/dist/commands/execute/pane-title.mjs +0 -20
  388. package/dist/commands/execute/pane-title.mjs.map +0 -1
  389. package/dist/commands/execute/plan-items.d.mts +0 -1
  390. package/dist/commands/execute/plan-items.mjs +0 -9
  391. package/dist/commands/execute/plan-items.mjs.map +0 -1
  392. package/dist/commands/execute/plan-validate.d.mts +0 -1
  393. package/dist/commands/execute/plan-validate.mjs +0 -68
  394. package/dist/commands/execute/plan-validate.mjs.map +0 -1
  395. package/dist/commands/execute/plan-verify.d.mts +0 -1
  396. package/dist/commands/execute/plan-verify.mjs +0 -43
  397. package/dist/commands/execute/plan-verify.mjs.map +0 -1
  398. package/dist/commands/execute/spawn-followups.d.mts +0 -1
  399. package/dist/commands/execute/spawn-followups.mjs +0 -22
  400. package/dist/commands/execute/spawn-followups.mjs.map +0 -1
  401. package/dist/commands/execute/team.d.mts +0 -3
  402. package/dist/commands/execute/team.mjs +0 -66
  403. package/dist/commands/execute/team.mjs.map +0 -1
  404. package/dist/commands/execute/token-usage.d.mts +0 -1
  405. package/dist/commands/execute/token-usage.mjs +0 -19
  406. package/dist/commands/execute/token-usage.mjs.map +0 -1
  407. package/dist/commands/execute/worker-audit-check.d.mts +0 -1
  408. package/dist/commands/execute/worker-audit-check.mjs +0 -34
  409. package/dist/commands/execute/worker-audit-check.mjs.map +0 -1
  410. package/dist/commands/execute/worker-dispatch.d.mts +0 -7
  411. package/dist/commands/execute/worker-dispatch.mjs +0 -64
  412. package/dist/commands/execute/worker-dispatch.mjs.map +0 -1
  413. package/dist/commands/execute/worker-state.d.mts +0 -1
  414. package/dist/commands/execute/worker-state.mjs +0 -28
  415. package/dist/commands/execute/worker-state.mjs.map +0 -1
  416. package/dist/commands/execute/worktree-lookup.d.mts +0 -1
  417. package/dist/commands/execute/worktree-lookup.mjs +0 -92
  418. package/dist/commands/execute/worktree-lookup.mjs.map +0 -1
  419. package/dist/commands/execute/worktree-status.d.mts +0 -1
  420. package/dist/commands/execute/worktree-status.mjs +0 -121
  421. package/dist/commands/execute/worktree-status.mjs.map +0 -1
  422. package/dist/commands/inspect/code-review.d.mts +0 -1
  423. package/dist/commands/inspect/code-review.mjs +0 -32
  424. package/dist/commands/inspect/code-review.mjs.map +0 -1
  425. package/dist/commands/inspect/container.d.mts +0 -1
  426. package/dist/commands/inspect/container.mjs +0 -25
  427. package/dist/commands/inspect/container.mjs.map +0 -1
  428. package/dist/commands/inspect/context-cost.d.mts +0 -1
  429. package/dist/commands/inspect/context-cost.mjs +0 -25
  430. package/dist/commands/inspect/context-cost.mjs.map +0 -1
  431. package/dist/commands/inspect/design-prep.d.mts +0 -1
  432. package/dist/commands/inspect/design-prep.mjs +0 -22
  433. package/dist/commands/inspect/design-prep.mjs.map +0 -1
  434. package/dist/commands/inspect/error-report.d.mts +0 -1
  435. package/dist/commands/inspect/error-report.mjs +0 -25
  436. package/dist/commands/inspect/error-report.mjs.map +0 -1
  437. package/dist/commands/inspect/error-zip.d.mts +0 -1
  438. package/dist/commands/inspect/error-zip.mjs +0 -24
  439. package/dist/commands/inspect/error-zip.mjs.map +0 -1
  440. package/dist/commands/inspect/log-report.d.mts +0 -1
  441. package/dist/commands/inspect/log-report.mjs +0 -26
  442. package/dist/commands/inspect/log-report.mjs.map +0 -1
  443. package/dist/commands/inspect/model-io.d.mts +0 -1
  444. package/dist/commands/inspect/model-io.mjs +0 -25
  445. package/dist/commands/inspect/model-io.mjs.map +0 -1
  446. package/dist/commands/inspect/profile-show.d.mts +0 -1
  447. package/dist/commands/inspect/profile-show.mjs +0 -28
  448. package/dist/commands/inspect/profile-show.mjs.map +0 -1
  449. package/dist/commands/inspect/recap.d.mts +0 -1
  450. package/dist/commands/inspect/recap.mjs +0 -30
  451. package/dist/commands/inspect/recap.mjs.map +0 -1
  452. package/dist/commands/inspect/resolve-task-key.d.mts +0 -1
  453. package/dist/commands/inspect/resolve-task-key.mjs +0 -24
  454. package/dist/commands/inspect/resolve-task-key.mjs.map +0 -1
  455. package/dist/commands/inspect/rollup.d.mts +0 -1
  456. package/dist/commands/inspect/rollup.mjs +0 -25
  457. package/dist/commands/inspect/rollup.mjs.map +0 -1
  458. package/dist/commands/inspect/run-audit.d.mts +0 -1
  459. package/dist/commands/inspect/run-audit.mjs +0 -25
  460. package/dist/commands/inspect/run-audit.mjs.map +0 -1
  461. package/dist/commands/inspect/set-work-status.d.mts +0 -1
  462. package/dist/commands/inspect/set-work-status.mjs +0 -29
  463. package/dist/commands/inspect/set-work-status.mjs.map +0 -1
  464. package/dist/commands/inspect/stage-map.d.mts +0 -1
  465. package/dist/commands/inspect/stage-map.mjs +0 -131
  466. package/dist/commands/inspect/stage-map.mjs.map +0 -1
  467. package/dist/commands/inspect/task-list.d.mts +0 -1
  468. package/dist/commands/inspect/task-list.mjs +0 -149
  469. package/dist/commands/inspect/task-list.mjs.map +0 -1
  470. package/dist/commands/inspect/task-show.d.mts +0 -1
  471. package/dist/commands/inspect/task-show.mjs +0 -108
  472. package/dist/commands/inspect/task-show.mjs.map +0 -1
  473. package/dist/commands/inspect/time-report.d.mts +0 -1
  474. package/dist/commands/inspect/time-report.mjs +0 -24
  475. package/dist/commands/inspect/time-report.mjs.map +0 -1
  476. package/dist/commands/inspect/usage-report.d.mts +0 -1
  477. package/dist/commands/inspect/usage-report.mjs +0 -23
  478. package/dist/commands/inspect/usage-report.mjs.map +0 -1
  479. package/dist/commands/inspect/user-response.d.mts +0 -1
  480. package/dist/commands/inspect/user-response.mjs +0 -35
  481. package/dist/commands/inspect/user-response.mjs.map +0 -1
  482. package/dist/commands/inspect/worker-liveness.d.mts +0 -1
  483. package/dist/commands/inspect/worker-liveness.mjs +0 -45
  484. package/dist/commands/inspect/worker-liveness.mjs.map +0 -1
  485. package/dist/commands/lifecycle/contract-check.d.mts +0 -1
  486. package/dist/commands/lifecycle/contract-check.mjs +0 -18
  487. package/dist/commands/lifecycle/contract-check.mjs.map +0 -1
  488. package/dist/commands/lifecycle/migrate.d.mts +0 -1
  489. package/dist/commands/lifecycle/migrate.mjs +0 -30
  490. package/dist/commands/lifecycle/migrate.mjs.map +0 -1
  491. package/dist/commands/lifecycle/model.d.mts +0 -1
  492. package/dist/commands/lifecycle/model.mjs +0 -22
  493. package/dist/commands/lifecycle/model.mjs.map +0 -1
  494. package/dist/commands/manager.d.mts +0 -3
  495. package/dist/commands/manager.mjs +0 -50
  496. package/dist/commands/manager.mjs.map +0 -1
  497. package/dist/commands/report/agent-activity.d.mts +0 -1
  498. package/dist/commands/report/agent-activity.mjs +0 -20
  499. package/dist/commands/report/agent-activity.mjs.map +0 -1
  500. package/dist/commands/report/approval-decision.d.mts +0 -1
  501. package/dist/commands/report/approval-decision.mjs +0 -21
  502. package/dist/commands/report/approval-decision.mjs.map +0 -1
  503. package/dist/commands/report/design-snapshot.d.mts +0 -1
  504. package/dist/commands/report/design-snapshot.mjs +0 -19
  505. package/dist/commands/report/design-snapshot.mjs.map +0 -1
  506. package/dist/commands/report/finalize.d.mts +0 -4
  507. package/dist/commands/report/finalize.mjs +0 -64
  508. package/dist/commands/report/finalize.mjs.map +0 -1
  509. package/dist/commands/report/inject-report-index.d.mts +0 -1
  510. package/dist/commands/report/inject-report-index.mjs +0 -21
  511. package/dist/commands/report/inject-report-index.mjs.map +0 -1
  512. package/dist/commands/report/render-final-report.d.mts +0 -1
  513. package/dist/commands/report/render-final-report.mjs +0 -23
  514. package/dist/commands/report/render-final-report.mjs.map +0 -1
  515. package/dist/commands/report/render-views.d.mts +0 -1
  516. package/dist/commands/report/render-views.mjs +0 -27
  517. package/dist/commands/report/render-views.mjs.map +0 -1
  518. package/dist/commands/report/translate.d.mts +0 -1
  519. package/dist/commands/report/translate.mjs +0 -33
  520. package/dist/commands/report/translate.mjs.map +0 -1
  521. package/runtime/bin/lib/okstra/tmux-pane.sh +0 -40
  522. package/runtime/bin/lib/okstra-ctl/cmd-batch.sh +0 -59
  523. package/runtime/bin/lib/okstra-ctl/cmd-list.sh +0 -35
  524. package/runtime/bin/lib/okstra-ctl/cmd-open.sh +0 -36
  525. package/runtime/bin/lib/okstra-ctl/cmd-projects.sh +0 -26
  526. package/runtime/bin/lib/okstra-ctl/cmd-reconcile.sh +0 -29
  527. package/runtime/bin/lib/okstra-ctl/cmd-reindex.sh +0 -38
  528. package/runtime/bin/lib/okstra-ctl/cmd-rerun.sh +0 -345
  529. package/runtime/bin/lib/okstra-ctl/cmd-show.sh +0 -27
  530. package/runtime/bin/lib/okstra-ctl/cmd-tail.sh +0 -92
  531. package/runtime/bin/lib/okstra-ctl/main.sh +0 -41
  532. package/runtime/bin/lib/okstra-ctl/prepare.sh +0 -31
  533. package/runtime/bin/lib/okstra-ctl/usage.sh +0 -23
  534. package/runtime/bin/okstra-central.sh +0 -152
  535. package/runtime/bin/okstra-incremental-carry.py +0 -10
  536. package/runtime/bin/okstra-incremental-scope.py +0 -10
  537. package/runtime/bin/okstra-team-reconcile.sh +0 -36
  538. package/runtime/python/okstra_ctl/agent_prompt_cli.py +0 -1156
  539. package/runtime/python/okstra_ctl/batch.py +0 -60
  540. package/runtime/python/okstra_ctl/clarification_items.py +0 -1050
  541. package/runtime/python/okstra_ctl/container_registry.py +0 -72
  542. package/runtime/python/okstra_ctl/improvement_assignment.py +0 -61
  543. package/runtime/python/okstra_ctl/resolver.py +0 -54
  544. package/runtime/python/okstra_ctl/team_reconcile.py +0 -275
  545. package/runtime/python/okstra_ctl/tmux.py +0 -134
  546. package/runtime/python/okstra_ctl/worktree.py +0 -1099
@@ -38,8 +38,25 @@ class _Node:
38
38
  self.children: list[_Node] = []
39
39
 
40
40
 
41
+ _NARRATIVE_SCHEMA_RELATIVE = ("schemas", "report-narrative-v3.0.schema.json")
42
+
43
+
41
44
  def _narrative_schema_path() -> Path:
42
- return Path(__file__).resolve().parents[2] / "schemas" / "report-narrative-v3.0.schema.json"
45
+ """schemas 를 실제로 가진 루트에서 해소한다.
46
+
47
+ 부모 개수를 세면 체크아웃에서만 맞는다 — 설치본은 패키지가
48
+ `~/.okstra/lib/python/` 이고 schemas 는 `~/.okstra/schemas/` 라 한 단계
49
+ 어긋나서, 설치본으로 실행할 때 서사 파싱이 스키마 없음으로 죽었다.
50
+ """
51
+ from .paths import find_asset_root
52
+
53
+ root = find_asset_root(_NARRATIVE_SCHEMA_RELATIVE)
54
+ if root is None:
55
+ raise NarrativeContractError(
56
+ "could not locate report-narrative-v3.0.schema.json. Set OKSTRA_HOME "
57
+ "or run from a checkout that contains schemas/."
58
+ )
59
+ return root.joinpath(*_NARRATIVE_SCHEMA_RELATIVE)
43
60
 
44
61
 
45
62
  def _narrative_schema() -> dict[str, Any]:
@@ -50,6 +67,77 @@ def _allowed_top_level() -> frozenset[str]:
50
67
  return frozenset(_narrative_schema().get("properties", {}))
51
68
 
52
69
 
70
+ def allowed_top_level_fields() -> frozenset[str]:
71
+ """작성자가 최상위에 쓸 수 있는 필드 이름(서사 스키마의 properties)."""
72
+ return _allowed_top_level()
73
+
74
+
75
+ # 완성 리포트 스키마가 작성자 소유 블록 안에서 required 로 거는 기계 소유 필드.
76
+ # `writer_owned_data` 가 떼어내고 `_NESTED_FORBIDDEN` 이 저작을 막는 바로 그
77
+ # 이름들이라, 값 제약을 그대로 쓰면 정상 서사가 전부 "필수 필드 없음" 으로
78
+ # 깨진다. 앞의 둘은 `_NESTED_FORBIDDEN` 에서 파생시켜 두 목록이 갈라지지
79
+ # 않게 한다.
80
+ _MACHINE_OWNED_NESTED = frozenset(
81
+ {path.rsplit(".", 1)[-1] for path in _NESTED_FORBIDDEN} | {"designSurfaceCoverage"}
82
+ )
83
+
84
+
85
+ def _without_machine_owned_required(node: Any, *, negated: bool = False) -> Any:
86
+ """`required` 목록에서 기계 소유 필드 이름만 뺀 스키마 사본.
87
+
88
+ `not` 아래는 건드리지 않는다. 거기서 `required: [designPreparation]` 은
89
+ "이 필드가 있으면 안 된다" 는 뜻이고 서사에는 원래 없으니 이미 만족한다.
90
+ 이름을 빼면 `not: {required: []}` 가 되는데, 빈 `required` 는 모든 객체가
91
+ 만족하므로 그 `not` 은 항상 거짓이 된다 — 통과해야 할 분기를 통과 불가로
92
+ 뒤집는다. 실제로 `direction-invalidated` 계획 분기가 그렇게 죽었다.
93
+ """
94
+ if isinstance(node, list):
95
+ return [_without_machine_owned_required(item, negated=negated) for item in node]
96
+ if not isinstance(node, dict):
97
+ return node
98
+ result: dict[str, Any] = {}
99
+ for key, value in node.items():
100
+ if key == "required" and isinstance(value, list) and not negated:
101
+ result[key] = [
102
+ name for name in value if name not in _MACHINE_OWNED_NESTED
103
+ ]
104
+ else:
105
+ result[key] = _without_machine_owned_required(
106
+ value, negated=negated or key == "not"
107
+ )
108
+ return result
109
+
110
+
111
+ def _writer_owned_schema(schema: Mapping[str, Any]) -> dict[str, Any]:
112
+ """작성자가 쓴 서사에 걸 값 제약 — 완성 리포트 스키마에서 잘라 온다.
113
+
114
+ `report-narrative-v3.0.schema.json` 은 최상위 **이름** 허용목록이고 값은
115
+ 전부 `{"type": "object"}` 스텁이다. 그 스텁으로만 검증하면 enum 밖 값과
116
+ 빠진 필수 필드가 서사 단계를 통과하고, Phase 7 조립이 완성 리포트 스키마에
117
+ 부딪힐 때에야 거절된다 — 작성기 워커가 이미 끝난 뒤라 되돌릴 방법이 없다.
118
+ 실측(2026-08-26, stage 10 run 002): `verdictCard.direction` 에 enum 에 없는
119
+ `proceed` 가, `humanSummary` 에 필수 `whyItMatters` 가 빠진 채로 서사가
120
+ 통과했고 `report-finalize` 의 `project-activity` 가 exit 1 로 떨어졌다.
121
+
122
+ 그래서 이름 소유권은 서사 스키마에서, 값 제약은 넘겨받은 완성 리포트
123
+ 스키마에서 가져온다. 두 벌을 만들지 않으므로 드리프트가 생기지 않는다.
124
+ 서사는 부분 문서다 — 최상위 `required` 는 싣지 않는다. 이 run 의 task
125
+ 블록만 있고 나머지 task-type 블록과 기계 소유 최상위 필드는 없기 때문이다.
126
+ """
127
+ allowed = _allowed_top_level()
128
+ properties = {
129
+ key: value
130
+ for key, value in schema.get("properties", {}).items()
131
+ if key in allowed
132
+ }
133
+ return {
134
+ "type": "object",
135
+ "additionalProperties": False,
136
+ "properties": _without_machine_owned_required(properties),
137
+ "$defs": _without_machine_owned_required(schema.get("$defs", {})),
138
+ }
139
+
140
+
53
141
  def writer_owned_data(data: Mapping[str, Any]) -> dict[str, Any]:
54
142
  """완성 리포트에서 보고서 작성자 소유 필드만 복사한다."""
55
143
  owned = {
@@ -190,7 +278,7 @@ def _parse_tree(markdown: str) -> _Node:
190
278
  _append_node(stack, field, item, level, number)
191
279
  elif value:
192
280
  level = _line_level(value.group("indent"), number)
193
- _append_value(stack, value.group("value"), level, number)
281
+ _append_value(stack, value.group("value"), level, number, lines)
194
282
  else:
195
283
  raise NarrativeContractError(
196
284
  f"line {number}: unsupported Markdown syntax `{line.strip()}` — "
@@ -216,9 +304,45 @@ def _append_node(stack: list[_Node], field: Any, item: Any, level: int, number:
216
304
  stack.append(node)
217
305
 
218
306
 
219
- def _append_value(stack: list[_Node], value: str, level: int, number: int) -> None:
307
+ def _misplaced_value_lines(lines: list[str]) -> int:
308
+ """자기 라벨보다 두 칸 더 들여쓰지 않은 `> value` 줄의 수.
309
+
310
+ 파서는 첫 결함에서 멈추므로 규모를 말하지 못한다 — 129줄이 전부 0열에
311
+ 붙은 서술문이 `line 5` 한 건으로 보고됐다(2026-09-02 실측). 여기서는
312
+ 직전 라벨(`- **…**` / `- Item N`)의 들여쓰기와 견줘 세기만 한다; 판정은
313
+ 여전히 파서의 것이다.
314
+ """
315
+ misplaced = 0
316
+ label_indent = None
317
+ for line in lines[1:]:
318
+ if not line.strip():
319
+ continue
320
+ match = _FIELD_RE.match(line) or _ITEM_RE.match(line)
321
+ if match:
322
+ label_indent = len(match.group("indent"))
323
+ continue
324
+ value = _VALUE_RE.match(line)
325
+ if value and label_indent is not None:
326
+ if len(value.group("indent")) != label_indent + 2:
327
+ misplaced += 1
328
+ return misplaced
329
+
330
+
331
+ def _append_value(
332
+ stack: list[_Node], value: str, level: int, number: int, lines: list[str] | None = None,
333
+ ) -> None:
220
334
  if stack[-1].level != level - 1:
221
- raise NarrativeContractError(f"line {number}: value is outside its field")
335
+ count = _misplaced_value_lines(lines) if lines is not None else 0
336
+ scale = (
337
+ f"; {count} value line(s) in this file share the defect"
338
+ if count > 1
339
+ else ""
340
+ )
341
+ raise NarrativeContractError(
342
+ f"line {number}: value is outside its field — a `> value` line is "
343
+ "indented exactly two spaces deeper than the `- **Label**` or "
344
+ f"`- Item N` line it belongs to{scale}"
345
+ )
222
346
  if stack[-1].children:
223
347
  raise NarrativeContractError(f"line {number}: field mixes values and children")
224
348
  stack[-1].values.append(value)
@@ -234,7 +358,37 @@ def _allowed_labels(node: Any, index: SchemaIndex, path: str) -> list[str]:
234
358
  return sorted(humanise(key) for key in keys)
235
359
 
236
360
 
237
- def _field_key(label: str, node: Any, index: SchemaIndex, path: str) -> str:
361
+ class _Defects:
362
+ """값 단계에서 모은 결함. 첫 건에서 멈추지 않고 문서 전체를 읽는다.
363
+
364
+ 파서가 첫 강제 변환 실패에서 예외를 내던 동안, 같은 종류의 결함이 여러
365
+ 자리에 있어도 한 자리만 보고돼 작성자가 같은 문서를 회차마다 한 자리씩
366
+ 고쳤다(2026-09-03 실측, dev-10626 implementation-option-selection: 소수
367
+ `coveragePercent` 가 랭킹 옵션 둘에 있는데 `rankedOptions[1]` 한 건만).
368
+ 스키마 검증(`validate`)은 이미 전건을 모으므로 값 단계도 그렇게 한다.
369
+ """
370
+
371
+ def __init__(self) -> None:
372
+ self.messages: list[str] = []
373
+ self.paths: list[str] = []
374
+
375
+ def add(self, path: str, message: str) -> None:
376
+ self.paths.append(path)
377
+ self.messages.append(message)
378
+
379
+ def covers(self, error: str) -> bool:
380
+ """스키마 검증이 같은 자리(또는 그 아래)에 낸 오류인가 — 값 단계가 이미
381
+ 보고한 자리를 두 번 말하지 않는다."""
382
+ location = error.split(": ", 1)[0]
383
+ return any(
384
+ location == path or location.startswith(f"{path}.") or location.startswith(f"{path}[")
385
+ for path in self.paths
386
+ )
387
+
388
+
389
+ def _field_key(
390
+ label: str, node: Any, index: SchemaIndex, path: str, defects: _Defects,
391
+ ) -> str | None:
238
392
  candidates: dict[str, list[str]] = {}
239
393
  for key in index.key_order(node):
240
394
  candidates.setdefault(label_key(humanise(key)), []).append(key)
@@ -247,20 +401,26 @@ def _field_key(label: str, node: Any, index: SchemaIndex, path: str) -> str:
247
401
  if not matches:
248
402
  allowed = _allowed_labels(node, index, path)
249
403
  listing = ", ".join(f"`{item}`" for item in allowed) or "(none)"
250
- raise NarrativeContractError(
404
+ defects.add(
405
+ display_path,
251
406
  f"owner=report-writer field `{display_path}` is not an allowed unique "
252
- f"field; allowed at this position: {listing}"
407
+ f"field; allowed at this position: {listing}",
253
408
  )
409
+ return None
254
410
  if len(matches) > 1:
255
411
  collisions = ", ".join(f"`{key}`" for key in matches)
256
- raise NarrativeContractError(
412
+ defects.add(
413
+ display_path,
257
414
  f"owner=report-writer field `{display_path}` is not an allowed unique "
258
- f"field; the label matches more than one schema key: {collisions}"
415
+ f"field; the label matches more than one schema key: {collisions}",
259
416
  )
417
+ return None
260
418
  return matches[0]
261
419
 
262
420
 
263
- def _parse_scalar(values: list[str], node: Any, index: SchemaIndex, path: str) -> Any:
421
+ def _parse_scalar(
422
+ values: list[str], node: Any, index: SchemaIndex, path: str, defects: _Defects,
423
+ ) -> Any:
264
424
  types = _node_types(node, index)
265
425
  text = "\n".join(values)
266
426
  if text == EMPTY_MARKER and "null" in types:
@@ -270,17 +430,21 @@ def _parse_scalar(values: list[str], node: Any, index: SchemaIndex, path: str) -
270
430
  if "integer" in types:
271
431
  try:
272
432
  return int(text)
273
- except ValueError as exc:
274
- raise NarrativeContractError(f"{path}: expected integer") from exc
433
+ except ValueError:
434
+ defects.add(path, f"{path}: expected integer")
435
+ return text
275
436
  if "number" in types:
276
437
  try:
277
438
  return float(text)
278
- except ValueError as exc:
279
- raise NarrativeContractError(f"{path}: expected number") from exc
439
+ except ValueError:
440
+ defects.add(path, f"{path}: expected number")
441
+ return text
280
442
  return text
281
443
 
282
444
 
283
- def _parse_value(node: _Node, schema_node: Any, index: SchemaIndex, path: str) -> Any:
445
+ def _parse_value(
446
+ node: _Node, schema_node: Any, index: SchemaIndex, path: str, defects: _Defects,
447
+ ) -> Any:
284
448
  types = _node_types(schema_node, index)
285
449
  if node.values:
286
450
  if node.values == [EMPTY_MARKER] and "null" in types:
@@ -289,52 +453,70 @@ def _parse_value(node: _Node, schema_node: Any, index: SchemaIndex, path: str) -
289
453
  return {}
290
454
  if node.values == [EMPTY_MARKER] and "array" in types:
291
455
  return []
292
- return _parse_scalar(node.values, schema_node, index, path)
456
+ return _parse_scalar(node.values, schema_node, index, path, defects)
293
457
  if "array" in types or all(child.kind == "item" for child in node.children):
294
- return _parse_array(node, schema_node, index, path)
295
- return _parse_object(node, schema_node, index, path)
458
+ return _parse_array(node, schema_node, index, path, defects)
459
+ return _parse_object(node, schema_node, index, path, defects)
296
460
 
297
461
 
298
- def _parse_array(node: _Node, schema_node: Any, index: SchemaIndex, path: str) -> list[Any]:
462
+ def _parse_array(
463
+ node: _Node, schema_node: Any, index: SchemaIndex, path: str, defects: _Defects,
464
+ ) -> list[Any]:
299
465
  if any(child.kind != "item" for child in node.children):
300
- raise NarrativeContractError(f"{path}: array requires Item rows")
466
+ defects.add(path, f"{path}: array requires Item rows")
467
+ return []
301
468
  positions = [int(child.label) for child in node.children]
302
469
  if positions != list(range(1, len(positions) + 1)):
303
- raise NarrativeContractError(f"{path}: Item numbers must be 1..N")
470
+ defects.add(path, f"{path}: Item numbers must be 1..N")
471
+ return []
304
472
  item_schema = index.item(schema_node)
305
473
  return [
306
- _parse_value(child, item_schema, index, f"{path}[{position - 1}]")
474
+ _parse_value(child, item_schema, index, f"{path}[{position - 1}]", defects)
307
475
  for position, child in zip(positions, node.children, strict=True)
308
476
  ]
309
477
 
310
478
 
311
- def _parse_object(node: _Node, schema_node: Any, index: SchemaIndex, path: str) -> dict[str, Any]:
479
+ def _parse_object(
480
+ node: _Node, schema_node: Any, index: SchemaIndex, path: str, defects: _Defects,
481
+ ) -> dict[str, Any]:
312
482
  result: dict[str, Any] = {}
313
483
  for child in node.children:
314
484
  if child.kind != "field":
315
- raise NarrativeContractError(f"{path}: object requires named fields")
316
- key = _field_key(child.label, schema_node, index, path)
485
+ defects.add(path, f"{path}: object requires named fields")
486
+ return result
487
+ key = _field_key(child.label, schema_node, index, path, defects)
488
+ if key is None:
489
+ continue
317
490
  child_path = f"{path}.{key}" if path else key
318
491
  if child_path in _NESTED_FORBIDDEN:
319
- raise NarrativeContractError(
320
- f"owner=report-writer cannot author `{path}.{child.label}`"
492
+ defects.add(
493
+ child_path, f"owner=report-writer cannot author `{path}.{child.label}`"
321
494
  )
495
+ continue
322
496
  if key in result:
323
- raise NarrativeContractError(f"duplicate field: {child_path}")
497
+ defects.add(child_path, f"duplicate field: {child_path}")
498
+ continue
324
499
  child_schema = index.child(schema_node, key)
325
500
  if not _node_types(child_schema, index):
326
501
  child_schema = index.schema_for_key(key)
327
502
  result[key] = _parse_value(
328
- child, child_schema, index, child_path
503
+ child, child_schema, index, child_path, defects
329
504
  )
330
505
  return result
331
506
 
332
507
 
333
508
  def parse_narrative(markdown: str, schema: Mapping[str, Any]) -> dict[str, Any]:
334
- """Markdown을 작성자 소유 자료로 읽고 소유권 표면을 검증한다."""
509
+ """Markdown을 작성자 소유 자료로 읽고, 소유권 표면과 값 제약을 검증한다.
510
+
511
+ `schema` 는 완성 리포트 스키마다. 이름·타입 해석과 값 검증이 모두 그
512
+ 한 벌에서 나온다(`_writer_owned_schema`). 값 단계의 결함(강제 변환·모양·
513
+ 허용되지 않는 필드)과 스키마 검증의 결함을 한 예외에 모두 싣는다 — 값
514
+ 단계가 보고한 자리는 스키마 검증에서 다시 말하지 않는다.
515
+ """
335
516
  root = _parse_tree(markdown)
336
517
  index = SchemaIndex(schema)
337
- result = _parse_object(root, schema, index, "")
518
+ defects = _Defects()
519
+ result = _parse_object(root, schema, index, "", defects)
338
520
  unknown = sorted(set(result) - _allowed_top_level())
339
521
  if unknown:
340
522
  labels = [humanise(key) for key in unknown]
@@ -345,7 +527,11 @@ def parse_narrative(markdown: str, schema: Mapping[str, Any]) -> dict[str, Any]:
345
527
  f"owner=report-writer cannot author fields: {labels}; "
346
528
  f"writer-owned top-level fields: {owned}"
347
529
  )
348
- errors = validate(result, _narrative_schema())
349
- if errors:
350
- raise NarrativeContractError("; ".join(errors))
530
+ errors = [
531
+ error
532
+ for error in validate(result, _writer_owned_schema(schema))
533
+ if not defects.covers(error)
534
+ ]
535
+ if defects.messages or errors:
536
+ raise NarrativeContractError("; ".join(defects.messages + errors))
351
537
  return result
@@ -168,6 +168,9 @@ def _worker_details(team_state: Mapping[str, Any]) -> list[dict[str, Any]]:
168
168
  usage = worker.get("usage") if isinstance(worker.get("usage"), Mapping) else {}
169
169
  row = {"label": _text(worker.get("role") or worker.get("workerId"), "Worker")}
170
170
  _populate_usage(row, usage)
171
+ # 작업 시간은 executionStatus 칸이다. workerDetails 스키마는
172
+ # durationMs 를 허용하지 않아 조립이 additionalProperties 로 거절한다.
173
+ row.pop("durationMs", None)
171
174
  for key in ("totalTokens", "cacheReadTokens", "billableTokens", "costUsd", "cliTotalTokens", "cliCostUsd"):
172
175
  row.setdefault(key, None)
173
176
  result.append(row)
@@ -231,6 +234,9 @@ def project_design(
231
234
  def _round_history(state: Mapping[str, Any]) -> dict[str, Any]:
232
235
  if not (state.get("config") or {}).get("enabled", True):
233
236
  return {"disabled": True}
237
+ skip_reason = state.get("round2SkippedReason", "not-skipped")
238
+ if skip_reason == "auto-disabled":
239
+ skip_reason = "single-analyser-only"
234
240
  rows = []
235
241
  for row in state.get("roundHistory") or []:
236
242
  if not isinstance(row, Mapping):
@@ -243,7 +249,7 @@ def _round_history(state: Mapping[str, Any]) -> dict[str, Any]:
243
249
  "dispatches": json.dumps(row.get("dispatches") or [], ensure_ascii=False),
244
250
  "skippedWorkers": json.dumps(row.get("skippedWorkers") or [], ensure_ascii=False),
245
251
  })
246
- return {"rounds": rows, "round2SkippedReason": state.get("round2SkippedReason", "not-skipped")}
252
+ return {"rounds": rows, "round2SkippedReason": skip_reason}
247
253
 
248
254
 
249
255
  def _source_items(finding: Mapping[str, Any]) -> list[str]:
@@ -265,7 +271,7 @@ def project_convergence(state: Mapping[str, Any]) -> dict[str, Any]:
265
271
  ticket_id = _text(ticket_ids[0] if ticket_ids else "unknown", "unknown")
266
272
  if finding.get("classification") in {"full-consensus", "partial-consensus"}:
267
273
  row = {
268
- "id": f"C-{len(consensus) + 1:03d}",
274
+ "id": f"CV-{len(consensus) + 1:03d}",
269
275
  "ticketId": ticket_id,
270
276
  "statement": _text(finding.get("summary"), "Converged finding"),
271
277
  "sourceItems": _source_items(finding),