| name | pre-commit-hooks |
| description | MANDATORY setup for all projects. Automated code quality enforcement before commits. Prevents bad code from entering repository. |
Pre-Commit Hooks
Core Principle
Automate quality gates - humans forget, machines don't.
When to Use This Skill
- Setting up new projects (MANDATORY)
- Onboarding team members
- Improving code quality
- Preventing bad commits
- Enforcing standards
The Iron Law
NO PROJECT WITHOUT PRE-COMMIT HOOKS.
Every repository MUST have automated quality checks before commits are allowed.
Why Pre-Commit Hooks Are Mandatory
Without hooks:
- ❌ Unformatted code enters repository
- ❌ Type errors slip through
- ❌ Tests broken by commits
- ❌ Secrets/credentials committed
- ❌ Linting errors ignored
With hooks:
- ✅ Automatic code formatting
- ✅ Type checking enforced
- ✅ Tests run before commit
- ✅ No secrets committed
- ✅ Consistent code quality
Authority: 95% of professional teams use pre-commit hooks. Not using them is unprofessional.
Setup by Language
JavaScript/TypeScript Projects
Install pre-commit framework:
npm install --save-dev husky lint-staged
npx husky install
npm set-script prepare "husky install"
Create .husky/pre-commit:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
Configure package.json:
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write",
"npm run test:related --bail"
],
"*.{json,md,yml,yaml}": [
"prettier --write"
]
},
"scripts": {
"prepare": "husky install",
"test:related": "jest --bail --findRelatedTests"
}
}
Minimum hooks:
- ✅ ESLint (linting + auto-fix)
- ✅ Prettier (formatting)
- ✅ TypeScript check (tsc --noEmit)
- ✅ Jest (related tests)
- ✅ No secrets check
Python Projects
Install pre-commit:
pip install pre-commit
Create .pre-commit-config.yaml:
repos:
# Code formatting
- repo: https://github.com/psf/black
rev: 23.12.1
hooks:
- id: black
language_version: python3.11
# Import sorting
- repo: https://github.com/PyCQA/isort
rev: 5.13.2
hooks:
- id: isort
# Linting
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.1.9
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
# Type checking
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
hooks:
- id: mypy
additional_dependencies: [types-requests]
# Security checks
- repo: https://github.com/PyCQA/bandit
rev: 1.7.6
hooks:
- id: bandit
args: ["-c", "pyproject.toml"]
# Tests
- repo: local
hooks:
- id: pytest
name: pytest
entry: pytest
language: system
pass_filenames: false
always_run: true
Install hooks:
pre-commit install
Minimum hooks:
- ✅ Black (formatting)
- ✅ isort (import sorting)
- ✅ Ruff (linting)
- ✅ mypy (type checking)
- ✅ pytest (tests)
- ✅ bandit (security)
PHP/Laravel Projects
Install pre-commit tools:
composer require --dev friendsofphp/php-cs-fixer phpstan/phpstan
Create .husky/pre-commit (using Husky):
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# Get staged PHP files
FILES=$(git diff --cached --name-only --diff-filter=ACMR | grep '\.php$')
if [ -n "$FILES" ]; then
# Format code
vendor/bin/php-cs-fixer fix $FILES --config=.php-cs-fixer.php
# Static analysis
vendor/bin/phpstan analyse $FILES
# Run tests for changed files
vendor/bin/paratest --filter=$(echo $FILES | tr '\n' '|')
# Re-add formatted files
git add $FILES
fi
Or use PHP pre-commit library:
composer require --dev brainmaestro/composer-git-hooks
Configure in composer.json:
{
"extra": {
"hooks": {
"pre-commit": [
"vendor/bin/php-cs-fixer fix --dry-run --diff",
"vendor/bin/phpstan analyse",
"vendor/bin/paratest"
]
}
}
}
Minimum hooks:
- ✅ PHP CS Fixer (formatting)
- ✅ PHPStan (static analysis)
- ✅ Psalm (type checking)
- ✅ Paratest (tests)
- ✅ No secrets check
Universal Hooks (All Projects)
Add these to every project:
1. No Secrets Check
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.1
hooks:
- id: gitleaks
2. Trailing Whitespace
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-json
- id: check-added-large-files
3. Commit Message Validation
# .husky/commit-msg
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no -- commitlint --edit "$1"
Install commitlint:
npm install --save-dev @commitlint/cli @commitlint/config-conventional
Configure commitlint.config.js:
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [2, 'always', [
'feat', 'fix', 'docs', 'style', 'refactor',
'perf', 'test', 'build', 'ci', 'chore'
]]
}
};
Frontend + Backend Test Requirements
For projects with both frontend and backend:
# .husky/pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
echo "🧪 Running pre-commit checks..."
# Check if backend files changed
BACKEND_CHANGED=$(git diff --cached --name-only | grep -E '\.(php|py|go|java)$')
# Check if frontend files changed
FRONTEND_CHANGED=$(git diff --cached --name-only | grep -E '\.(js|jsx|ts|tsx|vue)$')
# If backend changed, run backend tests
if [ -n "$BACKEND_CHANGED" ]; then
echo "🔧 Backend files changed - running backend tests..."
npm run test:backend || exit 1
fi
# If frontend changed, run frontend AND e2e tests
if [ -n "$FRONTEND_CHANGED" ]; then
echo "🎨 Frontend files changed - running frontend tests..."
npm run test:frontend || exit 1
echo "🌐 Running e2e tests..."
npm run test:e2e || exit 1
fi
# If both changed, ensure full test suite passes
if [ -n "$BACKEND_CHANGED" ] && [ -n "$FRONTEND_CHANGED" ]; then
echo "🔄 Full stack changes detected - running complete test suite..."
npm run test:all || exit 1
fi
echo "✅ All pre-commit checks passed!"
Bypassing Hooks (Emergency Only)
Never bypass hooks casually:
# WRONG - don't do this
git commit --no-verify
# ONLY use in true emergencies:
# - Hotfix for production outage
# - CI/CD system down, blocking critical deploy
# - After consulting team lead
If you bypass hooks:
- Add TODO comment explaining why
- Create follow-up task to fix properly
- Inform team in commit message
- Never bypass for "speed" or "convenience"
Verification
After setup, verify hooks work:
# Make a bad change (intentionally)
echo "const x = 1" > test.js # Missing semicolon
# Try to commit
git add test.js
git commit -m "test"
# Should FAIL with linting error
# If it commits successfully, hooks aren't working!
Team Onboarding
When new developer joins:
# Clone repo
git clone <repo>
cd <repo>
# Install dependencies (includes hook setup)
npm install # or pip install -r requirements.txt
# Verify hooks installed
ls .git/hooks/pre-commit
# Test hooks work
# (make intentional error, try to commit, should fail)
Common Issues
Issue: Hooks not running
Solution:
# Reinstall hooks
npm run prepare # for Husky
# or
pre-commit install # for Python
Issue: Hooks too slow
Solution:
# Run only on staged files (lint-staged)
# Skip slow tests in hooks, run in CI
# Use --bail flag to fail fast
Issue: Hooks block legitimate commits
Solution:
# Fix the actual issue (don't bypass)
# Update hook configuration if rule is wrong
# Discuss with team if standard should change
Integration with Skills
Pre-commit hooks enforce:
- verification-before-completion - Tests must pass
- git-workflow - Commit message format
- database-backup - Prevent commits without backup (if DB changed)
- code-review - Linting/formatting standards
Commitment Required
Before proceeding, acknowledge:
- I will set up pre-commit hooks on ALL projects
- I will NEVER bypass hooks without team discussion
- I understand hooks are quality gates, not obstacles
- I will help team members set up hooks correctly
- I will update hooks when new standards are adopted
The Iron Law (Repeated): NO PROJECT WITHOUT PRE-COMMIT HOOKS. This is not optional. This is professional software development.