Validates commit messages against Conventional Commits specification using programmatic validation. Replaces the git-conventional-commit-messages text file with a tool that provides instant feedback.
References (archive): SCAFFOLD_SKILLS_ARCHIVE_MAP.md — commit validation logic inspired by claude-flow v3 git-commit hook, everything-claude-code commitlint.
Validate a commit message string against Conventional Commits format:
Format: <type>(<scope>): <subject>
Types:
feat: A new featurefix: A bug fixdocs: Documentation only changesstyle: Code style changes (formatting, etc.)refactor: Code refactoringperf: Performance improvementstest: Adding or updating testschore: Maintenance tasksci: CI/CD changesbuild: Build system changesrevert: Reverting a previous commitValidation Rules:
Use this regex pattern for validation:
const CONVENTIONAL_COMMIT_REGEX =
/^(feat|fix|docs|style|refactor|perf|test|chore|ci|build|revert)(\(.+\))?: .{1,72}/;
function validateCommitMessage(message) {
const lines = message.trim().split('\n');
const header = lines[0];
// Check format
if (!CONVENTIONAL_COMMIT_REGEX.test(header)) {
return {
valid: false,
error: 'Commit message does not follow Conventional Commits format',
};
}
// Check length
if (header.length > 72) {
return {
valid: false,
error: 'Commit header exceeds 72 characters',
};
}
return { valid: true };
}
feat(auth): add OAuth2 login support
fix(api): resolve timeout issue in user endpoint
docs(readme): update installation instructions
refactor(components): extract common button logic
test(utils): add unit tests for date formatting
Added new feature # Missing type
feat:new feature # Missing space after colon
FEAT: Add feature # Type should be lowercase
feat: Added feature # Should use imperative tense
#!/bin/bash
commit_msg=$(git log -1 --pretty=%B)
if ! node .claude/tools/cli/validate-commit.mjs "$commit_msg"; then
echo "Commit message validation failed"
exit 1
fi
# .github/workflows/validate-commits.yml
- name: Validate commit messages
run: |
git log origin/main..HEAD --pretty=%B | while read msg; do
node .claude/tools/cli/validate-commit.mjs "$msg" || exit 1
done
Returns structured validation result:
{
"valid": true,
"type": "feat",
"scope": "auth",
"subject": "add OAuth2 login support",
"warnings": []
}
Or for invalid messages:
{
"valid": false,
"error": "Commit message does not follow Conventional Commits format",
"suggestions": [
"Use format: <type>(<scope>): <subject>",
"Valid types: feat, fix, docs, style, refactor, perf, test, chore, ci, build, revert"
]
}
# Validate a commit message
node .claude/tools/cli/validate-commit.mjs "feat(auth): implement jwt login"
# Validate from stdin (e.g. in a hook)
echo "fix: incorrect variable name" | node .claude/tools/cli/validate-commit.mjs
type: subject) is the foundation; messages without a type are unparseable for changelog generation and semantic versioning.feat: add user authentication alongside every rejection.| Anti-Pattern | Why It Fails | Correct Approach |
|---|---|---|
| Validating only in CI (not pre-commit) | Developers don't discover format issues until after push | Add pre-commit hook for local instant feedback |
| Blocking on body/footer format | Excessive friction leads developers to bypass hooks | Only block on missing type prefix and subject length |
| Rejection without format example | Developer must guess the correct format | Always show a passing example in the error message |
| Allowing freeform subject without type | Breaks changelog generation and semantic versioning | Require type: subject format unconditionally |
| Single-line validation (no body check) | Missing Co-Authored-By and footer go undetected | Validate presence of required footers when configured |
Before starting:
Read .claude/context/memory/learnings.md
After completing:
.claude/context/memory/learnings.md.claude/context/memory/issues.md.claude/context/memory/decisions.mdASSUME INTERRUPTION: If it's not in memory, it didn't happen.