@vibgrate/cli 2026.720.1 → 2026.720.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.
package/dist/cli.js CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import { resolveDsn, resolveIngestHost, dashHostForIngestHost, resolveCliInvocation, availableRegionIds, createWorkspaceDsn, writeStoredCredentials, credentialsPath, findGitRoot, ensureGitignored, gitignoreEntryForCredentials, clearStoredCredentials, inventory, uploadScanArtifact, ingestHostForRegionId, dsnCommand, pushCommand, CliError, ExitCode, NPX_INVOCATION, refreshIfStale, driftCount, embeddingsCached, resolveEmbedModel, loadEmbedder, getNodeEmbeddings, queryGraphSemantic, queryGraph, unavailableMessage, recordCliCall, CLI_TOOL_ALIASES, countPending, resolveOne, indexFor, countTokens, shortestPath, renderReport, impactOf, testsToRun, detectRunner, defaultGraphPath, serveStdio, ASSISTANTS, detectAssistants, usageError, loadGraph, detectServeLaunch, installAssistant, writeNavigationConfig, uninstallAssistant, coveringTests, FREE_PACK, readScanArtifact, enrichOnline, ECOSYSTEMS, discoverModels, readSavings, readUsage, exportGraph, catalogPath, libDir, stableStringify, buildGraph, writeArtifacts, writeSnapshot, UsageError, ResourceLimitError, epistemicBreakdown, 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, fetchHostedDocsCached, selectForBudget, formatForExt, hostedBase, verifyDeterminism, renderHtml, isModelReady, SAVINGS_TOOLS, DEFAULT_RATE_LABEL, readStoredCredentials, projectCredentialsPath, DEFAULT_RATE_PER_M, homeCredentialsPath, SKIP_DIRS, SKIP_FILES } from './chunk-VQLOVQZP.js';
3
- import { resolvedGrammarFiles, grammarSetVersion, hashString, canonicalize, hashBytes } from './chunk-VFO5UDAT.js';
4
- import { loadAdvancedScanHook, baselineCommand } from './chunk-VYSTOEAK.js';
5
- import { ensureDir, pathExists, writeDefaultConfig, parseDsn, detectVcs, computeRepoFingerprint, resolveRepositoryName, fetchScanPreflight, runCoreScan, VERSION, readJsonFile, writeTextFile, loadConfig, prepareCompressedUpload, computeDriftScore, titleBox, projectTypeToVulnEcosystem, driftBar, gitHistoryAvailable, buildVersionTimelines, findPackageAnyEcosystem, normalizeConstraint, findVersionCrossings, versionSatisfies, PROJECT_TYPE_TO_OSV_ECOSYSTEM, fetchRiskySymbols, brandProgressBar, resolveHead, workingTreeDirty } from './chunk-AJDUMRVI.js';
2
+ import { resolveDsn, resolveIngestHost, dashHostForIngestHost, resolveCliInvocation, availableRegionIds, createWorkspaceDsn, writeStoredCredentials, credentialsPath, findGitRoot, ensureGitignored, gitignoreEntryForCredentials, clearStoredCredentials, inventory, uploadScanArtifact, ingestHostForRegionId, dsnCommand, pushCommand, CliError, ExitCode, NPX_INVOCATION, refreshIfStale, driftCount, embeddingsCached, resolveEmbedModel, loadEmbedder, getNodeEmbeddings, queryGraphSemantic, queryGraph, unavailableMessage, recordCliCall, CLI_TOOL_ALIASES, countPending, resolveOne, indexFor, countTokens, shortestPath, renderReport, impactOf, testsToRun, detectRunner, defaultGraphPath, savingsLedgerPath, serveStdio, ASSISTANTS, detectAssistants, usageError, loadGraph, detectServeLaunch, installAssistant, writeNavigationConfig, uninstallAssistant, coveringTests, FREE_PACK, readScanArtifact, enrichOnline, ECOSYSTEMS, discoverModels, readSavings, readUsage, exportGraph, catalogPath, libDir, stableStringify, buildGraph, writeArtifacts, writeSnapshot, UsageError, ResourceLimitError, epistemicBreakdown, 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, fetchHostedDocsCached, selectForBudget, formatForExt, hostedBase, verifyDeterminism, renderHtml, isModelReady, SAVINGS_TOOLS, sanitizeClient, readStoredCredentials, projectCredentialsPath, homeCredentialsPath, SKIP_DIRS, SKIP_FILES } from './chunk-7R4O5CZD.js';
3
+ import { resolvedGrammarFiles, grammarSetVersion, hashString, canonicalize, hashBytes } from './chunk-BHA7QIPD.js';
4
+ import { loadAdvancedScanHook, baselineCommand } from './chunk-DDQ5JGBV.js';
5
+ import { ensureDir, pathExists, writeDefaultConfig, parseDsn, detectVcs, computeRepoFingerprint, resolveRepositoryName, fetchScanPreflight, runCoreScan, VERSION, readJsonFile, writeTextFile, loadConfig, prepareCompressedUpload, computeDriftScore, titleBox, projectTypeToVulnEcosystem, driftBar, gitHistoryAvailable, buildVersionTimelines, findPackageAnyEcosystem, normalizeConstraint, findVersionCrossings, versionSatisfies, PROJECT_TYPE_TO_OSV_ECOSYSTEM, fetchRiskySymbols, brandProgressBar, resolveHead, workingTreeDirty } from './chunk-OD5654VF.js';
6
6
  import './chunk-RXP66R2E.js';
7
7
  import { pathExists as pathExists$1, parseExcludePatterns } from './chunk-GI6W53LM.js';
8
- import * as fs8 from 'fs';
9
- import fs8__default, { realpathSync } from 'fs';
8
+ import * as fs9 from 'fs';
9
+ import fs9__default, { realpathSync } from 'fs';
10
10
  import * as path from 'path';
11
11
  import path__default from 'path';
12
12
  import { fileURLToPath, pathToFileURL } from 'url';
@@ -16,7 +16,7 @@ import * as crypto from 'crypto';
16
16
  import { randomUUID } from 'crypto';
17
17
  import chalk8 from 'chalk';
18
18
  import * as os from 'os';
