Dependency Graph β
The dependency graph is the data structure at the heart of Ctxo. Every MCP tool, whether it answers "what breaks if I change this?" or "who uses this?", is ultimately a query against this graph.
Shape of the graph β
- Nodes are symbols, one per declared identifier worth tracking.
- Edges are directed and typed. They record how one symbol reaches another.
The graph is maintained in memory by SymbolGraph and persisted per-file as JSON under .ctxo/index/ (one file per source file). SQLite under .ctxo/.cache/ is a rebuildable mirror.
Symbol kinds β
Nodes are symbols of kind: function | class | interface | method | variable | type. See the Symbol IDs reference for the full format and edge-case rules.
Edge kinds β
5 kinds: imports, calls, extends, implements, uses. See Edge kinds for direction, semantics, and per-parser emission rules.
Edge kinds drive blast radius confidence tiering.
Symbol IDs β
Every node has a deterministic, stable ID:
<relativeFile>::<name>::<kind>Examples:
packages/cli/src/core/graph/symbol-graph.ts::SymbolGraph::class
packages/cli/src/adapters/mcp/get-pr-impact.ts::handle::functionThis ID is what every MCP tool takes as input and what appears in every edge's from / to fields. Files use forward slashes regardless of OS.
See symbol IDs reference for edge-case rules (anonymous functions, overloaded methods, re-exports).
A tiny example β
file: src/db/user-repo.ts
class UserRepo ββββββββββββββββββ
method findById β
β extends
file: src/db/base-repo.ts βΌ
class BaseRepo
method findById
edges:
src/db/user-repo.ts::UserRepo::class
--extends--> src/db/base-repo.ts::BaseRepo::class
src/db/user-repo.ts::findById::method
--calls--> src/db/base-repo.ts::findById::methodTwo nodes, two edges, two different kinds. That is enough for find_importers, get_class_hierarchy, get_logic_slice, and get_blast_radius to all produce different but correct answers.
How the graph gets built β
- Plugins (
@ctxo/lang-typescript,@ctxo/lang-go,@ctxo/lang-csharp) parse source files and emit{ symbols, edges }per file. - The indexer writes each file's contribution to
.ctxo/index/<file>.json. - At query time,
SymbolGraph.addNode/addEdgeassemble the in-memory graph. Duplicate edges are deduplicated via an edge-key set. - Edge endpoints that do not resolve exactly fall back to a fuzzy lookup by
file::nameand a.js -> .tsextension swap so downstream queries still produce a connected graph even when module specifiers use compiled paths.
Related docs β
- Index schema see the committed
.ctxo/index/JSON. - Symbol IDs exact rules for naming edge cases.
- Edge kinds semantics and plugin authoring guidance.
search_symbolsfind a node by name or regex.find_importerstraverse reverse edges.
Implementation detail
Fuzzy edge resolution (file-and-name fallback, .js / .ts swap) lives in SymbolGraph.resolveNodeId. It is pragmatic; see the source for the current rules.