graph-indexer 2.1.1 → 3.0.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.
Files changed (131) hide show
  1. package/CHANGELOG.md +312 -1
  2. package/README.md +236 -312
  3. package/SECURITY.md +53 -58
  4. package/bin/graph-indexer.mjs +251 -0
  5. package/integrations/claude-code/.claude-plugin/plugin.json +9 -0
  6. package/integrations/claude-code/.mcp.json +8 -0
  7. package/integrations/claude-code/agents/code-structure.md +22 -0
  8. package/integrations/claude-code/hooks/gi-hook.mjs +103 -0
  9. package/integrations/claude-code/hooks/hooks.json +16 -0
  10. package/integrations/opencode/graph-indexer.js +129 -0
  11. package/package.json +32 -109
  12. package/src/cli/helper.mjs +67 -0
  13. package/src/cli/hook.mjs +366 -0
  14. package/src/cli/init.mjs +272 -0
  15. package/src/cli/resident.mjs +185 -0
  16. package/src/index/discover.mjs +128 -0
  17. package/src/index/fingerprint.mjs +30 -0
  18. package/src/index/graph.mjs +61 -0
  19. package/src/index/indexer.mjs +476 -0
  20. package/src/index/modules.mjs +332 -0
  21. package/src/index/resolver.mjs +961 -0
  22. package/src/mcp/render.mjs +60 -0
  23. package/src/mcp/server.mjs +147 -0
  24. package/src/mcp/tools.mjs +974 -0
  25. package/src/parse/arity.mjs +153 -0
  26. package/src/parse/extract.mjs +1038 -0
  27. package/src/parse/lang/csharp.mjs +106 -0
  28. package/src/parse/lang/ecma.mjs +438 -0
  29. package/src/parse/lang/go.mjs +194 -0
  30. package/src/parse/lang/jvm.mjs +118 -0
  31. package/src/parse/lang/kotlin.mjs +97 -0
  32. package/src/parse/lang/misc.mjs +115 -0
  33. package/src/parse/lang/native.mjs +126 -0
  34. package/src/parse/lang/python.mjs +153 -0
  35. package/src/parse/lang/rust.mjs +157 -0
  36. package/src/parse/lang/scripting.mjs +219 -0
  37. package/src/parse/languages.mjs +48 -0
  38. package/src/parse/runtime.mjs +56 -0
  39. package/src/query/check.mjs +345 -0
  40. package/src/query/facts.mjs +323 -0
  41. package/src/query/intel.mjs +683 -0
  42. package/src/query/read.mjs +81 -0
  43. package/src/query/testcmd.mjs +130 -0
  44. package/src/query/textsearch.mjs +178 -0
  45. package/src/search/search.mjs +210 -0
  46. package/src/search/tokenize.mjs +188 -0
  47. package/src/store/db.mjs +169 -0
  48. package/src/util/git.mjs +30 -0
  49. package/src/util/paths.mjs +63 -0
  50. package/src/util/regex.mjs +2 -0
  51. package/vendor/grammars/bash.wasm.gz +0 -0
  52. package/vendor/grammars/c.wasm.gz +0 -0
  53. package/vendor/grammars/c_sharp.wasm.gz +0 -0
  54. package/vendor/grammars/cpp.wasm.gz +0 -0
  55. package/vendor/grammars/css.wasm.gz +0 -0
  56. package/vendor/grammars/go.wasm.gz +0 -0
  57. package/vendor/grammars/java.wasm.gz +0 -0
  58. package/vendor/grammars/javascript.wasm.gz +0 -0
  59. package/vendor/grammars/kotlin.wasm.gz +0 -0
  60. package/vendor/grammars/manifest.json +148 -0
  61. package/vendor/grammars/php.wasm.gz +0 -0
  62. package/vendor/grammars/python.wasm.gz +0 -0
  63. package/vendor/grammars/ruby.wasm.gz +0 -0
  64. package/vendor/grammars/rust.wasm.gz +0 -0
  65. package/vendor/grammars/scala.wasm.gz +0 -0
  66. package/vendor/grammars/tsx.wasm.gz +0 -0
  67. package/vendor/grammars/typescript.wasm.gz +0 -0
  68. package/vendor/web-tree-sitter/web-tree-sitter.js +3980 -0
  69. package/vendor/web-tree-sitter/web-tree-sitter.wasm +0 -0
  70. package/cli-ui.mjs +0 -78
  71. package/colbert.mjs +0 -168
  72. package/config.mjs +0 -494
  73. package/daemon-ctl.mjs +0 -175
  74. package/daemon-lock.mjs +0 -69
  75. package/embedders/python/mlx_embed_server.py +0 -61
  76. package/embedders/python/requirements-mlx.txt +0 -5
  77. package/embedders/setup-mlx.mjs +0 -110
  78. package/embeddings.mjs +0 -571
  79. package/engine/binary.mjs +0 -437
  80. package/engine/memory.mjs +0 -1003
  81. package/engine/sqlite.mjs +0 -1136
  82. package/enrichment.mjs +0 -521
  83. package/git-signals.mjs +0 -248
  84. package/indexer.mjs +0 -546
  85. package/init.mjs +0 -1809
  86. package/layout.mjs +0 -123
  87. package/mcp/centrality.mjs +0 -117
  88. package/mcp/format.mjs +0 -61
  89. package/mcp/resolver.mjs +0 -116
  90. package/mcp/symbolgraph.mjs +0 -137
  91. package/mcp/taint.mjs +0 -225
  92. package/mcp/tools.mjs +0 -1582
  93. package/mcp/topology.mjs +0 -676
  94. package/mcp-server.mjs +0 -197
  95. package/parse/extractor.mjs +0 -628
  96. package/parse/imports.mjs +0 -365
  97. package/parse/interprocedural.mjs +0 -134
  98. package/parse/languages.mjs +0 -253
  99. package/parse/metadata.mjs +0 -701
  100. package/parse/routes.mjs +0 -327
  101. package/parse/scip.mjs +0 -391
  102. package/parse/taint-patterns.mjs +0 -119
  103. package/prompts/CORE.md +0 -91
  104. package/prompts/DOMAIN_TEMPLATE.md +0 -46
  105. package/prompts/INTEGRATION.md +0 -123
  106. package/prompts/frameworks/ANDROID.md +0 -17
  107. package/prompts/frameworks/ASPNET_CORE.md +0 -19
  108. package/prompts/frameworks/FASTAPI_DJANGO.md +0 -20
  109. package/prompts/frameworks/LARAVEL_SYMFONY.md +0 -22
  110. package/prompts/frameworks/NODE_EXPRESS_NESTJS.md +0 -19
  111. package/prompts/frameworks/RAILS.md +0 -20
  112. package/prompts/frameworks/REACT.md +0 -20
  113. package/prompts/frameworks/SPRING_BOOT.md +0 -20
  114. package/prompts/languages/BASH.md +0 -22
  115. package/prompts/languages/C.md +0 -21
  116. package/prompts/languages/CSHARP.md +0 -24
  117. package/prompts/languages/CSS_SCSS.md +0 -22
  118. package/prompts/languages/GO.md +0 -24
  119. package/prompts/languages/JAVA.md +0 -23
  120. package/prompts/languages/JAVASCRIPT_TYPESCRIPT.md +0 -24
  121. package/prompts/languages/KOTLIN.md +0 -24
  122. package/prompts/languages/PHP.md +0 -23
  123. package/prompts/languages/PYTHON.md +0 -23
  124. package/prompts/languages/RUBY.md +0 -23
  125. package/prompts/languages/RUST.md +0 -21
  126. package/prompts/languages/SWIFT.md +0 -21
  127. package/seal.mjs +0 -400
  128. package/search-core.mjs +0 -777
  129. package/search-sparse.mjs +0 -183
  130. package/storage.mjs +0 -73
  131. package/watch-daemon.mjs +0 -289
