Use when scoping a multi-step feature with production blast radius, sequencing risk, or cross-cutting refactor — before invoking writing-plans or any implementation tool — when the work is too large...
Upfront PR scoping is architectural work. It calibrates risk and review surface, decides what gets the careful eye of a reviewer, and shapes the order in which work meets reality. The right slice ships value; the wrong slice ships dead code. A good decomposition produces a small set of PRs that each land safely, deliver something on their own, and survive a future where the next PR never gets built.
Use this skill before invoking writing-plans (or Spec Kit /specify, or any other implementation tool) so the spec that goes in is already scoped to a single shippable unit.
Announce: "I'm using the decomposing-features-into-prs skill."
Triggers (any of these → use the skill):
Don't use this skill (single PR is right) when:
The default is single PR. Multi-PR is what you do when the work refuses to fit. Agents over-decompose far more often than they under-decompose, so the flowchart asks the "don't decompose" questions first.
digraph single_or_multi {
rankdir=LR;
spec [label="Feature spec\nin hand"];
uniform [label="Uniform/repetitive\nchange?", shape=diamond];
green [label="Isolated greenfield,\nno production exposure?", shape=diamond];
throw [label="Throwaway/\nprototype?", shape=diamond];
awkward [label="Would splits create\na worse intermediate\nstate than not splitting?", shape=diamond];
one [label="Single PR\n(use single-PR variant\nof the template)", shape=box, style=filled, fillcolor="#e8f5e8"];
many [label="Multi-PR\n(continue to step 5\nof the process)", shape=box, style=filled, fillcolor="#fff4e0"];
spec -> uniform;
uniform -> one [label="yes"];
uniform -> green [label="no"];
green -> one [label="yes"];
green -> throw [label="no"];
throw -> one [label="yes"];
throw -> awkward [label="no"];
awkward -> one [label="yes"];
awkward -> many [label="no"];
}
decomposition-template.md. Save to a project-appropriate location (typical: docs/decompositions/<feature>.md)./specify <paste>). Do not paraphrase, summarize, or merge with conversation context on the way in. Do not paste the Reviewer notes.A good slice ships an observable behavior end-to-end (schema + API + UI + test + flag if needed), scoped to one thing a user or operator can see. A bad slice ships a layer (all schema, then all API, then all UI) — each layer is dead code until the last one lands.
The trap: horizontal slices LOOK reasonable. "PR 1: add the table. PR 2: add the API. PR 3: add the UI." Each is small, each is independently mergeable, each has clean review boundaries. But until PR 3 ships, nothing works. If priorities shift after PR 1, you've shipped dead code with a passing test suite.
Example — OAuth login (Google) alongside existing email/password auth.
Horizontal (bad), 5 PRs:
oauth_providers table + Google row — no reader/auth/oauth/:provider/callback route + linking logic — no UIEach PR is "mergeable." None deliver value alone. The interface designed in PR 1 was justified by what reviewers wanted to see in isolation, not by a real second consumer — so when GitHub OAuth lands two quarters later, the interface gets reshaped anyway.
Vertical (good), 1-2 PRs:
Exception: when a horizontal layer is genuinely the unit of work — e.g., a pure schema expansion as part of an expand–migrate–contract sequence, where the next phase is days or weeks away — own that, call it out, and don't pretend it's vertical. But it's still ONE PR for the whole expansion phase, not a separate PR per table.
PR 1's Spec block, pasted verbatim into /specify — WHAT/WHY in business language, zero HOW.
Sign In With Google
## User Story (P1)
A user with a Google account can sign in to the app with one click via a "Sign in with Google" button alongside the existing email/password option. If the user already has an email/password account using the same Google email (verified by Google), the two identities link automatically and they sign in to the existing account. Otherwise a new account is created from the Google profile.
**Why this priority:** This is the entire feature's value. Anything less than end-to-end Google sign-in doesn't ship the feature.
**Independent Test:** A user clicks "Sign in with Google" on the login page, authenticates with Google, and is redirected back to the app signed in to a session indistinguishable from email/password sign-in. Verifiable end-to-end with one Google test account, no other PR required.
**Acceptance Scenarios:**
1. **Given** a user with no existing account, **When** they sign in with Google for the first time, **Then** a new account is created from their Google profile and they are signed in.
2. **Given** a user with an existing email/password account whose email matches a verified Google email, **When** they sign in with Google, **Then** the Google identity is linked to the existing account and they are signed in.
3. **Given** a user who previously linked Google, **When** they sign in with Google again, **Then** they are signed in to the same account.
4. **Given** the feature flag is off, **When** a user visits the login page, **Then** no "Sign in with Google" button is rendered and the Google callback path returns not-found.
## Edge Cases
- What happens when Google returns an unverified email? (Reject linking; do not silently bind to an existing account.)
- What happens if the user closes the Google consent screen mid-flow? (Return to the login page with no partial session created.)
- What happens if the Google identity is already linked to a different internal user? (Reject sign-in with a clear error; do not switch the linkage.)
## Functional Requirements
- **FR-001:** System MUST authenticate users via Google using standard OIDC with the user's email and profile.
- **FR-002:** Users MUST be able to link a Google identity to an existing email/password account when the Google-verified email matches.
- **FR-003:** System MUST create a new user account from the Google profile when no matching email exists.
- **FR-004:** System MUST NOT render the "Sign in with Google" UI or accept Google callbacks when the feature flag is off.
- **FR-005:** Sessions established via Google MUST be indistinguishable downstream from sessions established via email/password (same session shape, same expiry behavior, same downstream authorization).
## Key Entities
- **Identity provider configuration:** A configured external auth source (initially Google), with display name, enablement flag, and a reference to where credentials are stored.
- **External identity link:** Associates an external provider's verified user identifier with an internal user account. Unique per (provider, external user) pair.
## Success Criteria
- **SC-001:** A user can complete the Google sign-in flow end-to-end (click to landed) in under 10 seconds on the happy path.
- **SC-002:** Zero cross-account linkages during rollout — no user is ever signed in as a different user due to OAuth linking logic.
- **SC-003:** When the feature flag is off, zero Google-related UI is rendered and zero callback traffic is accepted (verifiable via metrics).
## Assumptions
- Google OAuth client credentials are provisioned out-of-band in the existing secret store before this PR ships.
- The existing session mechanism is reusable as-is.
- This PR is gated behind a server-side feature flag, off by default; flipping is a separate operational decision.
## Non-Goals
- Adding a second provider (e.g., GitHub) — see PR 2 sketch.
- Account unlinking, multi-identity management UI, or "log in with X" account-recovery flows.
- Changing the existing email/password flow.
- An admin UI for managing the identity-provider configuration (ops-managed for now).
Reviewer notes for this PR (not in the Spec block) would hold: the table schemas, the callback route pattern, JWKS verification details, dashboard URLs for the SC-### criteria, deploy ordering, rollback, and the 4-question check — all HOW.
For each candidate slice, answer all four:
If any answer is no, the slice is wrong. Merge it into a neighbor or re-cut it. Question 2 is the one agents misread most often — re-read it before answering.
Plan PR 1 and PR 2 fully. Sketch PR 3+ as named intentions only (title + one-line intent + optional rough size S/M/L for capacity planning). No Spec block. No acceptance criteria. No phase tables. No sprint allocation.
Why: detailed planning of PR 3+ assumes the architecture you assumed for PR 1 was right. You usually find out it wasn't while building PR 1. Detailed forward planning becomes sunk cost that biases you against course-correcting.
Exception — irreversible cutover steps: if a later PR is the genuinely irreversible step (PR 3 = setting NOT NULL after a backfill; PR 4 = removing the legacy API after dual-write), detail that PR too. The reason for sketching is the architecture will be wrong. The reason for detailing is this step is hard to take back. When both pressures apply to a later PR, detailing wins.
Under pressure to plan further (PM, reviewer, teammate): show sketched PR 3+ as named intentions, offer S/M/L sizing for capacity, explain that detail comes after PR 1 ships. Non-negotiable on the detailed part; rough sizing is fine. See the Failure Modes rows on "PM wants the whole plan" and "Detail PR 3 just enough" for the in-the-moment tells.
| Context | Default slicing |
|---|---|
| High blast radius (live customer data, payments, auth, billing) | Bias small. Each PR revertable in isolation. Slice by capability, not by layer. Feature-flag risky behavior. |
| Architectural commitments (canonical model, public API, schema choices hard to reverse) | Each commitment gets its own PR with explicit acceptance criteria. Don't fold commitments into feature PRs. |
| Greenfield / early MVP / no production users | Wider scopes fine. Tighten as real users approach. |
| Internal tooling / one-offs | Single PR usually right. Don't manufacture ceremony. |
| Throwaway / spike / prototype | Single PR. Often no decomposition doc needed unless you're keeping the work. |
Project-specific overrides (which systems are "high blast radius" for this codebase, deploy cadence, compliance gates) belong in the project's CLAUDE.md, not here.
The per-PR detail is split into two blocks. The Spec block is the paste-ready payload for Spec Kit's /specify (or any spec-driven tool); it describes WHAT and WHY in business language. The Reviewer notes hold operational HOW and slice-correctness checks for human review.
/specify) — WHAT/WHY onlyAligned with Spec Kit's spec-template.md structure so /specify produces high-quality output on first pass:
Given <state>, When <action>, Then <outcome> formatFR-001: System MUST... statements in business terms. Mark unknowns inline: [NEEDS CLARIFICATION: <question>]SC-001: measurable, technology-agnostic outcomes (user-facing AND operational, e.g. "operations team sees delivery rate within X seconds")/specify) — HOW onlyOperational detail Spec Kit's /specify doesn't want, but reviewers need to validate the slice:
Sketched PRs get only: title + one-line intent + optional S/M/L sizing. No Spec block.
The artifact this skill produces. Default location: docs/decompositions/<feature-name>.md (override per project convention). It captures:
Important: the Spec block for each PR must be SELF-CONTAINED. The implementation tool (/specify, writing-plans, etc.) reads only the Spec block — it cannot follow references to the Cross-Cutting Contracts section or elsewhere in the doc. If your Spec block says "see the Cross-Cutting Contracts section above," fix it: reproduce the relevant contract subset inline. The Cross-Cutting Contracts section exists for the human reviewer to see the contracts in one place; the per-PR Spec blocks are independent paste-payloads that happen to reproduce overlapping content. Tolerate the duplication.
Each detailed PR section contains TWO blocks:
/specify)" block — pure natural-language spec, ready to copy-paste into your implementation tool verbatim.Use the template at decomposition-template.md. Both multi-PR and single-PR variants are in there.
Once the decomposition doc is written:
/specify <paste> (Spec Kit), or hand to writing-plans, or whatever planning tool they use.The reason for strict zero-edit: rewriting the spec on the way into the tool degrades signal and creates two sources of truth. The decomposition doc IS the spec. The implementation tool reads the spec.
When ready to open PRs against the decomposition doc, see github-process.md for PR title and branch conventions, the PR description template (which quotes the Spec block verbatim), and the status lifecycle (planned → in-review → merged → shipped). Status updates flow back to the decomposition doc's PR Sequence Overview table and Re-evaluation Notes — only the shipped transition unblocks PR 2+ re-evaluation.
Process step 6a's dependency graph identifies PRs with no hard dependency on each other; the template's Parallel Work Opportunities table records the pairs. For mechanics, defer to superpowers:using-git-worktrees. Anti-pattern: never parallelize PR 1 with anything before PR 1 ships — PR 1 is the learning checkpoint; parallel work past it commits to assumptions you haven't validated.
Each row pairs an Excuse (what you might think) with a Reality that includes both a Tell (how to recognize you're rationalizing) and a Counter (what to do instead). Use the Tells in-flight — they're what catches the rationalization in the moment instead of after the decomposition has set.
Grouped by trigger type so you can jump to the relevant category under pressure.
| Excuse | Reality |
|---|---|
| "PM wants the whole plan up front" | Tell: you're about to write acceptance criteria for PR 5. Counter: show sketched PR 3+ as named intentions with rough S/M/L sizing for capacity, and explain that detailed planning of PR 3+ is sunk cost — it will change once PR 1 ships. Detailed-everything plans please the PM in the moment but cost more when they're discarded. |
| "PRs over N lines don't get proper review" → split to hit a line ceiling | Tell: you're slicing by line count instead of by behavior, and the seam between PR 1 and PR 2 cuts mid-feature. Counter: the shippable unit is the unit, not a line count. Reviewer fatigue is real, but the answer is structure/tests/walkthrough — not artificial cuts that produce vanity slices. If the real shippable unit is genuinely too large to review, ask "what structure or test framing would make this reviewable," not "where do I cut it." |
| "Phase the work into sprints up front so the PM can size it" | Tell: you've drawn a sprint-by-sprint allocation before deciding what's in PR 1. Counter: sprint allocation past PR 1-2 is sunk-cost planning. Estimate PR 1 + offer rough S/M/L for PRs 3+; re-estimate after PR 1 ships. |
| Excuse | Reality |
|---|---|
| "This PR forces the abstraction question on its own" / "isolated review of the interface" | Tell: PR 1 is an interface/registry/plugin system, PR 2 is the first concrete consumer. Counter: speculative abstraction. The abstraction is justified by a real second consumer, not by review aesthetics. Ship the first consumer's full vertical slice; generalize only when the second consumer exists. |
| "Add the column / abstraction now to avoid a future migration" | Tell: you're adding a field, table, or interface that no consumer in the current PR uses. Counter: speculative design. YAGNI. Add it when the consumer exists. The migration "saved" is hypothetical; the dead code is real. |
| "Each PR is independently mergeable" (read as "doesn't break anything") | Tell: your Q2 answer for some PR is "doesn't break anything" or "tests pass." Counter: mergeable ≠ shippable. Q2 asks delivers value alone, not fails to break things. Dead code with passing tests is still dead code. |
| "Auth-critical / production-critical so small PRs are justified" | Tell: you're slicing aggressively because the topic feels scary, even though the changes are additive and behind a flag. Counter: true for irreversible changes (schema migrations, hard cutover, public API removal). NOT true for additive validation, new behavior behind a flag, or anything revertable. Calibrate by what's actually irreversible, not by topic. (Compliance gating like SOC2/PCI is a separate axis — if the change-management process gates every auth touch, slice to fit the gate's review window.) |
| Excuse | Reality |
|---|---|
| "Telemetry / observability / runbook deserves its own PR" | Tell: you have a PR titled "Add metrics for X" or "Dashboards for Y" with no behavior change. Counter: almost never. Observability ships with the behavior it observes. A dashboard PR alone is ceremony. Observability is a field of the Per-PR Spec for high-blast PRs — it rides with the feature. |
| "Rollout config flip is its own PR" | Tell: you have three PRs labeled "Enable for 10%," "Enable for 50%," "Enable for 100%." Counter: for additive low-blast changes, the flip ships with the code. For high-blast progressive rollouts, the ramp protocol is ONE PR (with gating criteria + rollback triggers), not one PR per ramp step. |
| "Splitting expand–migrate–contract into one PR per step is the safe way" | Tell: you have separate PRs for "add nullable column," "backfill," and "create index" on the same set of tables. Counter: one PR per phase, not per step. The whole expansion (add column nullable + index + backfill, all 20 tables) is ONE PR. The contraction (set NOT NULL) is the next PR. Splitting further is horizontal slicing dressed as safety. |
| "PR 1 is done, I'll start PR 2 now" (after merge but before deploy) | Tell: you're opening PR 2's branch the same day PR 1 merged, before deploy + soak. Counter: "shipped" means merged + deployed + observed without regression — not just merged. Starting PR 2 before PR 1 is observed loses the entire ship-and-re-evaluate cycle. |
| Excuse | Reality |
|---|---|
| "PR 2 'delivers value alone' but I had to stretch to find it" | Tell: your Q2 answer reads "well, an operator could technically..." or "it enables future..." Counter: if Q2 felt strained or generous, you're rationalizing. Genuine operational value means an operator gains a new capability, not "this enables PR 3." When the honest answer is "it sets up the next PR," merge it into the PR that ships the user-visible behavior. |
| "I'll lightly reformat the spec on the way into the implementation tool" | Tell: you're about to paste the Spec block into /specify with edits, paraphrasing, or context merges. Counter: no. Strict zero-edit. If it needs reformatting, fix it in the decomposition doc and re-emit. Two sources of truth = two sources of bugs. |
If any of these are true, stop and re-decompose:
All of these mean: re-cut the slices using the 4-question test, ruthlessly merge or drop scaffolding-only PRs, and trim detailed planning past PR 2.
Decompose into multiple PRs when: production blast radius, sequencing risk, architectural commitment, cross-cutting refactor, validation needed mid-flight, multiple reviewer constituencies.
Single PR when: uniform/repetitive changes, isolated greenfield, throwaway, splits create worse intermediate states.
4-question test (every slice must pass all 4):
Detail PR 1 + PR 2. Sketch the rest as named intentions only. No Spec block, no acceptance criteria, no phase tables, no sprint allocation for PR 3+.
Per-PR Spec block includes: goal, non-goals, acceptance criteria, contracts, dependencies, test approach. (Rollback goes in Reviewer notes.)
Handoff: paste PR 1's fenced Spec block into your implementation tool verbatim. Never the Reviewer notes. No edits.