From diff to trustworthy draft

PR Lens’s analyze command has a narrow but important job: turn a real pull-request diff into a candidate graph without confusing generated description with repository fact. This chapter follows that job from commit selection to model response and repair. The practical result is a way to read an analyzed graph with calibrated confidence: you can tell what Git established, what the model inferred, and why a valid-looking draft still needs another boundary before it is rendered.

The comparison point is part of the evidence

A diff is not just a pile of changed text. It is a comparison between two states, and the choice of those states changes the story the graph can tell. PR Lens resolves the requested base and head references, then computes their merge base before collecting the change. The merge base is the point where the pull-request branch split from the base history. Comparing that point with the head means the graph describes work introduced by the pull request, rather than also blaming it for unrelated commits that later reached the base branch.

Imagine that a feature branch was created on Monday. On Tuesday, the base branch receives a separate logging change. On Wednesday, the feature branch changes a queue consumer. If the tool compares the feature head with Wednesday’s base tip, the diff includes the logging change even though the feature branch did not make it. The resulting graph may mark a component as changed for the wrong reason. Using the merge base keeps the comparison attached to the branch’s own transition.

That choice also gives the document meaningful provenance. The graph records the repository, the base and head commits, and optional pull-request information. A reviewer can therefore connect an element back to the revision that was analyzed. This matters especially for removed elements: a file that exists only on the base side needs a base reference, while a current element points to the head side. The picture is not an unmoored architecture sketch; it is an account of a particular change between identifiable states.

After finding the comparison point, the CLI collects a rename-aware file summary and patch. Git supplies the changed-file count and line additions/deletions. The patch is the material the model interprets, but it is subject to a configured byte budget. If the patch is too large, the CLI sends only its beginning and tells the model that the tail is missing. That warning changes the safe conclusion. A graph produced from a truncated diff can describe the visible part, but it must not pretend to know what the unseen portion contains.

This is a simple form of evidence discipline: the input boundary is part of the output’s meaning. A model can produce fluent JSON from a partial patch, and the contract can accept that JSON, but neither fluency nor structural validity restores omitted evidence. The CLI’s explicit truncation signal does not solve the problem by itself; it makes the limitation available to the model and, through the resulting provenance and operational context, to the people who decide whether the picture is sufficient.

The file summary and patch also play different roles. Counts help the tool report the scale of a change and populate statistics. The patch supplies local context around edits and the relationships the model must reconstruct. A binary file may have no useful line counts, while a source file may contain several unrelated edits. PR Lens does not treat the number of files as a substitute for understanding. It gives the model the diff and asks it to select the responsibilities and connections that matter to a reviewer.

The choice of comparison also affects what a file reference means after the run. A head-side reference points a reviewer toward the code that exists in the proposed state; a base-side reference can preserve the old implementation that the change removed. That distinction is easy to lose in a generic summary, but it matters when the reviewer is checking whether a replacement really covers the old behavior. Provenance turns the graph into a time-bounded explanation: it describes one transition, not an eternal claim about the repository.

The CLI also refuses an empty diff rather than asking a model to invent a story. If the two states contain no change, there is no review transition for the graph to describe. At the other extreme, a very large diff is truncated rather than sent without a limit. These two decisions define the useful domain of analysis: there must be a real transition, and the model’s claims must stay within the material the pipeline actually supplied.

That request is deliberately more constrained than “summarize this code.” The analysis prompt asks for a graph document with named lenses, reader-facing lanes, unchanged neighbors, a small number of emphasized relationships, and a flow only when there is an ordered sequence worth animating. It discourages folder-shaped architecture and exhaustive detail. The desired output is not a second copy of the patch. It is a compact interpretation that places the patch in the system around it.

The constraint creates a trade-off. More nodes can preserve more context, but they make the picture wider and the relationships harder to follow. Fewer nodes can make the main path clear, but can hide an unchanged dependency that determines the blast radius. The CLI cannot settle that trade-off mechanically because it depends on what the change means. The prompt supplies a design standard; the model still has to exercise judgment, and a reviewer still has to inspect whether the chosen map is useful.

Let the model propose, not define

Only the analysis step contacts a model. Rendering, validation, comment composition, export, and canvas operations work on the graph without sending a new request. In the CLI path, the user supplies the provider key through an environment variable rather than a command-line argument. That avoids placing the secret in shell history or a routine log line. The diff goes directly to the named provider; the CLI does not route it through a PR Lens analysis service.

