repowiserepowise
Features
Code health
A defect-validated 1 to 10 score per file. Zero LLM.
Agent provenance
See how much of your code AI wrote, and whether it is healthy.
AI context (MCP)
Ten MCP tools that give your agent real codebase context.
Change risk
A 0 to 10 defect-risk score for any commit or PR.
Security
Reachability-aware CVE triage on your dependency graph.
Auto wiki
A documented wiki of your codebase that rebuilds itself.
Git intelligence
Hotspots, ownership, hidden coupling, and bus factor.
Architecture (C4)
C4 system context, containers, and components.
Decisions
Architectural decisions mined from eight sources.
Solutions
developers
Give Claude Code, Cursor, and any MCP client a queryable model of your repo.
teams
One shared index, one credit pool, one org install. The whole team on the same brain.
team leads
Flag the risky PRs, the hotspots, and the hidden coupling, on every pull request.
engineering leaders
See how much of your code AI wrote, whether it is healthy, and who owns it.
security
CVE triage that knows whether you actually call the vulnerable code.
enterprise
Self-hosted, air-gapped, and commercially licensed for the whole org.
Book a demo →
Guides
Code healthAI context & MCPGit intelligenceChange riskArchitectureAuto-wikiDecisions & ADRsAgent provenanceSecurityAll guides →
Compare
vs CodeScenevs DeepWikivs Sourcegraphvs Cursorvs GitClearvs SonarQubeAll comparisons →
PricingExploreBlogDocs
Star—Sign in
Start free
Blog/Engineering

Our git co-change model failed on a monorepo refactor

repowise team·May 17, 2026·10 min read

co-change analysis git

Our git co-change model failed on a monorepo refactor
On this page
  • The graph looked right because the refactor created perfect-looking co-change pairs
  • What the refactor actually changed in the data: rename storms, mechanical edits, and one-off cleanup commits
  • A worked example: the false edge that sent planning down the wrong path
  • The fix: separate churn from coupling before you score co-change
  • Which filters were worth the complexity, and which ones were not
  • Where co-change still earns its keep in a monorepo
  • FAQ
  • How do you tell whether co-change analysis git is real coupling or refactor noise?
  • What should you filter out before trusting co-change pairs in a monorepo?
  • Can co-change analysis be used for ownership and blast radius in a large repo?
  • When does git co-change analysis stop being useful?
  • How do you tell whether co-change pairs are trustworthy enough for planning?

The first time we trusted co-change analysis git on a monorepo refactor, the graph looked embarrassingly good. Dense edges. Clear clusters. A few files that seemed to move together often enough to feel like coupling, and enough git history to make the signal look statistically respectable. It was wrong in exactly the way history-based models tend to be wrong: the model learned migration artifacts, not architecture.

The graph looked right because the refactor created perfect-looking co-change pairs

The failure mode was simple. A monorepo refactor moved code between packages, updated imports, renamed modules, and cleaned up call sites in one sweep. On paper, that creates a beautiful co-change analysis git graph. Files that were only adjacent because of the migration now appear to have a long-standing relationship. Why files that moved together are not necessarily coupled is the whole trap.

History does not tell you why two files changed together. It only tells you that they did. In a small repo, repeated co-edits often do mean real coupling. In a monorepo, a single migration can create dozens of edges that look just as convincing as organically evolved ones. If you only look at raw co-change counts, the refactor can dominate the window and drown out the rest of the signal.

That is especially true when you use a fixed history window. A window can be dense and still be semantically wrong. If one refactor contributes 40% of the edges, the graph is not “more informed.” It is mostly describing the refactor.

REFACOR GRAPH BEFORE FILTERINGREFACOR GRAPH BEFORE FILTERING

The academic framing for co-change analysis is not wrong. The classic empirical papers are asking a real question: when two files change together repeatedly, does that imply an evolutionary relationship? Yes, sometimes. But the result depends on the quality of the history. Monorepo history, at scale, is noisy by construction; Atlassian’s monorepo guidance says as much without being dramatic about it. The point is not that git history is useless. The point is that history needs interpretation before it becomes a coupling signal.

