| name | commit |
| description | MUST use before any commit operation. Detects VCS type (prefers jujutsu over git), enforces atomic commits, validates messages. Required before: 'git commit', 'jj commit', 'commit these changes', 'let me commit', 'ready to commit', 'git add', creating commits, branch operations, pull requests. |
Commit
Overview
Good commits enable clear history, easy debugging, and confident collaboration.
Bad commits create confusion, make debugging harder, and complicate code review.
Core principle: Every commit tells a clear story and is independently reviewable.
If the project uses jujutsu,
you MUST load and follow the jujutsu skill before running any commands.
Do not rely on your own knowledge of jujutsu.
Detecting Project Conventions (Required)
Before your first commit,
analyze recent commits for project conventions.
If recent commits are already visible in the conversation,
use those — do not fetch history redundantly.
State explicitly: "This project [uses/doesn't use] conventional commits."
If you skip this step, you will violate project conventions.
Convention Detection Checklist
Follow project convention if it exists, otherwise use general guidelines below.
Commit Cadence
Commit after each logical change, not after finishing all work.
If you have multiple uncommitted logical changes, stop.
Split them into separate commits before proceeding.
Atomic Commits
Definition: One logical change per commit.
The commit is a unit of reasoning, not a unit of effort.
What Makes a Commit Atomic?
Good atomic commits:
- Add a single feature with its tests
- Fix one bug with its test
- Refactor one component (no behaviour change)
- Update related configuration files together
- Include necessary prerequisites (e.g., add helper function + use it)
Bad atomic commits:
- Fix three unrelated bugs
- Add feature + unrelated refactoring
- Mix formatting changes with logic changes
- Change multiple unrelated files "while you're at it"
- Add two independent parsers or modules in one commit
- Bundle a review's worth of fixes into one commit
Separate Mechanical from Functional Changes
Mechanical changes (renames, moves, reformatting, dead code removal)
must be in separate commits from functional changes (new behaviour, bug fixes).
A reviewer can verify a mechanical commit in seconds
by confirming no behaviour changed.
Mixing them forces line-by-line review of the entire diff.
Generated-Code Commits
Machine-generated files ship in their own commit, never mixed with
hand-written changes — not even the hand-written source the generator
was run against.
Detection: a file is generated if it carries a generator banner
(Code generated by ... DO NOT EDIT.), is marked linguist-generated
in .gitattributes, or matches a known generator output pattern
(*.pb.go, *.pb.gw.go, *_mock.go, *_mock_test.go,
mocks/*.go, sdk/*/generated/*, *.gen.go, *.gen.ts).
Required commit message: chore: regenerate code (a scope is allowed —
e.g. chore(pb): regenerate code — but the subject's verb phrase is
always "regenerate code"). Do not invent a feat:/fix: title that
describes the upstream source change; the source change has its own
commit.
The commit contains only generator output. The hand-written input that
triggered regeneration (the .proto file, the interface whose mock was
regenerated, the OpenAPI spec) belongs in a separate commit alongside
the rest of the intent.
Reviewer flow this enables: skim the chore: regenerate code commit to
confirm it is mechanical output, then review the intent commit on its
own merits.
Breaking Changes
A change is breaking when it removes, renames, or alters the signature
of a public symbol (an exported Go identifier, a published API
endpoint or response field, a CLI flag, a proto message field, a
configuration key) such that an existing caller will fail to compile
or no longer interoperate. Adding optional fields, adding new
endpoints, or deprecating without removing are not breaking.
Required commit format (Conventional Commits):
- Subject:
<type>(<scope>)!: <description> — the ! immediately
before the colon marks the breaking change. The scope is optional;
the ! is not. Examples: feat(api)!: rename UserResponse.name to displayName, refactor(db)!: rename ConnectDB to OpenDB,
feat!: drop Python 3.8 support.
- Body: must contain a footer line beginning with the literal token
BREAKING CHANGE: (uppercase; BREAKING-CHANGE: with a hyphen is
also valid per the spec). The text after the token names the
migration path: what callers must change, and what to replace
removed symbols with.
The ! on the subject is for tooling and changelog generators; the
BREAKING CHANGE: footer is for the human reading the log. Both are
required, even when one feels redundant.
Anti-pattern: describing the breakage in prose only ("this renames
ConnectDB to OpenDB; callers should switch") without the ! marker
or the BREAKING CHANGE: token. Tooling will not detect the break,
and downstream consumers will not learn the migration path from
git log --grep.
The Atomic Commit Test
Before every commit, verify:
- Can I describe this commit in one sentence without using "and"?
- If this commit were reverted, would it cleanly undo one logical change?
- Could someone review this commit in isolation and understand it?
If any answer is "no", split the commit.
When to Split Commits
Split: Multiple bugs, feature + refactoring, independent components,
substantial preparation work, mechanical + functional changes
Don't split: Function definition + usage, test + implementation,
config + feature that needs it
Commit Message Structure
See commit-messages.md
for detailed format, examples, and templates.
Key requirements:
- Subject: 50 chars (max 72), imperative mood
- Describe why, not what — the diff shows what changed;
the message must supply the reasoning the diff cannot convey
- Body: Optional, explain context when not obvious
- Follow project conventions (conventional commits if used)
Pre-Commit Review (Required)
Before running any commit command, you must complete all steps below.
Step 1: Enumerate Changes
Review the diff and list every distinct modification
(renamed function, added function, changed import, added parameter, etc.).
Step 2: Verify Atomicity
Apply the atomic commit test to the enumerated list:
- Could any change be committed independently?
- Are there mechanical changes mixed with functional changes?
- Would reverting this commit undo more than one decision?
If yes to any: you must split into separate commits.
Do not ask — unstage, re-stage selectively,
and commit each change separately.
Step 3: Review Content
Check for debug statements, commented-out code,
TODO/FIXME markers, unintended files.
Step 4: Stage Selectively
Stage specific files, not git add . or blind inclusion.
Step 5: Validate Message
Only after all steps pass may you create the commit.
Post-Commit Verification
After committing, review message and diff. Verify:
If check fails: git commit --fixup=<sha> against the specific commit.
Run git rebase --autosquash before pushing if the commit is
unpublished; otherwise leave the fixup as a follow-up.
Never git commit --amend. It modifies whatever HEAD points to and
silently clobbers commits added by concurrent processes
(e.g. a parallel agent).
Cleaning Up Unpublished History
Before pushing, rewrite local history so it reads as if planned
from the start.
- Fold fix-ups into the commit that introduced the issue,
so each commit stays atomic and self-explanatory.
- Keep commits that capture a significant decision or step in
the reasoning — those belong in the history, not compressed away.
- Only rewrite unpublished commits. Once pushed, create a
follow-up commit per fix (atomic, never batched).
Anti-Patterns
If you are about to say or think any of these phrases, STOP IMMEDIATELY:
Anti-Patterns:
- "commit all changes" / "git add ." / "add all files" -> Stage selectively
- "fixes A, B, and C" / "adds X and fixes Y" -> Split into atomic commits
- "skip checking conventions" -> Check conventions first
- "amend this commit" / "git commit --amend" -> Use
git commit --fixup=<sha>; amend follows HEAD blindly and can clobber a concurrent commit
- "Quick commit" / "WIP commit" -> Not without user request
- "I'll commit everything at the end" -> Commit after each logical change
- "These are related so I'll commit them together" -> Apply the atomic commit test
- "add X and Y parsers/modules" -> Each independent module is a separate commit
- "review fixes" / "various improvements" / "address review findings" / "post-review fixes" -> Each fix is a separate atomic commit
- "refactor and fix" -> Mechanical and functional changes are separate commits
- "ship the regenerated proto with the feature" -> Generated diff is its own
chore: regenerate code commit
- "feat: add endpoint (includes regenerated mocks)" -> Generated files never share a commit with hand-written intent
- "refactor: rename Foo to Bar (callers must update)" -> Renaming a public symbol is breaking; subject needs
! and body needs BREAKING CHANGE: footer
- "the diff makes it obvious it's breaking" -> Tooling reads the
! marker and the BREAKING CHANGE: token, not the diff
Jujutsu-specific anti-patterns are covered in the jujutsu skill.
When detected:
- Stop immediately
- State which requirement you skipped
- Complete the requirement before proceeding
- Resume with correct approach
Examples
See commit-messages.md
for good and bad commit message examples.