gitaiflow
Docsgitaiflow

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 v1.1.3Updated Oct 1, 202636 pages
# 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

Start here

The shortest path from nothing to your first result.

  1. 13 min readInstallationgitaiflow is distributed as a standalone native binary — no Python runtime or package manager is required to run it.
  2. 28 min readCommand Referencegitaiflow has a single-command surface — every feature is a flag on the root gitaiflow invocation rather than a subcommand.
  3. 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.

Full project README

Release License Platform Architecture Zero Dependencies GitHub Stars


It is distributed as a standalone native binary, so it runs locally without requiring a Python runtime or a package manager.

bash
$ 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 signatures

What 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-summary writes over several groups, when every group it's built from is unchanged; pass --regenerate-summary to force a fresh call anyway.
  • Changelog Generation — builds CHANGELOG.md entries 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 model
  • api/ — the binary's command-line surface and configuration contract
  • user-guide/ — installation, configuration, commands, model selection, OpenRouter, telemetry
  • deployment/ — local development, native compilation, and package release
  • runbooks/ — release checklist and troubleshooting

Installation

macOS & Linux (bash):

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.0

Windows (PowerShell):

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 | iex

Either 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 with GITAIFLOW_INSTALL_DIR). If ~/.local/bin isn't on your PATH yet, the installer prints the export PATH=... line to add to your shell profile. Re-running the installer upgrades the binary in place.
  • Windows: %LOCALAPPDATA%\Programs\gitaiflow, added to your user PATH.

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:

bash
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 --version

make 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

bash
# 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.

  • 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:

https://openrouter.ai/

  • openrouter/free automatically 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:
bash
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/free auto-router, list what's currently available instead of hand-writing curl/jq:
bash
gitaiflow --list-models --free-only
text
ID                                    CONTEXT    FREE
nvidia/nemotron-3.5-lightning:free    128000     yes
meta-llama/llama-3.3-70b:free         131000     yes
...
  • Drop the --free-only flag to see paid models too, or add --json for 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-models works independent of your configured AI_PROVIDER -- it always targets OpenRouter regardless of what you have AI_MODEL set to today, and doesn't require an API key (OpenRouter's catalog endpoint is public). It only looks up models; it never changes your configured AI_MODEL.


Other providers

1. Gemini (free tier, cloud)

bash
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-latest

2. Ollama (local, no cost — but AI_API_KEY still needs some value)

bash
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` exactly

3. Any OpenAI-compatible provider (OpenAI, Grok, self-hosted, ...)

bash
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 .env file instead of real environment variables (same KEY=value format). 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.env created by the installer. If any of the four mandatory variables is still empty after checking all three, gitaiflow fails 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

bash
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 itself

Which 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-summary is meant to be piped straight into git:
bash
gitaiflow --path . --change-summary > /tmp/msg.txt && git commit -F /tmp/msg.txt
  • --show-analysis only 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-summary never 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-summary forces 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 switching AI_MODEL/AI_PROVIDER and 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-billing skips 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)writes RELEASE_NOTES.md at the project root (see above) from the CHANGELOG.md entry for the current resolved project version -- run --changelog first 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.md if you have one (see How changelog sections are chosen).

  • --usage reads ~/.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.6 aggregates every run recorded under that gitaiflow version; combine with --since/--until (same formats as --changelog) for a custom date range instead.

  • -h/--help always 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:

  1. Upstream tracking. Whatever the current branch's @{u} resolves to -- i.e. exactly what a bare git push/git pull would use. Set once per branch via git push -u <remote> <branch>.
  2. 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.
  3. origin, if that remote exists -- still the most common convention.
  4. 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

bash
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 --until is omitted, an exact --since defaults through the end of today; a relative --since defaults 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.

  • gitaiflow resolves <remote>/<base-branch> the same way a normal summary run does (see Remote & base-branch resolution above -- upstream tracking, then GITAIFLOW_REMOTE, then origin, 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 plain gitaiflow --path ... run writes to change-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 --changelog from, 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 as README.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.md above 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.md is written at the project root (see Which repo does a command act on?) -- not inside the folder --path happens to point at -- and change-summary artifacts are read from that repo's own change-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 --changelog so 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:

  1. 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/.py files → Python; go.mod/.go → Go; package.json/tsconfig.json/.js/.ts/.tsx → JavaScript/TypeScript). For example: lockfiles and go.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.
  2. Commit type -- an entry no rule claimed gets its type's default section (fix: → Fixes, refactor: → Refactoring, ...).
  3. 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:

toml
[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:
json
{
  "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, and files_changed come straight from git -- never from the model -- so they're accurate even if the AI call fails or hallucinates. commit.title / commit.body are the only model-generated fields, and they're the ones designed to be commit-ready as-is.

  • base is also what --changelog matches against: it only includes an artifact whose base equals the <remote>/<base-branch> it resolved for that run.

  • --markdown renders 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 --path points 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-chunk to 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:
bash
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:

text
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:
bash
export GITAIFLOW_TELEMETRY=true   # or false

which always overrides the saved answer -- handy for CI, where gitaiflow never prompts (no interactive terminal) and defaults to off unless this is set.

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:

bash
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_TELEMETRY still 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) / 4 heuristic, 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).

License

See the LICENSE and NOTICE file for details.