@colbymchenry/codegraph 0.9.3 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +142 -82
  2. package/npm-shim.js +215 -27
  3. package/package.json +7 -7
package/README.md CHANGED
@@ -6,19 +6,21 @@
6
6
 
7
7
  **~35% cheaper · ~70% fewer tool calls · 100% local**
8
8
 
9
+ ### [Documentation & Website →](https://colbymchenry.github.io/codegraph/)
10
+
9
11
  [![npm version](https://img.shields.io/npm/v/@colbymchenry/codegraph.svg)](https://www.npmjs.com/package/@colbymchenry/codegraph)
10
12
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
11
13
  [![Self-contained](https://img.shields.io/badge/Node.js-bundled%20%C2%B7%20none%20required-brightgreen.svg)](https://nodejs.org/)
12
14
 
13
- [![Windows](https://img.shields.io/badge/Windows-supported-blue.svg)](#)
14
- [![macOS](https://img.shields.io/badge/macOS-supported-blue.svg)](#)
15
- [![Linux](https://img.shields.io/badge/Linux-supported-blue.svg)](#)
15
+ [![Windows](https://img.shields.io/badge/Windows-supported-blue.svg)](#supported-platforms)
16
+ [![macOS](https://img.shields.io/badge/macOS-supported-blue.svg)](#supported-platforms)
17
+ [![Linux](https://img.shields.io/badge/Linux-supported-blue.svg)](#supported-platforms)
16
18
 
17
- [![Claude Code](https://img.shields.io/badge/Claude_Code-supported-blueviolet.svg)](#)
18
- [![Cursor](https://img.shields.io/badge/Cursor-supported-blueviolet.svg)](#)
19
- [![Codex CLI](https://img.shields.io/badge/Codex_CLI-supported-blueviolet.svg)](#)
20
- [![opencode](https://img.shields.io/badge/opencode-supported-blueviolet.svg)](#)
21
- [![Hermes Agent](https://img.shields.io/badge/Hermes_Agent-supported-blueviolet.svg)](#)
19
+ [![Claude Code](https://img.shields.io/badge/Claude_Code-supported-blueviolet.svg)](#supported-agents)
20
+ [![Cursor](https://img.shields.io/badge/Cursor-supported-blueviolet.svg)](#supported-agents)
21
+ [![Codex CLI](https://img.shields.io/badge/Codex_CLI-supported-blueviolet.svg)](#supported-agents)
22
+ [![opencode](https://img.shields.io/badge/opencode-supported-blueviolet.svg)](#supported-agents)
23
+ [![Hermes Agent](https://img.shields.io/badge/Hermes_Agent-supported-blueviolet.svg)](#supported-agents)
22
24
 
23
25
  </div>
24
26
 
@@ -56,6 +58,16 @@ codegraph init -i
56
58
 
57
59
  </div>
58
60
 
61
+ ### Uninstall
62
+
63
+ Changed your mind? One command removes CodeGraph from every agent it configured:
64
+
65
+ ```bash
66
+ codegraph uninstall
67
+ ```
68
+
69
+ <sub>Reverses the installer — strips CodeGraph's MCP server config, instructions, and permissions from each configured agent. Your project indexes (`.codegraph/`) are left untouched; remove those per-project with `codegraph uninit`. Use `--target` to remove from specific agents, or `--yes` to run non-interactively.</sub>
70
+
59
71
  ---
60
72
 
61
73
  ## Why CodeGraph?
@@ -66,26 +78,26 @@ When Claude Code explores a codebase, it spawns **Explore agents** that scan fil
66
78
 
67
79
  ### Benchmark Results
68
80
 
69
- Tested across **7 real-world open-source codebases** spanning 7 languages, comparing an agent (Claude Code, headless) answering one architecture question **with** and **without** CodeGraph. Each cell is the savings at the **median of 4 runs per arm**.
81
+ Tested across **7 real-world open-source codebases** spanning 7 languages, comparing an agent (Claude Code, headless) answering one architecture question **with** and **without** CodeGraph. Each cell is the savings at the **median of 4 runs per arm**. _Re-validated on **v0.9.4** (2026-05-24)._
70
82
 
71
- > **Average: 35% cheaper · 59% fewer tokens · 49% faster · 70% fewer tool calls**
83
+ > **Average: 35% cheaper · 57% fewer tokens · 46% faster · 71% fewer tool calls**
72
84
 
73
85
  | Codebase | Language | Cost | Tokens | Time | Tool calls |
74
86
  |----------|----------|------|--------|------|------------|
75
- | **VS Code** | TypeScript · ~10k files | 35% cheaper | 73% fewer | 41% faster | 72% fewer |
76
- | **Excalidraw** | TypeScript · ~600 | 47% cheaper | 73% fewer | 60% faster | 86% fewer |
77
- | **Django** | Python · ~2.7k | 34% cheaper | 64% fewer | 59% faster | 81% fewer |
78
- | **Tokio** | Rust · ~700 | 52% cheaper | 81% fewer | 63% faster | 89% fewer |
79
- | **OkHttp** | Java · ~640 | 17% cheaper | 41% fewer | 36% faster | 64% fewer |
80
- | **Gin** | Go · ~150 | 22% cheaper | 23% fewer | 34% faster | 19% fewer |
81
- | **Alamofire** | Swift · ~100 | 38% cheaper | 59% fewer | 51% faster | 77% fewer |
87
+ | **VS Code** | TypeScript · ~10k files | 26% cheaper | 78% fewer | 52% faster | 85% fewer |
88
+ | **Excalidraw** | TypeScript · ~640 | 52% cheaper | 90% fewer | 73% faster | 96% fewer |
89
+ | **Django** | Python · ~3k | 12% cheaper | 36% fewer | 19% faster | 53% fewer |
90
+ | **Tokio** | Rust · ~790 | 82% cheaper | 86% fewer | 71% faster | 92% fewer |
91
+ | **OkHttp** | Java · ~645 | 2% cheaper | 13% fewer | 31% faster | 45% fewer |
92
+ | **Gin** | Go · ~110 | 21% cheaper | 34% fewer | 27% faster | 40% fewer |
93
+ | **Alamofire** | Swift · ~110 | 47% cheaper | 64% fewer | 48% faster | 83% fewer |
82
94
 
83
95
  The gains scale with codebase size: on large repos the agent answers from the index in a handful of calls with **zero file reads**, while the no-CodeGraph agent fans out across grep/find/Read (and the sub-agents it spawns). On a small repo like Gin (~150 files) native search is already cheap, so the margin narrows.
84
96
 
85
97
  <details>
86
98
  <summary><strong>Full benchmark details</strong></summary>
87
99
 
88
- **Methodology.** Each arm is `claude -p` (Claude Opus 4.7, Claude Code v2.1.145) run headlessly against the repo with `--strict-mcp-config`: **WITH** = CodeGraph's MCP server enabled, **WITHOUT** = an empty MCP config. Built-in Read/Grep/Bash stay available to both. Same question per repo, **4 runs per arm, median reported**. Cost = the run's `total_cost_usd`; Tokens = total tokens processed (input incl. cached + output); Time = wall-clock; Tool calls = every tool invocation, including those inside any sub-agents the model spawns. Repos cloned at `--depth 1` and indexed by the same CodeGraph build that served them.
100
+ **Methodology.** Each arm is `claude -p` (Claude Opus 4.7) run headlessly against the repo with `--strict-mcp-config`: **WITH** = CodeGraph's MCP server enabled, **WITHOUT** = an empty MCP config. Built-in Read/Grep/Bash stay available to both. Same question per repo, **4 runs per arm, median reported**. Cost = the run's `total_cost_usd`; Tokens = total tokens processed (input incl. cached + output); Time = wall-clock; Tool calls = every tool invocation, including those inside any sub-agents the model spawns. Repos cloned at `--depth 1` and indexed by the same CodeGraph build that served them. Re-validated on codegraph **v0.9.4** (2026-05-24); per-repo numbers move run-to-run with how hard the without-arm thrashes (the median-of-4 smooths it, but tails remain — e.g. Tokio's without-arm hit $2.41/3m one batch).
89
101
 
90
102
  **Queries:**
91
103
  | Codebase | Query |
@@ -101,13 +113,13 @@ The gains scale with codebase size: on large repos the agent answers from the in
101
113
  **Raw medians — WITH → WITHOUT:**
102
114
  | Codebase | Cost | Tokens | Time | Tool calls |
103
115
  |----------|------|--------|------|------------|
104
- | VS Code | $0.42 → $0.64 | 393k1.4M | 1m 0s1m 43s | 723 |
105
- | Excalidraw | $0.54 → $1.02 | 851k → 3.2M | 1m 17s 3m 14s | 1283 |
106
- | Django | $0.41 → $0.62 | 499k → 1.4M | 1m 0s2m 25s | 9 → 48 |
107
- | Tokio | $0.50 → $1.04 | 657k3.4M | 1m 5s 2m 56s | 975 |
108
- | OkHttp | $0.36 → $0.44 | 352k596k | 45s → 1m 11s | 514 |
109
- | Gin | $0.36 → $0.46 | 431k562k | 47s → 1m 11s | 78 |
110
- | Alamofire | $0.61 → $0.99 | 1.1M → 2.6M | 1m 19s → 2m 41s | 1564 |
116
+ | VS Code | $0.60 → $0.80 | 601k2.8M | 1m 10s2m 26s | 855 |
117
+ | Excalidraw | $0.43 → $0.90 | 344k → 3.5M | 48s2m 58s | 379 |
118
+ | Django | $0.59 → $0.67 | 739k → 1.2M | 1m 19s1m 38s | 9 → 19 |
119
+ | Tokio | $0.42 → $2.41 | 379k2.6M | 53s3m 2s | 453 |
120
+ | OkHttp | $0.47 → $0.47 | 636k730k | 42s → 1m 1s | 611 |
121
+ | Gin | $0.37 → $0.47 | 444k675k | 44s → 1m 0s | 610 |
122
+ | Alamofire | $0.61 → $1.14 | 1.0M → 2.8M | 1m 17s → 2m 27s | 1269 |
111
123
 
112
124
  **Why CodeGraph wins:** with the index available, the agent answers directly — `codegraph_context` to map the area, then one `codegraph_explore` for the relevant source — and stops, usually with zero file reads. Without it, the agent (and the Explore sub-agents it spawns) spends most of its budget on discovery (find/ls/grep) before reading the right code. CodeGraph only helps when queried *directly*, so its instructions steer agents to answer directly rather than delegate exploration to file-reading sub-agents — otherwise a sub-agent reads files regardless and CodeGraph becomes overhead.
113
125
 
@@ -123,8 +135,9 @@ The gains scale with codebase size: on large repos the agent answers from the in
123
135
  | **Full-Text Search** | Find code by name instantly across your entire codebase, powered by FTS5 |
124
136
  | **Impact Analysis** | Trace callers, callees, and the full impact radius of any symbol before making changes |
125
137
  | **Always Fresh** | File watcher uses native OS events (FSEvents/inotify/ReadDirectoryChangesW) with debounced auto-sync — the graph stays current as you code, zero config |
126
- | **19+ Languages** | TypeScript, JavaScript, Python, Go, Rust, Java, C#, PHP, Ruby, C, C++, Swift, Kotlin, Dart, Lua, Luau, Svelte, Liquid, Pascal/Delphi |
138
+ | **20+ Languages** | TypeScript, JavaScript, Python, Go, Rust, Java, C#, PHP, Ruby, C, C++, Objective-C, Swift, Kotlin, Dart, Lua, Luau, Svelte, Liquid, Pascal/Delphi |
127
139
  | **Framework-aware Routes** | Recognizes web-framework routing files and links URL patterns to their handlers across 14 frameworks |
140
+ | **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 |
128
141
  | **100% Local** | No data leaves your machine. No API keys. No external services. SQLite database only |
129
142
 
130
143
  ---
@@ -152,6 +165,35 @@ CodeGraph detects web-framework routing files and emits `route` nodes linked by
152
165
 
153
166
  ---
154
167
 
168
+ ## Mixed iOS / React Native / Expo bridging
169
+
170
+ Real iOS and React Native codebases live across multiple languages — a Swift caller invokes an Objective-C selector that's been auto-bridged, a JS file calls into a native module via the React Native bridge, a JSX component delegates to a native view manager. Static tree-sitter extraction stops at each language boundary. CodeGraph bridges them so `trace`, `callers`, `callees`, and `impact` connect end-to-end across the gap.
171
+
172
+ | Boundary | JS / Swift side | Native side | How |
173
+ |---|---|---|---|
174
+ | **Swift → ObjC** | Swift `obj.foo(bar:)` | ObjC selector `-fooWithBar:` | `@objc` auto-bridging rules (including init/property/protocol forms) + Cocoa preposition prefixes (`With`/`For`/`By`/`In`/`On`/`At`/…) |
175
+ | **ObjC → Swift** | ObjC `[obj fooWithBar:]` | Swift `@objc func foo(bar:)` | Reverse-bridge name candidates; verifies `@objc` exposure from source |
176
+ | **React Native legacy bridge** | JS `NativeModules.X.fn(...)` | ObjC `RCT_EXPORT_METHOD` / `RCT_REMAP_METHOD` · Java/Kotlin `@ReactMethod` | Parses macro/annotation declarations to build a JS-name → native-method map |
177
+ | **React Native TurboModules** | JS `import M from './NativeM'; M.fn(...)` | Native impl matching the Codegen spec | Treats the `Native<X>.ts` spec interface as ground truth |
178
+ | **RN native → JS events** | JS `new NativeEventEmitter(...).addListener('e', cb)` | ObjC `[self sendEventWithName:@"e" body:...]` · Swift `sendEvent(withName: "e", ...)` · Java/Kotlin `.emit("e", ...)` | Synthesized cross-language event channel keyed by literal event name |
179
+ | **Expo Modules** | JS `requireNativeModule('X').fn(...)` | Swift / Kotlin `Module { Name("X"); AsyncFunction("fn") { ... } }` | Parses the Expo DSL literals; synthetic method nodes resolve via existing name-match |
180
+ | **Fabric view components** | JSX `<MyView prop={v}/>` | TS Codegen spec + native impl class | Spec → `component` node; convention-based name+suffix lookup (`View`/`ComponentView`/`Manager`/`ViewManager`) bridges to native |
181
+ | **Legacy Paper view managers** | JSX `<MyView prop={v}/>` | ObjC `RCT_EXPORT_VIEW_PROPERTY` · Java/Kotlin `@ReactProp` | Same as Fabric — Paper-era declarations also produce `component` + `property` nodes |
182
+
183
+ **Validated on real codebases** (small + medium + large for each bridge):
184
+
185
+ | Bridge | Small | Medium | Large |
186
+ |---|---|---|---|
187
+ | Swift ↔ ObjC | [Charts](https://github.com/danielgindi/Charts) | [realm-swift](https://github.com/realm/realm-swift) | [Wikipedia-iOS](https://github.com/wikimedia/wikipedia-ios) |
188
+ | RN legacy bridge | [AsyncStorage](https://github.com/react-native-async-storage/async-storage) | [react-native-svg](https://github.com/software-mansion/react-native-svg) | [react-native-firebase](https://github.com/invertase/react-native-firebase) |
189
+ | RN native → JS events | [RNGeolocation](https://github.com/Agontuk/react-native-geolocation-service) | — | react-native-firebase |
190
+ | Expo Modules | expo-haptics | expo-camera | expo SDK sweep (7 packages) |
191
+ | Fabric / Paper views | [react-native-segmented-control](https://github.com/react-native-segmented-control/segmented-control) | [react-native-screens](https://github.com/software-mansion/react-native-screens) | [react-native-skia](https://github.com/Shopify/react-native-skia) |
192
+
193
+ Each bridge emits edges tagged `provenance:'heuristic'` with `metadata.synthesizedBy:` set to a stable channel name (e.g. `swift-objc-bridge`, `rn-event-channel`, `fabric-native-impl`, `expo-module-extract`), so the agent can tell at a glance how a hop got into the graph.
194
+
195
+ ---
196
+
155
197
  ## Quick Start
156
198
 
157
199
  ### 1. Run the Installer
@@ -253,25 +295,21 @@ CodeGraph builds a semantic knowledge graph of codebases for faster, smarter cod
253
295
 
254
296
  ### If `.codegraph/` exists in the project
255
297
 
256
- **NEVER call `codegraph_explore` or `codegraph_context` directly in the main session.** These tools return large amounts of source code that fills up main session context. Instead, ALWAYS spawn an Explore agent for any exploration question (e.g., "how does X work?", "explain the Y system", "where is Z implemented?").
257
-
258
- **When spawning Explore agents**, include this instruction in the prompt:
259
-
260
- > This project has CodeGraph initialized (.codegraph/ exists). Use `codegraph_explore` as your PRIMARY tool — it returns full source code sections from all relevant files in one call.
261
- >
262
- > **Rules:**
263
- > 1. Follow the explore call budget in the `codegraph_explore` tool description — it scales automatically based on project size.
264
- > 2. Do NOT re-read files that codegraph_explore already returned source code for. The source sections are complete and authoritative.
265
- > 3. Only fall back to grep/glob/read for files listed under "Additional relevant files" if you need more detail, or if codegraph returned no results.
298
+ **Answer directly with CodeGraph don't delegate exploration to a file-reading sub-agent or a grep/read loop.** CodeGraph *is* the pre-built search index; re-deriving its answers with grep + Read repeats work it already did and costs more for the same result. For "how does X work?", architecture, trace, or where-is-X questions, answer in a handful of CodeGraph calls and stop — typically with **zero file reads**. The returned source is complete and authoritative: treat it as already read and do not re-open those files. Reach for raw Read/Grep only to confirm a specific detail CodeGraph didn't cover.
266
299
 
267
- **The main session may only use these lightweight tools directly** (for targeted lookups before making edits, not for exploration):
300
+ **Tool selection by intent:**
268
301
 
269
302
  | Tool | Use For |
270
303
  |------|---------|
271
- | `codegraph_search` | Find symbols by name |
272
- | `codegraph_callers` / `codegraph_callees` | Trace call flow |
304
+ | `codegraph_context` | Map a task / feature / area first — composes search + node + callers + callees in one call |
305
+ | `codegraph_trace` | "How does X reach Y" — the call path, each hop's body inline (follows dynamic-dispatch hops grep can't) |
306
+ | `codegraph_explore` | Survey several related symbols' source in ONE budget-capped call |
307
+ | `codegraph_search` | Find a symbol by name |
308
+ | `codegraph_callers` / `codegraph_callees` | Walk call flow one hop at a time |
273
309
  | `codegraph_impact` | Check what's affected before editing |
274
- | `codegraph_node` | Get a single symbol's details |
310
+ | `codegraph_node` | Get a single symbol's source / signature |
311
+
312
+ A direct CodeGraph answer is a handful of calls; a grep/read exploration is dozens.
275
313
 
276
314
  ### If `.codegraph/` does NOT exist
277
315
 
@@ -287,34 +325,23 @@ At the start of a session, ask the user if they'd like to initialize CodeGraph:
287
325
  ## How It Works
288
326
 
289
327
  ```
290
- ┌─────────────────────────────────────────────────────────────────┐
291
- │ Claude Code │
292
- │ │
293
- │ "Implement user authentication" │
294
- │ │ │
295
- │ ▼ │
296
- │ ┌─────────────────┐ ┌─────────────────┐ │
297
- │ │ Explore Agent │ ──── │ Explore Agent │ │
298
- │ └────────┬────────┘ └────────┬────────┘ │
299
- │ │ │ │
300
- └───────────┼────────────────────────┼─────────────────────────────┘
301
- │ │
302
- ▼ ▼
303
328
  ┌───────────────────────────────────────────────────────────────────┐
304
- CodeGraph MCP Server
305
- ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
306
- Search │ Callers │ │ Context │ │
307
- │ "auth" │ │ "login()" │ │ for task
308
- └──────┬──────┘ └──────┬──────┘ └──────┬──────┘
309
- │ │ │ │ │
310
- └────────────────┼────────────────┘ │
311
-
312
- │ ┌───────────────────────┐ │
313
- │ SQLite Graph DB
314
- • 387 symbols │ │
315
- │ • 1,204 edges
316
- • Instant lookups
317
- └───────────────────────┘
329
+ Claude Code
330
+
331
+ "How does a request reach the database?"
332
+ calls CodeGraph tools directly — no Explore sub-agent
333
+
334
+ └─────────────────────────────────┬─────────────────────────────────┘
335
+
336
+
337
+ ┌───────────────────────────────────────────────────────────────────┐
338
+ CodeGraph MCP Server
339
+
340
+ context · trace · explore · callers · callees · impact
341
+
342
+
343
+ │ SQLite knowledge graph │
344
+ │ symbols · edges · files · FTS5 full-text search │
318
345
  └───────────────────────────────────────────────────────────────────┘
319
346
  ```
320
347
 
@@ -333,6 +360,7 @@ At the start of a session, ask the user if they'd like to initialize CodeGraph:
333
360
  ```bash
334
361
  codegraph # Run interactive installer
335
362
  codegraph install # Run installer (explicit)
363
+ codegraph uninstall # Remove CodeGraph from your agents (inverse of install)
336
364
  codegraph init [path] # Initialize in a project (--index to also index)
337
365
  codegraph uninit [path] # Remove CodeGraph from a project (--force to skip prompt)
338
366
  codegraph index [path] # Full index (--force to re-index, --quiet for less output)
@@ -341,6 +369,9 @@ codegraph status [path] # Show statistics
341
369
  codegraph query <search> # Search symbols (--kind, --limit, --json)
342
370
  codegraph files [path] # Show file structure (--format, --filter, --max-depth, --json)
343
371
  codegraph context <task> # Build context for AI (--format, --max-nodes)
372
+ codegraph callers <symbol> # Find what calls a function/method (--limit, --json)
373
+ codegraph callees <symbol> # Find what a function/method calls (--limit, --json)
374
+ codegraph impact <symbol> # Analyze what code is affected by changing a symbol (--depth, --json)
344
375
  codegraph affected [files...] # Find test files affected by changes (see below)
345
376
  codegraph serve --mcp # Start MCP server
346
377
  ```
@@ -383,10 +414,12 @@ When running as an MCP server, CodeGraph exposes these tools to Claude Code:
383
414
  |------|---------|
384
415
  | `codegraph_search` | Find symbols by name across the codebase |
385
416
  | `codegraph_context` | Build relevant code context for a task |
417
+ | `codegraph_trace` | Trace the call path between two symbols ("how does X reach Y") in one call — each hop with its body inline, following dynamic-dispatch hops (callbacks, React re-render, interface→impl) that grep can't |
386
418
  | `codegraph_callers` | Find what calls a function |
387
419
  | `codegraph_callees` | Find what a function calls |
388
420
  | `codegraph_impact` | Analyze what code is affected by changing a symbol |
389
421
  | `codegraph_node` | Get details about a specific symbol (optionally with source code) |
422
+ | `codegraph_explore` | Return source for several related symbols grouped by file, plus a relationship map, in one call |
390
423
  | `codegraph_files` | Get indexed file structure (faster than filesystem scanning) |
391
424
  | `codegraph_status` | Check index health and statistics |
392
425
 
@@ -418,23 +451,49 @@ cg.close();
418
451
 
419
452
  ## Configuration
420
453
 
421
- There isn't any — CodeGraph is zero-config. It indexes every file whose
422
- extension maps to a [supported language](#supported-languages) and **respects
423
- your `.gitignore`**: in git repos via git itself, and in non-git projects by
424
- reading `.gitignore` files directly (root and nested, the same way git would).
454
+ There isn't any — CodeGraph is zero-config, with **no config file** to write or
455
+ keep in sync. Language support is automatic from the file extension; there's
456
+ nothing to wire up per language.
457
+
458
+ What it skips out of the box:
459
+
460
+ - **Dependency, build, and cache directories** — `node_modules`, `vendor`,
461
+ `dist`, `build`, `target`, `.venv`, `Pods`, `.next`, and the like across every
462
+ [supported stack](#supported-languages) — so the graph is your code, not
463
+ third-party noise. This holds even with no `.gitignore`.
464
+ - **Anything in your `.gitignore`** — honored in git repos via git, and in
465
+ non-git projects by reading `.gitignore` directly (root and nested).
466
+ - **Files larger than 1 MB** — generated bundles, minified JS, vendored blobs.
467
+
468
+ To keep something else out, add it to `.gitignore`. To pull a default-excluded
469
+ directory back **in** (say you really do want a vendored dependency indexed),
470
+ add a negation — `!vendor/`. The defaults apply uniformly, so committing a
471
+ dependency or build directory doesn't force it into the graph; the `.gitignore`
472
+ negation is the explicit opt-in.
473
+
474
+ ## Supported Platforms
475
+
476
+ Every release ships a self-contained build (bundled Node runtime — nothing to
477
+ compile) for all three desktop OSes, on both Intel/AMD (x64) and ARM (arm64):
478
+
479
+ | Platform | Architectures | Install |
480
+ |----------|---------------|---------|
481
+ | Windows | x64, arm64 | PowerShell installer or npm |
482
+ | macOS | x64, arm64 | shell installer or npm |
483
+ | Linux | x64, arm64 | shell installer or npm |
484
+
485
+ See [Get Started](#get-started) for the one-line install commands.
425
486
 
426
- What that means in practice:
487
+ ## Supported Agents
427
488
 
428
- - Anything git ignores `node_modules`, build output, secrets in `.env` — is
429
- never indexed. **To keep something out of the graph, add it to `.gitignore`.**
430
- - There's no config file to write or keep in sync, and nothing to wire up per
431
- language: support is automatic from the file extension.
432
- - Files larger than 1 MB are skipped (generated bundles, minified JS, vendored
433
- blobs) — they cost parse budget for no useful symbols.
489
+ The interactive installer auto-detects and configures each of these wiring up
490
+ the MCP server and writing its instructions file:
434
491
 
435
- > Committed files that aren't gitignored *are* indexed, even under `vendor/` or a
436
- > committed `dist/`. If you commit a dependency or build directory you don't want
437
- > in the graph, add it to `.gitignore`.
492
+ - **Claude Code**
493
+ - **Cursor**
494
+ - **Codex CLI**
495
+ - **opencode**
496
+ - **Hermes Agent**
438
497
 
439
498
  ## Supported Languages
440
499
 
@@ -451,6 +510,7 @@ What that means in practice:
451
510
  | Ruby | `.rb` | Full support |
452
511
  | C | `.c`, `.h` | Full support |
453
512
  | C++ | `.cpp`, `.hpp`, `.cc` | Full support |
513
+ | Objective-C | `.m`, `.mm`, `.h` | Partial support (classes, protocols, methods, `@property`, `#import`, message sends; `.mm` ObjC++ may parse incompletely) |
454
514
  | Swift | `.swift` | Full support |
455
515
  | Kotlin | `.kt`, `.kts` | Full support |
456
516
  | Scala | `.scala`, `.sc` | Full support (classes, traits, methods, type aliases, Scala 3 enums) |
@@ -475,7 +535,7 @@ What that means in practice:
475
535
 
476
536
  **MCP server not connecting** — Ensure the project is initialized/indexed, verify the path in your MCP config, and check that `codegraph serve --mcp` works from the command line.
477
537
 
478
- **Missing symbols** — The MCP server auto-syncs on save (wait a couple seconds). Run `codegraph sync` manually if needed. Check that the file's language is supported and isn't excluded by config patterns.
538
+ **Missing symbols** — The MCP server auto-syncs on save (wait a couple seconds). Run `codegraph sync` manually if needed. Check that the file's language is supported and isn't inside a `.gitignore`d or default-excluded directory (e.g. `node_modules`, `dist`).
479
539
 
480
540
  ## Star History
481
541
 
package/npm-shim.js CHANGED
@@ -11,48 +11,236 @@
11
11
  // (with node:sqlite), regardless of the user's Node version. The user's Node is
12
12
  // only ever a launcher; even an ancient version can run this file.
13
13
  //
14
+ // Self-heal (issue #303): some registries — notably the npmmirror/cnpm mirrors,
15
+ // and some corporate proxies — don't reliably mirror the per-platform
16
+ // optionalDependencies. npm treats an unfetchable optional dep as success and
17
+ // silently skips it, so the bundle goes missing and every command fails. When
18
+ // the installed bundle can't be resolved, this shim falls back to downloading
19
+ // the matching bundle straight from GitHub Releases — the very archive
20
+ // install.sh uses — into a cache dir, then runs that. Knobs:
21
+ // CODEGRAPH_NO_DOWNLOAD=1 disable the network fallback (print guidance)
22
+ // CODEGRAPH_INSTALL_DIR=DIR cache location (default: ~/.codegraph)
23
+ // CODEGRAPH_DOWNLOAD_BASE=URL release-download base (for mirrors/air-gapped)
24
+ //
14
25
  // Wired up at release time as the main package's `bin`:
15
- // "bin": { "codegraph": "scripts/npm-shim.js" }
26
+ // "bin": { "codegraph": "npm-shim.js" }
16
27
  // with the platform packages listed in `optionalDependencies`.
17
28
 
18
29
  var childProcess = require('child_process');
30
+ var fs = require('fs');
31
+ var os = require('os');
32
+ var path = require('path');
19
33
 
20
34
  var target = process.platform + '-' + process.arch; // e.g. darwin-arm64, linux-x64
21
35
  var pkg = '@colbymchenry/codegraph-' + target;
22
36
  var isWindows = process.platform === 'win32';
37
+ var REPO = 'colbymchenry/codegraph';
38
+
39
+ main().catch(function (e) {
40
+ process.stderr.write('codegraph: ' + (e && e.message ? e.message : String(e)) + '\n');
41
+ process.exit(1);
42
+ });
43
+
44
+ async function main() {
45
+ // Happy path: the npm-installed optional dependency. Fall back to a download
46
+ // when the registry didn't deliver it.
47
+ var resolved = resolveInstalledBundle() || (await selfHealBundle());
48
+ var res = childProcess.spawnSync(resolved.command, resolved.args, { stdio: 'inherit' });
49
+ if (res.error) {
50
+ process.stderr.write('codegraph: ' + res.error.message + '\n');
51
+ process.exit(1);
52
+ }
53
+ process.exit(res.status === null ? 1 : res.status);
54
+ }
23
55
 
24
- // On Windows the bundle's launcher is a .cmd batch file. Modern Node refuses to
25
- // spawn .cmd/.bat directly spawnSync throws EINVAL (the CVE-2024-27980
26
- // hardening, observed on Node 24). So on Windows we skip the .cmd and invoke the
27
- // bundled node.exe against the app entry point directly. On unix the bin launcher
28
- // is a shell script that spawns cleanly.
29
- var command, args;
30
- try {
56
+ // Resolve the launcher from the installed per-platform optionalDependency.
57
+ // Returns {command, args} or null if the package isn't installed.
58
+ function resolveInstalledBundle() {
59
+ try {
60
+ if (isWindows) {
61
+ // Modern Node refuses to spawn the bundle's .cmd directly (EINVAL, the
62
+ // CVE-2024-27980 hardening on Node 24), so invoke the bundled node.exe
63
+ // against the app entry point and pass --liftoff-only here.
64
+ var nodeExe = require.resolve(pkg + '/node.exe');
65
+ var entry = require.resolve(pkg + '/lib/dist/bin/codegraph.js');
66
+ return { command: nodeExe, args: liftoff(entry) };
67
+ }
68
+ return { command: require.resolve(pkg + '/bin/codegraph'), args: process.argv.slice(2) };
69
+ } catch (e) {
70
+ return null;
71
+ }
72
+ }
73
+
74
+ // Locate the launcher inside an extracted GitHub bundle directory (same
75
+ // node/lib/bin layout as the npm platform package). Returns {command, args} or
76
+ // null when the directory doesn't hold a usable bundle yet.
77
+ function launcherIn(dir) {
31
78
  if (isWindows) {
32
- command = require.resolve(pkg + '/node.exe');
33
- var entry = require.resolve(pkg + '/lib/dist/bin/codegraph.js');
34
- // --liftoff-only: keep tree-sitter's WASM grammars off V8's turboshaft tier
35
- // to avoid the Zone OOM on Node >= 22 (issues #293/#298). The unix launcher
36
- // passes this too; on Windows we invoke node.exe directly so add it here.
37
- args = ['--liftoff-only', entry].concat(process.argv.slice(2));
79
+ var nodeExe = path.join(dir, 'node.exe');
80
+ var entry = path.join(dir, 'lib', 'dist', 'bin', 'codegraph.js');
81
+ if (fs.existsSync(nodeExe) && fs.existsSync(entry)) {
82
+ return { command: nodeExe, args: liftoff(entry) };
83
+ }
38
84
  } else {
39
- command = require.resolve(pkg + '/bin/codegraph');
40
- args = process.argv.slice(2);
85
+ var launcher = path.join(dir, 'bin', 'codegraph');
86
+ if (fs.existsSync(launcher)) return { command: launcher, args: process.argv.slice(2) };
87
+ }
88
+ return null;
89
+ }
90
+
91
+ // --liftoff-only keeps tree-sitter's WASM grammars off V8's turboshaft tier to
92
+ // avoid the Zone OOM on Node >= 22 (issues #293/#298). The unix bin/codegraph
93
+ // launcher already passes it; on Windows we invoke node.exe directly so add it.
94
+ function liftoff(entry) {
95
+ return ['--liftoff-only', entry].concat(process.argv.slice(2));
96
+ }
97
+
98
+ // Download + cache the platform bundle from GitHub Releases. Returns
99
+ // {command, args}; exits the process with guidance if it can't.
100
+ async function selfHealBundle() {
101
+ var version = readVersion();
102
+ var bundlesDir = path.join(process.env.CODEGRAPH_INSTALL_DIR || path.join(os.homedir(), '.codegraph'), 'bundles');
103
+ var dest = path.join(bundlesDir, target + '-' + version);
104
+
105
+ // Already downloaded by a previous run? Use it even when downloads are
106
+ // disabled — CODEGRAPH_NO_DOWNLOAD blocks fetching, not a cached bundle.
107
+ var cached = launcherIn(dest);
108
+ if (cached) return cached;
109
+
110
+ if (process.env.CODEGRAPH_NO_DOWNLOAD) {
111
+ fail('the network fallback is disabled (CODEGRAPH_NO_DOWNLOAD is set).');
41
112
  }
42
- } catch (e) {
113
+
114
+ var asset = 'codegraph-' + target + (isWindows ? '.zip' : '.tar.gz');
115
+ var base = process.env.CODEGRAPH_DOWNLOAD_BASE || ('https://github.com/' + REPO + '/releases/download');
116
+ var url = base + '/v' + version + '/' + asset;
117
+
43
118
  process.stderr.write(
44
- 'codegraph: no prebuilt bundle for ' + target + '.\n' +
45
- 'Expected the optional package ' + pkg + ' to be installed.\n' +
46
- 'Try reinstalling: npm i -g @colbymchenry/codegraph\n' +
47
- 'Or use the standalone installer (no Node required):\n' +
48
- ' curl -fsSL https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.sh | sh\n'
119
+ 'codegraph: platform bundle missing (registry did not provide ' + pkg + ').\n' +
120
+ 'codegraph: downloading ' + asset + ' from GitHub Releases (' + version + ')...\n'
49
121
  );
50
- process.exit(1);
122
+
123
+ // Stage inside bundlesDir so the final rename is on the same filesystem (atomic,
124
+ // no EXDEV across tmpfs). Strip the archive's top-level codegraph-<target>/ dir.
125
+ fs.mkdirSync(bundlesDir, { recursive: true });
126
+ var stage = fs.mkdtempSync(path.join(bundlesDir, '.dl-'));
127
+ try {
128
+ var archivePath = path.join(stage, asset);
129
+ await download(url, archivePath, 6);
130
+ await verifyChecksum(archivePath, asset, base, version);
131
+ var extracted = path.join(stage, 'bundle');
132
+ fs.mkdirSync(extracted);
133
+ extract(archivePath, extracted);
134
+
135
+ var raced = launcherIn(dest); // another process may have finished meanwhile
136
+ if (raced) { rmrf(stage); return raced; }
137
+ try {
138
+ fs.renameSync(extracted, dest);
139
+ } catch (e) {
140
+ var other = launcherIn(dest); // lost the race but theirs is valid
141
+ if (other) { rmrf(stage); return other; }
142
+ throw e;
143
+ }
144
+ } catch (e) {
145
+ rmrf(stage);
146
+ fail('download failed (' + e.message + ').\n URL: ' + url);
147
+ }
148
+ rmrf(stage);
149
+
150
+ var ready = launcherIn(dest);
151
+ if (!ready) fail('downloaded bundle is missing its launcher under ' + dest + '.');
152
+ process.stderr.write('codegraph: bundle ready.\n');
153
+ return ready;
154
+ }
155
+
156
+ function readVersion() {
157
+ try {
158
+ return require(path.join(__dirname, 'package.json')).version;
159
+ } catch (e) {
160
+ fail('could not read this package\'s version to locate a matching release.');
161
+ }
51
162
  }
52
163
 
53
- var res = childProcess.spawnSync(command, args, { stdio: 'inherit' });
54
- if (res.error) {
55
- process.stderr.write('codegraph: ' + res.error.message + '\n');
164
+ // GET with manual redirect following (GitHub release URLs redirect to a CDN).
165
+ function download(url, dest, redirectsLeft) {
166
+ return new Promise(function (resolve, reject) {
167
+ var https = require('https');
168
+ // timeout is an idle/inactivity timeout — it won't kill a slow-but-progressing
169
+ // download, only a stalled connection (so a blocked mirror fails fast with
170
+ // guidance instead of hanging the user's command forever).
171
+ var req = https.get(url, { headers: { 'User-Agent': 'codegraph-npm-shim' }, timeout: 30000 }, function (res) {
172
+ var status = res.statusCode;
173
+ if (status >= 300 && status < 400 && res.headers.location) {
174
+ res.resume();
175
+ if (redirectsLeft <= 0) { reject(new Error('too many redirects')); return; }
176
+ download(new URL(res.headers.location, url).toString(), dest, redirectsLeft - 1).then(resolve, reject);
177
+ return;
178
+ }
179
+ if (status !== 200) { res.resume(); reject(new Error('HTTP ' + status)); return; }
180
+ var file = fs.createWriteStream(dest);
181
+ res.on('error', reject);
182
+ res.pipe(file);
183
+ file.on('error', reject);
184
+ file.on('finish', function () { file.close(function () { resolve(); }); });
185
+ });
186
+ req.on('timeout', function () { req.destroy(new Error('connection timed out')); });
187
+ req.on('error', reject);
188
+ });
189
+ }
190
+
191
+ // Best-effort integrity check. When the release publishes a SHA256SUMS file, the
192
+ // downloaded archive MUST match its listed hash or we abort. When that file is
193
+ // absent (older releases) or simply unreachable, we proceed — the archive still
194
+ // arrived from GitHub over TLS. So tampering/corruption is caught, while a
195
+ // missing checksum never breaks an install.
196
+ async function verifyChecksum(archivePath, asset, base, version) {
197
+ var sumsPath = archivePath + '.SHA256SUMS';
198
+ try {
199
+ await download(base + '/v' + version + '/SHA256SUMS', sumsPath, 6);
200
+ } catch (e) {
201
+ return; // not published / unreachable → skip
202
+ }
203
+ var expected = null;
204
+ var lines = fs.readFileSync(sumsPath, 'utf8').split('\n');
205
+ for (var i = 0; i < lines.length; i++) {
206
+ var m = lines[i].trim().match(/^([0-9a-fA-F]{64})\s+\*?(.+)$/);
207
+ if (m && path.basename(m[2].trim()) === asset) { expected = m[1].toLowerCase(); break; }
208
+ }
209
+ if (!expected) return; // asset not listed → nothing to check
210
+ var actual = require('crypto').createHash('sha256').update(fs.readFileSync(archivePath)).digest('hex');
211
+ if (actual !== expected) {
212
+ throw new Error('checksum mismatch for ' + asset +
213
+ ' (expected ' + expected.slice(0, 12) + '…, got ' + actual.slice(0, 12) + '…)');
214
+ }
215
+ process.stderr.write('codegraph: checksum verified.\n');
216
+ }
217
+
218
+ // Extract via the system tar — present on macOS, Linux, and Windows 10+
219
+ // (bsdtar reads .zip too). No third-party dependency in the shim.
220
+ function extract(archive, destDir) {
221
+ var args = isWindows
222
+ ? ['-xf', archive, '-C', destDir, '--strip-components=1']
223
+ : ['-xzf', archive, '-C', destDir, '--strip-components=1'];
224
+ var res = childProcess.spawnSync('tar', args, { stdio: 'ignore' });
225
+ if (res.error) throw new Error('tar unavailable: ' + res.error.message);
226
+ if (res.status !== 0) throw new Error('tar exited ' + res.status);
227
+ }
228
+
229
+ function rmrf(p) {
230
+ try { fs.rmSync(p, { recursive: true, force: true }); } catch (e) { /* best effort */ }
231
+ }
232
+
233
+ function fail(reason) {
234
+ process.stderr.write(
235
+ 'codegraph: no prebuilt bundle for ' + target + '.\n' +
236
+ (reason ? 'codegraph: ' + reason + '\n' : '') +
237
+ 'Expected the optional package ' + pkg + ' to be installed.\n' +
238
+ 'A registry mirror (e.g. npmmirror/cnpm) that did not mirror the per-platform\n' +
239
+ 'package is the usual cause. Fixes:\n' +
240
+ ' - install from the official registry:\n' +
241
+ ' npm i -g @colbymchenry/codegraph --registry=https://registry.npmjs.org\n' +
242
+ ' - or use the standalone installer (no Node required):\n' +
243
+ ' curl -fsSL https://raw.githubusercontent.com/' + REPO + '/main/install.sh | sh\n'
244
+ );
56
245
  process.exit(1);
57
246
  }
58
- process.exit(res.status === null ? 1 : res.status);
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@colbymchenry/codegraph",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "description": "Local-first code intelligence for AI agents (MCP). Self-contained — bundles its own runtime.",
5
5
  "bin": {
6
6
  "codegraph": "npm-shim.js"
7
7
  },
8
8
  "optionalDependencies": {
9
- "@colbymchenry/codegraph-darwin-arm64": "0.9.3",
10
- "@colbymchenry/codegraph-darwin-x64": "0.9.3",
11
- "@colbymchenry/codegraph-linux-arm64": "0.9.3",
12
- "@colbymchenry/codegraph-linux-x64": "0.9.3",
13
- "@colbymchenry/codegraph-win32-arm64": "0.9.3",
14
- "@colbymchenry/codegraph-win32-x64": "0.9.3"
9
+ "@colbymchenry/codegraph-darwin-arm64": "0.9.5",
10
+ "@colbymchenry/codegraph-darwin-x64": "0.9.5",
11
+ "@colbymchenry/codegraph-linux-arm64": "0.9.5",
12
+ "@colbymchenry/codegraph-linux-x64": "0.9.5",
13
+ "@colbymchenry/codegraph-win32-arm64": "0.9.5",
14
+ "@colbymchenry/codegraph-win32-x64": "0.9.5"
15
15
  },
16
16
  "files": [
17
17
  "npm-shim.js",