Git Cleanup Safely clean up accumulated git worktrees and local branches by categorizing them into: safely deletable (merged), potentially related (similar themes), and active work (keep). When to Use - When the user has accumulated many local branches and worktrees - When branches have been merged but not cleaned up locally - When remote branches have been deleted but local tracking branches remain When NOT to Use - Do not use for remote branch management (this is local cleanup only) - Do not use for repository maintenance tasks like gc or prune - Not designed for headless or non-interactive…

\n\n# List all local branches with tracking info\ngit branch -vv\n\n# List all worktrees\ngit worktree list\n\n# Fetch and prune to sync remote state\ngit fetch --prune\n\n# Get merged branches (into default branch)\ngit branch --merged \"$default_branch\"\n\n# Get recent PR merge history (squash-merge detection)\ngit log --oneline \"$default_branch\" | grep -iE \"#[0-9]+\" | head -30\n\n# For EACH non-protected branch, get unique commits and sync status\nfor branch in $(git branch --format='%(refname:short)' \\\n | grep -vE \"$protected\"); do\n echo \"=== $branch ===\"\n echo \"Commits not in $default_branch:\"\n git log --oneline \"$default_branch\"..\"$branch\" 2>/dev/null \\\n | head -5\n echo \"Commits not pushed to remote:\"\n git log --oneline \"origin/$branch\"..\"$branch\" 2>/dev/null \\\n | head -5 || echo \"(no remote tracking)\"\ndone\n```\n\n**Note on branch names:** Git branch names can contain characters that break shell expansion. Always quote `\"$branch\"` in commands.\n\n### Phase 2: Group Related Branches\n\n**Do this BEFORE individual categorization.**\n\nIdentify branch groups by shared prefixes:\n\n```bash\n# List branches and extract prefixes\ngit branch --format='%(refname:short)' | sed 's/-[^-]*$//' | sort | uniq -c | sort -rn\n```\n\nFor each group with 2+ branches:\n\n1. **Compare commit histories** - Which branches contain commits from others?\n2. **Find merge evidence** - Which PRs incorporated work from this group?\n3. **Identify the \"final\" branch** - Usually the most recent or most complete\n4. **Mark superseded branches** - Older iterations whose work is in main or in a newer branch\n\n**SUPERSEDED requires evidence, not just shared prefix:**\n- A PR merged the work into main, OR\n- A newer branch contains all commits from the older branch\n- Name prefix alone is NOT sufficient — similarly named branches may contain independent work\n\nExample analysis for `feature/api-*` branches:\n\n```markdown\n### Related Branch Group: feature/api-*\n\n| Branch | Commits | PR Merged | Status |\n|--------|---------|-----------|--------|\n| feature/api | 12 | #29 (initial API) | Superseded - work in main |\n| feature/api-v2 | 8 | #45 (API improvements) | Superseded - work in main |\n| feature/api-refactor | 5 | #67 (refactor) | Superseded - work in main |\n| feature/api-final | 4 | None found | Superseded by above PRs |\n\n**Recommendation:** All 4 branches can be deleted - work incorporated via PRs #29, #45, #67\n```\n\n### Phase 3: Categorize Remaining Branches\n\nFor branches NOT in a related group, categorize individually:\n\n```\nIs branch merged into default branch?\n├─ YES → SAFE_TO_DELETE (use -d)\n└─ NO → Is tracking a remote?\n ├─ YES → Remote deleted? ([gone])\n │ ├─ YES → Was work squash-merged? (check main for PR)\n │ │ ├─ YES → SQUASH_MERGED (use -D)\n │ │ └─ NO → REMOTE_GONE (needs review)\n │ └─ NO → Local ahead of remote? (check: git log origin/\u003cbranch>..\u003cbranch>)\n │ ├─ YES (has output) → UNPUSHED_WORK (keep)\n │ └─ NO (empty output) → SYNCED_WITH_REMOTE (keep)\n └─ NO → Has unique commits?\n ├─ YES → LOCAL_WORK (keep)\n └─ NO → SAFE_TO_DELETE (use -d)\n```\n\n**Category definitions:**\n\n| Category | Meaning | Delete Command |\n|----------|---------|----------------|\n| SAFE_TO_DELETE | Merged into default branch | `git branch -d` |\n| SQUASH_MERGED | Work incorporated via squash merge | `git branch -D` |\n| SUPERSEDED | Part of a group, work verified in main via PR or in newer branch | `git branch -D` |\n| REMOTE_GONE | Remote deleted, work NOT found in main | Review needed |\n| UNPUSHED_WORK | Has commits not pushed to remote | Keep |\n| LOCAL_WORK | Untracked branch with unique commits | Keep |\n| SYNCED_WITH_REMOTE | Up to date with remote | Keep |\n\n### Phase 4: Dirty State Detection\n\nCheck ALL worktrees and current directory for uncommitted changes:\n\n```bash\n# For each worktree path\ngit -C \u003cworktree-path> status --porcelain\n\n# For current directory\ngit status --porcelain\n```\n\n**Display warnings prominently:**\n\n```markdown\nWARNING: ../proj-auth has uncommitted changes:\n M src/auth.js\n ?? new-file.txt\n\nThese changes will be LOST if you remove this worktree.\n```\n\n### GATE 1: Present Complete Analysis\n\nPresent everything in ONE comprehensive view. Group related branches together:\n\n```markdown\n## Git Cleanup Analysis\n\n### Related Branch Groups\n\n**Group: feature/api-* (4 branches)**\n| Branch | Status | Evidence |\n|--------|--------|----------|\n| feature/api | Superseded | Work merged in PR #29 |\n| feature/api-v2 | Superseded | Work merged in PR #45 |\n| feature/api-refactor | Superseded | Work merged in PR #67 |\n| feature/api-final | Superseded | Older iteration, diverged |\n\nRecommendation: Delete all 4 (work is in main)\n\n---\n\n### Individual Branches\n\n**Safe to Delete (merged with -d)**\n| Branch | Merged Into |\n|--------|-------------|\n| fix/typo | main |\n\n**Safe to Delete (squash-merged, requires -D)**\n| Branch | Merged As |\n|--------|-----------|\n| feature/login | PR #42 |\n\n**Needs Review ([gone] remotes, no PR found)**\n| Branch | Last Commit |\n|--------|-------------|\n| experiment/old | abc1234 \"WIP something\" |\n\n**Keep (active work)**\n| Branch | Status |\n|--------|--------|\n| wip/new-feature | 5 unpushed commits |\n\n### Worktrees\n| Path | Branch | Status |\n|------|--------|--------|\n| ../proj-auth | feature/auth | STALE (merged) |\n\n---\n\n**Summary:**\n- 4 related branches (feature/api-*) - recommend delete all\n- 1 merged branch - safe to delete\n- 1 squash-merged branch - safe to delete\n- 1 needs review\n- 1 to keep\n\nWhich would you like to clean up?\n```\n\nUse AskUserQuestion with clear options:\n- Delete all recommended (groups + merged + squash-merged)\n- Delete specific groups/categories\n- Let me pick individual branches\n\n**Do not proceed until user responds.**\n\n### GATE 2: Final Confirmation with Exact Commands\n\nShow the EXACT commands that will run, with correct flags:\n\n```markdown\nI will execute:\n\n# Merged branches (safe delete)\ngit branch -d fix/typo\n\n# Squash-merged branches (force delete - work is in main via PRs)\ngit branch -D feature/login\ngit branch -D feature/api\ngit branch -D feature/api-v2\ngit branch -D feature/api-refactor\ngit branch -D feature/api-final\n\n# Worktrees\ngit worktree remove ../proj-auth\n\nConfirm? (yes/no)\n```\n\n**IMPORTANT:** This is the ONLY confirmation needed for deletion. Do not add extra confirmations if `-D` is required.\n\n### Phase 5: Execute\n\nRun each deletion as a **separate command** so partial failures don't block remaining deletions. Report the result of each:\n\n```bash\ngit branch -d fix/typo\ngit branch -D feature/login\ngit branch -D feature/api\ngit branch -D feature/api-v2\ngit branch -D feature/api-refactor\ngit branch -D feature/api-final\ngit worktree remove ../proj-auth\n```\n\nIf a deletion fails, report the error and continue with remaining deletions.\n\n### Phase 6: Report\n\n```markdown\n## Cleanup Complete\n\n### Deleted\n- fix/typo\n- feature/login\n- feature/api\n- feature/api-v2\n- feature/api-refactor\n- feature/api-final\n- Worktree: ../proj-auth\n\n### Remaining (4 branches)\n| Branch | Status |\n|--------|--------|\n| main | current |\n| wip/new-feature | active work |\n| experiment/old | needs review |\n```\n\n## Safety Rules\n\n1. **Never invoke automatically** - Only run when user explicitly uses `/git-cleanup`\n2. **Two confirmation gates only** - Analysis review, then deletion confirmation\n3. **Use correct delete command** - `-d` for merged, `-D` for squash-merged/superseded\n4. **Never touch protected branches** - main, master, develop, release/* (filtered programmatically)\n5. **Block dirty worktree removal** - Refuse without explicit data loss acknowledgment\n6. **Group related branches** - Don't scatter them across categories\n\n## Rationalizations to Reject\n\nThese are common shortcuts that lead to data loss. Reject them:\n\n| Rationalization | Why It's Wrong |\n|-----------------|----------------|\n| \"The branch is old, it's probably safe to delete\" | Age doesn't indicate merge status. Old branches may contain unmerged work. |\n| \"I can recover from reflog if needed\" | Reflog entries expire. Users often don't know how to use reflog. Don't rely on it as a safety net. |\n| \"It's just a local branch, nothing important\" | Local branches may contain the only copy of work not pushed anywhere. |\n| \"The PR was merged, so the branch is safe\" | Squash merges don't preserve branch history. Verify the *specific* commits were incorporated. |\n| \"I'll just delete all the `[gone]` branches\" | `[gone]` only means the remote was deleted. The local branch may have unpushed commits. |\n| \"The user seems to want everything deleted\" | Always present analysis first. Let the user choose what to delete. |\n| \"The branch has commits not in main, so it has unpushed work\" | \"Not in main\" ≠ \"not pushed\". A branch can be synced with its remote but not merged to main. Always check `git log origin/\u003cbranch>..\u003cbranch>`. |\n---","attachment_filenames":[],"attachments":[],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Git Cleanup","type":"text"}]},{"type":"paragraph","content":[{"text":"Safely clean up accumulated git worktrees and local branches by categorizing them into: safely deletable (merged), potentially related (similar themes), and active work (keep).","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When to Use","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"When the user has accumulated many local branches and worktrees","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"When branches have been merged but not cleaned up locally","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"When remote branches have been deleted but local tracking branches remain","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When NOT to Use","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Do not use for remote branch management (this is local cleanup only)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Do not use for repository maintenance tasks like gc or prune","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Not designed for headless or non-interactive automation (requires user confirmations at two gates)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Core Principle: SAFETY FIRST","type":"text"}]},{"type":"paragraph","content":[{"text":"Never delete anything without explicit user confirmation.","type":"text","marks":[{"type":"strong"}]},{"text":" This skill uses a gated workflow where users must approve each step before any destructive action.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Critical Implementation Notes","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Squash-Merged Branches Require Force Delete","type":"text"}]},{"type":"paragraph","content":[{"text":"IMPORTANT:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"git branch -d","type":"text","marks":[{"type":"code_inline"}]},{"text":" will ALWAYS fail for squash-merged branches because git cannot detect that the work was incorporated. This is expected behavior, not an error.","type":"text"}]},{"type":"paragraph","content":[{"text":"When you identify a branch as squash-merged:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Plan to use ","type":"text"},{"text":"git branch -D","type":"text","marks":[{"type":"code_inline"}]},{"text":" (force delete) from the start","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Do NOT try ","type":"text"},{"text":"git branch -d","type":"text","marks":[{"type":"code_inline"}]},{"text":" first and then ask again for ","type":"text"},{"text":"-D","type":"text","marks":[{"type":"code_inline"}]},{"text":" - this wastes user confirmations","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"In the confirmation step, show ","type":"text"},{"text":"git branch -D","type":"text","marks":[{"type":"code_inline"}]},{"text":" for squash-merged branches","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Group Related Branches BEFORE Categorization","type":"text"}]},{"type":"paragraph","content":[{"text":"MANDATORY:","type":"text","marks":[{"type":"strong"}]},{"text":" Before categorizing individual branches, group them by name prefix:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Extract common prefixes from branch names\n# e.g., feature/auth-*, feature/api-*, fix/login-*","type":"text"}]},{"type":"paragraph","content":[{"text":"Branches sharing a prefix (e.g., ","type":"text"},{"text":"feature/api","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"feature/api-v2","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"feature/api-refactor","type":"text","marks":[{"type":"code_inline"}]},{"text":") are almost certainly related iterations. Analyze them as a group:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Find the oldest and newest by commit date","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check if newer branches contain commits from older ones","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check which PRs merged work from each","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Determine if older branches are superseded","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Present related branches together with a clear recommendation, not scattered across categories.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Thorough PR History Investigation","type":"text"}]},{"type":"paragraph","content":[{"text":"Don't rely on simple keyword matching. For ","type":"text"},{"text":"[gone]","type":"text","marks":[{"type":"code_inline"}]},{"text":" branches:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# 1. Get the branch's commits that aren't in default branch\ngit log --oneline \"$default_branch\"..\"$branch\"\n\n# 2. Search default branch for PRs that incorporated this work\n# Search by: branch name, commit message keywords, PR numbers\ngit log --oneline \"$default_branch\" | grep -iE \"(branch-name|keyword|#[0-9]+)\"\n\n# 3. For related branch groups, trace which PRs merged which work\ngit log --oneline \"$default_branch\" | grep -iE \"(#[0-9]+)\" | head -20","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Workflow","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Phase 1: Comprehensive Analysis","type":"text"}]},{"type":"paragraph","content":[{"text":"Gather ALL information upfront before any categorization:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Get default branch name\ndefault_branch=$(git symbolic-ref refs/remotes/origin/HEAD \\\n 2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo \"main\")\n\n# Protected branches - never analyze or delete\nprotected='^(main|master|develop|release/.*)

