plugins/commitlint/skills/commitlint/SKILL.md
When setting up commit message validation for a project. When project has commitlint.config.js or .commitlintrc files. When configuring CI/CD to enforce commit format. When extracting commit rules for LLM prompt generation. When debugging commit message rejection errors.
npx skillsauth add jamie-bitflight/claude_skills commitlintInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Validate commit messages against Conventional Commits format using commitlint configuration and rules.
Use this skill when:
Commitlint uses cosmiconfig to find configuration files in this priority order:
Dedicated config files:
.commitlintrc
.commitlintrc.json
.commitlintrc.yaml
.commitlintrc.yml
.commitlintrc.js
.commitlintrc.cjs
.commitlintrc.mjs
.commitlintrc.ts
.commitlintrc.cts
commitlint.config.js
commitlint.config.cjs
commitlint.config.mjs
commitlint.config.ts
commitlint.config.cts
Package files:
package.json with commitlint fieldpackage.yaml (PNPM) with commitlint fieldJavaScript ES Modules (Recommended):
// commitlint.config.js or commitlint.config.mjs
export default {
extends: ['@commitlint/config-conventional'],
};
TypeScript:
// commitlint.config.ts
import type { UserConfig } from '@commitlint/types';
import { RuleConfigSeverity } from '@commitlint/types';
const config: UserConfig = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [RuleConfigSeverity.Error, 'always', ['feat', 'fix', 'docs']],
},
};
export default config;
JSON:
{
"extends": ["@commitlint/config-conventional"]
}
YAML:
extends:
- "@commitlint/config-conventional"
Rules are configured as arrays: [level, applicability, value]
Severity Levels:
| Level | Meaning | TypeScript Enum |
| ----- | -------- | ----------------------------- |
| 0 | Disabled | RuleConfigSeverity.Disabled |
| 1 | Warning | RuleConfigSeverity.Warning |
| 2 | Error | RuleConfigSeverity.Error |
Applicability:
'always' - Rule must match'never' - Rule must not matchExample rules:
rules: {
// Error if type is not in enum
'type-enum': [2, 'always', ['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'build', 'ci', 'chore', 'revert']],
// Error if type is empty
'type-empty': [2, 'never'],
// Error if subject is empty
'subject-empty': [2, 'never'],
// Error if header exceeds 100 chars
'header-max-length': [2, 'always', 100],
// Error if subject ends with period
'subject-full-stop': [2, 'never', '.'],
// Warning if body doesn't have leading blank line
'body-leading-blank': [1, 'always'],
}
The most widely used shareable configuration. Default error-level rules:
| Rule | Configuration | Pass Example | Fail Example |
| ------------------------ | ------------------------------------------------------------------------------------------------ | ------------------------ | ------------------- |
| type-enum | ['build', 'chore', 'ci', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'style', 'test'] | fix: message | foo: message |
| type-case | 'lowerCase' | fix: message | FIX: message |
| type-empty | never | fix: message | : message |
| subject-case | never + ['sentence-case', 'start-case', 'pascal-case', 'upper-case'] | fix: some message | fix: Some Message |
| subject-empty | never | fix: message | fix: |
| subject-full-stop | never, '.' | fix: message | fix: message. |
| header-max-length | 100 | Short header | Header > 100 chars |
| body-leading-blank | always (warning) | Blank line before body | No blank line |
| body-max-line-length | 100 | Lines <= 100 chars | Line > 100 chars |
| footer-leading-blank | always (warning) | Blank line before footer | No blank line |
| footer-max-line-length | 100 | Lines <= 100 chars | Line > 100 chars |
// commitlint.config.js
export default {
// Extend shareable configs (resolved via node resolution)
extends: ['@commitlint/config-conventional'],
// Parser preset for parsing commit messages
parserPreset: 'conventional-changelog-atom',
// Output formatter
formatter: '@commitlint/format',
// Custom rules (override inherited rules)
rules: {
'type-enum': [2, 'always', ['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore']],
},
// Functions that return true to ignore specific commits
// Merged with default ignores (merge commits, reverts, semver tags)
ignores: [(commit) => commit.includes('WIP')],
// Whether to use default ignore patterns
// Default patterns: 'Merge pull request', 'Revert X', 'v1.2.3', etc.
defaultIgnores: true,
// Custom help URL shown on failure
helpUrl: 'https://example.com/commit-guidelines',
// Prompt configuration (for @commitlint/cz-commitlint)
prompt: {
messages: {},
questions: {
type: {
description: 'Select the type of change:',
},
},
},
};
# npm
npm install -D @commitlint/cli @commitlint/config-conventional
# yarn
yarn add -D @commitlint/cli @commitlint/config-conventional
# pnpm
pnpm add -D @commitlint/cli @commitlint/config-conventional
# Lint the last commit
npx commitlint --last
# Lint a range of commits
npx commitlint --from HEAD~5
# Lint from a specific commit
npx commitlint --from abc1234
# Lint message from stdin
echo "feat: add feature" | npx commitlint
# Lint message from file
npx commitlint < .git/COMMIT_EDITMSG
# Lint with edit flag (reads .git/COMMIT_EDITMSG)
npx commitlint --edit
# Lint with custom config path
npx commitlint --config ./custom-commitlint.config.js
# Print resolved config
npx commitlint --print-config
# Strict mode (warnings become exit code 2)
npx commitlint --last --strict
| Code | Meaning | | ---- | ---------------------------------- | | 0 | Success (no errors) | | 1 | Lint errors found | | 2 | Warnings found (strict mode only) | | 3 | Errors found (strict mode) | | 9 | Config file missing (with -g flag) |
| Rule | Condition | Default Applicability |
| ----------------- | ------------------------------------- | ------------------------ |
| type-enum | type is found in value | always |
| type-case | type is in case value | always, 'lower-case' |
| type-empty | type is empty | never |
| type-max-length | type has value or less characters | always, Infinity |
| type-min-length | type has value or more characters | always, 0 |
| Rule | Condition | Default Applicability |
| -------------------------- | ---------------------------------------- | --------------------- |
| subject-case | subject is in case value | always |
| subject-empty | subject is empty | never |
| subject-full-stop | subject ends with value | never, '.' |
| subject-max-length | subject has value or less characters | always, Infinity |
| subject-min-length | subject has value or more characters | always, 0 |
| subject-exclamation-mark | subject has exclamation before : | never |
| Rule | Condition | Default Applicability |
| ------------------ | -------------------------------------- | ------------------------ |
| scope-enum | scope is found in value | always, [] |
| scope-case | scope is in case value | always, 'lower-case' |
| scope-empty | scope is empty | never |
| scope-max-length | scope has value or less characters | always, Infinity |
| scope-min-length | scope has value or more characters | always, 0 |
| Rule | Condition | Default Applicability |
| ------------------- | ------------------------------------------- | ------------------------ |
| header-case | header is in case value | always, 'lower-case' |
| header-full-stop | header ends with value | never, '.' |
| header-max-length | header has value or less characters | always, 72 |
| header-min-length | header has value or more characters | always, 0 |
| header-trim | header has no leading/trailing whitespace | always |
| Rule | Condition | Default Applicability |
| ---------------------- | ------------------------------------------------------------ | ------------------------ |
| body-leading-blank | body begins with blank line | always |
| body-empty | body is empty | never |
| body-max-length | body has value or less characters | always, Infinity |
| body-max-line-length | body lines have value or less characters (URLs excluded) | always, Infinity |
| body-min-length | body has value or more characters | always, 0 |
| body-case | body is in case value | always, 'lower-case' |
| body-full-stop | body ends with value | never, '.' |
| Rule | Condition | Default Applicability |
| ------------------------ | ---------------------------------------------- | --------------------- |
| footer-leading-blank | footer begins with blank line | always |
| footer-empty | footer is empty | never |
| footer-max-length | footer has value or less characters | always, Infinity |
| footer-max-line-length | footer lines have value or less characters | always, Infinity |
| footer-min-length | footer has value or more characters | always, 0 |
For rules that check case (*-case):
[
'lower-case', // lowercase
'upper-case', // UPPERCASE
'camel-case', // camelCase
'kebab-case', // kebab-case
'pascal-case', // PascalCase
'sentence-case', // Sentence case
'snake-case', // snake_case
'start-case', // Start Case
]
import load from '@commitlint/load';
async function getCommitlintConfig() {
const config = await load();
console.log(config.rules);
return config;
}
import load from '@commitlint/load';
import lint from '@commitlint/lint';
async function validateMessage(message) {
const config = await load();
const result = await lint(message, config.rules);
return {
valid: result.valid,
errors: result.errors,
warnings: result.warnings,
};
}
Generate LLM-friendly constraints from commitlint config:
def extract_rules_for_prompt(config: dict) -> str:
"""Extract commitlint rules into LLM-friendly format."""
rules = config.get('rules', {})
prompt_parts = []
# Extract type-enum if present
if 'type-enum' in rules:
level, applicability, types = rules['type-enum']
if level > 0 and applicability == 'always':
prompt_parts.append(f"Allowed commit types: {', '.join(types)}")
# Extract scope-enum if present
if 'scope-enum' in rules:
level, applicability, scopes = rules['scope-enum']
if level > 0 and applicability == 'always' and scopes:
prompt_parts.append(f"Allowed scopes: {', '.join(scopes)}")
# Extract header-max-length
if 'header-max-length' in rules:
level, applicability, length = rules['header-max-length']
if level > 0:
prompt_parts.append(f"Header must be {length} characters or less")
# Extract subject-case
if 'subject-case' in rules:
level, applicability, cases = rules['subject-case']
if level > 0 and applicability == 'never':
prompt_parts.append(f"Subject must NOT use: {', '.join(cases)}")
return '\n'.join(prompt_parts)
import subprocess
async def validate_with_commitlint(message: str, cwd: Path | None = None) -> tuple[bool, list[str]]:
"""
Validate commit message with commitlint.
Args:
message: The commit message to validate
cwd: Working directory (defaults to current directory)
Returns:
Tuple of (is_valid, error_messages)
"""
result = subprocess.run(
['npx', 'commitlint'],
input=message,
capture_output=True,
text=True,
cwd=cwd,
)
if result.returncode == 0:
return True, []
# Parse errors from stderr
errors = [line.strip() for line in result.stderr.split('\n') if line.strip()]
return False, errors
Validation loop pattern:
Node v24 ESM issues:
Use .mjs extension for ES modules config, or add "type": "module" to package.json
Missing extends:
Config without extends or rules fails with "Please add rules" error. Include at least one:
export default {
extends: ['@commitlint/config-conventional'],
};
Empty config error:
Config file must have at least extends or rules defined.
Scope enum empty array:
scope-enum with [] passes all scopes. Use specific array to restrict:
rules: {
'scope-enum': [2, 'always', ['api', 'ui', 'docs']],
}
Subject case trap:
@commitlint/config-conventional uses never with specific cases, meaning those cases are forbidden (not required):
// This forbids sentence-case, start-case, pascal-case, upper-case
'subject-case': [2, 'never', ['sentence-case', 'start-case', 'pascal-case', 'upper-case']]
For pre-commit hook integration with commitlint, activate the pre-commit skill:
Skill(skill: "python3-development:pre-commit")
For Conventional Commits format specification and examples, activate the conventional-commits skill:
Skill(skill: "conventional-commits:conventional-commits")
This skill was created from reference documentation at the commit-polish repository (2025-12-01)
development
When an application needs to store config, data, cache, or state files. When designing where user-specific files should live. When code writes to ~/.appname or hardcoded home paths. When implementing cross-platform file storage with platformdirs.
testing
Enforce mandatory pre-action verification checkpoints to prevent pattern-matching from overriding explicit reasoning. Use this skill when about to execute implementation actions (Bash, Write, Edit) to verify hypothesis-action alignment. Blocks execution when hypothesis unverified or action targets different system than hypothesis identified. Critical for preventing cognitive dissonance where correct diagnosis leads to wrong implementation.
tools
Reference guide for the Twelve-Factor App methodology — 15 principles (12 original + 3 modern extensions) for building portable, resilient, cloud-native applications. Use when evaluating application architecture, designing cloud-native services, reviewing codebases for methodology compliance, advising on configuration, scaling, observability, security, and deployment patterns. Incorporates the 2025 open-source community evolution and cloud-native reinterpretations of each factor.
tools
Converts user-facing documentation (how-to guides, tutorials, API references, examples) in any format — Markdown, PDF, DOCX, PPTX, XLSX, AsciiDoc, RST, HTML, Jupyter notebooks, man pages, TOML/YAML/JSON configs, and plain text — into Claude Code skill directories with SKILL.md plus thematically grouped references/*.md files. Use when given a docs directory or mixed-format documentation to transform into an AI skill. Uses MCP file-reader server for binary formats.