@optave/codegraph 2.2.1 → 2.2.3-dev.44e8146

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.
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  <h1 align="center">codegraph</h1>
6
6
 
7
7
  <p align="center">
8
- <strong>Always-fresh code intelligence for AI agents sub-second incremental rebuilds, zero-cost by default, optionally enhanced with your LLM.</strong>
8
+ <strong>Give your AI the map before it starts exploring.</strong>
9
9
  </p>
10
10
 
11
11
  <p align="center">
@@ -13,63 +13,64 @@
13
13
  <a href="https://github.com/optave/codegraph/blob/main/LICENSE"><img src="https://img.shields.io/github/license/optave/codegraph?style=flat-square&logo=opensourceinitiative&logoColor=white" alt="Apache-2.0 License" /></a>
14
14
  <a href="https://github.com/optave/codegraph/actions"><img src="https://img.shields.io/github/actions/workflow/status/optave/codegraph/codegraph-impact.yml?style=flat-square&logo=githubactions&logoColor=white&label=CI" alt="CI" /></a>
15
15
  <img src="https://img.shields.io/badge/node-%3E%3D20-339933?style=flat-square&logo=node.js&logoColor=white" alt="Node >= 20" />
16
- <img src="https://img.shields.io/badge/graph-always%20fresh-brightgreen?style=flat-square&logo=shield&logoColor=white" alt="Always Fresh" />
17
16
  </p>
18
17
 
19
18
  <p align="center">
20
- <a href="#-why-codegraph">Why codegraph?</a>
21
- <a href="#-quick-start">Quick Start</a>
22
- <a href="#-features">Features</a>
23
- <a href="#-commands">Commands</a>
24
- <a href="#-language-support">Languages</a>
25
- <a href="#-ai-agent-integration">AI Integration</a>
26
- <a href="#-recommended-practices">Practices</a>
27
- <a href="#-ci--github-actions">CI/CD</a>
28
- <a href="#-roadmap">Roadmap</a>
29
- <a href="#-contributing">Contributing</a>
19
+ <a href="#the-problem">The Problem</a> &middot;
20
+ <a href="#what-codegraph-does">What It Does</a> &middot;
21
+ <a href="#-quick-start">Quick Start</a> &middot;
22
+ <a href="#-commands">Commands</a> &middot;
23
+ <a href="#-language-support">Languages</a> &middot;
24
+ <a href="#-ai-agent-integration">AI Integration</a> &middot;
25
+ <a href="#-how-it-works">How It Works</a> &middot;
26
+ <a href="#-recommended-practices">Practices</a> &middot;
27
+ <a href="#-roadmap">Roadmap</a>
30
28
  </p>
31
29
 
32
30
  ---
33
31
 
