vexp-cli 2.3.1 → 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.
@@ -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 } 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
  // ---------------------------------------------------------------------------
@@ -320,6 +320,19 @@ export function configureAgents(workspaceRoot, binaryPath, version, agentFilter,
320
320
  const wrote = configureClaudeCodeGlobal(binaryPath, mcpServerPath, workspaceRoot);
321
321
  if (wrote)
322
322
  mcpConfigs.push("~/.claude.json");
323
+ // Event-driven hint hook: default-on (non-blocking, fail-open — the
324
+ // 2.3 A2 opt-in applies to DENY hooks, this one cannot block).
325
+ const hintResult = installClaudeCodeHintHook(workspaceRoot, binaryPath);
326
+ installClaudeCodeStopGate(workspaceRoot, binaryPath);
327
+ if (hintResult) {
328
+ results.push({
329
+ agent: "Claude Code Hint",
330
+ configFile: ".claude/hooks/vexp-hint.sh",
331
+ content: vexpHintHookScript(binaryPath),
332
+ alreadyExists: false,
333
+ action: hintResult,
334
+ });
335
+ }
323
336
  // Guard is OPT-IN from 2.3 (see GuardMode): strict installs the
324
337
  // PreToolUse deny hook; default removes any previously installed one.
325
338
  if (guardMode() === "strict") {
@@ -349,6 +362,7 @@ export function configureAgents(workspaceRoot, binaryPath, version, agentFilter,
349
362
  const wrote = configureCodexGlobal(binaryPath, mcpServerPath, workspaceRoot);
350
363
  if (wrote)
351
364
  mcpConfigs.push("~/.codex/config.toml");
365
+ installCodexHintHook(workspaceRoot, binaryPath);
352
366
  }
353
367
  // Antigravity: machine-global MCP config (~/.gemini/antigravity/mcp_config.json)
354
368
  if (detector.agent === "Antigravity") {
@@ -376,6 +390,7 @@ export function configureAgents(workspaceRoot, binaryPath, version, agentFilter,
376
390
  const wrote = configureKiloMcp(workspaceRoot, binaryPath, mcpServerPath);
377
391
  if (wrote)
378
392
  mcpConfigs.push(wrote);
393
+ installOpencodeHintPlugin(workspaceRoot, binaryPath, ".kilo/plugin");
379
394
  // Kilo v7 vendors opencode, so it takes the same guard plugin — the rules
380
395
  // markdown alone was demonstrably not enough (a reported session loaded
381
396
  // vexp.md, quoted it back, and still read five files by hand).
@@ -437,6 +452,7 @@ export function configureAgents(workspaceRoot, binaryPath, version, agentFilter,
437
452
  // Claude Code PreToolUse hook) — which is only safe once MCP is registered.
438
453
  if (detector.agent === "Opencode") {
439
454
  const wroteMcp = configureOpencodeMcp(workspaceRoot, binaryPath, mcpServerPath);
455
+ installOpencodeHintPlugin(workspaceRoot, binaryPath, ".opencode/plugin");
440
456
  if (wroteMcp)
441
457
  mcpConfigs.push(wroteMcp);
442
458
  if (guardMode() === "strict") {
@@ -607,6 +623,8 @@ export function configureSelectedAgents(workspaceRoot, binaryPath, version, sele
607
623
  const wrote = configureClaudeCodeGlobal(binaryPath, mcpServerPath, workspaceRoot);
608
624
  if (wrote)
609
625
  mcpConfigs.push("~/.claude.json");
626
+ installClaudeCodeHintHook(workspaceRoot, binaryPath);
627
+ installClaudeCodeStopGate(workspaceRoot, binaryPath);
610
628
  if (guardMode() === "strict")
611
629
  installClaudeCodeHook(workspaceRoot);
612
630
  else
@@ -616,6 +634,7 @@ export function configureSelectedAgents(workspaceRoot, binaryPath, version, sele
616
634
  const wrote = configureCodexGlobal(binaryPath, mcpServerPath, workspaceRoot);
617
635
  if (wrote)
618
636
  mcpConfigs.push("~/.codex/config.toml");
637
+ installCodexHintHook(workspaceRoot, binaryPath);
619
638
  }
620
639
  if (detector.agent === "Antigravity") {
621
640
  const wrote = configureAntigravityGlobal(binaryPath, mcpServerPath);
@@ -636,6 +655,7 @@ export function configureSelectedAgents(workspaceRoot, binaryPath, version, sele
636
655
  const wrote = configureOpencodeMcp(workspaceRoot, binaryPath, mcpServerPath);
637
656
  if (wrote)
638
657
  mcpConfigs.push(wrote);
658
+ installOpencodeHintPlugin(workspaceRoot, binaryPath, ".opencode/plugin");
639
659
  if (guardMode() === "strict")
640
660
  installOpencodePlugin(workspaceRoot);
641
661
  else
@@ -655,6 +675,7 @@ export function configureSelectedAgents(workspaceRoot, binaryPath, version, sele
655
675
  const wrote = configureKiloMcp(workspaceRoot, binaryPath, mcpServerPath);
656
676
  if (wrote)
657
677
  mcpConfigs.push(wrote);
678
+ installOpencodeHintPlugin(workspaceRoot, binaryPath, ".kilo/plugin");
658
679
  if (guardMode() === "strict")
659
680
  installKiloPlugin(workspaceRoot);
660
681
  else
@@ -1634,20 +1655,23 @@ export function installClaudeCodeHook(workspaceRoot) {
1634
1655
  const existingPreToolUse = Array.isArray(hooks.PreToolUse) ? hooks.PreToolUse : [];
1635
1656
  // Aggressively remove ALL vexp-related hook entries (old format, stale matchers, malformed)
1636
1657
  const filtered = existingPreToolUse.filter((h) => !isVexpGuardHookEntry(h));
1637
- // Exec form (`args` present → direct spawn, no `sh -c`): the old shell-form
1638
- // `$CLAUDE_PROJECT_DIR/...` word-split on project paths containing spaces,
1639
- // so sh tried to exec the path's first fragment and the guard NEVER ran —
1640
- // non-blocking failure, every search went through unguarded. In exec form
1641
- // Claude Code substitutes `${CLAUDE_PROJECT_DIR}` itself (brace form
1642
- // required) before spawning. `timeout` is SECONDS (default 600), not ms:
1643
- // 3000 configured a 50-minute hook timeout.
1658
+ // Shell form with an explicit `bash` prefix and a QUOTED path — the one
1659
+ // shape that works on every OS Claude Code supports:
1660
+ // - the quotes fix the historical spaces bug (unquoted
1661
+ // `$CLAUDE_PROJECT_DIR/...` word-split and the guard never ran);
1662
+ // - exec form (`args` present → direct spawn) fixed spaces but broke
1663
+ // Windows entirely: the OS cannot spawn a `.sh` without a shell, the
1664
+ // hook errored, failed open, and enforced nothing (field report) —
1665
+ // Claude Code on Windows runs shell-form hooks through Git Bash, so
1666
+ // `bash "$VAR"` works there and on unix alike.
1667
+ // `timeout` is SECONDS (default 600), not ms: 3000 once configured a
1668
+ // 50-minute hook timeout.
1644
1669
  filtered.push({
1645
1670
  matcher: "Grep|Glob|Regex",
1646
1671
  hooks: [
1647
1672
  {
1648
1673
  type: "command",
1649
- command: "${CLAUDE_PROJECT_DIR}/.claude/hooks/vexp-guard.sh",
1650
- args: [],
1674
+ command: 'bash "$CLAUDE_PROJECT_DIR/.claude/hooks/vexp-guard.sh"',
1651
1675
  timeout: 5,
1652
1676
  },
1653
1677
  ],
@@ -1667,6 +1691,206 @@ export function installClaudeCodeHook(workspaceRoot) {
1667
1691
  }
1668
1692
  return existed ? "updated" : "created";
1669
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
+ }
1756
+ function isVexpHintHookEntry(h) {
1757
+ if (!h || typeof h !== "object")
1758
+ return false;
1759
+ const entry = h;
1760
+ const own = entry.command?.includes("vexp-hint");
1761
+ if (own)
1762
+ return true;
1763
+ const hks = entry.hooks;
1764
+ return Array.isArray(hks) && hks.some((hook) => {
1765
+ if (!hook || typeof hook !== "object")
1766
+ return false;
1767
+ return hook.command?.includes("vexp-hint") ?? false;
1768
+ });
1769
+ }
1770
+ /**
1771
+ * Install the event-driven hint hook for Claude Code (2.3.3 default).
1772
+ * Replaces the resident CLAUDE.md strategy text: a UserPromptSubmit hook
1773
+ * asks the daemon to classify each prompt against the index and injects a
1774
+ * one-line run_pipeline suggestion ONLY for unfamiliar-area prompts. On
1775
+ * oriented prompts the injection is empty — zero context cost. Fail-open by
1776
+ * construction (see VEXP_HINT_HOOK): any failure = vanilla behavior.
1777
+ * Non-blocking and additive, so it is NOT gated by guardMode (the 2.3 A2
1778
+ * opt-in applies to DENY hooks, which add a failure mode; this one cannot
1779
+ * block anything).
1780
+ */
1781
+ export function installClaudeCodeHintHook(workspaceRoot, binaryPath) {
1782
+ const hookDir = path.join(workspaceRoot, ".claude", "hooks");
1783
+ const hookPath = path.join(hookDir, "vexp-hint.sh");
1784
+ const settingsPath = path.join(workspaceRoot, ".claude", "settings.json");
1785
+ const script = vexpHintHookScript(binaryPath);
1786
+ fs.mkdirSync(hookDir, { recursive: true });
1787
+ const existed = fs.existsSync(hookPath);
1788
+ const scriptIdentical = existed && fs.readFileSync(hookPath, "utf-8") === script;
1789
+ if (!scriptIdentical) {
1790
+ fs.writeFileSync(hookPath, script, { mode: 0o755 });
1791
+ }
1792
+ const read = readJsonConfigSafe(settingsPath);
1793
+ if (!read.ok) {
1794
+ warnUnparseable(settingsPath);
1795
+ return scriptIdentical ? null : existed ? "updated" : "created";
1796
+ }
1797
+ const settings = read.data;
1798
+ const hooks = (settings.hooks ?? {});
1799
+ const existing = Array.isArray(hooks.UserPromptSubmit)
1800
+ ? hooks.UserPromptSubmit
1801
+ : [];
1802
+ const filtered = existing.filter((h) => !isVexpHintHookEntry(h));
1803
+ // Same cross-OS shape as the guard entry: bash prefix + QUOTED path
1804
+ // (spaces-safe on unix, Git Bash on Windows). timeout is SECONDS.
1805
+ filtered.push({
1806
+ hooks: [
1807
+ {
1808
+ type: "command",
1809
+ command: 'bash "$CLAUDE_PROJECT_DIR/.claude/hooks/vexp-hint.sh"',
1810
+ timeout: 5,
1811
+ },
1812
+ ],
1813
+ });
1814
+ const merged = { ...hooks, UserPromptSubmit: filtered };
1815
+ const settingsIdentical = JSON.stringify(merged) === JSON.stringify(settings.hooks ?? {});
1816
+ if (scriptIdentical && settingsIdentical)
1817
+ return null;
1818
+ settings.hooks = merged;
1819
+ if (!settingsIdentical) {
1820
+ if (read.existed)
1821
+ backupConfig(settingsPath);
1822
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
1823
+ }
1824
+ return existed ? "updated" : "created";
1825
+ }
1826
+ /**
1827
+ * opencode/Kilo hint plugin (2.4.0): default-ON, non-blocking, fail-open —
1828
+ * unlike the deny guard (strict-only), this cannot break a session; it only
1829
+ * ever appends an orientation line the daemon judged useful.
1830
+ */
1831
+ export function installOpencodeHintPlugin(workspaceRoot, binaryPath, pluginDir) {
1832
+ const pluginPath = path.join(workspaceRoot, pluginDir, "vexp-hint.js");
1833
+ const content = vexpOpencodeHintPlugin(binaryPath);
1834
+ fs.mkdirSync(path.dirname(pluginPath), { recursive: true });
1835
+ const existed = fs.existsSync(pluginPath);
1836
+ if (existed && fs.readFileSync(pluginPath, "utf-8") === content)
1837
+ return null;
1838
+ fs.writeFileSync(pluginPath, content);
1839
+ return existed ? "updated" : "created";
1840
+ }
1841
+ /**
1842
+ * Codex hint hook (2.4.0). Codex >=0.129 discovers `<repo>/.codex/hooks.json`
1843
+ * and its UserPromptSubmit stdout envelope is byte-identical to Claude
1844
+ * Code's (hookSpecificOutput.additionalContext), so the same prompt-hint
1845
+ * backend serves both: PROJECT-scope file, no global config touched.
1846
+ * Fail-open: missing bash/binary/daemon => no output => vanilla.
1847
+ */
1848
+ export function installCodexHintHook(workspaceRoot, binaryPath) {
1849
+ const dir = path.join(workspaceRoot, ".codex");
1850
+ const hookPath = path.join(dir, "vexp-hint.sh");
1851
+ const hooksJsonPath = path.join(dir, "hooks.json");
1852
+ const script = vexpHintHookScript(binaryPath);
1853
+ fs.mkdirSync(dir, { recursive: true });
1854
+ const existed = fs.existsSync(hookPath);
1855
+ const scriptIdentical = existed && fs.readFileSync(hookPath, "utf-8") === script;
1856
+ if (!scriptIdentical) {
1857
+ fs.writeFileSync(hookPath, script, { mode: 0o755 });
1858
+ }
1859
+ let root = {};
1860
+ if (fs.existsSync(hooksJsonPath)) {
1861
+ try {
1862
+ root = JSON.parse(fs.readFileSync(hooksJsonPath, "utf-8"));
1863
+ }
1864
+ catch {
1865
+ warnUnparseable(hooksJsonPath);
1866
+ return scriptIdentical ? null : existed ? "updated" : "created";
1867
+ }
1868
+ }
1869
+ // Codex hooks.json shape: EVENT NAME at top level (no "hooks" wrapper).
1870
+ const existing = Array.isArray(root.UserPromptSubmit)
1871
+ ? root.UserPromptSubmit
1872
+ : [];
1873
+ const filtered = existing.filter((h) => !isVexpHintHookEntry(h));
1874
+ // Absolute path: Codex has no $CLAUDE_PROJECT_DIR substitution.
1875
+ filtered.push({
1876
+ hooks: [
1877
+ {
1878
+ type: "command",
1879
+ command: `bash "${hookPath.replace(/\\/g, "/")}"`,
1880
+ timeout: 5,
1881
+ },
1882
+ ],
1883
+ });
1884
+ const before = JSON.stringify(root.UserPromptSubmit ?? null);
1885
+ root.UserPromptSubmit = filtered;
1886
+ const identical = before === JSON.stringify(filtered);
1887
+ if (scriptIdentical && identical)
1888
+ return null;
1889
+ if (!identical) {
1890
+ fs.writeFileSync(hooksJsonPath, JSON.stringify(root, null, 2) + "\n");
1891
+ }
1892
+ return existed ? "updated" : "created";
1893
+ }
1670
1894
  // ---------------------------------------------------------------------------
1671
1895
  // opencode plugin - blocks grep/glob (and shelled-out search) when the daemon
1672
1896
  // is healthy. opencode has no PreToolUse hook, but auto-loads plugins from
@@ -1929,90 +2153,33 @@ function generateAgentConfig(template, vars) {
1929
2153
  // ---------------------------------------------------------------------------
1930
2154
  function claudeCodeTemplate(vars) {
1931
2155
  return `## vexp - Context-Aware AI Coding <!-- vexp v${vars.version} -->
1932
-
1933
- ### Context strategy: one pipeline call up front, then your normal tools
1934
- For every multi-file task - bug fixes, features, refactors, debugging:
1935
- **call \`run_pipeline\` ONCE at the start**. It executes context search + impact
1936
- analysis + memory recall in a single call: which files matter, what breaks,
1937
- what was learned before. Then implement with your normal tools.
1938
-
1939
- Division of labor (each tool where it wins):
1940
- - **Architecture, impact, "where is X wired"**: \`run_pipeline\` - the graph
1941
- answers these better and cheaper than exploratory reading.
1942
- - **Literal text sweeps** (string constants, log messages, config keys, e.g.
1943
- \`localStorage\`, API paths): Grep/Glob directly - text search is what they
1944
- are best at, do NOT route text sweeps through vexp.
1945
- - **Editing**: Read the files you are about to modify; use \`get_skeleton\`
1946
- (detail: minimal/standard/detailed, 70-90% token savings) for files you only
2156
+ vexp runs entirely on this machine: local daemon, index in \`.vexp/\`.
2157
+ \`run_pipeline\` transmits nothing to any external service.
2158
+ - \`run_pipeline({ "task": "..." })\` - orientation in one call (ranked pivot
2159
+ files with line ranges + blast radius + session notes) when a task does NOT
2160
+ name the files/symbols to touch. If it does, SKIP vexp - use your normal tools.
2161
+ - \`get_skeleton\` - file structure at 70-90% token savings for files you only
1947
2162
  need to understand, not edit.
1948
-
1949
- Every extra tool call costs a turn: one good \`run_pipeline\` call beats five
1950
- small ones. Each result opens with a coverage header (index: N files, M nodes) -
1951
- if it does not match this repo, run \`vexp index\`.
1952
- vexp only covers indexed source inside the workspace: for runtime logs, build
1953
- output (dist/, .vite/, node_modules/) or files outside the repo use Bash/Read.
1954
- vexp runs entirely on this machine: local daemon, local index stored inside the
1955
- workspace (\`.vexp/\`). \`run_pipeline\` transmits nothing to any external service -
1956
- treat it like a local build tool; no data-sharing consent is needed to call it.
1957
-
1958
- ### Primary Tool
1959
- - \`run_pipeline\` - **USE THIS FOR EVERYTHING**. Single call that runs
1960
- capsule + impact + memory server-side. Returns compressed results.
1961
- Auto-detects intent (debug/modify/refactor/explore) from your task.
1962
- Includes full file content for pivots.
1963
- Examples:
1964
- - \`run_pipeline({ "task": "fix JWT validation bug" })\` - auto-detect
1965
- - \`run_pipeline({ "task": "refactor db layer", "preset": "refactor" })\` - explicit
1966
- - \`run_pipeline({ "task": "add auth", "observation": "using JWT" })\` - save insight in same call
1967
-
1968
- ### Other MCP tools (use only when run_pipeline is insufficient)
1969
- - \`get_skeleton\` - **preferred over Read** for inspecting files (minimal/standard/detailed detail levels, 70-90% token savings)
1970
- - \`index_status\` - indexing status and health check
1971
- - \`expand_vexp_ref\` - expand V-REF hash placeholders in v2 compact output
1972
-
1973
- ${QUERY_SHAPE}
1974
-
1975
- ### Workflow
1976
- 1. \`run_pipeline("your task")\` - ONCE at task start. Returns pivots + impact + memories in 1 call
1977
- 2. Literal string sweeps? Grep/Glob directly. Editing a file? Read it directly.
1978
- 3. Structural overview of a non-edit file? \`get_skeleton({ files: [...], detail: "detailed" })\`
1979
- 4. Make targeted changes based on the context returned
1980
- 5. \`run_pipeline\` again ONLY when the task moves to a new area - do NOT chain vexp calls per turn
1981
-
1982
- ### Subagent / Explore / Plan mode
1983
- - Subagents CAN call \`run_pipeline\` - always include the task description
1984
- - Before spawning Agent(Explore) for architecture questions, call \`run_pipeline\`
1985
- and pass the returned context into the agent prompt - it usually replaces the
1986
- exploration entirely
1987
-
1988
- ### Smart Features (automatic - no action needed)
1989
- - **Intent Detection**: auto-detects from your task keywords. "fix bug" -> Debug, "refactor" -> blast-radius, "add" -> Modify
1990
- - **Hybrid Search**: keyword + semantic + graph centrality ranking
1991
- - **Session Memory**: auto-captures observations; memories auto-surfaced in results
1992
- - **LSP Bridge**: VS Code captures type-resolved call edges
1993
- - **Change Coupling**: co-changed files included as related context
1994
-
1995
- ### Advanced Parameters
1996
- - \`preset: "debug"\` - forces debug mode (capsule+tests+impact+memory)
1997
- - \`preset: "refactor"\` - deep impact analysis (depth 5)
1998
- - \`max_tokens: 12000\` - increase total budget for complex tasks
1999
- - \`include_tests: true\` - include test files in results
2000
- - \`include_file_content: false\` - omit full file content (lighter response)
2001
-
2002
- ### Fallback
2003
- If \`run_pipeline\` returns \`status: "degraded"\` or 0 pivots with an INDEX EMPTY warning,
2004
- the index is empty or rebuilding. Use Grep, Glob, and Read directly until the index is ready.
2005
-
2006
- ### Multi-Repo Workspaces
2007
- \`run_pipeline\` auto-queries all indexed repos. Use \`repos: ["alias"]\` to scope.
2008
- Use \`index_status\` to discover available repo aliases.
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.
2166
+ - vexp may append a one-line hint to a prompt when orientation would help;
2167
+ otherwise it stays silent.
2168
+
2169
+ ### Query shape (do this)
2170
+ Anchor the task on real identifiers (ClassName, functionName) or file paths:
2171
+ \`run_pipeline({ "task": "fix JWT expiry in AuthService.validateToken" })\`
2009
2172
  <!-- /vexp -->`;
2010
2173
  }
2011
2174
  function cursorTemplate(vars) {
2012
2175
  return `## vexp rules for Cursor <!-- vexp v${vars.version} -->
2013
2176
 
2014
2177
  **Call \`run_pipeline\` ONCE at task start for orientation - then use your normal tools.**
2178
+ If the task already names the files/symbols to touch, skip vexp and work
2179
+ with your normal tools - a skipped call costs nothing.
2015
2180
  vexp returns graph-ranked context (relevant files, blast radius, memories) in one call.
2181
+ On non-trivial tasks pass \`eager: true\`: the single response then carries full file
2182
+ bodies, covering tests and impact - work directly from it instead of re-reading sources.
2016
2183
  For literal string sweeps (constants, log messages, config keys) use native search
2017
2184
  directly - do NOT route text sweeps through vexp.
2018
2185
  vexp runs entirely on this machine: local daemon, local index stored inside the
@@ -2029,9 +2196,15 @@ treat it like a local build tool; no data-sharing consent is needed to call it.
2029
2196
  - \`run_pipeline\` - **PRIMARY TOOL**. Runs capsule + impact + memory in 1 call.
2030
2197
  Auto-detects intent. Includes file content. Example: \`run_pipeline({ "task": "fix JWT expiry in AuthService.validateToken" })\`
2031
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.
2032
2201
  - \`index_status\` - indexing status
2033
2202
  - \`expand_vexp_ref\` - expand V-REF placeholders in v2 output
2034
2203
 
2204
+ (Recommended core set, not the full schema: paid plans advertise 14 MCP tools -
2205
+ capsule, impact graph, logic flow, memory and more. \`run_pipeline\` already runs
2206
+ those server-side, so the four above cover the normal workflow.)
2207
+
2035
2208
  ${QUERY_SHAPE}
2036
2209
 
2037
2210
  ### Agentic search
@@ -2059,7 +2232,11 @@ function windsurfTemplate(vars) {
2059
2232
  return `## vexp for Windsurf <!-- vexp v${vars.version} -->
2060
2233
 
2061
2234
  **Call \`run_pipeline\` ONCE at task start for orientation - then use your normal tools.**
2235
+ If the task already names the files/symbols to touch, skip vexp and work
2236
+ with your normal tools - a skipped call costs nothing.
2062
2237
  vexp returns graph-ranked context (relevant files, blast radius, memories) in one call.
2238
+ On non-trivial tasks pass \`eager: true\`: the single response then carries full file
2239
+ bodies, covering tests and impact - work directly from it instead of re-reading sources.
2063
2240
  For literal string sweeps (constants, log messages, config keys) use native search
2064
2241
  directly - do NOT route text sweeps through vexp.
2065
2242
  vexp runs entirely on this machine: local daemon, local index stored inside the
@@ -2076,9 +2253,15 @@ treat it like a local build tool; no data-sharing consent is needed to call it.
2076
2253
  - \`run_pipeline\` - **PRIMARY TOOL**. Runs capsule + impact + memory in 1 call.
2077
2254
  Auto-detects intent. Includes file content. Example: \`run_pipeline({ "task": "fix JWT expiry in AuthService.validateToken" })\`
2078
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.
2079
2258
  - \`index_status\` - indexing status
2080
2259
  - \`expand_vexp_ref\` - expand V-REF placeholders in v2 output
2081
2260
 
2261
+ (Recommended core set, not the full schema: paid plans advertise 14 MCP tools -
2262
+ capsule, impact graph, logic flow, memory and more. \`run_pipeline\` already runs
2263
+ those server-side, so the four above cover the normal workflow.)
2264
+
2082
2265
  ${QUERY_SHAPE}
2083
2266
 
2084
2267
  ### Agentic search
@@ -2117,7 +2300,11 @@ function augmentTemplate(vars) {
2117
2300
  return `## vexp for Augment <!-- vexp v${vars.version} -->
2118
2301
 
2119
2302
  **Call \`run_pipeline\` ONCE at task start for orientation - then use your normal tools.**
2303
+ If the task already names the files/symbols to touch, skip vexp and work
2304
+ with your normal tools - a skipped call costs nothing.
2120
2305
  vexp returns graph-ranked context (relevant files, blast radius, memories) in one call.
2306
+ On non-trivial tasks pass \`eager: true\`: the single response then carries full file
2307
+ bodies, covering tests and impact - work directly from it instead of re-reading sources.
2121
2308
  For literal string sweeps (constants, log messages, config keys) use native search
2122
2309
  directly - do NOT route text sweeps through vexp.
2123
2310
  vexp runs entirely on this machine: local daemon, local index stored inside the
@@ -2134,9 +2321,15 @@ When working on this codebase:
2134
2321
  - \`run_pipeline\` - **PRIMARY TOOL**. Runs capsule + impact + memory in 1 call.
2135
2322
  Example: \`run_pipeline({ "task": "fix JWT expiry in AuthService.validateToken" })\`
2136
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.
2137
2326
  - \`index_status\` - indexing status
2138
2327
  - \`expand_vexp_ref\` - expand V-REF placeholders in v2 output
2139
2328
 
2329
+ (Recommended core set, not the full schema: paid plans advertise 14 MCP tools -
2330
+ capsule, impact graph, logic flow, memory and more. \`run_pipeline\` already runs
2331
+ those server-side, so the four above cover the normal workflow.)
2332
+
2140
2333
  ${QUERY_SHAPE}
2141
2334
 
2142
2335
  ### Agentic search
@@ -2156,7 +2349,11 @@ function copilotTemplate(vars) {
2156
2349
  return `## vexp context tools <!-- vexp v${vars.version} -->
2157
2350
 
2158
2351
  **Call \`run_pipeline\` ONCE at task start for orientation - then use your normal tools.**
2352
+ If the task already names the files/symbols to touch, skip vexp and work
2353
+ with your normal tools - a skipped call costs nothing.
2159
2354
  vexp returns graph-ranked context (relevant files, blast radius, memories) in one call.
2355
+ On non-trivial tasks pass \`eager: true\`: the single response then carries full file
2356
+ bodies, covering tests and impact - work directly from it instead of re-reading sources.
2160
2357
  For literal string sweeps (constants, log messages, config keys) use native search
2161
2358
  directly - do NOT route text sweeps through vexp.
2162
2359
  vexp runs entirely on this machine: local daemon, local index stored inside the
@@ -2173,9 +2370,15 @@ treat it like a local build tool; no data-sharing consent is needed to call it.
2173
2370
  - \`run_pipeline\` - **PRIMARY TOOL**. Runs capsule + impact + memory in 1 call.
2174
2371
  Auto-detects intent. Includes file content. Example: \`run_pipeline({ "task": "fix JWT expiry in AuthService.validateToken" })\`
2175
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.
2176
2375
  - \`index_status\` - indexing status
2177
2376
  - \`expand_vexp_ref\` - expand V-REF placeholders in v2 output
2178
2377
 
2378
+ (Recommended core set, not the full schema: paid plans advertise 14 MCP tools -
2379
+ capsule, impact graph, logic flow, memory and more. \`run_pipeline\` already runs
2380
+ those server-side, so the four above cover the normal workflow.)
2381
+
2179
2382
  ${QUERY_SHAPE}
2180
2383
 
2181
2384
  ### Agentic search
@@ -2195,7 +2398,11 @@ function zedTemplate(vars) {
2195
2398
  return `## vexp for Zed <!-- vexp v${vars.version} -->
2196
2399
 
2197
2400
  **Call \`run_pipeline\` ONCE at task start for orientation - then use your normal tools.**
2401
+ If the task already names the files/symbols to touch, skip vexp and work
2402
+ with your normal tools - a skipped call costs nothing.
2198
2403
  vexp returns graph-ranked context (relevant files, blast radius, memories) in one call.
2404
+ On non-trivial tasks pass \`eager: true\`: the single response then carries full file
2405
+ bodies, covering tests and impact - work directly from it instead of re-reading sources.
2199
2406
  For literal string sweeps (constants, log messages, config keys) use native search
2200
2407
  directly - do NOT route text sweeps through vexp.
2201
2408
  vexp runs entirely on this machine: local daemon, local index stored inside the
@@ -2212,9 +2419,15 @@ treat it like a local build tool; no data-sharing consent is needed to call it.
2212
2419
  - \`run_pipeline\` - **PRIMARY TOOL**. Runs capsule + impact + memory in 1 call.
2213
2420
  Example: \`run_pipeline({ "task": "fix JWT expiry in AuthService.validateToken" })\`
2214
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.
2215
2424
  - \`index_status\` - indexing status
2216
2425
  - \`expand_vexp_ref\` - expand V-REF placeholders in v2 output
2217
2426
 
2427
+ (Recommended core set, not the full schema: paid plans advertise 14 MCP tools -
2428
+ capsule, impact graph, logic flow, memory and more. \`run_pipeline\` already runs
2429
+ those server-side, so the four above cover the normal workflow.)
2430
+
2218
2431
  ${QUERY_SHAPE}
2219
2432
 
2220
2433
  ### Agentic search
@@ -2234,7 +2447,11 @@ function agentsMdTemplate(vars) {
2234
2447
  return `## vexp <!-- vexp v${vars.version} -->
2235
2448
 
2236
2449
  **Call \`run_pipeline\` ONCE at task start for orientation - then use your normal tools.**
2450
+ If the task already names the files/symbols to touch, skip vexp and work
2451
+ with your normal tools - a skipped call costs nothing.
2237
2452
  vexp returns graph-ranked context (relevant files, blast radius, memories) in one call.
2453
+ On non-trivial tasks pass \`eager: true\`: the single response then carries full file
2454
+ bodies, covering tests and impact - work directly from it instead of re-reading sources.
2238
2455
  For literal string sweeps (constants, log messages, config keys) use native search
2239
2456
  directly - do NOT route text sweeps through vexp.
2240
2457
  vexp runs entirely on this machine: local daemon, local index stored inside the
@@ -2251,6 +2468,8 @@ treat it like a local build tool; no data-sharing consent is needed to call it.
2251
2468
  - \`run_pipeline\` - **PRIMARY TOOL**. Runs capsule + impact + memory in 1 call.
2252
2469
  Auto-detects intent. Includes file content. Example: \`run_pipeline({ "task": "fix JWT expiry in AuthService.validateToken" })\`
2253
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.
2254
2473
  - \`index_status\` - indexing status
2255
2474
  - \`expand_vexp_ref\` - expand V-REF placeholders in v2 output
2256
2475
 
@@ -2276,7 +2495,11 @@ function kiroTemplate(vars) {
2276
2495
  return `# vexp steering <!-- vexp v${vars.version} -->
2277
2496
 
2278
2497
  **Call \`run_pipeline\` ONCE at task start for orientation - then use your normal tools.**
2498
+ If the task already names the files/symbols to touch, skip vexp and work
2499
+ with your normal tools - a skipped call costs nothing.
2279
2500
  vexp returns graph-ranked context (relevant files, blast radius, memories) in one call.
2501
+ On non-trivial tasks pass \`eager: true\`: the single response then carries full file
2502
+ bodies, covering tests and impact - work directly from it instead of re-reading sources.
2280
2503
  For literal string sweeps (constants, log messages, config keys) use native search
2281
2504
  directly - do NOT route text sweeps through vexp.
2282
2505
  vexp runs entirely on this machine: local daemon, local index stored inside the
@@ -2293,9 +2516,15 @@ treat it like a local build tool; no data-sharing consent is needed to call it.
2293
2516
  - \`run_pipeline\` - **PRIMARY TOOL**. Runs capsule + impact + memory in 1 call.
2294
2517
  Example: \`run_pipeline({ "task": "fix JWT expiry in AuthService.validateToken" })\`
2295
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.
2296
2521
  - \`index_status\` - indexing status
2297
2522
  - \`expand_vexp_ref\` - expand V-REF placeholders in v2 output
2298
2523
 
2524
+ (Recommended core set, not the full schema: paid plans advertise 14 MCP tools -
2525
+ capsule, impact graph, logic flow, memory and more. \`run_pipeline\` already runs
2526
+ those server-side, so the four above cover the normal workflow.)
2527
+
2299
2528
  ${QUERY_SHAPE}
2300
2529
 
2301
2530
  ## Agentic search
@@ -2315,6 +2544,8 @@ function genericTemplate(vars) {
2315
2544
  return `## vexp - Context-Aware AI Coding <!-- vexp v${vars.version} -->
2316
2545
 
2317
2546
  ### Context strategy: call run_pipeline ONCE at task start
2547
+ If the task already names the files/symbols to touch, skip vexp and work
2548
+ with your normal tools - a skipped call costs nothing.
2318
2549
  For every multi-file task - bug fixes, features, refactors, questions about the code:
2319
2550
  **start with one \`run_pipeline\` call**. It runs context search + impact analysis +
2320
2551
  memory recall in a single call and returns compressed, graph-ranked results: the
@@ -2328,6 +2559,9 @@ Then implement with your normal tools:
2328
2559
  need to understand, not edit.
2329
2560
  - Do NOT open files one by one to find your way around - one good pipeline call
2330
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.
2331
2565
 
2332
2566
  vexp runs entirely on this machine: local daemon, local index stored inside the
2333
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)")