okstra 0.34.1 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.kr.md +26 -16
- package/README.md +26 -16
- package/docs/kr/architecture.md +59 -45
- package/docs/kr/cli.md +61 -18
- package/docs/pr-template-usage.md +65 -0
- package/docs/project-structure-overview.md +358 -354
- package/docs/superpowers/plans/2026-05-12-ticket-id-in-reports.md +1 -1
- package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +1 -1
- package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +1 -1
- package/docs/superpowers/plans/2026-05-20-final-report-language.md +1501 -0
- package/docs/superpowers/plans/2026-05-20-implementation-planning-multi-stage.md +1267 -0
- package/docs/superpowers/plans/2026-05-20-okstra-run-prompt-sot-b1.md +1007 -0
- package/docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md +720 -0
- package/docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md +681 -0
- package/docs/superpowers/plans/2026-05-21-improvement-discovery-task-type.md +1691 -0
- package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +383 -0
- package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +320 -0
- package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +299 -0
- package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +335 -0
- package/docs/task-process/README.md +74 -0
- package/docs/task-process/common-flow.md +166 -0
- package/docs/task-process/error-analysis.md +101 -0
- package/docs/task-process/final-verification.md +167 -0
- package/docs/task-process/implementation-planning.md +128 -0
- package/docs/task-process/implementation.md +149 -0
- package/docs/task-process/release-handoff.md +206 -0
- package/docs/task-process/requirements-discovery.md +115 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/SKILL.md +12 -2
- package/runtime/agents/workers/claude-worker.md +26 -0
- package/runtime/agents/workers/codex-worker.md +27 -1
- package/runtime/agents/workers/gemini-worker.md +27 -1
- package/runtime/agents/workers/report-writer-worker.md +8 -1
- package/runtime/bin/okstra-central.sh +6 -6
- package/runtime/bin/okstra-codex-exec.sh +49 -28
- package/runtime/bin/okstra-gemini-exec.sh +39 -21
- package/runtime/bin/okstra-render-final-report.py +13 -2
- package/runtime/bin/okstra-wrapper-status.py +155 -0
- package/runtime/bin/okstra.sh +2 -2
- package/runtime/prompts/profiles/_common-contract.md +11 -6
- package/runtime/prompts/profiles/error-analysis.md +3 -7
- package/runtime/prompts/profiles/implementation-planning.md +22 -21
- package/runtime/prompts/profiles/implementation.md +28 -11
- package/runtime/prompts/profiles/improvement-discovery.md +42 -0
- package/runtime/prompts/profiles/kr/_common-contract.md +92 -0
- package/runtime/prompts/profiles/kr/error-analysis.md +36 -0
- package/runtime/prompts/profiles/kr/final-verification.md +48 -0
- package/runtime/prompts/profiles/kr/implementation-planning.md +90 -0
- package/runtime/prompts/profiles/kr/implementation.md +144 -0
- package/runtime/prompts/profiles/kr/improvement-discovery.md +42 -0
- package/runtime/prompts/profiles/kr/release-handoff.md +104 -0
- package/runtime/prompts/profiles/kr/requirements-discovery.md +42 -0
- package/runtime/prompts/profiles/release-handoff.md +1 -1
- package/runtime/prompts/profiles/requirements-discovery.md +8 -12
- package/runtime/prompts/wizard/prompts.ko.json +230 -0
- package/runtime/python/lib/okstra/cli.sh +2 -49
- package/runtime/python/lib/okstra/globals.sh +21 -21
- package/runtime/python/lib/okstra/interactive.sh +7 -7
- package/runtime/python/okstra_ctl/clarification_items.py +3 -9
- package/runtime/python/okstra_ctl/consumers.py +53 -0
- package/runtime/python/okstra_ctl/final_report_schema.py +0 -7
- package/runtime/python/okstra_ctl/i18n.py +73 -0
- package/runtime/python/okstra_ctl/improvement_lenses.py +44 -0
- package/runtime/python/okstra_ctl/index.py +1 -1
- package/runtime/python/okstra_ctl/paths.py +23 -20
- package/runtime/python/okstra_ctl/render.py +147 -202
- package/runtime/python/okstra_ctl/render_final_report.py +53 -10
- package/runtime/python/okstra_ctl/run.py +292 -107
- package/runtime/python/okstra_ctl/run_context.py +22 -0
- package/runtime/python/okstra_ctl/seeding.py +186 -0
- package/runtime/python/okstra_ctl/wizard.py +348 -127
- package/runtime/python/okstra_ctl/workflow.py +21 -2
- package/runtime/python/okstra_ctl/worktree.py +54 -1
- package/runtime/python/okstra_project/resolver.py +4 -3
- package/runtime/python/okstra_token_usage/report.py +2 -2
- package/runtime/schemas/final-report-v1.0.schema.json +22 -16
- package/runtime/skills/okstra-brief/SKILL.md +124 -31
- package/runtime/skills/okstra-convergence/SKILL.md +2 -3
- package/runtime/skills/okstra-report-writer/SKILL.md +35 -15
- package/runtime/skills/okstra-run/SKILL.md +5 -4
- package/runtime/skills/okstra-schedule/SKILL.md +4 -4
- package/runtime/skills/okstra-setup/SKILL.md +27 -0
- package/runtime/skills/okstra-team-contract/SKILL.md +1 -1
- package/runtime/templates/okstra.CLAUDE.md +104 -0
- package/runtime/templates/reports/final-report.template.md +93 -98
- package/runtime/templates/reports/i18n/en.json +135 -0
- package/runtime/templates/reports/i18n/ko.json +135 -0
- package/runtime/templates/reports/implementation-planning-input.template.md +18 -0
- package/runtime/templates/reports/improvement-discovery-input.template.md +78 -0
- package/runtime/templates/reports/task-brief.template.md +2 -2
- package/runtime/validators/lib/fixtures.sh +30 -0
- package/runtime/validators/lib/runners.sh +1 -1
- package/runtime/validators/validate-implementation-plan-stages.py +211 -0
- package/runtime/validators/validate-run.py +121 -26
- package/runtime/validators/validate-workflow.sh +2 -2
- package/runtime/validators/validate_improvement_report.py +275 -0
- package/src/config.mjs +18 -0
- package/src/install.mjs +41 -14
- package/src/setup.mjs +133 -1
- package/src/uninstall.mjs +21 -1
|
@@ -0,0 +1,720 @@
|
|
|
1
|
+
# Wizard Messages JSON SOT (A1 Follow-up) 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:** Phase A1 의 JSON SOT 를 wizard.py 의 모든 user-facing 한글 문자열 (`_submit_*` echo variants, `WizardError` 메시지, `confirmation_block` 출력) 까지 확장한다. wizard.py 의 한글 0 (docstring 제외) 달성.
|
|
6
|
+
|
|
7
|
+
**Architecture:**
|
|
8
|
+
1. JSON SOT (`prompts/wizard/prompts.ko.json`) 스키마 확장 — step entry 안에 `echo_variants` / `errors` 서브필드 추가, top-level `confirmation` 섹션 추가.
|
|
9
|
+
2. wizard.py 에 `_load_wizard_root(workspace_root)` 헬퍼 (전체 root 캐시) + `_msg(workspace_root, section, key, **vars)` 헬퍼 (top-level 섹션 lookup) 추가. 기존 `_p()` 의 반환 dict 에 `echo_variants`/`errors` 패스스루.
|
|
10
|
+
3. 7 한글 사이트 (3 `_submit_*` echo + 2 `WizardError` + 2 `confirmation_block` 라인) atomic migrate.
|
|
11
|
+
4. Korean grep gate scope 를 `_submit_*` + `confirmation_block` 까지 확장 — wizard.py 코드 라인 한글 0 (docstring 제외) 정적 보장.
|
|
12
|
+
|
|
13
|
+
**Tech Stack:** Python 3 stdlib (`json`, `pathlib`), pytest.
|
|
14
|
+
|
|
15
|
+
**Reference:** [docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md](../specs/2026-05-20-okstra-run-prompt-sot-design.md) §3. 본 plan 은 Phase A1 의 plan scope 외였던 follow-up 작업.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## File Structure
|
|
20
|
+
|
|
21
|
+
**수정 only**
|
|
22
|
+
- `prompts/wizard/prompts.ko.json` — Task 1
|
|
23
|
+
- `scripts/okstra_ctl/wizard.py` — Task 2 (loader/`_msg`) + Task 3 (사이트 migration)
|
|
24
|
+
- `tests/test_wizard_prompts.py` — Task 2 (헬퍼 테스트) + Task 4 (grep gate scope 확장)
|
|
25
|
+
- `CHANGES.md` — Task 4
|
|
26
|
+
|
|
27
|
+
손대지 않음: `runtime/` (build output), 다른 모듈.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Migration target inventory (구체적)
|
|
32
|
+
|
|
33
|
+
| # | wizard.py 위치 | 현재 한글 | JSON 신규 키 |
|
|
34
|
+
|---|---|---|---|
|
|
35
|
+
| 1 | `_submit_task_group:643` | `"task-group: (직접 입력)"` | `steps.task_group.echo_variants.free_input` |
|
|
36
|
+
| 2 | `_submit_task_id:694` | `"task-id: (직접 입력)"` | `steps.task_id.echo_variants.free_input` |
|
|
37
|
+
| 3 | `_submit_brief_keep:769` | `f"brief: {state.brief_path} (유지)"` | `steps.brief_keep.echo_variants.kept` (with `{brief_path}` placeholder) |
|
|
38
|
+
| 4 | `_submit_approved_plan_pick:900` | `"기본 approved-plan 경로를 찾을 수 없습니다. '다른 경로 입력'을 선택하세요."` | `steps.approved_plan_pick.errors.default_not_found` |
|
|
39
|
+
| 5 | `_submit_workers_override:1080` | `"워커를 최소 1개 선택해주세요"` | `steps.workers_override.errors.min_one_required` |
|
|
40
|
+
| 6 | `confirmation_block:1717` | `"선택 확인:"` | `confirmation.header` |
|
|
41
|
+
| 7 | `confirmation_block:1727` | `" workers : (프로필 기본 — executor + verifier 2 + report-writer)"` | `confirmation.workers_implementation_default` |
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Task 1: JSON SOT 스키마 확장
|
|
46
|
+
|
|
47
|
+
**Files:**
|
|
48
|
+
- Modify: `prompts/wizard/prompts.ko.json`
|
|
49
|
+
|
|
50
|
+
이 task 의 commit 후에도 동작 동일 — JSON 신규 필드는 caller 없음 (Task 3 에서 사용).
|
|
51
|
+
|
|
52
|
+
- [ ] **Step 1: 신규 필드 추가**
|
|
53
|
+
|
|
54
|
+
`prompts/wizard/prompts.ko.json` 의 5개 step entry 에 `echo_variants` / `errors` 서브필드 추가 + 최상위 `confirmation` 섹션 추가:
|
|
55
|
+
|
|
56
|
+
`steps.task_group` 에 다음 키 추가:
|
|
57
|
+
```json
|
|
58
|
+
"echo_variants": {
|
|
59
|
+
"free_input": "task-group: (직접 입력)"
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`steps.task_id` 에:
|
|
64
|
+
```json
|
|
65
|
+
"echo_variants": {
|
|
66
|
+
"free_input": "task-id: (직접 입력)"
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`steps.brief_keep` 에:
|
|
71
|
+
```json
|
|
72
|
+
"echo_variants": {
|
|
73
|
+
"kept": "brief: {brief_path} (유지)"
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`steps.approved_plan_pick` 에:
|
|
78
|
+
```json
|
|
79
|
+
"errors": {
|
|
80
|
+
"default_not_found": "기본 approved-plan 경로를 찾을 수 없습니다. '다른 경로 입력'을 선택하세요."
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`steps.workers_override` 에:
|
|
85
|
+
```json
|
|
86
|
+
"errors": {
|
|
87
|
+
"min_one_required": "워커를 최소 1개 선택해주세요"
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
최상위 (top-level, `steps` 와 형제) 에 신규 섹션:
|
|
92
|
+
```json
|
|
93
|
+
"confirmation": {
|
|
94
|
+
"header": "선택 확인:",
|
|
95
|
+
"workers_implementation_default": " workers : (프로필 기본 — executor + verifier 2 + report-writer)"
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
JSON 인덴트는 기존 파일 패턴 따름 (2-space).
|
|
100
|
+
|
|
101
|
+
- [ ] **Step 2: 유효성 확인**
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
python3 -m json.tool prompts/wizard/prompts.ko.json > /dev/null && echo "JSON valid"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
python3 << 'PY'
|
|
109
|
+
import json
|
|
110
|
+
d = json.load(open('prompts/wizard/prompts.ko.json'))
|
|
111
|
+
assert d['steps']['task_group']['echo_variants']['free_input'] == "task-group: (직접 입력)"
|
|
112
|
+
assert d['steps']['task_id']['echo_variants']['free_input'] == "task-id: (직접 입력)"
|
|
113
|
+
assert "{brief_path}" in d['steps']['brief_keep']['echo_variants']['kept']
|
|
114
|
+
assert "approved-plan" in d['steps']['approved_plan_pick']['errors']['default_not_found']
|
|
115
|
+
assert d['steps']['workers_override']['errors']['min_one_required'] == "워커를 최소 1개 선택해주세요"
|
|
116
|
+
assert d['confirmation']['header'] == "선택 확인:"
|
|
117
|
+
assert "report-writer" in d['confirmation']['workers_implementation_default']
|
|
118
|
+
print("OK: all 7 new entries present")
|
|
119
|
+
PY
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
- [ ] **Step 3: 회귀 통과 (변동 없음)**
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
python3 -m pytest tests/ -x
|
|
126
|
+
```
|
|
127
|
+
Expected: 604 passed, 1 skipped (baseline 유지 — 새 JSON 키는 caller 없어서 영향 없음).
|
|
128
|
+
|
|
129
|
+
- [ ] **Step 4: Commit**
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
git add prompts/wizard/prompts.ko.json
|
|
133
|
+
git commit -m "feat(prompts): add echo_variants/errors/confirmation sections to wizard SOT"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
No Claude trailer, no Co-Authored-By, no `🤖 Generated with` footer.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Task 2: `_load_wizard_root` + `_msg` 헬퍼 추가 + `_p()` 확장 (TDD)
|
|
141
|
+
|
|
142
|
+
**Files:**
|
|
143
|
+
- Modify: `scripts/okstra_ctl/wizard.py`
|
|
144
|
+
- Modify: `tests/test_wizard_prompts.py`
|
|
145
|
+
|
|
146
|
+
이 task 의 commit 후에도 동작 동일 — 새 헬퍼는 정의됐을 뿐 caller 없음 (Task 3).
|
|
147
|
+
|
|
148
|
+
- [ ] **Step 1: Failing tests 추가**
|
|
149
|
+
|
|
150
|
+
`tests/test_wizard_prompts.py` 끝에 추가:
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
def test_p_includes_echo_variants():
|
|
154
|
+
"""_p() 반환 dict 가 echo_variants 도 포함해야 한다."""
|
|
155
|
+
t = _p(str(REPO_ROOT), "task_group")
|
|
156
|
+
assert "echo_variants" in t
|
|
157
|
+
assert t["echo_variants"]["free_input"] == "task-group: (직접 입력)"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_p_includes_errors():
|
|
161
|
+
"""_p() 반환 dict 가 errors 도 포함해야 한다."""
|
|
162
|
+
t = _p(str(REPO_ROOT), "approved_plan_pick")
|
|
163
|
+
assert "errors" in t
|
|
164
|
+
assert "approved-plan" in t["errors"]["default_not_found"]
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_p_echo_variants_default_empty():
|
|
168
|
+
"""echo_variants 가 없는 step 도 빈 dict 로 안전하게 반환."""
|
|
169
|
+
t = _p(str(REPO_ROOT), "task_pick")
|
|
170
|
+
assert t.get("echo_variants", {}) == {} or "echo_variants" not in t or t["echo_variants"] == {}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def test_msg_returns_confirmation_header():
|
|
174
|
+
"""_msg() 가 top-level confirmation 섹션의 값을 반환."""
|
|
175
|
+
from okstra_ctl.wizard import _msg
|
|
176
|
+
result = _msg(str(REPO_ROOT), "confirmation", "header")
|
|
177
|
+
assert result == "선택 확인:"
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_msg_unknown_section_raises():
|
|
181
|
+
"""미정의 섹션 → WizardError."""
|
|
182
|
+
from okstra_ctl.wizard import _msg
|
|
183
|
+
with pytest.raises(WizardError) as exc:
|
|
184
|
+
_msg(str(REPO_ROOT), "nonexistent_section", "any_key")
|
|
185
|
+
assert "nonexistent_section" in str(exc.value)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def test_msg_unknown_key_raises():
|
|
189
|
+
"""섹션 안에 키 없으면 WizardError."""
|
|
190
|
+
from okstra_ctl.wizard import _msg
|
|
191
|
+
with pytest.raises(WizardError) as exc:
|
|
192
|
+
_msg(str(REPO_ROOT), "confirmation", "nonexistent_key_xyz")
|
|
193
|
+
assert "nonexistent_key_xyz" in str(exc.value)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def test_msg_applies_placeholder():
|
|
197
|
+
"""_msg() 가 placeholder 를 vars 로 치환 (workers_implementation_default 같은 정적 케이스는 placeholder 없음 — 함수 동작 확인용)."""
|
|
198
|
+
from okstra_ctl.wizard import _msg
|
|
199
|
+
# Use brief_keep echo_variants which has {brief_path} placeholder via _p, but also test _msg behavior with a synthetic case.
|
|
200
|
+
# The real placeholder test: workers_implementation_default has no placeholder, but _msg should still handle empty vars.
|
|
201
|
+
result = _msg(str(REPO_ROOT), "confirmation", "workers_implementation_default")
|
|
202
|
+
assert "프로필 기본" in result
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
- [ ] **Step 2: 테스트 실패 확인**
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
python3 -m pytest tests/test_wizard_prompts.py -v 2>&1 | tail -15
|
|
209
|
+
```
|
|
210
|
+
Expected: 신규 7 tests 모두 FAIL (import error: `_msg` 없음, 또는 `echo_variants`/`errors` 키 없음).
|
|
211
|
+
|
|
212
|
+
- [ ] **Step 3: wizard.py 에 `_load_wizard_root` + `_msg` 구현 + `_p()` 확장**
|
|
213
|
+
|
|
214
|
+
`scripts/okstra_ctl/wizard.py` 의 기존 `_load_wizard_prompts` 정의 직전에 새 함수 추가:
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
_WIZARD_ROOT_CACHE: dict[str, dict] = {}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _load_wizard_root(workspace_root: str) -> dict:
|
|
221
|
+
"""Load and cache the full wizard prompts JSON root for the given workspace_root.
|
|
222
|
+
|
|
223
|
+
Returns the entire parsed JSON object (with `schema_version`, `locale`, `steps`,
|
|
224
|
+
optional `confirmation` etc. at top level). Used internally by
|
|
225
|
+
`_load_wizard_prompts` (which returns `steps`) and `_msg` (which reads
|
|
226
|
+
top-level sections like `confirmation`).
|
|
227
|
+
"""
|
|
228
|
+
if workspace_root in _WIZARD_ROOT_CACHE:
|
|
229
|
+
return _WIZARD_ROOT_CACHE[workspace_root]
|
|
230
|
+
path = Path(workspace_root) / "prompts" / "wizard" / "prompts.ko.json"
|
|
231
|
+
if not path.is_file():
|
|
232
|
+
raise WizardError(
|
|
233
|
+
f"wizard prompt SOT not found: {path}. "
|
|
234
|
+
"Re-run `okstra install` or check the workspace_root."
|
|
235
|
+
)
|
|
236
|
+
try:
|
|
237
|
+
raw = json.loads(path.read_text(encoding="utf-8"))
|
|
238
|
+
except json.JSONDecodeError as exc:
|
|
239
|
+
raise WizardError(f"wizard prompt JSON malformed: {path}: {exc}") from exc
|
|
240
|
+
if not isinstance(raw, dict):
|
|
241
|
+
raise WizardError(f"wizard prompt JSON root must be an object: {path}")
|
|
242
|
+
if raw.get("schema_version") != 1:
|
|
243
|
+
raise WizardError(
|
|
244
|
+
f"wizard prompt schema_version mismatch (expected 1): {path}"
|
|
245
|
+
)
|
|
246
|
+
if raw.get("locale") != "ko":
|
|
247
|
+
raise WizardError(
|
|
248
|
+
f"wizard prompt locale unsupported (expected 'ko'): {path}"
|
|
249
|
+
)
|
|
250
|
+
steps = raw.get("steps")
|
|
251
|
+
if not isinstance(steps, dict):
|
|
252
|
+
raise WizardError(f"wizard prompt 'steps' missing or not a dict: {path}")
|
|
253
|
+
_WIZARD_ROOT_CACHE[workspace_root] = raw
|
|
254
|
+
return raw
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
기존 `_load_wizard_prompts` 함수를 `_load_wizard_root` 의 thin wrapper 로 교체:
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
def _load_wizard_prompts(workspace_root: str) -> dict:
|
|
261
|
+
"""Backwards-compatible accessor returning the `steps` dict."""
|
|
262
|
+
return _load_wizard_root(workspace_root)["steps"]
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
(기존 `_WIZARD_PROMPTS_CACHE` 변수 — 더 이상 직접 사용 안 함. 새 `_WIZARD_ROOT_CACHE` 가 root 를 캐싱하므로 `_load_wizard_prompts` 가 매번 `_load_wizard_root["steps"]` 를 호출해도 캐시 hit. 옛 `_WIZARD_PROMPTS_CACHE` 는 dead code — 제거.)
|
|
266
|
+
|
|
267
|
+
기존 `_p` 함수의 return 에 echo_variants / errors 추가:
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
def _p(workspace_root: str, step_id: str, **vars: str) -> dict:
|
|
271
|
+
"""... (기존 docstring 유지, 다음 한 줄 추가)
|
|
272
|
+
Returned dict also includes 'echo_variants' and 'errors' sub-dicts (may be empty).
|
|
273
|
+
"""
|
|
274
|
+
steps = _load_wizard_prompts(workspace_root)
|
|
275
|
+
raw = steps.get(step_id)
|
|
276
|
+
if raw is None:
|
|
277
|
+
raise WizardError(f"unknown wizard step_id: {step_id!r}")
|
|
278
|
+
label_template = raw.get("label", "")
|
|
279
|
+
try:
|
|
280
|
+
label = label_template.format(**vars)
|
|
281
|
+
except KeyError as exc:
|
|
282
|
+
missing = exc.args[0] if exc.args else "<unknown>"
|
|
283
|
+
raise WizardError(
|
|
284
|
+
f"missing placeholder {missing!r} for wizard step {step_id!r}"
|
|
285
|
+
) from exc
|
|
286
|
+
return {
|
|
287
|
+
"label": label,
|
|
288
|
+
"echo_template": raw.get("echo_template", ""),
|
|
289
|
+
"options": raw.get("options", {}),
|
|
290
|
+
"echo_variants": raw.get("echo_variants", {}),
|
|
291
|
+
"errors": raw.get("errors", {}),
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
새 `_msg` 함수 추가 (`_p` 정의 직후):
|
|
296
|
+
|
|
297
|
+
```python
|
|
298
|
+
def _msg(workspace_root: str, section: str, key: str, **vars: str) -> str:
|
|
299
|
+
"""Look up a top-level message (e.g., `confirmation.header`) and interpolate.
|
|
300
|
+
|
|
301
|
+
Returns the string value at `<section>.<key>`. Raises WizardError if the
|
|
302
|
+
section or key is unknown, or if a required placeholder is missing.
|
|
303
|
+
|
|
304
|
+
Use for wizard-level messages (confirmation block etc.). For step-scoped
|
|
305
|
+
messages (echo_variants, errors), use `_p()` instead.
|
|
306
|
+
"""
|
|
307
|
+
root = _load_wizard_root(workspace_root)
|
|
308
|
+
sect = root.get(section)
|
|
309
|
+
if not isinstance(sect, dict):
|
|
310
|
+
raise WizardError(f"unknown wizard section: {section!r}")
|
|
311
|
+
template = sect.get(key)
|
|
312
|
+
if template is None:
|
|
313
|
+
raise WizardError(f"unknown wizard message: {section}.{key!r}")
|
|
314
|
+
try:
|
|
315
|
+
return template.format(**vars)
|
|
316
|
+
except KeyError as exc:
|
|
317
|
+
missing = exc.args[0] if exc.args else "<unknown>"
|
|
318
|
+
raise WizardError(
|
|
319
|
+
f"missing placeholder {missing!r} for wizard message {section}.{key!r}"
|
|
320
|
+
) from exc
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
- [ ] **Step 4: 단위 테스트 + 회귀 통과 확인**
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
python3 -m pytest tests/test_wizard_prompts.py -v 2>&1 | tail -20
|
|
327
|
+
```
|
|
328
|
+
Expected: 모든 기존 9 + 신규 7 = 16 tests PASS.
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
python3 -m pytest tests/ -x
|
|
332
|
+
```
|
|
333
|
+
Expected: 611 passed, 1 skipped (604 + 7 new).
|
|
334
|
+
|
|
335
|
+
- [ ] **Step 5: Commit**
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
git add scripts/okstra_ctl/wizard.py tests/test_wizard_prompts.py
|
|
339
|
+
git commit -m "feat(wizard): add _load_wizard_root + _msg helpers; extend _p() with echo_variants/errors"
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
No Claude trailer, no Co-Authored-By, no `🤖 Generated with` footer.
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Task 3: 7 한글 사이트 atomic migration
|
|
347
|
+
|
|
348
|
+
**Files:**
|
|
349
|
+
- Modify: `scripts/okstra_ctl/wizard.py`
|
|
350
|
+
|
|
351
|
+
부분 마이그레이션 시 SOT 가 깨지므로 한 commit. 동작 보존 — pytest 회귀가 검증.
|
|
352
|
+
|
|
353
|
+
- [ ] **Step 1: `_submit_task_group:643` 교체**
|
|
354
|
+
|
|
355
|
+
`scripts/okstra_ctl/wizard.py` line 643 부근:
|
|
356
|
+
|
|
357
|
+
**Before:**
|
|
358
|
+
```python
|
|
359
|
+
if value == PICK_TYPE_CUSTOM:
|
|
360
|
+
state.task_group_pending_text = True
|
|
361
|
+
return f"task-group: (직접 입력)"
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
**After:**
|
|
365
|
+
```python
|
|
366
|
+
if value == PICK_TYPE_CUSTOM:
|
|
367
|
+
state.task_group_pending_text = True
|
|
368
|
+
t = _p(state.workspace_root, "task_group")
|
|
369
|
+
return t["echo_variants"]["free_input"]
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
- [ ] **Step 2: `_submit_task_id:694` 교체**
|
|
373
|
+
|
|
374
|
+
`scripts/okstra_ctl/wizard.py` line 694 부근:
|
|
375
|
+
|
|
376
|
+
**Before:**
|
|
377
|
+
```python
|
|
378
|
+
if value == PICK_TYPE_CUSTOM:
|
|
379
|
+
state.task_id_pending_text = True
|
|
380
|
+
return f"task-id: (직접 입력)"
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
**After:**
|
|
384
|
+
```python
|
|
385
|
+
if value == PICK_TYPE_CUSTOM:
|
|
386
|
+
state.task_id_pending_text = True
|
|
387
|
+
t = _p(state.workspace_root, "task_id")
|
|
388
|
+
return t["echo_variants"]["free_input"]
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
- [ ] **Step 3: `_submit_brief_keep:769` 교체**
|
|
392
|
+
|
|
393
|
+
`scripts/okstra_ctl/wizard.py` line 769 부근:
|
|
394
|
+
|
|
395
|
+
**Before:**
|
|
396
|
+
```python
|
|
397
|
+
if state.keep_existing_brief:
|
|
398
|
+
state.brief_path = state.existing_brief_path
|
|
399
|
+
return f"brief: {state.brief_path} (유지)"
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
**After:**
|
|
403
|
+
```python
|
|
404
|
+
if state.keep_existing_brief:
|
|
405
|
+
state.brief_path = state.existing_brief_path
|
|
406
|
+
t = _p(state.workspace_root, "brief_keep", existing_brief_path=state.existing_brief_path)
|
|
407
|
+
return t["echo_variants"]["kept"].format(brief_path=state.brief_path)
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
(주의: `_p()` 의 `vars` 는 `label` 만 보간 — `echo_variants` 의 placeholder 는 별도 `.format()` 필요. 또는 `brief_path` 가 위 `existing_brief_path` 와 동일한 값이므로 단순화 가능.)
|
|
411
|
+
|
|
412
|
+
대안 (더 간단):
|
|
413
|
+
```python
|
|
414
|
+
if state.keep_existing_brief:
|
|
415
|
+
state.brief_path = state.existing_brief_path
|
|
416
|
+
t = _p(state.workspace_root, "brief_keep")
|
|
417
|
+
return t["echo_variants"]["kept"].format(brief_path=state.brief_path)
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
- [ ] **Step 4: `_submit_approved_plan_pick:900` WizardError 교체**
|
|
421
|
+
|
|
422
|
+
`scripts/okstra_ctl/wizard.py` line 900 부근:
|
|
423
|
+
|
|
424
|
+
**Before:**
|
|
425
|
+
```python
|
|
426
|
+
if default is None:
|
|
427
|
+
raise WizardError(
|
|
428
|
+
"기본 approved-plan 경로를 찾을 수 없습니다. '다른 경로 입력'을 선택하세요."
|
|
429
|
+
)
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
**After:**
|
|
433
|
+
```python
|
|
434
|
+
if default is None:
|
|
435
|
+
t = _p(state.workspace_root, "approved_plan_pick")
|
|
436
|
+
raise WizardError(t["errors"]["default_not_found"])
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
- [ ] **Step 5: `_submit_workers_override:1080` WizardError 교체**
|
|
440
|
+
|
|
441
|
+
`scripts/okstra_ctl/wizard.py` line 1080 부근:
|
|
442
|
+
|
|
443
|
+
**Before:**
|
|
444
|
+
```python
|
|
445
|
+
if not chosen:
|
|
446
|
+
raise WizardError("워커를 최소 1개 선택해주세요")
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
**After:**
|
|
450
|
+
```python
|
|
451
|
+
if not chosen:
|
|
452
|
+
t = _p(state.workspace_root, "workers_override")
|
|
453
|
+
raise WizardError(t["errors"]["min_one_required"])
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
- [ ] **Step 6: `confirmation_block:1717` + `:1727` 교체**
|
|
457
|
+
|
|
458
|
+
`scripts/okstra_ctl/wizard.py` line 1715-1748 부근의 `confirmation_block` 함수:
|
|
459
|
+
|
|
460
|
+
**Before:**
|
|
461
|
+
```python
|
|
462
|
+
def confirmation_block(state: WizardState) -> str:
|
|
463
|
+
"""Human-readable echo of the resolved selections (for the Confirm step)."""
|
|
464
|
+
lines: list[str] = ["선택 확인:"]
|
|
465
|
+
lines.append(f" task-type : {state.task_type}")
|
|
466
|
+
...
|
|
467
|
+
if state.task_type == "implementation":
|
|
468
|
+
lines.append(f" executor : {state.executor or '(default)'}")
|
|
469
|
+
lines.append(
|
|
470
|
+
f" workers : (프로필 기본 — executor + verifier 2 + report-writer)"
|
|
471
|
+
)
|
|
472
|
+
else:
|
|
473
|
+
...
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
**After:**
|
|
477
|
+
```python
|
|
478
|
+
def confirmation_block(state: WizardState) -> str:
|
|
479
|
+
"""Human-readable echo of the resolved selections (for the Confirm step)."""
|
|
480
|
+
header = _msg(state.workspace_root, "confirmation", "header")
|
|
481
|
+
lines: list[str] = [header]
|
|
482
|
+
lines.append(f" task-type : {state.task_type}")
|
|
483
|
+
...
|
|
484
|
+
if state.task_type == "implementation":
|
|
485
|
+
lines.append(f" executor : {state.executor or '(default)'}")
|
|
486
|
+
lines.append(
|
|
487
|
+
_msg(state.workspace_root, "confirmation", "workers_implementation_default")
|
|
488
|
+
)
|
|
489
|
+
else:
|
|
490
|
+
...
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
- [ ] **Step 7: 한글 grep 점검 (informational — 정식 게이트는 Task 4)**
|
|
494
|
+
|
|
495
|
+
```bash
|
|
496
|
+
grep -nP '[가-힣]' scripts/okstra_ctl/wizard.py
|
|
497
|
+
```
|
|
498
|
+
Expected: docstring (line ~1054-1055 `_build_workers_override`) + 주석만. 코드 라인 한글 0.
|
|
499
|
+
|
|
500
|
+
- [ ] **Step 8: 회귀 통과**
|
|
501
|
+
|
|
502
|
+
```bash
|
|
503
|
+
python3 -m pytest tests/ -x
|
|
504
|
+
```
|
|
505
|
+
Expected: 611 passed, 1 skipped.
|
|
506
|
+
|
|
507
|
+
기존 wizard 테스트:
|
|
508
|
+
```bash
|
|
509
|
+
python3 -m pytest tests/test_okstra_ctl_wizard.py -v 2>&1 | tail -15
|
|
510
|
+
```
|
|
511
|
+
Expected: PASS.
|
|
512
|
+
|
|
513
|
+
- [ ] **Step 9: Commit**
|
|
514
|
+
|
|
515
|
+
```bash
|
|
516
|
+
git add scripts/okstra_ctl/wizard.py
|
|
517
|
+
git commit -m "refactor(wizard): externalize _submit_* echo + WizardError + confirmation_block strings"
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
No Claude trailer, no Co-Authored-By, no `🤖 Generated with` footer.
|
|
521
|
+
|
|
522
|
+
---
|
|
523
|
+
|
|
524
|
+
## Task 4: Korean grep gate scope 확장 + CHANGES.md + 최종 회귀
|
|
525
|
+
|
|
526
|
+
**Files:**
|
|
527
|
+
- Modify: `tests/test_wizard_prompts.py` — `test_no_korean_in_build_functions` 의 scope 를 `_build_*` + `_submit_*` + `confirmation_block` 까지 확장
|
|
528
|
+
- Modify: `CHANGES.md`
|
|
529
|
+
|
|
530
|
+
- [ ] **Step 1: grep gate scope 확장**
|
|
531
|
+
|
|
532
|
+
`tests/test_wizard_prompts.py` 의 `test_no_korean_in_build_functions` 함수 명을 `test_no_korean_in_wizard_functions` 로 변경 + 함수 범위 detect regex 확장:
|
|
533
|
+
|
|
534
|
+
**Before** (기존):
|
|
535
|
+
```python
|
|
536
|
+
def test_no_korean_in_build_functions():
|
|
537
|
+
...
|
|
538
|
+
for i, line in enumerate(src_lines):
|
|
539
|
+
m = re.match(r'^def (_build_\w+)\(', line)
|
|
540
|
+
...
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
**After:**
|
|
544
|
+
```python
|
|
545
|
+
def test_no_korean_in_wizard_functions():
|
|
546
|
+
"""`_build_*` / `_submit_*` / `confirmation_block` 함수의 비-주석 코드 라인에 한글 0.
|
|
547
|
+
|
|
548
|
+
Scope: user-facing 한글이 코드에 다시 들어오는 회귀 차단.
|
|
549
|
+
정책: 주석/docstring 안의 한글은 허용 (정책상 외부화 대상 아님).
|
|
550
|
+
"""
|
|
551
|
+
...
|
|
552
|
+
SCOPED_FUNCTION_RE = re.compile(r'^def (_build_\w+|_submit_\w+|confirmation_block)\(')
|
|
553
|
+
for i, line in enumerate(src_lines):
|
|
554
|
+
m = SCOPED_FUNCTION_RE.match(line)
|
|
555
|
+
...
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
전체 함수 body 변경 (실제 구현 시 기존 detection 로직 그대로 사용하되 regex 만 위처럼 교체):
|
|
559
|
+
|
|
560
|
+
```python
|
|
561
|
+
def test_no_korean_in_wizard_functions():
|
|
562
|
+
"""`_build_*` / `_submit_*` / `confirmation_block` 함수의 비-주석 코드 라인에 한글 0.
|
|
563
|
+
|
|
564
|
+
Scope: user-facing 한글이 코드에 다시 들어오는 회귀 차단.
|
|
565
|
+
정책:
|
|
566
|
+
- 주석 라인 (# 시작) 안의 한글은 허용
|
|
567
|
+
- docstring 블록 (`\"\"\"...\"\"\"`) 안의 한글은 허용
|
|
568
|
+
- 그 외 user-facing UI 문자열은 `prompts/wizard/prompts.ko.json` 에 있어야 함.
|
|
569
|
+
"""
|
|
570
|
+
import re
|
|
571
|
+
|
|
572
|
+
KOREAN = re.compile(r"[가-힣]")
|
|
573
|
+
SCOPED_FUNCTION_RE = re.compile(r'^def (_build_\w+|_submit_\w+|confirmation_block)\(')
|
|
574
|
+
src_lines = WIZARD_PY.read_text(encoding="utf-8").splitlines()
|
|
575
|
+
|
|
576
|
+
function_ranges: list[tuple[str, int, int]] = []
|
|
577
|
+
current_name: str | None = None
|
|
578
|
+
current_start: int | None = None
|
|
579
|
+
for i, line in enumerate(src_lines):
|
|
580
|
+
m = SCOPED_FUNCTION_RE.match(line)
|
|
581
|
+
if m:
|
|
582
|
+
if current_name is not None and current_start is not None:
|
|
583
|
+
function_ranges.append((current_name, current_start, i))
|
|
584
|
+
current_name = m.group(1)
|
|
585
|
+
current_start = i
|
|
586
|
+
continue
|
|
587
|
+
if current_name is not None and re.match(r'^def \w+\(|^[A-Za-z_]|^@', line):
|
|
588
|
+
function_ranges.append((current_name, current_start, i))
|
|
589
|
+
current_name = None
|
|
590
|
+
current_start = None
|
|
591
|
+
if current_name is not None and current_start is not None:
|
|
592
|
+
function_ranges.append((current_name, current_start, len(src_lines)))
|
|
593
|
+
|
|
594
|
+
offenders: list[str] = []
|
|
595
|
+
for name, start, end in function_ranges:
|
|
596
|
+
in_docstring = False
|
|
597
|
+
i = start + 1
|
|
598
|
+
while i < end:
|
|
599
|
+
line = src_lines[i]
|
|
600
|
+
stripped = line.strip()
|
|
601
|
+
if not in_docstring and (stripped.startswith('"""') or stripped.startswith("'''")):
|
|
602
|
+
quote = stripped[:3]
|
|
603
|
+
rest = stripped[3:]
|
|
604
|
+
if rest.endswith(quote) and len(rest) >= 3:
|
|
605
|
+
pass
|
|
606
|
+
else:
|
|
607
|
+
in_docstring = True
|
|
608
|
+
i += 1
|
|
609
|
+
continue
|
|
610
|
+
if in_docstring:
|
|
611
|
+
if stripped.endswith('"""') or stripped.endswith("'''"):
|
|
612
|
+
in_docstring = False
|
|
613
|
+
i += 1
|
|
614
|
+
continue
|
|
615
|
+
if stripped.startswith("#"):
|
|
616
|
+
i += 1
|
|
617
|
+
continue
|
|
618
|
+
code_part = line.split("#", 1)[0]
|
|
619
|
+
if KOREAN.search(code_part):
|
|
620
|
+
offenders.append(f" {name} ({WIZARD_PY.name}:{i+1}): {line.rstrip()[:120]}")
|
|
621
|
+
i += 1
|
|
622
|
+
|
|
623
|
+
assert not offenders, (
|
|
624
|
+
"wizard 함수 본문에 user-facing 한글 문자열이 남아 있음 — "
|
|
625
|
+
"prompts/wizard/prompts.ko.json 으로 이동 필요:\n" + "\n".join(offenders)
|
|
626
|
+
)
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
- [ ] **Step 2: 게이트 실행 + 통과 확인**
|
|
630
|
+
|
|
631
|
+
```bash
|
|
632
|
+
python3 -m pytest tests/test_wizard_prompts.py::test_no_korean_in_wizard_functions -v
|
|
633
|
+
```
|
|
634
|
+
Expected: PASS (Task 3 후 wizard.py 코드 라인 한글 0).
|
|
635
|
+
|
|
636
|
+
만약 FAIL — 메시지가 짚는 한글이 `_build_*` / `_submit_*` / `confirmation_block` 안에 잔존. 그 사이트를 JSON 으로 이동.
|
|
637
|
+
|
|
638
|
+
전체 회귀:
|
|
639
|
+
```bash
|
|
640
|
+
python3 -m pytest tests/ -x
|
|
641
|
+
```
|
|
642
|
+
Expected: 611 passed, 1 skipped.
|
|
643
|
+
|
|
644
|
+
- [ ] **Step 3: CHANGES.md 항목 추가**
|
|
645
|
+
|
|
646
|
+
`CHANGES.md` 의 `## 2026-05-20` 섹션 최상단 (Phase A1 entry 위) 에:
|
|
647
|
+
|
|
648
|
+
```markdown
|
|
649
|
+
### refactor(wizard): wizard 의 모든 user-facing 한글 JSON SOT 단일화 (A1 follow-up)
|
|
650
|
+
|
|
651
|
+
- Phase A1 의 plan scope 외였던 잔존 한글 외부화: `_submit_*` echo 변형 (3개), `WizardError` 메시지 (2개), `confirmation_block` 라인 (2개) 을 `prompts/wizard/prompts.ko.json` 으로 이전.
|
|
652
|
+
- 스키마 확장: step entry 안에 `echo_variants` / `errors` 서브필드, 최상위 `confirmation` 섹션 추가. `schema_version` 은 1 유지 (backward compatible 추가).
|
|
653
|
+
- 신규 헬퍼: `_load_wizard_root(workspace_root)` (전체 root 캐시) + `_msg(workspace_root, section, key, **vars)` (top-level 섹션 lookup). 기존 `_p()` 는 echo_variants / errors 패스스루.
|
|
654
|
+
- 정적 게이트 scope 확장: `test_no_korean_in_wizard_functions` 가 `_build_*` + `_submit_*` + `confirmation_block` 함수 본문 모두 검사. wizard.py 코드 라인 한글 0 보장 (docstring 제외).
|
|
655
|
+
- 사용자 영향: 없음 (UI 동일). 컨트리뷰터 영향: wizard 의 모든 user-facing 문자열이 한 JSON 파일에. i18n 시 `prompts.<locale>.json` 하나만 추가하면 됨.
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
- [ ] **Step 4: npm build smoke**
|
|
659
|
+
|
|
660
|
+
```bash
|
|
661
|
+
npm run build
|
|
662
|
+
```
|
|
663
|
+
Expected: 에러 없음.
|
|
664
|
+
|
|
665
|
+
검증:
|
|
666
|
+
```bash
|
|
667
|
+
python3 -c "import json; d=json.load(open('runtime/prompts/wizard/prompts.ko.json')); assert d['confirmation']['header'] == '선택 확인:'; print('OK')"
|
|
668
|
+
```
|
|
669
|
+
Expected: OK.
|
|
670
|
+
|
|
671
|
+
- [ ] **Step 5: 최종 회귀 + e2e**
|
|
672
|
+
|
|
673
|
+
```bash
|
|
674
|
+
python3 -m pytest tests/ -v 2>&1 | tail -10
|
|
675
|
+
```
|
|
676
|
+
Expected: 611 passed, 1 skipped.
|
|
677
|
+
|
|
678
|
+
```bash
|
|
679
|
+
bash tests-e2e/scenario-01-record-start-reconcile.sh
|
|
680
|
+
```
|
|
681
|
+
Expected: PASS.
|
|
682
|
+
|
|
683
|
+
- [ ] **Step 6: Commit**
|
|
684
|
+
|
|
685
|
+
```bash
|
|
686
|
+
git add tests/test_wizard_prompts.py CHANGES.md
|
|
687
|
+
git commit -m "test(wizard): extend Korean grep gate to _submit_* + confirmation_block"
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
(CHANGES.md + gate scope 확장이 같은 PR 의 연관 작업이라 한 commit. 또는 분리해도 OK:
|
|
691
|
+
```bash
|
|
692
|
+
git add tests/test_wizard_prompts.py
|
|
693
|
+
git commit -m "test(wizard): extend Korean grep gate to _submit_* + confirmation_block"
|
|
694
|
+
git add CHANGES.md
|
|
695
|
+
git commit -m "docs(changes): note wizard messages JSON SOT extension"
|
|
696
|
+
```
|
|
697
|
+
)
|
|
698
|
+
|
|
699
|
+
No Claude trailer, no Co-Authored-By, no `🤖 Generated with` footer.
|
|
700
|
+
|
|
701
|
+
---
|
|
702
|
+
|
|
703
|
+
## Self-Review 체크리스트
|
|
704
|
+
|
|
705
|
+
- [x] **Spec coverage**: 7 한글 사이트 모두 Task 3 에 명시. JSON 신규 키 Task 1 에 명시. 헬퍼 (Task 2). 게이트 scope 확장 (Task 4). 모두 커버.
|
|
706
|
+
- [x] **Placeholder scan**: TBD/TODO 없음. 모든 step 에 실제 코드/명령/grep 패턴.
|
|
707
|
+
- [x] **Type consistency**: `_load_wizard_root(workspace_root: str) -> dict` / `_msg(workspace_root: str, section: str, key: str, **vars: str) -> str` / `_p()` 확장 반환 키 (`echo_variants`, `errors`) — 모든 task 에서 일관.
|
|
708
|
+
- [x] **Atomic migration**: Task 3 한 commit. 부분 migration 위험 없음.
|
|
709
|
+
- [x] **Backward compatibility**: 기존 `_load_wizard_prompts` API 보존 (steps return). schema_version 1 유지 (새 필드는 additive).
|
|
710
|
+
|
|
711
|
+
---
|
|
712
|
+
|
|
713
|
+
## 실행 옵션
|
|
714
|
+
|
|
715
|
+
Plan complete and saved to [docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md](docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md). 두 가지 실행 방식:
|
|
716
|
+
|
|
717
|
+
1. **Subagent-Driven (recommended)** — task 마다 새로운 subagent + spec/code quality 2-stage review.
|
|
718
|
+
2. **Inline Execution** — 본 세션에서 executing-plans 로 batch 실행.
|
|
719
|
+
|
|
720
|
+
어떤 방식으로 진행할까요?
|