Safely reclaim gigabytes eaten by dormant repositories.
Finds dependency directories (node_modules, vendor, .venv, target…) and removes only those that can be fully restored by the package manager.
You have dozens of cloned repos that haven't been touched in months. Each one carries a dependency directory packed with packages that can be reinstalled in seconds:
~/code/
old-api/ node_modules/ → 1.2 GB 45 000 files
php-legacy/ vendor/ → 120 MB 8 000 files
ml-experiment/ .venv/ → 2.1 GB 15 000 files
rust-prototype/ target/ → 3.4 GB 85 000 files
repo-cleaner scans your code directory, determines which ones are safe to delete, and removes them — in one command.
- Deep lock-file validation — parses lock files to confirm every dependency is downloadable before touching anything
- Non-destructive by default — dry-run mode only reports; deletion requires an explicit flag
- Archive mode — zips a dependency directory into a single file before removing it
- Modular — adding support for a new package manager takes one file and a registration line
- No external dependencies — pure Go stdlib, compiles to a single static binary
go install github.com/rpsl/repo-cleaner/cmd/repo-cleaner@latestOr build from source:
git clone https://github.com/rpsl/repo-cleaner
cd repo-cleaner
go build -o repo-cleaner ./cmd/repo-cleaner# Scan and report — nothing is deleted
repo-cleaner --path ~/code
# Delete safe dependency directories
repo-cleaner --path ~/code --delete
# Zip each directory into one archive, then delete the originals
repo-cleaner --path ~/code --archive
# Scan only specific package managers
repo-cleaner --path ~/code --managers js,python
# Override safety checks (dangerous — use with care)
repo-cleaner --path ~/code --delete --forceScanning /home/user/code...
[go ] /home/user/code/api/vendor 245.3 MB 1.2K files SAFE
[js ] /home/user/code/frontend/node_modules 1.2 GB 45.1K files SAFE
[php ] /home/user/code/legacy/vendor 120.0 MB 8.0K files UNSAFE
! composer.json has path repository: ../local-pkg
[python] /home/user/code/ml-exp/.venv 2.1 GB 14.8K files SAFE
[rust ] /home/user/code/cli/target 3.4 GB 85.3K files SAFE
────────────────────────────────────────────────────────────────────────────────────────────────────
Reclaimable (safe): 7.0 GB across 146.4K files, 4 candidate(s)
Skipped (unsafe): 1 candidate(s) — use --force to override
Run with --delete to remove safe candidates, --archive to zip them first.
A dependency directory is only eligible for removal if its lock file passes deep validation. Unsafe candidates are always reported but never touched without --force.
| Manager | Target dir | Lock file | Flagged as unsafe when |
|---|---|---|---|
| php (Composer) | vendor/ |
composer.lock |
path-type repository in composer.json; package with no downloadable dist/source URL |
| js (npm · yarn · pnpm) | node_modules/ |
package-lock.json / yarn.lock / pnpm-lock.yaml |
file: or link: local dependency; no lock file present |
| python | .venv/ venv/ env/ |
poetry.lock / Pipfile.lock / requirements.txt |
no reproducible source found; unpinned versions; local path dependency |
| go | vendor/ |
go.mod + go.sum |
replace directive targeting a local path |
| rust | target/ |
Cargo.lock |
Cargo.lock missing (target/ is build artifacts — always regeneratable otherwise) |
Archive safety: when using
--archive, the zip is fully verified (every entry re-read) before the original is removed. If verification fails, the zip is discarded and the original is preserved.
- Create
internal/manager/mypm.goimplementing theManagerinterface:
type MyPM struct{}
func (m *MyPM) Name() string { return "mypm" }
func (m *MyPM) TargetDirs() []string { return []string{"packages"} }
func (m *MyPM) Inspect(dir, parent string) (*manager.Candidate, error) {
if !fileExists(filepath.Join(parent, "mypm.lock")) {
return nil, nil // manifest absent — not our project type
}
reasons := parseLock(filepath.Join(parent, "mypm.lock"))
return candidate(m.Name(), dir, parent, reasons), nil
}- Register
&MyPM{}inbuildRegistryincmd/repo-cleaner/main.go. - Add tests using the
makeFixture/assertSafe/assertUnsafehelpers frominternal/manager/testhelper_test.go.
MIT