Skip to content

feat(cli): add commit message normalizer - #235

Open
gregggreg wants to merge 1 commit into
marcus:mainfrom
gregggreg:commit-normalize-normalizer
Open

feat(cli): add commit message normalizer#235
gregggreg wants to merge 1 commit into
marcus:mainfrom
gregggreg:commit-normalize-normalizer

Conversation

@gregggreg

Copy link
Copy Markdown

Summary

Adds a dependency-free internal/commitmsg package that parses, validates and
normalizes commit messages against one Conventional Commits format, and exposes
it three ways from a single canonical spec string so the CLI, the git hook,
the docs and the agent prompts can never drift apart:

  1. nightshift commit-msg--check / --fix / --print-spec / --quiet,
    reading a message file or stdin (-). Issues print as
    file:line: severity: message (rule); errors exit non-zero, warnings don't.
  2. scripts/commit-msg.sh — a git hook installed by make install-hooks
    alongside the existing pre-commit hook. It prefers the built binary, falls
    back to go run ./cmd/nightshift, prints the spec on failure, and documents
    the --no-verify escape hatch.
  3. Orchestrator promptsbuildPlanPrompt and buildImplementPrompt now
    embed commitmsg.PromptSpec(task.Type) instead of a hardcoded trailer blurb,
    so autonomous agents are told exactly the format the hook enforces. The
    Nightshift-Task / Nightshift-Ref trailers are unchanged.

Format enforced

type(scope)!: subject, header ≤72 chars, imperative mood, no trailing period,
blank line before the body, body wrapped at 72 columns, and a single trailing
block of git trailers.

--fix infers a type when it can (Fixed bug in X.fix: fixed bug in X),
lowercases the type, strips trailing periods, inserts the missing blank line,
reflows body paragraphs (leaving code blocks, indented text, lists and long
URLs alone), hoists stray trailers into one trailing block and dedupes them.
Normalization is idempotent, and an already-conforming message is a byte-exact
no-op. Git's comment lines and --verbose diff are stripped before parsing so a
diff can never leak into a message.

Scope assumption

This makes the repository enforce one format going forward. Rewriting
historical commits is destructive and deliberately out of scope.

Nightshift-Task / Nightshift-Ref are not required of human commits — the
hook would otherwise reject every manual commit. They're required via
Options.RequiredTrailers, which the orchestrator path and agents use.

Testing

Test-driven — internal/commitmsg/commitmsg_test.go was written before the
implementation. Verified with gofmt -l . (clean), go vet ./...,
go build ./..., the full go test ./... suite, and a manual smoke test:

$ printf 'Fixed the thing.' | ./nightshift commit-msg --check -
<stdin>:1: error: header must be "type(scope): subject", got "Fixed the thing." (header-format)
<stdin>:1: error: subject must not end with a period (subject-trailing-period)
<stdin>:1: warning: subject should use the imperative mood ("Fixed" reads as past tense) (subject-mood)
... [spec printed] ...
exit=1

$ printf 'Fixed the thing.' | ./nightshift commit-msg --fix -
fix: fixed the thing

The hook was exercised for real: this PR's own commit passed
scripts/commit-msg.sh during git commit.

🤖 Generated with Claude Code

Add an internal/commitmsg package that parses, validates and normalizes
commit messages against one Conventional Commits format, and expose it
three ways from a single canonical spec: a nightshift commit-msg
subcommand, a commit-msg git hook installed by make install-hooks, and
the orchestrator's plan and implement prompts.

Scope note: this makes the repository enforce one format going forward.
Rewriting historical commits is destructive and deliberately out of
scope.

Nightshift-Task: commit-normalize
Nightshift-Ref: https://github.com/marcus/nightshift
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant