graph-indexer 2.1.0 → 3.0.0

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