Changelog

Every release, in one place

New features, improvements, and fixes, shipped continuously.

v3.3.2LatestOctober 1, 2026

Ruby calls without parentheses are calls.

The Rails user behind 3.3.1 sent the list that matters in a real codebase: delegate, concerns pulled in with a plain include, has_many through: with source:, and public_send. Measured on 3.3.1, each one dead-ended somewhere, and so did something larger: a hand-written method called without parentheses, which in Ruby is most calls, was never linked at all. 3.3.2 follows them, through the class hierarchy Ruby itself would use, and checked on two open-source Rails apps, maybe-finance and Mastodon, before shipping. It also stops a Dart dot shorthand in a list from costing the whole file, and gives the self-hosted SDK a free Dev license you can get yourself, with the gateway holding every plan to its limits.

Every Ruby call, with or without parentheses. post.author.full_name, a bare display_name inside User, User.find_active and self.badge are now calls in vexp impact and flow, whether the method is written by hand or generated by Rails. The receiver decides the class: a variable named post is a Post, current_user a User, a constant is that class, and a hop of a chain is what the association before it returns. A method is never linked by its name alone, so x.name or x.save on a receiver vexp cannot read stays unlinked instead of pointing at every name or save in the codebase; active? is the predicate, never the scope active.

Concerns, parent classes and delegate. A class reaches what its superclass and the modules it includes declare, so post.taggings lands on the Taggable concern and admin.display_name on User. Methods written in class << self, included do and class_methods do are now symbols too. delegate :full_name, to: :author, prefix: true defines author_full_name, and impact follows it through to User#full_name. Constants resolve the way Ruby resolves them, so two concerns that are both called Provided (Security::Provided, ExchangeRate::Provided) are never mixed up, and the model copies a migration declares stay out of the app’s classes.

has_many through: follows source:. has_many :subscribers, through: :subscriptions, source: :user now returns Users, the way Rails reads it, so post.subscribers.active reaches User’s active scope instead of stopping at a Subscriber class that does not exist. Without source:, vexp looks for the source association on the through model, as Rails does, and class_name: and source_type: are followed along the chain.

public_send is a call, or a flag. public_send(:publish), send("publish") and try(:publish) are calls to publish. When the name is computed (post.public_send(field)), vexp cannot know the method, so it no longer drops the call silently: vexp impact on a method of that class lists it as a dynamic call that may also reach it, and counts the dynamic calls whose receiver it cannot read.

Dart: a dot shorthand in a list no longer costs the file. A dot shorthand used as a list element, a map value or a record field (.active in [.active, .paused]) made the Dart grammar give up on the enclosing code, and in a class the whole file was lost. vexp now recovers those files: on 663 Dart files of open-source apps, the files lost whole went from 8 to 0. A file an older vexp could only partly read is read again at the next start, with no rebuild.

SDK: a one-repository trial, a free Dev tier a form away, both metered. The free Dev license for the self-hosted SDK gateway is now self-serve at vexp.dev/sdk/dev: an email, a link, a license, and the same again for a new one after 30 days. The gateway counts the Dev tier’s 30 lookups a day itself (run_pipeline, get_context_capsule, get_skeleton), once per request however many shards it reaches, and keeps the count across restarts. Every metered answer says how many are left; past the limit those three answer 429 with Retry-After until 00:00 UTC, while status, indexing and webhooks keep working. Without an SDK license, or with an expired one, the gateway is a trial on one repository with 20 lookups a day, says so at startup and in /health, and points to the Dev license when the day’s lookups run out. A consumer license, which covers a person’s own work in the editor and agent CLIs, counts as no SDK license. Standalone shard processes keep to the same repository band as in-process ones.

Upgrade note: a rebuild only where there is Ruby. An index that contains Ruby is rebuilt once at the first start after the upgrade, about as long as a first index; every other index is left as it is. vexp index --status shows “Parser: 3.3.2 (current)” when it is done.

v3.3.1September 30, 2026

Rails models, read the way Rails reads them.

A Tier 4 user working in RubyMine with Claude Code and Codex asked how to get has_many and belongs_to into the graph without keeping VS Code open in the background. The editor was never the missing piece: the methods Rails generates from a declaration had no symbol in the index, so nothing, not even a language server, had anything to point a call at. 3.3.1 teaches the Ruby indexer what ActiveRecord generates and links the calls to it, with no editor and no language server, for every agent alike. It also gives ZCode its own section in vexp doctor, makes it possible to tell a hook that never ran from one that ran in the wrong folder, and lets a new git worktree work from its first prompt without a manual vexp index.

Associations and scopes are symbols. has_many :posts now gives Author a posts and a post_ids method; belongs_to :author and has_one :profile give the reader plus build_, create_ and reload_ (a polymorphic belongs_to gets no build_ or create_, as in Rails); has_and_belongs_to_many and scope :published are covered too, and so are the declarations inside a concern’s included do block. Each one is anchored to its declaration line, so vexp search, skeletons and capsules show has_many :posts for posts, and a def of the same name, which is what Rails runs, keeps its place.

author.posts is a call. Ruby calls associations without parentheses, and that is exactly what vexp used to miss. vexp impact and flow now follow author.posts, post.author.profile, Post.published and a bare posts inside Author. The receiver decides which model: author.posts goes to Author, not to every model with a posts association, and class_name: is followed through a chain. A local variable named posts, a symbol such as includes(:posts) and a hash key never count, and code in other languages never links to a Ruby association. An edge a language server submits for one of these methods now has somewhere to land as well.

vexp doctor checks ZCode. ZCode reads AGENTS.md from the folder it opened, MCP servers from .zcode/config.json and hooks only from ~/.zcode/cli/config.json, with hooks off by default. Doctor now checks each of these in that order, runs vexp’s hook once to prove it works, reads the failures ZCode logged for it and spots a project nested in a folder of the same name. It speaks only in projects that show ZCode, so a machine-wide hook is never a failure everywhere else.

A silent hook you can diagnose. Sessions now say whether they are an agent, the hook or a CLI call, so the extension’s own polling no longer looks like a busy agent and the VS Code sidebar counts agent sessions only. The prompts the hook skips on purpose (under eight words, or an empty index) are recorded with their true reason, and a hook that ran from a parent folder or a subfolder and found no vexp leaves a note that doctor turns into a warning. Doctor also warns when a project lives in iCloud Drive, OneDrive, Dropbox or another synced folder, where the index is synced along with your code.

A new git worktree works from its first prompt. A customer running Codex with a git worktree per task found that every new worktree connected with no vexp tools until someone ran vexp index in it. vexp indexes only the folders you have set it up in, and a fresh worktree has no .vexp folder yet. A worktree is the same project as the repository it belongs to, so it now takes that repository’s answer: the first agent session in a worktree of a repository set up with vexp indexes it in the background, while a worktree of a repository that never had vexp is still left alone. Until that index is complete, answers say they cover only the files indexed so far, so a symbol not reached yet is not mistaken for one that does not exist; a second session in the same worktree uses the first one’s build instead of starting over, and a build cut short because its session ended is finished by the next one. A repository that commits .vexp/manifest.json used to give its new worktrees and clones an empty index for the whole session; that manifest is now rebuilt into an index. To have the index ready before the first prompt, put vexp index in the script your tool runs when it creates a worktree, such as a Codex local environment’s setup script, or set VEXP_WORKTREE_INDEX=1 so the post-checkout hook builds it during git worktree add.

A worktree leaves with its index. A worktree’s index is kept out of git, so git worktree remove works without --force and takes the index with it, and a vexp daemon started in a worktree stops by itself about a minute after the worktree is removed, so short-lived worktrees leave no processes behind. On Windows a file in use cannot be deleted: removing a worktree while a vexp daemon runs in it leaves the folder behind, then the daemon stops and the folder can be deleted. For that reason, on Windows, agents connected through vexp’s Node MCP server (Claude Code set up by the VS Code extension, for one) do not start a daemon in a new worktree and still need vexp index there; Codex, which runs vexp’s engine directly, is not affected.

Upgrade note: a rebuild only where there is Ruby. An index that contains Ruby is rebuilt once at the first start after the upgrade, about as long as a first index; every other index is left exactly as it is. vexp index --status shows “Parser: 3.3.1 (current)” when it is done. If an older vexp install on the same machine, such as an editor extension not yet updated, re-reads a Ruby file afterwards, vexp notices and rebuilds once more, so update the VS Code extension together with the CLI.

v3.3.0September 26, 2026

The file that does the work comes first — and when it doesn’t, vexp tells you why.

A Tier 4 user tested vexp the hard way: plain-English questions about a large Flutter and C# app, written before searching and scored by one rule — the expected file among the first three. Every miss had a cause vexp could not show you: a Markdown page that used every word of the question raised the bar the code had to clear, an untracked draft took the slot of the file it copied, the word typed (“voting”) was not the word in the code (“vote”), or the only link to the answer sat inside a function body. 3.3.0 fixes each of them where it happens, keeps generated files like translation classes and protobuf bindings from crowding your hand-written code, and adds vexp capsule --explain: name the file you expected and vexp tells you exactly where it dropped out, and why. It also makes Codex a first-class citizen on Windows and remembers every “no” you give it. On the public benchmark of forty real issues retrieval is unchanged.

Ask vexp why a file is missing. Name the file you expected — vexp capsule “how are refunds approved?” --explain billing_service.ts — and vexp runs the same retrieval and tells you where that file left it: never a candidate (and which words of your question it lacks), not admitted (and which file set the bar), cut at the top ten, under the score floor, past the pivot cap, over the token budget, or published at rank k. A bare file name expands to every file with that name, side by side. It writes nothing, so your next real answer is exactly the same.

Code outranks the pages that describe it. A Markdown page that repeats every word of your question used to set the bar the code had to clear, and on questions about behaviour pages took most of the admission slots. Pages now compete in their own lane: on “where does the app decide…” questions the best-covered code files are admitted first, and a small file that covers what a giant file covers is admitted beside it instead of being shut out by sheer size.

The words you type reach the words in the code. “voting” now reaches vote, “links” reaches link, and “upvote” reaches code that says vote: shorter forms your index actually contains are searched as exact words, and compounds are read as their parts. Guard rails keep it precise — an identifier you type is never split, an inflected word splits only on its base, and every form vexp adds or leaves out is logged with the reason.

Wiring inside function bodies now counts. The crash handler registered at startup, the analytics hook set up in a bootstrap function: when the only link between your question and a file was an identifier used inside a body, vexp missed it. The index now keeps the identifiers each function references — calls, callbacks passed by name, receivers, types — in a table of its own that feeds ranking but never touches vexp search, whose output stays byte-identical. A very long function found this way is shown as the thirty lines where your question’s words gather, not pasted whole.

Drafts stop taking the real file’s place. Untracked copies — a _NEW or _OLD variant, an old version parked in a scratch folder — used to outrank the tracked file they copy. vexp now recognises a copy by what it defines, ranks it under its original and doesn’t publish it while the original is there, so the freed slot goes to the next real candidate. Ask for the copy by its path (forward or back slashes) or by something only it contains and you get it; a new file that copies nothing is left alone, however recently your agent created it.

Generated files step aside. A translation class carries every string in your app; a protobuf binding carries every field name of your API. On plain questions they used to win. Files written by flutter gen-l10n, build_runner (*.g.dart, *.freezed.dart), protoc, client-gen or a designer (*.Designer.cs), or marked “GENERATED CODE — DO NOT MODIFY”, now rank under hand-written code unless you ask for them: name the file, type one of its identifiers, or say translations, protobuf or generated code. On open-source Flutter apps that commit their generated translations, the file that answers the question now lands in the top three far more often. VEXP_RANK_GENERATED=1 turns it off.

Every ranking stage, logged file by file. VEXP_LOG_LEVEL=warn,vexp::pool=debug now prints what each stage did to each file, by repo-relative path: the words searched and left out, the admission bar and the file that set it, one line per scored file with how it entered, every demotion, and what the capsule did with each of the ten candidates it received. One log target, so one filter tells the whole story.

Flutter and Dart: impact follows widget reads. Inside a State class, widget.order.isPaid now links to the getter it reads, so vexp impact lists every screen that depends on it and flow can walk through it. Classes written with Dart’s new primary-constructor syntax are parsed correctly — before, their methods were silently scattered as top-level functions — and a file that parses only in part is reported instead of passing in silence. A short name resolves to the tracked file when a draft defines the same member.

Codex runs vexp without asking — on Windows too. vexp’s tools now declare that they only read your code, so Codex stops asking permission on every call and no longer refuses them in non-interactive runs. On Windows the orientation hook really runs (Codex starts hooks through PowerShell, and vexp now registers a command that works there), vexp shows you the one-time step to trust it, and doctor says whether Codex trusts it. With several projects, Codex now gets a separate index per project on its own; vexp use --pin keeps the old single-project behaviour. On Windows, approve vexp-hint once more in Codex after updating.

Your “no” sticks. Skip on the git-hooks question now holds, project by project, and the vexp.gitHooksInstall setting finally does what it says. Tools vexp finds only on your machine — not in the project — are offered once with Set up, Not here or Never instead of being configured behind your back, and Not here and Never also remove what vexp had written for them.

Ignore rules apply at the next command. Add a folder to .vexpignore and the very next vexp capsule, search or impact drops it from the index — no daemon needed — and says so once. The live watcher follows exactly the rules of a full scan (anchored folders, negations, nested ignore files, .git/info/exclude), excluded files take their change notes with them, a vexp.toml saved as UTF-16 by Windows PowerShell is read, and a .vexpignore saved that way is flagged by index --status and doctor instead of being silently ignored.

Know what the free plan leaves out. When the free plan’s node limit leaves files out of the index, VS Code, index_status and doctor now say how many files were skipped and the index’s estimated full size, and the daily-limit message shows each plan’s real limit.

Every setup checks in, and tells you it counted. vexp now checks in once a day from every setup — VS Code, the CLI, and AI agents that run vexp on their own with no editor in sight — so your plan stays current and every machine shows up on your devices page. Activation now confirms the machine is registered and how many of your plan’s devices are in use, and the devices page shows which license it reads. The check-in sends your license token, an anonymous device id and the vexp version, nothing else; VEXP_OFFLINE=1 turns it off.

Upgrade note: one rebuild. Because 3.3.0 reads more of every file, the first start after the upgrade rebuilds the index once — about as long as a first index — and answers keep working in the meantime; vexp index --status shows “Parser: 3.3.0 (current)” when it is done. Update the VS Code extension together with the CLI. The extra reading costs a few tens of milliseconds per question on large repositories, while questions that used to be crowded by drafts or giant files often come back faster.

v3.2.5September 22, 2026

The question you type, answered by the file that answers it.

A Tier 4 user re-ran four orientation questions on 3.2.4, scored them by one rule — the expected file among the first three — and sent the questions as typed, straight from the ledger vexp keeps of every call. Three misses had one shape: the file that answered was a documented service, and the file that won was a sibling whose name repeated one word of the question, a one-line helper that carried all of them, or a two-thousand-line golden test with a single symbol. vexp ranked symbols; the questions were about files. 3.2.5 ranks both: for a short typed question a file competes as a whole, a test is found by what it calls and by what it says about itself, a symbol name typed as two words is that symbol, and impact never mistakes containment for a dependency. On the public benchmark of forty real issues retrieval is unchanged; on the wider set of a hundred and twenty it is better, with no issue worse.

A file competes as a whole. A service whose documentation explains the behaviour across forty members used to lose to a sibling whose name repeated one word: each member was a longer, weaker hit on its own, and only symbols were ranked. For a short typed question every candidate file is now also scored as one document — over the words of the question, the best match any of its members has for that word, where the members other than the file’s best add at most what the best one says on its own, so a screen of sixty one-word members does not win on count — and a file whose symbols together cover more of the question than any candidate is admitted even when no single symbol carries every word. The reason line says which it was: “file covers 6 of 6 question words across 42 members”. Two smaller things feed it. A question word is searched with its inflections as the index holds them, so “rate” reaches “rating” and “decide” reaches “decides”; and possessives and contractions are read as the word they belong to — “the phone’s language isn’t supported” searches phone, language and supported, where the fragments used to be search terms of their own. Documents are never lifted this way, and a file named by the question still leads.

A question that asks for a test is answered by a test. A golden test with no doc comment and one symbol was invisible to the text search: nothing but its path words was indexed, and the one route that admitted test files let a helper with all four question words take its place. Two changes. What a test says about itself is searchable: for Dart, JavaScript and TypeScript test files the descriptions given to test(...), group(...), describe(...) and it(...) are part of the enclosing symbol’s search text, which takes one index rebuild on the first start after the upgrade. And tests are found by what they call: on a question that asks for a test, the test files with call edges into the symbol the question names join the candidates with its score (“test calling GuideParser.parseGuide, named in the question”), tests of the other leading candidates join below the code they call, non-test files are listed after the tests as context and never cut for it, and a test found this way is shown at its call site, not as its whole body. A test file named by its bare file name is answered directly; on 3.2.4 that returned nothing. Known limit: a JavaScript test whose describe and it callbacks sit outside any named function has no symbol to carry its descriptions and stays reachable by path only.

A symbol name typed as words is that symbol. “guide parser” is GuideParser, the way “guide-parser” already was; a space no longer costs the match. On an ordinary question it is a strong candidate beside the text hits; on a question that asks for a test it is the symbol whose tests are asked for. An agent noun reaches its verb — “parser” also finds parse, parsed and parses — with the noun itself ahead when both exist, and a short list keeps user, order, timer, header and folder as they are. The reason line is honest about the weaker kind of match: “name contains “language”” when a symbol name merely contains a question word, “symbol name matches query” only for a whole identifier or an exact token.

impact never mistakes containment for a dependency. The impact of a class member listed the class itself as a dependent, under a CONTAINS row, and everything that depended on the class beneath it: every constructor call and importer of Order appeared under isPaid. The index knows that Order contains isPaid, and the walk followed that link like a dependency. It never climbs from a member to its class now: the class is named once in the header as context, never listed, never counted, at any depth, and the list holds the readers and callers of the member itself. So that the list is not empty for a getter, a read of a Dart getter without parentheses or of a C# property is linked as a call when the receiver’s type can be read from the code — a typed field, parameter or local, a constructor, a static accessor such as Service.instance or Provider.of(context), a typed collection in a where() or a for-in, and chains up to four hops; Java, Kotlin, Swift and Scala gain the same chains through their accessors. The first start after the upgrade rebuilds the index once and the daemon log says why. flow keeps the class-to-member hop on purpose: there the question is whether a path exists.

Documents rank below code on a question about behaviour. A LANGUAGE_POLICY.md and a store data-safety page sat fourth and fifth on questions about where the app decides something, behind code, but a name rule that demotes them would also demote a RETRY_POLICY.md on “explain the retry policy”. 3.2.5 reads the question instead: a short question about behaviour — where, when, how something is decided or handled — that does not ask for a document ranks prose below code and lists code first. A guide is not a document cue, and a question that asks to explain a policy keeps its page on top.

A partial class is one definition, and a field says it is not indexed. MainForm.cs and MainForm.Designer.cs declare one WinForms class, and an anchor on MainForm.NotifyIcon answered “1 of 2 definitions, the question did not say which”. The parts of a partial class now count as one, and a member the index does not hold under that class says so on the class pivot — “NotifyIcon is not an indexed member of MainForm (fields and auto-properties are not indexed) — anchored on the class” — instead of an ambiguity note. Ambiguity counts definitions, not admitted rows: a constructor beside its class is no longer a third place for two.

