okstra 0.82.1 → 0.83.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 (70) hide show
  1. package/README.kr.md +7 -6
  2. package/README.md +6 -6
  3. package/docs/kr/architecture.md +8 -7
  4. package/docs/kr/cli.md +2 -2
  5. package/docs/kr/performance-improvement-plan-v2.md +14 -14
  6. package/docs/project-structure-overview.md +7 -8
  7. package/docs/superpowers/plans/2026-06-15-coding-preflight-pack-dispatch-path.md +504 -0
  8. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-final-fixups.md +342 -0
  9. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-fixups.md +258 -0
  10. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-remaining-fixups.md +387 -0
  11. package/docs/superpowers/plans/2026-06-15-internal-skill-resource-migration.md +749 -0
  12. package/docs/superpowers/plans/2026-06-15-worker-prompt-anchor-final-fixups.md +828 -0
  13. package/docs/superpowers/plans/2026-06-15-worker-prompt-header-error-contract.md +490 -0
  14. package/docs/task-process/README.md +1 -1
  15. package/docs/task-process/error-analysis.md +1 -1
  16. package/docs/task-process/final-verification.md +1 -1
  17. package/docs/task-process/implementation-planning.md +1 -1
  18. package/docs/task-process/implementation.md +1 -1
  19. package/docs/task-process/release-handoff.md +1 -1
  20. package/docs/task-process/requirements-discovery.md +2 -2
  21. package/package.json +1 -1
  22. package/runtime/BUILD.json +2 -2
  23. package/runtime/agents/TODO.md +2 -0
  24. package/runtime/agents/workers/claude-worker.md +8 -8
  25. package/runtime/agents/workers/codex-worker.md +8 -8
  26. package/runtime/agents/workers/gemini-worker.md +8 -8
  27. package/runtime/agents/workers/report-writer-worker.md +2 -2
  28. package/runtime/bin/lib/okstra/globals.sh +0 -1
  29. package/runtime/bin/okstra-wrapper-status.py +1 -1
  30. package/runtime/{skills/okstra-coding-preflight → prompts/coding-preflight}/languages/python.md +2 -2
  31. package/runtime/{skills/okstra-coding-preflight → prompts/coding-preflight}/languages/rust.md +1 -1
  32. package/runtime/{skills/okstra-coding-preflight/SKILL.md → prompts/coding-preflight/overview.md} +27 -38
  33. package/runtime/prompts/launch.template.md +5 -3
  34. package/runtime/{skills/okstra-context-loader/SKILL.md → prompts/lead/context-loader.md} +7 -14
  35. package/runtime/{skills/okstra-convergence/SKILL.md → prompts/lead/convergence.md} +12 -19
  36. package/runtime/{agents/SKILL.md → prompts/lead/okstra-lead-contract.md} +53 -59
  37. package/runtime/{skills/okstra-report-writer/SKILL.md → prompts/lead/report-writer.md} +12 -19
  38. package/runtime/{skills/okstra-team-contract/SKILL.md → prompts/lead/team-contract.md} +13 -19
  39. package/runtime/prompts/profiles/_coding-conventions-preflight.md +2 -2
  40. package/runtime/prompts/profiles/_common-contract.md +2 -2
  41. package/runtime/prompts/profiles/_implementation-executor.md +2 -2
  42. package/runtime/prompts/profiles/_implementation-verifier.md +2 -2
  43. package/runtime/prompts/profiles/error-analysis.md +2 -2
  44. package/runtime/prompts/profiles/final-verification.md +1 -1
  45. package/runtime/prompts/profiles/implementation-planning.md +4 -4
  46. package/runtime/prompts/profiles/requirements-discovery.md +2 -2
  47. package/runtime/python/okstra_ctl/codex_dispatch.py +12 -61
  48. package/runtime/python/okstra_ctl/context_cost.py +14 -11
  49. package/runtime/python/okstra_ctl/dispatch_core.py +36 -13
  50. package/runtime/python/okstra_ctl/paths.py +27 -1
  51. package/runtime/python/okstra_ctl/render.py +62 -8
  52. package/runtime/python/okstra_ctl/run.py +5 -5
  53. package/runtime/python/okstra_ctl/worker_prompt_headers.py +126 -0
  54. package/runtime/python/okstra_token_usage/claude.py +1 -1
  55. package/runtime/python/okstra_token_usage/collect.py +1 -1
  56. package/runtime/templates/reports/task-brief.template.md +2 -2
  57. package/runtime/templates/worker-prompt-preamble.md +2 -2
  58. package/runtime/validators/lib/validate-assets.sh +12 -4
  59. package/runtime/validators/validate-run.py +3 -3
  60. package/runtime/validators/validate_session_conformance.py +11 -11
  61. package/src/install.mjs +95 -81
  62. package/src/skill-catalog.mjs +35 -0
  63. package/src/uninstall.mjs +5 -0
  64. /package/runtime/{skills/okstra-coding-preflight/architecture → prompts/coding-preflight/architectures}/hexagonal.md +0 -0
  65. /package/runtime/{skills/okstra-coding-preflight → prompts/coding-preflight}/clean-code.md +0 -0
  66. /package/runtime/{skills/okstra-coding-preflight/languages/nodejs.md → prompts/coding-preflight/frameworks/node-server.md} +0 -0
  67. /package/runtime/{skills/okstra-coding-preflight → prompts/coding-preflight}/languages/java.md +0 -0
  68. /package/runtime/{skills/okstra-coding-preflight → prompts/coding-preflight}/languages/javascript-typescript.md +0 -0
  69. /package/runtime/{skills/okstra-coding-preflight → prompts/coding-preflight}/languages/kotlin.md +0 -0
  70. /package/runtime/{skills/okstra-coding-preflight → prompts/coding-preflight}/languages/sql.md +0 -0
@@ -9,7 +9,7 @@ at Phase 5, BEFORE constructing the verifier worker dispatch prompts.
9
9
 
10
10
  ## Verifier roles (resolved at run-prep time)
11
11
 
