okstra 0.34.1 → 0.36.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.kr.md +27 -19
- package/README.md +27 -19
- package/docs/kr/architecture.md +59 -45
- package/docs/kr/cli.md +61 -18
- package/docs/pr-template-usage.md +65 -0
- package/docs/project-structure-overview.md +353 -354
- package/docs/superpowers/plans/2026-05-12-ticket-id-in-reports.md +1 -1
- package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +1 -1
- package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +1 -1
- package/docs/superpowers/plans/2026-05-20-final-report-language.md +1501 -0
- package/docs/superpowers/plans/2026-05-20-implementation-planning-multi-stage.md +1267 -0
- package/docs/superpowers/plans/2026-05-20-okstra-run-prompt-sot-b1.md +1007 -0
- package/docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md +720 -0
- package/docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md +681 -0
- package/docs/superpowers/plans/2026-05-21-improvement-discovery-task-type.md +1691 -0
- package/docs/superpowers/plans/2026-05-24-implementation-lead-context-slimming.md +1700 -0
- package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +383 -0
- package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +320 -0
- package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +299 -0
- package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +335 -0
- package/docs/task-process/README.md +74 -0
- package/docs/task-process/common-flow.md +166 -0
- package/docs/task-process/error-analysis.md +101 -0
- package/docs/task-process/final-verification.md +167 -0
- package/docs/task-process/implementation-planning.md +128 -0
- package/docs/task-process/implementation.md +149 -0
- package/docs/task-process/release-handoff.md +206 -0
- package/docs/task-process/requirements-discovery.md +115 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/SKILL.md +30 -7
- package/runtime/agents/workers/claude-worker.md +31 -6
- package/runtime/agents/workers/codex-worker.md +37 -10
- package/runtime/agents/workers/gemini-worker.md +34 -7
- package/runtime/agents/workers/report-writer-worker.md +19 -10
- package/runtime/bin/okstra-central.sh +6 -6
- package/runtime/bin/okstra-codex-exec.sh +49 -28
- package/runtime/bin/okstra-gemini-exec.sh +39 -21
- package/runtime/bin/okstra-render-final-report.py +13 -2
- package/runtime/bin/okstra-wrapper-status.py +155 -0
- package/runtime/bin/okstra.sh +2 -2
- package/runtime/prompts/launch.template.md +1 -0
- package/runtime/prompts/profiles/_common-contract.md +11 -6
- package/runtime/prompts/profiles/_implementation-deliverable.md +53 -0
- package/runtime/prompts/profiles/_implementation-executor.md +60 -0
- package/runtime/prompts/profiles/_implementation-verifier.md +76 -0
- package/runtime/prompts/profiles/error-analysis.md +3 -7
- package/runtime/prompts/profiles/implementation-planning.md +22 -21
- package/runtime/prompts/profiles/implementation.md +28 -118
- package/runtime/prompts/profiles/improvement-discovery.md +42 -0
- package/runtime/prompts/profiles/release-handoff.md +1 -1
- package/runtime/prompts/profiles/requirements-discovery.md +8 -12
- package/runtime/prompts/wizard/prompts.ko.json +230 -0
- package/runtime/python/lib/okstra/cli.sh +2 -49
- package/runtime/python/lib/okstra/globals.sh +21 -21
- package/runtime/python/lib/okstra/interactive.sh +7 -7
- package/runtime/python/okstra_ctl/clarification_items.py +3 -9
- package/runtime/python/okstra_ctl/consumers.py +53 -0
- package/runtime/python/okstra_ctl/final_report_schema.py +0 -7
- package/runtime/python/okstra_ctl/i18n.py +73 -0
- package/runtime/python/okstra_ctl/improvement_lenses.py +44 -0
- package/runtime/python/okstra_ctl/index.py +1 -1
- package/runtime/python/okstra_ctl/paths.py +26 -20
- package/runtime/python/okstra_ctl/render.py +166 -207
- package/runtime/python/okstra_ctl/render_final_report.py +53 -10
- package/runtime/python/okstra_ctl/run.py +299 -108
- package/runtime/python/okstra_ctl/run_context.py +22 -0
- package/runtime/python/okstra_ctl/seeding.py +186 -0
- package/runtime/python/okstra_ctl/session.py +65 -7
- package/runtime/python/okstra_ctl/wizard.py +348 -127
- package/runtime/python/okstra_ctl/workflow.py +21 -2
- package/runtime/python/okstra_ctl/worktree.py +54 -1
- package/runtime/python/okstra_project/resolver.py +4 -3
- package/runtime/python/okstra_token_usage/report.py +2 -2
- package/runtime/schemas/final-report-v1.0.schema.json +22 -16
- package/runtime/skills/okstra-brief/SKILL.md +102 -218
- package/runtime/skills/okstra-convergence/SKILL.md +2 -3
- package/runtime/skills/okstra-inspect/SKILL.md +581 -0
- package/runtime/skills/okstra-report-writer/SKILL.md +35 -15
- package/runtime/skills/okstra-run/SKILL.md +8 -7
- package/runtime/skills/okstra-schedule/SKILL.md +14 -157
- package/runtime/skills/okstra-setup/SKILL.md +28 -1
- package/runtime/skills/okstra-team-contract/SKILL.md +16 -107
- package/runtime/templates/okstra.CLAUDE.md +104 -0
- package/runtime/templates/reports/brief.template.md +204 -0
- package/runtime/templates/reports/final-report.template.md +93 -98
- package/runtime/templates/reports/i18n/en.json +135 -0
- package/runtime/templates/reports/i18n/ko.json +135 -0
- package/runtime/templates/reports/implementation-planning-input.template.md +18 -0
- package/runtime/templates/reports/improvement-discovery-input.template.md +78 -0
- package/runtime/templates/reports/schedule.template.md +12 -3
- package/runtime/templates/reports/task-brief.template.md +2 -2
- package/runtime/templates/worker-prompt-preamble.md +108 -0
- package/runtime/validators/lib/fixtures.sh +30 -0
- package/runtime/validators/lib/runners.sh +1 -1
- package/runtime/validators/validate-implementation-plan-stages.py +211 -0
- package/runtime/validators/validate-run.py +121 -26
- package/runtime/validators/validate-workflow.sh +2 -2
- package/runtime/validators/validate_improvement_report.py +275 -0
- package/src/config.mjs +18 -0
- package/src/install.mjs +41 -14
- package/src/setup.mjs +133 -1
- package/src/uninstall.mjs +27 -3
- package/runtime/skills/okstra-history/SKILL.md +0 -165
- package/runtime/skills/okstra-logs/SKILL.md +0 -173
- package/runtime/skills/okstra-report-finder/SKILL.md +0 -111
- package/runtime/skills/okstra-status/SKILL.md +0 -246
- package/runtime/skills/okstra-time-summary/SKILL.md +0 -172
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
# Wizard Prompt JSON SOT (Phase A1) Implementation Plan
|
|
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:** `scripts/okstra_ctl/wizard.py` 의 한글 UI 문자열 (label / echo_template / 정적 option label) 을 `prompts/wizard/prompts.ko.json` 단독 권위로 분리한다. wizard.py 는 상태 머신 + 동적 옵션 계산만 담당, 한글 문자열 0개.
|
|
6
|
+
|
|
7
|
+
**Architecture:**
|
|
8
|
+
1. JSON SOT 파일 `prompts/wizard/prompts.ko.json` — step id 별로 `label`, `echo_template`, (optional) `options{}` 보유.
|
|
9
|
+
2. wizard.py 가 `workspace_root` 를 통해 JSON 을 lazy-load (기존 `prompts/profiles/*.md` 패턴과 동일 — install 시 `~/.okstra/` 로 복사 안 되고 npm 패키지의 `runtime/prompts/` 에서 직접 읽힘).
|
|
10
|
+
3. 신규 `_p(workspace_root, step_id, **vars) -> dict` 헬퍼가 단일 lookup 진입점. placeholder 보간은 Python `str.format(**vars)`.
|
|
11
|
+
4. `_build_*` 함수 ~28 개 일괄 migrate (atomic commit).
|
|
12
|
+
5. 새 unit test 가 (a) step id 동기화 (wizard.py 의 `S_*` 상수 ↔ JSON 키 동치), (b) `_p()` placeholder 동작, (c) wizard.py 내 한글 문자 0개 grep gate 강제.
|
|
13
|
+
|
|
14
|
+
**왜 JSON 인가** (spec 의 YAML 결정 변경):
|
|
15
|
+
- 프로젝트는 npm 으로 배포 (`npx okstra@latest install`) — `pip install` 단계 없음.
|
|
16
|
+
- PyYAML 은 stdlib 가 아니라 vendor 필요 — ~500KB 추가 비용. JSON 은 stdlib (`json`) — zero dep.
|
|
17
|
+
- 구조 (label / echo_template / options) 와 placeholder 의미는 spec 와 동일.
|
|
18
|
+
- 사용자 결정 (2026-05-20): JSON 채택.
|
|
19
|
+
|
|
20
|
+
**Tech Stack:** Python 3 stdlib (`json`, `pathlib`, `re`), pytest, bash (grep gate)
|
|
21
|
+
|
|
22
|
+
**Reference:** [docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md](../specs/2026-05-20-okstra-run-prompt-sot-design.md) §3 (Phase A1). 본 plan 은 spec 의 YAML → JSON 으로 format 만 변경.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## File Structure
|
|
27
|
+
|
|
28
|
+
**생성**
|
|
29
|
+
- `prompts/wizard/prompts.ko.json` — 단일 SOT. ~28 step 엔트리.
|
|
30
|
+
- `tests/test_wizard_prompts.py` — SOT-sync + `_p()` + Korean-char grep gate (Task 2 에서 생성, Task 4 에서 확장)
|
|
31
|
+
|
|
32
|
+
**수정**
|
|
33
|
+
- `scripts/okstra_ctl/wizard.py` — Task 2 에서 loader / `_p()` helper 추가, Task 3 에서 `_build_*` migrate
|
|
34
|
+
- `CHANGES.md` — Task 5 사용자 영향 항목 추가
|
|
35
|
+
|
|
36
|
+
**손대지 않음**
|
|
37
|
+
- `tools/build.mjs` — 이미 `prompts/` 디렉터리 트리를 `runtime/prompts/` 로 동기화 — 자동으로 새 `wizard/` 서브디렉터리 포함
|
|
38
|
+
- `src/install.mjs` — install 은 `prompts/` 를 `~/.okstra/` 로 복사 안 함. 그러나 wizard 가 `workspace_root` (npm 패키지의 `runtime/`) 에서 읽으므로 install 변경 불요
|
|
39
|
+
- `runtime/` — 빌드 출력물, 수동 편집 금지
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Task 1: JSON SOT 파일 생성
|
|
44
|
+
|
|
45
|
+
**Files:**
|
|
46
|
+
- Create: `prompts/wizard/prompts.ko.json`
|
|
47
|
+
|
|
48
|
+
이 task 는 wizard.py 의 한글 문자열을 **inventory** 하여 JSON 파일로 옮긴다. wizard.py 코드는 손대지 않음 (Task 3 에서). JSON 파일이 단독으로 commit 되어도 동작 동일성 유지 — 사용 caller 없음.
|
|
49
|
+
|
|
50
|
+
### Step 1: wizard.py 의 prompt 사용처 inventory
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
grep -nE 'Prompt\(|label[ ]*=|echo_template[ ]*=' scripts/okstra_ctl/wizard.py | grep -vE '^[0-9]+:#' | head -80
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
추가 inventory:
|
|
57
|
+
```bash
|
|
58
|
+
grep -nE 'def _build_' scripts/okstra_ctl/wizard.py
|
|
59
|
+
grep -nE '^S_[A-Z_]+ =' scripts/okstra_ctl/wizard.py
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
각 `_build_*(state) -> Prompt` 함수의 본문을 읽어 다음 데이터 형태로 추출:
|
|
63
|
+
- step_id (S_* 상수 값 — 예: `S_TASK_PICK = "task_pick"` → step_id = `"task_pick"`)
|
|
64
|
+
- `label` (한글 문자열, 또는 f-string interpolated 형태 — `{placeholder}` 로 변환)
|
|
65
|
+
- `echo_template` (그대로)
|
|
66
|
+
- 정적 옵션이 있다면 (value → label 매핑): 옵션 값 → 한글 라벨. 동적 옵션 (state-derived) 은 코드 잔류 — JSON 에 안 들어감.
|
|
67
|
+
|
|
68
|
+
### Step 2: JSON 스키마 + 파일 작성
|
|
69
|
+
|
|
70
|
+
`prompts/wizard/prompts.ko.json` 생성. 구조:
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"schema_version": 1,
|
|
75
|
+
"locale": "ko",
|
|
76
|
+
"steps": {
|
|
77
|
+
"task_pick": {
|
|
78
|
+
"label": "어느 task?",
|
|
79
|
+
"echo_template": "task: {value}",
|
|
80
|
+
"options": {
|
|
81
|
+
"_NEW_": "Start a brand-new task"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"task_group": {
|
|
85
|
+
"label": "Task group 을 알려주세요 (예: backend-api, INV-1234, refactor)",
|
|
86
|
+
"echo_template": "task-group: {value}"
|
|
87
|
+
},
|
|
88
|
+
"task_group_with_suggestion": {
|
|
89
|
+
"label": "Task group? (brief 추천: {suggestion})",
|
|
90
|
+
"echo_template": "task-group: {value}"
|
|
91
|
+
},
|
|
92
|
+
"task_group_text": {
|
|
93
|
+
"label": "Task group 을 입력해주세요 (예: backend-api, INV-1234, refactor)",
|
|
94
|
+
"echo_template": "task-group: {value}"
|
|
95
|
+
},
|
|
96
|
+
"brief_keep": {
|
|
97
|
+
"label": "기존 brief 경로 [{existing_brief_path}] 를 유지할까요?",
|
|
98
|
+
"echo_template": "brief: {value}",
|
|
99
|
+
"options": {
|
|
100
|
+
"yes": "유지",
|
|
101
|
+
"no": "변경"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**규칙:**
|
|
109
|
+
- 최상위 `schema_version` (정수, 현재 `1`), `locale` (`"ko"`), `steps` (dict)
|
|
110
|
+
- `steps` 의 키 = wizard.py 의 `S_*` 상수의 값 (예: `S_TASK_PICK = "task_pick"` → 키 `"task_pick"`)
|
|
111
|
+
- 각 step entry 는 반드시 `label`, `echo_template` 보유. `options` 는 정적 옵션이 있을 때만.
|
|
112
|
+
- `label` 안의 placeholder 는 `{name}` (Python str.format 문법). `{value}` 는 echo_template 전용 placeholder (user answer).
|
|
113
|
+
- 같은 step id 가 2개 형태 (예: suggestion 있는 pick + 없는 text) 면 별도 키로 (`task_group` + `task_group_with_suggestion`). 키 명명: 변형 형태에는 `_with_suggestion` / `_text` 같은 suffix.
|
|
114
|
+
|
|
115
|
+
**Inventory 가이드** (wizard.py 의 _build_* 함수당 entry 작성):
|
|
116
|
+
|
|
117
|
+
| `_build_*` 함수 (대략 위치) | step_id (JSON 키) | 비고 |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| `_build_task_pick` (~466) | `task_pick` | + 정적 option `_NEW_` |
|
|
120
|
+
| `_build_task_group` (~521) | `task_group` + `task_group_with_suggestion` | sugg 분기 |
|
|
121
|
+
| `_build_task_group_text` (~558) | `task_group_text` | |
|
|
122
|
+
| `_build_task_id` (~570) | `task_id` + `task_id_with_suggestion` | |
|
|
123
|
+
| `_build_task_id_text` (~605) | `task_id_text` | |
|
|
124
|
+
| `_build_task_type` (~617) | `task_type` | |
|
|
125
|
+
| `_build_brief_keep` (~650) | `brief_keep` | state interpolation `{existing_brief_path}` + 정적 options |
|
|
126
|
+
| `_build_brief_path` (~669) | `brief_path` | |
|
|
127
|
+
| `_build_base_ref_pick` (~691) | `base_ref_pick` | |
|
|
128
|
+
| `_build_base_ref_text` (~714) | `base_ref_text` | |
|
|
129
|
+
| `_build_approved_plan_pick` (~770) | `approved_plan_pick` | state interpolation `{default}` |
|
|
130
|
+
| `_build_approved_plan` (~804) | `approved_plan` | |
|
|
131
|
+
| `_build_directive_pick` (~819) | `directive_pick` | |
|
|
132
|
+
| `_build_related_tasks_pick` (~842) | `related_tasks_pick` | |
|
|
133
|
+
| `_build_clarification_pick` (~865) | `clarification_pick` | |
|
|
134
|
+
| `_build_pr_template_pick` (~888) | `pr_template_pick` | |
|
|
135
|
+
| `_build_executor` (~912) | `executor` | |
|
|
136
|
+
| `_build_defaults_or_custom` (~930) | `defaults_or_custom` | |
|
|
137
|
+
| `_build_workers_override` (~947) | `workers_override` | 동적 옵션 (worker 명) 은 코드 잔류 — label 만 JSON |
|
|
138
|
+
| `_build_lead_model` (~995) | `lead_model` | |
|
|
139
|
+
| `_build_executor_model` (~1005) | `executor_model` | |
|
|
140
|
+
| `_build_claude_model` (~1020) | `claude_model` | |
|
|
141
|
+
| `_build_codex_model` (~1030) | `codex_model` | |
|
|
142
|
+
| `_build_gemini_model` (~1040) | `gemini_model` | |
|
|
143
|
+
| `_build_report_writer_model` (~1050) | `report_writer_model` | |
|
|
144
|
+
| `_build_directive` (~1062) | `directive` | |
|
|
145
|
+
| `_build_related_tasks` (~1076) | `related_tasks` | |
|
|
146
|
+
| `_build_clarification` (~1092) | `clarification` | |
|
|
147
|
+
| `_build_pr_template` (~1111) | `pr_template` | |
|
|
148
|
+
| `_build_pr_template_scope` (~1138) | `pr_template_scope` | + 옵션 |
|
|
149
|
+
| `_build_confirm` (~1160) | `confirm` | + 옵션 (Proceed/Edit) |
|
|
150
|
+
| `_build_edit_target` (~1182) | `edit_target` | |
|
|
151
|
+
|
|
152
|
+
(목록은 시작점 — 실제 파일 grep 결과를 권위로 사용. 발견된 모든 `_build_*` 함수는 JSON 에 1 entry 가 있어야 한다.)
|
|
153
|
+
|
|
154
|
+
**Note** — `_build_workers_override` 의 label 은 다음 같이 다중 줄 + 큰따옴표 escape 가 필요할 수 있음:
|
|
155
|
+
```python
|
|
156
|
+
label=("참여시킬 분석 워커를 선택해주세요 (최소 1개). "
|
|
157
|
+
"기본 셋은 task-type 별 ...")
|
|
158
|
+
```
|
|
159
|
+
이는 JSON 에서 하나의 string 으로 합쳐서 표현 (`" "` 로 줄바꿈 자리). 줄바꿈 `\n` 이 필요하면 명시.
|
|
160
|
+
|
|
161
|
+
### Step 3: JSON 유효성 확인
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
python3 -c "import json; json.load(open('prompts/wizard/prompts.ko.json'))"
|
|
165
|
+
```
|
|
166
|
+
Expected: 에러 없이 종료.
|
|
167
|
+
|
|
168
|
+
step id 카운트 확인 (대략 28-32):
|
|
169
|
+
```bash
|
|
170
|
+
python3 -c "import json; d=json.load(open('prompts/wizard/prompts.ko.json')); print(len(d['steps']), 'steps:', sorted(d['steps'].keys()))"
|
|
171
|
+
```
|
|
172
|
+
Expected: 28+ step (정확한 수는 wizard.py inventory 에 의존). 각 키가 wizard.py 의 `S_*` 상수 값에 1:1 대응.
|
|
173
|
+
|
|
174
|
+
### Step 4: pytest 회귀 (변동 없음 보증)
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
python3 -m pytest tests/ -x
|
|
178
|
+
```
|
|
179
|
+
Expected: 595 passed, 1 skipped (Phase B1 baseline 그대로 — 이 task 는 JSON 파일만 추가, 어떤 코드도 안 손댐).
|
|
180
|
+
|
|
181
|
+
### Step 5: Commit
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
git add prompts/wizard/prompts.ko.json
|
|
185
|
+
git commit -m "feat(prompts): add prompts/wizard/prompts.ko.json (wizard prompt SOT)"
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
No Claude trailer, no Co-Authored-By, no `🤖 Generated with` footer.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Task 2: wizard.py loader + `_p()` 헬퍼 + 기초 unit tests
|
|
193
|
+
|
|
194
|
+
**Files:**
|
|
195
|
+
- Modify: `scripts/okstra_ctl/wizard.py` — `_load_wizard_prompts(workspace_root)` + `_p(workspace_root, step_id, **vars)` 추가. 아직 `_build_*` 안에서 호출 안 함.
|
|
196
|
+
- Create: `tests/test_wizard_prompts.py` — SOT-sync + `_p()` 단위 테스트.
|
|
197
|
+
|
|
198
|
+
이 task commit 후에도 외부 동작은 동일. 새 헬퍼는 정의됐을 뿐 caller 없음.
|
|
199
|
+
|
|
200
|
+
### Step 1: Failing test 작성
|
|
201
|
+
|
|
202
|
+
`tests/test_wizard_prompts.py` 생성:
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
"""wizard.py prompt JSON SOT — loader + _p() 헬퍼 + step id 동기화."""
|
|
206
|
+
from __future__ import annotations
|
|
207
|
+
|
|
208
|
+
import sys
|
|
209
|
+
from pathlib import Path
|
|
210
|
+
|
|
211
|
+
import pytest
|
|
212
|
+
|
|
213
|
+
LIB_DIR = Path(__file__).resolve().parent.parent / "scripts"
|
|
214
|
+
sys.path.insert(0, str(LIB_DIR))
|
|
215
|
+
|
|
216
|
+
from okstra_ctl import wizard # noqa: E402
|
|
217
|
+
from okstra_ctl.wizard import WizardError, _p # noqa: E402
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def test_prompts_json_loadable():
|
|
224
|
+
"""JSON 파일이 schema_version=1, locale='ko', steps dict 보유."""
|
|
225
|
+
raw = wizard._load_wizard_prompts(str(REPO_ROOT))
|
|
226
|
+
assert isinstance(raw, dict)
|
|
227
|
+
assert raw # not empty
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def test_p_returns_label_and_echo():
|
|
231
|
+
"""_p() 가 step id 로 label + echo_template 을 반환."""
|
|
232
|
+
t = _p(str(REPO_ROOT), "task_pick")
|
|
233
|
+
assert "label" in t
|
|
234
|
+
assert "echo_template" in t
|
|
235
|
+
assert t["echo_template"] == "task: {value}"
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def test_p_applies_placeholder():
|
|
239
|
+
"""_p() 가 placeholder 를 vars 로 치환."""
|
|
240
|
+
t = _p(str(REPO_ROOT), "brief_keep", existing_brief_path="/foo/bar.md")
|
|
241
|
+
assert "/foo/bar.md" in t["label"]
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def test_p_missing_step_raises():
|
|
245
|
+
"""미정의 step id → WizardError."""
|
|
246
|
+
with pytest.raises(WizardError) as exc:
|
|
247
|
+
_p(str(REPO_ROOT), "nonexistent_step_xyz")
|
|
248
|
+
assert "nonexistent_step_xyz" in str(exc.value)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def test_p_missing_placeholder_raises():
|
|
252
|
+
"""label 에 placeholder 가 있는데 vars 안 주면 WizardError."""
|
|
253
|
+
with pytest.raises(WizardError) as exc:
|
|
254
|
+
_p(str(REPO_ROOT), "brief_keep") # 'existing_brief_path' 미제공
|
|
255
|
+
assert "existing_brief_path" in str(exc.value) or "missing" in str(exc.value).lower()
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def test_step_id_sync_wizard_to_json():
|
|
259
|
+
"""wizard.py 의 모든 S_* 상수값이 JSON 의 step id 로 존재해야 한다."""
|
|
260
|
+
import inspect
|
|
261
|
+
raw = wizard._load_wizard_prompts(str(REPO_ROOT))
|
|
262
|
+
json_keys = set(raw.keys())
|
|
263
|
+
wizard_step_ids = {
|
|
264
|
+
v for k, v in inspect.getmembers(wizard)
|
|
265
|
+
if k.startswith("S_") and isinstance(v, str)
|
|
266
|
+
}
|
|
267
|
+
# 일부 S_* 는 terminal/non-prompt (S_DONE 등) — JSON 에 없어도 OK.
|
|
268
|
+
# 단, JSON 에 있는 모든 키는 wizard 의 어딘가 (S_* 또는 _with_suggestion 변형)에서 사용돼야.
|
|
269
|
+
# 이 테스트는 JSON 잉여 키 (orphan) 검사를 강화:
|
|
270
|
+
SUFFIXES_ALLOWED = ("", "_with_suggestion", "_text")
|
|
271
|
+
orphans = []
|
|
272
|
+
for json_key in json_keys:
|
|
273
|
+
matched = False
|
|
274
|
+
for suffix in SUFFIXES_ALLOWED:
|
|
275
|
+
if json_key.endswith(suffix):
|
|
276
|
+
base = json_key[: -len(suffix)] if suffix else json_key
|
|
277
|
+
if base in wizard_step_ids or json_key in wizard_step_ids:
|
|
278
|
+
matched = True
|
|
279
|
+
break
|
|
280
|
+
if not matched:
|
|
281
|
+
orphans.append(json_key)
|
|
282
|
+
assert not orphans, f"JSON has step ids not referenced in wizard.py: {orphans}"
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Step 2: 테스트 실패 확인
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
python3 -m pytest tests/test_wizard_prompts.py -v
|
|
289
|
+
```
|
|
290
|
+
Expected: 모든 테스트 ImportError (`cannot import name '_p'` 또는 `_load_wizard_prompts`) 또는 AttributeError.
|
|
291
|
+
|
|
292
|
+
### Step 3: wizard.py 에 loader + `_p()` 구현
|
|
293
|
+
|
|
294
|
+
`scripts/okstra_ctl/wizard.py` 의 적절한 위치 (다른 모듈-레벨 helper 함수 근처, 예: `_profile_path` 정의 직후 ~ line 410) 에 다음 추가:
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
# --------------------------------------------------------------------------- #
|
|
298
|
+
# Wizard prompt JSON SOT (Phase A1)
|
|
299
|
+
# --------------------------------------------------------------------------- #
|
|
300
|
+
|
|
301
|
+
_WIZARD_PROMPTS_CACHE: dict[str, dict] = {}
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _load_wizard_prompts(workspace_root: str) -> dict:
|
|
305
|
+
"""Load and cache wizard prompts JSON for the given workspace root.
|
|
306
|
+
|
|
307
|
+
The JSON file lives at `<workspace_root>/prompts/wizard/prompts.ko.json`
|
|
308
|
+
in the okstra runtime tree. The SOT contract:
|
|
309
|
+
- Top-level fields: `schema_version` (1), `locale` ("ko"), `steps` (dict).
|
|
310
|
+
- Each entry in `steps[<step_id>]` has `label`, `echo_template`, and
|
|
311
|
+
optional `options` (dict[value → label] for static option pickers).
|
|
312
|
+
|
|
313
|
+
Loaded once per workspace_root path (cached) — re-imports are cheap.
|
|
314
|
+
"""
|
|
315
|
+
if workspace_root in _WIZARD_PROMPTS_CACHE:
|
|
316
|
+
return _WIZARD_PROMPTS_CACHE[workspace_root]
|
|
317
|
+
path = Path(workspace_root) / "prompts" / "wizard" / "prompts.ko.json"
|
|
318
|
+
if not path.is_file():
|
|
319
|
+
raise WizardError(
|
|
320
|
+
f"wizard prompt SOT not found: {path}. "
|
|
321
|
+
"Re-run `okstra install` or check the workspace_root."
|
|
322
|
+
)
|
|
323
|
+
try:
|
|
324
|
+
raw = json.loads(path.read_text(encoding="utf-8"))
|
|
325
|
+
except json.JSONDecodeError as exc:
|
|
326
|
+
raise WizardError(f"wizard prompt JSON malformed: {path}: {exc}") from exc
|
|
327
|
+
if raw.get("schema_version") != 1:
|
|
328
|
+
raise WizardError(
|
|
329
|
+
f"wizard prompt schema_version mismatch (expected 1): {path}"
|
|
330
|
+
)
|
|
331
|
+
steps = raw.get("steps")
|
|
332
|
+
if not isinstance(steps, dict):
|
|
333
|
+
raise WizardError(f"wizard prompt 'steps' missing or not a dict: {path}")
|
|
334
|
+
_WIZARD_PROMPTS_CACHE[workspace_root] = steps
|
|
335
|
+
return steps
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _p(workspace_root: str, step_id: str, **vars: str) -> dict:
|
|
339
|
+
"""Look up a wizard prompt entry by step_id and interpolate placeholders.
|
|
340
|
+
|
|
341
|
+
Returns a dict with keys: 'label' (str, possibly interpolated),
|
|
342
|
+
'echo_template' (str, raw — contains `{value}` for the user's answer),
|
|
343
|
+
'options' (dict[value → label], may be empty).
|
|
344
|
+
|
|
345
|
+
Raises WizardError if the step_id is unknown or a required placeholder
|
|
346
|
+
is missing from the provided vars.
|
|
347
|
+
"""
|
|
348
|
+
steps = _load_wizard_prompts(workspace_root)
|
|
349
|
+
raw = steps.get(step_id)
|
|
350
|
+
if raw is None:
|
|
351
|
+
raise WizardError(f"unknown wizard step_id: {step_id!r}")
|
|
352
|
+
label_template = raw.get("label", "")
|
|
353
|
+
try:
|
|
354
|
+
label = label_template.format(**vars) if vars else label_template
|
|
355
|
+
except KeyError as exc:
|
|
356
|
+
missing = exc.args[0] if exc.args else "<unknown>"
|
|
357
|
+
raise WizardError(
|
|
358
|
+
f"missing placeholder {missing!r} for wizard step {step_id!r}"
|
|
359
|
+
) from exc
|
|
360
|
+
return {
|
|
361
|
+
"label": label,
|
|
362
|
+
"echo_template": raw.get("echo_template", ""),
|
|
363
|
+
"options": raw.get("options", {}),
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
상단 import 에 `import json` 이 없으면 추가. `Path` 는 이미 사용 중.
|
|
368
|
+
|
|
369
|
+
### Step 4: 단위 테스트 통과 확인
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
python3 -m pytest tests/test_wizard_prompts.py -v
|
|
373
|
+
```
|
|
374
|
+
Expected: 6 tests PASS.
|
|
375
|
+
|
|
376
|
+
전체 회귀:
|
|
377
|
+
```bash
|
|
378
|
+
python3 -m pytest tests/ -x
|
|
379
|
+
```
|
|
380
|
+
Expected: 601 passed, 1 skipped (595 + 6 new).
|
|
381
|
+
|
|
382
|
+
### Step 5: Commit
|
|
383
|
+
|
|
384
|
+
```bash
|
|
385
|
+
git add scripts/okstra_ctl/wizard.py tests/test_wizard_prompts.py
|
|
386
|
+
git commit -m "feat(wizard): add prompt JSON loader + _p() helper"
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
No Claude trailer, no Co-Authored-By, no `🤖 Generated with` footer.
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## Task 3: `_build_*` 함수 migrate (atomic)
|
|
394
|
+
|
|
395
|
+
**Files:**
|
|
396
|
+
- Modify: `scripts/okstra_ctl/wizard.py` — 모든 `_build_*` 함수의 `Prompt(label=..., echo_template=..., options=[...])` 호출을 `_p()` lookup 기반으로 교체.
|
|
397
|
+
|
|
398
|
+
이 task 가 핵심. 부분 마이그레이션 시 SOT 가 깨지므로 한 commit 에 묶음. 동작 변화 없음 — 회귀 테스트가 검증.
|
|
399
|
+
|
|
400
|
+
### Step 1: 마이그레이션 패턴
|
|
401
|
+
|
|
402
|
+
**Before (hard-coded Korean):**
|
|
403
|
+
```python
|
|
404
|
+
def _build_brief_keep(state: WizardState) -> Prompt:
|
|
405
|
+
return Prompt(
|
|
406
|
+
step=S_BRIEF_KEEP, kind="pick",
|
|
407
|
+
label=f"기존 brief 경로 [{state.existing_brief_path}] 를 유지할까요?",
|
|
408
|
+
options=[_opt("yes", "유지"), _opt("no", "변경")],
|
|
409
|
+
echo_template="brief: {value}",
|
|
410
|
+
)
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
**After (JSON lookup):**
|
|
414
|
+
```python
|
|
415
|
+
def _build_brief_keep(state: WizardState) -> Prompt:
|
|
416
|
+
t = _p(state.workspace_root, "brief_keep",
|
|
417
|
+
existing_brief_path=state.existing_brief_path)
|
|
418
|
+
return Prompt(
|
|
419
|
+
step=S_BRIEF_KEEP, kind="pick",
|
|
420
|
+
label=t["label"],
|
|
421
|
+
options=[_opt(k, v) for k, v in t["options"].items()],
|
|
422
|
+
echo_template=t["echo_template"],
|
|
423
|
+
)
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
**규칙:**
|
|
427
|
+
- `_p()` 의 첫 인자는 항상 `state.workspace_root`.
|
|
428
|
+
- 두 번째 인자 (step_id) 는 `S_*` 상수의 값 (예: `S_BRIEF_KEEP` → `"brief_keep"`). 명시적 문자열로 — `S_BRIEF_KEEP` 값과 동일하게.
|
|
429
|
+
- placeholder 가 있는 label 은 `**vars` 로 전달.
|
|
430
|
+
- 정적 options 가 있으면 `t["options"]` 의 dict items 를 `_opt(value, label)` 로 변환.
|
|
431
|
+
- 동적 options (예: `_build_workers_override` 의 worker 목록, `_build_task_pick` 의 task catalog) 는 **state 에서 계산해 직접 `options` 리스트에 추가** — JSON 에서 안 옴.
|
|
432
|
+
- 동적 + 정적 hybrid (예: `_build_task_pick` 의 catalog tasks + 정적 `_NEW_` 옵션) 는: 동적 옵션을 코드에서 만들고, 마지막에 JSON 의 정적 옵션 (`t["options"]`) 도 추가:
|
|
433
|
+
```python
|
|
434
|
+
options = [...] # 동적 옵션
|
|
435
|
+
for value, label in t["options"].items():
|
|
436
|
+
options.append(_opt(value, label))
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### Step 2: 모든 `_build_*` 함수 migrate
|
|
440
|
+
|
|
441
|
+
`scripts/okstra_ctl/wizard.py` 의 28개 (대략) `_build_*` 함수를 위 패턴으로 변환:
|
|
442
|
+
|
|
443
|
+
```bash
|
|
444
|
+
grep -nE 'def _build_' scripts/okstra_ctl/wizard.py
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
각 함수마다:
|
|
448
|
+
1. 첫 줄에 `t = _p(state.workspace_root, "<step_id>", **interpolation_vars)` 추가.
|
|
449
|
+
2. `Prompt(...)` 호출에서:
|
|
450
|
+
- `label="..."` → `label=t["label"]`
|
|
451
|
+
- `echo_template="..."` → `echo_template=t["echo_template"]`
|
|
452
|
+
- 정적 `_opt(value, label)` 호출 → `_opt(value, t["options"][value])` 또는 위 hybrid 패턴.
|
|
453
|
+
3. 함수 안에 한글 string literal 0개 — 확인 grep:
|
|
454
|
+
```bash
|
|
455
|
+
grep -nP '[가-힣]' scripts/okstra_ctl/wizard.py
|
|
456
|
+
```
|
|
457
|
+
Expected: 0 hits 또는 주석 (코멘트 안의 한글은 정책상 허용 — JSON SOT 의 대상은 user-facing UI 문자열뿐).
|
|
458
|
+
|
|
459
|
+
**특수 케이스 — `_build_workers_override`** (line ~947):
|
|
460
|
+
- label 이 multi-line — JSON 에서 단일 문자열로 합침.
|
|
461
|
+
- 옵션은 동적 (worker 명 + "(옵션)" suffix 포함 가능). 동적 옵션의 한글 (`(옵션)` 등) 이 코드에 남아도 되는가? 정책: **이런 contextual suffix 는 JSON 안의 별도 항목으로** — 예: `t["options"]["_OPTIONAL_SUFFIX_"] = "(옵션)"` 또는 별도 step 의 `options` 안에. 가장 단순한 방법은 `_p()` 의 추가 placeholder 로 처리:
|
|
462
|
+
```json
|
|
463
|
+
"workers_override": {
|
|
464
|
+
"label": "참여시킬 분석 워커를 선택해주세요 (최소 1개). ...",
|
|
465
|
+
"echo_template": "workers: {value}",
|
|
466
|
+
"options": { "_OPTIONAL_SUFFIX": " (옵션)" }
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
코드 (`_build_workers_override`) 에서:
|
|
470
|
+
```python
|
|
471
|
+
t = _p(state.workspace_root, "workers_override")
|
|
472
|
+
suffix = t["options"].get("_OPTIONAL_SUFFIX", "")
|
|
473
|
+
for w in candidates:
|
|
474
|
+
label = f"{w}{suffix}" if w in optional_set else w
|
|
475
|
+
options.append(_opt(value=w, label=label))
|
|
476
|
+
```
|
|
477
|
+
- 또는 더 깔끔하게: optional suffix 를 JSON 의 `metadata` 같은 별도 키에. step entry 스키마를 확장하면 됨. 본 plan 에서는 `_OPTIONAL_SUFFIX` 키를 옵션 dict 안에 두는 방식 채택 (스키마 변경 최소).
|
|
478
|
+
|
|
479
|
+
**특수 케이스 — `_build_*_model`** (lead/executor/claude/codex/gemini/report_writer):
|
|
480
|
+
- 6 함수가 공통 헬퍼 `_build_model_pick(state, step, label, echo, ...)` 같은 형태일 가능성 (확인 필요 — wizard.py:990-1050 부근).
|
|
481
|
+
- 헬퍼가 step/label/echo 를 받는다면, 각 caller 가 `_p(...)` 로 미리 dict 받아서 헬퍼에 전달:
|
|
482
|
+
```python
|
|
483
|
+
def _build_lead_model(state):
|
|
484
|
+
t = _p(state.workspace_root, "lead_model")
|
|
485
|
+
return _build_model_pick(state, S_LEAD_MODEL, t["label"], t["echo_template"])
|
|
486
|
+
```
|
|
487
|
+
- 6 함수 모두 같은 패턴.
|
|
488
|
+
|
|
489
|
+
### Step 3: 회귀 통과 확인
|
|
490
|
+
|
|
491
|
+
```bash
|
|
492
|
+
python3 -m pytest tests/ -x
|
|
493
|
+
```
|
|
494
|
+
Expected: 601 passed, 1 skipped — wizard 기존 회귀 (test_okstra_ctl_wizard.py 등) 가 동작 보존을 검증.
|
|
495
|
+
|
|
496
|
+
E2E (wizard 가 실행되는 시나리오 — 있다면):
|
|
497
|
+
```bash
|
|
498
|
+
ls tests-e2e/
|
|
499
|
+
# scenario-* 안에서 wizard 가 실행되는 게 있는지 확인.
|
|
500
|
+
```
|
|
501
|
+
없을 경우 unit 회귀로 충분.
|
|
502
|
+
|
|
503
|
+
### Step 4: 한글 grep 점검 (informational — 정식 게이트는 Task 4)
|
|
504
|
+
|
|
505
|
+
```bash
|
|
506
|
+
grep -nP '[가-힣]' scripts/okstra_ctl/wizard.py | grep -vE '^[0-9]+:[ ]*#'
|
|
507
|
+
```
|
|
508
|
+
Expected: 0 hits (주석 외).
|
|
509
|
+
|
|
510
|
+
만약 주석 안 한글이 있고 의도된 한국어 설명이면 OK — 본 task 는 **user-facing UI 문자열** 만 외부화 대상.
|
|
511
|
+
|
|
512
|
+
### Step 5: Commit
|
|
513
|
+
|
|
514
|
+
```bash
|
|
515
|
+
git add scripts/okstra_ctl/wizard.py
|
|
516
|
+
git commit -m "refactor(wizard): externalize Korean prompts to prompts.ko.json"
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
No Claude trailer, no Co-Authored-By, no `🤖 Generated with` footer.
|
|
520
|
+
|
|
521
|
+
---
|
|
522
|
+
|
|
523
|
+
## Task 4: Korean-char grep gate test
|
|
524
|
+
|
|
525
|
+
**Files:**
|
|
526
|
+
- Modify: `tests/test_wizard_prompts.py` — wizard.py 안에 user-facing 한글 문자열이 다시 추가되지 않도록 정적 검사 추가.
|
|
527
|
+
|
|
528
|
+
### Step 1: 추가할 테스트 함수
|
|
529
|
+
|
|
530
|
+
`tests/test_wizard_prompts.py` 끝에 다음 추가:
|
|
531
|
+
|
|
532
|
+
```python
|
|
533
|
+
WIZARD_PY = Path(__file__).resolve().parents[1] / "scripts" / "okstra_ctl" / "wizard.py"
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def test_no_korean_in_wizard_py_code():
|
|
537
|
+
"""wizard.py 의 비-주석 코드 라인에 한글 문자가 0개 — JSON SOT 회귀 방지.
|
|
538
|
+
|
|
539
|
+
주석 (`#` 시작) 안의 한글은 한국어 docstring/설명용으로 허용.
|
|
540
|
+
내용:
|
|
541
|
+
- user-facing UI 문자열 (label, echo, option label) 은 모두 prompts/wizard/prompts.ko.json 에 있어야 함.
|
|
542
|
+
"""
|
|
543
|
+
import re
|
|
544
|
+
|
|
545
|
+
KOREAN = re.compile(r"[가-힣]")
|
|
546
|
+
offenders: list[str] = []
|
|
547
|
+
for lineno, raw in enumerate(WIZARD_PY.read_text(encoding="utf-8").splitlines(), 1):
|
|
548
|
+
# 빈 줄 / 주석만 있는 줄 skip
|
|
549
|
+
stripped = raw.strip()
|
|
550
|
+
if not stripped or stripped.startswith("#"):
|
|
551
|
+
continue
|
|
552
|
+
# 줄 안에 `#` 가 있으면 그 앞 부분만 검사 (inline comment 제외).
|
|
553
|
+
code_part = raw.split("#", 1)[0]
|
|
554
|
+
# 문자열 안의 한글도 violation — JSON 으로 옮겨야 함.
|
|
555
|
+
# docstring (3중 따옴표 영역) 안의 한글은? — 정책상 docstring 한글도 외부화 대상 아님 (코드 설명).
|
|
556
|
+
# 본 테스트는 단순 라인 기반 — docstring 안의 한글은 통과 (false negative). 정밀 검사가 필요하면 ast 사용.
|
|
557
|
+
# 일단 single/triple quoted string content 내 한글이 있어도 보수적으로 detection.
|
|
558
|
+
if KOREAN.search(code_part):
|
|
559
|
+
offenders.append(f" {WIZARD_PY.name}:{lineno}: {raw.rstrip()[:120]}")
|
|
560
|
+
assert not offenders, (
|
|
561
|
+
"wizard.py 에 user-facing 한글 문자열이 남아 있음 — "
|
|
562
|
+
"prompts/wizard/prompts.ko.json 으로 이동 필요:\n" + "\n".join(offenders)
|
|
563
|
+
)
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
### Step 2: 테스트 실행
|
|
567
|
+
|
|
568
|
+
```bash
|
|
569
|
+
python3 -m pytest tests/test_wizard_prompts.py::test_no_korean_in_wizard_py_code -v
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
Expected:
|
|
573
|
+
- Task 3 가 깨끗이 됐다면 PASS.
|
|
574
|
+
- FAIL 이면 메시지가 짚는 라인의 한글을 JSON 으로 이동 + wizard.py 코드를 `_p()` lookup 으로 교체.
|
|
575
|
+
|
|
576
|
+
전체 회귀:
|
|
577
|
+
```bash
|
|
578
|
+
python3 -m pytest tests/ -x
|
|
579
|
+
```
|
|
580
|
+
Expected: 602 passed, 1 skipped (601 + 1 new).
|
|
581
|
+
|
|
582
|
+
### Step 3: 만약 docstring 안 한글이 false positive 로 잡히면
|
|
583
|
+
|
|
584
|
+
본 테스트는 라인 기반 — docstring 안의 한국어 설명도 잡힐 수 있음. 옵션:
|
|
585
|
+
- (a) docstring 한글도 영어로 (또는 별도 docs/ 파일로) 외부화 — 정책 강화.
|
|
586
|
+
- (b) 테스트 strictness 완화 — docstring 라인을 skip 하도록 ast 사용으로 변경.
|
|
587
|
+
|
|
588
|
+
본 plan 의 기본은 (a) — wizard.py 의 한글 자체를 0개로. docstring 도 영어 또는 외부 docs 로. 만약 한국어 docstring 보존이 우선이면 (b) 로 테스트 완화 후 별도 task.
|
|
589
|
+
|
|
590
|
+
### Step 4: Commit
|
|
591
|
+
|
|
592
|
+
```bash
|
|
593
|
+
git add tests/test_wizard_prompts.py
|
|
594
|
+
git commit -m "test(wizard): assert no Korean characters remain in wizard.py code"
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
No Claude trailer, no Co-Authored-By, no `🤖 Generated with` footer.
|
|
598
|
+
|
|
599
|
+
---
|
|
600
|
+
|
|
601
|
+
## Task 5: CHANGES.md + 최종 검증
|
|
602
|
+
|
|
603
|
+
**Files:**
|
|
604
|
+
- Modify: `CHANGES.md`
|
|
605
|
+
|
|
606
|
+
### Step 1: CHANGES.md 항목 추가
|
|
607
|
+
|
|
608
|
+
`CHANGES.md` 의 최상단 (오늘 date 섹션) 에:
|
|
609
|
+
|
|
610
|
+
```markdown
|
|
611
|
+
### refactor(wizard): wizard prompt 한글 문자열 JSON SOT 단일화 (2026-05-20)
|
|
612
|
+
|
|
613
|
+
- `scripts/okstra_ctl/wizard.py` 의 한글 UI 문자열 (label, echo_template, 정적 option label) 을 `prompts/wizard/prompts.ko.json` 단독 권위로 이전.
|
|
614
|
+
- 신규 `_p(workspace_root, step_id, **vars)` 헬퍼 — placeholder 보간 + step id lookup.
|
|
615
|
+
- 한글 grep gate test 추가 — wizard.py 안에 user-facing 한글이 다시 들어오는 회귀 차단.
|
|
616
|
+
- 사용자 영향: 없음 (UI 동일). 컨트리뷰터 영향: prompt 문구 수정 시 Python 코드 안 건드리고 JSON 만 편집.
|
|
617
|
+
- i18n 준비: 향후 `prompts.en.yaml` / `prompts.<locale>.json` 추가 시 locale 선택 메커니즘만 추가하면 됨 (본 PR 범위 외).
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
기존 CHANGES.md 의 entry pattern (`### type(scope): summary` + 배경/변경/사용자 영향 구조) 을 따를 것 — `head -20 CHANGES.md` 로 먼저 확인.
|
|
621
|
+
|
|
622
|
+
### Step 2: npm build smoke
|
|
623
|
+
|
|
624
|
+
```bash
|
|
625
|
+
npm run build
|
|
626
|
+
```
|
|
627
|
+
Expected: 에러 없음, `runtime/prompts/wizard/prompts.ko.json` 이 생성됨.
|
|
628
|
+
|
|
629
|
+
검증:
|
|
630
|
+
```bash
|
|
631
|
+
test -f runtime/prompts/wizard/prompts.ko.json && echo OK
|
|
632
|
+
```
|
|
633
|
+
Expected: `OK`.
|
|
634
|
+
|
|
635
|
+
### Step 3: 최종 회귀 3종
|
|
636
|
+
|
|
637
|
+
```bash
|
|
638
|
+
python3 -m pytest tests/ -v 2>&1 | tail -10
|
|
639
|
+
```
|
|
640
|
+
Expected: 602 passed, 1 skipped.
|
|
641
|
+
|
|
642
|
+
```bash
|
|
643
|
+
bash validators/validate-workflow.sh 2>&1 | tail -10
|
|
644
|
+
```
|
|
645
|
+
Expected: 기존 pre-existing failure 그대로 (Phase B1 후속과 무관).
|
|
646
|
+
|
|
647
|
+
E2E (wizard 흐름이 들어간 시나리오 — 있다면 실행, 없으면 skip):
|
|
648
|
+
```bash
|
|
649
|
+
ls tests-e2e/scenario-*.sh | head -5
|
|
650
|
+
```
|
|
651
|
+
의도된 wizard 시나리오가 있으면 실행. 없으면 OK — wizard 의 회귀는 unit 으로 충분 (`tests/test_okstra_ctl_wizard.py`).
|
|
652
|
+
|
|
653
|
+
### Step 4: Commit
|
|
654
|
+
|
|
655
|
+
```bash
|
|
656
|
+
git add CHANGES.md
|
|
657
|
+
git commit -m "docs(changes): note wizard prompt JSON SOT unification"
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
No Claude trailer, no Co-Authored-By, no `🤖 Generated with` footer.
|
|
661
|
+
|
|
662
|
+
---
|
|
663
|
+
|
|
664
|
+
## Self-Review 체크리스트
|
|
665
|
+
|
|
666
|
+
- [x] **Spec coverage**: design 문서 §3 (Phase A1) 의 모든 항목이 task 로 포함 — 3.1 외부화 대상 (Task 1: label/echo/정적 옵션, Task 3 의 동적 옵션 코드 잔류), 3.2 JSON 스키마 (Task 1 step 2), 3.3 loader/`_p()` (Task 2), 3.4 검증 (Task 2 의 SOT-sync test + Task 4 의 한글 grep gate), 3.5 마이그레이션 (Task 3 atomic), 3.6 의도적 안 하는 것 (i18n 활성화는 Task 5 CHANGES.md 에 언급), 3.7 실패모드 (`_p()` 의 WizardError).
|
|
667
|
+
- [x] **Placeholder scan**: TBD/TODO 없음. 모든 step 에 실제 명령·코드·grep 패턴 명시. JSON 의 28+ entries 는 Task 1 의 inventory 절차로 명확히 — exact entries 는 wizard.py grep 결과를 권위로 사용.
|
|
668
|
+
- [x] **Type consistency**: `_p(workspace_root: str, step_id: str, **vars: str) -> dict` / `_load_wizard_prompts(workspace_root: str) -> dict` / `WizardError(Exception)` (기존 — 본 plan 에서 새로 정의 안 함) — 모든 task 에서 동일.
|
|
669
|
+
- [x] **Atomic migration**: Task 3 한 commit. 부분 마이그레이션 위험 없음.
|
|
670
|
+
- [x] **Format 변경 (YAML → JSON)**: spec 의 의도를 보존하면서 zero-dep 구현 — design 문서와의 차이를 본 plan 의 "왜 JSON 인가" 섹션에 명시.
|
|
671
|
+
|
|
672
|
+
---
|
|
673
|
+
|
|
674
|
+
## 실행 옵션
|
|
675
|
+
|
|
676
|
+
Plan complete and saved to [docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md](docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md). 두 가지 실행 방식:
|
|
677
|
+
|
|
678
|
+
1. **Subagent-Driven (recommended)** — task 마다 새로운 subagent 가 들어가 작업, task 사이에서 review, 빠른 iteration
|
|
679
|
+
2. **Inline Execution** — 본 세션 안에서 executing-plans 로 batch 실행, checkpoint 마다 review
|
|
680
|
+
|
|
681
|
+
어떤 방식으로 진행할까요?
|