@stupidloud/codegraph 0.7.15 → 0.8.1

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 (147) hide show
  1. package/README.md +82 -58
  2. package/dist/bin/codegraph.js +127 -24
  3. package/dist/bin/codegraph.js.map +1 -1
  4. package/dist/bin/node-version-check.d.ts +3 -0
  5. package/dist/bin/node-version-check.d.ts.map +1 -1
  6. package/dist/bin/node-version-check.js +5 -2
  7. package/dist/bin/node-version-check.js.map +1 -1
  8. package/dist/bin/uninstall.d.ts +7 -7
  9. package/dist/bin/uninstall.d.ts.map +1 -1
  10. package/dist/bin/uninstall.js +23 -135
  11. package/dist/bin/uninstall.js.map +1 -1
  12. package/dist/context/index.d.ts.map +1 -1
  13. package/dist/context/index.js +4 -2
  14. package/dist/context/index.js.map +1 -1
  15. package/dist/db/queries.d.ts.map +1 -1
  16. package/dist/db/queries.js +7 -1
  17. package/dist/db/queries.js.map +1 -1
  18. package/dist/extraction/index.d.ts +1 -1
  19. package/dist/extraction/index.d.ts.map +1 -1
  20. package/dist/extraction/index.js +63 -37
  21. package/dist/extraction/index.js.map +1 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +3 -2
  24. package/dist/index.js.map +1 -1
  25. package/dist/installer/claude-md-template.d.ts +10 -6
  26. package/dist/installer/claude-md-template.d.ts.map +1 -1
  27. package/dist/installer/claude-md-template.js +15 -40
  28. package/dist/installer/claude-md-template.js.map +1 -1
  29. package/dist/installer/config-writer.d.ts +17 -24
  30. package/dist/installer/config-writer.d.ts.map +1 -1
  31. package/dist/installer/config-writer.js +46 -239
  32. package/dist/installer/config-writer.js.map +1 -1
  33. package/dist/installer/index.d.ts +57 -4
  34. package/dist/installer/index.d.ts.map +1 -1
  35. package/dist/installer/index.js +286 -81
  36. package/dist/installer/index.js.map +1 -1
  37. package/dist/installer/instructions-template.d.ts +28 -0
  38. package/dist/installer/instructions-template.d.ts.map +1 -0
  39. package/dist/installer/instructions-template.js +65 -0
  40. package/dist/installer/instructions-template.js.map +1 -0
  41. package/dist/installer/targets/claude.d.ts +31 -0
  42. package/dist/installer/targets/claude.d.ts.map +1 -0
  43. package/dist/installer/targets/claude.js +308 -0
  44. package/dist/installer/targets/claude.js.map +1 -0
  45. package/dist/installer/targets/codex.d.ts +18 -0
  46. package/dist/installer/targets/codex.d.ts.map +1 -0
  47. package/dist/installer/targets/codex.js +185 -0
  48. package/dist/installer/targets/codex.js.map +1 -0
  49. package/dist/installer/targets/cursor.d.ts +35 -0
  50. package/dist/installer/targets/cursor.d.ts.map +1 -0
  51. package/dist/installer/targets/cursor.js +229 -0
  52. package/dist/installer/targets/cursor.js.map +1 -0
  53. package/dist/installer/targets/opencode.d.ts +30 -0
  54. package/dist/installer/targets/opencode.d.ts.map +1 -0
  55. package/dist/installer/targets/opencode.js +235 -0
  56. package/dist/installer/targets/opencode.js.map +1 -0
  57. package/dist/installer/targets/registry.d.ts +35 -0
  58. package/dist/installer/targets/registry.d.ts.map +1 -0
  59. package/dist/installer/targets/registry.js +83 -0
  60. package/dist/installer/targets/registry.js.map +1 -0
  61. package/dist/installer/targets/shared.d.ts +77 -0
  62. package/dist/installer/targets/shared.d.ts.map +1 -0
  63. package/dist/installer/targets/shared.js +246 -0
  64. package/dist/installer/targets/shared.js.map +1 -0
  65. package/dist/installer/targets/toml.d.ts +52 -0
  66. package/dist/installer/targets/toml.d.ts.map +1 -0
  67. package/dist/installer/targets/toml.js +147 -0
  68. package/dist/installer/targets/toml.js.map +1 -0
  69. package/dist/installer/targets/types.d.ts +116 -0
  70. package/dist/installer/targets/types.d.ts.map +1 -0
  71. package/dist/installer/targets/types.js +16 -0
  72. package/dist/installer/targets/types.js.map +1 -0
  73. package/dist/mcp/index.d.ts +12 -0
  74. package/dist/mcp/index.d.ts.map +1 -1
  75. package/dist/mcp/index.js +143 -20
  76. package/dist/mcp/index.js.map +1 -1
  77. package/dist/mcp/server-instructions.d.ts +1 -1
  78. package/dist/mcp/server-instructions.d.ts.map +1 -1
  79. package/dist/mcp/server-instructions.js +20 -5
  80. package/dist/mcp/server-instructions.js.map +1 -1
  81. package/dist/mcp/tools.d.ts +75 -5
  82. package/dist/mcp/tools.d.ts.map +1 -1
  83. package/dist/mcp/tools.js +472 -89
  84. package/dist/mcp/tools.js.map +1 -1
  85. package/dist/mcp/transport.d.ts +17 -0
  86. package/dist/mcp/transport.d.ts.map +1 -1
  87. package/dist/mcp/transport.js +63 -0
  88. package/dist/mcp/transport.js.map +1 -1
  89. package/dist/resolution/frameworks/index.d.ts +1 -0
  90. package/dist/resolution/frameworks/index.d.ts.map +1 -1
  91. package/dist/resolution/frameworks/index.js +5 -1
  92. package/dist/resolution/frameworks/index.js.map +1 -1
  93. package/dist/resolution/frameworks/nestjs.d.ts +26 -0
  94. package/dist/resolution/frameworks/nestjs.d.ts.map +1 -0
  95. package/dist/resolution/frameworks/nestjs.js +374 -0
  96. package/dist/resolution/frameworks/nestjs.js.map +1 -0
  97. package/dist/search/query-utils.d.ts.map +1 -1
  98. package/dist/search/query-utils.js +29 -26
  99. package/dist/search/query-utils.js.map +1 -1
  100. package/dist/sync/git-hooks.d.ts +45 -0
  101. package/dist/sync/git-hooks.d.ts.map +1 -0
  102. package/dist/sync/git-hooks.js +223 -0
  103. package/dist/sync/git-hooks.js.map +1 -0
  104. package/dist/sync/index.d.ts +4 -0
  105. package/dist/sync/index.d.ts.map +1 -1
  106. package/dist/sync/index.js +12 -1
  107. package/dist/sync/index.js.map +1 -1
  108. package/dist/sync/watch-policy.d.ts +48 -0
  109. package/dist/sync/watch-policy.d.ts.map +1 -0
  110. package/dist/sync/watch-policy.js +124 -0
  111. package/dist/sync/watch-policy.js.map +1 -0
  112. package/dist/sync/watcher.d.ts.map +1 -1
  113. package/dist/sync/watcher.js +10 -0
  114. package/dist/sync/watcher.js.map +1 -1
  115. package/dist/ui/glyphs.d.ts +42 -0
  116. package/dist/ui/glyphs.d.ts.map +1 -0
  117. package/dist/ui/glyphs.js +78 -0
  118. package/dist/ui/glyphs.js.map +1 -0
  119. package/dist/ui/shimmer-progress.d.ts +1 -0
  120. package/dist/ui/shimmer-progress.d.ts.map +1 -1
  121. package/dist/ui/shimmer-progress.js +7 -0
  122. package/dist/ui/shimmer-progress.js.map +1 -1
  123. package/dist/ui/shimmer-worker.js +20 -11
  124. package/dist/ui/shimmer-worker.js.map +1 -1
  125. package/dist/ui/types.d.ts +1 -0
  126. package/dist/ui/types.d.ts.map +1 -1
  127. package/dist/vectors/embedder.d.ts +11 -1
  128. package/dist/vectors/embedder.d.ts.map +1 -1
  129. package/dist/vectors/embedder.js +48 -18
  130. package/dist/vectors/embedder.js.map +1 -1
  131. package/dist/vectors/index.d.ts +1 -1
  132. package/dist/vectors/index.d.ts.map +1 -1
  133. package/dist/vectors/index.js.map +1 -1
  134. package/dist/vectors/manager.d.ts +5 -0
  135. package/dist/vectors/manager.d.ts.map +1 -1
  136. package/dist/vectors/manager.js +44 -23
  137. package/dist/vectors/manager.js.map +1 -1
  138. package/package.json +4 -3
  139. package/scripts/agent-eval/audit.sh +68 -0
  140. package/scripts/agent-eval/itrun.sh +107 -0
  141. package/scripts/agent-eval/parse-run.mjs +45 -0
  142. package/scripts/agent-eval/parse-session.mjs +93 -0
  143. package/scripts/agent-eval/run-agent.sh +34 -0
  144. package/scripts/agent-eval/run-all.sh +67 -0
  145. package/scripts/extract-release-notes.mjs +130 -0
  146. package/scripts/local-install.sh +41 -0
  147. package/scripts/release.sh +68 -0
package/README.md CHANGED
@@ -2,17 +2,21 @@
2
2
 
3
3
  # CodeGraph
4
4
 
5
- ### Supercharge Claude Code with Semantic Code Intelligence
5
+ ### Supercharge Claude Code, Cursor, Codex, and OpenCode with Semantic Code Intelligence
6
6
 
7
- **94% fewer tool calls · 77% faster exploration · 100% local**
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-18+-green.svg)](https://nodejs.org/)
11
+ [![Node.js](https://img.shields.io/badge/Node.js-20--24-green.svg)](https://nodejs.org/)
12
12
 
13
13
  [![Windows](https://img.shields.io/badge/Windows-supported-blue.svg)](#)
14
14
  [![macOS](https://img.shields.io/badge/macOS-supported-blue.svg)](#)
15
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)](#)
16
20
 
17
21
  [English](./README.md) · [简体中文](./README.zh-CN.md)
18
22
 
@@ -24,7 +28,7 @@
24
28
  npx @stupidloud/codegraph
25
29
  ```
26
30
 
27
- <sub>Interactive installer configures Claude Code automatically</sub>
31
+ <sub>Interactive installer auto-configures your agent(s) — Claude Code, Cursor, Codex CLI, opencode</sub>
28
32
 
29
33
  #### Initialize Projects
30
34
 
@@ -47,61 +51,50 @@ When Claude Code explores a codebase, it spawns **Explore agents** that scan fil
47
51
 
48
52
  ### Benchmark Results
49
53
 
50
- Tested across 6 real-world codebases comparing Claude Code's Explore agent **with** and **without** CodeGraph:
54
+ 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**.
51
55
 
52
- > **Average: 92% fewer tool calls · 71% faster**
56
+ > **Average: 35% cheaper · 59% fewer tokens · 49% faster · 70% fewer tool calls**
53
57
 
54
- | Codebase | With CG | Without CG | Improvement |
55
- |----------|---------|------------|-------------|
56
- | **VS Code** · TypeScript | 3 calls, 17s | 52 calls, 1m 37s | **94% fewer · 82% faster** |
57
- | **Excalidraw** · TypeScript | 3 calls, 29s | 47 calls, 1m 45s | **94% fewer · 72% faster** |
58
- | **Claude Code** · Python + Rust | 3 calls, 39s | 40 calls, 1m 8s | **93% fewer · 43% faster** |
59
- | **Claude Code** · Java | 1 call, 19s | 26 calls, 1m 22s | **96% fewer · 77% faster** |
60
- | **Alamofire** · Swift | 3 calls, 22s | 32 calls, 1m 39s | **91% fewer · 78% faster** |
61
- | **Swift Compiler** · Swift/C++ | 6 calls, 35s | 37 calls, 2m 8s | **84% fewer · 73% faster** |
58
+ | Codebase | Language | Cost | Tokens | Time | Tool calls |
59
+ |----------|----------|------|--------|------|------------|
60
+ | **VS Code** | TypeScript · ~10k files | 35% cheaper | 73% fewer | 41% faster | 72% fewer |
61
+ | **Excalidraw** | TypeScript · ~600 | 47% cheaper | 73% fewer | 60% faster | 86% fewer |
62
+ | **Django** | Python · ~2.7k | 34% cheaper | 64% fewer | 59% faster | 81% fewer |
63
+ | **Tokio** | Rust · ~700 | 52% cheaper | 81% fewer | 63% faster | 89% fewer |
64
+ | **OkHttp** | Java · ~640 | 17% cheaper | 41% fewer | 36% faster | 64% fewer |
65
+ | **Gin** | Go · ~150 | 22% cheaper | 23% fewer | 34% faster | 19% fewer |
66
+ | **Alamofire** | Swift · ~100 | 38% cheaper | 59% fewer | 51% faster | 77% fewer |
67
+
68
+ 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.
62
69
 
63
70
  <details>
64
71
  <summary><strong>Full benchmark details</strong></summary>
65
72
 
66
- All tests used Claude Opus 4.6 (1M context) with Claude Code v2.1.91. Each test spawned a single Explore agent with the same question.
73
+ **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.
67
74
 
68
- **Queries used:**
75
+ **Queries:**
69
76
  | Codebase | Query |
70
77
  |----------|-------|
71
78
  | VS Code | "How does the extension host communicate with the main process?" |
72
- | Excalidraw | "How does collaborative editing and real-time sync work?" |
73
- | Claude Code (Python+Rust) | "How does tool execution work end to end?" |
74
- | Claude Code (Java) | "How does tool execution work end to end?" |
75
- | Alamofire | "Trace how a request flows from Session.request() through to the URLSession layer" |
76
- | Swift Compiler | "How does the Swift compiler handle error diagnostics?" |
77
-
78
- **With CodeGraph — the agent uses `codegraph_explore` and stops:**
79
- | Codebase | Files Indexed | Nodes | Tool Uses | Tokens | Time | File Reads |
80
- |----------|--------------|-------|-----------|--------|------|------------|
81
- | VS Code (TypeScript) | 4,002 | 59,377 | 3 | 56.6k | 17s | 0 |
82
- | Excalidraw (TypeScript) | 626 | 9,859 | 3 | 57.1k | 29s | 0 |
83
- | Claude Code (Python+Rust) | 115 | 3,080 | 3 | 67.1k | 39s | 0 |
84
- | Claude Code (Java) | | | 1 | 40.8k | 19s | 0 |
85
- | Alamofire (Swift) | 102 | 2,624 | 3 | 57.3k | 22s | 0 |
86
- | Swift Compiler (Swift/C++) | 25,874 | 272,898 | 6 | 77.4k | 35s | 0 |
87
-
88
- **Without CodeGraph the agent uses grep, find, ls, and Read extensively:**
89
- | Codebase | Tool Uses | Tokens | Time | File Reads |
90
- |----------|-----------|--------|------|------------|
91
- | VS Code (TypeScript) | 52 | 89.4k | 1m 37s | ~15 |
92
- | Excalidraw (TypeScript) | 47 | 77.9k | 1m 45s | ~20 |
93
- | Claude Code (Python+Rust) | 40 | 69.3k | 1m 8s | ~15 |
94
- | Claude Code (Java) | 26 | 73.3k | 1m 22s | ~15 |
95
- | Alamofire (Swift) | 32 | 52.4k | 1m 39s | ~10 |
96
- | Swift Compiler (Swift/C++) | 37 | 99.1k | 2m 8s | ~20 |
97
-
98
- **Key observations:**
99
- - With CodeGraph, the agent **never fell back to reading files** — it trusted the codegraph_explore results completely
100
- - Without CodeGraph, agents spent most of their time on discovery (find, ls, grep) before they could even start reading relevant code
101
- - The Java codebase needed only **1 codegraph_explore call** to answer the entire question
102
- - Cross-language queries (Python+Rust) worked seamlessly — CodeGraph's graph traversal found connections across language boundaries
103
- - The Swift benchmark (Alamofire) traced a **9-step call chain** from `Session.request()` to `URLSession.dataTask()` — CodeGraph's graph traversal at depth 3 captured the full chain in one explore call
104
- - The **Swift Compiler** benchmark is the largest codebase tested (**25,874 files, 272,898 nodes**) — CodeGraph indexed it in under 4 minutes and the agent answered a complex cross-cutting question with **6 explore calls and zero file reads** in 35 seconds
79
+ | Excalidraw | "How does Excalidraw render and update canvas elements?" |
80
+ | Django | "How does Django's ORM build and execute a query from a QuerySet?" |
81
+ | Tokio | "How does tokio schedule and run async tasks on its runtime?" |
82
+ | OkHttp | "How does OkHttp process a request through its interceptor chain?" |
83
+ | Gin | "How does gin route requests through its middleware chain?" |
84
+ | Alamofire | "How does Alamofire build, send, and validate a request?" |
85
+
86
+ **Raw medians WITH WITHOUT:**
87
+ | Codebase | Cost | Tokens | Time | Tool calls |
88
+ |----------|------|--------|------|------------|
89
+ | VS Code | $0.42 $0.64 | 393k 1.4M | 1m 0s → 1m 43s | 7 → 23 |
90
+ | Excalidraw | $0.54 $1.02 | 851k 3.2M | 1m 17s 3m 14s | 12 → 83 |
91
+ | Django | $0.41 $0.62 | 499k 1.4M | 1m 0s 2m 25s | 9 → 48 |
92
+ | Tokio | $0.50 $1.04 | 657k 3.4M | 1m 5s → 2m 56s | 9 → 75 |
93
+ | OkHttp | $0.36 $0.44 | 352k 596k | 45s 1m 11s | 5 → 14 |
94
+ | Gin | $0.36 → $0.46 | 431k → 562k | 47s → 1m 11s | 7 → 8 |
95
+ | Alamofire | $0.61 $0.99 | 1.1M 2.6M | 1m 19s → 2m 41s | 15 → 64 |
96
+
97
+ **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.
105
98
 
106
99
  </details>
107
100
 
@@ -131,6 +124,7 @@ CodeGraph detects web-framework routing files and emits `route` nodes linked by
131
124
  | **Flask** | `@app.route('/path', methods=[...])`, blueprint routes |
132
125
  | **FastAPI** | `@app.get(...)`, `@router.post(...)`, all standard methods |
133
126
  | **Express** | `app.get(...)`, `router.post(...)` with middleware chains |
127
+ | **NestJS** | `@Controller` + `@Get/@Post/...`, GraphQL `@Resolver` + `@Query/@Mutation`, `@MessagePattern`/`@EventPattern`, `@SubscribeMessage` |
134
128
  | **Laravel** | `Route::get()`, `Route::resource()`, `Controller@action`, tuple syntax |
135
129
  | **Rails** | `get '/x', to: 'users#index'`, hash-rocket `=>` syntax |
136
130
  | **Spring** | `@GetMapping`, `@PostMapping`, `@RequestMapping` on methods |
@@ -151,15 +145,33 @@ npx @stupidloud/codegraph
151
145
  ```
152
146
 
153
147
  The installer will:
154
- - Prompt to install `codegraph` globally (needed for the MCP server)
155
- - Configure the MCP server in `~/.claude.json`
156
- - Set up auto-allow permissions for CodeGraph tools
157
- - Add global instructions to `~/.claude/CLAUDE.md`
158
- - Optionally initialize your current project
148
+ - Ask which agent(s) to configure auto-detects installed ones from: **Claude Code**, **Cursor**, **Codex CLI**, **opencode**
149
+ - Prompt to install `codegraph` on your PATH (so agents can launch the MCP server)
150
+ - Ask whether configs apply to all your projects or just this one
151
+ - Write each chosen agent's MCP server config + an instructions file (e.g. `CLAUDE.md`, `.cursor/rules/codegraph.mdc`, `~/.codex/AGENTS.md`)
152
+ - Set up auto-allow permissions when Claude Code is one of the targets
153
+ - Initialize your current project (local installs only)
154
+
155
+ **Non-interactive (scripting / CI):**
156
+
157
+ ```bash
158
+ codegraph install --yes # auto-detect agents, install global
159
+ codegraph install --target=cursor,claude --yes # explicit target list
160
+ codegraph install --target=auto --location=local # detected agents, project-local
161
+ codegraph install --print-config codex # print snippet, no file writes
162
+ ```
159
163
 
160
- ### 2. Restart Claude Code
164
+ | Flag | Values | Default |
165
+ |---|---|---|
166
+ | `--target` | `auto`, `all`, `none`, or csv (`claude,cursor,...`) | prompt |
167
+ | `--location` | `global`, `local` | prompt |
168
+ | `--yes` | (boolean) | prompt every step |
169
+ | `--no-permissions` | (boolean) skip Claude auto-allow list | permissions on |
170
+ | `--print-config <id>` | dump snippet for one agent and exit | — |
161
171
 
162
- Restart Claude Code for the MCP server to load.
172
+ ### 2. Restart Your Agent
173
+
174
+ Restart your agent (Claude Code / Cursor / Codex CLI / opencode) for the MCP server to load.
163
175
 
164
176
  ### 3. Initialize Projects
165
177
 
@@ -168,7 +180,9 @@ cd your-project
168
180
  codegraph init -i
169
181
  ```
170
182
 
171
- That's it! Claude Code will use CodeGraph tools automatically when a `.codegraph/` directory exists.
183
+ Builds the per-project knowledge graph index. Also wires up any project-local agent surfaces (e.g. Cursor's `.cursor/rules/codegraph.mdc`) so a single global `codegraph install` works in every project you open — no need to re-run the installer per project.
184
+
185
+ That's it — your agent will use CodeGraph tools automatically when a `.codegraph/` directory exists.
172
186
 
173
187
  <details>
174
188
  <summary><strong>Manual Setup (Alternative)</strong></summary>
@@ -469,6 +483,16 @@ The `.codegraph/config.json` file controls indexing:
469
483
 
470
484
  **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.
471
485
 
486
+ ## Star History
487
+
488
+ <a href="https://www.star-history.com/?repos=colbymchenry%2Fcodegraph&type=date&legend=top-left">
489
+ <picture>
490
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=colbymchenry/codegraph&type=date&theme=dark&legend=top-left" />
491
+ <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=colbymchenry/codegraph&type=date&legend=top-left" />
492
+ <img alt="Star History Chart" src="https://api.star-history.com/chart?repos=colbymchenry/codegraph&type=date&legend=top-left" />
493
+ </picture>
494
+ </a>
495
+
472
496
  ## License
473
497
 
474
498
  MIT
@@ -477,7 +501,7 @@ MIT
477
501
 
478
502
  <div align="center">
479
503
 
480
- **Made for the Claude Code community**
504
+ **Made for AI coding agents — Claude Code, Cursor, Codex CLI, and opencode**
481
505
 
482
506
  [Report Bug](https://github.com/colbymchenry/codegraph/issues) · [Request Feature](https://github.com/colbymchenry/codegraph/issues)
483
507
 
@@ -58,6 +58,7 @@ const fs = __importStar(require("fs"));
58
58
  const child_process_1 = require("child_process");
59
59
  const directory_1 = require("../directory");
60
60
  const shimmer_progress_1 = require("../ui/shimmer-progress");
61
+ const glyphs_1 = require("../ui/glyphs");
61
62
  const node_version_check_1 = require("./node-version-check");
62
63
  // Lazy-load heavy modules (CodeGraph, runInstaller) to keep CLI startup fast.
63
64
  async function loadCodeGraph() {
@@ -66,7 +67,7 @@ async function loadCodeGraph() {
66
67
  }
67
68
  catch (err) {
68
69
  const msg = err instanceof Error ? err.message : String(err);
69
- console.error('\x1b[31m✗\x1b[0m Failed to load CodeGraph modules.');
70
+ console.error(`\x1b[31m${(0, glyphs_1.getGlyphs)().err}\x1b[0m Failed to load CodeGraph modules.`);
70
71
  console.error(`\n Node: ${process.version} Platform: ${process.platform} ${process.arch}`);
71
72
  console.error(`\n Error: ${msg}`);
72
73
  console.error('\n Try reinstalling with: npm install -g @stupidloud/codegraph\n');
@@ -204,12 +205,14 @@ function main() {
204
205
  function createVerboseProgress() {
205
206
  let lastPhase = '';
206
207
  let lastPct = -1;
208
+ let lastDetail = '';
207
209
  const startTime = Date.now();
208
210
  return (progress) => {
209
211
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
210
212
  if (progress.phase !== lastPhase) {
211
213
  lastPhase = progress.phase;
212
214
  lastPct = -1;
215
+ lastDetail = '';
213
216
  console.log(`[${elapsed}s] Phase: ${progress.phase}`);
214
217
  }
215
218
  if (progress.total > 0) {
@@ -217,7 +220,13 @@ function main() {
217
220
  // Log every 5% to keep output manageable
218
221
  if (pct >= lastPct + 5 || progress.current === progress.total) {
219
222
  lastPct = pct;
220
- console.log(`[${elapsed}s] ${progress.current}/${progress.total} (${pct}%)${progress.currentFile ? ` ${progress.currentFile}` : ''}`);
223
+ console.log(`[${elapsed}s] ${progress.current}/${progress.total} (${pct}%)${progress.currentFile ? ` ${(0, glyphs_1.getGlyphs)().dash} ${progress.currentFile}` : ''}`);
224
+ }
225
+ }
226
+ else if (progress.currentFile) {
227
+ if (progress.currentFile !== lastDetail) {
228
+ lastDetail = progress.currentFile;
229
+ console.log(`[${elapsed}s] ${progress.currentFile}`);
221
230
  }
222
231
  }
223
232
  else if (progress.current > 0) {
@@ -232,25 +241,25 @@ function main() {
232
241
  * Print success message
233
242
  */
234
243
  function success(message) {
235
- console.log(chalk.green('✓') + ' ' + message);
244
+ console.log(chalk.green((0, glyphs_1.getGlyphs)().ok) + ' ' + message);
236
245
  }
237
246
  /**
238
247
  * Print error message
239
248
  */
240
249
  function error(message) {
241
- console.error(chalk.red('✗') + ' ' + message);
250
+ console.error(chalk.red((0, glyphs_1.getGlyphs)().err) + ' ' + message);
242
251
  }
243
252
  /**
244
253
  * Print info message
245
254
  */
246
255
  function info(message) {
247
- console.log(chalk.blue('ℹ') + ' ' + message);
256
+ console.log(chalk.blue((0, glyphs_1.getGlyphs)().info) + ' ' + message);
248
257
  }
249
258
  /**
250
259
  * Print warning message
251
260
  */
252
261
  function warn(message) {
253
- console.log(chalk.yellow('⚠') + ' ' + message);
262
+ console.log(chalk.yellow((0, glyphs_1.getGlyphs)().warn) + ' ' + message);
254
263
  }
255
264
  /**
256
265
  * Print indexing results using clack log methods
@@ -270,7 +279,7 @@ function main() {
270
279
  // continuing to the misleading "No files found" branch or throwing.
271
280
  if (!result.success && !hasErrors && result.filesIndexed === 0) {
272
281
  const generic = result.errors.find((e) => e.severity === 'error');
273
- clack.log.error(generic?.message ?? 'Indexing failed no further details available');
282
+ clack.log.error(generic?.message ?? `Indexing failed ${(0, glyphs_1.getGlyphs)().dash} no further details available`);
274
283
  return;
275
284
  }
276
285
  if (result.filesIndexed > 0) {
@@ -283,7 +292,7 @@ function main() {
283
292
  clack.log.info(`${formatNumber(result.nodesCreated)} nodes, ${formatNumber(result.edgesCreated)} edges in ${formatDuration(result.durationMs)}`);
284
293
  }
285
294
  else if (hasErrors) {
286
- clack.log.error(`Indexing failed all ${formatNumber(result.filesErrored)} files had errors`);
295
+ clack.log.error(`Indexing failed ${(0, glyphs_1.getGlyphs)().dash} all ${formatNumber(result.filesErrored)} files had errors`);
287
296
  }
288
297
  else {
289
298
  clack.log.warn('No files found to index');
@@ -313,7 +322,7 @@ function main() {
313
322
  clack.log.info('See .codegraph/errors.log for details');
314
323
  }
315
324
  if (result.filesIndexed > 0) {
316
- clack.log.info('The index is fully usable only the failed files are missing.');
325
+ clack.log.info(`The index is fully usable ${(0, glyphs_1.getGlyphs)().dash} only the failed files are missing.`);
317
326
  }
318
327
  }
319
328
  else if (projectPath) {
@@ -350,7 +359,7 @@ function main() {
350
359
  }
351
360
  }
352
361
  const lines = [
353
- `CodeGraph Error Log ${new Date().toISOString()}`,
362
+ `CodeGraph Error Log - ${new Date().toISOString()}`,
354
363
  `${errorsByFile.size} files with errors`,
355
364
  '',
356
365
  ];
@@ -383,6 +392,21 @@ function main() {
383
392
  if ((0, directory_1.isInitialized)(projectPath)) {
384
393
  clack.log.warn(`Already initialized in ${projectPath}`);
385
394
  clack.log.info('Use "codegraph index" to re-index or "codegraph sync" to update');
395
+ // Re-run agent surface wiring so re-running `init` is the
396
+ // documented way to recover a project that's missing its
397
+ // Cursor rules file (or future per-agent project surfaces).
398
+ try {
399
+ const { wireProjectSurfacesForGlobalAgents } = await Promise.resolve().then(() => __importStar(require('../installer')));
400
+ for (const { target, file } of wireProjectSurfacesForGlobalAgents()) {
401
+ clack.log.success(`${target.displayName}: ${file.action} ${file.path}`);
402
+ }
403
+ }
404
+ catch { /* non-fatal */ }
405
+ try {
406
+ const { offerWatchFallback } = await Promise.resolve().then(() => __importStar(require('../installer')));
407
+ await offerWatchFallback(clack, projectPath);
408
+ }
409
+ catch { /* non-fatal */ }
386
410
  clack.outro('');
387
411
  return;
388
412
  }
@@ -394,6 +418,20 @@ function main() {
394
418
  config: semanticConfig,
395
419
  });
396
420
  clack.log.success(`Initialized in ${projectPath}`);
421
+ // Bootstrap project-local surfaces for any agent that's
422
+ // configured globally (Cursor needs ./.cursor/rules/codegraph.mdc
423
+ // to actually prefer codegraph over native grep). Silent when
424
+ // there's nothing to write.
425
+ try {
426
+ const { wireProjectSurfacesForGlobalAgents } = await Promise.resolve().then(() => __importStar(require('../installer')));
427
+ for (const { target, file } of wireProjectSurfacesForGlobalAgents()) {
428
+ clack.log.success(`${target.displayName}: ${file.action} ${file.path}`);
429
+ }
430
+ }
431
+ catch (err) {
432
+ const msg = err instanceof Error ? err.message : String(err);
433
+ clack.log.warn(`Skipped wiring project-local agent surfaces: ${msg}`);
434
+ }
397
435
  if (options.index) {
398
436
  let result;
399
437
  if (options.verbose) {
@@ -403,7 +441,7 @@ function main() {
403
441
  });
404
442
  }
405
443
  else {
406
- process.stdout.write(`${colors.dim}│${colors.reset}\n`);
444
+ process.stdout.write(`${colors.dim}${(0, glyphs_1.getGlyphs)().rail}${colors.reset}\n`);
407
445
  const progress = (0, shimmer_progress_1.createShimmerProgress)();
408
446
  result = await cg.indexAll({
409
447
  onProgress: progress.onProgress,
@@ -415,6 +453,11 @@ function main() {
415
453
  else {
416
454
  clack.log.info('Run "codegraph index" to index the project');
417
455
  }
456
+ try {
457
+ const { offerWatchFallback } = await Promise.resolve().then(() => __importStar(require('../installer')));
458
+ await offerWatchFallback(clack, projectPath);
459
+ }
460
+ catch { /* non-fatal */ }
418
461
  clack.outro('Done');
419
462
  cg.destroy();
420
463
  }
@@ -442,7 +485,7 @@ function main() {
442
485
  const readline = await Promise.resolve().then(() => __importStar(require('readline')));
443
486
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
444
487
  const answer = await new Promise((resolve) => {
445
- rl.question(chalk.yellow('⚠ This will permanently delete all CodeGraph data. Continue? (y/N) '), resolve);
488
+ rl.question(chalk.yellow(`${(0, glyphs_1.getGlyphs)().warn} This will permanently delete all CodeGraph data. Continue? (y/N) `), resolve);
446
489
  });
447
490
  rl.close();
448
491
  if (answer.toLowerCase() !== 'y') {
@@ -453,6 +496,15 @@ function main() {
453
496
  const { default: CodeGraph } = await loadCodeGraph();
454
497
  const cg = CodeGraph.openSync(projectPath);
455
498
  cg.uninitialize();
499
+ // Clean up any git sync hooks we installed (no-op if none / not a repo).
500
+ try {
501
+ const { removeGitSyncHook } = await Promise.resolve().then(() => __importStar(require('../sync/git-hooks')));
502
+ const removed = removeGitSyncHook(projectPath);
503
+ if (removed.installed.length > 0) {
504
+ info(`Removed git ${removed.installed.join(', ')} sync hook${removed.installed.length > 1 ? 's' : ''}`);
505
+ }
506
+ }
507
+ catch { /* non-fatal */ }
456
508
  success(`Removed CodeGraph from ${projectPath}`);
457
509
  }
458
510
  catch (err) {
@@ -503,7 +555,7 @@ function main() {
503
555
  });
504
556
  }
505
557
  else {
506
- process.stdout.write(`${colors.dim}│${colors.reset}\n`);
558
+ process.stdout.write(`${colors.dim}${(0, glyphs_1.getGlyphs)().rail}${colors.reset}\n`);
507
559
  const progress = (0, shimmer_progress_1.createShimmerProgress)();
508
560
  result = await cg.indexAll({
509
561
  onProgress: progress.onProgress,
@@ -547,7 +599,7 @@ function main() {
547
599
  }
548
600
  const clack = await importESM('@clack/prompts');
549
601
  clack.intro('Syncing CodeGraph');
550
- process.stdout.write(`${colors.dim}│${colors.reset}\n`);
602
+ process.stdout.write(`${colors.dim}${(0, glyphs_1.getGlyphs)().rail}${colors.reset}\n`);
551
603
  const progress = (0, shimmer_progress_1.createShimmerProgress)();
552
604
  const result = await cg.sync({
553
605
  onProgress: progress.onProgress,
@@ -566,7 +618,7 @@ function main() {
566
618
  details.push(`Modified: ${result.filesModified}`);
567
619
  if (result.filesRemoved > 0)
568
620
  details.push(`Removed: ${result.filesRemoved}`);
569
- clack.log.info(`${details.join(', ')} ${formatNumber(result.nodesUpdated)} nodes in ${formatDuration(result.durationMs)}`);
621
+ clack.log.info(`${details.join(', ')} ${(0, glyphs_1.getGlyphs)().dash} ${formatNumber(result.nodesUpdated)} nodes in ${formatDuration(result.durationMs)}`);
570
622
  }
571
623
  clack.outro('Done');
572
624
  cg.destroy();
@@ -641,7 +693,7 @@ function main() {
641
693
  // when the native build fails.
642
694
  const backendLabel = backend === 'native'
643
695
  ? chalk.green('native')
644
- : chalk.yellow('wasm slower fallback; run `npm rebuild better-sqlite3`');
696
+ : chalk.yellow(`wasm ${(0, glyphs_1.getGlyphs)().dash} slower fallback; run \`npm rebuild better-sqlite3\``);
645
697
  console.log(` Backend: ${backendLabel}`);
646
698
  console.log();
647
699
  // Node breakdown
@@ -886,8 +938,9 @@ function main() {
886
938
  const renderNode = (node, prefix, isLast, depth) => {
887
939
  if (maxDepth !== undefined && depth > maxDepth)
888
940
  return;
889
- const connector = isLast ? '└── ' : '├── ';
890
- const childPrefix = isLast ? ' ' : '│ ';
941
+ const glyphs = (0, glyphs_1.getGlyphs)();
942
+ const connector = isLast ? glyphs.treeLast : glyphs.treeBranch;
943
+ const childPrefix = isLast ? ' ' : glyphs.treePipe;
891
944
  if (node.name) {
892
945
  let line = prefix + connector + node.name;
893
946
  if (node.file && includeMetadata) {
@@ -954,8 +1007,14 @@ function main() {
954
1007
  .description('Start CodeGraph as an MCP server for AI assistants')
955
1008
  .option('-p, --path <path>', 'Project path (optional for MCP mode, uses rootUri from client)')
956
1009
  .option('--mcp', 'Run as MCP server (stdio transport)')
1010
+ .option('--no-watch', 'Disable the file watcher (no auto-sync; useful on slow filesystems like WSL2 /mnt drives)')
957
1011
  .action(async (options) => {
958
1012
  const projectPath = options.path ? resolveProjectPath(options.path) : undefined;
1013
+ // Commander sets watch=false when --no-watch is passed. Route it through
1014
+ // the same env-var chokepoint the watcher and MCP server already honor.
1015
+ if (options.watch === false) {
1016
+ process.env.CODEGRAPH_NO_WATCH = '1';
1017
+ }
959
1018
  try {
960
1019
  if (options.mcp) {
961
1020
  // Start MCP server - it handles initialization lazily based on rootUri from client
@@ -968,7 +1027,7 @@ function main() {
968
1027
  // Default: show info about MCP mode.
969
1028
  // Use stderr so stdout stays clean for any piped/stdio usage.
970
1029
  console.error(chalk.bold('\nCodeGraph MCP Server\n'));
971
- console.error(chalk.blue('ℹ') + ' Use --mcp flag to start the MCP server');
1030
+ console.error(chalk.blue((0, glyphs_1.getGlyphs)().info) + ' Use --mcp flag to start the MCP server');
972
1031
  console.error('\nTo use with Claude Code, add to your MCP configuration:');
973
1032
  console.error(chalk.dim(`
974
1033
  {
@@ -1144,7 +1203,7 @@ function main() {
1144
1203
  }
1145
1204
  const lockPath = path.join((0, directory_1.getCodeGraphDir)(projectPath), 'codegraph.lock');
1146
1205
  if (!fs.existsSync(lockPath)) {
1147
- info('No lock file found nothing to do');
1206
+ info(`No lock file found ${(0, glyphs_1.getGlyphs)().dash} nothing to do`);
1148
1207
  return;
1149
1208
  }
1150
1209
  fs.unlinkSync(lockPath);
@@ -1290,10 +1349,54 @@ function main() {
1290
1349
  */
1291
1350
  program
1292
1351
  .command('install')
1293
- .description('Run interactive installer for Claude Code integration')
1294
- .action(async () => {
1295
- const { runInstaller } = await Promise.resolve().then(() => __importStar(require('../installer')));
1296
- await runInstaller();
1352
+ .description('Install codegraph MCP server into one or more agents (Claude Code, Cursor, Codex CLI, opencode)')
1353
+ .option('-t, --target <ids>', 'Target agent(s): comma-separated ids, or "auto"|"all"|"none". Default: prompt')
1354
+ .option('-l, --location <where>', 'Install location: "global" or "local". Default: prompt')
1355
+ .option('-y, --yes', 'Non-interactive: defaults to --location=global --target=auto, auto-allow on')
1356
+ .option('--no-permissions', 'Skip writing the auto-allow permissions list (Claude Code only)')
1357
+ .option('--print-config <id>', 'Print MCP config snippet for the named agent and exit (no file writes)')
1358
+ .action(async (opts) => {
1359
+ if (opts.printConfig) {
1360
+ const { getTarget, listTargetIds } = await Promise.resolve().then(() => __importStar(require('../installer/targets/registry')));
1361
+ const target = getTarget(opts.printConfig);
1362
+ if (!target) {
1363
+ const known = listTargetIds().join(', ');
1364
+ error(`Unknown target "${opts.printConfig}". Known: ${known}.`);
1365
+ process.exit(1);
1366
+ }
1367
+ const loc = (opts.location === 'local' ? 'local' : 'global');
1368
+ process.stdout.write(target.printConfig(loc));
1369
+ return;
1370
+ }
1371
+ const { runInstallerWithOptions } = await Promise.resolve().then(() => __importStar(require('../installer')));
1372
+ if (opts.location && opts.location !== 'global' && opts.location !== 'local') {
1373
+ error(`--location must be "global" or "local" (got "${opts.location}").`);
1374
+ process.exit(1);
1375
+ }
1376
+ try {
1377
+ // Commander's `--no-permissions` makes `opts.permissions === false`;
1378
+ // omitting the flag leaves it `true` (the positive-form default).
1379
+ // We MUST treat the default-true as "user did not override — let
1380
+ // the orchestrator prompt" and only forward an explicit `false`
1381
+ // (or `true` when --yes implies it). Otherwise the auto-allow
1382
+ // prompt is silently skipped on every interactive run.
1383
+ const explicitNoPermissions = opts.permissions === false;
1384
+ const autoAllow = explicitNoPermissions
1385
+ ? false
1386
+ : opts.yes
1387
+ ? true
1388
+ : undefined;
1389
+ await runInstallerWithOptions({
1390
+ target: opts.target,
1391
+ location: opts.location,
1392
+ autoAllow,
1393
+ yes: opts.yes,
1394
+ });
1395
+ }
1396
+ catch (err) {
1397
+ error(err instanceof Error ? err.message : String(err));
1398
+ process.exit(1);
1399
+ }
1297
1400
  });
1298
1401
  // Parse and run
1299
1402
  program.parse();