docs(kyberforge): add pre-commit hook research documentation

## Why
Captures structured reference material for pre-commit hooks to support
skill authoring and hook configuration work in the kyberforge plugin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P87CiC58Ru2PPWYTeXtjHT
This commit is contained in:
2026-06-27 21:57:34 +00:00
parent 8b8cb33df6
commit 9f94164177
8 changed files with 1096 additions and 0 deletions

View File

@@ -0,0 +1,152 @@
---
topic: cli-reference
source_keys:
- context7-pre-commit-com
- pre-commit-com
---
## Exit codes
| Code | Meaning |
|------|---------|
| 0 | All hooks passed |
| 1 | Hook(s) failed or files were modified |
| 3 | Unexpected internal error |
| 130 | Interrupted (Ctrl+C) |
## `pre-commit run`
Run hooks against files.
```bash
pre-commit run # staged files only (normal commit flow)
pre-commit run --all-files # entire working tree
pre-commit run check-yaml # single hook by ID
pre-commit run --files path/to/file.py # specific files
pre-commit run --from-ref HEAD~3 --to-ref HEAD # diff range (CI use)
pre-commit run --hook-stage pre-push # specific lifecycle stage
pre-commit run --show-diff-on-failure # print diff when hook fails
pre-commit run --verbose # always show hook output
```
For CI, prefer `--from-ref`/`--to-ref` over `--all-files` on large repos. Cache `$PRE_COMMIT_HOME` keyed on `.pre-commit-config.yaml` hash.
## `pre-commit install`
Wire pre-commit into `.git/hooks/`.
```bash
pre-commit install # default: pre-commit stage
pre-commit install -f # overwrite existing hooks
pre-commit install --install-hooks # pre-create all environments now
pre-commit install -t pre-commit -t pre-push # multiple stages
pre-commit install --allow-missing-config # silently skip if no config file
```
Must be run once per clone. Re-run after changing `default_install_hook_types`.
## `pre-commit uninstall`
```bash
pre-commit uninstall
pre-commit uninstall -t pre-push
```
## `pre-commit autoupdate`
Update `rev` values in `.pre-commit-config.yaml` to the latest tag.
```bash
pre-commit autoupdate # latest tag for all repos
pre-commit autoupdate --bleeding-edge # use default branch HEAD
pre-commit autoupdate --freeze # pin to commit SHA (reproducible)
pre-commit autoupdate --repo https://github.com/pre-commit/pre-commit-hooks # single repo
pre-commit autoupdate -j 4 # parallel fetches (v3.3.0+)
```
This modifies `.pre-commit-config.yaml` in-place. A skill updating an existing config should call this rather than manually editing `rev` values.
## `pre-commit validate-config`
Validate `.pre-commit-config.yaml` schema. Non-zero exit means the file is invalid.
```bash
pre-commit validate-config
pre-commit validate-config path/to/other-config.yaml
```
A skill must call this after writing or modifying a config before considering the task complete.
## `pre-commit validate-manifest`
Validate a `.pre-commit-hooks.yaml` hook definition file.
```bash
pre-commit validate-manifest .pre-commit-hooks.yaml
```
## `pre-commit try-repo`
Test a hook repo without adding it to the config.
```bash
pre-commit try-repo https://github.com/pre-commit/pre-commit-hooks
pre-commit try-repo ../local-hook-repo --all-files --verbose
pre-commit try-repo ../hook-repo specific-hook-id --verbose
```
Supports all `run` options. Use this in a skill to smoke-test a hook before writing it to config.
## `pre-commit sample-config`
Print a minimal starter config to stdout.
```bash
pre-commit sample-config > .pre-commit-config.yaml
```
## `pre-commit install-hooks`
Pre-create all hook environments without running any hooks.
```bash
pre-commit install-hooks
```
## `pre-commit gc`
Remove unused cached hook environments (safe to run at any time).
```bash
pre-commit gc
```
## `pre-commit clean`
Wipe all cached environments. Forces full rebuild on next run.
```bash
pre-commit clean
```
## `SKIP` environment variable
Skip specific hooks by ID without modifying config. Comma-separated, exact IDs, no spaces.
```bash
SKIP=flake8,check-yaml git commit -m "wip"
```
## CI recipe
```yaml
# GitHub Actions
- name: Cache pre-commit envs
uses: actions/cache@v3
with:
path: ~/.cache/pre-commit
key: pre-commit|${{ hashFiles('.pre-commit-config.yaml') }}
- name: Run pre-commit
run: pre-commit run --from-ref ${{ github.event.pull_request.base.sha }} --to-ref HEAD
```

