---
name: subagent-driven-dev
description: Use when executing implementation plans with independent tasks in the current session
---

# Subagent-Driven Development

Execute plan **wave-by-wave**. Within each wave, dispatch all implementer subagents **in parallel** inside the same feature-branch worktree, then run two-stage review (spec compliance, then code quality) per task. Move to the next wave only after every task in the current wave is approved and committed.

**Why subagents:** You delegate tasks to specialized agents with isolated context. By precisely crafting their instructions and context, you ensure they stay focused and succeed at their task. They should never inherit your session's context or history — you construct exactly what they need. This also preserves your own context for coordination work.

**Why parallel waves:** Sequential execution of independent tasks wastes wall-clock time. The plan's `## Execution Waves` summary already proves which tasks are safe to run concurrently — honor it.

**Why one shared worktree (Pattern B):** Per-agent worktrees add filesystem ceremony and a merge step. Instead, the wave's implementers all work in the feature-branch worktree on **strictly disjoint file sets** (enforced by the plan's `Worktree-safe:` declarations). To prevent git-index races, **implementers never run git** — they edit files and report changed paths; the controller stages and commits per task in a deterministic order after the wave finishes.

**Core principle:** Parallel-by-default within a wave + disjoint file sets + controller-only git = maximum throughput, zero races.

## Inputs

When invoked from `orchestrator/SKILL.md` Phase 5, this skill accepts:

- `plan_path` (required, absolute) — the implementation plan to execute.
- `spec_path` (optional, absolute) — the design spec the plan was derived from. Required to enable the Step 4.5 pre-merge `pr-test-analyzer` dispatch (the analyzer reads acceptance criteria verbatim from this file).
- `summary_dir` (optional, absolute, trailing slash) — the orchestrator's `$RESEARCH_DIR_ABS/`. Required to enable the Step 4.5 pre-merge `pr-test-analyzer` dispatch.
- `tier` (optional, one of `trivial`/`small`/`medium`/`large`) — used to gate Step 4.5. Only `large` runs the dispatch; all other tiers skip silently.

Inputs other than `plan_path` are additive and backward-compatible: pre-#92 manual SDD invocations continue to work unchanged (Step 4.5 is a no-op when `spec_path`, `summary_dir`, or `tier` is absent).

## Loop caps

Every retry loop in this skill is bounded. The caps are named constants — the future `sdd-waves` workflow inherits these exact names and defaults rather than re-inventing them, and this capped directive loop is retained permanently as the non-Workflow fallback path (Gemini / Copilot / pre-Workflow harnesses execute it directly):

- **`fix_rounds` = 3** — per task, per review stage (spec compliance OR code quality): at most 3 review → fix → re-review iterations. On exhaustion, treat the task as BLOCKED (see "Handling Implementer Status") — never keep looping.
- **`retest_rounds` = 2** — per wave: at most 2 regression-attribution re-dispatches of a suspected implementer after a red full-suite run. On exhaustion, halt the wave with committed work intact and escalate.
- **`context_rounds` = 2** — per task: at most 2 NEEDS_CONTEXT answer-and-re-dispatch cycles. Overflow routes into the same BLOCKED ladder — a task still lacking context after 2 supplements has a plan problem, not a context problem.

Cap exhaustion is never silent: report which cap fired and route through the BLOCKED ladder rather than accepting unreviewed work or looping unboundedly.

## Isolation: Pattern B is the opt-out

This skill's wave-based controller-only-git approach is intentionally **not** worktree-isolated — it relies on provable file-set partitioning per wave. For any *other* parallel-agent dispatch (review fanouts, ad-hoc multi-agent edits), default to `isolation: "worktree"` on the Agent tool calls — see the `uberdev:dispatching-parallel-agents` skill.

## When to Use

## Routed child adapter (mandatory)

<!-- BEGIN child-callsite-contracts-v1 -->
```json
{
  "sdd.task.implement":{"inputs":["task_path","working_dir","allowed_paths","denied_paths","failure_path","attempt"],"optional_inputs":[],"allowed_workflows":["solve","turbo"],"risk_scope":"subtask","risk_argument":"subtask"},
  "sdd.task.spec_review":{"inputs":["spec_path","plan_path","commit_sha","allowed_paths","report_path"],"optional_inputs":[],"allowed_workflows":["solve","turbo"],"risk_scope":"subtask","risk_argument":"subtask"},
  "sdd.task.quality_review":{"inputs":["plan_path","base_sha","head_sha","allowed_paths","report_path"],"optional_inputs":[],"allowed_workflows":["solve","turbo"],"risk_scope":"subtask","risk_argument":"subtask"},
  "sdd.premerge.test_review":{"inputs":["commit_range_path","spec_path","plan_path","acceptance_path","summary_path"],"optional_inputs":[],"allowed_workflows":["solve","turbo"],"risk_scope":"subtask","risk_argument":"subtask"}
}
```
<!-- END child-callsite-contracts-v1 -->

