APM

>Agent Skill

@athola/precommit-setup

skilldevelopment

Configure three-layer pre-commit system with linting, type checking, and testing hooks. Use for quality gate setup and code standards. Skip if pre-commit is optimally configured.

apm::install
$apm install @athola/precommit-setup
apm::skill.md
---
name: precommit-setup
description: "Configure three-layer pre-commit system with linting, type checking, and testing hooks. Use for quality gate setup and code standards. Skip if pre-commit is optimally configured."
# Custom metadata (not used by Claude for matching):
model: claude-sonnet-4
tools: [Read, Write, Bash]
category: infrastructure
tags: [pre-commit, quality-gates, linting, type-checking, testing]
complexity: intermediate
estimated_tokens: 1800
---
## Table of Contents

- [When To Use](#when-to-use)
- [Philosophy: Three-Layer Defense](#philosophy:-three-layer-defense)
- [Standard Hooks (Layer 1)](#standard-hooks-(layer-1))
- [Python Projects](#python-projects)
- [Basic Quality Checks](#basic-quality-checks)
- [Configuration](#configuration)
- [Rust Projects](#rust-projects)
- [TypeScript Projects](#typescript-projects)
- [Component-Specific Checks (Layer 2)](#component-specific-checks-(layer-2))
- [Python Monorepo/Plugin Architecture](#python-monorepo-plugin-architecture)
- [1. Lint Changed Components (`scripts/run-component-lint.sh`)](#1-lint-changed-components-(scripts-run-component-lintsh))
- [2. Type Check Changed Components (`scripts/run-component-typecheck.sh`)](#2-type-check-changed-components-(scripts-run-component-typechecksh))
- [3. Test Changed Components (`scripts/run-component-tests.sh`)](#3-test-changed-components-(scripts-run-component-testssh))
- [Add to Pre-commit Configuration](#add-to-pre-commit-configuration)
- [Validation Hooks (Layer 3)](#validation-hooks-(layer-3))
- [Example: Plugin Structure Validation](#example:-plugin-structure-validation)
- [Workflow](#workflow)
- [1. Create Configuration Files](#1-create-configuration-files)
- [2. Configure Python Type Checking](#2-configure-python-type-checking)
- [3. Configure Testing](#3-configure-testing)
- [4. Install and Test Hooks](#4-install-and-test-hooks)
- [5. Create Manual Quality Scripts](#5-create-manual-quality-scripts)
- [`scripts/check-all-quality.sh`](#scripts-check-all-qualitysh)
- [Hook Execution Order](#hook-execution-order)
- [Performance Optimization](#performance-optimization)
- [Typical Timings](#typical-timings)
- [Optimization Strategies](#optimization-strategies)
- [Hook Configuration](#hook-configuration)
- [Skip Specific Hooks](#skip-specific-hooks)
- [Custom Hooks](#custom-hooks)
- [CI Integration](#ci-integration)
- [Troubleshooting](#troubleshooting)
- [Hooks Too Slow](#hooks-too-slow)
- [Cache Issues](#cache-issues)
- [Hook Failures](#hook-failures)
- [Import Errors in Tests](#import-errors-in-tests)
- [Type Checking Errors](#type-checking-errors)
- [Best Practices](#best-practices)
- [For New Projects](#for-new-projects)
- [For Existing Projects](#for-existing-projects)
- [For Monorepos/Plugin Architectures](#for-monorepos-plugin-architectures)
- [Complete Example: Python Monorepo](#complete-example:-python-monorepo)
- [Related Skills](#related-skills)
- [See Also](#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
1. **pre-commit-hooks** - File validation (trailing whitespace, EOF, YAML/TOML/JSON syntax)
2. **ruff** - Ultra-fast linting and formatting (~50ms)
3. **ruff-format** - Code formatting
4. **mypy** - Static type checking (~200ms)
5. **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

1. **rustfmt** - Code formatting
2. **clippy** - Linting
3. **cargo-check** - Compilation check

### TypeScript Projects

1. **eslint** - Linting
2. **prettier** - Code formatting
3. **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:

\`\`\`
1. File Validation (whitespace, EOF, YAML/TOML/JSON syntax)
2. Security Scanning (bandit)
3. Global Linting (ruff - all files)
4. Global Type Checking (mypy - all files)
5. Component Linting (changed components only)
6. Component Type Checking (changed components only)
7. Component Tests (changed components only)
8. 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

1. **Only test changed components** - Default behavior
2. **Parallel execution** - Hooks run concurrently when possible
3. **Caching** - Dependencies cached by uv
4. **Incremental mypy** - Use `--incremental` flag

## 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** - 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.