@dev.fast/code-graph
v0.1.15
Published
Semantic code graph extraction for TypeScript repos and broad source-language code graph extraction for JavaScript, Python, Go, Rust, Java, C/C++, Ruby, Kotlin, and Swift. The dev.fast monorepo is the TypeScript regression target, but the extractor, daemo
Readme
@dev.fast/code-graph
Semantic code graph extraction for TypeScript repos and broad source-language
code graph extraction for JavaScript, Python, Go, Rust, Java, C/C++, Ruby,
Kotlin, and Swift. The dev.fast monorepo is the TypeScript regression
target, but the extractor, daemon, editor, and call-tree UI run through a
root-aware plugin path that can target external repos with --root.
Quick start
# Refresh the SQLite-backed code graph (~30s, needs 8GB heap)
pnpm --filter @dev.fast/code-graph refresh
# Or target another repo/language
pnpm --filter @dev.fast/code-graph refresh -- --root /path/to/typescript/repo
pnpm --filter @dev.fast/code-graph refresh -- --root /path/to/python/repo --languages python
# Start the singleton daemon and UIs
pnpm --filter @dev.fast/code-graph daemonrefresh extracts into a temporary SQLite database, publishes it atomically to
.code-graph/state/graph.sqlite, and asks the daemon to drop any open graph
connection around the swap. It does not need Docker or a long-lived graph DB
process.
Querying SQLite
Query via bash:
sqlite3 .code-graph/state/graph.sqlite \
"SELECT label, count(*) FROM nodes GROUP BY label ORDER BY 2 DESC;"Node types
| Label | Count | Description |
| ----------------- | ----- | ---------------------------------------------------------------------------- |
| Project | 34 | Each app/package/integration |
| File | ~740 | TypeScript source files |
| Declaration | ~7900 | Functions, classes, interfaces, types, enums, variables, methods, properties |
| ExternalPackage | ~100 | npm dependencies (not walked internally) |
| RpcService | 14 | ConnectRPC service definitions from proto |
| RpcMethod | 99 | Individual RPC methods |
Edge types
| Type | Count | Meaning |
| ---------------- | ----- | ----------------------------------------------- |
| CALLS | ~9000 | Function/method directly calls another |
| COMPOSES | varies | Declaration structurally composes another declaration, such as JSX component usage |
| REGISTERS_CALLBACK | varies | Declaration registers or passes callable behavior, such as a JSX event callback prop |
| DECLARES | ~6200 | File declares a symbol |
| IMPORTS_SYMBOL | ~6100 | File imports a named symbol |
| IMPORTS | ~2900 | File imports from another file |
| READS | ~2500 | Function reads a module-level variable |
| CONTAINS | ~2400 | Project contains file, or class contains method |
| PARAM_TYPE | ~1100 | Function parameter references a type |
| REEXPORTS | ~900 | Barrel re-exports |
| RETURNS_TYPE | ~700 | Function return type references a type |
| PROPERTY_TYPE | ~160 | Property type annotation |
| INSTANTIATES | ~160 | new Foo(), targeting a constructor declaration when one exists |
| WRITES | ~160 | Function writes a module-level variable |
| IMPLEMENTS_RPC | ~114 | Server handler implements an RPC method |
| DEFINES_METHOD | 99 | RpcService contains RpcMethod |
| EXTENDS | 50 | Class/interface extends |
| IMPLEMENTS | 22 | Class implements interface |
| CALLS_RPC | 9 | Client calls an RPC method (cross-service) |
Useful queries
-- Most-called functions
SELECT target.id, count(*) AS c
FROM edges e
JOIN nodes target ON target.id = e.to_id
WHERE e.type = 'CALLS' AND target.label = 'Declaration'
GROUP BY target.id
ORDER BY c DESC
LIMIT 15;
-- Cross-project import dependencies
SELECT a.project, b.project, count(*) AS cnt
FROM edges e
JOIN nodes a ON a.id = e.from_id AND a.label = 'File'
JOIN nodes b ON b.id = e.to_id AND b.label = 'File'
WHERE e.type = 'IMPORTS' AND a.project <> b.project
GROUP BY a.project, b.project
ORDER BY cnt DESC
LIMIT 15;
-- Class hierarchy
SELECT child.name, parent.name
FROM edges e
JOIN nodes child ON child.id = e.from_id
JOIN nodes parent ON parent.id = e.to_id
WHERE e.type = 'EXTENDS';
-- Who implements a specific interface?
SELECT impl.name, impl.file
FROM edges e
JOIN nodes impl ON impl.id = e.from_id
JOIN nodes iface ON iface.id = e.to_id
WHERE e.type = 'IMPLEMENTS' AND iface.name = 'SandboxClient';
-- Full RPC trace: caller -> proto method -> server handler
SELECT caller.file, method.name, handler.file
FROM edges call
JOIN nodes caller ON caller.id = call.from_id
JOIN nodes method ON method.id = call.to_id
JOIN edges impl ON impl.type = 'IMPLEMENTS_RPC' AND impl.to_id = method.id
JOIN nodes handler ON handler.id = impl.from_id
WHERE call.type = 'CALLS_RPC';
-- What does a function read and write?
SELECT e.type, variable.name
FROM edges e
JOIN nodes fn ON fn.id = e.from_id
JOIN nodes variable ON variable.id = e.to_id
WHERE fn.name = 'initOtel' AND e.type IN ('READS', 'WRITES');
-- Files with most declarations
SELECT file.id, count(*) AS decls
FROM edges e
JOIN nodes file ON file.id = e.from_id AND file.label = 'File'
JOIN nodes decl ON decl.id = e.to_id AND decl.label = 'Declaration'
WHERE e.type = 'DECLARES'
GROUP BY file.id
ORDER BY decls DESC
LIMIT 10;Language Plugins
The installed plugins are:
typescript: TypeScript Compiler API extraction with declarations, imports, hierarchy, type edges, references/calls, JSX composition, RPC descriptors, and call hierarchy enrichment.javascript,python,go,rust,java,cpp,ruby,kotlin,swift: source-language extraction with project/file discovery, declarations, structural folds, imports, hierarchy edges, calls, and instantiation edges. JavaScript/JSX/MJS/CJS uses the TypeScript parser for declarations, ES/CommonJS/dynamic imports, reexports, local target resolution, direct and receiver/member calls,newexpressions, module/class reads and writes, and provenance/confidence metadata on AST-backed reference edges. Python uses a stdlibasthelper forpyproject.toml/setup.*project metadata, package/module resolution through relative imports and__init__.py, declarations, imports/reexports, direct and receiver/member calls, instantiation, inheritance, annotation type edges, state reads/writes, structural folds, and provenance/confidence metadata on resolved local edges. Go uses a stdlibgo/parser/go/typeshelper forgo.mod/go.workprojects, generated-file detection, public API declarations, local package imports, direct/cross-file calls, selector/member calls, composite literals andnewinstantiation, embedding/interface implementation edges, return/parameter/field type edges, package/type state reads and writes, folds, and provenance/confidence metadata on resolved local edges. Rust uses Cargo metadata plus a localsynparser helper for Cargo project/crate roots, generated-file detection,mod/use/pub useresolution, public API declarations, direct and cross-file calls, associated function and practical receiver method calls, struct construction, trait conformance, signature/field type edges, static/const reads and writes, folds, and conservative provenance/confidence metadata. Java and Kotlin use a JVM source parser adapter for Maven/Gradle project roots, package/import indexing, generated-file detection, public declarations, direct/cross-file calls, practical receiver/member calls, constructor calls, inheritance/interface conformance, return/parameter/property type edges, static/class-level reads and writes, structural folds, and conservative provenance/confidence metadata. The adapter intentionally leaves ambiguous or unresolved symbols unlinked; it does not require localjavac/Kotlin compiler installations, so compiler-only overload and classpath semantics remain medium-confidence gaps. C/C++ prefers localclang/clang++JSON AST extraction for CMake, Make, and compile database roots, with local include resolution, public/header API declarations, direct/cross-file and receiver member calls, construction, inheritance, signature/field type edges, state read/write edges, folds, and provenance/confidence metadata. Header/member declarations and conservative state/call edges have text fallbacks when clang omits source ownership or a local compiler is unavailable. Ruby uses stdlibRipperfor Gemfile/gemspec project discovery,require/require_relativelocal resolution, declarations, hierarchy and mixin edges, direct and receiver/member calls,Class.newinstantiation, class/module/global state reads and writes, folds, and conservative provenance/confidence metadata for resolved local edges. Swift additionally discovers Swift Package/Xcode roots, resolves local package modules, marks generated files, records public API declarations, hierarchy/protocol conformance, typed member calls, constructor calls, return/parameter/property type edges, state read/write edges, structural folds, and provenance/confidence metadata whenswiftc -dump-parseis available.
Enable one or more plugins with --languages or CODE_GRAPH_LANGUAGES:
pnpm --filter @dev.fast/code-graph extract -- --root /tmp/requests --languages python
CODE_GRAPH_LANGUAGES=go pnpm --filter @dev.fast/code-graph extract -- --root /tmp/muxReal-world smoke coverage is repeatable with:
pnpm --filter @dev.fast/code-graph run validate:source-languages
pnpm --filter @dev.fast/code-graph run validate:source-languages -- --languages python,goThe validation script clones representative public repos into
/tmp/code-graph-hairy when missing, extracts each non-TypeScript language,
emits Cypher to /tmp/code-graph-hairy-output/<language>, and enforces semantic
coverage minimums so silent no-op or declaration-only extraction fails loudly.
Use --languages or CODE_GRAPH_VALIDATE_LANGUAGES for targeted validator
runs while iterating.
Canvas whiteboard
The canvas is a blank React Flow app that an agent draws on via MCP tools. The intended workflow is: query the SQLite graph store to understand the code, then throw nodes/edges onto the canvas to explain architecture visually.
Setup
# Start the canvas React app
pnpm --filter @dev.fast/code-graph canvas # http://localhost:5555
# Register the MCP server with Claude Code
claude mcp add code-graph-canvas -- pnpm --filter @dev.fast/code-graph mcpMCP tools
| Tool | Args | Description |
| -------------- | ---------------------------- | ------------------------------------------------- |
| add_node | id, label, sublabel? | Add a card to the canvas |
| remove_node | id | Remove a node and its edges |
| add_edge | source, target, label? | Connect two nodes (directed, with optional label) |
| remove_edge | source, target | Disconnect two nodes |
| clear_canvas | | Wipe everything |
Nodes are auto-positioned by dagre (left-to-right layout). Edges show direction arrows and optional labels. Click a node to highlight its connections.
Tips
- The canvas and graph are not linked. You query SQLite via bash, then manually add the interesting parts to the canvas.
- Use
sublabelfor context like file paths or descriptions. - Use
labelon edges for relationship semantics like "calls", "implements", "imports". - Multiple diagrams can coexist on the same canvas (dagre lays out connected components separately).
- To fully reset, call
clear_canvas.
Architecture notes
- Target context is shared across CLI, daemon, refresh, editor/source loading, and call-tree flows. By default each target stores local state under
.code-graph/{state,output,work}at the target repo root. - Graph store is a local SQLite database at
.code-graph/state/graph.sqlite. Refresh writes a complete replacement database under.code-graph/workfirst, then atomically renames it into place. - Extraction runs through installed language plugins. TypeScript uses the TypeScript Compiler API (
ts.Program+ts.TypeChecker) per project, plusts.LanguageServicefor call hierarchy enrichment. Source-language plugins use language-specific project markers, declaration forms, import forms, inheritance syntax, and call/instantiation patterns while emitting the same graph node and edge model. The shared plugin boundary keeps project identity language-neutral and stores language-only data, such astsconfigPath, in each plugin's project config. - RPC extraction is two-pass: first pass extracts proto service descriptors from
*_pb.tsfiles, second pass matchesrouter.service()implementations andcreateClient()call sites. - Memory: projects are loaded sequentially (one
ts.Programat a time) but all kept alive for the RPC second pass. Needs--max-old-space-size=8192. - Historical spike:
~/monorepo/repos/code-graphremains a read-only reference for future Go/Python/SCIP plugin ideas; this package does not import that implementation.
