agentleFS
heffreyheffrey/claude-skills/repo-cleanup/SKILL.md

repo-cleanup

Skill1 starsChanged 7 months agoMITView on GitHub

Use when a repo has accumulated stale worktrees, branches, or half-finished work — asked to clean up or prune branches and worktrees, tidy a repo, work out what's safe to delete, or find out what's still lying around unfinished

  • Reads credentials

6 other projects use a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

3 rules. 0 are common, 3 are distinctive, and 0 are very specific to this project.

  • 0

    Common

    Most of the 5 edited versions we read keep these.

  • 3

    Distinctive

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    If a forge CLI is available, pull request state too — it resolves most ambiguity for free:

    line 34
  2. Distinctive:

    Every branch and worktree lands in exactly one bucket.

    line 44
  3. Distinctive:

    Reporting instead of acting: If a branch is provably in base, delete it.

    line 142

“Common” is measured: kept by at least 6 in 10 of the 5 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Repo Cleanup

Rigid. Survey everything before deleting anything, and work the buckets in order — cruft, then quick wins, then the report. The classification rules and the safety rules are not optional. How much of a quick win you finish is a judgment call; what counts as deletable is not.

The two ways this goes wrong

  • Deleting live work. Trust-destroying, even though the reflog usually saves you.
  • Assessing everything and deleting nothing. The common failure. A tidy report that leaves forty stale branches in place has cleaned up exactly zero.

The order below avoids both: remove what is provably redundant without ceremony, then spend the remaining effort on the few things that need a person.

Step 1: Survey (read-only)

Gather the whole picture before touching anything. Nothing here modifies the repo.

base=$(git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null | sed 's|^origin/||')
base=${base:-main}
git fetch --prune origin
git worktree list
git for-each-ref --sort=-committerdate \
  --format='%(refname:short)|%(upstream:track)|%(committerdate:relative)' refs/heads
git stash list
git status --porcelain

git fetch --prune only drops remote-tracking refs for branches deleted on the forge. It never touches a local branch, and it is what makes the [gone] marker below trustworthy.

If a forge CLI is available, pull request state too — it resolves most ambiguity for free:

gh pr list --state all --limit 100 --json number,title,state,headRefName,isDraft

Artifact: a list of every worktree, local branch, and stash, each with its tracking state, age, and PR state if known.

Step 2: Classify

Every branch and worktree lands in exactly one bucket. Do not act until each one has a bucket.

Cruft — delete, no confirmation needed

The work is already in the base branch, or was never work at all:

  • Upstream [gone]. git for-each-ref prints [gone] when the remote branch was deleted — which is what a forge does on merge. Strongest single signal, and it covers the squash and rebase merges that --merged misses.
  • Merged by ancestry. git branch --merged "$base" lists them. Reliable, but only catches true merges.
  • Squash-merged. The content is in base under a different commit, so --merged will not see it. Replay the branch tree onto the merge base and ask whether that patch already exists:

    mb=$(git merge-base "$base" "$b")
    synth=$(git commit-tree "$b^{tree}" -p "$mb" -m _)
    git cherry "$base" "$synth" | grep -q '^+' || echo "$b: content already in $base"

    git cherry prints a line per commit — - when an equivalent patch is already upstream, + when it is not. Test for the absence of + lines; the output is never empty, so an emptiness check would never fire. Both ways this test fails err toward keeping the branch: a branch with no unique content produces an empty patch and reads as +, and a squash applied onto a base that has since moved may no longer match by patch-id. Neither can cause a wrong deletion.

  • No unique commits. git log --oneline "$base".."$b" is empty — the branch is a bookmark pointing at history base already has.
  • Worktrees whose branch is any of the above and whose git status --porcelain --ignored is clean. Use --ignored, not plain --porcelain. Git refuses to remove a worktree holding modified or untracked files, but ignored files are not covered by either protection: a worktree whose only contents are a gitignored .env, a local database, or a build cache reads as clean, and git worktree remove deletes it without complaint.
  • Worktree registrations whose directory no longer exists — git worktree prune clears these.

