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