@vibgrate/cli 2026.722.1 → 2026.727.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.
Files changed (125) hide show
  1. package/DOCS.md +418 -196
  2. package/README.md +34 -7
  3. package/dist/baseline-K7YBFKZI.js +7 -0
  4. package/dist/{baseline-J44M7MWP.js.map → baseline-K7YBFKZI.js.map} +1 -1
  5. package/dist/chunk-3BVSDMHV.js +2496 -0
  6. package/dist/chunk-3BVSDMHV.js.map +1 -0
  7. package/dist/{chunk-PY3DNX5H.js → chunk-3Q7QBWFW.js} +13 -2
  8. package/dist/chunk-3Q7QBWFW.js.map +1 -0
  9. package/dist/chunk-4CQ3MGNN.js +136 -0
  10. package/dist/chunk-4CQ3MGNN.js.map +1 -0
  11. package/dist/chunk-5I4VSBWQ.js +764 -0
  12. package/dist/chunk-5I4VSBWQ.js.map +1 -0
  13. package/dist/chunk-5Q54GEHM.js +29 -0
  14. package/dist/chunk-5Q54GEHM.js.map +1 -0
  15. package/dist/{chunk-XKXKWYJF.js → chunk-65MEOA4E.js} +21 -18
  16. package/dist/chunk-65MEOA4E.js.map +1 -0
  17. package/dist/{chunk-WNIIKCNF.js → chunk-6IOQJGFT.js} +29 -238
  18. package/dist/chunk-6IOQJGFT.js.map +1 -0
  19. package/dist/{chunk-CS37OBE3.js → chunk-7JHPQ6WF.js} +791 -783
  20. package/dist/chunk-7JHPQ6WF.js.map +1 -0
  21. package/dist/chunk-BPF3YX2U.js +64 -0
  22. package/dist/chunk-BPF3YX2U.js.map +1 -0
  23. package/dist/chunk-BWPBB45M.js +88 -0
  24. package/dist/chunk-BWPBB45M.js.map +1 -0
  25. package/dist/chunk-C4CKTAZP.js +1695 -0
  26. package/dist/chunk-C4CKTAZP.js.map +1 -0
  27. package/dist/chunk-EDAAG7BL.js +612 -0
  28. package/dist/chunk-EDAAG7BL.js.map +1 -0
  29. package/dist/chunk-EDBV33B7.js +57 -0
  30. package/dist/chunk-EDBV33B7.js.map +1 -0
  31. package/dist/chunk-HABUT7HW.js +233 -0
  32. package/dist/chunk-HABUT7HW.js.map +1 -0
  33. package/dist/{chunk-EXQV26MF.js → chunk-HYZVHJ3Q.js} +3 -3
  34. package/dist/{chunk-EXQV26MF.js.map → chunk-HYZVHJ3Q.js.map} +1 -1
  35. package/dist/chunk-IS7VTH2Q.js +627 -0
  36. package/dist/chunk-IS7VTH2Q.js.map +1 -0
  37. package/dist/chunk-IZDH67DX.js +289 -0
  38. package/dist/chunk-IZDH67DX.js.map +1 -0
  39. package/dist/chunk-J7LKEB2R.js +51 -0
  40. package/dist/chunk-J7LKEB2R.js.map +1 -0
  41. package/dist/chunk-JIBOJTGS.js +313 -0
  42. package/dist/chunk-JIBOJTGS.js.map +1 -0
  43. package/dist/chunk-JZYGEZTU.js +82 -0
  44. package/dist/chunk-JZYGEZTU.js.map +1 -0
  45. package/dist/chunk-K7REHDQS.js +776 -0
  46. package/dist/chunk-K7REHDQS.js.map +1 -0
  47. package/dist/chunk-KHJO4D5N.js +108 -0
  48. package/dist/chunk-KHJO4D5N.js.map +1 -0
  49. package/dist/chunk-NDMDSPFX.js +69 -0
  50. package/dist/chunk-NDMDSPFX.js.map +1 -0
  51. package/dist/chunk-OOIG3RUM.js +276 -0
  52. package/dist/chunk-OOIG3RUM.js.map +1 -0
  53. package/dist/{chunk-6CXTPC74.js → chunk-OXQNQW5J.js} +27 -6
  54. package/dist/chunk-OXQNQW5J.js.map +1 -0
  55. package/dist/chunk-PMYWZRRS.js +62 -0
  56. package/dist/chunk-PMYWZRRS.js.map +1 -0
  57. package/dist/chunk-PXHNTC6D.js +382 -0
  58. package/dist/chunk-PXHNTC6D.js.map +1 -0
  59. package/dist/{chunk-VXT6LUGP.js → chunk-S6CYDZEW.js} +1395 -1975
  60. package/dist/chunk-S6CYDZEW.js.map +1 -0
  61. package/dist/chunk-SZLF66FJ.js +843 -0
  62. package/dist/chunk-SZLF66FJ.js.map +1 -0
  63. package/dist/chunk-VXGV6NTD.js +476 -0
  64. package/dist/chunk-VXGV6NTD.js.map +1 -0
  65. package/dist/{chunk-JSNHZCIV.js → chunk-WLETRGAC.js} +16 -9
  66. package/dist/chunk-WLETRGAC.js.map +1 -0
  67. package/dist/{chunk-2EVQKZTO.js → chunk-YDAT7LD2.js} +190 -13
  68. package/dist/chunk-YDAT7LD2.js.map +1 -0
  69. package/dist/cli.js +3451 -509
  70. package/dist/cli.js.map +1 -1
  71. package/dist/execution-env-3BN7Z737.js +3 -0
  72. package/dist/execution-env-3BN7Z737.js.map +1 -0
  73. package/dist/federation-EPZDUWCK.js +6 -0
  74. package/dist/federation-EPZDUWCK.js.map +1 -0
  75. package/dist/git-ref-HPRLUFFW.js +3 -0
  76. package/dist/git-ref-HPRLUFFW.js.map +1 -0
  77. package/dist/graph-backend-ZMSYOZMW.js +10 -0
  78. package/dist/graph-backend-ZMSYOZMW.js.map +1 -0
  79. package/dist/index.d.ts +432 -126
  80. package/dist/index.js +14 -7
  81. package/dist/index.js.map +1 -1
  82. package/dist/{interactive-736PBC7J.js → interactive-ICL7D7LO.js} +185 -365
  83. package/dist/interactive-ICL7D7LO.js.map +1 -0
  84. package/dist/llm-host-2RAYQSDQ.js +8 -0
  85. package/dist/llm-host-2RAYQSDQ.js.map +1 -0
  86. package/dist/load-OHVKDX4R.js +7 -0
  87. package/dist/load-OHVKDX4R.js.map +1 -0
  88. package/dist/local-runtime-OJFLX2HI.js +4 -0
  89. package/dist/local-runtime-OJFLX2HI.js.map +1 -0
  90. package/dist/model-execution-profile-MQ2QCP24.js +3 -0
  91. package/dist/model-execution-profile-MQ2QCP24.js.map +1 -0
  92. package/dist/model-orchestrator-FFTZI3U2.js +8 -0
  93. package/dist/model-orchestrator-FFTZI3U2.js.map +1 -0
  94. package/dist/parse-worker.d.ts +1 -1
  95. package/dist/parse-worker.js +1 -1
  96. package/dist/paths-MCH75O3G.js +5 -0
  97. package/dist/paths-MCH75O3G.js.map +1 -0
  98. package/dist/resolve-gguf-4UPIGPEE.js +7 -0
  99. package/dist/resolve-gguf-4UPIGPEE.js.map +1 -0
  100. package/dist/runtime-session-IDU5BDUJ.js +17 -0
  101. package/dist/runtime-session-IDU5BDUJ.js.map +1 -0
  102. package/dist/session-L2OSDRPI.js +10 -0
  103. package/dist/{session-POJUDFPH.js.map → session-L2OSDRPI.js.map} +1 -1
  104. package/dist/{stream-json-Q2JGWPHB.js → stream-json-F74W5FCK.js} +16 -6
  105. package/dist/stream-json-F74W5FCK.js.map +1 -0
  106. package/dist/{types-Jl6RJ175.d.ts → types-ZMg1mSuu.d.ts} +3 -1
  107. package/dist/vgd-EGBQD7BW.js +16 -0
  108. package/dist/vgd-EGBQD7BW.js.map +1 -0
  109. package/package.json +9 -6
  110. package/dist/baseline-J44M7MWP.js +0 -7
  111. package/dist/chunk-2EVQKZTO.js.map +0 -1
  112. package/dist/chunk-6CXTPC74.js.map +0 -1
  113. package/dist/chunk-CS37OBE3.js.map +0 -1
  114. package/dist/chunk-GGJZA3Q6.js +0 -961
  115. package/dist/chunk-GGJZA3Q6.js.map +0 -1
  116. package/dist/chunk-JBXNQCGE.js +0 -484
  117. package/dist/chunk-JBXNQCGE.js.map +0 -1
  118. package/dist/chunk-JSNHZCIV.js.map +0 -1
  119. package/dist/chunk-PY3DNX5H.js.map +0 -1
  120. package/dist/chunk-VXT6LUGP.js.map +0 -1
  121. package/dist/chunk-WNIIKCNF.js.map +0 -1
  122. package/dist/chunk-XKXKWYJF.js.map +0 -1
  123. package/dist/interactive-736PBC7J.js.map +0 -1
  124. package/dist/session-POJUDFPH.js +0 -6
  125. package/dist/stream-json-Q2JGWPHB.js.map +0 -1
@@ -1,12 +1,17 @@
1
- import { resolveOne, resolveVersion, resolveLib, loadCatalog, driftFor, localPackageDocs, readDoc, findNodes, inventory, localApiSurface } from './chunk-GGJZA3Q6.js';
2
- import { loadConfig, computeUpgradeImpact, getChangelogSignals, gitHistoryAvailable, buildVersionTimelines, findPackageTimeline, severityRank, projectTypeToVulnEcosystem, VERSION } from './chunk-JSNHZCIV.js';
3
- import { langById, grammarSetVersion, langForExtension, setGrammarsOverride, parseSource } from './chunk-PY3DNX5H.js';
4
- import { writeTextFile, pathExists, readJsonFile } from './chunk-CRAMOEBE.js';
5
- import { impactOf, indexFor, withTimeout, getNodeEmbeddings, queryGraphSemantic, queryGraph, loadEmbedder, isModelReady, cacheDir, loadCache, stableStringify, serializeGraph, parseGraph, acquireLock, releaseLock } from './chunk-WNIIKCNF.js';
1
+ import { loadConfig, computeUpgradeImpact, getChangelogSignals, gitHistoryAvailable, buildVersionTimelines, findPackageTimeline, severityRank, projectTypeToVulnEcosystem, VERSION } from './chunk-WLETRGAC.js';
2
+ import { resolveVersion, resolveLib, loadCatalog, driftFor, localPackageDocs, readDoc, inventory, localApiSurface, edgeId, buildModuleResolver, resolve, applyStaticTestLinkage, nodeId } from './chunk-C4CKTAZP.js';
3
+ import { resolveOne, findNodes } from './chunk-J7LKEB2R.js';
4
+ import { discoverModels } from './chunk-OOIG3RUM.js';
5
+ import { impactOf, indexFor, withTimeout, getNodeEmbeddings, queryGraphSemantic, queryGraph, loadEmbedder, isModelReady, cacheDir, loadCache, acquireLock, releaseLock } from './chunk-6IOQJGFT.js';
6
+ import { langById, grammarSetVersion, langForExtension, setGrammarsOverride, parseSource } from './chunk-3Q7QBWFW.js';
7
+ import { redactSecrets } from './chunk-RXP66R2E.js';
8
+ import { loadGraph, ensureVibgrateGitignore, vibgrateDir, writeArtifacts } from './chunk-HABUT7HW.js';
9
+ import { serializeGraph, stableStringify } from './chunk-5Q54GEHM.js';
6
10
  import { shortId, canonicalize, hashString, hashBytes } from './chunk-JFGYT6BI.js';
7
- import * as fs18 from 'fs';
11
+ import { writeTextFile, pathExists, readJsonFile } from './chunk-CRAMOEBE.js';
12
+ import * as fs15 from 'fs';
8
13
  import { spawnSync, execFileSync } from 'child_process';
9
- import * as path2 from 'path';
14
+ import * as path17 from 'path';
10
15
  import ignore from 'ignore';
11
16
  import * as v8 from 'v8';
12
17
  import Graph from 'graphology';
@@ -24,8 +29,8 @@ import { Command } from 'commander';
24
29
  import chalk from 'chalk';
25
30
  import * as zlib from 'zlib';
26
31
  import { promisify } from 'util';
27
- import { encode, decode } from 'gpt-tokenizer/encoding/cl100k_base';
28
32
  import { Worker } from 'worker_threads';
33
+ import { encode, decode } from 'gpt-tokenizer/encoding/cl100k_base';
29
34
 
30
35
  var NPX_INVOCATION = "npx @vibgrate/cli";
