@vibgrate/cli 2026.720.1 → 2026.720.3

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.
@@ -1,8 +1,8 @@
1
- import { hashString, langById, shortId, canonicalize, grammarSetVersion, hashBytes, langForExtension, setGrammarsOverride, parseSource } from './chunk-VFO5UDAT.js';
2
- import { writeTextFile, pathExists, readJsonFile, loadConfig, computeUpgradeImpact, getChangelogSignals, gitHistoryAvailable, buildVersionTimelines, findPackageTimeline, severityRank, projectTypeToVulnEcosystem, VERSION } from './chunk-AJDUMRVI.js';
3
- import * as fs19 from 'fs';
1
+ import { hashString, langById, shortId, canonicalize, grammarSetVersion, hashBytes, langForExtension, setGrammarsOverride, parseSource } from './chunk-BHA7QIPD.js';
2
+ import { writeTextFile, pathExists, readJsonFile, loadConfig, computeUpgradeImpact, getChangelogSignals, gitHistoryAvailable, buildVersionTimelines, findPackageTimeline, severityRank, projectTypeToVulnEcosystem, VERSION } from './chunk-B6ZWB3WH.js';
3
+ import * as fs17 from 'fs';
4
4
  import { spawnSync, execFileSync } from 'child_process';
5
- import * as path19 from 'path';
5
+ import * as path17 from 'path';
6
6
  import ignore from 'ignore';
7
7
  import * as v8 from 'v8';
8
8
  import Graph from 'graphology';
@@ -41,9 +41,9 @@ function whichOnPath(cmd) {
41
41
  }
42
42
  function isOwnBinary(binPath) {
43
43
  try {
44
- const real = fs19.realpathSync(binPath);
44
+ const real = fs17.realpathSync(binPath);
45
45
  if (/[\\/]@vibgrate[\\/]cli[\\/]/.test(real)) return true;
46
- const head = fs19.readFileSync(real, { encoding: "utf8" }).slice(0, 2048);
46
+ const head = fs17.readFileSync(real, { encoding: "utf8" }).slice(0, 2048);
47
47
  return head.includes("@vibgrate/cli") || head.includes("vibgrate");
48
48
  } catch {
49
49
  return false;
@@ -53,7 +53,7 @@ function isEphemeralNpxBinary(binPath) {
53
53
  const NPX_SEGMENT = /[\\/]_npx[\\/]/;
54
54
  if (NPX_SEGMENT.test(binPath)) return true;
55
55
  try {
56
- return NPX_SEGMENT.test(fs19.realpathSync(binPath));
56
+ return NPX_SEGMENT.test(fs17.realpathSync(binPath));
57
57
  } catch {
58
58
  return false;
59
59
  }
@@ -219,19 +219,19 @@ var SKIP_FILES = /* @__PURE__ */ new Set([
219
219
  "flake.lock"
220
220
  ]);
221
221
  function toPosix(p) {
222
- return p.split(path19.sep).join("/");
222
+ return p.split(path17.sep).join("/");
223
223
  }
224
224
  function buildRootIgnore(root, exclude) {
225
225
  const ig = ignore();
226
- const gitignorePath = path19.join(root, ".gitignore");
227
- if (fs19.existsSync(gitignorePath)) {
228
- ig.add(fs19.readFileSync(gitignorePath, "utf8"));
226
+ const gitignorePath = path17.join(root, ".gitignore");
227
+ if (fs17.existsSync(gitignorePath)) {
228
+ ig.add(fs17.readFileSync(gitignorePath, "utf8"));
229
229
  }
230
230
  if (exclude.length) ig.add(exclude);
231
231
  return ig;
232
232
  }
233
233
  function discover(options) {
234
- const root = path19.resolve(options.root);
234
+ const root = path17.resolve(options.root);
235
235
  const onlyLangs = (options.only ?? []).filter(Boolean);
236
236
  const allowLang = (lang) => onlyLangs.length === 0 || onlyLangs.includes(lang.id);
237
237
  for (const id of onlyLangs) {
@@ -240,27 +240,27 @@ function discover(options) {
240
240
  }
241
241
  }
242
242
  const rootIg = buildRootIgnore(root, options.exclude ?? []);
243
- const scopeAbs = (options.paths && options.paths.length ? options.paths.map((p) => path19.resolve(root, p)) : [root]).filter((p) => fs19.existsSync(p));
243
+ const scopeAbs = (options.paths && options.paths.length ? options.paths.map((p) => path17.resolve(root, p)) : [root]).filter((p) => fs17.existsSync(p));
244
244
  const found = /* @__PURE__ */ new Map();
245
245
  const considerFile = (abs) => {
246
- const rel2 = toPosix(path19.relative(root, abs));
246
+ const rel2 = toPosix(path17.relative(root, abs));
247
247
  if (rel2.startsWith("..")) return;
248
248
  if (rel2 === "" || rootIg.ignores(rel2)) return;
249
- if (SKIP_FILES.has(path19.basename(abs).toLowerCase())) return;
250
- const lang = langForExtension(path19.extname(abs));
249
+ if (SKIP_FILES.has(path17.basename(abs).toLowerCase())) return;
250
+ const lang = langForExtension(path17.extname(abs));
251
251
  if (!lang || !allowLang(lang)) return;
252
252
  found.set(rel2, { rel: rel2, abs, lang });
253
253
  };
254
254
  const walk2 = (dir) => {
255
255
  let entries;
256
256
  try {
257
- entries = fs19.readdirSync(dir, { withFileTypes: true });
257
+ entries = fs17.readdirSync(dir, { withFileTypes: true });
258
258
  } catch {
259
259
  return;
260
260
  }
261
261
  for (const entry of entries) {
262
- const abs = path19.join(dir, entry.name);
263
- const rel2 = toPosix(path19.relative(root, abs));
262
+ const abs = path17.join(dir, entry.name);
263
+ const rel2 = toPosix(path17.relative(root, abs));
264
264
  if (entry.isDirectory()) {
265
265
  if (SKIP_DIRS.has(entry.name)) continue;
266
266
  if (rel2 && rootIg.ignores(`${rel2}/`)) continue;
@@ -271,7 +271,7 @@ function discover(options) {
271
271
  }
272
272
  };
273
273
  for (const scope of scopeAbs) {
274
- const stat = fs19.statSync(scope);
274
+ const stat = fs17.statSync(scope);
275
275
  if (stat.isDirectory()) walk2(scope);
276
276
  else if (stat.isFile()) considerFile(scope);
277
277
  }
@@ -336,7 +336,7 @@ function formatBytes(n) {
336
336
  if (n >= 1024) return `${(n / 1024).toFixed(1)} KiB`;
337
337
  return `${n} B`;
338
338
  }
339
- var JS_TS_EXT = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
339
+ var JS_TS_EXT = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".vue", ".svelte", ".astro"];
340
340
  var OTHER_EXT = [
341
341
  ".py",
342
342
  ".pyi",
@@ -365,7 +365,13 @@ var OTHER_EXT = [
365
365
  ".hpp",
366
366
  ".hh",
367
367
  ".hxx",
368
- ".h"
368
+ ".h",
369
+ ".m",
370
+ ".mm",
371
+ ".ml",
372
+ ".mli",
373
+ ".res",
374
+ ".sol"
369
375
  ];
370
376
  var EXT_GROUPS = {
371
377
  ".py": [".py", ".pyi"],
@@ -399,7 +405,19 @@ Object.assign(EXT_GROUPS, {
399
405
  ".hpp": CPP_EXT,
400
406
  ".hh": CPP_EXT,
401
407
  ".hxx": CPP_EXT,
402
- ".h": [...CPP_EXT, ".c"]
408
+ ".h": [...CPP_EXT, ".c"],
409
+ // ObjC #imports headers; ObjC++ additionally reaches C++ headers.
410
+ ".m": [".h", ".m", ".mm"],
411
+ ".mm": [".h", ".m", ".mm", ...CPP_EXT],
412
+ ".ml": [".ml", ".mli"],
413
+ ".mli": [".ml", ".mli"],
414
+ ".res": [".res"],
415
+ ".sol": [".sol"],
416
+ // Template containers: ERB fragments are Ruby; HTML/EJS script is JS.
417
+ ".erb": [".rb", ".erb"],
418
+ ".html": JS_TS_EXT,
419
+ ".htm": JS_TS_EXT,
420
+ ".ejs": JS_TS_EXT
403
421
  });
404
422
  function buildModuleResolver(root, relSet) {
405
423
  const probe = makeProbe(relSet);
@@ -437,10 +455,10 @@ function buildModuleResolver(root, relSet) {
437
455
  resolve(fromRel, source) {
438
456
  if (source.includes("\\")) source = source.split("\\").join(".");
439
457
  if (source.startsWith("./") || source.startsWith("../")) {
440
- return probe(posixJoin(path19.posix.dirname(fromRel), source));
458
+ return probe(posixJoin(path17.posix.dirname(fromRel), source));
441
459
  }
442
460
  if (/\.[A-Za-z0-9]+$/.test(source) && !source.startsWith("<")) {
443
- const hit = probe(posixJoin(path19.posix.dirname(fromRel), source));
461
+ const hit = probe(posixJoin(path17.posix.dirname(fromRel), source));
444
462
  if (hit) return hit;
445
463
  }
446
464
  if (source.startsWith(".")) {
@@ -511,8 +529,8 @@ function resolvePyRelative(probe, fromRel, source) {
511
529
  let dots = 0;
512
530
  while (source[dots] === ".") dots++;
513
531
  const rest = source.slice(dots);
514
- let dir = path19.posix.dirname(fromRel);
515
- for (let i = 1; i < dots; i++) dir = path19.posix.dirname(dir);
532
+ let dir = path17.posix.dirname(fromRel);
533
+ for (let i = 1; i < dots; i++) dir = path17.posix.dirname(dir);
516
534
  const sub = rest.replace(/\./g, "/");
517
535
  return probe(sub ? posixJoin(dir, sub) : dir);
518
536
  }
@@ -520,7 +538,7 @@ function relativeResolver(relSet) {
520
538
  const probe = makeProbe(relSet);
521
539
  return {
522
540
  resolve(fromRel, source) {
523
- if (source.startsWith(".")) return probe(posixJoin(path19.posix.dirname(fromRel), source));
541
+ if (source.startsWith(".")) return probe(posixJoin(path17.posix.dirname(fromRel), source));
524
542
  return resolveDotted(probe, source);
525
543
  }
526
544
  };
@@ -538,12 +556,12 @@ function makeProbe(relSet) {
538
556
  };
539
557
  }
540
558
  function normalizeRel(p) {
541
- const norm = path19.posix.normalize(p);
559
+ const norm = path17.posix.normalize(p);
542
560
  const stripped = norm.startsWith("./") ? norm.slice(2) : norm;
543
561
  return stripped.startsWith("..") ? "" : stripped;
544
562
  }
545
563
  function posixJoin(a, b) {
546
- return path19.posix.normalize(path19.posix.join(a, b));
564
+ return path17.posix.normalize(path17.posix.join(a, b));
547
565
  }
548
566
  function matchPattern(pattern, source) {
549
567
  const star = pattern.indexOf("*");
@@ -556,8 +574,8 @@ function matchPattern(pattern, source) {
556
574
  }
557
575
  function loadTsconfigPaths(root) {
558
576
  for (const name of ["tsconfig.base.json", "tsconfig.json"]) {
559
- const file = path19.join(root, name);
560
- if (fs19.existsSync(file)) {
577
+ const file = path17.join(root, name);
578
+ if (fs17.existsSync(file)) {
561
579
  const merged = readTsconfigChain(root, file, /* @__PURE__ */ new Set());
562
580
  if (merged && merged.paths.length) return merged;
563
581
  }
@@ -569,21 +587,21 @@ function readTsconfigChain(root, file, seen) {
569
587
  seen.add(file);
570
588
  let cfg;
571
589
  try {
572
- cfg = parseJsonc(fs19.readFileSync(file, "utf8"));
590
+ cfg = parseJsonc(fs17.readFileSync(file, "utf8"));
573
591
  } catch {
574
592
  return null;
575
593
  }
576
- const dir = path19.dirname(file);
594
+ const dir = path17.dirname(file);
577
595
  let base = { baseUrlRel: "", paths: [] };
578
596
  if (cfg.extends && cfg.extends.startsWith(".")) {
579
- const extPath = path19.resolve(dir, cfg.extends.endsWith(".json") ? cfg.extends : `${cfg.extends}.json`);
580
- if (fs19.existsSync(extPath)) {
597
+ const extPath = path17.resolve(dir, cfg.extends.endsWith(".json") ? cfg.extends : `${cfg.extends}.json`);
598
+ if (fs17.existsSync(extPath)) {
581
599
  const inherited = readTsconfigChain(root, extPath, seen);
582
600
  if (inherited) base = inherited;
583
601
  }
584
602
  }
585
603
  const co = cfg.compilerOptions ?? {};
586
- const baseUrlRel = co.baseUrl !== void 0 ? path19.relative(root, path19.resolve(dir, co.baseUrl)).split(path19.sep).join("/") : base.baseUrlRel;
604
+ const baseUrlRel = co.baseUrl !== void 0 ? path17.relative(root, path17.resolve(dir, co.baseUrl)).split(path17.sep).join("/") : base.baseUrlRel;
587
605
  const paths = [...base.paths];
588
606
  if (co.paths) for (const [k, v] of Object.entries(co.paths)) paths.push([k, v]);
589
607
  return { baseUrlRel, paths };
@@ -591,27 +609,27 @@ function readTsconfigChain(root, file, seen) {
591
609
  function loadWorkspacePackages(root) {
592
610
  const map = /* @__PURE__ */ new Map();
593
611
  const globs = [];
594
- const rootPkg = path19.join(root, "package.json");
595
- if (fs19.existsSync(rootPkg)) {
612
+ const rootPkg = path17.join(root, "package.json");
613
+ if (fs17.existsSync(rootPkg)) {
596
614
  try {
597
- const pkg = JSON.parse(fs19.readFileSync(rootPkg, "utf8"));
615
+ const pkg = JSON.parse(fs17.readFileSync(rootPkg, "utf8"));
598
616
  const ws = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces?.packages;
599
617
  if (ws) globs.push(...ws);
600
618
  } catch {
601
619
  }
602
620
  }
603
- const pnpmWs = path19.join(root, "pnpm-workspace.yaml");
604
- if (fs19.existsSync(pnpmWs)) {
605
- for (const line of fs19.readFileSync(pnpmWs, "utf8").split("\n")) {
621
+ const pnpmWs = path17.join(root, "pnpm-workspace.yaml");
622
+ if (fs17.existsSync(pnpmWs)) {
623
+ for (const line of fs17.readFileSync(pnpmWs, "utf8").split("\n")) {
606
624
  const m = /^\s*-\s*['"]?([^'"\n]+)['"]?\s*$/.exec(line);
607
625
  if (m) globs.push(m[1].trim());
608
626
  }
609
627
  }
610
628
  for (const dir of expandWorkspaceGlobs(root, globs)) {
611
- const pj = path19.join(dir, "package.json");
629
+ const pj = path17.join(dir, "package.json");
612
630
  try {
613
- const name = JSON.parse(fs19.readFileSync(pj, "utf8")).name;
614
- if (name) map.set(name, path19.relative(root, dir).split(path19.sep).join("/"));
631
+ const name = JSON.parse(fs17.readFileSync(pj, "utf8")).name;
632
+ if (name) map.set(name, path17.relative(root, dir).split(path17.sep).join("/"));
615
633
  } catch {
616
634
  }
617
635
  }
@@ -621,10 +639,10 @@ function expandWorkspaceGlobs(root, globs) {
621
639
  const out = /* @__PURE__ */ new Set();
622
640
  for (const glob of globs) {
623
641
  const clean = glob.replace(/\/\*\*$/, "").replace(/\/\*$/, "");
624
- const baseDir = path19.join(root, clean);
642
+ const baseDir = path17.join(root, clean);
625
643
  const recurse = glob.endsWith("**");
626
644
  if (glob === clean) {
627
- if (fs19.existsSync(path19.join(baseDir, "package.json"))) out.add(baseDir);
645
+ if (fs17.existsSync(path17.join(baseDir, "package.json"))) out.add(baseDir);
628
646
  continue;
629
647
  }
630
648
  collectPackageDirs(baseDir, recurse ? 4 : 1, out);
@@ -635,14 +653,14 @@ function collectPackageDirs(dir, depth, out) {
635
653
  if (depth < 0) return;
636
654
  let entries;
637
655
  try {
638
- entries = fs19.readdirSync(dir, { withFileTypes: true });
656
+ entries = fs17.readdirSync(dir, { withFileTypes: true });
639
657
  } catch {
640
658
  return;
641
659
  }
642
660
  for (const e of entries) {
643
661
  if (!e.isDirectory() || e.name === "node_modules" || e.name.startsWith(".")) continue;
644
- const child = path19.join(dir, e.name);
645
- if (fs19.existsSync(path19.join(child, "package.json"))) out.add(child);
662
+ const child = path17.join(dir, e.name);
663
+ if (fs17.existsSync(path17.join(child, "package.json"))) out.add(child);
646
664
  else collectPackageDirs(child, depth - 1, out);
647
665
  }
648
666
  }
@@ -1141,14 +1159,14 @@ var DEFAULT_PATHS = [
1141
1159
  ];
1142
1160
  function loadCoverage(root, explicit) {
1143
1161
  const candidates = (explicit && explicit.length ? explicit : DEFAULT_PATHS).map(
1144
- (p) => path19.resolve(root, p)
1162
+ (p) => path17.resolve(root, p)
1145
1163
  );
1146
- const found = candidates.filter((p) => fs19.existsSync(p));
1164
+ const found = candidates.filter((p) => fs17.existsSync(p));
1147
1165
  if (found.length === 0) return null;
1148
1166
  const map = /* @__PURE__ */ new Map();
1149
1167
  for (const file of found) {
1150
1168
  try {
1151
- const text = fs19.readFileSync(file, "utf8");
1169
+ const text = fs17.readFileSync(file, "utf8");
1152
1170
  if (file.endsWith(".json")) mergeIstanbul(map, text, root);
1153
1171
  else mergeLcov(map, text, root);
1154
1172
  } catch {
@@ -1157,8 +1175,8 @@ function loadCoverage(root, explicit) {
1157
1175
  return map.size ? map : null;
1158
1176
  }
1159
1177
  function rel(root, p) {
1160
- const abs = path19.isAbsolute(p) ? p : path19.resolve(root, p);
1161
- return path19.relative(root, abs).split(path19.sep).join("/");
1178
+ const abs = path17.isAbsolute(p) ? p : path17.resolve(root, p);
1179
+ return path17.relative(root, abs).split(path17.sep).join("/");
1162
1180
  }
1163
1181
  function bump(map, file, line, hits) {
1164
1182
  let lh = map.get(file);
@@ -1494,7 +1512,7 @@ async function parseInline(files, options) {
1494
1512
  onProgress?.(0, files.length);
1495
1513
  for (const file of files) {
1496
1514
  try {
1497
- const source = fs19.readFileSync(file.abs, "utf8");
1515
+ const source = fs17.readFileSync(file.abs, "utf8");
1498
1516
  out.push(await parseSource(file.rel, file.lang.id, source));
1499
1517
  } catch (err) {
1500
1518
  out.push(emptyParse(file, `parse failed: ${err.message}`));
@@ -1549,9 +1567,9 @@ function isWorkerOom(err) {
1549
1567
  return e?.code === "ERR_WORKER_OUT_OF_MEMORY" || /out of memory/i.test(e?.message ?? "");
1550
1568
  }
1551
1569
  function resolveWorkerFile() {
1552
- const here = path19.dirname(fileURLToPath(import.meta.url));
1553
- const candidate = path19.join(here, "parse-worker.js");
1554
- return fs19.existsSync(candidate) ? candidate : null;
1570
+ const here = path17.dirname(fileURLToPath(import.meta.url));
1571
+ const candidate = path17.join(here, "parse-worker.js");
1572
+ return fs17.existsSync(candidate) ? candidate : null;
1555
1573
  }
1556
1574
  function chunk(items, buckets) {
1557
1575
  const out = Array.from({ length: Math.min(buckets, items.length || 1) }, () => []);
@@ -1590,7 +1608,7 @@ function resolve4(parses, resolver) {
1590
1608
  addNode(baseNodes, {
1591
1609
  id: fileId,
1592
1610
  kind: "file",
1593
- name: path19.posix.basename(p.rel),
1611
+ name: path17.posix.basename(p.rel),
1594
1612
  qualifiedName: p.rel,
1595
1613
  file: p.rel,
1596
1614
  span: { start: 1, end: 1 },
@@ -2026,14 +2044,14 @@ function enclosing3(nodes, line) {
2026
2044
  return best;
2027
2045
  }
2028
2046
  function normalize2(p) {
2029
- return path19.resolve(p).split(path19.sep).join("/");
2047
+ return path17.resolve(p).split(path17.sep).join("/");
2030
2048
  }
2031
2049
  var CACHE_VERSION = "vg-parse-cache/2";
2032
2050
  function cacheDir(root) {
2033
- return path19.join(root, ".vibgrate", "cache");
2051
+ return path17.join(root, ".vibgrate", "cache");
2034
2052
  }
2035
2053
  function cachePath(root) {
2036
- return path19.join(cacheDir(root), "parse-cache.json");
2054
+ return path17.join(cacheDir(root), "parse-cache.json");
2037
2055
  }
2038
2056
  function loadCache(root, opts) {
2039
2057
  const file = cachePath(root);
@@ -2043,9 +2061,9 @@ function loadCache(root, opts) {
2043
2061
  grammars: opts.grammars,
2044
2062
  entries: {}
2045
2063
  };
2046
- if (!opts.disabled && fs19.existsSync(file)) {
2064
+ if (!opts.disabled && fs17.existsSync(file)) {
2047
2065
  try {
2048
- const loaded = JSON.parse(fs19.readFileSync(file, "utf8"));
2066
+ const loaded = JSON.parse(fs17.readFileSync(file, "utf8"));
2049
2067
  if (loaded.version === CACHE_VERSION && loaded.toolVersion === opts.toolVersion && loaded.grammars === opts.grammars && loaded.entries) {
2050
2068
  data = loaded;
2051
2069
  }
@@ -2066,8 +2084,8 @@ function loadCache(root, opts) {
2066
2084
  }
2067
2085
  },
2068
2086
  save() {
2069
- fs19.mkdirSync(cacheDir(root), { recursive: true });
2070
- fs19.writeFileSync(file, stableStringify(data, 0));
2087
+ fs17.mkdirSync(cacheDir(root), { recursive: true });
2088
+ fs17.writeFileSync(file, stableStringify(data, 0));
2071
2089
  }
2072
2090
  };
2073
2091
  }
@@ -2099,7 +2117,7 @@ function epistemicBreakdown(edges) {
2099
2117
  // src/engine/build.ts
2100
2118
  async function buildGraph(options) {
2101
2119
  const start = nowMs();
2102
- const root = path19.resolve(options.root);
2120
+ const root = path17.resolve(options.root);
2103
2121
  const files = discover({
2104
2122
  root,
2105
2123
  only: options.only,
@@ -2127,7 +2145,7 @@ async function buildGraph(options) {
2127
2145
  for (const file of files) {
2128
2146
  let stat;
2129
2147
  try {
2130
- stat = fs19.statSync(file.abs);
2148
+ stat = fs17.statSync(file.abs);
2131
2149
  } catch {
2132
2150
  continue;
2133
2151
  }
@@ -2145,7 +2163,7 @@ async function buildGraph(options) {
2145
2163
  }
2146
2164
  let hash = "";
2147
2165
  try {
2148
- hash = hashBytes(fs19.readFileSync(file.abs));
2166
+ hash = hashBytes(fs17.readFileSync(file.abs));
2149
2167
  } catch {
2150
2168
  continue;
2151
2169
  }
@@ -2237,7 +2255,7 @@ async function buildGraph(options) {
2237
2255
  toolchain
2238
2256
  },
2239
2257
  meta: {
2240
- root: path19.basename(root) === "" ? "." : ".",
2258
+ root: path17.basename(root) === "" ? "." : ".",
2241
2259
  languages,
2242
2260
  counts: {
2243
2261
  nodes: analysis.nodes.length,
@@ -2283,14 +2301,14 @@ function toRepoRel(p) {
2283
2301
  function loadScipIndex(root, explicit) {
2284
2302
  const candidates = [
2285
2303
  explicit,
2286
- path19.join(root, "index.scip"),
2287
- path19.join(root, ".vibgrate", "index.scip")
2304
+ path17.join(root, "index.scip"),
2305
+ path17.join(root, ".vibgrate", "index.scip")
2288
2306
  ].filter((p) => Boolean(p));
2289
2307
  for (const file of candidates) {
2290
- const abs = path19.isAbsolute(file) ? file : path19.resolve(root, file);
2291
- if (!fs19.existsSync(abs)) continue;
2308
+ const abs = path17.isAbsolute(file) ? file : path17.resolve(root, file);
2309
+ if (!fs17.existsSync(abs)) continue;
2292
2310
  try {
2293
- const index = decodeScipIndex(new Uint8Array(fs19.readFileSync(abs)));
2311
+ const index = decodeScipIndex(new Uint8Array(fs17.readFileSync(abs)));
2294
2312
  if (index.documents.length) {
2295
2313
  return { index, tool: index.toolVersion ? `${index.toolName} ${index.toolVersion}` : index.toolName };
2296
2314
  }
@@ -2451,46 +2469,46 @@ function esc(s) {
2451
2469
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2452
2470
  }
2453
2471
  function vibgrateDir(root) {
2454
- return path19.join(root, ".vibgrate");
2472
+ return path17.join(root, ".vibgrate");
2455
2473
  }
2456
2474
  function defaultGraphPath(root) {
2457
- return path19.join(vibgrateDir(root), "graph.json");
2475
+ return path17.join(vibgrateDir(root), "graph.json");
2458
2476
  }
2459
2477
  function writeArtifacts(graph, options) {
2460
2478
  const dir = vibgrateDir(options.root);
2461
- fs19.mkdirSync(dir, { recursive: true });
2479
+ fs17.mkdirSync(dir, { recursive: true });
2462
2480
  const graphPath = options.graphPath ?? defaultGraphPath(options.root);
2463
- fs19.mkdirSync(path19.dirname(graphPath), { recursive: true });
2481
+ fs17.mkdirSync(path17.dirname(graphPath), { recursive: true });
2464
2482
  const tmp = `${graphPath}.${process.pid}.tmp`;
2465
2483
  try {
2466
- fs19.writeFileSync(tmp, serializeGraph(graph));
2467
- fs19.renameSync(tmp, graphPath);
2484
+ fs17.writeFileSync(tmp, serializeGraph(graph));
2485
+ fs17.renameSync(tmp, graphPath);
2468
2486
  } catch (err) {
2469
- fs19.rmSync(tmp, { force: true });
2487
+ fs17.rmSync(tmp, { force: true });
2470
2488
  throw err;
2471
2489
  }
2472
2490
  const written = { graphPath };
2473
2491
  if (options.report !== false) {
2474
- const reportPath = path19.join(dir, "GRAPH_REPORT.md");
2475
- fs19.writeFileSync(reportPath, renderReport(graph));
2492
+ const reportPath = path17.join(dir, "GRAPH_REPORT.md");
2493
+ fs17.writeFileSync(reportPath, renderReport(graph));
2476
2494
  written.reportPath = reportPath;
2477
2495
  }
2478
2496
  if (options.html !== false) {
2479
- const htmlPath = path19.join(dir, "graph.html");
2480
- fs19.writeFileSync(htmlPath, renderHtml(graph));
2497
+ const htmlPath = path17.join(dir, "graph.html");
2498
+ fs17.writeFileSync(htmlPath, renderHtml(graph));
2481
2499
  written.htmlPath = htmlPath;
2482
2500
  }
2483
2501
  if (graph.facts && graph.facts.length) {
2484
- const factsPath = path19.join(dir, "facts.jsonl");
2485
- fs19.writeFileSync(factsPath, graph.facts.map((f) => JSON.stringify(f)).join("\n") + "\n");
2502
+ const factsPath = path17.join(dir, "facts.jsonl");
2503
+ fs17.writeFileSync(factsPath, graph.facts.map((f) => JSON.stringify(f)).join("\n") + "\n");
2486
2504
  written.factsPath = factsPath;
2487
2505
  }
2488
2506
  return written;
2489
2507
  }
2490
2508
  function loadGraph(root, graphPath) {
2491
2509
  const file = graphPath ?? defaultGraphPath(root);
2492
- if (!fs19.existsSync(file)) return null;
2493
- return parseGraph(fs19.readFileSync(file, "utf8"));
2510
+ if (!fs17.existsSync(file)) return null;
2511
+ return parseGraph(fs17.readFileSync(file, "utf8"));
2494
2512
  }
2495
2513
 
2496
2514
  // src/engine/verify.ts
@@ -2567,7 +2585,7 @@ function isProcessAlive(pid) {
2567
2585
  }
2568
2586
  function lockIsStale(file, staleMs) {
2569
2587
  try {
2570
- const { pid, at } = JSON.parse(fs19.readFileSync(file, "utf8"));
2588
+ const { pid, at } = JSON.parse(fs17.readFileSync(file, "utf8"));
2571
2589
  if (typeof at === "number" && Date.now() - at > staleMs) return true;
2572
2590
  if (typeof pid === "number" && !isProcessAlive(pid)) return true;
2573
2591
  return false;
@@ -2578,10 +2596,10 @@ function lockIsStale(file, staleMs) {
2578
2596
  function acquireLock(file, staleMs = DEFAULT_LOCK_STALE_MS) {
2579
2597
  const write = () => {
2580
2598
  try {
2581
- fs19.mkdirSync(path19.dirname(file), { recursive: true });
2582
- const fd = fs19.openSync(file, "wx");
2583
- fs19.writeFileSync(fd, JSON.stringify({ pid: process.pid, at: Date.now() }));
2584
- fs19.closeSync(fd);
2599
+ fs17.mkdirSync(path17.dirname(file), { recursive: true });
2600
+ const fd = fs17.openSync(file, "wx");
2601
+ fs17.writeFileSync(fd, JSON.stringify({ pid: process.pid, at: Date.now() }));
2602
+ fs17.closeSync(fd);
2585
2603
  return true;
2586
2604
  } catch {
2587
2605
  return false;
@@ -2590,7 +2608,7 @@ function acquireLock(file, staleMs = DEFAULT_LOCK_STALE_MS) {
2590
2608
  if (write()) return true;
2591
2609
  if (lockIsStale(file, staleMs)) {
2592
2610
  try {
2593
- fs19.rmSync(file, { force: true });
2611
+ fs17.rmSync(file, { force: true });
2594
2612
  } catch {
2595
2613
  }
2596
2614
  return write();
@@ -2599,7 +2617,7 @@ function acquireLock(file, staleMs = DEFAULT_LOCK_STALE_MS) {
2599
2617
  }
2600
2618
  function releaseLock(file) {
2601
2619
  try {
2602
- fs19.rmSync(file, { force: true });
2620
+ fs17.rmSync(file, { force: true });
2603
2621
  } catch {
2604
2622
  }
2605
2623
  }
@@ -2609,14 +2627,14 @@ function resolveEmbedModel(model) {
2609
2627
  return model ?? "bge-small-en-v1.5";
2610
2628
  }
2611
2629
  function modelCacheDir() {
2612
- const base = process.env.XDG_CACHE_HOME || path19.join(os.homedir(), ".cache");
2613
- return path19.join(base, "vibgrate", "models");
2630
+ const base = process.env.XDG_CACHE_HOME || path17.join(os.homedir(), ".cache");
2631
+ return path17.join(base, "vibgrate", "models");
2614
2632
  }
2615
2633
  function modelReadyMarker(modelId) {
2616
- return path19.join(modelCacheDir(), `.ready-${safe(modelId)}`);
2634
+ return path17.join(modelCacheDir(), `.ready-${safe(modelId)}`);
2617
2635
  }
2618
2636
  function isModelReady(modelId = resolveEmbedModel()) {
2619
- return fs19.existsSync(modelReadyMarker(modelId));
2637
+ return fs17.existsSync(modelReadyMarker(modelId));
2620
2638
  }
2621
2639
  function unavailableMessage(reason) {
2622
2640
  switch (reason) {
@@ -2633,13 +2651,13 @@ function unavailableMessage(reason) {
2633
2651
  }
2634
2652
  function modelCacheInfo(modelId = resolveEmbedModel()) {
2635
2653
  const dir = modelCacheDir();
2636
- return { dir, present: isModelReady(modelId) || fs19.existsSync(dir), bytes: dirSize(dir) };
2654
+ return { dir, present: isModelReady(modelId) || fs17.existsSync(dir), bytes: dirSize(dir) };
2637
2655
  }
2638
2656
  function clearModelCache() {
2639
2657
  const dir = modelCacheDir();
2640
2658
  const bytes = dirSize(dir);
2641
2659
  try {
2642
- fs19.rmSync(dir, { recursive: true, force: true });
2660
+ fs17.rmSync(dir, { recursive: true, force: true });
2643
2661
  } catch {
2644
2662
  }
2645
2663
  return bytes;
@@ -2648,16 +2666,16 @@ function dirSize(dir) {
2648
2666
  let total = 0;
2649
2667
  let entries;
2650
2668
  try {
2651
- entries = fs19.readdirSync(dir, { withFileTypes: true });
2669
+ entries = fs17.readdirSync(dir, { withFileTypes: true });
2652
2670
  } catch {
2653
2671
  return 0;
2654
2672
  }
2655
2673
  for (const e of entries) {
2656
- const p = path19.join(dir, e.name);
2674
+ const p = path17.join(dir, e.name);
2657
2675
  if (e.isDirectory()) total += dirSize(p);
2658
2676
  else {
2659
2677
  try {
2660
- total += fs19.statSync(p).size;
2678
+ total += fs17.statSync(p).size;
2661
2679
  } catch {
2662
2680
  }
2663
2681
  }
@@ -2669,7 +2687,7 @@ function isPermissionError(e) {
2669
2687
  return code === "EACCES" || code === "EPERM" || code === "EROFS";
2670
2688
  }
2671
2689
  function embeddingsCached(root, modelId) {
2672
- return fs19.existsSync(path19.join(cacheDir(root), `embeddings-${safe(modelId)}.json`));
2690
+ return fs17.existsSync(path17.join(cacheDir(root), `embeddings-${safe(modelId)}.json`));
2673
2691
  }
2674
2692
  async function loadEmbedder(options = {}) {
2675
2693
  if (options.local) return null;
@@ -2683,7 +2701,7 @@ async function loadEmbedder(options = {}) {
2683
2701
  if (!mod?.FlagEmbedding) return fail("not-installed");
2684
2702
  const cache = modelCacheDir();
2685
2703
  try {
2686
- fs19.mkdirSync(cache, { recursive: true });
2704
+ fs17.mkdirSync(cache, { recursive: true });
2687
2705
  } catch (e) {
2688
2706
  return fail(isPermissionError(e) ? "no-permission" : "init-failed");
2689
2707
  }
@@ -2698,7 +2716,7 @@ async function loadEmbedder(options = {}) {
2698
2716
  "embedding model init timed out"
2699
2717
  );
2700
2718
  try {
2701
- fs19.writeFileSync(modelReadyMarker(modelId), (/* @__PURE__ */ new Date(0)).toISOString());
2719
+ fs17.writeFileSync(modelReadyMarker(modelId), (/* @__PURE__ */ new Date(0)).toISOString());
2702
2720
  } catch {
2703
2721
  }
2704
2722
  return {
@@ -2723,7 +2741,7 @@ async function importEmbedBackend() {
2723
2741
  const hostDir = process.env.VIBGRATE_EMBEDDER_PATH;
2724
2742
  if (hostDir) {
2725
2743
  try {
2726
- const req = createRequire(path19.join(hostDir, "package.json"));
2744
+ const req = createRequire(path17.join(hostDir, "package.json"));
2727
2745
  const mod = await import(pathToFileURL(req.resolve("fastembed")).href);
2728
2746
  const resolved = mod?.FlagEmbedding ? mod : mod?.default;
2729
2747
  if (resolved?.FlagEmbedding) return resolved;
@@ -2809,7 +2827,7 @@ function cosine(a, b) {
2809
2827
  var EMBED_CHUNK = 256;
2810
2828
  var CACHE_WRITE_INTERVAL_MS = 1500;
2811
2829
  function vectorCachePath(root, modelId) {
2812
- return path19.join(cacheDir(root), `embeddings-${safe(modelId)}.json`);
2830
+ return path17.join(cacheDir(root), `embeddings-${safe(modelId)}.json`);
2813
2831
  }
2814
2832
  function countPending(graph, root, modelId) {
2815
2833
  const entries = readCacheEntries(vectorCachePath(root, modelId), modelId);
@@ -2824,9 +2842,9 @@ function countPending(graph, root, modelId) {
2824
2842
  return pending;
2825
2843
  }
2826
2844
  function readCacheEntries(file, modelId) {
2827
- if (!fs19.existsSync(file)) return {};
2845
+ if (!fs17.existsSync(file)) return {};
2828
2846
  try {
2829
- const loaded = JSON.parse(fs19.readFileSync(file, "utf8"));
2847
+ const loaded = JSON.parse(fs17.readFileSync(file, "utf8"));
2830
2848
  if (loaded.model === modelId && loaded.entries) return loaded.entries;
2831
2849
  } catch {
2832
2850
  }
@@ -2835,9 +2853,9 @@ function readCacheEntries(file, modelId) {
2835
2853
  async function getNodeEmbeddings(graph, embedder, root, onProgress) {
2836
2854
  const file = vectorCachePath(root, embedder.id);
2837
2855
  let cache = { model: embedder.id, entries: {} };
2838
- if (fs19.existsSync(file)) {
2856
+ if (fs17.existsSync(file)) {
2839
2857
  try {
2840
- const loaded = JSON.parse(fs19.readFileSync(file, "utf8"));
2858
+ const loaded = JSON.parse(fs17.readFileSync(file, "utf8"));
2841
2859
  if (loaded.model === embedder.id && loaded.entries) cache = loaded;
2842
2860
  } catch {
2843
2861
  }
@@ -2855,10 +2873,10 @@ async function getNodeEmbeddings(graph, embedder, root, onProgress) {
2855
2873
  }
2856
2874
  const persist = () => {
2857
2875
  try {
2858
- fs19.mkdirSync(cacheDir(root), { recursive: true });
2876
+ fs17.mkdirSync(cacheDir(root), { recursive: true });
2859
2877
  const tmp = `${file}.tmp.${process.pid}`;
2860
- fs19.writeFileSync(tmp, JSON.stringify(cache));
2861
- fs19.renameSync(tmp, file);
2878
+ fs17.writeFileSync(tmp, JSON.stringify(cache));
2879
+ fs17.renameSync(tmp, file);
2862
2880
  } catch {
2863
2881
  }
2864
2882
  };
@@ -2896,21 +2914,21 @@ function safe(id) {
2896
2914
  }
2897
2915
  var SNAPSHOT_VERSION = "vg-freshness/1";
2898
2916
  function snapshotPath(root) {
2899
- return path19.join(cacheDir(root), "freshness.json");
2917
+ return path17.join(cacheDir(root), "freshness.json");
2900
2918
  }
2901
2919
  function writeSnapshot(root, corpusHash, fileStats, scope = {}) {
2902
2920
  const files = {};
2903
2921
  for (const f of fileStats) files[f.rel] = { size: f.size, mtimeMs: f.mtimeMs, hash: f.hash };
2904
2922
  const snapshot = { version: SNAPSHOT_VERSION, corpusHash, scope: pruneScope(scope), files };
2905
2923
  try {
2906
- fs19.mkdirSync(cacheDir(root), { recursive: true });
2907
- fs19.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2924
+ fs17.mkdirSync(cacheDir(root), { recursive: true });
2925
+ fs17.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2908
2926
  } catch {
2909
2927
  }
2910
2928
  }
2911
2929
  function loadSnapshot(root) {
2912
2930
  try {
2913
- const loaded = JSON.parse(fs19.readFileSync(snapshotPath(root), "utf8"));
2931
+ const loaded = JSON.parse(fs17.readFileSync(snapshotPath(root), "utf8"));
2914
2932
  if (loaded.version === SNAPSHOT_VERSION && loaded.files && typeof loaded.corpusHash === "string") {
2915
2933
  return { ...loaded, scope: loaded.scope ?? {} };
2916
2934
  }
@@ -2946,7 +2964,7 @@ function probeFreshness(root) {
2946
2964
  }
2947
2965
  let stat;
2948
2966
  try {
2949
- stat = fs19.statSync(file.abs);
2967
+ stat = fs17.statSync(file.abs);
2950
2968
  } catch {
2951
2969
  drift.removed.push(file.rel);
2952
2970
  continue;
@@ -2954,7 +2972,7 @@ function probeFreshness(root) {
2954
2972
  if (stat.size === entry.size && stat.mtimeMs === entry.mtimeMs) continue;
2955
2973
  let hash = "";
2956
2974
  try {
2957
- hash = hashBytes(fs19.readFileSync(file.abs));
2975
+ hash = hashBytes(fs17.readFileSync(file.abs));
2958
2976
  } catch {
2959
2977
  drift.removed.push(file.rel);
2960
2978
  continue;
@@ -2971,7 +2989,7 @@ function probeFreshness(root) {
2971
2989
  }
2972
2990
  if (absorbed && !hasDrift(drift)) {
2973
2991
  try {
2974
- fs19.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2992
+ fs17.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2975
2993
  } catch {
2976
2994
  }
2977
2995
  }
@@ -3288,221 +3306,6 @@ function estimateTokens(text) {
3288
3306
  function round2(x) {
3289
3307
  return Math.round(x * 1e6) / 1e6;
3290
3308
  }
3291
- var REFRESH_LOCK_STALE_MS = 10 * 60 * 1e3;
3292
- function refreshLockPath(root) {
3293
- return path19.join(cacheDir(root), "refresh.lock");
3294
- }
3295
- async function refreshIfStale(root, opts = {}) {
3296
- const probe = probeFreshness(root);
3297
- if (!probe) return { status: "no-snapshot" };
3298
- if (!hasDrift(probe.drift)) return { status: "fresh" };
3299
- const lock = refreshLockPath(root);
3300
- if (!acquireLock(lock, REFRESH_LOCK_STALE_MS)) return { status: "locked" };
3301
- const start = process.hrtime.bigint();
3302
- try {
3303
- const { scope } = probe;
3304
- const result = await buildGraph({
3305
- root,
3306
- only: scope.only,
3307
- exclude: scope.exclude,
3308
- paths: scope.paths,
3309
- deep: scope.deep,
3310
- noGround: scope.noGround,
3311
- scip: scope.scip,
3312
- noScip: scope.noScip,
3313
- noTsc: scope.noTsc,
3314
- cluster: scope.cluster,
3315
- grammarsDir: scope.grammarsDir,
3316
- inline: opts.inline,
3317
- jobs: opts.jobs
3318
- });
3319
- const wrote = result.graph.provenance.corpusHash !== probe.corpusHash;
3320
- if (wrote) {
3321
- const dir = vibgrateDir(root);
3322
- writeArtifacts(result.graph, {
3323
- root,
3324
- report: fs19.existsSync(path19.join(dir, "GRAPH_REPORT.md")),
3325
- html: fs19.existsSync(path19.join(dir, "graph.html"))
3326
- });
3327
- }
3328
- writeSnapshot(root, result.graph.provenance.corpusHash, result.fileStats, scope);
3329
- const ms = Number((process.hrtime.bigint() - start) / 1000000n);
3330
- return {
3331
- status: "refreshed",
3332
- drift: probe.drift,
3333
- ms,
3334
- reparsed: result.reparsed,
3335
- totalFiles: result.totalFiles,
3336
- wrote
3337
- };
3338
- } catch (err) {
3339
- return { status: "error", message: err.message };
3340
- } finally {
3341
- releaseLock(lock);
3342
- }
3343
- }
3344
- var LEDGER = "savings.jsonl";
3345
- var PER_FILE_TOKENS = 400;
3346
- var SAVINGS_TOOLS = /* @__PURE__ */ new Set(["query_graph", "get_node"]);
3347
- var CLI_TOOL_ALIASES = {
3348
- ask: "query_graph",
3349
- show: "get_node",
3350
- impact: "impact_of",
3351
- path: "find_path",
3352
- hubs: "list_hubs",
3353
- areas: "list_areas",
3354
- tree: "tree"
3355
- };
3356
- function sanitizeClient(name) {
3357
- if (typeof name !== "string") return "unknown";
3358
- const cleaned = name.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
3359
- return cleaned || "unknown";
3360
- }
3361
- function recordCliCall(root, entry, now) {
3362
- recordSaving(
3363
- root,
3364
- {
3365
- tool: entry.tool,
3366
- source: "cli",
3367
- client: sanitizeClient(entry.client),
3368
- outcome: entry.outcome,
3369
- vgTokens: entry.vgTokens ?? 0,
3370
- baselineTokens: (entry.baselineFiles ?? 0) * PER_FILE_TOKENS
3371
- },
3372
- now
3373
- );
3374
- }
3375
- function ledgerPath(root) {
3376
- return path19.join(cacheDir(root), LEDGER);
3377
- }
3378
- function savingsRecorded(root) {
3379
- return fs19.existsSync(ledgerPath(root));
3380
- }
3381
- function recordSaving(root, entry, now) {
3382
- try {
3383
- fs19.mkdirSync(cacheDir(root), { recursive: true });
3384
- const line = JSON.stringify({ ts: now, ...entry });
3385
- fs19.appendFileSync(ledgerPath(root), line + "\n");
3386
- } catch {
3387
- }
3388
- }
3389
- var DEFAULT_RATE_PER_M = 3;
3390
- var DEFAULT_RATE_LABEL = "input @ $3/1M";
3391
- function readSavings(root, days, now, ratePerM = DEFAULT_RATE_PER_M) {
3392
- const file = ledgerPath(root);
3393
- const cutoff = now - days * 24 * 60 * 60 * 1e3;
3394
- let queries = 0;
3395
- let vgTokens = 0;
3396
- let baselineTokens = 0;
3397
- if (fs19.existsSync(file)) {
3398
- for (const line of fs19.readFileSync(file, "utf8").split("\n")) {
3399
- if (!line.trim()) continue;
3400
- try {
3401
- const e = JSON.parse(line);
3402
- if (e.ts < cutoff) continue;
3403
- if (!SAVINGS_TOOLS.has(e.tool)) continue;
3404
- queries++;
3405
- vgTokens += e.vgTokens;
3406
- baselineTokens += e.baselineTokens;
3407
- } catch {
3408
- }
3409
- }
3410
- }
3411
- const estCostVg = vgTokens / 1e6 * ratePerM;
3412
- const estCostBaseline = baselineTokens / 1e6 * ratePerM;
3413
- return {
3414
- enabled: savingsRecorded(root),
3415
- days,
3416
- queries,
3417
- vgTokens,
3418
- baselineTokens,
3419
- ratio: vgTokens > 0 ? Math.round(baselineTokens / vgTokens * 100) / 100 : 0,
3420
- estCostVg: round22(estCostVg),
3421
- estCostBaseline: round22(estCostBaseline),
3422
- saved: round22(estCostBaseline - estCostVg),
3423
- rateLabel: DEFAULT_RATE_LABEL
3424
- };
3425
- }
3426
- function round22(x) {
3427
- return Math.round(x * 100) / 100;
3428
- }
3429
- function toDimensionStats(byKey) {
3430
- return [...byKey.entries()].map(([key, r]) => {
3431
- const calls = r.complete + r.partial + r.miss;
3432
- return {
3433
- key,
3434
- calls,
3435
- complete: r.complete,
3436
- partial: r.partial,
3437
- miss: r.miss,
3438
- successPct: calls ? Math.round((r.complete + r.partial) / calls * 100) : 0
3439
- };
3440
- }).sort((a, b) => b.calls - a.calls || a.key.localeCompare(b.key));
3441
- }
3442
- function readUsage(root, days, now) {
3443
- const file = ledgerPath(root);
3444
- const cutoff = now - days * 24 * 60 * 60 * 1e3;
3445
- const byTool = /* @__PURE__ */ new Map();
3446
- const bySource = /* @__PURE__ */ new Map();
3447
- const byClient = /* @__PURE__ */ new Map();
3448
- const msByTool = /* @__PURE__ */ new Map();
3449
- const bump2 = (m, key, outcome) => {
3450
- const row = m.get(key) ?? { complete: 0, partial: 0, miss: 0 };
3451
- row[outcome]++;
3452
- m.set(key, row);
3453
- };
3454
- if (fs19.existsSync(file)) {
3455
- for (const line of fs19.readFileSync(file, "utf8").split("\n")) {
3456
- if (!line.trim()) continue;
3457
- try {
3458
- const e = JSON.parse(line);
3459
- if (e.ts < cutoff) continue;
3460
- const outcome = e.outcome ?? "complete";
3461
- bump2(byTool, e.tool, outcome);
3462
- bump2(bySource, e.source ?? "mcp", outcome);
3463
- bump2(byClient, e.client ?? "unknown", outcome);
3464
- if (typeof e.ms === "number" && e.ms >= 0) {
3465
- const timing = msByTool.get(e.tool) ?? { sum: 0, n: 0 };
3466
- timing.sum += e.ms;
3467
- timing.n++;
3468
- msByTool.set(e.tool, timing);
3469
- }
3470
- } catch {
3471
- }
3472
- }
3473
- }
3474
- const commands = toDimensionStats(byTool).map((d) => {
3475
- const timing = msByTool.get(d.key);
3476
- return {
3477
- tool: d.key,
3478
- calls: d.calls,
3479
- complete: d.complete,
3480
- partial: d.partial,
3481
- miss: d.miss,
3482
- successPct: d.successPct,
3483
- avgMs: timing && timing.n > 0 ? Math.round(timing.sum / timing.n) : null
3484
- };
3485
- });
3486
- const totals = commands.reduce(
3487
- (t, c) => ({
3488
- calls: t.calls + c.calls,
3489
- complete: t.complete + c.complete,
3490
- partial: t.partial + c.partial,
3491
- miss: t.miss + c.miss
3492
- }),
3493
- { calls: 0, complete: 0, partial: 0, miss: 0 }
3494
- );
3495
- const avgSuccessPct = commands.length ? Math.round(commands.reduce((s, c) => s + c.successPct, 0) / commands.length) : 0;
3496
- return {
3497
- enabled: savingsRecorded(root),
3498
- days,
3499
- commands,
3500
- sources: toDimensionStats(bySource),
3501
- clients: toDimensionStats(byClient),
3502
- totals,
3503
- avgSuccessPct
3504
- };
3505
- }
3506
3309
 
3507
3310
  // src/engine/lookup.ts
3508
3311
  function findNodes(graph, query) {
@@ -3660,10 +3463,10 @@ function testsToRun(graph, rootId, depth = 4) {
3660
3463
  };
3661
3464
  }
3662
3465
  function detectRunner(root, lang) {
3663
- const pkgPath = path19.join(root, "package.json");
3664
- if (fs19.existsSync(pkgPath)) {
3466
+ const pkgPath = path17.join(root, "package.json");
3467
+ if (fs17.existsSync(pkgPath)) {
3665
3468
  try {
3666
- const pkg = JSON.parse(fs19.readFileSync(pkgPath, "utf8"));
3469
+ const pkg = JSON.parse(fs17.readFileSync(pkgPath, "utf8"));
3667
3470
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
3668
3471
  if (deps.vitest) return { name: "vitest", command: (f) => `npx vitest run ${f.join(" ")}`.trim() };
3669
3472
  if (deps.jest) return { name: "jest", command: (f) => `npx jest ${f.join(" ")}`.trim() };
@@ -3671,10 +3474,10 @@ function detectRunner(root, lang) {
3671
3474
  } catch {
3672
3475
  }
3673
3476
  }
3674
- if (lang === "py" || fs19.existsSync(path19.join(root, "pyproject.toml")) || fs19.existsSync(path19.join(root, "pytest.ini"))) {
3477
+ if (lang === "py" || fs17.existsSync(path17.join(root, "pyproject.toml")) || fs17.existsSync(path17.join(root, "pytest.ini"))) {
3675
3478
  return { name: "pytest", command: (f) => `pytest ${f.join(" ")}`.trim() };
3676
3479
  }
3677
- if (lang === "go" || fs19.existsSync(path19.join(root, "go.mod"))) {
3480
+ if (lang === "go" || fs17.existsSync(path17.join(root, "go.mod"))) {
3678
3481
  return { name: "go test", command: () => "go test ./..." };
3679
3482
  }
3680
3483
  if (lang === "cs" || hasFile(root, /\.csproj$/) || hasFile(root, /\.sln$/)) {
@@ -3685,7 +3488,7 @@ function detectRunner(root, lang) {
3685
3488
  }
3686
3489
  function hasFile(root, re) {
3687
3490
  try {
3688
- return fs19.readdirSync(root).some((f) => re.test(f));
3491
+ return fs17.readdirSync(root).some((f) => re.test(f));
3689
3492
  } catch {
3690
3493
  return false;
3691
3494
  }
@@ -3757,7 +3560,7 @@ function findManifests(root) {
3757
3560
  if (depth > 8 || scanned > MAX_ENTRIES2) return;
3758
3561
  let entries;
3759
3562
  try {
3760
- entries = fs19.readdirSync(dir, { withFileTypes: true });
3563
+ entries = fs17.readdirSync(dir, { withFileTypes: true });
3761
3564
  } catch {
3762
3565
  return;
3763
3566
  }
@@ -3765,11 +3568,11 @@ function findManifests(root) {
3765
3568
  scanned++;
3766
3569
  if (scanned > MAX_ENTRIES2) break;
3767
3570
  if (e.isDirectory()) {
3768
- if (!SKIP_DIRS2.has(e.name) && !e.name.startsWith(".")) walk2(path19.join(dir, e.name), depth + 1);
3571
+ if (!SKIP_DIRS2.has(e.name) && !e.name.startsWith(".")) walk2(path17.join(dir, e.name), depth + 1);
3769
3572
  continue;
3770
3573
  }
3771
3574
  const eco = MANIFEST_BY_FILE[e.name] ?? (/\.(cs|fs)proj$/i.test(e.name) ? "dotnet" : void 0);
3772
- if (eco) set[eco].push(path19.join(dir, e.name));
3575
+ if (eco) set[eco].push(path17.join(dir, e.name));
3773
3576
  }
3774
3577
  };
3775
3578
  walk2(root, 0);
@@ -3781,11 +3584,11 @@ function npmDeps(files) {
3781
3584
  for (const file of files) {
3782
3585
  let pkg;
3783
3586
  try {
3784
- pkg = JSON.parse(fs19.readFileSync(file, "utf8"));
3587
+ pkg = JSON.parse(fs17.readFileSync(file, "utf8"));
3785
3588
  } catch {
3786
3589
  continue;
3787
3590
  }
3788
- const dir = path19.dirname(file);
3591
+ const dir = path17.dirname(file);
3789
3592
  const all = { ...pkg.dependencies, ...pkg.devDependencies };
3790
3593
  for (const [name, declared] of Object.entries(all)) {
3791
3594
  out.push({ name, ecosystem: "npm", declared, installed: installedNpmVersion(dir, name) });
@@ -3796,12 +3599,12 @@ function npmDeps(files) {
3796
3599
  function installedNpmVersion(dir, name) {
3797
3600
  let cur = dir;
3798
3601
  for (let i = 0; i < 12; i++) {
3799
- const p = path19.join(cur, "node_modules", name, "package.json");
3602
+ const p = path17.join(cur, "node_modules", name, "package.json");
3800
3603
  try {
3801
- return JSON.parse(fs19.readFileSync(p, "utf8")).version;
3604
+ return JSON.parse(fs17.readFileSync(p, "utf8")).version;
3802
3605
  } catch {
3803
3606
  }
3804
- const parent = path19.dirname(cur);
3607
+ const parent = path17.dirname(cur);
3805
3608
  if (parent === cur) break;
3806
3609
  cur = parent;
3807
3610
  }
@@ -3818,21 +3621,21 @@ function normalizePep503(name) {
3818
3621
  function sitePackagesDirs(base) {
3819
3622
  const out = [];
3820
3623
  try {
3821
- for (const d of fs19.readdirSync(path19.join(base, "lib"))) {
3822
- if (d.startsWith("python")) out.push(path19.join(base, "lib", d, "site-packages"));
3624
+ for (const d of fs17.readdirSync(path17.join(base, "lib"))) {
3625
+ if (d.startsWith("python")) out.push(path17.join(base, "lib", d, "site-packages"));
3823
3626
  }
3824
3627
  } catch {
3825
3628
  }
3826
- out.push(path19.join(base, "Lib", "site-packages"));
3629
+ out.push(path17.join(base, "Lib", "site-packages"));
3827
3630
  return out;
3828
3631
  }
3829
3632
  function installedPypiVersion(root, name) {
3830
3633
  const target = normalizePep503(name);
3831
3634
  for (const venv of [".venv", "venv", "env", ".tox"]) {
3832
- for (const sp of sitePackagesDirs(path19.join(root, venv))) {
3635
+ for (const sp of sitePackagesDirs(path17.join(root, venv))) {
3833
3636
  let entries;
3834
3637
  try {
3835
- entries = fs19.readdirSync(sp);
3638
+ entries = fs17.readdirSync(sp);
3836
3639
  } catch {
3837
3640
  continue;
3838
3641
  }
@@ -3847,7 +3650,7 @@ function installedPypiVersion(root, name) {
3847
3650
  function installedPhpVersion(root, name) {
3848
3651
  let data;
3849
3652
  try {
3850
- data = JSON.parse(fs19.readFileSync(path19.join(root, "vendor", "composer", "installed.json"), "utf8"));
3653
+ data = JSON.parse(fs17.readFileSync(path17.join(root, "vendor", "composer", "installed.json"), "utf8"));
3851
3654
  } catch {
3852
3655
  return void 0;
3853
3656
  }
@@ -3866,11 +3669,11 @@ function pypiDeps(files) {
3866
3669
  for (const file of files) {
3867
3670
  let text;
3868
3671
  try {
3869
- text = fs19.readFileSync(file, "utf8");
3672
+ text = fs17.readFileSync(file, "utf8");
3870
3673
  } catch {
3871
3674
  continue;
3872
3675
  }
3873
- if (path19.basename(file) === "requirements.txt") {
3676
+ if (path17.basename(file) === "requirements.txt") {
3874
3677
  for (const line of text.split("\n")) {
3875
3678
  const t = line.trim();
3876
3679
  if (!t || t.startsWith("#") || t.startsWith("-")) continue;
@@ -3954,7 +3757,7 @@ function goDeps(files) {
3954
3757
  for (const mod of files) {
3955
3758
  let text;
3956
3759
  try {
3957
- text = fs19.readFileSync(mod, "utf8");
3760
+ text = fs17.readFileSync(mod, "utf8");
3958
3761
  } catch {
3959
3762
  continue;
3960
3763
  }
@@ -3972,7 +3775,7 @@ function cargoDeps(files) {
3972
3775
  for (const file of files) {
3973
3776
  let text;
3974
3777
  try {
3975
- text = fs19.readFileSync(file, "utf8");
3778
+ text = fs17.readFileSync(file, "utf8");
3976
3779
  } catch {
3977
3780
  continue;
3978
3781
  }
@@ -4004,7 +3807,7 @@ function rubyDeps(files) {
4004
3807
  for (const file of files) {
4005
3808
  let text;
4006
3809
  try {
4007
- text = fs19.readFileSync(file, "utf8");
3810
+ text = fs17.readFileSync(file, "utf8");
4008
3811
  } catch {
4009
3812
  continue;
4010
3813
  }
@@ -4019,7 +3822,7 @@ function phpDeps(files) {
4019
3822
  for (const file of files) {
4020
3823
  let pkg;
4021
3824
  try {
4022
- pkg = JSON.parse(fs19.readFileSync(file, "utf8"));
3825
+ pkg = JSON.parse(fs17.readFileSync(file, "utf8"));
4023
3826
  } catch {
4024
3827
  continue;
4025
3828
  }
@@ -4036,7 +3839,7 @@ function dotnetDeps(files) {
4036
3839
  for (const file of files) {
4037
3840
  let text;
4038
3841
  try {
4039
- text = fs19.readFileSync(file, "utf8");
3842
+ text = fs17.readFileSync(file, "utf8");
4040
3843
  } catch {
4041
3844
  continue;
4042
3845
  }
@@ -4056,7 +3859,7 @@ function swiftDeps(files) {
4056
3859
  for (const file of files) {
4057
3860
  let text;
4058
3861
  try {
4059
- text = fs19.readFileSync(file, "utf8");
3862
+ text = fs17.readFileSync(file, "utf8");
4060
3863
  } catch {
4061
3864
  continue;
4062
3865
  }
@@ -4078,7 +3881,7 @@ function dartDeps(files) {
4078
3881
  for (const file of files) {
4079
3882
  let text;
4080
3883
  try {
4081
- text = fs19.readFileSync(file, "utf8");
3884
+ text = fs17.readFileSync(file, "utf8");
4082
3885
  } catch {
4083
3886
  continue;
4084
3887
  }
@@ -4103,11 +3906,11 @@ function javaDeps(files) {
4103
3906
  for (const file of files) {
4104
3907
  let text;
4105
3908
  try {
4106
- text = fs19.readFileSync(file, "utf8");
3909
+ text = fs17.readFileSync(file, "utf8");
4107
3910
  } catch {
4108
3911
  continue;
4109
3912
  }
4110
- if (path19.basename(file) === "pom.xml") {
3913
+ if (path17.basename(file) === "pom.xml") {
4111
3914
  for (const block of text.match(/<dependency>[\s\S]*?<\/dependency>/g) ?? []) {
4112
3915
  const g = /<groupId>([^<]+)<\/groupId>/.exec(block);
4113
3916
  const a = /<artifactId>([^<]+)<\/artifactId>/.exec(block);
@@ -4153,10 +3956,10 @@ function discoverModels(home = os.homedir()) {
4153
3956
  );
4154
3957
  }
4155
3958
  function ollama(home) {
4156
- const base = path19.join(home, ".ollama", "models", "manifests");
3959
+ const base = path17.join(home, ".ollama", "models", "manifests");
4157
3960
  const out = [];
4158
3961
  walk(base, 4, (file) => {
4159
- const rel2 = path19.relative(base, file).split(path19.sep);
3962
+ const rel2 = path17.relative(base, file).split(path17.sep);
4160
3963
  if (rel2.length >= 2) {
4161
3964
  const tag = rel2[rel2.length - 1];
4162
3965
  const model = rel2[rel2.length - 2];
@@ -4166,24 +3969,24 @@ function ollama(home) {
4166
3969
  return dedupe(out);
4167
3970
  }
4168
3971
  function lmStudio(home) {
4169
- const bases = [path19.join(home, ".lmstudio", "models"), path19.join(home, ".cache", "lm-studio", "models")];
3972
+ const bases = [path17.join(home, ".lmstudio", "models"), path17.join(home, ".cache", "lm-studio", "models")];
4170
3973
  const out = [];
4171
3974
  for (const base of bases) {
4172
3975
  walk(base, 5, (file) => {
4173
3976
  if (!file.endsWith(".gguf")) return;
4174
- const rel2 = path19.relative(base, file).replace(/\\/g, "/");
3977
+ const rel2 = path17.relative(base, file).replace(/\\/g, "/");
4175
3978
  out.push({ runtime: "lm-studio", name: rel2, path: file });
4176
3979
  });
4177
3980
  }
4178
3981
  return dedupe(out);
4179
3982
  }
4180
3983
  function looseGguf(home) {
4181
- const bases = [path19.join(home, "models"), path19.join(home, ".cache", "huggingface")];
3984
+ const bases = [path17.join(home, "models"), path17.join(home, ".cache", "huggingface")];
4182
3985
  const out = [];
4183
3986
  for (const base of bases) {
4184
3987
  walk(base, 4, (file) => {
4185
3988
  if (!file.endsWith(".gguf")) return;
4186
- out.push({ runtime: "gguf", name: path19.basename(file), path: file });
3989
+ out.push({ runtime: "gguf", name: path17.basename(file), path: file });
4187
3990
  });
4188
3991
  }
4189
3992
  return dedupe(out);
@@ -4192,12 +3995,12 @@ function walk(dir, depth, onFile) {
4192
3995
  if (depth < 0) return;
4193
3996
  let entries;
4194
3997
  try {
4195
- entries = fs19.readdirSync(dir, { withFileTypes: true });
3998
+ entries = fs17.readdirSync(dir, { withFileTypes: true });
4196
3999
  } catch {
4197
4000
  return;
4198
4001
  }
4199
4002
  for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
4200
- const abs = path19.join(dir, e.name);
4003
+ const abs = path17.join(dir, e.name);
4201
4004
  if (e.isDirectory()) walk(abs, depth - 1, onFile);
4202
4005
  else if (e.isFile()) onFile(abs);
4203
4006
  }
@@ -4228,7 +4031,7 @@ function lockfileVersion(root, ecosystem, name) {
4228
4031
  function gradleLock(root, name) {
4229
4032
  let text;
4230
4033
  try {
4231
- text = fs19.readFileSync(path19.join(root, "gradle.lockfile"), "utf8");
4034
+ text = fs17.readFileSync(path17.join(root, "gradle.lockfile"), "utf8");
4232
4035
  } catch {
4233
4036
  return void 0;
4234
4037
  }
@@ -4238,7 +4041,7 @@ function gradleLock(root, name) {
4238
4041
  function packageResolved(root, name) {
4239
4042
  let data;
4240
4043
  try {
4241
- data = JSON.parse(fs19.readFileSync(path19.join(root, "Package.resolved"), "utf8"));
4044
+ data = JSON.parse(fs17.readFileSync(path17.join(root, "Package.resolved"), "utf8"));
4242
4045
  } catch {
4243
4046
  return void 0;
4244
4047
  }
@@ -4254,7 +4057,7 @@ function packageResolved(root, name) {
4254
4057
  function pubspecLock(root, name) {
4255
4058
  let text;
4256
4059
  try {
4257
- text = fs19.readFileSync(path19.join(root, "pubspec.lock"), "utf8");
4060
+ text = fs17.readFileSync(path17.join(root, "pubspec.lock"), "utf8");
4258
4061
  } catch {
4259
4062
  return void 0;
4260
4063
  }
@@ -4278,7 +4081,7 @@ function escapeRegExp(s) {
4278
4081
  function gemfileLock(root, name) {
4279
4082
  let text;
4280
4083
  try {
4281
- text = fs19.readFileSync(path19.join(root, "Gemfile.lock"), "utf8");
4084
+ text = fs17.readFileSync(path17.join(root, "Gemfile.lock"), "utf8");
4282
4085
  } catch {
4283
4086
  return void 0;
4284
4087
  }
@@ -4288,7 +4091,7 @@ function gemfileLock(root, name) {
4288
4091
  function composerLock(root, name) {
4289
4092
  let data;
4290
4093
  try {
4291
- data = JSON.parse(fs19.readFileSync(path19.join(root, "composer.lock"), "utf8"));
4094
+ data = JSON.parse(fs17.readFileSync(path17.join(root, "composer.lock"), "utf8"));
4292
4095
  } catch {
4293
4096
  return void 0;
4294
4097
  }
@@ -4303,7 +4106,7 @@ function composerLock(root, name) {
4303
4106
  function packagesLock(root, name) {
4304
4107
  let data;
4305
4108
  try {
4306
- data = JSON.parse(fs19.readFileSync(path19.join(root, "packages.lock.json"), "utf8"));
4109
+ data = JSON.parse(fs17.readFileSync(path17.join(root, "packages.lock.json"), "utf8"));
4307
4110
  } catch {
4308
4111
  return void 0;
4309
4112
  }
@@ -4328,7 +4131,7 @@ function pypiLockVersion(root, name) {
4328
4131
  function tomlPackageLock(root, file, name, normalize3) {
4329
4132
  let text;
4330
4133
  try {
4331
- text = fs19.readFileSync(path19.join(root, file), "utf8");
4134
+ text = fs17.readFileSync(path17.join(root, file), "utf8");
4332
4135
  } catch {
4333
4136
  return void 0;
4334
4137
  }
@@ -4345,7 +4148,7 @@ function tomlPackageLock(root, file, name, normalize3) {
4345
4148
  function pipfileLock(root, name) {
4346
4149
  let data;
4347
4150
  try {
4348
- data = JSON.parse(fs19.readFileSync(path19.join(root, "Pipfile.lock"), "utf8"));
4151
+ data = JSON.parse(fs17.readFileSync(path17.join(root, "Pipfile.lock"), "utf8"));
4349
4152
  } catch {
4350
4153
  return void 0;
4351
4154
  }
@@ -4364,7 +4167,7 @@ function pipfileLock(root, name) {
4364
4167
  function packageLockVersion(root, name) {
4365
4168
  let data;
4366
4169
  try {
4367
- data = JSON.parse(fs19.readFileSync(path19.join(root, "package-lock.json"), "utf8"));
4170
+ data = JSON.parse(fs17.readFileSync(path17.join(root, "package-lock.json"), "utf8"));
4368
4171
  } catch {
4369
4172
  return void 0;
4370
4173
  }
@@ -4376,7 +4179,7 @@ function packageLockVersion(root, name) {
4376
4179
  function yarnLockVersion(root, name) {
4377
4180
  let text;
4378
4181
  try {
4379
- text = fs19.readFileSync(path19.join(root, "yarn.lock"), "utf8");
4182
+ text = fs17.readFileSync(path17.join(root, "yarn.lock"), "utf8");
4380
4183
  } catch {
4381
4184
  return void 0;
4382
4185
  }
@@ -4404,19 +4207,19 @@ function headerNamesPackage(header, name) {
4404
4207
  // src/engine/lib.ts
4405
4208
  var LIB_SCHEMA = "vg-lib/1.0";
4406
4209
  function catalogPath(root) {
4407
- return path19.join(root, "vibgrate.lib.json");
4210
+ return path17.join(root, "vibgrate.lib.json");
4408
4211
  }
4409
4212
  function libDir(root) {
4410
- return path19.join(root, ".vibgrate", "lib");
4213
+ return path17.join(root, ".vibgrate", "lib");
4411
4214
  }
4412
4215
  function libId(name) {
4413
4216
  return name.trim().toLowerCase().replace(/^@/, "").replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
4414
4217
  }
4415
4218
  function loadCatalog(root) {
4416
4219
  const file = catalogPath(root);
4417
- if (fs19.existsSync(file)) {
4220
+ if (fs17.existsSync(file)) {
4418
4221
  try {
4419
- const data = JSON.parse(fs19.readFileSync(file, "utf8"));
4222
+ const data = JSON.parse(fs17.readFileSync(file, "utf8"));
4420
4223
  if (data.schemaVersion === LIB_SCHEMA && data.libraries) return data;
4421
4224
  } catch {
4422
4225
  }
@@ -4424,7 +4227,7 @@ function loadCatalog(root) {
4424
4227
  return { schemaVersion: LIB_SCHEMA, libraries: {} };
4425
4228
  }
4426
4229
  function saveCatalog(root, catalog) {
4427
- fs19.writeFileSync(catalogPath(root), `${stableStringify(catalog, 2)}
4230
+ fs17.writeFileSync(catalogPath(root), `${stableStringify(catalog, 2)}
4428
4231
  `);
4429
4232
  }
4430
4233
  function resolveLib(catalog, name) {
@@ -4500,23 +4303,23 @@ async function addLibrary(source, opts) {
4500
4303
  content = await res.text();
4501
4304
  type = source.endsWith("llms.txt") ? "llms.txt" : source.endsWith(".json") ? "openapi" : "website";
4502
4305
  } else {
4503
- const abs = path19.resolve(root, source);
4504
- if (!fs19.existsSync(abs)) throw new Error(`source not found: ${source}`);
4306
+ const abs = path17.resolve(root, source);
4307
+ if (!fs17.existsSync(abs)) throw new Error(`source not found: ${source}`);
4505
4308
  content = readLocal(abs);
4506
4309
  type = "local";
4507
4310
  }
4508
4311
  const name = opts.name ?? inferName(source);
4509
4312
  const id = libId(name);
4510
4313
  const version = opts.version ?? installedVersion(root, name) ?? "*";
4511
- fs19.mkdirSync(libDir(root), { recursive: true });
4512
- const docFileAbs = path19.join(libDir(root), `${id}.md`);
4513
- fs19.writeFileSync(docFileAbs, content);
4314
+ fs17.mkdirSync(libDir(root), { recursive: true });
4315
+ const docFileAbs = path17.join(libDir(root), `${id}.md`);
4316
+ fs17.writeFileSync(docFileAbs, content);
4514
4317
  const entry = {
4515
4318
  id,
4516
4319
  name,
4517
4320
  version,
4518
4321
  source: { type, location: source },
4519
- docFile: path19.relative(root, docFileAbs).split(path19.sep).join("/"),
4322
+ docFile: path17.relative(root, docFileAbs).split(path17.sep).join("/"),
4520
4323
  docHash: hashString(content),
4521
4324
  bytes: Buffer.byteLength(content, "utf8")
4522
4325
  };
@@ -4526,15 +4329,15 @@ async function addLibrary(source, opts) {
4526
4329
  return entry;
4527
4330
  }
4528
4331
  function readDoc(root, entry) {
4529
- const abs = path19.resolve(root, entry.docFile);
4530
- return fs19.existsSync(abs) ? fs19.readFileSync(abs, "utf8") : "";
4332
+ const abs = path17.resolve(root, entry.docFile);
4333
+ return fs17.existsSync(abs) ? fs17.readFileSync(abs, "utf8") : "";
4531
4334
  }
4532
4335
  function npmPackageDir(root, name) {
4533
4336
  let cur = root;
4534
4337
  for (let i = 0; i < 12; i++) {
4535
- const dir = path19.join(cur, "node_modules", name);
4536
- if (fs19.existsSync(path19.join(dir, "package.json"))) return dir;
4537
- const parent = path19.dirname(cur);
4338
+ const dir = path17.join(cur, "node_modules", name);
4339
+ if (fs17.existsSync(path17.join(dir, "package.json"))) return dir;
4340
+ const parent = path17.dirname(cur);
4538
4341
  if (parent === cur) break;
4539
4342
  cur = parent;
4540
4343
  }
@@ -4545,24 +4348,24 @@ function localPackageDocs(root, name) {
4545
4348
  if (!dir) return void 0;
4546
4349
  const version = resolveVersion(root, name).served;
4547
4350
  for (const f of ["llms.txt", "README.md", "README.mdx", "README", "readme.md"]) {
4548
- const p = path19.join(dir, f);
4351
+ const p = path17.join(dir, f);
4549
4352
  try {
4550
- const docs = fs19.readFileSync(p, "utf8");
4353
+ const docs = fs17.readFileSync(p, "utf8");
4551
4354
  if (docs.trim()) {
4552
- return { docs, version, source: path19.relative(root, p).split(path19.sep).join("/") };
4355
+ return { docs, version, source: path17.relative(root, p).split(path17.sep).join("/") };
4553
4356
  }
4554
4357
  } catch {
4555
4358
  }
4556
4359
  }
4557
4360
  try {
4558
- const pkg = JSON.parse(fs19.readFileSync(path19.join(dir, "package.json"), "utf8"));
4361
+ const pkg = JSON.parse(fs17.readFileSync(path17.join(dir, "package.json"), "utf8"));
4559
4362
  if (pkg.description?.trim()) {
4560
4363
  return {
4561
4364
  docs: `# ${name}
4562
4365
 
4563
4366
  ${pkg.description}`,
4564
4367
  version: version ?? pkg.version,
4565
- source: path19.relative(root, path19.join(dir, "package.json")).split(path19.sep).join("/")
4368
+ source: path17.relative(root, path17.join(dir, "package.json")).split(path17.sep).join("/")
4566
4369
  };
4567
4370
  }
4568
4371
  } catch {
@@ -4571,17 +4374,17 @@ ${pkg.description}`,
4571
4374
  }
4572
4375
  function dtsEntry(dir) {
4573
4376
  try {
4574
- const pkg = JSON.parse(fs19.readFileSync(path19.join(dir, "package.json"), "utf8"));
4377
+ const pkg = JSON.parse(fs17.readFileSync(path17.join(dir, "package.json"), "utf8"));
4575
4378
  const rel2 = pkg.types ?? pkg.typings;
4576
4379
  if (typeof rel2 === "string") {
4577
- const p = path19.join(dir, rel2);
4578
- if (fs19.existsSync(p)) return p;
4380
+ const p = path17.join(dir, rel2);
4381
+ if (fs17.existsSync(p)) return p;
4579
4382
  }
4580
4383
  } catch {
4581
4384
  }
4582
4385
  for (const c of ["index.d.ts", "dist/index.d.ts", "lib/index.d.ts", "types/index.d.ts"]) {
4583
- const p = path19.join(dir, c);
4584
- if (fs19.existsSync(p)) return p;
4386
+ const p = path17.join(dir, c);
4387
+ if (fs17.existsSync(p)) return p;
4585
4388
  }
4586
4389
  return void 0;
4587
4390
  }
@@ -4614,7 +4417,7 @@ function localApiSurface(root, name) {
4614
4417
  if (!dts) return void 0;
4615
4418
  let src;
4616
4419
  try {
4617
- src = fs19.readFileSync(dts, "utf8");
4420
+ src = fs17.readFileSync(dts, "utf8");
4618
4421
  } catch {
4619
4422
  return void 0;
4620
4423
  }
@@ -4622,36 +4425,36 @@ function localApiSurface(root, name) {
4622
4425
  return decls.length ? decls.join("\n\n") : void 0;
4623
4426
  }
4624
4427
  function cloneAndReadGit(url) {
4625
- const dir = fs19.mkdtempSync(path19.join(os.tmpdir(), "vg-lib-git-"));
4428
+ const dir = fs17.mkdtempSync(path17.join(os.tmpdir(), "vg-lib-git-"));
4626
4429
  try {
4627
4430
  execFileSync("git", ["clone", "--depth", "1", "--quiet", url, dir], {
4628
4431
  stdio: ["ignore", "ignore", "pipe"]
4629
4432
  });
4630
4433
  } catch (err) {
4631
- fs19.rmSync(dir, { recursive: true, force: true });
4434
+ fs17.rmSync(dir, { recursive: true, force: true });
4632
4435
  const detail = err instanceof Error && err.message ? `: ${err.message.split("\n")[0]}` : "";
4633
4436
  throw new Error(`git clone failed for ${url}${detail}`);
4634
4437
  }
4635
4438
  try {
4636
4439
  return readGitDocs(dir);
4637
4440
  } finally {
4638
- fs19.rmSync(dir, { recursive: true, force: true });
4441
+ fs17.rmSync(dir, { recursive: true, force: true });
4639
4442
  }
4640
4443
  }
4641
4444
  function readGitDocs(dir) {
4642
- const docsDir = path19.join(dir, "docs");
4643
- if (fs19.existsSync(docsDir) && fs19.statSync(docsDir).isDirectory()) return readLocal(docsDir);
4644
- const readme = ["README.md", "README.mdx", "README.txt", "README.rst", "readme.md"].map((f) => path19.join(dir, f)).find((p) => fs19.existsSync(p));
4645
- if (readme) return fs19.readFileSync(readme, "utf8");
4445
+ const docsDir = path17.join(dir, "docs");
4446
+ if (fs17.existsSync(docsDir) && fs17.statSync(docsDir).isDirectory()) return readLocal(docsDir);
4447
+ const readme = ["README.md", "README.mdx", "README.txt", "README.rst", "readme.md"].map((f) => path17.join(dir, f)).find((p) => fs17.existsSync(p));
4448
+ if (readme) return fs17.readFileSync(readme, "utf8");
4646
4449
  return readLocal(dir);
4647
4450
  }
4648
4451
  function readLocal(abs) {
4649
- const stat = fs19.statSync(abs);
4650
- if (stat.isFile()) return fs19.readFileSync(abs, "utf8");
4452
+ const stat = fs17.statSync(abs);
4453
+ if (stat.isFile()) return fs17.readFileSync(abs, "utf8");
4651
4454
  const parts = [];
4652
- const files = fs19.readdirSync(abs, { withFileTypes: true }).filter((e) => e.isFile() && /\.(md|mdx|txt|rst)$/i.test(e.name)).map((e) => e.name).sort();
4455
+ const files = fs17.readdirSync(abs, { withFileTypes: true }).filter((e) => e.isFile() && /\.(md|mdx|txt|rst)$/i.test(e.name)).map((e) => e.name).sort();
4653
4456
  for (const f of files) parts.push(`<!-- ${f} -->
4654
- ${fs19.readFileSync(path19.join(abs, f), "utf8")}`);
4457
+ ${fs17.readFileSync(path17.join(abs, f), "utf8")}`);
4655
4458
  return parts.join("\n\n");
4656
4459
  }
4657
4460
  function inferName(source) {
@@ -4660,10 +4463,10 @@ function inferName(source) {
4660
4463
  return base.replace(/\.(md|mdx|txt|rst|json)$/i, "").replace(/^llms$/, "docs");
4661
4464
  }
4662
4465
  function findGitRoot(startDir = process.cwd()) {
4663
- let dir = path19.resolve(startDir);
4466
+ let dir = path17.resolve(startDir);
4664
4467
  for (; ; ) {
4665
- if (fs19.existsSync(path19.join(dir, ".git"))) return dir;
4666
- const parent = path19.dirname(dir);
4468
+ if (fs17.existsSync(path17.join(dir, ".git"))) return dir;
4469
+ const parent = path17.dirname(dir);
4667
4470
  if (parent === dir) return null;
4668
4471
  dir = parent;
4669
4472
  }
@@ -4674,19 +4477,19 @@ function normalizeEntry(line) {
4674
4477
  function ensureGitignored(entry, startDir = process.cwd()) {
4675
4478
  const root = findGitRoot(startDir);
4676
4479
  if (!root) return { status: "not-a-repo", entry };
4677
- const gitignorePath = path19.join(root, ".gitignore");
4480
+ const gitignorePath = path17.join(root, ".gitignore");
4678
4481
  const target = normalizeEntry(entry);
4679
4482
  let existing = "";
4680
4483
  let fileExisted = true;
4681
4484
  try {
4682
- existing = fs19.readFileSync(gitignorePath, "utf8");
4485
+ existing = fs17.readFileSync(gitignorePath, "utf8");
4683
4486
  } catch {
4684
4487
  fileExisted = false;
4685
4488
  }
4686
4489
  const alreadyPresent = existing.split(/\r?\n/).some((line) => normalizeEntry(line) === target);
4687
4490
  if (alreadyPresent) return { status: "present", entry, gitignorePath };
4688
4491
  const needsLeadingNewline = existing.length > 0 && !existing.endsWith("\n");
4689
- fs19.appendFileSync(gitignorePath, `${needsLeadingNewline ? "\n" : ""}${entry}
4492
+ fs17.appendFileSync(gitignorePath, `${needsLeadingNewline ? "\n" : ""}${entry}
4690
4493
  `, "utf8");
4691
4494
  return { status: fileExisted ? "added" : "created", entry, gitignorePath };
4692
4495
  }
@@ -4840,14 +4643,14 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
4840
4643
  console.log(chalk.dim("The secret must be registered on your Vibgrate ingest API."));
4841
4644
  }
4842
4645
  if (opts.write) {
4843
- const writePath = path19.resolve(opts.write);
4646
+ const writePath = path17.resolve(opts.write);
4844
4647
  await writeTextFile(writePath, dsn + "\n");
4845
4648
  console.log("");
4846
4649
  console.log(chalk.green("\u2714") + ` DSN written to ${opts.write}`);
4847
- const root = findGitRoot(path19.dirname(writePath));
4848
- const rel2 = root ? path19.relative(root, writePath).split(path19.sep).join("/") : "";
4650
+ const root = findGitRoot(path17.dirname(writePath));
4651
+ const rel2 = root ? path17.relative(root, writePath).split(path17.sep).join("/") : "";
4849
4652
  if (root && rel2 && !rel2.startsWith("..")) {
4850
- const res = ensureGitignored(rel2, path19.dirname(writePath));
4653
+ const res = ensureGitignored(rel2, path17.dirname(writePath));
4851
4654
  if (res.status === "created") {
4852
4655
  console.log(chalk.green("\u2714") + ` Created .gitignore and ignored ${rel2}`);
4853
4656
  } else if (res.status === "added") {
@@ -4865,30 +4668,30 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
4865
4668
  var STORE_DIRNAME = ".vibgrate";
4866
4669
  var STORE_FILENAME = "credentials.json";
4867
4670
  function homeCredentialsPath() {
4868
- return path19.join(os.homedir(), STORE_DIRNAME, STORE_FILENAME);
4671
+ return path17.join(os.homedir(), STORE_DIRNAME, STORE_FILENAME);
4869
4672
  }
4870
4673
  function projectCredentialsPath(cwd = process.cwd()) {
4871
4674
  const root = findGitRoot(cwd) ?? cwd;
4872
- return path19.join(root, STORE_DIRNAME, STORE_FILENAME);
4675
+ return path17.join(root, STORE_DIRNAME, STORE_FILENAME);
4873
4676
  }
4874
4677
  function credentialsPath(opts = {}) {
4875
4678
  const override = process.env.VIBGRATE_CREDENTIALS?.trim();
4876
- if (override) return path19.resolve(override);
4679
+ if (override) return path17.resolve(override);
4877
4680
  const local = projectCredentialsPath(opts.cwd);
4878
4681
  if (opts.local) return local;
4879
- if (fs19.existsSync(local)) return local;
4682
+ if (fs17.existsSync(local)) return local;
4880
4683
  return homeCredentialsPath();
4881
4684
  }
4882
4685
  function gitignoreEntryForCredentials(repoRoot, credsFile = credentialsPath()) {
4883
- const rel2 = path19.relative(repoRoot, credsFile);
4884
- if (rel2 && !rel2.startsWith("..") && !path19.isAbsolute(rel2)) {
4885
- return rel2.split(path19.sep).join("/");
4686
+ const rel2 = path17.relative(repoRoot, credsFile);
4687
+ if (rel2 && !rel2.startsWith("..") && !path17.isAbsolute(rel2)) {
4688
+ return rel2.split(path17.sep).join("/");
4886
4689
  }
4887
4690
  return `${STORE_DIRNAME}/${STORE_FILENAME}`;
4888
4691
  }
4889
4692
  function readStoredCredentials(opts = {}) {
4890
4693
  try {
4891
- const raw = fs19.readFileSync(credentialsPath(opts), "utf8");
4694
+ const raw = fs17.readFileSync(credentialsPath(opts), "utf8");
4892
4695
  const parsed = JSON.parse(raw);
4893
4696
  return parsed && typeof parsed.dsn === "string" && parsed.dsn ? parsed : null;
4894
4697
  } catch {
@@ -4897,16 +4700,16 @@ function readStoredCredentials(opts = {}) {
4897
4700
  }
4898
4701
  function writeStoredCredentials(creds, opts = {}) {
4899
4702
  const file = credentialsPath(opts);
4900
- fs19.mkdirSync(path19.dirname(file), { recursive: true });
4901
- fs19.writeFileSync(file, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf8", mode: 384 });
4703
+ fs17.mkdirSync(path17.dirname(file), { recursive: true });
4704
+ fs17.writeFileSync(file, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf8", mode: 384 });
4902
4705
  try {
4903
- fs19.chmodSync(file, 384);
4706
+ fs17.chmodSync(file, 384);
4904
4707
  } catch {
4905
4708
  }
4906
4709
  }
4907
4710
  function clearStoredCredentials(opts = {}) {
4908
4711
  try {
4909
- fs19.rmSync(credentialsPath(opts));
4712
+ fs17.rmSync(credentialsPath(opts));
4910
4713
  return true;
4911
4714
  } catch {
4912
4715
  return false;
@@ -5277,7 +5080,7 @@ var pushCommand = new Command("push").description("Push scan results to Vibgrate
5277
5080
  if (opts.strict) process.exit(1);
5278
5081
  return;
5279
5082
  }
5280
- const filePath = path19.resolve(opts.file);
5083
+ const filePath = path17.resolve(opts.file);
5281
5084
  if (!await pathExists(filePath)) {
5282
5085
  console.error(chalk.red(`Scan artifact not found: ${filePath}`));
5283
5086
  console.error(chalk.dim('Run "vibgrate scan" first.'));
@@ -5340,8 +5143,8 @@ var pushCommand = new Command("push").description("Push scan results to Vibgrate
5340
5143
  });
5341
5144
  function readScanArtifact(root) {
5342
5145
  try {
5343
- const file = path19.join(root, ".vibgrate", "scan_result.json");
5344
- return JSON.parse(fs19.readFileSync(file, "utf8"));
5146
+ const file = path17.join(root, ".vibgrate", "scan_result.json");
5147
+ return JSON.parse(fs17.readFileSync(file, "utf8"));
5345
5148
  } catch {
5346
5149
  return null;
5347
5150
  }
@@ -5731,7 +5534,7 @@ var CACHE_VERSION2 = "vg-hosted-docs/1";
5731
5534
  var HOSTED_DOCS_TTL_MS = 24 * 60 * 60 * 1e3;
5732
5535
  var MAX_ENTRIES = 64;
5733
5536
  function cachePath2(root) {
5734
- return path19.join(cacheDir(root), "hosted-docs.json");
5537
+ return path17.join(cacheDir(root), "hosted-docs.json");
5735
5538
  }
5736
5539
  function hostedDocsCacheKey(req, opts = {}) {
5737
5540
  const material = JSON.stringify([
@@ -5747,7 +5550,7 @@ function hostedDocsCacheKey(req, opts = {}) {
5747
5550
  }
5748
5551
  function loadFile(root) {
5749
5552
  try {
5750
- const parsed = JSON.parse(fs19.readFileSync(cachePath2(root), "utf8"));
5553
+ const parsed = JSON.parse(fs17.readFileSync(cachePath2(root), "utf8"));
5751
5554
  if (parsed && parsed.version === CACHE_VERSION2 && parsed.entries && typeof parsed.entries === "object") return parsed;
5752
5555
  } catch {
5753
5556
  }
@@ -5761,8 +5564,8 @@ function saveFile(root, file) {
5761
5564
  delete file.entries[k];
5762
5565
  }
5763
5566
  }
5764
- fs19.mkdirSync(cacheDir(root), { recursive: true });
5765
- fs19.writeFileSync(cachePath2(root), JSON.stringify(file));
5567
+ fs17.mkdirSync(cacheDir(root), { recursive: true });
5568
+ fs17.writeFileSync(cachePath2(root), JSON.stringify(file));
5766
5569
  } catch {
5767
5570
  }
5768
5571
  }
@@ -5881,7 +5684,7 @@ function measure(root, files) {
5881
5684
  for (const rel2 of files) {
5882
5685
  let size;
5883
5686
  try {
5884
- size = fs19.statSync(path19.join(root, rel2)).size;
5687
+ size = fs17.statSync(path17.join(root, rel2)).size;
5885
5688
  } catch {
5886
5689
  continue;
5887
5690
  }
@@ -5904,9 +5707,9 @@ function matchLines(text, needleLower) {
5904
5707
  }
5905
5708
  function readCandidate(abs, knownSize) {
5906
5709
  try {
5907
- const size = knownSize ?? fs19.statSync(abs).size;
5710
+ const size = knownSize ?? fs17.statSync(abs).size;
5908
5711
  if (size > MAX_FILE_BYTES) return null;
5909
- const text = fs19.readFileSync(abs, "utf8");
5712
+ const text = fs17.readFileSync(abs, "utf8");
5910
5713
  return text.includes(NUL) ? null : text;
5911
5714
  } catch {
5912
5715
  return null;
@@ -5917,7 +5720,7 @@ function scanInline(root, files, needleLower, cap, stopAtCap, sizes) {
5917
5720
  let total = 0;
5918
5721
  let truncated = false;
5919
5722
  for (const rel2 of files) {
5920
- const text = readCandidate(path19.join(root, rel2), sizes?.get(rel2));
5723
+ const text = readCandidate(path17.join(root, rel2), sizes?.get(rel2));
5921
5724
  if (text === null) continue;
5922
5725
  for (const h of matchLines(text, needleLower)) {
5923
5726
  total++;
@@ -6096,13 +5899,13 @@ function walkCandidates(root) {
6096
5899
  const dir = stack.pop();
6097
5900
  let entries;
6098
5901
  try {
6099
- entries = fs19.readdirSync(dir, { withFileTypes: true });
5902
+ entries = fs17.readdirSync(dir, { withFileTypes: true });
6100
5903
  } catch {
6101
5904
  continue;
6102
5905
  }
6103
5906
  for (const e of entries) {
6104
5907
  if (e.name.startsWith(".") || IGNORE_DIRS.has(e.name)) continue;
6105
- const abs = path19.join(dir, e.name);
5908
+ const abs = path17.join(dir, e.name);
6106
5909
  if (e.isDirectory()) {
6107
5910
  stack.push(abs);
6108
5911
  continue;
@@ -6111,7 +5914,7 @@ function walkCandidates(root) {
6111
5914
  truncated = true;
6112
5915
  continue;
6113
5916
  }
6114
- files.push(path19.relative(root, abs));
5917
+ files.push(path17.relative(root, abs));
6115
5918
  }
6116
5919
  }
6117
5920
  files.sort();
@@ -6827,211 +6630,19 @@ function navigationToolsetConfig(serverName = "vibgrate") {
6827
6630
  configs: Object.fromEntries(HOT_TOOLS.map((n) => [n, { defer_loading: false }]))
6828
6631
  };
6829
6632
  }
6830
- var PROBE_INTERVAL_MS = 2e3;
6831
- var MAX_PROBE_INTERVAL_MS = 3e4;
6832
- var PROBE_DUTY_FACTOR = 20;
6833
- var REFRESH_BUDGET_MS = 5e3;
6834
- var FAILURE_COOLDOWN_MS = 6e4;
6835
- var GraphSource = class {
6836
- constructor(graphPath, refresh = false, tuning = {}) {
6837
- this.graphPath = graphPath;
6838
- this.refresh = refresh;
6839
- this.tuning = tuning;
6840
- this.root = path19.dirname(path19.dirname(graphPath));
6841
- }
6842
- graphPath;
6843
- refresh;
6844
- tuning;
6845
- cachedMtimeMs = -1;
6846
- cached = null;
6847
- root;
6848
- lastProbeAt = 0;
6849
- failedUntil = 0;
6850
- inflight = null;
6851
- /** Self-tuned: grows with measured probe cost so huge repos aren't penalized. */
6852
- probeIntervalMs = PROBE_INTERVAL_MS;
6853
- /** Current graph: auto-refreshed if the tree drifted, reloaded if the file changed. */
6854
- async get() {
6855
- if (this.refresh) await this.maybeRefresh();
6856
- const stat = fs19.statSync(this.graphPath);
6857
- if (stat.mtimeMs !== this.cachedMtimeMs || !this.cached) {
6858
- this.cached = parseGraph(fs19.readFileSync(this.graphPath, "utf8"));
6859
- this.cachedMtimeMs = stat.mtimeMs;
6860
- }
6861
- return this.cached;
6862
- }
6863
- /**
6864
- * Debounced, single-flight refresh. Never throws — a refresh problem must
6865
- * degrade to "answer from the current map", not break the tool call.
6866
- */
6867
- async maybeRefresh() {
6868
- const now = Date.now();
6869
- if (!this.inflight) {
6870
- const interval = this.tuning.probeIntervalMs ?? this.probeIntervalMs;
6871
- if (now < this.failedUntil || now - this.lastProbeAt < interval) return;
6872
- this.lastProbeAt = now;
6873
- this.inflight = refreshIfStale(this.root).then((r) => {
6874
- if (r.status === "error") this.failedUntil = Date.now() + FAILURE_COOLDOWN_MS;
6875
- if (r.status === "fresh" || r.status === "no-snapshot" || r.status === "locked") {
6876
- const cost = Date.now() - now;
6877
- this.probeIntervalMs = Math.min(
6878
- MAX_PROBE_INTERVAL_MS,
6879
- Math.max(PROBE_INTERVAL_MS, cost * PROBE_DUTY_FACTOR)
6880
- );
6881
- }
6882
- }).catch(() => {
6883
- this.failedUntil = Date.now() + FAILURE_COOLDOWN_MS;
6884
- }).finally(() => {
6885
- this.inflight = null;
6886
- });
6887
- }
6888
- await Promise.race([this.inflight, sleep(this.tuning.refreshBudgetMs ?? REFRESH_BUDGET_MS)]);
6889
- }
6890
- };
6891
- function createServer(source, opts = {}) {
6892
- const { savings = false, shareStats = false, local = false, dedup = false, stats } = opts;
6893
- const record = savings || shareStats;
6894
- const root = path19.dirname(path19.dirname(source.graphPath));
6895
- const seen = /* @__PURE__ */ new Set();
6896
- const server = new Server(
6897
- { name: "vg", version: VERSION },
6898
- {
6899
- capabilities: { tools: {} },
6900
- // Routing guidance once at the server level (hosts that surface
6901
- // `instructions` get it at zero per-step schema cost): the flashlight
6902
- // vs the map.
6903
- instructions: 'vg is a code map. Use search_symbols to find a known name or literal string fast \u2014 a multi-word/quoted phrase runs a complete literal sweep and reports totalTextMatches, so reach for it instead of grep even for plain-string "find every occurrence" lookups. Use orient/query_graph for meaning: symptoms, relationships, and what-breaks-if. Responses are concise by default; pass response_format:"detailed" only when a node proves load-bearing. Navigate as little as possible: one good search/query usually locates the code. As soon as you have the file and line, read that file and make the edit \u2014 do not call further graph tools unless the edit fails or the match was wrong. For how-do-I-use-this-library questions call resolve_library once, then library_docs with the returned targetId and a focused query: the docs are official and matched to the version THIS project has installed (drift-annotated) \u2014 prefer them over web search or training-data recall when they conflict. Skip them for language built-ins or APIs already shown in context. If two library_docs calls have not surfaced the section you need, read the package source under node_modules instead of searching again.'
6904
- }
6905
- );
6906
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
6907
- tools: TOOLS.map((t) => ({
6908
- name: t.name,
6909
- description: t.description,
6910
- inputSchema: t.inputSchema,
6911
- annotations: { readOnlyHint: true, openWorldHint: false }
6912
- }))
6913
- }));
6914
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
6915
- const tool = TOOLS.find((t) => t.name === request.params.name);
6916
- if (!tool) {
6917
- return errorResult(`unknown tool "${request.params.name}"`);
6918
- }
6919
- let graph;
6920
- try {
6921
- graph = await source.get();
6922
- } catch {
6923
- return errorResult(
6924
- "no code map found. Run `vg` in the project to build .vibgrate/graph.json, then retry."
6925
- );
6926
- }
6927
- const startedAt = Date.now();
6928
- try {
6929
- const args = request.params.arguments ?? {};
6930
- const result = await tool.handler(graph, args, { root, local, dedup, seen });
6931
- const ms = Date.now() - startedAt;
6932
- stats?.record({ ...measureCall(tool.name, result), client: sanitizeClient(detectClient(server)), ms });
6933
- if (record) recordUsage(root, tool.name, result, detectClient(server), ms);
6934
- return renderToolResult(result);
6935
- } catch (err) {
6936
- stats?.record({
6937
- tool: tool.name,
6938
- client: sanitizeClient(detectClient(server)),
6939
- outcome: "miss",
6940
- ms: Date.now() - startedAt,
6941
- vgTokens: 0,
6942
- baselineTokens: 0
6943
- });
6944
- return errorResult(`tool "${tool.name}" failed: ${err.message}`);
6945
- }
6946
- });
6947
- return server;
6948
- }
6949
- async function serveStdio(graphPath, opts = {}) {
6950
- const source = new GraphSource(graphPath, opts.refresh !== false);
6951
- const server = createServer(source, opts);
6952
- warmEmbedderInBackground(opts.local);
6953
- await server.connect(new StdioServerTransport());
6954
- }
6955
- function sleep(ms) {
6956
- return new Promise((resolve12) => {
6957
- const timer = setTimeout(resolve12, ms);
6958
- timer.unref?.();
6959
- });
6960
- }
6961
- function recordUsage(root, tool, result, client, ms) {
6962
- recordSaving(
6963
- root,
6964
- { ...measureCall(tool, result), source: "mcp", client: sanitizeClient(client), ...ms !== void 0 ? { ms } : {} },
6965
- Date.now()
6966
- );
6967
- }
6968
- function measureCall(tool, result) {
6969
- const outcome = classifyOutcome(result);
6970
- let vgTokens = 0;
6971
- let baselineTokens = 0;
6972
- if (SAVINGS_TOOLS.has(tool) && result && typeof result === "object") {
6973
- vgTokens = countTokens(renderedText(renderToolResult(result)));
6974
- baselineTokens = referencedFiles(result).size * PER_FILE_TOKENS;
6975
- }
6976
- return { tool, outcome, vgTokens, baselineTokens };
6977
- }
6978
- function detectClient(server) {
6979
- try {
6980
- const info = server.getClientVersion?.();
6981
- return typeof info?.name === "string" ? info.name : void 0;
6982
- } catch {
6983
- return void 0;
6984
- }
6985
- }
6986
- function classifyOutcome(result) {
6987
- if (Array.isArray(result)) return result.length === 0 ? "miss" : "complete";
6988
- if (!result || typeof result !== "object") return "miss";
6989
- const r = result;
6990
- if (typeof r.error === "string" && r.error) return "miss";
6991
- if (r.connected === false) return "miss";
6992
- if (Array.isArray(r.matches) && r.matches.length === 0) return "miss";
6993
- return isPartial(r) ? "partial" : "complete";
6994
- }
6995
- function isPartial(r) {
6996
- if (r.moreAvailable === true) return true;
6997
- if (r._truncated && typeof r._truncated === "object") return true;
6998
- for (const [key, value] of Object.entries(r)) {
6999
- if (typeof value !== "number" || !key.endsWith("Total")) continue;
7000
- const base = key.slice(0, -"Total".length);
7001
- const shown = Array.isArray(r[base]) ? r[base].length : 0;
7002
- if (value > shown) return true;
7003
- }
7004
- return false;
7005
- }
7006
- function renderedText(rendered) {
7007
- const block = rendered.content?.find((b) => b.type === "text");
7008
- return block && "text" in block && typeof block.text === "string" ? block.text : "";
7009
- }
7010
- function referencedFiles(result) {
7011
- const r = result;
7012
- const files = /* @__PURE__ */ new Set();
7013
- if (typeof r.file === "string" && r.file) files.add(r.file);
7014
- for (const m of asArray(r.matches)) {
7015
- const f = m.file;
7016
- if (typeof f === "string" && f) files.add(f);
7017
- }
7018
- for (const name of [...asArray(r.calls), ...asArray(r.calledBy)]) {
7019
- if (typeof name !== "string") continue;
7020
- const i = name.indexOf(":");
7021
- if (i > 0) files.add(name.slice(0, i));
7022
- }
7023
- return files;
7024
- }
7025
- function asArray(v) {
7026
- return Array.isArray(v) ? v : [];
7027
- }
7028
- function errorResult(message) {
7029
- return { content: [{ type: "text", text: message }], isError: true };
7030
- }
7031
6633
 
7032
6634
  // src/install/content.ts
7033
6635
  var NUDGE_BEGIN = "<!-- vg:begin -->";
7034
6636
  var NUDGE_END = "<!-- vg:end -->";
6637
+ var INSTALL_CONTENT_VERSION = 2;
6638
+ var VERSION_MARKER_RE = /<!--\s*vg:v(\d+)\b[^>]*-->/;
6639
+ function versionMarker() {
6640
+ return `<!-- vg:v${INSTALL_CONTENT_VERSION} \xB7 managed by \`vg install\` \u2014 auto-refreshed when these instructions evolve; remove this line to opt out -->`;
6641
+ }
6642
+ function installedContentVersion(text) {
6643
+ const m = VERSION_MARKER_RE.exec(text);
6644
+ return m ? Number(m[1]) : null;
6645
+ }
7035
6646
  function clientFlag(client) {
7036
6647
  return client ? `--client=${client}` : "--client=<your-ai>";
7037
6648
  }
@@ -7042,26 +6653,31 @@ name: vg
7042
6653
  description: Query the local code graph (vg) for structure, impact, and navigation instead of grepping/reading many files.
7043
6654
  ---
7044
6655
 
6656
+ ${versionMarker()}
6657
+
7045
6658
  # vg \u2014 the code map
7046
6659
 
7047
6660
  This repo has a deterministic code graph built by \`vg\`. Prefer it over reading or
7048
6661
  grepping many files \u2014 it is smaller, more relevant context, and free.
7049
6662
 
7050
- ## Prefer the MCP tools
6663
+ ## Use the MCP tools \u2014 not the CLI
7051
6664
 
7052
- If the \`vg\` MCP server is registered (it is after \`vg install\`), call its
7053
- read-only tools directly \u2014 they are the **fastest** path. The server keeps the
7054
- map parsed, the relation index warm, and the embedding model loaded across calls,
7055
- so each query is cheaper than spawning the CLI fresh. Use:
6665
+ When the \`vg\` MCP server is registered (it is after \`vg install\`), **always call
6666
+ its read-only tools instead of shelling out to the CLI.** The server keeps the
6667
+ map parsed, the relation index warm, and the embedding model loaded across
6668
+ calls, so an MCP call answers in milliseconds \u2014 while every CLI invocation pays
6669
+ Node startup plus a fresh map parse, hundreds of times more. Tools:
7056
6670
  \`query_graph\`, \`get_node\`, \`impact_of\`, \`find_path\`, \`list_hubs\`, \`list_areas\`,
7057
6671
  \`get_graph_summary\`, \`search_symbols\`. They are side-effect-free and
7058
6672
  auto-approvable, and the server records which client is calling automatically.
6673
+ Reach for the CLI only when the MCP server is genuinely unavailable \u2014 never as
6674
+ the first resort.
7059
6675
 
7060
- ## If you use the \`vg\` CLI instead
6676
+ ## CLI fallback \u2014 only when the MCP server is unavailable
7061
6677
 
7062
- When the MCP server isn't available, use the CLI \u2014 and **always pass \`${cf}\`**
7063
- so your calls are counted (that's how the CLI-vs-MCP split is measured and the
7064
- tools improved):
6678
+ If (and only if) no \`vg\` MCP server is available, use the CLI \u2014 and **always
6679
+ pass \`${cf}\`** so your calls are counted (that's how the CLI-vs-MCP split is
6680
+ measured and the tools improved):
7065
6681
 
7066
6682
  - **Understand code:** \`vg "<question>" ${cf}\` \u2014 a budget-bounded, fact-annotated context block.
7067
6683
  - **Find a symbol:** \`vg show <name> ${cf}\` \u2014 what it is, what it calls, what calls it.
@@ -7104,22 +6720,28 @@ function nudgeMarkdown(smallRepo, client) {
7104
6720
  const cf = clientFlag(client);
7105
6721
  if (smallRepo) {
7106
6722
  return `${NUDGE_BEGIN}
6723
+ ${versionMarker()}
7107
6724
  ## Code navigation (vg)
7108
6725
 
7109
6726
  This repo is small enough that searching files directly is fine. Still, before a
7110
- change, \`vg impact <name> ${cf}\` shows what it breaks, and \`vg "<question>" ${cf}\`
7111
- gives a compact, cited context block when you want one. Prefer the \`vg\` MCP tools
7112
- when the server is registered; when you use the CLI, pass \`${cf}\` so the call is
7113
- counted.
6727
+ change, the \`vg\` graph shows what breaks: use the \`vg\` MCP tools when the server
6728
+ is registered (\`impact_of\`, \`query_graph\` \u2014 the warm server answers far faster
6729
+ than spawning the CLI). Only without the server, fall back to the CLI and pass
6730
+ \`${cf}\` so the call is counted: \`vg impact <name> ${cf}\`,
6731
+ \`vg "<question>" ${cf}\`.
7114
6732
  ${NUDGE_END}`;
7115
6733
  }
7116
6734
  return `${NUDGE_BEGIN}
7117
- ## Code navigation (vg) \u2014 prefer the map
6735
+ ${versionMarker()}
6736
+ ## Code navigation (vg) \u2014 use the MCP tools first
7118
6737
 
7119
6738
  A deterministic code graph is available. It is usually the faster, cheaper path
7120
- than reading or grepping many files. Prefer the \`vg\` MCP tools when the server is
7121
- registered (fastest \u2014 the map and model stay warm across calls). When you use the
7122
- CLI instead, pass \`${cf}\` so the call is counted:
6739
+ than reading or grepping many files. **When the \`vg\` MCP server is registered,
6740
+ always use its tools** (\`query_graph\`, \`get_node\`, \`impact_of\`,
6741
+ \`search_symbols\`, \u2026) \u2014 the map, indexes, and embedding model stay warm across
6742
+ calls, so they answer in milliseconds, while every CLI invocation pays Node
6743
+ startup plus a fresh map parse. Fall back to the CLI only when the server is
6744
+ unavailable, and then pass \`${cf}\` so the call is counted:
7123
6745
 
7124
6746
  - \`vg "<question>" ${cf}\` \u2014 a compact, fact-annotated context block for a question.
7125
6747
  - \`vg show <name> ${cf}\` \u2014 what a symbol is, its callers and callees.
@@ -7215,12 +6837,12 @@ function detectAssistants(root, opts = {}) {
7215
6837
  const which = opts.which ?? whichOnPath;
7216
6838
  const found = [];
7217
6839
  for (const assistant of ASSISTANTS) {
7218
- const repoHit = (assistant.markers ?? []).find((m) => fs19.existsSync(path19.join(root, m)));
6840
+ const repoHit = (assistant.markers ?? []).find((m) => fs17.existsSync(path17.join(root, m)));
7219
6841
  if (repoHit) {
7220
6842
  found.push({ assistant, via: "repo", marker: repoHit });
7221
6843
  continue;
7222
6844
  }
7223
- const homeHit = (assistant.homeMarkers ?? []).find((m) => fs19.existsSync(path19.join(home, m)));
6845
+ const homeHit = (assistant.homeMarkers ?? []).find((m) => fs17.existsSync(path17.join(home, m)));
7224
6846
  if (homeHit) {
7225
6847
  found.push({ assistant, via: "home", marker: homeHit });
7226
6848
  continue;
@@ -7252,19 +6874,19 @@ function installAssistant(a, opts) {
7252
6874
  const skipped = [];
7253
6875
  let note;
7254
6876
  if (a.skill) {
7255
- writeFileEnsured(path19.join(opts.root, a.skill), skillMarkdown(a.id));
6877
+ writeFileEnsured(path17.join(opts.root, a.skill), skillMarkdown(a.id));
7256
6878
  wrote.push(a.skill);
7257
6879
  }
7258
6880
  if (a.mcp) {
7259
6881
  const launch = opts.launch ?? detectServeLaunch();
7260
- upsertMcp(path19.join(opts.root, a.mcp.file), a.mcp, launch);
6882
+ upsertMcp(path17.join(opts.root, a.mcp.file), a.mcp, launch);
7261
6883
  wrote.push(a.mcp.file);
7262
6884
  note = launch.note;
7263
6885
  } else {
7264
6886
  skipped.push("mcp (host-specific setup)");
7265
6887
  }
7266
6888
  if (a.nudge && opts.hook !== false) {
7267
- writeNudge(path19.join(opts.root, a.nudge.file), a.nudge, opts.smallRepo, a.id);
6889
+ writeNudge(path17.join(opts.root, a.nudge.file), a.nudge, opts.smallRepo, a.id);
7268
6890
  wrote.push(a.nudge.file);
7269
6891
  } else if (a.nudge) {
7270
6892
  skipped.push("nudge (--no-hook)");
@@ -7274,40 +6896,40 @@ function installAssistant(a, opts) {
7274
6896
  function uninstallAssistant(a, root, purge) {
7275
6897
  const removed = [];
7276
6898
  if (a.mcp) {
7277
- const file = path19.join(root, a.mcp.file);
6899
+ const file = path17.join(root, a.mcp.file);
7278
6900
  if (removeMcp(file, a.mcp)) removed.push(a.mcp.file);
7279
6901
  }
7280
6902
  if (a.nudge) {
7281
- const file = path19.join(root, a.nudge.file);
6903
+ const file = path17.join(root, a.nudge.file);
7282
6904
  if (a.nudge.kind === "block") {
7283
6905
  if (removeBlock(file)) removed.push(a.nudge.file);
7284
- } else if (fs19.existsSync(file)) {
7285
- fs19.rmSync(file);
6906
+ } else if (fs17.existsSync(file)) {
6907
+ fs17.rmSync(file);
7286
6908
  removed.push(a.nudge.file);
7287
6909
  }
7288
6910
  }
7289
6911
  if (purge && a.skill) {
7290
- const file = path19.join(root, a.skill);
7291
- if (fs19.existsSync(file)) {
7292
- fs19.rmSync(file);
6912
+ const file = path17.join(root, a.skill);
6913
+ if (fs17.existsSync(file)) {
6914
+ fs17.rmSync(file);
7293
6915
  removed.push(a.skill);
7294
6916
  }
7295
6917
  }
7296
6918
  return removed;
7297
6919
  }
7298
6920
  function writeFileEnsured(file, content) {
7299
- fs19.mkdirSync(path19.dirname(file), { recursive: true });
7300
- fs19.writeFileSync(file, content);
6921
+ fs17.mkdirSync(path17.dirname(file), { recursive: true });
6922
+ fs17.writeFileSync(file, content);
7301
6923
  }
7302
6924
  function writeNavigationConfig(root) {
7303
- const rel2 = path19.join(".vibgrate", "mcp-navigation.json");
6925
+ const rel2 = path17.join(".vibgrate", "mcp-navigation.json");
7304
6926
  const doc = {
7305
6927
  _readme: "Deferred-loading config for agents embedding `vg serve` via the Claude API (defer_loading + tool-search). The hot navigation core stays in context; the rest load on demand \u2014 ~350-450 schema tokens/step vs ~1,881 for the full set. Hosts without defer_loading ignore this and serve the whole (already optimized) tool set. See docs/graph/VG-NAVIGATION-PROFILE.md.",
7306
6928
  hot_core: [...HOT_TOOLS],
7307
6929
  deferred: deferredToolNames(),
7308
6930
  toolset: navigationToolsetConfig()
7309
6931
  };
7310
- writeFileEnsured(path19.join(root, rel2), `${JSON.stringify(doc, null, 2)}
6932
+ writeFileEnsured(path17.join(root, rel2), `${JSON.stringify(doc, null, 2)}
7311
6933
  `);
7312
6934
  return rel2;
7313
6935
  }
@@ -7322,7 +6944,7 @@ function upsertMcp(file, target, launch) {
7322
6944
  `);
7323
6945
  }
7324
6946
  function removeMcp(file, target) {
7325
- if (!fs19.existsSync(file)) return false;
6947
+ if (!fs17.existsSync(file)) return false;
7326
6948
  const config = readJson(file);
7327
6949
  const bag = config[target.key];
7328
6950
  if (!bag || !(bag.vg !== void 0)) return false;
@@ -7331,6 +6953,57 @@ function removeMcp(file, target) {
7331
6953
  `);
7332
6954
  return true;
7333
6955
  }
6956
+ var SMALL_REPO_FILES = 150;
6957
+ function refreshInstalledInstructions(root, smallRepo) {
6958
+ const out = [];
6959
+ const staleVersion = (text, legacySignature) => {
6960
+ const v = installedContentVersion(text);
6961
+ if (v !== null) return v < INSTALL_CONTENT_VERSION ? v : null;
6962
+ return text.includes(legacySignature) ? 0 : null;
6963
+ };
6964
+ for (const a of ASSISTANTS) {
6965
+ if (a.skill) {
6966
+ const file = path17.join(root, a.skill);
6967
+ if (fs17.existsSync(file)) {
6968
+ const text = readTextSafe(file);
6969
+ const from = text === null ? null : staleVersion(text, "# vg \u2014 the code map");
6970
+ if (from !== null) {
6971
+ writeFileEnsured(file, skillMarkdown(a.id));
6972
+ out.push({ file: a.skill, from, to: INSTALL_CONTENT_VERSION });
6973
+ }
6974
+ }
6975
+ }
6976
+ if (a.nudge) {
6977
+ const file = path17.join(root, a.nudge.file);
6978
+ if (!fs17.existsSync(file)) continue;
6979
+ const text = readTextSafe(file);
6980
+ if (text === null) continue;
6981
+ if (a.nudge.kind === "block") {
6982
+ const block = new RegExp(`${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}`).exec(text);
6983
+ if (!block) continue;
6984
+ const from = staleVersion(block[0], "## Code navigation (vg)");
6985
+ if (from !== null) {
6986
+ writeNudge(file, a.nudge, smallRepo, a.id);
6987
+ out.push({ file: a.nudge.file, from, to: INSTALL_CONTENT_VERSION });
6988
+ }
6989
+ } else {
6990
+ const from = staleVersion(text, "## Code navigation (vg)");
6991
+ if (from !== null) {
6992
+ writeNudge(file, a.nudge, smallRepo, a.id);
6993
+ out.push({ file: a.nudge.file, from, to: INSTALL_CONTENT_VERSION });
6994
+ }
6995
+ }
6996
+ }
6997
+ }
6998
+ return out;
6999
+ }
7000
+ function readTextSafe(file) {
7001
+ try {
7002
+ return fs17.readFileSync(file, "utf8");
7003
+ } catch {
7004
+ return null;
7005
+ }
7006
+ }
7334
7007
  function writeNudge(file, target, smallRepo, client) {
7335
7008
  if (target.kind === "file") {
7336
7009
  const body = file.endsWith(".mdc") ? `---
@@ -7345,8 +7018,8 @@ ${stripMarkers(nudgeMarkdown(smallRepo, client))}` : stripMarkers(nudgeMarkdown(
7345
7018
  upsertBlock(file, nudgeMarkdown(smallRepo, client));
7346
7019
  }
7347
7020
  function upsertBlock(file, block) {
7348
- fs19.mkdirSync(path19.dirname(file), { recursive: true });
7349
- let existing = fs19.existsSync(file) ? fs19.readFileSync(file, "utf8") : "";
7021
+ fs17.mkdirSync(path17.dirname(file), { recursive: true });
7022
+ let existing = fs17.existsSync(file) ? fs17.readFileSync(file, "utf8") : "";
7350
7023
  const re = new RegExp(`${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}`);
7351
7024
  if (re.test(existing)) existing = existing.replace(re, block);
7352
7025
  else existing = existing.length ? `${existing.replace(/\s*$/, "")}
@@ -7354,20 +7027,20 @@ function upsertBlock(file, block) {
7354
7027
  ${block}
7355
7028
  ` : `${block}
7356
7029
  `;
7357
- fs19.writeFileSync(file, existing);
7030
+ fs17.writeFileSync(file, existing);
7358
7031
  }
7359
7032
  function removeBlock(file) {
7360
- if (!fs19.existsSync(file)) return false;
7361
- const existing = fs19.readFileSync(file, "utf8");
7033
+ if (!fs17.existsSync(file)) return false;
7034
+ const existing = fs17.readFileSync(file, "utf8");
7362
7035
  const re = new RegExp(`\\n*${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}\\n*`);
7363
7036
  if (!re.test(existing)) return false;
7364
- fs19.writeFileSync(file, existing.replace(re, "\n"));
7037
+ fs17.writeFileSync(file, existing.replace(re, "\n"));
7365
7038
  return true;
7366
7039
  }
7367
7040
  function readJson(file) {
7368
- if (!fs19.existsSync(file)) return {};
7041
+ if (!fs17.existsSync(file)) return {};
7369
7042
  try {
7370
- const parsed = JSON.parse(fs19.readFileSync(file, "utf8"));
7043
+ const parsed = JSON.parse(fs17.readFileSync(file, "utf8"));
7371
7044
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
7372
7045
  return parsed;
7373
7046
  }
@@ -7385,6 +7058,425 @@ function stripMarkers(s) {
7385
7058
  function escapeRe(s) {
7386
7059
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7387
7060
  }
7061
+ var REFRESH_LOCK_STALE_MS = 10 * 60 * 1e3;
7062
+ function refreshLockPath(root) {
7063
+ return path17.join(cacheDir(root), "refresh.lock");
7064
+ }
7065
+ async function refreshIfStale(root, opts = {}) {
7066
+ const probe = probeFreshness(root);
7067
+ if (!probe) return { status: "no-snapshot" };
7068
+ if (!hasDrift(probe.drift)) return { status: "fresh" };
7069
+ const lock = refreshLockPath(root);
7070
+ if (!acquireLock(lock, REFRESH_LOCK_STALE_MS)) return { status: "locked" };
7071
+ const start = process.hrtime.bigint();
7072
+ try {
7073
+ const { scope } = probe;
7074
+ const result = await buildGraph({
7075
+ root,
7076
+ only: scope.only,
7077
+ exclude: scope.exclude,
7078
+ paths: scope.paths,
7079
+ deep: scope.deep,
7080
+ noGround: scope.noGround,
7081
+ scip: scope.scip,
7082
+ noScip: scope.noScip,
7083
+ noTsc: scope.noTsc,
7084
+ cluster: scope.cluster,
7085
+ grammarsDir: scope.grammarsDir,
7086
+ inline: opts.inline,
7087
+ jobs: opts.jobs
7088
+ });
7089
+ const wrote = result.graph.provenance.corpusHash !== probe.corpusHash;
7090
+ if (wrote) {
7091
+ const dir = vibgrateDir(root);
7092
+ writeArtifacts(result.graph, {
7093
+ root,
7094
+ report: fs17.existsSync(path17.join(dir, "GRAPH_REPORT.md")),
7095
+ html: fs17.existsSync(path17.join(dir, "graph.html"))
7096
+ });
7097
+ }
7098
+ writeSnapshot(root, result.graph.provenance.corpusHash, result.fileStats, scope);
7099
+ const ms = Number((process.hrtime.bigint() - start) / 1000000n);
7100
+ return {
7101
+ status: "refreshed",
7102
+ drift: probe.drift,
7103
+ ms,
7104
+ reparsed: result.reparsed,
7105
+ totalFiles: result.totalFiles,
7106
+ wrote
7107
+ };
7108
+ } catch (err) {
7109
+ return { status: "error", message: err.message };
7110
+ } finally {
7111
+ releaseLock(lock);
7112
+ }
7113
+ }
7114
+ var LEDGER = "savings.jsonl";
7115
+ var PER_FILE_TOKENS = 400;
7116
+ var SAVINGS_TOOLS = /* @__PURE__ */ new Set(["query_graph", "get_node"]);
7117
+ var CLI_TOOL_ALIASES = {
7118
+ ask: "query_graph",
7119
+ show: "get_node",
7120
+ impact: "impact_of",
7121
+ path: "find_path",
7122
+ hubs: "list_hubs",
7123
+ areas: "list_areas",
7124
+ tree: "tree"
7125
+ };
7126
+ function sanitizeClient(name) {
7127
+ if (typeof name !== "string") return "unknown";
7128
+ const cleaned = name.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
7129
+ return cleaned || "unknown";
7130
+ }
7131
+ function recordCliCall(root, entry, now) {
7132
+ recordSaving(
7133
+ root,
7134
+ {
7135
+ tool: entry.tool,
7136
+ source: "cli",
7137
+ client: sanitizeClient(entry.client),
7138
+ outcome: entry.outcome,
7139
+ vgTokens: entry.vgTokens ?? 0,
7140
+ baselineTokens: (entry.baselineFiles ?? 0) * PER_FILE_TOKENS
7141
+ },
7142
+ now
7143
+ );
7144
+ }
7145
+ function savingsLedgerPath(root) {
7146
+ return path17.join(cacheDir(root), LEDGER);
7147
+ }
7148
+ function ledgerPath(root) {
7149
+ return savingsLedgerPath(root);
7150
+ }
7151
+ function savingsRecorded(root) {
7152
+ return fs17.existsSync(ledgerPath(root));
7153
+ }
7154
+ function recordSaving(root, entry, now) {
7155
+ try {
7156
+ fs17.mkdirSync(cacheDir(root), { recursive: true });
7157
+ const line = JSON.stringify({ ts: now, ...entry });
7158
+ fs17.appendFileSync(ledgerPath(root), line + "\n");
7159
+ } catch {
7160
+ }
7161
+ }
7162
+ var DEFAULT_RATE_PER_M = 3;
7163
+ var DEFAULT_RATE_LABEL = "input @ $3/1M";
7164
+ function readSavings(root, days, now, ratePerM = DEFAULT_RATE_PER_M) {
7165
+ const file = ledgerPath(root);
7166
+ const cutoff = now - days * 24 * 60 * 60 * 1e3;
7167
+ let queries = 0;
7168
+ let vgTokens = 0;
7169
+ let baselineTokens = 0;
7170
+ if (fs17.existsSync(file)) {
7171
+ for (const line of fs17.readFileSync(file, "utf8").split("\n")) {
7172
+ if (!line.trim()) continue;
7173
+ try {
7174
+ const e = JSON.parse(line);
7175
+ if (e.ts < cutoff) continue;
7176
+ if (!SAVINGS_TOOLS.has(e.tool)) continue;
7177
+ queries++;
7178
+ vgTokens += e.vgTokens;
7179
+ baselineTokens += e.baselineTokens;
7180
+ } catch {
7181
+ }
7182
+ }
7183
+ }
7184
+ const estCostVg = vgTokens / 1e6 * ratePerM;
7185
+ const estCostBaseline = baselineTokens / 1e6 * ratePerM;
7186
+ return {
7187
+ enabled: savingsRecorded(root),
7188
+ days,
7189
+ queries,
7190
+ vgTokens,
7191
+ baselineTokens,
7192
+ ratio: vgTokens > 0 ? Math.round(baselineTokens / vgTokens * 100) / 100 : 0,
7193
+ estCostVg: round22(estCostVg),
7194
+ estCostBaseline: round22(estCostBaseline),
7195
+ saved: round22(estCostBaseline - estCostVg),
7196
+ rateLabel: DEFAULT_RATE_LABEL
7197
+ };
7198
+ }
7199
+ function round22(x) {
7200
+ return Math.round(x * 100) / 100;
7201
+ }
7202
+ function toDimensionStats(byKey) {
7203
+ return [...byKey.entries()].map(([key, r]) => {
7204
+ const calls = r.complete + r.partial + r.miss;
7205
+ return {
7206
+ key,
7207
+ calls,
7208
+ complete: r.complete,
7209
+ partial: r.partial,
7210
+ miss: r.miss,
7211
+ successPct: calls ? Math.round((r.complete + r.partial) / calls * 100) : 0
7212
+ };
7213
+ }).sort((a, b) => b.calls - a.calls || a.key.localeCompare(b.key));
7214
+ }
7215
+ function readUsage(root, days, now) {
7216
+ const file = ledgerPath(root);
7217
+ const cutoff = now - days * 24 * 60 * 60 * 1e3;
7218
+ const byTool = /* @__PURE__ */ new Map();
7219
+ const bySource = /* @__PURE__ */ new Map();
7220
+ const byClient = /* @__PURE__ */ new Map();
7221
+ const msByTool = /* @__PURE__ */ new Map();
7222
+ const bump2 = (m, key, outcome) => {
7223
+ const row = m.get(key) ?? { complete: 0, partial: 0, miss: 0 };
7224
+ row[outcome]++;
7225
+ m.set(key, row);
7226
+ };
7227
+ if (fs17.existsSync(file)) {
7228
+ for (const line of fs17.readFileSync(file, "utf8").split("\n")) {
7229
+ if (!line.trim()) continue;
7230
+ try {
7231
+ const e = JSON.parse(line);
7232
+ if (e.ts < cutoff) continue;
7233
+ const outcome = e.outcome ?? "complete";
7234
+ bump2(byTool, e.tool, outcome);
7235
+ bump2(bySource, e.source ?? "mcp", outcome);
7236
+ bump2(byClient, e.client ?? "unknown", outcome);
7237
+ if (typeof e.ms === "number" && e.ms >= 0) {
7238
+ const timing = msByTool.get(e.tool) ?? { sum: 0, n: 0 };
7239
+ timing.sum += e.ms;
7240
+ timing.n++;
7241
+ msByTool.set(e.tool, timing);
7242
+ }
7243
+ } catch {
7244
+ }
7245
+ }
7246
+ }
7247
+ const commands = toDimensionStats(byTool).map((d) => {
7248
+ const timing = msByTool.get(d.key);
7249
+ return {
7250
+ tool: d.key,
7251
+ calls: d.calls,
7252
+ complete: d.complete,
7253
+ partial: d.partial,
7254
+ miss: d.miss,
7255
+ successPct: d.successPct,
7256
+ avgMs: timing && timing.n > 0 ? Math.round(timing.sum / timing.n) : null
7257
+ };
7258
+ });
7259
+ const totals = commands.reduce(
7260
+ (t, c) => ({
7261
+ calls: t.calls + c.calls,
7262
+ complete: t.complete + c.complete,
7263
+ partial: t.partial + c.partial,
7264
+ miss: t.miss + c.miss
7265
+ }),
7266
+ { calls: 0, complete: 0, partial: 0, miss: 0 }
7267
+ );
7268
+ const avgSuccessPct = commands.length ? Math.round(commands.reduce((s, c) => s + c.successPct, 0) / commands.length) : 0;
7269
+ return {
7270
+ enabled: savingsRecorded(root),
7271
+ days,
7272
+ commands,
7273
+ sources: toDimensionStats(bySource),
7274
+ clients: toDimensionStats(byClient),
7275
+ totals,
7276
+ avgSuccessPct
7277
+ };
7278
+ }
7279
+ var PROBE_INTERVAL_MS = 2e3;
7280
+ var MAX_PROBE_INTERVAL_MS = 3e4;
7281
+ var PROBE_DUTY_FACTOR = 20;
7282
+ var REFRESH_BUDGET_MS = 5e3;
7283
+ var FAILURE_COOLDOWN_MS = 6e4;
7284
+ var GraphSource = class {
7285
+ constructor(graphPath, refresh = false, tuning = {}) {
7286
+ this.graphPath = graphPath;
7287
+ this.refresh = refresh;
7288
+ this.tuning = tuning;
7289
+ this.root = path17.dirname(path17.dirname(graphPath));
7290
+ }
7291
+ graphPath;
7292
+ refresh;
7293
+ tuning;
7294
+ cachedMtimeMs = -1;
7295
+ cached = null;
7296
+ root;
7297
+ lastProbeAt = 0;
7298
+ failedUntil = 0;
7299
+ inflight = null;
7300
+ /** Self-tuned: grows with measured probe cost so huge repos aren't penalized. */
7301
+ probeIntervalMs = PROBE_INTERVAL_MS;
7302
+ /** Current graph: auto-refreshed if the tree drifted, reloaded if the file changed. */
7303
+ async get() {
7304
+ if (this.refresh) await this.maybeRefresh();
7305
+ const stat = fs17.statSync(this.graphPath);
7306
+ if (stat.mtimeMs !== this.cachedMtimeMs || !this.cached) {
7307
+ this.cached = parseGraph(fs17.readFileSync(this.graphPath, "utf8"));
7308
+ this.cachedMtimeMs = stat.mtimeMs;
7309
+ }
7310
+ return this.cached;
7311
+ }
7312
+ /**
7313
+ * Debounced, single-flight refresh. Never throws — a refresh problem must
7314
+ * degrade to "answer from the current map", not break the tool call.
7315
+ */
7316
+ async maybeRefresh() {
7317
+ const now = Date.now();
7318
+ if (!this.inflight) {
7319
+ const interval = this.tuning.probeIntervalMs ?? this.probeIntervalMs;
7320
+ if (now < this.failedUntil || now - this.lastProbeAt < interval) return;
7321
+ this.lastProbeAt = now;
7322
+ this.inflight = refreshIfStale(this.root).then((r) => {
7323
+ if (r.status === "error") this.failedUntil = Date.now() + FAILURE_COOLDOWN_MS;
7324
+ if (r.status === "fresh" || r.status === "no-snapshot" || r.status === "locked") {
7325
+ const cost = Date.now() - now;
7326
+ this.probeIntervalMs = Math.min(
7327
+ MAX_PROBE_INTERVAL_MS,
7328
+ Math.max(PROBE_INTERVAL_MS, cost * PROBE_DUTY_FACTOR)
7329
+ );
7330
+ }
7331
+ }).catch(() => {
7332
+ this.failedUntil = Date.now() + FAILURE_COOLDOWN_MS;
7333
+ }).finally(() => {
7334
+ this.inflight = null;
7335
+ });
7336
+ }
7337
+ await Promise.race([this.inflight, sleep(this.tuning.refreshBudgetMs ?? REFRESH_BUDGET_MS)]);
7338
+ }
7339
+ };
7340
+ function createServer(source, opts = {}) {
7341
+ const { savings = false, shareStats = false, local = false, dedup = false, stats } = opts;
7342
+ const record = savings || shareStats;
7343
+ const root = path17.dirname(path17.dirname(source.graphPath));
7344
+ const seen = /* @__PURE__ */ new Set();
7345
+ const server = new Server(
7346
+ { name: "vg", version: VERSION },
7347
+ {
7348
+ capabilities: { tools: {} },
7349
+ // Routing guidance once at the server level (hosts that surface
7350
+ // `instructions` get it at zero per-step schema cost): the flashlight
7351
+ // vs the map.
7352
+ instructions: 'vg is a code map. Use search_symbols to find a known name or literal string fast \u2014 a multi-word/quoted phrase runs a complete literal sweep and reports totalTextMatches, so reach for it instead of grep even for plain-string "find every occurrence" lookups. Use orient/query_graph for meaning: symptoms, relationships, and what-breaks-if. Responses are concise by default; pass response_format:"detailed" only when a node proves load-bearing. Navigate as little as possible: one good search/query usually locates the code. As soon as you have the file and line, read that file and make the edit \u2014 do not call further graph tools unless the edit fails or the match was wrong. For how-do-I-use-this-library questions call resolve_library once, then library_docs with the returned targetId and a focused query: the docs are official and matched to the version THIS project has installed (drift-annotated) \u2014 prefer them over web search or training-data recall when they conflict. Skip them for language built-ins or APIs already shown in context. If two library_docs calls have not surfaced the section you need, read the package source under node_modules instead of searching again.'
7353
+ }
7354
+ );
7355
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
7356
+ tools: TOOLS.map((t) => ({
7357
+ name: t.name,
7358
+ description: t.description,
7359
+ inputSchema: t.inputSchema,
7360
+ annotations: { readOnlyHint: true, openWorldHint: false }
7361
+ }))
7362
+ }));
7363
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
7364
+ const tool = TOOLS.find((t) => t.name === request.params.name);
7365
+ if (!tool) {
7366
+ return errorResult(`unknown tool "${request.params.name}"`);
7367
+ }
7368
+ let graph;
7369
+ try {
7370
+ graph = await source.get();
7371
+ } catch {
7372
+ return errorResult(
7373
+ "no code map found. Run `vg` in the project to build .vibgrate/graph.json, then retry."
7374
+ );
7375
+ }
7376
+ const startedAt = Date.now();
7377
+ try {
7378
+ const args = request.params.arguments ?? {};
7379
+ const result = await tool.handler(graph, args, { root, local, dedup, seen });
7380
+ const ms = Date.now() - startedAt;
7381
+ stats?.record({ ...measureCall(tool.name, result), client: sanitizeClient(detectClient(server)), ms });
7382
+ if (record) recordUsage(root, tool.name, result, detectClient(server), ms);
7383
+ return renderToolResult(result);
7384
+ } catch (err) {
7385
+ stats?.record({
7386
+ tool: tool.name,
7387
+ client: sanitizeClient(detectClient(server)),
7388
+ outcome: "miss",
7389
+ ms: Date.now() - startedAt,
7390
+ vgTokens: 0,
7391
+ baselineTokens: 0
7392
+ });
7393
+ return errorResult(`tool "${tool.name}" failed: ${err.message}`);
7394
+ }
7395
+ });
7396
+ return server;
7397
+ }
7398
+ async function serveStdio(graphPath, opts = {}) {
7399
+ const source = new GraphSource(graphPath, opts.refresh !== false);
7400
+ const server = createServer(source, opts);
7401
+ warmEmbedderInBackground(opts.local);
7402
+ await server.connect(new StdioServerTransport());
7403
+ }
7404
+ function sleep(ms) {
7405
+ return new Promise((resolve12) => {
7406
+ const timer = setTimeout(resolve12, ms);
7407
+ timer.unref?.();
7408
+ });
7409
+ }
7410
+ function recordUsage(root, tool, result, client, ms) {
7411
+ recordSaving(
7412
+ root,
7413
+ { ...measureCall(tool, result), source: "mcp", client: sanitizeClient(client), ...ms !== void 0 ? { ms } : {} },
7414
+ Date.now()
7415
+ );
7416
+ }
7417
+ function measureCall(tool, result) {
7418
+ const outcome = classifyOutcome(result);
7419
+ let vgTokens = 0;
7420
+ let baselineTokens = 0;
7421
+ if (SAVINGS_TOOLS.has(tool) && result && typeof result === "object") {
7422
+ vgTokens = countTokens(renderedText(renderToolResult(result)));
7423
+ baselineTokens = referencedFiles(result).size * PER_FILE_TOKENS;
7424
+ }
7425
+ return { tool, outcome, vgTokens, baselineTokens };
7426
+ }
7427
+ function detectClient(server) {
7428
+ try {
7429
+ const info = server.getClientVersion?.();
7430
+ return typeof info?.name === "string" ? info.name : void 0;
7431
+ } catch {
7432
+ return void 0;
7433
+ }
7434
+ }
7435
+ function classifyOutcome(result) {
7436
+ if (Array.isArray(result)) return result.length === 0 ? "miss" : "complete";
7437
+ if (!result || typeof result !== "object") return "miss";
7438
+ const r = result;
7439
+ if (typeof r.error === "string" && r.error) return "miss";
7440
+ if (r.connected === false) return "miss";
7441
+ if (Array.isArray(r.matches) && r.matches.length === 0) return "miss";
7442
+ return isPartial(r) ? "partial" : "complete";
7443
+ }
7444
+ function isPartial(r) {
7445
+ if (r.moreAvailable === true) return true;
7446
+ if (r._truncated && typeof r._truncated === "object") return true;
7447
+ for (const [key, value] of Object.entries(r)) {
7448
+ if (typeof value !== "number" || !key.endsWith("Total")) continue;
7449
+ const base = key.slice(0, -"Total".length);
7450
+ const shown = Array.isArray(r[base]) ? r[base].length : 0;
7451
+ if (value > shown) return true;
7452
+ }
7453
+ return false;
7454
+ }
7455
+ function renderedText(rendered) {
7456
+ const block = rendered.content?.find((b) => b.type === "text");
7457
+ return block && "text" in block && typeof block.text === "string" ? block.text : "";
7458
+ }
7459
+ function referencedFiles(result) {
7460
+ const r = result;
7461
+ const files = /* @__PURE__ */ new Set();
7462
+ if (typeof r.file === "string" && r.file) files.add(r.file);
7463
+ for (const m of asArray(r.matches)) {
7464
+ const f = m.file;
7465
+ if (typeof f === "string" && f) files.add(f);
7466
+ }
7467
+ for (const name of [...asArray(r.calls), ...asArray(r.calledBy)]) {
7468
+ if (typeof name !== "string") continue;
7469
+ const i = name.indexOf(":");
7470
+ if (i > 0) files.add(name.slice(0, i));
7471
+ }
7472
+ return files;
7473
+ }
7474
+ function asArray(v) {
7475
+ return Array.isArray(v) ? v : [];
7476
+ }
7477
+ function errorResult(message) {
7478
+ return { content: [{ type: "text", text: message }], isError: true };
7479
+ }
7388
7480
 
7389
7481
  // src/engine/export.ts
7390
7482
  function formatForExt(ext) {
@@ -7589,6 +7681,6 @@ function spdx(ctx) {
7589
7681
  return JSON.stringify(doc, null, 2) + "\n";
7590
7682
  }
7591
7683
 
7592
- export { ASSISTANTS, CLI_TOOL_ALIASES, CliError, DEFAULT_RATE_LABEL, DEFAULT_RATE_PER_M, ECOSYSTEMS, ExitCode, FREE_PACK, GraphIndex, GraphSource, NPX_INVOCATION, ResourceLimitError, SAVINGS_TOOLS, SCHEMA_VERSION, SKIP_DIRS, SKIP_FILES, TOOLS, UsageError, addLibrary, analyze, applyCoverage, applyStaticTestLinkage, assessDocQuality, assistantById, availableRegionIds, buildFacts, buildGraph, buildModuleResolver, cacheDir, catalogPath, clearModelCache, clearStoredCredentials, cosine, countPending, countTokens, coveringTests, createServer, createWorkspaceDsn, credentialsPath, dashHostForIngestHost, decodeScipIndex, defaultGraphPath, detectAssistants, detectRunner, detectServeLaunch, discover, discoverModels, driftCount, driftFor, dsnCommand, embeddingsCached, enrichOnline, ensureGitignored, epistemicBreakdown, exportGraph, fetchHostedDocsCached, findGitRoot, findNodes, formatForExt, getNodeEmbeddings, gitignoreEntryForCredentials, groundGraph, hasDrift, homeCredentialsPath, hostedBase, identifierParts, impactOf, indexFor, ingestHostForRegionId, installAssistant, inventory, isModelReady, isTestFile, libDir, libId, loadCatalog, loadCoverage, loadEmbedder, loadGraph, loadSnapshot, localApiSurface, localPackageDocs, modelCacheInfo, nodeById, nodeEmbedText, parseDsn, parseGraph, parseJsonc, probeFreshness, projectCredentialsPath, publishPrivateLibrary, pushCommand, queryGraph, queryGraphSemantic, readDoc, readSavings, readScanArtifact, readStoredCredentials, readUsage, recordCliCall, recordSaving, refreshIfStale, relativeResolver, renderHtml, renderReport, resolveCliInvocation, resolveDsn, resolveEmbedModel, resolveIngestHost, resolveLib, resolveLimits, resolveOne, resolveVersion, saveCatalog, savingsRecorded, scipEdges, selectForBudget, serializeGraph, serveStdio, shortestPath, stableStringify, symbolsFromApi, testsToRun, unavailableMessage, uninstallAssistant, uploadScanArtifact, usageError, verifyDeterminism, vibgrateDir, writeArtifacts, writeNavigationConfig, writeSnapshot, writeStoredCredentials };
7593
- //# sourceMappingURL=chunk-VQLOVQZP.js.map
7594
- //# sourceMappingURL=chunk-VQLOVQZP.js.map
7684
+ export { ASSISTANTS, CLI_TOOL_ALIASES, CliError, ECOSYSTEMS, ExitCode, FREE_PACK, GraphIndex, GraphSource, NPX_INVOCATION, ResourceLimitError, SAVINGS_TOOLS, SCHEMA_VERSION, SKIP_DIRS, SKIP_FILES, SMALL_REPO_FILES, TOOLS, UsageError, addLibrary, analyze, applyCoverage, applyStaticTestLinkage, assessDocQuality, assistantById, availableRegionIds, buildFacts, buildGraph, buildModuleResolver, cacheDir, catalogPath, clearModelCache, clearStoredCredentials, cosine, countPending, countTokens, coveringTests, createServer, createWorkspaceDsn, credentialsPath, dashHostForIngestHost, decodeScipIndex, defaultGraphPath, detectAssistants, detectRunner, detectServeLaunch, discover, discoverModels, driftCount, driftFor, dsnCommand, embeddingsCached, enrichOnline, ensureGitignored, epistemicBreakdown, exportGraph, fetchHostedDocsCached, findGitRoot, findNodes, formatForExt, getNodeEmbeddings, gitignoreEntryForCredentials, groundGraph, hasDrift, homeCredentialsPath, hostedBase, identifierParts, impactOf, indexFor, ingestHostForRegionId, installAssistant, inventory, isModelReady, isTestFile, libDir, libId, loadCatalog, loadCoverage, loadEmbedder, loadGraph, loadSnapshot, localApiSurface, localPackageDocs, modelCacheInfo, nodeById, nodeEmbedText, parseDsn, parseGraph, parseJsonc, probeFreshness, projectCredentialsPath, publishPrivateLibrary, pushCommand, queryGraph, queryGraphSemantic, readDoc, readSavings, readScanArtifact, readStoredCredentials, readUsage, recordCliCall, recordSaving, refreshIfStale, refreshInstalledInstructions, relativeResolver, renderHtml, renderReport, resolveCliInvocation, resolveDsn, resolveEmbedModel, resolveIngestHost, resolveLib, resolveLimits, resolveOne, resolveVersion, sanitizeClient, saveCatalog, savingsLedgerPath, savingsRecorded, scipEdges, selectForBudget, serializeGraph, serveStdio, shortestPath, stableStringify, symbolsFromApi, testsToRun, unavailableMessage, uninstallAssistant, uploadScanArtifact, usageError, verifyDeterminism, vibgrateDir, writeArtifacts, writeNavigationConfig, writeSnapshot, writeStoredCredentials };
7685
+ //# sourceMappingURL=chunk-ZFDVHKID.js.map
7686
+ //# sourceMappingURL=chunk-ZFDVHKID.js.map