Skip to content

Latest commit

 

History

History
433 lines (323 loc) · 29.8 KB

File metadata and controls

433 lines (323 loc) · 29.8 KB

Asimov Change Log

All notable changes to this project will be documented in this file.

This project adheres to Semantic Versioning.

Added

  • [scan] dirs config key: an exhaustive list of scan roots, unlike [scan] extra which adds to home. Home is scanned only if you list it yourself, so you can scan just ~/Code instead of all of home (#129).

Changed

Fixed

Removed

0.12.0 — 2026-07-30

Added

  • Vagrant boxes (~/.vagrant.d/boxes) is now one of the built-in global caches, excluded when you opt in with [fixed_dirs] enabled = true (#117).

  • [skip_paths] config section: name directories that Asimov should never search, alongside the built-in ~/.Trash and ~/Library. Thanks @lunaluxie!

  • asimov doctor now checks the data files and reports how many sentinels and fixed directories are loaded, so an incomplete install is diagnosed rather than just failing.

Changed

  • Asimov is no longer a single file. The launcher lives at bin/asimov, the logic in lib/asimov/*.sh, and the directory lists in data/*.tsv. Installed, those become <prefix>/bin/asimov, <prefix>/libexec/asimov/ and <prefix>/share/asimov/. Behaviour is unchanged; every existing test passes untouched.
  • Sentinels, fixed directories and skip paths are now tab-separated data files instead of bash arrays. Each record carries its ecosystem or owning tool as a real field, so adding a pattern is a one-line edit with no bash syntax involved.
  • The remote installer (scripts/install-remote.sh) downloads a tarball instead of a single script, and installs under ~/.local.
  • Release assets are now asimov-<version>.tar.gz rather than a bare asimov script.
  • The Homebrew formula installs the three pieces under one prefix (bin/asimov, libexec/asimov/, share/asimov/) instead of a single script. brew install asimov and brew services start asimov are unchanged.

Fixed

Removed

0.11.0 — 2026-07-29

Added

  • asimov doctor checks an install rather than your projects: which asimov your shell actually runs (and flags an older one shadowing it on PATH), whether a schedule is installed and loaded, whether ~/.cache/asimov/ is readable and writable by you, whether the config file parses, and whether tmutil can read exclusions at all. It is read-only — it prints the fix rather than applying it — and never executes another asimov binary it finds, reading versions out of the file instead, because v0.3.0 ignores every argument and would start a real scan. Exits 1 if it finds anything (#122).

  • UPGRADING.md: a section for upgrading from v0.3.0, the version most people have, covering the three leftovers that outlive it — the LaunchAgent, the old cellar, and a root-owned cache — in an order that works (#122).

  • make test-system-bash runs the suite under the macOS system bash (3.2), which is what most users get — asimov starts with #!/usr/bin/env bash, and a development machine usually has 5.x first on PATH. make test BASH_BIN=<path> pins any interpreter, and make test BATS_JOBS=N runs tests concurrently (needs GNU parallel).

  • asimov prune reports Time Machine exclusions whose directory no longer exists, and compacts Asimov's own path cache. It is read-only with respect to Time Machine: stale entries live in the system-wide "sticky" list (tmutil addexclusion -p), which Asimov never writes, so prune prints the sudo tmutil removeexclusion -p command rather than running it. Asimov's own exclusions are stored as an attribute on the directory itself and are therefore removed automatically when the directory is deleted — only sticky entries can outlive their directory (#38).

  • README: a "What Asimov doesn't do" section, covering the three things Asimov is most often expected to do and doesn't — hide directories from Spotlight (a separate mechanism from Time Machine exclusions), shrink backups that already exist, and delete anything — plus how to verify a run reached your projects (#90, #45).

Changed

  • CI now runs a matrix of both macOS versions × both bash versions (system 3.2 and Homebrew 5.x), with ShellCheck split into its own job so it runs once instead of once per cell. All jobs run in parallel, fail-fast is off so one failing combination cannot hide the others, and a newer push cancels an in-flight run for the same ref.

Fixed

  • An unreadable or unwritable file under ~/.cache/asimov/ no longer kills the run. The cache is an optimisation, but a bare cat on an unreadable state file failed under set -Eeu -o pipefail and aborted immediately, printing nothing but Permission denied. Every cache read and write is now guarded: Asimov warns once, names the reset command, and continues without the cache (#122).
  • A run as root no longer leaves root-owned files behind that break the next run as your own user — the cause of the failure above. The cache directory was chowned to the console user, but the state files written afterwards were not; they are now created before the chown, and appending never changes an existing file's owner (#122).

Removed

0.10.0 — 2026-07-26

First release under the project's new home, AsimovMac/asimov — the original stevegrunwell/asimov repository transferred to a dedicated organization (stars, history, and the old URL preserved via GitHub's redirect). Folds in all fork work from v0.4.0 through v0.8.0, after a v0.9.0-beta.1/beta.2 testing round.

Added

  • Scan directories beyond your home directory. Add a [scan] section to ~/.config/asimov/config with one extra = line per additional root (e.g. extra = /private/var/www); each is scanned on every run alongside home. Configured directories that don't exist are skipped with a warning rather than failing the run, and directories nested inside another scanned root are pruned to avoid traversing the same tree twice (#108)
  • Go's module cache (~/go/pkg/mod) is now one of the built-in global caches (opt-in via [fixed_dirs] enabled = true). Excluding the cache root covers every dependency inside it in a single tmutil call, instead of attempting each vendor/ directory individually (~11s apiece, and doomed — see below). Using a custom GOPATH? Add it with [fixed_dirs] extra

Changed

  • Project home is now AsimovMac/asimov; release tooling, README, and install scripts retargeted accordingly

Fixed

  • Read-only directories are now detected and skipped up front with a clear read-only, cannot be excluded message, instead of burning ~11s on a tmutil addexclusion call that can never succeed. A Time Machine exclusion is stored as an extended attribute on the item itself, so a 0555 directory can never take one — Go's module cache is deliberately read-only, which is why its vendor/ directories always failed
  • tmutil's POSIXError(_nsError: …Code=22 "Invalid argument") dump no longer leaks into Asimov's output. tmutil prints it to stdout (not stderr), so the existing 2>/dev/null never suppressed it; both streams are now silenced and Asimov prints its own warning instead
  • Corrected a long-standing misdiagnosis in the code comments: these failures were attributed to the @ in Go module version paths. @ is irrelevant — a writable path containing @ excludes fine. Read-only permissions were always the cause (regression tests now cover both)

0.8.0 — 2026-06-19

Added

  • --no-read-cache and --no-write-cache flags that control the cache on two independent axes. --no-read-cache ignores all cached state (re-discovers and re-verifies everything, then rebuilds the cache); --no-write-cache reads the cache but persists nothing. --full-scan is now an alias for --no-read-cache, and --no-cache an alias for both together — both kept for backwards compatibility

Changed

  • --full-scan now re-evaluates everything: it ignores the excluded and failed state too (not just paths/mdfind_seen), so a path you manually un-excluded, or a previously failed path, is re-checked against the tmutil isexcluded ground truth. Previously it still trusted the excluded cache and skipped those paths
  • --no-cache now honors its documented contract — it reads and writes nothing. Previously it still read and wrote the excluded/failed state files
  • --full-scan and --no-cache are no longer mutually exclusive (combining them is well-defined: both reads and writes are skipped); the previous "mutually exclusive" error is removed

Fixed

  • Security: a wildcard sentinel value from ~/.config/asimov/config ([sentinels] extra) was interpolated unquoted into a find -execdir sh -c … string, so a crafted value like *.x'; cmd; ' could run arbitrary commands during a scan. The glob is now passed as a positional argument and never re-parsed as shell code (globbing still works); built-in sentinels were unaffected
  • Persistent failed-state (~/.cache/asimov/failed) is now deduplicated each run like the other caches, instead of growing by a duplicate line on every run that hits a tmutil failure (e.g. Go @-module paths)
  • scripts/install.sh / scripts/uninstall.sh now resolve asimov and the plist relative to the script's own location (via BASH_SOURCE) instead of the caller's working directory, so make install/uninstall no longer depend on being run from the repo root
  • Hardening: asimov sets umask 077 so its temp files (which list the full home-directory layout) and its ~/.cache/asimov state are created private to the user
  • --dry-run now takes the exact same code path as a real run: it runs the read-only tmutil isexcluded ground-truth guard instead of stopping at the Spotlight cache, then prints Would exclude (and never persists state or calls addexclusion). Previously the preview over-reported, listing directories that a real run would skip — most visibly a dependency dir already covered by a manually excluded ancestor (e.g. ~/.nvm), since path-based Time Machine exclusions aren't reported by Spotlight (#19)
  • make bump-formula no longer commits a bad Homebrew formula when the release tarball download fails. The old curl … | shasum | awk pipeline swallowed a failed curl (pipeline exit status came from awk), so a transient network error produced the SHA-256 of an empty string (e3b0c4…b855) — which slipped past the all-zeros guard and was committed/pushed to the tap, breaking brew install. Now downloads to a temp file with an explicit failure check, rejects an empty tarball, enables pipefail, and rejects the empty-string hash. Surfaced during the 0.7.0 release

Removed

0.7.0 — 2026-06-18

Added

  • moonrepo: exclude .moon/cache when a workspace.yml sits beside it
  • CONTRIBUTING.md: troubleshooting entry for make prep-release failing on a dirty tree when the uncommitted changes ARE the release content (manual fallback)

Changed

  • README: surface user-defined sentinels prominently — a new Add your own patterns subsection explains the directory sentinel format with examples, and a pointer under the ecosystems table sends readers there. Custom exclusion patterns were already supported via [sentinels] extra; this just makes them discoverable
  • Makefile pins gh to django23/asimov via export GH_REPO, so a configured upstream remote (e.g. stevegrunwell/asimov) can no longer hijack release-flow gh calls. Surfaced during the 0.6.4 release when make ship-formula 404'd against the upstream release.yml and gh pr create --fill failed to compute a diff range
  • make verify-release now invokes $(brew --prefix asimov)/bin/asimov --version directly instead of bare asimov, so an older binary earlier on PATH no longer hides the freshly-installed version

Fixed

  • README badges restored — the 0.6.4 README rewrite stripped the ![…](badge.svg) image syntax, leaving plain text links instead of the Tests / Latest release / Stars / macOS / License / Shell shields

Removed

0.6.4 — 2026-05-01

Added

  • make prep-release VERSION=X.Y.Z (backed by scripts/prep-release.sh) — bumps ASIMOV_VERSION, promotes [Unreleased] in CHANGELOG with compare links, runs make check, and commits on a release/X.Y.Z branch. Removes the two manual edit steps that were the most error-prone part of the release flow
  • make ship-formula — waits for release.yml to finish, runs bump-formula, and pushes the Homebrew tap. Replaces the sleep 6 + gh run watch recipe that previously lived only in CONTRIBUTING.md
  • make verify-releasebrew upgrade asimov (or install) + asimov --version

Changed

  • CONTRIBUTING.md release flow trimmed from a 4-step prose narrative to ~10 commands; the orchestration now lives in Makefile targets so an AI agent (or human) can execute the release end-to-end without re-deriving sed scripts or watch loops
  • README rewritten with an outcome-first hook, worktree/AI-coding angle, and a "What you'll see" sample run; install moved above the explainer to lower friction, and the --stats flag is now documented in the Usage table

Fixed

Removed

0.6.3 — 2026-04-21

Changed

  • README: added "Latest release" and "Stars" badges alongside the existing Tests / macOS / License / Shell badges so progress and social proof are visible at a glance

0.6.2 — 2026-04-20

Fixed

  • SECURITY.md: replaced invalid django23@github.com contact with the project maintainer's GitHub noreply address so vulnerability reports no longer bounce
  • README: Uninstall section used legacy launchctl unload; replaced with modern launchctl bootout to match the Schedule section

0.6.1 — 2026-04-20

Changed

  • README: moved Usage section above Schedule so the core command reference comes before scheduling details

0.6.0 — 2026-04-20

Added

  • Persistent path cache (~/.cache/asimov/paths) that stores discovered dependency paths across runs. Subsequent runs skip the full find traversal entirely, reducing runtime from ~75s to ~1-2s on typical home directories
  • Incremental Spotlight discovery via mdfind to find newly created projects without traversing the filesystem
  • --full-scan flag to force a full filesystem scan, ignoring any existing cache
  • --no-cache flag to run a full scan without reading or writing cache
  • --stats flag to show per-directory sizes and a total-space summary (opt-in, replaces previous always-on behaviour)
  • Optional positional [directory] argument to scope the scan to a specific directory instead of the full home directory
  • make bench target to compare dry-run timing against tests/fixture/ across versions
  • make bench-home target to compare dry-run timing against the real home directory across versions
  • tests/fixture/ directory with representative project structures for correctness checks and benchmarking
  • Persistent excluded-path state (~/.cache/asimov/excluded) that tracks successful tmutil addexclusion calls across runs. Handles interrupted runs and Spotlight indexing delays — paths excluded in a previous run are never re-excluded, even if Spotlight hasn't indexed them yet
  • Persistent failed-path state (~/.cache/asimov/failed) that remembers paths where tmutil addexclusion fails (e.g. Go module paths with @ characters). Failed paths are automatically skipped on subsequent runs instead of retrying; use --full-scan to retry
  • Persistent mdfind-seen cache (~/.cache/asimov/mdfind_seen) that remembers all candidates checked by Spotlight discovery. Directories without sentinels (false positives) are not re-checked on subsequent runs, eliminating ~1,200 redundant sentinel lookups per cached run

Changed

  • Default run with an existing cache now uses the cache instead of a full find traversal; use --full-scan to force re-traversal
  • Directory sizes and "totalling" summary are no longer computed by default; use --stats to enable them. Removes the du -sk call per matched directory, significantly reducing wall-clock time on large home directories.
  • Already-excluded paths are now filtered via a fast cache lookup (grep) instead of adding per-path -not -prune clauses to find. Removes the O(directories × excluded_paths) overhead that dominated scan time on large home directories. Speed improvements of 37%
  • tmutil addexclusion uses per-path calls instead of batch to avoid wasting minutes when a single path fails (e.g. Go module paths with @ characters). Batch mode provided no speed benefit — tmutil processes paths sequentially at ~11s each regardless
  • Paths that are descendants of fixed dirs (e.g. ~/.cache) are skipped during sentinel-based exclusion since the fixed dir will be excluded unconditionally
  • Nested dependency paths are automatically deduplicated — if a parent directory is excluded, its descendants are skipped since Time Machine exclusions are recursive
  • Full scan now streams find output through tee to write the cache incrementally; an interrupted scan leaves a partial cache that accelerates the next run
  • Spotlight incremental discovery now bulk-filters cached paths with a single grep -Fxvf instead of one grep subprocess per candidate, and uses bash parameter expansion instead of dirname/basename subprocesses — eliminates ~27,000 subprocess spawns on typical home directories
  • Spotlight discovery now prefix-filters candidates nested under cached paths before sentinel checking — eliminates thousands of redundant sentinel lookups (e.g. node_modules/dep/node_modules under an already-cached node_modules)
  • Spotlight discovery uses temp files instead of bash string variables for large datasets — avoids slow printf on 15K-line strings
  • Spotlight discovery now runs descendant filter before exact-match filter and uses sort+comm instead of grep -Fxvf for set difference — BSD grep -Fxvf is O(n×m) and took 35s on 15K candidates; sort+comm is O(n log n) and takes <1s
  • Excluded-path filtering now uses a single bulk grep -Fxvf instead of per-path subprocess spawns
  • Default branch renamed to main (was develop); branch protection requires signed commits + passing CI
  • CI workflow now runs on every PR (including from forks), not just specific base branches
  • make release is now strict: requires clean tree, must be on main, signed tags only
  • Added make bump-formula to update the Homebrew tap (django23/homebrew-tap) after a release

Fixed

Removed

0.5.0 — 2026-03-02

Added

  • --quiet flag to suppress all non-error output
  • --verbose flag to show already-excluded directories (hidden by default)
  • Config file support (~/.config/asimov/config) for enabling fixed dirs, adding extra dirs/sentinels, and disabling built-in sentinels
  • Curl-based installer (scripts/install-remote.sh) for non-Homebrew users
  • Unit tests for format_size_kb() boundary values
  • Flag combination tests (--dry-run --verbose, --dry-run --quiet)
  • GitHub issue/PR templates
  • SECURITY.md with private vulnerability reporting instructions
  • UPGRADING.md with migration guide from v0.4.x and original asimov

Changed

  • BREAKING: Fixed dirs (global caches) are now opt-in via config file (previously always on)
  • BREAKING: LaunchAgent label renamed from com.stevegrunwell.asimov to com.django23.asimov
  • Colors are now disabled when stdout is not a terminal (e.g. launchd, pipes)
  • tmutil isexcluded check removed from default path (now --verbose-only); mdfind optimization already prunes excluded paths
  • record_excluded_path() calls du once instead of twice per path
  • format_size_kb() uses bash arithmetic instead of spawning awk
  • Simplified print_exclusion_summary() to a single printf with early return
  • Removed stale @version header comment
  • Removed unnecessary defensive guards on always-initialized variables
  • Mock tmutil now idempotent (matches real tmutil behavior)
  • README rewritten with concise install-first structure
  • Updated all repo URLs from stevegrunwell/asimov to django23/asimov

Removed

  • tmutil isexcluded per-path check in default (non-verbose) mode

0.4.0 — 2025-02-20

Added

  • Validation that ASIMOV_ROOT exists and is a directory before running (clear error and exit 1 if not)
  • Test for project paths containing spaces; test for dry-run summary format (count and size)
  • --help and --version options; unknown options exit with an error and usage message
  • --dry-run option to print what would be excluded without calling tmutil
  • Support glob patterns in sentinel definitions, enabling wildcards like *.xcodeproj (stevegrunwell/asimov#64, props @mdab121)
  • Exclude Xcode DerivedData when *.xcodeproj is present (stevegrunwell/asimov#64, props @mdab121)
  • Exclude well-known global cache directories (~/.cache, ~/.gradle/caches, ~/.m2/repository, ~/.npm/_cacache, ~/.nuget/packages, ~/.kube/cache, etc.) without requiring sentinel files (inspired by stevegrunwell/asimov#69, props @pkuczynski)
  • Display a summary of total count and size of newly excluded directories at the end of each run (inspired by stevegrunwell/asimov#84, props @Vadorequest)
  • Exclude Next.js build cache (.next)
  • Exclude Nuxt build cache (.nuxt)
  • Exclude Angular CLI cache (.angular)
  • Exclude SvelteKit build output (.svelte-kit)
  • Exclude Turborepo cache (.turbo)
  • Exclude Yarn Berry cache (.yarn)
  • Exclude Leiningen/Clojure CLI target and .cpcache directories
  • Exclude Shadow-CLJS cache (.shadow-cljs)
  • Exclude Python virtualenv venv with pyproject.toml sentinel
  • Exclude PEP 582/PDM local packages (__pypackages__)
  • Exclude Elixir/Mix standard _build directory
  • Exclude Terraform providers/modules (.terraform with .terraform.lock.hcl)
  • Exclude direnv output (.direnv)
  • Exclude OCaml/Dune build output (_build)
  • Exclude Zig build cache (.zig-cache) and output (zig-out)
  • Exclude Elm packages (elm-stuff)
  • Exclude Godot 4 editor cache (.godot)
  • Exclude R renv environment (renv)
  • Exclude .NET build output (bin, obj) when *.csproj or *.fsproj project files are present (inspired by stevegrunwell/asimov#87, props @guigomesa)
  • Added make install and make uninstall targets for streamlined setup and removal ([#35], props @sylver)
  • Added scripts/uninstall.sh to cleanly remove Asimov and its launchd schedule ([#35], props @sylver)
  • Added common interval reference comments to com.stevegrunwell.asimov.plist ([#35], props @sylver)
  • Dropped macOS 13 from the CI test matrix (runner was retired December 2025)

Changed

  • Use ASIMOV_ROOT in ASIMOV_SKIP_PATHS and ASIMOV_FIXED_DIRS so skip/fixed paths are correct when running as root (e.g. launchd)
  • Refactor: extract record_excluded_path() for DRY size logging and output; resolve_asimov_root(), build_find_skip_params(), build_find_vendor_params(), print_exclusion_summary(), format_size_kb(); rename exclude_file to exclude_paths_from_stdin; add named constants for size and colors
  • Skip non-directory paths in exclude_paths_from_stdin (avoids failures if a path disappears between find and processing)
  • Use printf instead of echo -e in install/uninstall scripts for portability
  • Skip directories already excluded from Time Machine backups for faster subsequent runs (inspired by stevegrunwell/asimov#97, props @VladRassokhin)
  • Migrated test suite from PHP/PHPUnit to Bats (Bash Automated Testing System), removing the PHP dependency for contributors
  • Replaced Travis CI pipeline with GitHub Actions (macOS 14 + 15 matrix)
  • Replaced PHP tmutil mock with a pure bash implementation
  • Moved install script to scripts/install.sh with shared variables, now copies binary instead of symlinking ([#35], props @sylver)

Fixed

  • Handle tmutil errors gracefully instead of crashing; paths that fail exclusion are skipped with a warning (stevegrunwell/asimov#101, stevegrunwell/asimov#86)
  • Detect the logged-in user's home directory when running as root, fixing brew services and sudo invocations that would search /var/root instead (stevegrunwell/asimov#72)
  • Fixed duplicate Gradle sentinel entries in the sentinels list
  • Fixed typo in comment ("decendents" → "descendants")

Removed

  • Removed PHP test infrastructure (composer.json, phpunit.xml.dist, and PHP test files)
  • Removed Travis CI configuration (.travis.yml)

[Version 0.3.0] — 2020-06-16

Added

  • Added Homebrew support 🙌 ([#34], props @Dids)
  • Exclude Bower dependencies ([#22], props @moezzie)
  • Exclude Maven builds ([#30], props @bertschneider)
  • Exclude Stack dependencies ([#32], props @alex-kononovich)
  • Exclude Carthage dependencies ([#37], props @qvacua)
  • Exclude CocoaPods dependencies and Swift builds ([#43], props @slashmo)
  • Exclude Bundler, Cargo, and Dart dependencies ([#56])
  • Define a Travis CI pipeline for Asimov ([#20])
  • Add an automated test suite using PHPUnit ([#31])

Fixed

  • Removed an extraneous read -r path, which was causing the first match to be skipped ([#15], props @rowanbeentje)
  • Use the full system path when running chmod in install.sh ([#33], props @ko-dever)

Changed

  • The size of the excluded directories are now included in the Asimov output ([#16], props @rowanbeentje)
  • Switch to using find's -prune switch to exclude match subdirectories for speed, and exclude ~/Library folder from searches ([#17], props @rowanbeentje)
  • Rework the find command and path variables so that find is only run once however many FILEPATHS are set ([#18], @props @rowanbeentje, yet again 😉) Fix incorrect directory pruning, simplify path handling ([#36], props @rwe)
  • Recommend cloning via HTTPS rather than SSH for manual installations ([#52], props @Artoria2e5)
  • Don't look for matches in ~/.Trash ([#55])

[Version 0.2.0] — 2017-11-25

Added

  • Bundle the script with com.stevegrunwell.asimov.plist, enabling Asimov to be scheduled to run daily. Users can set this up in a single step by running the new install.sh script. Added a formal change log to the repository. ([#5])

Fixed

  • Fixed pathing issue when resolving the script directory for install.sh. Props @morganestes. ([#7])

Changed

  • Change the scope of Asimov to find matching directories within the current user's home directory, not just ~/Sites. Props to @vitch for catching this! ([#10]).

[Version 0.1.0] — 2017-10-17

Initial public release.