12
- - **Verifier dispatch labelling.** When Lead dispatches a verifier (here, and identically in `final-verification`) it MUST set the Agent `name` to `<provider>-verifier` (e.g. `claude-verifier`, `codex-verifier`) — NOT the generic `<provider>-worker` — so the FleetView teammate pill names the job; see `agents/SKILL.md` Phase 4 "Agent `name` on dispatch". For a `codex` / `gemini` verifier, Lead MUST additionally inject a `**Pane role:** verifier` line into the dispatched prompt body so the CLI wrapper titles its tmux trace pane `<cli>-verifier-<pid>` (the wrapper reads that line per `agents/workers/_cli-wrapper-template.md`). Token attribution is unaffected — the collector matches any `agentName` beginning `<provider>-`.
12
+ - **Verifier dispatch labelling.** When Lead dispatches a verifier (here, and identically in `final-verification`) it MUST set the Agent `name` to `<provider>-verifier` (e.g. `claude-verifier`, `codex-verifier`) — NOT the generic `<provider>-worker` — so the FleetView teammate pill names the job; see `prompts/lead/okstra-lead-contract.md` Phase 4 "Agent `name` on dispatch". For a `codex` / `gemini` verifier, Lead MUST additionally inject a `**Pane role:** verifier` line into the dispatched prompt body so the CLI wrapper titles its tmux trace pane `<cli>-verifier-<pid>` (the wrapper reads that line per `agents/workers/_cli-wrapper-template.md`). Token attribution is unaffected — the collector matches any `agentName` beginning `<provider>-`.
13
13
  - The verifier slots are `Claude verifier` and `Codex verifier`, plus `Gemini verifier` **only when `gemini` is in the resolved `--workers` roster**. Every verifier in the resolved roster is dispatched regardless of which provider holds the executor role; the executor's own provider is run *separately* as a verifier (a fresh CLI session with no shared context) so that no verdict is produced from the same session that wrote the diff. Verifiers MUST NOT call Edit, Write, or any Bash command that mutates files outside the run's artifact directories. If a verifier wants a fix, it records the recommendation in its worker result; it does not apply the fix itself.
14
14
  - Session isolation — not model-variant divergence — is the primary self-review safeguard: each verifier is a separate CLI invocation with its own context window, so reusing the same model variant for executor and same-provider verifier is acceptable. Different model variants (e.g. executor=opus / Claude verifier=sonnet) remain recommended when available.
15
15
  - Phase-specific model defaults override the shared defaults: `Claude verifier`=`opus`, `Codex verifier`=`gpt-5.5`, `Gemini verifier`=`auto` (only when present in the roster). The `Executor`'s model is taken from the provider-specific worker model corresponding to `--executor`: claude→`--claude-model` (default `opus`), codex→`--codex-model` (default `gpt-5.5`), gemini→`--gemini-model` (default `auto`).
@@ -88,7 +88,7 @@ The final report keeps both — executor's `Validation evidence` AND each verifi
88
88
  Re-running commands proves the diff *builds and passes*; it does NOT prove the diff is *well-designed*. Lint/test green is necessary but not sufficient — self-mocked tests, interaction-only assertions, and untruthful names all survive a green pipeline. This gate is the filter for exactly those defects, so the executor's design errors are caught here instead of in post-merge PR review. It is a real gate, not a checklist: it enumerates the full diff and a blocking hit forces `FAIL`.
89
89
 
90
90
  - **Scope (no silent sampling).** Enumerate every changed source/test file via `git diff --name-only <base>...HEAD` and review each one. Skipping a changed file silently is a `contract-violated` outcome. If a file's language has no reference and is not covered by the agnostic checks below, record `design-review skipped: <file> (language=<x> no reference)` — never pass it silently.
91
- - **Load the same conventions the executor used, per language.** For each touched language load the coding-conventions reference by reading `~/.claude/skills/okstra-coding-preflight/languages/<lang>.md` + `clean-code.md` + the `architecture/hexagonal.md` overlay when the layout matches; degrade to the agnostic checks below when those files are not readable. The verifier does NOT inline language rules — it loads them per situation, identical to the executor preflight.
91
+ - **Load the same conventions the executor used via the routed pack.** Use this worker prompt's `**Coding preflight pack:**` anchor header as the absolute path to the installed routed pack. Read `overview.md` first, then `clean-code.md`, then apply the router's three ordered stages: language, framework, architecture. In each stage, iterate every rule, treat a rule as matched when any listed condition is true, and accumulate every matching resource — including `frameworks/node-server.md` for server-side Node work and `architectures/hexagonal.md` for ports-and-adapters / NestJS-hex layouts. Degrade to the agnostic checks below when the resolved pack is unreadable, and record either `coding-conventions: resources=<...>` or `coding-conventions: resource-unavailable → applied <project rules + agnostic principles>`. The verifier does NOT inline language rules — it loads the same situation-specific resources as the executor preflight.
92
92
  - **Load project review rule packs when present.** Search the project root, `.claude/skills`, and up to two parent `skills/` directories for `*review*/SKILL.md` rule packs. Read their referenced `references/*.md` files and apply them as an overlay on this static review. If a premium review skill exists, use its coverage philosophy (recall-first enumeration followed by verify-only confirmation) as the verifier's mental model, but do NOT dispatch extra reviewer agents unless the task explicitly configured them. Record `project-review-rules: <paths read>` or `project-review-rules: none found` in the worker result.
93
93
  - **Blocking checks (any hit → verdict `FAIL`, cited `path:line` + rule name, recommended fix recorded — the verifier does NOT apply it):**
94
94
  - **New duplication / DRY:** two or more newly added or meaningfully modified blocks implement the same helper stack, transform, or domain rule. Literal copy-paste is always blocking; semantically equivalent transforms across services are blocking unless the approved plan explicitly justified keeping them separate. Recommend the shared module location.
@@ -37,8 +37,8 @@
37
37
  - **Codebase-first ambiguity resolution (defect rule)**: any ambiguity about repro, file behavior, or symbol semantics that can be answered by `Read` / `Grep` / log inspection MUST be resolved that way and recorded with file:line (or log-line) evidence. Writing a clarification row for something the codebase or shipped logs already answer is a defect of this phase.
38
38
  - **Evidence note required inside `Statement`**: every clarification row includes `Evidence checked: <path:line>` or `Evidence checked: none — <reporter-only reason>` in the `Statement` cell. `none` is allowed ONLY when the row's nature is "only the reporter can answer this" (reporter-side data, business priority, environment they observed). A row with `none` that *could* have been answered by code or logs is a defect.
39
39
  - Cross-verification mode:
40
- - Phase 5.5 convergence runs in **adversarial mode** for this phase (`convergence.adversarial=true`). Verifiers actively try to refute each root-cause / reproduction claim by directly re-inspecting the cited code, logs, or config; the burden of proof sits on the claim. See `skills/okstra-convergence/SKILL.md` §"Adversarial Verification Mode". A single evidence-backed refutation prevents a finding from reaching consensus.
41
- - **Coverage critic (opt-in)**: when `convergence.critic.enabled=true` (chosen via the okstra-run picker or `--critic`), a reused-worker critic pass is dispatched concurrently with the first convergence reverify round to surface missed findings; its gaps are merged only after a 1-round adversarial reverify that follows convergence. See `skills/okstra-convergence/SKILL.md` "Coverage critic pass".
40
+ - Phase 5.5 convergence runs in **adversarial mode** for this phase (`convergence.adversarial=true`). Verifiers actively try to refute each root-cause / reproduction claim by directly re-inspecting the cited code, logs, or config; the burden of proof sits on the claim. See `prompts/lead/convergence.md` §"Adversarial Verification Mode". A single evidence-backed refutation prevents a finding from reaching consensus.
41
+ - **Coverage critic (opt-in)**: when `convergence.critic.enabled=true` (chosen via the okstra-run picker or `--critic`), a reused-worker critic pass is dispatched concurrently with the first convergence reverify round to surface missed findings; its gaps are merged only after a 1-round adversarial reverify that follows convergence. See `prompts/lead/convergence.md` "Coverage critic pass".
42
42
  - Non-goals:
