Atomic Commit Workflow
Create atomic commits with conventional format. One commit at a time, user-reviewed.
Hook Enforcement (Automatic)
Hooks auto-validate all git commands — no manual checks needed:
| Command | Hook Validates |
|---|---|
| git add | Rejects wildcards, directories, -A, --all |
| git commit | Validates conventional format, rejects AI footers |
Critical Rules
- NEVER chain commands with
&&— each git add, pre-commit, git commit is a separate Bash call - NEVER use wildcards or directories in git add — explicit file paths only
- NEVER add AI footers (Co-Authored-By: Claude, Generated by AI)
- NEVER commit until pre-commit hooks pass (exit code 0)
- Process ONE commit at a time — present, approve, execute, then next
- Include commit body for most commits — skip only for truly trivial changes (typo fix)
Workflow
1. Verify State
pwd
git status --short
git diff --cached --name-only
If files staged: git reset HEAD to unstage first.
2. Analyze Changes
git diff --stat
git log -3 --oneline
3. Group Logically
Separate commits for:
- feat vs test (feature code vs tests)
- feat vs refactor (new vs restructure)
- code vs docs (implementation vs documentation)
- code vs chore (changes vs config)
Atomic = one logical change, buildable, reversible.
4. For Each Commit
a) Present via AskUserQuestion — user MUST approve before execution:
AskUserQuestion(
questions=[{
"question": """Ready to create commit 1/N?
**Commit Message:**
feat(auth): add JWT refresh endpoint
Implements automatic token refresh 5 minutes before expiry.
Tokens are refreshed in background to avoid request delays.
**Files:**
- src/auth.py (+45/-3)
- src/tokens.py (+30)
**Stats:** 2 files, +75/-3
**Commands:**
1. git add src/auth.py src/tokens.py
2. pre-commit run
3. git commit -m "feat(auth): add JWT refresh endpoint
Implements automatic token refresh 5 minutes before expiry.
Tokens are refreshed in background to avoid request delays."
Proceed with this commit?""",
"header": "Commit 1/N",
"options": [
{"label": "Execute", "description": "Stage files, run hooks, and commit"},
{"label": "Skip", "description": "Skip this commit"}
],
"multiSelect": false
}]
)
CRITICAL: The commit body shown in "Commit Message" MUST appear in the git commit command too. Do NOT put explanation only in the dialog.
b) If user selects "Execute", run commands ONE AT A TIME:
# Step 1: Stage (hook validates — no wildcards/directories)
git add src/auth.py src/tokens.py
# Step 2: Pre-commit hooks
pre-commit run
# Step 3: Commit ONLY if hooks pass (exit code 0)
git commit -m "feat(auth): add JWT refresh endpoint
Implements automatic token refresh 5 minutes before expiry.
Tokens are refreshed in background to avoid request delays."
NEVER batch these commands — run each, wait for result, then proceed.
5. Pre-commit Handling
| Exit Code | Action |
|---|---|
| 0 | Proceed to commit |
| Non-zero + files modified | Re-stage same files, retry (max 5 retries) |
| Non-zero + error only | Show error, ask user via AskUserQuestion |
Auto-fixable (retry automatically): ruff-format, ruff, trailing-whitespace, end-of-file-fixer, mixed-line-ending
Semi-automatic (agent fixes, no user action):
- Bandit B904: Add
from eto bare re-raises, re-stage, retry - Gitleaks in docs/comments: Replace with placeholder, re-stage, retry
Retry loop (separate Bash calls):
git add file1.py file2.py # Re-stage
pre-commit run # Retry
# If pass → commit
If manual fix needed, present via AskUserQuestion:
AskUserQuestion(
questions=[{
"question": """Pre-commit hook failed — manual fix required:
**Hook:** {hook_name}
**File:** {file}:{line}
**Error:** {error_message}
How would you like to proceed?""",
"header": "Hook Failed",
"options": [
{"label": "Fix manually", "description": "I'll fix the code, then retry"},
{"label": "Skip commit", "description": "Skip this commit entirely"}
],
"multiSelect": false
}]
)
6. Submodule Handling
When git status shows modified: submodule (modified content):
Step 1: Enter submodule (separate command):
cd submodule
pwd # Verify
Step 2: Commit inside (follow same workflow — AskUserQuestion per commit):
git status
git add file.py
pre-commit run
git commit -m "feat: change description"
Step 3: Return to root (separate command):
cd ..
pwd # Verify
Step 4: Update reference:
git add submodule
git commit -m "chore(submodule): update reference"
CRITICAL: Never chain cd with other commands. Run cd alone, wait for response, then pwd to verify, then proceed.
7. Summary Report
## Commits Created
1. `abc1234` feat(auth): add JWT refresh endpoint
- 2 files, +75/-3
- Pre-commit: Passed
2. `def5678` test(auth): add refresh token tests
- 1 file, +120
- Pre-commit: Passed (2 retries)
Total: 2 commits | 3 files | +195/-3
Commit Message Format
NEVER add AI footers — no "Co-Authored-By: Claude", no "Generated by AI". The hook will block them, but don't generate them in the first place.
<type>(<scope>): <subject>
<body - explain what and why, wrap at 72 chars>
<footer - BREAKING CHANGE: or Fixes #123>
| Part | Rule |
|---|---|
| Subject | Imperative, lowercase, no period, <=50 chars |
| Body | Include for most commits, wrap at 72 chars |
| Footer | BREAKING CHANGE: or issue refs (Fixes #123) |
Types: feat, fix, docs, style, refactor, test, chore, perf, ci, build
Anti-Patterns
# WRONG: Chained commands
git add file.py && pre-commit run && git commit -m "msg"
# WRONG: Wildcards / directories
git add *.py
git add src/
# WRONG: AI footer
git commit -m "feat: add feature
Co-Authored-By: Claude <noreply@anthropic.com>"
# WRONG: Chained cd
cd submodule && git status
# WRONG: Body only in dialog, not in commit
git commit -m "feat: add feature" # Missing body that was shown in AskUserQuestion
