Commit Splitter
Split one big, mixed-up change into a series of small, atomic commits — each a single logical change that builds and passes tests on its own — by grouping hunks by intent and staging them piecemeal. Use when a working tree or a fat commit mixes a feature, a refactor, a bug fix, and formatting, or before opening a PR you want reviewers to actually read.
npx agentscamp add skills/commit-splitterInstall to ~/.claude/skills/commit-splitter/SKILL.md
Turns one fat, mixed change into a series of atomic commits a reviewer can actually read. It inventories the diff, groups hunks by intent (feature, refactor, fix, formatting), stages each group with git add -p, orders them so every commit builds and passes tests independently, and writes a focused conventional message per commit.
A 600-line diff that mixes a feature, a drive-by refactor, a bug fix, and a formatter run is unreviewable — reviewers skim it and approve on faith. This skill decomposes that change into a sequence of small commits, each one a single logical intent that compiles and passes tests on its own. It groups the diff by purpose, stages one group at a time with git add -p, orders them so prerequisites land first, and gives each commit a focused message — so reviewers read the story instead of guessing at it, and git bisect/git revert stay meaningful.
When to use this skill
- An uncommitted working tree mixes concerns — a new feature, an unrelated refactor, a bug fix, and whitespace/formatting churn all tangled together.
- A single fat commit (yours, not yet pushed) bundles several logical changes and you want to split it before review.
- You're about to open a PR and want the commit series to read as a deliberate narrative, not a
wipdump.
WARNING
Splitting only pays off if each commit independently builds and passes tests. A series where intermediate commits are broken defeats git bisect and makes any single-commit revert land a non-working tree — worse than one honest fat commit. Verify every commit, not just the tip.
Instructions
- Inventory what changed. Run
git status --porcelainandgit diff --stat(add--cachedfor staged hunks;git show --stat HEADif splitting an existing commit). Read the actual hunks withgit diffso you reason about real code, not filenames. Note any new/deleted/renamed files — those move as whole units, not per-hunk. - Group hunks by logical intent. Assign every hunk to exactly one group. Typical buckets, in dependency order:
- Prerequisite refactor — renames, extractions, signature changes the feature depends on (no behavior change).
- Bug fix — a self-contained correctness fix, ideally with its own test.
- Feature — the new behavior, built on the refactor above.
- Formatting / lint — pure whitespace, import sorting, autoformatter noise. Isolate this; mixed-in formatting is what makes diffs unreadable.
- Unrelated cleanup — dead code, typo, comment. Its own commit (or a separate PR). Watch for hidden coupling: a feature that won't compile without the refactor must come after it, never before.
- Stage one group at a time. Use
git add -p <files>and answer per hunk:yto stage,nto skip,sto split a hunk into smaller pieces. When a single hunk mixes two intents thatscan't separate (e.g. a logic change and a reformat on adjacent lines), usegit add -e(oreat the prompt) to hand-edit the staged patch — delete the+/-lines that belong to the other group, keep context lines intact. Stage exactly one group, then go to step 4. - Verify the staged group in isolation, then commit. Before committing, prove the staged subset stands alone:
git stash push --keep-indexparks everything not staged, leaving only this group in the tree. Run the project's build + tests (detect them —npm run build && npm test,pytest,go build ./... && go test ./...). If it builds and passes, commit (step 6); thengit stash popto restore the rest and return to step 3 for the next group. If it fails, you mis-grouped — a prerequisite is in a later group; re-order and re-stage. - For an already-committed mess, rewrite local history. Two routes:
- Re-stage the whole commit:
git reset HEAD~1(soft-ish — keeps changes in the working tree, unstaged), then proceed from step 2 to rebuild it as several commits. - Surgical split inside a series:
git rebase -i <base>, mark the offending commitedit. When the rebase stops on it,git reset HEAD~1to unstage its contents, then split via steps 3–6, andgit rebase --continue. Usegit rebase --abortto bail back to the original state if anything looks wrong.
- Re-stage the whole commit:
- Write a focused conventional message per commit. One intent per subject line:
refactor(parser): extract tokenizer,fix(auth): reject expired tokens,feat(auth): add SSO login,style: apply formatter. The subject names the single thing this commit does; if you need "and" or a bullet list of unrelated items, the commit is still mixed — split further. - Confirm the series reads as a story and every commit is green. Run
git log --oneline <base>..HEADto read the sequence top-to-bottom: prerequisites → fix → feature → cleanup. Then verify each commit independently —git rebase --exec '<build && test>' <base>replays the series running your command after every commit, failing on the first that breaks. This is the proof that the split is bisect-safe.
WARNING
Rewriting history that's already pushed or shared (reset, rebase -i) forces every collaborator to recover their local copy and can orphan their work. Only reshape local, unpushed history. If the commits are already on a shared branch, coordinate first — or leave history alone and split going forward.
Output
- Commit breakdown — an ordered table: each proposed commit's purpose (its single intent), the files/hunks it claims, and its dependency on earlier commits.
- Exact reproduction steps — the concrete
git add -p/git add -esequence (or therebase -i+reset HEAD~1plan) that produces that breakdown, including the per-groupstash push --keep-index→ build/test → commit →stash poploop. - Recommended commit messages — one conventional-commit subject (and body where it earns it) per commit, in apply order.
- Verification result — confirmation that
git rebase --execran the build+tests after every commit and the whole series is green, with any commit that needed re-grouping called out.
Example breakdown for a tangled working tree:
| # | Commit | Hunks / files | Depends on |
|---|---|---|---|
| 1 | refactor(parser): extract Tokenizer class | parser.ts (lines 12–88), new tokenizer.ts | — |
| 2 | fix(parser): handle empty input | parser.ts (lines 140–152), parser.test.ts (new case) | 1 |
| 3 | feat(parser): support inline comments | tokenizer.ts (lines 40–72), parser.ts (lines 95–110) | 1 |
| 4 | style: apply prettier | whitespace-only across 6 files | — |
Related
- Conventional CommitsGenerate clear Conventional Commits messages from staged changes. Use when committing code and you want a well-structured, consistent commit message.
- Branch RebaserRebase the current branch onto its base and walk every conflict methodically, resolving each by understanding both sides. Use when your feature branch has fallen behind main and you want a clean, linear history without clobbering changes.
- PR DescriptionDraft a clear pull request description from the branch diff against its base. Use when you have a finished branch and want a reviewer-ready PR body before opening the PR.