References (archive): SCAFFOLD_SKILLS_ARCHIVE_MAP.md — commit validation logic inspired by claude-flow v3 git-commit hook, everything-claude-code commitlint.
Step 1: Validate Commit Message
Validate a commit message string against Conventional Commits format:
Format: <type>(<scope>): <subject>
Types:
-
feat : A new feature
-
fix : A bug fix
-
docs : Documentation only changes
-
style : Code style changes (formatting, etc.)
-
refactor : Code refactoring
-
perf : Performance improvements
-
test : Adding or updating tests
-
chore : Maintenance tasks
-
ci : CI/CD changes
-
build : Build system changes
-
revert : Reverting a previous commit
Validation Rules:
-
Must start with type (required)
-
Scope is optional (in parentheses)
-
Subject is required (after colon and space)
-
Use imperative, present tense ("add" not "added")
-
Don't capitalize first letter
-
No period at end
-
Can include body and footer (separated by blank line) </execution_process>
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 }; }
</code_example>
<code_example> Valid Examples:
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
</code_example>
<code_example> Invalid Examples:
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
</code_example>
<code_example> Pre-commit Hook (.git/hooks/pre-commit ):
#!/bin/bash commit_msg=$(git log -1 --pretty=%B) if ! node .claude/tools/validate-commit.mjs "$commit_msg"; then echo "Commit message validation failed" exit 1 fi
</code_example>
<code_example> CI/CD Integration:
.github/workflows/validate-commits.yml
- name: Validate commit messages run: | git log origin/main..HEAD --pretty=%B | while read msg; do node .claude/tools/validate-commit.mjs "$msg" || exit 1 done
</code_example>
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" ] }
</formatting_example>
Validate a commit message
node .claude/tools/validate-commit.mjs "feat(auth): implement jwt login"
Validate from stdin (e.g. in a hook)
echo "fix: incorrect variable name" | node .claude/tools/validate-commit.mjs
</usage_example>
Iron Laws
-
ALWAYS validate commit messages in both pre-commit hook and CI — pre-commit catches local violations; CI catches cases where the hook was bypassed or not installed; both layers are required.
-
NEVER accept commit messages without a type prefix — conventional commit format (type: subject ) is the foundation; messages without a type are unparseable for changelog generation and semantic versioning.
-
ALWAYS enforce subject line length limit (72 characters) — subjects over 72 characters are truncated in git log --oneline and GitHub PR views; conciseness is enforced, not just encouraged.
-
NEVER block commits for body/footer format issues — only type, subject, and length are blocking; optional sections (body, footer, co-authorship) should warn, not block.
-
ALWAYS provide the correct format example in rejection messages — error messages without examples cause developers to guess the format; show feat: add user authentication alongside every rejection.
Anti-Patterns
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
Memory Protocol (MANDATORY)
Before starting: Read .claude/context/memory/learnings.md
After completing:
-
New pattern -> .claude/context/memory/learnings.md
-
Issue found -> .claude/context/memory/issues.md
-
Decision made -> .claude/context/memory/decisions.md
ASSUME INTERRUPTION: If it's not in memory, it didn't happen.