okstra 0.34.1 → 0.36.1
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 +27 -19
- package/README.md +27 -19
- 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 +353 -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/plans/2026-05-24-implementation-lead-context-slimming.md +1700 -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 +30 -7
- package/runtime/agents/workers/claude-worker.md +31 -6
- package/runtime/agents/workers/codex-worker.md +37 -10
- package/runtime/agents/workers/gemini-worker.md +34 -7
- package/runtime/agents/workers/report-writer-worker.md +19 -10
- 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/launch.template.md +1 -0
- package/runtime/prompts/profiles/_common-contract.md +11 -6
- package/runtime/prompts/profiles/_implementation-deliverable.md +53 -0
- package/runtime/prompts/profiles/_implementation-executor.md +60 -0
- package/runtime/prompts/profiles/_implementation-verifier.md +76 -0
- 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 -118
- package/runtime/prompts/profiles/improvement-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 +26 -20
- package/runtime/python/okstra_ctl/render.py +166 -207
- package/runtime/python/okstra_ctl/render_final_report.py +53 -10
- package/runtime/python/okstra_ctl/run.py +299 -108
- package/runtime/python/okstra_ctl/run_context.py +22 -0
- package/runtime/python/okstra_ctl/seeding.py +186 -0
- package/runtime/python/okstra_ctl/session.py +65 -7
- 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 +102 -218
- package/runtime/skills/okstra-convergence/SKILL.md +2 -3
- package/runtime/skills/okstra-inspect/SKILL.md +581 -0
- package/runtime/skills/okstra-report-writer/SKILL.md +35 -15
- package/runtime/skills/okstra-run/SKILL.md +8 -7
- package/runtime/skills/okstra-schedule/SKILL.md +14 -157
- package/runtime/skills/okstra-setup/SKILL.md +28 -1
- package/runtime/skills/okstra-team-contract/SKILL.md +16 -107
- package/runtime/templates/okstra.CLAUDE.md +104 -0
- package/runtime/templates/reports/brief.template.md +204 -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/schedule.template.md +12 -3
- package/runtime/templates/reports/task-brief.template.md +2 -2
- package/runtime/templates/worker-prompt-preamble.md +108 -0
- 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 +27 -3
- package/runtime/skills/okstra-history/SKILL.md +0 -165
- package/runtime/skills/okstra-logs/SKILL.md +0 -173
- package/runtime/skills/okstra-report-finder/SKILL.md +0 -111
- package/runtime/skills/okstra-status/SKILL.md +0 -246
- package/runtime/skills/okstra-time-summary/SKILL.md +0 -172
|
@@ -15,10 +15,13 @@ state passing, and are read once at the start.
|
|
|
15
15
|
"""
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
|
+
import importlib.util
|
|
18
19
|
import json
|
|
19
20
|
import os
|
|
20
21
|
import re
|
|
21
22
|
import shutil
|
|
23
|
+
import subprocess as _subprocess
|
|
24
|
+
import sys
|
|
22
25
|
from dataclasses import dataclass, field
|
|
23
26
|
from datetime import datetime, timezone
|
|
24
27
|
from pathlib import Path
|
|
@@ -35,6 +38,8 @@ from .material import (
|
|
|
35
38
|
from .models import resolve_model_metadata
|
|
36
39
|
from .path_resolve import relative_to_project_root, resolve_user_file
|
|
37
40
|
from .render import (
|
|
41
|
+
apply_lead_prompt_defaults,
|
|
42
|
+
inject_lead_prompt_computed_tokens,
|
|
38
43
|
render_latest_task_discovery,
|
|
39
44
|
render_reference_expectations,
|
|
40
45
|
render_run_manifest,
|
|
@@ -42,13 +47,17 @@ from .render import (
|
|
|
42
47
|
render_task_index,
|
|
43
48
|
render_task_manifest,
|
|
44
49
|
render_team_state,
|
|
45
|
-
|
|
50
|
+
render_template_with_ctx,
|
|
46
51
|
render_timeline,
|
|
47
52
|
)
|
|
48
53
|
from .run_context import compute_and_write_run_context, write_run_inputs
|
|
49
54
|
from .seeding import (
|
|
55
|
+
AgentsMdLinkError,
|
|
56
|
+
ClaudeMdLinkError,
|
|
50
57
|
SettingsLinkError,
|
|
51
58
|
cleanup_obsolete_generated_docs,
|
|
59
|
+
ensure_project_agents_md,
|
|
60
|
+
ensure_project_claude_md,
|
|
52
61
|
ensure_project_settings_symlink,
|
|
53
62
|
installed_version,
|
|
54
63
|
verify_installation,
|
|
@@ -67,21 +76,30 @@ from .workers import (
|
|
|
67
76
|
from .workflow import compute_workflow_state
|
|
68
77
|
from .worktree import provision_task_worktree
|
|
69
78
|
|
|
70
|
-
#
|
|
79
|
+
# Frontmatter approval-flag matcher.
|
|
71
80
|
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
APPROVED_PLAN_PATTERN = re.compile(
|
|
80
|
-
r"^[ \t]*(?:[-*+][ \t]+)?`?(APPROVED([ \t]|:|$|`)|\[x\][ \t]*Approved`?|"
|
|
81
|
-
r"User[ \t]+Approval[ \t]*:[ \t]*(APPROVED|granted|yes)`?)",
|
|
81
|
+
# Final-report 의 YAML frontmatter 안에서 `approved: true` / `approved: false`
|
|
82
|
+
# 한 줄만 식별한다. report-writer 가 항상 이 한 줄을 출력하고, 사용자가
|
|
83
|
+
# 직접 편집하거나 `--approve` CLI 가 이 줄만 toggle 한다. 본문(body) 의 다른
|
|
84
|
+
# `approved:` 등장과 충돌하지 않도록 호출자는 frontmatter 블록을 먼저 추출
|
|
85
|
+
# (`_extract_frontmatter_block`) 한 뒤 이 패턴을 적용한다.
|
|
86
|
+
APPROVED_FRONTMATTER_PATTERN = re.compile(
|
|
87
|
+
r"^approved:[ \t]+(true|false)[ \t]*$",
|
|
82
88
|
re.IGNORECASE | re.MULTILINE,
|
|
83
89
|
)
|
|
84
90
|
|
|
91
|
+
_FRONTMATTER_BLOCK_PATTERN = re.compile(r"\A---\n(.*?)\n---\n", re.DOTALL)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _extract_frontmatter_block(body: str) -> str | None:
|
|
95
|
+
"""final-report 의 leading `---` 펜스 사이에 든 YAML 블록 텍스트를 반환.
|
|
96
|
+
|
|
97
|
+
펜스가 없거나 닫히지 않으면 None. report-writer 의 표준 산출물은 항상
|
|
98
|
+
`---\\n...frontmatter...\\n---\\n` 로 시작한다.
|
|
99
|
+
"""
|
|
100
|
+
m = _FRONTMATTER_BLOCK_PATTERN.match(body)
|
|
101
|
+
return m.group(1) if m else None
|
|
102
|
+
|
|
85
103
|
|
|
86
104
|
class PrepareError(Exception):
|
|
87
105
|
"""surface to caller — task bundle prepare failed."""
|
|
@@ -108,6 +126,7 @@ class PrepareInputs:
|
|
|
108
126
|
work_category: str = ""
|
|
109
127
|
base_ref: str = ""
|
|
110
128
|
approved_plan_path: str = ""
|
|
129
|
+
stage: str = "auto"
|
|
111
130
|
clarification_response_path: str = "" # absolute or empty
|
|
112
131
|
# release-handoff 전용: PR 본문 템플릿 1회성 override. 빈 문자열이면
|
|
113
132
|
# project.json → global config → 스킬 디폴트 순으로 해석된다.
|
|
@@ -137,25 +156,35 @@ def _validate_approved_plan(path: str) -> None:
|
|
|
137
156
|
if not p.is_file():
|
|
138
157
|
raise PrepareError(f"approved plan file not found: {path}")
|
|
139
158
|
body = p.read_text(encoding="utf-8", errors="replace")
|
|
140
|
-
|
|
159
|
+
frontmatter = _extract_frontmatter_block(body)
|
|
160
|
+
if frontmatter is None:
|
|
141
161
|
raise PrepareError(
|
|
142
|
-
f"approved plan has no
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
" shortcut: re-run okstra with --approve to have the CLI itself "
|
|
147
|
-
"record the approval marker on this file."
|
|
162
|
+
f"approved plan has no YAML frontmatter block: {path}\n"
|
|
163
|
+
" expected the report to begin with `---\\n...\\n---\\n`. "
|
|
164
|
+
"report-writer worker emits this header on every run; "
|
|
165
|
+
"regenerate the report if the header is missing."
|
|
148
166
|
)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
167
|
+
m = APPROVED_FRONTMATTER_PATTERN.search(frontmatter)
|
|
168
|
+
if not m:
|
|
169
|
+
raise PrepareError(
|
|
170
|
+
f"approved plan frontmatter has no `approved:` field: {path}\n"
|
|
171
|
+
" expected a single line of the form `approved: true` "
|
|
172
|
+
"(or `approved: false` for the unflipped state)."
|
|
173
|
+
)
|
|
174
|
+
if m.group(1).lower() != "true":
|
|
175
|
+
raise PrepareError(
|
|
176
|
+
f"approved plan is not yet approved (frontmatter `approved: {m.group(1)}`): {path}\n"
|
|
177
|
+
" open the report and change the frontmatter line to `approved: true`, "
|
|
178
|
+
"or re-run okstra with `--approve` to flip it from the CLI.\n"
|
|
179
|
+
" resolve any `Blocks=approval` rows in `## 5. Clarification Items` first."
|
|
180
|
+
)
|
|
181
|
+
# frontmatter approved == true 상태. §5 Clarification Items 의
|
|
182
|
+
# Blocks=approval 행이 아직 open/answered 면 승인을 무효화한다.
|
|
154
183
|
blockers = unresolved_approval_blockers(body)
|
|
155
184
|
if blockers:
|
|
156
185
|
lines = [
|
|
157
|
-
f"approved plan
|
|
158
|
-
f"`Blocks=approval` row(s); resolve them or mark them obsolete
|
|
186
|
+
f"approved plan frontmatter has `approved: true` but §5 has {len(blockers)} "
|
|
187
|
+
f"unresolved `Blocks=approval` row(s); resolve them or mark them obsolete first:",
|
|
159
188
|
]
|
|
160
189
|
for b in blockers:
|
|
161
190
|
lines.append(f" - {b.row_id} (Status={b.raw_status})")
|
|
@@ -163,31 +192,121 @@ def _validate_approved_plan(path: str) -> None:
|
|
|
163
192
|
raise PrepareError("\n".join(lines))
|
|
164
193
|
|
|
165
194
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
# Group 1: leading whitespace + optional bullet + optional opening backtick.
|
|
170
|
-
# Group 2: optional closing backtick + trailing whitespace.
|
|
171
|
-
# Both groups are preserved verbatim in the replacement so a backtick-wrapped
|
|
172
|
-
# `- \`[ ] Approved\`` flips to `- \`[x] Approved\`` without losing the
|
|
173
|
-
# surrounding code span — the validator regex tolerates either form.
|
|
174
|
-
APPROVAL_UNCHECKED_PATTERN = re.compile(
|
|
175
|
-
r"^([ \t]*(?:[-*+][ \t]+)?`?)\[[ \t]\][ \t]*Approved(`?[ \t]*)$",
|
|
176
|
-
re.IGNORECASE | re.MULTILINE,
|
|
195
|
+
_STAGE_VALIDATOR_PATH = (
|
|
196
|
+
Path(__file__).resolve().parents[2] / "validators"
|
|
197
|
+
/ "validate-implementation-plan-stages.py"
|
|
177
198
|
)
|
|
178
199
|
|
|
179
200
|
|
|
201
|
+
def _validate_stage_structure(plan_path: str) -> None:
|
|
202
|
+
"""Run validators/validate-implementation-plan-stages.py against the approved plan."""
|
|
203
|
+
r = _subprocess.run(
|
|
204
|
+
[sys.executable, str(_STAGE_VALIDATOR_PATH), "--plan", plan_path],
|
|
205
|
+
capture_output=True, text=True,
|
|
206
|
+
)
|
|
207
|
+
if r.returncode != 0:
|
|
208
|
+
raise PrepareError(
|
|
209
|
+
f"approved plan failed stage validation:\n{r.stderr.strip()}"
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _resolve_effective_stage(
|
|
214
|
+
stages: list,
|
|
215
|
+
done_stages: set,
|
|
216
|
+
requested: str,
|
|
217
|
+
) -> int:
|
|
218
|
+
"""Return the stage number to execute.
|
|
219
|
+
|
|
220
|
+
`requested` is either "auto" or a decimal string.
|
|
221
|
+
Raises PrepareError on all rejection cases.
|
|
222
|
+
"""
|
|
223
|
+
if requested == "auto":
|
|
224
|
+
for s in stages:
|
|
225
|
+
if s["stage_number"] in done_stages:
|
|
226
|
+
continue
|
|
227
|
+
if all(d in done_stages for d in s["depends_on"]):
|
|
228
|
+
return s["stage_number"]
|
|
229
|
+
raise PrepareError(
|
|
230
|
+
"no stage is ready: every remaining stage has unsatisfied depends-on"
|
|
231
|
+
)
|
|
232
|
+
try:
|
|
233
|
+
n = int(requested)
|
|
234
|
+
except ValueError:
|
|
235
|
+
raise PrepareError(
|
|
236
|
+
f"--stage must be 'auto' or an integer, got {requested!r}"
|
|
237
|
+
)
|
|
238
|
+
target = next((s for s in stages if s["stage_number"] == n), None)
|
|
239
|
+
if target is None:
|
|
240
|
+
raise PrepareError(
|
|
241
|
+
f"--stage {n} not in Stage Map "
|
|
242
|
+
f"(have {[s['stage_number'] for s in stages]})"
|
|
243
|
+
)
|
|
244
|
+
if n in done_stages:
|
|
245
|
+
raise PrepareError(
|
|
246
|
+
f"--stage {n} already completed (consumers.jsonl status:done exists)"
|
|
247
|
+
)
|
|
248
|
+
return n
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _parse_stage_map_into_ctx(plan_path: str) -> list:
|
|
252
|
+
"""Reuse the validator's parser to extract StageMeta dicts for the ctx."""
|
|
253
|
+
text = Path(plan_path).read_text(encoding="utf-8")
|
|
254
|
+
spec = importlib.util.spec_from_file_location(
|
|
255
|
+
"_ip_stage_validator", _STAGE_VALIDATOR_PATH
|
|
256
|
+
)
|
|
257
|
+
if spec is None or spec.loader is None:
|
|
258
|
+
raise PrepareError(f"cannot load stage validator at {_STAGE_VALIDATOR_PATH}")
|
|
259
|
+
mod = importlib.util.module_from_spec(spec)
|
|
260
|
+
# Register before exec_module so dataclass field-type resolution can find
|
|
261
|
+
# the module in sys.modules (required on Python 3.9).
|
|
262
|
+
sys.modules["_ip_stage_validator"] = mod
|
|
263
|
+
try:
|
|
264
|
+
spec.loader.exec_module(mod)
|
|
265
|
+
finally:
|
|
266
|
+
sys.modules.pop("_ip_stage_validator", None)
|
|
267
|
+
stages, _errs = mod._parse_stage_map(text)
|
|
268
|
+
return [
|
|
269
|
+
{
|
|
270
|
+
"stage_number": s.stage_number,
|
|
271
|
+
"title": s.title,
|
|
272
|
+
"depends_on": list(s.depends_on),
|
|
273
|
+
"step_count": s.step_count,
|
|
274
|
+
"exit_contract_summary": s.exit_contract_summary,
|
|
275
|
+
}
|
|
276
|
+
for s in stages
|
|
277
|
+
]
|
|
278
|
+
|
|
279
|
+
|
|
180
280
|
def _apply_cli_approval(path: str) -> str:
|
|
181
|
-
"""`--approve` 가 지정된 경우 approved-plan
|
|
281
|
+
"""`--approve` 가 지정된 경우 approved-plan 의 frontmatter `approved` 를 true 로 토글.
|
|
182
282
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
283
|
+
동작 요약:
|
|
284
|
+
- frontmatter 에 `approved: false` 가 있으면 `approved: true` 로 교체하고
|
|
285
|
+
audit 라인을 append → `"frontmatter-flipped"` 반환.
|
|
286
|
+
- 이미 `approved: true` 면 한 번도 기록되지 않은 audit 라인만 append →
|
|
287
|
+
`"already-approved-audit-appended"`, 이미 동일 audit 가 있으면
|
|
288
|
+
`"already-approved"` 로 no-op.
|
|
289
|
+
- frontmatter 가 없거나 `approved:` 라인이 없으면 PrepareError.
|
|
290
|
+
|
|
291
|
+
Idempotent: 동일 audit 라인은 한 번만 기록된다.
|
|
186
292
|
"""
|
|
187
293
|
p = Path(path)
|
|
188
294
|
if not p.is_file():
|
|
189
295
|
raise PrepareError(f"approved plan file not found: {path}")
|
|
190
296
|
body = p.read_text(encoding="utf-8", errors="replace")
|
|
297
|
+
frontmatter = _extract_frontmatter_block(body)
|
|
298
|
+
if frontmatter is None:
|
|
299
|
+
raise PrepareError(
|
|
300
|
+
f"--approve was given but the approved-plan file has no YAML frontmatter: {path}\n"
|
|
301
|
+
" expected the report to begin with `---\\n...\\n---\\n`."
|
|
302
|
+
)
|
|
303
|
+
m = APPROVED_FRONTMATTER_PATTERN.search(frontmatter)
|
|
304
|
+
if not m:
|
|
305
|
+
raise PrepareError(
|
|
306
|
+
f"--approve was given but the approved-plan frontmatter has no `approved:` field: {path}\n"
|
|
307
|
+
" expected a single line of the form `approved: false` "
|
|
308
|
+
"(report-writer worker emits this by default)."
|
|
309
|
+
)
|
|
191
310
|
|
|
192
311
|
audit_iso = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
193
312
|
audit_line = (
|
|
@@ -195,37 +314,28 @@ def _apply_cli_approval(path: str) -> str:
|
|
|
195
314
|
"(user CLI invocation treated as approval signal)"
|
|
196
315
|
)
|
|
197
316
|
|
|
198
|
-
if
|
|
199
|
-
# 이미 사용자(또는 이전 --approve 호출)가 마커를 남긴 상태. audit 라인이
|
|
200
|
-
# 없으면 보조적으로 한 줄만 추가하고 마커 자체는 건드리지 않는다.
|
|
317
|
+
if m.group(1).lower() == "true":
|
|
201
318
|
if audit_line.split(" — ")[1] in body:
|
|
202
319
|
return "already-approved"
|
|
203
320
|
new_body = body.rstrip("\n") + "\n" + audit_line + "\n"
|
|
204
321
|
p.write_text(new_body, encoding="utf-8")
|
|
205
322
|
return "already-approved-audit-appended"
|
|
206
323
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
lambda m: f"{m.group(1)}[x] Approved{m.group(2)}", body, count=1,
|
|
210
|
-
)
|
|
211
|
-
new_body = new_body.rstrip("\n") + "\n" + audit_line + "\n"
|
|
212
|
-
p.write_text(new_body, encoding="utf-8")
|
|
213
|
-
return "checkbox-flipped"
|
|
214
|
-
|
|
215
|
-
raise PrepareError(
|
|
216
|
-
f"--approve was given but the approved-plan file has no `User Approval Request` "
|
|
217
|
-
f"checkbox to flip: {path}\n"
|
|
218
|
-
" expected a line of the form `- [ ] Approved` near the top of the report "
|
|
219
|
-
"(see templates/reports/final-report.template.md)."
|
|
324
|
+
flipped_frontmatter = APPROVED_FRONTMATTER_PATTERN.sub(
|
|
325
|
+
"approved: true", frontmatter, count=1,
|
|
220
326
|
)
|
|
327
|
+
new_body = body.replace(frontmatter, flipped_frontmatter, 1)
|
|
328
|
+
new_body = new_body.rstrip("\n") + "\n" + audit_line + "\n"
|
|
329
|
+
p.write_text(new_body, encoding="utf-8")
|
|
330
|
+
return "frontmatter-flipped"
|
|
221
331
|
|
|
222
332
|
|
|
223
333
|
def _ensure_task_directories(ctx: dict) -> None:
|
|
224
334
|
for key in (
|
|
225
|
-
"TASK_ROOT", "
|
|
335
|
+
"TASK_ROOT", "INSTRUCTION_SET_PATH", "RUNS_DIR", "HISTORY_DIR",
|
|
226
336
|
"RUN_DIR", "RUN_MANIFESTS_DIR", "RUN_STATE_DIR", "RUN_PROMPTS_DIR",
|
|
227
337
|
"RUN_REPORTS_DIR", "RUN_STATUS_DIR", "RUN_SESSIONS_DIR",
|
|
228
|
-
"RUN_LOGS_DIR", "
|
|
338
|
+
"RUN_LOGS_DIR", "WORKER_RESULTS_PATH", "RUN_CARRY_PATH", "OKSTRA_DISCOVERY_DIR",
|
|
229
339
|
):
|
|
230
340
|
Path(ctx[key]).mkdir(parents=True, exist_ok=True)
|
|
231
341
|
|
|
@@ -329,11 +439,11 @@ def _record_start(
|
|
|
329
439
|
project_root=ctx["PROJECT_ROOT"],
|
|
330
440
|
task_group=ctx["TASK_GROUP"],
|
|
331
441
|
task_id=ctx["TASK_ID"],
|
|
332
|
-
task_type=ctx.get("
|
|
442
|
+
task_type=ctx.get("TASK_TYPE", ""),
|
|
333
443
|
run_seq=int(ctx["RUN_MANIFESTS_SEQ"]),
|
|
334
444
|
when=ctx["RUN_TIMESTAMP_ISO"],
|
|
335
|
-
workers=[w for w in ctx.get("
|
|
336
|
-
lead_model=ctx.get("
|
|
445
|
+
workers=[w for w in ctx.get("RECOMMENDED_ANALYSERS", "").split(",") if w],
|
|
446
|
+
lead_model=ctx.get("LEAD_MODEL", ""),
|
|
337
447
|
run_dir_rel=ctx.get("RUN_DIR_RELATIVE_PATH", ""),
|
|
338
448
|
final_report_rel=ctx.get("FINAL_REPORT_RELATIVE_PATH", ""),
|
|
339
449
|
final_status_rel=ctx.get("FINAL_STATUS_RELATIVE_PATH", ""),
|
|
@@ -358,7 +468,7 @@ def _brief_sha256(path: Path) -> str:
|
|
|
358
468
|
|
|
359
469
|
def _canonical_argv(inp: PrepareInputs, ctx: dict) -> list[str]:
|
|
360
470
|
"""rerun 충실 재현을 위한 canonical argv 재구성."""
|
|
361
|
-
workers = inp.workers_override or ctx.get("
|
|
471
|
+
workers = inp.workers_override or ctx.get("RECOMMENDED_ANALYSERS", "")
|
|
362
472
|
pairs = [
|
|
363
473
|
("--task-type", inp.task_type),
|
|
364
474
|
("--project-id", inp.project_id),
|
|
@@ -369,11 +479,11 @@ def _canonical_argv(inp: PrepareInputs, ctx: dict) -> list[str]:
|
|
|
369
479
|
("--approved-plan", inp.approved_plan_path),
|
|
370
480
|
("--clarification-response", inp.clarification_response_path),
|
|
371
481
|
("--workers", workers),
|
|
372
|
-
("--lead-model", inp.lead_model or ctx.get("
|
|
373
|
-
("--claude-model", inp.claude_model or ctx.get("
|
|
374
|
-
("--codex-model", inp.codex_model or ctx.get("
|
|
375
|
-
("--gemini-model", inp.gemini_model or ctx.get("
|
|
376
|
-
("--report-writer-model", inp.report_writer_model or ctx.get("
|
|
482
|
+
("--lead-model", inp.lead_model or ctx.get("LEAD_MODEL", "")),
|
|
483
|
+
("--claude-model", inp.claude_model or ctx.get("CLAUDE_WORKER_MODEL", "")),
|
|
484
|
+
("--codex-model", inp.codex_model or ctx.get("CODEX_WORKER_MODEL", "")),
|
|
485
|
+
("--gemini-model", inp.gemini_model or ctx.get("GEMINI_WORKER_MODEL", "")),
|
|
486
|
+
("--report-writer-model", inp.report_writer_model or ctx.get("REPORT_WRITER_MODEL", "")),
|
|
377
487
|
("--executor", inp.executor or ctx.get("EXECUTOR_PROVIDER", "")),
|
|
378
488
|
("--related-tasks", inp.related_tasks_raw),
|
|
379
489
|
("--work-category", inp.work_category),
|
|
@@ -471,17 +581,25 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
471
581
|
)
|
|
472
582
|
if inp.approve_plan_ack:
|
|
473
583
|
# 사용자가 직접 `--approve` 를 입력한 행위 자체를 승인 의사로 모델링한다.
|
|
474
|
-
#
|
|
475
|
-
#
|
|
584
|
+
# 파일의 frontmatter approved 를 true 로 toggle 한 뒤 동일한 검증
|
|
585
|
+
# 경로(`_validate_approved_plan`) 를 그대로 통과시킨다.
|
|
476
586
|
_apply_cli_approval(inp.approved_plan_path)
|
|
477
587
|
_validate_approved_plan(inp.approved_plan_path)
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
588
|
+
_validate_stage_structure(inp.approved_plan_path)
|
|
589
|
+
ctx_stage_map = _parse_stage_map_into_ctx(inp.approved_plan_path)
|
|
590
|
+
else:
|
|
591
|
+
if inp.approve_plan_ack:
|
|
592
|
+
# implementation 외 task-type 에서 `--approve` 는 의미가 없다. 사용자에게
|
|
593
|
+
# 정확한 시점을 알려주기 위해 조용히 무시하지 않고 즉시 거부한다.
|
|
594
|
+
raise PrepareError(
|
|
595
|
+
"--approve is only meaningful with --task-type implementation "
|
|
596
|
+
"and --approved-plan <path>"
|
|
597
|
+
)
|
|
598
|
+
if inp.stage != "auto":
|
|
599
|
+
raise PrepareError(
|
|
600
|
+
f"--stage is only meaningful with --task-type implementation; "
|
|
601
|
+
f"got {inp.task_type}"
|
|
602
|
+
)
|
|
485
603
|
if inp.clarification_response_path and not Path(inp.clarification_response_path).is_file():
|
|
486
604
|
raise PrepareError(
|
|
487
605
|
f"clarification response file not found: {inp.clarification_response_path}"
|
|
@@ -660,7 +778,7 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
660
778
|
profile_content = _expand_profile_includes(profile_file)
|
|
661
779
|
review_material = build_analysis_material(inp.brief_path, inp.directive)
|
|
662
780
|
related_items = resolve_related_tasks(
|
|
663
|
-
task_manifest_path=Path(ctx["
|
|
781
|
+
task_manifest_path=Path(ctx["TASK_MANIFEST_PATH"]),
|
|
664
782
|
raw_related=inp.related_tasks_raw,
|
|
665
783
|
)
|
|
666
784
|
related_tasks_json_str = json.dumps(related_items, ensure_ascii=False)
|
|
@@ -686,25 +804,24 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
686
804
|
|
|
687
805
|
# ---- assemble full ctx (the values render functions expect) ----
|
|
688
806
|
ctx.update({
|
|
689
|
-
"
|
|
690
|
-
"
|
|
807
|
+
"ANALYSIS_PROFILE": inp.task_type,
|
|
808
|
+
"RECOMMENDED_ANALYSERS": selected_reviewers,
|
|
691
809
|
"PR_TEMPLATE_PATH": pr_template_path_str,
|
|
692
810
|
"PR_TEMPLATE_SOURCE": pr_template_source,
|
|
693
811
|
"CLAUDE_SESSION_ID": claude_session_id,
|
|
694
812
|
"CLARIFICATION_RESPONSE_PATH": inp.clarification_response_path,
|
|
695
|
-
"CLARIFICATION_RESPONSE_FILE": inp.clarification_response_path,
|
|
696
813
|
"CLARIFICATION_RESPONSE_RELATIVE_PATH": clarification_relative,
|
|
697
814
|
"BRIEF_FILE_PATH": str(inp.brief_path),
|
|
698
815
|
"BRIEF_RELATIVE_PATH": brief_relative,
|
|
699
|
-
"
|
|
816
|
+
"LEAD_MODEL": lead.display,
|
|
700
817
|
"LEAD_MODEL_EXECUTION_VALUE": lead.execution,
|
|
701
|
-
"
|
|
818
|
+
"CLAUDE_WORKER_MODEL": cw.display,
|
|
702
819
|
"CLAUDE_WORKER_MODEL_EXECUTION_VALUE": cw.execution,
|
|
703
|
-
"
|
|
820
|
+
"CODEX_WORKER_MODEL": co.display,
|
|
704
821
|
"CODEX_WORKER_MODEL_EXECUTION_VALUE": co.execution,
|
|
705
|
-
"
|
|
822
|
+
"GEMINI_WORKER_MODEL": ge.display,
|
|
706
823
|
"GEMINI_WORKER_MODEL_EXECUTION_VALUE": ge.execution,
|
|
707
|
-
"
|
|
824
|
+
"REPORT_WRITER_MODEL": rw.display,
|
|
708
825
|
"REPORT_WRITER_MODEL_EXECUTION_VALUE": rw.execution,
|
|
709
826
|
"EXECUTOR_PROVIDER": executor_provider,
|
|
710
827
|
"EXECUTOR_DISPLAY_NAME": executor_display_name,
|
|
@@ -725,12 +842,39 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
725
842
|
"OKSTRA_VERSION": installed_version(),
|
|
726
843
|
**workflow_state,
|
|
727
844
|
})
|
|
845
|
+
if inp.task_type == "implementation":
|
|
846
|
+
ctx["parsed_stage_map"] = ctx_stage_map
|
|
847
|
+
# Resolve effective stage and append `started` row to consumers.jsonl
|
|
848
|
+
from .consumers import read_consumers, append_consumer
|
|
849
|
+
import datetime as _dt
|
|
850
|
+
plan_run_root = Path(inp.approved_plan_path).resolve().parents[1]
|
|
851
|
+
consumed = read_consumers(plan_run_root)
|
|
852
|
+
done_stages = {r["stage"] for r in consumed if r.get("status") == "done"}
|
|
853
|
+
effective = _resolve_effective_stage(
|
|
854
|
+
ctx["parsed_stage_map"], done_stages, inp.stage
|
|
855
|
+
)
|
|
856
|
+
ctx["effective_stage"] = effective
|
|
857
|
+
inp.stage = str(effective)
|
|
858
|
+
print(f"selected stage: {inp.stage}", file=sys.stdout)
|
|
859
|
+
head_proc = _subprocess.run(
|
|
860
|
+
["git", "rev-parse", "HEAD"],
|
|
861
|
+
cwd=inp.project_root, capture_output=True, text=True,
|
|
862
|
+
)
|
|
863
|
+
head_sha = head_proc.stdout.strip() if head_proc.returncode == 0 else ""
|
|
864
|
+
append_consumer(
|
|
865
|
+
plan_run_root,
|
|
866
|
+
impl_task_key=ctx["TASK_KEY"],
|
|
867
|
+
stage=effective,
|
|
868
|
+
status="started",
|
|
869
|
+
started_at=_dt.datetime.now(_dt.timezone.utc).isoformat(),
|
|
870
|
+
head_commit=head_sha,
|
|
871
|
+
)
|
|
728
872
|
|
|
729
873
|
# ---- prepare directories + cleanup ----
|
|
730
874
|
_ensure_task_directories(ctx)
|
|
731
875
|
_migrate_legacy_run_artifacts(ctx)
|
|
732
876
|
cleanup_obsolete_generated_docs(
|
|
733
|
-
project_root=project_root, instruction_set_dir=Path(ctx["
|
|
877
|
+
project_root=project_root, instruction_set_dir=Path(ctx["INSTRUCTION_SET_PATH"]),
|
|
734
878
|
)
|
|
735
879
|
# Always materialise the resume command script. Even in --render-only
|
|
736
880
|
# preparation flows the user (or a later non-interactive runner) may
|
|
@@ -738,12 +882,18 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
738
882
|
# leaves runs/<phase>/sessions/ empty and the manifest pointing at a
|
|
739
883
|
# path that does not exist.
|
|
740
884
|
write_claude_resume_command_file(
|
|
741
|
-
resume_command_path=Path(ctx["
|
|
742
|
-
project_root=project_root,
|
|
885
|
+
resume_command_path=Path(ctx["CLAUDE_RESUME_COMMAND_PATH"]),
|
|
886
|
+
project_root=project_root,
|
|
887
|
+
claude_session_id=claude_session_id,
|
|
888
|
+
task_key=ctx["TASK_KEY"],
|
|
889
|
+
task_type=ctx["TASK_TYPE"],
|
|
890
|
+
phase_state=ctx["CURRENT_RUN_STATUS"],
|
|
891
|
+
worker_prompts_dir_relative=ctx["RUN_PROMPTS_RELATIVE_PATH"],
|
|
892
|
+
prompt_seq=ctx["RUN_PROMPTS_SEQ"],
|
|
743
893
|
)
|
|
744
894
|
|
|
745
895
|
# ---- write instruction-set scaffolding ----
|
|
746
|
-
instruction_set = Path(ctx["
|
|
896
|
+
instruction_set = Path(ctx["INSTRUCTION_SET_PATH"])
|
|
747
897
|
instruction_set.mkdir(parents=True, exist_ok=True)
|
|
748
898
|
profile_rendered = profile_content
|
|
749
899
|
for key in (
|
|
@@ -772,10 +922,17 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
772
922
|
render_reference_expectations(
|
|
773
923
|
str(inp.brief_path), str(instruction_set / "reference-expectations.md"), ctx,
|
|
774
924
|
)
|
|
775
|
-
|
|
776
|
-
|
|
925
|
+
# inject populates ctx with compute + default tokens consumed by the lead
|
|
926
|
+
# prompt render below (claude-execution-prompt.md). The final-report
|
|
927
|
+
# template render is effectively a copy (Jinja2 `{{ var }}` syntax does
|
|
928
|
+
# not match `_TOKEN_RE`); routed through render_template_with_ctx for SOT
|
|
929
|
+
# consistency.
|
|
930
|
+
inject_lead_prompt_computed_tokens(ctx)
|
|
931
|
+
apply_lead_prompt_defaults(ctx)
|
|
932
|
+
render_template_with_ctx(
|
|
933
|
+
str(final_report_template), ctx["FINAL_REPORT_TEMPLATE_PATH"], ctx,
|
|
777
934
|
)
|
|
778
|
-
|
|
935
|
+
render_template_with_ctx(
|
|
779
936
|
str(prompt_template), str(instruction_set / "claude-execution-prompt.md"), ctx,
|
|
780
937
|
)
|
|
781
938
|
prompt_text = (instruction_set / "claude-execution-prompt.md").read_text(encoding="utf-8")
|
|
@@ -810,12 +967,12 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
810
967
|
if inp.render_only:
|
|
811
968
|
ctx["CURRENT_TASK_STATUS"] = "instruction-set-generated"
|
|
812
969
|
ctx["CURRENT_RUN_STATUS"] = "prepared"
|
|
813
|
-
ctx["LATEST_REPORT_PATH"] = ctx["
|
|
970
|
+
ctx["LATEST_REPORT_PATH"] = ctx["FINAL_REPORT_PATH"]
|
|
814
971
|
ctx["LATEST_REPORT_RELATIVE_PATH"] = ctx["FINAL_REPORT_RELATIVE_PATH"]
|
|
815
972
|
else:
|
|
816
973
|
ctx["CURRENT_TASK_STATUS"] = "claude-session-started"
|
|
817
974
|
ctx["CURRENT_RUN_STATUS"] = "in-progress"
|
|
818
|
-
ctx["LATEST_REPORT_PATH"] = ctx["
|
|
975
|
+
ctx["LATEST_REPORT_PATH"] = ctx["FINAL_REPORT_PATH"]
|
|
819
976
|
ctx["LATEST_REPORT_RELATIVE_PATH"] = ctx["FINAL_REPORT_RELATIVE_PATH"]
|
|
820
977
|
ctx.update(compute_workflow_state(
|
|
821
978
|
task_type=inp.task_type,
|
|
@@ -824,11 +981,11 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
824
981
|
render_only=inp.render_only,
|
|
825
982
|
work_category=inp.work_category,
|
|
826
983
|
))
|
|
827
|
-
render_team_state(ctx["
|
|
828
|
-
render_task_manifest(ctx["
|
|
829
|
-
render_task_index(str(task_index_template), ctx["
|
|
830
|
-
render_run_manifest(ctx["
|
|
831
|
-
render_timeline(ctx["
|
|
984
|
+
render_team_state(ctx["TEAM_STATE_PATH"], ctx)
|
|
985
|
+
render_task_manifest(ctx["TASK_MANIFEST_PATH"], ctx)
|
|
986
|
+
render_task_index(str(task_index_template), ctx["TASK_INDEX_PATH"], ctx)
|
|
987
|
+
render_run_manifest(ctx["RUN_MANIFEST_PATH"], ctx)
|
|
988
|
+
render_timeline(ctx["TIMELINE_PATH"], ctx)
|
|
832
989
|
render_task_catalog_discovery(ctx["OKSTRA_TASK_CATALOG_FILE"], ctx)
|
|
833
990
|
render_latest_task_discovery(ctx["OKSTRA_LATEST_TASK_FILE"], ctx)
|
|
834
991
|
|
|
@@ -867,6 +1024,31 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
867
1024
|
file=__import__("sys").stderr,
|
|
868
1025
|
)
|
|
869
1026
|
|
|
1027
|
+
try:
|
|
1028
|
+
claude_md_link = ensure_project_claude_md(project_root=Path(inp.project_root))
|
|
1029
|
+
except ClaudeMdLinkError as exc:
|
|
1030
|
+
print(
|
|
1031
|
+
f"okstra-claude-md: failed to provision project CLAUDE.md import — "
|
|
1032
|
+
f"Claude Code sessions in this project will not auto-load okstra guidance. ({exc})",
|
|
1033
|
+
file=__import__("sys").stderr,
|
|
1034
|
+
)
|
|
1035
|
+
else:
|
|
1036
|
+
if claude_md_link is None:
|
|
1037
|
+
print(
|
|
1038
|
+
"okstra-claude-md: ~/.okstra/templates/okstra.CLAUDE.md missing — "
|
|
1039
|
+
"re-run 'npx okstra@latest install' to provision the symlink target.",
|
|
1040
|
+
file=__import__("sys").stderr,
|
|
1041
|
+
)
|
|
1042
|
+
|
|
1043
|
+
try:
|
|
1044
|
+
ensure_project_agents_md(project_root=Path(inp.project_root))
|
|
1045
|
+
except AgentsMdLinkError as exc:
|
|
1046
|
+
print(
|
|
1047
|
+
f"okstra-agents-md: failed to provision <PROJECT>/AGENTS.md symlink — "
|
|
1048
|
+
f"codex / aider sessions in this project will not auto-load okstra guidance. ({exc})",
|
|
1049
|
+
file=__import__("sys").stderr,
|
|
1050
|
+
)
|
|
1051
|
+
|
|
870
1052
|
return PrepareOutputs(
|
|
871
1053
|
ctx=ctx,
|
|
872
1054
|
prompt_text=prompt_text,
|
|
@@ -898,14 +1080,22 @@ def main(argv: list[str]) -> int:
|
|
|
898
1080
|
p.add_argument("--executor", default="")
|
|
899
1081
|
p.add_argument("--related-tasks", default="", dest="related_tasks_raw")
|
|
900
1082
|
p.add_argument("--approved-plan", default="", dest="approved_plan_path")
|
|
1083
|
+
p.add_argument(
|
|
1084
|
+
"--stage", default="auto", dest="stage",
|
|
1085
|
+
help=(
|
|
1086
|
+
"implementation task only. Which Stage Map entry to execute. "
|
|
1087
|
+
"'auto' (default) = lowest-numbered stage whose depends-on are all "
|
|
1088
|
+
"consumers.jsonl status:done. Numeric '<N>' = force that stage."
|
|
1089
|
+
),
|
|
1090
|
+
)
|
|
901
1091
|
p.add_argument(
|
|
902
1092
|
"--approve",
|
|
903
1093
|
action="store_true",
|
|
904
1094
|
dest="approve_plan_ack",
|
|
905
1095
|
help=(
|
|
906
1096
|
"Treat the CLI invocation itself as the plan approval signal. "
|
|
907
|
-
"Flips
|
|
908
|
-
"and appends an audit line."
|
|
1097
|
+
"Flips `approved: false` to `approved: true` in the --approved-plan file's "
|
|
1098
|
+
"YAML frontmatter and appends an audit line."
|
|
909
1099
|
),
|
|
910
1100
|
)
|
|
911
1101
|
p.add_argument("--clarification-response", default="", dest="clarification_response_path")
|
|
@@ -992,6 +1182,7 @@ def main(argv: list[str]) -> int:
|
|
|
992
1182
|
work_category=args.work_category,
|
|
993
1183
|
base_ref=args.base_ref,
|
|
994
1184
|
approved_plan_path=args.approved_plan_path,
|
|
1185
|
+
stage=args.stage,
|
|
995
1186
|
clarification_response_path=clarification_abs,
|
|
996
1187
|
pr_template_path=args.pr_template_path,
|
|
997
1188
|
render_only=args.render_only,
|
|
@@ -1010,18 +1201,18 @@ def main(argv: list[str]) -> int:
|
|
|
1010
1201
|
print(f"okstra task root: {ctx['TASK_ROOT']}")
|
|
1011
1202
|
print(f"okstra latest task discovery file: {ctx['OKSTRA_LATEST_TASK_FILE']}")
|
|
1012
1203
|
print(f"okstra task catalog file: {ctx['OKSTRA_TASK_CATALOG_FILE']}")
|
|
1013
|
-
print(f"okstra instruction-set: {ctx['
|
|
1204
|
+
print(f"okstra instruction-set: {ctx['INSTRUCTION_SET_PATH']}")
|
|
1014
1205
|
print(f"okstra reference expectations: {ctx['REFERENCE_EXPECTATIONS_FILE']}")
|
|
1015
|
-
print(f"okstra final report template: {ctx['
|
|
1206
|
+
print(f"okstra final report template: {ctx['FINAL_REPORT_TEMPLATE_PATH']}")
|
|
1016
1207
|
if inputs.render_only:
|
|
1017
1208
|
print()
|
|
1018
1209
|
print(out.prompt_text, end="")
|
|
1019
1210
|
else:
|
|
1020
1211
|
print(f"okstra current run dir: {ctx['RUN_DIR']}")
|
|
1021
|
-
print(f"final report path: {ctx['
|
|
1022
|
-
print(f"lead model: {ctx['
|
|
1212
|
+
print(f"final report path: {ctx['FINAL_REPORT_PATH']}")
|
|
1213
|
+
print(f"lead model: {ctx['LEAD_MODEL']}")
|
|
1023
1214
|
print(f"claude session id: {ctx['CLAUDE_SESSION_ID']}")
|
|
1024
|
-
print(f"resume command file: {ctx['
|
|
1215
|
+
print(f"resume command file: {ctx['CLAUDE_RESUME_COMMAND_PATH']}")
|
|
1025
1216
|
print("launch mode: interactive Claude handoff")
|
|
1026
1217
|
print(f"claude working directory: {ctx['PROJECT_ROOT']}")
|
|
1027
1218
|
print()
|
|
@@ -1031,7 +1222,7 @@ def main(argv: list[str]) -> int:
|
|
|
1031
1222
|
"claudeSessionId": ctx["CLAUDE_SESSION_ID"],
|
|
1032
1223
|
"leadModelExecutionValue": ctx["LEAD_MODEL_EXECUTION_VALUE"],
|
|
1033
1224
|
"projectRoot": ctx["PROJECT_ROOT"],
|
|
1034
|
-
"promptFile": str(Path(ctx["
|
|
1225
|
+
"promptFile": str(Path(ctx["INSTRUCTION_SET_PATH"]) / "claude-execution-prompt.md"),
|
|
1035
1226
|
}
|
|
1036
1227
|
print(f"__OKSTRA_LAUNCH__ {json.dumps(machine)}")
|
|
1037
1228
|
return 0
|