Commit Message Generator
Turn the current git state into one or more well-formed Conventional Commit
messages, each confirmed by the user before the commit lands. Never guess
what changed — scripts/analyze-diff.py is the single source of truth.
Hard rules
- Always run
scripts/analyze-diff.py first. Never read the diff manually,
never call git diff / git status / git ls-files yourself. The
script's output is the only input you use to plan groups and write
messages.
- Staged wins. If the script reports
State: staged, work with the
staged files only. Ignore unstaged / untracked files — the user staged
what they meant to stage.
- If the script exits non-zero (nothing to commit), stop and tell the
user.
- Never commit without explicit user confirmation. Print the message,
wait for a yes/edit/no response, then act. Never pass
--no-verify,
never amend, never git push.
- Never add co-author trailers unless the user explicitly asks.
- Never
git add -A / git add .. Stage exactly the paths for the
current group and nothing else.
Process
Step 1 — Inspect state
Run the analyzer:
python3 <skill-dir>/scripts/analyze-diff.py
Two possible states:
State: staged → skip to Step 3 (message generation for the staged
diff).
State: unstaged → continue with Step 2 (propose groups).
Step 2 — Propose commit groups (unstaged state only)
Read Files and propose 1–4 logical commit groups. Apply the heuristics
below. Then:
1 group → stage all files with git add -- <paths> and go to
Step 3. Show the user the single-group plan first only if there is
visible ambiguity; otherwise stage silently and move on.
2+ groups → print a numbered proposal like:
Proposed 2 commits:
[1] feat(api): <files...>
[2] docs: README.md, CHANGELOG.md
Confirm? (yes / edit / merge / cancel)
Responses:
yes / lgtm → iterate groups in order (see Sequential loop).
edit (or free-form feedback like "move X to group 2", "split group 1")
→ revise the proposal, re-print, re-prompt.
merge → treat everything as one group; stage all, go to Step 3.
cancel / no → stop. Do not stage anything.
More than 4 groups → do not propose. Print the file list grouped by
top-level folder and ask the user to narrow scope before rerunning.
Step 3 — Generate the message
Re-run the analyzer if you just staged files, so scope/type signals reflect
what is actually staged now. Then:
Pick the type from Suggested type, overriding only when the diff
clearly says otherwise:
feat — new user-visible capability
fix — bug fix
refactor — behavior-preserving code change
test — tests only
docs — documentation only
chore — tooling, deps, meta files
Anything beyond this list (perf, build, ci, style, revert) or a
breaking change → consult references/conventional-commits.md.
Pick the scope from Likely scopes:
- 1 scope → use it as-is (lowercase, short noun).
- 2 scopes touching one logical change → comma-separated
(
fix(api,ui):).
- >3 scopes → do not force a scope. See Gotchas below.
- Root-only files → omit scope.
- Generic top-level folder (
src, lib, app) → look one level deeper
in the file list for a meaningful scope (e.g. src/api/... → api).
Write the subject. Imperative mood, ≤72 chars, no trailing period.
"add", not "added" or "adds".
Body — only if it earns its keep. Trivial diff → omit. Otherwise 2–5
bullets, one line each, facts only. Do not restate the subject.
Footers. Branch name matches [A-Z]{2,}-\d+ → add Refs: KEY-123.
Breaking / revert / closes → see the reference file.
Step 4 — Confirmation loop
Print the message inside one fenced code block and ask
Commit this? (yes / edit / no).
yes / lgtm / ship it / commit → run git commit -F - with the
message on stdin. Report the resulting hash + subject on one line.
- Feedback (any edit, reword, scope change, "shorter", etc.) → revise and
loop back to the start of this step. Do not commit until confirmed.
no / cancel / abort → cancel semantics (below).
Step 5 — Sequential loop (multi-group runs only)
After each commit, print [N/M] committed <hash> <subject> and start the
next group at Step 2 → staging for that group. Re-run the analyzer
between groups. Stop when all groups are committed or the user cancels.
Grouping heuristics
- One logical change spanning multiple folders (a shared type used in
api/ + ui/, a rename that spans both) → one group.
- Distinct top-level folders with clearly unrelated changes → separate
groups.
- Test changes for a same-run feature → keep with the feature. Tests for
pre-existing code → separate
test group.
- Lockfile-only files (
package-lock.json, yarn.lock,
pnpm-lock.yaml, Cargo.lock, poetry.lock, go.sum) → their own
chore(deps): update lockfile group.
- Docs-only files (README,
docs/, *.md) → their own docs group,
unless the docs ship with a feature in the same diff.
- Config / meta files (
.github/, .gitignore, tooling configs) →
their own chore group unless intertwined with a feature change.
- Cap: max 4 groups per run.
Cancel semantics
If the user cancels a message confirmation mid-run:
- Commits already made in this run stay committed — they were confirmed.
- The current group's staged files are unstaged with
git reset HEAD -- <paths>. File contents on disk are never modified.
- Remaining groups are not started.
- Print a one-line summary:
Committed N of M groups; group K unstaged and skipped.
Message format
<type>[(scope)]: <short imperative subject>
[optional body — 2–5 bullets, one line each]
[optional footer(s)]
Allowed types: feat, fix, refactor, test, docs, chore. Extend
only via the reference file.
Gotchas
- More than 3 distinct scopes in one group. Do not force
chore(misc): or silently drop the scope. Print the scope list and
suggest re-grouping.
- Mixed change types in one group (a
feat + unrelated fix +
docs). Same treatment: name the mix, suggest re-grouping.
- Rename-only diff →
refactor with the new name in the subject.
- Breaking change, revert, merge, unfamiliar footer → read
references/conventional-commits.md and follow its rules. Do not
improvise.
Output
Single-group / already-staged run: one fenced code block with the message,
one confirmation line. On confirmation, one line with hash + subject.
Multi-group run: the numbered group proposal, then per-group
message-confirmation-commit cycles, with a [N/M] committed ... line after
each. On cancel, the one-line summary described in Cancel semantics.
Nothing else. No essays, no explanations of what changed — the log and the
diff already say it.
1---2name: commit-message-generator3description: Generates Conventional Commit messages from staged changes in any git repository, and can auto-stage + group unstaged changes into a sequence of commits. Use when asked to write a commit message, generate a commit, draft a commit for staged changes, commit the current work, or when the user says "commit", "commit message", "commit for staged changes", or "conventional commit".4---56# Commit Message Generator78Turn the current git state into one or more well-formed Conventional Commit9messages, each confirmed by the user before the commit lands. Never guess10what changed — `scripts/analyze-diff.py` is the single source of truth.1112## Hard rules1314- **Always run `scripts/analyze-diff.py` first.** Never read the diff manually,15 never call `git diff` / `git status` / `git ls-files` yourself. The16 script's output is the only input you use to plan groups and write17 messages.18- **Staged wins.** If the script reports `State: staged`, work with the19 staged files only. Ignore unstaged / untracked files — the user staged20 what they meant to stage.21- **If the script exits non-zero** (nothing to commit), stop and tell the22 user.23- **Never commit without explicit user confirmation.** Print the message,24 wait for a yes/edit/no response, then act. Never pass `--no-verify`,25 never amend, never `git push`.26- **Never add co-author trailers** unless the user explicitly asks.27- **Never `git add -A` / `git add .`.** Stage exactly the paths for the28 current group and nothing else.2930## Process3132### Step 1 — Inspect state3334Run the analyzer:3536```37python3 <skill-dir>/scripts/analyze-diff.py38```3940Two possible states:4142- `State: staged` → skip to **Step 3** (message generation for the staged43 diff).44- `State: unstaged` → continue with **Step 2** (propose groups).4546### Step 2 — Propose commit groups (unstaged state only)4748Read `Files` and propose 1–4 logical commit groups. Apply the heuristics49below. Then:5051- **1 group** → stage all files with `git add -- <paths>` and go to52 **Step 3**. Show the user the single-group plan first only if there is53 visible ambiguity; otherwise stage silently and move on.54- **2+ groups** → print a numbered proposal like:5556 ```57 Proposed 2 commits:58 [1] feat(api): <files...>59 [2] docs: README.md, CHANGELOG.md60 Confirm? (yes / edit / merge / cancel)61 ```6263 Responses:64 - `yes` / `lgtm` → iterate groups in order (see **Sequential loop**).65 - `edit` (or free-form feedback like "move X to group 2", "split group 1")66 → revise the proposal, re-print, re-prompt.67 - `merge` → treat everything as one group; stage all, go to **Step 3**.68 - `cancel` / `no` → stop. Do not stage anything.6970- **More than 4 groups** → do not propose. Print the file list grouped by71 top-level folder and ask the user to narrow scope before rerunning.7273### Step 3 — Generate the message7475Re-run the analyzer if you just staged files, so scope/type signals reflect76what is actually staged now. Then:77781. **Pick the type** from `Suggested type`, overriding only when the diff79 clearly says otherwise:80 - `feat` — new user-visible capability81 - `fix` — bug fix82 - `refactor` — behavior-preserving code change83 - `test` — tests only84 - `docs` — documentation only85 - `chore` — tooling, deps, meta files8687 Anything beyond this list (`perf`, `build`, `ci`, `style`, `revert`) or a88 breaking change → consult `references/conventional-commits.md`.89902. **Pick the scope** from `Likely scopes`:91 - 1 scope → use it as-is (lowercase, short noun).92 - 2 scopes touching one logical change → comma-separated93 (`fix(api,ui):`).94 - **>3 scopes** → do not force a scope. See *Gotchas* below.95 - Root-only files → omit scope.96 - Generic top-level folder (`src`, `lib`, `app`) → look one level deeper97 in the file list for a meaningful scope (e.g. `src/api/...` → `api`).98993. **Write the subject.** Imperative mood, ≤72 chars, no trailing period.100 "add", not "added" or "adds".1011024. **Body — only if it earns its keep.** Trivial diff → omit. Otherwise 2–5103 bullets, one line each, facts only. Do not restate the subject.1041055. **Footers.** Branch name matches `[A-Z]{2,}-\d+` → add `Refs: KEY-123`.106 Breaking / revert / closes → see the reference file.107108### Step 4 — Confirmation loop109110Print the message inside one fenced code block and ask111`Commit this? (yes / edit / no)`.112113- `yes` / `lgtm` / `ship it` / `commit` → run `git commit -F -` with the114 message on stdin. Report the resulting hash + subject on one line.115- Feedback (any edit, reword, scope change, "shorter", etc.) → revise and116 loop back to the start of this step. Do not commit until confirmed.117- `no` / `cancel` / `abort` → **cancel semantics** (below).118119### Step 5 — Sequential loop (multi-group runs only)120121After each commit, print `[N/M] committed <hash> <subject>` and start the122next group at **Step 2 → staging** for that group. Re-run the analyzer123between groups. Stop when all groups are committed or the user cancels.124125## Grouping heuristics126127- One logical change spanning multiple folders (a shared type used in128 `api/` + `ui/`, a rename that spans both) → one group.129- Distinct top-level folders with clearly unrelated changes → separate130 groups.131- Test changes for a same-run feature → keep with the feature. Tests for132 pre-existing code → separate `test` group.133- **Lockfile-only files** (`package-lock.json`, `yarn.lock`,134 `pnpm-lock.yaml`, `Cargo.lock`, `poetry.lock`, `go.sum`) → their own135 `chore(deps): update lockfile` group.136- **Docs-only files** (README, `docs/`, `*.md`) → their own `docs` group,137 unless the docs ship *with* a feature in the same diff.138- **Config / meta files** (`.github/`, `.gitignore`, tooling configs) →139 their own `chore` group unless intertwined with a feature change.140- Cap: max 4 groups per run.141142## Cancel semantics143144If the user cancels a message confirmation mid-run:145146- Commits already made in this run stay committed — they were confirmed.147- The current group's staged files are unstaged with148 `git reset HEAD -- <paths>`. File contents on disk are never modified.149- Remaining groups are not started.150- Print a one-line summary: `Committed N of M groups; group K unstaged and151 skipped.`152153## Message format154155```156<type>[(scope)]: <short imperative subject>157158[optional body — 2–5 bullets, one line each]159160[optional footer(s)]161```162163Allowed types: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`. Extend164only via the reference file.165166## Gotchas167168- **More than 3 distinct scopes in one group.** Do not force169 `chore(misc):` or silently drop the scope. Print the scope list and170 suggest re-grouping.171- **Mixed change types in one group** (a `feat` + unrelated `fix` +172 `docs`). Same treatment: name the mix, suggest re-grouping.173- **Rename-only diff** → `refactor` with the new name in the subject.174- **Breaking change, revert, merge, unfamiliar footer** → read175 `references/conventional-commits.md` and follow its rules. Do not176 improvise.177178## Output179180Single-group / already-staged run: one fenced code block with the message,181one confirmation line. On confirmation, one line with hash + subject.182183Multi-group run: the numbered group proposal, then per-group184message-confirmation-commit cycles, with a `[N/M] committed ...` line after185each. On cancel, the one-line summary described in *Cancel semantics*.186187Nothing else. No essays, no explanations of what changed — the log and the188diff already say it.