Prek: Git Hook Automation¶
prek is a fast Git hook runner written in Rust. In Zenith, it validates
code hygiene, detects secrets, verifies schemas, and enforces formatting before
commits are created.
What It Does¶
prek manages Git hook lifecycles. It intercepts git commit commands to run
automated inspections across staged files.
Zenith configures prek through prek.toml with multiple validation layers:
- File Sanitization: Trims trailing whitespace, fixes end-of-file newlines, validates merge and case conflicts, and blocks oversized files.
- Secret Detection: Runs
gitleaksto detect hardcoded credentials, API keys, and sensitive tokens. - Manifest Validation: Runs
validate-pyprojectto ensurepyproject.tomlschema integrity. - Code Formatters: Runs
ruff,taplo,rumdl, andprettierdirectly on staged files. - Local Quality Gates: Executes
pyrefly,complexipy, andtachto ensure code meets strict typing, cognitive complexity, and layer boundary standards before leaving the workstation.
Why It's in Zenith¶
Zenith standardizes on modern, fast, Rust-native developer tooling. Unlike
traditional Python hook runners, prek executes hooks in milliseconds without
Python interpreter startup latency or global virtual environment overhead.
prek establishes an automated local barrier that prevents defective code,
unformatted files, and accidental secret disclosures from ever entering the Git
commit history.
Configuration in Zenith¶
Configured in prek.toml at the project root:
# ==============================================================================
# prek Configuration (prek.toml)
#
# Tool: prek (https://prek.j178.dev)
# Purpose: Fast Git hook framework written in Rust.
# Why it's in Zenith: Validates file integrity, detects secrets, formats TOML,
# and checks code formatting and typing prior to every git commit.
# ==============================================================================
#:schema https://www.schemastore.org/prek.json
default_install_hook_types = ["pre-commit", "pre-push"]
default_stages = ["pre-commit"]
fail_fast = false
# Hooks with matching priorities run concurrently. Keep file-mutating stages
# separate when their inputs or outputs can overlap.
[priorities]
checks = 7
end_of_file = 2
independent_formatters = 4
line_endings = 0
lockfile = 3
python_format = 6
source_fix = 5
trailing_whitespace = 1
[[repos]]
hooks = [
{ id = "check-case-conflict", name = "🛠️ prek · Check for case conflicts", priority = "checks" },
{ id = "check-merge-conflict", name = "🛠️ prek · Check for merge conflicts", priority = "checks" },
{ id = "check-yaml", name = "🛠️ prek · Check YAML files", priority = "checks" },
{ id = "end-of-file-fixer", name = "🛠️ prek · Fix end of file", priority = "end_of_file" },
{ id = "mixed-line-ending", name = "🛠️ prek · Check mixed line endings", priority = "line_endings" },
{ id = "trailing-whitespace", name = "🛠️ prek · Check trailing whitespace", priority = "trailing_whitespace" },
{ id = "check-json", name = "🛠️ prek · Check JSON files", priority = "checks" },
{ id = "check-added-large-files", name = "🛠️ prek · Check for large files", args = [
"--maxkb=2000",
], priority = "checks" },
{ id = "check-xml", name = "🛠️ prek · Check XML files", priority = "checks" },
{ id = "check-toml", name = "🛠️ prek · Check TOML files", priority = "checks" },
{ id = "pretty-format-json", name = "🛠️ prek · Check and fix JSON files", args = [
"--autofix",
"--indent=4",
"--no-sort-keys",
], priority = "independent_formatters" },
]
repo = "https://github.com/pre-commit/pre-commit-hooks"
rev = "v6.0.0"
[[repos]]
hooks = [
{ id = "gitleaks", name = "🔒 security · Detect hardcoded secrets", priority = "checks" },
]
repo = "https://github.com/gitleaks/gitleaks"
rev = "v8.30.0"
[[repos]]
hooks = [
{ id = "validate-pyproject", name = "🐍 validate-pyproject · Validate pyproject.toml", additional_dependencies = [
"validate-pyproject-schema-store[all]",
], priority = "checks" },
]
repo = "https://github.com/abravalheri/validate-pyproject"
rev = "0.26"
[[repos]]
hooks = [
{ id = "ruff-check", name = "🐍 ruff · Check Python code", args = [
"--fix",
"--config",
"./ruff.toml",
], files = '\.(py|pyi)$', priority = "source_fix" },
{ id = "ruff-format", name = "🐍 ruff · Format Python code", args = [
"--config",
"./ruff.toml",
], priority = "python_format" },
]
repo = "https://github.com/astral-sh/ruff-pre-commit"
rev = "v0.17.0"
[[repos]]
hooks = [
{ id = "rumdl-fmt", name = "📝 rumdl · Format Markdown files", types = [
"text",
], files = '\.md$', additional_dependencies = [
"ruff>=0.17.0",
], priority = "source_fix" },
]
repo = "https://github.com/rvben/rumdl-pre-commit"
rev = "v0.2.75"
[[repos]]
hooks = [
{ id = "taplo-format", name = "⚙️ taplo · Format TOML files", priority = "independent_formatters" },
]
repo = "https://github.com/ComPWA/taplo-pre-commit"
rev = "v0.9.3"
[[repos]]
hooks = [
{ id = "typos", name = "🔤 typos · Check for typos", priority = "checks" },
]
repo = "https://github.com/crate-ci/typos"
rev = "v1.51.1"
[[repos]]
hooks = [
{ id = "prettier", name = "🎨 prettier · Format YAML files", types = [
"yaml",
], priority = "independent_formatters" },
{ id = "prettier", name = "🎨 prettier · Format CSS files", alias = "prettier-css", types = [
"css",
], priority = "independent_formatters" },
]
repo = "https://github.com/pre-commit/mirrors-prettier"
rev = "v3.1.0"
[[repos]]
hooks = [
{ id = "pyrefly", name = "🐍 pyrefly · Type check Python code", entry = "uv run pyrefly check", language = "system", pass_filenames = false, files = '(\.(py|pyi)$|(^|/)(pyproject\.toml|uv\.lock|pyrefly\.toml)$)', priority = "checks", stages = [
"pre-push",
] },
{ id = "pyrefly-coverage", name = "🐍 pyrefly · Type coverage and completeness check", entry = "uv run pyrefly coverage check src --fail-under 100 --strict", language = "system", pass_filenames = false, files = '(\.(py|pyi)$|(^|/)(pyproject\.toml|uv\.lock|pyrefly\.toml)$)', priority = "checks", stages = [
"pre-push",
] },
{ id = "complexipy", name = "🧮 complexipy · Cognitive complexity check", entry = "uv run complexipy src tests", language = "system", pass_filenames = false, files = '(\.(py|pyi)$|(^|/)complexipy\.toml$)', priority = "checks", stages = [
"pre-push",
] },
{ id = "tach", name = "📐 tach · Architecture boundaries check", entry = "uv run tach check", language = "system", pass_filenames = false, files = '(\.(py|pyi)$|(^|/)(pyproject\.toml|uv\.lock|tach\.toml)$)', priority = "checks", stages = [
"pre-push",
] },
]
repo = "local"
[[repos]]
hooks = [
{ id = "uv-lock", name = "🐍 uv · Lock dependencies", priority = "lockfile" },
]
repo = "https://github.com/astral-sh/uv-pre-commit"
rev = "0.12.7"
Architectural Rationale¶
Local hooks call tools directly so they can process staged file paths and return reliable exit codes.
Direct Invocations Vs. Poe Tasks¶
In Zenith, local hooks in prek.toml invoke tools directly using uv run <tool>
(for example, uv run pyrefly check or uv run tach check) rather than delegating
to task aliases (uv run poe type-check):
- Staged File Argument Passing:
prekpasses the list of staged filenames directly to hook commands. Direct tool invocations receive and process these file arguments natively. In contrast, wrapping calls in task runners can misroute or drop path arguments. - Subprocess Overhead Elimination: Direct
uv run <tool>invocations avoid nested subprocess wrappers (prek → shell → uv → poe → python → tool), keeping local Git commit operations instantaneous. - Deterministic Exit Codes: Direct commands propagate exit codes straight to Git without an intermediate task runner layer.
Key Invocations¶
uv run prek install # Install Git commit hooks into .git/hooks/
uv run prek run --all-files # Execute all hooks across the entire repository
uv run prek run <hook_id> -a # Execute a specific hook across all files
uv run prek update # Update hook repositories to their latest revs
uv run prek validate-config # Validate configuration syntax