Skip to content
49 changes: 24 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Then run `codegraph init` in each project, as in [Get Started](#get-started). In

### What the fork adds

Compared with upstream `main` at `290e03f`. Each item was checked against upstream's tree at that commit; the table has not been rechecked against `6560052`.
Compared with upstream `main` at `6560052` (v1.6.2). Each item here and in the dispatch and framework lists below was checked against upstream's tree at that commit.

| Feature | Upstream | Fork | What it does |
|---|:-:|:-:|---|
Expand All @@ -85,10 +85,10 @@ Compared with upstream `main` at `290e03f`. Each item was checked against upstre
| Change questions | — | ✓ | "What did my changes touch?" or `main..HEAD` is answered from the diff: the changed functions, their callers and their tests. |
| Name-only links marked | — | ✓ | Call links matched only by a function's name, with no import or receiver type behind them, are marked, so an agent knows which hop to check. |
| Worktree seeding | — | ✓ | `codegraph init` in a new git worktree starts from a sibling worktree's index and re-reads only the files that differ. |
| Inferred links kept current on sync | Reruns every pass | Reruns the affected passes | Links inferred from events, callbacks, React re-renders, function pointers and similar dispatch are rebuilt after each sync, so a sync ends with the graph a full index would build. Upstream added a refresh in [#2033](https://github.com/colbymchenry/codegraph/pull/2033) that reruns every pass; the fork reruns only the passes the changed files can affect. |
| Inferred links kept current on sync | Rebuilt inside the sync | Rebuilt once edits pause | Links inferred from events, callbacks, React re-renders, function pointers and similar dispatch are rebuilt after a sync, so the graph ends where a full index would. Upstream's refresh ([#2033](https://github.com/colbymchenry/codegraph/pull/2033)) runs inside each sync that touches an inferred link. The fork runs the same full refresh after edits pause, so a save is indexed without waiting the 3–5 s synthesis takes on a large repository; until the refresh runs, an inferred link can be missing, or stale when its registration moved to another file. |
| Devin | — | ✓ | `codegraph install` can wire up Devin (CLI and Desktop). |
| Reloading MCP launcher | — | ✓ (opt-in) | A long-running MCP server picks up a new build without the agent reconnecting. |
| Build revision in version output | — | ✓ | `codegraph --version` and `status --json` report the source revision the build came from. |
| Build revision in version output | — | ✓ (stamped builds) | `codegraph --version` and `status --json` report the source revision when the build carries `dist/build-revision.json`. A deploy step writes that file; `npm run build` does not, so a plain source build reports the package version. |

### Parsing, resolution and languages

Expand All @@ -102,8 +102,6 @@ Compared with upstream `main` at `290e03f`. Each item was checked against upstre

The other languages are the same in both, listed under [Supported Languages](#supported-languages).

Vue, Svelte and Astro files have one file node containing their component. The component contains top-level script members, while nested functions and methods keep their own parents. Vue `<script setup>`, Svelte instance scripts and Astro frontmatter assign top-level calls and references, including constant initializers, to the component. Imports, module-level execution and Astro browser scripts remain with the file. Svelte recognizes both `context="module"` and the `module` attribute.

Dispatch and framework coverage the fork adds, by kind:

**Dispatch links**
Expand All @@ -113,28 +111,27 @@ Dispatch and framework coverage the fork adds, by kind:
| C function pointers | `x->f = fn;` assignments, alongside table initializers |
| Drupal hooks | `invokeAll()` / `invoke()` / `alter()` call sites to hook implementations, including Drupal 11 `#[Hook]` attributes |
| NgRx effects | Dispatched actions to the effects that handle them |
| React Native `NativeModules[key]` | Computed native-module calls to the native method |
| `window.postMessage` | Posted messages to their listeners |

Kotlin infix expressions contribute call edges with parenthesized operands and comments, and same-line infix names beginning with `e` keep their enclosing class intact. Flow-annotated JavaScript is parsed through the TSX grammar. Java and C# calls through declared fields or properties use their declared types; unresolved external types remain unresolved. Rust, Go, Scala, Swift and Kotlin calls also use the receiver and lexical scope at the call site. Kotlin receiver inference follows bounded chains of declared returns and verified receiver-preserving methods. Properties initialized by typed factory calls retain compatible imported extensions. Explicit casts, single-type `when` branches, filtered collection elements and bound generic factory arguments also supply receiver types. Kotlin chains use the callee position and declared return type, including nested and multiline calls; imported return-type hypotheses keep confidence at most 0.7 when the receiver type is unknown. Rust chain matches without a proved receiver type also keep confidence at most 0.7.

Kotlin `when` guards, open-ended ranges, multi-dollar strings and nullable function-type receivers retain their source offsets during normalization. Multi-dollar strings retain their interpolation threshold. C++ namespace-opening macros and aliases use the declarations visible to each caller; argument-compatible overloads resolve when the declaration and call supply enough evidence, and ambiguous owners remain unresolved. Objective-C `super` calls follow the superclass, Solidity bare calls follow contract inheritance, and Erlang bare calls honor explicit module imports.

Lua local aliases follow members exported by a required project module, including renamed members and bounded re-exports. Standard-library and external-module aliases remain unresolved. CommonJS calls follow explicit default exports, module forwarding and `require('./module').member` bindings.

Literal local `require` calls also create file dependencies, including side-effect calls and calls inside functions. Computed specifiers, external packages and a locally shadowed `require` do not create these dependencies.

TypeScript namespace re-exports retain their namespace boundary and allow nested member calls. C++ access macros and mid-declaration conditionals preserve members, indexing the first branch of each normalized conditional. Visible class-scoped aliases and declared complex receivers retain their method owners; unsubstituted template parameters and ambiguous owners remain unresolved.

Java and Kotlin enum constants retain their own methods and body calls. Explicit external imports own their names, while project imports follow nested types and aliases. Scala block locals and package objects, C# namespaces and nested types, and Java types follow their lexical and import scopes. Calls distinguish overloads by argument shape or Swift labels; receiver calls keep the owner supplied by construction, typed properties, Kotlin DSL lambdas or pytest fixtures. Python package imports follow bounded re-exports. Python `super()` calls follow the class's method resolution order. A Python method value read through a module-level variable (`settings.conn.fetch`) follows the classes constructed into that variable. Framework name heuristics start in the calling file and apply the native visibility checks. React and Express naming conventions require imports to reach another file. NestJS provider lookup also supports convention siblings in the same directory. Minified bundles, private component scripts and test suites do not supply unrelated production call targets.

React Router links and `navigate` calls can use a route-config object's `getHref` helper. Literal destinations and whole-segment template parameters are supported; factory-created config objects, shadowed bindings, spread overrides and computed path fragments remain unresolved.
| React Native `NativeModules[key]` | Computed native-module calls to the native method, when the key is a literal or a same-file literal binding |
| `window.postMessage` | Posted messages to the listeners that check the same literal `source` string or member name |

Call resolution the fork adds:

- Kotlin infix calls keep their call edge when a comment sits inside the expression.
- Kotlin receiver inference follows bounded chains of declared returns and verified receiver-preserving methods. Properties initialized by typed factory calls keep compatible imported extensions. Explicit casts, single-type `when` branches, filtered collection elements and bound generic factory arguments also supply receiver types.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- Kotlin chains on instance and extension receivers use the declared return type, including nested and multiline calls; upstream covers class and companion-factory chains. An imported return-type guess for an unknown receiver keeps confidence at most 0.7, and so does a Rust chain match without a proved receiver type.
- Kotlin multi-dollar strings keep their interpolation threshold.
- C++ visible class-scoped aliases and declared complex receivers keep their method owners; unsubstituted template parameters and ambiguous owners stay unresolved.
- C++ namespace aliases and declarations in macro-opened namespaces are looked up only in the caller's include closure; upstream pools them across all files.
- C# namespace `using` directives and `using` aliases apply only inside their enclosing namespace, not to sibling namespaces in the same file (`using static` is still file-wide, as upstream).
- A Java field declared with a qualified type (`outside.Repository`) keeps its qualifier, so calls through it never resolve to an unrelated project class with the same simple name.
- Python `super().method()` resolves along the class's C3 method resolution order, starting after the class itself. Upstream resolves it like `self.method()` and drops the link only when it lands on the calling method.
- A `require` call creates no file dependency when a local binding shadows `require`.

**Server endpoints**

| Addition | What it links |
|---|---|
| HTTP routes | Literal routes in Hono, Elysia, Fastify, Koa router, H3, Hyper-Express, Bun, Effect v4 and Vixeny; Fastify plugin files with `@fastify/autoload` directory prefixes; Nuxt `server/routes/`, method suffixes and route groups |
| HTTP routes | Routes in Hono, Elysia, Fastify, Koa router, H3, Hyper-Express, Bun, Effect v4 and Vixeny, read from the router object a call is made on rather than the names `app` and `router`, with composed prefixes (Hono `basePath`, Koa `prefix`, Elysia `group`, Fastify `register`); Fastify plugin files with `@fastify/autoload` directory prefixes; Nuxt `server/routes/` and method suffixes. Upstream matches calls on `app` and `router` by name, composes Express `X.use('/prefix', router)` mounts across files, and reads Nuxt `server/api/` |
| Route groups | Group prefixes in route paths for gin, chi, gorilla, actix `web::scope` and GoFrame |
| TanStack Start server routes | `server.handlers` tables in file routes as method-qualified endpoints, linked to named handlers |

Expand All @@ -143,10 +140,12 @@ React Router links and `navigate` calls can use a route-config object's `getHref
| Addition | What it links |
|---|---|
| Analog | `src/app/pages/**/*.page.ts` file routes, linked to their page component classes |
| Angular Router | On top of upstream's reader: `provideRouter` / `RouterModule` imported under an alias (a `$`-prefixed one included) still register routes, a routes file behind an NgModule's routing module or an `export *` barrel sits under its lazy path, and named-`outlet` or `...spread` entries name no screen |
| Astro routes | Pages calling their own file's components, endpoint method exports to handlers, and `<a href>` / `Astro.redirect` navigation |
| Angular Router | On top of upstream's reader: `provideRouter` / `RouterModule` imported under an alias (a `$`-prefixed one included) still register routes, a routes file one hop behind an NgModule's routing module, including a routing module the NgModule imports through a local barrel, sits under its lazy path, and named-`outlet` or `...spread` entries name no screen |
| Astro routes | `<a href>` and `Astro.redirect` navigation between pages, endpoint method aliases such as `export { handler as GET }`, and top-level calls in browser scripts attributed to the file rather than the component (upstream binds a page to its component and directly exported endpoint methods to handlers) |
| Nuxt pages | `(group)` folders dropped from page route paths |
| Qwik City | `src/routes` index pages and `onGet`/`onPost`-style endpoint handlers, linked to their components and handlers |
| React Router framework mode | Pages declared in `app/routes.ts`, linked to each module's default component |
| React Router `getHref` | On top of upstream's reader: a shadowed binding, a factory-created config object, a spread override or a computed path fragment is left unlinked rather than linked to a guessed route |
| RedwoodSDK routes | `defineApp` route trees (`route`, `index`, `render`, `layout`, `prefix`, method tables), linked to their handlers |
| Remix / React Router file routes | The default `app/routes/` file convention (and `flatRoutes()`), linked to each page's default component |
| Solid Router | `<Route>` JSX and route-config arrays, including arrays imported from another file, linked to their (possibly lazy) components |
Expand Down Expand Up @@ -184,7 +183,7 @@ Upstream at `6560052` takes 2.3 to 2.7 times as long to index Python as at `290e
| supabase | 6.30 s | 2.94 s | 2.77 s |
| n8n | 13.2 s | 4.38 s | 4.06 s |

Both builds rebuild the links inferred from events, callbacks and function pointers after a sync, so a sync ends with the graph a full index would build ([colbymchenry/codegraph#1988](https://github.com/colbymchenry/codegraph/issues/1988)). Upstream reruns every inference pass; the fork reruns only the passes the changed files can affect. `CODEGRAPH_SYNC_RESYNTHESIS=0` turns the fork's rebuild off, trading it for stale inferred links.
Both builds rebuild the links inferred from events, callbacks and function pointers after a sync, so a sync ends with the graph a full index would build ([colbymchenry/codegraph#1988](https://github.com/colbymchenry/codegraph/issues/1988)). Upstream reruns every inference pass inside each sync that touches an inferred link; the fork reruns the same passes a full index runs once edits pause. `CODEGRAPH_SYNC_RESYNTHESIS=0` turns the fork's rebuild off, trading it for stale inferred links.

**MCP server** on n8n (`codegraph serve --mcp`; memory and CPU summed over the server and the daemon it starts):

Expand Down