okstra 0.202.0 → 0.205.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 (263) hide show
  1. package/README.md +7 -6
  2. package/dist/cli-registry.mjs +7 -7
  3. package/dist/cli-registry.mjs.map +1 -1
  4. package/dist/commands/lifecycle/install.mjs +50 -124
  5. package/dist/commands/lifecycle/install.mjs.map +1 -1
  6. package/dist/commands/memory/memory.mjs +41 -8
  7. package/dist/commands/memory/memory.mjs.map +1 -1
  8. package/dist/lib/install-assets.mjs +3 -0
  9. package/dist/lib/install-assets.mjs.map +1 -1
  10. package/dist/lib/runtime-manifest.mjs +2 -1
  11. package/dist/lib/runtime-manifest.mjs.map +1 -1
  12. package/dist/lib/types.d.mts +2 -1
  13. package/docs/architecture/storage-model.md +14 -11
  14. package/docs/architecture.md +26 -20
  15. package/docs/cli.md +15 -12
  16. package/docs/contributor-change-matrix.md +3 -2
  17. package/docs/performance-improvement-plan-v2.md +3 -9
  18. package/docs/project-structure-overview.md +39 -11
  19. package/docs/task-process/README.md +1 -1
  20. package/docs/task-process/common-flow.md +1 -1
  21. package/docs/task-process/final-verification.md +3 -1
  22. package/docs/task-process/implementation-option-selection.md +1 -1
  23. package/docs/task-process/implementation.md +1 -1
  24. package/docs/task-process/release-handoff.md +36 -39
  25. package/package.json +1 -2
  26. package/runtime/BUILD.json +2 -2
  27. package/runtime/agents/common.json +28 -0
  28. package/runtime/agents/operations/code-review.json +6 -0
  29. package/runtime/agents/operations/report-translation.json +6 -0
  30. package/runtime/agents/operations/schedule-verification.json +6 -0
  31. package/runtime/agents/roles/analyser.json +18 -0
  32. package/runtime/agents/roles/critic.json +18 -0
  33. package/runtime/agents/roles/designer.json +18 -0
  34. package/runtime/agents/roles/implementer.json +20 -0
  35. package/runtime/agents/roles/leader.json +20 -0
  36. package/runtime/agents/roles/planner.json +18 -0
  37. package/runtime/agents/roles/report-writer.json +19 -0
  38. package/runtime/agents/roles/translator.json +19 -0
  39. package/runtime/agents/roles/verifier.json +18 -0
  40. package/runtime/bin/lib/okstra/usage.sh +5 -5
  41. package/runtime/prompts/duties/acceptance-critic.json +32 -0
  42. package/runtime/prompts/duties/acceptance-verifier.json +32 -0
  43. package/runtime/prompts/duties/analysis-worker.json +32 -0
  44. package/runtime/prompts/duties/code-reviewer.json +32 -0
  45. package/runtime/prompts/duties/diagnosis-worker.json +32 -0
  46. package/runtime/prompts/duties/direction-selection-worker.json +32 -0
  47. package/runtime/prompts/duties/discovery-worker.json +32 -0
  48. package/runtime/prompts/duties/implementation-executor.json +32 -0
  49. package/runtime/prompts/duties/implementation-verifier.json +32 -0
  50. package/runtime/prompts/duties/lead.json +32 -0
  51. package/runtime/prompts/duties/planning-worker.json +36 -0
  52. package/runtime/prompts/duties/report-writer.json +32 -0
  53. package/runtime/prompts/duties/reverification-worker.json +32 -0
  54. package/runtime/prompts/duties/schedule-verifier.json +32 -0
  55. package/runtime/prompts/duties/scope-critic.json +32 -0
  56. package/runtime/prompts/duties/technical-verification-worker.json +32 -0
  57. package/runtime/prompts/duties/translator.json +32 -0
  58. package/runtime/prompts/launch.template.md +2 -1
  59. package/runtime/prompts/lead/adapters/cmux.md +1 -1
  60. package/runtime/prompts/lead/convergence.md +4 -4
  61. package/runtime/prompts/lead/okstra-lead-contract.md +115 -6
  62. package/runtime/prompts/lead/plan-body-verification.md +6 -6
  63. package/runtime/prompts/lead/report-writer.md +3 -3
  64. package/runtime/prompts/profiles/_common-contract.md +2 -2
  65. package/runtime/prompts/profiles/_implementation-diff-review.md +1 -1
  66. package/runtime/prompts/profiles/_implementation-executor.md +4 -1
  67. package/runtime/prompts/profiles/_implementation-self-check.md +1 -1
  68. package/runtime/prompts/profiles/_implementation-verifier.md +2 -2
  69. package/runtime/prompts/profiles/change-impact-analysis.json +31 -0
  70. package/runtime/prompts/profiles/change-impact-analysis.md +0 -20
  71. package/runtime/prompts/profiles/error-analysis.json +39 -0
  72. package/runtime/prompts/profiles/error-analysis.md +0 -25
  73. package/runtime/prompts/profiles/feature-analysis.json +31 -0
  74. package/runtime/prompts/profiles/feature-analysis.md +0 -20
  75. package/runtime/prompts/profiles/final-verification.json +30 -0
  76. package/runtime/prompts/profiles/final-verification.md +4 -23
  77. package/runtime/prompts/profiles/forbidden-actions.json +4 -3
  78. package/runtime/prompts/profiles/implementation-option-selection.json +31 -0
  79. package/runtime/prompts/profiles/implementation-option-selection.md +0 -20
  80. package/runtime/prompts/profiles/implementation-planning.json +40 -0
  81. package/runtime/prompts/profiles/implementation-planning.md +4 -29
  82. package/runtime/prompts/profiles/implementation.json +30 -0
  83. package/runtime/prompts/profiles/implementation.md +1 -20
  84. package/runtime/prompts/profiles/improvement-discovery.json +31 -0
  85. package/runtime/prompts/profiles/improvement-discovery.md +0 -20
  86. package/runtime/prompts/profiles/project-analysis.json +31 -0
  87. package/runtime/prompts/profiles/project-analysis.md +0 -20
  88. package/runtime/prompts/profiles/release-handoff.json +5 -0
  89. package/runtime/prompts/profiles/release-handoff.md +74 -74
  90. package/runtime/prompts/profiles/requirements-discovery.json +39 -0
  91. package/runtime/prompts/profiles/requirements-discovery.md +0 -25
  92. package/runtime/prompts/profiles/technical-verification.json +39 -0
  93. package/runtime/prompts/profiles/technical-verification.md +0 -25
  94. package/runtime/prompts/wizard/prompts.ko.json +14 -18
  95. package/runtime/python/okstra_ctl/adapters/hosts/antigravity/relay.md +1 -0
  96. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +3 -0
  97. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/manifest.json +1 -1
  98. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +4 -3
  99. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/worker-session.md +108 -0
  100. package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +1 -0
  101. package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +2 -0
  102. package/runtime/python/okstra_ctl/adapters/hosts/kimi/relay.md +2 -0
  103. package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +8 -1
  104. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +8 -0
  105. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +23 -6
  106. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +6 -2
  107. package/runtime/python/okstra_ctl/agent/invocation.py +168 -113
  108. package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +120 -0
  109. package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +107 -2
  110. package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +0 -49
  111. package/runtime/python/okstra_ctl/analysis_packet.py +4 -1
  112. package/runtime/python/okstra_ctl/application/open_worker.py +6 -1
  113. package/runtime/python/okstra_ctl/assignment_resolver.py +16 -5
  114. package/runtime/python/okstra_ctl/cmux.py +69 -20
  115. package/runtime/python/okstra_ctl/code_review_target.py +16 -8
  116. package/runtime/python/okstra_ctl/conformance.py +43 -0
  117. package/runtime/python/okstra_ctl/consumers.py +23 -8
  118. package/runtime/python/okstra_ctl/container.py +31 -8
  119. package/runtime/python/okstra_ctl/context_cost.py +11 -15
  120. package/runtime/python/okstra_ctl/contract_refreeze.py +156 -0
  121. package/runtime/python/okstra_ctl/convergence_engine.py +38 -0
  122. package/runtime/python/okstra_ctl/convergence_provenance.py +7 -1
  123. package/runtime/python/okstra_ctl/design_prep.py +34 -1
  124. package/runtime/python/okstra_ctl/dispatch_core.py +53 -27
  125. package/runtime/python/okstra_ctl/domain/host.py +5 -0
  126. package/runtime/python/okstra_ctl/domain/worker_runtime.py +10 -0
  127. package/runtime/python/okstra_ctl/error_report.py +4 -3
  128. package/runtime/python/okstra_ctl/execution_manifest.py +71 -18
  129. package/runtime/python/okstra_ctl/handoff.py +384 -286
  130. package/runtime/python/okstra_ctl/handoff_verification.py +25 -6
  131. package/runtime/python/okstra_ctl/implementation_stage.py +9 -0
  132. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +113 -0
  133. package/runtime/python/okstra_ctl/lead_progress.py +1 -1
  134. package/runtime/python/okstra_ctl/legacy_model_selection.py +2 -2
  135. package/runtime/python/okstra_ctl/manager_cli.py +92 -4
  136. package/runtime/python/okstra_ctl/manager_launch.py +1 -1
  137. package/runtime/python/okstra_ctl/manager_paths.py +14 -3
  138. package/runtime/python/okstra_ctl/manager_store.py +210 -3
  139. package/runtime/python/okstra_ctl/manager_sync.py +4 -1
  140. package/runtime/python/okstra_ctl/manager_view.py +2 -1
  141. package/runtime/python/okstra_ctl/model_discovery.py +30 -0
  142. package/runtime/python/okstra_ctl/model_io/lines.py +14 -1
  143. package/runtime/python/okstra_ctl/model_io/renderers.py +4 -3
  144. package/runtime/python/okstra_ctl/models.py +1 -1
  145. package/runtime/python/okstra_ctl/next_phase.py +16 -6
  146. package/runtime/python/okstra_ctl/operation_invocation.py +86 -0
  147. package/runtime/python/okstra_ctl/option_comparison.py +168 -0
  148. package/runtime/python/okstra_ctl/path_hints.py +9 -0
  149. package/runtime/python/okstra_ctl/paths.py +3 -0
  150. package/runtime/python/okstra_ctl/profile_show.py +42 -1
  151. package/runtime/python/okstra_ctl/registry/host_discovery.py +20 -12
  152. package/runtime/python/okstra_ctl/registry/host_registry.py +11 -0
  153. package/runtime/python/okstra_ctl/render.py +79 -0
  154. package/runtime/python/okstra_ctl/report_contract.py +1 -1
  155. package/runtime/python/okstra_ctl/report_html/view_models/release_handoff.py +21 -3
  156. package/runtime/python/okstra_ctl/report_synthesis_packet.py +177 -17
  157. package/runtime/python/okstra_ctl/report_translation.py +2 -1
  158. package/runtime/python/okstra_ctl/report_translation_dispatch.py +69 -9
  159. package/runtime/python/okstra_ctl/role_requirements.py +142 -129
  160. package/runtime/python/okstra_ctl/rollup.py +3 -1
  161. package/runtime/python/okstra_ctl/run.py +76 -29
  162. package/runtime/python/okstra_ctl/schedule_semantics.py +17 -6
  163. package/runtime/python/okstra_ctl/stage_fix_carry.py +23 -4
  164. package/runtime/python/okstra_ctl/stage_integrate.py +178 -18
  165. package/runtime/python/okstra_ctl/stage_map.py +16 -2
  166. package/runtime/python/okstra_ctl/stage_targets.py +209 -43
  167. package/runtime/python/okstra_ctl/team.py +22 -13
  168. package/runtime/python/okstra_ctl/time_report.py +2 -1
  169. package/runtime/python/okstra_ctl/usage_report.py +3 -1
  170. package/runtime/python/okstra_ctl/wizard/confirmation.py +3 -9
  171. package/runtime/python/okstra_ctl/wizard/ids.py +1 -1
  172. package/runtime/python/okstra_ctl/wizard/registry.py +1 -1
  173. package/runtime/python/okstra_ctl/wizard/state.py +3 -5
  174. package/runtime/python/okstra_ctl/wizard/steps_plan.py +12 -21
  175. package/runtime/python/okstra_ctl/worker_prompt_contract.py +5 -1
  176. package/runtime/python/okstra_ctl/worker_prompt_headers.py +35 -7
  177. package/runtime/python/okstra_ctl/worker_prompt_policy.py +66 -48
  178. package/runtime/python/okstra_ctl/workflow.py +1 -1
  179. package/runtime/python/okstra_ctl/worktree/__init__.py +3 -1
  180. package/runtime/python/okstra_ctl/worktree/naming.py +9 -0
  181. package/runtime/python/okstra_ctl/worktree_registry.py +38 -9
  182. package/runtime/python/okstra_token_usage/pricing.py +6 -4
  183. package/runtime/schemas/agent-common-v1.schema.json +34 -0
  184. package/runtime/schemas/agent-duty-v1.schema.json +38 -0
  185. package/runtime/schemas/agent-operation-v1.schema.json +11 -0
  186. package/runtime/schemas/agent-profile-v1.schema.json +46 -0
  187. package/runtime/schemas/agent-role-v1.schema.json +29 -0
  188. package/runtime/schemas/final-report-v2.0.schema.json +118 -97
  189. package/runtime/schemas/final-report-v3.0.schema.json +118 -97
  190. package/runtime/skills/okstra-brief-gen/SKILL.md +84 -4
  191. package/runtime/skills/okstra-chat/SKILL.md +2 -2
  192. package/runtime/skills/okstra-code-review/SKILL.md +23 -9
  193. package/runtime/skills/okstra-container-build/SKILL.md +10 -10
  194. package/runtime/skills/okstra-inspect/SKILL.md +1 -1
  195. package/runtime/skills/okstra-inspect/facets/cost.md +1 -1
  196. package/runtime/skills/okstra-inspect/facets/error-zip.md +9 -9
  197. package/runtime/skills/okstra-inspect/facets/errors.md +16 -16
  198. package/runtime/skills/okstra-inspect/facets/logs.md +7 -7
  199. package/runtime/skills/okstra-inspect/facets/recap.md +2 -2
  200. package/runtime/skills/okstra-inspect/facets/report.md +1 -1
  201. package/runtime/skills/okstra-inspect/facets/status.md +4 -3
  202. package/runtime/skills/okstra-inspect/facets/time.md +11 -10
  203. package/runtime/skills/okstra-manager/SKILL.md +18 -2
  204. package/runtime/skills/okstra-pr-gen/SKILL.md +6 -5
  205. package/runtime/skills/okstra-rollup/SKILL.md +5 -5
  206. package/runtime/skills/okstra-run/SKILL.md +31 -12
  207. package/runtime/skills/okstra-schedule-gen/SKILL.md +19 -14
  208. package/runtime/skills/okstra-setup/SKILL.md +12 -10
  209. package/runtime/skills/okstra-setup/references/project-config.md +7 -6
  210. package/runtime/skills/okstra-usage/SKILL.md +1 -1
  211. package/runtime/skills/okstra-user-response/SKILL.md +1 -1
  212. package/runtime/templates/manager/view.template.html +1 -0
  213. package/runtime/templates/report-writer-prompt-preamble.md +8 -0
  214. package/runtime/templates/reports/brief.template.md +14 -4
  215. package/runtime/templates/reports/html/i18n/en.json +5 -4
  216. package/runtime/templates/reports/html/i18n/ko.json +5 -4
  217. package/runtime/templates/reports/html/tasks/release-handoff.template.html +8 -5
  218. package/runtime/templates/reports/i18n/en.json +1 -1
  219. package/runtime/templates/reports/md/tasks/release-handoff.template.md +1 -1
  220. package/runtime/templates/reports/release-handoff-input.template.md +6 -4
  221. package/runtime/templates/translator-prompt-preamble.md +36 -0
  222. package/runtime/validators/checks/validate-assets-01.py +7 -8
  223. package/runtime/validators/validate-brief.py +70 -0
  224. package/runtime/validators/validate-implementation-plan-stages.py +2 -1
  225. package/runtime/validators/validate-run.py +72 -15
  226. package/runtime/validators/validate-schedule.py +9 -0
  227. package/docs/for-ai/README.md +0 -68
  228. package/docs/for-ai/skills/okstra-brief-gen.md +0 -262
  229. package/docs/for-ai/skills/okstra-chat.md +0 -34
  230. package/docs/for-ai/skills/okstra-code-review.md +0 -57
  231. package/docs/for-ai/skills/okstra-container-build.md +0 -129
  232. package/docs/for-ai/skills/okstra-inspect.md +0 -262
  233. package/docs/for-ai/skills/okstra-manager.md +0 -86
  234. package/docs/for-ai/skills/okstra-memory.md +0 -126
  235. package/docs/for-ai/skills/okstra-pr-gen.md +0 -49
  236. package/docs/for-ai/skills/okstra-rollup.md +0 -114
  237. package/docs/for-ai/skills/okstra-run.md +0 -250
  238. package/docs/for-ai/skills/okstra-schedule-gen.md +0 -240
  239. package/docs/for-ai/skills/okstra-setup.md +0 -167
  240. package/docs/for-ai/skills/okstra-usage.md +0 -29
  241. package/docs/for-ai/skills/okstra-user-response.md +0 -72
  242. package/runtime/agents/workers/claude-worker.md +0 -128
  243. package/runtime/agents/workers/report-writer-worker.md +0 -37
  244. package/runtime/agents/workers/translator-worker.md +0 -63
  245. package/runtime/prompts/duties/acceptance-critic.md +0 -44
  246. package/runtime/prompts/duties/acceptance-verifier.md +0 -44
  247. package/runtime/prompts/duties/analysis-worker.md +0 -44
  248. package/runtime/prompts/duties/code-reviewer.md +0 -44
  249. package/runtime/prompts/duties/common.md +0 -39
  250. package/runtime/prompts/duties/diagnosis-worker.md +0 -44
  251. package/runtime/prompts/duties/direction-selection-worker.md +0 -44
  252. package/runtime/prompts/duties/discovery-worker.md +0 -44
  253. package/runtime/prompts/duties/implementation-executor.md +0 -44
  254. package/runtime/prompts/duties/implementation-verifier.md +0 -44
  255. package/runtime/prompts/duties/lead.md +0 -44
  256. package/runtime/prompts/duties/planning-worker.md +0 -52
  257. package/runtime/prompts/duties/report-writer.md +0 -44
  258. package/runtime/prompts/duties/reverification-worker.md +0 -44
  259. package/runtime/prompts/duties/schedule-verifier.md +0 -44
  260. package/runtime/prompts/duties/scope-critic.md +0 -44
  261. package/runtime/prompts/duties/technical-verification-worker.md +0 -44
  262. package/runtime/prompts/duties/translator.md +0 -44
  263. package/runtime/python/okstra_ctl/pane_title.py +0 -154