A workspace on a network share keeps its path. A project on a mapped drive, a \\server\share path or \\wsl$ canonicalizes on Windows to the verbatim form \\?\UNC\server\share\dir, and vexp dropped only the \\?\ prefix, leaving a relative path that was resolved against whatever the process’s working directory happened to be. The daemon then tried to create its .vexp folder under the VS Code install directory and died with “Access denied (os error 5)” right after the licence line, on a machine whose permissions were fine — a user on a Samba share from Windows 11 read the error correctly and said so. The verbatim UNC form now maps back to \\server\share\dir, the failing step names the path it could not create, and the extension derives the daemon’s pipe name and its registry row from the same canonical spelling the daemon uses, so a mapped drive no longer leaves two rows for one workspace or takes two slots on the plan’s concurrent-workspace cap. The command line, the MCP bridge and the agent configurations vexp writes resolve the root the same way before hashing it, so on a share “vexp status” finds the daemon instead of starting a second one, and Claude Code and Codex reach the pipe the daemon actually bound.

The trace answers the question you would ask next. At debug level the candidate search used to print two lines: how many candidates the text search returned and how many survived. Thirty is the cap, so the count said the search filled its quota, not what with. The trace now prints each stage and what it added — the stem groups, the file-coverage scan and every file it admitted, the file scores, a symbol named by the question’s words, the tests found through their calls, the file-name stage, and which candidates a test question set aside as context — so whether the file you expected was ever a candidate is answered, file by file, with the reason. Two engines on one index are visible too: the manifest carries a parser stamp that only 3.2.5 writes, index --status prints “Parser: 3.2.5 — rebuild pending” when an older daemon has re-parsed the checkout, and the next 3.2.5 start rebuilds once.

v3.2.4September 20, 2026

Two calls count as two, wherever the index is opened from.

A Tier 4 user made two calls and was told his daily budget of 1,500 was spent. He sent the exact line from the daemon log, and it named the cause: the usage counter is signed against tampering, and two calls fired in the same agent turn had raced each other on that signature. The same week a Windows user stopped a daemon that had indexed his whole home directory four times, and four times a launcher brought it back. This release closes both, and the way each was reported — an entry that reads as its count, a directory that is refused before anything is written — is the fix.

A counter written by two calls at once is still a counter. Every daily-usage row carries a signature, so a count edited by hand reads as the day’s whole budget spent. The increment, the read-back and the signature were three separate statements, and every request runs on its own connection: when an agent issued run_pipeline and get_skeleton in the same turn, the signature written last could belong to the count written first. The row then held two calls with the signature for one, and until midnight UTC the daemon answered every quota-gated call with “1500/1500”. A user quoting that line was right to say it did not demonstrate 1,500 calls; it demonstrated two. The increment is one transaction now, holding the write lock from the first statement to the commit, so a signature can never lag behind the count it sits next to. The regression test fires six increments at once, sixty times over, on a file-backed index; it failed on the old code in about a third of its rounds.

The signature travels with the index, not with the machine. The signing key used to include the home directory, so an index opened from two environments was signed with two keys: the host and a Dev Container, WSL and the Windows binary on the same checkout under /mnt/c, a shell with VEXP_HOME set and one without. Whichever daemon had not written last read the row as tampered, with the same result as above. The key now includes a random salt minted once per index and kept beside the rows it signs, so every process that opens that index verifies the same signatures. Reads never mint one: index_status reads usage on shards the SDK gateway holds read-only, and that promise stands. A row signed by an earlier release still verifies through the old key on the day you upgrade, and the next call re-signs it; an edited count stays tampered under both.

Your home directory is never a workspace. The .vexp folder in your home directory is where vexp keeps its own state — configuration, licence, the daemon registry — so it always looked initialized, and a daemon started there indexes everything under home. On Windows a PowerShell opens in the home directory, and so does many an agent’s shell; one daemon started that way holds an index, and from then on every folder under home without a workspace of its own resolved back to it. A user stopped that daemon, removed its registry row and deleted its index, four times; the login supervisor restarted the row, and a start from a shell whose working directory was home trusted its working directory. Every entry point now asks the same question before touching anything — the daemon, the command-line start, index, init, the MCP server, the Node bridge — and the home directory and a filesystem root are refused with the reason and the command to run instead. The supervisor never resurrects a home row, doctor names one when it sees it, and VEXP_ALLOW_HOME_WORKSPACE=1 lifts the refusal for someone who really means it.

vexp forget keeps a workspace forgotten. Stopping a daemon, removing its registry row and deleting its index each undo one of the three things that bring it back: the login supervisor restarts every registered manifest within a minute, a live daemon rewrites its row on the next health tick, and a fresh start registers itself again. vexp forget does all three at once, for the current directory or the workspace you name, and vexp stop points at it when the workspace has a manifest. At the home directory only the index files go — configuration, licence tokens and the registry stay, which is what deleting the folder by hand would have taken with it — and --purge is refused there.

vexp use says when Codex was left where it was. A hand-written [mcp_servers.vexp] section in ~/.codex/config.toml is never rewritten, and vexp use printed its success line anyway: the user “repointed” Codex and watched it launch from the old directory. The command now exits saying what did not happen and the two lines to add by hand, and doctor reports the directory pin a Codex section carries.

v3.2.2September 18, 2026

Every line of the answer describes the same workspace.

The tester whose nine-repository Windows workspace lost its daemon in 3.2.1 ran a full regression pass on the fix. The question that crashed it now completes on CPU and GPU, and two idle GPU restarts no longer write a verdict against the card. The pass turned up four more findings, and three had the shape of the last round: a line that described the primary repository while the answer had come from all nine. This release closes them, gives the daemon a log however it was started, and removes a write that a self-hosted SDK deployment should never have made.

An impact list names the repository each caller lives in. Ask for the blast radius of a method in a connected repository and the callers come from that repository’s graph, which is where vexp walks it. The structured impact summary, though, labelled every one of them with the primary’s name: the caller and its path were right, and the label pointed at a repository where the file does not exist. Anything that follows the label, an agent opening the file or a tool grouping callers by repository, went to the wrong place. Each dependent now carries the repository it was found in, on the plain and the streaming path alike.

The notes under a workspace header describe the workspace. Since 3.2.1 the header of a multi-repository answer counts everything the answer covers — 4,667 files and 85,284 nodes across nine repositories in the report. Two notes below it still read one manifest, the primary’s. So the answer said “57% of this index is css”, which was true of the primary’s 6,812 symbols and false of the workspace, where css is 11,986 of 85,284, about 14%. Both notes now read every repository the header counts. A language that makes up a large share of the whole answer and resolves no calls is still named; a stylesheet-heavy primary no longer speaks for nine repositories of Java. Single-repository output is unchanged, character for character.

Each repository says which vexp built its index. Symbols and call edges are computed when a repository is indexed, so an index built by an older release answers with that release’s parser until it is rebuilt, and vexp says so. After a setup that re-indexed only the primary, the note disappeared — only the primary’s version was read — while eight members were still on the previous release. The note now names the repositories it is about, one line per building version, with the command to run on each, and index_status reports the building version per repository. The version itself is harder to fool, too: a git commit or a checkout sync used to restamp an index with the running release without rebuilding anything, so one commit after an upgrade silenced the note. Only a full index moves it now. Nothing changed at index time between 3.2.0 and 3.2.1, so a member built by 3.2.0 is not missing anything today; the note is for the release where it will.

Self-hosted SDK: the indexes you serve are only read. The SDK gateway serves your repositories from their indexes and promises to open them read-only. Every query it answered nevertheless left a row of internal budget feedback in the index of the first repository of each shard, a write present since the earliest SDK builds and missed by the 3.1.1 sweep because it lived inside the ranking engine rather than the request handler. It sat in SQLite’s write-ahead log, which is why our own certification caught it only when a checkpoint happened to land inside the check’s window. The write is gone, with two quieter ones beside it (V-REF cache persistence and the prompt-hint activity log), and the certification now measures the property itself: across three queries and a shutdown, every index file and every write-ahead log stays byte-identical. The V-REF cache keeps working in memory, so answers do not grow by a single token.

A daemon log, however the daemon was started. The daemon writes its log to one place, and the program that launches it decides where that goes. The command-line start writes .vexp/daemon.log and VS Code writes .vexp/vexp.log, but the login supervisor started the daemon with its output discarded: in the report, a daemon answered requests for hours while daemon.log still ended at the previous day’s crash. The supervisor now writes daemon.log the same way, keeping the previous boot as daemon.log.1. The local model got the same treatment. Every inference was already logged with its prompt size, never its content, but a stage skipped because the model was busy with another request left no line, so an empty log could mean either. It says so now, and a CPU machine states once that the smart preprocessor stays rule-based there by design.

v3.2.1September 17, 2026

The model cannot take the daemon with it.

A tester asked one question of a nine-repository Windows workspace and the daemon was gone. Not an error, not a fallback: llama.cpp does not refuse a prompt longer than its batch, it asserts, and a ggml assertion is abort() — there is no error to catch and no fallback to run, the process simply ends. His client then reported a JSON parse failure about a process that no longer existed. This release is that report: the crash, five other ways the daemon could be lost that the same review turned up, and the three reporting gaps he sent with it — every one of which described his primary repository while the answer had come from nine.

A prompt the local model cannot take is refused, not fatal. The inference context was built with the window alone, so the batch limit stayed at llama.cpp’s default of 2048 tokens while the whole prompt went into a single decode. Above that the library asserts rather than returning, and the assertion runs before any computation, so a GPU daemon would have died exactly as his CPU one did. Reproduced here against the shipped model, then closed at the only place that can be sure of the count: the prompt is measured and admitted before llama.cpp sees a token, and the batch limit is stated in our code rather than inherited from theirs. A refused prompt now costs a reranking, because every caller already treats an error from the model as “use the deterministic path”. The prompt that overflowed his workspace was the reranking one, built from every pivot a nine-repository fan-out had merged with nothing counting its tokens; it fills a budget now, and the candidates past it keep the order they already had. And the whole local-model path is compiled and tested in CI for the first time, which is why nothing in our own suite could reach this.

Five more ways to lose the daemon, closed. A release build aborts on a panic, so a panic anywhere in the daemon is the daemon. Six places truncated a string by counting bytes, which panics when the cut lands inside a multi-byte character: two sibling folders named café and cafè share four bytes, and slicing there ends the process. Three of the six sit on paths that run at the daemon’s default log level. Separately, the GPU verdict read an ordinary Windows stop as a card that cannot run the kernel: the marker said “running the model” from the moment loading finished, for the daemon’s whole life, and on Windows every stop is a hard kill — so two restarts with no inference in between wrote a verdict against hardware that had never been asked anything. It is written only around an inference that is actually running now, an idle stop is never counted, and a machine with no GPU never enters the lifecycle at all.

A daemon that stops mid-request says so. A daemon that goes away while holding a request closes the connection without a byte, and that empty reply went straight to the JSON parser — so the agent was told “EOF while parsing a value at line 1 column 0” about a process that had ceased to exist, and the Node bridge, which is the server most agents actually use, said only that the connection closed unexpectedly. Both clients now name the tool that went unanswered, say where the reason is, and tell the agent to retry once before falling back to its own tools. The error is marked rather than worded, so the input that ended one daemon is never handed to a fresh one three times over, and the paragraph written for an agent is stripped from the commands a person runs in a terminal.

A multi-repository answer says which repositories it came from. His member repositories held 773 unparsed JSP files and 22 EJS files that never reached the answer, because unparsed files were read from the primary repository alone while the oversized-file warning crossed the whole workspace. Every repository the answer came from is read now, and one merged line names the gap with paths you can open. The header counted the primary index too — 421 files and 6,812 nodes printed above a pivot that came from another repository, while doctor reported 4,667 and 85,284 for the same workspace at the same moment. It counts what the answer covers and names the scope, the health verdict follows the same figures so an umbrella root holding its members is no longer called an empty index, and a repository that holds nothing is named rather than hidden inside a total. The oversized-file note points at the repository that actually holds the file, and at that repository’s own configuration for raising the cap. Single-repository output is unchanged, character for character.

An annotated method keeps its signature. A Java method under @Override on its own line came back as “@Override { … }”: a body with no name, no parameters and no return type. The signature vexp stores is the first line of a declaration, and in Java, C#, Kotlin, Scala, Swift, PHP and Dart the annotation is part of the declaration, so the first line is the annotation. The signature is recovered from the body when the skeleton is rendered, which means an index you already have needs no rebuild. The standard form keeps the annotations in front of the real signature and shortens a long or wrapped one to a marker; the minimal form prints the signature alone. Skeletons for a pivot in a connected repository are read from that repository as well, where they used to be looked up in the primary and found nothing.

v3.2.0September 16, 2026

Ask in plain English. vexp reads your comments now.

The question a developer actually types is the one without a symbol name in it: "where do we decide the fallback locale", "which screen shows sync status". 3.2.0 is built for that question. vexp now indexes the doc comments of fifteen more languages, chooses between same-named symbols the way you would, tells you plainly when it is guessing, keeps notes and status reports from outranking your source, follows C# and JVM calls through their receiver, and only counts an orientation when the agent actually received it. Every change ships with a test that failed before it, and retrieval quality on a public benchmark of forty real issues across eight languages is unchanged or better.

Your comments are searchable. Dart ///, C# <summary>, Javadoc, Doxygen and # doc comments are now part of every symbol, for Dart, C#, Java, Kotlin, Scala, Swift, C, C++, Objective-C, PHP, Ruby, Bash, PowerShell, Lua and OCaml, next to the JSDoc, Python, Go and Rust docs vexp always read. A question written in the words of your comments lands on the file they describe. On a Flutter plus C# project, questions phrased in comment vocabulary went from four of eight to eight of eight top-1, with no question getting worse. Licence headers, trailing remarks and pragmas stay out; secret-shaped values in a comment are masked before they are stored. The index rebuilds itself once on the first start after the upgrade.

Same name, seven definitions: vexp picks the one you mean. Ask about isReady on the sync controller and you get SyncController.isReady, chosen by the rest of your sentence, with the why line saying so: "1 of 7 definitions, matched by sync controller". Qualify a name and it joins: SyncController.isReady, SocketClient.connection, even when the member is a plain word. A Dart isReady is never answered with a C# IsReady. And when the question genuinely does not say which one, the answer says so and lists the classes, instead of handing you five arbitrary ones.

The answer says when it is guessing. Every run_pipeline answer now carries one honest line: anchored on an identifier you typed; anchored on a name with N definitions, listed; anchored on a plain word that happens to name symbols, with a hint to qualify it; ranked by text similarity alone; or, when nothing matched, which of your words the index does not know. A plain English word that is also a symbol name no longer decides the whole answer: it is shown, at most twice, and the files your comments point at stay in the list. Terms added by the local model only widen the search; they never pose as something you typed.

Typed questions match the words you use. "validated" finds Validate, "commands" finds command: for a typed question of up to twenty-five words, vexp also tries a stem of each plain word. Identifiers, pasted issues and the exhaustive vexp search stay literal, exactly as before. Turn it off per index with query_stemming = false or VEXP_QUERY_STEMMING=0.

Your notes and status reports no longer outrank your code. A trial log that records the questions you ask, a MISSION_ACCOMPLISHED.md an agent wrote when it finished, a dated state note: all of them restate your question in your own words, which is exactly why they used to win. 3.2.0 treats them as records, the way it already treats changelogs, while a FAQ or a guide that answers the question keeps its rank. Agent instruction files (.claude/, .cursor/, CLAUDE.md, AGENTS.md) rank as repository furniture, vexp search lists code before the documents that mention a name, and the why line tells you which rule applied.

C# and JVM call graphs follow the receiver. validator.Validate(...), base.Validate(...), PcCommandValidator.Validate(...): calls through a receiver to a method name with several definitions now resolve by the receiver's declared type, the base class or the static class, in C#, Java, Dart, Kotlin, Swift and Scala. Overloads bind by argument count, an [Attribute] above a declaration is not a call, and a bare call inside a class stays in that class. C# expression-bodied properties and members inside #if blocks are indexed. vexp impact prints through which node and at which line each dependent was reached, and says when same-named symbols exist that a receiver could not be linked to. Questions that name a test, a fixture, a golden or a snapshot get test files in their answer.

An orientation counts only when the agent received it. The prompt hook waits three seconds for the daemon; on a large index a long pasted prompt can take longer, and until now the daemon counted that orientation as served. It is recorded as late now, in the ledger, in vexp savings, in usage-report and as a doctor warning, it does not spend one of the session's orientation slots, and after two late orientations the daemon stops computing seeds for long prompts until one is delivered again. VEXP_HINT_BUDGET_MS tunes the client budget between 0.5 and 4.5 seconds. vexp capsule runs from the command line are written to the same ledger, so a trial you ran by hand is recoverable afterwards.

Windows: no more terminal windows. On Windows 11 a daemon started in the background has no console, and every git it ran opened a Windows Terminal window for a quarter of a second, up to a hundred a minute. The daemon now spawns every child with a hidden console and the Node supervisors hide theirs; nothing flashes.

ZCode and Friday Code. vexp setup now configures sixteen agents: ZCode (zcode.z.ai), with its mcp.servers entry and user-level hooks, and Friday Code (tryfriday.ai), with the friday.md it reads at the project root.

Also in this release. A v2 to v5 index reaches the current format in one open, and an older binary opening a rebuilt index does not trigger a second rebuild. A query made only of emoji or punctuation returns no pivots instead of an error. The impact mermaid graph draws transitive dependents to their real parent. vexp answer says "6 of 9 definitions" when it truncates. C# expression-bodied properties render as one line in skeletons, and generic type names such as List<int> survive in summaries.

v3.1.3September 10, 2026

Finished work is not a timeout.

A tester on a 229-file Python repository asked his agent to read the vexp ledger by hand and came back with the best bug report this project has received: every verify_done call an agent made had ended in “Timeout: no response from daemon after 30s”, while the daemon had finished each one in forty to a hundred and seventy seconds and cached a verdict nobody read. The repository lived on a Windows drive mounted into Docker over 9p, where every file operation crosses an operating-system boundary, and doctor had reported every line green. This release is the answer to that report — and, because he asked for it, the command that writes the next one.

A client waits as long as the tool takes. Every vexp client waited thirty seconds for every tool, one number for a health check and for a whole-tree verification alike. A client that stops waiting cancels nothing, so on a slow filesystem the only effect of the limit was to turn finished work into an error. Each tool now has its own budget — verify_done gets two minutes, run_pipeline ninety seconds, everything else the thirty it had — held identically in the Rust proxy and the Node bridge, with a test that reads one to check the other. When the budget does run out, the message says what is true: the daemon accepted the request and is still working, and for verify_done, that one more call in a moment returns the finished verdict from cache.

One git call per check, and a log line that says where the time went. Where the forty seconds went: a verify_done round trip ran git status four times, spawned one git process per touched file to read its committed version — including files that had never been committed — and walked into virtualenvs looking for tests. On a native disk none of that shows; behind an OS boundary every spawn and every stat is paid in full. It is one status per check now, one batched git process for every committed version, nothing asked for untracked files, and a skip list that knows what a Python dependency tree looks like. The ticket also asked what the daemon was doing while the client waited, so every call now logs its phase breakdown — git, parse, dependents, imports, tests, docs — and a call slower than five seconds carries it in the report.

