okstra 0.202.0 → 0.204.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 (258) hide show
  1. package/README.md +3 -3
  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 +25 -19
  15. package/docs/cli.md +15 -12
  16. package/docs/contributor-change-matrix.md +3 -2
  17. package/docs/performance-improvement-plan-v2.md +2 -3
  18. package/docs/project-structure-overview.md +35 -9
  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.md +1 -1
  23. package/docs/task-process/release-handoff.md +36 -39
  24. package/package.json +1 -2
  25. package/runtime/BUILD.json +2 -2
  26. package/runtime/agents/common.json +28 -0
  27. package/runtime/agents/operations/code-review.json +6 -0
  28. package/runtime/agents/operations/report-translation.json +6 -0
  29. package/runtime/agents/operations/schedule-verification.json +6 -0
  30. package/runtime/agents/roles/analyser.json +18 -0
  31. package/runtime/agents/roles/critic.json +18 -0
  32. package/runtime/agents/roles/designer.json +18 -0
  33. package/runtime/agents/roles/implementer.json +20 -0
  34. package/runtime/agents/roles/leader.json +20 -0
  35. package/runtime/agents/roles/planner.json +18 -0
  36. package/runtime/agents/roles/report-writer.json +19 -0
  37. package/runtime/agents/roles/translator.json +19 -0
  38. package/runtime/agents/roles/verifier.json +18 -0
  39. package/runtime/bin/lib/okstra/usage.sh +5 -5
  40. package/runtime/prompts/duties/acceptance-critic.json +32 -0
  41. package/runtime/prompts/duties/acceptance-verifier.json +32 -0
  42. package/runtime/prompts/duties/analysis-worker.json +32 -0
  43. package/runtime/prompts/duties/code-reviewer.json +32 -0
  44. package/runtime/prompts/duties/diagnosis-worker.json +32 -0
  45. package/runtime/prompts/duties/direction-selection-worker.json +32 -0
  46. package/runtime/prompts/duties/discovery-worker.json +32 -0
  47. package/runtime/prompts/duties/implementation-executor.json +32 -0
  48. package/runtime/prompts/duties/implementation-verifier.json +32 -0
  49. package/runtime/prompts/duties/lead.json +32 -0
  50. package/runtime/prompts/duties/planning-worker.json +36 -0
  51. package/runtime/prompts/duties/report-writer.json +32 -0
  52. package/runtime/prompts/duties/reverification-worker.json +32 -0
  53. package/runtime/prompts/duties/schedule-verifier.json +32 -0
  54. package/runtime/prompts/duties/scope-critic.json +32 -0
  55. package/runtime/prompts/duties/technical-verification-worker.json +32 -0
  56. package/runtime/prompts/duties/translator.json +32 -0
  57. package/runtime/prompts/launch.template.md +2 -1
  58. package/runtime/prompts/lead/adapters/cmux.md +1 -1
  59. package/runtime/prompts/lead/convergence.md +4 -4
  60. package/runtime/prompts/lead/okstra-lead-contract.md +113 -4
  61. package/runtime/prompts/lead/plan-body-verification.md +6 -6
  62. package/runtime/prompts/lead/report-writer.md +3 -3
  63. package/runtime/prompts/profiles/_common-contract.md +2 -2
  64. package/runtime/prompts/profiles/_implementation-executor.md +4 -1
  65. package/runtime/prompts/profiles/_implementation-verifier.md +2 -2
  66. package/runtime/prompts/profiles/change-impact-analysis.json +31 -0
  67. package/runtime/prompts/profiles/change-impact-analysis.md +0 -20
  68. package/runtime/prompts/profiles/error-analysis.json +39 -0
  69. package/runtime/prompts/profiles/error-analysis.md +0 -25
  70. package/runtime/prompts/profiles/feature-analysis.json +31 -0
  71. package/runtime/prompts/profiles/feature-analysis.md +0 -20
  72. package/runtime/prompts/profiles/final-verification.json +30 -0
  73. package/runtime/prompts/profiles/final-verification.md +3 -22
  74. package/runtime/prompts/profiles/forbidden-actions.json +4 -3
  75. package/runtime/prompts/profiles/implementation-option-selection.json +31 -0
  76. package/runtime/prompts/profiles/implementation-option-selection.md +0 -20
  77. package/runtime/prompts/profiles/implementation-planning.json +40 -0
  78. package/runtime/prompts/profiles/implementation-planning.md +4 -29
  79. package/runtime/prompts/profiles/implementation.json +30 -0
  80. package/runtime/prompts/profiles/implementation.md +1 -20
  81. package/runtime/prompts/profiles/improvement-discovery.json +31 -0
  82. package/runtime/prompts/profiles/improvement-discovery.md +0 -20
  83. package/runtime/prompts/profiles/project-analysis.json +31 -0
  84. package/runtime/prompts/profiles/project-analysis.md +0 -20
  85. package/runtime/prompts/profiles/release-handoff.json +5 -0
  86. package/runtime/prompts/profiles/release-handoff.md +71 -73
  87. package/runtime/prompts/profiles/requirements-discovery.json +39 -0
  88. package/runtime/prompts/profiles/requirements-discovery.md +0 -25
  89. package/runtime/prompts/profiles/technical-verification.json +39 -0
  90. package/runtime/prompts/profiles/technical-verification.md +0 -25
  91. package/runtime/prompts/wizard/prompts.ko.json +12 -17
  92. package/runtime/python/okstra_ctl/adapters/hosts/antigravity/relay.md +1 -0
  93. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +3 -0
  94. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/manifest.json +1 -1
  95. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +4 -3
  96. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/worker-session.md +108 -0
  97. package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +1 -0
  98. package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +2 -0
  99. package/runtime/python/okstra_ctl/adapters/hosts/kimi/relay.md +2 -0
  100. package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +8 -1
  101. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +8 -0
  102. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +23 -6
  103. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +6 -2
  104. package/runtime/python/okstra_ctl/agent/invocation.py +168 -113
  105. package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +120 -0
  106. package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +107 -2
  107. package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +0 -49
  108. package/runtime/python/okstra_ctl/analysis_packet.py +4 -1
  109. package/runtime/python/okstra_ctl/application/open_worker.py +6 -1
  110. package/runtime/python/okstra_ctl/assignment_resolver.py +16 -5
  111. package/runtime/python/okstra_ctl/cmux.py +69 -20
  112. package/runtime/python/okstra_ctl/code_review_target.py +16 -8
  113. package/runtime/python/okstra_ctl/conformance.py +43 -0
  114. package/runtime/python/okstra_ctl/consumers.py +6 -3
  115. package/runtime/python/okstra_ctl/container.py +31 -8
  116. package/runtime/python/okstra_ctl/context_cost.py +11 -15
  117. package/runtime/python/okstra_ctl/contract_refreeze.py +156 -0
  118. package/runtime/python/okstra_ctl/convergence_provenance.py +7 -1
  119. package/runtime/python/okstra_ctl/design_prep.py +34 -1
  120. package/runtime/python/okstra_ctl/dispatch_core.py +53 -27
  121. package/runtime/python/okstra_ctl/domain/host.py +5 -0
  122. package/runtime/python/okstra_ctl/domain/worker_runtime.py +10 -0
  123. package/runtime/python/okstra_ctl/error_report.py +4 -3
  124. package/runtime/python/okstra_ctl/execution_manifest.py +71 -18
  125. package/runtime/python/okstra_ctl/handoff.py +167 -277
  126. package/runtime/python/okstra_ctl/implementation_stage.py +9 -0
  127. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +113 -0
  128. package/runtime/python/okstra_ctl/lead_progress.py +1 -1
  129. package/runtime/python/okstra_ctl/legacy_model_selection.py +2 -2
  130. package/runtime/python/okstra_ctl/manager_cli.py +92 -4
  131. package/runtime/python/okstra_ctl/manager_launch.py +1 -1
  132. package/runtime/python/okstra_ctl/manager_paths.py +14 -3
  133. package/runtime/python/okstra_ctl/manager_store.py +210 -3
  134. package/runtime/python/okstra_ctl/manager_sync.py +4 -1
  135. package/runtime/python/okstra_ctl/manager_view.py +2 -1
  136. package/runtime/python/okstra_ctl/model_discovery.py +30 -0
  137. package/runtime/python/okstra_ctl/model_io/lines.py +14 -1
  138. package/runtime/python/okstra_ctl/model_io/renderers.py +4 -3
  139. package/runtime/python/okstra_ctl/models.py +1 -1
  140. package/runtime/python/okstra_ctl/next_phase.py +16 -6
  141. package/runtime/python/okstra_ctl/operation_invocation.py +86 -0
  142. package/runtime/python/okstra_ctl/option_comparison.py +168 -0
  143. package/runtime/python/okstra_ctl/path_hints.py +9 -0
  144. package/runtime/python/okstra_ctl/paths.py +3 -0
  145. package/runtime/python/okstra_ctl/profile_show.py +42 -1
  146. package/runtime/python/okstra_ctl/registry/host_discovery.py +20 -12
  147. package/runtime/python/okstra_ctl/registry/host_registry.py +11 -0
  148. package/runtime/python/okstra_ctl/render.py +50 -0
  149. package/runtime/python/okstra_ctl/report_contract.py +1 -1
  150. package/runtime/python/okstra_ctl/report_html/view_models/release_handoff.py +21 -3
  151. package/runtime/python/okstra_ctl/report_synthesis_packet.py +177 -17
  152. package/runtime/python/okstra_ctl/report_translation.py +2 -1
  153. package/runtime/python/okstra_ctl/report_translation_dispatch.py +69 -9
  154. package/runtime/python/okstra_ctl/role_requirements.py +142 -129
  155. package/runtime/python/okstra_ctl/rollup.py +3 -1
  156. package/runtime/python/okstra_ctl/run.py +76 -29
  157. package/runtime/python/okstra_ctl/schedule_semantics.py +17 -6
  158. package/runtime/python/okstra_ctl/stage_fix_carry.py +23 -4
  159. package/runtime/python/okstra_ctl/stage_integrate.py +178 -18
  160. package/runtime/python/okstra_ctl/stage_map.py +16 -2
  161. package/runtime/python/okstra_ctl/stage_targets.py +209 -43
  162. package/runtime/python/okstra_ctl/team.py +22 -13
  163. package/runtime/python/okstra_ctl/time_report.py +2 -1
  164. package/runtime/python/okstra_ctl/usage_report.py +3 -1
  165. package/runtime/python/okstra_ctl/wizard/confirmation.py +3 -9
  166. package/runtime/python/okstra_ctl/wizard/ids.py +1 -1
  167. package/runtime/python/okstra_ctl/wizard/registry.py +1 -1
  168. package/runtime/python/okstra_ctl/wizard/state.py +3 -5
  169. package/runtime/python/okstra_ctl/wizard/steps_plan.py +3 -23
  170. package/runtime/python/okstra_ctl/worker_prompt_contract.py +5 -1
  171. package/runtime/python/okstra_ctl/worker_prompt_headers.py +35 -7
  172. package/runtime/python/okstra_ctl/worker_prompt_policy.py +66 -48
  173. package/runtime/python/okstra_ctl/workflow.py +1 -1
  174. package/runtime/python/okstra_ctl/worktree/__init__.py +3 -1
  175. package/runtime/python/okstra_ctl/worktree/naming.py +9 -0
  176. package/runtime/python/okstra_ctl/worktree_registry.py +38 -9
  177. package/runtime/python/okstra_token_usage/pricing.py +6 -4
  178. package/runtime/schemas/agent-common-v1.schema.json +34 -0
  179. package/runtime/schemas/agent-duty-v1.schema.json +38 -0
  180. package/runtime/schemas/agent-operation-v1.schema.json +11 -0
  181. package/runtime/schemas/agent-profile-v1.schema.json +46 -0
  182. package/runtime/schemas/agent-role-v1.schema.json +29 -0
  183. package/runtime/schemas/final-report-v2.0.schema.json +118 -97
  184. package/runtime/schemas/final-report-v3.0.schema.json +118 -97
  185. package/runtime/skills/okstra-brief-gen/SKILL.md +84 -4
  186. package/runtime/skills/okstra-chat/SKILL.md +2 -2
  187. package/runtime/skills/okstra-code-review/SKILL.md +23 -9
  188. package/runtime/skills/okstra-container-build/SKILL.md +10 -10
  189. package/runtime/skills/okstra-inspect/SKILL.md +1 -1
  190. package/runtime/skills/okstra-inspect/facets/cost.md +1 -1
  191. package/runtime/skills/okstra-inspect/facets/error-zip.md +9 -9
  192. package/runtime/skills/okstra-inspect/facets/errors.md +16 -16
  193. package/runtime/skills/okstra-inspect/facets/logs.md +7 -7
  194. package/runtime/skills/okstra-inspect/facets/recap.md +2 -2
  195. package/runtime/skills/okstra-inspect/facets/report.md +1 -1
  196. package/runtime/skills/okstra-inspect/facets/status.md +4 -3
  197. package/runtime/skills/okstra-inspect/facets/time.md +11 -10
  198. package/runtime/skills/okstra-manager/SKILL.md +18 -2
  199. package/runtime/skills/okstra-pr-gen/SKILL.md +6 -5
  200. package/runtime/skills/okstra-rollup/SKILL.md +5 -5
  201. package/runtime/skills/okstra-run/SKILL.md +31 -12
  202. package/runtime/skills/okstra-schedule-gen/SKILL.md +19 -14
  203. package/runtime/skills/okstra-setup/SKILL.md +12 -10
  204. package/runtime/skills/okstra-setup/references/project-config.md +7 -6
  205. package/runtime/skills/okstra-usage/SKILL.md +1 -1
  206. package/runtime/skills/okstra-user-response/SKILL.md +1 -1
  207. package/runtime/templates/manager/view.template.html +1 -0
  208. package/runtime/templates/report-writer-prompt-preamble.md +8 -0
  209. package/runtime/templates/reports/brief.template.md +14 -4
  210. package/runtime/templates/reports/html/i18n/en.json +5 -4
  211. package/runtime/templates/reports/html/i18n/ko.json +5 -4
  212. package/runtime/templates/reports/html/tasks/release-handoff.template.html +8 -5
  213. package/runtime/templates/reports/i18n/en.json +1 -1
  214. package/runtime/templates/reports/md/tasks/release-handoff.template.md +1 -1
  215. package/runtime/templates/reports/release-handoff-input.template.md +6 -4
  216. package/runtime/templates/translator-prompt-preamble.md +36 -0
  217. package/runtime/validators/checks/validate-assets-01.py +7 -8
  218. package/runtime/validators/validate-brief.py +70 -0
  219. package/runtime/validators/validate-implementation-plan-stages.py +2 -1
  220. package/runtime/validators/validate-run.py +59 -9
  221. package/runtime/validators/validate-schedule.py +9 -0
  222. package/docs/for-ai/README.md +0 -68
  223. package/docs/for-ai/skills/okstra-brief-gen.md +0 -262
  224. package/docs/for-ai/skills/okstra-chat.md +0 -34
  225. package/docs/for-ai/skills/okstra-code-review.md +0 -57
  226. package/docs/for-ai/skills/okstra-container-build.md +0 -129
  227. package/docs/for-ai/skills/okstra-inspect.md +0 -262
  228. package/docs/for-ai/skills/okstra-manager.md +0 -86
  229. package/docs/for-ai/skills/okstra-memory.md +0 -126
  230. package/docs/for-ai/skills/okstra-pr-gen.md +0 -49
  231. package/docs/for-ai/skills/okstra-rollup.md +0 -114
  232. package/docs/for-ai/skills/okstra-run.md +0 -250
  233. package/docs/for-ai/skills/okstra-schedule-gen.md +0 -240
  234. package/docs/for-ai/skills/okstra-setup.md +0 -167
  235. package/docs/for-ai/skills/okstra-usage.md +0 -29
  236. package/docs/for-ai/skills/okstra-user-response.md +0 -72
  237. package/runtime/agents/workers/claude-worker.md +0 -128
  238. package/runtime/agents/workers/report-writer-worker.md +0 -37
  239. package/runtime/agents/workers/translator-worker.md +0 -63
  240. package/runtime/prompts/duties/acceptance-critic.md +0 -44
  241. package/runtime/prompts/duties/acceptance-verifier.md +0 -44
  242. package/runtime/prompts/duties/analysis-worker.md +0 -44
  243. package/runtime/prompts/duties/code-reviewer.md +0 -44
  244. package/runtime/prompts/duties/common.md +0 -39
  245. package/runtime/prompts/duties/diagnosis-worker.md +0 -44
  246. package/runtime/prompts/duties/direction-selection-worker.md +0 -44
  247. package/runtime/prompts/duties/discovery-worker.md +0 -44
  248. package/runtime/prompts/duties/implementation-executor.md +0 -44
  249. package/runtime/prompts/duties/implementation-verifier.md +0 -44
  250. package/runtime/prompts/duties/lead.md +0 -44
  251. package/runtime/prompts/duties/planning-worker.md +0 -52
  252. package/runtime/prompts/duties/report-writer.md +0 -44
  253. package/runtime/prompts/duties/reverification-worker.md +0 -44
  254. package/runtime/prompts/duties/schedule-verifier.md +0 -44
  255. package/runtime/prompts/duties/scope-critic.md +0 -44
  256. package/runtime/prompts/duties/technical-verification-worker.md +0 -44
  257. package/runtime/prompts/duties/translator.md +0 -44
  258. package/runtime/python/okstra_ctl/pane_title.py +0 -154
