okstra 0.49.0 → 0.50.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.
Files changed (52) hide show
  1. package/docs/kr/architecture.md +8 -8
  2. package/docs/kr/cli.md +2 -2
  3. package/docs/project-structure-overview.md +3 -3
  4. package/docs/superpowers/plans/2026-06-05-wizard-batch-prompts.md +559 -0
  5. package/docs/superpowers/specs/2026-06-05-wizard-batch-prompts-design.md +121 -0
  6. package/docs/task-process/error-analysis.md +1 -1
  7. package/docs/task-process/final-verification.md +1 -1
  8. package/docs/task-process/release-handoff.md +1 -1
  9. package/docs/task-process/requirements-discovery.md +1 -1
  10. package/package.json +1 -1
  11. package/runtime/BUILD.json +2 -2
  12. package/runtime/agents/SKILL.md +3 -3
  13. package/runtime/agents/workers/claude-worker.md +1 -1
  14. package/runtime/agents/workers/codex-worker.md +1 -1
  15. package/runtime/agents/workers/gemini-worker.md +1 -1
  16. package/runtime/agents/workers/report-writer-worker.md +3 -3
  17. package/runtime/bin/okstra-render-report-views.py +1 -1
  18. package/runtime/prompts/launch.template.md +1 -1
  19. package/runtime/prompts/profiles/_common-contract.md +11 -11
  20. package/runtime/prompts/profiles/_implementation-deliverable.md +1 -1
  21. package/runtime/prompts/profiles/_implementation-executor.md +1 -1
  22. package/runtime/prompts/profiles/_implementation-verifier.md +1 -1
  23. package/runtime/prompts/profiles/error-analysis.md +1 -1
  24. package/runtime/prompts/profiles/final-verification.md +2 -2
  25. package/runtime/prompts/profiles/implementation-planning.md +9 -9
  26. package/runtime/prompts/profiles/improvement-discovery.md +5 -5
  27. package/runtime/prompts/profiles/release-handoff.md +2 -2
  28. package/runtime/prompts/profiles/requirements-discovery.md +2 -2
  29. package/runtime/python/okstra_ctl/clarification_items.py +11 -11
  30. package/runtime/python/okstra_ctl/render.py +1 -1
  31. package/runtime/python/okstra_ctl/render_final_report.py +1 -1
  32. package/runtime/python/okstra_ctl/report_views.py +12 -12
  33. package/runtime/python/okstra_ctl/run.py +3 -3
  34. package/runtime/python/okstra_ctl/wizard.py +90 -3
  35. package/runtime/python/okstra_ctl/workflow.py +1 -1
  36. package/runtime/skills/okstra-brief/SKILL.md +1 -1
  37. package/runtime/skills/okstra-convergence/SKILL.md +8 -8
  38. package/runtime/skills/okstra-report-writer/SKILL.md +22 -22
  39. package/runtime/skills/okstra-run/SKILL.md +2 -0
  40. package/runtime/skills/okstra-team-contract/SKILL.md +1 -1
  41. package/runtime/templates/reports/final-report.template.md +187 -187
  42. package/runtime/templates/reports/i18n/en.json +4 -4
  43. package/runtime/templates/reports/i18n/ko.json +4 -4
  44. package/runtime/templates/reports/implementation-planning-input.template.md +1 -1
  45. package/runtime/templates/reports/release-handoff-input.template.md +1 -1
  46. package/runtime/templates/reports/user-response.template.md +1 -1
  47. package/runtime/templates/worker-prompt-preamble.md +1 -1
  48. package/runtime/validators/lib/fixtures.sh +2 -2
  49. package/runtime/validators/validate-implementation-plan-stages.py +9 -9
  50. package/runtime/validators/validate-report-views.py +10 -10
  51. package/runtime/validators/validate-run.py +36 -36
  52. package/runtime/validators/validate_improvement_report.py +8 -8
@@ -343,7 +343,7 @@ okstra phase 는 PRD / issue file 을 직접 쓰지 않습니다. 동등한 결
343
343
  |---|---|---|---|---|
344
344
  | `requirements-discovery` | 요청을 bugfix/feature/refactor/ops/improvement 중 하나로 분류하고 안전한 다음 phase로 라우팅 | work category, routing decision, missing-input list, clarification requests | `pending-routing-decision` (사용자 답변 후 결정) | 금지 |
345
345
  | `error-analysis` | 보고된 에러/사고의 증상·원인·재현 갭을 증거 기반으로 분석 | symptom/trigger 정리, root-cause 가설, reproduction gap, validation 경로 | `implementation-planning` | 금지 |
