@vibgrate/cli 2026.703.5 → 2026.704.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { resolveDsn, resolveIngestHost, dashHostForIngestHost, availableRegionIds, createWorkspaceDsn, writeStoredCredentials, credentialsPath, findGitRoot, ensureGitignored, gitignoreEntryForCredentials, clearStoredCredentials, inventory, uploadScanArtifact, dsnCommand, pushCommand, ExitCode, CliError, 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-W2QP2YH6.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, 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-YODVLV37.js';
3
3
  import { resolvedGrammarFiles, grammarSetVersion } from './chunk-X5YT263H.js';
4
- import { loadAdvancedScanHook, baselineCommand } from './chunk-6T3R7RF2.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-L7YBNIBF.js';
4
+ import { loadAdvancedScanHook, baselineCommand } from './chunk-W4ANUENJ.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-RJHYTD62.js';
6
6
  import './chunk-RXP66R2E.js';
7
- import { pathExists as pathExists$1, parseExcludePatterns } from './chunk-4M2MUQRO.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
  }
@@ -1059,52 +1061,103 @@ function escapeRe(s) {
1059
1061
  }
1060
1062
  var PIN = "2020-01-01T00:00:00.000Z";
1061
1063
  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) {
1064
+ 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
1065
  const global = readGlobal(this);
1064
1066
  const root = rootOf(global);
1065
1067
  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
- };
1068
+ const result = await runBenchmarkSuite(root, budget);
1091
1069
  if (global.json) {
1092
1070
  json(result);
1093
1071
  return;
1094
1072
  }
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) {
1073
+ const { repo, build, throughput, memory, determinism, tokenReduction } = result;
1074
+ info(`${c.cyan("vg benchmark")} \xB7 ${repo.files} files \xB7 ${repo.nodes} nodes`);
1075
+ info(` build cold ${build.coldMs.toFixed(0)}ms \xB7 incremental ${build.incrementalMs.toFixed(0)}ms (reused ${build.reusedOnWarm})`);
1076
+ info(` speed ${throughput.filesPerSec.toFixed(0)} files/s \xB7 ${throughput.mbPerSec.toFixed(1)} MB/s (cold, ${mb2(throughput.corpusBytes).toFixed(1)} MB corpus)`);
1077
+ info(
1078
+ ` 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)")}`
1079
+ );
1080
+ info(` graph ${mb2(memory.graphJsonBytes).toFixed(1)} MB serialized \xB7 ${memory.bytesPerNode.toFixed(0)} B/node`);
1081
+ info(` determinism ${determinism.byteIdentical ? c.green("byte-identical \u2713") : c.red("NON-DETERMINISTIC \u2717")}`);
1082
+ if (tokenReduction.questions.length) {
1099
1083
  info(` token reduction vs grep/read baseline (${c.dim("estimates")}):`);
1100
- for (const q of perQuestion) {
1084
+ for (const q of tokenReduction.questions) {
1101
1085
  info(` ${pad2(`${q.ratio.toFixed(1)}\xD7`, 6)} ${c.dim(`vg ${q.vgTokens} vs ~${q.baselineTokens}`)} "${q.question}"`);
1102
1086
  }
1103
- info(` ${c.bold(`aggregate \u2248 ${aggregateRatio.toFixed(1)}\xD7 fewer tokens`)} ${c.dim("(honest estimate, scales with repo size)")}`);
1087
+ info(` ${c.bold(`aggregate \u2248 ${tokenReduction.aggregateRatio.toFixed(1)}\xD7 fewer tokens`)} ${c.dim("(honest estimate, scales with repo size)")}`);
1104
1088
  }
1105
1089
  });
1106
1090
  applyGlobalOptions(cmd);
1107
1091
  }