The stop gate reads your prompt, not the harness caveat. The verification gate took the first user turn of the transcript as the task. After a /clear or a slash command, that turn is the harness talking to itself — “Caveat: The messages below were generated by the user while running local commands” — and twelve of the twenty-six verify_done calls in that ledger carried it as their task text. The scope verdict anchored on nothing, the memo never matched the agent's own call, and up to a hundred seconds of analysis answered a question nobody had asked. The gate now finds the first turn that is actually a prompt, and reads the transcript only that far instead of loading the whole file on every stop.

vexp doctor --report: the bug report, written for you. The tester suggested it in one line: a way to ask the tool for a report you can send. vexp doctor --report writes a Markdown file with the doctor output as printed, the local tool-call ledger from the last thirty days — per tool: calls, median, p95, max, how many ran past the old thirty seconds, how many ended with no result, and the ten slowest — plus the warnings and errors from the daemon log with repeats collapsed, and your vexp configuration. Nothing a person would have to redact leaves the machine: parameters are reported by length only, and secret-shaped values are masked everywhere. The ledger comes from a new vexp-core usage-report command that opens the index database directly, so a daemon that is down is not a reason to have no report. The file lands in .vexp/vexp-report.md, gitignored, ready to attach to a ticket.

Doctor names the slow filesystem, and the socket's quiet move. On the machine that reported all this, nothing in vexp was wrong except where it was running, and no line of doctor said so. Both doctors — the CLI and the VS Code extension — now name a workspace that sits on a 9p, drvfs, grpcfuse or network mount, say what it costs, and give the remedy: keep the clone on the native filesystem. A second tester measured something else: the full socket path of his Claude Code worktrees came to eighty-nine of the hundred characters a Unix socket allows, beyond which every vexp client silently moves the socket to /tmp — and a slightly longer auto-generated worktree name would have crossed it with nothing announcing the change. Doctor now prints the margin, warns at fifteen characters or fewer, and explains a socket that already lives in /tmp rather than reporting a daemon as missing. The daemon logs the relocation at startup. And the opt-in stop gate is no longer flagged as a warning when it is, by default, not installed.

Secrets never reach the ledger, and a poll is not activity. The tool-call ledger kept every parameter verbatim, and one of them was a prompt that read export LOGFIRE_TOKEN=… — local and gitignored, but the same database a bug report is built from. Secret-shaped values are masked before the row is written: the shield's own detectors for tokens, keys and private keys, plus KEY=value assignments and bearer headers. The same ledger was 99.6% health checks — index_status polls from sidebars, doctors and hooks, some 860 a day — drowning the rows the analytics exist for. A poll is no longer logged as agent activity, and the report counts the ones an older daemon left behind so they cannot fill the window.

v3.1.2September 7, 2026

A zero that means “not measured”.

Three testers went looking for what vexp gets wrong when it looks right: a 3,000-file Flutter and C# monorepo, a nine-repository Windows workspace rebuilt from a clean state, and a Windows machine running three IDEs at once. Between them they found vexp reporting a version it had not read, a call graph it had never built, and a daemon count it had just deleted — each one indistinguishable, from the outside, from a correct answer. That is the only kind worth a release: a wrong answer you can see is a bug, and a wrong answer you cannot is a belief.

Dart: a call written inside a method is a call. The Dart grammar splits a declaration into a signature and a separate body, and vexp compensated for that only where the body sits directly beside the signature — which is true of a top-level function and false of every method in a class. So a Dart method was stored with its own signature as its entire body: twenty-three bytes where the source has the call. No call written inside a Dart method could produce a call edge, which in a Flutter codebase is nearly all of the code, build() included. On the monorepo that reported it, a privacy gate with 27 call sites across 15 files answered “no callers”, while vexp search found all 27 — the class node keeps the full class text, so the references were always there and only the graph was empty. Its author's control was the giveaway: a second gate that worked, because the callers of that one lived in test files, and a Dart test calls from inside a top-level main(). The same extraction path was also dropping constructors, getters, setters and operator == entirely, and attaching mixin and extension members to the file rather than to their owner. All indexed now. The conformance matrix that certified Dart as full throughout had two top-level functions calling each other — the one shape that worked; a second matrix now puts the caller inside a class, across twelve languages.

The header says what this index cannot answer. A language that holds most of a codebase and resolves no calls makes impact answer nothing everywhere, and nothing about that answer says so. One line now does, in the header of every run_pipeline result: 96% of this index is dart and its call graph is EMPTY — a zero from impact means “not measured”, not “no callers”. It is computed at index time, so it costs nothing per query, appears only when the gap is big enough to change what an answer means, and stays silent otherwise; a language that resolves no calls by nature rather than by defect gets a different sentence, so EMPTY always means something is wrong. Size-skips are now read from every connected repository instead of the primary alone — oversized files in a member repo were invisible to the disclosure that exists to make them visible, while doctor listed them at the same moment. And because symbols and call edges are computed once, at index time, a parser fix lands only on re-index: an index built by a different engine than the one answering now says so, in the header and in index_status.

Two vexp installs now converge instead of fighting. Everywhere two installs contend for one shared thing, vexp asked “is this the same as mine?” and acted on any difference. With one install that reads as caution; with several at mixed versions it is a fight nobody wins. A supervisor left by an older install killed the newer MCP server on port 7821 and spawned its own, the newer CLI did the reverse, and a tester watched the server come back with a fresh pid every two minutes for days. The daemon adoption guard had the same shape, so with three IDEs at mixed extension versions each killed the other's daemon on activation — and the sidebar's own warning offered a Restart button whose only effect against a newer daemon would have been to downgrade it. All ordered now: older is replaced, newer is adopted as it is, equal is left alone. The version recorded for the MCP server was the version of the process that spawned it, read once at startup and held forever, so a supervisor running since before an upgrade stamped its own old version onto children running the new build — doctor reported v3.0.1 for a process two minutes old, on a machine where 3.0.1 was no longer installed anywhere. The record now carries the build being served and, separately, the process that started it, which is the one thing no takeover rule can fix from this side: doctor names that process and its pid, and ending it is the whole remediation.

A supervisor never deletes a daemon it does not own. vexp serve rebuilt the daemon registry from scratch on every health pass, keeping only the rows it had just resurrected and writing that as the whole file. Anything its plan skipped — a daemon started by someone else, a linked worktree — was deleted while it was alive and answering on its socket, and because the pass repeats every minute, the deletion renewed itself. On the machine that found it, three live daemons reported as one, and the two that vanished were precisely the two still serving a pre-upgrade build. The concurrency fence counts those rows, so this was not only a visibility bug: restarting a supervisor silently freed license slots. Liveness decides what stays in the registry now, never ownership. And because an upgrade restarts the daemon of the workspace it runs in and leaves every other one alone, vexp daemons flags any daemon older than the installed engine — it is the one place that sees them all.

One connection is enough to ask a daemon a question. On Windows the daemon serves its named pipe one client at a time: it creates an instance, waits, handles that connection, and only then creates the next. Two commands probed the endpoint for liveness and then opened a second connection to do the actual work, so the probe consumed the only instance and the real call arrived to nothing. vexp daemons printed v?, 0 nodes, up 0m for a daemon doctor reported healthy with 85,284 nodes, and vexp capsule --repos fell back to its in-process index against a perfectly reachable daemon. Unix has a listen backlog and never showed it. The call is the probe now, and the connection retries the two errors that mean the server is between accepts. Where stats cannot be read they are reported as unavailable rather than rendered as zeroes. The capsule fallback also says what it lost: the in-process engine opens one repository, so a --repos scope naming another was not narrowed but ignored, and the answer came from somewhere else entirely.

Setup writes the hook it registers, and never gives up in silence. Claude Code reported a missing vexp-hint.sh on every prompt: the extension deleted that script under its shipped defaults while registering the hook that points at it, re-running the configuration re-did the deletion, and since the extension reconfigures on every activation, reopening the window undid any manual repair. A script and its registration are removed together now, and a test asserts the rule rather than the one hook. The Antigravity MCP writer was the last config reader still intolerant of a UTF-8 BOM — PowerShell's default — so on a file written that way it registered nothing and said nothing, reported as “setup does not recreate the registration”; the Codex hooks file had the same latent bug. An existing entry that vexp cannot version, such as a hand-written launcher, is still left alone, but now says so, because a setup that changes nothing must not look like one that worked. The bootstrap that replaces a stale daemon was skipped silently outside a configured workspace. And vexp setup no longer blocks on its agent prompt when nothing is attached to the terminal: for a script or an agent following a doctor line, that prompt was not a question, it was a hang.

Smaller fixes that shipped along the way. A migration backup kept inside the project — a dated copy of an agent config directory — was indexed and returned as an architectural pivot: a backup holds a full copy of what it shadows, so ranking cannot help, because the copy really is as relevant as the original. Those are pruned now, narrowly. A workspace member whose directory was replaced wholesale lost the link back to its primary and silently indexed with standalone defaults, a 512 KB cap where the workspace said 2048; indexing from the primary repairs the link and prints what it repaired. The size-skip warning prints the absolute path of coverage.json, because on a monorepo people reasonably look for it beside the package they are working in. doctor reports the compressor as loading while the model is still starting, instead of naming the rule compressor for twenty seconds. setup-llm --status asks the running daemon what it is actually doing, so GPU capability and GPU in use are two lines rather than one. And the remediation for a stale MCP server names the command that does the job.

v3.1.1September 3, 2026

One project, one index.

Five field reports in three days, each reproduced on the shape it described: a headless Ubuntu box that rebooted into thirty daemons, a Windows workstation where the MCP command was the editor itself, a WSL project Windsurf could not see, a Dev Container whose paths came home to Windows, and a WordPress plugin whose agent cited code that never shipped. The thread: vexp must never multiply itself, never point at a binary that is not there, and always say which side of a boundary a file is on.

A git worktree is the same project — and stays that way after a reboot. Agent harnesses create a linked worktree per parallel task; Claude Code keeps them under .claude/worktrees. The generated git hooks indexed every one of them — a full copy of the repository, an 80 MB index and a daemon apiece — and the login supervisor brought them all back at boot: on one 16-core host, thirty daemons in 237 milliseconds, 1,585% CPU, two gigabytes a second of reads and a hard reset. The hooks now recognise a linked worktree from .git's own gitdir line — no path convention, so every harness and a plain git worktree add look the same — and skip it (VEXP_WORKTREE_INDEX=1 opts back in). vexp serve never resurrects a worktree daemon and staggers the daemons it does bring back. A session working inside a worktree can still start one on demand; it lives for the task and shares one license slot with its repository.

Pivots outside the package are tagged (not shipped). vexp indexes the working tree; a project delivered as a package ships a subset, and the file that says which is not .gitignore — it is .distignore for a WordPress plugin, .npmignore for an npm package. An agent reasoning from the index cited code that was present locally and absent from the ZIP: 655 indexed files, 341 in the archive, a broken delivery. When the repository root has such a file, or delivery_ignore in .vexp/vexp.toml names one, run_pipeline now adds a header line — 2 of 5 pivots are outside the package — and tags each of them in its heading. Real gitignore semantics, ranking untouched, and byte-identical output for everyone without such a file.

Windows setup that survives the editor, the upgrade and PowerShell. Four things from one report, six agents on one machine. The node resolver accepted the extension host's executable behind a denylist of names that every renamed Electron fork walks through — Cursor.exe became the MCP command, the editor launched instead of a server, and because the path existed the repair logic adopted it forever; the check is positive now, and an entry that launches Code, Cursor, Windsurf, Trae, Kiro, Zed or Antigravity is rewritten. The extension's hooks bake a path inside a versioned folder the editor deletes on update, so they fell open until the next activation; they now fall back to the newest installed vexp binary at run time. A UTF-8 BOM, PowerShell's default, no longer makes a config unparseable — and since vexp rewrites without one, the next setup heals the file for the editor too. And setup finds node where a Dock- or Start-menu-launched editor cannot: Homebrew on Apple Silicon, Volta, fnm, the Windows installers, and as a last resort the user's login shell. vexp doctor exits 1 when it reports failures, so scripts can gate on it; the safety copies it keeps before rewriting a config live under .vexp/backups instead of next to the file.

Windsurf and Devin Desktop read what vexp writes. Cascade reads exactly one MCP configuration, ~/.codeium/windsurf/mcp_config.json, and no project-level file; the .windsurf/mcp.json vexp wrote for several releases was read by nothing, and a user in WSL found out by building the Windows-side bridge himself. Setup now writes the file Cascade reads, .devin/mcp_config.json for the Devin Local agent, and the Devin Desktop Next channel's own copy when that build is installed; the file nobody read is retired. Inside WSL, where Cascade starts its servers on the Windows side, vexp setup and vexp doctor print the wsl.exe bridge entry with your real paths, ready to paste.

The home directory is not a workspace. ~/.vexp holds vexp's own state — configuration, license, the daemon registry — and the workspace discovery mistook it for a workspace marker. An agent that spawns MCP servers from the home directory, as Antigravity does, resolved the whole home as its workspace, and the same directory counted as consent to index it. The state directory is no longer a marker, in the resolver, the MCP bridge and doctor alike, and consent needs an initialized index or a real .vexp of your own.

A connected repository answers everywhere the primary does. On a nine-repository Windows workspace: get_skeleton returned nothing for a file run_pipeline had just located in a connected repo, because a call without an alias consulted the primary only — it now walks every connected repository, and an absolute path pins its own. vexp doctor run inside a connected repo treated it as a standalone workspace and reported a daemon that was never meant to exist; it now targets the parent workspace, and coverage gaps are listed per repository with the alias. Files matching exclude_patterns no longer count as oversized — the walk consulted the exclusions after the size gate, so a file excluded on purpose kept appearing as a gap. And doctor runs the Codex Windows hook the way Codex runs it, so a correct hooks.json no longer fails the probe.

Smaller fixes that shipped along the way. The LLM banner in the sidebar offers a Restart daemon button and never names a CLI command an extension-only user does not have — enabling the model changes nothing until the daemon restarts, and now it says so. The manifest stamp follows HEAD after the startup sync, so an agent reading it no longer reports an index three weeks older than it is. vexp mcp --proxy with no daemon fails at startup with the two ways out, on every platform. doctor names the HTTP MCP server's version and warns when an old build still answers on port 7821. daemon-job-warning and mcp.token are ignored in .vexp. And the Activity counters explain themselves for agents without prompt hooks — GitHub Copilot's usage shows under Sessions as MCP tool calls, which is where it always was.

v3.1.0August 31, 2026

The index says what it left out.

Three field reports arrived in one week, from a lifetime customer on a 3,000-file Flutter and C# monorepo, a Windows user setting up Cursor, and a GitHub Copilot user who saw no vexp tools at all. Every one of them was right, every one was reproduced on the shape they described, and this release is the answers. The thread running through it: when vexp does not know something — a file it skipped, a caller it cannot see, a server that never started — it has to say so where you are looking, not in a log you would never open.

A file skipped for size is visible everywhere, at any threshold. Files over max_file_size_kb (512 KB by default) are left out of the index on purpose: a two-megabyte source file indexed in full degrades the answers for everything around it. The skip itself was fine; its invisibility was not. On the monorepo that reported it, five hand-written Dart files — a fifth of the bytes of the main source directory — were missing from every impact, search and pipeline answer while vexp doctor, the index_status tool and coverage.json all reported a healthy index. The only trace was a line in the daemon log. Every skip is now recorded in .vexp/coverage.json with the cap in force, the counts and the list, biggest first; doctor reports it as a warning naming the files; index_status returns a coverage block; vexp index prints a summary; and every run_pipeline answer discloses it in its header, so an agent knows the blast radius it is reading was computed without those files. Raising the cap still moves the cliff. The cliff is announced wherever it stands.

vexp search finds the callers, not only the definition. The help text promised rename sweeps and zero-reference audits. What the exhaustive pass actually covered was the symbols a query names — declarations, signatures, docstrings — so a class used from eleven files came back as its own file plus a doc that mentioned it. The docs described the right behavior; the command did the other thing, and it failed in the dangerous direction, where an under-count reads as "safe to delete". search now also returns every line inside an indexed symbol body that references the identifier, reported once, at the innermost symbol it sits in, with identifier boundaries so RoutingFeedbackService does not match RoutingFeedbackServiceX. Text output marks them ref, --json carries kind: symbol or ref with the enclosing symbol, --files-only unions both. One limit, printed with every result because it matters before a deletion: references are matched inside indexed symbol bodies, so an import line or a file-level statement outside any symbol is not covered — grep for those.

The measurement court counts a turn once. vexp savings compares sessions that received orientation with sessions held out as a control, and a customer noticed its "average" over a fixed two sessions climbing on every reading — 8,699 turns, then 8,765, then 8,948. Two things were wrong. A Claude Code transcript writes one line per content block of a single response, each carrying the same usage, and every line was counted as a turn: on a real 114 MB transcript that was 21,990 lines for 11,144 messages, and twice the output tokens actually spent. And a session still running keeps accumulating, so any average that includes it moves between readings. Usage is now counted once per message, and a session whose transcript was written in the last thirty minutes is shown as in progress and left out of the averages until it settles. The court had never rendered a verdict on that install — it needs five sessions a side — so nothing acted on was affected; the numbers it will judge from are these.

The CLI and the MCP tools accept the same symbol. Ask the MCP tool get_impact_graph for file.dart::hitsFor and it resolves the method inside its class; ask vexp impact the same string and it answered "Node not found". The daemon has always had a resolver that accepts <file>::<member>, <file>::<Class>::<member> and a bare unique name; the CLI went straight to an exact lookup. Both front-ends use the same resolver now, the CLI prints what it resolved to on stderr so piped output stays clean, and a genuine miss says which forms are accepted and how to find the exact name with vexp search. Along the way: vexp impact --cross-repo, which the vexp command passed through and the engine rejected as an unexpected argument, works, answered by the daemon that holds every repository of the workspace.

Cursor rules live in the folder Cursor reads. Cursor keeps project rules as a folder of .mdc files at .cursor/rules. vexp named that path as a single file. On a project that already had the folder, vexp setup tried to read a directory and stopped with EISDIR — after indexing and the MCP server had already come up. On a project that did not, vexp created a file called rules that Cursor never read and that stood in the way of Cursor creating its own folder. The rule is now .cursor/rules/vexp.mdc with alwaysApply set, written next to your own rules and leaving them untouched; the stale file is removed when it holds only our section, and a folder met where a file was expected gets a file inside instead of a crash.

Copilot's vexp server starts from the Dock. A GitHub Copilot user set up the vexp agent, indexing succeeded, and VS Code listed no vexp server and Chat offered no vexp tools. The entry vexp wrote into .vscode/mcp.json started the server with a plain node, resolved through the PATH of the VS Code process — and a VS Code launched from the Dock or the Start menu carries the login PATH, which on most developer machines has no nvm, Volta or Homebrew node on it. The server died with spawn node ENOENT, and nothing on our side said a word. vexp setup now writes the absolute path of the node that runs it, for every editor it configures, and repairs entries written the old way; vexp doctor checks .vscode/mcp.json, names this exact failure when it sees it, and tells you where VS Code shows the server and its log — and that MCP tools exist only in Chat Agent mode, which is the other half of most "no tools" reports.

Your licence keeps its thirty-day window, and a renewal is recognised. Two changes on vexp.dev, live with the site. The long licence token on disk has a thirty-day validity that is a refresh window, not your expiry; it was re-issued only when your plan changed, so for a stable subscription it lapsed a month after activation and the licence lived on the rolling seven-day token alone — seven days offline and a paid plan degraded to free. It is now rolled forward on every refresh, as it already was for lifetime licences, so a full thirty-day offline window is always available and the date agents read as "licence expiry" is never imminent. And when the billing period the token knows about ends, vexp.dev now checks whether the same subscription simply renewed — a monthly or annual rollover — before treating it as ended, and hands back tokens carrying the new period. It used to look only for a brand-new subscription.

