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.
Files changed (101) hide show
  1. package/README.kr.md +26 -16
  2. package/README.md +26 -16
  3. package/docs/kr/architecture.md +59 -45
  4. package/docs/kr/cli.md +61 -18
  5. package/docs/pr-template-usage.md +65 -0
  6. package/docs/project-structure-overview.md +358 -354
  7. package/docs/superpowers/plans/2026-05-12-ticket-id-in-reports.md +1 -1
  8. package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +1 -1
  9. package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +1 -1
  10. package/docs/superpowers/plans/2026-05-20-final-report-language.md +1501 -0
  11. package/docs/superpowers/plans/2026-05-20-implementation-planning-multi-stage.md +1267 -0
  12. package/docs/superpowers/plans/2026-05-20-okstra-run-prompt-sot-b1.md +1007 -0
  13. package/docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md +720 -0
  14. package/docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md +681 -0
  15. package/docs/superpowers/plans/2026-05-21-improvement-discovery-task-type.md +1691 -0
  16. package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +383 -0
  17. package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +320 -0
  18. package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +299 -0
  19. package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +335 -0
  20. package/docs/task-process/README.md +74 -0
  21. package/docs/task-process/common-flow.md +166 -0
  22. package/docs/task-process/error-analysis.md +101 -0
  23. package/docs/task-process/final-verification.md +167 -0
  24. package/docs/task-process/implementation-planning.md +128 -0
  25. package/docs/task-process/implementation.md +149 -0
  26. package/docs/task-process/release-handoff.md +206 -0
  27. package/docs/task-process/requirements-discovery.md +115 -0
  28. package/package.json +1 -1
  29. package/runtime/BUILD.json +2 -2
  30. package/runtime/agents/SKILL.md +12 -2
  31. package/runtime/agents/workers/claude-worker.md +26 -0
  32. package/runtime/agents/workers/codex-worker.md +27 -1
  33. package/runtime/agents/workers/gemini-worker.md +27 -1
  34. package/runtime/agents/workers/report-writer-worker.md +8 -1
  35. package/runtime/bin/okstra-central.sh +6 -6
  36. package/runtime/bin/okstra-codex-exec.sh +49 -28
  37. package/runtime/bin/okstra-gemini-exec.sh +39 -21
  38. package/runtime/bin/okstra-render-final-report.py +13 -2
  39. package/runtime/bin/okstra-wrapper-status.py +155 -0
  40. package/runtime/bin/okstra.sh +2 -2
  41. package/runtime/prompts/profiles/_common-contract.md +11 -6
  42. package/runtime/prompts/profiles/error-analysis.md +3 -7
  43. package/runtime/prompts/profiles/implementation-planning.md +22 -21
  44. package/runtime/prompts/profiles/implementation.md +28 -11
  45. package/runtime/prompts/profiles/improvement-discovery.md +42 -0
  46. package/runtime/prompts/profiles/kr/_common-contract.md +92 -0
  47. package/runtime/prompts/profiles/kr/error-analysis.md +36 -0
  48. package/runtime/prompts/profiles/kr/final-verification.md +48 -0
  49. package/runtime/prompts/profiles/kr/implementation-planning.md +90 -0
  50. package/runtime/prompts/profiles/kr/implementation.md +144 -0
  51. package/runtime/prompts/profiles/kr/improvement-discovery.md +42 -0
  52. package/runtime/prompts/profiles/kr/release-handoff.md +104 -0
  53. package/runtime/prompts/profiles/kr/requirements-discovery.md +42 -0
  54. package/runtime/prompts/profiles/release-handoff.md +1 -1
  55. package/runtime/prompts/profiles/requirements-discovery.md +8 -12
  56. package/runtime/prompts/wizard/prompts.ko.json +230 -0
  57. package/runtime/python/lib/okstra/cli.sh +2 -49
  58. package/runtime/python/lib/okstra/globals.sh +21 -21
  59. package/runtime/python/lib/okstra/interactive.sh +7 -7
  60. package/runtime/python/okstra_ctl/clarification_items.py +3 -9
  61. package/runtime/python/okstra_ctl/consumers.py +53 -0
  62. package/runtime/python/okstra_ctl/final_report_schema.py +0 -7
  63. package/runtime/python/okstra_ctl/i18n.py +73 -0
  64. package/runtime/python/okstra_ctl/improvement_lenses.py +44 -0
  65. package/runtime/python/okstra_ctl/index.py +1 -1
  66. package/runtime/python/okstra_ctl/paths.py +23 -20
  67. package/runtime/python/okstra_ctl/render.py +147 -202
  68. package/runtime/python/okstra_ctl/render_final_report.py +53 -10
  69. package/runtime/python/okstra_ctl/run.py +292 -107
  70. package/runtime/python/okstra_ctl/run_context.py +22 -0
  71. package/runtime/python/okstra_ctl/seeding.py +186 -0
  72. package/runtime/python/okstra_ctl/wizard.py +348 -127
  73. package/runtime/python/okstra_ctl/workflow.py +21 -2
  74. package/runtime/python/okstra_ctl/worktree.py +54 -1
  75. package/runtime/python/okstra_project/resolver.py +4 -3
  76. package/runtime/python/okstra_token_usage/report.py +2 -2
  77. package/runtime/schemas/final-report-v1.0.schema.json +22 -16
  78. package/runtime/skills/okstra-brief/SKILL.md +124 -31
  79. package/runtime/skills/okstra-convergence/SKILL.md +2 -3
  80. package/runtime/skills/okstra-report-writer/SKILL.md +35 -15
  81. package/runtime/skills/okstra-run/SKILL.md +5 -4
  82. package/runtime/skills/okstra-schedule/SKILL.md +4 -4
  83. package/runtime/skills/okstra-setup/SKILL.md +27 -0
  84. package/runtime/skills/okstra-team-contract/SKILL.md +1 -1
  85. package/runtime/templates/okstra.CLAUDE.md +104 -0
  86. package/runtime/templates/reports/final-report.template.md +93 -98
  87. package/runtime/templates/reports/i18n/en.json +135 -0
  88. package/runtime/templates/reports/i18n/ko.json +135 -0
  89. package/runtime/templates/reports/implementation-planning-input.template.md +18 -0
  90. package/runtime/templates/reports/improvement-discovery-input.template.md +78 -0
  91. package/runtime/templates/reports/task-brief.template.md +2 -2
  92. package/runtime/validators/lib/fixtures.sh +30 -0
  93. package/runtime/validators/lib/runners.sh +1 -1
  94. package/runtime/validators/validate-implementation-plan-stages.py +211 -0
  95. package/runtime/validators/validate-run.py +121 -26
  96. package/runtime/validators/validate-workflow.sh +2 -2
  97. package/runtime/validators/validate_improvement_report.py +275 -0
  98. package/src/config.mjs +18 -0
  99. package/src/install.mjs +41 -14
  100. package/src/setup.mjs +133 -1
  101. package/src/uninstall.mjs +21 -1
