On this page
Context-aware code review means the reviewer, human or bot, sees what the diff doesn't: who calls the changed code (blast radius) and which files git history says usually change with it (co-change). For tightly coupled code and microservices, the common bug is a missing change, so review has to look outside the diff to catch it.
| Review approach | Sees callers outside the diff | Sees files that usually change together | Across repositories | Needs a model |
|---|---|---|---|---|
| Diff-only review (human or LLM) | No | No | No | Optional |
| LLM review with a code graph (Greptile, CodeRabbit Advanced blast radius, Qodo Software Map) | Yes, from the code graph | Not described in their docs | CodeRabbit multi-repo linking, Qodo cross-repo review | Yes |
| repowise PR bot, Signals mode | Yes, symbol level, from the call graph | Yes, from git history | No, one repository per comment | No |
git log script in CI | No | Yes, roughly | Only if you merge histories | No |
Scroll the table sideways to see every column.
Why diff-only review misses coupled code
A diff is a list of what changed. Coupled code fails in the opposite way: something that should have changed didn't.
Typical pairs are an interface and its implementation, a protobuf file (a schema for the messages services send each other) and the code generated from it, or a config key and the two services that read it. When someone edits one side and forgets the other, the diff is small, clean and easy to approve. The reviewer has no reason to open the file that isn't there. Tests may pass too, if nobody wrote a test that crosses the boundary.
This is why AI code review for highly coupled code is a hard problem. A model reading the diff, even a very good one, sees the same thing the human sees. Giving it more of the repository helps when the missing piece is reachable through imports or calls. It doesn't help when the link only exists in history: two files in different services that change together because they encode one rule, with no import between them. We wrote a longer guide on finding hidden coupling with co-change detection; this post is about putting it into review.
What co-change looks like in a real microservice codebase
Temporal's server is a good example because it is split into services inside one repository: service/frontend, service/history, service/matching, plus shared code in common/. The table below lists some of the strongest co-change pairs repowise found in the 9,894 commits it read for temporalio/temporal. "Confidence" is the share of the first file's commits that also touched the second.
| File | Usually changes with | Commits together | Confidence |
|---|---|---|---|
service/history/interfaces/mutable_state.go | service/history/workflow/mutable_state_impl.go | 302 | 0.944 |
common/dynamicconfig/constants.go | service/history/configs/config.go | 275 | 0.379 |
api/persistence/v1/executions.pb.go | proto/.../persistence/v1/executions.proto | 193 | 0.937 |
common/dynamicconfig/constants.go | service/frontend/service.go | 190 | 0.262 |
api/historyservice/v1/request_response.pb.go | proto/.../historyservice/v1/request_response.proto | 165 | 0.927 |
common/dynamicconfig/constants.go | service/frontend/workflow_handler.go | 156 | 0.215 |
Scroll the table sideways to see every column.
The pairs fall into different kinds of coupling, and each kind needs a different response.
The first row is interface-and-implementation coupling, which exists by design. A change to the MutableState interface almost always means a change to its implementation, and the compiler will catch most misses. Here co-change mostly confirms what the types already enforce.
The .proto and .pb.go pairs link a contract to its generated code, and they sit above 0.92. Missing one side is usually a forgotten code generation step. A reviewer who sees "this proto file changed and its generated file didn't" saves a broken build or, worse, a stale wire format.
The dynamicconfig/constants.go rows show shared config across services, and they matter most for microservices. One file in common/ defines settings, and the history and frontend services each read them. These pairs cross a service boundary, and the confidence is lower, 0.2 to 0.4, because not every new setting touches every service. Review misses happen in pairs like these, because the link is real but not constant, so nobody remembers it. You can browse the full list on Temporal's coupling view.
None of this reflects badly on Temporal's design, because every large service codebase has pairs like these. The knowledge already exists in git history, and a reviewer can't hold 9,894 commits in their head.
Blast radius at the symbol level
Co-change answers "what usually changes with this", while blast radius answers "what breaks if this changes". Blast radius comes from the code graph: parse the repository, resolve calls and imports, and when a pull request changes a function's signature, list everyone outside the pull request who calls it. Working at the symbol level matters: if a file has 40 importers but the pull request only edits the body of one private function, file-level blast radius is noise. Symbol-level blast radius stays quiet unless a contract actually changed.
A real example from the repowise repository, PR #1204: a signature change to mark_tombstone_pages in persist.py was called by 9 symbols outside the pull request, including the update command and two tests. The comment named three and linked the rest.
For microservices, blast radius inside one repo covers a shared library and its callers, but it stops at the network. An HTTP handler's callers are in other services, and no call graph sees through a URL, so that case needs contract extraction and cross-repo analysis, covered below.
How the repowise PR bot uses both
I build repowise, so this section sticks to what its PR bot does, as written in its documentation, and nothing more.
In Signals mode, the default, it builds a comment from the repository's index with no model involved, and posts only when something fires. These are the signals that matter for coupled code:
- Callers a change breaks: when a pull request changes a function's signature, it names the callers outside the pull request, symbol level.
- Files that usually change together: when a top co-change partner of a changed file is missing from the pull request, it names it. A real line from PR #2599:
perf/dialects/python.py"changed with"perf/dialects/ts_js.py"in 11 past commits and is not in this PR". - Tests to run first, found from the import graph, and suggested reviewers who own the risky files.
The same diff always gets the same comment, and no line of code goes to a model. On Pro and Teams you can add AI triage (about 2 cents a push, only when a signal fired) or Full AI review (about 10 cents a push), which reads the diff with callers, importers and definitions around it.
The bot has limits: it works one repository at a time, and co-change needs history, so a new repo or a renamed file has little to go on. A co-change hint is also only a prompt to look, and it proves no bug: plenty of pull requests correctly change only one side of a pair.
When your microservices live in separate repositories
Everything above works when services share a repository. With one repo per service, both signals get harder: no single git history holds both sides, and no call graph crosses the boundary.
The tools handle this in different ways:
- CodeRabbit multi-repo analysis links related repositories, by hand on Essentials and automatically on Team and above, then checks a pull request for impact on the linked repos, such as API changes that break consumers. The number of linked repos depends on the plan (1 on Essentials, 5 on Team, 10 on Advanced).
- Qodo offers cross-repo review that flags signature changes in shared libraries affecting downstream repos, and a Software Map of how repos, services and contracts connect.
- repowise workspaces index several repositories together and add cross-repo co-change (files in different repos modified in the same time window), API contract extraction for HTTP routes, gRPC services and message topics, and package dependencies. This lives in the workspace views and the CLI. The PR bot comment itself is still per repository.
If you can choose, the cheapest fix is structural: put the contract in one place (a shared proto or OpenAPI repo), generate clients from it, and make CI (continuous integration, the checks that run on every push) fail when generated code is stale. Then co-change across repos becomes a build error instead of a review question.
A version you can run today with git log
You can try co-change without a product. This script prints the file pairs that changed together in at least 5 of the last 2,000 commits, skipping huge commits (formatting sweeps, vendoring) that would pair everything with everything:
git log --no-merges --name-only --format='format:@@' -n 2000 | awk '
function flush(){ if(n>1 && n<=50) for(i=1;i<=n;i++) for(j=i+1;j<=n;j++){a=f[i];b=f[j]; if(a>b){t=a;a=b;b=t}; c[a" "b]++}; n=0 }
/^@@$/ {flush(); next}
NF {f[++n]=$0}
END {flush(); for(k in c) if(c[k]>=5) print c[k], k}' | sort -rn | head -20
Run it in CI against the files in a pull request, and you have a crude "you changed X, X usually changes with Y" check. It lacks rename tracking, recency weighting so old pairs fade, and a confidence ratio so a file that changes in every commit doesn't top every list.
How we measured
The Temporal pairs come from repowise's public co-change API for the snapshot indexed on 6 October 2026 at commit a2de6d8, which read 9,894 commits. The PR examples come from public pull requests on the repowise repository. Competitor capabilities come from each vendor's own docs and pricing pages on the same day. We didn't run a head-to-head test of the review tools, so this post can't tell you which catches more coupled-change bugs in practice.
FAQ
What is context-aware code review?
It is review that uses information beyond the diff: the callers of changed code, the files that history says usually change with it, the tests that import it and who owns it. It matters most when the bug is a change that is missing from the pull request.
How do you review tightly coupled code?
Make the coupling visible at review time. List the callers of any changed signature, flag co-change partners that are missing from the pull request, and point the reviewer at the tests that cover the changed files. Then decide case by case whether the missing file really needed a change.
Can AI code review catch breaking changes across microservices?
They can catch part of it. Tools with a code graph catch callers inside a repository. Cross-repo features, like CodeRabbit's linked repositories or Qodo's cross-repo review, look for API and library breaks in other repos. None of them can see a dependency that exists only at runtime, like a message format two services agree on without sharing code.
What is co-change analysis?
Co-change analysis reads git history to find files that are modified in the same commits. If two files changed together in 275 commits, editing one without the other deserves a second look. It finds coupling that imports miss, such as a config file and the services that read it.
Is blast radius the same as co-change?
No, they are different signals. Blast radius comes from the code graph and answers who calls the changed code. Co-change comes from git history and answers what usually changes with it. A file can have a large blast radius and no co-change partners, or the reverse.