docs: add ADR-0007 for pre-commit hooks and align documentation references
- decisions/0007-pre-commit-hooks-and-conventional-commits.md formalizes Husky + lint-staged + commitlint with Conventional Commits as the local quality-gate baseline. - decisions/README.md index updated. - docs/setup/03 section 8 rewritten to reference the ADR and document the full hook setup (pre-commit, commit-msg, commitlint config). - docs/setup/03 future-work table 'ADR(s)' column removed; future ADR numbers are now assigned at the moment each ADR is written, not pre-reserved. - CLAUDE.md aligned: pre-allocated phase-2 ADR numbers replaced by phase references; a pointer to ADR-0007 added under 'Local quality gates'.
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
---
|
||||
status: accepted
|
||||
date: 2026-04-29
|
||||
decision-makers: R&D Lead
|
||||
tags: [process]
|
||||
---
|
||||
|
||||
# Pre-commit hooks and Conventional Commits
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
Without local enforcement, trivial issues — unformatted code, lint errors, ill-formed commit messages — only surface in CI. Each round-trip costs minutes of feedback latency and pollutes the commit history with fixup noise. We need a lightweight, well-known mechanism that catches these issues at commit time and enforces a consistent commit message format, while staying fast enough not to discourage frequent commits.
|
||||
|
||||
Which tooling do we adopt for git pre-commit checks and commit message validation?
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
* Fast feedback: catch trivial issues locally; CI is defense in depth, not the first line.
|
||||
* Consistency of code style and commit history across contributors.
|
||||
* Conventional commit history that machines can read (later automation: changelog, release notes, semver bumps).
|
||||
* Mainstream tooling — no exotic shell hooks, no single-maintainer projects.
|
||||
* Easy onboarding: no extra runtime to manage beyond Node/pnpm.
|
||||
|
||||
## Considered Options
|
||||
|
||||
* **Husky + lint-staged + commitlint with Conventional Commits.** (Chosen.)
|
||||
* No git hooks — rely on CI.
|
||||
* `pre-commit` (Python framework).
|
||||
* `lefthook` (Go single-binary hook manager).
|
||||
* Custom shell scripts in `.git/hooks/`.
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Chosen option: **Husky + lint-staged + commitlint, with [Conventional Commits](https://www.conventionalcommits.org/) as the commit-message specification**.
|
||||
|
||||
Hooks installed under `.husky/`:
|
||||
|
||||
| Hook | Action |
|
||||
| --- | --- |
|
||||
| `pre-commit` | `pnpm exec lint-staged` — runs lint and format on staged files only |
|
||||
| `commit-msg` | `pnpm exec commitlint --edit "$1"` — validates the commit message against Conventional Commits |
|
||||
|
||||
Configuration:
|
||||
- `package.json`: `"prepare": "husky"` script (Husky 9+ pattern), `lint-staged` config block.
|
||||
- `commitlint.config.cjs`: `module.exports = { extends: ['@commitlint/config-conventional'] }`.
|
||||
|
||||
Scope of `lint-staged`: only fast checks on staged files (lint, format). Type-check and tests stay in `pnpm nx affected -t lint test build` in CI — running the full graph on every commit would slow commits unacceptably and discourage frequent commits.
|
||||
|
||||
CI re-runs the same checks (defense in depth — hooks can be bypassed with `--no-verify`).
|
||||
|
||||
### Consequences
|
||||
|
||||
* Good, because trivial issues are caught locally and don't pollute PR history.
|
||||
* Good, because Conventional Commits gives us a machine-readable history — enables automated changelogs, semver inference, and release tooling without retroactive cleanup.
|
||||
* Good, because Husky / lint-staged / commitlint are the de facto standard in JS/TS projects — wide community, abundant documentation, low surprise factor.
|
||||
* Good, because lint-staged keeps commit time low (only changed files are checked).
|
||||
* Bad, because hooks can be bypassed (`git commit --no-verify`); CI must remain authoritative.
|
||||
* Bad, because Husky 9 changed its installation pattern (no `husky install`, just `husky`); contributors with stale instructions can be confused. Mitigated by the setup guide.
|
||||
* Bad, because Conventional Commits adds a small learning curve; mitigated by IDE plugins, `commitlint` error messages, and a one-page contributor cheatsheet (future doc).
|
||||
|
||||
### Confirmation
|
||||
|
||||
* `package.json` declares `husky`, `lint-staged`, `@commitlint/cli`, `@commitlint/config-conventional` as `devDependencies`.
|
||||
* `package.json` has a `"prepare": "husky"` script.
|
||||
* `.husky/pre-commit` runs `pnpm exec lint-staged` and is executable.
|
||||
* `.husky/commit-msg` runs `pnpm exec commitlint --edit "$1"` and is executable.
|
||||
* `commitlint.config.cjs` exists at the workspace root and extends `@commitlint/config-conventional`.
|
||||
* CI pipeline (future ADR) re-runs lint and format checks on every push, plus a Conventional Commits validation on the PR commit range.
|
||||
|
||||
## Pros and Cons of the Options
|
||||
|
||||
### Husky + lint-staged + commitlint (chosen)
|
||||
|
||||
* Good, because the trio is the de facto standard in the JS/TS ecosystem.
|
||||
* Good, because each tool is single-purpose and composable.
|
||||
* Good, because the configuration lives with the repo and is versioned with the code.
|
||||
* Bad, because three packages instead of one — slightly more dependency surface.
|
||||
|
||||
### No git hooks
|
||||
|
||||
* Good, because zero local setup.
|
||||
* Bad, because every trivial issue costs a CI round-trip. Wasteful and noisy.
|
||||
|
||||
### `pre-commit` (Python)
|
||||
|
||||
* Good, because language-agnostic, used in mixed-language repos (Python + JS + Go).
|
||||
* Bad, because adds a Python runtime dependency to a Node-only project — extra setup for contributors, especially under WSL where Python toolchains are not always uniform.
|
||||
|
||||
### lefthook
|
||||
|
||||
* Good, because single Go binary, very fast, parallel hook execution, declarative YAML config.
|
||||
* Bad, because smaller community than Husky in JS/TS land; less prior art and fewer answers when something breaks.
|
||||
* Status: kept on the watch list for re-evaluation if commit performance becomes a pain.
|
||||
|
||||
### Custom shell scripts in `.git/hooks/`
|
||||
|
||||
* Good, because zero dependency.
|
||||
* Bad, because hooks aren't versioned with the repo by default; a wrapper layer is needed anyway. Bricolage.
|
||||
|
||||
## More Information
|
||||
|
||||
* Husky: https://typicode.github.io/husky/
|
||||
* lint-staged: https://github.com/lint-staged/lint-staged
|
||||
* commitlint: https://commitlint.js.org/
|
||||
* Conventional Commits: https://www.conventionalcommits.org/
|
||||
* Related: future quality / CI ADR will re-run these checks as defense in depth.
|
||||
@@ -50,3 +50,4 @@ ADRs are listed in numerical order. To slice by topic, filter on the `Tags` colu
|
||||
| [0004](0004-frontend-stack-angular-csr-zoneless-signals.md) | Frontend stack — Angular (latest LTS), standalone, zoneless, Signals, CSR-only, Vitest | accepted | `frontend` | 2026-04-29 |
|
||||
| [0005](0005-backend-stack-nestjs.md) | Backend stack — NestJS over Express, Fastify, Hono | accepted | `backend` | 2026-04-29 |
|
||||
| [0006](0006-persistence-postgresql-prisma.md) | Persistence — PostgreSQL with Prisma | accepted | `data`, `backend` | 2026-04-29 |
|
||||
| [0007](0007-pre-commit-hooks-and-conventional-commits.md) | Pre-commit hooks and Conventional Commits | accepted | `process` | 2026-04-29 |
|
||||
|
||||
Reference in New Issue
Block a user