chore: add a commit message normalizer, hook, and linter - #229
Conversation
Adopt Conventional Commits as the repository standard, documented in docs/commit-messages.md and derived from the format the existing history already trends toward. Ships three dependency-free shell scripts: an in-place normalizer, a --check linter reused over a commit range, and a commit-msg hook wrapper. The normalizer rewrites only the subject line; the body and every trailer are preserved byte-for-byte so Nightshift-Task/Nightshift-Ref survive. Enforcement is opt-in: the hook installs via `make install-hooks`, and the new CI job is non-blocking so pre-existing history does not fail the gate. Nightshift-Task: commit-normalize Nightshift-Ref: https://github.com/marcus/nightshift
Two defects from review of the initial normalizer: --check only proved a message was normalizable, so 'Feat(API): Add thing.' and a body with no blank separator both passed. It now compares the normalized result against the original and rejects any difference, which makes the documented Bad examples actually fail and lets the CI job work as a real gate once continue-on-error is dropped. The comment-stripping loop dropped every '#' line, deleting body text. Git runs its cleanup after the commit-msg hook and is flow-aware: it strips comments for editor-authored messages but keeps them for 'git commit -m'. Only the comment block preceding the subject and the scissors section are dropped now, so git decides the rest. Also fixes CRLF input, which left a stray CR-only line between the subject and body. Line endings are detected from the subject and preserved. Tests: 34 pass, covering both defects and CRLF. Nightshift-Task: commit-normalize Nightshift-Ref: https://github.com/marcus/nightshift
|
Review round 2 — both defects fixed, plus the minor CRLF issue. (1) So the Bad examples in (2) The hook no longer deletes body comments. Verified that git runs its cleanup after The middle paragraph survives, matching the byte-for-byte guarantee. The editor flow still ends up with template comments stripped — by git, as before. (3) CRLF. Line endings are detected from the subject and preserved, so Verification: |
Summary
Standardizes commit messages on Conventional Commits and ships dependency-free tooling to normalize and validate them.
The convention isn't new — 132 of the last 174 commits (76%) already use
type(scope): subject. This documents what the history already trends toward and adds tooling so the rest converges.What's here
docs/commit-messages.md— the written convention: format, the 11 allowed types (derived from types already in use plus the standard set), the 19 scopes already in use, breaking-change syntax, trailer rules, exemptions, and good/bad examples.scripts/normalize-commit-msg.sh— rewrites a message file in place. Strips comment/scissors scaffolding, trims whitespace, lowercases the type and scope, drops a trailing period (but not an ellipsis), guarantees exactly one blank line after the subject, and warns — never fails — on a subject over 72 characters. Rejects unknown types and free-form subjects with an actionable error.--checkvalidates without writing.The body and every trailer are preserved byte-for-byte. Only the subject line is ever rewritten, so
Nightshift-Task/Nightshift-Ref/Co-Authored-Bysurvive intact — verified by a dedicated test and by this PR's own commit, which was written by the hook.scripts/lint-commit-msg.sh— the same validation over files or a whole range (--range base..head), for CI.scripts/commit-msg.sh— the git hook wrapper.scripts/test-normalize-commit-msg.sh— 23 tests covering each transformation, each exemption, both rejection paths, trailer preservation,--checknon-mutation, and idempotency.Exemptions
Merge …,Revert "…", andfixup!/squash!/amend!messages pass through untouched.Installation is opt-in
Extends the existing target, which already symlinked
scripts/pre-commit.shinto.git/hooks/; it now installscommit-msgthe same way. Idempotent. Nothing installs automatically, so no contributor's workflow changes without them asking.git commit --no-verifyskips it.CI is non-blocking by default
The new
commit-messagesjob lints every commit in the PR range but carriescontinue-on-error: true. The repo has ~25 pre-existing commits in recent history that predate the convention, and a blocking gate would fail PRs for history nobody is going to rewrite. Removing that one line makes it enforcing — documented in the doc.Assumptions
make install-hooks.Verification
./scripts/test-normalize-commit-msg.sh— 23/23 passmake test— 21 packages, 0 failuresmake lint— golangci-lint, 0 issuesbash -nclean on all scriptsFEAT(CLI): add a thing.with a body and Nightshift trailers normalized tofeat(cli): add a thingwith trailers intact;wibble: nopeblocked; a merge message passed through;make install-hooksidempotent across two runs.Deviation from the plan
The plan specified
.githooks/+core.hooksPath. This repo already installs hooks by symlinkingscripts/*.shinto.git/hooks/, and switching tocore.hooksPathwould silently disable thepre-commithook for everyone who already ranmake install-hooks. Matched the existing convention instead.Nightshift-Task: commit-normalize
Nightshift-Ref: https://github.com/marcus/nightshift
🤖 Generated with Claude Code