okstra 0.34.1 → 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 +12 -2
- 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
|
@@ -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 로 토글.
|
|
282
|
+
|
|
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.
|
|
182
290
|
|
|
183
|
-
|
|
184
|
-
runtime audit line). Idempotent: if the file already carries a valid
|
|
185
|
-
approval marker, no edits are written and `"already-approved"` is returned.
|
|
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,12 @@ 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["
|
|
885
|
+
resume_command_path=Path(ctx["CLAUDE_RESUME_COMMAND_PATH"]),
|
|
742
886
|
project_root=project_root, claude_session_id=claude_session_id,
|
|
743
887
|
)
|
|
744
888
|
|
|
745
889
|
# ---- write instruction-set scaffolding ----
|
|
746
|
-
instruction_set = Path(ctx["
|
|
890
|
+
instruction_set = Path(ctx["INSTRUCTION_SET_PATH"])
|
|
747
891
|
instruction_set.mkdir(parents=True, exist_ok=True)
|
|
748
892
|
profile_rendered = profile_content
|
|
749
893
|
for key in (
|
|
@@ -772,10 +916,17 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
772
916
|
render_reference_expectations(
|
|
773
917
|
str(inp.brief_path), str(instruction_set / "reference-expectations.md"), ctx,
|
|
774
918
|
)
|
|
775
|
-
|
|
776
|
-
|
|
919
|
+
# inject populates ctx with compute + default tokens consumed by the lead
|
|
920
|
+
# prompt render below (claude-execution-prompt.md). The final-report
|
|
921
|
+
# template render is effectively a copy (Jinja2 `{{ var }}` syntax does
|
|
922
|
+
# not match `_TOKEN_RE`); routed through render_template_with_ctx for SOT
|
|
923
|
+
# consistency.
|
|
924
|
+
inject_lead_prompt_computed_tokens(ctx)
|
|
925
|
+
apply_lead_prompt_defaults(ctx)
|
|
926
|
+
render_template_with_ctx(
|
|
927
|
+
str(final_report_template), ctx["FINAL_REPORT_TEMPLATE_PATH"], ctx,
|
|
777
928
|
)
|
|
778
|
-
|
|
929
|
+
render_template_with_ctx(
|
|
779
930
|
str(prompt_template), str(instruction_set / "claude-execution-prompt.md"), ctx,
|
|
780
931
|
)
|
|
781
932
|
prompt_text = (instruction_set / "claude-execution-prompt.md").read_text(encoding="utf-8")
|
|
@@ -810,12 +961,12 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
810
961
|
if inp.render_only:
|
|
811
962
|
ctx["CURRENT_TASK_STATUS"] = "instruction-set-generated"
|
|
812
963
|
ctx["CURRENT_RUN_STATUS"] = "prepared"
|
|
813
|
-
ctx["LATEST_REPORT_PATH"] = ctx["
|
|
964
|
+
ctx["LATEST_REPORT_PATH"] = ctx["FINAL_REPORT_PATH"]
|
|
814
965
|
ctx["LATEST_REPORT_RELATIVE_PATH"] = ctx["FINAL_REPORT_RELATIVE_PATH"]
|
|
815
966
|
else:
|
|
816
967
|
ctx["CURRENT_TASK_STATUS"] = "claude-session-started"
|
|
817
968
|
ctx["CURRENT_RUN_STATUS"] = "in-progress"
|
|
818
|
-
ctx["LATEST_REPORT_PATH"] = ctx["
|
|
969
|
+
ctx["LATEST_REPORT_PATH"] = ctx["FINAL_REPORT_PATH"]
|
|
819
970
|
ctx["LATEST_REPORT_RELATIVE_PATH"] = ctx["FINAL_REPORT_RELATIVE_PATH"]
|
|
820
971
|
ctx.update(compute_workflow_state(
|
|
821
972
|
task_type=inp.task_type,
|
|
@@ -824,11 +975,11 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
824
975
|
render_only=inp.render_only,
|
|
825
976
|
work_category=inp.work_category,
|
|
826
977
|
))
|
|
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["
|
|
978
|
+
render_team_state(ctx["TEAM_STATE_PATH"], ctx)
|
|
979
|
+
render_task_manifest(ctx["TASK_MANIFEST_PATH"], ctx)
|
|
980
|
+
render_task_index(str(task_index_template), ctx["TASK_INDEX_PATH"], ctx)
|
|
981
|
+
render_run_manifest(ctx["RUN_MANIFEST_PATH"], ctx)
|
|
982
|
+
render_timeline(ctx["TIMELINE_PATH"], ctx)
|
|
832
983
|
render_task_catalog_discovery(ctx["OKSTRA_TASK_CATALOG_FILE"], ctx)
|
|
833
984
|
render_latest_task_discovery(ctx["OKSTRA_LATEST_TASK_FILE"], ctx)
|
|
834
985
|
|
|
@@ -867,6 +1018,31 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
867
1018
|
file=__import__("sys").stderr,
|
|
868
1019
|
)
|
|
869
1020
|
|
|
1021
|
+
try:
|
|
1022
|
+
claude_md_link = ensure_project_claude_md(project_root=Path(inp.project_root))
|
|
1023
|
+
except ClaudeMdLinkError as exc:
|
|
1024
|
+
print(
|
|
1025
|
+
f"okstra-claude-md: failed to provision project CLAUDE.md import — "
|
|
1026
|
+
f"Claude Code sessions in this project will not auto-load okstra guidance. ({exc})",
|
|
1027
|
+
file=__import__("sys").stderr,
|
|
1028
|
+
)
|
|
1029
|
+
else:
|
|
1030
|
+
if claude_md_link is None:
|
|
1031
|
+
print(
|
|
1032
|
+
"okstra-claude-md: ~/.okstra/templates/okstra.CLAUDE.md missing — "
|
|
1033
|
+
"re-run 'npx okstra@latest install' to provision the symlink target.",
|
|
1034
|
+
file=__import__("sys").stderr,
|
|
1035
|
+
)
|
|
1036
|
+
|
|
1037
|
+
try:
|
|
1038
|
+
ensure_project_agents_md(project_root=Path(inp.project_root))
|
|
1039
|
+
except AgentsMdLinkError as exc:
|
|
1040
|
+
print(
|
|
1041
|
+
f"okstra-agents-md: failed to provision <PROJECT>/AGENTS.md symlink — "
|
|
1042
|
+
f"codex / aider sessions in this project will not auto-load okstra guidance. ({exc})",
|
|
1043
|
+
file=__import__("sys").stderr,
|
|
1044
|
+
)
|
|
1045
|
+
|
|
870
1046
|
return PrepareOutputs(
|
|
871
1047
|
ctx=ctx,
|
|
872
1048
|
prompt_text=prompt_text,
|
|
@@ -898,14 +1074,22 @@ def main(argv: list[str]) -> int:
|
|
|
898
1074
|
p.add_argument("--executor", default="")
|
|
899
1075
|
p.add_argument("--related-tasks", default="", dest="related_tasks_raw")
|
|
900
1076
|
p.add_argument("--approved-plan", default="", dest="approved_plan_path")
|
|
1077
|
+
p.add_argument(
|
|
1078
|
+
"--stage", default="auto", dest="stage",
|
|
1079
|
+
help=(
|
|
1080
|
+
"implementation task only. Which Stage Map entry to execute. "
|
|
1081
|
+
"'auto' (default) = lowest-numbered stage whose depends-on are all "
|
|
1082
|
+
"consumers.jsonl status:done. Numeric '<N>' = force that stage."
|
|
1083
|
+
),
|
|
1084
|
+
)
|
|
901
1085
|
p.add_argument(
|
|
902
1086
|
"--approve",
|
|
903
1087
|
action="store_true",
|
|
904
1088
|
dest="approve_plan_ack",
|
|
905
1089
|
help=(
|
|
906
1090
|
"Treat the CLI invocation itself as the plan approval signal. "
|
|
907
|
-
"Flips
|
|
908
|
-
"and appends an audit line."
|
|
1091
|
+
"Flips `approved: false` to `approved: true` in the --approved-plan file's "
|
|
1092
|
+
"YAML frontmatter and appends an audit line."
|
|
909
1093
|
),
|
|
910
1094
|
)
|
|
911
1095
|
p.add_argument("--clarification-response", default="", dest="clarification_response_path")
|
|
@@ -992,6 +1176,7 @@ def main(argv: list[str]) -> int:
|
|
|
992
1176
|
work_category=args.work_category,
|
|
993
1177
|
base_ref=args.base_ref,
|
|
994
1178
|
approved_plan_path=args.approved_plan_path,
|
|
1179
|
+
stage=args.stage,
|
|
995
1180
|
clarification_response_path=clarification_abs,
|
|
996
1181
|
pr_template_path=args.pr_template_path,
|
|
997
1182
|
render_only=args.render_only,
|
|
@@ -1010,18 +1195,18 @@ def main(argv: list[str]) -> int:
|
|
|
1010
1195
|
print(f"okstra task root: {ctx['TASK_ROOT']}")
|
|
1011
1196
|
print(f"okstra latest task discovery file: {ctx['OKSTRA_LATEST_TASK_FILE']}")
|
|
1012
1197
|
print(f"okstra task catalog file: {ctx['OKSTRA_TASK_CATALOG_FILE']}")
|
|
1013
|
-
print(f"okstra instruction-set: {ctx['
|
|
1198
|
+
print(f"okstra instruction-set: {ctx['INSTRUCTION_SET_PATH']}")
|
|
1014
1199
|
print(f"okstra reference expectations: {ctx['REFERENCE_EXPECTATIONS_FILE']}")
|
|
1015
|
-
print(f"okstra final report template: {ctx['
|
|
1200
|
+
print(f"okstra final report template: {ctx['FINAL_REPORT_TEMPLATE_PATH']}")
|
|
1016
1201
|
if inputs.render_only:
|
|
1017
1202
|
print()
|
|
1018
1203
|
print(out.prompt_text, end="")
|
|
1019
1204
|
else:
|
|
1020
1205
|
print(f"okstra current run dir: {ctx['RUN_DIR']}")
|
|
1021
|
-
print(f"final report path: {ctx['
|
|
1022
|
-
print(f"lead model: {ctx['
|
|
1206
|
+
print(f"final report path: {ctx['FINAL_REPORT_PATH']}")
|
|
1207
|
+
print(f"lead model: {ctx['LEAD_MODEL']}")
|
|
1023
1208
|
print(f"claude session id: {ctx['CLAUDE_SESSION_ID']}")
|
|
1024
|
-
print(f"resume command file: {ctx['
|
|
1209
|
+
print(f"resume command file: {ctx['CLAUDE_RESUME_COMMAND_PATH']}")
|
|
1025
1210
|
print("launch mode: interactive Claude handoff")
|
|
1026
1211
|
print(f"claude working directory: {ctx['PROJECT_ROOT']}")
|
|
1027
1212
|
print()
|
|
@@ -1031,7 +1216,7 @@ def main(argv: list[str]) -> int:
|
|
|
1031
1216
|
"claudeSessionId": ctx["CLAUDE_SESSION_ID"],
|
|
1032
1217
|
"leadModelExecutionValue": ctx["LEAD_MODEL_EXECUTION_VALUE"],
|
|
1033
1218
|
"projectRoot": ctx["PROJECT_ROOT"],
|
|
1034
|
-
"promptFile": str(Path(ctx["
|
|
1219
|
+
"promptFile": str(Path(ctx["INSTRUCTION_SET_PATH"]) / "claude-execution-prompt.md"),
|
|
1035
1220
|
}
|
|
1036
1221
|
print(f"__OKSTRA_LAUNCH__ {json.dumps(machine)}")
|
|
1037
1222
|
return 0
|