43
43
  - implementation details unless they are necessary to validate the cause
44
44
  - **source code edits, builds, migrations, or deployments** — this run produces evidence and cause analysis only; the fix belongs to a later `implementation-planning` run followed by an `implementation` run
@@ -48,7 +48,7 @@
48
48
  4. **Verifier dissent preserved** — if workers reach different verdicts, the disagreement is visible in section 1.2; synthesis hides nothing.
49
49
  5. **No source-mutation audit** — scan the run's session transcripts for Edit / Write or state-mutating Bash commands that touch paths OUTSIDE `<PROJECT_ROOT>/.okstra/**` and outside the assigned run-artifact paths. Writes to worker prompts, audit sidecars, team-state, the final-report `data.json`, and rendered reports under the run directory are allowed okstra artifacts. Any source/schema/deployment mutation means the run has crossed into implementation and MUST be re-routed; do NOT silently strip the evidence.
50
50
  - Cross-verification mode:
51
- - **Acceptance critic (opt-in)**: when `convergence.critic.enabled=true` (chosen via the okstra-run picker or `--critic`), a reused-worker **acceptance devil's-advocate** pass is dispatched concurrently with the first convergence reverify round to surface candidate acceptance blockers the verifiers may have missed; candidates are verified only after convergence completes. Each candidate is verified **confirm-or-downgrade**: confirmed → an `Acceptance Blockers` row (which, since `accepted` requires zero blockers, moves the verdict to `conditional-accept` / `blocked`); unconfirmed → a `Residual Risk` row (never dropped). See `skills/okstra-convergence/SKILL.md` "Acceptance critic pass (final-verification)".
51
+ - **Acceptance critic (opt-in)**: when `convergence.critic.enabled=true` (chosen via the okstra-run picker or `--critic`), a reused-worker **acceptance devil's-advocate** pass is dispatched concurrently with the first convergence reverify round to surface candidate acceptance blockers the verifiers may have missed; candidates are verified only after convergence completes. Each candidate is verified **confirm-or-downgrade**: confirmed → an `Acceptance Blockers` row (which, since `accepted` requires zero blockers, moves the verdict to `conditional-accept` / `blocked`); unconfirmed → a `Residual Risk` row (never dropped). See `prompts/lead/convergence.md` "Acceptance critic pass (final-verification)".
52
52
  - Non-goals:
53
53
  - proposing unrelated refactors beyond the delivered scope
54
54
  - **source code edits, follow-up bug fixes, or scope expansion** — this run renders a verdict only; defects detected here become inputs to a new `error-analysis` or `implementation-planning` run
@@ -40,9 +40,9 @@
40
40
  - Approval gate (phase-specific addendum to shared authority rule):
41
41
  - The YAML frontmatter `approved: true|false` field is the only authorised approval gate. report-writer always emits `approved: false`. The user clears it either by (a) editing the frontmatter line to `approved: true` directly, or (b) invoking the next phase with `--approve` so the CLI flips the frontmatter on the user's behalf. `okstra_ctl.run._validate_approved_plan` reads this field and refuses entry until it is `true`.
42
42
  - Cross-verification mode:
43
- - Phase 5.5 finding convergence runs in **adversarial mode** for this phase (`convergence.adversarial=true`). Verifiers actively try to refute each worker finding (requirement gap / risk / option) by re-inspecting its cited evidence; the burden of proof sits on the claim. See `skills/okstra-convergence/SKILL.md` §"Adversarial Verification Mode".
44
- - §5.5.9 plan-body verification runs with an **adversarial posture** (`skills/okstra-convergence/SKILL.md` §"Adversarial plan-body posture"): verifiers open and confirm every cited path / command and put the burden of proof on the plan. The gate threshold is unchanged — a *majority* `DISAGREE` (`majority-disagree`) is still required to block approval; a single dissent does not.
45
- - **Coverage critic (opt-in)**: when `convergence.critic.enabled=true` (chosen via the okstra-run picker or `--critic`), a reused-worker critic pass is dispatched concurrently with the first convergence reverify round to surface missed findings; its gaps are merged only after a 1-round adversarial reverify that follows convergence. See `skills/okstra-convergence/SKILL.md` "Coverage critic pass".
43
+ - Phase 5.5 finding convergence runs in **adversarial mode** for this phase (`convergence.adversarial=true`). Verifiers actively try to refute each worker finding (requirement gap / risk / option) by re-inspecting its cited evidence; the burden of proof sits on the claim. See `prompts/lead/convergence.md` §"Adversarial Verification Mode".
44
+ - §5.5.9 plan-body verification runs with an **adversarial posture** (`prompts/lead/convergence.md` §"Adversarial plan-body posture"): verifiers open and confirm every cited path / command and put the burden of proof on the plan. The gate threshold is unchanged — a *majority* `DISAGREE` (`majority-disagree`) is still required to block approval; a single dissent does not.
45
+ - **Coverage critic (opt-in)**: when `convergence.critic.enabled=true` (chosen via the okstra-run picker or `--critic`), a reused-worker critic pass is dispatched concurrently with the first convergence reverify round to surface missed findings; its gaps are merged only after a 1-round adversarial reverify that follows convergence. See `prompts/lead/convergence.md` "Coverage critic pass".
46
46
  - Non-goals:
47
47
  - code-level micro-optimization unless it changes the implementation approach
48
48
  - **source code edits of any kind** — this run produces a plan document only; Edit/Write on project source files is forbidden until the plan is approved and a separate `implementation` run starts
@@ -90,7 +90,7 @@
90
90
  - the YAML frontmatter MUST include the line `implementation-option:` directly under `approved:` (report-writer always emits it with an **empty value**). The user selects which Option Candidate the next `implementation` run executes by filling this line with that option's name (manual edit or `--implementation-option <name>` CLI). When left empty, the `implementation` run falls back to the `Recommended Option`.
91
91
  - **the frontmatter `approved: false` line is rendered unconditionally; if the plan-body verification gate (§5.5.9) returns `blocked-by-disagreement` or `aborted-non-result`, the writer MUST keep `approved: false` and the validator refuses any report that ships with `approved: true` under such a gate result.**
