vexp-cli 2.4.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,108 +1,78 @@
1
- # vexp — Context Engine for AI Coding Agents
1
+ # vexp-cli — Context Engine for AI Coding Agents (Claude Code, Cursor, Codex)
2
2
 
3
- **58% Less Cost. 90% Fewer Tool Calls. 100% On Your Machine.**
3
+ **Give your AI coding agent the right context, then verify its work — 100% on your machine.**
4
4
 
5
- vexp is a local-first context engine that gives AI coding agents the right code context — automatically. One install, and every agent gets smarter answers with fewer tokens.
5
+ `vexp-cli` is a local-first [MCP server](https://modelcontextprotocol.io) that pre-indexes your codebase into a dependency graph and feeds any AI coding agent only the code that matters for the task — cutting **87% of the tokens** it would otherwise waste reading whole files. New in 2.5: it also checks the agent's work mechanically when it says it's done, and scans your code for PII and secrets before any model reads them. Your source never leaves your laptop.
6
6
 
7
- Works with **14+ AI coding agents** and **30 programming languages** out of the box.
7
+ ```bash
8
+ npm install -g vexp-cli
9
+ vexp setup # auto-configures Claude Code, Cursor, Codex, and 14 agents
10
+ vexp index # builds the local graph — no login, no API key
11
+ ```
8
12
 
9
- → [SWE-bench Verified: 73% pass@1 at $0.67/task](https://vexp.dev/benchmark)
13
+ [![npm version](https://img.shields.io/npm/v/vexp-cli?color=CB3837)](https://www.npmjs.com/package/vexp-cli)
14
+ [![downloads](https://img.shields.io/npm/dm/vexp-cli?color=CB3837)](https://www.npmjs.com/package/vexp-cli)
15
+ ![local-first](https://img.shields.io/badge/code-never%20leaves%20your%20machine-brightgreen)
10
16
 
11
- ## Why vexp?
17
+ ---
12
18
 
13
- AI coding agents waste more than half their budget exploring codebases before writing a single line of code — 23 tool calls on average just to orient themselves. vexp pre-indexes your project into a dependency graph and delivers only the code that matters, ranked by relevance.
19
+ ## Why developers install vexp
14
20
 
15
- - **23 tool calls → 2** — the agent stops guessing and starts coding
16
- - **$0.78 → $0.33 per task** — measured on SWE-bench Verified
17
- - **117s → 74s per task** — less context to process = faster answers
18
- - **Local-first** — your code never leaves your machine
19
- - **Zero config** — auto-detects your agents and configures MCP
20
- - **Universal** — works with any agent that supports MCP or custom rules
21
- - **Optional local AI** — unlock +30% extra savings with `vexp-devmind`, a ~3.5 GB model that runs 100% on your machine. GPU-accelerated on NVIDIA (CUDA) and Apple Silicon (Metal). Opt in with one command.
21
+ Your AI agent burns most of its budget exploring your repo before writing a line of code. It reads entire files, floods its context window, and still misses the one function that mattered. vexp fixes the input **and** the output:
22
22
 
23
- ## Supported AI Coding Agents
23
+ - **⚡ 87% fewer tokens per call.** vexp returns ranked pivot files with exact line ranges and blast radius — not whole files. Measured on real usage, not a synthetic benchmark.
24
+ - **🔍 Verifies the work (new in 2.5).** When your agent declares a task done, `verify_done` reports what's provably broken or missing — broken imports, parse errors, dependents never updated — each with file and line. No model grades anything; it's git + tree-sitter + the code graph.
25
+ - **🛡️ Shield scan (new in 2.5).** `vexp shield scan` shows the emails, API keys, tokens and private keys sitting in your comments and strings that every AI agent currently reads. Local, deterministic, masked.
26
+ - **🔒 100% local.** Zero network calls, zero cloud indexing, no account, no API key. Your code stays yours.
27
+ - **🤝 Works with everything.** One `vexp setup` configures Claude Code, Cursor, Windsurf, GitHub Copilot, OpenAI Codex, Zed, Cline, Aider, opencode, and more — 14 agents in total.
24
28
 
25
- | Agent | Auto-Configured |
26
- |-------|:-:|
27
- | Claude Code | ✓ |
28
- | Cursor | ✓ |
29
- | Windsurf | ✓ |
30
- | GitHub Copilot | ✓ |
31
- | Continue.dev | ✓ |
32
- | Augment | ✓ |
33
- | Zed | ✓ |
34
- | Codex (OpenAI) | ✓ |
35
- | Cline | ✓ |
36
- | Aider | ✓ |
37
- | Kilo Code | ✓ |
38
- | Kiro | ✓ |
39
- | Antigravity | ✓ |
40
- | Opencode | ✓ |
29
+ ## How it works
41
30
 
42
- Your agents don't compete with each other — they all share the same context layer.
31
+ vexp parses **34 languages** with tree-sitter into a local SQLite dependency graph, then serves your agent through the Model Context Protocol:
43
32
 
44
- ## Supported Languages (30)
33
+ - `run_pipeline` — ranked pivot files + line ranges + blast radius, in one call
34
+ - `get_skeleton` — file structure at 70-90% token savings
35
+ - `verify_done` — mechanical completion check
36
+ - `expand_vexp_ref` — full symbol bodies on demand
45
37
 
46
- TypeScript, JavaScript, Python, Go, Rust, Java, C#, C, C++, Ruby, Kotlin, Scala, Swift, Dart, Elixir, Haskell, OCaml, Lua, R, PHP, Zig, HCL/Terraform, Objective-C, Bash/Shell, Dockerfile, Clojure, F#, SQL, HTML/CSS, YAML/JSON
38
+ You never call these yourself — `vexp setup` writes the agent config, and your agent calls them automatically. You don't even mention vexp in your prompts.
47
39
 
48
- ## Quick Start
40
+ ## Quickstart
49
41
 
50
42
  ```bash
43
+ # 1. Install
51
44
  npm install -g vexp-cli
52
- vexp
53
- ```
54
-
55
- That's it. The interactive CLI guides you through setup, indexing, and agent configuration.
56
-
57
- ## What You Get
58
-
59
- - **Semantic + graph-ranked context** — finds the right symbols even when keywords don't match
60
- - **Intent detection in 15 languages** — English, Italian, French, German, Spanish, Portuguese, Russian, Chinese, Japanese, Korean, Arabic, Hindi, Turkish, Dutch, Polish
61
- - **Cross-session memory** — your agent picks up exactly where it left off, even days later
62
- - **Stale-knowledge alerts** — when code changes, outdated memory is automatically flagged
63
- - **Pattern & anti-pattern detection** — recurring changes surface as guidelines; dead-end explorations get flagged early
64
- - **Change awareness** — files frequently modified together are automatically linked
65
- - **Security filter** — `.env`, credentials, and SSH keys are excluded automatically
66
45
 
67
- ## Platforms
46
+ # 2. Configure your agents (auto-detected) + build the graph
47
+ cd your-project
48
+ vexp setup
49
+ vexp index
68
50
 
69
- Native binaries — no runtime dependencies:
51
+ # 3. Use your agent normally. That's it.
52
+ # Check savings any time:
53
+ vexp savings
70
54
 
71
- | Platform | Architecture | GPU Acceleration |
72
- |----------|:-:|:-:|
73
- | Linux | x64, ARM64 | CUDA (NVIDIA) |
74
- | macOS | x64, ARM64 (Apple Silicon) | Metal (built-in) |
75
- | Windows | x64 | CUDA (NVIDIA) |
55
+ # 4. See what your code exposes to AI:
56
+ vexp shield scan
76
57
 
77
- GPU acceleration applies to the optional local AI layer. Vexp's core context engine runs on any modern CPU.
78
-
79
- ## VS Code Extension
80
-
81
- For the best experience, install the [vexp VS Code extension](https://marketplace.visualstudio.com/items?itemName=Vexp.vexp-vscode) — it manages the daemon automatically and adds a sidebar dashboard with token savings tracking.
82
-
83
- ## Local AI — Optional +30% Savings
58
+ # 5. Diagnose anything:
59
+ vexp doctor
60
+ ```
84
61
 
85
- Turn on `vexp-devmind` — a code-specialised AI model that runs entirely on your machine — for an extra +30% token savings on top of the baseline.
62
+ ## Supported agents
86
63
 
87
- - **100% local** — the model never calls out. Your code stays on your hardware.
88
- - **GPU-accelerated** — Metal on Apple Silicon, CUDA on NVIDIA. Auto-detected.
89
- - **One-command install** — ~3.5 GB one-time download. Minimum 6 GB RAM.
90
- - **Always up to date** — `--check-updates` compares your local version to the published release without downloading anything.
64
+ Claude Code · Cursor · Windsurf · GitHub Copilot · OpenAI Codex · Zed · Continue.dev · Cline · Aider · Kiro · Kilo Code · opencode · Augment · Antigravity
91
65
 
92
- Sharper context ranking, higher-quality session memory, smarter prompt preprocessing. All without sending a single byte to any server.
66
+ ## Supported languages
93
67
 
94
- ```bash
95
- vexp setup-llm --install # ~3.5 GB one-time download
96
- vexp setup-llm --check-updates # no-op if already up to date
97
- vexp setup-llm --status # show current config + hardware
98
- vexp setup-llm --disable # turn it off any time
99
- ```
68
+ TypeScript, JavaScript, Python, Rust, Go, Java, Kotlin, Scala, C, C++, C#, Ruby, PHP, Swift, Objective-C, Lua, Dart, Elixir, Clojure, Haskell, OCaml, F#, Zig, Bash, SQL, and more — 34 in total.
100
69
 
101
70
  ## Links
102
71
 
103
- - [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=Vexp.vexp-vscode)
104
- - [Website](https://vexp.dev)
72
+ - 🌐 Website & docs → [vexp.dev](https://vexp.dev)
73
+ - 🧩 VS Code / Cursor extension → [Marketplace](https://marketplace.visualstudio.com/items?itemName=vexp.vexp-vscode) · [Open VSX](https://open-vsx.org/extension/vexp/vexp-vscode)
74
+ - 🐛 Issues → [github.com/Vexp-ai/vexp/issues](https://github.com/Vexp-ai/vexp/issues)
105
75
 
106
- ## License
76
+ ---
107
77
 
108
- MIT
78
+ **Keywords:** ai context engine · mcp server · claude code context · cursor context · save tokens · reduce ai coding cost · code context · dependency graph · tree-sitter · local-first · token optimization · ai coding assistant · code intelligence · model context protocol
@@ -10,7 +10,7 @@ import * as fs from "fs";
10
10
  import * as path from "path";
11
11
  import * as os from "os";
12
12
  import * as crypto from "crypto";
13
- import { VEXP_GUARD_HOOK, VEXP_OPENCODE_GUARD, VEXP_CURSOR_GUARD, vexpHintHookScript, vexpOpencodeHintPlugin } from "./hook-template.js";
13
+ import { VEXP_GUARD_HOOK, VEXP_OPENCODE_GUARD, VEXP_CURSOR_GUARD, vexpHintHookScript, vexpStopGateHookScript, vexpOpencodeHintPlugin } from "./hook-template.js";
14
14
  // ---------------------------------------------------------------------------
15
15
  // Constants
16
16
  // ---------------------------------------------------------------------------
@@ -323,6 +323,7 @@ export function configureAgents(workspaceRoot, binaryPath, version, agentFilter,
323
323
  // Event-driven hint hook: default-on (non-blocking, fail-open — the
324
324
  // 2.3 A2 opt-in applies to DENY hooks, this one cannot block).
325
325
  const hintResult = installClaudeCodeHintHook(workspaceRoot, binaryPath);
326
+ installClaudeCodeStopGate(workspaceRoot, binaryPath);
326
327
  if (hintResult) {
327
328
  results.push({
328
329
  agent: "Claude Code Hint",
@@ -623,6 +624,7 @@ export function configureSelectedAgents(workspaceRoot, binaryPath, version, sele
623
624
  if (wrote)
624
625
  mcpConfigs.push("~/.claude.json");
625
626
  installClaudeCodeHintHook(workspaceRoot, binaryPath);
627
+ installClaudeCodeStopGate(workspaceRoot, binaryPath);
626
628
  if (guardMode() === "strict")
627
629
  installClaudeCodeHook(workspaceRoot);
628
630
  else
@@ -1689,6 +1691,68 @@ export function installClaudeCodeHook(workspaceRoot) {
1689
1691
  }
1690
1692
  return existed ? "updated" : "created";
1691
1693
  }
1694
+ function isVexpHookEntry(h, needle) {
1695
+ if (!h || typeof h !== "object")
1696
+ return false;
1697
+ const entry = h;
1698
+ if (entry.command?.includes(needle))
1699
+ return true;
1700
+ const hks = entry.hooks;
1701
+ return Array.isArray(hks) && hks.some((hook) => {
1702
+ if (!hook || typeof hook !== "object")
1703
+ return false;
1704
+ return hook.command?.includes(needle) ?? false;
1705
+ });
1706
+ }
1707
+ /**
1708
+ * Horizon F2a: install the Stop-hook verification gate for Claude Code.
1709
+ * Same fail-open family as the hint hook: the Rust `stop-gate` command
1710
+ * blocks a stop at most once per session and only on mechanically broken
1711
+ * references (parse errors, imports of removed names). No daemon = no
1712
+ * gate = vanilla stop. Additive like the hint hook, so not guardMode-gated.
1713
+ */
1714
+ export function installClaudeCodeStopGate(workspaceRoot, binaryPath) {
1715
+ const hookDir = path.join(workspaceRoot, ".claude", "hooks");
1716
+ const hookPath = path.join(hookDir, "vexp-verify.sh");
1717
+ const settingsPath = path.join(workspaceRoot, ".claude", "settings.json");
1718
+ fs.mkdirSync(hookDir, { recursive: true });
1719
+ const script = vexpStopGateHookScript(binaryPath);
1720
+ const existed = fs.existsSync(hookPath);
1721
+ const scriptIdentical = existed && fs.readFileSync(hookPath, "utf8") === script;
1722
+ if (!scriptIdentical) {
1723
+ fs.writeFileSync(hookPath, script, { mode: 0o755 });
1724
+ }
1725
+ const read = readJsonConfigSafe(settingsPath);
1726
+ if (!read.ok) {
1727
+ warnUnparseable(settingsPath);
1728
+ return scriptIdentical ? null : existed ? "updated" : "created";
1729
+ }
1730
+ const settings = read.data;
1731
+ const hooks = (settings.hooks ?? {});
1732
+ const existing = Array.isArray(hooks.Stop) ? hooks.Stop : [];
1733
+ const filtered = existing.filter((h) => !isVexpHookEntry(h, "vexp-verify"));
1734
+ // Cross-OS shape as everywhere: bash + QUOTED path; timeout in SECONDS.
1735
+ filtered.push({
1736
+ hooks: [
1737
+ {
1738
+ type: "command",
1739
+ command: 'bash "$CLAUDE_PROJECT_DIR/.claude/hooks/vexp-verify.sh"',
1740
+ timeout: 15,
1741
+ },
1742
+ ],
1743
+ });
1744
+ const merged = { ...hooks, Stop: filtered };
1745
+ const settingsIdentical = JSON.stringify(merged) === JSON.stringify(settings.hooks ?? {});
1746
+ if (scriptIdentical && settingsIdentical)
1747
+ return null;
1748
+ settings.hooks = merged;
1749
+ if (!settingsIdentical) {
1750
+ if (read.existed)
1751
+ backupConfig(settingsPath);
1752
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
1753
+ }
1754
+ return existed ? "updated" : "created";
1755
+ }
1692
1756
  function isVexpHintHookEntry(h) {
1693
1757
  if (!h || typeof h !== "object")
1694
1758
  return false;
@@ -2096,6 +2160,9 @@ vexp runs entirely on this machine: local daemon, index in \`.vexp/\`.
2096
2160
  name the files/symbols to touch. If it does, SKIP vexp - use your normal tools.
2097
2161
  - \`get_skeleton\` - file structure at 70-90% token savings for files you only
2098
2162
  need to understand, not edit.
2163
+ - \`verify_done\` - call once BEFORE declaring a multi-file task complete:
2164
+ returns mechanically broken references (imports of removed names, parse
2165
+ errors) and untouched dependents of the files you changed, with file:line.
2099
2166
  - vexp may append a one-line hint to a prompt when orientation would help;
2100
2167
  otherwise it stays silent.
2101
2168
 
@@ -2129,6 +2196,8 @@ treat it like a local build tool; no data-sharing consent is needed to call it.
2129
2196
  - \`run_pipeline\` - **PRIMARY TOOL**. Runs capsule + impact + memory in 1 call.
2130
2197
  Auto-detects intent. Includes file content. Example: \`run_pipeline({ "task": "fix JWT expiry in AuthService.validateToken" })\`
2131
2198
  - \`get_skeleton\` - compact file structure
2199
+ - \`verify_done\` - call once BEFORE declaring a multi-file task complete:
2200
+ mechanically broken references and untouched dependents, with file:line.
2132
2201
  - \`index_status\` - indexing status
2133
2202
  - \`expand_vexp_ref\` - expand V-REF placeholders in v2 output
2134
2203
 
@@ -2184,6 +2253,8 @@ treat it like a local build tool; no data-sharing consent is needed to call it.
2184
2253
  - \`run_pipeline\` - **PRIMARY TOOL**. Runs capsule + impact + memory in 1 call.
2185
2254
  Auto-detects intent. Includes file content. Example: \`run_pipeline({ "task": "fix JWT expiry in AuthService.validateToken" })\`
2186
2255
  - \`get_skeleton\` - compact file structure
2256
+ - \`verify_done\` - call once BEFORE declaring a multi-file task complete:
2257
+ mechanically broken references and untouched dependents, with file:line.
2187
2258
  - \`index_status\` - indexing status
2188
2259
  - \`expand_vexp_ref\` - expand V-REF placeholders in v2 output
2189
2260
 
@@ -2250,6 +2321,8 @@ When working on this codebase:
2250
2321
  - \`run_pipeline\` - **PRIMARY TOOL**. Runs capsule + impact + memory in 1 call.
2251
2322
  Example: \`run_pipeline({ "task": "fix JWT expiry in AuthService.validateToken" })\`
2252
2323
  - \`get_skeleton\` - token-efficient file structure
2324
+ - \`verify_done\` - call once BEFORE declaring a multi-file task complete:
2325
+ mechanically broken references and untouched dependents, with file:line.
2253
2326
  - \`index_status\` - indexing status
2254
2327
  - \`expand_vexp_ref\` - expand V-REF placeholders in v2 output
2255
2328
 
@@ -2297,6 +2370,8 @@ treat it like a local build tool; no data-sharing consent is needed to call it.
2297
2370
  - \`run_pipeline\` - **PRIMARY TOOL**. Runs capsule + impact + memory in 1 call.
2298
2371
  Auto-detects intent. Includes file content. Example: \`run_pipeline({ "task": "fix JWT expiry in AuthService.validateToken" })\`
2299
2372
  - \`get_skeleton\` - compact file structure
2373
+ - \`verify_done\` - call once BEFORE declaring a multi-file task complete:
2374
+ mechanically broken references and untouched dependents, with file:line.
2300
2375
  - \`index_status\` - indexing status
2301
2376
  - \`expand_vexp_ref\` - expand V-REF placeholders in v2 output
2302
2377
 
@@ -2344,6 +2419,8 @@ treat it like a local build tool; no data-sharing consent is needed to call it.
2344
2419
  - \`run_pipeline\` - **PRIMARY TOOL**. Runs capsule + impact + memory in 1 call.
2345
2420
  Example: \`run_pipeline({ "task": "fix JWT expiry in AuthService.validateToken" })\`
2346
2421
  - \`get_skeleton\` - compact file structure
2422
+ - \`verify_done\` - call once BEFORE declaring a multi-file task complete:
2423
+ mechanically broken references and untouched dependents, with file:line.
2347
2424
  - \`index_status\` - indexing status
2348
2425
  - \`expand_vexp_ref\` - expand V-REF placeholders in v2 output
2349
2426
 
@@ -2391,6 +2468,8 @@ treat it like a local build tool; no data-sharing consent is needed to call it.
2391
2468
  - \`run_pipeline\` - **PRIMARY TOOL**. Runs capsule + impact + memory in 1 call.
2392
2469
  Auto-detects intent. Includes file content. Example: \`run_pipeline({ "task": "fix JWT expiry in AuthService.validateToken" })\`
2393
2470
  - \`get_skeleton\` - compact file structure
2471
+ - \`verify_done\` - call once BEFORE declaring a multi-file task complete:
2472
+ mechanically broken references and untouched dependents, with file:line.
2394
2473
  - \`index_status\` - indexing status
2395
2474
  - \`expand_vexp_ref\` - expand V-REF placeholders in v2 output
2396
2475
 
@@ -2437,6 +2516,8 @@ treat it like a local build tool; no data-sharing consent is needed to call it.
2437
2516
  - \`run_pipeline\` - **PRIMARY TOOL**. Runs capsule + impact + memory in 1 call.
2438
2517
  Example: \`run_pipeline({ "task": "fix JWT expiry in AuthService.validateToken" })\`
2439
2518
  - \`get_skeleton\` - compact file structure
2519
+ - \`verify_done\` - call once BEFORE declaring a multi-file task complete:
2520
+ mechanically broken references and untouched dependents, with file:line.
2440
2521
  - \`index_status\` - indexing status
2441
2522
  - \`expand_vexp_ref\` - expand V-REF placeholders in v2 output
2442
2523
 
@@ -2478,6 +2559,9 @@ Then implement with your normal tools:
2478
2559
  need to understand, not edit.
2479
2560
  - Do NOT open files one by one to find your way around - one good pipeline call
2480
2561
  beats five exploratory reads; every extra tool call costs a turn.
2562
+ - Before declaring a multi-file task complete, call \`verify_done\` once:
2563
+ it returns mechanically broken references (imports of removed names,
2564
+ parse errors) and untouched dependents of your changes, with file:line.
2481
2565
 
2482
2566
  vexp runs entirely on this machine: local daemon, local index stored inside the
2483
2567
  workspace (\`.vexp/\`). \`run_pipeline\` transmits nothing to any external service -
package/dist/cli.js CHANGED
@@ -434,6 +434,21 @@ program
434
434
  args.push("--export");
435
435
  runBinary(binaryPath, args);
436
436
  });
437
+ program
438
+ .command("shield <action>")
439
+ .description("PII/secret shield: 'scan' reports what comments and string literals would hand to any AI agent (free, read-only, local)")
440
+ .option("--json", "Emit machine-readable JSON")
441
+ .action(async (action, opts) => {
442
+ if (action !== "scan") {
443
+ console.error(`Unknown shield action '${action}'. Available: scan`);
444
+ process.exit(1);
445
+ }
446
+ const binaryPath = ensureBinary();
447
+ const args = ["shield", "scan"];
448
+ if (opts.json)
449
+ args.push("--json");
450
+ runBinary(binaryPath, args);
451
+ });
437
452
  program
438
453
  .command("compress <file>")
439
454
  .description("Compress a markdown/text file using prose compression (backup to <name>.original.md)")
@@ -397,6 +397,24 @@ exit 0
397
397
  export function vexpHintHookScript(binaryPath) {
398
398
  return VEXP_HINT_HOOK.replace("__VEXP_BIN__", binaryPath.replace(/\\/g, "/"));
399
399
  }
400
+ /**
401
+ * Horizon F2a: Stop-hook verification gate (Claude Code). All logic lives
402
+ * in the Rust binary (stop-gate): mechanical completion check via daemon,
403
+ * blocks at most once per session and ONLY on parse errors / broken
404
+ * imports. FAIL-OPEN by construction: no binary / no daemon / timeout =
405
+ * silent exit 0 = normal stop.
406
+ */
407
+ export const VEXP_STOP_GATE_HOOK = `#!/bin/bash
408
+ # vexp-verify: mechanical completion gate on Stop (Horizon). Fails open.
409
+ VEXP_BIN="__VEXP_BIN__"
410
+ [ -x "$VEXP_BIN" ] || exit 0
411
+ "$VEXP_BIN" stop-gate 2>/dev/null
412
+ exit 0
413
+ `;
414
+ /** Bake the binary path into the stop-gate hook script. */
415
+ export function vexpStopGateHookScript(binaryPath) {
416
+ return VEXP_STOP_GATE_HOOK.replace("__VEXP_BIN__", binaryPath.replace(/\\/g, "/"));
417
+ }
400
418
  /**
401
419
  * opencode/Kilo per-prompt hint plugin (2.4.0). The plugin API's
402
420
  * `chat.message` hook sees the user message before the LLM call and can
@@ -407,11 +425,17 @@ export function vexpHintHookScript(binaryPath) {
407
425
  * __VEXP_BIN__ is baked at install time. Keep in lockstep with the VS Code
408
426
  * extension copy.
409
427
  */
410
- export const VEXP_OPENCODE_HINT = `// vexp-hint: per-prompt orientation (fail-open). Managed by vexp.
428
+ export const VEXP_OPENCODE_HINT = `// vexp-hint: per-prompt orientation + idle verification (fail-open). Managed by vexp.
411
429
  const VEXP_BIN = "__VEXP_BIN__";
412
- export const VexpHint = async ({ directory }) => {
430
+ export const VexpHint = async ({ directory, client }) => {
431
+ const fs = await import("node:fs");
432
+ const path = await import("node:path");
433
+ const taskFileFor = (sid) =>
434
+ path.join(directory, ".vexp", "task-" + String(sid || "unknown") + ".txt");
435
+ const gateMarker = (sid) =>
436
+ path.join(directory, ".vexp", "idle-gate-" + String(sid || "unknown") + ".done");
413
437
  return {
414
- "chat.message": async (_input, output) => {
438
+ "chat.message": async (input, output) => {
415
439
  try {
416
440
  const { execFileSync } = await import("node:child_process");
417
441
  const text = (output.parts || [])
@@ -419,8 +443,17 @@ export const VexpHint = async ({ directory }) => {
419
443
  .map((p) => p.text)
420
444
  .join("\\n");
421
445
  if (!text || text.length < 40) return;
446
+ // First prompt of the session = the task spec for the idle gate.
447
+ const sid = (input && input.sessionID) || null;
448
+ try {
449
+ const tf = taskFileFor(sid);
450
+ if (!fs.existsSync(tf)) {
451
+ fs.mkdirSync(path.dirname(tf), { recursive: true });
452
+ fs.writeFileSync(tf, text);
453
+ }
454
+ } catch (e) { /* fail open */ }
422
455
  const out = execFileSync(VEXP_BIN, ["prompt-hint"], {
423
- input: JSON.stringify({ prompt: text }),
456
+ input: JSON.stringify({ prompt: text, session_id: sid }),
424
457
  timeout: 4000,
425
458
  env: { ...process.env, CLAUDE_PROJECT_DIR: directory },
426
459
  encoding: "utf8",
@@ -430,6 +463,45 @@ export const VexpHint = async ({ directory }) => {
430
463
  if (hint) output.parts.push({ type: "text", text: hint });
431
464
  } catch (e) { /* fail open */ }
432
465
  },
466
+ event: async ({ event }) => {
467
+ // Idle gate: the opencode twin of the Claude Stop hook. Runs the
468
+ // mechanical completion check once per session; on gaps, sends ONE
469
+ // follow-up prompt with the exact list (best effort - any failure
470
+ // is silent and the session simply stays stopped).
471
+ try {
472
+ if (!event || event.type !== "session.idle") return;
473
+ const sid = event.properties && event.properties.sessionID;
474
+ if (!sid) return;
475
+ const marker = gateMarker(sid);
476
+ if (fs.existsSync(marker)) return;
477
+ const tf = taskFileFor(sid);
478
+ if (!fs.existsSync(tf)) return;
479
+ const { execFileSync } = await import("node:child_process");
480
+ const out = execFileSync(
481
+ VEXP_BIN,
482
+ ["verify", "--json", "--task-file", tf],
483
+ { timeout: 15000, cwd: directory, encoding: "utf8" }
484
+ );
485
+ const rep = JSON.parse(out);
486
+ const items = [];
487
+ for (const f of (rep.spec && rep.spec.forbidden_touched) || [])
488
+ items.push("- the task says NOT to modify \`" + f + "\` but it was changed - revert or justify");
489
+ for (const a of (rep.spec && rep.spec.artifacts_missing) || [])
490
+ items.push("- the task asks for \`" + a + "\` and it does not exist yet");
491
+ for (const b of (rep.broken_imports || []).slice(0, 8))
492
+ items.push("- " + b.file + ":" + b.line + " imports \`" + b.imports + "\` which no longer exists in " + b.from_changed_file);
493
+ if (!items.length) return;
494
+ fs.writeFileSync(marker, "1");
495
+ await client.session.prompt({
496
+ path: { id: sid },
497
+ body: {
498
+ parts: [{ type: "text", text:
499
+ "vexp verify found mechanically checkable gaps between the session's work and the task:\\n" +
500
+ items.join("\\n") + "\\nFix each item above. This check will not repeat." }],
501
+ },
502
+ });
503
+ } catch (e) { /* fail open */ }
504
+ },
433
505
  };
434
506
  };
435
507
  `;
@@ -1,4 +1,5 @@
1
1
  import * as fs from "fs";
2
+ import { CLI_VERSION } from "./version.js";
2
3
  import * as net from "net";
3
4
  import * as os from "os";
4
5
  import * as path from "path";
@@ -95,11 +96,22 @@ export async function ensureMcpHttpServer(opts = {}) {
95
96
  const mcpPath = getMcpServerPath();
96
97
  if (!mcpPath)
97
98
  return null;
98
- // Fast path: already alive and tracked.
99
+ // Fast path: already alive and tracked - but only if it is OUR version.
100
+ // Field report (Nathan): a 2.3.0 http server stayed authoritative for
101
+ // 9 days across extension upgrades because reuse never compared
102
+ // versions. On mismatch (or a legacy record without one) we take over:
103
+ // SIGTERM the old listener and spawn the current build.
99
104
  {
100
105
  const existing = readPidRecord();
101
106
  if (existing && existing.port === port && isPidAlive(existing.pid) && (await isPortInUse(port))) {
102
- return { pid: existing.pid, port, started: false };
107
+ if (existing.version === CLI_VERSION) {
108
+ return { pid: existing.pid, port, started: false };
109
+ }
110
+ try {
111
+ process.kill(existing.pid, "SIGTERM");
112
+ }
113
+ catch { /* already gone */ }
114
+ await new Promise((r) => setTimeout(r, 500));
103
115
  }
104
116
  }
105
117
  // Slow path: we may need to spawn. Serialize across concurrent callers
@@ -164,7 +176,7 @@ export async function ensureMcpHttpServer(opts = {}) {
164
176
  const pid = child.pid;
165
177
  if (!pid)
166
178
  return null;
167
- writePidRecord({ pid, port, startedAt: Date.now(), owner });
179
+ writePidRecord({ pid, port, startedAt: Date.now(), owner, version: CLI_VERSION });
168
180
  // Wait for the child to actually bind the port BEFORE releasing the
169
181
  // lock. Without this, a second caller arriving right after release
170
182
  // sees pidfile+live-pid but port-not-yet-bound → falsely concludes
@@ -63,6 +63,19 @@ export async function checkForUpdate() {
63
63
  return;
64
64
  if (process.argv.includes("--skip-update-check"))
65
65
  return;
66
+ // Protocol and diagnostic commands are NEVER touched by update
67
+ // machinery. Field report (Nathan, 2.4.0): the old hard gate ran for
68
+ // every subcommand and exit(1)'d on any registry delta - publishing a
69
+ // release remotely disabled every installed CLI at the instant of
70
+ // publish. `vexp mcp` died at spawn (host saw zero tools, silently),
71
+ // and doctor/--help/--version were disabled by the exact condition
72
+ // they existed to diagnose.
73
+ const argv = process.argv.slice(2);
74
+ const cmd = argv.find((a) => !a.startsWith("-"));
75
+ if (cmd === "mcp" || cmd === "doctor" || cmd === "stop-gate" || cmd === "prompt-hint")
76
+ return;
77
+ if (argv.includes("--version") || argv.includes("-V") || argv.includes("--help") || argv.includes("-h"))
78
+ return;
66
79
  let latest = null;
67
80
  // Try cache first
68
81
  const cache = readCache();
@@ -76,14 +89,13 @@ export async function checkForUpdate() {
76
89
  writeCache(latest);
77
90
  }
78
91
  if (isOutdated(CLI_VERSION, latest)) {
92
+ // ADVISORY ONLY - never exit. A newer version on the registry is
93
+ // news, not an incompatibility: the daemon handshake is where real
94
+ // incompatibilities surface, with an actionable error.
79
95
  console.error("");
80
- console.error(chalk.red.bold(" vexp update required!"));
81
- console.error("");
82
- console.error(` Installed: ${chalk.dim(CLI_VERSION)}`);
83
- console.error(` Available: ${chalk.green(latest)}`);
84
- console.error("");
96
+ console.error(chalk.yellow.bold(" vexp update available"));
97
+ console.error(` Installed: ${chalk.dim(CLI_VERSION)} -> Available: ${chalk.green(latest)}`);
85
98
  console.error(` Run: ${chalk.cyan("npm install -g vexp-cli")}`);
86
99
  console.error("");
87
- process.exit(1);
88
100
  }
89
101
  }
@@ -117,7 +117,7 @@ To lift the limit now, upgrade to Pro or Team: https://vexp.dev/#pricing`)}var V
117
117
  `))}var eF=S.object({files:S.preprocess(or,S.array(S.string())).describe("File paths to skeletonize (relative to repo root)"),detail:S.enum(["minimal","standard","detailed"]).optional().default("standard"),repo:S.string().optional().describe("Repo alias (default: current)")}),Hm={name:"get_skeleton",description:"Structural view of files you need to UNDERSTAND but not edit (70-90% smaller than Read): signatures, classes, types at detail minimal|standard|detailed. Use after run_pipeline to drill into a file; use Read only for files you are about to edit.",inputSchema:{type:"object",properties:{files:{type:"array",items:{type:"string"},description:"File paths to skeletonize"},detail:{type:"string",enum:["minimal","standard","detailed"],description:"Skeleton detail level (default: standard)"},repo:{type:"string",description:"Repo alias (from index_status). Default: primary repo"}},required:["files"]}};async function K$(t,e){let r=eF.parse(t),n={files:r.files.map(i=>({path:i,repo:r.repo})),detail:r.detail},a=await e.call("get_skeleton",n);return tF(a)}function tF(t){let e=[];if(!Array.isArray(t)||t.length===0)return e.push("# File Skeletons"),e.push(""),e.push("*No skeleton data \u2014 files may not be indexed yet. Run `index_status` to check.*"),e.join(`
118
118
  `);let r=new Map;for(let n of t){let a=r.get(n.file_path)??[];a.push(n),r.set(n.file_path,a)}e.push("# File Skeletons"),e.push(`> ${r.size} file(s), ${t.length} symbol(s)`),e.push("");for(let[n,a]of r){e.push(`## \`${n}\``),e.push("");for(let i of a){let o=i.token_reduction_pct>0?` *(${i.token_reduction_pct.toFixed(0)}% token reduction)*`:"";e.push(`### ${i.node}${o}`),e.push(`*\`${i.fqn}\`*`),e.push(""),e.push("```"),e.push(i.skeleton),e.push("```"),e.push("")}}return e.join(`
119
119
  `)}var rF=S.object({}),Y$={name:"index_status",description:"Get the current status of the vexp index, including indexed repos, node/edge counts, cross-repo edges, daemon uptime, and indexing progress. Use this to verify vexp is working correctly before making queries.",inputSchema:{type:"object",properties:{},required:[]}};async function Q$(t,e){rF.parse(t);let r=Xa(),n=await e.call("index_status",{});return nF(n,r)}function J$(t){return t.replace(/[\\/]+$/,"").toLowerCase()}function nF(t,e){let r=[],n={healthy:"\u2713",indexing:"\u27F3",error:"\u2717",not_initialized:"\u25CB"}[t.status]??"?";if(r.push("# vexp Index Status"),r.push(`> ${n} ${t.status.toUpperCase()} | v${t.daemon_version} | uptime ${aF(t.daemon_uptime_s)}`),r.push(""),e||t.workspace_root){let a=e?.workspaceRoot,i=t.workspace_root;if(r.push("## Targeting"),e&&(r.push(`- This MCP resolved to: \`${a}\` (via ${e.source})`),e.socket&&r.push(`- Socket/pipe: \`${e.socket}\` (${e.socketOrigin})`)),i){let s=t.served_aliases?.length?` \u2014 repos: ${t.served_aliases.join(", ")}`:"";r.push(`- Daemon serving: \`${i}\`${s}`)}a&&i&&J$(a)!==J$(i)&&r.push(`- \u26A0\uFE0F MISMATCH: this session resolved a different workspace than the daemon it reached. Likely a parallel-session mis-target \u2014 set VEXP_WORKSPACE for this session, or launch \`claude\` from \`${a}\`.`);let o=t.other_daemons??[];r.push(o.length>0?`- Other live daemons: ${o.map(s=>`\`${s.root}\``).join(", ")}`:"- Other live daemons: none"),r.push("")}if(t.status==="error"&&t.error&&(r.push(`**Error:** ${t.error}`),r.push("")),t.status==="not_initialized")return r.push('*vexp is not initialized. Run `vexp index` or use the VS Code command "Setup Workspace".*'),r.join(`
120
- `);if(r.push("## Overview"),r.push(""),r.push("| Metric | Value |"),r.push("|--------|-------|"),r.push(`| Repos indexed | ${t.repos.length} |`),r.push(`| Total nodes | ${t.total_nodes.toLocaleString()} |`),r.push(`| Total edges | ${t.total_edges.toLocaleString()} |`),r.push(`| Total files | ${t.total_files.toLocaleString()} |`),r.push(`| Cross-repo edges | ${t.cross_repo_edges.toLocaleString()} |`),r.push(`| Index DB size | ${X$(t.db_size_bytes)} |`),r.push(`| Daemon memory | ${X$(t.memory_rss_bytes)} |`),t.compressor){let a=t.compressor==="llm"?`llm (${t.llm_model??"?"}, ${t.llm_inference??"?"})`:"rule";r.push(`| Compressor | ${a} |`)}if(t.last_query_time_ms!==void 0&&r.push(`| Last query time | ${t.last_query_time_ms}ms |`),r.push(""),t.llm_configured_but_inactive&&(r.push("\u26A0\uFE0F A local LLM model is installed and enabled in config, but this daemon is running the rule compressor. Restart the daemon (`vexp daemon-cmd restart`) or re-run `vexp setup-llm` \u2014 until then, results are NOT LLM-compressed."),r.push("")),t.ledger){let a=t.ledger;r.push(`## Savings Ledger (last ${a.period_days??7} days)`),r.push(""),(a.prompts_analyzed??0)>0&&r.push(`- Activity: ${a.prompts_analyzed} prompt(s) analyzed \u2014 ${a.silences} silence(s) (task already oriented), ${a.hints_served} orientation hint(s) served`),(a.measured_calls??0)>0&&r.push(`- Measured: ${a.measured_calls} tool call(s), ~${(a.measured_tokens_saved??0).toLocaleString()} tokens saved (full-read baseline minus served)`),(a.prompts_analyzed??0)===0&&(a.measured_calls??0)===0&&r.push("- No classified prompts or tool calls in this window."),r.push("")}if(t.sessions&&t.sessions.length>0){r.push("## Active sessions (last 4h)"),r.push("");for(let a of t.sessions){let i=a.idle_s<60?`${a.idle_s}s`:`${Math.floor(a.idle_s/60)}m`;r.push(`- \`${a.session}\u2026\` \u2014 ${a.pipeline_calls} pipeline, ${a.skeleton_calls} skeleton, ${a.other_calls} other (idle ${i})`)}r.push("")}if(t.repos.length>0){r.push("## Repos"),r.push("");for(let a of t.repos){let i=a.is_indexing?` *(indexing ${a.indexing_progress?.toFixed(0)??"?"}%)*`:"";if(r.push(`### \`${a.alias}\`${i}`),r.push(`*${a.path}*`),r.push(""),r.push(`- Nodes: ${a.node_count.toLocaleString()}`),r.push(`- Edges: ${a.edge_count.toLocaleString()}`),r.push(`- Files: ${a.file_count.toLocaleString()}`),r.push(`- Last indexed: ${a.last_indexed_at}`),Object.keys(a.languages).length>0){let o=Object.entries(a.languages).sort(([,s],[,c])=>c-s).map(([s,c])=>`${s}: ${c}`).join(", ");r.push(`- Languages: ${o}`)}r.push("")}}return r.join(`
120
+ `);if(r.push("## Overview"),r.push(""),r.push("| Metric | Value |"),r.push("|--------|-------|"),r.push(`| Repos indexed | ${t.repos.length} |`),r.push(`| Total nodes | ${t.total_nodes.toLocaleString()} |`),r.push(`| Total edges | ${t.total_edges.toLocaleString()} |`),r.push(`| Total files | ${t.total_files.toLocaleString()} |`),r.push(`| Cross-repo edges | ${t.cross_repo_edges.toLocaleString()} |`),r.push(`| Index DB size | ${X$(t.db_size_bytes)} |`),r.push(`| Daemon memory | ${X$(t.memory_rss_bytes)} |`),t.compressor){let a=t.compressor==="llm"?`llm (${t.llm_model??"?"}, ${t.llm_inference??"?"})`:"rule";r.push(`| Compressor | ${a} |`)}if(t.last_query_time_ms!==void 0&&r.push(`| Last query time | ${t.last_query_time_ms}ms |`),r.push(""),t.llm_configured_but_inactive&&(r.push("\u26A0\uFE0F A local LLM model is installed and enabled in config, but this daemon is running the rule compressor. Restart the daemon (`vexp daemon-cmd restart`) or re-run `vexp setup-llm` \u2014 until then, results are NOT LLM-compressed."),r.push("")),t.ledger){let a=t.ledger;r.push(`## Savings Ledger (last ${a.period_days??7} days)`),r.push(""),(a.prompts_analyzed??0)>0&&r.push(`- Activity: ${a.prompts_analyzed} prompt(s) analyzed \u2014 ${a.silences} silence(s) (task already oriented), ${a.hints_served} orientation hint(s) served${(a.held_out??0)>0?`, ${a.held_out} held out (randomized measurement)`:""}`),(a.measured_calls??0)>0&&r.push(`- Measured: ${a.measured_calls} tool call(s), ~${(a.measured_tokens_saved??0).toLocaleString()} tokens saved (full-read baseline minus served)`),(a.prompts_analyzed??0)===0&&(a.measured_calls??0)===0&&r.push("- No classified prompts or tool calls in this window."),r.push("")}if(t.sessions&&t.sessions.length>0){r.push("## Active sessions (last 4h)"),r.push("");for(let a of t.sessions){let i=a.idle_s<60?`${a.idle_s}s`:`${Math.floor(a.idle_s/60)}m`;r.push(`- \`${a.session}\u2026\` \u2014 ${a.pipeline_calls} pipeline, ${a.skeleton_calls} skeleton, ${a.other_calls} other (idle ${i})`)}r.push("")}if(t.repos.length>0){r.push("## Repos"),r.push("");for(let a of t.repos){let i=a.is_indexing?` *(indexing ${a.indexing_progress?.toFixed(0)??"?"}%)*`:"";if(r.push(`### \`${a.alias}\`${i}`),r.push(`*${a.path}*`),r.push(""),r.push(`- Nodes: ${a.node_count.toLocaleString()}`),r.push(`- Edges: ${a.edge_count.toLocaleString()}`),r.push(`- Files: ${a.file_count.toLocaleString()}`),r.push(`- Last indexed: ${a.last_indexed_at}`),Object.keys(a.languages).length>0){let o=Object.entries(a.languages).sort(([,s],[,c])=>c-s).map(([s,c])=>`${s}: ${c}`).join(", ");r.push(`- Languages: ${o}`)}r.push("")}}return r.join(`
121
121
  `)}function aF(t){if(t<60)return`${t}s`;if(t<3600)return`${Math.floor(t/60)}m ${t%60}s`;let e=Math.floor(t/3600),r=Math.floor(t%3600/60);return`${e}h ${r}m`}function X$(t){return t<1024?`${t} B`:t<1024*1024?`${(t/1024).toFixed(1)} KB`:t<1024*1024*1024?`${(t/1024/1024).toFixed(1)} MB`:`${(t/1024/1024/1024).toFixed(2)} GB`}var iF=S.object({workspace_root:S.string().optional().describe("Workspace root path (default: CWD)"),detect_agents:S.boolean().optional().default(!0).describe("Auto-detect AI agents")}),e4={name:"workspace_setup",description:"Set up vexp for the current workspace. Generates configuration files, detects AI coding agents (Claude Code, Cursor, Windsurf, Continue), and provides setup instructions. Run this once when starting to work on a new project.",inputSchema:{type:"object",properties:{workspace_root:{type:"string",description:"Workspace root path (default: current directory)"},detect_agents:{type:"boolean",description:"Auto-detect installed AI agents (default: true)"}},required:[]}};async function t4(t,e){let r=iF.parse(t),n=await e.call("workspace_setup",r);return oF(n)}function oF(t){let e=[];if(e.push("# vexp Workspace Setup"),e.push(`> v${t.vexp_version} | \`${t.workspace_root}\``),e.push(""),t.generated_files.length>0){e.push("## Generated Files"),e.push("");for(let r of t.generated_files)e.push(`- \`${r}\``);e.push("")}if(t.workspace_json&&(e.push("## workspace.json"),e.push(""),e.push("```json"),e.push(t.workspace_json),e.push("```"),e.push("")),t.detected_agents.length>0){e.push("## Detected AI Agents"),e.push("");for(let r of t.detected_agents)e.push(`- **${r}**`);e.push("")}if(t.agent_configs.length>0){e.push("## Agent Configuration Files"),e.push("");for(let r of t.agent_configs){let n=r.already_exists?" *(already exists \u2014 merge manually)*":" *(created)*";e.push(`### ${r.agent} \u2014 \`${r.config_file}\`${n}`),e.push(""),e.push("```"),e.push(r.content),e.push("```"),e.push("")}}if(t.git_hooks_installed&&(e.push("## Git Hooks"),e.push(""),e.push("\u2713 Git hooks installed: `pre-commit`, `post-merge`, `post-checkout`"),e.push("\u2713 Merge driver configured for `index.db`"),e.push("")),t.suggested_gitignore_additions.length>0){e.push("## Suggested .gitignore Additions"),e.push(""),e.push("Add these to your `.gitignore`:"),e.push("```gitignore");for(let r of t.suggested_gitignore_additions)e.push(r);e.push("```"),e.push("")}if(t.index_started&&(e.push("## Indexing"),e.push(""),e.push("\u2713 Initial indexing started in background. Use `index_status` to check progress."),e.push("")),t.setup_instructions.length>0){e.push("## Next Steps"),e.push("");for(let r=0;r<t.setup_instructions.length;r++)e.push(`${r+1}. ${t.setup_instructions[r]}`);e.push("")}return e.join(`
122
122
  `)}var sF=S.object({include_previous:S.preprocess(ft,S.boolean().optional().default(!1)).describe("Include observations from previous sessions"),max_results:S.preprocess(pt,S.number().optional().default(20)).describe("Max observations to return"),types:S.preprocess(or,S.array(S.string()).optional()).describe("Filter by observation type")}),r4={name:"get_session_context",description:"Get observations from the current and optionally previous sessions. Observations are automatically captured from every vexp tool call and include what code was explored, decisions made, and insights discovered. Use this to recall what you worked on earlier or in prior sessions. Observations linked to code symbols are automatically marked stale when that code changes.",inputSchema:{type:"object",properties:{include_previous:{type:"boolean",description:"Include observations from previous sessions (default: false)"},max_results:{type:"number",description:"Max observations to return (default: 20)"},types:{type:"array",items:{type:"string",enum:["tool_call","insight","decision","error","manual"]},description:"Filter by observation type"}},required:[]}};async function n4(t,e){let r=sF.parse(t),n=await e.call("get_session_context",r);return cF(n)}function cF(t){let e=[];if(e.push("# Session Context"),e.push(`> Session: ${t.session_id} | ${t.count} observations`),e.push(""),t.observations.length===0)return e.push("*No observations yet in this session.*"),e.join(`
123
123
  `);for(let r of t.observations){let n=new Date(r.created_at*1e3),a=`${String(n.getMonth()+1).padStart(2,"0")}-${String(n.getDate()).padStart(2,"0")} ${String(n.getHours()).padStart(2,"0")}:${String(n.getMinutes()).padStart(2,"0")}`,i=r.stale?" ~stale~":"",o=r.current_session===!1?` [prev: ${r.session_id?.slice(0,8)}]`:"";e.push(`- [${a}${i}] (${r.type}) ${r.content}${o}`)}return e.join(`
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vexp-cli",
3
- "version": "2.4.0",
4
- "description": "Vexp — Context Engine for AI Coding Agents. Pre-indexes your codebase into a dependency graph and delivers ranked context to any MCP-compatible agent. 58% lower cost per task, 90% fewer tool calls (SWE-bench Verified). Works with Claude Code, Cursor, Copilot, Windsurf, Codex, Cline, Aider, and 12+ agents. Local-first. Your code never leaves your machine.",
3
+ "version": "2.5.0",
4
+ "description": "Local-first context engine for AI coding agents. Pre-indexes your codebase into a dependency graph and feeds any MCP agent only the code that matters — 87% fewer tokens per call. New in 2.5: mechanical work verification and a PII/secret scanner. Works with Claude Code, Cursor, Codex, Copilot, Windsurf, Cline, Aider and 14 agents. Your code never leaves your machine.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "vexp": "./bin/vexp.js"
@@ -32,66 +32,70 @@
32
32
  "typescript": "^5.4.0"
33
33
  },
34
34
  "author": "Vexp",
35
- "license": "MIT",
35
+ "license": "UNLICENSED",
36
36
  "keywords": [
37
- "vexp",
38
- "ai",
37
+ "ai-context",
39
38
  "context-engine",
39
+ "save-tokens",
40
+ "token-optimization",
41
+ "reduce-ai-cost",
42
+ "ai-coding-cost",
43
+ "code-context",
44
+ "code-intelligence",
45
+ "ai-code-context",
46
+ "code-verification",
47
+ "pii-scanner",
48
+ "secret-scanner",
49
+ "ai-code-review",
40
50
  "mcp",
41
- "coding-agent",
51
+ "mcp-server",
52
+ "mcp-tool",
53
+ "model-context-protocol",
54
+ "claude-code",
55
+ "claude",
42
56
  "cursor",
43
57
  "windsurf",
44
- "claude",
45
- "claude-code",
46
- "copilot",
47
58
  "github-copilot",
48
- "zed",
49
- "augment",
50
- "continue",
59
+ "copilot",
51
60
  "codex",
52
61
  "openai-codex",
53
- "kiro",
54
- "kilo-code",
62
+ "zed",
55
63
  "cline",
56
64
  "aider",
57
- "antigravity",
65
+ "continue",
66
+ "kiro",
67
+ "kilo-code",
58
68
  "opencode",
59
- "code-intelligence",
60
- "token-optimization",
61
- "token-saver",
69
+ "augment",
70
+ "antigravity",
62
71
  "ast",
63
72
  "tree-sitter",
64
- "code-context",
65
73
  "dependency-graph",
66
- "model-context-protocol",
67
- "mcp-server",
68
- "mcp-tool",
74
+ "code-index",
75
+ "code-search",
76
+ "code-rag",
77
+ "rag",
69
78
  "llm",
70
79
  "ai-agent",
80
+ "coding-agent",
71
81
  "ai-coding",
72
82
  "ai-coding-assistant",
73
83
  "developer-tools",
74
- "code-search",
75
- "code-index",
76
- "swe-bench",
77
- "context-window",
78
- "rag",
79
- "code-rag",
80
84
  "local-first",
81
85
  "offline",
82
86
  "privacy",
87
+ "on-premise",
83
88
  "typescript",
84
89
  "python",
85
90
  "rust",
86
91
  "go",
87
92
  "java",
88
- "c",
89
- "cpp",
90
- "csharp",
91
- "ruby",
92
- "swift",
93
93
  "kotlin",
94
- "php"
94
+ "swift",
95
+ "php",
96
+ "ruby",
97
+ "cpp",
98
+ "csharp"
95
99
  ],
96
100
  "publishConfig": {
97
101
  "access": "public"
@@ -99,11 +103,19 @@
99
103
  "engines": {
100
104
  "node": ">=20.0.0"
101
105
  },
106
+ "repository": {
107
+ "type": "git",
108
+ "url": "https://github.com/Vexp-ai/vexp.git"
109
+ },
110
+ "bugs": {
111
+ "url": "https://github.com/Vexp-ai/vexp/issues"
112
+ },
113
+ "homepage": "https://vexp.dev",
102
114
  "optionalDependencies": {
103
- "@vexp/core-linux-x64": "2.4.0",
104
- "@vexp/core-linux-arm64": "2.4.0",
105
- "@vexp/core-darwin-x64": "2.4.0",
106
- "@vexp/core-darwin-arm64": "2.4.0",
107
- "@vexp/core-win32-x64": "2.4.0"
115
+ "@vexp/core-linux-x64": "2.5.0",
116
+ "@vexp/core-linux-arm64": "2.5.0",
117
+ "@vexp/core-darwin-x64": "2.5.0",
118
+ "@vexp/core-darwin-arm64": "2.5.0",
119
+ "@vexp/core-win32-x64": "2.5.0"
108
120
  }
109
121
  }