Skip to content

Releases: dbowm91/cargo-cleanme

cargo-cleanme v0.2.11

Choose a tag to compare

@github-actions github-actions released this 25 Oct 17:23
Immutable release. Only release title and notes can be modified.

0.2.11

An output root inside another package's directory could delete that package's checked-in files. This is the one to read first if you are upgrading from 0.2.10. A covering root nested inside a package directory was classified private and cleaned, and the tool reported the space it had reclaimed — so a package whose target/ sat inside another package's tree lost its checked-in files, not just its build output. A covering root nested inside a package directory is now never a cleanup target, and the cleanup engine re-proves that at the moment of the spawn rather than trusting the earlier classification. If a package directory under a cleaned root looks thinner than it should, restore it from version control; the changelog names the defect and its regression test.

The cleanup spellings moved to the root, and the old ones still work. cargo cleanme --full is now the canonical Full-cleanup spelling and cargo cleanme ~/projects is the canonical bounded-root spelling — the root is the single cleanup front door for all three scopes and both modes:

$ cargo cleanme --full            # the canonical Full cleanup
$ cargo cleanme ~/projects        # the canonical bounded-root cleanup
$ cargo cleanme                   # routine maintenance

Nothing published was removed: cargo cleanme clean --full and cargo cleanme clean ~/projects still parse, still mean exactly what they meant, and are now hidden from the help. The reason for the move is that the two-level spelling had become a trap — a flag written at the root and the same flag written on clean could disagree, and one of them silently won. They are now the same flag, and a flag given at both levels is a usage error rather than a silent preference. --known does not move to the root: cargo cleanme --known is a usage error (exit 2), because the root has no --known; cargo cleanme clean --known still resolves to the bare invocation. scan --full remains a hidden alias of scan --all.

cargo cleanme . is now a bounded cleanup of .. It was a usage error (exit 2), because . was indistinguishable from the hidden clean alias. It is now the bounded-root spelling for the current directory.

The Full cleanup no longer refuses everything because one walk was incomplete. Until now, a Full reconciliation whose platform-wide walk did not finish refused the whole cleanup: the run exited 2 and nothing was cleaned, because an incomplete walk was read as "every learned root is unproven". That inference is wrong — an incomplete walk is evidence that the walk did not finish, not that every root under it is unmeasured. The Full cleanup now re-walks each learned root as its own walk root and judges it on its own traversal: a root that proves is cleaned, a root that does not is reported as unproven with the reason code skipped_incomplete_coverage, and the run exits 1 because the cleanup was partial. Two things did not change: a reconciliation that produced no fresh generation at all still refuses outright with the same message, and a missing per-root coverage verdict still fails closed.

A direct cargo cleanme silently discarded the ROOT you typed. The Cargo-injected token was stripped by spelling, and since the ROOT positional moved to the root the two are spelled identically — cargo-cleanme cleanme lost its own root and became a machine-wide Routine cleanup, so the scope label changed from explicit to routine and your project trees joined the selected roots. The token is now identified by launch context: Cargo exports CARGO to the external subcommand it runs, and a direct invocation does not.

A command-line --exclude spelled through a symlink protected nothing. The configured patterns were canonicalised and the command line's were not, so the tree you asked to keep was deleted. One canonicaliser now serves both.

A directory named cleanup authorization root could change the run's exit code. The cleanup engine's authorization verdict was a prose substring match on a human-readable message. The verdict is a typed BlockReason now; the message renders the verdict rather than being it.

The interactive selection surface renders what it draws. The frame was written with a bare newline — a linefeed without a carriage return in raw mode — so every line started at the column the previous one ended in, the frame occupied twice the rows it claimed, and the rewind erased most of it. The terminal cursor was never restored, so a session that ended in NOR left your shell with no visible cursor. The frame's geometry was wrong too: its width was clamped up to forty cells, so in a narrower terminal every line wrapped, and its height was bounded in lines against the terminal's height even though the frame occupies one row more than its line count — which scrolled the title off the top. The frame now adopts the terminal's geometry exactly and degrades in the order that costs you least: the reclaimable-size column goes first, then the ownership column's wording, then the name keeps what is left. The interactive preconditions are now checked before the scope is resolved, so a refused -i no longer spawns cargo metadata across every resolved root.

