Implement Issue
Full issue → PR → close flow, locally.
Contract docs. The issue mechanics come from the repo's
docs/agents/issue-tracker.md (its ## Delivery operations section) and
the change mechanics from docs/agents/code-host.md — read those two files
first if present. The commands below are the GitHub factory defaults
(gh), used verbatim when those docs are absent or confirm GitHub; when a doc
defines a different mechanic for an operation, the doc wins. "PR" below
means whatever the code host calls a reviewable change (pull request,
merge request, branch + change file).
Their annexes are deferred, not optional. Those two docs link phase
annexes — code-host-ci.md when a change's CI has to be waited on, read or
classified; issue-authoring.md when issues are being created, which this
skill never does. Do not read an annex at the start: open it at the step
that names it, and not before. Implementing an issue and publishing a draft
change needs nothing from either.
Invoke
/implement-issue # lists open issues to pick from
/implement-issue 42 # implements issue #42 directly
/implement-issue 17 # if #17 has sub-issues, picks the first unblocked open one
One sub-issue per invocation — keeps sessions short and focused.
Flow
1. Select issue
If an orchestrator (e.g. /developer) already told you which sub-issue to implement, skip selection entirely — verify the issue is open and go to step 2. The checks below are for interactive use, where the given ref may be a parent. Enumerate its children per the tracker doc — GitHub default:
gh api graphql -f query='
{
repository(owner:"OWNER", name:"REPO") {
issue(number: ISSUE_NUM) {
subIssues(first: 50) {
pageInfo { hasNextPage }
nodes { number title state }
}
}
}
}' --jq '.data.repository.issue.subIssues'
If hasNextPage is true, stop and report: a parent with more than 50
children should be split, not worked through — and picking from a truncated
list would silently ignore the rest.
If sub-issues exist, pick the first unblocked one. Blockers may be wired as the tracker's native dependency links, as a "Blocked by" section in the sub-issue body, or both (/to-tickets prefers native edges where the tracker has them) — check both, per the tracker doc's blocker-state operation. GitHub default:
gh api repos/OWNER/REPO/issues/<N> --jq '.issue_dependencies_summary.blocked_by // 0' # open native blockers; 0 = clear
gh issue view <BLOCKER> --json state --jq '.state' # each body-listed blocker must be "CLOSED"
Pick the first open sub-issue where all blockers are closed. If none are unblocked, report to user and stop.
If no sub-issues exist, implement the issue directly.
If no ref given, list open issues carrying the AFK-ready triage label per the tracker doc — GitHub default:
gh issue list --state open --label "ready-for-agent" --json number,title,labels \
--jq '.[] | "#\(.number) \(.title)"'
(ready-for-agent is the triage vocabulary from docs/agents/triage-labels.md; use the repo's mapping if it differs.)
Priority order: bugs > tracer bullets > polish > refactors. Pick highest-priority unblocked issue, or ask user to confirm.
2. Read spec
Read the issue with its comments per the tracker doc — GitHub default:
gh issue view <N> --comments
Read the full body, acceptance criteria, and all comments.
The issue is the spec — the parent is the fallback. A well-formed ticket
carries a ## Spec extract section with the parent's Implementation and
Testing Decisions that apply to it, copied verbatim (the tracker doc requires
it of /to-tickets). When that section is there, build from it and do not
read the parent: the rest of the parent's body is decisions for other
tickets, and it competes for context with the code you still have to explore.
Read the full parent spec only when the section is missing (an older
ticket, or one written by hand) — then pull it per the ## Parent section in
the issue body, and treat that as the exception it is.
3. Create branch
# slug = issue title lowercased, spaces→dashes, max 50 chars
# <N> = the issue ref, slugified if it isn't a plain number
git fetch origin main
git checkout -b agent/issue-<N>-<slug> origin/main
(On a local code host there is no origin — branch from local main
instead: git checkout -b agent/issue-<N>-<slug> main. The code-host doc
names the base.)
Never git checkout main — when running in a linked worktree (the /developer
pipeline always does), main is checked out in the primary worktree and the
command fails. Branching straight from origin/main works everywhere.
As a /developer worker, confirm you really are in a linked worktree before
branching: git rev-parse --path-format=absolute --git-dir --git-common-dir
prints two different paths there. The same path twice means you escaped into
the user's primary checkout — stop and report blocked instead of branching
there. (Interactive use in the primary checkout is fine.)
Keep --path-format=absolute: without it git prints whichever form is
shortest from your cwd, so from a subdirectory of the primary checkout you get
/abs/path/.git and ../.git — two different strings for the same repo, and
the check silently clears you to touch the user's checkout.
Branch before you explore. A linked worktree is created from the local
main, which can lag origin/main — source read before this step may be
missing already-merged work and send you down a stale path.
In a fresh worktree, right after branching:
- Install dependencies (
pnpm install --reporter=silentor the project's equivalent) — worktrees do not sharenode_modules, and missing deps produce misleading typecheck/test failures in packages you never touched. Install quietly: the log is hundreds of lines you will never read, and when the install fails the tail says why. Where the tool has no quiet flag, redirect to a file (> /tmp/install.log 2>&1) and read only that tail, only on failure. - Run any prerequisite build the project's agent docs call out (e.g. a shared
contract package the apps consume from
dist— checkAGENTS.md/CLAUDE.mdfor the exact command).
All file reads and edits use paths inside the worktree (relative to cwd) — never absolute paths into the primary checkout.
4. Implement
- Before grepping for prior art, check the repo's agent docs (
AGENTS.md/CLAUDE.mdand anything they link underdocs/agents/— e.g. pattern recipes naming golden files to copy). Only explore for what the docs don't already answer. - Explore relevant source files before writing any code
- Follow the parent spec's Implementation Decisions and Testing Decisions where present: build to the interfaces it fixes, write tests at the pre-agreed seams (external behaviour, not implementation details), and reuse the prior-art tests it names
- Use TDD where tests exist: write failing test → implement → pass (red → green). Leave refactor-level cleanups to the review phase — the reviewer flags them; don't overload the implementation session
- Keep change as small as possible — only what the issue requires
Read the repo's map first, then its index — and only then grep. Where the
agent docs prescribe a way to read code — a zone map, an index/outline command
(just outline <path>, ctags, whatever it is called) — that method is
binding, not advice: read the zone's map whole, and use the index command
to locate symbols instead of cat-ing a large file. Where the repo has none,
grep -n over definitions (^\s*\(pub \)\?\(fn\|class\|def\|struct\|func\|export\))
is your index. Then batch: several index or grep -n calls in one command
separated by echo ===, rather than one call per question — each call costs a
whole turn, and in a field build 60 chained one-question calls came to 7m40s of
a 18-minute build, against 2m30s for the same exploration done in batches.
Never truncate an index or silence its errors. | head -2 and
2>/dev/null on the command that tells you where everything is throw away the
one thing you asked for. If the index comes back empty, your invocation is
wrong — fix it (usually a missing path argument, or the wrong cwd) instead of
falling back to cat and grepping blind for the next seven minutes. That is
the exact shape that made the slow build slow.
Run the tests you are working on, not all of them. Each red → green loop runs only the affected test file, with the project's quietest reporter:
pnpm test <path/to/the.test.ts> --reporter=dot # or --silent, per the project
The full suite runs once, at the end of this step, after the last loop is green — together with the typecheck:
pnpm typecheck
pnpm test --reporter=dot
(See AGENTS.md / CLAUDE.md for this project's exact commands and its quiet
reporter.) The suite printed after every loop is what actually exhausts a
worker's context — far more than any source file — and it tells you nothing the
one file didn't. When a run comes back red, re-run just the failing file or
test name for its output; never the suite.
Where the project has no quiet reporter — cargo test, go test, most
build steps — keep the log out of your context but keep the verdict:
<the command> > /tmp/check.log 2>&1; echo "exit=$?"
grep -nEi 'error|FAILED|test result' /tmp/check.log | tail -30 # only when exit≠0
Never pipe the command into tail. The pipe throws the exit code away and
returns whatever printed last, which on a multi-step recipe is the next step's
output: a field build read a coverage table instead of its verdict and re-ran
the whole gate three more times, 2m32s to recover one bit it had already
computed. Once the gate is green it stays green — never re-run it to confirm.
Fix all failures before proceeding. If you cannot fix them, see Blocked below.
Read each file once. The other half of the same problem, and in a measured
build the larger half: 58% of that worker's tool output was re-reading source it
had already read, one test file seven times. So: read a file whole once,
and afterwards go back to it with grep -n '<symbol>' or sed -n '<from>,<to>p'
— never a second cat -n of the whole thing. Never re-read a file to confirm
your own edit; the harness echoes the edited region back to you and that echo is
the confirmation. And prefer one edit per coherent change to five edits on
consecutive lines, since every edit pays for that echo.
Then run the project's formatter and its lint gate, before you commit. The
suite passing is not the same as the change being publishable: a build once
pushed code that was green on 974 tests and red on cargo fmt --check, and it
cost a full review → fix → re-review cycle to put back a whitespace change the
formatter would have made in a second.
Find the commands where the project keeps them — AGENTS.md, CLAUDE.md, or
the runner it uses (justfile, Makefile, package.json scripts). Do not
reconstruct CI's list of checks from its workflow files: run the repo's own
recipe, whatever it is called.
Run the writing form first, then the checking one:
cargo fmt --all # or: biome check --write . / prettier -w / ruff format
just lint # or whatever the repo calls its lint gate
That order matters. A formatter's output is not an opinion to be reviewed — it is derivable from the source, so anything it can fix by itself must never reach a reviewer. A linter's findings are not derivable, so those you read and fix.
Where the repo installs a pre-push hook this is the same gate you would hit
at push time; running it here means you find it while you still have the
context to fix it cheaply. Never push past a hook with --no-verify.
5. Commit
Single commit, conventional format:
<type>(<scope>): <short description>
Implements #<N>: <issue title>
- <key decision 1>
- <key decision 2>
Wrap body lines at 100 characters — commitlint's conventional config rejects
longer lines (body-max-line-length).
6. Publish the change (push + open PR)
Publish a draft change per the code-host doc, linked to the issue for closing. GitHub default:
git push origin agent/issue-<N>-<slug>
gh pr create \
--draft \
--base main \
--title "<type>(<scope>): <short description>" \
--body "Closes #<N>
## What changed
<brief summary>
## Test plan
- [ ] <acceptance criterion 1>
- [ ] <acceptance criterion 2>
## Discoveries
<see below — omit the section when empty, the normal case>"
Whatever the host, the change body keeps this shape — Closes <ref>,
## What changed, ## Test plan, optional ## Discoveries — the
orchestrator's harvest depends on it. Use the issue's tracker ref in
Closes; whether that auto-closes anything is the code-host doc's call.
Discoveries is how hard-won knowledge outlives your context: an orchestrator harvests these sections across PRs and promotes what repeats into the repo's agent docs. List only things that meet both bars:
- no repo doc (
AGENTS.md/CLAUDE.md,docs/agents/,docs/stack-notes,CONTEXT.md) answered it, and - it actually cost you something — a failed approach, reverse-engineering a pattern from several files, or finding that a doc contradicts the code.
One line each, written for the next agent (name files/commands, not your journey). Everyday exploration does not qualify; most PRs should have no Discoveries section.
7. Done
Do not close the issue manually. If the code host auto-closes linked issues on merge (GitHub/GitLab with issues in the same repo — see the code-host doc), Closes #<N> handles it; otherwise closing after the merge belongs to whoever merges (the orchestrator under /developer, the human interactively). The parent issue stays open until all sub-issues are merged.
Blocked
If you cannot implement (missing context, unfixable failures, external dependency), comment on the issue per the tracker doc — GitHub default:
gh issue comment <N> --body "Blocked: <specific reason>. <what is needed to unblock>."
Do not close the issue. Stop and report. When running unattended, do not wait for an answer — the blocking comment plus your final report is the output.
Rules
- One sub-issue per invocation — always check for sub-issues before treating an issue as standalone
- Run the project's formatter (writing form) and lint gate before committing — a formatting finding in a review costs a whole fix cycle to undo work a formatter does in a second.
- Run each gate once and read its exit code (
> /tmp/check.log 2>&1; echo "exit=$?"), never| tail. A green gate is never re-run to confirm it. - Never bypass git hooks (
--no-verify,-n). If a pre-push check fails in a package your change didn't touch, first suspect missing installs in the worktree (pnpm install); if it is genuinely broken onorigin/main, report Blocked instead of pushing around the gate - No commented-out code or TODO comments in committed code
- Do not modify files unrelated to the issue
- Never close the issue manually — closing happens on merge (auto-close where the host supports it, otherwise by whoever merges)