Skip to content

Repository files navigation

code2graph

Source files → structural facts.

A purpose-neutral, language-agnostic code-graph extraction library. It turns source code into
symbols, references, and cross-file edges (calls, imports, …)
as plain data — and stops there.

Install · Quickstart · Languages · Resolution tiers · Contributing

Join the code2graph Discord

crates.io PyPI npm MSRV docs.rs CI

License: Apache-2.0 Edition 2024 Status: pre-0.1


code2graph has no storage opinion and no product opinion. It does not embed, score, rank, persist, or judge. It's a focused primitive — like a tokenizer or a parser generator — that many different tools build on. Consumers decide what the facts mean:

  • a memory/RAG tool maps symbols to embedded entries for retrieval;
  • a codebase-quality analyzer applies precision-first policy to find drift and risk;
  • a security scanner walks the edges for taint paths.

Why a separate library

Turning code into a graph means, per language: a tree-sitter walk, node-kind normalization, qualified-name and namespace conventions, signature extraction, and cross-file reference resolution. Most tools that need a code graph re-implement this from scratch.

code2graph does it once, behind a neutral output and a stable identity scheme, so a consumer builds its own layer (retrieval, analysis, navigation) without redoing parsing. The wider ecosystem can share one substrate instead of many bespoke ones.

When to use code2graph

Use it when you're building a tool that needs to understand code structure — and you want to own the storage and policy decisions yourself.

code2graph is a low-level primitive, not a finished product. If your tool needs symbols, a reference graph, and cross-file edges, you have two choices: re-implement per-language tree-sitter walks, SCIP-aligned identity, and cross-file resolution from scratch (and maintain all of it as grammars drift), or depend on code2graph and get neutral facts out of the box.

It exists so other tools don't each rebuild the same conversion layer.

Storage- and database-agnostic by design. Extraction returns FileFacts containing symbols and raw references; resolution returns a CodeGraph containing symbols and edges. code2graph never persists either and has no opinion on where a consumer keeps them. Put consumer-owned data in a graph database, a vector store, SQLite, an in-memory index, or flat files — your call.

Most code-intelligence tools ship a baked-in storage engine and a fixed query model bolted to the parser; code2graph deliberately keeps them separate, so you're never fighting someone else's persistence or query opinion.

Reach for it when:

  • you're building developer tooling: code search, RAG over code, refactoring, dependency or impact analysis, security scanning — and don't want to own the parsing layer;
  • you need a code graph but want to choose your own storage, index, and query engine;
  • you want honest, deterministic facts with an explicit Confidence on every edge. Not a black box that scores, ranks, or persists for you.

It's not for you if you want a turnkey, batteries-included code-intelligence product. Code2graph is the substrate beneath that, not the product itself.

Install

Choose the surface that owns the work you need:

Surface Package Install
Conversion primitive code2graph cargo add code2graph
Optional in-memory query index code2graph-query cargo add code2graph-query
Project-query CLI (c2g binary) code2graph-cli cargo install code2graph-cli
Python binding code2graph-rs pip install code2graph-rs
Node / Bun binding @nodedb-lab/code2graph npm install @nodedb-lab/code2graph

code2graph-query is optional and storage-free: it builds an owned in-memory index over a resolved graph, leaving persistence to its caller. A CodeGraph contains the extracted symbol definitions plus resolved edges; each edge records its source and target IDs, relationship role, confidence, provenance, and reference occurrence.

use code2graph::resolve::{Resolver, SymbolTableResolver};
use code2graph_query::GraphIndex;

let graph = SymbolTableResolver.resolve(&[a, b])?;
let index = GraphIndex::from_graph(graph)?; // owned by this process
let helpers = index.symbols_named("helper");

The CLI is a consumer application, not part of the core or query crates. It builds a local, consumer-owned cache for project commands; use --no-cache when a cache should not be read or written.

c2g index .
c2g symbols helper
c2g callers helper
c2g impact helper --depth 3

By default the CLI rejects an incomplete index. --allow-partial explicitly permits publishing and querying a partial source set; inspect the reported omissions before relying on its results.

Driving the CLI from a coding agent: docs/agent-integration.md carries a copy-pasteable rule block for CLAUDE.md / AGENTS.md and explains why a mechanical trigger is the only kind an agent reliably follows.

cargo install code2graph-cli builds the binary from source with Cargo; the installed command is c2g. No prebuilt binary distribution is promised here.

Managing the cache

The CLI keeps a per-project SQLite cache under the OS cache directory (on Linux, $XDG_CACHE_HOME/code2graph or ~/.cache/code2graph; the equivalent on macOS/Windows), keyed by an opaque hash of the project's canonical root. The cache is incremental and self-bounding: re-indexing reuses unchanged work, and superseded snapshots are garbage-collected on publish so the database does not grow without limit. The cache subcommand inspects and manages it — all commands accept --json.

c2g --root . cache path       # print this project's cache directory and database path
c2g --root . cache status     # + size, reclaimable space, schema version, per-snapshot breakdown
c2g cache status --all        # every cached project: size, state, reclaimable (no --root needed)
c2g --root . cache compact    # return fragmentation to the filesystem; reports bytes freed
c2g cache compact --all       # compact every cached project (no --root needed)
c2g --root . cache rebuild    # discard this project's cache and index it again
c2g --root . cache clear      # delete this project's cache; reports bytes freed
c2g cache clear --all         # delete every project's cache (no --root needed)
c2g cache prune               # delete only unusable caches (no --root needed)

cache status reports reclaimable bytes — space the database has freed but not yet returned to the filesystem. When it is large, cache compact rewrites the database and gives that space back. cache status --all labels every cached project current, outdated (older schema, rebuilt on next use), newer (written by a newer binary), or orphaned (project root gone).

cache rebuild discards the database and indexes from source. Prefer index --force to re-extract while keeping the cache file; reach for rebuild when the cache file itself is the thing you want gone.

Automatic pruning. index prunes the cache root at most once a week, so caches for deleted projects do not accumulate. When the interval has not elapsed the check is a single file read, and queries never do it at all. It removes only what cache prune removes — nothing that can still serve a query — and prints a line to stderr when it removes something. Set CODE2GRAPH_AUTO_PRUNE=off to disable it.

If you would rather sweep on a schedule, run c2g cache prune from your own timer. On Linux with systemd:

# ~/.config/systemd/user/code2graph-prune.service
[Service]
Type=oneshot
ExecStart=%h/.cargo/bin/c2g cache prune

# ~/.config/systemd/user/code2graph-prune.timer
[Timer]
OnCalendar=weekly
Persistent=true

[Install]
WantedBy=timers.target

Enable with systemctl --user enable --now code2graph-prune.timer. With cron, 0 3 * * 0 c2g cache prune is the equivalent; on macOS, a launchd StartCalendarInterval job running the same command. Add c2g cache compact --all alongside it to return fragmentation as well.

cache prune removes the two kinds of cache that can never serve a query again: an orphaned one whose project root no longer exists (a deleted checkout, or a temporary directory), and an outdated one written by an older schema, which the next command on that project would discard and rebuild anyway. It keeps every current cache, and keeps any cache stamped newer than the running binary. It reports what it removed, what it kept, and the bytes freed.

cache clear only ever removes directories under <cache>/projects/; it never touches your source tree. Deleting a project's cache simply forces a fresh index on the next command.

The Python and Node/Bun packages expose the conversion and query handles as native language objects; see bindings/python and bindings/node for their APIs. A public Pi host integration is available as @nodedb-lab/pi-code2graph; it composes the native binding for agent-host tools without changing the core library's storage-neutral contract. Other host integrations are described only as integrations, not as part of the core API.

API reference: docs.rs/code2graph.

Quickstart

The pipeline is two pure, deterministic stages:

source ──[extract]──▶ FileFacts (symbols + references) ──[resolve]──▶ CodeGraph (symbols + edges)
use code2graph::{extract_path, resolve::{Resolver, SymbolTableResolver}};

let a = extract_path("src/util.rs", "pub fn helper() {}")?;
let b = extract_path("src/main.rs", "pub fn run() { helper() }")?;

let graph = SymbolTableResolver.resolve(&[a, b])?; // run --calls--> helper

Language is inferred from the file extension — there's nothing to configure. Symbols carry a byte span, not source text; the consumer slices what it needs.

Symbol identity wire format

SymbolId::to_scip_string() remains a standard, SCIP-parseable display/interoperability string. SCIP has no language or local-file coordinate, so serde-backed API payloads use the versioned lossless object form { "version": 1, "scip": "…", "lang": "…" } for global IDs and { "version": 1, "scip": "local …", "file": "…" } for local IDs. These coordinates are part of code2graph identity and must be retained when persisting or forwarding facts. Readers continue to accept the legacy SCIP-string form; it lacks those coordinates and therefore cannot preserve full identity.

Scope

In scope:

  • Multi-language symbol definitions (functions, types, traits/classes, consts, modules, …).
  • References (call sites / usages) with file:line:col.
  • Cross-file edges built by resolving references to definitions (calls, imports, inherits; richer reference kinds and data-flow later).
  • A neutral FileFacts value with symbols and raw references, and a resolved CodeGraph with symbols and edges.

Out of scope (belongs in the consumer):

  • Storage, indexing, embeddings, ranking, scoring.
  • Recall-first heuristics, retrieval signals, ACLs.
  • Document/Markdown ingestion. code2graph is code.

Languages

Coverage spans systems, JVM, scripting, web (incl. embedded single-file components like Svelte, whose <script> blocks are extracted as real TS/JS), and declarative DSLs (SQL, HCL/Terraform) — at varying depth.

