@vibgrate/cli 2026.708.2 → 2026.709.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
1
  import { hashString, langById, shortId, canonicalize, grammarSetVersion, hashBytes, langForExtension, setGrammarsOverride, parseSource } from './chunk-X5YT263H.js';
2
- import { writeTextFile, pathExists, readJsonFile, computeUpgradeImpact, getChangelogSignals, gitHistoryAvailable, buildVersionTimelines, findPackageTimeline, severityRank, projectTypeToVulnEcosystem, VERSION } from './chunk-ICBBG6U3.js';
3
- import * as fs18 from 'fs';
2
+ import { writeTextFile, pathExists, readJsonFile, computeUpgradeImpact, getChangelogSignals, gitHistoryAvailable, buildVersionTimelines, findPackageTimeline, severityRank, projectTypeToVulnEcosystem, VERSION } from './chunk-PXZ5KPVN.js';
3
+ import * as fs19 from 'fs';
4
4
  import { execFileSync } from 'child_process';
5
- import * as path18 from 'path';
5
+ import * as path19 from 'path';
6
6
  import ignore from 'ignore';
7
7
  import * as v8 from 'v8';
8
8
  import Graph from 'graphology';
@@ -38,9 +38,9 @@ function whichOnPath(cmd) {
38
38
  }
