Skip to content

Contributing

Conventions and guidelines for modifying this dotfiles repository.

Note

Even for personal use, following these conventions keeps the repo maintainable as it grows.

For Maintainers

If you're setting up repository settings or managing GitHub configuration, see GitHub Setup Guide for branch protection rules, status checks, and other repository settings.


File Naming Conventions

The dot_ Prefix

Files in the repo root use the dot_ prefix to represent dotfiles (chezmoi convention):

In Repo Deploys To
dot_zshrc ~/.zshrc
dot_aliases ~/.aliases
dot_config/nvim/ ~/.config/nvim/

Why? - Dotfiles are hidden by default, making them hard to browse - The prefix makes the repo structure explicit - Chezmoi uses this convention for templating


Adding a New Tool

Follow this checklist when adding a new CLI tool:

1. Add to mise.toml

[tools]
# Existing tools...
"github:owner/toolname" = "latest"

2. Create Aliases (if applicable)

In dot_aliases, add to the appropriate section:

# ═══════════════════════════════════════════════════════════════
# [Category Name]
# ═══════════════════════════════════════════════════════════════

alias short='toolname'

3. Update Documentation

File Update
docs/TOOLS.md Add entry with rationale, commands, config
README.md Add to Tools table if notable
docs/meta/CHANGELOG.md Add to "Added" section

4. Update Doctor (if critical)

If the tool is essential, add a check to mise-tasks/doctor:

check_cmd toolname

5. Test

mise install           # Install the new tool
mise run doctor        # Verify setup

Adding Aliases

Naming Conventions

Type Convention Examples
Single letter Common operations g (git), v (nvim)
Short Memorable abbreviations lg (lazygit), ll (ls -l)
Prefix groups Related commands dot-* for dotfile management

Section Organization

Group aliases by function with section headers:

# ═══════════════════════════════════════════════════════════════
# Section Name
# ═══════════════════════════════════════════════════════════════

Current sections in dot_aliases: 1. Navigation 2. Directory Listing 3. Git 4. Utils 5. Modern Coreutils 6. Safety 7. System & Config 8. Dotfile Management 9. Container 10. Editor 11. Python 12. Zsh Suffix Aliases 13. WSL Integration 14. Service Packs

Documentation

  • Simple aliases (alias g='git') don't need comments
  • Complex functions should have a brief comment explaining the "why"
  • Reference docs/TOOLS.md for detailed tool documentation
  • Check for Namespace Collisions (see below)

Alias Safety & Namespace Collisions

Because this setup uses frameworks like oh-my-zsh plugins, simple names like gcl or ps might already be taken by plugins. This can cause "parse error near ()" crashes when you try to redefine them as functions.

Best Practices: 1. Check before you alias: Run type <name> or which <name> in a full shell to see if it exists. 2. Defensive Unalias: If you must overwrite a common name with a function (e.g., specific overrides like ps or gcl), include a defensive unalias guard:

unalias gcl 2>/dev/null
gcl() { ... }
3. Prefer Unique Names: Use prefixes (e.g., dot-) for custom tools to avoid conflicts entirely.


Modifying Configurations

When to Edit Synced Files

Edit the synced file when: - The change should apply to all machines - It's a fix or improvement everyone benefits from - You're adding a new tool/feature

When to Use Local Overrides

Use ~/.zshrc.local when: - Machine-specific paths or tools - Work vs. personal configuration - Experimental changes you're testing

See CUSTOMIZATION.md for override patterns.


Dependency Updates

This project uses Renovate Bot to automate tool and dependency updates.

Workflow

  1. Weekly Schedule: Renovate checks for updates every weekend.
  2. Pull Requests:
  3. Updates are grouped to minimize PR noise (e.g., "mise tools").
  4. Patch updates for stable languages (Node, Python, Rust) are automerged if CI passes.
  5. Major/Minor updates require manual review and merge.
  6. Manual Updates:
  7. You can manually update tools by running mise upgrade locally and committing the changes to mise.toml.

Configuration

Configuration is located in .github/renovate.json. It handles: - mise.toml tool versions - Brewfile (macOS dependencies) - .pre-commit-config.yaml (hook versions) - GitHub Actions workflow versions


Adding a Mise Task

Mise tasks are shell scripts that automate common operations. They live in mise-tasks/ and are invoked via mise run <task>.

Task File Structure

Create a new file in mise-tasks/ (no extension needed):

#!/bin/bash
#MISE description="Short description shown in 'mise tasks'"
#MISE alias="shortname"
#USAGE flag "-f --force" help="Force the operation"
#USAGE flag "-v --verbose" help="Enable verbose output"
#USAGE arg "<input>" help="Input file path"

set -e

# Source shared colors (with fallback for standalone execution)
if [ -f "scripts/lib/colors.sh" ]; then
    # shellcheck disable=SC1091
    source "scripts/lib/colors.sh"
else
    GREEN='\033[0;32m'
    YELLOW='\033[0;33m'
    RED='\033[0;31m'
    NC='\033[0m'
fi

# Parse flags (usage-lib handles this, but manual parsing works too)
for arg in "$@"; do
    case "$arg" in
        --force|-f) FORCE=true ;;
        --verbose|-v) VERBOSE=true ;;
    esac
done

# Task implementation here
echo -e "${GREEN}Task complete!${NC}"

Pragma Reference

Pragma Purpose Example
#MISE description="..." Shown in mise tasks list "Check environment health"
#MISE alias="..." Short name for the task "doc" for mise run doc
#MISE depends=["..."] Run other tasks first depends=["lint", "test"]
#MISE sources=["..."] Files that trigger re-run sources=["src/**/*.ts"]
#MISE outputs=["..."] Files the task produces outputs=["dist/**"]
#USAGE flag "..." Define CLI flags flag "-v --verbose"
#USAGE arg "..." Define positional args arg "<file>" help="Input"

