Repo hygiene action-router: scan (inventory), caches, build, git (prune/branch audit), stash (stash audit/triage), tree (destructive fresh-pull reset), tree-batch (multi-repo tree reset with skip-list + dirty guard), all, and fleet batch forms of the selective tiers (caches-batch / build-batch / git-batch / all-batch over many repos behind one gate). Bare invocation detects intent from conversation or shows a menu. Dry-run-first; destructive actions require explicit confirmation. Use when: 'c...
Scanned 9/2/2026
Install to Claude Code
npx -y skills add melodic-software/claude-code-plugins --skill clean --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Clean?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/melodic-software-clean-claude-code-plugins)More formats (shields.io, HTML) on the badges page.
---
description: "Repo hygiene action-router: scan (inventory), caches, build, git (prune/branch audit), stash (stash audit/triage), tree (destructive fresh-pull reset), tree-batch (multi-repo tree reset with skip-list + dirty guard), all, and fleet batch forms of the selective tiers (caches-batch / build-batch / git-batch / all-batch over many repos behind one gate). Bare invocation detects intent from conversation or shows a menu. Dry-run-first; destructive actions require explicit confirmation. Use when: 'clean', 'free up disk space', 'remove caches', 'clear build artifacts', 'fresh pull', 'fresh clone state', 'reset to origin', 'reset all my repos', 'clean caches across all repos', 'clear build artifacts across all my repos', 'prune git across the fleet', 'stale branches', 'clean up my stashes', 'repo hygiene'. Skip: removing git worktree directories (a worktree-management tool handles those)."
user-invocable: true
disable-model-invocation: false
argument-hint: "[scan|caches|build|git|stash|tree|tree-batch|all|caches-batch|build-batch|git-batch|all-batch|aliases…] (bare → menu or auto-detect)"
allowed-tools:
# Read-only scripts only, one narrow rule each. The mutating scripts
# (clean-caches / clean-build / git-prune / git-tree-reset[-batch] /
# remove-path / clean-batch) are deliberately NOT pre-approved: they stay
# behind the PreToolUse destructive guard and the permission flow, which is
# where the dry-run-then-confirm contract below is actually enforced.
- Bash(${CLAUDE_SKILL_DIR}/scripts/resolve-clean-action.sh:*)
- Bash(${CLAUDE_SKILL_DIR}/scripts/scan.sh:*)
- Bash(${CLAUDE_SKILL_DIR}/scripts/preflight.sh:*)
- Bash(${CLAUDE_SKILL_DIR}/scripts/git-branch-audit.sh:*)
- Bash(${CLAUDE_SKILL_DIR}/scripts/git-stash-audit.sh:*)
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
# Shell form (no `args`) on purpose: exec form resolves `command` via PATH,
# which on Windows finds the WSL relay (System32\bash.exe) and the guard
# never launches, a silent fail-open. Shell form with `shell: bash` makes
# Claude Code itself resolve Git Bash on every platform.
- type: command
command: "bash \"${CLAUDE_PLUGIN_ROOT}/skills/clean/scripts/destructive-guard.sh\""
shell: bash
shell: bash
metadata:
workflow-stage: anytime
summary: Clean caches, build artifacts, stale branches, and stashes per repo
---
## Pre-computed context
Uncommitted changes (empty = none): !`{ git status --porcelain 2>/dev/null || echo "(git status unavailable)"; } | head -5`
Current branch: !`git branch --show-current 2>/dev/null || echo "unknown"`
## Purpose
Return the repo toward a known-good state. **Selective tiers** (`scan`, `caches`, `build`, `git`, `all`) remove *artifacts* while preserving secrets, runtime deps, and skill data. **`tree`** is the destructive tier. `reset --hard` + `clean -fdx`, but **safe-by-default**: it preserves the same secrets / runtime-deps / skill-data classes unless you opt in via `--include-deps` / `--include-secrets`.
Bare invocation never mutates silently: resolve intent → dry-run → user confirmation → `--apply`. Full menu, aliases, and confirmation matrix: [context/action-router.md](context/action-router.md).
Bundled-script invocation uses two deliberate forms. Paired `${CLAUDE_SKILL_DIR}` in this file (matches `allowed-tools`) and interpreter-led `${CLAUDE_PLUGIN_ROOT}` in routed `context/*.md` detail files. Rationale and decide-lane verdict: [reference/invocation-forms.md](reference/invocation-forms.md).
## Arguments
`$ARGUMENTS`. Cleanup action or alias. Resolve first:
```bash
${CLAUDE_SKILL_DIR}/scripts/resolve-clean-action.sh $ARGUMENTS
```
When a leading action token is followed by free text, the resolver also emits a `Note: <text>` line. That note is **advisory context you must address**, a question to answer (e.g. "does this include stashes?") or a live-session constraint to honor (e.g. "6-7 live sessions, mind WIP"), not part of action selection. Surface it and act on it alongside the resolved action; never silently drop it.
### Bare invocation (empty args)
1. Infer from conversation (fresh pull → `tree`, disk space → `scan`, …. See action-router).
2. If still unclear, present the action table from [context/action-router.md](context/action-router.md) and ask ([Confirmation gate](#confirmation-gate)).
3. Safest fallback: `scan`.
### Action table
| Action | Say it as… | Risk | Pre-flight? | In `all`? |
|--------|------------|------|-------------|-----------|
| `scan` | Show what's reclaimable | Safe | No | |
| `caches` | Clear tool and linter caches | Low | Yes | |
| `build` | Clear build output and logs | Low | Yes (includes caches) | |
| `git` | Prune stale git metadata; audit branches + stashes | Low | No | Yes |
| `stash` | Audit and triage stashes (age, source, diffstat) | Safe | No | |
| `tree` | Reset working tree like a fresh pull | **Destructive** | No (always dry-run first) | **Never** |
| `tree-batch` | Reset many repos like a fresh pull (skip-list + dirty guard) | **Destructive** | No (always dry-run first) | **Never** |
| `all` | Sweep caches + build + git hygiene | Medium | Yes | |
Tiers cumulative: `build` includes `caches`. `all` = `build` + `git`. **Neither `tree` nor `tree-batch` is ever composed into `all`.**
**Fleet (batch) forms.** Each selective tier has a multi-repo form. `caches-batch`, `build-batch`, `git-batch`, `all-batch`. That runs it across a repo set behind ONE gate (§8). `tree-batch` is the destructive tier's separate batch form (§6.5).
Aliases (`fresh`, `inventory`, `artifacts`, `caches-fleet`, …): [context/action-router.md](context/action-router.md).
## What clean NEVER touches by default
Protected-path enforcement gates `scan`, `caches`, `build`, `git`, AND `tree` (`tree` honors the same classes by default). Full list: [reference/cleanup-config.md](reference/cleanup-config.md).
- **Secrets / config**. `.env*`, `*.local.json` / `.jsonc` / `.md`, IDE user config. `tree` removes only with `--include-secrets` (UNRECOVERABLE).
- **Runtime dependencies**. `node_modules/`, `.venv/`, `vendor/`. `tree` removes only with `--include-deps` (rebuildable).
- **Skill-owned `data/`**, user-generated synthesis, always preserved; no flag removes it.
`tree` requires explicit confirmation and is never auto-invoked. Any file tracked by git is reset via `git reset --hard`, not selective deletion, and any tracked file deleted by reparse-point traversal (junction/symlink into a tracked dir) is auto-restored.
**Session-scoped destructive guard (frontmatter hook).** While this skill is active, a PreToolUse hook (`scripts/destructive-guard.sh`) blocks destructive Bash commands (`rm -rf`, `git clean -f*`, `git reset --hard`, `git checkout --`, `git stash drop`/`clear`, recursive `Remove-Item`). After the [confirmation gate](#confirmation-gate) passes, re-issue the confirmed command with the acknowledgement prefix `CLEAN_GUARD_ACK=1 <command>`, never add the prefix without the user's explicit confirmation in this session. Kill switch: the `clean_destructive_guard_enabled` userConfig option set to `false` (`/plugin configure repo-hygiene@<marketplace>`).
## Confirmation gate
**Question surface. Every question this skill asks.** Prefer `AskUserQuestion`: its answer is the user's own and cannot be fabricated. It is not always usable, in two distinct ways, a bare-name `permissions.deny` rule or a `disallowed-tools` entry removes it from context entirely, while permission mode `dontAsk` denies it even when an allow rule names it, leaving it visible and every call failing. Fall back to the same question asked inline as a numbered choice whenever the tool is absent, denied, **or otherwise unusable**, including a denial discovered only by calling it; a denied call is an unanswered question, never an answer. Then wait for the reply. The surface varies; nothing below it does.
**Destructive confirmation. Every `--apply`, branch deletion, and stash drop.** Show the dry-run first, then take the user's own affirmative answer, given in this interactive session, naming exactly the set just shown. A prior general request, an alias, a flag, "clean everything", approval of a different set, or silence is not confirmation, never supply or infer the answer yourself. Autonomous sessions abort here rather than ask.
## Cleanup configuration
Per-tier targets: [reference/cleanup-config.md](reference/cleanup-config.md). Script binding: `scripts/lib/cleanup-paths.sh`.
## Workflow
### 0. Resolve action and repo root
Run `resolve-clean-action.sh`. If `Action: menu`, show table + ask. Otherwise dispatch to the matching § below. **Never `--apply` on first invocation.**
- Repo root for the run: `REPO_ROOT=$(git rev-parse --show-toplevel)`
### 1. Scan (`scan`)
`${CLAUDE_SKILL_DIR}/scripts/scan.sh`. Read-only inventory. Stop if action is `scan`.
### 1.5. Pre-flight (caches / build / all only)
`${CLAUDE_SKILL_DIR}/scripts/preflight.sh`. See [context/preflight.md](context/preflight.md). Interactive: [confirm](#confirmation-gate) before `--apply` when non-empty. Autonomous: abort.
#### Dry-run → confirm → apply manifest flow (caches / build)
Both selective mutating tiers pay the filesystem walk **once**. `--dry-run` writes a session-scoped manifest and prints two machine-parseable lines: `Manifest: <path>` and `Summary: planned=N bytes=K` (bytes reclaimable. Surface this in the confirmation gate). After the user confirms, apply the **same** manifest with `CLEAN_GUARD_ACK=1 … --apply --manifest <path>`. Apply re-stats each entry (staleness guard) and removes it without re-walking, then prints `Summary: removed=N failed=M bytes=K` and exits non-zero if `failed>0`. A killed apply **resumes** by re-running the identical `--apply --manifest <path>` (already-removed entries are idempotent no-ops). Capture `<path>` from the dry-run's `Manifest:` line and thread it through unchanged; the manifest is ephemeral (mktemp default), so pass `--manifest <path>` on the dry-run too if you need a stable location. **Build tier: repeat `--include-caches` on the apply call too**. `clean-build.sh` gates which manifest classes an apply accepts by that invocation's own `--include-caches` flag, not by what built the manifest, so an apply that omits it rejects the manifest's `caches` lines (`Rejected (wrong tier)`, `failed>0`) even though the dry-run folded them in.
### 2. Caches
`${CLAUDE_SKILL_DIR}/scripts/clean-caches.sh`, default `--dry-run`; apply per the manifest flow above (`--apply --manifest <path>`) only after confirmation.
### 3. Build (includes caches)
`${CLAUDE_SKILL_DIR}/scripts/clean-build.sh --include-caches`, default `--dry-run`; apply per the manifest flow above only after confirmation. `--include-caches` folds the caches tier into the one build manifest.
### 4. Git
**Write-safe metadata only**, not working-tree reset (that is §6 `tree`).
#### 4.1 Prune and gc
`${CLAUDE_SKILL_DIR}/scripts/git-prune.sh`. `--dry-run` default; `--apply` after confirmation.
#### 4.2 Branch audit
`${CLAUDE_SKILL_DIR}/scripts/git-branch-audit.sh`. Deletion via the [confirmation gate](#confirmation-gate) per [context/git-branch-cleanup.md](context/git-branch-cleanup.md). Branches in the `WORKTREE` tier are checked out in a linked worktree: never offer them for `git branch -d`. Route the user to the worktree-management tool to clean up the worktree first. Branches carrying `no upstream, M commits not on origin/<default>` are never-pushed local work. Surface the count and confirm before any deletion.
#### 4.3 Stash audit
`${CLAUDE_SKILL_DIR}/scripts/git-stash-audit.sh`. Read-only per-stash facts (age, source branch, diffstat, PR/merge signal, advisory). **Never drops a stash.** Present the list and, for each stash, ask the user keep-or-drop ([Confirmation gate](#confirmation-gate)); a `possibly superseded` / `likely superseded` advisory is a hint to raise first, never an autonomous drop. Dedup a fleet sweep by the `StashStore:` key (linked worktrees share one stash ref). When the resolved action is `stash`, run only this step.
**Dropping stashes safely.** A confirmed drop is destructive and gated by the session guard, after the user confirms, re-issue as `CLEAN_GUARD_ACK=1 git stash drop <ref>`. The `Stash:` selector (`stash@{n}`) is **volatile**: the list renumbers after every drop, so dropping more than one by selector top-down retargets the wrong entry. Drop by the stable `Commit:` id (resolve it to its current selector immediately before each drop), or drop the highest-numbered selector first so lower indices stay valid.
### 5. All
§1.5 once, then §2–§4. **Does not run §6.**
### 6. Tree (destructive)
`${CLAUDE_SKILL_DIR}/scripts/git-tree-reset.sh`, default `--dry-run`. Detail: [context/git-tree-reset.md](context/git-tree-reset.md). Default-preserve; opt-in `--include-deps` / `--include-secrets`; `--allow-unpushed` when HEAD is ahead of upstream.
**Mandatory gate:** show dry-run output → [confirmation gate](#confirmation-gate) → only then `--apply`. Surface the dry-run's `PreserveDeps` / `PreserveSecrets` / `AheadCount` lines in the confirmation so the user knows what survives. An exit 4 (`unpushed-commits`) or non-zero `AheadCount` means HEAD has unpushed commits. Confirm loss before adding `--allow-unpushed`. Autonomous sessions: abort. Post-step: after a tree reset that removed dependencies, suggest reinstalling them with the project's own bootstrap/setup and re-validating the environment. For a truly pristine tree, close running dev tooling first (MCP servers, telemetry collectors, build/test watchers). Live processes recreate ignored dirs (`obj/`, `node_modules/`, and the like) the moment they are deleted, and may hold locks that surface as `Unremovable:`.
### 6.5. Tree batch. Multi-repo (destructive)
`${CLAUDE_SKILL_DIR}/scripts/git-tree-reset-batch.sh`, default `--dry-run`. Runs §6 `tree` across a set of repos behind one gate, with a separator-agnostic skip list and a dirty-by-default guard. Detail + examples: [context/git-tree-reset-batch.md](context/git-tree-reset-batch.md). Additive over §6, the batch layer runs no destructive git itself; each per-repo reset delegates to the unchanged `git-tree-reset.sh`, preserving every single-repo gate.
Repo sources: `--repo` (repeatable; a shell glob expands to these) and `--repos-from FILE|-` (ingests `ghq list -p` output). Skip list: `--skip ENTRY` / `--skip-from FILE` (absolute path, `owner/repo`, or bare `repo`; separator-agnostic). Passthrough to the child: `--force-default-branch` / `--include-deps` / `--include-secrets`.
**Mandatory gate (single, batch-wide):** show the `--dry-run` whole-batch plan (per-repo `Outcome`/`Reason`, the `Summary` totals, and any `UnmatchedSkip:` warnings) → [confirmation gate](#confirmation-gate) **once** → only then `--apply` **once**. Do not gate per repo. A fresh-clone fleet is typically all on the default branch, so expect an all-blocked dry-run unless `--force-default-branch`. Surface that in the confirmation. `--include-dirty` re-enables the exact data-loss vector (resets repos with uncommitted/untracked changes or unpushed commits); it needs its own explicit confirmation naming the dirty repos, exactly like `--include-secrets`. Autonomous sessions: abort.
### 7. Orphaned path removal (destructive, on explicit request only)
`${CLAUDE_SKILL_DIR}/scripts/remove-path.sh <target>`, default `--dry-run`. Removes a whole clone or leftover directory under the ghq root (`--root` overrides), e.g. a local clone whose upstream repository was deleted. Not composed into any tier and never inferred: run it only when the user explicitly asks to delete that path. Guards resolve paths physically and require the target to share the root's filesystem device (symlink/junction/cross-mount ancestors cannot escape containment), and refuse the containment root, symlink targets, linked worktrees (that lifecycle belongs to `git worktree remove`), any plain directory still holding nested git repos (normal, bare, or worktree), any target holding ignored skill-owned `data/` (irreplaceable. No override; move it out first), and any repo with uncommitted changes, stashes, registered worktrees, ignored secret-class files (`--include-secrets` to discard), or unpushed refs (`--allow-unpushed` to discard).
**Mandatory gate:** show dry-run output → [confirmation gate](#confirmation-gate) → only then `--apply`. Surface `Kind` / `UnpushedRefs` / `SecretsCount` / `SkillData` in the confirmation. Autonomous sessions: abort.
**Documented boundaries.** Containment is path- and device-based (physical resolution plus a same-device check). A *same-device* `mount --bind` under the root shares the root's filesystem device, so no path-based check can detect it; closing that would require a Linux-only mount-table (`/proc/self/mountinfo`) model that would also refuse legitimate under-root mounts, so it stays out of scope for this local, dry-run-default, explicit-`--apply` tool. The unpushed-ref guard covers `refs/heads` and `refs/tags`; other locally-created namespaces (e.g. `refs/notes`) are not scanned, and auto-generated ones (`refs/prefetch/*` from git-maintenance, `refs/replace/*`) are intentionally not treated as unpushed. `--allow-unpushed` is the escape hatch for any local ref. The secret scan gates only ignored (unrecoverable) files; tracked files are git's domain (recoverable via reset/remote, and separately blocked when dirty or unpushed).
### 8. Batch. Multi-repo selective tiers (`caches-batch` / `build-batch` / `git-batch` / `all-batch`)
`${CLAUDE_SKILL_DIR}/scripts/clean-batch.sh --tier <caches|build|git|all>`, default `--dry-run`. Runs the §2–§5 selective tiers across a set of repos behind one gate, the selective-tier sibling of §6.5 `tree-batch`. Detail + examples: [context/clean-batch.md](context/clean-batch.md). Additive over the single-repo tiers, the batch layer runs no removal itself; each per-repo action delegates to the unchanged child (`clean-caches.sh` / `clean-build.sh` / `git-prune.sh`), preserving every child gate. **`tree` is not batched here** (use §6.5); **branch audit/deletion is not batched** (interactive per-branch deletion can't sit behind one gate). Batch `git` is prune/gc/remote-prune only, once per unique shared object store.
Repo sources: `--repo` (repeatable; a shell glob expands to these) and `--repos-from FILE|-` (ingests `ghq list -p`; backslash paths normalized). Skip list: `--skip ENTRY` / `--skip-from FILE` (same separator-agnostic matcher as `tree-batch`).
**Mandatory gate (single, batch-wide):** run `--dry-run` once → it writes a **batch plan** and prints `BatchPlan: <path>`, per-repo `Outcome`/`Reason`, any `UnmatchedSkip:`, and an aggregate `Summary: repos=N planned=P bytes=K` (surface the reclaimable `bytes`). [Confirmation gate](#confirmation-gate) **once** → then `CLEAN_GUARD_ACK=1 … --apply --batch-plan <path>` **once**. The plan IS the gated set: apply targets exactly those repos (`--apply` errors without `--batch-plan`), so a repo that vanished after the dry-run applies idempotently and one that appeared is never touched. Apply prints `Summary: removed=N failed=M bytes=K` and exits non-zero on any failure. Autonomous sessions: abort.
## Integration
| Surface | Relationship |
|-------|-------------|
| A worktree-management tool | Removes git worktree directories, not in-place reset |
| The project's build / verify workflow | Rebuild after `build` or `tree` |
| The project's bootstrap/setup | Restore dependencies after `tree` |
| The project's environment-validation tooling | Full env validation after `tree` or `all` |
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!