okstra 0.102.1 → 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 (162) hide show
  1. package/README.kr.md +5 -6
  2. package/README.md +5 -6
  3. package/docs/kr/architecture/storage-model.md +1 -1
  4. package/docs/kr/architecture.md +8 -12
  5. package/docs/kr/cli.md +6 -6
  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/cli-registry.mjs +1 -1
  36. package/src/commands/lifecycle/install.mjs +10 -20
  37. package/src/commands/lifecycle/uninstall.mjs +7 -10
  38. package/docs/superpowers/plans/2026-05-12-ticket-id-in-reports.md +0 -638
  39. package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +0 -1568
  40. package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +0 -167
  41. package/docs/superpowers/plans/2026-05-20-final-report-language.md +0 -1501
  42. package/docs/superpowers/plans/2026-05-20-implementation-planning-multi-stage.md +0 -1267
  43. package/docs/superpowers/plans/2026-05-20-okstra-run-prompt-sot-b1.md +0 -1007
  44. package/docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md +0 -720
  45. package/docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md +0 -681
  46. package/docs/superpowers/plans/2026-05-21-improvement-discovery-task-type.md +0 -1691
  47. package/docs/superpowers/plans/2026-05-24-implementation-lead-context-slimming.md +0 -1700
  48. package/docs/superpowers/plans/2026-05-25-okstra-project-root-rename.md +0 -158
  49. package/docs/superpowers/plans/2026-05-26-wizard-3-option-picker.md +0 -860
  50. package/docs/superpowers/plans/2026-06-02-final-verification-protocol-hardening.md +0 -326
  51. package/docs/superpowers/plans/2026-06-02-okstra-run-branch-confirm-step.md +0 -337
  52. package/docs/superpowers/plans/2026-06-02-okstra-run-phase-pane-cleanup.md +0 -410
  53. package/docs/superpowers/plans/2026-06-02-requirements-discovery-fanout.md +0 -728
  54. package/docs/superpowers/plans/2026-06-04-adversarial-implementation-planning.md +0 -294
  55. package/docs/superpowers/plans/2026-06-04-adversarial-verification.md +0 -570
  56. package/docs/superpowers/plans/2026-06-04-coverage-critic.md +0 -516
  57. package/docs/superpowers/plans/2026-06-04-stage-cohesion-planner.md +0 -351
  58. package/docs/superpowers/plans/2026-06-04-stage-run-batching.md +0 -457
  59. package/docs/superpowers/plans/2026-06-05-acceptance-critic.md +0 -251
  60. package/docs/superpowers/plans/2026-06-05-compact-markdown-report-tables.md +0 -323
  61. package/docs/superpowers/plans/2026-06-05-wizard-batch-prompts.md +0 -559
  62. package/docs/superpowers/plans/2026-06-06-final-verification-whole-task-gate.md +0 -993
  63. package/docs/superpowers/plans/2026-06-06-stage-parallel-and-pending-fixes.md +0 -93
  64. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p1.md +0 -447
  65. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p2.md +0 -289
  66. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p3.md +0 -774
  67. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p4.md +0 -303
  68. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p5-multidep-base.md +0 -387
  69. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase2.md +0 -275
  70. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase3.md +0 -282
  71. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4a.md +0 -147
  72. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4b.md +0 -262
  73. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4c.md +0 -184
  74. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4d.md +0 -88
  75. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4e.md +0 -250
  76. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa.md +0 -409
  77. package/docs/superpowers/plans/2026-06-09-implementation-run-artifact-stage-isolation.md +0 -320
  78. package/docs/superpowers/plans/2026-06-10-concurrent-run-team-guard.md +0 -456
  79. package/docs/superpowers/plans/2026-06-10-git-reconcile-stale-sha-recovery.md +0 -1408
  80. package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling-PROBE.md +0 -42
  81. package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling.md +0 -337
  82. package/docs/superpowers/plans/2026-06-10-p6-token-usage-incremental.md +0 -1029
  83. package/docs/superpowers/plans/2026-06-10-stage-group-handoff.md +0 -1572
  84. package/docs/superpowers/plans/2026-06-11-fix-cycle.md +0 -1290
  85. package/docs/superpowers/plans/2026-06-11-wizard-whole-task-final-verification.md +0 -526
  86. package/docs/superpowers/plans/2026-06-12-html-plan-approval.md +0 -1000
  87. package/docs/superpowers/plans/2026-06-13-repo-risk-hardening.md +0 -493
  88. package/docs/superpowers/plans/2026-06-14-host-runtime-auto-execution.md +0 -1775
  89. package/docs/superpowers/plans/2026-06-15-coding-preflight-pack-dispatch-path.md +0 -504
  90. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-final-fixups.md +0 -342
  91. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-fixups.md +0 -258
  92. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-remaining-fixups.md +0 -387
  93. package/docs/superpowers/plans/2026-06-15-internal-skill-resource-migration.md +0 -749
  94. package/docs/superpowers/plans/2026-06-15-worker-prompt-anchor-final-fixups.md +0 -828
  95. package/docs/superpowers/plans/2026-06-15-worker-prompt-header-error-contract.md +0 -490
  96. package/docs/superpowers/plans/2026-06-17-okstra-error-report.md +0 -724
  97. package/docs/superpowers/plans/2026-06-18-subagent-pane-reclaim.md +0 -483
  98. package/docs/superpowers/plans/2026-06-19-agent-operating-standard.md +0 -476
  99. package/docs/superpowers/plans/2026-06-19-okstra-inspect-recap-facet.md +0 -647
  100. package/docs/superpowers/plans/2026-06-20-error-feedback-loop.md +0 -1019
  101. package/docs/superpowers/plans/2026-06-20-run-index-row-normalization.md +0 -583
  102. package/docs/superpowers/plans/2026-06-20-stage-auto-integrate-teardown.md +0 -819
  103. package/docs/superpowers/plans/2026-06-21-okstra-container-local-user-test.md +0 -714
  104. package/docs/superpowers/plans/2026-06-21-release-handoff-local-checkout.md +0 -348
  105. package/docs/superpowers/plans/2026-06-23-implementation-stage-multiselect-chain.md +0 -630
  106. package/docs/superpowers/plans/2026-06-23-implementation-step-commit-granularity.md +0 -274
  107. package/docs/superpowers/plans/2026-06-23-phase-batch-cleanup-enforcement.md +0 -409
  108. package/docs/superpowers/plans/2026-06-24-cross-project-precondition.md +0 -542
  109. package/docs/superpowers/plans/2026-06-24-decision-drafts-render-gap.md +0 -568
  110. package/docs/superpowers/plans/2026-06-24-manual-user-test-section.md +0 -203
  111. package/docs/superpowers/plans/2026-06-24-okstra-container-build-rename.md +0 -525
  112. package/docs/superpowers/specs/2026-05-12-ticket-id-in-reports-design.md +0 -131
  113. package/docs/superpowers/specs/2026-05-15-implementation-plan-verification-design.md +0 -254
  114. package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +0 -383
  115. package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +0 -322
  116. package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +0 -299
  117. package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +0 -335
  118. package/docs/superpowers/specs/2026-06-02-okstra-run-branch-confirm-step-design.md +0 -113
  119. package/docs/superpowers/specs/2026-06-02-okstra-run-phase-pane-cleanup-design.md +0 -173
  120. package/docs/superpowers/specs/2026-06-02-requirements-discovery-fanout-design.md +0 -154
  121. package/docs/superpowers/specs/2026-06-04-adversarial-implementation-planning-design.md +0 -90
  122. package/docs/superpowers/specs/2026-06-04-adversarial-verification-design.md +0 -176
  123. package/docs/superpowers/specs/2026-06-04-coverage-critic-design.md +0 -99
  124. package/docs/superpowers/specs/2026-06-04-stage-splitting-cost-aware-design.md +0 -98
  125. package/docs/superpowers/specs/2026-06-05-acceptance-critic-design.md +0 -90
  126. package/docs/superpowers/specs/2026-06-05-compact-markdown-report-tables-design.md +0 -87
  127. package/docs/superpowers/specs/2026-06-05-wizard-batch-prompts-design.md +0 -121
  128. package/docs/superpowers/specs/2026-06-06-final-verification-whole-task-gate-design.md +0 -126
  129. package/docs/superpowers/specs/2026-06-06-stage-worktree-isolation-design.md +0 -180
  130. package/docs/superpowers/specs/2026-06-06-vertical-slice-tdd-planning-design.md +0 -179
  131. package/docs/superpowers/specs/2026-06-07-stage-conformance-qa-design.md +0 -169
  132. package/docs/superpowers/specs/2026-06-09-executor-model-custom-id-cascade-design.md +0 -66
  133. package/docs/superpowers/specs/2026-06-09-implementation-run-artifact-stage-isolation-design.md +0 -87
  134. package/docs/superpowers/specs/2026-06-10-blocking-contract-posthoc-conformance-design.md +0 -168
  135. package/docs/superpowers/specs/2026-06-10-concurrent-run-team-guard-design.md +0 -107
  136. package/docs/superpowers/specs/2026-06-10-git-reconcile-stale-sha-recovery-design.md +0 -105
  137. package/docs/superpowers/specs/2026-06-10-lead-worker-completion-polling-design.md +0 -113
  138. package/docs/superpowers/specs/2026-06-10-stage-group-handoff-design.md +0 -156
  139. package/docs/superpowers/specs/2026-06-11-brief-entry-only-handoff-stage-entry-design.md +0 -158
  140. package/docs/superpowers/specs/2026-06-11-fix-cycle-design.md +0 -94
  141. package/docs/superpowers/specs/2026-06-11-wizard-whole-task-final-verification-design.md +0 -89
  142. package/docs/superpowers/specs/2026-06-12-codex-lead-adapter-design.md +0 -358
  143. package/docs/superpowers/specs/2026-06-12-html-plan-approval-design.md +0 -85
  144. package/docs/superpowers/specs/2026-06-12-stage-discipline-rule-design.md +0 -108
  145. package/docs/superpowers/specs/2026-06-13-forbidden-actions-ssot-design.md +0 -134
  146. package/docs/superpowers/specs/2026-06-13-neutral-tmux-lead-adapter-design.md +0 -284
  147. package/docs/superpowers/specs/2026-06-14-host-runtime-auto-execution-design.md +0 -404
  148. package/docs/superpowers/specs/2026-06-17-okstra-error-report-design.md +0 -123
  149. package/docs/superpowers/specs/2026-06-18-subagent-pane-reclaim-design.md +0 -150
  150. package/docs/superpowers/specs/2026-06-19-agent-operating-standard-design.md +0 -120
  151. package/docs/superpowers/specs/2026-06-19-okstra-inspect-recap-facet-design.md +0 -96
  152. package/docs/superpowers/specs/2026-06-20-error-feedback-loop-design.md +0 -171
  153. package/docs/superpowers/specs/2026-06-20-run-index-row-normalization-design.md +0 -112
  154. package/docs/superpowers/specs/2026-06-20-stage-auto-integrate-teardown-design.md +0 -145
  155. package/docs/superpowers/specs/2026-06-21-okstra-container-local-user-test-design.md +0 -125
  156. package/docs/superpowers/specs/2026-06-21-release-handoff-local-checkout-design.md +0 -113
  157. package/docs/superpowers/specs/2026-06-23-implementation-stage-multiselect-chain-design.md +0 -138
  158. package/docs/superpowers/specs/2026-06-23-implementation-step-commit-granularity-design.md +0 -120
  159. package/docs/superpowers/specs/2026-06-24-cross-project-precondition-design.md +0 -129
  160. package/docs/superpowers/specs/2026-06-24-decision-drafts-render-gap-design.md +0 -185
  161. package/docs/superpowers/specs/2026-06-24-manual-user-test-section-design.md +0 -121
  162. 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 검사 모두 불변.