code-graph
A deterministic dependency graph for Laravel, Django, FastAPI, Flask, NestJS, Next.js, Express, Nuxt, Flutter, Rust, C and C++ codebases, so you (and your AI agent) can see everything a change touches before you make it.
Ask "what depends on this table, connection, config key or method?" and get every caller, route, command and page that
reaches it, each hop backed by file:line evidence. It runs locally on your source files, and every answer is exact,
deterministic and reproducible.
Status: beta. Laravel (PHP), Django, FastAPI / Starlette and Flask (Python), TypeScript/JavaScript (Nuxt/Vue, NestJS, Next.js, Express/Fastify/Koa/Hono), Flutter (Dart), Rust, C and C++ are supported natively; other languages can be imported through SCIP. See Limitations.
Quickstart · Demo · What you get · Supported stacks · Prerequisites · AI agents · Docs
Why
Say you need to change how stock is reserved from a second database, the warehouse connection. Four files mention
warehouse by name. code-graph shows you the whole picture before you edit:
- the two public routes,
POST /v1/ordersandPOST /v1/stock/reserve, that reach that code through a service two calls away, in files whose text never mentions "warehouse"; - the change's siblings: add a column in
Admin\BookController::store, and it points you to the separateupdatepath and the admin form that also writebooks(planned changes list these for you); - which callers run on every request and which are a one-off console command;
- the route that touches the warehouse only in a branch a feature flag has already switched off.
code-graph builds the graph from parsers and the type checker (calls, routes, models, tables, columns, config, HTTP calls from the frontend to backend routes), so every answer is a real path you can follow hop by hop:
$ cg reaches connection:warehouse table:warehouse_stock --db out/graph.db --no-paths # abridged
== RUNTIME (reached from http_route / scheduled / queue_job / listener / message_handler): 4 functions/methods
[Http/Controllers]
App\Http\Controllers\OrderController::store depth=3 conf=resolved http_route(1)
App\Http\Controllers\StockController::reserve depth=3 conf=resolved http_route(1)
[Services]
App\Services\StockService::reserve depth=2 conf=resolved http_route(2)
App\Services\StockService::reserveFromWarehouse depth=1 conf=resolved http_route(2)
== OPERATOR-ONLY (artisan_command / admin_panel / cli_command; one-off import & provisioning): 1 functions/methods
[Console/Commands]
App\Console\Commands\SyncWarehouseCommand::handle depth=1 conf=resolved artisan_command(1)
== GATED UNDER SCENARIO 'new_inventory' (dead when the scenario holds; live otherwise): 1 functions/methods
[Http/Controllers/Admin]
App\Http\Controllers\Admin\InventoryController::index gated_target entry-when-off: http_route(1)
Every edge is either exact (syntactically certain), resolved (needed type or name resolution) or heuristic
(clearly labelled fallback), so you know how far to trust each answer.
Quickstart (about 2 minutes, on the bundled sample apps)
The repo ships two small fictional apps: examples/bookstore-api (Laravel) and examples/bookstore-web (Nuxt).
The same bookstore also exists as examples/bookstore-nest, examples/bookstore-next and examples/bookstore-express
(see docs/ts-frameworks.md).
Watch it first: the setup video (MP4, about 80 s) runs these exact steps on a fresh clone, from install to first query, the visual view and connecting an AI agent through MCP.
Prerequisites for this quickstart: Python 3.11+, PHP 8.2+ with Composer 2, and Node.js 20+. Other stacks need other tools; see Prerequisites per language.
Install cg (a user-level tool: no sudo, no checkout needed; docs/install.md for Windows,
options and updates):
curl -fsSL https://raw.githubusercontent.com/cyberchronos00/code-graph/main/install.sh | sh
# or: uv tool install git+https://github.com/cyberchronos00/code-graph
# or: pipx install git+https://github.com/cyberchronos00/code-graph
# or from PyPI: pip install cg-code-graph (uv tool install cg-code-graph / pipx install cg-code-graph)
cg doctor # what indexes exact / heuristic on this machine, and what to install for the rest
git clone https://github.com/cyberchronos00/code-graph.git && cd code-graph # the sample apps used below
Update with uv tool upgrade cg-code-graph, pipx upgrade cg-code-graph or install.sh --update. cg keeps its caches
(extractor installs, SCIP outputs, parse caches) under ~/.cache/codegraph; cg doctor shows their size and
cg clean ROOT, cg clean --stale or cg clean --all removes them (docs/cli.md). Working on cg itself:
CONTRIBUTING.md.
Index both apps and link them into one graph (a few seconds):
mkdir -p out
cg index examples/bookstore-api --name bookstore-api --gates examples/bookstore.gates.json --db out/api.db > out/api.stats.json
cg index examples/bookstore-web --name bookstore-web --db out/web.db > out/web.stats.json
cg link --backend out/api.db --frontend out/web.db \
--backend-name bookstore-api --frontend-name bookstore-web --db out/graph.db > out/link.stats.json
A monorepo lists its apps once in .cg.yaml (apps: [{name: api, root: apps/api, role: backend}, {name: web, root: apps/web, role: frontend}]), and cg index <root> --db out/mono.db indexes and links them in one command
(docs/configuration.md).
Ask it something:
cg reaches connection:warehouse table:warehouse_stock --db out/graph.db # who depends on the warehouse DB?
cg impact StockService::reserve --db out/graph.db # what calls this, from which routes?
cg path page:/reports/:id table:orders --db out/graph.db # frontend page -> DB table, hop by hop
cg resolutions timezone --db out/graph.db # where is "timezone" decided?
cg routes --writes --db out/graph.db # which routes write data, and with which guards?
cg plan check preorders --plans-dir examples/plans --db out/graph.db # what does this planned change miss?
The same bookstore in other stacks. Each sample indexes on its own; PHP is only needed for Laravel and Node only for the TypeScript stacks:
cg index examples/bookstore-nest --name bookstore-nest --db out/nest.db > out/nest.stats.json # NestJS
cg index examples/bookstore-next --name bookstore-next --db out/next.db > out/next.stats.json # Next.js
cg link --backend out/nest.db --frontend out/next.db --backend-name bookstore-nest \
--frontend-name bookstore-next --db out/nn.db > out/nn.stats.json
cg api-calls all --db out/nn.db # every client call with its matched Nest route and handler
cg index examples/bookstore-django --name bookstore-django --db out/django.db > out/django.stats.json # Django
cg routes --writes --unguarded --db out/django.db # routes that write data without an auth guard
cg index examples/rust-kvstore --gates examples/native.gates.json --db out/kv.db > out/kv.stats.json # Rust
cg reaches kv_core::store::Store::get --db out/kv.db # dyn/generic dispatch, grouped RUNTIME / LIBRARY API / DEV
cg downstream kv::main --db out/kv.db # env keys, unsafe, features and cfgs the binary touches
examples/bookstore-express (Express), examples/bookstore-flutter (Flutter, links to the Django sample), examples/bookstore-android (Kotlin / Compose, links to the Django sample), examples/bookstore-ios (SwiftUI, links to the Django sample),
examples/c-ringbuf (C) and examples/cpp-eventbus (C++) work the same way. Rust, C and C++ index in exact mode when
rust-analyzer or scip-clang is installed, and in a labelled heuristic mode otherwise; C/C++ setup, including the
compile database, is in docs/native.md.
scripts/reproduce.sh runs the same steps end to end (into out/graph.db), and also writes HTML views and (if Chrome is installed)
screenshots to out/.
Demo
Five short videos on the bundled sample apps; everything they show is also written out as text in this README.
-
Setup (MP4, about 80 s): a fresh clone, the quickstart install, the first index of both apps, a first
impactquery,cg servefor the visual view, and a Cursormcp.jsonentry that connects your AI agent. -
Terminal demo (MP4, about 60 s): index and link both apps, then
reacheson the warehouse connection (runtime, operator-only and gated groups),pathfrom a Nuxt page to a column,impactandplan check. -
Visual view demo (MP4, about 40 s):
cg servein a browser. Search for the warehouse connection, select a node to highlight its evidence paths and open its source, then switch to the planned-change overlay for thepreordersplan and inspect two items it still needs to cover. -
AI agent over MCP (MP4, about 195 s): a live Cursor CLI agent with cg connected. It finds every route that writes data without auth in one
routescall, catches what thepreordersplan leaves out before any code is written, then fixes all of it in the same chat (guards every unprotected write route and implements the plan including what it missed), re-indexes and re-checks with the graph, withgit diff --statat the end. Part of that last run is shown at 6× speed, marked on screen. Before the plan question it shows the plan itself, examples/plans/preorders.yaml. Nothing is replayed: the answers stream in live from the model. -
The same agent without code-graph (MP4, about 285 s): the same model and the same three tasks in a fresh copy with no MCP server, using its built-in search and file reading, in one live take. It ends with the comparison card.
All five are scripted, so they can be re-recorded: scripts/demo/record-setup.sh, scripts/demo/record-terminal.sh
and scripts/demo/record-agent.sh (record-agent.sh baseline for the take without code-graph; vhs tapes), and
scripts/demo/record-view.sh (Playwright).
Comparison (same agent, Cursor CLI with GPT-5.4 Mini at medium reasoning; results checked against the code):
| task | with code-graph | without code-graph |
|---|---|---|
| 1. Security review: write routes without auth (3 in the code) | 12 s, 1 tool call, 46.4k in / 894 out tokens; found 3 of 3 | 40 s, 39 tool calls, 205.7k in / 4.6k out; found 3 of 3 |
2. Plan check: gaps in preorders.yaml (7 in the code) |
13 s, 1 tool call, 52.6k in / 1.1k out; found 7 of 7, plus the failed auth:api requirement |
26 s, 6 tool calls, 50.6k in / 3.6k out; found 3 of 7 (admin update path, UpdateBookRequest, mobile client) |
| 3. Fix everything: guard the routes, implement the plan, verify | 228 s, 56 tool calls (10 cg), 2,071.5k in / 23.5k out; routes guarded 3 of 3; gaps fixed 7 of 7 (6 in code, the mobile client recorded in the plan since the client is not in the copy); re-indexed, plan_check 0 missing, verify OK |
119 s, 50 tool calls, 1,127.1k in / 14.6k out; routes guarded 3 of 3; gaps fixed 4 of 7 (not the Filament form, the customer on the POST /v1/orders path, the mobile client); verified by re-reading the route file |
| total | 253 s, 58 tool calls, 2,170.5k in / 25.5k out | 185 s, 95 tool calls, 1,383.4k in / 22.8k out |
Time, tool calls and tokens come from the Cursor CLI's own usage report (input tokens include cached input); one live
take per side, so the numbers vary between runs. Results were checked against the code afterwards: both copies pass
php -l and re-index after task 3, and both also put POST /v1/stock/reserve behind auth:api as the plan requires.
The 7 plan gaps are the admin update path, UpdateBookRequest, Book::$fillable, the API and Filament
BookResource, the POST /v1/orders path (the customer passed to reserve) and the mobile client.
What you get
1. A CLI for impact questions
reaches, impact, downstream, path, writers, siblings, routes, search, api-calls, channels, tests
and platforms all work on one SQLite graph, and across repos once the frontend and backend are linked. Every hop shows its evidence:
$ cg path page:/reports/:id table:orders --db out/graph.db
page:app/pages/reports/[id].vue
-CALLS[resolved @ bookstore-web/app/pages/reports/[id].vue:10]-> function:app/composables/useReports.ts#useReports.fetchTop
-HTTP_CALLS[resolved @ bookstore-web/app/composables/useReports.ts:9]-> http:GET /api/v1/main/admin/reports/top
-MATCHES_ROUTE[resolved @ bookstore-api/routes/api.php:11]-> route:GET /v1/{store}/admin/reports/top
-ROUTES_TO[exact @ bookstore-api/routes/api.php:11]-> method:App\Http\Controllers\ReportController::top
-CALLS[resolved @ bookstore-api/app/Http/Controllers/ReportController.php:21]-> method:App\Services\SalesReportService::report
-CALLS[exact @ bookstore-api/app/Services/SalesReportService.php:12]-> method:App\Services\SalesReportService::build
-READS_COLUMN[resolved @ bookstore-api/app/Services/SalesReportService.php:19]-> column:orders.placed_at
It also traces values. resolutions <concept> finds every place a value is picked through a fallback chain, shows
where the chains disagree, and checks whether the frontend actually sends the key:
$ cg resolutions timezone --db out/graph.db # abridged
[A] input:timezone > column:orders.customer_timezone > setting:locale.timezone > column:stores.default_timezone > 'UTC'
[B] input:timezone > setting:reports.timezone > 'UTC'
[A] vs [B]: same up to input:timezone; then [A] column:orders.customer_timezone vs [B] setting:reports.timezone
GET /api/v1/main/admin/reports/top -> chain B
=> 'timezone': never sent (builder key is conditional and no call site passes it)
When a page passes a key that its request helper never puts on the request, path and resolutions point it out:
note: sent but not forwarded: date_from (passed @ bookstore-web/app/pages/reports/[id].vue:10; the request built @ bookstore-web/app/composables/useReports.ts:9 sends only category_id, mode, timezone)
routes lists every route that reaches a write, a table or any other node, together with its middleware, guards and
auth checks (Laravel middleware, Nest guards, Express middleware, Next.js middleware.ts, django-ninja auth=,
Django and DRF access checks, FastAPI Depends() / Security() dependencies, Flask view decorators):
$ cg routes --writes --db out/graph.db --no-paths # abridged
routes reaching a write (any table): 4 of 9 routes
auth guard: 1 with, 3 without (auth = a framework preset auth guard or a name matching the auth pattern)
auth guards by source: preset laravel 1
DELETE /v1/{store}/admin/reports/{report} @bookstore-api/routes/api.php:14 NO AUTH
guards: (none)
writes orders via Services\SalesReportService::remove conf=resolved
called from: page:app/pages/index.vue @index.vue:4
POST /v1/orders @bookstore-api/routes/api.php:21
guards: auth:api [auth]
writes books via Services\StockService::recordSale conf=resolved
Add --unguarded for the routes without an auth-like guard, or --missing auth:api for the routes without one
specific guard. Routes whose only check is a shared secret or signature (webhook signature middleware, Laravel
signed URLs) are shown as SECRET-CHECKED rather than NO AUTH. When a query comes back empty, the answer says why
and suggests the next query to run.
channels answers who may join a broadcast channel, what publishes on it and which client code listens, and tests
lists the tests that exercise a symbol, route or table (test code never counts as a caller in the other queries):
$ cg channels board.42 --no-source --db out/graph.db # abridged
== channel board.{board} [private] @ backend/routes/channels.php:22
WHO CAN JOIN
auth route route:POST /api/broadcasting/auth middleware=['api', 'auth:sanctum'] (from ->withBroadcasting bootstrap/app.php:7)
channel class App\Broadcasting\BoardChannel (join())
CALLS Support\BoardAccess::visibleBoardIds @ backend/app/Broadcasting/BoardChannel.php:12 [exact]
PUBLISHED BY (1)
event:App\Events\TaskMoved name=board.{board_id} [private] broadcastOn @ app/Events/TaskMoved.php:22
dispatched by Http\Controllers\TaskController::move @ backend/app/Http/Controllers/TaskController.php:22 http_route(1)
LISTENED TO BY (1)
board.{boardId} [private] events: TaskMoved (exact)
subscribed in useBoardRealtime (useBoardRealtime.ts) @ frontend/app/composables/useBoardRealtime.ts:5
pages: page:app/pages/boards/[id].vue
$ cg tests 'PATCH /api/tasks/{task}/move' --no-paths --db out/graph.db
targets: 1 node(s): route:PATCH /tasks/{task}/move
tests: 2 direct, 0 nearby transitive (app depth <= 3) (of 10 test cases in the graph: phpunit 4, pest 3, playwright 2, vitest 1)
== DIRECT (the test code itself calls / requests the target): 2
TaskMoveTest::test_moving_a_task_updates_its_state [phpunit] backend/tests/Feature/TaskMoveTest.php:17 depth=2 conf=exact
board page > moving a task through the API [playwright] frontend/e2e/board.spec.ts:9 depth=2 conf=resolved
Details: docs/channels-and-tests.md.
Code that ships to several targets is tagged per target. --platform shows one target's build, and platforms divergence finds the gaps between per-platform implementations:
$ cg impact open_logs --platform windows --db out/app.db
platform: windows (4 nodes and 13 references not built for it left out; 0 conditions could not be evaluated for it, the code under them stays in)
open_logs is not built for windows: nothing calls it there (...)
$ cg platforms divergence --db out/app.db # abridged
== REFERENCED WHERE THE CALLEE IS NOT BUILT: 1
function:dirs_demo::main -CALLS-> function:dirs_demo::open_logs @ src/main.rs:18 missing on: windows, macos (callee: linux, cfg(target_os = "linux"))
Details: docs/platforms.md.
Port gaps between two platform apps (an iOS app and its Android port): cg parity --db ios.db --against android.db
lists the types, functions, enum cases and constants with no counterpart (docs/parity.md).
Full reference: docs/cli.md · value facts: docs/value-facts.md
2. An MCP server for AI agents
The same queries as MCP tools (reaches, impact, callers, siblings, path, downstream, routes, search, api_calls,
channels, tests_covering, resolutions, plan_check, index, coverage, starters, platform_divergence, …), so an agent can check the blast radius before it edits. Replies are compact,
use repo-relative paths, and plan_check starts with a summary (details=true for the full report). Every reply also
carries a machine-readable completeness object, so the agent knows when an answer covers the whole repository and
where to fall back to text search when it does not (docs/completeness.md). It runs locally
over stdio:
> impact(method="StockService::reserve")
transitive callers: 2; entry points: 2
## http_route (2)
POST /v1/orders ROUTES_TO@api.php:21 → CALLS@OrderController.php:17~r → Services\StockService::reserve
POST /v1/stock/reserve ROUTES_TO@api.php:19 → CALLS@StockController.php:16~r → Services\StockService::reserve
Setup: Using it with an AI agent · all tools: docs/mcp.md
3. A visual view
cg serve --db out/graph.db --plans-dir examples/plans starts a local, read-only web view at
http://127.0.0.1:8177/. It draws the same query results as a graph grouped by repo and module. Click a node to see
its source snippet and evidence edges. cg viz-export … writes the same view as a single HTML file that opens from
disk.
More: docs/viz.md
4. A planned-change layer
Write the agreed scope of a change as a small YAML plan: new columns, methods to modify, forbidden paths, required
middleware. plan check compares it with the real graph and lists what the plan forgot, before anyone writes code:
$ cg plan check preorders --plans-dir examples/plans --db out/graph.db # abridged
summary: refs 15/15 resolve | MISSING FROM PLAN 10 | review 7 | covered 7 | forbidden paths present 1 | open findings touching 2 (unlinked 1) | requirements failed 1
require POST /v1/stock/reserve [auth:api]: MISSING auth:api
- [admin_surface] Filament\Resources\BookResource::form: Filament form for Book; saves bypass the graph's WRITES edges
- [model_fillable] Book::$fillable lacks preorder_until (mass assignment would drop it)
- [table_writer] Admin\BookController::update: writes books (price, stock, title); must set/keep new preorder_until
- [external_client] client:example/bookstore-mobile/pages/cart.vue: POST /stock/reserve -> POST /v1/stock/reserve
forbid no-warehouse-for-preorders: path STILL PRESENT
After you implement and re-index, plan check --verify confirms that the planned nodes and edges now exist, the
forbidden paths are gone or guarded, and the requirements are met.
Workflow: Planned changes · schema and checks: docs/plans.md
Supported languages and frameworks
Every edge carries a confidence: exact (the parser or compiler saw it), resolved (needed type or name resolution)
or heuristic (a labelled name-based fallback). The mode column says where each stack gets its references from.
Each detected framework also applies its preset (the auth guards it ships, its skip lists), so the stacks below work
without configuration; cg config show lists what was applied (docs/configuration.md).
| language / framework | mode | what is modelled |
|---|---|---|
| PHP | exact + resolved (nikic/php-parser, type inference) | classes, methods, calls with type inference, properties (reads / writes of declared and promoted properties, docs/php.md), interfaces, traits |
| Laravel | exact + resolved | routes + middleware, Eloquent models → tables/columns, migrations, DB connections, config/env, commands, scheduler, jobs, events/listeners, container bindings, FormRequests, settings reads, broadcast channels (auth callbacks, broadcastOn(), the auth route), PHPUnit / Pest tests |
| Filament | resolved | admin panels as entry points, resource $model binding |
| TypeScript / Vue | exact + resolved (TypeScript checker, Vue SFC compiler) | modules, functions, components, template usage, HTTP calls (fetch, $fetch, axios, ofetch / ky instances) with base URLs from runtime config and env, Laravel Echo / pusher-js channel subscriptions, Vitest / Jest / Playwright / Cypress tests |
| Nuxt | exact + resolved | file-based page routes, layouts, auto-imports, global components, Pinia stores, i18n keys; source at the root, app/ or src/; clean checkouts without .nuxt |
| NestJS | exact + resolved | modules, controllers + routes (global prefix, URI versioning, RouterModule), DI (class / @Inject tokens, useClass/useExisting/useFactory/useValue), guards / interceptors / pipes, DTO fields, GraphQL resolvers, @Cron/@Interval, Bull/BullMQ, @OnEvent, microservice and WebSocket handlers, nest-commander (operator), TypeORM / Mongoose / Prisma / Kysely tables, ConfigService / env |
| Next.js | exact + resolved | app router (pages, layouts, route.ts handlers, dynamic / catch-all segments, route groups, parallel / intercepting routes), pages router + pages/api, server actions, middleware.ts matchers, basePath / rewrites, env incl. NEXT_PUBLIC_*, in-repo client → handler links |
| Express, Fastify, Koa, Hono | exact + resolved | routes, router mounting chains across files (use, register({prefix}), route, basePath), route and router-level middleware, Fastify schemas |
| JavaScript (CommonJS / ESM) | resolved (TS checker with allowJs) |
the same extractor; resolution follows what the checker infers |
| Python | resolved (stdlib ast, import resolution, type inference) + heuristic fallback |
modules, classes, functions, calls; source roots detected from the layout (src/, lib/, packaging config, several package roots, namespace packages, nested projects) or set in .cg.yaml; entry points (__main__ blocks, python -m pkg, console scripts and plugin entry points from pyproject.toml / Poetry / setup.cfg / setup.py, MCP tools, click / typer commands); functions used as values (dispatch tables, plugin lists, callbacks, registering decorators) and calls through them; pytest / unittest tests with fixtures, parametrize and HTTP test clients linked to routes, tests that run the project's CLI in a subprocess (python -m, -c, script paths, console scripts, through CLI helpers) linked to its entry point (docs/python.md) |
| Django | resolved + heuristic fallback | urls.py (path/re_path/include/namespaces, app_name), class/function views, view access checks (login_required, permission decorators, access mixins), models → tables/columns/relations, ORM reads/writes, settings/env (os.environ, getenv, django-environ), signals, management commands, admin |
| django-ninja | resolved | NinjaAPI/Router/add_router prefixes, operations with path params, auth=, request/response Schema and ModelSchema fields |
| Django REST Framework | resolved | routers, ViewSets (+ @action), APIView/generic views, permission_classes, serializer fields |
| FastAPI / Starlette | resolved | FastAPI / APIRouter / Starlette / Router objects, include_router / mount prefix chains across files (prefixes from constants and settings attributes such as settings.API_V1_STR), verb decorators, api_route, add_api_route, Starlette routes=[Route, Mount, WebSocketRoute], websockets, path parameters, Depends() / Security() dependencies (parameters, Annotated aliases, dependencies=) as route access with what each checks (raised 401 / 403, security schemes, nested dependencies), name= for url_path_for; apps / routers received as parameters or fixtures or returned by factories, Starlette Host, @cbv / InferringRouter, classy-fastapi Routable |
| Flask | resolved | Flask / Blueprint objects, register_blueprint (url_prefix from the blueprint or the registration, nested blueprints), @route / verb decorators, add_url_rule incl. MethodView.as_view() (and its methods), endpoint-only rules, werkzeug Rule / Submount, the built-in static route, views defined in an app factory, apps received as parameters or pytest fixtures, flask-restful / flask-restx resources, subdomain= / defaults=, <int:id> parameters, blueprint.endpoint names for url_for, view decorators (login_required) as route access |
| Celery / Channels | resolved | tasks + .delay/.apply_async dispatches; websocket routing to consumers |
| Dart | resolved (package:analyzer parse, declared types) + heuristic fallback | libraries/parts, classes, methods, functions, calls with import resolution |
| Flutter | resolved + heuristic fallback | widgets/State, bloc/cubit events → handlers → states → UI, Navigator/go_router/auto_route pages, HTTP calls (package:http, Dio, dart:io, Retrofit/Chopper), WebSockets, json_serializable/freezed and hand-written JSON keys |
| Rust | exact with rust-analyzer (SCIP); heuristic without | crates, modules, pub API, traits → impls (dyn/generic dispatch), bins, tests, benches, examples, build.rs, FFI, unsafe, #[cfg(feature)] gates, env keys, #[tokio::main], axum/actix routes (docs/native.md) |
| Kotlin | heuristic (tree-sitter-kotlin); exact calls with a scip-java index | classes, objects, functions / extension functions, calls by name or compiler-resolved (scip-java); Ktor (incl. type-safe resources) and Spring routes with guards (SecurityFilterChain rules too), Spring Data / Exposed table access, Retrofit / Ktor client / OkHttp endpoints, Compose Navigation (typed, Navigation 3) pages, AndroidManifest components and deep links, workers, KMP source sets and expect / actual (docs/kotlin.md) |
| Swift | heuristic (tree-sitter-swift, no Xcode needed); exact calls from the compiler's index store (swift build, Xcode DerivedData) |
classes, structs, enums, actors, protocols, extensions, calls by name or compiler-resolved; Vapor routes with groups and guards, Fluent models / migrations / queries as tables, URLSession / Alamofire / Moya TargetType endpoints, SwiftUI / UIKit navigation pages, @main / app-delegate / background-task entries, #if os(...) / canImport(...) platform tags (docs/swift.md) |
| C | exact with scip-clang + compile_commands.json; heuristic without |
translation units, includes, main and test entry points, exported API, #if gates, getenv keys, macros (docs/native.md) |
| C++ | exact with scip-clang + compile_commands.json; heuristic without |
the C facts plus namespaces, classes, overloads, virtual dispatch (overrides and implementations) (docs/native.md) |
| Frontend → backend | resolved, or heuristic for suffix-only matches | client HTTP calls (fetch, axios, $fetch/ofetch, ky, SWR, OpenAPI-generated clients, Dart clients) matched to Laravel, Django, Nest, Next and Express routes (link), plus a request/response field check |
| Go, Java | via SCIP (experimental) | definitions and references imported from an existing SCIP index |
| Platform-specific code | Rust #[cfg] / cfg!, C / C++ #if and platform paths, Dart Platform.isX / kIsWeb / conditional imports, React Native Platform.OS / Platform.select / .ios.ts files |
every symbol and reference carries the targets it is built for; --platform ios views one target's build; cg platforms divergence lists variants that leave a target uncovered, API differences and calls into code a target does not build (docs/platforms.md) |
| Web / native bridges | Capacitor plugins, React Native / Expo native modules, Flutter method and event channels, Pigeon APIs | each JS / Dart call (and each native invokeMethod / Pigeon @FlutterApi call into Dart) linked through a shared endpoint:<protocol>:<module>#<method> node to its Kotlin, Java, Swift or Objective-C receiver per platform; cg bridges lists methods missing on a platform, without a receiver or implemented outside the repo; impact / tests / --platform cross the bridge (docs/bridges.md) |
| Desktop processes | Electron ipcMain / ipcRenderer / webContents.send and contextBridge.exposeInMainWorld; Tauri invoke → #[tauri::command] |
endpoint:electron-ipc:<channel>, endpoint:electron-preload:<key>#<member>, endpoint:tauri:<command> with SENDS_TO / RECEIVED_BY across the processes, process roles (main / preload / renderer, webview / core) on module nodes; checks for channels nobody receives and unregistered commands (docs/bridges.md) |
| AI harnesses | MCP servers (FastMCP / MCPServer / low-level) and clients, OpenAI / Anthropic tool schemas, Agents SDK, LangChain, LlamaIndex tools, hand-written agent loops (Python) | endpoint:llm_tool:<name> / endpoint:mcp_tool:<server>/<name> (resources, prompts) with the handler as an llm_tool entry, agent:<name> with OFFERS_TOOL / HANDS_OFF_TO; cg tools lists handlers, tables reached, agents, no_receiver / no_sender, dynamic dispatch and model calls (docs/ai-tools.md) |
| External systems | Laravel database connections, env keys read by code (DB_*, DATABASE_URL, REDIS_*, SMTP_*, MONGO*, AMQP_*, LDAP_*, SFTP_*, S3_* ...), Python settings (DATABASES, CACHES, CELERY_BROKER_URL ...), .env.example values, docker-compose services, DSNs |
external:<protocol>:<host:port> (or env:<KEY>) with CONNECTS_TO from code and connections, CONFIGURED_BY, CREDENTIAL_FROM (location only, never the value), TLS; one node per system across linked repos; cg external (docs/external.md) |
| Protocol links | HTTP calls / routes, Pusher channels, NestJS messages, Bull / Laravel / Celery jobs, application events, bridges and IPC in one view; python-socketio / Flask-SocketIO events | endpoint:<protocol>:<name> with SENDS_TO / RECEIVED_BY and MATCHES_ENDPOINT (path, MQTT, NATS, AMQP topic, glob and template matchers in a registry plugins extend); cg protocols lists senders, receivers, guards and the checks no_receiver, no_sender, ambiguous, schema_mismatch, unguarded; .cg.yaml protocols.external for known outside parties (docs/protocols.md) |
| Generated and copied files | detected (.gitattributes, generator banners, framework build paths, generator file names, Capacitor / Cordova copy targets, .openapi-generator/FILES) |
kept out of the graph and listed by cg coverage by reason; copies map back to their source; --include-generated indexes them labelled attrs.generated (docs/generated.md) |
Prerequisites per language
Python 3.11+ (tested with 3.11 and 3.13) runs the indexer, CLI and MCP server for every stack. Each language adds:
| language | you need | install |
|---|---|---|
| all | Python packages (tree-sitter grammars included) | installed with cg (install.sh, uv tool install, pipx install) |
| PHP / Laravel | PHP 8.2+ (tested 8.4), Composer 2 | extractor packages install into the user cache on the first index, or cg setup php |
| TypeScript / JavaScript (Nuxt, Vue, NestJS, Next.js, Express, Fastify, Koa, Hono) | Node.js 20+ (tested 20.19), npm | on the first index, or cg setup typescript |
| Python / Django | nothing extra (stdlib ast) |
— |
| Dart / Flutter | Dart SDK 3.x (tested 3.13); the target project needs no pub get |
dart on PATH or $DART; the extractor's packages are fetched on first use |
| Rust | rust-analyzer for exact mode (any 2024+ release) | rustup component add rust-analyzer (install.sh --with rust) |
| C / C++ | scip-clang 0.4+ and a compile_commands.json for exact mode |
install.sh --with c; compile database: docs/native.md |
| Kotlin | a JDK and scip-java 0.12 (Kotlin ≤ 2.1 builds) / 0.13 (2.2.0 - 2.2.10) for exact mode | install.sh --with kotlin (both); opt-in: docs/kotlin.md |
| Swift | a Swift toolchain (5.9+, Linux or Xcode) for exact mode | install.sh --with swift; opt-in: docs/swift.md |
| Go, Java | an existing SCIP index | cg index <root> --scip index.scip |
How it works
flowchart LR
subgraph backend[Laravel repo]
P[PHP extractor<br/>nikic/php-parser] --> LP[PHP + Laravel plugins]
end
subgraph frontend[Nuxt repo]
T[TS extractor<br/>TypeScript checker + Vue SFC] --> NP[TS + Nuxt plugins]
end
LP --> A[(api.db)]
NP --> W[(web.db)]
A --> K[link<br/>HTTP calls ↔ routes]
W --> K
K --> G[(graph.db)]
G --> CLI[CLI]
G --> MCP[MCP server]
G --> VIZ[visual view]
PL[plans/*.yaml] --> CHK[plan check]
G --> CHK
- Extract. A PHP process and a Node process each parse the whole project once and emit JSON facts. They work purely from source files, so indexing is safe and side-effect free on any checkout.
- Resolve. Language plugins build symbol tables and resolve calls through types. Framework plugins add what
the framework implies (a route points to a controller, a model maps to a table,
->where('col')reads a column). - Gate (optional). With a gates file, branches that a feature-flag scenario switches off are marked, and the edges inside them are kept and tagged as gated.
- Store. Nodes and edges go into SQLite, and each edge keeps its
file:lineand confidence. Entry points (routes, commands, jobs, listeners, pages) are tagged so every result can say who reaches it. - Query. Recursive walks over the edges that propagate dependency, with the shortest evidence path rebuilt for each result.
Details: docs/architecture.md · schema: docs/schema.md
Using it with an AI agent
Add the server to your MCP host. Most hosts, Cursor and Claude Desktop among them, accept an mcpServers entry.
cg-mcp is installed next to cg; replace /path/to/code-graph with the directory holding the graph:
{
"mcpServers": {
"code-graph": {
"command": "cg-mcp",
"args": ["--db", "out/graph.db",
"--gates", "examples/bookstore.gates.json", "--plans", "examples/plans"],
"cwd": "/path/to/code-graph"
}
}
}
A workflow that works well:
- Before editing, the agent calls
reacheson the table, column, connection or config key it plans to touch, andimpact/siblingson the methods. It now has the full list of callers, entry points and parallel code paths, with evidence. - For a non-trivial change, it writes a plan (
plans/<name>.yaml) and runsplan_check. It then resolves each missing from plan item by adding it to the plan, marking it covered, or marking it out of scope with a reason. - After editing, it calls
indexto rebuild the graph and runsplan_check(verify=true). - In its summary, it quotes the
file:lineevidence so a reviewer can check the claims.
A ready-to-paste rules snippet is in docs/mcp.md, and the Cursor CLI
setup (.cursor/cli.json, agent mcp enable, print mode) is in docs/mcp.md. When a
language is not covered (see coverage), or an answer could miss a route or handler registered in a way cg does not
model (a blind spot, with its file:line), the reply says so and tells the agent to fall back to its normal search
exactly there.
To see an agent at work, watch the agent demo (MP4).
Planned changes
- Write
plans/<name>.yamlwith the agreed scope, linked issues, forbidden paths and requirements. - Run
cg plan validate <name>, thencg plan check <name>(add--plans-dir DIRif your plans live elsewhere). Work through MISSING FROM PLAN and link any unlinked findings. - Run
cg plan baseline <name>. This records a hash of each target's current source. - Implement, then re-index (
cg index+cg link, or the MCPindextool). - Run
cg plan check <name> --verify. It reportsverify_okwhen the planned nodes, edges, forbidden paths and requirements all check out, and lists any completeness gaps still open.
cg viz-plan <name> (or the plan overlay mode in serve) draws the plan on top of the real graph: planned items
in green, gaps in magenta, forbidden paths in red. See docs/plans.md.
Configuration
code-graph indexes a project with zero configuration. The optional inputs are:
-
Gate scenarios (
index --gates FILE): name a feature-flag state, e.g. "features.new_inventory.enabledis true", and code that this state switches off is reported in its own gated group, apart from live code:{"scenarios": [{"name": "new_inventory", "setting_accessors": ["getSetting"], "true_settings": ["features.new_inventory.enabled"], "false_settings": []}]}
-
Viz presets (
serve --presets FILE): a JSON list of canned queries for the landing page's starter cards,{id, label, mode, specs[, sinks]}. Without one, the landing page offers starter queries derived from your graph (a write route without an auth guard, the busiest tables, connections and env keys, the most-called functions;cg starters). -
Plans directory (
--plans-dir DIR; MCP server:--plans DIR). The default isplans/. -
Framework presets are picked by detection: each detected framework brings its curated auth guards (Laravel, Django, DRF, django-ninja, NestJS, Next.js, Express / Fastify / Koa / Hono, Nuxt) and every language its skip lists, so
routes --unguardedis accurate out of the box.cg indexrecords the detected frameworks and applied presets. -
Project config file (
.cg.yamlat the indexed root, read automatically) records project knowledge once:excludeglobs,includedirectories, extra or keptskip_dirs, monorepoapps(indexed and linked in one command),frameworksto add or remove,auth/secretpatterns for your own guards,generatedrules,platformstargets,gates,plansandviz.presets, andpython.source_roots.cg config showprints every effective value with where it comes from, andcg config validatechecks the file:exclude: ["legacy/**"] frameworks: {remove: [flutter]} auth: {extra_patterns: ["requireTenantMember"]} plans: {dir: docs/plans}
Everything else (environment variables, screenshot tooling): docs/configuration.md
Limitations
The scope as of v0.3, so you know how far each answer reaches. The full list is in docs/limitations.md.
- Coverage and completeness.
cg indexrecords each language's parser mode (exact,heuristic,skippedwhen a toolchain such asphpornodeis missing), how many of its files are indexed (parse failures, files outside the source roots, files over the size limit), unsupported source types (by extension or#!line) and blind spots: route and handler registrations cg does not model (a NestJS decorator wrapped byapplyDecorators, Django URL patterns built by a function, routes registered in a loop, functions registered through a decorator or a registry). Route lists, caller lists and the other impact answers say when they could be partial and where to look; complete answers stay short. A missing indexer never fails the whole index. See docs/completeness.md. - Generated and copied files (build output, generated clients, Capacitor / Cordova web copies) stay out of the
graph and are listed by
cg coverage;--include-generatedindexes them, labelled. Generated code without a marker, path rule or.gitattributesentry is indexed as source;generated.pathsin.cg.yamlnames it. See docs/generated.md. - Static analysis. Types are flow-insensitive, and generics are outside the current scope. When a receiver has no
resolved type, a unique-method-name fallback fills the gap and is labelled
heuristic. - String-built names (dynamic table, column or URL names) become placeholders such as
{param}ortenant_{store.id}, or stay unresolved. - Python modules are named from the detected or configured source roots; files whose path is not an importable
name (
my-scripts/run.py) are listed asunmappedbycg coverage. See docs/python.md. - Python/Dart are parsed, not type-checked. Calls through untyped parameters,
**kwargs, dynamic dispatch (getattr, DI containers, Riverpod/Provider lookups without a type) fall back toheuristicor stay unresolved. Python dispatch tables, plugin lists, callbacks and registering decorators are followed as function references (docs/python.md). GraphQL APIs (graphene/strawberry) and Django template rendering are not modelled. - Next to index: seeders,
Artisan::commandclosures, observers fired by model writes, Nuxt server routes, and navigation edges (NuxtLink,navigateTo). - Broadcast channels are read from
Broadcast::channelandbroadcastOn(); names cg cannot evaluate keep a{?}segment, and Livewire Echo listeners are not client subscriptions. A channel's checks are the calls its callback makes. See docs/channels-and-tests.md. - Tests are found by naming conventions (pytest's own settings for Python) and never count as callers. Transitive test paths are static, so a browser test that stubs the API still reaches the backend through the page it opens. Python HTTP test requests link to Django, DRF, django-ninja, FastAPI, Starlette and Flask routes.
- Base URLs from runtime config and env are folded into endpoint paths when the value is in the repo (
nuxt.configdefaults,.env,.env.example,||defaults in code); values set only at deploy time stay an unknown origin. A Nuxt checkout without.nuxtis indexed with generated stand-ins for its own auto-imports and components. - TS frameworks:
linkcompares method and path for Nest/Express routes. Nest providers are global (one module scope). Express middleware order is tracked within one file. Monorepos list their apps in.cg.yamlapps(docs/configuration.md). Details: docs/ts-frameworks.md. - Gate scenarios cover one scenario per index and flags read through settings accessors. Paths behind middleware gates or flags stored in properties are reported as live, which keeps results conservative.
- Rust / C / C++: exact mode uses rust-analyzer or scip-clang (and, for C/C++, a compile database) and reflects
one build configuration: inactive
#ifbranches and macro-generated items get nodes, and their references come from heuristic mode; calls into Rust items gated for other targets are added from the syntax layer. Heuristic mode covers about half of the calls in generic or template-heavy code. See docs/limitations.md. - Platform conditions are evaluated per target from the source text; conditions on feature flags, build macros
or API levels count as unknown and keep their code in every target's view (
cg platformslists them). Swift#if os()/canImportblocks and Kotlin Multiplatform source sets /expect/actualare tagged by their plugins, targets come fromPackage.swift/ thekotlin { }block; Electron / Tauri IPC crosses processes through endpoints. See docs/platforms.md. - Route guards come from route definitions and global enhancers (Nest
APP_GUARD/useGlobal*, Expressapp.use). Whether a guard counts as auth is decided by the framework preset, then by its name; project guards are added withauth.extra_patternsin.cg.yamlor--auth-pattern. Details: docs/limitations.md. - Sent but not forwarded keys are found for call sites that pass an object literal to a request helper whose request keys are statically known (one call level).
- Plans use a fixed set of named completeness rules. Verify mode checks the graph; free-text intent is for the reviewer to judge.
- The visual view is comfortable up to a few hundred nodes. It is a local tool without auth: keep it on
127.0.0.1, or share aviz-exportfile.
Roadmap
Ideas we are exploring after v0.3. Feedback on priorities is welcome.
- Packaging: prebuilt extractor deps.
- Nuxt server routes (Nitro) and navigation edges.
- Payload checks in
link(Nest DTO / Fastify schema fields against client request keys), and Nest module scoping. - Laravel: seeders, closure commands, and observers triggered by model writes; Livewire Echo listeners as channel subscriptions.
- Multiple gate scenarios per index, and middleware-level gates.
- Tested SCIP recipes for Go and Java.
- Rust/C/C++: macro-expanded items, function-pointer dataflow, Bazel and Meson autodetection.
- Route guards: Laravel kernel middleware groups and controller-constructor middleware, Django's
MIDDLEWAREsetting and DRFDEFAULT_PERMISSION_CLASSESshown on each route. - Completeness: per-file reports for TypeScript / JavaScript, more blind-spot detectors (Express routers passed
through containers, Nest
SetMetadata-based job and event systems), and acknowledging known blind spots in a project config file. - Electron
MessagePort/utilityProcessand Tauri events (emit/listen) between processes. - Swift:
URLComponentsand helper-built URLs,Info.plist/.xcconfigbase URLs, OS-version conditions, App Intents / widget entries, value navigation through variables (docs/swift.md). - Protocol links: extraction for MQTT, NATS, AMQP, Kafka and Redis pub/sub (matchers registered), Socket.IO outside Python, raw WebSocket / SSE message names, gRPC / GraphQL / webhooks (epic #29; docs/protocols.md).
- AI harnesses: TypeScript MCP servers / clients and the Vercel AI SDK, LangGraph graphs, agent runners, tools declared inside functions (docs/ai-tools.md).
- External systems: client constructors with literal arguments (
psycopg.connect(host=),new Redis(), ...), Spring / Rails / Kubernetes configuration, env reads through config schemas (docs/external.md). - Web / native bridges beyond Capacitor, React Native, Flutter channels and Pigeon: React Native events, Capacitor
notifyListeners, Cordova plugins and native UI components (docs/bridges.md). - More HTTP clients beyond fetch, axios, ofetch and ky, and response-field modelling for the TypeScript client (setting → API response → client state); GraphQL APIs.
Documentation
| doc | contents |
|---|---|
| docs/install.md | install, update and uninstall (install.sh, install.ps1, uv, pipx), extractor dependencies, cg doctor |
| docs/cli.md | every command and option, query target syntax |
| docs/mcp.md | MCP tools, client config, agent instructions |
| docs/architecture.md | invariants, codemap, plugin interface, how queries, the TS/Nuxt plugin and link work |
| docs/schema.md | SQLite tables, node kinds, edge kinds, confidence, entry kinds |
| docs/native.md | Rust, C and C++: install, compile database, modes, facts, entry kinds, query specs, gates, env vars, validation numbers |
| docs/ts-frameworks.md | NestJS, Next.js and Express-style layers, validation on public projects |
| docs/python.md | Python source roots (detection, module names, .cg.yaml / --python-root, coverage output), entry points and function references, pytest / unittest tests, validation |
| docs/value-facts.md | request keys, settings, fallback chains, resolutions |
| docs/channels-and-tests.md | broadcast channels (channels) and test coverage (tests: PHPUnit, Pest, Vitest, Jest, Playwright, Cypress, pytest, unittest, Swift Testing, XCTest, JUnit / kotlin.test) |
| docs/plans.md | plan schema, every check, verify mode, overlay legend |
| docs/viz.md | visual view and static export |
| docs/configuration.md | project config file (.cg.yaml, cg config show), framework presets, gates, viz presets and starter queries, plans dir, environment variables |
| docs/ai-tools.md | AI harnesses: LLM tools, MCP servers / clients, agents, cg tools |
| docs/external.md | external systems: model, sources, target rules, secrets policy, cg external |
| docs/protocols.md | protocol links: endpoint model, registry and matchers, adapted kinds, checks, cg protocols, Socket.IO |
| docs/bridges.md | web / native bridges: endpoint model, supported registration forms, checks, cg bridges |
| docs/platforms.md | platform-specific code: targets, recognised conditions and variants, --platform, cg platforms divergence, .cg.yaml platforms |
| docs/generated.md | generated, copied and vendored files: detection rules, coverage output, --include-generated, COPY_OF, .cg.yaml generated |
| docs/completeness.md | file completeness, unsupported source types, blind-spot detectors, notes on partial answers, the MCP completeness object |
| docs/limitations.md | all known gaps |
| docs/validation.md | results on public projects: Django, Flutter, presets / starter queries / route guards per framework, and generated-file detection |
| CHANGELOG.md | changes per release, and what is coming in the next one |
| CONTRIBUTING.md | dev setup, running the 281 tests, adding a plugin |
| docs/mcp/sample_outputs.md | raw output of every MCP tool on the sample apps |
| docs/media/ | demo videos: setup, terminal, visual view, AI agent over MCP, without code-graph (recording scripts in scripts/demo/) |
Contributing
Issues and pull requests are welcome. See CONTRIBUTING.md for the dev setup, how to run the
tests, how to add a language or framework plugin, and what a PR needs. In short: tests pass, edges stay
deterministic, and every new edge carries file:line evidence and an honest confidence level.
To report a security issue, please use GitHub's private vulnerability reporting; see SECURITY.md.
License
MIT. Vendored third-party files keep their own licenses: Cytoscape.js, cytoscape-fcose, cose-base and
layout-base are MIT (codegraph/viz/static/vendor/VERSIONS.txt).
Metadata
Release files for cg-code-graph 0.10.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| cg_code_graph-0.10.1.tar.gz | 1.4 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| cg_code_graph-0.10.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 2.6 MB
Release files / cg_code_graph-0.10.1.tar.gz
| Download URL | cg_code_graph-0.10.1.tar.gz |
|---|---|
| Size | 1.4 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a5fd106c902f1178719b9f806687449eeb698fa6f9e93fd38dbd6306ac18e099
|
|
BLAKE2b-256 checksum How to use checksums |
899c80b19bedf2017a271314224dbcf663fb9617d1572130a4ff146d894b9e49
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.
Transparency logRelease files / cg_code_graph-0.10.1-py3-none-any.whl
| Download URL | cg_code_graph-0.10.1-py3-none-any.whl |
|---|---|
| Size | 1.2 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d64cbd23f7b96b284491fdd0e3f45da18bd589d6919ac6ef4b3aacb1d4f86320
|
|
BLAKE2b-256 checksum How to use checksums |
d61999c50e58fcadb1dcfbde37dfc6aa770d5e2ccf7f45248a685f73475959da
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.
Transparency log