This skill should be used when the user invokes "/milestone-driver:solve-issue <n>", or asks to "solve issue <n>", "fix issue <n>", or "drive issue <n>" through the milestone-driver gated procedure. Runs one GitHub issue end-to-end as an orchestrator - triage, dispatch the planner and approve its build packet, dispatch the implementer subagent (TDD, citations), unit + E2E gates, code review, PR to the integration branch, auto-merge on CI green, then close - never authoring application or test...
Installs into .claude/skills of the current project.
Are you the author of Solve Issue?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/kenmulford-solve-issue)
---
name: solve-issue
description: This skill should be used when the user invokes "/milestone-driver:solve-issue <n>", or asks to "solve issue <n>", "fix issue <n>", or "drive issue <n>" through the milestone-driver gated procedure. Runs one GitHub issue end-to-end as an orchestrator - triage, dispatch the planner and approve its build packet, dispatch the implementer subagent (TDD, citations), unit + E2E gates, code review, PR to the integration branch, auto-merge on CI green, then close - never authoring application or test code on the main thread.
---
# solve-issue - gated per-issue procedure
Run exactly one GitHub issue through a fixed, gated pipeline. The main thread acts only as an orchestrator: read, decide, dispatch, review, integrate. It never authors application or test code itself - the `force-subagent` hook enforces this mechanically. Orchestrate the `superpowers:*` skills for the inner loop. **Before anything else, check `#n`'s labels for `md-epic`: a parent issue takes `### Parent path` (see `skills/solve-issue/md-epic-fanout.md`) instead of the pipeline below.**
## Contents
Before starting · The procedure (0 Triage · 1 Read the issue · Build profile resolution · Resolve cited project-docs sections · Resolve cited `path (anchor)` citations · 2 Evaluate the codebase for root cause · 3 Dispatch the implementer · 4 Verification gates - no step 5, it merged into 4 · 6 Review → integrate → close) · Run-end cost record · Autonomy model · Permission pre-flight gate · Milestone granularity · Wave granularity · Async mode · Parent-issue detection · Output spec (Template 1 · Template 2) · Output style · Non-negotiables
## Before starting
Every `${CLAUDE_PLUGIN_ROOT}/scripts/*.{sh,ps1}` invocation in this skill selects its host the same way: **pwsh on Windows, bash elsewhere**.
1. Read the profile (see the plugin's `docs/profile-schema.md`).
| Profile-resolution decision point | Behavior |
|---|---|
| Resolution order (transitional) | Read `<repo>/.milestone-config/driver.json` first; if absent, fall back to the legacy root `<repo>/milestone-driver.json`. |
| Both files exist | `.milestone-config/driver.json` wins for the read. |
| Migration (`git mv`) | Deferred to step 3.5, so it rides the feature branch and does not trip the clean-tree precondition (step 2). Do **not** move here. |
| Neither file exists, or `integrationBranch` / `protectedBranch` / `sourceGlobs` missing | Invoke `milestone-driver:setup` to bootstrap it, then continue - do **not** fail. |
| `implementerAgent` | Defaults to `milestone-driver:implementer` when omitted. |
| Optional keys - `unitTestCmd`, `e2eTestCmd`, `e2eEnv`, `preflightCmd`, `domainSkills`, `nonNegotiables`, `projectDocs` | `projectDocs` defaults to `.project/`; every other step is skipped cleanly when absent. `domainSkills` is expanded first (`${CLAUDE_PLUGIN_ROOT}/scripts/expand-domain-skills.<sh|ps1> ~/.claude/plugins/cache <entry>…`): its **stdout** is what the ### 2 and ### 3 briefs carry, never the raw profile value, and each `unresolved: <entry>` on stderr is named there as `domainSkills unresolved: <entry>`. Empty stdout is an absent key: the brief omits `domainSkills` and still names those entries. |
1.1. **Self-heal the scratch-ignore (always, before any `.milestone-config/` scratch write).** Ensure a committed `.milestone-config/.gitignore` carrying exactly the block below; the directory also holds tracked config (`driver.json`, `feeder.json`), so never add a bare `*` or `/` rule, which swallows both. The suffix-scoped `*-notice` does not. Absent → create it (`mkdir -p .milestone-config`, then write the block). Present → do nothing. It rides the feature branch.
<!-- Keep this block in sync with the committed .milestone-config/.gitignore in this repo and with solve-milestone / scripts/triage-cache.{sh,ps1} / hooks/tests-green.{sh,ps1}, feeder setup / plan. -->
```gitignore
# milestone-driver / milestone-feeder per-clone scratch - git-invisible by default.
# Committed so per-run scratch stays out of `git status` with zero user setup.
# Patterns are relative to this .milestone-config/ directory. Tracked config
# (driver.json, feeder.json) is intentionally NOT listed, so it stays tracked.
*-notice
triage-cache.json
tests-stamp
.runtime/
worktrees/
```
1.1.1. **One-time notices.** Immediately after the profile read: read `${CLAUDE_PLUGIN_ROOT}/skills/notices.md` and, in file order, evaluate each section whose `Skills` field includes `solve-issue`, applying that section's `Trigger` → `Text` → `Marker` → `Legacy fallback` mechanics. Never evaluate a section scoped only to `solve-milestone`.
1.1.2. **Remediate handoff (feeder only).** Probe once: is `/milestone-feeder:remediate` resolvable in this session? Not resolvable → skip this sub-step: no question, no read, no error. Resolvable, and the caller supplied no answer → read `${CLAUDE_PLUGIN_ROOT}/skills/remediate-handoff.md` and follow its `## The feeder-installed gate` and `## The run-start question`; hold the answer for the whole run. A caller-supplied answer (a `solve-milestone` run's own run-start question) is reused, never re-asked - the held-value shape step 0's Branch A uses for the triage result.
2. **Require a clean working tree** (cold-start precondition) and a current local `integrationBranch` (`git fetch`, fast-forward). One exception, never stashed or discarded: step 3's probe finding an existing `issue/<n>-*` branch (committed or uncommitted prior work) skips this enforcement. Any other dirty state is a cold-start violation.
3. **Branch-state probe (resume an interrupted run).** `git fetch` first, then classify prior progress from git + gh before cutting anything; first match wins:
- **(a) A PR exists for `issue/<n>-*`**, merged or open (client-side filter: `gh pr list --state all --limit 200 --json number,headRefName,state,url --jq '.[] | select(.headRefName | startswith("issue/<n>-"))'`).
- **(b) No PR; branch `issue/<n>-*` exists (local or remote) with commits ahead of `integrationBranch`.**
- **(c) No PR; no commits ahead; the issue branch `issue/<n>-*` is checked out with uncommitted changes** (the normal implementer contract).
- **(d) Otherwise (no branch, no PR, clean tree):** cut a fresh feature branch from `integrationBranch` (e.g. `issue/<n>-<slug>`) - cold start: run ### 2. Evaluate the codebase for root cause, then ### 3. Dispatch the implementer with its `PACKET:`.
**(d) runs inline; (a), (b) and (c) are resumes** - read `${CLAUDE_PLUGIN_ROOT}/skills/solve-issue/resume-paths.md` and follow its `### Resume paths`.
This derives resume-state entirely from git + gh; there is no checkpoint file. **Never skip `/code-review`** on a resumed path; a fix it triggers runs step 6's normal re-review and push cycle.
**3.5. Profile migration (run once, on the feature branch).** `.milestone-config/driver.json` exists → use it. Else a legacy root `milestone-driver.json` exists → `mkdir -p .milestone-config`; `git mv` it to `.milestone-config/driver.json` (plain `mv` when untracked). Else: new project - setup creates the canonical file; hooks fail-open. Idempotent; both present → `.milestone-config/driver.json` wins, no move, no deletion. The move rides the issue's PR, not a separate commit.
4. Create one TodoWrite item per numbered step below. Work them in order - do not skip or reorder.
## The procedure
**The park action, referenced by every park below.** Post a comment on the issue in the park-comment shape (`skills/output-style.md`) opening with `🔴 Parked - ` and the reason (`gh issue comment <n>`); apply the named label, plus `in progress` when the feature branch has commits, via the apply-time helper (idempotent `gh label create --force` then `gh issue edit <n> --add-label`); leave the issue open; preserve the branch with any work done; clear the issue's dispatch-cap counters with `${CLAUDE_PLUGIN_ROOT}/scripts/reset-dispatch-cap.{sh,ps1} <repo-root> <n>`, so a re-run after the park starts a fresh chain; and return. Never an interactive prompt.
### 0. Triage
**Two branches - always run one:**
**Branch A - Explicit supply (reuse).** Fires iff the caller supplied this issue's triage result at invocation time as named fields with ACTUAL VALUES (e.g. `issueStates["N"] = { blockers: false, label: null, advisories: [...], risk: "light" }`, `edges["N"] = [...]`). Absent, label-only, or partial → Branch B. Use it directly - do not re-invoke `milestone-driver:triage <n>` - and proceed to the Blocker check.
**Branch B - Standalone / fallback (the safe default, never an error).** No explicit supply - including a result merely recalled from earlier context → invoke `milestone-driver:triage <n>` (single-issue mode) and use its return for the Blocker check.
**Blocker check (both branches).** A Blocker for this issue → park: triage has already posted the `🔴 Triage` comment; VERIFY it exists (`gh issue view <n> --comments`) and post it if missing (idempotent); apply the recommended label from `issueStates["<n>"].label` - `needs design` for a design gap, `needs decision` for a non-design decision; do not proceed to step 1. All-clear or Advisory-only → proceed to step 1.
**Held remediate answer is Auto** (sub-step 1.1.2) → read `${CLAUDE_PLUGIN_ROOT}/skills/remediate-handoff.md` and follow its `## The Auto loop` instead of parking now: invoke `/milestone-feeder:remediate <n>`, re-run triage on the corrected body, and clear the park label when the re-triage comes back clean, then proceed to step 1. Attempt cap: 1 per issue per run. Its `## Park for good` branches - `remediate` returns `NEEDS_HUMAN`, the re-triage is still dirty, the cap is spent, or the issue has no `🔴 Triage` comment - run the park action above unchanged: comment opening `🔴 Parked - `, label intact. No held answer, an answer of Leave-them-for-me, or the feeder absent → park immediately, as above.
### 1. Read the issue
Run `gh issue view <n>` with comments, redirected to the issue record `<repo-root>/.milestone-config/.runtime/triage/issue-<n>.md` by the `skills/triage/SKILL.md (redirect its full record)` command, which refreshes step 0's copy with comments posted since. Restate the acceptance criteria plainly before continuing.
**common.md.** A caller-supplied `common.md` path (a `solve-milestone` run) is reused, never re-assembled - the held-value shape sub-step 1.1.2 uses. None held → run the assembly call and its fail-open rule from `skills/solve-milestone/SKILL.md (Assemble common.md)` here, and hold its path.
### Build profile resolution (resolved after step 0, governs steps 2–6)
Read `issueStates["<n>"].risk` from the step-0 result (held Phase 0 result in a milestone run, fresh single-issue return standalone): `"light"` or `"heavy"`, defaulting to `"heavy"` when absent or inconclusive. That one read sets the brief token and the E2E gate's skip condition. Review effort and the cycle cap come from step 6.1.
| Profile | Planner and implementer brief | E2E gate (step 4, E2E row) |
|---|---|---|
| **Light** | Include a `risk:light` token in the brief | Skip when the issue touches no UI surface |
| **Heavy** (default) | Standard TDD brief (no `risk:light`) | Per step 4's E2E row (UI surface + e2eTestCmd) |
The safety floor is **unconditional for both profiles**: triage (step 0) and the `tests-green`, `force-subagent`, and `dispatch-cap` hooks always run. Light relaxes ceremony only - it never skips verification.
### Resolve cited project-docs sections (once, before dispatch)
Name the docs root and pass the `common.md` path **once, here in the orchestrator**; the planner quotes the `.project/` sections the change depends on into the packet (`skills/solve-issue/build-packet.md#Sections`), so the implementer reads exactly those sections and never a whole doc. Runs after the build profile resolves and before ### 2. Evaluate the codebase for root cause, adding inputs to the step-2 and step-3 briefs.
1. **Source the docs root.** Use `projectDocs` already resolved at step 1 (defaults to `.project/`). Do not re-resolve the profile here.
2. **Leave the anchors to the planner.** The planner reads the issue's cited `.project/<doc>#<section>` anchors and quotes each section `common.md` does not carry into the packet's `## Rules`; the main thread reads no section.
3. **Resolve the repo file index (once).** Invoke `${CLAUDE_PLUGIN_ROOT}/scripts/build-file-index.{sh,ps1}` once per run, never per-issue inside a milestone loop. Pipe the diff-scoped `{"files":[...]}` to stdin; it prints one `<path> → <purpose>` line per file. A ### 3 brief carrying no packet takes it as the resolved file index - the `build-file-index` output format, consumed, not re-derived.
4. **Pass the common.md path.** Put the held `common.md` path into the ### 2 and ### 3 briefs; never paste its contents. Sub-steps 3 and 4 apply identically under both the `light` and `heavy` build profiles - never skipped or altered for `risk:light`. The code-comment rule is `agents/implementer.md` rule 8; the brief does not restate it.
**Degradation (no error, ever):**
- **Absent `projectDocs`** → `.project/` (step 1).
- **Absent `.project/` directory**, or no cited anchors → no-op: no project grounding, no error, skipped cleanly like `unitTestCmd`/`preflightCmd`.
- **Missing/renamed cited anchor** → a planner park (`agents/planner.md#Park`).
- **Absent/failed file-index resolver** → no-op: no file index, no error - not fail-loud; nothing cites the index by name.
- **`assemble-common` fails twice** → no-op: the brief names no `common.md` path, no error; the implementer reads the four `skills/output-style.md` sections and `citationFormatPath` directly.
### Resolve cited `path (anchor)` citations (once, before dispatch)
Resolve the issue's `path (anchor)` citations (`skills/citation-format.md`) **once, here in the orchestrator**, and thread the resolved table into every subagent brief this run composes - no subagent re-derives it. Runs with the block above, before ### 2. Evaluate the codebase for root cause. Paths are repo-root-relative: an issue has no directory, so there is no multi-base fallback.
1. **Extract by model judgment over the `path (anchor)` shape - never a regex.** Apply `skills/citation-format.md`'s span and position tests to the issue body + acceptance criteria.
2. **Resolve each citation once.** Invoke `${CLAUDE_PLUGIN_ROOT}/scripts/resolve-citation.{sh,ps1}` once per citation: `resolve-citation.<sh|ps1> <file-path> <anchor-text>`. Exit 0 prints `PRIMARY <line> <text>` then zero or more `MATCH <line> <text>`, TAB-delimited, in file order; matching is literal, case-sensitive, and line-scoped.
3. **Feed the printed rows into the ### 2 and ### 3 briefs** as the resolved citations, as printed, unchanged; invent no new format.
**Degradation (no error, ever):**
- **No `path (anchor)` citation on the issue** → no-op: no `resolve-citation` invocation and no error.
- **A cited anchor not found**, or an unreadable file → `resolve-citation` fails loud (nonzero exit, naming the anchor and file on stderr, stdout empty). Surface it; never swallow it. Exit 2 is a wrong argument count or an empty anchor.
### 2. Evaluate the codebase for root cause
Dispatch `milestone-driver:planner` via the Agent tool, briefed with: the issue record path (step 1), the held step-0 result, the resolved build profile (a `risk:light` token under `light`), the profile keys `sourceGlobs`, the expanded `domainSkills`, `nonNegotiables` and `projectDocs`, the worktree path (the repo root standalone), the `common.md` path (omit when none is held), the resolved citations (omit any whose resolution was a no-op), `citationFormatPath` (the absolute path of `${CLAUDE_PLUGIN_ROOT}/skills/citation-format.md`, always passed), the packet path `<main-repo-root>/.milestone-config/.runtime/plans/issue-<n>.md` beside `common.md`, never inside a worktree, and the packet contract path `${CLAUDE_PLUGIN_ROOT}/skills/solve-issue/build-packet.md`. A run may add project facts to the brief, never work. The brief carries step 3's scratch-hygiene and command-shape rules; the planner's park cases are `agents/planner.md#Park`. Planner dispatches are capped at **2 per issue**, enforced by `hooks/dispatch-cap.sh`; a denied dispatch, or a return that is neither `PARK` nor `PLANNED` with a file at `PACKET:`, runs the park action with `blocked`, never a retry. A packet failing approval takes the one re-dispatch below.
- **`STATUS: PLANNED`** → hold the `PACKET:` path and approve it with check-packet, never a whole-packet read: run `${CLAUDE_PLUGIN_ROOT}/scripts/check-packet.{sh,ps1} <packet> <worktree>`, adding `light` under the light profile. Exit 0 → the packet is approved, the **locked** architecture for this issue. Nonzero → re-dispatch the planner once, same brief plus each `FAIL` line as one listed approval failure, per `agents/planner.md (On a re-dispatch, fold the listed approval failures)`, and approve its new packet likewise. A second failure → run the park action with `blocked`, naming the failures; the planner cap of 2 is spent, so no further dispatch.
- **`STATUS: PARK`** → run the park action with the returned `LABEL` and `REASON`.
Hold the last planner return's `DOMAIN_SKILLS_INVOKED` line for step 6.2.
### 3. Dispatch the implementer
Dispatch the profile's `implementerAgent` (default `milestone-driver:implementer`; a project-level override uses that agent's own name as-is) via the Agent tool with no `model` on the first build, so its frontmatter tier applies, orchestrating `superpowers:subagent-driven-development` + `superpowers:test-driven-development`. Brief it like a colleague walking in cold: the issue, the approved `PACKET:` path, the profile, the `common.md` path (omit when none is held), the resolved citations (omit any whose resolution was a no-op), the repo root (that issue's worktree path in a parallel-mode run), and `citationFormatPath`; under the `light` profile the brief must include a `risk:light` token. A run may add project facts to the brief, never work. Every implementer re-dispatch carries this brief, `PACKET:` included, plus the failure. Implementer dispatches are capped at **3 per issue** (step 4's shared loop), and a non-converging issue parks like any other. The brief must also carry this scratch-hygiene rule: write scratch only under a path named for that issue or that agent, never the shared scratchpad directory, and report what a probe printed rather than writing a probe file to read back later. The brief must also carry this command-shape rule: absolute paths only, and never change directory (`cd`, `pushd`, or a subshell); `git -C <dir>` for git, absolute file paths for `grep`, `cat`, `sed`, and `find`; a command needing a working directory (`unitTestCmd`, `preflightCmd`, `npm ci`) runs with the absolute worktree path as its cwd.
**The implementer is a leaf.** It returns an uncommitted diff plus its report and dispatches no subagent of its own (`docs/architecture.md` → `## Dispatch topology`). Every fan-out in this pipeline is the orchestrator's.
Verify the report honors the implementer contract: least-code / reuse-first, TDD red→green observed (or a `VERIFICATION (no test layer)` section when `unitTestCmd` is absent or under `risk:light`), verified citations where citable sources exist, a Decision Log, a `USER-FACING CHANGES` block (with `NEW_UI_ELEMENTS: yes|no`, `DESTRUCTIVE_OPS: yes|no`, and `POST_REVIEW_CHANGES: yes|no`), and **changes left uncommitted**. With `unitTestCmd` defined and `CLAUDE_HOOK_DISABLE_TESTS_GREEN` not `1`, the GREEN evidence is a scoped run of only the spec file(s) the implementer added or touched (`agents/implementer.md` rule 3) - this is expected, not a shortfall to flag; the full-suite run is step 4's Unit gate below. Then apply the declaration gates:
- **`NEW_UI_ELEMENTS: yes`** and the acceptance criteria are silent on the element's visual/UX detail → park with `needs design`, documenting the new elements and the direction needed.
- **`DESTRUCTIVE_OPS: yes`** and the confirmation UX is unspecified → park with `needs decision`, documenting the missing confirm flow.
**🔴 GATE - new dependency:** the implementer reports that the optimal solution requires a new library or toolkit → park with `needs decision`, naming the library and its license / OSS status. Do not ask the operator interactively.
**🔴 GATE - implementer STOPPED:** `STATUS: STOPPED` carrying no `PACKET_GAP:` line (architecture conflict, scope overrun, out-of-scope edit, or missing/ambiguous brief) → park describing the conflict, with `needs design` for a design/spec conflict, `needs decision` for an architecture call, or `blocked` for an otherwise-unresolvable gate. `PAUSED-FOR-APPROVAL` indicates a new-dependency case and routes to the gate above.
**A packet-gap stop re-plans:** a stop carrying a `PACKET_GAP:` line, `agents/implementer.md (A fact the packet lacks, or a quoted excerpt that does not match its file)`, re-dispatches the planner, step 2's brief plus that fact, per `agents/planner.md (On a re-dispatch, fold the listed approval failures or the missing fact)`. Planner dispatches already at 2 (step 2 re-dispatched) → park `blocked` naming the gap, no dispatch. `PLANNED` → approve per step 2's check-packet run; approved → re-dispatch the implementer with the new `PACKET:` path and no `model`, then apply step 3's checks to its return; a failure parks `blocked`, no further planner dispatch. `PARK` → run the park action with its `LABEL` and `REASON`, no implementer re-dispatch. A denied planner or implementer dispatch parks `blocked`, never retried (step 4's shared loop).
### 4. Verification gates
**There is no step 5** - the E2E gate folded into this step, and the gap stays rather than renumber because these headings are citation anchors (`.project/conventions.md (Keep ## headings stable)`); the next step is 6.
Unit, E2E, and preflight share one shape - **act → verify → retry → park**. `/code-review` is listed for visibility only (always-on, hence no trailing `?` in its row): it keeps its own step-6.1 procedure and is not iterated by the shared loop below.
| Gate | Applicability | `act` | Cap | Park / escape policy |
|---|---|---|---|---|
| **Unit?** | `unitTestCmd` defined | Run `${CLAUDE_PLUGIN_ROOT}/scripts/unit-gate.<sh\|ps1> <repo-root>` (pwsh on Windows, bash elsewhere), never `unitTestCmd` directly - it stages, runs `unitTestCmd`, and on green writes the same stamp `hooks/tests-green.<sh\|ps1>` reads, so the commit-time hook does not re-run a suite this gate already proved green on this exact tree. | Shared (below) | Park `blocked` (or `needs design` if the plan is wrong). |
| **E2E?** | Issue touches a UI surface and `e2eTestCmd` defined | Bug: run a targeted subset that proves the fix. Feature: implementer authors new E2E tests covering reasonable user stories, then runs them. Both against the profile's `e2eEnv`. | Shared (below) | Verified by other means (a DB assertion + an attached screenshot confirming the feature) → quarantine the flaky test, proceed, log the quarantine in the PR's Code Review section, apply `judgment call`. Not otherwise verified → park `blocked`. |
| **`/code-review`** | Unconditional: always runs, no applicability flag | See step 6.1, its own in-scope-fix / park-trigger classification | Step 6.1's classifier verdict, per `skills/review-depth.md § The ladder` | Step 6.1's own park-trigger classification (architecture deviation, shared-contract change, new dependency, out-of-scope edit, unmetable gate, material ambiguity), plus its second-cycle park rule |
| **Preflight?** | `preflightCmd` defined as a literal command | Run the literal `preflightCmd`. | Shared (below) | Park `blocked` (or `needs design` if the plan is wrong). |
**Preflight's `"github-ci"` sentinel mode.** When `preflightCmd` is the reserved sentinel `"github-ci"`, the Preflight row above does not apply: no local command runs, no cap, no park. The PR's own CI is the preflight, gated by step 8's existing wait-for-green merge under whichever granularity is in effect (the per-issue PR, the wave PR, or the milestone PR), each with its existing red-CI handling. Read `${CLAUDE_PLUGIN_ROOT}/skills/solve-issue/preflight-github-ci.md` for the record.
**🔴 GATE - shared loop (unit, E2E, preflight only):**
1. **Act.** Run the gate's `act` per the table row. Skip the gate cleanly, no error, when its applicability condition is not met (`unitTestCmd` absent; `e2eTestCmd` absent or no UI surface touched; `preflightCmd` absent).
2. **Verify.** Invoke `superpowers:verification-before-completion` and report real output, never assertion - for all three gates.
3. **Retry.** A failing gate re-dispatches the implementer (step 3's re-dispatch brief) with the failure and `model: "opus"` - or parks directly if the failure reveals the plan is wrong (see Autonomy). A preflight fix routes through step 6.1's After a fix, before committing branch set - `${CLAUDE_PLUGIN_ROOT}/scripts/classify-delta.{sh,ps1}` picks the branch, and `code-changed` re-runs the unit gate, never `unitTestCmd` directly (`skills/solve-issue/post-fix-commit.md § After a fix`), and `/code-review`. Cap: 3 implementer dispatches per issue in total, however split, shared across every gate and the step-6.1 review loop, enforced by `hooks/dispatch-cap.sh`. A denied dispatch is the park signal, never a retry.
4. **Park.** Still failing when the budget is spent → apply that gate's park/escape policy from the table above; the failed gate and its real output are the park comment's evidence slot. E2E's row is the one exception: verified-by-other-means quarantines and proceeds instead of parking.
**Call sites.** Unit and E2E run back-to-back immediately after the implementer dispatch (step 3). Preflight fires as the concluding action of step 6.1, after the `/code-review` loop converges and before version bump/commit.
### 6. Review → integrate → close
**Coherence review (before the final `/code-review`).** An optional read-only pass over the implementer's uncommitted diff. Run the review-depth classifier first (`skills/review-depth.md § Running the classifier`, `<repo-root>` as root): a `shallow` verdict skips this pass. Otherwise run it only when the `coherenceReviewAgent` - profile key, default-filled to `milestone-coherence-reviewer:coherence-reviewer` - is both present (dispatchable in this session) and configured; absent/unavailable OR explicitly unconfigured → silently skip: no error, no prompt, at most one log line. When it runs, read `${CLAUDE_PLUGIN_ROOT}/skills/solve-issue/coherence-review.md` and follow its `### The pass`.
1. **Resolve review depth, then review and resolve.** Read `${CLAUDE_PLUGIN_ROOT}/skills/review-depth.md` and follow it, passing `<repo-root>` as the classifier's root: it holds the verdict-to-effort mapping, the per-verdict cycle cap, the classifier's fail-open behavior, the re-classify-before-a-second-cycle rule, and the second-cycle park. Then run `/code-review` (`superpowers:requesting-code-review`) at that effort on the implementer's uncommitted changes, resolving findings autonomously per the Autonomy model, never pausing to ask the operator about an in-scope finding:
- **In-scope** (cosmetic, naming, style, local reversible refactor, missing/weak test): take the pre-fix snapshot first - `${CLAUDE_PLUGIN_ROOT}/scripts/classify-delta.<sh|ps1> --snapshot <repo-root>` (pwsh on Windows, bash elsewhere), holding the tree hash it prints; on nonzero exit hold nothing and still re-dispatch, the post-fix branch set names the move. Then re-dispatch the implementer (step 3's re-dispatch brief) to fix it with `model: "opus"` (the main thread cannot edit `sourceGlobs` - `force-subagent`); log it in the Decision Log. That hash is what the post-fix branch set below classifies against. Which findings the build profile fixes, and how a finding carrying no severity is scored, are `skills/review-depth.md § Which findings get fixed`.
- **Park trigger** (architecture deviation; a shared contract/interface/schema change; a new dependency; edits outside the issue's file scope; an unmetable gate; material ambiguity): park with `needs design`, `needs decision`, or `blocked`. Do not commit.
**The orchestrator runs this review itself, dispatching the reviewers directly as leaves** (`docs/architecture.md` → `## Dispatch topology`), and never dispatches an agent that itself runs `/code-review`. Reviewing several issues concurrently pins the shape to exactly one reviewer leaf per issue (`skills/solve-milestone/parallel-waves.md § Parallel mode - Phase 1: concurrent stage dispatch`, Stage B). The gate is unconditional: its `### 4. Verification gates` row carries no applicability flag, and `hooks/code-review-gate.sh` denies a `gh pr create` / `gh pr merge` whose PR body carries no `## Code Review` section. The brief must also carry the step-3 scratch-hygiene and command-shape rules and `citationFormatPath`.
**The brief must also put comment text out of review scope:** no finding at any severity about a comment's wording, density, or presence. One carve-out, in these terms and not redefined: a comment change altering a machine-read directive stays in scope, `scripts/classify-delta.sh (is_directive <src>)`'s enumeration deciding what counts as one.
**Omitting `/code-review` is not permitted.** Skipped under any constraint (time, token budget, tool error, self-review substitution) → treat the omission as a park trigger and park with `blocked`, giving the reason.
**After a fix, before committing.** No in-scope findings → commit directly. Otherwise at least one was fixed: read `${CLAUDE_PLUGIN_ROOT}/skills/solve-issue/post-fix-commit.md` and follow its `### After a fix`: the document-only and `classify-delta` branches, and the `tests-green` behavior they rely on. The cycle cap that bounds the loop is `skills/review-depth.md § The ladder`.
**Preflight gate (concluding action of 6.1).** Once the `/code-review` loop has converged, before version bump/commit, run the preflight gate - its applicability, `act`, cap, verify step, and park/escape policy are the Preflight row of `### 4. Verification gates` and its shared loop.
2. Assemble the Decision Log from the implementer's report for the PR body. Its slots are the Decision Log entry shape in `skills/output-style.md` - choice · rationale · citation · rejected alternatives, one entry per line. Carry `planner DOMAIN_SKILLS_INVOKED: <value>` and `implementer DOMAIN_SKILLS_INVOKED: <value>`, one line each, `none` recorded as `none`.
**Check every `path (anchor)` citation via `${CLAUDE_PLUGIN_ROOT}/scripts/resolve-citation.{sh,ps1}` before posting.** Resolves → post verbatim. Fails → correct that citation from code, or set it to `none` if no anchor exists. No other rewriting.
Post the block on the issue as the Decision Log comment opening `📋 Decision Log`, the implementer's CITATIONS block after it - the record the aggregate PR bodies' choice-only shape cites (`skills/output-style.md (Choice-only Decision Log)`). Probe `gh issue view <n> --json comments` for that opener: found, replace that comment's body (`gh api -X PATCH repos/{owner}/{repo}/issues/comments/<id> -F body=@<file>`), so a re-drive's entries land on the cited record; absent, create it (`gh issue comment <n>`). Never a second comment. **Ungated**: `none` never blocks, parks, or fails anything.
3. **Assemble the Code Review section** for the PR body. Record whether `/code-review` ran, the finding count and severity per run (the 1st, and the 2nd if a re-review ran), and each finding's resolution (re-dispatched and resolved / accepted with rationale / triggered park). Its slots are the `## Code Review` section shape in `skills/output-style.md`; the evidence slot is the ref each finding named (per `skills/citation-format.md`; at zero findings, the effort level used). Template:
```text
## Code Review
- /code-review run: yes | n/a - <reason> (`n/a` only for a PR carrying no `sourceGlobs` change; omission is a park trigger: a submitted PR always carries a real review; a parked run opens no PR)
- Findings, one line per run: <count> in-scope finding(s) at <effort> effort
- <finding> - <the ref it named, per skills/citation-format.md> → re-dispatched and resolved | accepted (rationale: <…>) | triggered park
- … (one line per finding, or "none" when count is 0)
- No park-triggering findings. | Park-triggering findings: <list>
```
The version-bump annotation this section carries is the one step 6.4's resolved mode names.
4. **Version bump.** Read `versioning` from the profile and select the mode: Version-free mode (`versioning: false`), Fail-safe degradation (versioned mode - `versioning` `true` or absent - but `.claude-plugin/plugin.json` does not exist), or Versioned mode (`versioning` `true` or absent, and `.claude-plugin/plugin.json` exists). Read `${CLAUDE_PLUGIN_ROOT}/skills/solve-issue/version-bump.md` and follow its `### Modes` for that mode's mechanics, its Code Review annotation, and - under Versioned mode - the milestone-run vs standalone-run split.
5. Commit on the feature branch - the `tests-green` hook (`PreToolUse` on `git commit`) re-checks the suite. Review-before-commit is enforced by audit trail (the mandatory **Code Review** section), not by a commit-time hook; `hooks/code-review-gate.sh` gates `gh pr create` / `gh pr merge`, not `git commit`.
6. Push the feature branch and open a PR with `--base <integrationBranch>` (never `protectedBranch` - enforced by the `no-push` / `no-pr-to-protected` hooks and GitHub branch protection). Put the Decision Log and the **Code Review** section in the PR body. Add a `judgment call` label if any borderline autonomous call was made. Record its number (`gh pr view --json number --jq .number`) as step 8's `<pr-number>`.
8. **Auto-merge on green.** Once CI is green, squash-merge for every issue, no UI/non-UI branch in front of it. This replaces the human-choice step of `superpowers:finishing-a-development-branch`.
**Every squash merge in this plugin passes an explicit subject and body**, here and at every squash site citing this section.
```bash
body="Decision Log and Code Review: PR #<pr-number>" \
&& title="$(gh pr view <pr-number> --json title --jq .title)" && [ -n "$title" ] \
&& gh pr merge <pr-number> --squash --delete-branch --subject "$title (#<pr-number>)" --body "$body"
```
**One Bash call, `&&`-joined** - a variable does not survive a call boundary, and an empty title aborts before the merge. The title is read at merge time, never held, and every value these strings interpolate is a variable, so a `$`, a backtick, or a `"` in one is safe. `$body` is `"Decision Log and Code Review: PR #<pr-number>"` where the PR body carries a Decision Log, `"Code Review: PR #<pr-number>"` where it carries none.
**`<pr-number>` is written into the command as literal digits, never a variable.** The merge gate's `pr_arg` regex reads the first token after `merge` and runs `gh pr view` on it (`hooks/code-review-gate.sh (gh pr view "$pr_arg")`); on a non-literal token that view fails and the gate falls open.
**Visual evidence (optional, never-gating), before that merge.** Runs only when the PR's diff matches `uiSurfaceGlobs`: a logic-only issue has no surface to shoot, and would boot a server to publish an empty comment. Then, with a `visualCapture` block carrying all three required keys - `serverCmd`, `readyUrl`, `signInPath` - on a sequential run (a parallel run defers the capture to the serial merge tail: `skills/solve-milestone/parallel-waves.md (Deferred to the serial tail)`), read `${CLAUDE_PLUGIN_ROOT}/skills/solve-issue/visual-capture.md` and follow its `### Capture flow`. No UI surface, `visualCapture` absent or incomplete, or any failure in it → skip with one log line: no capture, no PR comment, no hold. The merge proceeds regardless. `uiSurfaceGlobs` still decides the E2E gate row (step 4), this capture, the step-0 triage's `design-reviewer` dispatch, and `NEW_UI_ELEMENTS` handling (step 3) - keep the key; just nothing about the merge.
9. Confirm the issue is closed (a linked PR auto-closes it; otherwise `gh issue close <n>`) - unconditionally.
## Run-end cost record (additive, never-gating)
**Survives context compaction**: an in-context sum is gone the moment compaction runs, so nothing is aggregated in context. Every dispatch's usage is appended to disk as it returns; the run-end record is read back from that disk file alone. Never blocks, parks, or changes any outcome (`.project/design-philosophy.md#Error & failure philosophy`).
1. **Append, per dispatch, as it returns.** As soon as an Agent-dispatch report lands - planner (tier `opus`), implementer (tier `sonnet`, `opus` on a fix re-dispatch), `/code-review`, coherence-reviewer, direct triage; a background completion notification carries the same `<usage>` block - pipe `{"agent":"<dispatch label>","tier":"<opus|sonnet>","totalTokens":<subagent_tokens>,"durationMs":<duration_ms>}` to `${CLAUDE_PLUGIN_ROOT}/scripts/write-cost-record.{sh,ps1} --append <runId>` (`<runId>` the issue branch / run id, held constant for the whole run). It appends one line to `.milestone-config/.runtime/usage/<runId>.jsonl`; a dispatch never skips this even mid-run. An implementer or planner return carrying an `output_file:` path first merges the object `${CLAUDE_PLUGIN_ROOT}/scripts/measure-dispatch.{sh,ps1} <output_file>` prints into the piped one (`jq -c -s '.[0] + .[1]'` (Bash), or both objects through `ConvertFrom-Json -AsHashtable`, merged with `+`, then `ConvertTo-Json -Compress` (PowerShell)); no `output_file:`, or a nonzero exit, pipes the four-field object unchanged and never gates.
2. **Finalize, last action before returning to the caller** at every terminal exit - every park (steps 0, 2, 3, 4, 6.1) and the step-6.9 close. Run `${CLAUDE_PLUGIN_ROOT}/scripts/write-cost-record.{sh,ps1} --finalize <runId>`: it reads that `.jsonl` alone, sums `totalTokens` per tier into `inputTokens` (the same auditable-lower-bound map as before - `outputTokens`/`cacheReadTokens`/`cacheWriteTokens` at the 0 sentinel, `provenanceNote: "unsplit-total-as-input"`), sums `durationMs` into `wallClockSeconds`, and writes the record to `.milestone-config/.runtime/cost-records/` carrying one more field, `agents`: the raw per-dispatch entries, for a per-agent `durationMs` breakdown the summed total alone cannot give back.
3. **Skip cleanly.** No usage file for this `runId` (e.g. a triage-blocker park before any dispatch) → `--finalize` fails open on its own, one log line, no zero-value record. Writer script absent → silent no-op, one log line.
## Autonomy model (Balanced)
**Proceed autonomously (log on the PR):** implementation choices within the approved architecture; reuse of existing helpers, styles, and conventions; test design; local reversible refactors; resolving in-scope `/code-review` findings (step 6.1).
**PARK & continue (the autonomous runtime parks, never interactively waits):** deviation from the approved architecture; any change to a shared contract, interface, base class, or DB schema used beyond this issue; a new dependency; edits outside the issue's expected file scope; a gate that cannot be met without a design change; material ambiguity in the issue's intent; `/code-review` omission or substitution for any reason (time, token budget, tool error, self-review); budget pressure is not an exception.
The park is `## The procedure`'s park action, comment opening `🔴 Parked - <reason>`; its `in progress` label is the open-WIP signal the milestone loop reads, and that loop continues with independent, clean issues. **Only a systemic failure** (auth/`gh` failure, broken `integrationBranch`, missing tooling) halts the whole run. A standalone interactive `solve-issue` still parks durably; it may additionally narrate to the watching operator.
**Additional park triggers** (each a park, not silent resolution and not an interactive prompt):
- The recorded/locked design is internally contradictory → park with `needs design`.
- A self-noted risk about the **approved** design (e.g. "this list could get long at realistic data volumes") → park with `needs design`.
**Architecture is locked** at packet-approval time (step 2). The procedure executes approved architecture. If implementation proves the plan wrong → park, not pivot.
A change is **architecture** (→ park) if it touches any of: a component or data structure named in the approved packet; a shared contract, interface, base class, DB schema, or public API used by code outside this issue; data ownership or a cross-component boundary; a new external dependency; or any file outside this issue's stated scope. A change is an implementation detail (→ proceed, log in the Decision Log, step 6.2) if it is local to this issue's own files, changes no shared contract, and is reversible - a binding style, a private helper extracted in the same file, a local refactor, or test design. When the distinction is genuinely ambiguous, treat it as architecture and park.
**Audit trail (always):** a Decision Log on every PR, a Code Review section recording every `/code-review` run and its findings/resolutions, and a `judgment call` label on borderline calls.
## Permission pre-flight gate
**Runs once per run, before the first background dispatch - only when a leaf is about to be dispatched as `Agent(run_in_background: true)`. A fully synchronous run SKIPS it entirely.**
Background subagents auto-deny any tool call that would otherwise prompt, and a background leaf hitting an un-allowlisted tool fails outright with no interactive recovery. Before the first background dispatch: union `permissions.allow` across `~/.claude/settings.json`, `.claude/settings.json`, and `.claude/settings.local.json` - absent or unreadable layers are skipped in the union, not treated as gaps. Synchronous fallback fires only when the union misses the required tool surface, or no layer is readable. With that union in hand, read `${CLAUDE_PLUGIN_ROOT}/skills/solve-issue/permission-preflight.md` and follow its `### Tool surface and response`.
## Milestone granularity (`integrationGranularity: "milestone"`)
**Resolved from the profile at step 1, not from an invocation token.** `integrationGranularity` resolves to `"milestone"` (`docs/profile-schema.md (How should built issues integrate?)`) → read `${CLAUDE_PLUGIN_ROOT}/skills/solve-issue/milestone-clauses.md` and apply its `### Clauses`, plus `${CLAUDE_PLUGIN_ROOT}/skills/solve-milestone/milestone-granularity.md` for the branch model, integration-commit trailer, and resume query. Absent or invalid, with no milestone run holding a resolved value, resolves to `"issue"` here: neither read happens and the pipeline runs byte-unchanged. An explicit `"issue"` resolves the same way.
## Wave granularity (`integrationGranularity: "wave"`)
Resolved at the same step-1 profile read, not from an invocation token. `integrationGranularity` resolves to `"wave"` (`docs/profile-schema.md (How should built issues integrate?)`) → read `${CLAUDE_PLUGIN_ROOT}/skills/solve-issue/wave-clauses.md` and apply its `### Clauses`. Those clauses apply to **every** issue in the Wave, UI included.
## Async mode (`--async`): retired
**`--async` is an interpreted token, not a parsed CLI flag** - recognized by string presence in the invocation text - and now inert: the pipeline runs on the caller's own main line, and a habit-typed or stale token is a no-op, never an error (the same treatment a habit-typed `--parallel` gets in `solve-milestone`). No run reads `${CLAUDE_PLUGIN_ROOT}/skills/solve-issue/async-mode.md` - it is the human-read retirement record, excluded from this skill's size closure on that ground (`scripts/check-size-budgets.sh (retired, inert - read on no run)`).
## Parent-issue detection (`md-epic`)
**Runs before anything else** - before `## Before starting` step 1 and before `### 0. Triage`. Read `#n`'s labels (`gh issue view <n> --json labels`, exact match against `.labels[].name` for the literal `md-epic`). Absent → this section does not apply; the pipeline runs byte-unchanged from `## Before starting` step 1. Present → read `${CLAUDE_PLUGIN_ROOT}/skills/solve-issue/md-epic-fanout.md` and follow its `### Parent path`, which replaces the rest of this pipeline for this invocation.
## Output spec
<!-- KEEP THIS ICON LEGEND BYTE-IDENTICAL across solve-issue and solve-milestone. -->
**Icon legend:** ✅ merged · 🔨 building · ⏭️ queued · ⏸️ parked · 👁️ UI surface / visual evidence · ⚖️ judgment call · 🔴 Your move
### Template 1 - Run start / plan board
Show after the ### 0. Triage step completes.
```text
🚀 Issue #201 - [title] · [risk: light | heavy] · [UI | non-UI]
| Issue | Title | Risk | UI | Status |
|-------|---------|--------|----|-------------|
| #201 | [title] | [risk] | - | 🔨 building |
▶ Building - the floor is yours.
```
### Template 2 - Issue completion (terminal output)
<!-- Structural mirror of solve-milestone Template 2; keep column schema (Issue/Result/Gates/PR/Note) in sync. -->
One row per issue; emit only the row matching the actual outcome and suppress the others.
```text
🏁 Issue #[n] · [T] min
| Issue | Result | Gates | PR | Note |
|-------|------------|-------|----|-------------------------|
| #201 | ✅ merged | 🔍✓(0 findings) | #301 | - |
| #202 | ⏸️ parked | - | [#pr | -] | [park label] |
| #204 | ✅ committed | - | - | committed on its branch |
```
PR cell: show the PR number if the issue has one, else -. Gates legend: 🧪 = unit suite · 🔍 = code review · 🌐 = E2E
Note cell: an issue the run sent through the Auto loop records its outcome there - `remediated, cleared` (re-triage clean, label cleared), `remediated, still parked` (cap spent or re-triage still dirty), or `NEEDS_HUMAN, parked` (`skills/remediate-handoff.md (Park for good)`); Result still shows the state the pipeline reached. No Auto loop → the cell is unchanged. Slow first build: an issue whose first implementer dispatch printed `steps` over 40 or `minutesToFirstEdit` over 2 adds `<steps> steps, first edit <m> min` (no `minutesToFirstEdit`: `steps` alone decides, shown as `<steps> steps, no edit`), joined to any Auto loop outcome by `; `. Slow planner: an issue whose first planner dispatch printed `steps` over 25 adds `planner <steps> steps, <minutes> min` to the Note cell, joined to any other Note text by `; `.
## Output style
Read `${CLAUDE_PLUGIN_ROOT}/skills/output-style.md` - this plugin's output contract. `## Terminal output` governs what this skill prints (including the `## Output spec` template rule); `## GitHub-facing prose`, `## When prose is the correct form`, and `## Evidence slots` govern every issue comment, PR comment, Decision Log, and PR body. The two surfaces are distinct - terminal rules never reach GitHub. **Plain English.** Write every response, document, and GitHub issue, milestone, comment, and PR body in plain, concise English. Never include hypothesis, conjecture, or defensive text.
Read `${CLAUDE_PLUGIN_ROOT}/skills/citation-format.md` - the one format every citation in those slots takes.
`skills/solve-milestone/SKILL.md § Main-thread context` binds this skill too - it runs in-thread.
## Non-negotiables
- Gitflow. PRs target `integrationBranch` only - never `protectedBranch`.
- Honor the profile's `nonNegotiables` (framework versions, platform targets).
- The main thread never authors application or test code - always dispatch the implementer.