package/CHANGELOG.md CHANGED
@@ -4,7 +4,318 @@ All notable changes to graph-indexer are documented here. Dates are in YYYY-MM-D
4
4
 
5
5
  ---
6
6
 
7
- ## [Unreleased]
7
+ ## [3.0.1] — 2026-10-23
8
+
9
+ A security release. Upgrading is recommended for everyone; nothing else changes.
10
+
11
+ ### Security
12
+
13
+ - **Node.js 22.23.4+, 24.22.1+ or 26.12.1+** is now required (`engines`). These are the October
14
+ 2026 Node.js security releases, which fix CVE-2026-111497 (out-of-bounds write in the Brotli
15
+ decoder, remote code execution) and nine other vulnerabilities in TLS, DNS, HTTP keep-alive,
16
+ `crypto.randomUUID()` and the Permission Model. graph-indexer does not decompress Brotli itself,
17
+ but it runs inside the Node.js process the user has installed.
18
+ - **npx no longer runs a copy of graph-indexer from the repository.** Started from a repository
19
+ that ships `node_modules/graph-indexer` (3.x) with its `.bin` entry, `npx -y graph-indexer@3`
20
+ ran that code instead of the published package: the Claude Code plugin's hooks and MCP server,
21
+ the OpenCode / Kilo Code plugin and the configurations written by `init` did, on opening the
22
+ repository. Every npx command now passes `--prefix` so only the published package runs. Re-run
23
+ `npx -y --prefix / graph-indexer@3 init` to rewrite existing configurations.
24
+ - **`check_changes` refuses a `base` that git would read as an option.** `base: "--output=<file>"`
25
+ made `git diff` create or truncate any file the user can write. A revision must not start with
26
+ `-` or contain whitespace.
27
+ - **`read_code` reads only files inside the repository.** `../../etc/hosts` and a tracked symbolic
28
+ link to a file elsewhere were read and served. Paths are now resolved (`..` and links) and must
29
+ stay under the root; `.git/` and `.graph-indexer/` are not served. `search_text` and
30
+ `check_changes` skip links out of the repository too.
31
+ - **`search_text` stops a regular expression that backtracks catastrophically** (`(a+)+$`) after
32
+ 5 s and says so, instead of blocking the server and every other request.
33
+ - **The suggested test commands quote every file name** (a test file named `t$(cmd).test.ts`
34
+ reaches the runner as a name, not as a command substitution), since agents run those commands
35
+ as they are; test names that are not plain identifiers get no command.
36
+ - **git runs nothing that the indexed repository's own config names**: `core.fsmonitor`, hooks
37
+ (`core.hooksPath`), signature verification (`log.showSignature`), clean/smudge/process filters,
38
+ `diff.external` and textconv drivers are switched off on every git call, for repositories copied
39
+ together with their `.git` directory. Required filters such as Git LFS's no longer fail the diff.
40
+ - **A `.graph-indexer` committed as a symbolic link is refused** instead of having the index (which
41
+ contains excerpts of the source) written wherever it points. The directory is created with mode
42
+ 0700. The hook clients only talk to a socket, and a socket directory, owned by the user.
43
+ - **Manifests are read only as small regular files inside the repository**: a `tsconfig.json`,
44
+ `package.json` or `go.mod` linked to `/dev/zero` no longer hangs the indexer.
45
+ - **A file whose syntax makes queries super-linear** (tens of thousands of chained calls) is given
46
+ up after 15 s instead of stalling the index.
47
+ - **A field assigned an element of itself** (`self.impl = self.impl[0]`) no longer overflows the
48
+ indexer's stack.
49
+ - **Names are escaped as regular expressions** wherever they are matched in text, not only `$`, and
50
+ a `file:name` target no longer backtracks polynomially.
51
+ - **`init` does not write through links the repository committed** (`CLAUDE.md` → `~/.zshrc`,
52
+ `.claude` → `~/.claude`); it stops and names the file.
53
+ - **Per-session hook state** lives in a per-user directory with mode 0700 and is written
54
+ atomically, so another user of a shared temp directory cannot read it or redirect the write.
55
+ - On Windows, the hook clients quote the arguments they hand to `cmd.exe`.
56
+
57
+ ---
58
+
59
+ ## [3.0.0] — 2026-09-23
60
+
61
+ A rewrite around one goal: give coding agents answers that are exact, current and small.
62
+ Search, a real reference graph, change impact and edit checks, from a zero-dependency package
63
+ that needs no model, network or native build. Measured offline against the TypeScript compiler
64
+ and real commits, and end to end with coding agents
65
+ ([docs/AGENTIC-BENCHMARK.md](docs/AGENTIC-BENCHMARK.md)).
66
+
67
+ ### Breaking
68
+
69
+ - **Node.js 22.5+** is required (the index uses the built-in `node:sqlite`).
70
+ - **New tool surface** (eight read-only MCP tools): `search_code`, `search_text`, `read_code`,
71
+ `find_references`, `call_graph`, `change_impact`, `check_changes`, `outline`. `get_symbol`
72
+ (one definition by name, which `read_code` replaced) stays callable for clients configured for
73
+ it. The 2.x tools and their option set are gone.
74
+ - **Lean tool surface.** Only `find_references` asks MCP clients that defer tools behind a tool
75
+ search to load it up front — it was 62 of the 64 graph-indexer calls in real sessions; the
76
+ others are found through the tool search. Descriptions, the server instructions and the
77
+ CLAUDE.md / AGENTS.md block are a few lines each: graph-indexer adds 1,190 tokens to a Claude
78
+ Code prompt instead of 2,930, re-read at every turn.
79
+ - **Removed:** dense embeddings, Ollama/MLX/in-process embedders, LLM enrichment and reranking,
80
+ the watch daemon, sealed mode and its attestation, taint tools, and the grammar auto-installer.
81
+ Search is lexical + structural and needs no model.
82
+ - **CLI:** `graph-indexer init | serve | index | status | search | grep | files | read | symbol |
83
+ refs | callgraph | impact | check | outline | hook`. `idx-mcp` and `idx-index` remain as aliases of
84
+ `serve` and `index`.
85
+ - The index lives in `.graph-indexer/index.db` (SQLite); 2.x index files are ignored.
86
+
87
+ ### Added
88
+
89
+ - **Bundled parsing.** web-tree-sitter 0.25 and 16 grammars (JavaScript, TypeScript, TSX,
90
+ Python, Go, Java, Kotlin, Scala, C#, Rust, C, C++, Ruby, PHP, Bash, CSS) vendored as
91
+ WebAssembly with pinned versions and checksums; nothing is downloaded or compiled.
92
+ - **Reference graph.** Every call, instantiation, type use, inheritance, decorator, value and
93
+ member-read reference is bound to its definition through lexical scopes, the enclosing type and
94
+ its bases, imports (barrels, tsconfig paths, workspaces, Go modules, Python packages — including
95
+ a module reached as an attribute of a package alias, `exp.Column` through `from pkg import exp`
96
+ and star re-exports, where a later import rebinds the name — Rust crates, C includes), packages
97
+ and globals, with a confidence label. `find_references` includes
98
+ overload sets and calls through base types, labelled. Ambiguous and standard-library method
99
+ names (`get`, `set`, `apply`…) stay unbound instead of being guessed; `super()` calls reach the
100
+ parent's method only. Go implicit interface satisfaction, field reads and struct literal keys;
101
+ Python `super()`, string annotations and `self` attributes as fields; TypeScript computed keys
102
+ and `typeof`.
103
+ - **Receiver type inference** across languages: annotations, initialisers, casts, `await`,
104
+ declared and inferred return types (including generic bounds and defaults, and `Self`/`this`),
105
+ value roots (`export const NestFactory = new NestFactoryStatic()`), fields and
106
+ constructor-injected dependencies, collections and their elements (`xs[i]`, loops, array
107
+ callbacks, `list.get(0)`), optional/nullable and wrapper types, closures and access chains
108
+ (also fluent chains formatted one call per line, and chains that start with `(await …)`).
109
+ Callback parameters without annotations take their types from the signature of the function
110
+ they are passed to (`helpers.connect((err, socket) => socket.send())`). Members of primitives
111
+ and collections are never guessed.
112
+ - **Hybrid search** over symbols: exact/qualified name channel, BM25F over name, signature, doc,
113
+ path and body, file-level relevance, a concept thesaurus, interpretable priors and PageRank
114
+ centrality, with per-file diversification.
115
+ - **Maps and dictionaries** in the type model (`Map<K, V>`, `dict[K, V]`, `HashMap<K, V>`, Go
116
+ `map[K]V`, classes that extend them): `m.get(k)`, `m[k]` and `m.values()` reach the value type.
117
+ - `find_references` states where other same-name references resolve, separates plausible unbound
118
+ call sites (in files that use the type or a subclass inheriting the member) from unrelated
119
+ ones, says when a list of calls is complete, and lists members named by string
120
+ (`sinon.stub(obj, 'name')`, `jest.spyOn`, `getattr(o, "name")`, `patch("pkg.C.name")`), which a
121
+ rename has to update too. For a class or interface it lists every type that inherits it,
122
+ directly or through a subclass (and through which one); `path` limits any list to a directory.
123
+ Ambiguous names list the alternatives as ready-to-use qualified targets (overloads count as one
124
+ definition).
125
+ - **`read_code`** (`graph-indexer read`): reads symbols (`Class.method`), line ranges
126
+ (`path:120-180`) or files, up to 12 per call, with line numbers — and under each one, where
127
+ every name the code uses is defined (file:line, signature, first doc line), about one line per
128
+ dozen lines read, so following a call or a type is a read of the listed targets rather than a
129
+ search. A symbol also shows what it overrides, who uses it and the tests that reference it; a
130
+ file over 300 lines comes back as its outline unless `full` is set; a definition already shown
131
+ in the same answer is not listed again.
132
+ - **`search_text`** (`graph-indexer grep`): text search over every file — code, configuration,
133
+ docs. Each code match carries its enclosing definition and, for identifiers, the symbol it
134
+ refers to, one compact line per match; when the search did not match the definition of the
135
+ name it looked for (wrong file, a pattern that misses the definition line), it says where the
136
+ definition is. `graph-indexer files` finds files by path or glob.
137
+ - **Change impact:** call sites to update, overrides and implementations, transitive dependents
138
+ by distance, tests that exercise them (including describe blocks), public surface, git
139
+ co-change history, blind spots, and `diff: true` for the uncommitted working tree; "risk:
140
+ unknown" when the graph cannot tell.
141
+ - **`check_changes`** (`graph-indexer check`): verifies uncommitted edits without building —
142
+ syntax errors introduced, calls that no longer fit a changed signature, removed or renamed
143
+ definitions still in use, callers the edit did not touch — and names the tests to run with the
144
+ command for the project's runner (jest, vitest, mocha, pytest, unittest, Django, go test,
145
+ cargo, Maven/Gradle). About 1 s on nestjs.
146
+ - **Freshness:** file watcher plus stat sweeps; changed files are re-indexed before every answer
147
+ and re-validated before their lines are rendered. Symbol ids stay stable across edits.
148
+ - **Robustness:** the index rebuilds itself when the extractor changes (fingerprint), recovers
149
+ from interrupted runs, never reuses row ids, skips minified/generated/huge files, and ignores
150
+ symlinks that point outside the repository. Path arguments accept absolute and `./` paths.
151
+ - **MCP** implemented without an SDK: stdio JSON-RPC, protocol versions 2024-11-05 to
152
+ 2025-11-25 plus the stateless 2026-07-28 revision (`server/discover`), read-only tool
153
+ annotations, plain-text replies with token caps, and input schemas without `const` or
154
+ `additionalProperties` (some Gemini-family hosts drop tools whose schemas use them). Server
155
+ instructions are one paragraph: keep reading and searching text with the agent's own tools;
156
+ `find_references` for exact uses, `call_graph` for callers of callers, `change_impact` before
157
+ changing a signature, `check_changes` when done. Only `find_references` is marked
158
+ always-loaded for clients that defer MCP tools (see Lean tool surface).
159
+ - **`graph-indexer init`** configures Claude Code, Cursor, VS Code, Gemini CLI, Codex, OpenCode and
160
+ Kilo Code (`opencode.json`, or an existing `kilo.json`), Junie (`.junie/mcp/mcp.json`), Zed
161
+ (`context_servers` in `.zed/settings.json`) and Devin (instructions and the Claude Code hooks it
162
+ loads), never rewrites a config file whose comments or JSONC syntax a JSON round trip would lose
163
+ (it prints the entry to add instead), and adds a
164
+ short managed block to `CLAUDE.md`/`AGENTS.md` on how to look code up: several lookups per
165
+ message, the function or the lines needed rather than the file, one search for a definition
166
+ line across the package, and one check at the end; then graph-indexer for what a text search
167
+ cannot answer exactly — exact uses (`find_references`), callers of callers, what a change
168
+ reaches (`change_impact`), what an edit broke and the closest tests (`check_changes`), a
169
+ definition a search missed (`read_code`). The fourth benchmark round chose this arrangement:
170
+ sending every read and search through graph-indexer cost more on issue fixes than the rules
171
+ alone. `init --hooks` adds `graph-indexer hook` to Claude Code (an
172
+ installed `graph-indexer` binary when there is one, npx otherwise). After an edit it runs the
173
+ edit check on that file; after a search for a definition that did not find it, it says where
174
+ the definition is; once the agent is crawling — three searches or reads since its last edit —
175
+ a read gets where the names in the lines read are defined and a grep for a name several
176
+ definitions share says which is which; a subagent starts with the lookup rules. Otherwise it
177
+ stays silent, and it fails open under a deadline. The same hook reads Codex, Copilot CLI,
178
+ Devin and Gemini CLI payloads, and answers Cursor's events with Cursor's field. A Claude Code plugin (`integrations/claude-code`) bundles the
179
+ server and the hooks.
180
+ - **Structural helper.** For Claude Code, `init` writes `.claude/agents/code-structure.md` (the
181
+ plugin ships the same file; `--no-helper` skips it, and a file of that name the user wrote is
182
+ left alone): a read-only sub-agent on a small model that answers call-site, caller, subclass
183
+ and impact questions from the index and replies with the `path:line` list alone, saying whether
184
+ it is complete. The main agent hands the question off and gets back the answer, not the
185
+ searches and reads behind it. Its instructions are in `src/cli/helper.mjs`, for the MCP tools
186
+ or, in shells without MCP, the CLI.
187
+ - **Constructors by language.** `initialize` is a constructor only in Ruby (`__init__` and
188
+ `__new__` in Python, `__construct` in PHP, `constructor` in JavaScript and TypeScript, declared
189
+ constructors elsewhere), so `new Foo()` no longer shows up as a caller of an ordinary
190
+ `initialize` method, in the call graph or the edit check. A map built with type arguments
191
+ (`new Map<string, Foo>()`) types the values taken from it, and `call_graph` lists a recursive
192
+ function among its own callers.
193
+ - **Anonymous classes and map values.** A class expression without a name (`return class extends
194
+ ModuleRef {…}`, `mixin(class extends Base {…})`) is a class of its own, `<anonymous>` in the
195
+ index, so `this` in its methods reaches its bases' members: before, `this.find()` there was
196
+ bound to a `find` of the class around it and reported as complete. A default-exported anonymous
197
+ class is a class too. The values of a map reach the code that takes them — `m.forEach(v => …)`,
198
+ `for (const v of m.values())`, `Array.from(m.values())`, `[...m.values()]` — also when the map
199
+ is a class that extends `Map<K, V>`; a loop over such a class's `values()` used to be taken for
200
+ the language's own and left out without a mention. On nestjs this binds 4 more calls and 9 more
201
+ member reads, and changes no other binding in any of the seven benchmark repositories.
202
+ - **Go, measured against the Go type checker** (`bench/eval-graph-go.mjs`, `bench/oracle-go`), on
203
+ gin and on caddy and nats-server, which graph-indexer had not been run on before. Precision is
204
+ now 0.992–1.000 and recall 0.972–0.981 (the dispatch oracle), and implicit implementations
205
+ 0.977–1.000 / 0.938–1.000. Fixes: implicit implementations survive reopening the index (before,
206
+ they were lost on every server start); modules at major version 2 or later (`…/v2`,
207
+ `gopkg.in/yaml.v3`) are imported under their package name; interfaces are matched by method
208
+ signature as well as name, and count promoted methods and type aliases; package-level variables
209
+ of other files type the locals set from them; `(*T)(nil)` references `T`; library types keep
210
+ their package (`http.Request`), so their members are not bound by name to the repository's; a
211
+ local shadows the package function of the same name; embedded fields are named by their type,
212
+ named collection types (`type M map[string]*T`) type their elements, interface methods keep
213
+ their result type, and a chain may start with a type assertion (`v.(Module).Info().Name()`, and
214
+ `(x as Foo).bar()` in TypeScript).
215
+ - **Java, measured against the Java compiler** (`bench/eval-graph-java.mjs`,
216
+ `bench/oracle-java/Oracle.java`) on spring-petclinic, jsoup and Commons Collections: precision
217
+ 0.767–0.919 → 0.955–0.994, recall 0.791–0.995 → 0.861–0.989. Overloads are methods of their own:
218
+ calls carry their argument count and bind to the overload that takes it (fixed arity before
219
+ variadic, inherited overloads when the type's own do not fit), overrides match by parameter
220
+ count, and `find_references` no longer merges overloads in Java. A class named for a static
221
+ access (`StringUtil.join(…)`, `Foo::bar`) is a use of it; anonymous classes and enum constants
222
+ with bodies are subclasses of what they extend; nested types keep their enclosing type; fields
223
+ inherited from another file type their receivers; an argument is never a method. In every
224
+ language a member's enclosing type is its parent, not a type found by name.
225
+ - **A product monorepo, measured against the TypeScript compiler** (Twenty, an open-source CRM:
226
+ 28,512 files, a NestJS server, a React front end and a shared package). graph-indexer found 18%
227
+ of the server's references there, against 96% on the libraries: path aliases were read from the
228
+ root tsconfig only, and each package of a monorepo declares its own. Each file now resolves
229
+ through the nearest `tsconfig.json` above it (with `extends`), then the root's: recall 0.182 →
230
+ 0.924, precision 0.999. With it, in every JavaScript/TypeScript repository: a bare call inside a
231
+ method is not the method itself (it needs `this.`); members of object and type literals are not
232
+ names in scope; a function or class used as a value is a use in an arrow's body
233
+ (`@Field(() => Dto)`, `forwardRef(() => X)`), a conditional, `return`, an initialiser, an
234
+ assignment and `typeof` in a type; destructured values keep their types (`const { a: x } = e`,
235
+ `({ a }: Deps)`, `({ a }: { a: A })`); `Pick<T, …>`, `Omit<T, …>` and type aliases have the
236
+ members of the types they are made of.
237
+ - **Go agent round** (`go2`, 57 real Claude Code sessions on caddy, tasks graded by the Go type
238
+ checker): questions 14/14 with graph-indexer against 13/14 without, at 0.87 of the cost;
239
+ refactors all solved at 0.81 of the cost and half the time.
240
+ - **Resident process.** The MCP server, or `graph-indexer daemon` when no server runs (the hooks
241
+ start it and it exits after 30 idle minutes), keeps the index open and in sync and answers hooks
242
+ and CLI queries over a socket only its owner can reach, inside `.graph-indexer/` (a named pipe
243
+ on Windows). On a 1,600-file repository a hook drops from 280–570 ms to 70–120 ms and a CLI
244
+ query from ~370 ms to 110–210 ms, with the same output; without a resident everything works as
245
+ before. The plugin's hook command is a small client that talks to that socket instead of
246
+ starting graph-indexer through npx (about a second per call). `GRAPH_INDEXER_RESIDENT=0`
247
+ turns it off.
248
+ - **`check_changes` names the closest test functions**: the test functions you changed and those
249
+ that reach the changed code through calls the index sees (directly or through one helper), with
250
+ a command that runs only them (pytest, unittest, Django, `go test -run`, Maven, Gradle), before
251
+ the test files that exercise the change.
252
+ - **Resolved indirection after reads**: `read_code`, `get_symbol` and the post-read hook say which code actually runs. For a method, which override a subclass uses, which subclasses inherit it unchanged, and what a `self.x()` call can dispatch to. For registration tables (dict/object literals of handlers, extended with `**Base.TABLE` or `...base`), which entry handles a key and what a subclass table replaces. They also say which method a name built at run time reaches (`getattr(self, f"{key}_sql")`, `dir(cls)` + `endswith`) and what a decorator is. Static analysis only: silent when the lines have no indirection, never repeated in a session. Checked against the running program in the benchmark: precision 1.000 for overrides and inheritors, 0.995 for table keys on sqlglot.
253
+ - **Method resolution order**: which subclasses inherit a member is decided by C3 linearization. With multiple inheritance, the index used to count classes that run another base's override (inheriting-subclass precision on sqlglot went from 0.59 to 1.000).
254
+ - **Subclasses that inherit a changed method**: `check_changes` and `change_impact` list the
255
+ subclasses (transitively) that inherit a changed method without redeclaring it — they run the
256
+ new code too — and those that override it, and add the inheriting subclasses' test files to the
257
+ tests to run. In the fourth round's issue fixes, agents searched for these subclasses by hand
258
+ after editing, and the check named only the edited class's tests.
259
+ - **OpenCode and Kilo Code plugin** (`integrations/opencode/graph-indexer.js`, written by
260
+ `init --hooks` to `.opencode/plugins/` or `.kilo/plugin/`): after a read, a search or an edit it
261
+ appends to the tool's output what the Claude Code hooks add as context, answered by the resident
262
+ process; with none running it adds nothing and starts one.
263
+ - **Benchmarks** (`bench/`): symbol-search suites (377 queries, 9 repositories), reference
264
+ accuracy against the TypeScript compiler, and localization replayed from real commits, with
265
+ pinned fixtures (`bench/fixtures.mjs`). An agentic benchmark (`bench/agentic/`): code questions
266
+ graded by the TypeScript compiler, multi-site refactors graded by differential `tsc`, and real
267
+ issues from after the model's training cutoff graded by the hidden tests of their fix, with and
268
+ without grep, paired statistics and acceptance gates. The model's output is recovered from the
269
+ growth of the context between calls (transcripts keep only the first streamed usage and redact
270
+ reasoning), and `anatomy.mjs` reports where cost and time go, the calls before the first edit
271
+ and the share of code read that the fix changes.
272
+
273
+ ### Results (see docs/BENCHMARKS.md)
274
+
275
+ - References vs the TypeScript compiler (nestjs, 400 symbols): precision 0.997, recall 0.962
276
+ (grep: 0.128 / 0.993; name-only: 0.252 / 0.975); on Twenty's server 0.999 / 0.924 (grep
277
+ 0.009).
278
+ - Localization over 169 real commits: file Acc@1 0.544, function MRR@10 0.432 (grep-style
279
+ ranking 0.485 / 0.374; BM25 0.373 / 0.290).
280
+ - Symbol search (377 queries): rank-1 0.700, MRR 0.759 (2.x on the same queries: 0.552 / 0.646).
281
+ - Agents, four rounds (88 tasks, 359 runs; docs/AGENTIC-BENCHMARK.md). In the fourth (19 new
282
+ tasks, 122 runs, all solved), graph-indexer as `init` installs it answers code questions at
283
+ 0.44 of the cost of grep alone (95% CI 0.32–0.67) and 0.37 of the time, with half the model's
284
+ reasoning; questions and refactors together at 0.48; real issues at 0.85 (0.76–0.96), the same
285
+ as the lookup rules alone (0.86); one-file fixes at 0.85. On questions and refactors it cost
286
+ 0.76, 0.81 and 0.73 of grep in the first three rounds.
287
+ - Agents with a smaller model at half the price per token (25 earlier tasks, 61 runs): code
288
+ questions at 0.26 of grep's cost (0.18–0.50) and 0.32 of the time, answered exactly in 7 of 7
289
+ against 5 of 7 with grep alone — a tenth of what the usual model spent with grep; multi-site
290
+ refactors at 0.69 (0.56–0.87); real issues at 1.05 (0.82–1.46), and 1.19 with the lookup rules
291
+ alone.
292
+ - Delegated code questions (24 new, 72 runs, all on the smaller model): the structural helper
293
+ answered 20 of 24 exactly, as many as a general-purpose sub-agent with grep, at 0.24 (0.18–0.33)
294
+ of its cost and 0.28 of its time; Claude Code's Explore agent came to 0.76. Each handed back
295
+ about 170 tokens, against the 23k that looking the answer up adds to the asking agent's context.
296
+ After the fixes that followed the round, the index alone answers all 24 exactly.
297
+ - Real Claude Code sessions (`bench/agentic/session-round.mjs`; 12 new questions and 8 refactors,
298
+ 60 runs): with graph-indexer as `init` installs it every question was answered exactly, 9 of 12
299
+ without it (the misses were the three two-level caller questions), at 0.86 (0.81–0.92) of the
300
+ cost and 0.68 of the time; refactors were all solved either way at the same cost. The main agent
301
+ never handed a question to the structural helper or to Explore on these short tasks.
302
+ - Against graph-indexer 2.x (docs/BENCHMARKS.md section 5): references on 8 repositories in
303
+ TypeScript, Go and Java at precision 0.955–1.000, where 2.x is at 0.02–0.70 (it lists every chunk
304
+ that mentions a name; in Go it misses most method calls), with answers of a few hundred tokens
305
+ (2.x: up to 12,000 on average); symbol search MRR 0.760 against 0.646; localization file Acc@1
306
+ 0.544 against 0.473; 1,200 tokens added to each session against 10,600–11,300; an unchanged
307
+ repository re-synced in 0.2–1.8 s where 2.x rebuilds (9–180 s). In real Claude Code sessions
308
+ (nestjs and Twenty, 40 tasks, 120 runs), 3.0 cost 0.43 of 2.x on code questions and 0.46 on
309
+ refactors, and 0.84–0.86 of no index with half the tokens spent looking code up; 2.x cost
310
+ 1.86–1.97 of no index.
311
+ - Real sessions on the fourth round's 11 issues (66 runs): every run fixed its issue with or without
312
+ graph-indexer, and no agent called it; its tool definitions, instructions and CLAUDE.md block
313
+ alone made those sessions cost 1.07–1.10. With the lean surface (below) that fell to 1.02–1.05,
314
+ and the questions stayed exact at 0.80–0.84 of the cost (126 runs).
315
+
316
+ ---
317
+
318
+ ## [Unreleased 2.x — superseded by 3.0.0]
8
319
 
9
320
  ### Install-size fix: heavy optional packages no longer ship by default
10
321