gitaiflow
gitaiflow is a local-first, AI-native binary for Git diff analysis, engineering change summaries, pull request summaries, architectural analysis, Django-aware change detection, and hierarchical diff summarization.
# latest
curl -fsSL https://install.djangoplay.org/gitaiflow | bash# a specific version (note the -s -- before args when piping into bash)
curl -fsSL https://install.djangoplay.org/gitaiflow | bash -s -- v1.0.0# latest (run PowerShell as Administrator)
irm https://install.djangoplay.org/gitaiflow.ps1 | iex# a specific version
$env:GITAIFLOW_VERSION = "v1.0.0"; irm https://install.djangoplay.org/gitaiflow.ps1 | iexStart here
The shortest path from nothing to your first result.
- 13 min readInstallationgitaiflow is distributed as a standalone native binary — no Python runtime or package manager is required to run it.
- 28 min readCommand Referencegitaiflow has a single-command surface — every feature is a flag on the root gitaiflow invocation rather than a subcommand.
- 32 min readConfigurationAI_PROVIDER, AI_API_KEY, AI_BASE_URL, and AI_MODEL are all mandatory — none of them has a built-in default, and gitaiflow will not run until every one resolves to a non-empty value. GITAIFLOW_REMOT...
Browse by topic
Grouped the same way as the sidebar.
User guide7
1 more in User guide →API2
Architecture3
Changelog1
Deployment6
MCP7
- gitaiflow MCP — Claude Desktop Integration
- gitaiflow MCP — Docker Verification
- gitaiflow MCP Documentation
- gitaiflow MCP — Getting Started
- gitaiflow MCP — MCP Inspector Verification
Releases2
Runbooks5
Full project README
- Maintained by: DjangoPlay
- Documentation: Docs
It is distributed as a standalone native binary, so it runs locally without requiring a Python runtime or a package manager.
$ gitaiflow --path mailer/ --change-summary
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 signaturesWhat gitaiflow Does
- Git-Grounded Metadata — repository, branch, base ref, author, change window, and changed-file list come straight from Git, never from the model.
- Provider-Neutral AI Synthesis — one OpenAI-compatible client contract works against Gemini, OpenAI, Ollama, OpenRouter, Grok, DeepSeek, vLLM, or LM Studio.
- Chunked Requests — changed files are grouped by top-level subdirectory by default, keeping individual AI requests inside provider context-length and free-tier limits.
- Cross-Day Summary Caching — a diff that hashes identically to one already summarized is never re-sent to the AI, no matter how many days have passed -- and neither is the combined title
--change-summarywrites over several groups, when every group it's built from is unchanged; pass--regenerate-summaryto force a fresh call anyway. - Changelog Generation — builds
CHANGELOG.mdentries from existing change-summary artifacts (or Git history as a fallback) over a date range, organized into sections by language-aware template rules (Python, Go, JavaScript/TypeScript, plus a base set) with a single AI call only for what the rules can't place. - Local Usage Accounting — every run is logged locally with optional soft daily run/token limits; remote telemetry is a separate, explicit opt-in.
Documentation Structure
architecture/— pipeline architecture, execution flow, and security/privacy modelapi/— the binary's command-line surface and configuration contractuser-guide/— installation, configuration, commands, model selection, OpenRouter, telemetrydeployment/— local development, native compilation, and package releaserunbooks/— release checklist and troubleshooting
Installation
1. Quick Install (Recommended)
macOS & Linux (bash):
# latest
curl -fsSL https://install.djangoplay.org/gitaiflow | bash
# specific version — note the -s -- before args when piping into bash
curl -fsSL https://install.djangoplay.org/gitaiflow | bash -s -- v1.0.0Windows (PowerShell):
# latest
irm https://install.djangoplay.org/gitaiflow.ps1 | iex
# specific version
$env:GITAIFLOW_VERSION = "v1.0.0"; irm https://install.djangoplay.org/gitaiflow.ps1 | iexEither installer also creates an empty ~/.gitaiflow/config.env (left untouched on upgrades) for the four mandatory provider variables — see Configure an AI provider below.
Where it installs — no sudo/administrator rights needed
- macOS & Linux:
~/.local/bin/gitaiflow(override withGITAIFLOW_INSTALL_DIR). If~/.local/binisn't on yourPATHyet, the installer prints theexport PATH=...line to add to your shell profile. Re-running the installer upgrades the binary in place. - Windows:
%LOCALAPPDATA%\Programs\gitaiflow, added to your userPATH.
Older versions of the macOS/Linux installer used sudo and put the binary in /usr/local/bin. If you still have that copy, remove it so it can't shadow the new one: sudo rm -f /usr/local/bin/gitaiflow. (gitaiflow --uninstall also cleans up the ~/.local/bin copy, but never runs sudo itself — for a root-owned legacy copy it prints that same command for you to run.)
2. Build Native Binary From Source Locally
For customized environments, build via the provided script rather than invoking Nuitka directly — it resolves the version from pyproject.toml, sets up onefile caching, and stamps the binary's version metadata for you:
git clone https://gitlab.com/codefleet-labs/gitaiflow.git
cd gitaiflow
# Install compilation prerequisites into your virtualenv
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]" # includes nuitka
# Compile into a standalone single-file binary
make compile
./bin/gitaiflow --versionmake compile runs scripts/install/compile.sh, which reads the current version out of pyproject.toml, generates gitaiflow/_version.py so it's baked into the binary (so gitaiflow --version always matches the package version), and invokes Nuitka with --standalone --onefile --prefer-source-code. The resulting binary is written to bin/gitaiflow.
Supported Platforms & Asset Coverage
| Binary File | Target OS / Environment | Target Architecture |
|---|---|---|
gitaiflow-darwin-arm64 |
macOS (Apple Silicon: M1, M2, M3, M4) | ARM 64-bit (arm64) |
gitaiflow-linux-amd64 |
Linux (Ubuntu, Debian, RedHat, standard cloud VMs) | Intel/AMD 64-bit (x86_64) |
gitaiflow-linux-arm64 |
Linux (AWS Graviton, Raspberry Pi, Apple Silicon Docker) | ARM 64-bit (aarch64/arm64) |
gitaiflow-windows-amd64.exe |
Windows 10, 11, Server | 64-bit (x86_64) |
macOS Intel (x86_64) doesn't have a prebuilt binary yet — build from source instead.
Quickstart
# Summarize a directory
gitaiflow --path mailer/
# Summarize a single file
gitaiflow --path users/views/logout.py
# Print just the commit title/body, ready to pipe into git
gitaiflow --path . --change-summary > /tmp/msg.txt && git commit -F /tmp/msg.txt
# Print last change summary
gitaiflow --path . --last-summary
# Generate a CHANGELOG.md entry for the last week
gitaiflow --changelog --since "1 week ago" --path .See user-guide/installation.md to get gitaiflow on your machine, user-guide/configuration.md to configure an AI provider (required before any summary run), and user-guide/commands.md for the full flag reference.
Configure an AI provider (required)
gitaiflow needs four environment variables set — AI_PROVIDER, AI_API_KEY, AI_BASE_URL, and AI_MODEL — and none of them have a built-in default; it will not run until all four resolve to a non-empty value.
The one-line installers create an empty ~/.gitaiflow/config.env for exactly this purpose (left untouched on upgrades, so filling it in once is enough). Open it and fill in your provider's values, or set the same four as real environment variables in your shell/CI, or drop them in a project-local .env — gitaiflow checks all three, in that order, and never writes to your shell profile itself. gitaiflow --help shows the same reference inline, and running gitaiflow with any of the four still empty prints exactly which ones are missing.
OpenRouter (recommended)
-
gitaiflow can use OpenRouter's free model router by default, or any specific free or paid model available through your OpenRouter account.
-
Create an OpenRouter account and generate an API key:
openrouter/freeautomatically selects an available free model, so you do not need to maintain a model name manually.
Use a paid OpenRouter model
- If you have access to paid models through OpenRouter, use your own OpenRouter API key and specify the model you want:
AI_API_KEY=<your-openrouter-api-key>
AI_MODEL=<openrouter-model-id>- Usage is charged according to your OpenRouter account and selected model.
- gitaiflow does not provide or manage the model subscription.
Finding a free OpenRouter model
- If you're using OpenRouter and want to pick a specific model rather than the
openrouter/freeauto-router, list what's currently available instead of hand-writingcurl/jq:
gitaiflow --list-models --free-onlyID CONTEXT FREE
nvidia/nemotron-3.5-lightning:free 128000 yes
meta-llama/llama-3.3-70b:free 131000 yes
...-
Drop the
--free-onlyflag to see paid models too, or add--jsonfor the raw OpenRouter response (all metadata fields, not just the table columns shown above). This queries OpenRouter's live catalog on every call -- gitaiflow doesn't maintain its own model list, so newly added or removed models show up automatically. -
--list-modelsworks independent of your configuredAI_PROVIDER-- it always targets OpenRouter regardless of what you haveAI_MODELset to today, and doesn't require an API key (OpenRouter's catalog endpoint is public). It only looks up models; it never changes your configuredAI_MODEL.
Other providers
1. Gemini (free tier, cloud)
export AI_PROVIDER=gemini
export AI_API_KEY=<your-key> # https://aistudio.google.com/apikey
export AI_BASE_URL=https://generativelanguage.googleapis.com/v1beta/openai
export AI_MODEL=gemini-flash-lite-latest2. Ollama (local, no cost — but AI_API_KEY still needs some value)
export AI_PROVIDER=ollama
export AI_API_KEY=not-needed # Ollama ignores the value, but gitaiflow still requires one
export AI_BASE_URL=http://127.0.0.1:11434/v1
export AI_MODEL=llama3.2:3b # must match `ollama list` exactly3. Any OpenAI-compatible provider (OpenAI, Grok, self-hosted, ...)
export AI_PROVIDER=custom
export AI_BASE_URL=<endpoint>
export AI_API_KEY=<key>
export AI_MODEL=<model>-
Any of these can also live in a
.envfile instead of real environment variables (sameKEY=valueformat). A real environment variable always wins if the same key is set both ways, then a project-local.env(gitaiflow looks starting from the current directory and walking up to your repository root, so the file's exact location doesn't matter), then the global~/.gitaiflow/config.envcreated by the installer. If any of the four mandatory variables is still empty after checking all three,gitaiflowfails fast, naming exactly which ones, rather than partway through a run. -
Full config reference — the first four are mandatory, with no default:
| Variable | Default | Notes |
|---|---|---|
AI_PROVIDER |
(none — required) | Free-form label (gemini, ollama, openai, custom, ...); only affects display/usage-log grouping |
AI_API_KEY |
(none — required) | Needed for every provider, including local ones that ignore its value |
AI_BASE_URL |
(none — required) | Full OpenAI-compatible chat-completions base URL |
AI_MODEL |
(none — required) | e.g. gemini-flash-lite-latest, llama3.2:3b, gpt-4.1-mini |
AI_TEMPERATURE |
0.2 |
sampling temperature |
AI_MAX_TOKENS |
4096 |
Tokens for reasoned output. If you want detailed output, set it high |
AI_REQUEST_TIMEOUT |
60 |
seconds |
GITAIFLOW_REMOTE |
(none — optional) | Standing fallback for which git remote to diff against, for repos where automatic detection can't tell (see Remote & base-branch resolution below). Same three-tier lookup as the variables above |
Full configuration guide: https://docs.djangoplay.org/projects/gitaiflow/user-guide/configuration/#example-env
Usage
gitaiflow --path mailer/ # summarize a directory
gitaiflow --path users/views/logout.py # summarize a single file
gitaiflow --path . --skip migrations tests # skip paths
gitaiflow --path . --remote upstream --base-branch develop
gitaiflow --path ~/code/other-repo # summarize a different repo -- its output stays in that repo
gitaiflow --path . -o artifacts/ # custom output root (overrides the per-repo change-summary/)
gitaiflow --path . --markdown # also write a .md view
gitaiflow --path . --no-chunk # send whole diff as one AI request
gitaiflow --path . --change-summary # print title+body to stdout
gitaiflow --path . --change-summary --show-analysis # + the model's reasoning before its final answer
gitaiflow --path . --last-summary # print the most recent summary already on disk -- no diff, no AI call
gitaiflow --path . --regenerate-summary # force a fresh AI call even if this diff was already summarized
gitaiflow --path . --allow-billing # proceed even if the configured model may not be free
gitaiflow --release-notes # (re)write RELEASE_NOTES.md from the current CHANGELOG.md entry
gitaiflow --release-notes --path ~/code/other-repo # same, for another repo (any folder inside it works)
gitaiflow --usage # local token/run stats for the most recent run
gitaiflow --version # print the installed version
gitaiflow --uninstall # remove the gitaiflow binary and local state files
gitaiflow --help # full flag reference, generated from the CLI itselfWhich repo does a command act on?
Every command resolves its repo the same way git -C does: --path if given, otherwise the current directory, then up to that path's git root. Nothing is remembered between runs, so there is no hidden "last repo" -- what a command acts on is always visible in the command itself (or in the directory you're standing in).
| What | Where it goes |
|---|---|
change-summary/ (JSON, diffs, .cache/) |
<git root>/change-summary/ of the repo containing --path -- one per codebase, however many repos you point gitaiflow at from one place |
CHANGELOG.md, RELEASE_NOTES.md, version lookup |
the project root: the nearest folder from --path up to the git root that has a versioned manifest (pyproject.toml, package.json, ...), else the git root -- so --path repo/src/data/ reaches the same files as --path repo |
| Not inside a git repo | ./change-summary in the current directory (as before); --changelog stops with a message asking for --path <repo> |
-o/--output-dir |
always wins: that exact folder is used instead |
--last-summary, --changelog and --release-notes use the same rule, so --path ~/code/other-repo (or simply cd-ing into it) selects which repo's summaries they read and which changelog they write. The first time gitaiflow writes change-summary/ into a repo it adds /change-summary/ to that repo's local .git/info/exclude (never committed, your .gitignore is untouched) so the artifacts don't show up in git status; nothing is added if the folder is already ignored.
--change-summaryis meant to be piped straight into git:
gitaiflow --path . --change-summary > /tmp/msg.txt && git commit -F /tmp/msg.txt-
--show-analysisonly has an effect alongside--change-summary-- it prints the model's raw reasoning/analysis text (if any) before its final title/body, which is useful for debugging an odd or truncated commit message but noisy otherwise, so it's off by default. -
--last-summarynever calls the AI provider or runs a diff -- it just prints the single most recently generated JSON summary of the resolved repo (see above) straight from disk, across any past run/day. If none exists yet, it says where it looked instead of generating one. -
--regenerate-summaryforces a fresh AI call for every group (and the combined title) even when its diff exactly matches one already summarized before (see Cross-Day Summary Caching above) -- useful after switchingAI_MODEL/AI_PROVIDERand wanting the newer model's take on already-cached diffs, after tweaking the prompt, or just to double-check a summary you weren't happy with. -
--allow-billingskips the pre-flight free-model check (see Use a paid OpenRouter model above) and, for non-OpenRouter providers, silences the passive "can't verify pricing" reminder too. -
--release-notes(re)writesRELEASE_NOTES.mdat the project root (see above) from theCHANGELOG.mdentry for the current resolved project version -- run--changelogfirst if that entry doesn't exist yet. It's always a full overwrite describing the release currently being cut, not a rolling history, and uses the same AI provider/model (and error handling) as a normal summary run. Its highlights follow the sections of that changelog entry; its footer comes from.gitaiflow/release_footer.mdif you have one (see How changelog sections are chosen). -
--usagereads~/.gitaiflow/usage.jsonl(see Local usage log below) and exits without running a summary. Bare--usage(or--usage current) shows the most recent run;--usage 1.0.6aggregates every run recorded under that gitaiflow version; combine with--since/--until(same formats as--changelog) for a custom date range instead. -
-h/--helpalways reflects the exact flags of the binary you have installed -- treat it as the source of truth if anything here ever drifts from it.
Remote & base-branch resolution
--remote/--base-branch are optional overrides -- when omitted, gitaiflow picks both automatically, in this order:
- Upstream tracking. Whatever the current branch's
@{u}resolves to -- i.e. exactly what a baregit push/git pullwould use. Set once per branch viagit push -u <remote> <branch>. GITAIFLOW_REMOTE, if set (see config table above) -- for repos where signal 1 will never apply, e.g. local branches that are deliberately never pushed with-u.origin, if that remote exists -- still the most common convention.- The only remote, if there's exactly one -- nothing to disambiguate.
If none of those apply (multiple remotes, none named origin, current branch untracked, GITAIFLOW_REMOTE unset), gitaiflow can't guess safely and fails with an explicit error listing the available remotes, rather than risking a diff against the wrong host. Pass --remote <name> for that one run, or set GITAIFLOW_REMOTE to fix it for every future run in that repo.
Base branch (main vs master, etc.) is detected the same way independent of the remote: the remote's local HEAD symref if one exists (set by git clone/git remote set-head), else probing <remote>/main then <remote>/master, else falling back to main.
Changelog generation
gitaiflow --changelog --since 2026/08/01 --path . # exact date, through today
gitaiflow --changelog --since "3 days ago" --path . # relative, through right now
gitaiflow --changelog --since "2 weeks ago" --until "3 days ago" --path .
gitaiflow --changelog --since 2026/08/01 --path . -o artifacts/ # match a custom -o used to generate change-summary-
--since(required with--changelog) and--until(optional) each accept either an exact date (YYYY/MM/DD, e.g.2026/08/01) or a relative expression --<N> minute(s)/hour(s)/day(s)/week(s)/month(s) ago(e.g."3 hours ago","2 weeks ago"). Years aren't a supported unit -- use an exact date for anything that wide. If--untilis omitted, an exact--sincedefaults through the end of today; a relative--sincedefaults through right now. -
A range that resolves entirely before the repository's first commit, or into the future, is rejected upfront with an error instead of silently producing an empty changelog.
-
gitaiflowresolves<remote>/<base-branch>the same way a normal summary run does (see Remote & base-branch resolution above -- upstream tracking, thenGITAIFLOW_REMOTE, thenorigin, then the only remote; override either half with--remote/--base-branch), then builds the changelog primarily from change-summary JSON artifacts already generated for that range -- the same files a plaingitaiflow --path ...run writes tochange-summary/<YYYY>/<MM>/<DD>/json/. If more than one artifact touches the same file within the range, only the newest one is used; an older, overlapping artifact is treated as superseded rather than duplicated. An artifact generated against a different base than the one this run resolved is skipped rather than silently included. Likewise, an artifact is only included if most of the files it covers are still changed on the branch you run--changelogfrom, measured from the point where it forked from that base -- so summaries generated earlier the same day on another branch cut from the same base are skipped (each one is printed as[SKIP]), and so are changes that have since landed in the base or been reverted. Artifacts are checked before the newest-wins rule above, so another branch's artifact can't knock out yours by touching a shared file such asREADME.md. -
If no change-summary artifacts exist for the range at all, gitaiflow falls back to
git log <remote>/<base-branch> --since --until-- scoped to that remote branch, never to whatever's currently checked out locally, so a local-only or unpushed commit on another branch can't leak into the changelog either way. -
Either source is split into individual entries (one per bullet), placed into sections (see How sections are chosen below), and prepended as a new entry to
CHANGELOG.mdabove whatever's already there. Existing entries are never modified, except that a re-run for the same still-open version label merges in any new content rather than duplicating the entry -- every section already in that block is kept, including ones you wrote by hand, and any summary paragraph you put under the version heading. -
CHANGELOG.mdis written at the project root (see Which repo does a command act on?) -- not inside the folder--pathhappens to point at -- and change-summary artifacts are read from that repo's ownchange-summary/, so summaries generated for other repos are never mixed in.
If you generated the change-summary with a custom
-o/--output-dir, pass the same value to--changelogso it looks in the right place.
How changelog sections are chosen
Every entry is placed in three steps, cheapest first, so most changelogs never need an AI call at all:
- Template rules -- free, offline, deterministic. A base set of sections (Breaking Changes, Security, Features, Fixes, Performance, Refactoring, Dependencies, Documentation, Tests, Build & CI, Chores) has keyword and file-path rules, merged with a per-language layer picked automatically from your repo (
pyproject.toml/requirements*.txt/.pyfiles → Python;go.mod/.go→ Go;package.json/tsconfig.json/.js/.ts/.tsx→ JavaScript/TypeScript). For example: lockfiles andgo.mod/package.json→ Dependencies,*_test.go/test_*.py/*.spec.ts→ Tests, Django/Alembic migrations → Database Migrations (Python), CSS → Styling (JS/TS),type!:or "BREAKING CHANGE" → Breaking Changes. A change that touched only docs, only tests, or only CI config lands in that section as a whole; an entry starting with "Fix" goes to Fixes even inside a feature commit. - Commit type -- an entry no rule claimed gets its type's default section (
fix:→ Fixes,refactor:→ Refactoring, ...). - One AI call for the rest -- only feature-like entries (
feat:or untyped) that the rules didn't claim are candidates, and only when there are at least 4 of them. The AI chooses a section for each: an existing one, or a new area section named for what the entries are about (e.g. "Routing", "Bot Protection", "Analytics"). It only points at entries by number -- the text of every entry is copied verbatim from your change summaries, so nothing is reworded or invented. New sections need at least 2 entries and are capped at 10; sections that aren't in the template sit right after Features.
Runs that don't reach step 3 make no AI call, load no AI config and touch no network. If AI isn't configured, fails, or its answer is unusable, --changelog still succeeds and the affected entries just stay in their default section. On OpenRouter the same free-model check as a normal run applies (skip it with --allow-billing). AI calls are recorded in the local usage log as changelog runs.
Customizing. The rules live in gitaiflow/templates/changelog/ (base.toml, python.toml, go.toml, javascript.toml) -- the top of base.toml documents the format. To adjust them for one repo without touching gitaiflow, add .gitaiflow/changelog.toml at the repo root; it uses the same format and is merged last:
[profile]
ecosystems = ["typescript"] # optional: skip auto-detection (python, go, javascript/typescript)
[ai]
enabled = false # never call AI from --changelog in this repo
min_items = 6
[[section]] # extend an existing section...
key = "test"
paths = ["cypress/**"]
[[section]] # ...or add your own
key = "i18n"
title = "Translations"
order = 57
paths = ["locales/**"]--release-notes reads this sectioned changelog entry and writes its highlights from it. The footer of RELEASE_NOTES.md (installation steps, download links) is not AI-written and is not built in: put yours in .gitaiflow/release_footer.md (placeholders: {{version}}, {{changelog_link}}, {{repository}}, {{date}}); without that file the notes end with a plain "Full Changelog" link.
Output
- Every run writes one JSON file to
<git root>/change-summary/<YYYY>/<MM>/<DD>/json/<run-stamp>/<target>-<timestamp>.json(the repo containing--path; see Which repo does a command act on?) -- partitioned by the date of the run, so history from previous days is never touched or re-scanned by a later run:
{
"generated_at": "2026-08-19T14:32:07+05:30",
"target": "mailer",
"target_type": "directory",
"repository": "paystream",
"branch": "feature/mailer-retry",
"base": "origin/main",
"author": { "name": "Chandrashekhar Bhosale", "email": "shekhar@djangoplay.org" },
"change_window": {
"first_change_at": "2026-08-18 09:12:03",
"last_change_at": "2026-08-19 14:30:11"
},
"files_changed": [
{ "path": "mailer/tasks.py", "status": "modified" },
{ "path": "mailer/retry.py", "status": "added" }
],
"model": { "provider": "gemini", "name": "gemini-flash-lite-latest" },
"commit": {
"title": "mailer: add retry backoff for failed sends",
"body": "- Added exponential backoff retry logic in retry.py\n- tasks.py now retries send_mail up to 3 times on failure\n- No changes to public function signatures"
},
"summary": "(same content as commit.body)"
}-
author,branch,base,change_window, andfiles_changedcome straight from git -- never from the model -- so they're accurate even if the AI call fails or hallucinates.commit.title/commit.bodyare the only model-generated fields, and they're the ones designed to be commit-ready as-is. -
baseis also what--changelogmatches against: it only includes an artifact whosebaseequals the<remote>/<base-branch>it resolved for that run. -
--markdownrenders a second, human-facing view from the same JSON into<git root>/change-summary/<YYYY>/<MM>/<DD>/markdown/<run-stamp>/-- the JSON is always the source of truth.
Chunked output
-
By default, when
--pathpoints at a directory, gitaiflow groups changed files by their top-level subdirectory and generates one commit summary per group instead of one summary for the whole diff. This keeps individual AI requests small enough to avoid provider context-length limits and free-tier token quotas on large diffs. -
With
--change-summary, each chunk's title/body is printed under a=== <chunk-name> ===header. Pass--no-chunkto force a single request covering the entire diff (previous default behavior).
Local usage log
- Every run appends one line to
~/.gitaiflow/usage.jsonl-- timestamp, repo name, target type, model used, estimated token counts, duration, success. This file never leaves your machine. It exists so you can see your own usage and, optionally, set soft daily limits:
export GITAIFLOW_MAX_RUNS_PER_DAY=20
export GITAIFLOW_MAX_TOKENS_PER_DAY=50000- When set, gitaiflow prints a warning once you've crossed the threshold for the day. This is a courtesy guardrail against accidentally running up a cloud-model bill, not enforcement -- it's a local file, and any user can clear it.
Telemetry (asked once, off unless you say yes)
gitaiflow does not phone home by default. The first time telemetry status matters and you're at an interactive terminal, it asks:
Enable anonymous usage telemetry? [y/N]- A bare Enter, Ctrl-C, or EOF is treated as no. Your answer is saved locally so you're only asked once per machine (deliberately kept separate from the local usage log below, so clearing that log doesn't reset your telemetry decision too), and you can change it anytime with:
export GITAIFLOW_TELEMETRY=true # or falsewhich always overrides the saved answer -- handy for CI, where gitaiflow never prompts (no interactive terminal) and defaults to off unless this is set.
Durable consent (--telemetry)
GITAIFLOW_TELEMETRY only affects the current shell session. To set or check your answer durably -- so it sticks across terminals and reboots without exporting the env var every time -- use:
gitaiflow --telemetry enable # turn telemetry on, saved locally
gitaiflow --telemetry disable # turn it back off
gitaiflow --telemetry status # show the current saved answer
gitaiflow --telemetry history # show every recorded answer, with version + timestamp-
Each of these exits immediately -- they never run a summary or touch git.
GITAIFLOW_TELEMETRYstill overrides whatever's saved, for the current process only, which is why CI can flip it per-job without disturbing your durable local answer. -
If you say yes, each run sends exactly the details below to the configured telemetry receiver
https://app.djangoplay.org/gitaiflow-telemetry/v1/events(the maintainer's self-hosted receiver), and nothing else:
| Field | Example |
|---|---|
install_id |
random UUID, generated once locally |
event |
"run" |
timestamp |
2026-08-19T14:32:07Z |
gitaiflow_version |
"1.0.0" |
ai_provider |
"gemini" |
model_name |
"gemini-flash-lite-latest" |
target_type |
"file" | "directory" |
files_changed_count |
4 |
tokens_estimated_in / tokens_estimated_out |
1832 / 210 |
duration_ms |
2140 |
success |
true |
os |
"linux" |
🔒 NEVER SENT, EVEN WITH TELEMETRY ON: repository name, file paths, file contents, diff content, Git author/branch, commit messages, or the AI-generated summary text.
The consent prompt itself states exactly what's collected before asking, so you can decide with the facts in front of you, not after the fact.
Limitations
- An AI provider is mandatory -- gitaiflow does nothing without one configured, by design (see "Configure an AI provider" above).
- Daily run/token limits are a local, deletable courtesy check, not real enforcement. There's no license/quota server behind them.
- Secret redaction (
.env,*_API_KEY,*_SECRET_KEY, etc.) is best-effort pattern matching on the diff -- always review generated summaries before sharing them outside your team. - Token/cost estimates in the usage log are a rough
len(text) / 4heuristic, not provider-accurate billing. - Hosted model-routing backend for a future paid tier.
- Server-side license/quota enforcement.
- PR-platform integration (auto-post summaries to GitHub/GitLab).