Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- `codegraph install` now sets up IBM Bob, for Bob IDE and Bob Shell: it adds the CodeGraph MCP server to `~/.bob/settings/mcp.json`, or to `.bob/mcp.json` in a project. Thanks @QuentinDstl.
- `codegraph install` now sets up Factory Droid: it adds the CodeGraph MCP server to `~/.factory/mcp.json` or a project's `.factory/mcp.json`, plus a short CodeGraph section in `AGENTS.md`, and Droid picks it up without a restart. Thanks @Rubyj and @KrystianMarek.
- `codegraph install` now sets up Windsurf: it adds the CodeGraph MCP server to `~/.codeium/windsurf/mcp_config.json`, which Devin, Windsurf's new name, imports by default. Thanks @SEPURI-SAI-KRISHNA.
- Tauri apps now connect across the line between the frontend and Rust: `codegraph_explore`, callers and impact follow `invoke('save_file')`, tauri-specta's typed `commands.saveFile()` and your own wrappers around `invoke` to the `#[tauri::command]` function that answers, and follow events between Rust and the frontend, raw `emit` / `listen` and tauri-specta's typed events alike. Thanks @vdavid and @nk33-dev.
- `codegraph install` now sets up Qwen Code: it adds the CodeGraph MCP server to `settings.json`, keeping any comments in that file, plus a short CodeGraph section in `QWEN.md`, and it can auto-allow CodeGraph's tools as it does for Claude Code. Thanks @ZevGit and @LiangshouX.
- `codegraph install` now sets up DeepSeek Harness (`dsh`): one entry in `~/.dsh/cordis.patch.yml` covers every profile, a short CodeGraph section goes in `~/.dsh/AGENTS.md`, and an entry you wrote yourself is left as it is. Thanks @hfldqwe.
- `codegraph install` now sets up Grok Build: it adds the CodeGraph MCP server to `~/.grok/config.toml` or a project's `.grok/config.toml`, plus a short rule in `rules/codegraph.md`. Thanks @ximing.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ CodeGraph's parsing engine is a **native Rust kernel**: 20 languages — TypeScr
| **20+ Languages** | TypeScript, JavaScript, ArkTS, Python, Go, Rust, Java, C#, VB.NET, PHP, Ruby, C, C++, CUDA, Objective-C, Metal, Swift, Kotlin, Scala, Dart, Lua, Luau, R, Nix, Erlang, CFML, COBOL, Solidity, Terraform/OpenTofu, Svelte, Vue, Astro, Liquid, Pascal/Delphi |
| **Framework-aware Routes** | Recognizes web-framework routing files and links URL patterns to their handlers across 17 frameworks |
| **Mixed iOS / React Native / Expo** | Closes cross-language flows that static parsing misses: Swift ↔ ObjC bridging, React Native legacy bridge + TurboModules + Fabric view components, native → JS event emitters, Expo Modules |
| **Tauri** | Follows a desktop app across its web frontend and Rust core: `invoke('save_file')`, tauri-specta's typed `commands.saveFile()`, your own wrappers around `invoke`, and events in both directions |
| **100% Local** | No data leaves your machine. No API keys. No external services. SQLite database only |

<details>
Expand Down
732 changes: 732 additions & 0 deletions __tests__/tauri-ipc.test.ts

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/design/dynamic-dispatch-coverage-playbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ Status legend: ✅ done+validated · 🔬 hole identified · ⬜ not started.
| ObjC/Java/Kotlin → JS | React Native event emitters | native `sendEventWithName:`/`emit(...)` → JS `addListener('e', handler)` | S (cross-lang channel) | ✅ **rn-event-channel synthesizer** — matches ObjC `sendEventWithName:@"X"`, Swift `sendEvent(withName: "X", ...)`, and JVM `.emit("X", ...)` to JS `addListener('X', handler)` keyed by literal event name. Same fan-out cap (`EVENT_FANOUT_CAP=6`) as in-language channel. **Subscribe-wrapper fallback** for RN-library APIs (`const Foo = { watchX(listener) { addListener('e', listener) } }`) — when the handler arg is a parameter, falls back to the enclosing function and then the enclosing `constant`/`variable` (reachability-correct attribution to the JS API surface). RNFirebase L **3 push-notification flow edges** (UIApplicationDelegate → JS `onMessage`/`onNotificationOpenedApp`), RNGeolocation S **2 location-event edges** (Swift `onLocationChange`/`onLocationError` → JS `Geolocation`). 🔬 inline arrow handlers `addListener('e', d => …)` (anonymous frontier) |
| JS × Swift/Kotlin | Expo Modules | JS `requireNativeModule('X').fn(...)` → Swift/Kotlin `Function("fn") { ... }` | R (extract → synthetic method nodes) | ✅ **expo-modules framework extractor** — parses Swift/Kotlin `Module { Name("X"); Function("y") { ... }; AsyncFunction("z") { ... }; Property("w") { ... } }` literals and synthesizes `method` nodes named after each declaration. JS callsites resolve via existing name-matcher (no separate `resolve()` needed). expo-haptics S **6 method nodes** (`notificationAsync`, `impactAsync`, `selectionAsync` × Swift + Kotlin), expo-camera M **41** (full SDK surface incl. `takePictureAsync`, `record`, `scanFromURLAsync`, view props `width`/`height`), expo SDK sweep L **134** (7 packages, 72 Swift + 62 Kotlin). Same-name JS wrappers in the package itself shadow the native names (`CameraView.tsx`'s `pausePreview` wraps native `pausePreview`); external consumer apps bridge through to native directly. 🔬 closure body extraction (the Function trailing closure isn't a body-range node yet) |
| JS × native | React Native Fabric / Codegen + legacy Paper view components | JSX `<MyView prop={v}/>` → Codegen spec → native class (or Paper `RCT_EXPORT_VIEW_PROPERTY` / `@ReactProp`) | R (extract) + S (native-impl) + JSX | ✅ **fabric-view extractor + fabric-native-impl synthesizer** — extractor parses **both** modern Codegen TS specs (`codegenNativeComponent<NativeProps>('Name', ...)`) **and** legacy Paper view-manager macros (`RCT_EXPORT_VIEW_PROPERTY` on ObjC, `@ReactProp` on Java/Kotlin). Emits a `component` node per declaration + a `property` node per declared prop. Synthesizer links the component to its native impl class by RN's convention-based name+suffix (`exact`/`View`/`ComponentView`/`Manager`/`ViewManager`). Combined with `reactJsxChildEdges`, full consumer flow: JSX `<MyView/>` → fabric `component` → native class. Validated on RNSegmentedControl S **(legacy Paper) 1 component + 11 props + 4 bridges**, RNScreens M **(pure Codegen) 27 components + 272 props + 68 bridges** (was 0 before Phase 6), RNSkia L **(hybrid + monorepo) 5 + 14 + 15 across Codegen TS + Android Java + iOS ObjC**. **Monorepo detect** added: probes `packages/<sub>/package.json` etc. via `listDirectories` when the root manifest is a workspace declaration (was the gating bug on RNSkia). 🔬 Fabric event-handler props (`onTap={cb}`) — JSX attribute extraction needed |
| Rust × JS/TS | Tauri IPC (raw + tauri-specta) | frontend `invoke('foo_bar')` / `commands.fooBar()` → Rust `#[tauri::command] fn foo_bar`; `emit` ↔ `listen` either way; a listener → `#[derive(Event)]` struct | S (cross-language, literal on both sides) | ✅ **tauri-synthesizer** (`tauriEdges`): `tauri-command` from the calling function to the function a `generate_handler!` / `collect_commands!` list registers (`registeredAt` = the list entry), per app folder, a tie links nothing; a macro-built list falls back to the app's declared commands; `#[cfg]` platform variants all link; `plugin:name\|cmd` goes to the plugin's crate; a project's own function that forwards its first parameter to `invoke` counts as `invoke`. `tauri-event` pairs emit and listen by wire name in any direction, tauri-specta's `Struct { … }.emit(&app)` and `events.struct.listen()` included. The pass starts from two indexed lookups, so a Rust + TS project with no Tauri pays 2 ms. Validated: pot-desktop S (raw, 37 edges, 20 of 22 commands with a caller; the other 2 have none in its frontend), clash-verge-rev M (typed, 93 of 93 commands, the same 93 by camelCase with `bindings.ts` removed), Cmdr L (typed and raw on a macro-built list, 1,588-file IPC subtree: 611 of 630 commands, 135 event hops, 90 listener → struct), GitButler (wrappers, `backend.invoke`: 33 of 35 literal call sites), Yaak (6 plugin commands; the rest goes through one `rpc` command). Controls lightningcss, express, ripgrep byte-identical. 🔬 runtime-built names, GitButler's `extraOptions: { command }` table, listening through a project's wrapper around `listen` |

(Verify the exact supported set against `src/extraction/languages/` and
`src/resolution/frameworks/` before starting — this table is a starting point.)
Expand Down
7 changes: 7 additions & 0 deletions src/db/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1627,6 +1627,13 @@ export class QueryBuilder {
return new Set(rows.map((r) => r.language));
}

/** Whether any function of `language` carries `decorator`: a project's marker for a framework it uses. */
hasDecoratedFunction(language: Language, decorator: string): boolean {
return !!this.db.prepare(
`SELECT 1 FROM nodes WHERE kind = 'function' AND language = ? AND decorators LIKE ? LIMIT 1`
).get(language, `%"${decorator}"%`);
}

/**
* Get nodes by exact name match (uses idx_nodes_name index).
*
Expand Down
6 changes: 6 additions & 0 deletions src/graph/synthesized-hop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ export function describeSynthesizedHop(edge: Edge | null | undefined): Synthesiz
}
case 'event-emitter':
return hop(`event ${quoted(m.event, 'an event')}`);
case 'tauri-command':
return hop(`Tauri command ${quoted(m.command, 'a command')}`, " — the frontend's invoke onto its Rust handler");
case 'tauri-event': {
const direction = m.tier === 'server→client' ? ' → frontend' : m.tier === 'client→server' ? ' → Rust' : '';
return hop(`Tauri event ${quoted(m.event, 'an event')}`, direction);
}
case 'react-render':
return hop('React re-render via setState');
case 'jsx-render':
Expand Down
5 changes: 4 additions & 1 deletion src/resolution/callback-synthesizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { angularTemplateEdges } from './angular-template-synthesizer';
import { svelteKitLinkEdges, svelteKitPageComponentEdges } from './sveltekit-synthesizer';
import { createYielder, type MaybeYield } from './cooperative-yield';
import { crossTierEdges, hasCrossTierPattern, hasTestRequestPattern, testRequestEdges } from './tier-synthesizer';
import { hasTauriPattern, tauriEdges } from './tauri-synthesizer';
import { enclosingFn, makeLineAt } from './synth-utils';
import { resolveImportPath } from './import-resolver';
import { cppSpecializationArguments, crossesCodeBoundary, goRefQualification, isManualMockHiddenFrom, isManualMockPath, jsCodeBindsName } from './name-matcher';
Expand Down Expand Up @@ -5113,7 +5114,7 @@ export function hasSynthesisPattern(filePath: string, content: string): boolean
// Every Go file: besides the declarations the interface passes read, a call
// through a func field, or a function put in one, can sit in any of them.
if (/\.go$/.test(filePath)) return true;
if (hasCrossTierPattern(content) || hasTestRequestPattern(filePath, content)) return true;
if (hasCrossTierPattern(content) || hasTestRequestPattern(filePath, content) || hasTauriPattern(filePath, content)) return true;
if (/\b(?:render|build|setState|defineStore|createStore|createApi|Store|href|sendEvent|sendEventWithName)\b|<\/|\/>/.test(content)) return true;
// A wrapper renders the component it wraps (jsxEdges), in a file with no markup of its own too.
if (WRAPPER_CALL_RE.test(content)) return true;
Expand Down Expand Up @@ -5151,6 +5152,8 @@ export const SYNTH_PASSES: SynthPassDef[] = [
// Before the in-process emitter pass: the same (source, target) pair
// keeps the more specific edge — the one that says which tier it crosses.
{ name: 'tierEdges', gate: (has) => has(...JS_FAMILY), run: (_q, c, y) => crossTierEdges(c, y) },
// A Tauri frontend's `invoke('x')` onto the Rust command, and events across that seam.
{ name: 'tauriEdges', gate: (has) => has('rust') && has('vue', 'svelte', ...JS_FAMILY), run: (q, c, y) => tauriEdges(q, c, y) },
// A Spring / Laravel test's request (`mockMvc.perform(post("/x"))`,
// `$this->postJson('api/x')`) onto the route it reaches, so the handler
// counts as tested.
Expand Down
Loading