chore(githooks): add commit-msg hook for 80-column messages

Long commit subjects and bullets are hard to read in narrow terminals and
mailed logs. Contributors using `.githooks` via core.hooksPath now get an
automatic guard in addition to the two-file pre-commit cap.

- Add `.githooks/commit-msg` to fail when any message line exceeds 80 chars
- Skip length checks while `.git/MERGE_HEAD` exists for merge commits
- Sync AGENTS.md, README.md, CONTRIBUTING.md, and commit.prompt.md
This commit is contained in:
Jukka Aho
2026-05-11 02:37:38 +03:00
parent 6f3629784e
commit 77766d6ee4
5 changed files with 73 additions and 17 deletions
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
# Reject commit messages with any line longer than 80 characters.
# Enable with: git config core.hooksPath .githooks
#
# Merge commits often carry Git-generated "Merge branch …" lines that can
# exceed 80 columns when branch or remote names are long; skip the check then.
set -euo pipefail
msg_file=${1:?commit-msg hook requires path to message file}
if [[ -f .git/MERGE_HEAD ]]; then
exit 0
fi
lineno=0
while IFS= read -r line || [[ -n "${line}" ]]; do
lineno=$((lineno + 1))
len=${#line}
if ((len > 80)); then
echo "❌ COMMIT MESSAGE: line ${lineno} is ${len} chars (max 80)"
echo ""
echo "Wrap the subject, summary, and bullets to 80 columns or fewer."
echo "See .github/prompts/commit.prompt.md"
echo ""
echo "Offending line (truncated for display):"
printf '%s\n' "${line}" | cut -c1-120
exit 1
fi
done <"${msg_file}"
exit 0
+23 -7
View File
@@ -38,9 +38,17 @@ git add path/a.jl path/b.jl
- Avoid `git add .` and `git add -A` unless the user explicitly wants everything (still read the full staged diff after).
- Never stage unrelated changes into the same commit.
## Optional git hook (max two staged paths)
## Optional git hooks (`.githooks/`)
The repo ships `.githooks/pre-commit`. After `git config core.hooksPath .githooks`, commits with **more than two** staged files are rejected. This supports the common “impl + test” or “snippet + verifier” pair without allowing large batches. Split bigger changes across commits or adjust the hook deliberately.
After `git config core.hooksPath .githooks`:
- **pre-commit:** rejects commits with **more than two** staged files. This
supports the common “impl + test” or “snippet + verifier” pair without
allowing large batches. Split bigger changes across commits or adjust the
hook deliberately.
- **commit-msg:** rejects the commit if **any** message line is longer than
**80 characters** (including the subject). Wrap prose and bullets; merge
commits skip this check while `.git/MERGE_HEAD` is present.
## Read the full staged diff
@@ -94,17 +102,24 @@ EOF
Use `-` bullet lists; omit sections that do not apply.
### Line length (enforced when hooks are enabled)
Keep **every** line at **80 characters or fewer**, including the subject and
each bullet. This keeps `git log` readable in terminals and mail archives. If
the hook is enabled, overlong lines cause the commit to fail—rewrap before
retrying.
### Example (single file)
```
refactor(basis): Replace per-element lagrange includes with generator entrypoints
refactor(basis): fold lagrange includes behind generator entrypoints
The basis tree previously listed seven separate lagrange_*.jl includes; they are
folded behind lagrange_generator.jl and lagrange_generated.jl so new elements
register in one place.
The basis tree listed seven separate lagrange_*.jl includes; they now load
through lagrange_generator.jl and lagrange_generated.jl so registration lives
in one place.
- Drop redundant includes from JuliaFEM.jl
- Document the 'Basis' suffix convention in the include comment
- Note the Basis suffix convention in the include comment
```
### Example (multi-file — requires justification to user)
@@ -160,6 +175,7 @@ Never use `git diff --staged | head` (etc.). Read the full diff.
- [ ] If `.githooks` is active: are there **at most two** staged paths?
- [ ] Did I read the **full** `git diff --staged`?
- [ ] Subject line: Conventional Commits, specific, matches diff?
- [ ] Is **every** message line (subject, summary, bullets) **≤ 80 characters**?
- [ ] Body: blank line, then **13 sentence** summary, then **bullets scaled to diff size** (rich, grouped bullets for large / multi-concern commits)?
- [ ] Did I **propose** the commit and wait for **approval** before `git commit`?
+9 -5
View File
@@ -205,8 +205,10 @@ These are non-negotiable. Tests and code analysis enforce them.
## 5. Critical workflow rules
Contributor-facing workflow text lives in `.github/prompts/commit.prompt.md`
and `.github/copilot-instructions.md`. Optional `.githooks/pre-commit`
(enabling `core.hooksPath`) caps staged paths at two per commit.
and `.github/copilot-instructions.md`. Optional `.githooks/` hooks (enable with
`git config core.hooksPath .githooks`): **pre-commit** caps staged paths at
two per commit; **commit-msg** rejects any log line longer than **80
characters** (merge commits skip that check while `.git/MERGE_HEAD` exists).
Editor-local Cursor rules under `.cursor/` are not part of the git tree.
- Commits: prefer **small** steps (default one file per commit). Combine
@@ -215,8 +217,8 @@ Editor-local Cursor rules under `.cursor/` are not part of the git tree.
deliberately. Read the **full** staged diff (no `head`/`tail`). Message
format: Conventional Commits **subject**, blank line, **13 sentence**
summary, then bullets **scaled to the patch** (small change → few or none;
large / multi-concern → grouped, substantive bullets). **Propose** paths + full message
and wait for **explicit approval** before each `git commit`. See
large / multi-concern → grouped, substantive bullets). **Propose** paths and
full message and wait for **explicit approval** before each `git commit`. See
`.github/prompts/commit.prompt.md` for the full protocol.
- **AI agents: commits are not a loop variable.** Never drive `git commit` from a
shell or Python loop that stages paths and emits placeholder subjects such as
@@ -225,7 +227,9 @@ Editor-local Cursor rules under `.cursor/` are not part of the git tree.
`head`, `tail`, `less`, or other truncation when forming the message). The
subject and body must describe the **actual** API and behaviour changes in
those hunks; the optional two-path `.githooks/pre-commit` rule limits batch
size, it does **not** replace reading the diff. If many low-quality commits
size, it does **not** replace reading the diff. Keep every commit message line
at 80 columns or fewer when hooks are enabled (see **commit-msg**). If many
low-quality commits
already exist locally, repair with `git reset --soft <good_base>` and rebuild
following this file and `.github/prompts/commit.prompt.md`, or use
`git rebase -i` to reword; do not apply a second scripted sweep of generic
+5 -3
View File
@@ -85,9 +85,11 @@ what you changed.
**Git commits.** Keep history easy to read: small commits, usually one file;
**two files** in one commit is fine when they are inseparable (e.g. a helper and
its only caller). An optional hook (`.githooks/`, set `core.hooksPath`) caps
staged paths at two. If that workflow feels unfamiliar, open your PR with tests
passing and ask for help splitting history in review.
its only caller). With `.githooks/` and `core.hooksPath`, **pre-commit** caps
staged paths at two and **commit-msg** rejects log lines longer than **80
characters** (wrap subject, summary, and bullets). If that workflow feels
unfamiliar, open your PR with tests passing and ask for help splitting history
in review.
**Code expectations.** Assembly hot paths must stay type-stable and allocation-free
after warmup; CI and [`test/assemblers/test_dof_based_zero_alloc.jl`](test/assemblers/test_dof_based_zero_alloc.jl)
+4 -2
View File
@@ -64,8 +64,10 @@ when rendering the website) rather than chasing false positives in the library.
(e.g. implementation + its test). Never `git add .` or `git add -A`
unless you mean it. The full protocol is in
`.github/prompts/commit.prompt.md`. With
`git config core.hooksPath .githooks`, the pre-commit hook allows at
most **two** staged files per commit.
`git config core.hooksPath .githooks`, **pre-commit** allows at most **two**
staged files per commit, and **commit-msg** rejects messages with any line
longer than **80 characters** (merge commits skip the length check while
`.git/MERGE_HEAD` exists).
- Match **commit message depth** to the patch: a short summary suffices for
small edits; **large files, large diffs, or several concerns in one commit**
should add **grouped bullets** (major API or behavior, wiring, migrations,