devpilot-prd-to-issues
PRD → GitHub Issues
Overview
A PRD is one document. Engineering needs a graph of small, mergeable, individually-shippable issues. This skill decomposes a PRD into that graph: every issue is a deliverable slice (mergeable on its own, demonstrable to a stakeholder), every issue is a reasonable change size (≤ ~400 LOC diff or ≤ 2 dev-days), and every issue declares its relationships to others (epic / parent / blocks / blocked-by / related).
Core principle: an issue that ships nothing on its own, or that bundles unrelated work, is the wrong unit. Split until each ticket is independently mergeable; group with relationships, not scope creep.
When NOT to Use
- The PRD is one paragraph and the work is one PR → just open one issue, don't decompose.
- Bug triage from a scan → use
devpilot-scanning-repos(already issue-shaped) ordevpilot-issue-triage. - Roadmap-level epics with no spec yet → use
devpilot-pm/devpilot-product-researchfirst; don't manufacture sub-tickets from vapor. - Project tracker is Linear / Jira / Trello, not GitHub → adapt the relationship model but use the appropriate tool (
devpilot-trellofor Trello).
Workflow
-
Ingest the PRD. Read the source (file, URL, pasted text). Extract: goal, user-visible outcomes, non-goals, constraints (deadlines, dependencies, platforms), and any acceptance criteria already written. If goal or outcomes are missing, stop and ask — do not invent scope.
-
Confirm target repo and conventions.
gh repo view --json nameWithOwner. Snapshot existing labels:gh label list --limit 200 --json name,description. Check for an existing epic / parent-issue convention (e.g.epic,tracking, task-list checkboxes in a parent issue, GitHub sub-issues, or Projects v2 hierarchy). Reuse what the repo already does — do not introduce a new relationship model. -
Draft the work breakdown (no issues filed yet). Produce a single markdown plan in the conversation with this shape:
Epic: <PRD title> ├── #A1 <slice> [parent: epic] [blocks: A2, A3] ├── #A2 <slice> [parent: epic] [blocked-by: A1] ├── #A3 <slice> [parent: epic] [blocked-by: A1] [related: B1] └── #B1 <slice> [parent: epic]Each node has: provisional ID, one-line title, est. size (S/M/L = ½ day / 1 day / 2 days), and explicit relationships. Show this to the user and get explicit "go" before filing anything. Iterating on paper is cheap; iterating on filed issues is not.
-
Apply the size-and-shape rules to every node. See "Slice rules" below. If any ticket fails the rules, split or merge before filing. Re-show the tree if it changed materially.
-
File issues bottom-up. Create leaf issues first so parents can reference real numbers in their task lists. For each issue use the body template below, the canonical title format, and the canonical label set (see "Title & label conventions"). Reconcile against the step-2 label snapshot — reuse exact matches, rename non-canonical semantic matches via
gh label edit, only create labels that have no match. Do not file any issue that is missing the title prefix or any of the four required labels. -
Wire relationships using the repo's convention. In order of preference: (a) GitHub native sub-issues (
gh api … /sub_issues) if the repo uses them; (b) task-list checkboxes in the epic (- [ ] #123) which GitHub auto-tracks; (c)Blocks:/Blocked by:/Parent:/Related:lines in each issue body, with#Ncross-links. Pick one and apply it consistently across the whole tree. -
Post the summary. Print a compact table to the user:
ID | title | size | parent | blocks | blocked-by | URL. Confirm the epic's task list renders correctly (open it ingh browse) before declaring done.
Title & label conventions
Every issue filed by this skill MUST use the same title shape and the same four-label spine, so the whole tree is filterable in one query and humans can read the relationship at a glance.
Title format
[<feature-slug>] <type>: <imperative one-liner>
<feature-slug>— short kebab-case identifier for the PRD, identical across every issue in the tree (e.g.oauth-rotation,mobile-onboarding). Pick once in step 3, reuse everywhere. This is what makesis:issue [oauth-rotation]return the whole tree.<type>— one of:epic,feat,chore,infra,docs,test,spike. Exactly one. The epic issue usesepic; everything else picks the most accurate non-epic type.<imperative one-liner>— ≤ 70 chars, starts with a verb (Add,Wire,Migrate,Expose), no trailing period, no ticket numbers, no emoji.
Examples:
[oauth-rotation] epic: Rotate OAuth refresh tokens daily[oauth-rotation] feat: Add token-rotation worker behind flag[oauth-rotation] chore: Backfill rotation_at column
If a node can't be expressed in this format, the slice is probably wrong-shaped — fix the slice before fighting the title.
Label spine (exactly four, every issue)
| Slot | Allowed values | Purpose |
|---|---|---|
feature:<slug> |
one per PRD, matches the title slug | Groups the whole tree |
type:<kind> |
type:epic, type:feat, type:chore, type:infra, type:docs, type:test, type:spike |
Mirrors the title <type> |
size:<bucket> |
size:S, size:M, size:L |
≈ ½d / 1d / 2d; matches the body's Size field |
area:<top-level-dir-or-surface> |
e.g. area:api, area:web, area:cli |
Routes to the right owner |
Optional fifth labels (apply only when meaningful, never as filler):
priority:<p0|p1|p2>— apply ONLY when the PRD or the user explicitly states a priority for that slice. Do not invent a default. If unsure, omit the label.blocked— set automatically when the issue has openBlocked byissues; clear when they close.good-first-issue— only when the slice is genuinely standalone, well-scoped, and a newcomer could pick it up.
Repo-specific meta labels that are orthogonal to the spine (e.g. tracked-by-roadmap, needs-design, tech-debt) MAY be applied on top when they apply, but are never substitutes for any of the four spine slots.
Reconciliation rule (same as devpilot-scanning-repos):
- Exact match in the repo → reuse as-is.
- Semantic match with a non-canonical name (e.g. repo has
featurefortype:feat, orservice/apiforarea:api) →gh label edit "<old>" --name "<canonical>" --description "<canonical-desc>". Renaming preserves all existing issue associations. - Too generic / wrong intent / orthogonal to this PRD (e.g.
bugfor a feature PRD,tech-debt,needs-design,tracked-by-roadmap) → leave alone, do not force a mapping. Create the canonical label fresh if the spine slot needs filling. - No canonical bucket exists (e.g. repo has
XS/XLsize labels but the spine only definessize:S|M|L) → leave alone. Do NOT rename to the closest bucket — that lies about the size budget on existing issues. New tickets from this PRD use the canonical S/M/L; legacy XS/XL remain on legacy issues. - Layer vs. surface for
area:*: prefer surface labels (area:api,area:web,area:ios,area:cli,area:worker) that route to an owner. A repo label likefrontend/backendis borderline — rename only when it consistently maps to one surface in this repo; if it's a cross-surface layer label, leave alone and create the surface label fresh.
Confirmation gate: if the reconciliation plan would rename more than 3 labels, OR rename any label currently used on more than 50 issues, show the plan to the user and get explicit "go" before running any gh label edit. Bulk renames touch unrelated existing issues' triage queries.
Side-effect warning: renaming a generic label (e.g. epic → type:epic) retroactively co-tags every issue currently carrying it. Call this out in the reconciliation summary: epic → type:epic (also tags 5 pre-existing epic issues) so the user can veto before the rename runs.
Print a reconciliation summary before filing: N reused, R renamed (old → new, with affected-issue counts), M created, K left alone (with reason).
Title ⇔ label invariants
These must hold for every issue, on every run. Verify before declaring done:
- The
<feature-slug>in the title equals the value of thefeature:<slug>label. - The
<type>in the title equals the value of thetype:<kind>label. - The Size field in the body equals the
size:<bucket>label. - Exactly one issue in this PRD's tree has
type:epicand noParent:line. (The repo may have othertype:epicissues from prior PRDs — that's fine; the invariant is per-tree, not per-repo.)
If any invariant fails, the title and labels are out of sync — fix before continuing. Asymmetric metadata is the same bug class as asymmetric Blocks / Blocked by.
Slice rules
Every leaf issue MUST satisfy all four:
| Rule | What it means | If it fails |
|---|---|---|
| Deliverable | Merging this issue alone leaves main better than before — feature-flagged if needed, but not a half-wired stub that breaks build or UX. |
Either combine with the issue that completes it, or add a flag/scaffold so this one ships meaningfully. |
| Bounded | ≤ ~400 lines diff OR ≤ 2 dev-days. Hard ceiling: 800 LOC / 3 days. Tests count toward the budget; generated code does not. | Split along a natural seam: data-model vs. handler vs. UI; happy-path vs. error-paths; one entity at a time. |
| Independently testable | Has its own acceptance criteria the reviewer can verify without merging siblings. | Pull the verification surface (test fixtures, a CLI flag, a stub UI page) into this ticket. |
| Single-owner-friendly | One engineer can pick it up without coordinating mid-flight with another open ticket in the tree. | If two tickets must land in the same PR to be useful, they're one ticket. Merge them. |
If a node can't be made deliverable on its own, the right move is usually a scaffolding issue first (introduce the interface / flag / migration) so subsequent slices have a place to land.
Relationship taxonomy
Every issue body declares relationships explicitly. Use exactly these four kinds — don't invent more:
- Parent — the epic / tracking issue this rolls up to. Exactly one.
- Blocks — issues that cannot start (or cannot ship) until this one merges.
- Blocked by — the inverse; issues this one waits on. Symmetric with
Blocks:on the other side. - Related — useful context, not a dependency. Use sparingly; if everything is "related" the field is noise.
Every Blocks on issue A MUST appear as Blocked by on the named issue. Asymmetric edges are bugs in the breakdown — fix before filing.
Issue body template
## Context
<2–4 sentences: where this fits in the PRD, what problem this slice solves. Link the PRD.>
## Scope
- <bullet 1, concrete>
- <bullet 2, concrete>
- <bullet 3, concrete>
## Out of scope
- <thing a reviewer might assume but we are NOT doing here; usually points to a sibling issue>
## Acceptance criteria
- [ ] <observable behaviour 1>
- [ ] <observable behaviour 2>
- [ ] <test / verification step>
## Size
**S | M | L** (≈ ½d / 1d / 2d, ≤ ~400 LOC diff)
## Relationships
- **Parent:** #<epic>
- **Blocks:** #<id>, #<id>
- **Blocked by:** #<id>
- **Related:** #<id>
## PRD reference
<link or path, plus the section heading this slice maps to>
The epic issue gets the same template plus a top-level task list of every child:
## Children
- [ ] #A1 …
- [ ] #A2 …
GitHub auto-renders this list with checkboxes that flip when the child closes — that's the cheapest progress dashboard available. Use it.
Quick reference
| Situation | Move |
|---|---|
| Slice is 1500 LOC | Split along data-model / handler / UI seam |
| Slice "has to ship with" the next one | They're one slice — merge them |
| Slice can't be merged without breaking main | Add a feature flag, OR push the user-visible part to a later ticket |
| Two slices both need the same new helper | Extract a scaffolding ticket; both depend on it |
| PRD has 30 leaf items | Group into 3–5 chapters under the epic; chapters are intermediate parents |
| Mixed repos (frontend + backend) | One epic per repo, link them via Related: across the boundary |
Common mistakes
- Filing issues before the user signs off on the tree. Iterating on a markdown plan is free. Iterating on 14 already-filed issues with cross-links is not.
- Inventing a relationship model the repo doesn't use. If the repo uses task-list checkboxes, don't introduce sub-issues; if it uses sub-issues, don't fall back to plaintext
Parent:lines. Pick what's already there. - "Phase 1 / Phase 2" tickets that bundle unrelated work. A phase isn't a slice. Each phase decomposes into its own real tickets.
- Bulk-renaming high-volume labels without confirmation. Renaming
S/M/Lwhen ~80 existing issues carry them, orservice/apiwhen ~200 do, mass-mutates triage queries the team relies on. Hit the >3-renames OR >50-affected-issues threshold → confirm first. - Forcing every existing label into the spine. Orthogonal labels (
bugon a feature PRD,tracked-by-roadmap,needs-design) get left alone, not bent into atype:*they don't fit. - Renaming
XS/XLtosize:S/size:L. The spine has no XS/XL bucket and renaming lies about size budget. Leave them alone. - Inventing
priority:*defaults. Priority labels appear only when the PRD or user states one. "I think this is p1" is filler. - Inconsistent titles or labels across the tree. Half the issues say
[oauth-rotation], half sayoauth rotation —; some carrytype:feat, some don't. The whole point of the convention is one filter returns the whole tree. If you wouldn't bet $20 onis:issue label:feature:<slug>returning every child, the run failed. - Asymmetric relationships.
A blocks Bon A's body but nothing on B's body. Reviewers can't see the dependency from B. Always wire both sides. - Tickets with no acceptance criteria ("implement auth"). The reviewer has nothing to verify against. If the PRD doesn't give criteria for a slice, write them and confirm with the user before filing.
- Renaming the PRD as the epic title and shoving everything under one ticket. That's not decomposition; that's a rebrand. The epic exists to track the children, not to contain the work.
- Breaking research / spike work into the same tree. Spikes don't ship. File them separately or close them when their output (a doc, a decision) lands; don't put them in the deliverable graph.
- Skipping the dedupe step against existing open issues. Before filing,
gh issue list --search '<keywords>' --state openand reuse / link to anything that already covers a slice.
Acceptance criteria for a correct run
A correct invocation of this skill produces:
- A markdown work-breakdown shown to the user before any issue is filed, and explicit user confirmation captured.
- Exactly one epic / tracking issue, with a task list of every child issue that GitHub auto-tracks.
- Every child issue: ≤ ~400 LOC estimated diff, deliverable on its own (possibly behind a flag), with its own acceptance criteria.
- Every relationship is symmetric —
Blockson one side ⇔Blocked byon the other. - Every issue title matches
[<feature-slug>] <type>: <imperative one-liner>with the same<feature-slug>across the whole tree. - Every issue carries the four-label spine:
feature:<slug>,type:<kind>,size:<bucket>,area:<surface>. Title<type>and<feature-slug>agree with the matching labels. Existing repo labels reused or renamed to canonical; no orphan label families introduced. - A final summary table printed: ID, title, size, parent, blocks, blocked-by, URL.
If any of these is missing, the skill failed — stop and correct before continuing.
More from siyuqian/devpilot
devpilot-scanning-repos
Use when the user asks to scan, audit, or sweep an entire GitHub repository for issues and file them as tickets — "scan this repo", "audit the codebase", "find bugs/security holes/missing tests", "check the docs are still accurate", "/repo-scan", "open issues for all the problems you find". Scans security, edge cases, testing coverage, and doc/code drift (CLAUDE.md, AGENTS.md, README.md and the docs they link to) without assuming business logic. Do NOT use for reviewing a single PR (use devpilot-pr-review) or language-specific style review (use devpilot-google-go-style).
9devpilot-resolve-issues
Use when the user wants to resolve, fix, work through, or burn down open GitHub issues in a repository — "fix all the issues", "resolve these tickets", "work through the repo-scan issues", "clear the backlog", "fix issue #42", "/resolve-issues", "解决所有issue", "修复这些issue", "处理issue backlog". Runs as a loop over matching issues until none remain; each REAL issue is fixed in its own `git worktree` so the main checkout stays untouched. Do NOT use for creating issues (use devpilot-scanning-repos) or for reviewing a PR the user already has open (use devpilot-pr-review).
9devpilot-pr-creator
>
8devpilot-content-creator
SEO-optimized blog and content writing skill. Use this skill whenever the user wants to write a blog post, create content for their website, improve SEO rankings, do keyword research, or plan content strategy. Triggers on any mention of blog writing, SEO content, keyword research, content marketing, "写博客", "写文章", "内容创作", "SEO优化", or when the user wants to create any form of long-form content for a website or product. Even if the user just says "write something about X for our site", use this skill.
3devpilot-google-go-style
>
2devpilot-prompt-review
Review and improve prompts for Claude (system prompts, CLAUDE.md files, SKILL.md files, API prompts, or any LLM instruction text). Use this skill whenever the user wants to review a prompt, improve prompt quality, check prompt best practices, audit instructions for Claude, optimize a system prompt, or asks "is this prompt good?". Also trigger when the user shares a prompt and asks for feedback, or when they mention prompt engineering, prompt optimization, or prompt hygiene. Even if they just paste a prompt and say "thoughts?" — use this skill.
2