@vibgrate/cli 2026.720.2 → 2026.720.4

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-BHA7QIPD.js';
2
- import { writeTextFile, pathExists, readJsonFile, loadConfig, computeUpgradeImpact, getChangelogSignals, gitHistoryAvailable, buildVersionTimelines, findPackageTimeline, severityRank, projectTypeToVulnEcosystem, VERSION } from './chunk-OD5654VF.js';
3
- import * as fs19 from 'fs';
2
+ import { writeTextFile, pathExists, readJsonFile, loadConfig, computeUpgradeImpact, getChangelogSignals, gitHistoryAvailable, buildVersionTimelines, findPackageTimeline, severityRank, projectTypeToVulnEcosystem, VERSION } from './chunk-RAQBGOP5.js';
3
+ import * as fs17 from 'fs';
4
4
  import { spawnSync, execFileSync } from 'child_process';
5
- import * as path19 from 'path';
5
+ import * as path17 from 'path';
6
6
  import ignore from 'ignore';
7
7
  import * as v8 from 'v8';
8
8
  import Graph from 'graphology';
@@ -41,9 +41,9 @@ function whichOnPath(cmd) {
41
41
  }
42
42
  function isOwnBinary(binPath) {
43
43
  try {
44
- const real = fs19.realpathSync(binPath);
44
+ const real = fs17.realpathSync(binPath);
45
45
  if (/[\\/]@vibgrate[\\/]cli[\\/]/.test(real)) return true;
46
- const head = fs19.readFileSync(real, { encoding: "utf8" }).slice(0, 2048);
46
+ const head = fs17.readFileSync(real, { encoding: "utf8" }).slice(0, 2048);
47
47
  return head.includes("@vibgrate/cli") || head.includes("vibgrate");
48
48
  } catch {
49
49
  return false;
@@ -53,7 +53,7 @@ function isEphemeralNpxBinary(binPath) {
53
53
  const NPX_SEGMENT = /[\\/]_npx[\\/]/;
54
54
  if (NPX_SEGMENT.test(binPath)) return true;
55
55
  try {
56
- return NPX_SEGMENT.test(fs19.realpathSync(binPath));
56
+ return NPX_SEGMENT.test(fs17.realpathSync(binPath));
57
57
  } catch {
58
58
  return false;
59
59
  }
@@ -219,19 +219,19 @@ var SKIP_FILES = /* @__PURE__ */ new Set([
219
219
  "flake.lock"
220
220
  ]);
221
221
  function toPosix(p) {
222
- return p.split(path19.sep).join("/");
222
+ return p.split(path17.sep).join("/");
223
223
  }
224
224
  function buildRootIgnore(root, exclude) {
225
225
  const ig = ignore();
226
- const gitignorePath = path19.join(root, ".gitignore");
227
- if (fs19.existsSync(gitignorePath)) {
228
- ig.add(fs19.readFileSync(gitignorePath, "utf8"));
226
+ const gitignorePath = path17.join(root, ".gitignore");
227
+ if (fs17.existsSync(gitignorePath)) {
228
+ ig.add(fs17.readFileSync(gitignorePath, "utf8"));
229
229
  }
230
230
  if (exclude.length) ig.add(exclude);
231
231
  return ig;
232
232
  }
233
233
  function discover(options) {
234
- const root = path19.resolve(options.root);
234
+ const root = path17.resolve(options.root);
235
235
  const onlyLangs = (options.only ?? []).filter(Boolean);
236
236
  const allowLang = (lang) => onlyLangs.length === 0 || onlyLangs.includes(lang.id);
237
237
  for (const id of onlyLangs) {
@@ -240,27 +240,27 @@ function discover(options) {
240
240
  }
241
241
  }
242
242
  const rootIg = buildRootIgnore(root, options.exclude ?? []);
243
- const scopeAbs = (options.paths && options.paths.length ? options.paths.map((p) => path19.resolve(root, p)) : [root]).filter((p) => fs19.existsSync(p));
243
+ const scopeAbs = (options.paths && options.paths.length ? options.paths.map((p) => path17.resolve(root, p)) : [root]).filter((p) => fs17.existsSync(p));
244
244
  const found = /* @__PURE__ */ new Map();
245
245
  const considerFile = (abs) => {
246
- const rel2 = toPosix(path19.relative(root, abs));
246
+ const rel2 = toPosix(path17.relative(root, abs));
247
247
  if (rel2.startsWith("..")) return;
248
248
  if (rel2 === "" || rootIg.ignores(rel2)) return;
249
- if (SKIP_FILES.has(path19.basename(abs).toLowerCase())) return;
250
- const lang = langForExtension(path19.extname(abs));
249
+ if (SKIP_FILES.has(path17.basename(abs).toLowerCase())) return;
250
+ const lang = langForExtension(path17.extname(abs));
251
251
  if (!lang || !allowLang(lang)) return;
252
252
  found.set(rel2, { rel: rel2, abs, lang });
253
253
  };
254
254
  const walk2 = (dir) => {
255
255
  let entries;
256
256
  try {
257
- entries = fs19.readdirSync(dir, { withFileTypes: true });
257
+ entries = fs17.readdirSync(dir, { withFileTypes: true });
258
258
  } catch {
259
259
  return;
260
260
  }
261
261
  for (const entry of entries) {
262
- const abs = path19.join(dir, entry.name);
263
- const rel2 = toPosix(path19.relative(root, abs));
262
+ const abs = path17.join(dir, entry.name);
263
+ const rel2 = toPosix(path17.relative(root, abs));
264
264
  if (entry.isDirectory()) {
265
265
  if (SKIP_DIRS.has(entry.name)) continue;
266
266
  if (rel2 && rootIg.ignores(`${rel2}/`)) continue;
@@ -271,7 +271,7 @@ function discover(options) {
271
271
  }
272
272
  };
273
273
  for (const scope of scopeAbs) {
274
- const stat = fs19.statSync(scope);
274
+ const stat = fs17.statSync(scope);
275
275
  if (stat.isDirectory()) walk2(scope);
276
276
  else if (stat.isFile()) considerFile(scope);
277
277
  }
@@ -455,10 +455,10 @@ function buildModuleResolver(root, relSet) {
455
455
  resolve(fromRel, source) {
456
456
  if (source.includes("\\")) source = source.split("\\").join(".");
457
457
  if (source.startsWith("./") || source.startsWith("../")) {
458
- return probe(posixJoin(path19.posix.dirname(fromRel), source));
458
+ return probe(posixJoin(path17.posix.dirname(fromRel), source));
459
459
  }
460
460
  if (/\.[A-Za-z0-9]+$/.test(source) && !source.startsWith("<")) {
461
- const hit = probe(posixJoin(path19.posix.dirname(fromRel), source));
461
+ const hit = probe(posixJoin(path17.posix.dirname(fromRel), source));
462
462
  if (hit) return hit;
463
463
  }
464
464
  if (source.startsWith(".")) {
@@ -529,8 +529,8 @@ function resolvePyRelative(probe, fromRel, source) {
529
529
  let dots = 0;
530
530
  while (source[dots] === ".") dots++;
531
531
  const rest = source.slice(dots);
532
- let dir = path19.posix.dirname(fromRel);
533
- for (let i = 1; i < dots; i++) dir = path19.posix.dirname(dir);
532
+ let dir = path17.posix.dirname(fromRel);
533
+ for (let i = 1; i < dots; i++) dir = path17.posix.dirname(dir);
534
534
  const sub = rest.replace(/\./g, "/");
535
535
  return probe(sub ? posixJoin(dir, sub) : dir);
536
536
  }
@@ -538,7 +538,7 @@ function relativeResolver(relSet) {
538
538
  const probe = makeProbe(relSet);
539
539
  return {
540
540
  resolve(fromRel, source) {
541
- if (source.startsWith(".")) return probe(posixJoin(path19.posix.dirname(fromRel), source));
541
+ if (source.startsWith(".")) return probe(posixJoin(path17.posix.dirname(fromRel), source));
542
542
  return resolveDotted(probe, source);
543
543
  }
544
544
  };
@@ -556,12 +556,12 @@ function makeProbe(relSet) {
556
556
  };
557
557
  }
558
558
  function normalizeRel(p) {
559
- const norm = path19.posix.normalize(p);
559
+ const norm = path17.posix.normalize(p);
560
560
  const stripped = norm.startsWith("./") ? norm.slice(2) : norm;
561
561
  return stripped.startsWith("..") ? "" : stripped;
562
562
  }
563
563
  function posixJoin(a, b) {
564
- return path19.posix.normalize(path19.posix.join(a, b));
564
+ return path17.posix.normalize(path17.posix.join(a, b));
565
565
  }
566
566
  function matchPattern(pattern, source) {
567
567
  const star = pattern.indexOf("*");
@@ -574,8 +574,8 @@ function matchPattern(pattern, source) {
574
574
  }
575
575
  function loadTsconfigPaths(root) {
576
576
  for (const name of ["tsconfig.base.json", "tsconfig.json"]) {
577
- const file = path19.join(root, name);
578
- if (fs19.existsSync(file)) {
577
+ const file = path17.join(root, name);
578
+ if (fs17.existsSync(file)) {
579
579
  const merged = readTsconfigChain(root, file, /* @__PURE__ */ new Set());
580
580
  if (merged && merged.paths.length) return merged;
581
581
  }
@@ -587,21 +587,21 @@ function readTsconfigChain(root, file, seen) {
587
587
  seen.add(file);
588
588
  let cfg;
589
589
  try {
590
- cfg = parseJsonc(fs19.readFileSync(file, "utf8"));
590
+ cfg = parseJsonc(fs17.readFileSync(file, "utf8"));
591
591
  } catch {
592
592
  return null;
593
593
  }
594
- const dir = path19.dirname(file);
594
+ const dir = path17.dirname(file);
595
595
  let base = { baseUrlRel: "", paths: [] };
596
596
  if (cfg.extends && cfg.extends.startsWith(".")) {
597
- const extPath = path19.resolve(dir, cfg.extends.endsWith(".json") ? cfg.extends : `${cfg.extends}.json`);
598
- if (fs19.existsSync(extPath)) {
597
+ const extPath = path17.resolve(dir, cfg.extends.endsWith(".json") ? cfg.extends : `${cfg.extends}.json`);
598
+ if (fs17.existsSync(extPath)) {
599
599
  const inherited = readTsconfigChain(root, extPath, seen);
600
600
  if (inherited) base = inherited;
601
601
  }
602
602
  }
603
603
  const co = cfg.compilerOptions ?? {};
604
- const baseUrlRel = co.baseUrl !== void 0 ? path19.relative(root, path19.resolve(dir, co.baseUrl)).split(path19.sep).join("/") : base.baseUrlRel;
604
+ const baseUrlRel = co.baseUrl !== void 0 ? path17.relative(root, path17.resolve(dir, co.baseUrl)).split(path17.sep).join("/") : base.baseUrlRel;
605
605
  const paths = [...base.paths];
606
606
  if (co.paths) for (const [k, v] of Object.entries(co.paths)) paths.push([k, v]);
607
607
  return { baseUrlRel, paths };
@@ -609,27 +609,27 @@ function readTsconfigChain(root, file, seen) {
609
609
  function loadWorkspacePackages(root) {
610
610
  const map = /* @__PURE__ */ new Map();
611
611
  const globs = [];
612
- const rootPkg = path19.join(root, "package.json");
613
- if (fs19.existsSync(rootPkg)) {
612
+ const rootPkg = path17.join(root, "package.json");
613
+ if (fs17.existsSync(rootPkg)) {
614
614
  try {
615
- const pkg = JSON.parse(fs19.readFileSync(rootPkg, "utf8"));
615
+ const pkg = JSON.parse(fs17.readFileSync(rootPkg, "utf8"));
616
616
  const ws = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces?.packages;
617
617
  if (ws) globs.push(...ws);
618
618
  } catch {
619
619
  }
620
620
  }
621
- const pnpmWs = path19.join(root, "pnpm-workspace.yaml");
622
- if (fs19.existsSync(pnpmWs)) {
623
- for (const line of fs19.readFileSync(pnpmWs, "utf8").split("\n")) {
621
+ const pnpmWs = path17.join(root, "pnpm-workspace.yaml");
622
+ if (fs17.existsSync(pnpmWs)) {
623
+ for (const line of fs17.readFileSync(pnpmWs, "utf8").split("\n")) {
624
624
  const m = /^\s*-\s*['"]?([^'"\n]+)['"]?\s*$/.exec(line);
625
625
  if (m) globs.push(m[1].trim());
626
626
  }
627
627
  }
628
628
  for (const dir of expandWorkspaceGlobs(root, globs)) {
629
- const pj = path19.join(dir, "package.json");
629
+ const pj = path17.join(dir, "package.json");
630
630
  try {
631
- const name = JSON.parse(fs19.readFileSync(pj, "utf8")).name;
632
- if (name) map.set(name, path19.relative(root, dir).split(path19.sep).join("/"));
631
+ const name = JSON.parse(fs17.readFileSync(pj, "utf8")).name;
632
+ if (name) map.set(name, path17.relative(root, dir).split(path17.sep).join("/"));
633
633
  } catch {
634
634
  }
635
635
  }
@@ -639,10 +639,10 @@ function expandWorkspaceGlobs(root, globs) {
639
639
  const out = /* @__PURE__ */ new Set();
640
640
  for (const glob of globs) {
641
641
  const clean = glob.replace(/\/\*\*$/, "").replace(/\/\*$/, "");
642
- const baseDir = path19.join(root, clean);
642
+ const baseDir = path17.join(root, clean);
643
643
  const recurse = glob.endsWith("**");
644
644
  if (glob === clean) {
645
- if (fs19.existsSync(path19.join(baseDir, "package.json"))) out.add(baseDir);
645
+ if (fs17.existsSync(path17.join(baseDir, "package.json"))) out.add(baseDir);
646
646
  continue;
647
647
  }
648
648
  collectPackageDirs(baseDir, recurse ? 4 : 1, out);
@@ -653,14 +653,14 @@ function collectPackageDirs(dir, depth, out) {
653
653
  if (depth < 0) return;
654
654
  let entries;
655
655
  try {
656
- entries = fs19.readdirSync(dir, { withFileTypes: true });
656
+ entries = fs17.readdirSync(dir, { withFileTypes: true });
657
657
  } catch {
658
658
  return;
659
659
  }
660
660
  for (const e of entries) {
661
661
  if (!e.isDirectory() || e.name === "node_modules" || e.name.startsWith(".")) continue;
662
- const child = path19.join(dir, e.name);
663
- if (fs19.existsSync(path19.join(child, "package.json"))) out.add(child);
662
+ const child = path17.join(dir, e.name);
663
+ if (fs17.existsSync(path17.join(child, "package.json"))) out.add(child);
664
664
  else collectPackageDirs(child, depth - 1, out);
665
665
  }
666
666
  }
@@ -1159,14 +1159,14 @@ var DEFAULT_PATHS = [
1159
1159
  ];
1160
1160
  function loadCoverage(root, explicit) {
1161
1161
  const candidates = (explicit && explicit.length ? explicit : DEFAULT_PATHS).map(
1162
- (p) => path19.resolve(root, p)
1162
+ (p) => path17.resolve(root, p)
1163
1163
  );
1164
- const found = candidates.filter((p) => fs19.existsSync(p));
1164
+ const found = candidates.filter((p) => fs17.existsSync(p));
1165
1165
  if (found.length === 0) return null;
1166
1166
  const map = /* @__PURE__ */ new Map();
1167
1167
  for (const file of found) {
1168
1168
  try {
1169
- const text = fs19.readFileSync(file, "utf8");
1169
+ const text = fs17.readFileSync(file, "utf8");
1170
1170
  if (file.endsWith(".json")) mergeIstanbul(map, text, root);
1171
1171
  else mergeLcov(map, text, root);
1172
1172
  } catch {
@@ -1175,8 +1175,8 @@ function loadCoverage(root, explicit) {
1175
1175
  return map.size ? map : null;
1176
1176
  }
1177
1177
  function rel(root, p) {
1178
- const abs = path19.isAbsolute(p) ? p : path19.resolve(root, p);
1179
- return path19.relative(root, abs).split(path19.sep).join("/");
1178
+ const abs = path17.isAbsolute(p) ? p : path17.resolve(root, p);
1179
+ return path17.relative(root, abs).split(path17.sep).join("/");
1180
1180
  }
1181
1181
  function bump(map, file, line, hits) {
1182
1182
  let lh = map.get(file);
@@ -1512,7 +1512,7 @@ async function parseInline(files, options) {
1512
1512
  onProgress?.(0, files.length);
1513
1513
  for (const file of files) {
1514
1514
  try {
1515
- const source = fs19.readFileSync(file.abs, "utf8");
1515
+ const source = fs17.readFileSync(file.abs, "utf8");
1516
1516
  out.push(await parseSource(file.rel, file.lang.id, source));
1517
1517
  } catch (err) {
1518
1518
  out.push(emptyParse(file, `parse failed: ${err.message}`));
@@ -1567,9 +1567,9 @@ function isWorkerOom(err) {
1567
1567
  return e?.code === "ERR_WORKER_OUT_OF_MEMORY" || /out of memory/i.test(e?.message ?? "");
1568
1568
  }
1569
1569
  function resolveWorkerFile() {
1570
- const here = path19.dirname(fileURLToPath(import.meta.url));
1571
- const candidate = path19.join(here, "parse-worker.js");
1572
- return fs19.existsSync(candidate) ? candidate : null;
1570
+ const here = path17.dirname(fileURLToPath(import.meta.url));
1571
+ const candidate = path17.join(here, "parse-worker.js");
1572
+ return fs17.existsSync(candidate) ? candidate : null;
1573
1573
  }
1574
1574
  function chunk(items, buckets) {
1575
1575
  const out = Array.from({ length: Math.min(buckets, items.length || 1) }, () => []);
@@ -1608,7 +1608,7 @@ function resolve4(parses, resolver) {
1608
1608
  addNode(baseNodes, {
1609
1609
  id: fileId,
1610
1610
  kind: "file",
1611
- name: path19.posix.basename(p.rel),
1611
+ name: path17.posix.basename(p.rel),
1612
1612
  qualifiedName: p.rel,
1613
1613
  file: p.rel,
1614
1614
  span: { start: 1, end: 1 },
@@ -2044,14 +2044,14 @@ function enclosing3(nodes, line) {
2044
2044
  return best;
2045
2045
  }
2046
2046
  function normalize2(p) {
2047
- return path19.resolve(p).split(path19.sep).join("/");
2047
+ return path17.resolve(p).split(path17.sep).join("/");
2048
2048
  }
2049
2049
  var CACHE_VERSION = "vg-parse-cache/2";
2050
2050
  function cacheDir(root) {
2051
- return path19.join(root, ".vibgrate", "cache");
2051
+ return path17.join(root, ".vibgrate", "cache");
2052
2052
  }
2053
2053
  function cachePath(root) {
2054
- return path19.join(cacheDir(root), "parse-cache.json");
2054
+ return path17.join(cacheDir(root), "parse-cache.json");
2055
2055
  }
2056
2056
  function loadCache(root, opts) {
2057
2057
  const file = cachePath(root);
@@ -2061,9 +2061,9 @@ function loadCache(root, opts) {
2061
2061
  grammars: opts.grammars,
2062
2062
  entries: {}
2063
2063
  };
2064
- if (!opts.disabled && fs19.existsSync(file)) {
2064
+ if (!opts.disabled && fs17.existsSync(file)) {
2065
2065
  try {
2066
- const loaded = JSON.parse(fs19.readFileSync(file, "utf8"));
2066
+ const loaded = JSON.parse(fs17.readFileSync(file, "utf8"));
2067
2067
  if (loaded.version === CACHE_VERSION && loaded.toolVersion === opts.toolVersion && loaded.grammars === opts.grammars && loaded.entries) {
2068
2068
  data = loaded;
2069
2069
  }
@@ -2084,8 +2084,8 @@ function loadCache(root, opts) {
2084
2084
  }
2085
2085
  },
2086
2086
  save() {
2087
- fs19.mkdirSync(cacheDir(root), { recursive: true });
2088
- fs19.writeFileSync(file, stableStringify(data, 0));
2087
+ fs17.mkdirSync(cacheDir(root), { recursive: true });
2088
+ fs17.writeFileSync(file, stableStringify(data, 0));
2089
2089
  }
2090
2090
  };
2091
2091
  }
@@ -2117,7 +2117,7 @@ function epistemicBreakdown(edges) {
2117
2117
  // src/engine/build.ts
2118
2118
  async function buildGraph(options) {
2119
2119
  const start = nowMs();
2120
- const root = path19.resolve(options.root);
2120
+ const root = path17.resolve(options.root);
2121
2121
  const files = discover({
2122
2122
  root,
2123
2123
  only: options.only,
@@ -2145,7 +2145,7 @@ async function buildGraph(options) {
2145
2145
  for (const file of files) {
2146
2146
  let stat;
2147
2147
  try {
2148
- stat = fs19.statSync(file.abs);
2148
+ stat = fs17.statSync(file.abs);
2149
2149
  } catch {
2150
2150
  continue;
2151
2151
  }
@@ -2163,7 +2163,7 @@ async function buildGraph(options) {
2163
2163
  }
2164
2164
  let hash = "";
2165
2165
  try {
2166
- hash = hashBytes(fs19.readFileSync(file.abs));
2166
+ hash = hashBytes(fs17.readFileSync(file.abs));
2167
2167
  } catch {
2168
2168
  continue;
2169
2169
  }
@@ -2255,7 +2255,7 @@ async function buildGraph(options) {
2255
2255
  toolchain
2256
2256
  },
2257
2257
  meta: {
2258
- root: path19.basename(root) === "" ? "." : ".",
2258
+ root: path17.basename(root) === "" ? "." : ".",
2259
2259
  languages,
2260
2260
  counts: {
2261
2261
  nodes: analysis.nodes.length,
@@ -2301,14 +2301,14 @@ function toRepoRel(p) {
2301
2301
  function loadScipIndex(root, explicit) {
2302
2302
  const candidates = [
2303
2303
  explicit,
2304
- path19.join(root, "index.scip"),
2305
- path19.join(root, ".vibgrate", "index.scip")
2304
+ path17.join(root, "index.scip"),
2305
+ path17.join(root, ".vibgrate", "index.scip")
2306
2306
  ].filter((p) => Boolean(p));
2307
2307
  for (const file of candidates) {
2308
- const abs = path19.isAbsolute(file) ? file : path19.resolve(root, file);
2309
- if (!fs19.existsSync(abs)) continue;
2308
+ const abs = path17.isAbsolute(file) ? file : path17.resolve(root, file);
2309
+ if (!fs17.existsSync(abs)) continue;
2310
2310
  try {
2311
- const index = decodeScipIndex(new Uint8Array(fs19.readFileSync(abs)));
2311
+ const index = decodeScipIndex(new Uint8Array(fs17.readFileSync(abs)));
2312
2312
  if (index.documents.length) {
2313
2313
  return { index, tool: index.toolVersion ? `${index.toolName} ${index.toolVersion}` : index.toolName };
2314
2314
  }
@@ -2469,46 +2469,46 @@ function esc(s) {
2469
2469
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2470
2470
  }
2471
2471
  function vibgrateDir(root) {
2472
- return path19.join(root, ".vibgrate");
2472
+ return path17.join(root, ".vibgrate");
2473
2473
  }
2474
2474
  function defaultGraphPath(root) {
2475
- return path19.join(vibgrateDir(root), "graph.json");
2475
+ return path17.join(vibgrateDir(root), "graph.json");
2476
2476
  }
2477
2477
  function writeArtifacts(graph, options) {
2478
2478
  const dir = vibgrateDir(options.root);
2479
- fs19.mkdirSync(dir, { recursive: true });
2479
+ fs17.mkdirSync(dir, { recursive: true });
2480
2480
  const graphPath = options.graphPath ?? defaultGraphPath(options.root);
2481
- fs19.mkdirSync(path19.dirname(graphPath), { recursive: true });
2481
+ fs17.mkdirSync(path17.dirname(graphPath), { recursive: true });
2482
2482
  const tmp = `${graphPath}.${process.pid}.tmp`;
2483
2483
  try {
2484
- fs19.writeFileSync(tmp, serializeGraph(graph));
2485
- fs19.renameSync(tmp, graphPath);
2484
+ fs17.writeFileSync(tmp, serializeGraph(graph));
2485
+ fs17.renameSync(tmp, graphPath);
2486
2486
  } catch (err) {
2487
- fs19.rmSync(tmp, { force: true });
2487
+ fs17.rmSync(tmp, { force: true });
2488
2488
  throw err;
2489
2489
  }
2490
2490
  const written = { graphPath };
2491
2491
  if (options.report !== false) {
2492
- const reportPath = path19.join(dir, "GRAPH_REPORT.md");
2493
- fs19.writeFileSync(reportPath, renderReport(graph));
2492
+ const reportPath = path17.join(dir, "GRAPH_REPORT.md");
2493
+ fs17.writeFileSync(reportPath, renderReport(graph));
2494
2494
  written.reportPath = reportPath;
2495
2495
  }
2496
2496
  if (options.html !== false) {
2497
- const htmlPath = path19.join(dir, "graph.html");
2498
- fs19.writeFileSync(htmlPath, renderHtml(graph));
2497
+ const htmlPath = path17.join(dir, "graph.html");
2498
+ fs17.writeFileSync(htmlPath, renderHtml(graph));
2499
2499
  written.htmlPath = htmlPath;
2500
2500
  }
2501
2501
  if (graph.facts && graph.facts.length) {
2502
- const factsPath = path19.join(dir, "facts.jsonl");
2503
- fs19.writeFileSync(factsPath, graph.facts.map((f) => JSON.stringify(f)).join("\n") + "\n");
2502
+ const factsPath = path17.join(dir, "facts.jsonl");
2503
+ fs17.writeFileSync(factsPath, graph.facts.map((f) => JSON.stringify(f)).join("\n") + "\n");
2504
2504
  written.factsPath = factsPath;
2505
2505
  }
2506
2506
  return written;
2507
2507
  }
2508
2508
  function loadGraph(root, graphPath) {
2509
2509
  const file = graphPath ?? defaultGraphPath(root);
2510
- if (!fs19.existsSync(file)) return null;
2511
- return parseGraph(fs19.readFileSync(file, "utf8"));
2510
+ if (!fs17.existsSync(file)) return null;
2511
+ return parseGraph(fs17.readFileSync(file, "utf8"));
2512
2512
  }
2513
2513
 
2514
2514
  // src/engine/verify.ts
@@ -2585,7 +2585,7 @@ function isProcessAlive(pid) {
2585
2585
  }
2586
2586
  function lockIsStale(file, staleMs) {
2587
2587
  try {
2588
- const { pid, at } = JSON.parse(fs19.readFileSync(file, "utf8"));
2588
+ const { pid, at } = JSON.parse(fs17.readFileSync(file, "utf8"));
2589
2589
  if (typeof at === "number" && Date.now() - at > staleMs) return true;
2590
2590
  if (typeof pid === "number" && !isProcessAlive(pid)) return true;
2591
2591
  return false;
@@ -2596,10 +2596,10 @@ function lockIsStale(file, staleMs) {
2596
2596
  function acquireLock(file, staleMs = DEFAULT_LOCK_STALE_MS) {
2597
2597
  const write = () => {
2598
2598
  try {
2599
- fs19.mkdirSync(path19.dirname(file), { recursive: true });
2600
- const fd = fs19.openSync(file, "wx");
2601
- fs19.writeFileSync(fd, JSON.stringify({ pid: process.pid, at: Date.now() }));
2602
- fs19.closeSync(fd);
2599
+ fs17.mkdirSync(path17.dirname(file), { recursive: true });
2600
+ const fd = fs17.openSync(file, "wx");
2601
+ fs17.writeFileSync(fd, JSON.stringify({ pid: process.pid, at: Date.now() }));
2602
+ fs17.closeSync(fd);
2603
2603
  return true;
2604
2604
  } catch {
2605
2605
  return false;
@@ -2608,7 +2608,7 @@ function acquireLock(file, staleMs = DEFAULT_LOCK_STALE_MS) {
2608
2608
  if (write()) return true;
2609
2609
  if (lockIsStale(file, staleMs)) {
2610
2610
  try {
2611
- fs19.rmSync(file, { force: true });
2611
+ fs17.rmSync(file, { force: true });
2612
2612
  } catch {
2613
2613
  }
2614
2614
  return write();
@@ -2617,7 +2617,7 @@ function acquireLock(file, staleMs = DEFAULT_LOCK_STALE_MS) {
2617
2617
  }
2618
2618
  function releaseLock(file) {
2619
2619
  try {
2620
- fs19.rmSync(file, { force: true });
2620
+ fs17.rmSync(file, { force: true });
2621
2621
  } catch {
2622
2622
  }
2623
2623
  }
@@ -2627,14 +2627,14 @@ function resolveEmbedModel(model) {
2627
2627
  return model ?? "bge-small-en-v1.5";
2628
2628
  }
2629
2629
  function modelCacheDir() {
2630
- const base = process.env.XDG_CACHE_HOME || path19.join(os.homedir(), ".cache");
2631
- return path19.join(base, "vibgrate", "models");
2630
+ const base = process.env.XDG_CACHE_HOME || path17.join(os.homedir(), ".cache");
2631
+ return path17.join(base, "vibgrate", "models");
2632
2632
  }
2633
2633
  function modelReadyMarker(modelId) {
2634
- return path19.join(modelCacheDir(), `.ready-${safe(modelId)}`);
2634
+ return path17.join(modelCacheDir(), `.ready-${safe(modelId)}`);
2635
2635
  }
2636
2636
  function isModelReady(modelId = resolveEmbedModel()) {
2637
- return fs19.existsSync(modelReadyMarker(modelId));
2637
+ return fs17.existsSync(modelReadyMarker(modelId));
2638
2638
  }
2639
2639
  function unavailableMessage(reason) {
2640
2640
  switch (reason) {
@@ -2651,13 +2651,13 @@ function unavailableMessage(reason) {
2651
2651
  }
2652
2652
  function modelCacheInfo(modelId = resolveEmbedModel()) {
2653
2653
  const dir = modelCacheDir();
2654
- return { dir, present: isModelReady(modelId) || fs19.existsSync(dir), bytes: dirSize(dir) };
2654
+ return { dir, present: isModelReady(modelId) || fs17.existsSync(dir), bytes: dirSize(dir) };
2655
2655
  }
2656
2656
  function clearModelCache() {
2657
2657
  const dir = modelCacheDir();
2658
2658
  const bytes = dirSize(dir);
2659
2659
  try {
2660
- fs19.rmSync(dir, { recursive: true, force: true });
2660
+ fs17.rmSync(dir, { recursive: true, force: true });
2661
2661
  } catch {
2662
2662
  }
2663
2663
  return bytes;
@@ -2666,16 +2666,16 @@ function dirSize(dir) {
2666
2666
  let total = 0;
2667
2667
  let entries;
2668
2668
  try {
2669
- entries = fs19.readdirSync(dir, { withFileTypes: true });
2669
+ entries = fs17.readdirSync(dir, { withFileTypes: true });
2670
2670
  } catch {
2671
2671
  return 0;
2672
2672
  }
2673
2673
  for (const e of entries) {
2674
- const p = path19.join(dir, e.name);
2674
+ const p = path17.join(dir, e.name);
2675
2675
  if (e.isDirectory()) total += dirSize(p);
2676
2676
  else {
2677
2677
  try {
2678
- total += fs19.statSync(p).size;
2678
+ total += fs17.statSync(p).size;
2679
2679
  } catch {
2680
2680
  }
2681
2681
  }
@@ -2687,7 +2687,7 @@ function isPermissionError(e) {
2687
2687
  return code === "EACCES" || code === "EPERM" || code === "EROFS";
2688
2688
  }
2689
2689
  function embeddingsCached(root, modelId) {
2690
- return fs19.existsSync(path19.join(cacheDir(root), `embeddings-${safe(modelId)}.json`));
2690
+ return fs17.existsSync(path17.join(cacheDir(root), `embeddings-${safe(modelId)}.json`));
2691
2691
  }
2692
2692
  async function loadEmbedder(options = {}) {
2693
2693
  if (options.local) return null;
@@ -2701,7 +2701,7 @@ async function loadEmbedder(options = {}) {
2701
2701
  if (!mod?.FlagEmbedding) return fail("not-installed");
2702
2702
  const cache = modelCacheDir();
2703
2703
  try {
2704
- fs19.mkdirSync(cache, { recursive: true });
2704
+ fs17.mkdirSync(cache, { recursive: true });
2705
2705
  } catch (e) {
2706
2706
  return fail(isPermissionError(e) ? "no-permission" : "init-failed");
2707
2707
  }
@@ -2716,7 +2716,7 @@ async function loadEmbedder(options = {}) {
2716
2716
  "embedding model init timed out"
2717
2717
  );
2718
2718
  try {
2719
- fs19.writeFileSync(modelReadyMarker(modelId), (/* @__PURE__ */ new Date(0)).toISOString());
2719
+ fs17.writeFileSync(modelReadyMarker(modelId), (/* @__PURE__ */ new Date(0)).toISOString());
2720
2720
  } catch {
2721
2721
  }
2722
2722
  return {
@@ -2741,7 +2741,7 @@ async function importEmbedBackend() {
2741
2741
  const hostDir = process.env.VIBGRATE_EMBEDDER_PATH;
2742
2742
  if (hostDir) {
2743
2743
  try {
2744
- const req = createRequire(path19.join(hostDir, "package.json"));
2744
+ const req = createRequire(path17.join(hostDir, "package.json"));
2745
2745
  const mod = await import(pathToFileURL(req.resolve("fastembed")).href);
2746
2746
  const resolved = mod?.FlagEmbedding ? mod : mod?.default;
2747
2747
  if (resolved?.FlagEmbedding) return resolved;
@@ -2827,7 +2827,7 @@ function cosine(a, b) {
2827
2827
  var EMBED_CHUNK = 256;
2828
2828
  var CACHE_WRITE_INTERVAL_MS = 1500;
2829
2829
  function vectorCachePath(root, modelId) {
2830
- return path19.join(cacheDir(root), `embeddings-${safe(modelId)}.json`);
2830
+ return path17.join(cacheDir(root), `embeddings-${safe(modelId)}.json`);
2831
2831
  }
2832
2832
  function countPending(graph, root, modelId) {
2833
2833
  const entries = readCacheEntries(vectorCachePath(root, modelId), modelId);
@@ -2842,9 +2842,9 @@ function countPending(graph, root, modelId) {
2842
2842
  return pending;
2843
2843
  }
2844
2844
  function readCacheEntries(file, modelId) {
2845
- if (!fs19.existsSync(file)) return {};
2845
+ if (!fs17.existsSync(file)) return {};
2846
2846
  try {
2847
- const loaded = JSON.parse(fs19.readFileSync(file, "utf8"));
2847
+ const loaded = JSON.parse(fs17.readFileSync(file, "utf8"));
2848
2848
  if (loaded.model === modelId && loaded.entries) return loaded.entries;
2849
2849
  } catch {
2850
2850
  }
@@ -2853,9 +2853,9 @@ function readCacheEntries(file, modelId) {
2853
2853
  async function getNodeEmbeddings(graph, embedder, root, onProgress) {
2854
2854
  const file = vectorCachePath(root, embedder.id);
2855
2855
  let cache = { model: embedder.id, entries: {} };
2856
- if (fs19.existsSync(file)) {
2856
+ if (fs17.existsSync(file)) {
2857
2857
  try {
2858
- const loaded = JSON.parse(fs19.readFileSync(file, "utf8"));
2858
+ const loaded = JSON.parse(fs17.readFileSync(file, "utf8"));
2859
2859
  if (loaded.model === embedder.id && loaded.entries) cache = loaded;
2860
2860
  } catch {
2861
2861
  }
@@ -2873,10 +2873,10 @@ async function getNodeEmbeddings(graph, embedder, root, onProgress) {
2873
2873
  }
2874
2874
  const persist = () => {
2875
2875
  try {
2876
- fs19.mkdirSync(cacheDir(root), { recursive: true });
2876
+ fs17.mkdirSync(cacheDir(root), { recursive: true });
2877
2877
  const tmp = `${file}.tmp.${process.pid}`;
2878
- fs19.writeFileSync(tmp, JSON.stringify(cache));
2879
- fs19.renameSync(tmp, file);
2878
+ fs17.writeFileSync(tmp, JSON.stringify(cache));
2879
+ fs17.renameSync(tmp, file);
2880
2880
  } catch {
2881
2881
  }
2882
2882
  };
@@ -2914,21 +2914,21 @@ function safe(id) {
2914
2914
  }
2915
2915
  var SNAPSHOT_VERSION = "vg-freshness/1";
2916
2916
  function snapshotPath(root) {
2917
- return path19.join(cacheDir(root), "freshness.json");
2917
+ return path17.join(cacheDir(root), "freshness.json");
2918
2918
  }
2919
2919
  function writeSnapshot(root, corpusHash, fileStats, scope = {}) {
2920
2920
  const files = {};
2921
2921
  for (const f of fileStats) files[f.rel] = { size: f.size, mtimeMs: f.mtimeMs, hash: f.hash };
2922
2922
  const snapshot = { version: SNAPSHOT_VERSION, corpusHash, scope: pruneScope(scope), files };
2923
2923
  try {
2924
- fs19.mkdirSync(cacheDir(root), { recursive: true });
2925
- fs19.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2924
+ fs17.mkdirSync(cacheDir(root), { recursive: true });
2925
+ fs17.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2926
2926
  } catch {
2927
2927
  }
2928
2928
  }
2929
2929
  function loadSnapshot(root) {
2930
2930
  try {
2931
- const loaded = JSON.parse(fs19.readFileSync(snapshotPath(root), "utf8"));
2931
+ const loaded = JSON.parse(fs17.readFileSync(snapshotPath(root), "utf8"));
2932
2932
  if (loaded.version === SNAPSHOT_VERSION && loaded.files && typeof loaded.corpusHash === "string") {
2933
2933
  return { ...loaded, scope: loaded.scope ?? {} };
2934
2934
  }
@@ -2964,7 +2964,7 @@ function probeFreshness(root) {
2964
2964
  }
2965
2965
  let stat;
2966
2966
  try {
2967
- stat = fs19.statSync(file.abs);
2967
+ stat = fs17.statSync(file.abs);
2968
2968
  } catch {
2969
2969
  drift.removed.push(file.rel);
2970
2970
  continue;
@@ -2972,7 +2972,7 @@ function probeFreshness(root) {
2972
2972
  if (stat.size === entry.size && stat.mtimeMs === entry.mtimeMs) continue;
2973
2973
  let hash = "";
2974
2974
  try {
2975
- hash = hashBytes(fs19.readFileSync(file.abs));
2975
+ hash = hashBytes(fs17.readFileSync(file.abs));
2976
2976
  } catch {
2977
2977
  drift.removed.push(file.rel);
2978
2978
  continue;
@@ -2989,7 +2989,7 @@ function probeFreshness(root) {
2989
2989
  }
2990
2990
  if (absorbed && !hasDrift(drift)) {
2991
2991
  try {
2992
- fs19.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2992
+ fs17.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2993
2993
  } catch {
2994
2994
  }
2995
2995
  }
@@ -3306,224 +3306,6 @@ function estimateTokens(text) {
3306
3306
  function round2(x) {
3307
3307
  return Math.round(x * 1e6) / 1e6;
3308
3308
  }
3309
- var REFRESH_LOCK_STALE_MS = 10 * 60 * 1e3;
3310
- function refreshLockPath(root) {
3311
- return path19.join(cacheDir(root), "refresh.lock");
3312
- }
3313
- async function refreshIfStale(root, opts = {}) {
3314
- const probe = probeFreshness(root);
3315
- if (!probe) return { status: "no-snapshot" };
3316
- if (!hasDrift(probe.drift)) return { status: "fresh" };
3317
- const lock = refreshLockPath(root);
3318
- if (!acquireLock(lock, REFRESH_LOCK_STALE_MS)) return { status: "locked" };
3319
- const start = process.hrtime.bigint();
3320
- try {
3321
- const { scope } = probe;
3322
- const result = await buildGraph({
3323
- root,
3324
- only: scope.only,
3325
- exclude: scope.exclude,
3326
- paths: scope.paths,
3327
- deep: scope.deep,
3328
- noGround: scope.noGround,
3329
- scip: scope.scip,
3330
- noScip: scope.noScip,
3331
- noTsc: scope.noTsc,
3332
- cluster: scope.cluster,
3333
- grammarsDir: scope.grammarsDir,
3334
- inline: opts.inline,
3335
- jobs: opts.jobs
3336
- });
3337
- const wrote = result.graph.provenance.corpusHash !== probe.corpusHash;
3338
- if (wrote) {
3339
- const dir = vibgrateDir(root);
3340
- writeArtifacts(result.graph, {
3341
- root,
3342
- report: fs19.existsSync(path19.join(dir, "GRAPH_REPORT.md")),
3343
- html: fs19.existsSync(path19.join(dir, "graph.html"))
3344
- });
3345
- }
3346
- writeSnapshot(root, result.graph.provenance.corpusHash, result.fileStats, scope);
3347
- const ms = Number((process.hrtime.bigint() - start) / 1000000n);
3348
- return {
3349
- status: "refreshed",
3350
- drift: probe.drift,
3351
- ms,
3352
- reparsed: result.reparsed,
3353
- totalFiles: result.totalFiles,
3354
- wrote
3355
- };
3356
- } catch (err) {
3357
- return { status: "error", message: err.message };
3358
- } finally {
3359
- releaseLock(lock);
3360
- }
3361
- }
3362
- var LEDGER = "savings.jsonl";
3363
- var PER_FILE_TOKENS = 400;
3364
- var SAVINGS_TOOLS = /* @__PURE__ */ new Set(["query_graph", "get_node"]);
3365
- var CLI_TOOL_ALIASES = {
3366
- ask: "query_graph",
3367
- show: "get_node",
3368
- impact: "impact_of",
3369
- path: "find_path",
3370
- hubs: "list_hubs",
3371
- areas: "list_areas",
3372
- tree: "tree"
3373
- };
3374
- function sanitizeClient(name) {
3375
- if (typeof name !== "string") return "unknown";
3376
- const cleaned = name.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
3377
- return cleaned || "unknown";
3378
- }
3379
- function recordCliCall(root, entry, now) {
3380
- recordSaving(
3381
- root,
3382
- {
3383
- tool: entry.tool,
3384
- source: "cli",
3385
- client: sanitizeClient(entry.client),
3386
- outcome: entry.outcome,
3387
- vgTokens: entry.vgTokens ?? 0,
3388
- baselineTokens: (entry.baselineFiles ?? 0) * PER_FILE_TOKENS
3389
- },
3390
- now
3391
- );
3392
- }
3393
- function savingsLedgerPath(root) {
3394
- return path19.join(cacheDir(root), LEDGER);
3395
- }
3396
- function ledgerPath(root) {
3397
- return savingsLedgerPath(root);
3398
- }
3399
- function savingsRecorded(root) {
3400
- return fs19.existsSync(ledgerPath(root));
3401
- }
3402
- function recordSaving(root, entry, now) {
3403
- try {
3404
- fs19.mkdirSync(cacheDir(root), { recursive: true });
3405
- const line = JSON.stringify({ ts: now, ...entry });
3406
- fs19.appendFileSync(ledgerPath(root), line + "\n");
3407
- } catch {
3408
- }
3409
- }
3410
- var DEFAULT_RATE_PER_M = 3;
3411
- var DEFAULT_RATE_LABEL = "input @ $3/1M";
3412
- function readSavings(root, days, now, ratePerM = DEFAULT_RATE_PER_M) {
3413
- const file = ledgerPath(root);
3414
- const cutoff = now - days * 24 * 60 * 60 * 1e3;
3415
- let queries = 0;
3416
- let vgTokens = 0;
3417
- let baselineTokens = 0;
3418
- if (fs19.existsSync(file)) {
3419
- for (const line of fs19.readFileSync(file, "utf8").split("\n")) {
3420
- if (!line.trim()) continue;
3421
- try {
3422
- const e = JSON.parse(line);
3423
- if (e.ts < cutoff) continue;
3424
- if (!SAVINGS_TOOLS.has(e.tool)) continue;
3425
- queries++;
3426
- vgTokens += e.vgTokens;
3427
- baselineTokens += e.baselineTokens;
3428
- } catch {
3429
- }
3430
- }
3431
- }
3432
- const estCostVg = vgTokens / 1e6 * ratePerM;
3433
- const estCostBaseline = baselineTokens / 1e6 * ratePerM;
3434
- return {
3435
- enabled: savingsRecorded(root),
3436
- days,
3437
- queries,
3438
- vgTokens,
3439
- baselineTokens,
3440
- ratio: vgTokens > 0 ? Math.round(baselineTokens / vgTokens * 100) / 100 : 0,
3441
- estCostVg: round22(estCostVg),
3442
- estCostBaseline: round22(estCostBaseline),
3443
- saved: round22(estCostBaseline - estCostVg),
3444
- rateLabel: DEFAULT_RATE_LABEL
3445
- };
3446
- }
3447
- function round22(x) {
3448
- return Math.round(x * 100) / 100;
3449
- }
3450
- function toDimensionStats(byKey) {
3451
- return [...byKey.entries()].map(([key, r]) => {
3452
- const calls = r.complete + r.partial + r.miss;
3453
- return {
3454
- key,
3455
- calls,
3456
- complete: r.complete,
3457
- partial: r.partial,
3458
- miss: r.miss,
3459
- successPct: calls ? Math.round((r.complete + r.partial) / calls * 100) : 0
3460
- };
3461
- }).sort((a, b) => b.calls - a.calls || a.key.localeCompare(b.key));
3462
- }
3463
- function readUsage(root, days, now) {
3464
- const file = ledgerPath(root);
3465
- const cutoff = now - days * 24 * 60 * 60 * 1e3;
3466
- const byTool = /* @__PURE__ */ new Map();
3467
- const bySource = /* @__PURE__ */ new Map();
3468
- const byClient = /* @__PURE__ */ new Map();
3469
- const msByTool = /* @__PURE__ */ new Map();
3470
- const bump2 = (m, key, outcome) => {
3471
- const row = m.get(key) ?? { complete: 0, partial: 0, miss: 0 };
3472
- row[outcome]++;
3473
- m.set(key, row);
3474
- };
3475
- if (fs19.existsSync(file)) {
3476
- for (const line of fs19.readFileSync(file, "utf8").split("\n")) {
3477
- if (!line.trim()) continue;
3478
- try {
3479
- const e = JSON.parse(line);
3480
- if (e.ts < cutoff) continue;
3481
- const outcome = e.outcome ?? "complete";
3482
- bump2(byTool, e.tool, outcome);
3483
- bump2(bySource, e.source ?? "mcp", outcome);
3484
- bump2(byClient, e.client ?? "unknown", outcome);
3485
- if (typeof e.ms === "number" && e.ms >= 0) {
3486
- const timing = msByTool.get(e.tool) ?? { sum: 0, n: 0 };
3487
- timing.sum += e.ms;
3488
- timing.n++;
3489
- msByTool.set(e.tool, timing);
3490
- }
3491
- } catch {
3492
- }
3493
- }
3494
- }
3495
- const commands = toDimensionStats(byTool).map((d) => {
3496
- const timing = msByTool.get(d.key);
3497
- return {
3498
- tool: d.key,
3499
- calls: d.calls,
3500
- complete: d.complete,
3501
- partial: d.partial,
3502
- miss: d.miss,
3503
- successPct: d.successPct,
3504
- avgMs: timing && timing.n > 0 ? Math.round(timing.sum / timing.n) : null
3505
- };
3506
- });
3507
- const totals = commands.reduce(
3508
- (t, c) => ({
3509
- calls: t.calls + c.calls,
3510
- complete: t.complete + c.complete,
3511
- partial: t.partial + c.partial,
3512
- miss: t.miss + c.miss
3513
- }),
3514
- { calls: 0, complete: 0, partial: 0, miss: 0 }
3515
- );
3516
- const avgSuccessPct = commands.length ? Math.round(commands.reduce((s, c) => s + c.successPct, 0) / commands.length) : 0;
3517
- return {
3518
- enabled: savingsRecorded(root),
3519
- days,
3520
- commands,
3521
- sources: toDimensionStats(bySource),
3522
- clients: toDimensionStats(byClient),
3523
- totals,
3524
- avgSuccessPct
3525
- };
3526
- }
3527
3309
 
3528
3310
  // src/engine/lookup.ts
3529
3311
  function findNodes(graph, query) {
@@ -3681,10 +3463,10 @@ function testsToRun(graph, rootId, depth = 4) {
3681
3463
  };
3682
3464
  }
3683
3465
  function detectRunner(root, lang) {
3684
- const pkgPath = path19.join(root, "package.json");
3685
- if (fs19.existsSync(pkgPath)) {
3466
+ const pkgPath = path17.join(root, "package.json");
3467
+ if (fs17.existsSync(pkgPath)) {
3686
3468
  try {
3687
- const pkg = JSON.parse(fs19.readFileSync(pkgPath, "utf8"));
3469
+ const pkg = JSON.parse(fs17.readFileSync(pkgPath, "utf8"));
3688
3470
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
3689
3471
  if (deps.vitest) return { name: "vitest", command: (f) => `npx vitest run ${f.join(" ")}`.trim() };
3690
3472
  if (deps.jest) return { name: "jest", command: (f) => `npx jest ${f.join(" ")}`.trim() };
@@ -3692,10 +3474,10 @@ function detectRunner(root, lang) {
3692
3474
  } catch {
3693
3475
  }
3694
3476
  }
3695
- if (lang === "py" || fs19.existsSync(path19.join(root, "pyproject.toml")) || fs19.existsSync(path19.join(root, "pytest.ini"))) {
3477
+ if (lang === "py" || fs17.existsSync(path17.join(root, "pyproject.toml")) || fs17.existsSync(path17.join(root, "pytest.ini"))) {
3696
3478
  return { name: "pytest", command: (f) => `pytest ${f.join(" ")}`.trim() };
3697
3479
  }
3698
- if (lang === "go" || fs19.existsSync(path19.join(root, "go.mod"))) {
3480
+ if (lang === "go" || fs17.existsSync(path17.join(root, "go.mod"))) {
3699
3481
  return { name: "go test", command: () => "go test ./..." };
3700
3482
  }
3701
3483
  if (lang === "cs" || hasFile(root, /\.csproj$/) || hasFile(root, /\.sln$/)) {
@@ -3706,7 +3488,7 @@ function detectRunner(root, lang) {
3706
3488
  }
3707
3489
  function hasFile(root, re) {
3708
3490
  try {
3709
- return fs19.readdirSync(root).some((f) => re.test(f));
3491
+ return fs17.readdirSync(root).some((f) => re.test(f));
3710
3492
  } catch {
3711
3493
  return false;
3712
3494
  }
@@ -3778,7 +3560,7 @@ function findManifests(root) {
3778
3560
  if (depth > 8 || scanned > MAX_ENTRIES2) return;
3779
3561
  let entries;
3780
3562
  try {
3781
- entries = fs19.readdirSync(dir, { withFileTypes: true });
3563
+ entries = fs17.readdirSync(dir, { withFileTypes: true });
3782
3564
  } catch {
3783
3565
  return;
3784
3566
  }
@@ -3786,11 +3568,11 @@ function findManifests(root) {
3786
3568
  scanned++;
3787
3569
  if (scanned > MAX_ENTRIES2) break;
3788
3570
  if (e.isDirectory()) {
3789
- if (!SKIP_DIRS2.has(e.name) && !e.name.startsWith(".")) walk2(path19.join(dir, e.name), depth + 1);
3571
+ if (!SKIP_DIRS2.has(e.name) && !e.name.startsWith(".")) walk2(path17.join(dir, e.name), depth + 1);
3790
3572
  continue;
3791
3573
  }
3792
3574
  const eco = MANIFEST_BY_FILE[e.name] ?? (/\.(cs|fs)proj$/i.test(e.name) ? "dotnet" : void 0);
3793
- if (eco) set[eco].push(path19.join(dir, e.name));
3575
+ if (eco) set[eco].push(path17.join(dir, e.name));
3794
3576
  }
3795
3577
  };
3796
3578
  walk2(root, 0);
@@ -3802,11 +3584,11 @@ function npmDeps(files) {
3802
3584
  for (const file of files) {
3803
3585
  let pkg;
3804
3586
  try {
3805
- pkg = JSON.parse(fs19.readFileSync(file, "utf8"));
3587
+ pkg = JSON.parse(fs17.readFileSync(file, "utf8"));
3806
3588
  } catch {
3807
3589
  continue;
3808
3590
  }
3809
- const dir = path19.dirname(file);
3591
+ const dir = path17.dirname(file);
3810
3592
  const all = { ...pkg.dependencies, ...pkg.devDependencies };
3811
3593
  for (const [name, declared] of Object.entries(all)) {
3812
3594
  out.push({ name, ecosystem: "npm", declared, installed: installedNpmVersion(dir, name) });
@@ -3817,12 +3599,12 @@ function npmDeps(files) {
3817
3599
  function installedNpmVersion(dir, name) {
3818
3600
  let cur = dir;
3819
3601
  for (let i = 0; i < 12; i++) {
3820
- const p = path19.join(cur, "node_modules", name, "package.json");
3602
+ const p = path17.join(cur, "node_modules", name, "package.json");
3821
3603
  try {
3822
- return JSON.parse(fs19.readFileSync(p, "utf8")).version;
3604
+ return JSON.parse(fs17.readFileSync(p, "utf8")).version;
3823
3605
  } catch {
3824
3606
  }
3825
- const parent = path19.dirname(cur);
3607
+ const parent = path17.dirname(cur);
3826
3608
  if (parent === cur) break;
3827
3609
  cur = parent;
3828
3610
  }
@@ -3839,21 +3621,21 @@ function normalizePep503(name) {
3839
3621
  function sitePackagesDirs(base) {
3840
3622
  const out = [];
3841
3623
  try {
3842
- for (const d of fs19.readdirSync(path19.join(base, "lib"))) {
3843
- if (d.startsWith("python")) out.push(path19.join(base, "lib", d, "site-packages"));
3624
+ for (const d of fs17.readdirSync(path17.join(base, "lib"))) {
3625
+ if (d.startsWith("python")) out.push(path17.join(base, "lib", d, "site-packages"));
3844
3626
  }
3845
3627
  } catch {
3846
3628
  }
3847
- out.push(path19.join(base, "Lib", "site-packages"));
3629
+ out.push(path17.join(base, "Lib", "site-packages"));
3848
3630
  return out;
3849
3631
  }
3850
3632
  function installedPypiVersion(root, name) {
3851
3633
  const target = normalizePep503(name);
3852
3634
  for (const venv of [".venv", "venv", "env", ".tox"]) {
3853
- for (const sp of sitePackagesDirs(path19.join(root, venv))) {
3635
+ for (const sp of sitePackagesDirs(path17.join(root, venv))) {
3854
3636
  let entries;
3855
3637
  try {
3856
- entries = fs19.readdirSync(sp);
3638
+ entries = fs17.readdirSync(sp);
3857
3639
  } catch {
3858
3640
  continue;
3859
3641
  }
@@ -3868,7 +3650,7 @@ function installedPypiVersion(root, name) {
3868
3650
  function installedPhpVersion(root, name) {
3869
3651
  let data;
3870
3652
  try {
3871
- data = JSON.parse(fs19.readFileSync(path19.join(root, "vendor", "composer", "installed.json"), "utf8"));
3653
+ data = JSON.parse(fs17.readFileSync(path17.join(root, "vendor", "composer", "installed.json"), "utf8"));
3872
3654
  } catch {
3873
3655
  return void 0;
3874
3656
  }
@@ -3887,11 +3669,11 @@ function pypiDeps(files) {
3887
3669
  for (const file of files) {
3888
3670
  let text;
3889
3671
  try {
3890
- text = fs19.readFileSync(file, "utf8");
3672
+ text = fs17.readFileSync(file, "utf8");
3891
3673
  } catch {
3892
3674
  continue;
3893
3675
  }
3894
- if (path19.basename(file) === "requirements.txt") {
3676
+ if (path17.basename(file) === "requirements.txt") {
3895
3677
  for (const line of text.split("\n")) {
3896
3678
  const t = line.trim();
3897
3679
  if (!t || t.startsWith("#") || t.startsWith("-")) continue;
@@ -3975,7 +3757,7 @@ function goDeps(files) {
3975
3757
  for (const mod of files) {
3976
3758
  let text;
3977
3759
  try {
3978
- text = fs19.readFileSync(mod, "utf8");
3760
+ text = fs17.readFileSync(mod, "utf8");
3979
3761
  } catch {
3980
3762
  continue;
3981
3763
  }
@@ -3993,7 +3775,7 @@ function cargoDeps(files) {
3993
3775
  for (const file of files) {
3994
3776
  let text;
3995
3777
  try {
3996
- text = fs19.readFileSync(file, "utf8");
3778
+ text = fs17.readFileSync(file, "utf8");
3997
3779
  } catch {
3998
3780
  continue;
3999
3781
  }
@@ -4025,7 +3807,7 @@ function rubyDeps(files) {
4025
3807
  for (const file of files) {
4026
3808
  let text;
4027
3809
  try {
4028
- text = fs19.readFileSync(file, "utf8");
3810
+ text = fs17.readFileSync(file, "utf8");
4029
3811
  } catch {
4030
3812
  continue;
4031
3813
  }
@@ -4040,7 +3822,7 @@ function phpDeps(files) {
4040
3822
  for (const file of files) {
4041
3823
  let pkg;
4042
3824
  try {
4043
- pkg = JSON.parse(fs19.readFileSync(file, "utf8"));
3825
+ pkg = JSON.parse(fs17.readFileSync(file, "utf8"));
4044
3826
  } catch {
4045
3827
  continue;
4046
3828
  }
@@ -4057,7 +3839,7 @@ function dotnetDeps(files) {
4057
3839
  for (const file of files) {
4058
3840
  let text;
4059
3841
  try {
4060
- text = fs19.readFileSync(file, "utf8");
3842
+ text = fs17.readFileSync(file, "utf8");
4061
3843
  } catch {
4062
3844
  continue;
4063
3845
  }
@@ -4077,7 +3859,7 @@ function swiftDeps(files) {
4077
3859
  for (const file of files) {
4078
3860
  let text;
4079
3861
  try {
4080
- text = fs19.readFileSync(file, "utf8");
3862
+ text = fs17.readFileSync(file, "utf8");
4081
3863
  } catch {
4082
3864
  continue;
4083
3865
  }
@@ -4099,7 +3881,7 @@ function dartDeps(files) {
4099
3881
  for (const file of files) {
4100
3882
  let text;
4101
3883
  try {
4102
- text = fs19.readFileSync(file, "utf8");
3884
+ text = fs17.readFileSync(file, "utf8");
4103
3885
  } catch {
4104
3886
  continue;
4105
3887
  }
@@ -4124,11 +3906,11 @@ function javaDeps(files) {
4124
3906
  for (const file of files) {
4125
3907
  let text;
4126
3908
  try {
4127
- text = fs19.readFileSync(file, "utf8");
3909
+ text = fs17.readFileSync(file, "utf8");
4128
3910
  } catch {
4129
3911
  continue;
4130
3912
  }
4131
- if (path19.basename(file) === "pom.xml") {
3913
+ if (path17.basename(file) === "pom.xml") {
4132
3914
  for (const block of text.match(/<dependency>[\s\S]*?<\/dependency>/g) ?? []) {
4133
3915
  const g = /<groupId>([^<]+)<\/groupId>/.exec(block);
4134
3916
  const a = /<artifactId>([^<]+)<\/artifactId>/.exec(block);
@@ -4174,10 +3956,10 @@ function discoverModels(home = os.homedir()) {
4174
3956
  );
4175
3957
  }
4176
3958
  function ollama(home) {
4177
- const base = path19.join(home, ".ollama", "models", "manifests");
3959
+ const base = path17.join(home, ".ollama", "models", "manifests");
4178
3960
  const out = [];
4179
3961
  walk(base, 4, (file) => {
4180
- const rel2 = path19.relative(base, file).split(path19.sep);
3962
+ const rel2 = path17.relative(base, file).split(path17.sep);
4181
3963
  if (rel2.length >= 2) {
4182
3964
  const tag = rel2[rel2.length - 1];
4183
3965
  const model = rel2[rel2.length - 2];
@@ -4187,24 +3969,24 @@ function ollama(home) {
4187
3969
  return dedupe(out);
4188
3970
  }
4189
3971
  function lmStudio(home) {
4190
- const bases = [path19.join(home, ".lmstudio", "models"), path19.join(home, ".cache", "lm-studio", "models")];
3972
+ const bases = [path17.join(home, ".lmstudio", "models"), path17.join(home, ".cache", "lm-studio", "models")];
4191
3973
  const out = [];
4192
3974
  for (const base of bases) {
4193
3975
  walk(base, 5, (file) => {
4194
3976
  if (!file.endsWith(".gguf")) return;
4195
- const rel2 = path19.relative(base, file).replace(/\\/g, "/");
3977
+ const rel2 = path17.relative(base, file).replace(/\\/g, "/");
4196
3978
  out.push({ runtime: "lm-studio", name: rel2, path: file });
4197
3979
  });
4198
3980
  }
4199
3981
  return dedupe(out);
4200
3982
  }
4201
3983
  function looseGguf(home) {
4202
- const bases = [path19.join(home, "models"), path19.join(home, ".cache", "huggingface")];
3984
+ const bases = [path17.join(home, "models"), path17.join(home, ".cache", "huggingface")];
4203
3985
  const out = [];
4204
3986
  for (const base of bases) {
4205
3987
  walk(base, 4, (file) => {
4206
3988
  if (!file.endsWith(".gguf")) return;
4207
- out.push({ runtime: "gguf", name: path19.basename(file), path: file });
3989
+ out.push({ runtime: "gguf", name: path17.basename(file), path: file });
4208
3990
  });
4209
3991
  }
4210
3992
  return dedupe(out);
@@ -4213,12 +3995,12 @@ function walk(dir, depth, onFile) {
4213
3995
  if (depth < 0) return;
4214
3996
  let entries;
4215
3997
  try {
4216
- entries = fs19.readdirSync(dir, { withFileTypes: true });
3998
+ entries = fs17.readdirSync(dir, { withFileTypes: true });
4217
3999
  } catch {
4218
4000
  return;
4219
4001
  }
4220
4002
  for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
4221
- const abs = path19.join(dir, e.name);
4003
+ const abs = path17.join(dir, e.name);
4222
4004
  if (e.isDirectory()) walk(abs, depth - 1, onFile);
4223
4005
  else if (e.isFile()) onFile(abs);
4224
4006
  }
@@ -4249,7 +4031,7 @@ function lockfileVersion(root, ecosystem, name) {
4249
4031
  function gradleLock(root, name) {
4250
4032
  let text;
4251
4033
  try {
4252
- text = fs19.readFileSync(path19.join(root, "gradle.lockfile"), "utf8");
4034
+ text = fs17.readFileSync(path17.join(root, "gradle.lockfile"), "utf8");
4253
4035
  } catch {
4254
4036
  return void 0;
4255
4037
  }
@@ -4259,7 +4041,7 @@ function gradleLock(root, name) {
4259
4041
  function packageResolved(root, name) {
4260
4042
  let data;
4261
4043
  try {
4262
- data = JSON.parse(fs19.readFileSync(path19.join(root, "Package.resolved"), "utf8"));
4044
+ data = JSON.parse(fs17.readFileSync(path17.join(root, "Package.resolved"), "utf8"));
4263
4045
  } catch {
4264
4046
  return void 0;
4265
4047
  }
@@ -4275,7 +4057,7 @@ function packageResolved(root, name) {
4275
4057
  function pubspecLock(root, name) {
4276
4058
  let text;
4277
4059
  try {
4278
- text = fs19.readFileSync(path19.join(root, "pubspec.lock"), "utf8");
4060
+ text = fs17.readFileSync(path17.join(root, "pubspec.lock"), "utf8");
4279
4061
  } catch {
4280
4062
  return void 0;
4281
4063
  }
@@ -4299,7 +4081,7 @@ function escapeRegExp(s) {
4299
4081
  function gemfileLock(root, name) {
4300
4082
  let text;
4301
4083
  try {
4302
- text = fs19.readFileSync(path19.join(root, "Gemfile.lock"), "utf8");
4084
+ text = fs17.readFileSync(path17.join(root, "Gemfile.lock"), "utf8");
4303
4085
  } catch {
4304
4086
  return void 0;
4305
4087
  }
@@ -4309,7 +4091,7 @@ function gemfileLock(root, name) {
4309
4091
  function composerLock(root, name) {
4310
4092
  let data;
4311
4093
  try {
4312
- data = JSON.parse(fs19.readFileSync(path19.join(root, "composer.lock"), "utf8"));
4094
+ data = JSON.parse(fs17.readFileSync(path17.join(root, "composer.lock"), "utf8"));
4313
4095
  } catch {
4314
4096
  return void 0;
4315
4097
  }
@@ -4324,7 +4106,7 @@ function composerLock(root, name) {
4324
4106
  function packagesLock(root, name) {
4325
4107
  let data;
4326
4108
  try {
4327
- data = JSON.parse(fs19.readFileSync(path19.join(root, "packages.lock.json"), "utf8"));
4109
+ data = JSON.parse(fs17.readFileSync(path17.join(root, "packages.lock.json"), "utf8"));
4328
4110
  } catch {
4329
4111
  return void 0;
4330
4112
  }
@@ -4349,7 +4131,7 @@ function pypiLockVersion(root, name) {
4349
4131
  function tomlPackageLock(root, file, name, normalize3) {
4350
4132
  let text;
4351
4133
  try {
4352
- text = fs19.readFileSync(path19.join(root, file), "utf8");
4134
+ text = fs17.readFileSync(path17.join(root, file), "utf8");
4353
4135
  } catch {
4354
4136
  return void 0;
4355
4137
  }
@@ -4366,7 +4148,7 @@ function tomlPackageLock(root, file, name, normalize3) {
4366
4148
  function pipfileLock(root, name) {
4367
4149
  let data;
4368
4150
  try {
4369
- data = JSON.parse(fs19.readFileSync(path19.join(root, "Pipfile.lock"), "utf8"));
4151
+ data = JSON.parse(fs17.readFileSync(path17.join(root, "Pipfile.lock"), "utf8"));
4370
4152
  } catch {
4371
4153
  return void 0;
4372
4154
  }
@@ -4385,7 +4167,7 @@ function pipfileLock(root, name) {
4385
4167
  function packageLockVersion(root, name) {
4386
4168
  let data;
4387
4169
  try {
4388
- data = JSON.parse(fs19.readFileSync(path19.join(root, "package-lock.json"), "utf8"));
4170
+ data = JSON.parse(fs17.readFileSync(path17.join(root, "package-lock.json"), "utf8"));
4389
4171
  } catch {
4390
4172
  return void 0;
4391
4173
  }
@@ -4397,7 +4179,7 @@ function packageLockVersion(root, name) {
4397
4179
  function yarnLockVersion(root, name) {
4398
4180
  let text;
4399
4181
  try {
4400
- text = fs19.readFileSync(path19.join(root, "yarn.lock"), "utf8");
4182
+ text = fs17.readFileSync(path17.join(root, "yarn.lock"), "utf8");
4401
4183
  } catch {
4402
4184
  return void 0;
4403
4185
  }
@@ -4425,19 +4207,19 @@ function headerNamesPackage(header, name) {
4425
4207
  // src/engine/lib.ts
4426
4208
  var LIB_SCHEMA = "vg-lib/1.0";
4427
4209
  function catalogPath(root) {
4428
- return path19.join(root, "vibgrate.lib.json");
4210
+ return path17.join(root, "vibgrate.lib.json");
4429
4211
  }
4430
4212
  function libDir(root) {
4431
- return path19.join(root, ".vibgrate", "lib");
4213
+ return path17.join(root, ".vibgrate", "lib");
4432
4214
  }
4433
4215
  function libId(name) {
4434
4216
  return name.trim().toLowerCase().replace(/^@/, "").replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
4435
4217
  }
4436
4218
  function loadCatalog(root) {
4437
4219
  const file = catalogPath(root);
4438
- if (fs19.existsSync(file)) {
4220
+ if (fs17.existsSync(file)) {
4439
4221
  try {
4440
- const data = JSON.parse(fs19.readFileSync(file, "utf8"));
4222
+ const data = JSON.parse(fs17.readFileSync(file, "utf8"));
4441
4223
  if (data.schemaVersion === LIB_SCHEMA && data.libraries) return data;
4442
4224
  } catch {
4443
4225
  }
@@ -4445,7 +4227,7 @@ function loadCatalog(root) {
4445
4227
  return { schemaVersion: LIB_SCHEMA, libraries: {} };
4446
4228
  }
4447
4229
  function saveCatalog(root, catalog) {
4448
- fs19.writeFileSync(catalogPath(root), `${stableStringify(catalog, 2)}
4230
+ fs17.writeFileSync(catalogPath(root), `${stableStringify(catalog, 2)}
4449
4231
  `);
4450
4232
  }
4451
4233
  function resolveLib(catalog, name) {
@@ -4521,23 +4303,23 @@ async function addLibrary(source, opts) {
4521
4303
  content = await res.text();
4522
4304
  type = source.endsWith("llms.txt") ? "llms.txt" : source.endsWith(".json") ? "openapi" : "website";
4523
4305
  } else {
4524
- const abs = path19.resolve(root, source);
4525
- if (!fs19.existsSync(abs)) throw new Error(`source not found: ${source}`);
4306
+ const abs = path17.resolve(root, source);
4307
+ if (!fs17.existsSync(abs)) throw new Error(`source not found: ${source}`);
4526
4308
  content = readLocal(abs);
4527
4309
  type = "local";
4528
4310
  }
4529
4311
  const name = opts.name ?? inferName(source);
4530
4312
  const id = libId(name);
4531
4313
  const version = opts.version ?? installedVersion(root, name) ?? "*";
4532
- fs19.mkdirSync(libDir(root), { recursive: true });
4533
- const docFileAbs = path19.join(libDir(root), `${id}.md`);
4534
- fs19.writeFileSync(docFileAbs, content);
4314
+ fs17.mkdirSync(libDir(root), { recursive: true });
4315
+ const docFileAbs = path17.join(libDir(root), `${id}.md`);
4316
+ fs17.writeFileSync(docFileAbs, content);
4535
4317
  const entry = {
4536
4318
  id,
4537
4319
  name,
4538
4320
  version,
4539
4321
  source: { type, location: source },
4540
- docFile: path19.relative(root, docFileAbs).split(path19.sep).join("/"),
4322
+ docFile: path17.relative(root, docFileAbs).split(path17.sep).join("/"),
4541
4323
  docHash: hashString(content),
4542
4324
  bytes: Buffer.byteLength(content, "utf8")
4543
4325
  };
@@ -4547,15 +4329,15 @@ async function addLibrary(source, opts) {
4547
4329
  return entry;
4548
4330
  }
4549
4331
  function readDoc(root, entry) {
4550
- const abs = path19.resolve(root, entry.docFile);
4551
- return fs19.existsSync(abs) ? fs19.readFileSync(abs, "utf8") : "";
4332
+ const abs = path17.resolve(root, entry.docFile);
4333
+ return fs17.existsSync(abs) ? fs17.readFileSync(abs, "utf8") : "";
4552
4334
  }
4553
4335
  function npmPackageDir(root, name) {
4554
4336
  let cur = root;
4555
4337
  for (let i = 0; i < 12; i++) {
4556
- const dir = path19.join(cur, "node_modules", name);
4557
- if (fs19.existsSync(path19.join(dir, "package.json"))) return dir;
4558
- const parent = path19.dirname(cur);
4338
+ const dir = path17.join(cur, "node_modules", name);
4339
+ if (fs17.existsSync(path17.join(dir, "package.json"))) return dir;
4340
+ const parent = path17.dirname(cur);
4559
4341
  if (parent === cur) break;
4560
4342
  cur = parent;
4561
4343
  }
@@ -4566,24 +4348,24 @@ function localPackageDocs(root, name) {
4566
4348
  if (!dir) return void 0;
4567
4349
  const version = resolveVersion(root, name).served;
4568
4350
  for (const f of ["llms.txt", "README.md", "README.mdx", "README", "readme.md"]) {
4569
- const p = path19.join(dir, f);
4351
+ const p = path17.join(dir, f);
4570
4352
  try {
4571
- const docs = fs19.readFileSync(p, "utf8");
4353
+ const docs = fs17.readFileSync(p, "utf8");
4572
4354
  if (docs.trim()) {
4573
- return { docs, version, source: path19.relative(root, p).split(path19.sep).join("/") };
4355
+ return { docs, version, source: path17.relative(root, p).split(path17.sep).join("/") };
4574
4356
  }
4575
4357
  } catch {
4576
4358
  }
4577
4359
  }
4578
4360
  try {
4579
- const pkg = JSON.parse(fs19.readFileSync(path19.join(dir, "package.json"), "utf8"));
4361
+ const pkg = JSON.parse(fs17.readFileSync(path17.join(dir, "package.json"), "utf8"));
4580
4362
  if (pkg.description?.trim()) {
4581
4363
  return {
4582
4364
  docs: `# ${name}
4583
4365
 
4584
4366
  ${pkg.description}`,
4585
4367
  version: version ?? pkg.version,
4586
- source: path19.relative(root, path19.join(dir, "package.json")).split(path19.sep).join("/")
4368
+ source: path17.relative(root, path17.join(dir, "package.json")).split(path17.sep).join("/")
4587
4369
  };
4588
4370
  }
4589
4371
  } catch {
@@ -4592,17 +4374,17 @@ ${pkg.description}`,
4592
4374
  }
4593
4375
  function dtsEntry(dir) {
4594
4376
  try {
4595
- const pkg = JSON.parse(fs19.readFileSync(path19.join(dir, "package.json"), "utf8"));
4377
+ const pkg = JSON.parse(fs17.readFileSync(path17.join(dir, "package.json"), "utf8"));
4596
4378
  const rel2 = pkg.types ?? pkg.typings;
4597
4379
  if (typeof rel2 === "string") {
4598
- const p = path19.join(dir, rel2);
4599
- if (fs19.existsSync(p)) return p;
4380
+ const p = path17.join(dir, rel2);
4381
+ if (fs17.existsSync(p)) return p;
4600
4382
  }
4601
4383
  } catch {
4602
4384
  }
4603
4385
  for (const c of ["index.d.ts", "dist/index.d.ts", "lib/index.d.ts", "types/index.d.ts"]) {
4604
- const p = path19.join(dir, c);
4605
- if (fs19.existsSync(p)) return p;
4386
+ const p = path17.join(dir, c);
4387
+ if (fs17.existsSync(p)) return p;
4606
4388
  }
4607
4389
  return void 0;
4608
4390
  }
@@ -4635,7 +4417,7 @@ function localApiSurface(root, name) {
4635
4417
  if (!dts) return void 0;
4636
4418
  let src;
4637
4419
  try {
4638
- src = fs19.readFileSync(dts, "utf8");
4420
+ src = fs17.readFileSync(dts, "utf8");
4639
4421
  } catch {
4640
4422
  return void 0;
4641
4423
  }
@@ -4643,36 +4425,36 @@ function localApiSurface(root, name) {
4643
4425
  return decls.length ? decls.join("\n\n") : void 0;
4644
4426
  }
4645
4427
  function cloneAndReadGit(url) {
4646
- const dir = fs19.mkdtempSync(path19.join(os.tmpdir(), "vg-lib-git-"));
4428
+ const dir = fs17.mkdtempSync(path17.join(os.tmpdir(), "vg-lib-git-"));
4647
4429
  try {
4648
4430
  execFileSync("git", ["clone", "--depth", "1", "--quiet", url, dir], {
4649
4431
  stdio: ["ignore", "ignore", "pipe"]
4650
4432
  });
4651
4433
  } catch (err) {
4652
- fs19.rmSync(dir, { recursive: true, force: true });
4434
+ fs17.rmSync(dir, { recursive: true, force: true });
4653
4435
  const detail = err instanceof Error && err.message ? `: ${err.message.split("\n")[0]}` : "";
4654
4436
  throw new Error(`git clone failed for ${url}${detail}`);
4655
4437
  }
4656
4438
  try {
4657
4439
  return readGitDocs(dir);
4658
4440
  } finally {
4659
- fs19.rmSync(dir, { recursive: true, force: true });
4441
+ fs17.rmSync(dir, { recursive: true, force: true });
4660
4442
  }
4661
4443
  }
4662
4444
  function readGitDocs(dir) {
4663
- const docsDir = path19.join(dir, "docs");
4664
- if (fs19.existsSync(docsDir) && fs19.statSync(docsDir).isDirectory()) return readLocal(docsDir);
4665
- const readme = ["README.md", "README.mdx", "README.txt", "README.rst", "readme.md"].map((f) => path19.join(dir, f)).find((p) => fs19.existsSync(p));
4666
- if (readme) return fs19.readFileSync(readme, "utf8");
4445
+ const docsDir = path17.join(dir, "docs");
4446
+ if (fs17.existsSync(docsDir) && fs17.statSync(docsDir).isDirectory()) return readLocal(docsDir);
4447
+ const readme = ["README.md", "README.mdx", "README.txt", "README.rst", "readme.md"].map((f) => path17.join(dir, f)).find((p) => fs17.existsSync(p));
4448
+ if (readme) return fs17.readFileSync(readme, "utf8");
4667
4449
  return readLocal(dir);
4668
4450
  }
4669
4451
  function readLocal(abs) {
4670
- const stat = fs19.statSync(abs);
4671
- if (stat.isFile()) return fs19.readFileSync(abs, "utf8");
4452
+ const stat = fs17.statSync(abs);
4453
+ if (stat.isFile()) return fs17.readFileSync(abs, "utf8");
4672
4454
  const parts = [];
4673
- const files = fs19.readdirSync(abs, { withFileTypes: true }).filter((e) => e.isFile() && /\.(md|mdx|txt|rst)$/i.test(e.name)).map((e) => e.name).sort();
4455
+ const files = fs17.readdirSync(abs, { withFileTypes: true }).filter((e) => e.isFile() && /\.(md|mdx|txt|rst)$/i.test(e.name)).map((e) => e.name).sort();
4674
4456
  for (const f of files) parts.push(`<!-- ${f} -->
4675
- ${fs19.readFileSync(path19.join(abs, f), "utf8")}`);
4457
+ ${fs17.readFileSync(path17.join(abs, f), "utf8")}`);
4676
4458
  return parts.join("\n\n");
4677
4459
  }
4678
4460
  function inferName(source) {
@@ -4681,10 +4463,10 @@ function inferName(source) {
4681
4463
  return base.replace(/\.(md|mdx|txt|rst|json)$/i, "").replace(/^llms$/, "docs");
4682
4464
  }
4683
4465
  function findGitRoot(startDir = process.cwd()) {
4684
- let dir = path19.resolve(startDir);
4466
+ let dir = path17.resolve(startDir);
4685
4467
  for (; ; ) {
4686
- if (fs19.existsSync(path19.join(dir, ".git"))) return dir;
4687
- const parent = path19.dirname(dir);
4468
+ if (fs17.existsSync(path17.join(dir, ".git"))) return dir;
4469
+ const parent = path17.dirname(dir);
4688
4470
  if (parent === dir) return null;
4689
4471
  dir = parent;
4690
4472
  }
@@ -4695,19 +4477,19 @@ function normalizeEntry(line) {
4695
4477
  function ensureGitignored(entry, startDir = process.cwd()) {
4696
4478
  const root = findGitRoot(startDir);
4697
4479
  if (!root) return { status: "not-a-repo", entry };
4698
- const gitignorePath = path19.join(root, ".gitignore");
4480
+ const gitignorePath = path17.join(root, ".gitignore");
4699
4481
  const target = normalizeEntry(entry);
4700
4482
  let existing = "";
4701
4483
  let fileExisted = true;
4702
4484
  try {
4703
- existing = fs19.readFileSync(gitignorePath, "utf8");
4485
+ existing = fs17.readFileSync(gitignorePath, "utf8");
4704
4486
  } catch {
4705
4487
  fileExisted = false;
4706
4488
  }
4707
4489
  const alreadyPresent = existing.split(/\r?\n/).some((line) => normalizeEntry(line) === target);
4708
4490
  if (alreadyPresent) return { status: "present", entry, gitignorePath };
4709
4491
  const needsLeadingNewline = existing.length > 0 && !existing.endsWith("\n");
4710
- fs19.appendFileSync(gitignorePath, `${needsLeadingNewline ? "\n" : ""}${entry}
4492
+ fs17.appendFileSync(gitignorePath, `${needsLeadingNewline ? "\n" : ""}${entry}
4711
4493
  `, "utf8");
4712
4494
  return { status: fileExisted ? "added" : "created", entry, gitignorePath };
4713
4495
  }
@@ -4861,14 +4643,14 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
4861
4643
  console.log(chalk.dim("The secret must be registered on your Vibgrate ingest API."));
4862
4644
  }
4863
4645
  if (opts.write) {
4864
- const writePath = path19.resolve(opts.write);
4646
+ const writePath = path17.resolve(opts.write);
4865
4647
  await writeTextFile(writePath, dsn + "\n");
4866
4648
  console.log("");
4867
4649
  console.log(chalk.green("\u2714") + ` DSN written to ${opts.write}`);
4868
- const root = findGitRoot(path19.dirname(writePath));
4869
- const rel2 = root ? path19.relative(root, writePath).split(path19.sep).join("/") : "";
4650
+ const root = findGitRoot(path17.dirname(writePath));
4651
+ const rel2 = root ? path17.relative(root, writePath).split(path17.sep).join("/") : "";
4870
4652
  if (root && rel2 && !rel2.startsWith("..")) {
4871
- const res = ensureGitignored(rel2, path19.dirname(writePath));
4653
+ const res = ensureGitignored(rel2, path17.dirname(writePath));
4872
4654
  if (res.status === "created") {
4873
4655
  console.log(chalk.green("\u2714") + ` Created .gitignore and ignored ${rel2}`);
4874
4656
  } else if (res.status === "added") {
@@ -4886,30 +4668,30 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
4886
4668
  var STORE_DIRNAME = ".vibgrate";
4887
4669
  var STORE_FILENAME = "credentials.json";
4888
4670
  function homeCredentialsPath() {
4889
- return path19.join(os.homedir(), STORE_DIRNAME, STORE_FILENAME);
4671
+ return path17.join(os.homedir(), STORE_DIRNAME, STORE_FILENAME);
4890
4672
  }
4891
4673
  function projectCredentialsPath(cwd = process.cwd()) {
4892
4674
  const root = findGitRoot(cwd) ?? cwd;
4893
- return path19.join(root, STORE_DIRNAME, STORE_FILENAME);
4675
+ return path17.join(root, STORE_DIRNAME, STORE_FILENAME);
4894
4676
  }
4895
4677
  function credentialsPath(opts = {}) {
4896
4678
  const override = process.env.VIBGRATE_CREDENTIALS?.trim();
4897
- if (override) return path19.resolve(override);
4679
+ if (override) return path17.resolve(override);
4898
4680
  const local = projectCredentialsPath(opts.cwd);
4899
4681
  if (opts.local) return local;
4900
- if (fs19.existsSync(local)) return local;
4682
+ if (fs17.existsSync(local)) return local;
4901
4683
  return homeCredentialsPath();
4902
4684
  }
4903
4685
  function gitignoreEntryForCredentials(repoRoot, credsFile = credentialsPath()) {
4904
- const rel2 = path19.relative(repoRoot, credsFile);
4905
- if (rel2 && !rel2.startsWith("..") && !path19.isAbsolute(rel2)) {
4906
- return rel2.split(path19.sep).join("/");
4686
+ const rel2 = path17.relative(repoRoot, credsFile);
4687
+ if (rel2 && !rel2.startsWith("..") && !path17.isAbsolute(rel2)) {
4688
+ return rel2.split(path17.sep).join("/");
4907
4689
  }
4908
4690
  return `${STORE_DIRNAME}/${STORE_FILENAME}`;
4909
4691
  }
4910
4692
  function readStoredCredentials(opts = {}) {
4911
4693
  try {
4912
- const raw = fs19.readFileSync(credentialsPath(opts), "utf8");
4694
+ const raw = fs17.readFileSync(credentialsPath(opts), "utf8");
4913
4695
  const parsed = JSON.parse(raw);
4914
4696
  return parsed && typeof parsed.dsn === "string" && parsed.dsn ? parsed : null;
4915
4697
  } catch {
@@ -4918,16 +4700,16 @@ function readStoredCredentials(opts = {}) {
4918
4700
  }
4919
4701
  function writeStoredCredentials(creds, opts = {}) {
4920
4702
  const file = credentialsPath(opts);
4921
- fs19.mkdirSync(path19.dirname(file), { recursive: true });
4922
- fs19.writeFileSync(file, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf8", mode: 384 });
4703
+ fs17.mkdirSync(path17.dirname(file), { recursive: true });
4704
+ fs17.writeFileSync(file, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf8", mode: 384 });
4923
4705
  try {
4924
- fs19.chmodSync(file, 384);
4706
+ fs17.chmodSync(file, 384);
4925
4707
  } catch {
4926
4708
  }
4927
4709
  }
4928
4710
  function clearStoredCredentials(opts = {}) {
4929
4711
  try {
4930
- fs19.rmSync(credentialsPath(opts));
4712
+ fs17.rmSync(credentialsPath(opts));
4931
4713
  return true;
4932
4714
  } catch {
4933
4715
  return false;
@@ -5298,7 +5080,7 @@ var pushCommand = new Command("push").description("Push scan results to Vibgrate
5298
5080
  if (opts.strict) process.exit(1);
5299
5081
  return;
5300
5082
  }
5301
- const filePath = path19.resolve(opts.file);
5083
+ const filePath = path17.resolve(opts.file);
5302
5084
  if (!await pathExists(filePath)) {
5303
5085
  console.error(chalk.red(`Scan artifact not found: ${filePath}`));
5304
5086
  console.error(chalk.dim('Run "vibgrate scan" first.'));
@@ -5361,8 +5143,8 @@ var pushCommand = new Command("push").description("Push scan results to Vibgrate
5361
5143
  });
5362
5144
  function readScanArtifact(root) {
5363
5145
  try {
5364
- const file = path19.join(root, ".vibgrate", "scan_result.json");
5365
- return JSON.parse(fs19.readFileSync(file, "utf8"));
5146
+ const file = path17.join(root, ".vibgrate", "scan_result.json");
5147
+ return JSON.parse(fs17.readFileSync(file, "utf8"));
5366
5148
  } catch {
5367
5149
  return null;
5368
5150
  }
@@ -5752,7 +5534,7 @@ var CACHE_VERSION2 = "vg-hosted-docs/1";
5752
5534
  var HOSTED_DOCS_TTL_MS = 24 * 60 * 60 * 1e3;
5753
5535
  var MAX_ENTRIES = 64;
5754
5536
  function cachePath2(root) {
5755
- return path19.join(cacheDir(root), "hosted-docs.json");
5537
+ return path17.join(cacheDir(root), "hosted-docs.json");
5756
5538
  }
5757
5539
  function hostedDocsCacheKey(req, opts = {}) {
5758
5540
  const material = JSON.stringify([
@@ -5768,7 +5550,7 @@ function hostedDocsCacheKey(req, opts = {}) {
5768
5550
  }
5769
5551
  function loadFile(root) {
5770
5552
  try {
5771
- const parsed = JSON.parse(fs19.readFileSync(cachePath2(root), "utf8"));
5553
+ const parsed = JSON.parse(fs17.readFileSync(cachePath2(root), "utf8"));
5772
5554
  if (parsed && parsed.version === CACHE_VERSION2 && parsed.entries && typeof parsed.entries === "object") return parsed;
5773
5555
  } catch {
5774
5556
  }
@@ -5782,8 +5564,8 @@ function saveFile(root, file) {
5782
5564
  delete file.entries[k];
5783
5565
  }
5784
5566
  }
5785
- fs19.mkdirSync(cacheDir(root), { recursive: true });
5786
- fs19.writeFileSync(cachePath2(root), JSON.stringify(file));
5567
+ fs17.mkdirSync(cacheDir(root), { recursive: true });
5568
+ fs17.writeFileSync(cachePath2(root), JSON.stringify(file));
5787
5569
  } catch {
5788
5570
  }
5789
5571
  }
@@ -5883,13 +5665,19 @@ var ROW_CAP = 1e4;
5883
5665
  async function scanCandidates(root, files, needle, opts) {
5884
5666
  const needleLower = needle.toLowerCase();
5885
5667
  if (!needleLower || files.length === 0) return { hits: [], total: 0, truncated: false };
5886
- if (!opts.collectAll) return scanInline(root, files, needleLower, opts.budget, true, void 0);
5887
5668
  if (files.length < PARALLEL_MIN_FILES && !process.env.VG_PARALLEL_MIN_BYTES) {
5888
- return scanInline(root, files, needleLower, ROW_CAP, false, void 0);
5669
+ return opts.collectAll ? scanInline(root, files, needleLower, ROW_CAP, false, void 0) : scanInline(root, files, needleLower, opts.budget, true, void 0);
5889
5670
  }
5890
5671
  const { over, sizes } = measure(root, files);
5891
- if (!over) return scanInline(root, files, needleLower, ROW_CAP, false, sizes);
5892
- return scanParallel(root, files, needleLower);
5672
+ if (!over) {
5673
+ return opts.collectAll ? scanInline(root, files, needleLower, ROW_CAP, false, sizes) : scanInline(root, files, needleLower, opts.budget, true, sizes);
5674
+ }
5675
+ const out = await scanParallel(root, files, needleLower);
5676
+ if (!opts.collectAll && out.hits.length > opts.budget) {
5677
+ out.hits.length = opts.budget;
5678
+ out.truncated = true;
5679
+ }
5680
+ return out;
5893
5681
  }
5894
5682
  function parallelMinBytes() {
5895
5683
  const v = Number(process.env.VG_PARALLEL_MIN_BYTES);
@@ -5902,7 +5690,7 @@ function measure(root, files) {
5902
5690
  for (const rel2 of files) {
5903
5691
  let size;
5904
5692
  try {
5905
- size = fs19.statSync(path19.join(root, rel2)).size;
5693
+ size = fs17.statSync(path17.join(root, rel2)).size;
5906
5694
  } catch {
5907
5695
  continue;
5908
5696
  }
@@ -5925,9 +5713,9 @@ function matchLines(text, needleLower) {
5925
5713
  }
5926
5714
  function readCandidate(abs, knownSize) {
5927
5715
  try {
5928
- const size = knownSize ?? fs19.statSync(abs).size;
5716
+ const size = knownSize ?? fs17.statSync(abs).size;
5929
5717
  if (size > MAX_FILE_BYTES) return null;
5930
- const text = fs19.readFileSync(abs, "utf8");
5718
+ const text = fs17.readFileSync(abs, "utf8");
5931
5719
  return text.includes(NUL) ? null : text;
5932
5720
  } catch {
5933
5721
  return null;
@@ -5938,7 +5726,7 @@ function scanInline(root, files, needleLower, cap, stopAtCap, sizes) {
5938
5726
  let total = 0;
5939
5727
  let truncated = false;
5940
5728
  for (const rel2 of files) {
5941
- const text = readCandidate(path19.join(root, rel2), sizes?.get(rel2));
5729
+ const text = readCandidate(path17.join(root, rel2), sizes?.get(rel2));
5942
5730
  if (text === null) continue;
5943
5731
  for (const h of matchLines(text, needleLower)) {
5944
5732
  total++;
@@ -6020,8 +5808,70 @@ function runWorker(root, files, needleLower) {
6020
5808
  }
6021
5809
 
6022
5810
  // src/engine/search.ts
6023
- var IGNORE_DIRS = /* @__PURE__ */ new Set([".git", ".vibgrate", "node_modules", "dist", "build", "out", "target", "vendor", "__pycache__"]);
5811
+ var IGNORE_DIRS = /* @__PURE__ */ new Set([
5812
+ ".git",
5813
+ ".vibgrate",
5814
+ "node_modules",
5815
+ "dist",
5816
+ "build",
5817
+ "out",
5818
+ "target",
5819
+ "vendor",
5820
+ "__pycache__",
5821
+ // .NET intermediate output and coverage reports: purely generated trees that a
5822
+ // C#/coverage-heavy repo pays for on every literal sweep. `bin` is deliberately
5823
+ // NOT here — plenty of repos keep real scripts in bin/ (rails, node CLIs), and
5824
+ // completeness beats speed; the binary-extension skip below keeps a .NET bin/
5825
+ // cheap anyway.
5826
+ "obj",
5827
+ "coverage"
5828
+ ]);
6024
5829
  var MAX_FILES_SCANNED = 2e4;
5830
+ var BINARY_EXTS = /* @__PURE__ */ new Set([
5831
+ ".dll",
5832
+ ".pdb",
5833
+ ".exe",
5834
+ ".so",
5835
+ ".dylib",
5836
+ ".a",
5837
+ ".lib",
5838
+ ".o",
5839
+ ".class",
5840
+ ".jar",
5841
+ ".wasm",
5842
+ ".node",
5843
+ ".png",
5844
+ ".jpg",
5845
+ ".jpeg",
5846
+ ".gif",
5847
+ ".webp",
5848
+ ".ico",
5849
+ ".icns",
5850
+ ".bmp",
5851
+ ".zip",
5852
+ ".gz",
5853
+ ".tgz",
5854
+ ".bz2",
5855
+ ".xz",
5856
+ ".7z",
5857
+ ".rar",
5858
+ ".woff",
5859
+ ".woff2",
5860
+ ".ttf",
5861
+ ".otf",
5862
+ ".eot",
5863
+ ".mp3",
5864
+ ".mp4",
5865
+ ".mov",
5866
+ ".avi",
5867
+ ".pdf"
5868
+ ]);
5869
+ function hasBinaryExt(rel2) {
5870
+ return BINARY_EXTS.has(path17.extname(rel2).toLowerCase());
5871
+ }
5872
+ function toPosix2(rel2) {
5873
+ return path17.sep === "\\" ? rel2.replaceAll("\\", "/") : rel2;
5874
+ }
6025
5875
  async function searchSymbols(graph, root, query, limit) {
6026
5876
  const q2 = query.trim();
6027
5877
  if (!q2) return { matches: [], moreAvailable: false, hint: "query is required" };
@@ -6046,7 +5896,8 @@ async function searchSymbols(graph, root, query, limit) {
6046
5896
  const textHits = [];
6047
5897
  let truncatedScan = false;
6048
5898
  let totalTextMatches;
6049
- if (spare > 0) {
5899
+ const skipScan = !isPhrase && hasExactSymbolMatch(nodes, q2);
5900
+ if (spare > 0 && !skipScan) {
6050
5901
  const seen = new Set(symbolHits.map((h) => `${h.file}:${h.line}`));
6051
5902
  const scan = await scanFiles(root, q2, spare, seen, textHits, isPhrase);
6052
5903
  truncatedScan = scan.truncated;
@@ -6069,6 +5920,12 @@ async function searchSymbols(graph, root, query, limit) {
6069
5920
  }
6070
5921
  return result;
6071
5922
  }
5923
+ function hasExactSymbolMatch(nodes, q2) {
5924
+ if (nodes.length === 0) return false;
5925
+ if (/^(.*):(\d+)$/.test(q2)) return true;
5926
+ const lower = q2.toLowerCase();
5927
+ return nodes.some((n) => n.id === q2 || n.qualifiedName.toLowerCase() === lower || n.name.toLowerCase() === lower);
5928
+ }
6072
5929
  function queryTokens(q2) {
6073
5930
  return [...new Set(q2.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 2))];
6074
5931
  }
@@ -6107,7 +5964,23 @@ async function scanFiles(root, needle, budget, seen, out, countAll) {
6107
5964
  return { total: outcome.total - overlap, truncated: listing.truncated || outcome.truncated };
6108
5965
  }
6109
5966
  function listCandidateFiles(root, needle) {
6110
- return ripgrepCandidates(root, needle) ?? walkCandidates(root);
5967
+ return ripgrepCandidates(root, needle) ?? walkCandidatesCached(root);
5968
+ }
5969
+ var LISTING_TTL_MS = 2e3;
5970
+ var listingCache = /* @__PURE__ */ new Map();
5971
+ function listingTtlMs() {
5972
+ const v = Number(process.env.VG_LISTING_TTL_MS);
5973
+ return Number.isFinite(v) && v >= 0 ? v : LISTING_TTL_MS;
5974
+ }
5975
+ function walkCandidatesCached(root) {
5976
+ const ttl = listingTtlMs();
5977
+ if (ttl > 0) {
5978
+ const hit = listingCache.get(root);
5979
+ if (hit && Date.now() - hit.at < ttl) return hit.listing;
5980
+ }
5981
+ const listing = walkCandidates(root);
5982
+ if (ttl > 0) listingCache.set(root, { listing, at: Date.now() });
5983
+ return listing;
6111
5984
  }
6112
5985
  function walkCandidates(root) {
6113
5986
  const files = [];
@@ -6117,22 +5990,23 @@ function walkCandidates(root) {
6117
5990
  const dir = stack.pop();
6118
5991
  let entries;
6119
5992
  try {
6120
- entries = fs19.readdirSync(dir, { withFileTypes: true });
5993
+ entries = fs17.readdirSync(dir, { withFileTypes: true });
6121
5994
  } catch {
6122
5995
  continue;
6123
5996
  }
6124
5997
  for (const e of entries) {
6125
5998
  if (e.name.startsWith(".") || IGNORE_DIRS.has(e.name)) continue;
6126
- const abs = path19.join(dir, e.name);
5999
+ const abs = path17.join(dir, e.name);
6127
6000
  if (e.isDirectory()) {
6128
6001
  stack.push(abs);
6129
6002
  continue;
6130
6003
  }
6004
+ if (hasBinaryExt(e.name)) continue;
6131
6005
  if (files.length >= MAX_FILES_SCANNED) {
6132
6006
  truncated = true;
6133
6007
  continue;
6134
6008
  }
6135
- files.push(path19.relative(root, abs));
6009
+ files.push(toPosix2(path17.relative(root, abs)));
6136
6010
  }
6137
6011
  }
6138
6012
  files.sort();
@@ -6159,17 +6033,23 @@ function ripgrepCandidates(root, needle) {
6159
6033
  { cwd: root, encoding: "utf8", timeout: 15e3, maxBuffer: 32 * 1024 * 1024 }
6160
6034
  );
6161
6035
  if (res.error || res.status !== 0 && res.status !== 1 || res.signal) return null;
6036
+ const files = parseRgFileList(res.stdout);
6037
+ if (files.length > MAX_FILES_SCANNED) return { files: files.slice(0, MAX_FILES_SCANNED), truncated: true };
6038
+ return { files, truncated: false };
6039
+ }
6040
+ function parseRgFileList(stdout, sep10 = path17.sep) {
6162
6041
  const seen = /* @__PURE__ */ new Set();
6163
- for (const raw of res.stdout.split("\n")) {
6164
- const rel2 = raw.startsWith("./") ? raw.slice(2) : raw;
6042
+ for (const raw of stdout.split("\n")) {
6043
+ if (!raw) continue;
6044
+ const posix3 = sep10 === "\\" ? raw.replaceAll("\\", "/") : raw;
6045
+ const rel2 = posix3.startsWith("./") ? posix3.slice(2) : posix3;
6165
6046
  if (!rel2) continue;
6166
6047
  const segs = rel2.split("/");
6167
6048
  if (segs.some((s) => s.startsWith(".") || IGNORE_DIRS.has(s))) continue;
6049
+ if (hasBinaryExt(rel2)) continue;
6168
6050
  seen.add(rel2);
6169
6051
  }
6170
- const files = [...seen].sort();
6171
- if (files.length > MAX_FILES_SCANNED) return { files: files.slice(0, MAX_FILES_SCANNED), truncated: true };
6172
- return { files, truncated: false };
6052
+ return [...seen].sort();
6173
6053
  }
6174
6054
 
6175
6055
  // src/mcp/tools.ts
@@ -6848,211 +6728,19 @@ function navigationToolsetConfig(serverName = "vibgrate") {
6848
6728
  configs: Object.fromEntries(HOT_TOOLS.map((n) => [n, { defer_loading: false }]))
6849
6729
  };
6850
6730
  }
6851
- var PROBE_INTERVAL_MS = 2e3;
6852
- var MAX_PROBE_INTERVAL_MS = 3e4;
6853
- var PROBE_DUTY_FACTOR = 20;
6854
- var REFRESH_BUDGET_MS = 5e3;
6855
- var FAILURE_COOLDOWN_MS = 6e4;
6856
- var GraphSource = class {
6857
- constructor(graphPath, refresh = false, tuning = {}) {
6858
- this.graphPath = graphPath;
6859
- this.refresh = refresh;
6860
- this.tuning = tuning;
6861
- this.root = path19.dirname(path19.dirname(graphPath));
6862
- }
6863
- graphPath;
6864
- refresh;
6865
- tuning;
6866
- cachedMtimeMs = -1;
6867
- cached = null;
6868
- root;
6869
- lastProbeAt = 0;
6870
- failedUntil = 0;
6871
- inflight = null;
6872
- /** Self-tuned: grows with measured probe cost so huge repos aren't penalized. */
6873
- probeIntervalMs = PROBE_INTERVAL_MS;
6874
- /** Current graph: auto-refreshed if the tree drifted, reloaded if the file changed. */
6875
- async get() {
6876
- if (this.refresh) await this.maybeRefresh();
6877
- const stat = fs19.statSync(this.graphPath);
6878
- if (stat.mtimeMs !== this.cachedMtimeMs || !this.cached) {
6879
- this.cached = parseGraph(fs19.readFileSync(this.graphPath, "utf8"));
6880
- this.cachedMtimeMs = stat.mtimeMs;
6881
- }
6882
- return this.cached;
6883
- }
6884
- /**
6885
- * Debounced, single-flight refresh. Never throws — a refresh problem must
6886
- * degrade to "answer from the current map", not break the tool call.
6887
- */
6888
- async maybeRefresh() {
6889
- const now = Date.now();
6890
- if (!this.inflight) {
6891
- const interval = this.tuning.probeIntervalMs ?? this.probeIntervalMs;
6892
- if (now < this.failedUntil || now - this.lastProbeAt < interval) return;
6893
- this.lastProbeAt = now;
6894
- this.inflight = refreshIfStale(this.root).then((r) => {
6895
- if (r.status === "error") this.failedUntil = Date.now() + FAILURE_COOLDOWN_MS;
6896
- if (r.status === "fresh" || r.status === "no-snapshot" || r.status === "locked") {
6897
- const cost = Date.now() - now;
6898
- this.probeIntervalMs = Math.min(
6899
- MAX_PROBE_INTERVAL_MS,
6900
- Math.max(PROBE_INTERVAL_MS, cost * PROBE_DUTY_FACTOR)
6901
- );
6902
- }
6903
- }).catch(() => {
6904
- this.failedUntil = Date.now() + FAILURE_COOLDOWN_MS;
6905
- }).finally(() => {
6906
- this.inflight = null;
6907
- });
6908
- }
6909
- await Promise.race([this.inflight, sleep(this.tuning.refreshBudgetMs ?? REFRESH_BUDGET_MS)]);
6910
- }
6911
- };
6912
- function createServer(source, opts = {}) {
6913
- const { savings = false, shareStats = false, local = false, dedup = false, stats } = opts;
6914
- const record = savings || shareStats;
6915
- const root = path19.dirname(path19.dirname(source.graphPath));
6916
- const seen = /* @__PURE__ */ new Set();
6917
- const server = new Server(
6918
- { name: "vg", version: VERSION },
6919
- {
6920
- capabilities: { tools: {} },
6921
- // Routing guidance once at the server level (hosts that surface
6922
- // `instructions` get it at zero per-step schema cost): the flashlight
6923
- // vs the map.
6924
- instructions: 'vg is a code map. Use search_symbols to find a known name or literal string fast \u2014 a multi-word/quoted phrase runs a complete literal sweep and reports totalTextMatches, so reach for it instead of grep even for plain-string "find every occurrence" lookups. Use orient/query_graph for meaning: symptoms, relationships, and what-breaks-if. Responses are concise by default; pass response_format:"detailed" only when a node proves load-bearing. Navigate as little as possible: one good search/query usually locates the code. As soon as you have the file and line, read that file and make the edit \u2014 do not call further graph tools unless the edit fails or the match was wrong. For how-do-I-use-this-library questions call resolve_library once, then library_docs with the returned targetId and a focused query: the docs are official and matched to the version THIS project has installed (drift-annotated) \u2014 prefer them over web search or training-data recall when they conflict. Skip them for language built-ins or APIs already shown in context. If two library_docs calls have not surfaced the section you need, read the package source under node_modules instead of searching again.'
6925
- }
6926
- );
6927
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
6928
- tools: TOOLS.map((t) => ({
6929
- name: t.name,
6930
- description: t.description,
6931
- inputSchema: t.inputSchema,
6932
- annotations: { readOnlyHint: true, openWorldHint: false }
6933
- }))
6934
- }));
6935
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
6936
- const tool = TOOLS.find((t) => t.name === request.params.name);
6937
- if (!tool) {
6938
- return errorResult(`unknown tool "${request.params.name}"`);
6939
- }
6940
- let graph;
6941
- try {
6942
- graph = await source.get();
6943
- } catch {
6944
- return errorResult(
6945
- "no code map found. Run `vg` in the project to build .vibgrate/graph.json, then retry."
6946
- );
6947
- }
6948
- const startedAt = Date.now();
6949
- try {
6950
- const args = request.params.arguments ?? {};
6951
- const result = await tool.handler(graph, args, { root, local, dedup, seen });
6952
- const ms = Date.now() - startedAt;
6953
- stats?.record({ ...measureCall(tool.name, result), client: sanitizeClient(detectClient(server)), ms });
6954
- if (record) recordUsage(root, tool.name, result, detectClient(server), ms);
6955
- return renderToolResult(result);
6956
- } catch (err) {
6957
- stats?.record({
6958
- tool: tool.name,
6959
- client: sanitizeClient(detectClient(server)),
6960
- outcome: "miss",
6961
- ms: Date.now() - startedAt,
6962
- vgTokens: 0,
6963
- baselineTokens: 0
6964
- });
6965
- return errorResult(`tool "${tool.name}" failed: ${err.message}`);
6966
- }
6967
- });
6968
- return server;
6969
- }
6970
- async function serveStdio(graphPath, opts = {}) {
6971
- const source = new GraphSource(graphPath, opts.refresh !== false);
6972
- const server = createServer(source, opts);
6973
- warmEmbedderInBackground(opts.local);
6974
- await server.connect(new StdioServerTransport());
6975
- }
6976
- function sleep(ms) {
6977
- return new Promise((resolve12) => {
6978
- const timer = setTimeout(resolve12, ms);
6979
- timer.unref?.();
6980
- });
6981
- }
6982
- function recordUsage(root, tool, result, client, ms) {
6983
- recordSaving(
6984
- root,
6985
- { ...measureCall(tool, result), source: "mcp", client: sanitizeClient(client), ...ms !== void 0 ? { ms } : {} },
6986
- Date.now()
6987
- );
6988
- }
6989
- function measureCall(tool, result) {
6990
- const outcome = classifyOutcome(result);
6991
- let vgTokens = 0;
6992
- let baselineTokens = 0;
6993
- if (SAVINGS_TOOLS.has(tool) && result && typeof result === "object") {
6994
- vgTokens = countTokens(renderedText(renderToolResult(result)));
6995
- baselineTokens = referencedFiles(result).size * PER_FILE_TOKENS;
6996
- }
6997
- return { tool, outcome, vgTokens, baselineTokens };
6998
- }
6999
- function detectClient(server) {
7000
- try {
7001
- const info = server.getClientVersion?.();
7002
- return typeof info?.name === "string" ? info.name : void 0;
7003
- } catch {
7004
- return void 0;
7005
- }
7006
- }
7007
- function classifyOutcome(result) {
7008
- if (Array.isArray(result)) return result.length === 0 ? "miss" : "complete";
7009
- if (!result || typeof result !== "object") return "miss";
7010
- const r = result;
7011
- if (typeof r.error === "string" && r.error) return "miss";
7012
- if (r.connected === false) return "miss";
7013
- if (Array.isArray(r.matches) && r.matches.length === 0) return "miss";
7014
- return isPartial(r) ? "partial" : "complete";
7015
- }
7016
- function isPartial(r) {
7017
- if (r.moreAvailable === true) return true;
7018
- if (r._truncated && typeof r._truncated === "object") return true;
7019
- for (const [key, value] of Object.entries(r)) {
7020
- if (typeof value !== "number" || !key.endsWith("Total")) continue;
7021
- const base = key.slice(0, -"Total".length);
7022
- const shown = Array.isArray(r[base]) ? r[base].length : 0;
7023
- if (value > shown) return true;
7024
- }
7025
- return false;
7026
- }
7027
- function renderedText(rendered) {
7028
- const block = rendered.content?.find((b) => b.type === "text");
7029
- return block && "text" in block && typeof block.text === "string" ? block.text : "";
7030
- }
7031
- function referencedFiles(result) {
7032
- const r = result;
7033
- const files = /* @__PURE__ */ new Set();
7034
- if (typeof r.file === "string" && r.file) files.add(r.file);
7035
- for (const m of asArray(r.matches)) {
7036
- const f = m.file;
7037
- if (typeof f === "string" && f) files.add(f);
7038
- }
7039
- for (const name of [...asArray(r.calls), ...asArray(r.calledBy)]) {
7040
- if (typeof name !== "string") continue;
7041
- const i = name.indexOf(":");
7042
- if (i > 0) files.add(name.slice(0, i));
7043
- }
7044
- return files;
7045
- }
7046
- function asArray(v) {
7047
- return Array.isArray(v) ? v : [];
7048
- }
7049
- function errorResult(message) {
7050
- return { content: [{ type: "text", text: message }], isError: true };
7051
- }
7052
6731
 
7053
6732
  // src/install/content.ts
7054
6733
  var NUDGE_BEGIN = "<!-- vg:begin -->";
7055
6734
  var NUDGE_END = "<!-- vg:end -->";
6735
+ var INSTALL_CONTENT_VERSION = 2;
6736
+ var VERSION_MARKER_RE = /<!--\s*vg:v(\d+)\b[^>]*-->/;
6737
+ function versionMarker() {
6738
+ return `<!-- vg:v${INSTALL_CONTENT_VERSION} \xB7 managed by \`vg install\` \u2014 auto-refreshed when these instructions evolve; remove this line to opt out -->`;
6739
+ }
6740
+ function installedContentVersion(text) {
6741
+ const m = VERSION_MARKER_RE.exec(text);
6742
+ return m ? Number(m[1]) : null;
6743
+ }
7056
6744
  function clientFlag(client) {
7057
6745
  return client ? `--client=${client}` : "--client=<your-ai>";
7058
6746
  }
@@ -7063,26 +6751,31 @@ name: vg
7063
6751
  description: Query the local code graph (vg) for structure, impact, and navigation instead of grepping/reading many files.
7064
6752
  ---
7065
6753
 
6754
+ ${versionMarker()}
6755
+
7066
6756
  # vg \u2014 the code map
7067
6757
 
7068
6758
  This repo has a deterministic code graph built by \`vg\`. Prefer it over reading or
7069
6759
  grepping many files \u2014 it is smaller, more relevant context, and free.
7070
6760
 
7071
- ## Prefer the MCP tools
6761
+ ## Use the MCP tools \u2014 not the CLI
7072
6762
 
7073
- If the \`vg\` MCP server is registered (it is after \`vg install\`), call its
7074
- read-only tools directly \u2014 they are the **fastest** path. The server keeps the
7075
- map parsed, the relation index warm, and the embedding model loaded across calls,
7076
- so each query is cheaper than spawning the CLI fresh. Use:
6763
+ When the \`vg\` MCP server is registered (it is after \`vg install\`), **always call
6764
+ its read-only tools instead of shelling out to the CLI.** The server keeps the
6765
+ map parsed, the relation index warm, and the embedding model loaded across
6766
+ calls, so an MCP call answers in milliseconds \u2014 while every CLI invocation pays
6767
+ Node startup plus a fresh map parse, hundreds of times more. Tools:
7077
6768
  \`query_graph\`, \`get_node\`, \`impact_of\`, \`find_path\`, \`list_hubs\`, \`list_areas\`,
7078
6769
  \`get_graph_summary\`, \`search_symbols\`. They are side-effect-free and
7079
6770
  auto-approvable, and the server records which client is calling automatically.
6771
+ Reach for the CLI only when the MCP server is genuinely unavailable \u2014 never as
6772
+ the first resort.
7080
6773
 
7081
- ## If you use the \`vg\` CLI instead
6774
+ ## CLI fallback \u2014 only when the MCP server is unavailable
7082
6775
 
7083
- When the MCP server isn't available, use the CLI \u2014 and **always pass \`${cf}\`**
7084
- so your calls are counted (that's how the CLI-vs-MCP split is measured and the
7085
- tools improved):
6776
+ If (and only if) no \`vg\` MCP server is available, use the CLI \u2014 and **always
6777
+ pass \`${cf}\`** so your calls are counted (that's how the CLI-vs-MCP split is
6778
+ measured and the tools improved):
7086
6779
 
7087
6780
  - **Understand code:** \`vg "<question>" ${cf}\` \u2014 a budget-bounded, fact-annotated context block.
7088
6781
  - **Find a symbol:** \`vg show <name> ${cf}\` \u2014 what it is, what it calls, what calls it.
@@ -7125,22 +6818,28 @@ function nudgeMarkdown(smallRepo, client) {
7125
6818
  const cf = clientFlag(client);
7126
6819
  if (smallRepo) {
7127
6820
  return `${NUDGE_BEGIN}
6821
+ ${versionMarker()}
7128
6822
  ## Code navigation (vg)
7129
6823
 
7130
6824
  This repo is small enough that searching files directly is fine. Still, before a
7131
- change, \`vg impact <name> ${cf}\` shows what it breaks, and \`vg "<question>" ${cf}\`
7132
- gives a compact, cited context block when you want one. Prefer the \`vg\` MCP tools
7133
- when the server is registered; when you use the CLI, pass \`${cf}\` so the call is
7134
- counted.
6825
+ change, the \`vg\` graph shows what breaks: use the \`vg\` MCP tools when the server
6826
+ is registered (\`impact_of\`, \`query_graph\` \u2014 the warm server answers far faster
6827
+ than spawning the CLI). Only without the server, fall back to the CLI and pass
6828
+ \`${cf}\` so the call is counted: \`vg impact <name> ${cf}\`,
6829
+ \`vg "<question>" ${cf}\`.
7135
6830
  ${NUDGE_END}`;
7136
6831
  }
7137
6832
  return `${NUDGE_BEGIN}
7138
- ## Code navigation (vg) \u2014 prefer the map
6833
+ ${versionMarker()}
6834
+ ## Code navigation (vg) \u2014 use the MCP tools first
7139
6835
 
7140
6836
  A deterministic code graph is available. It is usually the faster, cheaper path
7141
- than reading or grepping many files. Prefer the \`vg\` MCP tools when the server is
7142
- registered (fastest \u2014 the map and model stay warm across calls). When you use the
7143
- CLI instead, pass \`${cf}\` so the call is counted:
6837
+ than reading or grepping many files. **When the \`vg\` MCP server is registered,
6838
+ always use its tools** (\`query_graph\`, \`get_node\`, \`impact_of\`,
6839
+ \`search_symbols\`, \u2026) \u2014 the map, indexes, and embedding model stay warm across
6840
+ calls, so they answer in milliseconds, while every CLI invocation pays Node
6841
+ startup plus a fresh map parse. Fall back to the CLI only when the server is
6842
+ unavailable, and then pass \`${cf}\` so the call is counted:
7144
6843
 
7145
6844
  - \`vg "<question>" ${cf}\` \u2014 a compact, fact-annotated context block for a question.
7146
6845
  - \`vg show <name> ${cf}\` \u2014 what a symbol is, its callers and callees.
@@ -7236,12 +6935,12 @@ function detectAssistants(root, opts = {}) {
7236
6935
  const which = opts.which ?? whichOnPath;
7237
6936
  const found = [];
7238
6937
  for (const assistant of ASSISTANTS) {
7239
- const repoHit = (assistant.markers ?? []).find((m) => fs19.existsSync(path19.join(root, m)));
6938
+ const repoHit = (assistant.markers ?? []).find((m) => fs17.existsSync(path17.join(root, m)));
7240
6939
  if (repoHit) {
7241
6940
  found.push({ assistant, via: "repo", marker: repoHit });
7242
6941
  continue;
7243
6942
  }
7244
- const homeHit = (assistant.homeMarkers ?? []).find((m) => fs19.existsSync(path19.join(home, m)));
6943
+ const homeHit = (assistant.homeMarkers ?? []).find((m) => fs17.existsSync(path17.join(home, m)));
7245
6944
  if (homeHit) {
7246
6945
  found.push({ assistant, via: "home", marker: homeHit });
7247
6946
  continue;
@@ -7273,19 +6972,19 @@ function installAssistant(a, opts) {
7273
6972
  const skipped = [];
7274
6973
  let note;
7275
6974
  if (a.skill) {
7276
- writeFileEnsured(path19.join(opts.root, a.skill), skillMarkdown(a.id));
6975
+ writeFileEnsured(path17.join(opts.root, a.skill), skillMarkdown(a.id));
7277
6976
  wrote.push(a.skill);
7278
6977
  }
7279
6978
  if (a.mcp) {
7280
6979
  const launch = opts.launch ?? detectServeLaunch();
7281
- upsertMcp(path19.join(opts.root, a.mcp.file), a.mcp, launch);
6980
+ upsertMcp(path17.join(opts.root, a.mcp.file), a.mcp, launch);
7282
6981
  wrote.push(a.mcp.file);
7283
6982
  note = launch.note;
7284
6983
  } else {
7285
6984
  skipped.push("mcp (host-specific setup)");
7286
6985
  }
7287
6986
  if (a.nudge && opts.hook !== false) {
7288
- writeNudge(path19.join(opts.root, a.nudge.file), a.nudge, opts.smallRepo, a.id);
6987
+ writeNudge(path17.join(opts.root, a.nudge.file), a.nudge, opts.smallRepo, a.id);
7289
6988
  wrote.push(a.nudge.file);
7290
6989
  } else if (a.nudge) {
7291
6990
  skipped.push("nudge (--no-hook)");
@@ -7295,40 +6994,40 @@ function installAssistant(a, opts) {
7295
6994
  function uninstallAssistant(a, root, purge) {
7296
6995
  const removed = [];
7297
6996
  if (a.mcp) {
7298
- const file = path19.join(root, a.mcp.file);
6997
+ const file = path17.join(root, a.mcp.file);
7299
6998
  if (removeMcp(file, a.mcp)) removed.push(a.mcp.file);
7300
6999
  }
7301
7000
  if (a.nudge) {
7302
- const file = path19.join(root, a.nudge.file);
7001
+ const file = path17.join(root, a.nudge.file);
7303
7002
  if (a.nudge.kind === "block") {
7304
7003
  if (removeBlock(file)) removed.push(a.nudge.file);
7305
- } else if (fs19.existsSync(file)) {
7306
- fs19.rmSync(file);
7004
+ } else if (fs17.existsSync(file)) {
7005
+ fs17.rmSync(file);
7307
7006
  removed.push(a.nudge.file);
7308
7007
  }
7309
7008
  }
7310
7009
  if (purge && a.skill) {
7311
- const file = path19.join(root, a.skill);
7312
- if (fs19.existsSync(file)) {
7313
- fs19.rmSync(file);
7010
+ const file = path17.join(root, a.skill);
7011
+ if (fs17.existsSync(file)) {
7012
+ fs17.rmSync(file);
7314
7013
  removed.push(a.skill);
7315
7014
  }
7316
7015
  }
7317
7016
  return removed;
7318
7017
  }
7319
7018
  function writeFileEnsured(file, content) {
7320
- fs19.mkdirSync(path19.dirname(file), { recursive: true });
7321
- fs19.writeFileSync(file, content);
7019
+ fs17.mkdirSync(path17.dirname(file), { recursive: true });
7020
+ fs17.writeFileSync(file, content);
7322
7021
  }
7323
7022
  function writeNavigationConfig(root) {
7324
- const rel2 = path19.join(".vibgrate", "mcp-navigation.json");
7023
+ const rel2 = path17.join(".vibgrate", "mcp-navigation.json");
7325
7024
  const doc = {
7326
7025
  _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.",
7327
7026
  hot_core: [...HOT_TOOLS],
7328
7027
  deferred: deferredToolNames(),
7329
7028
  toolset: navigationToolsetConfig()
7330
7029
  };
7331
- writeFileEnsured(path19.join(root, rel2), `${JSON.stringify(doc, null, 2)}
7030
+ writeFileEnsured(path17.join(root, rel2), `${JSON.stringify(doc, null, 2)}
7332
7031
  `);
7333
7032
  return rel2;
7334
7033
  }
@@ -7343,7 +7042,7 @@ function upsertMcp(file, target, launch) {
7343
7042
  `);
7344
7043
  }
7345
7044
  function removeMcp(file, target) {
7346
- if (!fs19.existsSync(file)) return false;
7045
+ if (!fs17.existsSync(file)) return false;
7347
7046
  const config = readJson(file);
7348
7047
  const bag = config[target.key];
7349
7048
  if (!bag || !(bag.vg !== void 0)) return false;
@@ -7352,6 +7051,57 @@ function removeMcp(file, target) {
7352
7051
  `);
7353
7052
  return true;
7354
7053
  }
7054
+ var SMALL_REPO_FILES = 150;
7055
+ function refreshInstalledInstructions(root, smallRepo) {
7056
+ const out = [];
7057
+ const staleVersion = (text, legacySignature) => {
7058
+ const v = installedContentVersion(text);
7059
+ if (v !== null) return v < INSTALL_CONTENT_VERSION ? v : null;
7060
+ return text.includes(legacySignature) ? 0 : null;
7061
+ };
7062
+ for (const a of ASSISTANTS) {
7063
+ if (a.skill) {
7064
+ const file = path17.join(root, a.skill);
7065
+ if (fs17.existsSync(file)) {
7066
+ const text = readTextSafe(file);
7067
+ const from = text === null ? null : staleVersion(text, "# vg \u2014 the code map");
7068
+ if (from !== null) {
7069
+ writeFileEnsured(file, skillMarkdown(a.id));
7070
+ out.push({ file: a.skill, from, to: INSTALL_CONTENT_VERSION });
7071
+ }
7072
+ }
7073
+ }
7074
+ if (a.nudge) {
7075
+ const file = path17.join(root, a.nudge.file);
7076
+ if (!fs17.existsSync(file)) continue;
7077
+ const text = readTextSafe(file);
7078
+ if (text === null) continue;
7079
+ if (a.nudge.kind === "block") {
7080
+ const block = new RegExp(`${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}`).exec(text);
7081
+ if (!block) continue;
7082
+ const from = staleVersion(block[0], "## Code navigation (vg)");
7083
+ if (from !== null) {
7084
+ writeNudge(file, a.nudge, smallRepo, a.id);
7085
+ out.push({ file: a.nudge.file, from, to: INSTALL_CONTENT_VERSION });
7086
+ }
7087
+ } else {
7088
+ const from = staleVersion(text, "## Code navigation (vg)");
7089
+ if (from !== null) {
7090
+ writeNudge(file, a.nudge, smallRepo, a.id);
7091
+ out.push({ file: a.nudge.file, from, to: INSTALL_CONTENT_VERSION });
7092
+ }
7093
+ }
7094
+ }
7095
+ }
7096
+ return out;
7097
+ }
7098
+ function readTextSafe(file) {
7099
+ try {
7100
+ return fs17.readFileSync(file, "utf8");
7101
+ } catch {
7102
+ return null;
7103
+ }
7104
+ }
7355
7105
  function writeNudge(file, target, smallRepo, client) {
7356
7106
  if (target.kind === "file") {
7357
7107
  const body = file.endsWith(".mdc") ? `---
@@ -7366,8 +7116,8 @@ ${stripMarkers(nudgeMarkdown(smallRepo, client))}` : stripMarkers(nudgeMarkdown(
7366
7116
  upsertBlock(file, nudgeMarkdown(smallRepo, client));
7367
7117
  }
7368
7118
  function upsertBlock(file, block) {
7369
- fs19.mkdirSync(path19.dirname(file), { recursive: true });
7370
- let existing = fs19.existsSync(file) ? fs19.readFileSync(file, "utf8") : "";
7119
+ fs17.mkdirSync(path17.dirname(file), { recursive: true });
7120
+ let existing = fs17.existsSync(file) ? fs17.readFileSync(file, "utf8") : "";
7371
7121
  const re = new RegExp(`${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}`);
7372
7122
  if (re.test(existing)) existing = existing.replace(re, block);
7373
7123
  else existing = existing.length ? `${existing.replace(/\s*$/, "")}
@@ -7375,20 +7125,20 @@ function upsertBlock(file, block) {
7375
7125
  ${block}
7376
7126
  ` : `${block}
7377
7127
  `;
7378
- fs19.writeFileSync(file, existing);
7128
+ fs17.writeFileSync(file, existing);
7379
7129
  }
7380
7130
  function removeBlock(file) {
7381
- if (!fs19.existsSync(file)) return false;
7382
- const existing = fs19.readFileSync(file, "utf8");
7131
+ if (!fs17.existsSync(file)) return false;
7132
+ const existing = fs17.readFileSync(file, "utf8");
7383
7133
  const re = new RegExp(`\\n*${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}\\n*`);
7384
7134
  if (!re.test(existing)) return false;
7385
- fs19.writeFileSync(file, existing.replace(re, "\n"));
7135
+ fs17.writeFileSync(file, existing.replace(re, "\n"));
7386
7136
  return true;
7387
7137
  }
7388
7138
  function readJson(file) {
7389
- if (!fs19.existsSync(file)) return {};
7139
+ if (!fs17.existsSync(file)) return {};
7390
7140
  try {
7391
- const parsed = JSON.parse(fs19.readFileSync(file, "utf8"));
7141
+ const parsed = JSON.parse(fs17.readFileSync(file, "utf8"));
7392
7142
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
7393
7143
  return parsed;
7394
7144
  }
@@ -7406,6 +7156,425 @@ function stripMarkers(s) {
7406
7156
  function escapeRe(s) {
7407
7157
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7408
7158
  }
7159
+ var REFRESH_LOCK_STALE_MS = 10 * 60 * 1e3;
7160
+ function refreshLockPath(root) {
7161
+ return path17.join(cacheDir(root), "refresh.lock");
7162
+ }
7163
+ async function refreshIfStale(root, opts = {}) {
7164
+ const probe = probeFreshness(root);
7165
+ if (!probe) return { status: "no-snapshot" };
7166
+ if (!hasDrift(probe.drift)) return { status: "fresh" };
7167
+ const lock = refreshLockPath(root);
7168
+ if (!acquireLock(lock, REFRESH_LOCK_STALE_MS)) return { status: "locked" };
7169
+ const start = process.hrtime.bigint();
7170
+ try {
7171
+ const { scope } = probe;
7172
+ const result = await buildGraph({
7173
+ root,
7174
+ only: scope.only,
7175
+ exclude: scope.exclude,
7176
+ paths: scope.paths,
7177
+ deep: scope.deep,
7178
+ noGround: scope.noGround,
7179
+ scip: scope.scip,
7180
+ noScip: scope.noScip,
7181
+ noTsc: scope.noTsc,
7182
+ cluster: scope.cluster,
7183
+ grammarsDir: scope.grammarsDir,
7184
+ inline: opts.inline,
7185
+ jobs: opts.jobs
7186
+ });
7187
+ const wrote = result.graph.provenance.corpusHash !== probe.corpusHash;
7188
+ if (wrote) {
7189
+ const dir = vibgrateDir(root);
7190
+ writeArtifacts(result.graph, {
7191
+ root,
7192
+ report: fs17.existsSync(path17.join(dir, "GRAPH_REPORT.md")),
7193
+ html: fs17.existsSync(path17.join(dir, "graph.html"))
7194
+ });
7195
+ }
7196
+ writeSnapshot(root, result.graph.provenance.corpusHash, result.fileStats, scope);
7197
+ const ms = Number((process.hrtime.bigint() - start) / 1000000n);
7198
+ return {
7199
+ status: "refreshed",
7200
+ drift: probe.drift,
7201
+ ms,
7202
+ reparsed: result.reparsed,
7203
+ totalFiles: result.totalFiles,
7204
+ wrote
7205
+ };
7206
+ } catch (err) {
7207
+ return { status: "error", message: err.message };
7208
+ } finally {
7209
+ releaseLock(lock);
7210
+ }
7211
+ }
7212
+ var LEDGER = "savings.jsonl";
7213
+ var PER_FILE_TOKENS = 400;
7214
+ var SAVINGS_TOOLS = /* @__PURE__ */ new Set(["query_graph", "get_node", "search_symbols"]);
7215
+ var CLI_TOOL_ALIASES = {
7216
+ ask: "query_graph",
7217
+ show: "get_node",
7218
+ impact: "impact_of",
7219
+ path: "find_path",
7220
+ hubs: "list_hubs",
7221
+ areas: "list_areas",
7222
+ tree: "tree"
7223
+ };
7224
+ function sanitizeClient(name) {
7225
+ if (typeof name !== "string") return "unknown";
7226
+ const cleaned = name.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
7227
+ return cleaned || "unknown";
7228
+ }
7229
+ function recordCliCall(root, entry, now) {
7230
+ recordSaving(
7231
+ root,
7232
+ {
7233
+ tool: entry.tool,
7234
+ source: "cli",
7235
+ client: sanitizeClient(entry.client),
7236
+ outcome: entry.outcome,
7237
+ vgTokens: entry.vgTokens ?? 0,
7238
+ baselineTokens: (entry.baselineFiles ?? 0) * PER_FILE_TOKENS
7239
+ },
7240
+ now
7241
+ );
7242
+ }
7243
+ function savingsLedgerPath(root) {
7244
+ return path17.join(cacheDir(root), LEDGER);
7245
+ }
7246
+ function ledgerPath(root) {
7247
+ return savingsLedgerPath(root);
7248
+ }
7249
+ function savingsRecorded(root) {
7250
+ return fs17.existsSync(ledgerPath(root));
7251
+ }
7252
+ function recordSaving(root, entry, now) {
7253
+ try {
7254
+ fs17.mkdirSync(cacheDir(root), { recursive: true });
7255
+ const line = JSON.stringify({ ts: now, ...entry });
7256
+ fs17.appendFileSync(ledgerPath(root), line + "\n");
7257
+ } catch {
7258
+ }
7259
+ }
7260
+ var DEFAULT_RATE_PER_M = 3;
7261
+ var DEFAULT_RATE_LABEL = "input @ $3/1M";
7262
+ function readSavings(root, days, now, ratePerM = DEFAULT_RATE_PER_M) {
7263
+ const file = ledgerPath(root);
7264
+ const cutoff = now - days * 24 * 60 * 60 * 1e3;
7265
+ let queries = 0;
7266
+ let vgTokens = 0;
7267
+ let baselineTokens = 0;
7268
+ if (fs17.existsSync(file)) {
7269
+ for (const line of fs17.readFileSync(file, "utf8").split("\n")) {
7270
+ if (!line.trim()) continue;
7271
+ try {
7272
+ const e = JSON.parse(line);
7273
+ if (e.ts < cutoff) continue;
7274
+ if (!SAVINGS_TOOLS.has(e.tool)) continue;
7275
+ queries++;
7276
+ vgTokens += e.vgTokens;
7277
+ baselineTokens += e.baselineTokens;
7278
+ } catch {
7279
+ }
7280
+ }
7281
+ }
7282
+ const estCostVg = vgTokens / 1e6 * ratePerM;
7283
+ const estCostBaseline = baselineTokens / 1e6 * ratePerM;
7284
+ return {
7285
+ enabled: savingsRecorded(root),
7286
+ days,
7287
+ queries,
7288
+ vgTokens,
7289
+ baselineTokens,
7290
+ ratio: vgTokens > 0 ? Math.round(baselineTokens / vgTokens * 100) / 100 : 0,
7291
+ estCostVg: round22(estCostVg),
7292
+ estCostBaseline: round22(estCostBaseline),
7293
+ saved: round22(estCostBaseline - estCostVg),
7294
+ rateLabel: DEFAULT_RATE_LABEL
7295
+ };
7296
+ }
7297
+ function round22(x) {
7298
+ return Math.round(x * 100) / 100;
7299
+ }
7300
+ function toDimensionStats(byKey) {
7301
+ return [...byKey.entries()].map(([key, r]) => {
7302
+ const calls = r.complete + r.partial + r.miss;
7303
+ return {
7304
+ key,
7305
+ calls,
7306
+ complete: r.complete,
7307
+ partial: r.partial,
7308
+ miss: r.miss,
7309
+ successPct: calls ? Math.round((r.complete + r.partial) / calls * 100) : 0
7310
+ };
7311
+ }).sort((a, b) => b.calls - a.calls || a.key.localeCompare(b.key));
7312
+ }
7313
+ function readUsage(root, days, now) {
7314
+ const file = ledgerPath(root);
7315
+ const cutoff = now - days * 24 * 60 * 60 * 1e3;
7316
+ const byTool = /* @__PURE__ */ new Map();
7317
+ const bySource = /* @__PURE__ */ new Map();
7318
+ const byClient = /* @__PURE__ */ new Map();
7319
+ const msByTool = /* @__PURE__ */ new Map();
7320
+ const bump2 = (m, key, outcome) => {
7321
+ const row = m.get(key) ?? { complete: 0, partial: 0, miss: 0 };
7322
+ row[outcome]++;
7323
+ m.set(key, row);
7324
+ };
7325
+ if (fs17.existsSync(file)) {
7326
+ for (const line of fs17.readFileSync(file, "utf8").split("\n")) {
7327
+ if (!line.trim()) continue;
7328
+ try {
7329
+ const e = JSON.parse(line);
7330
+ if (e.ts < cutoff) continue;
7331
+ const outcome = e.outcome ?? "complete";
7332
+ bump2(byTool, e.tool, outcome);
7333
+ bump2(bySource, e.source ?? "mcp", outcome);
7334
+ bump2(byClient, e.client ?? "unknown", outcome);
7335
+ if (typeof e.ms === "number" && e.ms >= 0) {
7336
+ const timing = msByTool.get(e.tool) ?? { sum: 0, n: 0 };
7337
+ timing.sum += e.ms;
7338
+ timing.n++;
7339
+ msByTool.set(e.tool, timing);
7340
+ }
7341
+ } catch {
7342
+ }
7343
+ }
7344
+ }
7345
+ const commands = toDimensionStats(byTool).map((d) => {
7346
+ const timing = msByTool.get(d.key);
7347
+ return {
7348
+ tool: d.key,
7349
+ calls: d.calls,
7350
+ complete: d.complete,
7351
+ partial: d.partial,
7352
+ miss: d.miss,
7353
+ successPct: d.successPct,
7354
+ avgMs: timing && timing.n > 0 ? Math.round(timing.sum / timing.n) : null
7355
+ };
7356
+ });
7357
+ const totals = commands.reduce(
7358
+ (t, c) => ({
7359
+ calls: t.calls + c.calls,
7360
+ complete: t.complete + c.complete,
7361
+ partial: t.partial + c.partial,
7362
+ miss: t.miss + c.miss
7363
+ }),
7364
+ { calls: 0, complete: 0, partial: 0, miss: 0 }
7365
+ );
7366
+ const avgSuccessPct = commands.length ? Math.round(commands.reduce((s, c) => s + c.successPct, 0) / commands.length) : 0;
7367
+ return {
7368
+ enabled: savingsRecorded(root),
7369
+ days,
7370
+ commands,
7371
+ sources: toDimensionStats(bySource),
7372
+ clients: toDimensionStats(byClient),
7373
+ totals,
7374
+ avgSuccessPct
7375
+ };
7376
+ }
7377
+ var PROBE_INTERVAL_MS = 2e3;
7378
+ var MAX_PROBE_INTERVAL_MS = 3e4;
7379
+ var PROBE_DUTY_FACTOR = 20;
7380
+ var REFRESH_BUDGET_MS = 5e3;
7381
+ var FAILURE_COOLDOWN_MS = 6e4;
7382
+ var GraphSource = class {
7383
+ constructor(graphPath, refresh = false, tuning = {}) {
7384
+ this.graphPath = graphPath;
7385
+ this.refresh = refresh;
7386
+ this.tuning = tuning;
7387
+ this.root = path17.dirname(path17.dirname(graphPath));
7388
+ }
7389
+ graphPath;
7390
+ refresh;
7391
+ tuning;
7392
+ cachedMtimeMs = -1;
7393
+ cached = null;
7394
+ root;
7395
+ lastProbeAt = 0;
7396
+ failedUntil = 0;
7397
+ inflight = null;
7398
+ /** Self-tuned: grows with measured probe cost so huge repos aren't penalized. */
7399
+ probeIntervalMs = PROBE_INTERVAL_MS;
7400
+ /** Current graph: auto-refreshed if the tree drifted, reloaded if the file changed. */
7401
+ async get() {
7402
+ if (this.refresh) await this.maybeRefresh();
7403
+ const stat = fs17.statSync(this.graphPath);
7404
+ if (stat.mtimeMs !== this.cachedMtimeMs || !this.cached) {
7405
+ this.cached = parseGraph(fs17.readFileSync(this.graphPath, "utf8"));
7406
+ this.cachedMtimeMs = stat.mtimeMs;
7407
+ }
7408
+ return this.cached;
7409
+ }
7410
+ /**
7411
+ * Debounced, single-flight refresh. Never throws — a refresh problem must
7412
+ * degrade to "answer from the current map", not break the tool call.
7413
+ */
7414
+ async maybeRefresh() {
7415
+ const now = Date.now();
7416
+ if (!this.inflight) {
7417
+ const interval = this.tuning.probeIntervalMs ?? this.probeIntervalMs;
7418
+ if (now < this.failedUntil || now - this.lastProbeAt < interval) return;
7419
+ this.lastProbeAt = now;
7420
+ this.inflight = refreshIfStale(this.root).then((r) => {
7421
+ if (r.status === "error") this.failedUntil = Date.now() + FAILURE_COOLDOWN_MS;
7422
+ if (r.status === "fresh" || r.status === "no-snapshot" || r.status === "locked") {
7423
+ const cost = Date.now() - now;
7424
+ this.probeIntervalMs = Math.min(
7425
+ MAX_PROBE_INTERVAL_MS,
7426
+ Math.max(PROBE_INTERVAL_MS, cost * PROBE_DUTY_FACTOR)
7427
+ );
7428
+ }
7429
+ }).catch(() => {
7430
+ this.failedUntil = Date.now() + FAILURE_COOLDOWN_MS;
7431
+ }).finally(() => {
7432
+ this.inflight = null;
7433
+ });
7434
+ }
7435
+ await Promise.race([this.inflight, sleep(this.tuning.refreshBudgetMs ?? REFRESH_BUDGET_MS)]);
7436
+ }
7437
+ };
7438
+ function createServer(source, opts = {}) {
7439
+ const { savings = false, shareStats = false, local = false, dedup = false, stats } = opts;
7440
+ const record = savings || shareStats;
7441
+ const root = path17.dirname(path17.dirname(source.graphPath));
7442
+ const seen = /* @__PURE__ */ new Set();
7443
+ const server = new Server(
7444
+ { name: "vg", version: VERSION },
7445
+ {
7446
+ capabilities: { tools: {} },
7447
+ // Routing guidance once at the server level (hosts that surface
7448
+ // `instructions` get it at zero per-step schema cost): the flashlight
7449
+ // vs the map.
7450
+ instructions: 'vg is a code map. Use search_symbols to find a known name or literal string fast \u2014 a multi-word/quoted phrase runs a complete literal sweep and reports totalTextMatches, so reach for it instead of grep even for plain-string "find every occurrence" lookups. Use orient/query_graph for meaning: symptoms, relationships, and what-breaks-if. Responses are concise by default; pass response_format:"detailed" only when a node proves load-bearing. Navigate as little as possible: one good search/query usually locates the code. As soon as you have the file and line, read that file and make the edit \u2014 do not call further graph tools unless the edit fails or the match was wrong. For how-do-I-use-this-library questions call resolve_library once, then library_docs with the returned targetId and a focused query: the docs are official and matched to the version THIS project has installed (drift-annotated) \u2014 prefer them over web search or training-data recall when they conflict. Skip them for language built-ins or APIs already shown in context. If two library_docs calls have not surfaced the section you need, read the package source under node_modules instead of searching again.'
7451
+ }
7452
+ );
7453
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
7454
+ tools: TOOLS.map((t) => ({
7455
+ name: t.name,
7456
+ description: t.description,
7457
+ inputSchema: t.inputSchema,
7458
+ annotations: { readOnlyHint: true, openWorldHint: false }
7459
+ }))
7460
+ }));
7461
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
7462
+ const tool = TOOLS.find((t) => t.name === request.params.name);
7463
+ if (!tool) {
7464
+ return errorResult(`unknown tool "${request.params.name}"`);
7465
+ }
7466
+ let graph;
7467
+ try {
7468
+ graph = await source.get();
7469
+ } catch {
7470
+ return errorResult(
7471
+ "no code map found. Run `vg` in the project to build .vibgrate/graph.json, then retry."
7472
+ );
7473
+ }
7474
+ const startedAt = Date.now();
7475
+ try {
7476
+ const args = request.params.arguments ?? {};
7477
+ const result = await tool.handler(graph, args, { root, local, dedup, seen });
7478
+ const ms = Date.now() - startedAt;
7479
+ stats?.record({ ...measureCall(tool.name, result), client: sanitizeClient(detectClient(server)), ms });
7480
+ if (record) recordUsage(root, tool.name, result, detectClient(server), ms);
7481
+ return renderToolResult(result);
7482
+ } catch (err) {
7483
+ stats?.record({
7484
+ tool: tool.name,
7485
+ client: sanitizeClient(detectClient(server)),
7486
+ outcome: "miss",
7487
+ ms: Date.now() - startedAt,
7488
+ vgTokens: 0,
7489
+ baselineTokens: 0
7490
+ });
7491
+ return errorResult(`tool "${tool.name}" failed: ${err.message}`);
7492
+ }
7493
+ });
7494
+ return server;
7495
+ }
7496
+ async function serveStdio(graphPath, opts = {}) {
7497
+ const source = new GraphSource(graphPath, opts.refresh !== false);
7498
+ const server = createServer(source, opts);
7499
+ warmEmbedderInBackground(opts.local);
7500
+ await server.connect(new StdioServerTransport());
7501
+ }
7502
+ function sleep(ms) {
7503
+ return new Promise((resolve12) => {
7504
+ const timer = setTimeout(resolve12, ms);
7505
+ timer.unref?.();
7506
+ });
7507
+ }
7508
+ function recordUsage(root, tool, result, client, ms) {
7509
+ recordSaving(
7510
+ root,
7511
+ { ...measureCall(tool, result), source: "mcp", client: sanitizeClient(client), ...ms !== void 0 ? { ms } : {} },
7512
+ Date.now()
7513
+ );
7514
+ }
7515
+ function measureCall(tool, result) {
7516
+ const outcome = classifyOutcome(result);
7517
+ let vgTokens = 0;
7518
+ let baselineTokens = 0;
7519
+ if (SAVINGS_TOOLS.has(tool) && result && typeof result === "object") {
7520
+ vgTokens = countTokens(renderedText(renderToolResult(result)));
7521
+ baselineTokens = referencedFiles(result).size * PER_FILE_TOKENS;
7522
+ }
7523
+ return { tool, outcome, vgTokens, baselineTokens };
7524
+ }
7525
+ function detectClient(server) {
7526
+ try {
7527
+ const info = server.getClientVersion?.();
7528
+ return typeof info?.name === "string" ? info.name : void 0;
7529
+ } catch {
7530
+ return void 0;
7531
+ }
7532
+ }
7533
+ function classifyOutcome(result) {
7534
+ if (Array.isArray(result)) return result.length === 0 ? "miss" : "complete";
7535
+ if (!result || typeof result !== "object") return "miss";
7536
+ const r = result;
7537
+ if (typeof r.error === "string" && r.error) return "miss";
7538
+ if (r.connected === false) return "miss";
7539
+ if (Array.isArray(r.matches) && r.matches.length === 0) return "miss";
7540
+ return isPartial(r) ? "partial" : "complete";
7541
+ }
7542
+ function isPartial(r) {
7543
+ if (r.moreAvailable === true) return true;
7544
+ if (r._truncated && typeof r._truncated === "object") return true;
7545
+ for (const [key, value] of Object.entries(r)) {
7546
+ if (typeof value !== "number" || !key.endsWith("Total")) continue;
7547
+ const base = key.slice(0, -"Total".length);
7548
+ const shown = Array.isArray(r[base]) ? r[base].length : 0;
7549
+ if (value > shown) return true;
7550
+ }
7551
+ return false;
7552
+ }
7553
+ function renderedText(rendered) {
7554
+ const block = rendered.content?.find((b) => b.type === "text");
7555
+ return block && "text" in block && typeof block.text === "string" ? block.text : "";
7556
+ }
7557
+ function referencedFiles(result) {
7558
+ const r = result;
7559
+ const files = /* @__PURE__ */ new Set();
7560
+ if (typeof r.file === "string" && r.file) files.add(r.file);
7561
+ for (const m of asArray(r.matches)) {
7562
+ const f = m.file;
7563
+ if (typeof f === "string" && f) files.add(f);
7564
+ }
7565
+ for (const name of [...asArray(r.calls), ...asArray(r.calledBy)]) {
7566
+ if (typeof name !== "string") continue;
7567
+ const i = name.indexOf(":");
7568
+ if (i > 0) files.add(name.slice(0, i));
7569
+ }
7570
+ return files;
7571
+ }
7572
+ function asArray(v) {
7573
+ return Array.isArray(v) ? v : [];
7574
+ }
7575
+ function errorResult(message) {
7576
+ return { content: [{ type: "text", text: message }], isError: true };
7577
+ }
7409
7578
 
7410
7579
  // src/engine/export.ts
7411
7580
  function formatForExt(ext) {
@@ -7610,6 +7779,6 @@ function spdx(ctx) {
7610
7779
  return JSON.stringify(doc, null, 2) + "\n";
7611
7780
  }
7612
7781
 
7613
- export { ASSISTANTS, CLI_TOOL_ALIASES, CliError, ECOSYSTEMS, ExitCode, FREE_PACK, GraphIndex, GraphSource, NPX_INVOCATION, ResourceLimitError, SAVINGS_TOOLS, SCHEMA_VERSION, SKIP_DIRS, SKIP_FILES, TOOLS, UsageError, addLibrary, analyze, applyCoverage, applyStaticTestLinkage, assessDocQuality, assistantById, availableRegionIds, buildFacts, buildGraph, buildModuleResolver, cacheDir, catalogPath, clearModelCache, clearStoredCredentials, cosine, countPending, countTokens, coveringTests, createServer, createWorkspaceDsn, credentialsPath, dashHostForIngestHost, decodeScipIndex, defaultGraphPath, detectAssistants, detectRunner, detectServeLaunch, discover, discoverModels, driftCount, driftFor, dsnCommand, embeddingsCached, enrichOnline, ensureGitignored, epistemicBreakdown, exportGraph, fetchHostedDocsCached, findGitRoot, findNodes, formatForExt, getNodeEmbeddings, gitignoreEntryForCredentials, groundGraph, hasDrift, homeCredentialsPath, hostedBase, identifierParts, impactOf, indexFor, ingestHostForRegionId, installAssistant, inventory, isModelReady, isTestFile, libDir, libId, loadCatalog, loadCoverage, loadEmbedder, loadGraph, loadSnapshot, localApiSurface, localPackageDocs, modelCacheInfo, nodeById, nodeEmbedText, parseDsn, parseGraph, parseJsonc, probeFreshness, projectCredentialsPath, publishPrivateLibrary, pushCommand, queryGraph, queryGraphSemantic, readDoc, readSavings, readScanArtifact, readStoredCredentials, readUsage, recordCliCall, recordSaving, refreshIfStale, relativeResolver, renderHtml, renderReport, resolveCliInvocation, resolveDsn, resolveEmbedModel, resolveIngestHost, resolveLib, resolveLimits, resolveOne, resolveVersion, sanitizeClient, saveCatalog, savingsLedgerPath, savingsRecorded, scipEdges, selectForBudget, serializeGraph, serveStdio, shortestPath, stableStringify, symbolsFromApi, testsToRun, unavailableMessage, uninstallAssistant, uploadScanArtifact, usageError, verifyDeterminism, vibgrateDir, writeArtifacts, writeNavigationConfig, writeSnapshot, writeStoredCredentials };
7614
- //# sourceMappingURL=chunk-7R4O5CZD.js.map
7615
- //# sourceMappingURL=chunk-7R4O5CZD.js.map
7782
+ export { ASSISTANTS, CLI_TOOL_ALIASES, CliError, ECOSYSTEMS, ExitCode, FREE_PACK, GraphIndex, GraphSource, NPX_INVOCATION, ResourceLimitError, SAVINGS_TOOLS, SCHEMA_VERSION, SKIP_DIRS, SKIP_FILES, SMALL_REPO_FILES, TOOLS, UsageError, addLibrary, analyze, applyCoverage, applyStaticTestLinkage, assessDocQuality, assistantById, availableRegionIds, buildFacts, buildGraph, buildModuleResolver, cacheDir, catalogPath, clearModelCache, clearStoredCredentials, cosine, countPending, countTokens, coveringTests, createServer, createWorkspaceDsn, credentialsPath, dashHostForIngestHost, decodeScipIndex, defaultGraphPath, detectAssistants, detectRunner, detectServeLaunch, discover, discoverModels, driftCount, driftFor, dsnCommand, embeddingsCached, enrichOnline, ensureGitignored, epistemicBreakdown, exportGraph, fetchHostedDocsCached, findGitRoot, findNodes, formatForExt, getNodeEmbeddings, gitignoreEntryForCredentials, groundGraph, hasDrift, homeCredentialsPath, hostedBase, identifierParts, impactOf, indexFor, ingestHostForRegionId, installAssistant, inventory, isModelReady, isTestFile, libDir, libId, loadCatalog, loadCoverage, loadEmbedder, loadGraph, loadSnapshot, localApiSurface, localPackageDocs, modelCacheInfo, nodeById, nodeEmbedText, parseDsn, parseGraph, parseJsonc, probeFreshness, projectCredentialsPath, publishPrivateLibrary, pushCommand, queryGraph, queryGraphSemantic, readDoc, readSavings, readScanArtifact, readStoredCredentials, readUsage, recordCliCall, recordSaving, refreshIfStale, refreshInstalledInstructions, relativeResolver, renderHtml, renderReport, resolveCliInvocation, resolveDsn, resolveEmbedModel, resolveIngestHost, resolveLib, resolveLimits, resolveOne, resolveVersion, sanitizeClient, saveCatalog, savingsLedgerPath, savingsRecorded, scipEdges, selectForBudget, serializeGraph, serveStdio, shortestPath, stableStringify, symbolsFromApi, testsToRun, unavailableMessage, uninstallAssistant, uploadScanArtifact, usageError, verifyDeterminism, vibgrateDir, writeArtifacts, writeNavigationConfig, writeSnapshot, writeStoredCredentials };
7783
+ //# sourceMappingURL=chunk-AS7DIHZZ.js.map
7784
+ //# sourceMappingURL=chunk-AS7DIHZZ.js.map