Stop Writing Commit Messages: Generating AI-Ready Git Summaries with gitaiflow
How gitaiflow turns a plain `git diff` into a structured, commit-ready summary — using whatever AI provider you already have, free tier or paid, local or cloud.
CB
I keep ending up with the same two kinds of commits: the careful ones I write when I have time to think about what actually changed, and the rushed ones — fix stuff, wip, updates — that I write when I don't. Neither is really about laziness. Writing a good commit message means re-reading your own diff and translating it into plain English, every single time, and that's exactly the kind of small repetitive task an AI model is good at.
gitaiflow is the CLI I built to do that translation for me. Point it at a path, it reads the diff, sends it to an AI provider, and writes back one JSON file with a ready-to-use commit title and body. It works with a free Google Gemini key, a fully local Ollama model, or any OpenAI-compatible endpoint — OpenAI, Groq, DeepSeek, OpenRouter, vLLM, LM Studio — so it isn't locked to one vendor.
Here's the shape of a single run:
1. One Command, Any AI Provider You Already Have
gitaiflow doesn't ship with its own AI model, and it won't run at all until you point it at one — that's a deliberate choice, not a missing feature. Instead, it works with whatever you already have access to:
- OpenRouter — the recommended default. Point it at
openrouter/freeand it auto-selects an available free model for you, no model name to maintain. - Gemini — Google's free tier, cloud-hosted.
- Ollama — fully local, no API key, no cost, as long as the model name matches what
ollama listshows you. - Any OpenAI-compatible endpoint — OpenAI, Groq, DeepSeek, self-hosted vLLM or LM Studio, or anything else that speaks the same API shape.
All of it lives in a .env file. Here's the minimal OpenRouter setup:
# .env — anywhere from your current directory up to your repo root AI_PROVIDER=custom AI_BASE_URL=https://openrouter.ai/api/v1 AI_API_KEY=<your-openrouter-api-key> AI_MODEL=openrouter/free
You don't have to put this file in the exact folder you run gitaiflow from — it walks up from wherever you are to your repository root looking for it. A real shell environment variable always wins over the .env file if both set the same key, which is handy for one-off overrides without editing a file.
If you're using OpenRouter and want a specific paid or free model instead of the auto-router, you don't have to go hunt for model IDs by hand:
gitaiflow --list-models --free-only
This queries OpenRouter's live catalog every time you run it, so newly added or removed models just show up — gitaiflow doesn't keep its own stale copy of the list. It works independently of whatever AI_PROVIDER you have configured today, and it never changes your saved AI_MODEL — it only looks things up.
2. How a Diff Actually Turns Into a Commit Message
Once a provider is configured, generating a summary is one command:
gitaiflow --path mailer/ --print-commit mailer: add retry backoff for failed sends - Added exponential backoff retry logic in retry.py - tasks.py now retries send_mail up to 3 times on failure - No changes to public function signatures
Point --path at a directory or a single file. When it's a directory, gitaiflow groups the changed files by their top-level subdirectory and generates one summary per group instead of one giant summary for the whole diff — this keeps each individual AI request small enough to stay under provider context limits and free-tier token quotas. If you'd rather have one request cover everything, --no-chunk turns that grouping off.
--print-commit is meant to be piped straight into git:
gitaiflow --path . --print-commit > /tmp/msg.txt && git commit -F /tmp/msg.txt
Before any of that diff reaches the AI provider, it goes through best-effort secret redaction — pattern matching against things like .env contents, *_API_KEY, and *_SECRET_KEY. It's a real safeguard, but it's pattern matching, not a guarantee — always skim a generated summary before you share it outside your team.
3. The JSON File Is the Source of Truth, Not the AI's Words
Every run writes exactly one JSON file, partitioned by the date of the run:
change-summary/<YYYY>/<MM>/<DD>/json/<target>-<timestamp>.json
The important design decision here is which fields come from where. author, branch, base, the change window, and the list of changed files all come straight from git — never from the model. The only two fields the AI actually writes are commit.title and commit.body. That split matters: even if a provider has a bad day and hallucinates something, the factual metadata around the commit is still accurate, because it was never the model's job to produce it in the first place.
If you'd rather read a human-facing view than the raw JSON, --markdown renders a second copy into a markdown/ folder next to it — the JSON file stays the source of truth either way.
4. Turning Days of Summaries Into a Changelog — Without Calling the AI Again
Once you've been running gitaiflow for a while, you end up with a folder full of these JSON artifacts. --changelog turns a date range of them into a proper CHANGELOG.md entry:
gitaiflow --changelog --since "2 weeks ago" --path .
It resolves <remote>/<base-branch> the same way a normal run does, then builds the changelog primarily from the change-summary artifacts already generated for that window — bucketed by conventional-commit type (feat:, fix:, and so on). If more than one artifact touched the same file in the range, only the newest one counts. If no artifacts exist for that range at all, it falls back to git log, scoped strictly to the resolved remote branch — never to whatever happens to be checked out locally — so an unpushed local commit on some other branch can't sneak into the changelog. Existing entries in CHANGELOG.md are never rewritten, except that re-running for a version label that's still open merges in new content instead of duplicating the entry.
Worth knowing: --changelog never calls an AI provider itself. It only reads what's already on disk (or falls back to git log), so it doesn't touch the usage log or telemetry described below either way.
5. What Never Leaves Your Machine
Every run appends one line to a local file, ~/.gitaiflow/usage.jsonl — timestamp, repo name, target type, model used, estimated token counts, duration, success. This file never leaves your machine; it exists purely so you can see your own usage and, if you want, set a soft daily ceiling:
export GITAIFLOW_MAX_RUNS_PER_DAY=20 export GITAIFLOW_MAX_TOKENS_PER_DAY=50000
That's a courtesy warning, not enforcement — it's a local file anyone can clear.
Telemetry is a separate, opt-in decision, kept deliberately separate so clearing your usage log doesn't reset it. gitaiflow doesn't phone home by default. The first time it matters and you're at an interactive terminal, it asks once and saves your answer; a bare Enter, Ctrl-C, or EOF all count as no. You can check or change that saved answer any time:
gitaiflow --telemetry enable # or disable / status / history
GITAIFLOW_TELEMETRY=true|false always overrides the saved answer, but only for that one process — which is why CI can flip it per job without touching your durable local setting, and why CI defaults to off with no prompt (there's no terminal to ask at).
If you do say yes, what's sent is intentionally thin —
- An install UUID, the gitaiflow version, which provider and model you used, file counts, rough token/duration numbers, and whether the run succeeded.
- Repository names, file paths, file contents, diff content, git author or branch, commit messages, and the AI-generated summary text itself are never sent, telemetry on or off.
Want the Full Picture?
This post covers the everyday workflow — summaries, changelogs, and what does or doesn't leave your machine. For the complete configuration reference, every provider's exact setup, and the telemetry server side of things, see the gitaiflow documentation ↗.
What I'd Tell You If You're Building Something Similar
- Never let the model own facts it doesn't need to generate. Git already knows the author, branch, and changed files — pull those directly instead of asking an AI to reconstruct what a diff already tells you for free.
- Chunk before you hit a limit, not after. Grouping a large diff by directory keeps every request small and predictable, instead of discovering a provider's context ceiling mid-run.
- Make the privacy trade-offs a checkbox, not a paragraph buried in a README. Asking once, defaulting to off, and stating exactly what's collected before the prompt appears is a small amount of extra code that buys a lot of trust.