graph-indexer 1.0.2 → 1.1.0
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/PROMPT.md +143 -218
- package/README.md +179 -156
- package/indexer.mjs +4 -22
- package/init.mjs +179 -68
- package/mcp-server.mjs +10 -1
- package/package.json +14 -13
- package/parser-utils.mjs +83 -20
- package/watch-daemon.mjs +23 -19
package/README.md
CHANGED
|
@@ -14,31 +14,44 @@
|
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
-
##
|
|
17
|
+
## Table of Contents
|
|
18
|
+
|
|
19
|
+
- [What it does](#what-it-does)
|
|
20
|
+
- [Results](#results)
|
|
21
|
+
- [Getting started](#getting-started)
|
|
22
|
+
- [MCP tools](#mcp-tools)
|
|
23
|
+
- [Configuration](#configuration)
|
|
24
|
+
- [Supported languages](#supported-languages)
|
|
25
|
+
- [How it works](#how-it-works)
|
|
26
|
+
- [Best practices](#best-practices)
|
|
27
|
+
- [Troubleshooting](#troubleshooting)
|
|
28
|
+
- [Development](#development)
|
|
29
|
+
- [Security](#security)
|
|
30
|
+
- [License](#license)
|
|
18
31
|
|
|
19
|
-
|
|
32
|
+
---
|
|
20
33
|
|
|
21
|
-
|
|
34
|
+
## What it does
|
|
22
35
|
|
|
23
|
-
|
|
36
|
+
graph-indexer pre-indexes your codebase into an AST-precise search index and exposes it as an MCP server. Instead of reading full files, AI agents call `search_code("payment validation")` and get back the exact functions that match — with type signatures, docstrings, dependency graph, and call sites — in a fraction of the tokens.
|
|
24
37
|
|
|
25
|
-
|
|
38
|
+
It runs entirely on your machine. No external database, no cloud APIs, no telemetry.
|
|
26
39
|
|
|
27
|
-
|
|
40
|
+
**Token comparison:**
|
|
28
41
|
|
|
29
42
|
```
|
|
30
43
|
Agent task: "Add error handling to the payment processing flow"
|
|
31
44
|
|
|
32
45
|
Without graph-indexer:
|
|
33
|
-
readFile("src/payments/service.ts") →
|
|
34
|
-
readFile("src/payments/handlers.ts") →
|
|
35
|
-
readFile("src/types/payment.ts") →
|
|
36
|
-
Total
|
|
46
|
+
readFile("src/payments/service.ts") → 8,400 tokens
|
|
47
|
+
readFile("src/payments/handlers.ts") → 6,200 tokens
|
|
48
|
+
readFile("src/types/payment.ts") → 1,800 tokens
|
|
49
|
+
Total: ~ 16,400 tokens
|
|
37
50
|
|
|
38
51
|
With graph-indexer:
|
|
39
|
-
search_code("payment processing") →
|
|
40
|
-
get_chunk("chunk_id") →
|
|
41
|
-
Total
|
|
52
|
+
search_code("payment processing") → 650 tokens (5 exact chunks)
|
|
53
|
+
get_chunk("chunk_id") → 280 tokens (full function body)
|
|
54
|
+
Total: ~ 930 tokens
|
|
42
55
|
```
|
|
43
56
|
|
|
44
57
|
The difference compounds across a session. Agents that read files also lack topology — they can't see which other functions call the one they're modifying, so they miss side effects.
|
|
@@ -47,91 +60,40 @@ The difference compounds across a session. Agents that read files also lack topo
|
|
|
47
60
|
|
|
48
61
|
## Results
|
|
49
62
|
|
|
50
|
-
Measured across 5 production open-source codebases (7,503
|
|
51
|
-
|
|
52
|
-
### Token usage per query: with vs. without graph-indexer
|
|
63
|
+
Measured across 5 production open-source codebases (7,503 AST chunks, 70 ground-truth queries across 3 difficulty levels).
|
|
53
64
|
|
|
54
|
-
|
|
55
|
-
| :--- | :--- | ---: | ---: | ---: | ---: | ---: |
|
|
56
|
-
| Axios v1.6.0 | JavaScript | 111k tokens | ~2,290 tok/query | ~655 tok/query | **71.4%** | 0.2 ms |
|
|
57
|
-
| Express 4.18.2 | JavaScript | 145k tokens | ~7,330 tok/query | ~850 tok/query | **88.4%** | 0.2 ms |
|
|
58
|
-
| NestJS v10.4.9 | TypeScript | 700k tokens | ~2,535 tok/query | ~900 tok/query | **64.5%** | 0.7 ms |
|
|
59
|
-
| FastAPI 0.103.0 | Python | 859k tokens | ~4,030 tok/query | ~685 tok/query | **83.0%** | 1.0 ms |
|
|
60
|
-
| Gin v1.9.1 | Go | 131k tokens | ~2,530 tok/query | ~450 tok/query | **82.2%** | 0.4 ms |
|
|
61
|
-
| **Mean** | | | | | **77.9%** | **0.5 ms** |
|
|
65
|
+
### Token savings per query
|
|
62
66
|
|
|
63
|
-
|
|
67
|
+
| Project | Language | Without (full files) | With graph-indexer | Savings | Latency |
|
|
68
|
+
| :--- | :--- | ---: | ---: | ---: | ---: |
|
|
69
|
+
| Axios v1.6.0 | JavaScript | ~2,290 tok | ~655 tok | **71.4%** | 0.2 ms |
|
|
70
|
+
| Express 4.18.2 | JavaScript | ~7,330 tok | ~850 tok | **88.4%** | 0.2 ms |
|
|
71
|
+
| NestJS v10.4.9 | TypeScript | ~2,535 tok | ~900 tok | **64.5%** | 0.7 ms |
|
|
72
|
+
| FastAPI 0.103.0 | Python | ~4,030 tok | ~685 tok | **83.0%** | 1.0 ms |
|
|
73
|
+
| Gin v1.9.1 | Go | ~2,530 tok | ~450 tok | **82.2%** | 0.4 ms |
|
|
74
|
+
| **Mean** | | | | **77.9%** | **0.5 ms** |
|
|
64
75
|
|
|
65
|
-
|
|
66
|
-
- NestJS has 700k tokens of source — larger than most LLMs' full context window. graph-indexer reduces each query to ~900 tokens.
|
|
67
|
-
- Express shows 88.4% savings because its core router logic is spread across large files with much non-relevant code.
|
|
68
|
-
- Latency is sub-millisecond for all suites at their real corpus sizes.
|
|
76
|
+
> "Full files" = tokens in the source files containing the top-5 results. "With graph-indexer" = tokens in the returned chunk snippets only. Measured at 4 chars/token. NestJS is 700k tokens of source — larger than most LLMs' context windows.
|
|
69
77
|
|
|
70
78
|
### Search quality
|
|
71
79
|
|
|
72
|
-
| Project | Chunks | Recall@1 | Recall@3 | Recall@5 | MRR |
|
|
73
|
-
| :--- | ---: | ---: | ---: | ---: | ---: |
|
|
74
|
-
| Axios v1.6.0 | 435 | 0.86 | 1.00 | **1.00** | 0.92 |
|
|
75
|
-
| Express 4.18.2 | 389 | 0.93 | 0.93 | **1.00** | 0.95 |
|
|
76
|
-
| NestJS v10.4.9 | 2,019 | 1.00 | 1.00 | **1.00** | **1.00** |
|
|
77
|
-
| FastAPI 0.103.0 | 3,572 | 0.93 | 1.00 | **1.00** | 0.96 |
|
|
78
|
-
| Gin v1.9.1 | 1,088 | 0.85 | 1.00 | **1.00** | 0.92 |
|
|
79
|
-
| **Mean** | **7,503** | **0.91** | **0.99** | **1.00** | **0.95** |
|
|
80
|
+
| Project | Chunks | Recall@1 | Recall@3 | Recall@5 | MRR |
|
|
81
|
+
| :--- | ---: | ---: | ---: | ---: | ---: |
|
|
82
|
+
| Axios v1.6.0 | 435 | 0.86 | 1.00 | **1.00** | 0.92 |
|
|
83
|
+
| Express 4.18.2 | 389 | 0.93 | 0.93 | **1.00** | 0.95 |
|
|
84
|
+
| NestJS v10.4.9 | 2,019 | 1.00 | 1.00 | **1.00** | **1.00** |
|
|
85
|
+
| FastAPI 0.103.0 | 3,572 | 0.93 | 1.00 | **1.00** | 0.96 |
|
|
86
|
+
| Gin v1.9.1 | 1,088 | 0.85 | 1.00 | **1.00** | 0.92 |
|
|
87
|
+
| **Mean** | **7,503** | **0.91** | **0.99** | **1.00** | **0.95** |
|
|
80
88
|
|
|
81
|
-
|
|
82
|
-
- **MRR = 0.95** overall: on average, the correct answer is the first or second result.
|
|
83
|
-
- Queries span three difficulty levels: easy (exact symbol names), medium (multi-token descriptions), hard (semantic concepts with vocabulary mismatch).
|
|
84
|
-
|
|
85
|
-
---
|
|
86
|
-
|
|
87
|
-
## How it works
|
|
88
|
-
|
|
89
|
-
### Indexing
|
|
90
|
-
|
|
91
|
-
When you run `npm run mcp:index`, graph-indexer:
|
|
92
|
-
|
|
93
|
-
1. **Parses** every source file with Tree-sitter to build an AST — no regex, exact code boundaries.
|
|
94
|
-
2. **Extracts chunks**: named functions, classes, methods, and exports. Each chunk includes its name, docstring, parameters, return type, call sites, and type references.
|
|
95
|
-
3. **Builds a dependency graph**: bidirectional import map so each chunk knows what it imports and what imports it.
|
|
96
|
-
4. **Creates two search indexes**:
|
|
97
|
-
- A BM25 inverted index (lexical search, O(1) per-token lookup).
|
|
98
|
-
- A vector embedding per chunk via Ollama `nomic-embed-text` (optional; falls back to lexical-only if Ollama is unavailable).
|
|
99
|
-
5. **Serializes to disk**: `code-index.json` (metadata + BM25 index) and `code-index.embeddings.bin` (binary float32 vectors).
|
|
100
|
-
|
|
101
|
-
A background file watcher daemon (`watch-daemon.mjs`) re-indexes changed files automatically after the initial index is built.
|
|
102
|
-
|
|
103
|
-
### Search
|
|
104
|
-
|
|
105
|
-
When an agent calls `search_code("payment validation middleware")`:
|
|
106
|
-
|
|
107
|
-
1. **Lexical search** over the BM25 inverted index — returns candidates with BM25 scores in O(query_terms) time.
|
|
108
|
-
2. **Vector search** over pre-normalized float32 embeddings — returns semantically similar chunks.
|
|
109
|
-
3. **Reciprocal Rank Fusion (RRF)** merges both ranked lists with weights (lexical 1.5×, vector 1.0×).
|
|
110
|
-
4. **Scoring adjustments**: exact name match +2.0×, snake_case suffix match +1.4×, path token match +1.4×, test/example files demoted.
|
|
111
|
-
5. **Returns** top-k chunks with: code snippet, type signature, docstring, call graph, and dependency topology — all in one response.
|
|
112
|
-
|
|
113
|
-
### Architecture
|
|
114
|
-
|
|
115
|
-
```
|
|
116
|
-
Source files (.ts .js .py .go .rs …)
|
|
117
|
-
│
|
|
118
|
-
▼
|
|
119
|
-
Tree-sitter AST parser
|
|
120
|
-
│
|
|
121
|
-
├──► Named chunks (functions, classes, methods)
|
|
122
|
-
│ │
|
|
123
|
-
│ ├──► BM25 inverted index ──┐
|
|
124
|
-
│ └──► Ollama embeddings ──┤── RRF fusion ──► Ranked results
|
|
125
|
-
│ │
|
|
126
|
-
└──► Dependency graph ────────────────┘
|
|
127
|
-
(importedBy / imports) ▼
|
|
128
|
-
MCP tools for agents
|
|
129
|
-
```
|
|
89
|
+
**Recall@5 = 1.00** across every project: the correct answer is always in the top 5. **MRR = 0.95**: the correct answer is rank 1 or 2 on average.
|
|
130
90
|
|
|
131
91
|
---
|
|
132
92
|
|
|
133
93
|
## Getting started
|
|
134
94
|
|
|
95
|
+
**Requirements:** Node.js v18+ · Ollama (optional, for semantic search)
|
|
96
|
+
|
|
135
97
|
### 1. Install
|
|
136
98
|
|
|
137
99
|
```bash
|
|
@@ -139,9 +101,27 @@ npm install graph-indexer --save-dev
|
|
|
139
101
|
npx graph-indexer init
|
|
140
102
|
```
|
|
141
103
|
|
|
142
|
-
`init` auto-detects your IDE (Claude, Cursor, VS Code), adds npm scripts to `package.json`,
|
|
104
|
+
`init` auto-detects your IDE (Claude, Cursor, VS Code), adds npm scripts to `package.json`, updates `.gitignore`, and opens an interactive language selector:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
⚙️ Select languages (Arrows/Tab: move, Space: toggle, Enter: confirm):
|
|
108
|
+
|
|
109
|
+
❯ ◯ TypeScript / TSX .ts, .tsx
|
|
110
|
+
◯ JavaScript .js, .jsx, .mjs, .cjs
|
|
111
|
+
◯ Python .py
|
|
112
|
+
◯ Go .go
|
|
113
|
+
◯ Rust .rs
|
|
114
|
+
◯ PHP .php
|
|
115
|
+
◯ Java .java
|
|
116
|
+
◯ Kotlin .kt, .kts
|
|
117
|
+
◯ C# .cs
|
|
118
|
+
◯ Ruby .rb
|
|
119
|
+
◯ CSS / SCSS .css, .scss
|
|
120
|
+
```
|
|
143
121
|
|
|
144
|
-
|
|
122
|
+
Navigate with **↑ ↓**, toggle with **Space**, confirm with **Enter**. Leaving all unselected enables every language. Your selection is saved to `.graph-indexer.json`. Pass `--all-languages` to skip the prompt.
|
|
123
|
+
|
|
124
|
+
### 2. Index your codebase
|
|
145
125
|
|
|
146
126
|
**With semantic search (recommended):**
|
|
147
127
|
```bash
|
|
@@ -155,30 +135,32 @@ npm run mcp:index
|
|
|
155
135
|
INDEXER_EMBEDDINGS=off npm run mcp:index
|
|
156
136
|
```
|
|
157
137
|
|
|
158
|
-
Lexical-only still achieves Recall@5 = 1.00
|
|
138
|
+
Lexical-only still achieves Recall@5 = 1.00 on all benchmarks above.
|
|
159
139
|
|
|
160
140
|
### 3. Configure your IDE
|
|
161
141
|
|
|
142
|
+
`init` writes the config automatically. Manual examples:
|
|
143
|
+
|
|
162
144
|
**Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
163
145
|
```json
|
|
164
146
|
{
|
|
165
147
|
"mcpServers": {
|
|
166
148
|
"graph-indexer": {
|
|
167
|
-
"command": "
|
|
168
|
-
"args": ["/path/to/project
|
|
149
|
+
"command": "npm",
|
|
150
|
+
"args": ["run", "--prefix", "/path/to/project", "mcp:start"],
|
|
169
151
|
"env": { "MCP_PROJECT_ROOT": "/path/to/project" }
|
|
170
152
|
}
|
|
171
153
|
}
|
|
172
154
|
}
|
|
173
155
|
```
|
|
174
156
|
|
|
175
|
-
**Cursor** (`.cursor/mcp.json`):
|
|
157
|
+
**Cursor** (`.cursor/mcp.json`) and **Claude Code** (`.mcp.json`):
|
|
176
158
|
```json
|
|
177
159
|
{
|
|
178
160
|
"mcpServers": {
|
|
179
161
|
"graph-indexer": {
|
|
180
|
-
"command": "
|
|
181
|
-
"args": ["
|
|
162
|
+
"command": "npm",
|
|
163
|
+
"args": ["run", "mcp:start"],
|
|
182
164
|
"env": { "MCP_PROJECT_ROOT": "${workspaceFolder}" }
|
|
183
165
|
}
|
|
184
166
|
}
|
|
@@ -191,8 +173,9 @@ Lexical-only still achieves Recall@5 = 1.00 for the test suites above.
|
|
|
191
173
|
"servers": {
|
|
192
174
|
"graph-indexer": {
|
|
193
175
|
"type": "stdio",
|
|
194
|
-
"command": "
|
|
195
|
-
"args": ["
|
|
176
|
+
"command": "npm",
|
|
177
|
+
"args": ["run", "mcp:start"],
|
|
178
|
+
"env": { "MCP_PROJECT_ROOT": "${workspaceFolder}" }
|
|
196
179
|
}
|
|
197
180
|
}
|
|
198
181
|
}
|
|
@@ -200,7 +183,7 @@ Lexical-only still achieves Recall@5 = 1.00 for the test suites above.
|
|
|
200
183
|
|
|
201
184
|
### 4. Add the agent system prompt
|
|
202
185
|
|
|
203
|
-
Copy
|
|
186
|
+
Copy [PROMPT.md](./PROMPT.md) into your AI agent's system prompt to instruct it to use graph-indexer tools instead of reading files directly.
|
|
204
187
|
|
|
205
188
|
---
|
|
206
189
|
|
|
@@ -208,96 +191,88 @@ Copy the contents of [PROMPT.md](./PROMPT.md) into your AI agent's system prompt
|
|
|
208
191
|
|
|
209
192
|
### `search_code`
|
|
210
193
|
|
|
211
|
-
|
|
194
|
+
Hybrid BM25 + vector search over the index. The primary entry point for agents.
|
|
212
195
|
|
|
213
196
|
| Parameter | Type | Default | Description |
|
|
214
197
|
| :--- | :--- | :--- | :--- |
|
|
215
198
|
| `query` | string | required | Natural language description of the logic to find |
|
|
216
199
|
| `exact_tokens` | string? | — | Exact symbol name — guarantees rank-1 placement |
|
|
217
|
-
| `detail` | `"signatures"` \| `"smart"` \| `"full"` | `"smart"` |
|
|
200
|
+
| `detail` | `"signatures"` \| `"smart"` \| `"full"` | `"smart"` | How much code to return per result |
|
|
218
201
|
| `top_k` | number | `5` | Results to return (1–20) |
|
|
219
|
-
| `include_topology` | boolean | `true` | Include
|
|
220
|
-
| `min_score` | number | `0.3` | Minimum
|
|
221
|
-
| `token_budget` | number? | auto |
|
|
202
|
+
| `include_topology` | boolean | `true` | Include imports / used-by / calls |
|
|
203
|
+
| `min_score` | number | `0.3` | Minimum relevance threshold |
|
|
204
|
+
| `token_budget` | number? | auto | Cap total tokens returned |
|
|
222
205
|
|
|
223
206
|
**`detail` levels:**
|
|
224
207
|
|
|
225
|
-
| Value |
|
|
208
|
+
| Value | Cost | Returns |
|
|
226
209
|
| :--- | ---: | :--- |
|
|
227
|
-
| `"signatures"` | ~20
|
|
228
|
-
| `"smart"` (default) | ~150
|
|
229
|
-
| `"full"` | ~300
|
|
230
|
-
|
|
231
|
-
Start with `"signatures"`, escalate only when you need implementation details.
|
|
210
|
+
| `"signatures"` | ~20 tok | Name, type, params, return type, topology |
|
|
211
|
+
| `"smart"` (default) | ~150 tok | Signature + lines relevant to the query |
|
|
212
|
+
| `"full"` | ~300 tok | Signature + complete source body |
|
|
232
213
|
|
|
233
214
|
---
|
|
234
215
|
|
|
235
216
|
### `resolve_symbol`
|
|
236
217
|
|
|
237
|
-
|
|
218
|
+
O(1) lookup by exact name — no search ranking needed.
|
|
238
219
|
|
|
239
220
|
| Parameter | Type | Description |
|
|
240
221
|
| :--- | :--- | :--- |
|
|
241
222
|
| `symbol` | string | Exact function, class, or type name (e.g. `"validateToken"`) |
|
|
242
223
|
|
|
243
|
-
Returns: definition location, type signature, docstring, type references, and cross-file topology. Faster than `search_code` for known names.
|
|
244
|
-
|
|
245
224
|
---
|
|
246
225
|
|
|
247
226
|
### `get_chunk`
|
|
248
227
|
|
|
249
|
-
Returns the
|
|
228
|
+
Returns the source code of a single chunk by its ID.
|
|
250
229
|
|
|
251
230
|
| Parameter | Type | Default | Description |
|
|
252
231
|
| :--- | :--- | :--- | :--- |
|
|
253
|
-
| `chunk_id` | string | required | ID
|
|
254
|
-
| `view` | `"full"` \| `"signature"` | `"full"` |
|
|
232
|
+
| `chunk_id` | string | required | ID from `search_code` results |
|
|
233
|
+
| `view` | `"full"` \| `"signature"` | `"full"` | Full body or first line only |
|
|
255
234
|
|
|
256
235
|
---
|
|
257
236
|
|
|
258
237
|
### `get_chunk_summary`
|
|
259
238
|
|
|
260
|
-
|
|
239
|
+
Signature + docstring + calls without the body. ~50 tokens vs ~300 for full.
|
|
261
240
|
|
|
262
241
|
| Parameter | Type | Description |
|
|
263
242
|
| :--- | :--- | :--- |
|
|
264
243
|
| `chunk_id` | string | ID from `search_code` results |
|
|
265
244
|
|
|
266
|
-
Use when you need to understand what a function does without reading its implementation.
|
|
267
|
-
|
|
268
245
|
---
|
|
269
246
|
|
|
270
247
|
### `get_file_skeleton`
|
|
271
248
|
|
|
272
|
-
|
|
249
|
+
All top-level exports and definitions in a file with line numbers — no bodies.
|
|
273
250
|
|
|
274
251
|
| Parameter | Type | Description |
|
|
275
252
|
| :--- | :--- | :--- |
|
|
276
253
|
| `file_path` | string | Relative path (e.g. `src/utils/auth.ts`) |
|
|
277
254
|
|
|
278
|
-
Useful for understanding what a file exports before deciding which chunk to read.
|
|
279
|
-
|
|
280
255
|
---
|
|
281
256
|
|
|
282
257
|
### `get_call_graph`
|
|
283
258
|
|
|
284
|
-
|
|
259
|
+
Every chunk across the repo that calls a specific function.
|
|
285
260
|
|
|
286
261
|
| Parameter | Type | Description |
|
|
287
262
|
| :--- | :--- | :--- |
|
|
288
263
|
| `target_function` | string | Exact function name (e.g. `"validateToken"`) |
|
|
289
264
|
|
|
290
|
-
|
|
265
|
+
Call this before modifying any exported function to find all affected call sites.
|
|
291
266
|
|
|
292
267
|
---
|
|
293
268
|
|
|
294
269
|
### `get_repo_map`
|
|
295
270
|
|
|
296
|
-
|
|
271
|
+
Compact symbol map ordered by PageRank (most-imported files first). Orients agents in an unfamiliar codebase in ~1,500 tokens.
|
|
297
272
|
|
|
298
273
|
| Parameter | Type | Default | Description |
|
|
299
274
|
| :--- | :--- | :--- | :--- |
|
|
300
|
-
| `path_filter` | string? | — |
|
|
275
|
+
| `path_filter` | string? | — | Limit to files whose path contains this string |
|
|
301
276
|
| `max_files` | number | `80` | Maximum files to include |
|
|
302
277
|
| `sort_by` | `"importance"` \| `"path"` | `"importance"` | Sort order |
|
|
303
278
|
|
|
@@ -305,13 +280,13 @@ Returns a compact symbol map of the entire codebase ordered by importance (PageR
|
|
|
305
280
|
|
|
306
281
|
### `list_index_stats`
|
|
307
282
|
|
|
308
|
-
|
|
283
|
+
Index health snapshot: chunk count, file count, symbol table size, vector count, search mode, daemon status, index age.
|
|
309
284
|
|
|
310
285
|
---
|
|
311
286
|
|
|
312
287
|
### `graph://dependencies/{file_path}` (resource)
|
|
313
288
|
|
|
314
|
-
|
|
289
|
+
Full bidirectional dependency topology for a file: what it imports and what imports it.
|
|
315
290
|
|
|
316
291
|
---
|
|
317
292
|
|
|
@@ -321,68 +296,116 @@ Returns the full bidirectional dependency topology for a file: what it imports a
|
|
|
321
296
|
|
|
322
297
|
| Variable | Default | Description |
|
|
323
298
|
| :--- | :--- | :--- |
|
|
324
|
-
| `MCP_PROJECT_ROOT` | `process.cwd()` | Project root
|
|
299
|
+
| `MCP_PROJECT_ROOT` | `process.cwd()` | Project root directory |
|
|
325
300
|
| `OLLAMA_HOST` | `http://localhost:11434` | Ollama API endpoint |
|
|
326
301
|
| `INDEXER_EMBEDDINGS` | — | Set to `off` to disable vector embeddings |
|
|
327
302
|
|
|
328
|
-
###
|
|
303
|
+
### Language selection
|
|
329
304
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
305
|
+
`init` saves your selection to `.graph-indexer.json` in the project root:
|
|
306
|
+
|
|
307
|
+
```json
|
|
308
|
+
{ "languages": ["typescript", "javascript", "python"] }
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Valid keys: `typescript`, `javascript`, `python`, `go`, `rust`, `php`, `java`, `kotlin`, `csharp`, `ruby`, `css`.
|
|
312
|
+
|
|
313
|
+
Update at any time by re-running `init`:
|
|
333
314
|
|
|
334
|
-
|
|
335
|
-
|
|
315
|
+
```bash
|
|
316
|
+
npx graph-indexer init # Re-run interactive selector
|
|
317
|
+
npx graph-indexer init --all-languages # Enable all, no prompt
|
|
336
318
|
```
|
|
337
319
|
|
|
320
|
+
If `.graph-indexer.json` is absent or has no `languages` key, all installed parsers are used.
|
|
321
|
+
|
|
338
322
|
---
|
|
339
323
|
|
|
340
324
|
## Supported languages
|
|
341
325
|
|
|
342
|
-
| Language | Extensions |
|
|
326
|
+
| Language | Extensions | Chunks extracted |
|
|
343
327
|
| :--- | :--- | :--- |
|
|
344
328
|
| TypeScript / TSX | `.ts`, `.tsx` | functions, classes, methods, exports |
|
|
345
329
|
| JavaScript | `.js`, `.jsx`, `.mjs`, `.cjs` | functions, classes, expressions |
|
|
346
|
-
| Python | `.py` |
|
|
347
|
-
| Go | `.go` |
|
|
330
|
+
| Python | `.py` | functions, classes |
|
|
331
|
+
| Go | `.go` | functions, methods, types |
|
|
348
332
|
| Rust | `.rs` | fn, struct, enum, trait, impl |
|
|
349
333
|
| Java | `.java` | class, method, interface, constructor, enum |
|
|
350
334
|
| Kotlin | `.kt`, `.kts` | function, class, object, companion |
|
|
351
335
|
| C# | `.cs` | class, method, interface, property, enum |
|
|
352
336
|
| Ruby | `.rb` | method, class, module |
|
|
353
337
|
| PHP | `.php` | function, class |
|
|
354
|
-
| CSS / SCSS | `.css`, `.scss` |
|
|
338
|
+
| CSS / SCSS | `.css`, `.scss` | rule sets |
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## How it works
|
|
343
|
+
|
|
344
|
+
### Indexing
|
|
345
|
+
|
|
346
|
+
When you run `npm run mcp:index`:
|
|
347
|
+
|
|
348
|
+
1. **Parses** every source file with Tree-sitter — no regex, exact AST boundaries.
|
|
349
|
+
2. **Extracts chunks**: named functions, classes, methods, and exports, each with name, docstring, parameters, return type, call sites, and type references.
|
|
350
|
+
3. **Builds a dependency graph**: bidirectional import map so each chunk knows what it imports and what imports it.
|
|
351
|
+
4. **Creates two indexes**: a BM25 inverted index (lexical) and per-chunk float32 embeddings via Ollama `nomic-embed-text` (optional).
|
|
352
|
+
5. **Serializes to disk**: `code-index.json` + `code-index.embeddings.bin`.
|
|
353
|
+
|
|
354
|
+
A background watcher daemon re-indexes changed files automatically. It respects
|
|
355
|
+
`.gitignore` and skips `node_modules`, build output, and dot-directories, so it
|
|
356
|
+
never traverses (or exhausts OS file-watcher limits on) dependency trees.
|
|
357
|
+
|
|
358
|
+
### Search pipeline
|
|
359
|
+
|
|
360
|
+
```
|
|
361
|
+
Source files
|
|
362
|
+
│
|
|
363
|
+
▼
|
|
364
|
+
Tree-sitter AST
|
|
365
|
+
│
|
|
366
|
+
├──► Chunks ──► BM25 inverted index ──┐
|
|
367
|
+
│ └──► Float32 embeddings ──┤── RRF fusion ──► Ranked results ──► MCP tools
|
|
368
|
+
│ │
|
|
369
|
+
└──► Dependency graph ────────────────┘
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
1. **BM25 lexical search** — O(query_terms) over the inverted index.
|
|
373
|
+
2. **Vector search** — cosine similarity over pre-normalized float32 embeddings.
|
|
374
|
+
3. **RRF fusion** merges both lists (lexical 1.5×, vector 1.0×).
|
|
375
|
+
4. **Score boosts**: exact name match +2.0×, suffix match +1.4×, path token +1.4×, test files demoted.
|
|
355
376
|
|
|
356
377
|
---
|
|
357
378
|
|
|
358
|
-
##
|
|
379
|
+
## Best practices
|
|
380
|
+
|
|
381
|
+
**Write descriptive docstrings.** Docstrings are embedded alongside code — better documentation directly improves semantic search quality.
|
|
359
382
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
383
|
+
**Use specific function names.** Names like `fetchAndCacheUserProfile` trigger the exact-name boost (2.0×) and reliably appear as rank-1 results when queried by name.
|
|
384
|
+
|
|
385
|
+
**Export named functions, not anonymous ones.** Anonymous default exports can't be targeted by `resolve_symbol` and rank lower in name-boost scoring.
|
|
386
|
+
|
|
387
|
+
**Avoid catch-all utility files.** Files that mix unrelated utilities produce weaker per-chunk signals. One responsibility per module indexes more cleanly.
|
|
365
388
|
|
|
366
389
|
---
|
|
367
390
|
|
|
368
391
|
## Troubleshooting
|
|
369
392
|
|
|
370
|
-
**Index takes too long
|
|
393
|
+
**Index takes too long**
|
|
371
394
|
- Verify Ollama is running: `curl http://localhost:11434/api/tags`
|
|
372
|
-
-
|
|
395
|
+
- Use lexical-only mode: `INDEXER_EMBEDDINGS=off npm run mcp:index`
|
|
373
396
|
|
|
374
397
|
**Search returns irrelevant results**
|
|
375
398
|
- Use `exact_tokens` for known symbol names
|
|
376
|
-
-
|
|
399
|
+
- Check coverage with `list_index_stats()`
|
|
377
400
|
- Increase `top_k` to see more candidates
|
|
378
401
|
|
|
379
402
|
**MCP server won't connect**
|
|
380
403
|
- Verify `MCP_PROJECT_ROOT` points to the indexed directory
|
|
381
|
-
- Check
|
|
382
|
-
- Test
|
|
404
|
+
- Check `code-index.json` exists: `ls -la code-index.json`
|
|
405
|
+
- Test manually: `npm run mcp:start`
|
|
383
406
|
|
|
384
407
|
**Results are stale after file changes**
|
|
385
|
-
- The
|
|
408
|
+
- The daemon auto-updates; if stale, run `npm run mcp:index` manually
|
|
386
409
|
- Check daemon status with `list_index_stats()`
|
|
387
410
|
|
|
388
411
|
---
|
|
@@ -400,15 +423,15 @@ npm run mcp:start # start MCP server
|
|
|
400
423
|
|
|
401
424
|
---
|
|
402
425
|
|
|
403
|
-
##
|
|
404
|
-
|
|
405
|
-
**Write descriptive docstrings.** The indexer embeds docstrings alongside code. Better documentation directly improves semantic search quality.
|
|
406
|
-
|
|
407
|
-
**Use specific function names.** Functions with exact, descriptive names (e.g. `fetchAndCacheUserProfile`) hit the exact-name boost (2.0×) and return as rank-1 results when queried by name.
|
|
426
|
+
## Security
|
|
408
427
|
|
|
409
|
-
|
|
428
|
+
graph-indexer runs locally and is air-gapped by default — its only outbound call
|
|
429
|
+
is to a local Ollama endpoint for embeddings (skipped entirely with
|
|
430
|
+
`INDEXER_EMBEDDINGS=off`). It never executes the code it indexes, and the index
|
|
431
|
+
artifacts contain source snippets, so keep them git-ignored (as `init` configures).
|
|
410
432
|
|
|
411
|
-
|
|
433
|
+
See [SECURITY.md](SECURITY.md) for the full threat model and how to report a
|
|
434
|
+
vulnerability.
|
|
412
435
|
|
|
413
436
|
---
|
|
414
437
|
|
package/indexer.mjs
CHANGED
|
@@ -19,7 +19,7 @@ import fs from 'fs';
|
|
|
19
19
|
import path from 'path';
|
|
20
20
|
import {
|
|
21
21
|
MAX_FILE_SIZE_BYTES, EXTENSIONS, getParserForFile, buildIgnoreFilter, getLocalEmbeddingsBatch,
|
|
22
|
-
extractImportsFromAST, extractSemanticChunks, resolveLocalImports
|
|
22
|
+
extractImportsFromAST, extractSemanticChunks, resolveLocalImports, buildEmbeddingPayload
|
|
23
23
|
} from './parser-utils.mjs';
|
|
24
24
|
import { MemoryGraphIndex, writeEmbeddingBinary } from './core-engine.mjs';
|
|
25
25
|
|
|
@@ -113,27 +113,9 @@ async function main() {
|
|
|
113
113
|
console.time("Embedding Generation Duration");
|
|
114
114
|
|
|
115
115
|
const worker = async (batch) => {
|
|
116
|
-
const textsToEmbed = batch.map(c =>
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const depsFile = path.relative(PROJECT_ROOT, d);
|
|
120
|
-
const chunkMatches = indexData.chunks.filter(c => c.file_path === depsFile);
|
|
121
|
-
return chunkMatches.map(c => c.name).join(' ');
|
|
122
|
-
});
|
|
123
|
-
const topologicalContext = cleanDeps.length > 0
|
|
124
|
-
? `This code architectural neighborhood connects with: ${cleanDeps.join(', ')}.`
|
|
125
|
-
: '';
|
|
126
|
-
|
|
127
|
-
return [
|
|
128
|
-
`File Location: ${c.file_path}`,
|
|
129
|
-
`Symbol Name: ${c.node_type} -> ${c.name}`,
|
|
130
|
-
c.docstring ? `Developer Documentation: ${c.docstring}` : '',
|
|
131
|
-
c.type_refs?.length ? `Type References: ${c.type_refs.join(', ')}` : '',
|
|
132
|
-
topologicalContext,
|
|
133
|
-
`--- Source Code ---`,
|
|
134
|
-
c.code_snippet
|
|
135
|
-
].filter(Boolean).join('\n');
|
|
136
|
-
});
|
|
116
|
+
const textsToEmbed = batch.map(c =>
|
|
117
|
+
buildEmbeddingPayload(c, indexData.graph.dependencies[c.file_path] || [])
|
|
118
|
+
);
|
|
137
119
|
|
|
138
120
|
const embeddingsMatrix = await getLocalEmbeddingsBatch(textsToEmbed, true);
|
|
139
121
|
if (embeddingsMatrix && embeddingsMatrix.length === batch.length) {
|