All provider execution in this skill goes through `lib/child-dispatch.sh`.
Before the first wave, source the adapter and capture the immutable root risks:

```bash
. "${PLUGIN_ROOT:-${CODEX_HOME:-$HOME/.codex}/plugins/uberdev-codex}/lib/child-dispatch.sh"
SDD_ROOT_REQUEST_JSON="${UBERDEV_AGENT_PREPARED_REQUEST_JSON:?missing routed root request}"
SDD_WORKTREE="$(git rev-parse --show-toplevel)"
SDD_RISK_JSON="$(python3 -I -B -c 'import json,sys; r=json.loads(sys.argv[1]); print(json.dumps(r.get("root_decision",{}).get("risk_signals",r.get("risk_signals",[])),separators=(",",":")),end="")' "$SDD_ROOT_REQUEST_JSON")"
SDD_CHILD_TIMEOUT="${SDD_CHILD_TIMEOUT:-600}"
case "$SDD_CHILD_TIMEOUT" in ''|*[!0-9]*|0) return 2 ;; esac
```

The runtime helper validates the registered edge, immutable carrier, instance,
inputs, risks, confinement, and allocation. It atomically creates the private
handoff and exports `UBERDEV_CHILD_HANDOFF`, `UBERDEV_CHILD_RESULT`, and
`UBERDEV_CHILD_STATUS`; callers never compute or write those paths.

Instance IDs are allocation identities and are never reused:

`sdd-w<WAVE>-t<TASK>-<STAGE>-a<ATTEMPT>`

where stage is one of `implement`, `spec-review`, `spec-fix`, `quality-review`,
`quality-fix`, or `test-review`. Wave, task, stage, and attempt are explicit
dynamic dimensions. The four stable routing edges are:

| Edge | Role | Gate |
|---|---|---|
| `sdd.task.implement` | `implementation-worker` | required |
| `sdd.task.spec_review` | `spec-compliance-reviewer` | required |
| `sdd.task.quality_review` | `code-reviewer` | required |
| `sdd.premerge.test_review` | `pr-test-analyzer` | advisory; large tier only |

Before any helper call, validate all instance dimensions and canonicalize the
task ownership lists. Ownership paths arrive repo-relative, but child inputs
carry absolute canonical paths confined under the current worktree. Existing
symlink ancestors are resolved before the confinement check; absolute inputs,
empty paths, escapes, and allow/deny overlap are rejected.