@@ -27,6 +27,7 @@ from .implementation_options import (
27
27
  )
28
28
  from .report_narrative import writer_owned_data, writer_owned_schema
29
29
  from .schema_excerpt import build_schema_excerpt
30
+ from .stage_ledger import build_stage_ledger
30
31
  from .scope_provenance import brief_end_state_id_sequence
31
32
 
32
33
  from .exact_coverage import COVERAGE_VERDICT_PRECEDENCE
@@ -78,6 +79,10 @@ class ReportSynthesisPacket:
78
79
  participating_analysers: tuple[str, ...] = ()
79
80
  # 증분 판정. 저작 계약이 이번 run 의 stage 번호를 말할 수 있는 유일한 출처다.
80
81
  incremental_decision: dict[str, Any] | None = None
82
+ # 증분이 아닌 재계획이 이어받아야 하는 이전 계획의 stage 번호. 증분 판정과
83
+ # 배타적이다 — 둘이 함께 실리면 같은 stage 에 "그대로 옮겨라" 와 "다시
84
+ # 써라" 가 동시에 도달한다.
85
+ prior_plan_carry: dict[str, Any] | None = None
81
86
  # 넘겨받은 완성 리포트 스키마에서 뽑은 값. 작성자는 스키마 가지를 읽지 않으므로
