Skip to main content
Glama
README.md
# RepoGraph

A local-first code intelligence graph for Python repos, exposed over MCP so agents like
Claude Code can query it instead of re-reading the whole codebase every session.

Coding agents don't remember your repo's structure between sessions. Ask one to change a
function and it either greps around or reads a pile of files just to figure out what
calls what. RepoGraph parses your repo once with tree-sitter, builds a typed graph of
functions/classes/modules and their calls/imports/inherits/tests relationships, and
keeps it updated incrementally from git diffs. Agents then query it directly: "what
calls this?", "what breaks if I change this?" — without touching the rest of the repo.

![repograph-build running against the fixture repo](docs/images/build.jpg)

## What it gives you

Three MCP tools:

- **`get_subgraph(entity, depth)`** — the neighborhood around a function/class/module
- **`find_callers(fn)`** — who calls this, directly
- **`find_impact(fn)`** — the full blast radius: everyone who transitively calls or tests it

![find_impact called from Claude Code](docs/images/find_impact.jpg)

Plus two resources (`repograph://schema`, `repograph://stats`), a reviewer agent that
pulls just the blast radius of a diff before asking Claude to review it, and a
graph-maintainer agent that flags orphaned code and modules with too many dependents.

## How it's built

```
git repo
  │  tree-sitter parse (full) / git diff (incremental)
  ▼
graph builder — nodes: module/class/function, edges: imports/inherits/calls/tests
  ▼
SQLite + NetworkX (local, no server, easy to inspect)
  ▼
FastMCP server — get_subgraph / find_callers / find_impact
  │                                        │
  ▼                                        ▼
reviewer agent                    graph-maintainer agent
```

Python 3.11+, single language for now (the parser/graph-builder split is where a second
tree-sitter grammar would plug in). Full stack: tree-sitter, NetworkX, SQLite, FastMCP,
GitPython, the Claude API for the two agents, pytest for everything else.

## Setup

```bash
python3.11 -m venv .venv
.venv/bin/pip install -e ".[dev]"
```

Build a graph and run the server:

```bash
.venv/bin/repograph-build /path/to/some/repo --db repograph.db
.venv/bin/repograph-mcp repograph.db
```

`--incremental` re-runs against the last indexed commit instead of parsing everything again.

## Adding it to Claude Code

```bash
claude mcp add repograph -- /absolute/path/to/repograph/.venv/bin/repograph-mcp /absolute/path/to/repograph.db
```

or drop this into a project's `.mcp.json` (see `.mcp.json.example`):

```json
{
  "mcpServers": {
    "repograph": {
      "command": "/absolute/path/to/repograph/.venv/bin/repograph-mcp",
      "args": ["/absolute/path/to/repograph.db"]
    }
  }
}
```

Then just ask it to check `find_impact` before touching something.

## Does it actually work? (the evaluation harnesses)

Most "code graph" tools ship a headline number with nothing backing it up. Every claim
here is a test, not a paragraph:

| Harness | File | Checks |
|---|---|---|
| Context reduction | `tests/test_context_reduction.py` | subgraph context is smaller than full-file context, with a real table below |
| Graph correctness | `tests/test_graph_correctness.py` | exact match on hand-labeled edges, plus precision/recall against an independent `ast`-based extractor |
| Staleness/drift | `tests/test_staleness_drift.py` | 50 simulated commits — incremental updates always converge to a full rebuild |
| MCP context budget | `tests/test_mcp_context_budget.py` | tool schemas stay under a fixed token budget |
| Impact-query accuracy | `tests/test_impact_query.py` | `find_impact` precision/recall against a hand-labeled blast-radius set |

```bash
.venv/bin/pytest -q
```

### Context reduction on the bundled fixture repo

<!-- BENCHMARK:START -->
| Target function | Full-file tokens (est.) | Subgraph tokens (est.) | Reduction |
|---|---:|---:|---:|
| `main.build_shapes` | 386 | 311 | 19.4% |
| `main.main` | 386 | 273 | 29.3% |
| `shapes.base.Shape.area` | 386 | 57 | 85.2% |
| `shapes.base.Shape.describe` | 386 | 57 | 85.2% |
| `shapes.circle.Circle.__init__` | 386 | 28 | 92.7% |
| `shapes.circle.Circle.area` | 386 | 27 | 93.0% |
| `shapes.rectangle.Rectangle.__init__` | 386 | 39 | 89.9% |
| `shapes.rectangle.Rectangle.area` | 386 | 29 | 92.5% |
| `shapes.utils.compute_total_area` | 386 | 296 | 23.3% |
| `shapes.utils.summarize` | 386 | 261 | 32.4% |
<!-- BENCHMARK:END -->

This is a ~10-function fixture repo, not a real production codebase, so treat the exact
percentages as illustrative. Point the benchmark at any real repo to regenerate it:

```bash
.venv/bin/python scripts/benchmark.py --repo /path/to/some/repo --out README.md
```

CI does this automatically on every push (`.github/workflows/ci.yml`).

## Where it falls short

Symbol resolution is a static heuristic, not real type inference, and it's tuned to
favor precision over recall:

- **Dynamic dispatch isn't resolved.** `s.area()` where `s` could be any subclass
  produces no edge rather than a guess. That's deliberate — see the fixture's
  `compute_total_area`, which is the one call the correctness harness expects to miss.
- **`self.method()` resolves to whatever's defined on the enclosing class**, not to
  whichever override would actually run.
- **Decorator arguments aren't parsed for calls** — `@app.route("/x")` won't create an
  edge to `app.route`.
- **Only same-repo imports get nodes.** Calls into stdlib/third-party code are correctly
  left unresolved instead of invented.

The graph-maintainer's orphan detection inherits this: a method whose only real caller
is dynamic dispatch will look "orphaned" even though it isn't. It's documented in
`tests/test_maintainer_agent.py`, not hidden.

## Layout

```
src/repograph/
  parser.py            tree-sitter extraction, one file at a time
  graph_builder.py      cross-file symbol resolution -> graph
  store.py               SQLite persistence
  git_integration.py     incremental updates from git diffs
  queries.py              get_subgraph / find_callers / find_impact
  benchmark.py            context-reduction measurement
  mcp_server.py           FastMCP server
  cli.py                  repograph-build
  agents/reviewer.py, maintainer.py
tests/
  fixtures/               hand-crafted sample repo + golden JSON
  ast_reference.py        independent ast-based ground truth
  test_*.py               one file per harness, plus MCP/agent/CLI tests
```