Contributor guides now match what the hook enforces so failures are obvious before push when core.hooksPath points at .githooks. - Refresh commit.prompt.md hook bullets and the canonical message checklist - Update AGENTS.md workflow paragraph for commit-msg structure plus line cap
6.7 KiB
Git Commit Workflow for AI Assistants
Goals
- Git history should read as a story: small steps, one clear intent per commit.
- Default: one file per commit (smallest practical unit).
- Exception: several files in one commit only when they share the same logical entity / same story (incomplete or misleading if split). This should be unusual—always prefer smaller commits when in doubt.
When to combine files (exception)
Combine only when splitting would:
- leave the tree broken or the feature half-wired, or
- tell an incoherent story (e.g. rename without updating the only call site).
Always state why those paths belong together in one commit when proposing a multi-file commit.
User initiation
❌ AI: "Should I commit these files?"
❌ AI: "Let me commit this..."
✅ Human: "Commit the changed files"
✅ Human: "Do the commits now"
Rule: Do not suggest or initiate commits. Only run this workflow after an explicit user command.
Staging
git add path/to/file.jl
# or, only when justified:
git add path/a.jl path/b.jl
- Avoid
git add .andgit add -Aunless the user explicitly wants everything (still read the full staged diff after). - Never stage unrelated changes into the same commit.
Optional git hooks (.githooks/)
After git config core.hooksPath .githooks:
- pre-commit: rejects commits with more than two staged files. This supports the common “impl + test” or “snippet + verifier” pair without allowing large batches. Split bigger changes across commits or adjust the hook deliberately.
- commit-msg: enforces the subject → blank → summary → blank → bullets
layout, ≥1
- ...detail line, no blanks inside the bullet block, and ≤80 characters per line. Merge commits skip checks while.git/MERGE_HEADis present.
Read the full staged diff
git diff --staged
Critical:
- Do not pipe through
head,tail,less, or otherwise truncate. - Read every line; the commit message must reflect the actual diff.
Exception: machine-generated or binary files—review only as far as is practical.
Approval before git commit
Before running git commit, present to the user:
- File(s) to be committed (exact paths).
- Full proposed commit message (subject + body in the format below).
- If more than one file: a short justification why one commit is appropriate.
Wait for explicit approval. Only then:
git commit -F- <<'EOF'
<paste full message>
EOF
(or equivalent multi-line -m usage). Do not commit silently.
Commit message structure
1. Subject line (first line) — Conventional Commits:
type(scope): specific description from the diff
- Types include
feat,fix,refactor,docs,test,chore, etc. scopeis optional but recommended when it clarifies the area.- Imperative mood; specific; no vague "update" / "fix things".
2. Blank line
3. Summary — one short paragraph, one to three sentences: what changed and why, for this commit only.
4. Blank line (separator before the bullet list).
5. Body detail — - bullets (required when .githooks/commit-msg is active):
After the separator blank line, write at least one - ... line. Scale depth
to the diff: a tiny change can use a single substantive bullet; large or
multi-file patches need grouped, substantive bullets (major types or
functions, behavior, wiring, migrations, caveats) so a reviewer can reconstruct
the story without re-reading every hunk.
Hook-enforced shape (with core.hooksPath → .githooks)
The commit-msg hook (except during merges while .git/MERGE_HEAD exists)
requires, in order:
- non-empty subject line;
- blank line;
- one or more summary lines (none may start like a
-bullet); - blank line;
- one or more lines each starting with
-(details).
Every line must be 80 characters or fewer. There must be no blank lines inside the bullet block. Rewrap before committing.
Example (single file)
refactor(basis): fold lagrange includes behind generator entrypoints
The basis tree listed seven separate lagrange_*.jl includes; they now load
through lagrange_generator.jl and lagrange_generated.jl so registration lives
in one place.
- Drop redundant includes from JuliaFEM.jl
- Note the Basis suffix convention in the include comment
Example (multi-file — requires justification to user)
When proposing:
Files:
src/foo.jl,test/foo/test_foo.jl
Why one commit: addscompute_barand the regression test that locks its behavior; splitting would leave the feature untested in history.
Message: …
Cycle per commit
- Stage the smallest set (usually one file).
git diff --staged— read all of it.- Propose message + paths (+ multi-file justification if needed).
- Wait for approval.
- Commit.
- Repeat for remaining work.
Common mistakes
Vague or mismatched messages
# ❌ WRONG
git commit -m "Update documentation"
# ✅ RIGHT — subject, blank, summary, blank, "- ..." bullets (from the diff)
Bundling unrelated files
# ❌ WRONG — two different stories
git add docs/CONTRIBUTING.md src/unrelated_fix.jl
# ✅ RIGHT — two commits, or ask user to split worktrees if mixed in one working tree
Committing without approval
Never run git commit until the user has approved the proposed paths and message.
Truncating the diff
Never use git diff --staged | head (etc.). Read the full diff.
Checklist before each commit
- Did the user explicitly ask to commit?
- Is the staged set as small as it reasonably can be?
- If multiple files: is there a clear single story, and will you justify it to the user?
- If
.githooksis active: are there at most two staged paths? - Did I read the full
git diff --staged? - Subject line: Conventional Commits, specific, matches diff?
- Is every message line (subject, summary, bullets) ≤ 80 characters?
- Shape: subject, blank, summary paragraph, blank, then ≥1
-bullet (required when the commit-msg hook is enabled)? - Bullets scaled to the diff (one tight bullet is fine for a one-liner patch; large patches need rich, grouped bullets)?
- Did I propose the commit and wait for approval before
git commit?
Why this matters
- Story: History is easy to read and bisect.
- Review: Each commit is reviewable in isolation.
- Honesty: Messages match what the diff actually does.
- Control: The author approves each step.
Git history is long-lived. Prefer small, well-explained commits and explicit approval before each git commit.