graph-indexer 1.1.0 → 2.0.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +412 -0
  2. package/README.md +228 -350
  3. package/SECURITY.md +68 -0
  4. package/TRADEMARK.md +31 -0
  5. package/cli-ui.mjs +78 -0
  6. package/config.mjs +269 -0
  7. package/daemon-ctl.mjs +175 -0
  8. package/daemon-lock.mjs +69 -0
  9. package/embedders/python/mlx_embed_server.py +61 -0
  10. package/embedders/python/requirements-mlx.txt +5 -0
  11. package/embedders/setup-mlx.mjs +110 -0
  12. package/embeddings.mjs +489 -0
  13. package/engine/binary.mjs +399 -0
  14. package/{core-engine.mjs → engine/memory.mjs} +359 -249
  15. package/engine/sqlite.mjs +863 -0
  16. package/enrichment.mjs +426 -0
  17. package/git-signals.mjs +248 -0
  18. package/indexer.mjs +216 -77
  19. package/init.mjs +1604 -170
  20. package/layout.mjs +123 -0
  21. package/mcp/format.mjs +61 -0
  22. package/mcp/tools.mjs +1157 -0
  23. package/mcp/topology.mjs +551 -0
  24. package/mcp-server.mjs +118 -573
  25. package/package.json +56 -16
  26. package/parse/extractor.mjs +617 -0
  27. package/parse/imports.mjs +365 -0
  28. package/parse/languages.mjs +89 -0
  29. package/parse/metadata.mjs +655 -0
  30. package/parse/routes.mjs +327 -0
  31. package/prompts/CORE.md +86 -0
  32. package/prompts/DOMAIN_TEMPLATE.md +46 -0
  33. package/prompts/INTEGRATION.md +123 -0
  34. package/prompts/frameworks/ANDROID.md +17 -0
  35. package/prompts/frameworks/ASPNET_CORE.md +19 -0
  36. package/prompts/frameworks/FASTAPI_DJANGO.md +19 -0
  37. package/prompts/frameworks/LARAVEL_SYMFONY.md +22 -0
  38. package/prompts/frameworks/NODE_EXPRESS_NESTJS.md +18 -0
  39. package/prompts/frameworks/RAILS.md +20 -0
  40. package/prompts/frameworks/REACT.md +20 -0
  41. package/prompts/frameworks/SPRING_BOOT.md +19 -0
  42. package/prompts/languages/BASH.md +22 -0
  43. package/prompts/languages/C.md +21 -0
  44. package/prompts/languages/CSHARP.md +24 -0
  45. package/prompts/languages/CSS_SCSS.md +22 -0
  46. package/prompts/languages/GO.md +23 -0
  47. package/prompts/languages/JAVA.md +23 -0
  48. package/prompts/languages/JAVASCRIPT_TYPESCRIPT.md +24 -0
  49. package/prompts/languages/KOTLIN.md +24 -0
  50. package/prompts/languages/PHP.md +23 -0
  51. package/prompts/languages/PYTHON.md +23 -0
  52. package/prompts/languages/RUBY.md +23 -0
  53. package/prompts/languages/RUST.md +21 -0
  54. package/prompts/languages/SWIFT.md +21 -0
  55. package/search-core.mjs +627 -0
  56. package/storage.mjs +68 -0
  57. package/watch-daemon.mjs +204 -69
  58. package/PROMPT.md +0 -143
  59. package/parser-utils.mjs +0 -861
package/README.md CHANGED
@@ -1,444 +1,322 @@
1
- # graph-indexer
1
+ <div align="center">
2
+ <img src="https://raw.githubusercontent.com/MaquinaTech/graph-indexer/main/assets/logo.jpg" alt="Graph Indexer Logo" width="250" />
2
3
 
3
- <p align="center">
4
- <strong>Zero-Database · Air-Gapped · AST-Precise · Hybrid Search</strong><br>
5
- <em>An MCP server that lets AI coding agents search your codebase by concept instead of reading files — reducing context usage by 65–88% per query.</em>
6
- </p>
4
+ <h1>Graph Indexer</h1>
7
5
 
8
- <p align="center">
9
- <a href="https://www.npmjs.com/package/graph-indexer"><img src="https://img.shields.io/npm/v/graph-indexer?color=blue&style=flat-square" alt="npm version"></a>
10
- <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square" alt="License: MIT"></a>
11
- <a href="https://nodejs.org"><img src="https://img.shields.io/badge/Node.js-%3E%3D18-green?style=flat-square" alt="Node.js 18+"></a>
12
- <a href="#results"><img src="https://img.shields.io/badge/Recall@5-100%25-brightgreen?style=flat-square" alt="Recall@5 100%"></a>
13
- </p>
6
+ <p>
7
+ <em>A local MCP server that gives AI coding agents an AST-precise, topology-aware index of your codebase.</em>
8
+ </p>
14
9
 
15
- ---
16
-
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)
10
+ <a href="https://www.npmjs.com/package/graph-indexer"><img src="https://img.shields.io/npm/v/graph-indexer?color=007acc&style=for-the-badge" alt="npm version"></a>
11
+ <a href="https://www.npmjs.com/package/graph-indexer"><img src="https://img.shields.io/npm/dt/graph-indexer?color=4caf50&style=for-the-badge" alt="NPM Downloads"></a>
12
+ <a href="https://nodejs.org"><img src="https://img.shields.io/badge/Node.js-%3E%3D18-brightgreen?style=for-the-badge&logo=nodedotjs" alt="Node.js 18+"></a>
13
+ <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge" alt="License: MIT"></a>
14
+ </div>
31
15
 
