testsAndMisc/.pre-commit-config.yaml
Krzysztof kuhy Rudnicki 7f706adb11
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Python tests / test (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Make the hook tests' non-vacuity re-verifiable, and gate it
63a5124 claimed "12 tests, 12 killed by at least one mutation", but the harness
that proved it lived in a session scratchpad. That left an unfalsifiable claim
about tests whose entire purpose is to not be vacuous -- and the facts it
encodes are exactly the ones a future edit to either hook would invalidate.

The mutations are per guard, not one whole-body gutting. Gutting a hook body
kills only the three positive tests, because a gutted hook emits neither a
launch nor any output, which is precisely what the nine negative tests expect.
Run that way the negative tests look vacuous when they are not; dropping each
guard in turn is what actually exercises them.

Two failure modes of the harness itself are handled. A mutation that leaves the
hook text unchanged now raises instead of quietly reporting "killed 0": a
behaviour-preserving edit to a hook, such as renaming a variable, would
otherwise turn a mutation into a no-op and weaken the proof without a word.
Verified by renaming count to n in a copy of the start hook -- the copy still
printed the correct banner, and the tool still refused it. And where the hooks
are not deployed at all the tool exits 0 rather than failing, since a CI runner
has no ~/.claude to mutate.

Wired into pre-commit scoped to the suite and the mutation definitions, the only
two files that can invalidate the proof. It costs ~40s, so it stays off
unrelated commits. It gates rather than warns: survivors mean exit 1.

One finding stands recorded: start-no-readable-guard kills nothing on its own.
The start hook's [[ ! -r "$STATE" ]] guard is redundant, since jq's stderr is
suppressed and an empty count already fails the arithmetic, so only the compound
mutation reaches those cases. The guard is left in place as defence in depth.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 17:37:34 +02:00

477 lines
19 KiB
YAML

# ==============================================================================
# Pre-commit Configuration - Multi-language Linting & Formatting
# ==============================================================================
# Install: pre-commit install && pre-commit install --hook-type pre-push
# Fast lint: pre-commit run --all-files (linters only, ~10 s)
# Full suite: pre-commit run --all-files --hook-stage pre-push (+ tests)
# Update hooks: pre-commit autoupdate
# ==============================================================================
# Global settings
default_language_version:
python: python3
# By default every hook runs ONLY at commit-time. Pre-push runs only the
# hooks that explicitly opt in via `stages: [pre-push]` (currently
# pytest-coverage + prettier). This keeps `git push` cheap; commit-time
# already enforces the full lint suite.
default_stages: [pre-commit]
# Fail fast on first error (set to false to see all errors)
fail_fast: false
# Configuration
ci:
autofix_commit_msg: "style: auto-fix by pre-commit hooks"
autoupdate_commit_msg: "chore: update pre-commit hooks"
repos:
# ===========================================================================
# GENERAL HOOKS - File formatting and validation
# ===========================================================================
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
args: [--markdown-linebreak-ext=md]
# chattr +i (self-commitment lock on the shutdown-enforcement
# script) makes this file unwritable on purpose; the fixer hooks
# can't open it for writing even as the owning user.
exclude: ^linux_configuration/scripts/periodic_background/digital_wellbeing/setup_midnight_shutdown\.sh$
- id: end-of-file-fixer
exclude: ^linux_configuration/scripts/periodic_background/digital_wellbeing/setup_midnight_shutdown\.sh$
- id: check-yaml
args: [--unsafe]
- id: check-json
# Exclude JSONC files (VS Code configs, TypeScript configs)
exclude: ^(\.vscode/|.*/\.vscode/|.*tsconfig.*\.json)
- id: check-toml
- id: check-xml
- id: check-added-large-files
args: [--maxkb=2000]
- id: check-merge-conflict
- id: check-case-conflict
- id: check-symlinks
- id: check-executables-have-shebangs
- id: check-shebang-scripts-are-executable
- id: detect-private-key
- id: debug-statements
- id: name-tests-test
args: [--pytest-test-first]
- id: check-ast
- id: check-builtin-literals
- id: check-docstring-first
- id: fix-byte-order-marker
- id: mixed-line-ending
args: [--fix=lf]
- id: requirements-txt-fixer
# ===========================================================================
# BINARY BLOCKER - Prevent binary/image files from being committed
# ===========================================================================
- repo: local
hooks:
- id: no-binaries
name: Block binary/image files
entry: meta/scripts/check_no_binaries.sh
language: script
always_run: false
- id: ai-evidence-contract
name: Require AI evidence artifacts for code changes
entry: meta/scripts/check_ai_evidence.sh
language: script
pass_filenames: false
always_run: true
- id: ai-multifile-contract
name: Require workflow contract for multi-file code changes
entry: meta/scripts/check_agent_contract.sh
language: script
pass_filenames: false
always_run: true
- id: append-only-sessions
name: Enforce append-only session logs
entry: meta/scripts/check_append_only_sessions.sh
language: script
pass_filenames: false
always_run: true
# ===========================================================================
# POLLING SCRIPT LINTER - Detect fork-storm anti-patterns in shell scripts
# ===========================================================================
- repo: local
hooks:
- id: no-polling-antipatterns
name: Block polling script anti-patterns
entry: meta/scripts/check_polling_antipatterns.sh
language: script
types: [shell]
require_serial: true
exclude: ^(\.git/|phone_focus_mode/lib/tests/|tests/)
# ===========================================================================
# NOQA BLOCKER - Zero tolerance for noqa/type:ignore suppression comments
# ===========================================================================
- repo: local
hooks:
- id: no-noqa
name: Block noqa comments
entry: '(?i)#\s*(noqa|type:\s*ignore)'
language: pygrep
types: [python]
- id: no-ruff-noqa
name: Block ruff noqa file-level comments
entry: '(?i)#\s*ruff:\s*noqa'
language: pygrep
types: [python]
# ===========================================================================
# RUFF - Fast Python linter and formatter (replaces black, isort, flake8, etc.)
# ===========================================================================
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.2
hooks:
# Linter - run first to catch issues
- id: ruff
args:
- --fix
- --unsafe-fixes
- --exit-non-zero-on-fix
- --show-fixes
types_or: [python, pyi]
# Formatter - run after linting
- id: ruff-format
types_or: [python, pyi]
# ===========================================================================
# MYPY - Static type checking (per-commit on changed files only)
# Was on pre-push, but force-push diffs caused full-repo scans + OOM. On
# pre-commit it sees only the file(s) currently staged → near-instant.
# ===========================================================================
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.13.0
hooks:
- id: mypy
stages: [pre-commit]
args:
- --ignore-missing-imports
- --no-error-summary
- --disable-error-code=no-untyped-def
- --disable-error-code=no-untyped-call
- --disable-error-code=var-annotated
- --disable-error-code=no-any-unimported
- --disable-error-code=type-arg
- --disable-error-code=no-any-return
- --disable-error-code=misc
- --disable-error-code=unused-ignore
- --disable-error-code=unreachable
- --disable-error-code=assignment
- --disable-error-code=no-redef
- --disable-error-code=attr-defined
- --disable-error-code=arg-type
- --disable-error-code=union-attr
- --disable-error-code=call-overload
- --disable-error-code=return-value
- --disable-error-code=redundant-cast
- --disable-error-code=empty-body
- --disable-error-code=list-item
exclude: >-
(?x)^(
Bash/.*|
\.venv/.*|
linux_configuration/scripts/single_use/misc/testsAndMisc-bash/tools/.*
)$
additional_dependencies:
- types-requests
- types-PyYAML
- types-python-dateutil
# ===========================================================================
# PYLINT - Comprehensive Python linter (per-commit on changed files only)
# Was on pre-push, but force-push diffs caused full-repo scans + OOM.
# ===========================================================================
- repo: https://github.com/pylint-dev/pylint
rev: v3.3.2
hooks:
- id: pylint
stages: [pre-commit]
args:
- --rcfile=pyproject.toml
- --fail-under=10
- --jobs=4
additional_dependencies:
- pytest
- python-chess
- requests
- pygame
- pillow
- gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.1.0#subdirectory=gatelock
- anthropic
- rich
- typer
# Test suites and conftest fixtures are linted separately; they use
# patterns (protected-access, missing docstrings, fixture shadowing)
# that don't belong in the source-code 10/10 gate.
exclude: ^(Bash/|\.venv/)|(^|/)(tests/|conftest\.py)
# ===========================================================================
# BANDIT - Security linter (per-commit on changed files only)
# Was on pre-push, but force-push diffs caused full-repo scans + OOM.
# ===========================================================================
- repo: https://github.com/PyCQA/bandit
rev: 1.7.10
hooks:
- id: bandit
stages: [pre-commit]
args:
- -c
- pyproject.toml
- --severity-level=high
- --confidence-level=medium
- --skip=B113
additional_dependencies: ["bandit[toml]"]
exclude: ^(Bash/|\.venv/|tests/|.*test.*\.py$)
# ===========================================================================
# PYTEST + COVERAGE - Run tests and enforce 100% code coverage
# Only tests for subpackages with changed files are run (see script).
# Uses pytest-xdist (-n auto) to parallelize across all CPUs.
# ===========================================================================
- repo: local
hooks:
- id: pytest-coverage
name: pytest with coverage enforcement
entry: python meta/scripts/pytest_changed_packages.py
language: system
types: [python]
pass_filenames: true
require_serial: true
stages: [pre-commit]
- id: requirements-sync
name: requirements.txt covers all python_pkg imports
entry: python3 meta/scripts/check_requirements_sync.py --package python_pkg
language: system
pass_filenames: false
files: ^(python_pkg/.*\.py|requirements\.txt)$
- id: session-autopsy-hook-mutations
name: session_autopsy hook tests are non-vacuous (mutation)
entry: python3 meta/scripts/mutate_session_autopsy_hooks.py
language: system
pass_filenames: false
require_serial: true
# Scoped to the two files that can invalidate the proof: the suite
# itself and the mutation definitions. Takes ~40s, so it must not run
# on unrelated commits. Skips itself where ~/.claude is absent (CI).
files: ^(linux_configuration/tests/test_session_autopsy_hooks\.py|meta/scripts/mutate_session_autopsy_hooks\.py)$
- id: ci-mirror
name: CI mirror (clean-venv install + --all-files + changed-pkg pytest)
entry: bash meta/scripts/ci_mirror.sh
language: system
pass_filenames: false
always_run: true
require_serial: true
stages: [pre-push]
# ===========================================================================
# VULTURE - Dead code detection (disabled - doesn't work well with pre-commit)
# ===========================================================================
# - repo: https://github.com/jendrikseipp/vulture
# rev: v2.13
# hooks:
# - id: vulture
# args:
# - --min-confidence=80
# - --exclude=.venv,Bash,__pycache__
# exclude: ^(Bash/|\.venv/)
# ===========================================================================
# PYUPGRADE - Upgrade Python syntax (disabled - incompatible with Python 3.14)
# ===========================================================================
# - repo: https://github.com/asottile/pyupgrade
# rev: v3.19.0
# hooks:
# - id: pyupgrade
# args:
# - --py310-plus
# ===========================================================================
# CODESPELL - Spell checking in code (expanded ignore list for non-English)
# ===========================================================================
- repo: https://github.com/codespell-project/codespell
rev: v2.3.0
hooks:
- id: codespell
args:
- --skip=*.json,*.lock,*-lock.yaml,*.min.js,*.min.css,.git,__pycache__,.venv,*.txt
- --ignore-words-list=als,ans,ect,nd,som,sur,te,nam,numer,lew,sie,wil,postion,clen,ther,folow,derrive,ony,tje,noe,theses,crate,doubleclick,wile,tabel,pary,blok,bloc,proces,serwer,parametr,adres,hart,dout,metod,tekst,synonim,grup,mosty,lokal,skalar,milion,nowe,tre,hel,alph,iif
exclude: ^(Bash/ffmpeg-build/|LaTeX/|.*\.geojson$)
# ===========================================================================
# DOCFORMATTER - Format docstrings (disabled - causes recursion errors)
# ===========================================================================
# - repo: local
# hooks:
# - id: docformatter
# name: docformatter
# entry: docformatter
# language: system
# types: [python]
# args:
# - --in-place
# - --wrap-summaries=88
# - --wrap-descriptions=88
# ===========================================================================
# INTERROGATE - Docstring coverage (disabled - causes recursion on large files)
# ===========================================================================
# - repo: https://github.com/econchick/interrogate
# rev: 1.7.0
# hooks:
# - id: interrogate
# args:
# - --fail-under=0
# - --verbose
# - --ignore-init-method
# - --ignore-init-module
# - --ignore-magic
# - --ignore-private
# - --ignore-semiprivate
# - --exclude=Bash,.venv,__pycache__
# pass_filenames: false
# ===========================================================================
# AUTOFLAKE - Remove unused imports/variables
# Disabled: fully redundant with ruff (F401, F841, F811) + --fix
# ===========================================================================
# - repo: https://github.com/PyCQA/autoflake
# rev: v2.3.1
# hooks:
# - id: autoflake
# args:
# - --in-place
# - --remove-all-unused-imports
# - --remove-unused-variables
# - --remove-duplicate-keys
# - --expand-star-imports
# ===========================================================================
# SAFETY - Check for security vulnerabilities in dependencies
# ===========================================================================
# Note: Safety requires API key for full functionality, disabled by default
# - repo: https://github.com/Lucas-C/pre-commit-hooks-safety
# rev: v1.3.2
# hooks:
# - id: python-safety-dependencies-check
# files: requirements.*\.txt$
# ===========================================================================
# PYRIGHT - Microsoft's type checker (very strict, optional)
# ===========================================================================
# Uncomment to enable - can be slow and very strict
# - repo: https://github.com/RobertCraiworthy/pyright-action
# rev: v1.1.350
# hooks:
# - id: pyright
# ===========================================================================
# CHECK JSON/YAML/TOML formatting (runs on push only — slow Node.js startup)
# ===========================================================================
- repo: local
hooks:
- id: prettier
name: prettier (capped scope)
# Wrapper runs prettier inside its own systemd-run scope so its
# memory budget is independent of the outer pre-push cgroup that
# has already accumulated page cache from pytest/mypy/pylint.
entry: meta/scripts/run_prettier_capped.sh
language: system
types_or: [yaml, json, markdown]
exclude: >-
(?x)^(
Bash/.*|
\.venv/.*|
third_party/.*|
\.github/skills/.*|
docs/superpowers/(plans|specs)/.*|
linux_configuration/report/.*|
.*\.lock
)$
stages: [pre-push]
# ===========================================================================
# SHELLCHECK - Shell script linting
# Wrapper batches files to avoid OOM on large repos.
# ===========================================================================
- repo: local
hooks:
- id: shellcheck
name: shellcheck
entry: bash -c 'printf "%s\0" "$@" | xargs -0 -n 40 shellcheck --severity=warning' --
language: system
types: [shell]
# shellcheck only understands sh/bash/dash/ksh; it cannot parse zsh
# syntax (e.g. extended globs like (#s)). zsh files are checked by the
# zsh-syntax hook below instead.
exclude_types: [zsh]
# ===========================================================================
# ZSH SYNTAX - zsh -n parse check (shellcheck has no zsh mode)
# ===========================================================================
- repo: local
hooks:
- id: zsh-syntax
name: zsh syntax check
entry: bash -c 'for f in "$@"; do zsh -n "$f" || exit 1; done' --
language: system
types: [zsh]
# ===========================================================================
# CHECK PYTHON LOCATION - All Python files must be under python_pkg/
# ===========================================================================
- repo: local
hooks:
- id: check-python-location
name: check Python files are under python_pkg/
entry: meta/scripts/check_python_location.sh
language: script
types: [python]
# ===========================================================================
# REMOVE EMPTY DIRECTORIES - Clean up empty folders in the repo
# ===========================================================================
- repo: local
hooks:
- id: remove-empty-dirs
name: remove empty directories
entry: find . -type d -empty -not -path './.git/*' -delete -print
language: system
pass_filenames: false
always_run: true
# ===========================================================================
# SECRET PATTERNS - Block commits containing sensitive data
# ===========================================================================
- repo: local
hooks:
- id: check-no-secrets
name: check for leaked secrets
entry: meta/scripts/check_no_secrets.sh
language: script
exclude: ^(\.secret-patterns|\.pre-commit-config\.yaml|.*\.geojson)$
- id: check-no-dufs-conf
name: block dufs secret config (.dufs_cloud.conf)
entry: meta/scripts/check_no_dufs_conf.sh
language: script
always_run: true
pass_filenames: false
# ===========================================================================
# COMMITIZEN - Conventional commits (optional)
# ===========================================================================
# - repo: https://github.com/commitizen-tools/commitizen
# rev: v3.13.0
# hooks:
# - id: commitizen
# - id: commitizen-branch
# stages: [push]