hotspots, ownership, and co-change pairs file and symbol graph

What the refactor actually changed in the data: rename storms, mechanical edits, and one-off cleanup commits

Once we inspected the commits behind the graph, the pattern was obvious in hindsight. The edges were coming from rename/move noise, mechanical commits, generated files or formatter churn, and a few dependency bumps that touched the same neighborhoods of the tree.

Here is the taxonomy we should have used earlier:

Commit typeTypical effect on co-changeHow we treat it
RefactorInflates many edges at onceExclude from coupling scoring
Rename / moveCreates false adjacency between old and new pathsTrack separately, don’t count as semantic coupling
Formatter-onlyTouches many files with no design meaningDown-weight aggressively or exclude
GeneratedRewrites large surfaces mechanicallyExclude unless the generated artifact is the subject of the question
Dependency bumpCreates broad package-level churnDown-weight; often unrelated to ownership
Semantic changeRepeated, localized, meaningful editsCount normally

Scroll the table sideways to see every column.

The surprising part was not that these commits distorted the graph. It was how little distortion it took. One large refactor can dominate a history window because co-change analysis git tends to reward breadth. A broad commit that touches 60 files creates more apparent relationships than 12 months of smaller, semantically clean changes.

That is why a monorepo refactor is dangerous to coupling inference. It compresses many architectural moves into a short span, and history-based scoring has no native way to tell “these files changed together because we moved them” from “these files changed together because they belong together.”

The same problem shows up in generated code. If a protobuf schema changes and the generated clients are checked in, every downstream file looks correlated with the schema. That is not ownership. It is propagation.

COMMIT TAXONOMY FOR CO-CHANGECOMMIT TAXONOMY FOR CO-CHANGE

We got this wrong initially because the raw counts were so tempting. A file pair with eight co-changes in 30 commits feels meaningful. But if six of those eight came from a single migration and one from a formatter sweep, the count is doing propaganda, not analysis.

freshness-scored wiki

A worked example: the false edge that sent planning down the wrong path

The easiest way to see the failure is with one edge that looked useful and wasn’t.

File pairRaw co-change countCommit types contributing to the edgeNormalized scoreTrusted?
packages/payments/src/router.ts ↔ packages/billing/src/router.ts91 refactor, 4 rename/move, 2 formatter-only, 2 semantic0.18No
packages/payments/src/router.ts ↔ packages/payments/src/rules.ts55 semantic0.84Yes

Scroll the table sideways to see every column.

The first pair looked stronger at a glance. Nine co-changes beat five. The graph placed the packages in the same community, and the planning doc followed suit. The team assumed the billing router was part of the same blast radius as payments router changes. That changed reviewer assignment, test planning, and who got paged for a follow-up fix.

It turned out to be migration noise. The two routers had been moved and renamed during the refactor, then massaged by a formatter and a dependency pin update. They were not sharing behavior. They were sharing a history window.

The operational cost of that mistake was not abstract. We sent the wrong reviewer to the wrong package, widened the blast radius in planning, and overestimated the risk of a change that was actually local. The later investigation was almost boring: the real dependency was within payments, not across the package boundary. The false edge had simply been louder.

This is the part that matters for teams using AI agents and automation. If your tooling reads the graph as a source of ownership, it will faithfully amplify the wrong relationship. The graph does not just suggest where to look. It can also steer a plan toward the wrong code path.

why a change was made

The fix: separate churn from coupling before you score co-change

The right response was not to abandon co-change analysis git. It was to stop treating every co-change as evidence of coupling.

We added guardrails in the scoring pipeline:

  1. Exclude pure refactors.
  2. Down-weight rename-only commits.
  3. Normalize by file churn, not just raw co-change count.
  4. Require semantic overlap before an edge is trusted.
  5. Treat large multi-file sweeps as a separate class of event.

