@vibgrate/cli 2026.708.2 → 2026.709.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,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, 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, 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, fetchHostedDocs, selectForBudget, formatForExt, verifyDeterminism, renderHtml, isModelReady } from './chunk-AWEAREOE.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, serveStdio, ASSISTANTS, 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, fetchHostedDocs, selectForBudget, formatForExt, verifyDeterminism, renderHtml, isModelReady } from './chunk-MPKMAERO.js';
3
3
  import { resolvedGrammarFiles, grammarSetVersion, canonicalize } from './chunk-X5YT263H.js';
4
- import { loadAdvancedScanHook, baselineCommand } from './chunk-GHLYQZWT.js';
5
- import { ensureDir, pathExists, writeDefaultConfig, parseDsn, detectVcs, computeRepoFingerprint, resolveRepositoryName, fetchScanPreflight, runCoreScan, VERSION, readJsonFile, writeTextFile, prepareCompressedUpload, computeDriftScore, titleBox, projectTypeToVulnEcosystem, driftBar, gitHistoryAvailable, buildVersionTimelines, findPackageAnyEcosystem, normalizeConstraint, findVersionCrossings, versionSatisfies, brandProgressBar, resolveHead, workingTreeDirty } from './chunk-ICBBG6U3.js';
4
+ import { loadAdvancedScanHook, baselineCommand } from './chunk-IRZCYPFC.js';
5
+ import { ensureDir, pathExists, writeDefaultConfig, parseDsn, detectVcs, computeRepoFingerprint, resolveRepositoryName, fetchScanPreflight, runCoreScan, VERSION, readJsonFile, writeTextFile, prepareCompressedUpload, computeDriftScore, titleBox, projectTypeToVulnEcosystem, driftBar, gitHistoryAvailable, buildVersionTimelines, findPackageAnyEcosystem, normalizeConstraint, findVersionCrossings, versionSatisfies, brandProgressBar, resolveHead, workingTreeDirty } from './chunk-PXZ5KPVN.js';
6
6
  import './chunk-RXP66R2E.js';
7
7
  import { pathExists as pathExists$1, parseExcludePatterns } from './chunk-GI6W53LM.js';
8
- import * as fs5 from 'fs';
9
- import fs5__default, { realpathSync } from 'fs';
8
+ import * as fs6 from 'fs';
9
+ import fs6__default, { realpathSync } from 'fs';
10
10
  import * as path from 'path';
11
11
  import path__default from 'path';
12
12
  import { fileURLToPath } from 'url';
@@ -14,10 +14,10 @@ import { Command, CommanderError, Option } from 'commander';
14
14
  import { execSync, spawn, spawnSync, execFileSync, execFile } from 'child_process';
15
15
  import * as crypto from 'crypto';
16
16
  import { randomUUID } from 'crypto';
17
- import chalk7 from 'chalk';
17
+ import chalk8 from 'chalk';
18
18
  import * as os from 'os';
19
19
  import * as readline from 'readline';
20
- import * as fs14 from 'fs/promises';
20
+ import * as fs15 from 'fs/promises';
21
21
  import semver from 'semver';
22
22
 
23
23
  var DSSE_PAYLOAD_TYPE = "application/vnd.in-toto+json";