View File

@@ -0,0 +1,206 @@
---
topic: configuration
source_keys:
- context7-pre-commit-com
- pre-commit-com
---
## `.pre-commit-config.yaml` structure
### Top-level keys
| Key | Type | Default | Purpose |
|-----|------|---------|---------|
| `repos` | List | required | List of repo blocks |
| `default_install_hook_types` | List | `[pre-commit]` | Hook types installed when `pre-commit install` is run without `-t` |
| `default_language_version` | Dict | `{}` | Maps language name → version string; overrides per-hook defaults |
| `default_stages` | List | all stages | Applied to every hook unless the hook specifies `stages` |
| `files` | Regex string | `''` | Global include pattern applied before hook-level filters |
| `exclude` | Regex string | `^$` | Global exclude pattern |
| `fail_fast` | Boolean | `false` | Stop after the first failing hook |
| `minimum_pre_commit_version` | String | `'0'` | Minimum pre-commit version required to use this config |
### Repo block keys
| Key | Required | Description |
|-----|----------|-------------|
| `repo` | Yes | Git URL, or the special values `local` or `meta` |
| `rev` | Yes (not for `local`/`meta`) | Tag or SHA — must be immutable |
| `hooks` | Yes | List of hook override blocks |
### Hook override block keys
These keys appear under `hooks:` inside a repo block. All are optional overrides of the hook's upstream manifest values.
| Key | Type | Description |
|-----|------|-------------|
| `id` | String (required) | Hook identifier — must match an `id` in the repo's `.pre-commit-hooks.yaml` |
| `alias` | String | Extra name for targeting with `pre-commit run <alias>` |
| `name` | String | Override the display name |
| `language_version` | String | Override language version |
| `files` | Regex string | Override file include pattern (appended to upstream with AND logic) |
| `exclude` | Regex string | Override file exclude pattern |
| `types` | List | AND-logic type filter (all tags must match) |
| `types_or` | List | OR-logic type filter (any tag must match) |
| `exclude_types` | List | Exclude files matching these types |
| `args` | List | Additional CLI arguments prepended before filenames |
| `stages` | List | Which git stages trigger this hook |
| `additional_dependencies` | List | Extra packages to install into hook environment |
| `always_run` | Boolean | Run even if no files match the filter |
| `verbose` | Boolean | Always print output (not only on failure) |
| `log_file` | String | Write output to this file path on failure |
### Complete annotated example
```yaml
minimum_pre_commit_version: '3.0.0'
fail_fast: false
default_language_version:
python: python3.11
default_stages: [pre-commit, pre-push]
exclude: ^vendor/
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: end-of-file-fixer
- id: check-yaml
- id: check-json
- id: pretty-format-json
args: [--autofix]
- id: trailing-whitespace
exclude: ^tests/fixtures/
- repo: local
hooks:
- id: validate-manifest
name: Validate marketplace manifest
entry: python scripts/validate_manifest.py
language: python
files: ^\.claude-plugin/marketplace\.json$
always_run: false
- repo: meta
hooks:
- id: check-hooks-apply
- id: check-useless-excludes
```
### Multi-line exclude pattern (readable for long lists)
```yaml
- id: my-hook
exclude: |
(?x)^(
path/to/file1.py|
path/to/file2.py|
path/to/generated/.*
)$
```
## Stages
Valid `stages` values: `pre-commit`, `pre-push`, `commit-msg`, `prepare-commit-msg`, `post-checkout`, `post-commit`, `post-merge`, `post-rewrite`, `pre-merge-commit`, `pre-rebase`, `manual`.
The `manual` stage only runs when explicitly invoked: `pre-commit run --hook-stage manual <hookid>`.
To install hooks for non-default stages, pass them to install:
```bash
pre-commit install -t pre-commit -t pre-push -t commit-msg
```
Or declare them in config so they install automatically:
```yaml
default_install_hook_types: [pre-commit, pre-push, commit-msg]
```
## `types` vs `types_or` vs `files`
- `types: [json, text]` — file must carry ALL listed tags (AND)
- `types_or: [javascript, typescript]` — file must carry ANY listed tag (OR)
- `files: \.py$` — regex applied via `re.search()` (not full match) on the file path
- `exclude: \.generated\.py$` — regex excludes matching paths after `files` matches
Both `types` and `files` filters must pass for a file to be processed. Use `identify-cli <file>` to see what tags a file has.
## Local hooks (`repo: local`)
No external repository required. Required fields: `id`, `name`, `language`, `entry`.
```yaml
- repo: local
hooks:
# System tool already installed (don't let pre-commit manage env)
- id: shellcheck
name: shellcheck
entry: shellcheck
language: unsupported # formerly "system"
types: [shell]
# Script in the repo
- id: run-tests
name: Run test suite
entry: bash tests/run-tests.sh
language: unsupported_script # formerly "script"
pass_filenames: false
always_run: true
stages: [pre-push]
# Always-fail guard (lightweight, no env needed)
- id: no-dotenv
name: No .env files
entry: .env files must not be committed
language: fail
files: \.env$
# Python with managed dependencies
- id: my-checker
name: My Python Checker
entry: python -m mymodule.checker
language: python
additional_dependencies: [requests==2.28.0]
types: [python]
```
### Language choices for local hooks
| Language | Description |
|----------|-------------|
| `unsupported` / `system` | Runs executable from system PATH — pre-commit does not manage environment |
| `unsupported_script` / `script` | Runs a script path relative to repo root |
| `fail` | Always fails; `entry` text becomes the error message — good for forbidden file patterns |
| `python` | Creates isolated venv; `additional_dependencies` are pip-installed |
| `node` | Creates isolated node env |
| `ruby`, `golang`, `rust`, `docker`, `docker_image` | Language-specific isolated envs |
### `pass_filenames` behavior
`true` (default): `entry arg1 arg2 file1 file2 file3`
`false`: `entry arg1 arg2` — hook gets no filenames; use for repo-wide or stateful checks.
## Meta hooks (`repo: meta`)
```yaml
- repo: meta
hooks:
- id: check-hooks-apply # each hook must match ≥1 file — catches dead hooks
- id: check-useless-excludes # each exclude must exclude ≥1 file — catches dead excludes
- id: identity # debug: prints every filename passed to pre-commit
```
## Hazmat helpers (v4.5.0+)
Entry-point prefixes for edge cases:
```yaml
# Change directory before running (monorepo)
entry: pre-commit hazmat cd subdir my-bin --
# Treat non-zero exit as warning instead of failure
entry: pre-commit hazmat ignore-exit-code my-bin --
verbose: true
# Run hook once per file (not batched)
entry: pre-commit hazmat n1 my-bin --
```

