opencode-swarm 7.99.3 → 7.99.5

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.
@@ -86,6 +86,7 @@ Present the eleven gates with their defaults (DEFAULT_QA_GATES), parallel coder
86
86
 
87
87
  Additionally, present these two sub-items as part of the same exchange:
88
88
  - Parallel coders (default: 1, range: 1-6) -- how many coders should run in parallel. Parallel coders each run in an isolated git worktree (separate working dir + branch) and merge back automatically, so they never overwrite each other's files -- safe and faster, but only for tasks whose file scopes do NOT overlap. The per-task file scopes that determine a safe parallel count are not known until the plan is finalized, so default to 1 (serial) here; the precise recommendation is made at plan time once the tasks and their scopes exist.
89
+ > COMMON MISCONCEPTION: worktree isolation is baseline for standard parallel coders, governed by the parallel execution profile plus top-level `worktree.policy`. It is not provided by Lean Turbo or Epic. Do not recommend Lean Turbo or Epic to obtain worktree isolation; recommend them only for what they add beyond baseline (Lean Turbo: lane planning, file locks, phase reviewer, integrated diff; Epic: co-change awareness and auto-decide). Worktrees also do not make overlapping scopes safe: dependency readiness, file-disjoint scopes, and merge-back ownership are still required.
89
90
  - Commit frequency (default: phase-level only) -- optional per-task checkpoint commit after each task completion.
90
91
  - auto_proceed (boolean, default: false) -- when true, auto-advance to the next phase without asking "Ready for Phase N+1?"; runtime toggle via /swarm auto-proceed on|off.
91
92
 
@@ -70,4 +70,4 @@ When selected-track inventory or candidate generation decomposes into independen
70
70
 
71
71
  **Incremental collection:** While lanes are running, poll with `collect_lane_results` (without `wait` or `wait: false`) to check progress and process any settled lanes immediately — call `retrieve_lane_output` for full text when `output_ref` is present, extract candidates, update coverage ledger entries, validate output quality — while continuing independent work between polls. Only use `wait: true` if lanes are still pending and no more independent architect work remains.
72
72
 
