Survey of tools that build a code-level knowledge graph — symbols, calls, imports, references — assessed for a solo Rust/TypeScript/Python developer who wants to attach concept notes to code entities durably. One of four source surveys behind knowledge-graph-research-2026-09-07.
For Agents
Survey date: 2026-09-07. All star counts and push dates were verified against the GitHub API on that date and are point-in-time facts, not live values. Verdict carried into the synthesis: Serena now (live LSP, no index to keep fresh); codebase-memory-mcp when cross-repo memory is wanted; code-graph-rag on the server only if automatic rename detection proves necessary. Ruled out: GitNexus (PolyForm Noncommercial), CodeQL (licence bars private repos), Joern, Potpie, CodeGraphContext, Aider repo-map, Nuanced, FalkorDB code-graph. Archived upstream: GitHub stack-graphs. Sourcegraph has no self-hostable OSS edition and killed Cody Free/Pro. Load-bearing primitive: SCIP symbol strings carry no line or column numbers, so they survive line churn. Rust encodes crate plus module path and therefore survives file moves; TypeScript and Python encode file/module path and break on move. Kept verbatim from the source report apart from this header and the
## Relatedfooter.
Date of survey: 2026-09-07. All GitHub metrics verified against the GitHub API on that date unless marked otherwise.
Target profile. Solo developer. Rust, TypeScript, Python. Many repos. Two Macs plus a Linux server reachable over Tailscale. Wants local-first, low operational burden, and specifically wants to attach concept-level markdown notes (architecture decisions, gotchas, design rationale) to code entities durably.
0. Read this first: three landscape shocks since 2025
These invalidate a lot of older advice and blog posts.
-
Kuzu is dead.
kuzudb/kuzuis flaggedarchived: truein the GitHub API. Final push 2025-10-10, final release v0.11.3, 4,025 stars frozen, MIT. An EU merger filing in February 2026 confirmed Apple acquired Kùzu, Inc. (completed October 2025).kuzudb.comno longer resolves. Reported at the time by The Register, 2025-10-14. Do not build on Kuzu. The de facto successor is LadybugDB (LadybugDB/ladybug, MIT, 1,714 stars, created 2025-10-07, pushed 2026-09-07), an MIT fork whose README states “The database was formerly known as Kuzu.” It shipspip install ladybug,npm @ladybugdb/core, andcargo add lbug. cognee defaults to it and GitLab migrated to it. -
SCIP left Sourcegraph. The repo moved to a neutral org: github.com/scip-code/scip, homepage scip-code.org. The GitHub API confirms the transfer (same repo id 490705184, created 2022-05-10, now owned by the
scip-codeorg). Apache-2.0, 781 stars, CLI v0.10.0, pushed 2026-09-03. Sourcegraph’s announcement “The future of SCIP” (2026-03-25) describes a Core Steering Committee with Uber and Meta engineers and an RFC process called SEP. That blog returned HTTP 403 to direct fetch; committee membership is PARTIALLY UNVERIFIED, but the org, site and repo transfers are directly verified. Also moved: scip-java, scip-go, scip-rust. Still undersourcegraph/: scip-typescript, scip-python, scip-clang. -
GitHub’s stack-graphs is archived. github/stack-graphs archived 2025-09-09, 875 stars, Apache-2.0. Final commit is literally “This repository is no longer being maintained.” It never supported Rust. Its last releases were December 2024.
A necessary caveat on star counts
Several 2026 entrants below carry star counts that would have been implausible two years ago: Graphify at 115,470 after five months, CodeGraph at 69,901 after seven, GitNexus at 47,105, codebase-memory-mcp at 42,518. I verified every one of these directly against the GitHub API. They are real numbers. But treat them as a measure of promotion velocity, not adoption or quality. Corroborating signals are poor: Graphify carries 1,264 open issues and 11,212 forks, CodeGraph 531 open issues, codebase-memory-mcp 539. Fork-to-star ratios near 10% and enormous README marketing surfaces are the signature of heavily promoted projects. Weight the engineering evidence (published schemas, benchmarks, honest limitation docs) far above the star count.
1. Comparison table
Legend: R/T/P = Rust / TypeScript / Python. “Stable ID” = an identifier for a code entity that survives line-number churn.
| Tool | Stars | Last commit | License | Storage | R/T/P | Query surface | Incremental | Stable ID | Rename detect | Code+prose |
|---|---|---|---|---|---|---|---|---|---|---|
| code-graph-rag | 5,055 | 2026-09-07 | MIT | Memgraph + Qdrant (Docker) | all first-class | ~29 MCP tools, Cypher | yes, benchmarked | yes, qualified_name | yes, explicit | headings only |
| codebase-memory-mcp | 42,518 | 2026-09-07 | MIT | SQLite, embedded | all “Good” tier | 15 MCP tools, openCypher read subset | yes, watcher | yes, <project>.<path>.<name> | not documented | ADR doc only |
| Serena | 28,931 | 2026-09-06 | MIT | LSP live + pickle cache | all via LSP | ~20 MCP tools, name paths | live, no index | file-scoped name path | via LSP rename | memories, unlinked |
| GitNexus | 47,105 | 2026-09-07 | PolyForm NC | LadybugDB, embedded | all first-class | 17 MCP tools, raw Cypher | yes (docs conflict) | yes, arity-suffixed | rename tool | yes, Section nodes |
| Graphify | 115,470 | 2026-09-05 | Apache-2.0 + MIT | graph.json file | all (untiered) | 7 MCP tools, no Cypher | partial, ghosts on delete | partial | no | yes, best |
| cognee (code graph) | 30,545 | 2026-09-07 | Apache-2.0 | LadybugDB, embedded | all via enola | 8 code ops (not on its MCP) | yes, fact-hash delta | yes, uuid5 | no | two disjoint paths |
| CodeGraphContext | 4,169 | 2026-09-06 | MIT | FalkorDB Lite / Kuzu | all (untiered) | 25 MCP tools, read Cypher | watcher, unclear | no — line in key | no | no markdown |
| CodeGraph (colbymchenry) | 69,901 | 2026-08-31 | MIT | SQLite, embedded | all, Rust kernel | 1 MCP tool | yes, OS file events | not documented | no | no |
| Joern | 3,478 | 2026-09-07 | Apache-2.0 | flatgraph file | Rust pre-alpha | CPGQL Scala REPL | full rebuild only | yes, fullName | no | no |
| GitLab Orbit Local | 46 (mirror) | 2026-09-07 | GitLab EE | DuckDB file | all | raw DuckDB SQL | UNVERIFIED | yes | no | no |
| SCIP (format) | 781 | 2026-09-03 | Apache-2.0 | protobuf file | R via r-a, T/P quiet | CLI, expt-convert to SQLite | per-indexer | yes, best design | no | no |
| ast-grep | 15,779 | 2026-09-06 | MIT | none | all first-class | CLI, YAML, MCP | stateless | no | no | no |
| Aider repo-map | 48,808 | 2026-05-22 | Apache-2.0 | tags cache only | all | none, internal | tags cached | no | no | no |
| CodeQL | 10,058 (queries) | 2026-09-07 | CLI restricted | relational DB | R GA 2025-10 | Datalog (QL) | no | yes | no | no |
| Potpie | 5,713 | 2026-09-07 | Apache-2.0 | falkordb_lite | no parser at all | CLI + skills | N/A | entity-level | N/A | prose only |
| Sourcebot | 3,930 | 2026-09-04 | FSL + proprietary | Docker × 3 | ctags only | search, MCP now paid | n/a | no | no | no |
| stack-graphs | 875 | archived | Apache-2.0 | SQLite | no Rust | CLI | yes | positional only | no | no |
| Nuanced | 128 | archived | MIT | JSON | Python only | CLI | no | undocumented | no | no |
| Kuzu | 4,025 | archived | MIT | embedded | n/a (a database) | Cypher | n/a | n/a | n/a | n/a |
2. Embedded local code graphs — the realistic picks
2.1 codebase-memory-mcp (DeusData)
github.com/DeusData/codebase-memory-mcp · 42,518 stars · 3,471 forks · 539 open issues · created 2026-02-24 · pushed 2026-09-07 · MIT · written in C.
(a) Graph model — published verbatim in the README. Node labels: Project, Package, Folder, File, Module, Class, Function, Method, Interface, Enum, Type, Route, Resource. Edge types: CONTAINS_PACKAGE, CONTAINS_FOLDER, CONTAINS_FILE, DEFINES, DEFINES_METHOD, IMPORTS, CALLS, CALL_REFERENCE, HTTP_CALLS, ASYNC_CALLS, IMPLEMENTS, HANDLES, USAGE, CONFIGURES, WRITES, MEMBER_OF, TESTS, USES_TYPE, FILE_CHANGES_WITH.
This is the only schema in the survey that ships both a TESTS edge and a FILE_CHANGES_WITH (git co-change) edge, two of the edge types the brief asked about. There are no commit or PR nodes.
(b) Languages. tree-sitter across 162 languages, plus what it calls Hybrid LSP semantic type resolution for Python, TypeScript/JavaScript/JSX/TSX, PHP, C#, Go, C, C++, Java, Kotlin, Rust and Perl — described as “a lightweight C implementation of language type-resolution algorithms, structurally inspired by and compatible with major language servers (tsserver / typescript-go, pyright, gopls, Roslyn…)“. Its own quality tiering places Python, TypeScript, TSX, Go, Rust in the “Good (75-89%)” band, not the top band. Publishing a self-critical accuracy tier is a good sign; it also means roughly one in six edges may be wrong in your languages.
(c) Query surface. 15 MCP tools: index_repository, list_projects, delete_project, index_status, search_graph, trace_path, detect_changes, query_graph, get_graph_schema, get_code_snippet, get_architecture, search_code, manage_adr, ingest_traces. query_graph is a read-only openCypher subset with a precisely documented grammar: MATCH / OPTIONAL MATCH / WHERE / WITH / RETURN / ORDER BY / SKIP / LIMIT / DISTINCT / UNWIND / UNION / CASE, variable-length paths [*1..3], and EXISTS { (n)-[:TYPE]->() }. Anything outside the subset fails with an explicit unsupported … error rather than silently returning empty — a genuinely good design choice. Writes, MERGE and CALL are not supported.
(d) Updates. File watcher with incremental updates; detect_changes maps a git diff to affected symbols with blast radius and risk classification. Claimed indexing speed: Linux kernel, 28M LOC, 75K files, in 3 minutes. That is a vendor claim, unreproduced here.
(e) Storage. SQLite, embedded, at ~/.cache/codebase-memory-mcp/ (override with CBM_CACHE_DIR). No Docker, no server, single static binary, no language runtime. It also writes .codebase-memory/graph.db.zst, a VACUUM INTO-compacted zstd snapshot of the graph committed next to your source, with a merge=ours gitattributes line auto-created. This is the single most relevant feature for a two-Mac-plus-server setup: the graph travels with the repo through git rather than being rebuilt per machine.
(f) Local-first. Yes. No API key, no network, all processing local.
(g) License. MIT.
(h) Extensibility. The store is a plain SQLite file you can open with the sqlite3 CLI. Cypher is read-only, so you cannot write note nodes through the MCP surface, but nothing stops you adding your own sidecar table keyed by qualified name. Export is the zstd artifact.
(i) Code and prose. Partial and easily overstated. manage_adr is CRUD over one Architecture Decision Record document per project, with section-level splicing (set_sections rewrites named ## Heading sections byte-preservingly). That is a per-project prose document, not note-to-symbol linking. There are no markdown node types in the graph model.
Stable ID: yes. get_code_snippet uses qualified names of the form <project>.<path_parts>.<name>. Line numbers are not in the key, so line churn is survivable. File moves are not — the path is in the identifier. Rename detection: not documented.
Skepticism. The README is 75KB and a large fraction of it is a table of integrations with 20+ agent harnesses, security badges, VirusTotal scans and SLSA attestations. It also writes to your agent configuration files by design. The engineering artifacts (published Cypher grammar, honest accuracy tiers, status:"degraded" on dump-verification failure) are better than the marketing suggests, but audit before installing.
2.2 CodeGraph (colbymchenry)
github.com/colbymchenry/codegraph · 69,901 stars · 4,460 forks · 531 open issues · created 2026-01-18 · pushed 2026-08-31 · MIT.
Native Rust parsing kernel over tree-sitter, 20+ languages including Rust, TypeScript and Python. Storage is local SQLite at .codegraph/codegraph.db with FTS5. Auto-sync watches via native OS file events with a 2-second debounce and incremental sync. Fully local, no API keys, no external services, bundles its own Node runtime.
The disqualifier for this user is the query surface. It exposes a single MCP tool, codegraph_explore, plus a buildContext JS API. There is no Cypher, no documented node/edge schema in the README, no qualified-name identity documented, no markdown or prose ingestion, and no graph export. It is a closed context-retrieval box tuned for token efficiency; its published benchmarks are agent-efficiency comparisons (tool calls, wall time, cost against VS Code, Excalidraw, Tokio) rather than graph-quality or index-timing numbers.
Good if you want fewer tokens per agent turn. Useless as a substrate you can attach your own notes to.
2.3 GitNexus
github.com/abhigyanpatwari/GitNexus · 47,105 stars · created 2025-08-02 · pushed 2026-09-07 · release v1.6.12-rc.5 · license NOASSERTION in the API, which corresponds to PolyForm Noncommercial 1.0.0 in the repo.
Technically the most sophisticated tool surveyed. 34 node tables (File, Folder, Function, Class, Interface, Method, Struct, Enum, Macro, Trait, Impl, TypeAlias, Namespace, Route, Tool, BasicBlock and more) with a single CodeRelation edge table carrying a type property, deliberately so an LLM can write Cypher without knowing edge tables. Relation types include CONTAINS, DEFINES, IMPORTS, CALLS, EXTENDS, IMPLEMENTS, OVERRIDES, ACCESSES, HANDLES_ROUTE, PUBLISHES_TO, CONSUMES_FROM plus program-dependence and taint layers (CFG, REACHING_DEF, TAINTED, SANITIZES, CDG). Edges carry a confidence score. Rust, TypeScript and Python are all first-class on the README’s 15-language capability matrix. 17 MCP tools including raw cypher, plus detect_changes and a graph-assisted rename. Embedded LadybugDB in .gitnexus/, no Docker, no API key for indexing or querying. Markdown is a first-class pipeline phase producing Section nodes with heading hierarchy and IMPORTS edges for cross-file links. IDs are of the form Function:src/embed.py:get_embeddings with an arity suffix; line numbers are properties, not part of the key.
Ruled out on license. PolyForm Noncommercial 1.0.0 is not an OSI-open licence and prohibits commercial use. If any of your repos are client work or a commercial product, this is a legal problem, not a philosophical one. Secondary issues: node properties are fixed DDL columns so you cannot attach arbitrary metadata, and the README roadmap still lists “Incremental Indexing” as actively building while the CLI docs describe it as working.
2.4 Graphify
github.com/Graphify-Labs/graphify (redirected from safishamsi/graphify) · 115,470 stars · 11,212 forks · 1,264 open issues · created 2026-04-03 · pushed 2026-09-05 · dual Apache-2.0 and MIT · default branch v8 · PyPI package graphifyy.
The only tool in the survey that genuinely unifies code and prose, and the only one that exports an Obsidian vault. It ingests .py .ts .js .go .rs alongside .md .txt .rst and PDFs into one graph; markdown links and wikilinks become references edges; ADR/RFC citations and # NOTE: / # WHY: comments become nodes. Exports include --obsidian --obsidian-dir ~/vault, GraphML for Gephi/yEd, Neo4j and FalkorDB Cypher, Mermaid, and a hand-editable graph.json. Given that this user already keeps an Obsidian vault, that is a direct hit on the stated goal.
But the graph model is the weakest of the serious contenders. From its ARCHITECTURE.md, every extractor returns nodes of the shape {"id", "label", "source_file", "source_location"} — there is no node-type field at all. Nodes are untyped strings. Relations are calls, imports, inherits, mixes_in, uses, method, references, depends_on with confidence tags EXTRACTED / INFERRED / AMBIGUOUS. Storage is a single graph.json file capped at 512 MiB, not a database. There is no Cypher query engine — Cypher is only an export target. Its own troubleshooting docs admit a defect: “Graph has fewer nodes after --update or rebuild — If a refactor deleted files, the old nodes linger,” requiring --force. Delete detection is broken by the maintainers’ own admission and there is no rename detection. Its temporal benchmark run over ERPNext (~1M LOC) produced only 22,620 nodes and 48,710 edges, which is very coarse for a repo that size. Prose ingestion requires an LLM; code-only extraction (--code-only) is free and offline.
Five months old at 115k stars. Use the Obsidian export as inspiration; be cautious about depending on the graph.
2.5 CodeGraphContext
github.com/CodeGraphContext/CodeGraphContext · 4,169 stars · 824 forks · created 2025-08-16 · pushed 2026-09-06 · MIT.
Best storage flexibility for a laptop: five backends including embedded FalkorDB Lite (default) and Kuzu as a cross-platform fallback, so no Docker and no Neo4j required. 25 MCP tools including read-only execute_cypher_query. 24 languages including Rust, TypeScript and Python, with no maturity tiering. No LLM key anywhere.
Two disqualifiers. First, its documented merge key for Function and Class is (name, path, line_number) — line-number churn creates a new node, which defeats the entire durable-note use case. Second, markdown is not in its language table at all, so docs are not indexed.
Also note the repo ships its own defect backlog in the root: CGC_GRAPH_INCONSISTENCIES.md, “100 items”, generated 2026-06-10, listing missing Rust CALLS edges, a missing Rust Function node, and edge types marked “not implemented — 0 matches in src/“. Its topics are hacktoberfest2025 and social-winter-of-code, so a large share of its 208 contributors are event drive-bys. Honest, but not ready.
3. Server-backed code graphs
3.1 code-graph-rag (vitali87) — the best-engineered tool in the survey
github.com/vitali87/code-graph-rag · docs code-graph-rag.com · 5,055 stars · 661 forks · 92 open issues · created 2025-06-16 · pushed 2026-09-07 · release v0.0.845 (2026-09-02) · MIT.
(a) Graph model, fully published at docs/architecture/graph-schema.md. Nodes: Project, Package, Folder, File, Module, Class, Function, Method, Interface, Enum, Type, Union, ModuleInterface, ModuleImplementation, ExternalPackage, ExternalModule, Resource, Section, Pattern, CodeSmell, SecurityIssue. Relationships: CONTAINS_*, CONTAINS_SECTION, DEFINES, DEFINES_METHOD, IMPORTS, EXPORTS, IMPLEMENTS_MODULE, INHERITS, IMPLEMENTS, OVERRIDES, RETURNS, ACCEPTS, DEPENDS_ON_EXTERNAL, CALLS, REFERENCES, INSTANTIATES, READS_FROM, WRITES_TO, FLOWS_TO, IMPLEMENTS_PATTERN, HAS_SMELL, HAS_VULNERABILITY. Call-site edges carry line, col, end_line, end_col, arg_count, kwarg_names, alias, imported_name, resolution where resolution is one of exact | overload | heuristic | trace_confirmed | dynamic. Publishing per-edge resolution confidence is unusually honest.
(b) Languages. Full support for Python, TypeScript, TSX, JavaScript, Rust, Go, Java, C, C++, C#, PHP, Lua, Dart via tree-sitter, with hybrid libclang for C/C++ and Roslyn for C#. All three of the user’s languages are first-class.
(c) Query surface. ~29 MCP tools. The navigation set — resolve, definition, callers, callees, implementors, overrides, importers, tests_reaching — is explicitly deterministic with no LLM in the loop. Plus index_repository, update_repository, reingest, query_code_graph, semantic_search, structural_search, get_code_snippet, surgical_replace_code, find_duplicate_code, explain_traceback, rank_root_causes. Cypher underneath via Memgraph. CLI is cgr.
(d) Updates — the only tool with published re-index benchmarks. From docs/reports/REINGEST_BENCHMARK.md, measured on its own repo (1,409 files, 661,870 lines, ~414k LOC), Apple Silicon, cgr 0.0.804, dated 2026-08-30:
| Edited file | Dependents | reingest p50 | reingest p95 |
|---|---|---|---|
| typical module | 2 | 194 ms | 445 ms |
| hub module | 54 | 3,492 ms | 3,733 ms |
A watchdog-based realtime_updater.py feeds the same reingest path, and cgr check --base <ref> covers CI and pre-commit.
(e) Storage — the cost. Memgraph plus Qdrant, via Docker (cgr daemon up). There is no embedded option. For this user that means running the stack on the Tailscale Linux box and pointing both Macs at it, which is actually a decent fit, or running Docker per laptop, which is not.
(f) Local-first. Indexing is tree-sitter only, no LLM. The deterministic tools need no model. Natural-language querying needs one, but Ollama is a first-class provider (ORCHESTRATOR_PROVIDER=ollama, CYPHER_PROVIDER=ollama), so fully offline is achievable.
(h) Extensibility — the best in the survey. It is a real Cypher database you control. You can write arbitrary node labels and properties yourself, which is exactly what note-to-symbol linking requires. cgr export -o graph.json plus a graph_loader Python API.
(i) Code and prose. Partial: markdown headings become Section nodes nested by CONTAINS_SECTION under the document’s Module, with line spans. There is no wikilink-to-symbol edge out of the box, but since you have write access to Memgraph, adding one is a MERGE statement.
Stable ID: the strongest of any graph tool here. qualified_name uniquely identifies Function, Method, Class and Module; a @<start_line> suffix is appended only on same-name collisions within a module. The docs state explicitly that cgr diff “treats these properties as location, not structure: a line shift never reports as a changed relationship.”
Rename detection: yes, and it is the only tool that ships it. symbols.renamed pairs a disappeared symbol with an appeared one sharing the same whole-skeleton AST fingerprint in the same file, and dangling_callers reports each affected call site with renamed_to. Deletions surface as symbols.removed and removed_files.
3.2 cognee code graph
github.com/topoteretes/cognee · 30,545 stars · 3,008 forks · 485 open issues · pushed 2026-09-07 · Apache-2.0 · v1.5.4 (2026-09-04).
Since commit be35e6b on 2026-07-11 cognee no longer parses code itself; it shells out to enola, an Apache-2.0 Go binary (enola-labs/enola), and maps .enola/facts.jsonl into DataPoints. Model in cognee/tasks/code_graph/models.py: CodeGraphEntity(name, kind, file_path, line, end_line, repo, enola_id, fact_properties, fact_hash, part_of) subclassed as CodeModule, CodeSymbol, ApiEndpoint, StorageResource, ExternalDependency, CodeService, CodeTestReference, CodeLintFinding. Edges come dynamically from enola: calls, imports, implements, declares, instantiates.
Rust, TypeScript and Python are all supported (Rust via Cargo.toml, TS via tsconfig.json). Storage now defaults to LadybugDB, embedded, no Docker, and auto-adopts an existing Kuzu file. The docs state the code pipeline and SearchType.CODE are deterministic and require no LLM or embedding configuration. Incremental via snapshot_id plus per-fact fact_hash delta with a stale sweep. enola publishes warm re-index timings of 7.5s for grafana (10,313 files) and 52.6s for the Linux kernel.
Two real problems. First, cognee’s own MCP search tool takes no code_query parameter, so the structured code operations are unreachable over cognee’s MCP — you would use enola’s own MCP instead, which rather defeats the point of adopting cognee. Second, the entire code graph now depends on one young vendor binary (enola created 2026-02-10, 215 stars), and an enola upgrade invalidates every snapshot_id, forcing a full re-ingest. Stable IDs are uuid5(NAMESPACE_OID, "enola:"+triple) with lines as properties. Deletes are detected by sweep; renames are not tracked.
3.3 blarify
github.com/blarApp/blarify · 232 stars · MIT · last commit 2026-08-17. tree-sitter plus LSP with optional SCIP. Edges include CONTAINS, FUNCTION_DEFINITION, CLASS_DEFINITION, IMPORTS, CALLS, INHERITS, INSTANTIATES, TYPES, ASSIGNS, USES, MODIFIED, DELETED, ADDED, DESCRIBES. The DESCRIBES edge links doc nodes to code nodes natively — conceptually the closest thing to what this user wants. Stable hashed node_id, incremental update, and a blarify-mcp. But it has no Rust support (Python, JS, TS, Ruby, Go, C#, PHP, Java), it requires a Neo4j or FalkorDB server, and its own most recent commit message admits “632 type errors, 91 lint errors and 18 unit-test failures, all pre-existing.”
3.4 GitLab Orbit Local
github.com/gitlabhq/orbit-knowledge-graph (mirror, 46 stars) · canonical at gitlab.com/gitlab-org/orbit/knowledge-graph · docs docs.gitlab.com/orbit · launched 2026-06-10 · GitLab EE license, source-available, not OSI-open.
Single Rust binary. orbit index . parses a local repository, extracts definitions and cross-file references, and writes a code-only call graph to a single DuckDB file at ~/.orbit/graph.duckdb. 11+ languages including Rust, TypeScript and Python. No GitLab account required at query time; runs offline after install. Install is a one-line curl script, or build from source with a stable Rust toolchain.
Caveats that matter. It is explicitly Beta — “The Query DSL and ontology may change.” The local query surface today is raw DuckDB SQL (orbit sql 'SELECT count(*) FROM gl_definition', plus orbit schema). The two MCP tools reported in GitLab’s marketing, query_graph and get_graph_schema, are documented in the context of Orbit Remote, the hosted SDLC graph gated behind a knowledge_graph feature flag on a top-level GitLab.com group; the local binary’s README marks local MCP as planned, not shipped. Treat local MCP as roadmap.
The engineering is the most credible in the survey — GitLab’s internal Orbit graph spans 40,000+ projects, 500M nodes and 2B relationships refreshed in under 45 minutes — but the licence and the beta ontology warning make it a watch-list item, not an adoption today.
Sources: GitLab blog on Orbit and Antigravity, Orbit indexed data docs.
4. Live LSP, no persistent graph
Serena
github.com/oraios/serena · 28,931 stars · 1,964 forks · 154 open issues · created 2025-03-23 · pushed 2026-09-06 · MIT.
Serena is not a graph tool and should not be evaluated as one. It wraps language servers behind an abstraction layer (SolidLanguageServer) and answers symbol queries live over LSP. Language servers per language, from ls_config.py: Rust → rust-analyzer, TypeScript/JS → typescript-language-server, Python → Pyright (default), with Basedpyright, Jedi and Ty as alternatives. Over 40 languages claimed.
Tools (from src/serena/tools/symbol_tools.py and the README): find_symbol, find_referencing_symbols, find_implementations, get_symbols_overview, symbolic editing (replace_symbol_body, insert_after_symbol, insert_before_symbol), rename, diagnostics, plus basic search_for_pattern, replace_content, list_dir, find_file, read_file, execute_shell_command, and memory tools.
Symbol identity — verified from source. find_symbol takes a name_path_pattern. The docstring states: “A name path is a path in the symbol tree within a source file. For example, the method my_method defined in class MyClass would have the name path MyClass/my_method.” Overloads get a 0-based index, MyClass/my_method[0]. Patterns can be a bare name, a relative suffix path class/method, or an absolute path /class/method requiring exact match within the source file.
The critical limitation for durable notes: the name path is file-scoped, not project-global. To identify a symbol uniquely you must pair it with relative_path. So a durable reference is the tuple (relative_path, name_path) — stable against line churn, broken by file moves, exactly like the qualified-name schemes above.
Storage. No graph. A two-tier pickle cache at .serena/cache/<language>/document_symbols_cache_v<version>.pkl, keyed by content hash so edits self-invalidate. serena project index pre-warms it. Cache hits take symbol queries from 100-500ms down to under 10ms.
Local-first: yes, no API key; the client supplies the LLM. Language servers are free and open source. The optional JetBrains backend is a paid plugin.
Known Rust problem worth flagging: issue #1556 (June 2026) reports rust_analyzer.py launching rust-analyzer with VS Code’s full heavyweight initializationOptions hardcoded, eagerly indexing the whole workspace plus all dependencies, producing 40-44 GB resident rust-analyzer processes on large Rust workspaces with no knob to tune it down. On a Mac that is a serious problem.
Verdict: Serena is complementary to a graph, not a substitute. It gives you always-correct, always-fresh, compiler-grade answers with zero index maintenance, at the cost of no persistence, no cross-repo view, no ability to attach metadata, and no offline queryable artifact.
5. Static analysis heavyweights
Joern
github.com/joernio/joern · 3,478 stars · 330 open issues · Apache-2.0 · pushed 2026-09-07 · release v4.0.621 published 2026-09-07 (near-daily releases). Lineage: ShiftLeft → Qwiet AI → acquired by Harness, effective 2025-09-26. No licence change on Joern itself was detected.
Code Property Graph: AST, CFG and PDG merged into one labelled directed multigraph. Query surface is CPGQL, a Scala 3 DSL in a REPL, with joern --server exposing an HTTP /query-sync endpoint. Storage is flatgraph (cpg.fg), a columnar embedded format introduced in 4.0.x that replaced OverflowDB, roughly 40% less memory, with disk-overflow deliberately dropped. Fully offline. Export is the best in the survey: joern-export --repr=all --format={neo4jcsv,graphml,graphson,dot}, so you can load a CPG into your own database. Custom properties are addable via diffgraph passes. Stable identity via METHOD_FULL_NAME / fullName / signature.
Two disqualifiers for this user.
First, the Rust frontend is pre-alpha. Joern’s own language maturity table rates C/C++ and Java Very High, JavaScript High, Python High — and does not list Rust at all. The rust2cpg frontend exists but commit archaeology shows the project started 2026-04-17, with initial AST creation 2026-05-04, and recent work is still foundational: match expressions landed 2026-07-23, if let 2026-08-05, unit-variant enum declarations 2026-08-19, type aliases 2026-09-03. Nearly every commit is by a single contributor. That is roughly five months old and not usable for real Rust work.
Second, full rebuild only. The workflow is importCode / importCpg with joern-scan --overwrite after changes. The CPG spec defines an optional HASH node property “to determine whether code has already been analyzed in incremental analysis pipelines” — a hook, not an implementation. No incremental mode ships. Across many repos on a laptop that is painful.
No official MCP server; third-party bridges exist (BlockSecCA/joern-mcp and others).
CodeQL — legally unusable here
A CodeQL database is not a graph. The QL specification states the data is relational: named relations holding sets of tuples, queried by a stratified-semantics dialect of Datalog. Extractors emit TRAP files that an importer turns into an extensional database. You can express graph traversals in QL, but there is no graph store to export.
Rust support reached public preview 2025-06-30 (CLI 2.22.1) and GA on 2025-10-14 (CLI 2.23.3), build-mode none only.
The disqualifier is the licence. The CodeQL CLI licence permits only academic research, demonstrating the software, testing queries released under an OSI-approved licence, and analysis of an Open Source Codebase. It explicitly prohibits use with “any codebase that is not an Open Source Codebase (e.g., code in a private repo in GitHub)” and prohibits database generation for automated analysis, CI or CD outside that carve-out. The only exception is a paid GitHub Advanced Security licence. A solo developer cannot lawfully run the CodeQL CLI over private repos. Note the split: the queries and libraries at github/codeql are MIT (10,058 stars), but MIT queries are useless without the restricted CLI and extractors. Existing MCP wrappers inherit the same restriction.
ast-grep — an excellent primitive, not a graph
github.com/ast-grep/ast-grep · 15,779 stars · 48 open issues · MIT · pushed 2026-09-06. Official MCP at ast-grep/ast-grep-mcp (459 stars, MIT, tools find_code, find_code_by_rule, dump_syntax_tree, test_match_code_rule).
Its own docs are refreshingly blunt: it “does not have deep semantic information or comparison equivalence. It only operates on the syntactic level,” and “There is no cross-file analysis and no type resolution.” It builds no call graph and no cross-file references. Rust, TypeScript and Python are all first-class. JSON output (--json=pretty|stream|compact) carries text, range with byte offsets and line/column, file, language, metaVariables. Matches are file paths plus byte offsets — no symbol identity, no rename detection.
Use it as the extraction feeder into a graph you build. Never as the graph.
Aider repo-map — good algorithm, dormant project
github.com/Aider-AI/aider · 48,808 stars · 1,854 open issues · Apache-2.0. Verified from aider/repomap.py: an nx.MultiDiGraph whose nodes are files, not symbols, with edges referencer→definer weighted mul * sqrt(num_refs), ranked by nx.pagerank. Identifiers are matched by bare name string with no scoping and no type resolution, so identical names collide across files and languages. Tags are cached in .aider.tags.cache.v3 (a diskcache SQLite keyed by mtime), but the graph itself is never persisted — it is rebuilt in memory on every call.
Health is poor. Last commit 2026-05-22, last GitHub release v0.86.0 on 2025-08-09, PyPI aider-chat 0.86.2 uploaded 2026-02-12. No feature release in about 13 months, no commits in about 3.5 months. Treat as dormant. Tag = (rel_fname, fname, line, name, kind) — no qualified name, no durable ID.
6. Index formats and primitives
SCIP — the best stable-identifier design available
Grammar: <scheme> ' ' <manager> ' ' <package-name> ' ' <version> ' ' (<descriptor>)+, or local <id>. Descriptor suffixes encode kind: / namespace, # type, . term, (…). method, […] type parameter, : meta, ! macro.
Real examples, from the indexers’ own test suites:
# Rust — rust-analyzer crates/rust-analyzer/src/cli/scip.rs
rust-analyzer cargo foo 0.1.0 example_mod/func().
rust-analyzer cargo foo 0.1.0 module/MyTrait#func().
rust-analyzer cargo foo 0.1.0 module/impl#[MyStruct][MyTrait]func().
# TypeScript — scip-typescript snapshots
scip-typescript npm syntax 1.0.0 src/`class.ts`/Class#method().
# Python — scip-python snapshots
scip-python python snapshot-util 0.1 `src.single_class`/ExampleClass#__init__().
Stability. Symbols contain no line or column numbers; positions live only in Occurrence.range. They are therefore completely immune to line-number churn. File moves differ by language: TypeScript embeds the file path and Python the module path, so both break on move; Rust embeds crate plus module path, not file path, so Rust symbols survive file moves as long as the module path holds. The package version is part of the symbol, so a version bump rewrites every symbol for that package — rust-analyzer uses . for workspace-local crates, sidestepping this.
Structure. Document → Occurrence (range, symbol, symbol_roles, enclosing_range) plus SymbolInformation (kind, documentation, signature, enclosing_symbol, and relationships with is_implementation, is_reference, is_definition, is_type_definition). Nodes are symbols; edges are definition/reference/implementation relationships. Call graphs must be derived, not read off.
CLI, verified from docs/CLI.md at v0.10.0: scip lint, scip print --json, scip snapshot, scip test, scip stats, and scip expt-convert — “[EXPERIMENTAL] Convert a SCIP index to a SQLite database” with --output defaulting to index.db. Occurrences are stored opaquely as a blob to keep DB size down. Bindings exist for Go, Rust, TypeScript and Haskell. Apache-2.0, local, offline, no API key.
Indexer maturity, honestly assessed. rust-analyzer scip <path> is shipped and in-tree (--output, --config-path, --exclude-vendored-libraries). But it is rough: the source ships a DUPLICATE_SYMBOLS_MESSAGE warning about “an internal rust-analyzer bug,” references known issues #18771 and #18772, and several tests are labelled “FIXME: This test represents current misbehavior.” A maintainer states “Our SCIP support is pretty limited,” and dependencies require cargo vendor (discussion #21125). scip-typescript and scip-python are not deprecated and not archived, but both went quiet: scip-typescript’s last real commit on main is 2025-10-03 (v0.4.0), scip-python’s last on branch scip is 2025-09-05 (v0.6.5, licence NOASSERTION, a Pyright fork). Repo-level pushed_at dates in 2026 are non-default-branch or bot pushes — do not be fooled by them.
No rename detection. A renamed symbol simply produces a different string; you diff index to index yourself.
universal-ctags
v6.2.0, 2025-11-26. Definitions only, no references, no call graph. But it emits qualified names with --extras=+q and its JSON output separates scope and scopeKind. Cheap, universal, durable. A reasonable floor.
7. Hosted and SaaS — all ruled out on privacy or price
- Sourcegraph / Cody. Cody Free and Pro were killed: signups stopped 2025-06-25, access cut 2025-07-23. Cody Enterprise survives, sales-led. Code Search was relicensed from Apache to proprietary on 2023-06-13 and the main repo made private on 2024-08-22. There is no OSS self-hostable Sourcegraph. Amp spun out as an independent company 2025-12-02.
- Greptile. Claims a repo-wide graph index, though its own security page describes storing vector embeddings of file paths, documentation and AI-generated docstrings — treat the graph framing as marketing. Self-hosting is Enterprise-only; in cloud mode your code passes through their servers. ~1/review beyond 50. GitHub and GitLab only.
- DeepWiki (Cognition/Devin). Hosted MCP at
https://mcp.deepwiki.com/mcpwithread_wiki_structure,read_wiki_contents,ask_question. Public repositories only; private repos require a Devin account and API key. Dead end for private code. The OSS equivalent deepwiki-open (17,900 stars, MIT, pushed 2026-09-03) runs fully offline with Ollama, but builds a RAG embedding index (chunk_size: 350,top_k: 20), not a graph. - Augment Code. Not shut down and not acquired, but it exited the individual-developer segment: completions removed from Indie/Standard on 2026-03-31, those plans retired, IDE extensions sunset around June 2026, cheapest plan now Business at $100/month. Pivoted to Cosmos (2026-06-05). Context engine was always proprietary and cloud. (Trap: “Augment acquires Merlin”, 2026-04-29, refers to goaugment.com, an unrelated supply-chain company.)
- Claude Context (Zilliz). github.com/zilliztech/claude-context, 12,497 stars, MIT, last push 2026-07-14 (about two months stale), 145 open issues. Hybrid BM25 plus dense vector with AST-based chunking. No graph. Rust, TS, Python and Markdown all supported. Incremental via Merkle trees, no published timings. Default quickstart sends chunks to OpenAI embeddings and Zilliz Cloud, but the FAQ confirms a fully local deployment with Milvus in Docker plus Ollama
nomic-embed-text. - Continue. 35,817 stars, Apache-2.0, pushed 2026-09-07.
@Codebaseis deprecated in favour of agent-mode codebase awareness. Embeddings plus keyword search, no graph. Notable for being the most inspectable: LanceDB for vectors plus SQLite at~/.continue/index/index.sqlite, both on your disk, with embeddings computed locally bytransformers.jsusingall-MiniLM-L6-v2and no API key. - GitHub. stack-graphs archived. Precise code navigation appears unshipped; current docs describe only search-based tree-sitter navigation. There is no public REST or GraphQL API for definitions, references or symbols. The Copilot Spaces REST API went GA 2026-05-18 but covers only CRUD on spaces, collaborators and resources; there is no API to ask a question against a Space.
- Sourcebot. 3,930 stars, very active, but not a graph — its Prisma schema has zero code entities and its own docs call find-references a “search heuristic” implemented as
\b{symbolName}\b. Two relicences: root is FSL-1.1-ALv2,ee/is proprietary, and v5.0.0 (2026-06-04) moved code navigation and the MCP server intoee/at $20/user/month. Docker mandatory (three containers). Service Ping every 24h; 7 days without a successful ping downgrades you.
8. Dead, pivoted, or miscategorised
- Nuanced — nuanced-dev/nuanced-py archived 2026-03-05, 128 stars, last commit 2025-06-26. Python-only call graphs, no Rust, no TypeScript. The company pivoted to a macOS desktop app. Do not adopt.
- FalkorDB code-graph — FalkorDB/code-graph, 348 stars, 95 open issues. GitHub’s own About field calls it “A code-graph demo.” Python, Java and C# only — no Rust, no TypeScript. Graph model undocumented beyond a
CALLSedge. The backend repo was archived 2026-03-10. - Potpie — 5,713 stars, Apache-2.0, very active, but v2 abandoned the code-symbol graph entirely. Its ontology doc states that predicates like
CALLSandIMPLEMENTS“do not exist,” all code collapses into a singleCodeAssetlabel, and its ingestion doc says “Scanners are DELETED … no.pysource remains.” There is no source parser; the LLM authors the graph. It is now a decision/SDLC memory graph — excellent at the prose half, absent on the code half. - Neo4j has no official code-graph product.
llm-graph-builderingests documents, not ASTs;neo4j-graphrag-python’s file loader accepts only PDF and Markdown;neo4j-labs/create-context-graphingests Claude Code session logs, not code.neo4j-contrib/mcp-neo4jworks against any bolt URI but its last commit was 2026-04-10. jQAssistant has excellent stable IDs and embeds Neo4j Community, but has no Rust plugin at all, a dead Python plugin, a marginal TypeScript plugin, full rescan only, and no MCP. - Memgraph as a standalone choice: BSL 1.1 plus a commercial Enterprise licence, with
BSL.txtstating it “is not an ‘open source’ license.” No native macOS build, so Docker is mandatory on both Macs; in-memory with roughlyvertices×204B + edges×154Band advice to have double the RAM; on-disk mode still experimental. Fine as code-graph-rag’s hidden backend on a Linux server, poor as a deliberate choice.
9. Answers to the three specific questions
Q1. Which tools produce a stable identifier that survives line-number churn?
Four mechanisms exist, in descending order of durability.
-
SCIP symbol strings — the only design purpose-built for this. No line or column data is in the identifier at all.
rust-analyzer cargo foo 0.1.0 module/MyTrait#func().survives line churn absolutely, and for Rust also survives file moves because the descriptor encodes the crate and module path rather than the file path. TypeScript and Python SCIP symbols embed file/module path and so break on move. Version strings in the symbol are a hazard on published packages, mitigated for workspace-local crates by rust-analyzer’s.placeholder. -
Qualified names in graph tools. code-graph-rag’s
qualified_nameis the strongest implementation: line numbers are appended only on same-name collisions within a module, and the docs state explicitly that a line shift never registers as a changed relationship. codebase-memory-mcp’s<project>.<path_parts>.<name>and GitNexus’s arity-suffixedFunction:path:name#1are equivalent in kind. Joern’sMETHOD_FULL_NAMEandfullNamelikewise. All of these survive line churn and break on file move. -
Serena’s name path, verified from source:
MyClass/my_method, with[0]indices for overloads. It is a path in the symbol tree within a source file, so a durable reference is the pair (relative_path, name_path). Same durability class as the qualified names above, with the advantage that it is computed live by a real language server rather than read from a stale index. -
Failing: CodeGraphContext, whose documented merge key is
(name, path, line_number)— every line shift mints a new node. ast-grep (byte offsets), Aider (Tagincludes a line), and stack-graphs (line:column queries) have no durable identity at all.
Practical recommendation: for markdown notes, reference a function as the pair (repo-relative path, qualified name) and store the SCIP symbol string alongside it where an indexer exists. The path component is the fragile half; keep it in one place so a file move is a single find-and-replace.
Q2. Which tools detect that a referenced symbol was renamed or deleted?
Only one ships real rename detection: code-graph-rag. Its symbols.renamed output pairs a disappeared symbol with an appeared one sharing the same whole-skeleton AST fingerprint in the same file, and dangling_callers lists every affected call site with a renamed_to field. Deletions surface separately as symbols.removed and removed_files. This is exactly the mechanism that keeps external notes from silently rotting.
Partial credit:
- GitNexus has a
detect_changestool that maps git diffs to affected symbols and a graph-assistedrenametool withdry_run, but whether it auto-detects an externally-made rename rather than performing one is UNVERIFIED. - codebase-memory-mcp has
detect_changeswith blast-radius and risk classification, which will tell you a symbol’s neighbourhood changed, but rename-versus-delete disambiguation is not documented. - cognee detects deletions via a stale sweep but explicitly does not track renames — a rename appears as a delete plus an add.
- Serena can perform LSP renames, so if you rename through Serena the language server updates references. It does not detect a rename someone else made.
Everything else fails, and two fail loudly: Graphify’s own troubleshooting docs admit stale nodes linger after deletions and require --force, and SCIP simply emits a different symbol string with no diffing layer, so you would have to write the index-to-index diff yourself.
Practical consequence: if durable note-to-code links matter, you need a periodic reconciliation job that resolves every note’s stored identifier against the current index and reports misses. Only code-graph-rag gives you that for free. For anything else, budget an afternoon to write it — it is roughly “for each note, look up the qualified name; if absent, search for a same-fingerprint symbol; else flag.”
Q3. What is the lightest-weight approach a lazy senior dev would actually run?
Do not adopt a big framework. Potpie deleted its parsers, Joern cannot do Rust and cannot re-index incrementally, Neo4j has no code-graph product, CodeQL is legally barred from your private repos, and Sourcegraph no longer sells you anything self-hostable. The frameworks are the worst answer here.
The honest ranking of effort against payoff:
Tier 0, five minutes, no index at all: Serena. uv tool install serena-agent, add the MCP server, done. You get compiler-grade find_symbol and find_referencing_symbols in Rust, TypeScript and Python with zero index maintenance and nothing to keep fresh across three machines. It answers most of what people actually want a code graph for. The cost is no persistence, no cross-repo view, and no place to hang notes. Watch the rust-analyzer memory issue on large workspaces.
Tier 1, thirty minutes, the pragmatic pick: one embedded-SQLite MCP server plus a sidecar notes table. codebase-memory-mcp is the strongest fit: single static binary, SQLite, no Docker, no API key, a published read-only openCypher subset, stable <project>.<path>.<name> identifiers, TESTS and FILE_CHANGES_WITH edges, and — decisively for a two-Mac-plus-server setup — a zstd-compressed graph artifact committed next to your source with merge=ours, so the graph travels through git instead of being rebuilt three times. Your notes live in your own SQLite table keyed by qualified name, joined at query time. You do not need the tool to support notes; you need it to give you a stable key and an open file format, and it does both.
Tier 2, an afternoon, maximum control: SCIP into SQLite, with your own thin MCP. This is the approach with the best identifiers and the least vendor risk:
rust-analyzer scip . # emits index.scip
scip expt-convert --output index.db index.scipscip expt-convert is real, shipped in CLI v0.10.0, and documented — though flagged EXPERIMENTAL. You then own a SQLite database of symbols and occurrences keyed by strings that never contain a line number, and your markdown notes join to it on the symbol string. Add scip print --json for anything the SQLite schema does not expose. For TypeScript and Python use scip-typescript and scip-python, accepting that both went quiet around September and October 2025. The MCP layer over a SQLite file is a couple of hundred lines. Cost: rust-analyzer’s SCIP output is acknowledged by its own maintainers as limited, and you write your own rename-reconciliation.
Tier 3, only if you want the full graph: code-graph-rag on the Tailscale Linux box. It is the best-engineered tool in the survey — published schema, published re-index benchmarks (194ms p50 for a typical file), deterministic no-LLM navigation tools, explicit rename and delete detection, MIT, and a real Cypher database you can MERGE your own note nodes into. The Docker requirement (Memgraph plus Qdrant) is the price, and your Linux server is exactly the right place to pay it. Both Macs point at one instance. This is the only option that satisfies every requirement in the brief simultaneously, and it is genuinely more setup than the others.
What a lazy senior dev actually does: Tier 0 today, because it costs nothing and removes most of the pain. Then Tier 1 when you find yourself wanting cross-repo memory. Reach for Tier 3 only if note-rot detection turns out to matter enough to justify a container.
10. Recommendation for this setup
Run Serena everywhere as the always-correct symbol layer. Add codebase-memory-mcp for the persistent, git-portable graph across your two Macs and the Linux box. Keep concept notes in your existing Obsidian vault with front-matter that stores (repo, relative path, qualified name) per linked symbol, and write a small reconciliation script that flags dangling references after a pull. If the note-rot problem proves severe, migrate the graph to code-graph-rag on the Tailscale server and let its symbols.renamed and dangling_callers do the reconciliation for you.
Steal one idea from Graphify regardless of whether you adopt it: it is the only tool here that turns wikilinks into graph edges and exports an Obsidian vault. That is the right shape for the code-plus-prose goal, even if the underlying graph is too weakly typed to trust today.
Appendix: verified GitHub metrics, 2026-09-07
| Repo | Stars | Forks | Open issues | Created | Last push | Licence |
|---|---|---|---|---|---|---|
| Graphify-Labs/graphify | 115,470 | 11,212 | 1,264 | 2026-04-03 | 2026-09-05 | Apache-2.0 |
| colbymchenry/codegraph | 69,901 | 4,460 | 531 | 2026-01-18 | 2026-08-31 | MIT |
| abhigyanpatwari/GitNexus | 47,105 | 5,156 | 329 | 2025-08-02 | 2026-09-07 | NOASSERTION (PolyForm NC) |
| DeusData/codebase-memory-mcp | 42,518 | 3,471 | 539 | 2026-02-24 | 2026-09-07 | MIT |
| topoteretes/cognee | 30,545 | 3,008 | 485 | 2023-08-16 | 2026-09-07 | Apache-2.0 |
| oraios/serena | 28,931 | 1,964 | 154 | 2025-03-23 | 2026-09-06 | MIT |
| ast-grep/ast-grep | 15,779 | 439 | 48 | 2022-07-01 | 2026-09-06 | MIT |
| vitali87/code-graph-rag | 5,055 | 661 | 92 | 2025-06-16 | 2026-09-07 | MIT |
| CodeGraphContext/CodeGraphContext | 4,169 | 824 | 96 | 2025-08-16 | 2026-09-06 | MIT |
| kuzudb/kuzu | 4,025 | 518 | 329 | 2020-09-26 | 2025-10-10 | MIT — ARCHIVED |
| joernio/joern | 3,478 | 452 | 330 | 2019-03-14 | 2026-09-07 | Apache-2.0 |
| LadybugDB/ladybug | 1,714 | 135 | 80 | 2025-10-07 | 2026-09-07 | MIT |
| scip-code/scip | 781 | 79 | 11 | 2022-05-10 | 2026-09-03 | Apache-2.0 |
Addendum: trace-mcp, code-graph side evaluated from source
github.com/nikolai-vysotskyi/trace-mcp · 169 stars · 20 forks · 10 open issues · created 2026-04-03 · pushed 2026-09-07 · MIT · TypeScript · single maintainer (Nikolai Vysotskyi).
Evaluated by shallow-cloning the repo and reading source, not the README. Codebase is substantial: 923 TypeScript files, 256,648 LOC in src/, plus 1,012 test files and 189,741 LOC of tests. This is not a weekend project.
(a) How it parses Rust, TypeScript and Python
Tree-sitter WASM, for all three. src/parser/tree-sitter.ts is the single parser factory, using web-tree-sitter with grammars from tree-sitter-wasm. Its LANG_GRAMMARS map declares 32 real grammars, including rust, typescript, tsx and python. The file even documents why WASM was kept over native N-API bindings: native parses about 2.4x faster, but every SyntaxNode property access crosses the N-API boundary at roughly 2x cost, and plugins walk far more than they parse, producing a net ~30% regression.
But the plugin directory holds 79 languages, and the split is stark:
- 28 plugins call
getParserand are genuinely tree-sitter backed, includingrust/,typescript/,python/. - ~48 plugins extend
regex-base.tsorregex-base-v2.tsand are regular expressions, including Haskell, Erlang, F#, PowerShell, SQL, Perl, Julia, Fortran and COBOL.
So the README’s “81 languages” is a count of plugins, not of parsers. For this user it does not matter, because Rust, TypeScript and Python are all in the tree-sitter tier.
Edge types actually produced, per language. The canonical table is SEED_EDGE_TYPES in src/db/schema.ts, holding 193 edge types across 39 categories. The distribution is revealing: nextjs 11, laravel 11, nestjs 9, core 9, mongoose 8, django 8. This project’s centre of gravity is PHP/Laravel and JS/Python web frameworks.
| Language | Dedicated edge types | Call graph resolver | Heritage |
|---|---|---|---|
| Python | 6 (py_imports, py_reexports, py_param_type, py_return_type, py_inherits, py_uses_decorator) | python-calls.ts, 25.2 KB | python-heritage.ts |
| TypeScript | 2 (ts_extends, ts_implements) | typescript-calls.ts, 21.0 KB | heritage.ts, TypeScript-only |
| PHP (for contrast) | 4 plus 11 Laravel plus more | php-calls.ts, 34.2 KB | yes |
| Rust | 0 | none exists | none |
src/indexer/edge-resolvers/ contains php-calls.ts, python-calls.ts and typescript-calls.ts. There is no rust-calls.ts. Rust gets rust-imports.ts only. Reading src/indexer/plugins/language/rust/index.ts confirms it: the plugin pushes 9 symbol kinds and emits exactly one edge type, imports.
Two concrete Rust defects found in source.
-
The trait-implementation edge is dead code. In
rust/index.ts:236-243, the comment reads “Create an edge for trait implementation” and buildsconst _meta = { rustKind: 'impl', trait: traitNode.text }. The variable is underscore-prefixed and never used. No edge is ever created. The trait relationship survives only asmetadata.implTraiton the method symbols, soimpl Display for Foois invisible to any graph traversal. -
Trait methods collide. IDs come from
makeSymbolId(filePath, name, kind, parentName)whereparentNameis the impl’s type, not the trait. Soimpl Display for Foo { fn fmt() }andimpl Debug for Foo { fn fmt() }in one file both producepath::Foo::fmt#method.src/indexer/file-persister.ts:245acknowledges the consequence: “duplicate symbol_ids appear in the same batch — only the last ID survives.” One of the two methods is silently lost. That is idiomatic Rust, not an edge case.
Optional LSP enrichment can fill the call-graph gap, but is off by default. src/lsp/config.ts defines KNOWN_SERVERS including rust-analyzer, detected via Cargo.toml, and src/lsp/enrichment.ts uses callHierarchy/incomingCalls and outgoingCalls to synthesise calls edges. docs/configuration.md:450 states LSP is “Disabled by default — opt-in via configuration.” So out of the box, a Rust repo gets symbols and imports and no call graph at all. Turning LSP on means running rust-analyzer, which reintroduces exactly the memory profile that makes Serena painful on large Rust workspaces.
An unusually honest artifact. src/indexer/edge-resolvers/import-capable-languages.ts documents that the capability matrix “claimed 66 of 81 languages” for imports, but indexing a fixture where every language performs one real cross-file import “originally produced edges for only four: php, python, typescript and vue.” Rust has since gained a resolver. A maintainer who writes that comment into the source is being straight with you; it also confirms the marketing was inflated by an order of magnitude before someone measured it.
(b) Identifier stability
Format confirmed at src/indexer/file-persister.ts:236:
symbolId: s.symbolId || `${relPath}::${s.name}#${s.kind}`with a parent-qualified variant in the per-language helpers: `${filePath}::${parentName}::${name}#${kind}`.
Line churn: survived. The symbols table declares symbol_id TEXT NOT NULL UNIQUE alongside separate byte_start, byte_end, line_start, line_end columns. No positional data is in the key. The README claim holds.
File move: broken, and inconsistently so. src/indexer/rename-detector.ts detects file renames by exact content hash, then calls store.updateFilePath. That function, at src/db/repositories/file-repository.ts:225, runs UPDATE files SET path = ? ... WHERE id = ? and nothing else. It never rewrites symbols.symbol_id, which still embeds the old path. The row now claims a path the identifier contradicts. Note the side effect: decisions bound to that symbol_id keep resolving, so durability survives by accident rather than design. Detection also requires a byte-identical move; move-plus-edit is a delete plus an add.
Symbol rename: not detected. The rename detector operates on files only. A renamed function is a new symbol_id and the old one vanishes.
Reconciliation of decisions against vanished symbols: yes, and it is the best part of the tool. src/memory/decision-verification.ts runs at recall time and returns three verdicts:
ok— symbol resolves and its source slice is unchanged since the decision’screated_atsymbol_missing— the linkedsymbol_idno longer resolvescode_changed— the symbol resolves but its current source differs from the slice at the last commit at or before the decision’s timestamp
The second verdict catches note rot. The third catches something no other tool in this survey catches: a decision whose code still exists but has silently drifted away from what was decided. The module is deliberately fail-open, returning ok when git is unavailable or history is ambiguous, so it will not spam false positives. Its limitation is that symbol_missing collapses deleted, renamed and moved into one verdict, so it tells you a link rotted without telling you where it went.
(c) Incremental re-index
Content-hash skip in src/indexer/pipeline.ts, with an explicit fast path when hashing skips 100% of files. src/indexer/watcher.ts implements a debounced watcher. Indexing depth is tiered: minimal skips LSP enrichment, environment-variable scanning and git history snapshots; none skips edge resolution entirely and stores raw symbols only. Installed hooks include a PostToolUse reindex, so edits made by the agent trigger re-indexing without a separate daemon. No published wall-clock index timings for a mid-size repo were found in the repo; UNVERIFIED.
(d) The 181-tool surface and the install tiers
181 is the registered upper bound, not what you get. docs/configuration.md documents tools.preset defaulting to minimal, which is 28 tools. Other presets: standard 60, dev 42, architecture 41, security 35, perf 34, review 32, design 26, router 10, full everything. The docs describe a preset as “a deferral, not a restriction”: hidden tools stay registered and load_tools pulls them in mid-session. That is progressive disclosure done properly, not tool spam, but the banner headline is misleading. src/tools/ holds 127 non-test modules, so the tools are real implementations rather than thin aliases, though src/tools/tool-families.ts concedes that search, context and impact families contain tools an agent “could plausibly reach for interchangeably.”
Install tiers, defined at src/init/mcp-client.ts:798-802:
base— CLAUDE.md only, no hooksstandard— CLAUDE.md plus hooks:PreToolUseguard,PostToolUsereindex,PreCompact,SessionStart,UserPromptSubmitmax— CLAUDE.md plus hooks plus tweakcc system prompts
The max tier deserves a hard stop. src/init/tweakcc.ts writes routing prompts into the config directory of Piebald-AI/tweakcc, a third-party tool that patches Claude Code’s own system prompt by rewriting the installed CLI. Letting one MCP server invoke a second third-party tool to modify your agent’s system prompt is a large and poorly-bounded trust decision. Use base or standard.
(e) Tests and issue health
Test-to-source ratio is 189,741 test LOC against 256,648 source LOC, about 0.74, across 1,012 test files. That is the strongest test posture of any tool in this survey. Rust has dedicated coverage: tests/languages/rust.test.ts and tests/integration/rust-import-resolution-e2e.test.ts. Only 10 open issues, which given 169 stars likely reflects low usage as much as good hygiene.
The single most recent commit is telling: docs: stop claiming 100% local, and never publish the saving without the quality half (TRA-1013). The maintainer is actively walking back their own marketing. The current state is honest: one anonymous daily usage ping (version, OS, MCP client, aggregate counts, no code, no paths, no IP), opt-out via TRACE_MCP_TELEMETRY=off, with GA4 credentials shipped as plaintext so you can verify the destination. The HTTP daemon is unauthenticated by design and refuses a non-loopback --host without --allow-remote, which matters if you intend to share one index over Tailscale.
(f) Verdict and placement
On the notes-and-decisions side, trace-mcp is the best thing in this survey. It indexes an Obsidian vault into the same SQLite graph as code, with embeds and tagged edge types and wikilink resolution (markdown-wikilinks.ts, markdown-tags.ts). Decisions carry valid_from / valid_until and bind to symbol ids. decision-verification.ts detects both vanished symbols and silently drifted code. Nothing else surveyed does all of that, and symbols.metadata plus a plain SQLite file means you can attach whatever you like.
On the code-graph side, for a Rust-first developer, it is the weakest serious option. By default Rust receives symbols and import edges and nothing else: no calls, no trait implementations, and a dead-code path where the trait edge was supposed to be. Turning on LSP buys a call graph at the cost of a resident rust-analyzer. Two same-named trait methods in one file silently overwrite each other. TypeScript is markedly better (a 21 KB call resolver and heritage), Python better still (six edge types, call resolver, heritage, type edges). The framework catalogue is overwhelmingly Laravel, Next.js, NestJS, Django and Mongoose, none of which a Rust developer will touch.
Tier placement: Tier 1, alongside codebase-memory-mcp, not above it.
- Against codebase-memory-mcp: trace-mcp wins decisively on prose, decisions and rot detection, and on test rigour. It loses on code-graph completeness for Rust, on query surface (codebase-memory-mcp ships a documented openCypher read subset; trace-mcp exposes fixed tools), and on portability (codebase-memory-mcp’s git-committed zstd artifact solves the two-Macs-plus-server problem outright, which nothing in trace-mcp does).
- Against code-graph-rag: trace-mcp is far easier to run, with no Docker. But code-graph-rag treats Rust as first-class with a full call graph, publishes re-index benchmarks, ships genuine symbol-rename detection that names the new symbol, and gives you writable Cypher. For code-graph quality in Rust it is not close.
Should a solo Rust/TS/Python dev pick it as the single tool for code plus vault plus decisions? No, not as the single tool, and the reason is narrow and specific: its Rust code graph is roughly symbols-plus-imports, and one of your three primary languages deserves better than that. Its decision layer, though, is good enough to adopt on its own merits.
The pairing I would actually run: trace-mcp for the vault, decisions and rot detection, since that half is genuinely ahead of the field, plus Serena for live Rust symbol and reference queries where trace-mcp’s graph is thin. If you later want one system to do everything, code-graph-rag on the Tailscale box is the target, and you would rebuild the decision layer on top of its writable Cypher.
Worth reporting upstream if you adopt it: the dead _meta trait edge and the trait-method ID collision are both small, well-isolated fixes in a repo whose maintainer clearly responds to measurement.
Related
- knowledge-graph-research-2026-09-07 — the synthesis this survey feeds; recommended architecture and the reconciler
- memory-graph-survey-2026-09-07 — the concept-layer half of the same question, including trace-mcp’s decision layer
- pkm-survey-2026-09-07 — whether Obsidian stays the substrate
- ruflo-evaluation-2026-09-07 — the one candidate rejected on source-level evidence
- ai-project-brain — the Rust project whose scope this survey narrows