The orientation seed ends exploration; it does not start edits. Reading 486 paired sessions on real repository tasks showed the one way an oriented agent loses: it edits wider than the unoriented one on exactly the tasks it fails — 5.4 files against 4.1, identical when it wins — because a seed that lists neighbours and their coupling reads as a to-do list, and a wide change breaks tests the narrow fix would have left alone. The co-edited hint leaves the seed, and its closing line gains the counterweight: the fix is usually smaller than the context; nearby code is orientation, not a checklist.

Smaller fixes that shipped along the way. vexp daemons and vexp stop no longer start a daemon for the directory you run them from, which made a stopped daemon reappear the moment you listed them. vexp license explains that the MCP tool list shows four tools by default to keep the catalog small, that every tool stays callable, and that VEXP_ALL_TOOLS=1 lists them all. And the CLI test suite no longer writes into the developer's own home directory — the pid record of the login supervisor, its log and the Codex configuration were all being edited by tests that believed they were sandboxed.

v3.0.1August 27, 2026

The newest license token wins.

Your licence renews itself through a rolling token that the CLI, the editor extension and the daemon all share. Two of them kept private copies, and on one path the older copy could win. Found on day one of 3.0.0, on our own machine, fixed the same morning.

A stale cached token can no longer shadow a renewed one. The CLI refreshes the rolling token on disk whenever it runs; the editor extension keeps its own copy and, on activation, synced it back to disk after checking only that the signature was genuine. A genuine token can still be an old one: reopen the editor after the CLI had renewed, and the extension could overwrite the fresh token with its stale copy — at which point the daemon, reading the older pair, dropped a paid seat to the free tier and said so with a workspace-cap popup. Both sync directions now compare expiry and keep whichever token is newer, an expired copy can never overwrite a live one, and the extension reads the disk token directly when its own has lapsed. Four regression tests pin the rule.

The licence check gets the time it actually needs. The online validation that renews the rolling token allowed itself three seconds, silently gave up past that, and tried again much later. A cold serverless start behind a slow DNS resolver — the everyday reality of WSL — takes longer than that, so on some machines the renewal quietly never landed and the fallback chain did the rest. The budget is now ten seconds on every surface, the call remains fire-and-forget so nothing ever waits on it, and a failed attempt now leaves a line in the extension log instead of vanishing.

v3.0.0August 27, 2026

Tuned on the whole session.

Every release so far made single calls cheaper. This one was tuned on whole sessions: the same agent, the same real-world repository tasks, run pair-by-pair with and without vexp, scored on tasks the tuning never saw. It is a major version because the defaults changed to exactly the configuration that measured best, and nothing else earned its place.

Orientation now carries the evidence, not a reading list. When a session starts, vexp hands the agent a compact orientation: the files a task touches, with line ranges and the code itself. That orientation was capped at two blocks of code, a limit calibrated for retrieval precision rather than for what a session does with it — and transcripts showed the agent going on to edit six to eleven files after being handed three names. The cap is now six blocks inside a budget sized to what agents can actually accept: injected context above ten thousand characters gets spilled to a file and replaced with a pointer, which is an instruction to go read — the exact turn orientation exists to remove. Starting informed is what pays: the exploration turns the agent no longer needs are the most expensive turns a session has, because every turn re-sends everything before it.

Tuned on sessions, checked on tasks the tuning never saw. Every default in this release was chosen on paired sessions, not projected from single calls: real repository tasks from SWE-bench Pro, the same agent and the same model on the same day, with and without vexp, and the final check ran on tasks none of the tuning had seen, some from repositories vexp development never touched. The configuration that measured best on that check is the one that ships.

Quiet by default: vexp only subtracts. A year of measurement taught one arithmetic lesson: in an agentic session, one extra turn costs more than almost any amount of clever context can save, because the whole conversation is re-sent every time. So everything vexp does that could add a turn — the end-of-session completion gate, the edit-time coupling hints, the read substitution, the shell output cap — is now opt-in, together, behind vexp setup --interventions. The default is only what subtracts: orientation at turn zero, a trimmed tool catalog, and shorter instruction files for all fourteen agents — on clients that keep tool schemas resident, those tokens come back to you on every single turn. The completion check itself is not going anywhere: verify_done answers on every surface and vexp verify runs headless, exactly as before. What changed is that vexp no longer spends your turns unasked.

A repository slug is not a file. Ask about a checkout of org/project and the words org/project look, to a pattern match, like a path — and if the repository happens to contain a same-named directory, the orientation would pad itself with whatever lives there. On one real project a question about entropy collection was answered with three classes about alarm scheduling, purely because the package path contained the project’s own name. A name now has to look like an actual file — a real extension on a real stem — before vexp will treat it as one. The freed space goes to evidence that earns it: on that same project, the file that actually mattered moved up from a bare pointer to a full code block.

Smaller fixes that shipped along the way. Windsurf rules are now written where Cascade reads them and marked always-on, so the orientation is in the system prompt on every message instead of waiting to be noticed. Kilo’s per-prompt hint no longer sends a malformed message part. The licence popup names the plan you actually bought. And a daemon shut down by a sandbox’s job object now says so before it happens, instead of disappearing without a word.

v2.7.0August 22, 2026

The way people actually ask.

Most of the time nobody types a tidy sentence into their agent. They paste the bug report: a title, then a stack trace, a table of versions, the steps to reproduce, and a paragraph of template. vexp was reading all of that as one question, giving the same weight to the sentence that says what broke and to the line that says which version of Node someone is running. This release makes the title the question and the rest the evidence, so the files vexp names are the ones the report is actually about.

A pasted issue is answered from its headline. A GitHub title is the symptom compressed into one dense line, written by the person who hit it. Everything under it is corroboration. Asked with the whole report, vexp scored 0.194 against the human-annotated answers; asked with the first line alone, 0.259 — the same index, the same repositories, a third more of the right files, purely from the shape of the question. Retrieval now keys on the headline when a prompt has one, and on the whole prompt when it does not, so a one-line task description behaves exactly as before. Across the full set: 0.190 to 0.273, with recall from 0.281 to 0.380 and TypeScript doubling from 0.101 to 0.205. What the agent receives at the start of a turn improved further, from 0.009 to 0.228, and the instances where vexp had nothing at all to say went from three in forty to none.

A path mentioned in passing no longer decides the whole answer. Asked to explain a file by name, the right thing to do is open that file and stop. vexp did exactly that, and it was correct for years of questions phrased that way. A pasted bug report is the other kind: it quotes a path in a traceback, in a snippet, in a reproduction step, almost always, and none of those are what you are asking about. Files you name are now strong candidates ranked first rather than the entire answer, so the ranking, the filters and the blast radius all still run. The visible symptom was an answer that came back empty when the mentioned file turned out to be a test.

A changelog no longer answers questions about your code. A release-notes file matches a bug report better than any source file can, and not by accident: it contains the note for that exact fix, written in the reporter language. Asked about a timezone plugin bug, vexp returned CHANGELOG.md and not the plugin. Changelogs, release notes and history files are now treated as what they are, a record of changes rather than the change. Two words with a real name in them are also enough to work with now: Chroma.update_document bug names the code exactly, while fix #1241 names nothing, and counting words could not tell those apart.

The completion check is on every surface. vexp ships two MCP servers, one built into the binary and one bundled for Node, and verify_done — the mechanical check that lists broken imports, untouched dependents and the tests your change touches — was listed by the first and missing from the second, while the instructions vexp writes into your project told the agent to call it. It is now implemented and listed on both, and a test reads both lists so they cannot drift apart again. In the same pass, output that pointed the agent at a tool it could not see now points at one it can: memories are recovered through run_pipeline, which every surface has.

A graphics card that cannot run the model can keep the GPU. The protection added in 2.6.3 cleared its record once the model had loaded and a one-token warm-up had run. A user on an RTX 2060 SUPER showed why that is not enough: a warm-up does not exercise the same kernels a real prompt does, so his card passed the handshake and then failed inside the graphics library on the first actual question, over and over. The record now survives until a real inference completes, and it names the phase it failed in, because a card that cannot load a model and a card that cannot run one kernel need different answers. For the second case there is now a setting — flash_attention = "off" — that keeps the whole model on the GPU instead of dropping the machine to the processor. A crash while loading still disables the GPU on the first occurrence; a crash while running asks for a second, so stopping a daemon by hand is never mistaken for a broken card.

Diagnostics that describe what is actually happening. A daemon that is still syncing a large repository now says indexing rather than healthy, and explains that the local model is wired once that sync finishes, instead of advising a restart that begins the sync again. And an overriding core.hooksPath — the normal state of affairs under moon, husky or lefthook — is a note rather than a failure: our git hooks do not run there, but the index still refreshes, because the daemon watches the tree live and reconciles it against disk every five minutes. The old wording claimed the index stopped refreshing and pointed at a file those tools regenerate, which was both untrue and impossible to act on.

v2.6.3August 19, 2026

The local model stops fighting your GPU.

Nine fixes, every one of them from a user who wrote in, and the three that matter are all the same feature: the optional local model could refuse to install, delete a finished download, or take the whole daemon down on every single start. None of them looked like a vexp problem from the outside, which is exactly why they were worth chasing.

A GPU vexp cannot use no longer takes the daemon with it. A user on Windows sent five seconds of log, repeated five times: the graphics driver on his laptop is older than the toolchain our GPU code was compiled against, so the first operation failed inside the graphics library and killed the process outright. Our supervisor restarted it, it chose the same GPU, and it died again, until the attempts ran out and he was left with an error in the corner of his editor and no idea why. The safety net we had could not help, because it catches a GPU that reports an error and this one never got that far. vexp now records that a GPU attempt is under way and clears the record once the model is loaded and running. Finding that record still there at the next start means the previous attempt did not survive, so this one runs on the processor and says so. It is remembered per machine, not per project, because an incompatible driver is a property of the machine, and any successful start clears it, so updating your driver brings the GPU back on its own with nothing to undo.

Using the processor only now means the processor only. Three separate paths asked for the model to run without the GPU: the setting in your config, the rule that protects Intel Macs, and the automatic fallback after a GPU failure. All three arrived at one line of code that left the layer count at its default, and that default means every layer, on the GPU. Every documented way of avoiding the GPU still handed it the entire model. Verified on a four gigabyte laptop card, before and after: nought of thirty-six layers offloaded when the processor is asked for, thirty-six of thirty-six when it is not.

A finished download is no longer deleted as incomplete. The expected size of the model was written down in megabytes and compared against bytes counted in mebibytes, two units that differ by about seven percent. A complete download therefore looked short of a number that was never right, was removed as corrupt, and started again from zero, forever, on a file of several gigabytes. The check now compares against the length the download server itself declared, so the only thing that can fail it is a download that really is short.

Your licence says what you bought. Two reports, one week, both about the same screen telling people something untrue. Activating a licence stored the key but left the cached status alone, so the device you had just activated showed as zero devices until a refresh hours later, which reads as an activation that did not work. And a lifetime licence displayed a renewal date a month out, because the date shown was an internal token roll rather than the entitlement behind it. Activation now registers the machine immediately, and a lifetime licence is described as one everywhere it appears.

Diagnostics that check the thing, not the paperwork. vexp doctor read the Codex configuration and stopped there, so a per-prompt orientation hook that pointed at a binary no longer on disk passed every check while doing nothing on every prompt, silently, forever. Doctor now runs the hook and names the path it cannot find. An Intel Mac is no longer told its accelerator is the GPU, which has been correctly unused there since 2.6.1 while the line reporting it stayed wrong. The extension stops warning about optional files that are absent by design, and the savings view has the command it was already advertising.

v2.6.2August 14, 2026

The answer keeps room for your code.

One fix, for repositories where documentation outnumbers code, plus the publishing work that gets every platform to the same version on both marketplaces.

Documentation cannot crowd your code out of the answer. On a repository that is mostly documentation, and many are, every candidate the search returned could be a doc section, so a question about the code came back empty. Measured on a public project where documentation is 81 percent of the index: a question its own code answers returned nothing at all, while naming the exact symbol worked. Code is now guaranteed a place in the candidates and reserved slots in the answer. On a repository without documentation nothing changes at all. This one arrived an hour after 2.6.1 was cut, so it ships here.

Every platform, both marketplaces, same version. The VS Code gallery accepts a version several minutes before it serves it, and our release check gave up first, reporting a publish that had worked as a failure. It now waits long enough. Open VSX has a second state entirely, a version it has accepted but not activated, which is invisible to install and yet blocks republishing; the release now recognises that state by name instead of counting it as already done.

v2.6.1August 14, 2026

Everything that was quietly missing.

A maintenance release built entirely out of user reports, and every one of them was the same shape: something looked installed, looked answered, looked indexed, and was not. A crash on Intel Macs where the documented CPU fallback could never run. An index that dropped files while reporting success. An answer that pointed at your documentation and carried none of it. Half the hooks on an extension-only install. Nothing here is a new capability. All of it is the difference between what vexp reported and what vexp did.

Documentation pivots arrive with the documentation in them. A user compared vexp against plain file reading on his Blazor project and published the result. vexp ranked the right two files, his architecture notes and his roadmap, and returned them as a score and a line range with no text at all, so his agent had to read them anyway and the call was pure overhead. His verdict was correct. Three causes were stacked: documentation is indexed by heading rather than by prose, both renderers printed that empty body, and markdown has a grammar, so once the text was restored it went through the code compressor, which turns declarations into opcodes and a paragraph into silence. A pivot with no stored body now reads its own lines, bounded, and prose skips the code path entirely. Answers about your documentation now contain your documentation.

Your index no longer loses files in silence. On a busy or small machine a database error during indexing was logged and skipped, so files vanished from the index while the run reported success. Nothing said so, and a search that found nothing looked exactly like code that did not exist. The transient is retried where it happens, and a file that still fails is now reported by name as missing from the index. Two clean runs of the whole test suite on two cores, the shape that reproduced it every time before.

Intel Macs: no more daemon crash on start. On a MacBook Pro with a Radeon Pro the daemon died on every start, and the CPU fallback in our documentation never ran. It could not: the bundled Metal shaders target Apple Silicon, so a missing kernel takes the process down inside the graphics layer, where there is no error left for us to catch. Intel Macs now run the local model on CPU without asking for the GPU at all, which is exactly where the fallback would have landed, and the daemon stays up. Apple Silicon is unchanged.

The VS Code extension installs the verification gate too. A beginner wrote asking what he was supposed to do with vexp after installing the extension. Checking his setup to answer him found the real problem: the extension wrote the orientation and context hooks but never the completion check, so verify_done only ever reached people who also ran vexp setup in a terminal. Anyone who installed the extension and stopped there, which is what an extension invites you to do, had half the product and no way to know. Both halves now install together.

Blazor markup bindings count as callers. A handler wired to a button through markup alone, Click="LoadDirectory" and its relatives, reported zero dependents while being the only thing that button does. Worse, a handler called once from code and once from markup reported one caller out of two, and a plausible wrong number is harder to catch than an obvious zero. Markup outside the code block is now scanned and the component becomes a real caller of the handler it names.

Connecting an agent is not permission to index. A user found a private repository indexed twice without being asked, simply because a session started in that directory. An agent launches vexp wherever it happens to be, so a connection can never mean index this. The presence of a .vexp directory is the consent record now: without it the server refuses, names the directory, and says which command to run if you do want it served. The refusal leaves the folder exactly as it found it.

Smaller truths: hooks, status, Windows, dry runs. Git hooks written where git will never run them, because a custom core.hooksPath was configured, now say so instead of reporting success. Daemon status no longer claims running when the process holding that pid is not the one serving. A dry run stops announcing that the daemon is up when it started nothing. A terminal-only setup no longer tells you to restart an editor it never touched. And the Windows daemon probe, which referenced a helper that does not exist and could only fail on Windows, is gone.

v2.6.0August 12, 2026

Verification that points at the proof: run exactly these tests.

Every mechanism in this release was validated on a 300-rollout benchmark campaign against the official SWE-bench Pro harness before shipping. The lesson the data taught us: agents do not fail for lack of verification, they fail because verification keeps confirming structure while the proof lives in tests nobody names. So vexp now names them. Around that headline: four new file types in the index, an index that admits what it could not read, and a setup you can audit before it writes a single file. And for teams shipping in containers, the engine finally travels everywhere: a static build that starts on any Linux, Alpine included.

Impacted tests: the run-or-update mandate. When you finish a change, verify_done now lists the exact test files mechanically tied to what you touched: tests that reference your changed symbols through the dependency graph, plus suites the graph cannot see (anonymous jest/vitest files) caught by an import scan. Shared test infrastructure gets its own instruction: a test-utils helper tied to your change says UPDATE me, never run me. At the stop gate this becomes one precise instruction per session: run these files, fix what is red, then finish. Opt in to run mode and the gate executes them itself with your repo's own runner (jest, vitest, go test, pytest, cargo, gradle, maven, dotnet, rspec, phpunit) and blocks only on an actual failure, with the failing output as evidence. In the benchmark campaign this converted misses that a premium frontier model left on the table.

No more verify loops. Field data showed agents calling verification up to 16 times on an unchanged working tree, burning turns to re-confirm the same verdict. Now a repeat call on an identical tree returns instantly with the cached verdict and a clear instruction: nothing changed, the mandate stands, go run the tests. One call carries the signal; the loop is gone.

The layer survives compaction, and knows when to be quiet. Two sides of the same discipline. When your agent's context window is compacted or resumed, a lifecycle hook re-injects one paragraph so the fresh window still knows the daemon, the tools and the verification contract exist. And once a session is warm, orientation hints stop: after three served orientations vexp goes silent for that session, because by then the agent knows the repo and every extra injection is pure cost. Measured on long sessions, this is the difference between a layer that helps and a layer that lingers.

Stylesheets, Blazor components, WordPress wiring. Four file types that carried real structure and were simply invisible before. CSS, SCSS and LESS are parsed into rules, mixins and variables, so a class name in a template now leads somewhere. Razor and Blazor components (.razor, .cshtml) are parsed through their C# code blocks, which is what makes a Blazor caller of a C# service visible to impact analysis at all. And WordPress plugins finally wire up: add_action and add_filter registrations resolve to the handler they name, including the object forms ($this, method) and Class::method that every real plugin uses. Thirty-six languages now, with stylesheets alongside them.

The index tells you what it could not read. A user asked why a C# service reported zero dependents when a Blazor component clearly called it. The honest answer was that we had never parsed the caller's file type, and the honest answer was nowhere in the output. A confident zero is worse than no answer. So every orientation now carries a coverage line when it applies: these file types exist in your repository, vexp cannot parse them yet, so callers living in them are invisible to impact analysis, and an empty result means none found in indexed files rather than none exist. It names the extensions and the counts. You can trust the zero or go look, but you are no longer guessing which one you are reading.

A setup you can audit, and diagnostics that stop guessing. A measured third-party evaluation spent two days on a hook that setup had quietly failed to install: everything reported healthy, nothing was wired. That report is now a checklist we shipped. vexp setup --dry-run lists every file it would write, agent by agent, and writes nothing. A config it refuses to touch, or an agent whose target is not on the machine yet, gets a visible block at the end instead of a line lost inside a spinner. vexp doctor runs the orientation, verification and restore hooks the way your agent would, and reports what actually happened. When orientation stays silent, it now says which rule decided: this prompt already names known code, the session is already oriented, or this prompt is in the measurement control group. And the local LLM says why it fell back to CPU rather than leaving you to guess at a GPU that is right there.