73
- At every coverage, validation, and synthesis boundary, all lanes in the relevant batch must be settled before proceeding. Missing, stale, cancelled, or failed lanes are coverage gaps that must be closed before proceeding — they map to the existing `BLOCKED` invariant (#2 Coverage Closure) but with stricter resolution: (1) retry max 2 times with materially different parameters; (2) if retries fail, deploy a verified equivalent alternative (same agent type, same prompt, same scope, same isolation — different dispatch mechanism acceptable when equivalence is verified); (3) if no equivalent exists, the coverage unit becomes `BLOCKED` and the run is reported INCOMPLETE to the user. `SKIPPED_WITH_REASON` is not acceptable for dispatch-lane failures — it must be `BLOCKED` with an explicit retry/equivalent/INCOMPLETE trail.
73
+ At every coverage, validation, and synthesis boundary, all lanes in the relevant batch must be settled before proceeding. Missing, stale, cancelled, or failed lanes are coverage gaps that must be closed before proceeding — they map to the existing `BLOCKED` invariant (#2 Coverage Closure) but with stricter resolution: (1) retry max 2 times with materially different parameters; (2) if retries fail, deploy a verified equivalent alternative (same agent type, same prompt, same scope, same isolation — different dispatch mechanism acceptable when equivalence is verified, including Task-tool dispatch as the final fallback when lane tools do not work); (3) if no equivalent exists, the coverage unit becomes `BLOCKED` and the architect must surface the lane failure to the user before producing a report. `SKIPPED_WITH_REASON` is not acceptable for dispatch-lane failures — it must be `BLOCKED` with an explicit retry/equivalent/escalation trail, and no degraded review report is written.
@@ -115,9 +115,11 @@ Do NOT share other agents' responses at this stage.
115
115
  use `wait: true` if lanes are still pending and no more independent work
116
116
  remains. All three lanes must be settled before proceeding to synthesis.
117
117
  If `dispatch_lanes_async` is unavailable, use blocking `dispatch_lanes`
118
- and record that async advisory lanes were unavailable; do not substitute
119
- per-agent Task calls for this fallback unless lane tools are unavailable and
120
- you explicitly verify equivalent agent type, prompt, scope, and isolation. The
118
+ as the first fallback and record that async advisory lanes were unavailable.
119
+ This changes only when the architect waits, not whether all council lanes
120
+ must settle. Do not substitute Task-tool dispatch unless lane tools are
121
+ unavailable; when they are unavailable, Task is the final fallback and must be
122
+ verified as equivalent by agent type, prompt, scope, and isolation. The
121
123
  `round1Responses` array will contain entries with `memberId` of
122
124
  `council_generalist`, `council_skeptic`, and `council_domain_expert` and
123
125
  `role` of `generalist`, `skeptic`, and `domain_expert` respectively. If
@@ -85,9 +85,9 @@ Explorer missions are dispatched in parallel waves. Launch the wave promptly —
85
85
 
86
86
  **Incremental collection pattern:** While lanes are running, use `collect_lane_results` without `wait` (or `wait: false`) to poll progress. Process any settled lanes immediately — extract candidates, check `output_ref`, update the candidate ledger — while continuing independent architect work (scope refinement, local evidence reads, reviewer preparation) between polls. This avoids idle waiting and lets you pipeline candidate normalization with lane completion. Only use `wait: true` at the Step 4 boundary if lanes are still pending and no more independent work remains.
87
87
 
88
- At the Step 4 boundary, all lanes must be settled before proceeding. If non-blocking polls show lanes still running and you have exhausted independent work, call `collect_lane_results` with `wait: true` to block on the remaining lanes. **COVERAGE GATE:** Every lane must produce validated candidate output before proceeding. Missing, stale, cancelled, or failed lanes are coverage gaps that must be closed — not documented and skipped. If a lane fails: (1) retry max 2 times with materially different parameters; (2) if retries fail, deploy an equivalent alternative (same agent type, same prompt, same scope, same isolation — different dispatch mechanism acceptable when verified); (3) if no equivalent exists, report INCOMPLETE to the user. Do not proceed past a required lane with unclosed coverage.
88
+ At the Step 4 boundary, all lanes must be settled before proceeding. If non-blocking polls show lanes still running and you have exhausted independent work, call `collect_lane_results` with `wait: true` to block on the remaining lanes. **COVERAGE GATE:** Every lane must produce validated candidate output before proceeding. Missing, stale, cancelled, or failed lanes are coverage gaps that must be closed — not documented and skipped. If a lane fails: (1) retry max 2 times with materially different parameters; (2) if retries fail, deploy an equivalent alternative (same agent type, same prompt, same scope, same isolation — different dispatch mechanism acceptable when verified, including Task-tool dispatch as the final fallback when lane tools do not work); (3) if no equivalent exists, stop and surface the lane failure to the user as BLOCKED. Do not proceed past a required lane with unclosed coverage or produce a degraded review.
89
89
 
90
- When a collected or blocking lane result includes `output_ref`, treat `output` as a preview and call `retrieve_lane_output` before extracting candidate findings or declaring a lane clean. If the result is `output_degraded`, `transcript_incomplete`, truncated without a usable ref, missing, stale, cancelled, or failed — or if the lane reports `status: completed` but `parse_lane_candidates` returns 0 candidates (Mode B: intermediate reasoning only) — apply the COVERAGE GATE: retry, deploy equivalent, or report INCOMPLETE. Do not mark findings/coverage UNVERIFIED to proceed past the gap.
90
+ When a collected or blocking lane result includes `output_ref`, treat `output` as a preview and call `retrieve_lane_output` before extracting candidate findings or declaring a lane clean. If the result is `output_degraded`, `transcript_incomplete`, truncated without a usable ref, missing, stale, cancelled, or failed — or if the lane reports `status: completed` but `parse_lane_candidates` returns 0 candidates (Mode B: intermediate reasoning only) — apply the COVERAGE GATE: retry, deploy equivalent including Task-tool dispatch as the final fallback when lane tools do not work, or stop and surface the lane failure to the user as BLOCKED. Do not mark findings/coverage UNVERIFIED to proceed past the gap.
91
91
 
92
92
  Explorers generate CANDIDATE FINDINGS only — they do NOT make verdicts. All findings are unverified until Step 5.
93
93
 
@@ -127,10 +127,13 @@ lanes are still pending and no independent work remains. Do not advance to Step
127
127
  until every synthesis lane is settled. Collect all completed worker responses into
128
128
  a candidate findings set, each finding tagged with its subtopic, evidence refs,
129
129
  and the worker's confidence. Treat missing, stale, cancelled, or failed lanes as
130
- explicit coverage gaps. If `dispatch_lanes_async` is unavailable, use blocking
131
- `dispatch_lanes` and record that async advisory lanes were unavailable; do not
132
- substitute per-agent Task calls for this fallback unless lane tools are unavailable
133
- and you explicitly verify equivalent agent type, prompt, scope, and isolation.
130
+ explicit coverage gaps. If `dispatch_lanes_async` is unavailable, use
131
+ blocking `dispatch_lanes` as the first fallback and record that async advisory lanes were
132
+ unavailable. This changes only when the architect waits, not whether every
133
+ synthesis lane must settle before Step 5. Do not substitute Task-tool dispatch
134
+ unless lane tools are unavailable; when they are unavailable, Task is the final fallback
135
+ and must be verified as equivalent by agent type, prompt, scope, and
136
+ isolation.
134
137
 
135
138
  ## Step 5 — Dual-Reviewer Claim Verification
136
139
 
@@ -228,6 +228,7 @@ After `save_plan` succeeds, read `.swarm/context.md`:
228
228
  - final_council (default: OFF) - when enabled, after all phases complete the architect dispatches the full 5-member council (critic, reviewer, sme, test_engineer, explorer) -- NOT the General Council -- at project scope, collects `CouncilMemberVerdict` objects, and calls `write_final_council_evidence`. This does not require `council.general.enabled`.
229
229
  Additionally, present these two sub-items as part of the same exchange:
230
230
  - Parallel coders (default: 1, range: 1-6) - how many coders should run in parallel. Parallel coders each run in an isolated git worktree (separate working dir + branch) and merge back automatically, so they never overwrite each other's files - safe and faster, but only for tasks whose declared file scopes do NOT overlap. Inspect the plan and recommend a count equal to the number of dependency-ready, file-disjoint task groups (clamped 1-6); recommend 1 (serial) when scopes overlap or are unknown. State your recommendation and reasoning when you ask.
231
+ > COMMON MISCONCEPTION: worktree isolation is baseline for standard parallel coders, governed by the parallel execution profile plus top-level `worktree.policy`. It is not provided by Lean Turbo or Epic. Do not recommend Lean Turbo or Epic to obtain worktree isolation; recommend them only for what they add beyond baseline (Lean Turbo: lane planning, file locks, phase reviewer, integrated diff; Epic: co-change awareness and auto-decide). Worktrees also do not make overlapping scopes safe: dependency readiness, file-disjoint scopes, and merge-back ownership are still required.
231
232
  - Commit frequency (default: phase-level only) - optional per-task checkpoint commit after each task completion.
232
233
  The user answers all three (gates, parallel coders, commit frequency) in one exchange. Wait for the user's response.
233
234
  If the user says parallel coders > 1, write a `## Pending Parallelization Config` section to `.swarm/context.md` alongside the gate selection:
@@ -49,6 +49,7 @@ Present the eleven gates with their defaults (DEFAULT_QA_GATES), parallel coder
49
49
 
50
50
  Additionally, present these three sub-items as part of the same exchange:
51
51
  - Parallel coders (default: 1, range: 1-6) -- how many coders should run in parallel. Parallel coders each run in an isolated git worktree (separate working dir + branch) and merge back automatically, so they never overwrite each other's files -- safe and faster, but only for tasks whose file scopes do NOT overlap. The per-task file scopes that determine a safe parallel count are not known until the plan is finalized, so default to 1 (serial) here; the precise recommendation is made at plan time once the tasks and their scopes exist.
52
+ > COMMON MISCONCEPTION: worktree isolation is baseline for standard parallel coders, governed by the parallel execution profile plus top-level `worktree.policy`. It is not provided by Lean Turbo or Epic. Do not recommend Lean Turbo or Epic to obtain worktree isolation; recommend them only for what they add beyond baseline (Lean Turbo: lane planning, file locks, phase reviewer, integrated diff; Epic: co-change awareness and auto-decide). Worktrees also do not make overlapping scopes safe: dependency readiness, file-disjoint scopes, and merge-back ownership are still required.
52
53
  - Commit frequency (default: phase-level only) -- optional per-task checkpoint commit after each task completion.
53
54
  - auto_proceed (boolean, default: false) -- when true, auto-advance to the next phase without asking "Ready for Phase N+1?"; runtime toggle via /swarm auto-proceed on|off.
54
55
 
@@ -222,7 +222,9 @@ independent work is exhausted, to confirm every lane is settled.
222
222
  Missing, stale, cancelled, or failed lanes are coverage gaps that must be closed
223
223
  before marking any item RESOLVED/DISPROVED/PRE_EXISTING. Apply the COVERAGE GATE:
224
224
  retry failed lanes (max 2), deploy a verified equivalent alternative (same agent
225
- type, same prompt, same scope, same isolation), or report INCOMPLETE to the user.
225
+ type, same prompt, same scope, same isolation, with Task-tool dispatch as the
226
+ final fallback when lane tools do not work), or stop and surface the lane failure
227
+ to the user as BLOCKED.
226
228
  Do not proceed with "blocking verification and record that async advisory lanes
227
229
  were unavailable" — record-and-continue is not coverage closure.
228
230
 
@@ -146,7 +146,7 @@ Before deeper analysis, verify the PR meets the commit-pr skill's publication co
146
146
  - **Title format:** `<type>(<scope>): <description>` — lowercase description, no trailing period, allowed types: `feat`, `fix`, `perf`, `revert`, `docs`, `chore`, `refactor`, `test`, `ci`, `build`.
147
147
  - **Body contract:** `Closes #<issue-number>` as the first line (when the PR resolves an issue), followed by `## Summary`, `## Invariant audit` (all 12 invariants), and `## Test plan` sections.
148
148
 
149
- **`Closes #N` claim integrity (apply the COVERAGE GATE):** if the PR body claims `Closes #<issue-number>`, verify (a) the issue is currently open (`gh issue view <N> --json state`), and (b) the diff addresses the issue's acceptance criteria (read the issue, map each criterion to changed files/symbols, and inspect the diff for those areas). If the issue is already closed by another merged PR, do NOT re-close it — the duplicate `Closes #N` reference is misleading and will confuse release-please aggregation. If the issue is open but the diff does not address the acceptance criteria, mark the claim as `UNVERIFIED — claim integrity` in the validation provenance and report INCOMPLETE to the user.
149
+ **`Closes #N` claim integrity (apply the COVERAGE GATE):** if the PR body claims `Closes #<issue-number>`, verify (a) the issue is currently open (`gh issue view <N> --json state`), and (b) the diff addresses the issue's acceptance criteria (read the issue, map each criterion to changed files/symbols, and inspect the diff for those areas). If the issue is already closed by another merged PR, do NOT re-close it — the duplicate `Closes #N` reference is misleading and will confuse release-please aggregation. If the issue is open but the diff does not address the acceptance criteria, mark the claim as `UNVERIFIED — claim integrity` in the validation provenance and surface the unresolved claim-integrity gap to the user before synthesis.
150
150
 
151
151
  Non-compliance is a ledger item (advisory, not blocking — CI will catch it). If the PR is from an external contributor, note the compliance gap for the maintainer to address before merge.
152
152
 
@@ -517,8 +517,8 @@ Before Phase 4 or synthesis, all base lanes must be settled. `dispatch_lanes_asy
517
517
 
518
518
  For ANY lane that failed (either mode):
519
519
  1. **Retry** (max 2 attempts) with materially different parameters — different session, different prompt decomposition, or blocking `dispatch_lanes`.
520
- 2. If retries fail, **deploy an equivalent alternative** and **verify equivalence**: same agent type, same prompt, same scope, same isolation. State the equivalence verification explicitly. Task is not an early-poll or empty-partial-output fallback; use it only as a last-resort equivalent dispatch mechanism after `dispatch_lanes_async` is unavailable or a settled lane result is confirmed failed/empty, and after blocking `dispatch_lanes` is unavailable or inappropriate. Use `retrieve_lane_output` to inspect the full artifact before declaring equivalence or failure.
521
- 3. If no equivalent alternative can be verified, **report to the user as INCOMPLETE**. Present partial findings from successful lanes alongside the INCOMPLETE verdict. The user decides whether to accept reduced coverage. The architect NEVER makes that call.
520
+ 2. If retries fail, **deploy an equivalent alternative** and **verify equivalence**: same agent type, same prompt, same scope, same isolation. Fallback order is explicit: retry or re-collect `dispatch_lanes_async` first, use blocking `dispatch_lanes` when async dispatch or collection cannot close coverage, then use the Task tool as the last-resort equivalent dispatch mechanism when lane tools do not work. State the Task fallback equivalence verification explicitly. Task is not an early-poll or empty-partial-output fallback; use `retrieve_lane_output` to inspect the full artifact before declaring equivalence or failure.
521
+ 3. If no equivalent alternative can be verified, **STOP and surface the lane failure to the user as BLOCKED** with the lane id, scope, failure mode, retry attempts, and why equivalence could not be proven. Do not present partial findings, do not issue a review verdict, and do not synthesize from successful lanes. A low-quality partial review is worse than no review.
522
522
 
523
523
  ### Candidate extraction via parser
524
524
 
@@ -543,7 +543,7 @@ rather than preview-text extraction:
543
543
  lists. Each reviewer lane receives only the candidates from its assigned
544
544
  chunk.
545
545
 
546
- If a lane has `output_degraded: true`, `transcript_incomplete: true`, or no usable `output_ref`, apply the COVERAGE GATE from Phase 3: retry (max 2) with materially different parameters, deploy a verified equivalent alternative, or report INCOMPLETE to the user. Do not mark affected candidates UNVERIFIED to proceed past the gap. Never infer candidate absence from a preview.
546
+ If a lane has `output_degraded: true`, `transcript_incomplete: true`, or no usable `output_ref`, apply the COVERAGE GATE from Phase 3: retry (max 2) with materially different parameters, then use blocking `dispatch_lanes` or the Task tool as verified-equivalent fallbacks when lane tools do not work. If the gap cannot be closed, stop and surface the lane failure to the user as BLOCKED. Do not mark affected candidates UNVERIFIED to proceed past the gap. Never infer candidate absence from a preview.
547
547
 
548
548
  **Fallback convention:** If the parser is unavailable, the explorer MAY emit
549
549
  `[CANDIDATE]` rows in the lane output as a fallback convention (see the
@@ -603,7 +603,7 @@ Explorers must not use `CONFIRMED`, `DISPROVED`, or `PRE_EXISTING`.
603
603
 
604
604
  After base lanes are settled, inspect the context pack risk triggers. Launch focused micro-lanes for triggered categories only, using `dispatch_lanes_async` again when more than one read-only micro-lane is needed (`dispatch_lanes_async` accepts max 8 lanes per call — micro-lanes are dispatched in a separate batch from base lanes). Use the same incremental collection pattern: poll with `collect_lane_results` (without `wait`) to process settled micro-lanes while continuing independent work, falling back to `wait: true` only when no independent work remains. All micro-lanes must be settled before reviewer classification. Do not launch irrelevant micro-lanes.
605
605
 
606
- Apply the same parser-based extraction to micro-lanes: call `parse_lane_candidates` on each micro-lane `output_ref` (filter the returned `candidates[]` array by `row_format_family === "micro_lane"` after parsing). Apply the COVERAGE GATE from Phase 3 to micro-lanes: degraded, incomplete, or candidate-less lane artifacts are coverage gaps that must be closed by retry, verified equivalent alternative, or reported as INCOMPLETEnot treated as clean negative evidence.
606
+ Apply the same parser-based extraction to micro-lanes: call `parse_lane_candidates` on each micro-lane `output_ref` (filter the returned `candidates[]` array by `row_format_family === "micro_lane"` after parsing). Apply the COVERAGE GATE from Phase 3 to micro-lanes: degraded, incomplete, or candidate-less lane artifacts are coverage gaps that must be closed by retry, blocking `dispatch_lanes`, or Task-tool dispatch as a verified-equivalent fallback when lane tools do not work. If the gap cannot be closed, stop and surface it to the user as BLOCKED before reviewer classification never treat it as clean negative evidence and never proceed with a degraded review.
607
607
 
608
608
  Each micro-lane receives:
609
609
 
@@ -772,7 +772,7 @@ The `[CRITIC]` row in the format above is **mandatory contract**, not advisory o
772
772
 
773
773
  **Re-dispatch trigger:** when a critic lane response is missing the verdict row, the orchestrator must automatically re-dispatch that lane with the explicit instruction: "Your final line MUST be exactly the Phase 8 contract row: `[CRITIC] | finding_id | UPHELD/DOWNGRADED/DISPROVED/NEEDS_MORE_EVIDENCE | final_severity | reason | required_report_change`. A response without that exact row will be treated as a planning message and re-dispatched." Do not synthesize findings from the planning preamble; only from the re-dispatched verdict.
774
774
 
775
- **COVERAGE GATE alignment:** Critic lane failures follow the same COVERAGE GATE as explorer lanes: retry (max 2 attempts) with materially different parameters. If retries fail, deploy a verified equivalent alternative (same agent type, same prompt, same scope, same isolation). If no equivalent can be verified, report INCOMPLETE to the user — do NOT mark findings UNVERIFIED to continue past the gap. The orchestrator NEVER fabricates a critic verdict by parsing prose, by tolerating a planning preamble, or by silently accepting reduced coverage.
775
+ **COVERAGE GATE alignment:** Critic lane failures follow the same COVERAGE GATE as explorer lanes: retry (max 2 attempts) with materially different parameters. If retries fail, deploy a verified equivalent alternative (same agent type, same prompt, same scope, same isolation), including Task-tool dispatch as the final fallback when lane tools do not work. If no equivalent can be verified, stop and surface the critic-lane failure to the user as BLOCKED — do NOT mark findings UNVERIFIED or continue past the gap. The orchestrator NEVER fabricates a critic verdict by parsing prose, by tolerating a planning preamble, by presenting partial findings, or by silently accepting reduced coverage.
776
776
 
777
777
  Refuted findings become `DISPROVED` or `ADVISORY`, depending on critic rationale. Downgrades must be listed in the final validation provenance.
778
778
 
@@ -1200,9 +1200,9 @@ Council findings are supplementary, not authoritative overrides. Do not adopt co
1200
1200
  11. Obligation precedence is deterministic. Do not skip higher-precedence sources to fill gaps with LLM synthesis.
1201
1201
  12. Do not leak secrets from logs, evidence bundles, config files, URLs, or scanner output.
1202
1202
  13. Do not recommend destructive git or filesystem actions as fixes unless they are clearly scoped, safe, and necessary.
1203
- 14. If subagents fail, timeout, or return malformed output, retry with corrected parameters (max 2 attempts). If retries fail, deploy a provably equivalent alternative (same agent type, same prompt, same scope, same isolation — different dispatch mechanism acceptable) and verify equivalence. If no equivalent alternative exists, the affected coverage dimension is INCOMPLETE and must be reported to the user before synthesis. Do not fabricate validation results, and do not silently mark candidates UNVERIFIED to proceed past the gap.
1203
+ 14. If subagents fail, timeout, or return malformed output, retry with corrected parameters (max 2 attempts). If retries fail, deploy a provably equivalent alternative (same agent type, same prompt, same scope, same isolation — different dispatch mechanism acceptable), with Task-tool dispatch explicitly allowed as the final fallback when lane tools do not work, and verify equivalence. If no equivalent alternative exists, the affected coverage dimension is BLOCKED and must be surfaced to the user before synthesis. Do not fabricate validation results, do not present partial findings, and do not silently mark candidates UNVERIFIED to proceed past the gap.
1204
1204
 
1205
- 15. If context pack, repo graph, deterministic signals, or Swarm artifacts are unavailable, retry with alternative access paths. If unavailable after retry, the affected coverage dimension is INCOMPLETE and must be reported to the user. Do not proceed to synthesis with unclosed coverage gaps under a "best available evidence" rationale — the user decides whether to accept reduced coverage, not the architect.
1205
+ 15. If context pack, repo graph, deterministic signals, or Swarm artifacts are unavailable, retry with alternative access paths. If unavailable after retry, the affected coverage dimension is BLOCKED and must be surfaced to the user. Do not proceed to synthesis with unclosed coverage gaps under a "best available evidence" rationale — the architect is not authorized to produce a degraded review.
1206
1206
 
1207
1207
  ---
1208
1208
 
@@ -1245,7 +1245,7 @@ Before writing the final output, print this checklist with filled values. Every
1245
1245
 
1246
1246
  If the reviewer returned `REJECTED` or `CONCERNS`, route the issue back to implementation context or mark the candidate invalid with reason. Do not silently downgrade a rejection.
1247
1247
 
1248
- **COVERAGE GATE CONDITION:** If ANY validation dimension shows incomplete coverage (lanes that failed and were not closed by retry or verified equivalent alternative, CI that did not run, tools that were unavailable after retry), the Pre-Synthesis Gate FAILS. Do not proceed to final output. Report the unclosed gaps to the user as INCOMPLETE with the partial findings from successful dimensions. The architect is NEVER authorized to silently accept reduced coverage.
1248
+ **COVERAGE GATE CONDITION:** If ANY validation dimension shows incomplete coverage (lanes that failed and were not closed by retry or verified equivalent alternative, CI that did not run, tools that were unavailable after retry), the Pre-Synthesis Gate FAILS. Do not proceed to final output. Surface the unclosed gaps to the user as BLOCKED with exact failing dimensions and retry/equivalence evidence. Do not include partial findings from successful dimensions, do not issue a review verdict, and do not silently accept reduced coverage.
1249
1249
 
1250
1250
  ---
1251
1251
 
@@ -12,7 +12,7 @@ import {
12
12
  shouldRunOnStartup,
13
13
  writeBackupArtifact,
14
14
  writeDoctorArtifact
15
- } from "./index-svf2zjxs.js";
15
+ } from "./index-0d3pmjf9.js";
16
16
  import"./index-7hnwnw5g.js";
17
17
  import"./index-5e4e2hvv.js";
18
18
  import"./index-p0arc26j.js";
@@ -6,7 +6,7 @@ import {
6
6
  loadPlanJsonOnly,
7
7
  mergeDurableGateEntriesFromEvidence,
8
8
  readDurableGateEvidence
9
- } from "./index-6wgwybzj.js";
9
+ } from "./index-9twtnjkv.js";
10
10
  import"./index-r8f89sm9.js";
11
11
  import"./index-9b7qp18e.js";
12
12
  import"./index-94qwbx11.js";
@@ -1,15 +1,15 @@
1
1
  // @bun
2
2
  import {
3
3
  handleGuardrailExplain
4
- } from "./index-nytjghhx.js";
5
- import"./index-fhve3nj7.js";
4
+ } from "./index-tn5exv94.js";
5
+ import"./index-gjyrjr08.js";
6
6
  import"./index-xsswhy6k.js";
7
7
  import"./index-ydgy7vg5.js";
8
8
  import"./index-vwyjkzgs.js";
9
9
  import"./index-f7nma02k.js";
10
- import"./index-svf2zjxs.js";
10
+ import"./index-0d3pmjf9.js";
11
11
  import"./index-7hnwnw5g.js";
12
- import"./index-6wgwybzj.js";
12
+ import"./index-9twtnjkv.js";
13
13
  import"./index-adz3nk9b.js";
14
14
  import"./index-v4fcn4tr.js";
15
15
  import"./index-r8f89sm9.js";
@@ -79,6 +79,21 @@ function emitObjectTypeMismatch(key, value, findings) {
79
79
  });
80
80
  }
81
81
  }
82
+ function emitWorktreeIsolationLayeringAdvisory(config, findings) {
83
+ const parallelization = config.parallelization;
84
+ const worktreePolicy = config.worktree?.policy ?? "auto";
85
+ if (parallelization?.enabled === true && (parallelization.maxConcurrentTasks ?? 1) > 1 && worktreePolicy !== "disabled") {
86
+ findings.push({
87
+ id: "worktree-isolation-baseline-active",
88
+ title: "Worktree isolation is already active for standard parallel coders",
89
+ description: "Standard parallel coders already use baseline worktree isolation through the parallel execution profile plus top-level worktree.policy. Lean Turbo and Epic are additive strategies, not requirements for obtaining worktree isolation.",
90
+ severity: "warn",
91
+ path: "worktree.policy",
92
+ currentValue: worktreePolicy,
93
+ autoFixable: false
94
+ });
95
+ }
96
+ }
82
97
  function getUserConfigDir() {
83
98
  return process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
84
99
  }
@@ -639,6 +654,14 @@ function validateConfigKey(path2, value) {
639
654
  emitObjectTypeMismatch("summaries", value, findings);
640
655
  break;
641
656
  }
657
+ case "auto_review": {
658
+ emitObjectTypeMismatch("auto_review", value, findings);
659
+ break;
660
+ }
661
+ case "repo_graph": {
662
+ emitObjectTypeMismatch("repo_graph", value, findings);
663
+ break;
664
+ }
642
665
  case "review_passes": {
643
666
  emitObjectTypeMismatch("review_passes", value, findings);
644
667
  break;
@@ -715,6 +738,10 @@ function validateConfigKey(path2, value) {
715
738
  emitObjectTypeMismatch("skill_improver", value, findings);
716
739
  break;
717
740
  }
741
+ case "skills": {
742
+ emitObjectTypeMismatch("skills", value, findings);
743
+ break;
744
+ }
718
745
  case "spec_writer": {
719
746
  emitObjectTypeMismatch("spec_writer", value, findings);
720
747
  break;
@@ -900,6 +927,7 @@ function walkConfigAndValidate(obj, path2, findings, visited = new WeakSet) {
900
927
  function runConfigDoctor(config, directory) {
901
928
  const findings = [];
902
929
  walkConfigAndValidate(config, "", findings);
930
+ emitWorktreeIsolationLayeringAdvisory(config, findings);
903
931
  const summary = {
904
932
  info: findings.filter((f) => f.severity === "info").length,
905
933
  warn: findings.filter((f) => f.severity === "warn").length,
@@ -2047,6 +2047,7 @@ class PlanTaskRemovalNotAcknowledgedError extends Error {
2047
2047
  }
2048
2048
  }
2049
2049
  var startupLedgerCheckedWorkspaces = new Set;
2050
+ var ledgerStaleWorkspaces = new Set;
2050
2051
  var recoveryMutexes = new Map;
2051
2052
  var PLAN_JSON_CACHE_NAMESPACE = "plan-json:validated:v1";
2052
2053
  var _internals4 = {
@@ -2128,6 +2129,25 @@ async function getLatestLedgerHash(directory) {
2128
2129
  return "";
2129
2130
  }
2130
2131
  }
2132
+ async function surfaceLedgerStaleIfPersisted(directory, plan) {
2133
+ const resolvedWorkspace = path6.resolve(directory);
2134
+ if (!ledgerStaleWorkspaces.has(resolvedWorkspace)) {
2135
+ return plan;
2136
+ }
2137
+ try {
2138
+ const planHash = computePlanHash(plan);
2139
+ const ledgerHash = await getLatestLedgerHash(directory);
2140
+ if (ledgerHash !== "" && planHash === ledgerHash) {
2141
+ ledgerStaleWorkspaces.delete(resolvedWorkspace);
2142
+ return plan;
2143
+ }
2144
+ } catch {}
2145
+ plan._ledgerReplayStale = true;
2146
+ if (typeof plan._ledgerReplayStaleReason !== "string" || plan._ledgerReplayStaleReason.length === 0) {
2147
+ plan._ledgerReplayStaleReason = "plan.json still hash-mismatches the ledger after a startup ledger-replay failure (replay could not be applied and no critic-approved snapshot was available). Run /swarm reset-session if this persists.";
2148
+ }
2149
+ return plan;
2150
+ }
2131
2151
  async function parsePlanJsonCached(directory) {
2132
2152
  const planJsonPath = path6.resolve(directory, ".swarm", "plan.json");
2133
2153
  return readCachedParsedFile(planJsonPath, PLAN_JSON_CACHE_NAMESPACE, () => readSwarmFileAsync(directory, "plan.json"), (planJsonContent) => {
@@ -2186,7 +2206,7 @@ async function isPlanMdInSync(directory, plan, cache) {
2186
2206
  if (normalizedActual === normalizedExpected) {
2187
2207
  return true;
2188
2208
  }
2189
- return normalizedActual.includes(normalizedExpected) || normalizedExpected.includes(normalizedActual.replace(/^#.*$/gm, "").trim());
2209
+ return false;
2190
2210
  }
2191
2211
  async function regeneratePlanMarkdown(directory, plan) {
2192
2212
  const swarmDir = path6.resolve(directory, ".swarm");
@@ -2267,6 +2287,12 @@ async function loadPlan(directory, cache) {
2267
2287
  return approved.plan;
2268
2288
  }
2269
2289
  } catch {}
2290
+ {
2291
+ const runtimeStale = validated;
2292
+ runtimeStale._ledgerReplayStale = true;
2293
+ runtimeStale._ledgerReplayStaleReason = `Ledger replay failed during hash-mismatch rebuild and no approved snapshot was available: ${replayError instanceof Error ? replayError.message : String(replayError)}`;
2294
+ ledgerStaleWorkspaces.add(resolvedWorkspace);
2295
+ }
2270
2296
  warn(`[loadPlan] Ledger replay failed during hash-mismatch rebuild: ${replayError instanceof Error ? replayError.message : String(replayError)}. Returning stale plan.json. To recover: check .swarm/plan-export/SWARM_PLAN.md for a checkpoint, or run /swarm reset-session.`);
2271
2297
  }
2272
2298
  }
@@ -2311,7 +2337,7 @@ async function loadPlan(directory, cache) {
2311
2337
  } catch {}
2312
2338
  }
2313
2339
  }
2314
- return validated;
2340
+ return await surfaceLedgerStaleIfPersisted(directory, validated);
2315
2341
  }
2316
2342
  } catch (error2) {
2317
2343
  warn(`[loadPlan] plan.json validation failed: ${error2 instanceof Error ? error2.message : String(error2)}. Attempting rebuild from ledger. If rebuild fails, check .swarm/plan-export/SWARM_PLAN.md for a checkpoint.`);
@@ -57,7 +57,7 @@ import {
57
57
  readDoctorArtifact,
58
58
  removeStraySwarmDir,
59
59
  runConfigDoctor
60
- } from "./index-svf2zjxs.js";
60
+ } from "./index-0d3pmjf9.js";
61
61
  import {
62
62
  AGENT_TOOL_MAP,
63
63
  ALL_SUBAGENT_NAMES,
@@ -114,7 +114,7 @@ import {
114
114
  transientBackoff,
115
115
  validateProjectRoot,
116
116
  writeProjectedSpecSync
117
- } from "./index-6wgwybzj.js";
117
+ } from "./index-9twtnjkv.js";
118
118
  import {
119
119
  _internals as _internals2,
120
120
  _internals1 as _internals3,
@@ -909,7 +909,7 @@ var init_executor = __esm(() => {
909
909
  // package.json
910
910
  var package_default = {
911
911
  name: "opencode-swarm",
912
- version: "7.99.3",
912
+ version: "7.99.5",
913
913
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
914
914
  main: "dist/index.js",
915
915
  types: "dist/index.d.ts",
@@ -5368,6 +5368,7 @@ function resetSwarmState() {
5368
5368
  swarmState.pendingEvents = 0;
5369
5369
  swarmState.lastBudgetPct = 0;
5370
5370
  swarmState.agentSessions.clear();
5371
+ _lastIdleSweepAtMs = 0;
5371
5372
  clearTrajectoryCache();
5372
5373
  clearTrajectoryStepCounters();
5373
5374
  swarmState.pendingRehydrations.clear();
@@ -5409,8 +5410,10 @@ function resetSwarmStatePreservingSingletons() {
5409
5410
  swarmState.specWriterAgentNames = preservedSpecWriterAgentNames;
5410
5411
  swarmState.generatedAgentNames = preservedGeneratedAgentNames;
5411
5412
  }
5412
- function startAgentSession(sessionId, agentName, staleDurationMs = 7200000, directory) {
5413
- const now = Date.now();
5413
+ var STALE_SESSION_TTL_MS = 7200000;
5414
+ var IDLE_SWEEP_COOLDOWN_MS = 60000;
5415
+ var _lastIdleSweepAtMs = 0;
5416
+ function sweepStaleSessions(staleDurationMs = STALE_SESSION_TTL_MS, now = Date.now()) {
5414
5417
  const staleIds = [];
5415
5418
  for (const [id, session] of swarmState.agentSessions) {
5416
5419
  if (now - session.lastToolCallTime > staleDurationMs) {
@@ -5419,7 +5422,20 @@ function startAgentSession(sessionId, agentName, staleDurationMs = 7200000, dire
5419
5422
  }
5420
5423
  for (const id of staleIds) {
5421
5424
  swarmState.agentSessions.delete(id);
5425
+ swarmState.delegationChains.delete(id);
5426
+ }
5427
+ return staleIds;
5428
+ }
5429
+ function maybeSweepStaleSessions(staleDurationMs = STALE_SESSION_TTL_MS, now = Date.now()) {
5430
+ if (now - _lastIdleSweepAtMs < IDLE_SWEEP_COOLDOWN_MS) {
5431
+ return [];
5422
5432
  }
5433
+ _lastIdleSweepAtMs = now;
5434
+ return sweepStaleSessions(staleDurationMs, now);
5435
+ }
5436
+ function startAgentSession(sessionId, agentName, staleDurationMs = STALE_SESSION_TTL_MS, directory) {
5437
+ const now = Date.now();
5438
+ sweepStaleSessions(staleDurationMs, now);
5423
5439
  const sessionState = {
5424
5440
  agentName,
5425
5441
  lastToolCallTime: now,
@@ -5670,6 +5686,7 @@ function ensureAgentSession(sessionId, agentName, directory) {
5670
5686
  session.prSubscriptions = new Map;
5671
5687
  }
5672
5688
  session.lastToolCallTime = now;
5689
+ maybeSweepStaleSessions();
5673
5690
  return session;
5674
5691
  }
5675
5692
  _internals12.startAgentSession(sessionId, agentName ?? "unknown", 7200000, directory);
@@ -15869,6 +15886,7 @@ import { existsSync as existsSync16, mkdirSync as mkdirSync9, readFileSync as re
15869
15886
  import { homedir as homedir4 } from "os";
15870
15887
  import { join as join27 } from "path";
15871
15888
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
15889
+ var MAX_RESPONSE_BYTES = 256 * 1024;
15872
15890
  function cacheDir() {
15873
15891
  const xdg = process.env.XDG_CACHE_HOME;
15874
15892
  const base = xdg && xdg.length > 0 ? xdg : join27(homedir4(), ".cache");
@@ -17119,7 +17137,7 @@ async function handleDoctorCommand(directory, args) {
17119
17137
  const result = runConfigDoctor(config, directory);
17120
17138
  let output;
17121
17139
  if (enableAutoFix && result.hasAutoFixableIssues) {
17122
- const { runConfigDoctorWithFixes } = await import("./config-doctor-9fhfy6p7.js");
17140
+ const { runConfigDoctorWithFixes } = await import("./config-doctor-1j77p1jy.js");
17123
17141
  const fixResult = await runConfigDoctorWithFixes(directory, config, true);
17124
17142
  output = formatDoctorMarkdown(fixResult.result);
17125
17143
  } else {
@@ -17932,7 +17950,7 @@ async function handleEvidenceCommand(directory, args) {
17932
17950
  return formatTaskEvidenceMarkdown(evidenceData);
17933
17951
  }
17934
17952
  async function handleEvidenceSummaryCommand(directory) {
17935
- const { buildEvidenceSummary } = await import("./evidence-summary-service-wxarfgt8.js");
17953
+ const { buildEvidenceSummary } = await import("./evidence-summary-service-5ww1npaa.js");
17936
17954
  const artifact = await buildEvidenceSummary(directory);
17937
17955
  if (!artifact) {
17938
17956
  return "No plan found. Run `/swarm plan` to check plan status.";
@@ -26961,7 +26979,7 @@ function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
26961
26979
  };
26962
26980
  }
26963
26981
  }
26964
- const rawPathParts = workingDirectory.split(path53.sep);
26982
+ const rawPathParts = workingDirectory.split(/[\\/]/);
26965
26983
  if (rawPathParts.includes("..")) {
26966
26984
  return {
26967
26985
  success: false,
@@ -32278,7 +32296,7 @@ function buildDetailedHelp(commandName, entry) {
32278
32296
  async function handleHelpCommand(ctx) {
32279
32297
  const targetCommand = ctx.args.join(" ");
32280
32298
  if (!targetCommand) {
32281
- const { buildHelpText } = await import("./index-y1fgp4xa.js");
32299
+ const { buildHelpText } = await import("./index-s0nsx5v6.js");
32282
32300
  return buildHelpText();
32283
32301
  }
32284
32302
  const tokens = targetCommand.split(/\s+/);
@@ -32287,7 +32305,7 @@ async function handleHelpCommand(ctx) {
32287
32305
  return _internals46.buildDetailedHelp(resolved.key, resolved.entry);
32288
32306
  }
32289
32307
  const similar = _internals46.findSimilarCommands(targetCommand);
32290
- const { buildHelpText: fullHelp } = await import("./index-y1fgp4xa.js");
32308
+ const { buildHelpText: fullHelp } = await import("./index-s0nsx5v6.js");
32291
32309
  if (similar.length > 0) {
32292
32310
  return `Command '/swarm ${targetCommand}' not found.
32293
32311
 
@@ -32420,7 +32438,7 @@ var COMMAND_REGISTRY = {
32420
32438
  },
32421
32439
  "guardrail explain": {
32422
32440
  handler: async (ctx) => {
32423
- const { handleGuardrailExplain } = await import("./guardrail-explain-5fpwhqk5.js");
32441
+ const { handleGuardrailExplain } = await import("./guardrail-explain-2q5q86jv.js");
32424
32442
  return handleGuardrailExplain(ctx.directory, ctx.args);
32425
32443
  },
32426
32444
  description: "Dry-run: show what the guardrails would do to a command or write target (executes nothing)",
@@ -32759,7 +32777,7 @@ Subcommands:
32759
32777
  handler: (ctx) => handleModeCommandWithBundledSkills(ctx, handlePrReviewCommand),
32760
32778
  description: "Launch deep PR review with multi-lane analysis [url] [--council]",
32761
32779
  args: "<pr-url|owner/repo#N|N> [--council]",
32762
- details: "Launches a structured PR review: reconstructs PR intent via obligation extraction cascade, runs 6 parallel explorer lanes through the deterministic dispatch_lanes join barrier (correctness, security, dependencies, docs-intent-vs-actual, tests, performance-architecture), validates findings through independent reviewer confirmation, applies critic challenge to HIGH/CRITICAL findings, synthesizes structured report. --council variant fires adversarial multi-model review. Supports full GitHub URL, owner/repo#N shorthand, or bare PR number (resolves against origin remote).",
32780
+ details: "Launches a structured PR review: reconstructs PR intent via obligation extraction cascade, launches all 6 fixed base explorer lanes through dispatch_lanes_async while the architect keeps doing non-dependent work, polls collect_lane_results incrementally, runs every triggered micro-lane, validates findings through independent reviewer confirmation, applies critic challenge to HIGH/CRITICAL findings, then synthesizes only after coverage is closed. If lane tools cannot close coverage, Task-tool dispatch is the final verified-equivalent fallback; if equivalence cannot be proven, the review is BLOCKED rather than degraded. --council variant fires adversarial multi-model review. Supports full GitHub URL, owner/repo#N shorthand, or bare PR number (resolves against origin remote).",
32763
32781
  category: "agent",
32764
32782
  toolPolicy: "none"
32765
32783
  },
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  handleGuardrailExplain
4
- } from "./index-nytjghhx.js";
4
+ } from "./index-tn5exv94.js";
5
5
  import {
6
6
  handleGuardrailLog
7
7
  } from "./index-h9ptj4b1.js";
