okstra 0.102.2 → 0.102.3

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