19
- import * as fs15 from 'fs/promises';
19
+ import * as fs16 from 'fs/promises';
20
20
  import * as readline from 'readline';
21
21
  import semver from 'semver';
22
22
 
@@ -178,14 +178,14 @@ async function signGraphAttestation(root, graph, opts) {
178
178
  const explicitKey = opts.key ?? process.env.VG_ATTEST_KEY;
179
179
  const keyPath = explicitKey ? path.resolve(explicitKey) : path.join(root, ".vibgrate", DEFAULT_KEY);
180
180
  let keyGeneratedAt;
181
- if (!fs8.existsSync(keyPath)) {
181
+ if (!fs9.existsSync(keyPath)) {
182
182
  if (explicitKey) {
183
183
  throw new CliError(`signing key not found: ${explicitKey}`, ExitCode.USAGE_ERROR);
184
184
  }
185
185
  const kp = generateKeypair();
186
- fs8.mkdirSync(path.dirname(keyPath), { recursive: true });
187
- fs8.writeFileSync(keyPath, kp.privatePem, { mode: 384 });
188
- fs8.writeFileSync(`${keyPath}.pub`, kp.publicPem);
186
+ fs9.mkdirSync(path.dirname(keyPath), { recursive: true });
187
+ fs9.writeFileSync(keyPath, kp.privatePem, { mode: 384 });
188
+ fs9.writeFileSync(`${keyPath}.pub`, kp.publicPem);
189
189
  keyGeneratedAt = keyPath;
190
190
  notices.push(
191
191
  `minted a new Ed25519 signing key at ${rel(root, keyPath)} (keyid ${kp.keyid}) \u2014 keep it, add it to .gitignore, and reuse it to re-sign reproducibly`
@@ -193,7 +193,7 @@ async function signGraphAttestation(root, graph, opts) {
193
193
  }
194
194
  let privateKey;
195
195
  try {
196
- privateKey = crypto.createPrivateKey(fs8.readFileSync(keyPath, "utf8"));
196
+ privateKey = crypto.createPrivateKey(fs9.readFileSync(keyPath, "utf8"));
197
197
  } catch {
198
198
  throw new CliError(`could not read an Ed25519 private key from ${rel(root, keyPath)}`, ExitCode.USAGE_ERROR);
199
199
  }
@@ -207,8 +207,8 @@ async function signGraphAttestation(root, graph, opts) {
207
207
  const statement = buildStatement(graph, { commit });
208
208
  const envelope = signStatement(statement, privateKey);
209
209
  const outPath = opts.attestation ? path.resolve(opts.attestation) : path.join(root, ".vibgrate", DEFAULT_ATTESTATION);
210
- fs8.mkdirSync(path.dirname(outPath), { recursive: true });
211
- fs8.writeFileSync(outPath, serializeEnvelope(envelope));
210
+ fs9.mkdirSync(path.dirname(outPath), { recursive: true });
211
+ fs9.writeFileSync(outPath, serializeEnvelope(envelope));
212
212
  return {
213
213
  summary: {
214
214
  keyid: envelope.signatures[0]?.keyid ?? "",
@@ -223,7 +223,7 @@ async function signGraphAttestation(root, graph, opts) {
223
223
  }
224
224
  function verifyGraphAttestation(root, opts) {
225
225
  const bundlePath = opts.attestation ? path.resolve(opts.attestation) : path.join(root, ".vibgrate", DEFAULT_ATTESTATION);
226
- if (!fs8.existsSync(bundlePath)) {
226
+ if (!fs9.existsSync(bundlePath)) {
227
227
  if (opts.attestation || opts.pub) {
228
228
  throw new CliError(
229
229
  `no attestation at ${rel(root, bundlePath)} \u2014 sign one with \`vg build --attest\``,
@@ -232,8 +232,8 @@ function verifyGraphAttestation(root, opts) {
232
232
  }
233
233
  return { bundlePath, missing: true };
234
234
  }
235
- const envelope = parseEnvelope(fs8.readFileSync(bundlePath, "utf8"));
236
- const publicKeyPem = opts.pub ? fs8.readFileSync(path.resolve(opts.pub), "utf8") : void 0;
235
+ const envelope = parseEnvelope(fs9.readFileSync(bundlePath, "utf8"));
236
+ const publicKeyPem = opts.pub ? fs9.readFileSync(path.resolve(opts.pub), "utf8") : void 0;
237
237
  const graph = loadGraph(root) ?? void 0;
238
238
  return { bundlePath, missing: false, result: verifyEnvelope(envelope, { publicKeyPem, graph }) };
239
239
  }
@@ -586,8 +586,8 @@ function writeExport(graph, target) {
586
586
  ExitCode.USAGE_ERROR
587
587
  );
588
588
  }
589
- fs8.mkdirSync(path.dirname(path.resolve(target)), { recursive: true });
590
- fs8.writeFileSync(target, content);
589
+ fs9.mkdirSync(path.dirname(path.resolve(target)), { recursive: true });
590
+ fs9.writeFileSync(target, content);
591
591
  }
592
592
  function collect(value, prev) {
593
593
  return [...prev, value];
@@ -606,7 +606,7 @@ async function runStatus(global) {
606
606
  const root = path.resolve(global.cwd ?? ".");
607
607
  const graphPath = global.graph ?? defaultGraphPath(root);
608
608
  const graph = loadGraph(root, graphPath);
609
- const hasCache = fs8.existsSync(path.join(cacheDir(root), "parse-cache.json"));
609
+ const hasCache = fs9.existsSync(path.join(cacheDir(root), "parse-cache.json"));
610
610
  let stale = null;
611
611
  let staleExact = false;
612
612
  if (graph) {
@@ -1315,14 +1315,14 @@ function isCI(env = process.env) {
1315
1315
  function installId(env = process.env) {
1316
1316
  if (telemetryOptOut(env) !== null || isCI(env)) return randomUuid();
1317
1317
  try {
1318
- const existing = fs8.readFileSync(INSTALL_ID_FILE, "utf8").trim();
1318
+ const existing = fs9.readFileSync(INSTALL_ID_FILE, "utf8").trim();
1319
1319
  if (isUuid(existing)) return existing;
1320
1320
  } catch {
1321
1321
  }
1322
1322
  const id = randomUuid();
1323
1323
  try {
1324
- fs8.mkdirSync(INSTALL_ID_DIR, { recursive: true });
1325
- fs8.writeFileSync(INSTALL_ID_FILE, id + "\n", { mode: 384 });
1324
+ fs9.mkdirSync(INSTALL_ID_DIR, { recursive: true });
1325
+ fs9.writeFileSync(INSTALL_ID_FILE, id + "\n", { mode: 384 });
1326
1326
  } catch {
1327
1327
  }
1328
1328
  return id;
@@ -1346,7 +1346,7 @@ function ledgerFilePath(root) {
1346
1346
  }
1347
1347
  function readOffset(root) {
1348
1348
  try {
1349
- const raw = JSON.parse(fs8.readFileSync(offsetPath(root), "utf8"));
1349
+ const raw = JSON.parse(fs9.readFileSync(offsetPath(root), "utf8"));
1350
1350
  return typeof raw.offset === "number" && raw.offset >= 0 ? raw.offset : 0;
1351
1351
  } catch {
1352
1352
  return 0;
@@ -1354,8 +1354,8 @@ function readOffset(root) {
1354
1354
  }
1355
1355
  function writeOffset(root, offset) {
1356
1356
  try {
1357
- fs8.mkdirSync(cacheDir(root), { recursive: true });
1358
- fs8.writeFileSync(offsetPath(root), JSON.stringify({ offset }) + "\n");
1357
+ fs9.mkdirSync(cacheDir(root), { recursive: true });
1358
+ fs9.writeFileSync(offsetPath(root), JSON.stringify({ offset }) + "\n");
1359
1359
  } catch {
1360
1360
  }
1361
1361
  }
@@ -1363,7 +1363,7 @@ function buildBatch(root, fromOffset, id = installId()) {
1363
1363
  const file = ledgerFilePath(root);
1364
1364
  let size = 0;
1365
1365
  try {
1366
- size = fs8.statSync(file).size;
1366
+ size = fs9.statSync(file).size;
1367
1367
  } catch {
1368
1368
  return { batch: null, newOffset: 0 };
1369
1369
  }
@@ -1371,13 +1371,13 @@ function buildBatch(root, fromOffset, id = installId()) {
1371
1371
  if (start >= size) return { batch: null, newOffset: size };
1372
1372
  let text = "";
1373
1373
  try {
1374
- const fd = fs8.openSync(file, "r");
1374
+ const fd = fs9.openSync(file, "r");
1375
1375
  try {
1376
1376
  const buf = Buffer.alloc(size - start);
1377
- fs8.readSync(fd, buf, 0, buf.length, start);
1377
+ fs9.readSync(fd, buf, 0, buf.length, start);
1378
1378
  text = buf.toString("utf8");
1379
1379
  } finally {
1380
- fs8.closeSync(fd);
1380
+ fs9.closeSync(fd);
1381
1381
  }
1382
1382
  } catch {
1383
1383
  return { batch: null, newOffset: fromOffset };
@@ -1512,7 +1512,7 @@ function isLoopbackOrigin(origin) {
1512
1512
 
1513
1513
  // src/mcp/serve-stats.ts
1514
1514
  function zeroRow(key2) {
1515
- return { key: key2, calls: 0, complete: 0, partial: 0, miss: 0, totalMs: 0, vgTokens: 0, baselineTokens: 0 };
1515
+ return { key: key2, calls: 0, complete: 0, partial: 0, miss: 0, timed: 0, totalMs: 0, vgTokens: 0, baselineTokens: 0 };
1516
1516
  }
1517
1517
  var SessionStats = class {
1518
1518
  startedAt;
@@ -1521,16 +1521,26 @@ var SessionStats = class {
1521
1521
  totals = zeroRow("total");
1522
1522
  byClient = /* @__PURE__ */ new Map();
1523
1523
  byTool = /* @__PURE__ */ new Map();
1524
+ bySource = /* @__PURE__ */ new Map();
1524
1525
  constructor(now2 = Date.now()) {
1525
1526
  this.startedAt = now2;
1526
1527
  }
1527
1528
  record(sample, now2 = Date.now()) {
1528
1529
  this.revision++;
1529
1530
  this.lastCallAt = now2;
1530
- for (const row of [this.totals, this.rowFor(this.byClient, sample.client), this.rowFor(this.byTool, sample.tool)]) {
1531
+ const rows = [
1532
+ this.totals,
1533
+ this.rowFor(this.byClient, sample.client),
1534
+ this.rowFor(this.byTool, sample.tool),
1535
+ this.rowFor(this.bySource, sample.source ?? "mcp")
1536
+ ];
1537
+ for (const row of rows) {
1531
1538
  row.calls++;
1532
1539
  row[sample.outcome]++;
1533
- row.totalMs += sample.ms;
1540
+ if (typeof sample.ms === "number") {
1541
+ row.timed++;
1542
+ row.totalMs += sample.ms;
1543
+ }
1534
1544
  row.vgTokens += sample.vgTokens;
1535
1545
  row.baselineTokens += sample.baselineTokens;
1536
1546
  }
@@ -1543,7 +1553,8 @@ var SessionStats = class {
1543
1553
  lastCallAt: this.lastCallAt,
1544
1554
  totals: { ...this.totals },
1545
1555
  clients: sorted(this.byClient),
1546
- tools: sorted(this.byTool)
1556
+ tools: sorted(this.byTool),
1557
+ sources: sorted(this.bySource)
1547
1558
  };
1548
1559
  }
1549
1560
  rowFor(m, key2) {
@@ -1571,8 +1582,8 @@ function fmtTokens(n) {
1571
1582
  return String(n);
1572
1583
  }
1573
1584
  function fmtAvgMs(row) {
1574
- if (row.calls === 0) return "\u2014";
1575
- const avg = row.totalMs / row.calls;
1585
+ if (row.timed === 0) return "\u2014";
1586
+ const avg = row.totalMs / row.timed;
1576
1587
  return avg >= 1e3 ? `${(avg / 1e3).toFixed(1)}s` : `${Math.round(avg)}ms`;
1577
1588
  }
1578
1589
  var teal2 = (s) => c.hex("#3FB0A4")(s);
@@ -1585,15 +1596,17 @@ function serveStatusLines(snap, now2, tick = 0) {
1585
1596
  const lines = [];
1586
1597
  if (snap.totals.calls === 0) {
1587
1598
  lines.push(
1588
- ` ${spinner} ${c.bold.white("vg serve")} ${c.dim("\xB7")} up ${teal2(up)} ${c.dim("\xB7 waiting for your assistant\u2019s first tool call\u2026")}`
1599
+ ` ${spinner} ${c.bold.white("vg serve")} ${c.dim("\xB7")} up ${teal2(up)} ${c.dim("\xB7 waiting for your assistant\u2019s first call (MCP tool call, or `vg \u2026 --client=<ai>` CLI)\u2026")}`
1589
1600
  );
1590
1601
  return lines;
1591
1602
  }
1592
1603
  const t = snap.totals;
1593
1604
  const answered = Math.round((t.complete + t.partial) / t.calls * 100);
1605
+ const cli = snap.sources.find((s) => s.key === "cli");
1606
+ const via = cli ? c.dim(" \xB7 via ") + snap.sources.map((s) => `${c.white(s.key)} ${teal2(String(s.calls))}`).join(c.dim(" \xB7 ")) : "";
1594
1607
  const ago = snap.lastCallAt === null ? "" : c.dim(` \xB7 last call ${fmtUptime(now2 - snap.lastCallAt)} ago`);
1595
1608
  lines.push(
1596
- ` ${spinner} ${c.bold.white("vg serve")} ${c.dim("\xB7")} up ${teal2(up)} ${c.dim("\xB7")} ${c.bold.white(String(t.calls))} call${t.calls === 1 ? "" : "s"} ${c.dim("\xB7")} ${mint2(`${answered}%`)} answered${ago}`
1609
+ ` ${spinner} ${c.bold.white("vg serve")} ${c.dim("\xB7")} up ${teal2(up)} ${c.dim("\xB7")} ${c.bold.white(String(t.calls))} call${t.calls === 1 ? "" : "s"} ${c.dim("\xB7")} ${mint2(`${answered}%`)} answered${via}${ago}`
1597
1610
  );
1598
1611
  lines.push(
1599
1612
  ` ${c.dim("clients")} ` + snap.clients.map((cl) => `${c.white(cl.key)} ${teal2(String(cl.calls))}${c.dim(` (avg ${fmtAvgMs(cl)})`)}`).join(c.dim(" \xB7 "))
@@ -1613,13 +1626,12 @@ function serveStatusLines(snap, now2, tick = 0) {
1613
1626
  }
1614
1627
  if (t.baselineTokens > 0) {
1615
1628
  const savedTokens = Math.max(0, t.baselineTokens - t.vgTokens);
1616
- const savedUsd = savedTokens / 1e6 * DEFAULT_RATE_PER_M;
1617
1629
  const ratio = t.vgTokens > 0 ? (t.baselineTokens / t.vgTokens).toFixed(1) : "\u2014";
1618
1630
  lines.push(
1619
1631
  ` ${c.dim("session")} ctx ${c.bold.white(fmtTokens(t.vgTokens))} ` + c.dim(`vs grep/read \u2248${fmtTokens(t.baselineTokens)} \u2192 `) + mint2(`${ratio}\xD7 fewer`)
1620
1632
  );
1621
1633
  lines.push(
1622
- ` ${c.dim("est. saved \u2248")} ${mint2(`${fmtTokens(savedTokens)} tokens ($${savedUsd.toFixed(2)})`)} ` + c.dim(`(${DEFAULT_RATE_LABEL}; estimate)`)
1634
+ ` ${c.dim("est. saved \u2248")} ${mint2(`${fmtTokens(savedTokens)} context tokens`)} ${c.dim("(estimate)")}`
1623
1635
  );
1624
1636
  }
1625
1637
  return lines;
@@ -1629,7 +1641,9 @@ function serveHeartbeatLine(snap, now2) {
1629
1641
  const t = snap.totals;
1630
1642
  if (t.calls === 0) return c.dim(`vg \xB7 serving for ${up} \u2014 no tool calls yet`);
1631
1643
  const clients = snap.clients.map((cl) => `${cl.key} ${cl.calls}`).join(", ");
1644
+ const cli = snap.sources.find((s) => s.key === "cli");
1632
1645
  let line = `vg \xB7 serving for ${up} \u2014 ${t.calls} call${t.calls === 1 ? "" : "s"} (${clients})`;
1646
+ if (cli) line += ` \xB7 ${cli.calls} via CLI`;
1633
1647
  if (t.baselineTokens > 0) {
1634
1648
  const savedTokens = Math.max(0, t.baselineTokens - t.vgTokens);
1635
1649
  line += ` \xB7 ctx ${fmtTokens(t.vgTokens)} vs \u2248${fmtTokens(t.baselineTokens)} grep/read \xB7 est. saved \u2248 ${fmtTokens(savedTokens)} tokens`;
@@ -1723,6 +1737,89 @@ var ServeStatusDisplay = class {
1723
1737
  this.cursorHidden = false;
1724
1738
  };
1725
1739
  };
1740
+ var POLL_INTERVAL_MS = 2e3;
1741
+ var LedgerTail = class {
1742
+ constructor(file, stats, intervalMs = POLL_INTERVAL_MS) {
1743
+ this.file = file;
1744
+ this.stats = stats;
1745
+ this.intervalMs = intervalMs;
1746
+ }
1747
+ file;
1748
+ stats;
1749
+ intervalMs;
1750
+ timer = null;
1751
+ /** Byte offset of the next unread ledger byte (always at a line boundary). */
1752
+ offset = 0;
1753
+ start() {
1754
+ if (this.timer) return;
1755
+ this.offset = this.sizeOf();
1756
+ this.timer = setInterval(() => this.poll(), this.intervalMs);
1757
+ this.timer.unref?.();
1758
+ }
1759
+ stop() {
1760
+ if (this.timer) {
1761
+ clearInterval(this.timer);
1762
+ this.timer = null;
1763
+ }
1764
+ }
1765
+ /** One poll cycle — exposed for tests (deterministic, no timers needed). */
1766
+ poll(now2 = Date.now()) {
1767
+ let size;
1768
+ try {
1769
+ size = this.sizeOf();
1770
+ } catch {
1771
+ return;
1772
+ }
1773
+ if (size < this.offset) this.offset = 0;
1774
+ if (size === this.offset) return;
1775
+ let chunk;
1776
+ try {
1777
+ const fd = fs9.openSync(this.file, "r");
1778
+ try {
1779
+ chunk = Buffer.alloc(size - this.offset);
1780
+ fs9.readSync(fd, chunk, 0, chunk.length, this.offset);
1781
+ } finally {
1782
+ fs9.closeSync(fd);
1783
+ }
1784
+ } catch {
1785
+ return;
1786
+ }
1787
+ const lastNewline = chunk.lastIndexOf(10);
1788
+ if (lastNewline < 0) return;
1789
+ const text = chunk.subarray(0, lastNewline).toString("utf8");
1790
+ this.offset += lastNewline + 1;
1791
+ for (const line of text.split("\n")) ingestLedgerLine(this.stats, line, now2);
1792
+ }
1793
+ sizeOf() {
1794
+ try {
1795
+ return fs9.statSync(this.file).size;
1796
+ } catch {
1797
+ return 0;
1798
+ }
1799
+ }
1800
+ };
1801
+ function ingestLedgerLine(stats, line, now2) {
1802
+ if (!line.trim()) return;
1803
+ let e;
1804
+ try {
1805
+ e = JSON.parse(line);
1806
+ } catch {
1807
+ return;
1808
+ }
1809
+ if (e.source !== "cli" || typeof e.tool !== "string") return;
1810
+ stats.record(
1811
+ {
1812
+ tool: e.tool,
1813
+ client: sanitizeClient(e.client),
1814
+ outcome: e.outcome ?? "complete",
1815
+ source: "cli",
1816
+ // CLI lines carry no wall time — leave ms absent ("not measured").
1817
+ vgTokens: e.vgTokens ?? 0,
1818
+ baselineTokens: e.baselineTokens ?? 0
1819
+ },
1820
+ now2
1821
+ );
1822
+ }
1726
1823
 
1727
1824
  // src/commands/serve.ts
1728
1825
  var SHARE_FLUSH_INTERVAL_MS = 5 * 60 * 1e3;
@@ -1760,6 +1857,7 @@ function registerServe(program) {
1760
1857
  }
1761
1858
  if (shareStats) startSharing(root);
1762
1859
  const display = stats ? new ServeStatusDisplay(stats) : void 0;
1860
+ if (stats) new LedgerTail(savingsLedgerPath(root), stats).start();
1763
1861
  const freshness = refresh ? "auto-refresh" : "as built";
1764
1862
  if (opts.http) {
1765
1863
  await serveHttp(graphPath, opts.host ?? "127.0.0.1", Number(opts.port) || 7437, serveOpts, freshness, () => display?.start());
@@ -1773,7 +1871,7 @@ function registerServe(program) {
1773
1871
  }
1774
1872
  async function ensureServableGraph(root, graphPath, global, refresh, opts = {}) {
1775
1873
  if (refresh) {
1776
- if (!fs8.existsSync(graphPath)) {
1874
+ if (!fs9.existsSync(graphPath)) {
1777
1875
  info(c.dim("vg \xB7 no map found \u2014 building it before serving\u2026"));
1778
1876
  await runBuild(
1779
1877
  [],
@@ -1790,7 +1888,7 @@ async function ensureServableGraph(root, graphPath, global, refresh, opts = {})
1790
1888
  }
1791
1889
  }
1792
1890
  }
1793
- if (!fs8.existsSync(graphPath)) {
1891
+ if (!fs9.existsSync(graphPath)) {
1794
1892
  throw new CliError(
1795
1893
  `no map found at ${graphPath} \u2014 run \`vg\` to build one first`,
1796
1894
  ExitCode.NOT_FOUND
@@ -2073,7 +2171,7 @@ function manifestHash(root) {
2073
2171
  const walk2 = (dir) => {
2074
2172
  let entries;
2075
2173
  try {
2076
- entries = fs8.readdirSync(dir, { withFileTypes: true });
2174
+ entries = fs9.readdirSync(dir, { withFileTypes: true });
2077
2175
  } catch {
2078
2176
  return;
2079
2177
  }
@@ -2089,7 +2187,7 @@ function manifestHash(root) {
2089
2187
  if (!isManifest(abs) && !SKIP_FILES.has(base)) continue;
2090
2188
  try {
2091
2189
  const rel3 = path.relative(root, abs).split(path.sep).join("/");
2092
- files.push({ rel: rel3, hash: hashBytes(fs8.readFileSync(abs)) });
2190
+ files.push({ rel: rel3, hash: hashBytes(fs9.readFileSync(abs)) });
2093
2191
  } catch {
2094
2192
  }
2095
2193
  }
@@ -2100,7 +2198,7 @@ function manifestHash(root) {
2100
2198
  }
2101
2199
  function loadScanCache(root, key2) {
2102
2200
  try {
2103
- const raw = JSON.parse(fs8.readFileSync(cachePath(root), "utf8"));
2201
+ const raw = JSON.parse(fs9.readFileSync(cachePath(root), "utf8"));
2104
2202
  if (raw.version === CACHE_VERSION && raw.manifestHash === key2.manifestHash && raw.toolVersion === key2.toolVersion && raw.offline === key2.offline && raw.artifact) {
2105
2203
  return { manifestHash: raw.manifestHash, artifact: raw.artifact };
2106
2204
  }
@@ -2110,8 +2208,8 @@ function loadScanCache(root, key2) {
2110
2208
  }
2111
2209
  function writeScanCache(root, key2, artifact) {
2112
2210
  try {
2113
- fs8.mkdirSync(cacheDir(root), { recursive: true });
2114
- fs8.writeFileSync(
2211
+ fs9.mkdirSync(cacheDir(root), { recursive: true });
2212
+ fs9.writeFileSync(
2115
2213
  cachePath(root),
2116
2214
  stableStringify(
2117
2215
  { version: CACHE_VERSION, manifestHash: key2.manifestHash, toolVersion: key2.toolVersion, offline: key2.offline, artifact },
@@ -2935,7 +3033,7 @@ function hoverMarkdown(dep, contribution) {
2935
3033
  return lines.join("\n");
2936
3034
  }
2937
3035
  function startLanguageServer(opts) {
2938
- if (!fs8.existsSync(opts.root)) {
3036
+ if (!fs9.existsSync(opts.root)) {
2939
3037
  process.stderr.write(`vg lsp: root does not exist: ${opts.root}
2940
3038
  `);
2941
3039
  process.exit(1);
@@ -3074,7 +3172,7 @@ function registerShare(program) {
3074
3172
  const global = readGlobal(this);
3075
3173
  const root = rootOf(global);
3076
3174
  const gitDir = path.join(root, ".git");
3077
- if (!fs8.existsSync(gitDir)) {
3175
+ if (!fs9.existsSync(gitDir)) {
3078
3176
  throw new CliError("not a git repository \u2014 run `vg share` inside a git repo", ExitCode.USAGE_ERROR);
3079
3177
  }
3080
3178
  if (opts.undo) {
@@ -3096,22 +3194,22 @@ function registerShare(program) {
3096
3194
  }
3097
3195
  function writeVibgrateGitignore(root, reports) {
3098
3196
  const dir = path.join(root, ".vibgrate");
3099
- fs8.mkdirSync(dir, { recursive: true });
3197
+ fs9.mkdirSync(dir, { recursive: true });
3100
3198
  const lines = ["# Managed by `vg share` \u2014 keep graph.json committed, ignore the rest", "cache/", "facts.jsonl"];
3101
3199
  if (!reports) lines.push("graph.html", "GRAPH_REPORT.md");
3102
- fs8.writeFileSync(path.join(dir, ".gitignore"), `${lines.join("\n")}
3200
+ fs9.writeFileSync(path.join(dir, ".gitignore"), `${lines.join("\n")}
3103
3201
  `);
3104
3202
  }
3105
3203
  function writeGitAttributes(root) {
3106
3204
  const file = path.join(root, ".gitattributes");
3107
3205
  const line = ".vibgrate/graph.json merge=vg";
3108
- let content = fs8.existsSync(file) ? fs8.readFileSync(file, "utf8") : "";
3206
+ let content = fs9.existsSync(file) ? fs9.readFileSync(file, "utf8") : "";
3109
3207
  if (!content.split("\n").some((l) => l.trim() === line)) {
3110
3208
  content = content.length ? `${content.replace(/\s*$/, "")}
3111
3209
  ${line}
3112
3210
  ` : `${line}
3113
3211
  `;
3114
- fs8.writeFileSync(file, content);
3212
+ fs9.writeFileSync(file, content);
3115
3213
  }
3116
3214
  }
3117
3215
  function configureMergeDriver(root) {
@@ -3120,7 +3218,7 @@ function configureMergeDriver(root) {
3120
3218
  }
3121
3219
  function installPreCommitHook(root) {
3122
3220
  const hooksDir = hookDir(root);
3123
- fs8.mkdirSync(hooksDir, { recursive: true });
3221
+ fs9.mkdirSync(hooksDir, { recursive: true });
3124
3222
  const file = path.join(hooksDir, "pre-commit");
3125
3223
  const block = [
3126
3224
  HOOK_BEGIN,
@@ -3130,7 +3228,7 @@ function installPreCommitHook(root) {
3130
3228
  "fi",
3131
3229
  HOOK_END
3132
3230
  ].join("\n");
3133
- let content = fs8.existsSync(file) ? fs8.readFileSync(file, "utf8") : "#!/bin/sh\n";
3231
+ let content = fs9.existsSync(file) ? fs9.readFileSync(file, "utf8") : "#!/bin/sh\n";
3134
3232
  const re = new RegExp(`${escapeRe(HOOK_BEGIN)}[\\s\\S]*?${escapeRe(HOOK_END)}`);
3135
3233
  if (re.test(content)) content = content.replace(re, block);
3136
3234
  else content = `${content.replace(/\s*$/, "")}
@@ -3139,21 +3237,21 @@ ${block}
3139
3237
  `;
3140
3238
  if (!content.startsWith("#!")) content = `#!/bin/sh
3141
3239
  ${content}`;
3142
- fs8.writeFileSync(file, content);
3143
- fs8.chmodSync(file, 493);
3240
+ fs9.writeFileSync(file, content);
3241
+ fs9.chmodSync(file, 493);
3144
3242
  }
3145
3243
  function undo(root) {
3146
3244
  const hookFile = path.join(hookDir(root), "pre-commit");
3147
- if (fs8.existsSync(hookFile)) {
3245
+ if (fs9.existsSync(hookFile)) {
3148
3246
  const re = new RegExp(`\\n*${escapeRe(HOOK_BEGIN)}[\\s\\S]*?${escapeRe(HOOK_END)}\\n*`);
3149
- const next2 = fs8.readFileSync(hookFile, "utf8").replace(re, "\n");
3150
- if (next2.trim() === "#!/bin/sh") fs8.rmSync(hookFile);
3151
- else fs8.writeFileSync(hookFile, next2);
3247
+ const next2 = fs9.readFileSync(hookFile, "utf8").replace(re, "\n");
3248
+ if (next2.trim() === "#!/bin/sh") fs9.rmSync(hookFile);
3249
+ else fs9.writeFileSync(hookFile, next2);
3152
3250
  }
3153
3251
  const attr = path.join(root, ".gitattributes");
3154
- if (fs8.existsSync(attr)) {
3155
- const next2 = fs8.readFileSync(attr, "utf8").split("\n").filter((l) => l.trim() !== ".vibgrate/graph.json merge=vg").join("\n");
3156
- fs8.writeFileSync(attr, next2);
3252
+ if (fs9.existsSync(attr)) {
3253
+ const next2 = fs9.readFileSync(attr, "utf8").split("\n").filter((l) => l.trim() !== ".vibgrate/graph.json merge=vg").join("\n");
3254
+ fs9.writeFileSync(attr, next2);
3157
3255
  }
3158
3256
  try {
3159
3257
  git(root, ["config", "--remove-section", "merge.vg"]);
@@ -3286,7 +3384,7 @@ function grepBaselineTokens(term, files) {
3286
3384
  if (f.size > 512 * 1024) continue;
3287
3385
  let content = "";
3288
3386
  try {
3289
- content = fs8.readFileSync(f.abs, "utf8");
3387
+ content = fs9.readFileSync(f.abs, "utf8");
3290
3388
  } catch {
3291
3389
  continue;
3292
3390
  }
@@ -3296,7 +3394,7 @@ function grepBaselineTokens(term, files) {
3296
3394
  }
3297
3395
  function safeSize(abs) {
3298
3396
  try {
3299
- return fs8.statSync(abs).size;
3397
+ return fs9.statSync(abs).size;
3300
3398
  } catch {
3301
3399
  return 0;
3302
3400
  }
@@ -3630,7 +3728,7 @@ function loadStandards(root) {
3630
3728
  const p = path__default.join(root, rel3);
3631
3729
  let text;
3632
3730
  try {
3633
- text = fs8__default.readFileSync(p, "utf8");
3731
+ text = fs9__default.readFileSync(p, "utf8");
3634
3732
  } catch {
3635
3733
  continue;
3636
3734
  }
@@ -3869,7 +3967,7 @@ async function addCmd(root, rest, opts, asJson) {
3869
3967
  }
3870
3968
  function readPackageVersion(root) {
3871
3969
  try {
3872
- const pkg = JSON.parse(fs8__default.readFileSync(path__default.join(root, "package.json"), "utf8"));
3970
+ const pkg = JSON.parse(fs9__default.readFileSync(path__default.join(root, "package.json"), "utf8"));
3873
3971
  return typeof pkg.version === "string" ? pkg.version : void 0;
3874
3972
  } catch {
3875
3973
  return void 0;
@@ -3877,7 +3975,7 @@ function readPackageVersion(root) {
3877
3975
  }
3878
3976
  function readOptionalFile(p) {
3879
3977
  try {
3880
- const t = fs8__default.readFileSync(p, "utf8");
3978
+ const t = fs9__default.readFileSync(p, "utf8");
3881
3979
  return t.trim() ? t : void 0;
3882
3980
  } catch {
3883
3981
  return void 0;
@@ -4023,8 +4121,8 @@ function registerExport(program) {
4023
4121
  out(content.trimEnd());
4024
4122
  return;
4025
4123
  }
4026
- fs8.mkdirSync(path.dirname(path.resolve(file)), { recursive: true });
4027
- fs8.writeFileSync(file, content);
4124
+ fs9.mkdirSync(path.dirname(path.resolve(file)), { recursive: true });
4125
+ fs9.writeFileSync(file, content);
4028
4126
  info(`${c.green("\u2714")} wrote ${file} ${c.dim(`(${format})`)}`);
4029
4127
  });
4030
4128
  applyGlobalOptions(cmd);
@@ -4047,24 +4145,24 @@ function registerBundle(program) {
4047
4145
  } catch {
4048
4146
  throw new CliError("no grammars found to bundle (run a build first)", ExitCode.ERROR);
4049
4147
  }
4050
- fs8.mkdirSync(path.join(outDir, "grammars"), { recursive: true });
4148
+ fs9.mkdirSync(path.join(outDir, "grammars"), { recursive: true });
4051
4149
  let grammarCount = 0;
4052
4150
  for (const { fileName, absPath } of grammarFiles) {
4053
- fs8.copyFileSync(absPath, path.join(outDir, "grammars", fileName));
4151
+ fs9.copyFileSync(absPath, path.join(outDir, "grammars", fileName));
4054
4152
  grammarCount++;
4055
4153
  }
4056
4154
  const included = [`grammars/ (${grammarCount} .wasm)`];
4057
4155
  const graphSrc = global.graph ?? defaultGraphPath(root);
4058
- if (fs8.existsSync(graphSrc)) {
4059
- fs8.copyFileSync(graphSrc, path.join(outDir, "graph.json"));
4156
+ if (fs9.existsSync(graphSrc)) {
4157
+ fs9.copyFileSync(graphSrc, path.join(outDir, "graph.json"));
4060
4158
  included.push("graph.json");
4061
4159
  }
4062
4160
  const cat = catalogPath(root);
4063
- if (fs8.existsSync(cat)) {
4064
- fs8.copyFileSync(cat, path.join(outDir, "vibgrate.lib.json"));
4161
+ if (fs9.existsSync(cat)) {
4162
+ fs9.copyFileSync(cat, path.join(outDir, "vibgrate.lib.json"));
4065
4163
  included.push("vibgrate.lib.json");
4066
- if (fs8.existsSync(libDir(root))) {
4067
- fs8.cpSync(libDir(root), path.join(outDir, "lib"), { recursive: true });
4164
+ if (fs9.existsSync(libDir(root))) {
4165
+ fs9.cpSync(libDir(root), path.join(outDir, "lib"), { recursive: true });
4068
4166
  included.push("lib/");
4069
4167
  }
4070
4168
  }
@@ -4076,7 +4174,7 @@ function registerBundle(program) {
4076
4174
  included,
4077
4175
  usage: "Run `vg build --grammars <this dir>/grammars` to build fully offline."
4078
4176
  };
4079
- fs8.writeFileSync(path.join(outDir, "MANIFEST.json"), `${stableStringify(manifest, 2)}
4177
+ fs9.writeFileSync(path.join(outDir, "MANIFEST.json"), `${stableStringify(manifest, 2)}
4080
4178
  `);
4081
4179
  if (global.json) {
4082
4180
  json({ out: path.relative(root, outDir), ...manifest });
@@ -4100,7 +4198,7 @@ async function runDoctor(global) {
4100
4198
  const root = rootOf(global);
4101
4199
  const local = global.local === true;
4102
4200
  const graphPath = global.graph ?? defaultGraphPath(root);
4103
- const configFile = CONFIG_BASENAMES.find((f) => fs8.existsSync(path.join(root, f))) ?? null;
4201
+ const configFile = CONFIG_BASENAMES.find((f) => fs9.existsSync(path.join(root, f))) ?? null;
4104
4202
  const graph = loadGraph(root, graphPath);
4105
4203
  let staleFiles = null;
4106
4204
  if (graph) {
@@ -4128,7 +4226,7 @@ async function runDoctor(global) {
4128
4226
  },
4129
4227
  libCatalog: {
4130
4228
  path: path.relative(root, catalogPath(root)),
4131
- present: fs8.existsSync(catalogPath(root)),
4229
+ present: fs9.existsSync(catalogPath(root)),
4132
4230
  libraries: libCount
4133
4231
  },
4134
4232
  credentials: creds,
@@ -4220,7 +4318,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
4220
4318
  console.log(chalk8.green("\u2714") + ` Created ${chalk8.bold("vibgrate.config.ts")}`);
4221
4319
  }
4222
4320
  if (opts.baseline) {
4223
- const { runBaseline } = await import('./baseline-5QPV6API.js');
4321
+ const { runBaseline } = await import('./baseline-WIZAFIJS.js');
4224
4322
  await runBaseline(rootDir);
4225
4323
  }
4226
4324
  console.log("");
@@ -4330,9 +4428,9 @@ async function fileReferencesIdentifier(rootDir, relFile, identifier, readFile3)
4330
4428
  }
4331
4429
  async function defaultReadFile(absPath) {
4332
4430
  try {
4333
- const stat2 = await fs15.stat(absPath);
4431
+ const stat2 = await fs16.stat(absPath);
4334
4432
  if (!stat2.isFile() || stat2.size > MAX_FILE_BYTES) return null;
4335
- return await fs15.readFile(absPath, "utf8");
4433
+ return await fs16.readFile(absPath, "utf8");
4336
4434
  } catch {
4337
4435
  return null;
4338
4436
  }
@@ -4977,7 +5075,7 @@ var SKIP_DIRS2 = /* @__PURE__ */ new Set([
4977
5075
  function scanStaleness(rootDir, artifactPath) {
4978
5076
  let ref;
4979
5077
  try {
4980
- ref = fs8.statSync(artifactPath).mtimeMs;
5078
+ ref = fs9.statSync(artifactPath).mtimeMs;
4981
5079
  } catch {
4982
5080
  return { stale: false };
4983
5081
  }
@@ -4989,7 +5087,7 @@ function scanStaleness(rootDir, artifactPath) {
4989
5087
  if (depth > 8 || scanned > MAX_ENTRIES) return;
4990
5088
  let entries;
4991
5089
  try {
4992
- entries = fs8.readdirSync(dir, { withFileTypes: true });
5090
+ entries = fs9.readdirSync(dir, { withFileTypes: true });
4993
5091
  } catch {
4994
5092
  return;
4995
5093
  }
@@ -5004,7 +5102,7 @@ function scanStaleness(rootDir, artifactPath) {
5004
5102
  const abs = path.join(dir, e.name);
5005
5103
  let mtimeMs;
5006
5104
  try {
5007
- mtimeMs = fs8.statSync(abs).mtimeMs;
5105
+ mtimeMs = fs9.statSync(abs).mtimeMs;
5008
5106
  } catch {
5009
5107
  continue;
5010
5108
  }
@@ -5138,7 +5236,7 @@ function analyzeTree(root, packages) {
5138
5236
  if (depth > MAX_DEPTH || filesSeen >= MAX_FILES) return;
5139
5237
  let entries;
5140
5238
  try {
5141
- entries = fs8.readdirSync(dir, { withFileTypes: true });
5239
+ entries = fs9.readdirSync(dir, { withFileTypes: true });
5142
5240
  } catch {
5143
5241
  return;
5144
5242
  }
@@ -5156,8 +5254,8 @@ function analyzeTree(root, packages) {
5156
5254
  const full = path.join(dir, entry.name);
5157
5255
  let content;
5158
5256
  try {
5159
- if (fs8.statSync(full).size > MAX_FILE_BYTES2) continue;
5160
- content = fs8.readFileSync(full, "utf8");
5257
+ if (fs9.statSync(full).size > MAX_FILE_BYTES2) continue;
5258
+ content = fs9.readFileSync(full, "utf8");
5161
5259
  } catch {
5162
5260
  continue;
5163
5261
  }
@@ -5525,8 +5623,8 @@ async function fetchLatestVersion() {
5525
5623
  }
5526
5624
  async function writeCache(data) {
5527
5625
  try {
5528
- await fs15.mkdir(CACHE_DIR, { recursive: true });
5529
- await fs15.writeFile(CACHE_FILE, JSON.stringify(data), "utf-8");
5626
+ await fs16.mkdir(CACHE_DIR, { recursive: true });
5627
+ await fs16.writeFile(CACHE_FILE, JSON.stringify(data), "utf-8");
5530
5628
  } catch {
5531
5629
  }
5532
5630
  }
@@ -5537,7 +5635,7 @@ function detectGlobalInstall(execPath = process.argv[1] || "", cwd = process.cwd
5537
5635
  if (!execPath) return null;
5538
5636
  let resolved = execPath;
5539
5637
  try {
5540
- resolved = fs8.realpathSync(execPath);
5638
+ resolved = fs9.realpathSync(execPath);
5541
5639
  } catch {
5542
5640
  }
5543
5641
  const p = resolved.replace(/\\/g, "/");
@@ -7477,7 +7575,7 @@ function dispatch(argv, cwd) {
7477
7575
  }
7478
7576
  const looksLikeQuestion = /\s/.test(first) || first.endsWith("?");
7479
7577
  if (looksLikeQuestion) return ["ask", ...args];
7480
- if (fs8.existsSync(path.resolve(cwd, first))) {
7578
+ if (fs9.existsSync(path.resolve(cwd, first))) {
7481
7579
  return [hasBuildOnlyFlag(args) ? "build" : "scan", ...args];
7482
7580
  }
7483
7581
  const moved = MOVED_COMMANDS[first];