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
|
@@ -32,7 +32,10 @@ from okstra_ctl.models import (
|
|
|
32
32
|
resolve_model_metadata,
|
|
33
33
|
)
|
|
34
34
|
from okstra_ctl.pr_template import PrTemplateError, resolve_pr_template_path
|
|
35
|
-
from okstra_ctl.run import
|
|
35
|
+
from okstra_ctl.run import (
|
|
36
|
+
APPROVED_FRONTMATTER_PATTERN,
|
|
37
|
+
_extract_frontmatter_block,
|
|
38
|
+
)
|
|
36
39
|
from okstra_ctl.workers import (
|
|
37
40
|
ALLOWED_WORKERS,
|
|
38
41
|
WorkersError,
|
|
@@ -54,11 +57,12 @@ from okstra_project.state import (
|
|
|
54
57
|
|
|
55
58
|
TASK_TYPES: list[tuple[str, str]] = [
|
|
56
59
|
("requirements-discovery", "Classify request and route to next safe phase"),
|
|
60
|
+
("improvement-discovery", "Find improvement candidates within a codebase scope and lens whitelist"),
|
|
57
61
|
("error-analysis", "Evidence-based root-cause analysis (no code changes)"),
|
|
58
62
|
("implementation-planning", "Plan options + request user approval"),
|
|
59
63
|
("implementation", "Execute approved plan (requires approved final-report)"),
|
|
60
64
|
("final-verification", "Acceptance + residual-risk review"),
|
|
61
|
-
("release-handoff", "Drive commit/push/PR
|
|
65
|
+
("release-handoff", "Drive commit/push/PR — reuse the implementation task-key (new keys fail the empty-commits gate)"),
|
|
62
66
|
]
|
|
63
67
|
TASK_TYPE_VALUES = [tt for tt, _ in TASK_TYPES]
|
|
64
68
|
|
|
@@ -168,6 +172,7 @@ S_BASE_REF_PICK = "base_ref_pick"
|
|
|
168
172
|
S_BASE_REF_TEXT = "base_ref_text"
|
|
169
173
|
S_APPROVED_PLAN_PICK = "approved_plan_pick"
|
|
170
174
|
S_APPROVED_PLAN = "approved_plan"
|
|
175
|
+
S_STAGE_PICK = "stage_pick"
|
|
171
176
|
S_EXECUTOR = "executor"
|
|
172
177
|
S_DEFAULTS_OR_CUSTOM = "defaults_or_custom"
|
|
173
178
|
S_WORKERS_OVERRIDE = "workers_override"
|
|
@@ -230,6 +235,7 @@ class WizardState:
|
|
|
230
235
|
# impl extras
|
|
231
236
|
approved_plan_path: str = ""
|
|
232
237
|
approved_plan_pending_text: bool = False
|
|
238
|
+
selected_stage: str = "auto"
|
|
233
239
|
executor: str = ""
|
|
234
240
|
|
|
235
241
|
# customize
|
|
@@ -330,10 +336,24 @@ def _require_file(path_str: str, project_root: Path, label: str) -> Path:
|
|
|
330
336
|
def _validate_approved_plan(path_str: str, project_root: Path) -> Path:
|
|
331
337
|
p = _require_file(path_str, project_root, "approved plan")
|
|
332
338
|
body = p.read_text(encoding="utf-8", errors="replace")
|
|
333
|
-
|
|
339
|
+
frontmatter = _extract_frontmatter_block(body)
|
|
340
|
+
if frontmatter is None:
|
|
341
|
+
raise WizardError(
|
|
342
|
+
f"approved plan has no YAML frontmatter block: {p}\n"
|
|
343
|
+
" expected the report to begin with `---\\n...\\n---\\n`."
|
|
344
|
+
)
|
|
345
|
+
m = APPROVED_FRONTMATTER_PATTERN.search(frontmatter)
|
|
346
|
+
if not m:
|
|
347
|
+
raise WizardError(
|
|
348
|
+
f"approved plan frontmatter has no `approved:` field: {p}\n"
|
|
349
|
+
" expected `approved: true` (report-writer emits `approved: false` "
|
|
350
|
+
"by default; flip it once approved)."
|
|
351
|
+
)
|
|
352
|
+
if m.group(1).lower() != "true":
|
|
334
353
|
raise WizardError(
|
|
335
|
-
f"approved plan
|
|
336
|
-
|
|
354
|
+
f"approved plan is not yet approved (frontmatter `approved: {m.group(1)}`): {p}\n"
|
|
355
|
+
" edit the report and change the line to `approved: true`, or re-run "
|
|
356
|
+
"okstra with `--approve` to flip it from the CLI."
|
|
337
357
|
)
|
|
338
358
|
return p
|
|
339
359
|
|
|
@@ -411,6 +431,122 @@ def _load_profile_optional_workers(
|
|
|
411
431
|
return resolve_optional_workers(_profile_path(workspace_root, task_type))
|
|
412
432
|
|
|
413
433
|
|
|
434
|
+
# --------------------------------------------------------------------------- #
|
|
435
|
+
# Wizard prompt JSON SOT (Phase A1)
|
|
436
|
+
# --------------------------------------------------------------------------- #
|
|
437
|
+
|
|
438
|
+
_WIZARD_ROOT_CACHE: dict[str, dict] = {}
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def _load_wizard_root(workspace_root: str) -> dict:
|
|
442
|
+
"""Load and cache the full wizard prompts JSON root for the given workspace_root.
|
|
443
|
+
|
|
444
|
+
Returns the entire parsed JSON object (with `schema_version`, `locale`, `steps`,
|
|
445
|
+
optional `confirmation` etc. at top level). Callers that only need the steps
|
|
446
|
+
dict should index `["steps"]` on the return value; `_msg` reads top-level
|
|
447
|
+
sections like `confirmation`.
|
|
448
|
+
"""
|
|
449
|
+
if workspace_root in _WIZARD_ROOT_CACHE:
|
|
450
|
+
return _WIZARD_ROOT_CACHE[workspace_root]
|
|
451
|
+
path = Path(workspace_root) / "prompts" / "wizard" / "prompts.ko.json"
|
|
452
|
+
if not path.is_file():
|
|
453
|
+
raise WizardError(
|
|
454
|
+
f"wizard prompt SOT not found: {path}. "
|
|
455
|
+
"Re-run `okstra install` or check the workspace_root."
|
|
456
|
+
)
|
|
457
|
+
try:
|
|
458
|
+
raw = json.loads(path.read_text(encoding="utf-8"))
|
|
459
|
+
except json.JSONDecodeError as exc:
|
|
460
|
+
raise WizardError(f"wizard prompt JSON malformed: {path}: {exc}") from exc
|
|
461
|
+
if not isinstance(raw, dict):
|
|
462
|
+
raise WizardError(f"wizard prompt JSON root must be an object: {path}")
|
|
463
|
+
if raw.get("schema_version") != 1:
|
|
464
|
+
raise WizardError(
|
|
465
|
+
f"wizard prompt schema_version mismatch (expected 1): {path}"
|
|
466
|
+
)
|
|
467
|
+
if raw.get("locale") != "ko":
|
|
468
|
+
raise WizardError(
|
|
469
|
+
f"wizard prompt locale unsupported (expected 'ko'): {path}"
|
|
470
|
+
)
|
|
471
|
+
steps = raw.get("steps")
|
|
472
|
+
if not isinstance(steps, dict):
|
|
473
|
+
raise WizardError(f"wizard prompt 'steps' missing or not a dict: {path}")
|
|
474
|
+
_WIZARD_ROOT_CACHE[workspace_root] = raw
|
|
475
|
+
return raw
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def _p(workspace_root: str, step_id: str, **vars: str) -> dict:
|
|
479
|
+
"""Look up a wizard prompt entry by step_id and interpolate placeholders.
|
|
480
|
+
|
|
481
|
+
Returns a dict with keys: 'label' (str, possibly interpolated),
|
|
482
|
+
'echo_template' (str, raw — contains `{value}` for the user's answer),
|
|
483
|
+
'options' (dict[value → label], may be empty).
|
|
484
|
+
|
|
485
|
+
Returned dict also includes echo_variants and errors sub-dicts (may be empty).
|
|
486
|
+
|
|
487
|
+
Raises WizardError if the step_id is unknown or a required placeholder
|
|
488
|
+
is missing from the provided vars.
|
|
489
|
+
"""
|
|
490
|
+
steps = _load_wizard_root(workspace_root)["steps"]
|
|
491
|
+
raw = steps.get(step_id)
|
|
492
|
+
if raw is None:
|
|
493
|
+
raise WizardError(f"unknown wizard step_id: {step_id!r}")
|
|
494
|
+
label_template = raw.get("label", "")
|
|
495
|
+
try:
|
|
496
|
+
label = label_template.format(**vars)
|
|
497
|
+
except KeyError as exc:
|
|
498
|
+
missing = exc.args[0] if exc.args else "<unknown>"
|
|
499
|
+
raise WizardError(
|
|
500
|
+
f"missing placeholder {missing!r} for wizard step {step_id!r}"
|
|
501
|
+
) from exc
|
|
502
|
+
return {
|
|
503
|
+
"label": label,
|
|
504
|
+
"echo_template": raw.get("echo_template", ""),
|
|
505
|
+
"options": raw.get("options", {}),
|
|
506
|
+
"echo_variants": raw.get("echo_variants", {}),
|
|
507
|
+
"errors": raw.get("errors", {}),
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def _msg(workspace_root: str, section: str, key: str, **vars: str) -> str:
|
|
512
|
+
"""Look up a top-level message (e.g., `confirmation.header`) and interpolate.
|
|
513
|
+
|
|
514
|
+
Returns the string value at `<section>.<key>`. Raises WizardError if the
|
|
515
|
+
section or key is unknown, or if a required placeholder is missing.
|
|
516
|
+
|
|
517
|
+
Use for wizard-level messages (confirmation block etc.). For step-scoped
|
|
518
|
+
messages (echo_variants, errors), use `_p()` instead.
|
|
519
|
+
"""
|
|
520
|
+
root = _load_wizard_root(workspace_root)
|
|
521
|
+
sect = root.get(section)
|
|
522
|
+
if not isinstance(sect, dict):
|
|
523
|
+
raise WizardError(f"unknown wizard section: {section!r}")
|
|
524
|
+
template = sect.get(key)
|
|
525
|
+
if template is None:
|
|
526
|
+
raise WizardError(f"unknown wizard message: {section}.{key!r}")
|
|
527
|
+
try:
|
|
528
|
+
return template.format(**vars)
|
|
529
|
+
except KeyError as exc:
|
|
530
|
+
missing = exc.args[0] if exc.args else "<unknown>"
|
|
531
|
+
raise WizardError(
|
|
532
|
+
f"missing placeholder {missing!r} for wizard message {section}.{key!r}"
|
|
533
|
+
) from exc
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def _static_options(t: dict) -> list[tuple[str, str]]:
|
|
537
|
+
"""Return (value, label) pairs from the SOT's static options dict, excluding
|
|
538
|
+
suffix-decoration tokens (`_<NAME>_SUFFIX` / `_<NAME>_LABEL`).
|
|
539
|
+
|
|
540
|
+
Used by hybrid `_build_*` functions that mix dynamic and static options to
|
|
541
|
+
avoid leaking `_RECOMMENDED_SUFFIX` / `_OPTIONAL_SUFFIX` / `_DEFAULT_SUFFIX`
|
|
542
|
+
as a literal option entry.
|
|
543
|
+
"""
|
|
544
|
+
return [
|
|
545
|
+
(k, v) for k, v in t.get("options", {}).items()
|
|
546
|
+
if not (k.startswith("_") and (k.endswith("_SUFFIX") or k.endswith("_LABEL")))
|
|
547
|
+
]
|
|
548
|
+
|
|
549
|
+
|
|
414
550
|
def _resolved_roster(state: WizardState) -> list[str]:
|
|
415
551
|
"""Effective worker list AFTER override. Implementation: profile default
|
|
416
552
|
(caller never asks for override). Others: override or profile default."""
|
|
@@ -464,24 +600,26 @@ def _opt(value: str, label: str = "", description: str = "") -> Option:
|
|
|
464
600
|
# --- builders ---
|
|
465
601
|
|
|
466
602
|
def _build_task_pick(state: WizardState) -> Prompt:
|
|
603
|
+
t = _p(state.workspace_root, "task_pick")
|
|
467
604
|
project_root = Path(state.project_root)
|
|
468
605
|
tasks = list_project_tasks(project_root)
|
|
469
606
|
latest = read_latest_task(project_root) or {}
|
|
470
607
|
latest_key = latest.get("taskKey") or ""
|
|
608
|
+
latest_suffix = t["options"].get("_LATEST_SUFFIX", "")
|
|
471
609
|
options: list[Option] = []
|
|
472
610
|
for entry in tasks[:8]:
|
|
473
611
|
key = entry.get("taskKey") or ""
|
|
474
612
|
ttype = entry.get("taskType") or ""
|
|
475
613
|
phase = (entry.get("workflow") or {}).get("currentPhase") or ttype
|
|
476
614
|
nxt = (entry.get("workflow") or {}).get("nextRecommendedPhase") or ""
|
|
477
|
-
suffix =
|
|
615
|
+
suffix = latest_suffix if key == latest_key else ""
|
|
478
616
|
label = f"{key} · {phase} · next: {nxt}{suffix}"
|
|
479
617
|
options.append(_opt(value=key, label=label))
|
|
480
|
-
|
|
481
|
-
|
|
618
|
+
for value, label in _static_options(t):
|
|
619
|
+
options.append(_opt(value=value, label=label))
|
|
482
620
|
return Prompt(step=S_TASK_PICK, kind="pick",
|
|
483
|
-
label="
|
|
484
|
-
echo_template="
|
|
621
|
+
label=t["label"], options=options,
|
|
622
|
+
echo_template=t["echo_template"])
|
|
485
623
|
|
|
486
624
|
|
|
487
625
|
def _submit_task_pick(state: WizardState, value: str) -> Optional[str]:
|
|
@@ -521,18 +659,21 @@ def _submit_task_pick(state: WizardState, value: str) -> Optional[str]:
|
|
|
521
659
|
def _build_task_group(state: WizardState) -> Prompt:
|
|
522
660
|
sugg = state.task_group_suggestion
|
|
523
661
|
if sugg:
|
|
662
|
+
t = _p(state.workspace_root, "task_group_with_suggestion",
|
|
663
|
+
suggestion=sugg)
|
|
664
|
+
options = [
|
|
665
|
+
_opt(k, v.format(suggestion=sugg))
|
|
666
|
+
for k, v in t["options"].items()
|
|
667
|
+
]
|
|
524
668
|
return Prompt(
|
|
525
669
|
step=S_TASK_GROUP, kind="pick",
|
|
526
|
-
label=
|
|
527
|
-
|
|
528
|
-
_opt(PICK_USE_SUGGESTED, f"brief 값 사용: {sugg}"),
|
|
529
|
-
_opt(PICK_TYPE_CUSTOM, "다른 값 입력"),
|
|
530
|
-
],
|
|
531
|
-
echo_template="task-group: {value}",
|
|
670
|
+
label=t["label"], options=options,
|
|
671
|
+
echo_template=t["echo_template"],
|
|
532
672
|
)
|
|
673
|
+
t = _p(state.workspace_root, "task_group")
|
|
533
674
|
return Prompt(step=S_TASK_GROUP, kind="text",
|
|
534
|
-
label="
|
|
535
|
-
echo_template="
|
|
675
|
+
label=t["label"],
|
|
676
|
+
echo_template=t["echo_template"])
|
|
536
677
|
|
|
537
678
|
|
|
538
679
|
def _submit_task_group(state: WizardState, value: str) -> Optional[str]:
|
|
@@ -545,7 +686,8 @@ def _submit_task_group(state: WizardState, value: str) -> Optional[str]:
|
|
|
545
686
|
return f"task-group: {state.task_group} (brief)"
|
|
546
687
|
if value == PICK_TYPE_CUSTOM:
|
|
547
688
|
state.task_group_pending_text = True
|
|
548
|
-
|
|
689
|
+
t = _p(state.workspace_root, "task_group")
|
|
690
|
+
return t["echo_variants"]["free_input"]
|
|
549
691
|
raise WizardError(
|
|
550
692
|
f"expected {PICK_USE_SUGGESTED!r} or {PICK_TYPE_CUSTOM!r}, "
|
|
551
693
|
f"got: {value!r}"
|
|
@@ -556,9 +698,10 @@ def _submit_task_group(state: WizardState, value: str) -> Optional[str]:
|
|
|
556
698
|
|
|
557
699
|
|
|
558
700
|
def _build_task_group_text(state: WizardState) -> Prompt:
|
|
701
|
+
t = _p(state.workspace_root, "task_group_text")
|
|
559
702
|
return Prompt(step=S_TASK_GROUP_TEXT, kind="text",
|
|
560
|
-
label="
|
|
561
|
-
echo_template="
|
|
703
|
+
label=t["label"],
|
|
704
|
+
echo_template=t["echo_template"])
|
|
562
705
|
|
|
563
706
|
|
|
564
707
|
def _submit_task_group_text(state: WizardState, value: str) -> Optional[str]:
|
|
@@ -570,18 +713,21 @@ def _submit_task_group_text(state: WizardState, value: str) -> Optional[str]:
|
|
|
570
713
|
def _build_task_id(state: WizardState) -> Prompt:
|
|
571
714
|
sugg = state.task_id_suggestion
|
|
572
715
|
if sugg:
|
|
716
|
+
t = _p(state.workspace_root, "task_id_with_suggestion",
|
|
717
|
+
suggestion=sugg)
|
|
718
|
+
options = [
|
|
719
|
+
_opt(k, v.format(suggestion=sugg))
|
|
720
|
+
for k, v in t["options"].items()
|
|
721
|
+
]
|
|
573
722
|
return Prompt(
|
|
574
723
|
step=S_TASK_ID, kind="pick",
|
|
575
|
-
label=
|
|
576
|
-
|
|
577
|
-
_opt(PICK_USE_SUGGESTED, f"brief 값 사용: {sugg}"),
|
|
578
|
-
_opt(PICK_TYPE_CUSTOM, "다른 값 입력"),
|
|
579
|
-
],
|
|
580
|
-
echo_template="task-id: {value}",
|
|
724
|
+
label=t["label"], options=options,
|
|
725
|
+
echo_template=t["echo_template"],
|
|
581
726
|
)
|
|
727
|
+
t = _p(state.workspace_root, "task_id")
|
|
582
728
|
return Prompt(step=S_TASK_ID, kind="text",
|
|
583
|
-
label="
|
|
584
|
-
echo_template="
|
|
729
|
+
label=t["label"],
|
|
730
|
+
echo_template=t["echo_template"])
|
|
585
731
|
|
|
586
732
|
|
|
587
733
|
def _submit_task_id(state: WizardState, value: str) -> Optional[str]:
|
|
@@ -592,7 +738,8 @@ def _submit_task_id(state: WizardState, value: str) -> Optional[str]:
|
|
|
592
738
|
return f"task-id: {state.task_id} (brief)"
|
|
593
739
|
if value == PICK_TYPE_CUSTOM:
|
|
594
740
|
state.task_id_pending_text = True
|
|
595
|
-
|
|
741
|
+
t = _p(state.workspace_root, "task_id")
|
|
742
|
+
return t["echo_variants"]["free_input"]
|
|
596
743
|
raise WizardError(
|
|
597
744
|
f"expected {PICK_USE_SUGGESTED!r} or {PICK_TYPE_CUSTOM!r}, "
|
|
598
745
|
f"got: {value!r}"
|
|
@@ -603,9 +750,10 @@ def _submit_task_id(state: WizardState, value: str) -> Optional[str]:
|
|
|
603
750
|
|
|
604
751
|
|
|
605
752
|
def _build_task_id_text(state: WizardState) -> Prompt:
|
|
753
|
+
t = _p(state.workspace_root, "task_id_text")
|
|
606
754
|
return Prompt(step=S_TASK_ID_TEXT, kind="text",
|
|
607
|
-
label="
|
|
608
|
-
echo_template="
|
|
755
|
+
label=t["label"],
|
|
756
|
+
echo_template=t["echo_template"])
|
|
609
757
|
|
|
610
758
|
|
|
611
759
|
def _submit_task_id_text(state: WizardState, value: str) -> Optional[str]:
|
|
@@ -615,20 +763,22 @@ def _submit_task_id_text(state: WizardState, value: str) -> Optional[str]:
|
|
|
615
763
|
|
|
616
764
|
|
|
617
765
|
def _build_task_type(state: WizardState) -> Prompt:
|
|
766
|
+
t = _p(state.workspace_root, "task_type")
|
|
767
|
+
recommended_suffix = t["options"].get("_RECOMMENDED_SUFFIX", "")
|
|
618
768
|
options: list[Option] = []
|
|
619
769
|
recommended = state.task_type if not state.is_new_task else ""
|
|
620
770
|
seen: list[str] = []
|
|
621
771
|
if recommended and recommended in TASK_TYPE_VALUES:
|
|
622
772
|
d = dict(TASK_TYPES)[recommended]
|
|
623
|
-
options.append(_opt(recommended, f"{recommended}
|
|
773
|
+
options.append(_opt(recommended, f"{recommended}{recommended_suffix}", d))
|
|
624
774
|
seen.append(recommended)
|
|
625
775
|
for tt, desc in TASK_TYPES:
|
|
626
776
|
if tt in seen:
|
|
627
777
|
continue
|
|
628
778
|
options.append(_opt(tt, tt, desc))
|
|
629
779
|
return Prompt(step=S_TASK_TYPE, kind="pick",
|
|
630
|
-
label="
|
|
631
|
-
echo_template="
|
|
780
|
+
label=t["label"], options=options,
|
|
781
|
+
echo_template=t["echo_template"])
|
|
632
782
|
|
|
633
783
|
|
|
634
784
|
def _submit_task_type(state: WizardState, value: str) -> Optional[str]:
|
|
@@ -648,11 +798,13 @@ def _submit_task_type(state: WizardState, value: str) -> Optional[str]:
|
|
|
648
798
|
|
|
649
799
|
|
|
650
800
|
def _build_brief_keep(state: WizardState) -> Prompt:
|
|
801
|
+
t = _p(state.workspace_root, "brief_keep",
|
|
802
|
+
existing_brief_path=state.existing_brief_path)
|
|
651
803
|
return Prompt(
|
|
652
804
|
step=S_BRIEF_KEEP, kind="pick",
|
|
653
|
-
label=
|
|
654
|
-
options=[_opt(
|
|
655
|
-
echo_template="
|
|
805
|
+
label=t["label"],
|
|
806
|
+
options=[_opt(k, v) for k, v in t["options"].items()],
|
|
807
|
+
echo_template=t["echo_template"],
|
|
656
808
|
)
|
|
657
809
|
|
|
658
810
|
|
|
@@ -662,15 +814,18 @@ def _submit_brief_keep(state: WizardState, value: str) -> Optional[str]:
|
|
|
662
814
|
state.keep_existing_brief = value == "keep"
|
|
663
815
|
if state.keep_existing_brief:
|
|
664
816
|
state.brief_path = state.existing_brief_path
|
|
665
|
-
|
|
817
|
+
t = _p(state.workspace_root, "brief_keep",
|
|
818
|
+
existing_brief_path=state.existing_brief_path)
|
|
819
|
+
return t["echo_variants"]["kept"].format(brief_path=state.brief_path)
|
|
666
820
|
return None # next prompt is S_BRIEF_PATH
|
|
667
821
|
|
|
668
822
|
|
|
669
823
|
def _build_brief_path(state: WizardState) -> Prompt:
|
|
824
|
+
t = _p(state.workspace_root, "brief_path")
|
|
670
825
|
return Prompt(
|
|
671
826
|
step=S_BRIEF_PATH, kind="text",
|
|
672
|
-
label="
|
|
673
|
-
echo_template="
|
|
827
|
+
label=t["label"],
|
|
828
|
+
echo_template=t["echo_template"],
|
|
674
829
|
)
|
|
675
830
|
|
|
676
831
|
|
|
@@ -689,14 +844,17 @@ def _submit_brief_path(state: WizardState, value: str) -> Optional[str]:
|
|
|
689
844
|
|
|
690
845
|
|
|
691
846
|
def _build_base_ref_pick(state: WizardState) -> Prompt:
|
|
692
|
-
|
|
847
|
+
t = _p(state.workspace_root, "base_ref_pick")
|
|
848
|
+
recommended_suffix = t["options"].get("_RECOMMENDED_SUFFIX", "")
|
|
849
|
+
options = [_opt(r, f"main{recommended_suffix}" if r == "main" else r)
|
|
693
850
|
for r in CANONICAL_BASE_REFS]
|
|
694
|
-
|
|
851
|
+
for value, label in _static_options(t):
|
|
852
|
+
options.append(_opt(value=value, label=label))
|
|
695
853
|
return Prompt(
|
|
696
854
|
step=S_BASE_REF_PICK, kind="pick",
|
|
697
|
-
label="
|
|
855
|
+
label=t["label"],
|
|
698
856
|
options=options,
|
|
699
|
-
echo_template="
|
|
857
|
+
echo_template=t["echo_template"],
|
|
700
858
|
)
|
|
701
859
|
|
|
702
860
|
|
|
@@ -712,10 +870,11 @@ def _submit_base_ref_pick(state: WizardState, value: str) -> Optional[str]:
|
|
|
712
870
|
|
|
713
871
|
|
|
714
872
|
def _build_base_ref_text(state: WizardState) -> Prompt:
|
|
873
|
+
t = _p(state.workspace_root, "base_ref_text")
|
|
715
874
|
return Prompt(
|
|
716
875
|
step=S_BASE_REF_TEXT, kind="text",
|
|
717
|
-
label="
|
|
718
|
-
echo_template="
|
|
876
|
+
label=t["label"],
|
|
877
|
+
echo_template=t["echo_template"],
|
|
719
878
|
)
|
|
720
879
|
|
|
721
880
|
|
|
@@ -769,15 +928,17 @@ def _latest_implementation_planning_report(state: WizardState) -> Optional[Path]
|
|
|
769
928
|
|
|
770
929
|
def _build_approved_plan_pick(state: WizardState) -> Prompt:
|
|
771
930
|
default = _latest_implementation_planning_report(state)
|
|
931
|
+
t = _p(state.workspace_root, "approved_plan_pick",
|
|
932
|
+
default=str(default) if default is not None else "")
|
|
772
933
|
options = [
|
|
773
|
-
_opt(
|
|
774
|
-
|
|
934
|
+
_opt(k, v.format(default=str(default) if default is not None else ""))
|
|
935
|
+
for k, v in t["options"].items()
|
|
775
936
|
]
|
|
776
937
|
return Prompt(
|
|
777
938
|
step=S_APPROVED_PLAN_PICK, kind="pick",
|
|
778
|
-
label=
|
|
939
|
+
label=t["label"],
|
|
779
940
|
options=options,
|
|
780
|
-
echo_template="
|
|
941
|
+
echo_template=t["echo_template"],
|
|
781
942
|
)
|
|
782
943
|
|
|
783
944
|
|
|
@@ -785,9 +946,8 @@ def _submit_approved_plan_pick(state: WizardState, value: str) -> Optional[str]:
|
|
|
785
946
|
if value == PICK_USE_DEFAULT:
|
|
786
947
|
default = _latest_implementation_planning_report(state)
|
|
787
948
|
if default is None:
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
)
|
|
949
|
+
t = _p(state.workspace_root, "approved_plan_pick", default="")
|
|
950
|
+
raise WizardError(t["errors"]["default_not_found"])
|
|
791
951
|
p = _validate_approved_plan(str(default), Path(state.project_root))
|
|
792
952
|
state.approved_plan_path = str(p)
|
|
793
953
|
state.approved_plan_pending_text = False
|
|
@@ -802,10 +962,11 @@ def _submit_approved_plan_pick(state: WizardState, value: str) -> Optional[str]:
|
|
|
802
962
|
|
|
803
963
|
|
|
804
964
|
def _build_approved_plan(state: WizardState) -> Prompt:
|
|
965
|
+
t = _p(state.workspace_root, "approved_plan")
|
|
805
966
|
return Prompt(
|
|
806
967
|
step=S_APPROVED_PLAN, kind="text",
|
|
807
|
-
label="
|
|
808
|
-
echo_template="
|
|
968
|
+
label=t["label"],
|
|
969
|
+
echo_template=t["echo_template"],
|
|
809
970
|
)
|
|
810
971
|
|
|
811
972
|
|
|
@@ -816,15 +977,60 @@ def _submit_approved_plan(state: WizardState, value: str) -> Optional[str]:
|
|
|
816
977
|
return f"approved-plan: {p}"
|
|
817
978
|
|
|
818
979
|
|
|
980
|
+
def _build_stage_pick(state: WizardState) -> Prompt:
|
|
981
|
+
"""Parse the Stage Map from the approved plan and build the stage picker."""
|
|
982
|
+
import importlib.util as _ilu
|
|
983
|
+
import sys as _sys
|
|
984
|
+
t = _p(state.workspace_root, "stage_pick")
|
|
985
|
+
plan_text = Path(state.approved_plan_path).read_text(encoding="utf-8")
|
|
986
|
+
validator_path = (
|
|
987
|
+
Path(state.workspace_root) / "validators"
|
|
988
|
+
/ "validate-implementation-plan-stages.py"
|
|
989
|
+
)
|
|
990
|
+
spec = _ilu.spec_from_file_location("_ip_stage_v_wizard", str(validator_path))
|
|
991
|
+
mod = _ilu.module_from_spec(spec) # type: ignore[arg-type]
|
|
992
|
+
_sys.modules["_ip_stage_v_wizard"] = mod
|
|
993
|
+
try:
|
|
994
|
+
spec.loader.exec_module(mod) # type: ignore[union-attr]
|
|
995
|
+
stages, _errs = mod._parse_stage_map(plan_text)
|
|
996
|
+
finally:
|
|
997
|
+
_sys.modules.pop("_ip_stage_v_wizard", None)
|
|
998
|
+
options = [_opt(k, v) for k, v in t["options"].items()]
|
|
999
|
+
for s in stages:
|
|
1000
|
+
depends = ",".join(map(str, s.depends_on)) or "(none)"
|
|
1001
|
+
options.append(_opt(
|
|
1002
|
+
str(s.stage_number),
|
|
1003
|
+
f"{s.stage_number}: {s.title} [depends-on: {depends} | steps: {s.step_count}]",
|
|
1004
|
+
))
|
|
1005
|
+
return Prompt(
|
|
1006
|
+
step=S_STAGE_PICK, kind="pick",
|
|
1007
|
+
label=t["label"],
|
|
1008
|
+
options=options,
|
|
1009
|
+
echo_template=t["echo_template"],
|
|
1010
|
+
)
|
|
1011
|
+
|
|
1012
|
+
|
|
1013
|
+
def _submit_stage_pick(state: WizardState, answer: str) -> Optional[str]:
|
|
1014
|
+
if not answer:
|
|
1015
|
+
raise WizardError("value required")
|
|
1016
|
+
if answer != "auto":
|
|
1017
|
+
try:
|
|
1018
|
+
int(answer)
|
|
1019
|
+
except ValueError:
|
|
1020
|
+
raise WizardError(
|
|
1021
|
+
f"answer must be 'auto' or a stage number, got {answer!r}"
|
|
1022
|
+
)
|
|
1023
|
+
state.selected_stage = answer
|
|
1024
|
+
return f"stage: {answer}"
|
|
1025
|
+
|
|
1026
|
+
|
|
819
1027
|
def _build_directive_pick(state: WizardState) -> Prompt:
|
|
1028
|
+
t = _p(state.workspace_root, "directive_pick")
|
|
820
1029
|
return Prompt(
|
|
821
1030
|
step=S_DIRECTIVE_PICK, kind="pick",
|
|
822
|
-
label="
|
|
823
|
-
options=[
|
|
824
|
-
|
|
825
|
-
_opt(PICK_ENTER, "있음 (입력)"),
|
|
826
|
-
],
|
|
827
|
-
echo_template="directive(pick): {value}",
|
|
1031
|
+
label=t["label"],
|
|
1032
|
+
options=[_opt(k, v) for k, v in t["options"].items()],
|
|
1033
|
+
echo_template=t["echo_template"],
|
|
828
1034
|
)
|
|
829
1035
|
|
|
830
1036
|
|
|
@@ -840,14 +1046,12 @@ def _submit_directive_pick(state: WizardState, value: str) -> Optional[str]:
|
|
|
840
1046
|
|
|
841
1047
|
|
|
842
1048
|
def _build_related_tasks_pick(state: WizardState) -> Prompt:
|
|
1049
|
+
t = _p(state.workspace_root, "related_tasks_pick")
|
|
843
1050
|
return Prompt(
|
|
844
1051
|
step=S_RELATED_TASKS_PICK, kind="pick",
|
|
845
|
-
label="
|
|
846
|
-
options=[
|
|
847
|
-
|
|
848
|
-
_opt(PICK_ENTER, "있음 (입력)"),
|
|
849
|
-
],
|
|
850
|
-
echo_template="related-tasks(pick): {value}",
|
|
1052
|
+
label=t["label"],
|
|
1053
|
+
options=[_opt(k, v) for k, v in t["options"].items()],
|
|
1054
|
+
echo_template=t["echo_template"],
|
|
851
1055
|
)
|
|
852
1056
|
|
|
853
1057
|
|
|
@@ -863,14 +1067,12 @@ def _submit_related_tasks_pick(state: WizardState, value: str) -> Optional[str]:
|
|
|
863
1067
|
|
|
864
1068
|
|
|
865
1069
|
def _build_clarification_pick(state: WizardState) -> Prompt:
|
|
1070
|
+
t = _p(state.workspace_root, "clarification_pick")
|
|
866
1071
|
return Prompt(
|
|
867
1072
|
step=S_CLARIFICATION_PICK, kind="pick",
|
|
868
|
-
label="
|
|
869
|
-
options=[
|
|
870
|
-
|
|
871
|
-
_opt(PICK_ENTER, "있음 (입력)"),
|
|
872
|
-
],
|
|
873
|
-
echo_template="clarification(pick): {value}",
|
|
1073
|
+
label=t["label"],
|
|
1074
|
+
options=[_opt(k, v) for k, v in t["options"].items()],
|
|
1075
|
+
echo_template=t["echo_template"],
|
|
874
1076
|
)
|
|
875
1077
|
|
|
876
1078
|
|
|
@@ -886,14 +1088,12 @@ def _submit_clarification_pick(state: WizardState, value: str) -> Optional[str]:
|
|
|
886
1088
|
|
|
887
1089
|
|
|
888
1090
|
def _build_pr_template_pick(state: WizardState) -> Prompt:
|
|
1091
|
+
t = _p(state.workspace_root, "pr_template_pick")
|
|
889
1092
|
return Prompt(
|
|
890
1093
|
step=S_PR_TEMPLATE_PICK, kind="pick",
|
|
891
|
-
label="
|
|
892
|
-
options=[
|
|
893
|
-
|
|
894
|
-
_opt(PICK_ENTER, "직접 경로 입력 (1회성 override)"),
|
|
895
|
-
],
|
|
896
|
-
echo_template="pr-template(pick): {value}",
|
|
1094
|
+
label=t["label"],
|
|
1095
|
+
options=[_opt(k, v) for k, v in t["options"].items()],
|
|
1096
|
+
echo_template=t["echo_template"],
|
|
897
1097
|
)
|
|
898
1098
|
|
|
899
1099
|
|
|
@@ -910,13 +1110,15 @@ def _submit_pr_template_pick(state: WizardState, value: str) -> Optional[str]:
|
|
|
910
1110
|
|
|
911
1111
|
|
|
912
1112
|
def _build_executor(state: WizardState) -> Prompt:
|
|
913
|
-
|
|
1113
|
+
t = _p(state.workspace_root, "executor")
|
|
1114
|
+
default_suffix = t["options"].get("_DEFAULT_SUFFIX", "")
|
|
1115
|
+
options = [_opt(e, e + (default_suffix if e == "claude" else ""))
|
|
914
1116
|
for e in EXECUTORS]
|
|
915
1117
|
return Prompt(
|
|
916
1118
|
step=S_EXECUTOR, kind="pick",
|
|
917
|
-
label="
|
|
1119
|
+
label=t["label"],
|
|
918
1120
|
options=options,
|
|
919
|
-
echo_template="
|
|
1121
|
+
echo_template=t["echo_template"],
|
|
920
1122
|
)
|
|
921
1123
|
|
|
922
1124
|
|
|
@@ -928,12 +1130,12 @@ def _submit_executor(state: WizardState, value: str) -> Optional[str]:
|
|
|
928
1130
|
|
|
929
1131
|
|
|
930
1132
|
def _build_defaults_or_custom(state: WizardState) -> Prompt:
|
|
1133
|
+
t = _p(state.workspace_root, "defaults_or_custom")
|
|
931
1134
|
return Prompt(
|
|
932
1135
|
step=S_DEFAULTS_OR_CUSTOM, kind="pick",
|
|
933
|
-
label="
|
|
934
|
-
options=[_opt(
|
|
935
|
-
|
|
936
|
-
echo_template="customize: {value}",
|
|
1136
|
+
label=t["label"],
|
|
1137
|
+
options=[_opt(k, v) for k, v in t["options"].items()],
|
|
1138
|
+
echo_template=t["echo_template"],
|
|
937
1139
|
)
|
|
938
1140
|
|
|
939
1141
|
|
|
@@ -947,6 +1149,8 @@ def _submit_defaults_or_custom(state: WizardState, value: str) -> Optional[str]:
|
|
|
947
1149
|
def _build_workers_override(state: WizardState) -> Prompt:
|
|
948
1150
|
"""분석 워커 멀티픽. report-writer 는 옵션에서 빼고 항상 결과에 강제
|
|
949
1151
|
포함시킨다(프로필이 report-writer 를 Required 로 가질 때)."""
|
|
1152
|
+
t = _p(state.workspace_root, "workers_override")
|
|
1153
|
+
optional_suffix = t["options"].get("_OPTIONAL_SUFFIX", "")
|
|
950
1154
|
analyser_choices = [
|
|
951
1155
|
w for w in (state.profile_workers + state.profile_optional_workers)
|
|
952
1156
|
if w != "report-writer"
|
|
@@ -954,14 +1158,13 @@ def _build_workers_override(state: WizardState) -> Prompt:
|
|
|
954
1158
|
options: list[Option] = []
|
|
955
1159
|
for w in analyser_choices:
|
|
956
1160
|
is_optional = w in state.profile_optional_workers
|
|
957
|
-
label = f"{w}
|
|
1161
|
+
label = f"{w}{optional_suffix}" if is_optional else w
|
|
958
1162
|
options.append(_opt(value=w, label=label))
|
|
959
1163
|
return Prompt(
|
|
960
1164
|
step=S_WORKERS_OVERRIDE, kind="pick", multi=True,
|
|
961
|
-
label=
|
|
962
|
-
"report-writer 는 항상 포함됩니다."),
|
|
1165
|
+
label=t["label"],
|
|
963
1166
|
options=options,
|
|
964
|
-
echo_template="
|
|
1167
|
+
echo_template=t["echo_template"],
|
|
965
1168
|
)
|
|
966
1169
|
|
|
967
1170
|
|
|
@@ -970,7 +1173,8 @@ def _submit_workers_override(state: WizardState, value: str) -> Optional[str]:
|
|
|
970
1173
|
try:
|
|
971
1174
|
chosen = normalize_workers(raw) if raw else []
|
|
972
1175
|
if not chosen:
|
|
973
|
-
|
|
1176
|
+
t = _p(state.workspace_root, "workers_override")
|
|
1177
|
+
raise WizardError(t["errors"]["min_one_required"])
|
|
974
1178
|
validate_workers_against_profile(
|
|
975
1179
|
chosen,
|
|
976
1180
|
state.profile_workers,
|
|
@@ -993,8 +1197,9 @@ def _model_pick(step: str, label: str, options: list[str], echo: str) -> Prompt:
|
|
|
993
1197
|
|
|
994
1198
|
|
|
995
1199
|
def _build_lead_model(state: WizardState) -> Prompt:
|
|
996
|
-
|
|
997
|
-
|
|
1200
|
+
t = _p(state.workspace_root, "lead_model")
|
|
1201
|
+
return _model_pick(S_LEAD_MODEL, t["label"],
|
|
1202
|
+
CLAUDE_MODEL_OPTIONS, t["echo_template"])
|
|
998
1203
|
|
|
999
1204
|
|
|
1000
1205
|
def _submit_lead_model(state: WizardState, value: str) -> Optional[str]:
|
|
@@ -1003,11 +1208,12 @@ def _submit_lead_model(state: WizardState, value: str) -> Optional[str]:
|
|
|
1003
1208
|
|
|
1004
1209
|
|
|
1005
1210
|
def _build_executor_model(state: WizardState) -> Prompt:
|
|
1211
|
+
t = _p(state.workspace_root, "executor_model", executor=state.executor)
|
|
1006
1212
|
return _model_pick(
|
|
1007
1213
|
S_EXECUTOR_MODEL,
|
|
1008
|
-
|
|
1214
|
+
t["label"],
|
|
1009
1215
|
_executor_model_options(state.executor),
|
|
1010
|
-
|
|
1216
|
+
t["echo_template"].replace("{executor}", state.executor),
|
|
1011
1217
|
)
|
|
1012
1218
|
|
|
1013
1219
|
|
|
@@ -1018,8 +1224,9 @@ def _submit_executor_model(state: WizardState, value: str) -> Optional[str]:
|
|
|
1018
1224
|
|
|
1019
1225
|
|
|
1020
1226
|
def _build_claude_model(state: WizardState) -> Prompt:
|
|
1021
|
-
|
|
1022
|
-
|
|
1227
|
+
t = _p(state.workspace_root, "claude_model")
|
|
1228
|
+
return _model_pick(S_CLAUDE_MODEL, t["label"],
|
|
1229
|
+
CLAUDE_MODEL_OPTIONS, t["echo_template"])
|
|
1023
1230
|
|
|
1024
1231
|
|
|
1025
1232
|
def _submit_claude_model(state: WizardState, value: str) -> Optional[str]:
|
|
@@ -1028,8 +1235,9 @@ def _submit_claude_model(state: WizardState, value: str) -> Optional[str]:
|
|
|
1028
1235
|
|
|
1029
1236
|
|
|
1030
1237
|
def _build_codex_model(state: WizardState) -> Prompt:
|
|
1031
|
-
|
|
1032
|
-
|
|
1238
|
+
t = _p(state.workspace_root, "codex_model")
|
|
1239
|
+
return _model_pick(S_CODEX_MODEL, t["label"],
|
|
1240
|
+
CODEX_MODEL_OPTIONS, t["echo_template"])
|
|
1033
1241
|
|
|
1034
1242
|
|
|
1035
1243
|
def _submit_codex_model(state: WizardState, value: str) -> Optional[str]:
|
|
@@ -1038,8 +1246,9 @@ def _submit_codex_model(state: WizardState, value: str) -> Optional[str]:
|
|
|
1038
1246
|
|
|
1039
1247
|
|
|
1040
1248
|
def _build_gemini_model(state: WizardState) -> Prompt:
|
|
1041
|
-
|
|
1042
|
-
|
|
1249
|
+
t = _p(state.workspace_root, "gemini_model")
|
|
1250
|
+
return _model_pick(S_GEMINI_MODEL, t["label"],
|
|
1251
|
+
GEMINI_MODEL_OPTIONS, t["echo_template"])
|
|
1043
1252
|
|
|
1044
1253
|
|
|
1045
1254
|
def _submit_gemini_model(state: WizardState, value: str) -> Optional[str]:
|
|
@@ -1048,10 +1257,11 @@ def _submit_gemini_model(state: WizardState, value: str) -> Optional[str]:
|
|
|
1048
1257
|
|
|
1049
1258
|
|
|
1050
1259
|
def _build_report_writer_model(state: WizardState) -> Prompt:
|
|
1260
|
+
t = _p(state.workspace_root, "report_writer_model")
|
|
1051
1261
|
return _model_pick(S_REPORT_WRITER_MODEL,
|
|
1052
|
-
"
|
|
1262
|
+
t["label"],
|
|
1053
1263
|
CLAUDE_MODEL_OPTIONS,
|
|
1054
|
-
"
|
|
1264
|
+
t["echo_template"])
|
|
1055
1265
|
|
|
1056
1266
|
|
|
1057
1267
|
def _submit_report_writer_model(state: WizardState, value: str) -> Optional[str]:
|
|
@@ -1060,10 +1270,11 @@ def _submit_report_writer_model(state: WizardState, value: str) -> Optional[str]
|
|
|
1060
1270
|
|
|
1061
1271
|
|
|
1062
1272
|
def _build_directive(state: WizardState) -> Prompt:
|
|
1273
|
+
t = _p(state.workspace_root, "directive")
|
|
1063
1274
|
return Prompt(
|
|
1064
1275
|
step=S_DIRECTIVE, kind="text",
|
|
1065
|
-
label="
|
|
1066
|
-
echo_template="
|
|
1276
|
+
label=t["label"],
|
|
1277
|
+
echo_template=t["echo_template"],
|
|
1067
1278
|
)
|
|
1068
1279
|
|
|
1069
1280
|
|
|
@@ -1074,10 +1285,11 @@ def _submit_directive(state: WizardState, value: str) -> Optional[str]:
|
|
|
1074
1285
|
|
|
1075
1286
|
|
|
1076
1287
|
def _build_related_tasks(state: WizardState) -> Prompt:
|
|
1288
|
+
t = _p(state.workspace_root, "related_tasks")
|
|
1077
1289
|
return Prompt(
|
|
1078
1290
|
step=S_RELATED_TASKS, kind="text",
|
|
1079
|
-
label="
|
|
1080
|
-
echo_template="
|
|
1291
|
+
label=t["label"],
|
|
1292
|
+
echo_template=t["echo_template"],
|
|
1081
1293
|
)
|
|
1082
1294
|
|
|
1083
1295
|
|
|
@@ -1088,10 +1300,11 @@ def _submit_related_tasks(state: WizardState, value: str) -> Optional[str]:
|
|
|
1088
1300
|
|
|
1089
1301
|
|
|
1090
1302
|
def _build_clarification(state: WizardState) -> Prompt:
|
|
1303
|
+
t = _p(state.workspace_root, "clarification")
|
|
1091
1304
|
return Prompt(
|
|
1092
1305
|
step=S_CLARIFICATION, kind="text",
|
|
1093
|
-
label="
|
|
1094
|
-
echo_template="
|
|
1306
|
+
label=t["label"],
|
|
1307
|
+
echo_template=t["echo_template"],
|
|
1095
1308
|
)
|
|
1096
1309
|
|
|
1097
1310
|
|
|
@@ -1107,11 +1320,11 @@ def _submit_clarification(state: WizardState, value: str) -> Optional[str]:
|
|
|
1107
1320
|
|
|
1108
1321
|
|
|
1109
1322
|
def _build_pr_template(state: WizardState) -> Prompt:
|
|
1323
|
+
t = _p(state.workspace_root, "pr_template")
|
|
1110
1324
|
return Prompt(
|
|
1111
1325
|
step=S_PR_TEMPLATE, kind="text",
|
|
1112
|
-
label=
|
|
1113
|
-
|
|
1114
|
-
echo_template="pr-template: {value}",
|
|
1326
|
+
label=t["label"],
|
|
1327
|
+
echo_template=t["echo_template"],
|
|
1115
1328
|
)
|
|
1116
1329
|
|
|
1117
1330
|
|
|
@@ -1134,15 +1347,12 @@ def _submit_pr_template(state: WizardState, value: str) -> Optional[str]:
|
|
|
1134
1347
|
|
|
1135
1348
|
|
|
1136
1349
|
def _build_pr_template_scope(state: WizardState) -> Prompt:
|
|
1350
|
+
t = _p(state.workspace_root, "pr_template_scope")
|
|
1137
1351
|
return Prompt(
|
|
1138
1352
|
step=S_PR_TEMPLATE_SCOPE, kind="pick",
|
|
1139
|
-
label="
|
|
1140
|
-
options=[
|
|
1141
|
-
|
|
1142
|
-
_opt("project", "프로젝트에 저장 (project scope)"),
|
|
1143
|
-
_opt("global", "전역에 저장 (global scope)"),
|
|
1144
|
-
],
|
|
1145
|
-
echo_template="pr-template-scope: {value}",
|
|
1353
|
+
label=t["label"],
|
|
1354
|
+
options=[_opt(k, v) for k, v in t["options"].items()],
|
|
1355
|
+
echo_template=t["echo_template"],
|
|
1146
1356
|
)
|
|
1147
1357
|
|
|
1148
1358
|
|
|
@@ -1156,11 +1366,12 @@ def _submit_pr_template_scope(state: WizardState, value: str) -> Optional[str]:
|
|
|
1156
1366
|
|
|
1157
1367
|
|
|
1158
1368
|
def _build_confirm(state: WizardState) -> Prompt:
|
|
1369
|
+
t = _p(state.workspace_root, "confirm")
|
|
1159
1370
|
return Prompt(
|
|
1160
1371
|
step=S_CONFIRM, kind="pick",
|
|
1161
|
-
label="
|
|
1162
|
-
options=[_opt(
|
|
1163
|
-
echo_template="
|
|
1372
|
+
label=t["label"],
|
|
1373
|
+
options=[_opt(k, v) for k, v in t["options"].items()],
|
|
1374
|
+
echo_template=t["echo_template"],
|
|
1164
1375
|
)
|
|
1165
1376
|
|
|
1166
1377
|
|
|
@@ -1172,6 +1383,7 @@ def _submit_confirm(state: WizardState, value: str) -> Optional[str]:
|
|
|
1172
1383
|
|
|
1173
1384
|
|
|
1174
1385
|
def _build_edit_target(state: WizardState) -> Prompt:
|
|
1386
|
+
t = _p(state.workspace_root, "edit_target")
|
|
1175
1387
|
# offer every step that has been answered.
|
|
1176
1388
|
options: list[Option] = []
|
|
1177
1389
|
for sid in state.answered:
|
|
@@ -1180,9 +1392,9 @@ def _build_edit_target(state: WizardState) -> Prompt:
|
|
|
1180
1392
|
options.append(_opt(sid, sid))
|
|
1181
1393
|
return Prompt(
|
|
1182
1394
|
step=S_EDIT_TARGET, kind="pick",
|
|
1183
|
-
label="
|
|
1395
|
+
label=t["label"],
|
|
1184
1396
|
options=options,
|
|
1185
|
-
echo_template="
|
|
1397
|
+
echo_template=t["echo_template"],
|
|
1186
1398
|
)
|
|
1187
1399
|
|
|
1188
1400
|
|
|
@@ -1303,6 +1515,12 @@ STEPS: list[Step] = [
|
|
|
1303
1515
|
or _latest_implementation_planning_report(s) is None)),
|
|
1304
1516
|
build=_build_approved_plan, submit=_submit_approved_plan,
|
|
1305
1517
|
owns=("approved_plan_path", "approved_plan_pending_text")),
|
|
1518
|
+
Step(S_STAGE_PICK,
|
|
1519
|
+
applies=lambda s: (s.task_type == "implementation"
|
|
1520
|
+
and bool(s.approved_plan_path)
|
|
1521
|
+
and S_STAGE_PICK not in s.answered),
|
|
1522
|
+
build=_build_stage_pick, submit=_submit_stage_pick,
|
|
1523
|
+
owns=("selected_stage",)),
|
|
1306
1524
|
Step(S_EXECUTOR,
|
|
1307
1525
|
applies=lambda s: (s.task_type == "implementation"
|
|
1308
1526
|
and bool(s.approved_plan_path)
|
|
@@ -1500,6 +1718,7 @@ _FIELD_DEFAULTS: dict[str, Any] = {
|
|
|
1500
1718
|
"brief_path": "", "reuse_worktree": None, "base_ref": "",
|
|
1501
1719
|
"base_ref_pending_text": False, "approved_plan_path": "",
|
|
1502
1720
|
"approved_plan_pending_text": False,
|
|
1721
|
+
"selected_stage": "auto",
|
|
1503
1722
|
"executor": "", "use_defaults": None, "workers_override": "",
|
|
1504
1723
|
"lead_model": "", "claude_model": "", "codex_model": "",
|
|
1505
1724
|
"gemini_model": "", "report_writer_model": "", "directive": "",
|
|
@@ -1583,6 +1802,7 @@ def render_args(state: WizardState) -> dict[str, str]:
|
|
|
1583
1802
|
"task-brief": state.brief_path,
|
|
1584
1803
|
"executor": state.executor,
|
|
1585
1804
|
"approved-plan": state.approved_plan_path,
|
|
1805
|
+
"stage": (state.selected_stage or "auto") if state.task_type == "implementation" else "",
|
|
1586
1806
|
"base-ref": base_ref,
|
|
1587
1807
|
"workers": workers,
|
|
1588
1808
|
"directive": state.directive,
|
|
@@ -1599,7 +1819,8 @@ def render_args(state: WizardState) -> dict[str, str]:
|
|
|
1599
1819
|
|
|
1600
1820
|
def confirmation_block(state: WizardState) -> str:
|
|
1601
1821
|
"""Human-readable echo of the resolved selections (for the Confirm step)."""
|
|
1602
|
-
|
|
1822
|
+
header = _msg(state.workspace_root, "confirmation", "header")
|
|
1823
|
+
lines: list[str] = [header]
|
|
1603
1824
|
lines.append(f" task-type : {state.task_type}")
|
|
1604
1825
|
lines.append(f" task-key : {state.task_group}/{state.task_id}")
|
|
1605
1826
|
if state.reuse_worktree:
|
|
@@ -1609,7 +1830,7 @@ def confirmation_block(state: WizardState) -> str:
|
|
|
1609
1830
|
if state.task_type == "implementation":
|
|
1610
1831
|
lines.append(f" executor : {state.executor or '(default)'}")
|
|
1611
1832
|
lines.append(
|
|
1612
|
-
|
|
1833
|
+
_msg(state.workspace_root, "confirmation", "workers_implementation_default")
|
|
1613
1834
|
)
|
|
1614
1835
|
else:
|
|
1615
1836
|
roster = state.workers_override or ",".join(state.profile_workers) or "(profile default)"
|