Query The Code Graph

Ask an ingested repository who calls a symbol, what it calls, what breaks if it changes, how two nodes connect, what sits next to it, what a commit range changed, and why a symbol exists — from the CLI or from an agent over MCP.

Once a repository is ingested, eight read verbs answer questions about it: who calls this, what does it call, what breaks if I change it, how do these two things connect, what is next to it, what did a range of commits change, why does this exist, and what repository snapshot the graph currently holds. All eight are available from the CLI and to any agent connected over MCP.

Before You Start

  • A repository already ingested — Ingest a Repository covers that. Confirm the graph is current first by re-running dots repo ingest after the checkout moves, because a stale graph returns answers that no longer match the code.
  • Authority to read the graph (graph.read). Every built-in role from Viewer up carries it.

Naming A Symbol

Every verb takes a node reference. The most reliable form is a Code Symbol's human id, which you get by searching the Dotabase:

SH
dots search "resolveActor" --dotabase code-symbols

Search is a survey read: it answers at the summary disclosure level, so each hit is identity plus classification rather than a whole symbol record. That is all this step needs, because the verb below takes the id and nothing more. Pass --level full when you want to read the symbol rather than traverse from it.

The friendly <path>#<qualifiedName> form — src/auth/resolve.ts#resolveActor — is also accepted, and it is what the MCP tools advertise. A reference that matches nothing is a clear "symbol not found" error rather than an empty result, so an empty list always means "found it, nothing connects", never "typo".

Who Calls This

SH
dots graph callers SYM-412

Returns the queried symbol plus every symbol that calls it, each with its edge confidence. Cap the result with --limit:

SH
dots graph callers SYM-412 --limit 50

--limit takes a positive integer up to 500; a larger or non-integer value is rejected before any request goes out rather than being silently clamped.

What Does This Call

SH
dots graph callees SYM-412 --limit 50

The same shape in the forward direction, and the same --limit bound.

What Breaks If I Change This

SH
dots graph impact SYM-412

Impact is the transitive blast radius: every incoming dependent reachable from the symbol within a hop bound, each carrying its hop distance and confidence. Run it before changing a symbol other code depends on.

SH
dots graph impact SYM-412 --hops 4

--hops defaults to 2 and is capped at 5.

Two bounds apply to the result. Impact traverses calls and inherits edges only — it is a call-graph question, not a general reachability one, so an import relationship does not put a file in the blast radius. And it is bounded at 500 impacted nodes overall. A result at the cap is a sign the symbol is central enough that "what depends on this" is the wrong question; narrow the hops instead.

How Do These Two Connect

SH
dots graph trace SYM-412 SYM-980

Trace returns the shortest path between two nodes as ordered steps, or an empty path when nothing connects them. No route is not an error — an empty path is a real answer, and the command succeeds.

Trace is not limited to symbol-to-symbol. It reaches across kinds, so a path can run from a function to the file that defines it, to the commit that produced it, to a conversation that discussed it, to a plan that references it. Either endpoint can be a Code Symbol, a Repo File, a commit, or an artifact.

The hop budget defaults to the company's traversal setting — 6 — and --hops raises it per call, up to 12. --limit sets the node budget the walk may visit before it gives up (2500 by default, 10000 at most). An empty path carrying truncated: true is the one case where empty is not an answer: the budget ran out, so whether a route exists is undecided — retry with a larger --limit.

What Sits Next To This

SH
dots graph context SYM-412

Context returns the immediate neighborhood: every directly connected node, in both directions, across every edge kind rather than just calls. Each neighbor carries its direction, edge type, edge category, and confidence. --limit caps one page of neighbors — 500 both by default and at most — and a truncated: true response carries a nextCursor to replay with --cursor.

Run context first on a symbol you have never seen, before asking a narrower question about it.

What Did A Range Of Commits Change

SH
dots graph diff a1b2c3d 9f8e7d6

Diff takes two commit SHAs — full or a hex abbreviation of at least four characters — and reports what the range changed plus what depends on it. The range is base-exclusive and head-inclusive, so the base commit itself is not in it.

SH
dots graph diff a1b2c3d 9f8e7d6 --repo owner/repo --hops 4

