Privacy-Period-Tracker/docs/architecture/githooks
null 96dd878ac5 chore: adopt the project template and add the Kotlin/Compose skeleton
Period was a bare directory holding one 2,527-line specification, with no git
repository, no tracker and no documentation convention. This is the adoption
from Projects/Template/START-HERE-New-Project.md, plus a project that compiles
so the hooks and future guards have something real to run against.

Documents. scaffold.sh created 19 paths, 0 skipped. The specification moved to
docs/planning/PRODUCT_PLAN.md unchanged in substance, with a status header; the
capitalised Docs/ is gone. Every scaffolded document was filled in for Period.
docs/OPERATIONS.md deleted — an offline app is not a deployed service.
DOC_TRUST_MAP.md written last, describing what is actually here, including what
this project deliberately does not have.

Code. Four Gradle modules. domain/cycle and domain/prediction are kotlin("jvm")
and cannot see the Android SDK, so the engine is testable without an emulator —
17 tests pass, 12 of them the acceptance cases from PRODUCT_PLAN.md §51.
BaselinePredictionEngine is a robust-median prototype and explicitly not the
product; it exists so Batch 02's replacement can be shown to be better rather
than merely different.

Versions verified against their official sources today rather than inherited
from the specification's own numbers, which that document asks for: Kotlin
2.4.10, AGP 9.3.1, Gradle 9.7.0, Compose BOM 2026.08.00, Room 2.8.4, Hilt
2.60.1. AGP 9 ships Kotlin built in, so org.jetbrains.kotlin.android is no
longer applied. compileSdk is 37 because current AndroidX requires it; targetSdk
stays 36, Play's floor from 2026-08-31, and the difference is deliberate.

Six scripts taken into scripts/; the rest declined and named in docs/TOOLS.md.
Three hooks in .githooks/, with pre-commit adapted to Gradle.

closes #1
closes #2
2026-08-18 02:16:47 -05:00
..
README.md chore: adopt the project template and add the Kotlin/Compose skeleton 2026-08-18 02:16:47 -05:00

README.md

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/ holds the hooks and this document describes them. One copy of the code, one copy of the explanation.

Install

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, 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, 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 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

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.