39
39
  function isOwnBinary(binPath) {
40
40
  try {
41
- const real = fs18.realpathSync(binPath);
41
+ const real = fs19.realpathSync(binPath);
42
42
  if (/[\\/]@vibgrate[\\/]cli[\\/]/.test(real)) return true;
43
- const head = fs18.readFileSync(real, { encoding: "utf8" }).slice(0, 2048);
43
+ const head = fs19.readFileSync(real, { encoding: "utf8" }).slice(0, 2048);
44
44
  return head.includes("@vibgrate/cli") || head.includes("vibgrate");
45
45
  } catch {
46
46
  return false;
@@ -50,7 +50,7 @@ function isEphemeralNpxBinary(binPath) {
50
50
  const NPX_SEGMENT = /[\\/]_npx[\\/]/;
51
51
  if (NPX_SEGMENT.test(binPath)) return true;
52
52
  try {
53
- return NPX_SEGMENT.test(fs18.realpathSync(binPath));
53
+ return NPX_SEGMENT.test(fs19.realpathSync(binPath));
54
54
  } catch {
55
55
  return false;
56
56
  }
@@ -216,19 +216,19 @@ var SKIP_FILES = /* @__PURE__ */ new Set([
216
216
  "flake.lock"
217
217
  ]);
218
218
  function toPosix(p) {
219
- return p.split(path18.sep).join("/");
219
+ return p.split(path19.sep).join("/");
220
220
  }
221
221
  function buildRootIgnore(root, exclude) {
222
222
  const ig = ignore();
223
- const gitignorePath = path18.join(root, ".gitignore");
224
- if (fs18.existsSync(gitignorePath)) {
225
- ig.add(fs18.readFileSync(gitignorePath, "utf8"));
223
+ const gitignorePath = path19.join(root, ".gitignore");
224
+ if (fs19.existsSync(gitignorePath)) {
225
+ ig.add(fs19.readFileSync(gitignorePath, "utf8"));
226
226
  }
227
227
  if (exclude.length) ig.add(exclude);
228
228
  return ig;
229
229
  }
230
230
  function discover(options) {
231
- const root = path18.resolve(options.root);
231
+ const root = path19.resolve(options.root);
232
232
  const onlyLangs = (options.only ?? []).filter(Boolean);
233
233
  const allowLang = (lang) => onlyLangs.length === 0 || onlyLangs.includes(lang.id);
234
234
  for (const id of onlyLangs) {
@@ -237,27 +237,27 @@ function discover(options) {
237
237
  }
238
238
  }
239
239
  const rootIg = buildRootIgnore(root, options.exclude ?? []);
240
- const scopeAbs = (options.paths && options.paths.length ? options.paths.map((p) => path18.resolve(root, p)) : [root]).filter((p) => fs18.existsSync(p));
240
+ const scopeAbs = (options.paths && options.paths.length ? options.paths.map((p) => path19.resolve(root, p)) : [root]).filter((p) => fs19.existsSync(p));
241
241
  const found = /* @__PURE__ */ new Map();
242
242
  const considerFile = (abs) => {
243
- const rel2 = toPosix(path18.relative(root, abs));
243
+ const rel2 = toPosix(path19.relative(root, abs));
244
244
  if (rel2.startsWith("..")) return;
245
245
  if (rel2 === "" || rootIg.ignores(rel2)) return;
246
- if (SKIP_FILES.has(path18.basename(abs).toLowerCase())) return;
247
- const lang = langForExtension(path18.extname(abs));
246
+ if (SKIP_FILES.has(path19.basename(abs).toLowerCase())) return;
247
+ const lang = langForExtension(path19.extname(abs));
248
248
  if (!lang || !allowLang(lang)) return;
249
249
  found.set(rel2, { rel: rel2, abs, lang });
250
250
  };
251
251
  const walk2 = (dir) => {
252
252
  let entries;
253
253
  try {
254
- entries = fs18.readdirSync(dir, { withFileTypes: true });
254
+ entries = fs19.readdirSync(dir, { withFileTypes: true });
255
255
  } catch {
256
256
  return;
257
257
  }
258
258
  for (const entry of entries) {
259
- const abs = path18.join(dir, entry.name);
260
- const rel2 = toPosix(path18.relative(root, abs));
259
+ const abs = path19.join(dir, entry.name);
260
+ const rel2 = toPosix(path19.relative(root, abs));
261
261
  if (entry.isDirectory()) {
262
262
  if (SKIP_DIRS.has(entry.name)) continue;
263
263
  if (rel2 && rootIg.ignores(`${rel2}/`)) continue;
@@ -268,7 +268,7 @@ function discover(options) {
268
268
  }
269
269
  };
270
270
  for (const scope of scopeAbs) {
271
- const stat = fs18.statSync(scope);
271
+ const stat = fs19.statSync(scope);
272
272
  if (stat.isDirectory()) walk2(scope);
273
273
  else if (stat.isFile()) considerFile(scope);
274
274
  }
@@ -434,10 +434,10 @@ function buildModuleResolver(root, relSet) {
434
434
  resolve(fromRel, source) {
435
435
  if (source.includes("\\")) source = source.split("\\").join(".");
436
436
  if (source.startsWith("./") || source.startsWith("../")) {
437
- return probe(posixJoin(path18.posix.dirname(fromRel), source));
437
+ return probe(posixJoin(path19.posix.dirname(fromRel), source));
438
438
  }
439
439
  if (/\.[A-Za-z0-9]+$/.test(source) && !source.startsWith("<")) {
440
- const hit = probe(posixJoin(path18.posix.dirname(fromRel), source));
440
+ const hit = probe(posixJoin(path19.posix.dirname(fromRel), source));
441
441
  if (hit) return hit;
442
442
  }
443
443
  if (source.startsWith(".")) {
@@ -508,8 +508,8 @@ function resolvePyRelative(probe, fromRel, source) {
508
508
  let dots = 0;
509
509
  while (source[dots] === ".") dots++;
510
510
  const rest = source.slice(dots);
511
- let dir = path18.posix.dirname(fromRel);
512
- for (let i = 1; i < dots; i++) dir = path18.posix.dirname(dir);
511
+ let dir = path19.posix.dirname(fromRel);
512
+ for (let i = 1; i < dots; i++) dir = path19.posix.dirname(dir);
513
513
  const sub = rest.replace(/\./g, "/");
514
514
  return probe(sub ? posixJoin(dir, sub) : dir);
515
515
  }
@@ -517,7 +517,7 @@ function relativeResolver(relSet) {
517
517
  const probe = makeProbe(relSet);
518
518
  return {
519
519
  resolve(fromRel, source) {
520
- if (source.startsWith(".")) return probe(posixJoin(path18.posix.dirname(fromRel), source));
520
+ if (source.startsWith(".")) return probe(posixJoin(path19.posix.dirname(fromRel), source));
521
521
  return resolveDotted(probe, source);
522
522
  }
523
523
  };
@@ -535,12 +535,12 @@ function makeProbe(relSet) {
535
535
  };
536
536
  }
537
537
  function normalizeRel(p) {
538
- const norm = path18.posix.normalize(p);
538
+ const norm = path19.posix.normalize(p);
539
539
  const stripped = norm.startsWith("./") ? norm.slice(2) : norm;
540
540
  return stripped.startsWith("..") ? "" : stripped;
541
541
  }
542
542
  function posixJoin(a, b) {
543
- return path18.posix.normalize(path18.posix.join(a, b));
543
+ return path19.posix.normalize(path19.posix.join(a, b));
544
544
  }
545
545
  function matchPattern(pattern, source) {
546
546
  const star = pattern.indexOf("*");
@@ -553,8 +553,8 @@ function matchPattern(pattern, source) {
553
553
  }
554
554
  function loadTsconfigPaths(root) {
555
555
  for (const name of ["tsconfig.base.json", "tsconfig.json"]) {
556
- const file = path18.join(root, name);
557
- if (fs18.existsSync(file)) {
556
+ const file = path19.join(root, name);
557
+ if (fs19.existsSync(file)) {
558
558
  const merged = readTsconfigChain(root, file, /* @__PURE__ */ new Set());
559
559
  if (merged && merged.paths.length) return merged;
560
560
  }
@@ -566,21 +566,21 @@ function readTsconfigChain(root, file, seen) {
566
566
  seen.add(file);
567
567
  let cfg;
568
568
  try {
569
- cfg = parseJsonc(fs18.readFileSync(file, "utf8"));
569
+ cfg = parseJsonc(fs19.readFileSync(file, "utf8"));
570
570
  } catch {
571
571
  return null;
572
572
  }
573
- const dir = path18.dirname(file);
573
+ const dir = path19.dirname(file);
574
574
  let base = { baseUrlRel: "", paths: [] };
575
575
  if (cfg.extends && cfg.extends.startsWith(".")) {
576
- const extPath = path18.resolve(dir, cfg.extends.endsWith(".json") ? cfg.extends : `${cfg.extends}.json`);
577
- if (fs18.existsSync(extPath)) {
576
+ const extPath = path19.resolve(dir, cfg.extends.endsWith(".json") ? cfg.extends : `${cfg.extends}.json`);
577
+ if (fs19.existsSync(extPath)) {
578
578
  const inherited = readTsconfigChain(root, extPath, seen);
579
579
  if (inherited) base = inherited;
580
580
  }
581
581
  }
582
582
  const co = cfg.compilerOptions ?? {};
583
- const baseUrlRel = co.baseUrl !== void 0 ? path18.relative(root, path18.resolve(dir, co.baseUrl)).split(path18.sep).join("/") : base.baseUrlRel;
583
+ const baseUrlRel = co.baseUrl !== void 0 ? path19.relative(root, path19.resolve(dir, co.baseUrl)).split(path19.sep).join("/") : base.baseUrlRel;
584
584
  const paths = [...base.paths];
585
585
  if (co.paths) for (const [k, v] of Object.entries(co.paths)) paths.push([k, v]);
586
586
  return { baseUrlRel, paths };
@@ -588,27 +588,27 @@ function readTsconfigChain(root, file, seen) {
588
588
  function loadWorkspacePackages(root) {
589
589
  const map = /* @__PURE__ */ new Map();
590
590
  const globs = [];
591
- const rootPkg = path18.join(root, "package.json");
592
- if (fs18.existsSync(rootPkg)) {
591
+ const rootPkg = path19.join(root, "package.json");
592
+ if (fs19.existsSync(rootPkg)) {
593
593
  try {
594
- const pkg = JSON.parse(fs18.readFileSync(rootPkg, "utf8"));
594
+ const pkg = JSON.parse(fs19.readFileSync(rootPkg, "utf8"));
595
595
  const ws = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces?.packages;
596
596
  if (ws) globs.push(...ws);
597
597
  } catch {
598
598
  }
599
599
  }
600
- const pnpmWs = path18.join(root, "pnpm-workspace.yaml");
601
- if (fs18.existsSync(pnpmWs)) {
602
- for (const line of fs18.readFileSync(pnpmWs, "utf8").split("\n")) {
600
+ const pnpmWs = path19.join(root, "pnpm-workspace.yaml");
601
+ if (fs19.existsSync(pnpmWs)) {
602
+ for (const line of fs19.readFileSync(pnpmWs, "utf8").split("\n")) {
603
603
  const m = /^\s*-\s*['"]?([^'"\n]+)['"]?\s*$/.exec(line);
604
604
  if (m) globs.push(m[1].trim());
605
605
  }
606
606
  }
607
607
  for (const dir of expandWorkspaceGlobs(root, globs)) {
608
- const pj = path18.join(dir, "package.json");
608
+ const pj = path19.join(dir, "package.json");
609
609
  try {
610
- const name = JSON.parse(fs18.readFileSync(pj, "utf8")).name;
611
- if (name) map.set(name, path18.relative(root, dir).split(path18.sep).join("/"));
610
+ const name = JSON.parse(fs19.readFileSync(pj, "utf8")).name;
611
+ if (name) map.set(name, path19.relative(root, dir).split(path19.sep).join("/"));
612
612
  } catch {
613
613
  }
614
614
  }
@@ -618,10 +618,10 @@ function expandWorkspaceGlobs(root, globs) {
618
618
  const out = /* @__PURE__ */ new Set();
619
619
  for (const glob of globs) {
620
620
  const clean = glob.replace(/\/\*\*$/, "").replace(/\/\*$/, "");
621
- const baseDir = path18.join(root, clean);
621
+ const baseDir = path19.join(root, clean);
622
622
  const recurse = glob.endsWith("**");
623
623
  if (glob === clean) {
624
- if (fs18.existsSync(path18.join(baseDir, "package.json"))) out.add(baseDir);
624
+ if (fs19.existsSync(path19.join(baseDir, "package.json"))) out.add(baseDir);
625
625
  continue;
626
626
  }
627
627
  collectPackageDirs(baseDir, recurse ? 4 : 1, out);
@@ -632,14 +632,14 @@ function collectPackageDirs(dir, depth, out) {
632
632
  if (depth < 0) return;
633
633
  let entries;
634
634
  try {
635
- entries = fs18.readdirSync(dir, { withFileTypes: true });
635
+ entries = fs19.readdirSync(dir, { withFileTypes: true });
636
636
  } catch {
637
637
  return;
638
638
  }
639
639
  for (const e of entries) {
640
640
  if (!e.isDirectory() || e.name === "node_modules" || e.name.startsWith(".")) continue;
641
- const child = path18.join(dir, e.name);
642
- if (fs18.existsSync(path18.join(child, "package.json"))) out.add(child);
641
+ const child = path19.join(dir, e.name);
642
+ if (fs19.existsSync(path19.join(child, "package.json"))) out.add(child);
643
643
  else collectPackageDirs(child, depth - 1, out);
644
644
  }
645
645
  }
@@ -1137,14 +1137,14 @@ var DEFAULT_PATHS = [
1137
1137
  ];
1138
1138
  function loadCoverage(root, explicit) {
1139
1139
  const candidates = (explicit && explicit.length ? explicit : DEFAULT_PATHS).map(
1140
- (p) => path18.resolve(root, p)
1140
+ (p) => path19.resolve(root, p)
1141
1141
  );
1142
- const found = candidates.filter((p) => fs18.existsSync(p));
1142
+ const found = candidates.filter((p) => fs19.existsSync(p));
1143
1143
  if (found.length === 0) return null;
1144
1144
  const map = /* @__PURE__ */ new Map();
1145
1145
  for (const file of found) {
1146
1146
  try {
1147
- const text = fs18.readFileSync(file, "utf8");
1147
+ const text = fs19.readFileSync(file, "utf8");
1148
1148
  if (file.endsWith(".json")) mergeIstanbul(map, text, root);
1149
1149
  else mergeLcov(map, text, root);
1150
1150
  } catch {
@@ -1153,8 +1153,8 @@ function loadCoverage(root, explicit) {
1153
1153
  return map.size ? map : null;
1154
1154
  }
1155
1155
  function rel(root, p) {
1156
- const abs = path18.isAbsolute(p) ? p : path18.resolve(root, p);
1157
- return path18.relative(root, abs).split(path18.sep).join("/");
1156
+ const abs = path19.isAbsolute(p) ? p : path19.resolve(root, p);
1157
+ return path19.relative(root, abs).split(path19.sep).join("/");
1158
1158
  }
1159
1159
  function bump(map, file, line, hits) {
1160
1160
  let lh = map.get(file);
@@ -1490,7 +1490,7 @@ async function parseInline(files, options) {
1490
1490
  onProgress?.(0, files.length);
1491
1491
  for (const file of files) {
1492
1492
  try {
1493
- const source = fs18.readFileSync(file.abs, "utf8");
1493
+ const source = fs19.readFileSync(file.abs, "utf8");
1494
1494
  out.push(await parseSource(file.rel, file.lang.id, source));
1495
1495
  } catch (err) {
1496
1496
  out.push(emptyParse(file, `parse failed: ${err.message}`));
@@ -1545,9 +1545,9 @@ function isWorkerOom(err) {
1545
1545
  return e?.code === "ERR_WORKER_OUT_OF_MEMORY" || /out of memory/i.test(e?.message ?? "");
1546
1546
  }
1547
1547
  function resolveWorkerFile() {
1548
- const here = path18.dirname(fileURLToPath(import.meta.url));
1549
- const candidate = path18.join(here, "parse-worker.js");
1550
- return fs18.existsSync(candidate) ? candidate : null;
1548
+ const here = path19.dirname(fileURLToPath(import.meta.url));
1549
+ const candidate = path19.join(here, "parse-worker.js");
1550
+ return fs19.existsSync(candidate) ? candidate : null;
1551
1551
  }
1552
1552
  function chunk(items, buckets) {
1553
1553
  const out = Array.from({ length: Math.min(buckets, items.length || 1) }, () => []);
@@ -1585,7 +1585,7 @@ function resolve4(parses, resolver) {
1585
1585
  addNode(baseNodes, {
1586
1586
  id: fileId,
1587
1587
  kind: "file",
1588
- name: path18.posix.basename(p.rel),
1588
+ name: path19.posix.basename(p.rel),
1589
1589
  qualifiedName: p.rel,
1590
1590
  file: p.rel,
1591
1591
  span: { start: 1, end: 1 },
@@ -2002,14 +2002,14 @@ function enclosing3(nodes, line) {
2002
2002
  return best;
2003
2003
  }
2004
2004
  function normalize2(p) {
2005
- return path18.resolve(p).split(path18.sep).join("/");
2005
+ return path19.resolve(p).split(path19.sep).join("/");
2006
2006
  }
2007
2007
  var CACHE_VERSION = "vg-parse-cache/2";
2008
2008
  function cacheDir(root) {
2009
- return path18.join(root, ".vibgrate", "cache");
2009
+ return path19.join(root, ".vibgrate", "cache");
2010
2010
  }
2011
2011
  function cachePath(root) {
2012
- return path18.join(cacheDir(root), "parse-cache.json");
2012
+ return path19.join(cacheDir(root), "parse-cache.json");
2013
2013
  }
2014
2014
  function loadCache(root, opts) {
2015
2015
  const file = cachePath(root);
@@ -2019,9 +2019,9 @@ function loadCache(root, opts) {
2019
2019
  grammars: opts.grammars,
2020
2020
  entries: {}
2021
2021
  };
2022
- if (!opts.disabled && fs18.existsSync(file)) {
2022
+ if (!opts.disabled && fs19.existsSync(file)) {
2023
2023
  try {
2024
- const loaded = JSON.parse(fs18.readFileSync(file, "utf8"));
2024
+ const loaded = JSON.parse(fs19.readFileSync(file, "utf8"));
2025
2025
  if (loaded.version === CACHE_VERSION && loaded.toolVersion === opts.toolVersion && loaded.grammars === opts.grammars && loaded.entries) {
2026
2026
  data = loaded;
2027
2027
  }
@@ -2042,8 +2042,8 @@ function loadCache(root, opts) {
2042
2042
  }
2043
2043
  },
2044
2044
  save() {
2045
- fs18.mkdirSync(cacheDir(root), { recursive: true });
2046
- fs18.writeFileSync(file, stableStringify(data, 0));
2045
+ fs19.mkdirSync(cacheDir(root), { recursive: true });
2046
+ fs19.writeFileSync(file, stableStringify(data, 0));
2047
2047
  }
2048
2048
  };
2049
2049
  }
@@ -2075,7 +2075,7 @@ function epistemicBreakdown(edges) {
2075
2075
  // src/engine/build.ts
2076
2076
  async function buildGraph(options) {
2077
2077
  const start = nowMs();
2078
- const root = path18.resolve(options.root);
2078
+ const root = path19.resolve(options.root);
2079
2079
  const files = discover({
2080
2080
  root,
2081
2081
  only: options.only,
@@ -2103,7 +2103,7 @@ async function buildGraph(options) {
2103
2103
  for (const file of files) {
2104
2104
  let stat;
2105
2105
  try {
2106
- stat = fs18.statSync(file.abs);
2106
+ stat = fs19.statSync(file.abs);
2107
2107
  } catch {
2108
2108
  continue;
2109
2109
  }
@@ -2121,7 +2121,7 @@ async function buildGraph(options) {
2121
2121
  }
2122
2122
  let hash = "";
2123
2123
  try {
2124
- hash = hashBytes(fs18.readFileSync(file.abs));
2124
+ hash = hashBytes(fs19.readFileSync(file.abs));
2125
2125
  } catch {
2126
2126
  continue;
2127
2127
  }
@@ -2213,7 +2213,7 @@ async function buildGraph(options) {
2213
2213
  toolchain
2214
2214
  },
2215
2215
  meta: {
2216
- root: path18.basename(root) === "" ? "." : ".",
2216
+ root: path19.basename(root) === "" ? "." : ".",
2217
2217
  languages,
2218
2218
  counts: {
2219
2219
  nodes: analysis.nodes.length,
@@ -2259,14 +2259,14 @@ function toRepoRel(p) {
2259
2259
  function loadScipIndex(root, explicit) {
2260
2260
  const candidates = [
2261
2261
  explicit,
2262
- path18.join(root, "index.scip"),
2263
- path18.join(root, ".vibgrate", "index.scip")
2262
+ path19.join(root, "index.scip"),
2263
+ path19.join(root, ".vibgrate", "index.scip")
2264
2264
  ].filter((p) => Boolean(p));
2265
2265
  for (const file of candidates) {
2266
- const abs = path18.isAbsolute(file) ? file : path18.resolve(root, file);
2267
- if (!fs18.existsSync(abs)) continue;
2266
+ const abs = path19.isAbsolute(file) ? file : path19.resolve(root, file);
2267
+ if (!fs19.existsSync(abs)) continue;
2268
2268
  try {
2269
- const index = decodeScipIndex(new Uint8Array(fs18.readFileSync(abs)));
2269
+ const index = decodeScipIndex(new Uint8Array(fs19.readFileSync(abs)));
2270
2270
  if (index.documents.length) {
2271
2271
  return { index, tool: index.toolVersion ? `${index.toolName} ${index.toolVersion}` : index.toolName };
2272
2272
  }
@@ -2427,46 +2427,46 @@ function esc(s) {
2427
2427
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2428
2428
  }
2429
2429
  function vibgrateDir(root) {
2430
- return path18.join(root, ".vibgrate");
2430
+ return path19.join(root, ".vibgrate");
2431
2431
  }
2432
2432
  function defaultGraphPath(root) {
2433
- return path18.join(vibgrateDir(root), "graph.json");
2433
+ return path19.join(vibgrateDir(root), "graph.json");
2434
2434
  }
2435
2435
  function writeArtifacts(graph, options) {
2436
2436
  const dir = vibgrateDir(options.root);
2437
- fs18.mkdirSync(dir, { recursive: true });
2437
+ fs19.mkdirSync(dir, { recursive: true });
2438
2438
  const graphPath = options.graphPath ?? defaultGraphPath(options.root);
2439
- fs18.mkdirSync(path18.dirname(graphPath), { recursive: true });
2439
+ fs19.mkdirSync(path19.dirname(graphPath), { recursive: true });
2440
2440
  const tmp = `${graphPath}.${process.pid}.tmp`;
2441
2441
  try {
2442
- fs18.writeFileSync(tmp, serializeGraph(graph));
2443
- fs18.renameSync(tmp, graphPath);
2442
+ fs19.writeFileSync(tmp, serializeGraph(graph));
2443
+ fs19.renameSync(tmp, graphPath);
2444
2444
  } catch (err) {
2445
- fs18.rmSync(tmp, { force: true });
2445
+ fs19.rmSync(tmp, { force: true });
2446
2446
  throw err;
2447
2447
  }
2448
2448
  const written = { graphPath };
2449
2449
  if (options.report !== false) {
2450
- const reportPath = path18.join(dir, "GRAPH_REPORT.md");
2451
- fs18.writeFileSync(reportPath, renderReport(graph));
2450
+ const reportPath = path19.join(dir, "GRAPH_REPORT.md");
2451
+ fs19.writeFileSync(reportPath, renderReport(graph));
2452
2452
  written.reportPath = reportPath;
2453
2453
  }
2454
2454
  if (options.html !== false) {
2455
- const htmlPath = path18.join(dir, "graph.html");
2456
- fs18.writeFileSync(htmlPath, renderHtml(graph));
2455
+ const htmlPath = path19.join(dir, "graph.html");
2456
+ fs19.writeFileSync(htmlPath, renderHtml(graph));
2457
2457
  written.htmlPath = htmlPath;
2458
2458
  }
2459
2459
  if (graph.facts && graph.facts.length) {
2460
- const factsPath = path18.join(dir, "facts.jsonl");
2461
- fs18.writeFileSync(factsPath, graph.facts.map((f) => JSON.stringify(f)).join("\n") + "\n");
2460
+ const factsPath = path19.join(dir, "facts.jsonl");
2461
+ fs19.writeFileSync(factsPath, graph.facts.map((f) => JSON.stringify(f)).join("\n") + "\n");
2462
2462
  written.factsPath = factsPath;
2463
2463
  }
2464
2464
  return written;
2465
2465
  }
2466
2466
  function loadGraph(root, graphPath) {
2467
2467
  const file = graphPath ?? defaultGraphPath(root);
2468
- if (!fs18.existsSync(file)) return null;
2469
- return parseGraph(fs18.readFileSync(file, "utf8"));
2468
+ if (!fs19.existsSync(file)) return null;
2469
+ return parseGraph(fs19.readFileSync(file, "utf8"));
2470
2470
  }
2471
2471
 
2472
2472
  // src/engine/verify.ts
@@ -2543,7 +2543,7 @@ function isProcessAlive(pid) {
2543
2543
  }
2544
2544
  function lockIsStale(file, staleMs) {
2545
2545
  try {
2546
- const { pid, at } = JSON.parse(fs18.readFileSync(file, "utf8"));
2546
+ const { pid, at } = JSON.parse(fs19.readFileSync(file, "utf8"));
2547
2547
  if (typeof at === "number" && Date.now() - at > staleMs) return true;
2548
2548
  if (typeof pid === "number" && !isProcessAlive(pid)) return true;
2549
2549
  return false;
@@ -2554,10 +2554,10 @@ function lockIsStale(file, staleMs) {
2554
2554
  function acquireLock(file, staleMs = DEFAULT_LOCK_STALE_MS) {
2555
2555
  const write = () => {
2556
2556
  try {
2557
- fs18.mkdirSync(path18.dirname(file), { recursive: true });
2558
- const fd = fs18.openSync(file, "wx");
2559
- fs18.writeFileSync(fd, JSON.stringify({ pid: process.pid, at: Date.now() }));
2560
- fs18.closeSync(fd);
2557
+ fs19.mkdirSync(path19.dirname(file), { recursive: true });
2558
+ const fd = fs19.openSync(file, "wx");
2559
+ fs19.writeFileSync(fd, JSON.stringify({ pid: process.pid, at: Date.now() }));
2560
+ fs19.closeSync(fd);
2561
2561
  return true;
2562
2562
  } catch {
2563
2563
  return false;
@@ -2566,7 +2566,7 @@ function acquireLock(file, staleMs = DEFAULT_LOCK_STALE_MS) {
2566
2566
  if (write()) return true;
2567
2567
  if (lockIsStale(file, staleMs)) {
2568
2568
  try {
2569
- fs18.rmSync(file, { force: true });
2569
+ fs19.rmSync(file, { force: true });
2570
2570
  } catch {
2571
2571
  }
2572
2572
  return write();
@@ -2575,7 +2575,7 @@ function acquireLock(file, staleMs = DEFAULT_LOCK_STALE_MS) {
2575
2575
  }
2576
2576
  function releaseLock(file) {
2577
2577
  try {
2578
- fs18.rmSync(file, { force: true });
2578
+ fs19.rmSync(file, { force: true });
2579
2579
  } catch {
2580
2580
  }
2581
2581
  }
@@ -2585,14 +2585,14 @@ function resolveEmbedModel(model) {
2585
2585
  return model ?? "bge-small-en-v1.5";
2586
2586
  }
2587
2587
  function modelCacheDir() {
2588
- const base = process.env.XDG_CACHE_HOME || path18.join(os.homedir(), ".cache");
2589
- return path18.join(base, "vibgrate", "models");
2588
+ const base = process.env.XDG_CACHE_HOME || path19.join(os.homedir(), ".cache");
2589
+ return path19.join(base, "vibgrate", "models");
2590
2590
  }
2591
2591
  function modelReadyMarker(modelId) {
2592
- return path18.join(modelCacheDir(), `.ready-${safe(modelId)}`);
2592
+ return path19.join(modelCacheDir(), `.ready-${safe(modelId)}`);
2593
2593
  }
2594
2594
  function isModelReady(modelId = resolveEmbedModel()) {
2595
- return fs18.existsSync(modelReadyMarker(modelId));
2595
+ return fs19.existsSync(modelReadyMarker(modelId));
2596
2596
  }
2597
2597
  function unavailableMessage(reason) {
2598
2598
  switch (reason) {
@@ -2609,13 +2609,13 @@ function unavailableMessage(reason) {
2609
2609
  }
2610
2610
  function modelCacheInfo(modelId = resolveEmbedModel()) {
2611
2611
  const dir = modelCacheDir();
2612
- return { dir, present: isModelReady(modelId) || fs18.existsSync(dir), bytes: dirSize(dir) };
2612
+ return { dir, present: isModelReady(modelId) || fs19.existsSync(dir), bytes: dirSize(dir) };
2613
2613
  }
2614
2614
  function clearModelCache() {
2615
2615
  const dir = modelCacheDir();
2616
2616
  const bytes = dirSize(dir);
2617
2617
  try {
2618
- fs18.rmSync(dir, { recursive: true, force: true });
2618
+ fs19.rmSync(dir, { recursive: true, force: true });
2619
2619
  } catch {
2620
2620
  }
2621
2621
  return bytes;
@@ -2624,16 +2624,16 @@ function dirSize(dir) {
2624
2624
  let total = 0;
2625
2625
  let entries;
2626
2626
  try {
2627
- entries = fs18.readdirSync(dir, { withFileTypes: true });
2627
+ entries = fs19.readdirSync(dir, { withFileTypes: true });
2628
2628
  } catch {
2629
2629
  return 0;
2630
2630
  }
2631
2631
  for (const e of entries) {
2632
- const p = path18.join(dir, e.name);
2632
+ const p = path19.join(dir, e.name);
2633
2633
  if (e.isDirectory()) total += dirSize(p);
2634
2634
  else {
2635
2635
  try {
2636
- total += fs18.statSync(p).size;
2636
+ total += fs19.statSync(p).size;
2637
2637
  } catch {
2638
2638
  }
2639
2639
  }
@@ -2645,7 +2645,7 @@ function isPermissionError(e) {
2645
2645
  return code === "EACCES" || code === "EPERM" || code === "EROFS";
2646
2646
  }
2647
2647
  function embeddingsCached(root, modelId) {
2648
- return fs18.existsSync(path18.join(cacheDir(root), `embeddings-${safe(modelId)}.json`));
2648
+ return fs19.existsSync(path19.join(cacheDir(root), `embeddings-${safe(modelId)}.json`));
2649
2649
  }
2650
2650
  async function loadEmbedder(options = {}) {
2651
2651
  if (options.local) return null;
@@ -2659,7 +2659,7 @@ async function loadEmbedder(options = {}) {
2659
2659
  if (!mod?.FlagEmbedding) return fail("not-installed");
2660
2660
  const cache = modelCacheDir();
2661
2661
  try {
2662
- fs18.mkdirSync(cache, { recursive: true });
2662
+ fs19.mkdirSync(cache, { recursive: true });
2663
2663
  } catch (e) {
2664
2664
  return fail(isPermissionError(e) ? "no-permission" : "init-failed");
2665
2665
  }
@@ -2674,7 +2674,7 @@ async function loadEmbedder(options = {}) {
2674
2674
  "embedding model init timed out"
2675
2675
  );
2676
2676
  try {
2677
- fs18.writeFileSync(modelReadyMarker(modelId), (/* @__PURE__ */ new Date(0)).toISOString());
2677
+ fs19.writeFileSync(modelReadyMarker(modelId), (/* @__PURE__ */ new Date(0)).toISOString());
2678
2678
  } catch {
2679
2679
  }
2680
2680
  return {
@@ -2772,7 +2772,7 @@ function cosine(a, b) {
2772
2772
  var EMBED_CHUNK = 256;
2773
2773
  var CACHE_WRITE_INTERVAL_MS = 1500;
2774
2774
  function vectorCachePath(root, modelId) {
2775
- return path18.join(cacheDir(root), `embeddings-${safe(modelId)}.json`);
2775
+ return path19.join(cacheDir(root), `embeddings-${safe(modelId)}.json`);
2776
2776
  }
2777
2777
  function countPending(graph, root, modelId) {
2778
2778
  const entries = readCacheEntries(vectorCachePath(root, modelId), modelId);
@@ -2787,9 +2787,9 @@ function countPending(graph, root, modelId) {
2787
2787
  return pending;
2788
2788
  }
2789
2789
  function readCacheEntries(file, modelId) {
2790
- if (!fs18.existsSync(file)) return {};
2790
+ if (!fs19.existsSync(file)) return {};
2791
2791
  try {
2792
- const loaded = JSON.parse(fs18.readFileSync(file, "utf8"));
2792
+ const loaded = JSON.parse(fs19.readFileSync(file, "utf8"));
2793
2793
  if (loaded.model === modelId && loaded.entries) return loaded.entries;
2794
2794
  } catch {
2795
2795
  }
@@ -2798,9 +2798,9 @@ function readCacheEntries(file, modelId) {
2798
2798
  async function getNodeEmbeddings(graph, embedder, root, onProgress) {
2799
2799
  const file = vectorCachePath(root, embedder.id);
2800
2800
  let cache = { model: embedder.id, entries: {} };
2801
- if (fs18.existsSync(file)) {
2801
+ if (fs19.existsSync(file)) {
2802
2802
  try {
2803
- const loaded = JSON.parse(fs18.readFileSync(file, "utf8"));
2803
+ const loaded = JSON.parse(fs19.readFileSync(file, "utf8"));
2804
2804
  if (loaded.model === embedder.id && loaded.entries) cache = loaded;
2805
2805
  } catch {
2806
2806
  }
@@ -2818,10 +2818,10 @@ async function getNodeEmbeddings(graph, embedder, root, onProgress) {
2818
2818
  }
2819
2819
  const persist = () => {
2820
2820
  try {
2821
- fs18.mkdirSync(cacheDir(root), { recursive: true });
2821
+ fs19.mkdirSync(cacheDir(root), { recursive: true });
2822
2822
  const tmp = `${file}.tmp.${process.pid}`;
2823
- fs18.writeFileSync(tmp, JSON.stringify(cache));
2824
- fs18.renameSync(tmp, file);
2823
+ fs19.writeFileSync(tmp, JSON.stringify(cache));
2824
+ fs19.renameSync(tmp, file);
2825
2825
  } catch {
2826
2826
  }
2827
2827
  };
@@ -2859,21 +2859,21 @@ function safe(id) {
2859
2859
  }
2860
2860
  var SNAPSHOT_VERSION = "vg-freshness/1";
2861
2861
  function snapshotPath(root) {
2862
- return path18.join(cacheDir(root), "freshness.json");
2862
+ return path19.join(cacheDir(root), "freshness.json");
2863
2863
  }
2864
2864
  function writeSnapshot(root, corpusHash, fileStats, scope = {}) {
2865
2865
  const files = {};
2866
2866
  for (const f of fileStats) files[f.rel] = { size: f.size, mtimeMs: f.mtimeMs, hash: f.hash };
2867
2867
  const snapshot = { version: SNAPSHOT_VERSION, corpusHash, scope: pruneScope(scope), files };
2868
2868
  try {
2869
- fs18.mkdirSync(cacheDir(root), { recursive: true });
2870
- fs18.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2869
+ fs19.mkdirSync(cacheDir(root), { recursive: true });
2870
+ fs19.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2871
2871
  } catch {
2872
2872
  }
2873
2873
  }
2874
2874
  function loadSnapshot(root) {
2875
2875
  try {
2876
- const loaded = JSON.parse(fs18.readFileSync(snapshotPath(root), "utf8"));
2876
+ const loaded = JSON.parse(fs19.readFileSync(snapshotPath(root), "utf8"));
2877
2877
  if (loaded.version === SNAPSHOT_VERSION && loaded.files && typeof loaded.corpusHash === "string") {
2878
2878
  return { ...loaded, scope: loaded.scope ?? {} };
2879
2879
  }
@@ -2909,7 +2909,7 @@ function probeFreshness(root) {
2909
2909
  }
2910
2910
  let stat;
2911
2911
  try {
2912
- stat = fs18.statSync(file.abs);
2912
+ stat = fs19.statSync(file.abs);
2913
2913
  } catch {
2914
2914
  drift.removed.push(file.rel);
2915
2915
  continue;
@@ -2917,7 +2917,7 @@ function probeFreshness(root) {
2917
2917
  if (stat.size === entry.size && stat.mtimeMs === entry.mtimeMs) continue;
2918
2918
  let hash = "";
2919
2919
  try {
2920
- hash = hashBytes(fs18.readFileSync(file.abs));
2920
+ hash = hashBytes(fs19.readFileSync(file.abs));
2921
2921
  } catch {
2922
2922
  drift.removed.push(file.rel);
2923
2923
  continue;
@@ -2934,7 +2934,7 @@ function probeFreshness(root) {
2934
2934
  }
2935
2935
  if (absorbed && !hasDrift(drift)) {
2936
2936
  try {
2937
- fs18.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2937
+ fs19.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2938
2938
  } catch {
2939
2939
  }
2940
2940
  }
@@ -3005,6 +3005,15 @@ function push2(map, key, value) {
3005
3005
  if (list) list.push(value);
3006
3006
  else map.set(key, [value]);
3007
3007
  }
3008
+ var INDEX_CACHE = /* @__PURE__ */ new WeakMap();
3009
+ function indexFor(graph) {
3010
+ let idx = INDEX_CACHE.get(graph);
3011
+ if (!idx) {
3012
+ idx = new GraphIndex(graph);
3013
+ INDEX_CACHE.set(graph, idx);
3014
+ }
3015
+ return idx;
3016
+ }
3008
3017
 
3009
3018
  // src/engine/query.ts
3010
3019
  var STOPWORDS = /* @__PURE__ */ new Set([
@@ -3055,7 +3064,7 @@ function queryGraph(graph, question, options = {}) {
3055
3064
  const limit = options.limit ?? 12;
3056
3065
  const terms = tokenize(question);
3057
3066
  const weightOf = termWeights(graph, terms);
3058
- const index = new GraphIndex(graph);
3067
+ const index = indexFor(graph);
3059
3068
  const scored = [];
3060
3069
  for (const node of graph.nodes) {
3061
3070
  if (node.kind === "file" || node.kind === "external") continue;
@@ -3072,7 +3081,7 @@ async function queryGraphSemantic(graph, question, options) {
3072
3081
  const limit = options.limit ?? 12;
3073
3082
  const terms = tokenize(question);
3074
3083
  const weightOf = termWeights(graph, terms);
3075
- const index = new GraphIndex(graph);
3084
+ const index = indexFor(graph);
3076
3085
  const queryVec = await options.embedder.embedQuery(question);
3077
3086
  const lexRaw = /* @__PURE__ */ new Map();
3078
3087
  let lexMax = 0;
@@ -3215,7 +3224,7 @@ function round2(x) {
3215
3224
  }
3216
3225
  var REFRESH_LOCK_STALE_MS = 10 * 60 * 1e3;
3217
3226
  function refreshLockPath(root) {
3218
- return path18.join(cacheDir(root), "refresh.lock");
3227
+ return path19.join(cacheDir(root), "refresh.lock");
3219
3228
  }
3220
3229
  async function refreshIfStale(root, opts = {}) {
3221
3230
  const probe = probeFreshness(root);
@@ -3246,8 +3255,8 @@ async function refreshIfStale(root, opts = {}) {
3246
3255
  const dir = vibgrateDir(root);
3247
3256
  writeArtifacts(result.graph, {
3248
3257
  root,
3249
- report: fs18.existsSync(path18.join(dir, "GRAPH_REPORT.md")),
3250
- html: fs18.existsSync(path18.join(dir, "graph.html"))
3258
+ report: fs19.existsSync(path19.join(dir, "GRAPH_REPORT.md")),
3259
+ html: fs19.existsSync(path19.join(dir, "graph.html"))
3251
3260
  });
3252
3261
  }
3253
3262
  writeSnapshot(root, result.graph.provenance.corpusHash, result.fileStats, scope);
@@ -3266,6 +3275,157 @@ async function refreshIfStale(root, opts = {}) {
3266
3275
  releaseLock(lock);
3267
3276
  }
3268
3277
  }
3278
+ var LEDGER = "savings.jsonl";
3279
+ var PER_FILE_TOKENS = 400;
3280
+ var SAVINGS_TOOLS = /* @__PURE__ */ new Set(["query_graph", "get_node"]);
3281
+ var CLI_TOOL_ALIASES = {
3282
+ ask: "query_graph",
3283
+ show: "get_node",
3284
+ impact: "impact_of",
3285
+ path: "find_path",
3286
+ hubs: "list_hubs",
3287
+ areas: "list_areas",
3288
+ tree: "tree"
3289
+ };
3290
+ function sanitizeClient(name) {
3291
+ if (typeof name !== "string") return "unknown";
3292
+ const cleaned = name.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
3293
+ return cleaned || "unknown";
3294
+ }
3295
+ function recordCliCall(root, entry, now) {
3296
+ recordSaving(
3297
+ root,
3298
+ {
3299
+ tool: entry.tool,
3300
+ source: "cli",
3301
+ client: sanitizeClient(entry.client),
3302
+ outcome: entry.outcome,
3303
+ vgTokens: entry.vgTokens ?? 0,
3304
+ baselineTokens: (entry.baselineFiles ?? 0) * PER_FILE_TOKENS
3305
+ },
3306
+ now
3307
+ );
3308
+ }
3309
+ function ledgerPath(root) {
3310
+ return path19.join(cacheDir(root), LEDGER);
3311
+ }
3312
+ function savingsRecorded(root) {
3313
+ return fs19.existsSync(ledgerPath(root));
3314
+ }
3315
+ function recordSaving(root, entry, now) {
3316
+ try {
3317
+ fs19.mkdirSync(cacheDir(root), { recursive: true });
3318
+ const line = JSON.stringify({ ts: now, ...entry });
3319
+ fs19.appendFileSync(ledgerPath(root), line + "\n");
3320
+ } catch {
3321
+ }
3322
+ }
3323
+ var DEFAULT_RATE_PER_M = 3;
3324
+ var DEFAULT_RATE_LABEL = "input @ $3/1M";
3325
+ function readSavings(root, days, now, ratePerM = DEFAULT_RATE_PER_M) {
3326
+ const file = ledgerPath(root);
3327
+ const cutoff = now - days * 24 * 60 * 60 * 1e3;
3328
+ let queries = 0;
3329
+ let vgTokens = 0;
3330
+ let baselineTokens = 0;
3331
+ if (fs19.existsSync(file)) {
3332
+ for (const line of fs19.readFileSync(file, "utf8").split("\n")) {
3333
+ if (!line.trim()) continue;
3334
+ try {
3335
+ const e = JSON.parse(line);
3336
+ if (e.ts < cutoff) continue;
3337
+ if (!SAVINGS_TOOLS.has(e.tool)) continue;
3338
+ queries++;
3339
+ vgTokens += e.vgTokens;
3340
+ baselineTokens += e.baselineTokens;
3341
+ } catch {
3342
+ }
3343
+ }
3344
+ }
3345
+ const estCostVg = vgTokens / 1e6 * ratePerM;
3346
+ const estCostBaseline = baselineTokens / 1e6 * ratePerM;
3347
+ return {
3348
+ enabled: savingsRecorded(root),
3349
+ days,
3350
+ queries,
3351
+ vgTokens,
3352
+ baselineTokens,
3353
+ ratio: vgTokens > 0 ? Math.round(baselineTokens / vgTokens * 100) / 100 : 0,
3354
+ estCostVg: round22(estCostVg),
3355
+ estCostBaseline: round22(estCostBaseline),
3356
+ saved: round22(estCostBaseline - estCostVg),
3357
+ rateLabel: DEFAULT_RATE_LABEL
3358
+ };
3359
+ }
3360
+ function round22(x) {
3361
+ return Math.round(x * 100) / 100;
3362
+ }
3363
+ function toDimensionStats(byKey) {
3364
+ return [...byKey.entries()].map(([key, r]) => {
3365
+ const calls = r.complete + r.partial + r.miss;
3366
+ return {
3367
+ key,
3368
+ calls,
3369
+ complete: r.complete,
3370
+ partial: r.partial,
3371
+ miss: r.miss,
3372
+ successPct: calls ? Math.round((r.complete + r.partial) / calls * 100) : 0
3373
+ };
3374
+ }).sort((a, b) => b.calls - a.calls || a.key.localeCompare(b.key));
3375
+ }
3376
+ function readUsage(root, days, now) {
3377
+ const file = ledgerPath(root);
3378
+ const cutoff = now - days * 24 * 60 * 60 * 1e3;
3379
+ const byTool = /* @__PURE__ */ new Map();
3380
+ const bySource = /* @__PURE__ */ new Map();
3381
+ const byClient = /* @__PURE__ */ new Map();
3382
+ const bump2 = (m, key, outcome) => {
3383
+ const row = m.get(key) ?? { complete: 0, partial: 0, miss: 0 };
3384
+ row[outcome]++;
3385
+ m.set(key, row);
3386
+ };
3387
+ if (fs19.existsSync(file)) {
3388
+ for (const line of fs19.readFileSync(file, "utf8").split("\n")) {
3389
+ if (!line.trim()) continue;
3390
+ try {
3391
+ const e = JSON.parse(line);
3392
+ if (e.ts < cutoff) continue;
3393
+ const outcome = e.outcome ?? "complete";
3394
+ bump2(byTool, e.tool, outcome);
3395
+ bump2(bySource, e.source ?? "mcp", outcome);
3396
+ bump2(byClient, e.client ?? "unknown", outcome);
3397
+ } catch {
3398
+ }
3399
+ }
3400
+ }
3401
+ const commands = toDimensionStats(byTool).map((d) => ({
3402
+ tool: d.key,
3403
+ calls: d.calls,
3404
+ complete: d.complete,
3405
+ partial: d.partial,
3406
+ miss: d.miss,
3407
+ successPct: d.successPct
3408
+ }));
3409
+ const totals = commands.reduce(
3410
+ (t, c) => ({
3411
+ calls: t.calls + c.calls,
3412
+ complete: t.complete + c.complete,
3413
+ partial: t.partial + c.partial,
3414
+ miss: t.miss + c.miss
3415
+ }),
3416
+ { calls: 0, complete: 0, partial: 0, miss: 0 }
3417
+ );
3418
+ const avgSuccessPct = commands.length ? Math.round(commands.reduce((s, c) => s + c.successPct, 0) / commands.length) : 0;
3419
+ return {
3420
+ enabled: savingsRecorded(root),
3421
+ days,
3422
+ commands,
3423
+ sources: toDimensionStats(bySource),
3424
+ clients: toDimensionStats(byClient),
3425
+ totals,
3426
+ avgSuccessPct
3427
+ };
3428
+ }
3269
3429
 
3270
3430
  // src/engine/lookup.ts
3271
3431
  function findNodes(graph, query) {
@@ -3342,7 +3502,7 @@ function shortestPath(graph, srcId, dstId) {
3342
3502
  var DEPEND_EDGES = /* @__PURE__ */ new Set(["call", "import", "extends", "implements", "references"]);
3343
3503
  function impactOf(graph, rootId, opts = {}) {
3344
3504
  const maxDepth = Math.max(1, opts.depth ?? 4);
3345
- const index = new GraphIndex(graph);
3505
+ const index = indexFor(graph);
3346
3506
  const root = index.node(rootId);
3347
3507
  const affected = /* @__PURE__ */ new Map();
3348
3508
  let minEdgeConfidence = 1;
@@ -3389,7 +3549,7 @@ function round3(x) {
3389
3549
  return Math.round(x * 1e6) / 1e6;
3390
3550
  }
3391
3551
  function coveringTests(graph, node, index) {
3392
- const idx = index ?? new GraphIndex(graph);
3552
+ const idx = index ?? indexFor(graph);
3393
3553
  const byId = idx.nodeById;
3394
3554
  const out = /* @__PURE__ */ new Map();
3395
3555
  for (const e of idx.in(node.id, "test")) {
@@ -3403,7 +3563,7 @@ function coveringTests(graph, node, index) {
3403
3563
  return [...out.values()].sort((a, b) => b.confidence - a.confidence || a.file.localeCompare(b.file));
3404
3564
  }
3405
3565
  function testsToRun(graph, rootId, depth = 4) {
3406
- const idx = new GraphIndex(graph);
3566
+ const idx = indexFor(graph);
3407
3567
  const impact = impactOf(graph, rootId, { depth });
3408
3568
  const affectedIds = /* @__PURE__ */ new Set([rootId, ...impact.affected.map((a) => a.id)]);
3409
3569
  const testFiles = /* @__PURE__ */ new Set();
@@ -3423,10 +3583,10 @@ function testsToRun(graph, rootId, depth = 4) {
3423
3583
  };
3424
3584
  }
3425
3585
  function detectRunner(root, lang) {
3426
- const pkgPath = path18.join(root, "package.json");
3427
- if (fs18.existsSync(pkgPath)) {
3586
+ const pkgPath = path19.join(root, "package.json");
3587
+ if (fs19.existsSync(pkgPath)) {
3428
3588
  try {
3429
- const pkg = JSON.parse(fs18.readFileSync(pkgPath, "utf8"));
3589
+ const pkg = JSON.parse(fs19.readFileSync(pkgPath, "utf8"));
3430
3590
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
3431
3591
  if (deps.vitest) return { name: "vitest", command: (f) => `npx vitest run ${f.join(" ")}`.trim() };
3432
3592
  if (deps.jest) return { name: "jest", command: (f) => `npx jest ${f.join(" ")}`.trim() };
@@ -3434,10 +3594,10 @@ function detectRunner(root, lang) {
3434
3594
  } catch {
3435
3595
  }
3436
3596
  }
3437
- if (lang === "py" || fs18.existsSync(path18.join(root, "pyproject.toml")) || fs18.existsSync(path18.join(root, "pytest.ini"))) {
3597
+ if (lang === "py" || fs19.existsSync(path19.join(root, "pyproject.toml")) || fs19.existsSync(path19.join(root, "pytest.ini"))) {
3438
3598
  return { name: "pytest", command: (f) => `pytest ${f.join(" ")}`.trim() };
3439
3599
  }
3440
- if (lang === "go" || fs18.existsSync(path18.join(root, "go.mod"))) {
3600
+ if (lang === "go" || fs19.existsSync(path19.join(root, "go.mod"))) {
3441
3601
  return { name: "go test", command: () => "go test ./..." };
3442
3602
  }
3443
3603
  if (lang === "cs" || hasFile(root, /\.csproj$/) || hasFile(root, /\.sln$/)) {
@@ -3448,7 +3608,7 @@ function detectRunner(root, lang) {
3448
3608
  }
3449
3609
  function hasFile(root, re) {
3450
3610
  try {
3451
- return fs18.readdirSync(root).some((f) => re.test(f));
3611
+ return fs19.readdirSync(root).some((f) => re.test(f));
3452
3612
  } catch {
3453
3613
  return false;
3454
3614
  }
@@ -3520,7 +3680,7 @@ function findManifests(root) {
3520
3680
  if (depth > 8 || scanned > MAX_ENTRIES) return;
3521
3681
  let entries;
3522
3682
  try {
3523
- entries = fs18.readdirSync(dir, { withFileTypes: true });
3683
+ entries = fs19.readdirSync(dir, { withFileTypes: true });
3524
3684
  } catch {
3525
3685
  return;
3526
3686
  }
@@ -3528,11 +3688,11 @@ function findManifests(root) {
3528
3688
  scanned++;
3529
3689
  if (scanned > MAX_ENTRIES) break;
3530
3690
  if (e.isDirectory()) {
3531
- if (!SKIP_DIRS2.has(e.name) && !e.name.startsWith(".")) walk2(path18.join(dir, e.name), depth + 1);
3691
+ if (!SKIP_DIRS2.has(e.name) && !e.name.startsWith(".")) walk2(path19.join(dir, e.name), depth + 1);
3532
3692
  continue;
3533
3693
  }
3534
3694
  const eco = MANIFEST_BY_FILE[e.name] ?? (/\.(cs|fs)proj$/i.test(e.name) ? "dotnet" : void 0);
3535
- if (eco) set[eco].push(path18.join(dir, e.name));
3695
+ if (eco) set[eco].push(path19.join(dir, e.name));
3536
3696
  }
3537
3697
  };
3538
3698
  walk2(root, 0);
@@ -3544,11 +3704,11 @@ function npmDeps(files) {
3544
3704
  for (const file of files) {
3545
3705
  let pkg;
3546
3706
  try {
3547
- pkg = JSON.parse(fs18.readFileSync(file, "utf8"));
3707
+ pkg = JSON.parse(fs19.readFileSync(file, "utf8"));
3548
3708
  } catch {
3549
3709
  continue;
3550
3710
  }
3551
- const dir = path18.dirname(file);
3711
+ const dir = path19.dirname(file);
3552
3712
  const all = { ...pkg.dependencies, ...pkg.devDependencies };
3553
3713
  for (const [name, declared] of Object.entries(all)) {
3554
3714
  out.push({ name, ecosystem: "npm", declared, installed: installedNpmVersion(dir, name) });
@@ -3559,12 +3719,12 @@ function npmDeps(files) {
3559
3719
  function installedNpmVersion(dir, name) {
3560
3720
  let cur = dir;
3561
3721
  for (let i = 0; i < 12; i++) {
3562
- const p = path18.join(cur, "node_modules", name, "package.json");
3722
+ const p = path19.join(cur, "node_modules", name, "package.json");
3563
3723
  try {
3564
- return JSON.parse(fs18.readFileSync(p, "utf8")).version;
3724
+ return JSON.parse(fs19.readFileSync(p, "utf8")).version;
3565
3725
  } catch {
3566
3726
  }
3567
- const parent = path18.dirname(cur);
3727
+ const parent = path19.dirname(cur);
3568
3728
  if (parent === cur) break;
3569
3729
  cur = parent;
3570
3730
  }
@@ -3581,21 +3741,21 @@ function normalizePep503(name) {
3581
3741
  function sitePackagesDirs(base) {
3582
3742
  const out = [];
3583
3743
  try {
3584
- for (const d of fs18.readdirSync(path18.join(base, "lib"))) {
3585
- if (d.startsWith("python")) out.push(path18.join(base, "lib", d, "site-packages"));
3744
+ for (const d of fs19.readdirSync(path19.join(base, "lib"))) {
3745
+ if (d.startsWith("python")) out.push(path19.join(base, "lib", d, "site-packages"));
3586
3746
  }
3587
3747
  } catch {
3588
3748
  }
3589
- out.push(path18.join(base, "Lib", "site-packages"));
3749
+ out.push(path19.join(base, "Lib", "site-packages"));
3590
3750
  return out;
3591
3751
  }
3592
3752
  function installedPypiVersion(root, name) {
3593
3753
  const target = normalizePep503(name);
3594
3754
  for (const venv of [".venv", "venv", "env", ".tox"]) {
3595
- for (const sp of sitePackagesDirs(path18.join(root, venv))) {
3755
+ for (const sp of sitePackagesDirs(path19.join(root, venv))) {
3596
3756
  let entries;
3597
3757
  try {
3598
- entries = fs18.readdirSync(sp);
3758
+ entries = fs19.readdirSync(sp);
3599
3759
  } catch {
3600
3760
  continue;
3601
3761
  }
@@ -3610,7 +3770,7 @@ function installedPypiVersion(root, name) {
3610
3770
  function installedPhpVersion(root, name) {
3611
3771
  let data;
3612
3772
  try {
3613
- data = JSON.parse(fs18.readFileSync(path18.join(root, "vendor", "composer", "installed.json"), "utf8"));
3773
+ data = JSON.parse(fs19.readFileSync(path19.join(root, "vendor", "composer", "installed.json"), "utf8"));
3614
3774
  } catch {
3615
3775
  return void 0;
3616
3776
  }
@@ -3629,11 +3789,11 @@ function pypiDeps(files) {
3629
3789
  for (const file of files) {
3630
3790
  let text;
3631
3791
  try {
3632
- text = fs18.readFileSync(file, "utf8");
3792
+ text = fs19.readFileSync(file, "utf8");
3633
3793
  } catch {
3634
3794
  continue;
3635
3795
  }
3636
- if (path18.basename(file) === "requirements.txt") {
3796
+ if (path19.basename(file) === "requirements.txt") {
3637
3797
  for (const line of text.split("\n")) {
3638
3798
  const t = line.trim();
3639
3799
  if (!t || t.startsWith("#") || t.startsWith("-")) continue;
@@ -3717,7 +3877,7 @@ function goDeps(files) {
3717
3877
  for (const mod of files) {
3718
3878
  let text;
3719
3879
  try {
3720
- text = fs18.readFileSync(mod, "utf8");
3880
+ text = fs19.readFileSync(mod, "utf8");
3721
3881
  } catch {
3722
3882
  continue;
3723
3883
  }
@@ -3735,7 +3895,7 @@ function cargoDeps(files) {
3735
3895
  for (const file of files) {
3736
3896
  let text;
3737
3897
  try {
3738
- text = fs18.readFileSync(file, "utf8");
3898
+ text = fs19.readFileSync(file, "utf8");
3739
3899
  } catch {
3740
3900
  continue;
3741
3901
  }
@@ -3767,7 +3927,7 @@ function rubyDeps(files) {
3767
3927
  for (const file of files) {
3768
3928
  let text;
3769
3929
  try {
3770
- text = fs18.readFileSync(file, "utf8");
3930
+ text = fs19.readFileSync(file, "utf8");
3771
3931
  } catch {
3772
3932
  continue;
3773
3933
  }
@@ -3782,7 +3942,7 @@ function phpDeps(files) {
3782
3942
  for (const file of files) {
3783
3943
  let pkg;
3784
3944
  try {
3785
- pkg = JSON.parse(fs18.readFileSync(file, "utf8"));
3945
+ pkg = JSON.parse(fs19.readFileSync(file, "utf8"));
3786
3946
  } catch {
3787
3947
  continue;
3788
3948
  }
@@ -3799,7 +3959,7 @@ function dotnetDeps(files) {
3799
3959
  for (const file of files) {
3800
3960
  let text;
3801
3961
  try {
3802
- text = fs18.readFileSync(file, "utf8");
3962
+ text = fs19.readFileSync(file, "utf8");
3803
3963
  } catch {
3804
3964
  continue;
3805
3965
  }
@@ -3819,7 +3979,7 @@ function swiftDeps(files) {
3819
3979
  for (const file of files) {
3820
3980
  let text;
3821
3981
  try {
3822
- text = fs18.readFileSync(file, "utf8");
3982
+ text = fs19.readFileSync(file, "utf8");
3823
3983
  } catch {
3824
3984
  continue;
3825
3985
  }
@@ -3841,7 +4001,7 @@ function dartDeps(files) {
3841
4001
  for (const file of files) {
3842
4002
  let text;
3843
4003
  try {
3844
- text = fs18.readFileSync(file, "utf8");
4004
+ text = fs19.readFileSync(file, "utf8");
3845
4005
  } catch {
3846
4006
  continue;
3847
4007
  }
@@ -3866,11 +4026,11 @@ function javaDeps(files) {
3866
4026
  for (const file of files) {
3867
4027
  let text;
3868
4028
  try {
3869
- text = fs18.readFileSync(file, "utf8");
4029
+ text = fs19.readFileSync(file, "utf8");
3870
4030
  } catch {
3871
4031
  continue;
3872
4032
  }
3873
- if (path18.basename(file) === "pom.xml") {
4033
+ if (path19.basename(file) === "pom.xml") {
3874
4034
  for (const block of text.match(/<dependency>[\s\S]*?<\/dependency>/g) ?? []) {
3875
4035
  const g = /<groupId>([^<]+)<\/groupId>/.exec(block);
3876
4036
  const a = /<artifactId>([^<]+)<\/artifactId>/.exec(block);
@@ -3916,10 +4076,10 @@ function discoverModels(home = os.homedir()) {
3916
4076
  );
3917
4077
  }
3918
4078
  function ollama(home) {
3919
- const base = path18.join(home, ".ollama", "models", "manifests");
4079
+ const base = path19.join(home, ".ollama", "models", "manifests");
3920
4080
  const out = [];
3921
4081
  walk(base, 4, (file) => {
3922
- const rel2 = path18.relative(base, file).split(path18.sep);
4082
+ const rel2 = path19.relative(base, file).split(path19.sep);
3923
4083
  if (rel2.length >= 2) {
3924
4084
  const tag = rel2[rel2.length - 1];
3925
4085
  const model = rel2[rel2.length - 2];
@@ -3929,24 +4089,24 @@ function ollama(home) {
3929
4089
  return dedupe(out);
3930
4090
  }
3931
4091
  function lmStudio(home) {
3932
- const bases = [path18.join(home, ".lmstudio", "models"), path18.join(home, ".cache", "lm-studio", "models")];
4092
+ const bases = [path19.join(home, ".lmstudio", "models"), path19.join(home, ".cache", "lm-studio", "models")];
3933
4093
  const out = [];
3934
4094
  for (const base of bases) {
3935
4095
  walk(base, 5, (file) => {
3936
4096
  if (!file.endsWith(".gguf")) return;
3937
- const rel2 = path18.relative(base, file).replace(/\\/g, "/");
4097
+ const rel2 = path19.relative(base, file).replace(/\\/g, "/");
3938
4098
  out.push({ runtime: "lm-studio", name: rel2, path: file });
3939
4099
  });
3940
4100
  }
3941
4101
  return dedupe(out);
3942
4102
  }
3943
4103
  function looseGguf(home) {
3944
- const bases = [path18.join(home, "models"), path18.join(home, ".cache", "huggingface")];
4104
+ const bases = [path19.join(home, "models"), path19.join(home, ".cache", "huggingface")];
3945
4105
  const out = [];
3946
4106
  for (const base of bases) {
3947
4107
  walk(base, 4, (file) => {
3948
4108
  if (!file.endsWith(".gguf")) return;
3949
- out.push({ runtime: "gguf", name: path18.basename(file), path: file });
4109
+ out.push({ runtime: "gguf", name: path19.basename(file), path: file });
3950
4110
  });
3951
4111
  }
3952
4112
  return dedupe(out);
@@ -3955,12 +4115,12 @@ function walk(dir, depth, onFile) {
3955
4115
  if (depth < 0) return;
3956
4116
  let entries;
3957
4117
  try {
3958
- entries = fs18.readdirSync(dir, { withFileTypes: true });
4118
+ entries = fs19.readdirSync(dir, { withFileTypes: true });
3959
4119
  } catch {
3960
4120
  return;
3961
4121
  }
3962
4122
  for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
3963
- const abs = path18.join(dir, e.name);
4123
+ const abs = path19.join(dir, e.name);
3964
4124
  if (e.isDirectory()) walk(abs, depth - 1, onFile);
3965
4125
  else if (e.isFile()) onFile(abs);
3966
4126
  }
@@ -3991,7 +4151,7 @@ function lockfileVersion(root, ecosystem, name) {
3991
4151
  function gradleLock(root, name) {
3992
4152
  let text;
3993
4153
  try {
3994
- text = fs18.readFileSync(path18.join(root, "gradle.lockfile"), "utf8");
4154
+ text = fs19.readFileSync(path19.join(root, "gradle.lockfile"), "utf8");
3995
4155
  } catch {
3996
4156
  return void 0;
3997
4157
  }
@@ -4001,7 +4161,7 @@ function gradleLock(root, name) {
4001
4161
  function packageResolved(root, name) {
4002
4162
  let data;
4003
4163
  try {
4004
- data = JSON.parse(fs18.readFileSync(path18.join(root, "Package.resolved"), "utf8"));
4164
+ data = JSON.parse(fs19.readFileSync(path19.join(root, "Package.resolved"), "utf8"));
4005
4165
  } catch {
4006
4166
  return void 0;
4007
4167
  }
@@ -4017,7 +4177,7 @@ function packageResolved(root, name) {
4017
4177
  function pubspecLock(root, name) {
4018
4178
  let text;
4019
4179
  try {
4020
- text = fs18.readFileSync(path18.join(root, "pubspec.lock"), "utf8");
4180
+ text = fs19.readFileSync(path19.join(root, "pubspec.lock"), "utf8");
4021
4181
  } catch {
4022
4182
  return void 0;
4023
4183
  }
@@ -4041,7 +4201,7 @@ function escapeRegExp(s) {
4041
4201
  function gemfileLock(root, name) {
4042
4202
  let text;
4043
4203
  try {
4044
- text = fs18.readFileSync(path18.join(root, "Gemfile.lock"), "utf8");
4204
+ text = fs19.readFileSync(path19.join(root, "Gemfile.lock"), "utf8");
4045
4205
  } catch {
4046
4206
  return void 0;
4047
4207
  }
@@ -4051,7 +4211,7 @@ function gemfileLock(root, name) {
4051
4211
  function composerLock(root, name) {
4052
4212
  let data;
4053
4213
  try {
4054
- data = JSON.parse(fs18.readFileSync(path18.join(root, "composer.lock"), "utf8"));
4214
+ data = JSON.parse(fs19.readFileSync(path19.join(root, "composer.lock"), "utf8"));
4055
4215
  } catch {
4056
4216
  return void 0;
4057
4217
  }
@@ -4066,7 +4226,7 @@ function composerLock(root, name) {
4066
4226
  function packagesLock(root, name) {
4067
4227
  let data;
4068
4228
  try {
4069
- data = JSON.parse(fs18.readFileSync(path18.join(root, "packages.lock.json"), "utf8"));
4229
+ data = JSON.parse(fs19.readFileSync(path19.join(root, "packages.lock.json"), "utf8"));
4070
4230
  } catch {
4071
4231
  return void 0;
4072
4232
  }
@@ -4091,7 +4251,7 @@ function pypiLockVersion(root, name) {
4091
4251
  function tomlPackageLock(root, file, name, normalize3) {
4092
4252
  let text;
4093
4253
  try {
4094
- text = fs18.readFileSync(path18.join(root, file), "utf8");
4254
+ text = fs19.readFileSync(path19.join(root, file), "utf8");
4095
4255
  } catch {
4096
4256
  return void 0;
4097
4257
  }
@@ -4108,7 +4268,7 @@ function tomlPackageLock(root, file, name, normalize3) {
4108
4268
  function pipfileLock(root, name) {
4109
4269
  let data;
4110
4270
  try {
4111
- data = JSON.parse(fs18.readFileSync(path18.join(root, "Pipfile.lock"), "utf8"));
4271
+ data = JSON.parse(fs19.readFileSync(path19.join(root, "Pipfile.lock"), "utf8"));
4112
4272
  } catch {
4113
4273
  return void 0;
4114
4274
  }
@@ -4127,7 +4287,7 @@ function pipfileLock(root, name) {
4127
4287
  function packageLockVersion(root, name) {
4128
4288
  let data;
4129
4289
  try {
4130
- data = JSON.parse(fs18.readFileSync(path18.join(root, "package-lock.json"), "utf8"));
4290
+ data = JSON.parse(fs19.readFileSync(path19.join(root, "package-lock.json"), "utf8"));
4131
4291
  } catch {
4132
4292
  return void 0;
4133
4293
  }
@@ -4139,7 +4299,7 @@ function packageLockVersion(root, name) {
4139
4299
  function yarnLockVersion(root, name) {
4140
4300
  let text;
4141
4301
  try {
4142
- text = fs18.readFileSync(path18.join(root, "yarn.lock"), "utf8");
4302
+ text = fs19.readFileSync(path19.join(root, "yarn.lock"), "utf8");
4143
4303
  } catch {
4144
4304
  return void 0;
4145
4305
  }
@@ -4167,19 +4327,19 @@ function headerNamesPackage(header, name) {
4167
4327
  // src/engine/lib.ts
4168
4328
  var LIB_SCHEMA = "vg-lib/1.0";
4169
4329
  function catalogPath(root) {
4170
- return path18.join(root, "vibgrate.lib.json");
4330
+ return path19.join(root, "vibgrate.lib.json");
4171
4331
  }
4172
4332
  function libDir(root) {
4173
- return path18.join(root, ".vibgrate", "lib");
4333
+ return path19.join(root, ".vibgrate", "lib");
4174
4334
  }
4175
4335
  function libId(name) {
4176
4336
  return name.trim().toLowerCase().replace(/^@/, "").replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
4177
4337
  }
4178
4338
  function loadCatalog(root) {
4179
4339
  const file = catalogPath(root);
4180
- if (fs18.existsSync(file)) {
4340
+ if (fs19.existsSync(file)) {
4181
4341
  try {
4182
- const data = JSON.parse(fs18.readFileSync(file, "utf8"));
4342
+ const data = JSON.parse(fs19.readFileSync(file, "utf8"));
4183
4343
  if (data.schemaVersion === LIB_SCHEMA && data.libraries) return data;
4184
4344
  } catch {
4185
4345
  }
@@ -4187,7 +4347,7 @@ function loadCatalog(root) {
4187
4347
  return { schemaVersion: LIB_SCHEMA, libraries: {} };
4188
4348
  }
4189
4349
  function saveCatalog(root, catalog) {
4190
- fs18.writeFileSync(catalogPath(root), `${stableStringify(catalog, 2)}
4350
+ fs19.writeFileSync(catalogPath(root), `${stableStringify(catalog, 2)}
4191
4351
  `);
4192
4352
  }
4193
4353
  function resolveLib(catalog, name) {
@@ -4263,23 +4423,23 @@ async function addLibrary(source, opts) {
4263
4423
  content = await res.text();
4264
4424
  type = source.endsWith("llms.txt") ? "llms.txt" : source.endsWith(".json") ? "openapi" : "website";
4265
4425
  } else {
4266
- const abs = path18.resolve(root, source);
4267
- if (!fs18.existsSync(abs)) throw new Error(`source not found: ${source}`);
4426
+ const abs = path19.resolve(root, source);
4427
+ if (!fs19.existsSync(abs)) throw new Error(`source not found: ${source}`);
4268
4428
  content = readLocal(abs);
4269
4429
  type = "local";
4270
4430
  }
4271
4431
  const name = opts.name ?? inferName(source);
4272
4432
  const id = libId(name);
4273
4433
  const version = opts.version ?? installedVersion(root, name) ?? "*";
4274
- fs18.mkdirSync(libDir(root), { recursive: true });
4275
- const docFileAbs = path18.join(libDir(root), `${id}.md`);
4276
- fs18.writeFileSync(docFileAbs, content);
4434
+ fs19.mkdirSync(libDir(root), { recursive: true });
4435
+ const docFileAbs = path19.join(libDir(root), `${id}.md`);
4436
+ fs19.writeFileSync(docFileAbs, content);
4277
4437
  const entry = {
4278
4438
  id,
4279
4439
  name,
4280
4440
  version,
4281
4441
  source: { type, location: source },
4282
- docFile: path18.relative(root, docFileAbs).split(path18.sep).join("/"),
4442
+ docFile: path19.relative(root, docFileAbs).split(path19.sep).join("/"),
4283
4443
  docHash: hashString(content),
4284
4444
  bytes: Buffer.byteLength(content, "utf8")
4285
4445
  };
@@ -4289,15 +4449,15 @@ async function addLibrary(source, opts) {
4289
4449
  return entry;
4290
4450
  }
4291
4451
  function readDoc(root, entry) {
4292
- const abs = path18.resolve(root, entry.docFile);
4293
- return fs18.existsSync(abs) ? fs18.readFileSync(abs, "utf8") : "";
4452
+ const abs = path19.resolve(root, entry.docFile);
4453
+ return fs19.existsSync(abs) ? fs19.readFileSync(abs, "utf8") : "";
4294
4454
  }
4295
4455
  function npmPackageDir(root, name) {
4296
4456
  let cur = root;
4297
4457
  for (let i = 0; i < 12; i++) {
4298
- const dir = path18.join(cur, "node_modules", name);
4299
- if (fs18.existsSync(path18.join(dir, "package.json"))) return dir;
4300
- const parent = path18.dirname(cur);
4458
+ const dir = path19.join(cur, "node_modules", name);
4459
+ if (fs19.existsSync(path19.join(dir, "package.json"))) return dir;
4460
+ const parent = path19.dirname(cur);
4301
4461
  if (parent === cur) break;
4302
4462
  cur = parent;
4303
4463
  }
@@ -4308,24 +4468,24 @@ function localPackageDocs(root, name) {
4308
4468
  if (!dir) return void 0;
4309
4469
  const version = resolveVersion(root, name).served;
4310
4470
  for (const f of ["llms.txt", "README.md", "README.mdx", "README", "readme.md"]) {
4311
- const p = path18.join(dir, f);
4471
+ const p = path19.join(dir, f);
4312
4472
  try {
4313
- const docs = fs18.readFileSync(p, "utf8");
4473
+ const docs = fs19.readFileSync(p, "utf8");
4314
4474
  if (docs.trim()) {
4315
- return { docs, version, source: path18.relative(root, p).split(path18.sep).join("/") };
4475
+ return { docs, version, source: path19.relative(root, p).split(path19.sep).join("/") };
4316
4476
  }
4317
4477
  } catch {
4318
4478
  }
4319
4479
  }
4320
4480
  try {
4321
- const pkg = JSON.parse(fs18.readFileSync(path18.join(dir, "package.json"), "utf8"));
4481
+ const pkg = JSON.parse(fs19.readFileSync(path19.join(dir, "package.json"), "utf8"));
4322
4482
  if (pkg.description?.trim()) {
4323
4483
  return {
4324
4484
  docs: `# ${name}
4325
4485
 
4326
4486
  ${pkg.description}`,
4327
4487
  version: version ?? pkg.version,
4328
- source: path18.relative(root, path18.join(dir, "package.json")).split(path18.sep).join("/")
4488
+ source: path19.relative(root, path19.join(dir, "package.json")).split(path19.sep).join("/")
4329
4489
  };
4330
4490
  }
4331
4491
  } catch {
@@ -4334,17 +4494,17 @@ ${pkg.description}`,
4334
4494
  }
4335
4495
  function dtsEntry(dir) {
4336
4496
  try {
4337
- const pkg = JSON.parse(fs18.readFileSync(path18.join(dir, "package.json"), "utf8"));
4497
+ const pkg = JSON.parse(fs19.readFileSync(path19.join(dir, "package.json"), "utf8"));
4338
4498
  const rel2 = pkg.types ?? pkg.typings;
4339
4499
  if (typeof rel2 === "string") {
4340
- const p = path18.join(dir, rel2);
4341
- if (fs18.existsSync(p)) return p;
4500
+ const p = path19.join(dir, rel2);
4501
+ if (fs19.existsSync(p)) return p;
4342
4502
  }
4343
4503
  } catch {
4344
4504
  }
4345
4505
  for (const c of ["index.d.ts", "dist/index.d.ts", "lib/index.d.ts", "types/index.d.ts"]) {
4346
- const p = path18.join(dir, c);
4347
- if (fs18.existsSync(p)) return p;
4506
+ const p = path19.join(dir, c);
4507
+ if (fs19.existsSync(p)) return p;
4348
4508
  }
4349
4509
  return void 0;
4350
4510
  }
@@ -4377,7 +4537,7 @@ function localApiSurface(root, name) {
4377
4537
  if (!dts) return void 0;
4378
4538
  let src;
4379
4539
  try {
4380
- src = fs18.readFileSync(dts, "utf8");
4540
+ src = fs19.readFileSync(dts, "utf8");
4381
4541
  } catch {
4382
4542
  return void 0;
4383
4543
  }
@@ -4385,36 +4545,36 @@ function localApiSurface(root, name) {
4385
4545
  return decls.length ? decls.join("\n\n") : void 0;
4386
4546
  }
4387
4547
  function cloneAndReadGit(url) {
4388
- const dir = fs18.mkdtempSync(path18.join(os.tmpdir(), "vg-lib-git-"));
4548
+ const dir = fs19.mkdtempSync(path19.join(os.tmpdir(), "vg-lib-git-"));
4389
4549
  try {
4390
4550
  execFileSync("git", ["clone", "--depth", "1", "--quiet", url, dir], {
4391
4551
  stdio: ["ignore", "ignore", "pipe"]
4392
4552
  });
4393
4553
  } catch (err) {
4394
- fs18.rmSync(dir, { recursive: true, force: true });
4554
+ fs19.rmSync(dir, { recursive: true, force: true });
4395
4555
  const detail = err instanceof Error && err.message ? `: ${err.message.split("\n")[0]}` : "";
4396
4556
  throw new Error(`git clone failed for ${url}${detail}`);
4397
4557
  }
4398
4558
  try {
4399
4559
  return readGitDocs(dir);
4400
4560
  } finally {
4401
- fs18.rmSync(dir, { recursive: true, force: true });
4561
+ fs19.rmSync(dir, { recursive: true, force: true });
4402
4562
  }
4403
4563
  }
4404
4564
  function readGitDocs(dir) {
4405
- const docsDir = path18.join(dir, "docs");
4406
- if (fs18.existsSync(docsDir) && fs18.statSync(docsDir).isDirectory()) return readLocal(docsDir);
4407
- const readme = ["README.md", "README.mdx", "README.txt", "README.rst", "readme.md"].map((f) => path18.join(dir, f)).find((p) => fs18.existsSync(p));
4408
- if (readme) return fs18.readFileSync(readme, "utf8");
4565
+ const docsDir = path19.join(dir, "docs");
4566
+ if (fs19.existsSync(docsDir) && fs19.statSync(docsDir).isDirectory()) return readLocal(docsDir);
4567
+ const readme = ["README.md", "README.mdx", "README.txt", "README.rst", "readme.md"].map((f) => path19.join(dir, f)).find((p) => fs19.existsSync(p));
4568
+ if (readme) return fs19.readFileSync(readme, "utf8");
4409
4569
  return readLocal(dir);
4410
4570
  }
4411
4571
  function readLocal(abs) {
4412
- const stat = fs18.statSync(abs);
4413
- if (stat.isFile()) return fs18.readFileSync(abs, "utf8");
4572
+ const stat = fs19.statSync(abs);
4573
+ if (stat.isFile()) return fs19.readFileSync(abs, "utf8");
4414
4574
  const parts = [];
4415
- const files = fs18.readdirSync(abs, { withFileTypes: true }).filter((e) => e.isFile() && /\.(md|mdx|txt|rst)$/i.test(e.name)).map((e) => e.name).sort();
4575
+ const files = fs19.readdirSync(abs, { withFileTypes: true }).filter((e) => e.isFile() && /\.(md|mdx|txt|rst)$/i.test(e.name)).map((e) => e.name).sort();
4416
4576
  for (const f of files) parts.push(`<!-- ${f} -->
4417
- ${fs18.readFileSync(path18.join(abs, f), "utf8")}`);
4577
+ ${fs19.readFileSync(path19.join(abs, f), "utf8")}`);
4418
4578
  return parts.join("\n\n");
4419
4579
  }
4420
4580
  function inferName(source) {
@@ -4423,10 +4583,10 @@ function inferName(source) {
4423
4583
  return base.replace(/\.(md|mdx|txt|rst|json)$/i, "").replace(/^llms$/, "docs");
4424
4584
  }
4425
4585
  function findGitRoot(startDir = process.cwd()) {
4426
- let dir = path18.resolve(startDir);
4586
+ let dir = path19.resolve(startDir);
4427
4587
  for (; ; ) {
4428
- if (fs18.existsSync(path18.join(dir, ".git"))) return dir;
4429
- const parent = path18.dirname(dir);
4588
+ if (fs19.existsSync(path19.join(dir, ".git"))) return dir;
4589
+ const parent = path19.dirname(dir);
4430
4590
  if (parent === dir) return null;
4431
4591
  dir = parent;
4432
4592
  }
@@ -4437,19 +4597,19 @@ function normalizeEntry(line) {
4437
4597
  function ensureGitignored(entry, startDir = process.cwd()) {
4438
4598
  const root = findGitRoot(startDir);
4439
4599
  if (!root) return { status: "not-a-repo", entry };
4440
- const gitignorePath = path18.join(root, ".gitignore");
4600
+ const gitignorePath = path19.join(root, ".gitignore");
4441
4601
  const target = normalizeEntry(entry);
4442
4602
  let existing = "";
4443
4603
  let fileExisted = true;
4444
4604
  try {
4445
- existing = fs18.readFileSync(gitignorePath, "utf8");
4605
+ existing = fs19.readFileSync(gitignorePath, "utf8");
4446
4606
  } catch {
4447
4607
  fileExisted = false;
4448
4608
  }
4449
4609
  const alreadyPresent = existing.split(/\r?\n/).some((line) => normalizeEntry(line) === target);
4450
4610
  if (alreadyPresent) return { status: "present", entry, gitignorePath };
4451
4611
  const needsLeadingNewline = existing.length > 0 && !existing.endsWith("\n");
4452
- fs18.appendFileSync(gitignorePath, `${needsLeadingNewline ? "\n" : ""}${entry}
4612
+ fs19.appendFileSync(gitignorePath, `${needsLeadingNewline ? "\n" : ""}${entry}
4453
4613
  `, "utf8");
4454
4614
  return { status: fileExisted ? "added" : "created", entry, gitignorePath };
4455
4615
  }
@@ -4603,14 +4763,14 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
4603
4763
  console.log(chalk.dim("The secret must be registered on your Vibgrate ingest API."));
4604
4764
  }
4605
4765
  if (opts.write) {
4606
- const writePath = path18.resolve(opts.write);
4766
+ const writePath = path19.resolve(opts.write);
4607
4767
  await writeTextFile(writePath, dsn + "\n");
4608
4768
  console.log("");
4609
4769
  console.log(chalk.green("\u2714") + ` DSN written to ${opts.write}`);
4610
- const root = findGitRoot(path18.dirname(writePath));
4611
- const rel2 = root ? path18.relative(root, writePath).split(path18.sep).join("/") : "";
4770
+ const root = findGitRoot(path19.dirname(writePath));
4771
+ const rel2 = root ? path19.relative(root, writePath).split(path19.sep).join("/") : "";
4612
4772
  if (root && rel2 && !rel2.startsWith("..")) {
4613
- const res = ensureGitignored(rel2, path18.dirname(writePath));
4773
+ const res = ensureGitignored(rel2, path19.dirname(writePath));
4614
4774
  if (res.status === "created") {
4615
4775
  console.log(chalk.green("\u2714") + ` Created .gitignore and ignored ${rel2}`);
4616
4776
  } else if (res.status === "added") {
@@ -4628,30 +4788,30 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
4628
4788
  var STORE_DIRNAME = ".vibgrate";
4629
4789
  var STORE_FILENAME = "credentials.json";
4630
4790
  function homeCredentialsPath() {
4631
- return path18.join(os.homedir(), STORE_DIRNAME, STORE_FILENAME);
4791
+ return path19.join(os.homedir(), STORE_DIRNAME, STORE_FILENAME);
4632
4792
  }
4633
4793
  function projectCredentialsPath(cwd = process.cwd()) {
4634
4794
  const root = findGitRoot(cwd) ?? cwd;
4635
- return path18.join(root, STORE_DIRNAME, STORE_FILENAME);
4795
+ return path19.join(root, STORE_DIRNAME, STORE_FILENAME);
4636
4796
  }
4637
4797
  function credentialsPath(opts = {}) {
4638
4798
  const override = process.env.VIBGRATE_CREDENTIALS?.trim();
4639
- if (override) return path18.resolve(override);
4799
+ if (override) return path19.resolve(override);
4640
4800
  const local = projectCredentialsPath(opts.cwd);
4641
4801
  if (opts.local) return local;
4642
- if (fs18.existsSync(local)) return local;
4802
+ if (fs19.existsSync(local)) return local;
4643
4803
  return homeCredentialsPath();
4644
4804
  }
4645
4805
  function gitignoreEntryForCredentials(repoRoot, credsFile = credentialsPath()) {
4646
- const rel2 = path18.relative(repoRoot, credsFile);
4647
- if (rel2 && !rel2.startsWith("..") && !path18.isAbsolute(rel2)) {
4648
- return rel2.split(path18.sep).join("/");
4806
+ const rel2 = path19.relative(repoRoot, credsFile);
4807
+ if (rel2 && !rel2.startsWith("..") && !path19.isAbsolute(rel2)) {
4808
+ return rel2.split(path19.sep).join("/");
4649
4809
  }
4650
4810
  return `${STORE_DIRNAME}/${STORE_FILENAME}`;
4651
4811
  }
4652
4812
  function readStoredCredentials(opts = {}) {
4653
4813
  try {
4654
- const raw = fs18.readFileSync(credentialsPath(opts), "utf8");
4814
+ const raw = fs19.readFileSync(credentialsPath(opts), "utf8");
4655
4815
  const parsed = JSON.parse(raw);
4656
4816
  return parsed && typeof parsed.dsn === "string" && parsed.dsn ? parsed : null;
4657
4817
  } catch {
@@ -4660,16 +4820,16 @@ function readStoredCredentials(opts = {}) {
4660
4820
  }
4661
4821
  function writeStoredCredentials(creds, opts = {}) {
4662
4822
  const file = credentialsPath(opts);
4663
- fs18.mkdirSync(path18.dirname(file), { recursive: true });
4664
- fs18.writeFileSync(file, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf8", mode: 384 });
4823
+ fs19.mkdirSync(path19.dirname(file), { recursive: true });
4824
+ fs19.writeFileSync(file, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf8", mode: 384 });
4665
4825
  try {
4666
- fs18.chmodSync(file, 384);
4826
+ fs19.chmodSync(file, 384);
4667
4827
  } catch {
4668
4828
  }
4669
4829
  }
4670
4830
  function clearStoredCredentials(opts = {}) {
4671
4831
  try {
4672
- fs18.rmSync(credentialsPath(opts));
4832
+ fs19.rmSync(credentialsPath(opts));
4673
4833
  return true;
4674
4834
  } catch {
4675
4835
  return false;
@@ -5000,7 +5160,7 @@ var pushCommand = new Command("push").description("Push scan results to Vibgrate
5000
5160
  if (opts.strict) process.exit(1);
5001
5161
  return;
5002
5162
  }
5003
- const filePath = path18.resolve(opts.file);
5163
+ const filePath = path19.resolve(opts.file);
5004
5164
  if (!await pathExists(filePath)) {
5005
5165
  console.error(chalk.red(`Scan artifact not found: ${filePath}`));
5006
5166
  console.error(chalk.dim('Run "vibgrate scan" first.'));
@@ -5057,8 +5217,8 @@ var pushCommand = new Command("push").description("Push scan results to Vibgrate
5057
5217
  });
5058
5218
  function readScanArtifact(root) {
5059
5219
  try {
5060
- const file = path18.join(root, ".vibgrate", "scan_result.json");
5061
- return JSON.parse(fs18.readFileSync(file, "utf8"));
5220
+ const file = path19.join(root, ".vibgrate", "scan_result.json");
5221
+ return JSON.parse(fs19.readFileSync(file, "utf8"));
5062
5222
  } catch {
5063
5223
  return null;
5064
5224
  }
@@ -5570,14 +5730,14 @@ function scanFiles(root, needle, budget, seen, out) {
5570
5730
  const dir = stack.pop();
5571
5731
  let entries;
5572
5732
  try {
5573
- entries = fs18.readdirSync(dir, { withFileTypes: true });
5733
+ entries = fs19.readdirSync(dir, { withFileTypes: true });
5574
5734
  } catch {
5575
5735
  continue;
5576
5736
  }
5577
5737
  entries.sort((a, b) => a.name.localeCompare(b.name));
5578
5738
  for (const e of entries) {
5579
5739
  if (e.name.startsWith(".") || IGNORE_DIRS.has(e.name)) continue;
5580
- const abs = path18.join(dir, e.name);
5740
+ const abs = path19.join(dir, e.name);
5581
5741
  if (e.isDirectory()) {
5582
5742
  stack.push(abs);
5583
5743
  continue;
@@ -5585,13 +5745,13 @@ function scanFiles(root, needle, budget, seen, out) {
5585
5745
  if (++scanned > MAX_FILES_SCANNED) return true;
5586
5746
  let text;
5587
5747
  try {
5588
- if (fs18.statSync(abs).size > MAX_FILE_BYTES) continue;
5589
- text = fs18.readFileSync(abs, "utf8");
5748
+ if (fs19.statSync(abs).size > MAX_FILE_BYTES) continue;
5749
+ text = fs19.readFileSync(abs, "utf8");
5590
5750
  } catch {
5591
5751
  continue;
5592
5752
  }
5593
5753
  if (!text.toLowerCase().includes(lower)) continue;
5594
- const rel2 = path18.relative(root, abs);
5754
+ const rel2 = path19.relative(root, abs);
5595
5755
  const lines = text.split("\n");
5596
5756
  for (let i = 0; i < lines.length; i++) {
5597
5757
  if (!lines[i].toLowerCase().includes(lower)) continue;
@@ -5787,7 +5947,7 @@ var TOOLS = [
5787
5947
  handler: (graph, args, ctx) => {
5788
5948
  const { node, candidates } = resolveOne(graph, String(args.name ?? ""), numOrU(args.pick));
5789
5949
  if (!node) return unresolved(candidates);
5790
- const index = new GraphIndex(graph);
5950
+ const index = indexFor(graph);
5791
5951
  const edgeOk = edgeRecordFilter(args);
5792
5952
  const calleeRecs = index.callees(node.id).filter(edgeOk);
5793
5953
  const callerRecs = index.callers(node.id).filter(edgeOk);
@@ -6273,104 +6433,6 @@ function navigationToolsetConfig(serverName = "vibgrate") {
6273
6433
  configs: Object.fromEntries(HOT_TOOLS.map((n) => [n, { defer_loading: false }]))
6274
6434
  };
6275
6435
  }
6276
- var LEDGER = "savings.jsonl";
6277
- var PER_FILE_TOKENS = 400;
6278
- var SAVINGS_TOOLS = /* @__PURE__ */ new Set(["query_graph", "get_node"]);
6279
- function ledgerPath(root) {
6280
- return path18.join(cacheDir(root), LEDGER);
6281
- }
6282
- function savingsRecorded(root) {
6283
- return fs18.existsSync(ledgerPath(root));
6284
- }
6285
- function recordSaving(root, entry, now) {
6286
- try {
6287
- fs18.mkdirSync(cacheDir(root), { recursive: true });
6288
- const line = JSON.stringify({ ts: now, ...entry });
6289
- fs18.appendFileSync(ledgerPath(root), line + "\n");
6290
- } catch {
6291
- }
6292
- }
6293
- var DEFAULT_RATE_PER_M = 3;
6294
- var DEFAULT_RATE_LABEL = "input @ $3/1M";
6295
- function readSavings(root, days, now, ratePerM = DEFAULT_RATE_PER_M) {
6296
- const file = ledgerPath(root);
6297
- const cutoff = now - days * 24 * 60 * 60 * 1e3;
6298
- let queries = 0;
6299
- let vgTokens = 0;
6300
- let baselineTokens = 0;
6301
- if (fs18.existsSync(file)) {
6302
- for (const line of fs18.readFileSync(file, "utf8").split("\n")) {
6303
- if (!line.trim()) continue;
6304
- try {
6305
- const e = JSON.parse(line);
6306
- if (e.ts < cutoff) continue;
6307
- if (!SAVINGS_TOOLS.has(e.tool)) continue;
6308
- queries++;
6309
- vgTokens += e.vgTokens;
6310
- baselineTokens += e.baselineTokens;
6311
- } catch {
6312
- }
6313
- }
6314
- }
6315
- const estCostVg = vgTokens / 1e6 * ratePerM;
6316
- const estCostBaseline = baselineTokens / 1e6 * ratePerM;
6317
- return {
6318
- enabled: savingsRecorded(root),
6319
- days,
6320
- queries,
6321
- vgTokens,
6322
- baselineTokens,
6323
- ratio: vgTokens > 0 ? Math.round(baselineTokens / vgTokens * 100) / 100 : 0,
6324
- estCostVg: round22(estCostVg),
6325
- estCostBaseline: round22(estCostBaseline),
6326
- saved: round22(estCostBaseline - estCostVg),
6327
- rateLabel: DEFAULT_RATE_LABEL
6328
- };
6329
- }
6330
- function round22(x) {
6331
- return Math.round(x * 100) / 100;
6332
- }
6333
- function readUsage(root, days, now) {
6334
- const file = ledgerPath(root);
6335
- const cutoff = now - days * 24 * 60 * 60 * 1e3;
6336
- const byTool = /* @__PURE__ */ new Map();
6337
- if (fs18.existsSync(file)) {
6338
- for (const line of fs18.readFileSync(file, "utf8").split("\n")) {
6339
- if (!line.trim()) continue;
6340
- try {
6341
- const e = JSON.parse(line);
6342
- if (e.ts < cutoff) continue;
6343
- const outcome = e.outcome ?? "complete";
6344
- const row = byTool.get(e.tool) ?? { complete: 0, partial: 0, miss: 0 };
6345
- row[outcome]++;
6346
- byTool.set(e.tool, row);
6347
- } catch {
6348
- }
6349
- }
6350
- }
6351
- const commands = [...byTool.entries()].map(([tool, r]) => {
6352
- const calls = r.complete + r.partial + r.miss;
6353
- return {
6354
- tool,
6355
- calls,
6356
- complete: r.complete,
6357
- partial: r.partial,
6358
- miss: r.miss,
6359
- successPct: calls ? Math.round((r.complete + r.partial) / calls * 100) : 0
6360
- };
6361
- }).sort((a, b) => b.calls - a.calls || a.tool.localeCompare(b.tool));
6362
- const totals = commands.reduce(
6363
- (t, c) => ({
6364
- calls: t.calls + c.calls,
6365
- complete: t.complete + c.complete,
6366
- partial: t.partial + c.partial,
6367
- miss: t.miss + c.miss
6368
- }),
6369
- { calls: 0, complete: 0, partial: 0, miss: 0 }
6370
- );
6371
- const avgSuccessPct = commands.length ? Math.round(commands.reduce((s, c) => s + c.successPct, 0) / commands.length) : 0;
6372
- return { enabled: savingsRecorded(root), days, commands, totals, avgSuccessPct };
6373
- }
6374
6436
  var PROBE_INTERVAL_MS = 2e3;
6375
6437
  var MAX_PROBE_INTERVAL_MS = 3e4;
6376
6438
  var PROBE_DUTY_FACTOR = 20;
@@ -6381,7 +6443,7 @@ var GraphSource = class {
6381
6443
  this.graphPath = graphPath;
6382
6444
  this.refresh = refresh;
6383
6445
  this.tuning = tuning;
6384
- this.root = path18.dirname(path18.dirname(graphPath));
6446
+ this.root = path19.dirname(path19.dirname(graphPath));
6385
6447
  }
6386
6448
  graphPath;
6387
6449
  refresh;
@@ -6397,9 +6459,9 @@ var GraphSource = class {
6397
6459
  /** Current graph: auto-refreshed if the tree drifted, reloaded if the file changed. */
6398
6460
  async get() {
6399
6461
  if (this.refresh) await this.maybeRefresh();
6400
- const stat = fs18.statSync(this.graphPath);
6462
+ const stat = fs19.statSync(this.graphPath);
6401
6463
  if (stat.mtimeMs !== this.cachedMtimeMs || !this.cached) {
6402
- this.cached = parseGraph(fs18.readFileSync(this.graphPath, "utf8"));
6464
+ this.cached = parseGraph(fs19.readFileSync(this.graphPath, "utf8"));
6403
6465
  this.cachedMtimeMs = stat.mtimeMs;
6404
6466
  }
6405
6467
  return this.cached;
@@ -6433,8 +6495,9 @@ var GraphSource = class {
6433
6495
  }
6434
6496
  };
6435
6497
  function createServer(source, opts = {}) {
6436
- const { savings = false, local = false, dedup = false } = opts;
6437
- const root = path18.dirname(path18.dirname(source.graphPath));
6498
+ const { savings = false, shareStats = false, local = false, dedup = false } = opts;
6499
+ const record = savings || shareStats;
6500
+ const root = path19.dirname(path19.dirname(source.graphPath));
6438
6501
  const seen = /* @__PURE__ */ new Set();
6439
6502
  const server = new Server(
6440
6503
  { name: "vg", version: VERSION },
@@ -6470,7 +6533,7 @@ function createServer(source, opts = {}) {
6470
6533
  try {
6471
6534
  const args = request.params.arguments ?? {};
6472
6535
  const result = await tool.handler(graph, args, { root, local, dedup, seen });
6473
- if (savings) recordUsage(root, tool.name, result);
6536
+ if (record) recordUsage(root, tool.name, result, detectClient(server));
6474
6537
  return renderToolResult(result);
6475
6538
  } catch (err) {
6476
6539
  return errorResult(`tool "${tool.name}" failed: ${err.message}`);
@@ -6490,7 +6553,7 @@ function sleep(ms) {
6490
6553
  timer.unref?.();
6491
6554
  });
6492
6555
  }
6493
- function recordUsage(root, tool, result) {
6556
+ function recordUsage(root, tool, result, client) {
6494
6557
  const outcome = classifyOutcome(result);
6495
6558
  let vgTokens = 0;
6496
6559
  let baselineTokens = 0;
@@ -6498,7 +6561,19 @@ function recordUsage(root, tool, result) {
6498
6561
  vgTokens = countTokens(renderedText(renderToolResult(result)));
6499
6562
  baselineTokens = referencedFiles(result).size * PER_FILE_TOKENS;
6500
6563
  }
6501
- recordSaving(root, { tool, outcome, vgTokens, baselineTokens }, Date.now());
6564
+ recordSaving(
6565
+ root,
6566
+ { tool, outcome, vgTokens, baselineTokens, source: "mcp", client: sanitizeClient(client) },
6567
+ Date.now()
6568
+ );
6569
+ }
6570
+ function detectClient(server) {
6571
+ try {
6572
+ const info = server.getClientVersion?.();
6573
+ return typeof info?.name === "string" ? info.name : void 0;
6574
+ } catch {
6575
+ return void 0;
6576
+ }
6502
6577
  }
6503
6578
  function classifyOutcome(result) {
6504
6579
  if (Array.isArray(result)) return result.length === 0 ? "miss" : "complete";
@@ -6549,7 +6624,11 @@ function errorResult(message) {
6549
6624
  // src/install/content.ts
6550
6625
  var NUDGE_BEGIN = "<!-- vg:begin -->";
6551
6626
  var NUDGE_END = "<!-- vg:end -->";
6552
- function skillMarkdown() {
6627
+ function clientFlag(client) {
6628
+ return client ? `--client=${client}` : "--client=<your-ai>";
6629
+ }
6630
+ function skillMarkdown(client) {
6631
+ const cf = clientFlag(client);
6553
6632
  return `---
6554
6633
  name: vg
6555
6634
  description: Query the local code graph (vg) for structure, impact, and navigation instead of grepping/reading many files.
@@ -6560,13 +6639,26 @@ description: Query the local code graph (vg) for structure, impact, and navigati
6560
6639
  This repo has a deterministic code graph built by \`vg\`. Prefer it over reading or
6561
6640
  grepping many files \u2014 it is smaller, more relevant context, and free.
6562
6641
 
6563
- ## When to use it
6642
+ ## Prefer the MCP tools
6643
+
6644
+ If the \`vg\` MCP server is registered (it is after \`vg install\`), call its
6645
+ read-only tools directly \u2014 they are the **fastest** path. The server keeps the
6646
+ map parsed, the relation index warm, and the embedding model loaded across calls,
6647
+ so each query is cheaper than spawning the CLI fresh. Use:
6648
+ \`query_graph\`, \`get_node\`, \`impact_of\`, \`find_path\`, \`list_hubs\`, \`list_areas\`,
6649
+ \`get_graph_summary\`, \`search_symbols\`. They are side-effect-free and
6650
+ auto-approvable, and the server records which client is calling automatically.
6651
+
6652
+ ## If you use the \`vg\` CLI instead
6653
+
6654
+ When the MCP server isn't available, use the CLI \u2014 and **always pass \`${cf}\`**
6655
+ so your calls are counted (that's how the CLI-vs-MCP split is measured and the
6656
+ tools improved):
6564
6657
 
6565
- - **Understand code:** \`vg "<question>"\` returns a budget-bounded, fact-annotated
6566
- context block. Start here instead of opening files blindly.
6567
- - **Find a symbol:** \`vg show <name>\` \u2014 what it is, what it calls, what calls it.
6568
- - **Before changing something:** \`vg impact <name>\` \u2014 what breaks if you change it.
6569
- - **Navigate:** \`vg path <A> <B>\`, \`vg tree <name>\`, \`vg hubs\`, \`vg areas\`.
6658
+ - **Understand code:** \`vg "<question>" ${cf}\` \u2014 a budget-bounded, fact-annotated context block.
6659
+ - **Find a symbol:** \`vg show <name> ${cf}\` \u2014 what it is, what it calls, what calls it.
6660
+ - **Before changing something:** \`vg impact <name> ${cf}\` \u2014 what breaks if you change it.
6661
+ - **Navigate:** \`vg path <A> <B> ${cf}\`, \`vg tree <name> ${cf}\`.
6570
6662
 
6571
6663
  ## Dependencies & library docs
6572
6664
 
@@ -6576,12 +6668,6 @@ grepping many files \u2014 it is smaller, more relevant context, and free.
6576
6668
  - **Version-correct docs:** \`vg lib <name>\` returns drift-annotated, version-
6577
6669
  specific usage docs for a library \u2014 inject these instead of guessing an API.
6578
6670
 
6579
- ## Via MCP
6580
-
6581
- If the \`vg\` MCP server is registered, call its read-only tools directly:
6582
- \`query_graph\`, \`get_node\`, \`impact_of\`, \`find_path\`, \`list_hubs\`, \`list_areas\`,
6583
- \`get_graph_summary\`. They are side-effect-free and auto-approvable.
6584
-
6585
6671
  ## Keep it fresh
6586
6672
 
6587
6673
  The map keeps itself fresh: \`vg ask\` and the MCP tools detect changed files
@@ -6591,28 +6677,33 @@ The map lives at \`.vibgrate/graph.json\` and is deterministic (byte-identical
6591
6677
  across machines).
6592
6678
  `;
6593
6679
  }
6594
- function nudgeMarkdown(smallRepo) {
6680
+ function nudgeMarkdown(smallRepo, client) {
6681
+ const cf = clientFlag(client);
6595
6682
  if (smallRepo) {
6596
6683
  return `${NUDGE_BEGIN}
6597
6684
  ## Code navigation (vg)
6598
6685
 
6599
6686
  This repo is small enough that searching files directly is fine. Still, before a
6600
- change, \`vg impact <name>\` shows what it breaks, and \`vg "<question>"\` gives a
6601
- compact, cited context block when you want one.
6687
+ change, \`vg impact <name> ${cf}\` shows what it breaks, and \`vg "<question>" ${cf}\`
6688
+ gives a compact, cited context block when you want one. Prefer the \`vg\` MCP tools
6689
+ when the server is registered; when you use the CLI, pass \`${cf}\` so the call is
6690
+ counted.
6602
6691
  ${NUDGE_END}`;
6603
6692
  }
6604
6693
  return `${NUDGE_BEGIN}
6605
6694
  ## Code navigation (vg) \u2014 prefer the map
6606
6695
 
6607
6696
  A deterministic code graph is available. It is usually the faster, cheaper path
6608
- than reading or grepping many files:
6697
+ than reading or grepping many files. Prefer the \`vg\` MCP tools when the server is
6698
+ registered (fastest \u2014 the map and model stay warm across calls). When you use the
6699
+ CLI instead, pass \`${cf}\` so the call is counted:
6609
6700
 
6610
- - \`vg "<question>"\` \u2014 a compact, fact-annotated context block for a question.
6611
- - \`vg show <name>\` \u2014 what a symbol is, its callers and callees.
6612
- - \`vg impact <name>\` \u2014 what breaks if you change it.
6701
+ - \`vg "<question>" ${cf}\` \u2014 a compact, fact-annotated context block for a question.
6702
+ - \`vg show <name> ${cf}\` \u2014 what a symbol is, its callers and callees.
6703
+ - \`vg impact <name> ${cf}\` \u2014 what breaks if you change it.
6613
6704
 
6614
- Prefer these (or the \`vg\` MCP tools) before opening many files. This is advisory,
6615
- not mandatory \u2014 remove it with \`vg uninstall\` (or delete this block).
6705
+ This is advisory, not mandatory \u2014 remove it with \`vg uninstall\` (or delete this
6706
+ block).
6616
6707
  ${NUDGE_END}`;
6617
6708
  }
6618
6709
  function mcpServerEntry(launch = { command: "vg", args: ["serve"] }) {
@@ -6702,19 +6793,19 @@ function installAssistant(a, opts) {
6702
6793
  const skipped = [];
6703
6794
  let note;
6704
6795
  if (a.skill) {
6705
- writeFileEnsured(path18.join(opts.root, a.skill), skillMarkdown());
6796
+ writeFileEnsured(path19.join(opts.root, a.skill), skillMarkdown(a.id));
6706
6797
  wrote.push(a.skill);
6707
6798
  }
6708
6799
  if (a.mcp) {
6709
6800
  const launch = opts.launch ?? detectServeLaunch();
6710
- upsertMcp(path18.join(opts.root, a.mcp.file), a.mcp, launch);
6801
+ upsertMcp(path19.join(opts.root, a.mcp.file), a.mcp, launch);
6711
6802
  wrote.push(a.mcp.file);
6712
6803
  note = launch.note;
6713
6804
  } else {
6714
6805
  skipped.push("mcp (host-specific setup)");
6715
6806
  }
6716
6807
  if (a.nudge && opts.hook !== false) {
6717
- writeNudge(path18.join(opts.root, a.nudge.file), a.nudge, opts.smallRepo);
6808
+ writeNudge(path19.join(opts.root, a.nudge.file), a.nudge, opts.smallRepo, a.id);
6718
6809
  wrote.push(a.nudge.file);
6719
6810
  } else if (a.nudge) {
6720
6811
  skipped.push("nudge (--no-hook)");
@@ -6724,40 +6815,40 @@ function installAssistant(a, opts) {
6724
6815
  function uninstallAssistant(a, root, purge) {
6725
6816
  const removed = [];
6726
6817
  if (a.mcp) {
6727
- const file = path18.join(root, a.mcp.file);
6818
+ const file = path19.join(root, a.mcp.file);
6728
6819
  if (removeMcp(file, a.mcp)) removed.push(a.mcp.file);
6729
6820
  }
6730
6821
  if (a.nudge) {
6731
- const file = path18.join(root, a.nudge.file);
6822
+ const file = path19.join(root, a.nudge.file);
6732
6823
  if (a.nudge.kind === "block") {
6733
6824
  if (removeBlock(file)) removed.push(a.nudge.file);
6734
- } else if (fs18.existsSync(file)) {
6735
- fs18.rmSync(file);
6825
+ } else if (fs19.existsSync(file)) {
6826
+ fs19.rmSync(file);
6736
6827
  removed.push(a.nudge.file);
6737
6828
  }
6738
6829
  }
6739
6830
  if (purge && a.skill) {
6740
- const file = path18.join(root, a.skill);
6741
- if (fs18.existsSync(file)) {
6742
- fs18.rmSync(file);
6831
+ const file = path19.join(root, a.skill);
6832
+ if (fs19.existsSync(file)) {
6833
+ fs19.rmSync(file);
6743
6834
  removed.push(a.skill);
6744
6835
  }
6745
6836
  }
6746
6837
  return removed;
6747
6838
  }
6748
6839
  function writeFileEnsured(file, content) {
6749
- fs18.mkdirSync(path18.dirname(file), { recursive: true });
6750
- fs18.writeFileSync(file, content);
6840
+ fs19.mkdirSync(path19.dirname(file), { recursive: true });
6841
+ fs19.writeFileSync(file, content);
6751
6842
  }
6752
6843
  function writeNavigationConfig(root) {
6753
- const rel2 = path18.join(".vibgrate", "mcp-navigation.json");
6844
+ const rel2 = path19.join(".vibgrate", "mcp-navigation.json");
6754
6845
  const doc = {
6755
6846
  _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.",
6756
6847
  hot_core: [...HOT_TOOLS],
6757
6848
  deferred: deferredToolNames(),
6758
6849
  toolset: navigationToolsetConfig()
6759
6850
  };
6760
- writeFileEnsured(path18.join(root, rel2), `${JSON.stringify(doc, null, 2)}
6851
+ writeFileEnsured(path19.join(root, rel2), `${JSON.stringify(doc, null, 2)}
6761
6852
  `);
6762
6853
  return rel2;
6763
6854
  }
@@ -6772,7 +6863,7 @@ function upsertMcp(file, target, launch) {
6772
6863
  `);
6773
6864
  }
6774
6865
  function removeMcp(file, target) {
6775
- if (!fs18.existsSync(file)) return false;
6866
+ if (!fs19.existsSync(file)) return false;
6776
6867
  const config = readJson(file);
6777
6868
  const bag = config[target.key];
6778
6869
  if (!bag || !(bag.vg !== void 0)) return false;
@@ -6781,22 +6872,22 @@ function removeMcp(file, target) {
6781
6872
  `);
6782
6873
  return true;
6783
6874
  }
6784
- function writeNudge(file, target, smallRepo) {
6875
+ function writeNudge(file, target, smallRepo, client) {
6785
6876
  if (target.kind === "file") {
6786
6877
  const body = file.endsWith(".mdc") ? `---
6787
6878
  description: vg code graph
6788
6879
  alwaysApply: true
6789
6880
  ---
6790
- ${stripMarkers(nudgeMarkdown(smallRepo))}` : stripMarkers(nudgeMarkdown(smallRepo));
6881
+ ${stripMarkers(nudgeMarkdown(smallRepo, client))}` : stripMarkers(nudgeMarkdown(smallRepo, client));
6791
6882
  writeFileEnsured(file, `${body}
6792
6883
  `);
6793
6884
  return;
6794
6885
  }
6795
- upsertBlock(file, nudgeMarkdown(smallRepo));
6886
+ upsertBlock(file, nudgeMarkdown(smallRepo, client));
6796
6887
  }
6797
6888
  function upsertBlock(file, block) {
6798
- fs18.mkdirSync(path18.dirname(file), { recursive: true });
6799
- let existing = fs18.existsSync(file) ? fs18.readFileSync(file, "utf8") : "";
6889
+ fs19.mkdirSync(path19.dirname(file), { recursive: true });
6890
+ let existing = fs19.existsSync(file) ? fs19.readFileSync(file, "utf8") : "";
6800
6891
  const re = new RegExp(`${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}`);
6801
6892
  if (re.test(existing)) existing = existing.replace(re, block);
6802
6893
  else existing = existing.length ? `${existing.replace(/\s*$/, "")}
@@ -6804,20 +6895,20 @@ function upsertBlock(file, block) {
6804
6895
  ${block}
6805
6896
  ` : `${block}
6806
6897
  `;
6807
- fs18.writeFileSync(file, existing);
6898
+ fs19.writeFileSync(file, existing);
6808
6899
  }
6809
6900
  function removeBlock(file) {
6810
- if (!fs18.existsSync(file)) return false;
6811
- const existing = fs18.readFileSync(file, "utf8");
6901
+ if (!fs19.existsSync(file)) return false;
6902
+ const existing = fs19.readFileSync(file, "utf8");
6812
6903
  const re = new RegExp(`\\n*${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}\\n*`);
6813
6904
  if (!re.test(existing)) return false;
6814
- fs18.writeFileSync(file, existing.replace(re, "\n"));
6905
+ fs19.writeFileSync(file, existing.replace(re, "\n"));
6815
6906
  return true;
6816
6907
  }
6817
6908
  function readJson(file) {
6818
- if (!fs18.existsSync(file)) return {};
6909
+ if (!fs19.existsSync(file)) return {};
6819
6910
  try {
6820
- const parsed = JSON.parse(fs18.readFileSync(file, "utf8"));
6911
+ const parsed = JSON.parse(fs19.readFileSync(file, "utf8"));
6821
6912
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
6822
6913
  return parsed;
6823
6914
  }
@@ -7039,6 +7130,6 @@ function spdx(ctx) {
7039
7130
  return JSON.stringify(doc, null, 2) + "\n";
7040
7131
  }
7041
7132
 
7042
- export { ASSISTANTS, CliError, ECOSYSTEMS, ExitCode, FREE_PACK, GraphIndex, GraphSource, NPX_INVOCATION, ResourceLimitError, SCHEMA_VERSION, SKIP_DIRS, SKIP_FILES, TOOLS, UsageError, addLibrary, analyze, applyCoverage, applyStaticTestLinkage, assessDocQuality, assistantById, availableRegionIds, buildFacts, buildGraph, buildModuleResolver, cacheDir, catalogPath, clearModelCache, clearStoredCredentials, cosine, countPending, coveringTests, createServer, createWorkspaceDsn, credentialsPath, dashHostForIngestHost, decodeScipIndex, defaultGraphPath, detectRunner, detectServeLaunch, discover, discoverModels, driftCount, driftFor, dsnCommand, embeddingsCached, enrichOnline, ensureGitignored, epistemicBreakdown, exportGraph, fetchHostedDocs, findGitRoot, findNodes, formatForExt, getNodeEmbeddings, gitignoreEntryForCredentials, groundGraph, hasDrift, identifierParts, impactOf, ingestHostForRegionId, installAssistant, inventory, isModelReady, isTestFile, libDir, libId, loadCatalog, loadCoverage, loadEmbedder, loadGraph, loadSnapshot, localApiSurface, localPackageDocs, modelCacheInfo, nodeById, nodeEmbedText, parseDsn, parseGraph, parseJsonc, probeFreshness, publishPrivateLibrary, pushCommand, queryGraph, queryGraphSemantic, readDoc, readSavings, readScanArtifact, readUsage, 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 };
7043
- //# sourceMappingURL=chunk-AWEAREOE.js.map
7044
- //# sourceMappingURL=chunk-AWEAREOE.js.map
7133
+ export { ASSISTANTS, CLI_TOOL_ALIASES, CliError, ECOSYSTEMS, ExitCode, FREE_PACK, GraphIndex, GraphSource, NPX_INVOCATION, ResourceLimitError, 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, detectRunner, detectServeLaunch, discover, discoverModels, driftCount, driftFor, dsnCommand, embeddingsCached, enrichOnline, ensureGitignored, epistemicBreakdown, exportGraph, fetchHostedDocs, findGitRoot, findNodes, formatForExt, getNodeEmbeddings, gitignoreEntryForCredentials, groundGraph, hasDrift, identifierParts, impactOf, indexFor, ingestHostForRegionId, installAssistant, inventory, isModelReady, isTestFile, libDir, libId, loadCatalog, loadCoverage, loadEmbedder, loadGraph, loadSnapshot, localApiSurface, localPackageDocs, modelCacheInfo, nodeById, nodeEmbedText, parseDsn, parseGraph, parseJsonc, probeFreshness, publishPrivateLibrary, pushCommand, queryGraph, queryGraphSemantic, readDoc, readSavings, readScanArtifact, 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 };
7134
+ //# sourceMappingURL=chunk-MPKMAERO.js.map
7135
+ //# sourceMappingURL=chunk-MPKMAERO.js.map