Smart batch commit. Analyzes uncommitted changes, groups by cohesion, generates commit messages matching project style. Default: output git commands for manual execution. With --execute: directly run git add + git commit (requires user approval). Use when: user says 'commit', 'batch commit', 'prepare commit', 'write commit message', or /smart-commit
Scanned 5/27/2026
Install via CLI
openskills install sd0xdev/sd0x-dev-flow---
name: smart-commit
description: "Smart batch commit. Analyzes uncommitted changes, groups by cohesion, generates commit messages matching project style. Default: output git commands for manual execution. With --execute: directly run git add + git commit (requires user approval). Use when: user says 'commit', 'batch commit', 'prepare commit', 'write commit message', or /smart-commit"
allowed-tools: Bash(git:*), Read, Grep, Glob, AskUserQuestion
---
# Smart Commit
Analyze uncommitted changes → group by cohesion → generate commit messages → output git commands (or execute directly with `--execute`).
## Workflow
```mermaid
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**
```bash
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
bash scripts/run-skill.sh git-profile git-profile.sh doctor --json
```
If the script succeeds, parse the JSON output:
- `status: "ok"` → silent continue, use `effective_identity` and `signing` fields
- `status: "warn"` → display warnings from `issues[]`, continue
- `status: "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**:
```bash
# 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. Respect `includeIf` settings.
- **Interrupt only on anomaly**: Normal identity resolution produces no prompt.
- **Conflict ≠ multiple sources**: `includeIf` producing multiple config sources that resolve to the same value = normal.
**1d. Signing Diagnostics**
```bash
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):
```bash
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**
```bash
# 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
Check precommit status based on change type. Structural `.md` files (`skills/`) have test coverage (e.g., `skills-schema.test.js`) that can catch reference errors CI would find.
| Change Type | Required Check | Rationale |
|-------------|---------------|-----------|
| Code files (`.ts/.js/.py/.go/.rs` etc.) | `/precommit` or `/precommit-fast` passed | Code correctness + lint |
| Structural `.md` (`skills/**`) | `/precommit-fast` passed | Schema/ref tests cover SKILL.md structure |
| Other `.md` (README, docs/) | `/codex-review-doc` passed (per CLAUDE.md) | No structural tests; doc review sufficient |
| Comments / trivial whitespace | Skip allowed | No test coverage expected |
| Status | Action |
|--------|--------|
| Required check passed **in current session after last edit** | Continue |
| Not run, stale, or uncertain | **Halt** — ask user to run the required check first |
**Freshness**: A "passed" result is only valid if it ran after the most recent file edits in this session. Stale results from earlier in the session (before new edits) do not count.
**Policy note**: This pre-flight is intentionally stricter than the base auto-loop rule (`@rules/auto-loop.md`), which only requires `/codex-review-doc` for `.md` changes. `/smart-commit` is the last gate before commit — structural `.md` files under `skills/` have test coverage (e.g., `skills-schema.test.js`) that can catch reference errors the doc review alone cannot detect. This extra check prevents CI failures post-push.
**Fast vs full test suite warning**: `/precommit-fast` runs a subset of tests (`test:fast`), while CI runs the full suite (`test:ci`). When changes include deletions (skills, scripts), orphaned test files may only fail in CI. If the pre-flight detects that only `/precommit-fast` was run (not `/precommit`), output: `⚠️ Only fast tests ran. If you deleted files, consider /precommit (full suite) to catch orphaned imports before commit.`
### Step 3: Collect Changes
```bash
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:
```bash
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.
**Session-aware filtering** (default, disable with `--all`):
After collecting changes and applying exclusion/scope rules above, filter by session commit scope:
1. Read `.claude_review_state.json` field `session_commit_scope`
2. Validate `session_commit_scope.session_id` matches current session
3. If `--all` flag passed, or scope is unavailable/invalid → skip filtering (legacy behavior)
4. Otherwise, classify remaining files:
| Condition | Result | Display |
|-----------|--------|---------|
| Already staged (passed safety checks) | **Include** | Normal |
| Unstaged/untracked + in `touched_files` + NOT in `baseline_dirty_files` | **Include** | Normal |
| Unstaged/untracked + in `touched_files` + in `baseline_dirty_files` | **Include** | ⚠️ Warning badge |
| Unstaged/untracked + NOT in `touched_files` | **Exclude** | Show in "Excluded" section |
If all unstaged files are excluded and nothing is staged → report "No session changes to commit. Use `--all` to include pre-existing changes." and stop.
### Step 4: Group (High Cohesion)
Each group should form a semantically complete commit.
**Grouping strategy** (priority order):
1. **Already staged changes**: Respect user intent — separate group (no `git add`, just `git commit`)
2. **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
3. **Same type**: Pure tests → `test:`, pure docs → `docs:`, pure config → `chore:`
4. **Related changes**: `src/xxx.ts` + `test/unit/xxx.test.ts` in same group
5. **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:
```bash
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
**Selection mode**: session-aware (default)
**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 |
### Excluded — pre-existing uncommitted (not touched this session)
| File | Status | Reason |
|------|--------|--------|
| src/legacy.ts | M | Not touched in this session |
| config/dev.json | M | Not touched in this session |
⚠️ `src/config.ts` was already dirty before this session and was edited during this session. Pre-existing changes will be included.
> To include all uncommitted changes: rerun with `--all`
Adjust grouping?
```
### Step 5: Generate Commits (Loop)
**5a. Read diff**
```bash
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]`
- **`--type` override**: 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):
````markdown
### 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):
````markdown
### 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:
1. Use `AskUserQuestion` to show the full commit plan (all groups) and get approval once
2. For each approved commit group, execute `git add` (if needed)
3. **Runtime validation** — before each `git commit`, validate the sanitized message via temp file + `validate_msg()`. See [execute-mode.md](references/execute-mode.md) for full implementation.
4. After each commit, verify with `git log --oneline -1` to confirm success
5. 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):
````markdown
```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**:
```bash
git status --short
```
- Still has unhandled changes (committable files in included set) → return to Step 4
- Only excluded files remain (sensitive files, or session-excluded pre-existing files) → 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](references/execute-mode.md) 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-author` is 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 status` verify after completion
- **No secrets**: Sensitive files must be warned about, never included
- **No unauthorized execution**: Without `--execute` flag, **never** directly execute git add/commit
- **No silent execution**: In `--execute` mode, must use `AskUserQuestion` for approval before executing commits
- **No silent inclusion of pre-existing changes**: Without `--all`, do not silently include unstaged files that were not touched in this session
## Bundled References
| File | Purpose | When |
|------|---------|------|
| [execute-mode.md](references/execute-mode.md) | Runtime validation (`validate_msg()`) + post-commit AI trailer detection | `--execute` mode |
> **Note**: Bash code in `references/execute-mode.md` is a **behavioral specification**. Claude translates it to allowed tool calls (`Bash(git:*)` for `git 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
```
No comments yet. Be the first to comment!