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.
- package/CHANGELOG.md +412 -0
- package/README.md +228 -350
- package/SECURITY.md +68 -0
- package/TRADEMARK.md +31 -0
- package/cli-ui.mjs +78 -0
- package/config.mjs +269 -0
- package/daemon-ctl.mjs +175 -0
- package/daemon-lock.mjs +69 -0
- package/embedders/python/mlx_embed_server.py +61 -0
- package/embedders/python/requirements-mlx.txt +5 -0
- package/embedders/setup-mlx.mjs +110 -0
- package/embeddings.mjs +489 -0
- package/engine/binary.mjs +399 -0
- package/{core-engine.mjs → engine/memory.mjs} +359 -249
- package/engine/sqlite.mjs +863 -0
- package/enrichment.mjs +426 -0
- package/git-signals.mjs +248 -0
- package/indexer.mjs +216 -77
- package/init.mjs +1604 -170
- package/layout.mjs +123 -0
- package/mcp/format.mjs +61 -0
- package/mcp/tools.mjs +1157 -0
- package/mcp/topology.mjs +551 -0
- package/mcp-server.mjs +118 -573
- package/package.json +56 -16
- package/parse/extractor.mjs +617 -0
- package/parse/imports.mjs +365 -0
- package/parse/languages.mjs +89 -0
- package/parse/metadata.mjs +655 -0
- package/parse/routes.mjs +327 -0
- package/prompts/CORE.md +86 -0
- package/prompts/DOMAIN_TEMPLATE.md +46 -0
- package/prompts/INTEGRATION.md +123 -0
- package/prompts/frameworks/ANDROID.md +17 -0
- package/prompts/frameworks/ASPNET_CORE.md +19 -0
- package/prompts/frameworks/FASTAPI_DJANGO.md +19 -0
- package/prompts/frameworks/LARAVEL_SYMFONY.md +22 -0
- package/prompts/frameworks/NODE_EXPRESS_NESTJS.md +18 -0
- package/prompts/frameworks/RAILS.md +20 -0
- package/prompts/frameworks/REACT.md +20 -0
- package/prompts/frameworks/SPRING_BOOT.md +19 -0
- package/prompts/languages/BASH.md +22 -0
- package/prompts/languages/C.md +21 -0
- package/prompts/languages/CSHARP.md +24 -0
- package/prompts/languages/CSS_SCSS.md +22 -0
- package/prompts/languages/GO.md +23 -0
- package/prompts/languages/JAVA.md +23 -0
- package/prompts/languages/JAVASCRIPT_TYPESCRIPT.md +24 -0
- package/prompts/languages/KOTLIN.md +24 -0
- package/prompts/languages/PHP.md +23 -0
- package/prompts/languages/PYTHON.md +23 -0
- package/prompts/languages/RUBY.md +23 -0
- package/prompts/languages/RUST.md +21 -0
- package/prompts/languages/SWIFT.md +21 -0
- package/search-core.mjs +627 -0
- package/storage.mjs +68 -0
- package/watch-daemon.mjs +204 -69
- package/PROMPT.md +0 -143
- package/parser-utils.mjs +0 -861
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to graph-indexer are documented here. Dates are in YYYY-MM-DD format.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## [2.0.0] — 2026-06-21
|
|
8
|
+
|
|
9
|
+
This is a major release. The public API (MCP tools, CLI flags, config keys) has grown significantly, but the default behaviour — lexical-only search, in-memory store, zero external dependencies — is backward-compatible. Internal modules were reorganized into `engine/`, `mcp/`, and `parse/` (see [Module reorganization](#module-reorganization-breaking-only-for-deep-imports) below) — breaking only for code that deep-imported internal files.
|
|
10
|
+
|
|
11
|
+
### Onboarding (`graph-indexer init`) — environment-agnostic setup
|
|
12
|
+
|
|
13
|
+
The first-run setup now produces a working configuration in **any** repository, not just a Node repo that already has graph-indexer installed.
|
|
14
|
+
|
|
15
|
+
- **Self-contained MCP launch command.** The wired command is now decided per-project. When graph-indexer is resolvable as a local dependency, the wiring keeps using `npm run mcp:start`. Otherwise — **non-Node repos** (Python, Go, Rust, …) and **npx-only Node repos** — it wires a self-contained `npx -y -p graph-indexer idx-mcp --repo <abs path>` that launches the real server regardless of ecosystem. The same decision drives the npm scripts (wrapped in `npx -p` when not a local dep, so they aren't broken) and the global Claude Desktop config.
|
|
16
|
+
- **Subcommand dispatch.** `graph-indexer <idx-mcp|idx-index|idx-watch|idx-daemon> …` now delegates to the real bin (previously these tokens were swallowed by the init wizard, because `npx <pkg> <x>` runs the package's same-named bin, not the `<x>` bin). This makes the documented `npx -y graph-indexer idx-mcp` form work too.
|
|
17
|
+
- **Target a repo + CI flags.** `init` now honors a positional path and `--repo <path>` (resolved to an absolute root used everywhere), adds `--help`, and adds `--yes` / `--non-interactive` (a non-TTY stdin already implied this) for scripted/CI runs.
|
|
18
|
+
- **Claude Code MCP target.** Project-scoped servers are now written to `.mcp.json` (the file Claude Code reads for project MCP servers) instead of `.claude/settings.json`.
|
|
19
|
+
- **Claude Desktop on Linux.** Added the `~/.config/Claude` path (honoring `$XDG_CONFIG_HOME`); Linux users with Claude Desktop installed now get wired instead of reported "not installed".
|
|
20
|
+
- **Node version guard.** Selecting (or auto-resolving on a large repo to) the SQLite backend on Node < 22 now surfaces a clear warning in the summary instead of failing later at runtime.
|
|
21
|
+
- **Faster default path + ready to use.** The engine step opens with a single "Use recommended defaults?" confirm that skips the ~10 follow-up prompts, and the run ends by offering to build the index so the flow finishes genuinely ready to use. Editor wiring now distinguishes "detected" from "ready if you use it" in the summary.
|
|
22
|
+
|
|
23
|
+
Idempotency, merge-safe config writes, air-gapped defaults, and `--dry-run` are all preserved.
|
|
24
|
+
|
|
25
|
+
### New MCP tools
|
|
26
|
+
|
|
27
|
+
| Tool | Purpose |
|
|
28
|
+
|------|---------|
|
|
29
|
+
| `find_references` | Where a symbol is used: callers, subclasses, and type-annotation references, split into high-confidence vs name-only blast radius. |
|
|
30
|
+
| `find_routes` | HTTP routes mapped to their handler chunks — NestJS, Express/Koa, FastAPI/Flask, and Spring (Java); the controller/router prefix is joined onto the method path. Attribute-routed C#/ASP.NET, PHP (Laravel/Symfony), Rails, and Django URLconf are out of scope. |
|
|
31
|
+
| `get_subgraph` | Bounded connected subgraph (callers + callees + type/inheritance referers) around a seed symbol, in one call. Replaces multi-hop `search_code → get_call_graph → find_references` round-trips. |
|
|
32
|
+
|
|
33
|
+
HTTP route extraction (`parse/routes.mjs`) parses route definitions from the AST so `find_routes` can resolve an endpoint straight to its handler chunk; coverage is per-framework as noted above.
|
|
34
|
+
|
|
35
|
+
**All tools** now support a `response_format: 'json'` parameter that returns typed structured fields instead of markdown prose, for programmatic clients.
|
|
36
|
+
|
|
37
|
+
All tool responses carry an **index-freshness signal** (age, commit it was built at, pending uncommitted changes). The freshness footer is omitted on up-to-date indexes to keep card sizes lean.
|
|
38
|
+
|
|
39
|
+
### New language support
|
|
40
|
+
|
|
41
|
+
Added three languages, bringing the total to 14:
|
|
42
|
+
|
|
43
|
+
- **C** (`.c`, `.h`) — struct/function/declaration extraction with declarator name descender
|
|
44
|
+
- **Bash** (`.sh`, `.bash`) — command and function extraction with built-ins filter
|
|
45
|
+
- **Swift** (`.swift`) — class/struct/enum/function extraction
|
|
46
|
+
|
|
47
|
+
Existing language support: TypeScript/JavaScript, Python, Go, Rust, Java, Kotlin, C#, Ruby, PHP, CSS/SCSS.
|
|
48
|
+
|
|
49
|
+
### Prompt suite (new `prompts/` directory)
|
|
50
|
+
|
|
51
|
+
Replaced the single `PROMPT.md` with a structured, composable **3-layer prompt suite**:
|
|
52
|
+
|
|
53
|
+
- **`prompts/CORE.md`** — universal rules (4-call budget, batch-don't-iterate, consume-before-call, rule-of-one). Always required.
|
|
54
|
+
- **`prompts/languages/`** — 13 language-specific overlays: BASH, C, C#, CSS/SCSS, Go, Java, JavaScript/TypeScript, Kotlin, PHP, Python, Ruby, Rust, Swift
|
|
55
|
+
- **`prompts/frameworks/`** — 8 framework-specific overlays: Android, ASP.NET Core, FastAPI/Django, Laravel/Symfony, Node/Express/NestJS, Rails, React, Spring Boot
|
|
56
|
+
- **`prompts/DOMAIN_TEMPLATE.md`** — a copy-and-fill template for project-specific rules
|
|
57
|
+
- **`prompts/INTEGRATION.md`** — guide explaining the 3-layer architecture and how to compose the layers
|
|
58
|
+
|
|
59
|
+
### Embedding provider abstraction (`embeddings.mjs`)
|
|
60
|
+
|
|
61
|
+
New module with an `auto` provider selection policy:
|
|
62
|
+
|
|
63
|
+
1. Ollama (user's running daemon, highest quality)
|
|
64
|
+
2. In-process local model via the optional `@huggingface/transformers` dependency (`Xenova/all-MiniLM-L6-v2` by default) — runs with no external daemon
|
|
65
|
+
3. Lexical-only fallback (logged, never silent)
|
|
66
|
+
|
|
67
|
+
Embeddings remain **off by default**. The index stamps the resolved `{ provider, model, dim }` into its metadata; the server reads it back on load so index time and query time always use the same model.
|
|
68
|
+
|
|
69
|
+
New `--embed-provider <auto|ollama|local|off>` flag and `INDEXER_EMBED_PROVIDER` env variable to override provider selection.
|
|
70
|
+
|
|
71
|
+
New `mlx` provider uses a dedicated Python virtualenv (`embedders/venv-mlx/`) and the `mlx_embeddings` library to embed via the Apple Metal GPU as a persistent subprocess — no Ollama daemon required. Measured throughput on an **Apple M2 Mac mini (24 GB)**, `express-js` fixture, median of 3 cold builds: `mlx` **~42 ch/s** vs in-process Xenova **~18 ch/s** vs Ollama/nomic **~14 ch/s** (throughput is hardware-dependent — expect different figures on other chips). Note: on Apple Silicon, Ollama also uses the Metal GPU internally; the `mlx` provider's advantage is a smaller 4-bit model and no HTTP round-trip, not GPU vs CPU.
|
|
72
|
+
|
|
73
|
+
**Bug fix:** `indexer.mjs` (and the eval harness) now calls `_resetSubprocesses()` after encoding completes so the MLX Python subprocess is killed and the Node.js event loop can drain. Previously `npx idx-index --embed-provider mlx` would hang indefinitely after building a correct index.
|
|
74
|
+
|
|
75
|
+
**Setup & onboarding.** Install the MLX virtualenv with `npm run embed:setup:mlx` (`node embedders/setup-mlx.mjs`), or accept it during `graph-indexer init` when you select the MLX provider (onboarding also lets you choose the MLX embed model, default `mlx-community/all-MiniLM-L6-v2-4bit`, 384-dim). New MLX implementation files: `embedders/setup-mlx.mjs` (venv installer), `embedders/python/mlx_embed_server.py` (persistent MLX subprocess server), `embedders/python/requirements-mlx.txt`, `embedders/python/test_servers.mjs`.
|
|
76
|
+
|
|
77
|
+
**Graceful per-batch degradation.** Embedding generation no longer aborts the whole index when a batch fails or times out — those chunks are indexed lexical-only and retried on the next run, with a per-batch warning and an end-of-run summary of how many batches were skipped.
|
|
78
|
+
|
|
79
|
+
### Git signals (`git-signals.mjs`)
|
|
80
|
+
|
|
81
|
+
New module that computes three per-file ranking signals from the local git log (no network, no remote):
|
|
82
|
+
|
|
83
|
+
- **churn** — commit count per file
|
|
84
|
+
- **recency** — time since last commit
|
|
85
|
+
- **co-change** — files that historically change together
|
|
86
|
+
|
|
87
|
+
Signals are collected once at index time into a sidecar file (`code-index.git.json`). The co-change hint is surfaced in tool blast-radius responses. The recency/churn ranking weight is **off by default** (`gitRankBoost: 0`) — enable with `--git-rank-boost <0..1>` or `INDEXER_GIT_RANK_BOOST`.
|
|
88
|
+
|
|
89
|
+
### Daemon control CLI (`daemon-ctl.mjs` / `idx-daemon`)
|
|
90
|
+
|
|
91
|
+
New `idx-daemon` binary with subcommands:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
idx-daemon start Start the watch daemon (no-op if already running)
|
|
95
|
+
idx-daemon stop Gracefully stop it
|
|
96
|
+
idx-daemon restart Stop then start
|
|
97
|
+
idx-daemon status Show daemon + index state (default)
|
|
98
|
+
idx-daemon logs [-f] Print recent daemon logs
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
New `daemon-lock.mjs` ensures exactly one daemon runs per project — `start` is safe to call repeatedly. New npm scripts: `mcp:daemon`, `mcp:daemon:start`, `mcp:daemon:stop`, `mcp:daemon:restart`, `mcp:daemon:status`, `mcp:daemon:logs`.
|
|
102
|
+
|
|
103
|
+
### Directory layout (`layout.mjs`)
|
|
104
|
+
|
|
105
|
+
New `layout.mjs` is the single source of truth for on-disk paths. All machine-generated runtime files (index, vectors, SQLite database, enrichment cache, daemon PID/log, resolved config) now live under `.graph-indexer/` at the project root instead of being scattered at the root level. `migrateLegacyLayout()` relocates pre-v1.4 artifacts transparently on next run. The config file is `config.json` inside that directory; the legacy `.graph-indexer.json` root file is still read as a fallback for backward compatibility.
|
|
106
|
+
|
|
107
|
+
### CLI UI (`cli-ui.mjs`)
|
|
108
|
+
|
|
109
|
+
New dependency-free shared console styling module for CLI utilities (init, daemon control). Provides ANSI colour with graceful degradation when stdout is not a TTY or `NO_COLOR` is set, plus boxed banners, rules, and status glyphs for a consistent look.
|
|
110
|
+
|
|
111
|
+
### Receiver-aware call graph
|
|
112
|
+
|
|
113
|
+
`get_call_graph` now uses receiver hints captured by the parser (`this.X()`, `Receiver.X()`, free-function `X()`) to classify callers as **high-confidence** (the real blast radius) vs **name-only** (callers of a same-named symbol elsewhere). Eliminates false positives on methods with common names (e.g., `save`, `find`, `validate`).
|
|
114
|
+
|
|
115
|
+
### Lexical search improvements
|
|
116
|
+
|
|
117
|
+
- **Porter stemming** — language-agnostic Porter stemmer (steps 1–5 plus an agent-noun `-or` rule for code) bridging vocabulary gaps: `"intercepting"` ↔ `Interceptor`, `"managing"` ↔ `Manager`, `"injection"` ↔ `Injectable`. Applied **additively** (raw token always emitted alongside the stem) so exact matches, IDF statistics, and name boosts are byte-for-byte unchanged.
|
|
118
|
+
- **IDF-gated path boost** — for NL queries, the path-segment boost is restricted to file-path terms that are rare in the corpus (high IDF). Symbol/keyword queries keep the original path boost unchanged — the IDF gate, not the boost itself, is NL-only.
|
|
119
|
+
- **NL-adaptive vector weight** — the semantic channel weight is `0.4` for plain lexical text and `0.6` for LLM-enriched chunks (the joint optimum across the eval suites), so the embedding signal matters most where the index has a rich semantic representation.
|
|
120
|
+
- **Dense-channel window sub-chunking** — oversized definitions (beyond the single-vector context limit) are embedded as up to 4 overlapping windows and retrieved by the maximum cosine across windows (max-sim), recovering tail recall that single-vector truncation lost. Window 0 is byte-identical to the prior single vector, so existing head vectors are unchanged; extra windows are stored under `<key>|w1..|wN` keys in the embeddings binary.
|
|
121
|
+
|
|
122
|
+
### LLM rerank: over-fetch + pool rescue
|
|
123
|
+
|
|
124
|
+
When `rerank` is enabled, the server now **over-fetches** a deeper candidate pool (configurable `poolSize`, default 15) and lets the LLM judge reorder it before truncating to `top_k`. This lets the reranker rescue a correct-but-deep result that would never reach the agent otherwise.
|
|
125
|
+
|
|
126
|
+
### HyDE (Hypothetical Document Embedding)
|
|
127
|
+
|
|
128
|
+
New opt-in query-side technique: generate a hypothetical code snippet for the NL query, embed it, and blend with the query vector to bridge vocabulary gaps. Off by default. Enable with `hyde: { enabled: true }` in config or the `hyde` parameter in `search_code`. The HyDE prompt is **language-aware** — the repo's dominant language (detected from extension counts and chunk metadata) selects a per-language hypothetical-snippet template, with a generic, never-regressing fallback when the language is unknown.
|
|
129
|
+
|
|
130
|
+
### Configuration overhaul
|
|
131
|
+
|
|
132
|
+
Default values changed:
|
|
133
|
+
|
|
134
|
+
| Key | Old default | New default | Reason |
|
|
135
|
+
|-----|-------------|-------------|--------|
|
|
136
|
+
| `storage` | `'memory'` | `'auto'` | Auto-selects SQLite above ~15k chunks |
|
|
137
|
+
| `embeddings` | (on unless `INDEXER_EMBEDDINGS=off`) | `false` | Explicit opt-in, lexical-first default |
|
|
138
|
+
| `embedProvider` | (none — Ollama was the only path) | `'auto'` | Falls back to local model, then lexical |
|
|
139
|
+
| `embedModel` | `'nomic-embed-text'` | `'nomic-embed-text'` | Unchanged |
|
|
140
|
+
| `rerank.topM` | `8` | `12` | Wider rerank pool |
|
|
141
|
+
| `rerank.poolSize` | (none) | `15` | New over-fetch parameter |
|
|
142
|
+
|
|
143
|
+
CLI flag rename: `--llm-enrichment` (and `--enrich`) → `--enrichment`. The old forms are no longer accepted.
|
|
144
|
+
|
|
145
|
+
Startup logging now prints the **effective configuration** (storage backend, model names, which optional features are active) and emits a warning for any opt-in feature with a known trade-off.
|
|
146
|
+
|
|
147
|
+
New embedding/enrichment flags and environment variables:
|
|
148
|
+
|
|
149
|
+
- `--mlx-embed-model` / `INDEXER_MLX_EMBED_MODEL` — MLX model id (default `mlx-community/all-MiniLM-L6-v2-4bit`).
|
|
150
|
+
- `EMBED_MODEL` — env alias for `--embed-model`.
|
|
151
|
+
- `ENRICH_MODEL` — env that enables enrichment and names its model. Setting `enrichment.model: 'auto'` probes the local Ollama and picks the strongest available code model (preference ladder `qwen2.5-coder:7b → 3b → deepseek-coder-v2 → 1.5b`), falling back to the `qwen2.5-coder:1.5b` floor — best-effort and non-throwing.
|
|
152
|
+
- `INDEXER_EMBED_CONCURRENCY` (parallel embed batches, default `4`), `INDEXER_EMBED_TIMEOUT_MS` (per-batch timeout, default `120000`), `INDEXER_MLX_BATCH_SIZE` (default `32`), `INDEXER_EMBED_STARTUP_TIMEOUT_MS` (default `120000`).
|
|
153
|
+
|
|
154
|
+
Fixed the Ollama endpoint in the embeddings test scripts from the non-default `:11435` to Ollama's real default `:11434` (`OLLAMA_HOST`).
|
|
155
|
+
|
|
156
|
+
### Symbol references (`find_references`)
|
|
157
|
+
|
|
158
|
+
`find_references` (`findReferences` in `mcp/topology.mjs`) fuses three reference kinds, combining `classifyCallers` (calls) with the new `db.findReferers` storage method (which returns the non-call referers — `extends` and `type_refs` matches):
|
|
159
|
+
|
|
160
|
+
- **calls** — `findCallers` + high/name-only classification
|
|
161
|
+
- **inherits** — chunks whose `extends` names the target (subclasses / implementers)
|
|
162
|
+
- **types** — chunks whose `type_refs` names the target (params, returns, fields)
|
|
163
|
+
|
|
164
|
+
Heritage is indexed for TS/JS, Python, Java, C#, Kotlin, Swift, Ruby, PHP, Rust, and Go (Go via struct/interface embedding rather than an inheritance keyword). Type-annotation users for TS/JS, Python, Java, C#, Kotlin, Swift, PHP, Rust, Go, and C (C# is field-precise: params, fields, properties, returns, base list).
|
|
165
|
+
|
|
166
|
+
### Symlink path traversal hardening
|
|
167
|
+
|
|
168
|
+
`get_file_skeleton` now resolves symlinks with `realpath` on both the project root and the requested target, then re-verifies containment. A symlink inside the project pointing outside it can no longer escape the sandbox.
|
|
169
|
+
|
|
170
|
+
### Benchmarking harness (`bench/`)
|
|
171
|
+
|
|
172
|
+
New reproducible benchmarking suite:
|
|
173
|
+
|
|
174
|
+
- `bench/cell.mjs` — cold-build per config, runs one fixture × one config combination
|
|
175
|
+
- `bench/configs.mjs` — config matrix definitions
|
|
176
|
+
- `bench/synth.mjs` / `bench/synth-agent.mjs` / `bench/synth-best.mjs` — report generators (cross-language, agent-facing, per-fixture best-config)
|
|
177
|
+
- `bench/tokens.mjs` — token-savings measurement (drives the token-savings tables below)
|
|
178
|
+
- `bench/repeat-score.mjs`, `bench/query.mjs`, `bench/dump-chunks.mjs`, `bench/fixtures-doc.mjs`, `bench/_confidence.mjs` — scoring, query, and reporting helpers
|
|
179
|
+
- `bench/verify-suite.mjs` / `bench/verify-ground-truth.mjs` / `bench/verify-structural.mjs` — fabrication guards: ensure query answers actually exist in the index
|
|
180
|
+
- `bench/structural.mjs` — structural fixture verifier
|
|
181
|
+
- `bench/parity.mjs` — byte-identical parity check between backends
|
|
182
|
+
- `bench/provenance.mjs` / `bench/provenance.json` — query provenance tracking
|
|
183
|
+
- Shell runners: `run-all.sh`, `run-costly.sh`, `run-final.sh`, `run-focused.sh`, `run-list.sh` (plus internal `_rerun-v2.sh`)
|
|
184
|
+
|
|
185
|
+
### Test suite expansion
|
|
186
|
+
|
|
187
|
+
New test files:
|
|
188
|
+
|
|
189
|
+
| File | What it covers |
|
|
190
|
+
|------|----------------|
|
|
191
|
+
| `test/callgraph.mjs` | Receiver-aware call graph, `classifyCallers` |
|
|
192
|
+
| `test/references.mjs` | `find_references` / `findReferers` |
|
|
193
|
+
| `test/routes.mjs` | `find_routes` / HTTP route extraction per framework |
|
|
194
|
+
| `test/json-output.mjs` | `response_format: 'json'` across all tools |
|
|
195
|
+
| `test/git-signals.mjs` | Git churn/recency/co-change sidecar |
|
|
196
|
+
| `test/security.mjs` | Path-traversal / symlink escape guard |
|
|
197
|
+
| `test/embeddings.mjs` | Embedding provider abstraction |
|
|
198
|
+
| `test/languages.mjs` | Per-language parsing (all 14 languages) |
|
|
199
|
+
|
|
200
|
+
(`test/unit.mjs` and `test/metrics.mjs` already existed in v1.2.0 and were extended, not added.)
|
|
201
|
+
|
|
202
|
+
New test suites for additional frameworks/languages: `alamofire`, `android`, `aspnet`, `cjson`, `css`, `django`, `laravel`, `nvm`, `rails`, `react`, `rust`, `spring`, `symfony`. (The `axios`, `express-js`, `fastapi`, `gin`, and `nestjs` suites already shipped in v1.2.0 and had their query sets expanded.)
|
|
203
|
+
|
|
204
|
+
New npm test scripts: `test:callgraph`, `test:references`, `test:routes`, `test:jsonout`, `test:gitsignals`, `test:security`, `test:embed`, `test:languages`.
|
|
205
|
+
|
|
206
|
+
### Agent benchmark harness (`test/agent/`)
|
|
207
|
+
|
|
208
|
+
New end-to-end agent benchmark that drives real MCP tools and traces an agent's call chain:
|
|
209
|
+
|
|
210
|
+
- `test/agent/agent-cli.mjs` — CLI entry point
|
|
211
|
+
- `test/agent/benchmark.config.mjs` — fixture and config registry
|
|
212
|
+
- `test/agent/fixtures.manifest.mjs` / `test/agent/setup-fixtures.mjs` — fixture manifest and setup
|
|
213
|
+
- `test/agent/search-cases.mjs` — search case definitions
|
|
214
|
+
- `test/agent/search-eval.mjs` — search evaluation with MRR/success@k scoring
|
|
215
|
+
- `test/agent/parse-eval.mjs` / `test/agent/score-answers.mjs` / `test/agent/se-mrr.mjs` — eval parsing and scoring
|
|
216
|
+
- `test/agent/analyze.mjs` / `assemble.mjs` — analysis pipeline
|
|
217
|
+
- `test/agent/tool-bridge.mjs` — bridges the agent to the MCP server under test
|
|
218
|
+
|
|
219
|
+
### Documentation
|
|
220
|
+
|
|
221
|
+
- **README.md** — rewritten to roughly a third its previous length (~700 → ~310 lines). Focused on the quick start, MCP tool reference, configuration trade-offs table, CLI flags, and environment variables. Benchmark numbers verifiable via `npm run test:eval`.
|
|
222
|
+
- **`docs/benchmarks/`** — new directory with detailed benchmark reports: `BENCH_BASELINE.md`, `BENCH_FULL_SUITE.md`, `BENCH_LANGUAGES.md`, `BENCH_SUMMARY.md`, `BENCH_AGENT.md`, `BENCH_PER_FIXTURE.md`, `FIXTURES.md`.
|
|
223
|
+
- **`docs/internals/IMPROVEMENT_STEMMING.md`** — internal design note for the Porter stemming bridge.
|
|
224
|
+
- **SECURITY.md** — updated to document the git-signals subprocess, the one-time model-weight download for the local embedding provider, and the strengthened symlink path guard.
|
|
225
|
+
|
|
226
|
+
### CI
|
|
227
|
+
|
|
228
|
+
- `.github/workflows/ci.yml` — verifies all 14 languages are present in `parse/languages.mjs`, checks the `.graph-indexer/` data directory is created (not the legacy root layout), validates that `prompts/CORE.md` and `prompts/INTEGRATION.md` are present, and runs a package-integrity step that `npm pack`s the tarball and asserts the `engine/`, `mcp/`, and `parse/` subdir modules ship and every entrypoint resolves its imports from the package.
|
|
229
|
+
- Removed `.github/workflows/publish-with-provenance.yml` (deprecated).
|
|
230
|
+
|
|
231
|
+
### Dependencies
|
|
232
|
+
|
|
233
|
+
New optional and production dependencies:
|
|
234
|
+
|
|
235
|
+
| Package | Version | Type | Purpose |
|
|
236
|
+
|---------|---------|------|---------|
|
|
237
|
+
| `tree-sitter-bash` | 0.23.3 | optional | Bash language parser |
|
|
238
|
+
| `tree-sitter-c` | 0.21.4 | optional | C language parser |
|
|
239
|
+
| `tree-sitter-swift` | 0.5.0 | optional | Swift language parser |
|
|
240
|
+
| `tree-sitter-scss` | 1.0.0 | optional | SCSS language parser |
|
|
241
|
+
| `@huggingface/transformers` | 3.8.1 | optional | In-process local embedding model |
|
|
242
|
+
|
|
243
|
+
(`hnswlib-node` 3.0.0, the optional approximate-nearest-neighbour vector index, already shipped in v1.2.0 — it is not new in v2.0.0.)
|
|
244
|
+
|
|
245
|
+
### Module reorganization (breaking only for deep imports)
|
|
246
|
+
|
|
247
|
+
Internal source files were grouped into directories. The CLI bins, the MCP server, and the default behaviour are unchanged; only code that imported a specific internal file path is affected. The package entry point (`import 'graph-indexer'`) now resolves to `engine/memory.mjs`.
|
|
248
|
+
|
|
249
|
+
| Old path | New path |
|
|
250
|
+
|----------|----------|
|
|
251
|
+
| `core-engine.mjs` | `engine/memory.mjs` |
|
|
252
|
+
| `sqlite-store.mjs` | `engine/sqlite.mjs` |
|
|
253
|
+
| `mcp-tools.mjs` | `mcp/tools.mjs` |
|
|
254
|
+
| `parser-utils.mjs` | split into `parse/extractor.mjs`, `parse/imports.mjs`, `parse/languages.mjs`, `parse/metadata.mjs`, `parse/routes.mjs` |
|
|
255
|
+
|
|
256
|
+
New modules added in the reorg: `engine/binary.mjs` (embedding binary codec + vector index), `mcp/format.mjs`, `mcp/topology.mjs`, `embeddings.mjs`, `git-signals.mjs`, `layout.mjs`, `daemon-ctl.mjs`, `daemon-lock.mjs`, `cli-ui.mjs`. (`config.mjs` and `storage.mjs` already existed in v1.2.0 and were extended, not added.)
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
### Metrics
|
|
261
|
+
|
|
262
|
+
#### Honesty note
|
|
263
|
+
|
|
264
|
+
v1.2.0 reported benchmark numbers using the enrichment + rerank path (the "best" configuration). v2.0.0 adopts a lexical-first honest baseline: the default path is lexical-only (zero dependencies), and opt-in configurations (embeddings, rerank, enrichment) are measured separately.
|
|
265
|
+
|
|
266
|
+
#### 5-suite benchmark (axios, express, NestJS, FastAPI, gin — 100 queries: 69 symbolic + 31 semantic)
|
|
267
|
+
|
|
268
|
+
This is the primary eval harness (`npm run test:eval`). All numbers are strict symbol-level (a hit requires the exact symbol, not just the correct file).
|
|
269
|
+
|
|
270
|
+
| Channel | s@1 | s@5 | MRR | Semantic rank-1 | Semantic s@5 | File-only inflation |
|
|
271
|
+
|---|---|---|---|---|---|---|
|
|
272
|
+
| **v1.2.0 — Hybrid + enrichment + rerank (best path)** | — | **0.82** | 0.73 | 0.35 | 0.65 | ~11.6% |
|
|
273
|
+
| **v2.0.0 — Lexical default (+ stemming + IDF path boost)** | 0.58 | **0.81** | 0.65 | 0.19 | **0.61** | ~11.6% |
|
|
274
|
+
| v2.0.0 — Hybrid nomic, no rerank | 0.60 | 0.79 | 0.68 | 0.23 | 0.58 | ~11.6% |
|
|
275
|
+
| v2.0.0 — Hybrid nomic + LLM rerank | 0.64 | 0.80 | 0.71 | 0.35 | 0.61 | ~11.6% |
|
|
276
|
+
|
|
277
|
+
Key changes vs v1.2.0:
|
|
278
|
+
|
|
279
|
+
- **Lexical s@5 reaches 0.81** (within 1 point of v1.2.0's best path of 0.82) with zero external dependencies, driven by Porter stemming and the IDF-gated path boost.
|
|
280
|
+
- **Semantic s@5 (lexical):** 0.48 floor → **0.61** from stemming + the IDF-gated path boost combined (+13 points, +27%); the path boost alone contributes +0.097 (0.516 → 0.613).
|
|
281
|
+
- **Overall s@5 (lexical):** 0.775 → **0.807** after IDF-gated path boost (+0.032, ≈+3 points).
|
|
282
|
+
- **Symbolic rank-1 unchanged** at 0.75 (byte-identical — stemming and path boost are additive and non-destructive to exact-match ranking).
|
|
283
|
+
- **File-only inflation corrected:** the v1.2.0 `0.1%` figure was a 100× display-formatting bug (`fmtPct` rendered a 0–1 fraction without the ×100 scale); the true lexical value is **~11.6%** overall, while the held-out validation set has **0.0%** inflation. The hybrid path does not increase inflation over lexical.
|
|
284
|
+
- **Backend parity:** memory vs SQLite results are byte-identical on all 100 queries (enforced in CI).
|
|
285
|
+
|
|
286
|
+
#### Stemming: held-out semantic recall
|
|
287
|
+
|
|
288
|
+
Porter stemming was validated on the **held-out query set** (15 queries, never used for tuning):
|
|
289
|
+
|
|
290
|
+
| Metric | Before stemming | After stemming | Δ |
|
|
291
|
+
|---|---|---|---|
|
|
292
|
+
| Held-out rank-1 (overall) | 0.733 | **0.800** | **+0.067** |
|
|
293
|
+
| Held-out semantic rank-1 | 0.200 | **0.400** | **+0.20 (+100%)** |
|
|
294
|
+
| Held-out MRR (overall) | 0.789 | **0.833** | +0.044 |
|
|
295
|
+
| Symbolic rank-1 / MRR (strict, tuning) | 0.755 / 0.807 | 0.755 / 0.807 | 0 (byte-identical) |
|
|
296
|
+
|
|
297
|
+
#### IDF-gated path boost delta (lexical, tuning channel)
|
|
298
|
+
|
|
299
|
+
| Metric | Before | After | Δ |
|
|
300
|
+
|---|---|---|---|
|
|
301
|
+
| Overall s@5 | 0.7749 | **0.8065** | **+0.0316** |
|
|
302
|
+
| Semantic s@5 (31q) | 0.5161 | **0.6129** | **+0.0968** |
|
|
303
|
+
| Semantic rank-1 | 0.1935 | 0.1935 | 0 |
|
|
304
|
+
| Symbolic rank-1 | 0.7536 | 0.7536 | 0 (byte-identical) |
|
|
305
|
+
|
|
306
|
+
#### Embedding quality: qwen3-embedding:4b (express suite, 21 queries)
|
|
307
|
+
|
|
308
|
+
| Channel | Semantic rank-1 | Semantic s@5 | Symbolic rank-1 |
|
|
309
|
+
|---|---|---|---|
|
|
310
|
+
| Lexical floor | 0.43 | 0.57 | 0.79 |
|
|
311
|
+
| nomic-embed-text (768-dim) | 0.43 | 0.57 | 0.79 |
|
|
312
|
+
| **qwen3-embedding:4b (2560-dim)** | 0.43 | **0.71** | 0.79 |
|
|
313
|
+
|
|
314
|
+
qwen3-embedding:4b raises express semantic s@5 from 0.57 to 0.71 (+24%) while holding rank-1 and symbolic precision. Cost: qwen3 4b is **~23× slower** than nomic-embed-text. The embedding channel is **off by default**; enable with `--embed-provider ollama --embed-model qwen3-embedding:4b` or the equivalent config.
|
|
315
|
+
|
|
316
|
+
#### Enrichment + HyDE (gin suite, semantic, opt-in)
|
|
317
|
+
|
|
318
|
+
| Config | Semantic rank-1 | Semantic MRR | Semantic s@5 |
|
|
319
|
+
|---|---|---|---|
|
|
320
|
+
| Lexical floor | 0.20 | 0.30 | 0.60 |
|
|
321
|
+
| Plain + rerank (7B) | 0.60 | 0.80 | 1.00 |
|
|
322
|
+
| Enriched (7B) + rerank | **0.80** | **0.87** | 1.00 |
|
|
323
|
+
| HyDE (qwen2.5-coder:7B), enriched corpus | **0.00** | 0.34 | **1.00** |
|
|
324
|
+
|
|
325
|
+
Key finding: enrichment **inverts** depending on embedder strength — it helps with weak embedders (MiniLM) but **regresses** with strong ones (qwen3). Enrichment and rerank remain correctly off by default.
|
|
326
|
+
|
|
327
|
+
#### Cross-language benchmark (18 fixtures, new in v2.0.0)
|
|
328
|
+
|
|
329
|
+
A new reproducible 18-language × 8-config matrix (`bench/`), covering all 14 supported languages and 8 frameworks on pinned OSS fixtures. All cells are cold, isolated builds; scoring is strict (exact symbol). Backend parity is 18/18 byte-identical.
|
|
330
|
+
|
|
331
|
+
| Metric (default lexical path, 18 languages) | Mean | Min | Max |
|
|
332
|
+
|---|---|---|---|
|
|
333
|
+
| Symbolic rank-1 | **70%** | 43% | 86% |
|
|
334
|
+
| Semantic rank-1 | **22%** | 0% | 67% |
|
|
335
|
+
| Semantic s@5 | **60%** | 0% | 100% |
|
|
336
|
+
|
|
337
|
+
Per-language symbolic rank-1 highlights (default path):
|
|
338
|
+
|
|
339
|
+
| Language | Fixture | Symbolic rank-1 | Semantic rank-1 | Semantic s@5 |
|
|
340
|
+
|---|---|---|---|---|
|
|
341
|
+
| Go | gin | 85% | 20% | 100% |
|
|
342
|
+
| Java/Spring | spring | 86% | 25% | 100% |
|
|
343
|
+
| Python/Django | django | 83% | 33% | 67% |
|
|
344
|
+
| Swift | alamofire | 78% | 67% | 100% |
|
|
345
|
+
| TypeScript | nestjs | 57% | 14% | 43% |
|
|
346
|
+
| SCSS | css | 43% | 25% | 50% |
|
|
347
|
+
| Bash | nvm | 44% | 33% | 67% |
|
|
348
|
+
|
|
349
|
+
Known structural gaps (invocation-verified, per `docs/benchmarks/BENCH_LANGUAGES.md` — the single source of truth): `get_call_graph` is effectively empty for SCSS (only 6 trivial `@include`/`@function` edges) and degraded to class-granularity for Java/Spring (call edges resolve, but methods aren't their own chunks). C#/ASP.NET, PHP/Laravel, and PHP/Symfony all resolve call edges (the Tier-1 C# and PHP call-graph fixes landed in v2.0.0). The `find_references` type-usage (`type_refs`) channel is empty for express-js, Django, Rails, SCSS, and Bash.
|
|
350
|
+
|
|
351
|
+
#### Token savings (18 languages)
|
|
352
|
+
|
|
353
|
+
Savings are measured as the token footprint of top-5 compact cards vs reading the full source files of those hits. The amortized column models one `get_chunk` full-body expansion (the recommended agent pattern).
|
|
354
|
+
|
|
355
|
+
| Language | Top-5 vs full files | Amortized (net 1 `get_chunk`) |
|
|
356
|
+
|---|---|---|
|
|
357
|
+
| Bash | 95.9% | 98.2% |
|
|
358
|
+
| C | 91.9% | 96.8% |
|
|
359
|
+
| Rust | 89.2% | 95.0% |
|
|
360
|
+
| Express (JS) | 88.9% | 93.5% |
|
|
361
|
+
| Go | 87.9% | 91.0% |
|
|
362
|
+
| FastAPI (Python) | 84.0% | 91.4% |
|
|
363
|
+
| Swift | 83.1% | 94.2% |
|
|
364
|
+
| Django (Python) | 78.1% | 90.9% |
|
|
365
|
+
| axios (JS) | 73.1% | 85.2% |
|
|
366
|
+
| NestJS (TS) | 66.7% | 84.4% |
|
|
367
|
+
| SCSS | 65.4% | 79.4% |
|
|
368
|
+
| Kotlin/Android | 62.7% | 82.3% |
|
|
369
|
+
| Ruby/Rails | 58.6% | 81.3% |
|
|
370
|
+
| PHP/Symfony | 57.6% | 86.1% |
|
|
371
|
+
| React (TS) | 56.4% | 82.1% |
|
|
372
|
+
| Java/Spring | 50.8% | 79.8% |
|
|
373
|
+
| PHP/Laravel | 32.4% | 72.7% |
|
|
374
|
+
| C#/ASP.NET | 19.3% | 65.1% |
|
|
375
|
+
|
|
376
|
+
v1.2.0 reported a mean of 79.0% (gross, 5 suites). v2.0.0's gross mean across the original 5 suites is 79.6%. The amortized figure (new metric) accounts for get_chunk expansions and is 65%–98% across languages.
|
|
377
|
+
|
|
378
|
+
#### End-to-end agent trace (real MCP tools, LLM sub-agent)
|
|
379
|
+
|
|
380
|
+
New in v2.0.0. Tested on 3 fixtures with real MCP tool calls traced through a sub-agent:
|
|
381
|
+
|
|
382
|
+
| Fixture | Tasks | Success | Avg tool calls | Avg tokens/task |
|
|
383
|
+
|---|---|---|---|---|
|
|
384
|
+
| gin (Go) | 2 | 100% | 4.0 | 4,286 |
|
|
385
|
+
| express-js (JS) | 2 | 100% | 3.5 | 2,089 |
|
|
386
|
+
| django (Python) | 2 | 100% | 5.0 | 1,763 |
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## [1.2.0] — (main branch baseline)
|
|
391
|
+
|
|
392
|
+
Initial stable release with:
|
|
393
|
+
|
|
394
|
+
- AST indexing for 11 languages (TypeScript/JavaScript, Python, Go, Rust, Java, Kotlin, C#, Ruby, PHP, CSS/SCSS)
|
|
395
|
+
- Hybrid lexical + optional dense-vector search with RRF fusion
|
|
396
|
+
- 8 MCP tools: `search_code`, `get_chunk`, `resolve_symbol`, `get_chunk_summary`, `get_file_skeleton`, `get_call_graph`, `get_repo_map`, `list_index_stats`
|
|
397
|
+
- In-memory and SQLite storage backends with backend parity
|
|
398
|
+
- Optional LLM enrichment and reranker
|
|
399
|
+
- Watch daemon with file-change tracking
|
|
400
|
+
- Interactive `npx graph-indexer` setup wizard
|
|
401
|
+
|
|
402
|
+
### Metrics (v1.2.0, 5 suites — 100 queries)
|
|
403
|
+
|
|
404
|
+
Measured with the enrichment + rerank path active (the "best" configuration at the time).
|
|
405
|
+
|
|
406
|
+
| Channel | Strict s@5 | Rank-1 | MRR | Symbolic rank-1 | Semantic rank-1 | Semantic s@5 |
|
|
407
|
+
|---|---|---|---|---|---|---|
|
|
408
|
+
| Hybrid + enrichment + rerank | 0.82 | 0.66 | 0.73 | 0.80 | 0.35 | 0.65 |
|
|
409
|
+
| Hybrid (no rerank) | — | — | — | — | 0.23 | 0.55 |
|
|
410
|
+
| Lexical-only | — | — | — | — | 0.19 | 0.48 |
|
|
411
|
+
|
|
412
|
+
Token savings: 79.0% (gross, top-5 cards vs full-file reads). Loose recall@5: 0.90.
|