View File

@@ -0,0 +1,220 @@
---
topic: examples
source_keys:
- context7-pre-commit-com
- pre-commit-com
- context7-pre-commit-hooks
---
## Starter config
```bash
pre-commit sample-config > .pre-commit-config.yaml
pre-commit validate-config
pre-commit install
pre-commit run --all-files
```
## Common config patterns
### File hygiene only
```yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
- id: check-yaml
- id: check-json
- id: check-toml
- id: check-merge-conflict
- id: detect-private-key
```
### With auto-formatting
```yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
- id: pretty-format-json
args: [--autofix]
- id: mixed-line-ending
args: [--fix=lf]
```
### With branch protection and secret scanning
```yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: detect-private-key
- id: detect-aws-credentials
- id: no-commit-to-branch
args: [--branch, main, --branch, master]
- id: check-added-large-files
args: [--maxkb=500]
```
### Multi-stage config (pre-commit + pre-push + commit-msg)
```yaml
default_install_hook_types: [pre-commit, pre-push, commit-msg]
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: end-of-file-fixer
stages: [pre-commit]
- id: check-yaml
stages: [pre-commit]
- repo: https://github.com/compilerla/conventional-pre-commit
rev: v2.4.0
hooks:
- id: conventional-pre-commit
stages: [commit-msg]
- repo: local
hooks:
- id: run-tests
name: Run test suite
entry: bash tests/run-tests.sh
language: unsupported_script
pass_filenames: false
always_run: true
stages: [pre-push]
```
### Kubernetes-style YAML (needs --unsafe for custom tags)
```yaml
- id: check-yaml
args: ['--unsafe']
exclude: ^helm/templates/
```
### Monorepo with subdirectory scope
```yaml
- repo: local
hooks:
- id: validate-frontend
name: Validate frontend
entry: pre-commit hazmat cd frontend npm run lint --
language: unsupported
files: ^frontend/
pass_filenames: false
```
## Testing and validation workflow
```bash
# After writing a new config:
pre-commit validate-config
# Test all hooks against entire repo:
pre-commit run --all-files
# Test a single hook:
pre-commit run check-yaml --all-files
# Test a hook repo before adding it to config:
pre-commit try-repo https://github.com/psf/black --all-files
# Update all rev pins to latest tags:
pre-commit autoupdate
# Freeze revs to commit SHAs for reproducibility:
pre-commit autoupdate --freeze
# Skip a hook for one commit:
SKIP=check-yaml git commit -m "add yaml with custom tags"
# Run manually against a diff (CI):
pre-commit run --from-ref origin/main --to-ref HEAD
```
## Local hook patterns
### Forbidden file guard (cheapest possible hook)
```yaml
- repo: local
hooks:
- id: no-env-files
name: No .env files
entry: .env files must not be committed — use environment variables
language: fail
files: \.env(\..+)?$
```
### Validate a generated file
```yaml
- repo: local
hooks:
- id: validate-manifest
name: Validate plugin manifest
entry: bash scripts/check-manifests.sh
language: unsupported_script
files: ^\.claude-plugin/
pass_filenames: false
always_run: false
```
### Run full test suite pre-push
```yaml
- repo: local
hooks:
- id: run-tests
name: Run test suite
entry: bash tests/run-tests.sh
language: unsupported_script
pass_filenames: false
always_run: true
stages: [pre-push]
```
### Validate SKILL.md frontmatter (inline bash, as used in this repo)
```yaml
- repo: local
hooks:
- id: skill-frontmatter
name: SKILL.md frontmatter validation
entry: bash
language: system
files: 'SKILL\.md$'
args:
- -c
- |
for f in "$@"; do
if [[ -f "$f" ]]; then
if ! grep -q "^name:" "$f" || ! grep -q "^description:" "$f"; then
echo "ERROR: $f missing required frontmatter (name: and description:)"
exit 1
fi
fi
done
```
## Adding meta-validation
Add after all other repos to catch dead hooks/excludes:
```yaml
- repo: meta
hooks:
- id: check-hooks-apply
- id: check-useless-excludes
```

