To Issues¶
Bucket: Engineering ·
Slash command: /zsl:to-issues ·
Source: skills/engineering/to-issues/SKILL.md
When this skill activates
Claude Code matches this skill against the trigger text below. You can also invoke it explicitly with the slash command.
Break a plan, spec, or PRD into independently-grabbable issues on the project issue tracker using tracer-bullet vertical slices. Use when user wants to convert a plan into issues, create implementation tickets, or break down work into issues.
Break a plan into independently-grabbable issues using vertical slices (tracer bullets).
The issue tracker and triage label vocabulary should have been provided to you — run /setup-zsl-superpowers if not.
Process¶
1. Gather context¶
Work from whatever is already in the conversation context. If the user passes an issue reference (issue number, URL, or path) as an argument, fetch it from the issue tracker and read its full body and comments.
Guard against a pull-request reference (GitHub trackers only). Resolve the reference the way
docs/agents/issue-tracker.mdprescribes for this repo's tracker — don't assume GitHub. The collision this guards against is GitHub-specific: GitHub shares one number space across issues and PRs, so a bare number (orgh issue view <N>) can resolve to a PR — often a closed/merged one — not a plan. Apply the guard only when the configured tracker is GitHub: if the fetched object's URL contains/pull/or its state isMERGED/CLOSED, stop and ask whether the user really meant that reference (a shipped release PR has nothing to break down). For a local-markdown tracker a number is a feature/issue path (no PR collision); for GitLab, issues and MRs have separate number spaces — neither needs this guard.
2. Explore the codebase (optional)¶
If you have not already explored the codebase, do so to understand the current state of the code. Issue titles and descriptions should use the project's domain glossary vocabulary, and respect ADRs in the area you're touching.
3. Draft vertical slices¶
Break the plan into tracer bullet issues. Each issue is a thin vertical slice that cuts through ALL integration layers end-to-end, NOT a horizontal slice of one layer.
Slices may be 'HITL' or 'AFK'. A HITL slice requires a manual action a coding agent physically cannot perform — clicking through a third-party console, rotating a real credential, obtaining external sign-off, running a one-off production migration by hand. It is not an architectural decision or a design review: those must be resolved upstream via /grill-with-docs and recorded as ADRs before slices are cut, so the work falls out maximally AFK. AFK slices can be implemented and merged without human interaction. Prefer AFK over HITL where possible — a [HITL] slice that's really a decision in disguise is a process leak, not a slice. HITL slices are cleared by /human-itl, not /tdd.
Title format¶
Each slice gets a title in the form [<TYPE>] <wave>[<letter>] — <description>:
- Type prefix:
[AFK]or[HITL]. - Wave number: dependency depth, starting at 1. Slices sharing a wave number are runnable in parallel (same fan-out batch that
/tdd-parallelwould pick up). - Letter suffix: when a wave has more than one slice, assign
a,b,c... in the order slices were drafted. Single-slice waves stay unlettered. - Em dash separator:
—between the wave token and the description. - Description: short and action-oriented.
Examples:
[HITL] 1 — Register the OAuth app in the provider console and capture client id/secret[AFK] 2a — Add OAuth callback endpoint[AFK] 2b — Render login button[AFK] 3 — Wire callback to session store[AFK] 4 — Show user profile after login
4. Quiz the user¶
Present the proposed breakdown as a numbered list. For each slice, show:
- Title: as drafted in step 3 (with the
[TYPE] wave[letter] — descriptionformat) - Type: HITL / AFK
- Blocked by: which other slices (if any) must complete first
- User stories covered: which user stories this addresses (if the source material has them). Whatever the user approves here is persisted verbatim into the issue body's
## User stories coveredsection in step 5 — it is not just a quiz aid;/verify-coveragereads it back as its Tier A oracle. Each covered story carries its parent PRD'sAC<n>:acceptance criteria verbatim so the slice body is self-contained: the agent picking up the slice sees the story's testable contract without needing to re-fetch the PRD, and/verify-coverageTier B has those acceptance criteria as its test-generation hint at slice-resolution time.
Ask the user:
- Does the granularity feel right? (too coarse / too fine)
- Are the dependency relationships correct?
- Should any slices be merged or split further?
- Are the correct slices marked as HITL and AFK? Is every
[HITL]slice a genuine manual action, not a disguised decision (which belongs upstream in/grill-with-docs+ an ADR)?
Iterate until the user approves the breakdown.
5. Publish the issues to the issue tracker¶
For each approved slice, publish a new issue to the issue tracker. Use the issue body template below. Apply both the needs-triage triage label (so each issue enters the normal triage flow) and the backlog label (so each issue shows up on the project board).
Publish issues in dependency order (blockers first) so you can reference real issue identifiers in the "Blocked by" field.
Parent¶
A reference to the parent issue on the issue tracker (if the source was an existing issue, otherwise omit this section).
What to build¶
A concise description of this vertical slice. Describe the end-to-end behavior, not layer-by-layer implementation.
Acceptance criteria¶
- [ ] Criterion 1
- [ ] Criterion 2
- [ ] Criterion 3
User stories covered¶
The PRD user story numbers this slice addresses, each with its short
text and the parent's AC<n>: acceptance criteria carried over
verbatim. The persisted form of the quiz
mapping from step 4.
This section is MANDATORY on every PRD-derived slice (the only omission is a freeform plan with no user stories — see below). It is load-bearing in two places that both read it back from the issue body, not from the quiz:
/verify-coverageconsumes it as its Tier A story→slice map, and Tier B reads theAC<n>:acceptance criteria as the test-generation hint./tdd-parallelpartial runs read it to tell a deferred story (covered only by a slice still gated behind an open[HITL]) apart from a real gap. A slicing run that silently drops this section breaks partial-run coverage scoping: deferred stories get misclassified as gaps and spuriously re-fanned-out. (Legacy slices that predate this section fall back to/verify-coverage's lower-confidence inference — don't rely on it for new slices.)
Example:
- 7 — User can reset password via email
- AC1: POST /password-reset with a valid email enqueues a job that
sends a single email containing a one-time link.
- AC2: the link redeems exactly once and sets a new password; a
second redemption of the same link returns 410.
Write None — enabling/infrastructure slice for a slice that delivers
no user-facing story on its own. Omit this whole section only when the
source had no user stories (a freeform plan), the same way ## Parent
is omitted when there's no parent.
Blocked by¶
- A reference to the blocking ticket (if any)
Or "None - can start immediately" if no blockers.
6. Link each new issue as a sub-issue of the parent¶
If a parent issue exists and the tracker supports sub-issues, link each child to the parent so the parent auto-closes when all children close.
- GitHub: use the
addSubIssueGraphQL mutation. Fetch parent and child node IDs first, then link (replaceOWNER,REPO,PARENT,CHILD):PARENT_ID=$(gh api graphql -f query='query{repository(owner:"OWNER",name:"REPO"){issue(number:PARENT){id}}}' -q .data.repository.issue.id) CHILD_ID=$(gh api graphql -f query='query{repository(owner:"OWNER",name:"REPO"){issue(number:CHILD){id}}}' -q .data.repository.issue.id) gh api graphql -f query='mutation($p:ID!,$c:ID!){addSubIssue(input:{issueId:$p,subIssueId:$c}){subIssue{number}}}' -f p="$PARENT_ID" -f c="$CHILD_ID" - Linear: set
parentIdon each child when creating it. - GitLab / local files / unsupported trackers: skip; the
## Parenttext reference is the only link.
Do NOT close the parent or modify its body. The only allowed parent changes are adding sub-issue links and updating the state label as described in step 7.
7. Move the parent to tracking¶
If the source was an existing parent issue (i.e. the new issues were linked as sub-issues in step 6), update the parent's state label to tracking (use the configured label string from docs/agents/triage-labels.md). Remove any prior state label (needs-triage, ready-for-agent, etc.) — the parent is no longer a unit of work, it's a container.
GitHub will auto-close the parent when the last child closes. Do not close the parent yourself.
If docs/agents/project-board.md exists, also update the parent's project item Status to the option mapped to tracking (typically In progress). Use the same lookup-then-update procedure documented in triage/SKILL.md step 6: fetch the project item via gh api graphql filtered by the configured project node ID, then updateProjectV2ItemFieldValue with the mapped Status option ID. If the parent isn't on the configured project, log and continue. Best-effort — the label change is the source of truth.
Children created in step 5 are auto-added to the project (the user's existing "Auto-add to project" workflow handles that) and start in the project's default Status (Backlog); /triage will advance them to Ready later.
Skip this step if there's no parent issue (e.g. the source was a freeform plan in conversation).