vs-token-safer

English · 한국어

A token-saving code layer for Claude Code, for any codebase. TypeScript, JavaScript, and Python work with zero setup; C# and C++ add a language server; and 30+ more languages come in on the built-in tree-sitter tier. Proven up to a 26k-translation-unit Unreal Engine monorepo — so your web, backend, or data repo is the easy case.

Claude Code MCP Glama release License: MIT PRs Welcome Stars

Your coding agent has a small context window. Your repo is large. vs-token-safer sits in between.

Ask where something is, what calls it, or even "how does the auth flow work?" when the name escapes you — and instead of pasting a wall of source into the chat, it replies with a short file:line list.

  • Builds normally? The answers are exact — it reads the same code index your editor relies on.
  • No toolchain set up? It still locates your functions and classes.
  • Forgot the name, only remember what it does? It finds it from the vocabulary your own code already uses — no AI model, nothing uploaded.
  • Markdown or a config file? Jump straight to one section by its heading instead of opening the whole file.

A companion plugin does the same for giant editor and build logs. None of it leaves your machine.

# Claude tries to grep code → the hook REWRITES it to the indexed query, in place:
$ grep -rn "createSession" src/
↻ [vs-token-safer] Rerouted → search_symbol "createSession"   # semantic, not a text match
  func createSession (in AuthService)  @ src/auth/session.ts:142   (+2 more)
  → ~120 tokens   (grep would have dumped thousands of lines)

# Editing that symbol? Name it — no Read-the-whole-file, no line counting:
$ replace_symbol_body symbol="createSession" body="…"        # preview; apply=true writes
  replace_symbol_body "createSession" — PREVIEW at src/auth/session.ts:142-160

Same flow on TypeScript, Python, C#, C++, Go and more (clangd · Roslyn · tsserver · pyright · tree-sitter). VTS_REWRITE=0 blocks instead of rewriting.

Why

  • Keep the context lean. grep on a large repo — a TypeScript/Python monorepo, a C#/.NET solution, even a 26k-TU Unreal C++ tree — floods the context. The language-server index stays token-capped — ~97–99% smaller (benchmarks).
  • Claude keeps reaching for grep. The hook doesn't just block it — it rewrites the command to the indexed query in place, so the search still runs and the flow never breaks.
  • Edit by symbol, not by line. Replace/insert-around/delete a declaration by naming it — the index supplies the span, so you skip reading the whole file into context.
  • Review a diff by its blast radius, not by eye. detect_changes maps a git diff to the symbols it changed, walks each one's callers, and scores the risk LOW/MED/HIGH — from the blast radius, the caller-cascade depth, and which historically co-changed files this diff left out. A review starts from "what does this actually touch," capped to file:line, nothing uploaded. It's the code-review-context idea done the vs-token-safer way: the official language server for the blast radius and your own git history for the coupling — no persistent graph database, no embeddings.
  • You can't tell how much grep still slips through. vts discover reads your recent sessions and reports exactly which searches bypassed the index and what they cost.
  • The language server runs headlessly — no editor open, unlike an IDE-proxy approach.

Quickstart

# 1) Install (also auto-installs the gamedev-log-analyzer sibling)
/plugin marketplace add JSungMin/vs-token-safer
/plugin install vs-token-safer@vs-token-safer
/reload-plugins        # first run auto-installs the server deps (no manual npm)

# 2) Configure — detects the backend, asks for the project path, writes the config
/vs-token-safer:setup

