@vibgrate/cli 2026.727.1 → 2026.727.2

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 (124) hide show
  1. package/DOCS.md +0 -17
  2. package/README.md +0 -1
  3. package/dist/baseline-TG4ATZGZ.js +8 -0
  4. package/dist/{baseline-K7YBFKZI.js.map → baseline-TG4ATZGZ.js.map} +1 -1
  5. package/dist/{chunk-JZYGEZTU.js → chunk-2MBL43EJ.js} +3 -3
  6. package/dist/{chunk-JZYGEZTU.js.map → chunk-2MBL43EJ.js.map} +1 -1
  7. package/dist/{chunk-HABUT7HW.js → chunk-2XQDMLKB.js} +240 -8
  8. package/dist/chunk-2XQDMLKB.js.map +1 -0
  9. package/dist/{chunk-VXGV6NTD.js → chunk-3V24O47U.js} +11 -9
  10. package/dist/chunk-3V24O47U.js.map +1 -0
  11. package/dist/{chunk-6IOQJGFT.js → chunk-6ABXHZRN.js} +34 -77
  12. package/dist/chunk-6ABXHZRN.js.map +1 -0
  13. package/dist/{chunk-4CQ3MGNN.js → chunk-6AYDFFP5.js} +3 -3
  14. package/dist/{chunk-4CQ3MGNN.js.map → chunk-6AYDFFP5.js.map} +1 -1
  15. package/dist/chunk-6UQMXIAG.js +122 -0
  16. package/dist/chunk-6UQMXIAG.js.map +1 -0
  17. package/dist/{chunk-KHJO4D5N.js → chunk-7N4Y3E47.js} +2 -2
  18. package/dist/{chunk-KHJO4D5N.js.map → chunk-7N4Y3E47.js.map} +1 -1
  19. package/dist/{chunk-OXQNQW5J.js → chunk-CY4WKXB2.js} +4 -4
  20. package/dist/{chunk-OXQNQW5J.js.map → chunk-CY4WKXB2.js.map} +1 -1
  21. package/dist/chunk-DGUM43GV.js +10 -0
  22. package/dist/chunk-DGUM43GV.js.map +1 -0
  23. package/dist/{chunk-HYZVHJ3Q.js → chunk-EX4PQT6W.js} +3 -3
  24. package/dist/{chunk-HYZVHJ3Q.js.map → chunk-EX4PQT6W.js.map} +1 -1
  25. package/dist/{chunk-3BVSDMHV.js → chunk-J4RFRKZH.js} +7 -7
  26. package/dist/{chunk-3BVSDMHV.js.map → chunk-J4RFRKZH.js.map} +1 -1
  27. package/dist/chunk-K3MZIVZW.js +88 -0
  28. package/dist/chunk-K3MZIVZW.js.map +1 -0
  29. package/dist/{chunk-65MEOA4E.js → chunk-KCKYKDMS.js} +9 -8
  30. package/dist/chunk-KCKYKDMS.js.map +1 -0
  31. package/dist/{chunk-S6CYDZEW.js → chunk-KNELIQJO.js} +364 -179
  32. package/dist/chunk-KNELIQJO.js.map +1 -0
  33. package/dist/{chunk-EDAAG7BL.js → chunk-KSBHGWRV.js} +4 -3
  34. package/dist/chunk-KSBHGWRV.js.map +1 -0
  35. package/dist/{chunk-C4CKTAZP.js → chunk-LKVMI67M.js} +4 -4
  36. package/dist/chunk-LKVMI67M.js.map +1 -0
  37. package/dist/{chunk-7JHPQ6WF.js → chunk-OFUC6RYN.js} +3 -3
  38. package/dist/{chunk-7JHPQ6WF.js.map → chunk-OFUC6RYN.js.map} +1 -1
  39. package/dist/{chunk-YDAT7LD2.js → chunk-RNP3SMTV.js} +17 -9
  40. package/dist/chunk-RNP3SMTV.js.map +1 -0
  41. package/dist/{chunk-SZLF66FJ.js → chunk-TCDAZZNP.js} +5 -5
  42. package/dist/{chunk-SZLF66FJ.js.map → chunk-TCDAZZNP.js.map} +1 -1
  43. package/dist/{chunk-WLETRGAC.js → chunk-WGUL45AV.js} +11 -11
  44. package/dist/{chunk-WLETRGAC.js.map → chunk-WGUL45AV.js.map} +1 -1
  45. package/dist/cli.d.ts +3 -0
  46. package/dist/cli.js +97 -240
  47. package/dist/cli.js.map +1 -1
  48. package/dist/{execution-env-3BN7Z737.js → execution-env-ZU45JZ4G.js} +3 -2
  49. package/dist/{execution-env-3BN7Z737.js.map → execution-env-ZU45JZ4G.js.map} +1 -1
  50. package/dist/{federation-EPZDUWCK.js → federation-SZWZIFZ7.js} +3 -2
  51. package/dist/{federation-EPZDUWCK.js.map → federation-SZWZIFZ7.js.map} +1 -1
  52. package/dist/{fs-IPY4FJKH.js → fs-B5HZ2MIU.js} +3 -2
  53. package/dist/fs-B5HZ2MIU.js.map +1 -0
  54. package/dist/{fs-KCABDURV.js → fs-O5IQYXHO.js} +3 -2
  55. package/dist/fs-O5IQYXHO.js.map +1 -0
  56. package/dist/{git-ref-HPRLUFFW.js → git-ref-XDXB6JP5.js} +3 -2
  57. package/dist/{git-ref-HPRLUFFW.js.map → git-ref-XDXB6JP5.js.map} +1 -1
  58. package/dist/graph-backend-73URGTFY.js +11 -0
  59. package/dist/{graph-backend-ZMSYOZMW.js.map → graph-backend-73URGTFY.js.map} +1 -1
  60. package/dist/index.d.ts +44 -15
  61. package/dist/index.js +9 -8
  62. package/dist/index.js.map +1 -1
  63. package/dist/{interactive-ICL7D7LO.js → interactive-372NLI5C.js} +19 -18
  64. package/dist/interactive-372NLI5C.js.map +1 -0
  65. package/dist/{llm-host-2RAYQSDQ.js → llm-host-7GWDO6IX.js} +3 -2
  66. package/dist/{llm-host-2RAYQSDQ.js.map → llm-host-7GWDO6IX.js.map} +1 -1
  67. package/dist/load-I333TGNI.js +8 -0
  68. package/dist/{load-OHVKDX4R.js.map → load-I333TGNI.js.map} +1 -1
  69. package/dist/local-runtime-5O2NN3GA.js +5 -0
  70. package/dist/{local-runtime-OJFLX2HI.js.map → local-runtime-5O2NN3GA.js.map} +1 -1
  71. package/dist/mcp-tools-I7JK3S2M.js +4 -0
  72. package/dist/{mcp-tools-I6PME3AV.js.map → mcp-tools-I7JK3S2M.js.map} +1 -1
  73. package/dist/{model-execution-profile-MQ2QCP24.js → model-execution-profile-TFBKCNJG.js} +3 -2
  74. package/dist/{model-execution-profile-MQ2QCP24.js.map → model-execution-profile-TFBKCNJG.js.map} +1 -1
  75. package/dist/{model-orchestrator-FFTZI3U2.js → model-orchestrator-TVONAUPS.js} +5 -4
  76. package/dist/{model-orchestrator-FFTZI3U2.js.map → model-orchestrator-TVONAUPS.js.map} +1 -1
  77. package/dist/parse-worker.d.ts +1 -1
  78. package/dist/parse-worker.js +1 -0
  79. package/dist/parse-worker.js.map +1 -1
  80. package/dist/{paths-MCH75O3G.js → paths-2RAH3GME.js} +3 -2
  81. package/dist/{paths-MCH75O3G.js.map → paths-2RAH3GME.js.map} +1 -1
  82. package/dist/{resolve-gguf-4UPIGPEE.js → resolve-gguf-VPL4YQ6J.js} +3 -2
  83. package/dist/{resolve-gguf-4UPIGPEE.js.map → resolve-gguf-VPL4YQ6J.js.map} +1 -1
  84. package/dist/runtime-session-JRKCQUHY.js +18 -0
  85. package/dist/{runtime-session-IDU5BDUJ.js.map → runtime-session-JRKCQUHY.js.map} +1 -1
  86. package/dist/session-ITPYQ5XI.js +11 -0
  87. package/dist/{session-L2OSDRPI.js.map → session-ITPYQ5XI.js.map} +1 -1
  88. package/dist/session-store-WFBX7HXL.js +4 -0
  89. package/dist/{session-store-W5RZZHA3.js.map → session-store-WFBX7HXL.js.map} +1 -1
  90. package/dist/{stream-json-F74W5FCK.js → stream-json-SLMDKLXP.js} +10 -9
  91. package/dist/stream-json-SLMDKLXP.js.map +1 -0
  92. package/dist/{types-ZMg1mSuu.d.ts → types-7AHBUbFm.d.ts} +5 -1
  93. package/dist/ui-Q6DJ3GCU.js +5 -0
  94. package/dist/ui-Q6DJ3GCU.js.map +1 -0
  95. package/dist/{vgd-EGBQD7BW.js → vgd-7JV5KCNJ.js} +7 -6
  96. package/dist/vgd-7JV5KCNJ.js.map +1 -0
  97. package/package.json +1 -1
  98. package/dist/baseline-K7YBFKZI.js +0 -7
  99. package/dist/chunk-5Q54GEHM.js +0 -29
  100. package/dist/chunk-5Q54GEHM.js.map +0 -1
  101. package/dist/chunk-65MEOA4E.js.map +0 -1
  102. package/dist/chunk-6IOQJGFT.js.map +0 -1
  103. package/dist/chunk-C4CKTAZP.js.map +0 -1
  104. package/dist/chunk-EDAAG7BL.js.map +0 -1
  105. package/dist/chunk-EDBV33B7.js +0 -57
  106. package/dist/chunk-EDBV33B7.js.map +0 -1
  107. package/dist/chunk-HABUT7HW.js.map +0 -1
  108. package/dist/chunk-S6CYDZEW.js.map +0 -1
  109. package/dist/chunk-VXGV6NTD.js.map +0 -1
  110. package/dist/chunk-YDAT7LD2.js.map +0 -1
  111. package/dist/fs-IPY4FJKH.js.map +0 -1
  112. package/dist/fs-KCABDURV.js.map +0 -1
  113. package/dist/graph-backend-ZMSYOZMW.js +0 -10
  114. package/dist/interactive-ICL7D7LO.js.map +0 -1
  115. package/dist/load-OHVKDX4R.js +0 -7
  116. package/dist/local-runtime-OJFLX2HI.js +0 -4
  117. package/dist/mcp-tools-I6PME3AV.js +0 -3
  118. package/dist/runtime-session-IDU5BDUJ.js +0 -17
  119. package/dist/session-L2OSDRPI.js +0 -10
  120. package/dist/session-store-W5RZZHA3.js +0 -3
  121. package/dist/stream-json-F74W5FCK.js.map +0 -1
  122. package/dist/ui-HOVOPWMX.js +0 -4
  123. package/dist/ui-HOVOPWMX.js.map +0 -1
  124. package/dist/vgd-EGBQD7BW.js.map +0 -1