@@ -0,0 +1,383 @@
1
+ # Final Report Language Configuration — Design Spec
2
+
3
+ - Date: 2026-05-20
4
+ - Status: Approved (pending writing-plans)
5
+ - Owner: Claude lead (this session)
6
+ - Related: `templates/reports/final-report.template.md`, `scripts/okstra-render-final-report.py`, `skills/okstra-setup/SKILL.md`, `skills/okstra-report-writer/SKILL.md`, `agents/SKILL.md`
7
+
8
+ ## 1. Problem
9
+
10
+ The okstra final report is currently hardcoded to Korean in three places:
11
+
12
+ 1. `skills/okstra-report-writer/SKILL.md` Writing Guidelines — `Write the final report body in Korean.`
13
+ 2. `templates/reports/final-report.template.md` — ~30–40 lines of Korean fixed text (section intros, empty-state lines, column headers, token-summary "읽는 법" block, release-handoff 4.6.x labels, etc.).
14
+ 3. `agents/SKILL.md` line 322 — lead's post-persistence reply hardcoded to Korean.
15
+
16
+ There is no way to opt into English (or any other language) without manually patching skill / template files. The user request: add a setup step that selects the final-report language, defaulting to English.
17
+
18
+ ## 2. Goals & non-goals
19
+
20
+ ### Goals
21
+
22
+ - One project-level setting (`reportLanguage`) decides the language of (a) the prose authored by the report-writer worker and (b) the fixed labels rendered from the template.
23
+ - Default for **new** setups is English. Default for **existing** projects whose `project.json` predates this change is `auto` (no surprise switch to English for Korean-speaking users).
24
+ - `auto` resolves to `en` or `ko` based on the task brief's main prose language.
25
+ - Validator-checked English identifiers (`Option Candidates`, `Verdict Token`, `accepted` / `conditional-accept` / `blocked`, etc.) remain English in both languages.
26
+
27
+ ### Non-goals (this spec)
28
+
29
+ - Worker analysis prose, lead user-facing messages outside the final-report flow, `okstra-status` / `okstra-schedule` skill responses, and `prompts/profiles/*.md` Korean preferences are **out of scope**.
30
+ - Languages other than `en` and `ko`. (Adding a third language later requires only a new `i18n/<lang>.yaml` file plus extending the enum.)
31
+ - Worker-results audit files under `runs/.../worker-results/*.md` stay in their current language (Korean prose by default).
32
+
33
+ ## 3. Data model
34
+
35
+ ### 3.1 `project.json` — new optional field
36
+
37
+ ```json
38
+ {
39
+ "projectId": "...",
40
+ "projectRoot": "...",
41
+ "reportLanguage": "auto" | "en" | "ko"
42
+ }
43
+ ```
44
+
45
+ - Listed as a user-managed field in `scripts/okstra_project/resolver.py` so runtime `upsert_project_json` preserves it across `okstra setup` / `okstra run` invocations (same treatment as `worktreeSyncDirs`, `qaCommands`, `prTemplatePath`).
46
+ - Absent field ⇒ runtime treats as `"auto"`.
47
+
48
+ ### 3.2 `~/.okstra/config.json` — optional global fallback
49
+
50
+ ```json
51
+ { "reportLanguage": "auto" | "en" | "ko" }
52
+ ```
53
+
54
+ Same enum, used only when `project.json` doesn't have the field.
55
+
56
+ ### 3.3 `final-report-v1.0.schema.json` — audit field
57
+
58
+ Add required `meta.reportLanguage` ∈ `{"en", "ko"}` (no `"auto"` — the lead resolves before dispatching).
59
+
60
+ Rationale: the renderer needs an SSOT inside the data.json so re-rendering after the fact (e.g. someone re-runs `okstra-render-final-report.py` without a `--report-language` flag) produces the same artifact.
61
+
62
+ ### 3.4 Resolution priority (lead)
63
+
64
+ ```
65
+ CLI flag `--report-language`
66
+ > project.json.reportLanguage
67
+ > ~/.okstra/config.json.reportLanguage
68
+ > "auto"
69
+ ```
70
+
71
+ If the resolved value is `"auto"`, the lead inspects the task brief and picks `en` or `ko` based on its main prose language. Default to `en` when the brief is mostly code/identifiers and the language is ambiguous. The lead writes the resolved literal `en`/`ko` into the report-writer dispatch prompt's `**Report Language:**` line and into `data.json.meta.reportLanguage`.
72
+
73
+ ## 4. Setup flow
74
+
75
+ ### 4.1 `skills/okstra-setup/SKILL.md` — add Step 4.9
76
+
77
+ Insert between current Step 4.8 (PR body template) and Step 5 (`okstra doctor`).
78
+
79
+ ```
80
+ ## Step 4.9 (optional): choose final report language
81
+
82
+ Skip-friendly. Default behaviour when this step is skipped is `auto`
83
+ (report-writer infers from the task brief, falling back to English).
84
+
85
+ AskUserQuestion (fixed options):
86
+ - Question: "Final report를 어느 언어로 작성할까요?"
87
+ - Options:
88
+ 1. English (recommended) → reportLanguage: "en"
89
+ 2. 한국어 → reportLanguage: "ko"
90
+ 3. Auto (task brief 언어로 추론, 불분명하면 영어) → reportLanguage: "auto"
91
+
92
+ After selection:
93
+ okstra config set report-language <en|ko|auto> --scope project
94
+ ```
95
+
96
+ Project scope only at this step. Global scope is mentioned in README but not prompted during setup (advanced).
97
+
98
+ ### 4.2 New CLI subcommands
99
+
100
+ Same handler pattern as the existing `okstra config set pr-template-path`:
101
+
102
+ ```bash
103
+ okstra config set report-language <en|ko|auto> --scope <project|global>
104
+ okstra config get report-language --scope <project|global>
105
+ ```
106
+
107
+ Validation:
108
+ - Value must be one of `en`, `ko`, `auto`. Any other value exits `1` with a clear error.
109
+ - `--scope` is required and must be `project` or `global`.
110
+ - Atomic write (`config-write` path used by `pr-template-path`).
111
+ - Output: JSON to stdout naming which file was updated, same shape as the existing `pr-template-path` command.
112
+
113
+ Confirm during implementation that `okstra config get` already exists for `pr-template-path`; if not, extend it to support both keys.
114
+
115
+ ## 5. Template i18n
116
+
117
+ ### 5.1 Dictionary location
118
+
119
+ ```
120
+ templates/reports/i18n/en.yaml
121
+ templates/reports/i18n/ko.yaml
122
+ ```
123
+
124
+ YAML chosen because the Korean help text (e.g. the "읽는 법" block) is multi-line and benefits from `|` literal blocks; JSON would require escaped `\n` everywhere. If PyYAML is not already a runtime dependency of okstra Python (to be confirmed during implementation), fall back to JSON with `\n` escapes.
125
+
126
+ ### 5.2 Key naming
127
+
128
+ Dot-namespaced groups. Jinja accesses via `t.section.subkey`.
129
+
130
+ ```yaml
131
+ verdictCard:
132
+ intro: >-
133
+ At-a-glance verdict card. Every value in this table MUST exactly match
134
+ the authoritative values in `## 2. Final Verdict` and `## 6.
135
+ Recommended Next Steps`.
136
+
137
+ tokenSummary:
138
+ heading: "Token Usage Summary"
139
+ columns:
140
+ item: "Item"
141
+ raw: "Raw tokens"
142
+ billable: "Billable tokens (input-equiv.)"
143
+ cost: "Cost (USD)"
144
+ rows:
145
+ lead: "Lead"
146
+ workerTotal: "Worker subtotal"
147
+ grandTotal: "**Grand total**"
148
+ cliExtra: "Codex/Gemini CLI add-on"
149
+ howToRead: |
150
+ **How to read**: "Raw tokens" is the sum of input + output +
151
+ cache_creation + cache_read processed by the model …
152
+
153
+ emptyState:
154
+ consensusItems: "- No consensus items."
155
+ contestedItems: "- No items missing consensus."
156
+ primaryEvidence: "- No primary evidence."
157
+ secondaryEvidence: "- No secondary evidence or alternate interpretations."
158
+ risks: "- No missing information or risks."
159
+ dependencyRisk: "- No dependency / migration risks."
160
+ dissent: "- No dissenting opinions."
161
+ outOfPlanEdits: "- No out-of-plan edits."
162
+ noFollowUp: "- No follow-up tasks. The next phase for this run is in §6 (Recommended Next Steps)."
163
+ noClarification: "- No additional information requested. The Section 2 verdict stands as-is."
164
+
165
+ sectionAside:
166
+ dependencyRisk: "Dependency / Migration Risk"
167
+ validationChecklist: "Validation Checklist"
168
+ rollbackStrategy: "Rollback Strategy"
169
+ planBodyVerification: "Plan Body Verification"
170
+ recommendedOption: "Recommended Option"
171
+ optionCandidates: "Option Candidates"
172
+ tradeOffMatrix: "Trade-off Matrix"
173
+ stepwiseExecutionOrder: "Stepwise Execution Order"
174
+
175
+ # ... and similar groups for release-handoff 4.6.x, evidence column
176
+ # headers, etc.
177
+ ```
178
+
179
+ The `ko.yaml` mirror keeps Korean strings; **both files must have identical key sets** (enforced by a unit test — see §8).
180
+
181
+ ### 5.3 Validator-checked English substrings
182
+
183
+ These remain English in both `en.yaml` and `ko.yaml` (i.e. `sectionAside.optionCandidates` in `ko.yaml` is still the English string `Option Candidates`):
184
+
185
+ - Phase-specific heading anchors: `Option Candidates`, `Trade-off`, `Recommended Option`, `Stepwise Execution Order`, `Dependency`, `Validation Checklist`, `Rollback`, `User Approval Request`, `Plan Body Verification`, `Gate result:`
186
+ - Verdict tokens: `accepted`, `conditional-accept`, `blocked`, `not-applicable`
187
+ - Direction tokens: `continue-investigation`, `begin-implementation`, `approve`, `reject`, `hold`
188
+
189
+ Korean parenthetical aside is i18n'd. For Korean reports this renders as `### Recommended Option (권장 옵션)`; for English reports it renders as `### Recommended Option (Recommended Option)` — which looks silly. **Decision:** when Korean and English would produce the same string, the template omits the parenthetical entirely. Template form:
190
+
191
+ ```jinja
192
+ ### Recommended Option{% if t.sectionAside.recommendedOption != "Recommended Option" %} ({{ t.sectionAside.recommendedOption }}){% endif %}
193
+ ```
194
+
195
+ ### 5.4 Missing-key policy
196
+
197
+ Jinja `Environment(undefined=StrictUndefined)` — referencing a missing key raises `UndefinedError` at render time. No silent fallback to the key name. This makes "I forgot to translate X" fail loudly during e2e instead of shipping `{{ t.foo.bar }}` literal in production reports.
198
+
199
+ ### 5.5 Template conversion scope
200
+
201
+ Run `grep -P '[\x{AC00}-\x{D7A3}]'` (Korean Unicode range) against `templates/reports/final-report.template.md` until 0 matches remain. Estimated 50–70 i18n keys across the categories:
202
+
203
+ 1. Section intro prose (lines 35, 49, 54, 84, 131, 243, 275, 525)
204
+ 2. Empty-state lines (lines 122, 136, 162, 176, 188, 248, 299, 424, 449, 495, 527, 530, 570)
205
+ 3. Column headers (line 56 "한 줄 요약 / 출처", line 86 "처리 토큰 / 환산 토큰 / 비용", line 267 "확인 방법")
206
+ 4. Heading Korean aside (lines 245, 257, 273, the 4.5.x series)
207
+ 5. The token-summary "읽는 법" blockquote
208
+ 6. release-handoff 4.6.x Korean labels (lines 325, 329, 448, 473, etc.)
209
+
210
+ Identifier strings (`Verdict Token`, JSON code blocks, model names, file paths) stay verbatim.
211
+
212
+ ## 6. Renderer changes
213
+
214
+ ### 6.1 `scripts/okstra-render-final-report.py`
215
+
216
+ ```python
217
+ import argparse, json, yaml
218
+ from pathlib import Path
219
+ import jinja2
220
+
221
+ def main():
222
+ ap = argparse.ArgumentParser()
223
+ ap.add_argument("data_json", type=Path)
224
+ ap.add_argument("--report-language", choices=["en", "ko"], default=None)
225
+ args = ap.parse_args()
226
+
227
+ data = json.loads(args.data_json.read_text())
228
+ lang = (
229
+ args.report_language
230
+ or data.get("meta", {}).get("reportLanguage")
231
+ or "en"
232
+ )
233
+ if lang not in {"en", "ko"}:
234
+ raise SystemExit(f"reportLanguage must be 'en' or 'ko', got {lang!r}")
235
+
236
+ i18n_path = Path(__file__).parent.parent / "templates" / "reports" / "i18n" / f"{lang}.yaml"
237
+ i18n = yaml.safe_load(i18n_path.read_text())
238
+
239
+ env = jinja2.Environment(
240
+ loader=jinja2.FileSystemLoader(...),
241
+ undefined=jinja2.StrictUndefined,
242
+ autoescape=False,
243
+ keep_trailing_newline=True,
244
+ )
245
+ tmpl = env.get_template("final-report.template.md")
246
+ out_path = args.data_json.with_suffix("").with_suffix(".md")
247
+ out_path.write_text(tmpl.render(**data, t=i18n))
248
+ ```
249
+
250
+ (Sketch — actual file structure follows the existing renderer's conventions; the changes are: `--report-language` flag, `meta.reportLanguage` read, YAML loader, `t=i18n` context key, `StrictUndefined`.)
251
+
252
+ ### 6.2 `scripts/okstra-render-report-views.py`
253
+
254
+ Reads the rendered markdown — no logic change needed. The HTML view inherits whatever language the markdown was rendered in.
255
+
256
+ ## 7. Worker / lead contract changes
257
+
258
+ ### 7.1 `agents/SKILL.md` (Claude lead)
259
+
260
+ Add to the Phase 6 preparation checklist:
261
+
262
+ ```
263
+ - Resolve report language: read `project.json.reportLanguage`
264
+ (fallback `~/.okstra/config.json.reportLanguage`, then literal `auto`).
265
+ If the resolved value is `auto`, inspect the task brief and pick `en`
266
+ or `ko` based on its main prose language (default `en` when the brief
267
+ is mostly code/identifiers). Pass the final `en`/`ko` value as
268
+ `**Report Language:**` in the report-writer dispatch prompt and ensure
269
+ the worker writes the same value into `data.json.meta.reportLanguage`.
270
+ ```
271
+
272
+ Replace line 322:
273
+
274
+ ```diff
275
+ - After persistence, reply briefly in Korean with: completion status, ...
276
+ + After persistence, reply briefly in the resolved Report Language with:
277
+ + completion status, ...
278
+ ```
279
+
280
+ ### 7.2 `skills/okstra-report-writer/SKILL.md`
281
+
282
+ Add a new entry to the Phase 6 dispatch prompt's required-header list. The list currently has 11 items; insert the new entry immediately after the convergence-classifications item (current item 9) and renumber the remaining items:
283
+
284
+ ```
285
+ **Report Language:** <en|ko> # auto must be pre-resolved by the lead;
286
+ # the worker writes this verbatim into
287
+ # data.json.meta.reportLanguage
288
+ ```
289
+
290
+ Replace the two hardcoded "Korean" lines (Writing Guidelines + Response after Persistence):
291
+
292
+ ```diff
293
+ - - Write the final report body in Korean.
294
+ + - Write the final report body in the language passed as **Report Language**
295
+ + in the dispatch prompt. The template's fixed labels (section asides,
296
+ + empty-states, token summary, etc.) are i18n-rendered by
297
+ + `okstra-render-final-report.py` from `templates/reports/i18n/<lang>.yaml`;
298
+ + focus on the prose you author (Section 1 categories, Section 3 evidence
299
+ + narratives, Section 4 risks, Section 6 recommendations, etc.).
300
+ + Code identifiers, file paths, model names, status tokens, and the
301
+ + validator-checked English substrings stay in English regardless of
302
+ + Report Language.
303
+ ```
304
+
305
+ ```diff
306
+ - Provide a concise report in Korean covering the following:
307
+ + Provide a concise report in the Report Language covering the following:
308
+ ```
309
+
310
+ Add to the data.json contract (alongside the token-cell-null paragraph):
311
+
312
+ ```
313
+ - Set `meta.reportLanguage` to the resolved `en` or `ko` value passed in
314
+ **Report Language**. `auto` is forbidden here — the lead has already
315
+ resolved it. The renderer reads this field as the SSOT when no CLI
316
+ `--report-language` flag is given.
317
+ ```
318
+
319
+ ### 7.3 `agents/workers/report-writer-worker.md`
320
+
321
+ Add `templates/reports/i18n/en.yaml` and `templates/reports/i18n/ko.yaml` to the worker's reading set. One-line reminder that the `Report Language` header value goes into `data.json.meta.reportLanguage` verbatim.
322
+
323
+ ## 8. Tests
324
+
325
+ ### 8.1 Unit (`tests/`)
326
+
327
+ - `test_report_language_resolver.py` — resolution priority (CLI flag > project.json > global config > `"auto"`).
328
+ - `test_report_language_config_set.py` — `okstra config set report-language` accepts `en`/`ko`/`auto`, rejects others; writes to project.json or `~/.okstra/config.json` per scope.
329
+ - `test_i18n_keysets_match.py` — `en.yaml` and `ko.yaml` have **identical key sets** (set diff must be empty in both directions).
330
+ - `test_i18n_missing_key_raises.py` — referencing an undefined `t.foo.bar` from the template raises `jinja2.UndefinedError`.
331
+ - `test_render_meta_report_language.py` — renderer reads `data.json.meta.reportLanguage` as SSOT when no `--report-language` flag is given.
332
+ - `test_template_no_korean_glyphs.py` — `grep -P '[\x{AC00}-\x{D7A3}]'` against `templates/reports/final-report.template.md` returns zero matches. (Regression guard for "someone re-introduced Korean fixed text".)
333
+
334
+ ### 8.2 E2E (`tests-e2e/`)
335
+
336
+ - `scenario-N-final-report-en.sh` — full okstra run with `reportLanguage: "en"`. Asserts the rendered markdown contains the English `Token Usage Summary` heading and does NOT contain Korean characters in template-owned regions.
337
+ - `scenario-M-final-report-ko.sh` — same with `reportLanguage: "ko"`. Asserts the Korean `토큰 사용량 요약` heading is present.
338
+
339
+ ## 9. Migration / rollout
340
+
341
+ - pre-v1 → no compat shims. Existing reports are not re-rendered.
342
+ - Existing `project.json` files have no `reportLanguage` field → runtime treats as `"auto"`. Korean-speaking users writing Korean briefs continue to get Korean reports without any action.
343
+ - New `okstra setup` runs see Step 4.9 with `English` as the recommended default — users explicitly opt into the new default through that prompt.
344
+ - `CHANGES.md` entry:
345
+
346
+ ```
347
+ ### feat(setup): final report language is now configurable
348
+ - 사용자 영향: 신규 `okstra setup` 의 final report 기본 언어가 영어로 바뀝니다.
349
+ 기존 프로젝트(`project.json.reportLanguage` 미설정)는 `auto` 로 동작하여
350
+ task brief 의 주 서술 언어를 따라가므로, 한국어 brief 를 쓰던 사용자에게
351
+ 재설정 없이 종전과 같은 한국어 보고서가 출력됩니다.
352
+ - 새 CLI: `okstra config set report-language <en|ko|auto> --scope <project|global>`.
353
+ - i18n 사전: `templates/reports/i18n/{en,ko}.yaml`.
354
+ ```
355
+
356
+ ## 10. Files touched (final list)
357
+
358
+ | File | Change |
359
+ |---|---|
360
+ | `templates/reports/final-report.template.md` | Korean fixed text → `{{ t.* }}` i18n references |
361
+ | `templates/reports/i18n/en.yaml` | NEW |
362
+ | `templates/reports/i18n/ko.yaml` | NEW |
363
+ | `scripts/okstra-render-final-report.py` | `--report-language` flag, YAML loader, `StrictUndefined`, `t=i18n` |
364
+ | `schemas/final-report-v1.0.schema.json` | Add required `meta.reportLanguage` ∈ `{en, ko}` |
365
+ | `scripts/okstra_project/resolver.py` | `reportLanguage` added to preserved user fields list |
366
+ | `src/config.mjs` (or wherever `pr-template-path` lives) | `set/get report-language` subcommands |
367
+ | `skills/okstra-setup/SKILL.md` | Step 4.9 |
368
+ | `skills/okstra-report-writer/SKILL.md` | Dispatch prompt header + Writing Guidelines + data.json contract |
369
+ | `agents/SKILL.md` | Phase 6 prep checklist + line 322 |
370
+ | `agents/workers/report-writer-worker.md` | Reading set + `meta.reportLanguage` reminder |
371
+ | `tests/test_*.py` (6 new) | See §8.1 |
372
+ | `tests-e2e/scenario-N-final-report-en.sh` | NEW |
373
+ | `tests-e2e/scenario-M-final-report-ko.sh` | NEW |
374
+ | `CHANGES.md` | New entry per §9 |
375
+
376
+ ## 11. Open implementation questions
377
+
378
+ To be resolved during writing-plans / implementation:
379
+
380
+ 1. Whether PyYAML is already a runtime dependency of okstra Python (otherwise fall back to JSON dictionaries).
381
+ 2. Whether `okstra config get` exists today for `pr-template-path` — extend if yes, add if no.
382
+ 3. Exact i18n key namespacing for the release-handoff 4.6.x labels (largest single chunk of Korean text outside §4.5).
383
+ 4. Test scenario sequencing — pick free `scenario-NN` numbers in `tests-e2e/`.