dev-prune 1.23.0

Universal, lockfile-safe workspace pruner and background dependency cleaner
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
# IDE & Editor Integration

How `.devprune.json` gets IntelliSense, validation and a recognizable icon in editors —
what already works today with no extension installed, what lives in `editors/`, and the
maintainer checklist for publishing each piece. Written in the same spirit as
[RELEASING.md](RELEASING.md): the parts a human must do are marked as such.

---

<p align="center">
  <img src="../assets/github-readme-banner.png" alt="dev-prune — gigabytes back, nothing you can't rebuild" width="800" />
</p>

---

## The name is `.devprune.json`, permanently

`.dev-prune.json` was considered and rejected. The filename shipped in 1.0.0 and is
part of the backwards-compatibility contract: it is written into users' repositories
and their git exclude files, `ignore.devprune.json` derives from it, the Linux MIME
type is `application/x-devprune`, and the freedesktop icon names
(`application-x-devprune.png`) encode it. Every integration below must use
`.devprune.json` exactly.

---

## What works today, with nothing installed

- **The schema is the single source of truth.** [`schemas/devprune.schema.json`]../schemas/devprune.schema.json
  defines every key with a `description` (rendered as hover tooltips), types, ranges,
  and `additionalProperties: false`. A new config key means editing that file in the
  same commit as `src/config.rs` — [CLAUDE.md]../CLAUDE.md says so.
- **It is hosted at a stable URL:** <https://devprune.vkrishna04.me/schemas/v1/devprune.schema.json>.
  The site build copies the canonical file into `site/public/schemas/v1/` on every
  build (`site/scripts/sync-schema.mjs`, wired as `prebuild`), so the hosted copy
  cannot drift from the one the CLI parses. It once did — the hosted file advertised
  removed keys and rejected real ones — which is why the copy is automated rather than
  remembered.
- **Every file the CLI writes links the schema.** `devp config project` writes a
  `$schema` key pointing at the local copy `devp setup` installs (falling back to the
  hosted URL), so VS Code and every JetBrains IDE already give autocomplete and
  validation on generated files with no extension involved.
- **OS file managers show the icon.** `devp icon` registers `*.devprune.json` with
  Explorer / Finder / Nautilus — a real `shared-mime-info` type plus hicolor icons on
  Linux. This is file-manager integration, not IDE integration, and it stays: nothing
  in `devp setup` patches any editor's settings, so there is nothing to remove on that
  front.

The gap the pieces below close: **hand-written** files (no `$schema` key) in editors
that do not subscribe to SchemaStore, and the **icon inside IDE file trees**.

---

## SchemaStore (covers JetBrains, Visual Studio, Neovim, Zed, and more)

