Skip to content

Commit Message Conventions

When to use: Adopting or retrofitting commit message standards in a repo that uses git, npm, and agent-assisted delivery — including library ports and customer clones of the HoEN pattern.

Instructions

Goal

Give humans and agents a single, enforceable commit contract:

  1. Header — Conventional Commits with a 72-character subject cap.
  2. Body — optional; when present, starts after a blank line; long bodies open with a 👓 bottom-line line.
  3. Footer — optional Co-Authored-By trailer for agent attribution.
  4. Enforcement — commitlint via husky locally; CI backstop on PRs where the platform supports it.

Pair this skill with git-hygiene for branch policy; this skill covers message shape and lint wiring only.

Message contract

Header (required)

<type>(<scope>): <subject>

Allowed types: feat, fix, docs, refactor, test, chore, ci, build, perf, style, revert.

Scope is freeform — name the area touched (skills, library-maintenance, agents, repo, â€Ļ).

Subject rules enforced by commitlint:

  • max 72 characters (including type, scope, and colon)
  • no forced sentence case

Body (optional, 👓 when long)

When the change needs more than a one-liner, open the body with the bottom line up front. The 72-char subject is the headline; the first body line is the 👓 summary (emoji prefix, no colon); detail follows below.

commitlint enforces body-leading-blank and footer-leading-blank so the subject, body, and footer stay visually distinct in git log.

Example:

feat(skills): add git-hygiene enforcement

👓 block direct commits to main; require a feature branch.

The commit-msg hook now refuses commits made on main and prompts for a
feature branch. Rationale, edge cases, and rollout notes follow here.

Co-Authored-By: Cursor (Composer 2.5), Driven by Alex Example đŸ•šī¸

Co-Authored-By trailer (optional)

Use one Co-Authored-By: line per distinct agent session that contributed to the change. Pack the model(s) and human driver into that single line:

Co-Authored-By: <Agent or IDE> (<Model[, Model ...]>), Driven by <Human> đŸ•šī¸

Rules:

  • one trailer per agent session, not per file or turn
  • do not add a separate Co-Authored-By for the human git author
  • drop context-window tags (e.g. "1M context") from the agent label
  • keep the đŸ•šī¸ emoji — it is part of the convention

commitlint disables body/footer line-length limits so long 👓 bodies and trailers pass without --no-verify.

File layout baseline

Store tooling config under library-maintenance/config/ when the repo follows the HoEN library layout; otherwise use <repo-root>/ or an existing config/ folder — but keep one canonical commitlint config path and reference it from husky, npm scripts, and CI.

.
├── .husky/
│   └── commit-msg          # runs commitlint on each commit
├── library-maintenance/
│   └── config/
│       └── commitlint.config.mjs
├── package.json            # prepare + commitlint scripts
└── AGENTS.md               # human + agent policy (copy from reference)

Canonical templates live in this skill folder:

Asset Purpose
assets/commitlint.config.mjs Shared commitlint rules
assets/husky-commit-msg Husky hook template
assets/package-json-scripts.json npm script + devDependency snippet
assets/github-commitlint-workflow.yml GitHub Actions PR backstop
references/agents-md-commit-section.md Drop-in AGENTS.md section
references/azure-pipelines-commitlint-step.md Azure DevOps PR step

Retrofit rollout (existing repository)

Run in order:

  1. Copy config — place commitlint.config.mjs at the chosen config path.
  2. Wire husky — copy .husky/commit-msg; ensure package.json has "prepare": "husky" and commitlint devDependencies (see asset snippet).
  3. Add npm scripts — at minimum commitlint (hook) and lint:commits (branch range check).
  4. Document policy — add or merge the AGENTS.md section from references/agents-md-commit-section.md; link from CONTRIBUTING.md.
  5. Add CI backstop — GitHub: copy the workflow asset; Azure DevOps: add the step from references/azure-pipelines-commitlint-step.md.
  6. Bootstrap locally — run npm install once per clone so husky registers.
  7. Verify — test a good message and a deliberately bad header:
npm install
echo "bad message" | npx commitlint --config <config-path>
echo "feat(repo): test commitlint wiring" | npx commitlint --config <config-path>
npm run lint:commits   # after at least one conventional commit on the branch

Transplant to a customer or sibling library

When porting from HoEN to a licensed clone (e.g. Fonterra PRM):

  1. Copy the assets verbatim unless the target already has an equivalent path.
  2. Point husky and npm scripts at the same --config path as CI.
  3. Merge the AGENTS.md section; adjust scope examples (docs, internal team names) but keep types, 72-char cap, 👓 opener, and trailer rules unchanged.
  4. Record the port in skill provenance.source (ported_from, source_commit) on any new skills — commit conventions stay identical across ports.
  5. If the target uses Azure DevOps instead of GitHub, skip the workflow asset and use the Azure reference step.

Platform notes

Platform Local enforcement CI backstop
GitHub husky commit-msg .github/workflows/commitlint.yml
Azure DevOps husky commit-msg PR pipeline step (see reference)
No Node in repo not supported by this pattern document manual review only

Husky v9 expects the hook file at .husky/commit-msg with no shebang required; invoke commitlint via node_modules/.bin/commitlint.


References


Troubleshooting

Symptom Likely cause Fix
Hook never runs npm install not run after clone Run npm install; confirm .husky/commit-msg exists
commitlint: command not found devDependencies missing Add @commitlint/cli and @commitlint/config-conventional; reinstall
CI fails but local passes CI uses default root config Pass --config or set configFile in the workflow
header-max-length failures Subject too long Shorten subject; move detail to 👓 body
body must have leading blank line Subject glued to body Insert blank line between header and body
Co-Authored-By rejected Wrong commitlint rules Ensure body-max-line-length and footer-max-line-length are disabled
Contributors use --no-verify No CI backstop Add PR commitlint job; document in CONTRIBUTING.md