Thanks for your interest in contributing! Asimov is a small, focused project and contributions of all sizes are welcome.
Prerequisites: macOS with Homebrew installed.
git clone https://github.com/django23/asimov.git
cd asimov
brew install bats-core shellcheckVerify everything works:
make check| Command | What it does |
|---|---|
make help |
List available make targets with descriptions |
make test |
Run the Bats test suite |
make lint |
Run ShellCheck on all shell scripts |
make check |
Run both tests and linting |
make version |
Print asimov version |
make exclusions |
List all paths excluded from Time Machine (requires sudo) |
To run a single test by name: bats tests/behavior.bats --filter "substring of test name" or bats tests/sentinels.bats --filter "npm".
The main script supports --help, --version, --dry-run, --verbose, and --quiet; unknown options exit with an error.
This is the most common type of contribution. To add a new ecosystem or dependency directory:
- Add the sentinel pair to the
ASIMOV_VENDOR_DIR_SENTINELSarray inasimov— one'directory sentinel'entry per pattern. - Add a test in
tests/sentinels.batsusingcreate_projectto build the fixture. Keep this file in sync withASIMOV_VENDOR_DIR_SENTINELS(one test per sentinel pair). - Run
make checkto verify your changes pass tests and linting. - Add a changelog entry under the
[Unreleased]section inCHANGELOG.md.
Example sentinel entry:
'.zig-cache build.zig' # Zig build cacheThis means: exclude .zig-cache/ only when build.zig exists in the same directory.
This project uses Conventional Commits. Format your commits as:
type(scope): short description
Types: feat, fix, docs, test, refactor, chore, build, ci
Examples:
feat(sentinels): add Zig build cache exclusionfix: prevent duplicate exclusions on re-runtest: add coverage for Go modulesdocs: update installation instructions
- Branch from
main(the default branch). - Keep PRs focused — one feature or fix per PR.
- Ensure
make checkpasses before submitting. - Update
CHANGELOG.mdfor user-facing changes.
- Bash/shell: 2-space indentation, LF line endings, UTF-8.
- Enforced via EditorConfig — most editors pick this up automatically.
asimov # Main bash script
tests/
sentinels.bats # Tests for each dependency pattern
behavior.bats # Tests for edge cases and general behavior
format.bats # Unit tests for format_size_kb()
plist.bats # Tests for the LaunchAgent plist
test_helper.bash # Shared setup/teardown and assertions
bin/tmutil # Mock tmutil for testing
bin/mdfind # Mock mdfind for testing
scripts/
install.sh # Local installation script
install-remote.sh # Curl-based remote installer
uninstall.sh # Uninstallation script
Makefile # Build targets (test, lint, check, install, uninstall)
main is protected: signed commits + PR required + both CI checks (test (macos-14), test (macos-15)) must pass. The flow below respects all of that.
-
Install signing keys (see SSH-signed commits below) in both the asimov clone and the homebrew-tap clone.
make bump-formulacommits in the tap and will fail if signing isn't configured there. -
Clone the tap alongside this repo:
git clone git@github.com:django23/homebrew-tap.git ../homebrew-tap
make prep-release VERSION=X.Y.Z # branch, bump version, promote CHANGELOG, run check, commit
git push -u origin release/X.Y.Z
gh pr create --base main --fill --title "docs: release X.Y.Z"
gh pr checks <PR#> --watch --fail-fast
# After the PR is merged:
gh pr merge <PR#> --squash --delete-branch
git checkout main && git pull --ff-only
make release # signed tag + push
make ship-formula # waits for release.yml, bumps tap formula, pushes
make verify-release # brew upgrade + asimov --versionIf gh pr create fails with Head sha can't be blank (GraphQL indexing lag), retry once or fall back to the REST API:
gh api repos/django23/asimov/pulls -X POST \
-f title="docs: release X.Y.Z" \
-f head="release/X.Y.Z" -f base="main" \
-f body="See CHANGELOG.md"Skip prep-release (no CHANGELOG promotion needed). Run make release-beta from any branch — it auto-increments the -beta.N suffix and marks the GitHub release as pre-release. Skip bump-formula and ship-formula for betas.
Tag already exists (duplicate from an earlier attempt). Delete remote tag + release, prune locally, re-run:
gh release delete vX.Y.Z --yes --cleanup-tag
git fetch --prune --prune-tags origin
git tag -d vX.Y.Z 2>/dev/null || true
make release # re-tags from current mainmake bump-formula fails with gpg failed to sign the data: No secret key. The tap's git config doesn't have SSH signing set up. Apply the same git config block from the SSH-signed commits section inside ../homebrew-tap.
CI fails on the release PR. Fix locally, push to the PR branch, re-run gh pr checks <PR#> --watch. Don't merge until green.
You merged the PR but make release aborts with "working tree not clean". Run git status — you likely have a stray local file. git stash it and retry.
make prep-release fails with "working tree not clean" but the dirty files ARE the release. prep-release assumes the version bump comes first and the substantive changes follow. If your work is already uncommitted, do it manually: branch (git checkout -b release/X.Y.Z), commit your changes, then bump ASIMOV_VERSION in asimov and run the awk block from scripts/prep-release.sh against CHANGELOG.md to promote [Unreleased] and add the compare links. Run make check, commit, push, open the PR.
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_signing -N ""
gh auth refresh -s admin:ssh_signing_key
gh ssh-key add ~/.ssh/id_ed25519_signing.pub --type signing --title "$(hostname)"
git config user.email "$(gh api user --jq '.id')+$(gh api user --jq '.login')@users.noreply.github.com"
git config gpg.format ssh
git config user.signingkey ~/.ssh/id_ed25519_signing.pub
git config commit.gpgsign true
git config tag.gpgsign true
mkdir -p ~/.config/git
echo "$(git config user.email) $(awk '{print $1, $2}' ~/.ssh/id_ed25519_signing.pub)" >> ~/.config/git/allowed_signers
git config gpg.ssh.allowedSignersFile ~/.config/git/allowed_signersOpen an issue — happy to help.