One merged PR to [SchemaStore](https://github.com/SchemaStore/schemastore) gives every
subscribed editor schema-by-filename, no `$schema` key needed.

**Status: live** — [SchemaStore/schemastore#6226](https://github.com/SchemaStore/schemastore/pull/6226)
merged and published in the catalog, so every subscribed editor now resolves
`.devprune.json` by filename with no `$schema` key. The steps below record what the entry
contains, for the day it needs updating.

1. Fork `SchemaStore/schemastore`.
2. Add this entry to `src/api/json/catalog.json` (alphabetical by `name`):

   ```json
   {
     "name": ".devprune.json",
     "description": "Per-repository configuration for dev-prune, the lockfile-verified workspace cleaner",
     "fileMatch": [".devprune.json"],
     "url": "https://devprune.vkrishna04.me/schemas/v1/devprune.schema.json"
   }
   ```

3. Their CI validates that the URL serves a parseable schema. Externally-hosted schemas
   are accepted; hosting stays ours, so schema fixes never wait on a SchemaStore PR.
4. Do **not** add `ignore.devprune.json` to `fileMatch`: that file's contents are
   irrelevant by design (presence alone opts the repository out) and it is usually
   empty, which a JSON schema would flag as an error.

**The merged entry predates `project.devprune.json` and does not list it.** Its
`fileMatch` is `[".devprune.json"]` only, so a subscribed editor gives the committed
file no IntelliSense by filename. Nothing is broken by that: `devp config project
<PATH> --team` writes the `$schema` line into the file it creates, and every editor
here prefers an in-file `$schema` over any catalog match. It costs a hand-written
`project.devprune.json` its autocomplete until someone adds the name — which is a
one-line PR against the entry above, and the reason that entry is recorded here.

The URL in that entry is the one thing here this repository cannot change on its own —
moving the published path needs a second SchemaStore PR, and until it merges every
subscribed editor is asking for a file that is no longer served. `scripts/check-schema.sh`
runs in CI and refuses a mismatch between the schema's `$id`, `constants::JSON_SCHEMA_URL`
and the path `site/public/` publishes at, so the path cannot move quietly. It checks the
same thing about content: the extension's bundled copy and the published copy must both
equal `schemas/devprune.schema.json`. The two sync scripts regenerate them, but only at
build and packaging time — which is after the commit, so without this check a schema
change can merge with the other two copies still describing the previous one.

---

## VS Code family (VS Code, VSCodium, Cursor, Windsurf)

[`editors/vscode/`](../editors/vscode/) holds a near-zero-code extension: a
`package.json` with a `jsonValidation` contribution mapping `.devprune.json` to a
**bundled** copy of the schema, the marketplace icon, and one small `extension.js`
whose sole job is a one-time "devp is not on PATH" notification when a workspace
contains `.devprune.json` but the CLI is missing (trusted workspaces only). Bundling (added in extension 0.2.0) means
hand-written files validate offline, instantly, with nothing to go stale in VS Code's
remote-schema cache; files the CLI writes carry an in-file `$schema` link, which VS
Code prefers, so those keep tracking the hosted URL. The bundled copy cannot drift:
`sync-schema.mjs` runs as `vscode:prepublish`, so every packaging pass — CI or by
hand — refreshes it from `schemas/devprune.schema.json` first. `npx @vscode/vsce
package` in that directory produces the `.vsix` (verified working end to end in a live
VS Code).

**The extension releases on its own tags, not with the CLI.** It has its own version in
`editors/vscode/package.json`, its own changelog in `editors/vscode/CHANGELOG.md` and
its own workflow, `.github/workflows/release-extension.yml`, triggered by
`vscode-v<version>`. The CLI ships often and the extension rarely, so riding along with
every `v*` tag meant republishing an identical package over itself most of the time —
and shipping an extension fix meant cutting a CLI release with nothing in it. Its
release page carries `dev-prune-vscode-<version>.vsix`, the side-load path
(`code`/`codium`/`cursor --install-extension <file>`) for editors that cannot reach a
marketplace, and it is the exact file both marketplaces are published from.

That release is deliberately **not** marked *latest*. `devp update` asks GitHub for the
latest release and reads the version out of its tag; a `vscode-v0.4.0` sitting there
would leave every installed copy unable to compare its own version. The extension
fallback in `devp setup` walks the release list for the newest `vscode-v*` instead.

**Deliberate deviation from the obvious plan:** the extension does *not* use
`contributes.languages` to claim the filename with an icon. Declaring a new language id
for `.devprune.json` would detach the file from VS Code's built-in JSON language
server — killing exactly the autocomplete and validation the extension exists to
provide — and the language-level icon only shows when the active file-icon theme has no
mapping of its own, which the default theme always does. The icon in the *file tree*
comes from icon themes, and the way real config files get one is upstream PRs:

- [material-icon-theme]https://github.com/material-extensions/vscode-material-icon-theme —
  add a `.devprune.json` file association (this is the icon most VS Code users see).
- [vscode-icons]https://github.com/vscode-icons/vscode-icons — same.

Both PRs are submitted (2026-08-20) and await maintainer review:
[material-icon-theme#3567](https://github.com/material-extensions/vscode-material-icon-theme/pull/3567)
(leaf logo recolored to the Material palette, green A400 → cyan A400, as their
guidelines require) and
[vscode-icons#4223](https://github.com/vscode-icons/vscode-icons/pull/4223)
(brand gradient kept, filed against
[icon request #4222](https://github.com/vscode-icons/vscode-icons/issues/4222)).

**Status: published on both** —
[VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=VKrishna04.dev-prune)
and [OpenVSX](https://open-vsx.org/extension/VKrishna04/dev-prune), publisher
`VKrishna04`, extension ID `VKrishna04.dev-prune`. Each carries whatever the last
`vscode-v*` tag published; the two listings are the source of truth for that, not this
page.

**If validation goes quiet** — a file full of stale keys showing zero problems — the
cause is almost always VS Code's remote-schema cache: the JSON language server keeps a
downloaded schema for the life of the window, so a window opened before a schema fix
deployed validates against the pre-fix copy indefinitely. *Developer: Reload Window* or
*JSON: Clear Schema Cache* clears it. This is exactly why the extension bundles the
schema — only files with their own `$schema` link still fetch remotely.

**Publishing a new version (maintainer):**

1. Bump `version` in `editors/vscode/package.json`. Nothing derives it from the CLI's
   version and nothing should — they are two products with two changelogs.
2. Add a `## [<version>] - <YYYY-MM-DD>` section to `editors/vscode/CHANGELOG.md`. The
   workflow extracts it with the same `scripts/changelog-section.sh` the CLI uses, and
   it *becomes* the release body, so write it for the person reading the release page.
3. Commit, then tag and push:

   ```bash
   git tag -a vscode-v0.4.0 -m "vscode-v0.4.0"
   git push origin vscode-v0.4.0
   ```

The workflow refuses the tag if it disagrees with `package.json` — the marketplaces
take the version from the manifest and ignore the tag entirely, so a mismatch would
produce a release page named one thing and two listings named another. Both uploads
matter: VS Code and the forks on Microsoft's gallery read one registry, VSCodium,
Cursor, Windsurf and the rest read Open VSX.

By hand, if the workflow is not an option: `npx @vscode/vsce publish` from
`editors/vscode/` (Azure DevOps PAT with Marketplace → Manage scope), or upload the
`.vsix` in the [manage UI](https://marketplace.visualstudio.com/manage/publishers/VKrishna04);
then `npx ovsx publish dev-prune-vscode-<version>.vsix -p <token>` for Open VSX.

---

## AI-agent rules: `devp skill --agent <editor>`

Editors whose agents read per-repository rule files get theirs written by the CLI, not
by an extension. `devp skill --agent <editor>` writes the embedded rules
(`.agents/rules/dev-prune.rules.md`, compiled into the binary) into the file that
editor's agent actually reads:

| Editor | File |
|---|---|
| `cursor` | `.cursor/rules/dev-prune.mdc` (with Cursor's rule frontmatter) |
| `windsurf` | `.windsurf/rules/dev-prune.md` |
| `antigravity` | `.agent/rules/dev-prune.md` (Gemini Antigravity) |
| `cline` | `.clinerules/dev-prune.md` |
| `roo` | `.roo/rules/dev-prune.md` (Roo Code) |
| `kilocode` | `.kilocode/rules/dev-prune.md` (Kilo Code) |
| `continue` | `.continue/rules/dev-prune.md` |
| `amazon-q` | `.amazonq/rules/dev-prune.md` (Amazon Q Developer) |
| `kiro` | `.kiro/steering/dev-prune.md` |
| `trae` | `.trae/rules/dev-prune.md` |
| `junie` | `.junie/guidelines.md` — a marked block (JetBrains Junie) |
| `gemini` | `GEMINI.md` — a marked block (Gemini CLI) |
| `zed` | `.rules` — a marked block; Zed reads it ahead of every other convention |
| `copilot` | `.github/copilot-instructions.md` — a marked block |
| `agents-md` | `AGENTS.md` — a marked block; read by Codex, Jules, Amp, OpenCode and others |
| `aider` | `CONVENTIONS.md` — a marked block; the one file its editor does not read by finding it |

The first ten own their file outright. The last six share one with other tools, so
dev-prune writes only inside its `<!-- dev-prune:rules:start -->`…`<!-- dev-prune:rules:end -->`
markers — a re-run replaces that block and leaves every byte outside it as found.

Not on this list? `devp skill --rules-file <path>` writes the same marked block into
any path inside the repository, for an editor this table does not name yet, one that
renamed the file it reads, or one that reads a file of its own by a different
convention entirely. It needs no code change and no new release.

Aider is the exception to the rule that writing the file is enough. It reads
`CONVENTIONS.md` only when told to, so `devp skill --agent aider` prints the wiring
the file still needs: `read: CONVENTIONS.md` in `.aider.conf.yml`, or
`aider --read CONVENTIONS.md` at the command line. Rules an agent never loads are
worse than no rules at all — the repository looks configured and nothing is.

Claude Code is deliberately absent from that table: its skill installs globally
(`devp skill`, `devp setup`), so there is nothing to write per repository. It has a
section of its own [below](#claude-code-the-plugin-marketplace).

**This table is frozen by design.** `AgentEditor` does not grow every time an editor
ships, renames what its agent reads, or gets discontinued — chasing that treadmill
forever is exactly what `--rules-file` exists to avoid. For any editor not listed
above, point it at `devp skill --rules-file <path>` and it is onboarded today, with no
code change and no wait for a release.

Not sure which of these you have? Plain `devp skill` ends with a detection report:
every editor whose traces are on the machine (a home-directory footprint like
`~/.cursor`, an installed VS Code-family extension) or in the repository (a committed
`.windsurf/` or `GEMINI.md`), whether its rules here are current, stale or missing,
and the `--agent` command that writes them. The report only ever prints —
`devp skill --detected` is what writes rules for everything on it, in one pass, from
the repository root.

A dedicated `--agent` value is still worth adding when an editor is popular enough
that naming it directly is worth the ongoing upkeep. That is four small changes in
[`src/commands/skill.rs`](../src/commands/skill.rs) and
[`src/constants.rs`](../src/constants.rs):

1. Find where that editor's agent looks for project rules (its docs will name one
   file or directory — that fact is the whole contribution).
2. Add the path as a constant in `src/constants.rs`.
3. Add a variant to `AgentEditor` in `src/commands/skill.rs` with a doc comment naming
   the file (clap turns the variant into the `--agent` value), and one row in
   `AgentEditor::target()` pairing it with the constant. Pick `Style::OwnFile` if the
   editor reads a directory of rule files, or `Style::MarkedBlock` if it reads one
   file other tools also write to — the block writer touches nothing outside the
   markers.
4. Mention the new value in `SKILL_LONG` in `src/help.rs` and in
   [`docs/CLI_REFERENCE.md`]CLI_REFERENCE.md §13 — plus `site/public/llms.txt` and
   the skill's own `SKILL.md`, which restate the list. A unit test in
   `src/commands/skill.rs` (`every_agent_value_is_named_in_the_ide_integration_doc`)
   fails `cargo test` if a variant's name is missing from this page, so a forgotten row
   here is caught before it ships — it does not check the other three files, or that
   the row is still true.

An optional fifth step, if the editor leaves a well-known trace behind: add a row to
`AgentEditor::traces()` naming its home-directory footprint, repository config
directory, or extension ID prefix, so the detection report and `--detected` pick it
up. An editor with no row is still fully reachable by name — detection is a
convenience, not a requirement.

If the editor instead reads the cross-tool `AGENTS.md` convention, no code is needed —
it is already covered by `--agent agents-md`.

### Keeping this table accurate

The unit test above only catches drift between the `AgentEditor` enum and this page,
not between this page and reality. Whether `cursor` still reads
`.cursor/rules/dev-prune.mdc`, for instance, is not something dev-prune can check
automatically: nothing here polls Cursor's changelog. That verification is manual and
honest about it, not a scheduled job pretending to cover it. If a row on this page
turns out to be stale, that is a bug: open one, or send a PR correcting the row and
the constant it comes from.

---

## Claude Code: the plugin marketplace

Every other editor on this page needs `devp` on the machine before its agent learns
anything, because the rules file is written by the binary. Claude Code is the one that
can go the other way round, because this repository is also a plugin marketplace:

```text
/plugin marketplace add Life-Experimentalist/dev-prune
/plugin install dev-prune@dev-prune
```

Two commands, no account, and nothing queued for review. A Claude Code marketplace is a
Git repository with a `.claude-plugin/marketplace.json` in it, so the plugin is
installable the moment that file is on `main`, and `/plugin update` picks up a change the
moment one lands. Nobody is submitting anything to anybody.

What it installs is one skill and nothing else — no hooks, no MCP server, no agents, no
commands. `claude plugin details dev-prune` reports the whole cost:

| | |
| :--- | :--- |
| Skills | `dev-prune` — the same `SKILL.md` the binary embeds |
| Always on | ~110 tokens: the skill's name and description, so the agent knows it exists |
| On invoke | ~20k tokens, paid only when the skill actually fires |

`.claude-plugin/plugin.json` points its `skills` field at `./.agents/skills/`, which is
where the skill already lives — the same file `devp skill` exports and the same one
`include_str!` compiles into the binary. There is no second copy to drift. Everything
else under `.agents/skills/` is git-ignored, so a clone carries exactly one skill.

**The version field matters more than it looks.** `plugin.json` carries the release
version and Claude Code caches an installed plugin under it, so a stale one is not
cosmetic: the cache key never changes, and an install that already has that version
believes it is current forever. `scripts/check-version.sh` reads it on every push for
that reason, the same way it reads the skill's own version stamp.

**If `devp` is installed too, the skill is now on disk twice** — once at
`~/.claude/skills/dev-prune/` from `devp skill` or `devp setup`, once from the plugin.
That is not a collision; Claude Code namespaces plugin skills. But only one of the two
tracks the binary you have: `devp skill` re-exports the version you installed, while the
plugin follows `main`. Keep whichever matches how you got dev-prune.

### A guardrail hook, if rules are not enough

The rules file and the skill both tell an agent never to delete container volumes and
never to `rm -rf` a bloat directory by hand. Rules are prose, though, and an agent under
pressure to free disk space has been known to reach for `docker volume prune` anyway. If
your harness supports command hooks, you can turn the volume rule into a confirmation
prompt the agent cannot skip — not a hard block, just a question that reaches you before
the command runs.

For Claude Code, save this as `~/.claude/hooks/devp-volume-guard.sh`:

```sh
#!/bin/sh
# Copyright 2026 VKrishna04
# SPDX-License-Identifier: Apache-2.0
# PreToolUse hook: ask before any command that deletes container volumes.
# A volume is the only copy of its data, so the decision belongs to a human.
# devp's own volume flow (`devp caches clear docker --include-volumes`) already
# takes each deletion as a typed pick, so it never trips this.
input=$(cat)
if printf '%s' "$input" | grep -qE 'volume[[:space:]]+(rm|remove|prune)|(^|[^[:alnum:]-])--volumes'; then
  printf '%s' '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"This deletes container volumes, which are the only copy of their data. If disk space is the goal, devp caches clear docker --include-volumes lists the unused ones and takes each deletion as a typed pick."}}'
fi
exit 0
```

and wire it into `~/.claude/settings.json`:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "sh ~/.claude/hooks/devp-volume-guard.sh" }
        ]
      }
    ]
  }
}
```

The pattern deliberately does not match `--include-volumes` — that is devp's own
consent-gated flow — and it matches nothing about installs or restores, so `npm ci`
and `devp restore` never prompt. On Windows the same two patterns port directly to a
PowerShell hook (`volume\s+(rm|remove|prune)\b` and `(?<![\w-])--volumes\b`) run as
`powershell -File`. Other harnesses with pre-command hooks can reuse the regexes as-is;
the JSON envelope is Claude Code's.

---

## JetBrains IDEs

SchemaStore delivers the IntelliSense; [`editors/jetbrains/`](../editors/jetbrains/) is
the icon-only micro-plugin — a single Kotlin `LanguageFileType` over the JSON language,
registered for the exact filenames, so JSON features survive while the project tree
shows the dev-prune icon. Its README covers building; it needs a JDK and downloads the
IntelliJ platform on first build, so it is not part of the repository gate.

**Publishing (maintainer):** build `./gradlew buildPlugin`, then upload
`build/distributions/*.zip` at [plugins.jetbrains.com](https://plugins.jetbrains.com/)
(JetBrains account; first upload creates the listing, human review takes a few days).
Before first upload it needs a real vector icon — see the gap below.

---

## The vector logo

[`assets/devprune.svg`](../assets/devprune.svg) is a true vector — a single traced
path with the brand gradient (`#27ff63` → `#01f6fb`) as a real `linearGradient`, 2.2 KB,
produced 2026-08-20 by tracing the 256px raster with potrace and sampling the gradient
endpoints from the 512px one. It is the source for the JetBrains `pluginIcon.svg`
(40×40) and the JetBrains file-tree icon (`icons/devprune.svg`, 16×16 — IntelliJ's
`IconLoader` rasterizes SVG crisply at every HiDPI factor, which the 48px PNG it
replaced could not).

The `assets/favicon/*.svg` files are still the 1024×1024 raster wrapped in an `<svg>`
tag — fine for favicons, but anything new should start from `assets/devprune.svg`.

The material-icon-theme and vscode-icons PRs (the only way `.devprune.json` gets its
own icon in VS Code file trees, since icon themes always win over extension
contributions) are submitted and awaiting review — see the links in the VS Code
section above.