Custom pre-commit hooks for projects using Rhiza templates.
This repository extracts rhiza's local hooks into a standalone package, allowing rhiza and downstream projects to use them as an external hook repository.
Add to your .pre-commit-config.yaml:
repos:
- repo: https://github.com/Jebel-Quant/rhiza-hooks
rev: v0.6.2 # Use the latest release
hooks:
# Migrated from rhiza
- id: check-rhiza-workflow-names
- id: update-readme-help
# Additional utility hooks
- id: check-rhiza-config
- id: check-makefile-targets
- id: check-python-version-consistency
- id: check-template-bundlesThen install the hooks:
pre-commit install| Hook | Triggers on | Autofixes? | Exit code |
|---|---|---|---|
check-rhiza-workflow-names |
.github/workflows/rhiza_*.yml |
โ
rewrites a wrong name: |
1 if any file was changed or has an error, else 0 |
update-readme-help |
Makefile |
โ
rewrites README.md between markers |
1 if README.md was changed, else 0 (never fails when make help is unavailable) |
check-rhiza-config |
.rhiza/template.yml |
โ validates only | 1 if invalid, else 0 |
check-makefile-targets |
Makefile, .rhiza/*.mk |
โ warns only | 0 by default (warn-only); 1 on missing targets only with --strict |
check-python-version-consistency |
.python-version, pyproject.toml |
โ validates only | 1 on mismatch, else 0 |
check-template-bundles |
.rhiza/template.yml |
โ validates only (network) | 1 on validation failure, else 0; 0 when --offline |
Details for each hook follow.
Ensures GitHub Actions workflow names have the (RHIZA) prefix in uppercase. Automatically fixes files that don't conform.
Files: .github/workflows/rhiza_*.yml
Usage:
- id: check-rhiza-workflow-namesTroubleshooting:
- The hook only scans
.github/workflows/rhiza_*.yml; if nothing happens, confirm your workflow filename matches that pattern. - A hook failure after edits is expected when it auto-fixes
name:valuesโre-stage the workflow file and re-run.
Embeds the output of make help into README.md between marker comments.
Triggers on: Changes to Makefile
Usage:
- id: update-readme-helpTroubleshooting:
- If
make(ormake help) is unavailable, this hook exits successfully and skips updates by design.
Validates the .rhiza/template.yml configuration file to ensure:
- All required keys are present (
template-repository,template-branch) - At least one of
includeortemplates(or aliasprofiles) is present - The
template-repositoryis in the correctowner/repoformat - No unknown keys are present
- The
includelist (if present) is not empty - The
templateslist (or aliasprofiles, if present) is not empty
Usage:
- id: check-rhiza-configTroubleshooting:
- Validate that
.rhiza/template.ymlcontainstemplate-repositoryandtemplate-branch, plus at least one ofinclude,templates, orprofiles. - If you see unknown-key errors, compare your keys to the documented schema and remove unsupported entries.
Checks that your Makefile contains recommended targets for rhiza-based projects:
install- Install dependenciestest- Run testsfmt- Format codehelp- Show available targets
By default, this hook only warns about missing targets. Use --strict to fail on missing targets.
The expected set can be customised:
--target NAME(repeatable) replaces the default set with exactly the targets you list.--extend-target NAME(repeatable) adds to the active set (defaults, or whatever--targetselected).
Usage:
- id: check-makefile-targets
args: [--strict] # Optional: fail if targets are missing
# Require a custom set instead of the defaults:
- id: check-makefile-targets
args: [--target, build, --target, lint]
# Keep the defaults and also require `deploy`:
- id: check-makefile-targets
args: [--extend-target, deploy]Troubleshooting:
- Default mode is warn-only, so missing targets do not fail commits unless you pass
--strict. - If a required target is intentionally different, use
--target/--extend-targetto align checks with your Makefile.
Ensures Python version is consistent between .python-version and pyproject.toml's requires-python.
Usage:
- id: check-python-version-consistencyTroubleshooting:
- Keep
.python-versionaligned withproject.requires-pythoninpyproject.toml. - If ranges are used (for example
>=3.11), ensure the.python-versionvalue satisfies that range exactly.
Validates templates specified in .rhiza/template.yml against the template-bundles.yml file from the template repository. This hook:
- Fetches
template-bundles.ymlfrom the remote template repository specified in your config - Ensures all templates listed in your
.rhiza/template.ymlexist in the remote bundles - Validates bundle structure (each bundle has
descriptionandfiles) - Checks that bundle dependencies are valid
Triggers on: Changes to .rhiza/template.yml
This hook reaches the network on every run. Transient failures are retried with a short linear backoff, and each failed attempt is logged so CI failures are diagnosable. The retry count and per-request timeout are configurable, and --offline skips the remote fetch entirely (the hook then passes without validating), which is useful for offline commits.
Options:
| Flag | Default | Effect |
|---|---|---|
--offline |
off | Skip the remote fetch and pass without validating |
--retries N |
1 |
Retries after the first attempt on transient network errors (0 disables retrying) |
--timeout S |
10.0 |
Per-request network timeout, in seconds |
Usage:
- id: check-template-bundles
# args: [--offline] # Optional: skip the network fetch and pass
# args: [--retries, "3", --timeout, "20"] # Optional: tune flaky-network behaviourTroubleshooting:
- This hook normally fetches
template-bundles.ymlfrom the configured template repository and retries on transient network errors; raise--retries/--timeoutif your network is slow or flaky, and read the per-attempt log lines to see what failed. - Use
--offlinewhen committing without network access; it skips the fetch and exits successfully without remote validation.
- Python 3.11+
- uv (recommended) or pip
# Clone the repository
git clone https://github.com/Jebel-Quant/rhiza-hooks.git
cd rhiza-hooks
# Install dependencies
make install
# Install pre-commit hooks
pre-commit installmake install # Install dependencies
make test # Run tests with coverage
make fmt # Format and lint code
make deptry # Check for unused/missing dependencies
make help # Show all available targetsUse pre-commit try-repo to test hooks without committing:
# Test all hooks against your current project
pre-commit try-repo . --all-files
# Test a specific hook
pre-commit try-repo . check-rhiza-config --files .rhiza/template.ymlThis project enforces 100% line/branch coverage and a 100% mutation score (via mutmut). Both gates run in CI, but you can reproduce them locally before opening a PR:
make test # Run the suite with coverage (fails under 100%)
make mutation # Run mutation testing (fails on any surviving mutant)make mutation writes an HTML report to _tests/mutation/html/index.html โ open it to see exactly which mutants survived and which test should have caught each one. mutmut results lists survivors on the command line.
Coverage proves a line ran; mutation testing proves a wrong result would be caught. When a mutant survives, the fix is almost always a stronger assertion (pin the exact value/message rather than asserting "truthy").
Occasionally a mutant is genuinely equivalent โ it changes the code without changing any observable behaviour, so no test can kill it (e.g. swapping a boolean initializer that is only ever read in a truthiness check between False and None). Mark only these with a # pragma: no mutate comment that states why it is equivalent, e.g.:
failed = False # pragma: no mutate # equivalent: only ever read via `if failed`Reach for the pragma sparingly and only after confirming no assertion can distinguish the mutant โ a real, killable mutant should be killed with a test, not suppressed.
This project is licensed under the MIT License - see the LICENSE file for details.
- Ask questions and get support in GitHub Discussions using the Q&A template.
- Report bugs or request features using the issue templates.
- Rhiza - The template system these hooks are designed for
- pre-commit - The framework that makes this possible