git-pulse
Measure how much of your codebase your AI coding agents actually wrote — and what it cost you in rework.
Installed from PyPI as
gitpulse-ai; the command isgit-pulse.
git-pulse reads your commit history and reports agent-versus-human attribution, per-file churn, rework rates, work sessions, and the places where edits pile up in the same lines within hours of each other. It works on any git repo, runs entirely offline, and needs no API key.
What It Does
git-pulse reads your git history and measures how much of it was written by AI coding agents versus humans — then measures what that code cost you in rework, churn, and repeated edits to the same lines.
| Measurement | What you get |
|---|---|
| Attribution | Every commit scored as human, mixed, or agent, with the signal that decided it and the provider it came from |
| Churn | Insertions and deletions per file, with the agent share of each file's churn |
| Rework | How often files come back for another edit, split by agent and human |
| Velocity | Commits per day, active days, files per commit, and the peak day |
| Sessions | Work clustered per author by commit gap, so two people committing in the same hour aren't merged |
| Hotspots | Edits close in both line position and time, classified by who touched the region last |
Everything above runs locally with no API key and no network access. Pass --llm
to add an interpretive narrative on top.
Agent-Aware Analysis
git-pulse detects coding-agent attribution from commit metadata alone — Co-Authored-By: Claude, the copilot-swe-agent[bot] identity, [cursor] subject tags, aider: prefixes, Generated-by trailers, and git notes — across 11 providers: Claude Code, GitHub Copilot, Cursor, aider, OpenAI Codex, Devin, Windsurf, Sourcegraph Cody, Continue, Sweep, and gpt-engineer.
Every signal carries a weight, and a commit's score is the highest weight it matched — so a classification is always traceable to the one line of metadata that caused it. Nothing is inferred from the diff, and nothing is guessed.
Same figure as text
Agent-authored share — 43.9% of 376 commits carry a coding-agent signature
agent 165 commits 2,574 lines added
human 211 commits 4,707 lines added
signal weight matches provider
coauthor_trailer 0.95 48 Claude Code
bot_identity 0.90 96 GitHub Copilot
aider_prefix 0.90 34 aider
bracket_tag 0.85 35 Cursor
matches counts signal instances, not commits — bot_identity fires on both the
author and the committer field, so 96 matches is 48 commits.
Churn, ranked
Insertions plus deletions per file. Churn on its own is not a defect signal, but it tells you which files the next question is about.
Same figure as text
Top files by churn (insertions + deletions, 120 days)
src/shipyard/core/orders.py 1,735
src/shipyard/api/routes.py 1,467
src/shipyard/core/pricing.py 1,406
src/shipyard/api/auth.py 1,167
src/shipyard/core/scheduling.py 1,042
src/shipyard/db/models.py 953
tests/test_orders.py 886
tests/test_pricing.py 803
Agent share of that churn runs 30–47% per file, so no single file is agent-only.
Velocity over time
Commits per day across the window, with the peak and the last day labelled — enough to see whether the work is steady or arrives in bursts.
Same figure as text
Commit velocity — 376 commits over 120 days
active days 92 of 120
commits per day 3.13
files per commit 1.62
peak 12 commits on 2026-04-12
last day 3 commits on 2026-05-05
Spatiotemporal hotspots
A hotspot is a cluster of edits close together in both line position and time. The classification says who touched the region and in what order, which is the part that tells you whether an agent is being reworked or is doing the reworking.
Same figure as text
Spatiotemporal hotspots — 229 detected, seven highest-scoring shown
location edits span agent/human score pattern
src/shipyard/core/pricing.py:51-75 6 3.9h 3 / 3 7.4 agent-reworked
src/shipyard/core/scheduling.py:55-71 4 1.2h 2 / 2 7.3 human-fixing-agent
src/shipyard/core/pricing.py:151-160 5 2.5h 3 / 2 7.1 agent-reworked
src/shipyard/db/models.py:26-50 6 4.1h 3 / 3 7.0 agent-reworked
src/shipyard/db/models.py:51-75 6 4.1h 3 / 3 7.0 agent-reworked
src/shipyard/core/orders.py:82-100 5 2.7h 3 / 2 6.7 agent-reworked
tests/test_pricing.py:10-25 5 3.0h 3 / 2 6.3 agent-reworked
Score is edits² ÷ (1 + hours). human-fixing-agent means a human touched the
region last; agent-reworked means an agent did.
About the numbers in these figures. git-pulse's own history is entirely human-authored, so it cannot demonstrate agent attribution. Every figure above is a real
git-pulse analyzerun against a synthetic repository built with a fixed seed, so the numbers are reproducible rather than illustrative.
Install
pip install gitpulse-ai
Requires Python 3.11+. The installed command is git-pulse.
Quick Start
# Analyze the current repo (last 30 days) — no API key required
git-pulse analyze
# A specific repo, last 14 days
git-pulse analyze /path/to/repo --days 14
# Last 50 commits, or an explicit date range
git-pulse analyze --commits 50
git-pulse analyze --since 2026-01-01 --until 2026-03-31
# JSON output, for CI or further processing
git-pulse analyze --json
git-pulse analyze --output report.json
# Add an LLM narrative (requires a provider key)
export ANTHROPIC_API_KEY=sk-...
git-pulse analyze --llm
# Scaffold a config file
git-pulse config init
# Inspect or clear the history cache
git-pulse cache info
git-pulse cache clear
Example Output
git-pulse analyze prints attribution, authors, churn, velocity, sessions, and
hotspots. No API key is involved in any of it — this is git metadata, counted.
Same output as text
$ git-pulse analyze ~/src/shipyard --days 120
╭───────────────── git-pulse ─────────────────╮
│ git-pulse-demo · branch main · 55b89860 │
│ 2026-01-06 → 2026-05-05 · 376 commits │
╰───── v0.1.1.dev24+g86dac6cf1.d20260823 ─────╯
Attribution
┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Metric ┃ Value ┃
┡━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ Agent commits │ 165 (44%) │
│ Mixed commits │ 0 │
│ Human commits │ 211 │
│ Agent lines added │ 2574 (35%) │
│ Providers │ Claude Code (48), Cursor (35), GitHub Copilot (48), aider (34) │
└───────────────────┴────────────────────────────────────────────────────────────────┘
Authors
┏━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━┓
┃ Author ┃ Class ┃ Commits ┃ +/- ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━┩
│ Dana Whitfield │ agent │ 89 │ +2581/-955 │
│ Ravi Menon │ agent │ 83 │ +1386/-1046 │
│ Lena Osei │ agent │ 73 │ +1319/-943 │
│ copilot-swe-agent[bot] │ agent │ 48 │ +702/-528 │
│ Tomas Brandt │ human │ 42 │ +662/-493 │
│ Priya Raman │ human │ 41 │ +631/-451 │
└────────────────────────┴───────┴─────────┴─────────────┘
Churn, velocity, session, and hotspot tables follow; they are shown as figures above.
Optional: LLM Narrative
Every metric git-pulse reports is computed locally. The --llm flag adds a
written interpretation on top, and only that flag needs a provider key. If the
call fails or no key is set, git-pulse prints a warning and the metrics are
unaffected.
git-pulse uses LiteLLM under the hood, so it works with 100+ LLM providers out of the box. Set the appropriate environment variable for your provider:
# Anthropic (default model: claude-sonnet-4-20250514)
export ANTHROPIC_API_KEY=sk-ant-...
git-pulse analyze --llm
# OpenAI
export OPENAI_API_KEY=sk-...
git-pulse analyze --llm --model openai/gpt-4o
# AWS Bedrock
export AWS_PROFILE=my-profile
git-pulse analyze --llm --model bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0
# Any LiteLLM-supported provider
git-pulse analyze --llm --model <provider>/<model-id>
There is deliberately no key auto-detection: git-pulse analyze must produce the
same output on a laptop with keys in the environment as it does in CI without
them.
Configuration
git-pulse looks for TOML config files in this order:
--configflag (explicit path).gitpulse.tomlin the repo root~/.config/gitpulse/config.toml- Built-in defaults
Run git-pulse config init to write a commented starter file, or
git-pulse config show to print the effective configuration and where each
section came from.
Example .gitpulse.toml:
[analysis]
default_days = 30
max_hotspots = 20
exclude = ["*.lock", "package-lock.json", "*.generated.*"]
[sessions]
# Commits by the same author closer together than this belong to one session.
# 0.1.0 used 30, which split a session on any coffee break.
gap_minutes = 90
[llm]
# Equivalent to passing --llm on every run. Off by default.
enabled = false
model = "anthropic/claude-sonnet-4-20250514"
CLI Options
Pasted from --help, so it cannot drift from the code. --include and
--exclude are repeatable.
$ git-pulse --help
Usage: git-pulse [OPTIONS] COMMAND [ARGS]...
Measure how much of your git history was written by AI agents, and what it cost.
Commands:
analyze Analyze a repository's history and report agent vs. human contribution.
version Show the installed version.
cache Inspect or clear the history cache.
config Show or scaffold configuration.
$ git-pulse analyze --help
Usage: git-pulse analyze [OPTIONS] [PATH]
Analyze a repository's history and report agent vs. human contribution.
Runs entirely offline by default; --llm adds an interpretive narrative.
Arguments:
path [PATH] Path to a git repository. [default: .]
Options:
--days INTEGER Analyze the last N days.
--commits INTEGER Analyze the last N commits.
--since TEXT Analyze commits after this date (ISO 8601).
--until TEXT Analyze commits before this date.
--branch TEXT Branch to analyze (default: current).
--include TEXT Only files matching this glob.
--exclude TEXT Skip files matching this glob.
--include-merges Include merge commits. [default: no]
--max-hotspots INTEGER Maximum hotspots to report.
--llm Add an LLM narrative (needs an API key).
--model TEXT LiteLLM model string, e.g. gpt-4o-mini.
--json Emit JSON on stdout.
--output TEXT Also write the JSON report to this file.
--no-cache Bypass the history cache.
--refresh Recompute and overwrite the cache.
--config TEXT Path to a config file.
$ git-pulse cache --help
Usage: git-pulse cache [OPTIONS] COMMAND [ARGS]...
Commands:
info Show cache location, entry count, and size.
clear Delete every cached history entry.
$ git-pulse config --help
Usage: git-pulse config [OPTIONS] COMMAND [ARGS]...
Commands:
show Print the effective configuration and where each section came from.
init Write a commented configuration file.
How It Works
gitlayer collects and caches history → attribution scores each commit →
analysis computes churn, rework, velocity, sessions, and hotspots → render
emits terminal output or JSON → an optional analyst narrative sits on top.
Same diagram as text
cli analyze · cache · config · version
↓
report builds one immutable Report value
↓
analysis churn · velocity · sessions · hotspots
↓
gitlayer git plumbing · history cache
↓
git log · numstat · notes
shared by every layer above:
attribution signals · providers
models typed history & report
render terminal · JSON v1
analyst optional LLM narrative ← the only part that needs an API key
Deterministic layer (no LLM, no network):
gitlayerreads history throughgit log --numstat -zand unified-diff parsing, and caches the result on disk keyed by HEAD and the collection optionsattributionscores each commit against 11 provider signatures — trailers, bot identities, subject prefixes, message markers, and git notes — and records which signal matchedanalysiscomputes per-file churn and agent share, file rework rates, velocity, per-author work sessions, and spatiotemporal hotspotsrenderemits the rich terminal report or versioned JSON
Optional analyst layer (--llm):
- Receives the same JSON a user gets from
--json, minus the per-day series, raw SHAs, and any narrative from an earlier run — a model must not launder its own prior output back in as evidence - Returns a summary, categorised insights with evidence and a recommendation, and up to three prioritised actions
- Every failure path degrades to a warning; the metrics are never affected
A Report is a pure value. Renderers never hold a repository handle, so a report
can be serialised, cached, or diffed long after the checkout is gone. The JSON
shape is a versioned contract — see docs/json-schema.md.
Releases & Downloads
Latest release: on GitHub,
on PyPI.
| PyPI package | pypi.org/project/gitpulse-ai |
| Release history | All versions on PyPI · Git tags |
| Download stats | pepy.tech (totals, by version) · pypistats.org (daily, by Python version) |
Versioning follows Semantic Versioning. While the project is pre-1.0, minor versions may include breaking changes to the CLI and output schemas; pin an exact version if you depend on either.
Development
# Clone and install in dev mode
git clone https://github.com/srikanth1003/git-pulse.git
cd git-pulse
pip install -e ".[dev]"
# Run tests
pytest
# Run on any repo
git-pulse analyze /path/to/any/repo --days 14
Cutting a release
The version is derived from the git tag by hatch-vcs — there is no version string to edit. Tag, then build and upload:
git tag -a v0.2.0 -m "git-pulse 0.2.0"
git push origin v0.2.0
python -m build && twine upload dist/*
Builds from an untagged or dirty tree produce a local dev version (e.g. 0.1.1.dev0+g1b73c1f), which PyPI rejects by design — release only from a clean tagged commit.
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gitpulse_ai-0.2.0.tar.gz.
File metadata
- Download URL: gitpulse_ai-0.2.0.tar.gz
- Upload date:
- Size: 63.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2b51203e3a2ef16128ba6ecb550cb77bbafa6321d4df188ce0529e184afebd45
|
|
| MD5 |
b58e19cf2dbaadd13f797463df71f840
|
|
| BLAKE2b-256 |
2e0364ff72a4f38088a54811a3ca54bcc8442132be4ec29105c8743cb85f64de
|
File details
Details for the file gitpulse_ai-0.2.0-py3-none-any.whl.
File metadata
- Download URL: gitpulse_ai-0.2.0-py3-none-any.whl
- Upload date:
- Size: 52.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dcd16d660a61fcd27bbcb824db6f671cfbcb45970ec132357bc9bab267250985
|
|
| MD5 |
71023663f1f59734fd5704c17462b5f8
|
|
| BLAKE2b-256 |
8938dae4cca446a74892c56bad6fc70ee8ac3be8e7bb59c543e6bc52aeda29d8
|