Then restart the Claude Code session (the vs-search MCP server only starts on a fresh session). Verify the tools appear and that grep src/**/*.cpp is rerouted to the index. Prerequisites: Node ≥ 18 and a language server — clangd (C/C++) / Roslyn (C#) you install; JS/TS + Python auto-install. Details in Prerequisites below.

Want only the log analyzer? /plugin install gamedev-log-analyzer@vs-token-safer.

How it works

vs-token-safer isn't a search box — it's a precision ladder. You ask where something is, what calls it, or "how does the auth flow work?" when the name escapes you, and it answers at the highest precision it can reach, then tells you which rung the answer came from:

  • EXACT — you know the name and the project builds → the official language server (clangd / Roslyn / tsserver / pyright), the semantic ground truth.
  • SYNTACTIC — no toolchain set up → a tree-sitter parse (36 grammars bundled, no native build; 19 languages with tuned declaration extraction, the rest via a generic parse) still returns real declarations, not a grep.
  • FUZZY — you only remember what the code does → a concept dictionary mined from the repo's own identifiers + comments (no AI model, nothing uploaded).
  • SECTION — it's a doc or config, not code → Markdown / TOML / YAML / CSS / HTML addressed by heading.

The rungs aren't a one-time pick — they connect, and vts switches between them as it learns more. Start on FUZZY when you only know the intent; the moment concept_search surfaces a real name, vts climbs to EXACT to confirm it against the semantic index (and an exact search that misses drops back down to FUZZY). The hooks steer that hand-off in both directions, so "I don't know the name" turns into a precise, semantically-verified file:line instead of a dead end.

Graceful degradation — a locate never hard-errors on a cold or missing toolchain. When clangd is missing, cold, or still indexing, a locate degrades to the SYNTACTIC (tree-sitter) tier with a one-line advisory instead of erroring or blocking, then climbs to EXACT once clangd warms. If a committed .vts-index has drifted from the source, the answer is still served but labeled SYNTACTIC · STALE (with a climb: vts index hint). And on a cold locate over a large tree, vts auto-builds the .vts-index/ in the background so the next query (or the same session, once the build finishes) is instant — you never wait on it. So vts index below is best thought of as auto-built in the background; commit it to share, not a manual step you have to remember.

Every answer comes back capped to file:line (never source bodies) and carries a one-line completeness certificate naming the rung — so the model always knows whether it got the semantic truth or a fallback. On a 3-language, 150-file benchmark that's 87% fewer tokens than grep (~138× on a real Unreal Engine 5 tree). Underneath, four mechanisms make Claude actually use the ladder instead of reaching for grep:

Layer Effect
Rewrite/enforcement hook Covers four surfaces. Bash grep/rg/find -name over source → rewritten to the equivalent vts query in place (identifier → search_symbol, literal → search_text, find <dir> -namefind_files rooted at <dir>); ambiguous cases (pipeline, multi--name) block. Grep tool symbol hunt (bare identifier, ::/(/void·class regex, or a FooBar|BazQux CamelCase alternation) → blocked with a ready-to-use call; freeform/keyword alternations stay a warn. Glob tool concrete code file (*.cpp, Foo.h) → blocked toward find_files. Edit/MultiEdit that replaces or adds a whole declaration → a model-visible nudge toward the symbol-edit tools (replace_symbol_body/insert_symbol), escalating to a block on a safe insert after repeated ignores (VTS_EDIT_WARN, VTS_EDIT_BLOCK_AFTER); a sub-declaration tweak stays silent. Messages are agent-directed and i18n'd (EN/KO). Logs/.md/config pass through. Knobs: VTS_REWRITE=0, VTS_GREP_BLOCK=0, VTS_ENFORCE=0.
Token-capping core Turns LSP results into kind name @ file:line, caps, appends … N more. A refs-heavy result collapses to one row per file (Foo.cpp:42,88,120) with a shared dir prefix factored out once (VTS_COMPACT_RESULTS=0 restores per-line). A truncated find_files/search_text tees the full set to a recovery file.
Symbol-level editing replace_symbol_body/insert_*/safe_delete resolve a declaration by name via the outline and splice text at its exact span — preview by default, apply=true writes, safe_delete refuses while referenced. No whole-file Read into context.
Headless LSP client A fully-owned LSP client spawns the official engine over stdio. The project root is resolved per call (explicit projectPath → the file's enclosing project → the MCP workspace root), so one global server answers for every repo a session touches. Live backends are pooled and bounded (VTS_MAX_BACKENDS + idle reaper).
Savings + discover A local ledger records every search's tokens-saved (vts savings, with a 30-day graph). vts discover scans recent sessions for searches that bypassed the index — so you see the catch-rate, not just the wins.

Engine = official, glue = ours. clangd (LLVM) and Roslyn (Microsoft) do the analysis; this repo only writes the LSP↔MCP glue. No third-party MCP server runs over your source. Local-only, nothing uploaded.

Tools

All search/edit goes through an official language-server index — clangd (C/C++), Roslyn (C#/.NET), tsserver (JS/TS), pyright (Python) — and comes back as a compact, capped file:line list (never source bodies). MCP server vs-search; same tools as the vts CLI.

Search / navigate

Tool CLI Does
search_symbol vts symbol Find a symbol declaration by name/substring (semantic, not text).
find_references vts references Every call site of a symbol. Takes the name directly (symbol="FooBar") — the one to reach for when you change a function/type and must touch every use. detail=file/dir → a blast-radius summary (dependents grouped + ranked) instead of the per-line list. direction=callers/callees switches to a multi-hop call hierarchy (who transitively calls this = blast radius / what it calls) to depth hops — built on LSP callHierarchy, the semantic call graph, not a text scan. vts trace-calls = shorthand for references --direction callers.
read_symbol vts read-symbol Return the source of one named declaration (its span) — not the whole file. The read-side twin of replace_symbol_body: skip Read-ing a 700-line file to see one function. signatureOnly trims to the head.
goto_definition vts definition Jump to the definition at a position. kind= also does type_definition / implementation (concrete impls of an interface/virtual) / declaration.
hover vts hover Type/signature at a position.
document_symbols vts symbols Outline a file (classes/functions/types as file:line). scope=directory → a signatures-only repo skeleton of every code file under a dir (the shape of a module without Reading each file).
diagnostics vts diagnostics Compiler/linter errors + warnings as a token-capped file:line:col severity: message list — the compact stand-in for reading raw build output. One file by default; scope=directory scans the project.
find_files vts files Find files by name/glob — token-capped stand-in for find -name.
search_text vts text Raw text/regex search — capped stand-in for grep (path=/glob=/docs=true to target).
concept_search vts concept Fuzzy search for a concept you can't name ("auth login flow") — mines a dictionary from the repo's own identifier+comment co-occurrence (no embeddings, nothing sent) and ranks declarations; --flow traces the top hit's call graph.

Edit (symbol-level — name it, don't line-count) — preview by default, apply=true writes.

Tool CLI Does
rename vts rename Semantic project-wide rename (every reference, not a text sed).
replace_symbol_body vts replace-symbol Replace a whole declaration (signature + body) by name — the index supplies the span.
insert_symbol vts insert Insert text next to a declaration — position=after (default, e.g. a sibling method) or before (e.g. an import/attribute).
safe_delete vts safe-delete Delete a declaration — refuses while it's still referenced unless force=true.

Docs & config too (structure tier). Point any of document_symbols / read_symbol / replace_symbol_body / insert_symbol / safe_delete at a Markdown / AsciiDoc / reST / TOML / INI / YAML / JSON / text file and the "symbol" is a section (heading, [section], or key): outline a 2000-line CLAUDE.md in ~30 lines, read or replace one ## Section by name without Reading the whole file. No language server, no new tools — same token-safer move, for documents.

Admin / meta — one MCP tool vts_admin {op, params} (folded to keep the per-session tool-definition cost small; the CLI keeps the bare subcommands):

op CLI Does
git / p4 vts git / vts p4 Run a read-only git status/log/diff or Perforce opened/status/changes/reconcile, output grouped/deduped/capped. Mutating subcommands refused.
setup / config vts setup / vts config Configure / show settings (projectPath, backend, maxResults, clangdCmd, genCompileDb).
savings / savings_reset vts savings Token-savings ledger (graph/daily/history) / clear it.
warmup vts warmup Pre-build the language-server index.
preindex vts preindex Build the clangd index ahead of the first query (big-tree cold start).
index vts index Build/refresh the committable .vts-index/ cold-start symbol index (index --status to show it).
discover vts discover Find code searches that bypassed vts (missed savings).
gen_compile_db vts gen-compile-db Generate the Unreal clangd compile DB (UBT).

e.g. vts_admin {op:"git", params:{argv:["status","-s"]}}. Or hand a whole "where is X / what calls Y / find file W" lookup to the code-locator subagent — it searches in its own context and returns only the file:line table.

Dashboard — vts serve. A local, interactive view of what vts knows + how much it saved: the savings trend, language mix, per-tool savings, and an interactive 3D graph (WebGL / Three.js) with two modes — the include graph (files sized by include fan-in) and an on-demand call graph (type a symbol → its transitive callers/callees, traced live through LSP callHierarchy — no persistent index; shows call counts per node/edge). Nodes are laid out on a spherical shell (so they spread out, not clump); drag/WASD to orbit, wheel/+-- to zoom, R to fit, hover for file:line. Symbol search has live autocomplete (/symbols); color by connected-component groups · repo (which repository each node is from, with a legend) · heat; click a node to drill into its group (Esc/Backspace to pop out); a focus/maximize toggle, a highlight filter, and a node/edge metrics overlay.

Easiest via the slash commands: /vs-token-safer:viz (open) and /vs-token-safer:viz-stop (close). Or the CLI:

vts serve --open     # → http://127.0.0.1:8731/  (launches the browser; --port N to change)
vts serve --stop     # stop it (or Ctrl-C the process)

It's 127.0.0.1-only and serves a fully self-contained page — CSS/JS inlined and Three.js vendored locally (server/vendor/, served same-origin, never a CDN), so nothing leaves the machine; it renders with the network unplugged. Same trust model as the rest of vts. Built on Node's stdlib http (no web-framework dependency), and it runs only when you invoke it — the MCP server never starts it, so the steady-state package stays a thin stdio client. The 3D graph caps at VTS_VIZ_MAX_NODES (200) for smoothness.

$ vts symbol --q createSession --projectPath ./app
3 symbol(s) matching "createSession" (backend: typescript, root: ./app):
func createSession (in AuthService)  @ app/src/auth/session.ts:142
method createSessionToken (in TokenStore)  @ app/src/auth/token.ts:88
func createSessionCookie  @ app/src/http/cookies.ts:31

✓ Saved ~4,200 tokens here (96.8% / 31× smaller than the raw index response).

The two plugins

Plugin Does Needs
vs-token-safer (this page) Force code search/edit through the clangd/Roslyn/tsserver/pyright index over Bash grep, token-capped to file:line Node + a language server (clangd / Roslyn you install; JS/TS + Python auto). No IDE.
gamedev-log-analyzer Parse/dedup/classify huge Unreal/Unity/Godot/MSVC-UBT logs, search + diff + extract scalars Node only

vs-token-safer declares gamedev-log-analyzer as a dependency, so one install pulls in both. Used together: the log analyzer emits file:line per entry → hand it to goto_definition/find_references to open the code, without grepping or dumping the raw log. The handoff runs in reverse too — a code search aimed at a log (Logs/, .log/.jsonl) points you back at gamedev-log instead of an empty result.

Combined savings (measured) Bash / raw Plugin Reduction
Symbol search on a real UE5 repo (FGameplayTag) ~282,194 tok ~2,048 tok ~99.3% (~138×)
Raw index response → capped list (eval, 1,000 symbols) ~57,308 tok ~1,515 tok ~97.4%
Read a ~1 MB editor log (summary) ~267,000 tok ~130 tok ~99.95%

Companion: drive vs-search with a local model

vts-local-orchestrator — a separate, optional companion that lets any local LLM (Ollama, full-GPU — model-agnostic, default gemma4:e4b, chosen by benchmark) drive these same vs-search tools. Claude delegates cheap, high-volume code-location to the free local model and receives only the compact file:line answer, so the raw search output never enters Claude's context.

  • CLI (qvts) + a live web dashboard with a 3-way token-savings panel (this method vs CC-using-vs-search vs CC-using-grep), plus a delegation-routing skill for Claude Code.
  • Token-savers: a persistent savings ledger (qvts --savings), a locate cache (zero-cost repeats), batch delegation (--batch), and terse repo-relative file:line output.
  • Fully local — nothing transmitted off-machine, same as this plugin's charter.

Install: clone it next to this repo, npm install, then bash setup-macos.sh (Windows: setup.ps1). See that repo's README for the pipeline, the model benchmark, and the savings model.

Performance

The token win scales with repo size and is language-agnostic — a TypeScript service or a Python codebase sees the same shape (the deterministic 3-language benchmark cuts ~87%; details in BENCHMARK.md). The most extreme case we've measured is a large Unreal Engine 5 project: finding one public engine symbol (FGameplayTag) via Bash grep-and-paste vs this plugin. No project source is reproduced, only aggregate counts.

Bash grep-and-paste (whole repo) Plugin (clangd index, capped)
What the model receives 5,654 lines / 1,010 files 47 semantic decls (file:line)
Tokens to the model ~282,194 ~2,048

~99.3% fewer (~138×). grep returns the full text of every matching line and matches by text (comments, strings, unrelated identifiers); the plugin returns one file:line per semantic hit, capped. The mock-LSP eval (node eval/run.mjs, no toolchain) gates this on every commit: ~57,308 → ~1,515 tok = 97.4% (92 checks).

Syntactic tier vs clangd — same location, no cold wait. On a second real UE5 game module (3,143 files, 133,890 symbols; aggregate counts only), the tree-sitter tier returned the same file:line as clangd's EXACT answer on every sampled symbol (line delta 0), in ~240 ms vs clangd's ~127 s cold-to-first-answer — a ~530× gap the semantic tier doesn't close for a locate. That's why the ladder answers tree-sitter-first while clangd warms, then climbs to EXACT. One-time index build ~62 s; details in BENCHMARK.md.

  • Recall: the plugin returns the top N (cap), not every textual occurrence — the withheld tail is mostly comments/includes/substring noise. Need exhaustive? Raise maxResults, or use grep.
  • Precision: grep matches every substring (Foo also hits FooBar); the index returns distinct semantic declarations.

So for navigation (a definition plus representative usages) the plugin is both more accurate and far cheaper. For an exhaustive occurrence audit, raise the cap or fall back to grep on purpose.

clangd indexes asynchronously, so the first search pays a one-time warm-up. vts handles this like an IDE: the MCP server pre-warms at boot (VTS_PREWARM, on when projectPath is set) and keeps the client cached for the session, so you pay it once. vts warmup builds the on-disk index up front (CLI/CI), and VTS_CLANGD_REMOTE points clangd at a shared prebuilt index server.

Ordering matters: clangd boosts the priority of files you open, so vts warms query-history-first, then what you're editing now (git status / p4 opened), then git-log recency, then include-centrality, then mtime. On a huge tree you can only warm a small slice, so this is what makes the warm window contain what you search for. Measured lift (node eval/bench-hitrate.mjs, 2,000 files):

warm-up cap arbitrary order history-ordered lift
3% of files 1.5% 54.3% 36×
5% 7.8% 56.5% 7.3×
10% 11.3% 62.5% 5.6×
20% 24.8% 68.5% 2.8×
50% 46.3% 80.5% 1.7×

The smaller the slice you can afford to warm, the bigger the win.

On a huge monorepo (e.g. a full Unreal Engine source tree, ~26k translation units) the cold index is the one real cost. Two opt-in levers cut it — both stay local, nothing is transmitted:

1. Scope — index a subtree, not the whole tree. See what you'd scope, then set it:

vts scope --projectPath /path/to/UE        # shows current scope, kept/total TUs, and top-level dirs to pick
vts setup --scope "MyGame,Plugins"         # persist it (or set VTS_SCOPE="MyGame,Plugins"); then reload/restart

clangd then indexes only the in-scope translation units (live UE5: MyGame → 3,377 of 26,488 TUs, 13%), and every backend's warm-up is scoped with it. No scope set = whole-tree behavior, unchanged.

2. Pre-index — build the index ahead of the first query.

vts preindex --projectPath /path/to/UE     # honors the scope above

With the full LLVM release installed (it bundles clangd-indexer next to clangd), this builds a monolithic static index offline and clangd loads it via --index-file — a local file, no server — so the first query is instant instead of waiting on the lazy background crawl. Without clangd-indexer it falls back to a warm pass (and tells you to install full LLVM). Override the binary with VTS_CLANGD_INDEXER_CMD.

clangd background indexing scales by tree size (RAM/CPU tiering). To keep a huge tree from pinning your machine, background indexing is auto-tiered by translation-unit count: FULL at ≤ 4,000 TUs (normal priority), SAFE at 4k–15k (idle CPU priority, throttled), and OFF above 15,000 TUs (no whole-tree crawl — project-wide search_symbol/find_references fall to the syntactic tier, while single-file read_symbol/hover/outline stay cheap). A one-line advisory names the tier so you know search degraded on purpose; the bounded fix is to vts setup --scope <module> then vts preindex. Knobs: VTS_CLANGD_BG_INDEX (force full/off), VTS_CLANGD_BG_INDEX_SOFT_TUS (4000), VTS_CLANGD_BG_INDEX_HARD_TUS (15000), VTS_CLANGD_HUGE_WARM_CAP (8).

3. Zero-setup tier — works before any toolchain, on any repo. No compile DB, no language server, no wait? vts still answers search_symbol from a tree-sitter parse (an official standard parser; 36 grammars bundled as wasm — no native build; 19 languages with tuned declaration extraction, the rest via a generic parse) — real declarations, not a usage grep, in the same token-capped file:line shape. Make it instant and shareable by committing an index:

vts index --projectPath /path/to/repo      # writes .vts-index/symbols.jsonl (commit it!)
vts index --status                          # show the current committed index

You rarely have to run this by hand: on a cold locate over a large tree vts auto-builds .vts-index/ in the background (VTS_AUTO_INDEX, on by default) so a later query is instant. .vts-index/symbols.jsonl is plain, git-committable, and portable — commit it so teammates (and your own cold starts) get instant symbol search with zero setup. A language server, once it indexes, automatically supersedes it (the syntactic tier locates decls; the LSP adds reference/overload/type resolution on top). Benchmark (150-file symbol search): grep 4917 → tree-sitter 53 tokens = 98.9%, no toolchain.

Do existing users need to re-run setup? For default (whole-tree) behavior, no — just update the plugin and /reload-plugins. You only run vts setup --scope … (once) if you want to opt into scoping; the clangd-indexer path needs no vts setup at all (it's auto-detected — you just need full LLVM installed).

Each search records the tokens it saved vs forwarding the raw index response. Check it with /vs-token-safer:savings, vts savings (--graph/--daily/--history), or reset via vts savings-reset.

vs-token-safer savings (local, 1 search(es))
  total saved: ~4,200 tokens vs forwarding raw index responses
  raw → output: 4,340 → 140 tok (~31× smaller)
  est. value: ~$0.01 (@ $3/Mtok — set VTS_USD_PER_MTOK)

That's the caught side. vts discover scans recent sessions for searches that went around the index:

$ vts discover --since 1
  86 code search(es) bypassed vts (Grep×48, Glob×18, grep×12, find×8)
  catch-rate: ~770,333 tok caught (via vts) vs ~28,692 still bypassing → 96.4% routed through vts

(Searches the hook blocked don't count as bypasses.) discover is local and read-only — it reads transcript metadata and tool I/O sizes, never ships any of it anywhere. --learn feeds the files past searches hit into the warm-up set, so each session leaves the index warmer.

Prerequisites (details)

  • Node.js ≥ 18 on PATH.
  • C/C++ → clangd ≥ 22 (releases). The clangd 19.1.x bundled with Visual Studio deadlocks indexing real Unreal TUs in server mode; vts warns on an older one. Needs a compile_commands.json. Prefer the full LLVM release — it bundles clangd-indexer alongside clangd, which vts preindex uses for an instant static index (see Big trees: scope & pre-index).
  • C#/.NET → a Roslyn LSP. Install the VS Code C# extension (ms-dotnettools.csharp) — vts auto-detects Microsoft.CodeAnalysis.LanguageServer and its runtime from the bundle. Fallback: dotnet tool install --global csharp-ls. Needs a .sln/.csproj.
  • JS/TS → typescript-language-server, Python → pyright. Ship as plugin deps, install automatically on the first session (one-time ~50 MB; JS/TS wants Node 20+, skipped on 18).
  • Mixed repo? A query that targets a file uses that file's own language backend — a .py/.ts inside a C++/C# (clangd/roslyn-rooted) tree gets pyright/typescript automatically, so vts works in a UE tree with a Python tooling dir without a manual backend=. This even overrides a pinned backend / VTS_BACKEND when they conflict: one global server serves every repo you touch, so a backend:"clangd" set for a C++ project never sends another repo's .js/.cs/.py to clangd (which would answer -32001 invalid AST). A query with no file target (e.g. search_symbol by name) keeps the pinned backend.

clangd needs a compile database:

  • Unreal: <UE>/Engine/Build/BatchFiles/RunUBT … -mode=GenerateClangDatabase. If targets build with clang-cl, add -Compiler=VisualCpp or it fails clang-toolchain validation.
  • CMake: -DCMAKE_EXPORT_COMPILE_COMMANDS=ON.

No compile DB yet? You still get answers — search_symbol falls back to a bounded literal text search, labeled as such, and the first result carries a one-time advisory. The one-command fix: vts_admin {op:"gen_compile_db"} (CLI vts gen-compile-db) assembles the exact UBT command (finds the .uproject, derives the <Name>Editor target, locates the engine, adds -Compiler=VisualCpp). Dry-run by default; apply=true runs UBT and parks the DB outside the source tree (~/.vs-token-safer/db/<project>, with clangd's .cache/ next to it, so git/p4 reconcile never see an artifact). inTree=true keeps the classic project-root layout, protected by a VCS-ignore guard.

Not published to npm — install vts from a clone:

git clone https://github.com/JSungMin/vs-token-safer
cd vs-token-safer/server && npm install && npm link   # provides `vts`
# or run directly: node /path/to/vs-token-safer/server/cli.js symbol --q SpawnActor --projectPath /path/to/proj

Configuration

Settings live in ~/.vs-token-safer/config.json (read at startup — /reload-plugins after changes). Configure via /vs-token-safer:setup (guided), vts_admin {op:"setup"} / {op:"config"}, or vts setup --projectPath <root> --backend clangd. Backend auto-detects from the root. Precedence: env (VTS_*) > config file > default.

Updating: Claude Code caches the marketplace, so new commits aren't auto-fetched:

/plugin marketplace update vs-token-safer
/plugin update vs-token-safer
/reload-plugins
# then RESTART the session — REQUIRED.

⚠️ A new version only takes full effect after a session restart. /reload-plugins updates hooks/commands/skills, but the running vs-search MCP server serves the old tool code until you quit and reopen. Version history: Releases.

Precedence: VTS_* env > ~/.vs-token-safer/config.json > default.

Config key Env var Default Meaning
projectPath VTS_PROJECT_PATH cwd Project root (where the compile DB / .sln lives).
backend VTS_BACKEND auto clangd | roslyn | typescript | pyright.
maxResults VTS_MAX_RESULTS 60 Cap on returned file:line locations.
VTS_COMPACT_RESULTS 1 0 restores one-location-per-line output.
VTS_MAX_BACKENDS 2 Max concurrently-live language servers (LRU-evict past the cap).
VTS_BACKEND_IDLE_MS 300000 Idle language server shut down after this (0 = off).
clangdCmd VTS_CLANGD_CMD / VTS_CLANGD_ARGS clangd clangd executable (persist via vts setup --clangdCmd <path> — VS-bundled 19.1.x deadlocks UE, use ≥ 22) / args.
VTS_ROSLYN_DLL auto Path to a specific Microsoft.CodeAnalysis.LanguageServer.dll.
VTS_ROSLYN_CMD / VTS_ROSLYN_ARGS auto → csharp-ls Override the C# LSP.
VTS_TS_CMD / VTS_PY_CMD (+ _ARGS) bundled Override the JS/TS / Python LSP.
VTS_TS_OPEN_CAP / VTS_PY_OPEN_CAP 60 Files the JS/TS / Python warm-up opens.
VTS_LSP_TIMEOUT_MS 30000 Per-request LSP timeout. Raise for a cold, large index.
VTS_LSP_INDEX_WAIT_MS 120000 How long the clangd warm-up waits for background-index completion.
VTS_CLANGD_OPEN_CAP 100 Files the cold warm-up opens to prime clangd.
VTS_CLANGD_WARM_CAP_PERSISTED 8 Open cap when a persisted .cache/clangd index exists.
VTS_CLANGD_PERSISTED_WAIT_MS 60000 Cap on how long a query polls a still-loading persisted index.
VTS_CLANGD_PERSISTED_FLOOR_MS 3000 Brief floor before the first query starts polling.
VTS_CLANGD_INDEX_PRIORITY normal clangd background-index priority (background = idle-CPU-only).
VTS_CLANGD_JOBS cores-1 clangd async/index workers (-j).
VTS_CLANGD_BG_INDEX auto Force clangd background indexing full / off (default: auto-tiered by tree size — see Big trees).
VTS_CLANGD_BG_INDEX_SOFT_TUS 4000 ≤ this many TUs → FULL background index; above it → SAFE (idle-priority, throttled).
VTS_CLANGD_BG_INDEX_HARD_TUS 15000 Above this many TUs → background index OFF (no whole-tree crawl; falls to the syntactic tier).
VTS_CLANGD_HUGE_WARM_CAP 8 Warm-up open-cap in SAFE/OFF mode (a large tree's warm-up is itself a parse spike).
VTS_PREWARM on (if projectPath) MCP server pre-warms at boot; 0 disables.
VTS_PREWARM_BACKENDS auto auto / all / comma list — which backends to pre-warm.
VTS_WARM_CAP_RATIO / VTS_WARM_CAP_MAX 0.1 / 300 Adaptive warm-up open-cap (fraction of a language's files, clamped).
VTS_CLANGD_REMOTE Address of a shared/prebuilt clangd index server.
VTS_QUERY_HISTORY / VTS_INCLUDE_GRAPH ~/.vs-token-safer/… Warm-up ordering caches.
VTS_CENTRALITY_MAX / VTS_CENTRALITY_BUDGET_MS 20000 / 400 Include-centrality scan bounds (0 disables / cache-only).
VTS_ENFORCE 1 0 lets Bash code-grep through (escape hatch).
VTS_REWRITE 1 0 makes the hook block a Bash code-grep instead of rewriting it.
VTS_GREP_BLOCK 1 0 reverts the Grep/Glob tool escalation from block to warn-only.
VTS_EDIT_STEER 1 0 hides the one-line hint (on a focused search_symbol/goto_definition result) pointing at the symbol-edit tools. VTS_EDIT_STEER_MAX (10) caps the result size that gets it.
VTS_EDIT_WARN 1 0 silences the model-visible nudge when a built-in Edit/MultiEdit replaces or adds a whole declaration (it points at replace_symbol_body / insert_symbol). Sub-declaration tweaks are never nudged.
VTS_TEXT_STEER 1 0 hides the one-line hint appended to a search_text result whose query is really a symbol/class usage hunt (a Foo<Bar> template arg, :: scope, or CamelCase/snake identifier) — it points at find_references / search_symbol, which are semantic and complete (no 4s time-box). Fires only when the scan was truncated or the query carries a <>/:: cue.
VTS_EDIT_BLOCK_AFTER 0 (off) Opt-in. Set ≥1 to escalate the warn to a one-time block on a safe insert after that many consecutive ignored nudges (then it resets — fire-once, not a wall). Default off: a persistent block trapped the agent (it fought the wall with Edit retries instead of switching). A replace always stays a warn; VTS_GREP_BLOCK=0 also holds it to warn.
VTS_EXCLUDE_COMMANDS Comma list of executables to exempt (also excludeCommands in config).
VTS_COMPACT_VCS 1 0 stops rerouting read-only git/p4 to the compacted wrapper.
lang VTS_LANG auto Hook message language: ko / en (auto-detects from OS locale).
VTS_TEE / VTS_TEE_DIR truncate Recovery file for a capped find_files/search_text result.
VTS_USD_PER_MTOK 3 $/Mtok rate for the estimated-value line (informational).
starMin VTS_STAR_MIN 50000 Cumulative-saving threshold (tokens) past which vts savings appends a one-line ⭐ pointer.
VTS_STAR_NUDGE 1 0 hides the ⭐ line. Shown ONLY in the manual vts savings report (never in the search/edit flow); pure, no network / no star-status check.
VTS_SAVINGS_GRAPH 1 vts savings shows the 30-day graph by default; 0 (or graph:false) omits it for a terse report.
VTS_P4_EDIT 1 A symbol-edit / rename apply auto-runs p4 edit on a read-only (Perforce) file before writing — symbol edits write via the server, bypassing any built-in Edit/Write p4 hook. Only fires on read-only files (a writable/git repo never invokes p4); 0 disables.
VTS_P4_CMD p4 Perforce CLI used for the auto-checkout above (VTS_P4_TIMEOUT_MS, default 15000, caps it).
VTS_INDEX_ADVISORY 1 On an EMPTY clangd result, append a why-advisory: the file isn't in compile_commands.json, or the background index is only N% built. 0 silences it.
VTS_CLAUDE_PROJECTS `