|
| 1 | +--- |
| 2 | +name: deva-clean |
| 3 | +description: Disk cleanup for deva container workspaces. Maps bind mounts from .deva and /proc/mounts, classifies local dirs vs mounted volumes, git-triages worktrees for unpushed or uncommitted work, then reports a tiered dry-run plan before deleting anything. Use when the user says "clean up the workspace", "disk cleanup", "free up disk", "remove stale worktrees", or when disk fills up inside a deva workspace. Deleting a single known build dir needs no skill - just delete it. |
| 4 | +--- |
| 5 | + |
| 6 | +# deva-clean: mount-safe workspace disk cleanup |
| 7 | + |
| 8 | +A deva workspace mixes two kinds of directories that look identical in ls: |
| 9 | + |
| 10 | +- bind mounts declared in `.deva` (live host repos - untouchable) |
| 11 | +- local dirs created during work (git worktrees, node_modules, package |
| 12 | + stores - reclaimable) |
| 13 | + |
| 14 | +rm -rf on the wrong one destroys a live repo on the host. And since the |
| 15 | +workspace root is itself a host mount, "local" dirs still consume host |
| 16 | +disk - usually the disk that is actually full. |
| 17 | + |
| 18 | +Three rules, no exceptions: |
| 19 | + |
| 20 | +1. Mounts are untouchable. Build the mount map first; everything in it |
| 21 | + is off limits. |
| 22 | +2. Report before delete. The first pass produces a plan with sizes and |
| 23 | + exact commands. Deletion is a second, explicitly approved pass. |
| 24 | +3. No worktree dies with unpushed work. Triage git state per directory; |
| 25 | + anything unpushed or uncommitted goes to the keep list. |
| 26 | + |
| 27 | +## Process |
| 28 | + |
| 29 | +### 1. Map the mounts |
| 30 | + |
| 31 | +Read `.deva` for declared VOLUME=host:container[:mode] lines, but treat |
| 32 | +/proc/mounts as ground truth - .deva has comments and drift: |
| 33 | + |
| 34 | + grep "$(pwd)" /proc/mounts | awk '{print $2}' | sort |
| 35 | + |
| 36 | +The workspace root itself is usually a mount. Everything under it that |
| 37 | +is NOT a separate mount is a local dir living on host disk. |
| 38 | + |
| 39 | +### 2. Find where the pressure is |
| 40 | + |
| 41 | + df -h / "$(pwd)" |
| 42 | + |
| 43 | +Container overlay numbers can lie: on shared-VM runtimes (OrbStack and |
| 44 | +friends) overlay df reports the whole VM disk. Get container-local truth |
| 45 | +with: |
| 46 | + |
| 47 | + sudo du -xh -d1 / | sort -rh | head |
| 48 | + |
| 49 | +If the host mount is the full one, cleanup targets the workspace. If the |
| 50 | +overlay is genuinely full, target container caches (~/.cache, package |
| 51 | +stores) instead - not the workspace. |
| 52 | + |
| 53 | +### 3. Inventory candidates |
| 54 | + |
| 55 | +Candidates = dirs under the workspace that are not in the mount map. |
| 56 | +Typical: trees from `git worktree add`, one-off clones, node_modules, |
| 57 | +.pnpm-store, stray files from shell typos ("&1"). |
| 58 | + |
| 59 | + du -sh <candidates> | sort -rh |
| 60 | + |
| 61 | +### 4. Git triage per candidate |
| 62 | + |
| 63 | +For each candidate, in order: |
| 64 | + |
| 65 | +- Type: `.git` file -> linked worktree; `.git` dir -> standalone repo; |
| 66 | + neither -> plain dir. |
| 67 | +- Dirty: `git status --porcelain`. Untracked generated files (lockfiles, |
| 68 | + test output) are lower risk than modified source. |
| 69 | +- Pushed: `git branch -r --contains HEAD`. Empty output means the |
| 70 | + commits exist nowhere but this machine. |
| 71 | + Trap: do NOT use upstream config or ahead/behind as the pushed test. |
| 72 | + A branch created off origin/develop tracks origin/develop and shows |
| 73 | + "ahead 1" - looks fine, but the branch itself was never pushed. |
| 74 | +- Freshness: remote refs are only as new as the last fetch. Check |
| 75 | + `stat .git/FETCH_HEAD` on the parent repo and flag stale refs in the |
| 76 | + report. |
| 77 | +- Inside keepers: node_modules, dist, coverage and friends are |
| 78 | + separately prunable - offer cache-prune as the low-risk alternative |
| 79 | + to deletion. |
| 80 | + |
| 81 | +Also run `git worktree list` on each parent repo: entries marked |
| 82 | +"prunable" are stale metadata from already-deleted trees. |
| 83 | + |
| 84 | +### 5. Tiered dry-run report |
| 85 | + |
| 86 | +Produce the plan, then stop for approval: |
| 87 | + |
| 88 | +- Tier 1 (safe): clean + pushed. List with sizes and exact commands. |
| 89 | +- Tier 2 (confirm): pushed branch, dirty only with generated artifacts. |
| 90 | + Offer full removal and cache-prune-only variants. |
| 91 | +- Keep: unpushed commits or uncommitted diffs. Say exactly what would |
| 92 | + be lost and suggest pushing the branches. |
| 93 | +- Totals: reclaim per tier. |
| 94 | + |
| 95 | +### 6. Execute approved tiers only |
| 96 | + |
| 97 | +- Linked worktrees: `git -C <parent> worktree remove <path>`. It |
| 98 | + refuses when the tree is dirty - that refusal is the safety net, so |
| 99 | + never --force and never plain rm -rf (rm leaves stale metadata). |
| 100 | +- Stale metadata: `git -C <parent> worktree prune`. |
| 101 | +- Standalone repos (pushed, clean) and plain dirs: rm -rf. |
| 102 | +- Verify: df -h again; `git worktree list` shows no leftovers. |
| 103 | + |
| 104 | +Safe by construction: `git worktree remove` deletes the checkout only. |
| 105 | +Branch refs live in the parent repo's .git and survive, so committed |
| 106 | +work is never lost - only uncommitted files can be. The metadata edit |
| 107 | +inside the parent's .git is normal bookkeeping, not "touching the |
| 108 | +mount". |
| 109 | + |
| 110 | +## Anti-patterns |
| 111 | + |
| 112 | +- rm -rf on a linked worktree. Leaves .git/worktrees metadata dangling; |
| 113 | + use git worktree remove + prune. |
| 114 | +- Trusting upstream/ahead as proof a branch is pushed. Use |
| 115 | + `git branch -r --contains HEAD`. |
| 116 | +- Trusting .deva over /proc/mounts. Config drifts; the kernel does not. |
| 117 | +- Trusting overlay df inside shared-VM container runtimes. |
| 118 | +- Deleting anything on the first pass. The dry-run report is the |
| 119 | + deliverable; deletion needs explicit approval. |
| 120 | +- Cleaning container temp dirs when the full disk is the host mount |
| 121 | + (or vice versa). Find the pressure first. |
0 commit comments