```bash
sdd_validate_instance_dimensions() {
  local wave="$1" task="$2" stage="$3" attempt="$4"
  sdd_validate_positive_decimal "$wave" || return 2
  sdd_validate_positive_decimal "$task" || return 2
  sdd_validate_positive_decimal "$attempt" || return 2
  case "$stage" in implement|spec-review|spec-fix|quality-review|quality-fix|test-review) ;; *) return 2 ;; esac
}

sdd_validate_positive_decimal() {
  case "$1" in
    ''|*[!0-9]*) return 2 ;;
    *[1-9]*) return 0 ;;
    *) return 2 ;;
  esac
}

sdd_canonicalize_owned_paths() {
  local inputs_json="$1"
  python3 -I -B - "$SDD_WORKTREE" "$inputs_json" <<'PY'
import json,os,sys
root=os.path.realpath(sys.argv[1]); value=json.loads(sys.argv[2])
def canon(key):
    if key not in value: return set()
    raw=value.get(key,[])
    if not isinstance(raw,list) or any(not isinstance(p,str) or not p or os.path.isabs(p) for p in raw): raise SystemExit(2)
    out=[]
    for item in raw:
        path=os.path.realpath(os.path.join(root,item))
        if os.path.commonpath((root,path)) != root: raise SystemExit(2)
        out.append(path)
    if len(out)!=len(set(out)): raise SystemExit(2)
    value[key]=out
    return set(out)
allowed=canon("allowed_paths"); denied=canon("denied_paths")
if allowed & denied: raise SystemExit(2)
print(json.dumps(value,sort_keys=True,separators=(",",":")),end="")
PY
}

SDD_PREPARED_EDGES=(); SDD_PREPARED_INSTANCES=(); SDD_PREPARED_HANDOFFS=()
SDD_PREPARED_RESULTS=(); SDD_PREPARED_STATUSES=()
SDD_RECEIPT_INSTANCES=(); SDD_RECEIPT_STATUSES=(); SDD_RECEIPT_RESULTS=()
sdd_reset_batch() {
  SDD_PREPARED_EDGES=(); SDD_PREPARED_INSTANCES=(); SDD_PREPARED_HANDOFFS=()
  SDD_PREPARED_RESULTS=(); SDD_PREPARED_STATUSES=()
  SDD_RECEIPT_INSTANCES=(); SDD_RECEIPT_STATUSES=(); SDD_RECEIPT_RESULTS=()
}
sdd_begin_batch() {
  [ "${#SDD_PREPARED_HANDOFFS[@]}" -eq 0 ] || return 2
  [ "${#SDD_RECEIPT_INSTANCES[@]}" -eq 0 ] || return 2
  [ "${#SDD_RECEIPT_STATUSES[@]}" -eq 0 ] || return 2
  [ "${#SDD_RECEIPT_RESULTS[@]}" -eq 0 ] || return 2
  sdd_reset_batch
}
sdd_unwind_child_receipts() {
  local index status result cleanup_rc=0
  for ((index=0; index<${#SDD_RECEIPT_INSTANCES[@]}; index++)); do
    status="${SDD_RECEIPT_STATUSES[$index]}"; result="${SDD_RECEIPT_RESULTS[$index]}"
    if ! uberdev_unwind_child "$status" "$result" "$SDD_CHILD_TIMEOUT"; then cleanup_rc=1; fi
  done
  sdd_reset_batch
  return "$cleanup_rc"
}

sdd_dispatch_prepared() {
  local edge_id="$1" instance_id="$2" inputs_json="$3" risk_json="$4"
  local handoff result status create_rc cleanup_rc
  if uberdev_create_child_handoff "$edge_id" "$instance_id" "$inputs_json" "$risk_json"; then
    :
  else
    create_rc=$?; cleanup_rc=0
    sdd_unwind_child_receipts || cleanup_rc=$?
    [ "$cleanup_rc" -eq 0 ] || echo "error: SDD receipt unwind failed after handoff edge=$edge_id instance=$instance_id" >&2
    return "$create_rc"
  fi
  handoff="$UBERDEV_CHILD_HANDOFF"; result="$UBERDEV_CHILD_RESULT"; status="$UBERDEV_CHILD_STATUS"
  SDD_PREPARED_EDGES+=("$edge_id"); SDD_PREPARED_INSTANCES+=("$instance_id")
  SDD_PREPARED_HANDOFFS+=("$handoff"); SDD_PREPARED_RESULTS+=("$result")
  SDD_PREPARED_STATUSES+=("$status")
}

sdd_launch_prepared_batch() {
  local index edge instance handoff result status dispatch_rc cleanup_rc
  [ "${#SDD_PREPARED_HANDOFFS[@]}" -gt 0 ] || return 2
  uberdev_preflight_child_batch "${SDD_PREPARED_HANDOFFS[@]}" || {
    dispatch_rc=$?; sdd_reset_batch; return "$dispatch_rc"
  }
  for ((index=0; index<${#SDD_PREPARED_HANDOFFS[@]}; index++)); do
    edge="${SDD_PREPARED_EDGES[$index]}"; instance="${SDD_PREPARED_INSTANCES[$index]}"
    handoff="${SDD_PREPARED_HANDOFFS[$index]}"; result="${SDD_PREPARED_RESULTS[$index]}"
    status="${SDD_PREPARED_STATUSES[$index]}"
    if uberdev_dispatch_child "$edge" "$handoff" "$result" "$status"; then
      SDD_RECEIPT_INSTANCES+=("$instance")
      SDD_RECEIPT_STATUSES+=("$status")
      SDD_RECEIPT_RESULTS+=("$result")
    else
      dispatch_rc=$?; cleanup_rc=0
      sdd_unwind_child_receipts || cleanup_rc=$?
      [ "$cleanup_rc" -eq 0 ] || echo "error: bounded SDD unwind failed after edge=$edge instance=$instance" >&2
      return "$dispatch_rc"
    fi
  done
}

sdd_wait_prepared_batch() {
  local timeout="$1" index status result wait_rc first_rc=0 cleanup_rc=0
  case "$timeout" in ''|*[!0-9]*|0) return 2 ;; esac
  [ "${#SDD_RECEIPT_INSTANCES[@]}" -gt 0 ] || return 2
  for ((index=0; index<${#SDD_RECEIPT_INSTANCES[@]}; index++)); do
    status="${SDD_RECEIPT_STATUSES[$index]}"; result="${SDD_RECEIPT_RESULTS[$index]}"
    if uberdev_wait_child "$status" "$result" "$timeout"; then
      continue
    else
      wait_rc=$?
    fi
    [ "$first_rc" -ne 0 ] || first_rc="$wait_rc"
    if ! uberdev_unwind_child "$status" "$result" "$timeout"; then cleanup_rc=1; fi
  done
  sdd_reset_batch
  if [ "$first_rc" -ne 0 ]; then
    [ "$cleanup_rc" -eq 0 ] || echo "error: bounded SDD unwind failed after child wait" >&2
    return "$first_rc"
  fi
  return 0
}

sdd_json_string() {
  [ "$#" -eq 1 ] || return 2
  python3 -I -B -c 'import json,sys; print(json.dumps(sys.argv[1],separators=(",",":")),end="")' "$1"
}

sdd_json_decimal_integer() {
  [ "$#" -eq 1 ] || return 2
  python3 -I -B -c 'import re,sys; raw=sys.argv[1]; re.fullmatch(r"[0-9]+",raw) or sys.exit(2); print(str(int(raw,10)),end="")' "$1"
}

sdd_inputs_for_task() {
  local edge_id="$1" task_id="$2"
  local task_path_json working_dir_json failure_path_json attempt_json spec_path_json
  local plan_path_json commit_sha_json report_path_json base_sha_json head_sha_json
  local commit_range_path_json acceptance_path_json summary_path_json
  : "$task_id" # controller selects the task-scoped artifact variables below
  case "$edge_id" in
    sdd.task.implement)
      task_path_json="$(sdd_json_string "$task_path")" || return 2
      working_dir_json="$(sdd_json_string "$SDD_WORKTREE")" || return 2
      failure_path_json="$(sdd_json_string "$failure_path")" || return 2
      attempt_json="$(sdd_json_decimal_integer "$attempt")" || return 2
      uberdev_child_inputs_build sdd.task.implement \
        task_path "$task_path_json" \
        working_dir "$working_dir_json" \
        allowed_paths "$allowed_paths_json" \
        denied_paths "$denied_paths_json" \
        failure_path "$failure_path_json" \
        attempt "$attempt_json"
      ;;
    sdd.task.spec_review)
      spec_path_json="$(sdd_json_string "$spec_path")" || return 2
      plan_path_json="$(sdd_json_string "$plan_path")" || return 2
      commit_sha_json="$(sdd_json_string "$commit_sha")" || return 2
      report_path_json="$(sdd_json_string "$report_path")" || return 2
      uberdev_child_inputs_build sdd.task.spec_review \
        spec_path "$spec_path_json" \
        plan_path "$plan_path_json" \
        commit_sha "$commit_sha_json" \
        allowed_paths "$allowed_paths_json" \
        report_path "$report_path_json"
      ;;
    sdd.task.quality_review)
      plan_path_json="$(sdd_json_string "$plan_path")" || return 2
      base_sha_json="$(sdd_json_string "$base_sha")" || return 2
      head_sha_json="$(sdd_json_string "$head_sha")" || return 2
      report_path_json="$(sdd_json_string "$report_path")" || return 2
      uberdev_child_inputs_build sdd.task.quality_review \
        plan_path "$plan_path_json" \
        base_sha "$base_sha_json" \
        head_sha "$head_sha_json" \
        allowed_paths "$allowed_paths_json" \
        report_path "$report_path_json"
      ;;
    sdd.premerge.test_review)
      commit_range_path_json="$(sdd_json_string "$commit_range_path")" || return 2
      spec_path_json="$(sdd_json_string "$spec_path")" || return 2
      plan_path_json="$(sdd_json_string "$plan_path")" || return 2
      acceptance_path_json="$(sdd_json_string "$acceptance_path")" || return 2
      summary_path_json="$(sdd_json_string "$summary_path")" || return 2
      uberdev_child_inputs_build sdd.premerge.test_review \
        commit_range_path "$commit_range_path_json" \
        spec_path "$spec_path_json" \
        plan_path "$plan_path_json" \
        acceptance_path "$acceptance_path_json" \
        summary_path "$summary_path_json"
      ;;
    *) return 2 ;;
  esac
}
```

