Stress-testing dev-prune: every byte accounted for
Six disposable repositories, real installs, one accident. What a logged prune pass deleted, what it refused, and what devp restore put back, byte for byte.
Claims about deletion tools are cheap, so this is a receipt instead. We scaffolded six
disposable Git repositories on a scratch drive, gave each a real dependency install
(npm, pnpm with its store, bun, uv, Go modules with a vendor tree, cargo), registered
them, and let an AI agent drive devp end to end through the skill file that
devp skill exports. Every command was logged. The numbers below are from
those logs, not from memory.
Day one: it refused to touch anything
The first finding was a pass that did nothing, which is correct behaviour. Every
repository in the corpus was created that day, and devp run --explain gave
the same verdict for each: active, with activity today against a 15-day idle threshold.
A tool for idle repositories should treat a brand-new repository as the opposite of a
candidate. To test deletion at all we had to say so explicitly, with
--ignore-idle.
The pass, byte for byte
With the idle gate lifted, the pass verified each directory against its lockfile and pruned six:
• api-server → node_modules (95.2 MiB) [npm]
• data-pipeline → .venv (229.9 MiB) [uv]
• go-worker → vendor (34.2 MiB) [go]
• job-queue → node_modules (41.9 MiB) [bun]
• web-app → node_modules (0.5 MiB) [pnpm]
• dev-prune → site/node_modules (93.2 MiB) [npm]
Freed: 494.99 MiB across 6 directories
Two of those rows deserve a closer look.
The pnpm row is small on purpose. The install was 80.9 MiB on disk, but 80.4 MiB of it was hardlinked into the pnpm store, and deleting a hardlink does not free the store's copy. dev-prune counts the 0.5 MiB that actually came back and reports the hardlinked share separately, rather than taking credit for space that was never going to be freed.
The last row was an accident. The corpus registration swept in the
working copy of dev-prune itself, and with the idle gate explicitly lifted, the pass
verified our own site's node_modules against its lockfile and deleted it,
93.2 MiB, exactly as instructed. No flag saved us and none should have:
--ignore-idle means what it says. What made the mistake free is the next
section.
The restore, byte for byte
devp restore --last-run read the undo log and reinstalled all six
directories, 494.99 MiB, from the same lockfiles the pass had verified, including
rebuilding the uv environment on the Python version the log had recorded. Our own
node_modules came back with everything else. A second pass over the corpus
afterwards freed 367.5 MiB across four directories, which is the first pass minus the
accident and minus the Go vendor tree, and confirmed the numbers reproduce.
What it would not do
The corpus included a Rust service with a 186.97 MiB target/ directory,
and the pass left it alone. That is the opt-in policy, not a gap: target/
comes back by recompiling rather than downloading, which costs minutes rather than
seconds, so the cargo adapter (like Gradle, Maven, SwiftPM, .NET and the other build-tree
adapters) does nothing until you set enable_cargo in the config. With the
adapter enabled, the same pass claimed all 186.97 MiB.
What a real machine looks like
A synthetic corpus proves behaviour, not typical yield, so the number on the
front page is a different kind: the scheduled pass of 2026-09-04 on the
author's own machine, which freed 1.91 GiB from four directories across three
repositories, each row listed with the lockfile that proved it rebuildable. Your figure
is neither of these numbers: it is what devp run --dry-run prints on your
disk, and that command deletes nothing.
curl -fsSL https://devprune.vkrishna04.me/install.sh | sh
devp init ~/Code
devp run --dry-run
Common questions
Does devp restore actually work?
In this logged pass, yes, completely: six pruned directories, 494.99 MiB, all reinstalled by devp restore --last-run from the same lockfiles the prune had verified, including a uv virtualenv rebuilt on the Python version the undo log recorded. Restore is a reinstall, so it needs network access and works precisely because nothing is pruned without a verified lockfile.
Why did dev-prune skip the Rust target directory?
Because build trees are opt-in. A node_modules comes back by downloading; a target/ comes back by recompiling, which is a much more expensive rebuild. The cargo, Gradle, Maven, SwiftPM and .NET adapters therefore do nothing until you enable them in the config. Once enable_cargo was set, the same pass reclaimed the full 186.97 MiB.
Are these numbers what I should expect on my machine?
No. The corpus was built to exercise adapters, not to look like your disk. The honest way to get your number is devp run --dry-run, which walks your registered repositories, verifies every candidate against its lockfile, prints the total, and deletes nothing.