Skip to content

Query the symbol graph

The semantic layer keeps a live map of your code: every definition, every reference, the recorded call and test edges, and the type hierarchies that connect them. This guide shows you how to ask it questions — where a symbol lives, who calls it, and what depends on it — instead of reaching for grep.

Built and default-on — optional and additive

The tovio semantic queries are part of the optional, additive semantic layer, which is built and on by default — the commands below work today. The layer never sits on the critical path of a commit or sync, and every query is served offline from HEAD's index. The same graph is exposed to agents through the implemented symbol_context, symbol_relations, and change_impact MCP tools.

Why query instead of grep

grep matches text. The symbol graph matches meaning. A query knows the difference between a function named read and a comment that says "read," between the User type and a variable called user, and between a definition and the dozen places that call it. Three questions it answers cleanly:

  • Where is this defined? — jump straight to the definition, across files.
  • Who calls this? — the recorded caller list, the basis for "is this safe to change?"
  • What depends on this? — the transitive reach of a symbol, for planning a refactor.

Symbols are named by moniker

Every query except search takes a descriptor moniker — the symbol's qualified name in SCIP style: a function is verifyToken()., a type is Claims#, a method is Shop#Widget#greet()., a plain value is MAX_RETRIES.. When you don't know the exact spelling, start with search:

$ tovio semantic search verify
Symbols matching `verify`:
  verifyToken().    auth/token.ts:42    [prefix 708, 4 ref(s)]
  legacyVerify().   auth/legacy.ts:9    [word-boundary 502, 1 ref(s)]

Search ranks by match tier — exact, case-insensitive, prefix, word-boundary, substring, then fuzzy subsequence — with a bounded bonus for how often a symbol is referenced. The tiers are spaced far enough apart that the bonus reorders within a tier and never across one, so the ranking is an integer total order and two machines with the same index print the same list. --limit <n> widens the screenful (default 20, capped at 500).

Find where a symbol is defined

$ tovio semantic find-def 'verifyToken().'
Definition(s) of `verifyToken().`:
  in auth/token.ts

A moniker that resolves to several definitions lists each one. find-refs is the mirror image — every recorded reference to the symbol, by file.

List who calls a symbol

$ tovio semantic callers 'verifyToken().'
Caller(s) of `verifyToken().`:
  pruneExpired().
  refreshSession().
  requireAuth().

callers is a one-hop lookup over the recorded calls edges — it answers "who calls this," not "what would break." Two siblings use the same shape: tested-by lists the test functions that exercise a symbol, and types lists the supertypes it extends or satisfies (one hop upward only; "who implements this" needs a reverse index the graph does not yet carry).

Edges are recorded, not proven

Call and test edges are extracted syntactically. A call through a trait object, a call from inside a macro body, or a cross-module call to a differently-monikered target is invisible to the parsers and therefore absent. An empty result means no edge was recorded — never that a symbol is uncalled or untested.

Find what depends on a symbol

callers is one hop. impact is the transitive reach — everything that would feel a change, directly or through the chain — plus the tests that cover it:

$ tovio semantic impact 'verifyToken().' --depth 2
Impact of `verifyToken().` (2 hop(s)):
  requireAuth().      api/middleware/auth.ts   [1 hop(s)]
  refreshSession().   api/routes/session.ts    [1 hop(s)]
  sessionRouter().    api/routes/index.ts      [2 hop(s)]
Covering test(s):
  rejectsExpiredToken().  covers requireAuth().
  advisory: call edges are extracted syntactically — no recorded dependents is not proof of no impact

Use --depth to bound how far the walk goes (default 3). Omit the moniker entirely and impact seeds itself from what this change altered — HEAD's first parent versus HEAD — which is the question people actually have before landing: "what might my change break?"

Machine-readable output

Every query honors the standard --json contract — stable, decoration-free, the surface agents and CI read. The one-hop queries share one envelope: the moniker plus a sorted, deduplicated list under callers, tested_by, or supertypes.

$ tovio semantic callers 'verifyToken().' --json
{
  "moniker": "verifyToken().",
  "callers": ["pruneExpired().", "refreshSession().", "requireAuth()."]
}

find-def and find-refs return definitions / references as { "shard", "path" } pairs (the content address of the defining shard and the repository path, or null for a path you cannot read). impact returns seeds, affected (with distance in hops), tests, and truncated, and restates the advisory in the machine surface so a consumer can't read an empty affected as proof.

Agents query this surface directly

The Node/MCP edge exposes the same graph as structured tools — symbol_context for definitions and references, symbol_relations for the one-hop edges, and change_impact for the transitive walk — so an agent can pull exact symbol context instead of guessing from a text search. See the agents guide.

What the graph covers

You can ask for You get
search <text> Ranked definitions matching what you typed — the query for when you can't spell the moniker
find-def <moniker> The definition(s), with the defining file
find-refs <moniker> Every recorded reference, by file
callers <moniker> Direct callers — the one-hop calls set
tested-by <moniker> Tests that exercise the symbol — the one-hop tests set
types <moniker> Supertypes it extends or satisfies — one hop upward
impact [<moniker>] Transitive dependents and their covering tests, bounded by --depth

The graph indexes Rust, TypeScript/JavaScript, Python, and Go. A file in any other language simply isn't in the graph — queries skip it rather than failing, and you fall back to a text search for those files. Symbols under a policy-protected path are sealed to the file's recipients: a reader with the key sees them; anyone else, including a scope-limited agent token, sees only what they are cleared to read.

How it stays current

The graph updates incrementally on every commit — TOVIO re-indexes only the files that changed, writing one content-addressed symbol-shard per file and stitching them into the commit's symbol-xref, so a query always reflects your latest history without a full re-index. Both live as ordinary objects in your store, so they sync and re-hash on receipt like any other object.

Where to go next

Last reviewed September 9, 2026

Suggest an improvement to this page Not for security reports — see disclosure