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,230 @@
1
+ {
2
+ "schema_version": 1,
3
+ "locale": "ko",
4
+ "steps": {
5
+ "task_pick": {
6
+ "label": "어느 task?",
7
+ "echo_template": "task: {value}",
8
+ "options": {
9
+ "__new__": "Start a brand-new task",
10
+ "_LATEST_SUFFIX": " (latest)"
11
+ }
12
+ },
13
+ "task_group": {
14
+ "label": "Task group 을 알려주세요 (예: backend-api, INV-1234, refactor)",
15
+ "echo_template": "task-group: {value}",
16
+ "echo_variants": {
17
+ "free_input": "task-group: (직접 입력)"
18
+ }
19
+ },
20
+ "task_group_with_suggestion": {
21
+ "label": "Task group? (brief 추천: {suggestion})",
22
+ "echo_template": "task-group: {value}",
23
+ "options": {
24
+ "__use_suggested__": "brief 값 사용: {suggestion}",
25
+ "__free_input__": "다른 값 입력"
26
+ }
27
+ },
28
+ "task_group_text": {
29
+ "label": "Task group 을 입력해주세요 (예: backend-api, INV-1234, refactor)",
30
+ "echo_template": "task-group: {value}"
31
+ },
32
+ "task_id": {
33
+ "label": "Task id 를 알려주세요 (예: login-error-analysis, dev-9043)",
34
+ "echo_template": "task-id: {value}",
35
+ "echo_variants": {
36
+ "free_input": "task-id: (직접 입력)"
37
+ }
38
+ },
39
+ "task_id_with_suggestion": {
40
+ "label": "Task id? (brief 추천: {suggestion})",
41
+ "echo_template": "task-id: {value}",
42
+ "options": {
43
+ "__use_suggested__": "brief 값 사용: {suggestion}",
44
+ "__free_input__": "다른 값 입력"
45
+ }
46
+ },
47
+ "task_id_text": {
48
+ "label": "Task id 를 입력해주세요 (예: login-error-analysis, dev-9043)",
49
+ "echo_template": "task-id: {value}"
50
+ },
51
+ "task_type": {
52
+ "label": "Task type?",
53
+ "echo_template": "task-type: {value}",
54
+ "options": {
55
+ "_RECOMMENDED_SUFFIX": " (recommended)"
56
+ }
57
+ },
58
+ "brief_keep": {
59
+ "label": "기존 brief 경로 [{existing_brief_path}] 를 유지할까요?",
60
+ "echo_template": "brief: {value}",
61
+ "options": {
62
+ "keep": "유지",
63
+ "change": "변경"
64
+ },
65
+ "echo_variants": {
66
+ "kept": "brief: {brief_path} (유지)"
67
+ }
68
+ },
69
+ "brief_path": {
70
+ "label": "task brief markdown 의 경로를 알려주세요 (project root 기준 상대경로 또는 절대경로)",
71
+ "echo_template": "brief: {value}"
72
+ },
73
+ "base_ref_pick": {
74
+ "label": "이 task worktree 의 base branch?",
75
+ "echo_template": "base-ref: {value}",
76
+ "options": {
77
+ "_RECOMMENDED_SUFFIX": " (recommended)",
78
+ "__free_input__": "직접 입력"
79
+ }
80
+ },
81
+ "base_ref_text": {
82
+ "label": "base ref 를 입력해주세요 (branch, tag, 또는 short/full SHA)",
83
+ "echo_template": "base-ref: {value}"
84
+ },
85
+ "approved_plan_pick": {
86
+ "label": "approved final-report 경로 (기본: {default})",
87
+ "echo_template": "approved-plan(pick): {value}",
88
+ "options": {
89
+ "__use_default__": "기본 경로 사용: {default}",
90
+ "__other__": "다른 경로 입력"
91
+ },
92
+ "errors": {
93
+ "default_not_found": "기본 approved-plan 경로를 찾을 수 없습니다. '다른 경로 입력'을 선택하세요."
94
+ }
95
+ },
96
+ "approved_plan": {
97
+ "label": "approved final-report.md 의 경로를 알려주세요 (APPROVED 마커 필수)",
98
+ "echo_template": "approved-plan: {value}"
99
+ },
100
+ "stage_pick": {
101
+ "label": "실행할 stage 를 선택하세요. auto 는 의존성이 만족된 가장 빠른 미완료 stage 를 자동으로 잡습니다.",
102
+ "echo_template": "stage: {value}",
103
+ "options": {
104
+ "auto": "auto (다음 미완료 stage)"
105
+ }
106
+ },
107
+ "directive_pick": {
108
+ "label": "추가 directive 가 있나요?",
109
+ "echo_template": "directive(pick): {value}",
110
+ "options": {
111
+ "__skip__": "없음 (건너뛰기)",
112
+ "__enter__": "있음 (입력)"
113
+ }
114
+ },
115
+ "related_tasks_pick": {
116
+ "label": "관련 task id 목록이 있나요?",
117
+ "echo_template": "related-tasks(pick): {value}",
118
+ "options": {
119
+ "__skip__": "없음 (건너뛰기)",
120
+ "__enter__": "있음 (입력)"
121
+ }
122
+ },
123
+ "clarification_pick": {
124
+ "label": "clarification-response 파일 경로가 있나요? (follow-up 시에만)",
125
+ "echo_template": "clarification(pick): {value}",
126
+ "options": {
127
+ "__skip__": "없음 (건너뛰기)",
128
+ "__enter__": "있음 (입력)"
129
+ }
130
+ },
131
+ "pr_template_pick": {
132
+ "label": "PR 본문 템플릿 경로를 직접 지정할까요?",
133
+ "echo_template": "pr-template(pick): {value}",
134
+ "options": {
135
+ "__skip__": "자동 해석 (project.json → config → 기본)",
136
+ "__enter__": "직접 경로 입력 (1회성 override)"
137
+ }
138
+ },
139
+ "executor": {
140
+ "label": "실행자 (executor)?",
141
+ "echo_template": "executor: {value}",
142
+ "options": {
143
+ "_DEFAULT_SUFFIX": " (default)"
144
+ }
145
+ },
146
+ "defaults_or_custom": {
147
+ "label": "기본 워커/모델로 진행할까요, 아니면 커스터마이즈할까요?",
148
+ "echo_template": "customize: {value}",
149
+ "options": {
150
+ "defaults": "Use defaults",
151
+ "customize": "Customize"
152
+ }
153
+ },
154
+ "workers_override": {
155
+ "label": "참여시킬 분석 워커를 선택해주세요 (최소 1개). report-writer 는 항상 포함됩니다.",
156
+ "echo_template": "workers: {value}",
157
+ "options": {
158
+ "_OPTIONAL_SUFFIX": " (옵션)"
159
+ },
160
+ "errors": {
161
+ "min_one_required": "워커를 최소 1개 선택해주세요"
162
+ }
163
+ },
164
+ "lead_model": {
165
+ "label": "리더(Claude lead) 모델?",
166
+ "echo_template": "lead-model: {value}"
167
+ },
168
+ "executor_model": {
169
+ "label": "실행자({executor}) 모델?",
170
+ "echo_template": "{executor}-model: {value}"
171
+ },
172
+ "claude_model": {
173
+ "label": "claude 워커 모델?",
174
+ "echo_template": "claude-model: {value}"
175
+ },
176
+ "codex_model": {
177
+ "label": "codex 워커 모델?",
178
+ "echo_template": "codex-model: {value}"
179
+ },
180
+ "gemini_model": {
181
+ "label": "gemini 워커 모델?",
182
+ "echo_template": "gemini-model: {value}"
183
+ },
184
+ "report_writer_model": {
185
+ "label": "리포트 작성자(report-writer) 모델?",
186
+ "echo_template": "report-writer-model: {value}"
187
+ },
188
+ "directive": {
189
+ "label": "추가 directive 가 있으면 적어주세요 (없으면 빈 줄)",
190
+ "echo_template": "directive: {value}"
191
+ },
192
+ "related_tasks": {
193
+ "label": "관련 task id 목록을 쉼표로 구분해서 적어주세요 (없으면 빈 줄)",
194
+ "echo_template": "related-tasks: {value}"
195
+ },
196
+ "clarification": {
197
+ "label": "clarification-response 파일 경로 (follow-up 시에만, 없으면 빈 줄)",
198
+ "echo_template": "clarification: {value}"
199
+ },
200
+ "pr_template": {
201
+ "label": "PR 본문 템플릿 경로 1회성 override (빈 줄이면 project.json → ~/.okstra/config.json → 스킬 디폴트 순으로 자동 해석)",
202
+ "echo_template": "pr-template: {value}"
203
+ },
204
+ "pr_template_scope": {
205
+ "label": "방금 입력한 경로를 영구 저장할까요?",
206
+ "echo_template": "pr-template-scope: {value}",
207
+ "options": {
208
+ "once": "이번 run 만 (1회성)",
209
+ "project": "프로젝트에 저장 (project scope)",
210
+ "global": "전역에 저장 (global scope)"
211
+ }
212
+ },
213
+ "confirm": {
214
+ "label": "이대로 진행할까요?",
215
+ "echo_template": "confirm: {value}",
216
+ "options": {
217
+ "proceed": "Proceed",
218
+ "edit": "Edit"
219
+ }
220
+ },
221
+ "edit_target": {
222
+ "label": "어느 step 으로 돌아갈까요?",
223
+ "echo_template": "edit-target: {value}"
224
+ }
225
+ },
226
+ "confirmation": {
227
+ "header": "선택 확인:",
228
+ "workers_implementation_default": " workers : (프로필 기본 — executor + verifier 2 + report-writer)"
229
+ }
230
+ }
@@ -23,57 +23,10 @@ collect_required_arguments() {
23
23
  prompt_for_required_argument PROJECT_ID "Project ID"
24
24
  prompt_for_required_argument TASK_GROUP "Task Group"
25
25
  prompt_for_required_argument TASK_ID "Task ID"
26
- prompt_for_required_argument ANALYSIS_TYPE "Task Type"
26
+ prompt_for_required_argument TASK_TYPE "Task Type"
27
27
  prompt_for_required_argument BRIEF_PATH "Task Brief Path"
28
28
  }
