Skip to content

Dotfiles vs Maint

Two top-level modules cover everything-about-these-dotfiles:

  • dotfiles:: — what you do to use these dotfiles
  • maint:: — what you do to edit these dotfiles

The split is by subject, not audience. Both modules belong to the same person on a personal dotfiles repo, but they operate on different things — installed state vs repo source.

dotfiles:: — installed state

Operations on what's deployed at ~/. Anyone with these dotfiles installed runs them.

Recipe When
just dotfiles update Pull new dotfiles + upgrade chezmoi binary. Routine — weekly or after upstream changes.
just dotfiles apply Re-render templates and write changes. Reach for it after editing a managed file via
chezmoi edit
just dotfiles dry-run Preview what apply would change. Safe inspection.
just dotfiles check-updates Compare pinned external plugin tags against latest GitHub releases. Read-only
— doesn't change anything.
just dotfiles verify Health check: binaries on PATH, Brewfile satisfied, shell config present, git hook
layer wired. Run after anything that might leave the install in a weird state.

The top-level just update orchestrator chains these:

just update    # → dotfiles::update → sys::pm::update → dotfiles::verify

That's the routine refresh — every recipe in the chain is a positive lifecycle operation. Nothing destructive.

maint:: — repo source

Operations on the dotfiles repo as a thing being edited. You only reach for these when you're changing the framework — adding a recipe, fixing a script, building docs, regenerating the contract spec.

maint::
  test::            functional test suites
    bats::          shell-script tests (deployed bin/, hook scripts)
    contract::      pytest-just contract tests (recipe API)
    smoke::         docker-based bootstrap smoke tests
    spec::          generates the contract spec.yaml
  qa::              quality assurance
    hooks::         pre-commit framework infra (install / repair envs)
    shellcheck::    rendered template + script linting
  docs::            zensical docs site (sync / serve / build / clean)
  check             health + lint + functional tests (no smoke)
  purge             destructive teardown — wipes the chezmoi install

The maint::check orchestrator runs everything you'd want before a commit: dotfiles-verify.sh + shellcheck + bats + contract tests. Smoke tests are excluded — they spin up Docker containers and take minutes; run them deliberately when you've changed init-flow code.

Where does a new recipe belong?

Apply this test:

Does this recipe make sense for someone who has these dotfiles installed but no intent to edit them?

  • Yesdotfiles::. Routine, positive, operates on installed state.
  • Nomaint::. Repo-editing work, framework-level operations, testing the source.

Examples worked through:

  • chezmoi diff wrapper that highlights only managed-file changes — yes, anyone with the install might want to inspect drift. Belongs in dotfiles::.
  • Test runner for a new shellcheck rule set — no, only relevant when editing the repo's scripts. Belongs in maint::qa::shellcheck (extension).
  • purge — destroys the install. Looks like a dotfiles:: operation by virtue of touching installed state, but the audience is "someone testing a fresh-install flow" — a maintainer scenario. Lives in maint::purge.

Why this split, not one combined module?

For a personal dotfiles repo (one person), both modules serve the same human. The split has aesthetic cost (one more top-level entry in just --list) but pays back in:

  1. Daily just --list clarity — user-facing recipes aren't buried among test runners and lint tools.
  2. Conceptual map — a contributor (or future-you) can see at a glance what each module is for without reading every recipe.
  3. Avoids awkward readingsjust maint update for the routine dotfiles refresh would parse as "maintenance: update," which misnames a positive lifecycle operation.

The cost is small; the wins are real but small too. The split earns its keep without being load-bearing.