commit-prophet: I Built a Tool That Predicts Buggy Files Using Git History
I've fixed the same bug in src/billing.py four times this year. My git history knew it was going to happen. I didn't listen.
Every time I opened a PR touching that file, git log could have screamed at me: "This file has appeared in 11 bug-fix commits in the last 6 months. It co-changes with src/payments.py, which is also a disaster. You are about to make a mistake." But nobody was mining that data.
So I built commit-prophet.
The Problem
We have all the data we need to predict which files are going to cause problems. It's sitting right there in git log. Every commit message that starts with "fix:" is a breadcrumb. Every pair of files that change together in the same commit is a signal. But most developers — and most tools — completely ignore this longitudinal data.
Code review tools check the current diff. Linters check current style. But neither tells you: "This file has a history. And the history is bad."
The Three Signals
commit-prophet combines three independent signals into a single 0–100 risk score.
1. Churn — How Often Does This File Change?
Files that are touched constantly are often unstable. Either the requirements around them keep changing, or the code is so complex that every attempt to fix one thing breaks another. High churn is a yellow flag.
churn_score = min(file_churn / max_churn, 1.0) * 40 # 40% weight
2. Defect Coupling — How Many Bug Fixes Has This File Seen?
This is the smoking gun. A file that appears in commit messages like:
fix: resolve null pointer in billing processorbug: payment failure when currency is GBPpatch: crash on empty cart
...is telling you something. That file attracts bugs. We detect these commits by scanning for keywords: fix, bug, patch, error, issue, crash, defect, fault, resolve, repair.
defect_score = min(defect_commits / max_defects, 1.0) * 50 # 50% weight
3. Co-Change Analysis — Who Does This File Travel With?
This is the most interesting signal. If file A and file B always change together in the same commit, they are tightly coupled — even if they're in completely different parts of the codebase.
If A has bugs, B probably will too. If you change A without changing B, you probably missed something.
"If A and B always change together, and A has lots of bugs, B is risky."
coupling_score = min(risky_cochanged_files / 10, 1.0) * 10 # 10% weight
Demo: Real Terminal Output
Here's what commit-prophet looks like on a real codebase:
$ commit-prophet scan --days 90
Scanning /home/alice/my-app (90 days, branch=HEAD) ...
Parsed 312 commits.
Commit Prophet — Hotspot Risk Report
╭──────────┬──────────────────────────────────┬───────┬─────────────┬───────╮
│ Risk │ File │ Churn │ Bug Commits │ Score │
├──────────┼──────────────────────────────────┼───────┼─────────────┼───────┤
│ CRITICAL │ src/billing/processor.py │ 47 │ 12 │ 94 │
│ CRITICAL │ src/auth/session.py │ 38 │ 9 │ 87 │
│ HIGH │ src/api/payments.py │ 29 │ 5 │ 68 │
│ HIGH │ src/models/user.py │ 31 │ 3 │ 61 │
│ MEDIUM │ src/utils/validators.py │ 22 │ 2 │ 48 │
│ LOW │ src/config/settings.py │ 12 │ 0 │ 21 │
╰──────────┴──────────────────────────────────┴───────┴─────────────┴───────╯
And for a specific file:
$ commit-prophet predict src/billing/processor.py
╭──────────── Prediction: CRITICAL ──────────────────────────────────────╮
│ File: src/billing/processor.py │
│ Risk Score: 94/100 │
│ Risk Level: CRITICAL │
│ Churn: 47 commits │
│ Bug Commits: 12 bug-fix commits touched this file │
│ │
│ Why this score? │
│ • Changed 47 times (100% of max churn across all files). │
│ • Appeared in 12 bug-fix commit(s) — a strong defect signal. │
│ • Most frequently co-changed with: src/api/payments.py (31 times). │
│ • 2 of its coupled file(s) are also high-risk. │
╰────────────────────────────────────────────────────────────────────────╯
The Implementation
The tool is pure Python with just two runtime dependencies: click for the CLI and rich for the pretty output. Zero external git libraries — it shells out to git directly via subprocess.
from commit_prophet import get_commits, calculate_churn, calculate_defect_coupling
commits = get_commits("/path/to/repo", since_days=90)
churn = calculate_churn(commits)
defects = calculate_defect_coupling(commits)
The get_commits function parses git log --pretty=format:"COMMIT_START|%H|%an|%ai|%s" --name-only and builds structured Commit dataclasses. Bug-fix detection is a simple keyword scan — no ML required.
The co-change matrix is built by generating all file pairs within each commit using itertools.combinations. For a commit touching files [a, b, c], we get pairs (a,b), (a,c), (b,c) and increment their counts.
What I Learned
The 90/10 rule for code history: roughly 10% of files account for 90% of bugs. The data has always been there — it's just spread across thousands of commit messages that nobody reads systematically.
Defect coupling beats pure churn: A file that changes 50 times with zero bug-fix appearances is probably just evolving. A file that changes 20 times and appears in 8 bug-fix commits is a time bomb. That's why defect coupling gets 50% of the weight vs. 40% for churn.
Co-change graphs reveal hidden dependencies: The most valuable output is often the coupling command. You discover that your auth module and your payment module are secretly coupled through 40 commits — not via imports, but via shared environmental state that only shows up when both break at the same time.
Try It
pip install commit-prophet
cd your-git-repo
commit-prophet scan
Source: github.com/LakshmiSravyaVedantham/commit-prophet
I'd love to hear which files in your codebase come up as CRITICAL. Drop a comment — and if it's the billing module, know that you're not alone.
Top comments (0)