# Developer

> Orchestrates unattended spec delivery — loops over a spec's child issues in dependency order, dispatching dispatcher (complexity triage), code-author (implement), and diff-reviewer (review) workers per sub-issue, with a review→fix cycle until CLEAN, then merging per the repo's merge policy. Tracker- and host-agnostic — issues and changes live wherever docs/agents/issue-tracker.md and docs/agents/code-host.md say (GitHub via gh is the factory default). Factory defaults are parallel execution and manual merge; repo defaults live in docs/agents/developer-defaults.md and per-run flags (--parallel/--sequential, --auto-merge/--no-auto-merge) override them. Use when user says "/developer", "deliver this spec" (or "deliver this PRD"), "deliver this sub-issue", or wants the build→review→fix pipeline.

- Skill: `sgomez/developer` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add sgomez/developer`
- Raw SKILL.md: https://api.skillmd.com/api/skills/sgomez/developer/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Product & Planning
- Author: sgomez (https://skillmd.com/u/sgomez)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/sgomez/developer

---


# Developer (orchestrator)

Drives the triage → build → review → fix → merge pipeline across isolated
subagent workers, looping over every sub-issue of a spec unattended. Each
worker gets a **clean context** — the only thing it knows is the arguments you
pass in its prompt. You (the orchestrator) hold the state between steps.

## Invoke

```
/developer <issue>              # spec with sub-issues → deliver them all
                                # plain issue → deliver just that one
/developer <spec> <subissue>    # deliver a single specific sub-issue

Flags (override the repo defaults — see Run configuration):
  --parallel | --sequential     # spec mode: waves vs one-at-a-time
  --auto-merge | --no-auto-merge  # merge CLEAN PRs vs leave them ready
  --build-oversized             # build `oversized` tickets instead of escalating
```

If no issue number is given, ask for it and stop. Do not guess issue numbers.
The execution flags only change spec mode; in single mode they are a no-op.
Accept the bare words `parallel` / `sequential` as synonyms for the flags.

> **Namespacing.** Installed as a Claude Code plugin, skills and agents carry
> the plugin prefix: the skills appear as `developer-skills:<name>` and the
> subagents as `developer-skills:dispatcher` / `developer-skills:code-author` /
> `developer-skills:diff-reviewer`. Use the names exactly as they appear in
> your available-skills and available-agents lists; the short names below
> refer to whichever form is installed.

## Contract docs (tracker + code host)

The pipeline is agnostic about where issues and changes live. Two committed
docs define the mechanics for this repo, and every worker reads them in its
own context:

- **`docs/agents/issue-tracker.md`** — issue operations (read an issue,
  enumerate children of a parent, check a blocker, comment, label, close)
  in its `## Delivery operations` section.
- **`docs/agents/code-host.md`** — change operations (publish, check out in
  a worktree, review, mark ready, reply, merge, auto-close semantics).

Read those two once at the start (they are short — an allowed exception to
"never read bodies yourself").

**Their annexes are deferred, and every worker inherits that.** Each core doc
links phase annexes naming the phase that opens them: `code-host-ci.md` is
opened at the **checks gate**, when a change's CI has to be waited on, read
or classified — nowhere earlier; `issue-authoring.md` is for whatever
*creates* issues (`/to-tickets`) and this pipeline never opens it at all.
Read an annex at the step that names it and not before. A run that opens
them at the start pays the whole contract, three workers deep, per
sub-issue, to use a fraction of it. **Every command block below shows the GitHub
factory default (`gh`); when a contract doc defines a different mechanic
for the same operation, the doc wins.** If a doc is missing, the GitHub
defaults apply as-is — suggest `/setup-developer-skills` if that looks
wrong.