92
92
  - every ambiguity flagged during pre-planning that the user must resolve before approval registered as a `Blocks=approval` row in the `## 1. Clarification Items` table (do NOT create a separate `Open Questions` block under the implementation plan body — the unified table is the single home)
93
- - **§5.5.9 Plan Body Verification (BLOCKING).** After report-writer finishes the draft, the lead MUST run a worker peer-review round on the consolidated plan body (Option Candidates / Trade-off Matrix / Recommended Option / Stage Map and per-stage sections / Dependency / Validation Checklist / Rollback / Requirement Coverage) and populate `### 5.5.9 Plan Body Verification` in the final report. The round protocol, plan-item ID scheme (`P-Opt-*` / `P-Step-*` / `P-Dep-*` / `P-Val-*` / `P-Rb-*`), verdict semantics, gate-result classification, and dissent log format are defined in `skills/okstra-convergence/SKILL.md` "Plan-body verification mode". The four gate-result values are `passed`, `passed-with-dissent`, `blocked-by-disagreement`, `aborted-non-result`. When the gate would have been `blocked-by-disagreement` or `aborted-non-result`, the lead MUST NOT silently flip it to one of the passing values to "unblock" the run — that is a contract violation. When `convergence.adversarial=true` (the default for this phase), this round uses the adversarial posture — verifiers confirm cited paths/commands and the burden of proof is on the plan — but the gate threshold stays `majority-disagree` (see that skill's §"Adversarial plan-body posture").
93
+ - **§5.5.9 Plan Body Verification (BLOCKING).** After report-writer finishes the draft, the lead MUST run a worker peer-review round on the consolidated plan body (Option Candidates / Trade-off Matrix / Recommended Option / Stage Map and per-stage sections / Dependency / Validation Checklist / Rollback / Requirement Coverage) and populate `### 5.5.9 Plan Body Verification` in the final report. The round protocol, plan-item ID scheme (`P-Opt-*` / `P-Step-*` / `P-Dep-*` / `P-Val-*` / `P-Rb-*`), verdict semantics, gate-result classification, and dissent log format are defined in `prompts/lead/convergence.md` "Plan-body verification mode". The four gate-result values are `passed`, `passed-with-dissent`, `blocked-by-disagreement`, `aborted-non-result`. When the gate would have been `blocked-by-disagreement` or `aborted-non-result`, the lead MUST NOT silently flip it to one of the passing values to "unblock" the run — that is a contract violation. When `convergence.adversarial=true` (the default for this phase), this round uses the adversarial posture — verifiers confirm cited paths/commands and the burden of proof is on the plan — but the gate threshold stays `majority-disagree` (see that skill's §"Adversarial plan-body posture").
94
94
  - **Decision-record evaluation (sole owner)**: this phase is the **single owner** of decision-record evaluation in the okstra lifecycle. The brief never evaluates or drafts decision records — it only forwards `adr-candidate:*` signals. Every `adr-candidate:*` entry inherited from the brief's `Open Questions` is a mandatory evaluation target. In addition, evaluate every decision the recommended option introduces against the three criteria:
95
95
  1. **Hard to reverse** — would changing the decision later cost meaningfully more than deciding now?
96
96
  2. **Surprising without context** — would a future reader, seeing only the code, wonder "why was it built this way?"?
@@ -52,8 +52,8 @@
52
52
  - **Codebase-first ambiguity resolution (defect rule)**: any ambiguity that can be answered by `Read` / `Grep` / file inspection MUST be resolved that way and recorded with file:line evidence. Writing a clarification row for something the codebase already answers is a defect of this phase.
53
53
  - **Evidence note required inside `Statement`**: every clarification row includes `Evidence checked: <path:line>` or `Evidence checked: none — <human-only reason>` in the `Statement` cell. `none` is allowed ONLY when the row's nature is "only a human can answer this" (reporter intent, business priority, external authority). A row with `none` that *could* have been answered by the codebase is a defect.
54
54
  - Cross-verification mode:
55
- - Phase 5.5 convergence runs in **adversarial mode** for this phase (`convergence.adversarial=true`). Verifiers actively try to refute each worker's finding by directly re-inspecting the cited evidence; the burden of proof sits on the claim. See `skills/okstra-convergence/SKILL.md` §"Adversarial Verification Mode". A single evidence-backed refutation prevents a finding from reaching consensus.
56
- - **Coverage critic (opt-in)**: when `convergence.critic.enabled=true` (chosen via the okstra-run picker or `--critic`), a reused-worker critic pass is dispatched concurrently with the first convergence reverify round to surface missed findings; its gaps are merged only after a 1-round adversarial reverify that follows convergence. See `skills/okstra-convergence/SKILL.md` "Coverage critic pass".
55
+ - Phase 5.5 convergence runs in **adversarial mode** for this phase (`convergence.adversarial=true`). Verifiers actively try to refute each worker's finding by directly re-inspecting the cited evidence; the burden of proof sits on the claim. See `prompts/lead/convergence.md` §"Adversarial Verification Mode". A single evidence-backed refutation prevents a finding from reaching consensus.
56
+ - **Coverage critic (opt-in)**: when `convergence.critic.enabled=true` (chosen via the okstra-run picker or `--critic`), a reused-worker critic pass is dispatched concurrently with the first convergence reverify round to surface missed findings; its gaps are merged only after a 1-round adversarial reverify that follows convergence. See `prompts/lead/convergence.md` "Coverage critic pass".
57
57
  - Non-goals:
58
58
  - full implementation design unless it is required to decide the next phase
59
59
  - **source code edits, plan authoring, builds, or deployments** — this run only classifies the work and routes it; deeper analysis and planning belong to subsequent phases
@@ -21,6 +21,7 @@ from typing import Any, Mapping, Sequence
21
21
  from .lead_events import LeadEvent, append_lead_event
22
22
  from .wrapper_status import status_path_for_prompt
23
23
  from .paths import task_dir
24
+ from .worker_prompt_headers import WorkerPromptHeaderError, worker_prompt_headers
24
25
 
25
26
 
26
27
  SUPPORTED_CLI_WORKERS = {
@@ -32,7 +33,6 @@ BACKEND_MIXED = "mixed"
32
33
  MAX_WORKER_ATTEMPTS = 2
33
34
  REPORT_WRITER_WORKER_ID = "report-writer"
34
35
  REPORT_LANGUAGE_VALUES = {"en", "ko", "auto"}
35
- WORKER_PREAMBLE_PATH = Path.home() / ".okstra" / "templates" / "worker-prompt-preamble.md"
36
36
  ANALYSIS_WORKER_LABELS = {
37
37
  "codex": "Codex worker",
38
38
  "gemini": "Gemini worker",
@@ -676,19 +676,17 @@ def _base_prompt_headers(
676
676
  prompt_rel: str,
677
677
  result_rel: str,
678
678
  ) -> list[str]:
679
- prompt_path = _resolve_project_path(project_root, prompt_rel)
680
- return [
681
- f"**Project Root:** {project_root}",
682
- f"**Prompt History Path:** {prompt_rel}",
683
- f"**Result Path:** {result_rel}",
684
- f"Assigned worker prompt history path: {prompt_path}",
685
- f"**Worker Preamble Path:** {WORKER_PREAMBLE_PATH}",
686
- f"**Errors log path:** {_errors_log_path(project_root, manifest, active_context)}",
687
- (
688
- f"**Errors sidecar path:** "
689
- f"{_worker_errors_sidecar_path(project_root, manifest, active_context, worker_id)}"
690
- ),
691
- ]
679
+ try:
680
+ return worker_prompt_headers(
681
+ project_root=project_root,
682
+ prompt_rel=prompt_rel,
683
+ result_rel=result_rel,
684
+ worker_id=worker_id,
685
+ manifest=manifest,
686
+ active_context=active_context,
687
+ )
688
+ except WorkerPromptHeaderError as exc:
689
+ raise DispatchError(str(exc)) from exc
692
690
 
693
691
 
694
692
  def _analysis_input_lines(
@@ -814,53 +812,6 @@ def _implementation_executor_tail(
814
812
  return preflight_path.read_text(encoding="utf-8")
815
813
 
816
814
 
817
- def _errors_log_path(
818
- project_root: Path,
819
- manifest: Mapping[str, Any],
820
- active_context: Mapping[str, Any],
821
- ) -> Path:
822
- error_logs = active_context.get("errorLogs")
823
- if isinstance(error_logs, Mapping):
824
- value = _string_value(error_logs.get("runErrorsLogPath"))
825
- if value:
826
- return _resolve_project_path(project_root, value)
827
- run_dir = _run_directory_path(project_root, manifest)
828
- return run_dir / "logs" / f"errors-{_require_string(manifest, 'taskType')}-{_sequence(manifest, 'state')}.jsonl"
829
-
830
-
831
- def _worker_errors_sidecar_path(
832
- project_root: Path,
833
- manifest: Mapping[str, Any],
834
- active_context: Mapping[str, Any],
835
- worker_id: str,
836
- ) -> Path:
837
- error_logs = active_context.get("errorLogs")
838
- if isinstance(error_logs, Mapping):
839
- sidecars = error_logs.get("sidecarsByWorkerId")
840
- if isinstance(sidecars, Mapping):
841
- value = _string_value(sidecars.get(worker_id))
842
- if value:
843
- return _resolve_project_path(project_root, value)
844
- run_dir = _run_directory_path(project_root, manifest)
845
- task_type = _require_string(manifest, "taskType")
846
- seq = _sequence(manifest, "workerResults")
847
- return run_dir / "worker-results" / f"{worker_id}-worker-errors-{task_type}-{seq}.json"
848
-
849
-
850
- def _run_directory_path(project_root: Path, manifest: Mapping[str, Any]) -> Path:
851
- value = _string_value(manifest.get("runDirectoryPath"))
852
- if value:
853
- return _resolve_project_path(project_root, value)
854
- return _resolve_required_path(project_root, manifest, "teamStatePath", "team-state").parent.parent
855
-
856
-
857
- def _sequence(manifest: Mapping[str, Any], key: str) -> str:
858
- seqs = manifest.get("runSequencesByCategory")
859
- if not isinstance(seqs, Mapping):
860
- return _run_seq(manifest)
861
- return _string_value(seqs.get(key)) or _run_seq(manifest)
862
-
863
-
864
815
  def _project_relative_path(project_root: Path, path: Path) -> str:
865
816
  try:
866
817
  return path.relative_to(project_root).as_posix()
@@ -23,15 +23,17 @@ INPUT_FILES = (
23
23
  "reference-expectations.md",
24
24
  "clarification-response.md",
25
25
  )
26
- # Per-run hot-path instruction assets the lead/workers load OUTSIDE the task
27
- # bundle: the lifecycle skills (Phase 1 / 2-5 / 5.5 / 6-7) plus the worker
26
+ # Per-run hot-path instruction assets the lead loads OUTSIDE the task bundle:
27
+ # the lifecycle lead contracts (Phase 1 / 2-5 / 5.5 / 6-7) plus the worker
28
28
  # agent specs. These are the prompt-diet (perf plan v2 P2) targets; the
29
29
  # bundle-local metrics below cannot see them, so they get their own metric.
30
- HOT_PATH_SKILLS = (
31
- "okstra-context-loader",
32
- "okstra-team-contract",
33
- "okstra-convergence",
34
- "okstra-report-writer",
30
+ # The contracts ship as runtime resources under ~/.okstra/prompts/lead/, not
31
+ # as agent skills.
32
+ HOT_PATH_LEAD_RESOURCES = (
33
+ "context-loader",
34
+ "team-contract",
35
+ "convergence",
36
+ "report-writer",
35
37
  )
36
38
  WORKER_AGENT_FILES = (
37
39
  "claude-worker.md",
@@ -178,13 +180,14 @@ def _skill_assets_metric() -> dict:
178
180
  skill bodies + worker agent specs. These dominate the fixed per-run
179
181
  instruction footprint and are the prompt-diet ranking input."""
180
182
  entries = []
183
+ okstra_home = Path.home() / ".okstra"
181
184
  claude_home = Path.home() / ".claude"
182
- for name in HOT_PATH_SKILLS:
185
+ for name in HOT_PATH_LEAD_RESOURCES:
183
186
  path = _installed_or_dev(
184
- claude_home / "skills" / name / "SKILL.md",
185
- f"skills/{name}/SKILL.md",
187
+ okstra_home / "prompts" / "lead" / f"{name}.md",
188
+ f"prompts/lead/{name}.md",
186
189
  )
187
- entries.append((f"skill:{name}", path))
190
+ entries.append((f"resource:{name}", path))
188
191
  for fname in WORKER_AGENT_FILES:
189
192
  path = _installed_or_dev(
190
193
  claude_home / "agents" / fname,
@@ -12,6 +12,7 @@ from typing import Any, Mapping, Sequence
12
12
 
13
13
  from . import tmux
14
14
  from .lead_events import LeadEvent, append_lead_event
15
+ from .worker_prompt_headers import WorkerPromptHeaderError, worker_prompt_headers
15
16
  from .wrapper_status import read_wrapper_status, status_path_for_prompt
16
17
 
17
18
 
@@ -290,9 +291,19 @@ def _job_from_roster_worker(
290
291
  state = _worker_state(team_state, worker_id)
291
292
  provider = _provider_for_worker(worker_id)
292
293
  model = _model_for_worker(worker_id, state, report_writer_model)
293
- prompt_path = _resolve_project_path(project_root, _worker_prompt_path(manifest, worker_id))
294
- result_path = _resolve_project_path(project_root, _require_string(state, "resultPath"))
295
- _materialize_prompt_if_missing(prompt_path, worker_id, result_path, project_root)
294
+ prompt_rel = _worker_prompt_path(manifest, worker_id)
295
+ result_rel = _require_string(state, "resultPath")
296
+ prompt_path = _resolve_project_path(project_root, prompt_rel)
297
+ result_path = _resolve_project_path(project_root, result_rel)
298
+ _materialize_prompt_if_missing(
299
+ prompt_path=prompt_path,
300
+ prompt_rel=prompt_rel,
301
+ worker_id=worker_id,
302
+ result_rel=result_rel,
303
+ project_root=project_root,
304
+ manifest=manifest,
305
+ active_context=active_context,
306
+ )
296
307
  return WorkerJob(
297
308
  worker_id=worker_id,
298
309
  provider=provider,
@@ -747,19 +758,31 @@ def _resolve_wrapper(provider: str, workspace_root: Path, options: _BuildOptions
747
758
  raise DispatchError(f"{script} not found (searched: {', '.join(str(c) for c in candidates)})")
748
759
 
749
760
 
750
- def _materialize_prompt_if_missing(prompt_path: Path, worker_id: str, result_path: Path, project_root: Path) -> None:
761
+ def _materialize_prompt_if_missing(
762
+ *,
763
+ prompt_path: Path,
764
+ prompt_rel: str,
765
+ worker_id: str,
766
+ result_rel: str,
767
+ project_root: Path,
768
+ manifest: Mapping[str, Any],
769
+ active_context: Mapping[str, Any],
770
+ ) -> None:
751
771
  if prompt_path.is_file():
752
772
  return
753
773
  prompt_path.parent.mkdir(parents=True, exist_ok=True)
754
- prompt_path.write_text(
755
- "\n".join([
756
- f"# {worker_id} worker prompt",
757
- f"**Project Root:** {project_root}",
758
- f"**Result Path:** {result_path}",
759
- "",
760
- ]),
761
- encoding="utf-8",
762
- )
774
+ try:
775
+ headers = worker_prompt_headers(
776
+ project_root=project_root,
777
+ prompt_rel=prompt_rel,
778
+ result_rel=result_rel,
779
+ worker_id=worker_id,
780
+ manifest=manifest,
781
+ active_context=active_context,
782
+ )
783
+ except WorkerPromptHeaderError as exc:
784
+ raise DispatchError(str(exc)) from exc
785
+ prompt_path.write_text("\n".join([*headers, ""]), encoding="utf-8")
763
786
 
764
787
 
765
788
  def _provider_for_worker(worker_id: str) -> str:
@@ -33,9 +33,15 @@ __all__ = [
33
33
  "next_run_seq",
34
34
  "task_dir",
35
35
  "task_runs_dir",
36
+ "okstra_home",
36
37
  ]
37
38
 
38
39
 
40
+ def okstra_home() -> Path:
41
+ raw = os.environ.get("OKSTRA_HOME", "").strip()
42
+ return Path(raw).expanduser() if raw else Path.home() / ".okstra"
43
+
44
+
39
45
  def task_dir(project_root: Path, task_group: str, task_id: str) -> Path:
40
46
  """task root 경로: ``<project>/.okstra/tasks/<group-seg>/<id-seg>``.
41
47
 
@@ -188,6 +194,8 @@ def compute_run_paths(
188
194
  report_writer_worker_result = worker_results / f"report-writer-worker{suffixes['worker_results']}.md"
189
195
 
190
196
  run_errors_log = run_logs / f"errors-{task_type_segment}-{seqs['state']}.jsonl"
197
+ runtime_home = okstra_home()
198
+
191
199
  claude_worker_errors_sidecar = worker_results / f"claude-worker-errors{suffixes['worker_results']}.json"
192
200
  codex_worker_errors_sidecar = worker_results / f"codex-worker-errors{suffixes['worker_results']}.json"
193
201
  gemini_worker_errors_sidecar = worker_results / f"gemini-worker-errors{suffixes['worker_results']}.json"
@@ -209,7 +217,25 @@ def compute_run_paths(
209
217
  "OKSTRA_ROOT": str(okstra_root),
210
218
  "OKSTRA_TASKS_ROOT": str(tasks_root),
211
219
  "WORKER_PROMPT_PREAMBLE_PATH": str(
212
- Path.home() / ".okstra" / "templates" / "worker-prompt-preamble.md"
220
+ runtime_home / "templates" / "worker-prompt-preamble.md"
221
+ ),
222
+ "OKSTRA_LEAD_CONTRACT_PATH": str(
223
+ runtime_home / "prompts" / "lead" / "okstra-lead-contract.md"
224
+ ),
225
+ "OKSTRA_CONTEXT_LOADER_PATH": str(
226
+ runtime_home / "prompts" / "lead" / "context-loader.md"
227
+ ),
228
+ "OKSTRA_TEAM_CONTRACT_PATH": str(
229
+ runtime_home / "prompts" / "lead" / "team-contract.md"
230
+ ),
231
+ "OKSTRA_CONVERGENCE_PATH": str(
232
+ runtime_home / "prompts" / "lead" / "convergence.md"
233
+ ),
234
+ "OKSTRA_REPORT_WRITER_PATH": str(
235
+ runtime_home / "prompts" / "lead" / "report-writer.md"
236
+ ),
237
+ "OKSTRA_CODING_PREFLIGHT_DIR": str(
238
+ runtime_home / "prompts" / "coding-preflight"
213
239
  ),
214
240
  "OKSTRA_DISCOVERY_DIR": str(discovery_dir),
215
241
  "TASK_ROOT": str(task_root),
@@ -28,6 +28,7 @@ from okstra_project.dirs import OKSTRA_DIR_NAME, project_json_path
28
28
  # render_task_manifest 가 동일한 리스트/딕셔너리를 로컬에 중복 정의했는데,
29
29
  # 이는 silent drift 위험이 있어 SSOT import 로 통합한다.
30
30
  from . import fix_cycles
31
+ from .paths import okstra_home
31
32
  from .lead_runtime import lead_runtime_info
32
33
  from .workflow import DEFAULT_NEXT_PHASE, PHASE_SEQUENCE
33
34
 
@@ -369,6 +370,14 @@ def _active_error_logs(ctx: dict) -> dict:
369
370
  }
370
371
 
371
372
 
373
+ def _active_runtime_resources(ctx: dict) -> dict:
374
+ runtime_home = okstra_home()
375
+ return {
376
+ "codingPreflightDir": ctx.get("OKSTRA_CODING_PREFLIGHT_DIR")
377
+ or str(runtime_home / "prompts" / "coding-preflight"),
378
+ }
379
+
380
+
372
381
  def _active_executor_worktree(ctx: dict) -> dict:
373
382
  return {
374
383
  "status": ctx.get("EXECUTOR_WORKTREE_STATUS", ""),
@@ -419,6 +428,7 @@ def render_active_run_context(active_context_path: str, ctx: dict) -> None:
419
428
  "instructionSet": _active_instruction_set(ctx),
420
429
  "workers": _active_workers(ctx),
421
430
  "errorLogs": _active_error_logs(ctx),
431
+ "runtimeResources": _active_runtime_resources(ctx),
422
432
  "executorWorktree": _active_executor_worktree(ctx),
423
433
  "sourceArtifacts": _active_source_artifacts(ctx),
424
434
  "lazyReadPlan": _active_lazy_read_plan(),
@@ -1104,7 +1114,7 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
1104
1114
  def _build_convergence_block(ctx: dict) -> dict:
1105
1115
  """Resolve the `convergence` sub-tree written into task-manifest.json.
1106
1116
 
1107
- Defaults follow `skills/okstra-convergence/SKILL.md`:
1117
+ Defaults follow `prompts/lead/convergence.md`:
1108
1118
  - `enabled` default True
1109
1119
  - `maxRounds` default 1 for `requirements-discovery`, 2 otherwise
1110
1120
  - `verificationMode` default "lightweight"
@@ -1667,6 +1677,35 @@ def inject_lead_prompt_computed_tokens(ctx: dict) -> None:
1667
1677
  lead_agent_label = _lead_agent_label(ctx)
1668
1678
  lead_model = ctx.get("LEAD_MODEL", "")
1669
1679
  lead_model_execution = ctx.get("LEAD_MODEL_EXECUTION_VALUE", "")
1680
+ home_prompts = okstra_home() / "prompts"
1681
+ lead_contract_path = ctx.get("OKSTRA_LEAD_CONTRACT_PATH") or str(
1682
+ home_prompts / "lead" / "okstra-lead-contract.md"
1683
+ )
1684
+ context_loader_path = ctx.get("OKSTRA_CONTEXT_LOADER_PATH") or str(
1685
+ home_prompts / "lead" / "context-loader.md"
1686
+ )
1687
+ team_contract_path = ctx.get("OKSTRA_TEAM_CONTRACT_PATH") or str(
1688
+ home_prompts / "lead" / "team-contract.md"
1689
+ )
1690
+ convergence_path = ctx.get("OKSTRA_CONVERGENCE_PATH") or str(
1691
+ home_prompts / "lead" / "convergence.md"
1692
+ )
1693
+ report_writer_path = ctx.get("OKSTRA_REPORT_WRITER_PATH") or str(
1694
+ home_prompts / "lead" / "report-writer.md"
1695
+ )
1696
+ coding_preflight_dir = ctx.get("OKSTRA_CODING_PREFLIGHT_DIR") or str(
1697
+ home_prompts / "coding-preflight"
1698
+ )
1699
+ okstra_runtime_resources_block = (
1700
+ "## Okstra Runtime Resources\n"
1701
+ "\n"
1702
+ f"- Lead contract: `{lead_contract_path}`\n"
1703
+ f"- Context loader: `{context_loader_path}`\n"
1704
+ f"- Team contract: `{team_contract_path}`\n"
1705
+ f"- Convergence contract: `{convergence_path}`\n"
1706
+ f"- Report writer contract: `{report_writer_path}`\n"
1707
+ f"- Coding preflight pack: `{coding_preflight_dir}`"
1708
+ )
1670
1709
 
1671
1710
  def fmt_assignment(role: str, model: str, execution: str) -> str:
1672
1711
  if execution and execution != model:
@@ -1852,7 +1891,7 @@ def inject_lead_prompt_computed_tokens(ctx: dict) -> None:
1852
1891
  "## Team Creation Gate (BLOCKING)\n"
1853
1892
  "\n"
1854
1893
  "Before any `Agent` dispatch for workers, you MUST perform Phase 3 of the\n"
1855
- '`okstra` skill (`agents/SKILL.md` "Phase 3 Team creation"). Skipping\n'
1894
+ f'okstra lead contract (`{lead_contract_path}` \u2192 "Phase 3 \u2014 Team creation"). Skipping\n'
1856
1895
  "this gate silently degrades the run to in-process background dispatch and\n"
1857
1896
  "loses the Teams split-pane observability surface, even though worker\n"
1858
1897
  "outputs may still appear correct on disk.\n"
@@ -1861,7 +1900,7 @@ def inject_lead_prompt_computed_tokens(ctx: dict) -> None:
1861
1900
  "for this run (roster comes from `resultContract.requiredWorkerRoles` in\n"
1862
1901
  "`task-manifest.json` — it may be 1, 2, 3, or more workers):\n"
1863
1902
  "\n"
1864
- "1. Invoke the `okstra-team-contract` skill and verify the selected worker\n"
1903
+ f"1. Read the team-contract resource (`{team_contract_path}`) and verify the selected worker\n"
1865
1904
  " roster against `task-manifest.json`'s `resultContract.requiredWorkerRoles`.\n"
1866
1905
  f'2. Call `TeamCreate(team_name: "{team_name}", description: ...)`.\n'
1867
1906
  "3. Record the outcome in team-state under\n"
@@ -1943,8 +1982,10 @@ def inject_lead_prompt_computed_tokens(ctx: dict) -> None:
1943
1982
  else:
1944
1983
  lead_intro_line = f"You are `{lead_role}` for project `{ctx.get('PROJECT_ID', '')}`."
1945
1984
  lead_bootstrap_instruction = (
1946
- "Invoke the `okstra` skill now. Read the manifests below for all task "
1947
- "metadata, paths, model assignments, and worker roster."
1985
+ f"Read the Okstra lead contract at `{lead_contract_path}` now, then read "
1986
+ "the manifests below for all task metadata, paths, model assignments, and "
1987
+ "worker roster. Follow the contract's lazy-read rules for support resources; "
1988
+ "do not invoke hidden/internal skills."
1948
1989
  )
1949
1990
  lead_session_block = (
1950
1991
  "## Session\n"
@@ -1957,9 +1998,9 @@ def inject_lead_prompt_computed_tokens(ctx: dict) -> None:
1957
1998
  "\n"
1958
1999
  "- The `runs/<phase>/prompts/<worker>-worker-prompt-*.md` files referenced in `task-manifest.json → artifacts.workerPromptPathByWorkerId` are **NOT** rendered by the okstra runtime. Those paths are the *assigned* locations where the prompt history MUST be written at dispatch time.\n"
1959
2000
  "- Therefore: at the start of every phase the prompts/ directory is normally empty (or contains only previously-dispatched workers' files). This is expected. Do NOT narrate it as \"missing\", \"누락\", or \"not yet rendered\" — it just means dispatch has not happened yet.\n"
1960
- "- Before dispatching any required worker, **you (the lead) construct the worker prompt and persist it to the assigned absolute path using `Write`** (per `okstra-team-contract` rule 6). Only after persisting do you call the worker subagent (Agent tool / Codex / Gemini wrapper). The wrapper subagents will also re-write the same file on their end; the double-write is intentional and idempotent.\n"
2001
+ "- Before dispatching any required worker, **you (the lead) construct the worker prompt and persist it to the assigned absolute path using `Write`** (per `team-contract` rule 6). Only after persisting do you call the worker subagent (Agent tool / Codex / Gemini wrapper). The wrapper subagents will also re-write the same file on their end; the double-write is intentional and idempotent.\n"
1961
2002
  "- Do not \"check if the file exists and skip dispatch\" — file presence is not a signal to skip. Worker selection and skipping rules come from team-state, never from prompts/ directory contents.\n"
1962
- f"- **Worker Preamble Path (single anchor — replaces inlined `[Required reading]` and `[Error reporting]` blocks).** Every worker prompt you construct MUST include the anchor header `**Worker Preamble Path:** {ctx.get('WORKER_PROMPT_PREAMBLE_PATH', str(Path.home() / '.okstra' / 'templates' / 'worker-prompt-preamble.md'))}`. The file at that path is the canonical SSOT for Required Reading + Error Reporting + Output sections; workers Read it end-to-end before producing output. Do NOT re-inline those blocks into worker prompt bodies — that is the legacy ~80-line dispatch boilerplate that this anchor is designed to replace."
2003
+ f"- **Worker Preamble Path (single anchor — replaces inlined `[Required reading]` and `[Error reporting]` blocks).** Every worker prompt you construct MUST include the anchor header `**Worker Preamble Path:** {ctx.get('WORKER_PROMPT_PREAMBLE_PATH', str(okstra_home() / 'templates' / 'worker-prompt-preamble.md'))}`. The file at that path is the canonical SSOT for Required Reading + Error Reporting + Output sections; workers Read it end-to-end before producing output. Do NOT re-inline those blocks into worker prompt bodies — that is the legacy ~80-line dispatch boilerplate that this anchor is designed to replace."
1963
2004
  )
1964
2005
 
1965
2006
  # Compute results (deterministic from ctx, 덮어쓰기)
@@ -1968,6 +2009,7 @@ def inject_lead_prompt_computed_tokens(ctx: dict) -> None:
1968
2009
  ctx["LEAD_SESSION_BLOCK"] = lead_session_block
1969
2010
  ctx["TEAM_CREATION_GATE"] = team_creation_gate_block
1970
2011
  ctx["WORKER_DISPATCH_GUIDANCE"] = worker_dispatch_guidance
2012
+ ctx["OKSTRA_RUNTIME_RESOURCES"] = okstra_runtime_resources_block
1971
2013
  ctx["WORKER_RESULT_PATH_LINES"] = "\n".join(worker_result_lines)
1972
2014
  ctx["MODEL_ASSIGNMENT_LINES"] = "\n".join(model_assignment_lines)
1973
2015
  ctx["TEAM_ROLE_LINES"] = "\n".join(team_role_lines)
@@ -2001,9 +2043,21 @@ def apply_lead_prompt_defaults(ctx: dict) -> None:
2001
2043
  # Empty for non-final-verification runs; the final-verification prepare
2002
2044
  # path overwrites it with the resolved verification target block.
2003
2045
  ctx.setdefault("VERIFICATION_TARGET", "")
2046
+ runtime_home = okstra_home()
2004
2047
  ctx.setdefault(
2005
2048
  "WORKER_PROMPT_PREAMBLE_PATH",
2006
- str(Path.home() / ".okstra" / "templates" / "worker-prompt-preamble.md"),
2049
+ str(runtime_home / "templates" / "worker-prompt-preamble.md"),
2050
+ )
2051
+ # Lead resource paths the launch template references directly. paths.py
2052
+ # seeds the production values; setdefault backfills callers that render the
2053
+ # template without the full path ctx (mirrors WORKER_PROMPT_PREAMBLE_PATH).
2054
+ ctx.setdefault(
2055
+ "OKSTRA_LEAD_CONTRACT_PATH",
2056
+ str(runtime_home / "prompts" / "lead" / "okstra-lead-contract.md"),
2057
+ )
2058
+ ctx.setdefault(
2059
+ "OKSTRA_TEAM_CONTRACT_PATH",
2060
+ str(runtime_home / "prompts" / "lead" / "team-contract.md"),
2007
2061
  )
2008
2062
  if "AVAILABLE_MCP_SERVERS" not in ctx:
2009
2063
  ctx["AVAILABLE_MCP_SERVERS"] = build_available_mcp_servers_block(
@@ -1067,7 +1067,7 @@ class _ResolvedAssets:
1067
1067
  prompt_template: Path
1068
1068
  task_index_template: Path
1069
1069
  final_report_template: Path
1070
- source_skill: Path
1070
+ lead_contract: Path
1071
1071
  run_validator: Path
1072
1072
 
1073
1073
 
@@ -1090,19 +1090,19 @@ def _resolve_runtime_assets(workspace_root: Path, inp: PrepareInputs) -> _Resolv
1090
1090
  final_report_template = (
1091
1091
  workspace_root / "templates" / "reports" / "final-report.template.md"
1092
1092
  )
1093
- source_skill = workspace_root / "agents" / "SKILL.md"
1093
+ lead_contract = workspace_root / "prompts" / "lead" / "okstra-lead-contract.md"
1094
1094
  run_validator = workspace_root / "validators" / "validate-run.py"
1095
- for required in (task_index_template, final_report_template, run_validator, source_skill):
1095
+ for required in (task_index_template, final_report_template, run_validator, lead_contract):
1096
1096
  if not required.is_file():
1097
1097
  raise PrepareError(
1098
- f"required okstra template or source skill missing: {required}.{_INSTALL_HINT}"
1098
+ f"required okstra template or lead contract missing: {required}.{_INSTALL_HINT}"
1099
1099
  )
1100
1100
  return _ResolvedAssets(
1101
1101
  profile_file=profile_file,
1102
1102
  prompt_template=prompt_template,
1103
1103
  task_index_template=task_index_template,
1104
1104
  final_report_template=final_report_template,
1105
- source_skill=source_skill,
1105
+ lead_contract=lead_contract,
1106
1106
  run_validator=run_validator,
1107
1107
  )
1108
1108