@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 +117 -126
- package/package.json +5 -5
- package/src/builder.js +96 -18
- package/src/cli.js +85 -25
- package/src/config.js +1 -0
- package/src/embedder.js +196 -15
- package/src/export.js +16 -7
- package/src/extractors/javascript.js +6 -8
- package/src/index.js +3 -0
- package/src/mcp.js +21 -7
- package/src/queries.js +222 -18
- package/src/structure.js +2 -1
- package/src/watcher.js +2 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<h1 align="center">codegraph</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<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="
|
|
21
|
-
<a href="
|
|
22
|
-
<a href="#-
|
|
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="#-
|
|
27
|
-
<a href="#-
|
|
28
|
-
<a href="#-roadmap">Roadmap</a>
|
|
29
|
-
<a href="#-contributing">Contributing</a>
|
|
19
|
+
<a href="#the-problem">The Problem</a> ·
|
|
20
|
+
<a href="#what-codegraph-does">What It Does</a> ·
|
|
21
|
+
<a href="#-quick-start">Quick Start</a> ·
|
|
22
|
+
<a href="#-commands">Commands</a> ·
|
|
23
|
+
<a href="#-language-support">Languages</a> ·
|
|
24
|
+
<a href="#-ai-agent-integration">AI Integration</a> ·
|
|
25
|
+
<a href="#-how-it-works">How It Works</a> ·
|
|
26
|
+
<a href="#-recommended-practices">Practices</a> ·
|
|
27
|
+
<a href="#-roadmap">Roadmap</a>
|
|
30
28
|
</p>
|
|
31
29
|
|
|
32
30
|
---
|
|
33
31
|
|
|
34
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
40
|
+
And when you hit `/clear` or run out of context? It starts from scratch.
|
|
45
41
|
|
|
46
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
60
|
+
### Why it matters
|
|
68
61
|
|
|
69
|
-
|
|
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
|
|
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
|
|
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/)
|
|
97
|
-
| **🌐** | **Multi-language, one CLI** | JS/TS + Python + Go + Rust + Java + C# + PHP + Ruby + HCL in a single graph
|
|
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
|
|
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
|
|
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) · [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
|
-
|
|
|
169
|
+
| ⚡ | **Always fresh** | Three-tier incremental detection — sub-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
|
-
|
|
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:
|
|
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) | **
|
|
408
|
-
| Build speed (WASM) | **
|
|
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.) | **~
|
|
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 #
|
|
423
|
-
codegraph mcp --repos a,b #
|
|
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. **
|
|
601
|
-
4. **
|
|
602
|
-
5. **
|
|
603
|
-
6. **
|
|
604
|
-
7. **
|
|
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
|
|
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.
|
|
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.
|
|
65
|
-
"@optave/codegraph-darwin-x64": "2.2.
|
|
66
|
-
"@optave/codegraph-linux-x64-gnu": "2.2.
|
|
67
|
-
"@optave/codegraph-win32-x64-msvc": "2.2.
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
418
|
+
hasEmbeddings
|
|
419
|
+
? `${deletions.replace('PRAGMA foreign_keys = ON;', '')} DELETE FROM embeddings; PRAGMA foreign_keys = ON;`
|
|
420
|
+
: deletions,
|
|
367
421
|
);
|
|
368
422
|
} else {
|
|
369
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
784
|
-
|
|
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
|
|
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}`);
|