Also fixed: a root --profile that silently discarded a clean --package and exited 0 after doing a different destructive action; before_bytes contradicting the row's own detail; a stale VISUAL hiding a usable $EDITOR; learned_root_retention_days truncated with as u16; the shared symlink predicate trimming a trailing backslash on Unix, where \ is an ordinary filename character; two human surfaces writing path text raw; and the --full alias no longer being advertised in scan --help or the shell completions.

The JSON envelope keeps its shape and its schema_version: 1; its before_bytes now publishes the size the decision used, and the log surface's diagnostics and unresolved fields now agree with the JSON's. Three exit codes moved: cargo cleanme . is no longer a usage error, the Full cleanup's incomplete-coverage case exits 1 instead of refusing with 2, and a selector given at both levels is a usage error instead of one of them silently winning.

Five prebuilt targets are attached — x86-64 and aarch64 for Linux, macOS, and Windows x64 — each with a SHA-256 sidecar, alongside the four installer scripts and a release manifest.

See CHANGELOG.md for the full record. Pre-1.0: read the changelog before upgrading.

cargo-cleanme v0.2.10

Choose a tag to compare

@github-actions github-actions released this 21 Oct 00:35
Immutable release. Only release title and notes can be modified.

0.2.10

A bare cargo cleanme now cleans the directory you are standing in. The working directory was a filter over a universe that did not contain it, so from a directory outside the machine's known roots the run proved nothing at all: on the machine that prompted this, cargo cleanme in ~/.codex reported 0 would-clean while cargo cleanme clean ~/.codex reported four workspaces and 107.15 GiB in the same directory at the same moment. The working directory now joins the maintenance scope as an additional root — the workspace that encloses you and the directory itself. The scope only ever grows: the working directory is added to the known roots, never substituted for them, so a target shared with a project outside the scope still stays shared and stays inventory-only.

The working directory joins only when the machine's known roots do not already reach it. Standing inside a known root, or in a directory that contains one — your home directory, for a scheduler — leaves the bounded machine-wide sweep bounded. Sweeping everything under your home would also sweep your Trash and your package caches, whose abandoned manifests cannot be resolved and would block the run: measured on that machine, it turned a nine-second sweep of the known roots into a sixty-five-second sweep of 264,183 directories that ended blocked on 1,357 unresolvable manifests. To sweep a whole directory on purpose, name it: cargo cleanme clean ~.

The read-only cargo cleanme scan --known inventory covers the same universe, so it stays an inventory of what the cleanup would act on. The scope label in the JSON envelope is unchanged (routine), and selected_roots carries the actual roots.

The interactive selection surface is now a compact finder rather than a full-screen list. cargo cleanme -i painted every resolved unit as a row in a frame that occupied the entire terminal, so a scope with hundreds of workspaces was a screenful to scroll rather than a list to read: there was no scroll-position indicator, no paging key, and the absolute path of every row took most of the width while the part that distinguishes one workspace from another sat at its far end. The frame is now a bounded page of at most fifteen rows that never outgrows the terminal, and a row is named by the shortest part of its path that identifies it among the candidates — siblings read project-01, project-19. The reclaimable size is right-aligned so the big ones line up, the ownership annotation is dropped entirely when it would say the same thing on every row, the gutter marks the cursor and the edges of the window, and a key-hint bar names the keys. PgUp, PgDn, Home and End move through the page. The surface is also the first in the crate to render colour; set NO_COLOR (or run with TERM=dumb) to turn it off. The interaction model is unchanged: the NOR/INS modes, the all-toggle row, the published key table, and the selection contract all stand.

A run that cleaned nothing now says so, in one line, and exits 0. The interactive surface refused an empty candidate list as a configuration error (exit 2) while the same scope cleaned without -i exited 0 — two spellings of one scope disagreed, and an unattended caller could not tell "there was nothing to do" from "the command was wrong". Both now print Nothing to clean. and exit 0. The same verdict replaces the scope census and the summary of zeroes for a human cleanup report that has nothing in it. A run that found units and deliberately left them alone still reports every one of them: "nothing was selected" never reads as "nothing was eligible".

The learned-root omission summary is no longer printed for by-design omissions. A learned root that sits at a known-transient origin — an agent worktree, a package cache, a temporary directory — is omitted by design: the tool would never have cleaned it, so omitting it narrows nothing. That summary fired on essentially every Routine invocation, in the tool's error shape, naming paths the operator never asked about. A durable learned root that has vanished, and a root that is no longer a directory, are still reported — those are news.

The JSON envelope, the log line, and every exit code other than the interactive empty case are unchanged.

Five prebuilt targets are attached — x86-64 and aarch64 for Linux, macOS, and Windows x64 — each with a SHA-256 sidecar, alongside the two installers and a release manifest.

See CHANGELOG.md for the full record. Pre-1.0: read the changelog before upgrading.

cargo-cleanme v0.2.9

Choose a tag to compare

@github-actions github-actions released this 19 Oct 20:31
Immutable release. Only release title and notes can be modified.

0.2.9

Scoped proof is the default; out-of-scope blocking was a product defect. The ownership proof now covers the selected scope — the working directory anchor for bare/--known cleanup, the explicit roots for clean ROOT, and the full system for clean --full (which keeps whole-system fail-closed blocking). Discovery and grouping still cover the whole scope, so cross-boundary output claims still force shared output. An unresolvable manifest outside the scope is reported in diagnostics and never blocks; an in-scope manifest Cargo itself refused is reported, never blocking. Only in-scope byte-capable unknowns block, and a scope that proves zero units against any unproven participant fails closed.

Exits are boundary-scoped: 0 when the boundary is fully cleaned, 1 when it is blocked or a unit fails. The never-published --partitioned flag and the result.islands field are removed.

A test with an unstated host premise was failing every CI lane. The tool was never wrong — it refuses a Cargo release it has not qualified, and it does so without invoking a Cargo clean — but the test that drove it asserted one outcome for a branch whose premise it never stated, so it broke the moment the hosted stable moved to a new patch release. The case now derives its expectation from the product's own capability check and asserts a specific outcome per premise, and a new host-independent case pins that a --profile/--package selector reaches Cargo's command line. No product behaviour changed, and no Cargo version was added to the capability allowlist.

Five prebuilt targets are attached — x86-64 and aarch64 for Linux, macOS, and Windows x64 — each with a SHA-256 sidecar, alongside the two installers and a release manifest.

See CHANGELOG.md for the full record. Pre-1.0: read the changelog before upgrading.

cargo-cleanme v0.2.8

Choose a tag to compare

@github-actions github-actions released this 15 Oct 04:52
Immutable release. Only release title and notes can be modified.

0.2.8

Bare cargo cleanme and clean --known now select the cleanup units anchored at the working directory. Root resolution and the ownership proof are unchanged: the full Routine scope is still discovered and classified, so a target shared with a workspace outside the anchor stays shared and stays inventory-only. clean ROOT and clean --full keep every resolved unit. Excluded units report as typed skipped_outside_selection rows.

scan requires an explicit selector: scan --all (-a) for Full machine reconciliation, scan --known for Routine inventory, or scan ROOT for one bounded scope. Bare scan is a usage error. scan --full stays as a hidden alias of --all.

New: -i / --interactive presents one row per resolved cleanup unit with its reclaimable bytes and cleans only what is checked. Combines with --dry-run for a review that spawns no cargo clean.

See CHANGELOG.md for the full record. Pre-1.0: read the changelog before upgrading.

cargo-cleanme v0.2.7

Choose a tag to compare

@github-actions github-actions released this 13 Oct 19:22
Immutable release. Only release title and notes can be modified.

Draft binary release staged by Eggpack. Maintainer review and publication are required before this release becomes public.

cargo-cleanme v0.2.6

Choose a tag to compare

@github-actions github-actions released this 12 Oct 20:17
Immutable release. Only release title and notes can be modified.

Draft binary release staged by Eggpack. Maintainer review and publication are required before this release becomes public.

cargo-cleanme v0.2.5

Choose a tag to compare

@github-actions github-actions released this 09 Oct 20:40
Immutable release. Only release title and notes can be modified.

Draft binary release staged by Eggpack. Maintainer review and publication are required before this release becomes public.

cargo-cleanme v0.2.4

Choose a tag to compare

@github-actions github-actions released this 08 Oct 21:59
Immutable release. Only release title and notes can be modified.

Draft binary release staged by Eggpack. Maintainer review and publication are required before this release becomes public.

cargo-cleanme v0.2.3

Choose a tag to compare

@github-actions github-actions released this 07 Oct 16:28
Immutable release. Only release title and notes can be modified.

Fixed — the installer now makes cargo-cleanme findable

