dev-sweep 0.1.6

A fast, interactive CLI tool to find and clean build artifacts and dependency caches across all your dev projects
Documentation
<p align="center">
  <img src="assets/logo.png" alt="dev-sweep logo" width="200">
</p>

<h1 align="center">dev-sweep</h1>

<p align="center">
  A fast, interactive CLI tool to find and clean build artifacts and dependency caches across all your developer projects.
</p>

<p align="center">
  <a href="https://crates.io/crates/dev-sweep"><img src="https://img.shields.io/crates/v/dev-sweep.svg" alt="crates.io"></a>
  <a href="https://docs.rs/dev-sweep"><img src="https://docs.rs/dev-sweep/badge.svg" alt="docs.rs"></a>
  <a href="https://github.com/markwaidjr/dev-sweep/blob/master/LICENSE"><img src="https://img.shields.io/crates/l/dev-sweep.svg" alt="license"></a>
</p>

Every developer accumulates gigabytes of `node_modules/`, `target/`, `.venv/`, and `build/` directories across dozens of old projects they haven't touched in months. **dev-sweep** scans your filesystem, finds those space hogs, and lets you reclaim disk space in seconds.

## Why dev-sweep?

There are other tools in this space — [kondo](https://github.com/tbillington/kondo), [npkill](https://npkill.js.org/), `cargo-sweep`, and others. dev-sweep stands out in a few key ways:

- **See before you sweep** — dev-sweep shows you a full table with project names, types, sizes, what will be cleaned, and when each project was last touched — *before* you decide to delete anything. Most similar tools prompt you project-by-project with no overview.
- **Flexible selection** — Pick exactly which projects to clean using numbers (`1,3,5`), ranges (`3-7`), or `all`. No scrolling through one-at-a-time y/n prompts.
- **Scriptable**`--json` output and `--dry-run` make it easy to integrate into CI pipelines, cron jobs, or disk monitoring scripts.
- **Configurable** — Persist your ignored paths, excluded project types, default scan roots, and max depth in `~/.config/dev-sweep/config.json` so they apply every time.
- **Truly polyglot** — 17 project types detected from a single binary, covering Rust, Node.js, Python, Java, .NET, Go, Zig, CMake, Swift, Elixir, Haskell, Dart, Ruby, Scala, Unity, Godot, and Terraform. No runtime dependencies — just one static binary.
- **Safe by default** — Every destructive operation requires confirmation. `--dry-run` shows exactly what would happen without touching a thing. Source files and marker files are never deleted.
- **Zero runtime dependencies** — Unlike npkill (requires Node.js), dev-sweep is a single compiled binary. Install it and it just works.
- **Thoroughly tested** — 181 tests covering edge cases like symlink loops, Unicode paths, deeply nested projects, empty artifact directories, and more.

## Features

- **Smart project detection** — automatically identifies 17 project types by their marker files
- **Parallel scanning** — uses [rayon]https://crates.io/crates/rayon for concurrent filesystem traversal and size calculation
- **Interactive cleaning** — select individual projects by number, range (`3-7`), or `all`
- **Safe by default** — confirmation prompts before every destructive operation; `--dry-run` to preview
- **Age filtering** — target stale projects with `--older-than 3m`
- **JSON output** — machine-readable mode (`--json`) for scripting and pipelines
- **Beautiful terminal output** — colored Unicode tables, animated spinner, human-readable sizes
- **Persistent configuration** — save ignored paths, excluded project types, and default scan roots
- **Minimal dependencies** — only 7 crates; ANSI colors and table rendering implemented from scratch
- **Comprehensive test suite** — 181 tests across 8 test files covering every module

## Installation

### From [crates.io]https://crates.io/crates/dev-sweep (recommended)

dev-sweep is published on crates.io. If you have [Rust](https://www.rust-lang.org/tools/install) installed, just run:

```bash
cargo install dev-sweep
```

This downloads, compiles, and installs the `dev-sweep` binary to `~/.cargo/bin/` — no need to clone the repo.

### From source

If you'd prefer to build from the latest code:

```bash
git clone https://github.com/markwaidjr/dev-sweep.git
cd dev-sweep
cargo install --path .
```

Requires Rust 1.85+ (edition 2024).

### Build locally (without installing)

```bash
cargo build --release
# Binary: ./target/release/dev-sweep
```

## Usage

### Scan (default command)

Discover projects and display reclaimable space:

```bash
# Scan the current directory
dev-sweep

# Scan a specific directory
dev-sweep ~/projects

# Limit scan depth
dev-sweep ~/projects -d 3

# Only show projects untouched for 3+ months
dev-sweep --older-than 3m ~/projects

# Output as JSON
dev-sweep --json ~/projects
```

### Clean

Interactively select and remove build artifacts:

```bash
# Interactive mode — pick which projects to clean
dev-sweep clean ~/projects

# Preview what would be cleaned (no deletions)
dev-sweep clean --dry-run ~/projects

# Clean everything without prompting
dev-sweep clean --all ~/projects

# Clean only stale projects
dev-sweep clean --older-than 6m ~/projects
```

When running interactively, `dev-sweep clean` presents a numbered list and accepts:

- Single numbers: `3`
- Comma-separated: `1,4,7`
- Ranges: `3-8`
- Mixed: `1,3-5,9`
- Everything: `all`

### Summary

Quick overview grouped by project type:

```bash
dev-sweep summary ~/projects
```

```
  📊 dev-sweep summary for /home/mark/projects

  Total projects:     28
  Reclaimable space:  53.4 GB

  By project type:
            Rust  22 projects, 48.1 GB
         Node.js  4 projects, 4.6 GB
          Python  1 projects, 33.0 MB
            .NET  1 projects, 695.2 MB
```

### Config

Manage persistent settings stored at `~/.config/dev-sweep/config.json`:

```bash
# Show current configuration
dev-sweep config --show

# Reset to defaults
dev-sweep config --reset
```

## CLI Reference

```
Usage: dev-sweep [OPTIONS] [PATH] [COMMAND]

Commands:
  scan      Scan for projects and show what can be cleaned (default)
  clean     Interactively select and clean projects
  summary   Show a quick summary of reclaimable space
  config    Manage dev-sweep configuration
  help      Print help for a command

Arguments:
  [PATH]    Directory to scan (defaults to current directory)

Options:
  -d, --max-depth <N>            Maximum directory depth to scan
  -o, --older-than <AGE>         Only show projects older than this (e.g. "30d", "3m", "1y")
      --json                     Output results as JSON
  -h, --help                     Print help
  -V, --version                  Print version
```

**`clean` subcommand options:**

```
  -a, --all       Clean all found projects without prompting
      --dry-run   Show what would be cleaned without actually deleting
```

### Age format

The `--older-than` flag accepts a number followed by a unit:

| Unit | Meaning          | Example |
|------|------------------|---------|
| `d`  | Days             | `30d`   |
| `w`  | Weeks            | `4w`    |
| `m`  | Months (30 days) | `3m`    |
| `y`  | Years (365 days) | `1y`    |

## Supported Project Types

| Type | Marker Files | Cleaned Directories |
|---|---|---|
| **Rust** | `Cargo.toml` | `target/` |
| **Node.js** | `package.json` | `node_modules/`, `.next/`, `.nuxt/`, `dist/`, `.cache/` |
| **Python** | `pyproject.toml`, `setup.py`, `requirements.txt` | `__pycache__/` (recursive), `.venv/`, `venv/`, `.tox/`, `*.egg-info/`, `.mypy_cache/`, `.pytest_cache/` |
| **Java** | `pom.xml`, `build.gradle`, `build.gradle.kts` | `target/`, `build/`, `.gradle/` |
| **.NET** | `*.csproj`, `*.fsproj`, `*.sln` | `bin/`, `obj/` |
| **Go** | `go.mod` | *(detected but no per-project artifacts to clean)* |
| **Zig** | `build.zig` | `zig-cache/`, `zig-out/` |
| **CMake** | `CMakeLists.txt` | `build/`, `cmake-build-debug/`, `cmake-build-release/` |
| **Swift** | `Package.swift` | `.build/` |
| **Elixir** | `mix.exs` | `_build/`, `deps/` |
| **Haskell** | `stack.yaml`, `*.cabal` | `.stack-work/` |
| **Dart** | `pubspec.yaml` | `.dart_tool/`, `build/` |
| **Ruby** | `Gemfile` | `vendor/bundle/` |
| **Scala** | `build.sbt` | `target/`, `project/target/` |
| **Unity** | `ProjectSettings/ProjectVersion.txt` | `Library/`, `Temp/`, `Obj/`, `Logs/` |
| **Godot** | `project.godot` | `.godot/` |
| **Terraform** | `main.tf`, `*.tf` | `.terraform/` |

Marker files support three matching strategies:
- **Exact name**`Cargo.toml`, `package.json`
- **Glob suffix**`*.csproj`, `*.cabal`, `*.tf`
- **Nested path**`ProjectSettings/ProjectVersion.txt`

## Configuration

dev-sweep looks for a config file at `~/.config/dev-sweep/config.json`. All fields are optional and default to empty/null:

```json
{
  "ignore_paths": ["/home/mark/projects/keep-this"],
  "exclude_kinds": ["Go", "Terraform"],
  "default_roots": ["~/projects", "~/work"],
  "max_depth": 5
}
```

| Field | Type | Description |
|---|---|---|
| `ignore_paths` | `string[]` | Absolute paths to skip during scanning |
| `exclude_kinds` | `string[]` | Project types to exclude (e.g. `"Rust"`, `"Node"`, `"Python"`) |
| `default_roots` | `string[]` | Default directories to scan when no path is given |
| `max_depth` | `number \| null` | Maximum directory traversal depth |

## Project Structure

```
dev-sweep/
├── Cargo.toml                          # Package manifest and dependencies
├── Cargo.lock                          # Locked dependency versions
├── LICENSE                             # MIT license
├── README.md
│
├── src/
│   ├── lib.rs                          # Library root — re-exports all modules
│   ├── main.rs                         # CLI entry point (clap), commands, arg parsing
│   ├── util.rs                         # Pure utilities: parse_age, format_bytes,
│   │                                   #   visible_len, pad_left/right, format_age,
│   │                                   #   truncate, shorten_path
│   ├── scanner/
│   │   ├── mod.rs                      # Re-exports
│   │   ├── project.rs                  # ProjectKind enum (17 variants), marker files,
│   │   │                               #   cleanable dirs, CleanTarget, ScannedProject
│   │   └── walk.rs                     # Filesystem walker, project detection,
│   │                                   #   analyze_project, dir_size, resolve_pattern,
│   │                                   #   pycache discovery, skip-dir filtering
│   ├── cleaner/
│   │   └── mod.rs                      # clean_project (with dry-run), clean_projects,
│   │                                   #   CleanResult, safe rm -rf wrapper
│   ├── config/
│   │   └── mod.rs                      # DevSweepConfig: load/save JSON, defaults
│   └── tui/
│       ├── mod.rs                      # Re-exports
│       └── display.rs                  # ANSI color helpers, Unicode table renderer,
│                                       #   print_results_table, print_clean_summary,
│                                       #   multi_select prompt, parse_selection, confirm
│
└── tests/
    ├── age_parser_test.rs              # 17 tests — parse_age valid/invalid inputs
    ├── cleaner_test.rs                 #  6 tests — dry-run, deletion, errors, multi-project
    ├── config_test.rs                  #  6 tests — defaults, round-trip, partial JSON, save/load
    ├── display_test.rs                 # 44 tests — format_bytes, visible_len, pad_*, format_age,
    │                                   #             truncate, shorten_path, ANSI helpers
    ├── scanner_analysis_test.rs        # 20 tests — dir_size, should_visit, analyze_project,
    │                                   #             pycache discovery, scan_directory integration
    ├── scanner_detection_test.rs       # 28 tests — all 17 project types, globs, subdirs, edge cases
    └── selection_parser_test.rs        # 16 tests — numbers, ranges, commas, dedup, error cases
                                        # ─────────
                                        # 137 total
```

### How scanning works

1. **Walk**`walkdir` traverses the directory tree, skipping known artifact directories (`.git`, `node_modules`, `target`, etc.) to avoid descending into millions of files.
2. **Detect** — Each directory is checked against the marker files for all 17 project types. The first match wins (ordered by `ProjectKind::all()`).
3. **Analyze** — For each detected project, `resolve_pattern()` maps cleanable-dir patterns to concrete directory paths, and `as_clean_target()` calculates the size of each. Python projects additionally run `find_pycache_recursive()` to discover nested `__pycache__/` directories.
4. **Filter** — Projects with zero reclaimable bytes are excluded. The results are sorted by size (largest first) and optionally filtered by age.
5. **Display** — Results are rendered as a Unicode box-drawing table with ANSI colors, or as JSON.

Size calculation and project analysis run in parallel using `rayon::par_iter`.

## Dependencies

| Crate | Purpose |
|---|---|
| [clap]https://crates.io/crates/clap | Command-line argument parsing with derive macros |
| [walkdir]https://crates.io/crates/walkdir | Recursive directory traversal |
| [rayon]https://crates.io/crates/rayon | Data parallelism for concurrent size calculation |
| [chrono]https://crates.io/crates/chrono | Date/time handling for last-modified timestamps |
| [serde]https://crates.io/crates/serde + [serde_json]https://crates.io/crates/serde_json | Serialization for config and JSON output |
| [dirs]https://crates.io/crates/dirs | Cross-platform home/config directory resolution |
| [anyhow]https://crates.io/crates/anyhow | Ergonomic error handling |

Terminal colors, table rendering, spinners, and input prompts are implemented without external crates using ANSI escape sequences and Unicode box-drawing characters.

## Testing

```bash
# Run all 181 tests
cargo test

# Run a specific test file
cargo test --test scanner_detection_test

# Run tests matching a pattern
cargo test format_bytes
```

Tests create temporary directories under the system temp dir (`/tmp/dev_sweep_test_*`) and clean up after themselves. No tests touch real project directories.

## Building for Release

```bash
cargo build --release
```

The release profile enables LTO, maximum optimization, and symbol stripping for a small, fast binary.

## License

MIT — see [LICENSE](LICENSE) for details.