@@ -79,18 +79,18 @@ import {
79
79
  handleWriteRetroCommand,
80
80
  normalizeSwarmCommandInput,
81
81
  resolveCommand
82
- } from "./index-fhve3nj7.js";
82
+ } from "./index-gjyrjr08.js";
83
83
  import"./index-xsswhy6k.js";
84
84
  import"./index-ydgy7vg5.js";
85
85
  import"./index-vwyjkzgs.js";
86
86
  import"./index-f7nma02k.js";
87
- import"./index-svf2zjxs.js";
87
+ import"./index-0d3pmjf9.js";
88
88
  import {
89
89
  AGENT_TOOL_MAP,
90
90
  ORCHESTRATOR_NAME,
91
91
  stripKnownSwarmPrefix
92
92
  } from "./index-7hnwnw5g.js";
93
- import"./index-6wgwybzj.js";
93
+ import"./index-9twtnjkv.js";
94
94
  import"./index-adz3nk9b.js";
95
95
  import"./index-v4fcn4tr.js";
96
96
  import"./index-r8f89sm9.js";
@@ -12,7 +12,7 @@ import {
12
12
  detectPosixWrites,
13
13
  detectWindowsWrites,
14
14
  resolveWriteTargets
15
- } from "./index-fhve3nj7.js";
15
+ } from "./index-gjyrjr08.js";
16
16
  import {
17
17
  checkFileAuthority,
18
18
  classifyFile,
package/dist/cli/index.js CHANGED
@@ -7,16 +7,16 @@ import {
7
7
  getPluginLockFilePaths,
8
8
  package_default,
9
9
  resolveCommand
10
- } from "./index-fhve3nj7.js";
10
+ } from "./index-gjyrjr08.js";
11
11
  import"./index-xsswhy6k.js";