346
- | `implementation-planning` | 코딩 시작 전 안전한 구현 방향과 옵션을 평가 | 최소 2개 구현 옵션, 영향 파일 목록, trade-off, 단계별 실행 순서, validation/rollback, **User Approval Request** 블록, **§4.5.9 Plan Body Verification** (Phase 6 워커 사후 검증 라운드 — 합성된 plan 의 내적 일관성을 워커가 `AGREE` / `DISAGREE(a-e)` / `SUPPLEMENT` 로 cross-verify; gate 결과가 `passed` / `passed-with-dissent` 일 때만 Approval 마커 렌더, `blocked-by-disagreement` / `aborted-non-result` 일 때는 majority DISAGREE 항목이 `## 5. Clarification Items` 의 `Blocks=approval` row 로 변환됨). **산출 구조**: 항상 `## 4.5 Stage Map` + N 개의 `## 4.5.<i> Stage <i>` 섹션. 각 stage 의 effective step ≤ 6. `depends-on (none)` 인 stage 들은 별도 `implementation` run 으로 병렬 실행 가능 | `implementation` (사용자 승인 후) | 금지 |
346
+ | `implementation-planning` | 코딩 시작 전 안전한 구현 방향과 옵션을 평가 | 최소 2개 구현 옵션, 영향 파일 목록, trade-off, 단계별 실행 순서, validation/rollback, **User Approval Request** 블록, **§5.5.9 Plan Body Verification** (Phase 6 워커 사후 검증 라운드 — 합성된 plan 의 내적 일관성을 워커가 `AGREE` / `DISAGREE(a-e)` / `SUPPLEMENT` 로 cross-verify; gate 결과가 `passed` / `passed-with-dissent` 일 때만 Approval 마커 렌더, `blocked-by-disagreement` / `aborted-non-result` 일 때는 majority DISAGREE 항목이 `## 1. Clarification Items` 의 `Blocks=approval` row 로 변환됨). **산출 구조**: 항상 `## 5.5 Stage Map` + N 개의 `## 5.5.<i> Stage <i>` 섹션. 각 stage 의 effective step ≤ 6. `depends-on (none)` 인 stage 들은 별도 `implementation` run 으로 병렬 실행 가능 | `implementation` (사용자 승인 후) | 금지 |
347
347
  | `implementation` | 승인된 `implementation-planning` final report의 단계대로 소스 코드를 수정. **한 run 에 한 stage 만 실행** (`--stage <auto\|N>` 인수로 stage 선택) | commit list, diff summary, out-of-plan edits 블록, validation/TDD evidence, rollback 검증, verifier 결과(Gemini/Codex/Claude), `carry/stage-<N>.json` evidence sidecar | `final-verification` | 허용 (승인된 plan의 파일 목록 한정, `git push`/publish/deploy/실제 migration 금지) |
348
348
  | `final-verification` | 완료된 작업의 잔존 결함·회귀 위험을 점검하고 release 판단 | acceptance verdict, residual risk, follow-up 라우팅(`error-analysis`/`implementation-planning`/`release-handoff`) | `pending-release-handoff` (verdict 가 `accepted` 일 때만 `release-handoff` 로 진입; 그 외에는 `error-analysis` 또는 `implementation-planning` 으로 리라우팅) | 금지 (read-only 테스트만 허용) |
349
349
  | `release-handoff` | `accepted` 받은 변경을 사용자가 선택한 방식대로 커밋·푸시·PR 로 전달 | 사용자 메뉴 응답(H1 action / H2 PR base / H3 message handling) 기록, 실행한 git/gh 명령 로그, commit SHA 목록, PR URL | `done-or-follow-up` | 허용 — 단 **사용자가 메뉴로 선택한 mutating 명령만** 실행. `git push --force*`, base 브랜치 직접 push, `--no-verify`, `gh release`, publish/deploy 는 금지. source code 자체는 수정 금지(이전 `implementation` 의 diff 를 그대로 패키징). |
@@ -370,12 +370,12 @@ okstra phase 는 PRD / issue file 을 직접 쓰지 않습니다. 동등한 결
370
370
  [brief: scope=codebase + priority-lenses]
371
371
  ↓ okstra-run --task-type improvement-discovery
372
372
  [improvement-discovery]
