A TUI for marking git commits with rebase actions and applying them via
git rebase -i. Ships as a Claude Code plugin with a /commit-composer:commit-compose
slash command and a model-invoked skill alias.
+- Commits (5) ---------------+- Commit a1b2c3d -----------+
| > a1b2c3 [pick ] feat| SHA: a1b2c3d... |
| d4e5f6 [squash ] cach| Author: jane <jane@ex.com> |
| 789abc [reword ] refa| Date: 2026-05-14 10:12 |
| 0fedcb [drop ] wip | Message: |
| 1234567 [claude-split] mix| chore: misc tidy |
| | Split: logical |
| | Files: M auth.go A test.go|
| | diff --git a/auth.go ... |
+-----------------------------+----------------------------+
[p] pick [r] reword [s] squash [f] fixup [d] drop [e] edit [c] claude-split ⏎ apply q cancel ? help
Works on macOS (Homebrew) and Linux (Linuxbrew).
brew tap mrcat71/tap
brew install commit-composerThen in a Claude Code session, one-time setup:
/plugin marketplace add $(brew --prefix)/share/commit-composer
/plugin install commit-composer@mrcat71
/reload-plugins
$(brew --prefix) resolves to /opt/homebrew on Apple Silicon,
/usr/local on Intel Macs, and /home/linuxbrew/.linuxbrew on Linux.
Run it once in a shell to see your actual path if you'd rather hard-code
it in the slash command.
Future upgrades are just brew upgrade commit-composer - the marketplace
path stays stable, so no re-adding is needed. Run /plugin marketplace update
or restart Claude Code to pick up plugin-file changes.
git clone https://github.com/mrcat71/commit-composer
cd commit-composer
./scripts/install.shThen, inside a Claude Code session:
/plugin marketplace add /absolute/path/to/commit-composer
/plugin install commit-composer@mrcat71
/reload-plugins
Requires Go 1.24+ and a recent git on $PATH.
Known issue: slash commands from directory-based local marketplaces can fail to register (claude-code#14929). The
commit-composerskill alias is unaffected. If/commit-composer:commit-composedoes not appear, push the repo to git and/plugin marketplace add <user>/<repo>instead.
/commit-composer:commit-compose # last <upstream>..HEAD (default), or HEAD~10..HEAD
/commit-composer:commit-compose HEAD~5 # the last 5 commits
/commit-composer:commit-compose <base>..<head> # explicit range
The slash command will:
- Refuse to start if the working tree is dirty.
- Refuse if any commit in the range is reachable from a protected branch
(
origin/main,origin/master,upstream/main,upstream/master). - Launch the TUI in an overlay (tmux popup / Zellij floating / kitty overlay / wezterm split / iTerm split / inline).
- Capture your structured plan and show it back for confirmation.
- Apply the plan via
git rebase -i <base>driven non-interactively byGIT_SEQUENCE_EDITORandGIT_EDITORhelpers.
You can also run the binary directly:
commit-composer HEAD~5 # interactive TUI, prints plan to stdout
commit-composer --output=plan.txt HEAD~5 # write plan to a file
commit-composer --apply --plan=plan.txt # apply (no claude-split)
commit-composer --apply --plan=plan.txt --splits=DIR # apply (with claude-split JSONs)
commit-composer __split-prepare --plan=FILE --out=DIR # extract diffs for Claude to analyse
commit-composer --list HEAD~5 # print resolved commits and exitj / ↓ cursor down
k / ↑ cursor up
J move highlighted commit DOWN
K move highlighted commit UP
p pick
r reword (choose: [e] $EDITOR [c] ask Claude)
s squash (fold into previous, keep both messages)
f fixup (fold into previous, drop this message)
d drop
e edit (rebase pauses on this commit)
c claude-recompose (mark / unmark)
space cycle action
ctrl+j / ctrl+k scroll diff
ctrl+d / ctrl+u diff page down / up
enter confirm plan and exit
q / esc cancel
? toggle help overlay
Pressing c marks a commit so Claude will redesign its place in the
history during apply. Consecutive marked commits get pooled into a
single analysis batch — mark 3 in a row and Claude looks at the
combined diff, then proposes a fresh sequence of commits grouped by
feature/topic (not mechanically by file).
After the TUI confirmation, Claude shows the full combined plan (rebase actions + per-pool proposed groups + protected-branch warning, if any) and offers three options:
yapply the plancleave a comment in plain English ("merge groups 1 and 2", "give pool A three commits instead of two", "rename the first commit") - Claude revises the proposal and asks againncancel
Pressing r opens an inline chooser:
eedit the message in$EDITOR(the existing manual flow)cask Claude to propose a new messageesccancel
If you pick c, the commit is marked claude-reword in the plan.
After you confirm the TUI, the slash command extracts the current
message plus the commit diff, asks Claude to draft a replacement under
the project's commit-message rules, and opens $EDITOR per commit with
the proposal pre-filled so you can review or edit before it lands.
The final, user-approved message is what ends up in the rewritten
history.
claude-recompose and claude-reword both produce Conventional
Commits (<type>(<scope>): <summary>). The scope is required when one
is clear and should describe the affected feature/module/service/
chart/role/package rather than the implementation technology (so
feat(gitlab-access-token): ... rather than feat(terraform): ...).
The full ruleset lives in
.claude-plugin/commands/commit-compose.md
under "Commit-message rules"; the binary has no validator, so the
rules are enforced by Claude's prompt and your $EDITOR review pass.
The binary emits a line-based v1 plan:
## commit-composer plan v1
base: <full-sha>
range: <base>..<head>
ops:
- pick a1b2c3d
- squash d4e5f6a
- reword 789abc0 :: new commit message here
- drop 0fedcba
- pick 1234567
Multi-line reword messages are base64-encoded:
- reword 789abc0 :: b64::Zm9vCgpiYXIK
claude-recompose ops carry no extra metadata in the plan line itself;
consecutive marked commits form a pool:
- claude-recompose 1234567
- claude-recompose 89abcde
- claude-recompose fedcba0
claude-reword ops are per-commit and likewise carry no message in the
plan line emitted by the TUI - the slash command fills the message in
before apply by rewriting these lines to regular reword ops:
- claude-reword 1234567
The actual split decisions (file groups + new commit messages) live in
separate <lastSHA>.split.json files in the splits directory, keyed by
the LAST commit of each pool, with pool_size describing how many
commits to dissolve:
{
"sha": "fedcba0...",
"pool_size": 3,
"groups": [
{ "files": ["auth.go", "auth_test.go"], "message": "feat: add Auth" },
{ "files": ["docs/auth.md"], "message": "docs: explain Auth" }
]
}An empty stdout (or a non-zero exit) means "cancelled - do nothing".
- The plugin refuses to start if the working tree is dirty.
- The plugin refuses to rewrite commits already on a protected branch
(
origin/main,origin/master,upstream/main,upstream/master). - The plugin never runs
git push --force. If you need to publish the rewritten history, it prints the suggestedgit push --force-with-leasecommand for you to run yourself. - On rebase conflict, the plugin stops and surfaces the conflict; it
never runs
git rebase --abortautomatically.
commit-composer/
├── README.md
├── LICENSE # MIT
├── go.mod
├── cmd/commit-composer/main.go
├── internal/
│ ├── git/ # exec wrappers + rebase driver
│ ├── plan/ # data model + serialize / parse
│ └── tui/ # bubbletea Model / Update / View
├── .claude-plugin/
│ ├── plugin.json
│ ├── marketplace.json
│ ├── commands/commit-compose.md
│ ├── skills/commit-composer/SKILL.md
│ └── scripts/
│ ├── launch-commit-composer.sh # terminal-overlay dispatcher
│ └── resolve-launcher.sh # user-override resolver
├── scripts/install.sh
└── docs/
├── commit-composer-plan.md # original design plan
├── install.md # full install + troubleshooting
└── next-steps.md # remaining milestones and follow-ups
go test ./... # unit + integration tests
go build ./... # build everything
./scripts/install.sh # build into .claude-plugin/bin
go run ./cmd/commit-composer HEAD~5 # run against current repoThe plugin manifest, skill-alias pattern, and bash-driven overlay detection are adapted from umputun/revdiff.
MIT - see LICENSE.