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.

Updated 2026-09-20

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.