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.
- package/CHANGELOG.md +260 -1
- package/README.md +232 -312
- package/SECURITY.md +37 -59
- package/bin/graph-indexer.mjs +251 -0
- package/integrations/claude-code/.claude-plugin/plugin.json +8 -0
- package/integrations/claude-code/.mcp.json +8 -0
- package/integrations/claude-code/agents/code-structure.md +22 -0
- package/integrations/claude-code/hooks/gi-hook.mjs +99 -0
- package/integrations/claude-code/hooks/hooks.json +16 -0
- package/integrations/opencode/graph-indexer.js +124 -0
- package/package.json +31 -105
- package/src/cli/helper.mjs +67 -0
- package/src/cli/hook.mjs +356 -0
- package/src/cli/init.mjs +255 -0
- package/src/cli/resident.mjs +175 -0
- package/src/index/discover.mjs +130 -0
- package/src/index/fingerprint.mjs +30 -0
- package/src/index/graph.mjs +61 -0
- package/src/index/indexer.mjs +476 -0
- package/src/index/modules.mjs +331 -0
- package/src/index/resolver.mjs +954 -0
- package/src/mcp/render.mjs +60 -0
- package/src/mcp/server.mjs +147 -0
- package/src/mcp/tools.mjs +973 -0
- package/src/parse/arity.mjs +152 -0
- package/src/parse/extract.mjs +1031 -0
- package/src/parse/lang/csharp.mjs +106 -0
- package/src/parse/lang/ecma.mjs +438 -0
- package/src/parse/lang/go.mjs +194 -0
- package/src/parse/lang/jvm.mjs +118 -0
- package/src/parse/lang/kotlin.mjs +97 -0
- package/src/parse/lang/misc.mjs +115 -0
- package/src/parse/lang/native.mjs +126 -0
- package/src/parse/lang/python.mjs +153 -0
- package/src/parse/lang/rust.mjs +157 -0
- package/src/parse/lang/scripting.mjs +219 -0
- package/src/parse/languages.mjs +48 -0
- package/src/parse/runtime.mjs +56 -0
- package/src/query/check.mjs +343 -0
- package/src/query/facts.mjs +323 -0
- package/src/query/intel.mjs +680 -0
- package/src/query/read.mjs +81 -0
- package/src/query/testcmd.mjs +119 -0
- package/src/query/textsearch.mjs +160 -0
- package/src/search/search.mjs +210 -0
- package/src/search/tokenize.mjs +188 -0
- package/src/store/db.mjs +169 -0
- package/src/util/paths.mjs +24 -0
- package/vendor/grammars/bash.wasm.gz +0 -0
- package/vendor/grammars/c.wasm.gz +0 -0
- package/vendor/grammars/c_sharp.wasm.gz +0 -0
- package/vendor/grammars/cpp.wasm.gz +0 -0
- package/vendor/grammars/css.wasm.gz +0 -0
- package/vendor/grammars/go.wasm.gz +0 -0
- package/vendor/grammars/java.wasm.gz +0 -0
- package/vendor/grammars/javascript.wasm.gz +0 -0
- package/vendor/grammars/kotlin.wasm.gz +0 -0
- package/vendor/grammars/manifest.json +148 -0
- package/vendor/grammars/php.wasm.gz +0 -0
- package/vendor/grammars/python.wasm.gz +0 -0
- package/vendor/grammars/ruby.wasm.gz +0 -0
- package/vendor/grammars/rust.wasm.gz +0 -0
- package/vendor/grammars/scala.wasm.gz +0 -0
- package/vendor/grammars/tsx.wasm.gz +0 -0
- package/vendor/grammars/typescript.wasm.gz +0 -0
- package/vendor/web-tree-sitter/web-tree-sitter.js +3980 -0
- package/vendor/web-tree-sitter/web-tree-sitter.wasm +0 -0
- package/cli-ui.mjs +0 -78
- package/colbert.mjs +0 -168
- package/config.mjs +0 -494
- package/daemon-ctl.mjs +0 -175
- package/daemon-lock.mjs +0 -69
- package/embedders/python/mlx_embed_server.py +0 -61
- package/embedders/python/requirements-mlx.txt +0 -5
- package/embedders/setup-mlx.mjs +0 -110
- package/embeddings.mjs +0 -571
- package/engine/binary.mjs +0 -437
- package/engine/memory.mjs +0 -1003
- package/engine/sqlite.mjs +0 -1136
- package/enrichment.mjs +0 -521
- package/git-signals.mjs +0 -248
- package/indexer.mjs +0 -546
- package/init.mjs +0 -1809
- package/layout.mjs +0 -123
- package/mcp/centrality.mjs +0 -117
- package/mcp/format.mjs +0 -61
- package/mcp/resolver.mjs +0 -116
- package/mcp/symbolgraph.mjs +0 -137
- package/mcp/taint.mjs +0 -225
- package/mcp/tools.mjs +0 -1582
- package/mcp/topology.mjs +0 -676
- package/mcp-server.mjs +0 -197
- package/parse/extractor.mjs +0 -628
- package/parse/imports.mjs +0 -365
- package/parse/interprocedural.mjs +0 -134
- package/parse/languages.mjs +0 -253
- package/parse/metadata.mjs +0 -701
- package/parse/routes.mjs +0 -327
- package/parse/scip.mjs +0 -391
- package/parse/taint-patterns.mjs +0 -119
- package/prompts/CORE.md +0 -91
- package/prompts/DOMAIN_TEMPLATE.md +0 -46
- package/prompts/INTEGRATION.md +0 -123
- package/prompts/frameworks/ANDROID.md +0 -17
- package/prompts/frameworks/ASPNET_CORE.md +0 -19
- package/prompts/frameworks/FASTAPI_DJANGO.md +0 -20
- package/prompts/frameworks/LARAVEL_SYMFONY.md +0 -22
- package/prompts/frameworks/NODE_EXPRESS_NESTJS.md +0 -19
- package/prompts/frameworks/RAILS.md +0 -20
- package/prompts/frameworks/REACT.md +0 -20
- package/prompts/frameworks/SPRING_BOOT.md +0 -20
- package/prompts/languages/BASH.md +0 -22
- package/prompts/languages/C.md +0 -21
- package/prompts/languages/CSHARP.md +0 -24
- package/prompts/languages/CSS_SCSS.md +0 -22
- package/prompts/languages/GO.md +0 -24
- package/prompts/languages/JAVA.md +0 -23
- package/prompts/languages/JAVASCRIPT_TYPESCRIPT.md +0 -24
- package/prompts/languages/KOTLIN.md +0 -24
- package/prompts/languages/PHP.md +0 -23
- package/prompts/languages/PYTHON.md +0 -23
- package/prompts/languages/RUBY.md +0 -23
- package/prompts/languages/RUST.md +0 -21
- package/prompts/languages/SWIFT.md +0 -21
- package/seal.mjs +0 -400
- package/search-core.mjs +0 -777
- package/search-sparse.mjs +0 -183
- package/storage.mjs +0 -73
- 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
|
-
## [
|
|
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
|
|