AI Web FeedsAI Web FeedsOpen web AI reader
  • Contributing
    Documentation

    Pre-commit Hooks

    Guide to pre-commit hooks and code quality automation in ai-web-feeds

    Source: apps/web/content/docs/contributing/pre-commit-hooks.mdx

    Overview

    ai-web-feeds uses pre-commit to automatically run code quality checks before each commit. This ensures consistent code style, catches common errors, and maintains high code quality across the project.

    Installation

    Pre-commit is included in the dev dependencies. Install and activate hooks:

    # Sync dependencies
    uv sync
    
    # Install pre-commit hooks
    uv run pre-commit install
    
    # Install commit-msg hook (for conventional commits)
    uv run pre-commit install --hook-type commit-msg
    
    # Verify installation
    ls -la .git/hooks/pre-commit
    ls -la .git/hooks/commit-msg

    Configured Hooks

    Python - Ruff (Linting & Formatting)

    Fast, comprehensive Python linter and formatter

    - repo: https://github.com/astral-sh/ruff-pre-commit
      hooks:
        - id: ruff # Linting with auto-fix
        - id: ruff-format # Code formatting

    Checks:

    • Code style (PEP 8)
    • Import organization
    • Unused variables/imports
    • Type annotations
    • Security issues (bandit rules)
    • Complexity
    • And 100+ other rules

    Manual run:

    uv run ruff check .              # Lint
    uv run ruff check --fix .        # Lint with auto-fix
    uv run ruff format .             # Format

    Python - ty (Type Checking)

    Static type checking for Python

    - repo: local
      hooks:
        - id: ty-smoke
          name: ty (python smoke)
          entry: uv run ty check ...

    Checks:

    • Type consistency
    • Type annotations
    • Return type validation
    • Optional handling

    Manual run:

    make type-check
    uv run pre-commit run ty-smoke --all-files

    Python - Bandit (Security)

    Security vulnerability scanner

    - repo: https://github.com/PyCQA/bandit
      hooks:
        - id: bandit
          args: [-c, pyproject.toml]

    Checks:

    • SQL injection risks
    • Command injection
    • Unsafe deserialization
    • Hardcoded passwords
    • Weak cryptography

    Manual run:

    uv run bandit -r src/ -c pyproject.toml

    TypeScript/JavaScript - ESLint

    Linting for TypeScript and React code

    - repo: https://github.com/pre-commit/mirrors-eslint
      hooks:
        - id: eslint
          name: eslint (apps/web)
          files: ^apps/web/.*\.[jt]sx?$
          args: [--fix, --max-warnings=0]

    Checks:

    • TypeScript errors
    • React best practices
    • Next.js patterns
    • Unused variables
    • Import issues

    Manual run:

    cd apps/web && pnpm lint
    cd apps/web && pnpm lint --fix

    TypeScript/JavaScript - Prettier

    Opinionated code formatter

    - repo: https://github.com/pre-commit/mirrors-prettier
      hooks:
        - id: prettier
          name: prettier (apps/web)
          files: ^apps/web/.*\.(js|jsx|ts|tsx|json|css|scss|md|mdx)$

    Formats:

    • JavaScript/TypeScript
    • JSON
    • CSS/SCSS
    • Markdown/MDX

    Manual run:

    cd apps/web && pnpm prettier --write .

    YAML Formatting

    YAML linting and formatting

    - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
      hooks:
        - id: pretty-format-yaml
          args: [--autofix, --indent, "2"]

    Manual run:

    yamllint data/feeds.yaml

    Markdown Formatting

    Markdown linting and formatting

    - repo: https://github.com/executablebooks/mdformat
      hooks:
        - id: mdformat
          additional_dependencies:
            - mdformat-gfm
            - mdformat-black
          args: [--wrap, "88"]

    Manual run:

    mdformat README.md

    Spell Checking

    Catch common spelling mistakes

    - repo: https://github.com/codespell-project/codespell
      hooks:
        - id: codespell
          args: [--ignore-words-list=<project-accepted-words>]

    Manual run:

    codespell .

    Shell Scripts

    Shell script linting

    - repo: https://github.com/shellcheck-py/shellcheck-py
      hooks:
        - id: shellcheck
          args: [--severity=warning]

    Manual run:

    shellcheck scripts/*.sh

    SQL Formatting

    SQL linting and formatting

    - repo: https://github.com/sqlfluff/sqlfluff
      hooks:
        - id: sqlfluff-lint
          args: [--dialect, sqlite]
        - id: sqlfluff-fix
          args: [--dialect, sqlite, --force]

    Manual run:

    sqlfluff lint data/*.sql
    sqlfluff fix data/*.sql

    Secrets Detection

    Prevent committing secrets

    - repo: https://github.com/Yelp/detect-secrets
      hooks:
        - id: detect-secrets
          args: [--baseline, .secrets.baseline]

    Manual run:

    uv run detect-secrets scan
    uv run detect-secrets audit .secrets.baseline

    Conventional Commits

    Enforce commit message format

    - repo: https://github.com/compilerla/conventional-pre-commit
      hooks:
        - id: conventional-pre-commit
          stages: [commit-msg]

    Manual test:

    echo "feat(core): test message" | npx commitlint

    General File Checks

    Basic file hygiene

    - repo: https://github.com/pre-commit/pre-commit-hooks
      hooks:
        - id: trailing-whitespace
        - id: end-of-file-fixer
        - id: check-yaml
        - id: check-json
        - id: check-toml
        - id: check-added-large-files
        - id: check-merge-conflict
        - id: mixed-line-ending
        - id: detect-private-key
        - id: no-commit-to-branch

    Local Hooks (Project-Specific)

    Python Tests

    - id: pytest
      name: pytest (packages)
      entry: bash -c 'cd packages/ai_web_feeds && uv run pytest tests/ -v'
      files: ^packages/ai_web_feeds/(src|tests)/.*\.py$

    Run tests when Python files change

    Python Coverage Check

    - id: pytest-cov
      name: pytest coverage (≥90%)
      entry: bash -c 'cd packages/ai_web_feeds && uv run pytest tests/ --cov=src --cov-fail-under=90'
      stages: [push]

    Enforces 90% coverage threshold on push

    TypeScript Type Check

    - id: tsc
      name: tsc (apps/web)
      entry: bash -c 'cd apps/web && pnpm tsc --noEmit'
      files: ^apps/web/.*\.[jt]sx?$

    Type check TypeScript files

    Next.js Build Check

    - id: nextjs-build
      name: next build check
      entry: bash -c 'cd apps/web && pnpm build'
      stages: [push]

    Verify Next.js builds successfully on push

    Data Assets Validation

    - id: validate-data-assets
      name: validate data assets
      entry: bash -c 'cd data && uv run python validate_data_assets.py'
      files: ^data/(feeds|topics)\.(yaml|json|schema\.json)$

    Validate feeds.yaml and topics.yaml against schemas

    Usage

    Automatic (Default)

    Hooks run automatically on git commit:

    git add .
    git commit -m "feat(core): add new feature"
    # Pre-commit hooks run automatically

    Manual Run

    Run all hooks on all files:

    uv run pre-commit run --all-files

    Run specific hook:

    uv run pre-commit run ruff --all-files
    uv run pre-commit run ty-smoke --all-files
    uv run pre-commit run prettier --all-files

    Run on specific files:

    uv run pre-commit run --files src/models.py

    Skip all hooks:

    git commit --no-verify -m "message"
    # or
    git commit -n -m "message"

    Skip specific hook by modifying SKIP env var:

    SKIP=pytest git commit -m "message"

    ⚠️ Warning: Only skip hooks when absolutely necessary. CI will still run all checks.

    Configuration

    pyproject.toml

    Ruff, ty, Pytest, and Coverage are configured in pyproject.toml and the local pre-commit hook:

    [tool.ruff]
    target-version = "py313"
    line-length = 100
    
    [tool.ruff.lint]
    select = ["E", "F", "I", "N", "UP", "ANN", "S", "B", ...]
    ignore = ["ANN401", "S101", ...]
    
    [tool.pytest.ini_options]
    testpaths = ["tests"]
    addopts = ["--cov", "--cov-report=term-missing"]
    
    [tool.coverage.report]
    fail_under = 90

    .pre-commit-config.yaml

    Main pre-commit configuration:

    default_language_version:
      python: python3.13
      node: 20.11.0
    
    repos:
      - repo: https://github.com/astral-sh/ruff-pre-commit
        rev: v0.8.4
        hooks:
          - id: ruff
          - id: ruff-format
      # ... more hooks

    Update Hook Versions

    # Update to latest versions
    uv run pre-commit autoupdate
    
    # Commit the changes
    git add .pre-commit-config.yaml
    git commit -m "chore(tooling): update pre-commit hook versions"

    Troubleshooting

    Hooks Not Running

    # Reinstall hooks
    uv run pre-commit uninstall
    uv run pre-commit install
    uv run pre-commit install --hook-type commit-msg

    Hook Environment Issues

    # Clean hook environments
    uv run pre-commit clean
    
    # Reinstall all hook environments
    uv run pre-commit install-hooks

    Specific Hook Failing

    # Run in verbose mode
    uv run pre-commit run <hook-id> --all-files --verbose
    
    # Example
    uv run pre-commit run ty-smoke --all-files --verbose

    Update Hook Dependencies

    # For Python hooks
    uv sync
    
    # For Node hooks
    cd apps/web && pnpm install

    Skip Problematic Files

    Add to .pre-commit-config.yaml:

    - id: hook-id
      exclude: ^path/to/exclude/

    CI Integration

    Pre-commit hooks also run in CI (.github/workflows/ci.yml):

    - name: Run pre-commit
      run: |
        uv sync
        uv run pre-commit run --all-files

    CI runs are more comprehensive and cannot be skipped.

    Performance

    First Run

    First run is slow (installing hook environments):

    # Install all environments upfront
    uv run pre-commit install-hooks

    Cached Runs

    Subsequent runs are fast (seconds):

    • Hooks only run on changed files
    • Environments are cached
    • Results are cached

    Optimize Large Repos

    # Run hooks in parallel
    uv run pre-commit run --all-files --verbose --parallel

    Best Practices

    1. Run Before Committing

    # Run all hooks on staged changes
    uv run pre-commit run
    
    # Or commit normally (auto-runs)
    git commit

    2. Fix Issues Early

    Don't skip hooks - fix the issues:

    # Auto-fix what can be fixed
    uv run pre-commit run --all-files
    
    # Review and fix remaining issues

    3. Keep Hooks Updated

    # Monthly or quarterly
    uv run pre-commit autoupdate

    4. Understand Each Hook

    Know what each hook does and why it's important.

    5. Add Project-Specific Hooks

    Add local hooks for project-specific validations.

    Resources

    FAQ

    Why pre-commit hooks?

    • Catch issues early - Before CI, before review
    • Consistent quality - Same checks for everyone
    • Fast feedback - Seconds, not minutes
    • Reduce CI load - Less failed CI runs
    • Learn best practices - Hooks teach good patterns

    Can I customize rules?

    Yes! Edit configuration files:

    • Python: pyproject.toml
    • TypeScript: eslint.config.mjs
    • Pre-commit: .pre-commit-config.yaml

    What if a hook is too slow?

    • Run only on changed files (default)
    • Skip expensive hooks: SKIP=pytest git commit
    • Move slow checks to CI only: stages: [push]

    How do I add a new hook?

    1. Find hook repo on pre-commit.com/hooks.html
    2. Add to .pre-commit-config.yaml
    3. Test: uv run pre-commit run <hook-id> --all-files
    4. Commit configuration

    What about Windows?

    Pre-commit works on Windows with Git Bash or WSL.

    Support

    For issues with pre-commit hooks:

    Pre-commit Hooks | AI Web Feeds