Executable batch shape (substitute the edge/role/stage from the table):

```bash
# Dispatch the complete batch first.
sdd_begin_batch || return $?
for task_id in $SDD_BATCH_TASK_IDS; do
  sdd_validate_instance_dimensions "$wave" "$task_id" "$stage" "$attempt" || return 2
  instance_id="sdd-w${wave}-t${task_id}-${stage}-a${attempt}"
  task_inputs_json="$(sdd_inputs_for_task "$edge_id" "$task_id")" || return 2
  task_inputs_json="$(sdd_canonicalize_owned_paths "$task_inputs_json")" || return 2
  task_inputs_json="$(uberdev_child_inputs_validate "$edge_id" "$task_inputs_json")" || return 2
  sdd_dispatch_prepared "$edge_id" "$instance_id" "$task_inputs_json" "$SDD_RISK_JSON" || return $?
done
sdd_launch_prepared_batch || return $?
# Only after every dispatch receipt, wait for the complete batch.
sdd_wait_prepared_batch "$SDD_CHILD_TIMEOUT" || return $?
```

For every parallel batch, issue every `uberdev_dispatch_child` call first.
Only after the complete batch has receipts may the controller wait for each
child. If a later dispatch fails, `sdd_unwind_child_receipts` drains every
earlier receipt to a truthful terminal and collects it before returning the
dispatch error; it never abandons a running lease. A wait failure still
inspects every sibling, boundedly unwinds each non-successful child, preserves
the first wait failure, and atomically resets all prepared/receipt state before
the next batch. Receipt fields are held in parallel shell arrays, never encoded
into delimiter-sensitive path strings. A required wait/review failure blocks the task. The
large-tier test-review edge preserves Step 4.5's advisory logging behavior.
These four SDD edges do not declare `retry.format`, so they never call
`uberdev_child_inputs_format_retry`; retry attempts rebuild their exact edge
inputs through `uberdev_child_inputs_build` instead.

