Skip to content

chore: Strengthen commitlint validation rules #97

@iamfj

Description

@iamfj

Title

chore: Strengthen commitlint validation rules


Labels

chore, dx


Body

Problem

The current commitlint configuration (commitlint.config.js) only extends @commitlint/config-conventional without any project-specific overrides. This leaves several gaps where commits that violate project conventions (as documented in AGENTS.md and CONTRIBUTING.md) pass validation silently.

Gaps identified

1. Warnings treated as non-blocking for structural rules

body-leading-blank and footer-leading-blank are warnings (severity 1) in the default config. Commits that omit the required blank line after the subject still exit 0 and are accepted.

# This passes (exit 0) — blank line missing between subject and body
printf "feat: subject\nno blank line" | npx commitlint
# ⚠ body must have leading blank line [body-leading-blank]
# ⚠ found 0 problems, 1 warnings  ← exit 0

Expected: These should be errors (severity 2) to enforce well-formed commit structure.

2. No scope-case enforcement

Scopes accept any casing. Uppercase, mixed-case, and otherwise inconsistent scopes all pass.

echo "feat(UPPER): test"       | npx commitlint  # ✅ passes
echo "feat(MixedCase): test"   | npx commitlint  # ✅ passes

Expected: scope-case should enforce lower-case for consistency.

3. No minimum subject length

Single-character subjects pass, allowing meaningless commit messages.

echo "feat: x" | npx commitlint  # ✅ passes

Expected: subject-min-length should enforce a reasonable minimum (e.g. 10 characters) to require descriptive messages.

4. Type enum mismatch with CONTRIBUTING.md

The default config-conventional type enum includes 11 types:
build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test

CONTRIBUTING.md documents only 9 types — missing ci and revert.

Type In commitlint In CONTRIBUTING.md
ci
revert

Both ci and revert are actively used in the git history (fix(ci):, Revert "...") so the fix here is likely updating CONTRIBUTING.md to document them rather than removing them from the enum — but the discrepancy should be resolved explicitly.

5. No CI-level commit message validation

Commitlint only runs as a local lefthook commit-msg hook. The CI workflow (.github/workflows/ci.yml) does not validate commit messages. This means:

  • Contributors who skip or lack lefthook can push non-conforming commits
  • GitHub merge commits bypass validation entirely
  • Squash-merge subjects edited in the GitHub UI are never checked

Expected: Add a CI job that runs commitlint --from on PR commits (e.g. using @commitlint/config-conventional with the commitlint GitHub Action or a manual npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }}).

6. No enforcement of the "no AI co-author" rule

AGENTS.md states:

No AI co-author trailers. Do not add Co-authored-by for AI assistants.

This is currently a documentation-only rule with no automated enforcement.

printf "feat: add feature\n\nCo-authored-by: AI <ai@bot.com>" | npx commitlint  # ✅ passes

This is harder to enforce reliably via commitlint alone (there's no built-in trailer-content rule). Options include a commitlint plugin, a custom lefthook script, or a CI check. This item is lower priority but worth tracking.

Proposed changes

Change File Complexity
Upgrade body-leading-blank to error commitlint.config.js trivial
Upgrade footer-leading-blank to error commitlint.config.js trivial
Add scope-case: [2, "always", "lower-case"] commitlint.config.js trivial
Add subject-min-length: [2, "always", 10] commitlint.config.js trivial
Align type enum with CONTRIBUTING.md (add ci + revert to docs) CONTRIBUTING.md trivial
Add commitlint CI job for PRs .github/workflows/ci.yml small
Investigate AI co-author trailer enforcement TBD separate issue

Acceptance criteria

  • commitlint.config.js overrides the five rules listed above
  • CONTRIBUTING.md type table matches the commitlint type enum
  • CI runs commitlint on PR commit ranges
  • All existing commits on main still pass (no retroactive breakage)
  • Verification: the six test cases from "Gaps identified" behave as expected

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions