Chapter 1

Code as a Graph

Before any clever algorithm, travsr needs one thing: a clean way to say “this exact piece of code” and “this connects to that.” Three ideas do all the work - nodes, edges, and a globally-unique address.

1. A node = one named thing in your code

A function, a method, a class, a file, an import. Each becomes a single dot (a “node”) in the graph. In our payment example, validateCard() is a node, PaymentService.charge is a node, and so on.

Explain like I’m 5 A node is just one “thing” in your code drawn as a dot, one function, one file, one class. Everything travsr knows is built out of these dots.
Analogy

A node is a house on a street. It has an address, and roads lead in and out of it.

2. The address: a “VName”

Every node gets a five-part address called a VName (a “virtual name”, borrowed from Google’s Kythe project). It’s globally unique, even across different repos and languages, so two tools always agree on what they’re pointing at.

Explain like I’m 5 Every function gets its own full home address that no other function shares, so travsr can never mix two functions up, even if they have the same name in different files.

The five parts

PartMeansExample
corpuswhich projectgithub.com/acme/shop
rootbuild root / branch""
pathfilesrc/payment.ts
languagelanguagetypescript
signaturethe symbolmethod:PaymentService.charge

Turned into a short ID

That five-part address is hashed (with BLAKE3) into a single 64-bit number called a NodeId. Same code → same ID, every time. That’s how travsr knows a function is “the same one” after you edit a different part of the file.

VName {
  corpus:    "github.com/acme/shop",
  path:      "src/payment.ts",
  language:  "typescript",
  signature: "method:PaymentService.charge",
}
        │  BLAKE3 hash (first 8 bytes)
        ▼
NodeId(0x9f3a…)   ← the primary key
crates/travsr-core/src/lib.rs · VName::id()
Analogy

The VName is the full postal address. The NodeId is the barcode the post office prints from it, short, unique, and machine-fast to sort by.

Why content-addressing matters

Because the ID comes from what the code is (not a random counter), travsr can re-index just the files that changed and the IDs still line up with everything else. There’s even a version byte baked into the hash (SIGNATURE_FORMAT_VERSION = 2), bump it, and every old database is intentionally invalidated so nothing stale is ever served.

3. Edges: the roads between nodes (and their importance)

An edge is a directed link: “A calls B”, “A imports B”, “A overrides B”. Crucially, not all roads are equal. A direct function call is a strong, meaningful link; a loose import is weaker. travsr bakes this into a number, the PPR weight - that the algorithms use later to decide what matters.

Explain like I’m 5 Arrows are roads between functions, and some roads are bigger than others. A direct call is a highway (counts a lot); a loose import is a back-alley (counts a little).
Edge kindWeightPlain meaning
RefCall1.00A directly calls B, the strongest signal
FFICall0.85A calls B across languages (e.g. TS → Rust)
DefinesBinding0.70A contains B (file contains function)
Exports0.60A module exposes B
Depends / ResolvesTo0.50imports / import-resolves-to-file
RefImports / IsImplementation0.40named import ref / implements interface
Configures0.35a config file (JSON/YAML/TOML/XML) configures a symbol
Overrides0.30method overrides a parent method
ExternalDependency0.30config points at an external package, weakest
crates/travsr-core/src/lib.rs · EdgeKind::ppr_weight()

data as nodes too travsr doesn't only parse code. Config and data files, JSON, YAML, TOML, and XML, are parsed into Phase A nodes, and the two edge kinds above (Configures / ExternalDependency) wire them into the same graph, so "what reads this config key?" is a real graph query.

The same graph, now showing edge weights

Thicker, brighter arrows carry more weight. The number on each arrow is the exact weight from the code. These weights are the “road sizes” the PageRank walk follows in Chapter 3.

Where it all lives

The whole graph is stored in a plain SQLite database file at .travsr/graph.db right next to your code. Three core tables:

Explain like I’m 5 The whole map is saved in one file sitting next to your code, a tiny database with a list of all the dots, a list of all the arrows, and a note of which files it has already read.

nodes

id, corpus, path, language, signature, kind. One row per code thing.

edges

src, dst, kind. One row per relationship. Indexed both directions for fast “who calls me?”.

files

path, sha256, last_indexed_at. The hash is how travsr skips unchanged files (next chapter).

It uses SQLite in WAL mode (write-ahead logging) so reads and writes don’t block each other, the daemon can update the graph while the AI is querying it.

Next: how the graph gets built →