Cline support, static Linux build, supervisor fix. Cline joins the supported agents: Setup Agents writes the rules file and registers the MCP server in every VS Code variant where Cline is installed, full tool surface included. The release train now ships a fully static Linux binary, smoke-tested on Alpine in CI, for containers, sandboxes and distroless images where dynamically linked binaries refuse to start. And an audit-grade field report on macOS led us straight to a supervisor defect: the shared MCP child was being replaced every 60 seconds because a version field was dropped on read. Fixed, with the reporter's exact regression cases in the suite.

v2.5.3August 7, 2026

Docs drift: your documentation can no longer quietly lie about your code.

Third release in four days shaped directly by field reports. The headline closes a loop users were running by hand: when a session removes or renames a symbol, verification now tells you which markdown files still describe the old shape, with file and line. Around it: a global view of which workspaces hold your concurrency slots, hardening that makes retry storms structurally impossible to amplify, and a set of fixes for containers, sandboxes and Intel Macs. Everything runs on your machine, as always.

Docs drift detection, at verify time. Rename a function, and somewhere a guide, a session note or an onboarding doc still describes the old shape. Hunting those down was a manual sweep; one power user had even assigned that exact job to vexp in his own review tooling. Now verification does it: when a session removes or renames a symbol, verify_done reports every markdown location still referencing it, each with file and line and the source file it vanished from. The design is deliberately anchored to the change, never to the corpus: we measured a corpus-wide "looks stale" scan at 61 percent noise on a real repository, which is how an oracle gets muted and forgotten. Anchored to what the session actually changed, findings are near-certain. Advisory by contract: it never blocks a stop and never fails a build. Release-notes files are recognized as records, not drift.

vexp daemons: see who holds your workspace slots. The concurrent-workspace ceiling made slots a resource, and one field report showed the failure mode: a notes vault opened once in an editor quietly held a slot for a day. vexp daemons lists every daemon on the machine with a live probe: which workspace, which engine version, index size, uptime, live entries first, stale entries labeled and never counted against you. The plan ceiling sits in the header and the release command in the footer. It also makes mixed-version daemons visible at a glance after an upgrade, which used to surface only as confusing tool errors. --json for scripting.

Retry storms can no longer amplify. An agent client that retries aggressively could fire the same request many times in under a second; each copy queued pipeline work and, with the local LLM enabled, stacked GPU jobs behind a busy device. One field report traced a machine-level crash to exactly that pile-up. Three defenses now ship together: identical concurrent requests are coalesced into one computation (strictly in-flight, nothing cached); the interactive LLM paths wait briefly and then fall open to the deterministic path instead of queueing behind a busy GPU; and a full inference queue refuses new work immediately rather than stalling callers that have already given up. A burst of eight retries now costs one computation.

Containers, sandboxes and Intel Macs. License readers in the CLI and the extension now resolve VEXP_HOME exactly like the engine does, so sandboxed and containerized setups read and refresh license tokens in the directory the daemon actually watches. On Intel Macs, the Metal shader now ships precompiled next to the binary, removing a compile of roughly 26 seconds that previously ran on every process start. And the LLM status probe now reports the Metal backend truthfully on healthy installs.

v2.5.2August 6, 2026

vexp search: the whole index, code and docs, nothing left out.

A power user showed us the sqlite query he was running by hand against vexp’s index for rename sweeps and zero-reference audits, exhaustive questions where a ranked top-K is the wrong shape by construction. He was right, so we built the official version. vexp search returns every matching node in your index, markdown included, with a stable output you can script against. Alongside it, the second half of last release’s fixes: a start blocked by the workspace ceiling now leaves the workspace completely untouched, and VS Code finally tells you why instead of retrying into a wall. Everything runs on your machine, as always.

vexp search: exhaustive by design. Orientation tools rank and cap results on purpose: an agent needs the right five files, not five hundred. But rename sweeps, dead-reference audits and "is this term still mentioned anywhere" are the opposite shape: they need every match or the answer is worthless. vexp search walks the full-text index of everything vexp knows, code in 34 languages and markdown alike, and prints every hit in file order. --files-only gives you the distinct file list, --json gives you a stable machine-readable shape, --substring matches partial identifiers the way a raw LIKE would, and --limit exists only if you ask for it. It reads the index directly, so it works with or without a running daemon. If you have been querying .vexp/index.db by hand: this is the supported version, and it will not break under you on the next schema change.

Docs are first-class citizens of the graph. The user behind this feature keeps 18,000 of his 22,000 indexed nodes in markdown: every time a rule migrates from docs into code, some document somewhere still describes the old shape. One conceptual query surfaces the right document; one vexp search confirms nothing else mentions the old name. Keeping documentation and code in agreement is a problem grep cannot solve by construction, and it turns out a code graph that indexes markdown can. We are exploring how far to push this; if docs drift is a pain you recognize, we want to hear your version of it.

A blocked start now leaves no fingerprints. In 2.5.1 a start refused by the workspace ceiling stopped flickering the socket; in 2.5.2 it stops touching the workspace entirely. The ceiling check now runs before the git hooks install and before the index database is created, so a refused workspace is left exactly as it was, with only a log line and the machine-readable refusal reason in .vexp/start-blocked.

VS Code stops retrying into a wall. The extension now reads the refusal reason the daemon writes and shows it where you are looking: the status bar reports "workspace cap" with the full message, which workspaces hold the slots and the command to free one, and clicking goes to the logs instead of the license panel. The respawn loop fails fast when the ceiling is the cause, because a plan boundary does not clear by retrying, and the notification offers an explicit Retry for when you have freed a slot.

v2.5.1August 5, 2026

The same-day release: field reports on 2.5.0, fixed in hours.

Users started measuring 2.5.0 the moment it landed, and within hours we had reports precise enough to act on immediately. This release is the result, shipped the same day. The headline: AppSumo Tier 3 and 4 now carry the full Team workspace ceiling they were always entitled to. Around it, a set of fixes that make hitting a limit feel like information instead of a failure: refusals that explain themselves, error messages that identify their source, and every command our own messages mention now actually exists. Everything runs on your machine, as always.

AppSumo Tier 3 and 4: full Team workspace ceiling. The AppSumo deal maps Tiers 3 and 4 to the Team plan, and the node and repo limits always resolved that way, but the concurrent-workspace ceiling shipped at the Pro value of 4. A Tier 3 developer running parallel Claude Code sessions across git worktrees hit it mid-session and told us within hours. Tiers 3 and 4 now allow 8 live workspaces, the same as Team. If you run agents in parallel worktrees, this is your fix.

A blocked start now says why, before anything flickers. In 2.5.0, starting a daemon past the workspace ceiling briefly brought the socket up, reported success, and then died: indistinguishable from a crash. The ceiling check now runs before the socket ever binds. vexp daemon-cmd start reports the actual reason with the list of live workspaces, and the new vexp stop <workspace> command, the one the message suggests, stops any of them from wherever you are standing. No more cd-ing into each workspace to free a slot.

vexp verify, now truly on both surfaces. The 2.5.0 notes promised verification as an MCP tool and a CLI command. The MCP tool shipped; the CLI entry point did not. vexp verify now works from the terminal with --json for machine-readable output, --task-file for instruction-level oracles, and --gate for CI pipelines that want a hard exit code on mechanical violations.

Errors that identify themselves. When a request lands on a daemon older than the tool it names (a mixed-version moment right after an upgrade, or a fallback to another workspace’s daemon), the answer used to be a bare "Unknown tool", which reads as "unimplemented" and sends you debugging the wrong thing. The daemon now states its version, the workspace it serves, and the one command that fixes it. On the SDK line, the gateway gained the same spirit: if a query routes to a shard that just died, it degrades to the surviving shards and returns partial results clearly flagged as such, instead of failing the call.

v2.5.0August 5, 2026

The verification release: vexp now checks the work, not just the context.

Orientation tells your agent where to start. From 2.5.0, vexp also tells it whether it actually finished. A new mechanical verification layer reads the session’s real changes and reports what is provably broken or missing, with file and line: parse errors, imports of names that no longer exist, dependents never touched, promised files never written. No model grades anything. Alongside it ships Shield, a free scanner that shows what your comments and string literals would hand to any AI agent that reads your code. Plus honest plan ceilings, full SDK parity, and a doctor command in the VS Code palette. Everything runs on your machine, as always.

Horizon verification: verify_done, everywhere and free. A new MCP tool and CLI command (vexp verify) that checks a session’s work against the code graph: files that no longer parse, imports broken by a rename, dependents of changed files that were never updated, and constraints stated in the task itself (files it forbade touching, artifacts it promised). Findings always carry file:line evidence you can check in seconds. It is free on every plan, because gating “did you actually finish” behind a paywall would be the wrong kind of business model. Claude Code sessions also get a stop-time check that challenges provably incomplete work once, with the evidence, and never blocks a clean stop.

Shield: see what your code hands to any AI agent. vexp shield scan (CLI, VS Code palette, SDK route) walks your workspace and reports the PII and secrets sitting in comments and string literals: emails, phone numbers, IBANs with real checksum validation, credit cards past a Luhn check, AWS and GitHub tokens, JWTs, private keys, credentialed URLs, high-entropy literals. Detection is deterministic, runs entirely on your machine, and the report only ever shows masked previews. Reserved documentation values (example.com, 555 numbers, test PANs, RFC1918 addresses) are deliberately not findings: a scanner you cannot trust to stay quiet is a scanner you turn off.

Honest ceilings on paid plans. Pro and Team now carry explicit daily-call and concurrent-workspace ceilings (1,000 and 1,500 calls per day, 4 and 8 live workspaces) sized so that interactive use never meets them: the heaviest measured day stays under half the Pro ceiling, and ambient hints never count. They exist as a boundary: automated, headless or service use belongs on the SDK line, and the Terms now say exactly that. Hitting a ceiling on a paid plan refuses the call with a clear message and leaves the daemon and everything else running.

SDK parity and a doctor in the palette. The SDK line (REST) gains verify_done and shield_scan with per-repo union merging across a fleet, so a clean repo can never mask another’s broken imports. VS Code gains two palette commands: vexp: Doctor (version skew, daemon health, license, hooks wiring) and vexp: Shield Scan. The engine also hardened its certification pipeline: pinned language-server toolchains, because a floating latest is how green turns red with no code change.

v2.4.0July 31, 2026

The ambient release: vexp now works at every prompt.

Until today, vexp helped when your agent asked it to. From 2.4.0 it also works ambiently: every prompt you type is classified against your local code graph, and vexp decides in milliseconds whether to stay perfectly silent or to hand your agent a one-line orientation before it starts reading. Silence when you already know where you are going, guidance the moment you do not. It ships with a new Savings Ledger that shows you every one of those decisions, instant in-place upgrades, and per-prompt support across Claude Code, Codex, VS Code Copilot, opencode and Kilo Code. Everything runs on your machine, as always.

Ambient orientation, on every prompt. A new classification engine in the daemon reads each prompt and checks it against the symbols and files of your indexed workspace. Name the code you want to touch and vexp adds nothing at all, zero tokens, zero noise. Describe a problem in an unfamiliar area and your agent receives a single orientation line before its first file read. The mechanism is fail-open by construction: if the daemon is off, the answer is silence, never an error in your session.

One brain, five agents. The same per-prompt intelligence now reaches Claude Code, OpenAI Codex (project-scoped, nothing global touched), VS Code Copilot, opencode and Kilo Code, each through its native extension point. Agents without a prompt channel keep the streamlined mandate and the full MCP toolset. One daemon, one behavior, conjugated per harness.

The Savings Ledger. A new surface in the CLI (vexp savings), the sidebar and index_status shows what vexp actually did for you: prompts analyzed, deliberate silences, orientations served, and, when your agent calls the tools, measured per-call token savings computed against a full-read baseline. Decisions are visible in real time, and the numbers are the same on every surface.

Upgrades that take effect instantly. The VS Code extension now recognizes a running daemon from an earlier version and replaces it automatically the moment you reload, so new capabilities are live the instant you update. CLI users get the same awareness in vexp doctor, down to the exact command to run.

Multi-repo, industrial grade. Workspaces with connected repositories get a stronger backbone: secondary repos come online one by one as each becomes ready, re-attach themselves automatically if anything is missing, and cross-repo edges are now durably persisted across restarts. Nested repositories are recognized as first-class boundaries by the watcher, the walker and reconciliation alike.

Configuration that listens, live. exclude_patterns joins the indexing engine across full walks, watcher events and reconciliation, and the daemon now picks up changes to your config and ignore files while it runs, no restart needed. Settings written under the legacy [index] table are honored too, and unknown keys produce an explicit warning naming every accepted option.

Windows, first class. On Windows with an NVIDIA GPU, the local LLM now engages CUDA acceleration out of the box: the runtime libraries load automatically from the plugin directory, with no environment variables and no editor restart. The guard uses one cross-platform invocation shape on every OS, and vexp doctor exercises it live on Windows just like everywhere else.

Sharper impact analysis. Blast-radius results are now built exclusively from corroborated call edges, so every listed caller genuinely references your symbol. The dependents count is one clearly labeled number, pivot headers carry exact line ranges for surgical follow-up reads, and the default MCP surface is leaner, with the reference expander appearing dynamically the moment compact output needs it.

v2.3.1July 25, 2026

You asked for a file. Now you get that file.

A precision release, shaped by detailed feedback from users running vexp on large real-world projects. Eleven refinements across retrieval, indexing and diagnostics, with one theme: when you name something, vexp brings you exactly that, in every one of the 34 indexed languages.

Name a file in your task, get that file back. Queries that mention exact file paths, like "review admin/protected-apps.php", resolve straight to those files as top results in every one of the 34 indexed languages. Path detection covers everything vexp indexes, understands Windows-style backslash paths, and shrugs off trailing punctuation.

Searches understand paths and dotted names. Query tokens are now split the same way the index itself splits text, so multi-word questions that mention paths like includes/dashboard-sections.php, dotted symbols like AuthService.validateToken, or hyphenated names match the code they refer to with full precision. Snake_case identifiers stay intact, exactly as before.

Minified bundles stay out of your context. Files with minified naming patterns, like a vendored editor or video player committed under assets/, are now skipped automatically wherever they live, and the max_file_size_kb setting (default 512) keeps huge generated files out of the graph. Every skip is logged, so you always know what was left out and why.

Status that tells the truth. Status now probes the actual socket or named pipe for a live answer, reports a daemon started by another process as exactly that, and labels a leftover socket as stale. What you read is what is running, on every platform.

doctor proves your setup end to end. vexp doctor now executes your configured guard hook the same way the agent would and reports the live allow-or-deny verdict, turning configuration checks into real end-to-end proof. It also reads project-level Codex configurations, so per-project setups are recognized everywhere.

Guard hook, robust on every path. The Claude Code guard hook is now registered in the invocation style Claude Code recommends, with a sensible timeout, and handles project folders with spaces in their names out of the box. Re-running vexp setup --guard-strict migrates an existing installation cleanly.

Your ranking preferences apply everywhere. priority_paths and depriority_paths from .vexp/vexp.toml now influence results in the standalone vexp capsule command too, not just through the daemon. If you deprioritise docs/old, that choice follows you into every entry point. Remember that these lists load at daemon start, so restart after editing them.

v2.3.0July 23, 2026

Works with your agent, not against it.

This release came out of three weeks of measuring vexp against real agent sessions, including benchmarks run by our own users, and acting on every number. The workflow that consistently cost the least is now the default: one orientation call up front, your agent's native tools for everything else, and no more blocking anything. Every answer now says what it is based on. The index repairs itself. Multi-topic questions return every area involved, plain relative imports finally count, and C# gets a call graph at all. And where your agent reads its instructions, vexp now states plainly what was always true: your code never leaves your machine.

A default workflow that measurably costs less. vexp used to insist on being consulted for everything and blocked your agent's own search tools while its engine was running. Real-world benchmarks showed the opposite of the intent: the block did not increase good usage, and in the worst case, a long-context session hitting the deny wall over and over, it multiplied the cost of a task many times over. The new default is the workflow that won every measurement: one run_pipeline call at the start of a task for architectural orientation, native search for literal text sweeps, direct reads on files being edited. Nothing is blocked anymore; teams that explicitly want enforcement can opt back in with vexp setup --guard-strict.

Every answer says what it is based on. Every run_pipeline response now opens with a one-line coverage header: how many files and symbols the index holds and which compression engine produced the result. If the index is too small to answer reliably, the response says so in plain words and tells the agent to use its own tools until the index is ready, an entire benchmark round was once run against a near-empty index without anyone noticing, and that can no longer happen silently.

The index repairs itself. Files written while the engine was off, a scaffolding tool, a git operation in another window, a container writing into the workspace, used to be invisible forever until a manual re-index. The engine now periodically compares what is on disk with what it has indexed and quietly folds in anything that was missed, including brand-new files it never saw being created.

Multi-topic questions return every topic. Ask about storage handling and auth tokens in one query, and vexp used to hand back three results from one dominant file while the second topic vanished entirely. Result selection now guarantees file diversity and rescues the best match of each area involved, verified by a permanent test suite: every area named in the query surfaces in the results, with the most relevant file still ranked first.

Plain relative imports count now, and C# has a call graph. Two long-standing blind spots, found by a new per-language conformance harness that measures what actually resolves instead of assuming. Imports written as ./module or ../module never produced a connection in any language, only alias-style paths worked, so the graph under-reported how files relate on exactly the codebases that use the plainest syntax. And in C#, method calls were never recognised at all, leaving an entire ecosystem with structure but no call graph. Both are fixed, and the measured capability of every supported language is now published in the docs, including an honest note when a language only gets structural coverage.

See whether the local LLM is actually on. A user benchmarked "vexp with local LLM" three times, and the model was only running in one of them, with nothing anywhere to tell them. The active compression engine (rule-based or local LLM, CPU or GPU) is now visible in the status output, the sidebar, and vexp doctor, and if a model is installed but not actually serving, vexp says so instead of letting you believe otherwise.

Swift declarations, all of them. Two Swift methods sharing a name, the language's bread and butter, collapsed into one entry, taking real declarations out of the graph, and init, deinit and subscript were never indexed at all. Every declaration now persists with a distinct identity, overloaded calls link to every candidate honestly, and constructors and subscripts show up as what they are. Validated against a regression bundle contributed by the user who reported it: all 24 checks pass.

Your code never leaves your machine, now in writing, where agents read it. vexp has always run entirely locally: the engine is a process on your machine, the index lives inside your workspace, and analysis sends nothing to any external service. One thing was missing, saying so where AI agents read their instructions. Without that line, a cautious agent classified vexp as an external service and asked its user for data-disclosure consent over and over. Every generated instruction file now states the locality guarantee explicitly.

Faster and steadier under the hood. Pipeline calls no longer wait on a cold or CPU-bound model, expensive analysis steps are skipped when they cannot pay for themselves, so no more 14-second first calls. Statistics that were recomputed on every request are cached. Configuration writes are atomic, closing a race where two engine starts seconds apart could read different settings. And the background supervisor no longer resurrects a workspace's engine against the wrong socket, an obscure multi-repo failure mode that could leave a stale engine squatting where the real one should be.

