---
name: resolve-issue
description: >
  Orchestrate the whole issue-to-PR pipeline for one ticket: fact-check the issue, draft a plan,
  harden it, implement the fix, write tests, review the fix, and open the PR — chaining the
  adversarial-review, test-authoring, and pr-lifecycle component skills behind a plan-approval gate,
  pausing again wherever a decision is yours.
  Use whenever someone wants an issue / bug / Jira ticket taken from diagnosis all the way to
  a pull request — even if they don't name the skill.
  Takes the ticket as an argument or from natural language; resumable across sessions from .claude/resolve/<ticket>/.
  Do NOT trigger when the user wants only ONE stage — route those to the component skill directly:
  "just fact-check this issue" is review-issue-fact; "review this plan" is review-plan-risk;
  "review my fix / diff before the PR" is review-code-risk; "add tests" is test-authoring;
  "open / raise a PR" is open-pr; "address / resolve the PR comments" is resolve-pr-comments.
  Also do NOT trigger for merging / completing a PR, or plain git operations.
  Trigger phrases: "resolve this issue", "run the issue-to-PR pipeline", "take this Jira from diagnosis to PR",
  "diagnose fix and open a PR", "/resolve-issue".
---

# Resolve issue

Drive one ticket through the full issue-to-PR pipeline, from fact-checking the issue to opening the pull request, by invoking the already-built component skills in order and holding the human gates the flow needs. This skill is a **sequencer, not a re-implementation**: it never re-derives what a component skill does, and it never threads a component's verdict, risk table, or test selection into the next as a program argument — each component reads its own input (the issue text, the plan file, the git diff) and produces its own output behind its own gates. The orchestrator's whole job is ordering, the human gates it owns, and the durable handoff artifacts that make the pipeline resumable.

Run this in the **main conversation loop**, never as a subagent. The component skills spawn their own fresh verifier subagents, and the gates here are interactive — a subagent context loses the human gates and `ExitPlanMode`, and eats the depth budget those verifiers need. If this skill finds itself running inside a subagent, it stops (see the preamble).

The pipeline splits at plan approval — its Phase A→B pivot — into **Phase A (diagnose and plan)** and **Phase B (build and open the PR)**, and ends at PR-created. Phase B adds conditional review checkpoints (b-security-review and the b-code-risk→b-open-pr checkpoint) that pause only when a review surfaces something the human must disposition, and otherwise pass through. **Plan approval is not the last stop, though**: `b-open-pr` pauses unconditionally for the open-PR confirmation, so every run waits on the human at least twice — and a-elicit-decisions adds a third whenever the ticket leaves a load-bearing decision open. Approving the plan does not hand the rest off — the full touchpoint list is under **Degradation and safety** below, and `SKILL_DIR/README.md` groups them by why they exist. Addressing the eventual review comments is **Phase C** (`resolve-pr-comments`), which the human invokes later — it is deliberately not part of this automated run and there is no polling loop.

## How resume works (read this before the steps)

The orchestrator is **stateless between steps**. Every invocation reconstructs "where am I" from `.claude/resolve/<ticket>/state.md` and the observable git facts, then continues from the first unfinished step. Staying in one session and resuming days later in a fresh session at a different effort are the **same code path** — only the size of the gap differs. There is no auto model/effort switch; "each phase at its own effort" simply means the human re-invokes at the effort they want and the pipeline picks up where it left off.

**Pick the model and effort before you invoke — Phase A (diagnose and plan) is the reasoning-critical part they drive.** Bias toward a stronger model and higher effort the more complex, ambiguous, or high-risk the issue is. The pipeline and every subagent it spawns follow the session's model and effort; nothing here pins, caps, or silently downgrades them — silently swapping the model you chose for a cheaper one would trade your output quality for cost without consent. If you are already running and wish you had gone higher, revise at the approval gate, or stop and re-invoke at a higher setting — the run is resumable and nothing is lost.

**Where the cost concentrates, so the choice is not made blind.** `b-write-tests` dominates a run: it spawns a writer and then an independent verifier, and the review passes spawn their own subagents on top of that. Even the smallest unit-test target pays for that pair. Nothing here reports cost back to you — the orchestrator cannot see its own token usage — so `resolve-issue-dashboard` is where the running totals live, and `timings.md` holds only per-step wall-clock. (A specific token figure used to sit here; it was measured once, by a method later found to double-count, and is withdrawn. Shipped guidance should carry the shape of the cost, not a number nobody can re-derive.)

Resume is **same-working-tree**. The handoff artifacts (`state.md`, `plan.md`, `fact-check.md`, `decisions.md`) are gitignored working files that live on the local disk, so a new session on the same machine sees them. A fresh clone on another machine has none of these in-flight artifacts (by design — they deliberately stay out of git), so it does not resume an in-flight pipeline; it starts fresh. That is an accepted trade-off, not a bug: keeping the artifacts out of git is what stops them polluting the PR.

## The handoff artifacts — `.claude/resolve/<ticket>/`

Five persistent markdown files — four handoff artifacts plus the append-only `timings.md` observability log — and nothing else persistent. One transient sixth exists only inside `a-harden-plan`: `plan.baseline.md`, the write-once snapshot that step hands to `review-plan-risk` and deletes once the cursor advances (see PLANNING.md). Do not read "five" as licence to delete it mid-step. Do not invent a machine-readable side-channel for verdicts or risk tables — those live inside the component skills' own output and gates. `timings.md` is not such a channel: no pipeline step reads it (only the read-only dashboard does), so it carries per-step wall-clock about the run, never a component's verdict threaded into the next step. (Separately, the generic-learning *dead-drop* that the cross-cutting capture step appends to is a **user-global** file outside this per-ticket directory — not a resume artifact and not a verdict side-channel; see "Capturing generic learnings" below.)