82
87
  # 여기 적어 줘야 도달한다(2026-09-02 실측: `Human Summary` 절 누락, `Verdict
83
88
  # Token` 에 `analysis-complete` — 둘 다 HTML 렌더에서야 거절됐다).
@@ -106,20 +111,83 @@ class ReportSynthesisPacket:
106
111
  판정을 아는 여기뿐이다.
107
112
  """
108
113
  decision = self.incremental_decision
109
- if not decision:
110
- return []
111
- carried = ", ".join(str(stage) for stage in decision.get("carryStages", []))
112
- reverify = ", ".join(str(stage) for stage in decision.get("reverifyStages", []))
114
+ if decision:
115
+ return self._incremental_carry_instructions(decision)
116
+ if self.prior_plan_carry:
117
+ return self._prior_plan_carry_instructions(self.prior_plan_carry)
118
+ return []
119
+
120
+ def _incremental_carry_instructions(
121
+ self, decision: Mapping[str, Any],
122
+ ) -> list[str]:
123
+ carry_stages = [
124
+ stage for stage in decision.get("carryStages", []) if isinstance(stage, int)
125
+ ]
126
+ reverify_stages = [
127
+ stage
128
+ for stage in decision.get("reverifyStages", [])
129
+ if isinstance(stage, int)
130
+ ]
131
+ carried = ", ".join(str(stage) for stage in carry_stages)
132
+ # 이월 전부·재검증 0건(`--carry-all-reason`)이 정상 판정이다. 번호 목록을
133
+ # 그대로 끼워 넣으면 "Author only stage(s) ." 가 되어, 덧붙이러 온 run 에게
134
+ # stage 를 하나도 쓰지 말라고 지시한다.
135
+ authoring = (
136
+ f"Author only stage(s) {', '.join(str(s) for s in reverify_stages)}."
137
+ if reverify_stages
138
+ else "Re-author none of them; a stage you add takes the next number "
139
+ f"after {max(carry_stages, default=0)}."
140
+ )
113
141
  return [
114
142
  "This run is an incremental re-plan. Record the decision verbatim as "
115
143
  "`implementationPlanning.incrementalDecision` with keys mode, "
116
144
  "reverifyStages, carryStages, reason.",
117
145
  f"Copy stage rows {carried} from the \"{CARRIED_STAGES_LABEL}\" source "
118
146
  f"into `implementationPlanning.stages` unchanged - every field, byte for "
119
- f"byte. Author only stage(s) {reverify}. `okstra incremental-carry` "
147
+ f"byte. {authoring} `okstra incremental-carry` "
120
148
  f"rejects a carried stage that changed or is missing.",
121
149
  ]
122
150
 
151
+ def _prior_plan_carry_instructions(
152
+ self, carry: Mapping[str, Any],
153
+ ) -> list[str]:
154
+ """이전 계획의 번호를 다시 선언하게 하는 지시.
155
+
156
+ ADR-0015 는 stage 번호를 append-only 로 두고, 계획 파서는 발행된 모든
157
+ 계획의 행이 1..N 이기를 요구한다(`stage_map._validate_stage_numbers`).
158
+ 이전 stage 를 싣지 않은 리포트는 두 규칙을 동시에 만족시킬 수 없어,
159
+ `okstra prepare` 가 구현 run 을 거절하고 Stage 원장이 읽히지 않는다.
160
+ """
161
+ taken = [s for s in carry.get("stages", []) if isinstance(s, int)]
162
+ done = [s for s in carry.get("doneStages", []) if isinstance(s, int)]
163
+ if not taken:
164
+ return []
165
+ reauthorable = [stage for stage in taken if stage not in set(done)]
166
+ lines = [
167
+ "This task already has a plan on disk, so stage number(s) "
168
+ f"{', '.join(str(s) for s in taken)} are taken. "
169
+ "`implementationPlanning.stages` and `implementationPlanning.stageMap` "
170
+ "MUST declare every one of them in ascending order with no gap, and a "
171
+ f"stage you add takes the next number after {max(taken)}. A report whose "
172
+ "rows do not start at stage 1 is refused by report assembly and by "
173
+ "`okstra prepare` for the implementation run.",
174
+ ]
175
+ if done:
176
+ lines.append(
177
+ f"Stage(s) {', '.join(str(s) for s in done)} are already implemented: "
178
+ f"copy their rows from the \"{CARRIED_STAGES_LABEL}\" source unchanged "
179
+ "- every field, byte for byte - and do not fold their work into a new "
180
+ "stage."
181
+ + (
182
+ " Stage(s) "
183
+ f"{', '.join(str(s) for s in reauthorable)} are not implemented, so "
184
+ "you may re-author their bodies in place, keeping their numbers."
185
+ if reauthorable
186
+ else ""
187
+ )
188
+ )
189
+ return lines
190
+
123
191
  def _schema_instructions(self) -> list[str]:
124
192
  """스키마가 이 task type 에 못 박은 값을 저작 계약 문장으로 옮긴다."""
125
193
  lines: list[str] = []
@@ -161,7 +229,11 @@ class ReportSynthesisPacket:
161
229
  return lines
162
230
 
163
231
  def _carry_validation_rules(self) -> list[str]:
164
- return ["carried-stages-unchanged"] if self.incremental_decision else []
232
+ if self.incremental_decision:
233
+ return ["carried-stages-unchanged"]
234
+ if self.prior_plan_carry:
235
+ return ["prior-stage-numbers-declared"]
236
+ return []
165
237
 
166
238
  def _task_instructions(self) -> list[str]:
167
239
  if self.task_type == "technical-verification":
@@ -623,6 +695,52 @@ def carried_stage_rows(prior_data: Mapping[str, Any], carry_stages: list) -> lis
623
695
  ]
624
696
 
625
697
 
698
+ def read_prior_plan_carry(
699
+ project_root: Path, manifest: Mapping[str, Any],
700
+ ) -> dict[str, Any] | None:
701
+ """증분이 아닌 재계획이 이어받아야 하는 이전 계획의 stage 사실. 없으면 ``None``.
702
+
703
+ 증분 판정은 "무엇을 다시 검증하지 않을지" 를 말하고, 이건 "어떤 번호가 이미
704
+ 점유됐는지" 를 말한다. 후자는 판정의 mode 와 무관하게 참이다 — 선택 방향이
705
+ 바뀐 full 재계획도 stage 6 부터 붙여야 하고, 그러려면 1..5 를 다시 선언해야
706
+ 한다(ADR-0015 + `stage_map._validate_stage_numbers`).
707
+ """
708
+ if _string(manifest.get("taskType")) != "implementation-planning":
709
+ return None
710
+ task_root = _string(manifest.get("taskRootPath"))
711
+ if not task_root:
712
+ return None
713
+ ledger = build_stage_ledger(_resolve(project_root, task_root))
714
+ # `None` 은 첫 계획 run, `unreadable` 은 계획을 못 읽은 것이다. 후자에서
715
+ # 번호를 주장하면 읽지 못한 파일을 근거로 지시하는 셈이고, 그 사실은
716
+ # analysis packet 이 이미 통지한다.
717
+ if not ledger or ledger.get("unreadable"):
718
+ return None
719
+ records = [
720
+ row
721
+ for row in ledger.get("stages") or ()
722
+ if isinstance(row, Mapping) and isinstance(row.get("stage"), int)
723
+ ]
724
+ if not records:
725
+ return None
726
+ return {
727
+ "sourcePlanPath": _string(ledger.get("latestPlan")),
728
+ "stages": sorted(row["stage"] for row in records),
729
+ "doneStages": sorted(
730
+ row["stage"] for row in records if row.get("status") == "done"
731
+ ),
732
+ }
733
+
734
+
735
+ @dataclass(frozen=True)
736
+ class CarriedStages:
737
+ """이월 source 파일과 그것을 만든 근거. 둘 중 하나만 채워진다."""
738
+
739
+ source_path: str
740
+ incremental_decision: dict[str, Any] | None = None
741
+ prior_plan: dict[str, Any] | None = None
742
+
743
+
626
744
  def carried_stages_path(narrative_path: Path) -> Path:
627
745
  _, markdown_path = report_synthesis_packet_paths(narrative_path)
628
746
  return markdown_path.with_name(
@@ -637,24 +755,60 @@ def materialize_carried_stages(
637
755
  project_root: Path,
638
756
  manifest: Mapping[str, Any],
639
757
  narrative_path: Path,
640
- ) -> tuple[str, dict] | None:
641
- """이월 stage 본문을 파일로 남기고 (프로젝트 상대 경로, 판정) 을 돌려준다.
758
+ ) -> CarriedStages | None:
759
+ """이월 stage 본문을 파일로 남기고 그 근거와 함께 돌려준다.
642
760
 
643
761
  묶음의 source 는 파일과 그 다이제스트다. 파생 내용을 특례로 인라인하지 않고
644
762
  실제 파일로 만드는 이유는 두 가지다 — 다이제스트가 다른 source 와 같은
645
763
  의미를 갖고, 어긋났을 때 사람이 열어 볼 수 있다.
764
+
765
+ 이월 근거는 둘이고 증분 판정이 이긴다. 증분이면 검증을 건너뛰는 stage 의
766
+ 본문이 필요하고, 아니면 번호를 점유한 stage 전부의 본문이 필요하다.
646
767
  """