32
- ---
16
+ <br />
33
17
 
34
18
  ## What it does
35
19
 
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.
37
-
38
- It runs entirely on your machine. No external database, no cloud APIs, no telemetry.
39
-
40
- **Token comparison:**
41
-
42
- ```
43
- Agent task: "Add error handling to the payment processing flow"
44
-
45
- Without graph-indexer:
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
50
-
51
- With graph-indexer:
52
- search_code("payment processing") → 650 tokens (5 exact chunks)
53
- get_chunk("chunk_id") → 280 tokens (full function body)
54
- Total: ~ 930 tokens
55
- ```
56
-
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.
58
-
59
- ---
20
+ Graph Indexer is a local [Model Context Protocol](https://modelcontextprotocol.io) server that builds an AST-precise index of your repository with Tree-sitter and serves it to AI coding agents. Instead of grepping text or embedding whole files, it indexes *semantic chunks* — functions, classes, methods — and the call graph and import topology that connect them, so an agent can find the right symbol, see every caller and dependency that would be affected by a change, and resolve references across files. It runs entirely on your machine: the default search path is lexical (BM25 + morphological stemming) and needs no model, no daemon, and no network. Dense vector embeddings, LLM enrichment, and an LLM reranker are all available but off by default — you opt into each one when you have a measured reason to.
60
21
 
61
- ## Results
22
+ **Why it matters for AI coding agents:**
62
23
 
63
- Measured across 5 production open-source codebases (7,503 AST chunks, 70 ground-truth queries across 3 difficulty levels).
24
+ - **Up to 98.2% Token Savings:** Delivers the exact AST chunk needed instead of dumping 1M token context files.
25
+ - **Blast radius before every edit.** `get_call_graph` and `find_references` surface every caller, subclass, and dependency a change would touch, so the agent reasons about impact on code it never opened.
26
+ - **Private by default.** Everything runs locally; the default path makes zero network calls and needs no model — your code never leaves your machine.
27
+ - **One command, any language.** Guided setup wires your editors in seconds, and the zero-dependency lexical engine indexes 14 languages out of the box.
64
28
 
65
- ### Token savings per query
29
+ ## 📦 Prerequisites
66
30
 
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** |
31
+ * **Node.js** 18+ (22+ recommended if you hit >15k chunks for automatic SQLite scaling).
32
+ * **OS:** Agnostic. macOS Apple Silicon required only for the optional MLX GPU acceleration.
33
+ * **Optional:** Ollama for embeddings, enrichment, and reranking (see [Ollama setup](#ollama-setup)).
34
+ * **Optional:** Python 3.10+ for the MLX embedder (macOS Apple Silicon only).
75
35
 
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.
36
+ ## Quick start
77
37
 
78
- ### Search quality
79
-
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** |
88
-
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.
90
-
91
- ---
92
-
93
- ## Getting started
94
-
95
- **Requirements:** Node.js v18+ · Ollama (optional, for semantic search)
96
-
97
- ### 1. Install
38
+ ### 1. Run the Interactive Indexer
39
+ Go to your project root (works for Python, Go, Rust, TS/JS, C#, and 9 more languages) and run:
98
40
 
99
41
  ```bash
100
- npm install graph-indexer --save-dev
101
- npx graph-indexer init
42
+ npx graph-indexer /path/to/your/repo
102
43
  ```
103
44
 
104
- `init` auto-detects your IDE (Claude, Cursor, VS Code), adds npm scripts to `package.json`, updates `.gitignore`, and opens an interactive language selector:
45
+ That runs the [guided setup](#guided-setup) against that repo, which leaves it ready to use. It is idempotent — re-run it whenever you add a language or change a setting; it merges into what you already have and never clobbers another tool's config. Every generated file lands in `.graph-indexer/` (git-ignored), so your repo root stays clean.
105
46
 
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
- ```
47
+ ### 2. Connect Your Agent
121
48
 
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.
49
+ **If you ran the guided setup and selected your agent in step 4, this is already done — skip ahead.**
123
50
 
124
- ### 2. Index your codebase
51
+ The guided setup writes the MCP config automatically (with absolute paths that survive GUI launches) for **Claude Code** (`.mcp.json`), **Claude Desktop**, **Cursor** (`.cursor/mcp.json`), and **VS Code** (`.vscode/mcp.json`). Re-run `npx graph-indexer /path/to/your/repo` at any time to add or refresh a client.
125
52
 
126
- **With semantic search (recommended):**
127
- ```bash
128
- # Install Ollama: https://ollama.ai
129
- ollama pull nomic-embed-text
130
- npm run mcp:index
131
- ```
53
+ <details>
54
+ <summary>Manual wiring (if you skipped setup or use a client not listed above)</summary>
55
+
56
+ #### Claude Code — CLI
132
57
 
133
- **Lexical-only (no Ollama required):**
134
58
  ```bash
135
- INDEXER_EMBEDDINGS=off npm run mcp:index
59
+ claude mcp add graph-indexer -- npx -y -p graph-indexer idx-mcp --repo /path/to/your/repo
136
60
  ```
137
61
 
138
- Lexical-only still achieves Recall@5 = 1.00 on all benchmarks above.
139
-
140
- ### 3. Configure your IDE
62
+ #### Cursor — `.cursor/mcp.json`
141
63
 
142
- `init` writes the config automatically. Manual examples:
143
-
144
- **Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
145
64
  ```json
146
65
  {
147
66
  "mcpServers": {
148
67
  "graph-indexer": {
149
- "command": "npm",
150
- "args": ["run", "--prefix", "/path/to/project", "mcp:start"],
151
- "env": { "MCP_PROJECT_ROOT": "/path/to/project" }
68
+ "command": "npx",
69
+ "args": ["-y", "-p", "graph-indexer", "idx-mcp", "--repo", "/path/to/your/repo"]
152
70
  }
153
71
  }
154
72
  }
155
73
  ```
156
74
 
157
- **Cursor** (`.cursor/mcp.json`) and **Claude Code** (`.mcp.json`):
75
+ #### VS Code — `.vscode/mcp.json`
76
+
158
77
  ```json
159
78
  {
160
- "mcpServers": {
79
+ "servers": {
161
80
  "graph-indexer": {
162
- "command": "npm",
163
- "args": ["run", "mcp:start"],
164
- "env": { "MCP_PROJECT_ROOT": "${workspaceFolder}" }
81
+ "command": "npx",
82
+ "args": ["-y", "-p", "graph-indexer", "idx-mcp", "--repo", "/path/to/your/repo"]
165
83
  }
166
84
  }
167
85
  }
168
86
  ```
169
87
 
170
- **VS Code** (`.vscode/mcp.json`):
88
+ #### Claude Desktop — `claude_desktop_config.json`
89
+
171
90
  ```json
172
91
  {
173
- "servers": {
92
+ "mcpServers": {
174
93
  "graph-indexer": {
175
- "type": "stdio",
176
- "command": "npm",
177
- "args": ["run", "mcp:start"],
178
- "env": { "MCP_PROJECT_ROOT": "${workspaceFolder}" }
94
+ "command": "npx",
95
+ "args": ["-y", "-p", "graph-indexer", "idx-mcp", "--repo", "/path/to/your/repo"]
179
96
  }
180
97
  }
181
98
  }
182
99
  ```
183
100
 
184
- ### 4. Add the agent system prompt
185
-
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.
187
-
188
- ---
189
-
190
- ## MCP tools
191
-
192
- ### `search_code`
193
-
194
- Hybrid BM25 + vector search over the index. The primary entry point for agents.
195
-
196
- | Parameter | Type | Default | Description |
197
- | :--- | :--- | :--- | :--- |
198
- | `query` | string | required | Natural language description of the logic to find |
199
- | `exact_tokens` | string? | — | Exact symbol name — guarantees rank-1 placement |
200
- | `detail` | `"signatures"` \| `"smart"` \| `"full"` | `"smart"` | How much code to return per result |
201
- | `top_k` | number | `5` | Results to return (1–20) |
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 |
205
-
206
- **`detail` levels:**
207
-
208
- | Value | Cost | Returns |
209
- | :--- | ---: | :--- |
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 |
213
-
214
- ---
215
-
216
- ### `resolve_symbol`
217
-
218
- O(1) lookup by exact name — no search ranking needed.
219
-
220
- | Parameter | Type | Description |
221
- | :--- | :--- | :--- |
222
- | `symbol` | string | Exact function, class, or type name (e.g. `"validateToken"`) |
223
-
224
- ---
225
-
226
- ### `get_chunk`
227
-
228
- Returns the source code of a single chunk by its ID.
229
-
230
- | Parameter | Type | Default | Description |
231
- | :--- | :--- | :--- | :--- |
232
- | `chunk_id` | string | required | ID from `search_code` results |
233
- | `view` | `"full"` \| `"signature"` | `"full"` | Full body or first line only |
234
-
235
- ---
236
-
237
- ### `get_chunk_summary`
101
+ > **Note:** The `-p graph-indexer idx-mcp` form is required — `npx graph-indexer` runs the setup wizard, not the MCP server. If `npx` isn't on your GUI PATH (common on macOS when launching editors from Finder/Dock), run the guided setup instead — it writes absolute paths that always work.
238
102
 
239
- Signature + docstring + calls without the body. ~50 tokens vs ~300 for full.
103
+ </details>
240
104
 
241
- | Parameter | Type | Description |
242
- | :--- | :--- | :--- |
243
- | `chunk_id` | string | ID from `search_code` results |
105
+ Once connected, the agent can call `search_code`. A query like `search_code("rate limiting middleware")` returns ranked semantic chunks, not whole files:
244
106
 
245
- ---
246
-
247
- ### `get_file_skeleton`
248
-
249
- All top-level exports and definitions in a file with line numbers — no bodies.
250
-
251
- | Parameter | Type | Description |
252
- | :--- | :--- | :--- |
253
- | `file_path` | string | Relative path (e.g. `src/utils/auth.ts`) |
254
-
255
- ---
256
-
257
- ### `get_call_graph`
258
-
259
- Every chunk across the repo that calls a specific function.
107
+ ```jsonc
108
+ [
109
+ {
110
+ "score": 8.41,
111
+ "chunk": {
112
+ "id": "src/middleware/rateLimit.ts:14",
113
+ "file_path": "src/middleware/rateLimit.ts",
114
+ "name": "rateLimiter",
115
+ "node_type": "function_declaration",
116
+ "start_line": 14, "end_line": 47,
117
+ "calls": ["tokenBucket", "getClientKey"],
118
+ "class_context": ""
119
+ }
120
+ }
121
+ ]
122
+ ```
260
123
 
261
- | Parameter | Type | Description |
262
- | :--- | :--- | :--- |
263
- | `target_function` | string | Exact function name (e.g. `"validateToken"`) |
124
+ The agent can then call `get_call_graph("rateLimiter")` to see what calls it (the blast radius) before changing it.
264
125
 
265
- Call this before modifying any exported function to find all affected call sites.
126
+ ## Guided setup
266
127
 
267
- ---
128
+ `npx graph-indexer <path>` runs an interactive wizard that leaves the repo ready to use. It is **idempotent** — re-run it whenever you add a language or change a setting; it merges into what you already have and never clobbers another tool's config. Every generated file lands in `.graph-indexer/` (git-ignored), so your repo root stays clean.
268
129
 
269
- ### `get_repo_map`
130
+ Before the steps, it scans the repo to pre-select your stack — languages from a bounded file walk, frameworks from your manifests (`package.json`, `pyproject.toml`, `composer.json`, `pom.xml`, `Gemfile`, `*.csproj`, …) — and, if it finds a pre-v1.4 layout, tidies those stray artifacts into `.graph-indexer/` first. Then six steps:
270
131
 
271
- Compact symbol map ordered by PageRank (most-imported files first). Orients agents in an unfamiliar codebase in ~1,500 tokens.
132
+ | Step | You choose | What happens |
133
+ |------|------------|--------------|
134
+ | **1 · Languages** | Languages to index | Detected languages are pre-checked — press Enter to accept. Selecting none indexes **all** supported languages. |
135
+ | **2 · Frameworks** | Prompt add-ons | Filtered to your languages and pre-checked from detection. Sharpens the agent prompt for React, Express/NestJS, FastAPI/Django, Spring, Rails, Laravel/Symfony, ASP.NET, or Android. |
136
+ | **3 · Search engine & LLM** | Retrieval engine | **Press Enter for the recommended default** — lexical search, no LLM, no network. Everything heavier is opt-in (see below). |
137
+ | **4 · Agents & IDEs** | Your coding tools | Multi-select which agents to wire: Claude Code/Desktop, Cursor, VS Code/Copilot, Windsurf, Cline/Roo, JetBrains Junie, Codex (AGENTS.md), Gemini CLI. Pre-checked from your saved choice, else what's detected, else all. Drives steps 5 **and** 7 — deselect a tool and graph-indexer generates nothing for it. |
138
+ | **5 · MCP server wiring** | *(automatic)* | Wires the MCP server for each **selected** editor (VS Code, Cursor, Claude Desktop, Claude Code). **Merge-safe**: your other MCP servers and keys are preserved. |
139
+ | **6 · Project files & daemon** | *(automatic)* | Adds `mcp:*` npm scripts (index + daemon control), a managed `.gitignore` block, and `.graph-indexer/config.json` (which remembers your agent selection). |
140
+ | **7 · Agent instructions** | *(automatic)* | Always writes the canonical layered prompt (`GRAPH_INDEXER_PROMPT.md`) + a `GRAPH_INDEXER_DOMAIN.md` template for your own rules. Then, for each **selected** agent: `@`-imports (no duplication) in `CLAUDE.md` and `GEMINI.md`; rule files for Cursor, Windsurf, and Cline/Roo; and managed blocks in `.github/copilot-instructions.md`, `.junie/guidelines.md`, and `AGENTS.md` (Codex/Zed/Jules) — preserving anything already there. |
272
141
 
273
- | Parameter | Type | Default | Description |
274
- | :--- | :--- | :--- | :--- |
275
- | `path_filter` | string? | — | Limit to files whose path contains this string |
276
- | `max_files` | number | `80` | Maximum files to include |
277
- | `sort_by` | `"importance"` \| `"path"` | `"importance"` | Sort order |
142
+ It finishes with a grouped summary (created / updated / kept / skipped / warnings), offers to **build the index now**, and prints your next steps.
278
143
 
279
- ---
144
+ **Most repos need nothing past step 3's default** — the lexical engine has zero dependencies and works in any language. Step 3 only branches when you decline the defaults:
280
145
 
281
- ### `list_index_stats`
146
+ - **Storage** — `auto` (in-memory, promoting to SQLite past ~15k chunks), or force in-memory / SQLite.
147
+ - **Embeddings** — `off` (default), `auto`, **Ollama**, **local** (in-process MiniLM, ~25 MB on first index), or **Apple Metal (MLX)** on macOS. Ollama is probed only when you actually pick it; choosing MLX offers to provision its Python venv on the spot.
148
+ - **Enrichment / reranker** — off by default. A measured per-language note is shown before the reranker prompt (it helps Go/Python, regresses JS/TS). Model pickers are provider-aware, and if you select MLX the wizard verifies `mlx_lm.server` end-to-end — installing `mlx-lm`, starting the server, and pre-loading the model — so your first index can't fail against a server that isn't up.
282
149
 
283
- Index health snapshot: chunk count, file count, symbol table size, vector count, search mode, daemon status, index age.
150
+ ### Non-interactive & preview
284
151
 
285
- ---
152
+ | Flag | Effect |
153
+ |------|--------|
154
+ | `--yes`, `-y` | Accept all detected/default selections — no prompts (CI). |
155
+ | `--dry-run` | Print every file action without writing anything. |
156
+ | `--all-languages` | Index every supported language (implies `--yes`). |
157
+ | `--help`, `-h` | Show usage. |
286
158
 
287
- ### `graph://dependencies/{file_path}` (resource)
159
+ Setup also runs non-interactively whenever stdin isn't a TTY, so piping into it behaves like `--yes`.
288
160
 
289
- Full bidirectional dependency topology for a file: what it imports and what imports it.
161
+ ## How it works
290
162
 
291
- ---
163
+ - **AST indexing.** Tree-sitter parses each file into a syntax tree, and the indexer extracts one *chunk* per top-level definition (function, class, method, struct, …) with its name, parameters, line range, call sites, and enclosing class. A "god class" is split so its methods become their own chunks. Supported languages: **TypeScript/JavaScript** (`.ts .tsx .js .jsx .mjs .cjs`), **Python**, **Go**, **Rust**, **Java**, **Kotlin**, **C#**, **C**, **Ruby**, **PHP**, **Bash**, and **Swift**, plus **CSS/SCSS**.
164
+ - **Retrieval.** A hybrid ranker fuses a lexical channel (BM25 with camelCase splitting and language-agnostic Porter stemming) with an optional dense-vector channel (local embeddings) via Reciprocal Rank Fusion. With embeddings off, only the lexical channel runs — and it is the default.
165
+ - **Call graph.** `get_call_graph` returns the callers and callees of a symbol — the *blast radius* of a change — so an agent can see what it might break before editing code it never read.
166
+ - **Backend parity.** The in-memory and SQLite backends share the same ranking core, so they return identical top-5 results for the same query (enforced by `test/sqlite.mjs`).
167
+
168
+ ### MCP tools
169
+
170
+ | Tool | Returns |
171
+ |------|---------|
172
+ | `search_code` | Ranked semantic chunks for a natural-language or symbol query. |
173
+ | `get_chunk` | The full source of one chunk by id. |
174
+ | `get_chunk_summary` | A compact summary of a chunk (signature, calls, context). |
175
+ | `resolve_symbol` | Exact, case-insensitive symbol lookup by name. |
176
+ | `get_file_skeleton` | The top-level structure (symbols + signatures) of a file. |
177
+ | `get_call_graph` | Callers and callees of a symbol — the blast radius. |
178
+ | `find_references` | Where a symbol is used: callers, subclasses, and type references. |
179
+ | `find_routes` | HTTP routes mapped to their handler chunks (NestJS, FastAPI/Flask, Spring, Express/Koa). |
180
+ | `get_subgraph` | A bounded connected subgraph around a seed symbol — its callees, high-confidence callers, and type/inheritance users, in one call. |
181
+ | `get_repo_map` | A high-level map of the repository's modules and topology. |
182
+ | `list_index_stats` | Index health: chunk/file/symbol/vector counts and the active config. |
292
183
 
293
184
  ## Configuration
294
185
 
295
- ### Environment variables
296
-
297
- | Variable | Default | Description |
298
- | :--- | :--- | :--- |
299
- | `MCP_PROJECT_ROOT` | `process.cwd()` | Project root directory |
300
- | `OLLAMA_HOST` | `http://localhost:11434` | Ollama API endpoint |
301
- | `INDEXER_EMBEDDINGS` | — | Set to `off` to disable vector embeddings |
302
-
303
- ### Language selection
304
-
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`:
186
+ Everything beyond the lexical default is opt-in. The server, indexer, and daemon all print their **effective configuration** at startup (storage backend, model names, which optional features are on), and emit a visible warning whenever an opt-in feature has a known trade-off — nothing is enabled silently.
187
+
188
+ ### Headline trade-offs
189
+
190
+ | Option | Default | When to enable | Cost |
191
+ |--------|---------|----------------|------|
192
+ | `--embeddings` | off | Larger repos where recall matters; lifts success@5 | Requires Ollama or the in-process MiniLM model; slower indexing |
193
+ | `--embed-model qwen3-embedding:4b` | `nomic-embed-text` | Better code recall + symbolic precision | slower indexing; requires Ollama |
194
+ | `--enrichment` | off | Only useful paired with `--rerank`; alone it regresses | slowest indexing |
195
+ | `--rerank` | off | Go/Python repos with weak semantic recall; regresses JS repos | Requires an Ollama 7B model; adds query latency |
196
+ | `--use-sqlite` | `auto` | Repos past ~15k chunks or memory-constrained environments | Slightly higher query latency; needs Node 22+ |
197
+
198
+ For most repos, the default (lexical + stemming, no embeddings) is the right starting point. Enable embeddings when you notice the agent missing chunks it should find. Enable the reranker only on Go or Python repos after measuring whether it helps — it is known to regress JavaScript repositories.
199
+
200
+ ### All CLI flags
201
+
202
+ | Flag | Default | Effect |
203
+ |------|---------|--------|
204
+ | `--repo <path>` | current directory | Repository to index / serve. |
205
+ | `--embeddings` | off | Enable the dense-vector channel. |
206
+ | `--embed-model <model>` | `nomic-embed-text` | Ollama embedding model (e.g. `qwen3-embedding:4b`). |
207
+ | `--embed-provider <auto\|ollama\|local\|mlx\|off>` | `auto` | Force the embedding backend (see [Embedder backends](#embedder-backends)). |
208
+ | `--mlx-embed-model <model>` | `mlx-community/all-MiniLM-L6-v2-4bit` | Model loaded by the MLX (Apple Metal) embedder. |
209
+ | `--use-sqlite` | `auto` | Force the disk-backed SQLite backend. |
210
+ | `--enrichment` | off | Enable LLM enrichment of central chunks. |
211
+ | `--enrich-model <model>` | `qwen2.5-coder:1.5b` | Model used for enrichment. |
212
+ | `--enrich-max <n>` | 500 | Cap on new LLM calls per index run. |
213
+ | `--enrich-concurrency <n>` | 4 | Parallel Ollama requests during enrichment. |
214
+ | `--rerank` | off | Enable the LLM reranker (one call per NL query). |
215
+ | `--no-git-signals` | (signals on) | Skip collecting local git churn/recency/co-change. |
216
+ | `--git-rank-boost <0..1>` | 0 | Opt-in weight for git recency/churn in ranking (0 = ranking unchanged). |
217
+ | `--llm-provider <ollama\|mlx>` | `ollama` | LLM backend for enrichment, reranking, and HyDE. `mlx` routes calls to a local `mlx_lm.server`. |
218
+ | `--mlx-lm-host <url>` | `http://localhost:8080` | Endpoint for the `mlx_lm.server` when `--llm-provider mlx` is set. |
219
+
220
+ ### All environment variables
221
+
222
+ | Variable | Default | Effect |
223
+ |----------|---------|--------|
224
+ | `MCP_PROJECT_ROOT` | current directory | Repository root when `--repo` is not given. |
225
+ | `OLLAMA_HOST` | `http://localhost:11434` | Ollama endpoint for embeddings/enrichment/rerank. |
226
+ | `INDEXER_EMBEDDINGS` | `off` | `on` enables embeddings; `off` always wins over `--embeddings`. |
227
+ | `EMBED_MODEL` | `nomic-embed-text` | Ollama embedding model (overrides config; overridden by `--embed-model`). |
228
+ | `INDEXER_EMBED_PROVIDER` | `auto` | `auto` \| `ollama` \| `local` \| `mlx` \| `off`. |
229
+ | `INDEXER_MLX_EMBED_MODEL` | `mlx-community/all-MiniLM-L6-v2-4bit` | Model loaded by the MLX embedder (overridden by `--mlx-embed-model`). |
230
+ | `INDEXER_MLX_BATCH_SIZE` | 32 | Texts per batch sent to the MLX subprocess (raise on large unified memory). |
231
+ | `INDEXER_STORAGE` | `auto` | `auto` \| `memory` \| `sqlite`. |
232
+ | `ENRICH_MODEL` | (unset) | Naming a model enables enrichment and selects it. |
233
+ | `RERANK_MODEL` | (unset) | Naming a model enables the reranker and selects it. |
234
+ | `INDEXER_GIT_SIGNALS` | (on) | Set to `off` to skip git-signal collection. |
235
+ | `INDEXER_GIT_RANK_BOOST` | 0 | Opt-in git recency/churn ranking weight (0..1). |
236
+ | `INDEXER_LLM_PROVIDER` | `ollama` | LLM backend for enrichment, reranking, and HyDE: `ollama` or `mlx`. |
237
+ | `INDEXER_MLX_LM_HOST` | `http://localhost:8080` | Endpoint for the `mlx_lm.server` when `INDEXER_LLM_PROVIDER=mlx`. |
238
+ | `INDEXER_EMBED_CONCURRENCY` | 4 | Parallel embedding batches; lower to 1 for large models on modest hardware. |
239
+ | `INDEXER_EMBED_TIMEOUT_MS` | 120000 | Per-batch embedding timeout; raise for very large models. |
240
+
241
+ When embeddings are enabled, the provider is selected in this order, and every fallback is logged (never silent): Ollama with `EMBED_MODEL` if set and reachable → Ollama with `nomic-embed-text` → the in-process MiniLM model (optional `@huggingface/transformers`) → lexical-only with a warning.
242
+
243
+ ### Embedder backends
244
+
245
+ Set the backend with `--embed-provider` (or `INDEXER_EMBED_PROVIDER`). `mlx` is a native Python embedder that runs the Apple Metal GPU as a reused subprocess — faster in-process vectors than the bundled Xenova path, with no running Ollama daemon. The in-process (`local`) and `mlx` backends produce all-MiniLM-L6-v2-family **384-dim** vectors; the default `auto`→Ollama path uses `nomic-embed-text` (768-dim).
246
+
247
+ | Backend | `--embed-provider` | Platform | Throughput¹ | Setup |
248
+ |---------|--------------------|----------|-------------|-------|
249
+ | Auto (Ollama → local → off) | `auto` (default) | any | varies | none |
250
+ | In-process (Xenova) | `local` | any | ~18 ch/s | `npm i @huggingface/transformers` |
251
+ | **Apple Metal (MLX)** | **`mlx`** | **macOS Apple Silicon** | **~42 ch/s** | `npm run embed:setup:mlx` |
252
+ | Ollama daemon | `ollama` | any | ~14 ch/s² | `ollama serve` + `ollama pull` |
253
+
254
+ ¹ **Throughput is hardware-dependent.** These figures are the median of 3 cold builds on an **Apple M2 Mac mini (24 GB)** indexing the `express-js` fixture (389 chunks): `node bench/cell.mjs express-js <E0|E0_MLX|O0>`. Expect different numbers on other chips; reproduce on your own machine. Throughput also varies with system load, model warm state, and corpus size — larger repos amortize cold-start better (e.g. Ollama/nomic reaches ~32 ch/s on the larger `gin` corpus).
255
+
256
+ ² On Apple Silicon, Ollama already uses the Metal GPU internally (via llama.cpp). The `mlx` provider's advantage comes from a smaller model (all-MiniLM-L6-v2-4bit, 22M params, 384-dim) and no HTTP round-trip, not from GPU vs CPU.
257
+
258
+ The index records which provider/model built it (in the `code-index.embeddings.bin.meta.json` sidecar) and queries with the same one, so vectors never get mixed across models. Switching providers or the MLX model between builds is detected and triggers a clean re-embed.
259
+
260
+ **macOS Apple Silicon (recommended):**
314
261
 
315
262
  ```bash
316
- npx graph-indexer init # Re-run interactive selector
317
- npx graph-indexer init --all-languages # Enable all, no prompt
318
- ```
319
-
320
- If `.graph-indexer.json` is absent or has no `languages` key, all installed parsers are used.
321
-
322
- ---
323
-
324
- ## Supported languages
325
-
326
- | Language | Extensions | Chunks extracted |
327
- | :--- | :--- | :--- |
328
- | TypeScript / TSX | `.ts`, `.tsx` | functions, classes, methods, exports |
329
- | JavaScript | `.js`, `.jsx`, `.mjs`, `.cjs` | functions, classes, expressions |
330
- | Python | `.py` | functions, classes |
331
- | Go | `.go` | functions, methods, types |
332
- | Rust | `.rs` | fn, struct, enum, trait, impl |
333
- | Java | `.java` | class, method, interface, constructor, enum |
334
- | Kotlin | `.kt`, `.kts` | function, class, object, companion |
335
- | C# | `.cs` | class, method, interface, property, enum |
336
- | Ruby | `.rb` | method, class, module |
337
- | PHP | `.php` | function, class |
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 ────────────────┘
263
+ npm run embed:setup:mlx # one-time: creates embedders/venv-mlx + installs MLX
264
+ npx idx-index --repo . --embeddings --embed-provider mlx
370
265
  ```
371
266
 
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.
267
+ `embed:setup:mlx` provisions a **dedicated Python virtualenv** under `embedders/venv-mlx` (so MLX's deps never touch your system Python) and is idempotent — re-running it is a no-op once ready. The interactive setup (`npx graph-indexer`) offers MLX directly and can run this step for you. The first index downloads the model (~90 MB) into the Hugging Face cache; later builds reuse it.
376
268
 
377
- ---
269
+ Choose a different MLX model with `--mlx-embed-model <id>` (or `INDEXER_MLX_EMBED_MODEL`); it must be an `mlx_embeddings`-compatible sentence model, and the default `mlx-community/all-MiniLM-L6-v2-4bit` is the proven option. Tune batch size with `INDEXER_MLX_BATCH_SIZE` (default 32; lower to 16 on 8 GB machines, raise to 64 on M-series Max/Ultra). `mlx` is macOS-only; requesting it elsewhere fails fast with the alternative to use.
378
270
 
379
- ## Best practices
271
+ ## Benchmarks
380
272
 
381
- **Write descriptive docstrings.** Docstrings are embedded alongside code — better documentation directly improves semantic search quality.
273
+ Every number here is produced by the eval harness on cold, isolated builds — never hand-edited — with **strict scoring** (exact symbol match, no file-path fallback) against a **held-out** split (~20–25% per language, never used to tune). Reproduce it all with `npm run test:eval`.
382
274
 
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.
275
+ What the measurements say, across 18 real-world fixtures spanning every supported language:
384
276
 
385
- **Export named functions, not anonymous ones.** Anonymous default exports can't be targeted by `resolve_symbol` and rank lower in name-boost scoring.
277
+ - **The zero-dependency lexical default is the right starting point.** It wins the held-out metric outright on 2 fixtures, and 6 more need only a cheap in-process embedder — so **8 of 18 run with no Ollama and no network**. Most repos never need more.
278
+ - **Symbolic lookups are strong** (mean rank-1 ≈ 0.70). Behavioural, natural-language queries are harder (≈ 0.30 on the default path) — that's where the optional embedding and reranker channels earn their keep.
279
+ - **Optional features are measured, not assumed.** Dense embeddings lift recall on larger repos; the LLM reranker is language- and repo-dependent (it lifts 8 fixtures but taxes JavaScript and Spring); enrichment helps only where proven (just rust). Setup surfaces each trade-off before you enable it.
280
+ - **Backend parity:** the in-memory and SQLite backends return byte-identical top-5 on all 18 fixtures (gated by `test/sqlite.mjs`).
386
281
 
387
- **Avoid catch-all utility files.** Files that mix unrelated utilities produce weaker per-chunk signals. One responsibility per module indexes more cleanly.
282
+ Per-fixture best configs, 3× spreads, and copy-paste enable flags live in **[docs/benchmarks/BENCH_PER_FIXTURE.md](docs/benchmarks/BENCH_PER_FIXTURE.md)**.
388
283
 
389
- ---
284
+ ### Structural coverage by language
390
285
 
391
- ## Troubleshooting
286
+ `get_call_graph` and `find_references` are richest for typed languages. Every verdict is confirmed by invocation on the real index, not by reading field counts:
392
287
 
393
- **Index takes too long**
394
- - Verify Ollama is running: `curl http://localhost:11434/api/tags`
395
- - Use lexical-only mode: `INDEXER_EMBEDDINGS=off npm run mcp:index`
288
+ | Capability | Strong | Limited |
289
+ |---|---|---|
290
+ | **Call graph** (callers/callees) | resolves on every supported language | Java/Spring is class-granular; SCSS effectively none |
291
+ | **Caller precision** | receiver-aware: TS/JS, Python, C#, Swift, PHP | name-only: Go, Rust, Kotlin, Ruby, Bash, C |
292
+ | **Typed `find_references`** | precise: TS/JS, Python · field-precise: C# | heuristic: Java/PHP/Kotlin/Swift/Rust/Go/C · empty: Ruby, Bash, SCSS, dynamic JS/Python |
396
293
 
397
- **Search returns irrelevant results**
398
- - Use `exact_tokens` for known symbol names
399
- - Check coverage with `list_index_stats()`
400
- - Increase `top_k` to see more candidates
294
+ AST chunking and lexical search cover **every** supported language; only the typed cross-reference channel narrows for dynamic ones.
401
295
 
402
- **MCP server won't connect**
403
- - Verify `MCP_PROJECT_ROOT` points to the indexed directory
404
- - Check `code-index.json` exists: `ls -la code-index.json`
405
- - Test manually: `npm run mcp:start`
296
+ ### Where it's weakest (honestly)
406
297
 
407
- **Results are stale after file changes**
408
- - The daemon auto-updates; if stale, run `npm run mcp:index` manually
409
- - Check daemon status with `list_index_stats()`
298
+ - Behavioural, natural-language recall on the default path (rank-1 ≈ 0.30) — closing it needs the embedding/reranker channel, not lexical tuning.
299
+ - The hardest fixtures stay below 0.65 held-out success@5 even with the full stack (rails 0.50, rust 0.61).
300
+ - Java/Spring `get_call_graph` reports at class granularity (the god-class split only fires ≥200 lines); SCSS has no meaningful call graph.
301
+ - LLM enrichment without reranking regresses precision — enable it only paired with `--rerank`.
410
302
 
411
- ---
412
-
413
- ## Development
303
+ ## Reproducing the benchmarks
414
304
 
415
305
  ```bash
416
- git clone https://github.com/MaquinaTech/graph-indexer.git
417
- cd graph-indexer
418
- npm install
419
- npm run mcp:index # index this repo
420
- npm run test # run test suite (lexical-only)
421
- npm run mcp:start # start MCP server
306
+ npm run test:all # full unit + integration suite
307
+ npm run test:setup # index the benchmark fixtures
308
+ npm run test:eval # default lexical path — prints the HELD-OUT block
309
+ npm run test:eval -- --suite css # any single fixture (18 authored suites)
310
+ npm run test:eval -- --embeddings # hybrid eval (requires Ollama)
311
+ npm run test:eval -- --verbose # per-query breakdown incl. file-rank column
312
+ node test/sqlite.mjs # backend-parity gate (memory ↔ SQLite top-5)
313
+ node bench/cell.mjs <fixture> L1 # cold rebuild + score one fixture
422
314
  ```
423
315
 
424
- ---
425
-
426
- ## Security
427
-
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).
432
-
433
- See [SECURITY.md](SECURITY.md) for the full threat model and how to report a
434
- vulnerability.
435
-
436
- ---
437
-
438
- ## License
439
-
440
- Released under the [MIT License](LICENSE). Copyright (c) 2026 MaquinaTech.
316
+ The harness is [test/evaluate.mjs](test/evaluate.mjs); deeper reproduction notes live in [docs/benchmarks/BENCH_BASELINE.md](docs/benchmarks/BENCH_BASELINE.md) and [docs/benchmarks/BENCH_FULL_SUITE.md](docs/benchmarks/BENCH_FULL_SUITE.md).
441
317
 
442
318
  ---
443
319
 
444
- Built by [MaquinaTech](https://github.com/MaquinaTech) · [Issues](https://github.com/MaquinaTech/graph-indexer/issues) · [npm](https://www.npmjs.com/package/graph-indexer)
320
+ <div align="center">
321
+ <sub>MIT licensed · runs entirely on your machine · see <a href="SECURITY.md">SECURITY.md</a> and <a href="TRADEMARK.md">TRADEMARK.md</a></sub>
322
+ </div>