Table of Contents
-
When To Use
-
Philosophy: Three-Layer Defense
-
Standard Hooks (Layer 1)
-
Python Projects
-
Basic Quality Checks
-
Configuration
-
Rust Projects
-
TypeScript Projects
-
Component-Specific Checks (Layer 2)
-
Python Monorepo/Plugin Architecture
-
- Lint Changed Components (scripts/run-component-lint.sh )
-
- Type Check Changed Components (scripts/run-component-typecheck.sh )
-
- Test Changed Components (scripts/run-component-tests.sh )
-
Add to Pre-commit Configuration
-
Validation Hooks (Layer 3)
-
Example: Plugin Structure Validation
-
Workflow
-
- Create Configuration Files
-
- Configure Python Type Checking
-
- Configure Testing
-
- Install and Test Hooks
-
- Create Manual Quality Scripts
-
scripts/check-all-quality.sh
-
Hook Execution Order
-
Performance Optimization
-
Typical Timings
-
Optimization Strategies
-
Hook Configuration
-
Skip Specific Hooks
-
Custom Hooks
-
CI Integration
-
Troubleshooting
-
Hooks Too Slow
-
Cache Issues
-
Hook Failures
-
Import Errors in Tests
-
Type Checking Errors
-
Best Practices
-
For New Projects
-
For Existing Projects
-
For Monorepos/Plugin Architectures
-
Complete Example: Python Monorepo
-
Related Skills
-
See Also
Pre-commit Setup Skill
Configure a detailed three-layer pre-commit quality system that enforces linting, type checking, and testing before commits.
When To Use
-
Setting up new project with code quality enforcement
-
Adding pre-commit hooks to existing project
-
Upgrading from basic linting to a full quality system
-
Setting up monorepo/plugin architecture with per-component quality checks
-
Updating pre-commit hook versions
When NOT To Use
-
Pre-commit hooks already configured and working optimally
-
Project doesn't use git version control
-
Team explicitly avoids pre-commit hooks for workflow reasons
-
Use /attune:upgrade-project instead for updating existing configurations
Philosophy: Three-Layer Defense
This skill implements a technical quality system based on three distinct layers. Layer 1 consists of fast global checks that perform quick linting and type checking on all files in approximately 50 to 200 milliseconds. Layer 2 focuses on component-specific checks, running detailed linting, type checking, and testing for changed components only, which typically takes between 10 and 30 seconds. Finally, Layer 3 uses validation hooks for structure verification, security scanning, and custom project checks. This multi-layered approach verifies that new code is automatically checked before commit, which prevents technical debt from entering the repository.
Standard Hooks (Layer 1)
Python Projects
Basic Quality Checks
-
pre-commit-hooks - File validation (trailing whitespace, EOF, YAML/TOML/JSON syntax)
-
ruff - Ultra-fast linting and formatting (~50ms)
-
ruff-format - Code formatting
-
mypy - Static type checking (~200ms)
-
bandit - Security scanning
Configuration
.pre-commit-config.yaml
repos:
-
repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-toml
- id: check-json
-
repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.14.2
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
-
repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.13.0
hooks:
- id: mypy
args: [--ignore-missing-imports]
-
repo: https://github.com/PyCQA/bandit
rev: 1.8.0
hooks:
- id: bandit
args: [-c, pyproject.toml]
Rust Projects
-
rustfmt - Code formatting
-
clippy - Linting
-
cargo-check - Compilation check
TypeScript Projects
-
eslint - Linting
-
prettier - Code formatting
-
tsc - Type checking
Component-Specific Checks (Layer 2)
For monorepos, plugin architectures, or projects with multiple components, add per-component quality checks.
Python Monorepo/Plugin Architecture
Create quality check scripts:
- Lint Changed Components (scripts/run-component-lint.sh )
#!/bin/bash
Lint only changed components based on staged files
set -euo pipefail
Detect changed components from staged files
CHANGED_COMPONENTS=$(git diff --cached --name-only | grep -E '^(plugins|components)/' | cut -d/ -f2 | sort -u) || true
if [ -z "$CHANGED_COMPONENTS" ]; then
echo "No components changed"
exit 0
fi
echo "Linting changed components: $CHANGED_COMPONENTS"
FAILED=()
for component in $CHANGED_COMPONENTS; do
if [ -d "plugins/$component" ]; then
echo "Linting $component..."
# Capture exit code to properly propagate failures
local exit_code=0
if [ -f "plugins/$component/Makefile" ] && grep -q "^lint:" "plugins/$component/Makefile"; then
(cd "plugins/$component" && make lint) || exit_code=$?
else
(cd "plugins/$component" && uv run ruff check .) || exit_code=$?
fi
if [ "$exit_code" -ne 0 ]; then
FAILED+=("$component")
fi
fi
done
if [ ${#FAILED[@]} -gt 0 ]; then
echo "Lint failed for: ${FAILED[*]}"
exit 1
fi
- Type Check Changed Components (scripts/run-component-typecheck.sh )
#!/bin/bash
Type check only changed components
set -euo pipefail
CHANGED_COMPONENTS=$(git diff --cached --name-only | grep -E '^(plugins|components)/' | cut -d/ -f2 | sort -u) || true
if [ -z "$CHANGED_COMPONENTS" ]; then
exit 0
fi
echo "Type checking changed components: $CHANGED_COMPONENTS"
FAILED=()
for component in $CHANGED_COMPONENTS; do
if [ -d "plugins/$component" ]; then
echo "Type checking $component..."
# Capture output and exit code separately to properly propagate failures
local output
local exit_code=0
if [ -f "plugins/$component/Makefile" ] && grep -q "^typecheck:" "plugins/$component/Makefile"; then
output=$(cd "plugins/$component" && make typecheck 2>&1) || exit_code=$?
else
output=$(cd "plugins/$component" && uv run mypy src/ 2>&1) || exit_code=$?
fi
# Display output (filter make noise)
echo "$output" | grep -v "^make[" || true
if [ "$exit_code" -ne 0 ]; then
FAILED+=("$component")
fi
fi
done
if [ ${#FAILED[@]} -gt 0 ]; then
echo "Type check failed for: ${FAILED[*]}"
exit 1
fi
- Test Changed Components (scripts/run-component-tests.sh )
#!/bin/bash
Test only changed components
set -euo pipefail
CHANGED_COMPONENTS=$(git diff --cached --name-only | grep -E '^(plugins|components)/' | cut -d/ -f2 | sort -u) || true
if [ -z "$CHANGED_COMPONENTS" ]; then
exit 0
fi
echo "Testing changed components: $CHANGED_COMPONENTS"
FAILED=()
for component in $CHANGED_COMPONENTS; do
if [ -d "plugins/$component" ]; then
echo "Testing $component..."
# Capture exit code to properly propagate failures
local exit_code=0
if [ -f "plugins/$component/Makefile" ] && grep -q "^test:" "plugins/$component/Makefile"; then
(cd "plugins/$component" && make test) || exit_code=$?
else
(cd "plugins/$component" && uv run pytest tests/) || exit_code=$?
fi
if [ "$exit_code" -ne 0 ]; then
FAILED+=("$component")
fi
fi
done
if [ ${#FAILED[@]} -gt 0 ]; then
echo "Tests failed for: ${FAILED[*]}"
exit 1
fi
Add to Pre-commit Configuration
.pre-commit-config.yaml (continued)
Layer 2: Component-Specific Quality Checks
- repo: local
hooks:
-
id: run-component-lint
name: Lint Changed Components
entry: ./scripts/run-component-lint.sh
language: system
pass_filenames: false
files: ^(plugins|components)/.*\.py$
-
id: run-component-typecheck
name: Type Check Changed Components
entry: ./scripts/run-component-typecheck.sh
language: system
pass_filenames: false
files: ^(plugins|components)/.*\.py$
-
id: run-component-tests
name: Test Changed Components
entry: ./scripts/run-component-tests.sh
language: system
pass_filenames: false
files: ^(plugins|components)/.*\.(py|md)$
Validation Hooks (Layer 3)
Add custom validation hooks for project-specific requirements.
Example: Plugin Structure Validation
Layer 3: Validation Hooks
- repo: local
hooks:
- id: validate-plugin-structure
name: Validate Plugin Structure
entry: python3 scripts/validate_plugins.py
language: system
pass_filenames: false
files: ^plugins/.*$
Workflow
- Create Configuration Files
Create .pre-commit-config.yaml
python3 plugins/attune/scripts/attune_init.py \
--lang python \
--name my-project \
--path .
Create quality check scripts (for monorepos)
mkdir -p scripts
chmod +x scripts/run-component-*.sh
- Configure Python Type Checking
Create pyproject.toml with strict type checking:
[tool.mypy]
python_version = "3.12"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
strict = true
Per-component configuration
[[tool.mypy.overrides]]
module = "plugins.*"
strict = true
- Configure Testing
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]
addopts = [
"-v", # Verbose output
"--strict-markers", # Strict marker enforcement
"--cov=src", # Coverage for src/
"--cov-report=term", # Terminal coverage report
]
markers = [
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
"integration: marks tests as integration tests",
]
- Install and Test Hooks
Install pre-commit tool
uv sync --extra dev
Install git hooks
uv run pre-commit install
Test on all files (first time)
uv run pre-commit run --all-files
Normal usage - test on staged files
git add .
git commit -m "feat: add feature"
Hooks run automatically
- Create Manual Quality Scripts
For full quality checks (CI/CD, monthly audits):
scripts/check-all-quality.sh
#!/bin/bash
Full quality check for all components
set -e
echo "=== Running Full Quality Checks ==="
Lint all components
./scripts/run-component-lint.sh --all
Type check all components
./scripts/run-component-typecheck.sh --all
Test all components
./scripts/run-component-tests.sh --all
echo "=== All Quality Checks Passed ==="
Hook Execution Order
Pre-commit hooks run in this order:
- File Validation (whitespace, EOF, YAML/TOML/JSON syntax)
- Security Scanning (bandit)
- Global Linting (ruff - all files)
- Global Type Checking (mypy - all files)
- Component Linting (changed components only)
- Component Type Checking (changed components only)
- Component Tests (changed components only)
- Custom Validation (structure, patterns, etc.)
All must pass for commit to succeed.
Performance Optimization
Typical Timings
Check Single Component Multiple Components All Components
Global Ruff ~50ms ~200ms ~500ms
Global Mypy ~200ms ~500ms ~1s
Component Lint ~2-5s ~4-10s ~30-60s
Component Typecheck ~3-8s ~6-16s ~60-120s
Component Tests ~5-15s ~10-30s ~120-180s
Total ~10-30s ~20-60s ~2-5min
Optimization Strategies
-
Only test changed components - Default behavior
-
Parallel execution - Hooks run concurrently when possible
-
Caching - Dependencies cached by uv
-
Incremental mypy - Use --incremental flag
Hook Configuration
Skip Specific Hooks
Skip specific hook for one commit
SKIP=run-component-tests git commit -m "WIP: tests in progress"
Skip component checks but keep global checks
SKIP=run-component-lint,run-component-typecheck,run-component-tests git commit -m "WIP"
Skip all hooks (DANGEROUS - use only for emergencies)
git commit --no-verify -m "Emergency fix"
Custom Hooks
Add project-specific hooks:
- repo: local
hooks:
-
id: check-architecture
name: Validate Architecture Decisions
entry: python3 scripts/check_architecture.py
language: system
pass_filenames: false
files: ^(plugins|src)/.*\.py$
-
id: check-coverage
name: Verify Test Coverage
entry: python3 scripts/check_coverage.py
language: system
pass_filenames: false
files: ^(plugins|src)/.*\.py$
CI Integration
Verify CI runs the same detailed checks:
.github/workflows/quality.yml
name: Code Quality
on: [push, pull_request]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install uv
run: pip install uv
- name: Install dependencies
run: uv sync
- name: Run Comprehensive Quality Checks
run: ./scripts/check-all-quality.sh
- name: Upload Coverage
uses: codecov/codecov-action@v4
with:
files: ./coverage.xml
Troubleshooting
Hooks Too Slow
Solution: Only changed components are checked by default. For even faster commits:
Skip tests during development
SKIP=run-component-tests git commit -m "WIP: feature development"
Run tests manually when ready
./scripts/run-component-tests.sh --changed
Cache Issues
Clear pre-commit cache
uv run pre-commit clean
Clear component caches
find . -name "pycache" -type d -exec rm -rf {} +
find . -name ".pytest_cache" -type d -exec rm -rf {} +
find . -name ".mypy_cache" -type d -exec rm -rf {} +
Hook Failures
See detailed output
uv run pre-commit run --verbose --all-files
Run specific component checks manually
cd plugins/my-component
make lint
make typecheck
make test
Import Errors in Tests
Ensure PYTHONPATH is set in pyproject.toml
[tool.pytest.ini_options]
pythonpath = ["src"]
Type Checking Errors
Use per-module overrides for gradual typing
[[tool.mypy.overrides]]
module = "legacy_module.*"
disallow_untyped_defs = false
Best Practices
For New Projects
Start with strict settings from the beginning, as they are easier to maintain over time. We recommend configuring type checking with strict = true in your pyproject.toml and setting up testing early by including pytest in your pre-commit hooks. If you must skip any hooks, always document the reason for the exception.
For Existing Projects
When adding hooks to an existing codebase, use a gradual adoption strategy. Start with global checks and add component-specific checks later as you resolve legacy issues. Fix identified quality problems progressively and create a baseline to document the current state for tracking improvements. Use the --no-verify flag sparingly and only for true emergencies.
For Monorepos and Plugin Architectures
Standardize your development targets by using per-component Makefiles for linting, type checking, and testing. Centralize common settings in a root pyproject.toml while allowing for per-component overrides. Automate the detection of changed components to keep commit times fast, and use a progressive disclosure approach to show summaries first and detailed errors only on failure.
Complete Example: Python Monorepo
.pre-commit-config.yaml
repos:
Layer 1: Fast Global Checks
-
repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-toml
- id: check-json
-
repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.14.2
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
-
repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.13.0
hooks:
- id: mypy
args: [--ignore-missing-imports]
-
repo: https://github.com/PyCQA/bandit
rev: 1.8.0
hooks:
- id: bandit
args: [-c, pyproject.toml]
Layer 2: Component-Specific Checks
- repo: local
hooks:
-
id: run-component-lint
name: Lint Changed Components
entry: ./scripts/run-component-lint.sh
language: system
pass_filenames: false
files: ^plugins/.*\.py$
-
id: run-component-typecheck
name: Type Check Changed Components
entry: ./scripts/run-component-typecheck.sh
language: system
pass_filenames: false
files: ^plugins/.*\.py$
-
id: run-component-tests
name: Test Changed Components
entry: ./scripts/run-component-tests.sh
language: system
pass_filenames: false
files: ^plugins/.*\.(py|md)$
Layer 3: Validation Hooks
- repo: local
hooks:
- id: validate-plugin-structure
name: Validate Plugin Structure
entry: python3 scripts/validate_plugins.py
language: system
pass_filenames: false
files: ^plugins/.*$
Related Skills
-
Skill(attune:project-init)
-
Full project initialization
-
Skill(attune:workflow-setup)
-
GitHub Actions setup
-
Skill(attune:makefile-generation)
-
Generate component Makefiles
-
Skill(pensive:shell-review)
-
Audit shell scripts for exit code and safety issues
See Also
-
Quality Gates - Three-layer validation: pre-commit hooks (formatting, linting), CI checks (tests, coverage), and PR review gates (code quality, security)
-
Testing Guide - Run make test for unit tests, make lint for static analysis, make format for auto-formatting. Target 85%+ coverage.