okstra 0.102.2 → 0.102.3

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.
Files changed (161) hide show
  1. package/README.kr.md +5 -5
  2. package/README.md +5 -5
  3. package/docs/kr/architecture/storage-model.md +1 -1
  4. package/docs/kr/architecture.md +7 -11
  5. package/docs/kr/cli.md +4 -4
  6. package/docs/kr/container.md +1 -1
  7. package/docs/kr/performance-improvement-plan-v2.md +2 -2
  8. package/docs/project-structure-overview.md +2 -2
  9. package/package.json +7 -2
  10. package/runtime/BUILD.json +2 -2
  11. package/runtime/bin/okstra-antigravity-exec.sh +1 -2
  12. package/runtime/bin/okstra-codex-exec.sh +1 -2
  13. package/runtime/bin/okstra-render-final-report.py +2 -9
  14. package/runtime/bin/okstra-spawn-followups.py +4 -6
  15. package/runtime/bin/okstra-trace-cleanup.sh +24 -4
  16. package/runtime/prompts/lead/okstra-lead-contract.md +2 -2
  17. package/runtime/prompts/lead/report-writer.md +1 -1
  18. package/runtime/prompts/profiles/_common-contract.md +9 -11
  19. package/runtime/python/okstra_ctl/codex_dispatch.py +4 -12
  20. package/runtime/python/okstra_ctl/consumers.py +35 -0
  21. package/runtime/python/okstra_ctl/container.py +4 -6
  22. package/runtime/python/okstra_ctl/dispatch_core.py +4 -12
  23. package/runtime/python/okstra_ctl/final_report_paths.py +24 -0
  24. package/runtime/python/okstra_ctl/handoff.py +2 -1
  25. package/runtime/python/okstra_ctl/implementation_stage.py +7 -11
  26. package/runtime/python/okstra_ctl/report_view_artifacts.py +14 -0
  27. package/runtime/python/okstra_ctl/report_views.py +7 -4
  28. package/runtime/python/okstra_ctl/run.py +3 -10
  29. package/runtime/python/okstra_ctl/stage_targets.py +18 -0
  30. package/runtime/python/okstra_ctl/wizard.py +24 -25
  31. package/runtime/python/okstra_ctl/wizard_stage_intent.py +54 -0
  32. package/runtime/python/okstra_token_usage/report.py +2 -5
  33. package/runtime/validators/validate-report-views.py +2 -1
  34. package/runtime/validators/validate-run.py +1 -11
  35. package/src/commands/lifecycle/install.mjs +5 -5
  36. package/src/commands/lifecycle/uninstall.mjs +4 -4
  37. package/docs/superpowers/plans/2026-05-12-ticket-id-in-reports.md +0 -638
  38. package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +0 -1568
  39. package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +0 -167
  40. package/docs/superpowers/plans/2026-05-20-final-report-language.md +0 -1501
  41. package/docs/superpowers/plans/2026-05-20-implementation-planning-multi-stage.md +0 -1267
  42. package/docs/superpowers/plans/2026-05-20-okstra-run-prompt-sot-b1.md +0 -1007
  43. package/docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md +0 -720
  44. package/docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md +0 -681
  45. package/docs/superpowers/plans/2026-05-21-improvement-discovery-task-type.md +0 -1691
  46. package/docs/superpowers/plans/2026-05-24-implementation-lead-context-slimming.md +0 -1700
  47. package/docs/superpowers/plans/2026-05-25-okstra-project-root-rename.md +0 -158
  48. package/docs/superpowers/plans/2026-05-26-wizard-3-option-picker.md +0 -860
  49. package/docs/superpowers/plans/2026-06-02-final-verification-protocol-hardening.md +0 -326
  50. package/docs/superpowers/plans/2026-06-02-okstra-run-branch-confirm-step.md +0 -337
  51. package/docs/superpowers/plans/2026-06-02-okstra-run-phase-pane-cleanup.md +0 -410
  52. package/docs/superpowers/plans/2026-06-02-requirements-discovery-fanout.md +0 -728
  53. package/docs/superpowers/plans/2026-06-04-adversarial-implementation-planning.md +0 -294
  54. package/docs/superpowers/plans/2026-06-04-adversarial-verification.md +0 -570
  55. package/docs/superpowers/plans/2026-06-04-coverage-critic.md +0 -516
  56. package/docs/superpowers/plans/2026-06-04-stage-cohesion-planner.md +0 -351
  57. package/docs/superpowers/plans/2026-06-04-stage-run-batching.md +0 -457
  58. package/docs/superpowers/plans/2026-06-05-acceptance-critic.md +0 -251
  59. package/docs/superpowers/plans/2026-06-05-compact-markdown-report-tables.md +0 -323
  60. package/docs/superpowers/plans/2026-06-05-wizard-batch-prompts.md +0 -559
  61. package/docs/superpowers/plans/2026-06-06-final-verification-whole-task-gate.md +0 -993
  62. package/docs/superpowers/plans/2026-06-06-stage-parallel-and-pending-fixes.md +0 -93
  63. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p1.md +0 -447
  64. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p2.md +0 -289
  65. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p3.md +0 -774
  66. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p4.md +0 -303
  67. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p5-multidep-base.md +0 -387
  68. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase2.md +0 -275
  69. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase3.md +0 -282
  70. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4a.md +0 -147
  71. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4b.md +0 -262
  72. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4c.md +0 -184
  73. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4d.md +0 -88
  74. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4e.md +0 -250
  75. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa.md +0 -409
  76. package/docs/superpowers/plans/2026-06-09-implementation-run-artifact-stage-isolation.md +0 -320
  77. package/docs/superpowers/plans/2026-06-10-concurrent-run-team-guard.md +0 -456
  78. package/docs/superpowers/plans/2026-06-10-git-reconcile-stale-sha-recovery.md +0 -1408
  79. package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling-PROBE.md +0 -42
  80. package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling.md +0 -337
  81. package/docs/superpowers/plans/2026-06-10-p6-token-usage-incremental.md +0 -1029
  82. package/docs/superpowers/plans/2026-06-10-stage-group-handoff.md +0 -1572
  83. package/docs/superpowers/plans/2026-06-11-fix-cycle.md +0 -1290
  84. package/docs/superpowers/plans/2026-06-11-wizard-whole-task-final-verification.md +0 -526
  85. package/docs/superpowers/plans/2026-06-12-html-plan-approval.md +0 -1000
  86. package/docs/superpowers/plans/2026-06-13-repo-risk-hardening.md +0 -493
  87. package/docs/superpowers/plans/2026-06-14-host-runtime-auto-execution.md +0 -1775
  88. package/docs/superpowers/plans/2026-06-15-coding-preflight-pack-dispatch-path.md +0 -504
  89. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-final-fixups.md +0 -342
  90. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-fixups.md +0 -258
  91. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-remaining-fixups.md +0 -387
  92. package/docs/superpowers/plans/2026-06-15-internal-skill-resource-migration.md +0 -749
  93. package/docs/superpowers/plans/2026-06-15-worker-prompt-anchor-final-fixups.md +0 -828
  94. package/docs/superpowers/plans/2026-06-15-worker-prompt-header-error-contract.md +0 -490
  95. package/docs/superpowers/plans/2026-06-17-okstra-error-report.md +0 -724
  96. package/docs/superpowers/plans/2026-06-18-subagent-pane-reclaim.md +0 -483
  97. package/docs/superpowers/plans/2026-06-19-agent-operating-standard.md +0 -476
  98. package/docs/superpowers/plans/2026-06-19-okstra-inspect-recap-facet.md +0 -647
  99. package/docs/superpowers/plans/2026-06-20-error-feedback-loop.md +0 -1019
  100. package/docs/superpowers/plans/2026-06-20-run-index-row-normalization.md +0 -583
  101. package/docs/superpowers/plans/2026-06-20-stage-auto-integrate-teardown.md +0 -819
  102. package/docs/superpowers/plans/2026-06-21-okstra-container-local-user-test.md +0 -714
  103. package/docs/superpowers/plans/2026-06-21-release-handoff-local-checkout.md +0 -348
  104. package/docs/superpowers/plans/2026-06-23-implementation-stage-multiselect-chain.md +0 -630
  105. package/docs/superpowers/plans/2026-06-23-implementation-step-commit-granularity.md +0 -274
  106. package/docs/superpowers/plans/2026-06-23-phase-batch-cleanup-enforcement.md +0 -409
  107. package/docs/superpowers/plans/2026-06-24-cross-project-precondition.md +0 -542
  108. package/docs/superpowers/plans/2026-06-24-decision-drafts-render-gap.md +0 -568
  109. package/docs/superpowers/plans/2026-06-24-manual-user-test-section.md +0 -203
  110. package/docs/superpowers/plans/2026-06-24-okstra-container-build-rename.md +0 -525
  111. package/docs/superpowers/specs/2026-05-12-ticket-id-in-reports-design.md +0 -131
  112. package/docs/superpowers/specs/2026-05-15-implementation-plan-verification-design.md +0 -254
  113. package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +0 -383
  114. package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +0 -322
  115. package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +0 -299
  116. package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +0 -335
  117. package/docs/superpowers/specs/2026-06-02-okstra-run-branch-confirm-step-design.md +0 -113
  118. package/docs/superpowers/specs/2026-06-02-okstra-run-phase-pane-cleanup-design.md +0 -173
  119. package/docs/superpowers/specs/2026-06-02-requirements-discovery-fanout-design.md +0 -154
  120. package/docs/superpowers/specs/2026-06-04-adversarial-implementation-planning-design.md +0 -90
  121. package/docs/superpowers/specs/2026-06-04-adversarial-verification-design.md +0 -176
  122. package/docs/superpowers/specs/2026-06-04-coverage-critic-design.md +0 -99
  123. package/docs/superpowers/specs/2026-06-04-stage-splitting-cost-aware-design.md +0 -98
  124. package/docs/superpowers/specs/2026-06-05-acceptance-critic-design.md +0 -90
  125. package/docs/superpowers/specs/2026-06-05-compact-markdown-report-tables-design.md +0 -87
  126. package/docs/superpowers/specs/2026-06-05-wizard-batch-prompts-design.md +0 -121
  127. package/docs/superpowers/specs/2026-06-06-final-verification-whole-task-gate-design.md +0 -126
  128. package/docs/superpowers/specs/2026-06-06-stage-worktree-isolation-design.md +0 -180
  129. package/docs/superpowers/specs/2026-06-06-vertical-slice-tdd-planning-design.md +0 -179
  130. package/docs/superpowers/specs/2026-06-07-stage-conformance-qa-design.md +0 -169
  131. package/docs/superpowers/specs/2026-06-09-executor-model-custom-id-cascade-design.md +0 -66
  132. package/docs/superpowers/specs/2026-06-09-implementation-run-artifact-stage-isolation-design.md +0 -87
  133. package/docs/superpowers/specs/2026-06-10-blocking-contract-posthoc-conformance-design.md +0 -168
  134. package/docs/superpowers/specs/2026-06-10-concurrent-run-team-guard-design.md +0 -107
  135. package/docs/superpowers/specs/2026-06-10-git-reconcile-stale-sha-recovery-design.md +0 -105
  136. package/docs/superpowers/specs/2026-06-10-lead-worker-completion-polling-design.md +0 -113
  137. package/docs/superpowers/specs/2026-06-10-stage-group-handoff-design.md +0 -156
  138. package/docs/superpowers/specs/2026-06-11-brief-entry-only-handoff-stage-entry-design.md +0 -158
  139. package/docs/superpowers/specs/2026-06-11-fix-cycle-design.md +0 -94
  140. package/docs/superpowers/specs/2026-06-11-wizard-whole-task-final-verification-design.md +0 -89
  141. package/docs/superpowers/specs/2026-06-12-codex-lead-adapter-design.md +0 -358
  142. package/docs/superpowers/specs/2026-06-12-html-plan-approval-design.md +0 -85
  143. package/docs/superpowers/specs/2026-06-12-stage-discipline-rule-design.md +0 -108
  144. package/docs/superpowers/specs/2026-06-13-forbidden-actions-ssot-design.md +0 -134
  145. package/docs/superpowers/specs/2026-06-13-neutral-tmux-lead-adapter-design.md +0 -284
  146. package/docs/superpowers/specs/2026-06-14-host-runtime-auto-execution-design.md +0 -404
  147. package/docs/superpowers/specs/2026-06-17-okstra-error-report-design.md +0 -123
  148. package/docs/superpowers/specs/2026-06-18-subagent-pane-reclaim-design.md +0 -150
  149. package/docs/superpowers/specs/2026-06-19-agent-operating-standard-design.md +0 -120
  150. package/docs/superpowers/specs/2026-06-19-okstra-inspect-recap-facet-design.md +0 -96
  151. package/docs/superpowers/specs/2026-06-20-error-feedback-loop-design.md +0 -171
  152. package/docs/superpowers/specs/2026-06-20-run-index-row-normalization-design.md +0 -112
  153. package/docs/superpowers/specs/2026-06-20-stage-auto-integrate-teardown-design.md +0 -145
  154. package/docs/superpowers/specs/2026-06-21-okstra-container-local-user-test-design.md +0 -125
  155. package/docs/superpowers/specs/2026-06-21-release-handoff-local-checkout-design.md +0 -113
  156. package/docs/superpowers/specs/2026-06-23-implementation-stage-multiselect-chain-design.md +0 -138
  157. package/docs/superpowers/specs/2026-06-23-implementation-step-commit-granularity-design.md +0 -120
  158. package/docs/superpowers/specs/2026-06-24-cross-project-precondition-design.md +0 -129
  159. package/docs/superpowers/specs/2026-06-24-decision-drafts-render-gap-design.md +0 -185
  160. package/docs/superpowers/specs/2026-06-24-manual-user-test-section-design.md +0 -121
  161. package/docs/superpowers/specs/2026-06-24-okstra-container-build-rename-design.md +0 -110
