27 KiB
Semantic Commit
Breaks big changes into small, meaningful commits with proper messages. Uses only standard git commands.
Usage
/semantic-commit [options]
Options
--dry-run: Show proposed commit splits without actually committing--lang <language>: Force language for commit messages (en)--max-commits <number>: Specify maximum number of commits (default: 10)
Basic Examples
# Analyze current changes and commit in logical units
/semantic-commit
# Check split proposal only (no actual commit)
/semantic-commit --dry-run
# Generate commit messages in English
/semantic-commit --lang en
# Split into maximum 5 commits
/semantic-commit --max-commits 5
How It Works
- Analyze Changes: Check what changed with
git diff HEAD - Group Files: Put related files together
- Create Messages: Write semantic commit messages for each group
- Commit Step by Step: Commit each group after you approve
When to Split Changes
What Makes a Change "Large"
We split when we see:
- Many Files: 5+ files changed
- Many Lines: 100+ lines changed
- Multiple Features: Changes in 2+ areas
- Mixed Types: feat + fix + docs together
# Analyze change scale
CHANGED_FILES=$(git diff HEAD --name-only | wc -l)
CHANGED_LINES=$(git diff HEAD --stat | tail -1 | grep -o '[0-9]\+ insertions\|[0-9]\+ deletions' | awk '{sum+=$1} END {print sum}')
if [ $CHANGED_FILES -ge 5 ] || [ $CHANGED_LINES -ge 100 ]; then
echo "Large change detected: splitting recommended"
fi
How to Split into Small, Meaningful Commits
1. Splitting by Functional Boundaries
# Identify functional units from directory structure
git diff HEAD --name-only | cut -d'/' -f1-2 | sort | uniq
# → src/auth, src/api, components/ui, etc.
2. Separation by Change Type
# New files vs existing file modifications
git diff HEAD --name-status | grep '^A' # New files
git diff HEAD --name-status | grep '^M' # Modified files
git diff HEAD --name-status | grep '^D' # Deleted files
3. Dependency Analysis
# Detect import relationship changes
git diff HEAD | grep -E '^[+-].*import|^[+-].*require' | \
cut -d' ' -f2- | sort | uniq
Detailed File Analysis
# Get list of changed files
git diff HEAD --name-only
# Analyze each file's changes individually
git diff HEAD -- <file>
# Determine change type for files
git diff HEAD --name-status | while read status file; do
case $status in
A) echo "$file: New creation" ;;
M) echo "$file: Modification" ;;
D) echo "$file: Deletion" ;;
R*) echo "$file: Renamed" ;;
esac
done
How to Group Files
-
By Feature: Keep related functions together
src/auth/files → Authenticationcomponents/files → UI components
-
By Type: Same kind of changes
- Only tests →
test: - Only docs →
docs: - Only config →
chore:
- Only tests →
-
By Dependencies: Files that need each other
- Model + Migration
- Component + Style
-
By Size: Keep commits manageable
- Max 10 files per commit
- Keep related files together
Output Example
$ /semantic-commit
Analyzing changes...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Detected changes:
• src/auth/login.ts (modified)
• src/auth/register.ts (new)
• src/auth/types.ts (modified)
• tests/auth.test.ts (new)
• docs/authentication.md (new)
Proposed commit splits:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Commit 1/3
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Message: feat: implement user registration and login system
Included files:
• src/auth/login.ts
• src/auth/register.ts
• src/auth/types.ts
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Commit 2/3
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Message: test: add comprehensive tests for authentication system
Included files:
• tests/auth.test.ts
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Commit 3/3
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Message: docs: add authentication system documentation
Included files:
• docs/authentication.md
Execute commit with this split plan? (y/n/edit):
Your Options
y: Go with the proposed splitn: Cancel everythingedit: Change commit messagesmerge <number1> <number2>: Combine commitssplit <number>: Break up a commit more
Dry Run Mode
$ /semantic-commit --dry-run
Analyzing changes... (DRY RUN)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[Commit split proposal display]
ℹ️ DRY RUN mode: No actual commits will be executed
💡 To execute, run again without --dry-run option
Smart Features
1. Understands Your Project
- Detects project type from config files
- Figures out features from folder structure
2. Change Pattern Recognition
# Detect bug fix patterns
- Keywords like "fix", "bug", "error"
- Addition of exception handling
- Condition branch fixes
# Detect new feature patterns
- New file creation
- New method additions
- API endpoint additions
3. Dependency Analysis
- Changes to import statements
- Addition/modification of type definitions
- Relationship with configuration files
How It's Built
Step-by-Step Commits with Git
1. Preprocessing: Save Current State
# Reset unstaged changes if any
git reset HEAD
git status --porcelain > /tmp/original_state.txt
# Check working branch
CURRENT_BRANCH=$(git branch --show-current)
echo "Working branch: $CURRENT_BRANCH"
2. Sequential Commit Execution by Group
# Read split plan
while IFS= read -r commit_plan; do
group_num=$(echo "$commit_plan" | cut -d':' -f1)
files=$(echo "$commit_plan" | cut -d':' -f2- | tr ' ' '\n')
echo "=== Executing commit $group_num ==="
# Stage only relevant files
echo "$files" | while read file; do
if [ -f "$file" ]; then
git add "$file"
echo "Staged: $file"
fi
done
# Check staging status
staged_files=$(git diff --staged --name-only)
if [ -z "$staged_files" ]; then
echo "Warning: No files staged"
continue
fi
# Generate commit message (LLM analysis)
commit_msg=$(generate_commit_message_for_staged_files)
# User confirmation
echo "Proposed commit message: $commit_msg"
echo "Staged files:"
echo "$staged_files"
read -p "Execute this commit? (y/n): " confirm
if [ "$confirm" = "y" ]; then
# Execute commit
git commit -m "$commit_msg"
echo "✅ Commit $group_num completed"
else
# Cancel staging
git reset HEAD
echo "❌ Skipped commit $group_num"
fi
done < /tmp/commit_plan.txt
3. Error Handling and Rollback
# Handle pre-commit hook failures
commit_with_retry() {
local commit_msg="$1"
local max_retries=2
local retry_count=0
while [ $retry_count -lt $max_retries ]; do
if git commit -m "$commit_msg"; then
echo "✅ Commit successful"
return 0
else
echo "❌ Commit failed (attempt $((retry_count + 1))/$max_retries)"
# Incorporate automatic fixes from pre-commit hooks
if git diff --staged --quiet; then
echo "Changes automatically fixed by pre-commit hook"
git add -u
fi
retry_count=$((retry_count + 1))
fi
done
echo "❌ Failed to commit. Please check manually."
return 1
}
# Recover from interruptions
resume_from_failure() {
echo "Detected interrupted commit process"
echo "Current staging status:"
git status --porcelain
read -p "Continue processing? (y/n): " resume
if [ "$resume" = "y" ]; then
# Resume from last commit
last_commit=$(git log --oneline -1 --pretty=format:"%s")
echo "Last commit: $last_commit"
else
# Full reset
git reset HEAD
echo "Process reset"
fi
}
4. Post-Completion Verification
# Verify all changes committed
remaining_changes=$(git status --porcelain | wc -l)
if [ $remaining_changes -eq 0 ]; then
echo "✅ All changes committed"
else
echo "⚠️ Uncommitted changes remain:"
git status --short
fi
# Display commit history
echo "Created commits:"
git log --oneline -n 10 --graph
5. Suppress Automatic Push
# Note: No automatic push
echo "📝 Note: Automatic push not performed"
echo "If needed, push with the following command:"
echo " git push origin $CURRENT_BRANCH"
Split Algorithm Details
Step 1: Initial Analysis
# Get and classify all changed files
git diff HEAD --name-status | while read status file; do
echo "$status:$file"
done > /tmp/changes.txt
# Statistics of changes by functional directory
git diff HEAD --name-only | cut -d'/' -f1-2 | sort | uniq -c
Step 2: Initial Grouping by Functional Boundaries
# Directory-based grouping
GROUPS=$(git diff HEAD --name-only | cut -d'/' -f1-2 | sort | uniq)
for group in $GROUPS; do
echo "=== Group: $group ==="
git diff HEAD --name-only | grep "^$group" | head -10
done
Step 3: Analyzing Change Similarity
# Analyze change type for each file
git diff HEAD --name-only | while read file; do
# Detect new function/class additions
NEW_FUNCTIONS=$(git diff HEAD -- "$file" | grep -c '^+.*function\|^+.*class\|^+.*def ')
# Detect bug fix patterns
BUG_FIXES=$(git diff HEAD -- "$file" | grep -c '^+.*fix\|^+.*bug\|^-.*error')
# Determine if test file
if [[ "$file" =~ test|spec ]]; then
echo "$file: TEST"
elif [ $NEW_FUNCTIONS -gt 0 ]; then
echo "$file: FEAT"
elif [ $BUG_FIXES -gt 0 ]; then
echo "$file: FIX"
else
echo "$file: REFACTOR"
fi
done
Step 4: Dependency-based Adjustments
# Analyze import relationships
git diff HEAD | grep -E '^[+-].*import|^[+-].*from.*import' | \
while read line; do
echo "$line" | sed 's/^[+-]//' | awk '{print $2}'
done | sort | uniq > /tmp/imports.txt
# Group related files
git diff HEAD --name-only | while read file; do
basename=$(basename "$file" .js .ts .py)
related=$(git diff HEAD --name-only | grep "$basename" | grep -v "^$file$")
if [ -n "$related" ]; then
echo "Related files: $file <-> $related"
fi
done
Step 5: Commit Size Optimization
# Adjust group size
MAX_FILES_PER_COMMIT=8
current_group=1
file_count=0
git diff HEAD --name-only | while read file; do
if [ $file_count -ge $MAX_FILES_PER_COMMIT ]; then
current_group=$((current_group + 1))
file_count=0
fi
echo "Commit $current_group: $file"
file_count=$((file_count + 1))
done
Step 6: Determining Final Groups
# Verify split results
for group in $(seq 1 $current_group); do
files=$(grep "Commit $group:" /tmp/commit_plan.txt | cut -d':' -f2-)
lines=$(echo "$files" | xargs git diff HEAD -- | wc -l)
echo "Commit $group: $(echo "$files" | wc -w) files, $lines lines changed"
done
Conventional Commits Compliance
Basic Format
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
Standard Types
Required Types:
feat: New feature (user-visible feature addition)fix: Bug fix
Optional Types:
build: Changes to build system or external dependencieschore: Other changes (no impact on release)ci: Changes to CI configuration files or scriptsdocs: Documentation-only changesstyle: Changes that do not affect code meaning (whitespace, formatting, semicolons, etc.)refactor: Code changes without bug fixes or feature additionsperf: Performance improvementstest: Adding or modifying tests
Scope (Optional)
Indicates the affected area of the change:
feat(api): add user authentication endpoint
fix(ui): resolve button alignment issue
docs(readme): update installation instructions
Breaking Change
When there are breaking API changes:
feat!: change user API response format
or
feat(api)!: change authentication flow
Automatically Detecting Project Conventions
Important: If project-specific conventions exist, they take precedence.
1. Check CommitLint Configuration
Automatically detect configuration from the following files:
commitlint.config.jscommitlint.config.mjscommitlint.config.cjscommitlint.config.ts.commitlintrc.js.commitlintrc.json.commitlintrc.yml.commitlintrc.yamlcommitlintsection inpackage.json
# Check example configuration files
cat commitlint.config.mjs
cat .commitlintrc.json
grep -A 10 '"commitlint"' package.json
2. Detecting Custom Types
Example of project-specific types:
// commitlint.config.mjs
export default {
extends: ["@commitlint/config-conventional"],
rules: {
"type-enum": [
2,
"always",
[
"feat",
"fix",
"docs",
"style",
"refactor",
"test",
"chore",
"wip", // Work in progress
"hotfix", // Emergency fix
"release", // Release
"deps", // Dependency update
"config", // Configuration change
],
],
},
};
3. Detecting Language Settings
// When project uses Japanese messages
export default {
rules: {
"subject-case": [0], // Disable for Japanese support
"subject-max-length": [2, "always", 72], // Adjust character limit for Japanese
},
};
Automatic Analysis Flow
-
Configuration File Search
find . -name "commitlint.config.*" -o -name ".commitlintrc.*" | head -1 -
Existing Commit Analysis
git log --oneline -50 --pretty=format:"%s" -
Type Usage Statistics
git log --oneline -100 --pretty=format:"%s" | \ grep -oE '^[a-z]+(\([^)]+\))?' | \ sort | uniq -c | sort -nr
Examples of Project Conventions
Angular Style
feat(scope): add new feature
fix(scope): fix bug
docs(scope): update documentation
Gitmoji Combined Style
✨ feat: add user registration
🐛 fix: resolve login issue
📚 docs: update API docs
Japanese Projects
feat: add user registration functionality
fix: resolve login process bug
docs: update API documentation
Language Detection
How we figure out your language:
-
Check CommitLint Settings for language configuration
# Determine Japanese if subject-case rule is disabled grep -E '"subject-case".*\[0\]|subject-case.*0' commitlint.config.* -
Git log analysis for automatic determination
# Analyze language of last 20 commits git log --oneline -20 --pretty=format:"%s" | \ grep -E '^[\x{3040}-\x{30ff}]|[\x{4e00}-\x{9fff}]' | wc -l # Japanese mode if over 50% are Japanese -
Project files language settings
# Check README.md language head -10 README.md | grep -E '^[\x{3040}-\x{30ff}]|[\x{4e00}-\x{9fff}]' | wc -l # Check package.json description grep -E '"description".*[\x{3040}-\x{30ff}]|[\x{4e00}-\x{9fff}]' package.json -
Comments and strings analysis in changed files
# Check comment language in changed files git diff HEAD | grep -E '^[+-].*//.*[\x{3040}-\x{30ff}]|[\x{4e00}-\x{9fff}]' | wc -l
Determination Algorithm
# English version always uses English
LANGUAGE="en"
Auto-Loading Config
What Happens at Runtime
We check for config files in this order:
-
Search for CommitLint configuration files
# Search in this order, use first found file commitlint.config.mjs commitlint.config.js commitlint.config.cjs commitlint.config.ts .commitlintrc.js .commitlintrc.json .commitlintrc.yml .commitlintrc.yaml package.json (commitlint section) -
Parse configuration content
- Extract list of available types
- Check for scope restrictions
- Get message length limits
- Check language settings
-
Analyze existing commit history
# Learn usage patterns from recent commits git log --oneline -100 --pretty=format:"%s" | \ head -20
Analyzing Configuration Examples
Standard commitlint.config.mjs:
export default {
extends: ["@commitlint/config-conventional"],
rules: {
"type-enum": [
2,
"always",
["feat", "fix", "docs", "style", "refactor", "perf", "test", "chore"],
],
"scope-enum": [2, "always", ["api", "ui", "core", "auth", "db"]],
},
};
Japanese-compatible configuration:
export default {
extends: ["@commitlint/config-conventional"],
rules: {
"subject-case": [0], // Disable for Japanese
"subject-max-length": [2, "always", 72],
"type-enum": [
2,
"always",
["feat", "fix", "docs", "style", "refactor", "test", "chore"],
],
},
};
Configuration with custom types:
export default {
extends: ["@commitlint/config-conventional"],
rules: {
"type-enum": [
2,
"always",
[
"feat",
"fix",
"docs",
"style",
"refactor",
"test",
"chore",
"wip", // Work in Progress
"hotfix", // Emergency fix
"release", // Release preparation
"deps", // Dependency update
"config", // Configuration change
],
],
},
};
Fallback Behavior
If no configuration file is found:
-
Automatic inference through git log analysis
# Extract types from last 100 commits git log --oneline -100 --pretty=format:"%s" | \ grep -oE '^[a-z]+(\([^)]+\))?' | \ sort | uniq -c | sort -nr -
Default to Conventional Commits standard
feat, fix, docs, style, refactor, perf, test, chore, build, ci -
Language determination
- Japanese mode if over 50% of commits are in Japanese
- English mode otherwise
Requirements
- Must be in a Git repo
- Need uncommitted changes
- Staged changes get reset temporarily
Important
- Won't push: You need to
git pushyourself - Same branch: Commits stay in current branch
- Back up first: Consider
git stashfor safety
Which Rules Win
When making commit messages, we follow this order:
-
CommitLint settings (highest priority)
- Settings in
commitlint.config.*files - Custom types and scope restrictions
- Message length and case restrictions
- Settings in
-
Existing commit history (second priority)
- Statistics of actually used types
- Message language (Japanese/English)
- Scope usage patterns
-
Project type (third priority)
package.json→ Node.js projectCargo.toml→ Rust projectpom.xml→ Java project
-
Conventional Commits standard (fallback)
- Standard behavior when no settings found
Examples of Convention Detection
Automatic scope detection in Monorepo:
# Infer scopes from packages/ folder
ls packages/ | head -10
# → Propose api, ui, core, auth, etc. as scopes
Framework-specific conventions:
// For Angular projects
{
'scope-enum': [2, 'always', [
'animations', 'common', 'core', 'forms', 'http', 'platform-browser',
'platform-server', 'router', 'service-worker', 'upgrade'
]]
}
// For React projects
{
'scope-enum': [2, 'always', [
'components', 'hooks', 'utils', 'types', 'styles', 'api'
]]
}
Company/team-specific conventions:
// Common pattern in Japanese companies
{
'type-enum': [2, 'always', [
'feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore',
'wip', // Work in progress (for pull requests)
'hotfix', // Emergency fix
'release' // Release preparation
]],
'subject-case': [0], // Support Japanese
'subject-max-length': [2, 'always', 72] // Longer limit for Japanese
}
Best Practices
- Follow the rules: Use existing patterns
- Keep it small: One logical change per commit
- Be clear: Say what changed
- Group smart: Related files together
- Tests separate: Test commits on their own
- Use configs: CommitLint helps teams stay consistent
Real-world Split Examples (Before/After)
Example 1: Large Authentication System Addition
Before (one massive commit):
# Changed files (15 files, 850 lines changed)
src/auth/login.js # New
src/auth/register.js # New
src/auth/password.js # New
src/auth/types.js # New
src/api/auth-routes.js # New
src/middleware/auth.js # New
src/database/migrations/001_users.sql # New
src/database/models/user.js # New
tests/auth/login.test.js # New
tests/auth/register.test.js # New
tests/api/auth-routes.test.js # New
docs/authentication.md # New
package.json # Dependency addition
README.md # Usage addition
.env.example # Environment variable example
# Problematic conventional commit
feat: implement complete user authentication system with login, registration, password reset, API routes, database models, tests and documentation
After (split into 5 meaningful commits):
# Commit 1: Database foundation
feat(db): add user model and authentication schema
Included files:
- src/database/migrations/001_users.sql
- src/database/models/user.js
- src/auth/types.js
Reason: Database structure is the foundation for other features, committed first
# Commit 2: Authentication logic
feat(auth): implement core authentication functionality
Included files:
- src/auth/login.js
- src/auth/register.js
- src/auth/password.js
- src/middleware/auth.js
Reason: Core authentication business logic committed together
# Commit 3: API endpoints
feat(api): add authentication API routes
Included files:
- src/api/auth-routes.js
Reason: API layer depends on authentication logic, committed later
# Commit 4: Comprehensive tests
test(auth): add comprehensive authentication tests
Included files:
- tests/auth/login.test.js
- tests/auth/register.test.js
- tests/api/auth-routes.test.js
Reason: Tests added after implementation is complete
# Commit 5: Documentation and configuration
docs(auth): add authentication documentation and configuration
Included files:
- docs/authentication.md
- package.json
- README.md
- .env.example
Reason: Documentation and configuration committed together at the end
Example 2: Mixed Bug Fixes and Refactoring
Before (problematic mixed commit):
# Changed files (8 files, 320 lines changed)
src/user/service.js # Bug fixes + Refactoring
src/user/validator.js # New (refactoring)
src/auth/middleware.js # Bug fix
src/api/user-routes.js # Bug fix + Error handling improvement
tests/user.test.js # Test addition
tests/auth.test.js # Bug fix test addition
docs/user-api.md # Documentation update
package.json # Dependency update
# Problematic commit
fix: resolve user validation bugs and refactor validation logic with improved error handling
After (split into 3 commits by type):
# Commit 1: Critical bug fixes
fix: resolve user validation and authentication bugs
Included files:
- src/user/service.js (bug fix portion only)
- src/auth/middleware.js
- tests/auth.test.js (bug fix tests only)
Reason: Production-affecting bugs fixed with highest priority
# Commit 2: Validation logic refactoring
refactor: extract and improve user validation logic
Included files:
- src/user/service.js (refactoring portion)
- src/user/validator.js
- src/api/user-routes.js
- tests/user.test.js
Reason: Structural improvements committed as functional units
# Commit 3: Documentation and dependency update
chore: update documentation and dependencies
Included files:
- docs/user-api.md
- package.json
Reason: Development environment improvements committed together at the end
Example 3: Simultaneous Development of Multiple Features
Before (cross-functional massive commit):
# Changed files (12 files, 600 lines changed)
src/user/profile.js # New feature A
src/user/avatar.js # New feature A
src/notification/email.js # New feature B
src/notification/sms.js # New feature B
src/api/profile-routes.js # New feature A API
src/api/notification-routes.js # New feature B API
src/dashboard/widgets.js # New feature C
src/dashboard/charts.js # New feature C
tests/profile.test.js # New feature A tests
tests/notification.test.js # New feature B tests
tests/dashboard.test.js # New feature C tests
package.json # All features' dependencies
# Problematic commit
feat: add user profile management, notification system and dashboard widgets
After (split into 4 commits by feature):
# Commit 1: User profile feature
feat(profile): add user profile management
Included files:
- src/user/profile.js
- src/user/avatar.js
- src/api/profile-routes.js
- tests/profile.test.js
Reason: Profile feature is an independent functional unit
# Commit 2: Notification system
feat(notification): implement email and SMS notifications
Included files:
- src/notification/email.js
- src/notification/sms.js
- src/api/notification-routes.js
- tests/notification.test.js
Reason: Notification feature is an independent functional unit
# Commit 3: Dashboard widgets
feat(dashboard): add interactive widgets and charts
Included files:
- src/dashboard/widgets.js
- src/dashboard/charts.js
- tests/dashboard.test.js
Reason: Dashboard feature is an independent functional unit
# Commit 4: Dependencies and infrastructure update
chore: update dependencies for new features
Included files:
- package.json
Reason: Common dependency updates committed together at the end
Comparison of Splitting Effects
| Item | Before (Massive Commit) | After (Proper Splitting) |
|---|---|---|
| Reviewability | ❌ Very difficult | ✅ Each commit is small and reviewable |
| Bug Tracking | ❌ Difficult to identify problem location | ✅ Problematic commits can be immediately identified |
| Reverting | ❌ Need to revert everything | ✅ Can pinpoint and revert only problematic parts |
| Parallel Development | ❌ Conflict-prone | ✅ Feature-based merging is easy |
| Deployment | ❌ All features deployed at once | ✅ Staged deployment possible |
Troubleshooting
When Commit Fails
- Check pre-commit hooks
- Resolve dependencies
- Retry with individual files
When Splitting is Inappropriate
- Adjust with
--max-commitsoption - Use manual
editmode - Re-run with finer granularity