Skip to content

Commit 01f6985

Browse files
lroolleclaude
andcommitted
feat(skills): add deva-clean workspace cleanup skill
- map bind mounts from .deva + /proc/mounts before touching anything - locate disk pressure: host mount vs container overlay (shared-VM runtimes make overlay df lie) - git-triage local dirs: worktree type, dirty state, pushed test via branch -r --contains HEAD (upstream/ahead is a trap), ref freshness - tiered dry-run report; deletion is a second, approved pass using git worktree remove + prune, never rm -rf on linked worktrees Close #401 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 25d21b6 commit 01f6985

2 files changed

Lines changed: 115 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
- `skills/deva-clean`: Claude Code skill for mount-safe workspace disk
12+
cleanup. Maps bind mounts from `.deva` + /proc/mounts, git-triages
13+
local worktrees for unpushed/uncommitted work, and reports a tiered
14+
dry-run plan before any deletion (#401)
15+
1016
## [0.13.0] - 2026-07-07
1117

1218
### Changed

skills/deva-clean/SKILL.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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

Comments
 (0)