v2.2.4July 21, 2026

The whole project, connected.

vexp now sees how your files actually fit together. On alias-first codebases, Next.js, Vite, anything importing through @/… paths, it had been resolving almost none of the connections between your files, which starved everything downstream: the blast radius of a change, tracing a call from one file into another, how results are ranked. That is fixed, and the graph fills in. Alongside it: index every repo in a workspace with one command, Drupal modules that finally show up, and Kiro connected the way it should have been.

vexp finally sees how your files connect. On projects that import through path aliases, the @/lib/… style Next.js and Vite use, vexp was dropping almost every link between your files: on a real app, more than nine in ten internal imports never resolved. Everything built on those links suffered with it, the blast radius of a change, following a call from one file into another, the ranking of results. vexp now follows path aliases, retries links that the indexing order had skipped, and refuses to guess a connection to a different file's same-named symbol rather than wire it wrong. On our own web app, the internal connections it found went from a couple of dozen to nearly three hundred.

Index every repo in a workspace with one command. Indexing a multi-repo workspace used to cover only the main repository and leave the rest empty, so you would set the workspace up, run the index, and see a single repo. It now indexes every connected repo in one pass, and a plain re-run reliably picks up file types that have only just become indexable.

Drupal modules are indexed now. Drupal keeps most of a module's code in files that do not end in .php, .module and .install hold its hooks, the most important code it has. vexp only recognised .php, so an entire custom module indexed down to almost nothing. It now reads all of Drupal's PHP file types, so your hooks, forms and services are in the graph where your assistant can find them.

Kiro connects to vexp. In Kiro, vexp's tools were never actually reaching the assistant: the connection was set up in a way a Mac launched from the dock could not start, so it failed quietly and the assistant fell back to plain text search, with vexp contributing nothing. Kiro is now pointed straight at vexp in a way that always starts, and its tools are pre-approved so you are not asked to confirm on every call.

Multi-repo setups are more forgiving. A workspace file that labelled itself with the older workspace_id key instead of name failed to load outright, silently taking every connected repo down with it. Both spellings are now accepted, so a config copied from an older example just works.

Type-resolved connections from the first session. The editor bridge that captures type-resolved links between symbols now reads every file you already have open the moment it starts, so those connections are present from your very first session instead of only after you save or reopen a file.

v2.2.3July 20, 2026

Connect a repo, and it is just there.

Multi-repo workspaces, the way they should have worked all along. Connect a second repository and vexp indexes it on the spot: no restart, no ceremony. The old workaround, opening the connected repo on its own to force it to index, used to spin up a second engine that fought the first over the same files until you killed everything and started over. That is gone: a connected repo now quietly uses the workspace it belongs to. Alongside it, a status view that finally shows every repo, a model download that resumes instead of restarting, a search guard for Cursor, and call edges that tell you how sure they are.

Connect a repo and it indexes right away, no restart. vexp read your workspace list only at startup, so a repository you connected to a running workspace sat there unindexed until the next restart. It now watches that list and reacts within seconds: a newly connected repo is indexed, served, and kept live as you edit, and one you remove is dropped just as quickly, with its files left untouched on disk. Connecting a repo from VS Code or the terminal no longer asks you to restart anything.

No more killing everything to open a connected repo. Opening a connected repository on its own used to start a second vexp for it, which then loaded the entire parent workspace and fought the first one over the same index files, until the only way out was to kill every process and begin again. Now a repo that belongs to a workspace simply uses that workspace: the terminal, the editor, and the background engine all point at the one already serving it, and tell you so. If that one genuinely is not running, the repo opens on its own exactly as before.

Status finally shows every repo in the workspace. Ask vexp for its status in a multi-repo workspace and it showed you one repo while quietly serving three. It now lists every connected repository with its own file, symbol, and connection counts, and flags the ones still catching up, so you can see at a glance what is indexed and what is not.

Connect and list repos from the terminal. The vexp command gained a Repos and Workspace menu: list the repositories connected to your workspace, or add another one, writing exactly the same configuration the VS Code command does, with the same plan limit checked before anything is saved. Run it from a connected repo and it operates on the workspace that repo belongs to.

The model download resumes instead of starting over. Installing the optional local model on a slow or flaky connection used to restart the multi-gigabyte download from zero every time the network dropped mid-transfer. It now picks up from where it left off, and a stalled transfer fails fast instead of hanging the install.

A search guard for Cursor. Cursor now gets the same guard Claude Code and opencode already had: while vexp is running it steers the agent to vexp instead of raw search, and steps aside the moment vexp is not, so an unindexed project still works. The copy that ships in the terminal and the copy that ships in the extension are kept identical by a test.

Call edges now tell you how sure they are. The type-resolved connections vexp captures from your editor come from sources with very different confidence, and once stored they all looked the same, so a quiet drop to a weaker, best-guess method was indistinguishable from a fully resolved one. Each edge now records how it was found, and that difference is visible in your index status.

Smaller terminal fixes. Re-indexing from the menu now covers every connected repo rather than just the current folder, the daemon menu can restart the engine, a one-shot index no longer double-counts a repo nested inside another, and a hint that pointed at a command that does not exist now points at the right one.

v2.2.2July 17, 2026

One machine, one device. And an index that stops repeating itself.

A reliability release across the whole stack. A lifetime-plan user reported the CLI showing his paid plan while VS Code, on the same machine and reading the same licence file, insisted he was on Free: vexp had been counting the CLI and the extension as two separate devices all along. Underneath it, a deeper one: your index was storing the same connection over and over, up to 45 times, which quietly skewed how results were ranked. Plus git hooks that turn out never to have run on an npm install, honest answers when a file has nothing to index, and three fixes for agents that got stuck.

Your licence is the same one in the CLI and in VS Code. A lifetime-plan user watched the CLI report his paid plan correctly while VS Code, on the same machine and reading the same licence file, called it Free and refused to add a second repo. vexp identifies a machine with a value it invents on first run and keeps privately, so the CLI and the extension had each invented their own and registered as two separate devices. On a plan that allows one device, whichever checked in first took the slot and the other was turned away, which from the inside is indistinguishable from having no licence at all. Both now settle on a single identity per machine, and the one your account already knows about is the one that wins. Updating is enough: a client that had been pushed to Free re-checks under the identity the server already has and is let straight back in.

Your index stops storing the same connection dozens of times. Every time vexp re-resolved your call graph, which is every time you save, it appended the whole set of connections again instead of recognising the ones it already had. Real indexes reached 45 times their true size: 270,589 stored connections for 5,951 real ones, one pair of functions recorded 75 times over. That was never just wasted space. vexp ranks results partly by how connected a symbol is, so a symbol counted 75 times looked 75 times more important than it was, and vexp flow answered with the same route printed several times because each copy looked like its own path. Both are fixed at the root: repeats are now rejected on the way in, while genuinely distinct call sites on the same pair are kept, because that is real multiplicity rather than duplication. Existing projects collapse once, automatically, the next time vexp opens them. No reindex, nothing to click, and if the cleanup cannot run for any reason your index keeps working exactly as it does today.

Git hooks were never running your index on an npm install. The git hook vexp installs looked for a program that does not exist in the distribution, so on an npm install both of the places it checked came up empty every single time and the hook quietly did nothing. It now uses the command that is actually there. Installs where no vexp is on your PATH at all, which is every VS Code extension install since it carries its own copy, fall back to the exact binary that is running, rewritten on every start so it can never go stale across an upgrade or a Node version switch.

vexp tells you when a file simply has nothing to index. vexp indexes declarations: functions, classes, methods, types. A file that declares none of those, a config object, a barrel of re-exports, a script that is just top-level statements, indexes to zero symbols. That is expected, but vexp answered as though the file had never been indexed at all, which sent people looking for a bug that was not there. One Tier 4 user concluded vexp did not support .mjs files, when the real answer was that their .mjs files were config objects. vexp now checks first and, when the file is indexed, says so and explains what genuinely extracts nothing. Asking about a file from inside a subfolder works too: a relative path used to be looked up against the wrong location and reported as missing.

vexp is visible before your first index. vexp only starts on its own once a project has an index, so in a project it had never seen it showed nothing at all: no status bar, and a sidebar icon that can hide in the activity bar overflow. The first thing you needed was the one thing you could not find. The status bar now starts on a prompt to run that first index. Commands that need an open folder also used to fail with a bare "command not found"; they now say what is actually wrong.

Running out of daily calls no longer looks like a broken install. On the Starter plan vexp stops itself once the day's 20 calls are spent, and VS Code says exactly that in the status bar. Every other agent got something far worse: a bare connection error, which a model reads as a passing glitch and retries, and retries. One user's local model looped on it for over 200,000 tokens and concluded vexp had broken his whole setup, which from where he sat it had. Agents now get a clear, final answer that says the quota is spent, when it resets, and not to retry until it does. Activating a licence clears it on the spot, so upgrading mid-day is never held to the free tier's reset.

The guard stops blocking searches vexp cannot answer. The opencode guard that steers your agent to vexp instead of raw search was refusing every grep and glob while vexp was running, whatever they were aimed at: runtime logs, build output, files outside your project. vexp has no answer for any of those, so the refusal was a dead end rather than a redirect. One agent debugging a rendering bug had its log searches blocked, was told to use vexp instead, and was left with no working tool at all. The guard now blocks only what vexp actually indexed, your source, and steps aside for everything else. It never touches shell commands either, which is what keeps an escape hatch open for everything the index does not cover.

A blocked search tells your agent what to do instead. The refusal used to say only that vexp was running, which a model reads as a tool failure, something to route around rather than a rule to follow. It now says plainly what to call instead, and the generated instructions spell out what vexp does not cover, so your agent stops both working around the block and spending calls on files that were never indexed.

Every agent is told how to phrase a query, not just Claude Code and Cursor. vexp finds code by identifier and path first, so a question written as prose ranks far worse than the same question anchored on the symbols and files it is about. That one rule had only ever been written into 2 of the 9 agent instruction sets, and one example actively demonstrated the weak version. Two separate reports that "the index seems not very useful" traced straight back to it. All nine now carry the same guidance from a single shared source, with examples built on real identifiers, and a test fails if any template loses it. Nothing changed in the engine: the index was never the problem.

Setting up agents no longer reports success while configuring nothing. Agent names had to match exactly, so asking for "opencode" rather than "Opencode" matched nothing, configured nothing, and still printed success and exited cleanly. That is the worst possible outcome for a flag whose whole purpose is unattended automation: a CI step that sets up no agent at all and reports green. Names now match regardless of case and punctuation, and one that still matches nothing is a hard error listing the valid names with a suggestion. The whole list is checked before anything is written, so a single typo can no longer leave a half-configured project behind.

A hand-tuned guard is kept when you upgrade. Setup still refreshes the opencode guard plugin whenever it differs, since freezing you on a stale one would be worse, but your previous version is now saved next to it as a .vexp-bak file, the same treatment every other vexp-managed config file already gets.

v2.2.1July 15, 2026

Index your project, not the folder it lives in.

A reliability release, driven almost entirely by what users sent us. The headline: if you'd ever used "Add repo to workspace", vexp could quietly index the whole folder that *contains* your project: every unrelated repo sitting next to it, burning your capacity on code you never asked about, while the repo you actually added indexed as empty. Alongside it, the answer to a request we heard clearly: your MCP config no longer gets overwritten every time you open a different editor. Plus fixes for Codex over HTTP, deeply nested macOS projects, the Windows CLI, and the memory of the edits you make by hand.

Multi-repo workspaces index your project, not its parent folder. If you added a second repo through the VS Code command, vexp recorded your main repo with a shorthand that, since 2.1.0, pointed one directory too high, at the folder your projects live in. That folder is real, so vexp took it at its word and indexed everything inside it: one user got 6,098 files and 66,853 nodes, two thirds of their plan's capacity spent on projects that had nothing to do with the one they were working in; on another machine the same shorthand grew a 19 GB index of 77 unrelated projects. Meanwhile the repo they had actually added pointed at a path that didn't exist and indexed nothing at all. So cross-repo answers, the entire reason for adding it, had never once worked. Existing setups keep working exactly as they are: vexp recognises the old shorthand and resolves it correctly. And a repo path that lands outside and above your workspace is now refused outright rather than indexed, so this class of mistake can't cost you capacity again.

Your MCP config stops being overwritten when you switch editors. Run VS Code, Antigravity and another agent against the same project and each one rewrote the shared MCP config on startup, pointing it at its own copy of vexp, so the setup broke whenever you switched. One user was keeping a copy per editor and renaming them by hand. (The same thing happened to a single editor on every vexp update, for the same reason.) vexp now leaves a working config alone, whoever wrote it: the piece it points at is a standalone helper that talks to your local vexp, and it doesn't care which editor's folder it came from. If it ever does point at something no longer installed, that's repaired automatically. You can retire the renaming.

opencode now enforces vexp instead of just suggesting it. In Claude Code, vexp installs a guard that hard-blocks raw grep/glob while it's running. That enforcement is what actually produces the token savings. opencode only ever got the written instruction, which a model follows when it feels like it, so vexp would run once and the rest of the turn fell back to native search (field report: "vexp only runs once, token savings is minimal"). Setup now installs a real guard plugin for opencode that blocks native search (and shelled-out grep, ripgrep and find) exactly like the Claude Code one, while still stepping aside when vexp isn't running so an unindexed project still works. Projects configured with a commented opencode.jsonc are now detected too; they were being skipped entirely.

Codex over HTTP no longer fails for one workspace in sixteen. Some projects got "no daemon" from Codex no matter what, on every platform, while vexp doctor insisted a perfectly healthy vexp was down. The workspace fingerprint that routes Codex to the right project is computed in five places that can't share code; two of them formatted it slightly differently, and for roughly one workspace in sixteen (those whose fingerprint happens to start with a zero) the two never matched. All five now agree, with a test built around exactly that case so they can't drift apart again.

Deeply nested projects work on macOS again, and Windows status tells the truth. macOS caps how long a connection path can be, and vexp has always had a fallback for projects buried deep in your folder tree, but the editor extension didn't know about it and forced the too-long path onto vexp anyway, so it simply never started. The CLI had the mirror image: it looked for vexp at the long path while vexp was listening at the short one, and declared it stopped. On Windows the CLI had been looking for a Mac/Linux connection file that never exists there, so it reported a running vexp as stopped, warned that startup had failed right after it succeeded, and retried on every command.

The edits you make by hand are remembered again. vexp quietly records what changed in your project so it can surface it later. Any edit you made without an agent attached, just you typing in your editor, was being dropped on the way into memory: 127 lost in two days of one user's log, with nothing to show for it but a line in a file nobody reads. Those edits are now kept.

Test runs stop flooding the log. If you use Playwright, vexp chased the throwaway folder created for every test case, usually already gone by the time it looked, and filled your log with dozens of warnings per run. Those folders are now skipped like node_modules, and a folder that disappears before vexp can watch it is no longer worth a warning.

v2.2.0July 14, 2026

Run your local model on an NPU, and index only the code that's yours.

Two headline changes. New reach: the optional local model can now run on a dedicated AI accelerator (NPU), or any local inference server you already have, not just in-process on the CPU. And a fix from a field report every large-repo user will recognise: a project that looked four times its real size, because full copies of the repo (the worktrees your AI agent creates) and a submodule were being indexed as if they were your source. vexp now follows git's own boundary and indexes only what belongs to your project. Alongside: the Windows "use vexp, not grep" guard finally has teeth, and three fixes to how results are found and shown.

Run the local model on an NPU, or any local server you already run. vexp's optional local model compresses context on your machine, privately. It can now talk to any local inference server that speaks the standard OpenAI API. Point it at one line of config and you're set. That opens the door to NPU acceleration on AMD Ryzen AI laptops (via FastFlowLM), and works just as well with llama.cpp's server, LM Studio, vLLM, AMD Lemonade, or Ollama. If the server isn't reachable, vexp falls back to its built-in engine automatically, nothing to babysit.

Worktrees and submodules no longer inflate your index. A user's large project was being indexed at nearly four times its real size (31,914 files where git tracked 7,488) and a full index dragged on for hours. The cause: vexp indexed anything git didn't explicitly ignore, and the worktrees an AI coding agent creates under .claude/worktrees/ are each a complete copy of your repo (git hides them structurally, so no ignore rule ever matched them), as was a submodule. vexp now respects git's own boundary: a folder that is itself a separate git checkout (a worktree, a submodule, a nested clone) is left to git, exactly as it never appears in your project's own file list. Your real project indexes in minutes and stays inside your plan's capacity. Projects with no nested checkouts are unaffected.

The Windows "use vexp, not grep" guard finally works. On Windows, the guard that steers your agent toward vexp's context tools instead of raw grep/glob never actually fired. It looked for a connection file that only exists on Mac and Linux, so it silently allowed every fallback search with no sign anything was wrong. It now uses the right signal on every platform, and a lockstep test keeps the extension and CLI copies from ever drifting apart again.

No more duplicate results for classes with a constructor. Ask about a C# or Java class that has an explicit constructor, and vexp returned it twice, once for the class and once for the constructor, which share a name, printing the same code in full both times and reporting two different impact counts for what is really one symbol. That roughly doubled the cost of those results and muddied blast-radius numbers. The class now comes back once, cleanly.

expand_vexp_ref returns real source code. The tool that expands a compact code reference back to its full form was returning a lossy reconstruction that wasn't valid code in the file's language. It now returns the actual indexed source, and labels what it handed back so a caller can always tell real code from a last-resort sketch.

Plain-English questions don't latch onto filler words. A question phrased in prose could match on incidental words: a query about combat once surfaced an economy class and a weather class because they happened to share a word like "behavior" with the sentence, scored as confidently as a real match. vexp is now stricter about what counts as a real symbol reference, so those false matches drop away, and the setup guidance notes that naming real identifiers or file paths gives the most reliable results.

v2.1.7July 13, 2026

get_skeleton fixed for Codex, and large repos index in minutes.

A focused reliability release. Editors that connect vexp's context tools directly over the native MCP protocol, Codex among them, were getting an empty result from get_skeleton for every file, on both Windows and Linux; that's fixed. Alongside it: full indexing of very large repositories no longer slows down as it grows (a 4-hour index becomes minutes), run_pipeline now respects your folder priorities, and the local LLM runs on minimal Linux container images.

get_skeleton works in Codex and other native MCP clients. Editors that connect vexp's context tools directly over the native MCP protocol, Codex among them, got an empty result from get_skeleton for every file, on both Windows and Linux, no matter how the request was phrased. The tool takes a list of file paths, and clients that sent them as plain strings (exactly as the tool advertises) tripped a format mismatch that came back as an empty list instead of the file's structure. The engine now accepts every shape a client can reasonably send (plain paths or detailed entries, with or without an explicit repository) so get_skeleton returns real skeletons everywhere. Claude Code and the VS Code extension were never affected.

Large-repo indexing: hours become minutes. Resolving imports during a full index did a slow, whole-table search for every imported name in every file. Cheap on a small project, but it grew with the size of the index itself, so on a very large repository (100k+ symbols) a clean index could take over four hours. That lookup is now backed by a database index and matches names exactly, which also removes a subtle inaccuracy where importing User could pull in UserService or getUser. Existing projects pick up the speedup automatically on the next open, no reindex needed.

run_pipeline honours your path priorities. If you told vexp to rank certain folders up, or down, in results, that preference was applied by the older context tool but silently skipped by run_pipeline, the primary one. run_pipeline now respects priority and de-priority paths in both single- and multi-repo workspaces, so the folders you care about rank where you asked.

