okstra 0.34.1 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.kr.md +26 -16
- package/README.md +26 -16
- package/docs/kr/architecture.md +59 -45
- package/docs/kr/cli.md +61 -18
- package/docs/pr-template-usage.md +65 -0
- package/docs/project-structure-overview.md +358 -354
- package/docs/superpowers/plans/2026-05-12-ticket-id-in-reports.md +1 -1
- package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +1 -1
- package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +1 -1
- package/docs/superpowers/plans/2026-05-20-final-report-language.md +1501 -0
- package/docs/superpowers/plans/2026-05-20-implementation-planning-multi-stage.md +1267 -0
- package/docs/superpowers/plans/2026-05-20-okstra-run-prompt-sot-b1.md +1007 -0
- package/docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md +720 -0
- package/docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md +681 -0
- package/docs/superpowers/plans/2026-05-21-improvement-discovery-task-type.md +1691 -0
- package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +383 -0
- package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +320 -0
- package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +299 -0
- package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +335 -0
- package/docs/task-process/README.md +74 -0
- package/docs/task-process/common-flow.md +166 -0
- package/docs/task-process/error-analysis.md +101 -0
- package/docs/task-process/final-verification.md +167 -0
- package/docs/task-process/implementation-planning.md +128 -0
- package/docs/task-process/implementation.md +149 -0
- package/docs/task-process/release-handoff.md +206 -0
- package/docs/task-process/requirements-discovery.md +115 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/SKILL.md +12 -2
- package/runtime/agents/workers/claude-worker.md +26 -0
- package/runtime/agents/workers/codex-worker.md +27 -1
- package/runtime/agents/workers/gemini-worker.md +27 -1
- package/runtime/agents/workers/report-writer-worker.md +8 -1
- package/runtime/bin/okstra-central.sh +6 -6
- package/runtime/bin/okstra-codex-exec.sh +49 -28
- package/runtime/bin/okstra-gemini-exec.sh +39 -21
- package/runtime/bin/okstra-render-final-report.py +13 -2
- package/runtime/bin/okstra-wrapper-status.py +155 -0
- package/runtime/bin/okstra.sh +2 -2
- package/runtime/prompts/profiles/_common-contract.md +11 -6
- package/runtime/prompts/profiles/error-analysis.md +3 -7
- package/runtime/prompts/profiles/implementation-planning.md +22 -21
- package/runtime/prompts/profiles/implementation.md +28 -11
- package/runtime/prompts/profiles/improvement-discovery.md +42 -0
- package/runtime/prompts/profiles/kr/_common-contract.md +92 -0
- package/runtime/prompts/profiles/kr/error-analysis.md +36 -0
- package/runtime/prompts/profiles/kr/final-verification.md +48 -0
- package/runtime/prompts/profiles/kr/implementation-planning.md +90 -0
- package/runtime/prompts/profiles/kr/implementation.md +144 -0
- package/runtime/prompts/profiles/kr/improvement-discovery.md +42 -0
- package/runtime/prompts/profiles/kr/release-handoff.md +104 -0
- package/runtime/prompts/profiles/kr/requirements-discovery.md +42 -0
- package/runtime/prompts/profiles/release-handoff.md +1 -1
- package/runtime/prompts/profiles/requirements-discovery.md +8 -12
- package/runtime/prompts/wizard/prompts.ko.json +230 -0
- package/runtime/python/lib/okstra/cli.sh +2 -49
- package/runtime/python/lib/okstra/globals.sh +21 -21
- package/runtime/python/lib/okstra/interactive.sh +7 -7
- package/runtime/python/okstra_ctl/clarification_items.py +3 -9
- package/runtime/python/okstra_ctl/consumers.py +53 -0
- package/runtime/python/okstra_ctl/final_report_schema.py +0 -7
- package/runtime/python/okstra_ctl/i18n.py +73 -0
- package/runtime/python/okstra_ctl/improvement_lenses.py +44 -0
- package/runtime/python/okstra_ctl/index.py +1 -1
- package/runtime/python/okstra_ctl/paths.py +23 -20
- package/runtime/python/okstra_ctl/render.py +147 -202
- package/runtime/python/okstra_ctl/render_final_report.py +53 -10
- package/runtime/python/okstra_ctl/run.py +292 -107
- package/runtime/python/okstra_ctl/run_context.py +22 -0
- package/runtime/python/okstra_ctl/seeding.py +186 -0
- package/runtime/python/okstra_ctl/wizard.py +348 -127
- package/runtime/python/okstra_ctl/workflow.py +21 -2
- package/runtime/python/okstra_ctl/worktree.py +54 -1
- package/runtime/python/okstra_project/resolver.py +4 -3
- package/runtime/python/okstra_token_usage/report.py +2 -2
- package/runtime/schemas/final-report-v1.0.schema.json +22 -16
- package/runtime/skills/okstra-brief/SKILL.md +124 -31
- package/runtime/skills/okstra-convergence/SKILL.md +2 -3
- package/runtime/skills/okstra-report-writer/SKILL.md +35 -15
- package/runtime/skills/okstra-run/SKILL.md +5 -4
- package/runtime/skills/okstra-schedule/SKILL.md +4 -4
- package/runtime/skills/okstra-setup/SKILL.md +27 -0
- package/runtime/skills/okstra-team-contract/SKILL.md +1 -1
- package/runtime/templates/okstra.CLAUDE.md +104 -0
- package/runtime/templates/reports/final-report.template.md +93 -98
- package/runtime/templates/reports/i18n/en.json +135 -0
- package/runtime/templates/reports/i18n/ko.json +135 -0
- package/runtime/templates/reports/implementation-planning-input.template.md +18 -0
- package/runtime/templates/reports/improvement-discovery-input.template.md +78 -0
- package/runtime/templates/reports/task-brief.template.md +2 -2
- package/runtime/validators/lib/fixtures.sh +30 -0
- package/runtime/validators/lib/runners.sh +1 -1
- package/runtime/validators/validate-implementation-plan-stages.py +211 -0
- package/runtime/validators/validate-run.py +121 -26
- package/runtime/validators/validate-workflow.sh +2 -2
- package/runtime/validators/validate_improvement_report.py +275 -0
- package/src/config.mjs +18 -0
- package/src/install.mjs +41 -14
- package/src/setup.mjs +133 -1
- package/src/uninstall.mjs +21 -1
|
@@ -0,0 +1,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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
53
|
+
RECOMMENDED_ANALYSERS=""
|
|
54
54
|
BRIEF_RELATIVE_PATH=""
|
|
55
55
|
TASK_GROUP_SEGMENT=""
|
|
56
56
|
TASK_ID_SEGMENT=""
|
|
57
57
|
TASK_ROOT=""
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
TASK_MANIFEST_PATH=""
|
|
59
|
+
TASK_INDEX_PATH=""
|
|
60
|
+
INSTRUCTION_SET_PATH=""
|
|
61
61
|
RUNS_DIR=""
|
|
62
62
|
HISTORY_DIR=""
|
|
63
|
-
|
|
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
|
-
|
|
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
|
-
|
|
85
|
-
|
|
84
|
+
FINAL_REPORT_PATH=""
|
|
85
|
+
FINAL_STATUS_PATH=""
|
|
86
86
|
FINAL_REPORT_RELATIVE_PATH=""
|
|
87
87
|
FINAL_STATUS_RELATIVE_PATH=""
|
|
88
|
-
|
|
88
|
+
FINAL_REPORT_TEMPLATE_PATH=""
|
|
89
89
|
FINAL_REPORT_TEMPLATE_RELATIVE_PATH=""
|
|
90
90
|
REFERENCE_EXPECTATIONS_FILE=""
|
|
91
91
|
REFERENCE_EXPECTATIONS_RELATIVE_PATH=""
|
|
92
|
-
|
|
92
|
+
TEAM_STATE_PATH=""
|
|
93
93
|
TEAM_STATE_RELATIVE_PATH=""
|
|
94
|
-
|
|
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
|
-
|
|
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
|
-
|
|
146
|
+
LEAD_MODEL=""
|
|
147
147
|
LEAD_MODEL_EXECUTION_VALUE=""
|
|
148
|
-
|
|
148
|
+
CLAUDE_WORKER_MODEL=""
|
|
149
149
|
CLAUDE_WORKER_MODEL_EXECUTION_VALUE=""
|
|
150
|
-
|
|
150
|
+
CODEX_WORKER_MODEL=""
|
|
151
151
|
CODEX_WORKER_MODEL_EXECUTION_VALUE=""
|
|
152
|
-
|
|
152
|
+
GEMINI_WORKER_MODEL=""
|
|
153
153
|
GEMINI_WORKER_MODEL_EXECUTION_VALUE=""
|
|
154
|
-
|
|
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 "$
|
|
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 "$
|
|
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 "$
|
|
221
|
-
|
|
222
|
-
printf 'autofill: task-type from manifest workflow.nextRecommendedPhase: %s\n' "$
|
|
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 "$
|
|
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" "${
|
|
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
|
|
9
|
-
``
|
|
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
|
|
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
|