okstra 0.34.1 → 0.36.1
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 +27 -19
- package/README.md +27 -19
- 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 +353 -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/plans/2026-05-24-implementation-lead-context-slimming.md +1700 -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 +30 -7
- package/runtime/agents/workers/claude-worker.md +31 -6
- package/runtime/agents/workers/codex-worker.md +37 -10
- package/runtime/agents/workers/gemini-worker.md +34 -7
- package/runtime/agents/workers/report-writer-worker.md +19 -10
- 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/launch.template.md +1 -0
- package/runtime/prompts/profiles/_common-contract.md +11 -6
- package/runtime/prompts/profiles/_implementation-deliverable.md +53 -0
- package/runtime/prompts/profiles/_implementation-executor.md +60 -0
- package/runtime/prompts/profiles/_implementation-verifier.md +76 -0
- 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 -118
- package/runtime/prompts/profiles/improvement-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 +26 -20
- package/runtime/python/okstra_ctl/render.py +166 -207
- package/runtime/python/okstra_ctl/render_final_report.py +53 -10
- package/runtime/python/okstra_ctl/run.py +299 -108
- package/runtime/python/okstra_ctl/run_context.py +22 -0
- package/runtime/python/okstra_ctl/seeding.py +186 -0
- package/runtime/python/okstra_ctl/session.py +65 -7
- 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 +102 -218
- package/runtime/skills/okstra-convergence/SKILL.md +2 -3
- package/runtime/skills/okstra-inspect/SKILL.md +581 -0
- package/runtime/skills/okstra-report-writer/SKILL.md +35 -15
- package/runtime/skills/okstra-run/SKILL.md +8 -7
- package/runtime/skills/okstra-schedule/SKILL.md +14 -157
- package/runtime/skills/okstra-setup/SKILL.md +28 -1
- package/runtime/skills/okstra-team-contract/SKILL.md +16 -107
- package/runtime/templates/okstra.CLAUDE.md +104 -0
- package/runtime/templates/reports/brief.template.md +204 -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/schedule.template.md +12 -3
- package/runtime/templates/reports/task-brief.template.md +2 -2
- package/runtime/templates/worker-prompt-preamble.md +108 -0
- 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 +27 -3
- package/runtime/skills/okstra-history/SKILL.md +0 -165
- package/runtime/skills/okstra-logs/SKILL.md +0 -173
- package/runtime/skills/okstra-report-finder/SKILL.md +0 -111
- package/runtime/skills/okstra-status/SKILL.md +0 -246
- package/runtime/skills/okstra-time-summary/SKILL.md +0 -172
|
@@ -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
|
|
@@ -87,7 +87,7 @@ def record_start(home: Path, *, project_id: str, project_root: str,
|
|
|
87
87
|
"leadModel": lead_model,
|
|
88
88
|
"validation": "not-run",
|
|
89
89
|
"finalReportRel": final_report_rel,
|
|
90
|
-
#
|
|
90
|
+
# FINAL_STATUS_PATH 의 카운터(RUN_STATUS_SEQ) 는 RUN_MANIFESTS_SEQ
|
|
91
91
|
# 와 별개로 advance 한다(render-only/실패 prep 은 manifest 만 증가).
|
|
92
92
|
# 따라서 status 파일을 추적할 때는 manifest seq 를 추정해 glob 으로
|
|
93
93
|
# 찾지 말고 run 시작 시점의 정식 경로를 row 에 박아 두고 사용한다.
|
|
@@ -4,8 +4,8 @@ Inputs: project root, identity (project-id/task-group/task-id), task-type,
|
|
|
4
4
|
workspace root, optional run-seq override.
|
|
5
5
|
|
|
6
6
|
Output: dict with every path value the bash render-context used to expose as
|
|
7
|
-
environment variables (TASK_ROOT, RUN_DIR, RUN_MANIFESTS_DIR,
|
|
8
|
-
|
|
7
|
+
environment variables (TASK_ROOT, RUN_DIR, RUN_MANIFESTS_DIR, RUN_MANIFEST_PATH,
|
|
8
|
+
FINAL_REPORT_PATH, ...). 호출자가 이 dict 를 그대로 인자로 사용하거나
|
|
9
9
|
`write_run_context()` 로 디스크에 박는다.
|
|
10
10
|
|
|
11
11
|
이 모듈은 read-only 한 path 계산만 담당한다. 디렉터리 생성, 파일 쓰기,
|
|
@@ -104,6 +104,7 @@ def compute_run_paths(
|
|
|
104
104
|
run_sessions = run_dir / "sessions"
|
|
105
105
|
run_logs = run_dir / "logs"
|
|
106
106
|
worker_results = run_dir / "worker-results"
|
|
107
|
+
run_carry = run_dir / "carry"
|
|
107
108
|
|
|
108
109
|
if run_seq_override is not None:
|
|
109
110
|
seq_int = int(run_seq_override)
|
|
@@ -158,20 +159,23 @@ def compute_run_paths(
|
|
|
158
159
|
"TASK_GROUP": task_group,
|
|
159
160
|
"TASK_ID": task_id,
|
|
160
161
|
"TASK_KEY": f"{project_id}:{task_group}:{task_id}",
|
|
161
|
-
"
|
|
162
|
+
"TASK_TYPE": task_type,
|
|
162
163
|
"TASK_GROUP_SEGMENT": task_group_segment,
|
|
163
164
|
"TASK_ID_SEGMENT": task_id_segment,
|
|
164
165
|
"TASK_TYPE_SEGMENT": task_type_segment,
|
|
165
166
|
"OKSTRA_ROOT": str(okstra_root),
|
|
166
167
|
"OKSTRA_TASKS_ROOT": str(tasks_root),
|
|
168
|
+
"WORKER_PROMPT_PREAMBLE_PATH": str(
|
|
169
|
+
Path.home() / ".okstra" / "templates" / "worker-prompt-preamble.md"
|
|
170
|
+
),
|
|
167
171
|
"OKSTRA_DISCOVERY_DIR": str(discovery_dir),
|
|
168
172
|
"TASK_ROOT": str(task_root),
|
|
169
|
-
"
|
|
170
|
-
"
|
|
171
|
-
"
|
|
173
|
+
"TASK_MANIFEST_PATH": str(task_manifest),
|
|
174
|
+
"TASK_INDEX_PATH": str(task_index),
|
|
175
|
+
"INSTRUCTION_SET_PATH": str(instruction_set),
|
|
172
176
|
"RUNS_DIR": str(runs_dir),
|
|
173
177
|
"HISTORY_DIR": str(history_dir),
|
|
174
|
-
"
|
|
178
|
+
"TIMELINE_PATH": str(timeline_file),
|
|
175
179
|
"RUN_DIR": str(run_dir),
|
|
176
180
|
"RUN_MANIFESTS_DIR": str(run_manifests),
|
|
177
181
|
"RUN_STATE_DIR": str(run_state),
|
|
@@ -180,31 +184,32 @@ def compute_run_paths(
|
|
|
180
184
|
"RUN_STATUS_DIR": str(run_status),
|
|
181
185
|
"RUN_SESSIONS_DIR": str(run_sessions),
|
|
182
186
|
"RUN_LOGS_DIR": str(run_logs),
|
|
183
|
-
"
|
|
184
|
-
"
|
|
187
|
+
"WORKER_RESULTS_PATH": str(worker_results),
|
|
188
|
+
"RUN_CARRY_PATH": str(run_carry),
|
|
189
|
+
"RUN_MANIFEST_PATH": str(run_manifest_file),
|
|
185
190
|
"RUN_PROMPT_SNAPSHOT_FILE": str(run_prompt_snapshot),
|
|
186
191
|
"CLAUDE_WORKER_PROMPT_FILE": str(claude_worker_prompt),
|
|
187
192
|
"CODEX_WORKER_PROMPT_FILE": str(codex_worker_prompt),
|
|
188
193
|
"GEMINI_WORKER_PROMPT_FILE": str(gemini_worker_prompt),
|
|
189
194
|
"REPORT_WRITER_WORKER_PROMPT_FILE": str(report_writer_worker_prompt),
|
|
190
|
-
"
|
|
191
|
-
"
|
|
192
|
-
"
|
|
193
|
-
"
|
|
195
|
+
"FINAL_REPORT_PATH": str(final_report),
|
|
196
|
+
"FINAL_STATUS_PATH": str(final_status),
|
|
197
|
+
"TEAM_STATE_PATH": str(team_state),
|
|
198
|
+
"FINAL_REPORT_TEMPLATE_PATH": str(final_report_template),
|
|
194
199
|
"REFERENCE_EXPECTATIONS_FILE": str(reference_expectations),
|
|
195
|
-
"
|
|
200
|
+
"CLAUDE_RESUME_COMMAND_PATH": str(claude_resume_command),
|
|
196
201
|
"OKSTRA_LATEST_TASK_FILE": str(latest_task_file),
|
|
197
202
|
"OKSTRA_TASK_CATALOG_FILE": str(task_catalog_file),
|
|
198
203
|
"CLAUDE_WORKER_RESULT_FILE": str(claude_worker_result),
|
|
199
204
|
"CODEX_WORKER_RESULT_FILE": str(codex_worker_result),
|
|
200
205
|
"GEMINI_WORKER_RESULT_FILE": str(gemini_worker_result),
|
|
201
206
|
"REPORT_WRITER_WORKER_RESULT_FILE": str(report_writer_worker_result),
|
|
202
|
-
"
|
|
203
|
-
"
|
|
204
|
-
"
|
|
205
|
-
"
|
|
206
|
-
"
|
|
207
|
-
"
|
|
207
|
+
"RUN_ERRORS_LOG_PATH": str(run_errors_log),
|
|
208
|
+
"CLAUDE_WORKER_ERRORS_SIDECAR_PATH": str(claude_worker_errors_sidecar),
|
|
209
|
+
"CODEX_WORKER_ERRORS_SIDECAR_PATH": str(codex_worker_errors_sidecar),
|
|
210
|
+
"GEMINI_WORKER_ERRORS_SIDECAR_PATH": str(gemini_worker_errors_sidecar),
|
|
211
|
+
"REPORT_WRITER_WORKER_ERRORS_SIDECAR_PATH": str(report_writer_worker_errors_sidecar),
|
|
212
|
+
"RUN_VALIDATOR_PATH": str(run_validator_script),
|
|
208
213
|
"RUN_MANIFEST_FILENAME": run_manifest_file.name,
|
|
209
214
|
"RUN_PROMPT_SNAPSHOT_FILENAME": run_prompt_snapshot.name,
|
|
210
215
|
"FINAL_REPORT_FILENAME": final_report.name,
|
|
@@ -248,6 +253,7 @@ def compute_run_paths(
|
|
|
248
253
|
("FINAL_STATUS_RELATIVE_PATH", final_status),
|
|
249
254
|
("TEAM_STATE_RELATIVE_PATH", team_state),
|
|
250
255
|
("WORKER_RESULTS_RELATIVE_PATH", worker_results),
|
|
256
|
+
("RUN_CARRY_RELATIVE_PATH", run_carry),
|
|
251
257
|
("FINAL_REPORT_TEMPLATE_RELATIVE_PATH", final_report_template),
|
|
252
258
|
("REFERENCE_EXPECTATIONS_RELATIVE_PATH", reference_expectations),
|
|
253
259
|
("CLAUDE_RESUME_COMMAND_RELATIVE_PATH", claude_resume_command),
|