First public release. dev-prune reclaims disk space from idle Git repositories by
deleting dependency and build directories that a lockfile can rebuild — and refuses to
delete anything it cannot prove is recoverable.
Pruning engine
- Lockfile-verified deletion. No directory is removed until its package manager has
confirmed a usable lockfile. Verification cannot be bypassed by any flag or setting. - Read-only verification, everywhere. Every adapter proves the lockfile can rebuild
the tree without writing to it —npm ci --dry-run,pnpm install --lockfile-only --frozen-lockfile,yarn install --immutable,uv lock --locked,cargo metadata --locked,go mod download. A lockfile that has drifted from its manifest is a
refusal, not something to quietly fix: a pass can be started by the OS scheduler, and
it must never leave a modified tracked file behind. The writing form runs in exactly
two cases — no lockfile exists at all, orallow_manifest_rewriteis set, which is
the informed opt-in and now means the same thing in every ecosystem. - Two enforcement tiers. With the manager installed, it resolves the manifest against
the lockfile. With the manager missing but a lockfile on disk, the lockfile is itself
the proof anddevp restorecan rebuild later. With neither, nothing is deleted. command_timeout_secsbounds every verification. Each package-manager command runs
under the configured ceiling (600s by default) and a hang fails the check rather than
the pass.- Idle detection. A repository is a candidate only after
idle_dayswith no commit
and no source modification.--ignore-idlelifts that threshold and nothing else. - Inverse selection.
run --except <repos>prunes everything but the named
repositories, so "clean up but keep the API project" does not mean pruning it and
downloading it back. - Adapter and size filters.
--only,--skipand--min-size, withmin_size_mb
as the persistent form of the last. - Symlink refusal. A symlinked or junctioned bloat directory points at storage the
repository does not own and is never deleted. - Per-directory selection. The interactive selector prunes exactly the directories
left ticked, and starts with every candidate ticked so keeping one is a single
keystroke. - Dry run.
--dry-runreports every candidate and its size without running a
package manager or touching disk. - Machine-readable output.
--jsononrunandstatusemits one document on
stdout and nothing else; every diagnostic goes to stderr, so the output is parseable
even when something went wrong.
Multi-ecosystem repositories
- Eight adapters: npm, pnpm, yarn, bun, uv, pip/venv, cargo, go.
- Any number of managers per repository. A repository may hold uv, npm and cargo in
its root, spread them acrossfrontend/,services/api/andtools/cli/, or mix
both. Every project is discovered, verified and pruned on its own terms, and each
directory is reported by its repository-relative path. - Bounded discovery. The walk descends
scan_depthlevels — six by default,
configurable globally and per repository — and never entersnode_modules,
target,vendor, virtual environments, hidden directories, or nested repositories —
a submodule is pruned as itself, never as part of its parent. - Single owner per directory. When npm, pnpm, yarn or bun all claim the same
node_modules, one is chosen: thepackageManagerfield ofpackage.json, else the
manager whose bookkeeping files are inside the installed tree, else the most recently
written lockfile. uv takes precedence over plain venv for the Python environment. - Virtual environments by marker, not by name. Any directory containing
pyvenv.cfgis recognised, whatever it is called.
Safety
- Deletion is refused when the lockfile is missing, unparseable, or — for
requirements.txt— lists no packages, because the tree could not be rebuilt. ignore.devprune.jsonin a repository root opts it out with a single file-existence
check, before any config is parsed..devprune.jsonholds only inert data: ignore flags, a display name, daemon/hook
opt-outs, and per-repository overrides for the same numeric and boolean settings the
global config takes. There is no key that names a command, a path to execute, or a
binary to run — nothing in a repository-tracked file can cause command execution, which
matters because these files arrive with agit clone.- A
.devprune.jsonthat cannot be parsed skips the repository and reports the syntax
error, rather than falling back to defaults — the unreadable file may have been the
one saying"ignore": true. - Nothing dev-prune installs edits an editor's settings, a shell startup file, or the
system PATH outside the installer scripts.devp config iconregisters the file type
with the OS file manager and prints an editor snippet for you to paste. - A run that fails any repository exits non-zero. Exit codes are
0success,1
failure,2unusable arguments.
Commands
init,link,unlink,undo— register repositories, individually or by scanning
a tree.unlink --missingclears every entry whose directory no longer exists in one
pass, which is what a registry accumulates from deleted clones and moved workspaces.run [PATH]— prune every registered repository, or one target.status— an interactive dashboard of every registered repository, its state,
reclaimable space and last activity, withito ignore andpto prune.
status daemonandstatus hookreport the background integrations.caches [--json]— the answer to "where did my disk actually go?". Finds every
package manager cache and store on the machine — npm, pnpm, yarn, bun, uv, pip, cargo's
registry, Go's module and build caches — sizes each one, orders them largest first, and
prints the command that clears it. Each manager is asked where its cache lives rather
than assumed, so aCARGO_HOMEor a corporate.npmrcis followed; a manager you have
since uninstalled still has its leftover cache reported. It deletes nothing, and no
flag makes it. A cache is shared by every project on the machine, so no single
lockfile can prove it recoverable — and it is what makesrestorefast. Run
devp cacheswhen you want the number, and the clear command yourself when you want
the space more than the speed.restore [PATH] [--last-run]— reinstall dependencies for every project in a tree.
--last-runrestores exactly what the most recent prune pass deleted, wherever those
projects were, so an over-eager pass is one command to undo.doctor [PATH]— a read-only diagnosis. Without a path it checks the installation:
the binary and its PATH entry, the registry and every setting in it, the integrations —
including the binary the scheduler and the hooks will actually run, so one left pointing
at a deleted directory is reported rather than silently doing nothing forever — which
package managers are actually reachable, and the release-check state. With a path
it checks one repository and names the reason a prune pass would skip it. It runs no
package manager and repairs nothing, so it can be run twice to see whether a fix
worked. Warnings exit0; only genuine breakage exits1.config— global settings (get,set,show, and awizardthat walks through
every one of them), per-repository.devprune.json, the OS scheduler, Git hooks, and
the file-manager icon for*.devprune.json.update [--offline]— reports the installed version, asks GitHub's public API for the
latest release, and prints the upgrade command for how it was installed.skill— exportsSKILL.mdfor AI coding assistants.setup [--status]— installs any missing integration;--statusreports without
changing anything.uninstall [--deep]— removes the scheduler and hooks;--deepadditionally clears
configuration after confirming the number of repositories affected.-V— version plus an environment audit: OS, architecture, config path, binary
directory, and PATH activation.- Shorthands.
devp hook,devp daemonanddevp iconreach theconfig
subcommands of the same name, andinstall/uninstall/on/offare accepted
whereverenable/disableare. A misspelled action is rejected instead of quietly
printing status. - Paths.
.means the current directory and is the default wherever a path is
optional. A leading~is expanded by dev-prune itself, not by the shell, so
devp init ~/Codebehaves the same in bash, PowerShell and cmd, quoted or not.
Background automation
A pruner that has to be remembered is a pruner that never runs, so the integrations
install themselves — at install time, and again on the first command after an upgrade if
anything is missing. devp setup is that pass, run by hand; it installs only what is
absent and reports what it declined to touch.
- OS scheduler —
schtaskson Windows, a LaunchAgent on macOS, a systemd user timer
on Linux, each running at the configuredcheck_interval_daysinterval. Scheduled
passes are non-interactive and skip repositories that setdisable_daemon. - Durable paths. The scheduler entry and the hook scripts both outlive the process
that wrote them, so both record the binary in<config>/binrather than wherever the
command happened to be run from. Installing throughnpx dev-pruneoruvx dev-prune
would otherwise register a path inside a cache the package manager deletes, and neither
a scheduled task nor a Git hook has anywhere to report that it has stopped working. - Git hooks —
post-commit,post-checkoutandpost-mergeauto-register the
repository you are working in. Git allows one globalcore.hooksPathand no chaining,
so when husky, pre-commit or lefthook already hold it,devp hook install --chain
takes the slot and writes shims thatexecthe displaced tool's hooks — same
arguments, same stdin, same exit codes — and uninstall puts the original path back.
The pass skips entirely whengitis not onPATH. Repositories that set
disable_hooksare skipped. - The
devpsecond binary andSKILL.md—devpis a real executable beside
dev-prune, not a shell alias, so it works in cmd, in an IDE terminal and in the OS
scheduler rather than only in the shell whose profile was edited. Both are kept in step
with the installed binary, so an upgrade cannot leave a stale copy or an outdated skill
file behind. - File-manager icons —
*.devprune.jsonis registered with the OS file manager as
part of the same pass, as far as each platform allows. - First-run walkthrough — on a fresh install the config wizard runs once, so the
defaults are agreed to rather than inherited. It is skipped, never guessed at, when
there is no terminal to ask on. - Off switches.
auto_daemon,auto_hooksandauto_setupeach turn off part or all
of it;auto_hooks_chaingoverns the chained install specifically.
DEV_PRUNE_NO_AUTO_SETUP=1turns off all of it without a config file, and CI and
container environments are detected and treated as unattended without being told.
Privacy and distribution
- No telemetry. No diagnostics, no usage data, no identifiers, no analytics of any
kind, on any code path. - One network request, and only one. The release check makes a single unauthenticated
GETto GitHub's public releases endpoint — no body, nothing identifying the machine —
at most once everyupdate_check_interval_days(7). It is opt-out
(devp config set update_check false, or--offlinefor one run), because a pruner
nobody thinks about is a pruner nobody updates. dev-prune never downloads or replaces
its own binary; it prints the upgrade command and stops. - Installer scripts verify the published SHA-256 checksum of the release archive and
refuse to install without one. - Six prebuilt binaries, no per-distribution builds. Windows, macOS and Linux, x64
and arm64. The Linux assets are statically linked against musl, so one file per
architecture runs on Debian, Ubuntu, Fedora, RHEL, Arch, NixOS and Alpine alike, with
no glibc version floor. - Install it however you already install things. The shell and PowerShell one-liners,
npx dev-prune/npm install -g dev-prune,uv tool install dev-prune/uvx/
pipx/pip,cargo binstall dev-prune/cargo install dev-prune, or a direct
download from GitHub Releases. cargo binstall dev-pruneneeds no Rust toolchain. crates.io distributes source, so
cargo installhas no binary to fetch and always compiles — surprising if you expected
a registry install to be instant.Cargo.tomlnow declares where each release archive
lives, socargo binstalldownloads and unpacks the same executable the installer
scripts use, in seconds, on all six platforms.- The npm and PyPI packages contain the binary. No
postinstallstep downloads
anything, so they install correctly undernpm ci --ignore-scripts, behind a corporate
registry mirror, and with no network access at all — and a dependency install never
turns into an outbound call to GitHub. npm gets six platform packages selected by
os/cpu; PyPI gets six platform wheels. Every npm tarball is published with
provenance, and every wheel through PyPI Trusted Publishing. - Apache-2.0, and provable. Copyright 2026 VKrishna04. Every source file carries an
SPDX-License-Identifier, so a licence scanner in your CI answers the same as the
LICENSE.mdin the repository, and every distributed artefact — the crate, all seven
npm packages, all six wheels — ships the full licence text rather than only a field
naming it. - Configuration lives in the platform config directory:
%APPDATA%\dev-pruneon
Windows,~/Library/Application Support/dev-pruneon macOS,
$XDG_CONFIG_HOME/dev-pruneon Linux.
Built with
Rust 1.85 (edition 2024), clap 4, ratatui, and no runtime dependencies beyond the
package managers already installed on the machine.
Which download do I want?
Most people want none of them. The installers below verify the SHA-256
sidecar, put the binary somewhere already on PATH, and create the devp
alias — three things unzipping an archive does not do.
| You are on | Run this |
|---|---|
| Windows (PowerShell) | iwr -useb https://devprune.vkrishna04.me/install.ps1 | iex |
| macOS / Linux | curl -fsSL https://devprune.vkrishna04.me/install.sh | sh |
| Node projects | npm install -g dev-prune@1.0.0 — or just npx dev-prune status |
| Python tooling | uv tool install dev-prune==1.0.0 / pipx install dev-prune==1.0.0 |
| Rust, no compile | cargo binstall dev-prune@1.0.0 |
| Rust, from source | cargo install dev-prune --version 1.0.0 (needs Rust 1.85+) |
The npm and PyPI packages contain these same binaries rather than downloading
one afterwards, so they work under npm ci --ignore-scripts, behind a registry
mirror, and offline. cargo install is the only channel that compiles on your
machine; cargo binstall reaches for the archives on this page instead.
Full instructions: https://devprune.vkrishna04.me/#install
The assets, then
Take one only if you are packaging dev-prune yourself, air-gapping it, or
pinning it into a container image.
| Asset | For |
|---|---|
dev-prune-v1.0.0-windows-x64.zip |
Windows on Intel/AMD — almost every Windows PC |
dev-prune-v1.0.0-windows-arm64.zip |
Windows on ARM — Surface Pro X, Snapdragon laptops, Windows in a Mac VM |
dev-prune-v1.0.0-darwin-arm64.tar.gz |
Apple Silicon Macs — M1 and later |
dev-prune-v1.0.0-darwin-x64.tar.gz |
Intel Macs — 2020 and earlier |
dev-prune-v1.0.0-linux-x64.tar.gz |
Linux on Intel/AMD |
dev-prune-v1.0.0-linux-arm64.tar.gz |
Linux on ARM — Raspberry Pi 4/5, Graviton, ARM CI runners |
Both Linux builds are statically linked against musl, so they run on any
distribution including Alpine, with no glibc floor.
Every archive ships a .sha256 sidecar. Check it before you trust it:
sha256sum -c dev-prune-v1.0.0-linux-x64.tar.gz.sha256(Get-FileHash dev-prune-v1.0.0-windows-x64.zip).Hash -eq (Get-Content dev-prune-v1.0.0-windows-x64.zip.sha256).Split(' ')[0].ToUpper()Full Changelog: https://github.com/Life-Experimentalist/dev-prune/commits/v1.0.0