dev-prune 1.8.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +282 -30
  2. package/bin/dev-prune.js +26 -7
  3. package/package.json +32 -12
package/README.md CHANGED
@@ -42,7 +42,7 @@ a single Rust binary, installs its own background schedule, and answers to two n
42
42
 
43
43
  <div align="center">
44
44
 
45
- **Start here**&nbsp;&nbsp; [Install](#install) · [Editors](#in-your-editor) · [60-second tour](#60-second-tour) · [What it looks like](#what-it-looks-like)
45
+ **Start here**&nbsp;&nbsp; [Install](#install) · [Where it is published](#every-place-it-is-published) · [Editors](#in-your-editor) · [60-second tour](#60-second-tour) · [What it looks like](#what-it-looks-like)
46
46
 
47
47
  **How it thinks**&nbsp;&nbsp; [Why it is safe](#why-it-is-safe) · [Features](#features) · [Commands](#commands) · [Ecosystems](#supported-ecosystems) · [Monorepos](#repositories-with-more-than-one-ecosystem)
48
48
 
@@ -96,6 +96,9 @@ problem.
96
96
 
97
97
  ```bash
98
98
  npm install -g dev-prune # or: npx dev-prune status
99
+ bun add -g dev-prune # same package, through bun
100
+ pnpm add -g dev-prune
101
+ yarn global add dev-prune # Yarn 1.x
99
102
  uv tool install dev-prune # or: uvx dev-prune status
100
103
  pipx install dev-prune
101
104
  pip install dev-prune
@@ -103,9 +106,10 @@ cargo binstall dev-prune # fetches the prebuilt release archive
103
106
  cargo install dev-prune # builds from source, needs Rust 1.88+
104
107
  ```
105
108
 
106
- The npm and PyPI packages **contain the binary** — there is no download step at install
107
- time, so they work under `npm ci --ignore-scripts`, behind a registry mirror and offline.
108
- Everything but `cargo install` ships a prebuilt executable.
109
+ The [npm](https://www.npmjs.com/package/dev-prune) and
110
+ [PyPI](https://pypi.org/project/dev-prune/) packages **contain the binary** there is no
111
+ download step at install time, so they work under `npm ci --ignore-scripts`, behind a
112
+ registry mirror and offline. Everything but `cargo install` ships a prebuilt executable.
109
113
 
110
114
  npm delivers it the way esbuild and Biome do: one small `dev-prune` package that lists
111
115
  seven platform packages as optional dependencies, of which npm installs exactly the one
@@ -113,8 +117,14 @@ matching your machine. That is why there is no download step to block. Windows w
113
117
  1.8.0 onwards — earlier versions installed and then reported no binary to run, so a
114
118
  machine still holding `dev-prune@1.7.0` needs `npm install -g dev-prune@latest`.
115
119
 
116
- crates.io stores source and nothing else, so `cargo install` has no binary to fetch and
117
- always compiles. [`cargo binstall`](https://github.com/cargo-bins/cargo-binstall) is the
120
+ bun, pnpm and Yarn install that same package, and dev-prune treats each as a channel of
121
+ its own rather than as npm: a copy `bun add -g` put there is upgraded and removed with
122
+ bun. Running npm against it would install a *second* copy under npm's prefix and leave
123
+ bun's, still on `PATH`, at the old version. `devp update --channels` prints every
124
+ channel's upgrade command if you want to see the whole table.
125
+
126
+ [crates.io](https://crates.io/crates/dev-prune) stores source and nothing else, so
127
+ `cargo install` has no binary to fetch and always compiles. [`cargo binstall`](https://github.com/cargo-bins/cargo-binstall) is the
118
128
  one that downloads: `Cargo.toml` tells it where this project's release archives live, so
119
129
  it unpacks the same executable the installers use, with no toolchain involved.
120
130
 
@@ -190,6 +200,18 @@ rules file that editor actually reads — `.github/copilot-instructions.md`, `.c
190
200
  `CLAUDE.md`, `.junie/guidelines.md` and the rest — so an agent working in the repository
191
201
  knows what dev-prune will and will not delete before it suggests anything.
192
202
 
203
+ **Claude Code can go first**, before `devp` exists on the machine at all — this
204
+ repository is also a Claude Code plugin marketplace:
205
+
206
+ ```text
207
+ /plugin marketplace add Life-Experimentalist/dev-prune
208
+ /plugin install dev-prune@dev-prune
209
+ ```
210
+
211
+ One skill, no hooks and no MCP server, and it is the same `SKILL.md` the binary embeds.
212
+ That is the useful order for an agent: read what dev-prune does and refuses to do, then
213
+ install it.
214
+
193
215
  Everything about editors, in one place:
194
216
  [docs/IDE_INTEGRATION.md](docs/IDE_INTEGRATION.md).
195
217
 
@@ -246,6 +268,39 @@ Every install channel in detail: [docs/DISTRIBUTION.md](docs/DISTRIBUTION.md).
246
268
  > terminal and a scheduled task alike — with no profile to re-source, and no chance of an
247
269
  > upgrade leaving `devp` on the old version.
248
270
 
271
+ ### Every place it is published
272
+
273
+ Nine channels, one project. The middle column is the **exact** string each registry
274
+ answers to — names this close together are how a typo becomes somebody else's package,
275
+ so they are worth copying rather than typing.
276
+
277
+ | Where | Published as | What you get |
278
+ |---|---|---|
279
+ | [crates.io](https://crates.io/crates/dev-prune) | **`dev-prune`** | Source. `cargo install` compiles it; `cargo binstall` fetches the release archive instead. |
280
+ | [PyPI](https://pypi.org/project/dev-prune/) | **`dev-prune`** | Six wheels, each containing the binary. `pip`, `pipx`, `uv tool`. |
281
+ | [npm](https://www.npmjs.com/package/dev-prune) | **`dev-prune`** | A dispatcher plus seven platform packages; npm installs the one matching your machine. |
282
+ | [GitHub Releases](https://github.com/Life-Experimentalist/dev-prune/releases) | `dev-prune-v<ver>-<platform>` | Seven checksummed, provenance-attested archives. What the install scripts fetch. |
283
+ | [Homebrew](https://github.com/Life-Experimentalist/homebrew-tap) | **`Life-Experimentalist/tap`** | A one-formula tap, so `brew upgrade` keeps finding new versions. |
284
+ | [Scoop](https://github.com/Life-Experimentalist/scoop-bucket) | **`life-experimentalist`** | A one-manifest bucket, for the same reason. |
285
+ | [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=VKrishna04.dev-prune) | **`VKrishna04.dev-prune`** | The editor extension. VS Code and the forks on Microsoft's gallery. |
286
+ | [Open VSX](https://open-vsx.org/extension/VKrishna04/dev-prune) | **`VKrishna04.dev-prune`** | The same extension, for VSCodium, Cursor, Windsurf, Positron and Kiro. |
287
+ | [SchemaStore](https://www.schemastore.org/) | **`.devprune.json`** | The config schema, by filename. No extension and no `$schema` key needed. |
288
+
289
+ [WinGet](https://github.com/microsoft/winget-pkgs/pull/422809) is submitted and in
290
+ review. It is not in the table because it does not resolve yet.
291
+
292
+ Two of those rows are not installs of dev-prune at all. SchemaStore is why JetBrains
293
+ IDEs, Visual Studio, Neovim and Zed validate `.devprune.json` with nothing installed —
294
+ the catalog entry points at a schema this repository hosts. And the editor extension is
295
+ a separate product with a separate version number, released on its own `vscode-v*` tags,
296
+ which is why the release marked *latest* on GitHub is always the CLI.
297
+
298
+ Anything calling itself dev-prune from anywhere other than these was not published by
299
+ its author. `devp doctor` names the channel the running copy came from, and every
300
+ release archive carries [GitHub build provenance](docs/RELEASING.md) that
301
+ `gh attestation verify` checks against this repository — a checksum only proves a file
302
+ arrived intact, which a substituted pair also does.
303
+
249
304
  ---
250
305
 
251
306
  ## 60-second tour
@@ -263,6 +318,7 @@ A few more worth knowing on day one:
263
318
  ```bash
264
319
  devp stats # how much has been reclaimed so far, and by which repositories
265
320
  devp caches # every package manager cache, sized. The report deletes nothing
321
+ devp caches docker # what Docker holds, and the prune commands. Read-only, permanently
266
322
  devp status --drift # anything installed that the lockfiles don't record?
267
323
  devp doctor . # why is this repository not being pruned?
268
324
  devp doctor --fix # repair a broken integration — never a first-time install
@@ -355,6 +411,13 @@ Package manager caches
355
411
  Total 21.99 GiB across 12 caches
356
412
 
357
413
  → Nothing above was deleted, and dev-prune never deletes any of it.
414
+
415
+ Container engines
416
+
417
+ docker 47.64 GiB 36.69 GiB reclaimable · devp caches docker
418
+
419
+ Container images, volumes and build cache are not package manager caches and are not in
420
+ the total above — dev-prune reports them and never deletes them.
358
421
  ```
359
422
 
360
423
  A cache lives outside every repository and is shared by all of them, so no single
@@ -371,6 +434,64 @@ a registered repository, and each such row names its store in the command it pri
371
434
 
372
435
  </details>
373
436
 
437
+ <details>
438
+ <summary><b><code>devp caches docker</code></b> — the engine that is usually bigger than all of them</summary>
439
+
440
+ ```console
441
+ $ devp caches docker
442
+
443
+ Container engines
444
+
445
+ docker
446
+
447
+ Images 37.36 GiB 29.28 GiB reclaimable 41 items, 9 in use
448
+ Containers 1.19 GiB 1.12 GiB reclaimable 12 items, 3 in use
449
+ Local Volumes 2.79 GiB 0 B reclaimable 5 items, 2 in use
450
+ Build Cache 6.29 GiB 6.29 GiB reclaimable 41 items, 0 in use
451
+
452
+ Total 47.64 GiB 36.69 GiB reclaimable
453
+
454
+ Reclaim it yourself what it takes with it
455
+ docker builder prune the build cache; costs a slower next build
456
+ docker image prune dangling images no tag points at any more
457
+ docker container prune stopped containers and each writable layer
458
+ docker system prune the three above at once; volumes untouched
459
+ docker system prune --volumes adds unused volumes — the one that deletes data
460
+
461
+ Nothing above was deleted, and nothing dev-prune runs on a schedule will ever delete it.
462
+ An image has no lockfile to prove it can be rebuilt, and a named volume is the one thing
463
+ here that cannot be rebuilt at all — so this command measures, prints the commands, and
464
+ leaves the decision with you.
465
+ ```
466
+
467
+ Clearing 6 GiB of npm cache while a Docker install nobody has looked at in a year sits on
468
+ 40 GiB is the mistake this exists to prevent. `devp caches podman` and `devp caches
469
+ nerdctl` are the same report for those engines, and `devp caches containers` runs every
470
+ one it finds and lists any local Kubernetes clusters (kind, k3d, minikube) by name.
471
+
472
+ **It is read-only, permanently.** There is no flag and no `clear` verb that makes
473
+ dev-prune run one of those commands — `devp caches clear docker` is a usage error that
474
+ says so — and no scheduler or Git hook reaches this code at all. That is the same rule
475
+ as everywhere else rather than extra caution: an image has no lockfile to prove it can be
476
+ rebuilt, the Dockerfile that built it may not be on this disk, and a named volume is the
477
+ one thing on the machine that cannot be rebuilt at all.
478
+
479
+ The figures come from the engine's own `system df`, not a walk of the disk. On Docker
480
+ Desktop and Podman the store lives inside a VM disk image the host filesystem cannot see,
481
+ so a size taken off the disk would be wrong by orders of magnitude in the reassuring
482
+ direction — and asking is the only way to learn what is *reclaimable*, which is the
483
+ figure that decides anything. 40 GB of images with 38 GB dangling is a different
484
+ situation from 40 GB with 2 GB dangling.
485
+
486
+ An engine that is installed with its daemon stopped is reported as exactly that, quoting
487
+ the engine's own first line, and contributes no figures: a blank, not a zero. An engine
488
+ that is not installed is absent from the report rather than listed as missing. Local
489
+ Kubernetes clusters are named and deliberately not sized — their nodes are containers
490
+ belonging to an engine already in the table, so a figure beside the cluster name would be
491
+ the same gigabytes counted twice.
492
+
493
+ </details>
494
+
374
495
  <details>
375
496
  <summary><b><code>devp doctor .</code></b> — the one reason a repository is being skipped</summary>
376
497
 
@@ -444,13 +565,14 @@ process that leaves a dirty working tree is a surprise.
444
565
  | 🧩 **Any number of ecosystems per repository** | uv, npm and cargo in one root, or spread across `frontend/`, `services/api/` and `tools/cli/` — each discovered, verified and pruned on its own terms |
445
566
  | ↩️ **One-command restore** | `devp restore .` reinstalls a tree; `devp restore --last-run` puts back exactly what the most recent pass deleted, across every repository it touched |
446
567
  | 🕒 **Activity-aware** | Combines `git log` timestamps with source-file `mtime`, so uncommitted work protects a repository just as a commit does |
568
+ | 🐳 **Container report** | `devp caches docker` (also `podman`, `nerdctl`, or `containers` for all of them plus local Kubernetes clusters) breaks a container engine's disk into images, containers, local volumes and build cache, each with what the engine itself calls reclaimable, and prints the prune commands narrowest first with what each takes with it. **Read-only, permanently** — `devp caches clear docker` is a usage error, and no scheduler or hook reaches this code: an image has no lockfile to prove it can be rebuilt, and a named volume cannot be rebuilt at all. `devp caches` carries a one-line summary per engine, outside its own total |
447
569
  | 📊 **Cache report** | `devp caches` sizes every package manager cache and store on the machine — npm to cargo to conda, Maven, Gradle, NuGet, vcpkg, Conan, Composer, CocoaPods and Hex — and prints the command that clears each. The report is read-only; `devp caches clear <manager>` runs that command for you, after asking. `devp config set cache_max_gb uv=10,npm=10` says how big is too big, per manager, and marks the ones past it — `devp caches clear --over-cap all` then empties exactly those, still only when you type it. Each manager also says how many of your registered repositories use it and what that works out to per repository, and `devp caches clear --unused all` empties the ones nothing uses at all. pnpm is reported once per filesystem, because a store it hardlinks into `node_modules` cannot cross one and projects kept off the system disk get a store of their own. Nothing on a schedule ever touches a cache, and Maven's `~/.m2/repository` is never cleared at all — it holds artifacts `mvn install:install-file` put there that no remote can hand back |
448
570
  | 🩺 **`devp doctor`** | One read-only pass that ends by naming the *single* reason a repository would or would not be pruned. Runs no package manager, repairs nothing, safe to run twice. `devp doctor --fix` then mends what it found — installed-but-broken only |
449
571
  | 🤖 **Self-installing automation** | OS-native scheduler (Task Scheduler, LaunchAgent, systemd user timer) and non-blocking Git hooks, installed at install time and restored after an upgrade. `auto_setup`, `auto_hooks`, `auto_daemon` or `DEV_PRUNE_NO_AUTO_SETUP=1` turn it off |
450
572
  | ⚡ **0ms opt-out** | An `ignore.devprune.json` in a repository root is honoured by file presence alone — no read, no parse |
451
573
  | 🔌 **`--json` on every reporting command** | `run`, `status`, `stats`, `trust` and `caches` each emit one versioned document on stdout, diagnostics on stderr. Built for scripts and agents |
452
- | 🧠 **AI agent skill** | A token-lean `SKILL.md` embedded in the binary; `devp skill` exports it and prints onboarding prompts for Claude Code, Gemini Antigravity, Cursor, Windsurf, Copilot and OpenClaw |
453
- | 🧰 **Editor extension** | Validates `.devprune.json` as you type and shows the workspace's reclaimable size in the status bar. `devp setup` offers to install it — once, only at a terminal — into VS Code, VSCodium, Cursor, Windsurf, Positron or Kiro, each from its own registry with the release `.vsix` as fallback. [docs/IDE_INTEGRATION.md](docs/IDE_INTEGRATION.md) |
574
+ | 🧠 **AI agent skill** | A token-lean `SKILL.md` embedded in the binary; `devp skill` exports it and prints onboarding prompts for Claude Code, Gemini Antigravity, Cursor, Windsurf, Copilot and OpenClaw, and the repository doubles as a Claude Code plugin marketplace |
575
+ | 🧰 **Editor extension** | Validates `.devprune.json` as you type and shows the workspace's reclaimable size in the status bar. `devp setup` offers to install it — once, only at a terminal — into VS Code, VSCodium, Cursor, Windsurf, Positron or Kiro, each from its own registry, falling back to the `.vsix` from the extension's own release. [docs/IDE_INTEGRATION.md](docs/IDE_INTEGRATION.md) |
454
576
  | 🖼️ **File manager icons** | `devp icon` registers `*.devprune.json` with the OS file manager — a real `shared-mime-info` type plus hicolor icons on Linux, a folder icon on Windows. It never edits your editor settings, `PATH` or shell startup files |
455
577
  | 🌏 **Unicode-safe paths** | A repository at `ワークスペース/项目目录名称测试/프론트엔드` scans, verifies, prunes and restores exactly like an ASCII one, on all three platforms. Terminal tables are padded by display *column*, not by character, so full-width CJK names keep `devp status` and `devp doctor` aligned. Accented Latin, Cyrillic, Arabic and emoji directory names too |
456
578
  | 🚫 **No telemetry** | One optional unauthenticated `GET` to GitHub's public releases endpoint, at most weekly, no body and no identifier. Nothing else leaves the machine |
@@ -467,9 +589,10 @@ process that leaves a dirty working tree is a surprise.
467
589
  | `devp undo` | :------------------------------------------------------------------ | Reverts the most recent `init` or `link` |
468
590
  | `devp run [PATH]` | `--dry-run`, `--only`, `--skip`, `--except`, `--min-size`, `--json` | Prunes every registered repository, or one target |
469
591
  | `devp status` | `--top N`, `--drift`, `--json` | Interactive dashboard; a plain table when there is no TTY. `--top N` shows only the N biggest repositories; `--drift` lists every environment holding packages its lockfile never recorded |
470
- | `devp stats` | `--json` | What has already been reclaimed: lifetime total, prune passes, the last pass, and the biggest contributors |
592
+ | `devp stats` | `--json` | What has already been reclaimed: lifetime total from pruning, a separate lifetime total from `devp caches clear`, prune passes, the last pass, and the biggest contributors |
471
593
  | `devp completions` | `bash`, `zsh`, `fish`, `powershell`, `elvish` | Prints a shell completion script to stdout, generated from the same argument definitions the binary parses with |
472
594
  | `devp caches` | `clear <manager\|all>`, `--json` | Sizes every package manager cache on the machine and prints the command that clears each. The report deletes nothing and nothing on a schedule ever will; `clear` empties one when you type it, after asking |
595
+ | `devp caches docker` | `podman`, `nerdctl`, `containers [ENGINE]`, `--json` | What a container engine holds — images, containers, volumes, build cache — each sized, with what the engine calls reclaimable, then the prune commands. Read-only permanently: it prints them, you run them |
473
596
  | `devp trust` | `--json` | What dev-prune may do on this machine: the guarantees the code enforces, then the scheduler, hooks and settings read live. Read-only |
474
597
  | `devp restore [PATH]` | `--last-run` | Reinstalls dependencies for every project in a tree; `--last-run` undoes the last prune pass |
475
598
  | `devp doctor [PATH]` | `--fix` | Diagnoses the installation, or one repository — ending with the single reason a pass would or would not touch it. `--fix` repairs what the checks found; it never performs a first-time install |
@@ -637,30 +760,159 @@ Global settings live in `%APPDATA%\dev-prune` (Windows),
637
760
  once on a first install — so the defaults are something you agreed to rather than
638
761
  inherited — and again after an upgrade adds a setting you have never been shown.
639
762
 
640
- | Key | Default | Meaning |
641
- | :--------------------------------------------------------------------------------- | :-------: | :---------------------------------------------------------------------------------- |
642
- | `idle_days` | `15` | How long a repository must be untouched to become a candidate |
643
- | `min_size_mb` | `0` | Smallest bloat directory worth deleting; `0` disables the floor |
644
- | `scan_depth` | `6` | Levels below a repository root that discovery descends |
645
- | `require_confirmation` | `true` | Whether a pass asks before deleting |
646
- | `allow_manifest_rewrite` | `false` | Whether verification may *repair* a drifted lockfile instead of refusing |
647
- | `command_timeout_secs` | `600` | Ceiling on any one package manager command |
648
- | `auto_setup` · `auto_daemon` · `auto_hooks` | `true` | Whether the integration pass may run unattended, and what it may install |
649
- | `auto_hooks_chain` | `false` | Whether it may take a `core.hooksPath` another tool holds, forwarding every hook on |
650
- | `check_interval_days` | `2` | How often the OS scheduler runs a pass |
651
- | `update_check` | `true` | Whether the periodic release check runs |
652
- | `update_check_interval_days` · `update_check_timeout_secs` | `7` · `5` | Minimum gap between checks, and how long one may hang |
653
- | `enable_cargo` … `enable_cmake_build` | `false` | Turn on an opt-in build-tool adapter; `build_idle_days` (`45`) gates all eight |
654
- | `adapter_idle_days` | *(none)* | Per-adapter idle floors, as `cargo=90,npm=30` each raises only its own window |
655
- | `disabled_adapters` | *(none)* | Adapters to leave alone entirely, by name — as if that ecosystem were not installed |
656
- | `cache_max_gb` | *(none)* | Per-manager cache caps in GiB, as `uv=10,npm=10` the cap itself deletes nothing |
763
+ If you already know what you want, `devp config recommended` is the one-command version:
764
+ it turns on the eight adapters and build trees that are off by default because they are
765
+ not universally wanted, and leaves `allow_manifest_rewrite` the one recommendation
766
+ that edits files Git tracks named, explained and off unless you add
767
+ `--with-cautious`. `devp config show` lists whatever you have not taken yet.
768
+
769
+ Thirty settings, in the seven groups the configurator asks them in — the order the
770
+ decisions actually arrive in. Every key, with its full description and range, is in the
771
+ [CLI reference](docs/CLI_REFERENCE.md#8-devp-config-action).
772
+
773
+ **The language this is all in** what dev-prune prints its own headings in.
774
+
775
+ | Key | Default | Meaning |
776
+ | :--- | :---: | :--- |
777
+ | `language` | `en` | One of twelve catalogues — `en`, `zh`, `hi`, `te`, `ta`, `kn`, `ml`, `bn`, `mr`, `gu`, `pa`, `sa`. Only dev-prune's own headings and summary lines move; `--json`, exit codes, flag names, config keys and adapter names are English in every one, so nothing a script reads changes. `DEV_PRUNE_LANG` overrides it for one command. See [TRANSLATIONS.md](docs/TRANSLATIONS.md) |
778
+
779
+ **What gets pruned** the size and age of what a pass considers at all.
780
+
781
+ | Key | Default | Meaning |
782
+ | :--- | :---: | :--- |
783
+ | `idle_days` | `15` | How long a repository must be untouched to become a candidate |
784
+ | `min_size_mb` | `0` | Smallest bloat directory worth deleting; `0` disables the floor |
785
+ | `scan_depth` | `6` | Levels below a repository root that discovery descends |
786
+ | `disabled_adapters` | *(none)* | Adapters to leave alone entirely, by name — as if that ecosystem were not installed |
787
+ | `adapter_idle_days` | *(none)* | Per-adapter idle floors, as `cargo=90,npm=30` — each raises only its own window |
788
+
789
+ **Before anything is deleted** — what stands between a candidate and a deletion.
790
+
791
+ | Key | Default | Meaning |
792
+ | :--- | :---: | :--- |
793
+ | `require_confirmation` | `true` | Whether a pass asks before deleting |
794
+ | `allow_manifest_rewrite` | `false` | Whether verification may *repair* a drifted lockfile instead of refusing |
795
+ | `command_timeout_secs` | `600` | Ceiling on one package manager command; nothing compiles under it |
796
+
797
+ **Build trees — off by default**, because everything here comes back by recompiling
798
+ rather than downloading. That is the whole reason each one is a switch.
799
+
800
+ | Key | Default | Meaning |
801
+ | :--- | :---: | :--- |
802
+ | `enable_cargo` … `enable_cmake_build` | `false` | Turn on an opt-in build-tool adapter; `build_idle_days` (`45`) gates all eight |
803
+
804
+ **Shared download caches** — one key, because the cap only ever marks.
805
+
806
+ | Key | Default | Meaning |
807
+ | :--- | :---: | :--- |
808
+ | `cache_max_gb` | *(none)* | Per-manager cache caps in GiB, as `uv=10,npm=10` — the cap itself deletes nothing |
809
+
810
+ **Running without being asked** — what dev-prune may install here, and how often it may
811
+ act on its own.
812
+
813
+ | Key | Default | Meaning |
814
+ | :--- | :---: | :--- |
815
+ | `auto_setup` · `auto_daemon` · `auto_hooks` | `true` | Whether the integration pass may run unattended, and what it may install |
816
+ | `auto_config` | `false` | Whether `devp init` / `devp link` write a `.devprune.json` into newly registered repositories |
817
+ | `auto_hooks_chain` | `false` | Whether it may take a `core.hooksPath` another tool holds — off, it is not yours |
818
+ | `check_interval_days` | `2` | How often the OS scheduler runs a pass |
819
+
820
+ **Keeping dev-prune current** — whether this copy looks for a newer one, and may install it.
821
+
822
+ | Key | Default | Meaning |
823
+ | :--- | :---: | :--- |
824
+ | `update_check` | `true` | Whether the periodic release check runs |
825
+ | `update_check_interval_days` · `update_check_timeout_secs` | `7` · `5` | Minimum gap between checks, and how long one may hang |
826
+ | `auto_update` | `true` | Install a newer release at the end of a pass. Stands aside on WinGet, Scoop and Homebrew, where the manager owns the upgrade |
827
+ | `version_lock` | `false` | Pin this copy to the version it is. Nothing dev-prune does then replaces the binary, and there is no flag that bypasses it |
828
+
829
+ ### Per-repository settings
657
830
 
658
831
  Three of them — `idle_days` (as `override_idle_days`), `min_size_mb` and `scan_depth` —
659
- also take a per-repository form in that project's `.devprune.json`, where they win for
660
- that tree only. The rest are deliberately global: a project can commit its
661
- `.devprune.json`, and a repository you have never read should not be able to grant itself
832
+ also take a per-repository form, where they win for that tree only. The rest are
833
+ deliberately global: a repository you have never read should not be able to grant itself
662
834
  permission to have its manifests rewritten during an unattended pass.
663
835
 
836
+ A repository can hold **two** config files, and the difference is who they are for.
837
+
838
+ | File | Committed? | For |
839
+ |---|---|---|
840
+ | `.devprune.json` | No — added to `.git/info/exclude` when written | Your copy of this one repository, on this one machine |
841
+ | `project.devprune.json` | **Yes** — `devp config project . --team` writes it to be staged | What the project decided, reaching a fresh clone by itself |
842
+
843
+ Where both exist, **every key the project file names wins**, and the personal file
844
+ answers everything it does not. That is the inverse of the usual "local overrides
845
+ global", and deliberately: a colleague's stale local answer should not quietly overrule
846
+ what the project decided, while a personal override still works on every setting the team
847
+ left open. "Names a key" means the key is literally in the file — a project file that
848
+ never mentions `ignore` does not un-ignore your repository, which is why it is created
849
+ holding nothing but its `$schema` line.
850
+
851
+ ```mermaid
852
+ flowchart TD
853
+ Key["One setting, in one repository"] --> Shared{"Named in<br/>project.devprune.json?"}
854
+ Shared -->|Yes| WinShared["The team's value.<br/>Committed, so a fresh clone starts here."]
855
+ Shared -->|No| Personal{"Named in<br/>.devprune.json?"}
856
+ Personal -->|Yes| WinPersonal["Your value.<br/>Never committed, never in git status."]
857
+ Personal -->|No| Global["The global setting on this machine,<br/>or its default."]
858
+ ```
859
+
860
+ Nothing dev-prune writes on its own touches the shared file. `devp link`,
861
+ `devp doctor --fix`, `devp config project . --update` and `[i]` in the dashboard all
862
+ write `.devprune.json`, so no routine action of yours turns into a change on a branch
863
+ your colleagues share; `--team` is the only way to write the other file, and you
864
+ have to type it. Run `devp config project .` in a repository that has both and it
865
+ prints which file each effective value came from.
866
+
867
+ ### Directories only the project knows about
868
+
869
+ Every adapter earns its right to delete the same way: it finds a lockfile, verifies the
870
+ lockfile can rebuild what is about to go, and only then deletes. Some trees have no
871
+ lockfile and are still rebuildable — a generated fixture set, a vendored toolchain, a
872
+ cache with a `make` target behind it. A repository can name those itself, in either
873
+ config file:
874
+
875
+ ```json
876
+ {
877
+ "prunable": {
878
+ "directories": [
879
+ {
880
+ "path": "tools/vendor",
881
+ "rebuild": "make vendor",
882
+ "why": "regenerated from tools/manifest.toml"
883
+ }
884
+ ]
885
+ }
886
+ }
887
+ ```
888
+
889
+ `rebuild` is required, and required is the point: an optional one would make "delete
890
+ this, I have no idea how to get it back" the easiest thing to write in a file that gets
891
+ committed and cloned. If a directory genuinely needs nothing to bring it back, say so
892
+ — `"rebuild": "echo not needed"` is a legal answer and works on every platform.
893
+ dev-prune shows the command; it never runs it.
894
+
895
+ Declared directories go through the ordinary pass under the adapter name `declared`, so
896
+ they are listed by `devp status`, obey `--dry-run`, `--min-size` and `--only`, and are
897
+ deleted by the scheduled run alongside everything else.
898
+
899
+ A declaration is a claim, not an instruction. `project.devprune.json` is committed, so a
900
+ repository you cloned can declare whatever it likes, and `devp run` may be running from a
901
+ scheduler with nobody watching. Before deleting one, dev-prune requires that:
902
+
903
+ - the path is relative, has no `..`, and is neither `.git` nor the repository root;
904
+ - it resolves to somewhere inside the repository, even through a symlinked parent;
905
+ - Git is tracking nothing inside it — a lockfile cannot rebuild a file that is in the
906
+ repository itself;
907
+ - the first word of `rebuild` is a program this machine actually has.
908
+
909
+ A claim that fails any of those is printed with the reason, and nothing is deleted.
910
+
911
+ This section is the one part of either file that does not follow the precedence above:
912
+ the two lists **add up**. A list is not a decision, so a team declaration never discards
913
+ one you wrote yourself, and naming the same path in both leaves one directory, rebuilt
914
+ by the committed command.
915
+
664
916
  An out-of-range value is rejected with the range in the message rather than silently
665
917
  clamped. `scan_depth` included: `config set` accepts `1`–`32` and refuses anything else
666
918
  outright — the clamp to that range survives only as the backstop for a hand-edited
@@ -723,7 +975,7 @@ flowchart TD
723
975
 
724
976
  Engine --> FastIgnore{ignore.devprune.json?}
725
977
  FastIgnore -->|Exists| Skip["Skip Repo O(1) 0ms"]
726
- FastIgnore -->|Missing| PerRepoConfig["Read .devprune.json"]
978
+ FastIgnore -->|Missing| PerRepoConfig["Read project.devprune.json,<br/>then .devprune.json"]
727
979
 
728
980
  PerRepoConfig --> GitScanner["Git Scanner & Activity Solver<br/>(.git commits + mtime fallback)"]
729
981
  Engine --> PreCheck["Required Ecosystem Binary Pre-Checker"]
package/bin/dev-prune.js CHANGED
@@ -66,7 +66,18 @@ const fallbacks = [
66
66
  const binaryPath =
67
67
  bundledBinary() || fallbacks.find((c) => fs.existsSync(c)) || exeName;
68
68
 
69
- const child = spawn(binaryPath, process.argv.slice(2), { stdio: 'inherit' });
69
+ let child;
70
+ try {
71
+ child = spawn(binaryPath, process.argv.slice(2), { stdio: 'inherit' });
72
+ } catch (err) {
73
+ // Windows throws out of `spawn` itself when the loader refuses the image — a
74
+ // wrong-architecture or truncated binary — because there is no child to emit an
75
+ // 'error' event on. Everywhere else the same failure arrives asynchronously. Without
76
+ // this branch the message written for exactly that case never runs, and the user gets
77
+ // a raw Node stack trace instead. `reportLaunchFailure` is a hoisted function
78
+ // declaration, so it is callable from above its definition; it never returns.
79
+ reportLaunchFailure(err);
80
+ }
70
81
 
71
82
  // Forward termination to the child. Ctrl+C already reaches it through the shared
72
83
  // process group, but a bare `kill` of this wrapper — process managers, CI timeouts —
@@ -85,11 +96,19 @@ for (const sig of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
85
96
 
86
97
  // Without this, a missing binary surfaces as an unhandled 'error' event and a Node
87
98
  // stack trace instead of an actionable message.
88
- child.on('error', (err) => {
89
- if (err.code === 'ENOENT' && binaryPath !== exeName && fs.existsSync(binaryPath)) {
90
- // The file is right there, yet the loader said "no such file" — the kernel's
91
- // report for a binary it cannot load, typically one built for a different
92
- // architecture. "Install what you already installed" would loop the user.
99
+ child.on('error', reportLaunchFailure);
100
+
101
+ function reportLaunchFailure(err) {
102
+ // ENOENT on a file that is demonstrably there, or Windows' UNKNOWN: both are a loader
103
+ // refusing the image rather than a file that is missing.
104
+ if (
105
+ (err.code === 'ENOENT' || err.code === 'UNKNOWN') &&
106
+ binaryPath !== exeName &&
107
+ fs.existsSync(binaryPath)
108
+ ) {
109
+ // The file is right there, yet the loader would not take it — typically a binary
110
+ // built for a different architecture. "Install what you already installed" would
111
+ // loop the user.
93
112
  console.error(
94
113
  `dev-prune: '${binaryPath}' exists but could not be executed.\n\n` +
95
114
  `It may be built for a different architecture than ${process.platform}-${process.arch},\n` +
@@ -116,7 +135,7 @@ child.on('error', (err) => {
116
135
  console.error(`dev-prune: failed to launch ${binaryPath}: ${err.message}`);
117
136
  }
118
137
  process.exit(127);
119
- });
138
+ }
120
139
 
121
140
  // `code` is null when the child was killed by a signal; `code || 0` reported success
122
141
  // in that case. Mirror the shell convention of 128 + signal number instead.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dev-prune",
3
- "version": "1.8.0",
3
+ "version": "1.10.0",
4
4
  "description": "Universal, lockfile-safe workspace pruner. Reclaims disk space from idle Git repositories by deleting only dependency and build directories a lockfile can rebuild.",
5
5
  "bin": {
6
6
  "dev-prune": "./bin/dev-prune.js",
@@ -12,13 +12,13 @@
12
12
  "LICENSE.md"
13
13
  ],
14
14
  "optionalDependencies": {
15
- "dev-prune-darwin-arm64": "1.8.0",
16
- "dev-prune-darwin-x64": "1.8.0",
17
- "dev-prune-linux-arm64": "1.8.0",
18
- "dev-prune-linux-x64": "1.8.0",
19
- "dev-prune-windows-arm64": "1.8.0",
20
- "dev-prune-windows-x64": "1.8.0",
21
- "dev-prune-windows-x86": "1.8.0"
15
+ "dev-prune-darwin-arm64": "1.10.0",
16
+ "dev-prune-darwin-x64": "1.10.0",
17
+ "dev-prune-linux-arm64": "1.10.0",
18
+ "dev-prune-linux-x64": "1.10.0",
19
+ "dev-prune-windows-arm64": "1.10.0",
20
+ "dev-prune-windows-x64": "1.10.0",
21
+ "dev-prune-windows-x86": "1.10.0"
22
22
  },
23
23
  "engines": {
24
24
  "node": ">=20"
@@ -26,13 +26,33 @@
26
26
  "keywords": [
27
27
  "dev-prune",
28
28
  "devp",
29
- "cleaner",
29
+ "cleanup",
30
+ "prune",
30
31
  "disk-space",
31
32
  "node_modules",
32
- "venv",
33
- "target",
34
33
  "monorepo",
35
- "rust"
34
+ "lockfile",
35
+ "cache",
36
+ "npm",
37
+ "pnpm",
38
+ "yarn",
39
+ "bun",
40
+ "python",
41
+ "venv",
42
+ "uv",
43
+ "poetry",
44
+ "rust",
45
+ "cargo",
46
+ "go",
47
+ "gradle",
48
+ "maven",
49
+ "composer",
50
+ "bundler",
51
+ "cocoapods",
52
+ "swift",
53
+ "elixir",
54
+ "terraform",
55
+ "flutter"
36
56
  ],
37
57
  "author": "VKrishna04",
38
58
  "license": "Apache-2.0",