# Code graph
> Index every repo in the hall into one local graph, and let agents ask it for source, callers and blast radius instead of grepping and reading file by file.
Source: https://ivar.run/docs/guide/graph

import { Callout } from 'fumadocs-ui/components/callout';

`ivar graph` builds a code graph of every repo in the hall and keeps it on disk
at `.ivar/memory.db`, a local SQLite file. Agents query it through an MCP server
or the CLI. Nothing leaves the machine.

## What goes in the graph

Files are parsed with tree-sitter. Rust, TypeScript and JavaScript sources
yield the detailed structure:

- **Symbols**: functions, methods, classes, structs, traits and their
  signatures.
- **Calls, imports and type references** between them.
- **HTTP routes** declared by a server.
- **Cross-repo edges**: a package imported from another repo in the hall, a CLI
  invoked by name, an HTTP client request matched to the route that serves it.

The cross-repo edges are what a per-repo index cannot give you. A change to an
API route shows the web client that calls it, even though the two live in
different repositories.

## Build the index

```sh
ivar graph index
```

The first run indexes every repo declared in `ivar.json`. Extraction runs in
parallel. Later runs are incremental: `ivar` diffs each repo against the commit
it last indexed and checks file size and modification time, so only changed
files are parsed again. On a hall of about 29,000 files the cold index took 77 s
and a rerun with no changes took 0.17 s.

Index one repo, or force a rebuild:

```sh
ivar graph index --repo api
ivar graph index --full
```

`ivar graph stats` prints how many repos, files, symbols and edges the database
holds.

## Feature sessions see the feature's code

Several feature sessions often run at the same time, each on its own branch. One
index per checkout would mean indexing the same repo many times, so the graph is
split in two:

- A **base index** of each repo's default branch, shared by every session.
- A **feature layer** per feature, holding only the files that feature changed,
  committed or not.

A query from a feature session reads the layer first and falls back to the base
for everything else, so the agent sees its own edits and not another feature's.
A discovery session has no feature bound and reads the base.

While an agent has `ivar graph mcp` running, one of those servers is the
**watcher leader**. It watches every default-branch worktree and every live
feature session's worktree, and reindexes a repo or layer about 150 ms after
its files or branch change. Commits, pulls and edits reach the graph without
`ivar sync` or `ivar graph index`.

A query from a feature session waits, at most 2 seconds, until the leader has
indexed its pending edits, then answers without running git. When no leader is
running, or the wait runs out, `ivar` checks the layer against the worktree
itself and reindexes what changed, as before. Base-view queries never wait.

<Callout type="warn" title="Stale answers fail loudly">
  If the layer cannot be refreshed, the MCP call returns an error. It does not
  fall back to answering from the base, because code that no longer matches the
  worktree is worse than no answer. This also applies when the watcher's wait
  runs out and the direct check fails.
</Callout>

Deleting or cleaning up a feature drops its layer. To remove one by hand:

```sh
ivar graph clean --feature billing-currency
```

## Use it from an agent

### Over MCP

`ivar graph index` registers the graph server in `ivar.json` as a
[local MCP server](/docs/guide/mcp) the first time it runs, unless a server
running `ivar graph mcp` is already declared:

```json title="ivar.json"
{
  "mcp": [
    {
      "name": "graph",
      "type": "local",
      "command": "ivar",
      "args": ["graph", "mcp"]
    }
  ]
}
```

Pass `--no-mcp` to skip that. If a server named `graph` already runs something
else, the index leaves `ivar.json` alone and warns; declare the entry above under
another name by hand. Then write the provider configs:

```sh
ivar sync
```

By default the server advertises one tool, `graph_explore`. Start the server
with `--tools all` to advertise the rest; the
[reference](/docs/reference/graph#mcp-tools) lists them.

`graph_explore` takes symbol names, file or directory paths, or a short intent,
several at once. One answer carries:

- the flow between the symbols you named,
- the line-numbered source of the most relevant files, within a size budget,
- the blast radius of each definition: its callers and type uses,
- a ready `Next:` call with `paths` for the files that did not fit.

Passing `paths` returns up to 12 whole files in one call, which is cheaper than
reading them one at a time. The tool description tells the agent to treat
returned source as already read.

### From the CLI

Some providers do not pass MCP servers to subagents; omp is one. A subagent can
still run the CLI:

```sh
ivar graph explore renderInvoice
ivar graph callers formatAmount --cross-repo
ivar graph affected --stdin
```

Every subcommand takes `--json`, and `--compact` for pipe-delimited records with
a schema header when output size matters.

### In a browser

```sh
ivar graph view --symbol renderInvoice
```

`view` serves an interactive viewer on a loopback port. `ivar graph viz` writes
a standalone HTML file instead.

## Keep it fresh

- A running `ivar graph mcp` keeps the base index and feature layers current
  on its own. The first server to start leads; if it exits, the next tool call
  from another server takes over and catches up through git.
- `ivar sync` refreshes the base index of an existing graph along with the rest
  of the hall. It does not create a graph that was never built.
- `ivar doctor` reports repos whose indexed commit is behind the worktree HEAD,
  a database whose schema version does not match the binary, and watcher scopes
  that failed to reindex or were left behind with no leader running. It also
  shows the leader's pid. Each finding names the command that fixes it.

## Limits

- Calls, imports and type references are extracted for Rust, TypeScript and
  JavaScript. Code in other languages does not get that structure.
- Edges come from static extraction. Dynamic dispatch, reflection and computed
  URLs can hide a caller; each edge carries a confidence score, and `callers`
  takes `--min-confidence` to filter.
- The database grows with the hall. The 29,000-file hall above produced a
  370 MB index with 144,000 symbols and 1.9 million edges.
- Treat graph answers as evidence, not proof. When a query comes back empty for
  something you expect to exist, search the source.