```dot
digraph when_to_use {
    "Have implementation plan?" [shape=diamond];
    "Tasks mostly independent?" [shape=diamond];
    "Stay in this session?" [shape=diamond];
    "uberdev:subagent-driven-dev" [shape=box];
    "uberdev:execute-plan" [shape=box];
    "Manual execution or brainstorm first" [shape=box];

    "Have implementation plan?" -> "Tasks mostly independent?" [label="yes"];
    "Have implementation plan?" -> "Manual execution or brainstorm first" [label="no"];
    "Tasks mostly independent?" -> "Stay in this session?" [label="yes"];
    "Tasks mostly independent?" -> "Manual execution or brainstorm first" [label="no - tightly coupled"];
    "Stay in this session?" -> "uberdev:subagent-driven-dev" [label="yes"];
    "Stay in this session?" -> "uberdev:execute-plan" [label="no - parallel session"];
}
```

**vs. uberdev:execute-plan (parallel session):**
- Same session (no context switch)
- Fresh subagent per task (no context pollution)
- Two-stage review after each task: spec compliance first, then code quality
- Faster iteration (no human-in-loop between tasks)

## The Process

### High-Level Flow

1. **Read plan once.** Extract every task's full text and the `## Execution Waves` summary.
2. **Create TodoWrite** with one todo per task, labeled with its wave (e.g., `[wave-2] Task 4: ...`).
3. **Verify clean baseline:** `git status` is clean; you're on the feature branch in the feature-branch worktree. Capture `BASELINE_SHA=$(git rev-parse HEAD)` — useful only for diagnostic logging now that the post-impl-review's `commit_range` is computed independently inside `/uberdev:review-pr` Phase 1.
4. **For each wave (sequential):**
   a. Build every implementer handoff from `./implementer-prompt.md`, using edge `sdd.task.implement`, role `implementation-worker`, phase `implementation`, and instance stage `implement`. Prepare every handoff, preflight the complete wave, then dispatch before waiting. Each handoff carries exact manifest keys, including canonical absolute `allowed_paths` and `denied_paths`.
   b. **Implementers never run git.** They edit files, run their tests, and report `Status + changed file paths + test results`.
   c. After all dispatch receipts exist, wait for all wave implementers with `uberdev_wait_child`; read each immutable result artifact only after its wait succeeds.
   d. For each completed implementer (in task ID order, sequential): controller stages **only that task's reported paths** with `git add <paths>` and commits with the task-specific message.
   e. Run the project's full test command in the worktree once after all wave commits land. If it fails, identify which task regressed and re-dispatch edge `sdd.task.implement` to that task's `implementation-worker`, with stage `implement` and the next attempt plus the failure context. Re-test after each fix, capped at `retest_rounds` (2) suspected-implementer re-dispatches for the wave — still red after the cap means halt the wave with committed work intact and escalate (BLOCKED ladder); never loop further.
   f. For each committed task, build a `sdd.task.spec_review` handoff for role `spec-compliance-reviewer` from `./spec-reviewer-prompt.md`. Prepare all wave-eligible reviewers, preflight the batch, then dispatch before waiting. Pass each reviewer these context inputs:
      - `spec_path`: absolute design spec
      - `plan_path`: absolute implementation plan
      - `commit_sha`: controller-created task commit
      - `allowed_paths`: canonical absolute ownership paths
      - `report_path`: immutable implementer result
   g. Loop spec fix-up per task until that task's reviewer approves, capped at `fix_rounds` (3) iterations per task. Fixes use `sdd.task.implement`/`implementation-worker`, stage `spec-fix`, and the next attempt; re-reviews use `sdd.task.spec_review` with the next attempt. Exhaustion routes the task into the BLOCKED ladder. Fix dispatches still don't run git — controller amends the task's commit (or creates a fix-up commit) using the implementer's reported new paths.
   h. As soon as a task's spec review approves, add its `sdd.task.quality_review`/`code-reviewer` handoff to the next eligible quality batch. Prepare all handoffs, preflight the batch, then dispatch before waiting. Do NOT hold all quality reviews hostage to the slowest sibling's spec fix-loop. Quality fixes use `sdd.task.implement`, stage `quality-fix`; quality re-reviews use the quality edge with the next attempt. Same `fix_rounds` (3) cap per task.
   i. Mark every task in the wave complete in TodoWrite.
   j. **Mark wave complete.** No additional accumulation required at the SDD layer — `/uberdev:review-pr` Phase 1, chained post-push from `finish-branch`, computes its own `changed_paths` and `commit_range` against the pushed PR.

   **Step 4.5 — Pre-merge `pr-test-analyzer` dispatch (large-tier only, requires `spec_path` and `summary_dir`).** Runs once after all waves complete and before the Step 5 handoff. If `tier == "large"` AND `spec_path` is non-empty AND `summary_dir` is non-empty, securely pre-create the regular artifact file (the handoff validator rejects directory-valued context and requires absolute artifact paths to exist), then dispatch edge `sdd.premerge.test_review` to role `pr-test-analyzer`, stage `test-review`, attempt 1:

```bash
SDD_TEST_REVIEW_OUTPUT="${summary_dir%/}/pr-test-analyzer.md"
python3 -I -B - "$SDD_TEST_REVIEW_OUTPUT" <<'PY'
import os,sys
fd=os.open(sys.argv[1],os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600)
os.close(fd)
PY
```

   Its exact inputs are `commit_range_path`, `spec_path`, `plan_path`,
   `acceptance_path`, and `summary_path=$SDD_TEST_REVIEW_OUTPUT`. The controller
   first writes commit range and acceptance criteria to private regular run
   artifacts. `summary_dir` is a controller gate, never a child input. Prepare
   this one-child batch, preflight, dispatch, and wait before Step 5. After a
   successful wait, copy the immutable child result byte-for-byte to the
   already-confined `summary_path`; do not parse or transform it. This is
   one advisory routed child, not the post-push reviewer fanout.

   All three return cases below end by proceeding to Step 5; `finish-branch`'s artifact-collection glob discovers whatever is on disk. Each case differs only in its log action:
   1. The result verdict is `APPROVE` — artifact is on disk; no log entry.
   2. The result verdict is `REJECT` — the agent completed analysis and found gaps; artifact IS on disk; log the `REJECT` verdict to `<summary_dir>/orchestrator.log` with a `REJECT` tag.
   3. Dispatch/wait fails or no valid result envelope exists — log `FAILURE` and the cause to `<summary_dir>/orchestrator.log` (best-effort; artifact may be absent or partial).

```yaml lineage
edge_id: sdd.finish_branch
model_invocation: false
```

