Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

stale-archive

Small Rust CLI that finds files unused for N months, groups them by extension and name similarity, and optionally packs each group into a zip then removes the originals (move into archive).

Default mode is dry-run (report only). Destructive work requires --apply.

Install

cargo install --path .
# or
cargo build --release
# binary: target/release/stale-archive

Release builds use LTO, opt-level = "z", and strip for a small binary (typically a few MB on macOS arm64).

Usage

# Dry-run: list proposed groups and zip names
# On a TTY, stderr shows progress during scan, grouping, and apply; non-TTY/CI is silent.
stale-archive ~/Documents

# Zip under default <DIR>/.stale-archive and delete originals
stale-archive ~/Documents --apply --yes

# Custom threshold, output dir, exclusions
stale-archive ~/Downloads --months 12 --out /Volumes/Archive/stale --exclude '*.tmp' --apply --yes

# Shell completions
stale-archive --completions zsh > ~/.zfunc/_stale-archive
stale-archive --completions bash > ~/.local/share/bash-completion/completions/stale-archive

Flags

Flag Description
DIR Root directory to scan (tab-completes as a directory)
--months <N> Stale threshold in months (default 6; month = 30 days)
--out <DIR> Where to write zips (default <DIR>/.stale-archive)
--min-group <N> Min files per named group; smaller clusters go to misc (default 2)
--exclude <GLOB> Extra ignore globs (repeatable)
--follow-symlinks Follow directory symlinks (file symlinks are always skipped)
--max-depth <N> Limit recursion depth
--apply Write zips and delete originals
--yes Skip interactive confirm (required when stdin is not a TTY)
--completions <shell> Print completion script (bash/zsh/fish/powershell/elvish)
-v, --verbose More logging
-h, --help / -V, --version Help / version

Exit codes: 0 success, 1 hard error, 2 partial (some groups failed or deletes failed).

Age detection

  1. macOS: usable Spotlight / Launch Services kMDItemLastUsedDate (Finder “Date Last Opened”) when present; otherwise mtime. Filesystem atime is not used — Finder browsing and Spotlight scans bump access without a real open.
  2. Other platforms: usable atime when present; otherwise mtime.
  3. Timestamps in the future are never treated as stale.
  4. Threshold: N * 30 * 24 * 3600 seconds (no calendar library).

“Usable” means seconds since Unix epoch > 0. Last-used can be unset (never opened / not indexed) — then mtime applies. --months 0 marks every file with a usable non-future timestamp as stale (handy for tests).

Grouping

  1. Bucket by normalized extension (jpeg→jpg, htm→html, none→_noext).
  2. Within each extension:
    • Normalize stem (lowercase, collapse _/-/. to spaces).
    • Strip only trailing allowlist tokens: final, copy, draft, wip, old, new, backup, bak, v[digits], (N), copyN — not bare years or integers.
    • Exact normalized-key groups with size ≥ --min-group.
    • Then first significant token (length ≥ 4).
    • Then guarded longest-common-prefix (prefix ≥ 4, ≥ 50% of shorter, ≥ 60% of longer, boundary + versionish suffix checks — does not merge data/database or report/repost).
    • Remainder → {ext}__misc when min-group ≥ 2.

Zip names: {ext}__{slug}__stale-{YYYYMMDD}.zip (UTC date).

Safety

  • Dry-run by default.
  • --apply without --yes prompts on a TTY; non-TTY requires --yes.
  • Per group: write *.zip.partial → finish → open-verify → rename → then delete originals. On zip failure the partial is removed and nothing is deleted for that group.
  • Zip entries are paths relative to the scan root with / separators; .., absolute, and prefix components are rejected.
  • Always skips default denylist dirs, hidden directories, .DS_Store / Thumbs.db, and the entire --out tree.
  • Symlink files are skipped; directory symlinks only with --follow-symlinks.

Default skip dirs

.git, .hg, .svn, .stale-archive, node_modules, target, dist, build, .Trash, .trash, __pycache__, .cache, .venv, venv, .idea, .vscode, plus any other hidden directory.

Development

cargo test
cargo build --release
ls -lh target/release/stale-archive

About

Small Rust CLI that finds files unused for N months, groups them by extension and name similarity, and optionally packs each group into a zip then **removes the originals** (move into archive).

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages