(TO THE GOD OF ABRAHAM, ISAAC AND JACOB. I DEDICATE THIS WORK TO YOU MAY YOU BLESS IT AND MAY IT BLESS THOSE YOU USE IT, MORESO MAY THEY KNOW YOU BY NAME, REPENT AND BE LED TO YOUR WILL AND KINGDOM.) Our Father who is in the heavens, let Your Name be set-apart,let Your reign come, let Your desire be done on earth as it is in heaven. Give us today our daily bread. And forgive us our debts, as we for- give our debtors. And do not lead us into trial, but deliver us from the wicked one because Yours is the reign and the power and the esteem, forever. Amen.
Writing conventional commits with AI: a 60-second workflow | devformat.tools Blog
gitaiworkflow

Writing conventional commits with AI: a 60-second workflow

Conventional Commits explained, plus a pre-commit hook that drafts the message from your staged diff. Saves you 30 seconds, 50 times a day.

By devformat.tools · · 4 min read

Writing conventional commits with AI: a 60-second workflow

I spend more time writing commit messages than I'd like to admit. Not because commit messages are hard — because they're tedious. Conventional Commits give them structure; AI fills in the prose. Together you spend two seconds, get a useful message, and move on.

This post explains the standard, then shows the pre-commit hook I actually use.

The standard

Conventional Commits 1.0.0 is a tiny spec. The grammar fits in one paragraph:

<type>[optional scope][!]: <description>

[optional body]

[optional footer(s)]

Required: type and description. Common types:

  • feat: a new feature
  • fix: a bug fix
  • docs: documentation only
  • style: formatting, no logic change
  • refactor: neither feat nor fix
  • perf: performance improvement
  • test: adding or fixing tests
  • build: build system or dependencies
  • ci: CI configuration
  • chore: everything else
  • revert: reverts a previous commit

Scope is freeform (feat(api):, fix(auth):). A ! before the colon or a BREAKING CHANGE: footer marks a breaking change — this is what tools like semantic-release use to compute version bumps.

Examples from a recent week of mine:

feat(rate-limit): allow per-key TTL override
fix(jwt): reject tokens with future iat
docs(readme): clarify install on macOS arm64
refactor(handlers): collapse duplicate error mapping
perf(cache): switch hot path from sync.Map to atomic.Pointer
chore(deps): bump go-redis to v9.10.0

Why bother? Because every modern release-automation tool — semantic-release, release-please, changesets, GitVersion — reads these prefixes to compute versions and generate changelogs. Free CHANGELOG.md is worth the discipline.

The 60-second workflow

The friction point is "I just wrote a commit, now I have to figure out the right type and scope." That's a parsing problem. The model is good at it.

Here's a .git/hooks/prepare-commit-msg hook that drafts a conventional commit message from the staged diff:

#!/usr/bin/env bash
# .git/hooks/prepare-commit-msg
COMMIT_MSG_FILE="$1"
COMMIT_SOURCE="$2"

# Skip if user passed -m or this is a merge/amend
if [ -n "$COMMIT_SOURCE" ]; then
  exit 0
fi

# Skip if there's already a non-empty message
if [ -s "$COMMIT_MSG_FILE" ] && ! grep -qE '^[[:space:]]*$' "$COMMIT_MSG_FILE"; then
  exit 0
fi

DIFF=$(git diff --cached --stat --no-color; echo; git diff --cached --no-color | head -c 6000)

if [ -z "$DIFF" ]; then
  exit 0
fi

PROMPT="Generate a Conventional Commits 1.0.0 message for this diff.
Format: <type>(<scope>): <description>
Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert.
Use lowercase. No quotes. Max 72 chars subject. Add a body only if non-obvious.

DIFF:
$DIFF"

# Call your preferred model — example uses the Anthropic CLI
MSG=$(claude -p "$PROMPT" 2>/dev/null) || exit 0

# Prepend the AI draft, keep any existing comments
{ echo "$MSG"; echo; cat "$COMMIT_MSG_FILE"; } > "$COMMIT_MSG_FILE.tmp"
mv "$COMMIT_MSG_FILE.tmp" "$COMMIT_MSG_FILE"

Save, chmod +x, and now git commit (with no -m) opens your editor with a pre-filled message. Edit if it's wrong, save if it's right.

Two notes on this:

  1. Don't auto-commit. The hook drafts, you review. Models occasionally pick the wrong type (feat vs fix is a judgment call when refactoring a bug into a new abstraction). You're still the author of record.
  2. head -c 6000 keeps token cost bounded. For huge diffs, the model sees the file stat plus the first 6KB of patch text. Good enough to identify scope and type. If the diff is genuinely too big to summarize, the commit is probably too big.

What if I don't want a local hook?

If you don't want to wire shell scripts (corporate machine, polyglot team, prefer browser), the AI Commit Message tool does the same job: paste git diff --cached, get a conventional-format message. Runs in your browser using an in-page model — your diff doesn't go to a third-party API. Useful if your repo has secrets that you'd rather not pipe through someone else's logs.

For commits where you genuinely don't understand what changed (rebase aftermath, merge resolution, refactor you took over from a colleague), the AI Code Explainer is the prior step — paste the diff, get plain-English narration, then commit.

A note on scope and discipline

The biggest gain from conventional commits isn't the automation. It's that the type prefix forces you to think: "is this a feat or a fix?" If you can't decide, your commit is doing two things and should be split. The standard nudges you toward atomic commits without you having to remember to.

I've shipped projects where the entire CHANGELOG.md was generated automatically from commit history and read like it was written by hand. That's the bar. The hook above gets me there with no friction.

Try it

Try our free developer tools

51+ tools that run in your browser. No data sent anywhere.

Browse Tools