hdb:split-pr
Decompose an oversize branch into multiple focused, non-overlapping PRs with correct dependency ordering and detailed recovery tasks.
Usage
/hdb:split-pr [--base <branch>] [--dry-run]
IMPORTANT
This skill produces two artifacts: (1) a partition plan mapping every change to exactly one PR, and (2) a detailed task list per PR that captures specific changes from the original branch. The goal is maximum recovery of past work with no overlap between PRs.
Description
When a branch accumulates too many changes for a single reviewable PR (rough threshold: >15 files changed, >500 net insertions, or >3 distinct themes), this skill decomposes it into multiple smaller PRs. Each PR is coherent (single theme), self-contained (passes tests independently), and non-overlapping (no file change appears in two PRs). The PRs are ordered so dependencies are satisfied: PR A merges before PR B if B depends on changes from A.
After partitioning, the skill generates detailed tasks for each PR — each task references specific files, original commits, and code from the source branch so the implementer can recover the exact changes efficiently.
Instructions
When the user invokes /hdb:split-pr [--base <branch>] [--dry-run]:
Phase 1: Inventory
Catalog everything on the branch before making any decisions.
Determine the base branch. If --base is provided, use it. Otherwise detect:
git merge-base master HEAD
If neither master nor main exists, ask the user to specify the base.
List all commits on the branch since divergence:
git log --oneline --reverse $(git merge-base <base> HEAD)..HEAD
Record the count. If there are fewer than 5 commits and fewer than 10 files changed, inform the user the branch is small enough for a single PR and stop unless they insist.
List all changed files with diff stats:
git diff --stat <base>..HEAD
git diff --name-only <base>..HEAD | sort
Read every commit message and categorize each commit by its logical theme. Use the commit message prefix (e.g., security:, feat:, fix:, docs:, test:, refactor:, chore:) as the primary signal. When the prefix is ambiguous, read the commit's diff:
git show --stat <sha>
Build the theme inventory table. Present it to the user for review:
| Theme |
Commits |
Files |
Net Lines |
Description |
| rate-limiting |
8 |
5 |
+320 |
In-memory and Redis rate limiter |
| webhook-security |
6 |
4 |
+180 |
HMAC verification, payload limits |
| ... |
... |
... |
... |
... |
| TOTAL |
62 |
43 |
+1913 |
|
Identify cross-cutting files — files touched by commits from multiple themes:
# For each file, list which themes' commits touch it
git log --format="%h %s" <base>..HEAD -- <file>
Present cross-cutting files in a separate table:
| File |
Themes |
Decision Needed |
backend/app/core/config.py |
rate-limiting, webhook-security |
Which PR owns the config changes? |
backend/app/main.py |
rate-limiting, auth-refactor |
Middleware registration spans two features |
Wait for user confirmation of the theme groupings before proceeding. The user may want to merge themes, split themes, or rename them.
Phase 2: Dependency Analysis
Determine the merge order.
For each theme group, identify dependencies on other groups. A theme depends on another if:
- It imports or calls functions introduced by the other theme
- It modifies files whose baseline state requires the other theme's changes
- Its tests rely on fixtures or utilities introduced by the other theme
- It extends a database migration introduced by the other theme
Check dependencies by reading the diffs:
git diff <base>..HEAD -- <file>
Build the dependency DAG. Present it as a table and an ASCII diagram:
| PR |
Theme |
Depends On |
Merge Order |
| 1 |
auth-refactor |
(none) |
First |
| 2 |
rate-limiting |
auth-refactor |
Second |
| 3 |
webhook-security |
auth-refactor |
Second (parallel with 2) |
| 4 |
docs |
rate-limiting, webhook-security |
Third |
1:auth-refactor
├── 2:rate-limiting
├── 3:webhook-security
└── 4:docs (after 2+3)
Detect cycles. If the dependency graph has a cycle, the themes cannot be cleanly separated. Options:
- Merge the cyclic themes into a single PR
- Extract shared infrastructure into a new "foundation" PR that breaks the cycle
Present the cycle to the user and recommend a resolution.
Wait for user confirmation of the dependency ordering before proceeding.
Phase 3: Partition
Assign every file change to exactly one PR.
Assign unambiguous files first. Files touched by only one theme go into that theme's PR. This handles the majority of files.
Resolve cross-cutting files. For each cross-cutting file, choose one strategy:
| Strategy |
When to Use |
How |
| Assign to earliest PR |
The file has a clear "foundation" change that later PRs build on |
Put all changes in the earliest PR in the dependency chain |
| Split by hunk |
The file has distinct, non-overlapping hunks for each theme |
Extract per-theme patches and partition hunks |
| Assign to infrastructure PR |
The file is shared config or wiring (e.g., main.py, config.py) |
Create a dedicated "infrastructure" PR or assign to the first PR |
| Sequential layering |
One theme adds a function, another modifies it |
Put the addition in the earlier PR; the modification in the later PR (which rebases on top) |
Handle special file categories:
| Category |
Strategy |
| Formatting/linting commits |
Assign each formatted file to whichever PR already owns that file. If a formatting commit touches files across multiple PRs, split it. |
| Test files |
Move test files with the feature they test. If a test file covers multiple features, assign it to the latest PR in the dependency chain or create a dedicated test PR. |
| Documentation |
Small doc changes travel with their feature. Large doc additions (new files, multi-file updates) go in a dedicated docs PR at the end of the chain. |
| Migration files |
Migrations travel with the schema change they implement. If two migrations exist, they go in separate PRs only if independent. Chain down_revision values accordingly. |
| Docker/CI/Makefile |
Infrastructure config goes in the earliest PR that needs it, or in a dedicated infrastructure PR. |
Build the partition table. Present it to the user:
| PR |
Branch Name |
Theme |
Files |
Cherry-Pick Candidates |
| 1 |
security/auth-refactor |
Auth renaming |
deps.py, admin_access.py, ... |
00102f8, f102b50 |
| 2 |
security/rate-limiting |
Rate limiting |
rate_limit.py, rate_limit_backend.py, ... |
603c653, 0d5f4bf, ... |
| ... |
... |
... |
... |
... |
Verify completeness. The union of all file lists must equal the original changed file list. No file may appear in two PRs:
Original files: 43
Assigned files: 43 (sum across all PRs)
Duplicates: 0
Unassigned: 0
If there are unassigned files or duplicates, resolve them before proceeding.
Wait for user confirmation of the partition before generating tasks.
Phase 4: Task Generation
For each PR, generate a detailed task list that captures the specific changes from the original branch.
For each PR, generate ordered tasks. Each task must have:
- Subject — imperative action (e.g., "Add Redis rate limiter module")
- Description — specific file paths, what to change, and references to original commits/diffs:
Create `backend/app/core/rate_limit.py` with:
- RateLimiter ABC with async `is_allowed(key: str) -> bool`
- InMemoryRateLimiter using sliding-window deque
- RedisRateLimiter using sorted-set pipeline
- Shared client factory `_get_async_redis(url)`
- `create_rate_limiter()` factory function
Reference: commits 603c653, 32f5453, 294f518 on chore-security-review
Recovery: `git show chore-security-review:backend/app/core/rate_limit.py`
- Acceptance criteria — how to verify the task is done:
- File exists and passes `mypy --strict`
- `uv run pytest tests/test_rate_limit.py` passes (15 tests)
Task ordering within each PR:
| Order |
Task Type |
Example |
| 1 |
Infrastructure / new modules |
Create rate_limit_backend.py enum |
| 2 |
Core implementation |
Create rate_limit.py with ABC + both backends |
| 3 |
Configuration |
Add settings to config.py |
| 4 |
Integration / wiring |
Wire rate limiter into agent_auth.py, board_webhooks.py |
| 5 |
Tests |
Create test_rate_limit.py with 15 tests |
| 6 |
Documentation |
Update security.md, api.md |
| 7 |
Linting / formatting |
Run ruff, black, isort and fix |
| 8 |
Commit and verify |
Run full test suite, commit |
Include recovery commands in each task so the implementer can pull exact code from the source branch:
# View the final state of a file on the source branch
git show <source-branch>:<file-path>
# View a specific commit's changes to a file
git show <sha> -- <file-path>
# Extract a file from the source branch into the working tree
git checkout <source-branch> -- <file-path>
# Generate a patch for specific files
git diff <base>..<source-branch> -- <file-path> > /tmp/partial.patch
Present the complete task list to the user, organized by PR:
## PR 1: security/auth-refactor (Auth Renaming)
Depends on: (none) | Base: master
1. Rename `require_admin_auth` → `require_user_auth` in deps.py
Recovery: git show chore-security-review:backend/app/api/deps.py
Acceptance: All 10 API modules import the new name; tests pass
2. Update all API modules to use renamed dependency
Files: activity.py, agents.py, approvals.py, ...
Recovery: git diff master..chore-security-review -- backend/app/api/activity.py
Acceptance: `rg require_admin_auth backend/` returns 0 matches
3. Run tests and commit
Acceptance: `uv run pytest` passes; commit message follows conventions
## PR 2: security/rate-limiting (Rate Limiting)
Depends on: PR 1 | Base: security/auth-refactor
1. Create rate_limit_backend.py enum
...
Phase 5: Extract (on user request)
Create the sub-PR branches. Work in dependency order (roots first, leaves last).
Decide extraction method per PR:
| Method |
When to Use |
Pros |
Cons |
| Cherry-pick |
Commits are clean and atomic; each commit belongs entirely to one PR |
Preserves history |
Fails when commits span multiple themes |
| File-level checkout |
Commits are mixed or squashed |
Clean result regardless of commit structure |
Loses original commit granularity |
In practice, most splits use file-level checkout because real branches rarely have perfectly atomic commits.
For each PR (in dependency order):
a. Create a new branch from the appropriate base:
# For the first PR (no dependencies):
git checkout -b <branch-name> <base>
# For PRs that depend on a prior PR:
git checkout -b <branch-name> <prior-pr-branch>
b. Apply the changes using the chosen method:
Cherry-pick method:
git cherry-pick <sha1> <sha2> <sha3>
File-level checkout method:
git checkout <source-branch> -- <file1> <file2> <file3>
git commit -m "<theme>: apply <description>"
Hunk-level extraction (for cross-cutting files):
git diff <base>..<source-branch> -- <file> > /tmp/partial.patch
# Edit the patch to keep only relevant hunks
git apply /tmp/partial.patch
c. Run tests to verify the branch works standalone:
cd backend && uv run pytest tests/ -x -q # Python
go test ./... # Go
cargo test # Rust
d. Run linters/formatters and fix any issues.
e. Verify the diff is limited to this PR's assigned files:
git diff --name-only <base-of-this-pr>..HEAD
If --dry-run was specified, stop after generating the partition table and task list. Do not create branches.
Phase 6: Verify
Confirm completeness and correctness after all branches are created.
Verify coverage. For each file in the original diff, confirm it appears in exactly one sub-PR:
for branch in <branch1> <branch2> ...; do
echo "=== $branch ==="
git diff --name-only <base>..$branch
done
Verify each branch passes tests independently.
Generate the final summary:
## Split Summary
Original branch: chore-security-review (62 commits, 43 files, +1913/-85)
Sub-PRs created: 5
| # | Branch | Theme | Files | Lines | Depends On | Tests | Tasks |
|---|--------|-------|-------|-------|------------|-------|-------|
| 1 | security/auth-refactor | Auth renaming | 12 | +120 | -- | PASS | 3 |
| 2 | security/rate-limiting | Rate limiting | 8 | +450 | #1 | PASS | 8 |
| 3 | security/webhook-hardening | Webhook security | 8 | +280 | #1 | PASS | 7 |
| 4 | security/docker-hardening | Docker non-root | 3 | +30 | -- | PASS | 2 |
| 5 | security/docs-and-tests | Docs + tests | 12 | +550 | #2, #3 | PASS | 5 |
Merge order: 1 and 4 (parallel) → 2 and 3 (parallel) → 5
Coverage: 43/43 files assigned. 0 duplicates. 0 unassigned.
Total tasks: 25
Phase 7: PR Creation (optional, on user request)
Create PRs in dependency order:
git checkout <branch>
git push -u origin <branch>
gh pr create --base <target-base> --title "<theme>: <concise description>" --body "$(cat <<'EOF'
## Summary
<1-3 bullet points>
## Context
PR N of M from the `<original-branch>` split.
**Merge order:** <where this fits>
**Depends on:** <dependency PR links, or "None">
## Test plan
- [ ] All existing tests pass
- [ ] <specific items>
Generated with [Claude Code](https://claude.com/claude-code)
EOF
)"
Link dependency PRs in each PR description so reviewers understand the merge order.
Decision Guide: Cross-Cutting Files
| File Type |
Resolution |
config.py / settings.py |
Put in the earliest PR that needs the config. Later PRs add their settings on top via rebase. |
main.py / app wiring |
Put in a dedicated infrastructure PR, or in the earliest PR. |
Makefile / docker-compose.yml |
Put in the PR whose feature the change supports. |
| Shared test fixtures |
Put in the earliest PR. Later PRs import from the fixture. |
__init__.py / re-exports |
Put in the PR that adds the module being re-exported. |
| Migration files |
Always travel with the schema change they implement. |
Common Pitfalls
1. Forgetting to rebase after merge
Later PRs were branched from earlier PR branches. After PR 1 merges into master, PR 2 must rebase:
git checkout security/rate-limiting
git rebase master
git push --force-with-lease
Failure to rebase causes the later PR to include the earlier PR's diff, confusing reviewers.
2. Tests that import across PR boundaries
If PR 2's tests import a helper added by PR 1, the tests fail when PR 2 is checked out before PR 1 merges. Solutions:
- Branch PR 2 from PR 1's branch (not from
master)
- Duplicate the helper in PR 2 (acceptable for small helpers)
- Move the helper to a shared utility in PR 1
3. Migration ordering across PRs
If PR 2 and PR 3 both add Alembic migrations, they both have the same down_revision. When both merge, this creates multiple heads. Solutions:
- Put all migrations in one PR
- Use
alembic merge heads after the second PR merges
- Chain the migrations: PR 3's migration depends on PR 2's
4. Formatting commits that touch everything
A single ruff format . commit touches every file. Do not put this in one PR. Instead:
- Re-run the formatter independently on each sub-PR branch
- Let each PR's files be formatted within that PR
5. Losing changes in the split
Always verify coverage (Phase 6, step 25). The most common cause: a file touched by two themes is assigned to only one PR, and the other theme's hunks are dropped.
6. Over-splitting
Not every theme needs its own PR. If two themes are small (< 3 files, < 50 lines each) and naturally related, merge them. The goal is reviewable PRs, not maximally granular PRs.
Size Guidelines
| Original Size |
Recommended Split |
| 5-15 files, < 500 lines |
Single PR (no split needed) |
| 15-30 files, 500-1000 lines |
2-3 PRs |
| 30-50 files, 1000-2000 lines |
3-5 PRs |
| 50+ files, 2000+ lines |
5-8 PRs (consider deferring some changes) |
Guidelines
- Get confirmation at each gate. Present the theme inventory, dependency DAG, and partition table to the user before proceeding. Backtracking after branch creation is expensive.
- Dependency order is non-negotiable. Every PR must pass tests independently when applied on top of its declared dependencies.
- Prefer file-level checkout over cherry-pick. Real branches rarely have perfectly atomic commits.
- Cross-cutting files go to the earliest PR. When in doubt, put shared files in the first PR in the dependency chain.
- Every task references recovery commands. The implementer should be able to pull exact code from the source branch for each task.
- Run tests on every sub-PR branch. Do not assume a clean partition means clean tests.
- Do not over-split. Fewer, larger PRs are better than many tiny PRs that create a long merge queue. Target 3-6 PRs.
- Document the merge order in every PR. Reviewers need to know "this is PR 2 of 5, depends on PR 1."
- Delete the original branch after all sub-PRs merge. It is dead weight after the split.
- Respect CLAUDE.md. The project's instructions override everything in this skill.
See Also
/hdb:design — design a feature with a PRD and task list (use before implementing, to prevent oversize branches)
/roborev:pull-request-reviewer — pre-review a PR before submitting
/hdb:alembic — diagnose and fix migration issues that arise during splits
1---2name: hdb-split-pr3description: Decompose an oversize branch into multiple focused PRs with dependency ordering and detailed task lists4---56# hdb:split-pr78Decompose an oversize branch into multiple focused, non-overlapping PRs with correct dependency ordering and detailed recovery tasks.910## Usage1112```13/hdb:split-pr [--base <branch>] [--dry-run]14```1516## IMPORTANT1718This skill produces two artifacts: (1) a **partition plan** mapping every change to exactly one PR, and (2) a **detailed task list** per PR that captures specific changes from the original branch. The goal is maximum recovery of past work with no overlap between PRs.1920## Description2122When a branch accumulates too many changes for a single reviewable PR (rough threshold: >15 files changed, >500 net insertions, or >3 distinct themes), this skill decomposes it into multiple smaller PRs. Each PR is coherent (single theme), self-contained (passes tests independently), and non-overlapping (no file change appears in two PRs). The PRs are ordered so dependencies are satisfied: PR A merges before PR B if B depends on changes from A.2324After partitioning, the skill generates detailed tasks for each PR — each task references specific files, original commits, and code from the source branch so the implementer can recover the exact changes efficiently.2526## Instructions2728When the user invokes `/hdb:split-pr [--base <branch>] [--dry-run]`:2930### Phase 1: Inventory3132Catalog everything on the branch before making any decisions.33341. **Determine the base branch.** If `--base` is provided, use it. Otherwise detect:3536 ```bash37 git merge-base master HEAD38 ```3940 If neither `master` nor `main` exists, ask the user to specify the base.41422. **List all commits** on the branch since divergence:4344 ```bash45 git log --oneline --reverse $(git merge-base <base> HEAD)..HEAD46 ```4748 Record the count. If there are fewer than 5 commits and fewer than 10 files changed, inform the user the branch is small enough for a single PR and stop unless they insist.49503. **List all changed files** with diff stats:5152 ```bash53 git diff --stat <base>..HEAD54 git diff --name-only <base>..HEAD | sort55 ```56574. **Read every commit message** and categorize each commit by its logical theme. Use the commit message prefix (e.g., `security:`, `feat:`, `fix:`, `docs:`, `test:`, `refactor:`, `chore:`) as the primary signal. When the prefix is ambiguous, read the commit's diff:5859 ```bash60 git show --stat <sha>61 ```62635. **Build the theme inventory table.** Present it to the user for review:6465 | Theme | Commits | Files | Net Lines | Description |66 |-------|---------|-------|-----------|-------------|67 | rate-limiting | 8 | 5 | +320 | In-memory and Redis rate limiter |68 | webhook-security | 6 | 4 | +180 | HMAC verification, payload limits |69 | ... | ... | ... | ... | ... |70 | **TOTAL** | **62** | **43** | **+1913** | |71726. **Identify cross-cutting files** — files touched by commits from multiple themes:7374 ```bash75 # For each file, list which themes' commits touch it76 git log --format="%h %s" <base>..HEAD -- <file>77 ```7879 Present cross-cutting files in a separate table:8081 | File | Themes | Decision Needed |82 |------|--------|-----------------|83 | `backend/app/core/config.py` | rate-limiting, webhook-security | Which PR owns the config changes? |84 | `backend/app/main.py` | rate-limiting, auth-refactor | Middleware registration spans two features |85867. **Wait for user confirmation** of the theme groupings before proceeding. The user may want to merge themes, split themes, or rename them.8788### Phase 2: Dependency Analysis8990Determine the merge order.91928. **For each theme group, identify dependencies on other groups.** A theme depends on another if:93 - It imports or calls functions introduced by the other theme94 - It modifies files whose baseline state requires the other theme's changes95 - Its tests rely on fixtures or utilities introduced by the other theme96 - It extends a database migration introduced by the other theme9798 Check dependencies by reading the diffs:99 ```bash100 git diff <base>..HEAD -- <file>101 ```1021039. **Build the dependency DAG.** Present it as a table and an ASCII diagram:104105 | PR | Theme | Depends On | Merge Order |106 |----|-------|------------|-------------|107 | 1 | auth-refactor | (none) | First |108 | 2 | rate-limiting | auth-refactor | Second |109 | 3 | webhook-security | auth-refactor | Second (parallel with 2) |110 | 4 | docs | rate-limiting, webhook-security | Third |111112 ```113 1:auth-refactor114 ├── 2:rate-limiting115 ├── 3:webhook-security116 └── 4:docs (after 2+3)117 ```11811910. **Detect cycles.** If the dependency graph has a cycle, the themes cannot be cleanly separated. Options:120 - Merge the cyclic themes into a single PR121 - Extract shared infrastructure into a new "foundation" PR that breaks the cycle122123 Present the cycle to the user and recommend a resolution.12412511. **Wait for user confirmation** of the dependency ordering before proceeding.126127### Phase 3: Partition128129Assign every file change to exactly one PR.13013112. **Assign unambiguous files first.** Files touched by only one theme go into that theme's PR. This handles the majority of files.13213313. **Resolve cross-cutting files.** For each cross-cutting file, choose one strategy:134135 | Strategy | When to Use | How |136 |----------|-------------|-----|137 | **Assign to earliest PR** | The file has a clear "foundation" change that later PRs build on | Put all changes in the earliest PR in the dependency chain |138 | **Split by hunk** | The file has distinct, non-overlapping hunks for each theme | Extract per-theme patches and partition hunks |139 | **Assign to infrastructure PR** | The file is shared config or wiring (e.g., `main.py`, `config.py`) | Create a dedicated "infrastructure" PR or assign to the first PR |140 | **Sequential layering** | One theme adds a function, another modifies it | Put the addition in the earlier PR; the modification in the later PR (which rebases on top) |14114214. **Handle special file categories:**143144 | Category | Strategy |145 |----------|----------|146 | **Formatting/linting commits** | Assign each formatted file to whichever PR already owns that file. If a formatting commit touches files across multiple PRs, split it. |147 | **Test files** | Move test files with the feature they test. If a test file covers multiple features, assign it to the latest PR in the dependency chain or create a dedicated test PR. |148 | **Documentation** | Small doc changes travel with their feature. Large doc additions (new files, multi-file updates) go in a dedicated docs PR at the end of the chain. |149 | **Migration files** | Migrations travel with the schema change they implement. If two migrations exist, they go in separate PRs only if independent. Chain `down_revision` values accordingly. |150 | **Docker/CI/Makefile** | Infrastructure config goes in the earliest PR that needs it, or in a dedicated infrastructure PR. |15115215. **Build the partition table.** Present it to the user:153154 | PR | Branch Name | Theme | Files | Cherry-Pick Candidates |155 |----|-------------|-------|-------|------------------------|156 | 1 | `security/auth-refactor` | Auth renaming | `deps.py`, `admin_access.py`, ... | `00102f8`, `f102b50` |157 | 2 | `security/rate-limiting` | Rate limiting | `rate_limit.py`, `rate_limit_backend.py`, ... | `603c653`, `0d5f4bf`, ... |158 | ... | ... | ... | ... | ... |15916016. **Verify completeness.** The union of all file lists must equal the original changed file list. No file may appear in two PRs:161162 ```163 Original files: 43164 Assigned files: 43 (sum across all PRs)165 Duplicates: 0166 Unassigned: 0167 ```168169 If there are unassigned files or duplicates, resolve them before proceeding.17017117. **Wait for user confirmation** of the partition before generating tasks.172173### Phase 4: Task Generation174175For each PR, generate a detailed task list that captures the specific changes from the original branch.17617718. **For each PR, generate ordered tasks.** Each task must have:178179 - **Subject** — imperative action (e.g., "Add Redis rate limiter module")180 - **Description** — specific file paths, what to change, and references to original commits/diffs:181 ```182 Create `backend/app/core/rate_limit.py` with:183 - RateLimiter ABC with async `is_allowed(key: str) -> bool`184 - InMemoryRateLimiter using sliding-window deque185 - RedisRateLimiter using sorted-set pipeline186 - Shared client factory `_get_async_redis(url)`187 - `create_rate_limiter()` factory function188189 Reference: commits 603c653, 32f5453, 294f518 on chore-security-review190 Recovery: `git show chore-security-review:backend/app/core/rate_limit.py`191 ```192 - **Acceptance criteria** — how to verify the task is done:193 ```194 - File exists and passes `mypy --strict`195 - `uv run pytest tests/test_rate_limit.py` passes (15 tests)196 ```19719819. **Task ordering within each PR:**199200 | Order | Task Type | Example |201 |-------|-----------|---------|202 | 1 | Infrastructure / new modules | Create `rate_limit_backend.py` enum |203 | 2 | Core implementation | Create `rate_limit.py` with ABC + both backends |204 | 3 | Configuration | Add settings to `config.py` |205 | 4 | Integration / wiring | Wire rate limiter into `agent_auth.py`, `board_webhooks.py` |206 | 5 | Tests | Create `test_rate_limit.py` with 15 tests |207 | 6 | Documentation | Update `security.md`, `api.md` |208 | 7 | Linting / formatting | Run `ruff`, `black`, `isort` and fix |209 | 8 | Commit and verify | Run full test suite, commit |21021120. **Include recovery commands** in each task so the implementer can pull exact code from the source branch:212213 ```bash214 # View the final state of a file on the source branch215 git show <source-branch>:<file-path>216217 # View a specific commit's changes to a file218 git show <sha> -- <file-path>219220 # Extract a file from the source branch into the working tree221 git checkout <source-branch> -- <file-path>222223 # Generate a patch for specific files224 git diff <base>..<source-branch> -- <file-path> > /tmp/partial.patch225 ```22622721. **Present the complete task list** to the user, organized by PR:228229 ```230 ## PR 1: security/auth-refactor (Auth Renaming)231 Depends on: (none) | Base: master232233 1. Rename `require_admin_auth` → `require_user_auth` in deps.py234 Recovery: git show chore-security-review:backend/app/api/deps.py235 Acceptance: All 10 API modules import the new name; tests pass236237 2. Update all API modules to use renamed dependency238 Files: activity.py, agents.py, approvals.py, ...239 Recovery: git diff master..chore-security-review -- backend/app/api/activity.py240 Acceptance: `rg require_admin_auth backend/` returns 0 matches241242 3. Run tests and commit243 Acceptance: `uv run pytest` passes; commit message follows conventions244245 ## PR 2: security/rate-limiting (Rate Limiting)246 Depends on: PR 1 | Base: security/auth-refactor247248 1. Create rate_limit_backend.py enum249 ...250 ```251252### Phase 5: Extract (on user request)253254Create the sub-PR branches. Work in dependency order (roots first, leaves last).25525622. **Decide extraction method per PR:**257258 | Method | When to Use | Pros | Cons |259 |--------|-------------|------|------|260 | **Cherry-pick** | Commits are clean and atomic; each commit belongs entirely to one PR | Preserves history | Fails when commits span multiple themes |261 | **File-level checkout** | Commits are mixed or squashed | Clean result regardless of commit structure | Loses original commit granularity |262263 In practice, most splits use file-level checkout because real branches rarely have perfectly atomic commits.26426523. **For each PR (in dependency order):**266267 a. **Create a new branch** from the appropriate base:268 ```bash269 # For the first PR (no dependencies):270 git checkout -b <branch-name> <base>271272 # For PRs that depend on a prior PR:273 git checkout -b <branch-name> <prior-pr-branch>274 ```275276 b. **Apply the changes** using the chosen method:277278 **Cherry-pick method:**279 ```bash280 git cherry-pick <sha1> <sha2> <sha3>281 ```282283 **File-level checkout method:**284 ```bash285 git checkout <source-branch> -- <file1> <file2> <file3>286 git commit -m "<theme>: apply <description>"287 ```288289 **Hunk-level extraction** (for cross-cutting files):290 ```bash291 git diff <base>..<source-branch> -- <file> > /tmp/partial.patch292 # Edit the patch to keep only relevant hunks293 git apply /tmp/partial.patch294 ```295296 c. **Run tests** to verify the branch works standalone:297 ```bash298 cd backend && uv run pytest tests/ -x -q # Python299 go test ./... # Go300 cargo test # Rust301 ```302303 d. **Run linters/formatters** and fix any issues.304305 e. **Verify the diff** is limited to this PR's assigned files:306 ```bash307 git diff --name-only <base-of-this-pr>..HEAD308 ```30931024. **If `--dry-run` was specified**, stop after generating the partition table and task list. Do not create branches.311312### Phase 6: Verify313314Confirm completeness and correctness after all branches are created.31531625. **Verify coverage.** For each file in the original diff, confirm it appears in exactly one sub-PR:317318 ```bash319 for branch in <branch1> <branch2> ...; do320 echo "=== $branch ==="321 git diff --name-only <base>..$branch322 done323 ```32432526. **Verify each branch passes tests independently.**32632727. **Generate the final summary:**328329 ```330 ## Split Summary331332 Original branch: chore-security-review (62 commits, 43 files, +1913/-85)333 Sub-PRs created: 5334335 | # | Branch | Theme | Files | Lines | Depends On | Tests | Tasks |336 |---|--------|-------|-------|-------|------------|-------|-------|337 | 1 | security/auth-refactor | Auth renaming | 12 | +120 | -- | PASS | 3 |338 | 2 | security/rate-limiting | Rate limiting | 8 | +450 | #1 | PASS | 8 |339 | 3 | security/webhook-hardening | Webhook security | 8 | +280 | #1 | PASS | 7 |340 | 4 | security/docker-hardening | Docker non-root | 3 | +30 | -- | PASS | 2 |341 | 5 | security/docs-and-tests | Docs + tests | 12 | +550 | #2, #3 | PASS | 5 |342343 Merge order: 1 and 4 (parallel) → 2 and 3 (parallel) → 5344 Coverage: 43/43 files assigned. 0 duplicates. 0 unassigned.345 Total tasks: 25346 ```347348### Phase 7: PR Creation (optional, on user request)34935028. **Create PRs in dependency order:**351352 ```bash353 git checkout <branch>354 git push -u origin <branch>355 gh pr create --base <target-base> --title "<theme>: <concise description>" --body "$(cat <<'EOF'356 ## Summary357 <1-3 bullet points>358359 ## Context360 PR N of M from the `<original-branch>` split.361 **Merge order:** <where this fits>362 **Depends on:** <dependency PR links, or "None">363364 ## Test plan365 - [ ] All existing tests pass366 - [ ] <specific items>367368 Generated with [Claude Code](https://claude.com/claude-code)369 EOF370 )"371 ```37237329. **Link dependency PRs** in each PR description so reviewers understand the merge order.374375---376377## Decision Guide: Cross-Cutting Files378379| File Type | Resolution |380|-----------|--------------------|381| `config.py` / `settings.py` | Put in the earliest PR that needs the config. Later PRs add their settings on top via rebase. |382| `main.py` / app wiring | Put in a dedicated infrastructure PR, or in the earliest PR. |383| `Makefile` / `docker-compose.yml` | Put in the PR whose feature the change supports. |384| Shared test fixtures | Put in the earliest PR. Later PRs import from the fixture. |385| `__init__.py` / re-exports | Put in the PR that adds the module being re-exported. |386| Migration files | Always travel with the schema change they implement. |387388## Common Pitfalls389390### 1. Forgetting to rebase after merge391392Later PRs were branched from earlier PR branches. After PR 1 merges into `master`, PR 2 must rebase:393394```bash395git checkout security/rate-limiting396git rebase master397git push --force-with-lease398```399400Failure to rebase causes the later PR to include the earlier PR's diff, confusing reviewers.401402### 2. Tests that import across PR boundaries403404If PR 2's tests import a helper added by PR 1, the tests fail when PR 2 is checked out before PR 1 merges. Solutions:405- Branch PR 2 from PR 1's branch (not from `master`)406- Duplicate the helper in PR 2 (acceptable for small helpers)407- Move the helper to a shared utility in PR 1408409### 3. Migration ordering across PRs410411If PR 2 and PR 3 both add Alembic migrations, they both have the same `down_revision`. When both merge, this creates multiple heads. Solutions:412- Put all migrations in one PR413- Use `alembic merge heads` after the second PR merges414- Chain the migrations: PR 3's migration depends on PR 2's415416### 4. Formatting commits that touch everything417418A single `ruff format .` commit touches every file. Do not put this in one PR. Instead:419- Re-run the formatter independently on each sub-PR branch420- Let each PR's files be formatted within that PR421422### 5. Losing changes in the split423424Always verify coverage (Phase 6, step 25). The most common cause: a file touched by two themes is assigned to only one PR, and the other theme's hunks are dropped.425426### 6. Over-splitting427428Not every theme needs its own PR. If two themes are small (< 3 files, < 50 lines each) and naturally related, merge them. The goal is reviewable PRs, not maximally granular PRs.429430## Size Guidelines431432| Original Size | Recommended Split |433|---------------|-------------------|434| 5-15 files, < 500 lines | Single PR (no split needed) |435| 15-30 files, 500-1000 lines | 2-3 PRs |436| 30-50 files, 1000-2000 lines | 3-5 PRs |437| 50+ files, 2000+ lines | 5-8 PRs (consider deferring some changes) |438439## Guidelines440441- **Get confirmation at each gate.** Present the theme inventory, dependency DAG, and partition table to the user before proceeding. Backtracking after branch creation is expensive.442- **Dependency order is non-negotiable.** Every PR must pass tests independently when applied on top of its declared dependencies.443- **Prefer file-level checkout over cherry-pick.** Real branches rarely have perfectly atomic commits.444- **Cross-cutting files go to the earliest PR.** When in doubt, put shared files in the first PR in the dependency chain.445- **Every task references recovery commands.** The implementer should be able to pull exact code from the source branch for each task.446- **Run tests on every sub-PR branch.** Do not assume a clean partition means clean tests.447- **Do not over-split.** Fewer, larger PRs are better than many tiny PRs that create a long merge queue. Target 3-6 PRs.448- **Document the merge order in every PR.** Reviewers need to know "this is PR 2 of 5, depends on PR 1."449- **Delete the original branch after all sub-PRs merge.** It is dead weight after the split.450- **Respect CLAUDE.md.** The project's instructions override everything in this skill.451452## See Also453454- `/hdb:design` — design a feature with a PRD and task list (use before implementing, to prevent oversize branches)455- `/roborev:pull-request-reviewer` — pre-review a PR before submitting456- `/hdb:alembic` — diagnose and fix migration issues that arise during splits