@@ -1,559 +0,0 @@
1
- # okstra wizard 멀티탭 배치 프롬프트 구현 계획
2
-
3
- > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
-
5
- **Goal:** customize 분기에서 서로 의존 없는 픽 step들을 멀티탭 `AskUserQuestion` 한 번으로 묶어 wizard 입력 왕복 수를 줄인다.
6
-
7
- **Architecture:** 기존 `Step` 레지스트리는 그대로 두고 "방출 계층"에만 그룹 개념을 추가한다. `next_prompt`가 첫 적용 가능한 미답변 step이 그룹 멤버이면 같은 그룹의 적용가능·미답변 픽 멤버를 최대 4개까지 모아 새 `kind="pick_group"` 프롬프트로 내보내고, `submit`은 JSON `--answer`를 각 멤버 `submit()`으로 라우팅한다. `answered`/`owns`/edit-rewind/`_ready_for_confirm`은 전부 개별 step id 단위로 유지된다.
8
-
9
- **Tech Stack:** Python 3 (`scripts/okstra_ctl/wizard.py`), pytest (`tests/`), okstra-run 스킬 마크다운.
10
-
11
- 설계 문서: [docs/superpowers/specs/2026-06-05-wizard-batch-prompts-design.md](../specs/2026-06-05-wizard-batch-prompts-design.md)
12
-
13
- ---
14
-
15
- ## File Structure
16
-
17
- - Modify: `scripts/okstra_ctl/wizard.py`
18
- - `Prompt` 데이터클래스 — `questions` 필드 + `to_json` 확장 ([wizard.py:299](../../../scripts/okstra_ctl/wizard.py))
19
- - 그룹 상수/정의 — S_* 상수 블록 직후 ([wizard.py:206](../../../scripts/okstra_ctl/wizard.py))
20
- - `_build_group_prompt` 신규 + `next_prompt` 수정 ([wizard.py:2221](../../../scripts/okstra_ctl/wizard.py))
21
- - `_submit_group` 신규 + `submit` 수정 ([wizard.py:2232](../../../scripts/okstra_ctl/wizard.py))
22
- - Modify: `skills/okstra-run/SKILL.md` — `pick_group` 렌더 규칙 ([SKILL.md:41](../../../skills/okstra-run/SKILL.md))
23
- - Modify: `tests/test_okstra_ctl_wizard.py` — 그룹 흐름 반영 (`test_error_analysis_full_customize` 등)
24
- - Create: `tests/test_wizard_pick_group.py` — 그룹 방출/제출 단위 테스트
25
-
26
- ---
27
-
28
- ## Task 1: `pick_group` 데이터 모델
29
-
30
- **Files:**
31
- - Modify: `scripts/okstra_ctl/wizard.py:299-318` (`Prompt`)
32
- - Modify: `scripts/okstra_ctl/wizard.py:206` (그룹 상수)
33
- - Test: `tests/test_wizard_pick_group.py`
34
-
35
- - [ ] **Step 1: 실패 테스트 작성**
36
-
37
- `tests/test_wizard_pick_group.py` 생성:
38
-
39
- ```python
40
- """pick_group 방출/제출 단위 테스트."""
41
- from __future__ import annotations
42
-
43
- import json
44
- import sys
45
- from pathlib import Path
46
-
47
- LIB_DIR = Path(__file__).resolve().parent.parent / "scripts"
48
- sys.path.insert(0, str(LIB_DIR))
49
-
50
- from okstra_ctl.wizard import ( # noqa: E402
51
- GROUP_MAX_TABS,
52
- GROUP_MODELS,
53
- PROMPT_GROUPS,
54
- Option,
55
- Prompt,
56
- )
57
-
58
-
59
- def test_pick_group_to_json_carries_questions():
60
- members = [
61
- Prompt(step="lead_model", kind="pick", label="Lead",
62
- options=[Option("default", "default")]),
63
- Prompt(step="claude_model", kind="pick", label="Claude",
64
- options=[Option("sonnet", "sonnet")]),
65
- ]
66
- p = Prompt(step=GROUP_MODELS, kind="pick_group",
67
- label="모델", questions=members)
68
- out = p.to_json()
69
- assert out["kind"] == "pick_group"
70
- assert [q["step"] for q in out["questions"]] == ["lead_model", "claude_model"]
71
- assert out["questions"][0]["options"][0]["value"] == "default"
72
- assert out["questions"][1]["multi"] is False
73
-
74
-
75
- def test_group_definitions_cover_model_and_option_picks():
76
- assert GROUP_MAX_TABS == 4
77
- assert "lead_model" in PROMPT_GROUPS[GROUP_MODELS]
78
- assert "report_writer_model" in PROMPT_GROUPS[GROUP_MODELS]
79
- ```
80
-
81
- - [ ] **Step 2: 실패 확인**
82
-
83
- Run: `python3 -m pytest tests/test_wizard_pick_group.py -v`
84
- Expected: FAIL — `ImportError: cannot import name 'GROUP_MODELS'` / `Prompt` has no `questions`.
85
-
86
- - [ ] **Step 3: 구현 — `Prompt.questions` + 그룹 상수**
87
-
88
- `scripts/okstra_ctl/wizard.py` `Prompt` 데이터클래스 ([:299](../../../scripts/okstra_ctl/wizard.py)) 에 필드 추가 (`multi` 줄 바로 아래):
89
-
90
- ```python
91
- multi: bool = False # only meaningful when kind == "pick"
92
- # only meaningful when kind == "pick_group": one entry per AskUserQuestion tab
93
- questions: list["Prompt"] = field(default_factory=list)
94
- ```
95
-
96
- `to_json` ([:309](../../../scripts/okstra_ctl/wizard.py)) 를 교체:
97
-
98
- ```python
99
- def to_json(self) -> dict[str, Any]:
100
- out = {
101
- "step": self.step,
102
- "kind": self.kind,
103
- "label": self.label,
104
- "options": [asdict(o) for o in self.options],
105
- "help": self.help,
106
- "echoTemplate": self.echo_template,
107
- "multi": self.multi,
108
- }
109
- if self.kind == "pick_group":
110
- out["questions"] = [
111
- {"step": q.step, "label": q.label,
112
- "options": [asdict(o) for o in q.options],
113
- "multi": q.multi}
114
- for q in self.questions
115
- ]
116
- return out
117
- ```
118
-
119
- S_* 상수 블록 직후 (`S_DONE = "done"` 다음, [:206](../../../scripts/okstra_ctl/wizard.py)) 에 그룹 정의 추가:
120
-
121
- ```python
122
- # ---- 멀티탭 배치 프롬프트 그룹 (방출 계층 전용) ----
123
- # 그룹 id 는 S_* 가 아니므로 prompts JSON SOT / step-id 동기화 검사 대상이 아니다.
124
- GROUP_MODELS = "models"
125
- GROUP_OPTIONS = "options"
126
- GROUP_MAX_TABS = 4 # AskUserQuestion 의 질문(탭) 수 한도
127
-
128
- # 멤버는 모두 서로 의존이 없는 단일선택 픽 step 이어야 한다.
129
- # *_TEXT 후속 / workers_override / pr_template_scope 는 의존성 때문에 개별 유지.
130
- PROMPT_GROUPS: dict[str, tuple[str, ...]] = {
131
- GROUP_MODELS: (S_LEAD_MODEL, S_EXECUTOR_MODEL, S_CLAUDE_MODEL,
132
- S_CODEX_MODEL, S_GEMINI_MODEL, S_REPORT_WRITER_MODEL),
133
- GROUP_OPTIONS: (S_DIRECTIVE_PICK, S_RELATED_TASKS_PICK,
134
- S_CLARIFICATION_PICK, S_PR_TEMPLATE_PICK),
135
- }
136
- GROUP_LABELS: dict[str, str] = {
137
- GROUP_MODELS: "모델 선택 (탭별로 선택)",
138
- GROUP_OPTIONS: "추가 옵션 (탭별로 선택)",
139
- }
140
- _STEP_TO_GROUP: dict[str, str] = {
141
- sid: gid for gid, ids in PROMPT_GROUPS.items() for sid in ids
142
- }
143
- ```
144
-
145
- - [ ] **Step 4: 통과 확인**
146
-
147
- Run: `python3 -m pytest tests/test_wizard_pick_group.py -v`
148
- Expected: PASS (2 passed)
149
-
150
- - [ ] **Step 5: 커밋**
151
-
152
- ```bash
153
- git add scripts/okstra_ctl/wizard.py tests/test_wizard_pick_group.py
154
- git commit -m "feat(wizard): add pick_group prompt model and group definitions"
155
- ```
156
-
157
- ---
158
-
159
- ## Task 2: `next_prompt` 그룹 방출
160
-
161
- **Files:**
162
- - Modify: `scripts/okstra_ctl/wizard.py:2221-2229` (`next_prompt`)
163
- - Test: `tests/test_wizard_pick_group.py`
164
-
165
- - [ ] **Step 1: 실패 테스트 작성**
166
-
167
- `tests/test_wizard_pick_group.py` 에 추가:
168
-
169
- ```python
170
- from okstra_ctl.wizard import next_prompt # noqa: E402
171
-
172
-
173
- def _models_state():
174
- """모델 그룹 직전까지 진행된 상태를 직접 구성한다 (비-implementation)."""
175
- from okstra_ctl.wizard import WizardState
176
- s = WizardState(workspace_root=".", project_root=".", project_id="p")
177
- s.task_type = "error-analysis"
178
- s.brief_path = "/tmp/brief.md"
179
- s.base_ref = "main"
180
- s.profile_workers = ["claude", "codex", "report-writer"]
181
- s.profile_optional_workers = ["gemini"]
182
- s.use_defaults = False
183
- s.workers_override = "claude,codex,report-writer"
184
- s.critic = "off"
185
- # identity/critic/workers 단계를 answered 로 마킹해 모델 그룹이 첫 미답변이 되게 한다.
186
- s.answered = [
187
- "task_pick", "brief_path", "task_group", "task_id", "task_type",
188
- "base_ref_pick", "critic_pick", "defaults_or_custom",
189
- "workers_override",
190
- ]
191
- return s
192
-
193
-
194
- def test_next_prompt_emits_models_group():
195
- p = next_prompt(_models_state())
196
- assert p.kind == "pick_group"
197
- assert p.step == GROUP_MODELS
198
- steps = [q.step for q in p.questions]
199
- # 로스터에 gemini 없음 → gemini_model 제외, executor 는 비-impl 이라 제외
200
- assert steps == ["lead_model", "claude_model", "codex_model",
201
- "report_writer_model"]
202
- assert len(steps) <= GROUP_MAX_TABS
203
-
204
-
205
- def test_models_group_caps_at_four_then_emits_remainder():
206
- s = _models_state()
207
- s.profile_workers = ["claude", "codex", "gemini", "report-writer"]
208
- s.profile_optional_workers = []
209
- s.workers_override = "claude,codex,gemini,report-writer"
210
- p = next_prompt(s)
211
- assert p.kind == "pick_group"
212
- assert [q.step for q in p.questions] == [
213
- "lead_model", "claude_model", "codex_model", "gemini_model"]
214
- # 첫 4개를 answered 처리하면 5번째(report_writer)는 단일 픽으로 방출
215
- s.answered += ["lead_model", "claude_model", "codex_model", "gemini_model"]
216
- p2 = next_prompt(s)
217
- assert p2.kind == "pick"
218
- assert p2.step == "report_writer_model"
219
- ```
220
-
221
- - [ ] **Step 2: 실패 확인**
222
-
223
- Run: `python3 -m pytest tests/test_wizard_pick_group.py -k next_prompt -v`
224
- Expected: FAIL — `next_prompt` 가 `kind == "pick"` (S_LEAD_MODEL) 를 반환.
225
-
226
- - [ ] **Step 3: 구현**
227
-
228
- `scripts/okstra_ctl/wizard.py` `next_prompt` ([:2221](../../../scripts/okstra_ctl/wizard.py)) 를 교체하고 헬퍼를 그 위에 추가:
229
-
230
- ```python
231
- def _build_group_prompt(state: WizardState, group_id: str) -> Prompt:
232
- """그룹의 적용가능·미답변 픽 멤버를 최대 GROUP_MAX_TABS 개 모은다.
233
-
234
- 멤버가 1개뿐이면 멀티탭 UI가 불필요하므로 그 멤버의 평범한 픽을 반환한다.
235
- """
236
- members: list[Prompt] = []
237
- for sid in PROMPT_GROUPS[group_id]:
238
- if sid in state.answered:
239
- continue
240
- step = STEP_BY_ID[sid]
241
- if not step.applies(state):
242
- continue
243
- members.append(step.build(state))
244
- if len(members) >= GROUP_MAX_TABS:
245
- break
246
- if len(members) == 1:
247
- return members[0]
248
- return Prompt(step=group_id, kind="pick_group",
249
- label=GROUP_LABELS[group_id], questions=members)
250
-
251
-
252
- def next_prompt(state: WizardState) -> Prompt:
253
- if state.confirmed:
254
- return Prompt(step=S_DONE, kind="done")
255
- for step in STEPS:
256
- if step.id in state.answered:
257
- continue
258
- if step.applies(state):
259
- group_id = _STEP_TO_GROUP.get(step.id)
260
- if group_id is not None:
261
- return _build_group_prompt(state, group_id)
262
- return step.build(state)
263
- return Prompt(step=S_DONE, kind="done")
264
- ```
265
-
266
- - [ ] **Step 4: 통과 확인**
267
-
268
- Run: `python3 -m pytest tests/test_wizard_pick_group.py -k next_prompt -v`
269
- Expected: PASS (2 passed)
270
-
271
- - [ ] **Step 5: 커밋**
272
-
273
- ```bash
274
- git add scripts/okstra_ctl/wizard.py tests/test_wizard_pick_group.py
275
- git commit -m "feat(wizard): emit pick_group in next_prompt with 4-tab cap"
276
- ```
277
-
278
- ---
279
-
280
- ## Task 3: `submit` 그룹 라우팅
281
-
282
- **Files:**
283
- - Modify: `scripts/okstra_ctl/wizard.py:2232-2246` (`submit`)
284
- - Test: `tests/test_wizard_pick_group.py`
285
-
286
- - [ ] **Step 1: 실패 테스트 작성**
287
-
288
- `tests/test_wizard_pick_group.py` 에 추가:
289
-
290
- ```python
291
- import pytest # noqa: E402
292
-
293
- from okstra_ctl.wizard import WizardError, submit # noqa: E402
294
-
295
-
296
- def test_submit_group_routes_json_to_members():
297
- s = _models_state()
298
- answer = json.dumps({
299
- "lead_model": "default",
300
- "claude_model": "sonnet",
301
- "codex_model": "gpt-5.5",
302
- "report_writer_model": "default",
303
- })
304
- result = submit(s, answer)
305
- assert s.claude_model == "sonnet"
306
- assert s.codex_model == "gpt-5.5"
307
- assert s.lead_model == "" # default → 빈 문자열
308
- assert s.report_writer_model == ""
309
- # 모든 멤버가 개별적으로 answered 처리됨
310
- for sid in ("lead_model", "claude_model", "codex_model",
311
- "report_writer_model"):
312
- assert sid in s.answered
313
- assert result["next"]["step"] != GROUP_MODELS
314
-
315
-
316
- def test_submit_group_missing_key_defaults():
317
- s = _models_state()
318
- result = submit(s, json.dumps({"claude_model": "sonnet"}))
319
- assert s.claude_model == "sonnet"
320
- assert s.lead_model == ""
321
- assert "lead_model" in s.answered
322
-
323
-
324
- def test_submit_group_invalid_json_raises():
325
- s = _models_state()
326
- with pytest.raises(WizardError):
327
- submit(s, "not-json")
328
-
329
-
330
- def test_submit_group_invalid_model_raises_and_no_marking():
331
- s = _models_state()
332
- with pytest.raises(WizardError):
333
- submit(s, json.dumps({"claude_model": "totally-unknown-model"}))
334
- # 검증 실패 → 어떤 멤버도 answered 로 마킹되지 않음(전부 재-프롬프트)
335
- assert "claude_model" not in s.answered
336
- assert "lead_model" not in s.answered
337
- ```
338
-
339
- - [ ] **Step 2: 실패 확인**
340
-
341
- Run: `python3 -m pytest tests/test_wizard_pick_group.py -k submit -v`
342
- Expected: FAIL — `submit` 가 group step을 `STEP_BY_ID[prompt.step]` 에서 찾다 `KeyError`.
343
-
344
- - [ ] **Step 3: 구현**
345
-
346
- `scripts/okstra_ctl/wizard.py` `submit` ([:2232](../../../scripts/okstra_ctl/wizard.py)) 를 교체하고 헬퍼를 그 위에 추가:
347
-
348
- ```python
349
- def _submit_group(state: WizardState, prompt: Prompt, value: str) -> dict[str, Any]:
350
- """pick_group 답(JSON 객체)을 각 멤버 submit() 으로 라우팅한다.
351
-
352
- 전부 검증 통과한 뒤에만 멤버들을 answered 로 마킹한다(부분 적용 금지).
353
- 멤버 submit 이 WizardError 를 던지면 그대로 전파되어 같은 그룹을 재-프롬프트한다.
354
- """
355
- try:
356
- answers = json.loads(value or "{}")
357
- except json.JSONDecodeError as exc:
358
- raise WizardError(f"pick_group answer must be a JSON object: {exc}")
359
- if not isinstance(answers, dict):
360
- raise WizardError("pick_group answer must be a JSON object")
361
- echoes: list[str] = []
362
- for q in prompt.questions:
363
- echo = STEP_BY_ID[q.step].submit(state, str(answers.get(q.step, "") or ""))
364
- if echo:
365
- echoes.append(echo)
366
- for q in prompt.questions:
367
- if q.step not in state.answered:
368
- state.answered.append(q.step)
369
- nxt = next_prompt(state)
370
- return {"echo": "; ".join(echoes), "next": nxt.to_json()}
371
-
372
-
373
- def submit(state: WizardState, value: str) -> dict[str, Any]:
374
- """Validate the answer for the *currently active* step and advance.
375
-
376
- Returns {"echo": "...", "next": <Prompt JSON>}. Raises WizardError on
377
- validation failure (caller may re-prompt).
378
- """
379
- prompt = next_prompt(state)
380
- if prompt.kind == "done":
381
- return {"echo": "", "next": prompt.to_json()}
382
- if prompt.kind == "pick_group":
383
- return _submit_group(state, prompt, value)
384
- step = STEP_BY_ID[prompt.step]
385
- echo = step.submit(state, value or "")
386
- if prompt.step not in state.answered:
387
- state.answered.append(prompt.step)
388
- nxt = next_prompt(state)
389
- return {"echo": echo or "", "next": nxt.to_json()}
390
- ```
391
-
392
- > 참고: `_validate_model` 가 WizardError 를 던지면 그 시점 이전 멤버의 state 필드는 이미
393
- > 설정되어 있지만 `answered` 에는 추가되지 않는다. 재-프롬프트 시 같은 그룹이 다시 나오고
394
- > 사용자가 다시 고른 값으로 덮어쓰므로 결과적으로 안전하다.
395
-
396
- - [ ] **Step 4: 통과 확인**
397
-
398
- Run: `python3 -m pytest tests/test_wizard_pick_group.py -v`
399
- Expected: PASS (전체)
400
-
401
- - [ ] **Step 5: 커밋**
402
-
403
- ```bash
404
- git add scripts/okstra_ctl/wizard.py tests/test_wizard_pick_group.py
405
- git commit -m "feat(wizard): route pick_group JSON answers to member submits"
406
- ```
407
-
408
- ---
409
-
410
- ## Task 4: 기존 customize 흐름 테스트 갱신
411
-
412
- **Files:**
413
- - Modify: `tests/test_okstra_ctl_wizard.py:389-501` (`test_error_analysis_full_customize`)
414
- - Modify: `tests/test_okstra_ctl_wizard.py` (그 외 `S_LEAD_MODEL`/`directive_pick` 단일픽을 가정하는 테스트)
415
-
416
- - [ ] **Step 1: 영향 테스트 식별**
417
-
418
- Run: `python3 -m pytest tests/test_okstra_ctl_wizard.py -v`
419
- Expected: 모델/옵션 단일픽을 가정하던 테스트가 FAIL (now `pick_group`). 실패 목록을 확인한다.
420
-
421
- - [ ] **Step 2: `test_error_analysis_full_customize` 갱신**
422
-
423
- `tests/test_okstra_ctl_wizard.py:444-466` (lead~report-writer 개별 블록) 을 그룹 제출로 교체:
424
-
425
- ```python
426
- # 7) 모델 그룹 (lead + claude + codex + report-writer, 로스터에 gemini 없음)
427
- p = next_prompt(state)
428
- assert p.kind == "pick_group"
429
- assert p.step == "models"
430
- assert [q.step for q in p.questions] == [
431
- "lead_model", "claude_model", "codex_model", "report_writer_model"]
432
- submit(state, json.dumps({
433
- "lead_model": "default",
434
- "claude_model": "sonnet",
435
- "codex_model": "gpt-5.5",
436
- "report_writer_model": "default",
437
- }))
438
- assert state.lead_model == ""
439
- assert state.claude_model == "sonnet"
440
- assert state.codex_model == "gpt-5.5"
441
- ```
442
-
443
- 이어서 `tests/test_okstra_ctl_wizard.py:468-481` (directive/related/clarification 개별 블록) 을 옵션 그룹 제출로 교체:
444
-
445
- ```python
446
- # 8) 옵션 그룹 (directive + related-tasks + clarification 픽)
447
- p = next_prompt(state)
448
- assert p.kind == "pick_group"
449
- assert p.step == "options"
450
- assert [q.step for q in p.questions] == [
451
- "directive_pick", "related_tasks_pick", "clarification_pick"]
452
- submit(state, json.dumps({
453
- "directive_pick": "__skip__",
454
- "related_tasks_pick": "__skip__",
455
- "clarification_pick": "__skip__",
456
- }))
457
- ```
458
-
459
- 파일 상단 import 에 `import json` 이 없으면 추가한다 (이미 있으면 생략).
460
-
461
- - [ ] **Step 3: 다른 영향 테스트 갱신**
462
-
463
- Step 1 에서 FAIL 한 나머지 테스트(예: `test_implementation_customize_has_executor_model_and_no_workers_override` [:611](../../../tests/test_okstra_ctl_wizard.py), 그 외 `assert p.step == S_LEAD_MODEL` / `== "directive_pick"` 를 직접 가정하는 테스트)를 동일 패턴으로 갱신한다. implementation 모델 그룹 멤버는 `["lead_model", "executor_model", "report_writer_model"]` 이다.
464
-
465
- 각 실패 테스트에서:
466
- - `assert p.step == S_LEAD_MODEL` → 그룹 방출 (`p.kind == "pick_group"`, `p.step == "models"`) 로 변경하고, 개별 `submit(state, "...")` 호출을 단일 JSON `submit` 으로 합친다.
467
- - `assert p.step == "directive_pick"` → `options` 그룹 방출로 변경.
468
-
469
- - [ ] **Step 4: 전체 통과 확인**
470
-
471
- Run: `python3 -m pytest tests/test_okstra_ctl_wizard.py tests/test_wizard_pick_group.py -v`
472
- Expected: PASS (전체)
473
-
474
- - [ ] **Step 5: 커밋**
475
-
476
- ```bash
477
- git add tests/test_okstra_ctl_wizard.py
478
- git commit -m "test(wizard): adapt customize-branch tests to grouped prompts"
479
- ```
480
-
481
- ---
482
-
483
- ## Task 5: SKILL.md `pick_group` 렌더 규칙
484
-
485
- **Files:**
486
- - Modify: `skills/okstra-run/SKILL.md:41-50`, `:96-98`
487
-
488
- - [ ] **Step 1: "How the wizard talks to you" 섹션에 규칙 추가**
489
-
490
- `skills/okstra-run/SKILL.md` 의 `kind` 설명 목록 ([:43](../../../skills/okstra-run/SKILL.md)) 바로 아래(`kind: "pick"` + `multi: true` 항목 다음)에 추가:
491
-
492
- ```markdown
493
- - `kind: "pick_group"` → render a SINGLE `AskUserQuestion` whose `questions` array maps 1:1 to the wizard's `questions[]`. For each entry use `questions[].label`, `questions[].options[].label`, and `multiSelect: questions[].multi`. Collect the user's chosen `options[].value` per tab, build a JSON object keyed by each `questions[].step`, and submit it as a single literal `--answer '{"lead_model":"opus","claude_model":"default",...}'`. A tab the user leaves at its default still gets its `"default"`/`""` value in the JSON. Never split a `pick_group` into multiple `AskUserQuestion` calls — the wizard already capped it at 4 tabs and will emit any remainder as the next prompt.
494
- ```
495
-
496
- - [ ] **Step 2: 프롬프트 루프 섹션에 동일 분기 추가**
497
-
498
- `skills/okstra-run/SKILL.md` Step 3 의 Render 목록 ([:96](../../../skills/okstra-run/SKILL.md)) 에 추가:
499
-
500
- ```markdown
501
- - `pick_group` → one `AskUserQuestion` with one question per `questions[]` entry (tab). Map each tab's selected `value` back by `questions[].step`, assemble a JSON object, and submit it as a single literal `--answer '<json>'`.
502
- ```
503
-
504
- - [ ] **Step 3: 빌드 동기화**
505
-
506
- Run: `npm run build`
507
- Expected: 성공, `runtime/` 에 SKILL.md 반영.
508
-
509
- - [ ] **Step 4: 커밋**
510
-
511
- ```bash
512
- git add skills/okstra-run/SKILL.md runtime/
513
- git commit -m "docs(skills/okstra-run): render pick_group as one multi-tab AskUserQuestion"
514
- ```
515
-
516
- ---
517
-
518
- ## Task 6: 회귀 검증
519
-
520
- **Files:** (없음 — 검증만)
521
-
522
- - [ ] **Step 1: 전체 단위 테스트**
523
-
524
- Run: `python3 -m pytest tests/ -q`
525
- Expected: 전체 PASS (특히 `tests/test_wizard_prompts.py` step-id 동기화/orphan 검사 — 그룹 상수는 `S_*` 가 아니므로 영향 없음).
526
-
527
- - [ ] **Step 2: phase contract validator**
528
-
529
- Run: `bash validators/validate-workflow.sh`
530
- Expected: 통과.
531
-
532
- - [ ] **Step 3: CLI smoke**
533
-
534
- Run: `node bin/okstra --version`
535
- Expected: 버전 출력.
536
-
537
- - [ ] **Step 4: CHANGES.md 항목 추가**
538
-
539
- `CHANGES.md` 상단에 사용자 영향 항목 추가:
540
-
541
- ```markdown
542
- - okstra-run: customize 단계의 모델/옵션 선택을 멀티탭으로 묶어 입력 왕복 횟수를 줄임.
543
- 사용자 영향: lead/worker/report 모델과 directive/related-tasks/clarification 픽을 한 화면에서 선택.
544
- ```
545
-
546
- - [ ] **Step 5: 커밋**
547
-
548
- ```bash
549
- git add CHANGES.md
550
- git commit -m "docs(changes): log multi-tab wizard prompts"
551
- ```
552
-
553
- ---
554
-
555
- ## Self-Review 메모
556
-
557
- - **Spec coverage**: pick_group 모델(§3.1)=Task1, 그룹정의(§3.2)=Task1, next_prompt(§3.3)=Task2, 제출(§3.4)=Task3, SKILL(§3.5)=Task5, 테스트(§5)=Task3·4·6. 전 항목 매핑됨.
558
- - **Type 일관성**: `GROUP_MODELS`/`GROUP_OPTIONS`/`GROUP_MAX_TABS`/`PROMPT_GROUPS`/`GROUP_LABELS`/`_STEP_TO_GROUP`/`_build_group_prompt`/`_submit_group`/`Prompt.questions` 식별자가 전 태스크에서 동일 표기.
559
- - **비영향**: `use_defaults=True` 경로, identity 단계, confirm/edit-target, `render_args`, `_ready_for_confirm` 의 개별 step 검사 모두 불변.