@@ -265,7 +265,8 @@ _CLI_EPILOG = r"""Usage:
265
265
 
266
266
  Output: JSON { ok, taskKey, byTaskType[], grandTotal, perWorker{}, perRunWallClock[],
267
267
  phaseTimelines[], unavailable[] }. All durations are raw milliseconds — the caller
268
- formats HH:MM:SS and renders tables. This is read-only; it never mutates task state.
268
+ formats HH:MM:SS and renders tables. Resolving a task may record a finished implementation outcome in its task
269
+ manifest, the task catalog, and consumers.jsonl; nothing else is written.
269
270
  """
270
271
 
271
272
 
@@ -274,8 +274,10 @@ def _positive_days(value: str) -> int:
274
274
  _CLI_EPILOG = r"""Usage:
275
275
  okstra usage-report [--days <positive-int>] [--project-root <dir>] [--cwd <dir>] [--json]
276
276
 
277
- Read-only. Prints JSON containing run coverage, raw and billable-equivalent
277
+ Prints JSON containing run coverage, raw and billable-equivalent
278
278
  tokens, known USD cost, CPU-sum time, and wall-clock time grouped by task type.
279
+ Resolving a task may record a finished implementation outcome in its task
280
+ manifest, the task catalog, and consumers.jsonl; nothing else is written.
279
281
  The default window is the current project's last 30 days.
280
282
  """
