core.hooksPath makes this hook global to every repo, which is why it is tracked
and travels — and why it must hold no repo knowledge. A repo opts in by
declaring `.precommit-triggers` at its root: staged pathspecs on the left, a
command on the right. If the staged diff touches a declared pathspec the command
runs, and a non-zero exit refuses the commit.
Three decisions worth stating rather than leaving to be rediscovered:
Path matching is delegated to `git diff --cached --name-only -- <pathspec>`
rather than reimplemented, so declarations use the pathspec syntax the repo's
users already know and globs behave as they do everywhere else in git.
The declaration file is read on fd 3, so a declared check that reads stdin
cannot swallow the remainder of the rules.
It is dependency-free by design — no yq, no python. A global convention that
needs a toolchain silently fails to travel to the next machine, and a check that
silently does not run is worse than no check, because its absence reads as a
pass. This is a deliberate departure from the YAML used by data that python
tools consume.
Scope: this is a tripwire, not an enforcement boundary. --no-verify steps over
it, and the message says so. It is worth having because the failure mode it
addresses is forgetting, not evading.
First consumer: studium-engine, where a corpus edit invalidates engine fixtures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A35wiD55yRHj5U1ECZAX4t