34
- > **The code graph that keeps up with your commits.**
35
- >
36
- > Codegraph parses your codebase with [tree-sitter](https://tree-sitter.github.io/) (native Rust or WASM), builds a function-level dependency graph in SQLite, and keeps it current with sub-second incremental rebuilds. Every query runs locally — no API keys, no Docker, no setup. When you want deeper intelligence, bring your own LLM provider and codegraph enhances search and analysis through the same API you already use. Your code only goes where you choose to send it.
32
+ ## The Problem
37
33
 
38
- ---
34
+ AI coding assistants are incredible — until your codebase gets big enough. Then they get lost.
39
35
 
40
- ## 🔄 Why most code graph tools can't keep up with your commits
36
+ On a large codebase, a great portion of your AI budget isn't going toward solving tasks. It's going toward the AI re-orienting itself in your code. Every session. Over and over. It burns tokens on tool calls — `grep`, `find`, `cat` — just to figure out what calls what. It loses context. It hallucinates dependencies. It modifies a function without realizing 14 callers across 9 files depend on it.
41
37
 
42
- If you use a code graph with an AI agent, the graph needs to be **current**. A stale graph gives the agent wrong answers — deleted functions still show up, new dependencies are invisible, impact analysis misses the code you just wrote. The graph should rebuild on every commit, ideally on every save.
38
+ When the AI catches these mistakes, you waste time and tokens on corrections. When it doesn't catch them, your codebase starts degrading with silent bugs until things stop working.
43
39
 
44
- Most tools in this space can't do that:
40
+ And when you hit `/clear` or run out of context? It starts from scratch.
45
41
 
46
- | Problem | Who has it | Why it breaks on every commit |
47
- |---|---|---|
48
- | **Full re-index on every change** | code-graph-rag, CodeMCP, axon, joern, cpg, GitNexus | No file-level change tracking. Change one file → re-parse and re-insert the entire codebase. On a 3,000-file project, that's 30+ seconds per commit minimum |
49
- | **Cloud API calls baked into the pipeline** | code-graph-rag, CodeRAG | Embeddings are generated through cloud APIs (OpenAI, Voyage AI, Gemini). Every rebuild = API round-trips for every function. Slow, expensive, and rate-limited. You can't put this in a commit hook |
50
- | **Heavy infrastructure that's slow to restart** | code-graph-rag (Memgraph), axon (KuzuDB), badger-graph (Dgraph) | External databases add latency to every write. Bulk-inserting a full graph into Memgraph is not a sub-second operation |
51
- | **No persistence between runs** | pyan, cflow | Re-parse from scratch every time. No database, no delta, no incremental anything |
42
+ ## What Codegraph Does
52
43
 
53
- **Codegraph solves this with three-tier incremental change detection:**
44
+ Codegraph gives your AI a pre-built, always-current map of your entire codebase — every function, every caller, every dependency — so it stops guessing and starts knowing.
54
45
 
55
- 1. **Tier 0 Journal (O(changed)):** If `codegraph watch` was running, a change journal records exactly which files were touched. The next build reads the journal and only processes those files zero filesystem scanning
56
- 2. **Tier 1 — mtime+size (O(n) stats, O(changed) reads):** No journal? Codegraph stats every file and compares mtime + size against stored values. Matching files are skipped without reading a single byte — 10-100x cheaper than hashing
57
- 3. **Tier 2 — Hash (O(changed) reads):** Files that fail the mtime/size check are read and MD5-hashed. Only files whose hash actually changed get re-parsed and re-inserted
46
+ It parses your code with [tree-sitter](https://tree-sitter.github.io/) (native Rust or WASM), builds a function-level dependency graph in SQLite, and keeps it current with sub-second incremental rebuilds. Your AI gets answers like _"this function has 14 callers across 9 files"_ instantly, instead of spending 30 tool calls to maybe discover half of them.
58
47
 
59
- **Result:** change one file in a 3,000-file project → rebuild completes in **under a second**. With watch mode active, rebuilds are near-instant the journal makes the build proportional to the number of changed files, not the size of the codebase. Put it in a commit hook, a file watcher, or let your AI agent trigger it. The graph is always current.
48
+ **Free. Open source. Fully local.** Zero network calls, zero telemetry. Your code stays on your machine. When you want deeper intelligence, bring your own LLM provider your code only goes where you choose to send it.
60
49
 
61
- And because the core pipeline is pure local computation (tree-sitter + SQLite), there are no API calls, no network latency, and no cost. LLM-powered features (semantic search, richer embeddings) are a separate optional layer — they enhance the graph but never block it from being current.
50
+ **Three commands to get started:**
62
51
 
63
- ---
52
+ ```bash
53
+ npm install -g @optave/codegraph
54
+ cd your-project
55
+ codegraph build
56
+ ```
64
57
 
65
- ## 💡 Why codegraph?
58
+ That's it. No config files, no Docker, no JVM, no API keys, no accounts. The graph is ready to query. Add `codegraph mcp` to your AI agent's config and it has full access to your dependency graph through 17 MCP tools.
66
59
 
67
- <sub>Comparison last verified: February 2026</sub>
60
+ ### Why it matters
68
61
 
69
- Most code graph tools make you choose: **fast local analysis with no AI, or powerful AI features that require full re-indexing through cloud APIs on every change.** Codegraph gives you both — a graph that rebuilds in milliseconds on every commit, with optional LLM enhancement through the provider you're already using.
62
+ | Without codegraph | With codegraph |
63
+ |---|---|
64
+ | AI spends 20+ tool calls per session re-discovering your code structure | AI gets full dependency context in one call |
65
+ | Modifies `parseConfig()` without knowing 9 files import it | `fn-impact parseConfig` shows every caller before the edit |
66
+ | Hallucinates that `auth.js` imports from `db.js` | `deps src/auth.js` shows the real import graph |
67
+ | After `/clear`, starts from scratch | Graph persists — next session picks up where this one left off |
68
+ | Suggests renaming a function, breaks 14 call sites silently | `diff-impact --staged` catches the breakage before you commit |
70
69
 
71
70
  ### Feature comparison
72
71
 
72
+ <sub>Comparison last verified: February 2026</sub>
73
+
73
74
  | Capability | codegraph | [joern](https://github.com/joernio/joern) | [narsil-mcp](https://github.com/postrv/narsil-mcp) | [code-graph-rag](https://github.com/vitali87/code-graph-rag) | [cpg](https://github.com/Fraunhofer-AISEC/cpg) | [GitNexus](https://github.com/abhigyanpatwari/GitNexus) | [CodeMCP](https://github.com/SimplyLiz/CodeMCP) | [axon](https://github.com/harshkedia177/axon) |
74
75
  |---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
75
76
  | Function-level analysis | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** |
@@ -90,82 +91,22 @@ Most code graph tools make you choose: **fast local analysis with no AI, or powe
90
91
 
91
92
  | | Differentiator | In practice |
92
93
  |---|---|---|
93
- | **⚡** | **Always-fresh graph** | Three-tier change detection: journal (O(changed)) → mtime+size (O(n) stats) → hash (O(changed) reads). Sub-second rebuilds even on large codebases. Competitors re-index everything from scratch; Merkle-tree approaches still require O(n) filesystem scanning |
94
- | **🔓** | **Zero-cost core, LLM-enhanced when you want** | Full graph analysis with no API keys, no accounts, no cost. Optionally bring your own LLM provider for richer embeddings and AI-powered search — your code only goes to the provider you already chose |
94
+ | **⚡** | **Always-fresh graph** | Three-tier change detection: journal (O(changed)) → mtime+size (O(n) stats) → hash (O(changed) reads). Sub-second rebuilds even on large codebases |
95
+ | **🔓** | **Zero-cost core, LLM-enhanced when you want** | Full graph analysis with no API keys, no accounts, no cost. Optionally bring your own LLM provider — your code only goes where you choose |
95
96
  | **🔬** | **Function-level, not just files** | Traces `handleAuth()` → `validateToken()` → `decryptJWT()` and shows 14 callers across 9 files break if `decryptJWT` changes |
96
- | **🤖** | **Built for AI agents** | 17-tool [MCP server](https://modelcontextprotocol.io/) with `context` and `explain` compound commands — AI assistants get full function context in one call. Single-repo by default, your code doesn't leak to other projects |
97
- | **🌐** | **Multi-language, one CLI** | JS/TS + Python + Go + Rust + Java + C# + PHP + Ruby + HCL in a single graph — no juggling Madge, pyan, and cflow |
97
+ | **🤖** | **Built for AI agents** | 17-tool [MCP server](https://modelcontextprotocol.io/) — AI assistants query your graph directly. Single-repo by default |
98
+ | **🌐** | **Multi-language, one CLI** | JS/TS + Python + Go + Rust + Java + C# + PHP + Ruby + HCL in a single graph |
98
99
  | **💥** | **Git diff impact** | `codegraph diff-impact` shows changed functions, their callers, and full blast radius — ships with a GitHub Actions workflow |
99
- | **🧠** | **Semantic search** | Local embeddings by default, LLM-powered embeddings when opted in — multi-query with RRF ranking via `"auth; token; JWT"` |
100
-
101
- ### How other tools compare
102
-
103
- The key question is: **can you rebuild your graph on every commit in a large codebase without it costing money or taking minutes?** Most tools in this space either re-index everything from scratch (slow), require cloud API calls for core features (costly), or both. Codegraph's three-tier incremental detection achieves true O(changed) in the best case — when the watcher is running, rebuilds are proportional only to the number of files that changed, not the size of the codebase. The core pipeline needs no API keys at all. LLM-powered features are opt-in, using whichever provider you already work with.
104
-
105
- | Tool | What it does well | The tradeoff |
106
- |---|---|---|
107
- | [joern](https://github.com/joernio/joern) | Full CPG (AST + CFG + PDG) for vulnerability discovery, Scala query DSL, 14 languages, daily releases | No incremental builds — full re-parse on every change. Requires JDK 21, no built-in MCP, no watch mode |
108
- | [narsil-mcp](https://github.com/postrv/narsil-mcp) | 90 MCP tools, 32 languages, taint analysis, SBOM, dead code, neural search, Merkle-tree incremental indexing, single ~30MB binary | Merkle trees still require O(n) filesystem scanning on every rebuild. Primarily MCP-only — no standalone CLI query interface. Neural search requires API key or ONNX source build |
109
- | [code-graph-rag](https://github.com/vitali87/code-graph-rag) | Graph RAG with Memgraph, multi-provider AI, semantic search, code editing via AST | No incremental rebuilds — full re-index + re-embed through cloud APIs on every change. Requires Docker |
110
- | [cpg](https://github.com/Fraunhofer-AISEC/cpg) | Formal Code Property Graph (AST + CFG + PDG + DFG), ~10 languages, MCP module, LLVM IR support, academic specifications | No incremental builds. Requires JVM + Gradle, no zero config, no watch mode |
111
- | [GitNexus](https://github.com/abhigyanpatwari/GitNexus) | Knowledge graph with precomputed structural intelligence, 7 MCP tools, hybrid search (BM25 + semantic + RRF), clustering, process tracing | Full 6-phase pipeline re-run on changes. KuzuDB graph DB, browser mode limited to ~5,000 files. **PolyForm NC — no commercial use** |
112
- | [CodeMCP](https://github.com/SimplyLiz/CodeMCP) | SCIP compiler-grade indexing, compound operations (83% token savings), secret scanning | No incremental builds. Custom license, requires SCIP toolchains per language |
113
- | [axon](https://github.com/harshkedia177/axon) | 11-phase pipeline, KuzuDB, community detection, dead code, change coupling | Full pipeline re-run on changes. No license, Python-only, no MCP |
114
- | [Madge](https://github.com/pahen/madge) | Simple file-level JS/TS dependency graphs | No function-level analysis, no impact tracing, JS/TS only |
115
- | [dependency-cruiser](https://github.com/sverweij/dependency-cruiser) | Architectural rule validation for JS/TS | Module-level only (function-level explicitly out of scope), requires config |
116
- | [Nx graph](https://nx.dev/) | Monorepo project-level dependency graph | Requires Nx workspace, project-level only (not file or function) |
117
- | [pyan](https://github.com/Technologicat/pyan) / [cflow](https://www.gnu.org/software/cflow/) | Function-level call graphs | Single-language each (Python / C only), no persistence, no queries |
118
-
119
- ### Codegraph vs. Narsil-MCP: How to Decide
120
-
121
- If you are looking for local code intelligence over MCP, the closest alternative to `codegraph` is [postrv/narsil-mcp](https://github.com/postrv/narsil-mcp). Both projects aim to give AI agents deep context about your codebase, but they approach the problem with fundamentally different philosophies.
122
-
123
- Here is a cold, analytical breakdown to help you decide which tool fits your workflow.
124
-
125
- #### The Core Difference
126
-
127
- * **Codegraph is a surgical scalpel.** It does one thing exceptionally well: building an always-fresh, function-level dependency graph in SQLite and exposing it to AI agents with zero fluff.
128
- * **Narsil-MCP is a Swiss Army knife.** It is a sprawling, "batteries-included" intelligence server that includes everything from taint analysis and SBOM generation to SPARQL knowledge graphs.
129
-
130
- #### Feature Comparison
131
-
132
- | Aspect | Optave Codegraph | Narsil-MCP |
133
- | :--- | :--- | :--- |
134
- | **Philosophy** | Lean, deterministic, AI-optimized | Comprehensive, feature-dense |
135
- | **AI Tool Count** | 17 focused tools | 90 distinct tools |
136
- | **Language Support** | 11 languages | 32 languages |
137
- | **Primary Interface** | CLI-first with MCP integration | MCP-first (CLI is secondary) |
138
- | **Supply Chain Risk** | Low (minimal dependency tree) | Higher (requires massive dependency graph for embedded ML/scanners) |
139
- | **Graph Updates** | **Three-tier O(changed)** — journal → mtime+size → hash. With watch mode, only changed files are touched | Merkle trees — O(n) filesystem scan on every rebuild to recompute tree hashes |
140
-
141
- #### Choose Codegraph if:
142
-
143
- * **You need the fastest possible incremental rebuilds.** Codegraph’s three-tier change detection (journal → mtime+size → hash) achieves true O(changed) when the watcher is running — only touched files are processed. Narsil’s Merkle trees still require O(n) filesystem scanning to recompute hashes on every rebuild, even when nothing changed. On a 3,000-file project, this is the difference between near-instant and noticeable.
144
- * **You want to optimize AI agent reasoning.** Large Language Models degrade in performance and hallucinate when overwhelmed with choices. Codegraph’s tight 17-tool surface area ensures agents quickly understand their capabilities without wasting context window tokens.
145
- * **You are concerned about supply chain attacks.** To support 90 tools, SBOMs, and neural embeddings, a tool must pull in a massive dependency tree. Codegraph keeps its dependencies minimal, dramatically reducing the risk of malicious code sneaking onto your machine.
146
- * **You want deterministic blast-radius checks.** Features like `diff-impact` are built specifically to tell you exactly how a changed function cascades through your codebase before you merge a PR.
147
- * **You value a strong standalone CLI.** You want to query your code graph locally without necessarily spinning up an AI agent.
148
-
149
- #### Choose Narsil-MCP if:
150
-
151
- * **You want security and code intelligence together.** You dont want a separated MCP for security and prefer an 'all-in-one solution.
152
- * **You use niche languages.** Your codebase relies heavily on languages outside of Codegraph's core 11 (e.g., Fortran, Erlang, Zig, Swift).
153
- * **You are willing to manage tool presets.** Because 90 tools will overload an AI's context window, you don't mind manually configuring preset files (like "Minimal" or "Balanced") to restrict what the AI can see depending on your editor.
100
+ | **🧠** | **Semantic search** | Local embeddings by default, LLM-powered when opted in — multi-query with RRF ranking via `"auth; token; JWT"` |
154
101
 
155
102
  ---
156
103
 
157
104
  ## 🚀 Quick Start
158
105
 
159
106
  ```bash
160
- # Install from npm
107
+ # Install
161
108
  npm install -g @optave/codegraph
162
109
 
163
- # Or install from source
164
- git clone https://github.com/optave/codegraph.git
165
- cd codegraph
166
- npm install
167
- npm link
168
-
169
110
  # Build a graph for any project
170
111
  cd your-project
171
112
  codegraph build # → .codegraph/graph.db created
@@ -176,6 +117,37 @@ codegraph query myFunc # find any function, see callers & callees
176
117
  codegraph deps src/index.ts # file-level import/export map
177
118
  ```
178
119
 
120
+ Or install from source:
121
+
122
+ ```bash
123
+ git clone https://github.com/optave/codegraph.git
124
+ cd codegraph && npm install && npm link
125
+ ```
126
+
127
+ ### For AI agents
128
+
129
+ Add codegraph to your agent's instructions (e.g. `CLAUDE.md`):
130
+
131
+ ```markdown
132
+ Before modifying code, always:
133
+ 1. `codegraph where <name>` — find where the symbol lives
134
+ 2. `codegraph context <name> -T` — get full context (source, deps, callers)
135
+ 3. `codegraph fn-impact <name> -T` — check blast radius before editing
136
+
137
+ After modifying code:
138
+ 4. `codegraph diff-impact --staged -T` — verify impact before committing
139
+ ```
140
+
141
+ Or connect directly via MCP:
142
+
143
+ ```bash
144
+ codegraph mcp # 17-tool MCP server — AI queries the graph directly
145
+ ```
146
+
147
+ Full agent setup: [AI Agent Guide](docs/ai-agent-guide.md) &middot; [CLAUDE.md template](docs/ai-agent-guide.md#claudemd-template)
148
+
149
+ ---
150
+
179
151
  ## ✨ Features
180
152
 
181
153
  | | Feature | Description |
@@ -194,7 +166,7 @@ codegraph deps src/index.ts # file-level import/export map
194
166
  | 🧠 | **Semantic search** | Embeddings-powered natural language search with multi-query RRF ranking |
195
167
  | 👀 | **Watch mode** | Incrementally update the graph as files change |
196
168
  | 🤖 | **MCP server** | 17-tool MCP server for AI assistants; single-repo by default, opt-in multi-repo |
197
- | 🔒 | **Your code, your choice** | Zero-cost core with no API keys. Optionally enhance with your LLM provider your code only goes where you send it |
169
+ | | **Always fresh** | Three-tier incremental detectionsub-second rebuilds even on large codebases |
198
170
 
199
171
  ## 📦 Commands
200
172
 
@@ -238,6 +210,7 @@ codegraph fn-impact <name> # What functions break if this one changes
238
210
  codegraph diff-impact # Impact of unstaged git changes
239
211
  codegraph diff-impact --staged # Impact of staged changes
240
212
  codegraph diff-impact HEAD~3 # Impact vs a specific ref
213
+ codegraph diff-impact main --format mermaid -T # Mermaid flowchart of blast radius
241
214
  ```
242
215
 
243
216
  ### Structure & Hotspots
@@ -261,10 +234,10 @@ codegraph cycles --functions # Function-level cycles
261
234
 
262
235
  ### Semantic Search
263
236
 
264
- Codegraph can build local embeddings for every function, method, and class, then search them by natural language. Everything runs locally using [@huggingface/transformers](https://huggingface.co/docs/transformers.js) — no API keys needed.
237
+ Local embeddings for every function, method, and class search by natural language. Everything runs locally using [@huggingface/transformers](https://huggingface.co/docs/transformers.js) — no API keys needed.
265
238
 
266
239
  ```bash
267
- codegraph embed # Build embeddings (default: minilm)
240
+ codegraph embed # Build embeddings (default: nomic-v1.5)
268
241
  codegraph embed --model nomic # Use a different model
269
242
  codegraph search "handle authentication"
270
243
  codegraph search "parse config" --min-score 0.4 -n 10
@@ -312,16 +285,6 @@ codegraph registry remove <name> # Unregister
312
285
 
313
286
  `codegraph build` auto-registers the project — no manual setup needed.
314
287
 
315
- ### AI Integration
316
-
317
- ```bash
318
- codegraph mcp # Start MCP server (single-repo, current project only)
319
- codegraph mcp --multi-repo # Enable access to all registered repos
320
- codegraph mcp --repos a,b # Restrict to specific repos (implies --multi-repo)
321
- ```
322
-
323
- By default, the MCP server only exposes the local project's graph. AI agents cannot access other repositories unless you explicitly opt in with `--multi-repo` or `--repos`.
324
-
325
288
  ### Common Flags
326
289
 
327
290
  | Flag | Description |
@@ -371,6 +334,16 @@ By default, the MCP server only exposes the local project's graph. AI agents can
371
334
  4. **Store** — Everything goes into SQLite as nodes + edges with tree-sitter node boundaries
372
335
  5. **Query** — All queries run locally against the SQLite DB — typically under 100ms
373
336
 
337
+ ### Incremental Rebuilds
338
+
339
+ The graph stays current without re-parsing your entire codebase. Three-tier change detection ensures rebuilds are proportional to what changed, not the size of the project:
340
+
341
+ 1. **Tier 0 — Journal (O(changed)):** If `codegraph watch` was running, a change journal records exactly which files were touched. The next build reads the journal and only processes those files — zero filesystem scanning
342
+ 2. **Tier 1 — mtime+size (O(n) stats, O(changed) reads):** No journal? Codegraph stats every file and compares mtime + size against stored values. Matching files are skipped without reading a single byte
343
+ 3. **Tier 2 — Hash (O(changed) reads):** Files that fail the mtime/size check are read and MD5-hashed. Only files whose hash actually changed get re-parsed and re-inserted
344
+
345
+ **Result:** change one file in a 3,000-file project and the rebuild completes in under a second. Put it in a commit hook, a file watcher, or let your AI agent trigger it.
346
+
374
347
  ### Dual Engine
375
348
 
376
349
  Codegraph ships with two parsing engines:
@@ -404,10 +377,10 @@ Self-measured on every release via CI ([full history](generated/BENCHMARKS.md)):
404
377
 
405
378
  | Metric | Latest |
406
379
  |---|---|
407
- | Build speed (native) | **2.5 ms/file** |
408
- | Build speed (WASM) | **5 ms/file** |
380
+ | Build speed (native) | **1.9 ms/file** |
381
+ | Build speed (WASM) | **6.6 ms/file** |
409
382
  | Query time | **1ms** |
410
- | ~50,000 files (est.) | **~125.0s build** |
383
+ | ~50,000 files (est.) | **~95.0s build** |
411
384
 
412
385
  Metrics are normalized per file for cross-version comparability. Times above are for a full initial build — incremental rebuilds only re-parse changed files.
413
386
 
@@ -419,8 +392,8 @@ Codegraph includes a built-in [Model Context Protocol](https://modelcontextproto
419
392
 
420
393
  ```bash
421
394
  codegraph mcp # Single-repo mode (default) — only local project
422
- codegraph mcp --multi-repo # Multi-repo all registered repos accessible
423
- codegraph mcp --repos a,b # Multi-repo with allowlist
395
+ codegraph mcp --multi-repo # Enable access to all registered repos
396
+ codegraph mcp --repos a,b # Restrict to specific repos (implies --multi-repo)
424
397
  ```
425
398
 
426
399
  **Single-repo mode (default):** Tools operate only on the local `.codegraph/graph.db`. The `repo` parameter and `list_repos` tool are not exposed to the AI agent.
@@ -591,17 +564,35 @@ const { results: fused } = await multiSearchData(
591
564
  - **Dynamic calls are best-effort** — complex computed property access and `eval` patterns are not resolved
592
565
  - **Python imports** — resolves relative imports but doesn't follow `sys.path` or virtual environment packages
593
566
 
567
+ ## 🔍 How Codegraph Compares
568
+
569
+ <sub>Last verified: February 2026. Full analysis: <a href="generated/COMPETITIVE_ANALYSIS.md">COMPETITIVE_ANALYSIS.md</a></sub>
570
+
571
+ | Capability | codegraph | [joern](https://github.com/joernio/joern) | [narsil-mcp](https://github.com/postrv/narsil-mcp) | [code-graph-rag](https://github.com/vitali87/code-graph-rag) | [cpg](https://github.com/Fraunhofer-AISEC/cpg) | [GitNexus](https://github.com/abhigyanpatwari/GitNexus) |
572
+ |---|:---:|:---:|:---:|:---:|:---:|:---:|
573
+ | Function-level analysis | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** |
574
+ | Multi-language | **11** | **14** | **32** | Multi | **~10** | **9** |
575
+ | Incremental rebuilds | **O(changed)** | — | O(n) Merkle | — | — | — |
576
+ | MCP / AI agent support | **Yes** | — | **Yes** | **Yes** | **Yes** | **Yes** |
577
+ | Git diff impact | **Yes** | — | — | — | — | **Yes** |
578
+ | Semantic search | **Yes** | — | **Yes** | **Yes** | — | **Yes** |
579
+ | Watch mode | **Yes** | — | **Yes** | — | — | — |
580
+ | Zero config, no Docker/JVM | **Yes** | — | **Yes** | — | — | — |
581
+ | Works without API keys | **Yes** | **Yes** | **Yes** | — | **Yes** | **Yes** |
582
+ | Commercial use (Apache/MIT) | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** | — |
583
+
594
584
  ## 🗺️ Roadmap
595
585
 
596
- See **[ROADMAP.md](ROADMAP.md)** for the full development roadmap. Current plan:
586
+ See **[ROADMAP.md](ROADMAP.md)** for the full development roadmap and **[STABILITY.md](STABILITY.md)** for the stability policy and versioning guarantees. Current plan:
597
587
 
598
588
  1. ~~**Rust Core**~~ — **Complete** (v1.3.0) — native tree-sitter parsing via napi-rs, parallel multi-core parsing, incremental re-parsing, import resolution & cycle detection in Rust
599
589
  2. ~~**Foundation Hardening**~~ — **Complete** (v1.4.0) — parser registry, 12-tool MCP server with multi-repo support, test coverage 62%→75%, `apiKeyCommand` secret resolution, global repo registry
600
- 3. **Intelligent Embeddings** — LLM-generated descriptions, hybrid search
601
- 4. **Natural Language Queries** — `codegraph ask` command, conversational sessions
602
- 5. **Expanded Language Support** — 8 new languages (12 → 20)
603
- 6. **GitHub Integration & CI** — reusable GitHub Action, PR review, SARIF output
604
- 7. **Visualization & Advanced** — web UI, dead code detection, monorepo support, agentic search
590
+ 3. **Architectural Refactoring** — parser plugin system, repository pattern, pipeline builder, engine strategy, domain errors, curated API
591
+ 4. **Intelligent Embeddings** — LLM-generated descriptions, hybrid search
592
+ 5. **Natural Language Queries** — `codegraph ask` command, conversational sessions
593
+ 6. **Expanded Language Support** — 8 new languages (12 20)
594
+ 7. **GitHub Integration & CI** — reusable GitHub Action, PR review, SARIF output
595
+ 8. **Visualization & Advanced** — web UI, dead code detection, monorepo support, agentic search
605
596
 
606
597
  ## 🤝 Contributing
607
598
 
@@ -623,5 +614,5 @@ Looking to add a new language? Check out **[Adding a New Language](docs/adding-a
623
614
  ---
624
615
 
625
616
  <p align="center">
626
- <sub>Built with <a href="https://tree-sitter.github.io/">tree-sitter</a> and <a href="https://github.com/WiseLibs/better-sqlite3">better-sqlite3</a>. Your code only goes where you choose to send it.</sub>
617
+ <sub>Built with <a href="https://tree-sitter.github.io/">tree-sitter</a> and <a href="https://github.com/WiseLibs/better-sqlite3">better-sqlite3</a>. Your code stays on your machine.</sub>
627
618
  </p>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optave/codegraph",
3
- "version": "2.2.1",
3
+ "version": "2.2.3-dev.44e8146",
4
4
  "description": "Local code graph CLI — parse codebases with tree-sitter, build dependency graphs, query them",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -61,10 +61,10 @@
61
61
  "optionalDependencies": {
62
62
  "@huggingface/transformers": "^3.8.1",
63
63
  "@modelcontextprotocol/sdk": "^1.0.0",
64
- "@optave/codegraph-darwin-arm64": "2.2.1",
65
- "@optave/codegraph-darwin-x64": "2.2.1",
66
- "@optave/codegraph-linux-x64-gnu": "2.2.1",
67
- "@optave/codegraph-win32-x64-msvc": "2.2.1"
64
+ "@optave/codegraph-darwin-arm64": "2.2.3-dev.44e8146",
65
+ "@optave/codegraph-darwin-x64": "2.2.3-dev.44e8146",
66
+ "@optave/codegraph-linux-x64-gnu": "2.2.3-dev.44e8146",
67
+ "@optave/codegraph-win32-x64-msvc": "2.2.3-dev.44e8146"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@biomejs/biome": "^2.4.4",
package/src/builder.js CHANGED
@@ -1,12 +1,11 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import fs from 'node:fs';
3
- import os from 'node:os';
4
3
  import path from 'node:path';
5
4
  import { loadConfig } from './config.js';
6
5
  import { EXTENSIONS, IGNORE_DIRS, normalizePath } from './constants.js';
7
6
  import { initSchema, openDb } from './db.js';
8
7
  import { readJournal, writeJournalHeader } from './journal.js';
9
- import { debug, warn } from './logger.js';
8
+ import { debug, info, warn } from './logger.js';
10
9
  import { getActiveEngine, parseFilesAuto } from './parser.js';
11
10
  import { computeConfidence, resolveImportPath, resolveImportsBatch } from './resolve.js';
12
11
 
@@ -43,8 +42,28 @@ const BUILTIN_RECEIVERS = new Set([
43
42
  'require',
44
43
  ]);
45
44
 
46
- export function collectFiles(dir, files = [], config = {}, directories = null) {
45
+ export function collectFiles(
46
+ dir,
47
+ files = [],
48
+ config = {},
49
+ directories = null,
50
+ _visited = new Set(),
51
+ ) {
47
52
  const trackDirs = directories !== null;
53
+
54
+ // Resolve real path to detect symlink loops
55
+ let realDir;
56
+ try {
57
+ realDir = fs.realpathSync(dir);
58
+ } catch {
59
+ return trackDirs ? { files, directories } : files;
60
+ }
61
+ if (_visited.has(realDir)) {
62
+ warn(`Symlink loop detected, skipping: ${dir}`);
63
+ return trackDirs ? { files, directories } : files;
64
+ }
65
+ _visited.add(realDir);
66
+
48
67
  let entries;
49
68
  try {
50
69
  entries = fs.readdirSync(dir, { withFileTypes: true });
@@ -67,7 +86,7 @@ export function collectFiles(dir, files = [], config = {}, directories = null) {
67
86
 
68
87
  const full = path.join(dir, entry.name);
69
88
  if (entry.isDirectory()) {
70
- collectFiles(full, files, config, directories);
89
+ collectFiles(full, files, config, directories, _visited);
71
90
  } else if (EXTENSIONS.has(path.extname(entry.name))) {
72
91
  files.push(full);
73
92
  hasFiles = true;
@@ -125,6 +144,28 @@ function fileStat(filePath) {
125
144
  }
126
145
  }
127
146
 
147
+ /**
148
+ * Read a file with retry on transient errors (EBUSY/EACCES/EPERM).
149
+ * Editors performing non-atomic saves can cause these during mid-write.
150
+ */
151
+ const TRANSIENT_CODES = new Set(['EBUSY', 'EACCES', 'EPERM']);
152
+ const RETRY_DELAY_MS = 50;
153
+
154
+ export function readFileSafe(filePath, retries = 2) {
155
+ for (let attempt = 0; ; attempt++) {
156
+ try {
157
+ return fs.readFileSync(filePath, 'utf-8');
158
+ } catch (err) {
159
+ if (attempt < retries && TRANSIENT_CODES.has(err.code)) {
160
+ const end = Date.now() + RETRY_DELAY_MS;
161
+ while (Date.now() < end) {}
162
+ continue;
163
+ }
164
+ throw err;
165
+ }
166
+ }
167
+ }
168
+
128
169
  /**
129
170
  * Determine which files have changed since last build.
130
171
  * Three-tier cascade:
@@ -193,7 +234,7 @@ function getChangedFiles(db, allFiles, rootDir) {
193
234
 
194
235
  let content;
195
236
  try {
196
- content = fs.readFileSync(absPath, 'utf-8');
237
+ content = readFileSafe(absPath);
197
238
  } catch {
198
239
  continue;
199
240
  }
@@ -256,7 +297,7 @@ function getChangedFiles(db, allFiles, rootDir) {
256
297
  for (const item of needsHash) {
257
298
  let content;
258
299
  try {
259
- content = fs.readFileSync(item.file, 'utf-8');
300
+ content = readFileSafe(item.file);
260
301
  } catch {
261
302
  continue;
262
303
  }
@@ -303,7 +344,7 @@ export async function buildGraph(rootDir, opts = {}) {
303
344
  // Engine selection: 'native', 'wasm', or 'auto' (default)
304
345
  const engineOpts = { engine: opts.engine || 'auto' };
305
346
  const { name: engineName, version: engineVersion } = getActiveEngine(engineOpts);
306
- console.log(`Using ${engineName} engine${engineVersion ? ` (v${engineVersion})` : ''}`);
347
+ info(`Using ${engineName} engine${engineVersion ? ` (v${engineVersion})` : ''}`);
307
348
 
308
349
  const aliases = loadPathAliases(rootDir);
309
350
  // Merge config aliases
@@ -316,7 +357,7 @@ export async function buildGraph(rootDir, opts = {}) {
316
357
  }
317
358
 
318
359
  if (aliases.baseUrl || Object.keys(aliases.paths).length > 0) {
319
- console.log(
360
+ info(
320
361
  `Loaded path aliases: baseUrl=${aliases.baseUrl || 'none'}, ${Object.keys(aliases.paths).length} path mappings`,
321
362
  );
322
363
  }
@@ -324,7 +365,7 @@ export async function buildGraph(rootDir, opts = {}) {
324
365
  const collected = collectFiles(rootDir, [], config, new Set());
325
366
  const files = collected.files;
326
367
  const discoveredDirs = collected.directories;
327
- console.log(`Found ${files.length} files to parse`);
368
+ info(`Found ${files.length} files to parse`);
328
369
 
329
370
  // Check for incremental build
330
371
  const { changed, removed, isFullBuild } = incremental
@@ -355,19 +396,36 @@ export async function buildGraph(rootDir, opts = {}) {
355
396
  /* ignore heal errors */
356
397
  }
357
398
  }
358
- console.log('No changes detected. Graph is up to date.');
399
+ info('No changes detected. Graph is up to date.');
359
400
  db.close();
360
401
  writeJournalHeader(rootDir, Date.now());
361
402
  return;
362
403
  }
363
404
 
405
+ // Check if embeddings table exists (created by `embed`, not by initSchema)
406
+ let hasEmbeddings = false;
407
+ try {
408
+ db.prepare('SELECT 1 FROM embeddings LIMIT 1').get();
409
+ hasEmbeddings = true;
410
+ } catch {
411
+ /* table doesn't exist */
412
+ }
413
+
364
414
  if (isFullBuild) {
415
+ const deletions =
416
+ 'PRAGMA foreign_keys = OFF; DELETE FROM node_metrics; DELETE FROM edges; DELETE FROM nodes; PRAGMA foreign_keys = ON;';
365
417
  db.exec(
366
- 'PRAGMA foreign_keys = OFF; DELETE FROM node_metrics; DELETE FROM edges; DELETE FROM nodes; PRAGMA foreign_keys = ON;',
418
+ hasEmbeddings
419
+ ? `${deletions.replace('PRAGMA foreign_keys = ON;', '')} DELETE FROM embeddings; PRAGMA foreign_keys = ON;`
420
+ : deletions,
367
421
  );
368
422
  } else {
369
- console.log(`Incremental: ${parseChanges.length} changed, ${removed.length} removed`);
370
- // Remove metrics/edges/nodes for changed and removed files
423
+ info(`Incremental: ${parseChanges.length} changed, ${removed.length} removed`);
424
+ // Remove embeddings/metrics/edges/nodes for changed and removed files
425
+ // Embeddings must be deleted BEFORE nodes (we need node IDs to find them)
426
+ const deleteEmbeddingsForFile = hasEmbeddings
427
+ ? db.prepare('DELETE FROM embeddings WHERE node_id IN (SELECT id FROM nodes WHERE file = ?)')
428
+ : null;
371
429
  const deleteNodesForFile = db.prepare('DELETE FROM nodes WHERE file = ?');
372
430
  const deleteEdgesForFile = db.prepare(`
373
431
  DELETE FROM edges WHERE source_id IN (SELECT id FROM nodes WHERE file = @f)
@@ -377,12 +435,14 @@ export async function buildGraph(rootDir, opts = {}) {
377
435
  'DELETE FROM node_metrics WHERE node_id IN (SELECT id FROM nodes WHERE file = ?)',
378
436
  );
379
437
  for (const relPath of removed) {
438
+ deleteEmbeddingsForFile?.run(relPath);
380
439
  deleteEdgesForFile.run({ f: relPath });
381
440
  deleteMetricsForFile.run(relPath);
382
441
  deleteNodesForFile.run(relPath);
383
442
  }
384
443
  for (const item of parseChanges) {
385
444
  const relPath = item.relPath || normalizePath(path.relative(rootDir, item.file));
445
+ deleteEmbeddingsForFile?.run(relPath);
386
446
  deleteEdgesForFile.run({ f: relPath });
387
447
  deleteMetricsForFile.run(relPath);
388
448
  deleteNodesForFile.run(relPath);
@@ -459,7 +519,7 @@ export async function buildGraph(rootDir, opts = {}) {
459
519
  const absPath = path.join(rootDir, relPath);
460
520
  let code;
461
521
  try {
462
- code = fs.readFileSync(absPath, 'utf-8');
522
+ code = readFileSafe(absPath);
463
523
  } catch {
464
524
  code = null;
465
525
  }
@@ -486,7 +546,7 @@ export async function buildGraph(rootDir, opts = {}) {
486
546
 
487
547
  const parsed = allSymbols.size;
488
548
  const skipped = filesToParse.length - parsed;
489
- console.log(`Parsed ${parsed} files (${skipped} skipped)`);
549
+ info(`Parsed ${parsed} files (${skipped} skipped)`);
490
550
 
491
551
  // Clean up removed file hashes
492
552
  if (upsertHash && removed.length > 0) {
@@ -780,15 +840,33 @@ export async function buildGraph(rootDir, opts = {}) {
780
840
  }
781
841
 
782
842
  const nodeCount = db.prepare('SELECT COUNT(*) as c FROM nodes').get().c;
783
- console.log(`Graph built: ${nodeCount} nodes, ${edgeCount} edges`);
784
- console.log(`Stored in ${dbPath}`);
843
+ info(`Graph built: ${nodeCount} nodes, ${edgeCount} edges`);
844
+ info(`Stored in ${dbPath}`);
845
+
846
+ // Warn about orphaned embeddings that no longer match any node
847
+ if (hasEmbeddings) {
848
+ try {
849
+ const orphaned = db
850
+ .prepare('SELECT COUNT(*) as c FROM embeddings WHERE node_id NOT IN (SELECT id FROM nodes)')
851
+ .get().c;
852
+ if (orphaned > 0) {
853
+ warn(
854
+ `${orphaned} embeddings are orphaned (nodes changed). Run "codegraph embed" to refresh.`,
855
+ );
856
+ }
857
+ } catch {
858
+ /* ignore — embeddings table may have been dropped */
859
+ }
860
+ }
861
+
785
862
  db.close();
786
863
 
787
864
  // Write journal header after successful build
788
865
  writeJournalHeader(rootDir, Date.now());
789
866
 
790
867
  if (!opts.skipRegistry) {
791
- const tmpDir = path.resolve(os.tmpdir());
868
+ const { tmpdir } = await import('node:os');
869
+ const tmpDir = path.resolve(tmpdir());
792
870
  const resolvedRoot = path.resolve(rootDir);
793
871
  if (resolvedRoot.startsWith(tmpDir)) {
794
872
  debug(`Skipping auto-registration for temp directory: ${resolvedRoot}`);