The foundation
Why graph beats RAG
Most AI coding tools lean on vector search (Copilot, Cursor, Codeium). But source code is a deterministic graph, not unstructured text, so chunking and embedding it can lose the structure that matters most.
| Dimension | Vector RAG (Copilot, Cursor, Codeium) | Travsr graph (BFS → PPR → PCST) |
|---|---|---|
| Structural accuracy | Approximate: cosine similarity tends to surface similar chunks, which are not always the related code | Exact: edges are real call graph, type graph, and import graph edges |
| Hallucination risk | Higher: the model can fill gaps from training data when chunks don't fully cover the call site | Only real edges: the graph is built from relationships found in your code, not inferred |
| Staleness | Scheduled: embeddings are often rebuilt on a batch schedule, so they can lag behind HEAD | Incremental: a git post-commit hook reindexes changed files on every commit |
| Token cost | Higher: top-k chunks often include extra context to compensate for imprecision | Fewer tokens: PCST aims to return the smallest subgraph that answers the query |
| Cross-file reasoning | Indirect: depends on whether related files land in the same top-k results | Native: graph edges cross file and package boundaries by design |
| Symbol identity | Limited: embeddings have no explicit notion of a symbol's identity | Stable: Kythe VNames identify a symbol consistently across re-indexing, repos, and languages |
| Local-first | Often not: many setups send code to an embedding API and store vectors in a cloud DB | Yes: graph lives on your machine; cloud opt-in only |
See the difference
One question, two answers
“Who calls PaymentService.charge?” - checkout.ts: a local charge() helper that just reads similar
- billing.test.ts: a test whose text mentions “charge”
- docs/payments.md: prose about charging cards
Similar text, not actual callers. The real call site in another package never made the top-k cut.
- api/orders.ts → OrderController.submit
- jobs/retry.ts → RetryWorker.run
- billing/refund.ts → Refund.reverse
Every real call site across every package, walked from ref/call edges. Nothing invented.
How the graph is built
Tree-sitter: structural AST
Every file is parsed into an AST without a compilation step. Tree-sitter grammars produce import edges, class/function declarations, and call expressions as graph nodes and edges.
LSIF & SCIP: semantic edges
Language indexers resolve precise type references, call targets, and
data-flow edges that Tree-sitter alone can't see: rust-analyzer
for Rust, an LSIF emitter for TypeScript/JavaScript, and scip-*
indexers for Python, Go, Java, and more, added on demand with
travsr lang install.
Kythe VNames: stable identity
Every node gets a globally unique Kythe VName, so the same symbol keeps a consistent identity across re-indexing, repositories, and languages. Cross-repo edges are first-class: your app can reference your library by VName.
BFS → PPR → PCST retrieval
Queries are answered by graph traversal, not vector similarity. BFS finds direct neighbours. Personalized PageRank scores importance. Prize-Collecting Steiner Tree extracts the minimum connected subgraph that fits within a token budget.
Twelve tools, not a prompt
The graph is exposed over the Model Context Protocol, so any MCP client (Claude, Copilot, Cursor) traverses it directly over stdio or SSE. No re-prompting, no guessing: the client reads the graph directly.
get_dependenciesget_callersget_blast_radiussearch_symbolget_repo_mapget_execution_pathget_contextget_graph_statsget_graph_jsonget_snippetsget_lang_statusrepo_languages
Always fresh, by construction
A git post-commit hook re-indexes only what changed via a
SHA-256 delta. There's no nightly embedding job to drift behind HEAD, so
the context an agent reads aims to stay in step with the code you just
wrote.