okstra 0.34.0 → 0.36.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 +26 -16
- package/README.md +26 -16
- package/docs/kr/architecture.md +59 -45
- package/docs/kr/cli.md +61 -18
- package/docs/pr-template-usage.md +65 -0
- package/docs/project-structure-overview.md +358 -354
- package/docs/superpowers/plans/2026-05-12-ticket-id-in-reports.md +1 -1
- package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +1 -1
- package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +1 -1
- package/docs/superpowers/plans/2026-05-20-final-report-language.md +1501 -0
- package/docs/superpowers/plans/2026-05-20-implementation-planning-multi-stage.md +1267 -0
- package/docs/superpowers/plans/2026-05-20-okstra-run-prompt-sot-b1.md +1007 -0
- package/docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md +720 -0
- package/docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md +681 -0
- package/docs/superpowers/plans/2026-05-21-improvement-discovery-task-type.md +1691 -0
- package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +383 -0
- package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +320 -0
- package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +299 -0
- package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +335 -0
- package/docs/task-process/README.md +74 -0
- package/docs/task-process/common-flow.md +166 -0
- package/docs/task-process/error-analysis.md +101 -0
- package/docs/task-process/final-verification.md +167 -0
- package/docs/task-process/implementation-planning.md +128 -0
- package/docs/task-process/implementation.md +149 -0
- package/docs/task-process/release-handoff.md +206 -0
- package/docs/task-process/requirements-discovery.md +115 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/SKILL.md +29 -13
- package/runtime/agents/workers/claude-worker.md +26 -0
- package/runtime/agents/workers/codex-worker.md +27 -1
- package/runtime/agents/workers/gemini-worker.md +27 -1
- package/runtime/agents/workers/report-writer-worker.md +8 -1
- package/runtime/bin/okstra-central.sh +6 -6
- package/runtime/bin/okstra-codex-exec.sh +49 -28
- package/runtime/bin/okstra-gemini-exec.sh +39 -21
- package/runtime/bin/okstra-render-final-report.py +13 -2
- package/runtime/bin/okstra-wrapper-status.py +155 -0
- package/runtime/bin/okstra.sh +2 -2
- package/runtime/prompts/profiles/_common-contract.md +11 -6
- package/runtime/prompts/profiles/error-analysis.md +3 -7
- package/runtime/prompts/profiles/implementation-planning.md +22 -21
- package/runtime/prompts/profiles/implementation.md +28 -11
- package/runtime/prompts/profiles/improvement-discovery.md +42 -0
- package/runtime/prompts/profiles/kr/_common-contract.md +92 -0
- package/runtime/prompts/profiles/kr/error-analysis.md +36 -0
- package/runtime/prompts/profiles/kr/final-verification.md +48 -0
- package/runtime/prompts/profiles/kr/implementation-planning.md +90 -0
- package/runtime/prompts/profiles/kr/implementation.md +144 -0
- package/runtime/prompts/profiles/kr/improvement-discovery.md +42 -0
- package/runtime/prompts/profiles/kr/release-handoff.md +104 -0
- package/runtime/prompts/profiles/kr/requirements-discovery.md +42 -0
- package/runtime/prompts/profiles/release-handoff.md +1 -1
- package/runtime/prompts/profiles/requirements-discovery.md +8 -12
- package/runtime/prompts/wizard/prompts.ko.json +230 -0
- package/runtime/python/lib/okstra/cli.sh +2 -49
- package/runtime/python/lib/okstra/globals.sh +21 -21
- package/runtime/python/lib/okstra/interactive.sh +7 -7
- package/runtime/python/okstra_ctl/clarification_items.py +3 -9
- package/runtime/python/okstra_ctl/consumers.py +53 -0
- package/runtime/python/okstra_ctl/final_report_schema.py +0 -7
- package/runtime/python/okstra_ctl/i18n.py +73 -0
- package/runtime/python/okstra_ctl/improvement_lenses.py +44 -0
- package/runtime/python/okstra_ctl/index.py +1 -1
- package/runtime/python/okstra_ctl/paths.py +23 -20
- package/runtime/python/okstra_ctl/render.py +147 -202
- package/runtime/python/okstra_ctl/render_final_report.py +53 -10
- package/runtime/python/okstra_ctl/run.py +292 -107
- package/runtime/python/okstra_ctl/run_context.py +22 -0
- package/runtime/python/okstra_ctl/seeding.py +186 -0
- package/runtime/python/okstra_ctl/wizard.py +348 -127
- package/runtime/python/okstra_ctl/workflow.py +21 -2
- package/runtime/python/okstra_ctl/worktree.py +54 -1
- package/runtime/python/okstra_project/resolver.py +4 -3
- package/runtime/python/okstra_token_usage/report.py +2 -2
- package/runtime/schemas/final-report-v1.0.schema.json +22 -16
- package/runtime/skills/okstra-brief/SKILL.md +124 -31
- package/runtime/skills/okstra-convergence/SKILL.md +2 -3
- package/runtime/skills/okstra-report-writer/SKILL.md +35 -15
- package/runtime/skills/okstra-run/SKILL.md +5 -4
- package/runtime/skills/okstra-schedule/SKILL.md +4 -4
- package/runtime/skills/okstra-setup/SKILL.md +27 -0
- package/runtime/skills/okstra-team-contract/SKILL.md +1 -1
- package/runtime/templates/okstra.CLAUDE.md +104 -0
- package/runtime/templates/reports/final-report.template.md +93 -98
- package/runtime/templates/reports/i18n/en.json +135 -0
- package/runtime/templates/reports/i18n/ko.json +135 -0
- package/runtime/templates/reports/implementation-planning-input.template.md +18 -0
- package/runtime/templates/reports/improvement-discovery-input.template.md +78 -0
- package/runtime/templates/reports/task-brief.template.md +2 -2
- package/runtime/validators/lib/fixtures.sh +30 -0
- package/runtime/validators/lib/runners.sh +1 -1
- package/runtime/validators/validate-implementation-plan-stages.py +211 -0
- package/runtime/validators/validate-run.py +121 -26
- package/runtime/validators/validate-workflow.sh +2 -2
- package/runtime/validators/validate_improvement_report.py +275 -0
- package/src/config.mjs +18 -0
- package/src/install.mjs +41 -14
- package/src/setup.mjs +133 -1
- package/src/uninstall.mjs +21 -1
|
@@ -22,6 +22,16 @@ import re
|
|
|
22
22
|
import sys
|
|
23
23
|
from pathlib import Path
|
|
24
24
|
|
|
25
|
+
|
|
26
|
+
class TokenRenderError(Exception):
|
|
27
|
+
"""Raised when a template references a `{{TOKEN}}` not present in ctx.
|
|
28
|
+
|
|
29
|
+
Specific to the pure-token renderer in this module. Distinct from
|
|
30
|
+
`okstra_ctl.render_final_report.FinalReportRenderError` which wraps
|
|
31
|
+
jinja2 / IO failures during final-report rendering.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
|
|
25
35
|
# --------------------------------------------------------------------------- #
|
|
26
36
|
# helpers
|
|
27
37
|
# --------------------------------------------------------------------------- #
|
|
@@ -152,9 +162,7 @@ def _frontmatter_mapping(ctx: dict) -> dict:
|
|
|
152
162
|
task_key = (ctx.get("TASK_KEY") or "").strip()
|
|
153
163
|
task_date = (ctx.get("TASK_DATE") or "").strip()
|
|
154
164
|
doc_type = (ctx.get("DOC_TYPE") or "").strip()
|
|
155
|
-
|
|
156
|
-
# `ANALYSIS_TYPE` fallback (workflow 의 render mapping 과 동일 우선순위).
|
|
157
|
-
task_type = (ctx.get("TASK_TYPE") or ctx.get("ANALYSIS_TYPE") or "").strip()
|
|
165
|
+
task_type = (ctx.get("TASK_TYPE") or "").strip()
|
|
158
166
|
|
|
159
167
|
fm_id = _frontmatter_id_from_task_key(task_key)
|
|
160
168
|
fm_id_scalar = f'"{fm_id}"' if fm_id else f'"{_FM_DEFAULT}"'
|
|
@@ -179,7 +187,7 @@ def _frontmatter_mapping(ctx: dict) -> dict:
|
|
|
179
187
|
|
|
180
188
|
def _resolve_workers(ctx: dict) -> list[str]:
|
|
181
189
|
return [
|
|
182
|
-
w.strip() for w in ctx.get("
|
|
190
|
+
w.strip() for w in ctx.get("RECOMMENDED_ANALYSERS", "").split(",") if w.strip()
|
|
183
191
|
]
|
|
184
192
|
|
|
185
193
|
|
|
@@ -190,7 +198,7 @@ def _worker_catalog(ctx: dict) -> dict:
|
|
|
190
198
|
"role": "Claude worker",
|
|
191
199
|
"agent": "claude",
|
|
192
200
|
"agentLabel": "Claude Code",
|
|
193
|
-
"model": ctx.get("
|
|
201
|
+
"model": ctx.get("CLAUDE_WORKER_MODEL", ""),
|
|
194
202
|
"modelExecutionValue": ctx.get("CLAUDE_WORKER_MODEL_EXECUTION_VALUE", ""),
|
|
195
203
|
"resultPath": ctx.get("CLAUDE_WORKER_RESULT_RELATIVE_PATH", ""),
|
|
196
204
|
"promptPath": ctx.get("CLAUDE_WORKER_PROMPT_RELATIVE_PATH", ""),
|
|
@@ -200,7 +208,7 @@ def _worker_catalog(ctx: dict) -> dict:
|
|
|
200
208
|
"role": "Codex worker",
|
|
201
209
|
"agent": "codex",
|
|
202
210
|
"agentLabel": "Codex",
|
|
203
|
-
"model": ctx.get("
|
|
211
|
+
"model": ctx.get("CODEX_WORKER_MODEL", ""),
|
|
204
212
|
"modelExecutionValue": ctx.get("CODEX_WORKER_MODEL_EXECUTION_VALUE", ""),
|
|
205
213
|
"resultPath": ctx.get("CODEX_WORKER_RESULT_RELATIVE_PATH", ""),
|
|
206
214
|
"promptPath": ctx.get("CODEX_WORKER_PROMPT_RELATIVE_PATH", ""),
|
|
@@ -210,7 +218,7 @@ def _worker_catalog(ctx: dict) -> dict:
|
|
|
210
218
|
"role": "Gemini worker",
|
|
211
219
|
"agent": "gemini",
|
|
212
220
|
"agentLabel": "Gemini",
|
|
213
|
-
"model": ctx.get("
|
|
221
|
+
"model": ctx.get("GEMINI_WORKER_MODEL", ""),
|
|
214
222
|
"modelExecutionValue": ctx.get("GEMINI_WORKER_MODEL_EXECUTION_VALUE", ""),
|
|
215
223
|
"resultPath": ctx.get("GEMINI_WORKER_RESULT_RELATIVE_PATH", ""),
|
|
216
224
|
"promptPath": ctx.get("GEMINI_WORKER_PROMPT_RELATIVE_PATH", ""),
|
|
@@ -220,7 +228,7 @@ def _worker_catalog(ctx: dict) -> dict:
|
|
|
220
228
|
"role": "Report writer worker",
|
|
221
229
|
"agent": "claude",
|
|
222
230
|
"agentLabel": "Claude Code",
|
|
223
|
-
"model": ctx.get("
|
|
231
|
+
"model": ctx.get("REPORT_WRITER_MODEL", ""),
|
|
224
232
|
"modelExecutionValue": ctx.get("REPORT_WRITER_MODEL_EXECUTION_VALUE", ""),
|
|
225
233
|
"resultPath": ctx.get("REPORT_WRITER_WORKER_RESULT_RELATIVE_PATH", ""),
|
|
226
234
|
"promptPath": ctx.get("REPORT_WRITER_WORKER_PROMPT_RELATIVE_PATH", ""),
|
|
@@ -255,13 +263,13 @@ def render_team_state(team_state_path: str, ctx: dict) -> None:
|
|
|
255
263
|
payload = {
|
|
256
264
|
"schemaVersion": "1.0",
|
|
257
265
|
"taskKey": ctx.get("TASK_KEY", ""),
|
|
258
|
-
"taskType": ctx.get("
|
|
266
|
+
"taskType": ctx.get("TASK_TYPE", ""),
|
|
259
267
|
"runDirectoryPath": ctx.get("RUN_DIR_RELATIVE_PATH", ""),
|
|
260
268
|
"workflowState": ctx.get("CURRENT_RUN_STATUS", ""),
|
|
261
269
|
"lead": {
|
|
262
270
|
"role": "Claude lead",
|
|
263
271
|
"agent": "claude",
|
|
264
|
-
"model": ctx.get("
|
|
272
|
+
"model": ctx.get("LEAD_MODEL", ""),
|
|
265
273
|
"modelExecutionValue": ctx.get("LEAD_MODEL_EXECUTION_VALUE", ""),
|
|
266
274
|
"status": ctx.get("CURRENT_RUN_STATUS", ""),
|
|
267
275
|
"sessionId": ctx.get("CLAUDE_SESSION_ID", ""),
|
|
@@ -311,7 +319,7 @@ def render_reference_expectations(brief_path: str, output_path: str, ctx: dict)
|
|
|
311
319
|
"# Task Reference Expectations",
|
|
312
320
|
"",
|
|
313
321
|
f"- Task Key: `{ctx.get('TASK_KEY', '')}`",
|
|
314
|
-
f"- Task Type: `{ctx.get('
|
|
322
|
+
f"- Task Type: `{ctx.get('TASK_TYPE', '')}`",
|
|
315
323
|
f"- Source brief snapshot: `{brief_relative}`",
|
|
316
324
|
"",
|
|
317
325
|
"## Usage Rules",
|
|
@@ -457,7 +465,7 @@ def render_task_catalog_discovery(output_path: str, ctx: dict) -> None:
|
|
|
457
465
|
|
|
458
466
|
|
|
459
467
|
def render_latest_task_discovery(output_path: str, ctx: dict) -> None:
|
|
460
|
-
task_manifest_path = Path(ctx.get("
|
|
468
|
+
task_manifest_path = Path(ctx.get("TASK_MANIFEST_PATH", ""))
|
|
461
469
|
task_manifest = {}
|
|
462
470
|
if task_manifest_path.exists():
|
|
463
471
|
try:
|
|
@@ -473,7 +481,7 @@ def render_latest_task_discovery(output_path: str, ctx: dict) -> None:
|
|
|
473
481
|
"schemaVersion": "1.0",
|
|
474
482
|
"updatedAt": ctx.get("RUN_TIMESTAMP_ISO", ""),
|
|
475
483
|
"taskKey": ctx.get("TASK_KEY", ""),
|
|
476
|
-
"taskType": ctx.get("
|
|
484
|
+
"taskType": ctx.get("TASK_TYPE", ""),
|
|
477
485
|
"workCategory": task_manifest.get(
|
|
478
486
|
"workCategory", ctx.get("WORKFLOW_WORK_CATEGORY", "unknown")
|
|
479
487
|
),
|
|
@@ -633,7 +641,7 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
|
|
|
633
641
|
if isinstance(workflow.get("phaseStates"), dict)
|
|
634
642
|
else {}
|
|
635
643
|
)
|
|
636
|
-
current_phase = ctx.get("WORKFLOW_CURRENT_PHASE", ctx.get("
|
|
644
|
+
current_phase = ctx.get("WORKFLOW_CURRENT_PHASE", ctx.get("TASK_TYPE", ""))
|
|
637
645
|
current_phase_state = ctx.get("WORKFLOW_CURRENT_PHASE_STATE", "not-started")
|
|
638
646
|
for phase in phase_sequence:
|
|
639
647
|
phase_states.setdefault(phase, "not-started")
|
|
@@ -746,7 +754,7 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
|
|
|
746
754
|
"taskGroupPathSegment": ctx.get("TASK_GROUP_SEGMENT", ""),
|
|
747
755
|
"taskIdPathSegment": ctx.get("TASK_ID_SEGMENT", ""),
|
|
748
756
|
"projectRoot": ctx.get("PROJECT_ROOT", ""),
|
|
749
|
-
"taskType": ctx.get("
|
|
757
|
+
"taskType": ctx.get("TASK_TYPE", ""),
|
|
750
758
|
"workCategory": work_category,
|
|
751
759
|
"taskBriefPath": ctx.get("BRIEF_RELATIVE_PATH", ""),
|
|
752
760
|
"recommendedWorkers": reviewers,
|
|
@@ -807,7 +815,7 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
|
|
|
807
815
|
"resultContract": {
|
|
808
816
|
"leadAgent": "claude",
|
|
809
817
|
"leadRole": "Claude lead",
|
|
810
|
-
"leadModel": ctx.get("
|
|
818
|
+
"leadModel": ctx.get("LEAD_MODEL", ""),
|
|
811
819
|
"leadModelExecutionValue": ctx.get("LEAD_MODEL_EXECUTION_VALUE", ""),
|
|
812
820
|
"leadExecutionMode": "synthesis-only",
|
|
813
821
|
"finalSynthesisOwner": "Claude lead",
|
|
@@ -890,7 +898,7 @@ def _build_convergence_block(ctx: dict) -> dict:
|
|
|
890
898
|
- `OKSTRA_PLAN_VERIFICATION`: "true" | "false" | "" (empty → default True).
|
|
891
899
|
Wired from CLI `--no-plan-verification` (sets "false").
|
|
892
900
|
"""
|
|
893
|
-
task_type = ctx.get("
|
|
901
|
+
task_type = ctx.get("TASK_TYPE", "")
|
|
894
902
|
default_max_rounds = 1 if task_type == "requirements-discovery" else 2
|
|
895
903
|
raw_plan_verify = (ctx.get("OKSTRA_PLAN_VERIFICATION", "") or "").strip().lower()
|
|
896
904
|
plan_verify_enabled = raw_plan_verify != "false"
|
|
@@ -907,7 +915,7 @@ def _build_convergence_block(ctx: dict) -> dict:
|
|
|
907
915
|
|
|
908
916
|
|
|
909
917
|
def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
|
|
910
|
-
task_manifest_path = Path(ctx.get("
|
|
918
|
+
task_manifest_path = Path(ctx.get("TASK_MANIFEST_PATH", ""))
|
|
911
919
|
task_manifest = {}
|
|
912
920
|
if task_manifest_path.exists():
|
|
913
921
|
try:
|
|
@@ -931,7 +939,7 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
|
|
|
931
939
|
"taskGroup": ctx.get("TASK_GROUP", ""),
|
|
932
940
|
"taskId": ctx.get("TASK_ID", ""),
|
|
933
941
|
"taskKey": ctx.get("TASK_KEY", ""),
|
|
934
|
-
"taskType": ctx.get("
|
|
942
|
+
"taskType": ctx.get("TASK_TYPE", ""),
|
|
935
943
|
"workCategory": task_manifest.get(
|
|
936
944
|
"workCategory", ctx.get("WORKFLOW_WORK_CATEGORY", "unknown")
|
|
937
945
|
),
|
|
@@ -995,7 +1003,7 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
|
|
|
995
1003
|
"teamContract": {
|
|
996
1004
|
"leadAgent": "claude",
|
|
997
1005
|
"leadRole": "Claude lead",
|
|
998
|
-
"leadModel": ctx.get("
|
|
1006
|
+
"leadModel": ctx.get("LEAD_MODEL", ""),
|
|
999
1007
|
"leadModelExecutionValue": ctx.get("LEAD_MODEL_EXECUTION_VALUE", ""),
|
|
1000
1008
|
"leadExecutionMode": "synthesis-only",
|
|
1001
1009
|
"finalSynthesisOwner": "Claude lead",
|
|
@@ -1034,7 +1042,7 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
|
|
|
1034
1042
|
|
|
1035
1043
|
|
|
1036
1044
|
def render_timeline(timeline_path: str, ctx: dict) -> None:
|
|
1037
|
-
task_manifest_path = Path(ctx.get("
|
|
1045
|
+
task_manifest_path = Path(ctx.get("TASK_MANIFEST_PATH", ""))
|
|
1038
1046
|
task_manifest = {}
|
|
1039
1047
|
if task_manifest_path.exists():
|
|
1040
1048
|
try:
|
|
@@ -1056,7 +1064,7 @@ def render_timeline(timeline_path: str, ctx: dict) -> None:
|
|
|
1056
1064
|
except Exception:
|
|
1057
1065
|
existing = {}
|
|
1058
1066
|
runs = existing.get("runs", [])
|
|
1059
|
-
current_run_manifest_path = ctx.get("
|
|
1067
|
+
current_run_manifest_path = ctx.get("RUN_MANIFEST_PATH", "")
|
|
1060
1068
|
current_run_manifest_relative_path = ctx.get("RUN_MANIFEST_RELATIVE_PATH", "")
|
|
1061
1069
|
filtered = [
|
|
1062
1070
|
item
|
|
@@ -1085,7 +1093,7 @@ def render_timeline(timeline_path: str, ctx: dict) -> None:
|
|
|
1085
1093
|
"sessions": ctx.get("RUN_SESSIONS_SEQ", ""),
|
|
1086
1094
|
"workerResults": ctx.get("WORKER_RESULTS_SEQ", ""),
|
|
1087
1095
|
},
|
|
1088
|
-
"taskType": ctx.get("
|
|
1096
|
+
"taskType": ctx.get("TASK_TYPE", ""),
|
|
1089
1097
|
"workCategory": task_manifest.get(
|
|
1090
1098
|
"workCategory", ctx.get("WORKFLOW_WORK_CATEGORY", "unknown")
|
|
1091
1099
|
),
|
|
@@ -1127,7 +1135,7 @@ def render_timeline(timeline_path: str, ctx: dict) -> None:
|
|
|
1127
1135
|
|
|
1128
1136
|
def render_task_index(template_path: str, output_path: str, ctx: dict) -> None:
|
|
1129
1137
|
template = Path(template_path).read_text(encoding="utf-8")
|
|
1130
|
-
task_manifest_path = Path(ctx["
|
|
1138
|
+
task_manifest_path = Path(ctx["TASK_MANIFEST_PATH"])
|
|
1131
1139
|
task_manifest = {}
|
|
1132
1140
|
if task_manifest_path.exists():
|
|
1133
1141
|
try:
|
|
@@ -1187,7 +1195,7 @@ def render_task_index(template_path: str, output_path: str, ctx: dict) -> None:
|
|
|
1187
1195
|
)
|
|
1188
1196
|
mapping = {
|
|
1189
1197
|
"{{TASK_KEY}}": task_manifest.get("taskKey", ctx.get("TASK_KEY", "")),
|
|
1190
|
-
"{{TASK_TYPE}}": task_manifest.get("taskType", ctx.get("
|
|
1198
|
+
"{{TASK_TYPE}}": task_manifest.get("taskType", ctx.get("TASK_TYPE", "")),
|
|
1191
1199
|
"{{TASK_DATE}}": ctx.get("TASK_DATE", ""),
|
|
1192
1200
|
"{{PROJECT_ID}}": ctx.get("PROJECT_ID", ""),
|
|
1193
1201
|
"{{TASK_GROUP}}": ctx.get("TASK_GROUP", ""),
|
|
@@ -1202,7 +1210,7 @@ def render_task_index(template_path: str, output_path: str, ctx: dict) -> None:
|
|
|
1202
1210
|
"{{RECOMMENDED_ANALYSERS}}": ", ".join(
|
|
1203
1211
|
task_manifest.get("recommendedWorkers", [])
|
|
1204
1212
|
),
|
|
1205
|
-
"{{LEAD_MODEL}}": rc.get("leadModel", ctx.get("
|
|
1213
|
+
"{{LEAD_MODEL}}": rc.get("leadModel", ctx.get("LEAD_MODEL", "")),
|
|
1206
1214
|
"{{OKSTRA_VERSION}}": ctx.get("OKSTRA_VERSION", ""),
|
|
1207
1215
|
"{{LATEST_RUN_RELATIVE_PATH}}": task_manifest.get(
|
|
1208
1216
|
"latestRunPath", ctx.get("LATEST_RUN_RELATIVE_PATH", "")
|
|
@@ -1222,11 +1230,11 @@ def render_task_index(template_path: str, output_path: str, ctx: dict) -> None:
|
|
|
1222
1230
|
),
|
|
1223
1231
|
"{{MODEL_ASSIGNMENT_LINES}}": "\n".join(
|
|
1224
1232
|
[
|
|
1225
|
-
f"- `Claude lead`: `{rc.get('leadModel', ctx.get('
|
|
1226
|
-
f"- `Claude worker`: `{ctx.get('
|
|
1227
|
-
f"- `Codex worker`: `{ctx.get('
|
|
1228
|
-
f"- `Gemini worker`: `{ctx.get('
|
|
1229
|
-
f"- `Report writer worker`: `{ctx.get('
|
|
1233
|
+
f"- `Claude lead`: `{rc.get('leadModel', ctx.get('LEAD_MODEL', ''))}`",
|
|
1234
|
+
f"- `Claude worker`: `{ctx.get('CLAUDE_WORKER_MODEL', '')}`",
|
|
1235
|
+
f"- `Codex worker`: `{ctx.get('CODEX_WORKER_MODEL', '')}`",
|
|
1236
|
+
f"- `Gemini worker`: `{ctx.get('GEMINI_WORKER_MODEL', '')}`",
|
|
1237
|
+
f"- `Report writer worker`: `{ctx.get('REPORT_WRITER_MODEL', '')}`",
|
|
1230
1238
|
]
|
|
1231
1239
|
),
|
|
1232
1240
|
"{{TASK_MANIFEST_RELATIVE_PATH}}": task_manifest.get(
|
|
@@ -1291,7 +1299,7 @@ def render_task_index(template_path: str, output_path: str, ctx: dict) -> None:
|
|
|
1291
1299
|
rendered = template
|
|
1292
1300
|
for k, v in mapping.items():
|
|
1293
1301
|
rendered = rendered.replace(k, v)
|
|
1294
|
-
rendered = _strip_phase_blocks(rendered, ctx.get("
|
|
1302
|
+
rendered = _strip_phase_blocks(rendered, ctx.get("TASK_TYPE", ""))
|
|
1295
1303
|
_write_text(Path(output_path), rendered.rstrip() + "\n")
|
|
1296
1304
|
|
|
1297
1305
|
|
|
@@ -1355,11 +1363,24 @@ def build_available_mcp_servers_block(project_root: Path) -> str:
|
|
|
1355
1363
|
# --------------------------------------------------------------------------- #
|
|
1356
1364
|
|
|
1357
1365
|
|
|
1358
|
-
def
|
|
1359
|
-
|
|
1366
|
+
def inject_lead_prompt_computed_tokens(ctx: dict) -> None:
|
|
1367
|
+
"""Populate ctx in-place with the 9 derived lead-prompt tokens.
|
|
1368
|
+
|
|
1369
|
+
Tokens that are not 1:1 with a ctx key (TEAM_CREATION_GATE,
|
|
1370
|
+
WORKER_RESULT_PATH_LINES, TEAM_ROLE_LINES, MODEL_ASSIGNMENT_LINES,
|
|
1371
|
+
REQUIRED_WORKER_ROLE_SENTENCE, GEMINI_ATTEMPT_SENTENCE,
|
|
1372
|
+
PREFERRED_WORKER_RESULTS_SENTENCE, EXECUTION_STATUS_EXACT_ENTRIES,
|
|
1373
|
+
EXECUTION_STATUS_TABLE_ROWS) are computed deterministically from ctx
|
|
1374
|
+
so the pure-lookup renderer (`render_template_with_ctx`) can resolve
|
|
1375
|
+
them via plain `ctx[token]` lookup.
|
|
1376
|
+
|
|
1377
|
+
Always overwrites — caller-supplied values for these 9 keys are replaced
|
|
1378
|
+
on every call. For optional defaults (VALIDATION_STATUS etc.) use the
|
|
1379
|
+
companion `apply_lead_prompt_defaults` which preserves caller values.
|
|
1380
|
+
"""
|
|
1360
1381
|
selected = _resolve_workers(ctx)
|
|
1361
1382
|
catalog = _worker_catalog(ctx)
|
|
1362
|
-
lead_model = ctx.get("
|
|
1383
|
+
lead_model = ctx.get("LEAD_MODEL", "")
|
|
1363
1384
|
lead_model_execution = ctx.get("LEAD_MODEL_EXECUTION_VALUE", "")
|
|
1364
1385
|
|
|
1365
1386
|
def fmt_assignment(role: str, model: str, execution: str) -> str:
|
|
@@ -1367,12 +1388,12 @@ def render_template_file(template_path: str, output_path: str, ctx: dict) -> Non
|
|
|
1367
1388
|
return f"- `{role}`: `{model}` (launch value: `{execution}`)"
|
|
1368
1389
|
return f"- `{role}`: `{model}`"
|
|
1369
1390
|
|
|
1370
|
-
worker_result_lines = []
|
|
1391
|
+
worker_result_lines: list[str] = []
|
|
1371
1392
|
team_role_lines = [f" 1. `Claude lead` (assigned model: `{lead_model}`)"]
|
|
1372
1393
|
model_assignment_lines = [
|
|
1373
1394
|
fmt_assignment("Claude lead", lead_model, lead_model_execution)
|
|
1374
1395
|
]
|
|
1375
|
-
worker_role_labels = []
|
|
1396
|
+
worker_role_labels: list[str] = []
|
|
1376
1397
|
execution_status_entries = ["`Claude lead`"]
|
|
1377
1398
|
execution_status_table_lines = [
|
|
1378
1399
|
"| 에이전트 | 역할 | 모델 | 상태 | 핵심 발견 요약 |",
|
|
@@ -1424,7 +1445,7 @@ def render_template_file(template_path: str, output_path: str, ctx: dict) -> Non
|
|
|
1424
1445
|
# selected) is single-lead and MUST NOT call `TeamCreate`. Emit a
|
|
1425
1446
|
# short notice instead of the BLOCKING gate.
|
|
1426
1447
|
# - All other phases keep the full team-creation contract.
|
|
1427
|
-
task_type = ctx.get("
|
|
1448
|
+
task_type = ctx.get("TASK_TYPE", "")
|
|
1428
1449
|
if task_type == "release-handoff" or not selected:
|
|
1429
1450
|
team_creation_gate_block = (
|
|
1430
1451
|
"## Single-Lead Phase (no team creation)\n"
|
|
@@ -1466,172 +1487,93 @@ def render_template_file(template_path: str, output_path: str, ctx: dict) -> Non
|
|
|
1466
1487
|
"response is to go back to step 2 — NOT to strip `team_name` and retry."
|
|
1467
1488
|
)
|
|
1468
1489
|
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
"
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
),
|
|
1525
|
-
"{{CLAUDE_WORKER_ERRORS_SIDECAR_RELATIVE_PATH}}": ctx.get(
|
|
1526
|
-
"CLAUDE_WORKER_ERRORS_SIDECAR_RELATIVE_PATH", ""
|
|
1527
|
-
),
|
|
1528
|
-
"{{CODEX_WORKER_ERRORS_SIDECAR_PATH}}": ctx.get(
|
|
1529
|
-
"CODEX_WORKER_ERRORS_SIDECAR_FILE", ""
|
|
1530
|
-
),
|
|
1531
|
-
"{{CODEX_WORKER_ERRORS_SIDECAR_RELATIVE_PATH}}": ctx.get(
|
|
1532
|
-
"CODEX_WORKER_ERRORS_SIDECAR_RELATIVE_PATH", ""
|
|
1533
|
-
),
|
|
1534
|
-
"{{GEMINI_WORKER_ERRORS_SIDECAR_PATH}}": ctx.get(
|
|
1535
|
-
"GEMINI_WORKER_ERRORS_SIDECAR_FILE", ""
|
|
1536
|
-
),
|
|
1537
|
-
"{{GEMINI_WORKER_ERRORS_SIDECAR_RELATIVE_PATH}}": ctx.get(
|
|
1538
|
-
"GEMINI_WORKER_ERRORS_SIDECAR_RELATIVE_PATH", ""
|
|
1539
|
-
),
|
|
1540
|
-
"{{REPORT_WRITER_WORKER_ERRORS_SIDECAR_PATH}}": ctx.get(
|
|
1541
|
-
"REPORT_WRITER_WORKER_ERRORS_SIDECAR_FILE", ""
|
|
1542
|
-
),
|
|
1543
|
-
"{{REPORT_WRITER_WORKER_ERRORS_SIDECAR_RELATIVE_PATH}}": ctx.get(
|
|
1544
|
-
"REPORT_WRITER_WORKER_ERRORS_SIDECAR_RELATIVE_PATH", ""
|
|
1545
|
-
),
|
|
1546
|
-
"{{LEAD_MODEL}}": lead_model,
|
|
1547
|
-
"{{LEAD_MODEL_EXECUTION_VALUE}}": lead_model_execution,
|
|
1548
|
-
"{{OKSTRA_VERSION}}": ctx.get("OKSTRA_VERSION", ""),
|
|
1549
|
-
"{{CLAUDE_WORKER_MODEL}}": ctx.get("CLAUDE_WORKER_MODEL_DISPLAY", ""),
|
|
1550
|
-
"{{CLAUDE_WORKER_MODEL_EXECUTION_VALUE}}": ctx.get(
|
|
1551
|
-
"CLAUDE_WORKER_MODEL_EXECUTION_VALUE", ""
|
|
1552
|
-
),
|
|
1553
|
-
"{{CODEX_WORKER_MODEL}}": ctx.get("CODEX_WORKER_MODEL_DISPLAY", ""),
|
|
1554
|
-
"{{CODEX_WORKER_MODEL_EXECUTION_VALUE}}": ctx.get(
|
|
1555
|
-
"CODEX_WORKER_MODEL_EXECUTION_VALUE", ""
|
|
1556
|
-
),
|
|
1557
|
-
"{{GEMINI_WORKER_MODEL}}": ctx.get("GEMINI_WORKER_MODEL_DISPLAY", ""),
|
|
1558
|
-
"{{GEMINI_WORKER_MODEL_EXECUTION_VALUE}}": ctx.get(
|
|
1559
|
-
"GEMINI_WORKER_MODEL_EXECUTION_VALUE", ""
|
|
1560
|
-
),
|
|
1561
|
-
"{{REPORT_WRITER_MODEL}}": ctx.get("REPORT_WRITER_MODEL_DISPLAY", ""),
|
|
1562
|
-
"{{REPORT_WRITER_MODEL_EXECUTION_VALUE}}": ctx.get(
|
|
1563
|
-
"REPORT_WRITER_MODEL_EXECUTION_VALUE", ""
|
|
1564
|
-
),
|
|
1565
|
-
"{{WORKER_RESULT_PATH_LINES}}": "\n".join(worker_result_lines),
|
|
1566
|
-
"{{MODEL_ASSIGNMENT_LINES}}": "\n".join(model_assignment_lines),
|
|
1567
|
-
"{{TEAM_ROLE_LINES}}": "\n".join(team_role_lines),
|
|
1568
|
-
"{{REQUIRED_WORKER_ROLE_SENTENCE}}": worker_role_sentence,
|
|
1569
|
-
"{{GEMINI_ATTEMPT_SENTENCE}}": worker_attempt_sentence,
|
|
1570
|
-
"{{PREFERRED_WORKER_RESULTS_SENTENCE}}": preferred_results_sentence,
|
|
1571
|
-
"{{EXECUTION_STATUS_EXACT_ENTRIES}}": ", ".join(execution_status_entries),
|
|
1572
|
-
"{{EXECUTION_STATUS_TABLE_ROWS}}": "\n".join(execution_status_table_lines),
|
|
1573
|
-
"{{FINAL_REPORT_TEMPLATE_PATH}}": ctx.get("FINAL_REPORT_TEMPLATE_FILE", ""),
|
|
1574
|
-
"{{FINAL_REPORT_TEMPLATE_RELATIVE_PATH}}": ctx.get(
|
|
1575
|
-
"FINAL_REPORT_TEMPLATE_RELATIVE_PATH", ""
|
|
1576
|
-
),
|
|
1577
|
-
"{{REFERENCE_EXPECTATIONS_RELATIVE_PATH}}": ctx.get(
|
|
1578
|
-
"REFERENCE_EXPECTATIONS_RELATIVE_PATH", ""
|
|
1579
|
-
),
|
|
1580
|
-
"{{CLAUDE_SESSION_ID}}": ctx.get("CLAUDE_SESSION_ID", ""),
|
|
1581
|
-
"{{CLAUDE_RESUME_COMMAND_PATH}}": ctx.get("CLAUDE_RESUME_COMMAND_FILE", ""),
|
|
1582
|
-
"{{CLAUDE_RESUME_COMMAND_RELATIVE_PATH}}": ctx.get(
|
|
1583
|
-
"CLAUDE_RESUME_COMMAND_RELATIVE_PATH", ""
|
|
1584
|
-
),
|
|
1585
|
-
"{{TASK_ROOT_RELATIVE_PATH}}": ctx.get("TASK_ROOT_RELATIVE_PATH", ""),
|
|
1586
|
-
"{{TASK_MANIFEST_RELATIVE_PATH}}": ctx.get("TASK_MANIFEST_RELATIVE_PATH", ""),
|
|
1587
|
-
"{{TASK_INDEX_RELATIVE_PATH}}": ctx.get("TASK_INDEX_RELATIVE_PATH", ""),
|
|
1588
|
-
"{{INSTRUCTION_SET_RELATIVE_PATH}}": ctx.get(
|
|
1589
|
-
"INSTRUCTION_SET_RELATIVE_PATH", ""
|
|
1590
|
-
),
|
|
1591
|
-
"{{RUNS_RELATIVE_PATH}}": ctx.get("RUNS_RELATIVE_PATH", ""),
|
|
1592
|
-
"{{HISTORY_RELATIVE_PATH}}": ctx.get("HISTORY_RELATIVE_PATH", ""),
|
|
1593
|
-
"{{OKSTRA_DISCOVERY_RELATIVE_PATH}}": ctx.get(
|
|
1594
|
-
"OKSTRA_DISCOVERY_RELATIVE_PATH", ""
|
|
1595
|
-
),
|
|
1596
|
-
"{{OKSTRA_LATEST_TASK_RELATIVE_PATH}}": ctx.get(
|
|
1597
|
-
"OKSTRA_LATEST_TASK_RELATIVE_PATH", ""
|
|
1598
|
-
),
|
|
1599
|
-
"{{OKSTRA_TASK_CATALOG_RELATIVE_PATH}}": ctx.get(
|
|
1600
|
-
"OKSTRA_TASK_CATALOG_RELATIVE_PATH", ""
|
|
1601
|
-
),
|
|
1602
|
-
"{{LATEST_RUN_RELATIVE_PATH}}": ctx.get("LATEST_RUN_RELATIVE_PATH", ""),
|
|
1603
|
-
"{{LATEST_REPORT_RELATIVE_PATH}}": ctx.get("LATEST_REPORT_RELATIVE_PATH", ""),
|
|
1604
|
-
"{{TIMELINE_RELATIVE_PATH}}": ctx.get("TIMELINE_RELATIVE_PATH", ""),
|
|
1605
|
-
"{{CURRENT_TASK_STATUS}}": ctx.get("CURRENT_TASK_STATUS", ""),
|
|
1606
|
-
"{{CURRENT_RUN_STATUS}}": ctx.get("CURRENT_RUN_STATUS", ""),
|
|
1607
|
-
"{{VALIDATION_STATUS}}": ctx.get("VALIDATION_STATUS", "not-run"),
|
|
1608
|
-
"{{RELATED_TASKS_BULLETS}}": ctx.get(
|
|
1609
|
-
"RELATED_TASKS_BULLETS", "- None recorded"
|
|
1610
|
-
),
|
|
1611
|
-
"{{RELATED_TASKS_INLINE}}": ctx.get("RELATED_TASKS_INLINE", "None"),
|
|
1612
|
-
"{{WORKFLOW_CURRENT_PHASE}}": ctx.get("WORKFLOW_CURRENT_PHASE", ""),
|
|
1613
|
-
"{{WORKFLOW_NEXT_RECOMMENDED_PHASE}}": ctx.get(
|
|
1614
|
-
"WORKFLOW_NEXT_RECOMMENDED_PHASE", ""
|
|
1615
|
-
),
|
|
1616
|
-
"{{PHASE_ALLOWED_OUTPUTS}}": ctx.get("PHASE_ALLOWED_OUTPUTS", ""),
|
|
1617
|
-
"{{PHASE_FORBIDDEN_ACTIONS}}": ctx.get("PHASE_FORBIDDEN_ACTIONS", ""),
|
|
1618
|
-
"{{AVAILABLE_MCP_SERVERS}}": ctx.get(
|
|
1619
|
-
"AVAILABLE_MCP_SERVERS",
|
|
1620
|
-
build_available_mcp_servers_block(Path(ctx.get("PROJECT_ROOT", "."))),
|
|
1621
|
-
),
|
|
1622
|
-
"{{EXECUTOR_WORKTREE_PATH}}": ctx.get("EXECUTOR_WORKTREE_PATH", ""),
|
|
1623
|
-
"{{EXECUTOR_WORKTREE_BRANCH}}": ctx.get("EXECUTOR_WORKTREE_BRANCH", ""),
|
|
1624
|
-
"{{EXECUTOR_WORKTREE_BASE_REF}}": ctx.get("EXECUTOR_WORKTREE_BASE_REF", ""),
|
|
1625
|
-
"{{EXECUTOR_WORKTREE_STATUS}}": ctx.get("EXECUTOR_WORKTREE_STATUS", ""),
|
|
1626
|
-
"{{EXECUTOR_WORKTREE_NOTE}}": ctx.get("EXECUTOR_WORKTREE_NOTE", ""),
|
|
1627
|
-
}
|
|
1490
|
+
# Compute results (deterministic from ctx, 덮어쓰기)
|
|
1491
|
+
ctx["TEAM_CREATION_GATE"] = team_creation_gate_block
|
|
1492
|
+
ctx["WORKER_RESULT_PATH_LINES"] = "\n".join(worker_result_lines)
|
|
1493
|
+
ctx["MODEL_ASSIGNMENT_LINES"] = "\n".join(model_assignment_lines)
|
|
1494
|
+
ctx["TEAM_ROLE_LINES"] = "\n".join(team_role_lines)
|
|
1495
|
+
ctx["REQUIRED_WORKER_ROLE_SENTENCE"] = worker_role_sentence
|
|
1496
|
+
ctx["GEMINI_ATTEMPT_SENTENCE"] = worker_attempt_sentence
|
|
1497
|
+
ctx["PREFERRED_WORKER_RESULTS_SENTENCE"] = preferred_results_sentence
|
|
1498
|
+
ctx["EXECUTION_STATUS_EXACT_ENTRIES"] = ", ".join(execution_status_entries)
|
|
1499
|
+
ctx["EXECUTION_STATUS_TABLE_ROWS"] = "\n".join(execution_status_table_lines)
|
|
1500
|
+
|
|
1501
|
+
|
|
1502
|
+
def apply_lead_prompt_defaults(ctx: dict) -> None:
|
|
1503
|
+
"""Apply default values for optional lead-prompt ctx fields.
|
|
1504
|
+
|
|
1505
|
+
Sets four optional tokens that the lead prompt template references but
|
|
1506
|
+
which callers may legitimately leave unset (e.g., no validation has run
|
|
1507
|
+
yet, no related tasks were declared). Caller-supplied values are
|
|
1508
|
+
preserved via `setdefault` / `if-not-in` semantics — this function only
|
|
1509
|
+
fills gaps, never overwrites.
|
|
1510
|
+
|
|
1511
|
+
Companion to `inject_lead_prompt_computed_tokens` (which always
|
|
1512
|
+
overwrites with deterministically-derived values). The two functions
|
|
1513
|
+
are kept separate so each has a single clear responsibility:
|
|
1514
|
+
inject = compute-and-overwrite, apply_defaults = fill-if-missing.
|
|
1515
|
+
"""
|
|
1516
|
+
ctx.setdefault("VALIDATION_STATUS", "not-run")
|
|
1517
|
+
ctx.setdefault("RELATED_TASKS_BULLETS", "- None recorded")
|
|
1518
|
+
ctx.setdefault("RELATED_TASKS_INLINE", "None")
|
|
1519
|
+
if "AVAILABLE_MCP_SERVERS" not in ctx:
|
|
1520
|
+
ctx["AVAILABLE_MCP_SERVERS"] = build_available_mcp_servers_block(
|
|
1521
|
+
Path(ctx.get("PROJECT_ROOT", "."))
|
|
1522
|
+
)
|
|
1523
|
+
|
|
1524
|
+
|
|
1525
|
+
_TOKEN_RE = re.compile(r"\{\{([A-Z][A-Z0-9_]*)\}\}")
|
|
1526
|
+
|
|
1527
|
+
|
|
1528
|
+
def render_template_with_ctx(template_path: str, output_path: str, ctx: dict) -> None:
|
|
1529
|
+
"""Render a `{{TOKEN}}` template with pure ctx[token] lookup.
|
|
1530
|
+
|
|
1531
|
+
- Tokens match regex `_TOKEN_RE` (uppercase snake).
|
|
1532
|
+
- Each token MUST exist in ctx. Missing → `TokenRenderError` (fail-fast).
|
|
1533
|
+
- Phase block stripping (`{% if header.taskType == 'X' %} ... {% endif %}`)
|
|
1534
|
+
is applied per `ctx['TASK_TYPE']`.
|
|
1535
|
+
- Frontmatter mapping (`_frontmatter_mapping`) is overlaid (same as legacy
|
|
1536
|
+
renderer).
|
|
1537
|
+
|
|
1538
|
+
Callers that need computed tokens (team_creation_gate etc.) MUST call
|
|
1539
|
+
`inject_lead_prompt_computed_tokens(ctx)` BEFORE invoking this function.
|
|
1540
|
+
Optional defaults (VALIDATION_STATUS etc.) should be filled by calling
|
|
1541
|
+
`apply_lead_prompt_defaults(ctx)` in the same setup step.
|
|
1542
|
+
"""
|
|
1543
|
+
template = Path(template_path).read_text(encoding="utf-8")
|
|
1544
|
+
|
|
1628
1545
|
fm_ctx = dict(ctx)
|
|
1629
1546
|
fm_ctx.setdefault("DOC_TYPE", _doc_type_from_template_path(template_path))
|
|
1630
|
-
|
|
1547
|
+
fm_overlay = _frontmatter_mapping(fm_ctx) # {"{{DOC_TITLE}}": "...", ...}
|
|
1548
|
+
|
|
1549
|
+
# frontmatter overlay 가 채우는 키들도 lookup 대상 — 단일 lookup 으로 통일
|
|
1550
|
+
lookup: dict[str, str] = {}
|
|
1551
|
+
for tok_with_braces, value in fm_overlay.items():
|
|
1552
|
+
key = tok_with_braces[2:-2] # "{{X}}" -> "X"
|
|
1553
|
+
lookup[key] = value
|
|
1554
|
+
|
|
1631
1555
|
rendered = template
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1556
|
+
missing: list[str] = []
|
|
1557
|
+
for match in _TOKEN_RE.finditer(template):
|
|
1558
|
+
token = match.group(1)
|
|
1559
|
+
if token in lookup:
|
|
1560
|
+
value = lookup[token]
|
|
1561
|
+
elif token in ctx:
|
|
1562
|
+
value = str(ctx[token])
|
|
1563
|
+
else:
|
|
1564
|
+
missing.append(token)
|
|
1565
|
+
continue
|
|
1566
|
+
rendered = rendered.replace("{{" + token + "}}", value)
|
|
1567
|
+
|
|
1568
|
+
if missing:
|
|
1569
|
+
names = ", ".join(sorted(set(missing)))
|
|
1570
|
+
raise TokenRenderError(
|
|
1571
|
+
f"undefined lead-prompt token(s): {names} (template={template_path}). "
|
|
1572
|
+
f"Add the key(s) to ctx in run.py / "
|
|
1573
|
+
f"inject_lead_prompt_computed_tokens() / apply_lead_prompt_defaults()."
|
|
1574
|
+
)
|
|
1575
|
+
|
|
1576
|
+
rendered = _strip_phase_blocks(rendered, ctx.get("TASK_TYPE", ""))
|
|
1635
1577
|
_write_text(Path(output_path), rendered.rstrip() + "\n")
|
|
1636
1578
|
|
|
1637
1579
|
|
|
@@ -1681,7 +1623,10 @@ def main(argv: list[str]) -> int:
|
|
|
1681
1623
|
render_task_index(template_path, output_path, _load_ctx(ctx_path))
|
|
1682
1624
|
elif sub == "template":
|
|
1683
1625
|
ctx_path, template_path, output_path = rest
|
|
1684
|
-
|
|
1626
|
+
ctx = _load_ctx(ctx_path)
|
|
1627
|
+
inject_lead_prompt_computed_tokens(ctx)
|
|
1628
|
+
apply_lead_prompt_defaults(ctx)
|
|
1629
|
+
render_template_with_ctx(template_path, output_path, ctx)
|
|
1685
1630
|
else:
|
|
1686
1631
|
print(f"unknown subcommand: {sub}", file=sys.stderr)
|
|
1687
1632
|
return 2
|