647
768
  decision = read_incremental_decision(project_root, manifest)
648
- if decision is None:
769
+ if decision is not None:
770
+ return _materialize_stage_rows(
771
+ project_root=project_root,
772
+ narrative_path=narrative_path,
773
+ source_plan=_string(decision.get("prevDataPath")),
774
+ stages=[
775
+ stage for stage in decision["carryStages"] if isinstance(stage, int)
776
+ ],
777
+ carried=CarriedStages("", incremental_decision=decision),
778
+ )
779
+ prior_plan = read_prior_plan_carry(project_root, manifest)
780
+ if prior_plan is None:
649
781
  return None
650
- prior = _resolve(project_root, _string(decision.get("prevDataPath")))
782
+ # 본문이 동결되는 것은 완료된 stage 뿐이다. 완료가 없으면 옮길 행이 없고,
783
+ # 빈 source 를 실으면 작성자가 옮길 것 없는 파일을 읽는다 — 번호를 이어받는
784
+ # 지시만 남긴다.
785
+ if not prior_plan["doneStages"]:
786
+ return CarriedStages("", prior_plan=prior_plan)
787
+ return _materialize_stage_rows(
788
+ project_root=project_root,
789
+ narrative_path=narrative_path,
790
+ source_plan=_string(prior_plan.get("sourcePlanPath")),
791
+ stages=list(prior_plan["doneStages"]),
792
+ carried=CarriedStages("", prior_plan=prior_plan),
793
+ )
794
+
795
+
796
+ def _materialize_stage_rows(
797
+ *,
798
+ project_root: Path,
799
+ narrative_path: Path,
800
+ source_plan: str,
801
+ stages: list[int],
802
+ carried: CarriedStages,
803
+ ) -> CarriedStages | None:
804
+ prior = _resolve(project_root, source_plan)
651
805
  if not prior.is_file():