--hops is the dependent walk out from the changed symbols, defaulting to 2 and capped at 5 like impact. --repo narrows SHA resolution to one repository — the Git Commits repository value — and is what you reach for when an abbreviation matches commits in more than one repo.

Three limits apply to what comes back:

  • Attribution is file-grained. Commit rows carry no hunks, so every symbol defined in a changed file is reported as changed. The response's precision field states what that costs and each symbol's attribution names how it was reached.
  • Retired symbols come back separately, attributed by timestamp alone.
  • graphSkew is non-null when the graph was not built from head — the range is being read against a graph built at a different commit, which is the signal that a re-ingest is due.

Diff reads the ingested commit rows, not your checkout. dots repo ingest ledgers the commits its shipment carried metadata for — up to a 1000-commit carry bound, and none at all from a checkout it could not read — so dots git ingest-commits <path> is the cold fill for the rest. With no commit rows at all, diff refuses with a typed error naming that command rather than returning an empty range, and a range wider than the server's 1000-commit walk cap is likewise refused with a typed error naming the cap.

Why Does This Exist

SH
dots graph why SYM-412

Why walks provenance rather than structure: out from the symbol to the file that defines it, and on to the commits, conversations, plans, rows, and findings that produced or reference it — one hop per edge, both endpoints named by human id.

SH
dots graph why SYM-412 --limit 20

--limit caps how many producing commits the walk carries forward, newest first — 5 by default, 50 at most.

Every response carries a narrative. A symbol that resolves but reaches nothing is a success, not an empty result: it comes back with a typed noProvenance reason and the remedy for it. truncated: true means a cap stopped a fan-out and the hops are a prefix, and unresolvedEndpoints counts incident edges whose far endpoint names no surviving row.

From An Agent Over MCP

The same graph is exposed to a connected agent as eight tools: callers_of, callees_of, impact, trace, context, diff, why, and detect_changes. The first seven mirror the identically named CLI verbs — the same node references, the same optional bounds, and the same caps — with diff's repository narrowing spelled repository rather than --repo. detect_changes maps to the hyphenated CLI command dots graph detect-changes.

One difference from the CLI affects how you write agent instructions:

  • detect_changes and dots graph detect-changes take no arguments and do not re-ingest anything. They report each ingested repository's name, root path, remote, default branch, and last ingested commit — enough for an agent to notice its graph is behind and say so. Refreshing it is a dots repo ingest run, which is a deliberate boundary: reading the graph never silently rewrites it.

Every one of these tools is gated on graph.read, and an agent's tool list is filtered to what its credential actually holds — so a credential without that atom is not offered the graph tools at all rather than being refused when it tries. Connect an Agent via MCP covers the connection itself, and MCP Tools is the full catalog.

When A Query Comes Back Empty Or Missing

  • "Symbol not found" means the reference did not resolve. Check the id with dots search --dotabase code-symbols, and check that the file's language is one the extractor handles — a Go or Ruby file is never ingested, so its symbols were never there to find.
  • An empty result means the node resolved and nothing connects it by the edges that verb traverses. For impact that is specifically calls and inherits; try context to see everything incident to the node instead. why is the exception — it never answers empty, returning a typed noProvenance reason and a remedy instead.
  • A result that looks out of date is a freshness question, not a query one. The repository row records the applied commit; if it is behind your checkout, re-run dots repo ingest — re-ingest is the single refresh path.
  • A retired file or symbol still resolves. Rows for deleted code are archived rather than erased, and they carry an archived stamp when they come back — so a node resolving does not by itself mean the code still exists.

The Bounds In One Place

VerbFlagDefaultMaximum
callers / callees--limitserver default500
impact--hops25
impact500 impacted nodes
trace--hops612
trace--limit2500 nodes10000 nodes
context--limit500500 neighbors
diff--hops25
diff1000 commits in range
why--limit5 commits50 commits

Repo Graph explains what produced the structure these verbs walk; Ingest a Repository is how you keep it current.

Query The Code Graph · This page in the graph

Connected Records18

Documented by
Mentions
DOT
References
Relevant to
18 connections.

Where To Go Next

Reading this as an agent?For Agents
Something wrong on this page?Dots On GitHub