007 / 024 · Dev · v0.17.1 · MIT
cleanup
After a change ships, the leftovers stay: the local branch, its copy on the remote, the worktree it was built in, and sometimes a directory holding nothing but ignored files. cleanup removes those, and keeps and reports anything whose history it cannot account for.
- Arguments
Each optional.
[--base=<branch>][--branches][--remote][--worktrees][--residue][--no-remote][--dry-run]
- When it fires
The prompts CI scores this skill against, on every release.
Fires on
- Clean up the branches that have already merged.
- 清理一下仓库,收拾下没用的分支
- Remove the worktrees I am no longer using.
- 清掉已经合并过的分支
- There are a dozen merged branches still sitting on GitHub. Prune them.
Stays out of
- Discard my uncommitted changes.
- Reset this branch to origin and throw away my commits.
- Delete all untracked files in here.
Whether a branch merged is a question for the forge
A rebase or squash merge rewrites commits, so after the merge git itself can no longer tell whether a branch landed — git branch --merged does not list it. Deleting on git's answer deletes the wrong branches; keeping on it leaves every squash-merged branch behind forever.
So before every deletion the run asks the forge what the branch's pull request actually reports, and acts only on MERGED. A branch with no pull request is not assumed abandoned either: it is deletable only once git merge-base --is-ancestor proves every one of its commits is already on the base, and the report names which of the two checks cleared it, because "no pull request" and "deleted" sitting together look alarming.
The remote is asked twice, once for merged and once for open. Those are not opposites: a branch can carry a merged pull request and a newer open one, and GitHub closes any pull request whose head branch is deleted.
Everything is listed before anything is removed
The first pass writes nothing. It prints one table — every branch, remote branch, worktree and residue directory it found, what it found out about each, and what it intends to do. --dry-run stops there.
Four passes run by default. --branches, --remote, --worktrees and --residue narrow it to one, and --no-remote keeps the other three, for a fork you cannot push to.
The residue pass handles a case git does not warn about: git mv moves only what git tracks, so an ignored directory like __pycache__ or node_modules stays where it was and keeps its parent alive, while git status still reports clean. The test is whether anything tracked still lives under the directory; if something does, it is not residue. An ignored directory sitting beside tracked files is working state and is kept — deleting a build cache costs a rebuild, and deleting a .venv costs an afternoon.
The table of what stayed
The final report has two tables: what went, and what stayed. The second is the one to read. Every row in it is something that looked removable and was not, with its reason stated — an unmerged branch, a remote branch with an open pull request, a dirty worktree, an ignored directory with tracked siblings.
Anything held back only because a person has to decide is asked as a question rather than filed under "kept". The common case is a merged branch checked out in your primary repository: there the checkout itself is the leftover, but a branch name is often the only record of what you were in the middle of, so the run offers rather than switches it.
What it does not do
It does not discard uncommitted work, reset a branch, or remove untracked files you have not been shown. A worktree with anything in it is kept, including one holding only untracked files. The worktree the run is standing in is never removed whatever its state, because the deletion cannot be carried out from inside it. And a branch whose remote copy was deleted without merging is kept and flagged: that is either abandoned work or somebody else's mistake, and once the local copy is gone it cannot be recovered.
Also in Dev
6 more skills · one installsync
/dev:syncsync does one thing: it lays out the difference between your local repository and its remote, and performs only the part of that which is unambiguously safe.
ship
/dev:shipship takes a change from your working tree to a merged pull request: branch off, run the project's own tests, commit, open the pull request, wait for CI, merge, then clean up the worktree.
retitle
/dev:retitleA conversation list gets hard to use not because the names are ugly but because they carry nothing you can sort on.
steward
/dev:stewardsync, ship and cleanup each work in the repository you are standing in. steward is the one that runs that loop across every repository you have actually been working in — it finds them, sweeps them, and hands back a single report of what is waiting on you: which branches are ready to merge, which pull requests are blocked and on what, which branches were written and never opened, and which worktrees a live session is still sitting in.
reunite
/dev:reunitesessions answers the moment you sign in with your other account and the sidebar is suddenly almost empty.
handoff
/dev:handoffhandoff keeps readable conversation history available in Claude Code and Codex on the same machine.