281
283
 
@@ -354,15 +354,9 @@ def confirmation_block(state: WizardState) -> str:
354
354
  lines.append(reverify_line)
355
355
  if state.selected_direction_path:
356
356
  lines.append(f" selected-direction: {state.selected_direction_path}")
357
- if state.task_type == "release-handoff" and state.handoff_mode:
358
- scope = (
359
- _msg(state.workspace_root, "confirmation",
360
- "handoff_scope_whole_task")
361
- if state.handoff_mode == "whole-task"
362
- else _msg(state.workspace_root, "confirmation",
363
- "handoff_scope_stage_group",
364
- stages=state.handoff_stages)
365
- )
357
+ if state.task_type == "release-handoff" and state.handoff_stages:
358
+ scope = _msg(state.workspace_root, "confirmation",
359
+ "handoff_scope_stages", stages=state.handoff_stages)
366
360
  lines.append(f" handoff scope : {scope}")
367
361
  if state.task_type == "release-handoff" and state.pr_template_path:
368
362
  lines.append(f" pr-template : {state.pr_template_path} ({state.pr_template_scope or 'once'})")
@@ -123,7 +123,7 @@ _BRIEF_PREFIX = "__brief:"
123
123
 
124
124
  # brief 본문 머리 인용구에 okstra-brief-gen 이 적어 넣는 진입 phase 줄.
