Changes and graph reviews
Opening and merging changes; reading findings, change maps, renames and the API view.
A change proposes merging one branch (the source) into another (the target), like a pull request. Every change gets a graph review: what the change does to the structure of the code, derived from the code itself.
The life of a change#
- Opening a change: from the repository's Changes page, or
bscope change create --title "…"(source: the branch you're on; target: the default branch). - When reviews run: on opening, again on every push to the source branch, and on demand with Re-run review.
- Checks: workflows and other CI report checks on the change's latest commit, shown above the review. Into a protected branch, the repository's required checks must pass before Merge works; an administrator can Merge anyway, giving a reason that's kept on the change.
- Merging: goes through the push guard, exactly like a push. A merge into a protected branch by an agent is refused, and so is a merge while the guard denies everything.
- Merge methods, as on GitHub: Create a merge commit (the default), Squash and merge (one commit on the target), or Rebase and merge (the change's commits replayed onto the target, one by one). The repository's Settings → Merging says which are allowed and which is offered first. Into a branch whose ruleset requires linear history, Create a merge commit is shown as unavailable, with why, and the first other method is offered; those on the ruleset's bypass list can still use it. Merging asks you to confirm, with the commit message to edit; a rebase keeps its commits' own messages.
- Conflicts are caught before you try, for each method: which files a merge commit or squash would conflict in, and whether a rebase would (at which commit) or can't, because the change contains merge commits. A merge that would conflict is refused, and nothing is written.
- Who the new commits name: Branchscope commits what it makes. You're the author of a merge commit; the change's author is the author of a squash, with
Co-authored-byfor the other authors of its commits; rebased commits keep their authors. Agents are named as<agent>@agents.<your Branchscope's domain>. When the site has a signing key, these commits are signed by Branchscope, so they pass a ruleset that requires signed commits; without one, merging into such a branch is refused. - The other tabs: Files shows the textual diff, and Commits lists what's in the change. When the change conflicts with its target, Conflicts shows both sides: see conflict maps.
Reading a graph review#
The review compares the code graphs of the change's base (where it branched from its target) and its head.
- same commit, same diagrams, byte for byte
- rule and plan checks down to the file and line
- reported as the check branchscope/review, which a ruleset can require before a merge
Summary#
The tiles at the top count:
- rule violations: what a
denyrule forbids; - needs review: what a
reviewrule flags; - against the plan: what the change touches that its PLAN.md rules out;
- the graph's size, before and after.
Findings#
- Breaks
rule-idand Needs review:rule-id:- each new violation, named in plain terms ("
InvoicesController.Create()writesinvoices"); - a link to the exact line;
- the count of older violations already in the target, which aren't this change's fault.
- each new violation, named in plain terms ("
- Fixes
rule-id: violations the change removes. - Renamed or moved: elements whose code is unchanged but which now have a new name or place. They count as one change, not a removal plus an addition, and their rule violations stay "existing".
- Rules changed: the change edits the rules themselves. The review uses the target's rules, so a change can't switch off the rule it breaks.
- Plan: what the change touches that the plan rules out, or doesn't mention. See rules and plans.
What changed: change maps#
One diagram for each connected group of changes:
- Elements: the changed functions, types and fields, drawn inside their project, file or class.
- Context: the unchanged code they connect to, the paths up from them, and the paths down to boundaries (endpoints, tables, screens).
- Connections: each changed connection, labelled with what it is (calls, reads, writes, handled by…).
| Colour or line | Means |
|---|---|
| green | new |
| blue | edited (its body changed) |
| red, dashed | removed |
| purple | renamed or moved ("was fetchInvoice()", "moved from src/a.ts") |
| grey | unchanged context |
| red line | a connection a deny rule forbids |
| amber line | a connection a review rule flags |
| dashed line | unresolved: built at runtime, matched by pattern |
Click any element to open it in the graph explorer at the right commit. Zoom with −, Fit and +. As text lists everything in the diagram, for screen readers and for copying.
API#
Endpoints added, changed or removed, and who calls and handles them, across languages. A TypeScript fetch and the C# action serving it are joined on the endpoint (http:billing-api:POST /invoices).
Other repositories#
After the API diagrams: the other repositories of the same owner (an organization, or your own repositories) on the other side of what the change touches, from the connection index. It covers each endpoint, table and channel the change adds, removes or changes, and each one whose handler or caller it modifies (that function's code changed).
Breaking: stops serving DELETE /invoices/{}, which web-app and mobile-bff still call (with links to each call).
Joined from every repository's graph by endpoint, channel and table; compared with the default branch, or with what's deployed to production. Repositories you can't read stay invisible. The organization's Graph tab draws it all.
- Breaking: an endpoint or channel this change stops serving or publishing, which other repositories still call or subscribe to, and none of them serves or publishes.
- Missing: an endpoint this change starts calling that no repository serves, this one included.
- Affected: an endpoint this change keeps serving but whose handler it modifies, with the repositories that call it. The graph has an endpoint's method and path, not its request and response shapes, so this is as much as it can say about a contract.
Then, for each endpoint, table or channel, the functions on the other side, each linked to its line in that repository's default branch, with how fresh that repository is: "as of a1b2c3d4; a newer build (e5f6a7b8) failed" means its connections come from the last default-branch graph that was built.
As deployed: Each repository at, above the findings, switches from Default branch to an environment that a repository you can read has deployed to, such as production. Each other repository is then compared at its deployed commit, or at its default branch, saying why, when it has no deployment there or that commit has no graph yet. See as deployed.
- Breaking can block a merge: through the review's check, when a ruleset requires it. Missing and Affected never do.
- Only what you can read: repositories you can't read are left out, with every finding only they would cause, as if they didn't exist. That holds for tokens limited to some repositories, and for agents.
- Not part of the graph: the section is a separate document, pinned to each other repository's commit, so the change's own graph doesn't depend on anything outside it.
- Channels: a change that stops publishing a channel, such as a topic constant renamed or a publish removed, is Breaking when another of your repositories still subscribes to it and none of them publishes it, and so fails
branchscope/reviewwhere that check is required. Channels on the brokerdefault, unresolved names and patterns never count. See messaging.
Conflict maps#
When the target branch has moved on since the change branched from it, Branchscope compares both sides from their merge base: what the target did since then (current), and what the change did (change). Like the review, it's derived from the code only. It's on a Conflicts tab whenever the text conflicts or a semantic conflict is found, and the change says how many elements both sides touched.
Both sides since the merge base, element by element, with code links on each side. Selecting one highlights it in both.
Semantic conflict: main removes formatInvoice(), which the change starts calling. Merges cleanly as text.
Shown as a warning in the merge box, from the same graphs as the review, before anything is merged.
- Both sides as change maps, side by side, each limited to the elements both sides touched and what they connect to directly. Those elements have a dashed outline; select one, in either map or in the lists, to highlight it on both sides. Past 40 collisions, the maps draw the first 40 and the lists have them all.
- Elements both sides touched, with what each side did (added, removed, renamed or moved, changed, or edited, with how many lines), each linked to its code on that side.
- Conflicting files: the files git's merge reports, each conflicting region placed on the functions and types of the merge base that hold it. Files the graph doesn't model, such as a README or a config file, are listed as files.
- Semantic conflicts, which merge cleanly as text but don't fit together:
- one side removes, renames or moves something, or stops serving an endpoint, and the other adds a call or a use of it;
- one side changes a declaration (an endpoint's route or method, a table's columns, a function becoming async) and the other adds a use of it;
- both sides add an element with the same ID, such as the same C# type in two files.
- A warning, never a block: semantic conflicts are shown in the merge box ("Merges cleanly as text, but:
createInvoice()was renamed toissueInvoice()on main, and this change adds a use of it"), and don't stop a merge. - When it's worked out: once the target has moved past the merge base, when the change is opened or viewed and whenever either branch is pushed. It's a job of its own beside the review, from the three graphs (built first if missing) and git's merge.
TypeScript parameters aren't in the graph, so a TypeScript function whose parameters changed shows as edited, not as a changed declaration. Nothing here suggests a resolution: the map shows what each side did.
From a script or an agent: bscope change conflicts 3 --wait, GET /v1/repos/<owner>/<repo>/changes/<n>/conflicts, or the MCP tool get_conflict_map.
The review as a check#
Each change's review is also a check, branchscope/review, on the change's head commit. It's listed with the change's other checks, and its Details link opens the review's findings. A ruleset can require it under Require status checks to pass, like any other check. Then a change merges only once its review passes.
| State | When |
|---|---|
| queued, running | The review hasn't finished. |
| failed | The review found an error: a new violation of a deny rule, or Breaking under other repositories. It's also failed when the review itself failed or its results can't be read. A required check that can't be worked out blocks the merge; re-run the review. |
| passed | No errors. Warnings don't fail it: review rules, what the plan rules out, Missing and Affected. Its summary counts them. |
- The same for everyone: one check, whoever looks. For Breaking, it counts only the other repositories that everyone who can read this repository can read too. For a public repository, that's the owner's public repositories. For a private one, it's the owner's other repositories, less any that one of this repository's outside collaborators can't read. Its summary names this repository's own rules, endpoints and channels, never another repository. Other repositories on the change page shows who's on the other side, as you can see it.
- Kept current: it's worked out again when the review moves, when another repository's default branch is reindexed (for that owner's open changes), and when a change is merged with the check required.
- Only Branchscope reports it: check names starting with
branchscope/can't be reported through the API, so nobody can pass it for the review. - Merging past it: as for any required check. Those on the ruleset's bypass list aren't held by it, and a repository administrator can Merge anyway, with a reason. Agents can't. See merging past required checks.
Reviews are deterministic#
The same base and head always give the same review. Nothing in it is written by an LLM: graphs come from the compilers' own understanding of the code (TypeScript's, through SCIP; C#'s, through Roslyn) and from framework adapters. When something can't be known statically, such as a URL built at runtime, the review says so (unresolved) rather than guessing.
From a script or an agent#
bscope change review 3 --wait # waits for the review, prints it as text
bscope change review 3 --json # everything, as JSON
bscope change review 3 --fail-on deny # exit 3 if a deny rule is broken
bscope change review 3 --against production # other repositories as deployed to production
bscope change review also prints the state of branchscope/review and other repositories, with where each one is taken from (its default branch, or as deployed), and --json includes both (check, connections).
The same data is at GET /v1/repos/<owner>/<repo>/changes/<n>/views (see API).
Other repositories are at GET /v1/repos/<owner>/<repo>/changes/<n>/connections, and agents get them in the MCP tool get_review, with what the agent can read. get_review also gives the state of branchscope/review. The check itself is at GET /v1/repos/<owner>/<repo>/changes/<n>/checks, and finishing sends check.completed to webhooks, as any check does.