The Problem
Refactors get scoped from the folder tree and the architecture diagram. Then the move lands, and a file three directories away breaks — because the files that change together were never the ones drawn together.
The evidence was in git log all along: every commit records which files moved as one. Nobody reads a few hundred commits to find the pattern, so the plan runs on folklore.
The Insight
Files that change together are coupled, wherever they live. Git history already holds the map — it only needs drawing.
The Instrument
churnmap walks your local git history with coupling-core, the engine it shares with couplingguard. Files that land in the same commit co-change; each pair scores co_changes ÷ max(changes_a, changes_b), from 0 to 1, and pairs seen together fewer than three times are dropped. Merge commits and binary files never count.
One run writes two artifacts that outlast the meeting: index.html — a heatmap of the most coupled files, a force graph where the strongest pairs draw the heaviest edges, and a table sorted by score — and report.json, every pair for scripts and dashboards.
One run, two artifacts
$ churnmap --repo couplingguard --lookback-days 180
| Analyzing git history...
Report generated: coupling-report\index.html
Report generated: coupling-report\report.json
$ head -n 17 coupling-report/report.json
{
"meta": {
"repo": "Meru143/couplingguard",
"generated_at": "2026-10-23",
"lookback_days": 180,
"total_commits_analyzed": 43,
"churnmap_version": "1.0.0"
},
"pairs": [
{
"file_a": "pyproject.toml",
"file_b": "src/couplingguard/__init__.py",
"score": 0.8,
"co_changes": 4,
"total_commits": 5,
"risk": "high"
},
Real session on a clone of couplingguard, captured on Windows on Fri 23 Oct 2026. The window counts back from today, so reaching its May–July history takes --lookback-days 180; the default is 90.
Spec
- Score
- co_changes ÷ max(changes_a, changes_b) — the coupling-core engine couplingguard runs too
- Risk bands
- 🟢 < 0.3 ≤ 🟡 < 0.7 ≤ 🔴 — both thresholds configurable
- Output
- index.html (heatmap · force graph · table) · report.json
- Privacy
- History never leaves the machine · the report loads D3 from jsDelivr to draw its charts
- Config
- .churnmap.yml — window (90 days), minimum co-changes (3), excludes, thresholds; flags override it
- Quality bar
- 54 tests · mypy --strict · ruff · pip-audit · test matrix Linux/macOS/Windows × Python 3.11–3.13
- Install
- PyPI churnmap · pip, pipx or uv
- Adoption
- 253 PyPI downloads, May–Oct 2026, mirrors excluded
- Quick start
pipx run churnmap
The Outcome
churnmap 1.0.0 shipped to PyPI on 28 May 2026, published by its semantic-release workflow through PyPI Trusted Publishing — the same day as couplingguard 0.1.2, on the coupling-core engine they share. Built so a refactor starts from the files that actually move together, with evidence that is still there after the meeting.