@@ -1,17 +1,17 @@
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';
1
+ import { loadConfig, computeUpgradeImpact, getChangelogSignals, gitHistoryAvailable, buildVersionTimelines, findPackageTimeline, severityRank, projectTypeToVulnEcosystem, VERSION } from './chunk-WGUL45AV.js';
2
+ import { resolveVersion, resolveLib, loadCatalog, driftFor, localPackageDocs, readDoc, inventory, localApiSurface, edgeId, buildModuleResolver, resolve, applyStaticTestLinkage, nodeId } from './chunk-LKVMI67M.js';
3
3
  import { resolveOne, findNodes } from './chunk-J7LKEB2R.js';
4
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';
5
+ import { impactOf, indexFor, withTimeout, getNodeEmbeddings, queryGraphSemantic, queryGraph, loadEmbedder, isModelReady, acquireLock, releaseLock } from './chunk-6ABXHZRN.js';
6
6
  import { langById, grammarSetVersion, langForExtension, setGrammarsOverride, parseSource } from './chunk-3Q7QBWFW.js';
7
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';
8
+ import { writeGraphIndex, SCHEMA_VERSION, loadGraph, ensureVibgrateGitignore, vibgrateDir, writeArtifacts } from './chunk-2XQDMLKB.js';
9
+ import { cacheDir, loadCache, serializeGraph, stableStringify } from './chunk-K3MZIVZW.js';
10
10
  import { shortId, canonicalize, hashString, hashBytes } from './chunk-JFGYT6BI.js';
11
11
  import { writeTextFile, pathExists, readJsonFile } from './chunk-CRAMOEBE.js';
12
- import * as fs15 from 'fs';
12
+ import * as fs16 from 'fs';
13
13
  import { spawnSync, execFileSync } from 'child_process';
14
- import * as path17 from 'path';
14
+ import * as path18 from 'path';
15
15
  import ignore from 'ignore';
16
16
  import * as v8 from 'v8';
17
17
  import Graph from 'graphology';
@@ -46,9 +46,9 @@ function whichOnPath(cmd) {
46
46
  }
