400	   - Display the error output and exit code
401	   - Warn: "The external command may have left uncommitted changes or partial edits
402	     in the working tree. Review `git status` and `git diff` before proceeding."
403	   - Offer three choices:
404	     - **retry** — run the same plan through cross-AI again
405	     - **skip** — fall back to normal executor for this plan (re-add to execute_waves list)
406	     - **abort** — stop execution entirely, preserve state for resume
407
408	5. **After all cross-AI plans processed:** Remove successfully handled plans from the
409	   incomplete plan list so execute_waves skips them. Any skipped-to-fallback plans remain
410	   in the list for normal executor processing.
411	</step>
412
413	<step name="execute_waves">
414	Execute each selected wave in sequence. Within a wave: parallel if `PARALLELIZATION=true`, sequential if `false`.
415
416	**Stream-idle-timeout prevention — checkpoint heartbeats (#2410):**
417
418	Multi-plan phases can accumulate enough subagent context that the Claude API
419	SSE layer terminates with `Stream idle timeout - partial response received`
420	between a large tool_result and the next assistant turn (seen on Claude Code
421	+ Opus 4.7 at ~200K+ cache_read). To keep the stream warm, emit short
422	assistant-text heartbeats — **no tool call, just a literal line** — at every
423	wave and plan boundary. Each heartbeat MUST start with `[checkpoint]` so
424	tooling and `/gsd:manager`'s background-completion handler can grep partial
425	transcripts. `{P}/{Q}` is the phase-wide completed/total plans counter and
426	increases monotonically across waves. `{status}` is `complete` (success),
427	`failed` (executor error), or `checkpoint` (human-gate returned).
428
429	```
430	[checkpoint] phase {PHASE_NUMBER} wave {N}/{M} starting, {wave_plan_count} plan(s), {P}/{Q} plans done
431	[checkpoint] phase {PHASE_NUMBER} wave {N}/{M} plan {plan_id} starting ({P}/{Q} plans done)
432	[checkpoint] phase {PHASE_NUMBER} wave {N}/{M} plan {plan_id} {status} ({P}/{Q} plans done)
433	[checkpoint] phase {PHASE_NUMBER} wave {N}/{M} complete, {P}/{Q} plans done ({wave_success}/{wave_plan_count} ok)
434	```
435
436	**For each wave:**
437
438	1. **Intra-wave files_modified overlap check (BEFORE spawning):**
439
440	   Before spawning any agents for this wave, inspect the `files_modified` list of all plans
441	   in the wave. Check every pair of plans in the wave — if any two plans share even one file
442	   in their `files_modified` lists, those plans have an implicit dependency and MUST NOT run
443	   in parallel.
444
445	   **Detection algorithm (pseudocode):**
446	   ```
447	   seen_files = {}
448	   overlapping_plans = []
449	   for each plan in wave_plans:
450	     for each file in plan.files_modified:
451	       if file in seen_files:
452	         overlapping_plans.add(plan, seen_files[file])  # both plans overlap on this file
453	       else:
454	         seen_files[file] = plan
455	   ```
456
457	   **If overlap is detected:**
458	   - Warn the user:
459	     ```
460	     ⚠ Intra-wave files_modified overlap detected in Wave {N}:
461	       Plan {A} and Plan {B} both modify {file}
462	       Running these plans sequentially to avoid parallel worktree conflicts.
463	     ```
464	   - Override `PARALLELIZATION` to `false` for this wave only — run all plans in the wave
465	     sequentially regardless of the global parallelization setting.
466	   - This is a safety net for plans that were incorrectly assigned to the same wave.
467	     The planner should have caught this; flag it as a planning defect so the user can
468	     replan the phase if desired.
469
470	   **If no overlap:** proceed normally (parallel if `PARALLELIZATION=true`).
471
472	2. **Describe what's being built (BEFORE spawning):**
473
474	   **First, emit the wave-start checkpoint heartbeat as a literal assistant-text
475	   line — no tool call (#2410). Do NOT skip this even for single-plan waves; it
476	   is required before any further reasoning or spawning:**
477
478	   ```
479	   [checkpoint] phase {PHASE_NUMBER} wave {N}/{M} starting, {wave_plan_count} plan(s), {P}/{Q} plans done
480	   ```
481
482	   Then read each plan's `<objective>`. Extract what's being built and why.
483
484	   ```
485	   ---
486	   ## Wave {N}
487
488	   **{Plan ID}: {Plan Name}**
489	   {2-3 sentences: what this builds, technical approach, why it matters}
490
491	   Spawning {count} agent(s)...
492	   ---
493	   ```
494
495	   - Bad: "Executing terrain generation plan"
496	   - Good: "Procedural terrain generator using Perlin noise — creates height maps, biome zones, and collision meshes. Required before vehicle physics can interact with ground."
497
498	2.5. **Per-plan worktree decision (run for each plan in this wave BEFORE its dispatch):**
499
500	   Read and execute `get-shit-done/workflows/execute-phase/steps/per-plan-worktree-gate.md` for each plan. It extracts `PLAN_FILES` from the plan's JSON, intersects against `SUBMODULE_PATHS` (with normalization, bidirectional matching, and glob-prefix handling), and sets `USE_WORKTREES_FOR_PLAN` to `false` when the plan touches a submodule path. Append `plan_id` to a `WAVE_WORKTREE_PLANS` accumulator when `USE_WORKTREES_FOR_PLAN != false`.
501
502	   The dispatch branches in step 3 below MUST gate on `USE_WORKTREES_FOR_PLAN` for the current plan, not on the project-level `USE_WORKTREES`.
503
504	3. **Spawn executor agents:**
505
506	   **Emit a plan-start heartbeat (literal line, no tool call) immediately before
507	   each `Agent()` dispatch (#2410):**
508
509	   `[checkpoint] phase {PHASE_NUMBER} wave {N}/{M} plan {plan_id} starting ({P}/{Q} plans done)`
510
511	   Pass paths only — executors read files themselves with their fresh context window.
512	   For 200k models, this keeps orchestrator context lean (~10-15%).
513	   For 1M+ models (Opus 4.6, Sonnet 4.6), richer context can be passed directly.
514
515	   **Worktree mode** (`USE_WORKTREES_FOR_PLAN` is not `false` — evaluated per-plan in step 2.5):
516
517	   Before spawning, capture the current HEAD:
518	   ```bash
519	   EXPECTED_BASE=$(git rev-parse HEAD)
520	   DISPATCH_TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
521	   EXPECTED_BRANCH=$(git rev-parse --abbrev-ref HEAD)
522	   if [ "${USE_WORKTREES_FOR_PLAN:-true}" != "false" ] && [ -z "${WAVE_WORKTREE_MANIFEST:-}" ]; then
523	     WAVE_WORKTREE_MANIFEST=$(mktemp "${TMPDIR:-/tmp}/gsd-worktree-wave-XXXXXX.json")
524	     printf '{"worktrees":[]}\n' > "$WAVE_WORKTREE_MANIFEST"
525	     export WAVE_WORKTREE_MANIFEST
526	   fi
527	   ```
528
529	   **Sequential dispatch for parallel execution (waves with 2+ agents):**
530	   Dispatch each `Agent()` call **one at a time with `run_in_background: true`**. Do NOT
531	   send all Agent calls in a single message: simultaneous `git worktree add` calls race
532	   on `.git/config.lock`. Agents still run in parallel once their worktrees are created.
533
534	   ```text
535	   # CORRECT: one Agent() per message with run_in_background: true
536	   # WRONG: multiple Agent() calls in one message -> .git/config.lock contention
537	   ```
538
539	   ```text
540	   Agent(
541	     subagent_type="gsd-executor",
542	     description="Execute plan {plan_number} of phase {phase_number}",
543	     # Only include model= when executor_model is an explicit model name.
544	     # When executor_model is "inherit", omit this parameter entirely so
545	     # Claude Code inherits the orchestrator model automatically.
546	     model="{executor_model}",  # omit this line when executor_model == "inherit"
547	     isolation="worktree",
548	     prompt="
549	       <objective>
550	       Execute plan {plan_number} of phase {phase_number}-{phase_name}.
551	       Commit each task atomically. Create SUMMARY.md.
552	       Do NOT update STATE.md or ROADMAP.md — the orchestrator owns those writes after all worktree agents in the wave complete.
553	       </objective>
554
555	       <worktree_branch_check>
556	       FIRST ACTION: HEAD assertion MUST run before any reset/checkout. Worktrees
557	       spawned by Claude Code's `isolation="worktree"` use the `worktree-agent-<id>`
558	       namespace. If HEAD is on a protected ref (main/master/develop/trunk/release/*)
559	       or detached, HALT — do NOT self-recover by force-rewinding via `git update-ref`,
560	       that destroys concurrent commits in multi-active scenarios (#2924). Only after
561	       Step 1 passes is `git reset --hard` safe (#2015 — affects all platforms).
562	       ```bash
563	       HEAD_REF=$(git symbolic-ref --quiet HEAD || echo "DETACHED")
564	       ACTUAL_BRANCH=$(git rev-parse --abbrev-ref HEAD)
565	       if [ "$HEAD_REF" = "DETACHED" ] || echo "$ACTUAL_BRANCH" | grep -Eq '^(main|master|develop|trunk|release/.*)$'; then
566	         echo "FATAL: worktree HEAD on '$ACTUAL_BRANCH' (expected worktree-agent-*); refusing to self-recover via 'git update-ref' (#2924)." >&2
567	         exit 1
568	       fi
569	       if ! echo "$ACTUAL_BRANCH" | grep -Eq '^worktree-agent-[A-Za-z0-9._/-]+$'; then
570	         echo "FATAL: worktree HEAD '$ACTUAL_BRANCH' is not in the worktree-agent-* namespace; refusing to commit (#2924)." >&2
571	         exit 1
572	       fi
573	       ACTUAL_BASE=$(git merge-base HEAD {EXPECTED_BASE})
574	       if [ "$ACTUAL_BASE" != "{EXPECTED_BASE}" ]; then
575	         git reset --hard {EXPECTED_BASE}
576	         [ "$(git rev-parse HEAD)" != "{EXPECTED_BASE}" ] && { echo "ERROR: could not correct worktree base"; exit 1; }
577	       fi
578	       ```
579	       Per-commit HEAD/cwd-drift/path-guard: `agents/gsd-executor.md` steps 0/0a/0b + `references/worktree-path-safety.md` (in <execution_context>).
580	       </worktree_branch_check>
581
582	       <parallel_execution>
583	       You are running as a PARALLEL executor agent in a git worktree. Worktree path safety (cwd-drift, absolute-path guards) is in `worktree-path-safety.md` (loaded below).
584	       Run `git commit` normally — hooks run by default. Do NOT pass `--no-verify`
585	       unless the orchestrator surfaces `workflow.worktree_skip_hooks=true` in this
586	       prompt; silent bypass violates project CLAUDE.md guidance (#2924).
587
588	       IMPORTANT: Do NOT modify STATE.md or ROADMAP.md. execute-plan.md
589	       auto-detects worktree mode (`.git` is a file, not a directory) and skips
590	       shared file updates automatically. The orchestrator updates them centrally
591	       after merge.
592
593	       REQUIRED: SUMMARY.md MUST be committed before you return. In worktree mode the
594	       git_commit_metadata step in execute-plan.md commits SUMMARY.md and REQUIREMENTS.md
595	       only (STATE.md and ROADMAP.md are excluded automatically). Do NOT skip or defer
596	       this commit — the orchestrator force-removes the worktree after you return, and
597	       any uncommitted SUMMARY.md will be permanently lost (#2070).
598	       REQUIRED ORDER: Write SUMMARY.md → commit → only then any narration. No text between Write and commit (truncation risk; #2070 rescue is not primary defense).
599	       </parallel_execution>
600
601	       <execution_context>
602	       @$HOME/.claude/get-shit-done/workflows/execute-plan.md
603	       @$HOME/.claude/get-shit-done/templates/summary.md
604	       @$HOME/.claude/get-shit-done/references/checkpoints.md
605	       @$HOME/.claude/get-shit-done/references/tdd.md
606	       @$HOME/.claude/get-shit-done/references/worktree-path-safety.md
607	       ${CONTEXT_WINDOW < 200000 ? '' : '@$HOME/.claude/get-shit-done/references/executor-examples.md'}
608	       </execution_context>
609
610	       <files_to_read>
611	       Read these files at execution start using the Read tool:
612	       - {phase_dir}/{plan_file} (Plan)
613	       - .planning/PROJECT.md (Project context — core value, requirements, evolution rules)
614	       - .planning/STATE.md (State)
615	       - .planning/config.json (Config, if exists)
616	       ${CONTEXT_WINDOW >= 500000 ? `
617	       - ${phase_dir}/*-CONTEXT.md (User decisions from discuss-phase — honors locked choices)
618	       - ${phase_dir}/*-RESEARCH.md (Technical research — pitfalls and patterns to follow)
619	       - ${prior_wave_summaries} (SUMMARY.md files from earlier waves in this phase — what was already built)
620	       ` : ''}
621	       - ./CLAUDE.md (Project instructions, if exists — follow project-specific guidelines and coding conventions)
622	       - .claude/skills/ or .agents/skills/ (Project skills, if either exists — list skills, read SKILL.md for each, follow relevant rules during implementation)
623	       </files_to_read>
624
625	       ${AGENT_SKILLS}
626
627	       <mcp_tools>
628	       If CLAUDE.md or project instructions reference MCP tools (e.g. jCodeMunch, context7,
629	       or other MCP servers), prefer those tools over Grep/Glob for code navigation when available.
630	       MCP tools often save significant tokens by providing structured code indexes.
631	       Check tool availability first — if MCP tools are not accessible, fall back to Grep/Glob.
632	       </mcp_tools>
633
634	       <success_criteria>
635	       - [ ] All tasks executed
636	       - [ ] Each task committed individually
637	       - [ ] SUMMARY.md created in plan directory
638	       - [ ] No modifications to shared orchestrator artifacts (the orchestrator handles all post-wave shared-file writes)
639	       </success_criteria>
640	     "
641	   )
642	   ```
643
644	   Immediately after each worktree `Agent()` spawn returns metadata, atomically append `{agent_id, worktree_path, branch, expected_base}` to `WAVE_WORKTREE_MANIFEST`. If any field is missing, stop and ask for recovery instead of scanning all agent worktrees.
645
646	   > **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above to spawn executor agent(s), stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
647
648	   **Sequential mode** (`USE_WORKTREES_FOR_PLAN` is `false` — either project-level `USE_WORKTREES=false`, or per-plan submodule intersection forced it false in step 2.5):
649
650	   Omit `isolation="worktree"` from the Agent call. Replace the `<parallel_execution>` block with:
651
652	   ```
653	       <sequential_execution>
654	       You are running as a SEQUENTIAL executor agent on the main working tree.
655	       Use normal git commits (with hooks). Do NOT use --no-verify.
656	       REQUIRED ORDER: Write SUMMARY.md → commit → only then any narration. No text between Write and commit (truncation risk; #2070 rescue is not primary defense).
657	       </sequential_execution>
658	   ```
659
660	   The sequential mode Agent prompt uses the same structure as worktree mode but with these differences in success_criteria — since there is only one agent writing at a time, there are no shared-file conflicts:
661
662	   ```
663	       <success_criteria>
664	       - [ ] All tasks executed
665	       - [ ] Each task committed individually
666	       - [ ] SUMMARY.md created in plan directory
667	       - [ ] STATE.md updated with position and decisions
668	       - [ ] ROADMAP.md updated with plan progress (via `roadmap update-plan-progress`)
669	       </success_criteria>
670	   ```
671
672	   When worktrees are disabled for a plan (per-plan or project-level), that plan's executor runs on the main working tree. If **any** plan in the current wave dropped to sequential mode, execute the affected plan(s) **one at a time** to avoid concurrent writes to the main working tree — plans in the same wave that retained worktree isolation can still run in parallel alongside the sequential ones, but two non-worktree plans in the same wave must serialize. When the project-level `USE_WORKTREES=false`, all plans in the wave serialize regardless of the `PARALLELIZATION` setting.
673
674	4. **Wait for all agents in wave to complete.**
675
676	   **Plan-complete heartbeat (#2410):** as each executor returns (or is verified
677	   via spot-check below), emit one line — `complete` advances `{P}`, `failed`
678	   and `checkpoint` do not but still warm the stream:
679
680	   ```
681	   [checkpoint] phase {PHASE_NUMBER} wave {N}/{M} plan {plan_id} complete ({P}/{Q} plans done)
682	   [checkpoint] phase {PHASE_NUMBER} wave {N}/{M} plan {plan_id} failed ({P}/{Q} plans done)
683	   [checkpoint] phase {PHASE_NUMBER} wave {N}/{M} plan {plan_id} checkpoint ({P}/{Q} plans done)
684	   ```
685
686	   **Completion signal fallback (Copilot and runtimes where Agent() may not return):**
687
688	   If a spawned agent does not return a completion signal but appears to have finished
689	   its work, do NOT block indefinitely. Instead, verify completion via spot-checks:
690
691	   ```bash
692	   # For each plan in this wave, check if the executor finished:
693	   SUMMARY_EXISTS=$(test -f "{phase_dir}/{plan_number}-{plan_padded}-SUMMARY.md" && echo "true" || echo "false")
694	   COMMITS_FOUND=$(git log --oneline --all --grep="{phase_number}-{plan_padded}" --since="1 hour ago" | head -1)
695	   COMMITS_SINCE_DISPATCH=$(git log "${EXPECTED_BRANCH}" --since="${DISPATCH_TS}" --oneline | head -1)
696	   ```
697
698	   **If SUMMARY.md exists AND commits are found:** The agent completed successfully —
699	   treat as done and proceed to step 5. Log: `"✓ {Plan ID} completed (verified via spot-check — completion signal not received)"`
700
701	   **If SUMMARY.md does NOT exist after a reasonable wait:** The agent may still be
702	   running or may have failed silently. Check `git log --oneline -5` for recent
703	   activity. If commits are still appearing, wait longer. If no activity, report
704	   the plan as failed and route to the failure handler in step 6.
705
706	   **Configurable stall surveillance (#3212):** Every `${EXECUTOR_STALL_INTERVAL_MINUTES}`
707	   minutes while waiting, inspect `git log "${EXPECTED_BRANCH}" --since="${DISPATCH_TS}"`
708	   for activity. If no completion signal, no SUMMARY.md, and no expected-branch
709	   commits appear for `${EXECUTOR_STALL_THRESHOLD_MINUTES}` minutes, pause and
710	   ask for one recovery path: `continue waiting`, `kill and retry`, or
711	   `kill and switch to inline execution`.
712
713	   **This fallback applies automatically to all runtimes.** Claude Code's Agent() normally
714	   returns synchronously, but the fallback ensures resilience if it doesn't.
715
716	5. **Post-wave hook validation (parallel mode only):** Hooks run on every executor commit by default (#2924); this post-wave run only fires when `workflow.worktree_skip_hooks=true` opted out of per-commit hooks:
717	   ```bash
718	   SKIP_HOOKS=$(gsd-sdk query config-get workflow.worktree_skip_hooks 2>/dev/null || echo "false")
719	   if [ "$SKIP_HOOKS" = "true" ]; then
720	     # Stash uncommitted changes under a named ref so we always pop (bare `git stash` strands them on hook/script failure). #3542: `refs/stash` is shared across worktrees, so this helper runs ONLY in the orchestrator's main checkout after all wave worktrees have been merged + removed; executors are forbidden from running any `git stash` subcommand (see `<destructive_git_prohibition>` in `agents/gsd-executor.md`).
721	     STASHED=false
722	     if (! git diff --quiet || ! git diff --cached --quiet) && git stash push -u -m "gsd-post-wave-hook-$$" >/dev/null 2>&1; then STASHED=true; fi
723	     git hook run pre-commit 2>&1 || echo "⚠ Pre-commit hooks failed — review before continuing"
724	     [ "$STASHED" = "true" ] && (git stash pop >/dev/null 2>&1 || echo "⚠ Could not pop gsd-post-wave-hook stash — recover manually")
725	   fi
726	   ```
727	   If hooks fail: report the failure and ask "Fix hook issues now?" or "Continue to next wave?"
728
729	5.5. **Worktree cleanup (when `isolation="worktree"` was used):**
730
731	   **Standard wave contract:** Each wave's worktrees merge to main via the templated path below before the next wave's worktrees fork. The cleanup loop runs once per wave at the end of the wave lifecycle. Worktrees created in wave N must be fully removed before wave N+1 forks new ones.
732
733	   **Cross-wave dependency deviation (supported execution mode):** When the orchestrator legitimately deviates from the standard wave model — for example, a phase with cross-wave plan dependencies that requires custom inter-worktree base-update merges (e.g., `merge: bring 09-01 + 09-02 into 09-03 base`) — the cleanup loop below is NOT automatically re-entered for those custom merges. The deviation path produces correct final history but bypasses this loop, leaving `worktree-agent-*` directories in place. Use the **cleanup-tail snippet** below to remove any residual worktrees after such a deviation.
734
735	   When executor agents ran in worktree isolation, their commits land on temporary branches in separate working trees. After the wave completes, merge these changes back and clean up:
736
737	   **Manifest source of truth (#3384):** Cleanup consumes the `WAVE_WORKTREE_MANIFEST` created and populated during executor dispatch in step 3. Do not recreate or truncate it here.
738
739	   Prefer the bounded helper, which validates branch identity, expected base, deletion
740	   diffs, merge result, and worktree removal before deleting the temporary branch.
741	   If the helper reports a blocked cleanup, resolve the reported manifest entry and
742	   rerun the same command. Do not fall back to broad worktree discovery.
743
744	   ```bash
745	   [ -n "${WAVE_WORKTREE_MANIFEST:-}" ] && [ -f "$WAVE_WORKTREE_MANIFEST" ] || {
746	     echo "BLOCKED: missing WAVE_WORKTREE_MANIFEST; refusing broad worktree cleanup (#3384)." >&2
747	     exit 1
748	   }
749
750	   # Guard: pin cleanup back to the primary worktree and fail on branch drift (#3174).
751	   PRIMARY_WT=$(git worktree list --porcelain | awk '/^worktree /{print substr($0,10); exit}')
752	   if [ -z "$PRIMARY_WT" ]; then
753	     echo "FATAL: could not resolve primary worktree before cleanup" >&2
754	     exit 1
755	   fi
756	   if [ -n "$PRIMARY_WT" ] && [ "$(pwd -P 2>/dev/null)" != "$(cd "$PRIMARY_WT" 2>/dev/null && pwd -P)" ]; then echo "⚠ Orchestrator CWD drifted to $(pwd) — pinning to $PRIMARY_WT before worktree cleanup (#3174)"; cd "$PRIMARY_WT" || { echo "FATAL: cannot cd to primary worktree $PRIMARY_WT" >&2; exit 1; }; fi
757	   ORCH_BRANCH=$(git rev-parse --abbrev-ref HEAD)
758	   [ -z "${EXPECTED_BRANCH:-}" ] || [ "$ORCH_BRANCH" = "$EXPECTED_BRANCH" ] || { echo "FATAL: orchestrator on '$ORCH_BRANCH' but expected '$EXPECTED_BRANCH' before worktree cleanup — refusing to merge (#3174-class drift)" >&2; exit 1; }
759
760	   if command -v gsd-sdk >/dev/null 2>&1; then
761	     gsd-sdk query worktree.cleanup-wave --manifest "$WAVE_WORKTREE_MANIFEST" || exit 1
762	   else
763	     echo "WARN: gsd-sdk unavailable; using manifest-scoped shell fallback (#3384)." >&2
764	   WT_PATHS_FILE=$(mktemp "${TMPDIR:-/tmp}/gsd-worktree-paths-XXXXXX")
765	   node -e 'const fs=require("fs");const p=process.env.WAVE_WORKTREE_MANIFEST;try{if(!p)throw new Error("WAVE_WORKTREE_MANIFEST is unset");if(!fs.existsSync(p))throw new Error("manifest does not exist");const s=fs.readFileSync(p,"utf8");if(!s.trim())throw new Error("manifest is empty");const j=JSON.parse(s);for(const w of j.worktrees||[])if(w.worktree_path)console.log(w.worktree_path)}catch(e){console.error(`ERROR: cannot read worktree manifest ${p||"(unset)"}: ${e.message}`);process.exit(1)}' > "$WT_PATHS_FILE" || { echo "BLOCKED: cannot read WAVE_WORKTREE_MANIFEST; refusing cleanup (#3384)." >&2; exit 1; }
766	   while IFS= read -r WT; do
767	     [ -z "$WT" ] && continue
768	     WT_BRANCH=$(git -C "$WT" rev-parse --abbrev-ref HEAD 2>/dev/null)
769	     if [ -n "$WT_BRANCH" ] && [ "$WT_BRANCH" != "HEAD" ]; then
770	       CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
771	       STATE_BACKUP=$(mktemp)
772	       ROADMAP_BACKUP=$(mktemp)
773	       [ -f .planning/STATE.md ] && cp .planning/STATE.md "$STATE_BACKUP" || true
774	       [ -f .planning/ROADMAP.md ] && cp .planning/ROADMAP.md "$ROADMAP_BACKUP" || true
775	       DELETIONS=$(git diff --diff-filter=D --name-only HEAD..."$WT_BRANCH" 2>/dev/null || true)
776	       if [ -n "$DELETIONS" ]; then
777	         echo "BLOCKED: Worktree branch $WT_BRANCH contains file deletions: $DELETIONS"
778	         echo "Review these deletions before merging. If intentional, remove this guard and re-run."
779	         rm -f "$STATE_BACKUP" "$ROADMAP_BACKUP"
780	         continue
781	       fi
782	       git merge "$WT_BRANCH" --no-ff --no-edit -m "chore: merge executor worktree ($WT_BRANCH)" 2>&1 || {
783	         echo "⚠ Merge conflict from worktree $WT_BRANCH — resolve manually"
784	         echo "  STATE.md backup:   $STATE_BACKUP"
785	         echo "  ROADMAP.md backup: $ROADMAP_BACKUP"
786	         echo "  Restore with: cp \$STATE_BACKUP .planning/STATE.md && cp \$ROADMAP_BACKUP .planning/ROADMAP.md"
787	         break
788	       }
789	       MERGE_DEL_COUNT=$(git diff --diff-filter=D --name-only HEAD~1 HEAD 2>/dev/null | grep -vc '^\.planning/' || true)
790	       if [ "$MERGE_DEL_COUNT" -gt 5 ] && [ "${ALLOW_BULK_DELETE:-0}" != "1" ]; then
791	         MERGE_DELETIONS=$(git diff --diff-filter=D --name-only HEAD~1 HEAD 2>/dev/null | grep -v '^\.planning/' || true)
792	         echo "⚠ BLOCKED: Merge of $WT_BRANCH deleted $MERGE_DEL_COUNT files outside .planning/ — reverting to protect repository integrity (#2384)"
793	         echo "$MERGE_DELETIONS"
794	         echo "  If these deletions are intentional, re-run with ALLOW_BULK_DELETE=1"
795	         git reset --hard HEAD~1 2>/dev/null || true
796	         rm -f "$STATE_BACKUP" "$ROADMAP_BACKUP"
797	         continue
798	       fi
799	       if [ -s "$STATE_BACKUP" ]; then

   - Display the error output and exit code
   - Warn: "The external command may have left uncommitted changes or partial edits
     in the working tree. Review `git status` and `git diff` before proceeding."
   - Offer three choices:
     - **retry** — run the same plan through cross-AI again
     - **skip** — fall back to normal executor for this plan (re-add to execute_waves list)
     - **abort** — stop execution entirely, preserve state for resume

5. **After all cross-AI plans processed:** Remove successfully handled plans from the
   incomplete plan list so execute_waves skips them. Any skipped-to-fallback plans remain
   in the list for normal executor processing.
</step>

<step name="execute_waves">
Execute each selected wave in sequence. Within a wave: parallel if `PARALLELIZATION=true`, sequential if `false`.

**Stream-idle-timeout prevention — checkpoint heartbeats (#2410):**

Multi-plan phases can accumulate enough subagent context that the Claude API
SSE layer terminates with `Stream idle timeout - partial response received`
between a large tool_result and the next assistant turn (seen on Claude Code
+ Opus 4.7 at ~200K+ cache_read). To keep the stream warm, emit short
assistant-text heartbeats — **no tool call, just a literal line** — at every
wave and plan boundary. Each heartbeat MUST start with `[checkpoint]` so
tooling and `/gsd:manager`'s background-completion handler can grep partial
transcripts. `{P}/{Q}` is the phase-wide completed/total plans counter and
increases monotonically across waves. `{status}` is `complete` (success),
`failed` (executor error), or `checkpoint` (human-gate returned).

```
[checkpoint] phase {PHASE_NUMBER} wave {N}/{M} starting, {wave_plan_count} plan(s), {P}/{Q} plans done
[checkpoint] phase {PHASE_NUMBER} wave {N}/{M} plan {plan_id} starting ({P}/{Q} plans done)
[checkpoint] phase {PHASE_NUMBER} wave {N}/{M} plan {plan_id} {status} ({P}/{Q} plans done)
[checkpoint] phase {PHASE_NUMBER} wave {N}/{M} complete, {P}/{Q} plans done ({wave_success}/{wave_plan_count} ok)
```

**For each wave:**

1. **Intra-wave files_modified overlap check (BEFORE spawning):**

   Before spawning any agents for this wave, inspect the `files_modified` list of all plans
   in the wave. Check every pair of plans in the wave — if any two plans share even one file
   in their `files_modified` lists, those plans have an implicit dependency and MUST NOT run
   in parallel.

   **Detection algorithm (pseudocode):**
   ```
   seen_files = {}
   overlapping_plans = []
   for each plan in wave_plans:
     for each file in plan.files_modified:
       if file in seen_files:
         overlapping_plans.add(plan, seen_files[file])  # both plans overlap on this file
       else:
         seen_files[file] = plan
   ```

   **If overlap is detected:**
   - Warn the user:
     ```
     ⚠ Intra-wave files_modified overlap detected in Wave {N}:
       Plan {A} and Plan {B} both modify {file}
       Running these plans sequentially to avoid parallel worktree conflicts.
     ```
   - Override `PARALLELIZATION` to `false` for this wave only — run all plans in the wave
     sequentially regardless of the global parallelization setting.
   - This is a safety net for plans that were incorrectly assigned to the same wave.
     The planner should have caught this; flag it as a planning defect so the user can
     replan the phase if desired.

   **If no overlap:** proceed normally (parallel if `PARALLELIZATION=true`).

2. **Describe what's being built (BEFORE spawning):**

   **First, emit the wave-start checkpoint heartbeat as a literal assistant-text
   line — no tool call (#2410). Do NOT skip this even for single-plan waves; it
   is required before any further reasoning or spawning:**

   ```
   [checkpoint] phase {PHASE_NUMBER} wave {N}/{M} starting, {wave_plan_count} plan(s), {P}/{Q} plans done
   ```

   Then read each plan's `<objective>`. Extract what's being built and why.

   ```
   ---
   ## Wave {N}

   **{Plan ID}: {Plan Name}**
   {2-3 sentences: what this builds, technical approach, why it matters}

   Spawning {count} agent(s)...
   ---
   ```

   - Bad: "Executing terrain generation plan"
   - Good: "Procedural terrain generator using Perlin noise — creates height maps, biome zones, and collision meshes. Required before vehicle physics can interact with ground."

2.5. **Per-plan worktree decision (run for each plan in this wave BEFORE its dispatch):**

   Read and execute `get-shit-done/workflows/execute-phase/steps/per-plan-worktree-gate.md` for each plan. It extracts `PLAN_FILES` from the plan's JSON, intersects against `SUBMODULE_PATHS` (with normalization, bidirectional matching, and glob-prefix handling), and sets `USE_WORKTREES_FOR_PLAN` to `false` when the plan touches a submodule path. Append `plan_id` to a `WAVE_WORKTREE_PLANS` accumulator when `USE_WORKTREES_FOR_PLAN != false`.

   The dispatch branches in step 3 below MUST gate on `USE_WORKTREES_FOR_PLAN` for the current plan, not on the project-level `USE_WORKTREES`.

3. **Spawn executor agents:**

   **Emit a plan-start heartbeat (literal line, no tool call) immediately before
   each `Agent()` dispatch (#2410):**

   `[checkpoint] phase {PHASE_NUMBER} wave {N}/{M} plan {plan_id} starting ({P}/{Q} plans done)`

   Pass paths only — executors read files themselves with their fresh context window.
   For 200k models, this keeps orchestrator context lean (~10-15%).
   For 1M+ models (Opus 4.6, Sonnet 4.6), richer context can be passed directly.

   **Worktree mode** (`USE_WORKTREES_FOR_PLAN` is not `false` — evaluated per-plan in step 2.5):

   Before spawning, capture the current HEAD:
   ```bash
   EXPECTED_BASE=$(git rev-parse HEAD)
   DISPATCH_TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
   EXPECTED_BRANCH=$(git rev-parse --abbrev-ref HEAD)
   if [ "${USE_WORKTREES_FOR_PLAN:-true}" != "false" ] && [ -z "${WAVE_WORKTREE_MANIFEST:-}" ]; then
     WAVE_WORKTREE_MANIFEST=$(mktemp "${TMPDIR:-/tmp}/gsd-worktree-wave-XXXXXX.json")
     printf '{"worktrees":[]}\n' > "$WAVE_WORKTREE_MANIFEST"
     export WAVE_WORKTREE_MANIFEST
   fi
   ```

   **Sequential dispatch for parallel execution (waves with 2+ agents):**
   Dispatch each `Agent()` call **one at a time with `run_in_background: true`**. Do NOT
   send all Agent calls in a single message: simultaneous `git worktree add` calls race
   on `.git/config.lock`. Agents still run in parallel once their worktrees are created.

   ```text
   # CORRECT: one Agent() per message with run_in_background: true
   # WRONG: multiple Agent() calls in one message -> .git/config.lock contention
   ```

   ```text
   Agent(
     subagent_type="gsd-executor",
     description="Execute plan {plan_number} of phase {phase_number}",
     # Only include model= when executor_model is an explicit model name.
     # When executor_model is "inherit", omit this parameter entirely so
     # Claude Code inherits the orchestrator model automatically.
     model="{executor_model}",  # omit this line when executor_model == "inherit"
     isolation="worktree",
     prompt="
       <objective>
       Execute plan {plan_number} of phase {phase_number}-{phase_name}.
       Commit each task atomically. Create SUMMARY.md.
       Do NOT update STATE.md or ROADMAP.md — the orchestrator owns those writes after all worktree agents in the wave complete.
       </objective>

       <worktree_branch_check>
       FIRST ACTION: HEAD assertion MUST run before any reset/checkout. Worktrees
       spawned by Claude Code's `isolation="worktree"` use the `worktree-agent-<id>`
       namespace. If HEAD is on a protected ref (main/master/develop/trunk/release/*)
       or detached, HALT — do NOT self-recover by force-rewinding via `git update-ref`,
       that destroys concurrent commits in multi-active scenarios (#2924). Only after
       Step 1 passes is `git reset --hard` safe (#2015 — affects all platforms).
       ```bash
       HEAD_REF=$(git symbolic-ref --quiet HEAD || echo "DETACHED")
       ACTUAL_BRANCH=$(git rev-parse --abbrev-ref HEAD)
       if [ "$HEAD_REF" = "DETACHED" ] || echo "$ACTUAL_BRANCH" | grep -Eq '^(main|master|develop|trunk|release/.*)$'; then
         echo "FATAL: worktree HEAD on '$ACTUAL_BRANCH' (expected worktree-agent-*); refusing to self-recover via 'git update-ref' (#2924)." >&2
         exit 1
       fi
       if ! echo "$ACTUAL_BRANCH" | grep -Eq '^worktree-agent-[A-Za-z0-9._/-]+$'; then
         echo "FATAL: worktree HEAD '$ACTUAL_BRANCH' is not in the worktree-agent-* namespace; refusing to commit (#2924)." >&2
         exit 1
       fi
       ACTUAL_BASE=$(git merge-base HEAD {EXPECTED_BASE})
       if [ "$ACTUAL_BASE" != "{EXPECTED_BASE}" ]; then
         git reset --hard {EXPECTED_BASE}
         [ "$(git rev-parse HEAD)" != "{EXPECTED_BASE}" ] && { echo "ERROR: could not correct worktree base"; exit 1; }
       fi
       ```
       Per-commit HEAD/cwd-drift/path-guard: `agents/gsd-executor.md` steps 0/0a/0b + `references/worktree-path-safety.md` (in <execution_context>).
       </worktree_branch_check>

       <parallel_execution>
       You are running as a PARALLEL executor agent in a git worktree. Worktree path safety (cwd-drift, absolute-path guards) is in `worktree-path-safety.md` (loaded below).
       Run `git commit` normally — hooks run by default. Do NOT pass `--no-verify`
       unless the orchestrator surfaces `workflow.worktree_skip_hooks=true` in this
       prompt; silent bypass violates project CLAUDE.md guidance (#2924).

       IMPORTANT: Do NOT modify STATE.md or ROADMAP.md. execute-plan.md
       auto-detects worktree mode (`.git` is a file, not a directory) and skips
       shared file updates automatically. The orchestrator updates them centrally
       after merge.

       REQUIRED: SUMMARY.md MUST be committed before you return. In worktree mode the
       git_commit_metadata step in execute-plan.md commits SUMMARY.md and REQUIREMENTS.md
       only (STATE.md and ROADMAP.md are excluded automatically). Do NOT skip or defer
       this commit — the orchestrator force-removes the worktree after you return, and
       any uncommitted SUMMARY.md will be permanently lost (#2070).
       REQUIRED ORDER: Write SUMMARY.md → commit → only then any narration. No text between Write and commit (truncation risk; #2070 rescue is not primary defense).
       </parallel_execution>

       <execution_context>
       @$HOME/.claude/get-shit-done/workflows/execute-plan.md
       @$HOME/.claude/get-shit-done/templates/summary.md
       @$HOME/.claude/get-shit-done/references/checkpoints.md
       @$HOME/.claude/get-shit-done/references/tdd.md
       @$HOME/.claude/get-shit-done/references/worktree-path-safety.md
       ${CONTEXT_WINDOW < 200000 ? '' : '@$HOME/.claude/get-shit-done/references/executor-examples.md'}
       </execution_context>

       <files_to_read>
       Read these files at execution start using the Read tool:
       - {phase_dir}/{plan_file} (Plan)
       - .planning/PROJECT.md (Project context — core value, requirements, evolution rules)
       - .planning/STATE.md (State)
       - .planning/config.json (Config, if exists)
       ${CONTEXT_WINDOW >= 500000 ? `
       - ${phase_dir}/*-CONTEXT.md (User decisions from discuss-phase — honors locked choices)
       - ${phase_dir}/*-RESEARCH.md (Technical research — pitfalls and patterns to follow)
       - ${prior_wave_summaries} (SUMMARY.md files from earlier waves in this phase — what was already built)
       ` : ''}
       - ./CLAUDE.md (Project instructions, if exists — follow project-specific guidelines and coding conventions)
       - .claude/skills/ or .agents/skills/ (Project skills, if either exists — list skills, read SKILL.md for each, follow relevant rules during implementation)
       </files_to_read>

       ${AGENT_SKILLS}

       <mcp_tools>
       If CLAUDE.md or project instructions reference MCP tools (e.g. jCodeMunch, context7,
       or other MCP servers), prefer those tools over Grep/Glob for code navigation when available.
       MCP tools often save significant tokens by providing structured code indexes.
       Check tool availability first — if MCP tools are not accessible, fall back to Grep/Glob.
       </mcp_tools>

       <success_criteria>
       - [ ] All tasks executed
       - [ ] Each task committed individually
       - [ ] SUMMARY.md created in plan directory
       - [ ] No modifications to shared orchestrator artifacts (the orchestrator handles all post-wave shared-file writes)
       </success_criteria>
     "
   )
   ```

   Immediately after each worktree `Agent()` spawn returns metadata, atomically append `{agent_id, worktree_path, branch, expected_base}` to `WAVE_WORKTREE_MANIFEST`. If any field is missing, stop and ask for recovery instead of scanning all agent worktrees.

   > **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above to spawn executor agent(s), stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.

   **Sequential mode** (`USE_WORKTREES_FOR_PLAN` is `false` — either project-level `USE_WORKTREES=false`, or per-plan submodule intersection forced it false in step 2.5):

   Omit `isolation="worktree"` from the Agent call. Replace the `<parallel_execution>` block with:

   ```
       <sequential_execution>
       You are running as a SEQUENTIAL executor agent on the main working tree.
       Use normal git commits (with hooks). Do NOT use --no-verify.
       REQUIRED ORDER: Write SUMMARY.md → commit → only then any narration. No text between Write and commit (truncation risk; #2070 rescue is not primary defense).
       </sequential_execution>
   ```

   The sequential mode Agent prompt uses the same structure as worktree mode but with these differences in success_criteria — since there is only one agent writing at a time, there are no shared-file conflicts:

   ```
       <success_criteria>
       - [ ] All tasks executed
       - [ ] Each task committed individually
       - [ ] SUMMARY.md created in plan directory
       - [ ] STATE.md updated with position and decisions
       - [ ] ROADMAP.md updated with plan progress (via `roadmap update-plan-progress`)
       </success_criteria>
   ```

   When worktrees are disabled for a plan (per-plan or project-level), that plan's executor runs on the main working tree. If **any** plan in the current wave dropped to sequential mode, execute the affected plan(s) **one at a time** to avoid concurrent writes to the main working tree — plans in the same wave that retained worktree isolation can still run in parallel alongside the sequential ones, but two non-worktree plans in the same wave must serialize. When the project-level `USE_WORKTREES=false`, all plans in the wave serialize regardless of the `PARALLELIZATION` setting.

4. **Wait for all agents in wave to complete.**

   **Plan-complete heartbeat (#2410):** as each executor returns (or is verified
   via spot-check below), emit one line — `complete` advances `{P}`, `failed`
   and `checkpoint` do not but still warm the stream:

   ```
   [checkpoint] phase {PHASE_NUMBER} wave {N}/{M} plan {plan_id} complete ({P}/{Q} plans done)
   [checkpoint] phase {PHASE_NUMBER} wave {N}/{M} plan {plan_id} failed ({P}/{Q} plans done)
   [checkpoint] phase {PHASE_NUMBER} wave {N}/{M} plan {plan_id} checkpoint ({P}/{Q} plans done)
   ```

   **Completion signal fallback (Copilot and runtimes where Agent() may not return):**

   If a spawned agent does not return a completion signal but appears to have finished
   its work, do NOT block indefinitely. Instead, verify completion via spot-checks:

   ```bash
   # For each plan in this wave, check if the executor finished:
   SUMMARY_EXISTS=$(test -f "{phase_dir}/{plan_number}-{plan_padded}-SUMMARY.md" && echo "true" || echo "false")
   COMMITS_FOUND=$(git log --oneline --all --grep="{phase_number}-{plan_padded}" --since="1 hour ago" | head -1)
   COMMITS_SINCE_DISPATCH=$(git log "${EXPECTED_BRANCH}" --since="${DISPATCH_TS}" --oneline | head -1)
   ```

   **If SUMMARY.md exists AND commits are found:** The agent completed successfully —
   treat as done and proceed to step 5. Log: `"✓ {Plan ID} completed (verified via spot-check — completion signal not received)"`

   **If SUMMARY.md does NOT exist after a reasonable wait:** The agent may still be
   running or may have failed silently. Check `git log --oneline -5` for recent
   activity. If commits are still appearing, wait longer. If no activity, report
   the plan as failed and route to the failure handler in step 6.

   **Configurable stall surveillance (#3212):** Every `${EXECUTOR_STALL_INTERVAL_MINUTES}`
   minutes while waiting, inspect `git log "${EXPECTED_BRANCH}" --since="${DISPATCH_TS}"`
   for activity. If no completion signal, no SUMMARY.md, and no expected-branch
   commits appear for `${EXECUTOR_STALL_THRESHOLD_MINUTES}` minutes, pause and
   ask for one recovery path: `continue waiting`, `kill and retry`, or
   `kill and switch to inline execution`.

   **This fallback applies automatically to all runtimes.** Claude Code's Agent() normally
   returns synchronously, but the fallback ensures resilience if it doesn't.

5. **Post-wave hook validation (parallel mode only):** Hooks run on every executor commit by default (#2924); this post-wave run only fires when `workflow.worktree_skip_hooks=true` opted out of per-commit hooks:
   ```bash
   SKIP_HOOKS=$(gsd-sdk query config-get workflow.worktree_skip_hooks 2>/dev/null || echo "false")
   if [ "$SKIP_HOOKS" = "true" ]; then
     # Stash uncommitted changes under a named ref so we always pop (bare `git stash` strands them on hook/script failure). #3542: `refs/stash` is shared across worktrees, so this helper runs ONLY in the orchestrator's main checkout after all wave worktrees have been merged + removed; executors are forbidden from running any `git stash` subcommand (see `<destructive_git_prohibition>` in `agents/gsd-executor.md`).
     STASHED=false
     if (! git diff --quiet || ! git diff --cached --quiet) && git stash push -u -m "gsd-post-wave-hook-$$" >/dev/null 2>&1; then STASHED=true; fi
     git hook run pre-commit 2>&1 || echo "⚠ Pre-commit hooks failed — review before continuing"
     [ "$STASHED" = "true" ] && (git stash pop >/dev/null 2>&1 || echo "⚠ Could not pop gsd-post-wave-hook stash — recover manually")
   fi
   ```
   If hooks fail: report the failure and ask "Fix hook issues now?" or "Continue to next wave?"

5.5. **Worktree cleanup (when `isolation="worktree"` was used):**

   **Standard wave contract:** Each wave's worktrees merge to main via the templated path below before the next wave's worktrees fork. The cleanup loop runs once per wave at the end of the wave lifecycle. Worktrees created in wave N must be fully removed before wave N+1 forks new ones.

   **Cross-wave dependency deviation (supported execution mode):** When the orchestrator legitimately deviates from the standard wave model — for example, a phase with cross-wave plan dependencies that requires custom inter-worktree base-update merges (e.g., `merge: bring 09-01 + 09-02 into 09-03 base`) — the cleanup loop below is NOT automatically re-entered for those custom merges. The deviation path produces correct final history but bypasses this loop, leaving `worktree-agent-*` directories in place. Use the **cleanup-tail snippet** below to remove any residual worktrees after such a deviation.

   When executor agents ran in worktree isolation, their commits land on temporary branches in separate working trees. After the wave completes, merge these changes back and clean up:

   **Manifest source of truth (#3384):** Cleanup consumes the `WAVE_WORKTREE_MANIFEST` created and populated during executor dispatch in step 3. Do not recreate or truncate it here.

   Prefer the bounded helper, which validates branch identity, expected base, deletion
   diffs, merge result, and worktree removal before deleting the temporary branch.
   If the helper reports a blocked cleanup, resolve the reported manifest entry and
   rerun the same command. Do not fall back to broad worktree discovery.

   ```bash
   [ -n "${WAVE_WORKTREE_MANIFEST:-}" ] && [ -f "$WAVE_WORKTREE_MANIFEST" ] || {
     echo "BLOCKED: missing WAVE_WORKTREE_MANIFEST; refusing broad worktree cleanup (#3384)." >&2
     exit 1
   }

   # Guard: pin cleanup back to the primary worktree and fail on branch drift (#3174).
   PRIMARY_WT=$(git worktree list --porcelain | awk '/^worktree /{print substr($0,10); exit}')
   if [ -z "$PRIMARY_WT" ]; then
     echo "FATAL: could not resolve primary worktree before cleanup" >&2
     exit 1
   fi
   if [ -n "$PRIMARY_WT" ] && [ "$(pwd -P 2>/dev/null)" != "$(cd "$PRIMARY_WT" 2>/dev/null && pwd -P)" ]; then echo "⚠ Orchestrator CWD drifted to $(pwd) — pinning to $PRIMARY_WT before worktree cleanup (#3174)"; cd "$PRIMARY_WT" || { echo "FATAL: cannot cd to primary worktree $PRIMARY_WT" >&2; exit 1; }; fi
   ORCH_BRANCH=$(git rev-parse --abbrev-ref HEAD)
   [ -z "${EXPECTED_BRANCH:-}" ] || [ "$ORCH_BRANCH" = "$EXPECTED_BRANCH" ] || { echo "FATAL: orchestrator on '$ORCH_BRANCH' but expected '$EXPECTED_BRANCH' before worktree cleanup — refusing to merge (#3174-class drift)" >&2; exit 1; }

   if command -v gsd-sdk >/dev/null 2>&1; then
     gsd-sdk query worktree.cleanup-wave --manifest "$WAVE_WORKTREE_MANIFEST" || exit 1
   else
     echo "WARN: gsd-sdk unavailable; using manifest-scoped shell fallback (#3384)." >&2
   WT_PATHS_FILE=$(mktemp "${TMPDIR:-/tmp}/gsd-worktree-paths-XXXXXX")
   node -e 'const fs=require("fs");const p=process.env.WAVE_WORKTREE_MANIFEST;try{if(!p)throw new Error("WAVE_WORKTREE_MANIFEST is unset");if(!fs.existsSync(p))throw new Error("manifest does not exist");const s=fs.readFileSync(p,"utf8");if(!s.trim())throw new Error("manifest is empty");const j=JSON.parse(s);for(const w of j.worktrees||[])if(w.worktree_path)console.log(w.worktree_path)}catch(e){console.error(`ERROR: cannot read worktree manifest ${p||"(unset)"}: ${e.message}`);process.exit(1)}' > "$WT_PATHS_FILE" || { echo "BLOCKED: cannot read WAVE_WORKTREE_MANIFEST; refusing cleanup (#3384)." >&2; exit 1; }
   while IFS= read -r WT; do
     [ -z "$WT" ] && continue
     WT_BRANCH=$(git -C "$WT" rev-parse --abbrev-ref HEAD 2>/dev/null)
     if [ -n "$WT_BRANCH" ] && [ "$WT_BRANCH" != "HEAD" ]; then
       CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
       STATE_BACKUP=$(mktemp)
       ROADMAP_BACKUP=$(mktemp)
       [ -f .planning/STATE.md ] && cp .planning/STATE.md "$STATE_BACKUP" || true
       [ -f .planning/ROADMAP.md ] && cp .planning/ROADMAP.md "$ROADMAP_BACKUP" || true
       DELETIONS=$(git diff --diff-filter=D --name-only HEAD..."$WT_BRANCH" 2>/dev/null || true)
       if [ -n "$DELETIONS" ]; then
         echo "BLOCKED: Worktree branch $WT_BRANCH contains file deletions: $DELETIONS"
         echo "Review these deletions before merging. If intentional, remove this guard and re-run."
         rm -f "$STATE_BACKUP" "$ROADMAP_BACKUP"
         continue
       fi
       git merge "$WT_BRANCH" --no-ff --no-edit -m "chore: merge executor worktree ($WT_BRANCH)" 2>&1 || {
         echo "⚠ Merge conflict from worktree $WT_BRANCH — resolve manually"
         echo "  STATE.md backup:   $STATE_BACKUP"
         echo "  ROADMAP.md backup: $ROADMAP_BACKUP"
         echo "  Restore with: cp \$STATE_BACKUP .planning/STATE.md && cp \$ROADMAP_BACKUP .planning/ROADMAP.md"
         break
       }
       MERGE_DEL_COUNT=$(git diff --diff-filter=D --name-only HEAD~1 HEAD 2>/dev/null | grep -vc '^\.planning/' || true)
       if [ "$MERGE_DEL_COUNT" -gt 5 ] && [ "${ALLOW_BULK_DELETE:-0}" != "1" ]; then
         MERGE_DELETIONS=$(git diff --diff-filter=D --name-only HEAD~1 HEAD 2>/dev/null | grep -v '^\.planning/' || true)
         echo "⚠ BLOCKED: Merge of $WT_BRANCH deleted $MERGE_DEL_COUNT files outside .planning/ — reverting to protect repository integrity (#2384)"
         echo "$MERGE_DELETIONS"
         echo "  If these deletions are intentional, re-run with ALLOW_BULK_DELETE=1"
         git reset --hard HEAD~1 2>/dev/null || true
         rm -f "$STATE_BACKUP" "$ROADMAP_BACKUP"
         continue
       fi
       if [ -s "$STATE_BACKUP" ]; then


800	         cp "$STATE_BACKUP" .planning/STATE.md
801	       fi
802	       if [ -s "$ROADMAP_BACKUP" ]; then
803	         cp "$ROADMAP_BACKUP" .planning/ROADMAP.md
804	       fi
805	       rm -f "$STATE_BACKUP" "$ROADMAP_BACKUP"
806	       # Detect files deleted on main but re-added by worktree merge (#2501).
807	       DELETED_FILES=$(git diff --diff-filter=A --name-only HEAD~1 -- .planning/ 2>/dev/null || true)
808	       for RESURRECTED in $DELETED_FILES; do
809	         WAS_DELETED=$(git log --follow --diff-filter=D --name-only --format="" HEAD~1 -- "$RESURRECTED" 2>/dev/null | grep -c . || true)
810	         if [ "${WAS_DELETED:-0}" -gt 0 ]; then
811	           git rm -f "$RESURRECTED" 2>/dev/null || true
812	         fi
813	       done
814	       if ! git diff --quiet .planning/STATE.md .planning/ROADMAP.md 2>/dev/null || \
815	          [ -n "$DELETED_FILES" ]; then
816	         COMMIT_DOCS=$(gsd-sdk query config-get commit_docs 2>/dev/null || echo "true")
817	         if [ "$COMMIT_DOCS" != "false" ]; then
818	           git add .planning/STATE.md .planning/ROADMAP.md 2>/dev/null || true
819	           git commit --amend --no-edit 2>/dev/null || true
820	         fi
821	       fi
822	       # Safety net: rescue uncommitted SUMMARY.md before worktree removal (#2070, #2838).
823	       while IFS= read -r SUMMARY; do
824	         [ -z "$SUMMARY" ] && continue
825	         REL_PATH="${SUMMARY#$WT/}"
826	         if [ ! -f "$REL_PATH" ] || ! cmp -s "$SUMMARY" "$REL_PATH"; then
827	           mkdir -p "$(dirname "$REL_PATH")"
828	           cp "$SUMMARY" "$REL_PATH"
829	           echo "⚠ Rescued $REL_PATH from worktree before removal"
830	         fi
831	       done < <(find "$WT/.planning" -name "*SUMMARY.md" 2>/dev/null)
832	       REMOVE_OK=false
833	       if git worktree remove "$WT" --force; then
834	         REMOVE_OK=true
835	       else
836	         WT_NAME=$(basename "$WT")
837	         if [ -f ".git/worktrees/${WT_NAME}/locked" ]; then
838	           echo "⚠ Worktree $WT is locked — attempting to unlock and retry"
839	           git worktree unlock "$WT" 2>/dev/null || true
840	           if git worktree remove "$WT" --force; then
841	             REMOVE_OK=true
842	           else
843	             echo "⚠ Residual worktree at $WT — manual cleanup required after session exits:"
844	             echo "    git worktree unlock \"$WT\" && git worktree remove \"$WT\" --force && git branch -D \"$WT_BRANCH\""
845	           fi
846	         else
847	           echo "⚠ Residual worktree at $WT (remove failed) — investigate manually"
848	         fi
849	       fi
850	       if [ "$REMOVE_OK" = "true" ]; then
851	         git branch -D "$WT_BRANCH" 2>/dev/null || true
852	       else
853	         echo "⚠ Keeping branch $WT_BRANCH because worktree removal failed (#3384)"
854	       fi
855	     fi
856	   done < "$WT_PATHS_FILE"
857	   fi
858	   ```
859
860	   **Cleanup-tail snippet (use after any wave whose merges did not flow through the templated path above):**
861
862	   If the orchestrator deviated from the standard wave merge path (e.g., custom inter-worktree base-update merges with `merge: bring …` style messages), run this snippet after the custom merges are complete. It reads only `WAVE_WORKTREE_MANIFEST`; do not discover unrelated `worktree-agent-*` worktrees.
863
864	   ```bash
865	   # Cleanup-tail: pin orchestrator CWD to primary worktree before cleanup-tail (#3174).
866	   PRIMARY_WT=$(git worktree list --porcelain | awk '/^worktree /{print substr($0,10); exit}')
867	   if [ -n "$PRIMARY_WT" ] && [ "$(pwd -P 2>/dev/null)" != "$(cd "$PRIMARY_WT" 2>/dev/null && pwd -P)" ]; then echo "⚠ Orchestrator CWD drifted to $(pwd) — pinning to $PRIMARY_WT before cleanup-tail (#3174)"; cd "$PRIMARY_WT" || { echo "FATAL: cannot cd to primary worktree $PRIMARY_WT" >&2; exit 1; }; fi
868	   # Cleanup-tail: remove residual agent worktrees after a cross-wave-dependency deviation.
869	   # Uses only the current wave manifest to avoid touching unrelated active agents (#3384).
870	   WT_PATHS_FILE=$(mktemp "${TMPDIR:-/tmp}/gsd-worktree-paths-XXXXXX")
871	   node -e 'const fs=require("fs");const p=process.env.WAVE_WORKTREE_MANIFEST;try{if(!p)throw new Error("WAVE_WORKTREE_MANIFEST is unset");if(!fs.existsSync(p))throw new Error("manifest does not exist");const s=fs.readFileSync(p,"utf8");if(!s.trim())throw new Error("manifest is empty");const j=JSON.parse(s);for(const w of j.worktrees||[])if(w.worktree_path)console.log(w.worktree_path)}catch(e){console.error(`ERROR: cannot read worktree manifest ${p||"(unset)"}: ${e.message}`);process.exit(1)}' > "$WT_PATHS_FILE" || { echo "BLOCKED: cannot read WAVE_WORKTREE_MANIFEST; refusing cleanup (#3384)." >&2; exit 1; }
872	   while IFS= read -r WT; do
873	     [ -z "$WT" ] && continue
874	     WT_BRANCH=$(git -C "$WT" rev-parse --abbrev-ref HEAD 2>/dev/null)
875	     [ -z "$WT_BRANCH" ] || [ "$WT_BRANCH" = "HEAD" ] && continue
876	     echo "Cleaning up residual worktree: $WT (branch: $WT_BRANCH)"
877	     git worktree unlock "$WT" 2>/dev/null || true
878	     if ! git worktree remove "$WT" --force; then
879	       WT_NAME=$(basename "$WT")
880	       if [ -f ".git/worktrees/${WT_NAME}/locked" ]; then
881	         echo "⚠ Worktree $WT is locked — unlock failed; manual cleanup required:"
882	         echo "    git worktree unlock \"$WT\" && git worktree remove \"$WT\" --force && git branch -D \"$WT_BRANCH\""
883	       else
884	         echo "⚠ Residual worktree at $WT — remove failed; manual cleanup required"
885	       fi
886	     else
887	       git branch -D "$WT_BRANCH" 2>/dev/null || true
888	     fi
889	   done < "$WT_PATHS_FILE"
890	   git worktree prune
891	   ```
892
893	   **When to skip step 5.5:**
894
895	   **If no plan in this wave used worktree isolation** (project-level `USE_WORKTREES=false` OR every plan in the wave had `USE_WORKTREES_FOR_PLAN=false` — i.e. `WAVE_WORKTREE_PLANS` from step 2.5 is empty): all agents ran on the main working tree — skip this step entirely.
896
897	   **If the orchestrator merged via custom messages (cross-wave-dependency deviation):** the templated cleanup loop above was not triggered for those merges. Run the cleanup-tail snippet above instead. After the snippet completes, proceed to step 5.6.
898
899	   **If at least one plan used worktrees but others did not:** still run this cleanup — it iterates over actual `git worktree list` output and only merges back the worktrees that were created, leaving sequential plans' commits on the main tree untouched.
900
901	   **If no worktrees found at runtime:** Skip silently — agents may have been spawned without worktree isolation, or the orchestrator already cleaned them up.
902
903	5.6. **Post-merge build & test gate:**
904
905	   After merging all worktrees in a wave (parallel mode), or after the last plan completes
906	   (serial mode), run a build and then the project's test suite to catch cross-plan
907	   integration issues that individual worktree self-checks cannot detect (e.g., conflicting
908	   type definitions, removed exports, import changes, link errors).
909
910	   This addresses the Generator self-evaluation blind spot identified in Anthropic's
911	   harness engineering research: agents reliably report Self-Check: PASSED even when
912	   merging their work creates failures.
913
914	   Read and execute `get-shit-done/workflows/execute-phase/steps/post-merge-gate.md`.
915
916	5.7. **Post-wave shared artifact update (when at least one plan used worktrees, skip if tests failed):**
917
918	   When **any** executor agent in this wave ran with `isolation="worktree"`, that agent skipped STATE.md and ROADMAP.md updates to avoid last-merge-wins overwrites. The orchestrator is the single writer for these files. After worktrees are merged back, update shared artifacts once for every completed plan in the wave (worktree-mode plans **and** sequential plans that ran on the main tree but deferred to the orchestrator for tracking writes).
919
920	   **Only update tracking when tests passed (TEST_EXIT=0).**
921	   If tests failed or timed out, skip the tracking update — plans should
922	   not be marked as complete when integration tests are failing or inconclusive.
923
924	   ```bash
925	   # Guard: only update tracking if post-merge tests passed
926	   # Timeout (124) is treated as inconclusive — do NOT mark plans complete
927	   if [ "${TEST_EXIT}" -eq 0 ]; then
928	     # Update ROADMAP plan progress for each completed plan in this wave
929	     for plan_id in {completed_plan_ids}; do
930	       gsd-sdk query roadmap.update-plan-progress "${PHASE_NUMBER}" "${plan_id}" "complete"
931	     done
932
933	     # Only commit tracking files if they actually changed
934	     if ! git diff --quiet .planning/ROADMAP.md .planning/STATE.md 2>/dev/null; then
935	       gsd-sdk query commit "docs(phase-${PHASE_NUMBER}): update tracking after wave ${N}" --files .planning/ROADMAP.md .planning/STATE.md
936	     fi
937	   elif [ "${TEST_EXIT}" -eq 124 ]; then
938	     echo "⚠ Skipping tracking update — test suite timed out. Plans remain in-progress. Run tests manually to confirm."
939	   else
940	     echo "⚠ Skipping tracking update — post-merge tests failed (exit ${TEST_EXIT}). Plans remain in-progress until tests pass."
941	   fi
942	   ```
943
944	   Where `WAVE_PLAN_IDS` is the space-separated list of plan IDs that completed in this wave.
945
946	   **If no plan in this wave used worktrees** (project-level `USE_WORKTREES=false` OR `WAVE_WORKTREE_PLANS` is empty): sequential agents already updated STATE.md and ROADMAP.md themselves — skip this step.
947
948	5.8. **Handle test gate failures (when `WAVE_FAILURE_COUNT > 0`):**
949
950	   ```
951	   ## ⚠ Post-Merge Test Failure (cumulative failures: ${WAVE_FAILURE_COUNT})
952
953	   Wave {N} worktrees merged successfully, but {M} tests fail after merge.
954	   This typically indicates conflicting changes across parallel plans
955	   (e.g., type definitions, shared imports, API contracts).
956
957	   Failed tests:
958	   {first 10 lines of failure output}
959
960	   Options:
961	   1. Fix now (recommended) — resolve conflicts before next wave
962	   2. Continue — failures may compound in subsequent waves
963	   ```
964
965	   Note: If `WAVE_FAILURE_COUNT > 1`, strongly recommend "Fix now" — compounding
966	   failures across multiple waves become exponentially harder to diagnose.
967
968	   If "Fix now": diagnose failures (typically import conflicts, missing types,
969	   or changed function signatures from parallel plans modifying the same module).
970	   Fix, commit as `fix: resolve post-merge conflicts from wave {N}`, re-run tests.
971
972	   **Why this matters:** Worktree isolation means each agent's Self-Check passes
973	   in isolation. But when merged, add/add conflicts in shared files (models, registries,
974	   CLI entry points) can silently drop code. The post-merge gate catches this before
975	   the next wave builds on a broken foundation.
976
977	6. **Report completion — spot-check claims first:**
978
979	   **Wave-close heartbeat (#2410):** after spot-checks finish (pass or fail),
980	   before the `## Wave {N} Complete` summary, emit as a literal line:
981
982	   ```
983	   [checkpoint] phase {PHASE_NUMBER} wave {N}/{M} complete, {P}/{Q} plans done ({wave_success}/{wave_plan_count} ok)
984	   ```
985
986
987
988	   For each SUMMARY.md:
989	   - Verify first 2 files from `key-files.created` exist on disk
990	   - Check `git log --oneline --all --grep="{phase}-{plan}"` returns ≥1 commit
991	   - Check for `## Self-Check: FAILED` marker
992
993	   If ANY spot-check fails: report which plan failed, route to failure handler — ask "Retry plan?" or "Continue with remaining waves?"
994
995	   If pass:
996	   ```
997	   ---
998	   ## Wave {N} Complete
999
1000	   **{Plan ID}: {Plan Name}**
1001	   {What was built — from SUMMARY.md}
1002	   {Notable deviations, if any}
1003
1004	   {If more waves: what this enables for next wave}
1005	   ---
1006	   ```
1007
1008	7. **Handle failures:**
1009	   **Step 7.0 — classify before branching (#3095):**
1010	   ```bash
1011	   CLASS_JSON=$(gsd-sdk query agent.classify-failure -- "$AGENT_RETURN_BODY")
1012	   CLASS=$(echo "$CLASS_JSON" | jq -r '.class')
1013	   SENTINEL=$(echo "$CLASS_JSON" | jq -r '.sentinel // empty')
1014	   RETRY_AFTER=$(echo "$CLASS_JSON" | jq -r '.retryAfterSeconds // empty')
1015	   if [ -n "$RETRY_AFTER" ]; then RETRY_HINT="  Provider hinted retry-after: ${RETRY_AFTER}s"; else RETRY_HINT=""; fi
1016	   ```
1017	   One classifier branch handles sentinels across Claude/Copilot/Codex/Gemini. Reference: `docs/research/provider-rate-limit-signals.md`.
1018	   **Step 7.1 — `class == "quota-exceeded"`:**
1019	   Do not offer "retry now". Run step-5 spot-check first; if SUMMARY.md is missing but commits exist, route to safe-resume (`state.verify-against-disk`) instead of immediate redispatch.
1020	   ```text
1021	   ⚠ Plan {plan_id} terminated by provider quota / rate limit
1022	     Runtime sentinel: {SENTINEL}
1023	     {RETRY_HINT}
1024	     Partial commits on worktree branch: {N}
1025	     SUMMARY.md present: {yes|no}
1026	     1. Wait for quota reset, then resume (recommended)
1027	   2. Switch to a different runtime / model and resume
1028	   3. Abort phase and report partial state
1029	   ```
1030	   Re-run `/gsd:execute-phase` after quota reset for Option 1.
1031	   **Step 7.2 — `class == "classify-handoff-bug"`:**
1032	   If error contains `classifyHandoffIfNeeded is not defined`, treat as Claude runtime bug. Run the same step-5 spot-checks; PASS => treat as success, FAIL => fall through.
1033	   **Step 7.3 — `class == "unknown-failure"`:**
1034	   Report failed plan and ask Continue/Stop; continuing may cascade into dependent plan failures.
1035
1036	7b. **Pre-wave dependency check (waves 2+ only):**
1037	    Before wave N+1, run `gsd-sdk query verify.key-links {phase_dir}/{plan}-PLAN.md` for each upcoming plan.
1038	    If any PRIOR-wave artifact link fails, present:
1039	    - `## Cross-Plan Wiring Gap` with plan/link/from/pattern rows
1040	    - Options: investigate+fix before continue, or continue with cascade risk
1041	    Skip key-links that reference files in the CURRENT (upcoming) wave.
1042	8. **Execute checkpoint plans between waves** — see `<checkpoint_handling>`.
1043	9. **Proceed to next wave.**
1044	</step>
1045	<step name="checkpoint_handling">
1046	Plans with `autonomous: false` require user interaction.
1047	**Auto-mode checkpoint handling:**
1048	Read auto-advance config (chain flag OR user preference — same boolean as `check.auto-mode`):
1049	```bash
1050	AUTO_MODE=$(gsd-sdk query check auto-mode --pick active 2>/dev/null || echo "false")
1051	```
1052
1053	When executor returns a checkpoint AND `AUTO_MODE` is `true`:
1054	- **human-verify** → Auto-spawn continuation agent with `{user_response}` = `"approved"`. Log `⚡ Auto-approved checkpoint`.
1055	- **decision** → Auto-spawn continuation agent with `{user_response}` = first option from checkpoint details. Log `⚡ Auto-selected: [option]`.
1056	- **human-action** → Present to user (existing behavior below). Auth gates cannot be automated.
1057
1058	**Standard flow (not auto-mode, or human-action type):**
1059
1060	1. Spawn agent for checkpoint plan
1061	2. Agent runs until checkpoint task or auth gate → returns structured state
1062	3. Agent return includes: completed tasks table, current task + blocker, checkpoint type/details, what's awaited
1063	4. **Present to user:**
1064	   ```
1065	   ## Checkpoint: [Type]
1066
1067	   **Plan:** 03-03 Dashboard Layout
1068	   **Progress:** 2/3 tasks complete
1069
1070	   [Checkpoint Details from agent return]
1071	   [Awaiting section from agent return]
1072	   ```
1073	5. User responds: "approved"/"done" | issue description | decision selection
1074	6. **Spawn continuation agent (NOT resume)** using continuation-prompt.md template:
1075	   - `{completed_tasks_table}`: From checkpoint return
1076	   - `{resume_task_number}` + `{resume_task_name}`: Current task
1077	   - `{user_response}`: What user provided
1078	   - `{resume_instructions}`: Based on checkpoint type
1079	7. Continuation agent verifies previous commits, continues from resume point
1080	8. Repeat until plan completes or user stops
1081
1082	**Why fresh agent, not resume:** Resume relies on internal serialization that breaks with parallel tool calls. Fresh agents with explicit state are more reliable.
1083
1084	**Checkpoints in parallel waves:** Agent pauses and returns while other parallel agents may complete. Present checkpoint, spawn continuation, wait for all before next wave.
1085	</step>
1086
1087	<step name="aggregate_results">
1088	After all waves:
1089
1090	```markdown
1091	## Phase {X}: {Name} Execution Complete
1092
1093	**Waves:** {N} | **Plans:** {M}/{total} complete
1094
1095	| Wave | Plans | Status |
1096	|------|-------|--------|
1097	| 1 | plan-01, plan-02 | ✓ Complete |
1098	| CP | plan-03 | ✓ Verified |
1099	| 2 | plan-04 | ✓ Complete |
1100
1101	### Plan Details
1102	1. **03-01**: [one-liner from SUMMARY.md]
1103	2. **03-02**: [one-liner from SUMMARY.md]
1104
1105	### Issues Encountered
1106	[Aggregate from SUMMARYs, or "None"]
1107	```
1108
1109	**Security gate check:**
1110	```bash
1111	SECURITY_CFG=$(gsd-sdk query config-get workflow.security_enforcement --raw 2>/dev/null || echo "true")
1112	SECURITY_FILE=$(ls "${PHASE_DIR}"/*-SECURITY.md 2>/dev/null | head -1)
1113	```
1114
1115	If `SECURITY_CFG` is `false`: skip.
1116
1117	If `SECURITY_CFG` is `true` AND `SECURITY_FILE` is empty (no SECURITY.md yet):
1118	Include in the next-steps routing output:
1119	```
1120	⚠ Security enforcement enabled — run before advancing:
1121	  /gsd:secure-phase {PHASE} ${GSD_WS}
1122	```
1123
1124	If `SECURITY_CFG` is `true` AND SECURITY.md exists: check frontmatter `threats_open`. If > 0:
1125	```
1126	⚠ Security gate: {threats_open} threats open
1127	  /gsd:secure-phase {PHASE} — resolve before advancing
1128	```
1129	</step>
1130
1131	<step name="tdd_review_checkpoint">
1132	**Optional step — TDD collaborative review.**
1133
1134	```bash
1135	TDD_MODE=$(gsd-sdk query config-get workflow.tdd_mode 2>/dev/null || echo "false")
1136	```
1137
1138	**Skip if `TDD_MODE` is `false`.**
1139
1140	When `TDD_MODE` is `true`, check whether any completed plans in this phase have `type: tdd` in their frontmatter:
1141
1142	```bash
1143	TDD_PLANS=$(grep -rl "^type: tdd" "${PHASE_DIR}"/*-PLAN.md 2>/dev/null | wc -l | tr -d ' ')
1144	```
1145
1146	**If `TDD_PLANS` > 0:** Insert end-of-phase collaborative review checkpoint.
1147
1148	1. Collect all SUMMARY.md files for TDD plans
1149	2. For each TDD plan summary, verify the RED/GREEN/REFACTOR gate sequence:
1150	   - RED gate: A failing test commit exists (`test(...)` commit with MUST-fail evidence)
1151	   - GREEN gate: An implementation commit exists (`feat(...)` commit making tests pass)
1152	   - REFACTOR gate: Optional cleanup commit (`refactor(...)` commit, tests still pass)
1153	3. If any TDD plan is missing the RED or GREEN gate commits, flag it:
1154	   ```
1155	   ⚠ TDD gate violation: Plan {plan_id} missing {RED|GREEN} phase commit.
1156	     Expected commit pattern: test({phase}-{plan}): ... → feat({phase}-{plan}): ...
1157	   ```
1158	4. Present collaborative review summary:
1159	   ```
1160	   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1161	    TDD REVIEW — Phase {X}
1162	   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1163
1164	   TDD Plans: {TDD_PLANS} | Gate violations: {count}
1165
1166	   | Plan | RED | GREEN | REFACTOR | Status |
1167	   |------|-----|-------|----------|--------|
1168	   | {id} |  ✓  |   ✓   |    ✓     | Pass   |
1169	   | {id} |  ✓  |   ✗   |    —     | FAIL   |
1170	   ```
1171
1172	**Escalation under MVP+TDD.** When `MVP_MODE=true` AND `TDD_MODE=true`, the review verdict escalates from advisory to **blocking**: missing RED or GREEN gate commits prevent marking the phase complete.
1173	```text
1174	Phase blocked: {N} TDD plan(s) violate the RED→GREEN gate sequence under MVP+TDD.
1175	Resolve and re-run /gsd execute-phase, or override with
1176	/gsd execute-phase {phase} --force-mvp-gate to ship anyway.
1177	```
1178	`--force-mvp-gate` is the escape hatch (documented, not yet implemented). Policy is:
1179	- `MVP_MODE=true` AND `TDD_MODE=true`: violations are **blocking** unless explicitly overridden.
1180	- otherwise: violations are advisory/non-blocking and are surfaced for review.
1181	The verifier agent (step `verify_phase_goal`) still checks TDD discipline in both cases.
1182	</step>
1183
1184	<step name="handle_partial_wave_execution">
1185	If `WAVE_FILTER` was used, re-run plan discovery after execution:
1186
1187	```bash
1188	POST_PLAN_INDEX=$(gsd-sdk query phase-plan-index "${PHASE_NUMBER}")
1189	```
1190
1191	Apply the same "incomplete" filtering rules as earlier:
1192	- ignore plans with `has_summary: true`
1193	- if `--gaps-only`, only consider `gap_closure: true` plans
1194
1195	**If incomplete plans still remain anywhere in the phase:**
1196	- STOP here
1197	- Do NOT run phase verification
1198	- Do NOT mark the phase complete in ROADMAP/STATE
1199	- Present:

         cp "$STATE_BACKUP" .planning/STATE.md
       fi
       if [ -s "$ROADMAP_BACKUP" ]; then
         cp "$ROADMAP_BACKUP" .planning/ROADMAP.md
       fi
       rm -f "$STATE_BACKUP" "$ROADMAP_BACKUP"
       # Detect files deleted on main but re-added by worktree merge (#2501).
       DELETED_FILES=$(git diff --diff-filter=A --name-only HEAD~1 -- .planning/ 2>/dev/null || true)
       for RESURRECTED in $DELETED_FILES; do
         WAS_DELETED=$(git log --follow --diff-filter=D --name-only --format="" HEAD~1 -- "$RESURRECTED" 2>/dev/null | grep -c . || true)
         if [ "${WAS_DELETED:-0}" -gt 0 ]; then
           git rm -f "$RESURRECTED" 2>/dev/null || true
         fi
       done
       if ! git diff --quiet .planning/STATE.md .planning/ROADMAP.md 2>/dev/null || \
          [ -n "$DELETED_FILES" ]; then
         COMMIT_DOCS=$(gsd-sdk query config-get commit_docs 2>/dev/null || echo "true")
         if [ "$COMMIT_DOCS" != "false" ]; then
           git add .planning/STATE.md .planning/ROADMAP.md 2>/dev/null || true
           git commit --amend --no-edit 2>/dev/null || true
         fi
       fi
       # Safety net: rescue uncommitted SUMMARY.md before worktree removal (#2070, #2838).
       while IFS= read -r SUMMARY; do
         [ -z "$SUMMARY" ] && continue
         REL_PATH="${SUMMARY#$WT/}"
         if [ ! -f "$REL_PATH" ] || ! cmp -s "$SUMMARY" "$REL_PATH"; then
           mkdir -p "$(dirname "$REL_PATH")"
           cp "$SUMMARY" "$REL_PATH"
           echo "⚠ Rescued $REL_PATH from worktree before removal"
         fi
       done < <(find "$WT/.planning" -name "*SUMMARY.md" 2>/dev/null)
       REMOVE_OK=false
       if git worktree remove "$WT" --force; then
         REMOVE_OK=true
       else
         WT_NAME=$(basename "$WT")
         if [ -f ".git/worktrees/${WT_NAME}/locked" ]; then
           echo "⚠ Worktree $WT is locked — attempting to unlock and retry"
           git worktree unlock "$WT" 2>/dev/null || true
           if git worktree remove "$WT" --force; then
             REMOVE_OK=true
           else
             echo "⚠ Residual worktree at $WT — manual cleanup required after session exits:"
             echo "    git worktree unlock \"$WT\" && git worktree remove \"$WT\" --force && git branch -D \"$WT_BRANCH\""
           fi
         else
           echo "⚠ Residual worktree at $WT (remove failed) — investigate manually"
         fi
       fi
       if [ "$REMOVE_OK" = "true" ]; then
         git branch -D "$WT_BRANCH" 2>/dev/null || true
       else
         echo "⚠ Keeping branch $WT_BRANCH because worktree removal failed (#3384)"
       fi
     fi
   done < "$WT_PATHS_FILE"
   fi
   ```

   **Cleanup-tail snippet (use after any wave whose merges did not flow through the templated path above):**

   If the orchestrator deviated from the standard wave merge path (e.g., custom inter-worktree base-update merges with `merge: bring …` style messages), run this snippet after the custom merges are complete. It reads only `WAVE_WORKTREE_MANIFEST`; do not discover unrelated `worktree-agent-*` worktrees.

   ```bash
   # Cleanup-tail: pin orchestrator CWD to primary worktree before cleanup-tail (#3174).
   PRIMARY_WT=$(git worktree list --porcelain | awk '/^worktree /{print substr($0,10); exit}')
   if [ -n "$PRIMARY_WT" ] && [ "$(pwd -P 2>/dev/null)" != "$(cd "$PRIMARY_WT" 2>/dev/null && pwd -P)" ]; then echo "⚠ Orchestrator CWD drifted to $(pwd) — pinning to $PRIMARY_WT before cleanup-tail (#3174)"; cd "$PRIMARY_WT" || { echo "FATAL: cannot cd to primary worktree $PRIMARY_WT" >&2; exit 1; }; fi
   # Cleanup-tail: remove residual agent worktrees after a cross-wave-dependency deviation.
   # Uses only the current wave manifest to avoid touching unrelated active agents (#3384).
   WT_PATHS_FILE=$(mktemp "${TMPDIR:-/tmp}/gsd-worktree-paths-XXXXXX")
   node -e 'const fs=require("fs");const p=process.env.WAVE_WORKTREE_MANIFEST;try{if(!p)throw new Error("WAVE_WORKTREE_MANIFEST is unset");if(!fs.existsSync(p))throw new Error("manifest does not exist");const s=fs.readFileSync(p,"utf8");if(!s.trim())throw new Error("manifest is empty");const j=JSON.parse(s);for(const w of j.worktrees||[])if(w.worktree_path)console.log(w.worktree_path)}catch(e){console.error(`ERROR: cannot read worktree manifest ${p||"(unset)"}: ${e.message}`);process.exit(1)}' > "$WT_PATHS_FILE" || { echo "BLOCKED: cannot read WAVE_WORKTREE_MANIFEST; refusing cleanup (#3384)." >&2; exit 1; }
   while IFS= read -r WT; do
     [ -z "$WT" ] && continue
     WT_BRANCH=$(git -C "$WT" rev-parse --abbrev-ref HEAD 2>/dev/null)
     [ -z "$WT_BRANCH" ] || [ "$WT_BRANCH" = "HEAD" ] && continue
     echo "Cleaning up residual worktree: $WT (branch: $WT_BRANCH)"
     git worktree unlock "$WT" 2>/dev/null || true
     if ! git worktree remove "$WT" --force; then
       WT_NAME=$(basename "$WT")
       if [ -f ".git/worktrees/${WT_NAME}/locked" ]; then
         echo "⚠ Worktree $WT is locked — unlock failed; manual cleanup required:"
         echo "    git worktree unlock \"$WT\" && git worktree remove \"$WT\" --force && git branch -D \"$WT_BRANCH\""
       else
         echo "⚠ Residual worktree at $WT — remove failed; manual cleanup required"
       fi
     else
       git branch -D "$WT_BRANCH" 2>/dev/null || true
     fi
   done < "$WT_PATHS_FILE"
   git worktree prune
   ```

   **When to skip step 5.5:**

   **If no plan in this wave used worktree isolation** (project-level `USE_WORKTREES=false` OR every plan in the wave had `USE_WORKTREES_FOR_PLAN=false` — i.e. `WAVE_WORKTREE_PLANS` from step 2.5 is empty): all agents ran on the main working tree — skip this step entirely.

   **If the orchestrator merged via custom messages (cross-wave-dependency deviation):** the templated cleanup loop above was not triggered for those merges. Run the cleanup-tail snippet above instead. After the snippet completes, proceed to step 5.6.

   **If at least one plan used worktrees but others did not:** still run this cleanup — it iterates over actual `git worktree list` output and only merges back the worktrees that were created, leaving sequential plans' commits on the main tree untouched.

   **If no worktrees found at runtime:** Skip silently — agents may have been spawned without worktree isolation, or the orchestrator already cleaned them up.

5.6. **Post-merge build & test gate:**

   After merging all worktrees in a wave (parallel mode), or after the last plan completes
   (serial mode), run a build and then the project's test suite to catch cross-plan
   integration issues that individual worktree self-checks cannot detect (e.g., conflicting
   type definitions, removed exports, import changes, link errors).

   This addresses the Generator self-evaluation blind spot identified in Anthropic's
   harness engineering research: agents reliably report Self-Check: PASSED even when
   merging their work creates failures.

   Read and execute `get-shit-done/workflows/execute-phase/steps/post-merge-gate.md`.

5.7. **Post-wave shared artifact update (when at least one plan used worktrees, skip if tests failed):**

   When **any** executor agent in this wave ran with `isolation="worktree"`, that agent skipped STATE.md and ROADMAP.md updates to avoid last-merge-wins overwrites. The orchestrator is the single writer for these files. After worktrees are merged back, update shared artifacts once for every completed plan in the wave (worktree-mode plans **and** sequential plans that ran on the main tree but deferred to the orchestrator for tracking writes).

   **Only update tracking when tests passed (TEST_EXIT=0).**
   If tests failed or timed out, skip the tracking update — plans should
   not be marked as complete when integration tests are failing or inconclusive.

   ```bash
   # Guard: only update tracking if post-merge tests passed
   # Timeout (124) is treated as inconclusive — do NOT mark plans complete
   if [ "${TEST_EXIT}" -eq 0 ]; then
     # Update ROADMAP plan progress for each completed plan in this wave
     for plan_id in {completed_plan_ids}; do
       gsd-sdk query roadmap.update-plan-progress "${PHASE_NUMBER}" "${plan_id}" "complete"
     done

     # Only commit tracking files if they actually changed
     if ! git diff --quiet .planning/ROADMAP.md .planning/STATE.md 2>/dev/null; then
       gsd-sdk query commit "docs(phase-${PHASE_NUMBER}): update tracking after wave ${N}" --files .planning/ROADMAP.md .planning/STATE.md
     fi
   elif [ "${TEST_EXIT}" -eq 124 ]; then
     echo "⚠ Skipping tracking update — test suite timed out. Plans remain in-progress. Run tests manually to confirm."
   else
     echo "⚠ Skipping tracking update — post-merge tests failed (exit ${TEST_EXIT}). Plans remain in-progress until tests pass."
   fi
   ```

   Where `WAVE_PLAN_IDS` is the space-separated list of plan IDs that completed in this wave.

   **If no plan in this wave used worktrees** (project-level `USE_WORKTREES=false` OR `WAVE_WORKTREE_PLANS` is empty): sequential agents already updated STATE.md and ROADMAP.md themselves — skip this step.

5.8. **Handle test gate failures (when `WAVE_FAILURE_COUNT > 0`):**

   ```
   ## ⚠ Post-Merge Test Failure (cumulative failures: ${WAVE_FAILURE_COUNT})

   Wave {N} worktrees merged successfully, but {M} tests fail after merge.
   This typically indicates conflicting changes across parallel plans
   (e.g., type definitions, shared imports, API contracts).

   Failed tests:
   {first 10 lines of failure output}

   Options:
   1. Fix now (recommended) — resolve conflicts before next wave
   2. Continue — failures may compound in subsequent waves
   ```

   Note: If `WAVE_FAILURE_COUNT > 1`, strongly recommend "Fix now" — compounding
   failures across multiple waves become exponentially harder to diagnose.

   If "Fix now": diagnose failures (typically import conflicts, missing types,
   or changed function signatures from parallel plans modifying the same module).
   Fix, commit as `fix: resolve post-merge conflicts from wave {N}`, re-run tests.

   **Why this matters:** Worktree isolation means each agent's Self-Check passes
   in isolation. But when merged, add/add conflicts in shared files (models, registries,
   CLI entry points) can silently drop code. The post-merge gate catches this before
   the next wave builds on a broken foundation.

6. **Report completion — spot-check claims first:**

   **Wave-close heartbeat (#2410):** after spot-checks finish (pass or fail),
   before the `## Wave {N} Complete` summary, emit as a literal line:

   ```
   [checkpoint] phase {PHASE_NUMBER} wave {N}/{M} complete, {P}/{Q} plans done ({wave_success}/{wave_plan_count} ok)
   ```

   For each SUMMARY.md:
   - Verify first 2 files from `key-files.created` exist on disk
   - Check `git log --oneline --all --grep="{phase}-{plan}"` returns ≥1 commit
   - Check for `## Self-Check: FAILED` marker

   If ANY spot-check fails: report which plan failed, route to failure handler — ask "Retry plan?" or "Continue with remaining waves?"

   If pass:
   ```
   ---
   ## Wave {N} Complete

   **{Plan ID}: {Plan Name}**
   {What was built — from SUMMARY.md}
   {Notable deviations, if any}

   {If more waves: what this enables for next wave}
   ---
   ```

7. **Handle failures:**
   **Step 7.0 — classify before branching (#3095):**
   ```bash
   CLASS_JSON=$(gsd-sdk query agent.classify-failure -- "$AGENT_RETURN_BODY")
   CLASS=$(echo "$CLASS_JSON" | jq -r '.class')
   SENTINEL=$(echo "$CLASS_JSON" | jq -r '.sentinel // empty')
   RETRY_AFTER=$(echo "$CLASS_JSON" | jq -r '.retryAfterSeconds // empty')
   if [ -n "$RETRY_AFTER" ]; then RETRY_HINT="  Provider hinted retry-after: ${RETRY_AFTER}s"; else RETRY_HINT=""; fi
   ```
   One classifier branch handles sentinels across Claude/Copilot/Codex/Gemini. Reference: `docs/research/provider-rate-limit-signals.md`.
   **Step 7.1 — `class == "quota-exceeded"`:**
   Do not offer "retry now". Run step-5 spot-check first; if SUMMARY.md is missing but commits exist, route to safe-resume (`state.verify-against-disk`) instead of immediate redispatch.
   ```text
   ⚠ Plan {plan_id} terminated by provider quota / rate limit
     Runtime sentinel: {SENTINEL}
     {RETRY_HINT}
     Partial commits on worktree branch: {N}
     SUMMARY.md present: {yes|no}
     1. Wait for quota reset, then resume (recommended)
   2. Switch to a different runtime / model and resume
   3. Abort phase and report partial state
   ```
   Re-run `/gsd:execute-phase` after quota reset for Option 1.
   **Step 7.2 — `class == "classify-handoff-bug"`:**
   If error contains `classifyHandoffIfNeeded is not defined`, treat as Claude runtime bug. Run the same step-5 spot-checks; PASS => treat as success, FAIL => fall through.
   **Step 7.3 — `class == "unknown-failure"`:**
   Report failed plan and ask Continue/Stop; continuing may cascade into dependent plan failures.

7b. **Pre-wave dependency check (waves 2+ only):**
    Before wave N+1, run `gsd-sdk query verify.key-links {phase_dir}/{plan}-PLAN.md` for each upcoming plan.
    If any PRIOR-wave artifact link fails, present:
    - `## Cross-Plan Wiring Gap` with plan/link/from/pattern rows
    - Options: investigate+fix before continue, or continue with cascade risk
    Skip key-links that reference files in the CURRENT (upcoming) wave.
8. **Execute checkpoint plans between waves** — see `<checkpoint_handling>`.
9. **Proceed to next wave.**
</step>
<step name="checkpoint_handling">
Plans with `autonomous: false` require user interaction.
**Auto-mode checkpoint handling:**
Read auto-advance config (chain flag OR user preference — same boolean as `check.auto-mode`):
```bash
AUTO_MODE=$(gsd-sdk query check auto-mode --pick active 2>/dev/null || echo "false")
```

When executor returns a checkpoint AND `AUTO_MODE` is `true`:
- **human-verify** → Auto-spawn continuation agent with `{user_response}` = `"approved"`. Log `⚡ Auto-approved checkpoint`.
- **decision** → Auto-spawn continuation agent with `{user_response}` = first option from checkpoint details. Log `⚡ Auto-selected: [option]`.
- **human-action** → Present to user (existing behavior below). Auth gates cannot be automated.

**Standard flow (not auto-mode, or human-action type):**

1. Spawn agent for checkpoint plan
2. Agent runs until checkpoint task or auth gate → returns structured state
3. Agent return includes: completed tasks table, current task + blocker, checkpoint type/details, what's awaited
4. **Present to user:**
   ```
   ## Checkpoint: [Type]

   **Plan:** 03-03 Dashboard Layout
   **Progress:** 2/3 tasks complete

   [Checkpoint Details from agent return]
   [Awaiting section from agent return]
   ```
5. User responds: "approved"/"done" | issue description | decision selection
6. **Spawn continuation agent (NOT resume)** using continuation-prompt.md template:
   - `{completed_tasks_table}`: From checkpoint return
   - `{resume_task_number}` + `{resume_task_name}`: Current task
   - `{user_response}`: What user provided
   - `{resume_instructions}`: Based on checkpoint type
7. Continuation agent verifies previous commits, continues from resume point
8. Repeat until plan completes or user stops

**Why fresh agent, not resume:** Resume relies on internal serialization that breaks with parallel tool calls. Fresh agents with explicit state are more reliable.

**Checkpoints in parallel waves:** Agent pauses and returns while other parallel agents may complete. Present checkpoint, spawn continuation, wait for all before next wave.
</step>

<step name="aggregate_results">
After all waves:

```markdown
## Phase {X}: {Name} Execution Complete

**Waves:** {N} | **Plans:** {M}/{total} complete

| Wave | Plans | Status |
|------|-------|--------|
| 1 | plan-01, plan-02 | ✓ Complete |
| CP | plan-03 | ✓ Verified |
| 2 | plan-04 | ✓ Complete |

### Plan Details
1. **03-01**: [one-liner from SUMMARY.md]
2. **03-02**: [one-liner from SUMMARY.md]

### Issues Encountered
[Aggregate from SUMMARYs, or "None"]
```

**Security gate check:**
```bash
SECURITY_CFG=$(gsd-sdk query config-get workflow.security_enforcement --raw 2>/dev/null || echo "true")
SECURITY_FILE=$(ls "${PHASE_DIR}"/*-SECURITY.md 2>/dev/null | head -1)
```

If `SECURITY_CFG` is `false`: skip.

If `SECURITY_CFG` is `true` AND `SECURITY_FILE` is empty (no SECURITY.md yet):
Include in the next-steps routing output:
```
⚠ Security enforcement enabled — run before advancing:
  /gsd:secure-phase {PHASE} ${GSD_WS}
```

If `SECURITY_CFG` is `true` AND SECURITY.md exists: check frontmatter `threats_open`. If > 0:
```
⚠ Security gate: {threats_open} threats open
  /gsd:secure-phase {PHASE} — resolve before advancing
```
</step>

<step name="tdd_review_checkpoint">
**Optional step — TDD collaborative review.**

```bash
TDD_MODE=$(gsd-sdk query config-get workflow.tdd_mode 2>/dev/null || echo "false")
```

**Skip if `TDD_MODE` is `false`.**

When `TDD_MODE` is `true`, check whether any completed plans in this phase have `type: tdd` in their frontmatter:

```bash
TDD_PLANS=$(grep -rl "^type: tdd" "${PHASE_DIR}"/*-PLAN.md 2>/dev/null | wc -l | tr -d ' ')
```

**If `TDD_PLANS` > 0:** Insert end-of-phase collaborative review checkpoint.

1. Collect all SUMMARY.md files for TDD plans
2. For each TDD plan summary, verify the RED/GREEN/REFACTOR gate sequence:
   - RED gate: A failing test commit exists (`test(...)` commit with MUST-fail evidence)
   - GREEN gate: An implementation commit exists (`feat(...)` commit making tests pass)
   - REFACTOR gate: Optional cleanup commit (`refactor(...)` commit, tests still pass)
3. If any TDD plan is missing the RED or GREEN gate commits, flag it:
   ```
   ⚠ TDD gate violation: Plan {plan_id} missing {RED|GREEN} phase commit.
     Expected commit pattern: test({phase}-{plan}): ... → feat({phase}-{plan}): ...
   ```
4. Present collaborative review summary:
   ```
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    TDD REVIEW — Phase {X}
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

   TDD Plans: {TDD_PLANS} | Gate violations: {count}

   | Plan | RED | GREEN | REFACTOR | Status |
   |------|-----|-------|----------|--------|
   | {id} |  ✓  |   ✓   |    ✓     | Pass   |
   | {id} |  ✓  |   ✗   |    —     | FAIL   |
   ```

**Escalation under MVP+TDD.** When `MVP_MODE=true` AND `TDD_MODE=true`, the review verdict escalates from advisory to **blocking**: missing RED or GREEN gate commits prevent marking the phase complete.
```text
Phase blocked: {N} TDD plan(s) violate the RED→GREEN gate sequence under MVP+TDD.
Resolve and re-run /gsd execute-phase, or override with
/gsd execute-phase {phase} --force-mvp-gate to ship anyway.
```
`--force-mvp-gate` is the escape hatch (documented, not yet implemented). Policy is:
- `MVP_MODE=true` AND `TDD_MODE=true`: violations are **blocking** unless explicitly overridden.
- otherwise: violations are advisory/non-blocking and are surfaced for review.
The verifier agent (step `verify_phase_goal`) still checks TDD discipline in both cases.
</step>

<step name="handle_partial_wave_execution">
If `WAVE_FILTER` was used, re-run plan discovery after execution:

```bash
POST_PLAN_INDEX=$(gsd-sdk query phase-plan-index "${PHASE_NUMBER}")
```

Apply the same "incomplete" filtering rules as earlier:
- ignore plans with `has_summary: true`
- if `--gaps-only`, only consider `gap_closure: true` plans

**If incomplete plans still remain anywhere in the phase:**
- STOP here
- Do NOT run phase verification
- Do NOT mark the phase complete in ROADMAP/STATE
- Present: