@vibgrate/cli 2026.703.5 → 2026.704.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,10 @@
1
- import { langById, shortId, canonicalize, grammarSetVersion, hashBytes, hashString, langForExtension, setGrammarsOverride, parseSource } from './chunk-X5YT263H.js';
2
- import { writeTextFile, pathExists, readJsonFile, VERSION, computeUpgradeImpact, getChangelogSignals, gitHistoryAvailable, buildVersionTimelines, findPackageTimeline, severityRank, projectTypeToVulnEcosystem } from './chunk-L7YBNIBF.js';
3
- import * as fs17 from 'fs';
1
+ import { langById, shortId, canonicalize, grammarSetVersion, hashString, hashBytes, langForExtension, setGrammarsOverride, parseSource } from './chunk-X5YT263H.js';
2
+ import { writeTextFile, pathExists, readJsonFile, VERSION, computeUpgradeImpact, getChangelogSignals, gitHistoryAvailable, buildVersionTimelines, findPackageTimeline, severityRank, projectTypeToVulnEcosystem } from './chunk-RJHYTD62.js';
3
+ import * as fs18 from 'fs';
4
+ import { execFileSync } from 'child_process';
4
5
  import * as path18 from 'path';
5
6
  import ignore from 'ignore';
7
+ import * as v8 from 'v8';
6
8
  import Graph from 'graphology';
7
9
  import pagerank from 'graphology-metrics/centrality/pagerank.js';
8
10
  import betweenness from 'graphology-metrics/centrality/betweenness.js';
@@ -12,7 +14,6 @@ import * as os from 'os';
12
14
  import { fileURLToPath } from 'url';
13
15
  import ts from 'typescript';
14
16
  import { bidirectional } from 'graphology-shortest-path/unweighted.js';
15
- import { execFileSync } from 'child_process';
16
17
  import * as crypto from 'crypto';
17
18
  import { Command } from 'commander';
18
19
  import chalk from 'chalk';
@@ -23,32 +24,196 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
23
24
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
24
25
  import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
25
26
 
27
+ var NPX_INVOCATION = "npx @vibgrate/cli";
28
+ function whichOnPath(cmd) {
29
+ try {
30
+ const out = execFileSync(process.platform === "win32" ? "where" : "which", [cmd], {
31
+ encoding: "utf8",
32
+ stdio: ["ignore", "pipe", "ignore"]
33
+ }).trim().split(/\r?\n/)[0];
34
+ return out || null;
35
+ } catch {
36
+ return null;
37
+ }
38
+ }
39
+ function isOwnBinary(binPath) {
40
+ try {
41
+ const real = fs18.realpathSync(binPath);
42
+ if (/[\\/]@vibgrate[\\/]cli[\\/]/.test(real)) return true;
43
+ const head = fs18.readFileSync(real, { encoding: "utf8" }).slice(0, 2048);
44
+ return head.includes("@vibgrate/cli") || head.includes("vibgrate");
45
+ } catch {
46
+ return false;
47
+ }
48
+ }
49
+ function isEphemeralNpxBinary(binPath) {
50
+ const NPX_SEGMENT = /[\\/]_npx[\\/]/;
51
+ if (NPX_SEGMENT.test(binPath)) return true;
52
+ try {
53
+ return NPX_SEGMENT.test(fs18.realpathSync(binPath));
54
+ } catch {
55
+ return false;
56
+ }
57
+ }
58
+ function isInstalledOwnBinary(binPath) {
59
+ return isOwnBinary(binPath) && !isEphemeralNpxBinary(binPath);
60
+ }
61
+ var cached;
62
+ function resolveCliInvocation(which) {
63
+ if (!which && cached !== void 0) return cached;
64
+ const lookup = which ?? whichOnPath;
65
+ const vg = lookup("vg");
66
+ let result;
67
+ if (vg && isInstalledOwnBinary(vg)) {
68
+ result = "vg";
69
+ } else {
70
+ const vibgrate = lookup("vibgrate");
71
+ result = vibgrate && isInstalledOwnBinary(vibgrate) ? "vibgrate" : NPX_INVOCATION;
72
+ }
73
+ if (!which) cached = result;
74
+ return result;
75
+ }
26
76
  var SKIP_DIRS = /* @__PURE__ */ new Set([
77
+ // Version control, IDE & tool metadata
27
78
  ".git",
28
- "node_modules",
79
+ ".svn",
80
+ ".hg",
81
+ ".idea",
82
+ ".vscode",
83
+ ".vs",
29
84
  ".vibgrate",
85
+ ".wrangler",
86
+ ".turbo",
87
+ ".cache",
88
+ // JavaScript / Node — installed dependency trees
89
+ "node_modules",
90
+ "bower_components",
91
+ "jspm_packages",
92
+ "web_modules",
93
+ ".pnpm-store",
94
+ ".yarn",
95
+ // JS/TS meta-framework & bundler build output
96
+ ".next",
97
+ ".nuxt",
98
+ ".output",
99
+ ".svelte-kit",
100
+ ".astro",
101
+ ".vercel",
102
+ ".netlify",
103
+ ".angular",
104
+ ".parcel-cache",
105
+ ".docusaurus",
106
+ "storybook-static",
107
+ // Generic build / dist / coverage output
30
108
  "dist",
31
109
  "build",
32
110
  "out",
33
111
  "coverage",
34
- ".next",
35
- ".nuxt",
36
- ".svelte-kit",
37
- ".turbo",
38
- ".cache",
112
+ // Vendored third-party source trees
39
113
  "vendor",
40
- "target",
41
- // rust / java
42
- "bin",
43
- "obj",
44
- // .NET
45
- "__pycache__",
114
+ "Pods",
115
+ "Carthage",
116
+ // Python — virtualenvs & tool caches
46
117
  ".venv",
47
118
  "venv",
119
+ "virtualenv",
48
120
  ".tox",
121
+ ".nox",
122
+ "__pycache__",
123
+ ".mypy_cache",
124
+ ".pytest_cache",
125
+ ".ruff_cache",
126
+ ".eggs",
127
+ ".ipynb_checkpoints",
128
+ // Ruby
129
+ ".bundle",
130
+ // Rust / Maven / sbt / Clojure — all build into target/
131
+ "target",
132
+ // JVM (Gradle / Maven) & .NET build output
49
133
  ".gradle",
50
- ".idea",
51
- ".vscode"
134
+ "bin",
135
+ "obj",
136
+ "TestResults",
137
+ // Swift / Xcode
138
+ ".build",
139
+ ".swiftpm",
140
+ "DerivedData",
141
+ // Dart / Flutter
142
+ ".dart_tool",
143
+ // Elixir / Erlang
144
+ "_build",
145
+ "deps",
146
+ // Haskell (Cabal / Stack)
147
+ "dist-newstyle",
148
+ ".stack-work",
149
+ // Infrastructure-as-code build/state
150
+ ".terraform",
151
+ ".serverless",
152
+ "cdk.out",
153
+ ".aws-sam"
154
+ ]);
155
+ var SKIP_FILES = /* @__PURE__ */ new Set([
156
+ // JavaScript / Node
157
+ "package-lock.json",
158
+ "npm-shrinkwrap.json",
159
+ "yarn.lock",
160
+ "pnpm-lock.yaml",
161
+ "bun.lockb",
162
+ "bun.lock",
163
+ "deno.lock",
164
+ ".pnp.cjs",
165
+ ".pnp.data.json",
166
+ ".pnp.loader.mjs",
167
+ // Python
168
+ "poetry.lock",
169
+ "pipfile.lock",
170
+ "pdm.lock",
171
+ "uv.lock",
172
+ "pylock.toml",
173
+ "conda-lock.yml",
174
+ // Ruby
175
+ "gemfile.lock",
176
+ // PHP
177
+ "composer.lock",
178
+ // Rust
179
+ "cargo.lock",
180
+ // Go
181
+ "go.sum",
182
+ "go.work.sum",
183
+ "gopkg.lock",
184
+ "glide.lock",
185
+ // .NET
186
+ "packages.lock.json",
187
+ // Java / JVM (Gradle)
188
+ "gradle.lockfile",
189
+ // Swift / Xcode
190
+ "package.resolved",
191
+ // Dart / Flutter
192
+ "pubspec.lock",
193
+ // Elixir
194
+ "mix.lock",
195
+ // Haskell (Cabal / Stack)
196
+ "cabal.project.freeze",
197
+ "stack.yaml.lock",
198
+ // Julia
199
+ "manifest.toml",
200
+ // R
201
+ "renv.lock",
202
+ // Perl (Carton)
203
+ "cpanfile.snapshot",
204
+ // CocoaPods / Carthage
205
+ "podfile.lock",
206
+ "cartfile.resolved",
207
+ // Terraform / IaC
208
+ ".terraform.lock.hcl",
209
+ // Helm
210
+ "chart.lock",
211
+ // C / C++ (Conan)
212
+ "conan.lock",
213
+ // Bazel
214
+ "module.bazel.lock",
215
+ // Nix
216
+ "flake.lock"
52
217
  ]);
53
218
  function toPosix(p) {
54
219
  return p.split(path18.sep).join("/");
@@ -56,8 +221,8 @@ function toPosix(p) {
56
221
  function buildRootIgnore(root, exclude) {
57
222
  const ig = ignore();
58
223
  const gitignorePath = path18.join(root, ".gitignore");
59
- if (fs17.existsSync(gitignorePath)) {
60
- ig.add(fs17.readFileSync(gitignorePath, "utf8"));
224
+ if (fs18.existsSync(gitignorePath)) {
225
+ ig.add(fs18.readFileSync(gitignorePath, "utf8"));
61
226
  }
62
227
  if (exclude.length) ig.add(exclude);
63
228
  return ig;
@@ -72,12 +237,13 @@ function discover(options) {
72
237
  }
73
238
  }
74
239
  const rootIg = buildRootIgnore(root, options.exclude ?? []);
75
- const scopeAbs = (options.paths && options.paths.length ? options.paths.map((p) => path18.resolve(root, p)) : [root]).filter((p) => fs17.existsSync(p));
240
+ const scopeAbs = (options.paths && options.paths.length ? options.paths.map((p) => path18.resolve(root, p)) : [root]).filter((p) => fs18.existsSync(p));
76
241
  const found = /* @__PURE__ */ new Map();
77
242
  const considerFile = (abs) => {
78
243
  const rel2 = toPosix(path18.relative(root, abs));
79
244
  if (rel2.startsWith("..")) return;
80
245
  if (rel2 === "" || rootIg.ignores(rel2)) return;
246
+ if (SKIP_FILES.has(path18.basename(abs).toLowerCase())) return;
81
247
  const lang = langForExtension(path18.extname(abs));
82
248
  if (!lang || !allowLang(lang)) return;
83
249
  found.set(rel2, { rel: rel2, abs, lang });
@@ -85,7 +251,7 @@ function discover(options) {
85
251
  const walk2 = (dir) => {
86
252
  let entries;
87
253
  try {
88
- entries = fs17.readdirSync(dir, { withFileTypes: true });
254
+ entries = fs18.readdirSync(dir, { withFileTypes: true });
89
255
  } catch {
90
256
  return;
91
257
  }
@@ -102,7 +268,7 @@ function discover(options) {
102
268
  }
103
269
  };
104
270
  for (const scope of scopeAbs) {
105
- const stat = fs17.statSync(scope);
271
+ const stat = fs18.statSync(scope);
106
272
  if (stat.isDirectory()) walk2(scope);
107
273
  else if (stat.isFile()) considerFile(scope);
108
274
  }
@@ -115,6 +281,58 @@ var UsageError = class extends Error {
115
281
  this.name = "UsageError";
116
282
  }
117
283
  };
284
+ var MIB = 1024 * 1024;
285
+ var DEFAULT_MAX_FILE_BYTES = 2 * MIB;
286
+ var DEFAULT_MAX_FILES = 1e5;
287
+ var DEFAULT_TSC_MAX_FILES = 1e4;
288
+ var HEAP_GUARD_FRACTION = 0.9;
289
+ var ResourceLimitError = class extends Error {
290
+ isResourceLimitError = true;
291
+ constructor(message) {
292
+ super(message);
293
+ this.name = "ResourceLimitError";
294
+ }
295
+ };
296
+ function envInt(name, fallback) {
297
+ const raw = process.env[name];
298
+ if (raw === void 0 || raw.trim() === "") return fallback;
299
+ const n = Number(raw);
300
+ return Number.isInteger(n) && n >= 0 ? n : fallback;
301
+ }
302
+ function envIntOptional(name) {
303
+ const n = envInt(name, 0);
304
+ return n > 0 ? n : void 0;
305
+ }
306
+ function defaultMemoryBudgetMb() {
307
+ return Math.floor(v8.getHeapStatistics().heap_size_limit / MIB * HEAP_GUARD_FRACTION);
308
+ }
309
+ function resolveLimits(overrides = {}) {
310
+ return {
311
+ maxFileBytes: overrides.maxFileBytes ?? envInt("VG_MAX_FILE_BYTES", DEFAULT_MAX_FILE_BYTES),
312
+ maxFiles: overrides.maxFiles ?? envInt("VG_MAX_FILES", DEFAULT_MAX_FILES),
313
+ tscMaxFiles: overrides.tscMaxFiles ?? envInt("VG_TSC_MAX_FILES", DEFAULT_TSC_MAX_FILES),
314
+ memoryBudgetMb: overrides.memoryBudgetMb ?? envInt("VG_MEMORY_BUDGET_MB", defaultMemoryBudgetMb())
315
+ };
316
+ }
317
+ function envJobs() {
318
+ return envIntOptional("VG_JOBS");
319
+ }
320
+ function envWorkerHeapMb() {
321
+ return envIntOptional("VG_WORKER_HEAP_MB");
322
+ }
323
+ function checkMemoryBudget(phase, budgetMb) {
324
+ if (budgetMb <= 0) return;
325
+ const usedMb = process.memoryUsage().heapUsed / MIB;
326
+ if (usedMb <= budgetMb) return;
327
+ throw new ResourceLimitError(
328
+ `graph build stopped during ${phase}: heap use ${Math.round(usedMb)} MiB exceeds the ${budgetMb} MiB budget. Narrow the build (scope to sub-paths, add --exclude globs, or --only <langs>), raise the Node heap (NODE_OPTIONS=--max-old-space-size=8192), or set VG_MEMORY_BUDGET_MB (0 disables this safeguard).`
329
+ );
330
+ }
331
+ function formatBytes(n) {
332
+ if (n >= MIB) return `${(n / MIB).toFixed(1)} MiB`;
333
+ if (n >= 1024) return `${(n / 1024).toFixed(1)} KiB`;
334
+ return `${n} B`;
335
+ }
118
336
  var JS_TS_EXT = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
119
337
  var OTHER_EXT = [
120
338
  ".py",
@@ -336,7 +554,7 @@ function matchPattern(pattern, source) {
336
554
  function loadTsconfigPaths(root) {
337
555
  for (const name of ["tsconfig.base.json", "tsconfig.json"]) {
338
556
  const file = path18.join(root, name);
339
- if (fs17.existsSync(file)) {
557
+ if (fs18.existsSync(file)) {
340
558
  const merged = readTsconfigChain(root, file, /* @__PURE__ */ new Set());
341
559
  if (merged && merged.paths.length) return merged;
342
560
  }
@@ -348,7 +566,7 @@ function readTsconfigChain(root, file, seen) {
348
566
  seen.add(file);
349
567
  let cfg;
350
568
  try {
351
- cfg = parseJsonc(fs17.readFileSync(file, "utf8"));
569
+ cfg = parseJsonc(fs18.readFileSync(file, "utf8"));
352
570
  } catch {
353
571
  return null;
354
572
  }
@@ -356,7 +574,7 @@ function readTsconfigChain(root, file, seen) {
356
574
  let base = { baseUrlRel: "", paths: [] };
357
575
  if (cfg.extends && cfg.extends.startsWith(".")) {
358
576
  const extPath = path18.resolve(dir, cfg.extends.endsWith(".json") ? cfg.extends : `${cfg.extends}.json`);
359
- if (fs17.existsSync(extPath)) {
577
+ if (fs18.existsSync(extPath)) {
360
578
  const inherited = readTsconfigChain(root, extPath, seen);
361
579
  if (inherited) base = inherited;
362
580
  }
@@ -371,17 +589,17 @@ function loadWorkspacePackages(root) {
371
589
  const map = /* @__PURE__ */ new Map();
372
590
  const globs = [];
373
591
  const rootPkg = path18.join(root, "package.json");
374
- if (fs17.existsSync(rootPkg)) {
592
+ if (fs18.existsSync(rootPkg)) {
375
593
  try {
376
- const pkg = JSON.parse(fs17.readFileSync(rootPkg, "utf8"));
594
+ const pkg = JSON.parse(fs18.readFileSync(rootPkg, "utf8"));
377
595
  const ws = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces?.packages;
378
596
  if (ws) globs.push(...ws);
379
597
  } catch {
380
598
  }
381
599
  }
382
600
  const pnpmWs = path18.join(root, "pnpm-workspace.yaml");
383
- if (fs17.existsSync(pnpmWs)) {
384
- for (const line of fs17.readFileSync(pnpmWs, "utf8").split("\n")) {
601
+ if (fs18.existsSync(pnpmWs)) {
602
+ for (const line of fs18.readFileSync(pnpmWs, "utf8").split("\n")) {
385
603
  const m = /^\s*-\s*['"]?([^'"\n]+)['"]?\s*$/.exec(line);
386
604
  if (m) globs.push(m[1].trim());
387
605
  }
@@ -389,7 +607,7 @@ function loadWorkspacePackages(root) {
389
607
  for (const dir of expandWorkspaceGlobs(root, globs)) {
390
608
  const pj = path18.join(dir, "package.json");
391
609
  try {
392
- const name = JSON.parse(fs17.readFileSync(pj, "utf8")).name;
610
+ const name = JSON.parse(fs18.readFileSync(pj, "utf8")).name;
393
611
  if (name) map.set(name, path18.relative(root, dir).split(path18.sep).join("/"));
394
612
  } catch {
395
613
  }
@@ -403,7 +621,7 @@ function expandWorkspaceGlobs(root, globs) {
403
621
  const baseDir = path18.join(root, clean);
404
622
  const recurse = glob.endsWith("**");
405
623
  if (glob === clean) {
406
- if (fs17.existsSync(path18.join(baseDir, "package.json"))) out.add(baseDir);
624
+ if (fs18.existsSync(path18.join(baseDir, "package.json"))) out.add(baseDir);
407
625
  continue;
408
626
  }
409
627
  collectPackageDirs(baseDir, recurse ? 4 : 1, out);
@@ -414,14 +632,14 @@ function collectPackageDirs(dir, depth, out) {
414
632
  if (depth < 0) return;
415
633
  let entries;
416
634
  try {
417
- entries = fs17.readdirSync(dir, { withFileTypes: true });
635
+ entries = fs18.readdirSync(dir, { withFileTypes: true });
418
636
  } catch {
419
637
  return;
420
638
  }
421
639
  for (const e of entries) {
422
640
  if (!e.isDirectory() || e.name === "node_modules" || e.name.startsWith(".")) continue;
423
641
  const child = path18.join(dir, e.name);
424
- if (fs17.existsSync(path18.join(child, "package.json"))) out.add(child);
642
+ if (fs18.existsSync(path18.join(child, "package.json"))) out.add(child);
425
643
  else collectPackageDirs(child, depth - 1, out);
426
644
  }
427
645
  }
@@ -921,12 +1139,12 @@ function loadCoverage(root, explicit) {
921
1139
  const candidates = (explicit && explicit.length ? explicit : DEFAULT_PATHS).map(
922
1140
  (p) => path18.resolve(root, p)
923
1141
  );
924
- const found = candidates.filter((p) => fs17.existsSync(p));
1142
+ const found = candidates.filter((p) => fs18.existsSync(p));
925
1143
  if (found.length === 0) return null;
926
1144
  const map = /* @__PURE__ */ new Map();
927
1145
  for (const file of found) {
928
1146
  try {
929
- const text = fs17.readFileSync(file, "utf8");
1147
+ const text = fs18.readFileSync(file, "utf8");
930
1148
  if (file.endsWith(".json")) mergeIstanbul(map, text, root);
931
1149
  else mergeLcov(map, text, root);
932
1150
  } catch {
@@ -1256,34 +1474,42 @@ var DEFAULT_INLINE_THRESHOLD = 24;
1256
1474
  async function parseFiles(files, options = {}) {
1257
1475
  const threshold = options.inlineThreshold ?? DEFAULT_INLINE_THRESHOLD;
1258
1476
  const cores = Math.max(1, os.cpus()?.length ?? 1);
1259
- const jobs = Math.max(1, options.jobs ?? (Math.min(cores - 1, files.length) || 1));
1477
+ const jobs = Math.max(1, options.jobs ?? envJobs() ?? (Math.min(cores - 1, files.length) || 1));
1260
1478
  const workerFile = resolveWorkerFile();
1261
1479
  const useInline = options.inline === true || jobs <= 1 || files.length < threshold || workerFile === null;
1262
1480
  if (useInline) {
1263
1481
  if (options.grammarsDir) setGrammarsOverride(options.grammarsDir);
1264
- return sortByRel(await parseInline(files, options.onProgress));
1482
+ return sortByRel(await parseInline(files, options));
1265
1483
  }
1266
- return sortByRel(
1267
- await parsePooled(files, jobs, workerFile, options.onProgress, options.grammarsDir)
1268
- );
1484
+ return sortByRel(await parsePooled(files, jobs, workerFile, options));
1269
1485
  }
1270
- async function parseInline(files, onProgress) {
1486
+ var MEM_CHECK_EVERY = 64;
1487
+ async function parseInline(files, options) {
1488
+ const { onProgress, memoryBudgetMb = 0 } = options;
1271
1489
  const out = [];
1272
1490
  onProgress?.(0, files.length);
1273
1491
  for (const file of files) {
1274
1492
  try {
1275
- const source = fs17.readFileSync(file.abs, "utf8");
1493
+ const source = fs18.readFileSync(file.abs, "utf8");
1276
1494
  out.push(await parseSource(file.rel, file.lang.id, source));
1277
1495
  } catch (err) {
1278
1496
  out.push(emptyParse(file, `parse failed: ${err.message}`));
1279
1497
  }
1280
1498
  onProgress?.(out.length, files.length);
1499
+ if (out.length % MEM_CHECK_EVERY === 0) checkMemoryBudget("parse", memoryBudgetMb);
1281
1500
  }
1282
1501
  return out;
1283
1502
  }
1284
- async function parsePooled(files, jobs, workerFile, onProgress, grammarsDir) {
1503
+ async function parsePooled(files, jobs, workerFile, options) {
1504
+ const { onProgress, grammarsDir, memoryBudgetMb = 0 } = options;
1285
1505
  const { default: Tinypool } = await import('tinypool');
1286
- const pool = new Tinypool({ filename: workerFile, maxThreads: jobs, minThreads: 1 });
1506
+ const workerHeapMb = envWorkerHeapMb();
1507
+ const pool = new Tinypool({
1508
+ filename: workerFile,
1509
+ maxThreads: jobs,
1510
+ minThreads: 1,
1511
+ ...workerHeapMb ? { resourceLimits: { maxOldGenerationSizeMb: workerHeapMb } } : {}
1512
+ });
1287
1513
  try {
1288
1514
  const total = files.length;
1289
1515
  const buckets = chunk(
@@ -1297,19 +1523,31 @@ async function parsePooled(files, jobs, workerFile, onProgress, grammarsDir) {
1297
1523
  (b) => pool.run({ tasks: b, grammarsDir }).then((r) => {
1298
1524
  done += b.length;
1299
1525
  onProgress?.(done, total);
1526
+ checkMemoryBudget("parse", memoryBudgetMb);
1300
1527
  return r;
1301
1528
  })
1302
1529
  )
1303
1530
  );
1304
1531
  return results.flat();
1532
+ } catch (err) {
1533
+ if (isWorkerOom(err)) {
1534
+ throw new ResourceLimitError(
1535
+ `graph build stopped: a parse worker exceeded its ${workerHeapMb ?? "?"} MiB heap cap (VG_WORKER_HEAP_MB). Raise the cap, exclude the offending files (--exclude), or run single-threaded with --jobs 1.`
1536
+ );
1537
+ }
1538
+ throw err;
1305
1539
  } finally {
1306
1540
  await pool.destroy();
1307
1541
  }
1308
1542
  }
1543
+ function isWorkerOom(err) {
1544
+ const e = err;
1545
+ return e?.code === "ERR_WORKER_OUT_OF_MEMORY" || /out of memory/i.test(e?.message ?? "");
1546
+ }
1309
1547
  function resolveWorkerFile() {
1310
1548
  const here = path18.dirname(fileURLToPath(import.meta.url));
1311
1549
  const candidate = path18.join(here, "parse-worker.js");
1312
- return fs17.existsSync(candidate) ? candidate : null;
1550
+ return fs18.existsSync(candidate) ? candidate : null;
1313
1551
  }
1314
1552
  function chunk(items, buckets) {
1315
1553
  const out = Array.from({ length: Math.min(buckets, items.length || 1) }, () => []);
@@ -1769,9 +2007,9 @@ function loadCache(root, opts) {
1769
2007
  grammars: opts.grammars,
1770
2008
  entries: {}
1771
2009
  };
1772
- if (!opts.disabled && fs17.existsSync(file)) {
2010
+ if (!opts.disabled && fs18.existsSync(file)) {
1773
2011
  try {
1774
- const loaded = JSON.parse(fs17.readFileSync(file, "utf8"));
2012
+ const loaded = JSON.parse(fs18.readFileSync(file, "utf8"));
1775
2013
  if (loaded.version === CACHE_VERSION && loaded.toolVersion === opts.toolVersion && loaded.grammars === opts.grammars && loaded.entries) {
1776
2014
  data = loaded;
1777
2015
  }
@@ -1792,8 +2030,8 @@ function loadCache(root, opts) {
1792
2030
  }
1793
2031
  },
1794
2032
  save() {
1795
- fs17.mkdirSync(cacheDir(root), { recursive: true });
1796
- fs17.writeFileSync(file, stableStringify(data, 0));
2033
+ fs18.mkdirSync(cacheDir(root), { recursive: true });
2034
+ fs18.writeFileSync(file, stableStringify(data, 0));
1797
2035
  }
1798
2036
  };
1799
2037
  }
@@ -1808,6 +2046,13 @@ async function buildGraph(options) {
1808
2046
  exclude: options.exclude,
1809
2047
  paths: options.paths
1810
2048
  });
2049
+ const limits = resolveLimits(options.limits);
2050
+ if (limits.maxFiles > 0 && files.length > limits.maxFiles) {
2051
+ throw new ResourceLimitError(
2052
+ `graph build stopped: ${files.length.toLocaleString()} files exceed the ${limits.maxFiles.toLocaleString()}-file limit. Scope the build (pass sub-paths, add --exclude globs, or --only <langs>), or set VG_MAX_FILES to raise the limit (0 disables it).`
2053
+ );
2054
+ }
2055
+ checkMemoryBudget("discovery", limits.memoryBudgetMb);
1811
2056
  const grammars = grammarSetVersion();
1812
2057
  const cache = loadCache(root, {
1813
2058
  toolVersion: VERSION,
@@ -1818,27 +2063,46 @@ async function buildGraph(options) {
1818
2063
  const fileStats = [];
1819
2064
  const toParse = [];
1820
2065
  const reused = [];
2066
+ const buildWarnings = [];
1821
2067
  for (const file of files) {
1822
- let hash = "";
1823
2068
  let stat;
1824
2069
  try {
1825
- stat = fs17.statSync(file.abs);
1826
- hash = hashBytes(fs17.readFileSync(file.abs));
2070
+ stat = fs18.statSync(file.abs);
2071
+ } catch {
2072
+ continue;
2073
+ }
2074
+ if (limits.maxFileBytes > 0 && stat.size > limits.maxFileBytes) {
2075
+ fileStats.push({
2076
+ rel: file.rel,
2077
+ size: stat.size,
2078
+ mtimeMs: stat.mtimeMs,
2079
+ hash: hashString(`vg:oversize:${stat.size}`)
2080
+ });
2081
+ buildWarnings.push(
2082
+ `${file.rel}: skipped \u2014 ${formatBytes(stat.size)} exceeds the ${formatBytes(limits.maxFileBytes)} per-file limit (set VG_MAX_FILE_BYTES to raise it, 0 to disable)`
2083
+ );
2084
+ continue;
2085
+ }
2086
+ let hash = "";
2087
+ try {
2088
+ hash = hashBytes(fs18.readFileSync(file.abs));
1827
2089
  } catch {
1828
2090
  continue;
1829
2091
  }
1830
2092
  hashes.set(file.rel, hash);
1831
2093
  fileStats.push({ rel: file.rel, size: stat.size, mtimeMs: stat.mtimeMs, hash });
1832
- const cached = cache.get(file.rel, hash);
1833
- if (cached) reused.push(cached);
2094
+ const cached2 = cache.get(file.rel, hash);
2095
+ if (cached2) reused.push(cached2);
1834
2096
  else toParse.push(file);
1835
2097
  }
1836
2098
  const parsedNew = await parseFiles(toParse, {
1837
2099
  jobs: options.jobs,
1838
2100
  inline: options.inline,
1839
2101
  onProgress: options.onParseProgress,
1840
- grammarsDir: options.grammarsDir
2102
+ grammarsDir: options.grammarsDir,
2103
+ memoryBudgetMb: limits.memoryBudgetMb
1841
2104
  });
2105
+ checkMemoryBudget("parse", limits.memoryBudgetMb);
1842
2106
  for (const p of parsedNew) cache.set(p.rel, p);
1843
2107
  const currentRels = new Set(files.map((f) => f.rel));
1844
2108
  cache.prune(currentRels);
@@ -1846,14 +2110,21 @@ async function buildGraph(options) {
1846
2110
  const parses = [...reused, ...parsedNew].sort(
1847
2111
  (a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0
1848
2112
  );
1849
- const warnings = parses.flatMap((p) => p.warnings ?? []);
2113
+ const warnings = [...buildWarnings, ...parses.flatMap((p) => p.warnings ?? [])];
1850
2114
  const moduleResolver = buildModuleResolver(root, new Set(parses.map((p) => p.rel)));
1851
2115
  const resolved = resolve4(parses, moduleResolver);
2116
+ checkMemoryBudget("resolve", limits.memoryBudgetMb);
1852
2117
  const nodeFileById = new Map(resolved.nodes.map((n) => [n.id, n.file]));
1853
2118
  let edges = resolved.edges;
1854
2119
  const resolvers = [...resolved.stats.resolvers];
1855
2120
  let tscStats;
1856
- const tsFiles = options.noTsc ? [] : files.filter((f) => f.lang.id === "ts" || f.lang.id === "tsx" || f.lang.id === "js").map((f) => ({ rel: f.rel, abs: f.abs }));
2121
+ let tsFiles = options.noTsc ? [] : files.filter((f) => (f.lang.id === "ts" || f.lang.id === "tsx" || f.lang.id === "js") && hashes.has(f.rel)).map((f) => ({ rel: f.rel, abs: f.abs }));
2122
+ if (limits.tscMaxFiles > 0 && tsFiles.length > limits.tscMaxFiles) {
2123
+ warnings.push(
2124
+ `typescript resolver skipped \u2014 ${tsFiles.length.toLocaleString()} TS/JS files exceed the ${limits.tscMaxFiles.toLocaleString()}-file limit; calls use the heuristic resolver (set VG_TSC_MAX_FILES to raise it, 0 to disable)`
2125
+ );
2126
+ tsFiles = [];
2127
+ }
1857
2128
  if (tsFiles.length) {
1858
2129
  const res = tsResolveEdges(root, tsFiles, resolved.nodes);
1859
2130
  if (res.stats.files > 0) {
@@ -1861,6 +2132,7 @@ async function buildGraph(options) {
1861
2132
  if (!resolvers.includes("tsc")) resolvers.unshift("tsc");
1862
2133
  tscStats = res.stats;
1863
2134
  }
2135
+ checkMemoryBudget("typescript resolution", limits.memoryBudgetMb);
1864
2136
  }
1865
2137
  const scip = options.noScip ? null : loadScipIndex(root, options.scip);
1866
2138
  let scipStats;
@@ -1875,6 +2147,7 @@ async function buildGraph(options) {
1875
2147
  const coverage = options.noCoverage ? null : loadCoverage(root, options.coverage);
1876
2148
  if (coverage) nodes = applyCoverage(nodes, coverage);
1877
2149
  const analysis = analyze(nodes, linked.edges, { cluster: options.cluster });
2150
+ checkMemoryBudget("analysis", limits.memoryBudgetMb);
1878
2151
  const languages = [...new Set(parses.map((p) => p.lang))].sort();
1879
2152
  const edgeKinds = [...new Set(analysis.edges.map((e) => e.kind))].sort();
1880
2153
  const corpusHash = computeCorpusHash(parses, hashes);
@@ -1943,9 +2216,9 @@ function loadScipIndex(root, explicit) {
1943
2216
  ].filter((p) => Boolean(p));
1944
2217
  for (const file of candidates) {
1945
2218
  const abs = path18.isAbsolute(file) ? file : path18.resolve(root, file);
1946
- if (!fs17.existsSync(abs)) continue;
2219
+ if (!fs18.existsSync(abs)) continue;
1947
2220
  try {
1948
- const index = decodeScipIndex(new Uint8Array(fs17.readFileSync(abs)));
2221
+ const index = decodeScipIndex(new Uint8Array(fs18.readFileSync(abs)));
1949
2222
  if (index.documents.length) {
1950
2223
  return { index, tool: index.toolVersion ? `${index.toolName} ${index.toolVersion}` : index.toolName };
1951
2224
  }
@@ -1987,7 +2260,7 @@ function isProcessAlive(pid) {
1987
2260
  }
1988
2261
  function lockIsStale(file, staleMs) {
1989
2262
  try {
1990
- const { pid, at } = JSON.parse(fs17.readFileSync(file, "utf8"));
2263
+ const { pid, at } = JSON.parse(fs18.readFileSync(file, "utf8"));
1991
2264
  if (typeof at === "number" && Date.now() - at > staleMs) return true;
1992
2265
  if (typeof pid === "number" && !isProcessAlive(pid)) return true;
1993
2266
  return false;
@@ -1998,10 +2271,10 @@ function lockIsStale(file, staleMs) {
1998
2271
  function acquireLock(file, staleMs = DEFAULT_LOCK_STALE_MS) {
1999
2272
  const write = () => {
2000
2273
  try {
2001
- fs17.mkdirSync(path18.dirname(file), { recursive: true });
2002
- const fd = fs17.openSync(file, "wx");
2003
- fs17.writeFileSync(fd, JSON.stringify({ pid: process.pid, at: Date.now() }));
2004
- fs17.closeSync(fd);
2274
+ fs18.mkdirSync(path18.dirname(file), { recursive: true });
2275
+ const fd = fs18.openSync(file, "wx");
2276
+ fs18.writeFileSync(fd, JSON.stringify({ pid: process.pid, at: Date.now() }));
2277
+ fs18.closeSync(fd);
2005
2278
  return true;
2006
2279
  } catch {
2007
2280
  return false;
@@ -2010,7 +2283,7 @@ function acquireLock(file, staleMs = DEFAULT_LOCK_STALE_MS) {
2010
2283
  if (write()) return true;
2011
2284
  if (lockIsStale(file, staleMs)) {
2012
2285
  try {
2013
- fs17.rmSync(file, { force: true });
2286
+ fs18.rmSync(file, { force: true });
2014
2287
  } catch {
2015
2288
  }
2016
2289
  return write();
@@ -2019,7 +2292,7 @@ function acquireLock(file, staleMs = DEFAULT_LOCK_STALE_MS) {
2019
2292
  }
2020
2293
  function releaseLock(file) {
2021
2294
  try {
2022
- fs17.rmSync(file, { force: true });
2295
+ fs18.rmSync(file, { force: true });
2023
2296
  } catch {
2024
2297
  }
2025
2298
  }
@@ -2036,7 +2309,7 @@ function modelReadyMarker(modelId) {
2036
2309
  return path18.join(modelCacheDir(), `.ready-${safe(modelId)}`);
2037
2310
  }
2038
2311
  function isModelReady(modelId = resolveEmbedModel()) {
2039
- return fs17.existsSync(modelReadyMarker(modelId));
2312
+ return fs18.existsSync(modelReadyMarker(modelId));
2040
2313
  }
2041
2314
  function unavailableMessage(reason) {
2042
2315
  switch (reason) {
@@ -2053,13 +2326,13 @@ function unavailableMessage(reason) {
2053
2326
  }
2054
2327
  function modelCacheInfo(modelId = resolveEmbedModel()) {
2055
2328
  const dir = modelCacheDir();
2056
- return { dir, present: isModelReady(modelId) || fs17.existsSync(dir), bytes: dirSize(dir) };
2329
+ return { dir, present: isModelReady(modelId) || fs18.existsSync(dir), bytes: dirSize(dir) };
2057
2330
  }
2058
2331
  function clearModelCache() {
2059
2332
  const dir = modelCacheDir();
2060
2333
  const bytes = dirSize(dir);
2061
2334
  try {
2062
- fs17.rmSync(dir, { recursive: true, force: true });
2335
+ fs18.rmSync(dir, { recursive: true, force: true });
2063
2336
  } catch {
2064
2337
  }
2065
2338
  return bytes;
@@ -2068,7 +2341,7 @@ function dirSize(dir) {
2068
2341
  let total = 0;
2069
2342
  let entries;
2070
2343
  try {
2071
- entries = fs17.readdirSync(dir, { withFileTypes: true });
2344
+ entries = fs18.readdirSync(dir, { withFileTypes: true });
2072
2345
  } catch {
2073
2346
  return 0;
2074
2347
  }
@@ -2077,7 +2350,7 @@ function dirSize(dir) {
2077
2350
  if (e.isDirectory()) total += dirSize(p);
2078
2351
  else {
2079
2352
  try {
2080
- total += fs17.statSync(p).size;
2353
+ total += fs18.statSync(p).size;
2081
2354
  } catch {
2082
2355
  }
2083
2356
  }
@@ -2089,7 +2362,7 @@ function isPermissionError(e) {
2089
2362
  return code === "EACCES" || code === "EPERM" || code === "EROFS";
2090
2363
  }
2091
2364
  function embeddingsCached(root, modelId) {
2092
- return fs17.existsSync(path18.join(cacheDir(root), `embeddings-${safe(modelId)}.json`));
2365
+ return fs18.existsSync(path18.join(cacheDir(root), `embeddings-${safe(modelId)}.json`));
2093
2366
  }
2094
2367
  async function loadEmbedder(options = {}) {
2095
2368
  if (options.local) return null;
@@ -2103,7 +2376,7 @@ async function loadEmbedder(options = {}) {
2103
2376
  if (!mod?.FlagEmbedding) return fail("not-installed");
2104
2377
  const cache = modelCacheDir();
2105
2378
  try {
2106
- fs17.mkdirSync(cache, { recursive: true });
2379
+ fs18.mkdirSync(cache, { recursive: true });
2107
2380
  } catch (e) {
2108
2381
  return fail(isPermissionError(e) ? "no-permission" : "init-failed");
2109
2382
  }
@@ -2118,7 +2391,7 @@ async function loadEmbedder(options = {}) {
2118
2391
  "embedding model init timed out"
2119
2392
  );
2120
2393
  try {
2121
- fs17.writeFileSync(modelReadyMarker(modelId), (/* @__PURE__ */ new Date(0)).toISOString());
2394
+ fs18.writeFileSync(modelReadyMarker(modelId), (/* @__PURE__ */ new Date(0)).toISOString());
2122
2395
  } catch {
2123
2396
  }
2124
2397
  return {
@@ -2225,15 +2498,15 @@ function countPending(graph, root, modelId) {
2225
2498
  for (const n of graph.nodes) {
2226
2499
  if (n.kind === "file" || n.kind === "external") continue;
2227
2500
  const h = hashString(nodeEmbedText(n, areaLabel.get(n.area)));
2228
- const cached = entries[n.id];
2229
- if (!cached || cached.hash !== h) pending++;
2501
+ const cached2 = entries[n.id];
2502
+ if (!cached2 || cached2.hash !== h) pending++;
2230
2503
  }
2231
2504
  return pending;
2232
2505
  }
2233
2506
  function readCacheEntries(file, modelId) {
2234
- if (!fs17.existsSync(file)) return {};
2507
+ if (!fs18.existsSync(file)) return {};
2235
2508
  try {
2236
- const loaded = JSON.parse(fs17.readFileSync(file, "utf8"));
2509
+ const loaded = JSON.parse(fs18.readFileSync(file, "utf8"));
2237
2510
  if (loaded.model === modelId && loaded.entries) return loaded.entries;
2238
2511
  } catch {
2239
2512
  }
@@ -2242,9 +2515,9 @@ function readCacheEntries(file, modelId) {
2242
2515
  async function getNodeEmbeddings(graph, embedder, root, onProgress) {
2243
2516
  const file = vectorCachePath(root, embedder.id);
2244
2517
  let cache = { model: embedder.id, entries: {} };
2245
- if (fs17.existsSync(file)) {
2518
+ if (fs18.existsSync(file)) {
2246
2519
  try {
2247
- const loaded = JSON.parse(fs17.readFileSync(file, "utf8"));
2520
+ const loaded = JSON.parse(fs18.readFileSync(file, "utf8"));
2248
2521
  if (loaded.model === embedder.id && loaded.entries) cache = loaded;
2249
2522
  } catch {
2250
2523
  }
@@ -2256,16 +2529,16 @@ async function getNodeEmbeddings(graph, embedder, root, onProgress) {
2256
2529
  for (const n of targets) {
2257
2530
  const text = nodeEmbedText(n, areaLabel.get(n.area));
2258
2531
  const h = hashString(text);
2259
- const cached = cache.entries[n.id];
2260
- if (cached && cached.hash === h) vectors.set(n.id, cached.vec);
2532
+ const cached2 = cache.entries[n.id];
2533
+ if (cached2 && cached2.hash === h) vectors.set(n.id, cached2.vec);
2261
2534
  else toEmbed.push({ id: n.id, text, hash: h });
2262
2535
  }
2263
2536
  const persist = () => {
2264
2537
  try {
2265
- fs17.mkdirSync(cacheDir(root), { recursive: true });
2538
+ fs18.mkdirSync(cacheDir(root), { recursive: true });
2266
2539
  const tmp = `${file}.tmp.${process.pid}`;
2267
- fs17.writeFileSync(tmp, JSON.stringify(cache));
2268
- fs17.renameSync(tmp, file);
2540
+ fs18.writeFileSync(tmp, JSON.stringify(cache));
2541
+ fs18.renameSync(tmp, file);
2269
2542
  } catch {
2270
2543
  }
2271
2544
  };
@@ -2421,31 +2694,31 @@ function defaultGraphPath(root) {
2421
2694
  }
2422
2695
  function writeArtifacts(graph, options) {
2423
2696
  const dir = vibgrateDir(options.root);
2424
- fs17.mkdirSync(dir, { recursive: true });
2697
+ fs18.mkdirSync(dir, { recursive: true });
2425
2698
  const graphPath = options.graphPath ?? defaultGraphPath(options.root);
2426
- fs17.mkdirSync(path18.dirname(graphPath), { recursive: true });
2699
+ fs18.mkdirSync(path18.dirname(graphPath), { recursive: true });
2427
2700
  const tmp = `${graphPath}.${process.pid}.tmp`;
2428
2701
  try {
2429
- fs17.writeFileSync(tmp, serializeGraph(graph));
2430
- fs17.renameSync(tmp, graphPath);
2702
+ fs18.writeFileSync(tmp, serializeGraph(graph));
2703
+ fs18.renameSync(tmp, graphPath);
2431
2704
  } catch (err) {
2432
- fs17.rmSync(tmp, { force: true });
2705
+ fs18.rmSync(tmp, { force: true });
2433
2706
  throw err;
2434
2707
  }
2435
2708
  const written = { graphPath };
2436
2709
  if (options.report !== false) {
2437
2710
  const reportPath = path18.join(dir, "GRAPH_REPORT.md");
2438
- fs17.writeFileSync(reportPath, renderReport(graph));
2711
+ fs18.writeFileSync(reportPath, renderReport(graph));
2439
2712
  written.reportPath = reportPath;
2440
2713
  }
2441
2714
  if (options.html !== false) {
2442
2715
  const htmlPath = path18.join(dir, "graph.html");
2443
- fs17.writeFileSync(htmlPath, renderHtml(graph));
2716
+ fs18.writeFileSync(htmlPath, renderHtml(graph));
2444
2717
  written.htmlPath = htmlPath;
2445
2718
  }
2446
2719
  if (graph.facts && graph.facts.length) {
2447
2720
  const factsPath = path18.join(dir, "facts.jsonl");
2448
- fs17.writeFileSync(factsPath, graph.facts.map((f) => JSON.stringify(f)).join("\n") + "\n");
2721
+ fs18.writeFileSync(factsPath, graph.facts.map((f) => JSON.stringify(f)).join("\n") + "\n");
2449
2722
  written.factsPath = factsPath;
2450
2723
  }
2451
2724
  return written;
@@ -2459,14 +2732,14 @@ function writeSnapshot(root, corpusHash, fileStats, scope = {}) {
2459
2732
  for (const f of fileStats) files[f.rel] = { size: f.size, mtimeMs: f.mtimeMs, hash: f.hash };
2460
2733
  const snapshot = { version: SNAPSHOT_VERSION, corpusHash, scope: pruneScope(scope), files };
2461
2734
  try {
2462
- fs17.mkdirSync(cacheDir(root), { recursive: true });
2463
- fs17.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2735
+ fs18.mkdirSync(cacheDir(root), { recursive: true });
2736
+ fs18.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2464
2737
  } catch {
2465
2738
  }
2466
2739
  }
2467
2740
  function loadSnapshot(root) {
2468
2741
  try {
2469
- const loaded = JSON.parse(fs17.readFileSync(snapshotPath(root), "utf8"));
2742
+ const loaded = JSON.parse(fs18.readFileSync(snapshotPath(root), "utf8"));
2470
2743
  if (loaded.version === SNAPSHOT_VERSION && loaded.files && typeof loaded.corpusHash === "string") {
2471
2744
  return { ...loaded, scope: loaded.scope ?? {} };
2472
2745
  }
@@ -2502,7 +2775,7 @@ function probeFreshness(root) {
2502
2775
  }
2503
2776
  let stat;
2504
2777
  try {
2505
- stat = fs17.statSync(file.abs);
2778
+ stat = fs18.statSync(file.abs);
2506
2779
  } catch {
2507
2780
  drift.removed.push(file.rel);
2508
2781
  continue;
@@ -2510,7 +2783,7 @@ function probeFreshness(root) {
2510
2783
  if (stat.size === entry.size && stat.mtimeMs === entry.mtimeMs) continue;
2511
2784
  let hash = "";
2512
2785
  try {
2513
- hash = hashBytes(fs17.readFileSync(file.abs));
2786
+ hash = hashBytes(fs18.readFileSync(file.abs));
2514
2787
  } catch {
2515
2788
  drift.removed.push(file.rel);
2516
2789
  continue;
@@ -2527,7 +2800,7 @@ function probeFreshness(root) {
2527
2800
  }
2528
2801
  if (absorbed && !hasDrift(drift)) {
2529
2802
  try {
2530
- fs17.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2803
+ fs18.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2531
2804
  } catch {
2532
2805
  }
2533
2806
  }
@@ -2573,8 +2846,8 @@ function usageError(message) {
2573
2846
  }
2574
2847
  function loadGraph(root, graphPath) {
2575
2848
  const file = graphPath ?? defaultGraphPath(root);
2576
- if (!fs17.existsSync(file)) return null;
2577
- return parseGraph(fs17.readFileSync(file, "utf8"));
2849
+ if (!fs18.existsSync(file)) return null;
2850
+ return parseGraph(fs18.readFileSync(file, "utf8"));
2578
2851
  }
2579
2852
 
2580
2853
  // src/engine/verify.ts
@@ -2583,10 +2856,10 @@ async function verifyDeterminism(opts) {
2583
2856
  const base = { root: opts.root, only: opts.only, exclude: opts.exclude, generatedAt: PINNED };
2584
2857
  const a = serializeGraph((await buildGraph({ ...base, noCache: true, jobs: opts.jobs })).graph);
2585
2858
  const b = serializeGraph((await buildGraph({ ...base, noCache: true, jobs: 1 })).graph);
2586
- const cached = serializeGraph((await buildGraph({ ...base, noCache: false })).graph);
2859
+ const cached2 = serializeGraph((await buildGraph({ ...base, noCache: false })).graph);
2587
2860
  const checks = [
2588
2861
  { name: "run-to-run determinism", ok: a === b, detail: diffHint(a, b) },
2589
- { name: "cache safety (incremental == full)", ok: a === cached, detail: diffHint(a, cached) }
2862
+ { name: "cache safety (incremental == full)", ok: a === cached2, detail: diffHint(a, cached2) }
2590
2863
  ];
2591
2864
  return {
2592
2865
  ok: checks.every((c) => c.ok),
@@ -2871,8 +3144,8 @@ async function refreshIfStale(root, opts = {}) {
2871
3144
  const dir = vibgrateDir(root);
2872
3145
  writeArtifacts(result.graph, {
2873
3146
  root,
2874
- report: fs17.existsSync(path18.join(dir, "GRAPH_REPORT.md")),
2875
- html: fs17.existsSync(path18.join(dir, "graph.html"))
3147
+ report: fs18.existsSync(path18.join(dir, "GRAPH_REPORT.md")),
3148
+ html: fs18.existsSync(path18.join(dir, "graph.html"))
2876
3149
  });
2877
3150
  }
2878
3151
  writeSnapshot(root, result.graph.provenance.corpusHash, result.fileStats, scope);
@@ -3049,9 +3322,9 @@ function testsToRun(graph, rootId, depth = 4) {
3049
3322
  }
3050
3323
  function detectRunner(root, lang) {
3051
3324
  const pkgPath = path18.join(root, "package.json");
3052
- if (fs17.existsSync(pkgPath)) {
3325
+ if (fs18.existsSync(pkgPath)) {
3053
3326
  try {
3054
- const pkg = JSON.parse(fs17.readFileSync(pkgPath, "utf8"));
3327
+ const pkg = JSON.parse(fs18.readFileSync(pkgPath, "utf8"));
3055
3328
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
3056
3329
  if (deps.vitest) return { name: "vitest", command: (f) => `npx vitest run ${f.join(" ")}`.trim() };
3057
3330
  if (deps.jest) return { name: "jest", command: (f) => `npx jest ${f.join(" ")}`.trim() };
@@ -3059,10 +3332,10 @@ function detectRunner(root, lang) {
3059
3332
  } catch {
3060
3333
  }
3061
3334
  }
3062
- if (lang === "py" || fs17.existsSync(path18.join(root, "pyproject.toml")) || fs17.existsSync(path18.join(root, "pytest.ini"))) {
3335
+ if (lang === "py" || fs18.existsSync(path18.join(root, "pyproject.toml")) || fs18.existsSync(path18.join(root, "pytest.ini"))) {
3063
3336
  return { name: "pytest", command: (f) => `pytest ${f.join(" ")}`.trim() };
3064
3337
  }
3065
- if (lang === "go" || fs17.existsSync(path18.join(root, "go.mod"))) {
3338
+ if (lang === "go" || fs18.existsSync(path18.join(root, "go.mod"))) {
3066
3339
  return { name: "go test", command: () => "go test ./..." };
3067
3340
  }
3068
3341
  if (lang === "cs" || hasFile(root, /\.csproj$/) || hasFile(root, /\.sln$/)) {
@@ -3073,7 +3346,7 @@ function detectRunner(root, lang) {
3073
3346
  }
3074
3347
  function hasFile(root, re) {
3075
3348
  try {
3076
- return fs17.readdirSync(root).some((f) => re.test(f));
3349
+ return fs18.readdirSync(root).some((f) => re.test(f));
3077
3350
  } catch {
3078
3351
  return false;
3079
3352
  }
@@ -3145,7 +3418,7 @@ function findManifests(root) {
3145
3418
  if (depth > 8 || scanned > MAX_ENTRIES) return;
3146
3419
  let entries;
3147
3420
  try {
3148
- entries = fs17.readdirSync(dir, { withFileTypes: true });
3421
+ entries = fs18.readdirSync(dir, { withFileTypes: true });
3149
3422
  } catch {
3150
3423
  return;
3151
3424
  }
@@ -3169,7 +3442,7 @@ function npmDeps(files) {
3169
3442
  for (const file of files) {
3170
3443
  let pkg;
3171
3444
  try {
3172
- pkg = JSON.parse(fs17.readFileSync(file, "utf8"));
3445
+ pkg = JSON.parse(fs18.readFileSync(file, "utf8"));
3173
3446
  } catch {
3174
3447
  continue;
3175
3448
  }
@@ -3186,7 +3459,7 @@ function installedNpmVersion(dir, name) {
3186
3459
  for (let i = 0; i < 12; i++) {
3187
3460
  const p = path18.join(cur, "node_modules", name, "package.json");
3188
3461
  try {
3189
- return JSON.parse(fs17.readFileSync(p, "utf8")).version;
3462
+ return JSON.parse(fs18.readFileSync(p, "utf8")).version;
3190
3463
  } catch {
3191
3464
  }
3192
3465
  const parent = path18.dirname(cur);
@@ -3206,7 +3479,7 @@ function normalizePep503(name) {
3206
3479
  function sitePackagesDirs(base) {
3207
3480
  const out = [];
3208
3481
  try {
3209
- for (const d of fs17.readdirSync(path18.join(base, "lib"))) {
3482
+ for (const d of fs18.readdirSync(path18.join(base, "lib"))) {
3210
3483
  if (d.startsWith("python")) out.push(path18.join(base, "lib", d, "site-packages"));
3211
3484
  }
3212
3485
  } catch {
@@ -3220,7 +3493,7 @@ function installedPypiVersion(root, name) {
3220
3493
  for (const sp of sitePackagesDirs(path18.join(root, venv))) {
3221
3494
  let entries;
3222
3495
  try {
3223
- entries = fs17.readdirSync(sp);
3496
+ entries = fs18.readdirSync(sp);
3224
3497
  } catch {
3225
3498
  continue;
3226
3499
  }
@@ -3235,7 +3508,7 @@ function installedPypiVersion(root, name) {
3235
3508
  function installedPhpVersion(root, name) {
3236
3509
  let data;
3237
3510
  try {
3238
- data = JSON.parse(fs17.readFileSync(path18.join(root, "vendor", "composer", "installed.json"), "utf8"));
3511
+ data = JSON.parse(fs18.readFileSync(path18.join(root, "vendor", "composer", "installed.json"), "utf8"));
3239
3512
  } catch {
3240
3513
  return void 0;
3241
3514
  }
@@ -3254,7 +3527,7 @@ function pypiDeps(files) {
3254
3527
  for (const file of files) {
3255
3528
  let text;
3256
3529
  try {
3257
- text = fs17.readFileSync(file, "utf8");
3530
+ text = fs18.readFileSync(file, "utf8");
3258
3531
  } catch {
3259
3532
  continue;
3260
3533
  }
@@ -3342,7 +3615,7 @@ function goDeps(files) {
3342
3615
  for (const mod of files) {
3343
3616
  let text;
3344
3617
  try {
3345
- text = fs17.readFileSync(mod, "utf8");
3618
+ text = fs18.readFileSync(mod, "utf8");
3346
3619
  } catch {
3347
3620
  continue;
3348
3621
  }
@@ -3360,7 +3633,7 @@ function cargoDeps(files) {
3360
3633
  for (const file of files) {
3361
3634
  let text;
3362
3635
  try {
3363
- text = fs17.readFileSync(file, "utf8");
3636
+ text = fs18.readFileSync(file, "utf8");
3364
3637
  } catch {
3365
3638
  continue;
3366
3639
  }
@@ -3392,7 +3665,7 @@ function rubyDeps(files) {
3392
3665
  for (const file of files) {
3393
3666
  let text;
3394
3667
  try {
3395
- text = fs17.readFileSync(file, "utf8");
3668
+ text = fs18.readFileSync(file, "utf8");
3396
3669
  } catch {
3397
3670
  continue;
3398
3671
  }
@@ -3407,7 +3680,7 @@ function phpDeps(files) {
3407
3680
  for (const file of files) {
3408
3681
  let pkg;
3409
3682
  try {
3410
- pkg = JSON.parse(fs17.readFileSync(file, "utf8"));
3683
+ pkg = JSON.parse(fs18.readFileSync(file, "utf8"));
3411
3684
  } catch {
3412
3685
  continue;
3413
3686
  }
@@ -3424,7 +3697,7 @@ function dotnetDeps(files) {
3424
3697
  for (const file of files) {
3425
3698
  let text;
3426
3699
  try {
3427
- text = fs17.readFileSync(file, "utf8");
3700
+ text = fs18.readFileSync(file, "utf8");
3428
3701
  } catch {
3429
3702
  continue;
3430
3703
  }
@@ -3444,7 +3717,7 @@ function swiftDeps(files) {
3444
3717
  for (const file of files) {
3445
3718
  let text;
3446
3719
  try {
3447
- text = fs17.readFileSync(file, "utf8");
3720
+ text = fs18.readFileSync(file, "utf8");
3448
3721
  } catch {
3449
3722
  continue;
3450
3723
  }
@@ -3466,7 +3739,7 @@ function dartDeps(files) {
3466
3739
  for (const file of files) {
3467
3740
  let text;
3468
3741
  try {
3469
- text = fs17.readFileSync(file, "utf8");
3742
+ text = fs18.readFileSync(file, "utf8");
3470
3743
  } catch {
3471
3744
  continue;
3472
3745
  }
@@ -3491,7 +3764,7 @@ function javaDeps(files) {
3491
3764
  for (const file of files) {
3492
3765
  let text;
3493
3766
  try {
3494
- text = fs17.readFileSync(file, "utf8");
3767
+ text = fs18.readFileSync(file, "utf8");
3495
3768
  } catch {
3496
3769
  continue;
3497
3770
  }
@@ -3580,7 +3853,7 @@ function walk(dir, depth, onFile) {
3580
3853
  if (depth < 0) return;
3581
3854
  let entries;
3582
3855
  try {
3583
- entries = fs17.readdirSync(dir, { withFileTypes: true });
3856
+ entries = fs18.readdirSync(dir, { withFileTypes: true });
3584
3857
  } catch {
3585
3858
  return;
3586
3859
  }
@@ -3616,7 +3889,7 @@ function lockfileVersion(root, ecosystem, name) {
3616
3889
  function gradleLock(root, name) {
3617
3890
  let text;
3618
3891
  try {
3619
- text = fs17.readFileSync(path18.join(root, "gradle.lockfile"), "utf8");
3892
+ text = fs18.readFileSync(path18.join(root, "gradle.lockfile"), "utf8");
3620
3893
  } catch {
3621
3894
  return void 0;
3622
3895
  }
@@ -3626,7 +3899,7 @@ function gradleLock(root, name) {
3626
3899
  function packageResolved(root, name) {
3627
3900
  let data;
3628
3901
  try {
3629
- data = JSON.parse(fs17.readFileSync(path18.join(root, "Package.resolved"), "utf8"));
3902
+ data = JSON.parse(fs18.readFileSync(path18.join(root, "Package.resolved"), "utf8"));
3630
3903
  } catch {
3631
3904
  return void 0;
3632
3905
  }
@@ -3642,7 +3915,7 @@ function packageResolved(root, name) {
3642
3915
  function pubspecLock(root, name) {
3643
3916
  let text;
3644
3917
  try {
3645
- text = fs17.readFileSync(path18.join(root, "pubspec.lock"), "utf8");
3918
+ text = fs18.readFileSync(path18.join(root, "pubspec.lock"), "utf8");
3646
3919
  } catch {
3647
3920
  return void 0;
3648
3921
  }
@@ -3666,7 +3939,7 @@ function escapeRegExp(s) {
3666
3939
  function gemfileLock(root, name) {
3667
3940
  let text;
3668
3941
  try {
3669
- text = fs17.readFileSync(path18.join(root, "Gemfile.lock"), "utf8");
3942
+ text = fs18.readFileSync(path18.join(root, "Gemfile.lock"), "utf8");
3670
3943
  } catch {
3671
3944
  return void 0;
3672
3945
  }
@@ -3676,7 +3949,7 @@ function gemfileLock(root, name) {
3676
3949
  function composerLock(root, name) {
3677
3950
  let data;
3678
3951
  try {
3679
- data = JSON.parse(fs17.readFileSync(path18.join(root, "composer.lock"), "utf8"));
3952
+ data = JSON.parse(fs18.readFileSync(path18.join(root, "composer.lock"), "utf8"));
3680
3953
  } catch {
3681
3954
  return void 0;
3682
3955
  }
@@ -3691,7 +3964,7 @@ function composerLock(root, name) {
3691
3964
  function packagesLock(root, name) {
3692
3965
  let data;
3693
3966
  try {
3694
- data = JSON.parse(fs17.readFileSync(path18.join(root, "packages.lock.json"), "utf8"));
3967
+ data = JSON.parse(fs18.readFileSync(path18.join(root, "packages.lock.json"), "utf8"));
3695
3968
  } catch {
3696
3969
  return void 0;
3697
3970
  }
@@ -3716,7 +3989,7 @@ function pypiLockVersion(root, name) {
3716
3989
  function tomlPackageLock(root, file, name, normalize3) {
3717
3990
  let text;
3718
3991
  try {
3719
- text = fs17.readFileSync(path18.join(root, file), "utf8");
3992
+ text = fs18.readFileSync(path18.join(root, file), "utf8");
3720
3993
  } catch {
3721
3994
  return void 0;
3722
3995
  }
@@ -3733,7 +4006,7 @@ function tomlPackageLock(root, file, name, normalize3) {
3733
4006
  function pipfileLock(root, name) {
3734
4007
  let data;
3735
4008
  try {
3736
- data = JSON.parse(fs17.readFileSync(path18.join(root, "Pipfile.lock"), "utf8"));
4009
+ data = JSON.parse(fs18.readFileSync(path18.join(root, "Pipfile.lock"), "utf8"));
3737
4010
  } catch {
3738
4011
  return void 0;
3739
4012
  }
@@ -3752,7 +4025,7 @@ function pipfileLock(root, name) {
3752
4025
  function packageLockVersion(root, name) {
3753
4026
  let data;
3754
4027
  try {
3755
- data = JSON.parse(fs17.readFileSync(path18.join(root, "package-lock.json"), "utf8"));
4028
+ data = JSON.parse(fs18.readFileSync(path18.join(root, "package-lock.json"), "utf8"));
3756
4029
  } catch {
3757
4030
  return void 0;
3758
4031
  }
@@ -3764,7 +4037,7 @@ function packageLockVersion(root, name) {
3764
4037
  function yarnLockVersion(root, name) {
3765
4038
  let text;
3766
4039
  try {
3767
- text = fs17.readFileSync(path18.join(root, "yarn.lock"), "utf8");
4040
+ text = fs18.readFileSync(path18.join(root, "yarn.lock"), "utf8");
3768
4041
  } catch {
3769
4042
  return void 0;
3770
4043
  }
@@ -3802,9 +4075,9 @@ function libId(name) {
3802
4075
  }
3803
4076
  function loadCatalog(root) {
3804
4077
  const file = catalogPath(root);
3805
- if (fs17.existsSync(file)) {
4078
+ if (fs18.existsSync(file)) {
3806
4079
  try {
3807
- const data = JSON.parse(fs17.readFileSync(file, "utf8"));
4080
+ const data = JSON.parse(fs18.readFileSync(file, "utf8"));
3808
4081
  if (data.schemaVersion === LIB_SCHEMA && data.libraries) return data;
3809
4082
  } catch {
3810
4083
  }
@@ -3812,7 +4085,7 @@ function loadCatalog(root) {
3812
4085
  return { schemaVersion: LIB_SCHEMA, libraries: {} };
3813
4086
  }
3814
4087
  function saveCatalog(root, catalog) {
3815
- fs17.writeFileSync(catalogPath(root), `${stableStringify(catalog, 2)}
4088
+ fs18.writeFileSync(catalogPath(root), `${stableStringify(catalog, 2)}
3816
4089
  `);
3817
4090
  }
3818
4091
  function resolveLib(catalog, name) {
@@ -3889,16 +4162,16 @@ async function addLibrary(source, opts) {
3889
4162
  type = source.endsWith("llms.txt") ? "llms.txt" : source.endsWith(".json") ? "openapi" : "website";
3890
4163
  } else {
3891
4164
  const abs = path18.resolve(root, source);
3892
- if (!fs17.existsSync(abs)) throw new Error(`source not found: ${source}`);
4165
+ if (!fs18.existsSync(abs)) throw new Error(`source not found: ${source}`);
3893
4166
  content = readLocal(abs);
3894
4167
  type = "local";
3895
4168
  }
3896
4169
  const name = opts.name ?? inferName(source);
3897
4170
  const id = libId(name);
3898
4171
  const version = opts.version ?? installedVersion(root, name) ?? "*";
3899
- fs17.mkdirSync(libDir(root), { recursive: true });
4172
+ fs18.mkdirSync(libDir(root), { recursive: true });
3900
4173
  const docFileAbs = path18.join(libDir(root), `${id}.md`);
3901
- fs17.writeFileSync(docFileAbs, content);
4174
+ fs18.writeFileSync(docFileAbs, content);
3902
4175
  const entry = {
3903
4176
  id,
3904
4177
  name,
@@ -3915,13 +4188,13 @@ async function addLibrary(source, opts) {
3915
4188
  }
3916
4189
  function readDoc(root, entry) {
3917
4190
  const abs = path18.resolve(root, entry.docFile);
3918
- return fs17.existsSync(abs) ? fs17.readFileSync(abs, "utf8") : "";
4191
+ return fs18.existsSync(abs) ? fs18.readFileSync(abs, "utf8") : "";
3919
4192
  }
3920
4193
  function npmPackageDir(root, name) {
3921
4194
  let cur = root;
3922
4195
  for (let i = 0; i < 12; i++) {
3923
4196
  const dir = path18.join(cur, "node_modules", name);
3924
- if (fs17.existsSync(path18.join(dir, "package.json"))) return dir;
4197
+ if (fs18.existsSync(path18.join(dir, "package.json"))) return dir;
3925
4198
  const parent = path18.dirname(cur);
3926
4199
  if (parent === cur) break;
3927
4200
  cur = parent;
@@ -3935,7 +4208,7 @@ function localPackageDocs(root, name) {
3935
4208
  for (const f of ["llms.txt", "README.md", "README.mdx", "README", "readme.md"]) {
3936
4209
  const p = path18.join(dir, f);
3937
4210
  try {
3938
- const docs = fs17.readFileSync(p, "utf8");
4211
+ const docs = fs18.readFileSync(p, "utf8");
3939
4212
  if (docs.trim()) {
3940
4213
  return { docs, version, source: path18.relative(root, p).split(path18.sep).join("/") };
3941
4214
  }
@@ -3943,7 +4216,7 @@ function localPackageDocs(root, name) {
3943
4216
  }
3944
4217
  }
3945
4218
  try {
3946
- const pkg = JSON.parse(fs17.readFileSync(path18.join(dir, "package.json"), "utf8"));
4219
+ const pkg = JSON.parse(fs18.readFileSync(path18.join(dir, "package.json"), "utf8"));
3947
4220
  if (pkg.description?.trim()) {
3948
4221
  return {
3949
4222
  docs: `# ${name}
@@ -3959,17 +4232,17 @@ ${pkg.description}`,
3959
4232
  }
3960
4233
  function dtsEntry(dir) {
3961
4234
  try {
3962
- const pkg = JSON.parse(fs17.readFileSync(path18.join(dir, "package.json"), "utf8"));
4235
+ const pkg = JSON.parse(fs18.readFileSync(path18.join(dir, "package.json"), "utf8"));
3963
4236
  const rel2 = pkg.types ?? pkg.typings;
3964
4237
  if (typeof rel2 === "string") {
3965
4238
  const p = path18.join(dir, rel2);
3966
- if (fs17.existsSync(p)) return p;
4239
+ if (fs18.existsSync(p)) return p;
3967
4240
  }
3968
4241
  } catch {
3969
4242
  }
3970
4243
  for (const c of ["index.d.ts", "dist/index.d.ts", "lib/index.d.ts", "types/index.d.ts"]) {
3971
4244
  const p = path18.join(dir, c);
3972
- if (fs17.existsSync(p)) return p;
4245
+ if (fs18.existsSync(p)) return p;
3973
4246
  }
3974
4247
  return void 0;
3975
4248
  }
@@ -4002,7 +4275,7 @@ function localApiSurface(root, name) {
4002
4275
  if (!dts) return void 0;
4003
4276
  let src;
4004
4277
  try {
4005
- src = fs17.readFileSync(dts, "utf8");
4278
+ src = fs18.readFileSync(dts, "utf8");
4006
4279
  } catch {
4007
4280
  return void 0;
4008
4281
  }
@@ -4010,36 +4283,36 @@ function localApiSurface(root, name) {
4010
4283
  return decls.length ? decls.join("\n\n") : void 0;
4011
4284
  }
4012
4285
  function cloneAndReadGit(url) {
4013
- const dir = fs17.mkdtempSync(path18.join(os.tmpdir(), "vg-lib-git-"));
4286
+ const dir = fs18.mkdtempSync(path18.join(os.tmpdir(), "vg-lib-git-"));
4014
4287
  try {
4015
4288
  execFileSync("git", ["clone", "--depth", "1", "--quiet", url, dir], {
4016
4289
  stdio: ["ignore", "ignore", "pipe"]
4017
4290
  });
4018
4291
  } catch (err) {
4019
- fs17.rmSync(dir, { recursive: true, force: true });
4292
+ fs18.rmSync(dir, { recursive: true, force: true });
4020
4293
  const detail = err instanceof Error && err.message ? `: ${err.message.split("\n")[0]}` : "";
4021
4294
  throw new Error(`git clone failed for ${url}${detail}`);
4022
4295
  }
4023
4296
  try {
4024
4297
  return readGitDocs(dir);
4025
4298
  } finally {
4026
- fs17.rmSync(dir, { recursive: true, force: true });
4299
+ fs18.rmSync(dir, { recursive: true, force: true });
4027
4300
  }
4028
4301
  }
4029
4302
  function readGitDocs(dir) {
4030
4303
  const docsDir = path18.join(dir, "docs");
4031
- if (fs17.existsSync(docsDir) && fs17.statSync(docsDir).isDirectory()) return readLocal(docsDir);
4032
- const readme = ["README.md", "README.mdx", "README.txt", "README.rst", "readme.md"].map((f) => path18.join(dir, f)).find((p) => fs17.existsSync(p));
4033
- if (readme) return fs17.readFileSync(readme, "utf8");
4304
+ if (fs18.existsSync(docsDir) && fs18.statSync(docsDir).isDirectory()) return readLocal(docsDir);
4305
+ const readme = ["README.md", "README.mdx", "README.txt", "README.rst", "readme.md"].map((f) => path18.join(dir, f)).find((p) => fs18.existsSync(p));
4306
+ if (readme) return fs18.readFileSync(readme, "utf8");
4034
4307
  return readLocal(dir);
4035
4308
  }
4036
4309
  function readLocal(abs) {
4037
- const stat = fs17.statSync(abs);
4038
- if (stat.isFile()) return fs17.readFileSync(abs, "utf8");
4310
+ const stat = fs18.statSync(abs);
4311
+ if (stat.isFile()) return fs18.readFileSync(abs, "utf8");
4039
4312
  const parts = [];
4040
- const files = fs17.readdirSync(abs, { withFileTypes: true }).filter((e) => e.isFile() && /\.(md|mdx|txt|rst)$/i.test(e.name)).map((e) => e.name).sort();
4313
+ const files = fs18.readdirSync(abs, { withFileTypes: true }).filter((e) => e.isFile() && /\.(md|mdx|txt|rst)$/i.test(e.name)).map((e) => e.name).sort();
4041
4314
  for (const f of files) parts.push(`<!-- ${f} -->
4042
- ${fs17.readFileSync(path18.join(abs, f), "utf8")}`);
4315
+ ${fs18.readFileSync(path18.join(abs, f), "utf8")}`);
4043
4316
  return parts.join("\n\n");
4044
4317
  }
4045
4318
  function inferName(source) {
@@ -4050,7 +4323,7 @@ function inferName(source) {
4050
4323
  function findGitRoot(startDir = process.cwd()) {
4051
4324
  let dir = path18.resolve(startDir);
4052
4325
  for (; ; ) {
4053
- if (fs17.existsSync(path18.join(dir, ".git"))) return dir;
4326
+ if (fs18.existsSync(path18.join(dir, ".git"))) return dir;
4054
4327
  const parent = path18.dirname(dir);
4055
4328
  if (parent === dir) return null;
4056
4329
  dir = parent;
@@ -4067,14 +4340,14 @@ function ensureGitignored(entry, startDir = process.cwd()) {
4067
4340
  let existing = "";
4068
4341
  let fileExisted = true;
4069
4342
  try {
4070
- existing = fs17.readFileSync(gitignorePath, "utf8");
4343
+ existing = fs18.readFileSync(gitignorePath, "utf8");
4071
4344
  } catch {
4072
4345
  fileExisted = false;
4073
4346
  }
4074
4347
  const alreadyPresent = existing.split(/\r?\n/).some((line) => normalizeEntry(line) === target);
4075
4348
  if (alreadyPresent) return { status: "present", entry, gitignorePath };
4076
4349
  const needsLeadingNewline = existing.length > 0 && !existing.endsWith("\n");
4077
- fs17.appendFileSync(gitignorePath, `${needsLeadingNewline ? "\n" : ""}${entry}
4350
+ fs18.appendFileSync(gitignorePath, `${needsLeadingNewline ? "\n" : ""}${entry}
4078
4351
  `, "utf8");
4079
4352
  return { status: fileExisted ? "added" : "created", entry, gitignorePath };
4080
4353
  }
@@ -4250,41 +4523,51 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
4250
4523
  }
4251
4524
  }
4252
4525
  });
4253
- function credentialsDir() {
4254
- return path18.join(os.homedir(), ".vibgrate");
4255
- }
4256
- function credentialsPath() {
4257
- return path18.join(credentialsDir(), "credentials.json");
4258
- }
4259
- function gitignoreEntryForCredentials(repoRoot) {
4260
- const rel2 = path18.relative(repoRoot, credentialsPath());
4526
+ var STORE_DIRNAME = ".vibgrate";
4527
+ var STORE_FILENAME = "credentials.json";
4528
+ function homeCredentialsPath() {
4529
+ return path18.join(os.homedir(), STORE_DIRNAME, STORE_FILENAME);
4530
+ }
4531
+ function projectCredentialsPath(cwd = process.cwd()) {
4532
+ const root = findGitRoot(cwd) ?? cwd;
4533
+ return path18.join(root, STORE_DIRNAME, STORE_FILENAME);
4534
+ }
4535
+ function credentialsPath(opts = {}) {
4536
+ const override = process.env.VIBGRATE_CREDENTIALS?.trim();
4537
+ if (override) return path18.resolve(override);
4538
+ const local = projectCredentialsPath(opts.cwd);
4539
+ if (opts.local) return local;
4540
+ if (fs18.existsSync(local)) return local;
4541
+ return homeCredentialsPath();
4542
+ }
4543
+ function gitignoreEntryForCredentials(repoRoot, credsFile = credentialsPath()) {
4544
+ const rel2 = path18.relative(repoRoot, credsFile);
4261
4545
  if (rel2 && !rel2.startsWith("..") && !path18.isAbsolute(rel2)) {
4262
4546
  return rel2.split(path18.sep).join("/");
4263
4547
  }
4264
- return ".vibgrate/credentials.json";
4548
+ return `${STORE_DIRNAME}/${STORE_FILENAME}`;
4265
4549
  }
4266
- function readStoredCredentials() {
4550
+ function readStoredCredentials(opts = {}) {
4267
4551
  try {
4268
- const raw = fs17.readFileSync(credentialsPath(), "utf8");
4552
+ const raw = fs18.readFileSync(credentialsPath(opts), "utf8");
4269
4553
  const parsed = JSON.parse(raw);
4270
4554
  return parsed && typeof parsed.dsn === "string" && parsed.dsn ? parsed : null;
4271
4555
  } catch {
4272
4556
  return null;
4273
4557
  }
4274
4558
  }
4275
- function writeStoredCredentials(creds) {
4276
- const dir = credentialsDir();
4277
- fs17.mkdirSync(dir, { recursive: true });
4278
- const file = credentialsPath();
4279
- fs17.writeFileSync(file, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf8", mode: 384 });
4559
+ function writeStoredCredentials(creds, opts = {}) {
4560
+ const file = credentialsPath(opts);
4561
+ fs18.mkdirSync(path18.dirname(file), { recursive: true });
4562
+ fs18.writeFileSync(file, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf8", mode: 384 });
4280
4563
  try {
4281
- fs17.chmodSync(file, 384);
4564
+ fs18.chmodSync(file, 384);
4282
4565
  } catch {
4283
4566
  }
4284
4567
  }
4285
- function clearStoredCredentials() {
4568
+ function clearStoredCredentials(opts = {}) {
4286
4569
  try {
4287
- fs17.rmSync(credentialsPath());
4570
+ fs18.rmSync(credentialsPath(opts));
4288
4571
  return true;
4289
4572
  } catch {
4290
4573
  return false;
@@ -4673,7 +4956,7 @@ var pushCommand = new Command("push").description("Push scan results to Vibgrate
4673
4956
  function readScanArtifact(root) {
4674
4957
  try {
4675
4958
  const file = path18.join(root, ".vibgrate", "scan_result.json");
4676
- return JSON.parse(fs17.readFileSync(file, "utf8"));
4959
+ return JSON.parse(fs18.readFileSync(file, "utf8"));
4677
4960
  } catch {
4678
4961
  return null;
4679
4962
  }
@@ -5648,13 +5931,13 @@ function ledgerPath(root) {
5648
5931
  return path18.join(cacheDir(root), LEDGER);
5649
5932
  }
5650
5933
  function savingsRecorded(root) {
5651
- return fs17.existsSync(ledgerPath(root));
5934
+ return fs18.existsSync(ledgerPath(root));
5652
5935
  }
5653
5936
  function recordSaving(root, entry, now) {
5654
5937
  try {
5655
- fs17.mkdirSync(cacheDir(root), { recursive: true });
5938
+ fs18.mkdirSync(cacheDir(root), { recursive: true });
5656
5939
  const line = JSON.stringify({ ts: now, ...entry });
5657
- fs17.appendFileSync(ledgerPath(root), line + "\n");
5940
+ fs18.appendFileSync(ledgerPath(root), line + "\n");
5658
5941
  } catch {
5659
5942
  }
5660
5943
  }
@@ -5666,8 +5949,8 @@ function readSavings(root, days, now, ratePerM = DEFAULT_RATE_PER_M) {
5666
5949
  let queries = 0;
5667
5950
  let vgTokens = 0;
5668
5951
  let baselineTokens = 0;
5669
- if (fs17.existsSync(file)) {
5670
- for (const line of fs17.readFileSync(file, "utf8").split("\n")) {
5952
+ if (fs18.existsSync(file)) {
5953
+ for (const line of fs18.readFileSync(file, "utf8").split("\n")) {
5671
5954
  if (!line.trim()) continue;
5672
5955
  try {
5673
5956
  const e = JSON.parse(line);
@@ -5723,9 +6006,9 @@ var GraphSource = class {
5723
6006
  /** Current graph: auto-refreshed if the tree drifted, reloaded if the file changed. */
5724
6007
  async get() {
5725
6008
  if (this.refresh) await this.maybeRefresh();
5726
- const stat = fs17.statSync(this.graphPath);
6009
+ const stat = fs18.statSync(this.graphPath);
5727
6010
  if (stat.mtimeMs !== this.cachedMtimeMs || !this.cached) {
5728
- this.cached = parseGraph(fs17.readFileSync(this.graphPath, "utf8"));
6011
+ this.cached = parseGraph(fs18.readFileSync(this.graphPath, "utf8"));
5729
6012
  this.cachedMtimeMs = stat.mtimeMs;
5730
6013
  }
5731
6014
  return this.cached;
@@ -5806,8 +6089,8 @@ async function serveStdio(graphPath, opts = {}) {
5806
6089
  await server.connect(new StdioServerTransport());
5807
6090
  }
5808
6091
  function sleep(ms) {
5809
- return new Promise((resolve11) => {
5810
- const timer = setTimeout(resolve11, ms);
6092
+ return new Promise((resolve12) => {
6093
+ const timer = setTimeout(resolve12, ms);
5811
6094
  timer.unref?.();
5812
6095
  });
5813
6096
  }
@@ -5960,9 +6243,9 @@ function assistantById(id) {
5960
6243
  }
5961
6244
  function detectServeLaunch(which = whichOnPath) {
5962
6245
  const vg = which("vg");
5963
- if (vg && isOwnBinary(vg)) return { command: "vg", args: ["serve"] };
6246
+ if (vg && isInstalledOwnBinary(vg)) return { command: "vg", args: ["serve"] };
5964
6247
  const vibgrate = which("vibgrate");
5965
- if (vibgrate && isOwnBinary(vibgrate)) {
6248
+ if (vibgrate && isInstalledOwnBinary(vibgrate)) {
5966
6249
  return {
5967
6250
  command: "vibgrate",
5968
6251
  args: ["serve"],
@@ -5975,27 +6258,6 @@ function detectServeLaunch(which = whichOnPath) {
5975
6258
  note: "vg is not installed on PATH \u2014 registered an npx launcher. Install globally (`npm i -g @vibgrate/cli`) and rerun `vg install` for a faster startup."
5976
6259
  };
5977
6260
  }
5978
- function whichOnPath(cmd) {
5979
- try {
5980
- const out = execFileSync(process.platform === "win32" ? "where" : "which", [cmd], {
5981
- encoding: "utf8",
5982
- stdio: ["ignore", "pipe", "ignore"]
5983
- }).trim().split(/\r?\n/)[0];
5984
- return out || null;
5985
- } catch {
5986
- return null;
5987
- }
5988
- }
5989
- function isOwnBinary(binPath) {
5990
- try {
5991
- const real = fs17.realpathSync(binPath);
5992
- if (/[\\/]@vibgrate[\\/]cli[\\/]/.test(real)) return true;
5993
- const head = fs17.readFileSync(real, { encoding: "utf8" }).slice(0, 2048);
5994
- return head.includes("@vibgrate/cli") || head.includes("vibgrate");
5995
- } catch {
5996
- return false;
5997
- }
5998
- }
5999
6261
  function installAssistant(a, opts) {
6000
6262
  const wrote = [];
6001
6263
  const skipped = [];
@@ -6030,23 +6292,23 @@ function uninstallAssistant(a, root, purge) {
6030
6292
  const file = path18.join(root, a.nudge.file);
6031
6293
  if (a.nudge.kind === "block") {
6032
6294
  if (removeBlock(file)) removed.push(a.nudge.file);
6033
- } else if (fs17.existsSync(file)) {
6034
- fs17.rmSync(file);
6295
+ } else if (fs18.existsSync(file)) {
6296
+ fs18.rmSync(file);
6035
6297
  removed.push(a.nudge.file);
6036
6298
  }
6037
6299
  }
6038
6300
  if (purge && a.skill) {
6039
6301
  const file = path18.join(root, a.skill);
6040
- if (fs17.existsSync(file)) {
6041
- fs17.rmSync(file);
6302
+ if (fs18.existsSync(file)) {
6303
+ fs18.rmSync(file);
6042
6304
  removed.push(a.skill);
6043
6305
  }
6044
6306
  }
6045
6307
  return removed;
6046
6308
  }
6047
6309
  function writeFileEnsured(file, content) {
6048
- fs17.mkdirSync(path18.dirname(file), { recursive: true });
6049
- fs17.writeFileSync(file, content);
6310
+ fs18.mkdirSync(path18.dirname(file), { recursive: true });
6311
+ fs18.writeFileSync(file, content);
6050
6312
  }
6051
6313
  function upsertMcp(file, target, launch) {
6052
6314
  const config = readJson(file);
@@ -6059,7 +6321,7 @@ function upsertMcp(file, target, launch) {
6059
6321
  `);
6060
6322
  }
6061
6323
  function removeMcp(file, target) {
6062
- if (!fs17.existsSync(file)) return false;
6324
+ if (!fs18.existsSync(file)) return false;
6063
6325
  const config = readJson(file);
6064
6326
  const bag = config[target.key];
6065
6327
  if (!bag || !(bag.vg !== void 0)) return false;
@@ -6082,8 +6344,8 @@ ${stripMarkers(nudgeMarkdown(smallRepo))}` : stripMarkers(nudgeMarkdown(smallRep
6082
6344
  upsertBlock(file, nudgeMarkdown(smallRepo));
6083
6345
  }
6084
6346
  function upsertBlock(file, block) {
6085
- fs17.mkdirSync(path18.dirname(file), { recursive: true });
6086
- let existing = fs17.existsSync(file) ? fs17.readFileSync(file, "utf8") : "";
6347
+ fs18.mkdirSync(path18.dirname(file), { recursive: true });
6348
+ let existing = fs18.existsSync(file) ? fs18.readFileSync(file, "utf8") : "";
6087
6349
  const re = new RegExp(`${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}`);
6088
6350
  if (re.test(existing)) existing = existing.replace(re, block);
6089
6351
  else existing = existing.length ? `${existing.replace(/\s*$/, "")}
@@ -6091,20 +6353,20 @@ function upsertBlock(file, block) {
6091
6353
  ${block}
6092
6354
  ` : `${block}
6093
6355
  `;
6094
- fs17.writeFileSync(file, existing);
6356
+ fs18.writeFileSync(file, existing);
6095
6357
  }
6096
6358
  function removeBlock(file) {
6097
- if (!fs17.existsSync(file)) return false;
6098
- const existing = fs17.readFileSync(file, "utf8");
6359
+ if (!fs18.existsSync(file)) return false;
6360
+ const existing = fs18.readFileSync(file, "utf8");
6099
6361
  const re = new RegExp(`\\n*${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}\\n*`);
6100
6362
  if (!re.test(existing)) return false;
6101
- fs17.writeFileSync(file, existing.replace(re, "\n"));
6363
+ fs18.writeFileSync(file, existing.replace(re, "\n"));
6102
6364
  return true;
6103
6365
  }
6104
6366
  function readJson(file) {
6105
- if (!fs17.existsSync(file)) return {};
6367
+ if (!fs18.existsSync(file)) return {};
6106
6368
  try {
6107
- const parsed = JSON.parse(fs17.readFileSync(file, "utf8"));
6369
+ const parsed = JSON.parse(fs18.readFileSync(file, "utf8"));
6108
6370
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
6109
6371
  return parsed;
6110
6372
  }
@@ -6262,6 +6524,6 @@ function spdx(ctx) {
6262
6524
  return JSON.stringify(doc, null, 2) + "\n";
6263
6525
  }
6264
6526
 
6265
- export { ASSISTANTS, CliError, ECOSYSTEMS, ExitCode, FREE_PACK, GraphIndex, GraphSource, SCHEMA_VERSION, SKIP_DIRS, TOOLS, UsageError, addLibrary, analyze, applyCoverage, applyStaticTestLinkage, assessDocQuality, assistantById, availableRegionIds, buildFacts, buildGraph, buildModuleResolver, cacheDir, catalogPath, clearModelCache, clearStoredCredentials, cosine, countPending, coveringTests, createServer, createWorkspaceDsn, credentialsPath, dashHostForIngestHost, decodeScipIndex, defaultGraphPath, detectRunner, detectServeLaunch, discover, discoverModels, driftCount, driftFor, dsnCommand, embeddingsCached, enrichOnline, ensureGitignored, exportGraph, fetchHostedDocs, findGitRoot, findNodes, formatForExt, getNodeEmbeddings, gitignoreEntryForCredentials, groundGraph, hasDrift, identifierParts, impactOf, installAssistant, inventory, isModelReady, isTestFile, libDir, libId, loadCatalog, loadCoverage, loadEmbedder, loadGraph, loadSnapshot, localApiSurface, localPackageDocs, modelCacheInfo, nodeById, nodeEmbedText, parseDsn, parseGraph, parseJsonc, probeFreshness, publishPrivateLibrary, pushCommand, queryGraph, queryGraphSemantic, readDoc, readSavings, readScanArtifact, recordSaving, refreshIfStale, relativeResolver, renderHtml, renderReport, resolveDsn, resolveEmbedModel, resolveIngestHost, resolveLib, resolveOne, resolveVersion, saveCatalog, savingsRecorded, scipEdges, selectForBudget, serializeGraph, serveStdio, shortestPath, stableStringify, symbolsFromApi, testsToRun, unavailableMessage, uninstallAssistant, uploadScanArtifact, usageError, verifyDeterminism, vibgrateDir, writeArtifacts, writeSnapshot, writeStoredCredentials };
6266
- //# sourceMappingURL=chunk-W2QP2YH6.js.map
6267
- //# sourceMappingURL=chunk-W2QP2YH6.js.map
6527
+ export { ASSISTANTS, CliError, ECOSYSTEMS, ExitCode, FREE_PACK, GraphIndex, GraphSource, NPX_INVOCATION, ResourceLimitError, SCHEMA_VERSION, SKIP_DIRS, SKIP_FILES, TOOLS, UsageError, addLibrary, analyze, applyCoverage, applyStaticTestLinkage, assessDocQuality, assistantById, availableRegionIds, buildFacts, buildGraph, buildModuleResolver, cacheDir, catalogPath, clearModelCache, clearStoredCredentials, cosine, countPending, coveringTests, createServer, createWorkspaceDsn, credentialsPath, dashHostForIngestHost, decodeScipIndex, defaultGraphPath, detectRunner, detectServeLaunch, discover, discoverModels, driftCount, driftFor, dsnCommand, embeddingsCached, enrichOnline, ensureGitignored, exportGraph, fetchHostedDocs, findGitRoot, findNodes, formatForExt, getNodeEmbeddings, gitignoreEntryForCredentials, groundGraph, hasDrift, identifierParts, impactOf, installAssistant, inventory, isModelReady, isTestFile, libDir, libId, loadCatalog, loadCoverage, loadEmbedder, loadGraph, loadSnapshot, localApiSurface, localPackageDocs, modelCacheInfo, nodeById, nodeEmbedText, parseDsn, parseGraph, parseJsonc, probeFreshness, publishPrivateLibrary, pushCommand, queryGraph, queryGraphSemantic, readDoc, readSavings, readScanArtifact, recordSaving, refreshIfStale, relativeResolver, renderHtml, renderReport, resolveCliInvocation, resolveDsn, resolveEmbedModel, resolveIngestHost, resolveLib, resolveLimits, resolveOne, resolveVersion, saveCatalog, savingsRecorded, scipEdges, selectForBudget, serializeGraph, serveStdio, shortestPath, stableStringify, symbolsFromApi, testsToRun, unavailableMessage, uninstallAssistant, uploadScanArtifact, usageError, verifyDeterminism, vibgrateDir, writeArtifacts, writeSnapshot, writeStoredCredentials };
6528
+ //# sourceMappingURL=chunk-YODVLV37.js.map
6529
+ //# sourceMappingURL=chunk-YODVLV37.js.map