47
47
  function isOwnBinary(binPath) {
48
48
  try {
49
- const real = fs15.realpathSync(binPath);
49
+ const real = fs16.realpathSync(binPath);
50
50
  if (/[\\/]@vibgrate[\\/]cli[\\/]/.test(real)) return true;
51
- const head = fs15.readFileSync(real, { encoding: "utf8" }).slice(0, 2048);
51
+ const head = fs16.readFileSync(real, { encoding: "utf8" }).slice(0, 2048);
52
52
  return head.includes("@vibgrate/cli") || head.includes("vibgrate");
53
53
  } catch {
54
54
  return false;
@@ -58,7 +58,7 @@ function isEphemeralNpxBinary(binPath) {
58
58
  const NPX_SEGMENT = /[\\/]_npx[\\/]/;
59
59
  if (NPX_SEGMENT.test(binPath)) return true;
60
60
  try {
61
- return NPX_SEGMENT.test(fs15.realpathSync(binPath));
61
+ return NPX_SEGMENT.test(fs16.realpathSync(binPath));
62
62
  } catch {
63
63
  return false;
64
64
  }
@@ -235,19 +235,19 @@ var SKIP_FILES = /* @__PURE__ */ new Set([
235
235
  "flake.lock"
236
236
  ]);
237
237
  function toPosix(p) {
238
- return p.split(path17.sep).join("/");
238
+ return p.split(path18.sep).join("/");
239
239
  }
240
240
  function buildRootIgnore(root, exclude) {
241
241
  const ig = ignore();
242
- const gitignorePath = path17.join(root, ".gitignore");
243
- if (fs15.existsSync(gitignorePath)) {
244
- ig.add(fs15.readFileSync(gitignorePath, "utf8"));
242
+ const gitignorePath = path18.join(root, ".gitignore");
243
+ if (fs16.existsSync(gitignorePath)) {
244
+ ig.add(fs16.readFileSync(gitignorePath, "utf8"));
245
245
  }
246
246
  if (exclude.length) ig.add(exclude);
247
247
  return ig;
248
248
  }
249
249
  function discover(options) {
250
- const root = path17.resolve(options.root);
250
+ const root = path18.resolve(options.root);
251
251
  const onlyLangs = (options.only ?? []).filter(Boolean);
252
252
  const allowLang = (lang) => onlyLangs.length === 0 || onlyLangs.includes(lang.id);
253
253
  for (const id of onlyLangs) {
@@ -256,27 +256,27 @@ function discover(options) {
256
256
  }
257
257
  }
258
258
  const rootIg = buildRootIgnore(root, options.exclude ?? []);
259
- const scopeAbs = (options.paths && options.paths.length ? options.paths.map((p) => path17.resolve(root, p)) : [root]).filter((p) => fs15.existsSync(p));
259
+ const scopeAbs = (options.paths && options.paths.length ? options.paths.map((p) => path18.resolve(root, p)) : [root]).filter((p) => fs16.existsSync(p));
260
260
  const found = /* @__PURE__ */ new Map();
261
261
  const considerFile = (abs) => {
262
- const rel2 = toPosix(path17.relative(root, abs));
262
+ const rel2 = toPosix(path18.relative(root, abs));
263
263
  if (rel2.startsWith("..")) return;
264
264
  if (rel2 === "" || rootIg.ignores(rel2)) return;
265
- if (SKIP_FILES.has(path17.basename(abs).toLowerCase())) return;
266
- const lang = langForExtension(path17.extname(abs));
265
+ if (SKIP_FILES.has(path18.basename(abs).toLowerCase())) return;
266
+ const lang = langForExtension(path18.extname(abs));
267
267
  if (!lang || !allowLang(lang)) return;
268
268
  found.set(rel2, { rel: rel2, abs, lang });
269
269
  };
270
270
  const walk = (dir) => {
271
271
  let entries;
272
272
  try {
273
- entries = fs15.readdirSync(dir, { withFileTypes: true });
273
+ entries = fs16.readdirSync(dir, { withFileTypes: true });
274
274
  } catch {
275
275
  return;
276
276
  }
277
277
  for (const entry of entries) {
278
- const abs = path17.join(dir, entry.name);
279
- const rel2 = toPosix(path17.relative(root, abs));
278
+ const abs = path18.join(dir, entry.name);
279
+ const rel2 = toPosix(path18.relative(root, abs));
280
280
  if (entry.isDirectory()) {
281
281
  if (SKIP_DIRS.has(entry.name)) continue;
282
282
  if (rel2 && rootIg.ignores(`${rel2}/`)) continue;
@@ -287,7 +287,7 @@ function discover(options) {
287
287
  }
288
288
  };
289
289
  for (const scope of scopeAbs) {
290
- const stat = fs15.statSync(scope);
290
+ const stat = fs16.statSync(scope);
291
291
  if (stat.isDirectory()) walk(scope);
292
292
  else if (stat.isFile()) considerFile(scope);
293
293
  }
@@ -540,25 +540,36 @@ function mulberry32(seed) {
540
540
  var CLUSTER_SEED = 42;
541
541
 
542
542
  // src/engine/analyze.ts
543
+ var TIER_LARGE_NODES = 5e3;
544
+ var TIER_XL_NODES = 5e4;
545
+ function pickAnalysisTier(nodeCount, forced) {
546
+ if (forced) return forced;
547
+ if (nodeCount > TIER_XL_NODES) return "xl";
548
+ if (nodeCount > TIER_LARGE_NODES) return "large";
549
+ return "full";
550
+ }
543
551
  var DEP_EDGES = /* @__PURE__ */ new Set(["call", "import", "extends", "implements", "references"]);
544
552
  var CLUSTER_EDGE_KINDS = /* @__PURE__ */ new Set([...DEP_EDGES, "contains"]);
545
553
  var IMPORTANCE_WEIGHTS = { pagerank: 0.4, betweenness: 0.25, eigenvector: 0.2, degree: 0.15 };
546
554
  var DEFAULT_BETWEENNESS_LIMIT = 5e3;
547
555
  function analyze(nodes, edges, options = {}) {
548
- const mode = options.cluster ?? "louvain";
556
+ const tier = pickAnalysisTier(nodes.length, options.tier);
557
+ const mode = options.cluster ?? (tier === "xl" ? "louvain" : "louvain");
549
558
  const ids = [...nodes.map((n) => n.id)].sort((a, b) => a.localeCompare(b));
550
559
  const dg = simpleGraph(ids, edges, DEP_EDGES, "directed");
551
560
  const pr = safeMetric(() => pagerank(dg), ids);
552
- const bw = dg.order <= (options.betweennessLimit ?? DEFAULT_BETWEENNESS_LIMIT) ? safeMetric(() => betweenness(dg), ids) : zeros(ids);
553
- const ev = safeMetric(() => eigenvector(dg), ids);
561
+ const bwLimit = options.betweennessLimit ?? DEFAULT_BETWEENNESS_LIMIT;
562
+ const bw = tier === "full" && dg.order <= bwLimit ? safeMetric(() => betweenness(dg), ids) : zeros(ids);
563
+ const ev = tier === "full" || tier === "large" ? safeMetric(() => eigenvector(dg), ids) : zeros(ids);
554
564
  const deg = degreeMap(dg, ids);
555
565
  const prN = normalize(pr);
556
566
  const bwN = normalize(bw);
557
567
  const evN = normalize(ev);
558
568
  const degN = normalize(deg);
569
+ const weights = tier === "xl" ? { pagerank: 0.7, betweenness: 0, eigenvector: 0, degree: 0.3 } : tier === "large" ? { pagerank: 0.5, betweenness: 0, eigenvector: 0.25, degree: 0.25 } : IMPORTANCE_WEIGHTS;
559
570
  const importance = /* @__PURE__ */ new Map();
560
571
  for (const id of ids) {
561
- const score = IMPORTANCE_WEIGHTS.pagerank * (prN.get(id) ?? 0) + IMPORTANCE_WEIGHTS.betweenness * (bwN.get(id) ?? 0) + IMPORTANCE_WEIGHTS.eigenvector * (evN.get(id) ?? 0) + IMPORTANCE_WEIGHTS.degree * (degN.get(id) ?? 0);
572
+ const score = weights.pagerank * (prN.get(id) ?? 0) + weights.betweenness * (bwN.get(id) ?? 0) + weights.eigenvector * (evN.get(id) ?? 0) + weights.degree * (degN.get(id) ?? 0);
562
573
  importance.set(id, score);
563
574
  }
564
575
  const importanceN = normalize(importance);
@@ -567,7 +578,7 @@ function analyze(nodes, edges, options = {}) {
567
578
  const variance = vals.reduce((a, b) => a + (b - mean) ** 2, 0) / (vals.length || 1);
568
579
  const std = Math.sqrt(variance);
569
580
  const hubThreshold = mean + 2 * std;
570
- const { areaByNode, mode: actualMode } = cluster(ids, edges, mode);
581
+ const { areaByNode, mode: actualMode } = tier === "xl" ? clusterFileLevel(nodes, edges, mode) : cluster(ids, edges, mode);
571
582
  const outNodes = nodes.map((n) => ({
572
583
  ...n,
573
584
  centrality: {
@@ -582,7 +593,42 @@ function analyze(nodes, edges, options = {}) {
582
593
  })).sort((a, b) => a.id.localeCompare(b.id));
583
594
  const areas = buildAreas(outNodes, edges, areaByNode);
584
595
  const outEdges = scoreSurprise(edges, areaByNode);
585
- return { nodes: outNodes, edges: outEdges, areas, cluster: actualMode };
596
+ return { nodes: outNodes, edges: outEdges, areas, cluster: actualMode, tier };
597
+ }
598
+ function clusterFileLevel(nodes, edges, mode) {
599
+ const fileNodes = nodes.filter((n) => n.kind === "file");
600
+ const fileIds = fileNodes.map((n) => n.id).sort((a, b) => a.localeCompare(b));
601
+ if (fileIds.length === 0) {
602
+ return { areaByNode: new Map(nodes.map((n) => [n.id, -1])), mode: "none" };
603
+ }
604
+ const fileIdByPath = new Map(fileNodes.map((n) => [n.file, n.id]));
605
+ const fileOf = /* @__PURE__ */ new Map();
606
+ for (const n of nodes) {
607
+ if (n.kind === "file") fileOf.set(n.id, n.id);
608
+ else if (n.file) {
609
+ const fid = fileIdByPath.get(n.file);
610
+ if (fid) fileOf.set(n.id, fid);
611
+ }
612
+ }
613
+ const fileEdges = [];
614
+ const seen = /* @__PURE__ */ new Set();
615
+ for (const e of edges) {
616
+ if (!DEP_EDGES.has(e.kind)) continue;
617
+ const sf = fileOf.get(e.src);
618
+ const df = fileOf.get(e.dst);
619
+ if (!sf || !df || sf === df) continue;
620
+ const key = `${e.kind}|${sf}|${df}`;
621
+ if (seen.has(key)) continue;
622
+ seen.add(key);
623
+ fileEdges.push({ ...e, src: sf, dst: df, id: key });
624
+ }
625
+ const { areaByNode: fileAreas, mode: used } = cluster(fileIds, fileEdges, mode);
626
+ const areaByNode = /* @__PURE__ */ new Map();
627
+ for (const n of nodes) {
628
+ const fid = fileOf.get(n.id);
629
+ areaByNode.set(n.id, fid ? fileAreas.get(fid) ?? -1 : -1);
630
+ }
631
+ return { areaByNode, mode: used };
586
632
  }
587
633
  function simpleGraph(ids, edges, kinds, type) {
588
634
  const g = new Graph({ type, multi: false, allowSelfLoops: false });
@@ -723,14 +769,14 @@ var DEFAULT_PATHS = [
723
769
  ];
724
770
  function loadCoverage(root, explicit) {
725
771
  const candidates = (explicit && explicit.length ? explicit : DEFAULT_PATHS).map(
726
- (p) => path17.resolve(root, p)
772
+ (p) => path18.resolve(root, p)
727
773
  );
728
- const found = candidates.filter((p) => fs15.existsSync(p));
774
+ const found = candidates.filter((p) => fs16.existsSync(p));
729
775
  if (found.length === 0) return null;
730
776
  const map = /* @__PURE__ */ new Map();
731
777
  for (const file of found) {
732
778
  try {
733
- const text = fs15.readFileSync(file, "utf8");
779
+ const text = fs16.readFileSync(file, "utf8");
734
780
  if (file.endsWith(".json")) mergeIstanbul(map, text, root);
735
781
  else mergeLcov(map, text, root);
736
782
  } catch {
@@ -739,8 +785,8 @@ function loadCoverage(root, explicit) {
739
785
  return map.size ? map : null;
740
786
  }
741
787
  function rel(root, p) {
742
- const abs = path17.isAbsolute(p) ? p : path17.resolve(root, p);
743
- return path17.relative(root, abs).split(path17.sep).join("/");
788
+ const abs = path18.isAbsolute(p) ? p : path18.resolve(root, p);
789
+ return path18.relative(root, abs).split(path18.sep).join("/");
744
790
  }
745
791
  function bump(map, file, line, hits) {
746
792
  let lh = map.get(file);
@@ -1027,9 +1073,6 @@ function matchConfidence(entry, nameParts, imports, callees) {
1027
1073
  }
1028
1074
  return conf;
1029
1075
  }
1030
-
1031
- // src/schema.ts
1032
- var SCHEMA_VERSION = "vg-graph/1.0";
1033
1076
  var DEFAULT_INLINE_THRESHOLD = 24;
1034
1077
  async function parseFiles(files, options = {}) {
1035
1078
  const threshold = options.inlineThreshold ?? DEFAULT_INLINE_THRESHOLD;
@@ -1050,7 +1093,7 @@ async function parseInline(files, options) {
1050
1093
  onProgress?.(0, files.length);
1051
1094
  for (const file of files) {
1052
1095
  try {
1053
- const source = fs15.readFileSync(file.abs, "utf8");
1096
+ const source = fs16.readFileSync(file.abs, "utf8");
1054
1097
  out.push(await parseSource(file.rel, file.lang.id, source));
1055
1098
  } catch (err) {
1056
1099
  out.push(emptyParse(file, `parse failed: ${err.message}`));
@@ -1105,9 +1148,9 @@ function isWorkerOom(err) {
1105
1148
  return e?.code === "ERR_WORKER_OUT_OF_MEMORY" || /out of memory/i.test(e?.message ?? "");
1106
1149
  }
1107
1150
  function resolveWorkerFile() {
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;
1151
+ const here = path18.dirname(fileURLToPath(import.meta.url));
1152
+ const candidate = path18.join(here, "parse-worker.js");
1153
+ return fs16.existsSync(candidate) ? candidate : null;
1111
1154
  }
1112
1155
  function chunk(items, buckets) {
1113
1156
  const out = Array.from({ length: Math.min(buckets, items.length || 1) }, () => []);
@@ -1356,7 +1399,7 @@ function enclosing3(nodes, line) {
1356
1399
  return best;
1357
1400
  }
1358
1401
  function normalize2(p) {
1359
- return path17.resolve(p).split(path17.sep).join("/");
1402
+ return path18.resolve(p).split(path18.sep).join("/");
1360
1403
  }
1361
1404
 
1362
1405
  // src/engine/epistemic.ts
@@ -1599,14 +1642,14 @@ var IMPORTANCE = {
1599
1642
  "other-config": 0.08
1600
1643
  };
1601
1644
  function toPosix2(p) {
1602
- return p.split(path17.sep).join("/");
1645
+ return p.split(path18.sep).join("/");
1603
1646
  }
1604
1647
  function buildRootIgnore2(root, exclude) {
1605
1648
  const ig = ignore();
1606
- const gitignorePath = path17.join(root, ".gitignore");
1607
- if (fs15.existsSync(gitignorePath)) {
1649
+ const gitignorePath = path18.join(root, ".gitignore");
1650
+ if (fs16.existsSync(gitignorePath)) {
1608
1651
  try {
1609
- ig.add(fs15.readFileSync(gitignorePath, "utf8"));
1652
+ ig.add(fs16.readFileSync(gitignorePath, "utf8"));
1610
1653
  } catch {
1611
1654
  }
1612
1655
  }
@@ -1615,7 +1658,7 @@ function buildRootIgnore2(root, exclude) {
1615
1658
  }
1616
1659
  function classifyProjectContext(relOrName) {
1617
1660
  const rel2 = relOrName.replace(/\\/g, "/");
1618
- const base = path17.basename(rel2);
1661
+ const base = path18.basename(rel2);
1619
1662
  const baseLower = base.toLowerCase();
1620
1663
  if (/^\.env($|\.)/i.test(base) && !isEnvExampleName(baseLower)) return null;
1621
1664
  if (SKIP_FILES.has(baseLower)) return null;
@@ -1628,7 +1671,7 @@ function classifyProjectContext(relOrName) {
1628
1671
  for (const { re, category } of PATH_PATTERNS) {
1629
1672
  if (re.test(rel2)) return category;
1630
1673
  }
1631
- const ext = path17.extname(baseLower);
1674
+ const ext = path18.extname(baseLower);
1632
1675
  if (DOC_EXTENSIONS.has(ext)) return ext === ".txt" ? "text" : "markdown";
1633
1676
  if (API_EXTENSIONS.has(ext)) return "api-contract";
1634
1677
  if ((ext === ".yaml" || ext === ".yml" || ext === ".json") && /openapi|swagger|asyncapi/i.test(base)) {
@@ -1646,24 +1689,24 @@ function isEnvExampleName(baseLower) {
1646
1689
  return false;
1647
1690
  }
1648
1691
  function discoverDocs(options) {
1649
- const root = path17.resolve(options.root);
1692
+ const root = path18.resolve(options.root);
1650
1693
  const maxFiles = options.maxFiles ?? DOC_MAX_FILES;
1651
1694
  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)
1695
+ const scopeAbs = (options.paths?.length ? options.paths.map((p) => path18.resolve(root, p)) : [root]).filter(
1696
+ (p) => fs16.existsSync(p)
1654
1697
  );
1655
1698
  const found = /* @__PURE__ */ new Map();
1656
1699
  const consider = (abs) => {
1657
1700
  if (found.size >= maxFiles) return;
1658
- const rel2 = toPosix2(path17.relative(root, abs));
1701
+ const rel2 = toPosix2(path18.relative(root, abs));
1659
1702
  if (!rel2 || rel2.startsWith("..")) return;
1660
1703
  if (rootIg.ignores(rel2)) return;
1661
- const base = path17.basename(abs);
1704
+ const base = path18.basename(abs);
1662
1705
  if (SKIP_FILES.has(base.toLowerCase())) return;
1663
1706
  const category = classifyProjectContext(rel2);
1664
1707
  if (!category) return;
1665
1708
  try {
1666
- const st = fs15.statSync(abs);
1709
+ const st = fs16.statSync(abs);
1667
1710
  if (!st.isFile() || st.size > DOC_FILE_MAX_BYTES) return;
1668
1711
  if (st.size === 0) return;
1669
1712
  } catch {
@@ -1675,14 +1718,14 @@ function discoverDocs(options) {
1675
1718
  if (found.size >= maxFiles || depth > 12) return;
1676
1719
  let entries;
1677
1720
  try {
1678
- entries = fs15.readdirSync(dir, { withFileTypes: true });
1721
+ entries = fs16.readdirSync(dir, { withFileTypes: true });
1679
1722
  } catch {
1680
1723
  return;
1681
1724
  }
1682
1725
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
1683
1726
  if (found.size >= maxFiles) return;
1684
- const abs = path17.join(dir, entry.name);
1685
- const rel2 = toPosix2(path17.relative(root, abs));
1727
+ const abs = path18.join(dir, entry.name);
1728
+ const rel2 = toPosix2(path18.relative(root, abs));
1686
1729
  if (entry.isDirectory()) {
1687
1730
  if (SKIP_DIRS.has(entry.name)) continue;
1688
1731
  if (rel2 && rootIg.ignores(`${rel2}/`) && !rel2.startsWith(".github")) continue;
@@ -1694,7 +1737,7 @@ function discoverDocs(options) {
1694
1737
  };
1695
1738
  for (const scope of scopeAbs) {
1696
1739
  try {
1697
- const st = fs15.statSync(scope);
1740
+ const st = fs16.statSync(scope);
1698
1741
  if (st.isDirectory()) walk(scope, 0);
1699
1742
  else if (st.isFile()) consider(scope);
1700
1743
  } catch {
@@ -1705,7 +1748,7 @@ function discoverDocs(options) {
1705
1748
  function prepareDocBody(rel2, raw, category) {
1706
1749
  const scrubbed = redactSecrets(raw);
1707
1750
  if (scrubbed.length <= DOC_BODY_MAX_CHARS) return scrubbed;
1708
- if (category === "manifest" && path17.basename(rel2).toLowerCase() === "package.json") {
1751
+ if (category === "manifest" && path18.basename(rel2).toLowerCase() === "package.json") {
1709
1752
  return summarizePackageJson(scrubbed);
1710
1753
  }
1711
1754
  if (category === "api-contract" && /\.(ya?ml|json)$/i.test(rel2)) {
@@ -1751,14 +1794,14 @@ function documentNodesFromDocs(docs) {
1751
1794
  for (const d of docs) {
1752
1795
  let raw = "";
1753
1796
  try {
1754
- raw = fs15.readFileSync(d.abs, "utf8");
1797
+ raw = fs16.readFileSync(d.abs, "utf8");
1755
1798
  } catch {
1756
1799
  continue;
1757
1800
  }
1758
1801
  const category = d.category ?? d.kind;
1759
1802
  const scrubbed = prepareDocBody(d.rel, raw, category);
1760
1803
  if (!scrubbed.trim()) continue;
1761
- const name = path17.basename(d.rel);
1804
+ const name = path18.basename(d.rel);
1762
1805
  const qualifiedName = d.rel.replace(/\\/g, "/");
1763
1806
  const id = nodeId({
1764
1807
  kind: "document",
@@ -1787,17 +1830,89 @@ function documentNodesFromDocs(docs) {
1787
1830
  }
1788
1831
  return nodes.sort((a, b) => a.file.localeCompare(b.file) || a.id.localeCompare(b.id));
1789
1832
  }
1833
+ function shardTsProjects(root, files) {
1834
+ if (files.length === 0) return [];
1835
+ const cache = /* @__PURE__ */ new Map();
1836
+ const byKey = /* @__PURE__ */ new Map();
1837
+ for (const f of files) {
1838
+ const dir = path18.dirname(f.abs);
1839
+ const key = nearestProjectKey(root, dir, cache);
1840
+ const list = byKey.get(key);
1841
+ if (list) list.push(f);
1842
+ else byKey.set(key, [f]);
1843
+ }
1844
+ return [...byKey.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([key, shardFiles]) => ({
1845
+ key,
1846
+ files: [...shardFiles].sort((a, b) => a.rel.localeCompare(b.rel))
1847
+ }));
1848
+ }
1849
+ function nearestProjectKey(root, startDir, cache) {
1850
+ const hit = cache.get(startDir);
1851
+ if (hit !== void 0) return hit;
1852
+ let dir = startDir;
1853
+ const rootAbs = path18.resolve(root);
1854
+ while (true) {
1855
+ if (fs16.existsSync(path18.join(dir, "tsconfig.json")) || fs16.existsSync(path18.join(dir, "tsconfig.base.json")) || fs16.existsSync(path18.join(dir, "package.json"))) {
1856
+ const key = toPosix3(path18.relative(rootAbs, dir)) || ".";
1857
+ cache.set(startDir, key);
1858
+ return key;
1859
+ }
1860
+ if (dir === rootAbs || path18.dirname(dir) === dir) {
1861
+ cache.set(startDir, ".");
1862
+ return ".";
1863
+ }
1864
+ dir = path18.dirname(dir);
1865
+ }
1866
+ }
1867
+ function toPosix3(p) {
1868
+ return p.split(path18.sep).join("/");
1869
+ }
1870
+
1871
+ // src/engine/timing.ts
1872
+ var StageTimer = class {
1873
+ marks = /* @__PURE__ */ new Map();
1874
+ durations = {};
1875
+ start(name) {
1876
+ this.marks.set(name, nowMs());
1877
+ }
1878
+ end(name) {
1879
+ const t0 = this.marks.get(name);
1880
+ if (t0 === void 0) return 0;
1881
+ const ms = Math.round((nowMs() - t0) * 1e3) / 1e3;
1882
+ this.durations[name] = ms;
1883
+ this.marks.delete(name);
1884
+ return ms;
1885
+ }
1886
+ /** Run `fn` under a named stage. */
1887
+ async measure(name, fn) {
1888
+ this.start(name);
1889
+ try {
1890
+ return await fn();
1891
+ } finally {
1892
+ this.end(name);
1893
+ }
1894
+ }
1895
+ snapshot() {
1896
+ return { ...this.durations };
1897
+ }
1898
+ };
1899
+ function nowMs() {
1900
+ return Number(process.hrtime.bigint() / 1000n) / 1e3;
1901
+ }
1790
1902
 
1791
1903
  // src/engine/build.ts
1792
1904
  async function buildGraph(options) {
1793
- const start = nowMs();
1794
- const root = path17.resolve(options.root);
1905
+ const timer = new StageTimer();
1906
+ timer.start("total");
1907
+ const root = path18.resolve(options.root);
1908
+ timer.start("discover");
1795
1909
  const files = discover({
1796
1910
  root,
1797
1911
  only: options.only,
1798
1912
  exclude: options.exclude,
1799
1913
  paths: options.paths
1800
1914
  });
1915
+ timer.end("discover");
1801
1916
  const limits = resolveLimits(options.limits);
1802
1917
  if (limits.maxFiles > 0 && files.length > limits.maxFiles) {
1803
1918
  throw new ResourceLimitError(
@@ -1811,15 +1926,17 @@ async function buildGraph(options) {
1811
1926
  grammars,
1812
1927
  disabled: options.noCache
1813
1928
  });
1929
+ timer.start("hash");
1814
1930
  const hashes = /* @__PURE__ */ new Map();
1815
1931
  const fileStats = [];
1816
1932
  const toParse = [];
1817
1933
  const reused = [];
1818
1934
  const buildWarnings = [];
1935
+ let statHits = 0;
1819
1936
  for (const file of files) {
1820
1937
  let stat;
1821
1938
  try {
1822
- stat = fs15.statSync(file.abs);
1939
+ stat = fs16.statSync(file.abs);
1823
1940
  } catch {
1824
1941
  continue;
1825
1942
  }
@@ -1835,18 +1952,38 @@ async function buildGraph(options) {
1835
1952
  );
1836
1953
  continue;
1837
1954
  }
1955
+ const byStat = options.noCache ? void 0 : cache.getByStat(file.rel, stat.mtimeMs, stat.size);
1956
+ if (byStat) {
1957
+ hashes.set(file.rel, byStat.hash);
1958
+ if (byStat.parse.hash !== byStat.hash) byStat.parse.hash = byStat.hash;
1959
+ fileStats.push({
1960
+ rel: file.rel,
1961
+ size: stat.size,
1962
+ mtimeMs: stat.mtimeMs,
1963
+ hash: byStat.hash
1964
+ });
1965
+ reused.push(byStat.parse);
1966
+ statHits++;
1967
+ continue;
1968
+ }
1838
1969
  let hash = "";
1839
1970
  try {
1840
- hash = hashBytes(fs15.readFileSync(file.abs));
1971
+ hash = hashBytes(fs16.readFileSync(file.abs));
1841
1972
  } catch {
1842
1973
  continue;
1843
1974
  }
1844
1975
  hashes.set(file.rel, hash);
1845
1976
  fileStats.push({ rel: file.rel, size: stat.size, mtimeMs: stat.mtimeMs, hash });
1846
1977
  const cached2 = cache.get(file.rel, hash);
1847
- if (cached2) reused.push(cached2);
1848
- else toParse.push(file);
1978
+ if (cached2) {
1979
+ reused.push(cached2);
1980
+ cache.set(file.rel, cached2, { mtimeMs: stat.mtimeMs, size: stat.size });
1981
+ } else {
1982
+ toParse.push(file);
1983
+ }
1849
1984
  }
1985
+ timer.end("hash");
1986
+ timer.start("parse");
1850
1987
  const parsedNew = await parseFiles(toParse, {
1851
1988
  jobs: options.jobs,
1852
1989
  inline: options.inline,
@@ -1854,8 +1991,12 @@ async function buildGraph(options) {
1854
1991
  grammarsDir: options.grammarsDir,
1855
1992
  memoryBudgetMb: limits.memoryBudgetMb
1856
1993
  });
1994
+ timer.end("parse");
1857
1995
  checkMemoryBudget("parse", limits.memoryBudgetMb);
1858
- for (const p of parsedNew) cache.set(p.rel, p);
1996
+ for (const p of parsedNew) {
1997
+ const st = fileStats.find((f) => f.rel === p.rel);
1998
+ cache.set(p.rel, p, st ? { mtimeMs: st.mtimeMs, size: st.size } : void 0);
1999
+ }
1859
2000
  const currentRels = new Set(files.map((f) => f.rel));
1860
2001
  cache.prune(currentRels);
1861
2002
  cache.save();
@@ -1863,31 +2004,61 @@ async function buildGraph(options) {
1863
2004
  (a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0
1864
2005
  );
1865
2006
  const warnings = [...buildWarnings, ...parses.flatMap((p) => p.warnings ?? [])];
2007
+ timer.start("resolve");
1866
2008
  const moduleResolver = buildModuleResolver(root, new Set(parses.map((p) => p.rel)));
1867
2009
  const resolved = resolve(parses, moduleResolver);
2010
+ timer.end("resolve");
1868
2011
  checkMemoryBudget("resolve", limits.memoryBudgetMb);
1869
2012
  const nodeFileById = new Map(resolved.nodes.map((n) => [n.id, n.file]));
1870
2013
  let edges = resolved.edges;
1871
2014
  const resolvers = [...resolved.stats.resolvers];
1872
2015
  const preciseCoveredFiles = /* @__PURE__ */ new Set();
1873
2016
  let tscStats;
1874
- let tsFiles = options.noTsc ? [] : files.filter((f) => (f.lang.id === "ts" || f.lang.id === "tsx" || f.lang.id === "js") && hashes.has(f.rel)).map((f) => ({ rel: f.rel, abs: f.abs }));
2017
+ const skipTsc = options.noTsc || options.fast;
2018
+ let tsFiles = skipTsc ? [] : files.filter((f) => (f.lang.id === "ts" || f.lang.id === "tsx" || f.lang.id === "js") && hashes.has(f.rel)).map((f) => ({ rel: f.rel, abs: f.abs }));
1875
2019
  if (limits.tscMaxFiles > 0 && tsFiles.length > limits.tscMaxFiles) {
1876
2020
  warnings.push(
1877
2021
  `typescript resolver skipped \u2014 ${tsFiles.length.toLocaleString()} TS/JS files exceed the ${limits.tscMaxFiles.toLocaleString()}-file limit; calls use the heuristic resolver (set VG_TSC_MAX_FILES to raise it, 0 to disable)`
1878
2022
  );
1879
2023
  tsFiles = [];
1880
2024
  }
2025
+ timer.start("tsc");
1881
2026
  if (tsFiles.length) {
1882
- const res = tsResolveEdges(root, tsFiles, resolved.nodes);
1883
- if (res.stats.files > 0) {
1884
- edges = mergePreciseEdges(edges, res.edges, res.coveredFiles, nodeFileById);
1885
- for (const f of res.coveredFiles) preciseCoveredFiles.add(f);
2027
+ const shards = shardTsProjects(root, tsFiles);
2028
+ let filesCovered = 0;
2029
+ let calls = 0;
2030
+ let jsx = 0;
2031
+ let heritage = 0;
2032
+ let resolvedCount = 0;
2033
+ const coveredAll = /* @__PURE__ */ new Set();
2034
+ const preciseAll = [];
2035
+ for (const shard of shards) {
2036
+ const res = tsResolveEdges(root, shard.files, resolved.nodes);
2037
+ if (res.stats.files === 0) continue;
2038
+ filesCovered += res.stats.files;
2039
+ calls += res.stats.calls;
2040
+ jsx += res.stats.jsx;
2041
+ heritage += res.stats.heritage;
2042
+ resolvedCount += res.stats.resolved;
2043
+ for (const f of res.coveredFiles) coveredAll.add(f);
2044
+ preciseAll.push(...res.edges);
2045
+ }
2046
+ if (filesCovered > 0) {
2047
+ edges = mergePreciseEdges(edges, preciseAll, coveredAll, nodeFileById);
2048
+ for (const f of coveredAll) preciseCoveredFiles.add(f);
1886
2049
  if (!resolvers.includes("tsc")) resolvers.unshift("tsc");
1887
- tscStats = res.stats;
2050
+ tscStats = {
2051
+ files: filesCovered,
2052
+ calls,
2053
+ jsx,
2054
+ heritage,
2055
+ resolved: resolvedCount,
2056
+ shards: shards.length
2057
+ };
1888
2058
  }
1889
2059
  checkMemoryBudget("typescript resolution", limits.memoryBudgetMb);
1890
2060
  }
2061
+ timer.end("tsc");
1891
2062
  const scip = options.noScip ? null : loadScipIndex(root, options.scip);
1892
2063
  let scipStats;
1893
2064
  if (scip) {
@@ -1908,8 +2079,8 @@ async function buildGraph(options) {
1908
2079
  });
1909
2080
  for (const d of docs) {
1910
2081
  try {
1911
- const st = fs15.statSync(d.abs);
1912
- const hash = hashBytes(fs15.readFileSync(d.abs));
2082
+ const st = fs16.statSync(d.abs);
2083
+ const hash = hashBytes(fs16.readFileSync(d.abs));
1913
2084
  hashes.set(d.rel, hash);
1914
2085
  fileStats.push({ rel: d.rel, size: st.size, mtimeMs: st.mtimeMs, hash });
1915
2086
  } catch {
@@ -1917,7 +2088,12 @@ async function buildGraph(options) {
1917
2088
  }
1918
2089
  const docNodes = documentNodesFromDocs(docs);
1919
2090
  if (docNodes.length) nodes = [...nodes, ...docNodes];
1920
- const analysis = analyze(nodes, linked.edges, { cluster: options.cluster });
2091
+ timer.start("analyze");
2092
+ const analysis = analyze(nodes, linked.edges, {
2093
+ cluster: options.cluster,
2094
+ tier: options.analysisTier
2095
+ });
2096
+ timer.end("analyze");
1921
2097
  checkMemoryBudget("analysis", limits.memoryBudgetMb);
1922
2098
  const languages = [.../* @__PURE__ */ new Set([...parses.map((p) => p.lang), ...docNodes.map((n) => n.lang)])].sort();
1923
2099
  const edgeKinds = [...new Set(analysis.edges.map((e) => e.kind))].sort();
@@ -1945,8 +2121,9 @@ async function buildGraph(options) {
1945
2121
  toolchain
1946
2122
  },
1947
2123
  meta: {
1948
- root: path17.basename(root) === "" ? "." : ".",
2124
+ root: path18.basename(root) === "" ? "." : ".",
1949
2125
  languages,
2126
+ analysisTier: analysis.tier,
1950
2127
  counts: {
1951
2128
  nodes: analysis.nodes.length,
1952
2129
  edges: analysis.edges.length,
@@ -1972,16 +2149,27 @@ async function buildGraph(options) {
1972
2149
  const grounding = groundGraph(analysis.nodes, analysis.edges, parses);
1973
2150
  if (grounding.length) graph.grounding = grounding;
1974
2151
  }
2152
+ let indexResult;
2153
+ if (!options.noIndex) {
2154
+ timer.start("index");
2155
+ const wr = writeGraphIndex(root, graph);
2156
+ indexResult = { ok: wr.ok, path: wr.ok ? wr.path : void 0, reason: wr.reason };
2157
+ timer.end("index");
2158
+ }
2159
+ timer.end("total");
2160
+ const stages = timer.snapshot();
1975
2161
  return {
1976
2162
  graph,
1977
- timing: { totalMs: Math.round((nowMs() - start) * 1e3) / 1e3 },
2163
+ timing: { totalMs: stages.total ?? 0, stages },
1978
2164
  reparsed: parsedNew.length,
1979
2165
  reused: reused.length,
2166
+ statHits,
1980
2167
  totalFiles: files.length,
1981
2168
  fileStats,
1982
2169
  resolveStats: resolved.stats,
1983
2170
  tsc: tscStats,
1984
2171
  scip: scipStats,
2172
+ index: indexResult,
1985
2173
  warnings
1986
2174
  };
1987
2175
  }
@@ -1991,14 +2179,14 @@ function toRepoRel(p) {
1991
2179
  function loadScipIndex(root, explicit) {
1992
2180
  const candidates = [
1993
2181
  explicit,
1994
- path17.join(root, "index.scip"),
1995
- path17.join(root, ".vibgrate", "index.scip")
2182
+ path18.join(root, "index.scip"),
2183
+ path18.join(root, ".vibgrate", "index.scip")
1996
2184
  ].filter((p) => Boolean(p));
1997
2185
  for (const file of candidates) {
1998
- const abs = path17.isAbsolute(file) ? file : path17.resolve(root, file);
1999
- if (!fs15.existsSync(abs)) continue;
2186
+ const abs = path18.isAbsolute(file) ? file : path18.resolve(root, file);
2187
+ if (!fs16.existsSync(abs)) continue;
2000
2188
  try {
2001
- const index = decodeScipIndex(new Uint8Array(fs15.readFileSync(abs)));
2189
+ const index = decodeScipIndex(new Uint8Array(fs16.readFileSync(abs)));
2002
2190
  if (index.documents.length) {
2003
2191
  return { index, tool: index.toolVersion ? `${index.toolName} ${index.toolVersion}` : index.toolName };
2004
2192
  }
@@ -2047,9 +2235,6 @@ function computeCorpusHash(parses, hashes) {
2047
2235
  const list = [...byRel.entries()].sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
2048
2236
  return hashString(canonicalize(list));
2049
2237
  }
2050
- function nowMs() {
2051
- return Number(process.hrtime.bigint() / 1000n) / 1e3;
2052
- }
2053
2238
 
2054
2239
  // src/engine/verify.ts
2055
2240
  var PINNED = "1970-01-01T00:00:00.000Z";
@@ -2116,21 +2301,21 @@ function usageError(message) {
2116
2301
  }
2117
2302
  var SNAPSHOT_VERSION = "vg-freshness/1";
2118
2303
  function snapshotPath(root) {
2119
- return path17.join(cacheDir(root), "freshness.json");
2304
+ return path18.join(cacheDir(root), "freshness.json");
2120
2305
  }
2121
2306
  function writeSnapshot(root, corpusHash, fileStats, scope = {}) {
2122
2307
  const files = {};
2123
2308
  for (const f of fileStats) files[f.rel] = { size: f.size, mtimeMs: f.mtimeMs, hash: f.hash };
2124
2309
  const snapshot = { version: SNAPSHOT_VERSION, corpusHash, scope: pruneScope(scope), files };
2125
2310
  try {
2126
- fs15.mkdirSync(cacheDir(root), { recursive: true });
2127
- fs15.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2311
+ fs16.mkdirSync(cacheDir(root), { recursive: true });
2312
+ fs16.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2128
2313
  } catch {
2129
2314
  }
2130
2315
  }
2131
2316
  function loadSnapshot(root) {
2132
2317
  try {
2133
- const loaded = JSON.parse(fs15.readFileSync(snapshotPath(root), "utf8"));
2318
+ const loaded = JSON.parse(fs16.readFileSync(snapshotPath(root), "utf8"));
2134
2319
  if (loaded.version === SNAPSHOT_VERSION && loaded.files && typeof loaded.corpusHash === "string") {
2135
2320
  return { ...loaded, scope: loaded.scope ?? {} };
2136
2321
  }
@@ -2166,7 +2351,7 @@ function probeFreshness(root) {
2166
2351
  }
2167
2352
  let stat;
2168
2353
  try {
2169
- stat = fs15.statSync(file.abs);
2354
+ stat = fs16.statSync(file.abs);
2170
2355
  } catch {
2171
2356
  drift.removed.push(file.rel);
2172
2357
  continue;
@@ -2174,7 +2359,7 @@ function probeFreshness(root) {
2174
2359
  if (stat.size === entry.size && stat.mtimeMs === entry.mtimeMs) continue;
2175
2360
  let hash = "";
2176
2361
  try {
2177
- hash = hashBytes(fs15.readFileSync(file.abs));
2362
+ hash = hashBytes(fs16.readFileSync(file.abs));
2178
2363
  } catch {
2179
2364
  drift.removed.push(file.rel);
2180
2365
  continue;
@@ -2191,7 +2376,7 @@ function probeFreshness(root) {
2191
2376
  }
2192
2377
  if (absorbed && !hasDrift(drift)) {
2193
2378
  try {
2194
- fs15.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2379
+ fs16.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2195
2380
  } catch {
2196
2381
  }
2197
2382
  }
@@ -2272,10 +2457,10 @@ function testsToRun(graph, rootId, depth = 4) {
2272
2457
  };
2273
2458
  }
2274
2459
  function detectRunner(root, lang) {
2275
- const pkgPath = path17.join(root, "package.json");
2276
- if (fs15.existsSync(pkgPath)) {
2460
+ const pkgPath = path18.join(root, "package.json");
2461
+ if (fs16.existsSync(pkgPath)) {
2277
2462
  try {
2278
- const pkg = JSON.parse(fs15.readFileSync(pkgPath, "utf8"));
2463
+ const pkg = JSON.parse(fs16.readFileSync(pkgPath, "utf8"));
2279
2464
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
2280
2465
  if (deps.vitest) return { name: "vitest", command: (f) => `npx vitest run ${f.join(" ")}`.trim() };
2281
2466
  if (deps.jest) return { name: "jest", command: (f) => `npx jest ${f.join(" ")}`.trim() };
@@ -2283,10 +2468,10 @@ function detectRunner(root, lang) {
2283
2468
  } catch {
2284
2469
  }
2285
2470
  }
2286
- if (lang === "py" || fs15.existsSync(path17.join(root, "pyproject.toml")) || fs15.existsSync(path17.join(root, "pytest.ini"))) {
2471
+ if (lang === "py" || fs16.existsSync(path18.join(root, "pyproject.toml")) || fs16.existsSync(path18.join(root, "pytest.ini"))) {
2287
2472
  return { name: "pytest", command: (f) => `pytest ${f.join(" ")}`.trim() };
2288
2473
  }
2289
- if (lang === "go" || fs15.existsSync(path17.join(root, "go.mod"))) {
2474
+ if (lang === "go" || fs16.existsSync(path18.join(root, "go.mod"))) {
2290
2475
  return { name: "go test", command: () => "go test ./..." };
2291
2476
  }
2292
2477
  if (lang === "cs" || hasFile(root, /\.csproj$/) || hasFile(root, /\.sln$/)) {
@@ -2297,16 +2482,16 @@ function detectRunner(root, lang) {
2297
2482
  }
2298
2483
  function hasFile(root, re) {
2299
2484
  try {
2300
- return fs15.readdirSync(root).some((f) => re.test(f));
2485
+ return fs16.readdirSync(root).some((f) => re.test(f));
2301
2486
  } catch {
2302
2487
  return false;
2303
2488
  }
2304
2489
  }
2305
2490
  function findGitRoot(startDir = process.cwd()) {
2306
- let dir = path17.resolve(startDir);
2491
+ let dir = path18.resolve(startDir);
2307
2492
  for (; ; ) {
2308
- if (fs15.existsSync(path17.join(dir, ".git"))) return dir;
2309
- const parent = path17.dirname(dir);
2493
+ if (fs16.existsSync(path18.join(dir, ".git"))) return dir;
2494
+ const parent = path18.dirname(dir);
2310
2495
  if (parent === dir) return null;
2311
2496
  dir = parent;
2312
2497
  }
@@ -2317,19 +2502,19 @@ function normalizeEntry(line) {
2317
2502
  function ensureGitignored(entry, startDir = process.cwd()) {
2318
2503
  const root = findGitRoot(startDir);
2319
2504
  if (!root) return { status: "not-a-repo", entry };
2320
- const gitignorePath = path17.join(root, ".gitignore");
2505
+ const gitignorePath = path18.join(root, ".gitignore");
2321
2506
  const target = normalizeEntry(entry);
2322
2507
  let existing = "";
2323
2508
  let fileExisted = true;
2324
2509
  try {
2325
- existing = fs15.readFileSync(gitignorePath, "utf8");
2510
+ existing = fs16.readFileSync(gitignorePath, "utf8");
2326
2511
  } catch {
2327
2512
  fileExisted = false;
2328
2513
  }
2329
2514
  const alreadyPresent = existing.split(/\r?\n/).some((line) => normalizeEntry(line) === target);
2330
2515
  if (alreadyPresent) return { status: "present", entry, gitignorePath };
2331
2516
  const needsLeadingNewline = existing.length > 0 && !existing.endsWith("\n");
2332
- fs15.appendFileSync(gitignorePath, `${needsLeadingNewline ? "\n" : ""}${entry}
2517
+ fs16.appendFileSync(gitignorePath, `${needsLeadingNewline ? "\n" : ""}${entry}
2333
2518
  `, "utf8");
2334
2519
  return { status: fileExisted ? "added" : "created", entry, gitignorePath };
2335
2520
  }
@@ -2483,14 +2668,14 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
2483
2668
  console.log(chalk.dim("The secret must be registered on your Vibgrate ingest API."));
2484
2669
  }
2485
2670
  if (opts.write) {
2486
- const writePath = path17.resolve(opts.write);
2671
+ const writePath = path18.resolve(opts.write);
2487
2672
  await writeTextFile(writePath, dsn + "\n");
2488
2673
  console.log("");
2489
2674
  console.log(chalk.green("\u2714") + ` DSN written to ${opts.write}`);
2490
- const root = findGitRoot(path17.dirname(writePath));
2491
- const rel2 = root ? path17.relative(root, writePath).split(path17.sep).join("/") : "";
2675
+ const root = findGitRoot(path18.dirname(writePath));
2676
+ const rel2 = root ? path18.relative(root, writePath).split(path18.sep).join("/") : "";
2492
2677
  if (root && rel2 && !rel2.startsWith("..")) {
2493
- const res = ensureGitignored(rel2, path17.dirname(writePath));
2678
+ const res = ensureGitignored(rel2, path18.dirname(writePath));
2494
2679
  if (res.status === "created") {
2495
2680
  console.log(chalk.green("\u2714") + ` Created .gitignore and ignored ${rel2}`);
2496
2681
  } else if (res.status === "added") {
@@ -2508,30 +2693,30 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
2508
2693
  var STORE_DIRNAME = ".vibgrate";
2509
2694
  var STORE_FILENAME = "credentials.json";
2510
2695
  function homeCredentialsPath() {
2511
- return path17.join(os.homedir(), STORE_DIRNAME, STORE_FILENAME);
2696
+ return path18.join(os.homedir(), STORE_DIRNAME, STORE_FILENAME);
2512
2697
  }
2513
2698
  function projectCredentialsPath(cwd = process.cwd()) {
2514
2699
  const root = findGitRoot(cwd) ?? cwd;
2515
- return path17.join(root, STORE_DIRNAME, STORE_FILENAME);
2700
+ return path18.join(root, STORE_DIRNAME, STORE_FILENAME);
2516
2701
  }
2517
2702
  function credentialsPath(opts = {}) {
2518
2703
  const override = process.env.VIBGRATE_CREDENTIALS?.trim();
2519
- if (override) return path17.resolve(override);
2704
+ if (override) return path18.resolve(override);
2520
2705
  const local = projectCredentialsPath(opts.cwd);
2521
2706
  if (opts.local) return local;
2522
- if (fs15.existsSync(local)) return local;
2707
+ if (fs16.existsSync(local)) return local;
2523
2708
  return homeCredentialsPath();
2524
2709
  }
2525
2710
  function gitignoreEntryForCredentials(repoRoot, credsFile = credentialsPath()) {
2526
- const rel2 = path17.relative(repoRoot, credsFile);
2527
- if (rel2 && !rel2.startsWith("..") && !path17.isAbsolute(rel2)) {
2528
- return rel2.split(path17.sep).join("/");
2711
+ const rel2 = path18.relative(repoRoot, credsFile);
2712
+ if (rel2 && !rel2.startsWith("..") && !path18.isAbsolute(rel2)) {
2713
+ return rel2.split(path18.sep).join("/");
2529
2714
  }
2530
2715
  return `${STORE_DIRNAME}/${STORE_FILENAME}`;
2531
2716
  }
2532
2717
  function readStoredCredentials(opts = {}) {
2533
2718
  try {
2534
- const raw = fs15.readFileSync(credentialsPath(opts), "utf8");
2719
+ const raw = fs16.readFileSync(credentialsPath(opts), "utf8");
2535
2720
  const parsed = JSON.parse(raw);
2536
2721
  return parsed && typeof parsed.dsn === "string" && parsed.dsn ? parsed : null;
2537
2722
  } catch {
@@ -2540,16 +2725,16 @@ function readStoredCredentials(opts = {}) {
2540
2725
  }
2541
2726
  function writeStoredCredentials(creds, opts = {}) {
2542
2727
  const file = credentialsPath(opts);
2543
- fs15.mkdirSync(path17.dirname(file), { recursive: true });
2544
- fs15.writeFileSync(file, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf8", mode: 384 });
2728
+ fs16.mkdirSync(path18.dirname(file), { recursive: true });
2729
+ fs16.writeFileSync(file, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf8", mode: 384 });
2545
2730
  try {
2546
- fs15.chmodSync(file, 384);
2731
+ fs16.chmodSync(file, 384);
2547
2732
  } catch {
2548
2733
  }
2549
2734
  }
2550
2735
  function clearStoredCredentials(opts = {}) {
2551
2736
  try {
2552
- fs15.rmSync(credentialsPath(opts));
2737
+ fs16.rmSync(credentialsPath(opts));
2553
2738
  return true;
2554
2739
  } catch {
2555
2740
  return false;
@@ -2920,7 +3105,7 @@ var pushCommand = new Command("push").description("Push scan results to Vibgrate
2920
3105
  if (opts.strict) process.exit(1);
2921
3106
  return;
2922
3107
  }
2923
- const filePath = path17.resolve(opts.file);
3108
+ const filePath = path18.resolve(opts.file);
2924
3109
  if (!await pathExists(filePath)) {
2925
3110
  console.error(chalk.red(`Scan artifact not found: ${filePath}`));
2926
3111
  console.error(chalk.dim('Run "vibgrate scan" first.'));
@@ -3015,7 +3200,7 @@ function measure(root, files) {
3015
3200
  for (const rel2 of files) {
3016
3201
  let size;
3017
3202
  try {
3018
- size = fs15.statSync(path17.join(root, rel2)).size;
3203
+ size = fs16.statSync(path18.join(root, rel2)).size;
3019
3204
  } catch {
3020
3205
  continue;
3021
3206
  }
@@ -3038,9 +3223,9 @@ function matchLines(text, needleLower) {
3038
3223
  }
3039
3224
  function readCandidate(abs, knownSize) {
3040
3225
  try {
3041
- const size = knownSize ?? fs15.statSync(abs).size;
3226
+ const size = knownSize ?? fs16.statSync(abs).size;
3042
3227
  if (size > MAX_FILE_BYTES) return null;
3043
- const text = fs15.readFileSync(abs, "utf8");
3228
+ const text = fs16.readFileSync(abs, "utf8");
3044
3229
  return text.includes(NUL) ? null : text;
3045
3230
  } catch {
3046
3231
  return null;
@@ -3051,7 +3236,7 @@ function scanInline(root, files, needleLower, cap, stopAtCap, sizes) {
3051
3236
  let total = 0;
3052
3237
  let truncated = false;
3053
3238
  for (const rel2 of files) {
3054
- const text = readCandidate(path17.join(root, rel2), sizes?.get(rel2));
3239
+ const text = readCandidate(path18.join(root, rel2), sizes?.get(rel2));
3055
3240
  if (text === null) continue;
3056
3241
  for (const h of matchLines(text, needleLower)) {
3057
3242
  total++;
@@ -3118,12 +3303,12 @@ var WORKER_SOURCE = [
3118
3303
  "parentPort.postMessage({ hits, total, truncated });"
3119
3304
  ].join("\n");
3120
3305
  function runWorker(root, files, needleLower) {
3121
- return new Promise((resolve11, reject) => {
3306
+ return new Promise((resolve12, reject) => {
3122
3307
  const w = new Worker(WORKER_SOURCE, { eval: true, workerData: { root, files, needleLower } });
3123
3308
  let settled = false;
3124
3309
  w.once("message", (m) => {
3125
3310
  settled = true;
3126
- resolve11(m);
3311
+ resolve12(m);
3127
3312
  });
3128
3313
  w.once("error", reject);
3129
3314
  w.once("exit", (code) => {
@@ -3192,10 +3377,10 @@ var BINARY_EXTS = /* @__PURE__ */ new Set([
3192
3377
  ".pdf"
3193
3378
  ]);
3194
3379
  function hasBinaryExt(rel2) {
3195
- return BINARY_EXTS.has(path17.extname(rel2).toLowerCase());
3380
+ return BINARY_EXTS.has(path18.extname(rel2).toLowerCase());
3196
3381
  }
3197
- function toPosix3(rel2) {
3198
- return path17.sep === "\\" ? rel2.replaceAll("\\", "/") : rel2;
3382
+ function toPosix4(rel2) {
3383
+ return path18.sep === "\\" ? rel2.replaceAll("\\", "/") : rel2;
3199
3384
  }
3200
3385
  async function searchSymbols(graph, root, query, limit) {
3201
3386
  let q = query.trim();
@@ -3334,13 +3519,13 @@ function walkCandidates(root) {
3334
3519
  const dir = stack.pop();
3335
3520
  let entries;
3336
3521
  try {
3337
- entries = fs15.readdirSync(dir, { withFileTypes: true });
3522
+ entries = fs16.readdirSync(dir, { withFileTypes: true });
3338
3523
  } catch {
3339
3524
  continue;
3340
3525
  }
3341
3526
  for (const e of entries) {
3342
3527
  if (e.name.startsWith(".") || IGNORE_DIRS.has(e.name)) continue;
3343
- const abs = path17.join(dir, e.name);
3528
+ const abs = path18.join(dir, e.name);
3344
3529
  if (e.isDirectory()) {
3345
3530
  stack.push(abs);
3346
3531
  continue;
@@ -3350,7 +3535,7 @@ function walkCandidates(root) {
3350
3535
  truncated = true;
3351
3536
  continue;
3352
3537
  }
3353
- files.push(toPosix3(path17.relative(root, abs)));
3538
+ files.push(toPosix4(path18.relative(root, abs)));
3354
3539
  }
3355
3540
  }
3356
3541
  files.sort();
@@ -3381,11 +3566,11 @@ function ripgrepCandidates(root, needle) {
3381
3566
  if (files.length > MAX_FILES_SCANNED) return { files: files.slice(0, MAX_FILES_SCANNED), truncated: true };
3382
3567
  return { files, truncated: false };
3383
3568
  }
3384
- function parseRgFileList(stdout, sep8 = path17.sep) {
3569
+ function parseRgFileList(stdout, sep9 = path18.sep) {
3385
3570
  const seen = /* @__PURE__ */ new Set();
3386
3571
  for (const raw of stdout.split("\n")) {
3387
3572
  if (!raw) continue;
3388
- const posix = sep8 === "\\" ? raw.replaceAll("\\", "/") : raw;
3573
+ const posix = sep9 === "\\" ? raw.replaceAll("\\", "/") : raw;
3389
3574
  const rel2 = posix.startsWith("./") ? posix.slice(2) : posix;
3390
3575
  if (!rel2) continue;
3391
3576
  const segs = rel2.split("/");
@@ -3397,8 +3582,8 @@ function parseRgFileList(stdout, sep8 = path17.sep) {
3397
3582
  }
3398
3583
  function readScanArtifact(root) {
3399
3584
  try {
3400
- const file = path17.join(root, ".vibgrate", "scan_result.json");
3401
- return JSON.parse(fs15.readFileSync(file, "utf8"));
3585
+ const file = path18.join(root, ".vibgrate", "scan_result.json");
3586
+ return JSON.parse(fs16.readFileSync(file, "utf8"));
3402
3587
  } catch {
3403
3588
  return null;
3404
3589
  }
@@ -3788,7 +3973,7 @@ var CACHE_VERSION = "vg-hosted-docs/1";
3788
3973
  var HOSTED_DOCS_TTL_MS = 24 * 60 * 60 * 1e3;
3789
3974
  var MAX_ENTRIES = 64;
3790
3975
  function cachePath(root) {
3791
- return path17.join(cacheDir(root), "hosted-docs.json");
3976
+ return path18.join(cacheDir(root), "hosted-docs.json");
3792
3977
  }
3793
3978
  function hostedDocsCacheKey(req, opts = {}) {
3794
3979
  const material = JSON.stringify([
@@ -3804,7 +3989,7 @@ function hostedDocsCacheKey(req, opts = {}) {
3804
3989
  }
3805
3990
  function loadFile(root) {
3806
3991
  try {
3807
- const parsed = JSON.parse(fs15.readFileSync(cachePath(root), "utf8"));
3992
+ const parsed = JSON.parse(fs16.readFileSync(cachePath(root), "utf8"));
3808
3993
  if (parsed && parsed.version === CACHE_VERSION && parsed.entries && typeof parsed.entries === "object") return parsed;
3809
3994
  } catch {
3810
3995
  }
@@ -3818,8 +4003,8 @@ function saveFile(root, file) {
3818
4003
  delete file.entries[k];
3819
4004
  }
3820
4005
  }
3821
- fs15.mkdirSync(cacheDir(root), { recursive: true });
3822
- fs15.writeFileSync(cachePath(root), JSON.stringify(file));
4006
+ fs16.mkdirSync(cacheDir(root), { recursive: true });
4007
+ fs16.writeFileSync(cachePath(root), JSON.stringify(file));
3823
4008
  } catch {
3824
4009
  }
3825
4010
  }
@@ -4794,12 +4979,12 @@ function detectAssistants(root, opts = {}) {
4794
4979
  const which = opts.which ?? whichOnPath;
4795
4980
  const found = [];
4796
4981
  for (const assistant of ASSISTANTS) {
4797
- const repoHit = (assistant.markers ?? []).find((m) => fs15.existsSync(path17.join(root, m)));
4982
+ const repoHit = (assistant.markers ?? []).find((m) => fs16.existsSync(path18.join(root, m)));
4798
4983
  if (repoHit) {
4799
4984
  found.push({ assistant, via: "repo", marker: repoHit });
4800
4985
  continue;
4801
4986
  }
4802
- const homeHit = (assistant.homeMarkers ?? []).find((m) => fs15.existsSync(path17.join(home, m)));
4987
+ const homeHit = (assistant.homeMarkers ?? []).find((m) => fs16.existsSync(path18.join(home, m)));
4803
4988
  if (homeHit) {
4804
4989
  found.push({ assistant, via: "home", marker: homeHit });
4805
4990
  continue;
@@ -4831,19 +5016,19 @@ function installAssistant(a, opts) {
4831
5016
  const skipped = [];
4832
5017
  let note;
4833
5018
  if (a.skill) {
4834
- writeFileEnsured(path17.join(opts.root, a.skill), skillMarkdown(a.id));
5019
+ writeFileEnsured(path18.join(opts.root, a.skill), skillMarkdown(a.id));
4835
5020
  wrote.push(a.skill);
4836
5021
  }
4837
5022
  if (a.mcp) {
4838
5023
  const launch = opts.launch ?? detectServeLaunch();
4839
- upsertMcp(path17.join(opts.root, a.mcp.file), a.mcp, launch);
5024
+ upsertMcp(path18.join(opts.root, a.mcp.file), a.mcp, launch);
4840
5025
  wrote.push(a.mcp.file);
4841
5026
  note = launch.note;
4842
5027
  } else {
4843
5028
  skipped.push("mcp (host-specific setup)");
4844
5029
  }
4845
5030
  if (a.nudge && opts.hook !== false) {
4846
- writeNudge(path17.join(opts.root, a.nudge.file), a.nudge, opts.smallRepo, a.id);
5031
+ writeNudge(path18.join(opts.root, a.nudge.file), a.nudge, opts.smallRepo, a.id);
4847
5032
  wrote.push(a.nudge.file);
4848
5033
  } else if (a.nudge) {
4849
5034
  skipped.push("nudge (--no-hook)");
@@ -4853,41 +5038,41 @@ function installAssistant(a, opts) {
4853
5038
  function uninstallAssistant(a, root, purge) {
4854
5039
  const removed = [];
4855
5040
  if (a.mcp) {
4856
- const file = path17.join(root, a.mcp.file);
5041
+ const file = path18.join(root, a.mcp.file);
4857
5042
  if (removeMcp(file, a.mcp)) removed.push(a.mcp.file);
4858
5043
  }
4859
5044
  if (a.nudge) {
4860
- const file = path17.join(root, a.nudge.file);
5045
+ const file = path18.join(root, a.nudge.file);
4861
5046
  if (a.nudge.kind === "block") {
4862
5047
  if (removeBlock(file)) removed.push(a.nudge.file);
4863
- } else if (fs15.existsSync(file)) {
4864
- fs15.rmSync(file);
5048
+ } else if (fs16.existsSync(file)) {
5049
+ fs16.rmSync(file);
4865
5050
  removed.push(a.nudge.file);
4866
5051
  }
4867
5052
  }
4868
5053
  if (purge && a.skill) {
4869
- const file = path17.join(root, a.skill);
4870
- if (fs15.existsSync(file)) {
4871
- fs15.rmSync(file);
5054
+ const file = path18.join(root, a.skill);
5055
+ if (fs16.existsSync(file)) {
5056
+ fs16.rmSync(file);
4872
5057
  removed.push(a.skill);
4873
5058
  }
4874
5059
  }
4875
5060
  return removed;
4876
5061
  }
4877
5062
  function writeFileEnsured(file, content) {
4878
- fs15.mkdirSync(path17.dirname(file), { recursive: true });
4879
- fs15.writeFileSync(file, content);
5063
+ fs16.mkdirSync(path18.dirname(file), { recursive: true });
5064
+ fs16.writeFileSync(file, content);
4880
5065
  }
4881
5066
  function writeNavigationConfig(root) {
4882
5067
  ensureVibgrateGitignore(root);
4883
- const rel2 = path17.join(".vibgrate", "mcp-navigation.json");
5068
+ const rel2 = path18.join(".vibgrate", "mcp-navigation.json");
4884
5069
  const doc = {
4885
5070
  _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.",
4886
5071
  hot_core: [...HOT_TOOLS],
4887
5072
  deferred: deferredToolNames(),
4888
5073
  toolset: navigationToolsetConfig()
4889
5074
  };
4890
- writeFileEnsured(path17.join(root, rel2), `${JSON.stringify(doc, null, 2)}
5075
+ writeFileEnsured(path18.join(root, rel2), `${JSON.stringify(doc, null, 2)}
4891
5076
  `);
4892
5077
  return rel2;
4893
5078
  }
@@ -4902,7 +5087,7 @@ function upsertMcp(file, target, launch) {
4902
5087
  `);
4903
5088
  }
4904
5089
  function removeMcp(file, target) {
4905
- if (!fs15.existsSync(file)) return false;
5090
+ if (!fs16.existsSync(file)) return false;
4906
5091
  const config = readJson(file);
4907
5092
  const bag = config[target.key];
4908
5093
  if (!bag || !(bag.vg !== void 0)) return false;
@@ -4921,8 +5106,8 @@ function refreshInstalledInstructions(root, smallRepo) {
4921
5106
  };
4922
5107
  for (const a of ASSISTANTS) {
4923
5108
  if (a.skill) {
4924
- const file = path17.join(root, a.skill);
4925
- if (fs15.existsSync(file)) {
5109
+ const file = path18.join(root, a.skill);
5110
+ if (fs16.existsSync(file)) {
4926
5111
  const text = readTextSafe(file);
4927
5112
  const from = text === null ? null : staleVersion(text, "# vg \u2014 the code map");
4928
5113
  if (from !== null) {
@@ -4932,8 +5117,8 @@ function refreshInstalledInstructions(root, smallRepo) {
4932
5117
  }
4933
5118
  }
4934
5119
  if (a.nudge) {
4935
- const file = path17.join(root, a.nudge.file);
4936
- if (!fs15.existsSync(file)) continue;
5120
+ const file = path18.join(root, a.nudge.file);
5121
+ if (!fs16.existsSync(file)) continue;
4937
5122
  const text = readTextSafe(file);
4938
5123
  if (text === null) continue;
4939
5124
  if (a.nudge.kind === "block") {
@@ -4957,7 +5142,7 @@ function refreshInstalledInstructions(root, smallRepo) {
4957
5142
  }
4958
5143
  function readTextSafe(file) {
4959
5144
  try {
4960
- return fs15.readFileSync(file, "utf8");
5145
+ return fs16.readFileSync(file, "utf8");
4961
5146
  } catch {
4962
5147
  return null;
4963
5148
  }
@@ -4976,8 +5161,8 @@ ${stripMarkers(nudgeMarkdown(smallRepo, client))}` : stripMarkers(nudgeMarkdown(
4976
5161
  upsertBlock(file, nudgeMarkdown(smallRepo, client));
4977
5162
  }
4978
5163
  function upsertBlock(file, block) {
4979
- fs15.mkdirSync(path17.dirname(file), { recursive: true });
4980
- let existing = fs15.existsSync(file) ? fs15.readFileSync(file, "utf8") : "";
5164
+ fs16.mkdirSync(path18.dirname(file), { recursive: true });
5165
+ let existing = fs16.existsSync(file) ? fs16.readFileSync(file, "utf8") : "";
4981
5166
  const re = new RegExp(`${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}`);
4982
5167
  if (re.test(existing)) existing = existing.replace(re, block);
4983
5168
  else existing = existing.length ? `${existing.replace(/\s*$/, "")}
@@ -4985,20 +5170,20 @@ function upsertBlock(file, block) {
4985
5170
  ${block}
4986
5171
  ` : `${block}
4987
5172
  `;
4988
- fs15.writeFileSync(file, existing);
5173
+ fs16.writeFileSync(file, existing);
4989
5174
  }
4990
5175
  function removeBlock(file) {
4991
- if (!fs15.existsSync(file)) return false;
4992
- const existing = fs15.readFileSync(file, "utf8");
5176
+ if (!fs16.existsSync(file)) return false;
5177
+ const existing = fs16.readFileSync(file, "utf8");
4993
5178
  const re = new RegExp(`\\n*${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}\\n*`);
4994
5179
  if (!re.test(existing)) return false;
4995
- fs15.writeFileSync(file, existing.replace(re, "\n"));
5180
+ fs16.writeFileSync(file, existing.replace(re, "\n"));
4996
5181
  return true;
4997
5182
  }
4998
5183
  function readJson(file) {
4999
- if (!fs15.existsSync(file)) return {};
5184
+ if (!fs16.existsSync(file)) return {};
5000
5185
  try {
5001
- const parsed = JSON.parse(fs15.readFileSync(file, "utf8"));
5186
+ const parsed = JSON.parse(fs16.readFileSync(file, "utf8"));
5002
5187
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
5003
5188
  return parsed;
5004
5189
  }
@@ -5018,7 +5203,7 @@ function escapeRe(s) {
5018
5203
  }
5019
5204
  var REFRESH_LOCK_STALE_MS = 10 * 60 * 1e3;
5020
5205
  function refreshLockPath(root) {
5021
- return path17.join(cacheDir(root), "refresh.lock");
5206
+ return path18.join(cacheDir(root), "refresh.lock");
5022
5207
  }
5023
5208
  async function refreshIfStale(root, opts = {}) {
5024
5209
  const probe = probeFreshness(root);
@@ -5050,8 +5235,8 @@ async function refreshIfStale(root, opts = {}) {
5050
5235
  writeArtifacts(result.graph, {
5051
5236
  root,
5052
5237
  graphPath: opts.graphPath,
5053
- report: fs15.existsSync(path17.join(dir, "GRAPH_REPORT.md")),
5054
- html: fs15.existsSync(path17.join(dir, "graph.html"))
5238
+ report: fs16.existsSync(path18.join(dir, "GRAPH_REPORT.md")),
5239
+ html: fs16.existsSync(path18.join(dir, "graph.html"))
5055
5240
  });
5056
5241
  }
5057
5242
  writeSnapshot(root, result.graph.provenance.corpusHash, result.fileStats, scope);
@@ -5071,6 +5256,6 @@ async function refreshIfStale(root, opts = {}) {
5071
5256
  }
5072
5257
  }
5073
5258
 
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
5259
+ export { ASSISTANTS, CliError, ExitCode, FREE_PACK, NPX_INVOCATION, ResourceLimitError, 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 };
5260
+ //# sourceMappingURL=chunk-KNELIQJO.js.map
5261
+ //# sourceMappingURL=chunk-KNELIQJO.js.map