373
- ↓ final-report (## 4.9 Improvement Candidates 후보 N개)
373
+ ↓ final-report (## 5.9 Improvement Candidates 후보 N개)
374
374
  ↓ (사용자가 후보 K개 선택, 각각 새 brief 작성)
375
375
  [requirements-discovery | implementation-planning | error-analysis] (선택된 후보별로 새 task-id 로)
376
376
  ````
377
377
 
378
- `PHASE_SEQUENCE` 의 정식 멤버에 들어가지 않는 sidetrack entry-point. 단방향 라이프사이클을 깨지 않으면서 코드베이스 발견 시나리오를 흡수한다. lens 화이트리스트와 candidate-cap 은 `scripts/okstra_ctl/improvement_lenses.py` SSOT 1개에서 통일된다. final-report 의 `## 4.9 Improvement Candidates` 표 (10 column) 는 `validators/validate_improvement_report.py` 의 11항목 contract 가 강제한다. 양방향 grilling 두 지점 (`okstra-brief` Step 4 강화 budget 8 + lead 의 Phase 1.5 reflect-back budget 12) 으로 사용자와 AI 의 이해도를 일치시킨다.
378
+ `PHASE_SEQUENCE` 의 정식 멤버에 들어가지 않는 sidetrack entry-point. 단방향 라이프사이클을 깨지 않으면서 코드베이스 발견 시나리오를 흡수한다. lens 화이트리스트와 candidate-cap 은 `scripts/okstra_ctl/improvement_lenses.py` SSOT 1개에서 통일된다. final-report 의 `## 5.9 Improvement Candidates` 표 (10 column) 는 `validators/validate_improvement_report.py` 의 11항목 contract 가 강제한다. 양방향 grilling 두 지점 (`okstra-brief` Step 4 강화 budget 8 + lead 의 Phase 1.5 reflect-back budget 12) 으로 사용자와 AI 의 이해도를 일치시킨다.
379
379
 
380
380
  ### requirements-discovery fan-out
381
381
 
@@ -860,11 +860,11 @@ Claude가 작성하는 최종 보고서는 아래 구조를 우선 사용합니
860
860
 
861
861
  - `## Verdict Card` — **최상단 의무 섹션**. Final Conclusion / Verdict Token / Direction / Approval Required? / Next Step 5 행. Verdict Token / Direction / Next Step 셀은 본문 §2 (실행 현황) 와 §6 (다음 단계) 의 권위 셀과 byte-match 해야 합니다.
862
862
  - (선택) `## 0. Clarification Response Carried In From Previous Run` — 직전 run 에서 응답이 carry-in 된 경우에만 렌더링. 빈 carry-in 일 때는 헤딩 자체를 출력하지 않습니다.
863
- - `## 1. 문제 또는 검증 대상 요약` — §1.1 Consensus / §1.2 Differences 표 각각 `Source items (worker:item)` 컬럼 보존 (cross-worker traceability).
863
+ - `## 1. 문제 또는 검증 대상 요약` — §6.1 Consensus / §6.2 Differences 표 각각 `Source items (worker:item)` 컬럼 보존 (cross-worker traceability).
864
864
  - `## 2. 에이전트별 실행 현황`
865
- - `## 3. Cross Verification 결과` — §3.1 Primary Evidence 에 `Source items (worker:item)` + `Source (path:line / log)` 컬럼.
866
- - `## 4. 최종 판단` — `implementation-planning` 의 §4.5.9 Plan Body Verification 은 `Verdict details` 표 (5-열, plan item × worker) 하나로 emit.
867
- - `## 5. Clarification Items` — 통합 8-열 표 한 곳. 기존 §5.1 / §5.2 / §4.5.8 / §4.5.9 Open Questions 는 deprecated 되어 validator 가 등장 시 fail.
865
+ - `## 3. Cross Verification 결과` — §2.1 Primary Evidence 에 `Source items (worker:item)` + `Source (path:line / log)` 컬럼.
866
+ - `## 4. 최종 판단` — `implementation-planning` 의 §5.5.9 Plan Body Verification 은 `Verdict details` 표 (5-열, plan item × worker) 하나로 emit.
867
+ - `## 1. Clarification Items` — 통합 8-열 표 한 곳. 기존 §6.1 / §6.2 / §5.5.8 / §5.5.9 Open Questions 는 deprecated 되어 validator 가 등장 시 fail.
868
868
  - `## 6. 권장 다음 단계`
869
869
  - `## Token Usage Summary` — sentinel (`pending` / `N/A` / `--` / `?` / 빈 셀) 또는 zero (`0` / `$0.00`) 박제 시 validator 가 출고를 차단합니다. `Codex/Gemini CLI 추가 비용` 행만 "CLI 미사용" 의미로 `$0.00` 허용.
870
870
 
@@ -940,7 +940,7 @@ Phase 7 step 1.5 가 final-report MD 한 본을 입력으로 두 view 를 결정
940
940
  phase 산출물의 출고 가능 여부를 강제하는 진입점:
941
941
 
942
942
  - `validators/validate-workflow.sh` — phase contract 통합 검증.
943
- - `validators/validate-run.py` — run-level final-report 본문 contract (Verdict Card 존재, deprecated §5.1/§5.2/§4.5.8/§4.5.9 Open Questions 부재, Plan Body Verification gate × Approval 마커 cross-check, Token Usage sentinel/zero 차단, 워커-결과 audit 사이드카 존재).
943
+ - `validators/validate-run.py` — run-level final-report 본문 contract (Verdict Card 존재, deprecated §6.1/§6.2/§5.5.8/§5.5.9 Open Questions 부재, Plan Body Verification gate × Approval 마커 cross-check, Token Usage sentinel/zero 차단, 워커-결과 audit 사이드카 존재).
944
944
  - `validators/validate-report-views.py` — slim MD / HTML view 의 phase substring 보존 및 form-control 영역 검사.
945
945
  - `validators/validate-brief.py` — brief schema (front-matter, `Reporter Confirmations` 섹션 존재, root parent-id self 규칙, slug 컨벤션 등) 강제. `bash validators/validate-brief.sh <brief.md>` 가 thin wrapper.
946
946
 
package/docs/kr/cli.md CHANGED
@@ -118,7 +118,7 @@ interactive terminal에서 실행하면 다음 규칙이 추가로 적용됩니
118
118
  - `scan-scope`: 1개 이상의 경로.
119
119
  - `out-of-scope`: 선택.
120
120
  - `candidate-cap`: 1–12, 기본 8.
121
- - 출력: `## 4.9 Improvement Candidates` 표 (10 column: Cand ID / Lens / Title / Scope / Severity / Effort / Consensus / Source workers / Recommended next-phase / Evidence).
121
+ - 출력: `## 5.9 Improvement Candidates` 표 (10 column: Cand ID / Lens / Title / Scope / Severity / Effort / Consensus / Source workers / Recommended next-phase / Evidence).
122
122
  - Verdict Token: `candidates-ready` / `no-candidates` / `blocked`.
123
123
  - 라우팅: 자동 spin-off 없음. 사용자가 후보를 골라 새 task-id 로 `requirements-discovery` / `implementation-planning` / `error-analysis` 진입.
124
124
  - 워커: claude + codex + gemini + report-writer 모두 필수.
@@ -475,7 +475,7 @@ scripts/okstra.sh --task-type error-analysis --related-tasks scanner-regression,
475
475
 
476
476
  `implementation-planning` task-type 의 Phase 6 plan-body verification 라운드를 끕니다. 기본값은 활성. 다른 task-type 에서는 무시됩니다.
477
477
 
478
- - **활성 (default)**: Phase 6 에서 Report writer worker 가 final-report draft 를 작성한 직후, lead 가 합성된 plan 의 §4.5 본문 (Option Candidates / Stepwise Execution Order / Dependency / Validation Checklist / Rollback) 을 `P-*` plan-item 단위로 쪼개 모든 analyser 워커 (`claude`, `codex`, 그리고 옵트인된 `gemini`) 에게 reverify dispatch 합니다. 워커의 평결 (`AGREE` / `DISAGREE(a-e)` / `SUPPLEMENT`) 을 집계해 4 가지 gate result (`passed` / `passed-with-dissent` / `blocked-by-disagreement` / `aborted-non-result`) 중 하나를 산출하고, `passed` / `passed-with-dissent` 일 때만 final-report 상단의 `- [ ] Approved` 마커가 렌더됩니다. majority DISAGREE 항목은 `## 5. Clarification Items` 의 `Blocks=approval` row 로 변환됩니다 (자동 revise 없음 — 사용자가 답변 후 같은 phase 를 resume 해야 함).
478
+ - **활성 (default)**: Phase 6 에서 Report writer worker 가 final-report draft 를 작성한 직후, lead 가 합성된 plan 의 §4.5 본문 (Option Candidates / Stepwise Execution Order / Dependency / Validation Checklist / Rollback) 을 `P-*` plan-item 단위로 쪼개 모든 analyser 워커 (`claude`, `codex`, 그리고 옵트인된 `gemini`) 에게 reverify dispatch 합니다. 워커의 평결 (`AGREE` / `DISAGREE(a-e)` / `SUPPLEMENT`) 을 집계해 4 가지 gate result (`passed` / `passed-with-dissent` / `blocked-by-disagreement` / `aborted-non-result`) 중 하나를 산출하고, `passed` / `passed-with-dissent` 일 때만 final-report 상단의 `- [ ] Approved` 마커가 렌더됩니다. majority DISAGREE 항목은 `## 1. Clarification Items` 의 `Blocks=approval` row 로 변환됩니다 (자동 revise 없음 — 사용자가 답변 후 같은 phase 를 resume 해야 함).
479
479
  - **비활성 (`--no-plan-verification` 전달 시)**: Phase 6 sub-step 전체가 skip 되고 final-report 상단의 Approval 마커가 무조건 렌더됩니다 (legacy 동작). 빠른 반복용 opt-out — handoff-ready plan 에는 권장하지 않습니다.
480
480
  - 본 flag 는 manifest 의 `convergence.planBodyVerification.enabled` 를 `false` 로 기록합니다. resume 명령에서도 같은 flag 를 명시해야 같은 동작이 유지됩니다 (`_canonical_argv` 가 resume fidelity emit 을 보장).
481
481
  - 자세한 라운드 프로토콜 / verdict semantics / state 파일 스키마는 `skills/okstra-convergence/SKILL.md` 의 "Plan-body verification mode (implementation-planning only)" 섹션 참고.
@@ -237,7 +237,7 @@ Token/cost accounting:
237
237
  | `validate-brief.py`, `validate-brief.sh` | Brief frontmatter/body contract validation |
238
238
  | `validate-report-views.py` | Slim/HTML view validation |
239
239
  | `validate-schedule.py` | Schedule section/order/code validation |
240
- | `validate-implementation-plan-stages.py` | Stage Map 구조 강제 — S1–S8 규칙 검사 (`## 4.5 Stage Map` + `## 4.5.<i> Stage <i>` 섹션, stage 당 step ≤ 6 등) |
240
+ | `validate-implementation-plan-stages.py` | Stage Map 구조 강제 — S1–S8 규칙 검사 (`## 5.5 Stage Map` + `## 5.5.<i> Stage <i>` 섹션, stage 당 step ≤ 6 등) |
241
241
  | `validate_improvement_report.py` | improvement-discovery final-report 의 11항목 contract 강제. `validate-run.py` 가 `task_type == "improvement-discovery"` 일 때 자동 호출 |
242
242
  | `validate-workflow.sh` | End-to-end fixture workflow validation |
243
243
  | `lib/*.sh` | Shared shell validator helpers and fixtures |
@@ -392,7 +392,7 @@ Project-local `<PROJECT_ROOT>/.claude/settings.local.json` is provisioned as a s
392
392
  |---|---|---|
393
393
  | `requirements-discovery` | Classify and route work | `error-analysis` or `implementation-planning` |
394
394
  | `error-analysis` | Reproduce and explain failure | `implementation-planning` |
395
- | `implementation-planning` | Compare options, produce approval-ready plan; 산출은 항상 `## 4.5 Stage Map` + N 개의 `## 4.5.<i> Stage <i>` 섹션 구조. `implementation` 은 stage 단위로 분할 실행 가능 | `implementation` after approval |
395
+ | `implementation-planning` | Compare options, produce approval-ready plan; 산출은 항상 `## 5.5 Stage Map` + N 개의 `## 5.5.<i> Stage <i>` 섹션 구조. `implementation` 은 stage 단위로 분할 실행 가능 | `implementation` after approval |
396
396
  | `implementation` | Executor changes code, verifiers check independently | `final-verification` |
397
397
  | `final-verification` | Read-only acceptance verification | `release-handoff` if accepted |
398
398
  | `release-handoff` | User-selected commit/PR handoff | done or follow-up |
@@ -436,7 +436,7 @@ When changing code, keep these docs in sync:
436
436
  | `Verdict Token` | `accepted`, `conditional-accept`, `blocked`, `not-applicable` |
437
437
  | `Direction` | `continue-investigation`, `begin-implementation`, `approve`, `reject`, `hold` |
438
438
 
439
- Clarifications now live in the unified `## 5. Clarification Items` table. Deprecated `5.1` / `5.2` split sections are no longer part of the schema.
439
+ Clarifications now live in the unified `## 1. Clarification Items` table. Deprecated `5.1` / `5.2` split sections are no longer part of the schema.
440
440
 
441
441
  ---
442
442
 
@@ -0,0 +1,559 @@
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 검사 모두 불변.