Skip to main content
Glama

code-graph

A static connection graph of the Example platform. It scans app-web (Laravel + React) and app-services (NestJS + Go + Python) and records how the code is wired together — which files call which HTTP routes, which tables and queues they read or write, and what they import — into an embedded Kùzu graph database you can query from the terminal, from Claude Code (MCP), or with raw Cypher.

It answers the cross-repo reachability questions that are otherwise a multi-file grep-and-guess exercise:

  • Who actually calls GET /resource-a?

  • What code touches the table_b table?

  • How does the web frontend reach a backend service?

Quickstart

uv sync                                    # install (Python 3.11–3.13)
uv run code-graph scan                     # build graph.kuzu from the sibling repos
uv run code-graph stats                    # node + per-edge-type counts
uv run code-graph query "callers:/resource-a"  # who calls a route

By default scan looks for app-web and app-services as siblings of this project (../app-web, ../app-services) and reads route data from ../route-inventory/dist/openapi.json. Point it at your own repos with env vars instead of editing the code:

export CODE_GRAPH_REPOS="/path/to/web-repo,/path/to/services-repo"   # or name=path pairs
export CODE_GRAPH_OPENAPI="/path/to/route-inventory/dist/openapi.json"

Missing repos are skipped, not errored. The scan is a static snapshot — re-run it after any change you want reflected. A full scan of two large repos takes roughly 15–20 minutes.

Each scan writes two gitignored artifacts at the project root: graph.kuzu (the database) and parser-report.txt (scan counts).

Related MCP server: woodpecker-mcp

The three interfaces

CLI

uv run code-graph scan
uv run code-graph stats
uv run code-graph query "<preset-or-cypher>"
uv run code-graph export --format html|graphml|dot

query takes one argument. If it looks like <preset>:<arg> it runs a preset; otherwise it runs as raw Cypher.

Preset

Finds

callers:<route>

files that issue an HTTP call to a matching route

calls:<node>

outbound edges from matching nodes

touches:<table|queue>

code that reads/writes a table or uses a queue

neighbors:<node>

one-hop neighbours of matching nodes

search:<text>

nodes whose id/name contains the text

export writes graph.{html,graphml,dot} (all gitignored, regenerated on demand). graph.html is a self-contained offline viewer; GraphML/DOT are for external graph tools.

MCP (in Claude Code)

CODE_GRAPH_DB=/home/ohad/sh/projects/code-graph/graph.kuzu uv run code-graph-mcp

Exposes the query layer over stdio as seven read-only tools: find_callers, what_calls, path_between, who_touches, neighbors, search_nodes, run_cypher. The server refuses to start until graph.kuzu exists — run scan first.

Raw Cypher