Quick win — finish it now

Small, close to done, and nothing about it needs a decision:

  • A branch whose diff is under roughly a hundred lines, whose tests pass, and which is one commit message or one push from a PR.
  • A branch that is only behind base — merge forward, confirm it still builds, push.
  • A stash that applies cleanly and is obviously the same work as an existing branch.

Finishing means finishing: run the tests, commit, push, open the PR. A quick win left at 90% is now a second piece of cruft.

Needs a person — report, do not act

  • Unmerged commits that are not obviously superseded.
  • Anything with a conflict, a failing test, or an open PR carrying review comments.
  • A stash you cannot attribute to a branch.
  • Anything months old with real content — age suggests abandonment, but only the author knows.

Old and abandoned is a recommendation, never an action. Present these with the evidence and the exact command, and let the human run it.

Step 3: Act, cruft first

Record every branch sha before deleting. This is the line that makes the rest safe to run:

git for-each-ref --format='%(refname:short) %(objectname)' refs/heads > branches-before.txt

Then clear the cruft in one pass:

git worktree remove <path>
git worktree prune
git branch -d <branch>
git remote prune origin

Use git branch -d, never -D, in the cruft pass. If -d refuses, the branch was misclassified — move it to "needs a person" rather than forcing it. -D is only for a branch the human has explicitly named. Likewise git worktree remove refuses a dirty tree; do not reach for --force to get past that.

Then do the quick wins, one at a time, verifying each before starting the next.

Safety rules

  • Never delete a worktree with uncommitted changes, ignored files included. If git status --porcelain --ignored is non-empty, it is not cruft regardless of its branch state. Commit tracked work to its own branch first, or report it.
  • Ignored files are the one thing here that does not come back. Git never had a copy, so there is no reflog and no sha to restore from — a deleted .env or local database is simply gone. Git's own refusal does not cover them, which makes --ignored in the check above the only thing standing between this skill and permanent loss. Report such a worktree and let the human clear the directory.
  • Never git stash drop or git stash clear. Stashes have no reflog protection worth relying on, and the stack is shared across every worktree in the repo — the entry you drop may belong to someone else's session. Report them.
  • Never delete the branch you are standing on, the base branch, or a release branch, whatever the classification says.
  • Never rewrite published history to make a branch look mergeable — no force-push, no rebase of anything already shared on the remote.
  • Deleting a local branch is recoverable from the sha you recorded: git branch <name> <sha>. Say so in the report, with the shas. Deleting a remote branch is not nearly as recoverable — that always needs confirmation first.

Step 4: Report

Three sections, in the order the work happened:

Removed (recoverable — git branch <name> <sha>):
- feat/old-thing            a1b2c3d   squash-merged, PR #41
- worktree ../wt-spike      —         branch gone, tree clean

Finished:
- fix/typo-in-readme        pushed, PR #58 opened

Needs you:
- feat/big-refactor         31 commits, 4 months old, no PR. Delete with:
                              git branch -D feat/big-refactor   # sha e4f5a6b
- stash@{0}                 "wip auth" — matches no branch

State counts plainly: how many deleted, how many finished, how many left. If nothing was deletable, say that — a clean repo is a valid result, and padding the report with speculative deletions is worse than a short one.

Anti-patterns

  • Reaching for -D or --force when the safe flag refuses. The refusal is information. Reclassify instead.
  • Trusting git branch --merged alone. It misses every squash and rebase merge, which on most repos is most of them.
  • Surveying without fetching. Stale remote-tracking refs make merged branches look unmerged and deleted ones look live.
  • Batch-deleting from a glob. git branch | grep feat/ | xargs git branch -D deletes by name pattern, not by evidence. Classify each branch or leave it.
  • Turning cleanup into refactoring. Finishing a quick win means finishing what is there, not improving it. If it needs design work, it is not a quick win.
  • Reporting instead of acting. If a branch is provably in base, delete it. That is what was asked for.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.