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.
- package/CHANGELOG.md +456 -0
- package/README.md +72 -15
- package/SECURITY.md +21 -4
- package/bin/graph-indexer.mjs +46 -5
- package/integrations/claude-code/.claude-plugin/plugin.json +3 -2
- package/integrations/claude-code/agents/code-structure.md +0 -3
- package/integrations/claude-code/skills/setup/SKILL.md +26 -0
- package/integrations/claude-code-local/.claude-plugin/plugin.json +9 -0
- package/integrations/{claude-code → claude-code-local}/.mcp.json +1 -1
- package/integrations/claude-code-local/README.md +10 -0
- package/integrations/claude-code-local/agents/code-structure.md +22 -0
- package/integrations/{claude-code → claude-code-local}/hooks/gi-hook.mjs +12 -6
- package/integrations/opencode/graph-indexer.js +13 -8
- package/package.json +2 -2
- package/src/cli/helper.mjs +15 -1
- package/src/cli/hook.mjs +140 -48
- package/src/cli/init.mjs +49 -17
- package/src/cli/resident.mjs +15 -5
- package/src/index/discover.mjs +3 -5
- package/src/index/indexer.mjs +77 -41
- package/src/index/modules.mjs +82 -7
- package/src/index/parse-worker.mjs +12 -0
- package/src/index/parsepool.mjs +73 -0
- package/src/index/resolver.mjs +371 -25
- package/src/mcp/server.mjs +16 -12
- package/src/mcp/tools.mjs +419 -26
- package/src/parse/arity.mjs +41 -3
- package/src/parse/extract.mjs +277 -46
- package/src/parse/lang/ecma.mjs +2 -0
- package/src/parse/lang/go.mjs +2 -0
- package/src/parse/lang/jvm.mjs +33 -0
- package/src/parse/lang/markdown.mjs +92 -0
- package/src/parse/lang/python.mjs +43 -0
- package/src/parse/lang/rust.mjs +215 -15
- package/src/parse/lang/sfc.mjs +125 -0
- package/src/parse/languages.mjs +9 -2
- package/src/query/check.mjs +55 -17
- package/src/query/facts.mjs +3 -2
- package/src/query/injection-spring.mjs +405 -0
- package/src/query/injection.mjs +173 -0
- package/src/query/intel.mjs +87 -22
- package/src/query/issue.mjs +156 -0
- package/src/query/orm.mjs +286 -0
- package/src/query/read.mjs +6 -1
- package/src/query/report.mjs +53 -0
- package/src/query/routes.mjs +142 -0
- package/src/query/showcase.mjs +59 -0
- package/src/query/testcmd.mjs +25 -14
- package/src/query/textsearch.mjs +29 -11
- package/src/search/search.mjs +85 -17
- package/src/search/semantic.mjs +201 -0
- package/src/search/vectors.mjs +122 -0
- package/src/store/db.mjs +22 -4
- package/src/util/git.mjs +30 -0
- package/src/util/paths.mjs +40 -1
- package/src/util/regex.mjs +2 -0
- package/vendor/grammars/README.md +47 -0
- package/vendor/grammars/kotlin.wasm.gz +0 -0
- package/vendor/grammars/manifest.json +4 -4
- /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.
|