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.