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
@@ -64,17 +64,36 @@ def require_finished_verification(manifest: dict, data: dict) -> None:
64
64
 
65
65
 
66
66
  def verification_row_is_current(row: dict) -> bool:
67
- """저장한 승인 행이 같은 단계의 최신 완료 검증을 가리키는지 확인한다."""
67
+ """저장한 승인 행이 그 단계의 최신 완료 검증을 가리키는지 확인한다.
68
+
69
+ 근거는 두 모양이다. 단독-stage 행은 그 stage 범위 run 을 가리키고 검증된
70
+ head 가 곧 그 stage 의 커밋이다. 전체 task 행은 task 범위 run 을 가리키고,
71
+ 검증된 head 는 tip stage 의 커밋이므로 행의 `verified_head_commit` 과
72
+ 대조한다 — 그 stage 가 보고서의 `stageReports` 에 실려 있어야 한다.
73
+ """
68
74
  try:
69
75
  report = final_report_data_path(Path(str(row.get("report_path") or "")))
70
76
  ref = RunRef.from_report_path(report.resolve())
71
77
  latest, manifest, data = load_latest_verification(ref.run_dir)
72
78
  require_finished_verification(manifest, data)
73
- return (latest == report.resolve()
74
- and ref.stage == row.get("stage")
75
- and manifest.get("taskKey") == row.get("impl_task_key")
76
- and (data.get("finalVerification") or {}).get("sourceImplementationReport", {}).get("capturedHeadSha") == row.get("head_commit")
77
- and data.get("finalVerdict") == row.get("final_verdict"))
79
+ if (latest != report.resolve()
80
+ or manifest.get("taskKey") != row.get("impl_task_key")
81
+ or data.get("finalVerdict") != row.get("final_verdict")):
82
+ return False
83
+ captured = (data.get("finalVerification") or {}).get(
84
+ "sourceImplementationReport", {}).get("capturedHeadSha")
85
+ if row.get("verification_scope") == "whole-task":
86
+ covered = {
87
+ entry.get("stage")
88
+ for entry in (data.get("finalVerification") or {}).get("stageReports") or ()
89
+ if isinstance(entry, dict)
90
+ }
91
+ return (ref.stage is None
92
+ and data.get("verificationScope") == "whole-task"
93
+ and row.get("stage") in covered
94
+ and captured == row.get("verified_head_commit"))
95
+ return (ref.stage == row.get("stage")
96
+ and captured == row.get("head_commit"))
78
97
  except (ValueError, JsonBoundaryError):
79
98
  # expected-miss: 이전 형식·재검증 중인 행에는 현재 인계 자격이 없다.
80
99
  return False
@@ -177,6 +177,15 @@ def claim_implementation_stage_run(
177
177
  candidate_base=head_sha,
178
178
  project_root=Path(task_worktree_path),
179
179
  plan_run_root=plan_run_root,
180
+ # 선행이 둘 이상인 stage 는 그 선행들만 담은 브랜치에서 시작한다.
181
+ # 그 브랜치를 만들 수 있는 것은 이 경로뿐이고, run.py 가 이미 task-key
182
+ # provision 락을 쥐고 있어 원시연산은 락을 다시 잡지 않는다.
183
+ identity=stage_targets.StageWorktreeIdentity(
184
+ project_id=inp.project_id,
185
+ task_group=task_group_segment,
186
+ task_id=task_id_segment,
187
+ work_category=inp.work_category,
188
+ ),
180
189
  )
181
190
  except stage_targets.StageTargetError as exc:
182
191
  raise _as_implementation_stage_error(exc) from exc
@@ -131,6 +131,8 @@ _REQUIRED_PROMPT_RESOURCES_HEADING = "## Required prompt resources"
131
131
  _HTML_COMMENTS = re.compile(r"<!--.*?-->\n?", re.DOTALL)
