okstra 0.102.3 → 0.103.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 (37) hide show
  1. package/README.kr.md +3 -2
  2. package/README.md +3 -2
  3. package/docs/for-ai/skills/okstra-brief.md +18 -2
  4. package/docs/for-ai/skills/okstra-run.md +9 -1
  5. package/docs/kr/architecture/storage-model.md +7 -2
  6. package/docs/kr/architecture.md +5 -2
  7. package/docs/kr/cli.md +1 -0
  8. package/docs/project-structure-overview.md +9 -4
  9. package/package.json +1 -1
  10. package/runtime/BUILD.json +2 -2
  11. package/runtime/bin/lib/okstra/interactive.sh +12 -0
  12. package/runtime/prompts/profiles/_common-contract.md +4 -0
  13. package/runtime/prompts/profiles/error-analysis.md +2 -0
  14. package/runtime/prompts/profiles/improvement-discovery.md +2 -0
  15. package/runtime/prompts/profiles/requirements-discovery.md +4 -0
  16. package/runtime/prompts/wizard/prompts.ko.json +1 -1
  17. package/runtime/python/okstra_ctl/codex_dispatch.py +2 -1
  18. package/runtime/python/okstra_ctl/dispatch_core.py +4 -1
  19. package/runtime/python/okstra_ctl/implementation_outcome.py +271 -0
  20. package/runtime/python/okstra_ctl/manager_cli.py +231 -0
  21. package/runtime/python/okstra_ctl/manager_launch.py +198 -0
  22. package/runtime/python/okstra_ctl/manager_paths.py +98 -0
  23. package/runtime/python/okstra_ctl/manager_store.py +318 -0
  24. package/runtime/python/okstra_ctl/manager_sync.py +144 -0
  25. package/runtime/python/okstra_ctl/path_hints.py +665 -0
  26. package/runtime/python/okstra_ctl/render.py +4 -3
  27. package/runtime/python/okstra_ctl/run_context.py +4 -2
  28. package/runtime/python/okstra_ctl/wizard.py +118 -16
  29. package/runtime/python/okstra_project/state.py +40 -2
  30. package/runtime/skills/okstra-brief/SKILL.md +32 -7
  31. package/runtime/skills/okstra-manager/SKILL.md +44 -0
  32. package/runtime/skills/okstra-run/SKILL.md +2 -2
  33. package/runtime/templates/reports/brief.template.md +25 -0
  34. package/runtime/validators/validate-brief.py +128 -0
  35. package/src/cli-registry.mjs +7 -0
  36. package/src/commands/manager.mjs +57 -0
  37. package/src/lib/skill-catalog.mjs +1 -0
