This guide explains how to set up automated code formatting and quality checks using a modular git hooks system with intelligent configuration precedence. The approach provides sensible defaults while allowing per-project customization and easy extensibility.
- Overview
- The Configuration Precedence System
- Modular Hooks Architecture
- Quick Start
- Installation Methods
- Configuration Files
- Managing Individual Hooks
- Troubleshooting
- Advanced Usage
Different projects need different code formatting rules, but you want:
- Consistent defaults across your projects
- Per-project customization when needed
- Zero setup for new projects
- Team collaboration without configuration conflicts
A universal git hook with inline defaults that automatically defers to local configuration files when they exist. This gives you:
✅ One script works everywhere - no per-project customization needed
✅ Smart precedence - config files override script defaults automatically
✅ Gradual adoption - start simple, add configs when needed
✅ Team friendly - commit configs for consistent team formatting
Understanding how tools resolve configuration is key to this approach:
- Command line arguments -
prettier --print-width=120 - Project config files -
.prettierrcin repo root - Global config files -
~/.prettierrcin home directory - Built-in tool defaults - what the tool uses if no config found
# Our git hook runs:
prettier --write '**/*.md' --print-width=100 --prose-wrap=preserve
# What actually happens:
# ✅ Project has .prettierrc? → Uses .prettierrc, ignores command line
# ✅ Global ~/.prettierrc exists? → Uses ~/.prettierrc, ignores command line
# ✅ No config files? → Uses command line args (our sensible defaults)This means one hook script provides defaults but respects local preferences.
The git hooks system creates a modular, extensible structure:
.githooks/
├── pre-commit # Orchestrator (runs all enabled hooks)
├── hooks.d/ # Individual hook scripts
│ ├── 10-format-code # ✅ Code formatting (enabled)
│ ├── 20-lint-markdown # ✅ Markdown linting (enabled)
│ ├── 30-run-tests # ⏸️ Tests (disabled by default)
│ └── 40-security-check # ⏸️ Security scanning (disabled by default)
└── lib/
└── common.sh # 📚 Shared functions and utilities
- Git calls
.githooks/pre-commit(the orchestrator) - Orchestrator runs all executable scripts in
hooks.d/in alphabetical order - Each hook focuses on one specific task (formatting, linting, testing, etc.)
- Numbering system (10-, 20-, 30-) controls execution order
- Enable/disable hooks by making them executable/non-executable
✅ Modular - Add/remove specific checks easily
✅ Ordered - Number prefixes ensure proper execution sequence
✅ Selective - Enable only the hooks you need per project
✅ Extensible - Add custom hooks as simple shell scripts
✅ Debuggable - Run individual hooks manually for testing
✅ Team-friendly - Different team members can have different hook preferences
# Enable optional hooks
chmod +x .githooks/hooks.d/30-run-tests
chmod +x .githooks/hooks.d/40-security-check
# Disable specific hooks
chmod -x .githooks/hooks.d/20-lint-markdown
# Add custom hook
cat > .githooks/hooks.d/50-custom-check << 'EOF'
#!/bin/sh
echo "Running my custom check..."
# Your custom logic here
EOF
chmod +x .githooks/hooks.d/50-custom-check
# Test individual hook
.githooks/hooks.d/10-format-code
# See which hooks are enabled
ls -la .githooks/hooks.d/For a new project, get instant formatting with sensible defaults:
# First time setup
curl -fsSL https://raw.githubusercontent.com/rickcogley/dotfiles/main/adhoc/setup-git-hooks.sh | bash
# Update existing setup (force overwrite)
curl -fsSL https://raw.githubusercontent.com/rickcogley/dotfiles/main/adhoc/setup-git-hooks.sh | bash -s -- --update📋 For complete installation options, see: Installation Commands Reference
- ✅ Modular git hooks system with orchestrator and individual hook scripts
- ✅ Code formatting (TypeScript/JavaScript with Deno or Prettier, Markdown with Prettier)
- ✅ Markdown linting with markdownlint-cli2
- ✅ Optional hooks for tests and security checks (disabled by default)
- ✅ Easy extensibility - add custom hooks as executable scripts
- ✅ Sensible defaults for line length, spacing, etc.
# Make a change
echo "# test" >> README.md
# Commit - you'll see the formatting happen
git add README.md
git commit -m "test formatting"For the full interactive experience, download and run the script:
# Download and run interactively
curl -fsSL https://raw.githubusercontent.com/rickcogley/dotfiles/main/adhoc/setup-git-hooks.sh -o setup-git-hooks.sh
chmod +x setup-git-hooks.sh
./setup-git-hooks.sh
# Or from local dotfiles
~/.dotfiles/adhoc/setup-git-hooks.shWhen piping to bash, the script automatically runs in auto mode:
# Basic setup (hooks only, with sensible defaults)
curl -fsSL https://raw.githubusercontent.com/rickcogley/dotfiles/main/adhoc/setup-git-hooks.sh | bash
# Full setup (hooks + config files)
curl -fsSL https://raw.githubusercontent.com/rickcogley/dotfiles/main/adhoc/setup-git-hooks.sh | bash -s --configsFor scripts or when you know exactly what you want:
# Hooks only
curl -fsSL https://raw.githubusercontent.com/rickcogley/dotfiles/main/adhoc/setup-git-hooks.sh | bash -s --auto
# Hooks + config files
curl -fsSL https://raw.githubusercontent.com/rickcogley/dotfiles/main/adhoc/setup-git-hooks.sh | bash -s --auto --configs
# Specify project type
curl -fsSL https://raw.githubusercontent.com/rickcogley/dotfiles/main/adhoc/setup-git-hooks.sh | bash -s --auto --type=deno --configs
# Force overwrite existing files
curl -fsSL https://raw.githubusercontent.com/rickcogley/dotfiles/main/adhoc/setup-git-hooks.sh | bash -s --auto --configs --force# Create hooks directory
mkdir -p .githooks
# Download and install hook
curl -fsSL https://raw.githubusercontent.com/rickcogley/dotfiles/main/adhoc/git/hooks/pre-commit-universal -o .githooks/pre-commit
chmod +x .githooks/pre-commit
# Configure git to use the hooks
git config core.hooksPath .githooksAdd to your ~/.zshrc or ~/.bashrc:
# Quick project setup aliases
alias setup-hooks='curl -fsSL https://raw.githubusercontent.com/rickcogley/dotfiles/main/adhoc/setup-git-hooks.sh | bash'
alias setup-full='curl -fsSL https://raw.githubusercontent.com/rickcogley/dotfiles/main/adhoc/setup-git-hooks.sh | bash -s --auto --configs'Use inline defaults (no config files) when:
- Quick personal projects
- Prototypes and experiments
- You're happy with the defaults
Add config files when:
- Working with a team (commit configs for consistency)
- Project has specific requirements (different line length, etc.)
- Integrating with existing project standards
Controls formatting for JavaScript, TypeScript, Markdown, JSON, YAML:
{
"printWidth": 100,
"tabWidth": 2,
"semi": true,
"singleQuote": false,
"proseWrap": "preserve",
"overrides": [
{
"files": "*.md",
"options": {
"printWidth": 100,
"proseWrap": "preserve"
}
}
]
}Controls markdown quality and consistency checking:
# Get the standard config files
curl -fsSL https://raw.githubusercontent.com/rickcogley/dotfiles/main/adhoc/configs/.prettierrc -o .prettierrc
curl -fsSL https://raw.githubusercontent.com/rickcogley/dotfiles/main/adhoc/configs/.markdownlint-cli2.jsonc -o .markdownlint-cli2.jsonc
# Or use the installer
./setup-project.sh --configsCheck if hook is installed:
ls -la .githooks/pre-commit
git config core.hooksPathExpected output:
-rwxr-xr-x 1 user staff 2.1K .githooks/pre-commit
.githooks
Fix:
chmod +x .githooks/pre-commit
git config core.hooksPath .githooksInstall required tools:
# macOS
brew install deno prettier markdownlint-cli2
# Or check what's missing
which deno prettier markdownlint-cli2If VS Code flags markdown issues after formatting:
- Install markdownlint extension for VS Code
- Add to VS Code settings (
.vscode/settings.json):
{
"markdownlint.config": {
"MD013": { "line_length": 100, "code_blocks": false },
"MD033": false,
"MD041": false
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}Debug the hook:
# Run hook manually to see errors
.githooks/pre-commit
# Check git status
git status
# See what files would be affected
git diff --name-onlyCommon issues:
- Permission denied →
chmod +x .githooks/pre-commit - Tool not found → Install missing tools
- Config file syntax error → Validate JSON/JSONC
# Skip hooks for emergency commits
git commit --no-verify -m "emergency fix"
# Skip hooks for work-in-progress
git commit --no-verify -m "WIP: debugging"- Phase 1: Install hooks with inline defaults
- Phase 2: Add config files and commit them
- Phase 3: Team members run installer to get same setup
- Phase 4: Customize configs for project needs
For projects mixing multiple languages:
# The universal hook handles multiple languages automatically
# It detects: Deno, Node.js, Rust, Python, Go
# And formats each with appropriate toolsOverride defaults by creating local config files:
# Tighter line limits for documentation
echo '{"printWidth": 80}' > .prettierrc
# More strict markdown linting
echo '{"config": {"MD013": {"line_length": 80}}}' > .markdownlint-cli2.jsoncSet personal defaults in your home directory:
# Your personal formatting preferences
cp ~/dotfiles/adhoc/configs/.prettierrc ~/.prettierrc
cp ~/dotfiles/adhoc/configs/.markdownlint-cli2.jsonc ~/.markdownlint-cli2.jsoncThe same hook works in CI environments:
# .github/workflows/quality.yml
name: Code Quality
on: [push, pull_request]
jobs:
format-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install tools
run: |
npm install -g prettier markdownlint-cli2
curl -sSfL https://deno.land/install.sh | sh
- name: Check formatting
run: .githooks/pre-commitThis approach gives you:
- Zero-configuration defaults that work everywhere
- Intelligent precedence that respects local preferences
- Team collaboration through committed config files
- Gradual complexity - start simple, add configs when needed
- Tool consistency - same formatting rules across all projects
The key insight is leveraging how formatting tools naturally resolve configuration, allowing one script to provide smart defaults while remaining flexible for customization.
Start simple with just the git hook, then add config files when you need project-specific settings. The system grows with your needs while maintaining consistency.
{ "config": { // Line length - disabled for code blocks and tables "MD013": { "line_length": 100, "code_blocks": false, "tables": false }, // Allow inline HTML (for flexibility) "MD033": false, // Don't require H1 as first line "MD041": false }, "globs": ["**/*.md"], "ignores": ["node_modules/", "CHANGELOG.md"] }