Full coverage, honestly: docs/supported-languages.md — the per-language matrix (extraction depth, what each emits, and the candidate / not-feasible / out-of-scope lists). Cross-language FFI boundaries: docs/ffi-support-matrix.md.

The canonical, always-current set is the Language enum + extension dispatch in src/lang.rs — read that, never a list cached in prose. Each language is a Cargo feature (all on by default). Builds can select a smaller set with, for example, default-features = false, features = ["rust"]; check Language::availability() before extraction, because a disabled language returns UnsupportedLanguage. JavaScript shares the typescript feature, and svelte enables it transitively. Adding one follows a mechanical recipe — see CONTRIBUTING.md.

Resolution tiers

Resolution is pluggable behind the Resolver trait — the tier seam. Every resolver emits the same CodeGraph schema, tagging each edge with a Confidence (how sure) and a Provenance (which analysis derived it). Consumers pick a tier without changing how they read the output.

Tier Resolver Confidence Behaviour
A SymbolTableResolver NameOnly Fast, all languages, recall-first. An ambiguous name links to all same-named definitions.
B ScopeGraphResolver Scoped / Exact Scope-aware: resolves through lexical scopes, imports, and qualified paths. It emits only syntactically supported resolutions and marks the resulting confidence; it is not type-checking.
FfiBridgeResolver Links cross-language boundaries (e.g. a #[no_mangle] Rust fn called from C, a PyO3 #[pyfunction] from Python, a #[wasm_bindgen]/#[napi] fn from JS/TS, a Java native method) by ABI name — even when the exported name differs from the definition name.

Both tiers emit the same shape, so a consumer reads the output identically and chooses the tier by the confidence it needs. The scope-aware tier is implemented for a growing subset of languages; others fall back to the recall-first baseline. Identity rendering and the graph schema may still evolve before 0.1.

Measuring resolution quality

Resolution quality is measured, not asserted. The code2graph-eval crate scores ref→def precision and recall per language and per resolver tier against a corpus (eval/corpus/). The evaluation unit is a located edge — a reference site bound to a definition site — so name-only fan-out is penalised exactly where it over-connects: a reference that links to N same-named definitions scores one true positive and N − 1 false positives.

cargo run  -p code2graph-eval    # print the scorecard
cargo test -p code2graph-eval    # regression gate on the invariants

Ground truth comes from hand-authored golden fixtures and from external SCIP oracles — indexes produced by mature, type-aware indexers (rust-analyzer, scip-typescript, scip-java, …) — so the numbers quantify each tier's lane against an independent source of truth. The normal build and test loop pulls no SCIP/indexer dependencies; see eval/ORACLE.md for the maintainer-only regeneration workflow.

Status

🚧 Early, pre-0.1. Extraction and the resolver tiers work end-to-end across the language set above. SCIP-aligned identity (SymbolId renders to a stable SCIP string, so cross-file matching is string equality) and the neutral fact schema are in place; both may still evolve before 0.1.

Used by

code2graph is designed as a neutral substrate for tools that apply their own policy and storage, including memory and retrieval systems, code-analysis tools, and security scanners.

Building something on code2graph? Open a Discussion — and if it's useful to you, a ⭐ on the repo genuinely helps others find it.

Contributing

Contributions are welcome — especially new languages and resolution-quality improvements. Start with CONTRIBUTING.md: it covers the architecture and invariants, the language-adding recipe, what to do when a language has no usable tree-sitter grammar, the resolver tiers, and how to validate changes against the eval harness. By participating you agree to the Code of Conduct.

Release operation

Pushing an immutable vX.Y.Z (or vX.Y.Z-alpha.N, -beta.N, -rc.N) tag runs Release Prepare. It validates and tests the tag, builds each native package once, and retains the checksummed bundle for 14 days. It publishes nothing.

Distribute that exact bundle manually after Prepare succeeds. The manual workflow checks the successful Prepare run's tag, source SHA, workflow identity, manifest, checksums, and complete file set before any registry or GitHub publication:

gh workflow run release.yml -f tag=vX.Y.Z -f prepare_run_id=PREPARE_RUN_ID

The optional distribution_ref is only for a distribution-workflow/helper fix; the source and prepared artifacts remain bound to the tag. To retry a failed registry or GitHub stage, dispatch the same command and disable every completed toggle (crates, pypi, npm, github). Do not rerun Prepare for a distribution failure. If the bundle expires, rerun Prepare for the same immutable tag and use its new run ID. A package already present with different content fails closed.

License

Apache-2.0. See LICENSE.

About

Source code → structural facts. A purpose-neutral, polyglot code-graph extraction library in Rust powered by tree-sitter. Turns source files into symbols, lexical scopes, and cross-file or FFI edges as plain data. Zero storage opinion.

Topics

Resources

Code of conduct

Contributing

Stars

4 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages