Validation log
Per-corpus tables behind How we validate. Shallow clones, default branch, October 2026. "Unchanged corpora" notes stay here.
The Python/Django and Dart/Flutter plugins, Laravel broadcasting and test indexing, the Nuxt layout
handling, the framework presets with their starter queries and route-guard classification, and generated-file detection were checked against shallow clones of well-known open-source projects
(default branch, October 2026). Numbers are from python -m codegraph.cli index <repo> on a single 8-core Linux box;
wall time includes the Dart extractor (facts cache cold) but not the one-off dart compile exe of the extractor.
"Spot check" means: 20 routes (or every route when there are fewer) sampled at random from the graph and compared by
hand with the source line they cite: HTTP method, full path including every include()/add_router()/router prefix,
path parameters, and the handler symbol.
Django / Python
| Project | What it exercises | .py files parsed | Index | Nodes / edges | Routes | Spot check | Parse failures |
|---|---|---|---|---|---|---|---|
| django-ninja (repo incl. docs) | NinjaAPI, Router, add_router, path params, auth, schemas | 183 / 185 | 1.7 s | 2,269 / 5,877 | 300 (33 mounted through urls; the rest are doc/test snippet routers, emitted with mounted=0) | 20/20 | 0 |
| Django-Styleguide-Example | APIView-based services, nested includes, Celery | 162 / 190 | 0.5 s | 806 / 1,440 | 22 | 20/20 | 0 |
| channels-examples | Channels URLRouter, consumers, function views | 25 / 28 | 0.1 s | 93 / 109 | 7 (2 websocket) | 6/6 paths; 2 handlers are Django's own auth views (external) | 0 |
| paperless-ngx | DRF routers, ViewSets, @action (incl. regex url_path), Channels, Celery, signals | 353 / 407 | 10 s | 7,672 / 31,332 | 223 | 20/20 paths; 3 handlers external (RedirectView, allauth) | 0 |
| netbox | large DRF API, plugin URL registries | 980 / 1,290 | 29 s | 23,917 / 112,393 | 956 | 20/20 | 0 |
| wagtail | admin viewsets, hooks, many include() levels | 1,058 / 1,324 | 29 s | 22,395 / 69,911 | 195 | 20/20 paths; 5 handlers unresolved (viewset registry as_view, external LoginView) | 0 |
| saleor | GraphQL-first API | 2,867 / 4,335 | 68 s | 30,521 / 134,683 | 9 | n/a (GraphQL root fields are protocol endpoints, see #34 below) | 0 |
The difference between files and parsed files is migrations (skipped by default; python_include_migrations turns
them on) and files over the size cap. No file failed ast.parse.
Gaps seen:
- netbox: the API prefix comes from an f-string over
settings.BASE_PATH, rendered as{?}; views registered throughregister_model_view()/get_model_urls()are partly unresolved. - wagtail:
page_viewset_registry/ hook-generated URL lists resolve paths but not always the handler. - saleor: one GraphQL endpoint; resolvers/mutations are not routes.
Python source-root detection (src/ and lib/ layouts, monorepos, namespace packages) is validated on
fastapi/full-stack-fastapi-template, opentelemetry-python, ansible, flask, pytest and netbox: see
validation. Test indexing (pytest, unittest, fixtures, Django / DRF test clients) is validated on
the same projects: see pytest and unittest.
tests.py modules and collection chains, checked on django (211 tests.py files), netbox, saleor, ansible,
opentelemetry-python, mkdocs, flake8, httpie, beets, sphinx and pylint: every graph is identical before and after (same
nodes, edges and attributes; index time within noise, for example django 50.5 s -> 52.2 s, saleor 62.8 s -> 62.7 s).
The Django tests.py files hold test cases and stay test code, and no collection call gained or lost an edge. On cg's
own source, the two plugin modules named tests.py that application code imports became application code (136
nodes, 148 test edges back to calls), and index_project gained the 9 calls through its framework and language plugin
lists (every framework plugin's contribute, the SCIP indexer plugins' index), all correct.
Flutter / Dart
| Project | What it exercises | .dart files | Index | Nodes / edges | HTTP call sites (endpoints) | Pages | Parse failures |
|---|---|---|---|---|---|---|---|
| flutter/samples (whole repo) | package:http, dart:io HttpClient, go_router (nested + ShellRoute), Navigator | 465 | 1.2 s | 5,391 / 11,366 | 31 (25) | 62 | 0 |
| lichess mobile | custom http.BaseClient, Uri(path:), Riverpod, Dart 3.13 primary constructors | 735 | 21 s | 131,930 / 284,800 | 148 (123) | 92 | 0 |
| localsend | Rust/FFI networking, enum-built URL table, custom router | 383 | 5 s | 33,979 / 74,062 | 7 | 1 | 0 |
| flutter_clean_arch | Dio + BaseOptions(baseUrl), bloc, auto_route v4 | 49 | 0.04 s | 196 / 479 | 4 (3) | 0 | 0 |
| mova | Dio, URL helper class, bloc | 61 | 0.07 s | 319 / 770 | 9 (7) | 3 | 0 |
Spot checks:
- flutter/samples: 12 sampled HTTP calls all correct (method, host, path,
{id}params); 2 calls whose whole URL is a runtime value are labelleddynamic. All 13 go_router routes ofnavigation_and_routing(nested paths joined to their parent,:id→{id}) and their screens are correct. - lichess: 18/20 HTTP calls correct; the two imprecise ones are an env-hosted CDN URL rendered with a leading
/and a HEAD whose URI comes from a list. 105 call sites keep anunknownorigin because the host is added in the client'ssend()override. Pages come from the project'sbuildScreenRoute(screen: X())helper (89 call sites in the source). - mova and flutter_clean_arch: every HTTP call correct, including the Dio
baseUrlpath prefix (/auth/login).
Gaps seen:
- localsend talks to peers through a Rust bridge and an enum whose constructor builds the paths; neither is modelled.
- auto_route v4 annotation configs (
@MaterialAutoRouter(routes: [...])) are not read; v6@RoutePageclasses are pages without a path. - Data-driven route lists (
demos.map((d) => GoRoute(path: d.route, ...))) leave navigation targets unresolved.
Before the analyzer 10 upgrade, 499 lichess files failed to parse (Dart 3.13 primary constructors); they now parse with
the primary-constructors experiment fallback.
Laravel broadcasting and tests
Shallow clones of Laravel apps that use Broadcast::channel (default branch, October 2026). "Channels" lists the
declared and undeclared channels found; publishers were compared by hand with every ShouldBroadcast event's
broadcastOn(), subscriptions and listened events with every Echo / pusher call in the repo's own JS.
| Project | Channels | Publishers (events → channel) | Client subscriptions matched | Listened events matched | Notes |
|---|---|---|---|---|---|
| UNIT3D | 2 (presence chatroom, private chatter) | 4 / 4 | 2 / 2 (Alpine + Echo in resources/js) | 3 / 3 that have a backend event (.new.ping and whispers have none) | presence visibility comes from the publishers |
| koel | 2 | 2 / 2 | 2 / 2 (Echo and pusher-js private-…) | 2 / 2 | the base event's broadcastOn() returns [] (correctly no channel) |
| invoiceninja | 4 (2 declared, 2 undeclared public) | 10 / 10 events that name a channel | n/a (the frontend is a separate repo) | n/a | one broadcast event returns [] |
| pixelfed | 4 | 9 / 9 | n/a (no Echo client in the repo) | n/a | live.chat.{id} has a callback but its 7 events publish on a public Channel: flagged PUBLIC PUBLISH |
| coolify | 2 | 18 / 18 | 0: listeners are Livewire echo-private: keys in PHP (not modelled) | n/a | route middleware given as arrays is skipped |
Tests: TEST_HTTP edges, 10 sampled at random per project and checked against the cited source line (verb, path or route name, target route): 40 / 40 correct.
| Project | Test cases | HTTP test calls | Matched to a route | Unmatched (main reason) |
|---|---|---|---|---|
| UNIT3D | 861 (852 Pest) | 601 | 538 | 59 (incl. 9 calls to a route name shared by a whole route group, left out on purpose) |
| koel | 1,673 PHPUnit (+ Vitest) | 741 (449 through getAs() / postAs() helpers) | 502 | 237 (Subsonic routes are registered in a foreach) |
| invoiceninja | 5,981 PHPUnit (+ Playwright) | 2,023 | 1,970 | 46 |
| pixelfed | 1,410 (1,175 Pest) | 592 | 566 | 20 |
Nuxt layouts and clean checkouts
Shallow clones indexed without npm install and without .nuxt (the clean-checkout path):
| Project | Layout | Pages / layouts / components found | Checks |
|---|---|---|---|
| breeze-nuxt (Nuxt 3 client for a Laravel Breeze API) | source at the repo root | 8 / 2 / 14 (all) | 8 / 8 HTTP calls found with method and path (7 through a $fetch.create instance auto-imported from utils/); the runtimeConfig.public.backendUrl default resolves from nuxt.config.ts |
| elk | app/ | 59 / 2 / 196 (all) | RENDERS edges: 20 / 20 sampled correct; of the 25 components without a RENDERS edge, 14 are rendered through <component :is> and 11 from TS (h(), TipTap node views) |
Presets, starter queries and route guards
cg index <repo> with no flags and no .cg.yaml, then cg starters and cg routes --unguarded. Monorepos are
indexed per app (immich/server, cal.com/apps/web, ...). "Starters" counts the starter queries that resolve to
existing nodes out of those generated; "auth by" counts auth guards by the rule that recognised them (preset X: the
framework preset lists the guard by its own name; name pattern: the shared auth token pattern; framework: the
plugin knows the check, e.g. Laravel broadcast channel callbacks). Index time includes the starters (last column).
| Project | Commit | Frameworks detected | Presets applied (+ common) | Starters | Routes / with auth / unguarded | Auth by | Index | Starters |
|---|---|---|---|---|---|---|---|---|
| koel/koel | 295d8c1 | laravel | php, typescript, laravel | 5 / 5 | 191 / 166 / 23 | preset laravel 161, name pattern 8, framework 2 | 12.22 s | 0.08 s |
| laravelio/laravel.io | 24be489 | laravel | php, typescript, laravel | 6 / 6 | 64 / 0 / 64 | – | 2.55 s | 0.01 s |
| netbox-community/netbox | 251458b | django, djangorestframework | python, django, djangorestframework | 6 / 6 | 956 / 92 / 864 | name pattern 105, preset django 31, preset djangorestframework 2 | 34.78 s | 1.2 s |
| saleor/saleor | 8385ca6 | django | python, django | 6 / 6 | 9 / 0 / 9 | – | 68.31 s | 0.37 s |
Laravel resource routes follow ResourceRegistrar (#118). On koel the six nested registrations (albums.songs, artists.albums, artists.songs, playlist-folders.playlists, playlists.songs, podcasts.episodes) expand to the registrar's URLs with singular parameters (playlist-folders → {playlist_folder}), and no resource route is left with a dotted path segment (only GET /manifest.json and GET /manifest-remote.json keep a dot, as registered). The route count is unchanged.
| immich-app/immich server/ | c5e06dc | nest, express | typescript, nest, express | 4 / 4 | 295 / 295 / 0 | name pattern 295, preset nest 295 | 12.3 s | 0.28 s |
| immich-app/immich mobile/ | c5e06dc | flutter | python, dart | 3 / 3 | – | – | 3.79 s | 0.07 s |
| calcom/cal.com apps/api/v2 | 54343aa | nest | typescript, nest | 3 / 3 | 162 / 140 / 22 | name pattern 217 | 7.76 s | 0.01 s |
| calcom/cal.com apps/web | 54343aa | nextjs | typescript, nextjs | 4 / 4 | 99 / 0 / 99 | – | 11.28 s | 0.06 s |
| brocoders/nestjs-boilerplate | 9620f15 | nest | typescript, nest | 6 / 6 | 22 / 11 / 11 | preset nest 16 | 2.6 s | 0.01 s |
| hagopj13/node-express-boilerplate | 179ae84 | express | typescript, express | 2 / 2 | 14 / 6 / 8 | name pattern 6 | 2.18 s | 0.0 s |
| elk-zone/elk | 8a90074 | nuxt | typescript, nuxt | 3 / 3 | – | – | 4.29 s | 0.01 s |
| BurntSushi/ripgrep | 3fce3b5 | – | rust | 3 / 3 | – | – | 27.59 s | 0.05 s |
| redis/redis | b540ca4 | – | python, c_cpp | 3 / 3 | – | – | 5.53 s | 0.09 s |
| pallets/flask | d73fa1c | – | python | 3 / 3 | – | – | 0.74 s | 0.01 s |
saleor sets testpaths = ["saleor"], its application package. Since
#18 only the files pytest collects there (plus conftest.py
and tests/ directories) are test code: test modules 2,866 -> 1,718, CALLS edges 0 -> 11,848 (TEST_CALLS
58,676 -> 42,370), the same 12,866 test cases, and 6 starters instead of 1. impact saleor.order.utils.invalidate_order_prices now lists 1,152 application callers (before: none, only tests). On
the now connected call graph the starters took 63 s at first, almost all of it in the unguarded-write-routes report
(one reverse closure per written table, 95 tables). Since
#25 the route report walks the graph once for all write
targets, limited to what the routes reach, with the same output (cg routes --writes --unguarded and the 6 starters
are identical), and the starters keep to a 20 s budget (starters_skipped names any left out). Index time, best of
three on the same box:
| Project | Before #25: index / starters | After #25: index / starters |
|---|---|---|
| saleor | 124.41 s / 57.47 s | 68.31 s / 0.37 s |
| netbox | 29.96 s / 0.98 s | 27.91 s / 0.27 s |
Every starter resolved on every project. Index time on netbox, best of three on the same box: 34.1 s before (v0.4.0) and 33.5 s after, with 1.1 s of that spent on the starters; the shared skip lists and presets add no measurable cost.
Hand-checked routes --unguarded samples. For each framework, the first unguarded routes and a sample of the
guarded ones were compared with the source:
| Project | Guards found by the framework preset | Unguarded routes, checked against the source | Needs a project pattern or a later feature |
|---|---|---|---|
| koel (Laravel) | auth, auth:sanctum-style aliases on route groups (161) | GET /api/ping, POST /api/me (login), POST /api/forgot-password, POST /api/reset-password, GET /api/invitations, GET /demo/new-session: public by design | – |
| laravel.io (Laravel) | – | POST /forum/{thread}/lock, PUT /forum/{thread}/mark-solution/{reply}, POST /articles, PUT /admin/articles/{article}/pinned: protected by $this->middleware(Authenticate::class, ...) in the controller constructor | controller-constructor middleware (planned) |
| netbox (Django + DRF) | LoginRequiredMixin, UserPassesTestMixin, IsAuthenticated; netbox's own ConditionalLoginRequiredMixin, ObjectPermissionRequiredMixin and IsSuperuser by name pattern | 851 REST API routes (POST /api/wireless/wireless-links/, ...): protected by REST_FRAMEWORK['DEFAULT_PERMISSION_CLASSES'] in settings; GET /login/, GET /logout/, POST /oauth/begin/{backend}/: public by design | DRF settings defaults (planned); IsAuthenticatedOrLoginNotRequired (anonymous access when LOGIN_REQUIRED is off) counts once listed in auth.extra_patterns: 864 → 860 |
| saleor (Django) | – | 9 plain Django views (the GraphQL endpoint, plugin webhook endpoints, thumbnails, images, JWKS, static files): public endpoints; the GraphQL API checks permissions per resolver and each plugin checks its own requests | – |
immich server/ (NestJS) | AuthGuard on all 295 routes (with immich's MaintenanceAuthGuard by name pattern) | none; the 2 routes the test setups in test/medium/specs/*.spec.ts build are test code since #19 (297 / 2 unguarded before) | – |
cal.com apps/api/v2 (NestJS) | cal.com's ApiAuthGuard, PermissionsGuard, OAuthClientGuard by name pattern | GET /health, GET /v2/atoms/event-types/{eventSlug}/public, POST /v2/auth/oauth2/token, OAuth callbacks: public by design; POST /v2/webhooks/vercel/deployment-promoted | VercelWebhookGuard counts as a signature check once listed in secret.extra_patterns (22 → 21) |
cal.com apps/web (Next.js) | – | POST /api/auth/two-factor/totp/disable, POST /api/availability/calendar: the handler calls getServerSession() and returns 401 itself; POST /api/auth/signup, POST /api/auth/forgot-password: public by design | session checks inside the handler body |
| nestjs-boilerplate (NestJS) | AuthGuard('jwt'), RolesGuard (16) | POST /v1/auth/email/login, /register, /forgot/password, /reset/password, social logins: public by design | – |
| node-express-boilerplate (Express) | the project's auth() middleware by name pattern | POST /login, /register, /refresh-tokens, /forgot-password, /reset-password, /verify-email: public by design | – |
A .cg.yaml for the two project patterns above:
auth:
extra_patterns: ["IsAuthenticatedOrLoginNotRequired"] # netbox
secret:
extra_patterns: ["WebhookGuard$"] # cal.com apps/api/v2
Monorepo apps
One cg index <root> with .cg.yaml apps (the file was written for the run and removed afterwards), compared
with indexing each app and linking each pair one by one: every per-app and per-pair graph has the same nodes and
edges.
| repo | apps | one command | per app (nodes / edges) | links (endpoints matched) |
|---|---|---|---|---|
| immich | server, ml (machine-learning), web, mobile | 20.6 s | server 8,028 / 36,234; ml 727 / 1,956; web 2,351 / 3,670; mobile 7,443 / 22,990 | web → server 0/0, mobile → server 0/0 (both call through generated SDKs that are not in the checkout) |
| cal.com | api-v2 (apps/api/v2), web (apps/web) | 18.5 s | api-v2 3,813 / 12,304; web 5,689 / 12,571 | web → api-v2 0/28 (the web app calls its own Next /api routes) |
| bundled samples | api (bookstore-django), web, flutter, android, ios | 2.0 s | api 207 / 341 | flutter 6/7, android 3/3, ios 3/3, web 0/6 (it targets the Laravel sample) |
Taking the extractor skip lists from the presets changed no graph: immich server / web / mobile, cal.com apps/web,
elk, social-app and nestjs-boilerplate index to the same nodes and edges as before.
Generated and copied files
cg index <repo> with no flags and no .cg.yaml (default: generated, copied and vendored files excluded), then again
with --include-generated; "before" is v0.4.0 plus the presets change. The Nuxt row is the nuxt/starter template
(v4 branch) after npm install and nuxi prepare, so .nuxt/ exists as in a developer checkout; the
Ionic/Capacitor row is a public Angular + Capacitor app with its android/ project and www/ build committed.
| Project | Commit | Files classified, by reason | Discovered source files (before → after) | Nodes / edges before → default → --include-generated |
|---|---|---|---|---|
| ionic-team/capacitor-plugins | 87c0bb8 | – (no build output or copies committed) | TS 96 → 96 | 28 / 45 → 28 / 45 → 28 / 45 |
| abritopach/angular-ionic-master-detail | ba286ac | copy of www/ (Capacitor webDir) 107, web build output (webDir) 104 | TS 351 → 140 | 38 / 50 → 38 / 50 → 38 / 50 |
| nuxt/starter (v4) | 9b0ac50 | Nuxt build output (.nuxt/) 27 | TS 29 → 2 | 1 / 0 → 1 / 0 → 1 / 0 |
grpc/grpc examples/ | e124571 | protoc Python 40, protoc / gRPC banner 16, protoc JS 4, <auto-generated> header 1 | Python 143 → 103, PHP 20 → 11, TS 13 → 9 | 1364 / 1835 → 1154 / 1670 → 1364 / 1835 |
| jellyfin/jellyfin-sdk-typescript | 1ef0625 | OpenAPI Generator banner 409 | TS 507 → 98 | 3082 / 9489 → 345 / 857 → 3082 / 9489 |
| openfga/js-sdk | d6d4035 | OpenAPI Generator banner 10, .openapi-generator/FILES 4 | TS 54 → 40 | 685 / 3219 → 453 / 2063 → 685 / 3219 |
| flutter/samples | a05867d | Flutter plugin registrant 60, *.g.dart 46, *.freezed.dart 18, Flutter ephemeral/ 2 | Dart 484 → 420, C/C++ 210 → 162 | 5394 / 11368 → 4631 / 10141 → 6091 / 12666 |
In every default index no node comes from a classified file, and cg coverage lists each one by reason. In every
--include-generated index, every node from a classified file carries attrs.generated (0 unlabelled): 210 on grpc,
2737 on jellyfin, 232 on openfga, 1478 on flutter/samples. flutter/samples grows past "before" with the flag, because
*.freezed.dart output, which the Dart plugin always skipped, is indexed too. On the Ionic app and on capacitor-plugins
the node count does not move: the TS plugin only reads src/, so the 211 copies were never parsed, but they were
counted as unindexed TypeScript and inflated the coverage gap. Linking the JS side of a Capacitor plugin to its
Android/iOS implementation needs the Kotlin/Java and Swift plugins (see Generated files).
Bundled examples are unchanged except bookstore-flutter (90 / 171 → 87 / 164: the book.g.dart json_serializable
output is no longer a node; PARSES_JSON is unchanged, because the file is still read for the exact JSON keys). The classification scan
takes 0.00–0.08 s on these projects; index time on netbox (no generated files),
median of five alternating runs on a shared box: 35.6 s before and 36.0 s after (run-to-run spread 33–37 s), with
the scan itself at 0.07 s.
Platform-specific code
cg index <repo> with no flags and no .cg.yaml, then cg platforms divergence. "Before" is the index without
platform tags; node counts are identical before and after on every project, and the added edges are the
platform_variant_of copies that give each sibling variant the callers of the one the resolver picked (plus Rust
cfg-inactive references, which carry the condition instead of being dropped). Targets come from the project itself:
Flutter folders, React Native and its react-native-web dependency, or the desktop default for native code, plus every
OS a condition names.
| Project | Commit | Targets | Conditions (not evaluable) | Tagged nodes / edges | Edges before → after | Variants / API differences / missing callees | Platforms pass |
|---|---|---|---|---|---|---|---|
| BurntSushi/ripgrep | 3fce3b5 | windows, linux, macos | 106 (7) | 59 / 316 | 25765 → 25812 | 17 / 0 / 0 | 0.29 s |
| alacritty/alacritty | d692748 | windows, linux, macos | 480 (18) | 324 / 1167 | 23135 → 23303 | 18 / 0 / 0 | 0.13 s |
| libuv/libuv (C, heuristic) | 49b1c06 | windows, linux, macos, ios, android | 1153 (211) | 5300 / 30318 | 52483 → 61235 | 235 / 0 / 85 | 0.77 s |
| curl/curl (C, compile database) | d4dc7d1 | windows, linux, macos | 476 (212) | 527 / 2486 | 113311 → 115781 | 114 / 0 / 0 | 0.28 s |
| dart-lang/http | 47c57df | 6 Flutter folders | 88 (0) | 104 / 204 | 11365 → 11378 | 30 / 1 / 1 | 0.21 s |
| bluesky-social/social-app (React Native) | db23528 | ios, android, web | 240 (3) | 1038 / 4328 | 44842 → 47065 | 108 / 9 / 102 | 1.13 s |
| localsend/localsend (Flutter) | c5bbe36 | 6 Flutter folders | 132 (64) | 10 / 95 | 10860 → 10860 | 1 / 0 / 0 | 0.15 s |
What the findings are, from spot checks:
- ripgrep, alacritty, curl: every variant group is a real per-OS definition set (
#[cfg(unix)]/#[cfg(windows)]pairs,#ifdef WIN32/#elsefunction and macro pairs), and no reference lacks a callee on any target. - dart-lang/http: the conditional imports (
client_stub.dart/io_client.dart/browser_client.dart,connect_stub.dart/ …) form the variant groups, including imports whose default is an SDK library (dart:isolate). The one API difference and missing callee areconnectinbrowser_web_socket.dart, a top-levelconst connect = BrowserWebSocket.connecttear-off the Dart extractor does not record as a symbol. - social-app: the
.ios/.android/.native/.webfile groups. Sampled findings come from variant files that re-export the symbol (export {x},export {a as b} from 'react-dom'), which the TS extractor does not record as a definition in that file, and from.web.tsfiles that tests call directly. - libuv: the per-OS function sets of
src/unix/*andsrc/win/*. Missing callees come from the C heuristic extractor: functions generated by macros (SOCKOPT_SETTERinsrc/win/udp.c), functions after a region tree-sitter cannot parse (src/win/tty.c), and one macro defined in two#ifbranches of one header. - localsend: almost all platform logic is runtime
Platform.isX/defaultTargetPlatformchecks inside shared code; 64 of them useTargetPlatformvalues that do not exist on web, so the web count of unevaluated conditions is high and those branches stay in for web.
Index time on netbox (no platform-specific code), best of three alternating runs on a shared box: 43.2 s before and 42.7 s after (spread 42.7–45.4 s); nodes and edges are identical (38028 / 140181).
Divergence false positives removed in #63 (before → after on the same commits):
| Project | Commit | missing_callee | variants | Edges | What changed |
|---|---|---|---|---|---|
| libuv | 49b1c06 | 38 → 35 (+3 counted, built for no target) | 496 → 495 | 70889 → 70826 | alloc_buffer of the separate docs/code/* programs is no variant group (63 mirrored edges between programs gone); strnlen (sunos.c) ×2 and an aix.c helper not listed |
| SwiftUIX | 3a99044 | 51 → 49 | 103 → 103 | unchanged | Path(...) bound to a project extension Path { init } under #if canImport(UIKit) |
| social-app | db23528 | 155 → 155 | 108 → 108 | 60563 → 60559 | bootstrap.test.ts imports ./index.web explicitly: its 4 test edges to index.ts are gone |
| mattermost-mobile, IceCubesApp | e5be311, 9efcb16 | unchanged | unchanged | unchanged |
Kotlin
Heuristic mode (tree-sitter-kotlin 1.1.0), shallow clones, cg index on the default branch:
| Repository | Kotlin files | Declarations | Calls resolved / unresolved | Framework facts | Index time |
|---|---|---|---|---|---|
| android/nowinandroid | 350 | 1,219 | 1,608 / 5,696 | 4 Retrofit endpoints, 3 activities, 1 service, 2 workers, 2 deep links | 0.8 s |
| ktorio/ktor-samples | 204 | 570 | 652 / 5,635 | 77 Ktor routes, 115 Ktor client endpoints, 10 KMP source-set files | 0.4 s |
| spring-petclinic/spring-petclinic-kotlin | 40 | 139 | 112 / 548 | 19 Spring routes | 0.1 s |
| touchlab/KaMPKit | 38 | 114 | 167 / 562 | 2 expect → actual links, 5 KMP source-set files, 1 activity | 0.1 s |
Unresolved calls are mostly library and standard-library calls (Compose, coroutines, collections), which have no node in the graph.
Framework facts added with exact mode (same corpora, before → after; every other node and edge unchanged apart from
calls now owned by the new page / route-handler nodes): nowinandroid 2 → 7 pages (Navigation 3 entry<Key>), 0 → 3
NAVIGATES_TO; ktor-samples 77 → 90 routes (type-safe resources), 3 Exposed tables with 14 reads / writes, 8 → 11
Ktor client calls (builder blocks); spring-petclinic-kotlin 4 tables with 17 Spring Data reads / writes (plus 38 from
tests). KaMPKit (URL built in a helper) and android/architecture-samples (routes from const val strings) are unchanged.
No corpus has a SecurityFilterChain; the fixture covers it.
Kotlin exact mode (scip-java)
scip-java 0.12.3 standalone launcher, JDK 17, CODEGRAPH_KOTLIN_SCIP_FILE pointing at the index. scip-java's
semanticdb-kotlinc plugin only loads into Kotlin ≤ 2.1, and these builds use Kotlin 2.4: each was indexed from a copy
with the Kotlin Gradle plugin pinned to 2.1.20 (Ktor samples also with -Xskip-metadata-version-check, since Ktor 3.6
is compiled with Kotlin 2.3 metadata); the sources are unchanged. Precision: share of heuristic call edges (CALLS /
INSTANTIATES, same source and target) that the index confirms; recall: share of compiler edges the heuristic layer
found.
| Project | Kotlin files in index | Defs matched / unmatched | Heuristic edges | Exact edges | Agree | Precision | Recall | scip-java run |
|---|---|---|---|---|---|---|---|---|
| spring-petclinic-kotlin | 38 / 38 | 140 / 4 | 98 | 96 | 96 | 0.98 | 1.00 | ~2 min cold, 24 s warm |
| ktor-samples/kweet | 16 / 16 | 74 / 7 | 77 | 95 | 74 | 0.96 | 0.78 | 28 s |
| ktor-samples/mvc-web | 12 / 12 | 30 / 1 | 25 | 32 | 25 | 1.00 | 0.78 | 21 s |
| ktor-samples/youkube | 9 / 9 | 24 / 2 | 25 | 29 | 25 | 1.00 | 0.86 | 27 s |
| ktor-samples/postgres | 7 / 7 | 14 / 1 | 11 | 11 | 11 | 1.00 | 1.00 | 24 s |
| 8 smaller ktor-samples (1–2 files each) | 11 / 11 | 100 / 12 | 17 | 28 | 15 | 0.88 | 0.54 | 17–26 s |
| Total (13 Gradle builds) | 93 | 382 / 27 | 253 | 291 | 246 | 0.97 | 0.85 |
The heuristic layer's misses are mostly extension functions called on a library receiver (call.redirect(...)),
overloads and lambda receivers (it.area()); its few disagreeing edges attribute a call to the file instead of the
enclosing function, or pick a same-named function. Unmatched definitions (SCIP definitions with no syntax-layer
declaration on that line) get no edges. Not run: chat, h2, opentelemetry (the build does not
configure with the pinned plugin), httpbin (needs Kotlin 2.2 language features), and the Android projects
(nowinandroid, KaMPKit, architecture-samples: no Android SDK on the validation machine). Without a JDK, or with the
unpinned Kotlin 2.4 build, the index completes in heuristic mode and cg coverage names the reason (no JDK, or
scip-java run failed (exit 1: ...)).
Kotlin 2.2 and mixed Kotlin / Java (scip-java 0.13.1 launcher next to 0.12.3, CODEGRAPH_KOTLIN_SCIP=1, build not
edited): statsig-io/java-server-sdk 9edc7ca, Kotlin 2.2.10, Gradle 8.5 wrapper, 86 Kotlin + 12 Java (test) files. cg
read Kotlin 2.2.10 from the build, ran 0.13.1 (93 s cold): 84 Kotlin files exact, 944 / 59 definitions matched /
unmatched (compiler-generated members such as data class copy / componentN and anonymous objects not counted),
2,207 references resolved to project code; heuristic precision 0.91, recall 0.81 (1,434 of 1,573 heuristic edges
confirmed, 1,779 exact). Java: 13 documents, 30 classes and 192 methods as java nodes, 358 exact Java -> Kotlin / Java edges;
coverage kotlin 86 exact; java 12 scip (heuristic mode: java 12 unsupported). Graph 1,003 -> 1,097 nodes,
3,226 -> 3,852 edges. The same Gradle fixture on Kotlin 2.1.20 (0.12.3) and 2.2.10 (0.13.1) gives identical edges;
on 2.2.21, 2.3.21 and 2.4.20 neither release loads (NoSuchMethodError / AnalyzerRegistrar is incompatible) and
coverage names the version.
Android: android/architecture-samples ee66e15 (Kotlin 2.1.10, modules app, shared-test) with an Android SDK
installed in the user's home (command-line tools + sdkmanager "platforms;android-35" "build-tools;35.0.0", no
sudo). The default scip-java run fails on the settings' FAIL_ON_PROJECT_REPOS; cg reports that and names both
Android modules. With an init script switching the mode to PREFER_SETTINGS and the variant tasks requested
explicitly (:app:compileDebugKotlin), the build compiles (1 min 27 s) but scip-java 0.12.3's Gradle plugin attaches
no compiler plugin to Android variant tasks: no SemanticDB output, so the index stays heuristic (61 Kotlin files).
Swift
Heuristic mode (tree-sitter-swift 0.7.3) on Linux without Xcode, shallow clones, cg index on the default branch:
| Repository | Swift files | Declarations | Calls resolved / unresolved | Framework facts | Index time |
|---|---|---|---|---|---|
| Alamofire/Alamofire | 101 | 2,019 | 3,817 / 8,170 | 198 extensions, 33 #if platform blocks, 5 AF.request endpoints (the example apps) | 1.1 s |
| Dimillian/IceCubesApp | 428 | 2,156 | 3,537 / 8,174 | 21 SwiftUI pages, 21 navigations, 14 @main entries (app, extensions, widgets), 259 #if platform blocks | 1.2 s |
| pointfreeco/isowords | 388 | 1,729 | 2,606 / 7,794 | 17 SwiftUI pages, 12 navigations, 12 @main entries, 1 URLSession endpoint | 1.1 s |
| vapor/template | 9 | 16 | 20 / 81 | 3 Vapor routes with their RouteCollection handlers | < 0.1 s |
Unresolved calls are mostly SDK calls (SwiftUI modifiers, Foundation, Combine), which have no node in the graph.
IceCubesApp and isowords call their APIs through typed endpoint enums and a swift-parsing router, which are not HTTP
client forms the plugin reads yet. The Vapor template's routes.swift contains Mustache markup, so its two
closure routes do not parse. Since exact mode, the body of an overloaded method is owned by its (merged) method node
rather than the enclosing type: Alamofire 3,825 → 3,817 resolved calls (overloads calling each other became self
calls), isowords gained one URLSession endpoint (built inside an overloaded request).
Moya and Fluent: none of the four corpora uses Moya (the fixture tests/swift_facts_fixture covers it, including a
TargetType with a relative base linked to Vapor routes). The Vapor template rendered with Fluent (fluent: true,
SQLite, no Leaf) gives table:todos (MAPS_TO_TABLE from Todo), 2 migration writes, 2 reads (Todo.query,
Todo.find) and 1 write (todo.delete(on:)) from TodoController, and 2 test uses; the unrendered template gives
the same. create saves the model returned by TodoDTO.toModel(), whose type is not inferred. No corpus has
@available(iOS / macOS, unavailable) or a call to a function defined per #if os(...) branch (the fixture covers
both). Apart from these and the overload change above, the four corpora index as before.
Swift exact mode (index store)
Swift 6.3.2 for Linux (Debian 12 build), libIndexStore.so from the same toolchain, swift build --enable-index-store (debug). Precision: share of heuristic call edges (CALLS / INSTANTIATES, same source and
target) in the files the store covers that the index confirms; recall: share of compiler edges the heuristic layer
found.
| Project | Swift files in store | Defs matched / unmatched | Heuristic edges | Exact edges | Agree | Precision | Recall | Build |
|---|---|---|---|---|---|---|---|---|
Alamofire/Alamofire (swift build, via CODEGRAPH_SWIFT_INDEX=1) | 44 / 96 | 868 / 15 | 655 | 833 | 544 | 0.83 | 0.65 | 7 s cold, cache hit 0.4 s |
pointfreeco/isowords (swift build --product server, existing store) | 104 / 384 | 424 / 17 | 246 | 345 | 157 | 0.64 | 0.46 | 41 s (dependencies built) |
| vapor/template (rendered with Fluent + SQLite, existing store) | 7 / 8 | 17 / 0 | 11 | 10 | 9 | 0.82 | 0.90 | ~2 min cold with dependencies |
| Total | 155 | 1,309 / 32 | 912 | 1,188 | 710 | 0.78 | 0.60 |
After the heuristic precision fixes of #58 (same stores, same sources; SDK initializers of extended types, initializer argument labels, standard-library collection methods, and the exact layer's initializers declared in extensions of SDK types):
| Project | Heuristic edges | Exact edges | Agree | Precision | Recall |
|---|---|---|---|---|---|
| Alamofire/Alamofire | 655 → 585 | 833 → 848 | 544 → 552 | 0.83 → 0.94 | 0.65 → 0.65 |
| pointfreeco/isowords (server product) | 246 → 174 | 345 → 374 | 157 → 163 | 0.64 → 0.94 | 0.46 → 0.44 |
| vapor/template | 11 → 11 | 10 → 10 | 9 → 9 | 0.82 → 0.82 | 0.90 → 0.90 |
| Total | 912 → 770 | 1,188 → 1,232 | 710 → 724 | 0.78 → 0.94 | 0.60 → 0.59 |
The exact edges grew by the initializers projects declare in extensions of SDK types (extension Data { init(hex:) }), which the layer used to drop; recall moves by that denominator, not by lost heuristic edges. The whole
graph also loses the SDK-initializer edges in files without a store (all edges: Alamofire 7,321 → 6,756, isowords
4,973 → 4,295, mostly Result, Data, URL, URLRequest and UUID constructions in tests).
What a Linux build covers: Alamofire's Source/ (tests and example apps need Apple frameworks); isowords' server
modules (the full swift build stops at SwiftUI; the server product needs libsqlite3-dev), the rest of its 384 files
keep heuristic edges and cg coverage counts them; the Vapor app's App target (its test target was not built). Not
run: Dimillian/IceCubesApp (iOS-only SwiftUI app, no Linux build; heuristic, with the reason in cg coverage).
Where they disagreed before #58: the heuristic layer linked initializers of SDK types the project extends (URL(...),
Date(...), UUID(...) → the extension's class node, while the compiler resolves them to Foundation) and picked
same-named methods (xs.first(where:) → a project first; now fixed; a Fluent migration's .create() still matches
TodoController.create); it misses initializer calls
through Self(...) / .init(...) / nested types, calls to explicit init declarations (the index adds a CALLS to
T.init next to INSTANTIATES), and members reached through inferred types (.live, closure parameters). In
Alamofire, 247 heuristic edges sit in inactive #if branches (Apple-only code); they are kept with
via: "not-compiled". Without the opt-in or a toolchain the index completes in heuristic mode and cg coverage
names the reason (Swift toolchain found but the package was not built ..., no Package.swift ...,
swift build failed (exit 1: ...)).
Web / native bridges
cg index <repo> with no flags and no .cg.yaml, then cg bridges (Web / native bridges). The two Capacitor
repositories are monorepos without a root tsconfig.json; the TypeScript plugin indexes their per-package
tsconfigs as one program (capacitor: 794 / 1664 → 1633 / 4521 nodes / edges, before that the plugin did not run;
capacitor-plugins: 589 / 640 → 959 / 1367, 20 package tsconfigs instead of 3–4 files picked up by the fallback). Index time is one run each on a shared box (the spread between repeated
runs of the same build was 2–4 s); the bridge pass itself takes the time shown.
| Project | Commit | Endpoints | Linked | Receivers (stubs) | Checks | Bridge pass | Index before → after |
|---|---|---|---|---|---|---|---|
| ionic-team/capacitor (framework) | ba28569 | 24 | 1 | 39 (19) | 4 external, 1 missing_on, 19 no_sender | 0.10 s | |
| ionic-team/capacitor-plugins (library) | 87c0bb8 | 81 | 0 | 159 (86) | 2 missing_on, 75 no_sender | 0.10 s | |
| flutter/samples (platform_channels, add_to_app, pigeon, platform_view_swift) | a05867d | 14 | 14 | 21 (7) | none | 0.05 s | 599 / 999 → 605 / 1023 |
| mattermost/mattermost-mobile (React Native) | e5be311 | 42 | 32 | 71 (30) | 3 no_sender | 0.05 s | 23.1–24.2 s → 24.3–25.0 s; 21639 / 81163 → 21711 / 81297 nodes / edges |
| bluesky-social/social-app (Expo modules) | db23528 | 33 | 1 | 62 (0) | 4 missing_on, 32 no_sender | 0.03 s | 15.6–19.2 s → 16.7–18.1 s; 9900 / 47568 → 9933 / 47632 |
social-app with include: [modules] | db23528 | 33 | 21 | 62 (0) | 12 no_sender | 0.03 s | |
immich-app/immich mobile/ (Flutter, Pigeon) | c5e06dc | 45 | 43 | 86 (0) | 3 missing_on, 2 no_sender | 0.08 s | 7443 / 22989 → 7488 / 23131 |
What the findings are, from spot checks:
- capacitor: the framework's own core plugins (CapacitorHttp, CapacitorCookies, WebView, Console) are received on
both platforms;
CapacitorHttp#requestis the one method the framework's own web code sends (throughnativePromise), the others are the plugins' public API, called by apps.Console#logexists on iOS only (Android logs through the web view). The external endpoints are test literals andApp#exitAppfrom the@capacitor/apppackage. - capacitor-plugins: a library repository, so no senders. The two
missing_onare real:TextZoom.get/setare Android methods (iOS hasgetPreferredonly). - immich mobile: 11 Pigeon APIs (10
@HostApi, 1@FlutterApi) frommobile/pigeon/*.dart(the generated files are git-ignored): 50 Dart sends, 86 Kotlin / Swift receivers, 3 native sends to the DartBackgroundWorkerBgService. Kotlin implementations inherit shared methods fromNativeSyncApiImplBase(hashAssetsis received there on Android and inNativeSyncApiImplon iOS). Themissing_onare real (BackgroundWorkerLockApiandViewIntentHostApiare implemented on Android only), as are the twono_sender(NetworkApi#addCertificate,NativeSyncApi#clearSyncCheckpointhave no Dart caller). - flutter/samples: every channel method is linked on both platforms. Native → Dart:
reportCounter(add_to_app fullscreen, Kotlin and Swift),setCount(multiple_flutters) andsetCellNumber(android_view list cells) are sent byinvokeMethodand received by the DartsetMethodCallHandler; the books sample's PigeonHostBookApi/FlutterBookApilink both ways.platform_view_swiftis an iOS-only app (noandroid/next to itspubspec.yaml), so itsswitchViewis not reported missing on Android. The stubs are Swift methods the Swift plugin does not index (multi-lineapplication(...)signatures,viewDidLoadoverrides). - mattermost-mobile: the app imports its modules from local
file:packages (@mattermost/rnutils,@mattermost/calls-native, ...) withoutnode_modules; they resolve to their source, throughisTurboModuleEnabled ? require('./NativeX').default : NativeModules.X,X || new Proxy(...)andObject.assign(X, { onEvent })(whose helpers are not endpoints).setNavigationBarColoris Android-only and its only call is underPlatform.OS === 'android', so it is not missing on iOS. The 3 senderlessMattermostSharemethods are called fromshare_extension/, which the default source dirs do not index; the module is declaredcodegenConfig.platforms: ["android"]. - social-app: the Expo modules are called from wrapper functions inside
modules/*/src, which is outside the default source dirs; withinclude: [modules]21 methods link and no platform gap remains (ExpoBlueskyReferreris called from an.android.tsfile,setAudioActiveafterif (Platform.OS !== 'ios') return). The senderless methods are view-ref methods (GifView.playAsync) and notification-extension preferences. - immich: the mobile app talks to native code through Pigeon-generated APIs only, which are not modelled yet.
Native events, Cordova and dynamic names (#61), before → after on the same commits:
| Project | Commit | Endpoints | Linked | New endpoints | Unresolved (dynamic) | Nodes / edges |
|---|---|---|---|---|---|---|
| mattermost-mobile | e5be311 | 42 → 49 | 32 → 33 | 7 react-native-event | 4 | 23813 / 95635 → 23820 / 95643 |
| capacitor-plugins (library) | 87c0bb8 | 81 → 96 | 0 → 0 | 15 capacitor-event (29 notifyListeners sends) | 6 | 1192 / 1847 → 1221 / 1876 |
| EddyVerbruggen/Toast-PhoneGap-Plugin (Cordova) | 55856bc | 0 → 2 | 0 → 2 | 2 cordova | 0 | |
| social-app | db23528 | 33 → 33 | 1 → 1 | none | 0 | unchanged |
- mattermost-mobile:
DimensionsChangedlinks the SwiftsendEvent(name:)touseWindowDimensions'NativeEventEmitter(RNUtils)listener (the Android side emits through an enum value, not followed). Three native events have "no JS listener found": their listeners sit in the local@mattermost/*packages and take the name from a constant object or aswitchon the event. ThreeDeviceEventEmitterlisteners belong to the app's JS event bus (emitted with names built elsewhere) and are not flagged. No existing endpoint or check changed. - capacitor-plugins: a library, so the events have no JS listener;
appRestoredResult,backButtonand the local / push notification action events are emitted on Android only. The unresolved sends arenotifyListenerscalls with a variable name (Browser, StatusBar). - Toast-PhoneGap-Plugin:
show/hidefromwww/Toast.jslink to the Javaexecuteactions (compared withACTION_SHOW_EVENT.equals(action)constants) and the Objective-CCDVPluginmethods on both platforms. - social-app: no native events or Cordova calls; its
addListenercalls are navigation,Keyboardand a web-onlyEventEmitter, none of which became endpoints.
Desktop process boundaries (Electron, Tauri) and declared targets
cg index <repo> with no flags and no .cg.yaml, then cg bridges; Rust in heuristic mode
(CODEGRAPH_RUST_SCIP=0). Index times are one run each on a shared box.
| Project | Commit | Endpoints | Linked | Checks | Index before → after (nodes / edges) |
|---|---|---|---|---|---|
| electron/fiddle | a63225b | 87 electron-ipc, 62 electron-preload | 87, 60 | 2 no_sender | 4.1 s → 3.8 s; 2195 / 5811 → 2456 / 6319 |
| clash-verge-rev/clash-verge-rev (Tauri 2, React) | 3607e66 | 90 tauri | 89 | 1 no_sender | 5.4 s → 5.0 s; 5263 / 15540 → 5353 / 15720 |
tauri-apps/tauri examples/ (no root Cargo.toml) | 30da1fd | 7 tauri (2 of them plugin:app-menu|… from a plugin Builder) | 0 | 7 no_sender | the frontends are .svelte / plain HTML, which the TypeScript extractor does not read: no senders |
- fiddle: channels are
IpcEventsenum members, registered through anipcMainManagerwrapper; main → renderer messages go to a preloadaddEventListenerthat maps event names to channels through a lookup table (a union-typed channel). Every channel is linked. The twono_sendermembers are real:getTemplateValuesandremoveAllListenersof the isolated run-button API are exposed but never called.impacton the main process'sreadThemeFilewent from 5 callers (main only) to 18, through the preload script into the renderer components. - clash-verge-rev: 90
#[tauri::command]functions, all registered ingenerate_handler!; 89 are invoked from the TypeScript services.perf_state(a command in a perf script crate) has no sender. - Declared targets: swift-Alamofire
windows, linux, macos, ios(desktop default) →windows, linux, macos, ios, android(Package.swiftplatforms, plus the targets its#if os(Linux) || os(Windows) || os(Android)names);canImport/targetEnvironmentconditions take it from 33 to 103 platform conditions and from 26 to 370 tagged nodes. KaMPKitwindows, linux, macos, ios, android→ios, android(thekotlin { }block ofshared/build.gradle.kts); Kotlin/kotlinx-datetime (c73ca37)windows, linux, macos, web→windows, linux, macos, ios, android, web(mingwX64(),linuxX64(),macosArm64(),iosArm64(),androidNativeArm64(),js(); a plainjvm()is not a platform target). Graph edges are unchanged on all three.
Overrides in tests / reaches, inherited specs, TypeScript class hierarchy
cg index <repo> before and after (#53), no flags. TypeScript graphs gain only class hierarchy edges (no node and no
other edge changes); Python graphs are identical (the change is in the queries).
| Project | Commit | Edges before → after | Added |
|---|---|---|---|
| brocoders/nestjs-boilerplate | 9620f15 | 2097 → 2147 | EXTENDS 8, IMPLEMENTS 6, IMPLEMENTED_BY 36 (repositories implements an abstract repository class) |
| bluesky-social/social-app | db23528 | 47632 → 47650 | IMPLEMENTS 13, EXTENDS 2, OVERRIDDEN_BY 3 |
| mattermost/mattermost-mobile | e5be311 | 81297 → 81308 | IMPLEMENTS 8, EXTENDS 3 |
calcom/cal.com apps/api/v2 | 54343aa | 12304 → 12322 | EXTENDS 9, IMPLEMENTS 9 |
| elk-zone/elk, breeze-nuxt, node-express-boilerplate | unchanged | (no class hierarchies) | |
| sphinx-doc/sphinx (Python) | b04a210 | 33264 → 33264 | none |
Spot checks: every added edge was a real extends / implements / override. On social-app,
impact MergeFeedSource_Following._getFeed had no callers and now lists MergeFeedSource._fetchNextInner (via the
base _getFeed) up to fetchNext; on nestjs-boilerplate the repository implementations were already reached through
Nest's BOUND_TO edges and now also state overrides: FileRepository.create. Calls on a TypeScript interface type
(api.fetch() with api: FeedAPI) still had no target node then; #55 below adds interface members.
Receiver types on inherited calls in Kotlin, Swift, Dart and PHP (#62)
cg index before and after on the same commits: nodes and edges unchanged, calls into an inherited method now
carry attrs.recv (the receiver's class), which impact / reaches / tests on Sub.method use to leave out
calls on sibling subclasses.
| Project | Commit | Nodes / edges | Calls with recv |
|---|---|---|---|
| android/nowinandroid (Kotlin) | a49ed25 | 1824 / 3414 unchanged | 0 → 2 |
| Dimillian/IceCubesApp (Swift) | 9efcb16 | 4028 / 8945 unchanged | 0 → 65 |
| koel/koel (PHP) | 295d8c1 | 15865 / 33158 unchanged | 0 → 708 |
| dart-lang/dart_style (Dart) | 60f31a6 | 1610 / 6365 unchanged | 0 → 150 |
Interface members, receiver-narrowed inherited specs, container bindings as dispatch (#55)
cg index <repo> before and after, no flags; queries with the old and the new code on the old and the new graph.
| Project | Commit | Nodes / edges before → after | Interface members | Calls into them | IMPLEMENTED_BY added (structural) | Calls with recv |
|---|---|---|---|---|---|---|
| bluesky-social/social-app | db23528 | 9933 / 47648 → 9959 / 47724 | 26 | 16 | 34 (8) | 4 |
| mattermost/mattermost-mobile | e5be311 | 21711 / 81269 → 21735 / 81384 | 24 | 91 | 0 | 224 |
immich-app/immich server/ | c5e06dc | 8028 / 36415 → 8038 / 36450 | 10 | 20 | 5 (0) | 1062 |
calcom/cal.com apps/api/v2 | 54343aa | 3813 / 12322 → 3821 / 12341 | 8 | 0 | 11 (0) | 0 |
| brocoders/nestjs-boilerplate | 9620f15 | 764 / 2147 → unchanged | 0 | 0 | 0 | 0 |
No edge was lost: the only edges that changed source are 22 REFERENCES_TYPE from an interface to the types its
member signatures use, which now start at the member. Precision, from spot checks: all 50 added IMPLEMENTED_BY
edges are real (implements declarations; the 8 structural ones are return new AuthorFeedAPI(...) etc. in a
function returning the other FeedAPI interface of social-app's followingV2), and the 36 sampled calls into
interface members are calls on values typed with that interface (api.peekLatest(), bulk.getAssetIds(...),
Intl.formatMessage, mattermost's REST client mixins). impact AuthorFeedAPI.peekLatest (social-app) 0 → 48
callers via FeedAPI.peekLatest; impact MemoryRepository.getAssetIds (immich) 0 → 15 callers, 7 entry points via
IBulkAsset.getAssetIds; impact BaseConfig.getCommand 0 → 3. mattermost's REST client implements its interfaces
through mixin class expressions, so those members have callers but no IMPLEMENTED_BY yet.
Inherited specs: impact AppDataOperator.handleRecords (mattermost) leaves out 4 of 42 calls into
BaseDataOperator.handleRecords, all this.handleRecords(...) in the sibling ServerDataOperatorBase (656 → 339
transitive callers); ExifTestContext.newUser (immich tests) 123 of 624 calls on LibraryTestContext /
SyncTestContext; MergeFeedSource_Custom.take (social-app) the call on MergeFeedSource_Following. Calls on
mattermost's ServerDataOperator (a class built with mix(ServerDataOperatorBase).with(...) merged with an
interface) are kept, since the graph does not know that class's ancestry. Container bindings: on
nestjs-boilerplate all 36 bound repository implementations listed the abstract method as a d=1 caller (284 callers
in total); now 0, the abstract method is under overrides: (248 callers, exactly those 36 fewer).
Queries on sphinx (graph unchanged): reaches on ASTBaseBase._stringify (135 overrides) 26 → 31 dependents (5 via
override), on Builder.write_doc 25 → 26; tests counts unchanged there, since sphinx's calls land on the base
declarations. DirectoryHTMLBuilder.write_doc (inherited) answered "no method matches" and now resolves to
StandaloneHTMLBuilder.write_doc (27 callers, 504 tests). On cg itself tests FrameworkPlugin.contribute goes from 0
to 226 tests (via the plugins' overrides) and impact FlaskPlugin.contribute resolves to the inherited
_PyWebPlugin.contribute. Index times are unchanged within noise (social-app 18.4 → 16.8 s, mattermost 25.7 → 26.4 s,
cal.com api v2 7.2 → 7.0 s, sphinx 7.9 → 7.8 s; single runs).
Exact index per target, Swift availability, re-exports in variant files, C macro gaps (#56)
cg index <repo> before and after, no flags (Rust in exact mode with rust-analyzer, CODEGRAPH_RUST_TARGETS
default auto); cg platforms divergence counts are variants / API surface / missing callee. Index times are one
run each on a shared box.
| Project | Commit | Edges before → after | Divergence before → after | Notes |
|---|---|---|---|---|
| BurntSushi/ripgrep | 3fce3b5 | 25812 → 25845 | extra runs windows + web: 47 exact references; cfg-inactive edges 16 → 2; 16.9 → 33.8 s | |
| alacritty/alacritty | d692748 | 23303 → 23586 | windows 275 + macos 126 exact references; cfg-inactive 123 → 5; 14.5 → 41.0 s | |
| tauri-apps/tauri | 30da1fd | 47065 → 48953 | windows 787 / macos 1869 / android 301 exact references; cfg-inactive 1208 → 137; target runs 149 + 108 + 59 s cold | |
| libuv/libuv (C, heuristic) | 49b1c06 | 61235 → 70877 | 235 / 0 / 85 → 496 / 0 / 43 | 33 macro-generated functions, 29 recovered definitions; calls into src/unix + src/win pairs; 3.4 → 3.5 s |
| dart-lang/http | 47c57df | 11448 → 11529 | 30 / 1 / 1 → 30 / 0 / 0 | export IMPORTS edges; the connect tear-off |
| bluesky-social/social-app | db23528 | 47724 → 47887 | 108 / 9 / 102 → 108 / 5 / 88 | +163 re-export IMPORTS edges |
| clash-verge-rev/clash-verge-rev | 3607e66 | 15720 → 15747 | +27 re-export IMPORTS edges | |
| expo/expo (full monorepo) | c0cac77 | 245593 → 246979 | 738 / 188 / 886 → 738 / 177 / 879 | 88030 nodes; +1376 re-export IMPORTS edges; 105 → 110 s |
- Rust: every lost edge was a
cfg-inactivename match the per-target index contradicts (ripgrep 6, alacritty 22, tauri 343: e.g. a call matched to another type'sload/height), and owner attribution improved (tauri's Windows event loopnew_any_threadnow owns its Windows-only calls). The extra runs roughly double a cold Rust index; rust-analyzer's cache makes later runs cheaper.CODEGRAPH_RUST_TARGETS=0turns them off. - libuv:
UV_LOOP_WATCHER_DEFINE/SOCKOPT_SETTERexpansions are functions now;kqueue.c'suv__io_pollhad swallowed 6 following definitions andwin/tty.c14. Calls to a function defined once insrc/unix/and once insrc/win/(uv_close,uv_tty_reset_mode, ...) had no edge at all, since the file-qualified keys of the two definitions were not seen as one symbol; they now reach both (+9,600 CALLS, mostly from tests and docs examples). 261 more per-platform definition groups (163 unix/win pairs; 2 are same-named functions of separate docs programs). 62 edges were lost: references inside a recovered definition that kept its head only, and calls the main parse had attributed to a swallowing function. - Re-exports: the findings removed on social-app (4 API surface, 14 missing callee), dart-lang/http and expo (15
API surface, 7 missing callee) were re-exports (
export {LockScroll} from 'react-remove-scroll',export { SymbolView } from './SymbolView.ios',const connect = IOWebSocketChannel.connect). Expo gains 4 API-surface findings, all web-only named exports the native variant does not have (AudioPlayerWebre-exported byAudioModule.web.ts). - Swift (expo): 194 declarations with
@availableversions (iOS 17: 53, iOS 18: 41, tvOS 17: 33, iOS 26: 30), 13 deprecated, 316#availablebranches with 254 references in them, 1 declaration unavailable everywhere (BaseModule.init). Spot checks of 8 matched the source. - Expo's targets come from
apps/bare-expo/app.json(macos, ios, android, web).
Protocol links: shared endpoint model (#31)
cg index with no flags and no .cg.yaml, then cg link for the pairs and cg protocols
(Protocol links). The existing links are read through adapters, so the graph and the outputs of
cg link, cg channels and cg bridges were compared before and after on every project below: nodes, edges and all
three outputs are identical. Index time is one run each on a shared box, within the run-to-run spread.
| Project | Commit | Protocols (endpoints / linked) | Checks | cg protocols | Index before → after |
|---|---|---|---|---|---|
immich-app/immich server/ | c5e06dc | http 297 / 0, nest-event-emitter 36 / 0 | 1 external, 35 no_receiver, 1 no_sender | 0.18 s | 11.7 s → 11.4 s |
| saleor/saleor | 8385ca6 | celery 75 / 69, django-signal 8 / 7, http 9 / 0 | 6 no_sender, 1 external, 9 unguarded | 0.35 s | 68.3 s → 70.2 s |
| netbox-community/netbox | 251458b | django-signal 44 / 10, http 956 / 0 | 34 external, 857 unguarded | 0.37 s | 27.4 s → 28.0 s |
| examples bookstore-nest + bookstore-next | – | http 26 / 6, bull 1 / 1, nest-event 2 / 0, nest-event-emitter 1 / 1, nest-rpc 1, nest-ws 1 | 5 no_receiver, 15 no_sender, 17 unguarded, 1 + 1 nest-event | 0.09 s | |
| a private Laravel API + Vue client pair (~29k nodes) | – | http 1,007 / 823, pusher 25 / 22, laravel-event 7 / 6, laravel-queue 7 / 7 | 4 external, 3 + 3 no_receiver, 83 test_sender_only, 91 no_sender, 40 unguarded | 0.3 s | identical graph, cg link 439 / 441 unchanged |
What the findings are, from spot checks:
- unguarded is the same classification as
cg routes --unguarded(the private pair: 40 = 40; netbox: 857 ofcg routes' 864, the rest are admin and unmounted routes, whichcg linkleaves out of its uncalled list too). A side is only judged when the graph holds the other side of that protocol, so a server indexed alone reports nono_senderfor its HTTP routes. - immich: the 35 events are emitted through immich's own
EventRepositoryand received with its own@OnEvent({ name })decorator (built onSetMetadata). Since #101 the object'snameis read: 44 named events, all 35 emitted ones meet their 86 listeners (before: oneevent:?endpoint; 20 sampled listener links correct). Its WebSocket gateway only emits (the web client listens throughsocket.io-client) and its jobs use the sameSetMetadatasystem, so there are no gateway messages or BullMQ jobs to show yet (#32). - bookstore-nest:
order.placedis emitted byClientProxy.emitand the microservice handler listens toorder.shipped; both ends are reported. - Django signals: the first run reported netbox's
post_save[Interface],user_logged_in,request_finished... asno_sender(10) ortest_sender_only(24): Django sends them itself, so adjango.*signal nobody in the repo sends isexternal("sent by the framework") now, as is saleor'spost_migrate. The 10 linked netbox signals are its own (core.signals.job_start, ...) and model signals fired by a resolved model write. - saleor: the 6 senderless Celery tasks are queued from data migrations (
delete_files_from_storage_task,update_discounted_prices_task; migrations are not indexed), by the beat schedule insettings.py(observability_reporter_task) or through a.delaypassed as a value (handle_transaction_request_task.delay). - Socket.IO (first new protocol):
tests/protocol_fixtures(a FastAPI service emitting through anAsyncClient, a Django worker with anAsyncServer) linksorder:createdand the templateorder:{status}, reports the unreceivedorder:cancelled, the externalaudit:orderand the unguardedping, andcg path "route:POST /orders" table:orderscrosses the two services through SENDS_TO / RECEIVED_BY.
AI harnesses: LLM tools, MCP servers and agents (#66)
cg index with no flags, then cg tools (AI tools). Graphs of projects without these SDKs are
unchanged (bookstore-django 207 / 341, netbox 38,028 / 140,181, saleor 43,390 / 210,377 nodes / edges, identical
before and after).
| Project | Commit | Server / declared tools (handled) | Client calls (matched) | Agents | Other |
|---|---|---|---|---|---|
| modelcontextprotocol/python-sdk | 2118f14 | 128 tools (124), 35 resources, 18 prompts | 264 tool / 111 resource / 28 prompt calls (58 / 32 / 6) | – | 11 s |
| openai/openai-agents-python | 81f0ccf | 90 llm tools (42; 48 declared inside tests), 6 MCP tools | 10 MCP tool calls (1) | 497 (6 handoffs) | 50 model calls, 39 s |
| code-graph itself | – | 29 MCP tools (29) | – | – | 29 decorator references replaced |
| modelcontextprotocol/servers | f46d957 | 0 | – | – | TypeScript servers; the Python ones branch on enum members (since #76: the 12 mcp-server-git tools) |
What the findings are, from spot checks:
- python-sdk:
examples/snippets/clients/stdio_client.pycall_tool("add")matches theaddtool of three example servers (Demo,Calculator,audited): the client does not name its server, so every server tool of that name is a candidate. Most unmatched client calls went to servers built inside a factory function (examples/stories/*/server.py:def build(): mcp = MCPServer(...)with nested@mcp.tool()handlers) or inside tests. Since #76 these are endpoints received by the enclosing function: MCP endpoints 584 → 955,no_receivertool calls 30 → 10. - openai-agents-python:
appointment_referral_status_lookup(examples/sandbox/healthcare_support) links the@function_toolhandler to the module building the agent;ask_order_agentis offered in a realtime session config whose handler isagent.as_tool()(not modelled). The first run flagged 7getattr(context, f.name)-style calls in the SDK itself as dynamic dispatch; only runtime tool names (name,tool_call.function.name, ...) count now. - langchain-ai/langgraph (7dc9195) did not finish indexing within 400 s, before and after this change (deep type inference in the Python plugin); not measured.
External systems (#40)
cg index with no flags, then cg external (External systems). The only graph change is additive:
external nodes and their CONNECTS_TO / CONFIGURED_BY / CREDENTIAL_FROM edges; projects without env reads,
connections or settings dicts are unchanged.
| Project | Commit | Systems | New nodes / edges | What was found |
|---|---|---|---|---|
| netbox-community/netbox | 251458b | 3 postgres, 5 redis | +8 / +12 (38,028 / 140,181 before) | DATABASES[default] and REDIS[tasks] / REDIS[caching] in configuration_example.py and configuration_testing.py (loopback hosts: config:<module>.<setting> targets with address_default, literal test password as location only); scripts/smoketest_configuration.py reads POSTGRES_HOST / REDIS_HOST with their password keys. Postgres comes from the dependencies (DATABASE has no ENGINE) |
immich-app/immich server/ | c5e06dc | 1 postgres | +1 / +2 (8,038 / 36,450 before) | DB_URL (protocol from the pg / postgres dependencies); the env schema is a zod DTO, so the main reader is not seen and the one user is src/bin/sync-open-api.ts, which assigns the variable; the compose files are under ../docker |
| outline/outline | 478e812 | 1 postgres, 1 https | +1 / +4 (13,358 / 52,050 before) | DATABASE_URL read by server/config/database.js, resolved through .env.sample to the compose service postgres:5432 (resource outline, credential in the URL); updates.getoutline.com; Redis / S3 / SMTP keys go through an environment.X wrapper and are missed; three example.com origins called only from tests are left out |
| bookstore-django (examples) | – | 1 redis | +1 / +2 (207 / 341 before) | CELERY_BROKER_URL from the environment with a loopback default |
| a private Laravel API + Vue client pair | – | 1 postgres, 1 redis, 1 memcached (API) | +3 / +12 (API); client graph identical | a custom connection with its host and password keys, the Redis keys read by config/database.php / config/reverb.php, the memcached store of config/cache.php; the default connection is sqlite (no network system). Link results identical |
Fixture (tests/external_fixture): a Laravel API, an Express worker and a Django project sharing db / redis
services: after cg link the API's connection:pgsql and the worker's pool() use the same
external:postgres:db:5432, and reaches lists OrderController::index and POST /sync. The fixture secrets
appear nowhere in the database files.
Seen on the way: column nodes from Laravel migrations differ between two runs of the same commit (a few columns attributed to another table), with or without this change.
Swift: selectors, static vs instance, SDK receivers (#70)
Same stores and sources as for #58 (heuristic edges in the files the store covers, against the compiler's):
| Project | Heuristic edges | Agree | Precision | Recall |
|---|---|---|---|---|
| Alamofire/Alamofire | 585 → 602 | 552 → 580 | 0.94 → 0.96 | 0.65 → 0.68 |
| pointfreeco/isowords (server product) | 174 → 180 | 163 → 167 | 0.94 → 0.93 | 0.44 → 0.45 |
| vapor/template | 11 → 9 | 9 → 9 | 0.82 → 1.00 | 0.90 → 0.90 |
| Total | 770 → 791 | 724 → 756 | 0.94 → 0.96 | 0.59 → 0.61 |
Recall goes up because labelled selectors on receivers cg cannot type (client.put(endpoint:), toastCenter.update(id:toast:))
are bound again when exactly one project method fits, also for short names. Precision on isowords moves by two edges:
calls of Dictionary.transformKeys, which isowords declares twice (in two modules); cg merges both into one node and
the compiler's target is the copy without a node, so these real calls count as disagreements. The Alamofire false
positives left (about 50 in the heuristic-only graph, 55 before) are mostly owner differences (HTTPHeaders.init
calling HTTPHeader.init) and Apple-only branches.
Dimillian/IceCubesApp (9efcb16, heuristic only, no Linux build), before → after:
MediaUIAttachmentVideoViewModel.resume: 20 callers → 1 (viewModel.resume());continuation.resume(returning:)insidewithCheckedContinuationis no longer bound.MastodonClient.post: the 10NotificationCenter.default.post(name:object:)calls are gone (64 → 54 edges, allclient.post(endpoint:)).InAppSafariManager.open: 9 → 1 (safariManager.open(url));cg platforms divergencemissing-callee findings 12 → 4 (the 8UIApplication.shared.openfindings are gone, the 4 left are the project's ownclosedefined per#if).- Removed 46 call edges, all false (also
data.write(to:)→DeepLUserAPIHandler.write,UserDefaults.standard.register(defaults:)→SoundEffectManager.register); added 122 (labelled selectors on untyped receivers,MediaContainer.pending(...)static factories,extension Viewmodifiers on SDK view chains, members of nested types).calls_sdk_selector: 2,132 calls left unbound by the SDK-selector rule. cg startersstill suggestsMastodonClient.get/.post, now with real callers only (76 / 42 direct callers).
Swift Testing, XCTest and Kotlin test cases (#71)
Test cases in cg tests, before → after (before, Swift and Kotlin test functions were test entries but the header
counted only test nodes, so every project read "of 0 test cases"; all graphs heuristic, sources at the commits
named):
| Project | Before | After (per framework) |
|---|---|---|
swiftlang/swift-testing (b5c410c) | 0 | 1,010 (swift-testing 885, xctest 125) |
Alamofire/Alamofire (bda9ed5) | 0 | 834 (xctest 761, swift-testing 73) |
Dimillian/IceCubesApp (9efcb16) | 0 | 65 (xctest 40, swift-testing 25) |
pointfreeco/isowords (c727d3a) | 0 | 237 (xctest 237) |
android/nowinandroid (a49ed25) | 0 | 232 (junit4 232) |
touchlab/KaMPKit (4af0200) | 0 | 28 (kotlin-test 24, xctest 4) |
spring-petclinic-kotlin (c77f77b) | 0 | 41 (junit5 41) |
ktorio/ktor-samples (1c9df7c) | 0 | 162 (kotlin-test 149, junit4 9, xctest 4) |
- Swift Testing cases are new (863 on swift-testing, 73 on Alamofire, 25 on IceCubesApp: every uncommented
@Testin Alamofire and IceCubesApp). On swift-testing's own suite, 137 are parameterized, 143 types are@Suites and 29 nodes carry tags. Its raw-identifier suites (struct `ABI.EncodedEvent Tests`,@Test func `Decode issueRecorded`()) parse now: 2,831 → 2,953 nodes and 6,952 → 7,159 edges. 59 functions that a parse error had turned into free functions are methods of their suite again. The@Testlines still missing are an@Testinside#ifin front of its function and a few declarations the grammar cannot read. - XCTest: 3
test*methods are no longer test cases, all helpers with parameters (IssueTests.testThrowing(_:producesIssueMatching:),BuiltInConformances.test(_:)on swift-testing,TimelineFilterTests.testCodableOn(filter:)on IceCubesApp). No other test case was lost, on any project. - Alamofire
cg tests Instant: 0 direct, 7 transitive (of 0 test cases) → 3 direct (the threeInstantTestscases), 8 transitive (a Swift TestingRequestInterceptorTestscase joins the XCTest ones), of 834. - Kotlin numbers are unchanged; they now have a framework from the file's imports. Kotlin
@Testlines without a test case: benchmarks insrc/main(nowinandroid, not test code by path),@Testinside lint-test source strings, and on ktor-samples 30 expression-bodied tests (fun anything() = testApplication { ... }) in three httpbin files where the Kotlin grammar drops the class body's functions.
Python inference on self-referencing attributes (#78)
langchain-ai/langgraph (7dc9195, 461 .py files) did not finish indexing in 400 s. PregelLoop re-assigns
self.checkpoint_pending_writes from list comprehensions over itself, several per method with one shared loop name
w; inferring the attribute re-entered it once per binding of w at every level, with a fresh per-function guard
each time, until the depth limit of 40, so the work grew exponentially. Attribute types (self.x assignments and
class attributes) are now memoised per class and attribute, an attribute already being inferred further up the stack
is unknown, and one top-level inference stops after 5,000 steps. A result computed while a cycle was cut is not
memoised, so a partial answer is not reused elsewhere. The plugin stats report the cuts as
inference_limits (attr_cycles, budget_exhausted).
- langgraph: indexes in 14 s, 9,774 nodes / 38,141 edges, 34 cycle cuts, budget never reached.
- The same graphs before and after (every node and every edge with kind and confidence identical), and times within noise or faster, on django (259 cuts), flask, saleor, netbox (186; 39 → 29 s), pytest (372), sphinx (20), ansible (9; 21 → 16 s), beets, flake8, httpie, microblog, mkdocs, pylint (17), opentelemetry-python (1,075) and full-stack-fastapi-template. The work budget was not reached on any of them.
tests/test_python_infer_cycles.pyhas the langgraph shape in a dozen lines: it indexed for more than 60 s before (stopped by the test's alarm) and takes 0.2 s now.
Laravel graphs independent of the hash seed (#79)
A property read on a receiver that can be several Eloquent models, in a ?? fallback chain, took the table of the
first model a set iteration produced, so two indexes of one commit could differ (in the issue's repro, seed 3 picked
incomes where seeds 1, 2, 4, 5, 6 picked expenses). Each such step now has one FALLS_BACK_TO edge per candidate
model's column, all with the step's order, ambiguous: true and candidates; the candidates are those whose
migrations declare the column, else those whose table the migrations create. A single-model read is unchanged.
Every graph below indexed under PYTHONHASHSEED 1 and 2 is identical (nodes, edges, attributes), and against the
previous version only the multi-model steps changed:
| Project | Edges before (seed 1) → after | What changed |
|---|---|---|
| koel, solidtime, firefly-iii, pixelfed, vito, panel, UNIT3D, akaunting, librenms, laravel.io | unchanged | no multi-model fallback step |
| monica | 34,205 → 34,211 | 3 resolutions (UpdateVCard, UpdateVCalendar, GetEtag): distant_etag on vcard_resources and vcalendar_resources, before vcard_resources only |
| coolify | 83,087 → 83,088 | Show::copyValue: value on environment_variables and shared_environment_variables, before the latter only |
| invoiceninja | 172,259 → 172,269 | line_items and design_id on credits, invoices, purchase_orders, quotes and recurring_invoices (before purchase_orders only); $entity->client on clients and vendors (no migration declares either column; before vendors only) |
tests/test_laravel_determinism.py indexes the issue's fixture under two seeds that differed before and compares the
whole graph.
Raw TCP / UDP sockets and UDP application protocols (#39)
Socket and UDP-application edges on the corpora that use them (indexed at the commits shown):
| Project | Endpoints (paired) | Edges | What pairs |
|---|---|---|---|
| mini-redis 3d93b42 | tcp 1 (1) | 1 RECEIVED_BY, 4 SENDS_TO | the pub / sub / hello_world examples and the CLI (clap default_value_t = DEFAULT_PORT) reach server::run through Client::connect's call sites on 6379 |
| tokio examples 5d5cd8b | tcp 3 (2), udp 1 (1) | 8 RECEIVED_BY, 3 SENDS_TO | hello_world with chat / graceful-shutdown on 6142, proxy with the 8080 servers, udp-client with echo-udp |
| statsd f7157b8 | tcp 2, udp 1 (1) | 3 RECEIVED_BY, 1 SENDS_TO | the Python example client with the Node UDP server on 8125; the TCP server and admin listeners (8125, 8126) have no client in the repo; the PHP example reads its port from an ini file and the Java / Kotlin clients take it as a constructor parameter with no caller, so they are counted as unresolved |
| libuv | tcp 5 (1), udp 6 (2) | 99 RECEIVED_BY, 99 SENDS_TO | the tests on TEST_PORT 9123 (and TEST_PORT_2 9124); 10 ephemeral binds are counted, not linked |
| python-zeroconf f0c27b3 | mdns 13 (1) | 1 RECEIVED_BY, 5 SENDS_TO, 84 TEST_CALLS | examples/async_registration.py advertises _http._tcp, async_browser.py / async_service_info_request.py browse it |
| aiocoap f867dc4 | coap 29 (3) | 12 RECEIVED_BY, 2 SENDS_TO, 35 TEST_CALLS | server.py's /time and /other/block with clientGET.py / clientPUT.py; 6 doctest examples in docstrings are skipped |
tauri gains one edge, the CLI's built-in dev server listening on its default port 1430 (port.unwrap_or(1430)).
No edge changed on redis, httpie, flask, opentelemetry-python, the MCP python-sdk and servers, electron-fiddle,
alacritty, ktor-samples, django, netbox, immich, outline, saleor, element-x-ios, eslint and ansible (whose SSH_AUTH_SOCK Unix-socket paths
are not TCP ports). A random 20 of the new edges were checked by hand against the source: 20 correct (an earlier
sample of 20 found a wrong / path for a concatenated CoAP URI, fixed before this one). tests/test_sockets.py covers
each language on tests/sockets_fixture and the BSD / libuv C calls on tests/sockets_c_fixture.
gRPC services from .proto contracts (#33)
gRPC edges on the example trees of the gRPC implementations and on the OpenTelemetry demo (indexed at the commits shown; "paired" endpoints have both a server and a client in the graph):
| Project | Services / methods | Paired | Edges | Notes |
|---|---|---|---|---|
grpc examples/ 724b3cc | 11 / 32 | 16 | 77 RECEIVED_BY, 92 SENDS_TO | C++ and Python; 17 C++ servers whose class the C/C++ parser lost after a file-level ABSL_FLAG(..); line were counted as server_class_without_methods until #131 (now 94 RECEIVED_BY); the PHP, Node and other trees are not indexed from this root. The released code stopped on this tree with an IndexError in the socket scan (fixed) |
grpc examples/php with examples/protos | 6 / 18 | 5 | 5 RECEIVED_BY, 5 SENDS_TO | Greeter / RouteGuideService extends ..Stub, clients new ..Client(..); echo/client.php calls at file level, outside any function |
grpc-node examples/ e742b11 | 6 / 13 | 10 | 30 RECEIVED_BY, 34 SENDS_TO | plain-JS programs (not indexed before): addService handler maps, proto-loader new pkg.Svc(..) and static-codegen clients |
tonic examples/ 2681a7e | 7 / 15 | 7 | 34 RECEIVED_BY, 36 SENDS_TO | trait impls and XClient::connect; reflection / health services have no client in the tree |
grpc-kotlin examples/ fffe032 | 5 / 8 | 8 | 8 RECEIVED_BY, 6 SENDS_TO, 5 TEST_CALLS | CoroutineImplBase servers, CoroutineStub clients and server tests |
grpc-dart example/route_guide, helloworld bdbcf3b | 1 / 4, 1 / 1 | 4, 1 | 6 RECEIVED_BY, 7 SENDS_TO | ServiceBase servers, RouteGuideClient fields |
grpc-swift Examples/v1 + Protos 65d0084 | 15 / 42 | 9 | 13 RECEIVED_BY, 10 SENDS_TO | providers, Echo_EchoAsyncClient parameters and Routeguide_RouteGuideAsyncClient members (makeRecordRouteCall) |
| connect-es examples 1be3b33 | 2 / 5 | 2 | 12 RECEIVED_BY, 38 SENDS_TO, 10 TEST_CALLS | router.service(ElizaService, ..) and createClient(ElizaService, ..) across the framework examples |
| opentelemetry-demo 7ea09b8 | 11 / 21 | 1 | 2 RECEIVED_BY, 12 SENDS_TO | the TypeScript frontend's gateways and the Python recommendation service; the C++ currency, plain-JS payment and Rust shipping services sit in sub-projects that are not indexed from the repo root, and Go / C# / Java / Ruby / Elixir have no plugin |
No edge or node changed on the 20 corpora checked for #39 (electron-fiddle, immich, saleor, django, flask, httpie,
outline, eslint, mcp-servers, netbox, opentelemetry-python, python-sdk, python-zeroconf, tauri, alacritty, redis,
aiocoap, ansible, ktor-samples, element-x-ios). A random 20 of the gRPC edges above were checked by hand against the
source: 20 correct. tests/test_rpc.py covers Python, Node / TypeScript, Connect, tonic and Kotlin on
tests/rpc_fixture (including two packages with a RouteGuide service each) and grpc++ on tests/rpc_cpp_fixture.
Thrift, tRPC and JSON-RPC (#33 part 2)
| Project | Endpoints (paired) | Edges | Notes |
|---|---|---|---|
thrift tutorial/ 50bbda1 | 5 (4) | 15 RECEIVED_BY, 18 SENDS_TO | C++ CalculatorIf / CalculatorClient, Python Processor(handler) / Client, Dart; getStruct stays on shared.SharedService |
the same tutorial's nodejs, rs, php trees with the .thrift files | 5 (4), 5 (5), 5 (0) | 10 / 10, 5 / 7, 5 / 0 | Node createServer maps and top-level clients (module level); Rust SyncHandler impls and factory functions; PHP clients are top-level scripts without a module node. Java has no plugin |
| create-t3-turbo 8f945b7 | 6 (4) | 6 RECEIVED_BY, 12 SENDS_TO | satisfies TRPCRouterRecord routers mounted in appRouter, trpc.post.all.queryOptions() clients |
| cal.com 54343aa | 174 (122) | 174 RECEIVED_BY, 226 SENDS_TO | 33 routers, imported mounts (eventTypesRouter as heavyEventTypesRouter) and procedure variables; 221 of 224 trpc.viewer.. chains in the app name a declared path |
| jayson 4c368aa | 28 (8) | 39 RECEIVED_BY, 73 SENDS_TO, 79 TEST_CALLS | method maps and new jayson.Server(..); 14 handlers wrapped in jayson.Method(..) unresolved; tests name methods the server lacks (add_4), which stay send-only |
jsonrpsee examples/ e6d59a0 | 20 (4) | 33 RECEIVED_BY, 32 SENDS_TO | #[rpc(namespace = "state")] traits (state_getKeys, exact), register_method closures (heuristic, the registering function) |
On the 20 #39 corpora the only change is in tauri: the CLI's jsonrpsee options method (register_method,
heuristic) and the three client.request("options", ..) calls that reach it, all correct; the JSON-RPC scan skips
MCP files, so mcp-servers and python-sdk are unchanged. On the gRPC corpora above only the connect-es top-level
clients changed (6 SENDS_TO, 1 TEST_CALLS from module nodes). A random 20 of the new Thrift / tRPC
/ JSON-RPC edges were checked by hand: 20 correct. tests/test_rpc.py covers Thrift (Python, Node, C++), tRPC
(inline, nested, imported, procedure-variable mounts and clients) and JSON-RPC (jayson, jsonrpcserver, payloads,
jsonrpsee) on the same fixtures.
C/C++: file-level macro statements and lock annotations (#131)
A file-level ABSL_FLAG(..); made tree-sitter-cpp read a function definition that ran to the next }, swallowing
the class after it. Thread-safety annotations after a declarator (void Write() ABSL_EXCLUSIVE_LOCKS_REQUIRED(&mu_) {) became the declarator. Both are now blanked before parsing. Heuristic types and unique-name member calls stay
within the files a caller can see, and the classes of separate programs (a main() per file) are not variants.
Before -> after on the C/C++ corpora (heuristic mode):
| Project | Edges | Nodes | Notes |
|---|---|---|---|
grpc examples/ 724b3cc | 2,425 -> 2,088 | 1,331 -> 1,397 | 28 classes, 48 methods and 32 fields recovered; gRPC RECEIVED_BY 77 -> 94 (server_class_without_methods 17 -> 0). 382 USES_TYPE edges from each RunServer to the GreeterServiceImpl of every other example are gone. 25 functions named ABSL_FLAG, a stray Run / HandleRpcs (now ServerImpl:: methods) and 14 locals read as globals are gone |
| leveldb 7ee830d | 8,107 -> 8,260 | 3,036 -> 3,075 | 21 methods and 28 fields recovered (GUARDED_BY, LOCKS_EXCLUDED, SCOPED_LOCKABLE MutexLock); +71 CALLS (mutex_.Lock() and the like). 22 correct calls to CondVar::Wait / HandleTable::Remove became ambiguous with a recovered TestState::Wait / PosixLockTable::Remove and are dropped |
| googletest 988ea2c | 4,609 -> 4,529 | 2,692 -> 2,692 | 19 functions GTEST_LOCK_EXCLUDED_ are now their Mock::.. methods. 111 cross-file guesses are gone (.str() -> a test mock's str 57, .size() 25) and 52 calls to the recovered methods are added |
| libuv 49b1c06 | 48,893 -> 48,188 | unchanged | 702 USES_VALUE edges mirrored between the loop globals of the separate docs/code/* programs are gone |
thrift tutorial/ 50bbda1 | 183 -> 178 | unchanged | client.ping() in the C++ client no longer binds to the server program's CalculatorHandler |
| redis b540ca4, flutter/samples a05867d | unchanged | unchanged |
A random 20 of the added edges were checked by hand against the source: 20 correct. The removed edges were
spot-checked by group (the cross-program USES_TYPE / USES_VALUE mirrors and the .str() / .size() / handler
guesses were all wrong before), except the 22 leveldb calls above.
GraphQL root fields (#34 part 1)
| Project | Root-field endpoints | Edges | Notes |
|---|---|---|---|
| saleor 8385ca6 (graphene) | 455 (448 in schema.graphql) | 446 RECEIVED_BY, 6,106 TEST_CALLS | Query(AccountQueries, ...) / Mutation(...) of build_federated_schema(..): 333 X.Field() mutations (262 own perform_mutation / mutate, 71 inherited, heuristic), 89 resolve_*, 24 resolver=; the two schema fields without a resolver are federation's _entities / _service; the 7 extra endpoints are test schemas. Tests post QUERY = """...""" constants and per-test query locals |
| saleor-dashboard f9093f2 (Apollo + graphql-codegen) | 457 (schema copy) | 625 SENDS_TO, 4 TEST_CALLS | callers of 672 generated use<Op>Query / Mutation hooks and client.query({query: XDocument}); 25 calls to hand-written use..Query wrappers name no operation (stats only) |
fullstack-tutorial fca98de, final/server + final/client | 6 + 8 | 6 RECEIVED_BY, 6 TEST_CALLS; 7 SENDS_TO | apollo-server resolver map (plain Node, now indexed); useQuery / useMutation clients; @client fields skipped; cg link joins all 6 server fields with their client senders |
| strawberry-examples ff2bd0d | 8 | 9 RECEIVED_BY | four apps; @strawberry.field / mutation / subscription roots of strawberry.Schema(..) |
| netbox 251458b (strawberry-django) | 260 | 5 TEST_CALLS | strawberry_django.field() root fields are served by the default resolver (no function); auto_camel_case=False keeps snake_case |
nest sample/ 12, 22, 23, 33 (35142c3) | 4, 6, 5, 5 | 20 RECEIVED_BY | endpoint twins of the route:GRAPHQL routes |
The 20 #39 corpora are unchanged except netbox (the 5 TEST_CALLS above and 1 endpoint at snapshot time; 260
endpoints with the final strawberry-django rule). A random 20 of the new edges across these corpora were checked by
hand against the source: 20 correct. An earlier sample found per-test query = """...""" locals bound to the first
one in the file; the binding now prefers the calling function's own assignment, then module level, then an import.
tests/test_graphql.py covers the document parser, SDL, resolver maps, Apollo clients, a codegen hook, Nest twins in
cg protocols, graphene, strawberry, ariadne and Python clients on tests/graphql_fixture.
Job queues: Celery, RQ, Dramatiq, Bull, Laravel, Messenger (#36)
| Project | Job / queue endpoints | Edges | Notes |
|---|---|---|---|
| saleor 8385ca6 (Celery in Django) | 75 jobs, 2 queues | 75 RECEIVED_BY, 2 QUEUE_ROUTES, 1 SENDS_TO | Every task gets an endpoint twin of the Django plugin's job node. The plugin's 78 DISPATCHES are not repeated and its entries are unchanged. observability (@app.task(queue=OBSERVABILITY_QUEUE_NAME)) is no_consumer: the only worker command in the repo (pyproject celery worker -E) consumes the default queue. The other queue=settings.X names are env settings with no default |
| authentik 2c6f657 (Dramatiq) | 71 actors | 71 RECEIVED_BY, 60 SENDS_TO, 82 TEST_CALLS | @actor(description=_(..)) decorators span lines. Sends are .send() / .send_with_options() plus ScheduleSpec(actor=x) registrations (heuristic): 46 of 71 actors are linked. Most of the rest are sync actors held as provider attributes (self.sync_objects_task). Workers start through ak worker, so no consumer is known |
| netbox 251458b (django-rq) | 1 job, 3 queues | 1 RECEIVED_BY, 1 QUEUE_ROUTES, 1 SENDS_TO | rq_queue.enqueue('extras.webhooks.send_webhook'). The systemd netbox-rq.service runs rqworker high default low. JobRunner.enqueue jobs go through netbox's own wrapper and are not followed |
| microblog a975ef6 (RQ) | 2 jobs, 1 queue | 1 RECEIVED_BY, 1 QUEUE_ROUTES, 2 SENDS_TO, 1 MATCHES_ENDPOINT | current_app.task_queue.enqueue(f'app.tasks.{name}') is a template matching app.tasks.export_posts (heuristic). The microblog-tasks queue is consumed by the Procfile worker and by the supervisor program |
| flask (examples/celery) | 3 jobs | 3 RECEIVED_BY, 3 SENDS_TO | Celery without Django: @shared_task plus .delay() from the views |
| pixelfed c7dc513 (Laravel + Horizon) | 127 job twins, 15 queues | 127 RECEIVED_BY, 87 QUEUE_ROUTES, 130 SENDS_TO | X::dispatch(..)->onQueue('feed') (multi-line chains, self::dispatch, Bus::batch): 157 queue sends, 1 left unresolved (a ->catch() closure in between). The 4 Horizon supervisors consume all 15 queues, so none is no_consumer. Entries are unchanged |
| outline 478e812 (Bull) | 4 queues, 4 queue-wide job templates | 4 RECEIVED_BY, 4 QUEUE_ROUTES, 7 SENDS_TO | The createQueue(name) factory and the globalEventQueue() getters bind the queues. Sends are .add(data) (queue only); receivers are the .process(fn) blocks of the worker service. Task classes dispatched by class name (BaseTask.schedule) are a follow-up |
| laravel.io 24be489, koel 295d8c1 | 9 + 9 job twins | 9 + 9 RECEIVED_BY | Only ShouldQueue classes get twins: 33 + 11 App\Jobs classes run synchronously |
nest sample/26-queues (35142c3) | 1 job twin | 1 RECEIVED_BY, 1 QUEUE_ROUTES | @Process('transcode') -> endpoint:job:bull:audio:transcode, merged into the job node with the controller's DISPATCHES |
The other 17 #39 corpora are unchanged (same nodes and edges; immich's BullMQ wrapper is a follow-up), and no corpus loses an edge. In saleor, netbox, flask
and microblog the only entry rows added are those of the new endpoints. authentik's actors are new
message_handler entries, so the entry counts of the code they reach change. A random 20 of the 320 new edges were
checked by hand against the source: 20 correct (actor and task definitions, .send / send_with_options / actor=
call sites, test sends). For part 2, a random 20 of the 476 new edges of pixelfed, outline, laravel.io and koel were checked: 20 correct.
An earlier part-2 sample found a twin for a laravel.io App\Jobs class without ShouldQueue (a synchronous job);
twins now need ShouldQueue. The queue edges were first RECEIVED_BY. On pixelfed that let an http route reach every
job of the queue it sends to, and changed 5,485 entry rows; QUEUE_ROUTES does not propagate, and the entry rows are
now unchanged. tests/test_jobs.py covers the cross-repo cases on tests/jobs_fixture:
- a Flask shop's
send_task("billing.charge")links to the billing worker's task withcg link, whilebilling.refundstaysno_receiver; - a
task_routesqueue missing from the Procfile's-Qisno_consumer; - RQ import paths and an f-string template resolve to the mailer repo's functions at link time;
- the compose
rq worker/dramatiq -Qconsumers are found; - Django twins keep their DISPATCHES.
- a BullMQ
emails.add("welcome")links to another repo'snew Worker("emails", fn)through theemails:{name}template; - Laravel
$queue/->onQueue()sends and Horizon consumers; - Symfony Messenger handlers,
messenger.yamlrouting andmessenger:consumeworkers.
Message brokers: Kafka, AMQP, Redis, MQTT, NATS (#35 part 1)
| Project | Endpoints | Edges | Notes |
|---|---|---|---|
rabbitmq-tutorials 6771f3e, python / php / javascript-nodejs | 9 / 9 / 10 | 6 SENDS_TO, 6 RECEIVED_BY, 3 MATCHES_ENDPOINT each (js: 7 RECEIVED_BY) | The six tutorial topologies in three languages. Hello / work queues (queue:hello, queue:task_queue) and RPC (queue:rpc_queue) pair by queue name. Fanout logs, direct direct_logs and topic topic_logs exchanges reach the consumers through the bindings of their server-named queues (pika result.method.queue, amqplib q.queue, php-amqplib list($queue_name, ,)). Binding keys come from argv, so the bindings are <exchange>/# (heuristic). The RPC reply (props.reply_to) and the callback queue held in self.callback_queue are counted as unresolved. The JS directory has no jsconfig, so it was indexed with one added (plain JS without a config was not indexed then; since #136 it is, with the same edges) |
| nats-by-example 232213c, Python and Node examples | 9 + 8 | 17 + 10 SENDS_TO, 4 + 1 RECEIVED_BY, 4 + 4 MATCHES_ENDPOINT | greet.* subscriptions match greet.joe / greet.bob publishes and requests. Nested cb=greet_handler functions have no node of their own, so the receiver is the enclosing main. JetStream publishes have no core subscriber (no_receiver; stream consumers are part 2) |
kafkajs 55b0b41 examples/ | 1 | 1 SENDS_TO, 1 RECEIVED_BY | producer.send({topic, messages}) and consumer.subscribe({topic}) with the shorthand { topic } (the examples' require('../index') was pointed at kafkajs) |
| outline 478e812 | 4 | 2 SENDS_TO, 2 RECEIVED_BY, 2 MATCHES_ENDPOINT, 1 TEST_CALLS | ioredis publish(\${CHANNEL_PREFIX}:${documentId}`)matchespsubscribe(`${CHANNEL_PREFIX}:*`). 14 .publish(calls on outline's own classes with apublish method (document.publish(ctx, ..)`) are skipped as wrapper calls |
| librenms 56761508 (php-amqplib) | 1 | 1 SENDS_TO | The Canopsis alert transport publishes to the topic exchange canopsis.events with the key '.' . $msg_body['resource'] -> canopsis.events/.{resource} (heuristic); the consumer is outside the repo. A commented-out assignment of the key is ignored |
| nest 35142c3 | 1 | 1 TEST_CALLS | An e2e spec's raw ioredis pub.publish(\${channel}.reply`). The microservices package's own Redis / RMQ / MQTT / NATS / Kafka transports take channel and queue names at run time (counted as unresolved). graphql-subscriptions pubSub.publish('catCreated')` in the samples is in-process and is not taken for Redis |
| zigbee2mqtt 344b6e0 | 0 | none | Every publish goes through the app's own class Mqtt (this.mqtt.publish("bridge/event")), which prefixes the base topic, so 87 wrapper calls are skipped instead of recorded under the wrong topic. The wrapper's own client calls use the run-time topic only (unresolved). Wrappers are part 2 |
The other #39 corpora are unchanged: immich, opentelemetry-demo, aiocoap, alacritty, ansible, authentik, django,
electron-fiddle, element-x-ios, eslint, flask, httpie, koel, ktor-samples, laravel.io, mcp-servers, microblog,
netbox, opentelemetry-python, pixelfed, python-sdk, python-zeroconf, redis, saleor and tauri, and so is invoiceninja. opentelemetry-demo's Kafka
producer is Go and its consumer Kotlin (part 2). A random 20 of the 95 new edges of the tutorial, example and outline rows were checked
by hand against the source. In the first sample, 2 were wrong: a nested def callback in receive_logs.py and
receive_logs_direct.py resolved to the same-named function of worker.py. A handler in another file is now taken
only when it is imported. In a second sample, after the fix, all 20 were correct. tests/test_brokers.py covers
tests/brokers_fixture:
- a TS orders API producing to Kafka (enum topic,
sendBatch, env default), a topic exchange, the default exchange, Redis, MQTT and NATS request; - a Python fulfilment worker consuming them (confluent-kafka with group and imported constants, a pika server-named
queue bound with
order.eu.*, redis-pysubscribe(**{ch: handler})andpsubscribe, pahoon_message, nats-py queue group); - a Laravel notifier (
Redis::publish(self::CHANNEL),Redis::subscribe, php-amqplib); - the
cg linkof the API and the worker (order.{region}.created->order.eu.*heuristic,stock.check.{sku}->stock.check.*,orders.cancelledno_receiver).
Message brokers: Kotlin and Rust clients, Spring listeners (#35 part 2)
| Project | Endpoints | Edges | Notes |
|---|---|---|---|
rabbitmq-tutorials 6771f3e, kotlin-java-client | 11 | 7 SENDS_TO, 7 RECEIVED_BY, 4 MATCHES_ENDPOINT | The RabbitMQ Java client from Kotlin: queueDeclare().queue server-named queues, DefaultConsumer objects passed to basicConsume, and exchange names in companion constants (EmitLog.EXCHANGE_NAME, taken from the file that declares the class, because every tutorial declares its own EXCHANGE_NAME). The tutorials' top-level fun main functions share one node id in the default package, so most edges are on the file node. The RPC reply to replyTo is unresolved |
rabbitmq-tutorials kotlin (kourier) | 9 | 6 SENDS_TO, 6 RECEIVED_BY, 3 MATCHES_ENDPOINT | Named arguments (exchange = "logs", routingKey = ""), queueDeclared.queueName, keys from a loop (<exchange>/#, heuristic) |
rabbitmq-tutorials rust-lapin | 9 | 6 SENDS_TO, 6 RECEIVED_BY, 3 MATCHES_ENDPOINT | The same six topologies with lapin: queue.name().as_str() bindings, ExchangeKind::Fanout / Direct / Topic. rust-amqprs (argument builders such as BasicPublishArguments::new(..)) gives 5 unresolved sends and no edges |
| nats-by-example 232213c, Rust examples | 26 | 40 SENDS_TO, 7 RECEIVED_BY, 7 MATCHES_ENDPOINT | async-nats publish / request / subscribe; format!("cars.{}", i) publishes match cars.>. A loop over a subject array is unresolved (2) |
opentelemetry-demo, src/fraud-detection (Kotlin) | 1 | 1 RECEIVED_BY | KafkaConsumer(props).apply { subscribe(listOf(topic)) } with val topic = System.getenv("KAFKA_TOPIC") ?: "orders" (heuristic) and attrs.group fraud-detection from GROUP_ID_CONFIG. The producer is Go, which has no plugin |
The Rust examples of nats-by-example were copied into one crate (src/bin/<example>.rs) for indexing. The other
Kotlin and Rust corpora have no broker clients and get no broker edges: KaMPKit, nowinandroid, spring-petclinic-kotlin,
element-x-android, bitwarden-android, grpc-kotlin, ktor-samples, tokio, tonic, mini-redis (its own client crate is
not taken for redis-rs), jsonrpsee, ripgrep, clash-verge-rev, alacritty and tauri. The part 1 corpora (JS / TS,
Python, PHP) are unchanged as well. A random 20 of the 103 new edges of the tutorial, nats-by-example and
fraud-detection rows were checked by hand against the source; all 20 were correct. tests/test_brokers.py adds:
- a Kotlin / Spring billing service:
@KafkaListenerwithgroupId,@RabbitListenerwith aQueueBindingon a topic exchange and withqueues,KafkaTemplate.send(Topics.INVOICES),basicPublishto agetenv ?: "d"exchange, a jnats publish with a${x}template; - a Rust ledger: lapin
basic_publishwithformat!, aqueue.name()binding, rdkafkasubscribe(&[topic.as_str()])withenv::var(..).unwrap_or_else(..); - the
cg linkof the two (billing.invoiceslinked).
Plain JavaScript without tsconfig / jsconfig (#136)
| Project | Before | After | Notes |
|---|---|---|---|
| request 3c0cddc | no JS program | 74 modules, 195 functions, 14 http nodes; 1,064 edges besides CONTAINS (816 TEST_CALLS, 215 CALLS) | Plain CommonJS at the root (index.js, lib/, tests/), no typescript dependency. Exactly the graph a hand-written jsconfig.json gives (1,064 of 1,064 edges equal) |
| node-gyp 399f6fa | Python only (gyp) | plus 33 JS modules, 117 functions, 43 methods, 121 tests; 845 JS edges | bin/ and lib/ are the source dirs; commitlint.config.mjs, eslint.config.js and .github/scripts are left out (coverage lists them as excluded / unmapped). The Python graph is unchanged |
rabbitmq-tutorials javascript-nodejs | no JS program | 12 modules, 14 functions, 16 broker edges | Identical to the #35 run with a jsconfig added (same nodes and edges) |
rabbitmq-tutorials javascript-nodejs-stream, kafkajs examples/ | no JS program | 4 modules each | Root-level example scripts |
Unchanged (same nodes and edges): django, sphinx, koel, thrift, full-stack-fastapi-template (other-language roots
whose package.json only carries tooling or a frontend workspace), and statsd, jayson, kafkajs, eslint,
cordova-toast, capacitor-plugins, create-t3-turbo, ws, express and amqplib, which one of the earlier rules already
picked up. A scan of every corpus root finds no other project that the new rule adds. A random 20 of the 1,909 edges
added in request and node-gyp were checked by hand against the source. 15 were correct and 1 was imprecise: request.get(..)
resolves to request, because get is assigned at run time. 4 were wrong, all from one pattern the JS extractor
already had: a variable that holds a call with a callback (var server = http.createServer(function (req, res) {..}))
becomes a function node, and its later uses (server.url, s2.listen(..), return l) are recorded as calls of it.
A jsconfig-configured index gives the same edges; the pattern is tracked in #138. tests/test_plain_js.py covers
tests/plainjs_fixture: a CommonJS app (functions, require calls, a mocha test, an amqplib endpoint; its
webpack.config.js is ignored), an ESM library ("type": "module", exports), and a Python project with a tooling-only
package.json, which gets no JS program.
Socket.IO in JS / TS (#32 part 1)
| Project | Endpoints | Sends / receivers | Linked | Notes |
|---|---|---|---|---|
| excalidraw-room 03ff435 + excalidraw ed10ac7 | 11 (after cg link) | 11 / 10 | 10 of 11 | Room server and collab client. WS_EVENTS.* constants and the volatile ? A : B event pair resolve; broadcast-unfollow really has no listener (no_receiver) |
| immich c5e06dc | 21 | 31 / 17 | 12 | 30 of the sends go through the clientSend / clientBroadcast / serverSend wrappers and are resolved at their callers. The web listeners are chained (websocket.on(..).on(..)) or come through $lib/stores/websocket. Most unmatched events are only listened to by the Dart mobile app (part 3) |
| outline 478e812 | 54 | 11 / 54 | 11 | The server sends most events as emit(event.name, ..), which is not a literal (left out) |
| open-webui 8bd8b4f | 19 | 34 / 16 | 6 | The python-socketio server is created under if WEBSOCKET_MANAGER == 'redis' and was not found before. It now pairs with the TS collaboration client. The rest of the client lives in .svelte files, which are not scanned |
| nest 35142c3 | 10 (tests only) | 18 TEST_CALLS | n/a | The net / TCP sockets of @nestjs/microservices and httpServer.on('connection') produce nothing |
| socket.io 1eaa582 | 19 (tests only) | 19 TEST_CALLS | n/a | Only the test suites use the public API |
Unchanged (same nodes and edges): ws, express, request, node-gyp, mocha, pino, nodemailer, amqplib, kafkajs, jayson,
commander.js, electron-fiddle, nestjs-boilerplate, node-express-boilerplate, zigbee2mqtt, mcp-servers, elk,
grpc-node, opentelemetry-demo, create-t3-app, saleor-dashboard, mattermost-mobile, social-app, cal.com and
full-stack-fastapi-template. No edges were removed anywhere. A random 20 of the 154 new JS / TS edges in the five
projects above were checked by hand against the source, and all 20 were correct. They include multi-line
socket.on(\n "event", ..) calls, a WS_EVENTS.USER_FOLLOW_CHANGE constant, wrapper sends with their room
argument, and immich's server-to-server server.on('AppRestart'). tests/test_realtime.py covers
tests/realtime_fixture: a server with a namespace, middleware, rooms, an ack, a template event, an emit wrapper and
a net socket that is not Socket.IO; a socket.io-client app with chained listeners; and a Nest gateway with a guard.
Both pairs are linked with cg link, and a python-socketio server created under a module-level if is also covered.
WebSocket connections and SSE (#32 part 2)
| Project | New | Notes |
|---|---|---|
| outline 478e812 | route:WS /collaboration/{rest*}, 1 test client | new WebSocket.Server({ noServer: true }) behind req.url?.startsWith(path) in the upgrade handler: a prefix route (heuristic) handled by init. The collaboration client is a Hocuspocus provider, not a WebSocket call |
| zigbee2mqtt 344b6e0 | route:WS /{path} | Two new WebSocket.Server(..) with path: posix.join(this.baseUrl, "api"); the path stays unresolved (heuristic) and both route to Frontend.onWebSocketConnection |
| coolify 0ed423a | http:WS /terminal/ws | The terminal client builds ${protocol}://${host}${port}${path} from an object literal; the server in docker/coolify-terminal/ is outside the indexed tree |
| aiocoap f867dc4 | route:WS / | websockets.asyncio.server.serve(functools.partial(self._new_connection, ..), host, port): handler _new_connection |
| open-webui 8bd8b4f | 16 routes stream: sse | StreamingResponse(.., media_type='text/event-stream') in the chat, message, file-status and Ollama / OpenAI proxy handlers; the proxies stream only when the upstream does |
| nest 35142c3 | 9 test clients | new WebSocket('ws://localhost:8080') / new EventSource(url + '/sse') in e2e specs (TEST_HTTP) |
| socket.io 1eaa582 | 2 test clients | ws://localhost:${port}/engine.io in the engine.io tests |
| jayson 4c368aa | route:WS / (port 12345), 1 client | The typings test file starts a server and a client on a literal port (origin_kind: other, not matched) |
| vito 661e497 | none | new WebSocket(${tokenData.url}?token=..): the whole URL comes from the server, so no endpoint |
| openai-agents-python 81f0ccf | none | Every websockets.serve is in a test (not an entry point); the FastAPI WS routes were already there. The browser clients live in examples/*/static/, which is not indexed |
Unchanged (same nodes and edges): ws, express, immich, excalidraw, excalidraw-room, authentik, tauri, gen, python-sdk,
langgraph, httpie, invoiceninja, firefly-iii, opentelemetry-demo, mattermost-mobile, elk, social-app,
saleor-dashboard and cal.com; clash-verge-rev differs only in Rust edges from the rust-analyzer index (no JS / TS change, its
ws use is in an unindexed scripts/perf/). No edges were removed anywhere else. A random 20 of the 56 added edges and changed routes in
the projects above were checked by hand against the source, and all 20 were correct; three are open-webui proxy routes
that answer SSE only when the upstream or the stream flag asks for it. tests/test_websockets.py covers
tests/ws_fixture: a ws server with a path, one on a port with an inline handler, a noServer server behind an
upgrade prefix check, express-ws on a mounted router, @fastify/websocket and Hono WS routes, express / Hono / FastAPI
SSE routes (and two that only look like it), python websockets.serve, and a browser client using WebSocket,
ReconnectingWebSocket, EventSource and fetchEventSource, linked with cg link.
Webhook receivers (#37 part 1)
| Project | Receivers | Events | Notes |
|---|---|---|---|
| coolify 0ed423a | 7 verified | 24 endpoints, 42 RECEIVED_BY | Stripe Webhook::constructEvent (its 8 event types are handled in StripeProcessJob, reached through the dispatched job: heuristic, the job reads data_get($this->event, 'type')); GitHub (app and manual), Gitea and Bitbucket hash_hmac + hash_equals; GitLab token headers compared with hash_equals (the app hook in GitlabApp). Events from X-GitHub-Event / X-Gitea-Event / x-event-key.0 header variables and GitLab object_kind; 13 of the edges go to functions a thin branch calls (the dispatched jobs, handleWorkflowJob, and helpers that run only for that event, such as webhookPushMatchesWatchPaths on push) |
| invoiceninja f1ffa5f | 2 verified | none | The Mailgun hooks (hash_hmac + hash_equals in isAuthorizedByMailgunHash; provider from the path). The payment-gateway webhooks go through a driver resolved at run time, so the Stripe constructEvent in StripePaymentDriver is not reached from a route |
No receiver in vito (its routes are spatie/laravel-route-attributes #[Post] attributes, which give no routes yet),
outline (the GitHub hook is a plugin router.post("github.webhooks", ..) RPC route, not indexed as a route), cal.com
(no Stripe receiver in this checkout; its webhooks are outbound), firefly-iii, pixelfed, koel, open-webui, saleor
(payment-plugin hooks are dispatched by plugin id at run time), authentik, nest, librenms, akaunting, UNIT3D, panel,
netbox, monica, laravel.io, solidtime or express; their graphs are unchanged. Coolify's Sentinel route reads
X-GitHub-Event only through auditLogWebhookFailure, a logger called from many functions, so it is not a receiver.
No edges were removed anywhere. A random 20 of the 51 new edges and route markers in coolify and invoiceninja were
checked by hand against the source, and all 20 were correct; one is a branch helper (webhookPushMatchesWatchPaths
for github:push), which runs only for that event but is not its handler. tests/test_webhooks.py covers
tests/webhooks_fixture: Express, FastAPI and Laravel receivers for Stripe, svix / standardwebhooks, GitHub (HMAC
and @octokit/webhooks), Twilio, GitLab and a generic HMAC hook, verified and unverified, with switch / if / PHP
match event dispatch and the cg routes markers.
Webhook senders (#37 part 2)
| Project | Senders | Notes |
|---|---|---|
| cal.com | 3 SENDS_TO cal.com:{event} | _sendPayload and WebhookService.sendWebhookDirectly (fetch POST with X-Cal-Signature-256 from createWebhookSignature / createHmac) and handleWebhookScheduledTriggers (headers["X-Cal-Signature-256"] = ..). The event comes from the stored subscription, so it stays {event} (heuristic) |
| netbox | 1 SENDS_TO netbox:{event} | extras.webhooks.send_webhook: prepared_request.headers['X-Hook-Signature'] = generate_signature(..), sent with session.send |
| firefly-iii | 1 SENDS_TO firefly-iii:{event} | StandardWebhookSender::send: Guzzle request('POST', ..) with a Signature header from $signatureGenerator->generate(..) |
No sender in the other 18 projects, and no receiver edge changed anywhere. Two false positives found on the way were
fixed before this run: librenms' Kayako transport (a signature form field of an API call, not a header) and request
headers read with headers['X-Sig']. Not seen: saleor (its signature header is the constant
SALEOR_SIGNATURE_HEADER), invoiceninja and coolify (their outgoing webhooks are unsigned; invoiceninja adds the subscriber's own headers). All 10 new edges (the 5
above and the fixture's 4 sends plus the cg link pairing of hooks-ts order.created with the hooks-py/hooks/acme receiver) were checked by hand against the source, and all 10 were correct. tests/test_webhooks.py
covers svix, spatie/laravel-webhook-server and hand-signed fetch / requests senders, event names from a caller's
argument, and the pairing.
Dart isolates (#38 part 3)
| Project | Edges | Notes |
|---|---|---|
| dart-lang/samples (shallow clone) | 4 SENDS_TO, 3 RECEIVED_BY | isolates/bin: Isolate.run(() => _readAndParseJson(filename)), and the long-running service Isolate.spawn(_readAndParseJsonService, p.sendPort) whose two p.send(..) replies reach _sendAndReceive through StreamQueue(p) |
| immich (mobile) | 4 SENDS_TO, 2 RECEIVED_BY | IsolateWorker.initialize spawns the static _isolateEntry; its sendPort.send(..) / Isolate.exit(sendPort, result) reach the RawReceivePort handler. A test's compute(_writerTask, path) is skipped |
Unchanged: flutter-samples, dart_style and grpc-dart. All 13 new edges were checked against the source and were
correct; reviewing them found the StreamQueue(port) reader and test-only isolates, both handled before landing.
tests/test_dart_isolates.py covers tests/isolate_fixture.
Child processes, worker_threads and Android exposure (#38 part 3)
| Project | Edges | Notes |
|---|---|---|
| pip | 3 SENDS_TO, 2 RECEIVED_BY (process) | tools/protected_pip.py runs python -m pip (to src/pip/__main__.py), tools/release runs tools/release/check_version.py |
| immich | 1 SENDS_TO, 1 RECEIVED_BY (process) | machine-learning prepared() runs python -m immich_ml.sessions.prepare in a child process (its __main__ block). The server's fork(workerFile) names the worker with a template and stays unlinked |
| eslint | 1 SENDS_TO, 1 RECEIVED_BY (worker) | lib/eslint/worker.js parentPort.postMessage(indexedResults) -> worker.once("message", ..) in runWorkers; the worker URL is pathToFileURL(path.join(__dirname, "./worker.js")) held in a local |
| bitwarden-android, element-x-android, nowinandroid | guards only | Manifest components get permission <name> / not exported guards; unguarded: bitwarden AuthCallbackActivity and AutofillCallbackActivity, element-x the five UnifiedPush connector actions (received from distributor apps). Launcher activities are not checked |
No edge was removed anywhere. Unchanged: electron-fiddle (its utilityProcess sample sits in static/, which is not
indexed), socket.io (cluster.fork() is not linked), pino and mocha (their forks are in tests), and supervisor,
mini-redis, tonic, beets, httpie, python-zeroconf, libuv, redis, tokio, flask, express, nodemailer, ws, jayson,
request, panel, tauri, ktor-samples and capacitor. All 9 new edges outside the fixture were checked against the source
and were correct. Reviewing the guards found launcher activities flagged unguarded, so they are now left unchecked.
tests/test_process_ipc.py covers tests/process_ipc_fixture (Python -m, Node spawn / fork, Electron
utilityProcess, fork messages both ways, worker_threads, a spawn in a test that stays a test call).
Android intents and AIDL (#38 part 3)
| Project | Edges | Notes |
|---|---|---|
| bitwarden-android (shallow clone) | 23 SENDS_TO, 18 RECEIVED_BY, 12 TEST_CALLS | intent: 12 explicit intents to MainActivity, AuthCallbackActivity, AccessibilityActivity and the autofill / credential activities (factories such as createAddTotpItemFromAuthenticatorIntent, tile services through PendingIntent.getActivity). intent-action: the five com.x8bit.bitwarden.credentials.ACTION_* constants sent through PendingIntent.getActivity and received by CredentialProviderActivity.onCreate from its manifest intent filter (not exported). aidl: IAuthenticatorBridgeService (7 methods) implemented by the object : IAuthenticatorBridgeService.Stub() in the app and called by the authenticatorbridge library, IAuthenticatorBridgeServiceCallback.onAccountsSync called back through a RemoteCallbackList; tests call both through captured mocks |
| element-x-android | 22 SENDS_TO, 16 RECEIVED_BY | intent: notification actions (PendingIntentCompat.getBroadcast / PendingIntent.getBroadcast) to NotificationBroadcastReceiver and DeclineCallBroadcastReceiver, startForegroundService to LiveLocationSharingService.onStartCommand, CallForegroundService (its onBind only returns null, so onCreate receives), newIntent factories of activities. intent-action: the UnifiedPush org.unifiedpush.android.connector.* / distributor.* actions received by manifest receivers (exported); the senders are other apps |
| nowinandroid | 1 SENDS_TO, 1 RECEIVED_BY | ComponentName(packageName, TARGET_ACTIVITY_NAME) in newsPendingIntent to MainActivity |
No edge was removed anywhere. Unchanged: KaMPKit, capacitor, ktor-samples, spring-petclinic-kotlin, grpc-kotlin and the 20
part 2 corpora run again (supervisor, mini-redis, tonic, beets, httpie, python-zeroconf, libuv, redis, tokio, pip,
flask, express, nodemailer, ws, jayson, request, mocha, electron-fiddle, panel, tauri). Reviewing two random samples of
20 found these errors, all fixed before landing: mockk every { } / verify { } stubs counted as AIDL calls, a
service whose onBind only returns null received at onBind, manifest receivers recorded at the Kotlin handler's line
instead of the <action> line, consuming calls opened on an earlier line (PendingIntent.getActivity(\n ctx, .., Intent(..))) and PendingIntentCompat missing from via, and ComponentName(pkg, name) values built for an
assertion or a Robolectric package-manager shadow taken as sends. After those fixes, a third random 20 of the new edges
were checked: 19 were correct and one had a wrong via, read from a comment (// ... handles setResult/finish); consuming
calls now skip comments, strings and coroutine launch { } blocks, and that edge reads launch(mainIntent). The authenticatorbridge client's calls inside ServiceConnection callbacks come from the class
(heuristic): the Kotlin extractor gives the anonymous object's onServiceConnected and the private
onServiceConnected(binder) one id. tests/test_android_ipc.py covers tests/android_ipc_fixture (an app module with a
manifest, services, receivers and an AIDL Stub, and a client module binding to it by ComponentName(pkg, CONST)).
Unix sockets, named pipes / FIFOs and D-Bus (#38 part 2)
| Project | Edges | Notes |
|---|---|---|
| zbus (shallow clone) | 37 SENDS_TO, 22 RECEIVED_BY, 27 TEST_CALLS (dbus) | fdo proxies (org.freedesktop.DBus.*, #[zbus(name = "GetConnectionUnixProcessID")] kept as written) and interfaces (Properties.Get / Set / GetAll, ObjectManager.GetManagedObjects, Introspectable.Introspect); signals sent by interfaces (InterfacesAdded, PropertiesChanged) and received by proxies (NameAcquired, NameOwnerChanged); the GeoClue client / service fixture crates meet on org.freedesktop.GeoClue2.*. Property setters (set_x) map to property X, r#match to Match. Interfaces inside mod tests and tests/ are test-only |
| tokio | 3 SENDS_TO, 4 RECEIVED_BY (pipe) | The named-pipe* examples: ServerOptions::new().create(PIPE_NAME) and ClientOptions::new().open(PIPE_NAME) on r"\\.\pipe\named-pipe-single-client" constants |
| tonic | 2 SENDS_TO, 1 RECEIVED_BY (unix) | The uds example: UnixListener::bind("/tmp/tonic/helloworld"), UnixStream::connect(path) in a connector closure, and GreeterClient::connect("unix:///tmp/tonic/helloworld") |
| libuv | 1 RECEIVED_BY, 2 test | uv_pipe_bind(&server, PIPENAME) in the pipe echo server docs example (the non-Windows #define value); tests connecting to a regular file and to a FIFO |
| nodemailer | 1 test | socketPath: '/var/run/docker.sock' in a fetch test |
No edge was removed anywhere. Unchanged: supervisor, redis, ansible, pip, opentelemetry-python, authentik, immich,
open-webui, mini-redis, alacritty, beets, httpie, python-zeroconf, tauri, panel, coolify, express, flask, ws,
electron-fiddle, jayson, openai-agents-python, request and mocha. Where these projects use Unix sockets, the path is
known only at run time: supervisor, redis and alacritty read it from configuration or the command line, ansible
derives it from a connection hash, and authentik's Rust server connects to gunicorn through
fn socket_path() -> PathBuf { temp_dir().join("authentik.sock") } while gunicorn's bind is set in its config.
These calls are counted under unix_path_unknown (alacritty 4, supervisor 5, ansible 4, authentik 7). Four errors
found while reviewing the zbus output were fixed before landing: fns nested in an interface method were taken as
members, property setters became SetX members, r#match became R, and interfaces inside mod tests counted as
application code. After those fixes, a random 20 of the 98 new edges then in libuv, tokio, tonic and zbus were checked by
hand against the source, and all 20 were correct (tonic's GreeterClient::connect edge came later and was checked too). tests/test_local_sockets.py covers tests/local_sockets_fixture
(Python socket / asyncio / socketserver / gRPC, Node net / http, Rust std / tokio / tonic / zbus, a FIFO and a
named pipe, dbus-next) and tests/local_sockets_c_fixture (sockaddr_un + bind / connect).
Local IPC in JS / TS (#38 part 1)
| Project | Edges | Notes |
|---|---|---|
darkreader (shallow clone, .cg.yaml include: [src], see #153) | 34 SENDS_TO, 45 RECEIVED_BY, 20 MATCHES_ENDPOINT (extension) | chrome.runtime.sendMessage / tabs.sendMessage with {type: MessageTypeUItoBG.X} enum members (resolved to ui-bg-* / cs-bg-* / bg-cs-* names); listeners that switch on enum members, also one call down (Messenger.messageListener -> onUIMessage, via the listener); runtime.connect({name: type}) as port:*. Catch-all listeners (makeChromiumHappy) receive * and match every typed sender |
| open-webui | 20 SENDS_TO, 13 RECEIVED_BY | Kokoro and Pyodide workers (this.worker.postMessage <-> self.onmessage, self.postMessage back to worker.onmessage / addEventListener('message')); the Pyodide sandbox iframe (parent.postMessage(.., '*'), a listener checking event.source per fs:* type); the OneDrive picker's window listener (initialize) |
| koel | 12 SENDS_TO, 3 RECEIVED_BY | Service worker: sw.postMessage through const getSW = () => navigator.serviceWorker?.controller, self.addEventListener('message'), client.postMessage back to navigator.serviceWorker.addEventListener('message'); the SSO popup listener (*) |
| saleor-dashboard | 1 SENDS_TO, 12 RECEIVED_BY | App iframes: contentWindow.postMessage(event, appOrigin) (type unknown: *); the app-actions listener receives 11 action types from handleAction(event.data)'s switch (via the listener) |
| excalidraw | 9 SENDS_TO, 3 RECEIVED_BY | Embeddable iframes (contentWindow.postMessage({type: 'command'}), event.source.postMessage), the diagram-to-code srcdoc listener |
| electron-fiddle | 5 SENDS_TO, 4 RECEIVED_BY | The isolated run-button iframe both ways, with origin and source checks; types as string constants (RESIZE_MESSAGE) |
| cal.com | 6 SENDS_TO, 2 RECEIVED_BY | OAuth setup popups (window.opener.postMessage({type: 'alby:oauth:success'})), embed preview instructions, CAL:${name} events to the opener (*, target_origin: "*") |
| immich, librenms | 4 + 2, 2 + 2 | Workers (new URL('$lib/workers/hash-file.ts', import.meta.url) by suffix; librenms through getWorker().postMessage) |
| social-app | 4 SENDS_TO, 2 RECEIVED_BY | BroadcastChannel('BSKY_BROADCAST_CHANNEL') / NOTIFS_BROADCAST_CHANNEL |
| elk, outline, eslint | 3, 1, 2 (test only) | elk's service worker files are not indexed, so only the page side shows |
No edge was removed anywhere. Unchanged: tauri, mattermost-mobile, socket.io, authentik, mkdocs, invoiceninja, vito,
pixelfed, panel and express. In tauri and mattermost-mobile the postMessage calls are window.__TAURI_INTERNALS__ /
window.ipc / React Native WebView bridges (cg bridges); authentik's web/ is a separate project from its Python
root. Two errors found in the hand-check were fixed before landing: a port:* connection matched a * message
listener (ports now match only port: listeners), and a type guard (isWidgetResizeAction(event.data)) was taken as
a handler (callees named is* / has* / can* / should* / check* / validate* are skipped). Also fixed:
this.#onmessage = h fields and typeof data.type !== 'string' are not listeners or types. A random 20 of the 210 new edges
in the batch were checked by hand against the source: 18 correct and the 2 above wrong. tests/test_local_ipc.py covers
tests/local_ipc_fixture: workers both ways (and through a getter), comlink, a service worker, BroadcastChannel,
postMessage with and without an origin check, extension messages with literal and enum types, ports and a
native-messaging host manifest.
Socket.IO and WebSocket in Dart / Kotlin / Swift / Rust (#32 part 3)
| Project | New | Notes |
|---|---|---|
| immich c5e06dc | 13 RECEIVED_BY, 2 endpoints | The Flutter app's socket.on('on_asset_update', _handleRemoteChange) and the other listeners in mobile/lib/providers/websocket.provider.dart now receive the server's Socket.IO events. AssetUploadReadyV1 / AssetEditReadyV1 are new endpoints: the server emits only the V2 events |
| ktor-samples 1c9df7c | 3 route:WS, 2 http:WS, 3 test clients | webSocket("/ws") in the chat sample and webSocket("/websocket") under route("/opentelemetry"), each with its handler lambda; the reverse proxy's webSocket(path) takes its path as a parameter and reads as the routing prefix / (which is what its one caller passes). Clients: client.ws("/opentelemetry/websocket"), webSocketSession(.., path = "/ws") and the chat tests' client.webSocket("/ws") / ws("/ws"). 9 calls and 2 property reads move from the routing function into the new handler lambdas |
| tauri 30da1fd | route:WS /__tauri_cli | The CLI dev server's axum route, whose ws_handler takes WebSocketUpgrade (was route:GET /__tauri_cli) |
| swift-IceCubesApp 9efcb16 | none | urlSession.webSocketTask(with: url, ..) takes a URL built by makeURL(scheme:endpoint:) from an endpoint enum, so there is no path |
Rust routes now carry uri / method: tauri's 7 and authentik's 12 axum routes, with no edge change (authentik's
/outpost.goauthentik.io/auth/envoy{*rest} becomes .../envoy{rest*}). Unchanged (same nodes and edges):
swift-isowords, swift-Alamofire, flutter-samples, grpc-dart, http, nowinandroid, KaMPKit, element-x-android,
grpc-kotlin, spring-petclinic-kotlin, element-x-ios, swift-template, bitwarden-ios, authentik, tokio, mini-redis,
jsonrpsee, tonic, parity and alacritty. No edges were removed anywhere except the 11 moved ktor-samples calls and
reads. A random 20 of the 25 added edges in the projects above (not counting the moved ones) were checked by hand
against the source, and all 20 were correct. tests/test_realtime_native.py covers tests/sio_native_fixture: a
socketioxide server with a namespace and a room, a rust_socketio bot, Dart, Kotlin and Swift clients with acks,
string templates and constants, Ktor / Vapor / axum WebSocket routes, and Ktor, OkHttp and URLSession WebSocket
clients.
JS callback variables, property reads and returns (#138)
Call edges are CALLS, TEST_CALLS and RENDERS, counted by (kind, source, target, line), before -> after.
| Project | Functions | Call edges | Notes |
|---|---|---|---|
| request 3c0cddc | 195 -> 136 | 1,031 -> 745 (-326, +40) | The 59 lost function nodes are var s = http.createServer(cb), var r = request(url, cb) and .map(cb) results. No calls of server / s / s2 remain (163 before). The callbacks' own calls move to the enclosing test or module. request(..) calls and expando calls like request.get(..) are kept |
| saleor-dashboard f9093f2 | 9,348 -> 9,317 | 34,511 -> 33,819 (-701, +9) | Mostly Comp.displayName = ".." (about 480) and zod schemas / fixtures (appManifestSchema.safeParse, attributes[0]) |
| cal.com 54343aa | 7,131 -> 7,070 | 20,472 -> 20,227 (-253, +8) | zod schemas (z.string().transform(fn)), vi.hoisted(() => ({..})) mocks, displayName, return handler |
| excalidraw ed10ac7 | 3,263 -> 3,259 | 26,796 -> 26,573 (-224, +1) | displayName, X.prototype.y = .. in the woff2 bindings. Object.assign(withInternalFallback(..), {..}) components and <MainMenu.Item> tags are kept |
| eslint | 4,534 -> 4,534 | 16,705 -> 16,288 (-417) | Bundled benchmark files (_.isFinite = function .., Buffer.prototype.x = ..) |
| outline 478e812 | 3,998 -> 3,945 | 24,025 -> 23,906 (-167, +48) | createLazyRegistry(() => ..) registries and .map(cb) results. Their callbacks' calls now come from the module |
| immich c5e06dc | 3,125 -> 3,110 | 22,355 -> 22,194 (-165, +4) | Test fixtures (Object.fromEntries(..map(cb))) and mocks.x reads. vitest.fn(impl) mocks are kept |
| socket.io 1eaa582, mocha a9fc529, jayson 4c368aa | 876 -> 849, 511 -> 506, 226 -> 220 | -154, -114, -68 | Mocha.prototype.x = .., return fn, .map(cb) and .filter(cb) results |
| social-app, mattermost-mobile | 5,701 -> 5,686, 5,756 -> 5,744 | -77, -95 | Same patterns |
Unchanged: node-gyp, commander.js, capacitor-plugins, breeze-nuxt, examples-es and fullstack-tutorial
(styled('img')(fn) stays a component). ws, pino, amqplib, kafkajs, nodemailer, express, nest, elk, mcp-servers,
electron-fiddle, open-webui, excalidraw-room, create-t3-turbo, cordova-toast, statsd and capacitor lose 2 to 30 edges
each, from the same patterns. A random 20 of the 2,380 changed call edges in ten of these projects were checked by
hand against the source: 16 removed and 4 added. 19 were correct. They include six displayName / PreviewProps
writes, a zod schema, an array result of .map(cb), s.url on a server, vi.hoisted(() => ({..})), a
Mocha.prototype write, and callbacks' calls moved to their module. One added edge is in a minified bundle
(socket.io.min.js) and could not be checked. tests/test_js_refs.py covers tests/jsref_fixture: a factory
callback, a wrapper, expando assignments, return l and test-file reads of a server.
XPC and Darwin notifications (#38 part 3)
exelban/stats (shallow clone): 1 service (eu.exelban.Stats.SMC.Helper, SMCHelper.helperConnection ->
Helper.listener), HelperProtocol with 6 methods all received by the privileged Helper, and 7 proxy calls in
Kit/helpers.swift; 15 new edges, all hand-checked. Three self.setFanMode / self.uninstall calls on
SMCHelper's own wrappers were false positives before calls on self / super were skipped. No corpus here posts
Darwin notifications; that path is covered by tests/xpc_fixture only. Swift regression set (swift-template,
Alamofire, Kingfisher, SwiftUIX, isowords, IceCubesApp, bitwarden-ios, element-x-ios): no node or edge changes.
Prisma datasources as external systems (#41)
umami-software/umami (shallow clone): the postgresql datasource's url is in prisma.config.ts
(env('DATABASE_URL')), so the system stays external:postgres:env:DATABASE_URL (docker-compose sets the URL
in the service environment, not an .env.example). 26 new CONNECTS_TO edges, one per @@map table, all checked
against the schema; impact on the system goes from 429 callers / 198 entry points (the code reading
DATABASE_URL) to 564 / 203 through the tables, and 5 sampled new callers (getTeamWebsiteCount,
updateTeamUser, updateReplaySaved, canViewBatchWebsites, the 2FA setup POST) all call prisma.client.<model>
directly. The other Prisma schemas in the corpora are sqlite (nest 22-graphql-prisma) or outside the indexed app
(cal.com's packages/prisma, read by apps/web and apps/api/v2 through a workspace package), so they add
nothing; no node or edge changes on the regression set.
TypeORM, Drizzle and Kysely as external systems (#41 step 2)
immich-app/immich server: 76 tables gain attrs.system and CONNECTS_TO
external:postgres:env:DB_URL (via: sole postgres system) — 16 from @immich/sql-tools @Table
(now recognised alongside sequelize-typescript) plus 60 from Kysely selectFrom / insertInto
(orm: kysely). @Table also adds CONTAINS column edges from the schema decorators. impact on the
system goes from 211 callers / 318 entry points to 1329 / 341; 5 sampled depth-2 callers
(withSharedBy, withMetadata, updateThumbnailBuilder, checkSharedLinkAccess,
ApiKeyRepository.update) all query through Kysely. nestjs-boilerplate: 5 @Entity tables attach via
the new DataSource({ type: process.env.DATABASE_TYPE, url: process.env.DATABASE_URL }) in
src/database/data-source.ts (via: typeorm datasource default); Mongoose @Schema collections stay
out. koel (Laravel tables without an ORM tag) and outline / create-t3-turbo / solidtime: no node or
edge changes.
Laravel tables, PHP PDO and Spring datasources (#41 step 3)
koel: 35 migration / Eloquent tables CONNECTS_TO external:mysql:env:DB_HOST through
connection:mysql (via: laravel connection); inferred aliases (information_schema.*, *_new) stay
unattached. impact on the system reaches 396 callers / 127 entry points. pixelfed: 135 tables the same
way. spring-petclinic-kotlin: application-mysql.properties jdbc:mysql://localhost/petclinic becomes
external:mysql:localhost:3306 (literal password location recorded, value not stored); 4 Spring Data
tables (owners, pets, vets, visits) attach; the default H2 profile is skipped. outline and
nestjs-boilerplate: no node or edge changes. shop-api fixture: table:orders -> default pgsql,
table:reports -> the model's $connection = reporting.
Redis key prefixes and Elasticsearch indices (#41 step 4)
attrs.key_prefixes on redis externals and attrs.indices on elasticsearch externals, from call-site
keys / f-string and template prefixes, Django KEY_PREFIX, and ES index / index-prefix defaults.
Shown by cg external.
| Corpus | Before | After | Notes |
|---|---|---|---|
| saleor | redis without prefixes | login:fail:ip:, login:block:ip:, allow_storefront_traffic:, bbrs, oidc:, … | Django cache + circuit-breaker KEY_PREFIX; attr-only (no edge delta) |
| umami | redis without prefixes | website:, link:, pixel:, account:, team:, session:, auth:, white-label: | redis.client.get/set/del/fetch template keys |
| open-webui | redis / ES without attrs | redis prefixes + open_webui_collections / open_webui indices | ELASTICSEARCH_INDEX_PREFIX default; short unrelated index kwargs filtered |
| shop-py fixture | – | cache:user:, sess:, login:block:ip:, shop: + index shop_products | unit coverage |
Edge-only ddiff is silent for this step (attrs only). Leftover FPs / missed helper-only keys -> follow-up.
pydantic-settings broker field defaults (#157)
settings-bus fixture: Settings.events_topic = "events" / jobs_queue = "jobs" / Field(default="audit.orders").
producer.send_and_wait(settings.events_topic) and consumer.subscribe([settings.events_topic]) share
endpoint:kafka:events; default_exchange.publish(..., routing_key=settings.jobs_queue) pairs with
declare_queue(settings.jobs_queue) + consume on endpoint:amqp:queue:jobs. self.settings.events_topic on a
wrapper class resolves the same way.
Broker helper parameters and consumer constructor topics (#137)
settings-bus TaskQueue._publish(queue, ...) with callers passing module constants: five / two AMQP queue
sends from the helper; AIOKafkaConsumer(settings.events_topic, group_id=...) is a kafka receive (no separate
.subscribe needed).
Third-party HTTP hosts as external nodes (#42 part 1)
http nodes with origin_kind other become external:http:<host>:80 / external:https:<host>:443
(or the explicit port) with CONNECTS_TO (via http). Loopback, templates, and test-only callers stay out.
https-client fixture: GitHub + Slack (https) and httpbin (http). outline: updates.getoutline.com.
cal.com / saleor / open-webui / immich server: no app-called other-origin hosts in this pass (same-origin /
unknown / none). Query-time https adapter removed; cg link still skips other-origin URLs.
Cloud / SaaS SDKs as external nodes (#42 part 2)
SDK construction and operations become resource nodes (external:s3:<bucket-or-env>,
external:gcs:…, external:azure-blob:…, external:aws:<service>[:resource], external:saas:stripe,
external:llm:<provider>). Fixtures: s3-py (PutObject + ambient creds, explicit keys, SQS, Secrets Manager),
gcs-py, azure-py, stripe-py, openai-chat, laravel-disk, django-storages. Test-only callers and
HTTP hosts already attached as external:http(s) are not duplicated.
| Corpus | Before | After | Notes |
|---|---|---|---|
| outline 478e812 | 15,551 nodes / 64,832 edges, index 24.54s | 15,552 / 64,843 (+1 node, +11 edges), index 24.12s | one external:s3:env:AWS_S3_UPLOAD_BUCKET_NAME (auth=ambient, CONFIGURED_BY that env key). 10 CONNECTS_TO from S3Storage (PutObject, DeleteObject, HeadObject, CopyObject + DeleteObject on moveFile, GetObject on getFileStream and getS3PresignedUrl, Upload, createPresignedPost, S3Client). No new external:http(s) node. |
Attack surface (#47)
Run of cg index and cg surface --format json on shallow clones of the default branch (October 2026), indexed in
full unless noted. Clone shas: juice-shop 1618a61, DVWA 088256f, NodeGoat c5cb68a, netbox 726a2bb, saleor bb75f87,
outline 6e6eaf6, nextcloud/server 21bf334 (whole repo), ansible bf2610e (whole repo), plus for #42 calcom/cal.com 54343aa
(web app only, as before), appwrite 8a47c4b, open-webui 8bd8b4f, documenso a166cab. Each finding was compared with
the cited source line: TP is the finding as described, FP is a route that is public by design or guarded in a way
the index does not see, or text that is not code, unclear needs a decision from the owner (an opt-in switch, a check in
another file). Up to 20 findings per type per corpus were sampled (seeded random sample, all when fewer). The counts below
are from the tree after the precision fixes of this pass; the "before" figure is the same corpus on 871328e.
Recall on the vulnerable apps
Items are the ones each project documents for the types cg covers (hardcoded credentials, plaintext, TLS off, SSRF, unguarded routes). Nothing is counted that the project does not document.
| corpus | documented item (where) | result |
|---|---|---|
| juice-shop | SSRF challenge (data/static/challenges.yml, key ssrfChallenge) | found: ssrf high, routes/profileImageUrlUpload.ts:24, body.imageUrl |
| juice-shop | Product Tampering, guard commented out on PUT /api/Products/:id (server.ts:389, marked vuln-line changeProductChallenge) | missed: the route is generated by the REST library, not registered as a route |
| juice-shop | Confidential Document, public /ftp listing (server.ts:288, directoryListingChallenge) | missed: app.use mounts are not routes |
| juice-shop | Exposed Credentials, test account in client code (challenges.yml, exposedCredentialsChallenge) | missed: not a protocol argument or a known config key |
| NodeGoat | SSRF (app/views/tutorial/ssrf.html) | found: ssrf high, app/routes/research.js:16, query.url |
| NodeGoat | hardcoded cookieSecret / cryptoKey (config/env/all.js:8-9, shown in a5.html, a6.html) | missed: literals in a plain config object |
| NodeGoat | plain HTTP server, "Insecure HTTP connection" (server.js:144, a6.html) | missed: http.createServer is not a plaintext finding |
| NodeGoat | missing function-level access control on /benefits (a7.html) | missed: the route has isLoggedIn; role checks are not modelled |
| DVWA | default DB password getenv('DB_PASSWORD') ?: 'p@ssw0rd' (config/config.inc.php.dist:21, README "Database Setup") | missed: a getenv() ?: literal default outside Laravel config is not read |
| DVWA | Authorisation Bypass lab (vulnerabilities/authbypass/help/help.php) | missed: a file-per-page PHP app has no modelled routes (0 inbound items) |
| DVWA | remote file inclusion with allow_url_include (README "PHP configuration") | missed: include / require are not outbound sinks |
Found 2 of 11 documented items. The vulnerable apps document few items of the covered types, and most of those depend on code shapes the index does not model (see Known limitations).
Precision by finding type
| corpus | finding | count | sampled | TP | FP | unclear |
|---|---|---|---|---|---|---|
| NodeGoat | ssrf | 1 | 1 | 1 | 0 | 0 |
| juice-shop | ssrf | 1 | 1 | 1 | 0 | 0 |
| juice-shop | unguarded | 10 | 10 | 4 | 5 | 1 |
| netbox | hardcoded | 1 | 1 | 1 | 0 | 0 |
| netbox | unguarded | 858 | 20 | 0 | 20 | 0 |
| saleor | unguarded | 9 | 9 | 0 | 5 | 4 |
| outline | hardcoded | 1 | 1 | 0 | 1 | 0 |
| outline | plaintext | 1 | 1 | 0 | 0 | 1 |
| outline | tls-off | 4 | 4 | 4 | 0 | 0 |
| outline | ipc-exposed | 1 | 1 | 1 | 0 | 0 |
| outline | unguarded | 28 | 20 | 0 | 19 | 1 |
| cal.com web app | tls-off | 1 | 1 | 1 | 0 | 0 |
| cal.com web app | ipc-exposed | 2 | 2 | 2 | 0 | 0 |
| cal.com web app | unguarded | 99 | 20 | 0 | 17 | 3 |
| open-webui | ssrf | 4 (14) | 4 | 3 | 1 | 0 |
| open-webui | tls-off | 2 (4) | 2 | 1 | 0 | 1 |
| open-webui | ipc-exposed | 2 | 2 | 2 | 0 | 0 |
| open-webui | unguarded | 14 | 14 | 0 | 7 | 7 |
| documenso | ipc-exposed | 28 | 20 | 20 | 0 | 0 |
| ansible | tls-off | 4 (5) | 4 | 0 | 0 | 4 |
| nextcloud | tls-off | 1 | 1 | 0 | 0 | 1 |
| DVWA, appwrite | all | 0 (appwrite 1 ssrf) | - | - | - | - |
A number in brackets is the count before this pass. appwrite had one ssrf and documenso one, both false positives
that are fixed here; ansible and open-webui lost one or two tls-off that were text, not code (see below).
Notes from the sampled checks:
unguardedis the noisiest type (counts above are from part 2c; part 2d below has the new ones). The false positives have four causes: routes that are public by design (health, robots, share links, login, jwks, static, OAuth token endpoints); a check inside the handler (a session lookup, a cron key, agetServerSessioncall, tRPC procedure middleware) that is not a guard fact; a framework-wide default set in settings (a DRFDEFAULT_PERMISSION_CLASSESthat applies to every route of one corpus, so all of its sampled findings are false positives, most from router registrations); and Socket.IO events whose auth is checked atconnector in the first lines of the handler (7 of 13 sampled handlers in one corpus). NodeGoat'ssession()middleware is read as an auth guard, so its/loginand/signuproutes show as guarded (fixed in part 2d).tls-off: 4 of 4 sampled true positives in one corpus (pattern: an opt-in branch that switches certificate verification off for a database, mail or cache client); two corpora have 5 sampled findings in total that areunclear(verification is off only when the user switches validation off).ipc-exposed: 25 of 25 sampled true positives, all one pattern class (a wildcard target origin inpostMessage). Whether the data sent is sensitive was not judged.hardcodedandplaintext: one corpus, 2 findings from example env files (a placeholder DSN, a compose service name); both are by design for example files.- Precision fixes made in this pass (each has a regression case):
from ssl import CERT_NONE, docstrings and module documentation string blocks no longer matchtls-off; a derived URL variable keeps its part (a template that appends a path to a base URL returned by a call is a path, not a whole URL); a value wrapped inrealpath/basename/intval/(int)/parseInt/Number/int/floatis no longer a URL; a template that starts with the tainted piece (`${id}/x`) is a whole URL; and an index run that crashed on a destructured parameter (const { url } = obj) now completes. Onessrffalse positive remains: taint that passes through the result of a call that gets the input only as an argument. - Follow-ups, not fixed: guards inside handler bodies and framework defaults (above); taint through call results;
getenv('X') ?: 'literal'and config-object literals ashardcoded;http.createServerasplaintext; file-per-page PHP routes; a role check as a guard; the exact items in the recall table. Listed in Known limitations.
Part 2d: unguarded precision, index time, fingerprints
Same clones as above (base 2542c89, which equals origin/main when this pass ran), indexed in full with cg index into a fresh database for the "before" (2542c89, in a separate worktree) and the "after" (this change), and read with cg surface --finding unguarded --format json. The "sampled" column is the full list when it has 20 or fewer entries, a seeded random sample of 20 otherwise (seed 47), so every finding below was classified. TP: no guard and no check, as described; FP: public by design or guarded in a way the index does not see; unclear: depends on a setting or a check in other code.
| corpus | before | after | cut | after by severity (confidence) | sampled | TP | FP | unclear |
|---|---|---|---|---|---|---|---|---|
| juice-shop | 10 | 8 | 20% | 8 low (7 resolved, 1 heuristic) | 8 | 4 | 1 | 3 |
| NodeGoat | 0 | 2 | - | 2 low (resolved) | 2 | 0 | 2 | 0 |
| netbox | 199 | 5 | 97.5% | 5 low (1 resolved, 4 heuristic) | 5 | 0 | 2 | 3 |
| saleor | 9 | 4 | 56% | 3 medium (resolved), 1 low (heuristic) | 4 | 0 | 0 | 4 |
| outline | 28 | 5 | 82% | 5 low (resolved) | 5 | 0 | 5 | 0 |
| cal.com web app | 99 | 18 | 82% | 11 low (resolved), 7 low (heuristic) | 18 | 0 | 10 | 8 |
| open-webui | 14 | 1 | 93% | 1 low (exact) | 1 | 0 | 1 | 0 |
| documenso | 0 | 0 | - | - | - | - | - | - |
The 2c table above reports 858 for netbox; recounting the clone used here on 2542c89 gives 199, which is the "before" number in this table.
Target: precision of at least 60% or a cut of at least 80% on netbox, outline and the cal.com web app. Met by the cut on all three (97.5%, 82%, 82%). Not met by precision: no TP remains on those three, because what is left is public-by-design, checked in code the index does not read, or depends on a setting, and no sampled finding on them was a missing guard. The remaining findings are low severity except saleor's three plugin webhook dispatchers, which stay medium (no guard, no recognised check, a write is reached; each plugin verifies its own request).
Recall on juice-shop's four true positives (all sampled before and after, server.ts):
| route | line | before | after |
|---|---|---|---|
POST /file-upload | 328 | medium | low, resolved |
POST /profile/image/file | 329 | medium | low, resolved |
POST /profile/image/url | 330 | medium | low, resolved |
POST /rest/memories | 331 | medium | low, heuristic (an unclassified middleware precedes the handler) |
All four are still reported, so there is no recall loss; their severity falls to low because none reaches a database write the graph can see (file system writes are not writes in this model). The two security.txt routes (public by design) are no longer reported. NodeGoat's two new findings are tutorial pages: they appear because an Express session() middleware is no longer read as authentication, so routes with only session() in front of them are now unguarded (low) where they used to be guarded.
What caused the cuts: framework defaults (DRF REST_FRAMEWORK default permission classes, Laravel global and group middleware, Nest APP_GUARD and @Public()), the public-by-design route list (Attack surface), checks at the top of a handler or of a Socket.IO connection, Express app.use(auth) ordering and mount path, and a DRF permission_classes = [] taken as explicit public access. --strict keeps route-level guards only: on netbox it reports 200 (199 before; one route that only session() guarded) and on outline 28, unchanged.
Index time against 8172833 (outline and the cal.com web app, three alternating runs, each run a fresh cg index from a separate worktree outside the repository; "before" is 8172833, "after" is this change). The profile on cal.com web app before the change put ssrf_input.apply at 5.7 s and insecure_transport.apply at 1.3 s of a 17.2 s profiled run. Fixes (output unchanged): ssrf_input skips files and functions that contain no sink text, insecure_transport checks one lowercase substring list per file before running its 35 regexes and finds the client library only when a rule matches, and the SDK file prefilter in sdk_systems lowercases the text once instead of two re.I scans. After the change on cal.com web app: ssrf_input.apply 0.6 s, insecure_transport.apply 0.6 s.
| run | outline s (before / after) | cal.com web app s (before / after) |
|---|---|---|
| 1 | 33.47 / 33.18 | 13.42 / 12.43 |
| 2 | 30.17 / 31.49 | 11.48 / 11.19 |
| 3 | 27.78 / 28.04 | 9.68 / 11.20 |
| mean | 30.47 / 30.90 (+1.4%) | 11.53 / 11.61 (+0.7%) |
The box is shared and its speed drifted between sessions. Earlier complete sets, taken on slower periods, are in the same range but noisier (outline before / after: 41.15 / 43.04 s with the first two fixes only, 30.09 / 35.46 s and 38.29 / 40.90 s with all of them; cal.com web app: 14.23 / 17.49, 12.70 / 14.02 and 16.44 / 16.09 s). The target is at most +10% against 8172833; the last alternating set meets it, and the noisy sets show the error of one run is larger than the margin.
Output check, cg surface --format json on outline, cal.com web app, open-webui and documenso, with the guard change held fixed (commit before the speed-up vs after): ssrf, tls-off and ipc-exposed findings are identical in all three types on all four corpora (outline 0 / 4 / 1, cal.com web app 0 / 1 / 2, open-webui 4 / 2 / 2, documenso 0 / 0 / 28), as are the node and edge counts and every attr that holds an insecure_transport, url_from_input or tls_verify fact. The only graph change comes from the SES v2 work: documenso gains external:aws:ses and 4 CONNECTS_TO edges (below).
Mail (#42), documenso re-run: the SES v2 identity commands (CreateEmailIdentityCommand, GetEmailIdentityCommand, DeleteEmailIdentityCommand from @aws-sdk/client-sesv2) are now recognised: 1 new external node and 4 CONNECTS_TO edges, one from each of the 4 functions that call them (checked against the source). The nodemailer SES and Resend transports are recognised too, but documenso uses neither with a transport the extractor can resolve, so they add nothing there.
Fingerprints: a marker is redacted:hmac:<8 hex>, keyed by a per-graph salt in meta redact_salt, so a common password no longer gives a known value (the unsalted marker of password was 5e884898) and the same value in two different graphs gives different markers.
Redaction check
Values counted are strings containing redacted:sha256: in any node or edge attr, and in doc after the fix below.
| corpus | redacted values | what they are |
|---|---|---|
| DVWA, NodeGoat, juice-shop, netbox, cal.com, appwrite, open-webui, documenso | 0 | - |
| saleor | 9 in 3 test functions and their cases | passwords of scheme://user:pw@host URLs in test parameters; credentials in test data |
| ansible | 20 in 3 test functions and their cases | passwords of fake URLs in test functions; credentials in test data |
| nextcloud | 2 | password entries of a test array |
| outline | 1 before the fix, 0 attrs after | a re-export whose name contains a credential word: the name of an exported class, not a credential (wrong redaction, fixed) |
All values of saleor, ansible and nextcloud were checked and are credentials in test data, so no wrong redaction there.
No *_PASSWORD / *_SECRET default or DSN password from a sampled source file is stored verbatim (a test-settings password in one corpus, the NodeGoat cookieSecret / cryptoKey, and DVWA's p@ssw0rd do not appear in the database; a scan of every
string attr of every corpus for scheme://user:pw@ and for literals under a credential-named attr found none).
Found and fixed: an example DSN with a placeholder password in a doc comment (one corpus) was kept in the doc column, which the sweep did not cover; the sweep now
replaces URL passwords there too. Fixed in part 2d: the marker was an unsalted 8-hex SHA-256 of the value, so a common password
(password gave 5e884898) could be recognised from it; it is now a keyed HMAC with a per-graph salt (redacted:hmac:). The counts above were taken with the old redacted:sha256: marker.
Mail / SMS / push / infra SDKs (#42 parts 3a and 3b)
Δ of external nodes and CONNECTS_TO edges for mail, SMS, push, Firebase, Kubernetes, Docker and key-management
systems, on 8172833 (before 3a) and 871328e (after 3b, same code as the attack-surface runs above). Both indexed from a
separate worktree.
| corpus | external nodes before / after | CONNECTS_TO before / after | index s before / after |
|---|---|---|---|
| outline (6e6eaf6) | 8 / 8 | 31 / 31 | 25.06 / 31.76 |
cal.com apps/web (54343aa) | 0 / 0 | 0 / 0 | 9.50 / 12.86 |
| saleor (bb75f87) | 3 / 3 | 6 / 6 | 84.71 / 101.54 |
| appwrite (8a47c4b) | 2 / 2 | 0 / 0 | 39.61 / 52.24 |
| open-webui (8bd8b4f) | 8 / 8 | 9 / 9 | 26.34 / 32.07 |
| documenso (a166cab) | 5 / 5 | 16 / 16 | 25.56 / 20.11 |
No corpus gains an external node or a CONNECTS_TO edge from 3a or 3b, so the requested hand check of at least 10
new edges could not be made. The same harness does detect them: tests/external_fixture/mail-sms-py has 0 external nodes
on 8172833 and 5 on 871328e. Reasons found by reading each corpus:
- outline and saleor send mail through plain SMTP (
external:smtp:env:SMTP_HOST,external:smtp:env:EMAIL_URL, present before), which 3a leaves out on purpose (smtpis an ignored mailer). - documenso uses nodemailer transports, a Resend transport package and AWS SES v2 identity commands. Before part 2d none was recognised; the SES v2 commands now are (see Part 2d above); its nodemailer and Resend transports are still not resolved.
- appwrite reaches Twilio, Resend and others through its own
Utopia\Messaging\Adapterclasses, which are not an SDK from the extractor's list (follow-up). - cal.com
apps/weband open-webui have no mail, SMS, push, Firebase, Kubernetes, Docker or key-management SDK calls in the indexed tree.
Index time: the 871328e runs are 12% to 54% slower than 8172833 on 9 of 12 corpora, within 5% on DVWA and NodeGoat, and 21% faster on documenso (single runs on a shared box, so noisy at about 20%). Three alternating runs: cal.com apps/web 10.06 s before and
13.21 s after (+31%), outline 25.93 s and 32.72 s (+26%). The rise is above 5% and is explained by the 2a and 2b
index passes, not by 3a or 3b: a profile of apps/web (profiling inflates it) puts ssrf_input.apply at about 28% and insecure_transport.apply at about 7% of the index
time, and the 3a / 3b changes add no external nodes on these corpora. The node and edge counts are identical before and after for every corpus
except ansible (+3 file:config: nodes for shell scripts and YAML).
Part 2d recovered this cost: against 8172833, the mean of three alternating runs is +1.4% on outline and +0.7% on cal.com apps/web (all runs in Part 2d). The ssrf and insecure_transport passes skip files and functions that cannot contain a sink or a rule hit, and the SDK file prefilter lowercases once.
TypeScript / JavaScript frameworks
Moved from the TS frameworks page. Shallow clones, CODEGRAPH_NO_CACHE=1, 8-core box. Index time is wall time for cg index. Spot-checks were compared by hand with decorators, router files and file-system routes. "Found" counts come from the graph.
| project (revision) | files | index time | nodes / edges | found | spot-check | resolution / links | parse failures | gaps |
|---|---|---|---|---|---|---|---|---|
immich server/ (a81ddfa), NestJS + Kysely | 458 TS | 7.4 s | 4,759 / 19,780 | 295 HTTP routes (= the 295 route decorators in 47 controllers, global prefix api, RouteKey enum paths), 13 nest-commander commands, 86 @OnEvent listeners, 60 tables | 47 album / asset / auth routes: all correct; GET /api/albums/{id} → controller → service → repository → table:album | DI: 200 of 201 project tokens resolved (99.5%), 49 library tokens; the miss is a string[] param | 0 | immich's own @OnJob job decorators are not entry points |
nest sample/ 01–37 (7fb52e7), federation sub-apps indexed one by one | 41 apps | 66 s total (1.4–2.6 s each) | 1,043 / 1,708 | 62 HTTP routes (incl. @Sse), 31 GraphQL operations, 5 message handlers, 1 queue job, 3 schedules, 1 listener | route count equals the route-decorator count in all 27 samples with routes | DI: 44 resolved + 13 library, 0 unresolved | 0 | the 31/32 roots hold several apps and are not a project themselves |
Ghost ghost/core (412652f), Express, CommonJS + TS | 1,740 JS/TS | 12.4 s | 9,429 / 19,391 | 335 routes: 242 Admin API, 20 Content API, 39 Members API, plus frontend and service routers | 20 random routes: 20/20 paths correct, 19/20 handlers resolved (http(api.posts.browse) → controller.browse.query through the lazy require() registry) | 11 routes without a project handler (library middleware factories), 5 routers never mounted from an app | 0 | middleware attribution on lazyUse apps is coarse |
dub apps/web (7e01013), Next.js app router + Prisma | 3,428 TS | 24.8 s | 15,726 / 50,246 | 198 pages, 57 layouts, 653 route handlers, 134 server actions, 86 Prisma models | 20 random route handlers and pages: all correct (route groups, [...slug], re-exported handlers) | in-repo client → handler links: 299 of 363 endpoints (the rest are external URLs or dynamic) | 0 | 7 handlers built by libraries (NextAuth(...), workflow serve(...)) have no project function |
vercel/next.js examples/ (fbd032ff) | 224 apps | 384 s total (≤ 2.3 s each) | 6,186 / 10,537 | 221 detected as Next: 413 pages, 236 layouts, 22 route handlers, 125 pages/api routes, 19 server actions | 22 random pages / routes: all correct | in-repo links: 47 of 69 endpoints | 1 file (with-custom-babel-config: non-standard syntax) | 5 apps with MD/MDX-only pages or Electron renderers have no pages; 3 monorepo roots (with-zones, with-yarn-workspaces, with-stencil) must be indexed per app |
| realworld express + Prisma (30b68e1) → realworld vue3 client (741c215) | 25 + 43 | 2.3 s + 1.9 s, link 0.05 s | 142 / 277 + 196 / 530 | 20 routes under /api (Router().use(..) chains) | 20/20 correct | link: 19 of 20 client endpoints matched (generated client, origin unknown → heuristic); the miss is a dynamic URL | 0 | — |
Spot-checks were done by hand against the source (decorators, router files, file-system routes). The "found" counts
come from the graph (SELECT count(*) FROM nodes WHERE kind='route' etc.).
React Router / Remix (shallow clones, CG_NO_CACHE=1). Counts are pages, layouts, route: nodes and
NAVIGATES_TO edges.
| project (revision) | pages | layouts | routes | NAVIGATES_TO | notes |
|---|---|---|---|---|---|
remix-run/react-router-templates default/ (067adb3) | 1 | 1 | 0 | 0 | app/routes.ts is index("routes/home.tsx") inside a root.tsx layout. Home has no loader, so no route: node. welcome.tsx is a component, not a route |
| remix-run/indie-stack (56abb93, archived Remix v2) | 8 | 2 | 12 | 11 | flatRoutes file conventions: _index, notes.$noteId, notes._index (so notes.tsx is a layout). 2 navigation sites stay unresolved. 5 form / fetcher calls match an in-repo loader or action |
How we validate
Public-project checks, not a tutorial. Each area was indexed from a shallow clone (default branch, October 2026) with python -m cg_code_graph.cli index <repo> on one 8-core Linux box. Wall time includes a cold extractor cache, not a one-off dart compile exe.
Value facts and resolutions
Facts about values, from the PHP and TypeScript ASTs (cg_code_graph/plugins/laravel/values.py and the TS extractor).