1092
+ async function runBenchmarkSuite(root, budget) {
1093
+ const baseline = process.memoryUsage();
1094
+ const sampler = startMemorySampler();
1095
+ const t0 = now();
1096
+ const cold = await buildGraph({ root, generatedAt: PIN, noCache: true });
1097
+ const coldMs = now() - t0;
1098
+ const t1 = now();
1099
+ const warm = await buildGraph({ root, generatedAt: PIN });
1100
+ const warmMs = now() - t1;
1101
+ const a = serializeGraph(cold.graph);
1102
+ const b = serializeGraph((await buildGraph({ root, generatedAt: PIN, noCache: true })).graph);
1103
+ const peak = sampler.stop();
1104
+ const deterministic = a === b;
1105
+ const after = process.memoryUsage();
1106
+ const corpusBytes = cold.fileStats.reduce((s, f) => s + f.size, 0);
1107
+ const graphJsonBytes = Buffer.byteLength(a, "utf8");
1108
+ const nodes = cold.graph.meta.counts.nodes;
1109
+ const questions = deriveQuestions(cold.graph);
1110
+ const fileSizes = fileSizeIndex(root);
1111
+ const perQuestion = questions.map((q) => {
1112
+ const vgTokens = queryGraph(cold.graph, q, { budget }).tokensEstimate;
1113
+ const baseTokens = grepBaselineTokens(q, fileSizes);
1114
+ return { question: q, vgTokens, baselineTokens: baseTokens, ratio: baseTokens > 0 ? round(baseTokens / Math.max(vgTokens, 1)) : 1 };
1115
+ });
1116
+ const totalVg = perQuestion.reduce((s, x) => s + x.vgTokens, 0);
1117
+ const totalBase = perQuestion.reduce((s, x) => s + x.baselineTokens, 0);
1118
+ const aggregateRatio = totalVg > 0 ? round(totalBase / totalVg) : 0;
1119
+ return {
1120
+ repo: { files: cold.totalFiles, nodes, edges: cold.graph.meta.counts.edges },
1121
+ build: { coldMs: round(coldMs), incrementalMs: round(warmMs), reusedOnWarm: warm.reused },
1122
+ throughput: {
1123
+ corpusBytes,
1124
+ filesPerSec: coldMs > 0 ? round(cold.totalFiles / coldMs * 1e3) : 0,
1125
+ // 6-decimal precision: a small corpus at 3 decimals would round to 0.
1126
+ mbPerSec: coldMs > 0 ? round6(mb2(corpusBytes) / coldMs * 1e3) : 0
1127
+ },
1128
+ memory: {
1129
+ baselineRssMb: round(mb2(baseline.rss)),
1130
+ peakRssMb: round(mb2(Math.max(peak.rss, baseline.rss))),
1131
+ peakHeapMb: round(mb2(Math.max(peak.heapUsed, baseline.heapUsed))),
1132
+ // Heap growth across the run ≈ what the loaded graphs + caches retain.
1133
+ retainedHeapMb: round(Math.max(0, mb2(after.heapUsed - baseline.heapUsed))),
1134
+ graphJsonBytes,
1135
+ bytesPerNode: nodes > 0 ? round(graphJsonBytes / nodes) : 0,
1136
+ note: "sampled at 25ms + phase boundaries; GC timing makes these approximate"
1137
+ },
1138
+ limits: resolveLimits(),
1139
+ determinism: { byteIdentical: deterministic },
1140
+ tokenReduction: { questions: perQuestion, aggregateRatio, note: "estimates (~4 chars/token); scales with repo size" }
1141
+ };
1142
+ }
1143
+ function startMemorySampler() {
1144
+ let rss = 0;
1145
+ let heapUsed = 0;
1146
+ const sample = () => {
1147
+ const m = process.memoryUsage();
1148
+ if (m.rss > rss) rss = m.rss;
1149
+ if (m.heapUsed > heapUsed) heapUsed = m.heapUsed;
1150
+ };
1151
+ const timer = setInterval(sample, 25);
1152
+ timer.unref?.();
1153
+ return {
1154
+ stop: () => {
1155
+ clearInterval(timer);
1156
+ sample();
1157
+ return { rss, heapUsed };
1158
+ }
1159
+ };
1160
+ }
1108
1161
  function deriveQuestions(graph) {
1109
1162
  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
1163
  }
@@ -1133,12 +1186,18 @@ function safeSize(abs) {
1133
1186
  return 0;
1134
1187
  }
1135
1188
  }
1189
+ function mb2(bytes) {
1190
+ return bytes / (1024 * 1024);
1191
+ }
1136
1192
  function now() {
1137
1193
  return Number(process.hrtime.bigint() / 1000n) / 1e3;
1138
1194
  }
1139
1195
  function round(x) {
1140
1196
  return Math.round(x * 1e3) / 1e3;
1141
1197
  }
1198
+ function round6(x) {
1199
+ return Math.round(x * 1e6) / 1e6;
1200
+ }
1142
1201
  function pad2(s, n) {
1143
1202
  return s.padStart(n, " ");
1144
1203
  }
@@ -1882,7 +1941,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
1882
1941
  console.log(chalk5.green("\u2714") + ` Created ${chalk5.bold("vibgrate.config.ts")}`);
1883
1942
  }
1884
1943
  if (opts.baseline) {
1885
- const { runBaseline } = await import('./baseline-AQKQEA2O.js');
1944
+ const { runBaseline } = await import('./baseline-FESLZK5O.js');
1886
1945
  await runBaseline(rootDir);
1887
1946
  }
1888
1947
  console.log("");
@@ -1925,7 +1984,9 @@ async function detectAiAssistant(rootDir) {
1925
1984
  function printAiContextPrompt(detectedAssistant) {
1926
1985
  const teal2 = chalk5.hex("#3FB0A4");
1927
1986
  const mint2 = chalk5.hex("#4FE3C1");
1928
- const installCmd = detectedAssistant ? `vg install ${detectedAssistant}` : "vg install --all";
1987
+ const cli = resolveCliInvocation();
1988
+ const installArgs = detectedAssistant ? `install ${detectedAssistant}` : "install --all";
1989
+ const installCmd = `${cli} ${installArgs}`;
1929
1990
  const detectedNote = detectedAssistant ? chalk5.dim(` (${detectedAssistant} config detected)`) : "";
1930
1991
  console.log("");
1931
1992
  console.log(teal2(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
@@ -2172,7 +2233,10 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
2172
2233
  projectScanTimeout: opts.projectScanTimeout ? parseInt(opts.projectScanTimeout, 10) || void 0 : void 0,
2173
2234
  repositoryName: opts.repositoryName?.trim() || void 0,
2174
2235
  force: opts.force,
2175
- quiet: opts.quiet
2236
+ quiet: opts.quiet,
2237
+ // Prefix for the upsell panel's `login → push` hint — `vg` when installed,
2238
+ // `npx @vibgrate/cli` when the user ran via npx (where bare `vg` fails).
2239
+ invocation: resolveCliInvocation()
2176
2240
  };
2177
2241
  const wantGraph = shouldBuildCodeMap({ graph: opts.graph, maxPrivacy: opts.maxPrivacy, noLocalArtifacts });
2178
2242
  if (wantGraph) {
@@ -2984,7 +3048,14 @@ function openUrl(url) {
2984
3048
 
2985
3049
  // src/reporting/commands/login.ts
2986
3050
  var delay = (ms) => new Promise((resolve15) => setTimeout(resolve15, ms));
2987
- var loginCommand = new Command("login").description("Authenticate the CLI with your Vibgrate workspace via the browser").option("--ingest <url>", "Ingest API URL (overrides --region)").option("--region <region>", `Data residency region (${availableRegionIds().join(", ")})`, "us").option("--no-browser", "Do not attempt to open a browser automatically").action(async (opts) => {
3051
+ function isInside(dir, file) {
3052
+ const rel2 = path9.relative(dir, file);
3053
+ return !!rel2 && !rel2.startsWith("..") && !path9.isAbsolute(rel2);
3054
+ }
3055
+ var loginCommand = new Command("login").description("Authenticate the CLI with your Vibgrate workspace via the browser").option("--ingest <url>", "Ingest API URL (overrides --region)").option("--region <region>", `Data residency region (${availableRegionIds().join(", ")})`, "us").option("--no-browser", "Do not attempt to open a browser automatically").option(
3056
+ "--local",
3057
+ "Store credentials in this project (.vibgrate/credentials.json) instead of ~/.vibgrate"
3058
+ ).action(async (opts) => {
2988
3059
  let ingestHost;
2989
3060
  try {
2990
3061
  ingestHost = resolveIngestHost(opts.region, opts.ingest);
@@ -3073,17 +3144,19 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
3073
3144
  process.exit(1);
3074
3145
  }
3075
3146
  }
3076
- writeStoredCredentials(creds);
3147
+ const storeOpts = { local: opts.local === true };
3148
+ writeStoredCredentials(creds, storeOpts);
3149
+ const credsFile = credentialsPath(storeOpts);
3077
3150
  console.log("");
3078
3151
  console.log(chalk5.green("\u2714") + " Logged in.");
3079
3152
  if (creds.workspaceId) {
3080
3153
  console.log(" Workspace: " + chalk5.bold(creds.workspaceId));
3081
3154
  }
3082
- console.log(chalk5.dim(` Credentials saved to ${credentialsPath()}`));
3155
+ console.log(chalk5.dim(` Credentials saved to ${credsFile}`));
3083
3156
  try {
3084
3157
  const root = findGitRoot();
3085
- if (root) {
3086
- const res = ensureGitignored(gitignoreEntryForCredentials(root), root);
3158
+ if (root && isInside(root, credsFile)) {
3159
+ const res = ensureGitignored(gitignoreEntryForCredentials(root, credsFile), root);
3087
3160
  if (res.status === "created") {
3088
3161
  console.log(chalk5.dim(` Created .gitignore and ignored ${res.entry}`));
3089
3162
  } else if (res.status === "added") {
@@ -3092,7 +3165,7 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
3092
3165
  }
3093
3166
  } catch {
3094
3167
  }
3095
- console.log(chalk5.dim(' You can now run "vibgrate scan --push".'));
3168
+ console.log(chalk5.dim(` You can now run "${resolveCliInvocation()} scan --push".`));
3096
3169
  return;
3097
3170
  }
3098
3171
  if (token.status === "access_denied") {
@@ -3650,10 +3723,14 @@ function buildProgram() {
3650
3723
  const program = new Command();
3651
3724
  program.name("vg").description(
3652
3725
  "vg \u2014 local codebase intelligence for AI coding agents: a deterministic, no-API-key code graph + MCP server, drift reporting, and version-correct library docs"
3653
- ).version(VERSION, "--version", "output the version number").showSuggestionAfterError(true).addHelpText(
3654
- "after",
3655
- '\nRun `vg` to scan + map the current folder, `vg "<question>"` to ask, `vg status`/`vg verify` for state.\nDrift: `vg scan` / `vg report`. Wire into your AI agent: `vg install`.\nThe `vibgrate` command is an alias for `vg`.\nDocs: https://vibgrate.com/help'
3656
- );
3726
+ ).version(VERSION, "--version", "output the version number").showSuggestionAfterError(true).addHelpText("after", () => {
3727
+ const cli = resolveCliInvocation();
3728
+ const aliasNote = cli === "vg" ? "The `vibgrate` command is an alias for `vg`.\n" : cli === NPX_INVOCATION ? "Install globally (`npm i -g @vibgrate/cli`) to use the shorter `vg` command.\n" : "";
3729
+ return `
3730
+ Run \`${cli}\` to scan + map the current folder, \`${cli} "<question>"\` to ask, \`${cli} status\`/\`${cli} verify\` for state.
3731
+ Drift: \`${cli} scan\` / \`${cli} report\`. Wire into your AI agent: \`${cli} install\`.
3732
+ ${aliasNote}Docs: https://vibgrate.com/help`;
3733
+ });
3657
3734
  registerBuild(program);
3658
3735
  registerStatus(program);
3659
3736
  registerVerify(program);