Project-Template/docs/architecture/githooks
null 6965915dbd chore(repo): put the template under version control
The basis for every project here was itself unversioned: no .git, no remote,
no history. Changes to it had no diff and no revert, and two of its own guards
could not run at all -- doc-claims.sh and doc-triggers.py both read git
history, so the script written to catch documentation drift could not be run
against the documents that define drift.

This is the tree as it stands, including work that until now existed only as
loose files on disk: WORK_CYCLE.md, TOOLS.md, the Portainer image-line fix in
deploy.py, the status vocabulary corrected to the four words the conformance
checker actually enforces, the Exempt: mechanism documented, and the Forgejo
instance named in README.md.

secrets.sh --tracked reports one candidate, migrate.sh:480. It is the comment
documenting the three Postgres credential shapes that script redacts, with
literal placeholders, and it is left alone deliberately: GUARDS.md section 2
is that a source-grep guard must tell code from the comment about code, and
deleting an explanation to quiet a scanner is the failure it names.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 22:44:26 -05:00
..
README.md chore(repo): put the template under version control 2026-08-17 22:44:26 -05:00
commit-msg chore(repo): put the template under version control 2026-08-17 22:44:26 -05:00
post-commit chore(repo): put the template under version control 2026-08-17 22:44:26 -05:00
pre-commit chore(repo): put the template under version control 2026-08-17 22:44:26 -05:00

README.md

Git hooks

Status: Current
Owner: <who maintains this>
Last reviewed: <YYYY-MM-DD>
Governs: docs/architecture/githooks/** and the .githooks/ a project installs
Review trigger: A new guard the repository wants run before a commit; any change
  to what a commit message must contain.

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. These are committed, and one command points git at them.

Install

mkdir -p .githooks
cp docs/architecture/githooks/{pre-commit,commit-msg,post-commit} .githooks/
chmod +x .githooks/*
git config core.hooksPath .githooks

core.hooksPath is per-clone, so every checkout runs that last line once. Say so in the project README — 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 typecheck, then the test suite, when .ts/.tsx is staged
commit-msg refuses a message with no conventional type
post-commit pushes to the remote, so a guarded commit does not sit locally

Two settings worth making before you rely on them

pre-commit will use a local test database if you tell it where one is. Set TEST_DB_PORT and TEST_DB_URL together. Leave them unset and the database suites skip, which is the right default on a machine with no Postgres — but it means the hook is running a fraction of the suite, so release.sh is deliberately stricter and refuses rather than warns.

post-commit pushes. That is the intent — the commit that first added a pre-commit hook to the reference project sat unpushed for a day, guarded and invisible — but it is a surprise if you were not expecting it. Read it before installing it on a repository with a protected branch.

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 docs/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.

Why a hook and not CI

Both. These are the guards that must run before the artifact exists: a hook that fires after a push, or a CI job that fires after a publish, 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.