Commit Messages
Conventional-commit format enforced by commitlint and a husky commit-msg hook.
Every commit in this monorepo is validated against the Conventional Commits specification. The check is wired into a husky commit-msg hook that runs commitlint against the message you just wrote — if the message does not match the rules, the commit is rejected and nothing is written to history.
Source of truth. The rules live in commitlint.config.js at the monorepo root, and the enforcement is wired in .husky/commit-msg. If this page disagrees with those files, the files win — open a PR to fix the docs.
The format
type(scope): short description [optional body] [optional footer]
type— one of the prefixes listed below. Required.scope— optional free-form hint at what area of the code is affected (e.g.ui,gateway,docs). Wrapped in parentheses.- short description — imperative mood, lower-case, no trailing period. Think "add button variant", not "Added a Button variant."
- body / footer — optional. Use the body to explain why the change was made. Use footers for
BREAKING CHANGE:notices or issue references.
The repo extends @commitlint/config-conventional, so the default conventional-commit rules apply (subject length, non-empty subject, lower-case type, etc.) on top of the custom type-enum defined below.
Allowed types
| Type | Use when... |
|---|---|
feat | You are adding a new user-facing feature or a new public API. |
fix | You are fixing a bug — something that used to behave incorrectly now behaves correctly. |
chore | Routine maintenance that does not touch source behaviour: bumping dependencies, tidying configs, housekeeping. |
ci | You are changing CI configuration — GitHub Actions workflows, husky hooks, turbo pipelines, release tooling. |
docs | Documentation-only changes — MDX in apps/docs, READMEs, inline JSDoc, concept notes, openspec specs. |
refactor | You are restructuring code without changing its observable behaviour. No new features, no bug fixes. |
test | Adding or updating tests. No production code changes. |
variant | Adding or adjusting a CVA variant on a UI component in @itu/ui (monorepo-specific type — see note below). |
style | Formatting, whitespace, missing semicolons, Prettier/ESLint auto-fixes. No code logic changes. |
perf | A code change that improves performance without changing behaviour. |
build | Changes to the build system, bundlers, or external dependencies (tsup, turbo build config, package.json build scripts). |
revert | Reverting a previous commit. The body should reference the reverted SHA. |
variant is monorepo-specific. The standard conventional-commit spec does not include variant — this repo adds it because CVA variant tweaks are frequent enough in @itu/ui to deserve their own prefix. Anything you would normally tag feat(ui) or style(ui) for a pure variant change can use variant instead.
Passing examples
A well-formed conventional commit looks like this:
feat(ui): add compact density to DataTable
Scope is optional — skip it when the change is cross-cutting:
docs: document the commit message convention
Longer message with a body explaining why:
fix(gateway): strip HTML from search snippets The WP REST API returns rendered HTML in `excerpt.rendered`. Passing that straight to the search index broke highlight ranges in the docs search modal.
A refactor with no behavioural change:
refactor(ui): extract Button variant config into cva factory
A variant tweak using the monorepo-specific variant type:
variant(ui): tighten Hero spacing on the compact variant
Failing examples
Each failing example below is followed by the exact error commitlint prints when the commit is rejected. The ⧗ input: line echoes your message, and the ✖ line explains what broke.
Missing type
added a new button variant
⧗ input: added a new button variant ✖ subject may not be empty [subject-empty] ✖ type may not be empty [type-empty] ✖ found 2 problems, 0 warnings ⓘ Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint husky - commit-msg script failed (code 1)
Commitlint cannot parse a subject at all because there is no type: prefix, so both type-empty and subject-empty fire.
Unknown type
feature(ui): add compact density to DataTable
⧗ input: feature(ui): add compact density to DataTable ✖ type must be one of [feat, fix, chore, ci, docs, refactor, test, variant, style, perf, build, revert] [type-enum] ✖ found 1 problems, 0 warnings ⓘ Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint husky - commit-msg script failed (code 1)
feature is not in the allowed list — use feat instead.
Upper-case type
Feat(ui): add compact density to DataTable
⧗ input: Feat(ui): add compact density to DataTable ✖ type must be lower-case [type-case] ✖ type must be one of [feat, fix, chore, ci, docs, refactor, test, variant, style, perf, build, revert] [type-enum] ✖ found 2 problems, 0 warnings ⓘ Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint husky - commit-msg script failed (code 1)
@commitlint/config-conventional enforces lower-case types, and type-enum matches case-sensitively — so Feat trips both rules.
Empty subject
chore:
⧗ input: chore: ✖ subject may not be empty [subject-empty] ✖ found 1 problems, 0 warnings ⓘ Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint husky - commit-msg script failed (code 1)
A type without a subject is not a message — describe what you actually did.
How the hook runs
The commit-msg hook is a one-liner:
npx --no -- commitlint --edit $1
$1 is the path to the temporary file that git uses to hold your message. --edit tells commitlint to read from that file, and --no makes npx refuse to fetch commitlint from the network — it must already be installed locally (it is, via the root package.json devDependencies). If the hook exits non-zero, git aborts the commit before it is written to history.
Fixing a rejected commit
When the hook rejects your message, git has not created the commit yet — your staged changes are still staged. Just run git commit again with a corrected message:
git commit -m "feat(ui): add compact density to DataTable"
If you had already opened the editor and written a long body, use:
git commit --edit
to re-open the last attempted message in your editor and fix it in place.
What not to do
- Do not bypass the hook with
--no-verify. It exists for a reason — see the git hooks guide for why skipping hooks is actively discouraged in this repo. - Do not pad types onto a non-conforming message just to make the hook happy (
chore: fix stuff). Pick the type that matches reality. - Do not use past tense (
fix: fixed button). Conventional commits use imperative mood:fix: fix button hover state.
Related
- Branch naming — the sibling convention for branch names.
- Git hooks — the full tour of husky hooks, including why
--no-verifyis off-limits.