@vibgrate/cli 2026.703.7 → 2026.704.3

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.
package/dist/cli.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { resolveDsn, resolveIngestHost, dashHostForIngestHost, resolveCliInvocation, availableRegionIds, createWorkspaceDsn, writeStoredCredentials, credentialsPath, findGitRoot, ensureGitignored, gitignoreEntryForCredentials, clearStoredCredentials, inventory, uploadScanArtifact, dsnCommand, pushCommand, ExitCode, CliError, NPX_INVOCATION, verifyDeterminism, refreshIfStale, driftCount, embeddingsCached, resolveEmbedModel, loadEmbedder, getNodeEmbeddings, queryGraphSemantic, queryGraph, unavailableMessage, countPending, resolveOne, GraphIndex, shortestPath, renderReport, impactOf, testsToRun, detectRunner, defaultGraphPath, serveStdio, ASSISTANTS, usageError, loadGraph, detectServeLaunch, installAssistant, uninstallAssistant, buildGraph, serializeGraph, coveringTests, FREE_PACK, readScanArtifact, enrichOnline, ECOSYSTEMS, discoverModels, readSavings, exportGraph, catalogPath, libDir, stableStringify, writeArtifacts, writeSnapshot, UsageError, cacheDir, probeFreshness, discover, vibgrateDir, modelCacheInfo, clearModelCache, GraphSource, createServer, assistantById, loadCatalog, driftFor, addLibrary, parseDsn as parseDsn$1, publishPrivateLibrary, resolveLib, libId, resolveVersion, readDoc, localPackageDocs, localApiSurface, assessDocQuality, symbolsFromApi, fetchHostedDocs, selectForBudget, formatForExt, renderHtml, isModelReady } from './chunk-WGXDYBAN.js';
2
+ import { resolveDsn, resolveIngestHost, dashHostForIngestHost, resolveCliInvocation, availableRegionIds, createWorkspaceDsn, writeStoredCredentials, credentialsPath, findGitRoot, ensureGitignored, gitignoreEntryForCredentials, clearStoredCredentials, inventory, uploadScanArtifact, dsnCommand, pushCommand, ExitCode, CliError, NPX_INVOCATION, verifyDeterminism, refreshIfStale, driftCount, embeddingsCached, resolveEmbedModel, loadEmbedder, getNodeEmbeddings, queryGraphSemantic, queryGraph, unavailableMessage, countPending, resolveOne, GraphIndex, shortestPath, renderReport, impactOf, testsToRun, detectRunner, defaultGraphPath, serveStdio, ASSISTANTS, usageError, loadGraph, detectServeLaunch, installAssistant, writeNavigationConfig, uninstallAssistant, coveringTests, FREE_PACK, readScanArtifact, enrichOnline, ECOSYSTEMS, discoverModels, readSavings, exportGraph, catalogPath, libDir, stableStringify, buildGraph, writeArtifacts, writeSnapshot, UsageError, ResourceLimitError, cacheDir, probeFreshness, discover, vibgrateDir, modelCacheInfo, clearModelCache, GraphSource, createServer, assistantById, serializeGraph, resolveLimits, loadCatalog, driftFor, addLibrary, parseDsn as parseDsn$1, publishPrivateLibrary, resolveLib, libId, resolveVersion, readDoc, localPackageDocs, localApiSurface, assessDocQuality, symbolsFromApi, fetchHostedDocs, selectForBudget, formatForExt, renderHtml, isModelReady } from './chunk-JL7FIC6W.js';
3
3
  import { resolvedGrammarFiles, grammarSetVersion } from './chunk-X5YT263H.js';
4
- import { loadAdvancedScanHook, baselineCommand } from './chunk-ZNVLRKWK.js';
5
- import { ensureDir, pathExists, writeDefaultConfig, parseDsn, detectVcs, computeRepoFingerprint, resolveRepositoryName, fetchScanPreflight, runCoreScan, VERSION, readJsonFile, writeTextFile, prepareCompressedUpload, titleBox, driftBar, gitHistoryAvailable, buildVersionTimelines, findPackageAnyEcosystem, normalizeConstraint, findVersionCrossings, versionSatisfies, brandProgressBar } from './chunk-QWS7M7DG.js';
4
+ import { loadAdvancedScanHook, baselineCommand } from './chunk-WHQRV66O.js';
5
+ import { ensureDir, pathExists, writeDefaultConfig, parseDsn, detectVcs, computeRepoFingerprint, resolveRepositoryName, fetchScanPreflight, runCoreScan, VERSION, readJsonFile, writeTextFile, prepareCompressedUpload, titleBox, driftBar, gitHistoryAvailable, buildVersionTimelines, findPackageAnyEcosystem, normalizeConstraint, findVersionCrossings, versionSatisfies, brandProgressBar } from './chunk-YN5OGRWU.js';
6
6
  import './chunk-RXP66R2E.js';
7
- import { pathExists as pathExists$1, parseExcludePatterns } from './chunk-UPPWLMVD.js';
7
+ import { pathExists as pathExists$1, parseExcludePatterns } from './chunk-GI6W53LM.js';
8
8
  import * as fs4 from 'fs';
9
9
  import fs4__default, { realpathSync } from 'fs';
10
10
  import * as path9 from 'path';
@@ -156,6 +156,7 @@ async function runBuild(paths, opts, global) {
156
156
  } catch (err) {
157
157
  bar?.done();
158
158
  if (err instanceof UsageError) throw usageError(err.message);
159
+ if (err instanceof ResourceLimitError) throw new CliError(err.message, ExitCode.ERROR);
159
160
  throw err;
160
161
  }
161
162
  bar?.done();
@@ -192,7 +193,8 @@ async function runBuild(paths, opts, global) {
192
193
  scip: result.scip,
193
194
  artifacts: written,
194
195
  corpusHash: result.graph.provenance.corpusHash,
195
- timingMs: result.timing.totalMs
196
+ timingMs: result.timing.totalMs,
197
+ warnings: result.warnings
196
198
  });
197
199
  return;
198
200
  }
@@ -226,18 +228,20 @@ async function runBuild(paths, opts, global) {
226
228
  function maybeWarmEmbeddings(root, graph, global, warm) {
227
229
  if (!warm || global.json || global.quiet || global.local) return;
228
230
  if (!process.stdout.isTTY && !process.stderr.isTTY) return;
229
- const modelId = resolveEmbedModel();
230
- if (!isModelReady(modelId)) return;
231
- if (countPending(graph, root, modelId) === 0) return;
232
231
  const cli = process.argv[1];
233
232
  if (!cli) return;
233
+ const modelId = resolveEmbedModel();
234
+ const ready = isModelReady(modelId);
235
+ if (ready && countPending(graph, root, modelId) === 0) return;
236
+ const args = ready ? [cli, "embed", "-C", root, "--bg"] : [cli, "embed", "-C", root, "--bg", "--download"];
234
237
  try {
235
- const child = spawn(process.execPath, [cli, "embed", "-C", root, "--bg"], {
236
- detached: true,
237
- stdio: "ignore"
238
- });
238
+ const child = spawn(process.execPath, args, { detached: true, stdio: "ignore" });
239
239
  child.unref();
240
- info(c.dim(" warming the semantic index in the background \u2014 `vg ask` will be instant"));
240
+ info(
241
+ c.dim(
242
+ ready ? " warming the semantic index in the background \u2014 `vg ask` will be instant" : " downloading the semantic model in the background (once) \u2014 `vg ask`/`vg serve` will be instant; disable with --no-warm"
243
+ )
244
+ );
241
245
  } catch {
242
246
  }
243
247
  }
@@ -454,7 +458,7 @@ function mb(bytes) {
454
458
  return `${Math.round(bytes / (1024 * 1024))} MB`;
455
459
  }
456
460
  function registerEmbed(program) {
457
- const cmd = program.command("embed").description("precompute the semantic index for instant `vg ask` (or --where / --clear)").option("--where", "show where the model is cached, and its size").option("--clear", "remove the downloaded model from the shared cache").addOption(new Option("--bg").hideHelp()).action(async function(opts) {
461
+ const cmd = program.command("embed").description("precompute the semantic index for instant `vg ask` (or --where / --clear)").option("--where", "show where the model is cached, and its size").option("--clear", "remove the downloaded model from the shared cache").addOption(new Option("--bg").hideHelp()).addOption(new Option("--download").hideHelp()).action(async function(opts) {
458
462
  const global = readGlobal(this);
459
463
  const modelId = resolveEmbedModel();
460
464
  if (opts.where) return showWhere(rootOf(global), modelId, global.json);
@@ -475,8 +479,10 @@ function registerEmbed(program) {
475
479
  let reason;
476
480
  const embedder = await loadEmbedder({
477
481
  local: global.local,
478
- noDownload: bg,
479
- // background warm-up never downloads
482
+ // Background warm-ups skip the download unless explicitly asked to fetch
483
+ // (`--bg --download`, spawned by `vg build` so the model is ready by the
484
+ // time `vg serve`/`vg ask` needs it).
485
+ noDownload: bg && opts.download !== true,
480
486
  showDownloadProgress: !bg,
481
487
  onUnavailable: (r) => reason = r
482
488
  });
@@ -910,8 +916,9 @@ function registerInstall(program) {
910
916
  const smallRepo = graph !== null && fileCount > 0 && fileCount < SMALL_REPO_FILES;
911
917
  const launch = detectServeLaunch();
912
918
  const results = targets.map((a) => ({ id: a.id, ...installAssistant(a, { root, hook: opts.hook, smallRepo, launch }) }));
919
+ const navConfig = writeNavigationConfig(root);
913
920
  if (global.json) {
914
- json({ root, smallRepo, launch: { command: launch.command, args: launch.args, note: launch.note ?? null }, results });
921
+ json({ root, smallRepo, navConfig, launch: { command: launch.command, args: launch.args, note: launch.note ?? null }, results });
915
922
  return;
916
923
  }
917
924
  for (const r of results) {
@@ -919,6 +926,7 @@ function registerInstall(program) {
919
926
  }
920
927
  if (launch.note && results.some((r) => r.note)) info(`${c.yellow("!")} ${launch.note}`);
921
928
  if (smallRepo) info(c.dim(` note: small repo (${fileCount} files) \u2014 nudge says searching is fine; vg is still used for impact/tests`));
929
+ info(c.dim(` wrote ${navConfig} \u2014 deferred-loading config for Claude-API agents (lower per-step token cost)`));
922
930
  info(c.dim(" run `vg serve` is wired via MCP; build the map with `vg` if you have not yet"));
923
931
  });
924
932
  applyGlobalOptions(install);
@@ -1059,52 +1067,103 @@ function escapeRe(s) {
1059
1067
  }
1060
1068
  var PIN = "2020-01-01T00:00:00.000Z";
1061
1069
  function registerBenchmark(program) {
1062
- const cmd = program.command("benchmark").description("reproducible build + token-reduction benchmark (honest estimates)").option("--budget <n>", "token budget for vg answers", "2000").action(async function(opts) {
1070
+ const cmd = program.command("benchmark").description("reproducible build + memory + token-reduction benchmark (honest estimates)").option("--budget <n>", "token budget for vg answers", "2000").action(async function(opts) {
1063
1071
  const global = readGlobal(this);
1064
1072
  const root = rootOf(global);
1065
1073
  const budget = Number(opts.budget) || 2e3;
1066
- const t0 = now();
1067
- const cold = await buildGraph({ root, generatedAt: PIN, noCache: true });
1068
- const coldMs = now() - t0;
1069
- const t1 = now();
1070
- const warm = await buildGraph({ root, generatedAt: PIN });
1071
- const warmMs = now() - t1;
1072
- const a = serializeGraph(cold.graph);
1073
- const b = serializeGraph((await buildGraph({ root, generatedAt: PIN, noCache: true })).graph);
1074
- const deterministic = a === b;
1075
- const questions = deriveQuestions(cold.graph);
1076
- const fileSizes = fileSizeIndex(root);
1077
- const perQuestion = questions.map((q) => {
1078
- const vgTokens = queryGraph(cold.graph, q, { budget }).tokensEstimate;
1079
- const baseTokens = grepBaselineTokens(q, fileSizes);
1080
- return { question: q, vgTokens, baselineTokens: baseTokens, ratio: baseTokens > 0 ? round(baseTokens / Math.max(vgTokens, 1)) : 1 };
1081
- });
1082
- const totalVg = perQuestion.reduce((s, x) => s + x.vgTokens, 0);
1083
- const totalBase = perQuestion.reduce((s, x) => s + x.baselineTokens, 0);
1084
- const aggregateRatio = totalVg > 0 ? round(totalBase / totalVg) : 0;
1085
- const result = {
1086
- repo: { files: cold.totalFiles, nodes: cold.graph.meta.counts.nodes, edges: cold.graph.meta.counts.edges },
1087
- build: { coldMs: round(coldMs), incrementalMs: round(warmMs), reusedOnWarm: warm.reused },
1088
- determinism: { byteIdentical: deterministic },
1089
- tokenReduction: { questions: perQuestion, aggregateRatio, note: "estimates (~4 chars/token); scales with repo size" }
1090
- };
1074
+ const result = await runBenchmarkSuite(root, budget);
1091
1075
  if (global.json) {
1092
1076
  json(result);
1093
1077
  return;
1094
1078
  }
1095
- info(`${c.cyan("vg benchmark")} \xB7 ${result.repo.files} files \xB7 ${result.repo.nodes} nodes`);
1096
- info(` build cold ${result.build.coldMs.toFixed(0)}ms \xB7 incremental ${result.build.incrementalMs.toFixed(0)}ms (reused ${warm.reused})`);
1097
- info(` determinism ${deterministic ? c.green("byte-identical \u2713") : c.red("NON-DETERMINISTIC \u2717")}`);
1098
- if (perQuestion.length) {
1079
+ const { repo, build, throughput, memory, determinism, tokenReduction } = result;
1080
+ info(`${c.cyan("vg benchmark")} \xB7 ${repo.files} files \xB7 ${repo.nodes} nodes`);
1081
+ info(` build cold ${build.coldMs.toFixed(0)}ms \xB7 incremental ${build.incrementalMs.toFixed(0)}ms (reused ${build.reusedOnWarm})`);
1082
+ info(` speed ${throughput.filesPerSec.toFixed(0)} files/s \xB7 ${throughput.mbPerSec.toFixed(1)} MB/s (cold, ${mb2(throughput.corpusBytes).toFixed(1)} MB corpus)`);
1083
+ info(
1084
+ ` memory peak rss ${memory.peakRssMb.toFixed(0)} MB (baseline ${memory.baselineRssMb.toFixed(0)} MB) \xB7 peak heap ${memory.peakHeapMb.toFixed(0)} MB \xB7 graph retains ~${memory.retainedHeapMb.toFixed(0)} MB ${c.dim("(approximate)")}`
1085
+ );
1086
+ info(` graph ${mb2(memory.graphJsonBytes).toFixed(1)} MB serialized \xB7 ${memory.bytesPerNode.toFixed(0)} B/node`);
1087
+ info(` determinism ${determinism.byteIdentical ? c.green("byte-identical \u2713") : c.red("NON-DETERMINISTIC \u2717")}`);
1088
+ if (tokenReduction.questions.length) {
1099
1089
  info(` token reduction vs grep/read baseline (${c.dim("estimates")}):`);
1100
- for (const q of perQuestion) {
1090
+ for (const q of tokenReduction.questions) {
1101
1091
  info(` ${pad2(`${q.ratio.toFixed(1)}\xD7`, 6)} ${c.dim(`vg ${q.vgTokens} vs ~${q.baselineTokens}`)} "${q.question}"`);
1102
1092
  }
1103
- info(` ${c.bold(`aggregate \u2248 ${aggregateRatio.toFixed(1)}\xD7 fewer tokens`)} ${c.dim("(honest estimate, scales with repo size)")}`);
1093
+ info(` ${c.bold(`aggregate \u2248 ${tokenReduction.aggregateRatio.toFixed(1)}\xD7 fewer tokens`)} ${c.dim("(honest estimate, scales with repo size)")}`);
1104
1094
  }
1105
1095
  });
1106
1096
  applyGlobalOptions(cmd);
1107
1097
  }
1098
+ async function runBenchmarkSuite(root, budget) {
1099
+ const baseline = process.memoryUsage();
1100
+ const sampler = startMemorySampler();
1101
+ const t0 = now();
1102
+ const cold = await buildGraph({ root, generatedAt: PIN, noCache: true });
1103
+ const coldMs = now() - t0;
1104
+ const t1 = now();
1105
+ const warm = await buildGraph({ root, generatedAt: PIN });
1106
+ const warmMs = now() - t1;
1107
+ const a = serializeGraph(cold.graph);
1108
+ const b = serializeGraph((await buildGraph({ root, generatedAt: PIN, noCache: true })).graph);
1109
+ const peak = sampler.stop();
1110
+ const deterministic = a === b;
1111
+ const after = process.memoryUsage();
1112
+ const corpusBytes = cold.fileStats.reduce((s, f) => s + f.size, 0);
1113
+ const graphJsonBytes = Buffer.byteLength(a, "utf8");
1114
+ const nodes = cold.graph.meta.counts.nodes;
1115
+ const questions = deriveQuestions(cold.graph);
1116
+ const fileSizes = fileSizeIndex(root);
1117
+ const perQuestion = questions.map((q) => {
1118
+ const vgTokens = queryGraph(cold.graph, q, { budget }).tokensEstimate;
1119
+ const baseTokens = grepBaselineTokens(q, fileSizes);
1120
+ return { question: q, vgTokens, baselineTokens: baseTokens, ratio: baseTokens > 0 ? round(baseTokens / Math.max(vgTokens, 1)) : 1 };
1121
+ });
1122
+ const totalVg = perQuestion.reduce((s, x) => s + x.vgTokens, 0);
1123
+ const totalBase = perQuestion.reduce((s, x) => s + x.baselineTokens, 0);
1124
+ const aggregateRatio = totalVg > 0 ? round(totalBase / totalVg) : 0;
1125
+ return {
1126
+ repo: { files: cold.totalFiles, nodes, edges: cold.graph.meta.counts.edges },
1127
+ build: { coldMs: round(coldMs), incrementalMs: round(warmMs), reusedOnWarm: warm.reused },
1128
+ throughput: {
1129
+ corpusBytes,
1130
+ filesPerSec: coldMs > 0 ? round(cold.totalFiles / coldMs * 1e3) : 0,
1131
+ // 6-decimal precision: a small corpus at 3 decimals would round to 0.
1132
+ mbPerSec: coldMs > 0 ? round6(mb2(corpusBytes) / coldMs * 1e3) : 0
1133
+ },
1134
+ memory: {
1135
+ baselineRssMb: round(mb2(baseline.rss)),
1136
+ peakRssMb: round(mb2(Math.max(peak.rss, baseline.rss))),
1137
+ peakHeapMb: round(mb2(Math.max(peak.heapUsed, baseline.heapUsed))),
1138
+ // Heap growth across the run ≈ what the loaded graphs + caches retain.
1139
+ retainedHeapMb: round(Math.max(0, mb2(after.heapUsed - baseline.heapUsed))),
1140
+ graphJsonBytes,
1141
+ bytesPerNode: nodes > 0 ? round(graphJsonBytes / nodes) : 0,
1142
+ note: "sampled at 25ms + phase boundaries; GC timing makes these approximate"
1143
+ },
1144
+ limits: resolveLimits(),
1145
+ determinism: { byteIdentical: deterministic },
1146
+ tokenReduction: { questions: perQuestion, aggregateRatio, note: "estimates (~4 chars/token); scales with repo size" }
1147
+ };
1148
+ }
1149
+ function startMemorySampler() {
1150
+ let rss = 0;
1151
+ let heapUsed = 0;
1152
+ const sample = () => {
1153
+ const m = process.memoryUsage();
1154
+ if (m.rss > rss) rss = m.rss;
1155
+ if (m.heapUsed > heapUsed) heapUsed = m.heapUsed;
1156
+ };
1157
+ const timer = setInterval(sample, 25);
1158
+ timer.unref?.();
1159
+ return {
1160
+ stop: () => {
1161
+ clearInterval(timer);
1162
+ sample();
1163
+ return { rss, heapUsed };
1164
+ }
1165
+ };
1166
+ }
1108
1167
  function deriveQuestions(graph) {
1109
1168
  return graph.nodes.filter((n) => n.kind !== "file" && n.kind !== "external").sort((a, b) => b.importance - a.importance || a.qualifiedName.localeCompare(b.qualifiedName)).slice(0, 5).map((n) => n.name);
1110
1169
  }
@@ -1133,12 +1192,18 @@ function safeSize(abs) {
1133
1192
  return 0;
1134
1193
  }
1135
1194
  }
1195
+ function mb2(bytes) {
1196
+ return bytes / (1024 * 1024);
1197
+ }
1136
1198
  function now() {
1137
1199
  return Number(process.hrtime.bigint() / 1000n) / 1e3;
1138
1200
  }
1139
1201
  function round(x) {
1140
1202
  return Math.round(x * 1e3) / 1e3;
1141
1203
  }
1204
+ function round6(x) {
1205
+ return Math.round(x * 1e6) / 1e6;
1206
+ }
1142
1207
  function pad2(s, n) {
1143
1208
  return s.padStart(n, " ");
1144
1209
  }
@@ -1882,7 +1947,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
1882
1947
  console.log(chalk5.green("\u2714") + ` Created ${chalk5.bold("vibgrate.config.ts")}`);
1883
1948
  }
1884
1949
  if (opts.baseline) {
1885
- const { runBaseline } = await import('./baseline-HBRBXJHJ.js');
1950
+ const { runBaseline } = await import('./baseline-64EUPWUH.js');
1886
1951
  await runBaseline(rootDir);
1887
1952
  }
1888
1953
  console.log("");