652
806
  return None
653
807
  try:
654
808
  prior_data = load_owned_object(prior, artifact="prior planning report")
655
809
  except (OSError, UnicodeError, JsonBoundaryError):
656
810
  return None
657
- rows = carried_stage_rows(prior_data, decision["carryStages"])
811
+ rows = carried_stage_rows(prior_data, stages)
658
812
  if not rows:
659
813
  return None
660
814
  path = carried_stages_path(narrative_path)
@@ -662,16 +816,21 @@ def materialize_carried_stages(
662
816
  path,
663
817
  {
664
818
  "schemaVersion": "1.0",
665
- "sourcePlanPath": _string(decision.get("prevDataPath")),
666
- "carryStages": list(decision["carryStages"]),
819
+ "sourcePlanPath": source_plan,
820
+ "carryStages": list(stages),
667
821
  "stages": rows,
668
822
  },
669
823
  artifact="carried plan stages",
670
824
  )
671
825
  try:
672
- return str(path.resolve().relative_to(project_root.resolve())), decision
826
+ relative = str(path.resolve().relative_to(project_root.resolve()))
673
827
  except ValueError:
674
- return str(path), decision
828
+ relative = str(path)
829
+ return CarriedStages(
830
+ relative,
831
+ incremental_decision=carried.incremental_decision,
832
+ prior_plan=carried.prior_plan,
833
+ )
675
834
 