31
36
  function whichOnPath(cmd) {
@@ -41,9 +46,9 @@ function whichOnPath(cmd) {
41
46
  }
42
47
  function isOwnBinary(binPath) {
43
48
  try {
44
- const real = fs18.realpathSync(binPath);
49
+ const real = fs15.realpathSync(binPath);
45
50
  if (/[\\/]@vibgrate[\\/]cli[\\/]/.test(real)) return true;
46
- const head = fs18.readFileSync(real, { encoding: "utf8" }).slice(0, 2048);
51
+ const head = fs15.readFileSync(real, { encoding: "utf8" }).slice(0, 2048);
47
52
  return head.includes("@vibgrate/cli") || head.includes("vibgrate");
48
53
  } catch {
49
54
  return false;
@@ -53,7 +58,7 @@ function isEphemeralNpxBinary(binPath) {
53
58
  const NPX_SEGMENT = /[\\/]_npx[\\/]/;
54
59
  if (NPX_SEGMENT.test(binPath)) return true;
55
60
  try {
56
- return NPX_SEGMENT.test(fs18.realpathSync(binPath));
61
+ return NPX_SEGMENT.test(fs15.realpathSync(binPath));
57
62
  } catch {
58
63
  return false;
59
64
  }
@@ -88,6 +93,14 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
88
93
  ".wrangler",
89
94
  ".turbo",
90
95
  ".cache",
96
+ // Migration / tool backup trees (NativeScript, codemods, agent worktrees)
97
+ ".migration_backup",
98
+ ".update_backup",
99
+ ".backup",
100
+ ".bak",
101
+ ".history",
102
+ ".snapshots",
103
+ ".snapshots_bak",
91
104
  // JavaScript / Node — installed dependency trees
92
105
  "node_modules",
93
106
  "bower_components",
@@ -112,6 +125,9 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
112
125
  "build",
113
126
  "out",
114
127
  "coverage",
128
+ // Native mobile platform build trees (NativeScript / Cordova / Capacitor)
129
+ "platforms",
130
+ "App_Resources",
115
131
  // Vendored third-party source trees
116
132
  "vendor",
117
133
  "Pods",
@@ -219,19 +235,19 @@ var SKIP_FILES = /* @__PURE__ */ new Set([
219
235
  "flake.lock"
220
236
  ]);
221
237
  function toPosix(p) {
222
- return p.split(path2.sep).join("/");
238
+ return p.split(path17.sep).join("/");
223
239
  }
224
240
  function buildRootIgnore(root, exclude) {
225
241
  const ig = ignore();
226
- const gitignorePath = path2.join(root, ".gitignore");
227
- if (fs18.existsSync(gitignorePath)) {
228
- ig.add(fs18.readFileSync(gitignorePath, "utf8"));
242
+ const gitignorePath = path17.join(root, ".gitignore");
243
+ if (fs15.existsSync(gitignorePath)) {
244
+ ig.add(fs15.readFileSync(gitignorePath, "utf8"));
229
245
  }
230
246
  if (exclude.length) ig.add(exclude);
231
247
  return ig;
232
248
  }
233
249
  function discover(options) {
234
- const root = path2.resolve(options.root);
250
+ const root = path17.resolve(options.root);
235
251
  const onlyLangs = (options.only ?? []).filter(Boolean);
236
252
  const allowLang = (lang) => onlyLangs.length === 0 || onlyLangs.includes(lang.id);
237
253
  for (const id of onlyLangs) {
@@ -240,39 +256,39 @@ function discover(options) {
240
256
  }
241
257
  }
242
258
  const rootIg = buildRootIgnore(root, options.exclude ?? []);
243
- const scopeAbs = (options.paths && options.paths.length ? options.paths.map((p) => path2.resolve(root, p)) : [root]).filter((p) => fs18.existsSync(p));
259
+ const scopeAbs = (options.paths && options.paths.length ? options.paths.map((p) => path17.resolve(root, p)) : [root]).filter((p) => fs15.existsSync(p));
244
260
  const found = /* @__PURE__ */ new Map();
245
261
  const considerFile = (abs) => {
246
- const rel2 = toPosix(path2.relative(root, abs));
262
+ const rel2 = toPosix(path17.relative(root, abs));
247
263
  if (rel2.startsWith("..")) return;
248
264
  if (rel2 === "" || rootIg.ignores(rel2)) return;
249
- if (SKIP_FILES.has(path2.basename(abs).toLowerCase())) return;
250
- const lang = langForExtension(path2.extname(abs));
265
+ if (SKIP_FILES.has(path17.basename(abs).toLowerCase())) return;
266
+ const lang = langForExtension(path17.extname(abs));
251
267
  if (!lang || !allowLang(lang)) return;
252
268
  found.set(rel2, { rel: rel2, abs, lang });
253
269
  };
254
- const walk2 = (dir) => {
270
+ const walk = (dir) => {
255
271
  let entries;
256
272
  try {
257
- entries = fs18.readdirSync(dir, { withFileTypes: true });
273
+ entries = fs15.readdirSync(dir, { withFileTypes: true });
258
274
  } catch {
259
275
  return;
260
276
  }
261
277
  for (const entry of entries) {
262
- const abs = path2.join(dir, entry.name);
263
- const rel2 = toPosix(path2.relative(root, abs));
278
+ const abs = path17.join(dir, entry.name);
279
+ const rel2 = toPosix(path17.relative(root, abs));
264
280
  if (entry.isDirectory()) {
265
281
  if (SKIP_DIRS.has(entry.name)) continue;
266
282
  if (rel2 && rootIg.ignores(`${rel2}/`)) continue;
267
- walk2(abs);
283
+ walk(abs);
268
284
  } else if (entry.isFile()) {
269
285
  considerFile(abs);
270
286
  }
271
287
  }
272
288
  };
273
289
  for (const scope of scopeAbs) {
274
- const stat = fs18.statSync(scope);
275
- if (stat.isDirectory()) walk2(scope);
290
+ const stat = fs15.statSync(scope);
291
+ if (stat.isDirectory()) walk(scope);
276
292
  else if (stat.isFile()) considerFile(scope);
277
293
  }
278
294
  return [...found.values()].sort((a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0);
@@ -336,458 +352,6 @@ function formatBytes(n) {
336
352
  if (n >= 1024) return `${(n / 1024).toFixed(1)} KiB`;
337
353
  return `${n} B`;
338
354
  }
339
- var JS_TS_EXT = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".vue", ".svelte", ".astro"];
340
- var OTHER_EXT = [
341
- ".py",
342
- ".pyi",
343
- ".go",
344
- ".rs",
345
- ".rb",
346
- ".java",
347
- ".cs",
348
- ".php",
349
- ".kt",
350
- ".kts",
351
- ".swift",
352
- ".scala",
353
- ".sc",
354
- ".dart",
355
- ".lua",
356
- ".ex",
357
- ".exs",
358
- ".sh",
359
- ".bash",
360
- ".zig",
361
- ".c",
362
- ".cpp",
363
- ".cc",
364
- ".cxx",
365
- ".hpp",
366
- ".hh",
367
- ".hxx",
368
- ".h",
369
- ".m",
370
- ".mm",
371
- ".ml",
372
- ".mli",
373
- ".res",
374
- ".sol"
375
- ];
376
- var EXT_GROUPS = {
377
- ".py": [".py", ".pyi"],
378
- ".pyi": [".py", ".pyi"],
379
- ".java": [".java"],
380
- ".cs": [".cs"],
381
- ".go": [".go"],
382
- ".rs": [".rs"],
383
- ".rb": [".rb"]
384
- };
385
- for (const e of JS_TS_EXT) EXT_GROUPS[e] = JS_TS_EXT;
386
- var CPP_EXT = [".cpp", ".cc", ".cxx", ".hpp", ".hh", ".hxx", ".h"];
387
- Object.assign(EXT_GROUPS, {
388
- ".php": [".php"],
389
- ".kt": [".kt", ".kts"],
390
- ".kts": [".kt", ".kts"],
391
- ".swift": [".swift"],
392
- ".scala": [".scala", ".sc"],
393
- ".sc": [".scala", ".sc"],
394
- ".dart": [".dart"],
395
- ".lua": [".lua"],
396
- ".ex": [".ex", ".exs"],
397
- ".exs": [".ex", ".exs"],
398
- ".sh": [".sh", ".bash"],
399
- ".bash": [".sh", ".bash"],
400
- ".zig": [".zig"],
401
- ".c": [".c", ".h"],
402
- ".cpp": CPP_EXT,
403
- ".cc": CPP_EXT,
404
- ".cxx": CPP_EXT,
405
- ".hpp": CPP_EXT,
406
- ".hh": CPP_EXT,
407
- ".hxx": CPP_EXT,
408
- ".h": [...CPP_EXT, ".c"],
409
- // ObjC #imports headers; ObjC++ additionally reaches C++ headers.
410
- ".m": [".h", ".m", ".mm"],
411
- ".mm": [".h", ".m", ".mm", ...CPP_EXT],
412
- ".ml": [".ml", ".mli"],
413
- ".mli": [".ml", ".mli"],
414
- ".res": [".res"],
415
- ".sol": [".sol"],
416
- // Template containers: ERB fragments are Ruby; HTML/EJS script is JS.
417
- ".erb": [".rb", ".erb"],
418
- ".html": JS_TS_EXT,
419
- ".htm": JS_TS_EXT,
420
- ".ejs": JS_TS_EXT
421
- });
422
- function buildModuleResolver(root, relSet) {
423
- const probe = makeProbe(relSet);
424
- const suffixIndex = buildSuffixIndex(relSet);
425
- const tsPaths = loadTsconfigPaths(root);
426
- const workspaces = loadWorkspacePackages(root);
427
- function resolveAlias(source) {
428
- if (!tsPaths) return null;
429
- for (const [pattern, targets] of tsPaths.paths) {
430
- const m = matchPattern(pattern, source);
431
- if (m === null) continue;
432
- for (const target of targets) {
433
- const sub = target.replace("*", m);
434
- const rel2 = posixJoin(tsPaths.baseUrlRel, sub);
435
- const hit = probe(rel2);
436
- if (hit) return hit;
437
- }
438
- }
439
- return null;
440
- }
441
- function resolveWorkspace(source) {
442
- if (!workspaces.size) return null;
443
- let best = null;
444
- for (const [name, dirRel] of workspaces) {
445
- if (source === name || source.startsWith(`${name}/`)) {
446
- const rest = source === name ? "" : source.slice(name.length + 1);
447
- if (!best || name.length > best.dirRel.length) best = { dirRel, rest };
448
- }
449
- }
450
- if (!best) return null;
451
- const base = best.rest ? posixJoin(best.dirRel, best.rest) : best.dirRel;
452
- return probe(base) ?? probe(posixJoin(base, "src/index")) ?? probe(posixJoin(base, "index"));
453
- }
454
- return {
455
- resolve(fromRel, source) {
456
- if (source.includes("\\")) source = source.split("\\").join(".");
457
- if (source.startsWith("./") || source.startsWith("../")) {
458
- return probe(posixJoin(path2.posix.dirname(fromRel), source));
459
- }
460
- if (/\.[A-Za-z0-9]+$/.test(source) && !source.startsWith("<")) {
461
- const hit = probe(posixJoin(path2.posix.dirname(fromRel), source));
462
- if (hit) return hit;
463
- }
464
- if (source.startsWith(".")) {
465
- return resolvePyRelative(probe, fromRel, source);
466
- }
467
- return resolveAlias(source) ?? resolveWorkspace(source) ?? resolveSuffix(suffixIndex, fromRel, source) ?? resolveDotted(probe, source);
468
- }
469
- };
470
- }
471
- function buildSuffixIndex(relSet) {
472
- const byLast = /* @__PURE__ */ new Map();
473
- for (const rel2 of relSet) {
474
- const dot = rel2.lastIndexOf(".");
475
- if (dot < 0) continue;
476
- const ext = rel2.slice(dot);
477
- if (!EXT_GROUPS[ext]) continue;
478
- let key = rel2.slice(0, dot);
479
- const base = key.slice(key.lastIndexOf("/") + 1);
480
- if (base === "__init__" || base === "index") {
481
- key = key.slice(0, Math.max(0, key.length - base.length - 1));
482
- }
483
- if (!key) continue;
484
- const last = key.slice(key.lastIndexOf("/") + 1);
485
- const list = byLast.get(last);
486
- if (list) list.push({ rel: rel2, key, ext });
487
- else byLast.set(last, [{ rel: rel2, key, ext }]);
488
- }
489
- return byLast;
490
- }
491
- function resolveSuffix(index, fromRel, source) {
492
- if (!/^[A-Za-z_][\w.]*$/.test(source) || !source.includes(".")) return null;
493
- const fromDot = fromRel.lastIndexOf(".");
494
- const fromExt = fromDot >= 0 ? fromRel.slice(fromDot) : "";
495
- const allowed = EXT_GROUPS[fromExt];
496
- if (!allowed) return null;
497
- const q = source.replace(/\./g, "/");
498
- const last = q.slice(q.lastIndexOf("/") + 1);
499
- const cands = index.get(last);
500
- if (!cands) return null;
501
- const matches = cands.filter(
502
- (c) => allowed.includes(c.ext) && (c.key === q || c.key.endsWith("/" + q))
503
- );
504
- if (matches.length === 0) return null;
505
- if (matches.length === 1) return matches[0].rel;
506
- return pickClosest(matches, fromRel).rel;
507
- }
508
- function pickClosest(matches, fromRel) {
509
- const fromSegs = fromRel.split("/");
510
- const score = (rel2) => {
511
- const segs = rel2.split("/");
512
- let i = 0;
513
- while (i < segs.length && i < fromSegs.length && segs[i] === fromSegs[i]) i++;
514
- return i;
515
- };
516
- return [...matches].sort((a, b) => {
517
- const d = score(b.rel) - score(a.rel);
518
- if (d) return d;
519
- if (a.rel.length !== b.rel.length) return a.rel.length - b.rel.length;
520
- return a.rel < b.rel ? -1 : 1;
521
- })[0];
522
- }
523
- function resolveDotted(probe, source) {
524
- if (!/^[A-Za-z_][\w.]*$/.test(source) || !source.includes(".")) return null;
525
- const slashed = source.replace(/\./g, "/");
526
- return probe(slashed) ?? probe(posixJoin("src", slashed));
527
- }
528
- function resolvePyRelative(probe, fromRel, source) {
529
- let dots = 0;
530
- while (source[dots] === ".") dots++;
531
- const rest = source.slice(dots);
532
- let dir = path2.posix.dirname(fromRel);
533
- for (let i = 1; i < dots; i++) dir = path2.posix.dirname(dir);
534
- const sub = rest.replace(/\./g, "/");
535
- return probe(sub ? posixJoin(dir, sub) : dir);
536
- }
537
- function relativeResolver(relSet) {
538
- const probe = makeProbe(relSet);
539
- return {
540
- resolve(fromRel, source) {
541
- if (source.startsWith(".")) return probe(posixJoin(path2.posix.dirname(fromRel), source));
542
- return resolveDotted(probe, source);
543
- }
544
- };
545
- }
546
- function makeProbe(relSet) {
547
- return (base) => {
548
- const norm = normalizeRel(base);
549
- if (!norm) return null;
550
- if (relSet.has(norm)) return norm;
551
- for (const ext of JS_TS_EXT) if (relSet.has(norm + ext)) return norm + ext;
552
- for (const ext of JS_TS_EXT) if (relSet.has(`${norm}/index${ext}`)) return `${norm}/index${ext}`;
553
- for (const ext of OTHER_EXT) if (relSet.has(norm + ext)) return norm + ext;
554
- if (relSet.has(`${norm}/__init__.py`)) return `${norm}/__init__.py`;
555
- return null;
556
- };
557
- }
558
- function normalizeRel(p) {
559
- const norm = path2.posix.normalize(p);
560
- const stripped = norm.startsWith("./") ? norm.slice(2) : norm;
561
- return stripped.startsWith("..") ? "" : stripped;
562
- }
563
- function posixJoin(a, b) {
564
- return path2.posix.normalize(path2.posix.join(a, b));
565
- }
566
- function matchPattern(pattern, source) {
567
- const star = pattern.indexOf("*");
568
- if (star === -1) return source === pattern ? "" : null;
569
- const prefix = pattern.slice(0, star);
570
- const suffix = pattern.slice(star + 1);
571
- if (!source.startsWith(prefix) || !source.endsWith(suffix)) return null;
572
- if (source.length < prefix.length + suffix.length) return null;
573
- return source.slice(prefix.length, source.length - suffix.length);
574
- }
575
- function loadTsconfigPaths(root) {
576
- for (const name of ["tsconfig.base.json", "tsconfig.json"]) {
577
- const file = path2.join(root, name);
578
- if (fs18.existsSync(file)) {
579
- const merged = readTsconfigChain(root, file, /* @__PURE__ */ new Set());
580
- if (merged && merged.paths.length) return merged;
581
- }
582
- }
583
- return null;
584
- }
585
- function readTsconfigChain(root, file, seen) {
586
- if (seen.has(file) || seen.size > 6) return null;
587
- seen.add(file);
588
- let cfg;
589
- try {
590
- cfg = parseJsonc(fs18.readFileSync(file, "utf8"));
591
- } catch {
592
- return null;
593
- }
594
- const dir = path2.dirname(file);
595
- let base = { baseUrlRel: "", paths: [] };
596
- if (cfg.extends && cfg.extends.startsWith(".")) {
597
- const extPath = path2.resolve(dir, cfg.extends.endsWith(".json") ? cfg.extends : `${cfg.extends}.json`);
598
- if (fs18.existsSync(extPath)) {
599
- const inherited = readTsconfigChain(root, extPath, seen);
600
- if (inherited) base = inherited;
601
- }
602
- }
603
- const co = cfg.compilerOptions ?? {};
604
- const baseUrlRel = co.baseUrl !== void 0 ? path2.relative(root, path2.resolve(dir, co.baseUrl)).split(path2.sep).join("/") : base.baseUrlRel;
605
- const paths = [...base.paths];
606
- if (co.paths) for (const [k, v] of Object.entries(co.paths)) paths.push([k, v]);
607
- return { baseUrlRel, paths };
608
- }
609
- function loadWorkspacePackages(root) {
610
- const map = /* @__PURE__ */ new Map();
611
- const globs = [];
612
- const rootPkg = path2.join(root, "package.json");
613
- if (fs18.existsSync(rootPkg)) {
614
- try {
615
- const pkg = JSON.parse(fs18.readFileSync(rootPkg, "utf8"));
616
- const ws = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces?.packages;
617
- if (ws) globs.push(...ws);
618
- } catch {
619
- }
620
- }
621
- const pnpmWs = path2.join(root, "pnpm-workspace.yaml");
622
- if (fs18.existsSync(pnpmWs)) {
623
- for (const line of fs18.readFileSync(pnpmWs, "utf8").split("\n")) {
624
- const m = /^\s*-\s*['"]?([^'"\n]+)['"]?\s*$/.exec(line);
625
- if (m) globs.push(m[1].trim());
626
- }
627
- }
628
- for (const dir of expandWorkspaceGlobs(root, globs)) {
629
- const pj = path2.join(dir, "package.json");
630
- try {
631
- const name = JSON.parse(fs18.readFileSync(pj, "utf8")).name;
632
- if (name) map.set(name, path2.relative(root, dir).split(path2.sep).join("/"));
633
- } catch {
634
- }
635
- }
636
- return map;
637
- }
638
- function expandWorkspaceGlobs(root, globs) {
639
- const out = /* @__PURE__ */ new Set();
640
- for (const glob of globs) {
641
- const clean = glob.replace(/\/\*\*$/, "").replace(/\/\*$/, "");
642
- const baseDir = path2.join(root, clean);
643
- const recurse = glob.endsWith("**");
644
- if (glob === clean) {
645
- if (fs18.existsSync(path2.join(baseDir, "package.json"))) out.add(baseDir);
646
- continue;
647
- }
648
- collectPackageDirs(baseDir, recurse ? 4 : 1, out);
649
- }
650
- return [...out];
651
- }
652
- function collectPackageDirs(dir, depth, out) {
653
- if (depth < 0) return;
654
- let entries;
655
- try {
656
- entries = fs18.readdirSync(dir, { withFileTypes: true });
657
- } catch {
658
- return;
659
- }
660
- for (const e of entries) {
661
- if (!e.isDirectory() || e.name === "node_modules" || e.name.startsWith(".")) continue;
662
- const child = path2.join(dir, e.name);
663
- if (fs18.existsSync(path2.join(child, "package.json"))) out.add(child);
664
- else collectPackageDirs(child, depth - 1, out);
665
- }
666
- }
667
- function parseJsonc(text) {
668
- return JSON.parse(stripJsonc(text));
669
- }
670
- function stripJsonc(text) {
671
- let out = "";
672
- let inStr = false;
673
- let strCh = "";
674
- for (let i = 0; i < text.length; i++) {
675
- const ch = text[i];
676
- const next = text[i + 1];
677
- if (inStr) {
678
- out += ch;
679
- if (ch === "\\") {
680
- out += next ?? "";
681
- i++;
682
- } else if (ch === strCh) {
683
- inStr = false;
684
- }
685
- continue;
686
- }
687
- if (ch === '"' || ch === "'") {
688
- inStr = true;
689
- strCh = ch;
690
- out += ch;
691
- continue;
692
- }
693
- if (ch === "/" && next === "/") {
694
- while (i < text.length && text[i] !== "\n") i++;
695
- out += "\n";
696
- continue;
697
- }
698
- if (ch === "/" && next === "*") {
699
- i += 2;
700
- while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) i++;
701
- i++;
702
- continue;
703
- }
704
- out += ch;
705
- }
706
- return out.replace(/,(\s*[}\]])/g, "$1");
707
- }
708
-
709
- // src/engine/ids.ts
710
- function nodeId(parts) {
711
- return shortId(
712
- canonicalize({
713
- t: "node",
714
- kind: parts.kind,
715
- qn: parts.qualifiedName,
716
- file: parts.file,
717
- sig: parts.signature ?? ""
718
- })
719
- );
720
- }
721
- function edgeId(kind, src, dst) {
722
- return shortId(canonicalize({ t: "edge", kind, src, dst }));
723
- }
724
-
725
- // src/engine/tests.ts
726
- function isTestFile(rel2) {
727
- const lower = rel2.toLowerCase();
728
- const base = rel2.split("/").pop() ?? rel2;
729
- const lbase = base.toLowerCase();
730
- if (/(^|\/)(__tests__|__test__)\//.test(lower)) return true;
731
- if (/(^|\/)(tests?|spec|specs)\//.test(lower)) return true;
732
- if (/(^|\/)src\/test\//.test(lower)) return true;
733
- if (/\.(test|spec)\.[cm]?[jt]sx?$/.test(lbase)) return true;
734
- if (/^test_.*\.py$/.test(lbase) || /_test\.py$/.test(lbase)) return true;
735
- if (/_test\.go$/.test(lbase)) return true;
736
- if (/[A-Za-z0-9](Test|Tests|IT)\.java$/.test(base)) return true;
737
- if (/_spec\.rb$/.test(lbase) || /_test\.rb$/.test(lbase)) return true;
738
- if (/[A-Za-z0-9](Test|Tests)\.cs$/.test(base)) return true;
739
- if (/[A-Za-z0-9](Test|Tests)\.swift$/.test(base)) return true;
740
- return false;
741
- }
742
- function applyStaticTestLinkage(nodes, edges) {
743
- const byId = new Map(nodes.map((n) => [n.id, n]));
744
- const testFiles = /* @__PURE__ */ new Set();
745
- for (const n of nodes) if (n.kind === "file" && isTestFile(n.file)) testFiles.add(n.file);
746
- const fileNodeIdByRel = /* @__PURE__ */ new Map();
747
- for (const n of nodes) if (n.kind === "file") fileNodeIdByRel.set(n.file, n.id);
748
- const covered = /* @__PURE__ */ new Set();
749
- const newEdges = /* @__PURE__ */ new Map();
750
- for (const e of edges) {
751
- if (e.kind !== "call") continue;
752
- const src = byId.get(e.src);
753
- const dst = byId.get(e.dst);
754
- if (!src || !dst) continue;
755
- if (testFiles.has(src.file) && !testFiles.has(dst.file) && dst.kind !== "external") {
756
- const testFileId = fileNodeIdByRel.get(src.file);
757
- if (!testFileId) continue;
758
- const id = edgeId("test", testFileId, dst.id);
759
- if (!newEdges.has(id)) {
760
- newEdges.set(id, {
761
- id,
762
- kind: "test",
763
- src: testFileId,
764
- dst: dst.id,
765
- resolution: e.resolution,
766
- confidence: e.confidence
767
- });
768
- }
769
- covered.add(dst.id);
770
- }
771
- }
772
- const outNodes = nodes.map((n) => ({ ...n, tested: testedFlag(n, covered.has(n.id), testFiles) }));
773
- const outEdges = [...edges, ...newEdges.values()].sort(
774
- (a, b) => a.kind.localeCompare(b.kind) || a.src.localeCompare(b.src) || a.dst.localeCompare(b.dst)
775
- );
776
- return {
777
- nodes: outNodes,
778
- edges: outEdges,
779
- testFiles: [...testFiles].sort(),
780
- testEdgeCount: newEdges.size
781
- };
782
- }
783
- function testedFlag(node, covered, testFiles) {
784
- if (node.kind === "external" || node.kind === "file" || node.kind === "package" || node.kind === "module") {
785
- return null;
786
- }
787
- if (testFiles.has(node.file)) return null;
788
- if (node.kind === "function" || node.kind === "method") return covered;
789
- return covered ? true : node.tested ?? null;
790
- }
791
355
 
792
356
  // src/engine/scip.ts
793
357
  var ROLE_DEFINITION = 1;
@@ -1159,14 +723,14 @@ var DEFAULT_PATHS = [
1159
723
  ];
1160
724
  function loadCoverage(root, explicit) {
1161
725
  const candidates = (explicit && explicit.length ? explicit : DEFAULT_PATHS).map(
1162
- (p) => path2.resolve(root, p)
726
+ (p) => path17.resolve(root, p)
1163
727
  );
1164
- const found = candidates.filter((p) => fs18.existsSync(p));
728
+ const found = candidates.filter((p) => fs15.existsSync(p));
1165
729
  if (found.length === 0) return null;
1166
730
  const map = /* @__PURE__ */ new Map();
1167
731
  for (const file of found) {
1168
732
  try {
1169
- const text = fs18.readFileSync(file, "utf8");
733
+ const text = fs15.readFileSync(file, "utf8");
1170
734
  if (file.endsWith(".json")) mergeIstanbul(map, text, root);
1171
735
  else mergeLcov(map, text, root);
1172
736
  } catch {
@@ -1175,8 +739,8 @@ function loadCoverage(root, explicit) {
1175
739
  return map.size ? map : null;
1176
740
  }
1177
741
  function rel(root, p) {
1178
- const abs = path2.isAbsolute(p) ? p : path2.resolve(root, p);
1179
- return path2.relative(root, abs).split(path2.sep).join("/");
742
+ const abs = path17.isAbsolute(p) ? p : path17.resolve(root, p);
743
+ return path17.relative(root, abs).split(path17.sep).join("/");
1180
744
  }
1181
745
  function bump(map, file, line, hits) {
1182
746
  let lh = map.get(file);
@@ -1486,7 +1050,7 @@ async function parseInline(files, options) {
1486
1050
  onProgress?.(0, files.length);
1487
1051
  for (const file of files) {
1488
1052
  try {
1489
- const source = fs18.readFileSync(file.abs, "utf8");
1053
+ const source = fs15.readFileSync(file.abs, "utf8");
1490
1054
  out.push(await parseSource(file.rel, file.lang.id, source));
1491
1055
  } catch (err) {
1492
1056
  out.push(emptyParse(file, `parse failed: ${err.message}`));
@@ -1541,9 +1105,9 @@ function isWorkerOom(err) {
1541
1105
  return e?.code === "ERR_WORKER_OUT_OF_MEMORY" || /out of memory/i.test(e?.message ?? "");
1542
1106
  }
1543
1107
  function resolveWorkerFile() {
1544
- const here = path2.dirname(fileURLToPath(import.meta.url));
1545
- const candidate = path2.join(here, "parse-worker.js");
1546
- return fs18.existsSync(candidate) ? candidate : null;
1108
+ const here = path17.dirname(fileURLToPath(import.meta.url));
1109
+ const candidate = path17.join(here, "parse-worker.js");
1110
+ return fs15.existsSync(candidate) ? candidate : null;
1547
1111
  }
1548
1112
  function chunk(items, buckets) {
1549
1113
  const out = Array.from({ length: Math.min(buckets, items.length || 1) }, () => []);
@@ -1569,335 +1133,6 @@ function emptyParse(file, warning) {
1569
1133
  warnings: [warning]
1570
1134
  };
1571
1135
  }
1572
- function resolve4(parses, resolver) {
1573
- const baseNodes = /* @__PURE__ */ new Map();
1574
- const relSet = new Set(parses.map((p) => p.rel));
1575
- const mod = resolver ?? relativeResolver(relSet);
1576
- const defsByName = /* @__PURE__ */ new Map();
1577
- const defsByFile = /* @__PURE__ */ new Map();
1578
- const fileNodeIdByRel = /* @__PURE__ */ new Map();
1579
- for (const p of parses) {
1580
- const fileId = nodeId({ kind: "file", qualifiedName: p.rel, file: p.rel });
1581
- fileNodeIdByRel.set(p.rel, fileId);
1582
- addNode(baseNodes, {
1583
- id: fileId,
1584
- kind: "file",
1585
- name: path2.posix.basename(p.rel),
1586
- qualifiedName: p.rel,
1587
- file: p.rel,
1588
- span: { start: 1, end: 1 },
1589
- lang: p.lang
1590
- });
1591
- for (const d of p.defs) {
1592
- const id = nodeId({
1593
- kind: d.kind,
1594
- qualifiedName: d.qualifiedName,
1595
- file: p.rel,
1596
- signature: d.signature
1597
- });
1598
- addNode(baseNodes, {
1599
- id,
1600
- kind: d.kind,
1601
- name: d.name,
1602
- qualifiedName: d.qualifiedName,
1603
- file: p.rel,
1604
- span: { start: d.startLine, end: d.endLine },
1605
- lang: p.lang,
1606
- signature: d.signature,
1607
- doc: d.doc
1608
- });
1609
- const ref = {
1610
- id,
1611
- name: d.name,
1612
- qualifiedName: d.qualifiedName,
1613
- rel: p.rel,
1614
- lang: p.lang,
1615
- kind: d.kind,
1616
- startByte: d.startByte,
1617
- endByte: d.endByte
1618
- };
1619
- push(defsByName, d.name, ref);
1620
- push(defsByFile, p.rel, ref);
1621
- }
1622
- }
1623
- const filesByNamespace = /* @__PURE__ */ new Map();
1624
- for (const p of parses) for (const ns of p.namespaces ?? []) addToSet(filesByNamespace, ns, p.rel);
1625
- const relsByLang = /* @__PURE__ */ new Map();
1626
- for (const p of parses) push(relsByLang, p.lang, p.rel);
1627
- const nsReachableByRel = /* @__PURE__ */ new Map();
1628
- const moduleReachableByRel = /* @__PURE__ */ new Map();
1629
- for (const p of parses) {
1630
- if (MODULE_SCOPED_LANGS.has(p.lang)) {
1631
- const mod2 = /* @__PURE__ */ new Set();
1632
- for (const rel2 of relsByLang.get(p.lang) ?? []) if (rel2 !== p.rel) mod2.add(rel2);
1633
- moduleReachableByRel.set(p.rel, mod2);
1634
- }
1635
- const reach = /* @__PURE__ */ new Set();
1636
- const namespaces = new Set(p.namespaces ?? []);
1637
- for (const imp of p.imports) {
1638
- namespaces.add(imp.source);
1639
- if (IMPORT_NAMES_A_TYPE.has(p.lang)) {
1640
- const sep9 = Math.max(imp.source.lastIndexOf("."), imp.source.lastIndexOf("\\"));
1641
- if (sep9 > 0) namespaces.add(imp.source.slice(0, sep9));
1642
- }
1643
- }
1644
- for (const ns of namespaces) for (const rel2 of filesByNamespace.get(ns) ?? []) if (rel2 !== p.rel) reach.add(rel2);
1645
- nsReachableByRel.set(p.rel, reach);
1646
- }
1647
- const superTypesByType = /* @__PURE__ */ new Map();
1648
- for (const p of parses) {
1649
- const localDefs = defsByFile.get(p.rel) ?? [];
1650
- for (const h of p.heritage) {
1651
- const encl = enclosingDefRef(localDefs, h.byte);
1652
- if (encl && (encl.kind === "class" || encl.kind === "interface")) addToSet(superTypesByType, encl.name, h.superName);
1653
- }
1654
- }
1655
- const edges = new EdgeSet();
1656
- const unresolved2 = /* @__PURE__ */ new Map();
1657
- const stats = {
1658
- callsResolved: 0,
1659
- callsUnresolved: 0,
1660
- importsResolvedToFile: 0,
1661
- importsExternal: 0,
1662
- resolvers: ["heuristic"]
1663
- };
1664
- for (const p of parses) {
1665
- const fileId = fileNodeIdByRel.get(p.rel);
1666
- const localByQn = /* @__PURE__ */ new Map();
1667
- for (const ref of defsByFile.get(p.rel) ?? []) localByQn.set(ref.qualifiedName, ref.id);
1668
- for (const d of p.defs) {
1669
- const id = localByQn.get(d.qualifiedName);
1670
- const parentQn = parentQualifiedName(d.qualifiedName);
1671
- const parentId = parentQn ? localByQn.get(parentQn) : void 0;
1672
- edges.add("contains", parentId ?? fileId, id, "heuristic", 1);
1673
- }
1674
- }
1675
- const importedFilesByRel = /* @__PURE__ */ new Map();
1676
- for (const p of parses) {
1677
- const fileId = fileNodeIdByRel.get(p.rel);
1678
- const importedRels = /* @__PURE__ */ new Set();
1679
- for (const imp of p.imports) {
1680
- const targetRel = mod.resolve(p.rel, imp.source);
1681
- if (targetRel && targetRel !== p.rel) {
1682
- importedRels.add(targetRel);
1683
- edges.add("import", fileId, fileNodeIdByRel.get(targetRel), "heuristic", 0.9);
1684
- stats.importsResolvedToFile++;
1685
- } else {
1686
- const extId = ensureExternalNode(baseNodes, imp.source);
1687
- edges.add("import", fileId, extId, "heuristic", 1);
1688
- stats.importsExternal++;
1689
- }
1690
- }
1691
- importedFilesByRel.set(p.rel, importedRels);
1692
- }
1693
- for (const p of parses) {
1694
- const fileId = fileNodeIdByRel.get(p.rel);
1695
- const localDefs = defsByFile.get(p.rel) ?? [];
1696
- const imported = importedFilesByRel.get(p.rel) ?? /* @__PURE__ */ new Set();
1697
- const testCaller = isTestFile(p.rel);
1698
- const nsReach = nsReachableByRel.get(p.rel) ?? /* @__PURE__ */ new Set();
1699
- const modReach = moduleReachableByRel.get(p.rel) ?? /* @__PURE__ */ new Set();
1700
- for (const call of p.calls) {
1701
- const srcId = enclosingDefId(localDefs, call.byte) ?? fileId;
1702
- const resolved = resolveCall(call, p.rel, p.lang, imported, defsByName, srcId, testCaller, nsReach, superTypesByType, modReach);
1703
- if (resolved) {
1704
- edges.add("call", srcId, resolved.id, "heuristic", resolved.confidence);
1705
- stats.callsResolved++;
1706
- } else {
1707
- stats.callsUnresolved++;
1708
- bumpUnresolved(unresolved2, srcId, call.callee, "call", p.rel);
1709
- }
1710
- }
1711
- }
1712
- for (const p of parses) {
1713
- const fileId = fileNodeIdByRel.get(p.rel);
1714
- const localDefs = defsByFile.get(p.rel) ?? [];
1715
- const imported = importedFilesByRel.get(p.rel) ?? /* @__PURE__ */ new Set();
1716
- const testCaller = isTestFile(p.rel);
1717
- const nsReach = nsReachableByRel.get(p.rel) ?? /* @__PURE__ */ new Set();
1718
- const modReach = moduleReachableByRel.get(p.rel) ?? /* @__PURE__ */ new Set();
1719
- for (const ref of p.typeRefs) {
1720
- const srcId = enclosingDefId(localDefs, ref.byte) ?? fileId;
1721
- const target = resolveType(ref.name, p.rel, p.lang, imported, defsByName, testCaller, nsReach, modReach);
1722
- if (target) edges.add("references", srcId, target.id, "heuristic", 0.7);
1723
- }
1724
- }
1725
- for (const p of parses) {
1726
- const localDefs = defsByFile.get(p.rel) ?? [];
1727
- const imported = importedFilesByRel.get(p.rel) ?? /* @__PURE__ */ new Set();
1728
- const testCaller = isTestFile(p.rel);
1729
- for (const h of p.heritage) {
1730
- const srcId = enclosingDefId(localDefs, h.byte);
1731
- if (!srcId) continue;
1732
- const nsReach = nsReachableByRel.get(p.rel) ?? /* @__PURE__ */ new Set();
1733
- const modReach = moduleReachableByRel.get(p.rel) ?? /* @__PURE__ */ new Set();
1734
- const target = resolveType(h.superName, p.rel, p.lang, imported, defsByName, testCaller, nsReach, modReach);
1735
- if (target) edges.add(h.kind, srcId, target.id, "heuristic", 0.85);
1736
- else bumpUnresolved(unresolved2, srcId, h.superName, h.kind, p.rel);
1737
- }
1738
- }
1739
- const nodes = [...baseNodes.values()].map((n) => ({
1740
- ...n,
1741
- importance: 0,
1742
- centrality: { degree: 0, pagerank: 0, betweenness: 0, eigenvector: 0 },
1743
- area: 0,
1744
- isHub: false,
1745
- tested: null
1746
- }));
1747
- const unresolvedList = [...unresolved2.values()].sort(
1748
- (a, b) => a.from.localeCompare(b.from) || a.kind.localeCompare(b.kind) || a.name.localeCompare(b.name)
1749
- );
1750
- return { nodes, edges: edges.toArray(), unresolved: unresolvedList, stats };
1751
- }
1752
- function bumpUnresolved(map, from, name, kind, fromRel) {
1753
- const key = `${from}\0${kind}\0${name}`;
1754
- const existing = map.get(key);
1755
- if (existing) existing.count++;
1756
- else map.set(key, { from, name, kind, count: 1, fromRel });
1757
- }
1758
- function addNode(map, n) {
1759
- if (!map.has(n.id)) map.set(n.id, n);
1760
- }
1761
- function ensureExternalNode(map, source) {
1762
- const id = nodeId({ kind: "external", qualifiedName: source, file: "" });
1763
- if (!map.has(id)) {
1764
- map.set(id, {
1765
- id,
1766
- kind: "external",
1767
- name: source,
1768
- qualifiedName: source,
1769
- file: "",
1770
- span: { start: 1, end: 1 },
1771
- lang: "external"
1772
- });
1773
- }
1774
- return id;
1775
- }
1776
- function push(map, key, value) {
1777
- const list = map.get(key);
1778
- if (list) list.push(value);
1779
- else map.set(key, [value]);
1780
- }
1781
- function parentQualifiedName(qn) {
1782
- const idx = qn.lastIndexOf(".");
1783
- return idx > 0 ? qn.slice(0, idx) : null;
1784
- }
1785
- function enclosingDefRef(defs, byte) {
1786
- let best;
1787
- for (const d of defs) {
1788
- if (d.startByte <= byte && d.endByte >= byte) {
1789
- if (!best || d.endByte - d.startByte < best.endByte - best.startByte) best = d;
1790
- }
1791
- }
1792
- return best;
1793
- }
1794
- function enclosingDefId(defs, byte) {
1795
- return enclosingDefRef(defs, byte)?.id;
1796
- }
1797
- function addToSet(map, key, value) {
1798
- const s = map.get(key);
1799
- if (s) s.add(value);
1800
- else map.set(key, /* @__PURE__ */ new Set([value]));
1801
- }
1802
- function parentTypeName(qualifiedName) {
1803
- const i = qualifiedName.lastIndexOf(".");
1804
- return i > 0 ? qualifiedName.slice(0, i) : null;
1805
- }
1806
- function disambiguateInterfaceImpl(pool, superTypesByType) {
1807
- const withParent = pool.map((c) => ({ c, parent: parentTypeName(c.qualifiedName) })).filter((x) => x.parent !== null);
1808
- if (withParent.length < 2) return null;
1809
- const parents = new Set(withParent.map((x) => x.parent));
1810
- const impls = withParent.filter(
1811
- (x) => [...parents].some((other) => other !== x.parent && superTypesByType.get(x.parent)?.has(other))
1812
- );
1813
- if (impls.length === 0) return null;
1814
- const interfaceParents = /* @__PURE__ */ new Set();
1815
- for (const impl of impls) for (const s of superTypesByType.get(impl.parent) ?? []) interfaceParents.add(s);
1816
- const interfaces = withParent.filter((x) => interfaceParents.has(x.parent));
1817
- const uniqueImplIds = new Set(impls.map((x) => x.c.id));
1818
- if (uniqueImplIds.size === 1) return impls[0].c;
1819
- const uniqueIfaceIds = new Set(interfaces.map((x) => x.c.id));
1820
- if (uniqueIfaceIds.size === 1) return interfaces[0].c;
1821
- return null;
1822
- }
1823
- var PACKAGE_SCOPED_LANGS = /* @__PURE__ */ new Set(["go", "java", "cs", "kotlin", "scala", "swift"]);
1824
- var IMPORT_NAMES_A_TYPE = /* @__PURE__ */ new Set(["java", "kotlin", "scala", "php"]);
1825
- var MODULE_SCOPED_LANGS = /* @__PURE__ */ new Set(["swift"]);
1826
- function dirOf(rel2) {
1827
- const i = rel2.lastIndexOf("/");
1828
- return i >= 0 ? rel2.slice(0, i) : "";
1829
- }
1830
- function resolveCall(call, fromRel, fromLang, importedRels, defsByName, enclosingId, fromIsTestFile = false, nsReachableRels = /* @__PURE__ */ new Set(), superTypesByType = /* @__PURE__ */ new Map(), moduleReachableRels = /* @__PURE__ */ new Set()) {
1831
- const candidates = defsByName.get(call.callee);
1832
- if (!candidates || candidates.length === 0) return null;
1833
- const callable = candidates.filter((c) => c.kind === "function" || c.kind === "method");
1834
- const pool = callable.length ? callable : candidates;
1835
- let sameFile = pool.filter((c) => c.rel === fromRel);
1836
- if (call.qualified && enclosingId) sameFile = sameFile.filter((c) => c.id !== enclosingId);
1837
- if (sameFile.length === 1) return { id: sameFile[0].id, confidence: 0.85 };
1838
- if (sameFile.length > 1) {
1839
- const pick = [...sameFile].sort((a, b) => a.id.localeCompare(b.id))[0];
1840
- return { id: pick.id, confidence: 0.4 };
1841
- }
1842
- const dir = dirOf(fromRel);
1843
- const notSelf = (c) => !(call.qualified && enclosingId && c.id === enclosingId);
1844
- const strict = pool.filter(
1845
- (c) => notSelf(c) && (importedRels.has(c.rel) || PACKAGE_SCOPED_LANGS.has(fromLang) && c.lang === fromLang && dirOf(c.rel) === dir || nsReachableRels.has(c.rel))
1846
- );
1847
- if (strict.length === 1) return { id: strict[0].id, confidence: 0.75 };
1848
- const broad = pool.filter((c) => notSelf(c) && (moduleReachableRels.has(c.rel) || strict.includes(c)));
1849
- if (broad.length > 1) {
1850
- const dedup = disambiguateInterfaceImpl(broad, superTypesByType);
1851
- if (dedup) return { id: dedup.id, confidence: 0.6 };
1852
- }
1853
- if (strict.length > 1) return null;
1854
- if (fromLang === "swift" && fromIsTestFile && pool.length === 1) {
1855
- return { id: pool[0].id, confidence: 0.5 };
1856
- }
1857
- return null;
1858
- }
1859
- function resolveType(name, fromRel, fromLang, importedRels, defsByName, fromIsTestFile = false, nsReachableRels = /* @__PURE__ */ new Set(), moduleReachableRels = /* @__PURE__ */ new Set()) {
1860
- const candidates = (defsByName.get(name) ?? []).filter(
1861
- (c) => c.kind === "class" || c.kind === "interface"
1862
- );
1863
- if (candidates.length === 0) return null;
1864
- const sameFile = candidates.filter((c) => c.rel === fromRel);
1865
- if (sameFile.length === 1) return { id: sameFile[0].id };
1866
- const imported = candidates.filter((c) => importedRels.has(c.rel));
1867
- if (imported.length === 1) return { id: imported[0].id };
1868
- if (imported.length > 1) return null;
1869
- if (PACKAGE_SCOPED_LANGS.has(fromLang)) {
1870
- const dir = dirOf(fromRel);
1871
- const samePkg = candidates.filter((c) => c.lang === fromLang && dirOf(c.rel) === dir);
1872
- if (samePkg.length === 1) return { id: samePkg[0].id };
1873
- }
1874
- if (nsReachableRels.size || moduleReachableRels.size) {
1875
- const reach = candidates.filter((c) => nsReachableRels.has(c.rel) || moduleReachableRels.has(c.rel));
1876
- if (reach.length === 1) return { id: reach[0].id };
1877
- }
1878
- if (fromLang === "swift" && fromIsTestFile && candidates.length === 1) {
1879
- return { id: candidates[0].id };
1880
- }
1881
- return null;
1882
- }
1883
- var EdgeSet = class {
1884
- map = /* @__PURE__ */ new Map();
1885
- add(kind, src, dst, resolution, confidence) {
1886
- const id = edgeId(kind, src, dst);
1887
- const existing = this.map.get(id);
1888
- if (existing) {
1889
- existing.count = (existing.count ?? 1) + 1;
1890
- if (confidence > existing.confidence) existing.confidence = confidence;
1891
- return;
1892
- }
1893
- this.map.set(id, { id, kind, src, dst, resolution, confidence, count: 1 });
1894
- }
1895
- toArray() {
1896
- return [...this.map.values()].sort(
1897
- (a, b) => a.kind.localeCompare(b.kind) || a.src.localeCompare(b.src) || a.dst.localeCompare(b.dst)
1898
- );
1899
- }
1900
- };
1901
1136
  var PRECISE_KIND = "tsc";
1902
1137
  function tsResolveEdges(root, tsFiles, nodes) {
1903
1138
  const empty = {
@@ -2121,7 +1356,7 @@ function enclosing3(nodes, line) {
2121
1356
  return best;
2122
1357
  }
2123
1358
  function normalize2(p) {
2124
- return path2.resolve(p).split(path2.sep).join("/");
1359
+ return path17.resolve(p).split(path17.sep).join("/");
2125
1360
  }
2126
1361
 
2127
1362
  // src/engine/epistemic.ts
@@ -2147,11 +1382,416 @@ function epistemicBreakdown(edges) {
2147
1382
  }
2148
1383
  return out;
2149
1384
  }
1385
+ var DOC_BODY_MAX_CHARS = 8e3;
1386
+ var DOC_FILE_MAX_BYTES = 512 * 1024;
1387
+ var DOC_MAX_FILES = 600;
1388
+ var DOC_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".mdx", ".txt", ".rst", ".adoc"]);
1389
+ var ENV_EXAMPLE_NAMES = /* @__PURE__ */ new Set([
1390
+ "example.env",
1391
+ "env.example",
1392
+ ".env.example",
1393
+ ".env.sample",
1394
+ ".env.template",
1395
+ ".env.development.example",
1396
+ ".env.local.example",
1397
+ "dotenv.example"
1398
+ ]);
1399
+ var EXACT_BASENAME = {
1400
+ // Manifests / package managers
1401
+ "package.json": "manifest",
1402
+ "package.yaml": "manifest",
1403
+ "package.yml": "manifest",
1404
+ "pyproject.toml": "manifest",
1405
+ "setup.cfg": "manifest",
1406
+ "requirements.txt": "manifest",
1407
+ "requirements-dev.txt": "manifest",
1408
+ "pipfile": "manifest",
1409
+ "go.mod": "manifest",
1410
+ "go.work": "manifest",
1411
+ "cargo.toml": "manifest",
1412
+ "composer.json": "manifest",
1413
+ "gemfile": "manifest",
1414
+ "podfile": "manifest",
1415
+ "pubspec.yaml": "manifest",
1416
+ "mix.exs": "manifest",
1417
+ "build.gradle": "manifest",
1418
+ "build.gradle.kts": "manifest",
1419
+ "settings.gradle": "manifest",
1420
+ "settings.gradle.kts": "manifest",
1421
+ "pom.xml": "manifest",
1422
+ "project.clj": "manifest",
1423
+ "stack.yaml": "manifest",
1424
+ "package.swift": "manifest",
1425
+ // Workspace / monorepo
1426
+ "pnpm-workspace.yaml": "workspace",
1427
+ "pnpm-workspace.yml": "workspace",
1428
+ "lerna.json": "workspace",
1429
+ "nx.json": "workspace",
1430
+ "turbo.json": "workspace",
1431
+ "rush.json": "workspace",
1432
+ "workspace.json": "workspace",
1433
+ // Docker
1434
+ dockerfile: "docker",
1435
+ "dockerfile.dev": "docker",
1436
+ "dockerfile.prod": "docker",
1437
+ "dockerfile.local": "docker",
1438
+ containerfile: "docker",
1439
+ // Compose
1440
+ "docker-compose.yml": "compose",
1441
+ "docker-compose.yaml": "compose",
1442
+ "compose.yml": "compose",
1443
+ "compose.yaml": "compose",
1444
+ "docker-compose.dev.yml": "compose",
1445
+ "docker-compose.prod.yml": "compose",
1446
+ "docker-compose.override.yml": "compose",
1447
+ // Task runners
1448
+ makefile: "task-runner",
1449
+ gnumakefile: "task-runner",
1450
+ justfile: "task-runner",
1451
+ "taskfile.yml": "task-runner",
1452
+ "taskfile.yaml": "task-runner",
1453
+ rakefile: "task-runner",
1454
+ // Build / tool config (exact)
1455
+ "tsconfig.json": "build-config",
1456
+ "jsconfig.json": "build-config",
1457
+ "vite.config.ts": "build-config",
1458
+ "vite.config.js": "build-config",
1459
+ "vite.config.mjs": "build-config",
1460
+ "vitest.config.ts": "build-config",
1461
+ "vitest.config.js": "build-config",
1462
+ "jest.config.js": "build-config",
1463
+ "jest.config.ts": "build-config",
1464
+ "jest.config.cjs": "build-config",
1465
+ "webpack.config.js": "build-config",
1466
+ "webpack.config.ts": "build-config",
1467
+ "rollup.config.js": "build-config",
1468
+ "esbuild.config.js": "build-config",
1469
+ "next.config.js": "build-config",
1470
+ "next.config.mjs": "build-config",
1471
+ "next.config.ts": "build-config",
1472
+ "nuxt.config.ts": "build-config",
1473
+ "nuxt.config.js": "build-config",
1474
+ "astro.config.mjs": "build-config",
1475
+ "svelte.config.js": "build-config",
1476
+ "remix.config.js": "build-config",
1477
+ "angular.json": "build-config",
1478
+ "babel.config.js": "build-config",
1479
+ "babel.config.json": "build-config",
1480
+ ".babelrc": "build-config",
1481
+ ".babelrc.js": "build-config",
1482
+ ".babelrc.json": "build-config",
1483
+ "eslint.config.js": "build-config",
1484
+ "eslint.config.mjs": "build-config",
1485
+ "eslint.config.cjs": "build-config",
1486
+ "eslint.config.ts": "build-config",
1487
+ ".eslintrc": "build-config",
1488
+ ".eslintrc.js": "build-config",
1489
+ ".eslintrc.cjs": "build-config",
1490
+ ".eslintrc.json": "build-config",
1491
+ ".eslintrc.yml": "build-config",
1492
+ ".prettierrc": "build-config",
1493
+ ".prettierrc.js": "build-config",
1494
+ ".prettierrc.json": "build-config",
1495
+ ".prettierrc.yml": "build-config",
1496
+ "prettier.config.js": "build-config",
1497
+ "prettier.config.cjs": "build-config",
1498
+ "ruff.toml": "build-config",
1499
+ ".ruff.toml": "build-config",
1500
+ "pytest.ini": "build-config",
1501
+ "tox.ini": "build-config",
1502
+ "mypy.ini": "build-config",
1503
+ ".mypy.ini": "build-config",
1504
+ "phpunit.xml": "build-config",
1505
+ "phpunit.xml.dist": "build-config",
1506
+ // CI exact
1507
+ "jenkinsfile": "ci",
1508
+ ".gitlab-ci.yml": "ci",
1509
+ ".travis.yml": "ci",
1510
+ "azure-pipelines.yml": "ci",
1511
+ "buildkite.yml": "ci",
1512
+ "cloudbuild.yaml": "ci",
1513
+ "appveyor.yml": "ci",
1514
+ "circle.yml": "ci",
1515
+ // Deploy / infra (root-level common names)
1516
+ "vercel.json": "infra",
1517
+ "netlify.toml": "infra",
1518
+ "wrangler.toml": "infra",
1519
+ "wrangler.json": "infra",
1520
+ "wrangler.jsonc": "infra",
1521
+ "fly.toml": "infra",
1522
+ "railway.toml": "infra",
1523
+ "render.yaml": "infra",
1524
+ "app.yaml": "infra",
1525
+ "Procfile": "infra",
1526
+ "procfile": "infra",
1527
+ "heroku.yml": "infra",
1528
+ "serverless.yml": "infra",
1529
+ "serverless.yaml": "infra",
1530
+ "sam.yaml": "infra",
1531
+ "samconfig.toml": "infra",
1532
+ "cdk.json": "infra",
1533
+ "pulumi.yaml": "infra",
1534
+ "terraform.tf": "infra",
1535
+ // Agent / project meta (also covered by .md extension)
1536
+ "agents.md": "markdown",
1537
+ "claude.md": "markdown",
1538
+ "codeowners": "other-config",
1539
+ ".nvmrc": "other-config",
1540
+ ".node-version": "other-config",
1541
+ ".python-version": "other-config",
1542
+ "runtime.txt": "other-config",
1543
+ ".tool-versions": "other-config",
1544
+ "editorconfig": "other-config",
1545
+ ".editorconfig": "other-config"
1546
+ };
1547
+ var BASENAME_PREFIX = [
1548
+ { re: /^dockerfile(\.|$)/i, category: "docker" },
1549
+ { re: /^docker-compose(\.|$)/i, category: "compose" },
1550
+ { re: /^compose\.(ya?ml)$/i, category: "compose" },
1551
+ { re: /^tsconfig(\..+)?\.json$/i, category: "build-config" },
1552
+ { re: /^jsconfig(\..+)?\.json$/i, category: "build-config" },
1553
+ { re: /^vite\.config\./i, category: "build-config" },
1554
+ { re: /^vitest\.config\./i, category: "build-config" },
1555
+ { re: /^jest\.config\./i, category: "build-config" },
1556
+ { re: /^webpack\.config\./i, category: "build-config" },
1557
+ { re: /^rollup\.config\./i, category: "build-config" },
1558
+ { re: /^next\.config\./i, category: "build-config" },
1559
+ { re: /^nuxt\.config\./i, category: "build-config" },
1560
+ { re: /^astro\.config\./i, category: "build-config" },
1561
+ { re: /^eslint\.config\./i, category: "build-config" },
1562
+ { re: /^\.eslintrc/i, category: "build-config" },
1563
+ { re: /^openapi\./i, category: "api-contract" },
1564
+ { re: /^swagger\./i, category: "api-contract" },
1565
+ { re: /^requirements(-\w+)?\.txt$/i, category: "manifest" },
1566
+ { re: /^dockerfile/i, category: "docker" }
1567
+ ];
1568
+ var PATH_PATTERNS = [
1569
+ // GitHub Actions / workflows
1570
+ { re: /^\.github\/workflows\/.+\.ya?ml$/i, category: "ci" },
1571
+ { re: /^\.github\/actions\/.+/i, category: "ci" },
1572
+ { re: /^\.circleci\/.+\.ya?ml$/i, category: "ci" },
1573
+ { re: /^\.buildkite\/.+/i, category: "ci" },
1574
+ // Helm / k8s (values + charts — not every template blob)
1575
+ { re: /^charts\/[^/]+\/(chart\.ya?ml|values.*\.ya?ml)$/i, category: "infra" },
1576
+ { re: /^deploy\/.+\.ya?ml$/i, category: "infra" },
1577
+ { re: /^k8s\/.+\.ya?ml$/i, category: "infra" },
1578
+ { re: /^kubernetes\/.+\.ya?ml$/i, category: "infra" },
1579
+ { re: /^infra\/.+\.ya?ml$/i, category: "infra" },
1580
+ { re: /^terraform\/.+\.tf$/i, category: "infra" },
1581
+ // API contracts by path
1582
+ { re: /(^|\/)(openapi|swagger)(\.|\/)/i, category: "api-contract" },
1583
+ { re: /(^|\/)api(-)?(spec|schema|contracts?)\//i, category: "api-contract" }
1584
+ ];
1585
+ var API_EXTENSIONS = /* @__PURE__ */ new Set([".graphql", ".gql", ".proto", ".avsc", ".thrift"]);
1586
+ var IMPORTANCE = {
1587
+ markdown: 0.1,
1588
+ text: 0.06,
1589
+ "env-example": 0.12,
1590
+ manifest: 0.16,
1591
+ docker: 0.14,
1592
+ compose: 0.15,
1593
+ ci: 0.14,
1594
+ "build-config": 0.11,
1595
+ "api-contract": 0.13,
1596
+ "task-runner": 0.13,
1597
+ workspace: 0.14,
1598
+ infra: 0.12,
1599
+ "other-config": 0.08
1600
+ };
1601
+ function toPosix2(p) {
1602
+ return p.split(path17.sep).join("/");
1603
+ }
1604
+ function buildRootIgnore2(root, exclude) {
1605
+ const ig = ignore();
1606
+ const gitignorePath = path17.join(root, ".gitignore");
1607
+ if (fs15.existsSync(gitignorePath)) {
1608
+ try {
1609
+ ig.add(fs15.readFileSync(gitignorePath, "utf8"));
1610
+ } catch {
1611
+ }
1612
+ }
1613
+ if (exclude.length) ig.add(exclude);
1614
+ return ig;
1615
+ }
1616
+ function classifyProjectContext(relOrName) {
1617
+ const rel2 = relOrName.replace(/\\/g, "/");
1618
+ const base = path17.basename(rel2);
1619
+ const baseLower = base.toLowerCase();
1620
+ if (/^\.env($|\.)/i.test(base) && !isEnvExampleName(baseLower)) return null;
1621
+ if (SKIP_FILES.has(baseLower)) return null;
1622
+ if (isEnvExampleName(baseLower)) return "env-example";
1623
+ const exact = EXACT_BASENAME[baseLower] ?? EXACT_BASENAME[base];
1624
+ if (exact) return exact;
1625
+ for (const { re, category } of BASENAME_PREFIX) {
1626
+ if (re.test(base)) return category;
1627
+ }
1628
+ for (const { re, category } of PATH_PATTERNS) {
1629
+ if (re.test(rel2)) return category;
1630
+ }
1631
+ const ext = path17.extname(baseLower);
1632
+ if (DOC_EXTENSIONS.has(ext)) return ext === ".txt" ? "text" : "markdown";
1633
+ if (API_EXTENSIONS.has(ext)) return "api-contract";
1634
+ if ((ext === ".yaml" || ext === ".yml" || ext === ".json") && /openapi|swagger|asyncapi/i.test(base)) {
1635
+ return "api-contract";
1636
+ }
1637
+ if (/^docker-compose\..+\.ya?ml$/i.test(base) || /^compose\..+\.ya?ml$/i.test(base)) {
1638
+ return "compose";
1639
+ }
1640
+ return null;
1641
+ }
1642
+ function isEnvExampleName(baseLower) {
1643
+ if (ENV_EXAMPLE_NAMES.has(baseLower)) return true;
1644
+ if (/^(\.)?env\.(example|sample|template)(\.|$)/i.test(baseLower)) return true;
1645
+ if (/^example\.env/i.test(baseLower)) return true;
1646
+ return false;
1647
+ }
1648
+ function discoverDocs(options) {
1649
+ const root = path17.resolve(options.root);
1650
+ const maxFiles = options.maxFiles ?? DOC_MAX_FILES;
1651
+ const rootIg = buildRootIgnore2(root, options.exclude ?? []);
1652
+ const scopeAbs = (options.paths?.length ? options.paths.map((p) => path17.resolve(root, p)) : [root]).filter(
1653
+ (p) => fs15.existsSync(p)
1654
+ );
1655
+ const found = /* @__PURE__ */ new Map();
1656
+ const consider = (abs) => {
1657
+ if (found.size >= maxFiles) return;
1658
+ const rel2 = toPosix2(path17.relative(root, abs));
1659
+ if (!rel2 || rel2.startsWith("..")) return;
1660
+ if (rootIg.ignores(rel2)) return;
1661
+ const base = path17.basename(abs);
1662
+ if (SKIP_FILES.has(base.toLowerCase())) return;
1663
+ const category = classifyProjectContext(rel2);
1664
+ if (!category) return;
1665
+ try {
1666
+ const st = fs15.statSync(abs);
1667
+ if (!st.isFile() || st.size > DOC_FILE_MAX_BYTES) return;
1668
+ if (st.size === 0) return;
1669
+ } catch {
1670
+ return;
1671
+ }
1672
+ found.set(rel2, { rel: rel2, abs, kind: category, category });
1673
+ };
1674
+ const walk = (dir, depth) => {
1675
+ if (found.size >= maxFiles || depth > 12) return;
1676
+ let entries;
1677
+ try {
1678
+ entries = fs15.readdirSync(dir, { withFileTypes: true });
1679
+ } catch {
1680
+ return;
1681
+ }
1682
+ for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
1683
+ if (found.size >= maxFiles) return;
1684
+ const abs = path17.join(dir, entry.name);
1685
+ const rel2 = toPosix2(path17.relative(root, abs));
1686
+ if (entry.isDirectory()) {
1687
+ if (SKIP_DIRS.has(entry.name)) continue;
1688
+ if (rel2 && rootIg.ignores(`${rel2}/`) && !rel2.startsWith(".github")) continue;
1689
+ walk(abs, depth + 1);
1690
+ } else if (entry.isFile()) {
1691
+ consider(abs);
1692
+ }
1693
+ }
1694
+ };
1695
+ for (const scope of scopeAbs) {
1696
+ try {
1697
+ const st = fs15.statSync(scope);
1698
+ if (st.isDirectory()) walk(scope, 0);
1699
+ else if (st.isFile()) consider(scope);
1700
+ } catch {
1701
+ }
1702
+ }
1703
+ return [...found.values()].sort((a, b) => a.rel.localeCompare(b.rel));
1704
+ }
1705
+ function prepareDocBody(rel2, raw, category) {
1706
+ const scrubbed = redactSecrets(raw);
1707
+ if (scrubbed.length <= DOC_BODY_MAX_CHARS) return scrubbed;
1708
+ if (category === "manifest" && path17.basename(rel2).toLowerCase() === "package.json") {
1709
+ return summarizePackageJson(scrubbed);
1710
+ }
1711
+ if (category === "api-contract" && /\.(ya?ml|json)$/i.test(rel2)) {
1712
+ return scrubbed.slice(0, DOC_BODY_MAX_CHARS);
1713
+ }
1714
+ if (category === "ci") {
1715
+ return scrubbed.slice(0, DOC_BODY_MAX_CHARS);
1716
+ }
1717
+ return scrubbed.slice(0, DOC_BODY_MAX_CHARS);
1718
+ }
1719
+ function summarizePackageJson(raw) {
1720
+ try {
1721
+ const o = JSON.parse(raw);
1722
+ const keep = {};
1723
+ for (const k of [
1724
+ "name",
1725
+ "version",
1726
+ "description",
1727
+ "private",
1728
+ "type",
1729
+ "packageManager",
1730
+ "engines",
1731
+ "workspaces",
1732
+ "scripts",
1733
+ "dependencies",
1734
+ "devDependencies",
1735
+ "peerDependencies",
1736
+ "optionalDependencies",
1737
+ "main",
1738
+ "bin",
1739
+ "exports"
1740
+ ]) {
1741
+ if (o[k] !== void 0) keep[k] = o[k];
1742
+ }
1743
+ const s = JSON.stringify(keep, null, 2);
1744
+ return s.length <= DOC_BODY_MAX_CHARS ? s : s.slice(0, DOC_BODY_MAX_CHARS);
1745
+ } catch {
1746
+ return raw.slice(0, DOC_BODY_MAX_CHARS);
1747
+ }
1748
+ }
1749
+ function documentNodesFromDocs(docs) {
1750
+ const nodes = [];
1751
+ for (const d of docs) {
1752
+ let raw = "";
1753
+ try {
1754
+ raw = fs15.readFileSync(d.abs, "utf8");
1755
+ } catch {
1756
+ continue;
1757
+ }
1758
+ const category = d.category ?? d.kind;
1759
+ const scrubbed = prepareDocBody(d.rel, raw, category);
1760
+ if (!scrubbed.trim()) continue;
1761
+ const name = path17.basename(d.rel);
1762
+ const qualifiedName = d.rel.replace(/\\/g, "/");
1763
+ const id = nodeId({
1764
+ kind: "document",
1765
+ qualifiedName,
1766
+ file: qualifiedName,
1767
+ signature: category
1768
+ });
1769
+ const lineCount = Math.max(1, scrubbed.split("\n").length);
1770
+ const langTag = category === "markdown" ? "md" : category === "env-example" ? "env" : category === "manifest" ? "manifest" : category === "docker" || category === "compose" ? "docker" : category === "ci" ? "ci" : category === "api-contract" ? "api" : category === "build-config" ? "config" : category === "task-runner" ? "task" : category === "workspace" ? "workspace" : category === "infra" ? "infra" : "txt";
1771
+ nodes.push({
1772
+ id,
1773
+ kind: "document",
1774
+ name,
1775
+ qualifiedName,
1776
+ file: qualifiedName,
1777
+ span: { start: 1, end: lineCount },
1778
+ lang: langTag,
1779
+ signature: `document:${category}`,
1780
+ doc: scrubbed,
1781
+ importance: IMPORTANCE[category] ?? 0.05,
1782
+ centrality: { degree: 0, pagerank: 0, betweenness: 0, eigenvector: 0 },
1783
+ area: -1,
1784
+ isHub: false,
1785
+ tested: null
1786
+ });
1787
+ }
1788
+ return nodes.sort((a, b) => a.file.localeCompare(b.file) || a.id.localeCompare(b.id));
1789
+ }
2150
1790
 
2151
1791
  // src/engine/build.ts
2152
1792
  async function buildGraph(options) {
2153
1793
  const start = nowMs();
2154
- const root = path2.resolve(options.root);
1794
+ const root = path17.resolve(options.root);
2155
1795
  const files = discover({
2156
1796
  root,
2157
1797
  only: options.only,
@@ -2179,7 +1819,7 @@ async function buildGraph(options) {
2179
1819
  for (const file of files) {
2180
1820
  let stat;
2181
1821
  try {
2182
- stat = fs18.statSync(file.abs);
1822
+ stat = fs15.statSync(file.abs);
2183
1823
  } catch {
2184
1824
  continue;
2185
1825
  }
@@ -2197,7 +1837,7 @@ async function buildGraph(options) {
2197
1837
  }
2198
1838
  let hash = "";
2199
1839
  try {
2200
- hash = hashBytes(fs18.readFileSync(file.abs));
1840
+ hash = hashBytes(fs15.readFileSync(file.abs));
2201
1841
  } catch {
2202
1842
  continue;
2203
1843
  }
@@ -2224,7 +1864,7 @@ async function buildGraph(options) {
2224
1864
  );
2225
1865
  const warnings = [...buildWarnings, ...parses.flatMap((p) => p.warnings ?? [])];
2226
1866
  const moduleResolver = buildModuleResolver(root, new Set(parses.map((p) => p.rel)));
2227
- const resolved = resolve4(parses, moduleResolver);
1867
+ const resolved = resolve(parses, moduleResolver);
2228
1868
  checkMemoryBudget("resolve", limits.memoryBudgetMb);
2229
1869
  const nodeFileById = new Map(resolved.nodes.map((n) => [n.id, n.file]));
2230
1870
  let edges = resolved.edges;
@@ -2261,9 +1901,25 @@ async function buildGraph(options) {
2261
1901
  let nodes = linked.nodes;
2262
1902
  const coverage = options.noCoverage ? null : loadCoverage(root, options.coverage);
2263
1903
  if (coverage) nodes = applyCoverage(nodes, coverage);
1904
+ const docs = discoverDocs({
1905
+ root,
1906
+ exclude: options.exclude,
1907
+ paths: options.paths
1908
+ });
1909
+ for (const d of docs) {
1910
+ try {
1911
+ const st = fs15.statSync(d.abs);
1912
+ const hash = hashBytes(fs15.readFileSync(d.abs));
1913
+ hashes.set(d.rel, hash);
1914
+ fileStats.push({ rel: d.rel, size: st.size, mtimeMs: st.mtimeMs, hash });
1915
+ } catch {
1916
+ }
1917
+ }
1918
+ const docNodes = documentNodesFromDocs(docs);
1919
+ if (docNodes.length) nodes = [...nodes, ...docNodes];
2264
1920
  const analysis = analyze(nodes, linked.edges, { cluster: options.cluster });
2265
1921
  checkMemoryBudget("analysis", limits.memoryBudgetMb);
2266
- const languages = [...new Set(parses.map((p) => p.lang))].sort();
1922
+ const languages = [.../* @__PURE__ */ new Set([...parses.map((p) => p.lang), ...docNodes.map((n) => n.lang)])].sort();
2267
1923
  const edgeKinds = [...new Set(analysis.edges.map((e) => e.kind))].sort();
2268
1924
  const corpusHash = computeCorpusHash(parses, hashes);
2269
1925
  const nodeKindById = new Map(analysis.nodes.map((n) => [n.id, n.kind]));
@@ -2289,7 +1945,7 @@ async function buildGraph(options) {
2289
1945
  toolchain
2290
1946
  },
2291
1947
  meta: {
2292
- root: path2.basename(root) === "" ? "." : ".",
1948
+ root: path17.basename(root) === "" ? "." : ".",
2293
1949
  languages,
2294
1950
  counts: {
2295
1951
  nodes: analysis.nodes.length,
@@ -2335,14 +1991,14 @@ function toRepoRel(p) {
2335
1991
  function loadScipIndex(root, explicit) {
2336
1992
  const candidates = [
2337
1993
  explicit,
2338
- path2.join(root, "index.scip"),
2339
- path2.join(root, ".vibgrate", "index.scip")
1994
+ path17.join(root, "index.scip"),
1995
+ path17.join(root, ".vibgrate", "index.scip")
2340
1996
  ].filter((p) => Boolean(p));
2341
1997
  for (const file of candidates) {
2342
- const abs = path2.isAbsolute(file) ? file : path2.resolve(root, file);
2343
- if (!fs18.existsSync(abs)) continue;
1998
+ const abs = path17.isAbsolute(file) ? file : path17.resolve(root, file);
1999
+ if (!fs15.existsSync(abs)) continue;
2344
2000
  try {
2345
- const index = decodeScipIndex(new Uint8Array(fs18.readFileSync(abs)));
2001
+ const index = decodeScipIndex(new Uint8Array(fs15.readFileSync(abs)));
2346
2002
  if (index.documents.length) {
2347
2003
  return { index, tool: index.toolVersion ? `${index.toolName} ${index.toolVersion}` : index.toolName };
2348
2004
  }
@@ -2384,188 +2040,17 @@ function computeToolchain(grammars, resolvers) {
2384
2040
  };
2385
2041
  }
2386
2042
  function computeCorpusHash(parses, hashes) {
2387
- const list = parses.map((p) => [p.rel, hashes.get(p.rel) ?? p.hash]).sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
2043
+ const fromParses = parses.map((p) => [p.rel, hashes.get(p.rel) ?? p.hash]);
2044
+ const fromHashes = [...hashes.entries()].map(([rel2, hash]) => [rel2, hash]);
2045
+ const byRel = /* @__PURE__ */ new Map();
2046
+ for (const [rel2, hash] of [...fromParses, ...fromHashes]) byRel.set(rel2, hash);
2047
+ const list = [...byRel.entries()].sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
2388
2048
  return hashString(canonicalize(list));
2389
2049
  }
2390
2050
  function nowMs() {
2391
2051
  return Number(process.hrtime.bigint() / 1000n) / 1e3;
2392
2052
  }
2393
2053
 
2394
- // src/engine/report.ts
2395
- function renderReport(graph) {
2396
- const { meta, provenance, nodes, edges } = graph;
2397
- const lines = [];
2398
- lines.push("# Code Graph Report");
2399
- lines.push("");
2400
- lines.push(`Generated by \`vg\` ${provenance.version} \xB7 schema \`${graph.schemaVersion}\``);
2401
- lines.push("");
2402
- lines.push("## Summary");
2403
- lines.push("");
2404
- lines.push(`- **Nodes:** ${meta.counts.nodes.toLocaleString("en-US")}`);
2405
- lines.push(`- **Edges:** ${meta.counts.edges.toLocaleString("en-US")}`);
2406
- lines.push(`- **Areas:** ${meta.counts.areas.toLocaleString("en-US")}`);
2407
- lines.push(`- **Languages:** ${meta.languages.join(", ") || "\u2014"}`);
2408
- lines.push(`- **Clustering:** ${meta.cluster}`);
2409
- lines.push(`- **Resolvers:** ${provenance.resolver.join(", ")}`);
2410
- lines.push(`- **Edge kinds:** ${meta.edgeKinds.join(", ") || "\u2014"}`);
2411
- lines.push("");
2412
- const fileNodes = nodes.filter((n) => n.kind === "file");
2413
- const defsByFile = /* @__PURE__ */ new Map();
2414
- for (const n of nodes) {
2415
- if (n.kind === "file" || n.kind === "external") continue;
2416
- defsByFile.set(n.file, (defsByFile.get(n.file) ?? 0) + 1);
2417
- }
2418
- const topFiles = [...defsByFile.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, 15);
2419
- if (topFiles.length) {
2420
- lines.push("## Largest files (by definitions)");
2421
- lines.push("");
2422
- lines.push("| File | Definitions |");
2423
- lines.push("| --- | ---: |");
2424
- for (const [file, count] of topFiles) lines.push(`| \`${file}\` | ${count} |`);
2425
- lines.push("");
2426
- }
2427
- const topNodes = nodes.filter((n) => n.kind !== "file" && n.kind !== "external").sort((a, b) => b.importance - a.importance || a.qualifiedName.localeCompare(b.qualifiedName)).slice(0, 15);
2428
- if (topNodes.length) {
2429
- lines.push("## Most-connected definitions");
2430
- lines.push("");
2431
- lines.push("| Symbol | Kind | File | Importance |");
2432
- lines.push("| --- | --- | --- | ---: |");
2433
- for (const n of topNodes) {
2434
- lines.push(
2435
- `| \`${n.qualifiedName}\` | ${n.kind} | \`${n.file}\`:${n.span.start} | ${n.importance.toFixed(3)} |`
2436
- );
2437
- }
2438
- lines.push("");
2439
- }
2440
- const byKind = /* @__PURE__ */ new Map();
2441
- for (const e of edges) byKind.set(e.kind, (byKind.get(e.kind) ?? 0) + 1);
2442
- if (byKind.size) {
2443
- lines.push("## Edges by kind");
2444
- lines.push("");
2445
- lines.push("| Kind | Count |");
2446
- lines.push("| --- | ---: |");
2447
- for (const [kind, count] of [...byKind.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
2448
- lines.push(`| ${kind} | ${count} |`);
2449
- }
2450
- lines.push("");
2451
- }
2452
- lines.push("---");
2453
- lines.push("");
2454
- lines.push(`_Files: ${fileNodes.length} \xB7 Corpus: \`${provenance.corpusHash.slice(0, 16)}\u2026\`_`);
2455
- lines.push("");
2456
- return lines.join("\n");
2457
- }
2458
-
2459
- // src/engine/html.ts
2460
- function renderHtml(graph) {
2461
- const top = graph.nodes.filter((n) => n.kind !== "file" && n.kind !== "external").sort((a, b) => b.importance - a.importance || a.qualifiedName.localeCompare(b.qualifiedName)).slice(0, 50);
2462
- const rows = top.map(
2463
- (n) => `<tr><td><code>${esc(n.qualifiedName)}</code></td><td>${esc(n.kind)}</td><td><code>${esc(n.file)}</code>:${n.span.start}</td><td>${n.importance.toFixed(3)}</td></tr>`
2464
- ).join("\n");
2465
- const embedded = stableStringify(graph, 0);
2466
- return `<!doctype html>
2467
- <html lang="en">
2468
- <head>
2469
- <meta charset="utf-8">
2470
- <meta name="viewport" content="width=device-width, initial-scale=1">
2471
- <title>vg \xB7 code graph</title>
2472
- <style>
2473
- :root { color-scheme: light dark; }
2474
- body { font: 14px/1.5 system-ui, sans-serif; margin: 2rem auto; max-width: 60rem; padding: 0 1rem; }
2475
- h1 { font-size: 1.4rem; } h2 { font-size: 1.1rem; margin-top: 2rem; }
2476
- table { border-collapse: collapse; width: 100%; }
2477
- th, td { text-align: left; padding: 4px 8px; border-bottom: 1px solid #8884; }
2478
- td:last-child, th:last-child { text-align: right; }
2479
- .meta { color: #888; } code { font-size: 0.9em; }
2480
- </style>
2481
- </head>
2482
- <body>
2483
- <h1>vg \xB7 code graph</h1>
2484
- <p class="meta">${graph.meta.counts.nodes} nodes \xB7 ${graph.meta.counts.edges} edges \xB7
2485
- ${graph.meta.counts.areas} areas \xB7 ${esc(graph.meta.languages.join(", ") || "\u2014")} \xB7
2486
- clustering: ${esc(graph.meta.cluster)} \xB7 vg ${esc(graph.provenance.version)}</p>
2487
- <h2>Most-connected definitions</h2>
2488
- <table>
2489
- <thead><tr><th>Symbol</th><th>Kind</th><th>Location</th><th>Importance</th></tr></thead>
2490
- <tbody>
2491
- ${rows}
2492
- </tbody>
2493
- </table>
2494
- <p class="meta">Interactive WebGL view arrives in Phase 1. The full graph is embedded below for tooling.</p>
2495
- <script type="application/json" id="vg-graph">
2496
- ${embedded}
2497
- </script>
2498
- </body>
2499
- </html>
2500
- `;
2501
- }
2502
- function esc(s) {
2503
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2504
- }
2505
- function vibgrateDir(root) {
2506
- return path2.join(root, ".vibgrate");
2507
- }
2508
- var DEFAULT_GITIGNORE = [
2509
- "# Created once by vg \u2014 local graph artifacts stay out of git by default.",
2510
- "# Run `vg share` to commit the map for your team (it rewrites this file).",
2511
- "# vg never touches an existing .vibgrate/.gitignore: edit it (or leave it",
2512
- "# empty) to manage these ignores yourself.",
2513
- ".gitignore",
2514
- "cache/",
2515
- "graph.json",
2516
- "graph.html",
2517
- "GRAPH_REPORT.md",
2518
- "facts.jsonl",
2519
- "mcp-navigation.json"
2520
- ];
2521
- function ensureVibgrateGitignore(root) {
2522
- const file = path2.join(vibgrateDir(root), ".gitignore");
2523
- if (fs18.existsSync(file)) return;
2524
- fs18.mkdirSync(vibgrateDir(root), { recursive: true });
2525
- fs18.writeFileSync(file, `${DEFAULT_GITIGNORE.join("\n")}
2526
- `);
2527
- }
2528
- function defaultGraphPath(root) {
2529
- return path2.join(vibgrateDir(root), "graph.json");
2530
- }
2531
- function writeArtifacts(graph, options) {
2532
- const dir = vibgrateDir(options.root);
2533
- fs18.mkdirSync(dir, { recursive: true });
2534
- ensureVibgrateGitignore(options.root);
2535
- const graphPath = options.graphPath ?? defaultGraphPath(options.root);
2536
- fs18.mkdirSync(path2.dirname(graphPath), { recursive: true });
2537
- const tmp = `${graphPath}.${process.pid}.tmp`;
2538
- try {
2539
- fs18.writeFileSync(tmp, serializeGraph(graph));
2540
- fs18.renameSync(tmp, graphPath);
2541
- } catch (err) {
2542
- fs18.rmSync(tmp, { force: true });
2543
- throw err;
2544
- }
2545
- const written = { graphPath };
2546
- if (options.report !== false) {
2547
- const reportPath = path2.join(dir, "GRAPH_REPORT.md");
2548
- fs18.writeFileSync(reportPath, renderReport(graph));
2549
- written.reportPath = reportPath;
2550
- }
2551
- if (options.html !== false) {
2552
- const htmlPath = path2.join(dir, "graph.html");
2553
- fs18.writeFileSync(htmlPath, renderHtml(graph));
2554
- written.htmlPath = htmlPath;
2555
- }
2556
- if (graph.facts && graph.facts.length) {
2557
- const factsPath = path2.join(dir, "facts.jsonl");
2558
- fs18.writeFileSync(factsPath, graph.facts.map((f) => JSON.stringify(f)).join("\n") + "\n");
2559
- written.factsPath = factsPath;
2560
- }
2561
- return written;
2562
- }
2563
- function loadGraph(root, graphPath) {
2564
- const file = graphPath ?? defaultGraphPath(root);
2565
- if (!fs18.existsSync(file)) return null;
2566
- return parseGraph(fs18.readFileSync(file, "utf8"));
2567
- }
2568
-
2569
2054
  // src/engine/verify.ts
2570
2055
  var PINNED = "1970-01-01T00:00:00.000Z";
2571
2056
  async function verifyDeterminism(opts) {
@@ -2631,21 +2116,21 @@ function usageError(message) {
2631
2116
  }
2632
2117
  var SNAPSHOT_VERSION = "vg-freshness/1";
2633
2118
  function snapshotPath(root) {
2634
- return path2.join(cacheDir(root), "freshness.json");
2119
+ return path17.join(cacheDir(root), "freshness.json");
2635
2120
  }
2636
2121
  function writeSnapshot(root, corpusHash, fileStats, scope = {}) {
2637
2122
  const files = {};
2638
2123
  for (const f of fileStats) files[f.rel] = { size: f.size, mtimeMs: f.mtimeMs, hash: f.hash };
2639
2124
  const snapshot = { version: SNAPSHOT_VERSION, corpusHash, scope: pruneScope(scope), files };
2640
2125
  try {
2641
- fs18.mkdirSync(cacheDir(root), { recursive: true });
2642
- fs18.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2126
+ fs15.mkdirSync(cacheDir(root), { recursive: true });
2127
+ fs15.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2643
2128
  } catch {
2644
2129
  }
2645
2130
  }
2646
2131
  function loadSnapshot(root) {
2647
2132
  try {
2648
- const loaded = JSON.parse(fs18.readFileSync(snapshotPath(root), "utf8"));
2133
+ const loaded = JSON.parse(fs15.readFileSync(snapshotPath(root), "utf8"));
2649
2134
  if (loaded.version === SNAPSHOT_VERSION && loaded.files && typeof loaded.corpusHash === "string") {
2650
2135
  return { ...loaded, scope: loaded.scope ?? {} };
2651
2136
  }
@@ -2681,7 +2166,7 @@ function probeFreshness(root) {
2681
2166
  }
2682
2167
  let stat;
2683
2168
  try {
2684
- stat = fs18.statSync(file.abs);
2169
+ stat = fs15.statSync(file.abs);
2685
2170
  } catch {
2686
2171
  drift.removed.push(file.rel);
2687
2172
  continue;
@@ -2689,7 +2174,7 @@ function probeFreshness(root) {
2689
2174
  if (stat.size === entry.size && stat.mtimeMs === entry.mtimeMs) continue;
2690
2175
  let hash = "";
2691
2176
  try {
2692
- hash = hashBytes(fs18.readFileSync(file.abs));
2177
+ hash = hashBytes(fs15.readFileSync(file.abs));
2693
2178
  } catch {
2694
2179
  drift.removed.push(file.rel);
2695
2180
  continue;
@@ -2706,7 +2191,7 @@ function probeFreshness(root) {
2706
2191
  }
2707
2192
  if (absorbed && !hasDrift(drift)) {
2708
2193
  try {
2709
- fs18.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2194
+ fs15.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2710
2195
  } catch {
2711
2196
  }
2712
2197
  }
@@ -2787,10 +2272,10 @@ function testsToRun(graph, rootId, depth = 4) {
2787
2272
  };
2788
2273
  }
2789
2274
  function detectRunner(root, lang) {
2790
- const pkgPath = path2.join(root, "package.json");
2791
- if (fs18.existsSync(pkgPath)) {
2275
+ const pkgPath = path17.join(root, "package.json");
2276
+ if (fs15.existsSync(pkgPath)) {
2792
2277
  try {
2793
- const pkg = JSON.parse(fs18.readFileSync(pkgPath, "utf8"));
2278
+ const pkg = JSON.parse(fs15.readFileSync(pkgPath, "utf8"));
2794
2279
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
2795
2280
  if (deps.vitest) return { name: "vitest", command: (f) => `npx vitest run ${f.join(" ")}`.trim() };
2796
2281
  if (deps.jest) return { name: "jest", command: (f) => `npx jest ${f.join(" ")}`.trim() };
@@ -2798,10 +2283,10 @@ function detectRunner(root, lang) {
2798
2283
  } catch {
2799
2284
  }
2800
2285
  }
2801
- if (lang === "py" || fs18.existsSync(path2.join(root, "pyproject.toml")) || fs18.existsSync(path2.join(root, "pytest.ini"))) {
2286
+ if (lang === "py" || fs15.existsSync(path17.join(root, "pyproject.toml")) || fs15.existsSync(path17.join(root, "pytest.ini"))) {
2802
2287
  return { name: "pytest", command: (f) => `pytest ${f.join(" ")}`.trim() };
2803
2288
  }
2804
- if (lang === "go" || fs18.existsSync(path2.join(root, "go.mod"))) {
2289
+ if (lang === "go" || fs15.existsSync(path17.join(root, "go.mod"))) {
2805
2290
  return { name: "go test", command: () => "go test ./..." };
2806
2291
  }
2807
2292
  if (lang === "cs" || hasFile(root, /\.csproj$/) || hasFile(root, /\.sln$/)) {
@@ -2812,82 +2297,16 @@ function detectRunner(root, lang) {
2812
2297
  }
2813
2298
  function hasFile(root, re) {
2814
2299
  try {
2815
- return fs18.readdirSync(root).some((f) => re.test(f));
2300
+ return fs15.readdirSync(root).some((f) => re.test(f));
2816
2301
  } catch {
2817
2302
  return false;
2818
2303
  }
2819
2304
  }
2820
- function discoverModels(home = os.homedir()) {
2821
- return [...ollama(home), ...lmStudio(home), ...looseGguf(home)].sort(
2822
- (a, b) => a.runtime.localeCompare(b.runtime) || a.name.localeCompare(b.name)
2823
- );
2824
- }
2825
- function ollama(home) {
2826
- const base = path2.join(home, ".ollama", "models", "manifests");
2827
- const out = [];
2828
- walk(base, 4, (file) => {
2829
- const rel2 = path2.relative(base, file).split(path2.sep);
2830
- if (rel2.length >= 2) {
2831
- const tag = rel2[rel2.length - 1];
2832
- const model = rel2[rel2.length - 2];
2833
- out.push({ runtime: "ollama", name: `${model}:${tag}`, path: file });
2834
- }
2835
- });
2836
- return dedupe(out);
2837
- }
2838
- function lmStudio(home) {
2839
- const bases = [path2.join(home, ".lmstudio", "models"), path2.join(home, ".cache", "lm-studio", "models")];
2840
- const out = [];
2841
- for (const base of bases) {
2842
- walk(base, 5, (file) => {
2843
- if (!file.endsWith(".gguf")) return;
2844
- const rel2 = path2.relative(base, file).replace(/\\/g, "/");
2845
- out.push({ runtime: "lm-studio", name: rel2, path: file });
2846
- });
2847
- }
2848
- return dedupe(out);
2849
- }
2850
- function looseGguf(home) {
2851
- const bases = [path2.join(home, "models"), path2.join(home, ".cache", "huggingface")];
2852
- const out = [];
2853
- for (const base of bases) {
2854
- walk(base, 4, (file) => {
2855
- if (!file.endsWith(".gguf")) return;
2856
- out.push({ runtime: "gguf", name: path2.basename(file), path: file });
2857
- });
2858
- }
2859
- return dedupe(out);
2860
- }
2861
- function walk(dir, depth, onFile) {
2862
- if (depth < 0) return;
2863
- let entries;
2864
- try {
2865
- entries = fs18.readdirSync(dir, { withFileTypes: true });
2866
- } catch {
2867
- return;
2868
- }
2869
- for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
2870
- const abs = path2.join(dir, e.name);
2871
- if (e.isDirectory()) walk(abs, depth - 1, onFile);
2872
- else if (e.isFile()) onFile(abs);
2873
- }
2874
- }
2875
- function dedupe(models) {
2876
- const seen = /* @__PURE__ */ new Set();
2877
- const out = [];
2878
- for (const m of models) {
2879
- const key = `${m.runtime}:${m.name}`;
2880
- if (seen.has(key)) continue;
2881
- seen.add(key);
2882
- out.push(m);
2883
- }
2884
- return out;
2885
- }
2886
2305
  function findGitRoot(startDir = process.cwd()) {
2887
- let dir = path2.resolve(startDir);
2306
+ let dir = path17.resolve(startDir);
2888
2307
  for (; ; ) {
2889
- if (fs18.existsSync(path2.join(dir, ".git"))) return dir;
2890
- const parent = path2.dirname(dir);
2308
+ if (fs15.existsSync(path17.join(dir, ".git"))) return dir;
2309
+ const parent = path17.dirname(dir);
2891
2310
  if (parent === dir) return null;
2892
2311
  dir = parent;
2893
2312
  }
@@ -2898,19 +2317,19 @@ function normalizeEntry(line) {
2898
2317
  function ensureGitignored(entry, startDir = process.cwd()) {
2899
2318
  const root = findGitRoot(startDir);
2900
2319
  if (!root) return { status: "not-a-repo", entry };
2901
- const gitignorePath = path2.join(root, ".gitignore");
2320
+ const gitignorePath = path17.join(root, ".gitignore");
2902
2321
  const target = normalizeEntry(entry);
2903
2322
  let existing = "";
2904
2323
  let fileExisted = true;
2905
2324
  try {
2906
- existing = fs18.readFileSync(gitignorePath, "utf8");
2325
+ existing = fs15.readFileSync(gitignorePath, "utf8");
2907
2326
  } catch {
2908
2327
  fileExisted = false;
2909
2328
  }
2910
2329
  const alreadyPresent = existing.split(/\r?\n/).some((line) => normalizeEntry(line) === target);
2911
2330
  if (alreadyPresent) return { status: "present", entry, gitignorePath };
2912
2331
  const needsLeadingNewline = existing.length > 0 && !existing.endsWith("\n");
2913
- fs18.appendFileSync(gitignorePath, `${needsLeadingNewline ? "\n" : ""}${entry}
2332
+ fs15.appendFileSync(gitignorePath, `${needsLeadingNewline ? "\n" : ""}${entry}
2914
2333
  `, "utf8");
2915
2334
  return { status: fileExisted ? "added" : "created", entry, gitignorePath };
2916
2335
  }
@@ -3064,14 +2483,14 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
3064
2483
  console.log(chalk.dim("The secret must be registered on your Vibgrate ingest API."));
3065
2484
  }
3066
2485
  if (opts.write) {
3067
- const writePath = path2.resolve(opts.write);
2486
+ const writePath = path17.resolve(opts.write);
3068
2487
  await writeTextFile(writePath, dsn + "\n");
3069
2488
  console.log("");
3070
2489
  console.log(chalk.green("\u2714") + ` DSN written to ${opts.write}`);
3071
- const root = findGitRoot(path2.dirname(writePath));
3072
- const rel2 = root ? path2.relative(root, writePath).split(path2.sep).join("/") : "";
2490
+ const root = findGitRoot(path17.dirname(writePath));
2491
+ const rel2 = root ? path17.relative(root, writePath).split(path17.sep).join("/") : "";
3073
2492
  if (root && rel2 && !rel2.startsWith("..")) {
3074
- const res = ensureGitignored(rel2, path2.dirname(writePath));
2493
+ const res = ensureGitignored(rel2, path17.dirname(writePath));
3075
2494
  if (res.status === "created") {
3076
2495
  console.log(chalk.green("\u2714") + ` Created .gitignore and ignored ${rel2}`);
3077
2496
  } else if (res.status === "added") {
@@ -3089,30 +2508,30 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
3089
2508
  var STORE_DIRNAME = ".vibgrate";
3090
2509
  var STORE_FILENAME = "credentials.json";
3091
2510
  function homeCredentialsPath() {
3092
- return path2.join(os.homedir(), STORE_DIRNAME, STORE_FILENAME);
2511
+ return path17.join(os.homedir(), STORE_DIRNAME, STORE_FILENAME);
3093
2512
  }
3094
2513
  function projectCredentialsPath(cwd = process.cwd()) {
3095
2514
  const root = findGitRoot(cwd) ?? cwd;
3096
- return path2.join(root, STORE_DIRNAME, STORE_FILENAME);
2515
+ return path17.join(root, STORE_DIRNAME, STORE_FILENAME);
3097
2516
  }
3098
2517
  function credentialsPath(opts = {}) {
3099
2518
  const override = process.env.VIBGRATE_CREDENTIALS?.trim();
3100
- if (override) return path2.resolve(override);
2519
+ if (override) return path17.resolve(override);
3101
2520
  const local = projectCredentialsPath(opts.cwd);
3102
2521
  if (opts.local) return local;
3103
- if (fs18.existsSync(local)) return local;
2522
+ if (fs15.existsSync(local)) return local;
3104
2523
  return homeCredentialsPath();
3105
2524
  }
3106
2525
  function gitignoreEntryForCredentials(repoRoot, credsFile = credentialsPath()) {
3107
- const rel2 = path2.relative(repoRoot, credsFile);
3108
- if (rel2 && !rel2.startsWith("..") && !path2.isAbsolute(rel2)) {
3109
- return rel2.split(path2.sep).join("/");
2526
+ const rel2 = path17.relative(repoRoot, credsFile);
2527
+ if (rel2 && !rel2.startsWith("..") && !path17.isAbsolute(rel2)) {
2528
+ return rel2.split(path17.sep).join("/");
3110
2529
  }
3111
2530
  return `${STORE_DIRNAME}/${STORE_FILENAME}`;
3112
2531
  }
3113
2532
  function readStoredCredentials(opts = {}) {
3114
2533
  try {
3115
- const raw = fs18.readFileSync(credentialsPath(opts), "utf8");
2534
+ const raw = fs15.readFileSync(credentialsPath(opts), "utf8");
3116
2535
  const parsed = JSON.parse(raw);
3117
2536
  return parsed && typeof parsed.dsn === "string" && parsed.dsn ? parsed : null;
3118
2537
  } catch {
@@ -3121,16 +2540,16 @@ function readStoredCredentials(opts = {}) {
3121
2540
  }
3122
2541
  function writeStoredCredentials(creds, opts = {}) {
3123
2542
  const file = credentialsPath(opts);
3124
- fs18.mkdirSync(path2.dirname(file), { recursive: true });
3125
- fs18.writeFileSync(file, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf8", mode: 384 });
2543
+ fs15.mkdirSync(path17.dirname(file), { recursive: true });
2544
+ fs15.writeFileSync(file, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf8", mode: 384 });
3126
2545
  try {
3127
- fs18.chmodSync(file, 384);
2546
+ fs15.chmodSync(file, 384);
3128
2547
  } catch {
3129
2548
  }
3130
2549
  }
3131
2550
  function clearStoredCredentials(opts = {}) {
3132
2551
  try {
3133
- fs18.rmSync(credentialsPath(opts));
2552
+ fs15.rmSync(credentialsPath(opts));
3134
2553
  return true;
3135
2554
  } catch {
3136
2555
  return false;
@@ -3501,7 +2920,7 @@ var pushCommand = new Command("push").description("Push scan results to Vibgrate
3501
2920
  if (opts.strict) process.exit(1);
3502
2921
  return;
3503
2922
  }
3504
- const filePath = path2.resolve(opts.file);
2923
+ const filePath = path17.resolve(opts.file);
3505
2924
  if (!await pathExists(filePath)) {
3506
2925
  console.error(chalk.red(`Scan artifact not found: ${filePath}`));
3507
2926
  console.error(chalk.dim('Run "vibgrate scan" first.'));
@@ -3562,935 +2981,935 @@ var pushCommand = new Command("push").description("Push scan results to Vibgrate
3562
2981
  if (opts.strict) process.exit(1);
3563
2982
  }
3564
2983
  });
3565
- function readScanArtifact(root) {
3566
- try {
3567
- const file = path2.join(root, ".vibgrate", "scan_result.json");
3568
- return JSON.parse(fs18.readFileSync(file, "utf8"));
3569
- } catch {
3570
- return null;
2984
+ var MAX_FILE_BYTES = 1e6;
2985
+ var PREVIEW_CHARS = 120;
2986
+ var NUL = "\0";
2987
+ var PARALLEL_MIN_BYTES = 64e6;
2988
+ var PARALLEL_MIN_FILES = 2e3;
2989
+ var ROW_CAP = 1e4;
2990
+ async function scanCandidates(root, files, needle, opts) {
2991
+ const needleLower = needle.toLowerCase();
2992
+ if (!needleLower || files.length === 0) return { hits: [], total: 0, truncated: false };
2993
+ if (files.length < PARALLEL_MIN_FILES && !process.env.VG_PARALLEL_MIN_BYTES) {
2994
+ return opts.collectAll ? scanInline(root, files, needleLower, ROW_CAP, false, void 0) : scanInline(root, files, needleLower, opts.budget, true, void 0);
2995
+ }
2996
+ const { over, sizes } = measure(root, files);
2997
+ if (!over) {
2998
+ return opts.collectAll ? scanInline(root, files, needleLower, ROW_CAP, false, sizes) : scanInline(root, files, needleLower, opts.budget, true, sizes);
2999
+ }
3000
+ const out = await scanParallel(root, files, needleLower);
3001
+ if (!opts.collectAll && out.hits.length > opts.budget) {
3002
+ out.hits.length = opts.budget;
3003
+ out.truncated = true;
3571
3004
  }
3005
+ return out;
3572
3006
  }
3573
- function loadVulnerabilities(root) {
3574
- return readScanArtifact(root)?.extended?.vulnerabilities ?? null;
3007
+ function parallelMinBytes() {
3008
+ const v = Number(process.env.VG_PARALLEL_MIN_BYTES);
3009
+ return Number.isFinite(v) && v >= 0 ? v : PARALLEL_MIN_BYTES;
3575
3010
  }
3576
- function resolvePackageTarget(root, pkg) {
3577
- const artifact = readScanArtifact(root);
3578
- for (const project of artifact?.projects ?? []) {
3579
- const dep = project.dependencies?.find((d) => d.package === pkg);
3580
- if (dep) {
3581
- return {
3582
- ecosystem: projectTypeToVulnEcosystem(project.type) ?? "unknown",
3583
- currentVersion: dep.resolvedVersion,
3584
- latestVersion: dep.latestStable,
3585
- majorsBehind: dep.majorsBehind
3586
- };
3011
+ function measure(root, files) {
3012
+ const threshold = parallelMinBytes();
3013
+ const sizes = /* @__PURE__ */ new Map();
3014
+ let total = 0;
3015
+ for (const rel2 of files) {
3016
+ let size;
3017
+ try {
3018
+ size = fs15.statSync(path17.join(root, rel2)).size;
3019
+ } catch {
3020
+ continue;
3587
3021
  }
3022
+ sizes.set(rel2, size);
3023
+ if (size <= MAX_FILE_BYTES) total += size;
3024
+ if (total >= threshold) return { over: true, sizes };
3588
3025
  }
3589
- const rec = inventory(root).records.find((r) => r.name === pkg);
3590
- if (rec) {
3591
- const ecosystem = rec.ecosystem === "npm" ? "npm" : rec.ecosystem === "pypi" ? "pypi" : "unknown";
3592
- return { ecosystem, currentVersion: rec.installed ?? null, latestVersion: rec.latest ?? null, majorsBehind: null };
3026
+ return { over: false, sizes };
3027
+ }
3028
+ function matchLines(text, needleLower) {
3029
+ const low = text.toLowerCase();
3030
+ if (!low.includes(needleLower)) return [];
3031
+ const lowLines = low.split("\n");
3032
+ const rawLines = text.split("\n");
3033
+ const hits = [];
3034
+ for (let i = 0; i < lowLines.length; i++) {
3035
+ if (lowLines[i].includes(needleLower)) hits.push({ line: i + 1, preview: rawLines[i].trim().slice(0, PREVIEW_CHARS) });
3593
3036
  }
3594
- return { ecosystem: "unknown", currentVersion: null, latestVersion: null, majorsBehind: null };
3037
+ return hits;
3595
3038
  }
3596
- function openFixableAdvisories(root, pkg) {
3597
- const vulns = loadVulnerabilities(root);
3598
- const match = vulns?.packages.find((p) => p.package === pkg);
3599
- return (match?.advisories ?? []).filter((a) => a.fixedVersions.length).map((a) => a.id);
3039
+ function readCandidate(abs, knownSize) {
3040
+ try {
3041
+ const size = knownSize ?? fs15.statSync(abs).size;
3042
+ if (size > MAX_FILE_BYTES) return null;
3043
+ const text = fs15.readFileSync(abs, "utf8");
3044
+ return text.includes(NUL) ? null : text;
3045
+ } catch {
3046
+ return null;
3047
+ }
3600
3048
  }
3601
- function filterBySeverity(result, minSeverity) {
3602
- if (!minSeverity || minSeverity === "unknown") return result;
3603
- const floor = severityRank(minSeverity);
3604
- const packages = result.packages.map((p) => ({ ...p, advisories: p.advisories.filter((a) => severityRank(a.severity) >= floor) })).filter((p) => p.advisories.length > 0);
3605
- const totalAdvisories = packages.reduce((n, p) => n + p.advisories.length, 0);
3606
- return { ...result, packages, totalAdvisories };
3049
+ function scanInline(root, files, needleLower, cap, stopAtCap, sizes) {
3050
+ const hits = [];
3051
+ let total = 0;
3052
+ let truncated = false;
3053
+ for (const rel2 of files) {
3054
+ const text = readCandidate(path17.join(root, rel2), sizes?.get(rel2));
3055
+ if (text === null) continue;
3056
+ for (const h of matchLines(text, needleLower)) {
3057
+ total++;
3058
+ if (hits.length < cap) hits.push({ file: rel2, line: h.line, preview: h.preview });
3059
+ else {
3060
+ truncated = true;
3061
+ if (stopAtCap) return { hits, total, truncated };
3062
+ }
3063
+ }
3064
+ }
3065
+ return { hits, total, truncated };
3607
3066
  }
3608
-
3609
- // src/mcp/attribution.ts
3610
- var MCP_MAX_COMMITS = 300;
3611
- var ECOSYSTEM_MAP = {
3612
- npm: "npm",
3613
- pypi: "pypi",
3614
- go: "go",
3615
- rust: "cargo",
3616
- ruby: "rubygems",
3617
- php: "composer",
3618
- dotnet: "nuget",
3619
- dart: "pub"
3620
- // swift (no advisory ecosystem) and java (no resolved lockfile) have no timeline.
3621
- };
3622
- function brief(commit) {
3623
- return { sha: commit.shortSha, author: commit.authorName, date: commit.date, subject: commit.subject };
3067
+ async function scanParallel(root, files, needleLower) {
3068
+ const n = Math.min(files.length, Math.max(2, Math.min(8, os.cpus().length - 1)));
3069
+ const shards = Array.from({ length: n }, () => []);
3070
+ files.forEach((f, i) => shards[i % n].push(f));
3071
+ let results;
3072
+ try {
3073
+ results = await Promise.all(shards.map((shard) => runWorker(root, shard, needleLower)));
3074
+ } catch {
3075
+ return scanInline(root, files, needleLower, ROW_CAP, false, void 0);
3076
+ }
3077
+ const hits = [];
3078
+ let total = 0;
3079
+ let truncated = false;
3080
+ for (const r of results) {
3081
+ total += r.total;
3082
+ truncated = truncated || r.truncated;
3083
+ for (const h of r.hits) hits.push(h);
3084
+ }
3085
+ hits.sort((a, b) => a.file < b.file ? -1 : a.file > b.file ? 1 : a.line - b.line);
3086
+ if (hits.length > ROW_CAP) {
3087
+ hits.length = ROW_CAP;
3088
+ truncated = true;
3089
+ }
3090
+ return { hits, total, truncated };
3624
3091
  }
3625
- async function attributedInventory(root, opts = {}) {
3626
- const inv = inventory(root);
3627
- if (!opts.attribute) return { ...inv, attribution: "off" };
3628
- if (!await gitHistoryAvailable(root)) return { ...inv, attribution: "unavailable" };
3629
- const timelines = await buildVersionTimelines(root, { maxCommits: MCP_MAX_COMMITS });
3630
- if (!timelines) return { ...inv, attribution: "unavailable" };
3631
- const records = inv.records.map((r) => {
3632
- const ecosystem = ECOSYSTEM_MAP[r.ecosystem];
3633
- if (!ecosystem) return r;
3634
- const pt = findPackageTimeline(timelines, ecosystem, r.name);
3635
- if (!pt || !pt.changes.length) return r;
3636
- return {
3637
- ...r,
3638
- addedBy: brief(pt.changes[0].commit),
3639
- currentVersionBy: brief(pt.changes[pt.changes.length - 1].commit)
3640
- };
3092
+ var WORKER_SOURCE = [
3093
+ "const { parentPort, workerData } = require('worker_threads');",
3094
+ "const fs = require('fs');",
3095
+ "const path = require('path');",
3096
+ `const MAX = ${MAX_FILE_BYTES};`,
3097
+ `const PREVIEW = ${PREVIEW_CHARS};`,
3098
+ `const CAP = ${ROW_CAP};`,
3099
+ "const NUL = String.fromCharCode(0);",
3100
+ "const NL = String.fromCharCode(10);",
3101
+ "const { root, files, needleLower } = workerData;",
3102
+ "const hits = []; let total = 0, truncated = false;",
3103
+ "for (const rel of files) {",
3104
+ " const abs = path.join(root, rel);",
3105
+ " let text;",
3106
+ ' try { if (fs.statSync(abs).size > MAX) continue; text = fs.readFileSync(abs, "utf8"); } catch { continue; }',
3107
+ " if (text.includes(NUL)) continue;",
3108
+ " const low = text.toLowerCase();",
3109
+ " if (!low.includes(needleLower)) continue;",
3110
+ " const lowLines = low.split(NL); const rawLines = text.split(NL);",
3111
+ " for (let i = 0; i < lowLines.length; i++) {",
3112
+ " if (!lowLines[i].includes(needleLower)) continue;",
3113
+ " total++;",
3114
+ " if (hits.length < CAP) hits.push({ file: rel, line: i + 1, preview: rawLines[i].trim().slice(0, PREVIEW) });",
3115
+ " else truncated = true;",
3116
+ " }",
3117
+ "}",
3118
+ "parentPort.postMessage({ hits, total, truncated });"
3119
+ ].join("\n");
3120
+ function runWorker(root, files, needleLower) {
3121
+ return new Promise((resolve11, reject) => {
3122
+ const w = new Worker(WORKER_SOURCE, { eval: true, workerData: { root, files, needleLower } });
3123
+ let settled = false;
3124
+ w.once("message", (m) => {
3125
+ settled = true;
3126
+ resolve11(m);
3127
+ });
3128
+ w.once("error", reject);
3129
+ w.once("exit", (code) => {
3130
+ if (!settled && code !== 0) reject(new Error(`literal-scan worker exited with code ${code}`));
3131
+ });
3641
3132
  });
3642
- return { ...inv, records, attribution: "git" };
3643
- }
3644
- function countTokens(text) {
3645
- return encode(text).length;
3646
- }
3647
- function truncateToTokens(text, budget, marker = "\n\u2026(truncated)") {
3648
- if (!Number.isFinite(budget) || budget <= 0) return { text, truncated: false, tokens: countTokens(text) };
3649
- const tokens = encode(text);
3650
- if (tokens.length <= budget) return { text, truncated: false, tokens: tokens.length };
3651
- const markerTokens = encode(marker).length;
3652
- const keep = Math.max(0, budget - markerTokens);
3653
- let slice = decode(tokens.slice(0, keep));
3654
- const lastNl = slice.lastIndexOf("\n");
3655
- if (lastNl > 0) slice = slice.slice(0, lastNl);
3656
- const out = slice + marker;
3657
- return { text: out, truncated: true, tokens: countTokens(out) };
3658
3133
  }
3659
3134
 
3660
- // src/engine/selection-weights.ts
3661
- var DEFAULT_SELECTION_WEIGHTS = {
3662
- headingUsage: 4,
3663
- headingPreamble: -6,
3664
- hasCode: 3,
3665
- codeDensity: 0.5,
3666
- queryOverlap: 5,
3667
- symbolMatch: 2,
3668
- position: -0.1,
3669
- linkDensity: -3
3670
- };
3671
-
3672
- // src/engine/select.ts
3673
- var STOPWORDS = /* @__PURE__ */ new Set(["the", "a", "an", "to", "of", "in", "for", "and", "or", "is", "how", "do", "i", "use", "using", "with", "on", "my"]);
3674
- var USAGE_RE = /\b(usage|example|examples|quick ?start|getting started|api|guide|how to|reference|tutorial|basic)\b/i;
3675
- var PREAMBLE_RE = /\b(install|installation|license|licence|contributing|contributors?|sponsors?|table of contents|toc|changelog|funding|code of conduct|security|acknowledge)\b/i;
3676
- function splitSections(md) {
3677
- const out = [];
3678
- let cur = { heading: "", level: 0};
3679
- let buf = [];
3680
- const flush = () => {
3681
- const body = buf.join("\n").trim();
3682
- if (cur.heading || body) out.push({ heading: cur.heading, level: cur.level, body });
3683
- buf = [];
3684
- };
3685
- for (const line of md.split("\n")) {
3686
- const h = /^(#{1,6})\s+(.*\S)\s*$/.exec(line);
3687
- if (h) {
3688
- flush();
3689
- cur = { heading: h[2].trim(), level: h[1].length, body: "" };
3690
- } else {
3691
- buf.push(line);
3692
- }
3693
- }
3694
- flush();
3695
- return out;
3135
+ // src/engine/search.ts
3136
+ var IGNORE_DIRS = /* @__PURE__ */ new Set([
3137
+ ".git",
3138
+ ".vibgrate",
3139
+ "node_modules",
3140
+ "dist",
3141
+ "build",
3142
+ "out",
3143
+ "target",
3144
+ "vendor",
3145
+ "__pycache__",
3146
+ // .NET intermediate output and coverage reports: purely generated trees that a
3147
+ // C#/coverage-heavy repo pays for on every literal sweep. `bin` is deliberately
3148
+ // NOT here plenty of repos keep real scripts in bin/ (rails, node CLIs), and
3149
+ // completeness beats speed; the binary-extension skip below keeps a .NET bin/
3150
+ // cheap anyway.
3151
+ "obj",
3152
+ "coverage"
3153
+ ]);
3154
+ var MAX_FILES_SCANNED = 2e4;
3155
+ var BINARY_EXTS = /* @__PURE__ */ new Set([
3156
+ ".dll",
3157
+ ".pdb",
3158
+ ".exe",
3159
+ ".so",
3160
+ ".dylib",
3161
+ ".a",
3162
+ ".lib",
3163
+ ".o",
3164
+ ".class",
3165
+ ".jar",
3166
+ ".wasm",
3167
+ ".node",
3168
+ ".png",
3169
+ ".jpg",
3170
+ ".jpeg",
3171
+ ".gif",
3172
+ ".webp",
3173
+ ".ico",
3174
+ ".icns",
3175
+ ".bmp",
3176
+ ".zip",
3177
+ ".gz",
3178
+ ".tgz",
3179
+ ".bz2",
3180
+ ".xz",
3181
+ ".7z",
3182
+ ".rar",
3183
+ ".woff",
3184
+ ".woff2",
3185
+ ".ttf",
3186
+ ".otf",
3187
+ ".eot",
3188
+ ".mp3",
3189
+ ".mp4",
3190
+ ".mov",
3191
+ ".avi",
3192
+ ".pdf"
3193
+ ]);
3194
+ function hasBinaryExt(rel2) {
3195
+ return BINARY_EXTS.has(path17.extname(rel2).toLowerCase());
3696
3196
  }
3697
- function chunkBody(body) {
3698
- const chunks = [];
3699
- let cur = [];
3700
- let inFence = false;
3701
- const flush = () => {
3702
- const c = cur.join("\n").trim();
3703
- if (c) chunks.push(c);
3704
- cur = [];
3705
- };
3706
- for (const line of body.split("\n")) {
3707
- if (/^\s*```/.test(line)) inFence = !inFence;
3708
- if (!inFence && line.trim() === "") {
3709
- flush();
3710
- continue;
3711
- }
3712
- cur.push(line);
3197
+ function toPosix3(rel2) {
3198
+ return path17.sep === "\\" ? rel2.replaceAll("\\", "/") : rel2;
3199
+ }
3200
+ async function searchSymbols(graph, root, query, limit) {
3201
+ let q = query.trim();
3202
+ if (!q) return { matches: [], moreAvailable: false, hint: "query is required" };
3203
+ const quoted = /^(["'`])(.+)\1$/.exec(q);
3204
+ if (quoted && quoted[2].trim()) q = quoted[2].trim();
3205
+ const isPhrase = Boolean(quoted) || /\s/.test(q) || /[/\\]/.test(q) && !q.includes(":") && !/[*?]/.test(q);
3206
+ let nodes = findNodes(graph, q).filter((n) => n.kind !== "file");
3207
+ if (nodes.length === 0) {
3208
+ nodes = fileSymbolNodes(graph, q);
3713
3209
  }
3714
- flush();
3715
- return chunks;
3210
+ if (nodes.length === 0 && isPhrase) {
3211
+ nodes = reconstructedIdentifierNodes(graph, q);
3212
+ }
3213
+ if (nodes.length === 0) {
3214
+ const tokens = queryTokens(q);
3215
+ if (tokens.length >= 2) nodes = multiTokenNodes(graph, tokens);
3216
+ }
3217
+ const symbolCap = isPhrase ? Math.max(1, Math.floor(limit / 3)) : limit;
3218
+ const symbolHits = nodes.slice(0, symbolCap).map((n, i) => ({
3219
+ kind: n.kind,
3220
+ name: n.qualifiedName,
3221
+ file: n.file,
3222
+ line: n.span.start,
3223
+ score: Math.round((1 - i / Math.max(nodes.length, 1)) * 100) / 100
3224
+ }));
3225
+ const spare = limit - symbolHits.length;
3226
+ const textHits = [];
3227
+ let truncatedScan = false;
3228
+ let totalTextMatches;
3229
+ const skipScan = !isPhrase && hasExactSymbolMatch(nodes, q);
3230
+ if (spare > 0 && !skipScan) {
3231
+ const seen = new Set(symbolHits.map((h) => `${h.file}:${h.line}`));
3232
+ const scan = await scanFiles(root, q, spare, seen, textHits, isPhrase);
3233
+ truncatedScan = scan.truncated;
3234
+ if (isPhrase) totalTextMatches = scan.total;
3235
+ }
3236
+ const matches = [...symbolHits, ...textHits];
3237
+ if (matches.length === 0) {
3238
+ return {
3239
+ matches,
3240
+ moreAvailable: false,
3241
+ hint: "no symbol or text match \u2014 for meaning-level questions (symptoms, relationships, what-breaks-if) use query_graph"
3242
+ };
3243
+ }
3244
+ const literalTruncated = totalTextMatches !== void 0 && totalTextMatches > textHits.length;
3245
+ const moreAvailable = isPhrase ? literalTruncated || truncatedScan : nodes.length > symbolHits.length || truncatedScan;
3246
+ const result = { matches, moreAvailable };
3247
+ if (totalTextMatches !== void 0) result.totalTextMatches = totalTextMatches;
3248
+ if (literalTruncated) {
3249
+ result.hint = `showing ${textHits.length} of ${totalTextMatches}${truncatedScan ? "+" : ""} literal matches \u2014 raise limit or narrow the query to get them all`;
3250
+ }
3251
+ return result;
3716
3252
  }
3717
- function stripNoise(body) {
3718
- return body.split("\n").filter((l) => !/^\s*(?:!\[[^\]]*\]\([^)]*\)\s*)+$/.test(l)).join("\n").trim();
3253
+ function fileSymbolNodes(graph, q) {
3254
+ if (!/[/\\]/.test(q) && !/\.\w+$/.test(q)) return [];
3255
+ const norm = q.replaceAll("\\", "/").replace(/^\.\//, "");
3256
+ const inFile = graph.nodes.filter(
3257
+ (n) => n.kind !== "file" && (n.file === norm || n.file.endsWith(`/${norm}`))
3258
+ );
3259
+ return [...inFile].sort((a, b) => b.importance - a.importance || a.qualifiedName.localeCompare(b.qualifiedName));
3719
3260
  }
3720
- function tokenizeQuery(q) {
3721
- if (!q) return [];
3722
- return [...new Set(q.toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length > 1 && !STOPWORDS.has(w)))];
3261
+ function hasExactSymbolMatch(nodes, q) {
3262
+ if (nodes.length === 0) return false;
3263
+ if (/^(.*):(\d+)$/.test(q)) return true;
3264
+ const lower = q.toLowerCase();
3265
+ return nodes.some((n) => n.id === q || n.qualifiedName.toLowerCase() === lower || n.name.toLowerCase() === lower);
3723
3266
  }
3724
- function symbolsFromApi(api) {
3725
- if (!api) return [];
3726
- const out = /* @__PURE__ */ new Set();
3727
- const re = /\b(?:function|class|interface|type|const|enum|namespace)\s+([A-Za-z_$][\w$]*)/g;
3728
- let m;
3729
- while (m = re.exec(api)) out.add(m[1]);
3730
- return [...out];
3267
+ function queryTokens(q) {
3268
+ return [...new Set(q.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 2))];
3731
3269
  }
3732
- function scoreSection(s, idx, queryTerms, symbols, w) {
3733
- const text = `${s.heading}
3734
- ${s.body}`.toLowerCase();
3735
- let score = 0;
3736
- if (USAGE_RE.test(s.heading)) score += w.headingUsage;
3737
- if (PREAMBLE_RE.test(s.heading)) score += w.headingPreamble;
3738
- const codeBlocks = Math.floor((s.body.match(/```/g) ?? []).length / 2);
3739
- if (codeBlocks > 0) score += w.hasCode + Math.min(codeBlocks, 5) * w.codeDensity;
3740
- for (const t of queryTerms) if (text.includes(t)) score += w.queryOverlap;
3741
- for (const sym of symbols) if (sym.length > 2 && text.includes(sym.toLowerCase())) score += w.symbolMatch;
3742
- score += idx * w.position;
3743
- const links = (s.body.match(/\]\(|https?:\/\//g) ?? []).length;
3744
- const lineCount = Math.max(1, s.body.split("\n").filter((l) => l.trim()).length);
3745
- if (links / lineCount > 0.5) score += w.linkDensity;
3746
- return score;
3270
+ function reconstructedIdentifierNodes(graph, q) {
3271
+ const words = q.trim().split(/\s+/).filter(Boolean);
3272
+ if (words.length < 2) return [];
3273
+ const concat = findNodes(graph, words.join(""));
3274
+ if (concat.length) return concat.filter((n) => n.kind !== "file");
3275
+ const snake = findNodes(graph, words.join("_"));
3276
+ return snake.filter((n) => n.kind !== "file");
3747
3277
  }
3748
- var apiBlock = (api) => api ? `## API (types)
3749
-
3750
- \`\`\`ts
3751
- ${api}
3752
- \`\`\`` : "";
3753
- function selectForBudget(input) {
3754
- const w = input.weights ?? DEFAULT_SELECTION_WEIGHTS;
3755
- const api = apiBlock(input.apiSurface);
3756
- const readme = input.readme.trim();
3757
- if (!input.budget || !Number.isFinite(input.budget)) {
3758
- const full2 = [readme, api].filter(Boolean).join("\n\n");
3759
- return { text: full2, tokens: countTokens(full2), truncated: false };
3760
- }
3761
- const budget = input.budget;
3762
- const queryTerms = tokenizeQuery(input.query);
3763
- const symbols = symbolsFromApi(input.apiSurface);
3764
- const render = (s) => {
3765
- const body = stripNoise(s.body);
3766
- if (!body) return "";
3767
- return `${s.heading ? `## ${s.heading}
3768
- ` : ""}${body}`.trim();
3769
- };
3770
- const sections = splitSections(readme).filter((s) => !PREAMBLE_RE.test(s.heading));
3771
- const full = [api, ...sections.map(render)].filter(Boolean).join("\n\n").trim();
3772
- if (countTokens(full) <= budget) {
3773
- return { text: full, tokens: countTokens(full), truncated: false };
3774
- }
3775
- const units = [];
3776
- for (const s of sections) {
3777
- if (countTokens(s.body) > budget) {
3778
- chunkBody(s.body).forEach((p, i) => units.push({ heading: i === 0 ? s.heading : "", level: s.level, body: p }));
3779
- } else {
3780
- units.push(s);
3781
- }
3782
- }
3783
- const ranked = units.map((s, idx) => ({ s, idx, score: scoreSection(s, idx, queryTerms, symbols, w) })).sort((a, b) => b.score - a.score || a.idx - b.idx);
3784
- const out = [];
3785
- let used = 0;
3786
- const add3 = (chunk2) => {
3787
- const t = countTokens(chunk2);
3788
- if (used + t <= budget) {
3789
- out.push(chunk2);
3790
- used += t;
3791
- return true;
3792
- }
3793
- return false;
3794
- };
3795
- if (api && !add3(api) && used < budget) {
3796
- out.push(truncateToTokens(api, budget - used).text);
3797
- used = budget;
3798
- }
3799
- const picked = [];
3800
- for (const { s, idx } of ranked) {
3801
- const chunk2 = render(s);
3802
- if (!chunk2) continue;
3803
- if (used + countTokens(`
3804
- ${chunk2}`) <= budget) {
3805
- picked.push(idx);
3806
- used += countTokens(`
3807
- ${chunk2}`);
3278
+ function multiTokenNodes(graph, tokens) {
3279
+ const scored = [];
3280
+ for (const n of graph.nodes) {
3281
+ if (n.kind === "file") continue;
3282
+ const name = n.name.toLowerCase();
3283
+ const qn = n.qualifiedName.toLowerCase();
3284
+ let hits = 0;
3285
+ let exact = false;
3286
+ for (const t of tokens) {
3287
+ if (name === t || qn === t) {
3288
+ hits++;
3289
+ exact = true;
3290
+ } else if (qn.includes(t)) hits++;
3808
3291
  }
3292
+ if (exact || hits >= 2) scored.push({ node: n, hits, exact });
3809
3293
  }
3810
- picked.sort((a, b) => a - b);
3811
- for (const idx of picked) out.push(render(units[idx]));
3812
- if (used < budget - 20) {
3813
- const next = ranked.find((r) => !picked.includes(r.idx) && render(r.s));
3814
- if (next) {
3815
- const head = truncateToTokens(render(next.s), budget - used).text;
3816
- if (head.trim()) out.push(head);
3294
+ return scored.sort((a, b) => Number(b.exact) - Number(a.exact) || b.hits - a.hits || b.node.importance - a.node.importance || a.node.qualifiedName.localeCompare(b.node.qualifiedName)).map((e) => e.node);
3295
+ }
3296
+ async function scanFiles(root, needle, budget, seen, out, countAll) {
3297
+ const listing = listCandidateFiles(root, needle);
3298
+ const outcome = await scanCandidates(root, listing.files, needle, { collectAll: countAll, budget });
3299
+ let overlap = 0;
3300
+ for (const h of outcome.hits) {
3301
+ if (seen.has(`${h.file}:${h.line}`)) {
3302
+ overlap++;
3303
+ continue;
3817
3304
  }
3305
+ seen.add(`${h.file}:${h.line}`);
3306
+ if (out.length < budget) out.push({ kind: "text", file: h.file, line: h.line, preview: h.preview });
3818
3307
  }
3819
- const text = out.join("\n\n").trim();
3820
- return { text, tokens: countTokens(text), truncated: true };
3308
+ return { total: outcome.total - overlap, truncated: listing.truncated || outcome.truncated };
3821
3309
  }
3822
-
3823
- // src/engine/quality.ts
3824
- function assessDocQuality(content, opts = {}) {
3825
- const minTokens = opts.minTokens ?? 60;
3826
- const lc = content.toLowerCase();
3827
- const reasons = [];
3828
- let score = 0;
3829
- const hasCode = /```/.test(content) || /(^|\n)( {4}|\t)\S/.test(content);
3830
- if (hasCode) score += 2;
3831
- else reasons.push("no code example");
3832
- const tokens = countTokens(content);
3833
- if (tokens >= minTokens) score += 1;
3834
- else reasons.push(`thin (${tokens} tokens)`);
3835
- const bareName = opts.name?.toLowerCase().replace(/^@[^/]+\//, "");
3836
- if (bareName && bareName.length > 1) {
3837
- if (lc.includes(bareName)) score += 1;
3838
- else reasons.push("library name absent");
3310
+ function listCandidateFiles(root, needle) {
3311
+ return ripgrepCandidates(root, needle) ?? walkCandidatesCached(root);
3312
+ }
3313
+ var LISTING_TTL_MS = 2e3;
3314
+ var listingCache = /* @__PURE__ */ new Map();
3315
+ function listingTtlMs() {
3316
+ const v = Number(process.env.VG_LISTING_TTL_MS);
3317
+ return Number.isFinite(v) && v >= 0 ? v : LISTING_TTL_MS;
3318
+ }
3319
+ function walkCandidatesCached(root) {
3320
+ const ttl = listingTtlMs();
3321
+ if (ttl > 0) {
3322
+ const hit = listingCache.get(root);
3323
+ if (hit && Date.now() - hit.at < ttl) return hit.listing;
3839
3324
  }
3840
- const qt = tokenizeQuery(opts.query);
3841
- let onTopic = qt.length === 0;
3842
- if (qt.length) {
3843
- const hits = qt.filter((t) => lc.includes(t)).length;
3844
- if (hits) {
3845
- score += Math.min(hits, 2);
3846
- onTopic = true;
3847
- } else {
3848
- reasons.push("query terms absent");
3325
+ const listing = walkCandidates(root);
3326
+ if (ttl > 0) listingCache.set(root, { listing, at: Date.now() });
3327
+ return listing;
3328
+ }
3329
+ function walkCandidates(root) {
3330
+ const files = [];
3331
+ let truncated = false;
3332
+ const stack = [root];
3333
+ while (stack.length) {
3334
+ const dir = stack.pop();
3335
+ let entries;
3336
+ try {
3337
+ entries = fs15.readdirSync(dir, { withFileTypes: true });
3338
+ } catch {
3339
+ continue;
3340
+ }
3341
+ for (const e of entries) {
3342
+ if (e.name.startsWith(".") || IGNORE_DIRS.has(e.name)) continue;
3343
+ const abs = path17.join(dir, e.name);
3344
+ if (e.isDirectory()) {
3345
+ stack.push(abs);
3346
+ continue;
3347
+ }
3348
+ if (hasBinaryExt(e.name)) continue;
3349
+ if (files.length >= MAX_FILES_SCANNED) {
3350
+ truncated = true;
3351
+ continue;
3352
+ }
3353
+ files.push(toPosix3(path17.relative(root, abs)));
3849
3354
  }
3850
3355
  }
3851
- if (opts.symbols?.length) {
3852
- if (opts.symbols.some((s) => s.length > 2 && lc.includes(s.toLowerCase()))) score += 1;
3853
- else reasons.push("no API symbols present");
3356
+ files.sort();
3357
+ return { files, truncated };
3358
+ }
3359
+ var rgAvailable;
3360
+ function hasRipgrep() {
3361
+ if (process.env.VG_DISABLE_RIPGREP) return false;
3362
+ if (rgAvailable === void 0) {
3363
+ try {
3364
+ rgAvailable = spawnSync("rg", ["--version"], { stdio: "ignore", timeout: 3e3 }).status === 0;
3365
+ } catch {
3366
+ rgAvailable = false;
3367
+ }
3854
3368
  }
3855
- const sufficient = hasCode && tokens >= minTokens && onTopic;
3856
- return { score, sufficient, reasons };
3369
+ return rgAvailable;
3857
3370
  }
3858
-
3859
- // src/engine/hosted.ts
3860
- function hostedBase(opts = {}) {
3861
- const explicit = opts.base || process.env.VIBGRATE_LIB_HOST;
3862
- if (explicit) return explicit.replace(/\/+$/, "");
3863
- let host;
3864
- try {
3865
- host = resolveIngestHost(opts.region, opts.ingest);
3866
- } catch {
3867
- host = "us.ingest.vibgrate.com";
3868
- }
3869
- return `https://${host}`;
3371
+ function ripgrepCandidates(root, needle) {
3372
+ if (!hasRipgrep()) return null;
3373
+ const globs = [...IGNORE_DIRS].flatMap((d) => ["--glob", `!**/${d}/**`, "--glob", `!${d}/**`]);
3374
+ const res = spawnSync(
3375
+ "rg",
3376
+ ["--files-with-matches", "--fixed-strings", "--ignore-case", "--no-ignore", "--no-messages", ...globs, "--", needle, "."],
3377
+ { cwd: root, encoding: "utf8", timeout: 15e3, maxBuffer: 32 * 1024 * 1024 }
3378
+ );
3379
+ if (res.error || res.status !== 0 && res.status !== 1 || res.signal) return null;
3380
+ const files = parseRgFileList(res.stdout);
3381
+ if (files.length > MAX_FILES_SCANNED) return { files: files.slice(0, MAX_FILES_SCANNED), truncated: true };
3382
+ return { files, truncated: false };
3870
3383
  }
3871
- async function fetchHostedDocs(req, opts = {}) {
3872
- const f = opts.fetchImpl ?? globalThis.fetch;
3873
- if (!f) return null;
3874
- const url = `${hostedBase(opts)}/v1/lib/docs`;
3875
- const ctrl = new AbortController();
3876
- const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 4e3);
3877
- const headers = { "content-type": "application/json" };
3878
- if (opts.auth) {
3879
- headers["Authorization"] = `VibgrateDSN ${opts.auth.keyId}:${opts.auth.secret}`;
3880
- headers["X-Vibgrate-Timestamp"] = String(Date.now());
3384
+ function parseRgFileList(stdout, sep8 = path17.sep) {
3385
+ const seen = /* @__PURE__ */ new Set();
3386
+ for (const raw of stdout.split("\n")) {
3387
+ if (!raw) continue;
3388
+ const posix = sep8 === "\\" ? raw.replaceAll("\\", "/") : raw;
3389
+ const rel2 = posix.startsWith("./") ? posix.slice(2) : posix;
3390
+ if (!rel2) continue;
3391
+ const segs = rel2.split("/");
3392
+ if (segs.some((s) => s.startsWith(".") || IGNORE_DIRS.has(s))) continue;
3393
+ if (hasBinaryExt(rel2)) continue;
3394
+ seen.add(rel2);
3881
3395
  }
3396
+ return [...seen].sort();
3397
+ }
3398
+ function readScanArtifact(root) {
3882
3399
  try {
3883
- const res = await f(url, {
3884
- method: "POST",
3885
- headers,
3886
- body: JSON.stringify({
3887
- name: req.name,
3888
- targetId: req.targetId,
3889
- query: req.query,
3890
- verbosity: req.verbosity,
3891
- max_tokens: req.maxTokens
3892
- }),
3893
- signal: ctrl.signal
3894
- });
3895
- if (!res.ok) return null;
3896
- const data = await res.json();
3897
- const content = typeof data.content === "string" ? data.content : typeof data.docs === "string" ? data.docs : "";
3898
- if (!content.trim()) return null;
3899
- return {
3900
- content,
3901
- version: typeof data.version === "string" ? data.version : null,
3902
- source: "hosted",
3903
- metadata: data.metadata ?? void 0
3904
- };
3400
+ const file = path17.join(root, ".vibgrate", "scan_result.json");
3401
+ return JSON.parse(fs15.readFileSync(file, "utf8"));
3905
3402
  } catch {
3906
3403
  return null;
3907
- } finally {
3908
- clearTimeout(timer);
3909
3404
  }
3910
3405
  }
3911
- async function publishPrivateLibrary(req, opts = {}) {
3912
- const f = opts.fetchImpl ?? globalThis.fetch;
3913
- if (!f) return { ok: false, status: 0, message: "no network available" };
3914
- if (!opts.auth) return { ok: false, status: 401, message: "a DSN is required to publish a private library (run `vibgrate login` or set VIBGRATE_DSN)" };
3915
- const url = `${hostedBase(opts)}/v1/lib/private`;
3916
- const ctrl = new AbortController();
3917
- const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
3918
- try {
3919
- const res = await f(url, {
3920
- method: "POST",
3921
- headers: {
3922
- "content-type": "application/json",
3923
- Authorization: `VibgrateDSN ${opts.auth.keyId}:${opts.auth.secret}`,
3924
- "X-Vibgrate-Timestamp": String(Date.now())
3925
- },
3926
- body: JSON.stringify({
3927
- name: req.name,
3928
- version: req.version,
3929
- readme: req.readme,
3930
- dts: req.dts,
3931
- compliance: req.compliance,
3932
- language: req.language,
3933
- sourceUrl: req.sourceUrl
3934
- }),
3935
- signal: ctrl.signal
3936
- });
3937
- const data = await res.json().catch(() => ({}));
3938
- if (!res.ok) return { ok: false, status: res.status, error: typeof data.error === "string" ? data.error : void 0, message: typeof data.message === "string" ? data.message : void 0 };
3939
- return {
3940
- ok: true,
3941
- targetId: String(data.targetId ?? ""),
3942
- ecosystem: String(data.ecosystem ?? ""),
3943
- entities: Number(data.entities) || 0,
3944
- indexedTokens: Number(data.indexedTokens) || 0
3945
- };
3946
- } catch (err) {
3947
- return { ok: false, status: 0, message: err instanceof Error ? err.message : "request failed" };
3948
- } finally {
3949
- clearTimeout(timer);
3406
+ function loadVulnerabilities(root) {
3407
+ return readScanArtifact(root)?.extended?.vulnerabilities ?? null;
3408
+ }
3409
+ function resolvePackageTarget(root, pkg) {
3410
+ const artifact = readScanArtifact(root);
3411
+ for (const project of artifact?.projects ?? []) {
3412
+ const dep = project.dependencies?.find((d) => d.package === pkg);
3413
+ if (dep) {
3414
+ return {
3415
+ ecosystem: projectTypeToVulnEcosystem(project.type) ?? "unknown",
3416
+ currentVersion: dep.resolvedVersion,
3417
+ latestVersion: dep.latestStable,
3418
+ majorsBehind: dep.majorsBehind
3419
+ };
3420
+ }
3421
+ }
3422
+ const rec = inventory(root).records.find((r) => r.name === pkg);
3423
+ if (rec) {
3424
+ const ecosystem = rec.ecosystem === "npm" ? "npm" : rec.ecosystem === "pypi" ? "pypi" : "unknown";
3425
+ return { ecosystem, currentVersion: rec.installed ?? null, latestVersion: rec.latest ?? null, majorsBehind: null };
3950
3426
  }
3427
+ return { ecosystem: "unknown", currentVersion: null, latestVersion: null, majorsBehind: null };
3428
+ }
3429
+ function openFixableAdvisories(root, pkg) {
3430
+ const vulns = loadVulnerabilities(root);
3431
+ const match = vulns?.packages.find((p) => p.package === pkg);
3432
+ return (match?.advisories ?? []).filter((a) => a.fixedVersions.length).map((a) => a.id);
3433
+ }
3434
+ function filterBySeverity(result, minSeverity) {
3435
+ if (!minSeverity || minSeverity === "unknown") return result;
3436
+ const floor = severityRank(minSeverity);
3437
+ const packages = result.packages.map((p) => ({ ...p, advisories: p.advisories.filter((a) => severityRank(a.severity) >= floor) })).filter((p) => p.advisories.length > 0);
3438
+ const totalAdvisories = packages.reduce((n, p) => n + p.advisories.length, 0);
3439
+ return { ...result, packages, totalAdvisories };
3951
3440
  }
3952
3441
 
3953
- // src/engine/hosted-cache.ts
3954
- var CACHE_VERSION = "vg-hosted-docs/1";
3955
- var HOSTED_DOCS_TTL_MS = 24 * 60 * 60 * 1e3;
3956
- var MAX_ENTRIES = 64;
3957
- function cachePath(root) {
3958
- return path2.join(cacheDir(root), "hosted-docs.json");
3442
+ // src/mcp/attribution.ts
3443
+ var MCP_MAX_COMMITS = 300;
3444
+ var ECOSYSTEM_MAP = {
3445
+ npm: "npm",
3446
+ pypi: "pypi",
3447
+ go: "go",
3448
+ rust: "cargo",
3449
+ ruby: "rubygems",
3450
+ php: "composer",
3451
+ dotnet: "nuget",
3452
+ dart: "pub"
3453
+ // swift (no advisory ecosystem) and java (no resolved lockfile) have no timeline.
3454
+ };
3455
+ function brief(commit) {
3456
+ return { sha: commit.shortSha, author: commit.authorName, date: commit.date, subject: commit.subject };
3959
3457
  }
3960
- function hostedDocsCacheKey(req, opts = {}) {
3961
- const material = JSON.stringify([
3962
- req.name ?? "",
3963
- req.targetId ?? "",
3964
- req.query ?? "",
3965
- req.verbosity ?? "",
3966
- req.maxTokens ?? 0,
3967
- hostedBase(opts),
3968
- opts.auth?.keyId ?? "anon"
3969
- ]);
3970
- return createHash("sha256").update(material).digest("hex");
3458
+ async function attributedInventory(root, opts = {}) {
3459
+ const inv = inventory(root);
3460
+ if (!opts.attribute) return { ...inv, attribution: "off" };
3461
+ if (!await gitHistoryAvailable(root)) return { ...inv, attribution: "unavailable" };
3462
+ const timelines = await buildVersionTimelines(root, { maxCommits: MCP_MAX_COMMITS });
3463
+ if (!timelines) return { ...inv, attribution: "unavailable" };
3464
+ const records = inv.records.map((r) => {
3465
+ const ecosystem = ECOSYSTEM_MAP[r.ecosystem];
3466
+ if (!ecosystem) return r;
3467
+ const pt = findPackageTimeline(timelines, ecosystem, r.name);
3468
+ if (!pt || !pt.changes.length) return r;
3469
+ return {
3470
+ ...r,
3471
+ addedBy: brief(pt.changes[0].commit),
3472
+ currentVersionBy: brief(pt.changes[pt.changes.length - 1].commit)
3473
+ };
3474
+ });
3475
+ return { ...inv, records, attribution: "git" };
3971
3476
  }
3972
- function loadFile(root) {
3973
- try {
3974
- const parsed = JSON.parse(fs18.readFileSync(cachePath(root), "utf8"));
3975
- if (parsed && parsed.version === CACHE_VERSION && parsed.entries && typeof parsed.entries === "object") return parsed;
3976
- } catch {
3977
- }
3978
- return { version: CACHE_VERSION, entries: {} };
3477
+ function countTokens(text) {
3478
+ return encode(text).length;
3979
3479
  }
3980
- function saveFile(root, file) {
3981
- try {
3982
- const keys = Object.keys(file.entries);
3983
- if (keys.length > MAX_ENTRIES) {
3984
- for (const k of keys.sort((a, b) => file.entries[a].at - file.entries[b].at).slice(0, keys.length - MAX_ENTRIES)) {
3985
- delete file.entries[k];
3986
- }
3987
- }
3988
- fs18.mkdirSync(cacheDir(root), { recursive: true });
3989
- fs18.writeFileSync(cachePath(root), JSON.stringify(file));
3990
- } catch {
3991
- }
3480
+ function truncateToTokens(text, budget, marker = "\n\u2026(truncated)") {
3481
+ if (!Number.isFinite(budget) || budget <= 0) return { text, truncated: false, tokens: countTokens(text) };
3482
+ const tokens = encode(text);
3483
+ if (tokens.length <= budget) return { text, truncated: false, tokens: tokens.length };
3484
+ const markerTokens = encode(marker).length;
3485
+ const keep = Math.max(0, budget - markerTokens);
3486
+ let slice = decode(tokens.slice(0, keep));
3487
+ const lastNl = slice.lastIndexOf("\n");
3488
+ if (lastNl > 0) slice = slice.slice(0, lastNl);
3489
+ const out = slice + marker;
3490
+ return { text: out, truncated: true, tokens: countTokens(out) };
3992
3491
  }
3993
- async function fetchHostedDocsCached(root, req, opts = {}) {
3994
- const now = opts.now ?? Date.now;
3995
- const ttl = opts.ttlMs ?? HOSTED_DOCS_TTL_MS;
3996
- const key = hostedDocsCacheKey(req, opts);
3997
- const file = loadFile(root);
3998
- if (!opts.bypass) {
3999
- const hit = file.entries[key];
4000
- if (hit && now() - hit.at < ttl && hit.result?.content) {
4001
- return { ...hit.result, metadata: { ...hit.result.metadata ?? {}, cached: true } };
3492
+
3493
+ // src/engine/selection-weights.ts
3494
+ var DEFAULT_SELECTION_WEIGHTS = {
3495
+ headingUsage: 4,
3496
+ headingPreamble: -6,
3497
+ hasCode: 3,
3498
+ codeDensity: 0.5,
3499
+ queryOverlap: 5,
3500
+ symbolMatch: 2,
3501
+ position: -0.1,
3502
+ linkDensity: -3
3503
+ };
3504
+
3505
+ // src/engine/select.ts
3506
+ var STOPWORDS = /* @__PURE__ */ new Set(["the", "a", "an", "to", "of", "in", "for", "and", "or", "is", "how", "do", "i", "use", "using", "with", "on", "my"]);
3507
+ var USAGE_RE = /\b(usage|example|examples|quick ?start|getting started|api|guide|how to|reference|tutorial|basic)\b/i;
3508
+ var PREAMBLE_RE = /\b(install|installation|license|licence|contributing|contributors?|sponsors?|table of contents|toc|changelog|funding|code of conduct|security|acknowledge)\b/i;
3509
+ function splitSections(md) {
3510
+ const out = [];
3511
+ let cur = { heading: "", level: 0};
3512
+ let buf = [];
3513
+ const flush = () => {
3514
+ const body = buf.join("\n").trim();
3515
+ if (cur.heading || body) out.push({ heading: cur.heading, level: cur.level, body });
3516
+ buf = [];
3517
+ };
3518
+ for (const line of md.split("\n")) {
3519
+ const h = /^(#{1,6})\s+(.*\S)\s*$/.exec(line);
3520
+ if (h) {
3521
+ flush();
3522
+ cur = { heading: h[2].trim(), level: h[1].length, body: "" };
3523
+ } else {
3524
+ buf.push(line);
4002
3525
  }
4003
3526
  }
4004
- const result = await fetchHostedDocs(req, opts);
4005
- if (result) {
4006
- file.entries[key] = { at: now(), result };
4007
- saveFile(root, file);
4008
- }
4009
- return result;
4010
- }
4011
-
4012
- // src/mcp/response.ts
4013
- var MAX_RESULT_TOKENS = 25e3;
4014
- function renderToolResult(result, maxTokens = MAX_RESULT_TOKENS) {
4015
- return toolResult(clampToBudget(compactResult(result), maxTokens));
3527
+ flush();
3528
+ return out;
4016
3529
  }
4017
- function toolResult(result) {
4018
- return {
4019
- content: [{ type: "text", text: JSON.stringify(result) }],
4020
- structuredContent: asStructured(result)
3530
+ function chunkBody(body) {
3531
+ const chunks = [];
3532
+ let cur = [];
3533
+ let inFence = false;
3534
+ const flush = () => {
3535
+ const c = cur.join("\n").trim();
3536
+ if (c) chunks.push(c);
3537
+ cur = [];
4021
3538
  };
4022
- }
4023
- function compactResult(value) {
4024
- return prune(value);
4025
- }
4026
- function prune(v) {
4027
- if (Array.isArray(v)) return v.map(prune);
4028
- if (v && typeof v === "object") {
4029
- const out = {};
4030
- for (const [k, raw] of Object.entries(v)) {
4031
- const pv = prune(raw);
4032
- if (!isEmpty(pv)) out[k] = pv;
3539
+ for (const line of body.split("\n")) {
3540
+ if (/^\s*```/.test(line)) inFence = !inFence;
3541
+ if (!inFence && line.trim() === "") {
3542
+ flush();
3543
+ continue;
4033
3544
  }
4034
- return out;
3545
+ cur.push(line);
4035
3546
  }
4036
- return v;
3547
+ flush();
3548
+ return chunks;
4037
3549
  }
4038
- function isEmpty(v) {
4039
- if (v === null || v === void 0 || v === "") return true;
4040
- if (Array.isArray(v)) return v.length === 0;
4041
- if (typeof v === "object") return Object.keys(v).length === 0;
4042
- return false;
3550
+ function stripNoise(body) {
3551
+ return body.split("\n").filter((l) => !/^\s*(?:!\[[^\]]*\]\([^)]*\)\s*)+$/.test(l)).join("\n").trim();
4043
3552
  }
4044
- function clampToBudget(value, maxTokens = MAX_RESULT_TOKENS) {
4045
- if (!value || typeof value !== "object" || Array.isArray(value)) return value;
4046
- if (tokensOf(value) <= maxTokens) return value;
4047
- const obj2 = { ...value };
4048
- const truncated = {};
4049
- for (let guard = 0; guard < 64 && tokensOf(obj2) > maxTokens; guard++) {
4050
- const arrays = Object.entries(obj2).filter(
4051
- (e) => Array.isArray(e[1]) && e[1].length > 0
4052
- );
4053
- if (arrays.length === 0) break;
4054
- arrays.sort((a, b) => tokensOf(b[1]) - tokensOf(a[1]));
4055
- const [field, arr] = arrays[0];
4056
- const total = truncated[field]?.total ?? arr.length;
4057
- const shown = Math.max(1, Math.floor(arr.length / 2));
4058
- obj2[field] = arr.slice(0, shown);
4059
- truncated[field] = { shown, total };
4060
- }
4061
- if (Object.keys(truncated).length) obj2._truncated = truncated;
4062
- return obj2;
3553
+ function tokenizeQuery(q) {
3554
+ if (!q) return [];
3555
+ return [...new Set(q.toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length > 1 && !STOPWORDS.has(w)))];
4063
3556
  }
4064
- function tokensOf(v) {
4065
- return countTokens(JSON.stringify(v));
3557
+ function symbolsFromApi(api) {
3558
+ if (!api) return [];
3559
+ const out = /* @__PURE__ */ new Set();
3560
+ const re = /\b(?:function|class|interface|type|const|enum|namespace)\s+([A-Za-z_$][\w$]*)/g;
3561
+ let m;
3562
+ while (m = re.exec(api)) out.add(m[1]);
3563
+ return [...out];
4066
3564
  }
4067
- function asStructured(result) {
4068
- if (result && typeof result === "object" && !Array.isArray(result)) {
4069
- return result;
4070
- }
4071
- return { result };
3565
+ function scoreSection(s, idx, queryTerms, symbols, w) {
3566
+ const text = `${s.heading}
3567
+ ${s.body}`.toLowerCase();
3568
+ let score = 0;
3569
+ if (USAGE_RE.test(s.heading)) score += w.headingUsage;
3570
+ if (PREAMBLE_RE.test(s.heading)) score += w.headingPreamble;
3571
+ const codeBlocks = Math.floor((s.body.match(/```/g) ?? []).length / 2);
3572
+ if (codeBlocks > 0) score += w.hasCode + Math.min(codeBlocks, 5) * w.codeDensity;
3573
+ for (const t of queryTerms) if (text.includes(t)) score += w.queryOverlap;
3574
+ for (const sym of symbols) if (sym.length > 2 && text.includes(sym.toLowerCase())) score += w.symbolMatch;
3575
+ score += idx * w.position;
3576
+ const links = (s.body.match(/\]\(|https?:\/\//g) ?? []).length;
3577
+ const lineCount = Math.max(1, s.body.split("\n").filter((l) => l.trim()).length);
3578
+ if (links / lineCount > 0.5) score += w.linkDensity;
3579
+ return score;
4072
3580
  }
4073
- function boundList(items, max) {
4074
- if (!Number.isFinite(max) || max < 0 || items.length <= max) {
4075
- return { items, total: items.length };
3581
+ var apiBlock = (api) => api ? `## API (types)
3582
+
3583
+ \`\`\`ts
3584
+ ${api}
3585
+ \`\`\`` : "";
3586
+ function selectForBudget(input) {
3587
+ const w = input.weights ?? DEFAULT_SELECTION_WEIGHTS;
3588
+ const api = apiBlock(input.apiSurface);
3589
+ const readme = input.readme.trim();
3590
+ if (!input.budget || !Number.isFinite(input.budget)) {
3591
+ const full2 = [readme, api].filter(Boolean).join("\n\n");
3592
+ return { text: full2, tokens: countTokens(full2), truncated: false };
4076
3593
  }
4077
- return { items: items.slice(0, max), total: items.length };
4078
- }
4079
- var NODE_EDGE_CAP = 50;
4080
- var MAX_FILE_BYTES = 1e6;
4081
- var PREVIEW_CHARS = 120;
4082
- var NUL = "\0";
4083
- var PARALLEL_MIN_BYTES = 64e6;
4084
- var PARALLEL_MIN_FILES = 2e3;
4085
- var ROW_CAP = 1e4;
4086
- async function scanCandidates(root, files, needle, opts) {
4087
- const needleLower = needle.toLowerCase();
4088
- if (!needleLower || files.length === 0) return { hits: [], total: 0, truncated: false };
4089
- if (files.length < PARALLEL_MIN_FILES && !process.env.VG_PARALLEL_MIN_BYTES) {
4090
- return opts.collectAll ? scanInline(root, files, needleLower, ROW_CAP, false, void 0) : scanInline(root, files, needleLower, opts.budget, true, void 0);
3594
+ const budget = input.budget;
3595
+ const queryTerms = tokenizeQuery(input.query);
3596
+ const symbols = symbolsFromApi(input.apiSurface);
3597
+ const render = (s) => {
3598
+ const body = stripNoise(s.body);
3599
+ if (!body) return "";
3600
+ return `${s.heading ? `## ${s.heading}
3601
+ ` : ""}${body}`.trim();
3602
+ };
3603
+ const sections = splitSections(readme).filter((s) => !PREAMBLE_RE.test(s.heading));
3604
+ const full = [api, ...sections.map(render)].filter(Boolean).join("\n\n").trim();
3605
+ if (countTokens(full) <= budget) {
3606
+ return { text: full, tokens: countTokens(full), truncated: false };
4091
3607
  }
4092
- const { over, sizes } = measure(root, files);
4093
- if (!over) {
4094
- return opts.collectAll ? scanInline(root, files, needleLower, ROW_CAP, false, sizes) : scanInline(root, files, needleLower, opts.budget, true, sizes);
3608
+ const units = [];
3609
+ for (const s of sections) {
3610
+ if (countTokens(s.body) > budget) {
3611
+ chunkBody(s.body).forEach((p, i) => units.push({ heading: i === 0 ? s.heading : "", level: s.level, body: p }));
3612
+ } else {
3613
+ units.push(s);
3614
+ }
4095
3615
  }
4096
- const out = await scanParallel(root, files, needleLower);
4097
- if (!opts.collectAll && out.hits.length > opts.budget) {
4098
- out.hits.length = opts.budget;
4099
- out.truncated = true;
3616
+ const ranked = units.map((s, idx) => ({ s, idx, score: scoreSection(s, idx, queryTerms, symbols, w) })).sort((a, b) => b.score - a.score || a.idx - b.idx);
3617
+ const out = [];
3618
+ let used = 0;
3619
+ const add3 = (chunk2) => {
3620
+ const t = countTokens(chunk2);
3621
+ if (used + t <= budget) {
3622
+ out.push(chunk2);
3623
+ used += t;
3624
+ return true;
3625
+ }
3626
+ return false;
3627
+ };
3628
+ if (api && !add3(api) && used < budget) {
3629
+ out.push(truncateToTokens(api, budget - used).text);
3630
+ used = budget;
4100
3631
  }
4101
- return out;
4102
- }
4103
- function parallelMinBytes() {
4104
- const v = Number(process.env.VG_PARALLEL_MIN_BYTES);
4105
- return Number.isFinite(v) && v >= 0 ? v : PARALLEL_MIN_BYTES;
4106
- }
4107
- function measure(root, files) {
4108
- const threshold = parallelMinBytes();
4109
- const sizes = /* @__PURE__ */ new Map();
4110
- let total = 0;
4111
- for (const rel2 of files) {
4112
- let size;
4113
- try {
4114
- size = fs18.statSync(path2.join(root, rel2)).size;
4115
- } catch {
4116
- continue;
3632
+ const picked = [];
3633
+ for (const { s, idx } of ranked) {
3634
+ const chunk2 = render(s);
3635
+ if (!chunk2) continue;
3636
+ if (used + countTokens(`
3637
+ ${chunk2}`) <= budget) {
3638
+ picked.push(idx);
3639
+ used += countTokens(`
3640
+ ${chunk2}`);
4117
3641
  }
4118
- sizes.set(rel2, size);
4119
- if (size <= MAX_FILE_BYTES) total += size;
4120
- if (total >= threshold) return { over: true, sizes };
4121
3642
  }
4122
- return { over: false, sizes };
3643
+ picked.sort((a, b) => a - b);
3644
+ for (const idx of picked) out.push(render(units[idx]));
3645
+ if (used < budget - 20) {
3646
+ const next = ranked.find((r) => !picked.includes(r.idx) && render(r.s));
3647
+ if (next) {
3648
+ const head = truncateToTokens(render(next.s), budget - used).text;
3649
+ if (head.trim()) out.push(head);
3650
+ }
3651
+ }
3652
+ const text = out.join("\n\n").trim();
3653
+ return { text, tokens: countTokens(text), truncated: true };
4123
3654
  }
4124
- function matchLines(text, needleLower) {
4125
- const low = text.toLowerCase();
4126
- if (!low.includes(needleLower)) return [];
4127
- const lowLines = low.split("\n");
4128
- const rawLines = text.split("\n");
4129
- const hits = [];
4130
- for (let i = 0; i < lowLines.length; i++) {
4131
- if (lowLines[i].includes(needleLower)) hits.push({ line: i + 1, preview: rawLines[i].trim().slice(0, PREVIEW_CHARS) });
3655
+
3656
+ // src/engine/quality.ts
3657
+ function assessDocQuality(content, opts = {}) {
3658
+ const minTokens = opts.minTokens ?? 60;
3659
+ const lc = content.toLowerCase();
3660
+ const reasons = [];
3661
+ let score = 0;
3662
+ const hasCode = /```/.test(content) || /(^|\n)( {4}|\t)\S/.test(content);
3663
+ if (hasCode) score += 2;
3664
+ else reasons.push("no code example");
3665
+ const tokens = countTokens(content);
3666
+ if (tokens >= minTokens) score += 1;
3667
+ else reasons.push(`thin (${tokens} tokens)`);
3668
+ const bareName = opts.name?.toLowerCase().replace(/^@[^/]+\//, "");
3669
+ if (bareName && bareName.length > 1) {
3670
+ if (lc.includes(bareName)) score += 1;
3671
+ else reasons.push("library name absent");
3672
+ }
3673
+ const qt = tokenizeQuery(opts.query);
3674
+ let onTopic = qt.length === 0;
3675
+ if (qt.length) {
3676
+ const hits = qt.filter((t) => lc.includes(t)).length;
3677
+ if (hits) {
3678
+ score += Math.min(hits, 2);
3679
+ onTopic = true;
3680
+ } else {
3681
+ reasons.push("query terms absent");
3682
+ }
4132
3683
  }
4133
- return hits;
3684
+ if (opts.symbols?.length) {
3685
+ if (opts.symbols.some((s) => s.length > 2 && lc.includes(s.toLowerCase()))) score += 1;
3686
+ else reasons.push("no API symbols present");
3687
+ }
3688
+ const sufficient = hasCode && tokens >= minTokens && onTopic;
3689
+ return { score, sufficient, reasons };
4134
3690
  }
4135
- function readCandidate(abs, knownSize) {
3691
+
3692
+ // src/engine/hosted.ts
3693
+ function hostedBase(opts = {}) {
3694
+ const explicit = opts.base || process.env.VIBGRATE_LIB_HOST;
3695
+ if (explicit) return explicit.replace(/\/+$/, "");
3696
+ let host;
4136
3697
  try {
4137
- const size = knownSize ?? fs18.statSync(abs).size;
4138
- if (size > MAX_FILE_BYTES) return null;
4139
- const text = fs18.readFileSync(abs, "utf8");
4140
- return text.includes(NUL) ? null : text;
3698
+ host = resolveIngestHost(opts.region, opts.ingest);
4141
3699
  } catch {
4142
- return null;
3700
+ host = "us.ingest.vibgrate.com";
4143
3701
  }
3702
+ return `https://${host}`;
4144
3703
  }
4145
- function scanInline(root, files, needleLower, cap, stopAtCap, sizes) {
4146
- const hits = [];
4147
- let total = 0;
4148
- let truncated = false;
4149
- for (const rel2 of files) {
4150
- const text = readCandidate(path2.join(root, rel2), sizes?.get(rel2));
4151
- if (text === null) continue;
4152
- for (const h of matchLines(text, needleLower)) {
4153
- total++;
4154
- if (hits.length < cap) hits.push({ file: rel2, line: h.line, preview: h.preview });
4155
- else {
4156
- truncated = true;
4157
- if (stopAtCap) return { hits, total, truncated };
4158
- }
4159
- }
3704
+ async function fetchHostedDocs(req, opts = {}) {
3705
+ const f = opts.fetchImpl ?? globalThis.fetch;
3706
+ if (!f) return null;
3707
+ const url = `${hostedBase(opts)}/v1/lib/docs`;
3708
+ const ctrl = new AbortController();
3709
+ const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 4e3);
3710
+ const headers = { "content-type": "application/json" };
3711
+ if (opts.auth) {
3712
+ headers["Authorization"] = `VibgrateDSN ${opts.auth.keyId}:${opts.auth.secret}`;
3713
+ headers["X-Vibgrate-Timestamp"] = String(Date.now());
4160
3714
  }
4161
- return { hits, total, truncated };
4162
- }
4163
- async function scanParallel(root, files, needleLower) {
4164
- const n = Math.min(files.length, Math.max(2, Math.min(8, os.cpus().length - 1)));
4165
- const shards = Array.from({ length: n }, () => []);
4166
- files.forEach((f, i) => shards[i % n].push(f));
4167
- let results;
4168
3715
  try {
4169
- results = await Promise.all(shards.map((shard) => runWorker(root, shard, needleLower)));
3716
+ const res = await f(url, {
3717
+ method: "POST",
3718
+ headers,
3719
+ body: JSON.stringify({
3720
+ name: req.name,
3721
+ targetId: req.targetId,
3722
+ query: req.query,
3723
+ verbosity: req.verbosity,
3724
+ max_tokens: req.maxTokens
3725
+ }),
3726
+ signal: ctrl.signal
3727
+ });
3728
+ if (!res.ok) return null;
3729
+ const data = await res.json();
3730
+ const content = typeof data.content === "string" ? data.content : typeof data.docs === "string" ? data.docs : "";
3731
+ if (!content.trim()) return null;
3732
+ return {
3733
+ content,
3734
+ version: typeof data.version === "string" ? data.version : null,
3735
+ source: "hosted",
3736
+ metadata: data.metadata ?? void 0
3737
+ };
4170
3738
  } catch {
4171
- return scanInline(root, files, needleLower, ROW_CAP, false, void 0);
4172
- }
4173
- const hits = [];
4174
- let total = 0;
4175
- let truncated = false;
4176
- for (const r of results) {
4177
- total += r.total;
4178
- truncated = truncated || r.truncated;
4179
- for (const h of r.hits) hits.push(h);
4180
- }
4181
- hits.sort((a, b) => a.file < b.file ? -1 : a.file > b.file ? 1 : a.line - b.line);
4182
- if (hits.length > ROW_CAP) {
4183
- hits.length = ROW_CAP;
4184
- truncated = true;
3739
+ return null;
3740
+ } finally {
3741
+ clearTimeout(timer);
4185
3742
  }
4186
- return { hits, total, truncated };
4187
- }
4188
- var WORKER_SOURCE = [
4189
- "const { parentPort, workerData } = require('worker_threads');",
4190
- "const fs = require('fs');",
4191
- "const path = require('path');",
4192
- `const MAX = ${MAX_FILE_BYTES};`,
4193
- `const PREVIEW = ${PREVIEW_CHARS};`,
4194
- `const CAP = ${ROW_CAP};`,
4195
- "const NUL = String.fromCharCode(0);",
4196
- "const NL = String.fromCharCode(10);",
4197
- "const { root, files, needleLower } = workerData;",
4198
- "const hits = []; let total = 0, truncated = false;",
4199
- "for (const rel of files) {",
4200
- " const abs = path.join(root, rel);",
4201
- " let text;",
4202
- ' try { if (fs.statSync(abs).size > MAX) continue; text = fs.readFileSync(abs, "utf8"); } catch { continue; }',
4203
- " if (text.includes(NUL)) continue;",
4204
- " const low = text.toLowerCase();",
4205
- " if (!low.includes(needleLower)) continue;",
4206
- " const lowLines = low.split(NL); const rawLines = text.split(NL);",
4207
- " for (let i = 0; i < lowLines.length; i++) {",
4208
- " if (!lowLines[i].includes(needleLower)) continue;",
4209
- " total++;",
4210
- " if (hits.length < CAP) hits.push({ file: rel, line: i + 1, preview: rawLines[i].trim().slice(0, PREVIEW) });",
4211
- " else truncated = true;",
4212
- " }",
4213
- "}",
4214
- "parentPort.postMessage({ hits, total, truncated });"
4215
- ].join("\n");
4216
- function runWorker(root, files, needleLower) {
4217
- return new Promise((resolve11, reject) => {
4218
- const w = new Worker(WORKER_SOURCE, { eval: true, workerData: { root, files, needleLower } });
4219
- let settled = false;
4220
- w.once("message", (m) => {
4221
- settled = true;
4222
- resolve11(m);
4223
- });
4224
- w.once("error", reject);
4225
- w.once("exit", (code) => {
4226
- if (!settled && code !== 0) reject(new Error(`literal-scan worker exited with code ${code}`));
4227
- });
4228
- });
4229
- }
4230
-
4231
- // src/engine/search.ts
4232
- var IGNORE_DIRS = /* @__PURE__ */ new Set([
4233
- ".git",
4234
- ".vibgrate",
4235
- "node_modules",
4236
- "dist",
4237
- "build",
4238
- "out",
4239
- "target",
4240
- "vendor",
4241
- "__pycache__",
4242
- // .NET intermediate output and coverage reports: purely generated trees that a
4243
- // C#/coverage-heavy repo pays for on every literal sweep. `bin` is deliberately
4244
- // NOT here — plenty of repos keep real scripts in bin/ (rails, node CLIs), and
4245
- // completeness beats speed; the binary-extension skip below keeps a .NET bin/
4246
- // cheap anyway.
4247
- "obj",
4248
- "coverage"
4249
- ]);
4250
- var MAX_FILES_SCANNED = 2e4;
4251
- var BINARY_EXTS = /* @__PURE__ */ new Set([
4252
- ".dll",
4253
- ".pdb",
4254
- ".exe",
4255
- ".so",
4256
- ".dylib",
4257
- ".a",
4258
- ".lib",
4259
- ".o",
4260
- ".class",
4261
- ".jar",
4262
- ".wasm",
4263
- ".node",
4264
- ".png",
4265
- ".jpg",
4266
- ".jpeg",
4267
- ".gif",
4268
- ".webp",
4269
- ".ico",
4270
- ".icns",
4271
- ".bmp",
4272
- ".zip",
4273
- ".gz",
4274
- ".tgz",
4275
- ".bz2",
4276
- ".xz",
4277
- ".7z",
4278
- ".rar",
4279
- ".woff",
4280
- ".woff2",
4281
- ".ttf",
4282
- ".otf",
4283
- ".eot",
4284
- ".mp3",
4285
- ".mp4",
4286
- ".mov",
4287
- ".avi",
4288
- ".pdf"
4289
- ]);
4290
- function hasBinaryExt(rel2) {
4291
- return BINARY_EXTS.has(path2.extname(rel2).toLowerCase());
4292
3743
  }
4293
- function toPosix2(rel2) {
4294
- return path2.sep === "\\" ? rel2.replaceAll("\\", "/") : rel2;
4295
- }
4296
- async function searchSymbols(graph, root, query, limit) {
4297
- let q = query.trim();
4298
- if (!q) return { matches: [], moreAvailable: false, hint: "query is required" };
4299
- const quoted = /^(["'`])(.+)\1$/.exec(q);
4300
- if (quoted && quoted[2].trim()) q = quoted[2].trim();
4301
- const isPhrase = Boolean(quoted) || /\s/.test(q) || /[/\\]/.test(q) && !q.includes(":") && !/[*?]/.test(q);
4302
- let nodes = findNodes(graph, q).filter((n) => n.kind !== "file");
4303
- if (nodes.length === 0) {
4304
- nodes = fileSymbolNodes(graph, q);
4305
- }
4306
- if (nodes.length === 0 && isPhrase) {
4307
- nodes = reconstructedIdentifierNodes(graph, q);
4308
- }
4309
- if (nodes.length === 0) {
4310
- const tokens = queryTokens(q);
4311
- if (tokens.length >= 2) nodes = multiTokenNodes(graph, tokens);
4312
- }
4313
- const symbolCap = isPhrase ? Math.max(1, Math.floor(limit / 3)) : limit;
4314
- const symbolHits = nodes.slice(0, symbolCap).map((n, i) => ({
4315
- kind: n.kind,
4316
- name: n.qualifiedName,
4317
- file: n.file,
4318
- line: n.span.start,
4319
- score: Math.round((1 - i / Math.max(nodes.length, 1)) * 100) / 100
4320
- }));
4321
- const spare = limit - symbolHits.length;
4322
- const textHits = [];
4323
- let truncatedScan = false;
4324
- let totalTextMatches;
4325
- const skipScan = !isPhrase && hasExactSymbolMatch(nodes, q);
4326
- if (spare > 0 && !skipScan) {
4327
- const seen = new Set(symbolHits.map((h) => `${h.file}:${h.line}`));
4328
- const scan = await scanFiles(root, q, spare, seen, textHits, isPhrase);
4329
- truncatedScan = scan.truncated;
4330
- if (isPhrase) totalTextMatches = scan.total;
4331
- }
4332
- const matches = [...symbolHits, ...textHits];
4333
- if (matches.length === 0) {
3744
+ async function publishPrivateLibrary(req, opts = {}) {
3745
+ const f = opts.fetchImpl ?? globalThis.fetch;
3746
+ if (!f) return { ok: false, status: 0, message: "no network available" };
3747
+ if (!opts.auth) return { ok: false, status: 401, message: "a DSN is required to publish a private library (run `vibgrate login` or set VIBGRATE_DSN)" };
3748
+ const url = `${hostedBase(opts)}/v1/lib/private`;
3749
+ const ctrl = new AbortController();
3750
+ const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
3751
+ try {
3752
+ const res = await f(url, {
3753
+ method: "POST",
3754
+ headers: {
3755
+ "content-type": "application/json",
3756
+ Authorization: `VibgrateDSN ${opts.auth.keyId}:${opts.auth.secret}`,
3757
+ "X-Vibgrate-Timestamp": String(Date.now())
3758
+ },
3759
+ body: JSON.stringify({
3760
+ name: req.name,
3761
+ version: req.version,
3762
+ readme: req.readme,
3763
+ dts: req.dts,
3764
+ compliance: req.compliance,
3765
+ language: req.language,
3766
+ sourceUrl: req.sourceUrl
3767
+ }),
3768
+ signal: ctrl.signal
3769
+ });
3770
+ const data = await res.json().catch(() => ({}));
3771
+ if (!res.ok) return { ok: false, status: res.status, error: typeof data.error === "string" ? data.error : void 0, message: typeof data.message === "string" ? data.message : void 0 };
4334
3772
  return {
4335
- matches,
4336
- moreAvailable: false,
4337
- hint: "no symbol or text match \u2014 for meaning-level questions (symptoms, relationships, what-breaks-if) use query_graph"
3773
+ ok: true,
3774
+ targetId: String(data.targetId ?? ""),
3775
+ ecosystem: String(data.ecosystem ?? ""),
3776
+ entities: Number(data.entities) || 0,
3777
+ indexedTokens: Number(data.indexedTokens) || 0
4338
3778
  };
3779
+ } catch (err) {
3780
+ return { ok: false, status: 0, message: err instanceof Error ? err.message : "request failed" };
3781
+ } finally {
3782
+ clearTimeout(timer);
4339
3783
  }
4340
- const literalTruncated = totalTextMatches !== void 0 && totalTextMatches > textHits.length;
4341
- const moreAvailable = isPhrase ? literalTruncated || truncatedScan : nodes.length > symbolHits.length || truncatedScan;
4342
- const result = { matches, moreAvailable };
4343
- if (totalTextMatches !== void 0) result.totalTextMatches = totalTextMatches;
4344
- if (literalTruncated) {
4345
- result.hint = `showing ${textHits.length} of ${totalTextMatches}${truncatedScan ? "+" : ""} literal matches \u2014 raise limit or narrow the query to get them all`;
4346
- }
4347
- return result;
4348
- }
4349
- function fileSymbolNodes(graph, q) {
4350
- if (!/[/\\]/.test(q) && !/\.\w+$/.test(q)) return [];
4351
- const norm = q.replaceAll("\\", "/").replace(/^\.\//, "");
4352
- const inFile = graph.nodes.filter(
4353
- (n) => n.kind !== "file" && (n.file === norm || n.file.endsWith(`/${norm}`))
4354
- );
4355
- return [...inFile].sort((a, b) => b.importance - a.importance || a.qualifiedName.localeCompare(b.qualifiedName));
4356
3784
  }
4357
- function hasExactSymbolMatch(nodes, q) {
4358
- if (nodes.length === 0) return false;
4359
- if (/^(.*):(\d+)$/.test(q)) return true;
4360
- const lower = q.toLowerCase();
4361
- return nodes.some((n) => n.id === q || n.qualifiedName.toLowerCase() === lower || n.name.toLowerCase() === lower);
3785
+
3786
+ // src/engine/hosted-cache.ts
3787
+ var CACHE_VERSION = "vg-hosted-docs/1";
3788
+ var HOSTED_DOCS_TTL_MS = 24 * 60 * 60 * 1e3;
3789
+ var MAX_ENTRIES = 64;
3790
+ function cachePath(root) {
3791
+ return path17.join(cacheDir(root), "hosted-docs.json");
4362
3792
  }
4363
- function queryTokens(q) {
4364
- return [...new Set(q.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 2))];
3793
+ function hostedDocsCacheKey(req, opts = {}) {
3794
+ const material = JSON.stringify([
3795
+ req.name ?? "",
3796
+ req.targetId ?? "",
3797
+ req.query ?? "",
3798
+ req.verbosity ?? "",
3799
+ req.maxTokens ?? 0,
3800
+ hostedBase(opts),
3801
+ opts.auth?.keyId ?? "anon"
3802
+ ]);
3803
+ return createHash("sha256").update(material).digest("hex");
4365
3804
  }
4366
- function reconstructedIdentifierNodes(graph, q) {
4367
- const words = q.trim().split(/\s+/).filter(Boolean);
4368
- if (words.length < 2) return [];
4369
- const concat = findNodes(graph, words.join(""));
4370
- if (concat.length) return concat.filter((n) => n.kind !== "file");
4371
- const snake = findNodes(graph, words.join("_"));
4372
- return snake.filter((n) => n.kind !== "file");
3805
+ function loadFile(root) {
3806
+ try {
3807
+ const parsed = JSON.parse(fs15.readFileSync(cachePath(root), "utf8"));
3808
+ if (parsed && parsed.version === CACHE_VERSION && parsed.entries && typeof parsed.entries === "object") return parsed;
3809
+ } catch {
3810
+ }
3811
+ return { version: CACHE_VERSION, entries: {} };
4373
3812
  }
4374
- function multiTokenNodes(graph, tokens) {
4375
- const scored = [];
4376
- for (const n of graph.nodes) {
4377
- if (n.kind === "file") continue;
4378
- const name = n.name.toLowerCase();
4379
- const qn = n.qualifiedName.toLowerCase();
4380
- let hits = 0;
4381
- let exact = false;
4382
- for (const t of tokens) {
4383
- if (name === t || qn === t) {
4384
- hits++;
4385
- exact = true;
4386
- } else if (qn.includes(t)) hits++;
3813
+ function saveFile(root, file) {
3814
+ try {
3815
+ const keys = Object.keys(file.entries);
3816
+ if (keys.length > MAX_ENTRIES) {
3817
+ for (const k of keys.sort((a, b) => file.entries[a].at - file.entries[b].at).slice(0, keys.length - MAX_ENTRIES)) {
3818
+ delete file.entries[k];
3819
+ }
4387
3820
  }
4388
- if (exact || hits >= 2) scored.push({ node: n, hits, exact });
3821
+ fs15.mkdirSync(cacheDir(root), { recursive: true });
3822
+ fs15.writeFileSync(cachePath(root), JSON.stringify(file));
3823
+ } catch {
4389
3824
  }
4390
- return scored.sort((a, b) => Number(b.exact) - Number(a.exact) || b.hits - a.hits || b.node.importance - a.node.importance || a.node.qualifiedName.localeCompare(b.node.qualifiedName)).map((e) => e.node);
4391
3825
  }
4392
- async function scanFiles(root, needle, budget, seen, out, countAll) {
4393
- const listing = listCandidateFiles(root, needle);
4394
- const outcome = await scanCandidates(root, listing.files, needle, { collectAll: countAll, budget });
4395
- let overlap = 0;
4396
- for (const h of outcome.hits) {
4397
- if (seen.has(`${h.file}:${h.line}`)) {
4398
- overlap++;
4399
- continue;
3826
+ async function fetchHostedDocsCached(root, req, opts = {}) {
3827
+ const now = opts.now ?? Date.now;
3828
+ const ttl = opts.ttlMs ?? HOSTED_DOCS_TTL_MS;
3829
+ const key = hostedDocsCacheKey(req, opts);
3830
+ const file = loadFile(root);
3831
+ if (!opts.bypass) {
3832
+ const hit = file.entries[key];
3833
+ if (hit && now() - hit.at < ttl && hit.result?.content) {
3834
+ return { ...hit.result, metadata: { ...hit.result.metadata ?? {}, cached: true } };
4400
3835
  }
4401
- seen.add(`${h.file}:${h.line}`);
4402
- if (out.length < budget) out.push({ kind: "text", file: h.file, line: h.line, preview: h.preview });
4403
3836
  }
4404
- return { total: outcome.total - overlap, truncated: listing.truncated || outcome.truncated };
3837
+ const result = await fetchHostedDocs(req, opts);
3838
+ if (result) {
3839
+ file.entries[key] = { at: now(), result };
3840
+ saveFile(root, file);
3841
+ }
3842
+ return result;
4405
3843
  }
4406
- function listCandidateFiles(root, needle) {
4407
- return ripgrepCandidates(root, needle) ?? walkCandidatesCached(root);
3844
+
3845
+ // src/mcp/response.ts
3846
+ var MAX_RESULT_TOKENS = 25e3;
3847
+ function renderToolResult(result, maxTokens = MAX_RESULT_TOKENS) {
3848
+ return toolResult(clampToBudget(compactResult(result), maxTokens));
4408
3849
  }
4409
- var LISTING_TTL_MS = 2e3;
4410
- var listingCache = /* @__PURE__ */ new Map();
4411
- function listingTtlMs() {
4412
- const v = Number(process.env.VG_LISTING_TTL_MS);
4413
- return Number.isFinite(v) && v >= 0 ? v : LISTING_TTL_MS;
3850
+ function toolResult(result) {
3851
+ return {
3852
+ content: [{ type: "text", text: JSON.stringify(result) }],
3853
+ structuredContent: asStructured(result)
3854
+ };
4414
3855
  }
4415
- function walkCandidatesCached(root) {
4416
- const ttl = listingTtlMs();
4417
- if (ttl > 0) {
4418
- const hit = listingCache.get(root);
4419
- if (hit && Date.now() - hit.at < ttl) return hit.listing;
4420
- }
4421
- const listing = walkCandidates(root);
4422
- if (ttl > 0) listingCache.set(root, { listing, at: Date.now() });
4423
- return listing;
3856
+ function compactResult(value) {
3857
+ return prune(value);
4424
3858
  }
4425
- function walkCandidates(root) {
4426
- const files = [];
4427
- let truncated = false;
4428
- const stack = [root];
4429
- while (stack.length) {
4430
- const dir = stack.pop();
4431
- let entries;
4432
- try {
4433
- entries = fs18.readdirSync(dir, { withFileTypes: true });
4434
- } catch {
4435
- continue;
4436
- }
4437
- for (const e of entries) {
4438
- if (e.name.startsWith(".") || IGNORE_DIRS.has(e.name)) continue;
4439
- const abs = path2.join(dir, e.name);
4440
- if (e.isDirectory()) {
4441
- stack.push(abs);
4442
- continue;
4443
- }
4444
- if (hasBinaryExt(e.name)) continue;
4445
- if (files.length >= MAX_FILES_SCANNED) {
4446
- truncated = true;
4447
- continue;
4448
- }
4449
- files.push(toPosix2(path2.relative(root, abs)));
3859
+ function prune(v) {
3860
+ if (Array.isArray(v)) return v.map(prune);
3861
+ if (v && typeof v === "object") {
3862
+ const out = {};
3863
+ for (const [k, raw] of Object.entries(v)) {
3864
+ const pv = prune(raw);
3865
+ if (!isEmpty(pv)) out[k] = pv;
4450
3866
  }
3867
+ return out;
4451
3868
  }
4452
- files.sort();
4453
- return { files, truncated };
3869
+ return v;
4454
3870
  }
4455
- var rgAvailable;
4456
- function hasRipgrep() {
4457
- if (process.env.VG_DISABLE_RIPGREP) return false;
4458
- if (rgAvailable === void 0) {
4459
- try {
4460
- rgAvailable = spawnSync("rg", ["--version"], { stdio: "ignore", timeout: 3e3 }).status === 0;
4461
- } catch {
4462
- rgAvailable = false;
4463
- }
3871
+ function isEmpty(v) {
3872
+ if (v === null || v === void 0 || v === "") return true;
3873
+ if (Array.isArray(v)) return v.length === 0;
3874
+ if (typeof v === "object") return Object.keys(v).length === 0;
3875
+ return false;
3876
+ }
3877
+ function clampToBudget(value, maxTokens = MAX_RESULT_TOKENS) {
3878
+ if (!value || typeof value !== "object" || Array.isArray(value)) return value;
3879
+ if (tokensOf(value) <= maxTokens) return value;
3880
+ const obj2 = { ...value };
3881
+ const truncated = {};
3882
+ for (let guard = 0; guard < 64 && tokensOf(obj2) > maxTokens; guard++) {
3883
+ const arrays = Object.entries(obj2).filter(
3884
+ (e) => Array.isArray(e[1]) && e[1].length > 0
3885
+ );
3886
+ if (arrays.length === 0) break;
3887
+ arrays.sort((a, b) => tokensOf(b[1]) - tokensOf(a[1]));
3888
+ const [field, arr] = arrays[0];
3889
+ const total = truncated[field]?.total ?? arr.length;
3890
+ const shown = Math.max(1, Math.floor(arr.length / 2));
3891
+ obj2[field] = arr.slice(0, shown);
3892
+ truncated[field] = { shown, total };
4464
3893
  }
4465
- return rgAvailable;
3894
+ if (Object.keys(truncated).length) obj2._truncated = truncated;
3895
+ return obj2;
4466
3896
  }
4467
- function ripgrepCandidates(root, needle) {
4468
- if (!hasRipgrep()) return null;
4469
- const globs = [...IGNORE_DIRS].flatMap((d) => ["--glob", `!**/${d}/**`, "--glob", `!${d}/**`]);
4470
- const res = spawnSync(
4471
- "rg",
4472
- ["--files-with-matches", "--fixed-strings", "--ignore-case", "--no-ignore", "--no-messages", ...globs, "--", needle, "."],
4473
- { cwd: root, encoding: "utf8", timeout: 15e3, maxBuffer: 32 * 1024 * 1024 }
4474
- );
4475
- if (res.error || res.status !== 0 && res.status !== 1 || res.signal) return null;
4476
- const files = parseRgFileList(res.stdout);
4477
- if (files.length > MAX_FILES_SCANNED) return { files: files.slice(0, MAX_FILES_SCANNED), truncated: true };
4478
- return { files, truncated: false };
3897
+ function tokensOf(v) {
3898
+ return countTokens(JSON.stringify(v));
4479
3899
  }
4480
- function parseRgFileList(stdout, sep9 = path2.sep) {
4481
- const seen = /* @__PURE__ */ new Set();
4482
- for (const raw of stdout.split("\n")) {
4483
- if (!raw) continue;
4484
- const posix3 = sep9 === "\\" ? raw.replaceAll("\\", "/") : raw;
4485
- const rel2 = posix3.startsWith("./") ? posix3.slice(2) : posix3;
4486
- if (!rel2) continue;
4487
- const segs = rel2.split("/");
4488
- if (segs.some((s) => s.startsWith(".") || IGNORE_DIRS.has(s))) continue;
4489
- if (hasBinaryExt(rel2)) continue;
4490
- seen.add(rel2);
3900
+ function asStructured(result) {
3901
+ if (result && typeof result === "object" && !Array.isArray(result)) {
3902
+ return result;
4491
3903
  }
4492
- return [...seen].sort();
3904
+ return { result };
3905
+ }
3906
+ function boundList(items, max) {
3907
+ if (!Number.isFinite(max) || max < 0 || items.length <= max) {
3908
+ return { items, total: items.length };
3909
+ }
3910
+ return { items: items.slice(0, max), total: items.length };
4493
3911
  }
3912
+ var NODE_EDGE_CAP = 50;
4494
3913
 
4495
3914
  // src/mcp/tools.ts
4496
3915
  var warmEmbedder = null;
@@ -5375,12 +4794,12 @@ function detectAssistants(root, opts = {}) {
5375
4794
  const which = opts.which ?? whichOnPath;
5376
4795
  const found = [];
5377
4796
  for (const assistant of ASSISTANTS) {
5378
- const repoHit = (assistant.markers ?? []).find((m) => fs18.existsSync(path2.join(root, m)));
4797
+ const repoHit = (assistant.markers ?? []).find((m) => fs15.existsSync(path17.join(root, m)));
5379
4798
  if (repoHit) {
5380
4799
  found.push({ assistant, via: "repo", marker: repoHit });
5381
4800
  continue;
5382
4801
  }
5383
- const homeHit = (assistant.homeMarkers ?? []).find((m) => fs18.existsSync(path2.join(home, m)));
4802
+ const homeHit = (assistant.homeMarkers ?? []).find((m) => fs15.existsSync(path17.join(home, m)));
5384
4803
  if (homeHit) {
5385
4804
  found.push({ assistant, via: "home", marker: homeHit });
5386
4805
  continue;
@@ -5412,19 +4831,19 @@ function installAssistant(a, opts) {
5412
4831
  const skipped = [];
5413
4832
  let note;
5414
4833
  if (a.skill) {
5415
- writeFileEnsured(path2.join(opts.root, a.skill), skillMarkdown(a.id));
4834
+ writeFileEnsured(path17.join(opts.root, a.skill), skillMarkdown(a.id));
5416
4835
  wrote.push(a.skill);
5417
4836
  }
5418
4837
  if (a.mcp) {
5419
4838
  const launch = opts.launch ?? detectServeLaunch();
5420
- upsertMcp(path2.join(opts.root, a.mcp.file), a.mcp, launch);
4839
+ upsertMcp(path17.join(opts.root, a.mcp.file), a.mcp, launch);
5421
4840
  wrote.push(a.mcp.file);
5422
4841
  note = launch.note;
5423
4842
  } else {
5424
4843
  skipped.push("mcp (host-specific setup)");
5425
4844
  }
5426
4845
  if (a.nudge && opts.hook !== false) {
5427
- writeNudge(path2.join(opts.root, a.nudge.file), a.nudge, opts.smallRepo, a.id);
4846
+ writeNudge(path17.join(opts.root, a.nudge.file), a.nudge, opts.smallRepo, a.id);
5428
4847
  wrote.push(a.nudge.file);
5429
4848
  } else if (a.nudge) {
5430
4849
  skipped.push("nudge (--no-hook)");
@@ -5434,41 +4853,41 @@ function installAssistant(a, opts) {
5434
4853
  function uninstallAssistant(a, root, purge) {
5435
4854
  const removed = [];
5436
4855
  if (a.mcp) {
5437
- const file = path2.join(root, a.mcp.file);
4856
+ const file = path17.join(root, a.mcp.file);
5438
4857
  if (removeMcp(file, a.mcp)) removed.push(a.mcp.file);
5439
4858
  }
5440
4859
  if (a.nudge) {
5441
- const file = path2.join(root, a.nudge.file);
4860
+ const file = path17.join(root, a.nudge.file);
5442
4861
  if (a.nudge.kind === "block") {
5443
4862
  if (removeBlock(file)) removed.push(a.nudge.file);
5444
- } else if (fs18.existsSync(file)) {
5445
- fs18.rmSync(file);
4863
+ } else if (fs15.existsSync(file)) {
4864
+ fs15.rmSync(file);
5446
4865
  removed.push(a.nudge.file);
5447
4866
  }
5448
4867
  }
5449
4868
  if (purge && a.skill) {
5450
- const file = path2.join(root, a.skill);
5451
- if (fs18.existsSync(file)) {
5452
- fs18.rmSync(file);
4869
+ const file = path17.join(root, a.skill);
4870
+ if (fs15.existsSync(file)) {
4871
+ fs15.rmSync(file);
5453
4872
  removed.push(a.skill);
5454
4873
  }
5455
4874
  }
5456
4875
  return removed;
5457
4876
  }
5458
4877
  function writeFileEnsured(file, content) {
5459
- fs18.mkdirSync(path2.dirname(file), { recursive: true });
5460
- fs18.writeFileSync(file, content);
4878
+ fs15.mkdirSync(path17.dirname(file), { recursive: true });
4879
+ fs15.writeFileSync(file, content);
5461
4880
  }
5462
4881
  function writeNavigationConfig(root) {
5463
4882
  ensureVibgrateGitignore(root);
5464
- const rel2 = path2.join(".vibgrate", "mcp-navigation.json");
4883
+ const rel2 = path17.join(".vibgrate", "mcp-navigation.json");
5465
4884
  const doc = {
5466
4885
  _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.",
5467
4886
  hot_core: [...HOT_TOOLS],
5468
4887
  deferred: deferredToolNames(),
5469
4888
  toolset: navigationToolsetConfig()
5470
4889
  };
5471
- writeFileEnsured(path2.join(root, rel2), `${JSON.stringify(doc, null, 2)}
4890
+ writeFileEnsured(path17.join(root, rel2), `${JSON.stringify(doc, null, 2)}
5472
4891
  `);
5473
4892
  return rel2;
5474
4893
  }
@@ -5483,7 +4902,7 @@ function upsertMcp(file, target, launch) {
5483
4902
  `);
5484
4903
  }
5485
4904
  function removeMcp(file, target) {
5486
- if (!fs18.existsSync(file)) return false;
4905
+ if (!fs15.existsSync(file)) return false;
5487
4906
  const config = readJson(file);
5488
4907
  const bag = config[target.key];
5489
4908
  if (!bag || !(bag.vg !== void 0)) return false;
@@ -5502,8 +4921,8 @@ function refreshInstalledInstructions(root, smallRepo) {
5502
4921
  };
5503
4922
  for (const a of ASSISTANTS) {
5504
4923
  if (a.skill) {
5505
- const file = path2.join(root, a.skill);
5506
- if (fs18.existsSync(file)) {
4924
+ const file = path17.join(root, a.skill);
4925
+ if (fs15.existsSync(file)) {
5507
4926
  const text = readTextSafe(file);
5508
4927
  const from = text === null ? null : staleVersion(text, "# vg \u2014 the code map");
5509
4928
  if (from !== null) {
@@ -5513,8 +4932,8 @@ function refreshInstalledInstructions(root, smallRepo) {
5513
4932
  }
5514
4933
  }
5515
4934
  if (a.nudge) {
5516
- const file = path2.join(root, a.nudge.file);
5517
- if (!fs18.existsSync(file)) continue;
4935
+ const file = path17.join(root, a.nudge.file);
4936
+ if (!fs15.existsSync(file)) continue;
5518
4937
  const text = readTextSafe(file);
5519
4938
  if (text === null) continue;
5520
4939
  if (a.nudge.kind === "block") {
@@ -5538,7 +4957,7 @@ function refreshInstalledInstructions(root, smallRepo) {
5538
4957
  }
5539
4958
  function readTextSafe(file) {
5540
4959
  try {
5541
- return fs18.readFileSync(file, "utf8");
4960
+ return fs15.readFileSync(file, "utf8");
5542
4961
  } catch {
5543
4962
  return null;
5544
4963
  }
@@ -5557,8 +4976,8 @@ ${stripMarkers(nudgeMarkdown(smallRepo, client))}` : stripMarkers(nudgeMarkdown(
5557
4976
  upsertBlock(file, nudgeMarkdown(smallRepo, client));
5558
4977
  }
5559
4978
  function upsertBlock(file, block) {
5560
- fs18.mkdirSync(path2.dirname(file), { recursive: true });
5561
- let existing = fs18.existsSync(file) ? fs18.readFileSync(file, "utf8") : "";
4979
+ fs15.mkdirSync(path17.dirname(file), { recursive: true });
4980
+ let existing = fs15.existsSync(file) ? fs15.readFileSync(file, "utf8") : "";
5562
4981
  const re = new RegExp(`${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}`);
5563
4982
  if (re.test(existing)) existing = existing.replace(re, block);
5564
4983
  else existing = existing.length ? `${existing.replace(/\s*$/, "")}
@@ -5566,20 +4985,20 @@ function upsertBlock(file, block) {
5566
4985
  ${block}
5567
4986
  ` : `${block}
5568
4987
  `;
5569
- fs18.writeFileSync(file, existing);
4988
+ fs15.writeFileSync(file, existing);
5570
4989
  }
5571
4990
  function removeBlock(file) {
5572
- if (!fs18.existsSync(file)) return false;
5573
- const existing = fs18.readFileSync(file, "utf8");
4991
+ if (!fs15.existsSync(file)) return false;
4992
+ const existing = fs15.readFileSync(file, "utf8");
5574
4993
  const re = new RegExp(`\\n*${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}\\n*`);
5575
4994
  if (!re.test(existing)) return false;
5576
- fs18.writeFileSync(file, existing.replace(re, "\n"));
4995
+ fs15.writeFileSync(file, existing.replace(re, "\n"));
5577
4996
  return true;
5578
4997
  }
5579
4998
  function readJson(file) {
5580
- if (!fs18.existsSync(file)) return {};
4999
+ if (!fs15.existsSync(file)) return {};
5581
5000
  try {
5582
- const parsed = JSON.parse(fs18.readFileSync(file, "utf8"));
5001
+ const parsed = JSON.parse(fs15.readFileSync(file, "utf8"));
5583
5002
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
5584
5003
  return parsed;
5585
5004
  }
@@ -5599,7 +5018,7 @@ function escapeRe(s) {
5599
5018
  }
5600
5019
  var REFRESH_LOCK_STALE_MS = 10 * 60 * 1e3;
5601
5020
  function refreshLockPath(root) {
5602
- return path2.join(cacheDir(root), "refresh.lock");
5021
+ return path17.join(cacheDir(root), "refresh.lock");
5603
5022
  }
5604
5023
  async function refreshIfStale(root, opts = {}) {
5605
5024
  const probe = probeFreshness(root);
@@ -5630,8 +5049,9 @@ async function refreshIfStale(root, opts = {}) {
5630
5049
  const dir = vibgrateDir(root);
5631
5050
  writeArtifacts(result.graph, {
5632
5051
  root,
5633
- report: fs18.existsSync(path2.join(dir, "GRAPH_REPORT.md")),
5634
- html: fs18.existsSync(path2.join(dir, "graph.html"))
5052
+ graphPath: opts.graphPath,
5053
+ report: fs15.existsSync(path17.join(dir, "GRAPH_REPORT.md")),
5054
+ html: fs15.existsSync(path17.join(dir, "graph.html"))
5635
5055
  });
5636
5056
  }
5637
5057
  writeSnapshot(root, result.graph.provenance.corpusHash, result.fileStats, scope);
@@ -5651,6 +5071,6 @@ async function refreshIfStale(root, opts = {}) {
5651
5071
  }
5652
5072
  }
5653
5073
 
5654
- export { ASSISTANTS, CliError, ExitCode, FREE_PACK, NPX_INVOCATION, ResourceLimitError, SCHEMA_VERSION, SKIP_DIRS, SKIP_FILES, SMALL_REPO_FILES, TOOLS, UsageError, analyze, applyCoverage, applyStaticTestLinkage, assessDocQuality, assistantById, availableRegionIds, buildFacts, buildGraph, buildModuleResolver, clearStoredCredentials, countTokens, coveringTests, createWorkspaceDsn, credentialsPath, dashHostForIngestHost, decodeScipIndex, defaultGraphPath, detectAssistants, detectRunner, detectServeLaunch, discover, discoverModels, driftCount, dsnCommand, ensureGitignored, epistemicBreakdown, fetchHostedDocsCached, findGitRoot, gitignoreEntryForCredentials, groundGraph, hasDrift, homeCredentialsPath, hostedBase, ingestHostForRegionId, installAssistant, isTestFile, loadCoverage, loadGraph, loadSnapshot, parseDsn, parseJsonc, probeFreshness, projectCredentialsPath, publishPrivateLibrary, pushCommand, readScanArtifact, readStoredCredentials, refreshIfStale, refreshInstalledInstructions, relativeResolver, renderHtml, renderReport, renderToolResult, resolveCliInvocation, resolveDsn, resolveIngestHost, resolveLimits, scipEdges, selectForBudget, shortestPath, symbolsFromApi, testsToRun, uninstallAssistant, uploadScanArtifact, usageError, verifyDeterminism, vibgrateDir, warmEmbedderInBackground, writeArtifacts, writeNavigationConfig, writeSnapshot, writeStoredCredentials };
5655
- //# sourceMappingURL=chunk-VXT6LUGP.js.map
5656
- //# sourceMappingURL=chunk-VXT6LUGP.js.map
5074
+ export { ASSISTANTS, CliError, ExitCode, FREE_PACK, NPX_INVOCATION, ResourceLimitError, SCHEMA_VERSION, SKIP_DIRS, SKIP_FILES, SMALL_REPO_FILES, TOOLS, UsageError, analyze, applyCoverage, assessDocQuality, assistantById, availableRegionIds, buildFacts, buildGraph, clearStoredCredentials, countTokens, coveringTests, createWorkspaceDsn, credentialsPath, dashHostForIngestHost, decodeScipIndex, detectAssistants, detectRunner, detectServeLaunch, discover, driftCount, dsnCommand, ensureGitignored, epistemicBreakdown, fetchHostedDocsCached, findGitRoot, gitignoreEntryForCredentials, groundGraph, hasDrift, homeCredentialsPath, hostedBase, ingestHostForRegionId, installAssistant, loadCoverage, loadSnapshot, parseDsn, probeFreshness, projectCredentialsPath, publishPrivateLibrary, pushCommand, readScanArtifact, readStoredCredentials, refreshIfStale, refreshInstalledInstructions, renderToolResult, resolveCliInvocation, resolveDsn, resolveIngestHost, resolveLimits, scipEdges, searchSymbols, selectForBudget, shortestPath, symbolsFromApi, testsToRun, uninstallAssistant, uploadScanArtifact, usageError, verifyDeterminism, warmEmbedderInBackground, writeNavigationConfig, writeSnapshot, writeStoredCredentials };
5075
+ //# sourceMappingURL=chunk-S6CYDZEW.js.map
5076
+ //# sourceMappingURL=chunk-S6CYDZEW.js.map