- name
- issue-resolver
- description
- Create an atomic PR closing a GitHub issue end-to-end via a 6-step pipeline. Use to resolve, fix, or implement issue #N. Don't use for analysis without fixing (/issue-analysis), reviewing a PR (/issue-pr-review), or bulk backlog work (/auto-pilot).
- license
- MIT
- compatibility
- Requires git and GitHub CLI (gh) with authentication and push access. Self-contained — uses shared agents from shared/agents/.
- metadata
- {"version":"0.21.0","author":"Luong NGUYEN <luongnv89@gmail.com>","effort":"max"}
# /issue-resolver N
Issue → atomic PR in 6 steps.
## Invocation
| Invocation (`N` = issue number) | What happens |
|------------|--------------|
| `/issue-resolver <N>` | Interactive; user picks the plan |
| `/issue-resolver <N> --auto` | No prompts; set by `/auto-pilot` |
| `/issue-resolver <N> --no-run-log` | Modifier, **orthogonal to `--auto`**: append nothing to `.idd/runs.jsonl`, return telemetry (*Run-log entry*) |
| `/issue-resolver <N> --resume` | Modifier: resume a killed run from its progress record (*Progress record*); the only way `--auto` resumes, while interactive also offers it whenever a record exists |
## Context re-entry <!-- a:ctx-reentry -->
**Only when `compaction.enabled` is true; otherwise skip this section** (`references/docs/context-threshold.md`). After a compaction mid-run, durable state is git plus GitHub; the *Progress record* serves only a killed session and is never read here.
- **Caller-managed lane** (`IDD_CALLER_WORKTREE=1`, this conversation's `parallel_lane` record) first: recover that record, then re-run Step 0e's caller-managed validation from a command that starts `cd -- "$IDD_WORKTREE_PATH" && …`, its exports repeated. Bind the branch and PR only inside that validated root. A record not recovered intact stops the lane with `failure_step: preflight`, `status: blocked_dirty`. **Never fall back to the ambient checkout.**
- Re-bind the branch live, `branch="$(git rev-parse --abbrev-ref HEAD)"`, then the PR with `gh pr list --head "$branch" --json number,state` — never a branch name pasted from the compaction summary.
- Read the current step from this conversation's `[N/5]` lines and re-read that step's reference file. Before repeating a push, `gh pr create`, comment or label change, confirm with a live `--json` read that it has not happened.
- **Step 4 loop:** if **either** the cycle count **or** the premise-reset ledger cannot be read from this conversation, treat the cycle cap as reached and take Step 4's cap-reached path: deliver with known issues and **no** QA marker. Interactive re-entry also treats the premise gate as blocked, so no `continue` sends another fix. **Never restart the loop at cycle 1.**
## Prerequisites
Run these checks in order before any operation. A non-zero exit or empty output stops the run: print that check's `references/error-messages.md` error and the *Closing Summary* `BLOCKED` block.
1. Git repository: `git rev-parse --git-dir`.
2. `gh` installed: `which gh`.
3. `gh` authenticated: `gh auth status`.
4. Remote present: `git remote -v` prints at least one line.
## Repo Sync Before Edits (mandatory)
In-place path only; worktree paths start from the fetched base, and an invalid `IDD_CALLER_WORKTREE=1` is a stop, never a sync bypass. Use the **stash-first pattern**: copy the snippet and recovery from `references/docs/sync-conventions.md` (*Quick Reference (Copy-Paste Snippet)*), never a bare rebase on a dirty tree. A missing `origin` or a conflict stops and asks (interactive), or aborts (auto).
## Configuration
Load config once; never re-read it. Run `python3 references/scripts/gi-config.py` — **Working directory:** the repo root; **Script path:** absolute, as the *Bundled dependency precheck* resolves its list. It prints `{"config": …, "config_file": …, "first_run": …}` merged over the defaults below (rationale: `references/steps/step-0-preflight.md`, *Configuration load*).
- **Exit 0** — use `config`.
- **Exit 3** — invalid `.idd.yml`: print the `references/error-messages.md` error (*Invalid config*), stop.
- **Script file absent** — a broken install and not a degrade: stop, print `✗ Missing bundled dependency`.
- **Anything else** (no `python3`, non-zero exit, unparsable stdout) — print `⚠ gi-config unavailable — reading .idd.yml by hand` and read it yourself *instead of* the script (else legacy `.gitissue.yml`, printing `⚠ legacy .gitissue.yml found — rename to .idd.yml`), over the keys and defaults below.
Either path: no `.idd.yml` (`first_run`) prints `○ First run — using default config. Run /init-idd to customize.`
**Capture the run clock here:** chain that same `python3` invocation as `python3 …; ec=$?; date +%s >&2; exit "$ec"`; the stderr epoch is `run_started_epoch`, the *Run Stats Footer*'s `elapsed` anchor. Take `date +%s` again as each `[N/5]` step starts and at the terminal outcome: those boundaries are the run log's `phases` (`references/report-templates.md`).
**Context threshold.** <!-- a:ctx-check --> Only when `compaction.enabled` is true (`references/docs/context-threshold.md`). At each `[N/5]` step start, beside that clock read, compare the remaining-context figure the host placed in your context (none: skip silently, never estimate) with `compaction.threshold.issue-resolver`. At or below it, interactive prints the `⚠` line and a `/compact <focus>` line (skill, `#N`, step id and name only) and pauses **at most once per run**; `--auto` / `IDD_AUTO_MODE=1` prints one `⚠` line per run and continues — never a pause or stop.
Defaults and behavior per key: `references/docs/config-schema.md` — `issue.auto_normalize` · `resolve.approval_gate` · `resolve.branch_prefix` · `resolve.auto_test` · `resolve.test_timeout` · `resolve.max_commits` · `resolve.qa_max_cycles` · `resolve.adaptive_effort` · `resolve.ui_review.browser_review` · `resolve.borrow_skills: false` · `compaction.enabled: true` · `compaction.threshold.issue-resolver: 40`.
---
## Subagent Architecture
Heavy work goes to subagents (`shared/agents/`), keeping the main agent's **context window** lean: it owns Step 0, Step 4's loop and Step 5; Steps 1-3 each spawn one (*Subagent Architecture Diagram*). Prompts: `references/agents/<name>.md`; conventions: `references/docs/shared-agent-conventions.md`. Without the Agent tool (e.g. Claude.ai), run each step inline via its fallback instructions.
### Spawning a subagent (canonical pattern)
Role, description and prompt file change per step. **Do NOT set `subagent_type`**:
```python
Agent(
description="{role} — {action} issue #N",
prompt=<{agent-file}.md prompt with {variables} replaced>,
# never subagent_type — the default general-purpose agent, not "code-reviewer"
)
```
**Per-role overrides:** at every spawn apply `references/docs/agent-overrides.md` — the resolved `agents.model.<role>` / `agents.effort.<role>` for the spawned role (`codebase-researcher`, `synthesizer`, `implementer`, `code-reviewer`, `ui-reviewer`, `fixer`); `null` (the default) passes nothing.
### Orchestrating the agents
Name the role in `description`, size model/effort per `references/docs/agent-model-effort.md`, and read each return before advancing: a missing or blocking return stops (interactive) or takes the auto behavior (*Orchestrating the agents*).
### Bundled dependency precheck
Check every path below exists relative to this SKILL.md's directory. A missing path stops the run: print the `✗ Missing bundled dependency` block from `references/error-messages.md` (*Bundled dependencies*); never guess a prompt.
```text
references/agents/codebase-researcher.md
references/agents/synthesizer.md
references/agents/implementer.md
references/agents/code-reviewer.md
references/agents/ui-reviewer.md
references/agents/fixer.md
references/pipeline-steps.md
references/steps/step-0-preflight.md
references/steps/step-0h-analysis-reuse.md
references/steps/step-0i-caller-payload.md
references/steps/step-0j-progress-record.md
references/steps/step-1-research.md
references/steps/step-2-plan.md
references/steps/step-3-implement.md
references/steps/step-4-qa.md
references/steps/step-5-deliver.md
references/report-templates.md
references/run-stats.md
references/bug-verification.md
references/skill-index.md
references/error-messages.md
references/docs/sync-conventions.md
references/docs/naming-conventions.md
references/docs/pre-commit-security.md
references/docs/idd-methodology.md
references/docs/github-projects-sync.md
references/docs/run-log-schema.md
references/docs/config-schema.md
references/docs/agent-model-effort.md
references/docs/agent-overrides.md
references/docs/shared-agent-conventions.md
references/docs/platform-github.md
references/docs/terminal-style.md
references/docs/ui-review.md
references/docs/context-threshold.md
references/scripts/gi-config.py
references/scripts/gi-runlog.py
references/scripts/gi-secscan.py
references/scripts/gi-branch.py
references/scripts/gi-gh.py
references/scripts/gi-issue.py
references/scripts/gi-state.py
references/scripts/gi-receipt.py
references/scripts/gi-sensitive.py
references/scripts/gi-premise.py
references/scripts/gi-sketch.py
references/scripts/gi-recipe.py
references/scripts/gi-progress.py
```
---
## Progress record <!-- a:rs-progress -->
A killed session can resume (`--resume`). Read the record after the precheck, before *0a*; from *0c* on, write it with `references/scripts/gi-progress.py` at every `[N/5]` start, at each Step 4 cycle, and before every effect. It holds only what git and GitHub cannot reproduce: the step, the plan summary, cycle counts, the premise ledger, open findings, the last effect. **A record never satisfies or skips a safety gate:** every gate re-runs live, and any doubt is stale (start from Step 0). Write points, resume, validation and reconciliation: `references/steps/step-0j-progress-record.md` (*Step 0j — Progress record*) — **read it now**.
---
## Pipeline Overview
6 steps (0-5) — Preflight, Research, Plan, Implement, QA, Deliver — each printing a `[N/5]` line (`●` → `✓`/`✗`); expected output example: `references/report-templates.md` (*Expected Inline Pipeline Output*).
### Step completion reports
Each step closes with `√`/`×` per check and a `Result: PASS | PARTIAL | FAIL` line (*Step Completion Reports* in `references/report-templates.md`, **read it now**). A step is incomplete until `Result:` prints.
---
## Step 0 — Preflight
Open with `● Preflight check for issue #N...`.
### 0a — Fetch issue
GitHub reads share the boundary in `references/scripts/gi-gh.py`. Classify any framed caller payload first (`/auto-pilot` captured it in mode-neutral *Step 1.2b*); *Step 0i — Caller payload gate* in `references/steps/step-0i-caller-payload.md` is its single home — `issue_payload = supplied | partial | absent`, the mandatory live `gh issue view N --json state,comments,updatedAt` under `supplied`, the exact match between retained and live `updatedAt` before 0d, discard-and-refresh on mismatch (identical for individual, array, and keyed-map payloads), never a gate. Then run `python3 references/scripts/gi-issue.py {N} --fields number,title,body,labels,assignees,state,comments,updatedAt`, reading `.issue` unless the gate substitutes a payload. **Capture `updatedAt` here** — 0d's `gh issue edit` bumps it, so *0h* uses this pre-normalization value. Exit 3 stops; anything else degrades to `gh issue view {N} --json number,title,body,labels,assignees,state,comments,updatedAt`. **0d rewrites the body, so it MUST end with `python3 references/scripts/gi-issue.py {N} --invalidate`.** <!-- a:rs-0a-payload-concurrency -->
**Not found:** error, stop. **Closed:** warning, stop.
### 0b — Check for existing work
1. List branches: `git branch -a | grep -i "{N}"`.
2. List open PRs: `gh pr list --state open --json number,title,body,headRefName --limit 20`. A PR targets the issue when its body has `Closes #N`, `Fixes #N` or `Resolves #N`.
3. If an **open** PR targets it: print the `⚠ PR already targets issue` block from `references/error-messages.md` (*Guards*), stop, return `status: pr_in_progress` with its `pr_number` and `branch_name`, and **never close the issue** (*Early exit*) — unless a resume's ownership check (*Progress record*) proves the PR is this run's own.
Only a **merged** PR or a closing commit on the default branch is `already_resolved`.
### 0c — Guards
**Interactive:** warn and ask if assigned elsewhere, or on `wontfix`/`blocked`/`do-not-merge` labels. **Auto:** skip the assignment guard, log blocking labels, never stop.
### 0d — Auto-normalize
If `issue.auto_normalize` is true and the body lacks a `<!-- idd:normalized v1 -->` marker (a legacy `gitissue:normalized` one counts):
1. **Security label check (SPEC §1.4)** — before any rewrite scan labels for `security`, `CVE`, `vulnerability` (case-insensitive). On a match:
- **Auto mode (`--auto` / `IDD_AUTO_MODE=1`):** print the `⚠ … Skipping auto-normalization` warning (`references/error-messages.md` → *Security-labeled issue (skip)*), first matching label as `{label}`, continue **without** rewriting.
- **Interactive mode:** same warning, then ask for explicit operator confirmation; default **no**, a decline continuing without normalization.
2. **Normalize inline** — otherwise, structure-only as `/issue-creator` Normalize mode (the resolver does **not** invoke `/issue-creator` as a subprocess):
1. Classify the type and generate the body with the marker.
2. Back up the original body in a comment.
3. Run `gh issue edit`, then `python3 references/scripts/gi-issue.py {N} --invalidate`, then re-fetch.
4. On any failure, **warn** and continue with the original body (`references/error-messages.md` → *Auto-normalization failed*).
### 0e — Workspace (interactive only)
Derive one `{branch_name}` first: `python3 references/scripts/gi-branch.py {N} --from-issue --type {type}`, reading `.branch`. **`--from-issue` is mandatory** — never put the issue title or configured prefix on the command line (`references/steps/step-0-preflight.md` → *Step 0e — Workspace*). `{type}` is one of six classified literals. Exit 3 stops; anything else degrades to `references/docs/naming-conventions.md`.
Ver no GitHub