29
29
 
30
- confirm_execution_plan() {
31
- local confirmation=""
32
- local normalized_confirmation=""
33
- if [[ "$ASSUME_YES" == "true" ]]; then
34
- return 0
35
- fi
36
-
37
- if ! is_interactive_session; then
38
- return 0
39
- fi
40
-
41
- cat >&2 <<CONFIRM_EOF
42
- okstra execution summary:
43
- render only: ${RENDER_ONLY}
44
- task type: ${ANALYSIS_TYPE}
45
- project id: ${PROJECT_ID}
46
- task group: ${TASK_GROUP}
47
- task id: ${TASK_ID}
48
- task brief path: ${BRIEF_FILE_PATH}
49
- clarification response: ${CLARIFICATION_RESPONSE_FILE:-None}
50
- related tasks: ${RELATED_TASKS_INLINE}
51
- recommended workers: ${SELECTED_REVIEWERS}
52
- lead model: ${LEAD_MODEL_DISPLAY}
53
- worker models: claude=${CLAUDE_WORKER_MODEL_DISPLAY}, codex=${CODEX_WORKER_MODEL_DISPLAY}, gemini=${GEMINI_WORKER_MODEL_DISPLAY}, report-writer=${REPORT_WRITER_MODEL_DISPLAY}
54
- executor (implementation only): ${EXECUTOR_OVERRIDE:-default(claude)}
55
- task key input: ${TASK_KEY_INPUT:-None}
56
- task key: ${TASK_KEY}
57
- task root: ${TASK_ROOT}
58
- run dir: ${RUN_DIR}
59
- final report path: ${FINAL_REPORT_FILE}
60
- final report template: ${FINAL_REPORT_TEMPLATE_FILE}
61
- CONFIRM_EOF
62
-
63
- printf 'Continue? [y/yes]: ' >&2
64
- if ! IFS= read -r confirmation; then
65
- printf 'confirmation cancelled\n' >&2
66
- exit 1
67
- fi
68
-
69
- normalized_confirmation="$(trim_whitespace "$confirmation")"
70
- normalized_confirmation="$(printf '%s' "$normalized_confirmation" | tr '[:upper:]' '[:lower:]')"
71
-
72
- if [[ "$normalized_confirmation" != "y" && "$normalized_confirmation" != "yes" ]]; then
73
- printf 'okstra cancelled before execution\n' >&2
74
- exit 1
75
- fi
76
- }
77
30
  require_option_value() {
78
31
  local option_name="$1"
79
32
  local option_value="${2-}"
@@ -143,7 +96,7 @@ while [[ $# -gt 0 ]]; do
143
96
  shift 2
144
97
  ;;
145
98
  --task-type)
146
- ANALYSIS_TYPE="$(require_option_value --task-type "${2-}")"
99
+ TASK_TYPE="$(require_option_value --task-type "${2-}")"
147
100
  shift 2
148
101
  ;;
149
102
  --project-id)
@@ -4,7 +4,7 @@ PROFILE_DIR="$WORKSPACE_ROOT/prompts/profiles"
4
4
  PROMPT_TEMPLATE="$WORKSPACE_ROOT/prompts/launch.template.md"
5
5
  TASK_INDEX_TEMPLATE="$WORKSPACE_ROOT/templates/project-docs/task-index.template.md"
6
6
  FINAL_REPORT_TEMPLATE_SOURCE="$WORKSPACE_ROOT/templates/reports/final-report.template.md"
7
- RUN_VALIDATOR_SCRIPT="$WORKSPACE_ROOT/validators/validate-run.py"
7
+ RUN_VALIDATOR_PATH="$WORKSPACE_ROOT/validators/validate-run.py"
8
8
  SOURCE_CLAUDE_OKSTRA_SKILL="$WORKSPACE_ROOT/agents/SKILL.md"
9
9
  OKSTRA_ROOT=""
10
10
  OKSTRA_TASKS_ROOT=""
@@ -27,14 +27,14 @@ EXECUTOR_OVERRIDE=""
27
27
  WORK_CATEGORY=""
28
28
  BASE_REF=""
29
29
  RELATED_TASKS_RAW=""
30
- ANALYSIS_TYPE=""
30
+ TASK_TYPE=""
31
31
  BRIEF_PATH=""
32
32
  PROJECT_ID=""
33
33
  TASK_GROUP=""
34
34
  TASK_ID=""
35
35
  TASK_KEY_INPUT=""
36
36
  TASK_KEY=""
37
- REVIEW_PROFILE=""
37
+ ANALYSIS_PROFILE=""
38
38
  DIRECTIVE=""
39
39
  CLARIFICATION_RESPONSE_PATH=""
40
40
  APPROVED_PLAN_PATH=""
@@ -42,7 +42,7 @@ APPROVE_PLAN_ACK="false"
42
42
  # Phase 6 plan-body verification toggle. Default "true" (round runs).
43
43
  # Flipped to "false" by --no-plan-verification on the CLI.
44
44
  PLAN_VERIFICATION_ENABLED="true"
45
- CLARIFICATION_RESPONSE_FILE=""
45
+ CLARIFICATION_RESPONSE_PATH=""
46
46
  CLARIFICATION_RESPONSE_RELATIVE_PATH=""
47
47
  PROJECT_ROOT=""
48
48
  PROJECT_ROOT_OVERRIDE=""
@@ -50,17 +50,17 @@ PROFILE_FILE=""
50
50
  BRIEF_FILE_PATH=""
51
51
  REVIEW_MATERIAL=""
52
52
  PROFILE_CONTENT=""
53
- SELECTED_REVIEWERS=""
53
+ RECOMMENDED_ANALYSERS=""
54
54
  BRIEF_RELATIVE_PATH=""
55
55
  TASK_GROUP_SEGMENT=""
56
56
  TASK_ID_SEGMENT=""
57
57
  TASK_ROOT=""
58
- TASK_MANIFEST_FILE=""
59
- TASK_INDEX_FILE=""
60
- INSTRUCTION_SET_DIR=""
58
+ TASK_MANIFEST_PATH=""
59
+ TASK_INDEX_PATH=""
60
+ INSTRUCTION_SET_PATH=""
61
61
  RUNS_DIR=""
62
62
  HISTORY_DIR=""
63
- TIMELINE_FILE=""
63
+ TIMELINE_PATH=""
64
64
  RUN_DIR=""
65
65
  RUN_MANIFESTS_DIR=""
66
66
  RUN_STATE_DIR=""
@@ -69,7 +69,7 @@ RUN_REPORTS_DIR=""
69
69
  RUN_STATUS_DIR=""
70
70
  RUN_SESSIONS_DIR=""
71
71
  RUN_LOGS_DIR=""
72
- RUN_MANIFEST_FILE=""
72
+ RUN_MANIFEST_PATH=""
73
73
  RUN_MANIFEST_RELATIVE_PATH=""
74
74
  RUN_PROMPT_SNAPSHOT_FILE=""
75
75
  RUN_PROMPT_SNAPSHOT_RELATIVE_PATH=""
@@ -81,17 +81,17 @@ GEMINI_WORKER_PROMPT_FILE=""
81
81
  GEMINI_WORKER_PROMPT_RELATIVE_PATH=""
82
82
  REPORT_WRITER_WORKER_PROMPT_FILE=""
83
83
  REPORT_WRITER_WORKER_PROMPT_RELATIVE_PATH=""
84
- FINAL_REPORT_FILE=""
85
- FINAL_STATUS_FILE=""
84
+ FINAL_REPORT_PATH=""
85
+ FINAL_STATUS_PATH=""
86
86
  FINAL_REPORT_RELATIVE_PATH=""
87
87
  FINAL_STATUS_RELATIVE_PATH=""
88
- FINAL_REPORT_TEMPLATE_FILE=""
88
+ FINAL_REPORT_TEMPLATE_PATH=""
89
89
  FINAL_REPORT_TEMPLATE_RELATIVE_PATH=""
90
90
  REFERENCE_EXPECTATIONS_FILE=""
91
91
  REFERENCE_EXPECTATIONS_RELATIVE_PATH=""
92
- TEAM_STATE_FILE=""
92
+ TEAM_STATE_PATH=""
93
93
  TEAM_STATE_RELATIVE_PATH=""
94
- WORKER_RESULTS_DIR=""
94
+ WORKER_RESULTS_PATH=""
95
95
  WORKER_RESULTS_RELATIVE_PATH=""
96
96
  RUN_VALIDATOR_RELATIVE_PATH=""
97
97
  CLAUDE_WORKER_RESULT_FILE=""
@@ -103,7 +103,7 @@ GEMINI_WORKER_RESULT_RELATIVE_PATH=""
103
103
  REPORT_WRITER_WORKER_RESULT_FILE=""
104
104
  REPORT_WRITER_WORKER_RESULT_RELATIVE_PATH=""
105
105
  CLAUDE_SESSION_ID=""
106
- CLAUDE_RESUME_COMMAND_FILE=""
106
+ CLAUDE_RESUME_COMMAND_PATH=""
107
107
  CLAUDE_RESUME_COMMAND_RELATIVE_PATH=""
108
108
  RUN_TIMESTAMP_ISO=""
109
109
  RUN_DATETIME_SEGMENT=""
@@ -143,15 +143,15 @@ RELATED_TASKS_JSON="[]"
143
143
  RELATED_TASKS_BULLETS="- None recorded"
144
144
  RELATED_TASKS_INLINE="None"
145
145
  PROMPT=""
146
- LEAD_MODEL_DISPLAY=""
146
+ LEAD_MODEL=""
147
147
  LEAD_MODEL_EXECUTION_VALUE=""
148
- CLAUDE_WORKER_MODEL_DISPLAY=""
148
+ CLAUDE_WORKER_MODEL=""
149
149
  CLAUDE_WORKER_MODEL_EXECUTION_VALUE=""
150
- CODEX_WORKER_MODEL_DISPLAY=""
150
+ CODEX_WORKER_MODEL=""
151
151
  CODEX_WORKER_MODEL_EXECUTION_VALUE=""
152
- GEMINI_WORKER_MODEL_DISPLAY=""
152
+ GEMINI_WORKER_MODEL=""
153
153
  GEMINI_WORKER_MODEL_EXECUTION_VALUE=""
154
- REPORT_WRITER_MODEL_DISPLAY=""
154
+ REPORT_WRITER_MODEL=""
155
155
  REPORT_WRITER_MODEL_EXECUTION_VALUE=""
156
156
  DEFAULT_WORKERS="claude,codex,report-writer"
157
157
  DEFAULT_LEAD_MODEL_NAME="${OKSTRA_DEFAULT_LEAD_MODEL:-opus-4-6}"
@@ -152,7 +152,7 @@ autofill_from_manifest() {
152
152
  if [[ -z "$PROJECT_ID" ]]; then
153
153
  return 0
154
154
  fi
155
- if [[ -n "$BRIEF_PATH" && -n "$ANALYSIS_TYPE" ]]; then
155
+ if [[ -n "$BRIEF_PATH" && -n "$TASK_TYPE" ]]; then
156
156
  return 0
157
157
  fi
158
158
  if [[ -z "$TASK_GROUP" || -z "$TASK_ID" ]]; then
@@ -176,7 +176,7 @@ autofill_from_manifest() {
176
176
 
177
177
  local need_brief_val need_type_val
178
178
  need_brief_val="$([[ -z "$BRIEF_PATH" ]] && printf '1' || printf '0')"
179
- need_type_val="$([[ -z "$ANALYSIS_TYPE" ]] && printf '1' || printf '0')"
179
+ need_type_val="$([[ -z "$TASK_TYPE" ]] && printf '1' || printf '0')"
180
180
  local autofill_output=""
181
181
  autofill_output="$(python3 - "$manifest_path" "$need_brief_val" "$need_type_val" <<'PY'
182
182
  import json, sys
@@ -217,9 +217,9 @@ PY
217
217
  BRIEF_PATH="$manifest_brief"
218
218
  printf 'autofill: brief-path from task-manifest.json: %s\n' "$BRIEF_PATH" >&2
219
219
  fi
220
- if [[ -z "$ANALYSIS_TYPE" && -n "$manifest_type" ]]; then
221
- ANALYSIS_TYPE="$manifest_type"
222
- printf 'autofill: task-type from manifest workflow.nextRecommendedPhase: %s\n' "$ANALYSIS_TYPE" >&2
220
+ if [[ -z "$TASK_TYPE" && -n "$manifest_type" ]]; then
221
+ TASK_TYPE="$manifest_type"
222
+ printf 'autofill: task-type from manifest workflow.nextRecommendedPhase: %s\n' "$TASK_TYPE" >&2
223
223
  fi
224
224
  }
225
225
 
@@ -229,7 +229,7 @@ missing_required_arguments_summary() {
229
229
  [[ -z "$PROJECT_ID" ]] && missing+=("<project-id>")
230
230
  [[ -z "$TASK_GROUP" ]] && missing+=("<task-group>")
231
231
  [[ -z "$TASK_ID" ]] && missing+=("<task-id>")
232
- [[ -z "$ANALYSIS_TYPE" ]] && missing+=("<task-type>")
232
+ [[ -z "$TASK_TYPE" ]] && missing+=("<task-type>")
233
233
  [[ -z "$BRIEF_PATH" ]] && missing+=("<brief-path>")
234
234
 
235
235
  if (( ${#missing[@]} == 0 )); then
@@ -349,7 +349,7 @@ run_resume_clarification() {
349
349
 
350
350
  local lookup_result=""
351
351
  if ! lookup_result="$(find_latest_final_report \
352
- "$resume_project_root" "$TASK_GROUP" "$TASK_ID" "${ANALYSIS_TYPE-}")"; then
352
+ "$resume_project_root" "$TASK_GROUP" "$TASK_ID" "${TASK_TYPE-}")"; then
353
353
  exit 1
354
354
  fi
355
355
  local report_path="${lookup_result%$'\t'*}"
@@ -5,8 +5,8 @@ single section) is the canonical home for every clarification an
5
5
  implementation-planning run owes the user — decisions, file attachments,
6
6
  single data points. Each row carries a ``Blocks`` column whose value picks
7
7
  one of ``{approval, next-phase, none}``. Rows with ``Blocks=approval`` are
8
- the approval gate: they MUST resolve before the user marks the report
9
- ``Approved`` and starts the next ``implementation`` run.
8
+ the approval gate: they MUST resolve before the user flips the frontmatter
9
+ ``approved`` field to ``true`` and starts the next ``implementation`` run.
10
10
 
11
11
  This module exposes one read function for that gate so both
12
12
  ``_validate_approved_plan`` (pre-implementation run-prep) and any later
@@ -168,7 +168,7 @@ UNRESOLVED_STATUSES = {"open", "answered"}
168
168
 
169
169
 
170
170
  def unresolved_approval_blockers(report_text: str) -> Optional[list[ClarificationItem]]:
171
- """Return rows that gate the User Approval Request — ``Blocks=approval``
171
+ """Return rows that gate the frontmatter ``approved`` flag — ``Blocks=approval``
172
172
  AND ``Status`` in ``{open, answered}``.
173
173
 
174
174
  ``None`` propagates the "schema absent" signal from
@@ -182,9 +182,3 @@ def unresolved_approval_blockers(report_text: str) -> Optional[list[Clarificatio
182
182
  it for it in items
183
183
  if it.blocks == "approval" and it.status in UNRESOLVED_STATUSES
184
184
  ]
185
-
186
-
187
- def unresolved_approval_blockers_in_file(path: Path) -> Optional[list[ClarificationItem]]:
188
- return unresolved_approval_blockers(
189
- Path(path).read_text(encoding="utf-8", errors="replace")
190
- )
@@ -0,0 +1,53 @@
1
+ """Append-only writer / reader for `consumers.jsonl` under a plan run's task root.
2
+
3
+ A row's identity for idempotency is the tuple
4
+ (impl_task_key, stage, status)
5
+ so the same (started / done) record is never duplicated."""
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from pathlib import Path
11
+ from typing import Any, Dict, List
12
+
13
+ from .run_context import consumers_mutex
14
+
15
+ CONSUMERS_FILENAME = "consumers.jsonl"
16
+
17
+
18
+ def _path(plan_run_root: Path) -> Path:
19
+ return plan_run_root / CONSUMERS_FILENAME
20
+
21
+
22
+ def read_consumers(plan_run_root: Path) -> List[Dict[str, Any]]:
23
+ p = _path(plan_run_root)
24
+ if not p.exists():
25
+ return []
26
+ out = []
27
+ for line in p.read_text(encoding="utf-8").splitlines():
28
+ line = line.strip()
29
+ if not line:
30
+ continue
31
+ out.append(json.loads(line))
32
+ return out
33
+
34
+
35
+ def append_consumer(plan_run_root: Path, *, impl_task_key: str, stage: int,
36
+ status: str, **fields: Any) -> None:
37
+ if status not in ("started", "done"):
38
+ raise ValueError(f"status must be 'started' or 'done', got: {status!r}")
39
+ with consumers_mutex(plan_run_root.name):
40
+ existing = read_consumers(plan_run_root)
41
+ for row in existing:
42
+ if (row.get("impl_task_key") == impl_task_key
43
+ and row.get("stage") == stage
44
+ and row.get("status") == status):
45
+ return # idempotent
46
+ record: Dict[str, Any] = {
47
+ "impl_task_key": impl_task_key,
48
+ "stage": stage,
49
+ "status": status,
50
+ **fields,
51
+ }
52
+ with _path(plan_run_root).open("a", encoding="utf-8") as f:
53
+ f.write(json.dumps(record, ensure_ascii=False) + "\n")
@@ -244,10 +244,3 @@ def load_schema(schema_path: Path | None = None) -> dict:
244
244
  "could not locate schemas/final-report-v1.0.schema.json"
245
245
  )
246
246
  return json.loads(Path(schema_path).read_text(encoding="utf-8"))
247
-
248
-
249
- def validate_data_file(data_path: Path, schema_path: Path | None = None) -> list[str]:
250
- """Convenience wrapper: read data.json, return validation errors."""
251
- schema = load_schema(schema_path)
252
- data = json.loads(Path(data_path).read_text(encoding="utf-8"))
253
- return validate(data, schema)
@@ -0,0 +1,73 @@
1
+ """Final-report i18n dictionary loader + Jinja2 lookup function.
2
+
3
+ 사전은 ``templates/reports/i18n/<lang>.json`` 에 둔다. ChainableUndefined
4
+ 환경에서도 누락 키가 silent 로 빈 문자열이 되지 않도록 lookup 함수가
5
+ 직접 raise 한다.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import os
11
+ from pathlib import Path
12
+ from typing import Any, Callable
13
+
14
+ SUPPORTED_LANGS = ("en", "ko")
15
+ DICTIONARY_REL = ("templates", "reports", "i18n")
16
+
17
+
18
+ class I18nError(RuntimeError):
19
+ """사전 lookup 실패 또는 사전 로드 실패."""
20
+
21
+
22
+ def _i18n_dir() -> Path:
23
+ okstra_home = os.environ.get("OKSTRA_HOME")
24
+ if okstra_home:
25
+ candidate = Path(okstra_home).joinpath(*DICTIONARY_REL)
26
+ if candidate.is_dir():
27
+ return candidate
28
+ here = Path(__file__).resolve()
29
+ for parent in [here, *here.parents]:
30
+ candidate = parent.joinpath(*DICTIONARY_REL)
31
+ if candidate.is_dir():
32
+ return candidate
33
+ raise I18nError(
34
+ "could not locate templates/reports/i18n/. Set OKSTRA_HOME or "
35
+ "run from a checkout that contains templates/reports/i18n/."
36
+ )
37
+
38
+
39
+ def load_dictionary(lang: str) -> dict[str, Any]:
40
+ if lang not in SUPPORTED_LANGS:
41
+ raise I18nError(
42
+ f"unsupported reportLanguage {lang!r}; supported: {SUPPORTED_LANGS}"
43
+ )
44
+ path = _i18n_dir() / f"{lang}.json"
45
+ try:
46
+ return json.loads(path.read_text(encoding="utf-8"))
47
+ except (OSError, json.JSONDecodeError) as exc:
48
+ raise I18nError(f"failed to load {path}: {exc}") from exc
49
+
50
+
51
+ def lookup(dictionary: dict[str, Any], dotted_key: str) -> str:
52
+ parts = dotted_key.split(".")
53
+ cur: Any = dictionary
54
+ for i, part in enumerate(parts):
55
+ if not isinstance(cur, dict):
56
+ raise I18nError(
57
+ f"i18n key {dotted_key!r}: segment {'.'.join(parts[:i]) or '<root>'!r} "
58
+ f"is not a dict (got {type(cur).__name__})"
59
+ )
60
+ if part not in cur:
61
+ raise I18nError(f"i18n key {dotted_key!r} not found in dictionary")
62
+ cur = cur[part]
63
+ if not isinstance(cur, str):
64
+ raise I18nError(
65
+ f"i18n key {dotted_key!r} resolved to {type(cur).__name__}, expected str"
66
+ )
67
+ return cur
68
+
69
+
70
+ def make_jinja_global(dictionary: dict[str, Any]) -> Callable[[str], str]:
71
+ def t(dotted_key: str) -> str:
72
+ return lookup(dictionary, dotted_key)
73
+ return t
@@ -0,0 +1,44 @@
1
+ """Improvement-discovery phase SSOT: lens enum and cap constants.
2
+
3
+ profile / brief skill / validator / wizard MUST import from this module.
4
+ Re-defining the enum anywhere else violates the single-reference-point rule
5
+ and is rejected by tests/test_okstra_improvement_lenses.py.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ LENSES: tuple[str, ...] = (
10
+ "performance",
11
+ "security",
12
+ "readability",
13
+ "architecture",
14
+ "test-coverage",
15
+ "dx",
16
+ "observability",
17
+ "accessibility",
18
+ )
19
+
20
+ DEFAULT_CANDIDATE_CAP = 8
21
+ ABSOLUTE_CANDIDATE_CAP = 12
22
+ MIN_PRIORITY_LENSES = 1
23
+ MAX_PRIORITY_LENSES = 4
24
+
25
+ # report-writer is the AUTHOR of the final report; it never produces source
26
+ # findings. validators reject `report-writer:<id>` entries in Source workers.
27
+ SOURCE_WORKERS: tuple[str, ...] = ("claude", "codex", "gemini")
28
+
29
+
30
+ def is_valid_lens(value: str) -> bool:
31
+ return value in LENSES
32
+
33
+
34
+ def is_valid_lens_subset(values: list[str]) -> bool:
35
+ if not (MIN_PRIORITY_LENSES <= len(values) <= MAX_PRIORITY_LENSES):
36
+ return False
37
+ return all(v in LENSES for v in values)
38
+
39
+
40
+ def is_within_candidate_cap(count: int, *, brief_cap: int | None) -> bool:
41
+ cap = brief_cap if brief_cap is not None else DEFAULT_CANDIDATE_CAP
42
+ if cap < 1 or cap > ABSOLUTE_CANDIDATE_CAP:
43
+ return False
44
+ return 1 <= count <= cap