GraphCodeMap
A queryable graph of symbols, calls and dataflow, so AI agents can understand a large repository without re-reading file after file.
3 min read
Stack
- Python
- tree-sitter
- SQLite
- MCP
- LSP
The problem
A coding agent in a large repository spends most of its context budget re-reading files to answer structural questions. "Who calls this function?" and "what breaks if I change this signature?" aren't text-search questions — they're graph questions.
GraphCodeMap indexes the repository with tree-sitter into a SQLite-backed graph, and exposes it through three interfaces: a Python library, a CLI and an MCP server.
The principle holding it together
The code is the source of truth; the graph is a derived cache.
A code index is only useful while it's correct, and the most common way to fail is aging silently. Here every indexed fact carries the content hash of its source file, and every query verifies freshness before answering — if the hash changed, the file gets reindexed right there, mid-question.
Freshness works in three layers: watchers keep the index hot during a session, a boot scan catches whatever changed while the process was down, and hash verification closes the rest.
Honest about limits
Static analysis gets things wrong. The choice here was to declare the error rather than hide it: every call edge carries an explicit confidence.
| Confidence | What it means |
|---|---|
certain | Semantically verified |
inferred | Name-based heuristic |
possible | Ambiguous |
The doctor command reports the index's state: what parsed, the confidence split with its
%certain metric, which L1 resolvers are active, and what's stale.
The layers
L0 — structural. tree-sitter extracts symbols, imports and call edges, through language-specific extractors or generic heuristics.
L1 — semantic. A pluggable resolver layer promotes edges to certain using language
servers: jedi for Python, tsserver for TypeScript and JavaScript, and generic LSP clients
for the rest — gopls, rust-analyzer, jdtls.
L3 — AI. LLM-generated summaries for symbols, modules and domains, cached to control cost, with token usage reported per generation.
Coverage
18 languages with dedicated extractors, including Python, TypeScript/TSX, JavaScript,
Rust, Go, Java, Kotlin, C#, C, C++, PHP, Ruby, Swift, Scala and Clojure. HTML and CSS/SCSS
are covered too, with cross-language edges — a className in TSX linked to its stylesheet
definition.
A generic tier covers a couple dozen more: Zig, PowerShell, Elixir, Dart, Vue, Svelte, Astro, SQL, Bash and others.
Dataflow and taint analysis — untrusted input reaching a dangerous sink — work across all 18 dedicated languages.
Using it
pip install graphcodemap
pip install "graphcodemap[l1]" # semantic refinement
pip install "graphcodemap[mcp]" # MCP servercodegraph index .
codegraph overview # PageRank-ranked repository map
codegraph callers auth.TokenService.validate # who calls it
codegraph impact auth.TokenService.validate # what breaks if I change it
codegraph taint --entry handle_request # untrusted data flowAs an MCP server, it goes into the agent's .mcp.json:
{ "mcpServers": { "codegraph": { "command": "graphcodemap-mcp", "args": ["--root", "."] } } }What it isn't
The graph complements grep, it doesn't replace it. To find where a string appears, grep is faster and cheaper. The graph earns its cost on structural questions.
Taint analysis is may-taint: it over-approximates, flagging paths that may never happen at runtime. It's flow-insensitive, though already field-sensitive for Python and JS/TS. And dynamic or reflective calls escape any static analysis — answers say so explicitly instead of faking certainty.
On performance: tested across 100k+ files, with a 324 MB memory peak and indexing in roughly 8 minutes on well-structured code.
Current state
Alpha, v0.1.0. The planned roadmap is implemented end to end, with around 165 regression tests — but it still lacks real-world battle-testing.
One detail I insist on keeping in the README: the 15-task SWE-bench-Lite pilot found the target file in 93% of cases against an 80% baseline, and that margin falls within the noise. It's directional, not proof.