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.
- package/README.md +3 -3
- package/dist/cli-registry.mjs +7 -7
- package/dist/cli-registry.mjs.map +1 -1
- package/dist/commands/lifecycle/install.mjs +50 -124
- package/dist/commands/lifecycle/install.mjs.map +1 -1
- package/dist/commands/memory/memory.mjs +41 -8
- package/dist/commands/memory/memory.mjs.map +1 -1
- package/dist/lib/install-assets.mjs +3 -0
- package/dist/lib/install-assets.mjs.map +1 -1
- package/dist/lib/runtime-manifest.mjs +2 -1
- package/dist/lib/runtime-manifest.mjs.map +1 -1
- package/dist/lib/types.d.mts +2 -1
- package/docs/architecture/storage-model.md +14 -11
- package/docs/architecture.md +25 -19
- package/docs/cli.md +15 -12
- package/docs/contributor-change-matrix.md +3 -2
- package/docs/performance-improvement-plan-v2.md +2 -3
- package/docs/project-structure-overview.md +35 -9
- package/docs/task-process/README.md +1 -1
- package/docs/task-process/common-flow.md +1 -1
- package/docs/task-process/final-verification.md +3 -1
- package/docs/task-process/implementation.md +1 -1
- package/docs/task-process/release-handoff.md +36 -39
- package/package.json +1 -2
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/common.json +28 -0
- package/runtime/agents/operations/code-review.json +6 -0
- package/runtime/agents/operations/report-translation.json +6 -0
- package/runtime/agents/operations/schedule-verification.json +6 -0
- package/runtime/agents/roles/analyser.json +18 -0
- package/runtime/agents/roles/critic.json +18 -0
- package/runtime/agents/roles/designer.json +18 -0
- package/runtime/agents/roles/implementer.json +20 -0
- package/runtime/agents/roles/leader.json +20 -0
- package/runtime/agents/roles/planner.json +18 -0
- package/runtime/agents/roles/report-writer.json +19 -0
- package/runtime/agents/roles/translator.json +19 -0
- package/runtime/agents/roles/verifier.json +18 -0
- package/runtime/bin/lib/okstra/usage.sh +5 -5
- package/runtime/prompts/duties/acceptance-critic.json +32 -0
- package/runtime/prompts/duties/acceptance-verifier.json +32 -0
- package/runtime/prompts/duties/analysis-worker.json +32 -0
- package/runtime/prompts/duties/code-reviewer.json +32 -0
- package/runtime/prompts/duties/diagnosis-worker.json +32 -0
- package/runtime/prompts/duties/direction-selection-worker.json +32 -0
- package/runtime/prompts/duties/discovery-worker.json +32 -0
- package/runtime/prompts/duties/implementation-executor.json +32 -0
- package/runtime/prompts/duties/implementation-verifier.json +32 -0
- package/runtime/prompts/duties/lead.json +32 -0
- package/runtime/prompts/duties/planning-worker.json +36 -0
- package/runtime/prompts/duties/report-writer.json +32 -0
- package/runtime/prompts/duties/reverification-worker.json +32 -0
- package/runtime/prompts/duties/schedule-verifier.json +32 -0
- package/runtime/prompts/duties/scope-critic.json +32 -0
- package/runtime/prompts/duties/technical-verification-worker.json +32 -0
- package/runtime/prompts/duties/translator.json +32 -0
- package/runtime/prompts/launch.template.md +2 -1
- package/runtime/prompts/lead/adapters/cmux.md +1 -1
- package/runtime/prompts/lead/convergence.md +4 -4
- package/runtime/prompts/lead/okstra-lead-contract.md +113 -4
- package/runtime/prompts/lead/plan-body-verification.md +6 -6
- package/runtime/prompts/lead/report-writer.md +3 -3
- package/runtime/prompts/profiles/_common-contract.md +2 -2
- package/runtime/prompts/profiles/_implementation-executor.md +4 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +2 -2
- package/runtime/prompts/profiles/change-impact-analysis.json +31 -0
- package/runtime/prompts/profiles/change-impact-analysis.md +0 -20
- package/runtime/prompts/profiles/error-analysis.json +39 -0
- package/runtime/prompts/profiles/error-analysis.md +0 -25
- package/runtime/prompts/profiles/feature-analysis.json +31 -0
- package/runtime/prompts/profiles/feature-analysis.md +0 -20
- package/runtime/prompts/profiles/final-verification.json +30 -0
- package/runtime/prompts/profiles/final-verification.md +3 -22
- package/runtime/prompts/profiles/forbidden-actions.json +4 -3
- package/runtime/prompts/profiles/implementation-option-selection.json +31 -0
- package/runtime/prompts/profiles/implementation-option-selection.md +0 -20
- package/runtime/prompts/profiles/implementation-planning.json +40 -0
- package/runtime/prompts/profiles/implementation-planning.md +4 -29
- package/runtime/prompts/profiles/implementation.json +30 -0
- package/runtime/prompts/profiles/implementation.md +1 -20
- package/runtime/prompts/profiles/improvement-discovery.json +31 -0
- package/runtime/prompts/profiles/improvement-discovery.md +0 -20
- package/runtime/prompts/profiles/project-analysis.json +31 -0
- package/runtime/prompts/profiles/project-analysis.md +0 -20
- package/runtime/prompts/profiles/release-handoff.json +5 -0
- package/runtime/prompts/profiles/release-handoff.md +71 -73
- package/runtime/prompts/profiles/requirements-discovery.json +39 -0
- package/runtime/prompts/profiles/requirements-discovery.md +0 -25
- package/runtime/prompts/profiles/technical-verification.json +39 -0
- package/runtime/prompts/profiles/technical-verification.md +0 -25
- package/runtime/prompts/wizard/prompts.ko.json +12 -17
- package/runtime/python/okstra_ctl/adapters/hosts/antigravity/relay.md +1 -0
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +3 -0
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/manifest.json +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +4 -3
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/worker-session.md +108 -0
- package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +1 -0
- package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +2 -0
- package/runtime/python/okstra_ctl/adapters/hosts/kimi/relay.md +2 -0
- package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +8 -1
- package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +8 -0
- package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +23 -6
- package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +6 -2
- package/runtime/python/okstra_ctl/agent/invocation.py +168 -113
- package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +120 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +107 -2
- package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +0 -49
- package/runtime/python/okstra_ctl/analysis_packet.py +4 -1
- package/runtime/python/okstra_ctl/application/open_worker.py +6 -1
- package/runtime/python/okstra_ctl/assignment_resolver.py +16 -5
- package/runtime/python/okstra_ctl/cmux.py +69 -20
- package/runtime/python/okstra_ctl/code_review_target.py +16 -8
- package/runtime/python/okstra_ctl/conformance.py +43 -0
- package/runtime/python/okstra_ctl/consumers.py +6 -3
- package/runtime/python/okstra_ctl/container.py +31 -8
- package/runtime/python/okstra_ctl/context_cost.py +11 -15
- package/runtime/python/okstra_ctl/contract_refreeze.py +156 -0
- package/runtime/python/okstra_ctl/convergence_provenance.py +7 -1
- package/runtime/python/okstra_ctl/design_prep.py +34 -1
- package/runtime/python/okstra_ctl/dispatch_core.py +53 -27
- package/runtime/python/okstra_ctl/domain/host.py +5 -0
- package/runtime/python/okstra_ctl/domain/worker_runtime.py +10 -0
- package/runtime/python/okstra_ctl/error_report.py +4 -3
- package/runtime/python/okstra_ctl/execution_manifest.py +71 -18
- package/runtime/python/okstra_ctl/handoff.py +167 -277
- package/runtime/python/okstra_ctl/implementation_stage.py +9 -0
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +113 -0
- package/runtime/python/okstra_ctl/lead_progress.py +1 -1
- package/runtime/python/okstra_ctl/legacy_model_selection.py +2 -2
- package/runtime/python/okstra_ctl/manager_cli.py +92 -4
- package/runtime/python/okstra_ctl/manager_launch.py +1 -1
- package/runtime/python/okstra_ctl/manager_paths.py +14 -3
- package/runtime/python/okstra_ctl/manager_store.py +210 -3
- package/runtime/python/okstra_ctl/manager_sync.py +4 -1
- package/runtime/python/okstra_ctl/manager_view.py +2 -1
- package/runtime/python/okstra_ctl/model_discovery.py +30 -0
- package/runtime/python/okstra_ctl/model_io/lines.py +14 -1
- package/runtime/python/okstra_ctl/model_io/renderers.py +4 -3
- package/runtime/python/okstra_ctl/models.py +1 -1
- package/runtime/python/okstra_ctl/next_phase.py +16 -6
- package/runtime/python/okstra_ctl/operation_invocation.py +86 -0
- package/runtime/python/okstra_ctl/option_comparison.py +168 -0
- package/runtime/python/okstra_ctl/path_hints.py +9 -0
- package/runtime/python/okstra_ctl/paths.py +3 -0
- package/runtime/python/okstra_ctl/profile_show.py +42 -1
- package/runtime/python/okstra_ctl/registry/host_discovery.py +20 -12
- package/runtime/python/okstra_ctl/registry/host_registry.py +11 -0
- package/runtime/python/okstra_ctl/render.py +50 -0
- package/runtime/python/okstra_ctl/report_contract.py +1 -1
- package/runtime/python/okstra_ctl/report_html/view_models/release_handoff.py +21 -3
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +177 -17
- package/runtime/python/okstra_ctl/report_translation.py +2 -1
- package/runtime/python/okstra_ctl/report_translation_dispatch.py +69 -9
- package/runtime/python/okstra_ctl/role_requirements.py +142 -129
- package/runtime/python/okstra_ctl/rollup.py +3 -1
- package/runtime/python/okstra_ctl/run.py +76 -29
- package/runtime/python/okstra_ctl/schedule_semantics.py +17 -6
- package/runtime/python/okstra_ctl/stage_fix_carry.py +23 -4
- package/runtime/python/okstra_ctl/stage_integrate.py +178 -18
- package/runtime/python/okstra_ctl/stage_map.py +16 -2
- package/runtime/python/okstra_ctl/stage_targets.py +209 -43
- package/runtime/python/okstra_ctl/team.py +22 -13
- package/runtime/python/okstra_ctl/time_report.py +2 -1
- package/runtime/python/okstra_ctl/usage_report.py +3 -1
- package/runtime/python/okstra_ctl/wizard/confirmation.py +3 -9
- package/runtime/python/okstra_ctl/wizard/ids.py +1 -1
- package/runtime/python/okstra_ctl/wizard/registry.py +1 -1
- package/runtime/python/okstra_ctl/wizard/state.py +3 -5
- package/runtime/python/okstra_ctl/wizard/steps_plan.py +3 -23
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +5 -1
- package/runtime/python/okstra_ctl/worker_prompt_headers.py +35 -7
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +66 -48
- package/runtime/python/okstra_ctl/workflow.py +1 -1
- package/runtime/python/okstra_ctl/worktree/__init__.py +3 -1
- package/runtime/python/okstra_ctl/worktree/naming.py +9 -0
- package/runtime/python/okstra_ctl/worktree_registry.py +38 -9
- package/runtime/python/okstra_token_usage/pricing.py +6 -4
- package/runtime/schemas/agent-common-v1.schema.json +34 -0
- package/runtime/schemas/agent-duty-v1.schema.json +38 -0
- package/runtime/schemas/agent-operation-v1.schema.json +11 -0
- package/runtime/schemas/agent-profile-v1.schema.json +46 -0
- package/runtime/schemas/agent-role-v1.schema.json +29 -0
- package/runtime/schemas/final-report-v2.0.schema.json +118 -97
- package/runtime/schemas/final-report-v3.0.schema.json +118 -97
- package/runtime/skills/okstra-brief-gen/SKILL.md +84 -4
- package/runtime/skills/okstra-chat/SKILL.md +2 -2
- package/runtime/skills/okstra-code-review/SKILL.md +23 -9
- package/runtime/skills/okstra-container-build/SKILL.md +10 -10
- package/runtime/skills/okstra-inspect/SKILL.md +1 -1
- package/runtime/skills/okstra-inspect/facets/cost.md +1 -1
- package/runtime/skills/okstra-inspect/facets/error-zip.md +9 -9
- package/runtime/skills/okstra-inspect/facets/errors.md +16 -16
- package/runtime/skills/okstra-inspect/facets/logs.md +7 -7
- package/runtime/skills/okstra-inspect/facets/recap.md +2 -2
- package/runtime/skills/okstra-inspect/facets/report.md +1 -1
- package/runtime/skills/okstra-inspect/facets/status.md +4 -3
- package/runtime/skills/okstra-inspect/facets/time.md +11 -10
- package/runtime/skills/okstra-manager/SKILL.md +18 -2
- package/runtime/skills/okstra-pr-gen/SKILL.md +6 -5
- package/runtime/skills/okstra-rollup/SKILL.md +5 -5
- package/runtime/skills/okstra-run/SKILL.md +31 -12
- package/runtime/skills/okstra-schedule-gen/SKILL.md +19 -14
- package/runtime/skills/okstra-setup/SKILL.md +12 -10
- package/runtime/skills/okstra-setup/references/project-config.md +7 -6
- package/runtime/skills/okstra-usage/SKILL.md +1 -1
- package/runtime/skills/okstra-user-response/SKILL.md +1 -1
- package/runtime/templates/manager/view.template.html +1 -0
- package/runtime/templates/report-writer-prompt-preamble.md +8 -0
- package/runtime/templates/reports/brief.template.md +14 -4
- package/runtime/templates/reports/html/i18n/en.json +5 -4
- package/runtime/templates/reports/html/i18n/ko.json +5 -4
- package/runtime/templates/reports/html/tasks/release-handoff.template.html +8 -5
- package/runtime/templates/reports/i18n/en.json +1 -1
- package/runtime/templates/reports/md/tasks/release-handoff.template.md +1 -1
- package/runtime/templates/reports/release-handoff-input.template.md +6 -4
- package/runtime/templates/translator-prompt-preamble.md +36 -0
- package/runtime/validators/checks/validate-assets-01.py +7 -8
- package/runtime/validators/validate-brief.py +70 -0
- package/runtime/validators/validate-implementation-plan-stages.py +2 -1
- package/runtime/validators/validate-run.py +59 -9
- package/runtime/validators/validate-schedule.py +9 -0
- package/docs/for-ai/README.md +0 -68
- package/docs/for-ai/skills/okstra-brief-gen.md +0 -262
- package/docs/for-ai/skills/okstra-chat.md +0 -34
- package/docs/for-ai/skills/okstra-code-review.md +0 -57
- package/docs/for-ai/skills/okstra-container-build.md +0 -129
- package/docs/for-ai/skills/okstra-inspect.md +0 -262
- package/docs/for-ai/skills/okstra-manager.md +0 -86
- package/docs/for-ai/skills/okstra-memory.md +0 -126
- package/docs/for-ai/skills/okstra-pr-gen.md +0 -49
- package/docs/for-ai/skills/okstra-rollup.md +0 -114
- package/docs/for-ai/skills/okstra-run.md +0 -250
- package/docs/for-ai/skills/okstra-schedule-gen.md +0 -240
- package/docs/for-ai/skills/okstra-setup.md +0 -167
- package/docs/for-ai/skills/okstra-usage.md +0 -29
- package/docs/for-ai/skills/okstra-user-response.md +0 -72
- package/runtime/agents/workers/claude-worker.md +0 -128
- package/runtime/agents/workers/report-writer-worker.md +0 -37
- package/runtime/agents/workers/translator-worker.md +0 -63
- package/runtime/prompts/duties/acceptance-critic.md +0 -44
- package/runtime/prompts/duties/acceptance-verifier.md +0 -44
- package/runtime/prompts/duties/analysis-worker.md +0 -44
- package/runtime/prompts/duties/code-reviewer.md +0 -44
- package/runtime/prompts/duties/common.md +0 -39
- package/runtime/prompts/duties/diagnosis-worker.md +0 -44
- package/runtime/prompts/duties/direction-selection-worker.md +0 -44
- package/runtime/prompts/duties/discovery-worker.md +0 -44
- package/runtime/prompts/duties/implementation-executor.md +0 -44
- package/runtime/prompts/duties/implementation-verifier.md +0 -44
- package/runtime/prompts/duties/lead.md +0 -44
- package/runtime/prompts/duties/planning-worker.md +0 -52
- package/runtime/prompts/duties/report-writer.md +0 -44
- package/runtime/prompts/duties/reverification-worker.md +0 -44
- package/runtime/prompts/duties/schedule-verifier.md +0 -44
- package/runtime/prompts/duties/scope-critic.md +0 -44
- package/runtime/prompts/duties/technical-verification-worker.md +0 -44
- package/runtime/prompts/duties/translator.md +0 -44
- 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.
|
|
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
|
-
|
|
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.
|
|
358
|
-
scope = (
|
|
359
|
-
|
|
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
|
-
# 값 규칙은 `
|
|
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=("
|
|
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
|
|
107
|
-
|
|
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
|
-
"
|
|
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
|
|
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"]["
|
|
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
|
-
|
|
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
|
-
|
|
188
|
-
if
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
171
|
-
executor_worker_id: str | None,
|
|
171
|
+
role: str | None,
|
|
172
172
|
dispatch_kind: str,
|
|
173
173
|
) -> PromptPlan:
|
|
174
|
-
"""Resolve prompt policy
|
|
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
|
|
180
|
-
|
|
181
|
-
|
|
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
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
"
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
|
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
|
-
|
|
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`);
|
|
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
|
|
17
|
-
A
|
|
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
|
-
|
|
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
|
|
152
|
-
f"task-key: {owner}. Choose a
|
|
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
|
-
|
|
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
|
-
|
|
350
|
-
|
|
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
|
|
143
|
-
# 2027-01-01 에 표준 요율 $1.50/$7.50 으로 오른다(
|
|
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
|
+
}
|