12
12
  import"./index-ydgy7vg5.js";
13
13
  import"./index-vwyjkzgs.js";
14
14
  import"./index-f7nma02k.js";
15
- import"./index-svf2zjxs.js";
15
+ import"./index-0d3pmjf9.js";
16
16
  import {
17
17
  DEFAULT_AGENT_CONFIGS
18
18
  } from "./index-7hnwnw5g.js";
19
- import"./index-6wgwybzj.js";
19
+ import"./index-9twtnjkv.js";
20
20
  import"./index-adz3nk9b.js";
21
21
  import"./index-v4fcn4tr.js";
22
22
  import"./index-r8f89sm9.js";
@@ -485,7 +485,7 @@ export declare const COMMAND_REGISTRY: {
485
485
  readonly handler: (ctx: CommandContext) => CommandResult;
486
486
  readonly description: "Launch deep PR review with multi-lane analysis [url] [--council]";
487
487
  readonly args: "<pr-url|owner/repo#N|N> [--council]";
488
- readonly details: "Launches a structured PR review: reconstructs PR intent via obligation extraction cascade, runs 6 parallel explorer lanes through the deterministic dispatch_lanes join barrier (correctness, security, dependencies, docs-intent-vs-actual, tests, performance-architecture), validates findings through independent reviewer confirmation, applies critic challenge to HIGH/CRITICAL findings, synthesizes structured report. --council variant fires adversarial multi-model review. Supports full GitHub URL, owner/repo#N shorthand, or bare PR number (resolves against origin remote).";
488
+ readonly details: "Launches a structured PR review: reconstructs PR intent via obligation extraction cascade, launches all 6 fixed base explorer lanes through dispatch_lanes_async while the architect keeps doing non-dependent work, polls collect_lane_results incrementally, runs every triggered micro-lane, validates findings through independent reviewer confirmation, applies critic challenge to HIGH/CRITICAL findings, then synthesizes only after coverage is closed. If lane tools cannot close coverage, Task-tool dispatch is the final verified-equivalent fallback; if equivalence cannot be proven, the review is BLOCKED rather than degraded. --council variant fires adversarial multi-model review. Supports full GitHub URL, owner/repo#N shorthand, or bare PR number (resolves against origin remote).";
489
489
  readonly category: "agent";
490
490
  readonly toolPolicy: "none";
491
491
  };
