okstra 0.76.0 → 0.78.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.kr.md +5 -3
- package/README.md +5 -3
- package/bin/okstra +1 -117
- package/docs/contributor-change-matrix.md +12 -0
- package/docs/kr/architecture.md +1 -1
- package/docs/kr/cli.md +22 -5
- package/docs/pr-template-usage.md +3 -3
- package/docs/project-structure-overview.md +1 -1
- package/docs/superpowers/plans/2026-05-25-okstra-project-root-rename.md +1 -1
- package/docs/superpowers/plans/2026-06-13-repo-risk-hardening.md +493 -0
- package/docs/superpowers/specs/2026-06-12-codex-lead-adapter-design.md +358 -0
- package/docs/superpowers/specs/2026-06-13-forbidden-actions-ssot-design.md +134 -0
- package/docs/superpowers/specs/2026-06-13-neutral-tmux-lead-adapter-design.md +284 -0
- package/package.json +5 -2
- package/runtime/BUILD.json +2 -2
- package/runtime/DO_NOT_EDIT.md +21 -0
- package/runtime/agents/SKILL.md +3 -0
- package/runtime/bin/lib/okstra/cli.sh +5 -1
- package/runtime/bin/lib/okstra/globals.sh +1 -0
- package/runtime/bin/lib/okstra/usage.sh +6 -4
- package/runtime/bin/okstra-trace-cleanup.sh +16 -12
- package/runtime/bin/okstra.sh +1 -0
- package/runtime/prompts/launch.template.md +4 -13
- package/runtime/prompts/profiles/error-analysis.md +6 -0
- package/runtime/prompts/profiles/forbidden-actions.json +69 -0
- package/runtime/prompts/profiles/implementation.md +1 -8
- package/runtime/prompts/profiles/improvement-discovery.md +5 -0
- package/runtime/prompts/profiles/release-handoff.md +3 -17
- package/runtime/python/okstra_ctl/analysis_packet.py +17 -0
- package/runtime/python/okstra_ctl/codex_dispatch.py +1552 -0
- package/runtime/python/okstra_ctl/context_cost.py +1 -1
- package/runtime/python/okstra_ctl/dispatch_core.py +897 -0
- package/runtime/python/okstra_ctl/doctor.py +292 -0
- package/runtime/python/okstra_ctl/lead_events.py +129 -0
- package/runtime/python/okstra_ctl/lead_runtime.py +72 -0
- package/runtime/python/okstra_ctl/paths.py +3 -0
- package/runtime/python/okstra_ctl/pr_template.py +1 -1
- package/runtime/python/okstra_ctl/render.py +211 -29
- package/runtime/python/okstra_ctl/run.py +89 -18
- package/runtime/python/okstra_ctl/team.py +267 -0
- package/runtime/python/okstra_ctl/tmux.py +181 -10
- package/runtime/python/okstra_ctl/workflow.py +30 -71
- package/runtime/python/okstra_ctl/wrapper_status.py +55 -0
- package/runtime/python/okstra_token_usage/codex.py +12 -7
- package/runtime/python/okstra_token_usage/collect.py +243 -54
- package/runtime/python/okstra_token_usage/gemini.py +12 -7
- package/runtime/schemas/final-report-v1.0.schema.json +3 -1
- package/runtime/skills/okstra-convergence/SKILL.md +3 -0
- package/runtime/skills/okstra-report-writer/SKILL.md +3 -0
- package/runtime/skills/okstra-setup/SKILL.md +1 -1
- package/runtime/skills/okstra-team-contract/SKILL.md +12 -0
- package/runtime/validators/forbidden_actions.py +135 -0
- package/runtime/validators/validate-run.py +112 -22
- package/runtime/validators/validate_session_conformance.py +127 -5
- package/src/cli-registry.mjs +277 -0
- package/src/codex-dispatch.mjs +70 -0
- package/src/codex-run.mjs +68 -0
- package/src/doctor.mjs +130 -5
- package/src/install.mjs +123 -26
- package/src/paths.mjs +17 -3
- package/src/render-bundle.mjs +2 -0
- package/src/runtime-manifest.mjs +29 -0
- package/src/team.mjs +63 -0
- package/src/uninstall.mjs +27 -4
- /package/runtime/{skills/okstra-run/templates → templates/prd}/pr-body.template.md +0 -0
|
@@ -28,6 +28,7 @@ from okstra_project.dirs import OKSTRA_DIR_NAME, project_json_path
|
|
|
28
28
|
# render_task_manifest 가 동일한 리스트/딕셔너리를 로컬에 중복 정의했는데,
|
|
29
29
|
# 이는 silent drift 위험이 있어 SSOT import 로 통합한다.
|
|
30
30
|
from . import fix_cycles
|
|
31
|
+
from .lead_runtime import lead_runtime_info
|
|
31
32
|
from .workflow import DEFAULT_NEXT_PHASE, PHASE_SEQUENCE
|
|
32
33
|
|
|
33
34
|
|
|
@@ -66,6 +67,30 @@ def _write_json(path: Path, payload: dict) -> None:
|
|
|
66
67
|
_write_text(path, json.dumps(payload, indent=2, ensure_ascii=False) + "\n")
|
|
67
68
|
|
|
68
69
|
|
|
70
|
+
def _lead_runtime(ctx: dict) -> str:
|
|
71
|
+
return ctx.get("LEAD_RUNTIME", "") or "claude-code"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _lead_info(ctx: dict):
|
|
75
|
+
return lead_runtime_info(_lead_runtime(ctx))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _lead_agent(ctx: dict) -> str:
|
|
79
|
+
return _lead_info(ctx).agent
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _lead_agent_label(ctx: dict) -> str:
|
|
83
|
+
return _lead_info(ctx).agent_label
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _lead_role(ctx: dict) -> str:
|
|
87
|
+
return _lead_info(ctx).role
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _lead_adapter(ctx: dict) -> dict:
|
|
91
|
+
return _lead_info(ctx).adapter_payload()
|
|
92
|
+
|
|
93
|
+
|
|
69
94
|
_PHASE_BLOCK_RE = re.compile(
|
|
70
95
|
r"\{% if header\.taskType == '(implementation-planning|release-handoff|implementation|final-verification)' %\}\n(.*?)\{% endif %\}\n",
|
|
71
96
|
re.DOTALL,
|
|
@@ -415,11 +440,14 @@ def render_team_state(team_state_path: str, ctx: dict) -> None:
|
|
|
415
440
|
"schemaVersion": "1.0",
|
|
416
441
|
"taskKey": ctx.get("TASK_KEY", ""),
|
|
417
442
|
"taskType": ctx.get("TASK_TYPE", ""),
|
|
443
|
+
"leadRuntime": _lead_runtime(ctx),
|
|
444
|
+
"leadAdapter": _lead_adapter(ctx),
|
|
445
|
+
"leadEventsPath": ctx.get("LEAD_EVENTS_RELATIVE_PATH", ""),
|
|
418
446
|
"runDirectoryPath": ctx.get("RUN_DIR_RELATIVE_PATH", ""),
|
|
419
447
|
"workflowState": ctx.get("CURRENT_RUN_STATUS", ""),
|
|
420
448
|
"lead": {
|
|
421
|
-
"role":
|
|
422
|
-
"agent":
|
|
449
|
+
"role": _lead_role(ctx),
|
|
450
|
+
"agent": _lead_agent(ctx),
|
|
423
451
|
"model": ctx.get("LEAD_MODEL", ""),
|
|
424
452
|
"modelExecutionValue": ctx.get("LEAD_MODEL_EXECUTION_VALUE", ""),
|
|
425
453
|
"status": ctx.get("CURRENT_RUN_STATUS", ""),
|
|
@@ -876,7 +904,9 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
|
|
|
876
904
|
catalog = _worker_catalog(ctx)
|
|
877
905
|
required_worker_roles = _required_worker_roles(ctx, reviewers)
|
|
878
906
|
worker_prompt_paths = {item: catalog[item]["promptPath"] for item in reviewers}
|
|
879
|
-
|
|
907
|
+
lead_agent = _lead_agent(ctx)
|
|
908
|
+
lead_role = _lead_role(ctx)
|
|
909
|
+
required_agent_status_entries = [lead_role] + [
|
|
880
910
|
catalog[item]["role"] for item in reviewers
|
|
881
911
|
]
|
|
882
912
|
related_tasks = json.loads(ctx.get("RELATED_TASKS_JSON", "[]"))
|
|
@@ -925,6 +955,8 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
|
|
|
925
955
|
"taskIdPathSegment": ctx.get("TASK_ID_SEGMENT", ""),
|
|
926
956
|
"projectRoot": ctx.get("PROJECT_ROOT", ""),
|
|
927
957
|
"taskType": ctx.get("TASK_TYPE", ""),
|
|
958
|
+
"leadRuntime": _lead_runtime(ctx),
|
|
959
|
+
"leadAdapter": _lead_adapter(ctx),
|
|
928
960
|
"workCategory": work_category,
|
|
929
961
|
"taskBriefPath": ctx.get("BRIEF_RELATIVE_PATH", ""),
|
|
930
962
|
"recommendedWorkers": reviewers,
|
|
@@ -984,16 +1016,17 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
|
|
|
984
1016
|
"resumeCommandPath": ctx.get("CLAUDE_RESUME_COMMAND_RELATIVE_PATH", ""),
|
|
985
1017
|
"teamStatePath": ctx.get("TEAM_STATE_RELATIVE_PATH", ""),
|
|
986
1018
|
"activeRunContextPath": ctx.get("ACTIVE_RUN_CONTEXT_RELATIVE_PATH", ""),
|
|
1019
|
+
"leadEventsPath": ctx.get("LEAD_EVENTS_RELATIVE_PATH", ""),
|
|
987
1020
|
"workerResultsDirectoryPath": ctx.get("WORKER_RESULTS_RELATIVE_PATH", ""),
|
|
988
1021
|
"validatorScriptPath": ctx.get("RUN_VALIDATOR_RELATIVE_PATH", ""),
|
|
989
1022
|
},
|
|
990
1023
|
"resultContract": {
|
|
991
|
-
"leadAgent":
|
|
992
|
-
"leadRole":
|
|
1024
|
+
"leadAgent": lead_agent,
|
|
1025
|
+
"leadRole": lead_role,
|
|
993
1026
|
"leadModel": ctx.get("LEAD_MODEL", ""),
|
|
994
1027
|
"leadModelExecutionValue": ctx.get("LEAD_MODEL_EXECUTION_VALUE", ""),
|
|
995
1028
|
"leadExecutionMode": "synthesis-only",
|
|
996
|
-
"finalSynthesisOwner":
|
|
1029
|
+
"finalSynthesisOwner": lead_role,
|
|
997
1030
|
"artifactFirst": True,
|
|
998
1031
|
"resultCollectionMode": "claude-managed",
|
|
999
1032
|
"finalReportFormat": "markdown",
|
|
@@ -1127,6 +1160,8 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
|
|
|
1127
1160
|
catalog = _worker_catalog(ctx)
|
|
1128
1161
|
required_worker_roles = _required_worker_roles(ctx, reviewers)
|
|
1129
1162
|
worker_prompt_paths = {item: catalog[item]["promptPath"] for item in reviewers}
|
|
1163
|
+
lead_agent = _lead_agent(ctx)
|
|
1164
|
+
lead_role = _lead_role(ctx)
|
|
1130
1165
|
related_tasks = json.loads(ctx.get("RELATED_TASKS_JSON", "[]"))
|
|
1131
1166
|
workflow = (
|
|
1132
1167
|
task_manifest.get("workflow", {})
|
|
@@ -1149,6 +1184,8 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
|
|
|
1149
1184
|
"taskId": ctx.get("TASK_ID", ""),
|
|
1150
1185
|
"taskKey": ctx.get("TASK_KEY", ""),
|
|
1151
1186
|
"taskType": ctx.get("TASK_TYPE", ""),
|
|
1187
|
+
"leadRuntime": _lead_runtime(ctx),
|
|
1188
|
+
"leadAdapter": _lead_adapter(ctx),
|
|
1152
1189
|
"workCategory": task_manifest.get(
|
|
1153
1190
|
"workCategory", ctx.get("WORKFLOW_WORK_CATEGORY", "unknown")
|
|
1154
1191
|
),
|
|
@@ -1174,6 +1211,7 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
|
|
|
1174
1211
|
"workerResults": ctx.get("WORKER_RESULTS_SEQ", ""),
|
|
1175
1212
|
},
|
|
1176
1213
|
"runManifestPath": ctx.get("RUN_MANIFEST_RELATIVE_PATH", ""),
|
|
1214
|
+
"leadEventsPath": ctx.get("LEAD_EVENTS_RELATIVE_PATH", ""),
|
|
1177
1215
|
"promptSnapshotPath": ctx.get("RUN_PROMPT_SNAPSHOT_RELATIVE_PATH", ""),
|
|
1178
1216
|
"workerPromptsDirectoryPath": ctx.get("RUN_PROMPTS_RELATIVE_PATH", ""),
|
|
1179
1217
|
"workerPromptPathByWorkerId": worker_prompt_paths,
|
|
@@ -1216,15 +1254,15 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
|
|
|
1216
1254
|
"lastSafeCheckpoint": workflow.get("lastSafeCheckpoint", {}),
|
|
1217
1255
|
},
|
|
1218
1256
|
"teamContract": {
|
|
1219
|
-
"leadAgent":
|
|
1220
|
-
"leadRole":
|
|
1257
|
+
"leadAgent": lead_agent,
|
|
1258
|
+
"leadRole": lead_role,
|
|
1221
1259
|
"leadModel": ctx.get("LEAD_MODEL", ""),
|
|
1222
1260
|
"leadModelExecutionValue": ctx.get("LEAD_MODEL_EXECUTION_VALUE", ""),
|
|
1223
1261
|
"leadExecutionMode": "synthesis-only",
|
|
1224
|
-
"finalSynthesisOwner":
|
|
1262
|
+
"finalSynthesisOwner": lead_role,
|
|
1225
1263
|
"requiredWorkerAttempts": reviewers,
|
|
1226
1264
|
"requiredWorkerRoles": required_worker_roles,
|
|
1227
|
-
"requiredAgentStatusEntries": [
|
|
1265
|
+
"requiredAgentStatusEntries": [lead_role]
|
|
1228
1266
|
+ [catalog[item]["role"] for item in reviewers],
|
|
1229
1267
|
"requireDistinctLeadFromClaudeWorker": True,
|
|
1230
1268
|
"requireAllRequiredWorkerAttempts": True,
|
|
@@ -1425,6 +1463,12 @@ def render_task_index(template_path: str, output_path: str, ctx: dict) -> None:
|
|
|
1425
1463
|
if isinstance(task_manifest.get("artifacts"), dict)
|
|
1426
1464
|
else {}
|
|
1427
1465
|
)
|
|
1466
|
+
lead_role = rc.get("leadRole", _lead_role(ctx))
|
|
1467
|
+
latest_resume_command = (
|
|
1468
|
+
task_manifest.get("latestResumeCommandPath")
|
|
1469
|
+
or ctx.get("CLAUDE_RESUME_COMMAND_RELATIVE_PATH", "")
|
|
1470
|
+
or "--"
|
|
1471
|
+
)
|
|
1428
1472
|
mapping = {
|
|
1429
1473
|
"{{TASK_KEY}}": task_manifest.get("taskKey", ctx.get("TASK_KEY", "")),
|
|
1430
1474
|
"{{TASK_TYPE}}": task_manifest.get("taskType", ctx.get("TASK_TYPE", "")),
|
|
@@ -1456,13 +1500,10 @@ def render_task_index(template_path: str, output_path: str, ctx: dict) -> None:
|
|
|
1456
1500
|
"{{VALIDATION_STATUS}}": cv.get(
|
|
1457
1501
|
"status", ctx.get("VALIDATION_STATUS", "not-run")
|
|
1458
1502
|
),
|
|
1459
|
-
"{{CLAUDE_RESUME_COMMAND_RELATIVE_PATH}}":
|
|
1460
|
-
"latestResumeCommandPath",
|
|
1461
|
-
ctx.get("CLAUDE_RESUME_COMMAND_RELATIVE_PATH", ""),
|
|
1462
|
-
),
|
|
1503
|
+
"{{CLAUDE_RESUME_COMMAND_RELATIVE_PATH}}": latest_resume_command,
|
|
1463
1504
|
"{{MODEL_ASSIGNMENT_LINES}}": "\n".join(
|
|
1464
1505
|
[
|
|
1465
|
-
f"- `
|
|
1506
|
+
f"- `{lead_role}`: `{rc.get('leadModel', ctx.get('LEAD_MODEL', ''))}`",
|
|
1466
1507
|
f"- `Claude worker`: `{ctx.get('CLAUDE_WORKER_MODEL', '')}`",
|
|
1467
1508
|
f"- `Codex worker`: `{ctx.get('CODEX_WORKER_MODEL', '')}`",
|
|
1468
1509
|
f"- `Gemini worker`: `{ctx.get('GEMINI_WORKER_MODEL', '')}`",
|
|
@@ -1597,22 +1638,22 @@ def build_available_mcp_servers_block(project_root: Path) -> str:
|
|
|
1597
1638
|
|
|
1598
1639
|
|
|
1599
1640
|
def inject_lead_prompt_computed_tokens(ctx: dict) -> None:
|
|
1600
|
-
"""Populate ctx in-place with
|
|
1641
|
+
"""Populate ctx in-place with derived lead-prompt tokens.
|
|
1601
1642
|
|
|
1602
|
-
Tokens that are not 1:1 with a ctx key (TEAM_CREATION_GATE,
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
EXECUTION_STATUS_TABLE_ROWS) are computed deterministically from ctx
|
|
1607
|
-
so the pure-lookup renderer (`render_template_with_ctx`) can resolve
|
|
1608
|
-
them via plain `ctx[token]` lookup.
|
|
1643
|
+
Tokens that are not 1:1 with a ctx key (TEAM_CREATION_GATE, lead identity
|
|
1644
|
+
blocks, worker dispatch guidance, worker result/status summaries, etc.) are
|
|
1645
|
+
computed deterministically from ctx so the pure-lookup renderer
|
|
1646
|
+
(`render_template_with_ctx`) can resolve them via plain `ctx[token]` lookup.
|
|
1609
1647
|
|
|
1610
|
-
Always overwrites — caller-supplied values for these
|
|
1648
|
+
Always overwrites — caller-supplied values for these computed keys are replaced
|
|
1611
1649
|
on every call. For optional defaults (VALIDATION_STATUS etc.) use the
|
|
1612
1650
|
companion `apply_lead_prompt_defaults` which preserves caller values.
|
|
1613
1651
|
"""
|
|
1614
1652
|
selected = _resolve_workers(ctx)
|
|
1615
1653
|
catalog = _worker_catalog(ctx)
|
|
1654
|
+
lead_runtime = _lead_runtime(ctx)
|
|
1655
|
+
lead_role = _lead_role(ctx)
|
|
1656
|
+
lead_agent_label = _lead_agent_label(ctx)
|
|
1616
1657
|
lead_model = ctx.get("LEAD_MODEL", "")
|
|
1617
1658
|
lead_model_execution = ctx.get("LEAD_MODEL_EXECUTION_VALUE", "")
|
|
1618
1659
|
|
|
@@ -1622,16 +1663,16 @@ def inject_lead_prompt_computed_tokens(ctx: dict) -> None:
|
|
|
1622
1663
|
return f"- `{role}`: `{model}`"
|
|
1623
1664
|
|
|
1624
1665
|
worker_result_lines: list[str] = []
|
|
1625
|
-
team_role_lines = [f" 1. `
|
|
1666
|
+
team_role_lines = [f" 1. `{lead_role}` (assigned model: `{lead_model}`)"]
|
|
1626
1667
|
model_assignment_lines = [
|
|
1627
|
-
fmt_assignment(
|
|
1668
|
+
fmt_assignment(lead_role, lead_model, lead_model_execution)
|
|
1628
1669
|
]
|
|
1629
1670
|
worker_role_labels: list[str] = []
|
|
1630
|
-
execution_status_entries = ["`
|
|
1671
|
+
execution_status_entries = [f"`{lead_role}`"]
|
|
1631
1672
|
execution_status_table_lines = [
|
|
1632
1673
|
"| 에이전트 | 역할 | 모델 | 상태 | 핵심 발견 요약 |",
|
|
1633
1674
|
"|----------|------|------|------|----------------|",
|
|
1634
|
-
f"|
|
|
1675
|
+
f"| {lead_agent_label} | {lead_role} | {lead_model} | completed / timeout / error / not-run | 최종 synthesis 작성 상태와 핵심 판단 |",
|
|
1635
1676
|
]
|
|
1636
1677
|
for index, worker in enumerate(selected, start=2):
|
|
1637
1678
|
m = catalog[worker]
|
|
@@ -1682,18 +1723,81 @@ def inject_lead_prompt_computed_tokens(ctx: dict) -> None:
|
|
|
1682
1723
|
# - All other phases keep the full team-creation contract.
|
|
1683
1724
|
task_type = ctx.get("TASK_TYPE", "")
|
|
1684
1725
|
concurrent_stages = str(ctx.get("CONCURRENT_RUN_STAGES", "") or "").strip()
|
|
1726
|
+
codex_dispatch_command = (
|
|
1727
|
+
"okstra codex-dispatch "
|
|
1728
|
+
f"--project-root {ctx.get('PROJECT_ROOT', '')} "
|
|
1729
|
+
f"--run-manifest {ctx.get('RUN_MANIFEST_RELATIVE_PATH', '')}"
|
|
1730
|
+
)
|
|
1731
|
+
team_dispatch_command = (
|
|
1732
|
+
"okstra team dispatch "
|
|
1733
|
+
f"--project-root {ctx.get('PROJECT_ROOT', '')} "
|
|
1734
|
+
f"--run-manifest {ctx.get('RUN_MANIFEST_RELATIVE_PATH', '')}"
|
|
1735
|
+
)
|
|
1736
|
+
team_await_command = (
|
|
1737
|
+
"okstra team await "
|
|
1738
|
+
f"--project-root {ctx.get('PROJECT_ROOT', '')} "
|
|
1739
|
+
f"--run-manifest {ctx.get('RUN_MANIFEST_RELATIVE_PATH', '')}"
|
|
1740
|
+
)
|
|
1685
1741
|
if task_type == "release-handoff" or not selected:
|
|
1686
1742
|
team_creation_gate_block = (
|
|
1687
1743
|
"## Single-Lead Phase (no team creation)\n"
|
|
1688
1744
|
"\n"
|
|
1689
1745
|
"This run is single-lead. There is no worker roster, no\n"
|
|
1690
1746
|
"`TeamCreate` call, no `Agent(...)` worker dispatch, and no\n"
|
|
1691
|
-
"convergence loop. The
|
|
1747
|
+
f"convergence loop. The {lead_role} performs every step inline\n"
|
|
1692
1748
|
"(reading inputs, drafting commit / PR text when applicable,\n"
|
|
1693
1749
|
"asking the user, running git / gh, and writing the final\n"
|
|
1694
1750
|
"report). Do NOT call `TeamCreate` or dispatch any sub-agent\n"
|
|
1695
1751
|
"from this run — that would be a contract violation."
|
|
1696
1752
|
)
|
|
1753
|
+
elif lead_runtime == "codex":
|
|
1754
|
+
team_creation_gate_block = (
|
|
1755
|
+
"## Codex Dispatch Gate (BLOCKING)\n"
|
|
1756
|
+
"\n"
|
|
1757
|
+
"This run was prepared for `leadRuntime=codex`. Do NOT call\n"
|
|
1758
|
+
"`TeamCreate`, do NOT call Claude Code `Agent(...)`, and do NOT\n"
|
|
1759
|
+
"invoke Claude slash skills. Codex dispatch is artifact-first and\n"
|
|
1760
|
+
"uses the prepared run manifest plus CLI-backed worker wrappers.\n"
|
|
1761
|
+
"\n"
|
|
1762
|
+
"Required actions, in order:\n"
|
|
1763
|
+
"\n"
|
|
1764
|
+
"1. Inspect the run manifest and team-state paths listed below.\n"
|
|
1765
|
+
"2. Emit `PROGRESS: phase-3-team-create skipped (codex-dispatch)`;\n"
|
|
1766
|
+
" there is no Claude Teams surface in this adapter.\n"
|
|
1767
|
+
"3. Plan worker execution with:\n"
|
|
1768
|
+
f" `{codex_dispatch_command} --dry-run`\n"
|
|
1769
|
+
"4. Dispatch supported Codex-side workers with:\n"
|
|
1770
|
+
f" `{codex_dispatch_command}`\n"
|
|
1771
|
+
" When `--workers` is omitted, unsupported roster entries such as\n"
|
|
1772
|
+
" `claude` are skipped with explicit `not-run` reasons.\n"
|
|
1773
|
+
"5. Run `report-writer` only with explicit opt-in:\n"
|
|
1774
|
+
f" `{codex_dispatch_command} --enable-codex-report-writer --report-writer-codex-model <model>`\n"
|
|
1775
|
+
"\n"
|
|
1776
|
+
"`okstra codex-dispatch` records `team-state.dispatchMode`, writes\n"
|
|
1777
|
+
"worker prompt histories when missing, persists worker results, and\n"
|
|
1778
|
+
"runs post-report validation for the Codex report-writer path."
|
|
1779
|
+
)
|
|
1780
|
+
elif lead_runtime == "external":
|
|
1781
|
+
team_creation_gate_block = (
|
|
1782
|
+
"## Tmux Worker Dispatch Gate (BLOCKING)\n"
|
|
1783
|
+
"\n"
|
|
1784
|
+
"This run was prepared for `leadRuntime=external`. Do NOT call "
|
|
1785
|
+
"`TeamCreate`, do NOT call Claude Code `Agent(...)`, and do NOT call "
|
|
1786
|
+
"`okstra codex-dispatch`. This adapter dispatches workers through "
|
|
1787
|
+
"okstra-owned tmux panes.\n"
|
|
1788
|
+
"\n"
|
|
1789
|
+
"Required actions, in order:\n"
|
|
1790
|
+
"1. Inspect the run manifest and team-state paths listed below.\n"
|
|
1791
|
+
"2. Emit `PROGRESS: phase-3-team-create skipped (tmux-pane)`; there is no Claude Teams surface in this adapter.\n"
|
|
1792
|
+
"3. Plan worker execution with:\n"
|
|
1793
|
+
f" `{team_dispatch_command} --dry-run`\n"
|
|
1794
|
+
"4. Dispatch workers with:\n"
|
|
1795
|
+
f" `{team_dispatch_command}`\n"
|
|
1796
|
+
"5. Arm one background wait using this harness's async shell primitive:\n"
|
|
1797
|
+
f" `{team_await_command}`\n"
|
|
1798
|
+
"6. On wake, read team-state. Worker completion is valid only from "
|
|
1799
|
+
"`workerDispatches[]` plus result files; pane creation alone is not completion."
|
|
1800
|
+
)
|
|
1697
1801
|
elif task_type == "implementation" and concurrent_stages:
|
|
1698
1802
|
team_creation_gate_block = (
|
|
1699
1803
|
"## Concurrent-run: no-team background (BLOCKING)\n"
|
|
@@ -1773,8 +1877,86 @@ def inject_lead_prompt_computed_tokens(ctx: dict) -> None:
|
|
|
1773
1877
|
"response is to go back to step 2 — NOT to strip `team_name` and retry."
|
|
1774
1878
|
)
|
|
1775
1879
|
|
|
1880
|
+
if lead_runtime == "external":
|
|
1881
|
+
lead_intro_line = f"You are `{lead_role}` for project `{ctx.get('PROJECT_ID', '')}`."
|
|
1882
|
+
lead_bootstrap_instruction = (
|
|
1883
|
+
"Read the manifests below for all task metadata, paths, model "
|
|
1884
|
+
"assignments, and worker roster. Use the tmux worker dispatch gate "
|
|
1885
|
+
"in this prompt; do not invoke Claude Code skills."
|
|
1886
|
+
)
|
|
1887
|
+
lead_session_block = (
|
|
1888
|
+
"## Session\n"
|
|
1889
|
+
"\n"
|
|
1890
|
+
"- Lead runtime: `external`\n"
|
|
1891
|
+
"- Lead session accounting: artifact-only; this harness does not expose Claude Code jsonl session IDs."
|
|
1892
|
+
)
|
|
1893
|
+
worker_dispatch_guidance = (
|
|
1894
|
+
"## Tmux Worker Dispatch\n"
|
|
1895
|
+
"\n"
|
|
1896
|
+
"- Worker prompt paths in `task-manifest.json` are assigned history "
|
|
1897
|
+
"locations. `okstra team dispatch` materializes missing prompt files "
|
|
1898
|
+
"from the run manifest and active-run-context; existing prompt files "
|
|
1899
|
+
"are never overwritten.\n"
|
|
1900
|
+
"- Do not call `TeamCreate`, `Agent(...)`, or `okstra codex-dispatch`. "
|
|
1901
|
+
"Use `okstra team dispatch` and `okstra team await` from the gate "
|
|
1902
|
+
"above for worker execution.\n"
|
|
1903
|
+
"- File presence is not a signal to skip. Worker selection and skipped "
|
|
1904
|
+
"statuses come from `team-state`, never from the prompts directory."
|
|
1905
|
+
)
|
|
1906
|
+
elif lead_runtime == "codex":
|
|
1907
|
+
lead_intro_line = f"You are `{lead_role}` for project `{ctx.get('PROJECT_ID', '')}`."
|
|
1908
|
+
lead_bootstrap_instruction = (
|
|
1909
|
+
"Read the manifests below for all task metadata, paths, model "
|
|
1910
|
+
"assignments, and worker roster. Use the Codex dispatch gate in this "
|
|
1911
|
+
"prompt; do not invoke Claude Code skills."
|
|
1912
|
+
)
|
|
1913
|
+
lead_session_block = (
|
|
1914
|
+
"## Session\n"
|
|
1915
|
+
"\n"
|
|
1916
|
+
"- Lead runtime: `codex`\n"
|
|
1917
|
+
"- Codex run resume is artifact/checkpoint based; `okstra codex-run` "
|
|
1918
|
+
"does not create a Claude session id."
|
|
1919
|
+
)
|
|
1920
|
+
worker_dispatch_guidance = (
|
|
1921
|
+
"## Codex Worker Dispatch\n"
|
|
1922
|
+
"\n"
|
|
1923
|
+
"- Worker prompt paths in `task-manifest.json` are assigned history "
|
|
1924
|
+
"locations. `okstra codex-dispatch` materializes missing prompt files "
|
|
1925
|
+
"from the run manifest and active-run-context; existing prompt files "
|
|
1926
|
+
"are never overwritten.\n"
|
|
1927
|
+
"- Do not call `TeamCreate` or `Agent(...)`. Use the command in the "
|
|
1928
|
+
"Codex Dispatch Gate above for worker execution.\n"
|
|
1929
|
+
"- File presence is not a signal to skip. Worker selection and skipped "
|
|
1930
|
+
"statuses come from `team-state`, never from the prompts directory."
|
|
1931
|
+
)
|
|
1932
|
+
else:
|
|
1933
|
+
lead_intro_line = f"You are `{lead_role}` for project `{ctx.get('PROJECT_ID', '')}`."
|
|
1934
|
+
lead_bootstrap_instruction = (
|
|
1935
|
+
"Invoke the `okstra` skill now. Read the manifests below for all task "
|
|
1936
|
+
"metadata, paths, model assignments, and worker roster."
|
|
1937
|
+
)
|
|
1938
|
+
lead_session_block = (
|
|
1939
|
+
"## Session\n"
|
|
1940
|
+
"\n"
|
|
1941
|
+
f"- Session ID: `{ctx.get('CLAUDE_SESSION_ID', '')}`\n"
|
|
1942
|
+
f"- Resume: `{ctx.get('CLAUDE_RESUME_COMMAND_RELATIVE_PATH', '')}`"
|
|
1943
|
+
)
|
|
1944
|
+
worker_dispatch_guidance = (
|
|
1945
|
+
"## Worker Prompt Files (not pre-rendered)\n"
|
|
1946
|
+
"\n"
|
|
1947
|
+
"- The `runs/<phase>/prompts/<worker>-worker-prompt-*.md` files referenced in `task-manifest.json → artifacts.workerPromptPathByWorkerId` are **NOT** rendered by the okstra runtime. Those paths are the *assigned* locations where the prompt history MUST be written at dispatch time.\n"
|
|
1948
|
+
"- Therefore: at the start of every phase the prompts/ directory is normally empty (or contains only previously-dispatched workers' files). This is expected. Do NOT narrate it as \"missing\", \"누락\", or \"not yet rendered\" — it just means dispatch has not happened yet.\n"
|
|
1949
|
+
"- Before dispatching any required worker, **you (the lead) construct the worker prompt and persist it to the assigned absolute path using `Write`** (per `okstra-team-contract` rule 6). Only after persisting do you call the worker subagent (Agent tool / Codex / Gemini wrapper). The wrapper subagents will also re-write the same file on their end; the double-write is intentional and idempotent.\n"
|
|
1950
|
+
"- Do not \"check if the file exists and skip dispatch\" — file presence is not a signal to skip. Worker selection and skipping rules come from team-state, never from prompts/ directory contents.\n"
|
|
1951
|
+
f"- **Worker Preamble Path (single anchor — replaces inlined `[Required reading]` and `[Error reporting]` blocks).** Every worker prompt you construct MUST include the anchor header `**Worker Preamble Path:** {ctx.get('WORKER_PROMPT_PREAMBLE_PATH', str(Path.home() / '.okstra' / 'templates' / 'worker-prompt-preamble.md'))}`. The file at that path is the canonical SSOT for Required Reading + Error Reporting + Output sections; workers Read it end-to-end before producing output. Do NOT re-inline those blocks into worker prompt bodies — that is the legacy ~80-line dispatch boilerplate that this anchor is designed to replace."
|
|
1952
|
+
)
|
|
1953
|
+
|
|
1776
1954
|
# Compute results (deterministic from ctx, 덮어쓰기)
|
|
1955
|
+
ctx["LEAD_INTRO_LINE"] = lead_intro_line
|
|
1956
|
+
ctx["LEAD_BOOTSTRAP_INSTRUCTION"] = lead_bootstrap_instruction
|
|
1957
|
+
ctx["LEAD_SESSION_BLOCK"] = lead_session_block
|
|
1777
1958
|
ctx["TEAM_CREATION_GATE"] = team_creation_gate_block
|
|
1959
|
+
ctx["WORKER_DISPATCH_GUIDANCE"] = worker_dispatch_guidance
|
|
1778
1960
|
ctx["WORKER_RESULT_PATH_LINES"] = "\n".join(worker_result_lines)
|
|
1779
1961
|
ctx["MODEL_ASSIGNMENT_LINES"] = "\n".join(model_assignment_lines)
|
|
1780
1962
|
ctx["TEAM_ROLE_LINES"] = "\n".join(team_role_lines)
|
|
@@ -39,6 +39,8 @@ from .material import (
|
|
|
39
39
|
resolve_related_tasks,
|
|
40
40
|
)
|
|
41
41
|
from .final_report_schema import load_schema
|
|
42
|
+
from .lead_events import LeadEvent, append_lead_event
|
|
43
|
+
from .lead_runtime import ALLOWED_LEAD_RUNTIMES
|
|
42
44
|
from .models import ModelAssignment, resolve_model_metadata
|
|
43
45
|
from .schema_excerpt import build_schema_excerpt
|
|
44
46
|
from .path_resolve import relative_to_project_root, resolve_user_file
|
|
@@ -80,7 +82,7 @@ from .workers import (
|
|
|
80
82
|
resolve_profile_workers,
|
|
81
83
|
validate_workers_against_profile,
|
|
82
84
|
)
|
|
83
|
-
from .workflow import compute_workflow_state
|
|
85
|
+
from .workflow import compute_workflow_state, load_phase_forbidden
|
|
84
86
|
from .locks import worktree_provision_mutex
|
|
85
87
|
from .worktree import (
|
|
86
88
|
WorktreeProvision,
|
|
@@ -266,6 +268,17 @@ class PrepareError(Exception):
|
|
|
266
268
|
"""surface to caller — task bundle prepare failed."""
|
|
267
269
|
|
|
268
270
|
|
|
271
|
+
_ALLOWED_LEAD_RUNTIMES = ALLOWED_LEAD_RUNTIMES
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _normalize_lead_runtime(value: str) -> str:
|
|
275
|
+
runtime = (value or "claude-code").strip()
|
|
276
|
+
if runtime not in _ALLOWED_LEAD_RUNTIMES:
|
|
277
|
+
allowed = ", ".join(_ALLOWED_LEAD_RUNTIMES)
|
|
278
|
+
raise PrepareError(f"unsupported lead runtime: {runtime} (allowed: {allowed})")
|
|
279
|
+
return runtime
|
|
280
|
+
|
|
281
|
+
|
|
269
282
|
@dataclass
|
|
270
283
|
class PrepareInputs:
|
|
271
284
|
workspace_root: Path
|
|
@@ -282,6 +295,7 @@ class PrepareInputs:
|
|
|
282
295
|
codex_model: str = ""
|
|
283
296
|
gemini_model: str = ""
|
|
284
297
|
report_writer_model: str = ""
|
|
298
|
+
lead_runtime: str = "claude-code"
|
|
285
299
|
executor: str = ""
|
|
286
300
|
critic: str = ""
|
|
287
301
|
related_tasks_raw: str = ""
|
|
@@ -932,6 +946,29 @@ def _brief_sha256(path: Path) -> str:
|
|
|
932
946
|
return ""
|
|
933
947
|
|
|
934
948
|
|
|
949
|
+
def _record_artifact_runtime_render_only_event(inp: PrepareInputs, ctx: dict) -> None:
|
|
950
|
+
if inp.lead_runtime not in ("codex", "external") or not inp.render_only:
|
|
951
|
+
return
|
|
952
|
+
append_lead_event(
|
|
953
|
+
Path(ctx["LEAD_EVENTS_PATH"]),
|
|
954
|
+
LeadEvent(
|
|
955
|
+
event_type="bundle-prepared",
|
|
956
|
+
lead_runtime=inp.lead_runtime,
|
|
957
|
+
task_key=ctx["TASK_KEY"],
|
|
958
|
+
task_type=ctx["TASK_TYPE"],
|
|
959
|
+
run_seq=ctx["RUN_MANIFESTS_SEQ"],
|
|
960
|
+
timestamp=ctx["RUN_TIMESTAMP_ISO"],
|
|
961
|
+
details={
|
|
962
|
+
"dispatchMode": "render-only",
|
|
963
|
+
"workerDispatch": "not-started",
|
|
964
|
+
"runManifestPath": ctx.get("RUN_MANIFEST_RELATIVE_PATH", ""),
|
|
965
|
+
"teamStatePath": ctx.get("TEAM_STATE_RELATIVE_PATH", ""),
|
|
966
|
+
"promptSnapshotPath": ctx.get("RUN_PROMPT_SNAPSHOT_RELATIVE_PATH", ""),
|
|
967
|
+
},
|
|
968
|
+
),
|
|
969
|
+
)
|
|
970
|
+
|
|
971
|
+
|
|
935
972
|
def _canonical_argv(inp: PrepareInputs, ctx: dict) -> list[str]:
|
|
936
973
|
"""rerun 충실 재현을 위한 canonical argv 재구성."""
|
|
937
974
|
workers = inp.workers_override or ctx.get("RECOMMENDED_ANALYSERS", "")
|
|
@@ -951,6 +988,7 @@ def _canonical_argv(inp: PrepareInputs, ctx: dict) -> list[str]:
|
|
|
951
988
|
("--codex-model", inp.codex_model or ctx.get("CODEX_WORKER_MODEL", "")),
|
|
952
989
|
("--gemini-model", inp.gemini_model or ctx.get("GEMINI_WORKER_MODEL", "")),
|
|
953
990
|
("--report-writer-model", inp.report_writer_model or ctx.get("REPORT_WRITER_MODEL", "")),
|
|
991
|
+
("--lead-runtime", inp.lead_runtime if inp.lead_runtime != "claude-code" else ""),
|
|
954
992
|
("--executor", inp.executor or ctx.get("EXECUTOR_PROVIDER", "")),
|
|
955
993
|
("--critic", inp.critic or ctx.get("CRITIC_CHOICE", "")),
|
|
956
994
|
("--related-tasks", inp.related_tasks_raw),
|
|
@@ -1770,6 +1808,7 @@ def _write_instruction_set_sources(
|
|
|
1770
1808
|
"EXECUTOR_WORKTREE_BASE_REF",
|
|
1771
1809
|
"EXECUTOR_WORKTREE_STATUS",
|
|
1772
1810
|
"EXECUTOR_WORKTREE_NOTE",
|
|
1811
|
+
"PHASE_FORBIDDEN_ACTIONS",
|
|
1773
1812
|
):
|
|
1774
1813
|
profile_rendered = profile_rendered.replace("{{" + key + "}}", ctx.get(key, ""))
|
|
1775
1814
|
(instruction_set / "analysis-profile.md").write_text(profile_rendered, encoding="utf-8")
|
|
@@ -1960,7 +1999,8 @@ def _record_fix_cycle_events(inp: PrepareInputs, ctx: dict) -> str:
|
|
|
1960
1999
|
|
|
1961
2000
|
|
|
1962
2001
|
def _finalize_status_and_render_manifests(
|
|
1963
|
-
inp: PrepareInputs, ctx: dict, task_index_template: Path
|
|
2002
|
+
inp: PrepareInputs, ctx: dict, task_index_template: Path,
|
|
2003
|
+
forbidden_by_phase: dict[str, str],
|
|
1964
2004
|
) -> None:
|
|
1965
2005
|
"""최종 task/run status 를 확정하고 workflow state 를 재계산한 뒤 team-state,
|
|
1966
2006
|
task-manifest, task-index, run-manifest, timeline, discovery 산출물을 렌더한다."""
|
|
@@ -1977,6 +2017,7 @@ def _finalize_status_and_render_manifests(
|
|
|
1977
2017
|
current_run_status=ctx["CURRENT_RUN_STATUS"],
|
|
1978
2018
|
current_task_status=ctx["CURRENT_TASK_STATUS"],
|
|
1979
2019
|
render_only=inp.render_only,
|
|
2020
|
+
forbidden_by_phase=forbidden_by_phase,
|
|
1980
2021
|
work_category=inp.work_category,
|
|
1981
2022
|
))
|
|
1982
2023
|
render_team_state(ctx["TEAM_STATE_PATH"], ctx)
|
|
@@ -2013,6 +2054,12 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
2013
2054
|
"""Produce a complete okstra task bundle on disk. See module docstring."""
|
|
2014
2055
|
workspace_root = Path(inp.workspace_root)
|
|
2015
2056
|
project_root = Path(inp.project_root)
|
|
2057
|
+
lead_runtime = _normalize_lead_runtime(inp.lead_runtime)
|
|
2058
|
+
if lead_runtime != "claude-code" and not inp.render_only:
|
|
2059
|
+
raise PrepareError(
|
|
2060
|
+
f"lead runtime `{lead_runtime}` is currently render-only; "
|
|
2061
|
+
"use --render-only until a dispatch backend is enabled for this lead runtime."
|
|
2062
|
+
)
|
|
2016
2063
|
|
|
2017
2064
|
# ---- input validation + asset resolution + roster/model 해소 ----
|
|
2018
2065
|
# 각 단계는 명시적 입력/반환을 갖는 헬퍼로 분리되어 있다 (상단 phase 헬퍼
|
|
@@ -2186,17 +2233,20 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
2186
2233
|
# ---- initial workflow state (current_run_status not yet known) ----
|
|
2187
2234
|
initial_task_status = "ready-for-claude"
|
|
2188
2235
|
initial_run_status = "not-run"
|
|
2236
|
+
forbidden_by_phase = load_phase_forbidden(workspace_root)
|
|
2189
2237
|
workflow_state = compute_workflow_state(
|
|
2190
2238
|
task_type=inp.task_type,
|
|
2191
2239
|
current_run_status=initial_run_status,
|
|
2192
2240
|
current_task_status=initial_task_status,
|
|
2193
2241
|
render_only=inp.render_only,
|
|
2242
|
+
forbidden_by_phase=forbidden_by_phase,
|
|
2194
2243
|
)
|
|
2195
2244
|
|
|
2196
2245
|
# ---- assemble full ctx (the values render functions expect) ----
|
|
2197
2246
|
ctx.update({
|
|
2198
2247
|
"ANALYSIS_PROFILE": inp.task_type,
|
|
2199
2248
|
"RECOMMENDED_ANALYSERS": selected_reviewers,
|
|
2249
|
+
"LEAD_RUNTIME": lead_runtime,
|
|
2200
2250
|
"PR_TEMPLATE_PATH": pr_template_path_str,
|
|
2201
2251
|
"PR_TEMPLATE_SOURCE": pr_template_source,
|
|
2202
2252
|
**handoff_tokens,
|
|
@@ -2235,6 +2285,10 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
2235
2285
|
"OKSTRA_VERSION": installed_version(),
|
|
2236
2286
|
**workflow_state,
|
|
2237
2287
|
})
|
|
2288
|
+
if lead_runtime == "codex":
|
|
2289
|
+
ctx["CLAUDE_RESUME_COMMAND_PATH"] = ""
|
|
2290
|
+
ctx["CLAUDE_RESUME_COMMAND_RELATIVE_PATH"] = ""
|
|
2291
|
+
ctx["CLAUDE_RESUME_COMMAND_FILENAME"] = ""
|
|
2238
2292
|
if inp.task_type == "final-verification":
|
|
2239
2293
|
_reserve_final_verification_target(inp, ctx, ctx_stage_map)
|
|
2240
2294
|
|
|
@@ -2244,21 +2298,23 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
2244
2298
|
cleanup_obsolete_generated_docs(
|
|
2245
2299
|
project_root=project_root, instruction_set_dir=Path(ctx["INSTRUCTION_SET_PATH"]),
|
|
2246
2300
|
)
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2301
|
+
if lead_runtime == "claude-code":
|
|
2302
|
+
# Always materialise the resume command script for Claude Code. Even in
|
|
2303
|
+
# --render-only preparation flows the user (or a later non-interactive
|
|
2304
|
+
# runner) may invoke it manually; deferring its creation until
|
|
2305
|
+
# interactive launch leaves runs/<phase>/sessions/ empty and the
|
|
2306
|
+
# manifest pointing at a path that does not exist. Codex runs resume via
|
|
2307
|
+
# artifacts/checkpoints instead, so they intentionally leave this empty.
|
|
2308
|
+
write_claude_resume_command_file(
|
|
2309
|
+
resume_command_path=Path(ctx["CLAUDE_RESUME_COMMAND_PATH"]),
|
|
2310
|
+
project_root=project_root,
|
|
2311
|
+
claude_session_id=claude_session_id,
|
|
2312
|
+
task_key=ctx["TASK_KEY"],
|
|
2313
|
+
task_type=ctx["TASK_TYPE"],
|
|
2314
|
+
phase_state=ctx["CURRENT_RUN_STATUS"],
|
|
2315
|
+
worker_prompts_dir_relative=ctx["RUN_PROMPTS_RELATIVE_PATH"],
|
|
2316
|
+
prompt_seq=ctx["RUN_PROMPTS_SEQ"],
|
|
2317
|
+
)
|
|
2262
2318
|
|
|
2263
2319
|
# ---- fix-cycle 기록 (manifest 재작성 전 디스크 값으로 lazy-close 판정) ----
|
|
2264
2320
|
# instruction-set 빌드보다 *앞*에서 호출해 이번 run 에서 새로 open 되는
|
|
@@ -2279,7 +2335,10 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
2279
2335
|
_persist_run_inputs(inp, ctx, models, selected_reviewers, brief_relative)
|
|
2280
2336
|
|
|
2281
2337
|
# ---- final status + manifest/discovery renders ----
|
|
2282
|
-
_finalize_status_and_render_manifests(
|
|
2338
|
+
_finalize_status_and_render_manifests(
|
|
2339
|
+
inp, ctx, task_index_template, forbidden_by_phase
|
|
2340
|
+
)
|
|
2341
|
+
_record_artifact_runtime_render_only_event(inp, ctx)
|
|
2283
2342
|
|
|
2284
2343
|
# ---- central index ----
|
|
2285
2344
|
initial_status = "prepared" if inp.render_only else "running"
|
|
@@ -2354,6 +2413,17 @@ def main(argv: list[str]) -> int:
|
|
|
2354
2413
|
p.add_argument("--codex-model", default="")
|
|
2355
2414
|
p.add_argument("--gemini-model", default="")
|
|
2356
2415
|
p.add_argument("--report-writer-model", default="")
|
|
2416
|
+
p.add_argument(
|
|
2417
|
+
"--lead-runtime",
|
|
2418
|
+
default="claude-code",
|
|
2419
|
+
choices=list(_ALLOWED_LEAD_RUNTIMES),
|
|
2420
|
+
dest="lead_runtime",
|
|
2421
|
+
help=(
|
|
2422
|
+
"Lead runtime adapter. Default: claude-code. `codex` renders "
|
|
2423
|
+
"Codex CLI artifact bundles; `external` renders neutral bootstrap "
|
|
2424
|
+
"bundles for manifest inspection until a dispatch backend is enabled."
|
|
2425
|
+
),
|
|
2426
|
+
)
|
|
2357
2427
|
p.add_argument("--executor", default="")
|
|
2358
2428
|
p.add_argument("--critic", default="")
|
|
2359
2429
|
p.add_argument("--related-tasks", default="", dest="related_tasks_raw")
|
|
@@ -2490,6 +2560,7 @@ def main(argv: list[str]) -> int:
|
|
|
2490
2560
|
codex_model=args.codex_model,
|
|
2491
2561
|
gemini_model=args.gemini_model,
|
|
2492
2562
|
report_writer_model=args.report_writer_model,
|
|
2563
|
+
lead_runtime=args.lead_runtime,
|
|
2493
2564
|
executor=args.executor,
|
|
2494
2565
|
critic=args.critic,
|
|
2495
2566
|
related_tasks_raw=args.related_tasks_raw,
|