graph-indexer 3.0.0 → 3.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.
Files changed (60) hide show
  1. package/CHANGELOG.md +456 -0
  2. package/README.md +72 -15
  3. package/SECURITY.md +21 -4
  4. package/bin/graph-indexer.mjs +46 -5
  5. package/integrations/claude-code/.claude-plugin/plugin.json +3 -2
  6. package/integrations/claude-code/agents/code-structure.md +0 -3
  7. package/integrations/claude-code/skills/setup/SKILL.md +26 -0
  8. package/integrations/claude-code-local/.claude-plugin/plugin.json +9 -0
  9. package/integrations/{claude-code → claude-code-local}/.mcp.json +1 -1
  10. package/integrations/claude-code-local/README.md +10 -0
  11. package/integrations/claude-code-local/agents/code-structure.md +22 -0
  12. package/integrations/{claude-code → claude-code-local}/hooks/gi-hook.mjs +12 -6
  13. package/integrations/opencode/graph-indexer.js +13 -8
  14. package/package.json +2 -2
  15. package/src/cli/helper.mjs +15 -1
  16. package/src/cli/hook.mjs +140 -48
  17. package/src/cli/init.mjs +49 -17
  18. package/src/cli/resident.mjs +15 -5
  19. package/src/index/discover.mjs +3 -5
  20. package/src/index/indexer.mjs +77 -41
  21. package/src/index/modules.mjs +82 -7
  22. package/src/index/parse-worker.mjs +12 -0
  23. package/src/index/parsepool.mjs +73 -0
  24. package/src/index/resolver.mjs +371 -25
  25. package/src/mcp/server.mjs +16 -12
  26. package/src/mcp/tools.mjs +419 -26
  27. package/src/parse/arity.mjs +41 -3
  28. package/src/parse/extract.mjs +277 -46
  29. package/src/parse/lang/ecma.mjs +2 -0
  30. package/src/parse/lang/go.mjs +2 -0
  31. package/src/parse/lang/jvm.mjs +33 -0
  32. package/src/parse/lang/markdown.mjs +92 -0
  33. package/src/parse/lang/python.mjs +43 -0
  34. package/src/parse/lang/rust.mjs +215 -15
  35. package/src/parse/lang/sfc.mjs +125 -0
  36. package/src/parse/languages.mjs +9 -2
  37. package/src/query/check.mjs +55 -17
  38. package/src/query/facts.mjs +3 -2
  39. package/src/query/injection-spring.mjs +405 -0
  40. package/src/query/injection.mjs +173 -0
  41. package/src/query/intel.mjs +87 -22
  42. package/src/query/issue.mjs +156 -0
  43. package/src/query/orm.mjs +286 -0
  44. package/src/query/read.mjs +6 -1
  45. package/src/query/report.mjs +53 -0
  46. package/src/query/routes.mjs +142 -0
  47. package/src/query/showcase.mjs +59 -0
  48. package/src/query/testcmd.mjs +25 -14
  49. package/src/query/textsearch.mjs +29 -11
  50. package/src/search/search.mjs +85 -17
  51. package/src/search/semantic.mjs +201 -0
  52. package/src/search/vectors.mjs +122 -0
  53. package/src/store/db.mjs +22 -4
  54. package/src/util/git.mjs +30 -0
  55. package/src/util/paths.mjs +40 -1
  56. package/src/util/regex.mjs +2 -0
  57. package/vendor/grammars/README.md +47 -0
  58. package/vendor/grammars/kotlin.wasm.gz +0 -0
  59. package/vendor/grammars/manifest.json +4 -4
  60. /package/integrations/{claude-code → claude-code-local}/hooks/hooks.json +0 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,462 @@ All notable changes to graph-indexer are documented here. Dates are in YYYY-MM-D
4
4
 
5
5
  ---
6
6
 
