Privacy-Period-Tracker/docs/architecture/githooks/README.md

90 lines
3.9 KiB
Markdown

# Git hooks
```
Status: Current
Owner: _null
Last reviewed: 2026-08-18
Governs: .githooks/ — what runs before and after a commit
Review trigger: A new guard the repository wants run before a commit; any change
to what a commit message must contain; any change to which
Gradle tasks pre-commit runs
```
Three hooks, and the reason they live in the repository rather than in
`.git/hooks`: that directory is not versioned, so a hook living there protects
exactly one clone on exactly one machine.
## The hooks are in `.githooks/`, and only there
The template this repository adopted keeps a master copy under
`docs/architecture/githooks/` and installs copies into `.githooks/`. **Period
does not**, deliberately: `pre-commit` here is adapted for Gradle rather than
npm, so a second copy would be a second version of a file somebody edits once
and forgets — the exact failure `../../DOC_TRUST_MAP.md` exists to prevent.
So [`.githooks/`](../../../.githooks) holds the hooks and this document
describes them. One copy of the code, one copy of the explanation.
## Install
```bash
git config core.hooksPath .githooks
```
That is the whole setup, and it is **per clone** — every checkout runs it once,
including a fresh clone on the same machine. An uninstalled hook fails silently,
which is the same class of problem the hooks exist to prevent.
## What each one does
| Hook | Guard |
| --- | --- |
| `pre-commit` | the staged-diff secret scan, then `:domain:cycle:test` and `:domain:prediction:test` when `.kt`/`.kts` or a build file is staged |
| `commit-msg` | refuses a message with no conventional type — the closed vocabulary is in the hook's own header |
| `post-commit` | pushes to `origin`, so a guarded commit does not sit unpushed |
## Two things worth knowing before you rely on them
**`pre-commit` does not compile the Android modules.** It runs the two pure-JVM
suites, which need no SDK and take about a second. Compiling `:app` needs the
Android SDK and half a minute, and a hook people reach for `--no-verify` to
avoid is worse than one that checks less. `./gradlew assembleRelease` belongs to
[`../../security/SECURITY_CHECKLIST.md`](../../security/SECURITY_CHECKLIST.md),
which is where it is.
The suite it *does* run is not an arbitrary subset: it is the prediction
acceptance cases from
[`../../planning/PRODUCT_PLAN.md` §51](../../planning/PRODUCT_PLAN.md), which
guard the one claim this product is built on.
**`post-commit` pushes.** That is the intent — the commit that first added a
pre-commit hook to the project this came from sat unpushed for a day, guarded
and invisible — but it is a surprise if you were not expecting it. It never
forces, stays out of the way mid-rebase, and `SKIP_PUSH=1` opts out loudly.
It has a second consequence worth knowing about: the push is what the Command
Center reads, so **whatever documentation was not in that commit is now behind
the code by one push**. That is the mechanical reason
[`../../WORK_CYCLE.md`](../../WORK_CYCLE.md) asks for doc edits in the same
commit as the change rather than in a tidy-up afterwards — with this hook
installed, "I will document it next commit" means the site has already published
the version without it.
## Escape hatches, and why they are loud
```bash
SKIP_GUARDS=1 git commit ... # skips the scan and the tests, and says so
SKIP_PUSH=1 git commit ... # commits without publishing, and says so
git commit --no-verify ... # skips the hooks entirely, silently
```
Prefer the first two. They leave a line in the terminal saying the guard did not
run, which is the difference between a deliberate exception and a habit.
## Why a hook and not CI
Both, eventually. These are the guards that must run before the artifact exists:
a check that fires after a push, or after a Play upload, catches the problem once
it is already somewhere it cannot be taken back from. CI is the second opinion;
this is the one that runs first.