Skip to content

The CollectionInstrument No. 10

churnmap

Before you move code, see what moves with it.

Coupling heatmaps

Language Python

Year 2026

PyPI (opens in a new tab)GitHub (opens in a new tab)

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.