PR Lens

Diagrams

How to generate an architecture diagram from a git diff

You can generate an architecture diagram from a diff, but not from the diff alone. Two working routes, with the real commands: your coding agent, or the PR Lens CLI with your own model key.

Fifteen components in three lanes. Only some of them are in the diff; the rest are why the diff makes sense.

You can, and the practical answer is two commands, but the diff on its own will not get you there. A diff is a list of changed lines in changed files. An architecture diagram is a claim about components and the calls between them, and most of those components are not in the diff. The tool has to read the repository as well. That gap is why so many diff-to-diagram experiments look thin: they draw the six files that moved and none of the machinery those six files talk to. It matters more now than it used to, because LinearB's 2026 benchmarks put AI-assisted pull requests at 408 lines at the 75th percentile against 157 for unassisted ones.

Why the diff alone is not enough

Take a real shape. A pull request moves email sending off a per-recipient trigger and onto a batch endpoint. The diff touches five files: a queue route, a new sender module, a shared library, a config, and the old worker that gets deleted.

Draw only those five and you have a picture that answers no question anyone had. Is the queue the same queue the rest of the system uses, or a new one? Does the new sender write to the same store the old worker did? What still calls the thing that is being deleted? Every one of those answers lives in files the diff never mentions.

A diff knows which lines moved; only the repository knows what they moved next to.

So the generation step is really two steps. Read the diff to find out what changed. Read the code around it to find out what the changed things are attached to, then draw the second set in a way that says "unchanged" rather than leaving them out. The hosted PR Lens App does this by keeping a baseline of the repository and tracking it across commits, so a pull request is drawn against the system rather than against itself. Locally, the model reading your repository does it in one pass. Either way, the unchanged neighbours are load-bearing, which is the argument in blast radius diagrams.

One more detail that changes what you get: measure the change against the merge base, not the tip of the base branch. Git's own documentation spells this out for the three-dot form: git diff A...B is equivalent to git diff $(git merge-base A B) B. Diff against the tip instead and your diagram will blame this branch for every change anyone else merged into main since it forked.

The fast route: the coding agent that already read the diff

If an agent wrote the branch, it has read every file it touched. Asking it to draw the change costs one sentence and no key.

npx skills add coldteadotai/pr-lens

Then, in the session:

Diagram the change you just made with PR Lens.

The skill gives the agent an operating manual rather than a drawing tool. It reads the diff with git diff --find-renames <base>...<head>, writes a JSON graph document to .pr-lens/graph.json, validates it, fixes whatever the validator names, renders it, and hands you the files. The model writes a document and a separate renderer draws it, which is the split I argue for in why the model should describe the graph and never draw it.

It works in Claude Code, Cursor, OpenCode and anything else that reads Agent Skills, because the skill is a Markdown file with YAML frontmatter that assumes nothing beyond a shell and npx.

If you want the diagram somewhere you can send to a person, add:

npx @coldtea/pr-lens-cli canvas push

That puts the document on prlens.dev as a page anyone with the link can open, and prints a view link, an edit link and an SVG embed link for a README. Push the same file again and the same canvas updates, so "rename that node and push again" keeps the link stable.

The reproducible route: the CLI with your own key

When there is no agent in the loop, or when you want the same steps to run in CI, the CLI does each step on its own.

export GEMINI_API_KEY=
 
npx @coldtea/pr-lens-cli analyze --base origin/main
npx @coldtea/pr-lens-cli render .pr-lens/graph.json

analyze is the only command that talks to a model. render, comment, validate, export and canvas never do. The key is read from the environment and never taken as a flag, because a flag lands in shell history and in the log of whatever CI runs it.

Gemini is the default rather than a requirement. Three request shapes are implemented: --provider gemini, --provider openai, and --provider openai-compatible with a --base-url, which is how you reach OpenRouter, DeepSeek, Ollama or a llama.cpp server without waiting for anyone to add a vendor.

npx @coldtea/pr-lens-cli analyze --base origin/main \
  --provider openai-compatible \
  --base-url http://localhost:11434/v1 \
  --model qwen3-coder \
  --api-key-env OLLAMA_KEY

Useful flags on analyze: --head for the tip of the change if it is not HEAD, --lens architecture if you only want the one, --pr <number> to record the pull request in provenance, and --dry-run to see what would be sent without sending it. On render, --theme light or --theme dark draws one half of the pair instead of both.

Everything lands in .pr-lens/, and the first write adds that directory to the repository's .gitignore along with a README saying what is in it. None of it is meant to be committed. The SVGs and the document rebuild from the diff whenever anyone wants them, and a diagram of a pull request that merged months ago is worse than no diagram.

What the model actually writes

The document is the interesting part, and it is small enough to read. Lanes are the bands the diagram is laid out in. Nodes are the components. Every node carries a kind that decides its icon, and a delta saying what the change did to it.

