Skip to content

Conventions

Privacy & PII Protection

This repository has strict rules against committing personal information.

Rules

  • No hardcoded usernames — Never include your local username in files
  • No home paths — Never include /home/yourname or /Users/yourname
  • No personal emails — Use placeholders or templating

Git Hooks

Hooks are orchestrated by the pre-commit framework via .pre-commit-config.yaml at repo root. Secret + PII scanning is delegated to betterleaks, whose ~170-rule default pack covers common credentials (AWS / GCP / Azure / GitHub / Stripe / Slack / SSH keys / JWTs / etc.). Project-specific policy hooks (subject line length, dirty-tree refusal, cross-branch push refusal) live in hooks/lib/.

For framework mechanics — cache layout, per-stage dispatch, what runs at pre-commit vs commit-msg vs pre-push, and debugging — see Git Hooks. This section covers only the privacy / PII-specific concerns.

Personal PII config: ~/.config/betterleaks/personal.toml (chezmoi-managed, create_ prefix — never overwritten). The file is intentionally outside the repo so personal PII patterns are never committed. The chezmoi setup script creates a .betterleaks.toml symlink at the repo root pointing at it; the symlink is gitignored. betterleaks auto-discovers .betterleaks.toml at the repo root — no env var needed.

A local betterleaks-config-gate hook runs first and fail-closes if the symlink is missing or broken, or if the config still contains a REVIEW_ME marker (shipped in fresh installs to force the user to read the file before their first commit).

If a commit is blocked by betterleaks finding a literal home path or username, replace it with a chezmoi template variable:

[{ .chezmoi.homeDir }]/.config

[{ .chezmoi.username }]

Note: Never use --no-verify to bypass these hooks.

Code Style

  • Idempotency: Scripts must be safe to run multiple times
  • Error handling: Use set -euo pipefail in bash scripts
  • Platform checks: Use .isMacos / .isLinux / .isBluefin for OS-specific logic
  • Tool guards: Always check command -v <tool> before using optional tools
  • Local overrides: Use 99-local.zsh for machine-specific changes (create_ — never overwritten)

Shell Best Practices

Use shellcheck to catch common bash pitfalls (unquoted variables, regex injection, [ ] vs [[ ]], unused variables, etc). Fix flagged warnings rather than disabling rules. Add # shellcheck disable=SC#### only with a comment explaining why.

Just Recipe Attributes

Recipe attributes must appear in this order (omit any that don't apply):

[default]
[private]
[group('name')]
[no-cd]
[script]
[no-exit-message]

This matches the convention used throughout _common.just.tmpl and all modules.

Just Indentation

Recipe bodies use 4 spaces — the default for just --fmt. This is enforced by ~/.editorconfig.

Template Delimiters

All .tmpl files use custom chezmoi delimiters to avoid clashing with just's {{ }}:

# chezmoi:template:left-delimiter=[{ right-delimiter=}]
  • [{ }] — chezmoi, evaluated at chezmoi apply time
  • {{ }} — just, evaluated at recipe runtime

For Docker/Go format strings inside just recipes, double the opening braces: {{{{.ID}} → renders as {{.ID}}. Do not double the closing braces.

See Templates for worked examples and the full three-layer escaping reference.

For details on script lifecycle and shell modules, see Platforms, Scripts, and Shell Modules.

Package Data Convention

All package declarations live in .chezmoidata/packages.yaml as the single source of truth.

Entry Format

Each entry is an object with pkg (required) and optional fields:

- {pkg: "neovim"}                                          # simple
- {pkg: "ffmpeg", skip_minimal: true, comment: "yazi: thumbnails"}  # with flags
- {pkg: "iputils-ping", bin: "ping"}                       # when binary differs
Field Used by Purpose
pkg Install scripts, Brewfile Package name passed to the package manager
type Brewfile template "cask" for Homebrew casks (default: brew)
comment Brewfile template Rendered as # comment after the brew/cask line
bin dotfiles-verify.sh Binary to check on $PATH (defaults to pkg if omitted)
skip_bluefin Brewfile template true to exclude on Bluefin (provides natively)
skip_minimal Brewfile template true to exclude in container installs

Structure

# Homebrew packages — by platform
brew:
  common:   # every machine
  macos:    # macOS only
  linux:    # Linux only

# OS packages — installed via apt/dnf/flatpak, not Homebrew
packages:
  linux_apt: # Debian/Ubuntu via apt
  linux_dnf: # Fedora/Bluefin via dnf
  flatpak:   # Linux desktop apps via flathub

Template Filtering

Brewfile.tmpl uses missing-key=zero so absent skip_* flags default to false (include the package). The filter logic:

[{- if not (and .skip_bluefin $.isBluefin) }]   # skip only if flag set AND on Bluefin
[{- if not (and .skip_minimal $.isMinimal) }]   # skip only if flag set AND minimal

Packages are grouped by purpose within each platform section, alphabetical within groups.

Dotfiles Repository

The repo identity and remote URLs are defined in .chezmoidata/data.yaml under repo:. A run_after script configures the chezmoi source repo on every apply:

  • user.name / user.email from .repo.name / .repo.email
  • Split remote: HTTPS fetch (no auth for public repo), SSH push (YubiKey)

Commits & Pull Requests

Commit Messages

Conventional Commits format is enforced by commitlint at the commit-msg stage, configured via .commitlintrc.yaml at the repo root. The config extends @commitlint/config-conventional (the spec's reference rule set) and adds header-max-length: 80 plus subject-min-length: 5.

type(scope): description
  • Required types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
  • Scope: Optional but encouraged (e.g., zsh, brew, nvim, bluefin)
  • Keep the subject line under 80 characters
  • Reference issues where applicable

Before Submitting

  1. Run just maint test smoke local-all — all distros must pass
  2. Run just dotfiles verify on the target machine
  3. Check chezmoi diff for unexpected changes
  4. Ensure no PII is included (pre-commit hook will catch most cases)