Borrowing it
Nothing to install: this file belongs to JSungMin/vs-token-safer. Take a copy, put it at the same path in your own repository, and replace the rules that are about this project with yours.
curl -O https://raw.githubusercontent.com/JSungMin/vs-token-safer/main/CLAUDE.mdgit clone --depth 1 https://github.com/JSungMin/vs-token-saferWrote this? Show the measurements
A badge with what this costs and how it scanned, read live from this page, so it follows the numbers instead of freezing them. Markdown for a README, HTML for a documentation site or a project page.
[](https://agentmods.dev/instructions/jsungmin/vs-token-safer/claude-md)<a href="https://agentmods.dev/instructions/jsungmin/vs-token-safer/claude-md"><img src="https://agentmods.dev/badge/instructions/jsungmin/vs-token-safer/claude-md.svg" alt="Measured on agentmods" height="20"></a>What it costs to keep this loaded
Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.
| Model | Per session | Once invoked |
|---|---|---|
| Fable 5.1 | $0.19098 | $0.19098 |
| Opus 5 | $0.09549 | $0.09549 |
| Sonnet 5 | $0.03820 | $0.03820 |
| Haiku 4.5 | $0.01910 | $0.01910 |
Grade A, and why
vs-token-safer CLAUDE.md scanned grade A with 1 finding against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 3d ago.
A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.
Runs shell commandslowCapability
Expected in a hook, worth knowing in a rule or an instructions file.
- **No console windows (Windows).** EVERY `spawn`/`execFile*`/`execSync` in `server/` MUST pass `windowsHide: true`. How it starts
The opening of the file, as written. The whole thing — 662 lines — stays where its author put it; the contents beside it link to each section on GitHub.
vs-token-safer — Claude rules
Force code search through an official language server's index (clangd for C++, a Roslyn-based C# LSP)
instead of Bash grep, and token-cap the result to a compact file:line list. The
Visual-Studio / IDE-agnostic sibling of rider-mcp-enforcer. Local-only. Ships as MCP server + CLI
(vts). npm package + plugin name: vs-token-safer.
First, orient (every session)
- Read this file, then
node eval/run.mjs— must printEVAL PASSED(41/41) before you change anything. - Resume context lives in: this file · the wiki (
wiki_query "vs-token-safer", pages under.omc/wiki/) · memory anchorproject-vs-token-safer. The wiki Status and TODO page is the live checklist.
What's true
- Engine = official, glue = ours. clangd (LLVM) / Roslyn (MS) do the analysis; we only write the LSP↔MCP glue. Never reuse a 3rd-party MCP server over source; never reimplement Roslyn.
- Local-only, zero transmission. Same trust model as the other plugins. The token-cap returns
file:line(no bodies) → less raw source reaches the model than grep-and-paste. - Async.
runToolis async (LSP is async). MCP/CLI adapters mustawaitanddisposeClients(). - Naming umbrella. "token-safer" is deliberately broad — more token-saving features/backends can be added under this name beyond C++/C# search.
Layout
server/lsp.js— generic LSP client (JSON-RPC/stdio). The one new, careful piece.didOpenis open-or-refresh: first call →didOpen(v1), a re-call on an already-open doc →didChange(bumped version, current disk text) so a file changed after warm-up isn't answered from a stale buffer; a since-deleted file →didClose. Position tools re-calldidOpenbefore each query, so hover/goto/ outline/rename always re-read the file. The LSP engine keeps UNOPENED files fresh itself (clangd file-watch + background re-index); our warmset caches self-invalidate (include-graph by mtime+size composite key + an FNV-1a content hash [warmset.js fnv1a, zero-dep — codebase-memory-mcp XXH3 parity; reuses cached includes when bytes are unchanged despite mtime/size jitter, catches a real change a mtime-only key would miss], query-history by re-record;_censusCacheis process-lifetime → restart/re-setup to refresh). LSP-spec conformance: server→client requests get shape-correct replies (_serverRequestReply:workspace/configuration→array,workspace/applyEdit→{applied:false},window/showDocument→{success:false}, void reqs→null, unknown→MethodNotFound -32601); a timed-out request sends$/cancelRequest; client declaressynchronization+workspace.configurationcapabilities.server/scope.js— INDEXING SCOPE (cold-latency attack): index a SUBTREE not the whole monorepo. Configscope/VTS_SCOPE(comma-list of dirs rel to root);vts setup --scope "MyGame,Plugins"persists it.scopeDirs/inScope/scopedCdb(writes a FILTERED compile_commands.json of only in-scope TUs to the out-of-tree dir → clangd--compile-commands-dirpoints there → it background-indexes far fewer TUs; live UE5:VTS_SCOPE=MyGame= 3,377 of 26,488 TUs (13%), ~7.8× cut) /scopeStats. UNIVERSAL: every backend's afterInit warm walk is scope-filtered too (no tsconfig/sln edit).backends/index.jseffectiveCdbDir(root)= scoped CDB when scope set, elseresolveCdbDir;scopeDirsFor(root). clangd STATIC PREINDEX:clangd-indexer(full LLVM release bundles it;VTS_CLANGD_INDEXER_CMD/next-to-clangd/ PATH) builds a monolithic .idx over the scoped CDB → clangd loads it via--index-file(LOCAL file, no remote server) for an instant project-wide index;buildStaticIndex/hasClangdIndexer/staticIndexPath, absent → warm-pass fallback + advisory to install full LLVM. Opsvts_scope(show scope + TU stats + top-level dirs) /vts_preindex(build ahead: static index if indexer present, else warm pass); CLIvts scope/vts preindex, folded intovts_admin. Eval guard 79. Env:VTS_SCOPE,VTS_CLANGD_INDEXER_CMD,VTS_INDEXER_TIMEOUT_MS(1800000). PREINDEX GATING:vts preindexDEFAULT = fast scoped background warm; the clangd-indexer STATIC--index-file(parses every in-scope TU, tens of min on a big scope) is OPT-IN viastatic=true/--static— never auto-run just because the indexer exists (an existingvts-static.idxis still auto-loaded, cheap). within-scope cert:completenessCert({scoped})qualifies a semantic COMPLETE/0 as "within the configured indexing scope" (search_symbol/find_references), andclangdIndexAdvisorycounts TUs/shards against the EFFECTIVE (scoped) CDB.server/policy.js— UNIFIED TOOL-ROUTING POLICY (vts COMPLEMENTS Claude Code, not competes).shouldSuppressSteer(file)stays SILENT where CC-native is clearly better — generated/build-output paths (Intermediate|Binaries|Saved| DerivedDataCache|node_modules|build|dist|out|obj|.git,*.generated.*/*.g.cs/*.min.js); wired into the edit-steer hook (a whole-decl edit there isn't nagged AND isn't counted against adoption).VTS_SUPPRESS=0off.routingDigest()= the SINGLE SessionStart message: a when-to-use-what decision tree (semantic→vts, whole-decl→ symbol-edit, doc/just-edited/sub-decl→CC-native Read/Grep/Edit, big-tree→scope+preindex) + live adoption posture + adaptive-controller state — replaces the adoption-only nudge inhooks/edit-report.js(one coherent policy, not scattered nudges). Eval guard 80.server/treesitter.js+server/symindex.js— SYNTACTIC TIER (zero-setup fallback BETWEEN the semantic LSP and the literal text scan; the answer to "tree-sitter/embedding rivals are popular because they need no toolchain").treesitter.js: lazy wasm tree-sitter (web-tree-sitterruntime +tree-sitter-wasmsprebuilt grammars, both optionalDependencies — NO native build, Windows-safe; resolved via the sdk.js-style createRequire anchors).tsFileSymbols(abs)walks an AST → real DECLARATIONS (name+kind+line). 36 grammars ship; decl extraction is configured for ~17 languages — 10 via a hand-tuned node-type walk (C/C++/C#/JS/TS/Py/Go/ Java/Rust/Ruby;nameOfdrills C/C++ declarator chains) + 7 via canonicalserver/tags/<grammar>.scmTAGS QUERIES (php/swift/kotlin/scala/dart/zig/bash) — a grammar with neither degrades to a GENERIC walk, never dark. The tags tier is the EXTENSION POINT: aTAGSsentinel config inEXT_MAP+ a.scmwith canonical@definition.<kind>/@name/@reference.*captures = a new language with NO JS (defTagsQueryFor/extractTagDefs/extractTagRefs, validated against the bundled grammar; query-construct failure → graceful fallback). References too: TAGS langs read@reference.*from the same .scm, others use inlineREF_QUERIES.tsSearchSymbols(root,q)ranks exact-before-substring across a scope (time+file-box). Charter-pure: tree-sitter is an OFFICIAL standard parser (GitHub/neovim), not a reimplement (the tags-query DSL is its own official interface, glue = ours); output stays token-capped file:line; nothing transmitted; SYNTACTIC means it locates decls but does NOT resolve refs/overloads/types (the LSP's job — so it's BELOW the semantic tier).symindex.js: COMMITTABLE index (Codeix-inspired) —vts indexwrites a portable, git-committable, team-shareable.vts-index/symbols.jsonl(one record/decl, paths RELATIVE) via tree-sitter;searchSymIndexanswerssearch_symbolINSTANTLY on a toolchain-less machine or before clangd's index builds (the 369s→51s cold problem). INCREMENTAL rebuild: the header carries a per-file manifesth:{rel:{mt,sz,h}}(mtime+size fast-path →warmset.fnv1acontent hash); a rebuild REUSES unchanged files verbatim (no read, no re-parse — parsing is the cost), reads+hashes only stat-changed files, re-parses only on a real content change, drops deleted files. Sovts indexafter editing a few files re-parses only those (returnsreused/reparsed; shown in the op output). core.jssyntacticSymbols(committed index → live tree-sitter, else literal scan) feeds the search_symbol no-backend / empty-result branches;completenessCert({syntactic})labels it. Opvts_index{status}(CLIvts index [--status], folded intovts_admin). Eval guard 81; benchmark arm C (zero-setup: 150-file symbol search grep 4917 → tree-sitter 53 tok = 98.9%, no toolchain).server/concept.js— FUZZY retrieval WITHOUT embeddings (approach "B"; the charter-pure answer to Code Context Engine's exact-vs-fuzzy critique — "how does the auth flow work" when you can't name the symbol). THE REPO IS ITS OWN THESAURUS: identifiers + the comments beside them are a distributional signal already in the source — tokens that NAME THE SAME THING co-occur. PURE/zero-dep module:splitIdent/tokenize(CamelCase/snake/digit split, drop digit+stop+len1),tokMatch(exact 1.0 / prefix≥4 0.7 — no stemmer),buildConceptModel(units,{maxUnitTokens:14})(df + co-occurrence over per-decl token bags = name subtokens + leading-docstring subtokens; the UNIT must be TIGHT — a long header comment attached to the first decl makes a giant unit where everything co-occurs → junk, so cap it),assoc(PMI-lite c·N/(df·df)),idf,expandQuery({k,minAssoc:1.5,minCooc:2})(gate single-shot noise: cooc≥2 AND df≥2),scoreSymbol(enriched·idf·bestMatch + comment channel ×0.5). HOT MCP toolconcept_search(core.js: tokenize q →conceptIndexFor(root)[cached tree-sittertsFileDeclDocswalk, scope-filtered, bounded] → expand → score [kind weight demotes const/var locals] → top-N with a relative floorVTS_CONCEPT_FLOOR0.2 +VTS_CONCEPT_MAX15;flow=trueexpands the top seed along the call graph via find_references direction). CLIvts concept --q "auth login flow" [--flow]. HONEST: A(subtoken)+D(comment) reliable, B(co-occurrence) recovers domain synonyms when vocab clusters (compile database→clang/ubt/generate) but noisy on cross-cutting generics; pure- synonym-no-lexical-bridge residual genuinely needs embeddings (stated).treesitter.js tsFileDeclDocs= decl + attached leading comment (gap≤3, skip header blocks ≥4 lines, cap 200ch) feeds the concept units. NO embeddings, nothing transmitted, output token-capped file:line. Eval guard 83; follow-up paperpaper/fuzzy-concept- dictionary.tex(companion to the Token-Safer paper, motivated by the CCE correspondence). Env:VTS_CONCEPT_*. SCORING = 3 deterministic channels (name > path > comment;scoreSymbol) + 2nd-pass structural-proximity boosts off TWO neighbour graphs (same LARGER anchor gate, reranks the matched set, never invents a match): (a) import-graph (importSpecifiers→ within-repo basename adjacency; lifted byVTS_CONCEPT_IMPORT_FACTOR0.3 × the neighbour's score) and (b) git CO-CHANGE (server/cochange.jscochangeNeighbors— files committed together in the lastVTS_COCHANGE_MAX_COMMITS500 commits are coupled; mega-commits >VTS_COCHANGE_MAX_FILES_PER_COMMIT30 skipped as merge/format noise; ≥VTS_COCHANGE_MIN_WEIGHT2 co-commits to count; lifted byVTS_CONCEPT_COCHANGE_FACTOR0.25, BELOW imports — a softer signal). Co-change is the M1 migration toward the Cursor/Augment "what clusters semantically" axis, embedding-free: the repo's own history is the cluster signal. PUREparseCoChange(git-log text → pair weights, both directions) + thingit logread, cached inconceptIndexFor; absent git → empty map (boost no-ops).VTS_CONCEPT_COCHANGE=0off. Eval guard 89; live-verified on the vts repo (core.js → 49 co-change neighbours: eval/run.mjs, policy.js, …). A click-feedback loop was CRITIC-REJECTED (self-confirming via position bias, non-deterministic, unmeasurable, erodes inspectability); the charter-pure adaptation paths are these code-mined structural signals + a committable synonym file (DONE): a team-curated, git-committable<root>/.vts-index/concept-synonyms.json({ "term": ["syn", …] }) —concept.js parseSynonyms(tokenises keys+values) feedsexpandQuery({synonyms}), injecting a curated bridge at weight 0.95 (below an exact 1.0, above a mined neighbour); additive (absent/malformed → mined model alone), inspectable, deterministic, no drift. Eval guard 83. TWO PAPER MIGRATIONS (charter-pure, no embeddings): (1) LARGER confidence-gate (concept.js anchorConfident/VTS_CONCEPT_ANCHOR_MIN0.5) — the import-graph proximity boost fires ONLY from high-confidence anchors (a neighbour lifts a symbol only if its own base clearsratio×topBase), so a weak/cross-cutting neighbour can't drag its imports up (live-verified: cross-repo gamedev/uiLangnoise dropped); (2) RM3 PRF (concept.js prfTerms/VTS_CONCEPT_PRF*) — a 2nd-pass mines feedback terms from the TOP-k pass-1 results' OWN vocabulary (name+comment subtokens, ≥2-doc consensus, idf-ranked, capped) and re-scores, bridging a synonym the query missed ("warm"→warming/dominant, "reachable"→fixpoint/cascades); the climb SEED stays the PRE-PRF intrinsic exact match (base0) so PRF widens recall without drifting the seed. Eval guard 83 (#c anchor, #d prf). PRECISION-LADDER NAV (VTS_CONCEPT_STEER): search_symbol(exact)+multi-word-miss → steers DOWN to concept_search; concept_search → points UP to find_references/goto. See [[identity-and-roadmap]].server/textstruct.js— STRUCTURE tier for prose/config files (the naming-umbrella extension: token-safer for DOCS, not just code). A text file's "symbol tree" = its SECTION hierarchy, so the EXISTING name-addressed tools work on it:document_symbols→ token-capped table of contents,read_symbol→ ONE section (not the whole file),replace_symbol_body/insert_symbol/safe_delete→ edit a section BY ITS HEADING/KEY (no whole-file Read + line-count). EXTENSIBLE provider registry (PROVIDERS: ext→parser): markdown/mdx (ATX+ setext, fence-aware), asciidoc, reStructuredText, toml/ini ([section]), yaml (indent-nested keys), json (pretty-printed keys), txt (heuristic), css/scss/less (parseCss: top-level selectors / at-rules (@media/@keyframes) at L1, SCSS-nested rules deeper, each with an EXACT brace-matched span via the sharedhtmlNetBracesscan — a stylesheet's "symbol tree" is its RULE hierarchy, so read/replace_symbol target ONE rule), html/htm/xhtml (parseHtml:<h1-6>+<style>/<script>blocks + id-landmarks at L1, and WITHIN style/script the top-level CSS selectors / JS FUNCTIONS at L2 via a brace-depth scanhtmlNetBraces/htmlJsDecl— so read/replace_symbol target a rule or function BY NAME; dogfooded on dashboard.html, a function read at ~153×). The heuristic embedded JS/CSS decls are taggedembeddedso they can be REPLACED by exact tree-sitter ranges: tree-sitter INJECTION is DONE (was the deferred robustness upgrade) —treesitter.js htmlEmbeddedDecls(text)re-parses each<script>/<style>with the real javascript/css grammar for EXACT decl ranges, recovering decls the heuristic misses (a MINIFIED one-line script, two CSS rules on one line, and — crucially — a function inside a top-level IIFE(function(){…})(), the dashboard.html pattern: the heuristic's depth-0 brace scan misses it, the injection'smaxBlockDepth≤1walk recovers it). textstruct stays PURE (no fs/async/tree-sitter) —structOutlineInjected(file,text,injector)takes the parser from core.js (which owns tree-sitter) and falls back to the heuristic when it returns null (deps absent);resolveInOutlineresolves against the already-computed (refined) outline. Each provider emits[{level,title,line[,endLine]}]; sharedcomputeSpanssets the section span (a providerendLine— brace-matched — wins over the to-next-heading heuristic),resolveSection/resolveInOutline(exact-then-substring,linedisambig) +fmtOutlineare format-agnostic — add a format = add one parser. core.js:STRUCT_TOOLS+structTool(synthesises an LSP-shaped range from a section span → reusessymbolEditResult/applyEditsToText; computes the injected outline ONCE, then resolves against it); anisStructFile(a.path)SHORT-CIRCUIT runs BEFORE backend resolution (a .md/.toml/.css has no language server); document_symbols + read_symbol carry acompletenessCert({section})(the SECTION rung). NO new MCP tools (the 5 existing symbol tools just work on text files — zero tool-budget cost). Zero-dep core, PURE, local, token-capped. Eval guards 84 (CSS provider) + 81 (HTML injection, under the tree-sitter block).vts symbols/read-symbol/replace-symbol/insert/safe-delete --path X.{md,css,html}.server/backends/index.js— clangd/roslyn/typescript/pyright spawn configs +pickBackend(root)(detect order: compile_commands→clangd > .sln/.csproj→roslyn > tsconfig/package.json→typescript > pyproject/*.py→pyright; strongest build-artifact first). MIXED-REPO FIX: a query that TARGETS a file usesbackendForPath(a.path)(core.js — ext→backend: .py→pyright, .ts/.js→typescript, .cpp/.h→clangd, .cs→ roslyn) BEFOREpickBackend(root), so a.py/.tsfile inside a clangd-rooted UE/C++ tree gets pyright/ typescript instead of clangd (else the query hits the wrong LSP, finds nothing, model abandons vts). Precedence (preferBackend, core.js): explicita.backend> the path's OWN backend WHEN it CONFLICTS with a forced backend (one global server serves every repo, so abackend:"clangd"pinned for a C++ project must NOT be sent this repo's.js/.cs/.py→ clangd answers-32001 invalid AST; live-found dogfooding goto on the vts repo while config pinned clangd for a UE tree) > forcedVTS_BACKEND/configbackend>backendForPath(a.path)>pickBackend(root). A path-less query (search_symbol by name) keeps the forced backend. Eval guard 55. CENSUS FALLBACK (core.jscensusFallbackBackends): a path-less query keeping the forced/root backend is the mixed-repo hole — clangd (root) answers 0 for a Python symbol and the model abandons vts. So when the primary backend'ssearch_symbolcomes back EMPTY on a path-less, non-explicit query, retry against the OTHER backends thelanguageCensusshows have files (most-code-first) BEFORE the syntactic/literal fallback — still the EXACT rung, just from the right LSP. Gated:!a.path && !a.backend, count ≥VTS_CENSUS_FALLBACK_MIN(1),VTS_CENSUS_FALLBACK=0off; no cost on a single-language repo (census returns no other candidate). Eval guard 90. Override viaVTS_CLANGD_CMD/ARGS,VTS_ROSLYN_CMD/ARGS,VTS_TS_CMD/ARGS,VTS_PY_CMD/ARGS.winShellflag spawns the npm.cmdshims (ts/pyright) through a shell on Windows.langIdForPath(lsp.js) maps file ext → LSP languageId.findProjectRoot(start)— bounded walk UP from a file to the nearest project marker (compile_commands/ *.uproject/.sln/.csproj/tsconfig/package.json/pyproject/…/.git as the repo-boundary fallback; nearest dir wins, never climbs past a.git). FeedsresolveRoot(core.js) so a per-callpathpins the right repo on a globally-installed server.server/core.js—runTool()dispatch, token-cap formatters, savings ledger. Tools:search_symbol,find_references(accepts EITHER a 0-basedpath+line+characterposition OR asymbolNAME — the code-modification primitive: by-name resolves the decl viac.symbol[exact-name-then-path-endsWith ranking],didOpens it, queries references atlocation.range.start; no indexed decl →scanTextUnderliteral-usage fallback. Discover showed name-driven usage hunts = the top bypass; this collapses the locate→position→refs dance that pushed the model to grep. CALL-HIERARCHY FOLD: adirection=callers|calleesparam turns the SAME tool into a MULTI-HOP call hierarchy [transitive callers = blast radius before an edit / callees] todepthhops [VTS_TRACE_MAX_DEPTH5, node capVTS_TRACE_MAX_NODES80] vialsp.jsprepareCallHierarchy→incoming/outgoingCalls [graceful -32601→[],traceFromDFS w/ cycle+dedup guard, indented file:line tree]; codebase-memory-mcptrace_pathparity but on the OFFICIAL LSP [zero-transmission, real semantic edges] and folded INTO find_references — NOT a new tool [no fixed-surface cost, reuses the symbol→pos resolution].vts trace-callsCLI =references --direction callers. Eval guard 70; live-verified on the vts repo itself. NAV STEER (refNavSteer): a LARGE flat ref result (> cap or ≥VTS_REF_NAV_MIN25) with nodetail=appends a one-line nudge to the CHEAPER views of the same set —detail=file/dir(per-file blast-radius summary) ordirection=callers(transitive caller tree);VTS_REF_NAV=0hides),goto_definition(akindparam folds intype_definition/implementation/declarationvialsp.js gotoByKind→ 3 more LSP nav requests, NO new MCP tools),hover,document_symbols,diagnostics(compiler/linter errors+warnings for a file as a token-cappedfile:line:col severity [code]: msglist, sorted error→hint + count summary — the compact alternative to reading raw build output;lsp.js diagnosticsForstores publishDiagnostics PER-uri sincenotifiedonly keeps the last, waits briefly for the first publish after didOpen; eval guard 63),rename(LSP; preview by default,apply=truewrites); SYMBOL-LEVEL EDITING (Serena-parity, the mutating set — all preview-by-default,apply=truewrites):replace_symbol_body/insert_symbol(position=after[default]|before— the after/before inserts MERGED into one tool to shrink the surface) /safe_delete—resolveSymbolForEdit(core.js) resolves a declaration by NAME via the LSP outline (documentSymbol's.range= whole body,.selectionRange= name;pathpins the file else the index resolves it, optionallinedisambiguates), then splices text at the span viaapplyEditsToText(symbolEditResultshared preview/apply, reuses the rename read-only/Perforce note).safe_deleterefuses while the symbol is still referenced (refs at the name) unlessforce=true. Token win: edit by naming a symbol instead of Read-ing the whole file + line-counting for an exact-match Edit. Eval guard 52.detect_changes(REVIEW BY IMPACT — a SURFACE not a rung:git diff[working tree /staged/base=<ref>, viarunExternal] →parseDiffHunkschanged line ranges → innermost enclosing decl per hunk [documentSymbol, tree-sitter fallback] → blast radius [buildCallGraphcallers, depth-bounded] + a DETERMINISTIC risk band [server/detect-changes.jsscoreRisk: 4 code-mined channels — blast / cascade-depth / git co-change coupling-GAP (cochangeNeighbors: partners historically co-changed but ABSENT from this diff) / test-reach dampener; fixed weights, NO learned artifact, no click-feedback, hand-recomputable]; EXACT rung when the blast came from the LSP, SYNTACTIC when a file fell back to tree-sitter [blast unknown, said so]; capped[risk] symbol file:line, no bodies. The charter-pure answer to code-review-graph — official LSP for the blast, your own git history for coupling, no persistent DB, no embeddings.VTS_DETECT_MAX_SYMBOLS/VTS_RISK_*; CLIvts detect-changes; eval guard 93);find_files,search_text(filesystem — sanctionedfind/grepreplacements, no backend needed;search_textTARGETING:path=<file>searches one named file /glob=<pat>matching files — naming it AUTO-INCLUDES that extension (a.mdetc), no docs flag;docs=true(no path/glob) widens the project-wide sweep to README/docs/config exts — default stays code-only. The grep-block hook reroutes a file-targeted text grep [grep X README.md] →vts text --path README.mdviabuildDocsGrepRewrite, rewrite-only never blocks);vts_git,vts_p4(OUTPUT COMPACTION, not index — run the realgit/p4and group/dedup/cap the result viaserver/compact.js: git status→by change-type+dir, log→one line/commit, diff→per-file +/- diffstat; p4 opened/status/reconcile→by action+depot-dir, changes→terse. The rtk slice under our roof + ledger; the grep-block hook reroutes a single read-onlygit status|log|diff/p4 opened|status|changes|reconcilehere viabuildVcsRewrite— never blocks,VTS_COMPACT_VCS=0disables.git grepstays a CODE search. CLIvts git/p4are full arg passthrough → run in cwd, no--projectPath). MCP-SURFACE FOLD: the 9 cold admin/meta tools (vts_git/vts_p4/vts_setup/vts_config/vts_savings/vts_savings_reset/vts_discover/vts_warmup/vts_gen_compile_db) are NO LONGER advertised individually — they're folded behind ONEvts_admin{op,params}MCP tool (index.js mapsvts_admin→runTool("vts_"+op,params); hot search/nav/edit tools stay first-class so the model still reaches for them). core.js runTool + the CLI keep the individualvts_*names UNCHANGED (the grep-block hook still reroutes git/p4 to the CLI, not this tool); eval guard 62. FOOTPRINT SLIM (v0.37.2 — vts's OWN MCP usage measured at ~24%, mostly the per-request tool schema): the self-evident common params (projectPath/backend/maxResults) carry NO description (sharedROOT/BACKEND/CAPconsts intools.js), tool descriptions trimmed (adoption "USE INSTEAD OF" + routing cues KEPT) → tool-list schema 3455→2723 tok (−21%, recurring every API call); guard 62 cap 3500→2900. Also the per-RESULTcompletenessCertrung lines +EMPTY_HINT/LOG_STEERand the SessionStartroutingDigest(policy.js) are trimmed ~40-50% (rung keywords + the one actionable command kept; guards 76/16/digest green). The folded ops:vts_warmup,vts_setup,vts_config,vts_savings(RTK-gain-style:graph/daily/history+ est. USD over timestamped day buckets; ALSO FOLDS IN the bundled gamedev-log-analyzer's ledger [~/.gamedev-log-analyzer/savings.json,VTS_GAMEDEV_SAVINGS_FILEoverride] → a+ gamedev-log-analyzer (logs)line + a COMBINED total, since its log-compaction saves toward the same goal; the dashboard/datadoes the same viasavings.sources. Local file read only),vts_savings_reset,vts_discover(scans~/.claude/projects/*.jsonlfor code searches that BYPASSED vts → missed-token report + catch-rate;learn=truefeeds their result files into the warm-set; ALSO MEASURES THE EDIT HABIT —classifyDeclEdit(server/edit-detect.js, SHARED with the enforcement hook) flags a built-in Edit/MultiEdit whoseold_stringis a whole declaration (replace →replaceDecl) OR whosenew_stringis (add →insertDecl) on a code file (≥VTS_EDIT_MIN_LINES+decl cue). CONTROL-FLOW EXCLUSION (dogfood-found FP): a) {opener also matchesif/for/while/switch/catch (…) {, soisWholeDeclnow only counts the opener when the callee identifier is NOT a reserved control-flow keyword (else a multi-lineif(…){…}block edited inside a body was flagged a whole decl → suggestedreplace_symbol_body symbol="if", not a named symbol); the hook'sdeclSymbolNamelikewise refuses a reserved keyword as the symbol name. v0.26.2 GENERALIZED it: the construct is decided by the chunk's FIRST meaningful line — aCTRL_FLOW_FIRSTheader short-circuits to false BEFORE the DECL_KW check, so anif(…){ (void)x; … }/if(…){ static int n; … }block (DECL_KWvoid/staticin the BODY) no longer false-positives (the v0.26.1 callee guard only covered the signature-opener branch). Eval guard 59. It attributes that file's PRIOR Read tokens [reads/readUseRead↔Edit correlation inscanBypasses, read counted ONCE] = the read a symbol-edit would've skipped →edit habit:line; ALSOeditUnreached= how many had NO prior vts search on that file [searchUse/searchedBnbasename match] = the fraction the search-result steer CAN'T reach. Measured 30d: 1284 whole-decl edits, ~468k tok read-first, 1194/1284 (93%) search-unreachable). STEER is THREE layers, soft→hard (Edit-rewrite impossible: cross-toolupdatedInputcan't switch Edit→MCP, and the read is sunk by Edit time so a block recovers nothing — only a LEARNING signal): (B)EDIT_STEERon a FOCUSEDsearch_symbol(≤VTS_EDIT_STEER_MAX10) /goto_definitionresult (VTS_EDIT_STEER=0hides); (L1) the grep-block hook now also matchesEdit|MultiEdit— a whole-decl replace/insert gets a MODEL-VISIBLEemitWarnwith a READY symbol-edit call (replace_symbol_body/insert_symbol,declSymbolNamebest-effort names it),VTS_EDIT_WARN=0off; (L1-Bash) the hook ALSO catches a code-file edit done via BASH —sed -i, anawkinplace/redirect, or apython/perlheredoc that opens a code file for write (isBashCodeEdit: a code-ext path AND an explicit write/in-place signal must BOTH be present, so a read-onlysedpipe or apython build.pyisn't nagged) — warn-only toward replace_symbol_body/ insert_symbol; the Edit-tool steer alone MISSED this (a python brace-match splice bypasses it — live-found on a large irregular-indent function), and Bash file-surgery is a big slice of the low symbol-edit adoption; (L2) OPT-IN escalation,VTS_EDIT_BLOCK_AFTERDEFAULT 0=OFF — set ≥1 and once the adoption ledger's ignore-streakhits it, a SAFE insert (insertDecl && !replaceDecl—insert_symbolcan't corrupt) is BLOCKED ONCE (exit 2) thenresetStreak()(fire-once, NOT a wall — a permanent block TRAPPED the agent: it fought the wall with Edit retries / code contortions instead of switching, and each blocked attempt re-escalated the streak; live-reproduced on US editing edit-ledger.js); a replace stays warn. ADOPTION LEDGER (server/edit-ledger.js,~/.vs-token-safer/ edit-adoption.json,VTS_EDIT_LEDGERoverride): hook recordsbuiltin-warn(streak++), core.js recordssymbol-editon every symbol-edit dispatch (streak→0);hooks/edit-report.js(SessionStart) re-injects the adoption % as a goal = the SkillOpt-style measure→re-inject loop (static skill can't self-improve, a re-injected live metric can). Eval guards 53 (steer+discover) + 54 (L1/L2 hook);eval/test-edit-steer.mjs.find_files/search_textwrite a recovery TEE file (VTS_TEE_DIR, default on-truncate) when a result is capped so the full set is recoverable without re-running; a cappedsearch_symbol/find_references("… N more") tees too (teeOverflow— the rows are already in memory, no re-query). The ledger aggregates PER TOOL (by tool:line invts savings) so you can see where the win comes from. BOOT AUTO-LEARN (index.js,VTS_AUTO_LEARNdefault on when projectPath set): 3s after boot,autoLearn(root, 7)(core.js, sharesscanBypasseswith discover) harvests bypassed-search result files into query-history — the self-improvement loop runs unattended every server start.agents/code-locator.md— context-isolated locator subagent (delegates a lookup, returns only file:line).server/compact.js— PURE output-compaction fns (compactGit/compactP4, string→string, no spawn) for thevts_git/vts_p4wrappers. Eval exercises them on canned input (deterministic). (No grep compaction here — grep reroutes to search_text, which scans + token-caps itself; there is no raw grep output to compact.)server/viz.js+server/serve.js+server/dashboard.html+server/vendor/— LOCAL DASHBOARD (vts serve, cbm-style viz but local-only/zero-transmission).viz.jsbuildVizData(root)assembles the savings ledger + language census + include-graph cache into one model;renderDashboardHtml()reads the SELF-CONTAINEDdashboard.html(CSS/JS inlined; 3D graph via Three.js rendered WebGL). NO CDN — Three.js is VENDORED atserver/vendor/three.module.min.js(MIT, r160) and served SAME-ORIGIN (/vendor/...); the page imports it relative, so nothing leaves the host.serve.jsis node:http ONLY (no express/ws), binds127.0.0.1(never 0.0.0.0), routes/→html ·/data→JSON (include graph) ·/callgraph?symbol=&direction=&depth=→JSON (ON-DEMAND call graph viacore.js buildCallGraph= LSP callHierarchy live, NOT a persistent semantic DB — the cbm-parity "call graph" view our charter allows; nodes carrycalls/calledBy/repo, edges a call-sitecount[fromRanges], +totalCallSites) ·/symbols?q=→JSON (core.js listSymbols= workspace/symbol autocomplete for the search box) ·/vendor/<allowlisted file>.core.js repoLabelFor(findProjectRoot → basename) tags every node with its repository./symbolgraph→JSON (core.js buildSymbolGraph= TREE-SITTER symbol graph: nodes=code files (weight=decl count), edges=within-repo import adjacency [importSpecifiers, text-based; ext WITHOUT the leading dot], SYNTACTIC + scope-filtered + bounded [VTS_VIZ_MAX_NODES], min.js/ vendor excluded, labeled syntactic so it's never read as the semantic call graph — the zero-toolchain viz twin of the clangd include-graph, works on any of the 17 tree-sitter langs; eval guard 94). The 3D viz: THREE modes (include / call-graph-by-symbol with live symbol autocomplete dropdown / tree-sitter symbol-graph), spherical-SHELL layout (radius ∝ node count+footprint, radius-aware collision so orbs don't clump/overlap),color:groups (union-find connected components) / repo (per-repository hue + legend) / heat, click-to-drill-into-a-group (Esc/Backspace pops out), focus/maximize + keyboard camera (WASD/arrows/+-/R/Esc), distance-scaled labels, highlight filter, metrics overlay (incl. call counts). OPT-IN + CLI-ONLY: started only byvts serve(cli.js special-cases it — long-running,--openlaunches the browser,--stop/SIGINT stop via a pidfile), NEVER by the MCP server, so the steady-state package stays a thin stdio client. Easy open/close via theskills/vs-vizskill +commands/viz.md/commands/viz-stop.md(/vs-token-safer:viz[-stop]).VTS_VIZ_MAX_NODES(200) bounds the graph. Eval guards 72 (dashboard + server) + 73 (buildCallGraph + /callgraph).server/cli.js—vts <cmd>.server/index.js— MCP server (async handler →await runTool). PER-CALL ROOT:resolveRoot(a)(core.js) replaces the old single-pina.projectPath || PROJECT_PATH || cwdfor every query — precedence: explicitprojectPath> apath's enclosing project (findProjectRoot, only when OUTSIDE every known root so an inside-path keeps clangd's compile-DB rooting) > an MCP workspace rootPROJECT_PATH> cwd.resolveCwdRoot(a)for git/p4 (MCP root beats server cwd; pin still ignored). One global server now serves every repo a session touches, not just the pinned one. index.js does the MCProotshandshake (getClientCapabilities→listRoots→setMcpRoots, re-fetched onroots/list_changed, undefined-safe on old SDKs); no roots advertised → collapses to the oldPROJECT_PATH || cwd. Boot prewarm/auto-learn usePROJECT_PATH || first MCP rootso a config-less install warms the current workspace (ONE root only). BACKEND POOL (memory guard for dynamic roots): theclientsmap is BOUNDED —VTS_MAX_BACKENDS(2) LRU-evicts the least-recently-used idle client past the cap,VTS_BACKEND_IDLE_MS(300000, 0=off) reaps idle clients via an unref'd sweep; a client with an in-flight request (pending.size) is never evicted/reaped. Steady state ≈ 1 warm backend; bouncing 2 repos keeps both warm; a 3rd evicts LRU.__pooltest surface + eval guards 45 (pool) / 46 (root resolution).server/sdk.js— createRequire MCP-SDK resolution.server/ensure-deps.mjs— SessionStart installer.server/warmset.js— prewarm ORDERING:orderForWarm(query-history > working-now [git status/p4 opened] > git-log recency > include-centrality [adaptive: prefix-read +VTS_CENTRALITY_BUDGET_MS- persistent include-graph cache that grows across warmups;
VTS_CENTRALITY_MAXbounds the loop] > mtime) +recordQueryResults. Steers clangd's open-set so the warm window hits likely queries; git + Perforce. Used bybackends/index.jsafterInit +core.js(records result files per search). Also LANGUAGE-MIX warm sizing:languageCensus(root)(cached file-count per backend lang, skips node_modules/build/...),warmCap(root,backend,env,base)(per-backend open-cap scales to that lang's file count ×VTS_WARM_CAP_RATIO, clamped[base,VTS_WARM_CAP_MAX]; explicitVTS_*_OPEN_CAPwins), andprewarmBackends(root,picked)(VTS_PREWARM_BACKENDSauto→[dominant] /all→every detected lang dominant-first / comma-list).index.jsboot warms each selected backend with its adaptive cap → a multi-lang repo warms in language proportion.
- persistent include-graph cache that grows across warmups;
server/psearch.js+ thePowerShellmatcher entry — THE SECOND SHELL. This environment exposes aPowerShelltool ALONGSIDEBash; the PreToolUse matcher listed onlyBash|Grep|Glob|Edit|MultiEdit|Read, soSelect-String -Path <src> -Pattern "A|B|C"ran fully unenforced AND uncounted (discover's "share routed through vts" was computed over a channel set that excluded it — it read high for the wrong reason). Found live on a UE tree.classifyPowerShellSearch(cmd)is PURE (shared by the hook andmatchBypass, likeshell-split.js) →null | {kind:"content"|"files", pattern, target, symbol}. It never rewrites — a naive PowerShell parser (backtick escapes,$dinterpolation,-Patprefix binding, positional args) would silently rewrite into a DIFFERENT search, which is worse than not intervening; the hook emits a READYsearch_symbol/search_text/find_filescall instead. Quiet by construction: a piped… | Select-String(filtering another command's output), a log/doc/config target, any mutating cmdlet in the command (Copy-Item/Remove-Item/… — a file list feeding a file-op must never be steered to a CAPPED list), a non-recursiveGet-ChildItem,-NotMatch(an inverted match — any call we suggest would return the COMPLEMENT of what was asked), and every SCRIPTED-VALUE shape (-Quiet,.LineNumber/.Count,$x = (Select-String …), anif (…)test) — those put no bulk text in context, so there is nothing to save, and no vts tool can return a value into a PowerShell variable. The pattern must BE a symbol, not merely CONTAIN one ("// TODO: FixMe later"is prose;symbolInrequires every|alternative to be a whole identifier /Foo::Bar/struct Foo) — the Grep tool's whole-pattern rule. WARN-ONLY by default (VTS_PS_BLOCK=1opts into blocking a symbol hunt): the channel was invisible until now so there is no conversion data, and this project already learned that a wall the agent can't satisfy makes it fight rather than switch (VTS_EDIT_BLOCK_AFTERdefaults to 0 for the same reason). With qvts installed it still blocks — there a working alternative demonstrably exists. Eval guard 98 asserts BOTH halves — the classifier AND that hooks.json is actually wired to the tool (the wiring is the half that failed silently and no behavioural test sees it). Known, accepted:Get-Content F.cpp | Select-String Xis piped, so ignored by design.hooks/block-code-grep.js+hooks.json— grep-block. A Bash code search (grep/rg/ack/ag/findstr/git grep/find -name) that is a SINGLE safe segment is REWRITTEN to the equivalentvtsCLI command via PreToolUseupdatedInput(token-capped, flow unbroken); anything ambiguous (pipeline, unsafe pattern, quote in the root) falls back to the exit-2 block. Segment splitting is QUOTE-AWARE (splitSegmentsinserver/shell-split.js, SHARED withvts discoverso enforcement and measurement agree): a|inside quotes is pattern, not pipeline — sogrep "FooA|FooB"/grep "^#include"(the top bypass shapes pervts discover) rewrite tovts text(regex); inside double quotes\"is an escaped literal; SAFE_TEXT allows| ^ #(always double-quoted;$/space/backslash still rejected).grepNudgeForembeds a READY-TO-USE equivalent call (identifier→search_symbol, regex→search_text) in every Grep nudge/block. GREP-TOOL enforcement v2 (A+) + v2.1: a clear SYMBOL HUNT is BLOCKED (exit 2) and routed to search_symbol/ search_text perisSymbolHuntGrep— (1) a bare identifier, (2) a regex with a code-structural cue (::/ literal(/void·class·struct·enum·template), OR (3) v2.1 an ALTERNATION (A|B|C) carrying a CamelCase/snake identifier (MaxWalkSpeed|MaxExcessSpeed,get_value|set_value) — the top measured bypass (UE type/symbol enumeration). KEPT as warn (false-positive-safe): freeform single tokens, AND keyword alternations (TODO|FIXME/GET|POST— ALL-CAPS, no lower→upper transition, so no CamelCase signal). The reroute is search_text (same regex, token-capped) → no wrong/missing results, just friction.VTS_GREP_BLOCK=0reverts all of it to warn-only. Measured: v2 block ~172k tok/30d; v2.1 adds ~319k (CamelCase alternation). GLOB/Search TOOL (filename search) v2.2: a CONCRETE code-file glob (*.cpp/Foo.h/**/Bar.*perisBlockableGlob) is BLOCKED →find_files(which is a DIFFERENT tool — can't updatedInput-rewrite a Glob — so it's a block with a ready-to-usefind_files q=… projectPath=<dir hint from the glob/path>); a bare*/**/*or code-DIR glob stays a warn. The warn alone was IGNORED — the model kept Glob-ing a giant UE tree and narrowing the path instead of switching (live dogfood). find_files/search_text are walk-BOUNDED: sharedSKIP_DIRS(node_modules/Intermediate/Binaries/Saved/build/… ) + a 4s time box so a huge tree can't hang them. FIND-DIR FIX (v2.2): a Bashfind <dir> -name Xrewrite now HONORS<dir>as the find_files root (extractFindDir) — it was dropped, sofind /abs/UE/path -name Xsearched the configured vts repo and falsely reported "No files" (a live correctness bug on a UE worktree).vts discoveralso counts the Glob tool as a find_files bypass. FILE-OPS FIND FP FIX (v0.33.14): afinddoing FILE-OPS — its own-exec/-delete/-type d(isFindFileOps), or alongside a file-op exec in the same command (hasFileOpsContext: cp/mv/tar/rsync/xargs/zip/du/… — a backup/copydu …; find … -name "*.cpp") — is NOT a code search → never blocked AND never rerouted to a (token-CAPPED) find_files, which would silently drop files from a copy/delete. A genuine code-filefind -name "*.cpp"with no file-op still rewrites to find_files. Live-found: a UE-depot backup find got blocked + the capped reroute would corrupt the backup. grep stays strict (a literal grep in a pipeline is usually content filtering). Eval guard 17b.VTS_REWRITE=0→ block instead of rewrite;excludeCommands(config) /VTS_EXCLUDE_COMMANDS(csv) opt a command out; escape hatchVTS_ENFORCE=0. Messages i18n'd (uiLang(): Korean whenVTS_LANG/configlang=koOR OS localeko-*, else English;VTS_LANG=en|koforces). Copy is AGENT-DIRECTED — the actionable part instructs the assistant ("re-run with the vts tool matching the intent" + the concrete call), with a brief human-facing reassurance that the red box is a redirect ("hold on"), not a failure — the hook output is consumed by the MODEL, which is the one that re-runs, not a human picking from a menu.skills/vs-search/SKILL.md— routing.commands/{setup,savings,update}.md.commands/update.md=/vs-token-safer:update— one-command REFRESH of a STALE committable.vts-index(opvts_admin{op:index}= incrementalbuildSymIndex, re-parses only stat/hash-changed files). Surfaced two ways: theSYNTACTIC · STALEcert (core.js) now names/vs-token-safer:update, andhooks/edit-report.js(SessionStart) emits apolicy.js stalenessLine(indexFreshness(root))cue INDEPENDENT of the adoption gate (a stale index matters on a fresh session too; en/ko; only when a committable index exists + it's stale). Eval guard 95.VTS_STALE_CHECK=0off.eval/run.mjs+eval/_mock-lsp.mjs— mock-LSP eval (no toolchain). Add a guard for every new path.- Config dir
~/.vs-token-safer, env prefixVTS_. MCP server namevs-search.
What this file has done since we first saw it
Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.
- 3d ago Changed · +52 lines · +1,466 tokens per session bb5f5f400135
- 8d ago First seen · 610 lines · 17,632 tokens per session scan A 43c13f80a10a
vs-token-safer CLAUDE.md is an instructions file published in the GitHub repository JSungMin/vs-token-safer (11 stars, last pushed today), licensed MIT. It adds 19,098 tokens to every session, about $0.0955 per session on Opus 5. A static security scan graded it A with 1 finding (runs shell commands). No closer match exists in the catalogue, so it is treated as the original; first seen 2026-08-30.
Other instructions, from other repositories
plan-forge api-patterns.instructions.md
API patterns for .NET — REST conventions, ProblemDetails, pagination, versioning, error responses.
plan-forge graphql.instructions.md
GraphQL patterns for .NET — Hot Chocolate, code-first schema, DataLoaders, authorization, multi-tenant resolvers.
plan-forge errorhandling.instructions.md
Error handling patterns — Exception hierarchy, ProblemDetails responses, error boundaries, global exception middleware.
weld cpp.instructions.md
C++ naming and structure guidance.
codex AGENTS.md
AGENTS.md instructions for openai/codex, covering rust/codex-rs, the codex-core crate, code review rules, crate api surface and model visible context.
vscode buildNext.instructions.md
Working notes and architecture documentation for the new esbuild-based build system in build/next. Use when making changes to the new build pipeline (transpile/bundle commands, NLS plugin, source-map handling, resource copying, or self-hosting watch tasks).