{
  "schemaVersion": "0.1.1",
  "kind": "graph",
  "title": "Touch the health check",
  "lenses": ["architecture"],
  "provenance": {
    "repo": { "owner": "coldteadotai", "name": "pr-lens" },
    "base": { "sha": "1111111" },
    "head": { "sha": "2222222" }
  },
  "lanes": [{ "id": "api", "label": "API" }],
  "nodes": [
    {
      "id": "health-route",
      "label": "GET /health",
      "kind": "route",
      "delta": "modified",
      "lane": "api"
    }
  ]
}
That document, drawn. One lane, one component, and the render says so and stops.

delta takes four values: added, modified, removed and unchanged. That fourth one is the whole reason the diagram is worth generating from a repository rather than from a diff. Unchanged elements are first class in the schema, not an omission.

kind is deliberately coarse: service, app, module, function, route, job, queue, datastore, cache, external, ui, config, test, package, and other for anything that does not fit. It drives the card icon and nothing else. No analysis hangs off it.

Nothing in the document says how to draw anything. There are no coordinates, no colours and no sizes, which is why the same document renders identically on a CI runner with no fonts installed and on your laptop.

Fixing a diagram that names things wrongly

Generated diagrams get names wrong. A path becomes packages/broadcast-lib/src/send.ts when everyone on the team calls it the broadcast sender, and a test helper shows up as a component.

Fix that in .github/pr-lens.yml, not in the generated output.

schemaVersion: 0.1.0
map:
  rename:
    - match: services/legacy-mailer.ts
      to: Postmark sender
  exclude:
    - "**/*.test.ts"

The file is an overlay applied at draw time. Inference never writes back into it, so the correction keeps holding as the code moves and as the model picks different names between runs. render also reports any correction that changed nothing about what it drew, which catches the case where a rename rule names a file that has since moved and has been quietly doing nothing for a month.

How to tell whether the generated diagram is any good

Four things I check, in order.

Are there unchanged components on the page? If every box is coloured, the tool drew the diff and called it architecture.

Do the lanes mean something? Lanes should be a boundary a person recognises, a runtime, a tier, a service, and not a mirror of the folder tree. A diagram with lanes called src, lib and test has learned nothing you did not already know.

Is there one edge that is obviously the point? A change is usually about one connection. If nothing stands out, the diagram has flattened the change into a network and you are back to reading files.

Does it say what was removed? Deletions are the edits most likely to be a mistake nobody notices, and a diagram that silently drops them is hiding the most reviewable thing in the pull request.

When to run the CLI, and when to use the skill

Start with what you want drawn. A diagram on every pull request, forever, is the GitHub App's job: install it once and it keeps the baseline of the repository, posts one comment, and redraws on every push, with no key of yours anywhere in it. How to visualise a pull request as a diagram ranks all five routes, including the ones that do not involve running anything.

Reach for the skill when the change in front of you is the one you want to see and an agent is already open on it. It costs a sentence, it reasons with the model that agent already runs, and there is no key to put anywhere.

Reach for the CLI when the same steps have to run again the same way. That is CI you control with a model key you control, which is what the GitHub Action wraps. It is also the local loop, where you draw the change before you open the pull request, look at what you actually built, and fix the thing that looks wrong while it is still cheap. That second one is the habit I would most like to spread, and it has its own post: diagram the change before you open the pull request.

Questions people ask

Can you generate an architecture diagram from a git diff?

Yes, as long as the tool can also read the repository the diff came from. The diff supplies the changed files and the changed lines. The surrounding components, the calls between them and the names a person would use for them come from the code around the diff, and a diagram without those is a picture of a patch rather than of a system.

What command generates a diagram from a diff?

With the PR Lens CLI it is two commands: `npx @coldtea/pr-lens-cli analyze --base origin/main` writes a graph document from the diff using your own model key, and `npx @coldtea/pr-lens-cli render .pr-lens/graph.json` draws that document as light and dark SVGs. Only analyze talks to a model.

Do I need an API key to draw a diagram of my changes?

Not if a coding agent is already open. The PR Lens agent skill has the agent write the diagram document itself using whatever model it already runs, then validates that document against the schema. You only need a provider key for the CLI's analyze step, which is the path that asks a model on your behalf.

Why does my generated diagram use the wrong names for things?

Because the names came from file paths and a model's reading of them. Fix it in `.github/pr-lens.yml` with a rename rule rather than by editing the generated SVG. The file is an overlay applied at draw time, so the correction keeps holding as the code moves and the diagram is redrawn.

Sources

Keep reading

Turn it on

Install the App and every pull request in the repositories you pick gets drawn. Or hand the prompt to the agent you already have open.

Add to GitHub