The foundation

Why graph beats text search

Most AI coding agents fall back on text: grep and ripgrep for exact matches, vector embeddings for fuzzy ones. But source code is a deterministic graph, not a bag of words, so pattern matching, exact or fuzzy, can only ever approximate the structure that actually connects your code.

Dimension Text search
(grep/ripgrep + vector RAG)
Travsr graph
(BFS → PPR → PCST)
Structural accuracy Approximate: regex matches surface text and embeddings match surface meaning, neither reads the call graph, type graph, or import graph underneath Exact: edges are real call graph, type graph, and import graph edges
Symbol identity Name-based: a search for charge hits every unrelated charge() in scope, and misses the real one the moment it's renamed Stable: Kythe VNames identify a symbol consistently across renames, re-indexing, repos, and languages
Multi-hop reasoning Manual: the agent chains searches by guessing the next name to look for, one hop at a time, and stalls at interfaces or dynamic dispatch Native: BFS and PPR walk real edges across files and packages in a single traversal, no guessing
Round-trip cost Higher: every dead-end search is its own tool call, and each one burns a turn re-reading files just to check relevance Fewer tokens: PCST returns the smallest connected subgraph that answers the query in one call
Hallucination risk Higher: when a search comes back empty or partial, the model fills the gap from training data instead of your actual codebase Only real edges: the graph is built from relationships found in your code, not inferred
Freshness Split: grep reads live files but stays blind to structure; embeddings carry structure but are often rebuilt on a batch schedule that lags behind HEAD Both: a git post-commit hook reindexes changed files on every commit, so structure stays fresh too
Local-first Mixed: grep stays on your machine, but embedding setups often send code to an API and store vectors in a cloud DB Yes: the graph lives on your machine

See the difference

One question, two answers

Agent asks “Who calls PaymentService.charge?”
Text search grep + embeddings
  • checkout.ts: an unrelated local charge() helper, name matches, symbol doesn't
  • billing.test.ts: a mock named charge that asserts nothing about real callers
  • docs/payments.md: prose that mentions “charge” in passing

Same string, not the same symbol. The real call site, reached only through PaymentGateway.pay()'s dispatch, never gets matched because the text doesn't match.

Travsr graph get_callers traversal
  • api/orders.tsOrderController.submit
  • jobs/retry.tsRetryWorker.run
  • billing/refund.tsRefund.reverse

Every real call site across every package, walked from ref/call edges. Nothing invented.

How the graph is built

01

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.

02

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.

03

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.

04

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.

05

Cross-encoder: honest confidence

For natural-language queries, a small local cross-encoder (ms-marco-MiniLM, ~45 MB, CPU-only) re-scores the candidates the graph already found and reports an honest confidence, or abstains, instead of guessing. It only ranks what the graph surfaced, it never decides which edges exist. The optional travsr-embed sidecar adds recall on top; it doesn't set relevance.

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. travsr init detects the tool you have installed and wires the server plus an always-on rules file automatically, no manual config.

  • get_dependencies
  • get_callers
  • get_blast_radius
  • search_symbol
  • get_repo_map
  • get_execution_path
  • get_context
  • get_graph_stats
  • get_graph_json
  • get_snippets
  • get_lang_status
  • repo_languages
Full tool reference →

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.

git commit SHA-256 delta graph updated

Ready to try it?