125
125
  # `templates/reports/brief.template.md` 의 "> - Recommended next phase: <...>" 이고,
126
- # 값 규칙은 `docs/for-ai/skills/okstra-brief-gen.md` § "Recommended next phase".
126
+ # 값 규칙은 `skills/okstra-brief-gen/SKILL.md` § "Step 6: Recommend next okstra phase".
127
127
  # 목록 표시(`- `)는 머리 인용구의 필드를 한 줄씩 렌더링시키려고 붙은 것이라
128
128
  # 선택적으로 받는다 — 그 표시가 없던 옛 brief 도 그대로 읽힌다.
129
129
  _BRIEF_NEXT_PHASE_RE = re.compile(
@@ -513,7 +513,7 @@ STEPS: list[Step] = [
513
513
  and bool(s.task_id)
514
514
  and S_HANDOFF_STAGE_PICK not in s.answered),
515
515
  build=_build_handoff_stage_pick, submit=_submit_handoff_stage_pick,
516
- owns=("handoff_mode", "handoff_stages", "approved_plan_path")),
516
+ owns=("handoff_stages", "approved_plan_path")),
517
517
  Step(S_EXECUTOR,
518
518
  applies=lambda s: (s.task_type == "implementation"
519
519
  and not _role_selection_enabled(s)
@@ -103,10 +103,8 @@ class WizardState:
103
103
  critic: str = ""
104
104
  critic_pending_text: bool = False
105
105
 
106
- # release-handoff: PR 로 내보낼 범위. mode 는 stages 선택의 파생값이다 —
107
- # whole-task = 빈 stages(whole-task 검증 기반 단일 PR), stage-group = csv.
108
- handoff_mode: str = "" # "" | "whole-task" | "stage-group"
109
- handoff_stages: str = "" # csv ("2,3"), whole-task 면 ""
106
+ # release-handoff: PR 을 열 stage 들. stage 하나가 PR 하나다.
107
+ handoff_stages: str = "" # csv ("2,3")
110
108
 
111
109
  # resume: 직전 run-inputs 재사용 여부 (None=미응답, True=재사용, False=재입력)
112
110
  reuse_previous: Optional[bool] = None
@@ -672,7 +670,7 @@ _FIELD_DEFAULTS: dict[str, Any] = {
672
670
  "design_prep_notes": "",
673
671
  "selected_stage": "auto",
674
672
  "selected_stages": "",
675
- "handoff_mode": "", "handoff_stages": "",
673
+ "handoff_stages": "",
676
674
  "executor": "", "critic": "", "critic_pending_text": False,
677
675
  "execution_identity_version": 1,
678
676
  "role_counts": {}, "role_models": {}, "role_selection_order": [],
@@ -728,30 +728,20 @@ def _handoff_eligibility(state: WizardState) -> list:
728
728
  return compute_eligibility(stage_map, rows)
729
729
 
730
730
 
731
- def _latest_whole_task_fv_release_ready(state: WizardState) -> str:
732
- """accepted whole-task final-verification 보고서 경로 — handoff 모듈 SSOT 위임."""
733
- from okstra_ctl.handoff import latest_whole_task_fv_release_ready
734
- return latest_whole_task_fv_release_ready(
735
- state.project_root, state.project_id, state.task_group, state.task_id)
736
-
737
-
738
731
  def _build_handoff_stage_pick(state: WizardState) -> Prompt:
739
732
  elig = _handoff_eligibility(state)
740
733
  eligible = [e for e in elig if e["eligible"]]
741
734
  blocked = [e for e in elig if not e["eligible"]]
742
- whole_task_report = _latest_whole_task_fv_release_ready(state)
743
735
  msgs = _handoff_msgs(state)
744
736
  blocked_summary = ("; ".join(
745
737
  f"stage {e['stage']} ({', '.join(e['reasons'])})" for e in blocked)
746
738
  or msgs["labels"]["blocked_none"])
747
- if not eligible and not whole_task_report:
739
+ if not eligible:
748
740
  raise WizardError(
749
741
  msgs["errors"]["nothing_eligible"].format(blocked=blocked_summary))
750
742
  t = _p(state.workspace_root, "handoff_stage_pick", blocked=blocked_summary)
751
- options: list[Option] = []
752
- if whole_task_report:
753
- options.append(_opt(WHOLE_TASK_STAGE, t["labels"]["whole_task"]))
754
743
  stage_label = t["labels"]["stage"]
744
+ options: list[Option] = []
755
745
  for e in eligible:
756
746
  deps = ", ".join(str(d) for d in e["depends_on"]) or "-"
757
747
  options.append(_opt(str(e["stage"]),
@@ -768,14 +758,6 @@ def _submit_handoff_stage_pick(state: WizardState, value: str) -> Optional[str]:
768
758
  picks = [v.strip() for v in (value or "").split(",") if v.strip()]
769
759
  if not picks:
770
760
  raise WizardError(t["errors"]["none_selected"])
771
- if WHOLE_TASK_STAGE in picks:
772
- if len(picks) > 1:
773
- raise WizardError(t["errors"]["whole_task_exclusive"])
774
- if not _latest_whole_task_fv_release_ready(state):
775
- raise WizardError(t["errors"]["whole_task_missing"])
776
- state.handoff_mode = "whole-task"
777
- state.handoff_stages = ""
778
- return t["echo_variants"]["whole_task"]
779
761
  eligible = {str(e["stage"]) for e in _handoff_eligibility(state)
780
762
  if e["eligible"]}
781
763
  bad = [p for p in picks if p not in eligible]
@@ -783,10 +765,8 @@ def _submit_handoff_stage_pick(state: WizardState, value: str) -> Optional[str]:
783
765
  raise WizardError(t["errors"]["not_eligible"].format(
784
766
  bad=", ".join(bad), eligible=", ".join(sorted(eligible))))
785
767
  nums = sorted({int(p) for p in picks})
786
- state.handoff_mode = "stage-group"
787
768
  state.handoff_stages = ",".join(str(n) for n in nums)
788
- return t["echo_variants"]["stage_group"].format(
789
- stages=state.handoff_stages)
769
+ return t["echo_variants"]["stages"].format(stages=state.handoff_stages)
790
770
 
791
771
 
792
772
  def _reverify_scope_step_pending(state: WizardState) -> bool:
@@ -28,7 +28,10 @@ from .worker_prompt_headers import EVIDENCE_LEDGER_HEADER
28
28
 
29
29
 
30
30
  MAX_FINAL_VERIFICATION_DIRECTIVE_LINES = 40
31
- MAX_FINAL_VERIFICATION_BODY_LINES = 96
31
+ # 96 → 108: 프롬프트 앞머리에 역할 계약(비어 있지 않은 12줄)이 더해졌다.
32
+ # 상한이 재는 것은 리드가 쓰는 지시문 분량이므로, 고정 서두가 늘어난 만큼
33
+ # 올려 지시문 허용량을 그대로 둔다.
34
+ MAX_FINAL_VERIFICATION_BODY_LINES = 108
32
35
 
33
36
  PROMPT_DELIVERY_MODE_HEADER = "**Prompt Delivery Mode:**"
34
37
  PROMPT_DELIVERY_MODES = frozenset({"eager-include", "lazy-path-reference"})
@@ -60,6 +63,7 @@ _NON_BODY_PREFIXES = (
60
63
  "**Audit sidecar path:**",
61
64
  "Assigned worker prompt history path:",
62
65
  "**Worker Preamble Path:**",
66
+ "**Host Session Contract Path:**",
63
67
  _EVIDENCE_LEDGER_HEADER_PREFIX,
64
68
  "**Evidence citations:**",
65
69
  *ERRORS_PATH_HEADERS,
@@ -12,6 +12,7 @@ from .worker_prompt_policy import (
12
12
  WORKER_ERROR_CONTRACT_FILENAME,
13
13
  WORKER_PREAMBLE_FILENAME_BY_AUDIENCE,
14
14
  resolve_prompt_plan_for_manifest,
15
+ worker_assignment,
15
16
  )
16
17
 
17
18
 
@@ -130,6 +131,9 @@ def worker_prompt_headers(
130
131
  f"**Worker Preamble Path:** {_worker_preamble_path(plan, active_context)}",
131
132
  f"**Worker Error Contract Path:** {_worker_error_contract_path(active_context)}",
132
133
  ]
134
+ host_session_contract = _host_session_contract_path(manifest, active_context, worker_id)
135
+ if host_session_contract:
136
+ headers.append(f"**Host Session Contract Path:** {host_session_contract}")
133
137
  if plan.allow_coding_preflight:
134
138
  headers.append(
135
139
  f"**Coding preflight pack:** {_coding_preflight_pack_path(active_context)}"
@@ -184,13 +188,11 @@ def _worker_provider(manifest: Mapping[str, Any], worker_id: str) -> str:
184
188
  로스터 행을 못 찾으면 종전 규칙으로 떨어진다 — 그 자리에선 공급자를 알
185
189
  방법이 없고, 헤더를 빼면 계약이 fail-closed 로 막는다.
186
190
  """
187
- workers = manifest.get("workerAssignments")
188
- if isinstance(workers, list):
189
- for row in workers:
190
- if isinstance(row, Mapping) and row.get("workerId") == worker_id:
191
- value = row.get("provider")
192
- return value if isinstance(value, str) else ""
193
- return worker_id
191
+ row = worker_assignment(manifest, worker_id)
192
+ if row is None:
193
+ return worker_id
194
+ value = row.get("provider")
195
+ return value if isinstance(value, str) else ""
194
196
 
195
197
 
196
198
  def _final_verification_target_headers(
@@ -232,6 +234,32 @@ def _worker_preamble_path(
232
234
  return okstra_home() / "templates" / filename
233
235
 
234
236
 
237
+ def _host_session_contract_path(
238
+ manifest: Mapping[str, Any],
239
+ active_context: Mapping[str, Any],
240
+ worker_id: str,
241
+ ) -> str:
242
+ """이 워커가 읽어야 하는 호스트 세션 계약. 해당 없으면 빈 문자열.
243
+
244
+ 호스트 세션 안에서 도는 워커(`runner=native-session`)에게만 해당한다.
245
+ cli-wrapper 워커는 자기 공급자 프로세스로 돌기 때문에 이 계약의 전제(리드와
246
+ 같은 셸, 같은 도구 계층)가 성립하지 않는다 — 전달하면 틀린 규칙을 주는 것이다.
247
+ """
248
+ if _worker_runner(manifest, worker_id) != "native-session":
249
+ return ""
250
+ runtime_resources = active_context.get("runtimeResources")
251
+ if not isinstance(runtime_resources, Mapping):
252
+ return ""
253
+ return _string_value(runtime_resources.get("hostSessionContractPath"))
254
+
255
+
256
+ def _worker_runner(manifest: Mapping[str, Any], worker_id: str) -> str:
257
+ """로스터가 이 워커에 확정한 러너. 행을 못 찾으면 빈 문자열."""
258
+ row = worker_assignment(manifest, worker_id)
259
+ value = row.get("runner") if row is not None else None
260
+ return value if isinstance(value, str) else ""
261
+
262
+
235
263
  def _worker_error_contract_path(active_context: Mapping[str, Any]) -> Path:
236
264
  runtime_resources = active_context.get("runtimeResources")
237
265
  if isinstance(runtime_resources, Mapping):
@@ -78,6 +78,7 @@ WORKER_PREAMBLE_FILENAME_BY_AUDIENCE = {
78
78
  "implementation-executor": "implementation-worker-preamble.md",
79
79
  "implementation-verifier": "implementation-worker-preamble.md",
80
80
  "report-writer": "report-writer-prompt-preamble.md",
81
+ "translator": "translator-prompt-preamble.md",
81
82
  }
82
83
  WORKER_ERROR_CONTRACT_FILENAME = "worker-error-contract.md"
83
84
  # coverage-critic gap 검증 라운드의 dispatch kind. 번호 라운드(`reverify-r<N>`)와
@@ -167,21 +168,29 @@ class PromptPlan:
167
168
  def resolve_prompt_plan(
168
169
  *,
169
170
  task_type: str,
170
- worker_id: str,
171
- executor_worker_id: str | None,
171
+ role: str | None,
172
172
  dispatch_kind: str,
173
173
  ) -> PromptPlan:
174
- """Resolve prompt policy without consulting provider or model identity."""
174
+ """Resolve prompt policy from the worker's role and the dispatch kind.
175
+
176
+ Neither the worker id nor the provider or model identity takes part. The id
177
+ used to: `report-writer`, `translator`, and "the executor's worker id" were
178
+ compared by spelling, so a verifier that happened to share the executor's
179
+ id received the executor's prompt.
180
+ """
175
181
  if task_type not in SUPPORTED_TASK_TYPES:
176
182
  raise ValueError(f"unsupported task type: {task_type}")
177
- if task_type == "release-handoff":
183
+ if task_type == "release-handoff" or dispatch_kind == "lead":
184
+ # 리드는 로스터 행이 없다. 종전에는 `worker_id="lead"` 가 어느 비교에도
185
+ # 걸리지 않아 분석 계획으로 떨어졌고, 리드 프롬프트가 분석 워커 직무를
186
+ # 달고 나갔다.
178
187
  return _plan("lead-only")
179
- if not worker_id.strip():
180
- raise ValueError("worker ID is required")
181
- if worker_id == "translator" or dispatch_kind == "translator":
182
- if worker_id != "translator" or dispatch_kind != "translator":
183
- raise ValueError("translator worker and dispatch kind must match")
188
+ if dispatch_kind == "translator":
189
+ if role not in (None, "translator"):
190
+ raise ValueError(f"translator dispatch cannot run role {role}")
184
191
  return _plan("translator", duty_audience="translator")
192
+ if role == "translator":
193
+ raise ValueError("translator role requires the translator dispatch kind")
185
194
  if is_verification_dispatch_kind(dispatch_kind):
186
195
  return _plan("reverify")
187
196
  # A critic pass keeps the full analysis contract — worker anchor headers, the
@@ -198,23 +207,30 @@ def resolve_prompt_plan(
198
207
  if task_type == "final-verification"
199
208
  else ANALYSIS_DUTY_BY_TASK_TYPE.get(task_type, "analysis-worker")
200
209
  )
201
- if task_type == "implementation" and not executor_worker_id:
202
- raise ValueError("implementation executor worker ID is required")
203
- if worker_id == "report-writer":
204
- return _plan("report-writer")
205
- if task_type == "implementation" and worker_id == executor_worker_id:
206
- return _plan(
207
- "implementation-executor",
208
- allow_coding_preflight=True,
209
- required_headers=IMPLEMENTATION_HEADERS,
210
- )
211
- if task_type == "implementation":
212
- return _plan(
213
- "implementation-verifier",
214
- equality_group="implementation-verifier-core",
215
- allow_coding_preflight=True,
216
- required_headers=IMPLEMENTATION_HEADERS,
217
- )
210
+ if dispatch_kind != "critic":
211
+ # 초기 디스패치는 역할이 audience 를 정한다. critic 은 역할과 무관하게
212
+ # 아래 분석 분기가 그 phase 의 critic 직무로 정한다.
213
+ if not role:
214
+ raise ValueError("worker role is required")
215
+ if role == "report-writer":
216
+ return _plan("report-writer")
217
+ if task_type == "implementation":
218
+ if role == "implementer":
219
+ return _plan(
220
+ "implementation-executor",
221
+ allow_coding_preflight=True,
222
+ required_headers=IMPLEMENTATION_HEADERS,
223
+ )
224
+ if role == "verifier":
225
+ return _plan(
226
+ "implementation-verifier",
227
+ equality_group="implementation-verifier-core",
228
+ allow_coding_preflight=True,
229
+ required_headers=IMPLEMENTATION_HEADERS,
230
+ )
231
+ raise ValueError(f"implementation has no prompt policy for role {role}")
232
+ elif task_type == "implementation":
233
+ raise ValueError("implementation runs no critic pass")
218
234
  if task_type == "final-verification":
219
235
  return _plan(
220
236
  "analysis",
@@ -248,16 +264,37 @@ def resolve_prompt_plan_for_manifest(
248
264
  worker_id: str,
249
265
  dispatch_kind: str,
250
266
  ) -> PromptPlan:
251
- """Resolve policy from canonical task and executor manifest fields."""
267
+ """Resolve policy from the manifest's task type and the worker's roster role.
268
+
269
+ *worker_id* is the lookup key into `workerAssignments`; its spelling decides
270
+ nothing. A worker with no roster row — the translator, or a verification
271
+ pass whose kind alone decides — resolves with no role.
272
+ """
252
273
  task_type = _required_string(manifest, "taskType")
274
+ if not worker_id.strip():
275
+ raise ValueError("worker ID is required")
276
+ row = worker_assignment(manifest, worker_id)
277
+ role = row.get("role") if row is not None else None
253
278
  return resolve_prompt_plan(
254
279
  task_type=task_type,
255
- worker_id=worker_id,
256
- executor_worker_id=_executor_worker_id(manifest),
280
+ role=role if isinstance(role, str) and role else None,
257
281
  dispatch_kind=dispatch_kind,
258
282
  )
259
283
 
260
284
 
285
+ def worker_assignment(
286
+ manifest: Mapping[str, Any], worker_id: str
287
+ ) -> Mapping[str, Any] | None:
288
+ """The `workerAssignments` row for *worker_id*, or None when absent."""
289
+ rows = manifest.get("workerAssignments")
290
+ if not isinstance(rows, list):
291
+ return None
292
+ for row in rows:
293
+ if isinstance(row, Mapping) and row.get("workerId") == worker_id:
294
+ return row
295
+ return None
296
+
297
+
261
298
  def _plan(
262
299
  audience: PromptAudience,
263
300
  *,
@@ -335,25 +372,6 @@ def critic_assignment_ref(task_type: str) -> str:
335
372
  return f"critic/{segment}"
336
373
 
337
374
 
338
- def _executor_worker_id(manifest: Mapping[str, Any]) -> str | None:
339
- """실행자의 **worker_id**. provider 이름이 아니다.
340
-
341
- 한 provider 가 실행자와 검증자를 겸하면 슬롯마다 역할이 붙어 실행자의 id 는
342
- `codex-implementer` 가 된다. 여기서 provider(`codex`)를 돌려주면 실행자가
343
- 자기 자신과 매칭되지 않아 검증자 프롬프트로 실체화되고, 그 사실은
344
- `agent invocation duty does not match expected audience` 로 디스패치 단계에서야
345
- 드러난다.
346
- """
347
- team_contract = manifest.get("teamContract")
348
- if not isinstance(team_contract, Mapping):
349
- return None
350
- executor = team_contract.get("executor")
351
- if not isinstance(executor, Mapping):
352
- return None
353
- worker_id = executor.get("workerId")
354
- return worker_id.strip() if isinstance(worker_id, str) and worker_id.strip() else None
355
-
356
-
357
375
  def _required_string(payload: Mapping[str, Any], key: str) -> str:
358
376
  value = payload.get(key)
359
377
  if not isinstance(value, str) or not value.strip():
@@ -133,7 +133,7 @@ PHASE_RULES: dict[str, dict[str, str]] = {
133
133
  "allowed": (
134
134
  " - acceptance verdict with requirement coverage assessment\n"
135
135
  " - residual risk and regression notes\n"
136
- " - routing recommendation as the `routingRecommendation` object `{target, rationale}`: `target` is one of `release-handoff`, `release-handoff(stage-group)`, `error-analysis`, `implementation-option-selection`, `implementation-planning`, `implementation`, `done`, and `rationale` ties that choice to the verdict and the blocker list. Both `release-handoff` forms require a release-ready verdict — `accepted`, or `conditional-accept` with every condition declaring `blocksReleaseHandoff: false` (`okstra_ctl.release_gate.release_handoff_allowed`); plain `release-handoff` additionally requires `whole-task` verification scope. Prose alone does not route the task — Phase 7 projects `workflow.nextRecommendedPhase` from `target`"
136
+ " - routing recommendation as the `routingRecommendation` object `{target, rationale}`: `target` is one of `release-handoff`, `release-handoff(stage-group)`, `error-analysis`, `implementation-option-selection`, `implementation-planning`, `implementation`, `done`, and `rationale` ties that choice to the verdict and the blocker list. Both `release-handoff` forms require a release-ready verdict — `accepted`, or `conditional-accept` with every condition declaring `blocksReleaseHandoff: false` (`okstra_ctl.release_gate.release_handoff_allowed`); either verification scope may route there, because release-handoff opens one PR per stage. Prose alone does not route the task — Phase 7 projects `workflow.nextRecommendedPhase` from `target`"
137
137
  ),
138
138
  },
139
139
  "release-handoff": {
@@ -61,7 +61,8 @@ from .git_ops import (
61
61
  move_worktree,
62
62
  remove_worktree_force,
63
63
  )
64
- from .naming import compute_branch_name, compute_worktree_path
64
+ from .naming import (compute_branch_name, compute_worktree_path,
65
+ stage_group_id)
65
66
  from .provision import (
66
67
  WorktreeProvision,
67
68
  provision_stage_worktree,
@@ -77,6 +78,7 @@ __all__ = [
77
78
  "_resolve_commit_sha",
78
79
  "compute_branch_name",
79
80
  "compute_worktree_path",
81
+ "stage_group_id",
80
82
  "dirty_entries_excluding_okstra",
81
83
  "is_ancestor",
82
84
  "is_dirty_excluding_okstra",
@@ -42,6 +42,15 @@ def _work_category_namespace(work_category: str) -> str:
42
42
  return _WORK_CATEGORY_NAMESPACE.get(key, "task")
43
43
 
44
44
 
45
+ def stage_group_id(stages) -> str:
46
+ """stage 번호 집합을 브랜치/경로 조각으로 옮긴다 (`{2, 3}` → `g2-3`).
47
+
48
+ `compute_branch_name(group_id=...)` 과 `compute_worktree_path(group_id=...)`
49
+ 가 받는 값이고, 여러 stage 의 작업을 한 브랜치에 모을 때 그 집합이 곧 이름이
50
+ 된다 — 같은 집합은 언제나 같은 브랜치다."""
51
+ return "g" + "-".join(str(n) for n in sorted(set(stages)))
52
+
53
+
45
54
  def compute_worktree_path(
46
55
  *,
47
56
  project_id: str,
@@ -13,8 +13,10 @@ Why a global registry:
13
13
  the **same** worktree on the **same** branch. Re-entry from any
14
14
  phase must look up the existing entry instead of creating a
15
15
  duplicate.
16
- - Two different task-keys must never collide on the same branch name.
17
- A global branch index makes that detectable cheaply.
16
+ - Two different task-keys in the same project must never collide on
17
+ the same branch name. A per-project branch index makes that
18
+ detectable cheaply. Names in different projects are different
19
+ repositories' branches and do not collide.
18
20
  - Cleanup of stale entries (worktree dir removed manually) needs a
19
21
  single source of truth.
20
22
 
@@ -94,9 +96,29 @@ def _load() -> dict:
94
96
  data = load_registry_json(_registry_path(), lambda: {"tasks": {}, "branches": {}})
95
97
  data.setdefault("tasks", {})
96
98
  data.setdefault("branches", {})
99
+ data["branches"] = _by_project(data["branches"])
97
100
  return data
98
101
 
99
102
 
103
+ def _by_project(branches: dict) -> dict:
104
+ """브랜치 색인을 `{project_id: {branch: task_key}}` 로 정규화한다.
105
+
106
+ 브랜치 이름은 그 프로젝트의 저장소 안에서만 의미가 있다. 색인이 프로젝트를
107
+ 구분하지 않던 시절의 평면 구조(`{branch: task_key}`)를 읽으면 task-key 의 첫
108
+ 마디가 곧 project_id 라는 점을 이용해 제자리에서 옮긴다 — 옮기지 않으면 옛 행이
109
+ 검사에 걸리지 않아 같은 프로젝트 안의 진짜 충돌을 놓친다.
110
+ """
111
+ migrated: dict[str, dict] = {}
112
+ for key, value in branches.items():
113
+ if isinstance(value, dict):
114
+ migrated.setdefault(key, {}).update(value)
115
+ continue
116
+ owner = str(value)
117
+ project = owner.split("/", 1)[0]
118
+ migrated.setdefault(project, {})[key] = owner
119
+ return migrated
120
+
121
+
100
122
  def _save(data: dict) -> None:
101
123
  save_registry_json(_registry_path(), data)
102
124
 
@@ -145,12 +167,16 @@ def reserve(
145
167
  f"{existing['worktree_path']} (branch {existing['branch']}). "
146
168
  "Use `lookup` to reuse it, or release it before reserving anew."
147
169
  )
148
- owner = data["branches"].get(branch)
170
+ # 브랜치 유일성은 프로젝트 안에서만 따진다. 다른 프로젝트의 같은 이름은
171
+ # 다른 저장소의 다른 브랜치이고, 같은 저장소 안의 진짜 충돌은 provision 의
172
+ # `_branch_exists` 가 git 에게 먼저 물어 잡는다.
173
+ project_branches = data["branches"].setdefault(project_id, {})
174
+ owner = project_branches.get(branch)
149
175
  if owner and owner != key:
150
176
  raise RuntimeError(
151
- f"branch {branch!r} is already registered to a different "
152
- f"task-key: {owner}. Choose a different work-category or "
153
- "release the conflicting task first."
177
+ f"branch {branch!r} is already registered in project "
178
+ f"{project_id!r} to a different task-key: {owner}. Choose a "
179
+ "different work-category or release the conflicting task first."
154
180
  )
155
181
  row = {
156
182
  "project_id": project_id,
@@ -172,7 +198,7 @@ def reserve(
172
198
  if existing and existing.get("implementation_base_commit"):
173
199
  row["implementation_base_commit"] = existing["implementation_base_commit"]
174
200
  data["tasks"][key] = row
175
- data["branches"][branch] = key
201
+ project_branches[branch] = key
176
202
  _save(data)
177
203
  return WorktreeEntry(task_key=key, **row)
178
204
 
@@ -346,8 +372,11 @@ def free_branch_slot(
346
372
  if not row:
347
373
  return False
348
374
  branch = row.get("branch")
349
- if branch and data["branches"].get(branch) == key:
350
- del data["branches"][branch]
375
+ project_branches = data["branches"].get(row.get("project_id") or project_id, {})
376
+ if branch and project_branches.get(branch) == key:
377
+ del project_branches[branch]
378
+ if not project_branches:
379
+ data["branches"].pop(row.get("project_id") or project_id, None)
351
380
  _save(data)
352
381
  return True
353
382
  return False
@@ -139,10 +139,12 @@ GEMINI_PRICING = {
139
139
  # forms appear in the wild, so include both for the new 3.x families.
140
140
 
141
141
  # Gemini 3 series.
142
- # 3.7 Flash 와 3.6 Flash 는 2026-12-31 까지 도입 요율 $0.75/$3.75 이고
143
- # 2027-01-01 에 표준 요율 $1.50/$7.50 으로 오른다(2026-08-26 확인). 이 표에는
144
- # 시점 개념이 없으므로 지금 청구되는 값을 담는다 — 그날 이후 두 계열을
145
- # (1.50, 7.50) 으로 갱신해야 한다.
142
+ # 3.8 Flash·3.7 Flash·3.6 Flash 는 2026-12-31 까지 도입 요율 $0.75/$3.75 이고
143
+ # 2027-01-01 에 표준 요율 $1.50/$7.50 으로 오른다(ai.google.dev/gemini-api/
144
+ # docs/pricing, 2026-09-23 확인). 이 표에는 시점 개념이 없으므로 지금 청구되는
145
+ # 값을 담는다 — 그날 이후 세 계열을 (1.50, 7.50) 으로 갱신해야 한다.
146
+ "3.8-flash": (0.75, 3.75),
147
+ "3-8-flash": (0.75, 3.75),
146
148
  "3.7-flash": (0.75, 3.75),
147
149
  "3-7-flash": (0.75, 3.75),
148
150
  "3.6-flash": (0.75, 3.75),
@@ -0,0 +1,34 @@
1
+ {
2
+ "type": "object",
3
+ "required": [
4
+ "schemaVersion",
5
+ "id",
6
+ "assignmentFidelity",
7
+ "requiredInputs",
8
+ "evidenceFirst",
9
+ "authorityAndScope",
10
+ "collaborationAndIndependence",
11
+ "instructionPrecedence",
12
+ "conflictHandling",
13
+ "completionHonesty"
14
+ ],
15
+ "properties": {
16
+ "schemaVersion": { "const": "1.0" },
17
+ "id": { "const": "common" },
18
+ "assignmentFidelity": { "$ref": "#/$defs/stringList" },
19
+ "requiredInputs": { "$ref": "#/$defs/stringList" },
20
+ "evidenceFirst": { "$ref": "#/$defs/stringList" },
21
+ "authorityAndScope": { "$ref": "#/$defs/stringList" },
22
+ "collaborationAndIndependence": { "$ref": "#/$defs/stringList" },
23
+ "instructionPrecedence": { "$ref": "#/$defs/stringList" },
24
+ "conflictHandling": { "$ref": "#/$defs/stringList" },
25
+ "completionHonesty": { "$ref": "#/$defs/stringList" }
26
+ },
27
+ "additionalProperties": false,
28
+ "$defs": {
29
+ "stringList": {
30
+ "type": "array",
31
+ "items": { "type": "string", "minLength": 1 }
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,38 @@
1
+ {
2
+ "type": "object",
3
+ "required": [
4
+ "schemaVersion",
5
+ "id",
6
+ "roleId",
7
+ "responsibilities",
8
+ "requiredConduct",
9
+ "decisionPrinciples",
10
+ "authorityAndBoundaries",
11
+ "evidenceStandards",
12
+ "collaborationContract",
13
+ "completionCriteria",
14
+ "prohibitions",
15
+ "blockedStateReporting"
16
+ ],
17
+ "properties": {
18
+ "schemaVersion": { "const": "1.0" },
19
+ "id": { "type": "string", "minLength": 1 },
20
+ "roleId": { "type": "string", "minLength": 1 },
21
+ "responsibilities": { "$ref": "#/$defs/stringList" },
22
+ "requiredConduct": { "$ref": "#/$defs/stringList" },
23
+ "decisionPrinciples": { "$ref": "#/$defs/stringList" },
24
+ "authorityAndBoundaries": { "$ref": "#/$defs/stringList" },
25
+ "evidenceStandards": { "$ref": "#/$defs/stringList" },
26
+ "collaborationContract": { "$ref": "#/$defs/stringList" },
27
+ "completionCriteria": { "$ref": "#/$defs/stringList" },
28
+ "prohibitions": { "$ref": "#/$defs/stringList" },
29
+ "blockedStateReporting": { "$ref": "#/$defs/stringList" }
30
+ },
31
+ "additionalProperties": false,
32
+ "$defs": {
33
+ "stringList": {
34
+ "type": "array",
35
+ "items": { "type": "string", "minLength": 1 }
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "type": "object",
3
+ "required": ["schemaVersion", "id", "dutyId", "count"],
4
+ "properties": {
5
+ "schemaVersion": { "const": "1.0" },
6
+ "id": { "type": "string", "minLength": 1 },
7
+ "dutyId": { "type": "string", "minLength": 1 },
8
+ "count": { "type": "integer", "minimum": 1 }
9
+ },
10
+ "additionalProperties": false
11
+ }