View File

@@ -0,0 +1,156 @@
---
topic: hook-authoring
source_keys:
- context7-pre-commit-com
- pre-commit-com
---
## What this covers
How to author a hook that lives in its own git repo (shareable), vs. a local hook that lives in the consuming repo. A pre-commit skill/agent needs to know both: it may create local hooks inline in the config, or scaffold a proper hook repo.
## Hook definition file: `.pre-commit-hooks.yaml`
Required in any git repo that others consume as a pre-commit hook source. Lives at the repo root.
```yaml
- id: my-hook
name: My Hook
description: One-line description shown in pre-commit output.
entry: my-hook-script # executable name on PATH, or path relative to repo root
language: python # controls how pre-commit installs the environment
types: [text] # file type filter (AND logic)
files: '' # regex filter on file path (re.search)
exclude: ^$ # regex exclusion on file path
args: [] # default arguments
pass_filenames: true # append matched filenames after args
always_run: false # run even with 0 matched files
require_serial: false # run in parallel by default
fail_fast: false # stop other hooks if this fails
verbose: false # always print stdout/stderr (not only on failure)
stages: [pre-commit] # git lifecycle stages that trigger this hook
additional_dependencies: [] # packages installed into hook environment
minimum_pre_commit_version: '0'
language_version: default # e.g. 'python3.11', 'node18'
```
Validate with: `pre-commit validate-manifest .pre-commit-hooks.yaml`
## Language types and what they control
| Language | Environment | When to use |
|----------|-------------|-------------|
| `python` | Isolated venv | Pure Python hook; `additional_dependencies` are pip packages |
| `node` | Isolated node_modules | JS/TS hook; `additional_dependencies` are npm packages |
| `golang` | Builds from source | Go hook; `additional_dependencies` are Go module paths |
| `ruby` | Isolated gem env | Ruby hook; `additional_dependencies` are gems |
| `rust` | Cargo build | Rust hook |
| `docker` | Docker image built from `entry` | Use when no other language fits |
| `docker_image` | Pulls Docker image by `entry` | When image is pre-built |
| `unsupported` / `system` | No environment — runs from system PATH | When tool is pre-installed on host |
| `unsupported_script` / `script` | No environment — runs repo-relative path | Scripts committed to the hook repo |
| `fail` | No environment — always exits 1 | Forbidden file guards |
| `conda` | Conda environment | Conda-native hooks |
| `coursier` | Coursier (Scala/JVM) | JVM hooks |
## `entry` field
`entry` is the executable invoked. It is resolved differently by language:
- `python`/`node`/etc.: the installed script name (what's in `scripts:` in `setup.cfg` or `package.json`)
- `system`/`unsupported`: resolved from system `PATH`
- `script`/`unsupported_script`: path relative to the hook repo root
- `fail`: the `entry` string is printed as the error message
Arguments in `entry` are supported: `entry: python -m mymodule.cli` works.
## `pass_filenames` and argument ordering
When `pass_filenames: true` (default), pre-commit calls:
```
entry <args from manifest> <args from config override> <matched file1> <file2> ...
```
When `pass_filenames: false`, pre-commit calls:
```
entry <args from manifest> <args from config override>
```
Use `pass_filenames: false` for:
- Hooks that check the repo state as a whole (test runners, manifest validators)
- Hooks whose tool only accepts one file at a time (combine with `require_serial: true` or use `pre-commit hazmat n1`)
## `require_serial`
Default is `false` — pre-commit batches files and runs hook processes in parallel. Set `true` when:
- The hook reads/writes shared state (a database, a lock file)
- The tool cannot handle concurrent invocations
- The tool must process all files in a single process call but `pass_filenames: false` is not appropriate
## Testing a hook during development
```bash
# Test without adding to any consuming repo's config:
pre-commit try-repo . --all-files --verbose
pre-commit try-repo . specific-hook-id --verbose
# Test on a specific file:
pre-commit try-repo . my-hook --files path/to/file.py --verbose
```
`try-repo .` runs from the hook repo's own directory. Use a path to the hook repo from the consuming repo.
## Minimum viable hook repo structure
```
my-hook-repo/
.pre-commit-hooks.yaml # hook manifest
hooks/
my_hook.py # hook script
setup.cfg # (if python) declares scripts entry point
pyproject.toml # (if python) build config
```
`setup.cfg` entry point example:
```ini
[options.entry_points]
console_scripts =
my-hook = hooks.my_hook:main
```
## Local hooks (no separate repo)
For hooks that belong to the consuming repo and don't need to be shared:
```yaml
- repo: local
hooks:
- id: my-local-check
name: My local check
entry: ./scripts/check.sh
language: unsupported_script
pass_filenames: false
always_run: true
```
No `.pre-commit-hooks.yaml` needed. All fields that would normally come from the manifest must be declared inline in the config.
## Inline bash hook (entry splits args)
```yaml
- repo: local
hooks:
- id: validate-frontmatter
name: Validate frontmatter
entry: bash
language: system
files: 'SKILL\.md$'
args:
- -c
- |
for f in "$@"; do
grep -q "^name:" "$f" || { echo "Missing name: in $f"; exit 1; }
done
pass_filenames: true
```
Note: `entry: bash` + `args: [-c, <script>]` means pre-commit calls `bash -c <script> -- file1 file2`. The `--` and `$@` pattern is important — without it, filenames are not available inside the script.

View File

@@ -0,0 +1,174 @@
---
topic: hooks-reference
source_keys:
- context7-pre-commit-hooks
- pre-commit-hooks-github
---
## pre-commit-hooks (official collection)
Repo: `https://github.com/pre-commit/pre-commit-hooks`
Latest version: `v6.0.0`
Pinned in this repo: `v4.5.0` — consider running `pre-commit autoupdate`
```yaml
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: <hook-id>
```
---
## File syntax / content checks
| Hook ID | What it does | Key args |
|---------|-------------|----------|
| `check-ast` | Validates Python files parse as valid AST | — |
| `check-json` | Validates JSON parses correctly | — |
| `check-toml` | Validates TOML parses correctly | — |
| `check-xml` | Validates XML parses correctly | — |
| `check-yaml` | Validates YAML parses correctly | `--allow-multiple-documents`, `--unsafe` (syntax-only, enables custom tags) |
| `check-merge-conflict` | Detects unresolved merge markers (`<<<<<<<`) | `--assume-in-merge` |
---
## Filesystem / naming / encoding checks
| Hook ID | What it does | Key args |
|---------|-------------|----------|
| `check-added-large-files` | Blocks files over size threshold | `--maxkb=N` (default 500), `--enforce-all` |
| `check-case-conflict` | Detects filenames differing only by case (macOS/Windows hazard) | — |
| `check-executables-have-shebangs` | Ensures executable files have a shebang | — |
| `check-shebang-scripts-are-executable` | Ensures files with shebangs are executable | — |
| `check-illegal-windows-names` | Detects filenames illegal on Windows | — |
| `check-symlinks` | Detects broken symlinks | — |
| `destroyed-symlinks` | Catches symlinks converted to regular files (common on Windows) | — |
| `fix-byte-order-marker` | Removes UTF-8 BOM | — |
---
## Security
| Hook ID | What it does | Key args |
|---------|-------------|----------|
| `detect-private-key` | Blocks PEM private key material | — |
| `detect-aws-credentials` | Blocks AWS credentials (reads `~/.aws/credentials`) | `--credentials-file PATH` (repeatable), `--allow-missing-credentials` |
---
## Git-related
| Hook ID | What it does | Key args |
|---------|-------------|----------|
| `no-commit-to-branch` | Blocks commits to protected branches | `-b`/`--branch NAME` (repeatable, default: `main`+`master`), `-p`/`--pattern REGEX` |
| `check-vcs-permalinks` | Ensures GitHub links are SHAs not branch refs | `--additional-github-domain DOMAIN` |
| `forbid-new-submodules` | Blocks adding new git submodules | — |
| `forbid-submodules` | Blocks any submodule in the repo | — |
Note: `no-commit-to-branch` runs with `always_run: true` by default — it ignores `files`/`types` filters.
---
## Whitespace / line-ending fixers
| Hook ID | What it does | Key args |
|---------|-------------|----------|
| `end-of-file-fixer` | Ensures files end with exactly one newline | — |
| `trailing-whitespace` | Removes trailing whitespace | `--markdown-linebreak-ext=md` (preserve ` ` line breaks), `--chars` |
| `mixed-line-ending` | Normalises line endings | `--fix=auto` (default), `--fix=lf`, `--fix=crlf`, `--fix=no` (check only) |
---
## Formatters / sorters
| Hook ID | What it does | Key args |
|---------|-------------|----------|
| `pretty-format-json` | Formats JSON; fails if not already formatted | `--autofix` (fix in place), `--indent N`, `--no-sort-keys`, `--no-ensure-ascii`, `--top-keys k1,k2` |
| `requirements-txt-fixer` | Sorts and deduplicates `requirements.txt` and `constraints.txt` | — |
| `file-contents-sorter` | Sorts lines in user-specified files alphabetically | `--ignore-case`, `--unique`; no files matched by default — must set `files:` |
| `sort-simple-yaml` | Sorts top-level keys in simple YAML files | No files matched by default — must set `files:` |
---
## Python-specific
| Hook ID | What it does | Key args |
|---------|-------------|----------|
| `check-builtin-literals` | Requires literal syntax for empty built-ins (`[]` not `list()`) | `--ignore=type1,type2`, `--no-allow-dict-kwargs` |
| `debug-statements` | Detects `import pdb`, `breakpoint()`, etc. | — |
| `double-quote-string-fixer` | Converts double-quoted strings to single-quoted | — |
| `name-tests-test` | Enforces test file naming convention | `--pytest` (default: `.*_test.py`), `--pytest-test-first` (`test_.*.py`), `--django`/`--unittest` |
---
## Deprecated hooks
| Hook ID | Replacement |
|---------|------------|
| `check-byte-order-marker` | Use `fix-byte-order-marker` |
| `fix-encoding-pragma` | Use `pyupgrade` |
| `check-docstring-first` | Deprecated without replacement |
---
## Argument examples
### `check-yaml` with Kubernetes manifests
```yaml
- id: check-yaml
args: ['--unsafe'] # needed for !Tag syntax used by k8s/Helm
```
### `no-commit-to-branch`
```yaml
- id: no-commit-to-branch
args: [--branch, main, --branch, master, --branch, production]
```
### `pretty-format-json` auto-fix
```yaml
- id: pretty-format-json
args: [--autofix, --indent, '2', --no-sort-keys]
```
### `sort-simple-yaml` opt-in (no files matched by default)
```yaml
- id: sort-simple-yaml
files: ^config/simple/
```
### `trailing-whitespace` preserving Markdown line breaks
```yaml
- id: trailing-whitespace
args: ['--markdown-linebreak-ext=md']
```
---
## Hooks in this repo (`.pre-commit-config.yaml`)
From `pre-commit/pre-commit-hooks@v4.5.0` (latest is `v6.0.0`):
| Hook | Stage | Notes |
|------|-------|-------|
| `end-of-file-fixer` | pre-commit | |
| `check-json` | pre-commit | |
| `pretty-format-json` | pre-commit | No `--autofix` — fails on unformatted JSON, does not fix |
| `check-yaml` | pre-commit | |
| `trailing-whitespace` | pre-commit | |
Local hooks in this repo:
| Hook | Stage | Entry |
|------|-------|-------|
| `run-tests` | pre-push | `bash tests/run-tests.sh` |
| `check-manifests` | pre-push | `bash scripts/check-manifests.sh` |
| `validate-plugins` | pre-push | `claude plugin validate --strict` per plugin dir |
| `validate-marketplace` | pre-push | `claude plugin validate --strict .claude-plugin/marketplace.json` |
| `skill-frontmatter` | pre-commit | Validates SKILL.md has `name:` and `description:` |

View File

@@ -0,0 +1,49 @@
---
topic: overview
source_keys:
- context7-pre-commit-com
- pre-commit-com
---
## What pre-commit is
Pre-commit is a framework for managing and executing git hooks. Hooks run automatically at specific git lifecycle points (pre-commit, pre-push, commit-msg, etc.) against staged or changed files. Each hook is pulled from a versioned external git repo; pre-commit clones and caches it in `~/.cache/pre-commit` (or `$PRE_COMMIT_HOME`) and manages isolated execution environments per hook. No system-wide language runtimes are required for most hooks.
## Key concepts
**Config file:** `.pre-commit-config.yaml` at the repo root. This is the single source of truth for all hooks.
**Hook repos vs local hooks:** Most hooks live in external git repos (pinned by `rev`). Local hooks (`repo: local`) live in the same repo and run system tools or scripts directly.
**`rev` must be immutable:** Always use a tag or commit SHA, never a branch. `autoupdate` will not work correctly with branches.
**File targeting:** Hooks receive only the files that match their `files` regex AND `types` filter. `pre-commit run --all-files` bypasses staging and runs against all files in the tree.
**Staged files only by default:** When run via git hooks, pre-commit passes only staged files. This means fixers (e.g. `trailing-whitespace`) modify files but the commit is blocked — the user must re-stage and recommit.
**`PRE_COMMIT=1`** is set in the environment whenever a hook is executing (since v2.5.0). Hooks can use this to detect they are running under pre-commit.
**`identify` library** determines file types. Inspect what tags a file has with `identify-cli <file>`. Types include: `file`, `text`, `binary`, `executable`, `python`, `json`, `yaml`, `shell`, `javascript`, `typescript`, `markdown`, `toml`, `xml`, etc.
## Mental model for a skill/agent
A pre-commit agent operates on three artefacts:
1. `.pre-commit-config.yaml` — the config it creates or modifies
2. `.pre-commit-hooks.yaml` — a hook manifest if the agent is also authoring hooks in the repo
3. The git hooks in `.git/hooks/` — installed by `pre-commit install`
The safe workflow for a skill:
1. Write or modify `.pre-commit-config.yaml`
2. Run `pre-commit validate-config` — abort if non-zero
3. Run `pre-commit run --all-files` — surface hook failures
4. Run `pre-commit autoupdate` if updating `rev` values
5. Run `pre-commit install` once to wire hooks into git
## Cache and environment
Default cache: `~/.cache/pre-commit` or `$XDG_CACHE_HOME/pre-commit`.
Override: `export PRE_COMMIT_HOME=/path/to/cache`.
Pre-create all environments: `pre-commit install-hooks`.
Wipe and rebuild: `pre-commit clean`.
Remove unused only: `pre-commit gc`.

View File

@@ -0,0 +1,29 @@
# Sources
## context7-pre-commit-com
- **URL:** context7:/pre-commit/pre-commit.com
- **Description:** Official pre-commit.com documentation — installation, configuration schema, CLI reference, hook authoring, advanced features, troubleshooting
- **Contributing files:** overview.md, configuration.md, cli-reference.md, hook-authoring.md, examples.md, troubleshooting.md
- **Status:** `extracted`
## context7-pre-commit-hooks
- **URL:** context7:/pre-commit/pre-commit-hooks
- **Description:** Official pre-commit-hooks collection — all available hook IDs with options and examples
- **Contributing files:** hooks-reference.md, examples.md
- **Status:** `extracted`
## pre-commit-com
- **URL:** https://pre-commit.com/
- **Description:** Pre-commit framework homepage — full docs covering install, config, CLI, hook authoring, stages, local hooks, meta hooks, hazmat helpers, CI integration
- **Contributing files:** overview.md, configuration.md, cli-reference.md, hook-authoring.md, examples.md, troubleshooting.md
- **Status:** `extracted`
## pre-commit-hooks-github
- **URL:** https://raw.githubusercontent.com/pre-commit/pre-commit-hooks/main/README.md
- **Description:** Official pre-commit-hooks README — complete hook listing with all args, categories, deprecated hooks, and latest version (v6.0.0)
- **Contributing files:** hooks-reference.md
- **Status:** `extracted`

View File

@@ -0,0 +1,110 @@
---
topic: troubleshooting
source_keys:
- context7-pre-commit-com
- pre-commit-com
---
## Common issues
### Hooks don't run on `git commit`
`pre-commit install` was never run in this clone. Run it. Git hooks are per-clone — they are not committed.
### Hook modified files but commit was blocked
Expected behavior. The hook fixed files, so the staged version is now stale. Re-stage the modified files and commit again.
```bash
git add -u
git commit -m "same message"
```
### `SKIP` not working
The value must be the exact `id` field from the hook definition. Comma-separated, no spaces.
```bash
SKIP=check-yaml,trailing-whitespace git commit -m "msg" # correct
SKIP=check-yaml, trailing-whitespace git commit -m "msg" # wrong — space after comma
```
### Hook runs but matches wrong files (or no files)
`files:` uses `re.search()`, not a full-string match. `\.py$` matches any path ending in `.py`. To match only repo root: `^[^/]+\.py$`.
Use `identify-cli <filename>` to see exactly what type tags a file has, then verify your `types:` filter.
### Hook environment is stale or broken
```bash
pre-commit clean # wipe all environments
pre-commit install-hooks # rebuild everything
```
Or clean just a specific repo:
```bash
pre-commit gc # remove only unused environments
```
### `rev` is a branch name — `autoupdate` broke it
Branch refs are mutable; pre-commit resolves them at install time and then they drift. Always use a tag or commit SHA. Fix:
```bash
pre-commit autoupdate # finds the latest tag and rewrites rev in place
```
### `validate-config` returns an error
Schema violation in the YAML. Common causes:
- Missing `id` under a hook block
- Missing `rev` under a non-local repo block
- `repo: local` hook missing required `language` or `entry` fields
- Indentation error (YAML parsed but pre-commit schema rejected it)
### `check-hooks-apply` fails
A hook's `files`/`types` filter matches zero files in the repo. Either broaden the filter or remove the hook. This is a sign the hook is dead weight.
### `check-useless-excludes` fails
An `exclude` pattern matches no files. Remove or fix it.
### SSH cloning fails in CI
Export `SSH_AUTH_SOCK` in the CI environment, or use HTTPS URLs for hook repos.
### HTTP proxy needed
```bash
export http_proxy=http://proxy.example.com:3128
export https_proxy=http://proxy.example.com:3128
export no_proxy=localhost,127.0.0.1
```
### Hook too slow in CI
- Check `require_serial: false` (default) — hooks run in parallel by default
- Cache `$PRE_COMMIT_HOME` keyed on the hash of `.pre-commit-config.yaml`
- Use `--from-ref`/`--to-ref` instead of `--all-files` to only check changed files
### `language: system` deprecated warning
Renamed to `language: unsupported`. Old name still works as an alias but triggers a deprecation warning on newer versions.
### `pre-commit install -f` wiped my existing hooks
`-f` overwrites `.git/hooks/pre-commit` unconditionally. Without `-f`, pre-commit migrates the existing hook so both run. Only use `-f` deliberately.
### `pretty-format-json` fails but doesn't fix
`pretty-format-json` only fixes in-place when `args: [--autofix]` is passed. Without it, the hook just fails. Add `--autofix` to have it modify the file (the commit will then be blocked until you re-stage).
## Skill/agent-specific gotchas
- Always call `pre-commit validate-config` after writing or modifying config — do not assume valid YAML is valid pre-commit schema.
- `pre-commit autoupdate` modifies the config file in-place. If a skill calls it, re-read the file to get updated `rev` values for display/logging.
- Local hooks with `language: unsupported_script` require the `entry` script to be executable. If the skill creates the script, `chmod +x` it.
- The `stages` key in a hook override must match what was set in `default_install_hook_types` (or the `-t` flags passed to `pre-commit install`), otherwise the hook will never run.
- `always_run: true` combined with `pass_filenames: false` is the correct pattern for repo-wide validators (test suites, manifest checks) that do not operate on individual files.