7
+ ## [3.1.0] — 2026-11-08
8
+
9
+ ### Added
10
+
11
+ - **Several queries in one call.**
12
+ - `search_code` and `explore` take `queries: [...]` (up to 6) besides `query`, and the CLI takes
13
+ `--or` once per extra query.
14
+ - The results of each query are fused by reciprocal rank (k = 60), so an agent that does not know
15
+ which of three names the code uses asks once instead of three times.
16
+ - **Documentation is indexed.**
17
+ - Each Markdown heading (`.md`, `.mdx`, `.markdown`) is a `section` definition, named by its heading
18
+ path (`Guide › Install`) and spanning to the next heading of the same or a higher level.
19
+ `read_code` reads a section by its name.
20
+ - Sections are kept in an index of their own and are searched only when the query asks for docs,
21
+ design, the reason for something or a guide (or with `docs: true`). Code queries never see them:
22
+ symbol search MRR@10 0.761 against 0.763 before, localization unchanged (file Acc@1 0.550).
23
+ - `check_changes` and the hooks still report on code files only.
24
+ - **Optional semantic search (opt-in).**
25
+ - `graph-indexer semantic install` downloads a static code embedding model
26
+ (minishlab/potion-code-16M-v2, MIT, 32 MB, pinned revision, SHA-256 checked) into
27
+ `~/.cache/graph-indexer/models`. With `GRAPH_INDEXER_SEMANTIC=1` search adds a fourth channel,
28
+ by meaning, to the name, concept and file channels.
29
+ - It runs in plain JavaScript, with no native code and no network after the install. Vectors are
30
+ stored in the index as int8 and computed lazily, file by file, so an edit re-embeds only that file.
31
+ - It is off by default because it does not help everywhere. Over the 377 search queries, top-5
32
+ success rises from 0.841 to 0.859, and from 0.674 to 0.728 on queries written in words. On
33
+ localizing 169 real commits it costs a little: file Acc@1 0.533–0.538 against 0.550 at the chosen
34
+ weights, 0.485 at twice and four times them. So the default stays lexical and graph-based.
35
+ - [docs/BENCHMARKS.md](docs/BENCHMARKS.md#3-symbol-search).
36
+ - **`explore` reads issues.**
37
+ - When the query reads like an issue (a long text, code spans or blocks), definitions are ranked
38
+ by where the issue's names meet (`src/query/issue.mjs`).
39
+ - Names are matched by their words, so `DATE_DIFF`, `DateDiff` and `_date_diff_sql` are one
40
+ term, and title terms weigh more.
41
+ - A word in capitals that only names an entry of a table with 100 or more members (a token enum)
42
+ is no longer looked up as a definition.
43
+ - Offline, over 50 fresh issues ([`bench/agentic/localize.mjs`](bench/agentic/localize.mjs)):
44
+ explore shows the code the fix changes in 25 issues instead of 12, given either the issue or the
45
+ one-line keyword query agents write.
46
+ - In real sessions this did not make fixing issues cheaper: cost 1.12 (1.01–1.25) and 1.03
47
+ (0.96–1.12) of Claude Code alone in the pilots `issue1p` and `issue2`.
48
+ - `init`'s instructions now suggest the issue's names and terms as the query.
49
+ - [docs/AGENTIC-BENCHMARK.md](docs/AGENTIC-BENCHMARK.md#issues-as-written-explore-offline-and-in-real-sessions-issue1p-issue2-47-tasks-94-runs).
50
+ - **`check_changes` with `by_name: true` lists, apart, the test files that name a changed module in
51
+ a string**, such as `transpile(sql, write="presto")` for `generators/presto.py`. They reach the
52
+ change through a dialect, plugin or backend lookup that no reference shows. It is off by default.
53
+ - Over 50 fresh issues, adding them takes the existing test files the gold fix affects from 11
54
+ of 15 detected to 14 of 15, and the developer's test files from 86 % to 94 %.
55
+ - The share of test files avoided falls from 88.5 % to 68.8 %.
56
+ - The gain came from sqlglot, where the rule was found. On SymPy, measured afterwards, it found
57
+ nothing more (1 of 2 affected files either way), so it stays opt-in. Neither setting reaches
58
+ the goal of 99 % of affected files.
59
+ - Measured with `localize.mjs select`, which runs the whole suite with and without the gold fix,
60
+ the way NameRTS measures test selection.
61
+ - **B5, silent errors** (`bench/agentic/mine-b5.mjs`): tasks from real commits whose partial fix,
62
+ the changed definition without the updates its existing users needed, breaks tests elsewhere.
63
+ - The criterion was fixed before mining ([PLAN-IDE-AGENTE §4](docs/PLAN-IDE-AGENTE.md)).
64
+ - `validate-fresh` checks that RIPPLE passes with the real fix and fails with the partial one.
65
+ - 17 tasks with reviewed statements, from 8 repositories: sqlglot 6, haystack 3, sympy 2,
66
+ sqlalchemy 2, mypy 2, scrapy 1, xarray 1. The goal is 30.
67
+ - A test of RIPPLE that does not run with the partial fix (its module, a conftest or an import
68
+ no longer loads, or its file never finishes) now makes the task loud, as rule 3 says; the
69
+ miner used to count it as broken. `mine-b5.mjs --recheck` applied the rule again to every kept
70
+ task and withdrew 8, six of them already stated. A test missing while its file ran cleanly
71
+ does not exist without the rest of the fix and is dropped from RIPPLE instead.
72
+ - The miner stops when a repository has no virtualenv and `GI_PYTHON` is unset, and the test
73
+ runner takes pytest from `GI_PYTHON` when `GI_PYTEST` is unset. Without them every file
74
+ "crashed" under the system Python and read as loud.
75
+ - Test runs remove the result files that runs killed on their timeout left behind.
76
+ - A first pilot (Sonnet 5.5 against Haiku 5.5, 6 B5 tasks with 6 questions and 4 refactors,
77
+ 128 runs; [AGENTIC-BENCHMARK](docs/AGENTIC-BENCHMARK.md)) found no silent error in either model
78
+ or arm: RIPPLE passed in all 48 B5 runs, so these tasks cannot show the B5 target. Haiku cost
79
+ about an eighth of Sonnet and solved the same; graph-indexer cost 1.22 (1.03–1.35) of no index
80
+ on B5 with Haiku and 1.00 with Sonnet.
81
+ - **Bench: the repositories' code runs sandboxed.** Tests at mining, measuring and grading run
82
+ under `bench/agentic/sandbox.sb` (`sandboxed()` in `lib.mjs`):
83
+ - no network beyond localhost;
84
+ - no writes outside the checkout and temporary directories;
85
+ - no reads of credentials;
86
+ - it fails closed.
87
+ - **Bench: test runs share the machine.** Parallel runs use only idle cores (`fairJobs`), and the
88
+ rule that drops slow tests uses CPU time instead of the wall clock.
89
+ - **[docs/PLAN-IDE-AGENTE.md](docs/PLAN-IDE-AGENTE.md)**, the active plan: graph-indexer as the
90
+ agent's IDE and memory (exact references, the impact of an edit, verification), with B3 as a
91
+ no-regression check and a new bench of silent errors (B5).
92
+
93
+ - **The arena's session axis** (`bench/agentic/competitors.mjs`, arms `cc+codegraph`, `cc+cbm`,
94
+ `cc+serena`, `cc+serena-prompt`): Claude Code sessions with CodeGraph 1.6.2,
95
+ codebase-memory-mcp 0.11.0 or Serena 1.7.0, each installed by its own installer or guide, next
96
+ to graph-indexer and to no tool. `session-round.mjs --in-repo` runs sessions in the repository
97
+ with the task as the prompt and the installed files committed first. `cleanup.mjs` frees a
98
+ graded round's worktrees and checkouts, and `session-round` runs it at the end. New
99
+ compiler-verified question set `qa-nestjs-r7`. Result (`arena1`, 170 runs, Sonnet 5.5): graph-indexer is the
100
+ only tool the agent called and the only arm that answered all 11 questions. Its cost against
101
+ CodeGraph was 0.65 / 0.74 / 0.87 (questions / refactors / issues), against codebase-memory-mcp
102
+ 0.79 / 0.91 / 0.96, and against Serena 0.87 / 0.93 / 1.12. It loses to Serena on issues, where
103
+ Serena's system prompt, not its tools, carries the gain (control `arena1b`, 45 runs).
104
+ [docs/AGENTIC-BENCHMARK.md](docs/AGENTIC-BENCHMARK.md#against-other-tools-in-real-sessions-arena1-34-tasks-170-runs).
105
+
106
+ - **`explore`**: one call that opens a task. For a question, bug or feature in words and/or named
107
+ symbols it returns the most relevant definitions with their code (line numbers kept for
108
+ editing), their direct callers, overrides and subclasses, one shared card of where the names
109
+ that code uses are defined, and the tests that reach it with the command to run them — inside
110
+ `max_tokens` (default 2,000), naming what it left out. It composes the existing engine (search,
111
+ references, method families, tests) and loads up front alongside `find_references`. CLI:
112
+ `graph-indexer explore <task in words> [--symbols a,b] [--path P] [--max-tokens N]`. Not yet
113
+ measured in agent sessions ([docs/PLAN-REFERENCIA.md](docs/PLAN-REFERENCIA.md) §1.1–1.2).
114
+ - **`init` ends with an answer about your repository** (`graph-indexer demo`): it builds the index
115
+ and picks a method that shares its name with other definitions, then shows what `grep -w` returns
116
+ for that name next to the calls the index binds to that one definition — on nestjs,
117
+ `NestApplicationContext.get`: 4,238 grep lines in 300 files against 129 calls in 67 files.
118
+ `--no-demo` skips it.
119
+ - **HTTP routes and their handlers** (`graph-indexer routes [path | METHOD /path]`), the edge no
120
+ compiler sees: FastAPI and Flask decorators (with `APIRouter(prefix=…)` / `Blueprint(url_prefix=…)`
121
+ prefixes), NestJS controllers, Spring mappings (with the class's `@RequestMapping`), Express /
122
+ Fastify / Koa / Hono and Gin / Echo / chi / net/http registrations, whose handler argument is
123
+ bound through the index. Route parameters match in any syntax (`{id}`, `:id`, `<int:id>`).
124
+ `explore` follows a route in its question — "what happens on POST /owners/42/edit" — to the handler
125
+ and says the framework calls it.
126
+ - **`graph-indexer report`**: a summary to paste into an issue — versions, index size, the share of
127
+ calls bound and exact per language, warm latencies — with no paths, names or code. A "wrong
128
+ answer" issue template asks for it and for the command that reproduces the problem.
129
+
130
+ - **The arena** (`bench/arena/`): any code-intelligence tool with an MCP server is scored against
131
+ the same compiler and type-checker oracles and samples as graph-indexer (`eval-graph.mjs` and
132
+ `eval-graph-python.mjs` take `--systems`), with its answer size, plus cold index time and size
133
+ (`arena.mjs speed`). Tools are declared in a JSON file written from what their servers expose;
134
+ graph-indexer's own server entered that way matches its native scores within 0.007.
135
+ First results with CodeGraph 1.6.2 and codebase-memory-mcp 0.11.0 (`bench/arena/systems.json`):
136
+ on nestjs (TypeScript oracle) precision / recall 0.993 / 0.886 against 0.743 / 0.755 and
137
+ 0.559 / 0.263; on fastapi (pyright) 0.997 / 0.826 against 0.652 / 0.363 and 0.929 / 0.571; cold
138
+ index 1.9 s against 3.0 s and 11.8 s on nestjs. graph-indexer's answers are the largest (310
139
+ tokens median against 80 and 96). Answers that name declarations instead of lines are read
140
+ as an agent reads them (the declaration's lines that contain the name as code).
141
+
142
+ - **Rust checked against rust-analyzer** (`bench/eval-graph-rust.mjs`) on serde_json, ripgrep and
143
+ tokio, and fixed where it disagreed: calls inside macro arguments and items inside item macros
144
+ (`tri!(…)`, `assert_eq!(…)`, `cfg_rt! { … }`), the crates of a workspace used by name, modules in
145
+ the 2018 layout, `use a::b::module`, and paths walked module by module (`crate::T::new()`,
146
+ `super::f()`, `Enum::Variant`, `de::Trait`). On the same samples precision / recall went from
147
+ 0.895 / 0.523 to 0.955 / 0.841 (serde_json), 0.914 / 0.555 to 0.941 / 0.768 (ripgrep) and
148
+ 0.887 / 0.389 to 0.930 / 0.582 (tokio). Rust moves to the verified level. Every other oracle
149
+ (TypeScript, six Python, three Go, three Java repositories) gives the same scores as before.
150
+
151
+ - **Python: three fixes found on flask and click.** An absolute import resolves only from an
152
+ import root (`import json` inside `flask/json/` is the standard library, not that package);
153
+ `import a.b` also binds `a`; names inside the subscripts of qualified generics
154
+ (`t.Optional[Command]`, `t.Callable[[Ctx], "Group"]`) are type references. click's recall
155
+ against pyright goes from 0.888 to 0.980, flask's from 0.909 to 0.929; no repository loses.
156
+
157
+ - **Dependency injection (NestJS)**: what an `@Inject(TOKEN)` receives — the `provide: TOKEN,
158
+ useClass | useExisting | useFactory | useValue` or `ClientsModule` registration — is shown by
159
+ `find_references` (on the provider: where it is injected; on the token: who provides it) and
160
+ `explore` ("receives by injection"), and `graph-indexer inject [name]` lists every token. Checked
161
+ by hand on Twenty and nestjs: 45 provider claims for 40 injection points, none wrong; one
162
+ provider computed at run time is not found.
163
+
164
+ - **Dependency injection (Spring)**: the bean an `@Autowired` / `@Inject` / `@Resource` field,
165
+ a component's constructor, a `@Bean` method's parameter or a Lombok `@RequiredArgsConstructor`
166
+ field receives — chosen by `@Qualifier`, `@Primary` and name as Spring does, within the
167
+ application the class belongs to (read from `pom.xml` / `build.gradle`); Feign clients, Spring
168
+ Data repositories and MyBatis mappers are named as proxies generated at run time. Shown by
169
+ `find_references` (on a bean: where it is injected, or where another bean wins; on an interface:
170
+ which bean fills each point), `explore` ("receives by injection") and `graph-indexer inject`.
171
+ Checked by an independent reading on spring-petclinic, piggymetrics, jhipster-sample-app and
172
+ mall: 312 injection points; the first reading found 3 wrong claims and 1 missed point, all fixed
173
+ ([docs/BENCHMARKS.md](docs/BENCHMARKS.md#dependency-injection-spring)).
174
+
175
+ - **Vue and Svelte components.** `.vue` and `.svelte` files are indexed: the `<script>` as
176
+ TypeScript, and each template tag of a component the script imports (`<UserCard>`,
177
+ `<user-card>`) as a use of that component, so `find_references` on a component lists where it is
178
+ rendered. SvelteKit's `$lib` / `kit.alias` and `package.json` `imports` (`#/*`) resolve. On
179
+ open-webui 3,602 of 3,604 such tags are bound, every one to the file imported; on vue-vben-admin
180
+ 1,178 of 1,179, the 207 direct imports all to the file imported and 80 of 80 sampled through
181
+ barrels and workspace packages correct
182
+ ([docs/BENCHMARKS.md](docs/BENCHMARKS.md#components-in-vue-and-svelte-templates)).
183
+
184
+ - **ORM models: tables, migrations, Prisma's client.** Prisma `model` blocks, Django models,
185
+ SQLAlchemy / SQLModel tables and TypeORM entities, each with the table its ORM gives it, the
186
+ migration statements that name that table (SQL, Alembic `op.*`, TypeORM `queryRunner`, Django
187
+ operations and data migrations) and, for Prisma, the client calls that use the model
188
+ (`prisma.envelope.findFirst(…)`, typed only by the generated client). `find_references` on a
189
+ model (or a Prisma model's name) and `explore` show them; `graph-indexer models [name]` lists
190
+ them. Checked by an independent reading on documenso, full-stack-fastapi-template,
191
+ django-oscar and twenty: of 2,671 migration statements and 1,073 client calls claimed, none
192
+ wrong; the models and statements it found missing were added
193
+ ([docs/BENCHMARKS.md](docs/BENCHMARKS.md#orm-models-their-migrations-and-prismas-client)).
194
+
195
+ ### Changed
196
+
197
+ - **Kotlin grammar built from `fwcd/tree-sitter-kotlin` d6af96d** (2026-10-31; still no release
198
+ after 0.3.8), with the procedure of 3.0.2, whose build of 5b7b9b7 was first reproduced byte for
199
+ byte. It reads Kotlin 2.x syntax that left ERROR regions before: guard conditions in `when`
200
+ (`is X if cond ->`), `..<`, `$$` strings, context parameters, binary literals, `suspend {}`
201
+ lambdas. On 3,147 files of okhttp, kotlinx.coroutines, nowinandroid, Exposed and the android
202
+ fixture, files with an unreadable region go from 107 to 2 (both templates with
203
+ `{{packageName}}`), symbols from 43,306 to 43,629 and references from 306,426 to 307,084. 334
204
+ members that error recovery had cut out of their class are back in it; the only 2 symbols that
205
+ disappear were local `val`s taken for properties after a parse error.
206
+ - **Full and large updates parse on several threads.** Extraction (tree-sitter parse and queries)
207
+ runs in a pool of worker threads while the main thread writes earlier batches to SQLite; a file
208
+ the pool fails on is parsed again inline. The index written is identical (checked on nestjs:
209
+ symbols, references and imports). A full nestjs index on 4 CPUs: 3.3–3.8 s instead of 4.5–4.7 s.
210
+ Updates of fewer than 48 files parse inline as before; `GRAPH_INDEXER_WORKERS` sets the number of
211
+ threads (0 turns the pool off).
212
+
213
+ ### Fixed
214
+
215
+ *What #5 showed for Kotlin, made general: answers no longer present what the index could not see as seen.*
216
+
217
+ - **A qualified name whose owner declares nothing of that name is reported, not replaced.**
218
+ `refs RefreshPort.execute` used to answer with the uses of another type's `execute` (#5's
219
+ `OtherPort.execute`) under a "matches 2 definitions" note, and could add "Complete". It now says
220
+ that nothing indexed as `RefreshPort` declares `execute`, lists the definitions of that name, and
221
+ names the lines where `RefreshPort` appears in code the parser could not read. A qualifier that
222
+ names the file or a directory (`utils.helper`, `pkg.mod.func`) still finds the top-level definition.
223
+ - **A call on a receiver declared as a type the index lacks is no longer bound by name.** A
224
+ parameter typed with a library type (`p: Admin` from kafkajs) or with a type in a file the parser
225
+ could not read was bound to the only member of that name in the repository, as "likely". The call
226
+ now stays unbound and shows under "Possibly missed" when its file mentions the type. Checked
227
+ against the TypeScript compiler: of the 207 bindings this removes in nestjs and Twenty, none was
228
+ correct (26 bound DOM methods to repository code, such as `TextDecoder.decode` to
229
+ `JwtWrapperService.decode`; the other 181 were calls into libraries). Type parameters and wrappers (`T`, `InstanceType<typeof X>`) keep the old fallback. The
230
+ compiler oracles (nestjs, Twenty, gin, spring-petclinic) give the same precision and recall.
231
+ - **"Complete" is limited to what the parser read.** `find_references` and `call_graph` say
232
+ "Complete in the files the parser read" and list the lines where the name occurs in code the
233
+ parser could not read (an ERROR region, give or take a line), where a use can be missing or
234
+ bound to another definition.
235
+ - **The index records which lines the parser could not read**, not just whether a file had an
236
+ error, and no longer counts a token the parser inserted to keep the structure (a TSX
237
+ `` styled.div<Props>`…` ``, which flagged 370 files of Twenty, loses nothing). Indexes are rebuilt.
238
+ - **Python 3.15 lazy imports** (`lazy import x`, `lazy from m import y`, PEP 810) are read as
239
+ imports; tree-sitter-python does not know the soft keyword yet.
240
+ - **The `init --hooks` test** no longer depends on graph-indexer being installed globally on the
241
+ machine that runs it.
242
+ - **Rust: methods belong to the impl of the type their file sees.** A workspace can have several
243
+ types of one name (ripgrep has four `RegexMatcher`s, one per crate); the methods of every
244
+ `impl RegexMatcher` were members of all four, so `RegexMatcher::new` in the printer crate bound
245
+ to the test helper's. An impl's owner is now the type its own file resolves under that name, or
246
+ one of the same Cargo package. ripgrep's `RegexMatcher::new` goes from 0 of 96 uses to all 96.
247
+ - **Rust: `impl Trait for &'a mut T`** made the methods' owner the text `&'a mut T`, so `self`
248
+ inside them had no type (serde_json's whole `Deserializer`).
249
+ - **Rust: a bare name is never a method.** A parameter or local `path` passed as an argument was
250
+ bound to a method `path` declared in an `impl` block of the same file; a macro (`suffix!`) to a
251
+ method through `use super::*`. Methods with a receiver are no longer items of their module (Rust
252
+ and Go).
253
+ - **Rust: 2018 module paths.** `mod runtime; pub use runtime::Runtime;` names the child module,
254
+ not an external crate, so `use tokio::runtime::Runtime` resolved to nothing in tokio's own tests.
255
+ - **Rust: types the index did not follow**:
256
+ - tuples: `let (tx, rx) = channel()` with `-> (Sender<T>, Receiver<T>)`, `for (i, x) in
257
+ xs.iter().enumerate()`, and the fields of a tuple struct (`self.0`);
258
+ - wrappers built in place: `Arc::new(x)`, `Box::pin(f)`, `Pin::new(&mut x)`, `Rc::clone(&x)`;
259
+ - a crate's own `Mutex`, `RwLock` or `Semaphore` is that type, not a transparent wrapper;
260
+ - type parameters stand for their bound: `fn f<R: Read>(r: &mut R)`, `R::decode(r)`, a field
261
+ `formatter: F` in `impl<W, F: Formatter>`, `where` clauses;
262
+ - patterns: `Value::Object(map) =>` and `if let Self::Number(n) = …` bind the variant's field,
263
+ `other =>` the matched value, `if let Some(x)` / `Ok(x)` the wrapped value;
264
+ - `Self::new()`, and sequential shadowing (`let x = a; let x = x.to_map();`) with block,
265
+ match-arm and `if let` scopes, so a name rebound elsewhere in the function no longer erases
266
+ both types;
267
+ - `clone()`, `unwrap()`, `get_mut()`, `upgrade()` and the like keep the value only when its own
268
+ type has no method of that name (tokio's `Weak::upgrade` returns a sender);
269
+ - longer path calls (`grep::regex::RegexMatcherBuilder::new()`) and associated types
270
+ (`type Value = …;` inside an impl is not the crate's `Value`).
271
+ - **Rust: calls through traits.** tokio recall 0.799 → 0.838, serde_json 0.925 → 0.947, precision
272
+ up on both (docs/BENCHMARKS.md, third round):
273
+ - a type parameter with several bounds (`W: AsyncWrite + AsyncSeek`, `where` clauses, `impl A +
274
+ B`, `dyn A + B`) has the methods of each, not only the first trait's: tokio's
275
+ `AsyncSeek::poll_complete` goes from 0 of 19 uses to 17. Also through a field typed by the
276
+ parameter, a method returning it, and pin-project's `self.project()`;
277
+ - trait-path calls (`SerializeSeq::serialize_element(self, v)`, `Debug::fmt(n, f)`, `<T as
278
+ Trait>::m(x)`) reach the impl of that trait for the first argument's type. Methods record the
279
+ trait of their impl block (shown as "implements"), so `Debug::fmt` and `Display::fmt` of one
280
+ type are two methods;
281
+ - the uses of a trait's method include the calls of its implementations, as rust-analyzer counts
282
+ them;
283
+ - methods of one name in impls of different types (`Cursor<Vec<u8>>`, `Cursor<&mut [u8]>`) or
284
+ traits are no longer merged as redeclarations, `self.m()` reaches the `m` of its own impl block,
285
+ and a call on a value prefers the impl for the value over the one for `&T`;
286
+ - a blanket impl (`impl<M: Matcher> Matcher for &M`, `… for Box<M>`) belongs to the trait, and
287
+ `f32::to_bits(self)` inside `impl Float for f32` is the primitive's method, not recursion.
288
+ - **Java: overloads of one arity bind by argument types.** Calls record the types their arguments
289
+ show (literals, typed locals and fields, `new`, casts, array elements, `"a" + x`), and among
290
+ overloads taking that many arguments the resolver keeps those the types fit, exact matches
291
+ before widening and boxing: `append('-')` is `append(char)`, not `append(String)`.
292
+ - **Java: a single-type import from a library package** (`import java.io.UncheckedIOException;`)
293
+ is the library's type, not a same-name class elsewhere in the repository.
294
+ - **Java: `Outer.this.m()`**, `new Node(…)` inside `Node` (the class, not its constructor), and
295
+ nested types named through their outer one (`Token.TokenType.EOF`, `Map.Kind.valueOf(…)`).
296
+ - **Go: constants and variables declared with a type** (`ByCid SortOpt = "cid"`) carry it, so a
297
+ local assigned one (`s := ByCid; s.IsValid()`) is typed.
298
+ - **Python: names inside tuples and sets** (`(InvalidSchema, "x")` in a parametrize list) are uses.
299
+ - **Bench: `eval-graph-rust.mjs` reads inactive code from rust-analyzer 1.9x**, which reports it through
300
+ pull diagnostics and as `inactive_code`: with 1.99 the oracle saw no inactive line, so uses in
301
+ `#[cfg]`-disabled files and items counted against graph-indexer (tokio: 20,195 inactive lines).
302
+ - **Indexing no longer overflows the stack on a field assigned an element of itself**
303
+ (`self.impl = self.impl[0]`, in SQLAlchemy's `test/perf`): each lookup added one element access
304
+ to the type, without end, and the whole index failed. A type now stacks at most 8 element
305
+ accesses. The four reference oracles give identical results.
306
+ - **Names built into regular expressions are escaped in full** (`src/util/regex.mjs`), not only
307
+ their `$`; the location query `file.ext:name` no longer backtracks polynomially on long input.
308
+ Identifiers are matched as before: the four reference oracles give identical results.
309
+ - **`check` no longer says "overridden, so not reached" of a subclass whose own version uses the
310
+ changed one.** Examples are `TRANSFORMS = {**MySQLGenerator.TRANSFORMS, …}` and `super().m()`.
311
+ - Such subclasses, and their subclasses, are now listed as running the changed code, with their
312
+ tests.
313
+ - `change_impact` and the symbol facts count them too.
314
+ - On sqlglot-1a782c55, `check` had missed `test_singlestore.py`, which the fix breaks.
315
+ - **`check` no longer flags calls to a Python `@classmethod`** as not fitting ("takes 2 arguments"):
316
+ its `cls` was counted because a classmethod is marked static. On the 50 gold fixes of the fresh
317
+ issues it now reports no problems.
318
+ - **Bench:** sqlglot's test hint gave `unittest discover tests`, which fails to import the suite;
319
+ it now gives `discover -s tests -t .`.
320
+
321
+ - **Hooks follow work done in the shell.** On Sonnet 5.5 the B3 sessions read files with
322
+ chained `cd repo; sed -n a,bp f; sed -n …` commands and edited them with Python heredocs and
323
+ `sed -i`; the post-tool hook recognised only a single read after `cd DIR &&`, and checked edits
324
+ only after the edit tool. It now splits a shell line into its commands (outside quotes and
325
+ heredoc bodies, paths resolved against a `cd`) and resolves every read in it, and after any
326
+ other shell command runs the edit checker on the source files that command changed (working-tree
327
+ files whose modification time moved). Replaying the b3h3 pilot's `cc+gi+hooks` runs: context on
328
+ 50 of 191 calls instead of 33. Measured in sessions (b3h4, 15 issues, Sonnet 5.5): the hooks
329
+ named 256 definitions and followed shell edits in 14 of 15 sessions, the agent still never
330
+ called graph-indexer, and the arm cost 1.07 (1.01–1.12) of working without it — a loss.
331
+
332
+ - **TypeScript default exports.** `export { default as X } from './x'` now follows to x's default
333
+ export; an anonymous `export default function () {}` counts as exported; a default import binds
334
+ to the module's own definition or default export before anything the module itself imports
335
+ under the importer's name. Found on vue-vben-admin; the TypeScript, Python, Go and Java oracles
336
+ give the same scores as before.
337
+
338
+ - **Claude Code 2.1.296 saw no graph-indexer tools.** It speaks MCP 2026-07-28, where every result
339
+ states `resultType: "complete"`; graph-indexer's results did not, so the server connected, its
340
+ instructions arrived, and `tools/list` was ignored — no tool, not even a deferred one. Results of
341
+ that protocol version now carry it (checked in a real session: the nine tools are listed and
342
+ `explore` answers). With it, the per-tool `anthropic/alwaysLoad` mark works again: `explore` and
343
+ `find_references` load up front (about 1,100 tokens with the instructions, against 3,000 for
344
+ marking the whole server).
345
+
346
+ - **Python references, measured against pyright** on six repositories
347
+ ([docs/BENCHMARKS.md](docs/BENCHMARKS.md), `bench/eval-graph-python.mjs`): recall rises on all six
348
+ (httpx 0.832 → 0.946, rich 0.867 → 0.971, fastapi 0.986 → 0.998) and precision on five (httpx
349
+ 0.910 → 0.998). `from pkg import submodule; submodule.X` no longer finds what `pkg/__init__.py`
350
+ re-exports as X; names re-exported from an uninstalled package are external instead of bound to a
351
+ same-name definition; untyped parameters and other locals shadow repository names; functions
352
+ nested in a function are no longer reached from outside it; `return name`, `A | B` unions,
353
+ `except` clauses, dictionary keys, generic arguments, `__all__`, `TypeVar(bound=…)`, `Class.attr`
354
+ and submodule chains are uses; `self.x` under a base class's property `x` is that property.
355
+ TypeScript (nestjs) and Java (spring-petclinic) are unchanged or better.
356
+ - **References in JavaScript/TypeScript modules**: a value read the extractor left unscoped (a
357
+ lambda parameter, a block-scoped `const`) was bound by name, at 0.6 confidence, to a top-level
358
+ name of another module (`cases.filter(c => …)` → `export const c` elsewhere). An ES module sees
359
+ another module's names only through an import, so such reads no longer bind across modules.
360
+ Reference accuracy on nestjs against the TypeScript compiler is unchanged (dispatch oracle,
361
+ 399 symbols: precision 0.998, recall 0.937).
362
+ - **Agent benchmark, mined Python issues (B3).** Sessions ran tests with the system Python
363
+ (no pytest) while grading used a virtualenv: they now get the grading virtualenv first on their
364
+ `PATH`, and the preflight checks it inside a session. `unittest` outcomes are recorded by a result
365
+ object instead of read from verbose text, which missed tests whose subtests fail. Mining puts SQL
366
+ fixtures in the hidden tests (and runs the modules that read them), runs each state twice to drop
367
+ flaky tests, and `validate-fresh.mjs` checks every task through the grader (base fails, the real
368
+ fix solves it three times). The fourth round's 11 tasks pass it unchanged.
369
+ - **"Defined elsewhere" card**: an unbound call no longer matches a constant or variable by name
370
+ (`Math.max(…)` listed a `const max` from another file).
371
+
372
+ ---
373
+
374
+ ## [3.0.2] — 2026-11-08
375
+
376
+ Kotlin `fun interface` support, contributed by [@jhernandez-movicoders](https://github.com/jhernandez-movicoders)
377
+ ([#5](https://github.com/MaquinaTech/graph-indexer/issues/5), [#6](https://github.com/MaquinaTech/graph-indexer/pull/6)),
378
+ and a Claude Code plugin that meets the plugin directory policy.
379
+
380
+ ### Fixed
381
+
382
+ - **Kotlin `fun interface`** ([#5](https://github.com/MaquinaTech/graph-indexer/issues/5)). The vendored grammar
383
+ (`fwcd/tree-sitter-kotlin` 0.3.8) could not parse a SAM interface (`fun interface Port { fun execute() }`).
384
+ The interface was missing, and its method became a top-level function. Calls through it were then bound to
385
+ another method with the same name, while `refs` still reported the list as complete.
386
+ - **The Kotlin grammar is now built from `fwcd/tree-sitter-kotlin` 5b7b9b7.** That commit adds `fun interface`
387
+ ([fwcd/tree-sitter-kotlin#169](https://github.com/fwcd/tree-sitter-kotlin/pull/169)) and is in no release yet.
388
+ It was built with `tree-sitter build --wasm` (tree-sitter-cli 0.25.10, the runtime's version).
389
+ - Measured on a 442-file Kotlin/Spring service: the 44 `fun interface` declarations and their methods are now
390
+ indexed.
391
+ - Resolved references go from 7,434 to 7,835, and exact ones from 5,325 to 5,545.
392
+ - No file loses a symbol. The one with one fewer had a local `val` taken for a property after a parse error
393
+ that the new grammar no longer makes.
394
+ - The grammar manifest is part of the index fingerprint, so existing indexes are rebuilt.
395
+
396
+ ### Changed
397
+
398
+ - **The Claude Code plugin listed in the plugin directory is Markdown and JSON only.** The directory
399
+ accepts command hooks, local MCP servers and scripts only from verified organizations, so
400
+ `integrations/claude-code` now holds the `code-structure` sub-agent (with the `name` and `description`
401
+ frontmatter keys only), a `setup` skill that tells Claude how to connect the MCP server
402
+ (`claude mcp add graph-indexer -- npx -y --prefix / graph-indexer@3.0.2 serve`, with the user's
403
+ consent) and the manifest.
404
+ - **The full plugin (MCP server, hooks, sub-agent) moved to `integrations/claude-code-local`** for manual
405
+ installs; `init --hooks` gives the same result without a plugin. Its launchers are pinned to the exact
406
+ release: the hook client ran `graph-indexer@3`, a range, and now runs `graph-indexer@3.0.2`. A test
407
+ keeps every pin equal to `package.json`.
408
+
409
+ ---
410
+
411
+ ## [3.0.1] — 2026-10-23
412
+
413
+ A security release. Upgrading is recommended for everyone; nothing else changes.
414
+
415
+ ### Security
416
+
417
+ - **Node.js 22.23.4+, 24.22.1+ or 26.12.1+** is now required (`engines`). These are the October
418
+ 2026 Node.js security releases, which fix CVE-2026-111497 (out-of-bounds write in the Brotli
419
+ decoder, remote code execution) and nine other vulnerabilities in TLS, DNS, HTTP keep-alive,
420
+ `crypto.randomUUID()` and the Permission Model. graph-indexer does not decompress Brotli itself,
421
+ but it runs inside the Node.js process the user has installed.
422
+ - **npx no longer runs a copy of graph-indexer from the repository.** Started from a repository
423
+ that ships `node_modules/graph-indexer` (3.x) with its `.bin` entry, `npx -y graph-indexer@3`
424
+ ran that code instead of the published package: the Claude Code plugin's hooks and MCP server,
425
+ the OpenCode / Kilo Code plugin and the configurations written by `init` did, on opening the
426
+ repository. Every npx command now passes `--prefix` so only the published package runs. Re-run
427
+ `npx -y --prefix / graph-indexer@3 init` to rewrite existing configurations.
428
+ - **`check_changes` refuses a `base` that git would read as an option.** `base: "--output=<file>"`
429
+ made `git diff` create or truncate any file the user can write. A revision must not start with
430
+ `-` or contain whitespace.
431
+ - **`read_code` reads only files inside the repository.** `../../etc/hosts` and a tracked symbolic
432
+ link to a file elsewhere were read and served. Paths are now resolved (`..` and links) and must
433
+ stay under the root; `.git/` and `.graph-indexer/` are not served. `search_text` and
434
+ `check_changes` skip links out of the repository too.
435
+ - **`search_text` stops a regular expression that backtracks catastrophically** (`(a+)+$`) after
436
+ 5 s and says so, instead of blocking the server and every other request.
437
+ - **The suggested test commands quote every file name** (a test file named `t$(cmd).test.ts`
438
+ reaches the runner as a name, not as a command substitution), since agents run those commands
439
+ as they are; test names that are not plain identifiers get no command.
440
+ - **git runs nothing that the indexed repository's own config names**: `core.fsmonitor`, hooks
441
+ (`core.hooksPath`), signature verification (`log.showSignature`), clean/smudge/process filters,
442
+ `diff.external` and textconv drivers are switched off on every git call, for repositories copied
443
+ together with their `.git` directory. Required filters such as Git LFS's no longer fail the diff.
444
+ - **A `.graph-indexer` committed as a symbolic link is refused** instead of having the index (which
445
+ contains excerpts of the source) written wherever it points. The directory is created with mode
446
+ 0700. The hook clients only talk to a socket, and a socket directory, owned by the user.
447
+ - **Manifests are read only as small regular files inside the repository**: a `tsconfig.json`,
448
+ `package.json` or `go.mod` linked to `/dev/zero` no longer hangs the indexer.
449
+ - **A file whose syntax makes queries super-linear** (tens of thousands of chained calls) is given
450
+ up after 15 s instead of stalling the index.
451
+ - **A field assigned an element of itself** (`self.impl = self.impl[0]`) no longer overflows the
452
+ indexer's stack.
453
+ - **Names are escaped as regular expressions** wherever they are matched in text, not only `$`, and
454
+ a `file:name` target no longer backtracks polynomially.
455
+ - **`init` does not write through links the repository committed** (`CLAUDE.md` → `~/.zshrc`,
456
+ `.claude` → `~/.claude`); it stops and names the file.
457
+ - **Per-session hook state** lives in a per-user directory with mode 0700 and is written
458
+ atomically, so another user of a shared temp directory cannot read it or redirect the write.
459
+ - On Windows, the hook clients quote the arguments they hand to `cmd.exe`.
460
+
461
+ ---
462
+
7
463
  ## [3.0.0] — 2026-09-23
8
464
 
9
465
  A rewrite around one goal: give coding agents answers that are exact, current and small.