@@ -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 (!fs5.existsSync(keyPath)) {
181
+ if (!fs6.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
- fs5.mkdirSync(path.dirname(keyPath), { recursive: true });
187
- fs5.writeFileSync(keyPath, kp.privatePem, { mode: 384 });
188
- fs5.writeFileSync(`${keyPath}.pub`, kp.publicPem);
186
+ fs6.mkdirSync(path.dirname(keyPath), { recursive: true });
187
+ fs6.writeFileSync(keyPath, kp.privatePem, { mode: 384 });
188
+ fs6.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(fs5.readFileSync(keyPath, "utf8"));
196
+ privateKey = crypto.createPrivateKey(fs6.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
- fs5.mkdirSync(path.dirname(outPath), { recursive: true });
211
- fs5.writeFileSync(outPath, serializeEnvelope(envelope));
210
+ fs6.mkdirSync(path.dirname(outPath), { recursive: true });
211
+ fs6.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 (!fs5.existsSync(bundlePath)) {
226
+ if (!fs6.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(fs5.readFileSync(bundlePath, "utf8"));
236
- const publicKeyPem = opts.pub ? fs5.readFileSync(path.resolve(opts.pub), "utf8") : void 0;
235
+ const envelope = parseEnvelope(fs6.readFileSync(bundlePath, "utf8"));
236
+ const publicKeyPem = opts.pub ? fs6.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
  }
@@ -274,9 +274,9 @@ function json(value) {
274
274
  process.stdout.write(`${JSON.stringify(value, null, 2)}
275
275
  `);
276
276
  }
277
- var c = chalk7;
277
+ var c = chalk8;
278
278
  function disableColor() {
279
- chalk7.level = 0;
279
+ chalk8.level = 0;
280
280
  }
281
281
 
282
282
  // src/util/logo.ts
@@ -346,7 +346,7 @@ var ProgressBar = class {
346
346
 
347
347
  // src/cli-options.ts
348
348
  function applyGlobalOptions(cmd) {
349
- return cmd.option("-C, --cwd <dir>", "run as if started in <dir>", ".").option("--deep", "do more: precise resolution, all edges, semantic links").option("--json", "machine-readable JSON on stdout").option("--generated-at <iso>", "pin the artifact timestamp for byte-deterministic output").option("--graph <file>", "override the map path").option("--no-cache", "full rebuild (ignore the incremental cache)").option("--local", "never touch the network (lexical-only semantic)").option("--quiet", "suppress progress output").option("--no-color", "disable colored output");
349
+ return cmd.option("-C, --cwd <dir>", "run as if started in <dir>", ".").option("--deep", "do more: precise resolution, all edges, semantic links").option("--json", "machine-readable JSON on stdout").option("--generated-at <iso>", "pin the artifact timestamp for byte-deterministic output").option("--graph <file>", "override the map path").option("--no-cache", "full rebuild (ignore the incremental cache)").option("--local", "never touch the network (lexical-only semantic)").option("--quiet", "suppress progress output").option("--client <name>", "identify the AI client (e.g. claude) so navigation calls are counted in vg savings").option("--no-color", "disable colored output");
350
350
  }
351
351
  function readGlobal(cmd) {
352
352
  const o = cmd.opts();
@@ -358,7 +358,8 @@ function readGlobal(cmd) {
358
358
  graph: o.graph,
359
359
  noCache: o.cache === false,
360
360
  local: o.local,
361
- quiet: o.quiet
361
+ quiet: o.quiet,
362
+ client: o.client
362
363
  };
363
364
  }
364
365
 
@@ -583,8 +584,8 @@ function writeExport(graph, target) {
583
584
  ExitCode.USAGE_ERROR
584
585
  );
585
586
  }
586
- fs5.mkdirSync(path.dirname(path.resolve(target)), { recursive: true });
587
- fs5.writeFileSync(target, content);
587
+ fs6.mkdirSync(path.dirname(path.resolve(target)), { recursive: true });
588
+ fs6.writeFileSync(target, content);
588
589
  }
589
590
  function collect(value, prev) {
590
591
  return [...prev, value];
@@ -603,7 +604,7 @@ async function runStatus(global) {
603
604
  const root = path.resolve(global.cwd ?? ".");
604
605
  const graphPath = global.graph ?? defaultGraphPath(root);
605
606
  const graph = loadGraph(root, graphPath);
606
- const hasCache = fs5.existsSync(path.join(cacheDir(root), "parse-cache.json"));
607
+ const hasCache = fs6.existsSync(path.join(cacheDir(root), "parse-cache.json"));
607
608
  let stale = null;
608
609
  let staleExact = false;
609
610
  if (graph) {
@@ -716,6 +717,20 @@ function registerAsk(program) {
716
717
  result = queryGraph(graph, q, { budget });
717
718
  if (global.local) note = "semantic skipped under --local; used lexical";
718
719
  }
720
+ if (global.client) {
721
+ const files = new Set(result.matches.map((m) => m.node.file).filter(Boolean));
722
+ recordCliCall(
723
+ root,
724
+ {
725
+ tool: CLI_TOOL_ALIASES.ask,
726
+ client: global.client,
727
+ outcome: result.matches.length === 0 ? "miss" : "complete",
728
+ vgTokens: result.tokensEstimate,
729
+ baselineFiles: files.size
730
+ },
731
+ Date.now()
732
+ );
733
+ }
719
734
  if (global.json) {
720
735
  json({
721
736
  question: result.question,
@@ -845,12 +860,28 @@ function registerShow(program) {
845
860
  }
846
861
  throw ambiguityError(`"${name}" is ambiguous`, candidates);
847
862
  }
848
- const index = new GraphIndex(graph);
863
+ const index = indexFor(graph);
849
864
  const callees = dedupe(index.callees(node.id).map((x) => x.node));
850
865
  const callers = dedupe(index.callers(node.id).map((x) => x.node));
851
866
  const extendsEdges = index.out(node.id, "extends").concat(index.out(node.id, "implements"));
852
867
  const supertypes = extendsEdges.map((e) => index.node(e.dst)?.qualifiedName).filter(Boolean);
853
868
  const area = graph.areas.find((a) => a.id === node.area);
869
+ if (global.client) {
870
+ const files = /* @__PURE__ */ new Set([node.file]);
871
+ for (const n of [...callees, ...callers]) if (n.file) files.add(n.file);
872
+ const shown = node.qualifiedName + (node.signature ?? "") + callees.map((n) => n.qualifiedName).join("") + callers.map((n) => n.qualifiedName).join("");
873
+ recordCliCall(
874
+ rootOf(global),
875
+ {
876
+ tool: CLI_TOOL_ALIASES.show,
877
+ client: global.client,
878
+ outcome: "complete",
879
+ vgTokens: countTokens(shown),
880
+ baselineFiles: files.size
881
+ },
882
+ Date.now()
883
+ );
884
+ }
854
885
  if (global.json) {
855
886
  json({
856
887
  id: node.id,
@@ -904,6 +935,13 @@ function registerPath(program) {
904
935
  const rb = resolveOne(graph, b, opts.pickB ? Number(opts.pickB) : void 0);
905
936
  if (!rb.node) throw ambiguityError(`"${b}" ${rb.candidates.length ? "is ambiguous" : "not found"}`, rb.candidates, "--pick-b");
906
937
  const result = shortestPath(graph, ra.node.id, rb.node.id);
938
+ if (global.client) {
939
+ recordCliCall(
940
+ rootOf(global),
941
+ { tool: CLI_TOOL_ALIASES.path, client: global.client, outcome: result ? "complete" : "miss" },
942
+ Date.now()
943
+ );
944
+ }
907
945
  if (!result) {
908
946
  throw new CliError(
909
947
  `no path between ${ra.node.qualifiedName} and ${rb.node.qualifiedName}`,
@@ -929,9 +967,16 @@ function registerTree(program) {
929
967
  const { graph } = requireGraph(global);
930
968
  const { node, candidates } = resolveOne(graph, name, opts.pick ? Number(opts.pick) : void 0);
931
969
  if (!node) throw ambiguityError(`"${name}" ${candidates.length ? "is ambiguous" : "not found"}`, candidates);
932
- const index = new GraphIndex(graph);
970
+ const index = indexFor(graph);
933
971
  const maxDepth = Math.max(1, Number(opts.depth) || 3);
934
972
  const direction = opts.callers ? "callers" : "callees";
973
+ if (global.client) {
974
+ recordCliCall(
975
+ rootOf(global),
976
+ { tool: CLI_TOOL_ALIASES.tree, client: global.client, outcome: "complete" },
977
+ Date.now()
978
+ );
979
+ }
935
980
  if (global.json) {
936
981
  json(toJsonTree(index, node, direction, maxDepth, /* @__PURE__ */ new Set()));
937
982
  return;
@@ -1091,6 +1136,13 @@ function registerImpact(program) {
1091
1136
  if (!node) throw ambiguityError(`"${name}" ${candidates.length ? "is ambiguous" : "not found"}`, candidates);
1092
1137
  const depth = Number(opts.depth) || 4;
1093
1138
  const result = impactOf(graph, node.id, { depth });
1139
+ if (global.client) {
1140
+ recordCliCall(
1141
+ rootOf(global),
1142
+ { tool: CLI_TOOL_ALIASES.impact, client: global.client, outcome: "complete" },
1143
+ Date.now()
1144
+ );
1145
+ }
1094
1146
  const withTests = opts.tests || opts.failOnUntested;
1095
1147
  const ti = withTests ? testsToRun(graph, node.id, depth) : void 0;
1096
1148
  const runner = ti ? detectRunner(rootOf(global), node.lang) : void 0;
@@ -1230,24 +1282,214 @@ function fmtRef(r) {
1230
1282
  function pad2(s, n) {
1231
1283
  return s.padStart(n, " ");
1232
1284
  }
1285
+ var SCHEMA_VERSION = "1";
1286
+ var OFFSET_FILE = "stats-share.json";
1287
+ var INSTALL_ID_DIR = path.join(os.homedir(), ".vibgrate");
1288
+ var INSTALL_ID_FILE = path.join(INSTALL_ID_DIR, "install-id");
1289
+ var SEND_TIMEOUT_MS = 4e3;
1290
+ function installId() {
1291
+ try {
1292
+ const existing = fs6.readFileSync(INSTALL_ID_FILE, "utf8").trim();
1293
+ if (isUuid(existing)) return existing;
1294
+ } catch {
1295
+ }
1296
+ const id = randomUuid();
1297
+ try {
1298
+ fs6.mkdirSync(INSTALL_ID_DIR, { recursive: true });
1299
+ fs6.writeFileSync(INSTALL_ID_FILE, id + "\n", { mode: 384 });
1300
+ } catch {
1301
+ }
1302
+ return id;
1303
+ }
1304
+ function isUuid(s) {
1305
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s);
1306
+ }
1307
+ function randomUuid() {
1308
+ return randomUUID();
1309
+ }
1310
+ function statsEndpoint() {
1311
+ const override = process.env.VIBGRATE_STATS_ENDPOINT;
1312
+ if (override && /^https?:\/\//.test(override)) return override;
1313
+ return `https://${resolveIngestHost()}/v1/ingest/cli-mcp-usage`;
1314
+ }
1315
+ function offsetPath(root) {
1316
+ return path.join(cacheDir(root), OFFSET_FILE);
1317
+ }
1318
+ function ledgerFilePath(root) {
1319
+ return path.join(cacheDir(root), "savings.jsonl");
1320
+ }
1321
+ function readOffset(root) {
1322
+ try {
1323
+ const raw = JSON.parse(fs6.readFileSync(offsetPath(root), "utf8"));
1324
+ return typeof raw.offset === "number" && raw.offset >= 0 ? raw.offset : 0;
1325
+ } catch {
1326
+ return 0;
1327
+ }
1328
+ }
1329
+ function writeOffset(root, offset) {
1330
+ try {
1331
+ fs6.mkdirSync(cacheDir(root), { recursive: true });
1332
+ fs6.writeFileSync(offsetPath(root), JSON.stringify({ offset }) + "\n");
1333
+ } catch {
1334
+ }
1335
+ }
1336
+ function buildBatch(root, fromOffset, id = installId()) {
1337
+ const file = ledgerFilePath(root);
1338
+ let size = 0;
1339
+ try {
1340
+ size = fs6.statSync(file).size;
1341
+ } catch {
1342
+ return { batch: null, newOffset: 0 };
1343
+ }
1344
+ const start = fromOffset > size ? 0 : fromOffset;
1345
+ if (start >= size) return { batch: null, newOffset: size };
1346
+ let text = "";
1347
+ try {
1348
+ const fd = fs6.openSync(file, "r");
1349
+ try {
1350
+ const buf = Buffer.alloc(size - start);
1351
+ fs6.readSync(fd, buf, 0, buf.length, start);
1352
+ text = buf.toString("utf8");
1353
+ } finally {
1354
+ fs6.closeSync(fd);
1355
+ }
1356
+ } catch {
1357
+ return { batch: null, newOffset: fromOffset };
1358
+ }
1359
+ const lastNl = text.lastIndexOf("\n");
1360
+ if (lastNl < 0) return { batch: null, newOffset: start };
1361
+ const consumed = start + lastNl + 1;
1362
+ const rows = /* @__PURE__ */ new Map();
1363
+ let total = 0;
1364
+ let minTs = Infinity;
1365
+ let maxTs = -Infinity;
1366
+ for (const line of text.slice(0, lastNl).split("\n")) {
1367
+ if (!line.trim()) continue;
1368
+ let e;
1369
+ try {
1370
+ e = JSON.parse(line);
1371
+ } catch {
1372
+ continue;
1373
+ }
1374
+ const source = e.source ?? "mcp";
1375
+ const client = e.client ?? "unknown";
1376
+ const outcome = e.outcome ?? "complete";
1377
+ const key = `${source}|${client}|${e.tool}|${outcome}`;
1378
+ const row = rows.get(key) ?? {
1379
+ source,
1380
+ client,
1381
+ tool: e.tool,
1382
+ outcome,
1383
+ calls: 0,
1384
+ vgTokens: 0,
1385
+ baselineTokens: 0
1386
+ };
1387
+ row.calls++;
1388
+ row.vgTokens += e.vgTokens ?? 0;
1389
+ row.baselineTokens += e.baselineTokens ?? 0;
1390
+ rows.set(key, row);
1391
+ total++;
1392
+ if (typeof e.ts === "number") {
1393
+ if (e.ts < minTs) minTs = e.ts;
1394
+ if (e.ts > maxTs) maxTs = e.ts;
1395
+ }
1396
+ }
1397
+ if (total === 0 || !Number.isFinite(minTs)) return { batch: null, newOffset: consumed };
1398
+ const batch = {
1399
+ schemaVersion: SCHEMA_VERSION,
1400
+ installId: id,
1401
+ vgVersion: VERSION,
1402
+ os: process.platform,
1403
+ arch: process.arch,
1404
+ windowStart: new Date(minTs).toISOString(),
1405
+ windowEnd: new Date(maxTs).toISOString(),
1406
+ totalCalls: total,
1407
+ rows: [...rows.values()]
1408
+ };
1409
+ return { batch, newOffset: consumed };
1410
+ }
1411
+ async function postBatch(batch) {
1412
+ const controller = new AbortController();
1413
+ const timer = setTimeout(() => controller.abort(), SEND_TIMEOUT_MS);
1414
+ timer.unref?.();
1415
+ try {
1416
+ const res = await fetch(statsEndpoint(), {
1417
+ method: "POST",
1418
+ headers: { "Content-Type": "application/json", Connection: "close" },
1419
+ body: JSON.stringify(batch),
1420
+ signal: controller.signal
1421
+ });
1422
+ return res.ok;
1423
+ } catch {
1424
+ return false;
1425
+ } finally {
1426
+ clearTimeout(timer);
1427
+ }
1428
+ }
1429
+ var StatsSharer = class {
1430
+ constructor(root) {
1431
+ this.root = root;
1432
+ this.offset = readOffset(root);
1433
+ }
1434
+ root;
1435
+ id = installId();
1436
+ offset;
1437
+ flushing = false;
1438
+ /**
1439
+ * Flush any new ledger entries. Single-flight (a slow POST won't overlap the
1440
+ * next tick). Best-effort: any failure is swallowed and retried next time.
1441
+ */
1442
+ async flush() {
1443
+ if (this.flushing) return;
1444
+ this.flushing = true;
1445
+ try {
1446
+ const { batch, newOffset } = buildBatch(this.root, this.offset, this.id);
1447
+ if (!batch) {
1448
+ if (newOffset > this.offset) {
1449
+ this.offset = newOffset;
1450
+ writeOffset(this.root, newOffset);
1451
+ }
1452
+ return;
1453
+ }
1454
+ const ok = await postBatch(batch);
1455
+ if (ok) {
1456
+ this.offset = newOffset;
1457
+ writeOffset(this.root, newOffset);
1458
+ }
1459
+ } catch {
1460
+ } finally {
1461
+ this.flushing = false;
1462
+ }
1463
+ }
1464
+ };
1465
+
1466
+ // src/commands/serve.ts
1467
+ var SHARE_FLUSH_INTERVAL_MS = 5 * 60 * 1e3;
1233
1468
  function registerServe(program) {
1234
- const cmd = program.command("serve").description("start Vibgrate AI Context \u2014 local-first MCP serving your code map, drift & version-correct docs to your AI").option("--http", "serve over streamable HTTP instead of stdio").option("--port <n>", "port for --http", "7437").option("--host <h>", "host for --http", "127.0.0.1").option("--savings", "record local, counts-only usage savings (opt-in; off by default)").option("--dedup", "collapse a node's heavy relation lists on repeat reads within a session (opt-in; saves tokens)").option("--no-refresh", "serve the map as built \u2014 skip the auto-rebuild when files change").action(async function(opts) {
1469
+ const cmd = program.command("serve").description("start Vibgrate AI Context \u2014 local-first MCP serving your code map, drift & version-correct docs to your AI").option("--http", "serve over streamable HTTP instead of stdio").option("--port <n>", "port for --http", "7437").option("--host <h>", "host for --http", "127.0.0.1").option("--savings", "record local, counts-only usage savings (opt-in; off by default)").option("--share-stats", "ALSO upload the counts-only usage ledger to Vibgrate to improve the local MCP (opt-in; off by default; implies --savings; disabled under --local)").option("--dedup", "collapse a node's heavy relation lists on repeat reads within a session (opt-in; saves tokens)").option("--no-refresh", "serve the map as built \u2014 skip the auto-rebuild when files change").action(async function(opts) {
1235
1470
  const global = readGlobal(this);
1236
1471
  const root = rootOf(global);
1237
1472
  const graphPath = global.graph ?? defaultGraphPath(root);
1238
1473
  const refresh = opts.refresh !== false && !global.graph;
1474
+ const local = global.local === true;
1475
+ const shareStats = opts.shareStats === true && !local;
1239
1476
  const serveOpts = {
1240
- savings: opts.savings === true,
1241
- local: global.local === true,
1477
+ savings: opts.savings === true || shareStats,
1478
+ shareStats,
1479
+ local,
1242
1480
  dedup: opts.dedup === true,
1243
1481
  refresh
1244
1482
  };
1245
- if (!fs5.existsSync(graphPath)) {
1483
+ if (!fs6.existsSync(graphPath)) {
1246
1484
  throw new CliError(
1247
1485
  `no map found at ${graphPath} \u2014 run \`vg\` to build one first`,
1248
1486
  ExitCode.NOT_FOUND
1249
1487
  );
1250
1488
  }
1489
+ if (opts.shareStats === true && local) {
1490
+ info(c.dim("vg \xB7 --share-stats ignored under --local (air-gapped): recording locally, not uploading."));
1491
+ }
1492
+ if (shareStats) startSharing(root);
1251
1493
  const freshness = refresh ? "auto-refresh" : "as built";
1252
1494
  if (opts.http) {
1253
1495
  await serveHttp(graphPath, opts.host ?? "127.0.0.1", Number(opts.port) || 7437, serveOpts, freshness);
@@ -1258,6 +1500,31 @@ function registerServe(program) {
1258
1500
  });
1259
1501
  applyGlobalOptions(cmd);
1260
1502
  }
1503
+ function startSharing(root) {
1504
+ const sharer = new StatsSharer(root);
1505
+ info(
1506
+ c.dim(
1507
+ `vg \xB7 sharing counts-only usage stats with Vibgrate (${statsEndpoint()}) to improve the local MCP. No code, paths, or questions are sent. Stop by omitting --share-stats.`
1508
+ )
1509
+ );
1510
+ const timer = setInterval(() => void sharer.flush(), SHARE_FLUSH_INTERVAL_MS);
1511
+ timer.unref?.();
1512
+ let flushed = false;
1513
+ const finalFlush = () => {
1514
+ if (flushed) return;
1515
+ flushed = true;
1516
+ void sharer.flush();
1517
+ };
1518
+ process.once("SIGINT", () => {
1519
+ finalFlush();
1520
+ process.exit(0);
1521
+ });
1522
+ process.once("SIGTERM", () => {
1523
+ finalFlush();
1524
+ process.exit(0);
1525
+ });
1526
+ process.once("beforeExit", finalFlush);
1527
+ }
1261
1528
  async function serveHttp(graphPath, host, port, opts, freshness) {
1262
1529
  const { createServer: createHttp } = await import('http');
1263
1530
  const { StreamableHTTPServerTransport } = await import('@modelcontextprotocol/sdk/server/streamableHttp.js');
@@ -1361,7 +1628,7 @@ function registerShare(program) {
1361
1628
  const global = readGlobal(this);
1362
1629
  const root = rootOf(global);
1363
1630
  const gitDir = path.join(root, ".git");
1364
- if (!fs5.existsSync(gitDir)) {
1631
+ if (!fs6.existsSync(gitDir)) {
1365
1632
  throw new CliError("not a git repository \u2014 run `vg share` inside a git repo", ExitCode.USAGE_ERROR);
1366
1633
  }
1367
1634
  if (opts.undo) {
@@ -1383,22 +1650,22 @@ function registerShare(program) {
1383
1650
  }
1384
1651
  function writeVibgrateGitignore(root, reports) {
1385
1652
  const dir = path.join(root, ".vibgrate");
1386
- fs5.mkdirSync(dir, { recursive: true });
1653
+ fs6.mkdirSync(dir, { recursive: true });
1387
1654
  const lines = ["# Managed by `vg share` \u2014 keep graph.json committed, ignore the rest", "cache/", "facts.jsonl"];
1388
1655
  if (!reports) lines.push("graph.html", "GRAPH_REPORT.md");
1389
- fs5.writeFileSync(path.join(dir, ".gitignore"), `${lines.join("\n")}
1656
+ fs6.writeFileSync(path.join(dir, ".gitignore"), `${lines.join("\n")}
1390
1657
  `);
1391
1658
  }
1392
1659
  function writeGitAttributes(root) {
1393
1660
  const file = path.join(root, ".gitattributes");
1394
1661
  const line = ".vibgrate/graph.json merge=vg";
1395
- let content = fs5.existsSync(file) ? fs5.readFileSync(file, "utf8") : "";
1662
+ let content = fs6.existsSync(file) ? fs6.readFileSync(file, "utf8") : "";
1396
1663
  if (!content.split("\n").some((l) => l.trim() === line)) {
1397
1664
  content = content.length ? `${content.replace(/\s*$/, "")}
1398
1665
  ${line}
1399
1666
  ` : `${line}
1400
1667
  `;
1401
- fs5.writeFileSync(file, content);
1668
+ fs6.writeFileSync(file, content);
1402
1669
  }
1403
1670
  }
1404
1671
  function configureMergeDriver(root) {
@@ -1407,7 +1674,7 @@ function configureMergeDriver(root) {
1407
1674
  }
1408
1675
  function installPreCommitHook(root) {
1409
1676
  const hooksDir = hookDir(root);
1410
- fs5.mkdirSync(hooksDir, { recursive: true });
1677
+ fs6.mkdirSync(hooksDir, { recursive: true });
1411
1678
  const file = path.join(hooksDir, "pre-commit");
1412
1679
  const block = [
1413
1680
  HOOK_BEGIN,
@@ -1417,7 +1684,7 @@ function installPreCommitHook(root) {
1417
1684
  "fi",
1418
1685
  HOOK_END
1419
1686
  ].join("\n");
1420
- let content = fs5.existsSync(file) ? fs5.readFileSync(file, "utf8") : "#!/bin/sh\n";
1687
+ let content = fs6.existsSync(file) ? fs6.readFileSync(file, "utf8") : "#!/bin/sh\n";
1421
1688
  const re = new RegExp(`${escapeRe(HOOK_BEGIN)}[\\s\\S]*?${escapeRe(HOOK_END)}`);
1422
1689
  if (re.test(content)) content = content.replace(re, block);
1423
1690
  else content = `${content.replace(/\s*$/, "")}
@@ -1426,21 +1693,21 @@ ${block}
1426
1693
  `;
1427
1694
  if (!content.startsWith("#!")) content = `#!/bin/sh
1428
1695
  ${content}`;
1429
- fs5.writeFileSync(file, content);
1430
- fs5.chmodSync(file, 493);
1696
+ fs6.writeFileSync(file, content);
1697
+ fs6.chmodSync(file, 493);
1431
1698
  }
1432
1699
  function undo(root) {
1433
1700
  const hookFile = path.join(hookDir(root), "pre-commit");
1434
- if (fs5.existsSync(hookFile)) {
1701
+ if (fs6.existsSync(hookFile)) {
1435
1702
  const re = new RegExp(`\\n*${escapeRe(HOOK_BEGIN)}[\\s\\S]*?${escapeRe(HOOK_END)}\\n*`);
1436
- const next2 = fs5.readFileSync(hookFile, "utf8").replace(re, "\n");
1437
- if (next2.trim() === "#!/bin/sh") fs5.rmSync(hookFile);
1438
- else fs5.writeFileSync(hookFile, next2);
1703
+ const next2 = fs6.readFileSync(hookFile, "utf8").replace(re, "\n");
1704
+ if (next2.trim() === "#!/bin/sh") fs6.rmSync(hookFile);
1705
+ else fs6.writeFileSync(hookFile, next2);
1439
1706
  }
1440
1707
  const attr = path.join(root, ".gitattributes");
1441
- if (fs5.existsSync(attr)) {
1442
- const next2 = fs5.readFileSync(attr, "utf8").split("\n").filter((l) => l.trim() !== ".vibgrate/graph.json merge=vg").join("\n");
1443
- fs5.writeFileSync(attr, next2);
1708
+ if (fs6.existsSync(attr)) {
1709
+ const next2 = fs6.readFileSync(attr, "utf8").split("\n").filter((l) => l.trim() !== ".vibgrate/graph.json merge=vg").join("\n");
1710
+ fs6.writeFileSync(attr, next2);
1444
1711
  }
1445
1712
  try {
1446
1713
  git(root, ["config", "--remove-section", "merge.vg"]);
@@ -1573,7 +1840,7 @@ function grepBaselineTokens(term, files) {
1573
1840
  if (f.size > 512 * 1024) continue;
1574
1841
  let content = "";
1575
1842
  try {
1576
- content = fs5.readFileSync(f.abs, "utf8");
1843
+ content = fs6.readFileSync(f.abs, "utf8");
1577
1844
  } catch {
1578
1845
  continue;
1579
1846
  }
@@ -1583,7 +1850,7 @@ function grepBaselineTokens(term, files) {
1583
1850
  }
1584
1851
  function safeSize(abs) {
1585
1852
  try {
1586
- return fs5.statSync(abs).size;
1853
+ return fs6.statSync(abs).size;
1587
1854
  } catch {
1588
1855
  return 0;
1589
1856
  }
@@ -1917,7 +2184,7 @@ function loadStandards(root) {
1917
2184
  const p = path__default.join(root, rel3);
1918
2185
  let text;
1919
2186
  try {
1920
- text = fs5__default.readFileSync(p, "utf8");
2187
+ text = fs6__default.readFileSync(p, "utf8");
1921
2188
  } catch {
1922
2189
  continue;
1923
2190
  }
@@ -2046,7 +2313,7 @@ function registerSavings(program) {
2046
2313
  if (usage.totals.calls === 0) {
2047
2314
  info(
2048
2315
  c.dim(
2049
- report.enabled ? " no calls recorded yet in this window" : " recording is off. Enable with `vg serve --savings`, then query via MCP."
2316
+ report.enabled ? " no calls recorded yet in this window" : " recording is off. Enable it for MCP with `vg serve --savings`, and for CLI calls by passing `--client=<ai>` to vg."
2050
2317
  )
2051
2318
  );
2052
2319
  return;
@@ -2057,6 +2324,7 @@ function registerSavings(program) {
2057
2324
  info(c.dim(" estimates with stated assumptions (~4 chars/token; ~400 tokens/file baseline); scales with repo size."));
2058
2325
  }
2059
2326
  printBreakdown(usage);
2327
+ printSplit(usage);
2060
2328
  });
2061
2329
  applyGlobalOptions(cmd);
2062
2330
  }
@@ -2083,6 +2351,24 @@ function printBreakdown(usage) {
2083
2351
  );
2084
2352
  info(c.dim(` avg success across commands: ${usage.avgSuccessPct}%`));
2085
2353
  }
2354
+ function printSplit(usage) {
2355
+ const label = (key) => key === "mcp" ? "MCP tools" : key === "cli" ? "vg CLI" : key;
2356
+ if (usage.sources.length) {
2357
+ info("");
2358
+ info(c.bold(" by source") + c.dim(" (how the call arrived)"));
2359
+ for (const s of usage.sources) {
2360
+ const pct = usage.totals.calls ? Math.round(s.calls / usage.totals.calls * 100) : 0;
2361
+ info(` ${label(s.key).padEnd(12)} ${String(s.calls).padStart(6)} calls ${c.dim(`(${pct}%)`)}`);
2362
+ }
2363
+ }
2364
+ if (usage.clients.length) {
2365
+ info("");
2366
+ info(c.bold(" by client") + c.dim(" (which AI is calling; pass --client to vg to attribute CLI calls)"));
2367
+ for (const cl of usage.clients) {
2368
+ info(` ${cl.key.padEnd(12)} ${String(cl.calls).padStart(6)} calls`);
2369
+ }
2370
+ }
2371
+ }
2086
2372
  function registerLib(program) {
2087
2373
  const cmd = program.command("lib").description("library currency: version-correct, drift-annotated usage docs").argument("[args...]", "a library name, or: add <source> | publish <name> | resolve <name> | refresh").option("--name <name>", "library name for `add`").option("--version <v>", "pin the doc version for `add`/`publish`").option("--online", "(deprecated; network is on by default) allow network for add/refresh URL sources").option("-b, --budget <n>", "trim docs to ~N tokens").option("--readme <path>", "README path for `publish` (default ./README.md)").option("--dts <path>", "TypeScript declaration (.d.ts) path for `publish`").option("--language <lang>", "primary language for `publish`").option("--region <region>", "data-residency region for the hosted catalog (same as scans; default us)").option("--ingest <url>", "hosted catalog/ingest URL override (host extracted; wins over --region)").action(async function(args, opts) {
2088
2374
  const global = readGlobal(this);
@@ -2136,7 +2422,7 @@ async function addCmd(root, rest, opts, asJson) {
2136
2422
  }
2137
2423
  function readPackageVersion(root) {
2138
2424
  try {
2139
- const pkg = JSON.parse(fs5__default.readFileSync(path__default.join(root, "package.json"), "utf8"));
2425
+ const pkg = JSON.parse(fs6__default.readFileSync(path__default.join(root, "package.json"), "utf8"));
2140
2426
  return typeof pkg.version === "string" ? pkg.version : void 0;
2141
2427
  } catch {
2142
2428
  return void 0;
@@ -2144,7 +2430,7 @@ function readPackageVersion(root) {
2144
2430
  }
2145
2431
  function readOptionalFile(p) {
2146
2432
  try {
2147
- const t = fs5__default.readFileSync(p, "utf8");
2433
+ const t = fs6__default.readFileSync(p, "utf8");
2148
2434
  return t.trim() ? t : void 0;
2149
2435
  } catch {
2150
2436
  return void 0;
@@ -2289,8 +2575,8 @@ function registerExport(program) {
2289
2575
  out(content.trimEnd());
2290
2576
  return;
2291
2577
  }
2292
- fs5.mkdirSync(path.dirname(path.resolve(file)), { recursive: true });
2293
- fs5.writeFileSync(file, content);
2578
+ fs6.mkdirSync(path.dirname(path.resolve(file)), { recursive: true });
2579
+ fs6.writeFileSync(file, content);
2294
2580
  info(`${c.green("\u2714")} wrote ${file} ${c.dim(`(${format})`)}`);
2295
2581
  });
2296
2582
  applyGlobalOptions(cmd);
@@ -2313,24 +2599,24 @@ function registerBundle(program) {
2313
2599
  } catch {
2314
2600
  throw new CliError("no grammars found to bundle (run a build first)", ExitCode.ERROR);
2315
2601
  }
2316
- fs5.mkdirSync(path.join(outDir, "grammars"), { recursive: true });
2602
+ fs6.mkdirSync(path.join(outDir, "grammars"), { recursive: true });
2317
2603
  let grammarCount = 0;
2318
2604
  for (const { fileName, absPath } of grammarFiles) {
2319
- fs5.copyFileSync(absPath, path.join(outDir, "grammars", fileName));
2605
+ fs6.copyFileSync(absPath, path.join(outDir, "grammars", fileName));
2320
2606
  grammarCount++;
2321
2607
  }
2322
2608
  const included = [`grammars/ (${grammarCount} .wasm)`];
2323
2609
  const graphSrc = global.graph ?? defaultGraphPath(root);
2324
- if (fs5.existsSync(graphSrc)) {
2325
- fs5.copyFileSync(graphSrc, path.join(outDir, "graph.json"));
2610
+ if (fs6.existsSync(graphSrc)) {
2611
+ fs6.copyFileSync(graphSrc, path.join(outDir, "graph.json"));
2326
2612
  included.push("graph.json");
2327
2613
  }
2328
2614
  const cat = catalogPath(root);
2329
- if (fs5.existsSync(cat)) {
2330
- fs5.copyFileSync(cat, path.join(outDir, "vibgrate.lib.json"));
2615
+ if (fs6.existsSync(cat)) {
2616
+ fs6.copyFileSync(cat, path.join(outDir, "vibgrate.lib.json"));
2331
2617
  included.push("vibgrate.lib.json");
2332
- if (fs5.existsSync(libDir(root))) {
2333
- fs5.cpSync(libDir(root), path.join(outDir, "lib"), { recursive: true });
2618
+ if (fs6.existsSync(libDir(root))) {
2619
+ fs6.cpSync(libDir(root), path.join(outDir, "lib"), { recursive: true });
2334
2620
  included.push("lib/");
2335
2621
  }
2336
2622
  }
@@ -2342,7 +2628,7 @@ function registerBundle(program) {
2342
2628
  included,
2343
2629
  usage: "Run `vg build --grammars <this dir>/grammars` to build fully offline."
2344
2630
  };
2345
- fs5.writeFileSync(path.join(outDir, "MANIFEST.json"), `${stableStringify(manifest, 2)}
2631
+ fs6.writeFileSync(path.join(outDir, "MANIFEST.json"), `${stableStringify(manifest, 2)}
2346
2632
  `);
2347
2633
  if (global.json) {
2348
2634
  json({ out: path.relative(root, outDir), ...manifest });
@@ -2358,22 +2644,22 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
2358
2644
  const rootDir = path.resolve(targetPath);
2359
2645
  const vibgrateDir2 = path.join(rootDir, ".vibgrate");
2360
2646
  await ensureDir(vibgrateDir2);
2361
- console.log(chalk7.green("\u2714") + ` Created ${chalk7.bold(".vibgrate/")} directory`);
2647
+ console.log(chalk8.green("\u2714") + ` Created ${chalk8.bold(".vibgrate/")} directory`);
2362
2648
  const configPath = path.join(rootDir, "vibgrate.config.ts");
2363
2649
  if (await pathExists(configPath)) {
2364
- console.log(chalk7.dim(" vibgrate.config.ts already exists, skipping"));
2650
+ console.log(chalk8.dim(" vibgrate.config.ts already exists, skipping"));
2365
2651
  } else {
2366
2652
  await writeDefaultConfig(rootDir);
2367
- console.log(chalk7.green("\u2714") + ` Created ${chalk7.bold("vibgrate.config.ts")}`);
2653
+ console.log(chalk8.green("\u2714") + ` Created ${chalk8.bold("vibgrate.config.ts")}`);
2368
2654
  }
2369
2655
  if (opts.baseline) {
2370
- const { runBaseline } = await import('./baseline-AMQ7VEB5.js');
2656
+ const { runBaseline } = await import('./baseline-7AFTGG3I.js');
2371
2657
  await runBaseline(rootDir);
2372
2658
  }
2373
2659
  console.log("");
2374
- console.log(chalk7.bold("Next steps:"));
2375
- console.log(` ${chalk7.cyan("vibgrate scan")} Scan for upgrade drift`);
2376
- console.log(` ${chalk7.cyan("vibgrate baseline")} Create a drift baseline`);
2660
+ console.log(chalk8.bold("Next steps:"));
2661
+ console.log(` ${chalk8.cyan("vibgrate scan")} Scan for upgrade drift`);
2662
+ console.log(` ${chalk8.cyan("vibgrate baseline")} Create a drift baseline`);
2377
2663
  console.log("");
2378
2664
  });
2379
2665
 
@@ -2408,22 +2694,22 @@ async function detectAiAssistant(rootDir) {
2408
2694
  return null;
2409
2695
  }
2410
2696
  function printAiContextPrompt(detectedAssistant) {
2411
- const teal2 = chalk7.hex("#3FB0A4");
2412
- const mint2 = chalk7.hex("#4FE3C1");
2697
+ const teal2 = chalk8.hex("#3FB0A4");
2698
+ const mint2 = chalk8.hex("#4FE3C1");
2413
2699
  const cli = resolveCliInvocation();
2414
2700
  const installArgs = detectedAssistant ? `install ${detectedAssistant}` : "install --all";
2415
2701
  const installCmd = `${cli} ${installArgs}`;
2416
- const detectedNote = detectedAssistant ? chalk7.dim(` (${detectedAssistant} config detected)`) : "";
2702
+ const detectedNote = detectedAssistant ? chalk8.dim(` (${detectedAssistant} config detected)`) : "";
2417
2703
  console.log("");
2418
2704
  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"));
2419
- console.log(teal2(" \u2502") + " " + mint2("\u25C6") + " " + chalk7.bold.white("Get AI-aware answers in your editor") + " " + teal2("\u2502"));
2705
+ console.log(teal2(" \u2502") + " " + mint2("\u25C6") + " " + chalk8.bold.white("Get AI-aware answers in your editor") + " " + teal2("\u2502"));
2420
2706
  console.log(teal2(" \u2570\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\u256F"));
2421
2707
  console.log("");
2422
- console.log(" " + chalk7.bold.white(installCmd) + detectedNote);
2708
+ console.log(" " + chalk8.bold.white(installCmd) + detectedNote);
2423
2709
  console.log("");
2424
- console.log(" " + teal2("\xB7") + " " + chalk7.white("Code map in your assistant") + chalk7.dim(" \u2014 call trees, impact surfaces, import paths"));
2425
- console.log(" " + teal2("\xB7") + " " + chalk7.white("Offline drift") + chalk7.dim(" \u2014 DriftScore and upgrade priorities, right inside your editor"));
2426
- console.log(" " + teal2("\xB7") + " " + chalk7.white("Version-correct library docs") + chalk7.dim(" \u2014 pinned to your lockfile, no hallucinated APIs"));
2710
+ console.log(" " + teal2("\xB7") + " " + chalk8.white("Code map in your assistant") + chalk8.dim(" \u2014 call trees, impact surfaces, import paths"));
2711
+ console.log(" " + teal2("\xB7") + " " + chalk8.white("Offline drift") + chalk8.dim(" \u2014 DriftScore and upgrade priorities, right inside your editor"));
2712
+ console.log(" " + teal2("\xB7") + " " + chalk8.white("Version-correct library docs") + chalk8.dim(" \u2014 pinned to your lockfile, no hallucinated APIs"));
2427
2713
  console.log("");
2428
2714
  }
2429
2715
 
@@ -2434,14 +2720,14 @@ function shouldBuildCodeMap(opts) {
2434
2720
  async function autoPush(artifact, rootDir, opts) {
2435
2721
  const dsn = resolveDsn(opts.dsn);
2436
2722
  if (!dsn) {
2437
- console.error(chalk7.red("No DSN provided for push."));
2438
- console.error(chalk7.dim('Run "vibgrate login", set VIBGRATE_DSN, or use the --dsn flag.'));
2723
+ console.error(chalk8.red("No DSN provided for push."));
2724
+ console.error(chalk8.dim('Run "vibgrate login", set VIBGRATE_DSN, or use the --dsn flag.'));
2439
2725
  if (opts.strict) process.exit(1);
2440
2726
  return;
2441
2727
  }
2442
2728
  const parsed = parseDsn(dsn);
2443
2729
  if (!parsed) {
2444
- console.error(chalk7.red("Invalid DSN format."));
2730
+ console.error(chalk8.red("Invalid DSN format."));
2445
2731
  if (opts.strict) process.exit(1);
2446
2732
  return;
2447
2733
  }
@@ -2452,7 +2738,7 @@ async function autoPush(artifact, rootDir, opts) {
2452
2738
  try {
2453
2739
  host = resolveIngestHost(opts.region);
2454
2740
  } catch (e) {
2455
- console.error(chalk7.red(e instanceof Error ? e.message : String(e)));
2741
+ console.error(chalk8.red(e instanceof Error ? e.message : String(e)));
2456
2742
  if (opts.strict) process.exit(1);
2457
2743
  return;
2458
2744
  }
@@ -2460,7 +2746,7 @@ async function autoPush(artifact, rootDir, opts) {
2460
2746
  const originalSize = JSON.stringify(artifact).length;
2461
2747
  const compressedSize = body.length;
2462
2748
  const ratio = ((1 - compressedSize / originalSize) * 100).toFixed(0);
2463
- console.log(chalk7.dim(`Uploading to ${host}... (${(compressedSize / 1024).toFixed(0)} KB, ${ratio}% smaller)`));
2749
+ console.log(chalk8.dim(`Uploading to ${host}... (${(compressedSize / 1024).toFixed(0)} KB, ${ratio}% smaller)`));
2464
2750
  try {
2465
2751
  const { response, host: uploadedHost } = await uploadScanArtifact({
2466
2752
  scheme: parsed.scheme,
@@ -2480,36 +2766,36 @@ async function autoPush(artifact, rootDir, opts) {
2480
2766
  const result = await response.json();
2481
2767
  if (result.unchanged) {
2482
2768
  console.log(
2483
- chalk7.green("\u2714") + ` Repository unchanged since ${result.lastScannedAt ?? "last scan"} \u2014 skipped upload (no credit used).`
2769
+ chalk8.green("\u2714") + ` Repository unchanged since ${result.lastScannedAt ?? "last scan"} \u2014 skipped upload (no credit used).`
2484
2770
  );
2485
2771
  if (result.previousIngestId) {
2486
2772
  emitIngestIdLine(result.previousIngestId, { unchanged: true });
2487
2773
  const dashUrl = `https://${dashHostForIngestHost(host)}/${parsed.workspaceId}/scan/${result.previousIngestId}`;
2488
- console.log(chalk7.dim(` Previous report: ${dashUrl}`));
2774
+ console.log(chalk8.dim(` Previous report: ${dashUrl}`));
2489
2775
  } else if (opts.strict) {
2490
- console.error(chalk7.red("Repository unchanged but no previous ingest id returned."));
2776
+ console.error(chalk8.red("Repository unchanged but no previous ingest id returned."));
2491
2777
  process.exit(1);
2492
2778
  }
2493
2779
  return;
2494
2780
  }
2495
- console.log(chalk7.green("\u2714") + ` Scan queued for processing (${result.ingestId ?? "ok"})`);
2781
+ console.log(chalk8.green("\u2714") + ` Scan queued for processing (${result.ingestId ?? "ok"})`);
2496
2782
  if (result.ingestId) {
2497
2783
  emitIngestIdLine(result.ingestId);
2498
2784
  const dashUrl = `https://${dashHostForIngestHost(host)}/${parsed.workspaceId}/scan/${result.ingestId}`;
2499
2785
  const CLEAR_LINE = process.platform === "win32" ? "\x1B[0G\x1B[2K" : "";
2500
2786
  console.log("");
2501
- console.log(CLEAR_LINE + chalk7.dim(" Processing continues in the background. Results available shortly."));
2787
+ console.log(CLEAR_LINE + chalk8.dim(" Processing continues in the background. Results available shortly."));
2502
2788
  console.log("");
2503
- console.log(CLEAR_LINE + chalk7.cyan("\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\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"));
2504
- console.log(CLEAR_LINE + chalk7.bold(" \u{1F4CA} View Scan Report"));
2505
- console.log(CLEAR_LINE + " " + chalk7.underline.cyan(dashUrl));
2506
- console.log(CLEAR_LINE + chalk7.cyan("\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\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"));
2789
+ console.log(CLEAR_LINE + chalk8.cyan("\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\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"));
2790
+ console.log(CLEAR_LINE + chalk8.bold(" \u{1F4CA} View Scan Report"));
2791
+ console.log(CLEAR_LINE + " " + chalk8.underline.cyan(dashUrl));
2792
+ console.log(CLEAR_LINE + chalk8.cyan("\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\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"));
2507
2793
  console.log("");
2508
2794
  console.log("");
2509
2795
  }
2510
2796
  } catch (e) {
2511
2797
  const msg = e instanceof Error ? e.message : String(e);
2512
- console.error(chalk7.red(`Upload failed: ${msg}`));
2798
+ console.error(chalk8.red(`Upload failed: ${msg}`));
2513
2799
  if (opts.strict) process.exit(1);
2514
2800
  }
2515
2801
  }
@@ -2521,15 +2807,15 @@ function reportStandards(rootDir) {
2521
2807
  if (!loaded.policy) return;
2522
2808
  const violations = checkStandards(loaded.policy, inventory(rootDir).records);
2523
2809
  if (violations.length === 0) {
2524
- console.log(chalk7.green("\u2714") + " Standards: no banned dependencies in use");
2810
+ console.log(chalk8.green("\u2714") + " Standards: no banned dependencies in use");
2525
2811
  return;
2526
2812
  }
2527
- console.log(chalk7.red(`
2813
+ console.log(chalk8.red(`
2528
2814
  \u26A0 Standards: ${violations.length} banned dependency(ies) in use`));
2529
2815
  for (const v of violations) {
2530
- const fix = v.use ? chalk7.dim(` \u2192 use ${v.use}`) : "";
2531
- const why = v.reason ? chalk7.dim(` (${v.reason})`) : "";
2532
- console.log(` ${chalk7.red("banned")} ${v.ecosystem}:${v.name}${v.installed ? chalk7.dim(` ${v.installed}`) : ""}${fix}${why}`);
2816
+ const fix = v.use ? chalk8.dim(` \u2192 use ${v.use}`) : "";
2817
+ const why = v.reason ? chalk8.dim(` (${v.reason})`) : "";
2818
+ console.log(` ${chalk8.red("banned")} ${v.ecosystem}:${v.name}${v.installed ? chalk8.dim(` ${v.installed}`) : ""}${fix}${why}`);
2533
2819
  }
2534
2820
  }
2535
2821
  function parseNonNegativeNumber(value, label) {
@@ -2548,7 +2834,7 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
2548
2834
  ).option("--concurrency <n>", "Max concurrent npm calls", "8").option("--push", "Auto-push results to Vibgrate API after scan").option("--dsn <dsn>", "DSN token for push (or use VIBGRATE_DSN env)").option("--region <region>", "Override data residency region for push (us, eu)").option("--strict", "Fail on push errors").option("--ui-purpose", "Enable optional UI purpose evidence extraction (slower)").option("--no-local-artifacts", "Do not write .vibgrate JSON artifacts to disk").option("--max-privacy", "Enable strongest privacy mode (minimal scanners, no local artifacts)").option("--offline", "Run without network calls; do not upload results").option("--full", "Comprehensive scan: turns on known-vulnerability detection (= --vulns) and, when a standards policy exists, a banned-dependency report \u2014 on top of drift scoring and the code map").option("--vulns", "Also scan installed dependencies for known vulnerabilities (OSV online, or advisories from --package-manifest when offline)").option("--package-manifest <file>", "Use local package-version manifest JSON/ZIP (for offline mode)").option("--project-scan-timeout <seconds>", "Per-project scan timeout in seconds (default: 180)").option("--drift-budget <score>", "Fail if drift score is above budget (0-100)").option("--drift-worsening <percent>", "Fail if drift worsens by more than % since baseline").option("--repository-name <name>", "Override the repository name recorded for this scan (defaults to the directory or package.json name)").option("--force", "Always create a fresh scan ingest, even if the repository is unchanged since the last scan (skips the unchanged/reuse optimization). Used by scheduled and dashboard-triggered scans.").option("--no-graph", "Skip building the local code map (the AI/docs index) that scan produces after scoring drift").option("--quiet", "Suppress promotional output (the free-plan tracking panel and the AI Context install prompt); scan results are unaffected").action(async (targetPath, opts) => {
2549
2835
  const rootDir = path.resolve(targetPath);
2550
2836
  if (!await pathExists$1(rootDir)) {
2551
- console.error(chalk7.red(`Path does not exist: ${rootDir}`));
2837
+ console.error(chalk8.red(`Path does not exist: ${rootDir}`));
2552
2838
  process.exit(1);
2553
2839
  }
2554
2840
  const hasDsn = !!resolveDsn(opts.dsn);
@@ -2575,9 +2861,9 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
2575
2861
  planTier = preflight.plan?.tier;
2576
2862
  upgradeUrl = preflight.upgradeUrl ?? `https://${dashHostForIngestHost(preflight.ingestHost ?? ingestHost)}/${parsed.workspaceId}`;
2577
2863
  if (preflight.vm && !preflight.vm.allowed) {
2578
- console.error(chalk7.red(preflight.error ?? "VM meter usage exhausted"));
2864
+ console.error(chalk8.red(preflight.error ?? "VM meter usage exhausted"));
2579
2865
  console.error(
2580
- chalk7.dim(
2866
+ chalk8.dim(
2581
2867
  `VM minutes: ${preflight.vm.used}/${preflight.vm.limit} (${preflight.plan.label} plan) \u2014 enable overages or upgrade your plan.`
2582
2868
  )
2583
2869
  );
@@ -2585,25 +2871,25 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
2585
2871
  }
2586
2872
  if (preflight.repositories && !preflight.repositories.allowed) {
2587
2873
  console.error(
2588
- chalk7.red(
2874
+ chalk8.red(
2589
2875
  preflight.error ?? `Repository limit reached for the ${preflight.plan.label} plan \u2014 cannot scan a new repository.`
2590
2876
  )
2591
2877
  );
2592
2878
  const max = preflight.repositories.max < 0 ? "unlimited" : String(preflight.repositories.max);
2593
2879
  console.error(
2594
- chalk7.dim(
2880
+ chalk8.dim(
2595
2881
  `Repositories: ${preflight.repositories.total}/${max} (${preflight.plan.label} plan) \u2014 archive a repository or upgrade your plan.`
2596
2882
  )
2597
2883
  );
2598
2884
  if (preflight.upgradeUrl) {
2599
- console.error(chalk7.dim(` Upgrade: ${preflight.upgradeUrl}`));
2885
+ console.error(chalk8.dim(` Upgrade: ${preflight.upgradeUrl}`));
2600
2886
  }
2601
2887
  process.exit(1);
2602
2888
  }
2603
2889
  if (preflight.status === "error" || !preflight.scans.allowed) {
2604
- console.error(chalk7.red(preflight.error ?? "Scan ingestion not allowed for this workspace."));
2890
+ console.error(chalk8.red(preflight.error ?? "Scan ingestion not allowed for this workspace."));
2605
2891
  console.error(
2606
- chalk7.dim(
2892
+ chalk8.dim(
2607
2893
  `Credits: ${preflight.scans.used}/${preflight.scans.limit} (${preflight.plan.label} plan)`
2608
2894
  )
2609
2895
  );
@@ -2611,27 +2897,27 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
2611
2897
  process.exit(1);
2612
2898
  }
2613
2899
  console.log(
2614
- chalk7.dim(
2900
+ chalk8.dim(
2615
2901
  `Plan: ${preflight.plan.label} \u2014 scan credits ${preflight.scans.used}/${preflight.scans.limit} this month`
2616
2902
  )
2617
2903
  );
2618
2904
  if (preflight.repository?.unchanged && !opts.force) {
2619
2905
  console.log(
2620
- chalk7.green("\u2714") + ` Repository unchanged at ${preflight.repository.lastVcsSha?.slice(0, 7) ?? "same revision"} \u2014 skipping scan.`
2906
+ chalk8.green("\u2714") + ` Repository unchanged at ${preflight.repository.lastVcsSha?.slice(0, 7) ?? "same revision"} \u2014 skipping scan.`
2621
2907
  );
2622
2908
  if (preflight.repository.lastIngestId) {
2623
2909
  emitIngestIdLine(preflight.repository.lastIngestId, { unchanged: true });
2624
2910
  const dashUrl = `https://${dashHostForIngestHost(ingestHost)}/${parsed.workspaceId}/scan/${preflight.repository.lastIngestId}`;
2625
- console.log(chalk7.dim(` Latest report: ${dashUrl}`));
2911
+ console.log(chalk8.dim(` Latest report: ${dashUrl}`));
2626
2912
  } else if (opts.strict) {
2627
- console.error(chalk7.red("Repository unchanged but no previous ingest id available."));
2913
+ console.error(chalk8.red("Repository unchanged but no previous ingest id available."));
2628
2914
  process.exit(1);
2629
2915
  }
2630
2916
  return;
2631
2917
  }
2632
2918
  } catch (e) {
2633
2919
  const msg = e instanceof Error ? e.message : String(e);
2634
- console.error(chalk7.yellow(`Preflight check failed: ${msg}`));
2920
+ console.error(chalk8.yellow(`Preflight check failed: ${msg}`));
2635
2921
  if (opts.strict) process.exit(1);
2636
2922
  }
2637
2923
  }
@@ -2705,24 +2991,24 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
2705
2991
  const hasErrors = artifact.findings.some((f) => f.level === "error");
2706
2992
  const hasWarnings = artifact.findings.some((f) => f.level === "warning");
2707
2993
  if (opts.failOn === "error" && hasErrors) {
2708
- console.error(chalk7.red(`
2994
+ console.error(chalk8.red(`
2709
2995
  Failing: ${artifact.findings.filter((f) => f.level === "error").length} error finding(s) detected.`));
2710
2996
  process.exit(2);
2711
2997
  }
2712
2998
  if (opts.failOn === "warn" && (hasErrors || hasWarnings)) {
2713
- console.error(chalk7.red(`
2999
+ console.error(chalk8.red(`
2714
3000
  Failing: findings detected at warn level or above.`));
2715
3001
  process.exit(2);
2716
3002
  }
2717
3003
  }
2718
3004
  if (scanOpts.driftBudget !== void 0 && artifact.drift.score > scanOpts.driftBudget) {
2719
- console.error(chalk7.red(`
3005
+ console.error(chalk8.red(`
2720
3006
  Failing fitness function: drift score ${artifact.drift.score}/100 exceeds budget ${scanOpts.driftBudget}.`));
2721
3007
  process.exit(2);
2722
3008
  }
2723
3009
  if (scanOpts.driftWorseningPercent !== void 0) {
2724
3010
  if (artifact.delta === void 0) {
2725
- console.error(chalk7.red("\nFailing fitness function: --drift-worsening requires --baseline to compare against previous drift."));
3011
+ console.error(chalk8.red("\nFailing fitness function: --drift-worsening requires --baseline to compare against previous drift."));
2726
3012
  process.exit(2);
2727
3013
  }
2728
3014
  if (artifact.delta > 0) {
@@ -2730,7 +3016,7 @@ Failing fitness function: drift score ${artifact.drift.score}/100 exceeds budget
2730
3016
  const denominator = Math.max(Math.abs(baselineScore), 1e-4);
2731
3017
  const worseningPercent = artifact.delta / denominator * 100;
2732
3018
  if (worseningPercent > scanOpts.driftWorseningPercent) {
2733
- console.error(chalk7.red(`
3019
+ console.error(chalk8.red(`
2734
3020
  Failing fitness function: drift worsened by ${worseningPercent.toFixed(2)}% (threshold ${scanOpts.driftWorseningPercent}%).`));
2735
3021
  process.exit(2);
2736
3022
  }
@@ -2808,7 +3094,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
2808
3094
  function scanStaleness(rootDir, artifactPath) {
2809
3095
  let ref;
2810
3096
  try {
2811
- ref = fs5.statSync(artifactPath).mtimeMs;
3097
+ ref = fs6.statSync(artifactPath).mtimeMs;
2812
3098
  } catch {
2813
3099
  return { stale: false };
2814
3100
  }
@@ -2820,7 +3106,7 @@ function scanStaleness(rootDir, artifactPath) {
2820
3106
  if (depth > 8 || scanned > MAX_ENTRIES) return;
2821
3107
  let entries;
2822
3108
  try {
2823
- entries = fs5.readdirSync(dir, { withFileTypes: true });
3109
+ entries = fs6.readdirSync(dir, { withFileTypes: true });
2824
3110
  } catch {
2825
3111
  return;
2826
3112
  }
@@ -2835,7 +3121,7 @@ function scanStaleness(rootDir, artifactPath) {
2835
3121
  const abs = path.join(dir, e.name);
2836
3122
  let mtimeMs;
2837
3123
  try {
2838
- mtimeMs = fs5.statSync(abs).mtimeMs;
3124
+ mtimeMs = fs6.statSync(abs).mtimeMs;
2839
3125
  } catch {
2840
3126
  continue;
2841
3127
  }
@@ -2969,7 +3255,7 @@ function analyzeTree(root, packages) {
2969
3255
  if (depth > MAX_DEPTH || filesSeen >= MAX_FILES) return;
2970
3256
  let entries;
2971
3257
  try {
2972
- entries = fs5.readdirSync(dir, { withFileTypes: true });
3258
+ entries = fs6.readdirSync(dir, { withFileTypes: true });
2973
3259
  } catch {
2974
3260
  return;
2975
3261
  }
@@ -2987,8 +3273,8 @@ function analyzeTree(root, packages) {
2987
3273
  const full = path.join(dir, entry.name);
2988
3274
  let content;
2989
3275
  try {
2990
- if (fs5.statSync(full).size > MAX_FILE_BYTES) continue;
2991
- content = fs5.readFileSync(full, "utf8");
3276
+ if (fs6.statSync(full).size > MAX_FILE_BYTES) continue;
3277
+ content = fs6.readFileSync(full, "utf8");
2992
3278
  } catch {
2993
3279
  continue;
2994
3280
  }
@@ -3061,48 +3347,48 @@ function deltaSummary(delta) {
3061
3347
  return parts.length ? parts.join(", ") : `${delta.total}`;
3062
3348
  }
3063
3349
  function riskLabel(score) {
3064
- if (score <= 15) return chalk7.green(`${score}/100 low`);
3065
- if (score <= 40) return chalk7.yellow(`${score}/100 moderate`);
3066
- return chalk7.red(`${score}/100 high`);
3350
+ if (score <= 15) return chalk8.green(`${score}/100 low`);
3351
+ if (score <= 40) return chalk8.yellow(`${score}/100 moderate`);
3352
+ return chalk8.red(`${score}/100 high`);
3067
3353
  }
3068
3354
  function planBlock(plan, recommended) {
3069
3355
  const lines = [];
3070
- const marker = recommended ? chalk7.green("\u25CF recommended") : chalk7.dim("\u25CB");
3071
- lines.push(`${marker} ${chalk7.bold(plan.label)} \u2014 ${chalk7.dim(plan.description)}`);
3356
+ const marker = recommended ? chalk8.green("\u25CF recommended") : chalk8.dim("\u25CB");
3357
+ lines.push(`${marker} ${chalk8.bold(plan.label)} \u2014 ${chalk8.dim(plan.description)}`);
3072
3358
  const drift = typeof plan.expectedDriftScore === "number" && typeof plan.driftDelta === "number" ? ` \xB7 DriftScore \u2192${plan.expectedDriftScore} (${plan.driftDelta <= 0 ? "" : "+"}${plan.driftDelta})` : "";
3073
3359
  lines.push(
3074
3360
  ` risk ${riskLabel(plan.riskScore)} \xB7 ${plan.upgrades.length} upgrade(s) \xB7 fixes ${deltaSummary(plan.fixes)}${drift}`
3075
3361
  );
3076
3362
  if (plan.introduces.total) {
3077
- lines.push(chalk7.red(` \u26A0 introduces ${deltaSummary(plan.introduces)} advisory(ies) in target versions`));
3363
+ lines.push(chalk8.red(` \u26A0 introduces ${deltaSummary(plan.introduces)} advisory(ies) in target versions`));
3078
3364
  }
3079
3365
  const shown = plan.upgrades.slice(0, 12);
3080
3366
  for (const u of shown) {
3081
- const codemod = u.playbook?.codemod ? chalk7.magenta(` [codemod: ${u.playbook.codemod}]`) : "";
3082
- lines.push(` ${chalk7.cyan(u.package)} ${chalk7.dim(`${u.from ?? "?"} \u2192 ${u.to ?? "?"}`)} ${chalk7.dim(u.reason)}${codemod}`);
3367
+ const codemod = u.playbook?.codemod ? chalk8.magenta(` [codemod: ${u.playbook.codemod}]`) : "";
3368
+ lines.push(` ${chalk8.cyan(u.package)} ${chalk8.dim(`${u.from ?? "?"} \u2192 ${u.to ?? "?"}`)} ${chalk8.dim(u.reason)}${codemod}`);
3083
3369
  }
3084
3370
  if (plan.upgrades.length > shown.length) {
3085
- lines.push(chalk7.dim(` \u2026 and ${plan.upgrades.length - shown.length} more`));
3371
+ lines.push(chalk8.dim(` \u2026 and ${plan.upgrades.length - shown.length} more`));
3086
3372
  }
3087
3373
  if (plan.upgrades.length === 0) {
3088
- lines.push(chalk7.dim(" (no upgrades in this plan)"));
3374
+ lines.push(chalk8.dim(" (no upgrades in this plan)"));
3089
3375
  }
3090
3376
  return lines.join("\n");
3091
3377
  }
3092
3378
  function dataNote(report) {
3093
3379
  if (report.vulnerabilityData === "unavailable") {
3094
- return chalk7.dim("Advisory data was unavailable \u2014 vulnerability impact is not shown.");
3380
+ return chalk8.dim("Advisory data was unavailable \u2014 vulnerability impact is not shown.");
3095
3381
  }
3096
3382
  if (report.vulnerabilityData === "partial") {
3097
- return chalk7.dim("Advisory data was partial \u2014 some ecosystems were not checked.");
3383
+ return chalk8.dim("Advisory data was partial \u2014 some ecosystems were not checked.");
3098
3384
  }
3099
3385
  return null;
3100
3386
  }
3101
3387
  function renderText(report) {
3102
3388
  const out2 = [];
3103
- out2.push(chalk7.bold("Vibgrate fix \u2014 upgrade plan"));
3389
+ out2.push(chalk8.bold("Vibgrate fix \u2014 upgrade plan"));
3104
3390
  out2.push(
3105
- chalk7.dim(
3391
+ chalk8.dim(
3106
3392
  `${report.totalCandidates} drifted dependency(ies) analysed \xB7 ${report.deepAnalysis ? "deep (major) analysis on" : "preflight only"}`
3107
3393
  )
3108
3394
  );
@@ -3110,18 +3396,18 @@ function renderText(report) {
3110
3396
  if (note) out2.push(note);
3111
3397
  if (report.exploitability && report.exploitability.kevPackages > 0) {
3112
3398
  const epss = report.exploitability.maxEpss != null ? `, peak EPSS ${(report.exploitability.maxEpss * 100).toFixed(0)}%` : "";
3113
- out2.push(chalk7.red(`\u26A0 ${report.exploitability.kevPackages} package(s) carry a KNOWN-EXPLOITED (KEV) advisory${epss} \u2014 prioritise these.`));
3399
+ out2.push(chalk8.red(`\u26A0 ${report.exploitability.kevPackages} package(s) carry a KNOWN-EXPLOITED (KEV) advisory${epss} \u2014 prioritise these.`));
3114
3400
  }
3115
3401
  out2.push("");
3116
3402
  for (const plan of report.plans) {
3117
3403
  out2.push(planBlock(plan, plan.tier === report.recommended));
3118
3404
  out2.push("");
3119
3405
  }
3120
- out2.push(chalk7.bold("Recommendation"));
3121
- out2.push(` ${chalk7.green(report.plans.find((p) => p.tier === report.recommended)?.label ?? report.recommended)} \u2014 ${report.rationale}`);
3406
+ out2.push(chalk8.bold("Recommendation"));
3407
+ out2.push(` ${chalk8.green(report.plans.find((p) => p.tier === report.recommended)?.label ?? report.recommended)} \u2014 ${report.rationale}`);
3122
3408
  if (report.unresolved.total) {
3123
3409
  out2.push("");
3124
- out2.push(chalk7.red(`\u26A0 ${deltaSummary(report.unresolved)} advisory(ies) have no upgrade path in any plan.`));
3410
+ out2.push(chalk8.red(`\u26A0 ${deltaSummary(report.unresolved)} advisory(ies) have no upgrade path in any plan.`));
3125
3411
  }
3126
3412
  return out2.join("\n");
3127
3413
  }
@@ -3193,10 +3479,14 @@ function estimateDriftScore(artifact, upgraded) {
3193
3479
  }
3194
3480
  return computeDriftScore(projects).score;
3195
3481
  }
3196
- function pmCommandFor(ecosystem, pkg, version, pm = "npm") {
3482
+ function isRootDir(dir) {
3483
+ return dir === "" || dir === ".";
3484
+ }
3485
+ function pmCommandFor(ecosystem, pkg, version, pm = "npm", opts = {}) {
3486
+ const ws = opts.workspaceRoot && pm === "pnpm" ? ["-w"] : [];
3197
3487
  switch (ecosystem) {
3198
3488
  case "npm":
3199
- if (pm === "pnpm") return { cmd: "pnpm", args: ["add", `${pkg}@${version}`] };
3489
+ if (pm === "pnpm") return { cmd: "pnpm", args: ["add", ...ws, `${pkg}@${version}`] };
3200
3490
  if (pm === "yarn") return { cmd: "yarn", args: ["add", `${pkg}@${version}`] };
3201
3491
  if (pm === "bun") return { cmd: "bun", args: ["add", `${pkg}@${version}`] };
3202
3492
  return { cmd: "npm", args: ["install", `${pkg}@${version}`] };
@@ -3224,37 +3514,241 @@ function pmCommandFor(ecosystem, pkg, version, pm = "npm") {
3224
3514
  }
3225
3515
  function defaultRun(cmd, cwd) {
3226
3516
  const res = spawnSync(cmd.cmd, cmd.args, { cwd, stdio: "inherit", shell: false });
3227
- if (res.error) return { ok: false, detail: res.error.message };
3517
+ if (res.error) {
3518
+ if (res.error.code === "ENOENT") {
3519
+ return { ok: false, toolMissing: true, detail: `${cmd.cmd} is not installed or not on PATH` };
3520
+ }
3521
+ return { ok: false, detail: res.error.message };
3522
+ }
3228
3523
  if (res.status !== 0) return { ok: false, detail: `exit ${res.status ?? "signal"}` };
3229
3524
  return { ok: true };
3230
3525
  }
3526
+ var ROOT_TARGET = [{ dir: ".", isWorkspaceRoot: false }];
3231
3527
  function applyPlan(rootDir, upgrades, opts = {}) {
3232
3528
  const run = opts.run ?? defaultRun;
3529
+ const resolveTargets = opts.resolveTargets ?? (() => ROOT_TARGET);
3233
3530
  const results = [];
3234
3531
  for (const u of upgrades) {
3235
3532
  if (!u.to) {
3236
3533
  results.push({ package: u.package, to: u.to, status: "skipped", detail: "no target version" });
3237
3534
  continue;
3238
3535
  }
3239
- const command = pmCommandFor(u.ecosystem, u.package, u.to, opts.packageManager);
3240
- if (!command) {
3536
+ if (!pmCommandFor(u.ecosystem, u.package, u.to, opts.packageManager)) {
3241
3537
  results.push({ package: u.package, to: u.to, status: "manual", detail: `${u.ecosystem}: update the manifest to ${u.to} manually` });
3242
3538
  continue;
3243
3539
  }
3540
+ const targets = resolveTargets(u);
3541
+ const effective = targets.length > 0 ? targets : ROOT_TARGET;
3542
+ const labelled = effective.length > 1;
3543
+ const where = (t) => labelled || !isRootDir(t.dir) ? `${t.dir || "."}: ` : "";
3244
3544
  if (opts.dryRun) {
3245
- results.push({ package: u.package, to: u.to, status: "skipped", detail: `would run: ${command.cmd} ${command.args.join(" ")}` });
3545
+ const detail = effective.map((t) => {
3546
+ const c2 = pmCommandFor(u.ecosystem, u.package, u.to, opts.packageManager, { workspaceRoot: t.isWorkspaceRoot });
3547
+ return `would run: ${c2.cmd} ${c2.args.join(" ")}${labelled || !isRootDir(t.dir) ? ` (in ${t.dir || "."})` : ""}`;
3548
+ }).join("; ");
3549
+ results.push({ package: u.package, to: u.to, status: "skipped", detail });
3246
3550
  continue;
3247
3551
  }
3248
- const outcome = run(command, rootDir);
3249
- results.push({
3250
- package: u.package,
3251
- to: u.to,
3252
- status: outcome.ok ? "applied" : "failed",
3253
- detail: outcome.detail
3552
+ const outcomes = effective.map((t) => {
3553
+ const c2 = pmCommandFor(u.ecosystem, u.package, u.to, opts.packageManager, { workspaceRoot: t.isWorkspaceRoot });
3554
+ return { t, res: run(c2, path.join(rootDir, t.dir)) };
3254
3555
  });
3556
+ const failures = outcomes.filter((o) => !o.res.ok);
3557
+ if (failures.length === 0) {
3558
+ results.push({
3559
+ package: u.package,
3560
+ to: u.to,
3561
+ status: "applied",
3562
+ detail: labelled ? `applied in ${effective.map((t) => t.dir || ".").join(", ")}` : void 0
3563
+ });
3564
+ } else if (failures.every((o) => o.res.toolMissing)) {
3565
+ results.push({
3566
+ package: u.package,
3567
+ to: u.to,
3568
+ status: "manual",
3569
+ detail: failures.map((o) => `${where(o.t)}${o.res.detail ?? "tool missing"} \u2014 upgrade manually`).join("; ")
3570
+ });
3571
+ } else {
3572
+ results.push({
3573
+ package: u.package,
3574
+ to: u.to,
3575
+ status: "failed",
3576
+ detail: failures.map((o) => `${where(o.t)}${o.res.detail ?? "failed"}`).join("; ")
3577
+ });
3578
+ }
3255
3579
  }
3256
3580
  return results;
3257
3581
  }
3582
+ var REGISTRY_URL = "https://registry.npmjs.org/@vibgrate%2fcli/latest";
3583
+ var CACHE_DIR = path.join(os.homedir(), ".vibgrate");
3584
+ var CACHE_FILE = path.join(CACHE_DIR, "update-check.json");
3585
+ async function fetchLatestVersion() {
3586
+ try {
3587
+ const controller = new AbortController();
3588
+ const timeout = setTimeout(() => controller.abort(), 1e4);
3589
+ timeout.unref?.();
3590
+ let response;
3591
+ try {
3592
+ response = await fetch(REGISTRY_URL, {
3593
+ headers: { Accept: "application/json" },
3594
+ signal: controller.signal
3595
+ });
3596
+ } finally {
3597
+ clearTimeout(timeout);
3598
+ }
3599
+ if (!response.ok) return null;
3600
+ const data = await response.json();
3601
+ const latest = data.version;
3602
+ if (!latest || !semver.valid(latest)) return null;
3603
+ await writeCache({ latest, checkedAt: Date.now() });
3604
+ return latest;
3605
+ } catch {
3606
+ return null;
3607
+ }
3608
+ }
3609
+ async function writeCache(data) {
3610
+ try {
3611
+ await fs15.mkdir(CACHE_DIR, { recursive: true });
3612
+ await fs15.writeFile(CACHE_FILE, JSON.stringify(data), "utf-8");
3613
+ } catch {
3614
+ }
3615
+ }
3616
+
3617
+ // src/reporting/commands/update.ts
3618
+ function detectGlobalInstall() {
3619
+ const execPath = process.argv[1] || "";
3620
+ if (execPath.includes("/lib/node_modules/") || execPath.includes("\\node_modules\\")) {
3621
+ if (!execPath.includes(process.cwd())) {
3622
+ if (execPath.includes("pnpm")) return "pnpm";
3623
+ if (execPath.includes("yarn")) return "yarn";
3624
+ if (execPath.includes("bun")) return "bun";
3625
+ return "npm";
3626
+ }
3627
+ }
3628
+ return null;
3629
+ }
3630
+ function getGlobalUpdateCommand(pm, pkg, version) {
3631
+ const spec = `${pkg}@${version}`;
3632
+ switch (pm) {
3633
+ case "pnpm":
3634
+ return `pnpm add -g ${spec}`;
3635
+ case "yarn":
3636
+ return `yarn global add ${spec}`;
3637
+ case "bun":
3638
+ return `bun add -g ${spec}`;
3639
+ case "npm":
3640
+ default:
3641
+ return `npm install -g ${spec}`;
3642
+ }
3643
+ }
3644
+ async function detectPackageManager(cwd) {
3645
+ if (await pathExists(path.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
3646
+ if (await pathExists(path.join(cwd, "bun.lockb"))) return "bun";
3647
+ if (await pathExists(path.join(cwd, "yarn.lock"))) return "yarn";
3648
+ return "npm";
3649
+ }
3650
+ async function detectWorkspaceRoot(cwd) {
3651
+ if (await pathExists(path.join(cwd, "pnpm-workspace.yaml"))) return true;
3652
+ try {
3653
+ const pkgPath = path.join(cwd, "package.json");
3654
+ const raw = await (await import('fs/promises')).readFile(pkgPath, "utf-8");
3655
+ const pkg = JSON.parse(raw);
3656
+ return pkg.workspaces != null;
3657
+ } catch {
3658
+ return false;
3659
+ }
3660
+ }
3661
+ function getInstallCommand(pm, pkg, version, isDev, opts = {}) {
3662
+ const spec = `${pkg}@${version}`;
3663
+ const ws = opts.workspaceRoot && pm === "pnpm" ? " -w" : "";
3664
+ switch (pm) {
3665
+ case "pnpm":
3666
+ return isDev ? `pnpm add${ws} -D ${spec}` : `pnpm add${ws} ${spec}`;
3667
+ case "yarn":
3668
+ return isDev ? `yarn add --dev ${spec}` : `yarn add ${spec}`;
3669
+ case "bun":
3670
+ return isDev ? `bun add -d ${spec}` : `bun add ${spec}`;
3671
+ case "npm":
3672
+ default:
3673
+ return isDev ? `npm install --save-dev ${spec}` : `npm install ${spec}`;
3674
+ }
3675
+ }
3676
+ async function isDevDependency(cwd) {
3677
+ try {
3678
+ const pkgPath = path.join(cwd, "package.json");
3679
+ const raw = await (await import('fs/promises')).readFile(pkgPath, "utf-8");
3680
+ const pkg = JSON.parse(raw);
3681
+ return Boolean(pkg.devDependencies?.["@vibgrate/cli"]);
3682
+ } catch {
3683
+ return true;
3684
+ }
3685
+ }
3686
+ async function confirmWorkspaceRoot(pm) {
3687
+ if (!(process.stdin.isTTY && process.stdout.isTTY)) return false;
3688
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
3689
+ return new Promise((resolve16) => {
3690
+ rl.question(chalk8.yellow(`Update the ${pm} workspace root with -w? [y/N]: `), (ans) => {
3691
+ rl.close();
3692
+ const t = ans.trim().toLowerCase();
3693
+ resolve16(t === "y" || t === "yes");
3694
+ });
3695
+ });
3696
+ }
3697
+ var updateCommand = new Command("update").description("Update vibgrate to the latest version").option("--check", "Only check for updates, do not install").option("--pm <manager>", "Package manager to use (npm, pnpm, yarn, bun)").option("--global", "Update global installation").option("-y, --yes", "Skip confirmation prompts (e.g. installing at a workspace root)").option("-w, --workspace-root", "Allow updating the pnpm workspace root (implies --yes for that prompt)").action(
3698
+ async (opts) => {
3699
+ console.log(chalk8.dim(`Current version: ${VERSION}`));
3700
+ console.log(chalk8.dim("Checking npm registry..."));
3701
+ const latest = await fetchLatestVersion();
3702
+ if (!latest) {
3703
+ console.error(chalk8.red("Could not reach the npm registry. Check your network connection."));
3704
+ process.exit(1);
3705
+ }
3706
+ const semver2 = await import('semver');
3707
+ if (!semver2.gt(latest, VERSION)) {
3708
+ console.log(chalk8.green("\u2714") + ` You are on the latest version (${VERSION}).`);
3709
+ return;
3710
+ }
3711
+ console.log(chalk8.yellow(`Update available: ${VERSION} \u2192 ${latest}`));
3712
+ if (opts.check) {
3713
+ console.log(chalk8.dim('Run "vg update" to install.'));
3714
+ return;
3715
+ }
3716
+ const cwd = process.cwd();
3717
+ const globalPm = detectGlobalInstall();
3718
+ const isGlobal = opts.global || globalPm !== null;
3719
+ const pm = opts.pm || (globalPm ?? await detectPackageManager(cwd));
3720
+ let cmd;
3721
+ if (isGlobal) {
3722
+ cmd = getGlobalUpdateCommand(pm, "@vibgrate/cli", latest);
3723
+ console.log(chalk8.dim(`Updating global installation with ${pm}: ${cmd}`));
3724
+ } else {
3725
+ const isDev = await isDevDependency(cwd);
3726
+ let workspaceRoot = false;
3727
+ if (pm === "pnpm" && await detectWorkspaceRoot(cwd)) {
3728
+ console.log(
3729
+ chalk8.yellow("Detected a pnpm workspace root.") + chalk8.dim(" Installing here needs the -w flag and updates the root package.json.")
3730
+ );
3731
+ const proceed = opts.yes || opts.workspaceRoot || await confirmWorkspaceRoot(pm);
3732
+ if (!proceed) {
3733
+ const rootCmd = getInstallCommand(pm, "@vibgrate/cli", latest, isDev, { workspaceRoot: true });
3734
+ console.log(chalk8.dim(`Skipped. To update the workspace root, run: ${rootCmd}`));
3735
+ console.log(chalk8.dim('or re-run "vg update --yes" (or --workspace-root) to let vg do it.'));
3736
+ return;
3737
+ }
3738
+ workspaceRoot = true;
3739
+ }
3740
+ cmd = getInstallCommand(pm, "@vibgrate/cli", latest, isDev, { workspaceRoot });
3741
+ console.log(chalk8.dim(`Using ${pm}: ${cmd}`));
3742
+ }
3743
+ try {
3744
+ execSync(cmd, { cwd, stdio: "inherit" });
3745
+ console.log(chalk8.green("\u2714") + ` Updated to @vibgrate/cli@${latest}`);
3746
+ } catch {
3747
+ console.error(chalk8.red(`Update failed. Run manually: ${cmd}`));
3748
+ process.exit(1);
3749
+ }
3750
+ }
3751
+ );
3258
3752
 
3259
3753
  // src/reporting/commands/fix.ts
3260
3754
  var SEVERITY_RANK = { unknown: 0, low: 1, moderate: 2, high: 3, critical: 4 };
@@ -3305,12 +3799,12 @@ async function loadArtifact(rootDir, inFile, scan = defaultFixScanner) {
3305
3799
  return readJsonFile(artifactPath);
3306
3800
  }
3307
3801
  console.error(
3308
- chalk7.dim(
3802
+ chalk8.dim(
3309
3803
  `Scan is out of date (${staleness.newestChanged ?? "a dependency manifest"} changed since the last scan) \u2014 re-running a drift scan first (code map skipped)\u2026`
3310
3804
  )
3311
3805
  );
3312
3806
  } else {
3313
- console.error(chalk7.dim("No scan found \u2014 running a drift scan first (code map skipped)\u2026"));
3807
+ console.error(chalk8.dim("No scan found \u2014 running a drift scan first (code map skipped)\u2026"));
3314
3808
  }
3315
3809
  return scan(rootDir);
3316
3810
  }
@@ -3327,20 +3821,20 @@ async function repositoryIdentity(rootDir, override) {
3327
3821
  var fixCommand = new Command("fix").description("Analyse drift and get ranked, risk-tiered upgrade plans from the hosted planner (requires login/DSN; read-only \u2014 never edits your project)").argument("[path]", "Path to analyse", ".").option("--in <file>", "Scan artifact to read", ".vibgrate/scan_result.json").option("--format <format>", "Output format (text|json|md)", "text").option("--dsn <dsn>", 'DSN token (or use VIBGRATE_DSN env / "vg login")').option("--region <region>", "Override data residency region (us, eu)").option("--repository-name <name>", "Override the repository name recorded for this plan").option("--plan <tier>", "Apply a specific plan non-interactively (safe|balanced|aggressive)").option("--yes", "Apply the recommended plan without prompting").option("--dry-run", "Show what would change without applying").option("--no-apply", "Only print the plans; never modify the project").option("--fail-on-vulns <severity>", "Exit non-zero if the recommended plan leaves an advisory at/above this severity unresolved (low|moderate|high|critical)").action(async (targetPath, opts) => {
3328
3822
  const rootDir = path.resolve(targetPath);
3329
3823
  if (!await pathExists(rootDir)) {
3330
- console.error(chalk7.red(`Path does not exist: ${rootDir}`));
3824
+ console.error(chalk8.red(`Path does not exist: ${rootDir}`));
3331
3825
  process.exit(1);
3332
3826
  }
3333
3827
  const failOn = opts.failOnVulns;
3334
3828
  if (failOn && !(failOn in SEVERITY_RANK)) {
3335
- console.error(chalk7.red(`Invalid --fail-on-vulns value '${opts.failOnVulns}'. Use one of: low, moderate, high, critical.`));
3829
+ console.error(chalk8.red(`Invalid --fail-on-vulns value '${opts.failOnVulns}'. Use one of: low, moderate, high, critical.`));
3336
3830
  process.exit(1);
3337
3831
  }
3338
3832
  const dsn = resolveDsn(opts.dsn);
3339
3833
  if (!dsn) {
3340
3834
  const cli = resolveCliInvocation();
3341
- console.error(chalk7.red("vg fix needs a Vibgrate login."));
3835
+ console.error(chalk8.red("vg fix needs a Vibgrate login."));
3342
3836
  console.error(
3343
- chalk7.dim(
3837
+ chalk8.dim(
3344
3838
  `Run "${cli} login" (or set VIBGRATE_DSN / pass --dsn) to analyse upgrades with the hosted planner. See https://vibgrate.com/cli for details.`
3345
3839
  )
3346
3840
  );
@@ -3348,21 +3842,21 @@ var fixCommand = new Command("fix").description("Analyse drift and get ranked, r
3348
3842
  }
3349
3843
  const parsed = parseDsn(dsn);
3350
3844
  if (!parsed) {
3351
- console.error(chalk7.red('Invalid DSN format. Re-run "vg login" or check VIBGRATE_DSN.'));
3845
+ console.error(chalk8.red('Invalid DSN format. Re-run "vg login" or check VIBGRATE_DSN.'));
3352
3846
  process.exit(1);
3353
3847
  }
3354
3848
  let host;
3355
3849
  try {
3356
3850
  host = opts.region ? resolveIngestHost(opts.region) : parsed.host;
3357
3851
  } catch (e) {
3358
- console.error(chalk7.red(e instanceof Error ? e.message : String(e)));
3852
+ console.error(chalk8.red(e instanceof Error ? e.message : String(e)));
3359
3853
  process.exit(1);
3360
3854
  }
3361
3855
  let artifact;
3362
3856
  try {
3363
3857
  artifact = await loadArtifact(rootDir, opts.in);
3364
3858
  } catch (e) {
3365
- console.error(chalk7.red(e instanceof Error ? e.message : String(e)));
3859
+ console.error(chalk8.red(e instanceof Error ? e.message : String(e)));
3366
3860
  process.exit(1);
3367
3861
  }
3368
3862
  const raw = collectCandidates(artifact);
@@ -3405,13 +3899,13 @@ var fixCommand = new Command("fix").description("Analyse drift and get ranked, r
3405
3899
  response = await parseFixPlanResponse(httpResponse);
3406
3900
  } catch (e) {
3407
3901
  const msg = e instanceof Error ? e.message : String(e);
3408
- console.error(chalk7.red(`Could not reach the upgrade planner: ${msg}`));
3409
- console.error(chalk7.dim("Check your connection and try again, or see https://vibgrate.com/help."));
3902
+ console.error(chalk8.red(`Could not reach the upgrade planner: ${msg}`));
3903
+ console.error(chalk8.dim("Check your connection and try again, or see https://vibgrate.com/help."));
3410
3904
  process.exit(1);
3411
3905
  }
3412
3906
  if (response.status === "error") {
3413
- console.error(chalk7.red(response.error ?? "The planner returned an error."));
3414
- if (response.requestId) console.error(chalk7.dim(` (ref ${response.requestId})`));
3907
+ console.error(chalk8.red(response.error ?? "The planner returned an error."));
3908
+ if (response.requestId) console.error(chalk8.dim(` (ref ${response.requestId})`));
3415
3909
  process.exit(1);
3416
3910
  }
3417
3911
  const currentDrift = artifact.drift?.score;
@@ -3440,7 +3934,7 @@ var fixCommand = new Command("fix").description("Analyse drift and get ranked, r
3440
3934
  }
3441
3935
  if (stillOpen > 0) {
3442
3936
  console.error(
3443
- chalk7.red(`
3937
+ chalk8.red(`
3444
3938
  Failing: the recommended plan leaves ${stillOpen} advisory(ies) at/above ${failOn} unresolved.`)
3445
3939
  );
3446
3940
  process.exit(2);
@@ -3450,21 +3944,21 @@ Failing: the recommended plan leaves ${stillOpen} advisory(ies) at/above ${failO
3450
3944
  function handleHttpError(response, workspaceId, host) {
3451
3945
  const upgradeUrl = `https://${dashHostForIngestHost(host)}/${workspaceId}`;
3452
3946
  if (response.status === 401 || response.status === 403) {
3453
- console.error(chalk7.red("Not authorised. Your DSN may be invalid or lack access to this workspace."));
3454
- console.error(chalk7.dim('Re-run "vg login" or check VIBGRATE_DSN.'));
3947
+ console.error(chalk8.red("Not authorised. Your DSN may be invalid or lack access to this workspace."));
3948
+ console.error(chalk8.dim('Re-run "vg login" or check VIBGRATE_DSN.'));
3455
3949
  process.exit(1);
3456
3950
  }
3457
3951
  if (response.status === 402) {
3458
- console.error(chalk7.red("vg fix is a paid capability that is not enabled on your current plan."));
3459
- console.error(chalk7.dim(`Upgrade to enable the hosted upgrade planner: ${upgradeUrl}`));
3952
+ console.error(chalk8.red("vg fix is a paid capability that is not enabled on your current plan."));
3953
+ console.error(chalk8.dim(`Upgrade to enable the hosted upgrade planner: ${upgradeUrl}`));
3460
3954
  process.exit(1);
3461
3955
  }
3462
3956
  if (response.status === 429) {
3463
- console.error(chalk7.red("Rate limited by the planner. Wait a moment and try again."));
3957
+ console.error(chalk8.red("Rate limited by the planner. Wait a moment and try again."));
3464
3958
  process.exit(1);
3465
3959
  }
3466
- console.error(chalk7.red(`The planner returned HTTP ${response.status}.`));
3467
- console.error(chalk7.dim("Try again shortly, or see https://vibgrate.com/help."));
3960
+ console.error(chalk8.red(`The planner returned HTTP ${response.status}.`));
3961
+ console.error(chalk8.dim("Try again shortly, or see https://vibgrate.com/help."));
3468
3962
  process.exit(1);
3469
3963
  }
3470
3964
  function emit(report, format) {
@@ -3481,6 +3975,28 @@ function emit(report, format) {
3481
3975
  break;
3482
3976
  }
3483
3977
  }
3978
+ function relProjectDir(rootDir, projectPath) {
3979
+ const rel3 = path.isAbsolute(projectPath) ? path.relative(rootDir, projectPath) : projectPath;
3980
+ return rel3 === "" ? "." : rel3;
3981
+ }
3982
+ async function buildTargetResolver(rootDir, artifact) {
3983
+ const rootIsWorkspace = await detectWorkspaceRoot(rootDir);
3984
+ const owners = /* @__PURE__ */ new Map();
3985
+ for (const project of artifact.projects ?? []) {
3986
+ const ecosystem = ecosystemId(project.type);
3987
+ const dir = relProjectDir(rootDir, project.path);
3988
+ for (const dep of project.dependencies ?? []) {
3989
+ const key = `${ecosystem}\0${dep.package}`;
3990
+ (owners.get(key) ?? owners.set(key, /* @__PURE__ */ new Set()).get(key)).add(dir);
3991
+ }
3992
+ }
3993
+ const rootFallback = [{ dir: ".", isWorkspaceRoot: rootIsWorkspace }];
3994
+ return (upgrade) => {
3995
+ const dirs = owners.get(`${upgrade.ecosystem}\0${upgrade.package}`);
3996
+ if (!dirs || dirs.size === 0) return rootFallback;
3997
+ return [...dirs].sort((a, b) => a.localeCompare(b)).map((dir) => ({ dir, isWorkspaceRoot: dir === "." && rootIsWorkspace }));
3998
+ };
3999
+ }
3484
4000
  function npmPackageManager(artifact) {
3485
4001
  for (const p of artifact.projects ?? []) {
3486
4002
  if ((p.type === "node" || p.type === "typescript") && p.packageManager) {
@@ -3492,11 +4008,11 @@ function npmPackageManager(artifact) {
3492
4008
  }
3493
4009
  function promptPlanSelection(response) {
3494
4010
  const plans = response.plans.filter((p) => p.upgrades.length > 0);
3495
- console.log(chalk7.bold("\nSelect a plan to apply:"));
4011
+ console.log(chalk8.bold("\nSelect a plan to apply:"));
3496
4012
  plans.forEach((p, i) => {
3497
- const rec = p.tier === response.recommended ? chalk7.green(" (recommended)") : "";
4013
+ const rec = p.tier === response.recommended ? chalk8.green(" (recommended)") : "";
3498
4014
  const drift = typeof p.expectedDriftScore === "number" && typeof response.currentDriftScore === "number" ? ` \xB7 DriftScore ${response.currentDriftScore}\u2192${p.expectedDriftScore}` : "";
3499
- console.log(` ${i + 1}) ${chalk7.bold(p.label)}${rec} risk ${p.riskScore}/100 \xB7 ${p.upgrades.length} upgrade(s)${drift}`);
4015
+ console.log(` ${i + 1}) ${chalk8.bold(p.label)}${rec} risk ${p.riskScore}/100 \xB7 ${p.upgrades.length} upgrade(s)${drift}`);
3500
4016
  });
3501
4017
  const defaultIdx = plans.findIndex((p) => p.tier === response.recommended);
3502
4018
  const def = defaultIdx >= 0 ? defaultIdx + 1 : 1;
@@ -3515,14 +4031,14 @@ function promptPlanSelection(response) {
3515
4031
  async function runApplyFlow(rootDir, artifact, response, opts) {
3516
4032
  const nonEmpty = response.plans.filter((p) => p.upgrades.length > 0);
3517
4033
  if (nonEmpty.length === 0) {
3518
- console.log(chalk7.green("\n\u2714 Nothing to upgrade \u2014 every tracked dependency is current."));
4034
+ console.log(chalk8.green("\n\u2714 Nothing to upgrade \u2014 every tracked dependency is current."));
3519
4035
  return;
3520
4036
  }
3521
4037
  let chosen;
3522
4038
  if (opts.plan) {
3523
4039
  chosen = response.plans.find((p) => p.tier === opts.plan);
3524
4040
  if (!chosen) {
3525
- console.error(chalk7.red(`Unknown plan '${opts.plan}'. Use safe, balanced, or aggressive.`));
4041
+ console.error(chalk8.red(`Unknown plan '${opts.plan}'. Use safe, balanced, or aggressive.`));
3526
4042
  process.exit(1);
3527
4043
  }
3528
4044
  } else if (nonEmpty.length === 1) {
@@ -3532,42 +4048,43 @@ async function runApplyFlow(rootDir, artifact, response, opts) {
3532
4048
  } else if (process.stdin.isTTY && process.stdout.isTTY) {
3533
4049
  const tier = await promptPlanSelection(response);
3534
4050
  if (!tier) {
3535
- console.log(chalk7.dim("No plan applied."));
4051
+ console.log(chalk8.dim("No plan applied."));
3536
4052
  return;
3537
4053
  }
3538
4054
  chosen = response.plans.find((p) => p.tier === tier);
3539
4055
  } else {
3540
- console.log(chalk7.dim("\nMultiple plans available \u2014 re-run with --plan <tier> or --yes to apply, or --dry-run to preview."));
4056
+ console.log(chalk8.dim("\nMultiple plans available \u2014 re-run with --plan <tier> or --yes to apply, or --dry-run to preview."));
3541
4057
  return;
3542
4058
  }
3543
4059
  if (!chosen || chosen.upgrades.length === 0) {
3544
- console.log(chalk7.dim("Selected plan has no upgrades."));
4060
+ console.log(chalk8.dim("Selected plan has no upgrades."));
3545
4061
  return;
3546
4062
  }
3547
4063
  const pm = npmPackageManager(artifact);
3548
- const results = applyPlan(rootDir, chosen.upgrades, { dryRun: opts.dryRun, packageManager: pm });
4064
+ const resolveTargets = await buildTargetResolver(rootDir, artifact);
4065
+ const results = applyPlan(rootDir, chosen.upgrades, { dryRun: opts.dryRun, packageManager: pm, resolveTargets });
3549
4066
  if (opts.dryRun) {
3550
- console.log(chalk7.bold(`
4067
+ console.log(chalk8.bold(`
3551
4068
  Dry run \u2014 ${chosen.label} plan (${chosen.upgrades.length} upgrade(s)):`));
3552
4069
  for (const r of results) {
3553
- if (r.status === "manual") console.log(chalk7.yellow(` \u26A0 ${r.package}: ${r.detail}`));
3554
- else console.log(chalk7.dim(` ${r.package}: ${r.detail}`));
4070
+ if (r.status === "manual") console.log(chalk8.yellow(` \u26A0 ${r.package}: ${r.detail}`));
4071
+ else console.log(chalk8.dim(` ${r.package}: ${r.detail}`));
3555
4072
  }
3556
4073
  if (typeof chosen.expectedDriftScore === "number") {
3557
- console.log(chalk7.dim(`Expected DriftScore after apply: ~${chosen.expectedDriftScore} (was ${response.currentDriftScore ?? "?"}).`));
4074
+ console.log(chalk8.dim(`Expected DriftScore after apply: ~${chosen.expectedDriftScore} (was ${response.currentDriftScore ?? "?"}).`));
3558
4075
  }
3559
4076
  return;
3560
4077
  }
3561
4078
  const applied = results.filter((r) => r.status === "applied");
3562
4079
  const failed = results.filter((r) => r.status === "failed");
3563
4080
  const manual = results.filter((r) => r.status === "manual");
3564
- console.log(chalk7.bold(`
4081
+ console.log(chalk8.bold(`
3565
4082
  Applied the ${chosen.label} plan:`));
3566
4083
  console.log(
3567
- chalk7.green(` \u2714 ${applied.length} upgraded`) + (failed.length ? chalk7.red(` \u2716 ${failed.length} failed`) : "") + (manual.length ? chalk7.yellow(` \u26A0 ${manual.length} need manual work`) : "")
4084
+ chalk8.green(` \u2714 ${applied.length} upgraded`) + (failed.length ? chalk8.red(` \u2716 ${failed.length} failed`) : "") + (manual.length ? chalk8.yellow(` \u26A0 ${manual.length} need manual work`) : "")
3568
4085
  );
3569
- for (const r of failed) console.log(chalk7.red(` \u2716 ${r.package} \u2192 ${r.to}: ${r.detail ?? "failed"}`));
3570
- for (const r of manual) console.log(chalk7.yellow(` \u26A0 ${r.package} \u2192 ${r.to}: ${r.detail}`));
4086
+ for (const r of failed) console.log(chalk8.red(` \u2716 ${r.package} \u2192 ${r.to}: ${r.detail ?? "failed"}`));
4087
+ for (const r of manual) console.log(chalk8.yellow(` \u26A0 ${r.package} \u2192 ${r.to}: ${r.detail}`));
3571
4088
  if (typeof chosen.expectedDriftScore === "number" && typeof response.currentDriftScore === "number") {
3572
4089
  const delta = chosen.expectedDriftScore - response.currentDriftScore;
3573
4090
  console.log(
@@ -3578,20 +4095,20 @@ Applied the ${chosen.label} plan:`));
3578
4095
  }
3579
4096
  function formatText(artifact) {
3580
4097
  const lines = [];
3581
- const teal2 = chalk7.hex("#3FB0A4");
3582
- const mint2 = chalk7.hex("#4FE3C1");
4098
+ const teal2 = chalk8.hex("#3FB0A4");
4099
+ const mint2 = chalk8.hex("#4FE3C1");
3583
4100
  lines.push("");
3584
4101
  lines.push(" " + teal2("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u256E") + mint2("\u279C"));
3585
- lines.push(" " + chalk7.dim("\u2524") + teal2("\u2502") + " " + mint2("\u25FC") + " " + mint2("\u25FC") + " " + teal2("\u2502") + chalk7.dim("\u251C") + " " + chalk7.bold.white("vibgrate"));
3586
- lines.push(" " + chalk7.dim("\u2524") + teal2("\u2502") + " " + chalk7.dim("\u2581\u2581") + " " + teal2("\u2502") + chalk7.dim("\u251C") + " " + chalk7.dim(`Code Intelligence Engine v${VERSION}`));
4102
+ lines.push(" " + chalk8.dim("\u2524") + teal2("\u2502") + " " + mint2("\u25FC") + " " + mint2("\u25FC") + " " + teal2("\u2502") + chalk8.dim("\u251C") + " " + chalk8.bold.white("vibgrate"));
4103
+ lines.push(" " + chalk8.dim("\u2524") + teal2("\u2502") + " " + chalk8.dim("\u2581\u2581") + " " + teal2("\u2502") + chalk8.dim("\u251C") + " " + chalk8.dim(`Code Intelligence Engine v${VERSION}`));
3587
4104
  lines.push(" " + teal2("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
3588
4105
  lines.push("");
3589
4106
  lines.push(...titleBox("Vibgrate Drift Report", teal2));
3590
4107
  lines.push("");
3591
4108
  for (const project of artifact.projects) {
3592
- lines.push(chalk7.bold(` \u2500\u2500 ${project.name} `) + chalk7.dim(`(${project.type}) ${project.path}`));
4109
+ lines.push(chalk8.bold(` \u2500\u2500 ${project.name} `) + chalk8.dim(`(${project.type}) ${project.path}`));
3593
4110
  if (project.runtime) {
3594
- const behindStr = project.runtimeMajorsBehind !== void 0 && project.runtimeMajorsBehind > 0 ? chalk7.yellow(` (${project.runtimeMajorsBehind} major${project.runtimeMajorsBehind > 1 ? "s" : ""} behind)`) : chalk7.green(" (current)");
4111
+ const behindStr = project.runtimeMajorsBehind !== void 0 && project.runtimeMajorsBehind > 0 ? chalk8.yellow(` (${project.runtimeMajorsBehind} major${project.runtimeMajorsBehind > 1 ? "s" : ""} behind)`) : chalk8.green(" (current)");
3595
4112
  lines.push(` Runtime: ${project.runtime}${behindStr}`);
3596
4113
  }
3597
4114
  if (project.targetFramework) {
@@ -3600,7 +4117,7 @@ function formatText(artifact) {
3600
4117
  if (project.frameworks.length > 0) {
3601
4118
  lines.push(" Frameworks:");
3602
4119
  for (const fw of project.frameworks) {
3603
- const lag = fw.majorsBehind !== null ? fw.majorsBehind === 0 ? chalk7.green("current") : chalk7.yellow(`${fw.majorsBehind} behind`) : chalk7.dim("unknown");
4120
+ const lag = fw.majorsBehind !== null ? fw.majorsBehind === 0 ? chalk8.green("current") : chalk8.yellow(`${fw.majorsBehind} behind`) : chalk8.dim("unknown");
3604
4121
  lines.push(` ${fw.name}: ${fw.currentVersion ?? "?"} \u2192 ${fw.latestVersion ?? "?"} (${lag})`);
3605
4122
  }
3606
4123
  }
@@ -3608,13 +4125,13 @@ function formatText(artifact) {
3608
4125
  const total = b.current + b.oneBehind + b.twoPlusBehind + b.unknown;
3609
4126
  if (total > 0) {
3610
4127
  lines.push(" Dependencies:");
3611
- lines.push(` ${chalk7.green(`${b.current} current`)} ${chalk7.yellow(`${b.oneBehind} 1-behind`)} ${chalk7.red(`${b.twoPlusBehind} 2+ behind`)} ${chalk7.dim(`${b.unknown} unknown`)}`);
4128
+ lines.push(` ${chalk8.green(`${b.current} current`)} ${chalk8.yellow(`${b.oneBehind} 1-behind`)} ${chalk8.red(`${b.twoPlusBehind} 2+ behind`)} ${chalk8.dim(`${b.unknown} unknown`)}`);
3612
4129
  }
3613
4130
  lines.push("");
3614
4131
  }
3615
4132
  if (artifact.delta !== void 0) {
3616
- const deltaStr = artifact.delta > 0 ? chalk7.red(`+${artifact.delta}`) : artifact.delta < 0 ? chalk7.green(`${artifact.delta}`) : chalk7.dim("0");
3617
- lines.push(chalk7.bold(" Drift Delta: ") + deltaStr + " (vs baseline)");
4133
+ const deltaStr = artifact.delta > 0 ? chalk8.red(`+${artifact.delta}`) : artifact.delta < 0 ? chalk8.green(`${artifact.delta}`) : chalk8.dim("0");
4134
+ lines.push(chalk8.bold(" Drift Delta: ") + deltaStr + " (vs baseline)");
3618
4135
  lines.push("");
3619
4136
  }
3620
4137
  if (artifact.extended) {
@@ -3625,15 +4142,15 @@ function formatText(artifact) {
3625
4142
  const warnings = artifact.findings.filter((f) => f.level === "warning");
3626
4143
  const notes = artifact.findings.filter((f) => f.level === "note");
3627
4144
  const summary = [
3628
- errors.length > 0 ? chalk7.red(`${errors.length} error${errors.length !== 1 ? "s" : ""}`) : "",
3629
- warnings.length > 0 ? chalk7.yellow(`${warnings.length} warning${warnings.length !== 1 ? "s" : ""}`) : "",
3630
- notes.length > 0 ? chalk7.blue(`${notes.length} note${notes.length !== 1 ? "s" : ""}`) : ""
3631
- ].filter(Boolean).join(chalk7.dim(", "));
3632
- lines.push(chalk7.bold.underline(` Findings`) + chalk7.dim(` (${summary})`));
4145
+ errors.length > 0 ? chalk8.red(`${errors.length} error${errors.length !== 1 ? "s" : ""}`) : "",
4146
+ warnings.length > 0 ? chalk8.yellow(`${warnings.length} warning${warnings.length !== 1 ? "s" : ""}`) : "",
4147
+ notes.length > 0 ? chalk8.blue(`${notes.length} note${notes.length !== 1 ? "s" : ""}`) : ""
4148
+ ].filter(Boolean).join(chalk8.dim(", "));
4149
+ lines.push(chalk8.bold.underline(` Findings`) + chalk8.dim(` (${summary})`));
3633
4150
  for (const f of artifact.findings) {
3634
- const icon = f.level === "error" ? chalk7.red("\u2716") : f.level === "warning" ? chalk7.yellow("\u26A0") : chalk7.blue("\u2139");
4151
+ const icon = f.level === "error" ? chalk8.red("\u2716") : f.level === "warning" ? chalk8.yellow("\u26A0") : chalk8.blue("\u2139");
3635
4152
  lines.push(` ${icon} ${f.message}`);
3636
- lines.push(chalk7.dim(` ${f.ruleId} in ${f.location}`));
4153
+ lines.push(chalk8.dim(` ${f.ruleId} in ${f.location}`));
3637
4154
  }
3638
4155
  lines.push("");
3639
4156
  }
@@ -3643,10 +4160,10 @@ function formatText(artifact) {
3643
4160
  lines.push("");
3644
4161
  for (let i = 0; i < actions.length; i++) {
3645
4162
  const a = actions[i];
3646
- const num = chalk7.bold.cyan(` ${i + 1}.`);
3647
- lines.push(`${num} ${chalk7.bold(a.title)}`);
3648
- lines.push(chalk7.dim(` ${a.explanation}`));
3649
- if (a.impact) lines.push(` Impact: ${chalk7.green(a.impact)}`);
4163
+ const num = chalk8.bold.cyan(` ${i + 1}.`);
4164
+ lines.push(`${num} ${chalk8.bold(a.title)}`);
4165
+ lines.push(chalk8.dim(` ${a.explanation}`));
4166
+ if (a.impact) lines.push(` Impact: ${chalk8.green(a.impact)}`);
3650
4167
  lines.push("");
3651
4168
  }
3652
4169
  }
@@ -3658,30 +4175,30 @@ function formatText(artifact) {
3658
4175
  lines.push("");
3659
4176
  for (const solution of artifact.solutions) {
3660
4177
  const solScore = solution.drift?.score;
3661
- const color = typeof solScore === "number" ? solScore <= 30 ? chalk7.green : solScore <= 60 ? chalk7.yellow : chalk7.red : chalk7.dim;
3662
- lines.push(` \u2022 ${solution.name} (${solution.projectPaths.length} projects) \u2014 ${typeof solScore === "number" ? color(`${solScore}/100`) : chalk7.dim("n/a")}`);
4178
+ const color = typeof solScore === "number" ? solScore <= 30 ? chalk8.green : solScore <= 60 ? chalk8.yellow : chalk8.red : chalk8.dim;
4179
+ lines.push(` \u2022 ${solution.name} (${solution.projectPaths.length} projects) \u2014 ${typeof solScore === "number" ? color(`${solScore}/100`) : chalk8.dim("n/a")}`);
3663
4180
  }
3664
4181
  lines.push("");
3665
4182
  }
3666
- const scoreColor = artifact.drift.score <= 30 ? chalk7.green : artifact.drift.score <= 60 ? chalk7.yellow : chalk7.red;
4183
+ const scoreColor = artifact.drift.score <= 30 ? chalk8.green : artifact.drift.score <= 60 ? chalk8.yellow : chalk8.red;
3667
4184
  lines.push(...titleBox("DriftScore Summary"));
3668
4185
  lines.push("");
3669
- lines.push(chalk7.bold(" DriftScore: ") + scoreColor.bold(`${artifact.drift.score}/100`));
3670
- lines.push(chalk7.bold(" Risk Level: ") + riskBadge(artifact.drift.riskLevel));
3671
- lines.push(chalk7.bold(" Projects: ") + `${artifact.projects.length}`);
4186
+ lines.push(chalk8.bold(" DriftScore: ") + scoreColor.bold(`${artifact.drift.score}/100`));
4187
+ lines.push(chalk8.bold(" Risk Level: ") + riskBadge(artifact.drift.riskLevel));
4188
+ lines.push(chalk8.bold(" Projects: ") + `${artifact.projects.length}`);
3672
4189
  if (artifact.vcs) {
3673
4190
  const vcsParts = [artifact.vcs.type];
3674
4191
  if (artifact.vcs.branch) vcsParts.push(artifact.vcs.branch);
3675
- if (artifact.vcs.shortSha) vcsParts.push(chalk7.dim(artifact.vcs.shortSha));
3676
- lines.push(chalk7.bold(" VCS: ") + vcsParts.join(" "));
4192
+ if (artifact.vcs.shortSha) vcsParts.push(chalk8.dim(artifact.vcs.shortSha));
4193
+ lines.push(chalk8.bold(" VCS: ") + vcsParts.join(" "));
3677
4194
  }
3678
4195
  lines.push("");
3679
4196
  const m = new Set(artifact.drift.measured ?? ["runtime", "framework", "dependency", "eol"]);
3680
- lines.push(" " + chalk7.bold.underline("Score Breakdown"));
3681
- lines.push(` Runtime: ${m.has("runtime") ? scoreBar(artifact.drift.components.runtimeScore) : chalk7.dim("n/a")}`);
3682
- lines.push(` Frameworks: ${m.has("framework") ? scoreBar(artifact.drift.components.frameworkScore) : chalk7.dim("n/a")}`);
3683
- lines.push(` Dependencies: ${m.has("dependency") ? scoreBar(artifact.drift.components.dependencyScore) : chalk7.dim("n/a")}`);
3684
- lines.push(` EOL Risk: ${m.has("eol") ? scoreBar(artifact.drift.components.eolScore) : chalk7.dim("n/a")}`);
4197
+ lines.push(" " + chalk8.bold.underline("Score Breakdown"));
4198
+ lines.push(` Runtime: ${m.has("runtime") ? scoreBar(artifact.drift.components.runtimeScore) : chalk8.dim("n/a")}`);
4199
+ lines.push(` Frameworks: ${m.has("framework") ? scoreBar(artifact.drift.components.frameworkScore) : chalk8.dim("n/a")}`);
4200
+ lines.push(` Dependencies: ${m.has("dependency") ? scoreBar(artifact.drift.components.dependencyScore) : chalk8.dim("n/a")}`);
4201
+ lines.push(` EOL Risk: ${m.has("eol") ? scoreBar(artifact.drift.components.eolScore) : chalk8.dim("n/a")}`);
3685
4202
  lines.push("");
3686
4203
  const scannedParts = [`Scanned at ${artifact.timestamp}`];
3687
4204
  if (artifact.durationMs !== void 0) {
@@ -3695,18 +4212,18 @@ function formatText(artifact) {
3695
4212
  scannedParts.push(`${artifact.treeSummary.totalFiles.toLocaleString()} workspace files`);
3696
4213
  scannedParts.push(`${artifact.treeSummary.totalDirs.toLocaleString()} dirs`);
3697
4214
  }
3698
- lines.push(chalk7.dim(` ${scannedParts.join(" \xB7 ")}`));
4215
+ lines.push(chalk8.dim(` ${scannedParts.join(" \xB7 ")}`));
3699
4216
  lines.push("");
3700
4217
  return lines.join("\n");
3701
4218
  }
3702
4219
  function riskBadge(level) {
3703
4220
  switch (level) {
3704
4221
  case "low":
3705
- return chalk7.bgGreen.black(" LOW ");
4222
+ return chalk8.bgGreen.black(" LOW ");
3706
4223
  case "moderate":
3707
- return chalk7.bgYellow.black(" MODERATE ");
4224
+ return chalk8.bgYellow.black(" MODERATE ");
3708
4225
  case "high":
3709
- return chalk7.bgRed.white(" HIGH ");
4226
+ return chalk8.bgRed.white(" HIGH ");
3710
4227
  default:
3711
4228
  return level;
3712
4229
  }
@@ -3741,11 +4258,11 @@ function formatExtended(ext) {
3741
4258
  const inv = ext.toolingInventory;
3742
4259
  const categories = Object.entries(inv).filter(([, items]) => items.length > 0);
3743
4260
  if (categories.length > 0) {
3744
- lines.push(chalk7.bold.underline(" Tech Stack"));
4261
+ lines.push(chalk8.bold.underline(" Tech Stack"));
3745
4262
  for (const [cat, items] of categories) {
3746
4263
  const label = CATEGORY_LABELS[cat] ?? cat;
3747
- const names = items.map((i) => chalk7.white(i.name)).join(chalk7.dim(", "));
3748
- lines.push(` ${chalk7.cyan(label)}: ${names}`);
4264
+ const names = items.map((i) => chalk8.white(i.name)).join(chalk8.dim(", "));
4265
+ lines.push(` ${chalk8.cyan(label)}: ${names}`);
3749
4266
  }
3750
4267
  lines.push("");
3751
4268
  }
@@ -3754,14 +4271,14 @@ function formatExtended(ext) {
3754
4271
  const svc = ext.serviceDependencies;
3755
4272
  const categories = Object.entries(svc).filter(([, items]) => items.length > 0);
3756
4273
  if (categories.length > 0) {
3757
- lines.push(chalk7.bold.underline(" Services & Integrations"));
4274
+ lines.push(chalk8.bold.underline(" Services & Integrations"));
3758
4275
  for (const [cat, items] of categories) {
3759
4276
  const label = CATEGORY_LABELS[cat] ?? cat;
3760
4277
  const names = items.map((i) => {
3761
- const ver = i.version ? chalk7.dim(` ${i.version}`) : "";
3762
- return chalk7.white(i.name) + ver;
3763
- }).join(chalk7.dim(", "));
3764
- lines.push(` ${chalk7.cyan(label)}: ${names}`);
4278
+ const ver = i.version ? chalk8.dim(` ${i.version}`) : "";
4279
+ return chalk8.white(i.name) + ver;
4280
+ }).join(chalk8.dim(", "));
4281
+ lines.push(` ${chalk8.cyan(label)}: ${names}`);
3765
4282
  }
3766
4283
  lines.push("");
3767
4284
  }
@@ -3769,19 +4286,19 @@ function formatExtended(ext) {
3769
4286
  if (ext.breakingChangeExposure) {
3770
4287
  const bc = ext.breakingChangeExposure;
3771
4288
  if (bc.deprecatedPackages.length > 0 || bc.legacyPolyfills.length > 0) {
3772
- lines.push(chalk7.bold.underline(" Breaking Change Exposure"));
3773
- const exposureColor = bc.exposureScore >= 40 ? chalk7.red : bc.exposureScore >= 20 ? chalk7.yellow : chalk7.green;
4289
+ lines.push(chalk8.bold.underline(" Breaking Change Exposure"));
4290
+ const exposureColor = bc.exposureScore >= 40 ? chalk8.red : bc.exposureScore >= 20 ? chalk8.yellow : chalk8.green;
3774
4291
  lines.push(` Exposure Score: ${exposureColor.bold(`${bc.exposureScore}/100`)}`);
3775
4292
  if (bc.deprecatedPackages.length > 0) {
3776
- lines.push(` ${chalk7.red("Deprecated")}: ${bc.deprecatedPackages.map((p) => chalk7.dim(p)).join(", ")}`);
4293
+ lines.push(` ${chalk8.red("Deprecated")}: ${bc.deprecatedPackages.map((p) => chalk8.dim(p)).join(", ")}`);
3777
4294
  }
3778
4295
  if (bc.legacyPolyfills.length > 0) {
3779
- lines.push(` ${chalk7.yellow("Polyfills")}: ${bc.legacyPolyfills.map((p) => chalk7.dim(p)).join(", ")}`);
4296
+ lines.push(` ${chalk8.yellow("Polyfills")}: ${bc.legacyPolyfills.map((p) => chalk8.dim(p)).join(", ")}`);
3780
4297
  }
3781
4298
  if (bc.peerConflictsDetected) {
3782
- lines.push(` ${chalk7.red("\u26A0")} Peer dependency conflicts detected`);
4299
+ lines.push(` ${chalk8.red("\u26A0")} Peer dependency conflicts detected`);
3783
4300
  }
3784
- lines.push(` Recommendation: ${chalk7.bold(bc.overallRecommendation)}`);
4301
+ lines.push(` Recommendation: ${chalk8.bold(bc.overallRecommendation)}`);
3785
4302
  const projectsWithPlans = bc.projectIntelligence.filter((p) => p.packages.length > 0).slice(0, 3);
3786
4303
  if (projectsWithPlans.length > 0) {
3787
4304
  lines.push(" Major Upgrade Intelligence:");
@@ -3797,21 +4314,21 @@ function formatExtended(ext) {
3797
4314
  }
3798
4315
  if (ext.tsModernity && ext.tsModernity.typescriptVersion) {
3799
4316
  const ts = ext.tsModernity;
3800
- lines.push(chalk7.bold.underline(" TypeScript"));
4317
+ lines.push(chalk8.bold.underline(" TypeScript"));
3801
4318
  const parts = [];
3802
4319
  parts.push(`v${ts.typescriptVersion}`);
3803
- if (ts.strict === true) parts.push(chalk7.green("strict \u2714"));
3804
- else if (ts.strict === false) parts.push(chalk7.yellow("strict \u2716"));
4320
+ if (ts.strict === true) parts.push(chalk8.green("strict \u2714"));
4321
+ else if (ts.strict === false) parts.push(chalk8.yellow("strict \u2716"));
3805
4322
  if (ts.moduleType) parts.push(ts.moduleType.toUpperCase());
3806
4323
  if (ts.target) parts.push(`target: ${ts.target}`);
3807
- lines.push(` ${parts.join(chalk7.dim(" \xB7 "))}`);
4324
+ lines.push(` ${parts.join(chalk8.dim(" \xB7 "))}`);
3808
4325
  lines.push("");
3809
4326
  }
3810
4327
  if (ext.buildDeploy) {
3811
4328
  const bd = ext.buildDeploy;
3812
4329
  const hasSomething = bd.ci.length > 0 || bd.docker.dockerfileCount > 0 || bd.packageManagers.length > 0;
3813
4330
  if (hasSomething) {
3814
- lines.push(chalk7.bold.underline(" Build & Deploy"));
4331
+ lines.push(chalk8.bold.underline(" Build & Deploy"));
3815
4332
  if (bd.ci.length > 0) lines.push(` CI: ${bd.ci.join(", ")}`);
3816
4333
  if (bd.docker.dockerfileCount > 0) {
3817
4334
  lines.push(` Docker: ${bd.docker.dockerfileCount} Dockerfile${bd.docker.dockerfileCount !== 1 ? "s" : ""} (${bd.docker.baseImages.join(", ")})`);
@@ -3824,42 +4341,42 @@ function formatExtended(ext) {
3824
4341
  }
3825
4342
  if (ext.uiPurpose) {
3826
4343
  const up = ext.uiPurpose;
3827
- lines.push(chalk7.bold.underline(" Product Purpose Signals"));
3828
- lines.push(` Frameworks: ${up.detectedFrameworks.length > 0 ? up.detectedFrameworks.join(", ") : chalk7.dim("unknown")}`);
3829
- lines.push(` Evidence: ${up.topEvidence.length}${up.capped ? chalk7.dim(` of ${up.evidenceCount} (capped)`) : ""}`);
4344
+ lines.push(chalk8.bold.underline(" Product Purpose Signals"));
4345
+ lines.push(` Frameworks: ${up.detectedFrameworks.length > 0 ? up.detectedFrameworks.join(", ") : chalk8.dim("unknown")}`);
4346
+ lines.push(` Evidence: ${up.topEvidence.length}${up.capped ? chalk8.dim(` of ${up.evidenceCount} (capped)`) : ""}`);
3830
4347
  const top = up.topEvidence.slice(0, 8);
3831
4348
  if (top.length > 0) {
3832
4349
  lines.push(" Top Signals:");
3833
4350
  for (const item of top) {
3834
- lines.push(` - [${item.kind}] ${item.value} ${chalk7.dim(`(${item.file})`)}`);
4351
+ lines.push(` - [${item.kind}] ${item.value} ${chalk8.dim(`(${item.file})`)}`);
3835
4352
  }
3836
4353
  }
3837
4354
  if (up.unknownSignals.length > 0) {
3838
4355
  lines.push(" Unknowns:");
3839
4356
  for (const u of up.unknownSignals.slice(0, 4)) {
3840
- lines.push(` - ${chalk7.yellow(u)}`);
4357
+ lines.push(` - ${chalk8.yellow(u)}`);
3841
4358
  }
3842
4359
  }
3843
4360
  lines.push("");
3844
4361
  }
3845
4362
  if (ext.securityPosture) {
3846
4363
  const sec = ext.securityPosture;
3847
- lines.push(chalk7.bold.underline(" Security Posture"));
4364
+ lines.push(chalk8.bold.underline(" Security Posture"));
3848
4365
  const checks = [];
3849
- checks.push(sec.lockfilePresent ? chalk7.green("Lockfile \u2714") : chalk7.red("Lockfile \u2716"));
3850
- checks.push(sec.gitignoreCoversEnv ? chalk7.green(".env \u2714") : chalk7.red(".env \u2716"));
3851
- checks.push(sec.gitignoreCoversNodeModules ? chalk7.green("node_modules \u2714") : chalk7.yellow("node_modules \u2716"));
3852
- if (sec.multipleLockfileTypes) checks.push(chalk7.yellow("Multiple lockfiles \u26A0"));
3853
- if (sec.envFilesTracked) checks.push(chalk7.red("Env files tracked \u2716"));
3854
- lines.push(` ${checks.join(chalk7.dim(" \xB7 "))}`);
4366
+ checks.push(sec.lockfilePresent ? chalk8.green("Lockfile \u2714") : chalk8.red("Lockfile \u2716"));
4367
+ checks.push(sec.gitignoreCoversEnv ? chalk8.green(".env \u2714") : chalk8.red(".env \u2716"));
4368
+ checks.push(sec.gitignoreCoversNodeModules ? chalk8.green("node_modules \u2714") : chalk8.yellow("node_modules \u2716"));
4369
+ if (sec.multipleLockfileTypes) checks.push(chalk8.yellow("Multiple lockfiles \u26A0"));
4370
+ if (sec.envFilesTracked) checks.push(chalk8.red("Env files tracked \u2716"));
4371
+ lines.push(` ${checks.join(chalk8.dim(" \xB7 "))}`);
3855
4372
  lines.push("");
3856
4373
  }
3857
4374
  if (ext.platformMatrix) {
3858
4375
  const pm = ext.platformMatrix;
3859
4376
  if (pm.nativeModules.length > 0 || pm.dockerBaseImages.length > 0) {
3860
- lines.push(chalk7.bold.underline(" Platform"));
4377
+ lines.push(chalk8.bold.underline(" Platform"));
3861
4378
  if (pm.nativeModules.length > 0) {
3862
- lines.push(` Native modules: ${pm.nativeModules.map((m) => chalk7.dim(m)).join(", ")}`);
4379
+ lines.push(` Native modules: ${pm.nativeModules.map((m) => chalk8.dim(m)).join(", ")}`);
3863
4380
  }
3864
4381
  if (pm.osAssumptions.length > 0) {
3865
4382
  lines.push(` OS assumptions: ${pm.osAssumptions.join(", ")}`);
@@ -3869,25 +4386,25 @@ function formatExtended(ext) {
3869
4386
  }
3870
4387
  if (ext.codeQuality) {
3871
4388
  const cq = ext.codeQuality;
3872
- lines.push(chalk7.bold.underline(" Code Quality"));
3873
- lines.push(` Files: ${chalk7.white(`${cq.filesAnalyzed}`)} \xB7 Functions: ${chalk7.white(`${cq.functionsAnalyzed}`)} \xB7 Avg complexity: ${chalk7.white(`${cq.avgCyclomaticComplexity}`)} \xB7 Avg length: ${chalk7.white(`${cq.avgFunctionLength}`)} lines`);
4389
+ lines.push(chalk8.bold.underline(" Code Quality"));
4390
+ lines.push(` Files: ${chalk8.white(`${cq.filesAnalyzed}`)} \xB7 Functions: ${chalk8.white(`${cq.functionsAnalyzed}`)} \xB7 Avg complexity: ${chalk8.white(`${cq.avgCyclomaticComplexity}`)} \xB7 Avg length: ${chalk8.white(`${cq.avgFunctionLength}`)} lines`);
3874
4391
  lines.push(` Max nesting: ${cq.maxNestingDepth} \xB7 Circular deps: ${cq.circularDependencies} \xB7 Dead code: ${cq.deadCodePercent}%`);
3875
4392
  if (cq.godFiles.length > 0) {
3876
4393
  const preview = cq.godFiles.slice(0, 3).map((f) => `${f.path} (${f.lines} lines)`).join(", ");
3877
- lines.push(` ${chalk7.yellow("God files")}: ${preview}`);
4394
+ lines.push(` ${chalk8.yellow("God files")}: ${preview}`);
3878
4395
  }
3879
4396
  lines.push("");
3880
4397
  }
3881
4398
  if (ext.dependencyGraph) {
3882
4399
  const dg = ext.dependencyGraph;
3883
4400
  if (dg.lockfileType) {
3884
- lines.push(chalk7.bold.underline(" Dependency Graph"));
3885
- lines.push(` ${dg.lockfileType}: ${chalk7.white(`${dg.totalUnique}`)} unique, ${chalk7.white(`${dg.totalInstalled}`)} installed`);
4401
+ lines.push(chalk8.bold.underline(" Dependency Graph"));
4402
+ lines.push(` ${dg.lockfileType}: ${chalk8.white(`${dg.totalUnique}`)} unique, ${chalk8.white(`${dg.totalInstalled}`)} installed`);
3886
4403
  if (dg.duplicatedPackages.length > 0) {
3887
- lines.push(` ${chalk7.yellow(`${dg.duplicatedPackages.length} duplicated`)} packages`);
4404
+ lines.push(` ${chalk8.yellow(`${dg.duplicatedPackages.length} duplicated`)} packages`);
3888
4405
  }
3889
4406
  if (dg.phantomDependencies.length > 0) {
3890
- lines.push(` ${chalk7.red(`${dg.phantomDependencies.length} phantom`)} dependencies`);
4407
+ lines.push(` ${chalk8.red(`${dg.phantomDependencies.length} phantom`)} dependencies`);
3891
4408
  }
3892
4409
  lines.push("");
3893
4410
  }
@@ -3898,13 +4415,13 @@ function formatArchitectureDiagram(arch) {
3898
4415
  const lines = [];
3899
4416
  lines.push(...titleBox("Architecture Layers"));
3900
4417
  lines.push("");
3901
- lines.push(chalk7.bold(" Archetype: ") + `${arch.archetype}` + chalk7.dim(` (${Math.round(arch.archetypeConfidence * 100)}% confidence)`));
3902
- lines.push(` Files classified: ${arch.totalClassified}` + (arch.unclassified > 0 ? chalk7.dim(` (${arch.unclassified} unclassified)`) : ""));
4418
+ lines.push(chalk8.bold(" Archetype: ") + `${arch.archetype}` + chalk8.dim(` (${Math.round(arch.archetypeConfidence * 100)}% confidence)`));
4419
+ lines.push(` Files classified: ${arch.totalClassified}` + (arch.unclassified > 0 ? chalk8.dim(` (${arch.unclassified} unclassified)`) : ""));
3903
4420
  lines.push("");
3904
4421
  if (arch.layers.length > 0) {
3905
4422
  for (const layer of arch.layers) {
3906
- const risk = layer.riskLevel === "none" ? chalk7.dim("none") : layer.riskLevel === "low" ? chalk7.green("low") : layer.riskLevel === "moderate" ? chalk7.yellow("moderate") : chalk7.red("high");
3907
- lines.push(` ${chalk7.bold(layer.layer)} ${layer.fileCount} file${layer.fileCount !== 1 ? "s" : ""} drift ${scoreBar(layer.driftScore)} risk ${risk}`);
4423
+ const risk = layer.riskLevel === "none" ? chalk8.dim("none") : layer.riskLevel === "low" ? chalk8.green("low") : layer.riskLevel === "moderate" ? chalk8.yellow("moderate") : chalk8.red("high");
4424
+ lines.push(` ${chalk8.bold(layer.layer)} ${layer.fileCount} file${layer.fileCount !== 1 ? "s" : ""} drift ${scoreBar(layer.driftScore)} risk ${risk}`);
3908
4425
  }
3909
4426
  lines.push("");
3910
4427
  }
@@ -4275,8 +4792,8 @@ function formatMarkdown(artifact) {
4275
4792
  var reportCommand = new Command("report").description("Generate a drift report from a scan artifact").option("--in <file>", "Input artifact file", ".vibgrate/scan_result.json").option("--format <format>", "Output format (md|text|json)", "text").action(async (opts) => {
4276
4793
  const artifactPath = path.resolve(opts.in);
4277
4794
  if (!await pathExists(artifactPath)) {
4278
- console.error(chalk7.red(`Artifact not found: ${artifactPath}`));
4279
- console.error(chalk7.dim('Run "vibgrate scan" first to generate a scan artifact.'));
4795
+ console.error(chalk8.red(`Artifact not found: ${artifactPath}`));
4796
+ console.error(chalk8.dim('Run "vibgrate scan" first to generate a scan artifact.'));
4280
4797
  process.exit(1);
4281
4798
  }
4282
4799
  const artifact = await readJsonFile(artifactPath);
@@ -4332,7 +4849,7 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
4332
4849
  try {
4333
4850
  ingestHost = resolveIngestHost(opts.region, opts.ingest);
4334
4851
  } catch (e) {
4335
- console.error(chalk7.red(e instanceof Error ? e.message : String(e)));
4852
+ console.error(chalk8.red(e instanceof Error ? e.message : String(e)));
4336
4853
  process.exit(1);
4337
4854
  }
4338
4855
  const base = `https://${ingestHost}/v1/auth/device`;
@@ -4344,28 +4861,28 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
4344
4861
  body: "{}"
4345
4862
  });
4346
4863
  if (!res.ok) {
4347
- console.error(chalk7.red(`Failed to start login (HTTP ${res.status}).`));
4864
+ console.error(chalk8.red(`Failed to start login (HTTP ${res.status}).`));
4348
4865
  process.exit(1);
4349
4866
  }
4350
4867
  start = await res.json();
4351
4868
  } catch (e) {
4352
- console.error(chalk7.red(`Could not reach ${ingestHost}: ${e instanceof Error ? e.message : String(e)}`));
4869
+ console.error(chalk8.red(`Could not reach ${ingestHost}: ${e instanceof Error ? e.message : String(e)}`));
4353
4870
  process.exit(1);
4354
4871
  }
4355
4872
  console.log("");
4356
4873
  console.log("To finish signing in, open this URL and approve the request:");
4357
4874
  console.log("");
4358
- console.log(" " + chalk7.cyan(start.verificationUri));
4875
+ console.log(" " + chalk8.cyan(start.verificationUri));
4359
4876
  console.log("");
4360
- console.log(" Your code: " + chalk7.bold(start.userCode));
4877
+ console.log(" Your code: " + chalk8.bold(start.userCode));
4361
4878
  console.log("");
4362
4879
  if (opts.browser) {
4363
4880
  const opened = openUrl(start.verificationUriComplete);
4364
4881
  if (opened) {
4365
- console.log(chalk7.dim("Opening your browser\u2026 (if it does not open, use the URL above)"));
4882
+ console.log(chalk8.dim("Opening your browser\u2026 (if it does not open, use the URL above)"));
4366
4883
  }
4367
4884
  }
4368
- console.log(chalk7.dim("Waiting for approval\u2026"));
4885
+ console.log(chalk8.dim("Waiting for approval\u2026"));
4369
4886
  const intervalMs = Math.max(2, start.interval || 5) * 1e3;
4370
4887
  const deadline = Date.now() + (start.expiresIn || 900) * 1e3;
4371
4888
  while (Date.now() < deadline) {
@@ -4393,7 +4910,7 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
4393
4910
  savedAt: (/* @__PURE__ */ new Date()).toISOString()
4394
4911
  };
4395
4912
  } else {
4396
- console.log(chalk7.dim("Setting up your workspace\u2026"));
4913
+ console.log(chalk8.dim("Setting up your workspace\u2026"));
4397
4914
  try {
4398
4915
  const provisioned = await createWorkspaceDsn({
4399
4916
  region: opts.region,
@@ -4408,10 +4925,10 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
4408
4925
  };
4409
4926
  } catch (e) {
4410
4927
  console.error(
4411
- chalk7.red("\u2716 Signed in, but workspace setup failed: ") + (e instanceof Error ? e.message : String(e))
4928
+ chalk8.red("\u2716 Signed in, but workspace setup failed: ") + (e instanceof Error ? e.message : String(e))
4412
4929
  );
4413
4930
  console.error(
4414
- chalk7.dim(' Finish setup with "vibgrate dsn create --workspace new".')
4931
+ chalk8.dim(' Finish setup with "vibgrate dsn create --workspace new".')
4415
4932
  );
4416
4933
  process.exit(1);
4417
4934
  }
@@ -4420,40 +4937,40 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
4420
4937
  writeStoredCredentials(creds, storeOpts);
4421
4938
  const credsFile = credentialsPath(storeOpts);
4422
4939
  console.log("");
4423
- console.log(chalk7.green("\u2714") + " Logged in.");
4940
+ console.log(chalk8.green("\u2714") + " Logged in.");
4424
4941
  if (creds.workspaceId) {
4425
- console.log(" Workspace: " + chalk7.bold(creds.workspaceId));
4942
+ console.log(" Workspace: " + chalk8.bold(creds.workspaceId));
4426
4943
  }
4427
- console.log(chalk7.dim(` Credentials saved to ${credsFile}`));
4944
+ console.log(chalk8.dim(` Credentials saved to ${credsFile}`));
4428
4945
  try {
4429
4946
  const root = findGitRoot();
4430
4947
  if (root && isInside(root, credsFile)) {
4431
4948
  const res = ensureGitignored(gitignoreEntryForCredentials(root, credsFile), root);
4432
4949
  if (res.status === "created") {
4433
- console.log(chalk7.dim(` Created .gitignore and ignored ${res.entry}`));
4950
+ console.log(chalk8.dim(` Created .gitignore and ignored ${res.entry}`));
4434
4951
  } else if (res.status === "added") {
4435
- console.log(chalk7.dim(` Added ${res.entry} to .gitignore`));
4952
+ console.log(chalk8.dim(` Added ${res.entry} to .gitignore`));
4436
4953
  }
4437
4954
  }
4438
4955
  } catch {
4439
4956
  }
4440
- console.log(chalk7.dim(` You can now run "${resolveCliInvocation()} scan --push".`));
4957
+ console.log(chalk8.dim(` You can now run "${resolveCliInvocation()} scan --push".`));
4441
4958
  return;
4442
4959
  }
4443
4960
  if (token.status === "access_denied") {
4444
- console.error(chalk7.red("\u2716 Login was denied in the browser."));
4961
+ console.error(chalk8.red("\u2716 Login was denied in the browser."));
4445
4962
  process.exit(1);
4446
4963
  }
4447
4964
  if (token.status === "expired" || token.status === "invalid") {
4448
- console.error(chalk7.red('\u2716 Login request expired. Run "vibgrate login" again.'));
4965
+ console.error(chalk8.red('\u2716 Login request expired. Run "vibgrate login" again.'));
4449
4966
  process.exit(1);
4450
4967
  }
4451
4968
  if (token.status === "error") {
4452
- console.error(chalk7.red(`\u2716 ${token.error ?? "Login failed."}`));
4969
+ console.error(chalk8.red(`\u2716 ${token.error ?? "Login failed."}`));
4453
4970
  process.exit(1);
4454
4971
  }
4455
4972
  }
4456
- console.error(chalk7.red('\u2716 Timed out waiting for approval. Run "vibgrate login" again.'));
4973
+ console.error(chalk8.red('\u2716 Timed out waiting for approval. Run "vibgrate login" again.'));
4457
4974
  process.exit(1);
4458
4975
  });
4459
4976
 
@@ -4474,190 +4991,20 @@ function unsetEnvCommand(varName, env = {}) {
4474
4991
  var logoutCommand = new Command("logout").description("Clear stored Vibgrate login credentials").action(() => {
4475
4992
  const cleared = clearStoredCredentials();
4476
4993
  if (cleared) {
4477
- console.log(chalk7.green("\u2714") + " Logged out. Stored credentials removed.");
4994
+ console.log(chalk8.green("\u2714") + " Logged out. Stored credentials removed.");
4478
4995
  } else {
4479
- console.log(chalk7.dim(`No stored credentials found at ${credentialsPath()}.`));
4996
+ console.log(chalk8.dim(`No stored credentials found at ${credentialsPath()}.`));
4480
4997
  }
4481
4998
  if (process.env.VIBGRATE_DSN) {
4482
4999
  console.log("");
4483
5000
  console.log(
4484
- chalk7.yellow("\u26A0") + " VIBGRATE_DSN is still set in your environment, so commands will keep using it."
5001
+ chalk8.yellow("\u26A0") + " VIBGRATE_DSN is still set in your environment, so commands will keep using it."
4485
5002
  );
4486
5003
  console.log(
4487
- chalk7.dim(` Unset it to fully sign out: ${unsetEnvCommand("VIBGRATE_DSN")}`)
5004
+ chalk8.dim(` Unset it to fully sign out: ${unsetEnvCommand("VIBGRATE_DSN")}`)
4488
5005
  );
4489
5006
  }
4490
5007
  });
4491
- var REGISTRY_URL = "https://registry.npmjs.org/@vibgrate%2fcli/latest";
4492
- var CACHE_DIR = path.join(os.homedir(), ".vibgrate");
4493
- var CACHE_FILE = path.join(CACHE_DIR, "update-check.json");
4494
- async function fetchLatestVersion() {
4495
- try {
4496
- const controller = new AbortController();
4497
- const timeout = setTimeout(() => controller.abort(), 1e4);
4498
- timeout.unref?.();
4499
- let response;
4500
- try {
4501
- response = await fetch(REGISTRY_URL, {
4502
- headers: { Accept: "application/json" },
4503
- signal: controller.signal
4504
- });
4505
- } finally {
4506
- clearTimeout(timeout);
4507
- }
4508
- if (!response.ok) return null;
4509
- const data = await response.json();
4510
- const latest = data.version;
4511
- if (!latest || !semver.valid(latest)) return null;
4512
- await writeCache({ latest, checkedAt: Date.now() });
4513
- return latest;
4514
- } catch {
4515
- return null;
4516
- }
4517
- }
4518
- async function writeCache(data) {
4519
- try {
4520
- await fs14.mkdir(CACHE_DIR, { recursive: true });
4521
- await fs14.writeFile(CACHE_FILE, JSON.stringify(data), "utf-8");
4522
- } catch {
4523
- }
4524
- }
4525
-
4526
- // src/reporting/commands/update.ts
4527
- function detectGlobalInstall() {
4528
- const execPath = process.argv[1] || "";
4529
- if (execPath.includes("/lib/node_modules/") || execPath.includes("\\node_modules\\")) {
4530
- if (!execPath.includes(process.cwd())) {
4531
- if (execPath.includes("pnpm")) return "pnpm";
4532
- if (execPath.includes("yarn")) return "yarn";
4533
- if (execPath.includes("bun")) return "bun";
4534
- return "npm";
4535
- }
4536
- }
4537
- return null;
4538
- }
4539
- function getGlobalUpdateCommand(pm, pkg, version) {
4540
- const spec = `${pkg}@${version}`;
4541
- switch (pm) {
4542
- case "pnpm":
4543
- return `pnpm add -g ${spec}`;
4544
- case "yarn":
4545
- return `yarn global add ${spec}`;
4546
- case "bun":
4547
- return `bun add -g ${spec}`;
4548
- case "npm":
4549
- default:
4550
- return `npm install -g ${spec}`;
4551
- }
4552
- }
4553
- async function detectPackageManager(cwd) {
4554
- if (await pathExists(path.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
4555
- if (await pathExists(path.join(cwd, "bun.lockb"))) return "bun";
4556
- if (await pathExists(path.join(cwd, "yarn.lock"))) return "yarn";
4557
- return "npm";
4558
- }
4559
- async function detectWorkspaceRoot(cwd) {
4560
- if (await pathExists(path.join(cwd, "pnpm-workspace.yaml"))) return true;
4561
- try {
4562
- const pkgPath = path.join(cwd, "package.json");
4563
- const raw = await (await import('fs/promises')).readFile(pkgPath, "utf-8");
4564
- const pkg = JSON.parse(raw);
4565
- return pkg.workspaces != null;
4566
- } catch {
4567
- return false;
4568
- }
4569
- }
4570
- function getInstallCommand(pm, pkg, version, isDev, opts = {}) {
4571
- const spec = `${pkg}@${version}`;
4572
- const ws = opts.workspaceRoot && pm === "pnpm" ? " -w" : "";
4573
- switch (pm) {
4574
- case "pnpm":
4575
- return isDev ? `pnpm add${ws} -D ${spec}` : `pnpm add${ws} ${spec}`;
4576
- case "yarn":
4577
- return isDev ? `yarn add --dev ${spec}` : `yarn add ${spec}`;
4578
- case "bun":
4579
- return isDev ? `bun add -d ${spec}` : `bun add ${spec}`;
4580
- case "npm":
4581
- default:
4582
- return isDev ? `npm install --save-dev ${spec}` : `npm install ${spec}`;
4583
- }
4584
- }
4585
- async function isDevDependency(cwd) {
4586
- try {
4587
- const pkgPath = path.join(cwd, "package.json");
4588
- const raw = await (await import('fs/promises')).readFile(pkgPath, "utf-8");
4589
- const pkg = JSON.parse(raw);
4590
- return Boolean(pkg.devDependencies?.["@vibgrate/cli"]);
4591
- } catch {
4592
- return true;
4593
- }
4594
- }
4595
- async function confirmWorkspaceRoot(pm) {
4596
- if (!(process.stdin.isTTY && process.stdout.isTTY)) return false;
4597
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
4598
- return new Promise((resolve16) => {
4599
- rl.question(chalk7.yellow(`Update the ${pm} workspace root with -w? [y/N]: `), (ans) => {
4600
- rl.close();
4601
- const t = ans.trim().toLowerCase();
4602
- resolve16(t === "y" || t === "yes");
4603
- });
4604
- });
4605
- }
4606
- var updateCommand = new Command("update").description("Update vibgrate to the latest version").option("--check", "Only check for updates, do not install").option("--pm <manager>", "Package manager to use (npm, pnpm, yarn, bun)").option("--global", "Update global installation").option("-y, --yes", "Skip confirmation prompts (e.g. installing at a workspace root)").option("-w, --workspace-root", "Allow updating the pnpm workspace root (implies --yes for that prompt)").action(
4607
- async (opts) => {
4608
- console.log(chalk7.dim(`Current version: ${VERSION}`));
4609
- console.log(chalk7.dim("Checking npm registry..."));
4610
- const latest = await fetchLatestVersion();
4611
- if (!latest) {
4612
- console.error(chalk7.red("Could not reach the npm registry. Check your network connection."));
4613
- process.exit(1);
4614
- }
4615
- const semver2 = await import('semver');
4616
- if (!semver2.gt(latest, VERSION)) {
4617
- console.log(chalk7.green("\u2714") + ` You are on the latest version (${VERSION}).`);
4618
- return;
4619
- }
4620
- console.log(chalk7.yellow(`Update available: ${VERSION} \u2192 ${latest}`));
4621
- if (opts.check) {
4622
- console.log(chalk7.dim('Run "vg update" to install.'));
4623
- return;
4624
- }
4625
- const cwd = process.cwd();
4626
- const globalPm = detectGlobalInstall();
4627
- const isGlobal = opts.global || globalPm !== null;
4628
- const pm = opts.pm || (globalPm ?? await detectPackageManager(cwd));
4629
- let cmd;
4630
- if (isGlobal) {
4631
- cmd = getGlobalUpdateCommand(pm, "@vibgrate/cli", latest);
4632
- console.log(chalk7.dim(`Updating global installation with ${pm}: ${cmd}`));
4633
- } else {
4634
- const isDev = await isDevDependency(cwd);
4635
- let workspaceRoot = false;
4636
- if (pm === "pnpm" && await detectWorkspaceRoot(cwd)) {
4637
- console.log(
4638
- chalk7.yellow("Detected a pnpm workspace root.") + chalk7.dim(" Installing here needs the -w flag and updates the root package.json.")
4639
- );
4640
- const proceed = opts.yes || opts.workspaceRoot || await confirmWorkspaceRoot(pm);
4641
- if (!proceed) {
4642
- const rootCmd = getInstallCommand(pm, "@vibgrate/cli", latest, isDev, { workspaceRoot: true });
4643
- console.log(chalk7.dim(`Skipped. To update the workspace root, run: ${rootCmd}`));
4644
- console.log(chalk7.dim('or re-run "vg update --yes" (or --workspace-root) to let vg do it.'));
4645
- return;
4646
- }
4647
- workspaceRoot = true;
4648
- }
4649
- cmd = getInstallCommand(pm, "@vibgrate/cli", latest, isDev, { workspaceRoot });
4650
- console.log(chalk7.dim(`Using ${pm}: ${cmd}`));
4651
- }
4652
- try {
4653
- execSync(cmd, { cwd, stdio: "inherit" });
4654
- console.log(chalk7.green("\u2714") + ` Updated to @vibgrate/cli@${latest}`);
4655
- } catch {
4656
- console.error(chalk7.red(`Update failed. Run manually: ${cmd}`));
4657
- process.exit(1);
4658
- }
4659
- }
4660
- );
4661
5008
  var OPENVEX_CONTEXT = "https://openvex.dev/ns/v0.2.0";
4662
5009
  var VEX_STATUSES = ["not_affected", "affected", "fixed", "under_investigation"];
4663
5010
  var VEX_JUSTIFICATIONS = [
@@ -4749,7 +5096,7 @@ var vexCommand = new Command("vex").description("Generate an OpenVEX document (e
4749
5096
  if (opts.from) {
4750
5097
  const fromPath = path.resolve(opts.from);
4751
5098
  if (!await pathExists(fromPath)) {
4752
- process.stderr.write(chalk7.red(`Statements file not found: ${fromPath}
5099
+ process.stderr.write(chalk8.red(`Statements file not found: ${fromPath}
4753
5100
  `));
4754
5101
  process.exit(1);
4755
5102
  }
@@ -4767,7 +5114,7 @@ var vexCommand = new Command("vex").description("Generate an OpenVEX document (e
4767
5114
  if (opts.out) {
4768
5115
  await writeTextFile(path.resolve(opts.out), json2 + "\n");
4769
5116
  process.stderr.write(
4770
- chalk7.green("\u2714") + ` Wrote OpenVEX document (${doc.statements.length} statement(s)) to ${path.resolve(opts.out)}
5117
+ chalk8.green("\u2714") + ` Wrote OpenVEX document (${doc.statements.length} statement(s)) to ${path.resolve(opts.out)}
4771
5118
  `
4772
5119
  );
4773
5120
  } else {
@@ -4775,7 +5122,7 @@ var vexCommand = new Command("vex").description("Generate an OpenVEX document (e
4775
5122
  }
4776
5123
  } catch (err) {
4777
5124
  if (err instanceof VexValidationError) {
4778
- process.stderr.write(chalk7.red(`VEX validation error: ${err.message}
5125
+ process.stderr.write(chalk8.red(`VEX validation error: ${err.message}
4779
5126
  `));
4780
5127
  process.exit(1);
4781
5128
  }
@@ -4952,7 +5299,7 @@ function formatDeltaText(base, current) {
4952
5299
  async function readArtifactOrExit(filePath) {
4953
5300
  const absolutePath = path.resolve(filePath);
4954
5301
  if (!await pathExists(absolutePath)) {
4955
- console.error(chalk7.red(`Artifact not found: ${absolutePath}`));
5302
+ console.error(chalk8.red(`Artifact not found: ${absolutePath}`));
4956
5303
  process.exit(1);
4957
5304
  }
4958
5305
  return readJsonFile(absolutePath);
@@ -4961,14 +5308,14 @@ var exportCommand = new Command("export").description("Export scan artifact as S
4961
5308
  const artifact = await readArtifactOrExit(opts.in);
4962
5309
  const format = opts.format.toLowerCase();
4963
5310
  if (format !== "cyclonedx" && format !== "spdx") {
4964
- console.error(chalk7.red("Invalid SBOM format. Use cyclonedx or spdx."));
5311
+ console.error(chalk8.red("Invalid SBOM format. Use cyclonedx or spdx."));
4965
5312
  process.exit(1);
4966
5313
  }
4967
5314
  const sbom = format === "cyclonedx" ? toCycloneDx(artifact) : toSpdx(artifact);
4968
5315
  const body = JSON.stringify(sbom, null, 2);
4969
5316
  if (opts.out) {
4970
5317
  await writeTextFile(path.resolve(opts.out), body);
4971
- console.log(chalk7.green("\u2714") + ` SBOM written to ${opts.out}`);
5318
+ console.log(chalk8.green("\u2714") + ` SBOM written to ${opts.out}`);
4972
5319
  } else {
4973
5320
  console.log(body);
4974
5321
  }
@@ -4979,7 +5326,7 @@ var deltaCommand = new Command("delta").description("Show SBOM delta between two
4979
5326
  const report = formatDeltaText(base, current);
4980
5327
  if (opts.out) {
4981
5328
  await writeTextFile(path.resolve(opts.out), report);
4982
- console.log(chalk7.green("\u2714") + ` SBOM delta report written to ${opts.out}`);
5329
+ console.log(chalk8.green("\u2714") + ` SBOM delta report written to ${opts.out}`);
4983
5330
  } else {
4984
5331
  console.log(report);
4985
5332
  }
@@ -5135,7 +5482,7 @@ function dispatch(argv, cwd) {
5135
5482
  }
5136
5483
  const looksLikeQuestion = /\s/.test(first) || first.endsWith("?");
5137
5484
  if (looksLikeQuestion) return ["ask", ...args];
5138
- if (fs5.existsSync(path.resolve(cwd, first))) {
5485
+ if (fs6.existsSync(path.resolve(cwd, first))) {
5139
5486
  return [hasBuildOnlyFlag(args) ? "build" : "scan", ...args];
5140
5487
  }
5141
5488
  const moved = MOVED_COMMANDS[first];