The CLI query command (when the argument isn't a preset) and the MCP run_cypher tool run arbitrary Cypher against the model below. The database is opened read-only at the Kùzu engine level, and run_cypher additionally rejects write/DDL keywords before executing — the graph cannot be mutated through either path.

uv run code-graph query "MATCH (f)-[c:HTTP_CALL]->(r) WHERE r.kind='route' RETURN f.id, r.id LIMIT 20"

Graph model

One Node table (discriminated by kind) and one relationship table per edge type — the idiomatic Kùzu shape, so filtering is a kind predicate and every traversal is a single relationship type.

Node kind

Represents

Example id

file

a source file

file:app-web/app-frontend/src/components/callRecord/StatusCheck.jsx

route

an HTTP route (seeded from route-inventory)

route:service-b:GET:/system/health-check-full

db_table

a database table

db_table:table_b

queue

a Redis stream / channel

queue:service-c-stream

package

an imported package/module

package:@nestjs/common

external

an HTTP call that didn't resolve to a known route

external:https://example.com/x

Edge type

Meaning

confidence

HTTP_CALL

file → route it calls (or external if unresolved)

0.8 resolved / 0.5 external

DB_ACCESS

file → table it reads/writes (Laravel $table/DB::table, Kysely)

0.7

QUEUE_PUB / QUEUE_SUB

file → queue/stream it publishes/consumes

0.6

IMPORTS

file → package or file it imports

0.9

Every edge carries source_file, line, and raw (the matched snippet), plus mode (read/write/unknown) on DB_ACCESS. confidence describes how the match was made, not a statistical accuracy — treat low-confidence and external edges as leads to verify against source_file:line, not ground truth.

How it works

  • Parsing. tree-sitter plus targeted regex over PHP, TS/TSX, JS/JSX, Go, and Python. No type resolution or control-flow analysis, and deliberately recall-biased — it favours catching real edges over avoiding every false positive.

  • Routes come from route-inventory. Route nodes aren't discovered by parsing; they're loaded from route-inventory's generated dist/openapi.json. HTTP_CALL edges resolve a call's (METHOD, path) against that catalog (with a suffix-match fallback and same-origin tie-break); unmatched calls become external nodes.

  • What's scanned. Both repos are walked recursively for the source extensions above, excluding node_modules, vendor, dist, build, .git, .claude (covers .claude/worktrees), .next, coverage. Symlinked directories are followed once, guarded by resolved real path.

Example output

From a scan of the real app-web + app-services trees (2026-07-16):

4248 files scanned → 10108 nodes, 0 parse errors
  nodes:  file 6653 · package 1594 · route 1559 · db_table 261 · external 41
  edges:  IMPORTS 21212 · DB_ACCESS 794 · HTTP_CALL 189 · QUEUE 0   (22195 total)
$ uv run code-graph query "callers:/system/health-check-full"
[
  {
    "caller": "file:app-web/app-frontend/src/components/callRecord/StatusCheck.jsx",
    "route": "route:service-b:GET:/system/health-check-full",
    "line": 168,
    "confidence": 0.8
  }
]

IMPORTS dwarfs the other edge types by design — one edge per import statement across thousands of files, versus per-call-site matches elsewhere.

Known limitations

  • Minified build bundles add noise. app-web/public/js/* hashed Vite/webpack output is currently scanned, producing some spurious HTTP_CALL edges and file nodes. Excluding build output at the walker level is a planned follow-up.

  • Relative imports aren't resolved. A relative specifier (./x, ../y) becomes a literal file: node id rather than being resolved to the real file it points at, so a share of file nodes are synthetic import targets. Bare package imports resolve correctly.

  • DB_ACCESS.mode is unknown for PHP. Only the TS/Kysely path (selectFrom/insertInto/…) infers read vs. write; Laravel $table/ DB::table hits have no per-call-site signal, so mode is left unknown rather than guessed.

  • Queues need string literals. QUEUE_* edges are only created for string-literal stream/channel names; Kafka-style subscribe(variable) calls aren't named. (QUEUE = 0 on the current tree is accurate, not a miss.)

Development

uv run pytest        # 42 tests
src/code_graph/
  cli.py            CLI entry point (scan / stats / query / export)
  mcp_server.py     MCP entry point (code-graph-mcp)
  config.py         ScanConfig: sibling-repo paths, exclude list, db path
  model.py          Node/Edge dataclasses + stable id helpers
  catalog.py        RouteCatalog: loads route-inventory OpenAPI, resolves calls
  walker.py         source-file walker (language detection, excludes, symlink guard)
  parsing.py        tree-sitter parse cache + AST helpers
  matchers/         HTTP_CALL / DB_ACCESS / QUEUE_PUB+SUB / IMPORTS extraction
  graph/
    loader.py       Kùzu schema + CSV bulk loader
    queries.py      GraphDB: read-only presets + guarded raw-Cypher escape hatch
  scan.py           orchestrates walker → matchers → loader, writes parser-report.txt
  report.py         parser-report.txt writer
skill/SKILL.md      Claude Code skill definition
docs/superpowers/   design spec + implementation plan

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    F
    maintenance
    Provides a local code knowledge graph for Java projects, enabling querying of classes, methods, fields, calls, inheritance, and imports via MCP tools like query, context, impact, and cypher.
    1
    -