5. Hand off to `uberdev:finish-branch` (no flag arg). The branch close-out detects unattended mode via the inherited `UBERDEV_TURBO=1` environment variable from the selected dispatch backend — under that signal, `finish-branch` auto-selects "Push and Create PR" without prompting (#97). For large tier, `pr-test-analyzer` was dispatched in Step 4.5 (above) and its findings are now on disk at `<summary_dir>/pr-test-analyzer.md`. `finish-branch` will discover and include them in the PR body's `## Reviewer findings summary` section. Post-implementation reviewer fanout is hosted by `/uberdev:review-pr` Phase 1 (chained from `finish-branch` after PR push); no reviewer *fanout* is dispatched from `subagent-driven-dev` itself (see Step 4.5 for the carve-out vs the retired `uberdev:post-impl-review` fanout).

### Parallel Dispatch Pattern

```
[wave-1] →  Child(T1, edits files only)  ┐
            Child(T2, edits files only)  ├─ dispatch complete batch, shared CWD
            Child(T3, edits files only)  ┘
                ↓ wait for all three
            controller: git add <T1 paths> && git commit  (sequential, deterministic)
            controller: git add <T2 paths> && git commit
            controller: git add <T3 paths> && git commit
            controller: run full test suite
                ↓
            spec reviewers (parallel) → fix loop → re-reviews
            quality reviewers (parallel) → fix loop → re-reviews
                ↓ wave complete (no SDD-layer accumulation —
                ↓ /review-pr Phase 1 computes its own diff post-push)
                ↓ no merge step — already on feature branch
[wave-2] →  Child(T4, edits files only)  ┐
            Child(T5, edits files only)  ┘  (parallel, depend on wave-1 commits)
            ...
[wave-N] →  ...  (last wave finishes)
                ↓
            hand off to uberdev:finish-branch
                ↓ (finish-branch pushes PR, then chains)
            /uberdev:review-pr
                Phase 1: uberdev:post-impl-review (6 agents, 1 message)
                Phase 2: simplify lenses (3 agents, 1 message)
```

### File-Ownership Enforcement

Before dispatching a wave, build the wave's ownership map:

```
T2 owns: src/recovery.ts, tests/recovery.test.ts
T3 owns: src/progress.ts, tests/progress.test.ts
T4 owns: src/telemetry.ts, tests/telemetry.test.ts
```

Every implementer prompt receives **its own allowlist + the union of sibling-owned paths as a denylist**. If two tasks claim the same file, the wave decomposition is wrong — bump one to the next wave before dispatching.

### Per-Task Inner Loop (unchanged)

```dot
digraph per_task {
    rankdir=TB;
    "Implementer (in worktree)" [shape=box];
    "Implementer questions?" [shape=diamond];
    "Answer & re-dispatch" [shape=box];
    "Spec reviewer" [shape=box];
    "Spec OK?" [shape=diamond];
    "Implementer fixes spec" [shape=box];
    "Code quality reviewer" [shape=box];
    "Quality OK?" [shape=diamond];
    "Implementer fixes quality" [shape=box];
    "Task complete" [shape=box style=filled fillcolor=lightgreen];

    "Implementer (in worktree)" -> "Implementer questions?";
    "Implementer questions?" -> "Answer & re-dispatch" [label="yes"];
    "Answer & re-dispatch" -> "Implementer (in worktree)";
    "Implementer questions?" -> "Spec reviewer" [label="no"];
    "Spec reviewer" -> "Spec OK?";
    "Spec OK?" -> "Implementer fixes spec" [label="no"];
    "Implementer fixes spec" -> "Spec reviewer";
    "Spec OK?" -> "Code quality reviewer" [label="yes"];
    "Code quality reviewer" -> "Quality OK?";
    "Quality OK?" -> "Implementer fixes quality" [label="no"];
    "Implementer fixes quality" -> "Code quality reviewer";
    "Quality OK?" -> "Task complete" [label="yes"];
}
```

## Handling Implementer Status

Implementer subagents report one of four statuses. Handle each appropriately:

**DONE:** Proceed to spec compliance review.

**DONE_WITH_CONCERNS:** The implementer completed the work but flagged doubts. Read the concerns before proceeding. If the concerns are about correctness or scope, address them before review. If they're observations (e.g., "this file is getting large"), note them and proceed to review.

**NEEDS_CONTEXT:** The implementer needs information that wasn't provided. Provide the missing context and re-dispatch — at most `context_rounds` (2) answer-and-re-dispatch cycles per task; overflow routes into the BLOCKED ladder below.

**BLOCKED:** The implementer cannot complete the task. Assess the blocker:
1. If it's a context problem, provide more context and re-dispatch the same stable edge
2. If the task exposes additional risk, update the root risk evidence and let policy select the warranted route; never put a model override in the handoff
3. If the task is too large, break it into smaller pieces
4. If the plan itself is wrong, escalate to the human

**Never** ignore an escalation or retry unchanged context and risk evidence. If the implementer said it's stuck, something needs to change.

## Prompt Templates

- `./implementer-prompt.md` - Dispatch implementer subagent
- `./spec-reviewer-prompt.md` - Dispatch spec compliance reviewer subagent
- `./code-quality-reviewer-prompt.md` - Dispatch code quality reviewer subagent

## Example Workflow

```
You: I'm using Subagent-Driven Development to execute this plan.

[Read plan file once: docs/uberdev/plans/feature-plan.md]
[Extract all 5 tasks + Execution Waves summary:
   wave-1: T1 (schema)
   wave-2: T2, T3, T4 (parallel — different files)
   wave-3: T5 (depends on T2,T3,T4)
]
[Create TodoWrite labeled by wave]

=== WAVE 1 ===

Task 1: Hook installation script (alone in wave-1)
T1 owns: scripts/install-hook.sh, tests/install-hook.test.sh

[Dispatch implementer in shared worktree, full task text + allowlist + "no git commands"]

Implementer: "Before I begin - should the hook be installed at user or system level?"

You: "User level (~/.config/uberdev/hooks/)"

Implementer: "Got it. Implementing now..."
[Later] Implementer:
  - Edited scripts/install-hook.sh, tests/install-hook.test.sh
  - Tests 5/5 passing
  - Self-review: Found I missed --force flag, added it
  - Status: DONE — paths: [scripts/install-hook.sh, tests/install-hook.test.sh]

[Controller: git add scripts/install-hook.sh tests/install-hook.test.sh && git commit -m "feat: install-hook script"]
[Run full test suite — green]

[Dispatch spec compliance reviewer]
Spec reviewer: ✅ Spec compliant - all requirements met, nothing extra

[Dispatch code quality reviewer]
Code reviewer: Strengths: Good test coverage, clean. Issues: None. Approved.

[Mark Task 1 complete]

=== WAVE 2 ===

Ownership map:
  T2 owns: src/recovery.ts, tests/recovery.test.ts
  T3 owns: src/progress.ts, tests/progress.test.ts
  T4 owns: src/telemetry.ts, tests/telemetry.test.ts

[Dispatch all three routed children before waiting — same shared worktree, no git permitted]
  Routed child(T2: Recovery modes,    allow=[T2 paths], deny=[T3+T4 paths])
  Routed child(T3: Progress reporting, allow=[T3 paths], deny=[T2+T4 paths])
  Routed child(T4: Telemetry hooks,    allow=[T4 paths], deny=[T2+T3 paths])

[Wait for all three implementers to report back with their changed paths]

[Controller, sequential — one commit per task in task ID order]
  git add <T2 paths> && git commit -m "feat: recovery modes"
  git add <T3 paths> && git commit -m "feat: progress reporting"
  git add <T4 paths> && git commit -m "feat: telemetry hooks"

[Run full test suite — green]

[Dispatch complete spec-review batch for T2, T3, T4 before waiting]
[Loop: any failed spec review → re-dispatch that task's implementer (no git); controller amends or fix-up commits using reported paths; re-review until ✅, max fix_rounds=3 per task; each task that reaches ✅ proceeds straight to its quality review]

[Dispatch complete eligible code-quality batch before waiting]
[Loop: any failed quality review → same fix pattern → re-review until ✅, max fix_rounds=3 per task]

[Mark Tasks 2, 3, 4 complete]

=== WAVE 3 ===

[T5 alone — depends on wave-2 commits being on the branch]
[Dispatch implementer in shared worktree]
[Controller commits → run full suite → spec review → fix loop → quality review → fix loop → mark complete]

=== AFTER ALL WAVES ===

[For large tier: SDD Step 4.5 dispatches pr-test-analyzer pre-merge before the finish-branch handoff]

[Hand off to uberdev:finish-branch — which pushes the PR and chains into /uberdev:review-pr Phase 1 (6 reviewer agents, advisory — roster owned by post-impl-review/SKILL.md)]
```

## Advantages

**vs. Manual execution:**
- Subagents follow TDD naturally
- Fresh context per task (no confusion)
- Parallel-safe (subagents don't interfere)
- Subagent can ask questions (before AND during work)

**vs. uberdev:execute-plan:**
- Same session (no handoff)
- Continuous progress (no waiting)
- Review checkpoints automatic

**Efficiency gains:**
- No file reading overhead (controller provides full text)
- Controller curates exactly what context is needed
- Subagent gets complete information upfront
- Questions surfaced before work begins (not after)

**Quality gates:**
- Self-review catches issues before handoff
- Two-stage review: spec compliance, then code quality
- Review loops ensure fixes actually work
- Spec compliance prevents over/under-building
- Code quality ensures implementation is well-built

**Cost:**
- More subagent invocations (implementer + 2 reviewers per task)
- Controller does more prep work (extracting all tasks upfront)
- Review loops add iterations
- But catches issues early (cheaper than debugging later)

## Red Flags

**Never:**
- Start implementation on main/master branch without explicit user consent
- Skip reviews (spec compliance OR code quality)
- Proceed with unfixed issues
- Dispatch multiple implementers **without explicit file allowlists/denylists** — they will trample each other's edits
- Let implementer subagents run **any** git command (`add`, `commit`, `stash`, `restore`) — that's the controller's job
- Use `git add -A` or `git add .` to stage a task's commit — always pass the implementer's reported paths explicitly
- Dispatch implementers from **different waves** in parallel — wave-N depends on wave-(N-1) being committed first
- Skip the post-wave full-test-suite run — without it, a regression introduced by parallel edits hides until much later
- Run tasks sequentially when the plan declares them in the same wave (defeats the whole point)
- Make subagent read plan file (provide full text instead)
- Skip scene-setting context (subagent needs to understand where task fits)
- Ignore subagent questions (answer before letting them proceed)
- Accept "close enough" on spec compliance (spec reviewer found issues = not done)
- Skip review loops (reviewer found issues = implementer fixes = review again)
- Let implementer self-review replace actual review (both are needed)
- **Start a task's code quality review before that task's spec compliance is ✅** (wrong order — but the gate is per task: a spec-approved task starts quality review immediately, regardless of siblings still in their spec fix-loops)
- Move to next task while either review has open issues

**If subagent asks questions:**
- Answer clearly and completely
- Provide additional context if needed
- Don't rush them into implementation

**If reviewer finds issues:**
- A fresh `implementation-worker` child on the implementation edge fixes them
- Reviewer reviews again
- Repeat until approved or the task's `fix_rounds` cap (3) is exhausted — then route through the BLOCKED ladder, never an unbounded loop
- Don't skip the re-review

**If subagent fails task:**
- Dispatch fix subagent with specific instructions
- Don't try to fix manually (context pollution)

## Integration

**Required workflow setup (run before this skill):**
- **Isolated worktree** — `git worktree add .worktrees/<feature-name> -b <branch-name>` (verify `.worktrees/` is in `.gitignore`; add and commit if not). Run the project's setup command (`npm install` / `cargo build` / `pip install -r requirements.txt` / `go mod download`) and the project's test command to verify a clean baseline before starting.

**Related skills:**
- **`uberdev:write-plan`** — creates the plan this skill executes
- **`uberdev:execute-plan`** — alternative for parallel-session/inline execution

**Subagents follow TDD discipline within each task:** write a minimal failing test for the new behavior FIRST, run it to see it fail for the expected reason, write the simplest code that makes it pass, run again to see green, then refactor while green. The implementer-prompt.md template enforces this; the spec and code-quality reviewers verify it was actually applied.

**Code review dispatch:** the code-quality reviewer in this skill dispatches the bundled `uberdev:code-reviewer` agent (see `plugins/uberdev/agents/code-reviewer.md`) — no separate "requesting-code-review" skill is needed, the agent's own prompt encapsulates the review template.

**Finishing the development branch:** after all tasks pass review and the final-pass code review approves, invoke `uberdev:finish-branch` to verify tests, present the 4-option close-out (merge / PR / keep / discard), execute the chosen one, and clean up the worktree.
