|
| 1 | +--- |
| 2 | +name: deva-clean |
| 3 | +description: Disk cleanup for deva container workspaces - reclaim space without touching bind mounts or unpushed work. Use when the user wants disk space reclaimed in a deva workspace, asks to remove stale worktrees, or a disk-full symptom traces into the 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 |
| 9 | +ls: bind mounts declared in `.deva` (live host repos) and local dirs |
| 10 | +created during work (git worktrees, node_modules, package stores). |
| 11 | +rm -rf on the wrong one destroys a live repo on the host. And because |
| 12 | +the workspace root is itself a host mount, local dirs still consume |
| 13 | +host disk - usually the disk that is actually full. |
| 14 | + |
| 15 | +The run is two passes. Pass one triages everything into a dry-run plan |
| 16 | +and stops for approval. Pass two executes only the approved tiers. |
| 17 | + |
| 18 | +## Pass one: triage |
| 19 | + |
| 20 | +### 1. Map the mounts |
| 21 | + |
| 22 | +Read `.deva` for declared VOLUME=host:container[:mode] lines, then take |
| 23 | +ground truth from the kernel - config drifts, /proc/mounts does not: |
| 24 | + |
| 25 | + grep "$(pwd)" /proc/mounts | awk '{print $2}' | sort |
| 26 | + |
| 27 | +The workspace root is usually itself a mount, so everything under it |
| 28 | +that is not a separate mount is a local dir living on host disk. |
| 29 | + |
| 30 | +Done when every dir under the workspace is classified mount or local. |
| 31 | +Mounts are off limits for the rest of the run. |
| 32 | + |
| 33 | +### 2. Locate the pressure |
| 34 | + |
| 35 | + df -h / "$(pwd)" |
| 36 | + |
| 37 | +Overlay numbers can lie: on shared-VM runtimes (OrbStack and friends) |
| 38 | +overlay df reports the whole VM disk. Container-local truth: |
| 39 | + |
| 40 | + sudo du -xh -d1 / | sort -rh | head |
| 41 | + |
| 42 | +Done when you know which disk is full. Host mount full: the workspace |
| 43 | +is the target. Overlay genuinely full: container caches (~/.cache, |
| 44 | +package stores) are the target instead. |
| 45 | + |
| 46 | +### 3. Size the candidates |
| 47 | + |
| 48 | +Candidates = the local dirs from step 1, plus stray files (shell-typo |
| 49 | +artifacts like "&1"). Typical: trees from `git worktree add`, one-off |
| 50 | +clones, node_modules, .pnpm-store. |
| 51 | + |
| 52 | + du -sh <candidates> | sort -rh |
| 53 | + |
| 54 | +Done when every candidate has a size. |
| 55 | + |
| 56 | +### 4. Git-triage each candidate |
| 57 | + |
| 58 | +Four verdicts per candidate: |
| 59 | + |
| 60 | +- Type: `.git` file -> linked worktree; `.git` dir -> standalone repo; |
| 61 | + neither -> plain dir. |
| 62 | +- Dirty: `git status --porcelain`. Untracked generated files (lockfiles, |
| 63 | + test output) are lower risk than modified source. |
| 64 | +- Pushed: `git branch -r --contains HEAD` - empty output means the |
| 65 | + commits exist nowhere but this machine. This is the only pushed test: |
| 66 | + upstream config and ahead/behind counts are a trap (a branch created |
| 67 | + off origin/develop tracks origin/develop and shows "ahead 1" while |
| 68 | + never pushed itself). |
| 69 | +- Freshness: remote refs are only as new as the last fetch. Check |
| 70 | + `stat .git/FETCH_HEAD` on the parent repo; flag stale refs in the |
| 71 | + plan. |
| 72 | + |
| 73 | +Inside keepers, node_modules / dist / coverage are separately prunable: |
| 74 | +offer cache-prune as the low-risk alternative to removal. Also run |
| 75 | +`git worktree list` on each parent repo - "prunable" entries are stale |
| 76 | +metadata from already-deleted trees. |
| 77 | + |
| 78 | +Done when every candidate carries all four verdicts. The smallest dir |
| 79 | +gets the same triage as the largest: a 4M worktree can hold the only |
| 80 | +copy of uncommitted work. |
| 81 | + |
| 82 | +### 5. Assemble the dry-run plan |
| 83 | + |
| 84 | +- Tier 1 (safe): clean + pushed. Sizes and exact commands. |
| 85 | +- Tier 2 (confirm): pushed branch, dirty only with generated artifacts. |
| 86 | + Offer full removal and cache-prune-only variants. |
| 87 | +- Keep: unpushed commits or uncommitted diffs. Say exactly what would |
| 88 | + be lost and suggest pushing the branches. |
| 89 | +- Totals: reclaim per tier. |
| 90 | + |
| 91 | +The plan is pass one's deliverable. Present it and stop - pass two |
| 92 | +starts only from explicit approval, tier by tier. |
| 93 | + |
| 94 | +## Pass two: execute approved tiers |
| 95 | + |
| 96 | +- Linked worktrees: `git -C <parent> worktree remove <path>` is the |
| 97 | + only removal path. Its refusal on a dirty tree is the safety net, so |
| 98 | + no --force - and no rm -rf, which bypasses the net and leaves stale |
| 99 | + metadata behind. |
| 100 | +- Stale metadata: `git -C <parent> worktree prune`. |
| 101 | +- Standalone repos (pushed, clean) and plain dirs: rm -rf. |
| 102 | + |
| 103 | +Safe by construction: `git worktree remove` deletes the checkout only; |
| 104 | +branch refs live in the parent repo's .git and survive. Committed work |
| 105 | +is never lost - only uncommitted files can be. The metadata edit inside |
| 106 | +the parent's .git is normal bookkeeping, not touching the mount. |
| 107 | + |
| 108 | +Done when df shows the reclaim and `git worktree list` on each parent |
| 109 | +has no leftovers. |
0 commit comments