The key idea is boring but effective: score co-change after filtering or normalizing for churn, not before.

A practical implementation looks like this:

  • If a commit touches many files but changes little semantic surface area, it should contribute less.
  • If a commit is mostly renames or moves, preserve the provenance but do not let it create strong coupling.
  • If two files only co-appear inside a single migration window, mark the edge as suspicious until repeated semantic co-edits show up later.

The phrase “semantic overlap” matters. We used it as a thresholding rule, not a vibe. Did the same files keep changing for the same reason? Did the edits land in the same function, schema, or decision boundary? If yes, the edge can earn trust. If not, it stays on probation.

FILTERED CO-CHANGE GRAPHFILTERED CO-CHANGE GRAPH

This is where tools like Repowise, Sourcegraph, and other code intelligence systems start to matter. The useful ones do not just expose a graph. They let you ask for context around the graph, then decide which edges deserve trust.

cross-repo co-change pairs

Which filters were worth the complexity, and which ones were not

We tried a few things that sounded smart and only some of them survived.

MitigationPrecisionRecallOperational costKept?
Exclude pure refactorsHighMediumLowYes
Track renames separatelyHighHighMediumYes
Down-weight churn-heavy commitsMediumMediumLowYes
Require repeated semantic co-editsHighLowerMediumYes
AST-level diff similarity across filesVery highLowHighNo
Commit-message classification with an LLMMediumMediumHighNo
Manual allowlist of “important” filesHighLowVery highNo

Scroll the table sideways to see every column.

The tradeoff is always precision vs recall. If you filter too aggressively, you miss real coupling that happens to appear in noisy commits. If you filter too loosely, you keep the migration artifacts and misread the graph. We chose to bias toward precision for ownership and blast radius use cases, because false positives are more expensive there than false negatives.

The more sophisticated ideas were not worth shipping. AST-level diff similarity sounded elegant, but it was expensive to maintain and brittle across languages. LLM commit classification worked until it didn’t, and then we had to explain why a model thought a generated file was “semantically meaningful” because the commit message was vague. Manual allowlists were worse: they institutionalized exceptions instead of fixing the signal.

The guardrails we kept are mostly cheap. That matters. If the filters require a separate human review loop, they will not survive contact with a fast-moving monorepo.

Where co-change still earns its keep in a monorepo

The useful case is still the original one: repeated, localized, semantically meaningful edits.

Co-change becomes meaningful when:

  • the same files change together across multiple unrelated commits,
  • the edits happen outside refactor windows,
  • the changes are small enough that they are likely about behavior, not migration,
  • the relationship survives renames, formatter sweeps, and dependency churn.

That is when co-change analysis git is actually telling you something about coupling. A stable module boundary that keeps getting edited together is a real signal. A pair of files that repeatedly break together during bug fixes is a real signal. A package that consistently shows up in the same blast radius as another package, after you remove the migration noise, is a signal worth using for ownership and reviewer routing.

This is also where co-change pairs help with reviewer assignment. Not “who touched these files once during the refactor,” but “who keeps touching the same behavioral surface.” That distinction is the difference between a graph that helps planning and a graph that just mirrors the last migration.

The best test is simple: if you removed the largest refactor from the history window, would the edge still exist? If yes, trust it more. If no, it was probably migration noise wearing a coupling costume.

FAQ

How do you tell whether co-change analysis git is real coupling or refactor noise?

Look for repetition outside the refactor window. Real coupling shows up as repeated semantic co-edits across multiple commits, not just a single dense migration event. If the edge disappears when you exclude rename-heavy or formatter-only commits, it was probably noise.

What should you filter out before trusting co-change pairs in a monorepo?

Start with pure refactors, rename/move noise, formatter-only commits, generated file churn, and broad dependency bumps. Those are the usual sources of false edges. Then normalize by churn so one large commit does not dominate the score.

Can co-change analysis be used for ownership and blast radius in a large repo?

