okstra 0.173.0 → 0.174.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 (62) hide show
  1. package/docs/architecture/storage-model.md +13 -3
  2. package/docs/architecture.md +5 -21
  3. package/docs/cli.md +3 -2
  4. package/docs/container.md +1 -1
  5. package/docs/contributor-change-matrix.md +1 -1
  6. package/docs/project-structure-overview.md +13 -13
  7. package/docs/task-process/README.md +1 -1
  8. package/docs/task-process/implementation-planning.md +1 -1
  9. package/package.json +1 -1
  10. package/runtime/BUILD.json +2 -2
  11. package/runtime/agents/workers/claude-worker.md +1 -1
  12. package/runtime/bin/lib/okstra/globals.sh +1 -1
  13. package/runtime/bin/okstra-provider-exec.py +29 -12
  14. package/runtime/bin/okstra-trace-cleanup.sh +58 -129
  15. package/runtime/prompts/lead/adapters/cmux.md +2 -0
  16. package/runtime/prompts/lead/okstra-lead-contract.md +1 -1
  17. package/runtime/prompts/lead/plan-body-verification.md +3 -3
  18. package/runtime/prompts/lead/report-writer.md +6 -6
  19. package/runtime/prompts/profiles/_common-contract.md +2 -2
  20. package/runtime/prompts/profiles/_implementation-executor.md +2 -0
  21. package/runtime/prompts/profiles/_implementation-verifier.md +2 -2
  22. package/runtime/prompts/profiles/error-analysis.md +1 -1
  23. package/runtime/prompts/profiles/implementation-planning.md +12 -9
  24. package/runtime/prompts/profiles/implementation.md +2 -1
  25. package/runtime/prompts/profiles/release-handoff.md +1 -1
  26. package/runtime/python/okstra_ctl/adapters/dispatch/__init__.py +1 -6
  27. package/runtime/python/okstra_ctl/adapters/hosts/external/relay.md +4 -4
  28. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +5 -0
  29. package/runtime/python/okstra_ctl/conformance.py +68 -0
  30. package/runtime/python/okstra_ctl/dispatch_core.py +89 -39
  31. package/runtime/python/okstra_ctl/dispatch_state.py +142 -14
  32. package/runtime/python/okstra_ctl/doctor.py +2 -2
  33. package/runtime/python/okstra_ctl/domain/worker_exec.py +5 -0
  34. package/runtime/python/okstra_ctl/final_report_schema.py +5 -4
  35. package/runtime/python/okstra_ctl/pane_reclaim.py +13 -22
  36. package/runtime/python/okstra_ctl/render_final_report.py +15 -19
  37. package/runtime/python/okstra_ctl/report_contract.py +0 -1
  38. package/runtime/python/okstra_ctl/report_finalize.py +68 -9
  39. package/runtime/python/okstra_ctl/run.py +43 -2
  40. package/runtime/python/okstra_ctl/schema_excerpt.py +1 -1
  41. package/runtime/python/okstra_ctl/scope_provenance.py +1 -1
  42. package/runtime/python/okstra_ctl/session.py +69 -12
  43. package/runtime/python/okstra_ctl/team.py +51 -25
  44. package/runtime/python/okstra_ctl/tmux.py +19 -149
  45. package/runtime/python/okstra_ctl/worker_request.py +2 -0
  46. package/runtime/python/okstra_ctl/worktree.py +69 -3
  47. package/runtime/python/okstra_token_usage/cli.py +1 -1
  48. package/runtime/python/okstra_token_usage/collect.py +66 -6
  49. package/runtime/skills/okstra-setup/references/project-config.md +11 -0
  50. package/runtime/templates/reports/settings.template.json +0 -24
  51. package/runtime/validators/lib/fixtures.sh +49 -17
  52. package/runtime/validators/validate-implementation-plan-stages.py +63 -3
  53. package/runtime/validators/validate-run.py +14 -473
  54. package/runtime/validators/validate_session_conformance.py +1 -1
  55. package/src/cli-registry.mjs +8 -1
  56. package/src/commands/execute/team.mjs +3 -3
  57. package/src/commands/execute/worktree-status.mjs +109 -0
  58. package/src/commands/lifecycle/install.mjs +0 -2
  59. package/src/commands/report/finalize.mjs +13 -6
  60. package/runtime/bin/okstra-subagent-reclaim.sh +0 -26
  61. package/runtime/schemas/final-report-v1.0.schema.json +0 -6366
  62. package/runtime/templates/reports/final-report.template.md +0 -1258