676
835
 
677
836
  def _source_specs(
@@ -878,7 +1037,7 @@ def build_report_synthesis_packet(
878
1037
  active_context,
879
1038
  team_state,
880
1039
  narrative_path,
881
- carried[0] if carried else "",
1040
+ carried.source_path if carried else "",
882
1041
  ),
883
1042
  )
884
1043
  task_type = _string(manifest.get("taskType"))
@@ -918,7 +1077,8 @@ def build_report_synthesis_packet(
918
1077
  accounting_snapshot=_accounting_snapshot(team_state),
919
1078
  original_requirement_ids=original_requirement_ids,
920
1079
  participating_analysers=participating_analysers,
921
- incremental_decision=carried[1] if carried else None,
1080
+ incremental_decision=carried.incremental_decision if carried else None,
1081
+ prior_plan_carry=carried.prior_plan if carried else None,
922
1082
  )
923
1083
 
924
1084
 
@@ -182,6 +182,7 @@ STRUCTURAL_KEYS = frozenset({
182
182
  "area", # slug-shaped ('scan-coverage')
183
183
  "artifact",
184
184
  "baseBranch",
185
+ "baseKind",
185
186
  "baseRefSha",
186
187
  "baseSha",
187
188
  "boundary",
@@ -194,7 +195,6 @@ STRUCTURAL_KEYS = frozenset({
194
195
  "clarificationId",
195
196
  "classification",
196
197
  "claudeCode",
197
- "collectorBranch",
198
198
  "command",
199
199
  "commandOrObservation",
200
200
  "commandOrTest",
@@ -232,6 +232,7 @@ STRUCTURAL_KEYS = frozenset({
232
232
  "gitStatusShort",
233
233
  "h1c",
234
234
  "h2",
235
+ "headBranch",
235
236
  "id",
236
237
  "impact", # 'high' / 'medium' alongside prose
237
238
  "impactKind",
@@ -10,9 +10,11 @@ fontsninja-v3-site dev-10628-3 implementation-option-selection, grok 리드):
10
10
  한 줄을 남겼고 HTML 은 영어로 남았다.
11
11
 
12
12
  이 모듈은 그 절차를 `report-finalize` 의 한 단계로 내린다. 예약이 없으면 지시문과
13
- 프롬프트를 발행하고(`agent-prompt materialize` 와 같은 코드), CLI 래퍼 디스패처로
14
- 워커를 띄운 뒤 사이드카가 생겼는지 본다. 디스패치 기록과 결과 링크는
15
- `dispatch_core` 가 배치 안에서 이미 하므로 여기서 다시 하지 않는다.
13
+ 프롬프트를 발행하고(`agent-prompt materialize` 와 같은 코드), 워커를 띄운 뒤
14
+ 사이드카가 생겼는지 본다. cmux run 이면 다른 워커와 같은 pane 으로 띄우고 끝날
15
+ 때까지 기다린다(`team dispatch` + `team await` 와 같은 코드). 그 밖의 run 은 CLI
16
+ 래퍼 디스패처로 띄운다. 디스패치 기록과 결과 링크는 `dispatch_core` 가 배치 안에서
17
+ 이미 하므로 여기서 다시 하지 않는다.
16
18
  """
17
19
  from __future__ import annotations
18
20
 
@@ -25,7 +27,15 @@ from typing import Any, Mapping
25
27
 
26
28
  from . import worker_dispatch
27
29
  from .agent.prompt_cli.cli import main as agent_prompt_main
28
- from .dispatch_core import translator_reservations
30
+ from .application.dispatch_assignments import dispatch_assignments
31
+ from .dispatch_core import (
32
+ BACKEND_CMUX_PANE,
33
+ await_dispatches,
34
+ dispatch_plan,
35
+ translator_reservations,
36
+ )
37
+ from .ports.worker_dispatch import WorkerDispatchRequest
38
+ from .team import team_dispatch_port
29
39
  from .dispatch_state import (
30
40
  DispatchError,
31
41
  load_json_object,
@@ -111,7 +121,9 @@ def translate_report(
111
121
  metadata_path = ensure_translator_reservation(
112
122
  project_root, manifest_path, manifest, lang
113
123
  )
114
- dispatch = dispatch_translator(project_root, workspace_root, manifest_path)
124
+ dispatch = dispatch_translator(
125
+ project_root, workspace_root, manifest_path, manifest
126
+ )
115
127
  except TranslateError as exc:
116
128
  return TranslateOutcome(1, "", str(exc))
117
129
  if sidecar.is_file():
@@ -166,13 +178,22 @@ def ensure_translator_reservation(
166
178
 
167
179
 
168
180
  def dispatch_translator(
169
- project_root: Path, workspace_root: Path, manifest_path: Path,
181
+ project_root: Path,
182
+ workspace_root: Path,
183
+ manifest_path: Path,
184
+ manifest: Mapping[str, Any],
170
185
  ) -> TranslateOutcome:
171
- """`okstra worker-dispatch --workers translator` 와 같은 코드를 프로세스 안에서 돈다.
186
+ """번역 워커를 띄우고 끝날 때까지 기다린다.
172
187
 
173
- 디스패처는 결과 JSON 을 stdout 에 찍는다. Phase 7 의 stdout 은 finalize 결과
174
- JSON 하나여야 하므로 여기서 받아 단계 결과로 넘긴다.
188
+ cmux run 은 pane 으로 띄운다 — 사용자가 다른 워커처럼 진행을 화면에서 본다.
189
+ 그 밖의 run 은 `okstra worker-dispatch --workers translator` 와 같은 코드를
190
+ 프로세스 안에서 돈다. 디스패처는 결과 JSON 을 stdout 에 찍는다. Phase 7 의
191
+ stdout 은 finalize 결과 JSON 하나여야 하므로 여기서 받아 단계 결과로 넘긴다.
175
192
  """
193
+ if str(manifest.get("terminalBackend") or "") == BACKEND_CMUX_PANE:
194
+ return _dispatch_translator_pane(
195
+ project_root, workspace_root, manifest_path, manifest
196
+ )
176
197
  out, err = io.StringIO(), io.StringIO()
177
198
  with contextlib.redirect_stdout(out), contextlib.redirect_stderr(err):
178
199
  try:
@@ -187,6 +208,45 @@ def dispatch_translator(
187
208
  return TranslateOutcome(code, out.getvalue(), err.getvalue())
188
209
 
189
210
 
211
+ def _dispatch_translator_pane(
212
+ project_root: Path,
213
+ workspace_root: Path,
214
+ manifest_path: Path,
215
+ manifest: Mapping[str, Any],
216
+ ) -> TranslateOutcome:
217
+ """`okstra team dispatch --workers translator` 뒤 `okstra team await` 와 같다.
218
+
219
+ pane 을 열지 못하면 디스패처가 CLI 래퍼로 강등하고 `DEGRADED` 줄과
220
+ `degradedReason` 을 남긴다 — 그 경우도 여기서 같은 대기로 정산된다. pane 은
221
+ run 끝의 `okstra team teardown` 이 기록을 보고 닫는다.
222
+ """
223
+ request = WorkerDispatchRequest(
224
+ project_root=project_root,
225
+ run_manifest=manifest_path,
226
+ workspace_root=workspace_root,
227
+ okstra_bin=None,
228
+ requested_workers=(TRANSLATOR_WORKER_ID,),
229
+ idle_timeout_seconds=None,
230
+ dispatch_kind="initial",
231
+ jobs_file=None,
232
+ )
233
+ out, err = io.StringIO(), io.StringIO()
234
+ with contextlib.redirect_stdout(out), contextlib.redirect_stderr(err):
235
+ try:
236
+ plan = dispatch_assignments(request, team_dispatch_port(manifest))
237
+ backend_plan = plan.backend_plan
238
+ if backend_plan is None:
239
+ raise TranslateError(
240
+ "translator pane dispatch did not produce a backend plan"
241
+ )
242
+ code = dispatch_plan(backend_plan, wait=False)
243
+ if code == 0:
244
+ code = await_dispatches(backend_plan, heartbeat_seconds=0)
245
+ except (DispatchError, OSError) as exc:
246
+ raise TranslateError(f"translator dispatch failed: {exc}") from exc
247
+ return TranslateOutcome(code, out.getvalue(), err.getvalue())
248
+
249
+
190
250
  def _materialize_translator(
191
251
  project_root: Path,
192
252
  manifest_path: Path,