Note one capability flag from `docs/agents/code-host.md`: **issue auto-close
on merge?** If not (e.g. issues on a tracker the code host can't close), the
orchestrator closes the delivered issue itself per the tracker ops right
after verifying the merge.

**If either doc says the tracker or the code host is `local` (files in the
repo, no remote), read `LOCAL-HOST.md` now, before anything else.** It holds
every standing adjustment a local host or tracker needs — capability
overrides, tracker writes, branch discipline, cleanup and wrap-up. A run on a
remote host never loads it.

Two more files are read **on demand**, never at the start: `MERGE-FIX.md` at
the first merge conflict, and `WRAP-UP.md` when the loop ends.

(All three live **next to this SKILL.md**, in the skill's own directory —
under the plugin root when installed as a plugin, the same place
`scripts/cleanup-worktrees.sh` comes from. They are part of this skill: a
step that says to read one is not optional, it is that step's other half.)

## Run configuration

Three knobs govern a run. Resolve each one **before mode detection**, in this
precedence order: CLI flag > repo default > factory default.

| Knob        | Values                    | Factory default |
|-------------|---------------------------|-----------------|
| `execution` | `parallel` / `sequential` | `parallel`      |
| `merge`     | `auto` / `manual`         | `manual`        |
| `oversized` | `escalate` / `build`      | `escalate`      |

Repo defaults live in `docs/agents/developer-defaults.md`, written by
`/setup-developer-skills`. Read it once at the start (it is short — this is
an allowed exception to "never read bodies yourself"); if it is missing or a
knob is absent, fall back to the factory default. State the resolved
configuration in one line before starting, e.g.
`Run config: execution=parallel, merge=manual (repo defaults)`.

What `merge` means:

- **`auto`** — a CLEAN verdict triggers the code host's merge operation
  (Merge step; `gh pr merge` on GitHub). The
  committed `merge: auto` line in `docs/agents/developer-defaults.md` is the
  user's standing authorization for these merges.
- **`manual`** — the pipeline stops at CLEAN: you already marked the PR
  ready after the review, so record the sub-issue as **ready-to-merge**
  and leave
  the merge to the human. Because sub-issues only close on merge
  (`Closes #N`), anything `Blocked by` a ready-to-merge sub-issue stays
  blocked for the rest of the run — expected, not an error; it lands in the
  wrap-up as the human's queue.

What `oversized` means — what to do with a sub-issue triage scores
`oversized` (`--build-oversized` sets it to `build` for the run):

- **`escalate`** — the default and the safe reading: the ticket is handed to
  a human to re-cut, and nothing is built (Triage step).
- **`build`** — build it anyway, at `opus`, exactly as if triage had said
  `complex`. This is the user's standing answer to "the ticket is too big":
  they have decided the split is not worth the round trip. Note the risk once
  when you resolve the config, then stop arguing it — the dispatcher's fault
  lines still go into the builder's prompt as its order of work, and if the
  builder does come back with half a feature, that PR escalates through the
  ordinary non-convergent path rather than a second opinion about size.

## Workers (subagents)

| Step    | Subagent        | Model                           | Isolation  | Skill it runs     |
|---------|-----------------|---------------------------------|------------|-------------------|
| triage  | `dispatcher`    | sonnet (pinned)                 | —          | (scores a wave)   |
| build   | `code-author`   | chosen by triage                | `worktree` | `implement-issue` |
| review  | `diff-reviewer` | opus first, sonnet on re-review | `worktree` | `review-pr`       |
| fix     | `code-author`   | escalates per cycle             | `worktree` | `fix-pr`          |
| harvest | `code-author`   | sonnet (pinned)                 | `worktree` | (reads PR bodies) |

Spawn each via the **Agent** tool with the matching `subagent_type`. Pass
`isolation: "worktree"` to every code-author and diff-reviewer spawn. Pass
`model` explicitly to code-author spawns (triage decides the tier) and to
**re-review** diff-reviewer spawns (`model: "sonnet"`) — the first review is
discovery across the whole change and stays on the agent's pinned opus, while a
re-review is verification of a diff the skill has already scoped down to the
last fix pass. Never run the skills yourself in the main context — the point is
isolation.

**Every spawn is `run_in_background: true`**, dispatchers included, in both
execution modes. A foreground spawn holds your turn open for the worker's whole
run, and anything that interrupts that turn — a Ctrl-C, a dropped connection —
takes the worker down with it: its context, its worktree and its commits are
gone for good. The same interruption leaves a background worker running and
still reachable. Sequential mode is not an exception to this: it means *wait
for this worker's result before spawning the next*, not *spawn it in the
foreground*.

Three things at every spawn, each cheap now and each the difference between a
resume and a rebuild later:

- Give the Agent tool a `description` that names the job and the sub-issue —
  `Build #<N>`, `Review PR #<PR>`, `Fix #<N> cycle 2`. It is how the worker is
  identified in the agent list and in the user's view of the run.
- **Keep the `agentId`** the tool returns for as long as that worker runs: it
  is the handle that picks the worker back up instead of starting it over (see
  **Resuming the orchestrator**). Drop it when its `RESULT` arrives.
- On a **BUILD, FIX or HARVEST** spawn — the jobs that hold uncommitted work
  and are expensive to lose — append a spawn row to this run's log as soon as
  the tool hands you the `agentId`, so the job leaves a trace that outlives
  your context:

  ```bash
  mkdir -p .scratch
  echo "$(date +%F) spec=#<spec> sub=#<N> event=spawned job=<build|fix|harvest> agent=<agentId> model=<tier>" \
    >> .scratch/developer-run-<spec>.log
  ```

  (`sub=none` on the harvest, which belongs to the whole run.) Triage and
  review spawns skip the row entirely: both are cheap to repeat, and pipeline
  step 0 reconstructs where a PR stands without them. Step 7 writes the other
  kind of row, the terminal one — a resume reads both, the wrap-up reads only
  the terminal ones.

## Context economy

The loop may cover many sub-issues; your context must survive all of them.

- **Never read issue or PR bodies yourself.** Workers read them in their own
  disposable contexts. You only run the cheap listing commands below.
- **Every command you run prints a bounded projection, never a blob.** Ask
  for the fields you need (`--json`/`--jq`, `--format`) and cap what is left
  (`head -20`, `| wc -l`). Two failure modes cost the most: a streaming or
  watching command (`gh pr checks --watch`, `gh run watch`) repainting its
  progress into your transcript, and a **malformed** command dumping its
  tool's entire `--help` — a single mis-escaped `gh pr list --search` did
  exactly that for 4k tokens in one field run. So send both streams of any
  probe you are not certain of through a cap: `<cmd> 2>&1 | head -20`. What
  you need from these commands is one number, one state or one exit code.
- **Every spawn costs about the same whatever it carries** — roughly 750
  tokens of prompt, launch metadata and result notification, against a payload
  that is often one word. So batch the work that can be batched (triage scores
  a whole wave in one dispatcher) and never spawn a worker to recover
  something a default already covers. Builds, reviews and fixes cannot be
  batched — each needs its own worktree and its own clean context — and are
  worth their envelope; a second dispatcher for one missing score is not.
- **Never read CI logs yourself** either — `gh run view --log-failed` and
  anything like it. The Merge step's `--json` classification is the whole
  diagnosis the orchestrator gets; the rest is the fixer's, in its context.
- A worker's whole final message **is** its `RESULT` line — the agents require
  it and every spawn prompt below restates it. A worker that reports prose
  before its line is spending your context, not its own; nothing it says there
  survives the run, so anything worth keeping belongs on the PR or the issue.
- Track per sub-issue: number, task id, chosen model, PR number, verdict, fix
  cycles, wave (parallel mode), the `agentId` of the worker running on it right
  now (dropped the moment its `RESULT` arrives), outcome (merged /
  ready-to-merge / escalated / blocked) — and write the row to the run log the
  moment the sub-issue goes terminal (delivery pipeline step 7), so the wrap-up
  reads facts instead of recalling them. Also keep the dispatcher's
  `touches`/`hints` just long enough to forward `hints` into that sub-issue's
  Build step — discard both once the build is spawned, they have no use after
  that.

## Resuming the orchestrator

A run outlives your turn: workers keep going in the background, their
notifications can arrive late, and sessions get interrupted, compacted or
restarted. So **while a run is in flight, every prompt that reaches you is a
resume** — including a bare `Continue from where you left off.`, an empty
continuation, or a notification you believe you have already handled. There is
no state in which the right answer is "no response requested": either work is
pending and you take its next step, or nothing is, and you go to **Wrap-up**.
Silence is the one failure mode this pipeline cannot recover from on its own.

(This is the orchestrator's own resume. Pipeline step 0 is the per-sub-issue
one — it is what step B below runs.)

On any such prompt:

1. **Rebuild the picture** from the three places that survive a dead context,
   never from recall:
   - the progress board (**TaskList**) — which sub-issues are `in_progress`;
   - `.scratch/developer-run-<spec>.log` — the terminal rows already recorded,
     and the `event=spawned` rows naming the worker that was running on each
     sub-issue still in flight. If a wrap-up already ran this spec, the rows
     from before it are in `.scratch/archive/developer-run-<spec>-*.log`;
   - **ListAgents** — which of those workers are still alive.
2. **Recover each non-terminal sub-issue** in this order, stopping at the first
   that works:

   **A. Its worker is still alive** → **SendMessage** to its `agentId` and ask
   it to report. Its context, its worktree and its commits are all intact, so
   this continues the job rather than repeating it — by far the cheapest
   recovery, and the only one that does not throw away work already paid for.
   A worker that finished while you were not looking answers here too, with the
   `RESULT` whose notification you missed.

   This works for `code-author`. It usually does not for `diff-reviewer`: a
   review changes no files, so its worktree is removed when it ends and a
   reviewer missing from `ListAgents` is missing for good. Try it if it is
   listed; otherwise go straight to B — a review is cheap to repeat, a build
   is not.

   **B. Its worker is gone** → run pipeline **step 0** on that sub-issue
   exactly as written. It asks the code host rather than your memory, and
   routes the sub-issue to Review, to the Fix cycle, or back to Triage when
   nothing was ever opened for it.

3. **Re-enter the loop**: recompute the unblocked set (spec loop step 1, or the
   wave in parallel mode) and carry on. Nothing left → **Wrap-up**.

Say in one line what you recovered and how, before continuing. During an
unattended run the board and that line are the user's whole window into it.

## Step 0 — Publish context docs before anything else

Workers branch from `origin/main`, so any domain-context file that is not
committed **and pushed** is invisible to them. Grilling/spec sessions edit
these files but do not commit them. Before dispatching any worker:

```bash
git status --porcelain -- CONTEXT-MAP.md '**/CONTEXT.md' docs/adr docs/agents AGENTS.md CLAUDE.md
```

If anything shows up, stage **only those paths** (never the user's unrelated
work-in-progress), commit on the current branch (must be `main` — if not,
stop and tell the user), and push:

```bash
git add CONTEXT-MAP.md '**/CONTEXT.md' docs/adr docs/agents AGENTS.md CLAUDE.md
git commit -m "docs(domain): publish context map and ADR updates"
git push origin main
```

If the push is rejected, stop and report — do not rebase or force anything.
This is the flow's start, before going unattended; the user is still there to
resolve it.

## Mode detection

Enumerate the children of the given issue per the tracker's Delivery
operations. GitHub default — native sub-issues (infer OWNER/REPO from
`git remote -v`):

```bash
gh api graphql -f query='
{
  repository(owner:"OWNER", name:"REPO") {
    issue(number: N) {
      subIssues(first: 50) {
        pageInfo { hasNextPage }
        nodes { number title state labels(first: 10) { nodes { name } } }
      }
    }
  }
}' --jq '.data.repository.issue.subIssues'
```

If `hasNextPage` is `true`, **stop and report**: a spec with more than 50
sub-issues is not sized for this pipeline — tell the user to split it and end
the run. Never proceed on the first page alone: delivering 50 of 60 while
reporting the spec complete is a silent failure, the one outcome worse than
stopping.

Keep each sub-issue's labels from this query — the pick reads them (spec loop
step 1). Where a tracker's enumeration carries no labels, get them per its
read-labels operation instead.

(Throughout this skill, `#<N>` stands for the issue ref in the tracker's
own format — a number on GitHub/GitLab, a file path on a local tracker —
and `#<PR>` for the change ref in the code host's format.)

- **Open sub-issues exist → spec mode**: loop over all of them (below).
- **No sub-issues → single mode**: run the delivery pipeline once on the given
  issue, with the issue itself as spec (no separate parent spec).
- **Two arguments given**: run the delivery pipeline once on `<subissue>` with
  `<spec>` as the spec. Skip the loop. If the sub-issue ends **merged**
  (verified CLOSED), read `WRAP-UP.md` and run its **Close the spec** step
  (step 4) afterwards — it may have been the spec's last open sub-issue. That
  one step is all this mode needs from the wrap-up.

## Progress board (spec mode — not optional)

The user follows the run through the harness task list. Keep it faithful at
every transition; a stale board defeats its purpose.

1. **Immediately after mode detection**, create one task per open sub-issue
   with **TaskCreate**, in sub-issue order: subject `#<N> <short>`,
   activeForm `Delivering #<N>`. The whole plan must be on the board before
   the first worker spawns.

   `<short>` is the sub-issue title **trimmed to about six words / 50
   characters**, cut at a word boundary and with no ellipsis — enough for the
   user to tell the rows apart, and it never grows. Keep the same `<short>`
   for that sub-issue's every later rename. The number is the identifier; the
   words are only a label, and the full title is one `gh issue view` away for
   anyone who needs it. This is not cosmetic: the harness re-injects the
   **whole board** into your context on a timer, so every character of every
   subject is re-read many times over a long run — a board of 25 full titles
   costs more over a run than the entire spawn traffic it is tracking.
2. When the delivery pipeline starts on a sub-issue → **TaskUpdate**
   `status: in_progress`. In parallel mode every wave member goes
   in_progress as its build spawns, so the board shows exactly what is
   running concurrently.
3. Terminal transitions, the moment they happen:
   - **merged** (sub-issue verified CLOSED) → `status: completed`.
   - **ready-to-merge** (`merge: manual`, verdict CLEAN) → back to
     `status: pending` and rename the subject to
     `#<N> <short> — ready to merge: PR #<PR>`. Not completed — the human
     still has to merge it.
   - **escalated** → back to `status: pending` and rename the subject to
     `#<N> <short> — escalated: <one-line reason>`. Never mark an escalated
     sub-issue completed — unchecked items at the end are the human's queue.
4. Sub-issues that never became deliverable (blocked by an escalated one, or
   by a ready-to-merge one the human hasn't merged yet) stay pending; rename
   them `#<N> <short> — blocked by #<M>` at wrap-up.

Single mode (no sub-issues) skips the board.

**The board is the report — do not narrate the run beside it.** Between the
run-config line and the wrap-up, a spec run's default output is *nothing*: the
task list already says which sub-issue is building, which is in review, which
is merged and which is waiting, and it says it live, without costing a turn.
Prose that restates it — "wave 1 launched", "triage complete", a table of the
tier each sub-issue drew, "5 of 25 merged" — is a second, staler copy of the
board, and the user has to read past it to reach the part that is not on the
board. Keep the board current instead; that *is* the progress report.

Six things still get said, each in **one or two lines**, never a table:

- the resolved run config, once, before starting (Run configuration);
- what you recovered, once, after a resume (Resuming the orchestrator);
- a switch into the conflict queue (Parallel mode);
- **how** to merge, the first time a sub-issue lands ready-to-merge under
  `merge: manual` — once for the run, not once per PR; the wrap-up repeats it
  for the rest;
- anything that **stops** the run or needs the human: an escalation and why,
  a denied permission, a spec too large to size;
- a direct question from the user, answered directly — the silence rule
  governs unprompted narration, never a reply.

Everything else the run learns goes where it survives: the board, the PR, the
issue, the run log, and the wrap-up summary at the end.

## Spec loop

Repeat while open sub-issues remain:

1. **Pick the next unblocked sub-issue**: for each open sub-issue (lowest
   number first), check its blockers without reading full bodies — the
   "check a blocker's state" operation from the tracker doc. Blockers may
   be wired as the tracker's **native dependency links**, as a
   `Blocked by` body section, or both (`/to-tickets` prefers native edges
   where the tracker has them) — check both. GitHub default:

   ```bash
   # native dependencies: count of OPEN blockers (0 or absent = clear)
   gh api repos/{owner}/{repo}/issues/<N> --jq '.issue_dependencies_summary.blocked_by // 0'
   # body fallback: every blocker listed in the section must be CLOSED
   gh issue view <N> --json body --jq '.body' \
     | awk '/^##[#]* *[Bb]locked by/{f=1;next} /^#/{f=0} f'
   gh issue view <BLOCKER> --json state --jq '.state'
   ```

   Extract the `Blocked by` **section**, never a fixed window around the
   heading: a `grep -A<n>` reads the wrong number of lines by construction —
   it drops the fifth blocker of a list of six and swallows the first lines
   of whatever section follows a list of two.

   Take the first open sub-issue whose blockers are all closed, and:

   - **Skip any sub-issue carrying the `ready-for-human` triage label** (the
     repo's own string for that role if `docs/agents/triage-labels.md` maps it
     differently) — from the labels the enumeration returned, plus the ones
     you applied yourself while escalating this run. That label is the
     escalation gate: someone already gave up on this sub-issue, and picking it
     up again buys three more fix cycles against the same wall. The gate is
     symmetric and it is the whole mechanism: **removing the label re-queues
     the sub-issue**, there is no other state to reset.
   - Whatever a gated sub-issue blocks stays blocked, as with any open one.
   - With `merge: manual`, sub-issues you already delivered as ready-to-merge
     count as done for *your* loop but their dependents stay blocked — skip
     both.

2. Run the **delivery pipeline** on it.

3. On **merged** or **ready-to-merge** → next iteration. On
   **escalated/blocked** → record it, next iteration.

4. When no deliverable sub-issue remains (all closed or ready-to-merge, or
   the rest are blocked by escalated/unmerged ones) → **wrap-up**.

## Parallel mode (`execution: parallel`)

Parallel is the factory default (see Run configuration). The trade-off:
sequential with `merge: auto` delivers one sub-issue fully before the next
starts, so each PR branches from a `main` that already contains the previous
one — no merge conflicts by construction. Parallel trades that guarantee for
throughput: independent sub-issues are built concurrently, and conflicts
between their PRs become expected work, resolved by extra merge-fix jobs.
Note that with `merge: manual` sibling PRs all branch from the same `main`
regardless of execution mode — sequential buys no conflict guarantee there,
so parallel costs nothing extra.

Work in **waves**:

1. **Wave = every open sub-issue whose blockers are all closed** (same check
   as step 1 of the spec loop), minus the ones that step's gate excludes —
   `ready-for-human` above all, whether this run applied it or an earlier one
   did.
2. Run the delivery pipeline on each wave member concurrently, entry points
   first: the pipeline's **step 0** resolves where each member starts, and only
   the ones with no open change get triaged and built. Spawn **one**
   `dispatcher` for all of them at once (Triage step — one spawn per wave, not
   per member, capped at 5 issues a spawn), then their `code-author` BUILD
   jobs in parallel
   (each in its own worktree, `run_in_background: true`) — **minus any member
   the Triage step escalates as `oversized`**, which leaves the wave without
   a build (under `oversized: build`, or against a no-split directive, it is
   built like any other member). A resumed member goes
   straight into the review or fix stage alongside them. As each build
   reports its PR, spawn its `diff-reviewer`; as each reviewer reports,
   mark that PR ready (step 3 of the pipeline); fix cycles run per PR
   exactly as in the sequential pipeline. Cap concurrent build/review/fix
   workers at **3**; queue the rest of the wave.
3. **Merges stay strictly serial** — never merge two PRs concurrently. With
   `merge: auto`, merge each PR as it reaches CLEAN. **After every successful
   merge, refresh the wave's still-open PRs** per the code host's
   update-branch operation — GitHub default, per open sibling:

   ```bash
   gh pr update-branch <PR>
   ```

   It is a remote operation — no local git. Each sibling branched from a
   `main` that did not contain this merge; left stale, its CI goes red for
   synchronization, not for a bug, and a full fix cycle ends up doing what
   this one call does. A sibling whose update fails on a conflict is left
   alone — but note it: that failure is what puts the PR in the conflict
   queue (step 4), which is the only place a conflicting PR is worked on.
   Learning it here rather than at the merge gate is most of the point —
   spec #994 skipped one refresh and met the conflict forty minutes later,
   with the queue idle in between.

   **Every still-open member, including the ones mid-fix-cycle.** A PR that
   is being fixed is exactly the one that will still be open in an hour and
   exactly the one that goes stale; skipping it because "it is not ready yet"
   defers the conflict to the moment you most want a clean merge.

   **One PR per Bash call.** Issue the refreshes as separate commands, not as
   a `for` loop over the wave: a loop that writes to the code host reads as a
   bulk operation to the permission classifier and gets denied wholesale
   (observed in spec #994 — the same two calls, run singly, went through
   untouched).

   Every PR in the wave branched from the same `main`, so any PR merged
   after the first may conflict: on merge failure **after** the Merge step's
   checks gate passed, run the merge-fix job (`MERGE-FIX.md`) and retry once.
   With `merge: manual` there is nothing to serialize — each CLEAN PR just
   becomes ready-to-merge.

   **The first conflict of the wave closes the parallel phase.** From that
   moment the wave finishes through a **conflict queue** (below), not
   concurrently. Conflicts are not independent work: every conflicting PR
   resolves against the same `main`, and the first one merged invalidates
   every resolution computed beside it. Two merge-fix workers running at once
   are one worker and one rewrite waiting to happen.
4. **Conflict queue.** Once step 3 has seen one conflict, the wave's
   remaining unmerged PRs form a queue, ordered oldest-PR-first, and it
   drains **one at a time**:

   - **At most one merge-fix worker is alive in the whole run.** Never spawn
     a second while one is running, whatever the worker cap allows.
   - A PR's merge-fix is spawned **only when that PR is at the head of the
     queue** — i.e. after the previous PR is merged into `main`. Until its
     turn a queued PR gets **no merge-fix worker**; its conflict is not stale
     work, it is work not yet started. (Step 3's `gh pr update-branch` refresh
     still runs on it after every merge — that call is remote, cheap, and
     often *is* the resolution.)
   - When the head PR merges, drop it from the queue and try the next one's
     merge before assuming it still conflicts: the winner's merge, plus the
     refresh, resolves most of the rest for free. Only a merge that actually
     fails earns a merge-fix worker.
   - Everything that is not the merge path — builds, reviews, and review fix
     cycles, including a queued PR's own — carries on in parallel underneath.
     The queue serializes conflict resolution, not the wave.

   Say the switch out loud once, in one line, e.g. `#936 conflicts — wave
   finishing through the conflict queue: #936 → #937 → #938.`

5. When every wave member is delivered (merged, ready-to-merge, or
   escalated), recompute the unblocked set → next wave. None left →
   **wrap-up**.

Everything else — context economy, escalation, wrap-up, rules — is unchanged.

## Delivery pipeline (per sub-issue)

### 0. Entry point — resume, never rebuild

(Pipeline step 0, not the top-level Step 0 that publishes the context docs.
This is the per-sub-issue resume, reached both on a fresh run and as step B of
**Resuming the orchestrator**.)

A run can die at any point — a dead session, a compaction, a Ctrl-C — and the
sub-issues it half-delivered are still open, so re-running `/developer <spec>`
picks them right back up. What the tracker forgets is how far each one got:
build from scratch again and you get a second PR for the same sub-issue and a
second review paying for it. So before triaging, ask the code host whether a
change already exists for this sub-issue, per its "open change for this issue"
operation. GitHub default:

```bash
gh pr list --state open --search '"Closes #<subissue>" in:body' --json number,isDraft
```

- **No open PR** → nothing to resume: step 1 (Triage).
- **One open PR, no unresolved review threads** → keep its `<PR>`, skip
  Triage and Build, start at step 3 (Review). The reviewer settles its own
  scope from the PR's review history, so this covers both shapes: a build
  that was never reviewed (full scope) and one whose review was answered in
  full but never reached a verdict (incremental). If it comes back
  `blocked reason=no new commits since the last review`, the previous review
  was the last word and everything it raised is resolved: treat it as
  **CLEAN** and go to Merge — here only, because no fix pass ran this cycle
  to leave findings standing.
- **One open PR with unresolved review threads** → a review landed and its
  fixes did not: start at step 4 (Fix cycle), counting from cycle 1 with the
  fixer at `opus` (the build tier died with the session that chose it).
- **More than one open PR matches** → **escalate**: two open changes for one
  sub-issue is a human's call, never a pick.

GitHub default for the unresolved-thread count:

```bash
gh api graphql -f query='
{
  repository(owner:"OWNER", name:"REPO") {
    pullRequest(number: <PR>) {
      reviewThreads(first: 50) { nodes { isResolved } }
    }
  }
}' --jq '[.data.repository.pullRequest.reviewThreads.nodes[]
          | select(.isResolved == false)] | length'
```

The fix-cycle budget starts fresh on a resume: a PR that already burned cycles
in the dead run gets three more here. That is deliberate — the alternative is
reconstructing a counter nothing ever recorded — and the `ready-for-human` gate
is what stops a sub-issue looping forever across runs.

### 1. Triage

**Triage a whole wave in one spawn, not one spawn per sub-issue.** In parallel
mode this step runs once for the wave, covering every member the pipeline's
step 0 found without an open change; in sequential mode the wave is one
sub-issue and the same prompt carries a list of one. Batch at most **5**
issues per dispatcher; a larger wave takes a second batched spawn, never a
spawn per issue. The reason is the spawn envelope, not the dispatcher: a
triage round trip costs the orchestrator ~750 tokens of prompt, launch
metadata and notification whatever it carries, and it carries one word. Five
scores in one spawn pay that once instead of five times, and triage is the
step where it is free to do so — the scores are independent, the codebase
glance is shared, and nothing downstream needs them at different times.

Spawn `dispatcher` with `run_in_background: true`:

> Triage issues #`<N1>`, #`<N2>`, … Score each one's implementation
> complexity per your rubric, independently of the others. Your entire final
> message must be one
> `RESULT issue=… complexity=… model=… touches=… hints=… why=…` line per
> issue, in the order given — nothing before the first, nothing between them,
> nothing after the last. Do not explain your scoring anywhere except the
> `why=` field, which is capped at 15 words: your final message lands in my
> context whole and stays there for the rest of the run, so a paragraph in
> front of the lines is charged to every turn that follows and read by no one.

**Repeat that last sentence in the spawn prompt every time.** It is the rule
this worker breaks most often — twice in field runs, once after the agent
definition had already been hardened — and the spawn prompt is the last thing
it reads before working. Parse `why=` and drop it: it exists to give the
dispatcher's justification a 20-token home instead of a 330-token one, not
because anything downstream needs it.

Match each line back to its sub-issue by `issue=`. A member with **no line at
all** is treated exactly like a malformed one (below). Then, per sub-issue,
parse `complexity=` first:

- **`complexity=oversized`** → the sub-issue does not fit in a single fresh
  context window, and what happens next is the `oversized` knob's call (Run
  configuration):
  - **`oversized: escalate`** (default) → **Do not build it.** No model tier
    rescues a ticket that does not fit: the builder runs out of room, the
    review finds half a feature, and three fix cycles burn against the same
    wall. Go straight to **Escalation**, quoting the dispatcher's `hints=` —
    they carry the fault lines — and move to the next sub-issue. No BUILD, no
    review, no fix cycles are spent on it.
  - **`oversized: build`** → continue to Build at `opus`, passing the
    dispatcher's `hints=` through as usual; the fault lines become the
    builder's order of work. Do not escalate, do not label, and do not
    re-argue the size — the knob is the answer to that argument.

  Before escalating, check the issue body once for an explicit **no-split
  directive** ("deliberately indivisible", "no dividir", "ship as one unit").
  The dispatcher is told to veto its own `oversized` score when it finds one,
  so an `oversized` line on such a ticket means triage missed it: build it at
  `opus` as if the knob said `build`. This is the one place you overrule a
  triage verdict, and only ever in that direction — the author's directive
  outranks the rubric, never the reverse.
- Anything else → parse `model=<tier>` and continue to Build.

On any malformed or missing result, default to `opus` and build — a line you
cannot parse, or that never arrived, is not an `oversized` verdict. Parse
`touches=` and `hints=` too, defaulting each to `none` if the line predates
these fields or omits them — never block the pipeline on a missing hint.
Never re-spawn a dispatcher to recover one missing line: the default costs
less than the round trip it would take to improve on it.

### 2. Build

Spawn `code-author` with `model: <tier>`, `isolation: "worktree"` and
`run_in_background: true`, then log the spawn row (Workers):

> BUILD job. Spec issue #`<spec>`, sub-issue #`<subissue>`.
> Run the implement-issue skill on the sub-issue. The sub-issue's
> `## Spec extract` section carries the spec decisions that apply to it —
> read the full spec issue only if that section is missing.
> Triage found: `<dispatcher's hints, verbatim, or omit this line
> entirely when hints=none>`.
> Your entire final message must be the `RESULT pr=… url=…` line — no summary
> before it, nothing after it. Whatever deserves a record goes in the PR body,
> not in your reply. Report only a PR number you have confirmed exists.

- `RESULT blocked …` → **escalate** (see below) and move to the next
  sub-issue.
- `RESULT pr=<PR> url=<URL>` → **confirm the PR exists**, then keep `<PR>` and
  continue:

  ```bash
  gh pr view <PR> --json number,state,headRefName
  ```

  Never skip it. A build has reported `pr=<N>` for a number the host 404s on,
  with its whole implementation sitting uncommitted in its worktree because the
  publish step never ran at all. A worker's `RESULT` is a claim; this is the one
  cheap command that turns it into a fact, and the only thing standing between a
  fabricated line and a reviewer sent after a PR that was never opened. (Change
  metadata is read per `docs/agents/code-host.md` on another host.)

  On a 404, **do not re-spawn the build** — the work is almost certainly intact
  in the worker's worktree. Recover the worker per **Resuming the orchestrator**
  step A: its spawn row holds the `agentId`, `ListAgents` says whether it is
  still alive, and **SendMessage** tells it what you found and to run its
  publish step for real, reporting only a number it has verified. If the worker
  is gone, escalate naming its branch and worktree, so nobody rebuilds on top of
  work that still exists.

### 3. Review

Spawn `diff-reviewer` with `isolation: "worktree"` and
`run_in_background: true`:

> Review PR #`<PR>` by running the review-pr skill on it — its step 1 plus
> the repo's `docs/agents/code-host.md` give the exact checkout procedure
> for your worktree; follow them, not memory. Post the review (inline
> comments + summary) as a single COMMENT submission — never an approval
> event — and do not mark the PR ready or merge; those are orchestrator
> steps. Your entire final message must be the `RESULT verdict=…` line — the
> review itself is your output, your reply is not.

Then **mark the PR ready yourself**, whatever the verdict — per the
code-host doc's mark-ready operation. GitHub default:

```bash
gh pr ready <PR>
```

Skip it whenever the PR is already ready — a re-review, or a resumed run
whose step 0 found `isDraft: false`.

- `verdict=CLEAN` → go to **Merge**.
- `verdict=NEEDS_FIXES` → enter the fix cycle.
- `RESULT blocked reason=no new commits since the last review …` → the fix
  pass pushed nothing, so there is nothing to re-review. Treat it exactly as
  that cycle's `NEEDS_FIXES`: the previous findings still stand. Do not
  re-spawn the reviewer — go straight to the next fix cycle (or escalate if
  the budget is spent).
- `RESULT blocked` because the change branch is held by another worktree
  (the worker quotes git's "already used by worktree" error) → a previous
  worker's worktree wasn't cleaned: run **Cleanup** (step 6) and re-spawn
  the reviewer, **once per sub-issue** — if it blocks again, escalate.
- Any other `RESULT blocked` or malformed result → **escalate**, next
  sub-issue.

### 4. Fix cycle (max 3)

For cycle `c` = 1, 2, 3:

1. Fixer model: cycle 1 uses the build tier, each later cycle escalates one
   tier (sonnet → opus; opus stays opus). A sub-issue resumed straight
   into this step has no build tier — step 0 already set it to `opus`.
2. Spawn `code-author` with that model, `isolation: "worktree"` and
   `run_in_background: true`, then log the spawn row (Workers):

   > FIX job. PR #`<PR>`. Run the fix-pr skill to address all review
   > threads — its step 1 plus the repo's `docs/agents/code-host.md` give
   > the exact checkout procedure for your worktree; follow them, not
   > memory. Pushing the fixes and replying to the review th

…(truncated)
