Disk cleanup for macOS developers that tells you why something is safe to delete.
Most cleanup tools match directory names. Name matching is how you lose data: a
directory called dist/ next to a pyproject.toml is usually build output and
occasionally the only copy of a dataset. devclean reads the contents, and
sorts every candidate by how much evidence actually backs deleting it.
uv tool install git+https://github.com/gojiplus/devclean.gitOr run it without installing:
uvx --from git+https://github.com/gojiplus/devclean.git devclean scandevclean scan # read-only inventory, never deletes
devclean plan # the same candidates grouped by tier
devclean clean --tier auto --dry-run
devclean clean --tier autoscan --json emits the same data for scripting.
Each scan section can be switched off per run: --no-venvs, --no-node,
--no-project, --no-system, --no-temp. --min-size N raises the size
floor for caches, system artifacts, and temporary directories.
Every candidate carries a tier, the evidence behind it, and the command that brings it back.
| Tier | Evidence | Bulk-deletable |
|---|---|---|
auto |
The owning tool documents a purge command (uv cache clean, npm cache clean) |
yes |
verified |
A marker file proves it is what its name says — pyvenv.cfg, package.json |
yes |
probe |
Name matched a known category; contents passed a shape check | no |
inspect |
A probe objected, and says why | no |
devclean clean --tier acts only on auto and verified. Anything else has to
be named explicitly, so a directory whose contents were never understood cannot
be swept up by a batch command.
A real example. This dist/ sits next to a pyproject.toml and is gitignored,
so nothing in git holds a copy:
$ devclean plan
inspect — 3.1 GB (needs your decision)
3.1 GB ~/Documents/GitHub/pai/dist
back via: Rebuilt by `python -m build`
concern: contains a subdirectory (_stage/) — dist/ should be flat
concern: DATAVERSE_UPLOAD.md is not a wheel or sdist
concern: pai_2022-2023_html.tar.gz is a tarball whose name carries
no release version — it may be data rather than a build artifact
concern: gitignored — no copy in version control
Those archives were a dataset staged for publication. Every structural signal said "build artifact". Only the contents disagreed.
The same probes clear genuine build output without complaint — a dist/ holding
naampy-0.9.0-py3-none-any.whl and a .gitignore reports no concerns.
The repo ships a skill that drives the CLI through a confirmation flow: measure, show the list, resolve every concern, confirm per group, then verify one restore actually works before calling it done.
/plugin marketplace add gojiplus/devclean
/plugin install devclean
Per-user caches for uv, pip, npm, pnpm, yarn, Poetry, conda, Homebrew,
pre-commit, Cargo, Go, Gradle, Maven, Playwright, Selenium, JetBrains, Xcode and
the iOS simulator; PyTorch, HuggingFace and Whisper model caches; virtualenvs
and node_modules under your project directories or directly under your home
directory; and project-local caches
(__pycache__, .pytest_cache, .mypy_cache, .ruff_cache, .tox) rolled up
per category rather than listed one directory at a time. One pruned filesystem
walk supplies all project-level checks, including roots from
additional_search_paths.
On macOS, devclean also reports non-current TeX Live and R installations,
inactive python.org frameworks, Homebrew global Python package trees, retained
MacTeX installers, broken MacPorts prefixes, Chrome code-signing clones, and VS
Code extension directories marked obsolete. These system findings always stay
in inspect. A stale version may be intentional, and finding it does not grant
permission to delete it.
The scan also checks direct children of /private/tmp, /var/tmp, and the
current user's macOS temporary directory. Only directories owned by the current
user are reported, and these parents always stay in inspect: being in
temporary storage is not proof that a directory is unused or reproducible.
Verified virtualenvs and node_modules nested inside those parents are listed
separately as bulk-deletable when a manifest proves how to restore them.
Overlapping parent and child candidates are counted only once in the scan total.
Model caches and anything a probe flags stay out of the bulk-deletable set.
Optional, at ./.devclean.toml or ~/.devclean.toml:
additional_search_paths = ["~/workspace", "/Volumes/code"]
[scan]
min_size_mb = 100
max_depth = 8
timeout_seconds = 120
include_system_artifacts = true
[safety]
protected_paths = ["~/important-project"]Relative additional paths resolve from your home directory. Overlapping roots are collapsed so a directory tree is not scanned twice.
devclean config init writes a starter file; devclean config show prints the
active settings.
One guard, in devclean/safety.py, used by every deletion path. It refuses your
home directory, its top-level folders, system roots, temporary roots, anything
directly inside a system root, and any parent of your home directory.
Configured protected_paths are honored everywhere. Outside your home,
deletion is limited to children of the recognized temporary roots.
System artifact detection is inventory only. The bulk cleanup command cannot delete system toolchains, package-manager prefixes, installers, Chrome clones, or obsolete editor extensions.
Deleting a nested path such as ~/Documents/GitHub/project/.venv is allowed —
that is the point — while ~/Documents itself is not.
make install-dev
make check-all # ruff, pyright, pydoclint, bandit, pytestMIT