chore: add a commit message standard with normalizer and linter - #212
chore: add a commit message standard with normalizer and linter#212gregggreg wants to merge 2 commits into
Conversation
Adopt Conventional Commits as the documented standard and add tooling that both normalizes and validates it. - docs/commit-messages.md defines the format, types, rules and examples - internal/commitmsg holds the rules, with table-driven tests - cmd/commitmsg normalizes a message file in place or lints a message or a rev-range - scripts/commit-msg.sh runs both, installed opt-in by make install-hooks - .github/workflows/commit-lint.yml checks the commits a PR adds The normalizer only fixes mechanical problems and never rewrites prose, so it is idempotent and leaves trailers untouched. Nightshift-Task: commit-normalize Nightshift-Ref: https://github.com/marcus/nightshift
Normalize walked backwards from the end of the message over any line shaped like "Word: text", so a body paragraph ending in "Before: 2.1s." or "Note: it is opt-in" was mistaken for a trailer block. That inserted a blank line into the middle of the paragraph, contradicting the standard's guarantee that the normalizer never rewrites prose, and it silently exempted those lines from the body width check. A trailing block now only counts as trailers when at least one of its keys is one we recognize (Co-Authored-By, Signed-off-by, Refs, Fixes, Nightshift-*, and friends). Hyphenated keys such as Reviewed-by are still kept with a block anchored by a known key, since prose never has that shape. Nightshift-Task: commit-normalize Nightshift-Ref: https://github.com/marcus/nightshift
|
Review follow-up (iteration 2): fixed the confirmed defect where
A trailing block now only counts as trailers when at least one of its keys is recognized ( Three new normalizer table cases (paragraph ending in |
|
Review note (iteration 3) The trailer-misdetection defect from the first review is fixed in e529f13 ( A trailer block is now only recognized when at least one line uses a known trailer key ( Verified on the exact repro from the review:
For reviewers: this branch is |
What
Adds an opt-in, documented commit message standard plus tooling that normalizes and validates it.
docs/commit-messages.mdinternal/commitmsg/NormalizeandLint, table-driven testscmd/commitmsg/normalize <file>,lint [file],lint --range <r> [--report-only]scripts/commit-msg.sh.github/workflows/commit-lint.ymlCONTRIBUTING.mdMakefile,README.mdmake install-hooksalso installs commit-msg; newmake commit-lintThe standard
Conventional Commits:
type(scope)!: subject, typesfeat fix docs style refactor perf test build ci chore revert. Imperative lowercase subject, no trailing period, ≤72 chars. Blank line before the body. Trailers (Nightshift-Task,Nightshift-Ref,Co-Authored-By, …) are never rewritten or width-checked.The normalizer fixes only mechanical problems — casing, the missing space after the colon, trailing periods, stray whitespace, blank-line placement, git comment/scissors stripping. It never rewraps or rewrites prose, which is what makes it idempotent and safe. Anything it cannot fix (too-long subject, unwrapped body) is reported by the linter for a human to fix.
Assumptions
make install-hooks.Deviations from the original plan, and why
.git/hooks/, notcore.hooksPath. The repo already installsscripts/pre-commit.shthat way. Switching tocore.hooksPathwould silently disable the pre-commit hook for everyone who already ranmake install-hooks. Matching the existing convention was the safer call.internal/commitmsg/commitmsg.goif you want them tightened.go test ./...CI job.Width checks skip fenced code, indented blocks, trailers, and lines with an unwrappable token (a long URL or path). Messages git writes itself —
Merge …,Revert "…",fixup!/squash!— are exempt entirely.Verification
go build ./...,go vet ./...,go test ./...all pass.Fixed all the thingsis rejected with the allowed-type list and a--no-verifyhint.commitmsg: checked 1 commit(s) in origin/main..HEAD, 0 with violations.Note: the historical-violation numbers above are informational — nothing in this PR touches existing history.
🤖 Generated with Claude Code