Classify and clean up merged, stale, and orphaned git branches and worktrees. Detects squash-merges that git branch --merged cannot see, stale worktrees, and remote-gone branches. Use when user asks to clean up branches, prune worktrees, tidy the repo, or perform git housekeeping.
Scanned 9/6/2026
Install to Claude Code
npx -y skills add shrwnsan/vibekit-claude-plugins --skill branch-hygiene --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Branch Hygiene?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/shrwnsan-branch-hygiene)More formats (shields.io, HTML) on the badges page.
---
name: branch-hygiene
description: Classify and clean up merged, stale, and orphaned git branches and worktrees. Detects squash-merges that git branch --merged cannot see, stale worktrees, and remote-gone branches. Use when user asks to clean up branches, prune worktrees, tidy the repo, or perform git housekeeping.
user-invocable: true
allowed-tools:
- bash(git:*)
- bash(sh:*)
- bash(bash:*)
- Read
---
# Branch & Worktree Hygiene
Classifies every local/remote branch and worktree against the mainline, then
cleans up what is resolved. Never guesses — every deletion is backed by a
verified verdict.
## The core problem
`git branch --merged` is **useless in squash-merge workflows**: it reports
nothing mergeable even when branches are fully resolved (content landed via
squashed PRs). The only reliable test is content-level: PR-number log matching,
file presence, and feature greps.
## Workflow
1. **Classify** — run the read-only classifier:
```bash
sh <skill-dir>/scripts/classify-branches.sh [mainline-ref]
```
Default mainline: `main` (falls back to `master`). Emits one line per item:
`<verdict> <kind> <ref-or-path> <evidence>`
2. **Verify** — the classifier's output is *evidence, not authority*. Before
acting on any verdict, spot-check it: open the cited PR commit, diff the
named files, confirm the feature grep. Classifiers hallucinate less than
models but more than never.
3. **Act per tier**:
| Verdict | Action | Confirmation |
|---|---|---|
| `merged-ancestry` / `merged-content` (branch) | `git branch -D` | none (reflog recovers) |
| `remote-gone` (local branch, upstream deleted) | `git branch -D` + remove any worktree | none |
| `stale-base` (branch predates mainline; merging would regress) | surface to user | user decides |
> `stale-base` is currently emitted by review judgment, not the classifier — treat divergence without squash evidence as `unmerged` and assess manually.
| `superseded` (content landed, mainline evolved past it) | surface to user | user decides |
| `unmerged` (genuinely unique commits) | surface to user, never delete | user decides |
| `worktree-prunable` (directory gone, registry entry remains) | `git worktree prune` | none (metadata only) |
| `worktree-clean-merged` (clean tree, branch resolved) | `git worktree remove` | **always confirm** (deletes a directory) |
| `worktree-dirty` / `worktree-unmerged` | never touch | — |
| `orphan-dir` (looks like a worktree, not registered) | surface, move with `trash` on confirm | user decides |
4. **Remote branches** — deleting `origin/*` refs is outward-facing: list them
with verdicts, delete only after explicit user approval, in one batch push.
## Squash-merge detection (how the classifier thinks)
For each branch not mergeable by ancestry:
1. Extract its commit subjects; grep mainline log for the same subjects with
`(#N)` suffixes — squash-merges preserve the subject.
2. Check file presence: key files the branch adds, present on mainline?
3. Feature greps: distinctive strings from the branch's diff, present in
mainline's version of those files?
All three hit → `merged-content`. Content present but mainline's copy is
larger/newer → `superseded`. Diff would *remove* lines mainline has → `stale-base`.
## Worktree specifics
- `git worktree list --porcelain` is the source of truth; each entry's `branch`
or detached `HEAD` gets the same verdict engine as free branches.
- Detached-HEAD worktrees: verdict keyed on the commit — `merged-ancestry`
if contained in mainline and tree is clean.
- Tool-created worktrees (e.g. `.claude/worktrees/`) are included; their
auto-cleanup is best-effort, this is the backstop.
## Known limitations
- **Origin only**: the remote scan covers `origin/*` exclusively. Repos with
additional remotes (`upstream/`, `fork/`) only get their origin refs
classified — run with awareness, or extend the remote loop.
- **Squash-merge detection is heuristic**: subject matching can false-positive
on generic subjects ("fix bugs"); the verify step exists for exactly this
reason. Never trust a verdict without checking its evidence.
## Hard rules
- The classifier script is **read-only** — it never deletes, pushes, or prunes.
- Dirty worktrees and `unmerged` refs are never modified, only reported.
- `git worktree remove` and remote ref deletion always require user confirmation.
- Use `trash`, never `rm -rf`, for orphaned directories.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!