precommit-setup
Configure comprehensive three-layer pre-commit quality system with linting, type checking, and testing enforcement
Installation
Follow the repository instructions for this skill and your agent. Review the files and dependencies before adding it to your workflow.
View source ↗GuidesSkill instructions
Table of Contents
- Use When
- 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
-
1. Lint Changed Components (
scripts/run-component-lint.sh) -
2. Type Check Changed Components (
scripts/run-component-typecheck.sh) -
3. Test Changed Components (
scripts/run-component-tests.sh) - Add to Pre-commit Configuration
- Validation Hooks (Layer 3)
- Example: Plugin Structure Validation
- Workflow
- 1. Create Configuration Files
- 2. Configure Python Type Checking
- 3. Configure Testing
- 4. Install and Test Hooks
- 5. 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.
Use When
- 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
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
```yaml
.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:
1. Lint Changed Components (scripts/run-component-lint.sh)
```bash #!/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 ```
2. Type Check Changed Components (scripts/run-component-typecheck.sh)
```bash #!/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 ```
3. Test Changed Components (scripts/run-component-tests.sh)
```bash #!/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
```yaml
.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
```yaml
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
1. Create Configuration Files
```bash
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 ```
2. Configure Python Type Checking
Create pyproject.toml with strict type checking:
```toml [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 ```
3. Configure Testing
```toml [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", ] ```
4. Install and Test Hooks
```bash
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
```
5. Create Manual Quality Scripts
For full quality checks (CI/CD, monthly audits):
scripts/check-all-quality.sh
```bash #!/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
--incrementalflag
Hook Configuration
Skip Specific Hooks
```bash
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:
```yaml
- 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:
```yaml
.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:
```bash
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
```bash
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
```bash
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
```toml
Ensure PYTHONPATH is set in pyproject.toml
[tool.pytest.ini_options] pythonpath = ["src"] ```
Type Checking Errors
```toml
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
```yaml
.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 Documentation - Detailed quality system guide
- Testing Guide - Testing best practices
Examples
Read the usage examples in the source repository.
View source ↗Source history
- Indexed on
- 2026-01-20
- Index updated
- 2026-01-31
- Branch
- main
Index and sync dates describe this directory. View the source repository for code history.
GitHub ↗chat Comments (0)
Sign in to join the discussion and leave a comment.
No comments yet. Be the first to share your thoughts!