Memtrace v1.2.8
A re-thought engine for every language, a code graph you can actually read, and sharper tools for your agents
New
- Perl is indexed from the grammar rather than by pattern. A
packagestatement changes the current lexical namespace, a qualified definition does not change the namespace of its body, and dynamic dispatch is reported as unresolved instead of guessed at. - Zig reads the module graph its
build.zigdeclares. Zig binds a bare-name import per module, so@import("config")in two different executables names two different files; flattening those aliases into one repo-wide map sent the second module's import to the wrong file. - Zig enum variants are understood as receivers.
Op.create.event_max()reads a variant as the receiver, and a variant is not a container — the receiver of its method is the enum that declares it. Nothing in the graph could previously say which names were variants. - Zig calls excluded by a comptime-known condition no longer produce edges. A branch guarded by a
constbound tofalsecannot call what it contains, and an edge saying otherwise invites you to trace a path the compiler already removed. - SQL routine bodies contribute calls, taken from parsed expressions and never from text inside comments or string literals.
- COBOL paragraphs and sections are both indexed as units control can be transferred to, which is what makes a COBOL program's flow followable.
- Nextflow repositories are indexed across all three syntaxes they mix: DSL2 pipeline and component files, the declarative configuration files, and nf-test suites.
- Dart indexing covers the declarations the language actually has — classes, mixins, extensions, extension types, enums, constructors, and function and method signatures — verified against the grammar's own node types.
- R projects expose the surface that makes an R graph useful: package and namespace dependencies, and sourced files.
- Vue component events are now a relationship you can follow. A template's
@event="handler"is a subscription, not a call: the handler binds to the child component and the child records what it emits, so “who handles what this component emits” is answerable across files. - Swift attributes reach the graph.
@objc,@available,@MainActorand@discardableResultwere recorded nowhere, so “what is exposed to Objective-C” and “what is deprecated” had no answer. - TypeScript ambient and namespace declarations are extracted. Members of a
declare classwere never read, so a call inherited from an ambient base could not resolve, and anamespacebody was treated as a block, so every value it declared was absent. - A callable's written signature facts reach the store and the read path. Each parameter's type and the return annotation are published in one canonical spelling — whitespace collapsed, a C++ plain name unqualified, a Rust borrow reduced to the type it borrows — with C++ member constness beside them, and
get_symbol_contextreturns them on the symbol, on its callers, callees and siblings, and on the candidate list returned when a name matches several declarations — which is where overloads are actually compared. They were computed during indexing and discarded before, so nothing outside the indexer could see them. The candidate list also names annotations and marks contributed symbols, so an ambiguous match no longer reads as if every choice were parsed and unannotated. - Named tool registrations are anchored to the exact handler declaration that serves them, so a codebase that registers tools can be asked which handler answers which name.
- Memtrace offers four ready-made workflows as MCP prompts:
before_modifying,before_deleting,new_codebaseandincident. Hosts that support prompts show them as slash commands, and each one expands into the full multi-tool routine with your symbol and repository filled in. - Every Memtrace MCP tool now declares whether it only reads, can remove data, or reaches the network. Hosts use these hints to decide what needs your approval, and Codex runs read-only Memtrace calls in parallel instead of one at a time.
Improved
- Text search now runs inside MemDB. Before
find_codecould answer, it built a private search index on the machine asking, by reading every record of the repository back out of the store. Against a hosted or self-hosted MemDB that meant downloading the whole repository over the network first. MemDB now builds that index beside the data and answers the search itself, so nothing is downloaded. In our measurements on a repository of about 4,000 indexed records at 45 ms of network latency, the firstfind_codewent from 32.8 seconds to 2.2 seconds, and repeat searches against a local store answer in about 60 ms. It makes no difference to ranking which side answers: the server and the local path share one scoring implementation, checked to produce bit-identical scores. Self-hosted MemDB servers gain this once they run this release; until then Memtrace keeps using the previous path, now with a time limit. - Every programming language Memtrace indexes is affected by this round. Twenty language frontends changed outright, and the shared work behind constructors, receivers, visibility and call resolution reaches the rest — ActionScript, Elixir, GDScript and Lua included. Configuration and infrastructure formats such as YAML, JSON, TOML and Kubernetes manifests are deliberately unchanged.
- Go projects resolve their imports. A module whose path carries no dot or slash —
module myapp, which covers most internal modules — resolved no package imports at all, so “what depends on this file” returned nothing for the whole project. - Java imports follow the package a file declares rather than the directory it sits in, so flat and non-standard layouts resolve. Wildcard imports such as
import com.example.models.*reached nothing whatsoever and now resolve, and a module boundary marked by apom.xmlorbuild.gradleis respected rather than crossed. - Swift files in one module now connect when one genuinely uses the other. Same-module files see each other without an
import, which we modelled as every file importing every other — that does not scale, and above a size threshold it silently produced nothing at all. A 400-file target went from no connections to roughly 2,400 meaningful ones. - Whether a declaration is exported is now decided the way each language expresses it, and that decision is the root set for dead-code analysis — a symbol nothing calls is only dead if it is also not part of the public API surface.
- Rust namespace paths resolve through declared modules and imports, so a value that happens to share a module's spelling can no longer hide it.
- Processes and impact walks get the benefit directly: cross-file imports now exist for Go, Java and Swift layouts that previously had none, so a process crossing a file boundary in those projects can be followed at all, and a blast radius no longer lists callers that never called.
- The
before_modifyingprompt and the routing instructions check a symbol with onepreflight_checkcall, which returns its dependents, the process flows it sits in, the files that change with it, its recent churn and a checklist of what to re-verify. They used to walk four separate lookups to gather the same answer. find_central_symbolsnames the symbol kinds a repository actually has when a request ranks nothing or asks for a kind that does not exist, such asInterfaceorProtocolin a Rust repository. The next call can ask for traits directly instead of listing every symbol to find out.find_symbolandfind_codename the repositories to choose from when they need arepo_id, so a retry no longer has to list every indexed repository first.get_timelinecalled with a guessedscope_paththat matches nothing now says to copy it from afind_symbolhit.- The All repositories graph shows each repository as its own island. Repositories that call or import each other sit side by side and blend along their shared border, unrelated ones sit apart, files with no relationships stay at the edge of their own repository, and each island keeps the shape it has when you open that repository on its own.
Changed
- Java types carry their package, so
com.a.Userandcom.b.Userstop collapsing into a single symbol, and a type declared inside a method carries the name the JVM gives it. This changes symbol identity for packaged Java — reindex your workspace after upgrading to pick it up. - C# file-scoped namespaces (
namespace Foo;, the modern default) now own the types written after them. Previously every type in such a file was unqualified, so two namespaces sharing a class name read as one type. - Constructors are one concept across fourteen languages, so “list this class's constructors” no longer answers differently depending on which language the class was written in. Python keeps
__init__as a method, because that is what the language makes it. - Members of a type are indexed as the kind they are. A property declared in a Kotlin, Dart or PHP class is now a field, as in Java and C#, where it used to be a variable, and a member of a Kotlin companion object belongs to its class. A Swift
static letwritten after an attribute such as@available(*, deprecated)is a constant; it was read as a mutable field. A Java enum's constants are indexed, as constants owned by their enum. Java, Go, JavaScript and Ruby methods are methods rather than functions, so a search filtered to methods no longer leaves them out. Anonymous functions in Dart, Elixir and GDScript are named by where they are written,<anonymous>@line:column, with the function that holds them recorded as their owner. Kinds and names are part of a symbol's identity, so each repository is re-parsed once, automatically, on the first start after upgrading. - A class-qualified call resolves only where the type is declared locally and nothing shadows it, and a named upper bound is treated as a receiver contract rather than a concrete construction type.
Fixed
find_codeagainst a hosted or self-hosted MemDB no longer waits indefinitely. Building its search index had no time limit, a request that gave up threw that partial work away so the next attempt started from nothing, and loading the query's embedding model could stall forever on a host with no route to the model download. Each is now bounded, and while an index is still being preparedfind_codesays so, answeringnot_readywithsearch_warming, instead of reporting no matches and suggesting a filesystem search.- Memtrace's own bookkeeping records, such as tool-call savings, session anchors and branch markers, are no longer indexed as searchable text, so they cannot appear among
find_coderesults or skew its ranking. - The same query over the same data now returns results in the same order every time. The search index was built on several threads at once, so scores that were almost equal could swap places from one build of the index to the next.
- A local store no longer loses saved changes after an unclean shutdown or a disk error. Four separate recovery faults could each discard changes that had already been saved, while the store still opened as if nothing was wrong: damage in data a checkpoint had already covered; a crash that cut a write off in its first few bytes, so the next save was written behind the fragment and lost on the restart after; a write that failed on a full disk; and an error while replaying changes at startup, which discarded the store's index of everything older. Recovery now keeps those changes. Where it cannot recover a store safely, the store now refuses to open with an error that names the problem, instead of opening without part of your data. A store that already lost changes this way is not repaired by upgrading: reindexing that repository rebuilds what was lost.
- A
memtrace startthat cannot open the store no longer stops the Memtrace already serving that workspace. When a store belongs to a workspace file or a named workspace, a barememtrace startfrom a folder above your projects is refused rather than allowed to widen it — but it stopped the running daemon on its way to refusing, so the workspace was left with nothing serving and no dashboard, under an error that ended “No data was changed”. The refusal now happens before anything is stopped, so the Memtrace you already had keeps serving and the message tells you which command to run. The added check is read-only — it takes no locks and writes nothing — and the authoritative check still runs where it always did. - The line Memtrace prints when a
memtrace startreplaces the daemon already serving a workspace no longer calls that daemon unhealthy. Three of the four reasons to replace one —--force, a change in workspace membership, and an unsafe store scope — say nothing about its health, and the actual reason is already printed on the line above. - An editor that launches Memtrace's MCP server while
memtrace startis still coming up now waits for it. Start makes itself visible a few seconds before it records how to attach, and an editor arriving in that window exited with “no runtime record … refusing to attach”, leaving it without Memtrace tools until it was restarted. - An MCP session opened before any Memtrace store is running no longer hangs on each tool call. It used to wait about 25 seconds on a connection nothing would answer; until a store is up, calls now fail at once, and the session attaches as soon as a store appears.
- Ruby assignments inside
class_eval,instance_eval,instance_execorClass.neware no longer attributed to the enclosing class's instance. Those blocks moveselfsomewhere else entirely, so the resulting answers pointed at the wrong object. - A method defined in a TypeScript object literal binds
thisto that literal rather than to the class around it. We were reporting a member access the compiler rejects outright. - Java method references in a predicate slot, such as
.filter(User::getName), no longer resolve to calls the compiler refuses. An unwritten or type-variable result now abstains instead of guessing. - A receiver typed with a bounded wildcard no longer resolves a call that cannot be written.
? superin Java andinin Kotlin genuinely can be called and still resolve. - PHP member calls on an untyped receiver are admitted only where the workspace holds exactly one method of that name with a matching arity and compatible arguments, and such a suggestion is never fed back into return-type propagation.
- Rust attributes are recorded against the declaration they were written on, so two
fmtmethods on one type no longer trade#[inline]between them. - The graph now shows every relationship a repository's edge count includes. The count covered every kind, but the graph's edge list used an older, fixed list of kinds, so field reads and writes, interface implementations, definitions and component events were counted yet never drawn, and the graph tools that rank or cluster symbols left them out too. A JavaScript or Python function parameter is also no longer reported as reading a field of the same name elsewhere in the repository.
- On Windows, Microsoft Defender began quarantining
memcore-server.exefrom the 1.2.8 nightly after a security intelligence update on September 24, which stopped Memtrace from opening its store. The store server is now built with the same compiler as every other Memtrace binary, and each Windows build is still scanned with current Defender definitions before it is published. - Four MCP tool descriptions no longer contradict their tools.
get_changes_sincestopped asking for inputs it rejects, so the calls an agent built from it now succeed;calculate_cyclomatic_complexitystates the risk bands it actually reports and whether a score was measured or estimated;get_timelinenames its required inputs and where to find them; anddelete_repositoryno longer claims re-indexing restores what it deletes. - MCP clients now see Memtrace's documentation pages as resources. The list was always empty because Memtrace asked the docs site for a page that does not exist. Listing resources also no longer waits up to 20 seconds on a machine that cannot reach the docs site: it answers within 2 seconds and falls back to the copy it cached last time.
find_dependency_pathrefuses anedge_typeit does not recognise and lists the valid ones, andfind_central_symbolsandfind_bridge_symbolsreport kinds they did not recognise inkinds_ignored. Both used to answer a different question without saying so.get_episode_replayingraph_summarymode says which section could not be computed and why, instead of returning an empty list that reads as "nothing central changed".- Passing a blank or placeholder
repo_idis reported as an input error rather than a server fault. - Skills installed as the Claude Code plugin now find the parameter reference they point to, because the installer copies it into the plugin. Pi skills name each tool the way Pi registers it, in their descriptions as well as their instructions.
get_changes_since,get_evolutionand episode replay count each change once. A full index could store its episode twice, so one commit read as two episodes with every total doubled, a commit recorded by both indexing and history replay was listed twice, and the markers an embedding pass and history replay leave behind were listed as changes.- Indexing a repository with history no longer leaves a second live copy of every symbol an earlier commit introduced and the latest commit did not touch. Reads showed one copy, but the duplicate doubled the raw graph, gave each symbol's timeline a phantom second version, and made deleted code look like it was still there.
get_codebase_briefingcalled without arepo_idbriefs the only repository in the workspace. It used to report that nothing was indexed and advise re-indexing a repository that was already there; with several repositories indexed it now asks which one.calculate_cyclomatic_complexityno longer refuses a unique function as ambiguous on a store that holds two rows for it, and it accepts the scope_path or the id other tools return, not only the bare name.find_codewithinclude_related=truenow returns the callers, callees and type users of its top hits, the same relationshipsget_symbol_contextlists, with each row markedoutboundorinboundand its edge kind. Search also lifts the graph neighbours of strong matches into its ranking. Both used to come back empty, because the graph walk was filtered by a number the store does not index edges under.- The symbol panel in
/uino longer lists a caller or callee whose link exists only on another branch. - The code graph no longer collapses into one white mass pushed to one side, with a ring of loose nodes around it. On repositories with many execution flows, every flow step was laid out as its own link, which switched the layout to a mode that does no clustering. Each flow is now drawn with one link to its entry point, so related code groups into clusters again. Selecting a flow still highlights every step, and the edge count shows how many links are drawn beside the total.
- Test functions no longer start execution flows. Every test that reached shared code added a near-copy of the same large flow, which buried the real flows and multiplied the graph size several times on large repositories. Run a reindex to shrink an existing index; otherwise each repository is corrected on its next saved edit.
- On Windows, the Files panel, search results, symbol details and the all-repositories file tree show paths relative to each repository instead of putting every file under a single
c:folder. Ignoring a file from the Files panel or from a graph node now works on Windows, and ignoring a file at the repository root ignores only that file, not every file with the same name in subfolders. A file's symbol list no longer includes symbols from a same-named file elsewhere in the repository. - The graph toolbar's zoom in, zoom out and fit-to-screen buttons work with the default renderer; they did nothing unless the compatibility renderer was selected. The graph also frames itself again once its layout settles, instead of staying framed on the wider starting layout.
- Upgrading with npm 12 no longer leaves your coding agents on the previous version's skills, MCP configuration and Claude Code plugin. npm 12 blocks install scripts unless you allow them, so Memtrace now finishes setup on the first
memtracecommand after an install or upgrade: an existing setup is refreshed with a one-line notice, a new install asks first in a terminal, and elsewhere it prints how to runmemtrace setup --yes. Your earlier--no-hooksand--project-onlychoices are kept,npm i -g memtrace --allow-scripts=memtraceruns setup during the install instead, andMEMTRACE_NO_AUTO_SETUP=1turns the first-run step off.
Install
npm install -g memtrace