Best Practices

  1. Always use set -e - Exit on first error
  2. Source colors with fallback - Tasks may run standalone
  3. Check for required tools - Use command -v tool >/dev/null
  4. Support non-interactive mode - Check $CI or [ ! -t 0 ]
  5. Add cleanup traps - trap 'cleanup' EXIT for temp files
  6. Make tasks idempotent - Safe to run multiple times
  7. Protect $HOME hygiene - Keep non-dot repo artifacts in .chezmoiignore

Testing Your Task

# List all tasks
mise tasks

# Run with verbose output
mise run mytask --verbose

# Check for shellcheck issues
shellcheck mise-tasks/mytask

Example Tasks

Task Purpose
install Apply dotfiles via chezmoi
doctor Validate environment health
test Run BATS test suite
lint Run all linters
backup Backup current configs

Directory Structure

dotfiles/
├── dot_zshrc                # → ~/.zshrc
├── dot_aliases              # → ~/.aliases
├── dot_gitconfig            # → ~/.gitconfig
├── dot_tmux.conf            # → ~/.tmux.conf
├── dot_config/              # → ~/.config/
│   ├── nvim/                # Neovim config
│   ├── atuin/               # Atuin config
│   └── wsl/                 # WSL templates
├── dot_secrets.example      # Template (not deployed)
├── .chezmoi.toml.tmpl       # Chezmoi config template
├── .chezmoidata.yaml        # Package lists per distro
├── .chezmoiignore           # Platform-specific ignores
├── .chezmoiversion          # Minimum chezmoi version
├── run_once_*.sh.tmpl       # One-time setup scripts
├── encrypted_dot_secrets.age  # Encrypted secrets
├── mise-tasks/              # Mise task scripts
│   ├── install              # Apply dotfiles
│   ├── doctor               # Validate environment
│   ├── update               # Update dotfiles + tools
│   ├── setup-secrets        # Configure age encryption
│   └── ...
├── scripts/                 # Utility scripts
│   └── bootstrap            # Initial setup
├── provision/               # OS provisioning
│   └── windows/             # Windows setup scripts
├── docs/                    # Documentation
├── mise.toml                # Tool definitions
├── Brewfile                 # macOS packages
└── README.md                # Quick start

Development Workflow

Pre-commit Hooks

We use pre-commit to ensure code quality before every commit.

Setup:

# Handled automatically by 'mise run install'
# Or manually:
mise run pre-commit install

Running manually:

mise run pre-commit

What is checked: - ShellCheck (scripts) - Executable permissions - Trailing whitespace - End-of-file fixers - Gitleaks (secrets detection)

Changelog & Releases

We follow Conventional Commits to automate changelogs.

Commit Message Format: type(scope): description

  • feat: New features
  • fix: Bug fixes
  • docs: Documentation
  • chore: Maintenance

Generating Changelog: We use git-cliff to generate changelogs.

# Preview unreleased changes
mise run changelog

# Create a release
mise run release

Attribution Policy: - Commit/changelog authorship must list only human contributors. - Do not add AI co-author or AI attribution lines (for example Co-authored-by entries for tool names).


Testing Changes

Before Committing

  1. Pre-commit: Ensure hooks pass

    pre-commit run --all-files
    # or
    mise run pre-commit
    

  2. Install check: Reinstall symlinks

    mise run install
    

  3. Health check: Run the doctor

    mise run doctor
    

  4. Run tests: Execute the test suite

    mise run test
    

  5. Functional test: Manually verify changes work

CI Failures

If CI fails on your PR, see CI Troubleshooting Guide for debugging steps.

Local Testing Workflows

Testing Alias/Function Changes

# Quick test in new shell
zsh -ic "source dot_aliases && myfunction arg1"

# Run specific BATS tests
bats tests/aliases.bats --filter "myfunction"

# Test interactively
zsh -i  # Opens new shell with changes

Testing Mise Task Changes

# Validate task syntax
shellcheck mise-tasks/mytask

# Run with debug output
bash -x mise-tasks/mytask

# Test idempotency (run twice)
mise run mytask && mise run mytask

Testing Zshrc/Config Changes

# Check for syntax errors
zsh -n dot_zshrc.tmpl

# Validate chezmoi template
chezmoi execute-template < dot_zshrc.tmpl > /dev/null

# Test shell startup time
time zsh -i -c exit

# Profile startup (detailed)
mise run profile

Testing in Isolation

# Use devcontainer for clean environment
# VS Code: "Reopen in Container"

# Or test with Docker directly
docker run -it --rm -v "$PWD:/dotfiles" ubuntu:latest bash

Cross-Platform Testing

If you have multiple machines: 1. Commit and push changes 2. Pull on another machine 3. Run mise run update 4. Verify with mise run doctor


Commit Conventions

Message Format

<type>: <short description>

[optional body explaining why]

Types

Type Use For
feat New tool, alias, or feature
fix Bug fix
docs Documentation only
refactor Code change that doesn't add features or fix bugs
chore Maintenance, cleanup, dependency updates

Examples

feat: add yazi file manager

docs: add troubleshooting guide for WSL clipboard

fix: correct fzf preview command for bat

chore: update mise tools to latest versions

Pull Request Guidelines

If working with others or contributing to forks:

PR Checklist

  • Follows file naming conventions
  • Documentation updated
  • CHANGELOG.md updated
  • mise run doctor passes
  • Checked for alias collisions
  • Tested on target platform(s)

PR Description Template

## What

Brief description of changes.

## Why

Rationale for the change.

## Testing

How this was tested.

## Checklist

- [ ] Docs updated
- [ ] Doctor passes