smart-commit
Smart Commit
Analyze uncommitted changes → group by cohesion → generate commit messages → output git commands (or execute directly with --execute).
Workflow
sequenceDiagram
participant C as Claude
participant U as User
C->>C: Step 1a: Detect permissions
C->>C: Step 1b: Learn commit style
C->>C: Step 1c: Identity diagnostics
C->>C: Step 1d: Signing diagnostics
C->>C: Step 1e: AI guard readiness
C->>C: Step 2: Pre-flight check (precommit)
C->>C: Step 3: Collect changes + exclude sensitive files
C->>C: Step 4: Group (high cohesion)
C->>U: Show commit plan (with Author/Signing/Guard)
U->>C: Confirm/adjust
loop Each commit group
C->>C: Read diff → generate message
C->>C: AI trailer sanitization
alt --execute mode
C->>C: Runtime validation → git commit
else manual mode
C->>U: Output git commands
end
end
C->>C: Step 6: Verification + post-commit leak detection
Step 1: Detect Permissions + Learn Style
1a. Permission Detection
Read CLAUDE.md and .claude/rules/git-workflow.md to determine mode:
| Mode | Condition | Behavior |
|---|---|---|
| manual | No --execute flag (default) |
Output commands only |
| execute | --execute flag passed |
Execute directly (with user approval via AskUserQuestion) |
Default to manual mode. Direct execution requires explicit --execute flag regardless of project git restrictions.
--execute mode: When --execute is passed, use AskUserQuestion to show the full commit plan and get explicit user approval before executing. This is a skill-level exception to git-workflow rules (same pattern as /push-ci).
1b. Learn Commit Style
git log --oneline -15
Infer format, type vocabulary, subject conventions (capitalization/tense/ticket ID), and language from recent commits.
1c. Identity Diagnostics
Shared diagnostic (preferred path):
bash scripts/run-skill.sh git-profile git-profile.sh doctor --json
If the script succeeds, parse the JSON output:
status: "ok"→ silent continue, useeffective_identityandsigningfieldsstatus: "warn"→ display warnings fromissues[], continuestatus: "halt"→ display halt issues, stop with guidance
If the script fails (not found, parse error, non-zero exit), fall back to the inline diagnostics below. Infrastructure failure = warn-only; never halt on fallback path itself.
Inline fallback:
# Read effective identity + origin
git config --show-origin --show-scope --get-all user.name
git config --show-origin --show-scope --get-all user.email
# Check environment variable overrides
printf "GIT_AUTHOR_NAME=%s\nGIT_AUTHOR_EMAIL=%s\nGIT_COMMITTER_NAME=%s\nGIT_COMMITTER_EMAIL=%s\n" \
"${GIT_AUTHOR_NAME:-}" "${GIT_AUTHOR_EMAIL:-}" \
"${GIT_COMMITTER_NAME:-}" "${GIT_COMMITTER_EMAIL:-}"
Decision logic:
| Condition | Behavior |
|---|---|
user.name and user.email resolve to single values |
Silent continue, record identity for commit plan |
git config --get user.name returns nothing |
HALT — output git config --local user.name "..." setup guidance |
git config --get user.email returns nothing |
HALT — output git config --local user.email "..." setup guidance |
GIT_AUTHOR_* or GIT_COMMITTER_* env vars set |
Warn: env vars will override config; commit plan shows (env override) |
--get-all returns multiple different values |
AskUserQuestion: list candidate profiles, user selects once |
Conflict + CI=true env var |
HALT (fail-closed) — output fix guidance, do not silently inherit |
Design principles:
- Diagnostic, not override: Do not use
git -c user.name=...to override. RespectincludeIfsettings. - Interrupt only on anomaly: Normal identity resolution produces no prompt.
- Conflict ≠ multiple sources:
includeIfproducing multiple config sources that resolve to the same value = normal.
1d. Signing Diagnostics
git config --show-origin --get commit.gpgsign 2>/dev/null || echo "unset"
git config --show-origin --get user.signingkey 2>/dev/null || echo "unset"
git config --show-origin --get gpg.format 2>/dev/null || echo "gpg"
Decision logic:
| Condition | Behavior |
|---|---|
commit.gpgsign=true + key exists |
Display Signing: enabled (<gpg.format>) |
commit.gpgsign=true + key missing |
⚠️ Warning: signing enabled but key not configured |
commit.gpgsign unset |
Display Signing: not configured (inherit) |
--execute mode signing failure |
Immediate stop + fix guidance |
Post-commit visibility (--execute mode):
git log -1 --format='%G?' # N=unsigned, G=good, U=good-untrusted, etc.
Signing override flags (--sign / --no-sign):
| Flag | Effect | Git flag |
|---|---|---|
--sign |
Force signing for this batch | -S on each git commit |
--no-sign |
Disable signing for this batch | --no-gpg-sign on each git commit |
| Both | Error — mutually exclusive | Halt with error message |
| Neither | Inherit from commit.gpgsign config |
(default behavior) |
When --sign or --no-sign is used, AskUserQuestion to confirm the override and warn about potential branch protection / CI policy conflicts.
1e. AI Guard Readiness
# Detect effective hook path (handles relative paths and worktrees)
HOOK_FILE=$(git rev-parse --git-path hooks/commit-msg 2>/dev/null)
# If core.hooksPath is set, use it instead
CUSTOM_HOOKS=$(git config --get core.hooksPath 2>/dev/null)
if [ -n "$CUSTOM_HOOKS" ]; then
# Resolve relative paths against repo root
case "$CUSTOM_HOOKS" in
/*) ;; # absolute — use as-is
*) CUSTOM_HOOKS="$(git rev-parse --show-toplevel 2>/dev/null)/${CUSTOM_HOOKS}" ;;
esac
HOOK_FILE="${CUSTOM_HOOKS}/commit-msg"
fi
# Check commit-msg hook
if [ -f "$HOOK_FILE" ] && [ ! -x "$HOOK_FILE" ]; then
echo "guard:not-executable"
elif [ -x "$HOOK_FILE" ]; then
echo "guard:installed"
else
echo "guard:missing"
fi
Decision logic:
| Condition | Behavior |
|---|---|
| Hook installed + executable | Display AI guard: active |
| Hook not installed | ⚠️ Warning (non-blocking): suggest install (/install-scripts commit-msg-guard then cp .claude/scripts/commit-msg-guard.sh <hooks-path>/commit-msg && chmod +x <hooks-path>/commit-msg) |
| Hook exists but not executable | ⚠️ Warning: suggest chmod +x <hook-path> |
Important: Hook installation is NOT a blocker for --execute mode. Runtime validation (Step 5c) provides an independent safety layer.
Step 2: Pre-flight Check
If changes include code files (.ts/.js/.py/.go/.rs etc.), check precommit status:
| Status | Action |
|---|---|
Passed /precommit |
Continue |
| Not run or uncertain | Halt — ask user to run /precommit first |
Step 3: Collect Changes
git status --short
git diff --stat
git diff --cached --stat
Classify changes:
| Type | Description |
|---|---|
| staged | Already git add-ed |
| modified | Tracked but unstaged |
| untracked | New files (decide whether to include) |
| deleted | Deleted files |
Exclusion rules (warn user, do not include):
.env* | *.pem | *.key | *.p12 | id_rsa* | .aws/credentials
*.secret | credentials.json | .npmrc | token.txt
node_modules/ | dist/ | .cache/ | files covered by .gitignore
Partial-staged detection: If a file has both staged and unstaged changes (MM in git status), warn user and ask them to resolve first.
--scope filtering: When --scope <path> is specified, only include changes under that path. Apply after collecting all changes:
git status --short -- "${SCOPE_PATH}"
Exclude changes outside the scope path. If no changes remain after filtering → report "No changes under <path>" and stop.
If no changes → report "No uncommitted changes" and stop.
Step 4: Group (High Cohesion)
Each group should form a semantically complete commit.
Grouping strategy (priority order):
- Already staged changes: Respect user intent — separate group (no
git add, justgit commit) - Same feature/module: Group by path prefix + filename semantics
- Same directory changes (e.g.
src/service/xxx/) - Flat files by name prefix
- Controller + Service + Test = complete feature
- Same directory changes (e.g.
- Same type: Pure tests →
test:, pure docs →docs:, pure config →chore: - Related changes:
src/xxx.ts+test/unit/xxx.test.tsin same group - Remaining scattered files: Merge into misc commit or ask user
Group limit: No more than 15 files per commit.
Ticket ID: If {TICKET_PATTERN} is configured, extract ticket ID from branch name:
git rev-parse --abbrev-ref HEAD
Show grouping plan and ask user to confirm. Include identity, signing, and AI guard metadata from Step 1c/1d/1e:
## Commit Plan
**Author**: Jane Doe <jane@company.com> (local config)
**Signing**: enabled (GPG, key: ABCD1234)
**AI guard**: active (commit-msg hook installed)
| # | Type | Files | Summary |
|---|------|-------|---------|
| 1 | fix | 3 | Fix circuit breaker logic |
| 2 | test | 2 | Add RPC client unit tests |
| 3 | docs | 4 | Update performance audit docs |
Adjust grouping?
Step 5: Generate Commits (Loop)
5a. Read diff
git diff -- <files> # unstaged
git diff --cached -- <files> # staged
5b. Generate commit message (following Step 1b inferred style)
- Subject focuses on "what was done", not "which files changed"
- If project convention includes scope →
<type>(<scope>): <subject> - If project convention includes ticket ID → append
[TICKET-ID] --typeoverride: When--type <type>is specified, use that type for all commit groups instead of inferring from changes. Takes precedence over inferred type.
AI trailer sanitization (mandatory, before outputting any commit command):
Scan the generated message for forbidden patterns and strip them silently unless --ai-co-author was explicitly passed:
| Forbidden Pattern | Regex (POSIX ERE, grep -Ei) |
|---|---|
| Co-Authored-By AI | Co-Authored-By:.*(Claude|Anthropic|GPT|OpenAI|Copilot|noreply@anthropic) |
| Generated-by tag | Generated (by|with).*(Claude|Claude Code|AI|GPT|Copilot) |
| Emoji robot tag | 🤖.*(Claude|AI|GPT) |
Note:
\|in the table above is Markdown table escaping. Actual POSIX ERE uses unescaped|. Canonical regex source:scripts/commit-msg-guard.sh(POSIX ERE,grep -Ei)
If any pattern matches and --ai-co-author was not passed → remove the matching line(s) from the message before output/execute.
--ai-co-author narrow whitelist (enforced by runtime validation in Step 5c): When --ai-co-author is passed, only the exact line Co-Authored-By: Claude <noreply@anthropic.com> is permitted. All other AI patterns (Generated by, 🤖, variant Co-Authored-By formats) remain blocked even with --ai-co-author. Note: the commit-msg hook (ALLOW_AI_COAUTHOR=1) bypasses all hook checks — the narrow whitelist is enforced solely by the runtime validate_msg() function, not by the hook.
5c. Output or execute commands
Manual mode — output copy-pasteable commands:
Already staged group (no git add needed):
### Commit 1/3: fix: Fix circuit breaker timeout logic
```bash
git commit -m "$(cat <<'EOF'
fix: Fix circuit breaker timeout logic
EOF
)"
```
Unstaged group (needs git add first):
### Commit 2/3: test: Add RPC client unit tests
```bash
git add test/unit/provider/clients/basic-json-rpc-client.test.ts \
test/unit/utils/concurrence-as-one.test.ts
git commit -m "$(cat <<'EOF'
test: Add RPC client unit tests
EOF
)"
```
Execute mode (--execute) — run commands directly:
- Use
AskUserQuestionto show the full commit plan (all groups) and get approval once - For each approved commit group, execute
git add(if needed) - Runtime validation — before each
git commit, validate the sanitized message via temp file +validate_msg(). See execute-mode.md for full implementation. - After each commit, verify with
git log --oneline -1to confirm success - If any commit fails or runtime validation fails, stop and report the error (do not continue to next group)
With --ai-co-author flag, append trailer and set ALLOW_AI_COAUTHOR=1 (required to pass commit-msg hook):
```bash
ALLOW_AI_COAUTHOR=1 git commit -m "$(cat <<'EOF'
fix: Fix circuit breaker timeout logic
Co-Authored-By: Claude <noreply@anthropic.com>
EOF
)"
```
5d. Continue to next group
Manual mode: Output all groups' git commands at once, prompt user to execute in order. Execute mode: Proceed to next group automatically after successful commit.
Step 6: Verification
Execute mode:
git status --short
- Still has unhandled changes (committable files) → return to Step 4
- Only excluded files remain (
.env*,*.pem, etc.) → stop with warning summary listing excluded files - All clear → output summary table
After each commit, run post-commit AI trailer detection (hard stop on leak): scan git log -1 --format='%B' for forbidden patterns. When --ai-co-author is active, strip the exact allowed line (Co-Authored-By: Claude <noreply@anthropic.com>) before scanning — same logic as validate_msg(). If any remaining match → immediately stop all remaining groups + output amend guidance. See execute-mode.md § Post-commit Detection for implementation. Do NOT auto-amend — amending is a destructive git operation reserved for the developer.
Manual mode:
In manual mode, Step 6 outputs a post-execution checklist (Claude has NOT executed any commands):
## Post-Execution Checklist
After running the commands above:
1. git status --short (verify all changes committed)
2. git log -3 --format='%H%n%B%n----' (verify commit messages have no AI trailers)
Do NOT run git status convergence loops or git log -1 trailer detection in manual mode — the commands have not been executed yet.
AI Co-Author Attribution
| Mode | Condition | Commit Message |
|---|---|---|
| Default | No --ai-co-author flag |
No Co-Authored-By trailer |
| Opt-in | --ai-co-author passed |
Append Co-Authored-By: Claude <noreply@anthropic.com> |
AI attribution is off by default. The developer owns the commit. Only add the trailer when explicitly requested via --ai-co-author.
Prohibited
- No AI signatures by default: Never add
Co-Authored-By,Generated by, or any AI attribution trailer unless--ai-co-authoris explicitly passed - No guessing: If uncertain about file grouping, ask user
- No merging unrelated changes: Better an extra commit than sacrificing cohesion
- No omissions: Must
git statusverify after completion - No secrets: Sensitive files must be warned about, never included
- No unauthorized execution: Without
--executeflag, never directly execute git add/commit - No silent execution: In
--executemode, must useAskUserQuestionfor approval before executing commits
Bundled References
| File | Purpose | When |
|---|---|---|
| execute-mode.md | Runtime validation (validate_msg()) + post-commit AI trailer detection |
--execute mode |
Note: Bash code in
references/execute-mode.mdis a behavioral specification. Claude translates it to allowed tool calls (Bash(git:*)forgit commit -F, etc.) — it is not a standalone script requiring additional shell permissions.
Examples
Input: Help me commit these changes
Action: Detect manual mode → pre-flight check → analyze 20 changes → group into 4 → output 4 sets of git commands
Input: /smart-commit
Action: Detect mode → pre-flight check → analyze 5 changes → all same feature → generate 1 commit
Input: /smart-commit --execute
Action: Override to execute mode → pre-flight check → analyze changes → group → AskUserQuestion approval → git add + git commit each group → git status verify
Input: /smart-commit --execute --ai-co-author
Action: Execute mode + AI co-author → group → approve → git add + git commit (with Co-Authored-By trailer) → verify