Git Cleanup Safely clean up accumulated git worktrees and local branches by categorizing them into: safely deletable (merged), potentially related (similar themes), and active work (keep). When to Use - When the user has accumulated many local branches and worktrees - When branches have been merged but not cleaned up locally - When remote branches have been deleted but local tracking branches remain When NOT to Use - Do not use for remote branch management (this is local cleanup only) - Do not use for repository maintenance tasks like gc or prune - Not designed for headless or non-interactive…

\n\n# List all local branches with tracking info\ngit branch -vv\n\n# List all worktrees\ngit worktree list\n\n# Fetch and prune to sync remote state\ngit fetch --prune\n\n# Get merged branches (into default branch)\ngit branch --merged \"$default_branch\"\n\n# Get recent PR merge history (squash-merge detection)\ngit log --oneline \"$default_branch\" | grep -iE \"#[0-9]+\" | head -30\n\n# For EACH non-protected branch, get unique commits and sync status\nfor branch in $(git branch --format='%(refname:short)' \\\n | grep -vE \"$protected\"); do\n echo \"=== $branch ===\"\n echo \"Commits not in $default_branch:\"\n git log --oneline \"$default_branch\"..\"$branch\" 2>/dev/null \\\n | head -5\n echo \"Commits not pushed to remote:\"\n git log --oneline \"origin/$branch\"..\"$branch\" 2>/dev/null \\\n | head -5 || echo \"(no remote tracking)\"\ndone","type":"text"}]},{"type":"paragraph","content":[{"text":"Note on branch names:","type":"text","marks":[{"type":"strong"}]},{"text":" Git branch names can contain characters that break shell expansion. Always quote ","type":"text"},{"text":"\"$branch\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" in commands.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Phase 2: Group Related Branches","type":"text"}]},{"type":"paragraph","content":[{"text":"Do this BEFORE individual categorization.","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"Identify branch groups by shared prefixes:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# List branches and extract prefixes\ngit branch --format='%(refname:short)' | sed 's/-[^-]*$//' | sort | uniq -c | sort -rn","type":"text"}]},{"type":"paragraph","content":[{"text":"For each group with 2+ branches:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Compare commit histories","type":"text","marks":[{"type":"strong"}]},{"text":" - Which branches contain commits from others?","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Find merge evidence","type":"text","marks":[{"type":"strong"}]},{"text":" - Which PRs incorporated work from this group?","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Identify the \"final\" branch","type":"text","marks":[{"type":"strong"}]},{"text":" - Usually the most recent or most complete","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Mark superseded branches","type":"text","marks":[{"type":"strong"}]},{"text":" - Older iterations whose work is in main or in a newer branch","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"SUPERSEDED requires evidence, not just shared prefix:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"A PR merged the work into main, OR","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"A newer branch contains all commits from the older branch","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Name prefix alone is NOT sufficient — similarly named branches may contain independent work","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Example analysis for ","type":"text"},{"text":"feature/api-*","type":"text","marks":[{"type":"code_inline"}]},{"text":" branches:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"markdown"},"content":[{"text":"### Related Branch Group: feature/api-*\n\n| Branch | Commits | PR Merged | Status |\n|--------|---------|-----------|--------|\n| feature/api | 12 | #29 (initial API) | Superseded - work in main |\n| feature/api-v2 | 8 | #45 (API improvements) | Superseded - work in main |\n| feature/api-refactor | 5 | #67 (refactor) | Superseded - work in main |\n| feature/api-final | 4 | None found | Superseded by above PRs |\n\n**Recommendation:** All 4 branches can be deleted - work incorporated via PRs #29, #45, #67","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Phase 3: Categorize Remaining Branches","type":"text"}]},{"type":"paragraph","content":[{"text":"For branches NOT in a related group, categorize individually:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Is branch merged into default branch?\n├─ YES → SAFE_TO_DELETE (use -d)\n└─ NO → Is tracking a remote?\n ├─ YES → Remote deleted? ([gone])\n │ ├─ YES → Was work squash-merged? (check main for PR)\n │ │ ├─ YES → SQUASH_MERGED (use -D)\n │ │ └─ NO → REMOTE_GONE (needs review)\n │ └─ NO → Local ahead of remote? (check: git log origin/\u003cbranch>..\u003cbranch>)\n │ ├─ YES (has output) → UNPUSHED_WORK (keep)\n │ └─ NO (empty output) → SYNCED_WITH_REMOTE (keep)\n └─ NO → Has unique commits?\n ├─ YES → LOCAL_WORK (keep)\n └─ NO → SAFE_TO_DELETE (use -d)","type":"text"}]},{"type":"paragraph","content":[{"text":"Category definitions:","type":"text","marks":[{"type":"strong"}]}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Category","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Meaning","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Delete Command","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"SAFE_TO_DELETE","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Merged into default branch","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"git branch -d","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"SQUASH_MERGED","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Work incorporated via squash merge","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"git branch -D","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"SUPERSEDED","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Part of a group, work verified in main via PR or in newer branch","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"git branch -D","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"REMOTE_GONE","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Remote deleted, work NOT found in main","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Review needed","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"UNPUSHED_WORK","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Has commits not pushed to remote","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Keep","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"LOCAL_WORK","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Untracked branch with unique commits","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Keep","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"SYNCED_WITH_REMOTE","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Up to date with remote","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Keep","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Phase 4: Dirty State Detection","type":"text"}]},{"type":"paragraph","content":[{"text":"Check ALL worktrees and current directory for uncommitted changes:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# For each worktree path\ngit -C \u003cworktree-path> status --porcelain\n\n# For current directory\ngit status --porcelain","type":"text"}]},{"type":"paragraph","content":[{"text":"Display warnings prominently:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"markdown"},"content":[{"text":"WARNING: ../proj-auth has uncommitted changes:\n M src/auth.js\n ?? new-file.txt\n\nThese changes will be LOST if you remove this worktree.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"GATE 1: Present Complete Analysis","type":"text"}]},{"type":"paragraph","content":[{"text":"Present everything in ONE comprehensive view. Group related branches together:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"markdown"},"content":[{"text":"## Git Cleanup Analysis\n\n### Related Branch Groups\n\n**Group: feature/api-* (4 branches)**\n| Branch | Status | Evidence |\n|--------|--------|----------|\n| feature/api | Superseded | Work merged in PR #29 |\n| feature/api-v2 | Superseded | Work merged in PR #45 |\n| feature/api-refactor | Superseded | Work merged in PR #67 |\n| feature/api-final | Superseded | Older iteration, diverged |\n\nRecommendation: Delete all 4 (work is in main)\n\n---\n\n### Individual Branches\n\n**Safe to Delete (merged with -d)**\n| Branch | Merged Into |\n|--------|-------------|\n| fix/typo | main |\n\n**Safe to Delete (squash-merged, requires -D)**\n| Branch | Merged As |\n|--------|-----------|\n| feature/login | PR #42 |\n\n**Needs Review ([gone] remotes, no PR found)**\n| Branch | Last Commit |\n|--------|-------------|\n| experiment/old | abc1234 \"WIP something\" |\n\n**Keep (active work)**\n| Branch | Status |\n|--------|--------|\n| wip/new-feature | 5 unpushed commits |\n\n### Worktrees\n| Path | Branch | Status |\n|------|--------|--------|\n| ../proj-auth | feature/auth | STALE (merged) |\n\n---\n\n**Summary:**\n- 4 related branches (feature/api-*) - recommend delete all\n- 1 merged branch - safe to delete\n- 1 squash-merged branch - safe to delete\n- 1 needs review\n- 1 to keep\n\nWhich would you like to clean up?","type":"text"}]},{"type":"paragraph","content":[{"text":"Use AskUserQuestion with clear options:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Delete all recommended (groups + merged + squash-merged)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Delete specific groups/categories","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Let me pick individual branches","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Do not proceed until user responds.","type":"text","marks":[{"type":"strong"}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"GATE 2: Final Confirmation with Exact Commands","type":"text"}]},{"type":"paragraph","content":[{"text":"Show the EXACT commands that will run, with correct flags:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"markdown"},"content":[{"text":"I will execute:\n\n# Merged branches (safe delete)\ngit branch -d fix/typo\n\n# Squash-merged branches (force delete - work is in main via PRs)\ngit branch -D feature/login\ngit branch -D feature/api\ngit branch -D feature/api-v2\ngit branch -D feature/api-refactor\ngit branch -D feature/api-final\n\n# Worktrees\ngit worktree remove ../proj-auth\n\nConfirm? (yes/no)","type":"text"}]},{"type":"paragraph","content":[{"text":"IMPORTANT:","type":"text","marks":[{"type":"strong"}]},{"text":" This is the ONLY confirmation needed for deletion. Do not add extra confirmations if ","type":"text"},{"text":"-D","type":"text","marks":[{"type":"code_inline"}]},{"text":" is required.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Phase 5: Execute","type":"text"}]},{"type":"paragraph","content":[{"text":"Run each deletion as a ","type":"text"},{"text":"separate command","type":"text","marks":[{"type":"strong"}]},{"text":" so partial failures don't block remaining deletions. Report the result of each:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"git branch -d fix/typo\ngit branch -D feature/login\ngit branch -D feature/api\ngit branch -D feature/api-v2\ngit branch -D feature/api-refactor\ngit branch -D feature/api-final\ngit worktree remove ../proj-auth","type":"text"}]},{"type":"paragraph","content":[{"text":"If a deletion fails, report the error and continue with remaining deletions.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Phase 6: Report","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"markdown"},"content":[{"text":"## Cleanup Complete\n\n### Deleted\n- fix/typo\n- feature/login\n- feature/api\n- feature/api-v2\n- feature/api-refactor\n- feature/api-final\n- Worktree: ../proj-auth\n\n### Remaining (4 branches)\n| Branch | Status |\n|--------|--------|\n| main | current |\n| wip/new-feature | active work |\n| experiment/old | needs review |","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Safety Rules","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Never invoke automatically","type":"text","marks":[{"type":"strong"}]},{"text":" - Only run when user explicitly uses ","type":"text"},{"text":"/git-cleanup","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Two confirmation gates only","type":"text","marks":[{"type":"strong"}]},{"text":" - Analysis review, then deletion confirmation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use correct delete command","type":"text","marks":[{"type":"strong"}]},{"text":" - ","type":"text"},{"text":"-d","type":"text","marks":[{"type":"code_inline"}]},{"text":" for merged, ","type":"text"},{"text":"-D","type":"text","marks":[{"type":"code_inline"}]},{"text":" for squash-merged/superseded","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Never touch protected branches","type":"text","marks":[{"type":"strong"}]},{"text":" - main, master, develop, release/* (filtered programmatically)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Block dirty worktree removal","type":"text","marks":[{"type":"strong"}]},{"text":" - Refuse without explicit data loss acknowledgment","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Group related branches","type":"text","marks":[{"type":"strong"}]},{"text":" - Don't scatter them across categories","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Rationalizations to Reject","type":"text"}]},{"type":"paragraph","content":[{"text":"These are common shortcuts that lead to data loss. Reject them:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Rationalization","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Why It's Wrong","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\"The branch is old, it's probably safe to delete\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Age doesn't indicate merge status. Old branches may contain unmerged work.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\"I can recover from reflog if needed\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Reflog entries expire. Users often don't know how to use reflog. Don't rely on it as a safety net.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\"It's just a local branch, nothing important\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Local branches may contain the only copy of work not pushed anywhere.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\"The PR was merged, so the branch is safe\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Squash merges don't preserve branch history. Verify the ","type":"text"},{"text":"specific","type":"text","marks":[{"type":"em"}]},{"text":" commits were incorporated.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\"I'll just delete all the ","type":"text"},{"text":"[gone]","type":"text","marks":[{"type":"code_inline"}]},{"text":" branches\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"[gone]","type":"text","marks":[{"type":"code_inline"}]},{"text":" only means the remote was deleted. The local branch may have unpushed commits.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\"The user seems to want everything deleted\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Always present analysis first. Let the user choose what to delete.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\"The branch has commits not in main, so it has unpushed work\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\"Not in main\" ≠ \"not pushed\". A branch can be synced with its remote but not merged to main. Always check ","type":"text"},{"text":"git log origin/\u003cbranch>..\u003cbranch>","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"git-cleanup","author":"@skillopedia","source":{"stars":5503,"repo_name":"skills","origin_url":"https://github.com/trailofbits/skills/blob/HEAD/plugins/git-cleanup/skills/git-cleanup/SKILL.md","repo_owner":"trailofbits","body_sha256":"5613682e30b29b09749dc627ccbb14981af2f827b2a37098e910911fecb1567e","cluster_key":"864c42bb28921f14f7a2895e04cb364ee9a886233283d67bf88e917cf6691e97","clean_bundle":{"format":"clean-skill-bundle-v1","source":"trailofbits/skills/plugins/git-cleanup/skills/git-cleanup/SKILL.md","bundle_sha256":"813f47f3949cf8caf66b70edeb17e5dc69d6f60fd4283838e13d0dc362aaa0d7","attachment_count":0,"text_attachments":0,"binary_attachments":0},"cluster_size":1,"skill_md_path":"plugins/git-cleanup/skills/git-cleanup/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"software-engineering","category_label":"Engineering"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"software-engineering","import_tag":"clean-skills-v1","description":"Safely analyzes and cleans up local git branches and worktrees by categorizing them as merged, squash-merged, superseded, or active work.","allowed-tools":"Bash Read Grep AskUserQuestion","disable-model-invocation":true}},"renderedAt":1782986542180}

Git Cleanup Safely clean up accumulated git worktrees and local branches by categorizing them into: safely deletable (merged), potentially related (similar themes), and active work (keep). When to Use - When the user has accumulated many local branches and worktrees - When branches have been merged but not cleaned up locally - When remote branches have been deleted but local tracking branches remain When NOT to Use - Do not use for remote branch management (this is local cleanup only) - Do not use for repository maintenance tasks like gc or prune - Not designed for headless or non-interactive…