The provider boundary is expressed through request shapes. The repository supports Gemini’s own endpoint, OpenAI’s endpoint, and an OpenAI-compatible chat-completions shape for compatible or local servers. This is an important distinction: “provider support” is not a hard-coded ranking of vendors. It is a small set of protocol choices, with the endpoint and model selected by the caller where needed. The repository also keeps OpenAI separate from compatible servers because their output-limit fields have diverged. A new compatible endpoint can be selected by its base URL without adding a new vendor branch to the project.

The model receives a system instruction, a user prompt containing repository facts and the bounded diff, and a JSON schema for the graph. It is told to return the document alone. It is also told what not to do: the graph is for comprehension, not for reporting bugs, risks, style issues, or security findings. This negative boundary is useful because a model that mixes review conclusions into the graph would make the picture look authoritative in a way the project does not intend.

The model is not allowed to define every field that looks factual. Before the response becomes a graph, the CLI stamps fields that the repository already knows: the contract version and document kind, generation time, provenance, requested lenses, and diff statistics. In particular, commit identities and line counts come from Git and the CLI. If the model were free to repeat those values, two parts of the same document could disagree about what was compared or how large it was. Stamping creates a clear division of authority: the model proposes structure and explanation; the repository supplies facts it can calculate directly.

The document contract is also versioned, and the versions need to be kept distinct. In this checkout, the graph contract is 0.1.1, while the published package versions for the CLI, renderer, and schema are different release numbers. A package release can change documentation or implementation without changing the document contract. Calling all of these numbers “the PR Lens version” would erase the distinction that determines whether a graph can be read. The CLI writes the contract version into the document so consumers can make that decision explicitly.

A model response can fail before its meaning is even considered. Some providers return a Markdown fence around JSON; some prepend a sentence; a response can also be cut off. The extraction layer makes a small amount of recovery possible by finding a JSON object and parsing it. That recovery does not loosen the graph contract. Once an object is obtained, the CLI sends it through the schema parser and graph-integrity checks. Unknown fields, missing references, invalid paths, incompatible lenses, and other contract failures remain failures.

If the response does not validate, the CLI gives the model the named problems and asks for the whole document again. It allows one correction round. This limit is not an arbitrary impatience with models. It separates a local repair from an unresolved interpretation. If an edge points to a node the model forgot to declare, a path-specific error may be enough to fix the document. If the model still cannot produce a valid graph after seeing that exact problem, repeated retries are unlikely to provide a principled improvement; they would mostly make the pipeline slower and less predictable.

sequenceDiagram participant R as Repository participant C as PR Lens CLI participant M as Named model provider participant S as Graph contract C->>R: Resolve base and head refs C->>R: Compute merge base and collect bounded diff C->>M: Send facts, prompt, and visible diff M-->>C: Candidate graph JSON C->>C: Stamp provenance, lenses, and Git counts C->>S: Parse and check integrity alt Valid document S-->>C: Graph ready for rendering else Contract failure S-->>C: Paths and machine-readable issues C->>M: Request one named-path repair M-->>C: Corrected graph JSON C->>S: Parse and check again end

The sequence has two kinds of failure. A provider can fail to answer, exceed its output limit, or return a response in an unusable shape. Those are transport or completion failures. A provider can also answer fluently with a graph that is structurally wrong. That is a document failure. PR Lens gives these cases different error paths so an operator knows whether to change the provider request or fix the graph content.

Neither path proves semantic completeness. A graph may validate even though the model missed a dependency that the visible diff did not make obvious. It may choose a lane that is structurally legal but not the clearest boundary for a reader. It may omit a flow because the sequence could not be inferred safely. The contract protects the handoff to the renderer; it does not certify the model’s architecture judgment. That is why the analysis prompt asks for file references and unchanged neighbors: they give later readers a route from the interpretation back to evidence.

The same boundary applies when a coding agent authors the graph without asking a provider. The agent already has the diff or code in its working context, but its document still goes through validation and rendering. The difference is who proposes the graph, not whether the graph is exempt from the contract. In both routes, a trustworthy draft means “the source of each kind of claim is visible and the document is internally coherent,” not “the generated picture is the final word about the system.”

The next boundary is therefore necessary. The model has supplied a candidate and the CLI has attached repository facts, but a candidate still needs to be rejected when its references do not hold together. Validation must answer not just whether the object looks like JSON, but whether the relationships that make a diagram meaningful are actually present. Then a repository needs a way to correct recurring names and boundaries without fighting a fresh inference on every run.

References