How it works

From source to graph, without guessing.

Lattice reads each Python file once, works out what every call points at, measures every symbol, and stores the result. Questions are answered from the stored graph, so they are fast and the source is never re-parsed.

The pipeline

Four stages, one direction.

  1. Parse

    The standard library's ast parser reads each file on its own and emits symbols (modules, classes, functions, methods, constants) and call stubs. No file is written, and no marker is left in the repository.

  2. Resolve

    A symbol table over the whole repository turns each call stub into an edge, cheapest rule first, and tags it with how it was resolved and how confident that is.

  3. Measure

    Topological depth from the entry points, PageRank, and in and out degree are computed once and stamped on every symbol.

  4. Serve

    MCP tools, the REST API, the command line, and the visualizer read the stored graph in PostgreSQL. A repository whose files have not changed is skipped on the next index.

Resolution

Types come from your annotations.

Most calls look like x.foo(), and you must know what x is to know what foo is. Many tools infer types and get it wrong. Lattice reads x's declared type from the source instead, which is why fully annotated code resolves best. Every edge records which rule produced it.

Resolution tags on call edges and what each means
TagWhat it meansConfidence
scopeA bare call resolved in the enclosing lexical scope.1.0
importResolved through an explicit import.1.0
selfself.foo() found on the enclosing class or one of its base classes.1.0
annotationThe receiver's type was written down: a parameter, a class attribute, or an explicit annotation.1.0
inferredA local assigned from a constructor call or a return-annotated call that itself resolved exactly. Tagged apart so a derived type is never mistaken for a written one.1.0
heuristicNothing else resolved the call, and the method name is unique in the repository.0.5
externalA standard library or third-party call. No node exists for it, and that is a final answer, not a failure.n/a
unresolvedNot resolvable with confidence. The raw call text is kept, and no edge is invented.none

The governing rule: never fabricate an edge. A confidently wrong call graph is worse than an incomplete one, because an agent acts on it.

Metrics

Depth, centrality, and blast radius.

  • Depth. A breadth-first walk from every entry point. Entry points are 0; foundations are deepest. Unreachable code gets no depth, which marks it as dormant.
  • PageRank. Power iteration over the call graph in plain Python. It sizes the dots and ranks search results.
  • Fan-in and fan-out. Counted at index time, so coupling is a lookup.
  • Blast radius. A recursive query walks callers backward and groups them by hop.
Lattice's blast-radius mode on a shared helper, with every dependent colored by hop distance.Lattice's blast-radius mode on a shared helper, with every dependent colored by hop distance.
Blast radius: a recursive query, drawn.
Limits

What it does not do.

  • It is not complete. Dynamic calls, untyped receivers, and code built from conditionals and loops can stay unresolved. Resolution quality follows annotation coverage.
  • It does not read bodies. It stores structure, not source text, so answers cite a location instead of quoting code.
  • It does not change anything. Indexing is read-only, and the visualizer and tools only read.
  • It is not a linter, a test runner, or a reviewer. It answers what a symbol is, what calls it, what it calls, and what breaks if it changes.