Linux: the local LLM works on minimal container images. On slimmed-down Linux images (Replit, distroless and similar) the local LLM reported "Backends registered: 0" and every model load failed, because a system library the CPU backend needs isn't present on those bases. The Linux packages now bundle that library next to the binary, so the local LLM is self-contained regardless of the image, the Linux counterpart to the Windows runtime fix in 2.1.6.

A clearer answer to how big your project is. When a plan's capacity limit stops an index short, vexp now prints how many source files it skipped and an estimate of the project's full size: the numbers you need to trim what's indexed or pick a plan.

v2.1.6July 11, 2026

Seven new languages, three new IDEs, on a foundation that can't trip over itself.

The biggest release since launch, in two halves. Reach: seven new languages (34 total: Vue, Svelte, Astro, SQL and more), first-class support for Google's Antigravity IDE, two more agents, and availability on Open VSX, the registry behind the whole wave of AI-first VS Code forks. Reliability: a Tier 3 customer's setup once ran for three hours and told a paying user to "upgrade your plan", because two indexers were silently racing on the same index. That entire class of collision is now impossible, and the same investigation hardened Windows, licensing, and crash recovery.

Seven new languages: Vue, Svelte, Astro, SQL, PowerShell, Groovy, Julia. Vue, Svelte and Astro components are indexed the way they deserve: vexp extracts the script block (and Astro's frontmatter), parses it as TypeScript, keeps every symbol on its real line in the component file, and adds a component node so files are searchable by name. SQL brings your migrations and schema files into the graph: tables, views, functions, indexes, triggers. PowerShell (dash-names included), Groovy (including plain build.gradle scripts) and Julia round out the set. 34 languages total.

Antigravity support that actually works. The previous Antigravity integration wrote a rules file the IDE never read, into a folder it never looks at, and never registered the MCP server. vexp now writes the cross-tool AGENTS.md that Antigravity reads natively, detects Antigravity both from your workspace and from the machine, and registers its context tools in Antigravity's global MCP configuration: one setup, every project.

Two more agents: Trae and Firebase Studio. ByteDance's Trae and Google's Firebase Studio join the auto-configured family (14 agents total): project rules plus MCP wiring, written by the same one-shot vexp setup.

On Open VSX, for Antigravity, Cursor, Windsurf, Kiro & co.. The AI-IDE forks of VS Code can't use the Microsoft Marketplace; their Extensions views run on the Open VSX registry. vexp is published there, so installing it inside Antigravity, Cursor, Windsurf, Kiro or VSCodium is a search away, and the same VSIX still installs manually everywhere else, Trae included.

Only one indexer can ever run on a workspace. Every write path to the index (full index, watcher saves, git-hook syncs, recovery) now goes through an exclusive per-workspace lock. A second indexer waits its turn and says so ("Another vexp indexer is already running…"), git hooks never hang behind a long index (they skip safely and catch up on the next sync), and the lock releases itself if its holder dies. The three-hour double-index death spiral is gone: the same scenario now completes in minutes, with one clean result.

Activating a license takes effect immediately, no restart. The capacity limit used to be read once at process start: activate a license while the engine was running and it kept enforcing the free-tier cap, with "upgrade your plan" messages, until you restarted it. Limits are now read live: the moment your license lands, the running engine picks it up, mid-index included. Single-file saves respect the cap coherently too, instead of quietly bypassing it.

The engine can no longer delete a database it merely failed to open. A transient "database is locked" during startup or a health check used to be treated like corruption, and the recovery path deleted the index to rebuild it, potentially destroying a healthy index another process was still writing. Locked now means locked: the engine waits or retries, re-checks health after acquiring the lock, and deletes only on genuine corruption.

Windows: the local LLM works without the VC++ Redistributable. On Windows machines without Microsoft's VC++ Redistributable, the LLM engine reported "Backends registered: 0" and every model load failed: the CPU backend needs a runtime DLL that Windows itself doesn't ship. The Windows packages now bundle the runtime app-local (self-contained, ~1 MB), and the diagnostics name the exact missing DLL with the fix link instead of a generic "reinstall" hint.

Engine logs survive a restart. Restarting the engine used to erase its log, exactly the evidence needed when something looked wrong. The previous session is now kept alongside the current one.

v2.1.5July 10, 2026

Ready in a second, and graceful when the GPU isn't.

Three field reports, one theme: resilience. On very large projects the engine took minutes to come up and VS Code gave up waiting with no way back. Some multi-repo users saw answers die mid-request with "connection closed". And an RTX 4090 owner couldn't finish the local-LLM install at all. This release makes the engine reachable from the first second, un-crashable on the results that used to kill it, and smart about running the local LLM on CPU when the GPU can't do the job, switching back to GPU on its own the moment it can.

The engine answers in about a second, even during a huge first index. The engine used to open its door only after the entire first index finished. On a big repo that meant minutes of silence, and VS Code stopped waiting after five of them, landing in an error state that only a restart cleared. The door now opens right away: the status bar shows a live "indexing" spinner, status reports honest progress, and anything you ask too early gets a clear "index in progress, retry shortly" instead of dead air. Agents, the CLI, and other editors see the engine immediately too.

No more random "connection closed" mid-answer. When a result happened to include a JSON manifest (a package.json, a service registry, a catalog file) the engine could crash mid-request, taking every open session down with it and reloading the local LLM from scratch. It looked random because it depended on which files matched your question. That whole failure class is gone: manifests now flow through results as plain content, and the code path that killed the engine can't be reached by any file type anymore.

The local LLM installs everywhere: GPU when it works, CPU when it can't. On some machines the GPU is detected but can't actually run the model, most commonly an NVIDIA driver older than what the GPU plugin is built against. The installer used to fail its smoke test there and give up, leaving the LLM disabled. Now the engine proves the GPU with a real warm-up inference at startup; if that fails, it tells you why (usually "update your driver"), finishes the install, and runs on CPU for the session. Your GPU preference is never rewritten. It's retried at every start, so the moment the driver is updated, inference moves back to the GPU by itself.

Force Re-index is now a true reset, even on a damaged index. If the index database was ever damaged (a hard power-off, a disk hiccup), Force Re-index used to rebuild into the damaged file, and the engine crashed on the spot, over and over, with no way out. The button now clears the index completely and rebuilds from zero, and the engine itself repairs a damaged index automatically at startup: it detects the failure, replaces the database, and re-indexes without you doing anything.

VS Code finds its way back on its own. If the engine ever dies or stalls while starting, the editor now notices immediately and respawns or reconnects with backoff, instead of parking in an error state until you restart VS Code.

v2.1.4July 9, 2026

Live indexing that keeps up: new folders, deletions, and all.

A user building out a Laravel project caught it: files created in brand-new folders mid-session weren't being picked up by the live index until a restart. This release makes the index track new directories the moment they appear (and sweeps in whatever is already inside), drops deleted files on the spot, and hardens the status command so it never crashes while the engine is busy.

New directories are indexed the moment they appear. To keep from exhausting the OS on huge repos, vexp watches folders one at a time rather than the whole tree at once, which meant a folder created after the engine started wasn't being watched, so files added inside it stayed invisible to the live index until a restart or a force re-index. Now a new folder is picked up as soon as it appears: it and its subfolders are watched, and the source files already inside are swept into the index straight away (respecting .gitignore / .vexpignore).

Deleted files leave the index right away. Removing a file while the engine was running used to leave its symbols behind until the next full re-index, so the live file count slowly drifted above reality. Deletions are now pruned on the spot, keeping the index and its counts accurate.

"Still indexing" instead of a misleading empty answer. A full re-index briefly clears the index while it rebuilds. A symbol lookup landing in that window used to come back empty, indistinguishable from "this file has no code," which could fool an agent into thinking a file was never indexed when it actually was. It now says the index is rebuilding and to retry, rather than returning a false blank.

Checking status is always safe. The status command read index stats by opening the database directly; while the engine was actively writing (indexing new files, sweeping folders) that could crash the command. It now reads in a strictly read-only mode that is safe alongside a live writer, so vexp daemon-cmd status is safe to run at any moment.

v2.1.3July 9, 2026

A calmer first index on big projects.

A user with a very large project reported the engine taking over a minute to come up and, in the meantime, multiplying into several background processes. The root cause: the daemon only announced itself once the first index finished, so until then it looked stopped and every command started another one. This release makes the engine visible from the first second: one process, an honest "starting" status, and a log you can actually watch.

One daemon, even during a long first index. On a large codebase, or when vexp is opened from a very broad folder like your home directory, the initial index can take well over a minute. The engine now registers itself before indexing begins, so a second launch during that window is a clean no-op instead of a duplicate process. No more pile-up of background daemons while the first index runs.

Status tells the truth: "starting", not "stopped". While the first index is running, daemon-cmd status now reports "starting (initial index in progress)" with a note not to restart, instead of "stopped", which is what made the engine look dead and triggered the restart loop in the first place.

A first index you can watch. The background engine now logs its progress by default, so .vexp/daemon.log shows the index starting, the file count, completion, and the socket coming online. Tail it and watch the first index move instead of guessing whether it stalled, without flooding the log with per-file noise.

Status is safe to run at any moment. Checking status while the engine was mid-index, or shutting down, could abort the status command. It now reads index statistics only when the daemon is fully up or fully stopped, so a status check is always safe, even in the middle of a large first index.

v2.1.2July 8, 2026

Impact lenses that always resolve.

The inline "dependents" lenses over your exported symbols now come straight from the index, with real fully-qualified names for every language vexp parses, so clicking one always lands on the right impact graph, the counts are exact, and scrolling stays smooth. Plus a license fix so VS Code registers your device right away.

CodeLens driven by the real index. The dependent-count lenses are now built from the daemon's own symbol list rather than a per-language text scan, so they carry the exact fully-qualified name of each definition. The impact click resolves every time, across all languages vexp parses, and lenses land only on real definitions, skipping tests and whole-file nodes that were just noise.

Exact dependent counts, no string guessing. The engine now returns structured impact numbers (total dependents, distinct files, cross-repo hits) instead of the client re-parsing a rendered string. The number on each lens is accurate, including cross-repo callers.

Smooth scrolling, fewer daemon round-trips. File symbols and their impact counts are cached per document version, so scrolling and re-rendering a file no longer re-queries the engine for unchanged code.

VS Code registers your device immediately. Activating in VS Code now runs its first license validation without hitting the 24-hour refresh backoff. That validation is what registers the machine as a device, so your seat is counted right away instead of after a restart-past-24h window. Unresolved-symbol clicks also show a friendly note instead of failing silently.

v2.1.1July 8, 2026

Docs stay docs. Legacy files stay searchable.

Two retrieval and reliability fixes from real reports: a code-shaped query on a documentation-heavy repo no longer gets Markdown handed back as if it were the code, and files saved in legacy Windows-1252 encoding index cleanly instead of crashing the engine. Login autostart is now strictly opt-in.

A code query never gets docs passed off as code. On a doc-heavy or freshly-started repo (planning docs, spec-kit artifacts), the query words often live in Markdown, so the engine used to surface doc sections as pivots to avoid an empty result, handing back prose for a code question. It now never manufactures a doc hit to fill a code-shaped query; docs still appear when they earn it on their own merit, but code queries get code.

Legacy non-UTF-8 files index without crashing. Source files saved in Windows-1252 (legacy PHP, accented Latin text like ção or ê) could panic the engine mid-index. Files are now decoded UTF-8-first with a Windows-1252 fallback and parsed from the decoded text, so legacy encodings index cleanly and stay searchable instead of turning into replacement characters.

Login autostart is opt-in. vexp no longer writes any OS login-persistence on its own. Autostart is installed only when you ask for it, via the setup wizard prompt or vexp autostart install, and non-interactive installs skip it entirely.

Windows build fix. A connection handler was compiled Unix-only, breaking the Windows build; it is now cross-platform.

v2.1.0June 28, 2026

Introducing the vexp SDK. Plus license reliability.

The engine behind the extension and CLI is now available as a self-hosted SDK: code intelligence as machine-to-machine infrastructure for your agents, PR bots, and pipelines across hundreds of repositories. This release also sharpens context retrieval and call-graph completeness for everyone, and lands the license-token reliability fixes from the 2.0.33 line.

vexp SDK: code intelligence as infrastructure. The same engine, exposed for automation: a self-hosted, machine-to-machine gateway that serves type-accurate context and cross-repo blast-radius over a simple REST API, with Python and TypeScript clients. Built for coding agents, PR review, and incident triage at fleet scale, on your own infrastructure, so your code never leaves your network. Details at vexp.dev/sdk.

Sharper retrieval for multi-word queries. Natural-language and multi-word searches now line up better with compound identifiers: PascalCase and snake_case names whose parts are spread across your query surface more reliably as the top pivots, so context capsules land on the right symbol more often. Existing indexes pick this up automatically on the first open after upgrading.

Fuller call graphs and blast-radius. The indexer reconciles more call edges when it builds the graph, so get_impact_graph and search_logic_flow return more complete callers and callees and a refactor blast-radius reflects more of the real connections, visible after the next reindex.

Impact and ranking refresh after every edit. Call edges and ranking signals are now recomputed after each batch of indexed changes, so impact analysis and search stay current as you work instead of lagging until a full reindex.

Activation clears a stale refresh token. Activating a license now clears any leftover short-lived refresh token first, so a re-activation or a plan change takes effect immediately instead of being shadowed by a cached token from the previous state.

Plan changes persist the new server token. When your plan changes, the server-issued long-lived token is now persisted on the spot, so the new node and repo caps apply across VS Code and the CLI without waiting for the next refresh cycle.

v2.0.32June 21, 2026

Multi-repo targeting that stays put.

Two reports converged on the same root cause: a leftover global VEXP_WORKSPACE that quietly pinned every agent session to a single repo, so work in other repos came back empty. This release makes per-session targeting win, fails loud instead of returning nothing, gets the VS Code extension and the CLI auto-start daemon to cooperate, and restores the Claude guard hook on Windows.

Per-session targeting wins over a stale global pin. A global VEXP_WORKSPACE environment variable used to override each session's own project, pinning every agent, Claude Code and Codex alike, to one repo's daemon. Agents working in other repos then queried the wrong index and got empty results from get_skeleton and run_pipeline (while the CLI, which resolves the index from the file's own folder, kept working, the tell-tale split). When VEXP_WORKSPACE disagrees with a session's actual project, the per-session signal now takes precedence, so parallel sessions across repos each target their own index. A deliberate single-project pin still behaves as before.

get_skeleton fails loud instead of returning nothing. Asking for a file in a repo the connected daemon does not serve used to silently fall back to the primary repo and return an empty list, indistinguishable from "file not found." It now returns a clear error naming the repos the daemon actually serves and pointing at the likely cause, so a misrouted request is obvious instead of masquerading as missing data.

vexp doctor catches the global-pin trap. vexp doctor now warns when a global VEXP_WORKSPACE is overriding per-session targeting, or when several daemons are registered behind a single pin, the exact misconfiguration behind empty cross-repo results, instead of reporting "no issues detected." A shell or OS-level environment variable is invisible to the config checks, so this surfaces it directly.

VS Code adopts a CLI-started daemon. When the CLI auto-start had already launched the engine, the extension could refuse to attach to that healthy daemon and sit on "starting" indefinitely, even though your agent was being served fine. It now recognizes and adopts a running daemon (including one started in a different context, where the liveness probe returns "permission denied" rather than "gone"), and a failed start resolves to a clear state instead of hanging on "starting."

Windows: the Claude guard hook works again. The PreToolUse hook that steers Claude toward vexp instead of Grep/Glob checked for a Unix socket file that never exists on Windows, so there it always failed open and Grep/Glob were never intercepted. The hook is now platform-aware (Windows keys off the named-pipe marker, Unix/macOS keep the socket-plus-live-PID check unchanged) so re-running agent setup restores the nudge.

v2.0.31June 17, 2026

Blast radius, for whole classes.

With call edges finally flowing, one gap remained: a refactor-impact query on a class reported "0 impacted" even though the connections were right there. Two fixes: impact now aggregates a class's methods, and the counter no longer drops its own results.

Impact on a class now includes its methods' callers. Call edges target method nodes, not the class node, so an impact query on a class found zero incoming edges (querying an individual method worked; querying its class did not). Impact analysis is now container-aware: for a class/struct/interface it seeds the dependents search with the class and all its members, so everyone who calls any method of the class counts as its blast radius. A query like "NpcProfile blast radius" now enumerates the callers instead of returning nothing.

The impacted counter no longer zeroes itself out. The pipeline capped collected impact nodes at a value derived from the token budget (impact_budget / 50), which rounded to 0 for the refactor preset, silently discarding every dependent so the "impacted" count read 0 even when the analysis found real callers. That cap is now a sensible node-count limit, independent of the token budget, so the count reflects the actual blast radius.

v2.0.30June 17, 2026

C# call graphs, lit up.

The 2.0.28–2.0.29 LSP fix announced that call edges resolve across FQN formats, but for one C# project they still came back empty. The reproduction package showed why, and the fix is now complete: impact and logic-flow queries surface real callers and callees, with no reindex required.

Dotted Class.Method callees now resolve. VS Code's Call Hierarchy names a callee as file::Class.Method(params) (and file::Ns.Class.Method), a dotted containing type, while the source side comes through as file::Method(params):ret. Our FQN parser stripped the parameter list but not the dotted class prefix, so the callee resolved to "BtDebugLogger.Header", matching no node. On the reported index that meant 17,675 sources resolved but only 37 callees, so every call edge was dropped. The method is now taken as the final segment after the last "." or "::", so dotted callees resolve: 0 → 10,944 reconciled CALLS edges on the same index.

Call edges resolve at submit time, no reindex. The socket path that accepts LSP edges from the extension created graph edges only on an exact FQN match, so format-mismatched edges had to wait for a full reindex to be picked up. Both the submit path and the indexer reconcile pass now share one format-tolerant resolver (exact match, then an unambiguous (file, method) fallback that declines on overloads). Open VS Code, let the bridge submit edges, and get_impact_graph / search_logic_flow light up immediately.

v2.0.28–2.0.29June 17, 2026

Your C# code, found.

A Pro user reported that vexp returned zero context for an entire C# codebase. The trail led through several compounding issues: a path filter, the embedding vocabulary, the semantic scoring, the LSP call edges, and the pivot budget. All fixed and validated end-to-end against the reported index.

Spec-Kit and feature directories no longer filtered out. The capsule's test/benchmark path filter matched the substring "/spec", so a codebase laid out under Spec-Kit directories (Claude/SPEC-08_CombatSystem/…) was classified as spec/test code and dropped from every result, only Markdown survived. Path classification now matches whole directory segments (tests/, spec/, __tests__/, benches/) and unambiguous filename patterns, never substrings, so SPEC-08_*/, specifications/, and latest.cs are treated as the production code they are. Query-side fix, no reindex required.

Cleaner embeddings, calibrated semantic scoring. TF-IDF tokenization now splits on every non-alphanumeric character and drops numeric and syntax artifacts (get;, key(5)], <summary>), and language keywords are filtered from the embedding vocabulary while the symbol name is kept verbatim. The query vector is computed against the index's global IDF statistics instead of a tiny per-query mini-corpus, so semantic similarity is meaningful: PascalCase queries that previously scored ~0 now score 70–90% on the right symbol.

Large classes are never dropped from a capsule. The pivot loop used to break on the first candidate whose body exceeded the budget, so an exactly-named 750-line C# class returned an empty capsule while a 350-line one worked. The top-ranked pivot is now always included (capped to the total budget and truncated if enormous) and oversized lower-ranked pivots are skipped instead of ending the loop.

LSP call edges resolve across FQN formats. VS Code emitted call edges as file::Method(params):ret while the indexer stored file::Class::Method, so the two never joined and get_impact_graph / search_logic_flow only ever returned class-membership edges. Reconciliation now resolves on (file, method) when an exact FQN match fails, declining on ambiguity, and the extension emits a file::Class::Method form. Call graphs light up for C#.

v2.0.18–2.0.27May–June 2026

Zero-config GPU, Codex hardening, lifetime tiers.

A run of platform and integration releases between the UTF-8 hotfix and the C# context work: GPU acceleration with no setup, a more robust Codex and MCP transport, multi-workspace targeting, and AppSumo lifetime plans.

Zero-config GPU acceleration. NVIDIA acceleration with no manual setup: the CUDA runtime is fetched on demand from the public PyPI redistributable the first time a GPU is detected, including CUDA 12.8 / Blackwell SASS support. There is no separate GPU build to install.

Codex & MCP transport hardening. Codex now uses a direct MCP transport with stdout kept strictly JSON, no reindex or abort chatter under it, which previously surfaced as "Transport closed". Agent and MCP config files are merged rather than overwritten on a parse failure, and written ASCII-clean.

Per-session, multi-workspace targeting. Each session resolves to the correct workspace daemon, so several projects open at once no longer cross their indexes. Plus license diagnostics, a force-reindex restart path, and Windows single-instance daemon handling to stop duplicate vexp-core processes from leaking handles.

AppSumo lifetime tiers. Lifetime-deal plans (tier1–tier4) are recognized across the license issuer and consumer, mapping to the right node and repo caps.

v2.0.17May 17, 2026

UTF-8 panic, gone.

Hotfix for a daemon crash on markdown files larger than 8 KB containing em-dashes, smart quotes, emoji, or accented letters. The indexer now snaps every byte-bounded truncation to a UTF-8 character boundary, across every site, not just the reported one.

Daemon no longer crashes on non-ASCII markdown. truncate_markdown_body sliced markdown content at a fixed 8000-byte offset that could land mid-codepoint, killing the daemon before it bound its socket. Every downstream call (vexp mcp --proxy, the VS Code sidebar, MCP requests) then failed silently with no diagnostic. The slice now snaps to the nearest UTF-8 char boundary via str::floor_char_boundary, so multi-byte characters at any offset are handled correctly.

Audit-wide fix, not a one-spot patch. A sweep of the Rust codebase surfaced five total sites with the same raw-byte-slice pattern (markdown indexing, JSON value summarization, tool-result logging, JSON-shape sniffing, impact-graph truncation). All five now use the stable str::floor_char_boundary stdlib method. The obsolete in-crate floor_char_boundary_safe helper was removed so contributors stop reaching for the wrong idiom.

Regression tests for the exact reproducer. Four new tests cover the reported em-dash-at-byte-8000 case plus the pathological all-em-dashes input and the 2-byte (accented letter) and 4-byte (emoji) UTF-8 boundary cases. 375/375 cargo tests green; end-to-end validated by rebuilding 2.0.17 with the local LLM feature and indexing the trigger files plus the full vexp repo.

v2.0.14May 15, 2026

Windows autostart, fixed.

Repairs the login-time launcher on Windows so vexp serve actually starts. Auto-migrates broken 2.0.12 installs in place, no manual reinstall needed.

Windows login launcher fixed. vexp 2.0.12 shipped an invalid VBScript template for the Startup-folder launcher, causing a Type mismatch error at login and silently preventing vexp serve from starting. The generator now emits valid VBScript (proper quote doubling) and routes through %ComSpec% /d /s /c so cmd.exe applies legacy single-strip quote semantics. Verified end-to-end: the MCP HTTP server starts on 127.0.0.1:7821 at login and writes to ~/.vexp/autostart.log.

Automatic migration from 2.0.12. On Windows, the next vexp invocation after upgrading detects any existing broken vexp-serve.vbs by signature and rewrites it with the fixed template, before the autostart marker short-circuit kicks in. No manual vexp autostart uninstall/install required. VEXP_NO_AUTOSTART_INSTALL=1 still opts out cleanly: the migration runs after the env-var check, so opted-out users are never touched.

v2.0.13May 14, 2026

Markdown as code. Secrets stay out. More headroom on Starter.

Index Markdown docs alongside code, keep sensitive files out of the index by default, and explore vexp with 20 daily calls on Starter.

Markdown indexing (first-class). .md and .mdx files are now indexed at section granularity. Each heading becomes a retrievable node; URLs inside sections become markdown_link nodes. Spec-Kit and planning artifacts (spec.md, plan.md, tasks.md, README.md, docs/adr/*) get a small ranking boost so agents surface them when relevant.

Secret filtering (default on). Files matching .env, .env.production, id_rsa, *.pem, *.key, secrets.yaml, secret.md, password.json, credentials.toml, and similar filename-boundary matches, are skipped at index time. Legitimate code like src/secret-manager.ts is unaffected. Public templates (.env.example, .env.sample, .env.template) are allowlisted. Opt out with filter_secrets = false in .vexp/vexp.toml.

.vexpignore filename alias. .vexpignore (no underscore, matching the .gitignore / .dockerignore convention) now works alongside the historical .vexp_ignore. If both files exist, their patterns are concatenated.

.ignore (ripgrep) support documented. vexp also honors .ignore files (the ripgrep / fd / Sublime Text convention) via its underlying walker. If your project already has one for ripgrep, vexp picks it up automatically.

Starter: 20 pipeline calls per day. Daily call quota raised from 8 to 20 on the Starter plan. Enough to explore vexp on real projects across a full work day. Quota still resets at midnight UTC.

Cross-repo collection respects ignore rules. The multi-repo cross-link detector now routes file collection through the same walker as the primary indexer: .gitignore, .vexp_ignore, and .vexpignore patterns are honored consistently everywhere vexp reads source files.

v2.0.10April 16, 2026

Works everywhere. Zero maintenance.

Broad Linux compatibility, fully automatic daemon lifecycle, and an interactive CLI that shows you exactly what's running.

Runs on every Linux distro. Ubuntu 20.04+, Debian 11+, RHEL 8+, Fedora, WSL2, Amazon Linux, all supported out of the box. No extra libraries to install.

Daemon auto-start. After first setup, the background engine starts automatically whenever you or your AI agent needs it. Nothing to manage.

Interactive CLI. Type `vexp` for a guided menu with live status of the daemon and local LLM. Setup, explore, and configure agents without memorizing commands.

Reliable agent connections. AI agents (Claude Code, Codex, Cursor, …) now find the daemon automatically, even when launched from different directories. Connection errors eliminated.

v2.0.0–2.0.1April 10–14, 2026

Local AI layer + GPU acceleration.

Optional on-device LLM (vexp-devmind) that compresses prompts and context further. Metal on Apple Silicon, CUDA on NVIDIA. Smart prompt preprocessor and agentic context loop.

vexp-devmind local LLM. A compact, code-specialised model that runs entirely on your machine and compresses prompts and context further. Zero cloud, zero account, zero code leaving your laptop. ~3.5 GB download.

GPU acceleration. Metal on Apple Silicon + Intel Macs, CUDA on NVIDIA (Linux & Windows). Auto-detected at runtime. CUDA plugin downloaded on-demand when an NVIDIA driver is detected.

Smart prompt preprocessor. Your question is analysed locally and pre-enriched with the right files, symbols, and call chains before reaching your AI agent. Fewer tool calls, sharper answers.

run_pipeline single-call. One MCP call replaces capsule + impact + memory + observation. Auto-detects intent (debug/modify/refactor/explore) from your task description.

v1.2.30March 21, 2026

30 languages. One engine.

vexp now parses 30 programming languages, from Kotlin and Swift to Terraform and Zig. Plus a daily free tier quota, pipeline streaming, and an upgraded LSP Bridge.

30 language support. From 12 to 30 languages in a single release. Kotlin, Scala, Swift, Dart, PHP, Elixir, Haskell, OCaml, Lua, R, Zig, HCL/Terraform, Objective-C, Dockerfile, Clojure, and F# join the existing lineup with full AST parsing and dependency graph support.

Daily free tier quota. Starter plan users now get 20 pipeline calls per day, enough to explore vexp on real projects. The quota resets daily at midnight UTC.

Pipeline streaming. run_pipeline now streams results as they arrive: pivots first, then impact analysis, then memories. Agents see initial context faster on large codebases.

LSP Bridge v2. The VS Code language server integration now captures richer type information with better performance. More accurate call graphs, especially for TypeScript generics and Go interfaces.

189 automated tests. Test suite expanded from 133 to 189 tests covering all 30 languages, pipeline streaming, and the new quota system.

v1.2.26–1.2.28March 12–18, 2026

Infrastructure improvements

Internal release pipeline fixes and package alignment for more reliable installs across all platforms.

Smoother installs. Fixed edge cases in the npm publish pipeline and aligned package versions across the VS Code extension and standalone CLI.

v1.2.25March 9, 2026

Smarter license handling

License verification is now more resilient, and the hook guard handles edge cases better.

Resilient license verification. License checks now handle edge cases and malformed responses gracefully. Your workflow is never interrupted by a transient verification issue.

Smarter parameter handling. MCP tool parameters are now automatically coerced to the correct types. Agents that send numbers as strings or booleans as "true" no longer cause errors.

Improved hook guard. The Claude Code hook guard now uses process-level detection to avoid conflicts with other tools. More reliable interception on busy systems.

v1.2.24March 6, 2026

Self-healing database

vexp now detects and recovers from database corruption automatically, plus real-time health monitoring.

Automatic database recovery. If the index database is corrupted (e.g. after a system crash), vexp detects it and rebuilds automatically from the manifest. Zero data loss, zero manual intervention.

Health monitoring. The daemon now monitors its own health continuously: database integrity and indexing throughput. Issues are surfaced in the status bar and index_status output.

133 automated tests. Comprehensive test suite covering database recovery, health checks, and all supported languages.

v1.2.23March 5, 2026

Smoother publishing, same great engine

Behind-the-scenes improvements to how vexp ships updates: faster, more reliable releases for everyone.

Faster, more reliable updates. We overhauled our release pipeline so new versions reach you quicker and more reliably across all platforms: Linux, macOS, and Windows.

v1.2.19March 1, 2026

Rock-solid connections

vexp now gracefully handles daemon restarts, stale connections, and multi-window setups, so your agent never loses context.

Automatic cleanup on shutdown. vexp now cleans up all connection files when the daemon stops, preventing stale sockets even after unexpected crashes.

Smarter hook guard. The Claude Code integration now detects stale connections and gracefully falls back, so your agent always has access to code search tools.

Regex support in hooks. The hook guard now intercepts Regex tool calls in addition to Grep and Glob, ensuring your agent always uses vexp's smarter search.

v1.2.18February 28, 2026

Multi-window harmony

Open the same project in multiple VS Code windows: vexp shares a single daemon across all of them, no conflicts, no duplicates.

Shared daemon across windows. Multiple VS Code windows now share a single vexp daemon. Only the original window manages its lifecycle, other windows connect automatically.

Automatic port recovery. If the MCP port is busy, vexp finds an available one automatically. No more restart loops or manual port changes.

Smart reconnection. If the daemon restarts, non-owner windows detect it and reconnect with exponential backoff. If the daemon is gone, a new window takes over automatically.

v1.2.17February 28, 2026

Fewer interruptions

Fixed a rare issue where brief daemon restarts could cause your agent to fall back to slower, less accurate tools.

Resilient daemon connection. The MCP client now retries transient connection errors automatically, keeping your agent on vexp's smarter search even during brief daemon restarts.

v1.2.16February 28, 2026

One call. Full context.

run_pipeline combines context search, impact analysis, and memory recall into a single MCP tool call. Plus CLI license enforcement, safer git hooks, and bug fixes.

run_pipeline MCP tool. Single-call pipeline that auto-detects intent (debug/modify/refactor/explore), combines capsule + impact + memory, and includes full file content for pivots. One call instead of three. Available on all plans (basic on Starter, full on Pro+).

CLI license enforcement. vexp activate, vexp deactivate, and vexp license commands. Shared license file at ~/.vexp/license.jwt: activate once, both VS Code and CLI use the same plan.

Marker-delimited git hooks. Git hooks now use # --- vexp start/end --- markers. Safely coexists with existing hooks (husky, lint-staged) without overwriting them.

Mobile & React Native defaults. Pods/, DerivedData/, and .expo/ are now excluded from indexing by default for faster mobile project setup.

Intent detection fix. Fixed false positives in intent detection caused by substring matching on non-English words. Now uses exact word boundary matching.

FTS5 query fix. Fixed crashes when search queries contained special characters like parentheses, asterisks, or quotes.

v1.2.14February 25, 2026

Install anywhere

vexp is now available as a standalone CLI via npm. Install once, use with any agent, no VS Code required.

Standalone CLI. npm install -g vexp-cli and start using vexp with Claude Code, Codex, Opencode, or any terminal agent. Zero dependencies, instant setup.

Direct binary mode. Run vexp-core mcp --workspace . for maximum performance. One native binary, no Node.js required.

Smarter file watcher. vexp now reads your .gitignore and skips ignored files entirely. Faster indexing, fewer false positives.

Cleaner multi-repo sidebar. Empty repositories are hidden by default. One toggle to show them when you need to.

5 platform packages. Native binaries for Linux (x64, arm64), macOS (Intel, Apple Silicon), and Windows. Downloaded automatically on first install.

v1.2.12February 24, 2026

Windows reliability

Rock-solid Windows support for named pipes and cross-platform MCP connections.

Windows named pipes fixed. Each workspace now gets a unique pipe name. No more connection failures when running multiple projects.

Smaller Windows extension. The VS Code package no longer bundles binaries for other platforms. 5 MB instead of 45 MB.

v1.2.11February 24, 2026

Intelligence without configuration

vexp now watches how you work, detects patterns, and generates project rules, all automatically.

Passive Observation. vexp watches every file change and correlates it with your agent's tool calls. It understands not just what changed, but why, without any setup.

Project Rules. Recurring patterns in your workflow are auto-promoted into rules. They persist across sessions and are injected into context capsules, so your agent learns your project's conventions.

Memory Consolidation. Repetitive observations are automatically merged, keeping memory clean and relevant without losing information.

Standalone MCP server. Run vexp as a native Rust daemon with MCP over stdin/stdout. Works with Claude Code, Codex, and any MCP-compatible agent, no VS Code needed.

Manifest-only git. Only a lightweight manifest.json is committed to git. No binary blobs, no merge conflicts. Teammates rebuild the full index in seconds.

v1.2.0February 22, 2026

Session Memory: your agent remembers

Agent-agnostic session memory linked to the code graph. 3 new MCP tools. 10 tools total.

Session Memory system. Full session memory: auto-capture, cross-session search, manual observations, and staleness detection, all linked to the code dependency graph. Works identically across all 12+ supported agents.

3 new MCP tools (Free Tier). get_session_context, search_memory, and save_observation. All 3 are available on the free Starter plan: memory is the product showcase.

Auto-capture every tool call. Every MCP tool call is automatically recorded as a compact observation (~100-200 bytes) with per-tool extraction: intent + pivots, symbol FQNs, start→end flows, file lists.

Capsule memory auto-surfacing. get_context_capsule automatically includes relevant memories from previous sessions, no extra tool calls needed. Budget: 10% of token budget, ranked by query + pivot proximity.

Code-graph linked staleness. Observations linked to code symbols are auto-flagged stale when the code changes. Stale memories are penalized in search ranking (-0.30) but never deleted.

Hybrid memory search. FTS5 BM25 (0.35) + TF-IDF cosine (0.25) + recency decay with 7-day half-life (0.20) + code-graph proximity (0.15) − staleness penalty. Every result includes a "why" field.

Session compression. Background task compresses inactive sessions (>2h): extracts key terms, file paths, node IDs into a structural summary. Ephemeral observations deleted; insights preserved permanently.

Progressive disclosure. 3 detail levels: L1 Headline (~20 tokens, capsule inject), L2 Standard (~50 tokens, default search), L3 Full (~100 tokens, deep queries).

Database schema v2. New tables: sessions, observations, observations_fts (FTS5 with porter stemmer), observation_node_links. Incremental migration, no existing data touched.

v1.1.2February 21, 2026

UTF-8 safety fix

Prevents panics on multi-byte UTF-8 string slicing.

UTF-8 multi-byte fix. Prevent panics on multi-byte UTF-8 string slicing in skeleton generation and TF-IDF tokenization.

v1.1.1February 21, 2026

Cross-repo, meet your IDE

Cross-repo queries go live. A new VS Code sidebar brings the graph into your editor.

Cross-repo queries are live. Context capsules, impact graphs, and logic flow searches now resolve symbols across repository boundaries. One query spans your entire stack: frontend, backend, and infra.

VS Code sidebar panel. Real-time daemon status, index statistics, per-repo cards, and quick actions (Force Re-index, Generate Capsule, Add Repository, View Logs) all without leaving the editor.

CodeLens on exports. Every exported symbol shows its dependent count inline: "12 dependents across 4 files". Know the blast radius before you refactor.

Hover impact data. Hover over any exported declaration to see dependents, cross-repo references, and top callers, without running a single command.

All 12 agent configs updated. Multi-repo documentation added to every agent template, from Claude Code's full CLAUDE.md to Cursor's compact rules file.

Smarter capsule ranking. File-path detection, churn-based scoring, and path priority boosting deliver more relevant results with fewer tokens.

v1.1.0February 20, 2026

Six new languages, one smarter engine

From 6 to 12 languages. Plus intent detection, hybrid search, and the LSP bridge.

6 new languages. Java, C#, C, C++, Ruby, and Bash join TypeScript, JavaScript, Python, Go, and Rust. Full class, method, and interface extraction for each.

Intent detection. vexp reads your prompt and adapts. "fix bug" activates debug mode and follows error paths. "refactor" shows blast radius. "add feature" prioritizes modification targets.

Hybrid search. Combines full-text search, TF-IDF semantic similarity, and graph centrality scoring. Finds validateCredentials when you search "authentication".

LSP Bridge. VS Code captures type-resolved call edges from the Language Server and feeds them into the dependency graph. Higher confidence, zero configuration.

New MCP tool: submit_lsp_edges. Dedicated tool for IDE-level call edge submission. 7 MCP tools total.

Claude Code stdio transport. Switched from HTTP to stdio transport, eliminating "Session not found" errors. Rock-solid, zero-config connection.

Context feedback loop. Repeated queries with similar terms automatically expand the result budget, giving you deeper context on the second pass.

v1.0.10February 2026

The foundation

The initial stable release: graph-powered context for AI coding agents.

Core context engine. tree-sitter parsing, AST dependency graph, SQLite index, and token-efficient context capsules that return the code that matters instead of whole files.

5 languages at launch. TypeScript, JavaScript, Python, Go, and Rust with full function, class, and type extraction.

6 MCP tools. get_context_capsule, get_impact_graph, search_logic_flow, get_skeleton, index_status, and workspace_setup.

Auto-configuration for 12 agents. One command detects your AI agent and writes the config file. Claude Code, Cursor, Windsurf, Copilot, and 8 more.

Git-native manifest. Only manifest.json (blake3 hashes) is committed to git. Clone a repo and the index rebuilds incrementally from the manifest in seconds.