Review code changes as a directed graph, not a file list.
Traditional code review tools show diffs file-by-file in alphabetical order. This forces reviewers to mentally reconstruct the code flow — you might review utils.ts before handler.ts even though the handler is the entry point. PR Flow Graph visualizes PR changes as an interactive directed graph starting from entry points (functions with no upstream callers in the diff) and lets you navigate downstream through the call chain.
Works for both human reviewers and AI agents.
GitHub PR URL
│
▼
Fetch files & contents (GitHub API)
│
▼
Parse ASTs with tree-sitter (WASM)
│
▼
3-pass graph construction
Pass 1: Extract function/class definitions
Pass 2: Resolve imports
Pass 3: Match call sites → build edges
│
▼
Filter to changed functions (diff overlap)
│
▼
Topological sort → review order + entry points
│
▼
Dagre layout (left-to-right DAG)
│
▼
Interactive React Flow visualization
Graph visualization
- Functions grouped by file, expandable/collapsible
- Color-coded by change type: green (added), red (deleted), yellow (modified)
- Animated edges for calls, dashed for imports
- Zoom controls, pan, and interactive minimap
- "Connected only" toggle — select a file, filter to its downstream subgraph
Code review
- Click any function to see its scoped diff (side-by-side)
- Mark functions as reviewed, track progress
- Review order follows the dependency graph, not alphabetical
Keyboard navigation
| Key | Action |
|---|---|
j / ↓ |
Next function in review order |
k / ↑ |
Previous function |
r |
Mark current as reviewed |
n |
Jump to next unreviewed |
Esc |
Deselect / close diff panel |
Agent API — structured endpoints for AI-powered review (no layout data, just semantics):
GET /api/agent/summary— PR shape, risk profile, review strategyGET /api/agent/review-plan— ordered steps, clusters, dependency chains
Supported languages: TypeScript, JavaScript, Python
- Node.js 18+
- A GitHub personal access token with
public_reposcope (orrepofor private repos)
git clone https://github.com/montecarlodata/prflow.git
cd prflow
npm install
# Set your GitHub token
echo "GITHUB_TOKEN=ghp_your_token_here" > .env.local
npm run devOpen http://localhost:3000, paste a PR URL, and explore the graph.
npm test # single run
npm run test:watch # watch modeThese endpoints return semantic review data optimized for LLM consumption — no pixel positions, just what to review, in what order, and why.
Both accept either explicit params or a full PR URL:
GET /api/agent/summary?url=https://github.com/owner/repo/pull/123
GET /api/agent/review-plan?url=https://github.com/owner/repo/pull/123&verbosity=standard
Lightweight PR shape for triage.
{
"pr": { "owner": "acme", "repo": "api", "number": 42, "headSha": "abc123" },
"shape": {
"totalNodes": 12,
"totalEdges": 8,
"independentFlows": 2,
"entryPoints": ["handleRequest", "migrateSchema"],
"languages": { "typescript": 10, "python": 2 }
},
"hubs": [
{ "name": "DatabaseClient", "file": "src/db/client.ts", "connections": 6 }
],
"riskProfile": { "high": 3, "medium": 4, "low": 5 },
"strategy": "2 independent flows. `handleRequest` flow (8 fns, 120 lines). `migrateSchema` flow (4 fns, 45 lines). Hub: `DatabaseClient` has 6 connections — review it early."
}Step-by-step review plan with dependency chains.
Verbosity levels:
| Level | Includes | Target tokens (20 nodes) |
|---|---|---|
minimal |
steps with order, name, file, lines, role, risk | <500 |
standard |
+ calledBy/calls, riskReasons, clusters, dependencies | <2000 |
full |
+ reviewHint, clusterId | ~3000 |
{
"pr": { "owner": "acme", "repo": "api", "number": 42, "headSha": "abc123", "baseSha": "def456" },
"stats": {
"totalSteps": 12,
"totalAdditions": 145,
"totalDeletions": 32,
"independentFlows": 2,
"filesChanged": 5
},
"steps": [
{
"order": 1,
"nodeId": "src/handler.ts::handleRequest",
"name": "handleRequest",
"file": "src/handler.ts",
"lines": [15, 42],
"type": "function",
"changeType": "modified",
"additions": 20,
"deletions": 5,
"role": "entry_point",
"calledBy": [],
"calls": ["src/handler.ts::validate", "src/db/client.ts::query"],
"risk": "high",
"riskReasons": ["entry_point", "large_diff"]
}
],
"clusters": [
{
"id": 0,
"label": "handler.ts",
"nodeIds": ["src/handler.ts::handleRequest", "src/handler.ts::validate"],
"reason": "2 related functions in handler.ts",
"suggestedReviewOrder": ["src/handler.ts::handleRequest", "src/handler.ts::validate"]
}
],
"dependencies": [
{
"from": "src/handler.ts::handleRequest",
"to": "src/handler.ts::validate",
"reason": "Review `handleRequest` before `validate` — `handleRequest` calls `validate`."
}
]
}1. GET /api/agent/summary?url=<PR> → Quick triage: size, risk, flows
2. GET /api/agent/review-plan?url=<PR> → Ordered steps + dependencies
3. For each step, read the file at the given line range
4. Follow topological order — review callees before callers
5. Batch comments by cluster
src/
├── app/
│ ├── page.tsx # Landing page — PR URL input
│ ├── [owner]/[repo]/pull/[number]/page.tsx # PR review page
│ └── api/
│ ├── graph/route.ts # GET /api/graph → LayoutGraph JSON
│ ├── graph/contents/route.ts # GET /api/graph/contents → file diffs
│ └── agent/
│ ├── summary/route.ts # GET /api/agent/summary
│ └── review-plan/route.ts # GET /api/agent/review-plan
├── components/
│ ├── FlowGraph.tsx # React Flow graph with file grouping, zoom, minimap
│ ├── FileNode.tsx # Expandable file container node
│ ├── FunctionNode.tsx # Individual function node
│ ├── DiffPanel.tsx # Side-by-side diff viewer (scoped to function)
│ └── ReviewProgress.tsx # Floating progress bar
├── hooks/
│ ├── useGraph.ts # Fetch + cache graph data
│ └── useReviewState.ts # localStorage review progress
└── lib/
├── github/
│ ├── client.ts # Octokit wrapper (GITHUB_TOKEN)
│ ├── pr.ts # Fetch PR files + contents (p-limit concurrency)
│ └── types.ts # PRFile, FileContent, PRContext
├── parser/
│ ├── engine.ts # Tree-sitter init, WASM loading, language detection
│ ├── types.ts # SymbolDefinition, CallSite, ImportStatement
│ └── languages/
│ ├── typescript.ts # TS/JS adapter — definitions, calls, imports
│ └── python.ts # Python adapter
├── graph/
│ ├── builder.ts # 3-pass graph construction + diff filtering
│ ├── topological.ts # Entry points + Kahn's algorithm
│ ├── layout.ts # Dagre left-to-right layout
│ ├── cache.ts # Shared in-memory LRU cache
│ └── types.ts # GraphNode, GraphEdge, PRGraph, LayoutGraph
└── agent/
├── types.ts # ReviewPlan, ReviewStep, PRSummary
├── parse-pr-url.ts # GitHub PR URL parser
├── graph-analysis.ts # Adjacency, risk, components, strategy
├── review-plan.ts # buildReviewPlan(graph, verbosity)
└── summary.ts # buildPRSummary(graph)
| Decision | Choice | Why |
|---|---|---|
| AST parsing | web-tree-sitter (WASM) | Works in Node.js + Turbopack without native bindings |
| Graph layout | Dagre (LR) | Purpose-built for DAGs, deterministic left-to-right layout |
| Visualization | React Flow | DOM-based custom nodes, built-in pan/zoom/minimap |
| Diff rendering | react-diff-viewer-continued | Scoped to function line range, syntax highlighting |
| Import resolution | Syntactic (relative only) | Good enough for most PRs; avoids needing full project context |
| Caching | In-memory LRU keyed by headSha | Same PR + same commit = same graph, shared between visual and agent APIs |
MIT