Restructure a range of existing commits into a clean stack of atomic commits
grouped by concern. The input is a commit range — anything git diff and
git log understand (branch name, main..HEAD, hash1..hash2, etc.).
-
Check branchless init:
if [ ! -d ".git/branchless" ]; then git branchless init; fi
-
Parse the range from $ARGUMENTS. Common forms:
main..HEAD or main.. — everything since main
<branch> — the branch tip vs its merge-base with main
<hash1>..<hash2> — explicit range
<hash>..HEAD — from a specific commit to current
Resolve to a <base> and <tip>:
BASE=<resolved base>
TIP=<resolved tip>
BASE=$(git merge-base main <branch>)
TIP=<branch>
-
Understand the current state. Run all of these:
git sl
git log --oneline $BASE..$TIP
git diff --stat $BASE..$TIP
git diff $BASE..$TIP
Read the full diff carefully. Count the total lines changed.
-
Analyze and classify every change into logical groups. Standard ordering:
- Refactoring / cleanup (renames, moves, formatting — no behavior change)
- Dependencies / build config changes
- Scaffolding / types / interfaces / schemas
- Core business logic (the main feature or fix)
- Edge cases / error handling
- Tests
Documentation goes WITH the feature commit it documents, not as a separate
commit at the end. Each commit should include the docs for what it introduces.
Dependencies (imports, config files) arrive in the commit that first uses
them — never frontloaded. Generated files should show incremental additions
even if the tool regenerates them wholesale.
For each group, note:
- Which files and hunks belong to it
- A one-sentence commit message
- Estimated line count (target 50-200 per commit)
If a file has changes spanning multiple groups, it will need git add -p
to split hunks across commits.
-
Present the plan to the user. Format as a numbered list:
Proposed stack (N commits, M total lines):
1. refactor(scope): description — files X, Y (~80 lines)
2. feat(scope): add types — files A, B (~120 lines)
3. feat(scope): core logic — files C, D, E (~150 lines)
...
Wait for user approval. Adjust if they have feedback on grouping or
ordering.
-
Check for uncommitted work before starting:
git status --short
If there are uncommitted changes, warn the user and ask whether to stash
or commit them first.
-
Flatten the range into the working tree:
git reset --soft $BASE
git restore --staged .
This uncommits everything in the range but keeps all changes in the
working tree. Verify:
git diff --stat
-
Commit each group in plan order:
git add <file1> <file2>
git commit -m "type(scope): description"
git add -p <file>
git commit -m "type(scope): description"
After each commit, verify nothing was missed or double-counted:
git diff --stat
Each commit must:
- Be describable in one sentence
- Leave the codebase in a working state
- Not mix concerns (if in doubt, split smaller)
-
Verify the result:
git sl
Show the user the new stack.
Confirm the total diff is identical:
git diff $BASE..HEAD --stat
-
Run tests if a test command is identifiable:
git test run -x '<test-command>' 'stack()'
Report any commits that break the build.