neuralmind.uk / NeuralMind v4.23.3 — a rebuild gives the graph a full build gives, re-extracts far less, and its type pass parses only the files that changed Edit this page on GitHub ↗

NeuralMind v4.23.3 — a rebuild gives the graph a full build gives, re-extracts far less, and its type pass parses only the files that changed

Type: Patch release Theme: the same output, sooner on every rebuild: an incremental build now writes what a full build writes, byte for byte, and neither its graph extraction nor its type pass redoes work an edit can’t affect

v4.23.2 made the build’s type pass, which annotates call edges with return types, read each file once: 17 s of a hermes-agent build instead of 122 s. It still parsed every file on every build, incremental ones included, so a build after a one-line commit paid the same 17 s as a build from scratch. Every neuralmind build runs that pass, including the git hooks’ rebuilds after a commit, pull, merge or branch switch.

This release keeps each file’s results between builds. The type edges, the rows the synapse store keeps and the block the agent gets come out identical, checked byte for byte.

A profile of NeuralMind 4.23.1 also found that rebuilding NeuralMind’s own repository after a one-file edit extracted the graph for longer than a full build did. Two problems lay behind that, and fixing the first exposed more:

All are fixed. An incremental build now writes the same graph.json as a full build, byte for byte, and re-extracts only what a change can affect.


What changed

The type pass keeps each file’s results between builds

The pass reduces each file to a map from every function name in it to its return type. That map is now kept in .neuralmind/type_cache.json, keyed by a hash of the file’s text, so the next build reuses it while the text is the same:

Like the rest of .neuralmind/, the file isn’t committed: the directory ignores itself. Deleting it costs one build at first-build speed and changes nothing in the result.

A first build walks less, too. The Python pass visits only the statements a function can be defined in, which meets functions in the same breadth-first order as ast.walk over every node, and the tree-sitter trees are walked with a cursor. Parsing still dominates a first build, so that saves about a second on hermes-agent.

An incremental build gives the graph a full build gives

The scan uses the build’s own file list

It compares the extraction cache with the files the build indexes, after .gitignore, .neuralmindignore and the config’s globs, so an ignored file is never “added”, and a file that becomes ignored leaves the graph like a deleted one.

Each unchanged file keeps its place

Its names go into the lookup tables, and its nodes and edges into the graph, where a full build would have extracted that file. So a call resolves to the same function as in a full build, and nodes and edges come out in the same order. Python, TypeScript, Go, Rust and C/C++ files replay this from the previous graph. Java, C#, PHP and Ruby take module names from a file’s package or namespace line, which the graph doesn’t record, so an unchanged file in those languages is parsed again for it.

What a change can affect is re-extracted, and only that

Files that only import a changed one keep their edges: the nodes those point at come back under the same ids. Before, a change re-extracted every file that imported it, those files’ importers, and so on.

The file watcher uses the same rules

update_files, which neuralmind watch --reindex calls, gives the same nodes and edges as a full build. Its output was never in a build’s order, and still isn’t.

Carrying community numbers over is no longer quadratic

It rebuilt the set of current files once for every node of the previous graph: 38 s of a one-file rebuild of hermes-agent’s graph.

What the agent sees post-install

Agent Before After
Claude Code (UserPromptSubmit) The block from the last build The same block; a rebuild after a commit or pull (the git hooks) finishes its type pass in about 1 s instead of 17 s on hermes-agent
Hermes-Agent (plugin) As above As above
Cursor, Cline, generic MCP The index the last build wrote The same index, written sooner
Every agent, after an incremental rebuild A call in an unedited file could point at a different same-named function than after a full build, and so could the context built from it The graph a full build gives, so the same context however the index was built
Anyone who builds The type pass took 17 s of every hermes-agent build, first or not 16 s on a first build, ~1 s on a rebuild, with the same type edges
Anyone who rebuilds after an edit (neuralmind build, the git hooks, watch --reindex) A one-file graph rebuild took 1.6–1.9 s on NeuralMind and 80.5–82.6 s on hermes-agent, longer than a full build 0.27–0.81 s and 9.8–10.7 s

Measured

The type pass

TypeVerifier.augment_graph on each project’s graph.json, timed by itself, on an M3 MacBook (8 cores, 16 GB). One run each on hermes-agent and requests, two on NeuralMind, while other sessions’ jobs shared the machine. A first build starts with no .neuralmind/type_cache.json; a rebuild runs on the same files with the cache the first build wrote.

Repository Type edges v4.23.2 v4.23.3, first build v4.23.3, rebuild Edges, rows and risks
requests 1,041 0.04 s 0.05 s 0.01 s identical
NeuralMind 9,364 0.55–0.59 s 0.43–0.45 s 0.02 s identical
hermes-agent 347,376 17.2 s 15.9 s 1.0–1.2 s identical

On hermes-agent:

“Identical” means the multiset of graph["type_edges"], the rows persist_type_edges writes, and detect_type_risks’s output, compared with v4.23.1’s code and v4.23.2’s. On hermes-agent that’s 347,376 edges, 258,415 rows and 159,919 risks, on a first build, a rebuild, a rebuild hashing every file and a rebuild after the one-file edit.

The prompt block. The sample fixture (tests/fixtures/sample_project) was built with v4.23.1’s type pass, whose output v4.23.2’s matches, and with this one, then rebuilt on the warm cache. Both gave the same graph.json, the same 49 stored type-edge rows and the same prompt block SHA-256 (tests/benchmark/hook_latency.py, 3 calls each).

Graph extraction

graphgen.write_graph alone, one-file edits, on an M3. v4.23.2’s graph extraction is v4.23.1’s:

  v4.23.2 v4.23.3 full build
NeuralMind 1.6–1.9 s 0.27–0.81 s, median 0.49 s 0.75–0.83 s
hermes-agent (10,270 code files) 80.5–82.6 s, and a graph that differs from a full build’s 9.8–10.7 s 34.5–36.0 s

A comment edit to neuralmind/embedder.py, which defines a property and its setter, re-extracts 1 file.

Every incremental graph was compared, byte for byte, with a full re-extraction of the same tree that keeps the previous graph.json, so community numbers carry over the same way:

All matched. A full build gives the same graph.json as v4.23.2 on NeuralMind, requests, hermes-agent and every fixture.

What it doesn’t change

Tests

Upgrading

pip install -U neuralmind

Nothing else to do. The first build after upgrading runs the type pass at first-build speed and writes .neuralmind/type_cache.json; later builds reuse it.

It also extracts the whole graph once: the extraction cache from earlier versions is dropped, because an incremental build in those versions could have left a call pointing at the wrong function, and incremental builds now carry unchanged files’ edges over. Embeddings are cached by content, so only nodes whose text or edges changed are embedded again.

Use cases