@@ -178,6 +178,25 @@ export type Plan = z.infer<typeof PlanSchema>;
178
178
  * `_midLoadRemovals` is attached by loadPlan-recovery paths that auto-
179
179
  * acknowledged task removals (issue #853) so the system-enhancer Layer A
180
180
  * can disclose the count to the model without re-reading the ledger.
181
+ *
182
+ * `_ledgerReplayStale` / `_ledgerReplayStaleReason` are attached by loadPlan
183
+ * when it returns a STALE plan.json: the plan.json hash mismatched the ledger,
184
+ * ledger replay failed (threw), AND no critic-approved snapshot was available,
185
+ * so the loader fell back to the (stale) plan.json (#1269 finding 2). Consumers
186
+ * in phase-complete.ts and update-task-status.ts read these to surface a
187
+ * structured staleness signal instead of silently trusting plan.json.
188
+ *
189
+ * RUNTIME-ONLY CONTRACT (mirrors `_specStale`): every field on RuntimePlan is a
190
+ * TypeScript-only overlay on the persisted `Plan`. It is NOT part of the durable
191
+ * `PlanSchema` (see this file ~line 95 — a plain `z.object`, no `.passthrough()`,
192
+ * so Zod strips unknown keys), so `savePlan`'s `JSON.stringify(PlanSchema.parse(...))`
193
+ * can never write these to .swarm/plan.json. It is also excluded from plan hashing:
194
+ * both `computePlanHash` (src/plan/ledger.ts) and `computePlanContentHash`
195
+ * (src/plan/manager.ts) hash an explicit allow-list of fields, never the whole
196
+ * object. Because of this, AGENTS.md invariant-5's "six places" (ledger replay,
197
+ * projection, checkpoint import/export, get_approved_plan, tests, docs) do NOT
198
+ * all apply — these are not durable schema fields. Do NOT move these into
199
+ * `PlanSchema`.
181
200
  */
182
201
  export type RuntimePlan = Plan & {
183
202
  _specStale?: boolean;
@@ -186,6 +205,8 @@ export type RuntimePlan = Plan & {
186
205
  count: number;
187
206
  source: string;
188
207
  };
208
+ _ledgerReplayStale?: boolean;
209
+ _ledgerReplayStaleReason?: string;
189
210
  };
190
211
  /**
191
212
  * Find the first phase that is in progress.