Yes, but only after guardrails. It works well when you use it as one input among several, especially for repeated semantic edits and stable module boundaries. Used raw, it will misassign owners and overstate blast radius after a monorepo refactor.

When does git co-change analysis stop being useful?

It stops being useful when the history window is dominated by migration artifacts, generated churn, or broad mechanical sweeps. At that point the graph tells you more about how the repo was reorganized than how the code is coupled. The signal comes back once you filter or normalize for churn.

How do you tell whether co-change pairs are trustworthy enough for planning?

Ask whether the pair survives three checks: it appears in more than one non-refactor commit, the changes are semantically related, and the edge remains after you remove rename-only and mechanical commits. If all three are true, it is probably safe to use for planning, reviewer routing, or blast-radius estimates.

Run this on your own codebase

repowise indexes a repo and generates the docs, the dependency graph and the MCP tools an agent reads from. Free for two public repos.

Index your repo freeBrowse the guides

On this page

  • The graph looked right because the refactor created perfect-looking co-change pairs
  • What the refactor actually changed in the data: rename storms, mechanical edits, and one-off cleanup commits
  • A worked example: the false edge that sent planning down the wrong path
  • The fix: separate churn from coupling before you score co-change
  • Which filters were worth the complexity, and which ones were not
  • Where co-change still earns its keep in a monorepo
  • FAQ
  • How do you tell whether co-change analysis git is real coupling or refactor noise?
  • What should you filter out before trusting co-change pairs in a monorepo?
  • Can co-change analysis be used for ownership and blast radius in a large repo?
  • When does git co-change analysis stop being useful?
  • How do you tell whether co-change pairs are trustworthy enough for planning?

Related posts

engineering8 min read

35x or 16%? we published both, and they measure different things

The same tool on the same codebase gives 35.6x fewer tokens on one measurement and 15.9% on another. Both are correct, and they measure different things.

2026-08-06Read →
engineering7 min read

a tool that did nothing came out 43% cheaper than the bare agent

A tool that never called its own server measured 43% cheaper than a bare agent. Prompt caching explains it, and it shows why dollar-cost benchmarks mislead.

2026-08-06Read →
engineering12 min read

how to benchmark a codebase tool so the number survives a rerun

JetBrains reran two token-saving claims and both collapsed. Here is the eight-part methodology we used to build a benchmark that survives an independent rerun.

2026-08-06Read →

Index your repo free

Index your repo freeRead the docs
repowiserepowise

Codebase intelligence for AI agents. Open source under AGPL-3.0, hosted SaaS for teams.

Features
  • Code health
  • Agent provenance
  • AI context (MCP)
  • Change risk
  • Security
  • Auto wiki
  • Git intelligence
  • Architecture (C4)
  • Decisions
Solutions
  • For developers
  • For teams
  • For team leads
  • For engineering leaders
  • For security
  • For enterprise
Compare
  • vs CodeScene
  • vs DeepWiki
  • vs Sourcegraph
  • vs Cursor
  • vs GitClear
  • vs SonarQube
  • vs Snyk Code
  • vs Codacy
  • vs Code Climate / qlty
  • vs Qodo
  • vs Greptile
  • vs Swimm
  • vs CodeRabbit
  • vs CodeGraph
  • vs Graphify
  • vs Serena
  • vs code-review-graph
  • All comparisons
Guides
  • Code health
  • AI context & MCP
  • Git intelligence
  • Change risk
  • Architecture
  • Auto-wiki
  • Decisions & ADRs
  • Agent provenance
  • Security
  • All guides
Product
  • Pricing
  • PR Bot
  • Browse indexed repos
  • Health leaderboard
  • Book a demo
Resources
  • GitHub
  • Docs
  • Benchmarks
  • Blog
  • Discord
About
  • Founder
  • Architecture
  • Contact
Legal
  • Privacy
  • Terms
  • Security
© 2026 repowise. AGPL-3.0.hello@repowise.dev
Featured on Aura++