@@ -0,0 +1,665 @@
1
+ """Compact path-hint persistence and legacy context hydration."""
2
+ from __future__ import annotations
3
+
4
+ from pathlib import Path
5
+ from typing import Any, Mapping
6
+
7
+ from okstra_project.dirs import (
8
+ DISCOVERY_RELATIVE,
9
+ OKSTRA_RELATIVE,
10
+ TASKS_RELATIVE,
11
+ okstra_home,
12
+ )
13
+
14
+ RUN_CONTEXT_KIND = "run-context"
15
+ RUN_CONTEXT_SCHEMA_VERSION = "2.0"
16
+ ACTIVE_CONTEXT_KIND = "active-run-context"
17
+
18
+
19
+ def compact_run_context(ctx: Mapping[str, Any]) -> dict[str, Any]:
20
+ """Persist only path clues that can rehydrate the legacy flat context."""
21
+ return {
22
+ "schemaVersion": RUN_CONTEXT_SCHEMA_VERSION,
23
+ "kind": RUN_CONTEXT_KIND,
24
+ "identity": _compact_identity(ctx),
25
+ "pathHints": {
26
+ "project": _compact_project_hints(),
27
+ "task": {
28
+ "taskRoot": ctx.get("TASK_ROOT_RELATIVE_PATH", ""),
29
+ },
30
+ "run": _compact_run_hints(ctx),
31
+ "runtime": {
32
+ "okstraHome": str(okstra_home()),
33
+ },
34
+ },
35
+ "timestamps": {
36
+ "runTimestampIso": ctx.get("RUN_TIMESTAMP_ISO", ""),
37
+ "taskDate": ctx.get("TASK_DATE", ""),
38
+ },
39
+ }
40
+
41
+
42
+ def compact_active_run_context(
43
+ ctx: Mapping[str, Any],
44
+ payload: Mapping[str, Any],
45
+ ) -> dict[str, Any]:
46
+ """Persist active-run-context with path hints instead of repeated paths."""
47
+ run_context = compact_run_context(ctx)
48
+ return {
49
+ "schemaVersion": RUN_CONTEXT_SCHEMA_VERSION,
50
+ "kind": ACTIVE_CONTEXT_KIND,
51
+ "identity": run_context["identity"],
52
+ "pathHints": run_context["pathHints"],
53
+ "task": _compact_active_task(payload),
54
+ "workflow": dict(_mapping(payload.get("workflow"))),
55
+ "run": {
56
+ "stage": ctx.get("RUN_STAGE", ""),
57
+ },
58
+ "inputs": _compact_active_inputs(payload),
59
+ "workers": _compact_active_workers(payload),
60
+ "executorWorktree": dict(_mapping(payload.get("executorWorktree"))),
61
+ "lazyReadPlan": dict(_mapping(payload.get("lazyReadPlan"))),
62
+ }
63
+
64
+
65
+ def hydrate_run_context(payload: Mapping[str, Any]) -> dict[str, Any]:
66
+ """Return the legacy flat context for compact run-context payloads."""
67
+ if payload.get("kind") != RUN_CONTEXT_KIND:
68
+ return dict(payload)
69
+ hints = payload.get("pathHints")
70
+ identity = payload.get("identity")
71
+ if not isinstance(hints, Mapping) or not isinstance(identity, Mapping):
72
+ return dict(payload)
73
+ return _hydrate_from_hints(identity, hints, payload.get("timestamps", {}))
74
+
75
+
76
+ def hydrate_active_run_context(payload: Mapping[str, Any]) -> dict[str, Any]:
77
+ """Return the legacy active-run-context shape for compact payloads."""
78
+ if payload.get("kind") != ACTIVE_CONTEXT_KIND or "pathHints" not in payload:
79
+ return dict(payload)
80
+ identity = _mapping(payload.get("identity"))
81
+ hints = _mapping(payload.get("pathHints"))
82
+ ctx = hydrate_run_context({
83
+ "kind": RUN_CONTEXT_KIND,
84
+ "identity": identity,
85
+ "pathHints": hints,
86
+ "timestamps": {},
87
+ })
88
+ return {
89
+ "schemaVersion": "1.0",
90
+ "kind": ACTIVE_CONTEXT_KIND,
91
+ "task": _hydrate_active_task(payload, ctx),
92
+ "workflow": dict(_mapping(payload.get("workflow"))),
93
+ "run": _hydrate_active_run(ctx),
94
+ "instructionSet": _hydrate_active_instruction_set(payload, ctx),
95
+ "workers": _hydrate_active_workers(payload, ctx),
96
+ "errorLogs": _hydrate_active_error_logs(ctx),
97
+ "runtimeResources": {
98
+ "codingPreflightDir": ctx.get("OKSTRA_CODING_PREFLIGHT_DIR", ""),
99
+ },
100
+ "executorWorktree": dict(_mapping(payload.get("executorWorktree"))),
101
+ "sourceArtifacts": _hydrate_active_source_artifacts(ctx),
102
+ "lazyReadPlan": dict(_mapping(payload.get("lazyReadPlan"))),
103
+ }
104
+
105
+
106
+ def _compact_identity(ctx: Mapping[str, Any]) -> dict[str, Any]:
107
+ return {
108
+ "projectId": ctx.get("PROJECT_ID", ""),
109
+ "projectRoot": ctx.get("PROJECT_ROOT", ""),
110
+ "workspaceRoot": ctx.get("WORKSPACE_ROOT", ""),
111
+ "taskGroup": ctx.get("TASK_GROUP", ""),
112
+ "taskId": ctx.get("TASK_ID", ""),
113
+ "taskKey": ctx.get("TASK_KEY", ""),
114
+ "taskType": ctx.get("TASK_TYPE", ""),
115
+ "segments": {
116
+ "taskGroup": ctx.get("TASK_GROUP_SEGMENT", ""),
117
+ "taskId": ctx.get("TASK_ID_SEGMENT", ""),
118
+ "taskType": ctx.get("TASK_TYPE_SEGMENT", ""),
119
+ },
120
+ }
121
+
122
+
123
+ def _compact_active_task(payload: Mapping[str, Any]) -> dict[str, Any]:
124
+ task = _mapping(payload.get("task"))
125
+ keep = (
126
+ "projectId", "taskGroup", "taskId", "taskKey", "taskType",
127
+ "workCategory", "projectRoot",
128
+ )
129
+ return {key: task.get(key, "") for key in keep}
130
+
131
+
132
+ def _compact_active_inputs(payload: Mapping[str, Any]) -> dict[str, bool]:
133
+ instruction_set = _mapping(payload.get("instructionSet"))
134
+ return {
135
+ "hasClarificationResponse": bool(instruction_set.get("clarificationResponsePath")),
136
+ }
137
+
138
+
139
+ def _compact_active_workers(payload: Mapping[str, Any]) -> list[dict[str, Any]]:
140
+ workers = payload.get("workers")
141
+ if not isinstance(workers, list):
142
+ return []
143
+ return [_compact_active_worker(worker) for worker in workers if isinstance(worker, Mapping)]
144
+
145
+
146
+ def _compact_active_worker(worker: Mapping[str, Any]) -> dict[str, Any]:
147
+ keep = (
148
+ "workerId", "role", "agent", "agentLabel", "model",
149
+ "modelExecutionValue", "attemptRequired",
150
+ )
151
+ return {key: worker.get(key, "") for key in keep}
152
+
153
+
154
+ def _compact_project_hints() -> dict[str, str]:
155
+ return {
156
+ "okstraRoot": str(OKSTRA_RELATIVE),
157
+ "tasksRoot": str(TASKS_RELATIVE),
158
+ "discoveryRoot": str(DISCOVERY_RELATIVE),
159
+ }
160
+
161
+
162
+ def _hydrate_active_task(payload: Mapping[str, Any], ctx: Mapping[str, str]) -> dict[str, str]:
163
+ task = dict(_mapping(payload.get("task")))
164
+ task["taskRootPath"] = ctx.get("TASK_ROOT_RELATIVE_PATH", "")
165
+ return task
166
+
167
+
168
+ def _hydrate_active_run(ctx: Mapping[str, str]) -> dict[str, str]:
169
+ return {
170
+ "runDirectoryPath": ctx.get("RUN_DIR_RELATIVE_PATH", ""),
171
+ "runManifestPath": ctx.get("RUN_MANIFEST_RELATIVE_PATH", ""),
172
+ "teamStatePath": ctx.get("TEAM_STATE_RELATIVE_PATH", ""),
173
+ "promptSnapshotPath": ctx.get("RUN_PROMPT_SNAPSHOT_RELATIVE_PATH", ""),
174
+ "finalReportPath": ctx.get("FINAL_REPORT_RELATIVE_PATH", ""),
175
+ "finalStatusPath": ctx.get("FINAL_STATUS_RELATIVE_PATH", ""),
176
+ "validatorScriptPath": ctx.get("RUN_VALIDATOR_RELATIVE_PATH", ""),
177
+ "resumeCommandPath": ctx.get("CLAUDE_RESUME_COMMAND_RELATIVE_PATH", ""),
178
+ "workerPromptsDirectoryPath": ctx.get("RUN_PROMPTS_RELATIVE_PATH", ""),
179
+ "workerResultsDirectoryPath": ctx.get("WORKER_RESULTS_RELATIVE_PATH", ""),
180
+ }
181
+
182
+
183
+ def _hydrate_active_instruction_set(
184
+ payload: Mapping[str, Any],
185
+ ctx: Mapping[str, str],
186
+ ) -> dict[str, str]:
187
+ instruction_set = ctx.get("INSTRUCTION_SET_RELATIVE_PATH", "")
188
+ inputs = _mapping(payload.get("inputs"))
189
+ clarification_response = ""
190
+ if inputs.get("hasClarificationResponse"):
191
+ clarification_response = f"{instruction_set}/clarification-response.md"
192
+ return {
193
+ "path": instruction_set,
194
+ "analysisPacketPath": ctx.get("ANALYSIS_PACKET_RELATIVE_PATH", ""),
195
+ "taskBriefPath": f"{instruction_set}/task-brief.md",
196
+ "analysisProfilePath": f"{instruction_set}/analysis-profile.md",
197
+ "analysisMaterialPath": f"{instruction_set}/analysis-material.md",
198
+ "referenceExpectationsPath": ctx.get("REFERENCE_EXPECTATIONS_RELATIVE_PATH", ""),
199
+ "clarificationResponsePath": clarification_response,
200
+ "finalReportTemplatePath": ctx.get("FINAL_REPORT_TEMPLATE_RELATIVE_PATH", ""),
201
+ "finalReportSchemaPath": ctx.get("FINAL_REPORT_SCHEMA_RELATIVE_PATH", ""),
202
+ }
203
+
204
+
205
+ def _hydrate_active_workers(
206
+ payload: Mapping[str, Any],
207
+ ctx: Mapping[str, str],
208
+ ) -> list[dict[str, Any]]:
209
+ workers = payload.get("workers")
210
+ if not isinstance(workers, list):
211
+ return []
212
+ return [
213
+ _hydrate_active_worker(worker, ctx)
214
+ for worker in workers
215
+ if isinstance(worker, Mapping)
216
+ ]
217
+
218
+
219
+ def _hydrate_active_worker(
220
+ worker: Mapping[str, Any],
221
+ ctx: Mapping[str, str],
222
+ ) -> dict[str, Any]:
223
+ worker_id = str(worker.get("workerId", ""))
224
+ hydrated = dict(worker)
225
+ hydrated["promptPath"] = ctx.get(_worker_path_key(worker_id, "prompt"), "")
226
+ hydrated["resultPath"] = ctx.get(_worker_path_key(worker_id, "result"), "")
227
+ return hydrated
228
+
229
+
230
+ def _hydrate_active_error_logs(ctx: Mapping[str, str]) -> dict[str, Any]:
231
+ return {
232
+ "runErrorsLogPath": ctx.get("RUN_ERRORS_LOG_RELATIVE_PATH", ""),
233
+ "sidecarsByWorkerId": {
234
+ "claude": ctx.get("CLAUDE_WORKER_ERRORS_SIDECAR_RELATIVE_PATH", ""),
235
+ "codex": ctx.get("CODEX_WORKER_ERRORS_SIDECAR_RELATIVE_PATH", ""),
236
+ "antigravity": ctx.get("ANTIGRAVITY_WORKER_ERRORS_SIDECAR_RELATIVE_PATH", ""),
237
+ "report-writer": ctx.get("REPORT_WRITER_WORKER_ERRORS_SIDECAR_RELATIVE_PATH", ""),
238
+ },
239
+ }
240
+
241
+
242
+ def _hydrate_active_source_artifacts(ctx: Mapping[str, str]) -> dict[str, str]:
243
+ return {
244
+ "taskManifestPath": ctx.get("TASK_MANIFEST_RELATIVE_PATH", ""),
245
+ "runContextPath": ctx.get("RUN_CONTEXT_RELATIVE_PATH", ""),
246
+ "runInputsPath": ctx.get("RUN_INPUTS_RELATIVE_PATH", ""),
247
+ "historyTimelinePath": ctx.get("TIMELINE_RELATIVE_PATH", ""),
248
+ }
249
+
250
+
251
+ def _worker_path_key(worker_id: str, kind: str) -> str:
252
+ by_worker = {
253
+ "claude": ("CLAUDE_WORKER_PROMPT_RELATIVE_PATH", "CLAUDE_WORKER_RESULT_RELATIVE_PATH"),
254
+ "codex": ("CODEX_WORKER_PROMPT_RELATIVE_PATH", "CODEX_WORKER_RESULT_RELATIVE_PATH"),
255
+ "antigravity": (
256
+ "ANTIGRAVITY_WORKER_PROMPT_RELATIVE_PATH",
257
+ "ANTIGRAVITY_WORKER_RESULT_RELATIVE_PATH",
258
+ ),
259
+ "report-writer": (
260
+ "REPORT_WRITER_WORKER_PROMPT_RELATIVE_PATH",
261
+ "REPORT_WRITER_WORKER_RESULT_RELATIVE_PATH",
262
+ ),
263
+ }
264
+ index = 0 if kind == "prompt" else 1
265
+ return by_worker.get(worker_id, ("", ""))[index]
266
+
267
+
268
+ def _compact_run_hints(ctx: Mapping[str, Any]) -> dict[str, Any]:
269
+ return {
270
+ "runRoot": ctx.get("RUN_DIR_RELATIVE_PATH", ""),
271
+ "stage": ctx.get("RUN_STAGE", ""),
272
+ "fileSuffix": ctx.get("RUN_FILE_SUFFIX", ""),
273
+ "sequences": {
274
+ "manifests": ctx.get("RUN_MANIFESTS_SEQ", ""),
275
+ "prompts": ctx.get("RUN_PROMPTS_SEQ", ""),
276
+ "reports": ctx.get("RUN_REPORTS_SEQ", ""),
277
+ "status": ctx.get("RUN_STATUS_SEQ", ""),
278
+ "state": ctx.get("RUN_STATE_SEQ", ""),
279
+ "sessions": ctx.get("RUN_SESSIONS_SEQ", ""),
280
+ "workerResults": ctx.get("WORKER_RESULTS_SEQ", ""),
281
+ },
282
+ }
283
+
284
+
285
+ def _hydrate_from_hints(
286
+ identity: Mapping[str, Any],
287
+ hints: Mapping[str, Any],
288
+ timestamps: Any,
289
+ ) -> dict[str, Any]:
290
+ paths = _build_path_set(identity, hints)
291
+ filenames = _filename_fields(paths)
292
+ relative = _relative_fields(paths["project_root"], paths)
293
+ return {
294
+ **_identity_fields(identity),
295
+ **_runtime_fields(paths),
296
+ **_absolute_fields(paths),
297
+ **filenames,
298
+ **_sequence_fields(paths),
299
+ **relative,
300
+ **_timestamp_fields(timestamps),
301
+ }
302
+
303
+
304
+ def _build_path_set(identity: Mapping[str, Any], hints: Mapping[str, Any]) -> dict[str, Any]:
305
+ project_root = Path(str(identity.get("projectRoot", "")))
306
+ workspace_root = Path(str(identity.get("workspaceRoot", "")))
307
+ project_hints = _mapping(hints.get("project"))
308
+ run_hints = _mapping(hints.get("run"))
309
+ task_root = _project_path(project_root, _task_root_hint(hints))
310
+ run_root = _project_path(project_root, str(run_hints.get("runRoot", "")))
311
+ task_type_segment = _segment(identity, "taskType")
312
+ sequences = _sequences(run_hints)
313
+ suffixes = _suffixes(task_type_segment, sequences)
314
+ paths = {
315
+ "project_root": project_root,
316
+ "workspace_root": workspace_root,
317
+ "okstra_root": _project_path(project_root, str(project_hints.get("okstraRoot", OKSTRA_RELATIVE))),
318
+ "tasks_root": _project_path(project_root, str(project_hints.get("tasksRoot", TASKS_RELATIVE))),
319
+ "discovery_dir": _project_path(project_root, str(project_hints.get("discoveryRoot", DISCOVERY_RELATIVE))),
320
+ "task_root": task_root,
321
+ "run_dir": run_root,
322
+ "runtime_home": Path(str(_mapping(hints.get("runtime")).get("okstraHome", okstra_home()))),
323
+ "task_type_segment": task_type_segment,
324
+ "run_stage": str(run_hints.get("stage", "")),
325
+ "run_file_suffix": str(run_hints.get("fileSuffix", suffixes["reports"])),
326
+ "sequences": sequences,
327
+ "suffixes": suffixes,
328
+ }
329
+ paths.update(_task_path_set(task_root))
330
+ paths.update(_run_path_set(run_root, task_type_segment, sequences, suffixes))
331
+ paths.update(_discovery_path_set(paths["discovery_dir"]))
332
+ return paths
333
+
334
+
335
+ def _task_path_set(task_root: Path) -> dict[str, Path]:
336
+ instruction_set = task_root / "instruction-set"
337
+ history_dir = task_root / "history"
338
+ recap_dir = task_root / "recap"
339
+ return {
340
+ "task_manifest": task_root / "task-manifest.json",
341
+ "task_index": task_root / "task-index.md",
342
+ "instruction_set": instruction_set,
343
+ "analysis_packet": instruction_set / "analysis-packet.md",
344
+ "task_qa": task_root / "qa",
345
+ "runs_dir": task_root / "runs",
346
+ "history_dir": history_dir,
347
+ "timeline_file": history_dir / "timeline.json",
348
+ "recap_dir": recap_dir,
349
+ "recap_log": recap_dir / "recap-log.jsonl",
350
+ "final_report_template": instruction_set / "final-report-template.md",
351
+ "final_report_schema": instruction_set / "final-report-schema.json",
352
+ "reference_expectations": instruction_set / "reference-expectations.md",
353
+ }
354
+
355
+
356
+ def _run_path_set(
357
+ run_dir: Path,
358
+ task_type_segment: str,
359
+ sequences: Mapping[str, str],
360
+ suffixes: Mapping[str, str],
361
+ ) -> dict[str, Path]:
362
+ run_prompts = run_dir / "prompts"
363
+ worker_results = run_dir / "worker-results"
364
+ return {
365
+ "run_manifests": run_dir / "manifests",
366
+ "run_state": run_dir / "state",
367
+ "run_prompts": run_prompts,
368
+ "run_reports": run_dir / "reports",
369
+ "run_status": run_dir / "status",
370
+ "run_sessions": run_dir / "sessions",
371
+ "run_logs": run_dir / "logs",
372
+ "worker_results": worker_results,
373
+ "run_carry": run_dir / "carry",
374
+ **_run_files(run_dir, run_prompts, worker_results, task_type_segment, sequences, suffixes),
375
+ }
376
+
377
+
378
+ def _run_files(
379
+ run_dir: Path,
380
+ run_prompts: Path,
381
+ worker_results: Path,
382
+ task_type_segment: str,
383
+ sequences: Mapping[str, str],
384
+ suffixes: Mapping[str, str],
385
+ ) -> dict[str, Path]:
386
+ run_manifests = run_dir / "manifests"
387
+ run_state = run_dir / "state"
388
+ run_reports = run_dir / "reports"
389
+ run_status = run_dir / "status"
390
+ run_sessions = run_dir / "sessions"
391
+ run_logs = run_dir / "logs"
392
+ return {
393
+ "run_manifest_file": run_manifests / f"run-manifest{suffixes['manifests']}.json",
394
+ "run_context_file": run_manifests / f"run-context-{task_type_segment}-{sequences['manifests']}.json",
395
+ "run_inputs_file": run_manifests / f"run-inputs-{task_type_segment}-{sequences['manifests']}.json",
396
+ "run_prompt_snapshot": run_prompts / f"claude-execution-prompt{suffixes['prompts']}.md",
397
+ "claude_worker_prompt": run_prompts / f"claude-worker-prompt{suffixes['prompts']}.md",
398
+ "codex_worker_prompt": run_prompts / f"codex-worker-prompt{suffixes['prompts']}.md",
399
+ "antigravity_worker_prompt": run_prompts / f"antigravity-worker-prompt{suffixes['prompts']}.md",
400
+ "report_writer_worker_prompt": run_prompts / f"report-writer-worker-prompt{suffixes['prompts']}.md",
401
+ "final_report": run_reports / f"final-report{suffixes['reports']}.md",
402
+ "final_status": run_status / f"final{suffixes['status']}.status",
403
+ "team_state": run_state / f"team-state{suffixes['state']}.json",
404
+ "active_run_context": run_state / f"active-run-context{suffixes['state']}.json",
405
+ "lead_events": run_state / f"lead-events-{task_type_segment}-{sequences['state']}.jsonl",
406
+ "claude_resume_command": run_sessions / f"claude-resume{suffixes['sessions']}.sh",
407
+ "claude_worker_result": worker_results / f"claude-worker{suffixes['worker_results']}.md",
408
+ "codex_worker_result": worker_results / f"codex-worker{suffixes['worker_results']}.md",
409
+ "antigravity_worker_result": worker_results / f"antigravity-worker{suffixes['worker_results']}.md",
410
+ "report_writer_worker_result": worker_results / f"report-writer-worker{suffixes['worker_results']}.md",
411
+ "run_errors_log": run_logs / f"errors-{task_type_segment}-{sequences['state']}.jsonl",
412
+ "claude_worker_errors_sidecar": worker_results / f"claude-worker-errors{suffixes['worker_results']}.json",
413
+ "codex_worker_errors_sidecar": worker_results / f"codex-worker-errors{suffixes['worker_results']}.json",
414
+ "antigravity_worker_errors_sidecar": worker_results / f"antigravity-worker-errors{suffixes['worker_results']}.json",
415
+ "report_writer_errors_sidecar": worker_results / f"report-writer-worker-errors{suffixes['worker_results']}.json",
416
+ }
417
+
418
+
419
+ def _discovery_path_set(discovery_dir: Path) -> dict[str, Path]:
420
+ return {
421
+ "latest_task_file": discovery_dir / "latest-task.json",
422
+ "task_catalog_file": discovery_dir / "task-catalog.json",
423
+ }
424
+
425
+
426
+ def _identity_fields(identity: Mapping[str, Any]) -> dict[str, str]:
427
+ return {
428
+ "PROJECT_ID": str(identity.get("projectId", "")),
429
+ "PROJECT_ROOT": str(identity.get("projectRoot", "")),
430
+ "WORKSPACE_ROOT": str(identity.get("workspaceRoot", "")),
431
+ "TASK_GROUP": str(identity.get("taskGroup", "")),
432
+ "TASK_ID": str(identity.get("taskId", "")),
433
+ "TASK_KEY": str(identity.get("taskKey", "")),
434
+ "TASK_TYPE": str(identity.get("taskType", "")),
435
+ "TASK_GROUP_SEGMENT": _segment(identity, "taskGroup"),
436
+ "TASK_ID_SEGMENT": _segment(identity, "taskId"),
437
+ "TASK_TYPE_SEGMENT": _segment(identity, "taskType"),
438
+ }
439
+
440
+
441
+ def _runtime_fields(paths: Mapping[str, Any]) -> dict[str, str]:
442
+ runtime_home = Path(paths["runtime_home"])
443
+ lead = runtime_home / "prompts" / "lead"
444
+ return {
445
+ "WORKER_PROMPT_PREAMBLE_PATH": str(runtime_home / "templates" / "worker-prompt-preamble.md"),
446
+ "OKSTRA_LEAD_CONTRACT_PATH": str(lead / "okstra-lead-contract.md"),
447
+ "OKSTRA_CONTEXT_LOADER_PATH": str(lead / "context-loader.md"),
448
+ "OKSTRA_TEAM_CONTRACT_PATH": str(lead / "team-contract.md"),
449
+ "OKSTRA_CONVERGENCE_PATH": str(lead / "convergence.md"),
450
+ "OKSTRA_REPORT_WRITER_PATH": str(lead / "report-writer.md"),
451
+ "OKSTRA_CODING_PREFLIGHT_DIR": str(runtime_home / "prompts" / "coding-preflight"),
452
+ }
453
+
454
+
455
+ def _absolute_fields(paths: Mapping[str, Any]) -> dict[str, str]:
456
+ return {
457
+ **_absolute_project_task_fields(paths),
458
+ **_absolute_run_fields(paths),
459
+ **_absolute_worker_fields(paths),
460
+ }
461
+
462
+
463
+ def _absolute_project_task_fields(paths: Mapping[str, Any]) -> dict[str, str]:
464
+ return {
465
+ "OKSTRA_ROOT": str(paths["okstra_root"]),
466
+ "OKSTRA_TASKS_ROOT": str(paths["tasks_root"]),
467
+ "OKSTRA_DISCOVERY_DIR": str(paths["discovery_dir"]),
468
+ "TASK_ROOT": str(paths["task_root"]),
469
+ "TASK_MANIFEST_PATH": str(paths["task_manifest"]),
470
+ "TASK_INDEX_PATH": str(paths["task_index"]),
471
+ "INSTRUCTION_SET_PATH": str(paths["instruction_set"]),
472
+ "ANALYSIS_PACKET_PATH": str(paths["analysis_packet"]),
473
+ "TASK_QA_PATH": str(paths["task_qa"]),
474
+ "RUNS_DIR": str(paths["runs_dir"]),
475
+ "HISTORY_DIR": str(paths["history_dir"]),
476
+ "TIMELINE_PATH": str(paths["timeline_file"]),
477
+ "RECAP_DIR": str(paths["recap_dir"]),
478
+ "RECAP_LOG_PATH": str(paths["recap_log"]),
479
+ "FINAL_REPORT_TEMPLATE_PATH": str(paths["final_report_template"]),
480
+ "FINAL_REPORT_SCHEMA_PATH": str(paths["final_report_schema"]),
481
+ "REFERENCE_EXPECTATIONS_FILE": str(paths["reference_expectations"]),
482
+ "OKSTRA_LATEST_TASK_FILE": str(paths["latest_task_file"]),
483
+ "OKSTRA_TASK_CATALOG_FILE": str(paths["task_catalog_file"]),
484
+ }
485
+
486
+
487
+ def _absolute_run_fields(paths: Mapping[str, Any]) -> dict[str, str]:
488
+ return {
489
+ "RUN_DIR": str(paths["run_dir"]),
490
+ "RUN_STAGE": str(paths["run_stage"]),
491
+ "RUN_MANIFESTS_DIR": str(paths["run_manifests"]),
492
+ "RUN_STATE_DIR": str(paths["run_state"]),
493
+ "RUN_PROMPTS_DIR": str(paths["run_prompts"]),
494
+ "RUN_REPORTS_DIR": str(paths["run_reports"]),
495
+ "RUN_STATUS_DIR": str(paths["run_status"]),
496
+ "RUN_SESSIONS_DIR": str(paths["run_sessions"]),
497
+ "RUN_LOGS_DIR": str(paths["run_logs"]),
498
+ "WORKER_RESULTS_PATH": str(paths["worker_results"]),
499
+ "RUN_CARRY_PATH": str(paths["run_carry"]),
500
+ "RUN_MANIFEST_PATH": str(paths["run_manifest_file"]),
501
+ "RUN_CONTEXT_FILE": str(paths["run_context_file"]),
502
+ "RUN_PROMPT_SNAPSHOT_FILE": str(paths["run_prompt_snapshot"]),
503
+ "FINAL_REPORT_PATH": str(paths["final_report"]),
504
+ "FINAL_STATUS_PATH": str(paths["final_status"]),
505
+ "TEAM_STATE_PATH": str(paths["team_state"]),
506
+ "ACTIVE_RUN_CONTEXT_PATH": str(paths["active_run_context"]),
507
+ "LEAD_EVENTS_PATH": str(paths["lead_events"]),
508
+ "CLAUDE_RESUME_COMMAND_PATH": str(paths["claude_resume_command"]),
509
+ "RUN_ERRORS_LOG_PATH": str(paths["run_errors_log"]),
510
+ "RUN_VALIDATOR_PATH": str(paths["workspace_root"] / "validators" / "validate-run.py"),
511
+ "LATEST_RUN_PATH": str(paths["run_dir"]),
512
+ }
513
+
514
+
515
+ def _absolute_worker_fields(paths: Mapping[str, Any]) -> dict[str, str]:
516
+ return {
517
+ "CLAUDE_WORKER_PROMPT_FILE": str(paths["claude_worker_prompt"]),
518
+ "CODEX_WORKER_PROMPT_FILE": str(paths["codex_worker_prompt"]),
519
+ "ANTIGRAVITY_WORKER_PROMPT_FILE": str(paths["antigravity_worker_prompt"]),
520
+ "REPORT_WRITER_WORKER_PROMPT_FILE": str(paths["report_writer_worker_prompt"]),
521
+ "CLAUDE_WORKER_RESULT_FILE": str(paths["claude_worker_result"]),
522
+ "CODEX_WORKER_RESULT_FILE": str(paths["codex_worker_result"]),
523
+ "ANTIGRAVITY_WORKER_RESULT_FILE": str(paths["antigravity_worker_result"]),
524
+ "REPORT_WRITER_WORKER_RESULT_FILE": str(paths["report_writer_worker_result"]),
525
+ "CLAUDE_WORKER_ERRORS_SIDECAR_PATH": str(paths["claude_worker_errors_sidecar"]),
526
+ "CODEX_WORKER_ERRORS_SIDECAR_PATH": str(paths["codex_worker_errors_sidecar"]),
527
+ "ANTIGRAVITY_WORKER_ERRORS_SIDECAR_PATH": str(paths["antigravity_worker_errors_sidecar"]),
528
+ "REPORT_WRITER_WORKER_ERRORS_SIDECAR_PATH": str(paths["report_writer_errors_sidecar"]),
529
+ }
530
+
531
+
532
+ def _filename_fields(paths: Mapping[str, Any]) -> dict[str, str]:
533
+ return {
534
+ "RUN_MANIFEST_FILENAME": Path(paths["run_manifest_file"]).name,
535
+ "RUN_PROMPT_SNAPSHOT_FILENAME": Path(paths["run_prompt_snapshot"]).name,
536
+ "FINAL_REPORT_FILENAME": Path(paths["final_report"]).name,
537
+ "FINAL_STATUS_FILENAME": Path(paths["final_status"]).name,
538
+ "CLAUDE_RESUME_COMMAND_FILENAME": Path(paths["claude_resume_command"]).name,
539
+ "RUN_FILE_SUFFIX": str(paths["run_file_suffix"]),
540
+ }
541
+
542
+
543
+ def _sequence_fields(paths: Mapping[str, Any]) -> dict[str, str]:
544
+ sequences = paths["sequences"]
545
+ return {
546
+ "RUN_MANIFESTS_SEQ": sequences["manifests"],
547
+ "RUN_PROMPTS_SEQ": sequences["prompts"],
548
+ "RUN_REPORTS_SEQ": sequences["reports"],
549
+ "RUN_STATUS_SEQ": sequences["status"],
550
+ "RUN_STATE_SEQ": sequences["state"],
551
+ "RUN_SESSIONS_SEQ": sequences["sessions"],
552
+ "WORKER_RESULTS_SEQ": sequences["worker_results"],
553
+ }
554
+
555
+
556
+ def _relative_fields(project_root: Path, paths: Mapping[str, Any]) -> dict[str, str]:
557
+ return {
558
+ "OKSTRA_DISCOVERY_RELATIVE_PATH": _rel(project_root, paths["discovery_dir"]),
559
+ "OKSTRA_LATEST_TASK_RELATIVE_PATH": _rel(project_root, paths["latest_task_file"]),
560
+ "OKSTRA_TASK_CATALOG_RELATIVE_PATH": _rel(project_root, paths["task_catalog_file"]),
561
+ "TASK_ROOT_RELATIVE_PATH": _rel(project_root, paths["task_root"]),
562
+ "TASK_MANIFEST_RELATIVE_PATH": _rel(project_root, paths["task_manifest"]),
563
+ "TASK_INDEX_RELATIVE_PATH": _rel(project_root, paths["task_index"]),
564
+ "INSTRUCTION_SET_RELATIVE_PATH": _rel(project_root, paths["instruction_set"]),
565
+ "ANALYSIS_PACKET_RELATIVE_PATH": _rel(project_root, paths["analysis_packet"]),
566
+ "RUNS_RELATIVE_PATH": _rel(project_root, paths["runs_dir"]),
567
+ "HISTORY_RELATIVE_PATH": _rel(project_root, paths["history_dir"]),
568
+ "TIMELINE_RELATIVE_PATH": _rel(project_root, paths["timeline_file"]),
569
+ "RECAP_DIR_RELATIVE_PATH": _rel(project_root, paths["recap_dir"]),
570
+ "RECAP_LOG_RELATIVE_PATH": _rel(project_root, paths["recap_log"]),
571
+ "RUN_DIR_RELATIVE_PATH": _rel(project_root, paths["run_dir"]),
572
+ "RUN_MANIFESTS_RELATIVE_PATH": _rel(project_root, paths["run_manifests"]),
573
+ "RUN_STATE_RELATIVE_PATH": _rel(project_root, paths["run_state"]),
574
+ "RUN_PROMPTS_RELATIVE_PATH": _rel(project_root, paths["run_prompts"]),
575
+ "RUN_REPORTS_RELATIVE_PATH": _rel(project_root, paths["run_reports"]),
576
+ "RUN_STATUS_RELATIVE_PATH": _rel(project_root, paths["run_status"]),
577
+ "RUN_SESSIONS_RELATIVE_PATH": _rel(project_root, paths["run_sessions"]),
578
+ **_relative_file_fields(project_root, paths),
579
+ }
580
+
581
+
582
+ def _relative_file_fields(project_root: Path, paths: Mapping[str, Any]) -> dict[str, str]:
583
+ return {
584
+ "RUN_MANIFEST_RELATIVE_PATH": _rel(project_root, paths["run_manifest_file"]),
585
+ "RUN_CONTEXT_RELATIVE_PATH": _rel(project_root, paths["run_context_file"]),
586
+ "RUN_INPUTS_RELATIVE_PATH": _rel(project_root, paths["run_inputs_file"]),
587
+ "RUN_PROMPT_SNAPSHOT_RELATIVE_PATH": _rel(project_root, paths["run_prompt_snapshot"]),
588
+ "CLAUDE_WORKER_PROMPT_RELATIVE_PATH": _rel(project_root, paths["claude_worker_prompt"]),
589
+ "CODEX_WORKER_PROMPT_RELATIVE_PATH": _rel(project_root, paths["codex_worker_prompt"]),
590
+ "ANTIGRAVITY_WORKER_PROMPT_RELATIVE_PATH": _rel(project_root, paths["antigravity_worker_prompt"]),
591
+ "REPORT_WRITER_WORKER_PROMPT_RELATIVE_PATH": _rel(project_root, paths["report_writer_worker_prompt"]),
592
+ "FINAL_REPORT_RELATIVE_PATH": _rel(project_root, paths["final_report"]),
593
+ "FINAL_STATUS_RELATIVE_PATH": _rel(project_root, paths["final_status"]),
594
+ "TEAM_STATE_RELATIVE_PATH": _rel(project_root, paths["team_state"]),
595
+ "ACTIVE_RUN_CONTEXT_RELATIVE_PATH": _rel(project_root, paths["active_run_context"]),
596
+ "LEAD_EVENTS_RELATIVE_PATH": _rel(project_root, paths["lead_events"]),
597
+ "WORKER_RESULTS_RELATIVE_PATH": _rel(project_root, paths["worker_results"]),
598
+ "RUN_CARRY_RELATIVE_PATH": _rel(project_root, paths["run_carry"]),
599
+ "FINAL_REPORT_TEMPLATE_RELATIVE_PATH": _rel(project_root, paths["final_report_template"]),
600
+ "FINAL_REPORT_SCHEMA_RELATIVE_PATH": _rel(project_root, paths["final_report_schema"]),
601
+ "REFERENCE_EXPECTATIONS_RELATIVE_PATH": _rel(project_root, paths["reference_expectations"]),
602
+ "CLAUDE_RESUME_COMMAND_RELATIVE_PATH": _rel(project_root, paths["claude_resume_command"]),
603
+ "RUN_VALIDATOR_RELATIVE_PATH": _rel(project_root, paths["workspace_root"] / "validators" / "validate-run.py"),
604
+ "CLAUDE_WORKER_RESULT_RELATIVE_PATH": _rel(project_root, paths["claude_worker_result"]),
605
+ "CODEX_WORKER_RESULT_RELATIVE_PATH": _rel(project_root, paths["codex_worker_result"]),
606
+ "ANTIGRAVITY_WORKER_RESULT_RELATIVE_PATH": _rel(project_root, paths["antigravity_worker_result"]),
607
+ "REPORT_WRITER_WORKER_RESULT_RELATIVE_PATH": _rel(project_root, paths["report_writer_worker_result"]),
608
+ "RUN_ERRORS_LOG_RELATIVE_PATH": _rel(project_root, paths["run_errors_log"]),
609
+ "CLAUDE_WORKER_ERRORS_SIDECAR_RELATIVE_PATH": _rel(project_root, paths["claude_worker_errors_sidecar"]),
610
+ "CODEX_WORKER_ERRORS_SIDECAR_RELATIVE_PATH": _rel(project_root, paths["codex_worker_errors_sidecar"]),
611
+ "ANTIGRAVITY_WORKER_ERRORS_SIDECAR_RELATIVE_PATH": _rel(project_root, paths["antigravity_worker_errors_sidecar"]),
612
+ "REPORT_WRITER_WORKER_ERRORS_SIDECAR_RELATIVE_PATH": _rel(project_root, paths["report_writer_errors_sidecar"]),
613
+ "LATEST_RUN_RELATIVE_PATH": _rel(project_root, paths["run_dir"]),
614
+ }
615
+
616
+
617
+ def _timestamp_fields(timestamps: Any) -> dict[str, str]:
618
+ values = _mapping(timestamps)
619
+ return {
620
+ "RUN_TIMESTAMP_ISO": str(values.get("runTimestampIso", "")),
621
+ "TASK_DATE": str(values.get("taskDate", "")),
622
+ }
623
+
624
+
625
+ def _task_root_hint(hints: Mapping[str, Any]) -> str:
626
+ task_hints = _mapping(hints.get("task"))
627
+ return str(task_hints.get("taskRoot", ""))
628
+
629
+
630
+ def _segment(identity: Mapping[str, Any], name: str) -> str:
631
+ segments = _mapping(identity.get("segments"))
632
+ return str(segments.get(name, ""))
633
+
634
+
635
+ def _mapping(value: Any) -> Mapping[str, Any]:
636
+ return value if isinstance(value, Mapping) else {}
637
+
638
+
639
+ def _sequences(run_hints: Mapping[str, Any]) -> dict[str, str]:
640
+ raw = _mapping(run_hints.get("sequences"))
641
+ return {
642
+ "manifests": str(raw.get("manifests", "")),
643
+ "prompts": str(raw.get("prompts", "")),
644
+ "reports": str(raw.get("reports", "")),
645
+ "status": str(raw.get("status", "")),
646
+ "state": str(raw.get("state", "")),
647
+ "sessions": str(raw.get("sessions", "")),
648
+ "worker_results": str(raw.get("workerResults", "")),
649
+ }
650
+
651
+
652
+ def _suffixes(task_type_segment: str, sequences: Mapping[str, str]) -> dict[str, str]:
653
+ return {key: f"-{task_type_segment}-{seq}" for key, seq in sequences.items()}
654
+
655
+
656
+ def _project_path(project_root: Path, value: str) -> Path:
657
+ path = Path(value)
658
+ return path if path.is_absolute() else project_root / path
659
+
660
+
661
+ def _rel(project_root: Path, target: Path) -> str:
662
+ try:
663
+ return str(target.resolve().relative_to(project_root.resolve()))
664
+ except (ValueError, OSError):
665
+ return str(target)