vexp-cli 2.4.0 → 2.5.1
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 +48 -79
- package/dist/agent-config.js +85 -1
- package/dist/cli.js +46 -0
- package/dist/hook-template.js +76 -4
- package/dist/mcp-supervisor.js +15 -3
- package/dist/update-check.js +18 -6
- package/mcp/mcp-server.cjs +1 -1
- package/package.json +44 -39
package/README.md
CHANGED
|
@@ -1,108 +1,77 @@
|
|
|
1
|
-
# vexp — Context Engine for AI Coding Agents
|
|
1
|
+
# vexp-cli — Context Engine for AI Coding Agents (Claude Code, Cursor, Codex)
|
|
2
2
|
|
|
3
|
-
**
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
13
|
+
[](https://www.npmjs.com/package/vexp-cli)
|
|
14
|
+
[](https://www.npmjs.com/package/vexp-cli)
|
|
15
|
+

|
|
10
16
|
|
|
11
|
-
|
|
17
|
+
---
|
|
12
18
|
|
|
13
|
-
|
|
19
|
+
## Why developers install vexp
|
|
14
20
|
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
46
|
+
# 2. Configure your agents (auto-detected) + build the graph
|
|
47
|
+
cd your-project
|
|
48
|
+
vexp setup
|
|
49
|
+
vexp index
|
|
68
50
|
|
|
69
|
-
|
|
51
|
+
# 3. Use your agent normally. That's it.
|
|
52
|
+
# Check savings any time:
|
|
53
|
+
vexp savings
|
|
70
54
|
|
|
71
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
62
|
+
## Supported agents
|
|
86
63
|
|
|
87
|
-
|
|
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
|
-
|
|
66
|
+
## Supported languages
|
|
93
67
|
|
|
94
|
-
|
|
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
|
-
-
|
|
104
|
-
- [
|
|
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)
|
|
105
74
|
|
|
106
|
-
|
|
75
|
+
---
|
|
107
76
|
|
|
108
|
-
|
|
77
|
+
**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
|
package/dist/agent-config.js
CHANGED
|
@@ -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,52 @@ program
|
|
|
434
434
|
args.push("--export");
|
|
435
435
|
runBinary(binaryPath, args);
|
|
436
436
|
});
|
|
437
|
+
program
|
|
438
|
+
.command("verify")
|
|
439
|
+
.description("Mechanical completion check of the working tree: parse errors, broken imports, dependents of changed files not yet touched (advice, never a block)")
|
|
440
|
+
.option("--json", "Emit raw JSON instead of the human report")
|
|
441
|
+
.option("--task-file <path>", "Task/instruction text: also checks instruction-level oracles (forbidden paths, promised artifacts)")
|
|
442
|
+
.option("--gate", "CI gate mode (Horizon): exit 2 on any mechanical violation")
|
|
443
|
+
.action(async (opts) => {
|
|
444
|
+
const binaryPath = ensureBinary();
|
|
445
|
+
const args = ["verify"];
|
|
446
|
+
if (opts.json)
|
|
447
|
+
args.push("--json");
|
|
448
|
+
if (opts.taskFile)
|
|
449
|
+
args.push("--task-file", opts.taskFile);
|
|
450
|
+
if (opts.gate)
|
|
451
|
+
args.push("--gate");
|
|
452
|
+
runBinary(binaryPath, args);
|
|
453
|
+
});
|
|
454
|
+
program
|
|
455
|
+
.command("stop [workspace]")
|
|
456
|
+
.description("Stop the vexp daemon of a workspace (default: current directory). Accepts any path shown by the concurrent-workspace cap message")
|
|
457
|
+
.action(async (workspace) => {
|
|
458
|
+
const binaryPath = ensureBinary();
|
|
459
|
+
const target = path.resolve(workspace ?? process.cwd());
|
|
460
|
+
if (!fs.existsSync(target)) {
|
|
461
|
+
console.error(`No such directory: ${target}`);
|
|
462
|
+
process.exit(1);
|
|
463
|
+
}
|
|
464
|
+
// daemon-cmd stop resolves the daemon from its cwd, so run it from the
|
|
465
|
+
// target workspace — this is what makes `vexp stop <workspace>` global.
|
|
466
|
+
runBinary(binaryPath, ["daemon-cmd", "stop"], { cwd: target });
|
|
467
|
+
});
|
|
468
|
+
program
|
|
469
|
+
.command("shield <action>")
|
|
470
|
+
.description("PII/secret shield: 'scan' reports what comments and string literals would hand to any AI agent (free, read-only, local)")
|
|
471
|
+
.option("--json", "Emit machine-readable JSON")
|
|
472
|
+
.action(async (action, opts) => {
|
|
473
|
+
if (action !== "scan") {
|
|
474
|
+
console.error(`Unknown shield action '${action}'. Available: scan`);
|
|
475
|
+
process.exit(1);
|
|
476
|
+
}
|
|
477
|
+
const binaryPath = ensureBinary();
|
|
478
|
+
const args = ["shield", "scan"];
|
|
479
|
+
if (opts.json)
|
|
480
|
+
args.push("--json");
|
|
481
|
+
runBinary(binaryPath, args);
|
|
482
|
+
});
|
|
437
483
|
program
|
|
438
484
|
.command("compress <file>")
|
|
439
485
|
.description("Compress a markdown/text file using prose compression (backup to <name>.original.md)")
|
package/dist/hook-template.js
CHANGED
|
@@ -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 (
|
|
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
|
`;
|
package/dist/mcp-supervisor.js
CHANGED
|
@@ -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
|
-
|
|
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
|
package/dist/update-check.js
CHANGED
|
@@ -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.
|
|
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
|
}
|
package/mcp/mcp-server.cjs
CHANGED
|
@@ -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
|
-
"description": "
|
|
3
|
+
"version": "2.5.1",
|
|
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": "
|
|
35
|
+
"license": "UNLICENSED",
|
|
36
36
|
"keywords": [
|
|
37
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
49
|
-
"augment",
|
|
50
|
-
"continue",
|
|
59
|
+
"copilot",
|
|
51
60
|
"codex",
|
|
52
61
|
"openai-codex",
|
|
53
|
-
"
|
|
54
|
-
"kilo-code",
|
|
62
|
+
"zed",
|
|
55
63
|
"cline",
|
|
56
64
|
"aider",
|
|
57
|
-
"
|
|
65
|
+
"continue",
|
|
66
|
+
"kiro",
|
|
67
|
+
"kilo-code",
|
|
58
68
|
"opencode",
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"token-saver",
|
|
69
|
+
"augment",
|
|
70
|
+
"antigravity",
|
|
62
71
|
"ast",
|
|
63
72
|
"tree-sitter",
|
|
64
|
-
"code-context",
|
|
65
73
|
"dependency-graph",
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
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
|
-
"
|
|
94
|
+
"swift",
|
|
95
|
+
"php",
|
|
96
|
+
"ruby",
|
|
97
|
+
"cpp",
|
|
98
|
+
"csharp"
|
|
95
99
|
],
|
|
96
100
|
"publishConfig": {
|
|
97
101
|
"access": "public"
|
|
@@ -99,11 +103,12 @@
|
|
|
99
103
|
"engines": {
|
|
100
104
|
"node": ">=20.0.0"
|
|
101
105
|
},
|
|
106
|
+
"homepage": "https://vexp.dev",
|
|
102
107
|
"optionalDependencies": {
|
|
103
|
-
"@vexp/core-linux-x64": "2.
|
|
104
|
-
"@vexp/core-linux-arm64": "2.
|
|
105
|
-
"@vexp/core-darwin-x64": "2.
|
|
106
|
-
"@vexp/core-darwin-arm64": "2.
|
|
107
|
-
"@vexp/core-win32-x64": "2.
|
|
108
|
+
"@vexp/core-linux-x64": "2.5.1",
|
|
109
|
+
"@vexp/core-linux-arm64": "2.5.1",
|
|
110
|
+
"@vexp/core-darwin-x64": "2.5.1",
|
|
111
|
+
"@vexp/core-darwin-arm64": "2.5.1",
|
|
112
|
+
"@vexp/core-win32-x64": "2.5.1"
|
|
108
113
|
}
|
|
109
114
|
}
|