Contributing¶
How this repository is wired together and the conventions to follow when changing it. For day-to-day usage — install, profiles, the lifecycle commands — start there; this page is the maintainer-facing companion to CONTRIBUTING.md.
Repository layout¶
- Root — the four entry-point scripts (
bootstrap.sh,install.sh,update.sh,verify.sh), package manifests (Brewfile,Brewfile.work), and repo meta (README.md,CONTRIBUTING.md). home/— the tracked dotfiles symlinked into$HOME(zshrc,zprofile,tmux.conf, …);home/examples/holds the*.local.exampletemplates. (gitconfiglives here too but is loaded via a thin~/.gitconfiginclude rather than symlinked, so global writes never touch the repo.)scripts/— the secondary entry scripts (macos.sh,setup-scheduler.sh,uninstall.sh,quick-fix.sh) and supporting/work-setup scripts. Sourced helper libraries live inscripts/lib/and unit tests inscripts/tests/.config/— XDG configs symlinked under~/.config(starship.toml,mise.toml,direnvrc), plusconfig/symlinks.map, the canonical symlink manifest (see below).templates/— work / secret-bearing configs shipped as*.templateplaceholders.docs/— this MkDocs site..github/workflows/— CI.system/— macOS / setup assets:LaunchAgents/(the launchd plist), plus the git-ignoredcerts/andinstallers/.
Adding a tracked dotfile¶
The dotfile→destination mapping is a single source of truth in config/symlinks.map; install.sh, verify.sh, bootstrap.sh --dry-run, and the CI install-smoke job all read it. To track a new dotfile:
- Add the file to the repo (
home/, orconfig/for XDG configs). config/symlinks.map— add onesrc dest [tags]line.srcis relative to the repo root,destis relative to$HOME, and the optional comma-separated profile-tag column (e.g.guifor GUI-only configs) controls which profiles get the link, viascripts/lib/profile_helpers.sh. Every consumer picks it up automatically — no other script or workflow needs editing.README.md— add a row to the Dotfiles table (human-readable reference).
Non-symlink setup (the thin ~/.gitconfig include, the ~/.config/git/local.gitconfig seed, the global pre-commit hook, and VS Code settings/extensions) is intentionally bespoke in install.sh and is deliberately not part of the manifest.
Bootstrap steps, dry-run, and TOTAL_STEPS¶
bootstrap.sh is the one-time installer. It prints numbered progress as [n/TOTAL_STEPS], where TOTAL_STEPS is a constant near the top of bootstrap.sh and the step() / dry_run_step() helpers consume it. Two rules keep this honest when you add or change a step:
- Bump the counter. When you add a real step, increment
TOTAL_STEPSso the[n/m]banner stays accurate (and decrement it if you remove one). - Keep the dry-run preview in lockstep.
bootstrap.sh --dry-runsourcesscripts/lib/dryrun_helpers.shand reports what each step would do without changing anything, ending in a numbered summary. Every real step has a matchingcheck_*preview (e.g.check_homebrew,check_corepack); when you change a real step, update its preview so dry-run never drifts from reality.
See Dry-Run & Pre-flight for the user-facing detail, and the bootstrap-dryrun.yml CI job below, which exercises --dry-run across profiles on every PR.
Shell-script conventions¶
- New bash scripts start with
#!/usr/bin/env bashandset -euo pipefail. - Reuse the shared helpers in
scripts/lib/bootstrap_helpers.shfor output (setup_colors,step,info,success,warn; callsetup_colorsonce after sourcing) instead of re-rolling colors and logging. Pure check functions forverify.shlive inscripts/lib/verify_helpers.sh, and the dry-run previews inscripts/lib/dryrun_helpers.sh. - Keep helpers side-effect-free — set result globals rather than printing or exiting — so they stay unit-testable.
install.sh,home/zshrc, andhome/zprofileare zsh; everything else is bash.
Testing with bats-core¶
Unit tests use bats-core and live in scripts/tests/test_*.bats. Install bats with brew install bats-core (it's already in the Brewfile), then run the whole suite from the repo root:
You can also run a single file, e.g. bats scripts/tests/test_verify_helpers.bats.
Conventions (see scripts/tests/test_verify_helpers.bats for the reference implementation):
- Every file is
#!/usr/bin/env batsand starts withload 'test_helper', which sourcesscripts/tests/test_helper.bashto expose$LIB_DIR/$SCRIPTS_DIR/$REPO_ROOT. - Write each case as a
@test "description" { ... }block; a non-zero command inside the block fails the test. - Use the per-test scratch dir
$BATS_TEST_TMPDIRfor fixtures (bats creates and removes it automatically — nomktemp -dortrapcleanup needed). - Source the helper under test once at the file's top level (or in
setup()), and assert on the result globals it sets. - Add new tests as
@testblocks in the*.batsfile next to the helper they cover; both CI andbats scripts/tests/auto-discover them, so no workflow edits are needed.
Static checks before you commit¶
- Run
shellcheck -S warning <script>on any bash you touched. - Run
zsh -n <file>on zsh files (install.sh,home/zshrc,home/zprofile, …). - Run the bats suite with
bats scripts/tests/. - For changes to install/verify behavior, exercise them against an isolated
HOME=$(mktemp -d). - A repo-local pre-commit hook runs when
pre-commitis installed and a.pre-commit-config.yamlis present;gitleaksscans for accidentally committed secrets in both pre-commit and CI.
Continuous integration¶
Four workflows run under .github/workflows/:
ci.yml— three jobs:shellcheck(bash scripts,-S warning),zsh-syntax(zsh -non the zsh files plus aBrewfileparse check), andinstall-smoke(runszsh install.shagainst a throwaway$HOMEand asserts every expected symlink exists).test-bootstrap.yml— installs bats-core and runs the fullscripts/tests/*.batssuite (auto-discovered viabats scripts/tests/), plusbash -nsyntax checks and abats --countparse-check of every.batsfile, when the relevant scripts change.bootstrap-dryrun.yml— exercisesbootstrap.sh --dry-run(which installs nothing) to prove profile gating still behaves: a cheap PR-time guardrail over thepersonalandminimalprofiles, plus a nightly/manual sweep across all four profiles, asserting exit 0 and the expected gated previews.docs.yml— builds this site withmkdocs build --stricton every PR, and on pushes tomaindeploys it to GitHub Pages.
Pull requests and commits¶
- Templates. New PRs follow
.github/pull_request_template.md(Summary / Type / Validation / Checklist). Issues use the forms under.github/ISSUE_TEMPLATE/(bug_report.md,feature_request.md). - Conventional commits. Use
type(scope): subject— e.g.feat(profiles): add server profile,fix(update): abort failed rebase,docs(contributing): mirror CONTRIBUTING.md. Common types:feat,fix,docs,refactor,test,chore,ci. - Attribution. When a change is co-authored (including with an agent), end the commit message with a trailing line:
- Docs PRs must keep
mkdocs build --strictclean: new pages belong in themkdocs.ymlnav, and links between pages should be relative (e.g.profiles.md) so they resolve both in-repo and on the published site.