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.
Files changed (108) hide show
  1. package/README.kr.md +27 -19
  2. package/README.md +27 -19
  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 +353 -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/plans/2026-05-24-implementation-lead-context-slimming.md +1700 -0
  17. package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +383 -0
  18. package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +320 -0
  19. package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +299 -0
  20. package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +335 -0
  21. package/docs/task-process/README.md +74 -0
  22. package/docs/task-process/common-flow.md +166 -0
  23. package/docs/task-process/error-analysis.md +101 -0
  24. package/docs/task-process/final-verification.md +167 -0
  25. package/docs/task-process/implementation-planning.md +128 -0
  26. package/docs/task-process/implementation.md +149 -0
  27. package/docs/task-process/release-handoff.md +206 -0
  28. package/docs/task-process/requirements-discovery.md +115 -0
  29. package/package.json +1 -1
  30. package/runtime/BUILD.json +2 -2
  31. package/runtime/agents/SKILL.md +30 -7
  32. package/runtime/agents/workers/claude-worker.md +31 -6
  33. package/runtime/agents/workers/codex-worker.md +37 -10
  34. package/runtime/agents/workers/gemini-worker.md +34 -7
  35. package/runtime/agents/workers/report-writer-worker.md +19 -10
  36. package/runtime/bin/okstra-central.sh +6 -6
  37. package/runtime/bin/okstra-codex-exec.sh +49 -28
  38. package/runtime/bin/okstra-gemini-exec.sh +39 -21
  39. package/runtime/bin/okstra-render-final-report.py +13 -2
  40. package/runtime/bin/okstra-wrapper-status.py +155 -0
  41. package/runtime/bin/okstra.sh +2 -2
  42. package/runtime/prompts/launch.template.md +1 -0
  43. package/runtime/prompts/profiles/_common-contract.md +11 -6
  44. package/runtime/prompts/profiles/_implementation-deliverable.md +53 -0
  45. package/runtime/prompts/profiles/_implementation-executor.md +60 -0
  46. package/runtime/prompts/profiles/_implementation-verifier.md +76 -0
  47. package/runtime/prompts/profiles/error-analysis.md +3 -7
  48. package/runtime/prompts/profiles/implementation-planning.md +22 -21
  49. package/runtime/prompts/profiles/implementation.md +28 -118
  50. package/runtime/prompts/profiles/improvement-discovery.md +42 -0
  51. package/runtime/prompts/profiles/release-handoff.md +1 -1
  52. package/runtime/prompts/profiles/requirements-discovery.md +8 -12
  53. package/runtime/prompts/wizard/prompts.ko.json +230 -0
  54. package/runtime/python/lib/okstra/cli.sh +2 -49
  55. package/runtime/python/lib/okstra/globals.sh +21 -21
  56. package/runtime/python/lib/okstra/interactive.sh +7 -7
  57. package/runtime/python/okstra_ctl/clarification_items.py +3 -9
  58. package/runtime/python/okstra_ctl/consumers.py +53 -0
  59. package/runtime/python/okstra_ctl/final_report_schema.py +0 -7
  60. package/runtime/python/okstra_ctl/i18n.py +73 -0
  61. package/runtime/python/okstra_ctl/improvement_lenses.py +44 -0
  62. package/runtime/python/okstra_ctl/index.py +1 -1
  63. package/runtime/python/okstra_ctl/paths.py +26 -20
  64. package/runtime/python/okstra_ctl/render.py +166 -207
  65. package/runtime/python/okstra_ctl/render_final_report.py +53 -10
  66. package/runtime/python/okstra_ctl/run.py +299 -108
  67. package/runtime/python/okstra_ctl/run_context.py +22 -0
  68. package/runtime/python/okstra_ctl/seeding.py +186 -0
  69. package/runtime/python/okstra_ctl/session.py +65 -7
  70. package/runtime/python/okstra_ctl/wizard.py +348 -127
  71. package/runtime/python/okstra_ctl/workflow.py +21 -2
  72. package/runtime/python/okstra_ctl/worktree.py +54 -1
  73. package/runtime/python/okstra_project/resolver.py +4 -3
  74. package/runtime/python/okstra_token_usage/report.py +2 -2
  75. package/runtime/schemas/final-report-v1.0.schema.json +22 -16
  76. package/runtime/skills/okstra-brief/SKILL.md +102 -218
  77. package/runtime/skills/okstra-convergence/SKILL.md +2 -3
  78. package/runtime/skills/okstra-inspect/SKILL.md +581 -0
  79. package/runtime/skills/okstra-report-writer/SKILL.md +35 -15
  80. package/runtime/skills/okstra-run/SKILL.md +8 -7
  81. package/runtime/skills/okstra-schedule/SKILL.md +14 -157
  82. package/runtime/skills/okstra-setup/SKILL.md +28 -1
  83. package/runtime/skills/okstra-team-contract/SKILL.md +16 -107
  84. package/runtime/templates/okstra.CLAUDE.md +104 -0
  85. package/runtime/templates/reports/brief.template.md +204 -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/schedule.template.md +12 -3
  92. package/runtime/templates/reports/task-brief.template.md +2 -2
  93. package/runtime/templates/worker-prompt-preamble.md +108 -0
  94. package/runtime/validators/lib/fixtures.sh +30 -0
  95. package/runtime/validators/lib/runners.sh +1 -1
  96. package/runtime/validators/validate-implementation-plan-stages.py +211 -0
  97. package/runtime/validators/validate-run.py +121 -26
  98. package/runtime/validators/validate-workflow.sh +2 -2
  99. package/runtime/validators/validate_improvement_report.py +275 -0
  100. package/src/config.mjs +18 -0
  101. package/src/install.mjs +41 -14
  102. package/src/setup.mjs +133 -1
  103. package/src/uninstall.mjs +27 -3
  104. package/runtime/skills/okstra-history/SKILL.md +0 -165
  105. package/runtime/skills/okstra-logs/SKILL.md +0 -173
  106. package/runtime/skills/okstra-report-finder/SKILL.md +0 -111
  107. package/runtime/skills/okstra-status/SKILL.md +0 -246
  108. 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
- 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
@@ -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
- # FINAL_STATUS_FILE 의 카운터(RUN_STATUS_SEQ) 는 RUN_MANIFESTS_SEQ
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, RUN_MANIFEST_FILE,
8
- FINAL_REPORT_FILE, ...). 호출자가 이 dict 를 그대로 인자로 사용하거나
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
- "ANALYSIS_TYPE": task_type,
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
- "TASK_MANIFEST_FILE": str(task_manifest),
170
- "TASK_INDEX_FILE": str(task_index),
171
- "INSTRUCTION_SET_DIR": str(instruction_set),
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
- "TIMELINE_FILE": str(timeline_file),
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
- "WORKER_RESULTS_DIR": str(worker_results),
184
- "RUN_MANIFEST_FILE": str(run_manifest_file),
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
- "FINAL_REPORT_FILE": str(final_report),
191
- "FINAL_STATUS_FILE": str(final_status),
192
- "TEAM_STATE_FILE": str(team_state),
193
- "FINAL_REPORT_TEMPLATE_FILE": str(final_report_template),
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
- "CLAUDE_RESUME_COMMAND_FILE": str(claude_resume_command),
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
- "RUN_ERRORS_LOG_FILE": str(run_errors_log),
203
- "CLAUDE_WORKER_ERRORS_SIDECAR_FILE": str(claude_worker_errors_sidecar),
204
- "CODEX_WORKER_ERRORS_SIDECAR_FILE": str(codex_worker_errors_sidecar),
205
- "GEMINI_WORKER_ERRORS_SIDECAR_FILE": str(gemini_worker_errors_sidecar),
206
- "REPORT_WRITER_WORKER_ERRORS_SIDECAR_FILE": str(report_writer_worker_errors_sidecar),
207
- "RUN_VALIDATOR_SCRIPT": str(run_validator_script),
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),