diff --git a/.githooks/commit-msg b/.githooks/commit-msg new file mode 100755 index 0000000..0c62bd3 --- /dev/null +++ b/.githooks/commit-msg @@ -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 diff --git a/.github/prompts/commit.prompt.md b/.github/prompts/commit.prompt.md index c8edfdf..5dcf414 100644 --- a/.github/prompts/commit.prompt.md +++ b/.github/prompts/commit.prompt.md @@ -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 **1–3 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`? diff --git a/AGENTS.md b/AGENTS.md index 82b2799..61e8494 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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, **1–3 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 ` 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 diff --git a/README.md b/README.md index 46a4b4f..fa6b8fd 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index ef6a9d2..7d58e73 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -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,