132
132
  _AGENT_CONTRACT_KEYS = {
133
133
  "schemaVersion",
134
+ # 이 run 이 동결한 계약 형식. 없는 run 은 Markdown 직무를 동결했다.
135
+ "contractFormatVersion",
134
136
  "dutyRootPath",
135
137
  "catalogDigest",
136
138
  "invocationReservationRootPath",
@@ -214,6 +216,117 @@ def materialize_initial_prompts(
214
216
  ) from exc
215
217
 
216
218
 
219
+ def declared_delivery_mode(manifest: Mapping[str, Any]) -> PromptDeliveryMode:
220
+ """이 run 의 어댑터가 선언한 초기 프롬프트 배송 방식.
221
+
222
+ 선언은 `leadAdapter.initialPromptDeliveryMode` 에 있고, 그 값은 render 가
223
+ 호스트 descriptor(어댑터가 워커 디스패치를 가져갔으면 그 어댑터)에서
224
+ 옮겨 적은 것이다. 선언이 없는 매니페스트는 이 필드가 생기기 전에 준비된
225
+ run 이므로 그때의 동작인 `eager-include` 로 간다. 아는 값이 아니면 멈춘다 —
226
+ 프롬프트를 발행하고 나면 그 프롬프트는 불변이라, 잘못 고른 배송 방식은
227
+ 나중에 고칠 수 없다.
228
+ """
229
+ adapter = manifest.get("leadAdapter")
230
+ declared = (
231
+ _string_value(adapter.get("initialPromptDeliveryMode"))
232
+ if isinstance(adapter, Mapping)
233
+ else ""
234
+ )
235
+ if not declared:
236
+ return PromptDeliveryMode.EAGER_INCLUDE
237
+ try:
238
+ return PromptDeliveryMode(declared)
239
+ except ValueError:
240
+ allowed = ", ".join(mode.value for mode in PromptDeliveryMode)
241
+ raise InitialPromptMaterializationError(
242
+ "required_input_missing",
243
+ "leadAdapter.initialPromptDeliveryMode is not a delivery mode: "
244
+ f"{declared!r} (allowed: {allowed})",
245
+ ) from None
246
+
247
+
248
+ def _read_side_context(
249
+ project_root: Path, run_manifest_path: Path, runtime_root: Path,
250
+ ) -> "_MaterializationContext":
251
+ """프롬프트를 쓰지 않는 호출자를 위한 컨텍스트.
252
+
253
+ 읽는 것은 매니페스트·active run context·런타임 루트뿐이다. 발행 권한 검사와
254
+ team-state 는 프롬프트를 쓰는 쪽(호출한 CLI)의 몫이다.
255
+ """
256
+ root = project_root.resolve()
257
+ manifest = _load_json_object(
258
+ _resolve_input_path(root, run_manifest_path), "run manifest"
259
+ )
260
+ return _MaterializationContext(
261
+ request=InitialPromptMaterializationRequest(
262
+ project_root=root,
263
+ run_manifest_path=run_manifest_path,
264
+ runtime_root=runtime_root,
265
+ delivery_mode=declared_delivery_mode(manifest),
266
+ workers=(),
267
+ ),
268
+ project_root=root,
269
+ manifest=manifest,
270
+ team_state={},
271
+ active_context=hydrate_active_run_context(
272
+ _load_referenced_json(root, manifest, "activeRunContextPath")
273
+ ),
274
+ )
275
+
276
+
277
+ def implementation_anchor_lines(
278
+ *,
279
+ project_root: Path,
280
+ run_manifest_path: Path,
281
+ runtime_root: Path,
282
+ worker_id: str,
283
+ ) -> list[str]:
284
+ """이 워커의 프롬프트가 싣는 구현 앵커 줄. 구현 audience 가 아니면 빈 목록.
285
+
286
+ executor 의 mutating cwd, 그리고 executor·verifier 공통의
287
+ `**Approved plan:**` / `**Stage for this implementation run:**` 이다.
288
+ `okstra agent-prompt jobs` 는 이 두 헤더를 요구하므로, 리드가 회수 경로로
289
+ 발행한 프롬프트는 그 검사에서 거절됐다(실측 2026-09-23, fontradar-v2-api
290
+ dev-10860 stage-3: 리드가 헤더 두 줄을 손으로 지시문에 붙여 통과시켰다).
291
+ """
292
+ context = _read_side_context(project_root, run_manifest_path, runtime_root)
293
+ return _implementation_anchor_lines(
294
+ context, _prompt_plan(context.manifest, worker_id)
295
+ )
296
+
297
+
298
+ def required_resource_block(
299
+ *,
300
+ project_root: Path,
301
+ run_manifest_path: Path,
302
+ runtime_root: Path,
303
+ worker_id: str,
304
+ ) -> str:
305
+ """이 워커의 프롬프트가 싣는 필수 자원 블록. 없으면 빈 문자열.
306
+
307
+ 디스패치 시점의 composer 와 같은 블록이다 — executor 는 BLOCKING 게이트 세
308
+ 본문과 stage discipline, verifier 는 `_implementation-verifier.md`, 둘 다
309
+ clarification 권한 블록과 fix-run carry. 리드가 `okstra agent-prompt
310
+ materialize` 로 먼저 발행한 프롬프트는 디스패치가 기존 파일로 그대로 쓰므로
311
+ composer 가 돌지 않았고, 이 블록이 통째로 빠졌다(실측 2026-09-22,
312
+ fontradar-v2-api dev-10860 implementation stage-1: executor·verifier 4개
313
+ 프롬프트 모두 0건). 그 명령이 이 함수로 같은 블록을 붙인다.
314
+ """
315
+ context = _read_side_context(project_root, run_manifest_path, runtime_root)
316
+ plan = _prompt_plan(context.manifest, worker_id)
317
+ item = _PromptItem(
318
+ worker=InitialPromptWorkerRequest(worker_id, ""),
319
+ final_path=Path(),
320
+ plan=plan,
321
+ existed=False,
322
+ clarification_input=_resolve_clarification_input(context, plan),
323
+ )
324
+ item = _with_prompt_resources(
325
+ item, _resolve_prompt_resources(context, plan, existed=False)
326
+ )
327
+ return "\n".join(_resource_lines(context, item)).strip()
328
+
329
+
217
330
  def _materialize_initial_prompts(
218
331
  request: InitialPromptMaterializationRequest,
219
332
  ) -> dict[str, Path]:
@@ -243,7 +243,7 @@ def _append(args: argparse.Namespace) -> int:
243
243
 
244
244
  _CLI_EPILOG = r"""Usage:
245
245
  okstra lead-progress append --project-root <dir> --run-manifest <path> --phase phase-1-intake
246
- okstra lead-progress append --project-root <dir> --run-manifest <path> --phase phase-4-dispatch --worker codex-worker --field model=gpt-5.6-sol
246
+ okstra lead-progress append --project-root <dir> --run-manifest <path> --phase phase-4-dispatch --worker codex-worker --field model=gpt-6-sol
247
247
  okstra lead-progress append --project-root <dir> --run-manifest <path> --phase phase-5-collect --worker codex-worker --field status=completed
248
248
 
249
249
  `progressLine` in the output is the line to emit to the user, verbatim.
@@ -11,7 +11,7 @@ from .domain.host import (
11
11
  HostSessionContext,
12
12
  )
13
13
  from .domain.role import RoleCatalogError, normalize_role
14
- from .role_requirements import RoleProfile, RoleRequirement
14
+ from .role_requirements import RoleProfile, RoleRequirement, StaticRoleRequirement
15
15
 
16
16
 
17
17
  class ModelSelectionInputError(ValueError):
@@ -404,7 +404,7 @@ def _validate_selection(
404
404
  explicit_counts: Mapping[str, int],
405
405
  ) -> None:
406
406
  requirements = {row.role: row for row in profile.roles if not row.dynamic}
407
- requirements["leader"] = RoleRequirement("leader", 1, 1, 1, "lead")
407
+ requirements["leader"] = StaticRoleRequirement("leader", 1, 1, 1, "lead")
408
408
  for role in sorted(set(counts) | set(models)):
409
409
  requirement = requirements.get(role)
410
410
  if requirement is None:
@@ -22,7 +22,13 @@ from .manager_store import (
22
22
  link_project,
23
23
  list_managers,
24
24
  list_projects,
25
+ list_task_groups,
25
26
  list_tasks,
27
+ remove_child,
28
+ remove_task,
29
+ remove_task_group,
30
+ unlink_project,
31
+ update_task,
26
32
  )
27
33
  from .manager_split import split_task
28
34
  from .manager_sync import status_task, sync_task
@@ -37,6 +43,8 @@ _LIST_ROWS = {
37
43
  ("project count", "projectCount"), ("task count", "taskCount"))),
38
44
  "list-projects": ("Project", (("ID", "projectId"), ("root", "projectRoot"), ("role", "role"),
39
45
  ("tags", "tags"), ("linked at", "linkedAt"))),
46
+ "list-task-groups": ("Task group", (("name", "taskGroup"), ("created at", "createdAt"),
47
+ ("task count", "taskCount"))),
40
48
  "list-tasks": ("Task", (("group", "taskGroup"), ("ID", "taskId"), ("objective", "objective"),
41
49
  ("progress mode", "progressMode"), ("child count", "childCount"),
42
50
  ("created at", "createdAt"))),
@@ -47,7 +55,8 @@ def render_manager_text(command: str, payload: dict | list[dict]) -> str:
47
55
  """manager 모델 표면에 승인된 식별자와 상태만 투영한다."""
48
56
  allowed = {"init", "discover-projects", "new-project", "new-task-group", "new-task",
49
57
  "task-assign", "task-note", "task-sync", "task-status", "task-run", "task-split",
50
- "list-managers", "list-projects", "list-tasks", "view"}
58
+ "list-managers", "list-projects", "list-task-groups", "list-tasks", "view",
59
+ "task-update", "task-remove-child", "remove-project", "remove-task", "remove-task-group"}
51
60
  if command not in allowed:
52
61
  raise ValueError("unknown manager text purpose")
53
62
  rows = [f"Okstra manager {command}\n", line("Status", "ready")]
@@ -94,11 +103,30 @@ def render_manager_text(command: str, payload: dict | list[dict]) -> str:
94
103
  for label, key in (("task key", "taskKey"), ("project ID", "projectId"), ("ticket", "ticketId"),
95
104
  ("path", "briefPath"), ("status", "briefStatus")):
96
105
  rows.append(line(f"Brief {index} {label}", brief.get(key)))
106
+ if command == "task-update":
107
+ rows.append(line("Changed fields", ", ".join(payload.get("changedFields") or []) or "none"))
108
+ rows.extend(value_lines("Manifest", payload.get("manifest")))
109
+ if command.startswith("remove-") or command == "task-remove-child":
110
+ rows.extend(_render_removal(payload))
97
111
  if command == "task-sync" and "syncedAt" in payload:
98
112
  rows.append(line("Synced at", payload.get("syncedAt")))
99
113
  return "".join(rows)
100
114
 
101
115
 
116
+ # 삭제 명령은 --confirm 이 없으면 대상만 보여 준다. 무엇이 지워지고 무엇이 남는지 한 줄씩 찍는다.
117
+ def _render_removal(payload: dict) -> list[str]:
118
+ rows = [line("Removed", "yes" if payload.get("removed") else "no — dry run; rerun with --confirm")]
119
+ for label, key in (("Removed path", "path"), ("Child count", "childCount")):
120
+ if key in payload:
121
+ rows.append(line(label, payload.get(key)))
122
+ for label, key in (("Kept child", "keptChildren"), ("Removed task", "taskIds")):
123
+ values = payload.get(key)
124
+ if isinstance(values, list):
125
+ rows.append(line(f"{label} count", len(values)))
126
+ rows.extend(line(f"{label} {index}", value) for index, value in enumerate(values, 1))
127
+ return rows
128
+
129
+
102
130
  def _render_list(command: str, items: list[dict]) -> list[str]:
103
131
  prefix, fields = _LIST_ROWS[command]
104
132
  rows: list[str] = []
@@ -126,10 +154,12 @@ def _render_manager_status(payload: dict) -> list[str]:
126
154
  continue
127
155
  for label, key in (("task key", "taskKey"), ("run status", "latestRunStatus"),
128
156
  ("current phase", "currentPhase"), ("work status", "workStatus"),
129
- ("summary", "summaryBucket"), ("ticket", "ticketId"), ("brief path", "briefPath"),
157
+ ("summary", "summaryBucket"), ("project linked", "projectLinked"),
158
+ ("ticket", "ticketId"), ("brief path", "briefPath"),
130
159
  ("report path", "latestReportRecordPath"), ("error", "error")):
131
160
  if key in child:
132
- rows.append(line(f"Child {index} {label}", child.get(key)))
161
+ value = child.get(key)
162
+ rows.append(line(f"Child {index} {label}", ("yes" if value else "no") if isinstance(value, bool) else value))
133
163
  return rows
134
164
 
135
165
 
@@ -188,11 +218,18 @@ _CLI_EPILOG = r"""Usage:
188
218
  okstra manager task note --manager-id <id> --task-group <task-group> --task-id <task-id> --scope <shared|project> --body <text>
189
219
  okstra manager task run --manager-id <id> --project-id <project-id> --task-group <task-group> --task-id <task-id>
190
220
  okstra manager task split --manager-id <id> --task-group <task-group> --task-id <task-id> --plan <split-plan.json> [--overwrite]
221
+ okstra manager task update --manager-id <id> --task-group <task-group> --task-id <task-id> [--objective <text>] [--common-brief <path>] [--progress-mode <manual|auto>]
222
+ okstra manager task remove-child --manager-id <id> --task-group <task-group> --task-id <task-id> --project-id <project-id> [--child-task-id <id>] [--confirm]
223
+ okstra manager remove project --manager-id <id> --project-id <project-id> [--confirm]
224
+ okstra manager remove task --manager-id <id> --task-group <task-group> --task-id <task-id> [--confirm]
225
+ okstra manager remove task-group --manager-id <id> --task-group <task-group> [--confirm]
191
226
  okstra manager list managers
192
227
  okstra manager list projects --manager-id <id>
228
+ okstra manager list task-groups --manager-id <id>
193
229
  okstra manager list tasks --manager-id <id>
194
230
  okstra manager view --manager-id <id>
195
231
 
232
+ remove and task remove-child only show their targets unless --confirm is given.
196
233
  --workspace-root is owned by this command.
197
234
  """
198
235
  _CLI_DESCRIPTION = "Manage cross-project okstra tasks."
@@ -277,10 +314,42 @@ def _parser() -> argparse.ArgumentParser:
277
314
  split.add_argument("--plan", required=True)
278
315
  split.add_argument("--overwrite", action="store_true")
279
316
 
317
+ update = task_sub.add_parser("update")
318
+ update.add_argument("--manager-id", required=True)
319
+ update.add_argument("--task-group", required=True)
320
+ update.add_argument("--task-id", required=True)
321
+ update.add_argument("--objective", default=None)
322
+ update.add_argument("--common-brief", default=None)
323
+ update.add_argument("--progress-mode", choices=["manual", "auto"], default=None)
324
+
325
+ remove_child_cmd = task_sub.add_parser("remove-child")
326
+ remove_child_cmd.add_argument("--manager-id", required=True)
327
+ remove_child_cmd.add_argument("--task-group", required=True)
328
+ remove_child_cmd.add_argument("--task-id", required=True)
329
+ remove_child_cmd.add_argument("--project-id", required=True)
330
+ remove_child_cmd.add_argument("--child-task-id", default=None)
331
+ remove_child_cmd.add_argument("--confirm", action="store_true")
332
+
333
+ remove = sub.add_parser("remove")
334
+ remove_sub = remove.add_subparsers(dest="remove_command", required=True)
335
+ remove_project = remove_sub.add_parser("project")
336
+ remove_project.add_argument("--manager-id", required=True)
337
+ remove_project.add_argument("--project-id", required=True)
338
+ remove_project.add_argument("--confirm", action="store_true")
339
+ remove_task_cmd = remove_sub.add_parser("task")
340
+ remove_task_cmd.add_argument("--manager-id", required=True)
341
+ remove_task_cmd.add_argument("--task-group", required=True)
342
+ remove_task_cmd.add_argument("--task-id", required=True)
343
+ remove_task_cmd.add_argument("--confirm", action="store_true")
344
+ remove_group = remove_sub.add_parser("task-group")
345
+ remove_group.add_argument("--manager-id", required=True)
346
+ remove_group.add_argument("--task-group", required=True)
347
+ remove_group.add_argument("--confirm", action="store_true")
348
+
280
349
  listing = sub.add_parser("list")
281
350
  list_sub = listing.add_subparsers(dest="list_command", required=True)
282
351
  list_sub.add_parser("managers")
283
- for name in ("projects", "tasks"):
352
+ for name in ("projects", "task-groups", "tasks"):
284
353
  list_sub.add_parser(name).add_argument("--manager-id", required=True)
285
354
 
286
355
  view = sub.add_parser("view")
@@ -391,6 +460,25 @@ def main(argv: list[str] | None = None) -> int:
391
460
  _emit("list-managers", list_managers(home), args.json)
392
461
  elif args.command == "list" and args.list_command == "projects":
393
462
  _emit("list-projects", list_projects(home, args.manager_id), args.json)
463
+ elif args.command == "task" and args.task_command == "update":
464
+ _emit("task-update", update_task(home, args.manager_id, args.task_group, args.task_id,
465
+ objective=args.objective, common_brief_path=args.common_brief,
466
+ progress_mode=args.progress_mode), args.json)
467
+ elif args.command == "task" and args.task_command == "remove-child":
468
+ _emit("task-remove-child", remove_child(home, args.manager_id, args.task_group, args.task_id,
469
+ args.project_id, child_task_id=args.child_task_id,
470
+ confirm=args.confirm), args.json)
471
+ elif args.command == "remove" and args.remove_command == "project":
472
+ _emit("remove-project", unlink_project(home, args.manager_id, args.project_id,
473
+ confirm=args.confirm), args.json)
474
+ elif args.command == "remove" and args.remove_command == "task":
475
+ _emit("remove-task", remove_task(home, args.manager_id, args.task_group, args.task_id,
476
+ confirm=args.confirm), args.json)
477
+ elif args.command == "remove" and args.remove_command == "task-group":
478
+ _emit("remove-task-group", remove_task_group(home, args.manager_id, args.task_group,
479
+ confirm=args.confirm), args.json)
480
+ elif args.command == "list" and args.list_command == "task-groups":
481
+ _emit("list-task-groups", list_task_groups(home, args.manager_id), args.json)
394
482
  elif args.command == "list" and args.list_command == "tasks":
395
483
  _emit("list-tasks", list_tasks(home, args.manager_id), args.json)
396
484
  elif args.command == "view":
@@ -33,7 +33,7 @@ def _find_project(projects: dict, project_id: str) -> dict:
33
33
  for project in projects.get("projects", []):
34
34
  if isinstance(project, dict) and project.get("projectId") == project_id:
35
35
  return project
36
- raise ManagerError(f"unknown project membership: {project_id}")
36
+ raise ManagerError(f"{project_id}: project unlinked from this manager; re-register it with `okstra manager new project`")
37
37
 
38
38
 
39
39
  def _target_task_key(project_id: str, task_group: str, task_id: str) -> str:
@@ -54,10 +54,21 @@ def manager_root(home: Path, manager_id: str) -> Path:
54
54
  return Path(home) / "managers" / canonical_manager_id(manager_id)
55
55
 
56
56
 
57
+ def task_groups_root(home: Path, manager_id: str) -> Path:
58
+ return manager_root(home, manager_id) / "task-groups"
59
+
60
+
61
+ def task_group_root(home: Path, manager_id: str, task_group: str) -> Path:
62
+ return task_groups_root(home, manager_id) / _slug_segment(task_group, "task-group")
63
+
64
+
65
+ def task_group_meta_path(home: Path, manager_id: str, task_group: str) -> Path:
66
+ """폴더 이름은 slug 라 원래 그룹 이름을 여기에 남긴다."""
67
+ return task_group_root(home, manager_id, task_group) / "group.json"
68
+
69
+
57
70
  def task_root(home: Path, manager_id: str, task_group: str, task_id: str) -> Path:
58
- group_segment = _slug_segment(task_group, "task-group")
59
- task_segment = _slug_segment(task_id, "task-id")
60
- return manager_root(home, manager_id) / "task-groups" / group_segment / task_segment
71
+ return task_group_root(home, manager_id, task_group) / _slug_segment(task_id, "task-id")
61
72
 
62
73
 
63
74
  def manager_json_path(home: Path, manager_id: str) -> Path: