| name | conventional-commit-message |
| description | Generate high-quality Conventional Commit messages for repositories using conventional-changelog-conventionalcommits. |
| license | MIT |
| compatibility | ["Claude Code","Codex","Cursor","Gemini CLI","GitHub Copilot","Windsurf","Cline","Roo Code","Goose","Continue","OpenCode","Amp","universal"] |
| metadata | {"author":"dangreen","tags":["conventional-commits","conventional-changelog","commit-message","changelog","release","monorepo"]} |
Conventional Commit Message
Use this skill when writing, amending, reviewing, or suggesting commit messages for repositories that use the conventional-changelog-conventionalcommits preset or compatible Conventional Commits release tooling.
Commit Shape
Use the Conventional Commits 1.0.0 message structure:
<type>[optional scope][optional !]: <description>
[optional body]
[optional footer(s)]
Rules:
type is required and is followed by an optional parenthesized scope, an optional !, and : .
description is required immediately after : . Keep it concise, changelog-ready, and lower-case unless a proper noun or identifier requires casing. Do not end it with a period.
scope is optional and should name a section of the codebase.
- A longer body is optional, starts one blank line after the description, and is free-form.
- Footers are optional, start one blank line after the body or description, and each use either
Token: value or Token #value.
- Footer tokens use hyphens instead of spaces, such as
Reviewed-by, except for BREAKING CHANGE. BREAKING-CHANGE is synonymous with BREAKING CHANGE.
- Footer values may contain spaces and newlines. A footer value ends when the next valid footer token and separator is observed.
- Conventional Commit units are not case-sensitive, except
BREAKING CHANGE, which must be uppercase when used as that footer token.
- Prefer one commit per coherent change. If one diff contains unrelated release visible changes, recommend splitting it.
- Write the description so it reads well as a bullet in a generated changelog. Avoid vague implementation notes like
add some feature, update stuff, or fix issue. Prefer user-facing outcomes such as support custom scopes, preserve prerelease tags, or require Node.js 22.
- Wrap code identifiers in the description in backticks — function, option, flag, type, or file names:
add `autocase` to match camelCase and kebab-case forms, support `--no-*` negation. GitHub renders backticks in commit messages and generated changelogs preserve them, and they mark words like flag or rest as code where plain prose would be ambiguous.
- Wrap any
@-prefixed token that is not an intentional GitHub user mention in backticks — scoped package names (@types/node), decorators (@Injectable), JSDoc/annotation tags (@deprecated). GitHub parses a bare @name in commit messages and changelogs as a user mention, which can link and notify an unrelated GitHub account.
- Active verb phrases are usually better than passive sentences. Prefer
support custom scopes over custom scopes were added unless passive voice is clearly more natural for the project.
Breaking changes are indicated by ! immediately before : in the header or by a breaking-change footer. Use the footer when the impact is known:
feat(parser)!: remove legacy token fallback
BREAKING CHANGE: custom token fallbacks are no longer applied during parsing.
If ! is used and no breaking-change footer is provided, the description is used to describe the breaking change.
Type Selection
Default visible types:
feat: user-facing feature; appears under Features; bumps minor. Use this when a commit adds a new feature.
feature: accepted alias for feat; prefer feat unless matching existing project style.
fix: bug fix or correctness fix; appears under Bug Fixes; bumps patch. Use this when a commit represents a bug fix.
perf: performance improvement; appears under Performance Improvements; bumps patch.
revert: revert commit; appears under Reverts; bumps patch.
Default hidden types:
docs: documentation-only change.
style: formatting, whitespace, punctuation, or style-only change with no behavior change.
chore: general maintenance that should not be in changelog and should not bump, used only when no more specific hidden type fits.
refactor: code change that intentionally preserves behavior/API.
test: tests only.
build: build system, packaging, dependency metadata, generated build setup.
ci: CI workflow or automation-only change.
Treat hidden types as no-changelog and no-bump intent. If a code change clearly must not affect released behavior or API, use a hidden type instead of fix or feat, even when source files changed.
Types other than feat and fix are allowed by the Conventional Commits specification, but the spec gives them no implicit SemVer effect unless they include a breaking-change marker. This repository's preset assigns additional visibility and bump behavior through its configured commit types.
Do not use chore as a catch-all for every hidden change. Prefer the hidden type that describes the change most precisely: build for package manager, build tooling, dependency metadata, generated build setup, and workspace configuration; ci for CI-only automation; test for tests; docs for documentation; refactor for behavior-preserving code restructuring; and style for formatting-only changes.
Do not add !, BREAKING CHANGE, or Release-As to a hidden commit unless the release impact is intentional. Those signals override the hidden/no-bump intent.
Before choosing a hidden type, first ask whether the diff changes the public contract of any published package, even if the changed files are metadata, configuration, tests, generated files, or CI. Public contract includes supported runtime and platform versions, package exports, CLI behavior, config presets, peer dependency requirements, generated output, documented APIs, and default behavior. If the public contract becomes stricter, removes previously supported usage, or changes user-visible behavior, use a visible type. If previously supported usage no longer works, use ! and a BREAKING CHANGE footer.
Common red flags for release-visible or breaking impact:
- Raising minimum runtime, platform, browser, or engine support.
- Removing, renaming, or narrowing package exports, entry points, CLI flags, options, config presets, or documented APIs.
- Tightening peer dependency ranges or required external tools.
- Changing generated output, defaults, validation rules, parsing behavior, or lint/config rules that consumers receive.
Impact Heuristics
Choose the type by release impact, not by file path:
- Public behavior added:
feat.
- Public behavior corrected:
fix.
- Runtime speed/memory improvement without behavior change:
perf.
- Runtime dependency updates in
dependencies: fix. They change the published package dependency graph, can improve correctness/security/runtime behavior, and should trigger a release even when source code is untouched. Use ! only if the dependency update changes or removes supported public behavior.
- Public contract made stricter or previously supported usage removed:
feat! or fix! with a BREAKING CHANGE footer. Example: raising engines.node to Node.js 22 should use a subject like require Node.js 22.
- Internal rewrite with preserved behavior:
refactor.
- Dependency lockfile/package-manager maintenance, build tooling, generated build setup, or workspace package-manager configuration with no shipped behavior change:
build, unless local style strongly prefers another hidden type.
- CI-only workflow edits:
ci.
- Tests added or adjusted without product code impact:
test.
- Docs/readme/examples prose only:
docs.
- General housekeeping that does not fit the more specific hidden types:
chore.
When unsure whether a source-code change is user-visible, inspect tests and public API usage. If the change is defensive cleanup, migration plumbing, renaming private helpers, lint fixes, or type-only restructuring, prefer a hidden type.
Scope Selection
Before choosing a scope, inspect the repository:
git diff --staged --name-only
git diff --name-only
rg --files -g '*commitlint*' -g '.commitlintrc*' -g 'commitlint.config.*'
rg --files -g 'package.json'
Use staged changes first. If nothing is staged, use the working tree diff.
If the project uses commitlint, read its config before choosing a message. The config may define allowed types, allowed scopes, required casing, max lengths, and other local formatting rules. Common locations include .commitlintrc, .commitlintrc.json, .commitlintrc.yaml, .commitlintrc.yml, .commitlintrc.js, .commitlintrc.cjs, .commitlintrc.mjs, .commitlintrc.ts, commitlint.config.js, commitlint.config.cjs, commitlint.config.mjs, and commitlint.config.ts.
Single-repo projects:
- Usually omit the scope.
- Use a scope only when the project already has clear scopes such as
cli, docs, deps, release, or subsystem names.
Monorepos:
- Prefer the affected package name as scope.
- Derive package scopes from workspace package names, existing commitlint
scope-enum, or recent commits.
- If commitlint or local tooling supports multiple scopes and one coherent release-visible change affects several published packages, use all affected package scopes instead of collapsing the change to one package.
- Prefer the repository's accepted multiple-scope separator. If local config enables multiple scopes but does not make the separator obvious, validate a comma-separated scope list with commitlint before using it.
- If package names are npm-scoped, follow the repo's existing convention: either the full package name or the unscoped package segment.
- If one package changed, use that package scope.
- If several packages changed for one shared reason, use a shared scope such as
deps, dev-deps, or release when that scope adds useful meaning.
- For monorepo-wide release-impacting changes that touch many packages, prefer omitting the scope unless the repo requires one. Release tooling usually uses the changed packages, not a
global scope, to decide which packages are affected.
- If several packages changed for different reasons, first try to describe the common intent honestly. If the only honest message would be vague or hide release impact, then suggest separate commits.
For dependency-only work:
- Use
fix(deps) for runtime dependency updates in dependencies when deps is an accepted scope.
- Use
dev-deps for development-only tooling dependency updates when that is an accepted scope.
Message Construction Workflow
- Inspect changed files and diff summary.
- Identify the released behavior impact.
- Choose visible vs hidden type.
- Choose the narrowest valid scope.
- Write the subject as a changelog bullet from the user's perspective when visible, or from the maintenance intent when hidden.
- Add body/footer only when needed for breaking changes, issue closure, release-as, or important context.
- If the work is explicitly tied to an issue, PR, bug report, or user-provided tracker URL, include the relevant reference in the commit body/footer. For fixes that should close the issue, use a closing footer such as
Fixes #123.. For non-closing context, use a non-closing reference such as Refs #123..
- If commitlint is configured and the CLI is available, validate the candidate before presenting it to the user.
Validate with the local installed commitlint binary first when possible. Prefer ./node_modules/.bin/commitlint over package-manager runners because it avoids package manager environment, sandbox, and registry behavior while still using the repository's installed version and config.
echo 'type(scope): subject' | ./node_modules/.bin/commitlint
Use package-manager runners only as fallback when the local binary is missing and running them is acceptable in the current environment:
echo 'type(scope): subject' | pnpm commitlint
echo 'type(scope): subject' | npm exec commitlint
echo 'type(scope): subject' | npx commitlint
Use the actual candidate message in place of the example. For multi-line messages, use printf or another command that preserves the full message exactly:
printf '%s\n\n%s\n' 'feat(parser)!: remove legacy token fallback' 'BREAKING CHANGE: custom token fallbacks are no longer applied.' | ./node_modules/.bin/commitlint
If commitlint fails, adjust the type, scope, case, or subject length and validate again. If commitlint is configured but the CLI cannot be run in the current environment, tell the user.
Use issue references naturally:
fix(parser): handle empty commit body refs #123
Use closing keywords only when the commit should close an issue:
fix(cli): preserve prerelease tag when appending changelog
Fixes #123.
For coherent changes affecting multiple packages in a monorepo, use multiple scopes when supported by the repository:
fix(package-a,package-b): preserve compare links
Fixes #123.
Examples
Visible release entries:
feat(writer): support scoped package links
feat!: require Node.js 22
feat: add `rest` function to read all remaining arguments
fix(parser): keep issue references from multiline footers
fix(cli): handle `--no-*` flags in strict mode
perf(git-client): avoid duplicate tag lookups
revert: feat(writer): support scoped package links
Hidden no-bump entries:
refactor(conventional-changelog): simplify context defaults
test(writer): cover empty reference groups
docs(conventionalcommits): clarify hidden commit types
build(dev-deps): update oxlint
ci: use node 22 in tests
chore(release): prepare package metadata
Monorepo package scopes:
fix(conventional-changelog-writer): preserve reference order
refactor(conventional-commits-parser): split token normalization
test(git-client): cover lightweight tags
Output Style
When the user asks for a commit message, output only the recommended message unless they ask for explanation or alternatives. For uncertain impact, provide two concise options and explain the release consequence:
fix(parser): handle empty commit body
or, if no release bump is intended:
refactor(parser): handle empty commit body defensively