The POSIX installer reported success while a new terminal could not find the
binary.
It installs a normal user account's copy into $HOME/.local/bin,
which is not on PATH by default on a stock macOS account. It detected that,
printed export PATH=… for you to run yourself, and exited 0 — so a first
install left a working binary that a newly opened terminal could not resolve
until you performed the printed step by hand.

A default non-root install now appends one bounded, guarded block to a single
startup file, after the verified binary is in place:

# >>> cargo-cleanme installer PATH >>>
case ":$PATH:" in
  *":$HOME/.local/bin:"*) ;;
  *) export PATH="$HOME/.local/bin:$PATH" ;;
esac
# <<< cargo-cleanme installer PATH <<<

It is idempotent, so re-running the installer adds nothing. Delete the markers
and everything between them to remove it.

The installer cannot change the PATH of the shell that invoked it — a
piped child cannot mutate its already-running parent — so it still prints the
export for the current shell. Open a new terminal, or paste it yourself.

Which file is used: $ZDOTDIR/.zshrc when ZDOTDIR is an absolute path and
$HOME/.zshrc otherwise for zsh; $HOME/.bashrc for bash on Linux; and on
macOS the first existing file among $HOME/.bash_profile, $HOME/.bash_login,
$HOME/.profile, else a new $HOME/.bash_profile, because Terminal there
starts a login shell.

Nothing is written when it should not be: a --dir destination (your
explicit choice), a root/system install, a shell that is not zsh or bash, an
unset or invalid ZDOTDIR, or a target that is a symlink, a directory, a FIFO,
or unwritable. The installer never sources, evaluates, or command-substitutes
the file it writes, never creates a directory to put a dotfile into, and never
follows a symlink. A failed write prints the manual step and leaves the
already-verified binary installed.

Added

  • install.sh --no-shell-profile — install the binary and print the manual
    PATH guidance, but write no startup file.

Changed

  • install.sh --no-path is now the broad opt-out: no startup-file mutation
    and no PATH output. It previously suppressed only the output. This is a
    deliberate strengthening for existing scripted callers — one that already
    opted out of PATH handling will not acquire a new side effect after
    upgrading.

install.ps1 and all Windows environment-variable handling are unchanged.

Verification

The installer fixture suite gained 19 cases that assert the state transition
rather than the presence of guidance text — the earlier assertion passed on a
printed instruction, which is how the defect survived a release cycle. Each was
shown to fail against a mutated wrapper. A hosted macOS lane starts a fresh
zsh -l -i with a fixture-owned HOME and asserts it resolves the exact
installed binary, and that the same shell does not resolve it once the profile
entry is removed.

No CLI, JSON, or release-asset contract changed in this release.

cargo-cleanme v0.2.2

Choose a tag to compare

@github-actions github-actions released this 07 Oct 02:21
Immutable release. Only release title and notes can be modified.

cargo-cleanme 0.2.2 — a matching fix.

One defect shipped in every release through 0.2.1, and this release fixes it.

The defect

A config exclude naming a directory whose canonical path contains [ or ]
— legal characters in a Windows file name — did not exclude it on Windows.
A directory named real[abc] was read as a character class, so the pattern
stopped matching the tree you named and matched neighbours you never wrote down.

Read this if you use exclude on Windows. An exclusion that silently fails
is the direction that matters: a project you excluded could still be cleaned.
Every other guard was unchanged — cleanup is opt-in, still runs through Cargo,
and still requires an ownership proof — but your stated boundary was not being
applied.

# On 0.2.1 and earlier this does not match, on Windows.
exclude = ["C:/work/real[abc]/*"]

Why it took until now

The pattern rewrite that makes a symlink-spelled pattern work was gated on an
escape it could not produce, so on Windows it never ran at all. Fixing it
exposed two more assumptions of the same kind — a Win32 \\?\ prefix whose ?
is a glob metacharacter, and a trailing \ that is a separator rather than a
dangling escape. All three are fixed.

The behaviour change is strictly protective: a written exclude now matches, so
nothing can newly become cleanable. Existing patterns are byte-identical after
the rewrite, so no configuration needs changing.

{ and } in a directory name still cannot be escaped and are left as written
— docs/USAGE.md documents the rule and this case.

Verifying a download

This release is immutable and carries a signed attestation binding it to this
repository, tag and every asset digest. See
docs/RELEASING.md.

Full details: CHANGELOG.md

Upgrading

cargo install cargo-cleanme --locked

or use the installers. A binary installed by Cargo is refused by
cargo cleanme update, by design.