@@ -60,7 +60,7 @@ The representative files below are all relative to the resolved run directory (`
60
60
 
61
61
  After the host-native lead takes over, the lead and its assigned workers add the following result files to the current run.
62
62
  - `sessions/claude-resume-<task-type>-<seq>.sh`
63
- - `reports/final-report-<task-type>-<seq>.data.json` *(schema v2 source of truth for both audiences; schema v1 remains readable for compatibility)*
63
+ - `reports/final-report-<task-type>-<seq>.data.json` *(schema v2 source of truth for both audiences)*
64
64
  - `reports/final-report-<task-type>-<seq>.md`
65
65
  - `reports/final-report-<task-type>-<seq>.html` *(Phase 7 deterministic post-processing: task-specific, self-contained HTML for human reviewers, with inline CSS/JS)*
66
66
  - `design-prep-requests/design-prep-request-<seq>-PREP-<NNN>.md` *(implementation-planning only: deterministic AI-prepared request for a `provisional` or `blocked` design item)*
@@ -90,7 +90,7 @@ Design-preparation storage has three separate authorities:
90
90
  | `design-prep-inputs/design-prep-input-<seq>-PREP-<NNN>-r<revision>-<uuid>.md` | user or wizard | Append-only and written only after semantic confirmation. Existing revisions are never edited or reserved before confirmation. |
91
91
 
92
92
  The resolver selects the highest unique revision whose assessment fingerprint still matches the approved snapshot. Duplicate revision numbers and stale fingerprints are skipped with warnings rather than granted authority. A markerless legacy plan has no sidecars to migrate: it proceeds with the `legacy-unassessed` warning. This compatibility path does not rewrite its report.
93
- The final result files are not created by saving worker stdout. The report writer writes `final-report-<task-type>-<seq>.data.json`; Phase 7 derives the Markdown and HTML siblings deterministically. New bundles use `schemas/final-report-v2.0.schema.json` and `templates/reports/final-report-v2.template.md`: AI handoff Markdown and task-specific human HTML are rendered independently from the same v2 data.json. Existing schema v1 data uses `schemas/final-report-v1.0.schema.json` and the legacy `templates/reports/final-report.template.md` compatibility path. Rendering never modifies the source data or a pre-existing Markdown input.
93
+ The final result files are not created by saving worker stdout. The report writer writes `final-report-<task-type>-<seq>.data.json`; Phase 7 derives the Markdown and HTML siblings deterministically. Bundles use `schemas/final-report-v2.0.schema.json` and `templates/reports/final-report-v2.template.md`: AI handoff Markdown and task-specific human HTML are rendered independently from the same data.json. Rendering never modifies the source data or a pre-existing Markdown input.
94
94
  For the standalone Claude launcher, `sessions/claude-resume-<task-type>-<seq>.sh` remains an interruption-recovery compatibility helper. In-host Codex runs use host task continuity plus the generic `leadSession` manifest identity.
95
95
 
96
96
  The resolved run directory collects execution history. It divides its contents into type-specific subdirectories such as `manifests/`, `state/`, `prompts/`, `reports/`, `status/`, `sessions/`, and `worker-results/`, then distinguishes each run-level artifact and result file with a `-<task-type>-<seq>` suffix (a three-digit, zero-padded per-category counter, such as `001` or `002`).
@@ -98,7 +98,17 @@ Worker prompt history is retained not under `/tmp`, but always as a canonical ar
98
98
 
99
99
  `state/team-state-<task-type>-<seq>.json` stores invocation-aware dispatch
100
100
  records. Code-owned worker launches record the invocation fields directly in
101
- `workerDispatches[]`. Host-native calls use `agentDispatches[]`, and
101
+ `workerDispatches[]`, including the `sessionId` the dispatch minted for that
102
+ attempt. That id is the only handle on a worker that runs as its own CLI
103
+ process, because such a session tags its transcript with neither an agent nor a
104
+ team name; it is empty for providers whose CLI takes no session-id argument. A
105
+ row is keyed by its dispatch ID rather than appended blindly: a row repeating a
106
+ `dispatchId` already present replaces that row in place, so one dispatch ID is
107
+ recorded at most once and a re-sent dispatch cannot make the run read as a
108
+ duplicate. Rows carrying no dispatch ID have no key to collapse on and still
109
+ stack. Nothing prunes the array and no row is ever dropped, so a replaced row
110
+ takes its `paneId` out of the ledger with it and the pane-reclaim gate no longer
111
+ offers that pane. Host-native calls use `agentDispatches[]`, and
102
112
  `agentResultLinks[]` associates an accepted result path with exactly one
103
113
  dispatch ID. The inverse is also exclusive: one dispatch ID cannot authorize
104
114
  multiple accepted paths. State mutation uses a run-state lock and unique
@@ -135,7 +135,7 @@ Runtime entry points are consolidated in Python packages. Bash and skills only c
135
135
 
136
136
  - `prompts/launch.template.md` — lead prompt template.
137
137
  - `prompts/profiles/*.md` — eleven task-type profiles: the seven lifecycle profiles (`requirements-discovery`, `error-analysis`, `implementation-option-selection`, `implementation-planning`, `implementation`, `final-verification`, `release-handoff`) plus `improvement-discovery`, `project-analysis`, `feature-analysis`, and `change-impact-analysis` sidetracks.
138
- - `templates/project-docs/task-index.template.md` · `templates/reports/final-report.template.md` · `templates/reports/final-report-v2.template.md` · `templates/reports/html/` · `templates/reports/settings.template.json` — runtime render inputs. The unversioned Markdown template is the schema v1 compatibility template; the v2 Markdown and task-specific HTML trees are separate audiences.
138
+ - `templates/project-docs/task-index.template.md` · `templates/reports/final-report-v2.template.md` · `templates/reports/final-report-v2.template.md` · `templates/reports/html/` · `templates/reports/settings.template.json` — runtime render inputs. The unversioned Markdown template is the schema v1 compatibility template; the v2 Markdown and task-specific HTML trees are separate audiences.
139
139
  - `<PROJECT_ROOT>/.okstra/project.json` — project self-registration. Created/verified automatically on the first okstra.sh run; when `--project-root` is omitted, PROJECT_ROOT is resolved through ancestors / `git toplevel`.
140
140
 
141
141
  ### Support assets (not referenced at runtime)
@@ -394,7 +394,7 @@ On the first run, it writes the four fields `projectId`, `projectRoot`, `created
394
394
 
395
395
  `architecture.style` (optional, one of `hexagonal` / `layered` / `none`, default `none`) declares the project's architecture. It is read by [`scripts/okstra_project/resolver.py`](../scripts/okstra_project/resolver.py) `resolve_architecture`, which falls back to `none` on an absent field, an unrecognized value, or an unreadable `project.json`. It is the switch for the second of two enforcement layers.
396
396
 
397
- **Layer 1 — always on, style-agnostic.** Independent of any declaration, every `implementation-planning` plan emits `variationPointAnalysis`: whether the same behavior is served by two or more implementations, and for each such point the interface it is extracted behind (`extractionDecision`) plus the Stage Map stage that builds it. `hasMultipleImplementations: false` is a claim rather than an omission, so it requires a written `noVariationRationale` and an empty `points` array. The recommended option carries `testSeams` — one row per boundary a test injects at and replaces — and each point becomes a `P-Var-<N>` item judged in the plan-body verification gate (§5.5.9), which DISAGREEs when an extraction branches on resource identity instead of extracting the interface the next implementation plugs into (the open/closed shape). **Enforced:** `schemas/final-report-v1.0.schema.json` `$defs.VariationPointAnalysis` pins the shape, [`validators/validate-run.py`](../validators/validate-run.py) `_validate_variation_point_analysis` rejects a rationale-less or points-carrying `false`, a point-less `true`, and an `extract: true` decision with an empty `interfaceKind` or `coveredBy`, and `okstra_ctl.plan_items` emits the `P-Var-*` items.
397
+ **Layer 1 — always on, style-agnostic.** Independent of any declaration, every `implementation-planning` plan emits `variationPointAnalysis`: whether the same behavior is served by two or more implementations, and for each such point the interface it is extracted behind (`extractionDecision`) plus the Stage Map stage that builds it. `hasMultipleImplementations: false` is a claim rather than an omission, so it requires a written `noVariationRationale` and an empty `points` array. The recommended option carries `testSeams` — one row per boundary a test injects at and replaces — and each point becomes a `P-Var-<N>` item judged in the plan-body verification gate (§5.5.9), which DISAGREEs when an extraction branches on resource identity instead of extracting the interface the next implementation plugs into (the open/closed shape). **Enforced:** `schemas/final-report-v2.0.schema.json` `$defs.VariationPointAnalysis` pins the shape, [`validators/validate-run.py`](../validators/validate-run.py) `_validate_variation_point_analysis` rejects a rationale-less or points-carrying `false`, a point-less `true`, and an `extract: true` decision with an empty `interfaceKind` or `coveredBy`, and `okstra_ctl.plan_items` emits the `P-Var-*` items.
398
398
 
399
399
  **Layer 2 — only when a style is declared.** A declared style promotes the placement rules from advisory to binding at three sites. `hexagonal`: extraction itself is not made mandatory, but when a point *is* extracted the interface has to be a port — an `extractionDecision` carrying `extract: true` with any `interfaceKind` other than `"port"` is a validator failure in the same `_validate_variation_point_analysis`, while `extract: false` remains a legal decision the style does not reject; the executor loads `prompts/coding-preflight/architectures/hexagonal.md` even when none of the router's Stage 3 layout signals matched, so the declaration, not the directory shape, decides; and the verifier promotes an added or modified service dependency injecting a concrete adapter instead of a port from an advisory recommendation to a blocking finding → verdict `FAIL` ([`prompts/profiles/_coding-conventions-preflight.md`](../prompts/profiles/_coding-conventions-preflight.md), [`_implementation-verifier.md`](../prompts/profiles/_implementation-verifier.md)). `layered` has no pack resource; its binding invariant is dependency direction — an upper layer may import a lower one, never the reverse — and it is worker judgement, because no machine check reads layer names.
400
400
 
@@ -834,21 +834,6 @@ New task bundles use `schemas/final-report-v2.0.schema.json` as the final-report
834
834
  - Each task template names the sections a reading agent should meet first and closes with a `md_rest()` sweep, so a field added to the schema reaches the Markdown without a template edit. **Enforced:** `tests/contract/test_ai_markdown_rendering.py`.
835
835
  - `templates/reports/html/tasks/<task-type>.template.html` produces the task-specific human-facing HTML. It leads with a plain-language decision summary and presents the selected task's findings, diagrams, tables, evidence, and actions. Worker execution and convergence detail stays in a visually subordinate audit section.
836
836
 
837
- The legacy schema v1 contract remains supported by `schemas/final-report-v1.0.schema.json` and `templates/reports/final-report.template.md`. Its Markdown structure is:
838
-
839
- - `## Verdict Card` — **mandatory top section**. Five rows: Final Conclusion / Verdict Token / Direction / Approval Required? / Next Step. The Verdict Token / Direction / Next Step cells must byte-match the authoritative cells in body §2 (execution status) and §6 (next steps).
840
- - (Optional) `## Reader Summary` — a five-row table rendered immediately below the Verdict Card only when data.json has `readerSummary`: decision (`decision`) / action required from a human (`humanActionRequired`) / blocking items (`blockingItems`) / safe-to-skip items (`safeToSkip`) / recommended command (`recommendedCommand`). When present, all five fields are required (schema `required`), and it contains only a summary rather than repeating raw evidence tables. Older data.json files without it continue to render unchanged.
841
- - `## Background and Rationale` — **mandatory for every task type** (data.json `rationale`). Reviewer-oriented prose that answers four questions in order: Why are we doing this (`motivation`)? / Why is it a problem (`problem`)? / What work is therefore needed (`approach`)? / Why is this a reasonable choice (`justification`)? It is prose, not a table. Every field must contain either an evidence reference such as `path:line`, a report ID (`C-001`), or `§5.4`, or an explicit insufficiency marker (`insufficient evidence`). `_validate_rationale_evidence` in `validators/validate-run.py` fails any field that has neither.
842
- - (Optional) `## 0. Clarification Response Carried In From Previous Run` — rendered only when a response was carried in from the previous run. The heading itself is omitted for an empty carry-in.
843
- - (Optional) `### 0.1 Incremental Re-Verification Scope` — rendered only when data.json contains `implementationPlanning.incrementalDecision`. When `mode == "incremental"`, the `Re-verified stages` and `Carried-forward stages` rows are mandatory, and `validators/validate-run.py` blocks their absence as `contract-violated`. Carried plan items record the run in which they were verified with a `carriedForwardFromSeq` tag.
844
- - `## 1. Problem or Verification Target Summary` — preserves the `Source items (worker:item)` column in both the §6.1 Consensus and §6.2 Differences tables for cross-worker traceability.
845
- - `## 2. Agent Execution Status`
846
- - `## 3. Cross-Verification Results` — §2.1 Primary Evidence contains the `Source items (worker:item)` and `Source (path:line / log)` columns.
847
- - `## 4. Final Assessment` — emits §5.5.9 Plan Body Verification for `implementation-planning` grouped by plan item: the item's `subject` (a one-line description of what was verified) becomes the heading, followed by a per-worker `Worker / Verdict / Breakage kind / Note` table. It also renders three legends: gate values, verdict tokens, and breakage kinds (a–f).
848
- - `## 1. Clarification Items` — a single consolidated eight-column table. The former §6.1 / §6.2 / §5.5.8 / §5.5.9 Open Questions are deprecated, and the validator fails if they appear.
849
- - `## 6. Recommended Next Steps`
850
- - `## Token Usage Summary` — the validator blocks publication if sentinel (`pending` / `N/A` / `--` / `?` / empty cell) or zero (`0` / `$0.00`) values are frozen into the report. Only the `Codex/Antigravity CLI Add-on` row may use `$0.00` to mean "CLI not used."
851
-
852
837
  The `## 0. Reading Confirmation` block from worker output is written to the sidecar at the resolved `<run-dir>/worker-results/<worker>-audit-<task-type>-<seq>.md`, not included in the report body (enforced by the validator).
853
838
 
854
839
  If there are no substantive differences, state that fact rather than manufacturing a contrast.
@@ -912,9 +897,8 @@ Errors that occur while provider workers, the report writer, or the Okstra lead
912
897
  - The `.log` is an execution transcript containing wrapper/CLI output, not the original prompt. `okstra log-report` pairs it with the sibling persisted `.md` prompt and reports `transcriptBytes`, `promptBytes`, and their ratio separately. The compatibility fields `path`, `sizeBytes`, and `totalBytes` continue to mean transcript data, so existing consumers and transcript-size ordering remain stable.
913
898
  - **Run-wide cap on the progress copy in the log** (`_LOG_PROGRESS_LINE_CAP` in `scripts/okstra_ctl/worker_runner.py`): workers read their required inputs end-to-end per the Worker Preamble's *Reading rules*, so a single report read can dump 170KB+ into the log and observed sidecars reach 8MB. The runner archives the first 5000 progress lines of the run and replaces the rest with a `[okstra log-cap] N progress line(s) elided` marker, emitted every 500 elided lines so a reader tailing the log can see the run is still producing. The cap is run-wide rather than per-block because a block boundary is a provider's own vocabulary and the shared runner has none; the cost is that a very long run keeps its opening rather than a sample throughout, which the elision notices make visible. **Only the log copy is capped** — never the screen, and never the result stream, because a truncated tool echo costs detail while a truncated answer costs the whole post-mortem.
914
899
  - **Progress lands in the worker's own pane, not in a sibling tail pane.** The presentation is passed to the entrypoint as `--presentation live|quiet`; `live` is claimed only by a backend that opened a pane, and the default is `quiet`. Under `live` the runner renders each event into one readable row on the caller's own streams (`→ Bash: …` for a tool call, ` ← ok (N bytes)` for its result, `!! PERMISSION DENIED — <tool>: <reason>` for a refusal); thinking events are dropped. Under `quiet` progress is withheld and only the worker's closing text is printed, which is what a `cli-wrapper` dispatch on a machine with no pane surface needs. `scripts/okstra_ctl/domain/worker_stream.py` owns all three projections (`format_live`, `format_log`, `final_text`) as pure functions over *normalised* events — `Text`, `ToolCall`, `ToolResult`, `Denial`, `Result`. Providers do not share a wire format and this layer may not name one, so each adapter supplies the function that turns its own events into those, declared on `ExecCommand.normalise` beside the stream format it belongs to. Adding a provider whose stream is shaped differently is that one function; a provider that declares `stream-json` without one is failed by `tests/contract/test_provider_execution_contract.py`, which runs the schema each provider claims through its own normaliser and requires rows out.
915
- - Because the wrappers no longer split a `tail -F` sibling, **nothing spawns a trace pane anymore**, and with it two pane tags lost their only writer: `@okstra_trace_run` (which marked a trace pane with its run) and `@okstra_status` (which pointed at that run's status sidecar). The operator watches the worker pane itself instead. Two consumers are now inert rather than wrong: `okstra-trace-cleanup.sh --reclaim-completed`, which reclaimed only trace panes whose `@okstra_status` read `stage=exited`, and `okstra-subagent-reclaim.sh`, which drives that mode from the `SubagentStop` / `TaskCompleted` hooks. Both still run and both now match nothing harmless, because the panes they closed are no longer created. The hooks stay installed on user machines, so removing this machinery is a deliberate follow-up rather than a side effect.
916
- - **Run-scoped tagging for cleanup**: `@okstra_worker_run=<RUN_DIR>` is still written, by `tmux.tag_pane` for the tmux-pane backend's worker-compute panes. `okstra-trace-cleanup.sh` discovers panes server-wide from that tag via `tmux list-panes -a` and runs `tmux kill-pane`, needing neither tmux environment variables nor a pane-ID registry. Because the tag is run-scoped, it does not kill panes belonging to other simultaneous okstra runs. Cleanup has two entry forms: the lead invokes it with `--run-dir <RUN_DIR>` to clean that run's worker panes, or the `hooks.SessionEnd` entry in `templates/reports/settings.template.json` invokes it with `--reap` to clean everything tagged below `$CLAUDE_PROJECT_DIR/.okstra/` when no single run directory exists at session end. Missing tmux and stale pane IDs silently degrade.
917
- - **Automatic cleanup on phase transitions, including worker-agent panes**: `okstra-trace-cleanup.sh --run-dir <RUN_DIR>` closes tagged worker-compute panes plus the worker-agent panes occupied by dispatched subagents. These harness-owned panes cannot be tagged, so the script identifies them within the lead's window (`tmux list-panes -t <lead-pane>`, no `-s`) through a title allowlist: `claude-worker` / `codex-worker` / `antigravity-worker` / `report-writer-worker`. Implementation role titles such as `claude-executor` / `codex-verifier`, and FleetView teammate prefixes `✳ ` / `⠂ `, are also treated as okstra panes. Every entry matches as a substring (`*agy-executor*`), which is how a former `agy-executor-tail` trace pane was caught even though the allowlist never named trace panes; no such pane is created now. Split-pane teammates always land in the lead's window, so window scope still catches all of this run's worker panes while leaving a second okstra lead running in another window of the same tmux session out of range. Window scoping and exclusion of the lead's own pane are determined by `<RUN_DIR>/state/lead-pane.id`; the lead pane is never killed even if its title matches. At every worker round boundary — after collecting that round's results and token usage, immediately before the next dispatch and before the `PROGRESS: phase-5.5-convergence` / `phase-6-synthesis` marker — the lead calls this script with `--run-dir` to reclaim the prior round's completed panes without prompting. `--keep <substr>` (repeatable) excludes panes whose title contains the substring, which is how an in-flight `report-writer-worker` survives the boundary. The lead first runs the same command with `--list` to count the panes it is about to reclaim and reports that count as `PROGRESS: phase-batch-cleanup panes=<n>`.
900
+ - **No okstra pane carries a tag any more, and the tag scan is gone with them.** Two writers disappeared at different times. The wrappers stopped splitting a `tail -F` sibling, so **nothing spawns a trace pane** and `@okstra_trace_run` / `@okstra_status` lost their writer the operator watches the worker pane itself instead. Then the `tmux-pane` dispatch backend was removed, so `tmux.tag_pane` and `@okstra_worker_run` went with it: a worker now gets a cmux surface or a cli-wrapper subprocess, neither of which is a tmux pane okstra owns. `okstra-trace-cleanup.sh` therefore no longer scans tags at all, and the two modes that existed only for that scan were removed — `--reclaim-completed` (with the `okstra-subagent-reclaim.sh` entry that drove it from `SubagentStop` / `TaskCompleted`) and `--reap` (with its `SessionEnd` entry). Those hooks are gone from `templates/reports/settings.template.json`; `SessionEnd` now carries only `okstra-team-reconcile.sh --session-end`.
901
+ - **What cleanup still does — worker-agent panes**: `okstra-trace-cleanup.sh --run-dir <RUN_DIR>` closes the worker-agent panes occupied by dispatched subagents. These harness-owned panes cannot be tagged, so the script identifies them within the lead's window (`tmux list-panes -t <lead-pane>`, no `-s`) through a title allowlist: `claude-worker` / `codex-worker` / `antigravity-worker` / `report-writer-worker`. Implementation role titles such as `claude-executor` / `codex-verifier`, and FleetView teammate prefixes `✳ ` / `⠂ `, are also treated as okstra panes. Every entry matches as a substring (`*agy-executor*`), which is how a former `agy-executor-tail` trace pane was caught even though the allowlist never named trace panes; no such pane is created now. Split-pane teammates always land in the lead's window, so window scope still catches all of this run's worker panes while leaving a second okstra lead running in another window of the same tmux session out of range. Window scoping and exclusion of the lead's own pane are determined by `<RUN_DIR>/state/lead-pane.id`; the lead pane is never killed even if its title matches. At every worker round boundary — after collecting that round's results and token usage, immediately before the next dispatch and before the `PROGRESS: phase-5.5-convergence` / `phase-6-synthesis` marker the lead calls this script with `--run-dir` to reclaim the prior round's completed panes without prompting. `--keep <substr>` (repeatable) excludes panes whose title contains the substring, which is how an in-flight `report-writer-worker` survives the boundary. The lead first runs the same command with `--list` to count the panes it is about to reclaim and reports that count as `PROGRESS: phase-batch-cleanup panes=<n>`.
918
902
  - **Cleanup survives compaction and precedes user gates**: A `SessionStart` hook with matcher `compact` (`okstra-compact-reminder.sh`, seeded in `settings.template.json`) fires after every `/compact` or auto-compaction. It reads `cwd` from the hook's stdin JSON, finds any in-progress okstra run for that project via `active.jsonl` (`pane_reclaim.active_run_dirs_for_project`), and, if one exists, prints a factual reminder naming the run dir and how completed panes/tasks are reclaimed — restoring the boundary/gate cleanup obligation that a compaction summary can otherwise drop. The hook never kills panes itself: a teammate pane is untagged and title-only, so a hook cannot tell an in-flight worker from a finished one; the actual `kill-pane` stays with the lead, which knows completion state. Separately, the lead runs the same completed-pane reclaim immediately before any user approval/clarification/decision gate (`PROGRESS: phase-gate-cleanup panes=<n>`), so a gate is never shown with finished worker panes still open. At every cleanup point the pane-kill (`trace-cleanup`) and the task-stop (`TaskStop`) are paired — a `TaskStop` alone idles the roster task but leaves the pane open.
919
903
  - **User confirmation at phase end**: At the final step of the run, the lead calls `okstra-trace-cleanup.sh --list --run-dir <RUN_DIR>` to show remaining okstra panes (worker-agent + trace), then asks once whether to "close all and clean up teammates / keep them." It follows the response (see *Phase wrap-up* in `prompts/profiles/_common-contract.md`). If approved, the lead cleans the panes. For a split-pane run, it then uses `okstra-team-reconcile.sh` to mark dead-pane members inactive and sends each completed teammate a `SendMessage` shutdown_request (`TeamDelete` was removed in v2.1.178; the implicit team disappears with the session). The lead does not gate this pane step by interpreting `lead-pane.id`; it **always** invokes the script, which safely returns an empty pane list and no-ops outside tmux. The teammate step is determined by the existence of an on-disk team configuration whose `leadSessionId` matches (`~/.claude/teams/session-*/config.json`), not by `teamCreate.status`. `--list` does not kill panes and prints only `<pane_id>\t<pane_title>`, so the user can see exactly what would be closed.
920
904
  - Disk accumulation is handled by the `okstra-inspect logs` flow, which offers a read-only inventory and suggests cleanup commands for the user to copy and paste.
@@ -988,7 +972,7 @@ Each validator blocks the phase with a `contract-violated` exit code when a cont
988
972
 
989
973
  - `README.md`
990
974
  - `templates/reports/task-brief.template.md`
991
- - `templates/reports/final-report.template.md`
975
+ - `templates/reports/final-report-v2.template.md`
992
976
  - `prompts/lead/okstra-lead-contract.md`
993
977
  - `scripts/okstra-error-log.py`
994
978
  - `scripts/okstra-token-usage.py`
package/docs/cli.md CHANGED
@@ -809,6 +809,7 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
809
809
  | `okstra code-review target --task-key <k> --stage <N> [--project-root <dir>] [--cwd <dir>] [--json]` / `okstra code-review target --branch <name> [--base <ref>] [--date <YYYY-MM-DD>] [--project-root <dir>] [--cwd <dir>] [--json]` | Resolve what a code review reads and where its result file goes. Output is always JSON, so `--json` only makes that explicit. `--project-root` and `--cwd` are shared pre-dispatch arguments and apply to both modes; `--cwd` is only consulted when `--project-root` is absent. Both modes return `{ ok, projectRoot, mode, worktreePath, branch, baseCommit, headCommit, reviewPath, round }`; stage mode additionally returns `taskKey`, `taskRoot`, and `stage`. Stage mode takes the diff base from the `base_ref` recorded on that stage's worktree-registry row when it was provisioned — not from a rule re-applied at review time — and names the result `.okstra/tasks/<task-group>/<task-id>/code-reviews/stage-<NN>.md`, where a re-review of the same stage becomes `-r2`, `-r3`, … (the `round` field). Only a legacy row provisioned before `base_ref` was recorded falls back to re-deriving the base through `stage_targets`, and a failure there is reported as `stage_base_unresolved`. `worktreePath` comes back empty whenever the stage worktree is not usable as a live checkout — the registry row is no longer `active` (whole-task final-verification released it), the row never carried a path, or the recorded directory is gone — and the review then reads the `branch` ref instead. Branch mode uses `--base` when given, otherwise the merge-base with the default branch (`refs/remotes/origin/HEAD`, else `main`/`master`), and names the result `.project-docs/code-reviews/<branch>/<YYYY-MM-DD>-<NN>.md`, where `<NN>` (the `round` field) is the next sequence number for that date — the highest already on disk plus one. Read-only: it resolves paths and creates no directory and no file, so the review directory does not exist until the caller writes the report. Backend for the okstra-code-review skill |
810
810
  | `okstra set-work-status <token> <todo\|in-progress\|blocked\|done> [--note <text>] [--task-group <g>] [--project-root <dir>]` | Update user-managed `workStatus` in task-manifest.json, along with `workStatusUpdatedAt` and, when `--note` is supplied, `workStatusNote`. `<token>` is a full task key or bare task ID. It uses the manifest renderer's serialization rules and returns `stage:"ambiguous"` plus `matches[]` when ambiguous |
811
811
  | `okstra worktree-lookup <task-key>` | Return the `worktree_registry.lookup` result: reserved path, branch, base ref, and current status |
812
+ | `okstra worktree-status [--path <dir>] [--check-clean]` | Answer "is this worktree clean?" over source paths only, excluding what okstra provisioned there — `.okstra`, the configured sync entries (`.project-docs`, `.claude`, …), and any nested stage worktree. A bare `git status --porcelain` in a task worktree is never empty for that reason, so a plan step asserting a clean tree with one fails on okstra's scaffolding instead of on the stage's own work; this is the same gate `handoff` and stage integration use. Output is JSON `{ ok, path, clean, entries, excluded }` where `entries` holds the `git status --short` rows that made it dirty. Exit code is 0 regardless unless `--check-clean` is given, which exits 1 on a dirty tree so it can stand as a shell assertion (`okstra worktree-status --check-clean && git tag -f stage-<N>-exit`). A path outside a git work tree exits 2 rather than reporting a clean tree |
812
813
  | `okstra plan-validate <plan-path>` | Run `_validate_approved_plan` and report frontmatter `approved` recognition plus unresolved Blocks=approval rows |
813
814
  | `okstra render-bundle <args…> [--stage <auto\|N>] [--stages <csv>]` | Thin shim over `prepare_task_bundle(render_only=True)` with the same signature as `python3 -m okstra_ctl.run --render-only`. `--stage` is for `implementation` and `final-verification`: for implementation, `auto` (default) selects the earliest incomplete stage with satisfied dependencies, while `<N>` forces a stage; for final-verification, `<N>` verifies one stage with artifacts under `runs/final-verification/stage-<N>/` and a `-fv-s<N>` team suffix, while an empty value performs whole-task verification with the flat layout. The separate `--stages <csv>` channel is for `release-handoff`: stage-group mode bundles the listed stage numbers into one PR, while an empty value selects whole-task mode. Preparation enforces eligibility—`done` + accepted `verified` + not yet `pr`—and automatically creates an input document that cites verification reports |
814
815
  | `okstra profile show <task-type> [--resolved]` | Print a phase profile. `--resolved` expands its `{{INCLUDE:}}` targets and appends the lazy-read sidecars named in the profile body — transitively, because sidecars name sidecars of their own (`_implementation-executor.md` points at the coding-conventions preflight, the diff-review sweep, and the completion self-check). That matters because a profile is assembled from three places, so grepping only the top-level file returns false negatives: `grep clarification prompts/profiles/implementation.md` finds nothing while the assembled profile has many hits. One grep over this output answers whether a task-type covers a rule. The sidecar list is read from the profile body, never hard-coded, so a newly added sidecar is picked up without a code change. Read-only: it writes no manifest and registers no run, which is what separates it from `render-bundle` — `render-bundle` answers the same question but records a run in `recent.jsonl`, so it cannot be used to look something up. Exits 2 for an unknown task-type |
@@ -819,7 +820,7 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
819
820
  | `okstra team dispatch --project-root <dir> --run-manifest <path> [--workers <csv>] [--jobs-file <path>] [--dry-run]` / `okstra team await --project-root <dir> --run-manifest <path> [--json]` / `okstra team teardown --project-root <dir> --run-manifest <path> [--dry-run] [--json]` | Read a `leadRuntime=external` run manifest and dispatch, await, or tear down tmux-pane workers. Default dispatch excludes report writer; Phase 6 selects it explicitly, and mixed analysis/report jobs are rejected. If a tmux pane cannot be created, gracefully degrade to the CLI wrapper and record the fallback in `workerDispatches[].degradedFrom` |
820
821
  | `okstra agent-activity append --project-root <dir> --run-manifest <path> --kind <kind> --agent <id> --summary <text> --outcome <outcome> [--plan-item-id <id>]… [--evidence-ref <ref>]… [--command-record <json>]… [--result-path <path>] [--audit-sidecar <path>]` | Append one structured activity to the run manifest's `leadEventsPath`. `kind` accepts `worker-dispatched`, `worker-completed`, `verification-round-completed`, `self-fix-applied`, `user-decision-required`, or `user-decision-evaluated`. `outcome` accepts `pending`, `completed`, `failed`, `blocked`, or `resolved`. Repeated `--command-record` values and `Evidence command` rows from `--audit-sidecar` must contain exactly `command`, `cwd`, `exitCode`, and `outputSummary`; malformed or potentially secret-bearing evidence stops the append. The command requires `activityContractVersion: 1` and returns the assigned `activityId` in JSON. |
821
822
  | `okstra agent-activity project --project-root <dir> --run-manifest <path> --data <data.json>` | Project this run's canonical activity events into `agentActivity[]`. The command preserves event order, rejects duplicate or decreasing activity IDs, and replaces no other report field. A historical manifest without `activityContractVersion: 1` returns an empty projection and leaves data.json unchanged. Normal Phase 7 execution reaches this behavior through `report-finalize`; use the standalone command only for diagnostics. |
822
- | `okstra report-finalize --project-root <dir> --run-manifest <path> --report <final-report.md>` | Run the whole Phase 7 post-report sequence in its contractual order: `project-activity` → `check-source` → `token-usage` → `render-views` → `spawn-followups` → `validate-run`. Stops at the first non-zero exit and names the failing step, then prints a per-step `[ok]` / `[FAIL]` / `[skip]` summary on stderr so the outcome is legible without parsing the JSON payload. `project-activity` is an in-process byte-preserving no-op for legacy manifests without activity contract v1. Every step is idempotent, so re-running after a fix is safe — but `--only <step>` (repeatable) reruns just the named steps in contractual order. For a non-English report, run `--only project-activity --only check-source` before translator dispatch, then run the full sequence after the translation sidecar exists. This is the same code path (`scripts/okstra_ctl/report_finalize.py`) every lead adapter runs after its report-writer completes. `--workspace-root` is owned by the Node wrapper. Prefer this over invoking the six steps individually. |
823
+ | `okstra report-finalize --project-root <dir> --run-manifest <path> --report <final-report.md>` | Run the whole Phase 7 post-report sequence in its contractual order: `project-activity` → `check-source` → `token-usage` → `render-views` → `spawn-followups` → `validate-run`. Stops at the first non-zero exit and names the failing step, except `token-usage`: its input is the lead session log, so a failure there defers rather than deleting the html, follow-ups and validation that come after it — the run still reports `ok: false`, and names that step unless a later one also fails, in which case the later failure is the reason. Whenever a step exits non-zero the command prints the `--only` flags that resume the sequence from the earliest failing step; after a deferral that tail is what re-renders the html the deferred step invalidated. Prints a per-step `[ok]` / `[FAIL]` / `[skip]` summary on stderr so the outcome is legible without parsing the JSON payload. `project-activity` is an in-process byte-preserving no-op for legacy manifests without activity contract v1. Every step is idempotent, so re-running after a fix is safe — but `--only <step>` (repeatable) reruns just the named steps in contractual order. For a non-English report, run `--only project-activity --only check-source` before translator dispatch, then run the full sequence after the translation sidecar exists. This is the same code path (`scripts/okstra_ctl/report_finalize.py`) every lead adapter runs after its report-writer completes. `--workspace-root` is owned by the Node wrapper. Prefer this over invoking the six steps individually. |
823
824
  | `okstra render-views <final-report.data.json\|final-report.md>` | The Phase 7 `render-views` step, runnable on its own. Schema v2 data is rendered directly (contract: `schemas/final-report-v2.0.schema.json`) into an always-generated, task-specific human HTML sibling while `templates/reports/final-report-v2.template.md` independently owns the AI handoff Markdown. Passing the Markdown sibling locates the same v2 data.json. Schema v1 and quick reports keep the legacy conditional renderer. The Node wrapper calls `scripts/okstra-render-report-views.py`; `validators/validate-report-views.py` verifies source/schema/template digests, required human fields, form controls, external assets, diagram/table ID parity, and Response ID parity |
824
825
  | `okstra design-prep <list\|show\|write>` | Review AI-prepared implementation design requests, inspect their effective confirmed response, or append a confirmed user/wizard response without editing the planning report |
825
826
  | `okstra wizard <init\|step\|render-args\|confirmation\|outcome> --state-file <path>` | Interactive input state machine for okstra-run, implemented by `okstra_ctl.wizard`. Seed a state file with `init`, then repeatedly call `step --answer <val>` to receive the next `Prompt` JSON. `--answer` is **required**; use `--no-submit` to peek at the next prompt without submitting a response. A `pick` with more choices than the host picker can display keeps `kind: "pick"` but adds `presentation: "numbered-text"`; render every option as a numbered Markdown list and submit the user's 1-based number, exact value, or exact label. Invalid, out-of-range, and ambiguous answers re-prompt without dropping choices. `render-args` returns the final `render-bundle` argument map, and `confirmation` returns the user echo block. On a completed wizard, `outcome` returns `renderArgs`, `persistActions`, and `confirmationText` together; project/global release-handoff PR-template persistence appears as `persistActions[].command == "config.set"`. For an `implementation` task type, `stage_pick` follows `approved_plan_pick` and selects the stage before `executor_pick`. The brief step appears only for entry task types—requirements-discovery, error-analysis, improvement-discovery, project-analysis, feature-analysis, and change-impact-analysis. Analysis inputs use `feature_evidence_pick` / `feature_evidence`, `project_evidence_pick` / `project_evidence`, and `analysis_target_pick` / `analysis_target`; a revision-requested report prioritizes its same-task, same-type rerun. Downstream lifecycle phases automatically carry the manifest brief, with a three-option `brief_carry` fallback when none is registered; `release-handoff` has no brief and enters multi-select `handoff_stage_pick` for eligible stage groups or the whole task |
@@ -875,4 +876,4 @@ For every dispatch, whichever provider runs it, okstra creates a `runs/<task-typ
875
876
 
876
877
  **Progress appears in the worker's own pane.** Earlier versions split a sibling `tail -F` trace pane next to each worker; they no longer do, and no trace pane is created at all. Instead the presentation is passed to the entrypoint as `--presentation live|quiet`, and only a backend that opened a pane asks for `live` — the default, and what a `cli-wrapper` subagent dispatch passes, is `quiet`. Under `live` each event becomes one readable row on the worker's own streams — `→ Bash: npm run check`, then ` ← ok (2481 bytes)`, and `!! PERMISSION DENIED — <tool>: <reason>` for a refusal. Under `quiet` progress is withheld and only the worker's closing text is printed, which is what a dispatch on a machine with no pane surface needs. The `.log` sidecar records the progress either way, so withholding it from the screen loses nothing.
877
878
 
878
- Because nothing spawns a trace pane, the `@okstra_trace_run` and `@okstra_status` pane user options have no writer left, and the `okstra-trace-cleanup.sh --reclaim-completed` mode plus the `okstra-subagent-reclaim.sh` hook that drives it now match no panes. Both stay installed and are harmless the panes they existed to close are no longer created. tmux-pane backend worker-compute panes are still tagged `@okstra_worker_run=<RUN_DIR>`. When Claude receives `/exit`, the `SessionEnd` hook cleans up within `$CLAUDE_PROJECT_DIR/.okstra/` scope by running `okstra-trace-cleanup.sh --reap`. When the lead calls the same script with `--run-dir <RUN_DIR>`, it removes the run's worker-compute panes and dispatched worker-agent panes within the lead-window scope (title scan uses `tmux list-panes -t <lead-pane>`, no `-s`, so a second lead in another window of the same session is out of range), while excluding the lead's own pane. Worker-agent titles include `claude-worker`, `codex-worker`, `antigravity-worker`, `report-writer-worker`, implementation role titles, and FleetView teammate prefixes `✳ ` / `⠂ `. The lead runs `okstra-trace-cleanup.sh --run-dir <RUN_DIR>` at every worker round boundary — after collecting that round's results and before the next dispatch, not once per phase — to reclaim the completed panes. `--keep <substr>` (repeatable) excludes panes whose title contains the substring, which is how an in-flight report writer is preserved (`--keep report-writer-worker`), and `--list` prints the same set without killing so the lead can count what it is about to reclaim. The lead also runs the same reclaim immediately before any user approval/clarification/decision gate (`PROGRESS: phase-gate-cleanup panes=<n>`), and an `okstra-compact-reminder.sh` `SessionStart` hook (matcher `compact`) re-injects this obligation after a `/compact`.
879
+ Every pane tag this script once scanned has lost its writer, so the tag scan is gone along with the two modes that existed only to drive it. `@okstra_trace_run` / `@okstra_status` went inert when the wrappers stopped splitting a trace pane, which retired `--reclaim-completed` and the `okstra-subagent-reclaim.sh` hook behind it; `@okstra_worker_run` went with the `tmux-pane` dispatch backend, which retired `--reap` and its `SessionEnd` hook. What okstra still has to close are the teammate panes the **harness** splits for dispatched worker agents panes okstra never created and cannot tag. The lead calls `okstra-trace-cleanup.sh --run-dir <RUN_DIR>`, which finds them by a title allowlist within the lead-window scope (`tmux list-panes -t <lead-pane>`, no `-s`, so a second lead in another window of the same session is out of range), while excluding the lead's own pane. Worker-agent titles include `claude-worker`, `codex-worker`, `antigravity-worker`, `report-writer-worker`, implementation role titles, and FleetView teammate prefixes `✳ ` / `⠂ `. The lead runs `okstra-trace-cleanup.sh --run-dir <RUN_DIR>` at every worker round boundary — after collecting that round's results and before the next dispatch, not once per phase — to reclaim the completed panes. `--keep <substr>` (repeatable) excludes panes whose title contains the substring, which is how an in-flight report writer is preserved (`--keep report-writer-worker`), and `--list` prints the same set without killing so the lead can count what it is about to reclaim. The lead also runs the same reclaim immediately before any user approval/clarification/decision gate (`PROGRESS: phase-gate-cleanup panes=<n>`), and an `okstra-compact-reminder.sh` `SessionStart` hook (matcher `compact`) re-injects this obligation after a `/compact`.
package/docs/container.md CHANGED
@@ -75,7 +75,7 @@ One watcher per container runs in the detached session.
75
75
  1. **Lightweight scan** — Fetch incremental logs with `docker compose logs --since` and match only regular expressions (`ERROR`/`FATAL`/`Exception`/`Traceback`/abnormal exit codes, and so on). If there are no matches, proceed to the next interval without an LLM call → zero token cost during healthy periods.
76
76
  2. **Deep analysis** — Only when a pattern is detected, the watcher AI analyzes the relevant log window and appends its findings to `findings.md`. Identical error signatures are debounced (meaningful numbers such as HTTP statuses and exit codes are preserved, while only noise such as timestamps and pids is normalized). The watcher **only detects and reports**; it does not modify code or configuration.
77
77
 
78
- Watcher/tail panes carry only the dedicated `@okstra_container_run` tag, so they survive even when a Claude session ends (SessionEnd `--reap`). Stop them with `stop-watcher` or `down`.
78
+ Watcher/tail panes carry only the dedicated `@okstra_container_run` tag and survive a Claude session ending no session-end hook reclaims panes any more. Stop them with `stop-watcher` or `down`.
79
79
 
80
80
  ## Labels and artifacts
81
81
 
@@ -10,7 +10,7 @@ Use this matrix before changing high-risk repo contracts. Update the source file
10
10
  | Change manager contract | `scripts/okstra_ctl/manager_*.py`, `src/commands/manager.mjs`, `skills/okstra-manager/SKILL.md`, `docs/for-ai/skills/okstra-manager.md`, `docs/cli.md`, `docs/architecture/storage-model.md` | `tests-js/manager.test.mjs`, `tests/test_okstra_manager_*.py` |
11
11
  | Add phase | `scripts/okstra_ctl/workflow.py`, `prompts/profiles/`, `validators/`, `tests/` | workflow and validation contract tests |
12
12
  | Change worker roster | `prompts/profiles/*.md`, `scripts/okstra_ctl/workers.py`, `tests/contract/test_repo_contracts.py` | worker roster contract tests |
13
- | Change report section | `schemas/final-report-v1.0.schema.json`, `templates/reports/final-report.template.md`, `scripts/okstra_ctl/render_final_report.py`, `validators/validate-run.py` | final-report schema, renderer, and validator tests |
13
+ | Change report section | `schemas/final-report-v2.0.schema.json`, `templates/reports/final-report-v2.template.md`, `scripts/okstra_ctl/render_final_report.py`, `validators/validate-run.py` | final-report schema, renderer, and validator tests |
14
14
  | Maintain Korean review mirrors | `config/korean-sources.json`, `tools/korean-sources/` (`lifecycle.mjs` plus its `lifecycle-*.mjs` support modules, CLI/hooks adapters, and shared workflow), `.agents/skills/sync-korean-sources/`, `.claude/skills/sync-korean-sources/` | `tests-js/korean-sources-*.test.mjs` (`lifecycle`, `cli`, `hooks`, and `skill`) |
15
15
 
16
16
  `runtime/` is build output. Never edit it directly; change source files and rebuild the runtime payload instead.
@@ -30,7 +30,7 @@ Current baseline:
30
30
  - lifecycle: `requirements-discovery → error-analysis → implementation-option-selection → implementation-planning → implementation → final-verification → release-handoff`
31
31
  - installed skills: 13
32
32
  - provider workers: `claude`, `codex`, `antigravity`, `grok`, `kimi`; functional report writer: `report-writer`
33
- - final report SSOT: current `schemas/final-report-v2.0.schema.json` + `*.data.json`; schema v1 remains a compatibility contract
33
+ - final report SSOT: `schemas/final-report-v2.0.schema.json` + `*.data.json`
34
34
 
35
35
  Design principles:
36
36
 
@@ -180,6 +180,7 @@ Runtime/install asset changes follow this checklist:
180
180
  | `worker-audit-check` | `src/commands/execute/worker-audit-check.mjs` | Apply the Phase 7 worker audit-sidecar rules while the worker session is still alive, so it can fix its own citations (Python: `okstra_ctl.worker_audit_check`, rules in `okstra_ctl.worker_audit_ledger`) |
181
181
  | `context-cost` | `src/commands/inspect/context-cost.mjs` | Estimate task bundle file/read context cost |
182
182
  | `worktree-lookup` | `src/commands/execute/worktree-lookup.mjs` | Look up a task-key's registered worktree |
183
+ | `worktree-status` | `src/commands/execute/worktree-status.mjs` | Clean-worktree check over source paths only, excluding okstra's provisioned entries and nested stage worktrees (Python: `okstra_ctl.worktree.dirty_entries_excluding_okstra`) |
183
184
  | `plan-validate` | `src/commands/execute/plan-validate.mjs` | Check approved-plan approval marker |
184
185
  | `render-bundle` | `src/commands/execute/render-bundle.mjs` | Preview `prepare_task_bundle(render_only=True)` |
185
186
  | `profile` | `src/commands/inspect/profile-show.mjs` | Print a phase profile with `{{INCLUDE:}}` expanded and its lazy-read sidecars appended transitively, so one grep answers whether a task-type covers a rule — a top-level grep alone returns false negatives (Python: `okstra_ctl.profile_show`). Read-only, unlike `render-bundle` |
@@ -191,7 +192,7 @@ Runtime/install asset changes follow this checklist:
191
192
  | `plan-items` | `src/commands/execute/plan-items.mjs` | Internal admin CLI for deterministic plan-body item extraction and exact-match validation (`extract`/`validate`; Python: `okstra_ctl.plan_items_cli`) |
192
193
  | `agent-activity` | `src/commands/report/agent-activity.mjs` | Thin Node shim for `okstra_ctl.agent_activity`; `append` records one run-bound activity and `project` writes the validated event projection into final-report data |
193
194
  | `report-finalize` | `src/commands/report/finalize.mjs` | Run the whole Phase 7 post-report sequence in contractual order (Python: `okstra_ctl.report_finalize`) — the single reference point shared with the Codex lead adapter |
194
- | `render-views` | `src/commands/report/render-views.mjs` | Render schema v2 data with its task-specific human template, or use the schema v1 / quick-report compatibility view |
195
+ | `render-views` | `src/commands/report/render-views.mjs` | Render schema v2 data with its task-specific human template, or use the quick-report compatibility view |
195
196
  | `render-final-report`, `inject-report-index` | `src/commands/report/*.mjs` | Render version-selected AI handoff Markdown from data.json; v1 index injection remains compatibility-only |
196
197
  | `wizard` | `src/commands/execute/wizard.mjs` | Drive the `okstra-run` interactive state machine, including the final outcome envelope |
197
198
  | `token-usage` | `src/commands/execute/token-usage.mjs` | Wrap installed Python token usage CLI |
@@ -225,11 +226,10 @@ Top-level scripts:
225
226
  | `okstra-wrapper-status.py` | Standalone writer for one worker status sidecar. No longer on the dispatch path — `worker_runner.py` writes the same document in-process |
226
227
  | `okstra-token-usage.py` | Token usage CLI entrypoint |
227
228
  | `okstra-render-final-report.py` | Render version-selected final-report Markdown from data.json |
228
- | `okstra-render-report-views.py` | Render schema v2 task-specific HTML directly from data.json, or a legacy view from schema v1 / quick Markdown |
229
+ | `okstra-render-report-views.py` | Render schema v2 task-specific HTML directly from data.json, or a legacy view from quick Markdown |
229
230
  | `okstra-error-log.py` | Normalize worker/lead error sidecars |
230
231
  | `okstra-spawn-followups.py` | Follow-up spawning helper |
231
- | `okstra-trace-cleanup.sh` | tmux okstra pane cleanup (worker-agent + worker-compute, excluding the lead pane), called by the lead at every worker round boundary — not once per phase; `--keep <substr>` (repeatable) spares panes whose title contains the substring, which is how an in-flight `report-writer-worker` survives a boundary; `--list` prints what would be reclaimed without killing. Its `--reclaim-completed` mode keyed on trace panes tagged `@okstra_status`; **no code writes that tag any more**, so the mode is inert rather than wrong — the trace panes it reclaimed are no longer created |
232
- | `okstra-subagent-reclaim.sh` | entry that walks active runs and drives `--reclaim-completed` (wired to the `SubagentStop`/`TaskCompleted` hooks). **Inert for the same reason** — it still runs and matches nothing. Left installed rather than removed: the hooks are already seeded on user machines, so retiring this is a deliberate follow-up |
232
+ | `okstra-trace-cleanup.sh` | Closes the harness-owned worker-agent panes of a run (excluding the lead pane), called by the lead at every worker round boundary — not once per phase; `--keep <substr>` (repeatable) spares panes whose title contains the substring, which is how an in-flight `report-writer-worker` survives a boundary; `--list` prints what would be reclaimed without killing. The tag-driven paths (`--reap`, `--reclaim-completed`, and the `@okstra_worker_run` / `@okstra_trace_run` / `@okstra_status` scan) were removed once nothing wrote those tags a title scan scoped to the lead's window is all that remains |
233
233
 
234
234
  ### 4.3 `scripts/okstra_ctl/` — Python orchestration core
235
235
 
@@ -268,9 +268,9 @@ Important modules:
268
268
  | `clarification_items.py` | Unified §5 clarification table parser and approval blockers |
269
269
  | `md_table.py` | Markdown pipe-table escape/split SSOT — the `mdcell` filter (`escape_pipes`) and the `\|`-aware `split_pipe_row`; shared by the renderer, HTML view, and validators |
270
270
  | `qa_commands.py` | QA command deny-list validation for plans |
271
- | `conformance.py` | validates task-level Tier 3 manifests, parses `QA-RESULT`, detects diff capability surfaces, and reduces results to PASS/ADVISORY/BLOCKING; DB/HTTP/external non-PASS is user-owned advisory while local IO and contract defects remain blocking, enforced by `scripts/okstra_ctl/conformance.py::decide_conformance_gate` and `validators/validate-run.py::_validate_conformance` |
271
+ | `conformance.py` | validates task-level Tier 3 manifests, parses `QA-RESULT`, detects diff capability surfaces, and reduces results to PASS/ADVISORY/BLOCKING; DB/HTTP/external non-PASS is user-owned advisory while local IO and contract defects remain blocking, enforced by `scripts/okstra_ctl/conformance.py::decide_conformance_gate` and `validators/validate-run.py::_validate_conformance`. Also the single definition of the plan's `Conformance tests:` declaration format (`parse_conformance_tests`, `malformed_conformance_stages`), read both at the approval boundary (`run.py::_validate_approved_plan`) and at the end of an implementation run (`validators/validate-run.py`) so the two cannot disagree |
272
272
  | `pr_template.py` | PR body template resolution for release-handoff |
273
- | `report_views.py`, `render_final_report.py`, `final_report_schema.py` | Versioned final-report contract: schema v2 data independently produces AI handoff Markdown and human HTML; schema v1 keeps the legacy Markdown/view pipeline |
273
+ | `report_views.py`, `render_final_report.py`, `final_report_schema.py` | Final-report contract: schema v2 data independently produces AI handoff Markdown and human HTML |
274
274
  | `report_markdown.py` | Schema-ordered Markdown serialisation of a data.json subtree for the AI handoff report — headings, tables for uniform row sets, prose for narrative fields; field order read from the schema, not from the mapping |
275
275
  | `final_report_paths.py`, `report_view_artifacts.py` | Path-helper SSOT for the final-report markdown/data.json pair and the generated view artifacts (HTML view, user-responses directory) |
276
276
  | `wizard.py` | `okstra-run` prompt state machine; user-facing Korean strings live in `prompts/wizard/prompts.ko.json` |
@@ -290,7 +290,7 @@ Important modules:
290
290
  | `code_review_paths.py` | filesystem-layout SSOT for code-review result files — `stage_review_dir` / `branch_review_dir` plus `next_stage_review` / `next_branch_review`, which read the existing files to derive the next round's name (`stage-<NN>.md`, then `-r2`, `-r3`, …) or the next same-day sequence (`<YYYY-MM-DD>-<NN>.md`), so skill markdown never re-derives a literal review path |
291
291
  | `code_review_target.py` | `okstra code-review target` backend — argument validation and JSON shaping only. Stage mode delegates whole to `okstra_project.state.code_review_target_snapshot`; branch mode is resolved here, defaulting the diff base to the merge-base with the default branch (`refs/remotes/origin/HEAD`, else `main`/`master`). Read-only: it never creates the review directory |
292
292
  | `session.py`, `tmux.py`, `seeding.py`, `locks.py`, `invocation.py`, `sequence.py`, `ids.py`, `material.py` | Supporting lifecycle helpers |
293
- | `pane_reclaim.py` | decides which completed trace panes are reclaim targets; imports the in-progress status set from the `reconcile.NON_TERMINAL_RECENT_STATUSES` SSOT. Inert alongside `--reclaim-completed` — no trace pane is created any more but still the owner of that decision if the mechanism returns |
293
+ | `pane_reclaim.py` | resolves which in-progress runs of the current project the `SessionStart(compact)` hook should re-inject the pane-cleanup obligation for; imports the in-progress status set from the `reconcile.NON_TERMINAL_RECENT_STATUSES` SSOT. The completed-pane decision it also used to own went away with `--reclaim-completed` |
294
294
  | `improvement_lenses.py` | lens enum SSOT + cap constants for the improvement-discovery phase (DEFAULT 8, ABSOLUTE 12, MIN/MAX PRIORITY 1/4, SOURCE_WORKERS) |
295
295
  | `improvement_assignment.py` | improvement-discovery primary-pass lens assignment — round-robins the resolved `requiredWorkerRoles` order over the resolved priority lenses (`assign_primary_lenses`) and validates the resulting map (`validate_primary_lens_assignments`). Only the primary pass rotates; every analyser still confirms the full lens set afterwards |
296
296
  | `container.py` | the `okstra container` convergence entrypoint of the okstra-container-build public skill — `provision_container_group` + `up`/`status`/`logs`/`stop-watcher`/`down` dispatch, env-override synthesis, compose argv assembly, and per-container watcher startup |
@@ -333,7 +333,7 @@ Important modules:
333
333
  | `plan_derivations.py` | the supersession sweep `_common-contract.md` requires an author to do by hand — extracts the symbols, paths, and ids an answered clarification names and reports every plan string that mentions one. Advisory: it locates candidates and never judges which are now false |
334
334
  | `scope_provenance.py` | single source of truth for the scope-provenance grammar every phase-emitted requirement must declare, shared by `validators/validate-run.py` and `validators/validate_fanout.py` so the planning report and fan-out packets cannot drift |
335
335
  | `worker_artifact_paths.py` | canonical worker artifact path derivation (e.g. `audit_sidecar_rel` inserts `-audit-` after the first `-worker-` token), so dispatch and validation agree on non-canonical-path rejection |
336
- | `report_finalize.py` | Phase 7 post-report sequence **SSOT** — runs `check-source` → `token-usage` → `render-views` → `spawn-followups` → `validate-run` in that load-bearing order, stops at the first non-zero exit and names the failing step. Both lead paths converge here: the Codex adapter calls it in-process (`codex_dispatch`), a Claude-led run reaches it through `okstra report-finalize`. Neither reimplements the sequence |
336
+ | `report_finalize.py` | Phase 7 post-report sequence **SSOT** — runs `check-source` → `token-usage` → `render-views` → `spawn-followups` → `validate-run` in that load-bearing order, stops at the first non-zero exit and names the failing step — except `token-usage`, whose failure defers so the later steps still produce their artifacts. Both lead paths converge here: the Codex adapter calls it in-process (`codex_dispatch`), a Claude-led run reaches it through `okstra report-finalize`. Neither reimplements the sequence |
337
337
  | `wrapper_status.py` | worker wrapper status sidecar reader — the host-side reader of the sidecar `worker_runner.py` writes. `is_terminal` is the one question it answers for the dispatch record and the pane reclaim: does `stage` read `exited` |
338
338
  | `worker_runner.py` | runs one worker CLI and records what happened — shared by every provider entrypoint. Owns the `selectors` pump over the child's streams, the stream-arrival idle watchdog (`killpg` on breach), the run-wide progress cap on the log copy, and the status sidecar's whole life. A run that dies after launch still closes its sidecar, so `worker_liveness` never reads a dead worker as running |
339
339
  | `worker_request.py` | assembles the `WorkerExecRequest` every strategy then takes on trust: resolved paths, the write scope in the order the CLIs are told it (project root → stage tree → the tree's git-common-dir), the verifier's toolchain grants, and the role's idle budget |
@@ -379,7 +379,7 @@ Token/cost accounting:
379
379
 
380
380
  | Path | Role |
381
381
  |---|---|
382
- | `templates/reports/final-report.template.md` | Schema v1 compatibility Markdown template |
382
+ | `templates/reports/final-report-v2.template.md` | AI handoff Markdown spine |
383
383
  | `templates/reports/final-report-v2.template.md` | Schema v2 AI handoff Markdown spine |
384
384
  | `templates/reports/md/tasks/*.template.md`, `md/macros/sections.md` | Eleven dedicated task bodies for the AI handoff Markdown, sibling of `html/tasks/`; shared section macro |
385
385
  | `templates/reports/html/base.template.html`, `html/tasks/*.template.html` | Shared HTML shell plus eleven dedicated task templates for human reports; task bodies are not shared |
@@ -395,7 +395,7 @@ Token/cost accounting:
395
395
 
396
396
  ### 4.8 `schemas/`
397
397
 
398
- `schemas/final-report-v2.0.schema.json` is the current final-report data.json contract. The report-writer worker writes `final-report-<task-type>-<seq>.data.json`; independent renderers produce AI handoff Markdown and task-specific human HTML. `schemas/final-report-v1.0.schema.json` remains supported for existing reports and quick-report compatibility.
398
+ `schemas/final-report-v2.0.schema.json` is the current final-report data.json contract. The report-writer worker writes `final-report-<task-type>-<seq>.data.json`; independent renderers produce AI handoff Markdown and task-specific human HTML.
399
399
 
400
400
  The deterministic convergence inputs are `schemas/convergence-groups-v1.0.schema.json`, `schemas/convergence-round-results-v1.0.schema.json`, and `schemas/convergence-critic-results-v1.0.schema.json`. `tools/build.mjs` syncs the entire source `schemas/` directory to `runtime/schemas/`; these JSON Schema files are runtime contracts, not Markdown publication-inventory entries.
401
401
 
@@ -408,7 +408,7 @@ Optional (v1.0 backward-compatible) top-level keys:
408
408
 
409
409
  | File | Role |
410
410
  |---|---|
411
- | `validate-run.py` | Version-aware run/final-report validation: schema v2 AI handoff order + structured data rules, with legacy schema v1 Markdown gates preserved |
411
+ | `validate-run.py` | Run/final-report validation: schema v2 AI handoff order + structured data rules |
412
412
  | `validate-brief.py`, `validate-brief.sh` | Brief frontmatter/body contract validation |
413
413
  | `validate-report-views.py` | HTML view validation (form-control placement / no external URLs / stale source digest / Response ID parity) |
414
414
  | `validate_analysis_report.py` | Cross-field validation for the three read-only analysis reports: frozen target/evidence snapshots, current-code evidence, review-source identity, and exact affected-ID resolution coverage on revision reruns |
@@ -537,7 +537,7 @@ Current report pipeline:
537
537
  4. For implementation-planning, `okstra plan-items extract` creates the complete `P-*` queue, `validate` proves it still matches data.json, and the analyser instances run the separate plan-body verification round.
538
538
  5. `scripts/okstra-render-final-report.py` renders compact AI handoff Markdown with `templates/reports/final-report-v2.template.md`.
539
539
  6. Token usage substitution fills usage/cost cells.
540
- 7. `scripts/okstra-render-report-views.py` independently selects one of eleven dedicated task templates and emits human-facing HTML directly from the same data.json; run validation checks both derived artifacts. Schema v1 and quick Markdown inputs retain their legacy conditional path.
540
+ 7. `scripts/okstra-render-report-views.py` independently selects one of eleven dedicated task templates and emits human-facing HTML directly from the same data.json; run validation checks both derived artifacts. A quick Markdown input retains its legacy conditional path.
541
541
 
542
542
  For the three analysis sidetracks, the HTML view also exports an immutable-source `## ANALYSIS REVIEW` sidecar. A revision rerun carries that sidecar, reanalyzes the whole confirmed scope, and records one `analysisReviewResolution` row for every affected ID before `validate_analysis_report.py` accepts the result.
543
543
 
@@ -63,7 +63,7 @@ flowchart TD
63
63
  | worker roster parser | [`scripts/okstra_ctl/workers.py`](../../scripts/okstra_ctl/workers.py) |
64
64
  | lead operating contract | [`prompts/lead/okstra-lead-contract.md`](../../prompts/lead/okstra-lead-contract.md) |
65
65
  | phase profiles | [`prompts/profiles/`](../../prompts/profiles/) |
66
- | final report shape / HTML view | [`templates/reports/final-report.template.md`](../../templates/reports/final-report.template.md), [`scripts/okstra_ctl/report_views.py`](../../scripts/okstra_ctl/report_views.py) |
66
+ | final report shape / HTML view | [`templates/reports/final-report-v2.template.md`](../../templates/reports/final-report-v2.template.md), [`scripts/okstra_ctl/report_views.py`](../../scripts/okstra_ctl/report_views.py) |
67
67
 
68
68
  ## 5. Quick comparison table
69
69
 
@@ -173,7 +173,7 @@ This phase only produces the plan document. Code-level micro-optimization, sourc
173
173
 
174
174
  - [`prompts/profiles/implementation-planning.md`](../../prompts/profiles/implementation-planning.md)
175
175
  - [`templates/reports/implementation-planning-input.template.md`](../../templates/reports/implementation-planning-input.template.md)
176
- - [`templates/reports/final-report.template.md`](../../templates/reports/final-report.template.md)
176
+ - [`templates/reports/final-report-v2.template.md`](../../templates/reports/final-report-v2.template.md)
177
177
  - [`scripts/okstra_ctl/render.py`](../../scripts/okstra_ctl/render.py)
178
178
  - [`validators/validate-run.py`](../../validators/validate-run.py)
179
179
  - [`prompts/lead/okstra-lead-contract.md`](../../prompts/lead/okstra-lead-contract.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.173.0",
3
+ "version": "0.174.0",
4
4
  "description": "Host-aware multi-provider cross-verification orchestrator runtime and agent skills.",
5
5
  "license": "MIT",
6
6
  "author": "devonshin",
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.173.0",
3
- "builtAt": "2026-08-16T04:09:59.317Z",
2
+ "package": "0.174.0",
3
+ "builtAt": "2026-08-17T05:25:03.210Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -64,7 +64,7 @@ Before producing any output, you MUST:
64
64
  2. Read every primary input file the lead enumerated under `## Inputs` (or equivalent heading) end-to-end, following the selected preamble. Analysis workers normally receive `analysis-packet.md`; implementation workers receive their role sidecar and approved deliverable inputs.
65
65
  3. When the prompt carries `**Evidence ledger:** required-v1`, follow the selected preamble's `Evidence read ledger` procedure for every claim-evidence file you open. Do not invent a separate audit-row format here.
66
66
 
67
- **Heartbeat — write the audit sidecar EARLY and APPEND per stage (BLOCKING).** This worker runs as an in-process Agent or a fresh-session tmux pane, so the lead has no `BashOutput`-style liveness signal while it waits for your return — the audit sidecar is the only signal that survives a silent hang.
67
+ **Heartbeat — write the audit sidecar EARLY and APPEND per stage (BLOCKING).** This worker runs as an in-process Agent, a cmux surface, or a CLI wrapper subprocess, so the lead has no `BashOutput`-style liveness signal while it waits for your return — the audit sidecar is the only signal that survives a silent hang.
68
68
 
69
69
  - **Where:** the absolute path in `**Audit sidecar path:**`.
70
70
  - **Start (before the per-file reads):** immediately after extracting `Project Root` and the assigned paths, `Write` just the heading line (`# Claude Worker Audit — <task-key>`) plus one `- PROGRESS: started <ISO-8601-UTC>` line.
@@ -3,7 +3,7 @@
3
3
  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
- FINAL_REPORT_TEMPLATE_SOURCE="$WORKSPACE_ROOT/templates/reports/final-report.template.md"
6
+ FINAL_REPORT_TEMPLATE_SOURCE="$WORKSPACE_ROOT/templates/reports/final-report-v2.template.md"
7
7
  RUN_VALIDATOR_PATH="$WORKSPACE_ROOT/validators/validate-run.py"
8
8
  OKSTRA_ROOT=""
9
9
  OKSTRA_TASKS_ROOT=""
@@ -40,10 +40,11 @@ from okstra_ctl.worker_runner import LIVE, QUIET, run_worker # noqa: E402
40
40
  _USAGE = (
41
41
  "usage: okstra-provider-exec.py <provider> <project-root> "
42
42
  "<model-execution-value> <prompt-path> [worktree-path] [role] "
43
- "[idle-timeout-seconds] [--presentation live|quiet]"
43
+ "[idle-timeout-seconds] [--presentation live|quiet] [--session-id <uuid>]"
44
44
  )
45
45
 
46
46
  _PRESENTATION_FLAG = "--presentation"
47
+ _SESSION_ID_FLAG = "--session-id"
47
48
  _PRESENTATIONS = (LIVE, QUIET)
48
49
 
49
50
 
@@ -66,6 +67,9 @@ class Invocation:
66
67
  def parse_invocation(argv: list[str]) -> Invocation:
67
68
  """Resolve the wrapper's positional contract into one runnable dispatch."""
68
69
  positional, presentation = _take_presentation(argv)
70
+ # Empty unless the dispatcher issued one. Without it the CLI picks its own
71
+ # id and nothing downstream can map that session back to this worker.
72
+ positional, session_id = _take_flag(positional, _SESSION_ID_FLAG, "")
69
73
  if not 4 <= len(positional) <= 7:
70
74
  raise PreflightError(64, _USAGE)
71
75
  provider_id, project_root_raw, model, prompt_raw = positional[:4]
@@ -90,6 +94,7 @@ def parse_invocation(argv: list[str]) -> Invocation:
90
94
  worktree_path=worktree,
91
95
  role=role,
92
96
  idle_timeout_seconds=idle_timeout_seconds,
97
+ session_id=session_id,
93
98
  )
94
99
  strategy = spec.exec_strategy
95
100
  _check_command(strategy, request)
@@ -103,27 +108,39 @@ def parse_invocation(argv: list[str]) -> Invocation:
103
108
  )
104
109
 
105
110
 
106
- def _take_presentation(argv: list[str]) -> tuple[list[str], str]:
107
- """Split the one flag out of an otherwise positional argv.
111
+ def _take_flag(argv: list[str], flag: str, default: str) -> tuple[list[str], str]:
112
+ """Split one value-carrying flag out of an otherwise positional argv.
108
113
 
109
- Defaults to ``quiet``. ``live`` is only ever right where a screen was
110
- declared, and the only callers that can declare one are the pane backends —
111
- which pass the flag explicitly. Defaulting the other way assumed a screen
112
- that a subagent dispatch does not have, and sent every worker's progress
113
- into its caller's context window instead.
114
+ The wrapper's contract is positional, so every flag it grows has to be
115
+ lifted out before the positions are counted. Written once because the flags
116
+ differ only in the value they carry: a second copy of this loop is where
117
+ their refusals of a flag with no value would drift apart.
114
118
  """
115
119
  positional: list[str] = []
116
- presentation = QUIET
120
+ value = default
117
121
  index = 0
118
122
  while index < len(argv):
119
- if argv[index] != _PRESENTATION_FLAG:
123
+ if argv[index] != flag:
120
124
  positional.append(argv[index])
121
125
  index += 1
122
126
  continue
123
127
  if index + 1 >= len(argv):
124
- raise PreflightError(64, f"{_PRESENTATION_FLAG} needs a value: {_USAGE}")
125
- presentation = argv[index + 1]
128
+ raise PreflightError(64, f"{flag} needs a value: {_USAGE}")
129
+ value = argv[index + 1]
126
130
  index += 2
131
+ return positional, value
132
+
133
+
134
+ def _take_presentation(argv: list[str]) -> tuple[list[str], str]:
135
+ """``_take_flag`` plus the allowlist only this flag has.
136
+
137
+ Defaults to ``quiet``. ``live`` is only ever right where a screen was
138
+ declared, and the only callers that can declare one are the pane backends —
139
+ which pass the flag explicitly. Defaulting the other way assumed a screen
140
+ that a subagent dispatch does not have, and sent every worker's progress
141
+ into its caller's context window instead.
142
+ """
143
+ positional, presentation = _take_flag(argv, _PRESENTATION_FLAG, QUIET)
127
144
  if presentation not in _PRESENTATIONS:
128
145
  allowed = " | ".join(_PRESENTATIONS)
129
146
  raise PreflightError(