- **`state.md`** — the resume cursor, written only by this skill. Fields: `next-step`, `ticket`, `base-branch`, `work-branch`, `plan-approved`, `pr-url`, `attention`, `started`, `ended`. `attention` is an optional one-line note, set only while the run is waiting on the human — either to disposition something (see the b-security-review and b-code-risk→b-open-pr checkpoints) or to confirm the PR draft (the b-open-pr open-PR confirmation) — and cleared the moment that wait ends and the run resumes (not deferred to the next step; a bare clarifying question that leaves the run still waiting does not clear it — the same question-versus-change distinction the a-gate-approve loop makes) — the dashboard renders the current step as blocked while it is set, so clearing `attention` only at the next step transition would leave the step showing blocked while it is in fact already running again. **This is the single definition of `attention`'s set/clear timing; the steps below set it with a step-specific reason and point here rather than restating the rule.** `started` and `ended` are UTC ISO 8601 timestamps (with a trailing `Z`) for the run's start and finish, so the dashboard can show an active duration anchored to the run itself rather than to whichever Claude session is later tailed. It is a convenience cursor; git history plus the artifacts are the deeper truth. (No `effort` field — there is no runtime switch to drive, and re-invoking does not set effort; effort is the human's choice at invocation.)
- **Timestamp rules for `started` / `ended` (read once, they bind every `state.md` write).** Capture the time only with a system command, in **exactly** UTC ISO 8601 with `Z` — never guess the clock, and never change the format (the dashboard parses it): `date -u +%Y-%m-%dT%H:%M:%SZ` (POSIX / Git Bash), or `python -c "import datetime;print(datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ'))"` (most portable). **`started` is write-once and create-only**: set it only when this invocation is a genuinely *fresh-from-top* run — the same condition the State reconcile calls fresh (no `state.md` and no `plan.md` and no fix commit, so the cursor is the first step `a-fact-check`). **Never** set `started` when the cursor was reconstructed from git at a later step (`b-write-tests` from an existing fix commit, `done` from an open PR) with no prior `state.md`: stamping `now` there would record a false late start — leave `started` absent and the dashboard falls back to the first transcript event. `ended` is set once at the `done` transition. **Preserve both on every rewrite**: `state.md` is rewritten at several points (P4 per step, the a-gate-approve approval, the b-code-risk→b-open-pr checkpoint, the b-open-pr open-PR confirmation's `attention` set/clear, `done`, and reconcile) — at each, read back the existing `started`/`ended` and carry them through unchanged; dropping `started` on any write defeats the run-scoped duration entirely.
- **`fact-check.md`** — written by `review-issue-fact` when a resolve context exists (which is why the preamble creates the directory before Step a-fact-check). The issue's per-claim verdict table and the overall HALT / PROCEED / RESOLVE recommendation.
- **`decisions.md`** — written by `a-elicit-decisions` when it elicits the human's open design decisions before planning; each entry marked `resolved` or `open`. It is the pre-plan **scratch**: `a-draft-plan` folds the `resolved` entries into `plan.md` and stays `plan.md`'s sole author, so `plan.md` is the authoritative copy the a-gate-approve approves and `review-code-risk` reads — on any divergence the folded `plan.md` wins. When `a-elicit-decisions` no-ops or is skipped (non-interactive) it still writes a one-line sentinel (`none — …` or `skipped (non-interactive)`) — so a thin ticket cannot pass by silently self-declaring; it is absent only before `a-elicit-decisions` has run (e.g. a fresh clone). A real (non-sentinel) `decisions.md` is overwritten fresh only on a genuinely fresh-from-top run.
- **`plan.md`** — drafted by this skill in a-draft-plan, hardened in place by `review-plan-risk` in a-harden-plan, appended to in b-security-review with one-line records of any approved scope expansion **and, when the security review did not positively run, a line beginning `SECURITY REVIEW DID NOT RUN`** (that line is a run record, not intent — `review-code-risk` should read it as context, never as something the fix was meant to satisfy), and read from the working-tree disk by `review-code-risk` in b-code-risk as its intent oracle. It is **not committed** by default (see the git-handling note at the end).
- **`timings.md`** — an append-only per-step timing log the orchestrator writes, one markdown line `- <UTC-ISO-Z> <step-id>` per step *entry* (the boundary write in P4). It is pure run observability: only the read-only `resolve-issue-dashboard` reads it, no pipeline step does, so a missing or malformed `timings.md` costs nothing but the dashboard's per-step durations. A re-entered step — a gate revise re-running a-harden-plan / a-elicit-decisions, or a future rework loop — appends another line rather than overwriting, so the log measures re-runs instead of hiding them: the reason it is a separate append-only file and not `state.md` fields, whose flat one-value-per-field shape and wholesale rewrite could neither hold the repeats nor survive the carry-forward. Not committed (the `.claude/resolve/.gitignore` `*` covers it).

`next-step` is a closed set, matching the steps below: `a-fact-check`, `a-elicit-decisions`, `a-draft-plan`, `a-harden-plan`, `a-gate-approve`, `b-implement`, `b-write-tests`, `b-security-review`, `b-code-risk`, `b-open-pr`, `done`. These ids — with their labels, phase, component, and order — are registered canonically in this plugin's `resources/resolve-issue-steps.json`, which the `resolve-issue-dashboard` dashboard renders from; when you add, rename, or reorder a step, update that file too (and write the new value into `state.md`) so the dashboard stays in sync. The behaviour of each step lives in this skill's prose — a disclosed step's in its cluster file (`PLANNING.md` / `REVIEW-PASSES.md`), a spine-resident step's (`a-gate-approve` and the leaf steps) inline below — never in that registry file; a rename must sweep the cluster files too, not only this one.

**Superseded runs are archived under the same ticket, not overwritten.** The files above are the *live* run, at the ticket top-level. Because the directory is keyed on the ticket alone, a later branch reusing the same ticket number would otherwise inherit a previous run's files; the P4 staleness gate prevents that by moving a superseded run into a timestamp subdir `.claude/resolve/<ticket>/<stamp>/` before the fresh run starts at the top-level. So a ticket directory may hold the live run plus past runs in dated subdirs. The `.gitignore` `*` covers the subdirs too, so none of it is committed, and the dashboard lists each archived run alongside the live one.

## Preamble — run on every invocation

Resolve this skill's own directory once at load, so the pointers to its sibling reference files resolve to absolute paths:

!`echo "${CLAUDE_SKILL_DIR}"`

Call that `SKILL_DIR`. If the line above did not expand to a real absolute path (it still shows a literal `${CLAUDE_SKILL_DIR}`), run `echo "$CLAUDE_SKILL_DIR"` with the Bash tool. Sibling files live at `SKILL_DIR/<name>`, in two kinds:

- **Cluster files — `PLANNING.md` (the Phase A authoring steps) and `REVIEW-PASSES.md` (the two Phase B review steps).** These hold the per-step how-to that a normal run **does** need. The spine below carries only a one-line contract per step; **load the cluster file before executing any step it covers — including a resume or re-entry that lands the cursor on a middle step**, as the Phase A and Phase B sections direct. Executing a disclosed step from its one-line contract alone would drop the step's safety rails — do not.
- **Conditional refs — `ARCHIVING.md`, `GIT-HANDLING.md`, `DASHBOARD.md`, `LEARNINGS.md`.** These hold branch-conditional or rationale detail; read one only when a step's branch points to it, so a normal run never needs them.

- **P0 — guard the context.** If running inside a subagent, stop and tell the user to run `resolve-issue` from the main conversation (reason above). If running non-interactively such that the human gates below cannot be reached, see Degradation.
- **P1 — bring up the dashboard first (once per invocation), so the run is observable from step zero.** **This is the first action after the P0 guard, and it runs before any pipeline work — before the ticket is even resolved — so the human is watching from the start and the launch can never slip behind fact-checking or a later step.** Performing the launch is a required preamble action on any interactive run; what is best-effort is only the *outcome*, not the attempt — the dashboard merely observes, so if Python is missing or the launch fails, say so in one line and carry on (a failed or absent dashboard must never block the pipeline), but silently not attempting the launch is a different failure and is not allowed. Invoke `/resolve-issue-dashboard` and treat it as **fire-and-return**: whichever outcome that skill reaches — server started, an already-running one reused, or could-not-launch — control returns here and the preamble continues (P2 onward), and the dashboard skill's closing *stop* ends only the launch sub-task, never this run. The dashboard is global and needs no ticket or `state.md` to come up — this repo shows as a placeholder and this run fills in the moment P4 writes `state.md`. It is **idempotent across resumes** (its singleton reuses a running server, no second tab) and **skipped only in a non-interactive run**. Mechanics and rationale: `SKILL_DIR/DASHBOARD.md`.
- **P2 — resolve ticket, base, and work-branch.** Ticket: take it from an explicit argument or Jira reference, else from the current branch (`git branch --show-current`, extract `acme-\d+` case-insensitive), else from a pasted issue (ask for a ticket, or use a stable `ad-hoc-<slug>`; never invent a fake `acme-` number). Two different numbers across argument / branch / Jira → ask which. Normalise the ticket twice — lowercase for the resolve directory (this skill's own namespace), uppercase (`ACME-…`) only for the Jira URL; the casing of a **commit** prefix is not this skill's to pick, it comes from the repo's history per b-implement. Base/target: **ask the remote, do not guess an order** — `git symbolic-ref --short refs/remotes/origin/HEAD` gives this repo's default branch (strip the `origin/`); only if that is unset fall back to whichever of `main` / `master` exists, preferring `main`. If the work targets a release line (a release-only fix or a backport), base is the corresponding `release/*` — determine it the same way `open-pr` does, and ask if it is genuinely ambiguous. Record base in `state.md` so b-code-risk's `git merge-base` and b-open-pr's target stay consistent.
- **P3 — create the resolve directory before Step a-fact-check.** Make `.claude/resolve/<ticket>/` (lowercase ticket, or `ad-hoc-<slug>`) if it does not exist, so `review-issue-fact` detects the resolve context and persists `fact-check.md` there instead of only printing it. Also, if `.claude/resolve/.gitignore` is absent, write it with a single line `*` — so the handoff artifacts never land in a commit no matter what the consumer repo's root `.gitignore` says. This scopes the skill's own output directory and does not touch the user's config.
- **P4 — resolve the cursor.** Read `state.md`, run the staleness gate below, then reconcile against the observable git facts and continue from `next-step`. The first write of a run records `ticket` (the value P2 resolved) alongside `next-step` and `started`, and every later rewrite carries it forward — the dashboard reads it for the run header and shows the run untitled without it. Immediately after each step completes, write the next step into `state.md` so any interruption resumes cleanly, **and — only when `next-step` changes to a new value (a real transition, not a same-step rewrite such as setting or clearing `attention`) — append one line `- <ts> <new-step>` to `.claude/resolve/<ticket>/timings.md`**, where `<ts>` is a UTC timestamp captured the same way as `started` (the `date -u` command in the timestamp rules) and `<new-step>` is the value just written — reusing the `started` value for the t0 `a-fact-check` line and the `ended` value for the `done` line, so no extra clock read is needed at those two. This append is append-only (`>>`, never a read-rewrite) and best-effort / non-fatal — a failed append prints one line and is skipped, never blocking the step — the same discipline as the generic-learning dead-drop. Its only consumer is the dashboard's per-step timing; the `timings.md` artifact bullet above owns the format and rationale. On a genuinely fresh-from-top run, write `state.md` **once here** — still before Phase A begins, but **not** a cue to jump into a-fact-check now: P5 (load conventions) still runs first, then Phase A. Write it with `next-step: a-fact-check` and `started` set — so this run now surfaces in the dashboard that P1 already brought up, showing Fact-check from its first moment, and so `started` is genuinely recorded while the cursor is `a-fact-check`, as the timestamp rules require. On this fresh-from-top start, before Phase A, also print one **non-blocking** line: the run is starting on the current model and effort and will **not** switch mid-flight, so if the human wants a stronger model or higher effort they should stop now and re-invoke (nothing is lost), otherwise it continues — do not pause for an answer, and do not claim to detect the current effort (the skill cannot read it). Thereafter, on every write carry the existing `started`/`ended` through unchanged (see the timestamp rules above). This t0 write is why the staleness gate and the reconcile below treat an `a-fact-check`-only `state.md` (no `work-branch`, no `plan.md`, no fix commit) as a fresh / resumable run, not an unrecognised one.
- **P5 — load learned conventions (best-effort, read-only).** Read `$HOME/.claude/resolve-learnings/conventions.md` if it exists and honor its preferences for the rest of this run **only when they are consistent with current behaviour** (honored preferences, not hard rules); skip entries past their `ttl`, and honor nothing if the file's top-line `schema_version` marker is unrecognised. **Do not write the file** (the physical prune belongs to `/resolve-issue-learnings`, the single writer). **Non-fatal**: if it is absent, unreadable, or unparseable, honor zero conventions and carry on. Scope (what a convention may cover), the per-file schema rule, and rationale: `SKILL_DIR/LEARNINGS.md`.

**Staleness gate — before the reconcile, whenever a top-level `state.md` already exists.** A ticket directory is keyed on the ticket alone, so a later branch reusing the same ticket number would otherwise inherit the previous run's `state.md` / `plan.md` — and a `plan-approved: yes` that git cannot refute — and mistake it for the current change. Before trusting the cursor, decide whether the top-level `state.md` belongs to *this* working tree, comparing the branch it recorded against the current branch (`git branch --show-current`):

- **`work-branch` recorded and different from the current branch** — a different run's residue → archive it (below) and start fresh. (If the current branch is empty — detached HEAD — do not count that as a mismatch; fall to the confirm case.)
- **`work-branch` recorded and equal to the current branch** — the legitimate resume of this run → do not archive; continue into the reconcile.
- **No `work-branch` recorded, but `state.md` shows in-flight progress** (`plan-approved: yes`, a cursor already in Phase B / `done`, a drafted `plan.md` not yet approved, or a `decisions.md` carrying real `resolved` / `open` decisions — an interrupted a-elicit-decisions) **and the current branch carries no matching fix commit** — branch alone cannot separate "continue this plan" from "a new change on the same ticket", so ask the human (continue / new) and archive only on *new*, else resume or report `done` (on *continue* with an `a-elicit-decisions` cursor, resume a-elicit-decisions and re-present the `open` entries, keeping `decisions.md`). `work-branch` is written only at the Phase B guard, so a run abandoned after planning but before `b-implement` has none; this one confirm closes that gap without mis-killing the legitimate "approved on base, just switched to the feature branch for b-implement" handoff.
- **Genuinely fresh** — `state.md` and `plan.md` both absent with no fix commit, **or** a `state.md` whose cursor is still `a-fact-check` with nothing approved (no `plan.md`, no `work-branch`, no fix commit), **or** an `a-elicit-decisions` cursor (no `plan.md`, no `work-branch`, no fix commit — as for the forms above) whose `decisions.md` is absent or holds only a `none` / `skipped` sentinel. The `a-fact-check` case is the normal residue of P4's t0 write; a sentinel-only or absent `a-elicit-decisions` residue is its equivalent — nothing the human answered is at stake. Both are tree-safe to re-run (a-fact-check is read-only; a no-op a-elicit-decisions re-asks nothing), so both take the fresh-from-top path: overwrite `started` fresh (do **not** preserve the orphan's value, so a stale start never bleeds in) and overwrite any leftover sentinel `decisions.md`; there is nothing approved to archive. **An `a-elicit-decisions` residue whose `decisions.md` carries real `resolved` / `open` decisions is NOT fresh** — the human already answered some, so it is in-flight Phase A work and takes the no-`work-branch` in-flight path above (continue → resume a-elicit-decisions, re-presenting the `open` entries and keeping `decisions.md`; new → archive), never a `decisions.md`-wiping fresh restart.

**Archive is a move, never a delete.** When the staleness gate above routes to *archive*, follow the full procedure in `SKILL_DIR/ARCHIVING.md` — it moves the superseded run's files into a timestamp subdir `.claude/resolve/<ticket>/<stamp>/` (stamp derived from the old `started`, `state.md` moved **last** so an interrupted archive is idempotent), never deleting anything, then proceeds fresh-from-top.

**Work-branch guard — before any Phase B commit.** The current branch must be a feature branch distinct from the base (`master` / `main`, or the targeted `release/*` for a backport). If still on the base branch (including a release line), stop and ask the user to create or switch to `feature/<ticket>-<slug>` — do not create it silently, and never commit the fix or tests onto the base. Record `work-branch` in `state.md` only once a valid feature branch (not the base) is confirmed. Step a-fact-check is read-only and may run on any branch; this guard binds only the Phase B commits.

**Invocation discipline.** Invoke each component by its explicit slash form (`/adversarial-review:…`, `/test-authoring:…`, `/pr-lifecycle:…`) and confirm it actually ran and produced its expected output (its result table or verdict) — do not approximate a component's behaviour in place of running it (there is no skill-to-skill API; the whole design rests on faithfully delegating). Note that `review-code-risk` correctly spawns no verifier when it auto-fixed nothing, so "a verifier ran" is not a reliable did-it-run signal. If a component cannot be loaded or invoked, stop and say so rather than imitating it. b-security-review invokes the **built-in** `security-review` by bare name (built-ins have no plugin namespace); confirm the built-in itself answered — the R4-bis preflight in b-security-review guards against a same-named plugin shadowing it — and degrade per the Degradation section if it did not: a shadowed, missing, or unconfirmable security review is treated as **not having run** and surfaces *loudly*.

**State reconcile (self-heal).** Reconcile `state.md` against git on every start, not only when it is missing. The git-observable facts are only what git can see — fix/test commits and an open PR; the Phase A approval evidence lives in the local gitignored `state.md` / `plan.md` (readable in the same working tree). Cursor judgement: `plan-approved` in `state.md` or an existing fix commit → Phase A done; a fix commit but no test commit → resume at `b-write-tests`; a fix commit and a test commit but no PR, with no trustworthy `state.md`, → resume at `b-security-review` (git cannot tell whether the review passes already ran — their fixes are ordinary commits — so re-run from the first review pass; re-running is tree-safe, which is why a present `state.md` cursor is trusted over this fallback); an open PR for the branch → `done`. A legacy `state.md` cursor still pointing at a removed step is redirected: `b-code-review` is advanced to `b-security-review`, and `b-commit-tests` — whose commit is now the tail of `b-write-tests` — resumes at `b-write-tests`, the same place the git-derived rule above sends a run with a fix commit but no test commit. When `state.md` disagrees with git (it says `b-implement` but a fix is already committed), trust git and say so — never re-run `b-implement` from a stale cursor and create a duplicate or conflicting change. If both `state.md` and `plan.md` are absent (a fresh clone) and there is no fix commit — or `state.md` carries only an `a-fact-check` or a sentinel-only `a-elicit-decisions` cursor with nothing approved (the pre-plan residue described in the staleness gate — a `decisions.md` with real answers is in-flight, routed by that gate, not fresh) — treat it as a fresh run and say so plainly; do not misjudge Phase A as done. When the P4 staleness gate has just archived a prior run, the top-level is empty, so this same fresh path applies.

## Capturing generic learnings (cross-cutting, best-effort)

At each step boundary (the same point P4 writes the next step to `state.md`), take one beat: did anything about the pipeline *itself* misfire, surprise, or prove awkward? **Bias to silence — the default is to write nothing.** Append a candidate only when the observation is all of: *generic and orchestration-scoped* (about resolve-issue's own gate flow / sequencing / state / a-draft-plan drafting / component-choice — **not** a delegated component's internals, which belong to that component's own loop), *grounded* in a concrete signal (a gate interaction, a `SKILL.md` instruction that misfired, a wrong verdict), and *novel*. When it clears that bar, append it — a true append (`>>`), not a read-rewrite — to the user-global dead-drop `$HOME/.claude/resolve-learnings/candidates.md` in the entry shape `/resolve-issue-learnings` documents (that skill owns the format). This is **capture only** (nothing is trusted until `/resolve-issue-learnings` verifies it against the current skill), **append-only, silent, and non-fatal**, and kept off `state.md` and every hot path. Full criteria, the dead-drop rationale, and why capture is per-step: `SKILL_DIR/LEARNINGS.md`.

## Phase A — diagnose and plan

The four planning steps share `decisions.md` / `plan.md` authoring and the revise loop, so their how-to lives in one cluster file. **Load `SKILL_DIR/PLANNING.md` before executing any of them.** On a fresh run that is before `a-fact-check`, but a resume or a gate-revise re-entry can land the cursor directly on `a-elicit-decisions`, `a-draft-plan`, or `a-harden-plan` — the load is **per-step, not once-per-phase**, so each of those must load `PLANNING.md` first too. The one-line contract per step below is a **non-normative spine index**, not the executable detail: each step's safety rails (the decision no-op sentinel, `a-draft-plan`'s sole-author invariant, `a-harden-plan`'s write-once baseline snapshot) live in `PLANNING.md`, and **where a one-liner and `PLANNING.md` differ, `PLANNING.md` wins**. Contract per step (id · what it produces):

- **`a-fact-check`** — invoke `/adversarial-review:review-issue-fact`; advisory HALT / PROCEED / RESOLVE (never a hard stop); writes `fact-check.md`. Runs only after the full preamble; read-only, any branch.
- **`a-elicit-decisions`** — settle the load-bearing open decisions the plan will rest on (facts looked up; decisions asked one-at-a-time as end-of-turn prose with a recommendation; voiced `none` sentinel if there are none); writes `decisions.md` (`resolved` / `open`); does **not** create `plan.md`.
- **`a-draft-plan`** — **sole author** of `plan.md`: folds the `resolved` decisions in, then drafts Summary, Verification / Acceptance (the oracle, on the ground-truth > differential > metamorphic > human-judgment hierarchy), Out of Scope, and Testing approach.
- **`a-harden-plan`** — snapshot `plan.baseline.md` (write-once), then invoke `/adversarial-review:review-plan-risk` naming that baseline copy so it auto-fixes the untracked plan in place; delete the baseline once the cursor advances.

**a-gate-approve — the one plan-approval gate (an approve / revise / question loop).** Present `.claude/resolve/<ticket>/plan.md` and confirm with the human directly — this is a bespoke present-and-confirm loop, **not** the built-in plan mode / `ExitPlanMode` (which would write the plan to `~/.claude/plans/`, a different file `review-code-risk` cannot read).

Present the gate as end-of-turn prose, and **MUST NOT render it as an `AskUserQuestion` picker.** Render the *full* current `plan.md` — its leading summary and its body both — as ordinary text in the turn, then end the turn so control returns to the main prompt. A summary, an excerpt, or a bare file-path reference does not satisfy this: the human has to be able to review the whole artifact they are approving without opening a file. If the plan is long enough that a full render risks truncation, say so and make sure the tail is not silently dropped — never let a truncated render pass as the whole plan (a genuinely bloated plan is a signal to tighten it, not to hide it). `review-plan-risk`'s result table may be shown alongside for reference (the risk view), but it is optional context, not the artifact under approval. The prose form is mandatory, not merely preferred, for two reasons: a picker captures keyboard input, which blocks the very `/model` / `/effort` change this gate invites (below); and this section already read as a "present-and-confirm loop" yet was still rendered as a picker, so the prohibition has to be explicit rather than implied.

The a-gate-approve is its own turn, reached only after a-harden-plan (`review-plan-risk`, including its Step 3.6 opt-in) has fully returned, and it **never absorbs or re-draws that opt-in.** The edge-case opt-in is `review-plan-risk`'s own interaction at a-harden-plan; by the a-gate-approve it is already settled, so the a-gate-approve presents only the final `plan.md` for approval and never re-surfaces the enhancement choices — folding the opt-in and the approval into one prompt is exactly what produced the picker.

When presenting the plan, add one line noting this pause is also the moment to change model or effort for Phase B: its reasoning runs at the current session setting, so if the human wants a different level they change their model/effort now (e.g. via `/effort` or `/model`) and then reply to approve — the approval starts Phase B on the new setting. This never blocks: only an explicit approval advances the cursor.

- A question that does not change the plan → answer it; the cursor stays at `a-gate-approve` and `plan.md` is untouched.
- A minor adjustment (wording, scope trim, dropping a step) → edit `plan.md` (refresh the leading summary to match), re-present the full updated plan; no need to re-run `review-plan-risk`.
- A substantive change (different approach, a new component, a changed contract) → edit `plan.md` (refresh the leading summary). **The risk surface changed, so `review-plan-risk` must re-run either way:** if the change invalidates a decision recorded in `decisions.md`, mark that decision `open` and re-enter planning at `a-elicit-decisions` (load `PLANNING.md` — its **Re-entry from a gate revise** note owns the targeted re-elicit → re-fold → re-harden, which ends in that re-run), so a stale decision never rides through approval; otherwise return straight to `a-harden-plan` and re-run `review-plan-risk`. Then re-present the full plan. Judge minor vs substantive; ask if unsure.
- Only an explicit approval advances `next-step` to `b-implement` and records `plan-approved: yes`. `plan.md` is not committed. During the loop the cursor stays at `a-gate-approve` (or briefly back at `a-harden-plan`, or `a-elicit-decisions` while a decision-invalidating revise re-elicits); a fresh session re-presents the current `plan.md` in full and continues the loop.

## Phase B — build and open the PR

**b-implement — implement the fix.** Implement the fix directly (no skill for this step), then commit it. Commit message: **take the convention from this repo's own history, do not assume one** — `git log --format=%s -n 30` shows whether subjects carry a ticket prefix and in what form (bracketed, bare, a Conventional Commits type, or none) and how long they run, and `git log -n 50 --format=%b | grep -c '^Co-Authored-By'` shows whether it uses that trailer. Match what you find, using the ticket from the work-branch when the history prefixes with one. Only where the history is empty or inconsistent, fall back to a plain `<summary>` (≤50 chars, no trailer) and say so.

**b-write-tests — write tests for the fix.** Invoke the scoped test writer for the change just made — `/test-authoring:add-unit-test` or `add-integration-test` (Mode A, git-diff scoped), or the matching `update-*-test` when the changed file already has a sibling test. Choose the type from what changed: pure logic / service / handler methods → unit; endpoint / persistence / cross-component behaviour → integration; if the right type or scope is genuinely unclear, ask one targeted question rather than guessing — and prefer the **type the plan's `Testing approach` named**; if the test you actually write diverges from it, record that in one line in `plan.md`, so `review-code-risk` (which reads `plan.md`) sees a reconciled record rather than a false mismatch. These run scoped to the change and carry their own writer and verifier subagents; they have no open-ended "select what to implement" gate. Do **not** use `scan-test-gaps` here — it is built for broad gap-hunting, surfaces untested code outside the fix, and has no clear end inside an automated flow. Gherkin scenario coverage is not something `test-authoring` writes, so if it looks needed, say so and leave it to the human rather than improvising it here. **Close the step by committing the tests as their own commit, staging only the test files.** That commit must land before the review passes (b-security-review onward): they read the committed diff (`git diff $(git merge-base origin/<base> HEAD)..HEAD`) and each needs a clean working tree so that pass's own edits are the only uncommitted ones — uncommitted tests would be invisible to them. Writing *and committing* the tests before the review passes is deliberate: they are an **independent regression oracle** for the code b-security-review and b-code-risk then modify.

**Discrimination check (bounded, anti-tautology).** Because b-write-tests runs *after* b-implement has committed the fix, a test written now can accidentally encode the fix's behaviour rather than the correct behaviour. Confirm the new test actually discriminates the bug: revert only the fixed files in the working tree (`git checkout <fix-commit>~ -- <files>`, where `<fix-commit>` is b-implement's commit), run the new test, and confirm it goes **red for the right reason**, then restore (`git checkout HEAD -- <files>`) and confirm it is **green**. One red/green demonstration, not a TDD loop. The assertion must encode a-draft-plan's oracle value, not whatever the fix currently returns. A **build / compile failure on revert is NOT a valid red** — it means the test references symbols the fix introduced, so the revert broke the build instead of exercising the old behaviour. When the fix changed a signature or added a symbol the test depends on (so pre-fix code cannot compile with the new test), the revert is infeasible: fall back to asserting the oracle value is **implementation-independent** (a known-correct value from a-draft-plan's ground truth, never read back from the fixed code), and record that the revert check was skipped as infeasible with the reason. If the test compiles against pre-fix code yet still passes green, the test mirrors the implementation — surface it, do not paper over it.

**`b-security-review`, `b-code-risk` — the review passes.** These two share the `apply → verify → commit` discipline and its verify-subagent, so their how-to lives in one cluster file with two within-cluster variations: the R4-bis preflight binds `b-security-review` (which invokes a bare-name built-in) but not `b-code-risk`, and the verify-subagent is a *guaranteed* build+test gate for `b-security-review` **when it changed code** but only a *backstop* for `b-code-risk`. **Load `SKILL_DIR/REVIEW-PASSES.md` before executing either of them.** On a straight run that is before `b-security-review`, but a resume can land the cursor directly on `b-code-risk` — the load is **per-step, not once-per-phase**, so each must load `REVIEW-PASSES.md` first too. The one-line contract per step below is a **non-normative spine index**: the discipline, the red-verify-subagent routing, each step's specifics, `b-code-risk`'s behavioural symptom re-check, and their safety rails (the commit-withholding guaranteed gate, never gaming a test green, the loud security-absence signal) live in `REVIEW-PASSES.md`, and **where a one-liner and `REVIEW-PASSES.md` differ, `REVIEW-PASSES.md` wins**. Contract per step (id · what it does):

- **`b-security-review`** — built-in `/security-review` (report-only); blast-radius disposition of findings; R4-bis preflight where a shadow is worse than an absence — anything not a positively-confirmed native run is treated as **not having run** and surfaces `SECURITY REVIEW DID NOT RUN` at the checkpoint below.
- **`b-code-risk`** — `git fetch`, then `/adversarial-review:review-code-risk` on its anchor triple (committed diff + issue + `plan.md`); its verifier is a backstop; auto-fixes land uncommitted and are committed only after the human sees the result table; ends with the behavioural symptom re-check.

**b-code-risk to b-open-pr checkpoint.** After `review-code-risk` reports, if any unresolved `real` / high risk or `proposed (failed verification)` row remains, pause and let the human disposition it before opening the PR — do not open a PR on a fix with known unaddressed risks. This checkpoint is also where a **security review that did not actually run surfaces as a first-class pre-PR item**: **grep `plan.md` for a line beginning `SECURITY REVIEW DID NOT RUN`** (b-security-review writes it there per `REVIEW-PASSES.md`, which also holds the full condition — unavailable, shadowed, misbehaving, or unconfirmable). Read it from the file rather than from your own memory of this session: on a resumed run there is no such memory, and that is exactly when the signal matters most. If the line is present, display it here and make the human explicitly choose to open the PR without security coverage — the loud signal lives at this gate, not only as an inline note left behind at b-security-review. (`review-code-risk`, unlike `review-issue-fact`, is not designed to progress unattended; it hands its result table to the human.) While paused here, set `attention` in `state.md` to a one-line reason (e.g. `attention: unresolved high risk in FooService`), and clear it per the `attention` rule (the `state.md` field above) the moment the human **dispositions** the risk — accepts it, or directs a fix — and you resume work. Keep `next-step` at `b-code-risk` when you clear `attention` (you are resuming the step, not advancing it); move `next-step` to `b-open-pr` only once the disposition work is done.

**b-open-pr — open the PR.** Invoke `/pr-lifecycle:open-pr`. It publishes the work-branch to the remote if it is not there yet (the Phase B commits can stay local until now), learns the caller's PR convention, drafts the title and description, and creates the PR only after explicit confirmation — it never pushes or creates without that confirmation. When the base is a `release/*` line, `open-pr` uses its backport format (provide the source PR number it asks for). `open-pr` pauses for that confirmation, so the run is waiting on the human for the whole time its draft sits on screen — the same "currently waiting on the human" condition the b-code-risk→b-open-pr checkpoint marks. Set `attention` in `state.md` (e.g. `attention: awaiting open-PR confirmation`) **before** invoking `open-pr`, and clear it per the `attention` rule (above) the moment that wait ends — the human confirms and the PR is created, or the human declines / the PR tool is missing and the run stops here. Keep `next-step` at `b-open-pr` until the PR actually exists (a declined confirmation or a missing PR tool leaves it at `b-open-pr`, per the degradation note); only the `done` transition below advances the cursor.

**done.** Record `pr-url` in `state.md`, set `ended` to the current UTC timestamp (preserving `started`), and point the human at Phase C — `/pr-lifecycle:resolve-pr-comments <PR_ID>` — as the later, user-invoked step once reviewers have commented. Then run the self-improvement upkeep and the result summary below.

**Self-improvement upkeep (best-effort, non-fatal, interactive only).** Count the **fresh** (non-`deferred`) generic-learning candidates in `$HOME/.claude/resolve-learnings/candidates.md`; if that count is at or above a small threshold (default 5) and this is an interactive run, auto-invoke `/resolve-issue-learnings` in its **unattended** mode so the captured learnings are verified and the high-confidence ones applied — no gate, this is internal upkeep. **Non-fatal** (a failure prints one line and is skipped — never blocks `done`) and **skipped in a non-interactive run**. If the harvest deferred any candidate, note in one line that a manual `/resolve-issue-learnings` pass would clear them. Why the fresh-count is also the snooze, and the mode boundaries: `SKILL_DIR/LEARNINGS.md`.

**Result summary (this session only — not the dashboard).** Print a short recap of what this orchestrator legitimately observed: the ticket and its outcome, the work-branch and base, the PR URL, whether the plan was approved, any risk paused on at the b-code-risk→b-open-pr checkpoint, whether the security review actually ran — re-read `plan.md` for the `SECURITY REVIEW DID NOT RUN` record rather than recalling it, and print that line prominently when present, and a one-line self-improvement report affirming capture ran, how many candidates it caught, and the harvest outcome (applied / deferred) — see `SKILL_DIR/LEARNINGS.md` for why that affirmation matters (it lets `0` mean "genuinely nothing" rather than "did not run"). Do **not** re-aggregate the component skills' verdict / risk tables — the sequencer does not thread those as data; point to where the detail lives (`fact-check.md`, the transcript, the PR) rather than restating it. This recap is terminal-only; the dashboard already visualises live progress and is not fed by this step.

## Degradation and safety

- **Atlassian MCP absent** — `review-issue-fact` degrades itself (`Jira anchor not available`). If there is no issue content at all, ask the human to paste the issue before a-fact-check.
- **`review-issue-fact` returns HALT** — advisory; surface it prominently, name the refuted claim, and ask whether to stop or proceed (default toward stopping). Record the decision.
- **`review-issue-fact` returns RESOLVE** — advisory, handled the same way as HALT above: surface it, name what is missing, ask whether to stop or proceed, default toward stopping. **What differs is the ask**, because four different things produce RESOLVE — `PLANNING.md`'s a-fact-check entry enumerates them and is the single place they are listed, so read it there rather than inferring from the verdict word alone.
- **Running inside a subagent** — stop at P0; do not proceed in a degraded mode that would silently lose the component verifiers and the human gates.
- **The built-in `security-review` unavailable or shadowed** — see the R4-bis preflight at b-security-review and **Invocation discipline**: a shadowed, missing, or unconfirmable `security-review` is treated as **not having run** and surfaces `SECURITY REVIEW DID NOT RUN` *loudly* at the b-code-risk→b-open-pr checkpoint and in the done summary. It is the pipeline's only built-in review pass, so a silently-accepted wrong tool would be false coverage.
- **Non-interactive (gates unreachable)** — the human touchpoints are a-fact-check (advisory), a-elicit-decisions (decision elicitation — self-degrades like a-fact-check: records `skipped (non-interactive)` in `decisions.md` and continues, never a hard stop), the a-gate-approve plan-approval loop, the b-security-review disposition of any findings or red verification, accepting `review-code-risk`'s auto-fixes before b-code-risk commits them, and the b-open-pr open-PR confirmation. Two more live inside the components rather than here, and they cost the human just as much: at a-harden-plan, `review-plan-risk`'s scope confirmation and its edge-case opt-in batch; at b-write-tests, the test-type question plus any quality flag the test verifier surfaces (each is a bounded question, not an open gate — but a run still sits there until it is answered). Run the read-only / local work up to the first touchpoint, checkpoint `state.md`, then stop and explain the gate and how to resume interactively. Never auto-approve a plan or auto-open a PR.
- **The detected platform's PR tool missing at b-open-pr** — when the platform's PR tool is unavailable or unauthenticated (`open-pr` detects Azure DevOps or GitHub and checks the matching tool), `open-pr` prints the prepared title and description for manual creation; leave `state.md` at `b-open-pr` (not `done`) so a later tooled re-invocation can finish.
- Never resolve merge conflicts automatically, never force-push, and commit only the changes made for this ticket.

## Git-handling note (self-scoped; no edits to the user's .gitignore)

`plan.md` and the other handoff artifacts are kept out of git by design (P3 writes a self-scoped `.claude/resolve/.gitignore` of `*`, never touching the consumer repo's root `.gitignore`); `review-code-risk` reads `plan.md` from disk and a-harden-plan hands `review-plan-risk` a baseline copy instead of a tracked file, so nothing here needs committing. Full rationale — why a gitignored file keeps the tree clean, why committing `plan.md` would pollute b-code-risk's diff and the PR, and the opt-in to commit it anyway — is in `SKILL_DIR/GIT-HANDLING.md`.
