@stupidloud/codegraph 0.8.1 → 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 (121) hide show
  1. package/README.md +91 -60
  2. package/dist/bin/codegraph.d.ts +4 -0
  3. package/dist/bin/codegraph.d.ts.map +1 -1
  4. package/dist/bin/codegraph.js +302 -8
  5. package/dist/bin/codegraph.js.map +1 -1
  6. package/dist/bin/node-version-check.d.ts +17 -0
  7. package/dist/bin/node-version-check.d.ts.map +1 -1
  8. package/dist/bin/node-version-check.js +37 -0
  9. package/dist/bin/node-version-check.js.map +1 -1
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/config.js +1 -11
  12. package/dist/config.js.map +1 -1
  13. package/dist/db/index.d.ts +30 -1
  14. package/dist/db/index.d.ts.map +1 -1
  15. package/dist/db/index.js +75 -25
  16. package/dist/db/index.js.map +1 -1
  17. package/dist/db/queries.d.ts +16 -0
  18. package/dist/db/queries.d.ts.map +1 -1
  19. package/dist/db/queries.js +80 -27
  20. package/dist/db/queries.js.map +1 -1
  21. package/dist/db/sqlite-adapter.d.ts +17 -23
  22. package/dist/db/sqlite-adapter.d.ts.map +1 -1
  23. package/dist/db/sqlite-adapter.js +51 -174
  24. package/dist/db/sqlite-adapter.js.map +1 -1
  25. package/dist/extraction/grammars.d.ts +7 -1
  26. package/dist/extraction/grammars.d.ts.map +1 -1
  27. package/dist/extraction/grammars.js +42 -2
  28. package/dist/extraction/grammars.js.map +1 -1
  29. package/dist/extraction/index.d.ts +9 -14
  30. package/dist/extraction/index.d.ts.map +1 -1
  31. package/dist/extraction/index.js +75 -94
  32. package/dist/extraction/index.js.map +1 -1
  33. package/dist/extraction/languages/index.d.ts.map +1 -1
  34. package/dist/extraction/languages/index.js +4 -0
  35. package/dist/extraction/languages/index.js.map +1 -1
  36. package/dist/extraction/languages/lua.d.ts +3 -0
  37. package/dist/extraction/languages/lua.d.ts.map +1 -0
  38. package/dist/extraction/languages/lua.js +150 -0
  39. package/dist/extraction/languages/lua.js.map +1 -0
  40. package/dist/extraction/languages/luau.d.ts +3 -0
  41. package/dist/extraction/languages/luau.d.ts.map +1 -0
  42. package/dist/extraction/languages/luau.js +37 -0
  43. package/dist/extraction/languages/luau.js.map +1 -0
  44. package/dist/extraction/tree-sitter.d.ts.map +1 -1
  45. package/dist/extraction/tree-sitter.js +38 -0
  46. package/dist/extraction/tree-sitter.js.map +1 -1
  47. package/dist/extraction/wasm/tree-sitter-lua.wasm +0 -0
  48. package/dist/extraction/wasm/tree-sitter-luau.wasm +0 -0
  49. package/dist/extraction/wasm-runtime-flags.d.ts +38 -0
  50. package/dist/extraction/wasm-runtime-flags.d.ts.map +1 -0
  51. package/dist/extraction/wasm-runtime-flags.js +105 -0
  52. package/dist/extraction/wasm-runtime-flags.js.map +1 -0
  53. package/dist/graph/traversal.d.ts.map +1 -1
  54. package/dist/graph/traversal.js +71 -36
  55. package/dist/graph/traversal.js.map +1 -1
  56. package/dist/index.d.ts +11 -5
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +28 -18
  59. package/dist/index.js.map +1 -1
  60. package/dist/installer/index.d.ts +54 -2
  61. package/dist/installer/index.d.ts.map +1 -1
  62. package/dist/installer/index.js +123 -1
  63. package/dist/installer/index.js.map +1 -1
  64. package/dist/installer/targets/claude.d.ts +16 -0
  65. package/dist/installer/targets/claude.d.ts.map +1 -1
  66. package/dist/installer/targets/claude.js +93 -0
  67. package/dist/installer/targets/claude.js.map +1 -1
  68. package/dist/installer/targets/cursor.d.ts.map +1 -1
  69. package/dist/installer/targets/cursor.js +57 -3
  70. package/dist/installer/targets/cursor.js.map +1 -1
  71. package/dist/installer/targets/hermes.d.ts +18 -0
  72. package/dist/installer/targets/hermes.d.ts.map +1 -0
  73. package/dist/installer/targets/hermes.js +305 -0
  74. package/dist/installer/targets/hermes.js.map +1 -0
  75. package/dist/installer/targets/registry.d.ts.map +1 -1
  76. package/dist/installer/targets/registry.js +2 -0
  77. package/dist/installer/targets/registry.js.map +1 -1
  78. package/dist/installer/targets/types.d.ts +1 -1
  79. package/dist/installer/targets/types.d.ts.map +1 -1
  80. package/dist/mcp/index.d.ts +4 -0
  81. package/dist/mcp/index.d.ts.map +1 -1
  82. package/dist/mcp/index.js +97 -0
  83. package/dist/mcp/index.js.map +1 -1
  84. package/dist/mcp/tools.d.ts +11 -1
  85. package/dist/mcp/tools.d.ts.map +1 -1
  86. package/dist/mcp/tools.js +131 -15
  87. package/dist/mcp/tools.js.map +1 -1
  88. package/dist/resolution/frameworks/drupal.d.ts +51 -0
  89. package/dist/resolution/frameworks/drupal.d.ts.map +1 -0
  90. package/dist/resolution/frameworks/drupal.js +335 -0
  91. package/dist/resolution/frameworks/drupal.js.map +1 -0
  92. package/dist/resolution/frameworks/index.d.ts +1 -0
  93. package/dist/resolution/frameworks/index.d.ts.map +1 -1
  94. package/dist/resolution/frameworks/index.js +5 -1
  95. package/dist/resolution/frameworks/index.js.map +1 -1
  96. package/dist/resolution/index.d.ts.map +1 -1
  97. package/dist/resolution/index.js +40 -7
  98. package/dist/resolution/index.js.map +1 -1
  99. package/dist/resolution/lru-cache.d.ts +24 -0
  100. package/dist/resolution/lru-cache.d.ts.map +1 -0
  101. package/dist/resolution/lru-cache.js +62 -0
  102. package/dist/resolution/lru-cache.js.map +1 -0
  103. package/dist/sync/watcher.d.ts +2 -4
  104. package/dist/sync/watcher.d.ts.map +1 -1
  105. package/dist/sync/watcher.js +4 -6
  106. package/dist/sync/watcher.js.map +1 -1
  107. package/dist/types.d.ts +1 -1
  108. package/dist/types.d.ts.map +1 -1
  109. package/dist/types.js +11 -0
  110. package/dist/types.js.map +1 -1
  111. package/dist/utils.js +1 -1
  112. package/package.json +2 -2
  113. package/scripts/add-lang/bench.sh +60 -0
  114. package/scripts/add-lang/check-grammar.mjs +75 -0
  115. package/scripts/add-lang/dump-ast.mjs +103 -0
  116. package/scripts/add-lang/verify-extraction.mjs +70 -0
  117. package/scripts/build-bundle.sh +118 -0
  118. package/scripts/npm-shim.js +246 -0
  119. package/scripts/pack-npm.sh +95 -0
  120. package/scripts/patch-tree-sitter-dart.js +0 -112
  121. package/scripts/release.sh +0 -68
package/README.md CHANGED
@@ -2,45 +2,68 @@
2
2
 
3
3
  # CodeGraph
4
4
 
5
- ### Supercharge Claude Code, Cursor, Codex, and OpenCode with Semantic Code Intelligence
5
+ ### Supercharge Claude Code, Cursor, Codex, OpenCode, and Hermes Agent with Semantic Code Intelligence
6
6
 
7
7
  **~35% cheaper · ~70% fewer tool calls · 100% local**
8
8
 
9
9
  [![npm version](https://img.shields.io/npm/v/@stupidloud/codegraph.svg)](https://www.npmjs.com/package/@stupidloud/codegraph)
10
10
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
11
- [![Node.js](https://img.shields.io/badge/Node.js-20--24-green.svg)](https://nodejs.org/)
11
+ [![Self-contained](https://img.shields.io/badge/Node.js-bundled%20%C2%B7%20none%20required-brightgreen.svg)](https://nodejs.org/)
12
12
 
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)](#)
16
- [![Claude Code](https://img.shields.io/badge/Claude_Code-supported-blueviolet.svg)](#)
17
- [![Cursor](https://img.shields.io/badge/Cursor-supported-blueviolet.svg)](#)
18
- [![Codex CLI](https://img.shields.io/badge/Codex_CLI-supported-blueviolet.svg)](#)
19
- [![opencode](https://img.shields.io/badge/opencode-supported-blueviolet.svg)](#)
13
+ [![Windows](https://img.shields.io/badge/Windows-supported-blue.svg)](#supported-platforms)
14
+ [![macOS](https://img.shields.io/badge/macOS-supported-blue.svg)](#supported-platforms)
15
+ [![Linux](https://img.shields.io/badge/Linux-supported-blue.svg)](#supported-platforms)
16
+
17
+ [![Claude Code](https://img.shields.io/badge/Claude_Code-supported-blueviolet.svg)](#supported-agents)
18
+ [![Cursor](https://img.shields.io/badge/Cursor-supported-blueviolet.svg)](#supported-agents)
19
+ [![Codex CLI](https://img.shields.io/badge/Codex_CLI-supported-blueviolet.svg)](#supported-agents)
20
+ [![opencode](https://img.shields.io/badge/opencode-supported-blueviolet.svg)](#supported-agents)
21
+ [![Hermes Agent](https://img.shields.io/badge/Hermes_Agent-supported-blueviolet.svg)](#supported-agents)
20
22
 
21
23
  [English](./README.md) · [简体中文](./README.zh-CN.md)
22
24
 
23
- <br />
25
+ </div>
26
+
27
+ ## Get Started
24
28
 
25
- ### Get Started
29
+ **No Node.js required** — one command grabs the right build for your OS:
26
30
 
27
31
  ```bash
28
32
  npx @stupidloud/codegraph
29
33
  ```
30
34
 
31
- <sub>Interactive installer auto-configures your agent(s) Claude Code, Cursor, Codex CLI, opencode</sub>
35
+ Already have Node? Use npm instead (works on any version):
32
36
 
33
- #### Initialize Projects
37
+ ```bash
38
+ npx @stupidloud/codegraph # zero-install, or:
39
+ npm i -g @stupidloud/codegraph
40
+ ```
41
+
42
+ <sub>CodeGraph bundles its own runtime — nothing to compile, no native build, works the same everywhere. The interactive installer auto-configures your agent(s) — Claude Code, Cursor, Codex CLI, opencode, Hermes Agent.</sub>
43
+
44
+ ### Initialize Projects
34
45
 
35
46
  ```bash
36
47
  cd your-project
37
48
  codegraph init -i
38
49
  ```
39
50
 
51
+ <div align="center">
52
+
40
53
  ![1_C_VYnhpys0UHrOuOgpgoyw](https://github.com/user-attachments/assets/f168182f-4d9a-44e0-94d7-08d018cc8a3a)
41
54
 
42
55
  </div>
43
56
 
57
+ ### Uninstall
58
+
59
+ Changed your mind? One command removes CodeGraph from every agent it configured:
60
+
61
+ ```bash
62
+ codegraph uninstall
63
+ ```
64
+
65
+ <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>
66
+
44
67
  ---
45
68
 
46
69
  ## Why CodeGraph?
@@ -108,8 +131,8 @@ The gains scale with codebase size: on large repos the agent answers from the in
108
131
  | **Full-Text Search** | Find code by name instantly across your entire codebase, powered by FTS5 |
109
132
  | **Impact Analysis** | Trace callers, callees, and the full impact radius of any symbol before making changes |
110
133
  | **Always Fresh** | File watcher uses native OS events (FSEvents/inotify/ReadDirectoryChangesW) with debounced auto-sync — the graph stays current as you code, zero config |
111
- | **19+ Languages** | TypeScript, JavaScript, Python, Go, Rust, Java, C#, PHP, Ruby, C, C++, Swift, Kotlin, Dart, Svelte, Liquid, Pascal/Delphi |
112
- | **Framework-aware Routes** | Recognizes web-framework routing files and links URL patterns to their handlers across 13 frameworks |
134
+ | **19+ Languages** | TypeScript, JavaScript, Python, Go, Rust, Java, C#, PHP, Ruby, C, C++, Swift, Kotlin, Dart, Lua, Luau, Svelte, Liquid, Pascal/Delphi |
135
+ | **Framework-aware Routes** | Recognizes web-framework routing files and links URL patterns to their handlers across 14 frameworks |
113
136
  | **100% Local** | No data leaves your machine. No API keys. No external services. SQLite database only |
114
137
 
115
138
  ---
@@ -126,6 +149,7 @@ CodeGraph detects web-framework routing files and emits `route` nodes linked by
126
149
  | **Express** | `app.get(...)`, `router.post(...)` with middleware chains |
127
150
  | **NestJS** | `@Controller` + `@Get/@Post/...`, GraphQL `@Resolver` + `@Query/@Mutation`, `@MessagePattern`/`@EventPattern`, `@SubscribeMessage` |
128
151
  | **Laravel** | `Route::get()`, `Route::resource()`, `Controller@action`, tuple syntax |
152
+ | **Drupal** | `*.routing.yml` routes (`_controller`, `_form`, entity handlers); `hook_*` implementations in `.module`/`.theme`/`.install`/`.inc` |
129
153
  | **Rails** | `get '/x', to: 'users#index'`, hash-rocket `=>` syntax |
130
154
  | **Spring** | `@GetMapping`, `@PostMapping`, `@RequestMapping` on methods |
131
155
  | **Gin / chi / gorilla / mux** | `r.GET(...)`, `router.HandleFunc(...)` |
@@ -145,7 +169,7 @@ npx @stupidloud/codegraph
145
169
  ```
146
170
 
147
171
  The installer will:
148
- - Ask which agent(s) to configure — auto-detects installed ones from: **Claude Code**, **Cursor**, **Codex CLI**, **opencode**
172
+ - Ask which agent(s) to configure — auto-detects installed ones from: **Claude Code**, **Cursor**, **Codex CLI**, **opencode**, **Hermes Agent**
149
173
  - Prompt to install `codegraph` on your PATH (so agents can launch the MCP server)
150
174
  - Ask whether configs apply to all your projects or just this one
151
175
  - Write each chosen agent's MCP server config + an instructions file (e.g. `CLAUDE.md`, `.cursor/rules/codegraph.mdc`, `~/.codex/AGENTS.md`)
@@ -171,7 +195,7 @@ codegraph install --print-config codex # print snippet, no file wr
171
195
 
172
196
  ### 2. Restart Your Agent
173
197
 
174
- Restart your agent (Claude Code / Cursor / Codex CLI / opencode) for the MCP server to load.
198
+ Restart your agent (Claude Code / Cursor / Codex CLI / opencode / Hermes Agent) for the MCP server to load.
175
199
 
176
200
  ### 3. Initialize Projects
177
201
 
@@ -317,6 +341,7 @@ At the start of a session, ask the user if they'd like to initialize CodeGraph:
317
341
  ```bash
318
342
  codegraph # Run interactive installer
319
343
  codegraph install # Run installer (explicit)
344
+ codegraph uninstall # Remove CodeGraph from your agents (inverse of install)
320
345
  codegraph init [path] # Initialize in a project (--index to also index)
321
346
  codegraph uninit [path] # Remove CodeGraph from a project (--force to skip prompt)
322
347
  codegraph index [path] # Full index (--force to re-index, --quiet for less output)
@@ -325,6 +350,9 @@ codegraph status [path] # Show statistics
325
350
  codegraph query <search> # Search symbols (--kind, --limit, --json)
326
351
  codegraph files [path] # Show file structure (--format, --filter, --max-depth, --json)
327
352
  codegraph context <task> # Build context for AI (--format, --max-nodes)
353
+ codegraph callers <symbol> # Find what calls a function/method (--limit, --json)
354
+ codegraph callees <symbol> # Find what a function/method calls (--limit, --json)
355
+ codegraph impact <symbol> # Analyze what code is affected by changing a symbol (--depth, --json)
328
356
  codegraph affected [files...] # Find test files affected by changes (see below)
329
357
  codegraph serve --mcp # Start MCP server
330
358
  ```
@@ -371,6 +399,7 @@ When running as an MCP server, CodeGraph exposes these tools to Claude Code:
371
399
  | `codegraph_callees` | Find what a function calls |
372
400
  | `codegraph_impact` | Analyze what code is affected by changing a symbol |
373
401
  | `codegraph_node` | Get details about a specific symbol (optionally with source code) |
402
+ | `codegraph_explore` | Return source for several related symbols grouped by file, plus a relationship map, in one call |
374
403
  | `codegraph_files` | Get indexed file structure (faster than filesystem scanning) |
375
404
  | `codegraph_status` | Check index health and statistics |
376
405
 
@@ -402,28 +431,47 @@ cg.close();
402
431
 
403
432
  ## Configuration
404
433
 
405
- The `.codegraph/config.json` file controls indexing:
434
+ There isn't any — CodeGraph is zero-config. It indexes every file whose
435
+ extension maps to a [supported language](#supported-languages) and **respects
436
+ your `.gitignore`**: in git repos via git itself, and in non-git projects by
437
+ reading `.gitignore` files directly (root and nested, the same way git would).
406
438
 
407
- ```json
408
- {
409
- "version": 1,
410
- "languages": ["typescript", "javascript"],
411
- "exclude": ["node_modules/**", "dist/**", "build/**", "*.min.js"],
412
- "frameworks": [],
413
- "maxFileSize": 1048576,
414
- "extractDocstrings": true,
415
- "trackCallSites": true
416
- }
417
- ```
439
+ What that means in practice:
418
440
 
419
- | Option | Description | Default |
420
- |--------|-------------|---------|
421
- | `languages` | Languages to index (auto-detected if empty) | `[]` |
422
- | `exclude` | Glob patterns to ignore | `["node_modules/**", ...]` |
423
- | `frameworks` | Framework hints for better resolution | `[]` |
424
- | `maxFileSize` | Skip files larger than this (bytes) | `1048576` (1MB) |
425
- | `extractDocstrings` | Extract docstrings from code | `true` |
426
- | `trackCallSites` | Track call site locations | `true` |
441
+ - Anything git ignores `node_modules`, build output, secrets in `.env` — is
442
+ never indexed. **To keep something out of the graph, add it to `.gitignore`.**
443
+ - There's no config file to write or keep in sync, and nothing to wire up per
444
+ language: support is automatic from the file extension.
445
+ - Files larger than 1 MB are skipped (generated bundles, minified JS, vendored
446
+ blobs) they cost parse budget for no useful symbols.
447
+
448
+ > Committed files that aren't gitignored *are* indexed, even under `vendor/` or a
449
+ > committed `dist/`. If you commit a dependency or build directory you don't want
450
+ > in the graph, add it to `.gitignore`.
451
+
452
+ ## Supported Platforms
453
+
454
+ Every release ships a self-contained build (bundled Node runtime — nothing to
455
+ compile) for all three desktop OSes, on both Intel/AMD (x64) and ARM (arm64):
456
+
457
+ | Platform | Architectures | Install |
458
+ |----------|---------------|---------|
459
+ | Windows | x64, arm64 | PowerShell installer or npm |
460
+ | macOS | x64, arm64 | shell installer or npm |
461
+ | Linux | x64, arm64 | shell installer or npm |
462
+
463
+ See [Get Started](#get-started) for the one-line install commands.
464
+
465
+ ## Supported Agents
466
+
467
+ The interactive installer auto-detects and configures each of these — wiring up
468
+ the MCP server and writing its instructions file:
469
+
470
+ - **Claude Code**
471
+ - **Cursor**
472
+ - **Codex CLI**
473
+ - **opencode**
474
+ - **Hermes Agent**
427
475
 
428
476
  ## Supported Languages
429
477
 
@@ -448,6 +496,8 @@ The `.codegraph/config.json` file controls indexing:
448
496
  | Vue | `.vue` | Full support (script + script-setup extraction, Nuxt page/API/middleware routes) |
449
497
  | Liquid | `.liquid` | Full support |
450
498
  | Pascal / Delphi | `.pas`, `.dpr`, `.dpk`, `.lpr` | Full support (classes, records, interfaces, enums, DFM/FMX form files) |
499
+ | Lua | `.lua` | Full support (functions, methods with receivers, local variables, `require` imports, call edges) |
500
+ | Luau | `.luau` | Full support (everything in Lua, plus `type`/`export type` aliases, typed signatures, and Roblox instance-path `require`) |
451
501
 
452
502
  ## Troubleshooting
453
503
 
@@ -455,29 +505,10 @@ The `.codegraph/config.json` file controls indexing:
455
505
 
456
506
  **Indexing is slow** — Check that `node_modules` and other large directories are excluded. Use `--quiet` to reduce output overhead.
457
507
 
458
- **Indexing is slow / MCP `database is locked` / WASM fallback active** `codegraph` ships with a WASM SQLite fallback for environments where `better-sqlite3` (a native module, declared as `optionalDependencies`) can't install. The fallback is 5-10x slower than the native backend and uses a journal mode that lets writers block readers, so MCP queries can also hit `database is locked` while indexing runs. Run `codegraph status` and look at the `Backend:` line:
459
-
460
- - `Backend: native` — you're on the fast path, nothing to do.
461
- - `Backend: wasm` — you're on the slow fallback. Common causes: missing C build tools, prebuilt binary unavailable for your Node version, or your Node version changed after install. Fix:
462
-
463
- ```bash
464
- # macOS
465
- xcode-select --install # installs the C compiler
466
-
467
- # Linux (Debian / Ubuntu)
468
- sudo apt install build-essential python3 make
469
-
470
- # Linux (RHEL / Fedora)
471
- sudo yum groupinstall "Development Tools"
472
-
473
- # Then rebuild on any platform:
474
- npm rebuild better-sqlite3
475
-
476
- # Or force-include as a hard dep:
477
- npm install better-sqlite3 --save
478
- ```
508
+ **MCP hits `database is locked`**current builds shouldn't: CodeGraph bundles its own Node runtime and uses Node's built-in `node:sqlite` in WAL mode, where concurrent reads never block on a writer. If you still see it:
479
509
 
480
- After the fix, `codegraph status` should show `Backend: native`.
510
+ - **You're on an old (pre-0.9) install.** Reinstall to get the bundled runtime — `curl -fsSL https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.sh | sh` (macOS/Linux), `irm https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.ps1 | iex` (Windows), or `npm i -g @colbymchenry/codegraph@latest`.
511
+ - **`codegraph status` shows `Journal:` other than `wal`** — WAL couldn't be enabled on this filesystem (common on network shares and WSL2 `/mnt`), so reads can block on writes. Move the project (with its `.codegraph/` folder) onto a local disk.
481
512
 
482
513
  **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.
483
514
 
@@ -501,7 +532,7 @@ MIT
501
532
 
502
533
  <div align="center">
503
534
 
504
- **Made for AI coding agents — Claude Code, Cursor, Codex CLI, and opencode**
535
+ **Made for AI coding agents — Claude Code, Cursor, Codex CLI, opencode, and Hermes Agent**
505
536
 
506
537
  [Report Bug](https://github.com/colbymchenry/codegraph/issues) · [Request Feature](https://github.com/colbymchenry/codegraph/issues)
507
538
 
@@ -7,6 +7,7 @@
7
7
  * Usage:
8
8
  * codegraph Run interactive installer (when no args)
9
9
  * codegraph install Run interactive installer
10
+ * codegraph uninstall Remove CodeGraph from your agents
10
11
  * codegraph init [path] Initialize CodeGraph in a project
11
12
  * codegraph uninit [path] Remove CodeGraph from a project
12
13
  * codegraph index [path] Index all files in the project
@@ -15,6 +16,9 @@
15
16
  * codegraph query <search> Search for symbols
16
17
  * codegraph files [options] Show project file structure
17
18
  * codegraph context <task> Build context for a task
19
+ * codegraph callers <symbol> Find what calls a function/method
20
+ * codegraph callees <symbol> Find what a function/method calls
21
+ * codegraph impact <symbol> Analyze what code is affected by changing a symbol
18
22
  * codegraph affected [files] Find test files affected by changes
19
23
  */
20
24
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"codegraph.d.ts","sourceRoot":"","sources":["../../src/bin/codegraph.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;GAiBG"}
1
+ {"version":3,"file":"codegraph.d.ts","sourceRoot":"","sources":["../../src/bin/codegraph.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;GAqBG"}
@@ -8,6 +8,7 @@
8
8
  * Usage:
9
9
  * codegraph Run interactive installer (when no args)
10
10
  * codegraph install Run interactive installer
11
+ * codegraph uninstall Remove CodeGraph from your agents
11
12
  * codegraph init [path] Initialize CodeGraph in a project
12
13
  * codegraph uninit [path] Remove CodeGraph from a project
13
14
  * codegraph index [path] Index all files in the project
@@ -16,6 +17,9 @@
16
17
  * codegraph query <search> Search for symbols
17
18
  * codegraph files [options] Show project file structure
18
19
  * codegraph context <task> Build context for a task
20
+ * codegraph callers <symbol> Find what calls a function/method
21
+ * codegraph callees <symbol> Find what a function/method calls
22
+ * codegraph impact <symbol> Analyze what code is affected by changing a symbol
19
23
  * codegraph affected [files] Find test files affected by changes
20
24
  */
21
25
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
@@ -60,6 +64,7 @@ const directory_1 = require("../directory");
60
64
  const shimmer_progress_1 = require("../ui/shimmer-progress");
61
65
  const glyphs_1 = require("../ui/glyphs");
62
66
  const node_version_check_1 = require("./node-version-check");
67
+ const wasm_runtime_flags_1 = require("../extraction/wasm-runtime-flags");
63
68
  // Lazy-load heavy modules (CodeGraph, runInstaller) to keep CLI startup fast.
64
69
  async function loadCodeGraph() {
65
70
  try {
@@ -94,6 +99,22 @@ if (nodeMajor >= 25) {
94
99
  }
95
100
  // Override active — banner shown for visibility, continuing.
96
101
  }
102
+ // Enforce the supported Node floor. `engines` in package.json only *warns* on
103
+ // install (unless engine-strict), so hard-block here to actually keep users off
104
+ // unsupported versions. Mirrors the 25+ block above. See package.json `engines`.
105
+ if (nodeMajor < node_version_check_1.MIN_NODE_MAJOR) {
106
+ process.stderr.write((0, node_version_check_1.buildNodeTooOldBanner)(nodeVersion) + '\n');
107
+ if (!process.env.CODEGRAPH_ALLOW_UNSAFE_NODE) {
108
+ process.exit(1);
109
+ }
110
+ // Override active — banner shown for visibility, continuing.
111
+ }
112
+ // Re-exec with V8's `--liftoff-only` if it isn't already set, so tree-sitter's
113
+ // large WASM grammars never hit the turboshaft Zone OOM (`Fatal process out of
114
+ // memory: Zone`) on Node >= 22. No-op under the bundled launcher, which already
115
+ // passes the flag. Must run before any grammar (in the parse worker, which
116
+ // inherits this process's flags) is compiled. See ../extraction/wasm-runtime-flags.
117
+ (0, wasm_runtime_flags_1.relaunchWithWasmRuntimeFlagsIfNeeded)(__filename);
97
118
  // Check if running with no arguments - run installer
98
119
  if (process.argv.length === 2) {
99
120
  Promise.resolve().then(() => __importStar(require('../installer'))).then(({ runInstaller }) => runInstaller()).catch((err) => {
@@ -656,6 +677,7 @@ function main() {
656
677
  const stats = cg.getStats();
657
678
  const changes = cg.getChangedFiles();
658
679
  const backend = cg.getBackend();
680
+ const journalMode = cg.getJournalMode();
659
681
  // JSON output mode
660
682
  if (options.json) {
661
683
  console.log(JSON.stringify({
@@ -666,6 +688,7 @@ function main() {
666
688
  edgeCount: stats.edgeCount,
667
689
  dbSizeBytes: stats.dbSizeBytes,
668
690
  backend,
691
+ journalMode,
669
692
  nodesByKind: stats.nodesByKind,
670
693
  languages: Object.entries(stats.filesByLanguage).filter(([, count]) => count > 0).map(([lang]) => lang),
671
694
  pendingChanges: {
@@ -687,14 +710,18 @@ function main() {
687
710
  console.log(` Nodes: ${formatNumber(stats.nodeCount)}`);
688
711
  console.log(` Edges: ${formatNumber(stats.edgeCount)}`);
689
712
  console.log(` DB Size: ${(stats.dbSizeBytes / 1024 / 1024).toFixed(2)} MB`);
690
- // Surface the active SQLite backend so users can spot the silent
691
- // WASM fallback (5-10x slower). better-sqlite3 is in
692
- // `optionalDependencies`, so `npm install` succeeds without it
693
- // when the native build fails.
694
- const backendLabel = backend === 'native'
695
- ? chalk.green('native')
696
- : chalk.yellow(`wasm ${(0, glyphs_1.getGlyphs)().dash} slower fallback; run \`npm rebuild better-sqlite3\``);
713
+ // Surface the active SQLite backend (node:sqlite Node's built-in real
714
+ // SQLite, full WAL + FTS5, no native build).
715
+ const backendLabel = chalk.green(`node:sqlite ${(0, glyphs_1.getGlyphs)().dash} built-in (full WAL)`);
697
716
  console.log(` Backend: ${backendLabel}`);
717
+ // Effective journal mode: 'wal' means concurrent reads never block on a
718
+ // writer; anything else means they can ("database is locked"). node:sqlite
719
+ // supports WAL everywhere, so a non-wal mode means the filesystem can't
720
+ // (network mounts, WSL2 /mnt). See issue #238.
721
+ const journalLabel = journalMode === 'wal'
722
+ ? chalk.green('wal')
723
+ : chalk.yellow(`${journalMode || 'unknown'} ${(0, glyphs_1.getGlyphs)().dash} WAL inactive; reads can block on writes`);
724
+ console.log(` Journal: ${journalLabel}`);
698
725
  console.log();
699
726
  // Node breakdown
700
727
  console.log(chalk.bold('Nodes by Kind:'));
@@ -1214,6 +1241,241 @@ function main() {
1214
1241
  process.exit(1);
1215
1242
  }
1216
1243
  });
1244
+ /**
1245
+ * codegraph callers <symbol>
1246
+ *
1247
+ * CLI parity with the MCP graph tools (codegraph_callers/callees/impact) so the
1248
+ * traversal queries work in scripts, CI, and git hooks without a running MCP
1249
+ * server.
1250
+ */
1251
+ program
1252
+ .command('callers <symbol>')
1253
+ .description('Find all functions/methods that call a specific symbol')
1254
+ .option('-p, --path <path>', 'Project path')
1255
+ .option('-l, --limit <number>', 'Maximum results', '20')
1256
+ .option('-j, --json', 'Output as JSON')
1257
+ .action(async (symbol, options) => {
1258
+ const projectPath = resolveProjectPath(options.path);
1259
+ try {
1260
+ if (!(0, directory_1.isInitialized)(projectPath)) {
1261
+ error(`CodeGraph not initialized in ${projectPath}`);
1262
+ process.exit(1);
1263
+ }
1264
+ const { default: CodeGraph } = await loadCodeGraph();
1265
+ const cg = await CodeGraph.open(projectPath);
1266
+ const limit = parseInt(options.limit || '20', 10);
1267
+ const matches = cg.searchNodes(symbol, { limit: 50 });
1268
+ if (matches.length === 0) {
1269
+ info(`Symbol "${symbol}" not found`);
1270
+ cg.destroy();
1271
+ return;
1272
+ }
1273
+ const seen = new Set();
1274
+ const allCallers = [];
1275
+ for (const match of matches) {
1276
+ const exactMatch = match.node.name === symbol || match.node.name.endsWith(`.${symbol}`) || match.node.name.endsWith(`::${symbol}`);
1277
+ if (!exactMatch && matches.length > 1)
1278
+ continue;
1279
+ for (const c of cg.getCallers(match.node.id)) {
1280
+ if (!seen.has(c.node.id)) {
1281
+ seen.add(c.node.id);
1282
+ allCallers.push({ name: c.node.name, kind: c.node.kind, filePath: c.node.filePath, startLine: c.node.startLine });
1283
+ }
1284
+ }
1285
+ }
1286
+ // Fallback: if exact filter removed everything, use the top match
1287
+ if (allCallers.length === 0 && matches[0]) {
1288
+ for (const c of cg.getCallers(matches[0].node.id)) {
1289
+ if (!seen.has(c.node.id)) {
1290
+ seen.add(c.node.id);
1291
+ allCallers.push({ name: c.node.name, kind: c.node.kind, filePath: c.node.filePath, startLine: c.node.startLine });
1292
+ }
1293
+ }
1294
+ }
1295
+ const limited = allCallers.slice(0, limit);
1296
+ if (options.json) {
1297
+ console.log(JSON.stringify({ symbol, callers: limited }, null, 2));
1298
+ }
1299
+ else if (limited.length === 0) {
1300
+ info(`No callers found for "${symbol}"`);
1301
+ }
1302
+ else {
1303
+ console.log(chalk.bold(`\nCallers of "${symbol}" (${limited.length}):\n`));
1304
+ for (const node of limited) {
1305
+ const loc = node.startLine ? `:${node.startLine}` : '';
1306
+ console.log(chalk.cyan(node.kind.padEnd(12)) +
1307
+ chalk.white(node.name));
1308
+ console.log(chalk.dim(` ${node.filePath}${loc}`));
1309
+ console.log();
1310
+ }
1311
+ }
1312
+ cg.destroy();
1313
+ }
1314
+ catch (err) {
1315
+ error(`callers failed: ${err instanceof Error ? err.message : String(err)}`);
1316
+ process.exit(1);
1317
+ }
1318
+ });
1319
+ /**
1320
+ * codegraph callees <symbol>
1321
+ */
1322
+ program
1323
+ .command('callees <symbol>')
1324
+ .description('Find all functions/methods that a specific symbol calls')
1325
+ .option('-p, --path <path>', 'Project path')
1326
+ .option('-l, --limit <number>', 'Maximum results', '20')
1327
+ .option('-j, --json', 'Output as JSON')
1328
+ .action(async (symbol, options) => {
1329
+ const projectPath = resolveProjectPath(options.path);
1330
+ try {
1331
+ if (!(0, directory_1.isInitialized)(projectPath)) {
1332
+ error(`CodeGraph not initialized in ${projectPath}`);
1333
+ process.exit(1);
1334
+ }
1335
+ const { default: CodeGraph } = await loadCodeGraph();
1336
+ const cg = await CodeGraph.open(projectPath);
1337
+ const limit = parseInt(options.limit || '20', 10);
1338
+ const matches = cg.searchNodes(symbol, { limit: 50 });
1339
+ if (matches.length === 0) {
1340
+ info(`Symbol "${symbol}" not found`);
1341
+ cg.destroy();
1342
+ return;
1343
+ }
1344
+ const seen = new Set();
1345
+ const allCallees = [];
1346
+ for (const match of matches) {
1347
+ const exactMatch = match.node.name === symbol || match.node.name.endsWith(`.${symbol}`) || match.node.name.endsWith(`::${symbol}`);
1348
+ if (!exactMatch && matches.length > 1)
1349
+ continue;
1350
+ for (const c of cg.getCallees(match.node.id)) {
1351
+ if (!seen.has(c.node.id)) {
1352
+ seen.add(c.node.id);
1353
+ allCallees.push({ name: c.node.name, kind: c.node.kind, filePath: c.node.filePath, startLine: c.node.startLine });
1354
+ }
1355
+ }
1356
+ }
1357
+ if (allCallees.length === 0 && matches[0]) {
1358
+ for (const c of cg.getCallees(matches[0].node.id)) {
1359
+ if (!seen.has(c.node.id)) {
1360
+ seen.add(c.node.id);
1361
+ allCallees.push({ name: c.node.name, kind: c.node.kind, filePath: c.node.filePath, startLine: c.node.startLine });
1362
+ }
1363
+ }
1364
+ }
1365
+ const limited = allCallees.slice(0, limit);
1366
+ if (options.json) {
1367
+ console.log(JSON.stringify({ symbol, callees: limited }, null, 2));
1368
+ }
1369
+ else if (limited.length === 0) {
1370
+ info(`No callees found for "${symbol}"`);
1371
+ }
1372
+ else {
1373
+ console.log(chalk.bold(`\nCallees of "${symbol}" (${limited.length}):\n`));
1374
+ for (const node of limited) {
1375
+ const loc = node.startLine ? `:${node.startLine}` : '';
1376
+ console.log(chalk.cyan(node.kind.padEnd(12)) +
1377
+ chalk.white(node.name));
1378
+ console.log(chalk.dim(` ${node.filePath}${loc}`));
1379
+ console.log();
1380
+ }
1381
+ }
1382
+ cg.destroy();
1383
+ }
1384
+ catch (err) {
1385
+ error(`callees failed: ${err instanceof Error ? err.message : String(err)}`);
1386
+ process.exit(1);
1387
+ }
1388
+ });
1389
+ /**
1390
+ * codegraph impact <symbol>
1391
+ */
1392
+ program
1393
+ .command('impact <symbol>')
1394
+ .description('Analyze what code is affected by changing a symbol')
1395
+ .option('-p, --path <path>', 'Project path')
1396
+ .option('-d, --depth <number>', 'Traversal depth', '2')
1397
+ .option('-j, --json', 'Output as JSON')
1398
+ .action(async (symbol, options) => {
1399
+ const projectPath = resolveProjectPath(options.path);
1400
+ try {
1401
+ if (!(0, directory_1.isInitialized)(projectPath)) {
1402
+ error(`CodeGraph not initialized in ${projectPath}`);
1403
+ process.exit(1);
1404
+ }
1405
+ const { default: CodeGraph } = await loadCodeGraph();
1406
+ const cg = await CodeGraph.open(projectPath);
1407
+ const depth = Math.min(Math.max(parseInt(options.depth || '2', 10), 1), 10);
1408
+ const matches = cg.searchNodes(symbol, { limit: 50 });
1409
+ if (matches.length === 0) {
1410
+ info(`Symbol "${symbol}" not found`);
1411
+ cg.destroy();
1412
+ return;
1413
+ }
1414
+ // Merge impact subgraphs across all exact-matching symbols
1415
+ const mergedNodes = new Map();
1416
+ const seenEdges = new Set();
1417
+ let edgeCount = 0;
1418
+ for (const match of matches) {
1419
+ const exactMatch = match.node.name === symbol || match.node.name.endsWith(`.${symbol}`) || match.node.name.endsWith(`::${symbol}`);
1420
+ if (!exactMatch && matches.length > 1)
1421
+ continue;
1422
+ const impact = cg.getImpactRadius(match.node.id, depth);
1423
+ for (const [id, n] of impact.nodes) {
1424
+ mergedNodes.set(id, { name: n.name, kind: n.kind, filePath: n.filePath, startLine: n.startLine });
1425
+ }
1426
+ for (const e of impact.edges) {
1427
+ const key = `${e.source}->${e.target}:${e.kind}`;
1428
+ if (!seenEdges.has(key)) {
1429
+ seenEdges.add(key);
1430
+ edgeCount++;
1431
+ }
1432
+ }
1433
+ }
1434
+ // Fallback to top match if exact filter removed everything
1435
+ if (mergedNodes.size === 0 && matches[0]) {
1436
+ const impact = cg.getImpactRadius(matches[0].node.id, depth);
1437
+ for (const [id, n] of impact.nodes) {
1438
+ mergedNodes.set(id, { name: n.name, kind: n.kind, filePath: n.filePath, startLine: n.startLine });
1439
+ }
1440
+ edgeCount = impact.edges.length;
1441
+ }
1442
+ if (options.json) {
1443
+ console.log(JSON.stringify({
1444
+ symbol,
1445
+ depth,
1446
+ nodeCount: mergedNodes.size,
1447
+ edgeCount,
1448
+ affected: Array.from(mergedNodes.values()),
1449
+ }, null, 2));
1450
+ }
1451
+ else if (mergedNodes.size === 0) {
1452
+ info(`No affected symbols found for "${symbol}"`);
1453
+ }
1454
+ else {
1455
+ console.log(chalk.bold(`\nImpact of changing "${symbol}" — ${mergedNodes.size} affected symbols:\n`));
1456
+ // Group by file
1457
+ const byFile = new Map();
1458
+ for (const node of mergedNodes.values()) {
1459
+ const list = byFile.get(node.filePath) || [];
1460
+ list.push({ name: node.name, kind: node.kind, startLine: node.startLine });
1461
+ byFile.set(node.filePath, list);
1462
+ }
1463
+ for (const [file, nodes] of byFile) {
1464
+ console.log(chalk.cyan(file));
1465
+ for (const node of nodes) {
1466
+ const loc = node.startLine ? `:${node.startLine}` : '';
1467
+ console.log(` ${chalk.dim(node.kind.padEnd(12))}${node.name}${chalk.dim(loc)}`);
1468
+ }
1469
+ console.log();
1470
+ }
1471
+ }
1472
+ cg.destroy();
1473
+ }
1474
+ catch (err) {
1475
+ error(`impact failed: ${err instanceof Error ? err.message : String(err)}`);
1476
+ process.exit(1);
1477
+ }
1478
+ });
1217
1479
  /**
1218
1480
  * codegraph affected [files...]
1219
1481
  *
@@ -1349,7 +1611,7 @@ function main() {
1349
1611
  */
1350
1612
  program
1351
1613
  .command('install')
1352
- .description('Install codegraph MCP server into one or more agents (Claude Code, Cursor, Codex CLI, opencode)')
1614
+ .description('Install codegraph MCP server into one or more agents (Claude Code, Cursor, Codex CLI, opencode, Hermes Agent)')
1353
1615
  .option('-t, --target <ids>', 'Target agent(s): comma-separated ids, or "auto"|"all"|"none". Default: prompt')
1354
1616
  .option('-l, --location <where>', 'Install location: "global" or "local". Default: prompt')
1355
1617
  .option('-y, --yes', 'Non-interactive: defaults to --location=global --target=auto, auto-allow on')
@@ -1398,6 +1660,38 @@ function main() {
1398
1660
  process.exit(1);
1399
1661
  }
1400
1662
  });
1663
+ /**
1664
+ * codegraph uninstall
1665
+ *
1666
+ * Inverse of `install`. Removes the codegraph MCP server entry,
1667
+ * instructions block, and permissions from every agent (or a
1668
+ * `--target` subset). Prompts global-vs-local when not given. Does NOT
1669
+ * delete the `.codegraph/` index — that's `codegraph uninit`.
1670
+ */
1671
+ program
1672
+ .command('uninstall')
1673
+ .description('Remove codegraph from your agents (Claude Code, Cursor, Codex CLI, opencode, Hermes Agent)')
1674
+ .option('-t, --target <ids>', 'Target agent(s): comma-separated ids, or "all". Default: all')
1675
+ .option('-l, --location <where>', 'Uninstall location: "global" or "local". Default: prompt')
1676
+ .option('-y, --yes', 'Non-interactive: defaults to --location=global --target=all')
1677
+ .action(async (opts) => {
1678
+ const { runUninstaller } = await Promise.resolve().then(() => __importStar(require('../installer')));
1679
+ if (opts.location && opts.location !== 'global' && opts.location !== 'local') {
1680
+ error(`--location must be "global" or "local" (got "${opts.location}").`);
1681
+ process.exit(1);
1682
+ }
1683
+ try {
1684
+ await runUninstaller({
1685
+ target: opts.target,
1686
+ location: opts.location,
1687
+ yes: opts.yes,
1688
+ });
1689
+ }
1690
+ catch (err) {
1691
+ error(err instanceof Error ? err.message : String(err));
1692
+ process.exit(1);
1693
+ }
1694
+ });
1401
1695
  // Parse and run
1402
1696
  program.parse();
1403
1697
  } // end main()