@vibgrate/cli 2026.615.3 → 2026.617.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
@@ -6,7 +6,7 @@ import {
6
6
  pathExists,
7
7
  readJsonFile,
8
8
  writeTextFile
9
- } from "./chunk-7343XMW2.js";
9
+ } from "./chunk-LWB7JBWP.js";
10
10
  import {
11
11
  computeRepoFingerprint,
12
12
  detectVcs,
@@ -16,7 +16,7 @@ import {
16
16
  resolveRepositoryName,
17
17
  runScan,
18
18
  writeDefaultConfig
19
- } from "./chunk-4G4YRE6K.js";
19
+ } from "./chunk-BMLE722B.js";
20
20
  import {
21
21
  require_semver
22
22
  } from "./chunk-5IXVOEZN.js";
@@ -29,8 +29,8 @@ import {
29
29
  } from "./chunk-JSBRDJBE.js";
30
30
 
31
31
  // src/cli.ts
32
- import { Command as Command11 } from "commander";
33
- import chalk12 from "chalk";
32
+ import { Command as Command13 } from "commander";
33
+ import chalk14 from "chalk";
34
34
 
35
35
  // src/commands/init.ts
36
36
  import * as path from "path";
@@ -49,7 +49,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
49
49
  console.log(chalk.green("\u2714") + ` Created ${chalk.bold("vibgrate.config.ts")}`);
50
50
  }
51
51
  if (opts.baseline) {
52
- const { runBaseline } = await import("./baseline-OK4HC3QV.js");
52
+ const { runBaseline } = await import("./baseline-K2GQA6G5.js");
53
53
  await runBaseline(rootDir);
54
54
  }
55
55
  console.log("");
@@ -60,7 +60,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
60
60
  });
61
61
 
62
62
  // src/commands/scan.ts
63
- import * as path3 from "path";
63
+ import * as path4 from "path";
64
64
  import { Command as Command3 } from "commander";
65
65
  import chalk3 from "chalk";
66
66
 
@@ -210,6 +210,49 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
210
210
  }
211
211
  });
212
212
 
213
+ // src/credentials.ts
214
+ import * as os from "os";
215
+ import * as path3 from "path";
216
+ import * as fs from "fs";
217
+ function credentialsDir() {
218
+ return path3.join(os.homedir(), ".vibgrate");
219
+ }
220
+ function credentialsPath() {
221
+ return path3.join(credentialsDir(), "credentials.json");
222
+ }
223
+ function readStoredCredentials() {
224
+ try {
225
+ const raw = fs.readFileSync(credentialsPath(), "utf8");
226
+ const parsed = JSON.parse(raw);
227
+ return parsed && typeof parsed.dsn === "string" && parsed.dsn ? parsed : null;
228
+ } catch {
229
+ return null;
230
+ }
231
+ }
232
+ function writeStoredCredentials(creds) {
233
+ const dir = credentialsDir();
234
+ fs.mkdirSync(dir, { recursive: true });
235
+ const file = credentialsPath();
236
+ fs.writeFileSync(file, JSON.stringify(creds, null, 2) + "\n", "utf8");
237
+ try {
238
+ fs.chmodSync(file, 384);
239
+ } catch {
240
+ }
241
+ }
242
+ function clearStoredCredentials() {
243
+ try {
244
+ fs.rmSync(credentialsPath());
245
+ return true;
246
+ } catch {
247
+ return false;
248
+ }
249
+ }
250
+ function resolveDsn(explicitDsn) {
251
+ if (explicitDsn) return explicitDsn;
252
+ if (process.env.VIBGRATE_DSN) return process.env.VIBGRATE_DSN;
253
+ return readStoredCredentials()?.dsn ?? void 0;
254
+ }
255
+
213
256
  // src/utils/ingest-id-output.ts
214
257
  function emitIngestIdLine(ingestId, options) {
215
258
  console.log(`VIBGRATE_INGEST_ID=${ingestId}`);
@@ -220,10 +263,10 @@ function emitIngestIdLine(ingestId, options) {
220
263
 
221
264
  // src/commands/scan.ts
222
265
  async function autoPush(artifact, rootDir, opts) {
223
- const dsn = opts.dsn || process.env.VIBGRATE_DSN;
266
+ const dsn = resolveDsn(opts.dsn);
224
267
  if (!dsn) {
225
268
  console.error(chalk3.red("No DSN provided for push."));
226
- console.error(chalk3.dim("Set VIBGRATE_DSN environment variable or use --dsn flag."));
269
+ console.error(chalk3.dim('Run "vibgrate login", set VIBGRATE_DSN, or use the --dsn flag.'));
227
270
  if (opts.strict) process.exit(1);
228
271
  return;
229
272
  }
@@ -320,15 +363,15 @@ var scanCommand = new Command3("scan").description("Scan a project for upgrade d
320
363
  collectExcludes,
321
364
  []
322
365
  ).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("--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").action(async (targetPath, opts) => {
323
- const rootDir = path3.resolve(targetPath);
366
+ const rootDir = path4.resolve(targetPath);
324
367
  if (!await pathExists2(rootDir)) {
325
368
  console.error(chalk3.red(`Path does not exist: ${rootDir}`));
326
369
  process.exit(1);
327
370
  }
328
- const hasDsn = !!(opts.dsn || process.env.VIBGRATE_DSN);
371
+ const hasDsn = !!resolveDsn(opts.dsn);
329
372
  const willPush = !opts.offline && (opts.push || hasDsn);
330
373
  if (willPush && hasDsn) {
331
- const dsn = opts.dsn || process.env.VIBGRATE_DSN;
374
+ const dsn = resolveDsn(opts.dsn);
332
375
  const parsed = parseDsn(dsn);
333
376
  if (parsed) {
334
377
  const ingestHost = opts.region ? resolveIngestHost(opts.region) : parsed.host;
@@ -340,6 +383,15 @@ var scanCommand = new Command3("scan").description("Scan a project for upgrade d
340
383
  repositoryName,
341
384
  vcsSha: fingerprint.vcsSha
342
385
  });
386
+ if (preflight.vm && !preflight.vm.allowed) {
387
+ console.error(chalk3.red(preflight.error ?? "VM meter usage exhausted"));
388
+ console.error(
389
+ chalk3.dim(
390
+ `VM minutes: ${preflight.vm.used}/${preflight.vm.limit} (${preflight.plan.label} plan) \u2014 enable overages or upgrade your plan.`
391
+ )
392
+ );
393
+ process.exit(1);
394
+ }
343
395
  if (preflight.status === "error" || !preflight.scans.allowed) {
344
396
  console.error(chalk3.red(preflight.error ?? "Scan ingestion not allowed for this workspace."));
345
397
  console.error(
@@ -440,7 +492,7 @@ Failing fitness function: drift worsened by ${worseningPercent.toFixed(2)}% (thr
440
492
  });
441
493
 
442
494
  // src/commands/report.ts
443
- import * as path4 from "path";
495
+ import * as path5 from "path";
444
496
  import { Command as Command4 } from "commander";
445
497
  import chalk5 from "chalk";
446
498
 
@@ -1157,7 +1209,7 @@ function formatMarkdown(artifact) {
1157
1209
 
1158
1210
  // src/commands/report.ts
1159
1211
  var reportCommand = new Command4("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) => {
1160
- const artifactPath = path4.resolve(opts.in);
1212
+ const artifactPath = path5.resolve(opts.in);
1161
1213
  if (!await pathExists(artifactPath)) {
1162
1214
  console.error(chalk5.red(`Artifact not found: ${artifactPath}`));
1163
1215
  console.error(chalk5.dim('Run "vibgrate scan" first to generate a scan artifact.'));
@@ -1178,12 +1230,146 @@ var reportCommand = new Command4("report").description("Generate a drift report
1178
1230
  }
1179
1231
  });
1180
1232
 
1181
- // src/commands/push.ts
1182
- import * as crypto2 from "crypto";
1183
- import * as path5 from "path";
1233
+ // src/commands/login.ts
1184
1234
  import { Command as Command5 } from "commander";
1185
1235
  import chalk6 from "chalk";
1186
1236
 
1237
+ // src/utils/open-url.ts
1238
+ import { spawn } from "child_process";
1239
+ function openUrl(url) {
1240
+ const platform = process.platform;
1241
+ let command;
1242
+ let args;
1243
+ if (platform === "darwin") {
1244
+ command = "open";
1245
+ args = [url];
1246
+ } else if (platform === "win32") {
1247
+ command = "cmd";
1248
+ args = ["/c", "start", "", url];
1249
+ } else {
1250
+ command = "xdg-open";
1251
+ args = [url];
1252
+ }
1253
+ try {
1254
+ const child = spawn(command, args, { stdio: "ignore", detached: true });
1255
+ child.on("error", () => {
1256
+ });
1257
+ child.unref();
1258
+ return true;
1259
+ } catch {
1260
+ return false;
1261
+ }
1262
+ }
1263
+
1264
+ // src/commands/login.ts
1265
+ var delay = (ms) => new Promise((resolve10) => setTimeout(resolve10, ms));
1266
+ var loginCommand = new Command5("login").description("Authenticate the CLI with your Vibgrate workspace via the browser").option("--ingest <url>", "Ingest API URL (overrides --region)").option("--region <region>", `Data residency region (${availableRegionIds().join(", ")})`, "us").option("--no-browser", "Do not attempt to open a browser automatically").action(async (opts) => {
1267
+ let ingestHost;
1268
+ try {
1269
+ ingestHost = resolveIngestHost(opts.region, opts.ingest);
1270
+ } catch (e) {
1271
+ console.error(chalk6.red(e instanceof Error ? e.message : String(e)));
1272
+ process.exit(1);
1273
+ }
1274
+ const base = `https://${ingestHost}/v1/auth/device`;
1275
+ let start;
1276
+ try {
1277
+ const res = await fetch(`${base}/start`, {
1278
+ method: "POST",
1279
+ headers: { "Content-Type": "application/json", Connection: "close" },
1280
+ body: "{}"
1281
+ });
1282
+ if (!res.ok) {
1283
+ console.error(chalk6.red(`Failed to start login (HTTP ${res.status}).`));
1284
+ process.exit(1);
1285
+ }
1286
+ start = await res.json();
1287
+ } catch (e) {
1288
+ console.error(chalk6.red(`Could not reach ${ingestHost}: ${e instanceof Error ? e.message : String(e)}`));
1289
+ process.exit(1);
1290
+ }
1291
+ console.log("");
1292
+ console.log("To finish signing in, open this URL and approve the request:");
1293
+ console.log("");
1294
+ console.log(" " + chalk6.cyan(start.verificationUri));
1295
+ console.log("");
1296
+ console.log(" Your code: " + chalk6.bold(start.userCode));
1297
+ console.log("");
1298
+ if (opts.browser) {
1299
+ const opened = openUrl(start.verificationUriComplete);
1300
+ if (opened) {
1301
+ console.log(chalk6.dim("Opening your browser\u2026 (if it does not open, use the URL above)"));
1302
+ }
1303
+ }
1304
+ console.log(chalk6.dim("Waiting for approval\u2026"));
1305
+ const intervalMs = Math.max(2, start.interval || 5) * 1e3;
1306
+ const deadline = Date.now() + (start.expiresIn || 900) * 1e3;
1307
+ while (Date.now() < deadline) {
1308
+ await delay(intervalMs);
1309
+ let token;
1310
+ try {
1311
+ const res = await fetch(`${base}/token`, {
1312
+ method: "POST",
1313
+ headers: { "Content-Type": "application/json", Connection: "close" },
1314
+ body: JSON.stringify({ deviceCode: start.deviceCode })
1315
+ });
1316
+ token = await res.json();
1317
+ } catch {
1318
+ continue;
1319
+ }
1320
+ if (token.status === "authorization_pending") continue;
1321
+ if (token.status === "complete" && token.dsn) {
1322
+ writeStoredCredentials({
1323
+ dsn: token.dsn,
1324
+ workspaceId: token.workspaceId,
1325
+ keyId: token.keyId,
1326
+ ingestHost: token.ingestHost ?? ingestHost,
1327
+ savedAt: (/* @__PURE__ */ new Date()).toISOString()
1328
+ });
1329
+ console.log("");
1330
+ console.log(chalk6.green("\u2714") + " Logged in.");
1331
+ if (token.workspaceId) {
1332
+ console.log(" Workspace: " + chalk6.bold(token.workspaceId));
1333
+ }
1334
+ console.log(chalk6.dim(` Credentials saved to ${credentialsPath()}`));
1335
+ console.log(chalk6.dim(' You can now run "vibgrate scan --push".'));
1336
+ return;
1337
+ }
1338
+ if (token.status === "access_denied") {
1339
+ console.error(chalk6.red("\u2716 Login was denied in the browser."));
1340
+ process.exit(1);
1341
+ }
1342
+ if (token.status === "expired" || token.status === "invalid") {
1343
+ console.error(chalk6.red('\u2716 Login request expired. Run "vibgrate login" again.'));
1344
+ process.exit(1);
1345
+ }
1346
+ if (token.status === "error") {
1347
+ console.error(chalk6.red(`\u2716 ${token.error ?? "Login failed."}`));
1348
+ process.exit(1);
1349
+ }
1350
+ }
1351
+ console.error(chalk6.red('\u2716 Timed out waiting for approval. Run "vibgrate login" again.'));
1352
+ process.exit(1);
1353
+ });
1354
+
1355
+ // src/commands/logout.ts
1356
+ import { Command as Command6 } from "commander";
1357
+ import chalk7 from "chalk";
1358
+ var logoutCommand = new Command6("logout").description("Clear stored Vibgrate login credentials").action(() => {
1359
+ const cleared = clearStoredCredentials();
1360
+ if (cleared) {
1361
+ console.log(chalk7.green("\u2714") + " Logged out. Stored credentials removed.");
1362
+ } else {
1363
+ console.log(chalk7.dim(`No stored credentials found at ${credentialsPath()}.`));
1364
+ }
1365
+ });
1366
+
1367
+ // src/commands/push.ts
1368
+ import * as crypto2 from "crypto";
1369
+ import * as path6 from "path";
1370
+ import { Command as Command7 } from "commander";
1371
+ import chalk8 from "chalk";
1372
+
1187
1373
  // src/utils/compact-artifact.ts
1188
1374
  import * as zlib from "zlib";
1189
1375
  import { promisify } from "util";
@@ -1454,25 +1640,25 @@ function parseDsn2(dsn) {
1454
1640
  function computeHmac(body, secret) {
1455
1641
  return crypto2.createHmac("sha256", secret).update(body).digest("base64");
1456
1642
  }
1457
- var pushCommand = new Command5("push").description("Push scan results to Vibgrate API").option("--dsn <dsn>", "DSN token (or use VIBGRATE_DSN env)").option("--region <region>", `Override data residency region (${availableRegionIds().join(", ")})`).option("--file <file>", "Scan artifact file", ".vibgrate/scan_result.json").option("--strict", "Fail on upload errors").action(async (opts) => {
1458
- const dsn = opts.dsn || process.env.VIBGRATE_DSN;
1643
+ var pushCommand = new Command7("push").description("Push scan results to Vibgrate API").option("--dsn <dsn>", "DSN token (or use VIBGRATE_DSN env)").option("--region <region>", `Override data residency region (${availableRegionIds().join(", ")})`).option("--file <file>", "Scan artifact file", ".vibgrate/scan_result.json").option("--strict", "Fail on upload errors").action(async (opts) => {
1644
+ const dsn = resolveDsn(opts.dsn);
1459
1645
  if (!dsn) {
1460
- console.error(chalk6.red("No DSN provided."));
1461
- console.error(chalk6.dim("Set VIBGRATE_DSN environment variable or use --dsn flag."));
1646
+ console.error(chalk8.red("No DSN provided."));
1647
+ console.error(chalk8.dim('Run "vibgrate login", set VIBGRATE_DSN, or use the --dsn flag.'));
1462
1648
  if (opts.strict) process.exit(1);
1463
1649
  return;
1464
1650
  }
1465
1651
  const parsed = parseDsn2(dsn);
1466
1652
  if (!parsed) {
1467
- console.error(chalk6.red("Invalid DSN format."));
1468
- console.error(chalk6.dim("Expected: vibgrate+https://<key_id>:<secret>@<host>/<workspace_id>"));
1653
+ console.error(chalk8.red("Invalid DSN format."));
1654
+ console.error(chalk8.dim("Expected: vibgrate+https://<key_id>:<secret>@<host>/<workspace_id>"));
1469
1655
  if (opts.strict) process.exit(1);
1470
1656
  return;
1471
1657
  }
1472
- const filePath = path5.resolve(opts.file);
1658
+ const filePath = path6.resolve(opts.file);
1473
1659
  if (!await pathExists(filePath)) {
1474
- console.error(chalk6.red(`Scan artifact not found: ${filePath}`));
1475
- console.error(chalk6.dim('Run "vibgrate scan" first.'));
1660
+ console.error(chalk8.red(`Scan artifact not found: ${filePath}`));
1661
+ console.error(chalk8.dim('Run "vibgrate scan" first.'));
1476
1662
  if (opts.strict) process.exit(1);
1477
1663
  return;
1478
1664
  }
@@ -1484,7 +1670,7 @@ var pushCommand = new Command5("push").description("Push scan results to Vibgrat
1484
1670
  try {
1485
1671
  host = resolveIngestHost(opts.region);
1486
1672
  } catch (e) {
1487
- console.error(chalk6.red(e instanceof Error ? e.message : String(e)));
1673
+ console.error(chalk8.red(e instanceof Error ? e.message : String(e)));
1488
1674
  if (opts.strict) process.exit(1);
1489
1675
  return;
1490
1676
  }
@@ -1493,7 +1679,7 @@ var pushCommand = new Command5("push").description("Push scan results to Vibgrat
1493
1679
  const originalSize = JSON.stringify(artifact).length;
1494
1680
  const compressedSize = body.length;
1495
1681
  const ratio = ((1 - compressedSize / originalSize) * 100).toFixed(0);
1496
- console.log(chalk6.dim(`Uploading to ${host}... (${(compressedSize / 1024).toFixed(0)} KB, ${ratio}% smaller)`));
1682
+ console.log(chalk8.dim(`Uploading to ${host}... (${(compressedSize / 1024).toFixed(0)} KB, ${ratio}% smaller)`));
1497
1683
  try {
1498
1684
  const response = await fetch(url, {
1499
1685
  method: "POST",
@@ -1512,36 +1698,36 @@ var pushCommand = new Command5("push").description("Push scan results to Vibgrat
1512
1698
  throw new Error(`HTTP ${response.status}: ${text}`);
1513
1699
  }
1514
1700
  const result = await response.json();
1515
- console.log(chalk6.green("\u2714") + ` Scan queued for processing (${result.ingestId ?? "ok"})`);
1701
+ console.log(chalk8.green("\u2714") + ` Scan queued for processing (${result.ingestId ?? "ok"})`);
1516
1702
  console.log();
1517
- console.log(chalk6.dim("Processing continues in the background. Results available shortly."));
1703
+ console.log(chalk8.dim("Processing continues in the background. Results available shortly."));
1518
1704
  console.log();
1519
1705
  if (result.ingestId) {
1520
1706
  const dashHost = dashHostForIngestHost(host);
1521
1707
  const reportUrl = `https://${dashHost}/${parsed.workspaceId}/scan/${result.ingestId}`;
1522
- console.log(chalk6.dim("View report: ") + chalk6.underline(reportUrl));
1708
+ console.log(chalk8.dim("View report: ") + chalk8.underline(reportUrl));
1523
1709
  }
1524
1710
  } catch (e) {
1525
1711
  const msg = e instanceof Error ? e.message : String(e);
1526
- console.error(chalk6.red(`Upload failed: ${msg}`));
1712
+ console.error(chalk8.red(`Upload failed: ${msg}`));
1527
1713
  if (opts.strict) process.exit(1);
1528
1714
  }
1529
1715
  });
1530
1716
 
1531
1717
  // src/commands/update.ts
1532
1718
  import { execSync } from "child_process";
1533
- import * as path7 from "path";
1534
- import { Command as Command6 } from "commander";
1535
- import chalk7 from "chalk";
1719
+ import * as path8 from "path";
1720
+ import { Command as Command8 } from "commander";
1721
+ import chalk9 from "chalk";
1536
1722
 
1537
1723
  // src/utils/update-check.ts
1538
1724
  var import_semver = __toESM(require_semver(), 1);
1539
- import * as fs from "fs/promises";
1540
- import * as path6 from "path";
1541
- import * as os from "os";
1725
+ import * as fs2 from "fs/promises";
1726
+ import * as path7 from "path";
1727
+ import * as os2 from "os";
1542
1728
  var REGISTRY_URL = "https://registry.npmjs.org/@vibgrate%2fcli/latest";
1543
- var CACHE_DIR = path6.join(os.homedir(), ".vibgrate");
1544
- var CACHE_FILE = path6.join(CACHE_DIR, "update-check.json");
1729
+ var CACHE_DIR = path7.join(os2.homedir(), ".vibgrate");
1730
+ var CACHE_FILE = path7.join(CACHE_DIR, "update-check.json");
1545
1731
  var CHECK_INTERVAL_MS = 12 * 60 * 60 * 1e3;
1546
1732
  async function checkForUpdate() {
1547
1733
  try {
@@ -1605,7 +1791,7 @@ async function fetchLatestVersion() {
1605
1791
  }
1606
1792
  async function readCache() {
1607
1793
  try {
1608
- const raw = await fs.readFile(CACHE_FILE, "utf-8");
1794
+ const raw = await fs2.readFile(CACHE_FILE, "utf-8");
1609
1795
  const data = JSON.parse(raw);
1610
1796
  if (data.latest && typeof data.checkedAt === "number") return data;
1611
1797
  return null;
@@ -1615,8 +1801,8 @@ async function readCache() {
1615
1801
  }
1616
1802
  async function writeCache(data) {
1617
1803
  try {
1618
- await fs.mkdir(CACHE_DIR, { recursive: true });
1619
- await fs.writeFile(CACHE_FILE, JSON.stringify(data), "utf-8");
1804
+ await fs2.mkdir(CACHE_DIR, { recursive: true });
1805
+ await fs2.writeFile(CACHE_FILE, JSON.stringify(data), "utf-8");
1620
1806
  } catch {
1621
1807
  }
1622
1808
  }
@@ -1649,9 +1835,9 @@ function getGlobalUpdateCommand(pm, pkg2, version) {
1649
1835
  }
1650
1836
  }
1651
1837
  async function detectPackageManager(cwd) {
1652
- if (await pathExists(path7.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
1653
- if (await pathExists(path7.join(cwd, "bun.lockb"))) return "bun";
1654
- if (await pathExists(path7.join(cwd, "yarn.lock"))) return "yarn";
1838
+ if (await pathExists(path8.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
1839
+ if (await pathExists(path8.join(cwd, "bun.lockb"))) return "bun";
1840
+ if (await pathExists(path8.join(cwd, "yarn.lock"))) return "yarn";
1655
1841
  return "npm";
1656
1842
  }
1657
1843
  function getInstallCommand(pm, pkg2, version, isDev) {
@@ -1670,7 +1856,7 @@ function getInstallCommand(pm, pkg2, version, isDev) {
1670
1856
  }
1671
1857
  async function isDevDependency(cwd) {
1672
1858
  try {
1673
- const pkgPath = path7.join(cwd, "package.json");
1859
+ const pkgPath = path8.join(cwd, "package.json");
1674
1860
  const raw = await (await import("fs/promises")).readFile(pkgPath, "utf-8");
1675
1861
  const pkg2 = JSON.parse(raw);
1676
1862
  return Boolean(pkg2.devDependencies?.["@vibgrate/cli"]);
@@ -1678,22 +1864,22 @@ async function isDevDependency(cwd) {
1678
1864
  return true;
1679
1865
  }
1680
1866
  }
1681
- var updateCommand = new Command6("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").action(async (opts) => {
1682
- console.log(chalk7.dim(`Current version: ${VERSION}`));
1683
- console.log(chalk7.dim("Checking npm registry..."));
1867
+ var updateCommand = new Command8("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").action(async (opts) => {
1868
+ console.log(chalk9.dim(`Current version: ${VERSION}`));
1869
+ console.log(chalk9.dim("Checking npm registry..."));
1684
1870
  const latest = await fetchLatestVersion();
1685
1871
  if (!latest) {
1686
- console.error(chalk7.red("Could not reach the npm registry. Check your network connection."));
1872
+ console.error(chalk9.red("Could not reach the npm registry. Check your network connection."));
1687
1873
  process.exit(1);
1688
1874
  }
1689
1875
  const semver2 = await import("./semver-2FJFIYVN.js");
1690
1876
  if (!semver2.gt(latest, VERSION)) {
1691
- console.log(chalk7.green("\u2714") + ` You are on the latest version (${VERSION}).`);
1877
+ console.log(chalk9.green("\u2714") + ` You are on the latest version (${VERSION}).`);
1692
1878
  return;
1693
1879
  }
1694
- console.log(chalk7.yellow(`Update available: ${VERSION} \u2192 ${latest}`));
1880
+ console.log(chalk9.yellow(`Update available: ${VERSION} \u2192 ${latest}`));
1695
1881
  if (opts.check) {
1696
- console.log(chalk7.dim('Run "vibgrate update" to install.'));
1882
+ console.log(chalk9.dim('Run "vibgrate update" to install.'));
1697
1883
  return;
1698
1884
  }
1699
1885
  const cwd = process.cwd();
@@ -1703,26 +1889,26 @@ var updateCommand = new Command6("update").description("Update vibgrate to the l
1703
1889
  let cmd;
1704
1890
  if (isGlobal) {
1705
1891
  cmd = getGlobalUpdateCommand(pm, "@vibgrate/cli", latest);
1706
- console.log(chalk7.dim(`Updating global installation with ${pm}: ${cmd}`));
1892
+ console.log(chalk9.dim(`Updating global installation with ${pm}: ${cmd}`));
1707
1893
  } else {
1708
1894
  const isDev = await isDevDependency(cwd);
1709
1895
  cmd = getInstallCommand(pm, "@vibgrate/cli", latest, isDev);
1710
- console.log(chalk7.dim(`Using ${pm}: ${cmd}`));
1896
+ console.log(chalk9.dim(`Using ${pm}: ${cmd}`));
1711
1897
  }
1712
1898
  try {
1713
1899
  execSync(cmd, { cwd, stdio: "inherit" });
1714
- console.log(chalk7.green("\u2714") + ` Updated to @vibgrate/cli@${latest}`);
1900
+ console.log(chalk9.green("\u2714") + ` Updated to @vibgrate/cli@${latest}`);
1715
1901
  } catch {
1716
- console.error(chalk7.red(`Update failed. Run manually: ${cmd}`));
1902
+ console.error(chalk9.red(`Update failed. Run manually: ${cmd}`));
1717
1903
  process.exit(1);
1718
1904
  }
1719
1905
  });
1720
1906
 
1721
1907
  // src/commands/sbom.ts
1722
- import * as path8 from "path";
1908
+ import * as path9 from "path";
1723
1909
  import { randomUUID } from "crypto";
1724
- import { Command as Command7 } from "commander";
1725
- import chalk8 from "chalk";
1910
+ import { Command as Command9 } from "commander";
1911
+ import chalk10 from "chalk";
1726
1912
  function flattenDependencies(artifact) {
1727
1913
  const rows = [];
1728
1914
  for (const project of artifact.projects) {
@@ -1860,50 +2046,50 @@ function formatDeltaText(base, current) {
1860
2046
  return lines.join("\n");
1861
2047
  }
1862
2048
  async function readArtifactOrExit(filePath) {
1863
- const absolutePath = path8.resolve(filePath);
2049
+ const absolutePath = path9.resolve(filePath);
1864
2050
  if (!await pathExists(absolutePath)) {
1865
- console.error(chalk8.red(`Artifact not found: ${absolutePath}`));
2051
+ console.error(chalk10.red(`Artifact not found: ${absolutePath}`));
1866
2052
  process.exit(1);
1867
2053
  }
1868
2054
  return readJsonFile(absolutePath);
1869
2055
  }
1870
- var exportCommand = new Command7("export").description("Export scan artifact as SBOM").option("--in <file>", "Input artifact file", ".vibgrate/scan_result.json").option("--out <file>", "Output SBOM file").option("--format <format>", "SBOM format (cyclonedx|spdx)", "cyclonedx").action(async (opts) => {
2056
+ var exportCommand = new Command9("export").description("Export scan artifact as SBOM").option("--in <file>", "Input artifact file", ".vibgrate/scan_result.json").option("--out <file>", "Output SBOM file").option("--format <format>", "SBOM format (cyclonedx|spdx)", "cyclonedx").action(async (opts) => {
1871
2057
  const artifact = await readArtifactOrExit(opts.in);
1872
2058
  const format = opts.format.toLowerCase();
1873
2059
  if (format !== "cyclonedx" && format !== "spdx") {
1874
- console.error(chalk8.red("Invalid SBOM format. Use cyclonedx or spdx."));
2060
+ console.error(chalk10.red("Invalid SBOM format. Use cyclonedx or spdx."));
1875
2061
  process.exit(1);
1876
2062
  }
1877
2063
  const sbom = format === "cyclonedx" ? toCycloneDx(artifact) : toSpdx(artifact);
1878
2064
  const body = JSON.stringify(sbom, null, 2);
1879
2065
  if (opts.out) {
1880
- await writeTextFile(path8.resolve(opts.out), body);
1881
- console.log(chalk8.green("\u2714") + ` SBOM written to ${opts.out}`);
2066
+ await writeTextFile(path9.resolve(opts.out), body);
2067
+ console.log(chalk10.green("\u2714") + ` SBOM written to ${opts.out}`);
1882
2068
  } else {
1883
2069
  console.log(body);
1884
2070
  }
1885
2071
  });
1886
- var deltaCommand = new Command7("delta").description("Show SBOM delta between two scan artifacts").requiredOption("--from <file>", "Baseline scan artifact path").requiredOption("--to <file>", "Current scan artifact path").option("--out <file>", "Write report to file").action(async (opts) => {
2072
+ var deltaCommand = new Command9("delta").description("Show SBOM delta between two scan artifacts").requiredOption("--from <file>", "Baseline scan artifact path").requiredOption("--to <file>", "Current scan artifact path").option("--out <file>", "Write report to file").action(async (opts) => {
1887
2073
  const base = await readArtifactOrExit(opts.from);
1888
2074
  const current = await readArtifactOrExit(opts.to);
1889
2075
  const report = formatDeltaText(base, current);
1890
2076
  if (opts.out) {
1891
- await writeTextFile(path8.resolve(opts.out), report);
1892
- console.log(chalk8.green("\u2714") + ` SBOM delta report written to ${opts.out}`);
2077
+ await writeTextFile(path9.resolve(opts.out), report);
2078
+ console.log(chalk10.green("\u2714") + ` SBOM delta report written to ${opts.out}`);
1893
2079
  } else {
1894
2080
  console.log(report);
1895
2081
  }
1896
2082
  });
1897
- var sbomCommand = new Command7("sbom").description("SBOM export and delta reports for dependency drift tracking").addCommand(exportCommand).addCommand(deltaCommand);
2083
+ var sbomCommand = new Command9("sbom").description("SBOM export and delta reports for dependency drift tracking").addCommand(exportCommand).addCommand(deltaCommand);
1898
2084
 
1899
2085
  // src/commands/extract.ts
1900
- import * as path9 from "path";
1901
- import * as os2 from "os";
1902
- import * as fs2 from "fs/promises";
1903
- import { existsSync } from "fs";
1904
- import { spawn, spawnSync } from "child_process";
1905
- import { Command as Command8 } from "commander";
1906
- import chalk9 from "chalk";
2086
+ import * as path12 from "path";
2087
+ import * as os4 from "os";
2088
+ import * as fs4 from "fs/promises";
2089
+ import { existsSync, rmSync as rmSync2 } from "fs";
2090
+ import { spawn as spawn3, spawnSync as spawnSync2 } from "child_process";
2091
+ import { Command as Command10 } from "commander";
2092
+ import chalk11 from "chalk";
1907
2093
 
1908
2094
  // src/behavioural/derive.ts
1909
2095
  import { createHash as createHash2 } from "crypto";
@@ -2756,6 +2942,303 @@ function deriveBehaviouralFacts(facts, opts) {
2756
2942
  return { envelopes, summary: { assertions: unique.length, surfaceFacts, assertedFacts, behaviouralConfidence } };
2757
2943
  }
2758
2944
 
2945
+ // src/utils/vcs.ts
2946
+ import * as path10 from "path";
2947
+ import * as fs3 from "fs/promises";
2948
+ async function detectVcs2(rootDir) {
2949
+ try {
2950
+ return await detectGit(rootDir);
2951
+ } catch {
2952
+ return { type: "unknown" };
2953
+ }
2954
+ }
2955
+ async function detectGit(rootDir) {
2956
+ const gitDir = await findGitDir(rootDir);
2957
+ if (!gitDir) {
2958
+ return { type: "unknown" };
2959
+ }
2960
+ const headPath = path10.join(gitDir, "HEAD");
2961
+ let headContent;
2962
+ try {
2963
+ headContent = (await fs3.readFile(headPath, "utf8")).trim();
2964
+ } catch {
2965
+ return { type: "unknown" };
2966
+ }
2967
+ let sha;
2968
+ let branch;
2969
+ if (headContent.startsWith("ref: ")) {
2970
+ const refPath = headContent.slice(5);
2971
+ branch = refPath.startsWith("refs/heads/") ? refPath.slice(11) : refPath;
2972
+ sha = await resolveRef(gitDir, refPath);
2973
+ } else if (/^[0-9a-f]{40}$/i.test(headContent)) {
2974
+ sha = headContent;
2975
+ }
2976
+ const remoteUrl = await readGitRemoteUrl(gitDir);
2977
+ return {
2978
+ type: "git",
2979
+ sha: sha ?? void 0,
2980
+ shortSha: sha ? sha.slice(0, 7) : void 0,
2981
+ branch: branch ?? void 0,
2982
+ remoteUrl
2983
+ };
2984
+ }
2985
+ async function findGitDir(startDir) {
2986
+ let dir = path10.resolve(startDir);
2987
+ const root = path10.parse(dir).root;
2988
+ while (dir !== root) {
2989
+ const gitPath = path10.join(dir, ".git");
2990
+ try {
2991
+ const stat4 = await fs3.stat(gitPath);
2992
+ if (stat4.isDirectory()) {
2993
+ return gitPath;
2994
+ }
2995
+ if (stat4.isFile()) {
2996
+ const content = (await fs3.readFile(gitPath, "utf8")).trim();
2997
+ if (content.startsWith("gitdir: ")) {
2998
+ const resolved = path10.resolve(dir, content.slice(8));
2999
+ return resolved;
3000
+ }
3001
+ }
3002
+ } catch {
3003
+ }
3004
+ dir = path10.dirname(dir);
3005
+ }
3006
+ return null;
3007
+ }
3008
+ async function resolveRef(gitDir, refPath) {
3009
+ const loosePath = path10.join(gitDir, refPath);
3010
+ try {
3011
+ const sha = (await fs3.readFile(loosePath, "utf8")).trim();
3012
+ if (/^[0-9a-f]{40}$/i.test(sha)) {
3013
+ return sha;
3014
+ }
3015
+ } catch {
3016
+ }
3017
+ const packedPath = path10.join(gitDir, "packed-refs");
3018
+ try {
3019
+ const packed = await fs3.readFile(packedPath, "utf8");
3020
+ for (const line of packed.split("\n")) {
3021
+ if (line.startsWith("#") || line.startsWith("^")) continue;
3022
+ const parts = line.trim().split(" ");
3023
+ if (parts.length >= 2 && parts[1] === refPath) {
3024
+ return parts[0];
3025
+ }
3026
+ }
3027
+ } catch {
3028
+ }
3029
+ return void 0;
3030
+ }
3031
+ async function readGitRemoteUrl(gitDir) {
3032
+ const configPath = await resolveGitConfigPath(gitDir);
3033
+ if (!configPath) return void 0;
3034
+ try {
3035
+ const config = await fs3.readFile(configPath, "utf8");
3036
+ const originBlock = config.match(/\[remote\s+"origin"\]([\s\S]*?)(?=\n\[|$)/);
3037
+ if (!originBlock) return void 0;
3038
+ const urlMatch = originBlock[1]?.match(/\n\s*url\s*=\s*(.+)\s*/);
3039
+ return urlMatch?.[1]?.trim();
3040
+ } catch {
3041
+ return void 0;
3042
+ }
3043
+ }
3044
+ async function resolveGitConfigPath(gitDir) {
3045
+ const directConfig = path10.join(gitDir, "config");
3046
+ try {
3047
+ const stat4 = await fs3.stat(directConfig);
3048
+ if (stat4.isFile()) return directConfig;
3049
+ } catch {
3050
+ }
3051
+ const commonDirFile = path10.join(gitDir, "commondir");
3052
+ try {
3053
+ const commonDir = (await fs3.readFile(commonDirFile, "utf8")).trim();
3054
+ if (!commonDir) return void 0;
3055
+ const resolvedCommonDir = path10.resolve(gitDir, commonDir);
3056
+ const commonConfig = path10.join(resolvedCommonDir, "config");
3057
+ const stat4 = await fs3.stat(commonConfig);
3058
+ if (stat4.isFile()) return commonConfig;
3059
+ } catch {
3060
+ return void 0;
3061
+ }
3062
+ return void 0;
3063
+ }
3064
+
3065
+ // src/utils/clone.ts
3066
+ import * as path11 from "path";
3067
+ import * as os3 from "os";
3068
+ import { mkdtemp, rm } from "fs/promises";
3069
+ import { spawn as spawn2, spawnSync } from "child_process";
3070
+ var SCP_LIKE = /^[A-Za-z0-9._-]+@[A-Za-z0-9._-]+:.+$/;
3071
+ function looksLikeRepoUrl(arg) {
3072
+ if (!arg) return false;
3073
+ if (/^(https?|ssh|git):\/\//i.test(arg)) return true;
3074
+ if (SCP_LIKE.test(arg)) return true;
3075
+ return false;
3076
+ }
3077
+ function redactRemoteUrl(url) {
3078
+ if (!url) return url;
3079
+ if (SCP_LIKE.test(url) && !url.includes("://")) return url;
3080
+ try {
3081
+ const parsed = new URL(url);
3082
+ if (parsed.username || parsed.password) {
3083
+ parsed.username = "";
3084
+ parsed.password = "";
3085
+ }
3086
+ return parsed.toString();
3087
+ } catch {
3088
+ return url.replace(/^([a-z]+:\/\/)[^/@]+@/i, "$1");
3089
+ }
3090
+ }
3091
+ var CloneError = class extends Error {
3092
+ constructor(message) {
3093
+ super(message);
3094
+ this.name = "CloneError";
3095
+ }
3096
+ };
3097
+ function isGitAvailable() {
3098
+ const result = spawnSync("git", ["--version"], { stdio: "ignore" });
3099
+ return !result.error && result.status === 0;
3100
+ }
3101
+ function gitInstallHint() {
3102
+ switch (process.platform) {
3103
+ case "darwin":
3104
+ return "Install git with: brew install git (or xcode-select --install).";
3105
+ case "win32":
3106
+ return "Install git with: winget install Git.Git";
3107
+ default:
3108
+ return "Install git via your distro package manager (e.g. apt install git).";
3109
+ }
3110
+ }
3111
+ function runGit(args, opts) {
3112
+ return new Promise((resolve10, reject) => {
3113
+ const child = spawn2("git", args, {
3114
+ stdio: ["ignore", "ignore", "pipe"],
3115
+ // Never prompt for credentials on a private/forbidden repo — fail fast.
3116
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "echo" }
3117
+ });
3118
+ let stderr = "";
3119
+ let killed = false;
3120
+ const timer = setTimeout(() => {
3121
+ killed = true;
3122
+ child.kill("SIGKILL");
3123
+ }, opts.timeoutMs);
3124
+ child.stderr.on("data", (d) => {
3125
+ if (stderr.length < 8192) stderr += scrubCredentials(String(d));
3126
+ });
3127
+ child.on("error", (err) => {
3128
+ clearTimeout(timer);
3129
+ reject(new CloneError(`Failed to run git: ${err.message}`));
3130
+ });
3131
+ child.on("close", (code) => {
3132
+ clearTimeout(timer);
3133
+ if (killed) {
3134
+ reject(new CloneError(`git clone timed out for ${opts.redacted}`));
3135
+ return;
3136
+ }
3137
+ resolve10({ code: code ?? 0, stderr: stderr.trim() });
3138
+ });
3139
+ });
3140
+ }
3141
+ function scrubCredentials(text) {
3142
+ return text.replace(/([a-z]+:\/\/)[^/\s@]+@/gi, "$1");
3143
+ }
3144
+ async function cloneRepo(url, opts) {
3145
+ const redacted = redactRemoteUrl(url);
3146
+ if (!looksLikeRepoUrl(url)) {
3147
+ throw new CloneError(
3148
+ `Not a supported repository URL: ${redacted}. Use an http(s), ssh, git://, or scp-like (git@host:owner/repo.git) URL.`
3149
+ );
3150
+ }
3151
+ if (/^file:\/\//i.test(url)) {
3152
+ throw new CloneError("file:// URLs are not allowed for remote clone.");
3153
+ }
3154
+ if (!isGitAvailable()) {
3155
+ throw new CloneError(`git is required to clone ${redacted} but was not found on PATH. ${gitInstallHint()}`);
3156
+ }
3157
+ const dir = await mkdtemp(path11.join(os3.tmpdir(), "vibgrate-hcs-clone-"));
3158
+ const cleanup = async () => {
3159
+ await rm(dir, { recursive: true, force: true }).catch(() => {
3160
+ });
3161
+ };
3162
+ try {
3163
+ if (opts.verbose) process.stderr.write(`Cloning ${redacted} \u2026
3164
+ `);
3165
+ const clone = await runGit(
3166
+ ["clone", "--depth", "1", "--quiet", "--", url, dir],
3167
+ { timeoutMs: opts.timeoutMs, redacted }
3168
+ );
3169
+ if (clone.code !== 0) {
3170
+ throw new CloneError(`git clone failed for ${redacted}: ${clone.stderr || `exit ${clone.code}`}`);
3171
+ }
3172
+ if (opts.ref) {
3173
+ if (opts.verbose) process.stderr.write(`Checking out ${opts.ref} \u2026
3174
+ `);
3175
+ const fetch2 = await runGit(
3176
+ ["-C", dir, "fetch", "--depth", "1", "--quiet", "origin", opts.ref],
3177
+ { timeoutMs: opts.timeoutMs, redacted }
3178
+ );
3179
+ const checkoutTarget = fetch2.code === 0 ? "FETCH_HEAD" : opts.ref;
3180
+ const checkout = await runGit(
3181
+ ["-C", dir, "checkout", "--quiet", "--detach", checkoutTarget],
3182
+ { timeoutMs: opts.timeoutMs, redacted }
3183
+ );
3184
+ if (checkout.code !== 0) {
3185
+ throw new CloneError(
3186
+ `Could not check out ref "${opts.ref}" from ${redacted}: ${checkout.stderr || `exit ${checkout.code}`}`
3187
+ );
3188
+ }
3189
+ }
3190
+ return { dir, cleanup };
3191
+ } catch (err) {
3192
+ await cleanup();
3193
+ throw err instanceof CloneError ? err : new CloneError(String(err));
3194
+ }
3195
+ }
3196
+
3197
+ // src/utils/scan-manifest.ts
3198
+ import * as crypto3 from "crypto";
3199
+ function computeFactContentHash(factLines) {
3200
+ const hash = crypto3.createHash("sha256");
3201
+ for (const line of factLines) {
3202
+ hash.update(line);
3203
+ hash.update("\n");
3204
+ }
3205
+ return `sha256:${hash.digest("hex")}`;
3206
+ }
3207
+ function deriveRepoName(remoteUrl) {
3208
+ if (!remoteUrl) return void 0;
3209
+ const withoutScheme = remoteUrl.replace(/^[a-z]+:\/\//i, "").replace(/^[^/@]+@/, "");
3210
+ const pathPart = withoutScheme.replace(/^[^/:]+[/:]/, "");
3211
+ const segments = pathPart.split("/").filter(Boolean);
3212
+ if (segments.length === 0) return void 0;
3213
+ const repo = segments[segments.length - 1].replace(/\.git$/i, "");
3214
+ const owner = segments.length >= 2 ? segments[segments.length - 2] : void 0;
3215
+ return owner ? `${owner}/${repo}` : repo;
3216
+ }
3217
+ function buildScanManifest(input) {
3218
+ return {
3219
+ factType: "ScanManifest",
3220
+ manifestVersion: "1.0",
3221
+ emittedAt: input.emittedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
3222
+ repository: {
3223
+ remoteUrl: input.remoteUrl,
3224
+ name: input.repositoryName ?? deriveRepoName(input.remoteUrl)
3225
+ },
3226
+ vcs: {
3227
+ sha: input.sha,
3228
+ shortSha: input.shortSha,
3229
+ branch: input.branch,
3230
+ ref: input.ref
3231
+ },
3232
+ source: input.source,
3233
+ languages: [...input.languages].sort(),
3234
+ scanId: input.scanId,
3235
+ applicationId: input.applicationId,
3236
+ vibgrateVersion: input.vibgrateVersion,
3237
+ factCount: input.factLines.length,
3238
+ contentHash: computeFactContentHash(input.factLines)
3239
+ };
3240
+ }
3241
+
2759
3242
  // src/commands/extract.ts
2760
3243
  var EXIT_SUCCESS = 0;
2761
3244
  var EXIT_SCHEMA_FAILURE = 1;
@@ -2840,14 +3323,14 @@ async function detectLanguages(rootDir, includeTests) {
2840
3323
  async function walk(dir, relBase) {
2841
3324
  let entries;
2842
3325
  try {
2843
- entries = await fs2.readdir(dir, { withFileTypes: true });
3326
+ entries = await fs4.readdir(dir, { withFileTypes: true });
2844
3327
  } catch {
2845
3328
  return;
2846
3329
  }
2847
3330
  for (const entry of entries) {
2848
3331
  if (entry.name.startsWith(".")) continue;
2849
- const absPath = path9.join(dir, entry.name);
2850
- const relPath = path9.join(relBase, entry.name);
3332
+ const absPath = path12.join(dir, entry.name);
3333
+ const relPath = path12.join(relBase, entry.name);
2851
3334
  if (entry.isDirectory()) {
2852
3335
  if (!SKIP_DIRS.has(entry.name)) {
2853
3336
  await walk(absPath, relPath);
@@ -2856,7 +3339,7 @@ async function detectLanguages(rootDir, includeTests) {
2856
3339
  }
2857
3340
  if (!entry.isFile()) continue;
2858
3341
  if (!includeTests && isTestPath(relPath)) continue;
2859
- const ext = path9.extname(entry.name).toLowerCase();
3342
+ const ext = path12.extname(entry.name).toLowerCase();
2860
3343
  for (const [lang, exts] of Object.entries(LANGUAGE_EXTENSIONS)) {
2861
3344
  if (exts.has(ext)) {
2862
3345
  counts.set(lang, (counts.get(lang) ?? 0) + 1);
@@ -2882,6 +3365,15 @@ function validateFactLine(line) {
2882
3365
  }
2883
3366
  return { valid: true };
2884
3367
  }
3368
+ if (obj.factType === "ScanManifest") {
3369
+ if (typeof obj.manifestVersion !== "string") {
3370
+ return { valid: false, error: "ScanManifest missing manifestVersion" };
3371
+ }
3372
+ if (typeof obj.emittedAt !== "string") {
3373
+ return { valid: false, error: "ScanManifest missing emittedAt" };
3374
+ }
3375
+ return { valid: true };
3376
+ }
2885
3377
  if (typeof obj.factId !== "string" || !obj.factId) {
2886
3378
  return { valid: false, error: "Missing or invalid factId" };
2887
3379
  }
@@ -2923,7 +3415,7 @@ function splitHcsOutput(lines) {
2923
3415
  continue;
2924
3416
  }
2925
3417
  const obj = parsed;
2926
- if (obj.factType === "Models" || obj.factType === "References") {
3418
+ if (obj.factType === "Models" || obj.factType === "References" || obj.factType === "ScanManifest") {
2927
3419
  preamble.push(line);
2928
3420
  } else {
2929
3421
  facts.push(line);
@@ -2932,10 +3424,10 @@ function splitHcsOutput(lines) {
2932
3424
  return { preamble, facts };
2933
3425
  }
2934
3426
  function resolveHcsWorkerBin() {
2935
- const base = import.meta.dirname ?? path9.dirname(new URL(import.meta.url).pathname);
2936
- const bundledPath = path9.resolve(base, "..", "dist", "hcs-worker.js");
3427
+ const base = import.meta.dirname ?? path12.dirname(new URL(import.meta.url).pathname);
3428
+ const bundledPath = path12.resolve(base, "..", "dist", "hcs-worker.js");
2937
3429
  if (existsSync(bundledPath)) return bundledPath;
2938
- const siblingPath = path9.resolve(base, "hcs-worker.js");
3430
+ const siblingPath = path12.resolve(base, "hcs-worker.js");
2939
3431
  if (existsSync(siblingPath)) return siblingPath;
2940
3432
  try {
2941
3433
  const resolved = import.meta.resolve("@vibgrate/hcs-node-worker");
@@ -2943,9 +3435,9 @@ function resolveHcsWorkerBin() {
2943
3435
  if (existsSync(resolvedPath)) return resolvedPath;
2944
3436
  } catch {
2945
3437
  }
2946
- const monorepoPath = path9.resolve(base, "..", "..", "..", "vibgrate-hcs", "node", "dist", "main.js");
3438
+ const monorepoPath = path12.resolve(base, "..", "..", "..", "vibgrate-hcs", "node", "dist", "main.js");
2947
3439
  if (existsSync(monorepoPath)) return monorepoPath;
2948
- const devPath = path9.resolve(base, "..", "..", "..", "vibgrate-hcs", "node", "src", "main.ts");
3440
+ const devPath = path12.resolve(base, "..", "..", "..", "vibgrate-hcs", "node", "src", "main.ts");
2949
3441
  if (existsSync(devPath)) return devPath;
2950
3442
  throw new Error(
2951
3443
  'Cannot locate HCS worker. Run "pnpm build" in the CLI package to bundle the worker into dist/.'
@@ -3024,7 +3516,7 @@ var NATIVE_RUNTIME_REQUIREMENTS = {
3024
3516
  function commandExistsOnPath(command) {
3025
3517
  const probeArgs = process.platform === "win32" ? ["/c", command, "--version"] : ["--version"];
3026
3518
  const probeCmd = process.platform === "win32" ? "cmd" : command;
3027
- const result = spawnSync(probeCmd, probeArgs, { stdio: "ignore" });
3519
+ const result = spawnSync2(probeCmd, probeArgs, { stdio: "ignore" });
3028
3520
  return !result.error && result.status === 0;
3029
3521
  }
3030
3522
  function buildNativeInstallHint(language) {
@@ -3048,12 +3540,12 @@ async function runNodeWorker(rootDir, language, opts) {
3048
3540
  args.push("--cobol-copybook-paths", opts.copybookPaths);
3049
3541
  }
3050
3542
  }
3051
- return new Promise((resolve9) => {
3543
+ return new Promise((resolve10) => {
3052
3544
  const facts = [];
3053
3545
  const errors = [];
3054
3546
  let stdoutBuf = "";
3055
3547
  let killed = false;
3056
- const child = spawn("node", ["--enable-source-maps", workerBin, ...args], {
3548
+ const child = spawn3("node", ["--enable-source-maps", workerBin, ...args], {
3057
3549
  cwd: rootDir,
3058
3550
  stdio: ["ignore", "pipe", "pipe"],
3059
3551
  env: { ...process.env, NODE_OPTIONS: "--max-old-space-size=4096" }
@@ -3085,7 +3577,7 @@ async function runNodeWorker(rootDir, language, opts) {
3085
3577
  continue;
3086
3578
  }
3087
3579
  if (opts.verbose) {
3088
- process.stderr.write(chalk9.dim(`[${language}] ${trimmed}
3580
+ process.stderr.write(chalk11.dim(`[${language}] ${trimmed}
3089
3581
  `));
3090
3582
  }
3091
3583
  if (trimmed.startsWith("[error]")) {
@@ -3099,15 +3591,15 @@ async function runNodeWorker(rootDir, language, opts) {
3099
3591
  facts.push(stdoutBuf.trim());
3100
3592
  }
3101
3593
  if (killed) {
3102
- resolve9({ language, facts, errors: ["Worker killed: timeout exceeded"], exitCode: EXIT_TIMEOUT });
3594
+ resolve10({ language, facts, errors: ["Worker killed: timeout exceeded"], exitCode: EXIT_TIMEOUT });
3103
3595
  } else {
3104
- resolve9({ language, facts, errors, exitCode: code ?? 0 });
3596
+ resolve10({ language, facts, errors, exitCode: code ?? 0 });
3105
3597
  }
3106
3598
  });
3107
3599
  child.on("error", (err) => {
3108
3600
  clearTimeout(timer);
3109
3601
  errors.push(`Failed to spawn worker: ${err.message}`);
3110
- resolve9({ language, facts, errors, exitCode: EXIT_PARSE_FAILURE });
3602
+ resolve10({ language, facts, errors, exitCode: EXIT_PARSE_FAILURE });
3111
3603
  });
3112
3604
  });
3113
3605
  }
@@ -3127,7 +3619,7 @@ function resolveDotnetPublishedWorker(workersDir) {
3127
3619
  };
3128
3620
  const candidates = candidatesByPlatform[process.platform] ?? [];
3129
3621
  for (const filename of candidates) {
3130
- const fullPath = path9.join(workersDir, filename);
3622
+ const fullPath = path12.join(workersDir, filename);
3131
3623
  if (existsSync(fullPath)) {
3132
3624
  return fullPath;
3133
3625
  }
@@ -3135,30 +3627,30 @@ function resolveDotnetPublishedWorker(workersDir) {
3135
3627
  return null;
3136
3628
  }
3137
3629
  function resolveNativeWorker(language, projectDir) {
3138
- const base = import.meta.dirname ?? path9.dirname(new URL(import.meta.url).pathname);
3139
- const hcsFromBundle = path9.resolve(base, "..", "..", "vibgrate-hcs");
3140
- const hcsFromSrc = path9.resolve(base, "..", "..", "..", "vibgrate-hcs");
3630
+ const base = import.meta.dirname ?? path12.dirname(new URL(import.meta.url).pathname);
3631
+ const hcsFromBundle = path12.resolve(base, "..", "..", "vibgrate-hcs");
3632
+ const hcsFromSrc = path12.resolve(base, "..", "..", "..", "vibgrate-hcs");
3141
3633
  const hcsRoot = existsSync(hcsFromBundle) ? hcsFromBundle : hcsFromSrc;
3142
- const workersDir = path9.resolve(base, "workers");
3634
+ const workersDir = path12.resolve(base, "workers");
3143
3635
  switch (language) {
3144
3636
  case "go": {
3145
- const bin = path9.join(workersDir, process.platform === "win32" ? "vibgrate-hcs-go.exe" : "vibgrate-hcs-go");
3637
+ const bin = path12.join(workersDir, process.platform === "win32" ? "vibgrate-hcs-go.exe" : "vibgrate-hcs-go");
3146
3638
  if (existsSync(bin)) {
3147
3639
  return { cmd: bin, args: ["--project", projectDir, "--output", "ndjson"] };
3148
3640
  }
3149
- const src = path9.join(hcsRoot, "go");
3150
- if (existsSync(path9.join(src, "main.go"))) {
3641
+ const src = path12.join(hcsRoot, "go");
3642
+ if (existsSync(path12.join(src, "main.go"))) {
3151
3643
  return { cmd: "go", args: ["run", ".", "--project", projectDir, "--output", "ndjson"], cwd: src };
3152
3644
  }
3153
3645
  return null;
3154
3646
  }
3155
3647
  case "python": {
3156
- const bin = path9.join(workersDir, "vibgrate-hcs-python");
3648
+ const bin = path12.join(workersDir, "vibgrate-hcs-python");
3157
3649
  if (existsSync(bin)) {
3158
3650
  return { cmd: bin, args: ["--project", projectDir, "--output", "ndjson"] };
3159
3651
  }
3160
- const src = path9.join(hcsRoot, "python");
3161
- if (existsSync(path9.join(src, "pyproject.toml"))) {
3652
+ const src = path12.join(hcsRoot, "python");
3653
+ if (existsSync(path12.join(src, "pyproject.toml"))) {
3162
3654
  return {
3163
3655
  cmd: "python3",
3164
3656
  args: ["-m", "vibgrate_hcs_python.main", "--project", projectDir, "--output", "ndjson"],
@@ -3168,13 +3660,13 @@ function resolveNativeWorker(language, projectDir) {
3168
3660
  return null;
3169
3661
  }
3170
3662
  case "java": {
3171
- const jar = path9.join(workersDir, "vibgrate-hcs-jvm.jar");
3663
+ const jar = path12.join(workersDir, "vibgrate-hcs-jvm.jar");
3172
3664
  if (existsSync(jar)) {
3173
3665
  return { cmd: "java", args: ["-jar", jar, "--project", projectDir, "--output", "ndjson"] };
3174
3666
  }
3175
- const src = path9.join(hcsRoot, "jvm");
3176
- if (existsSync(path9.join(src, "build.gradle.kts"))) {
3177
- const gradlew = path9.join(src, process.platform === "win32" ? "gradlew.bat" : "gradlew");
3667
+ const src = path12.join(hcsRoot, "jvm");
3668
+ if (existsSync(path12.join(src, "build.gradle.kts"))) {
3669
+ const gradlew = path12.join(src, process.platform === "win32" ? "gradlew.bat" : "gradlew");
3178
3670
  const launcher = existsSync(gradlew) ? gradlew : "gradle";
3179
3671
  return {
3180
3672
  cmd: launcher,
@@ -3190,11 +3682,11 @@ function resolveNativeWorker(language, projectDir) {
3190
3682
  if (publishedWorker) {
3191
3683
  return { cmd: publishedWorker, args: ["--project", projectDir, "--output", "ndjson"] };
3192
3684
  }
3193
- const dll = path9.join(workersDir, "VibgrateHcsWorker.dll");
3685
+ const dll = path12.join(workersDir, "VibgrateHcsWorker.dll");
3194
3686
  if (existsSync(dll)) {
3195
3687
  return { cmd: "dotnet", args: [dll, "--project", projectDir, "--output", "ndjson"] };
3196
3688
  }
3197
- const csproj = path9.join(hcsRoot, "dotnet", "src", "VibgrateHcsWorker", "VibgrateHcsWorker.csproj");
3689
+ const csproj = path12.join(hcsRoot, "dotnet", "src", "VibgrateHcsWorker", "VibgrateHcsWorker.csproj");
3198
3690
  if (existsSync(csproj)) {
3199
3691
  return {
3200
3692
  cmd: "dotnet",
@@ -3220,19 +3712,19 @@ async function runNativeWorker(rootDir, language, opts) {
3220
3712
  exitCode: EXIT_PARSE_FAILURE
3221
3713
  };
3222
3714
  }
3223
- return new Promise((resolve9) => {
3715
+ return new Promise((resolve10) => {
3224
3716
  const facts = [];
3225
3717
  const errors = [];
3226
3718
  let stdoutBuf = "";
3227
3719
  let killed = false;
3228
3720
  if (opts.verbose) {
3229
- process.stderr.write(chalk9.dim(`[${language}] Spawning: ${spec.cmd} ${spec.args.join(" ")}
3721
+ process.stderr.write(chalk11.dim(`[${language}] Spawning: ${spec.cmd} ${spec.args.join(" ")}
3230
3722
  `));
3231
3723
  }
3232
3724
  const runtimeReq = NATIVE_RUNTIME_REQUIREMENTS[language];
3233
3725
  const usesPathCommand = runtimeReq && spec.cmd === runtimeReq.command;
3234
3726
  if (usesPathCommand && !commandExistsOnPath(spec.cmd)) {
3235
- resolve9({
3727
+ resolve10({
3236
3728
  language,
3237
3729
  facts: [],
3238
3730
  errors: [
@@ -3244,7 +3736,7 @@ async function runNativeWorker(rootDir, language, opts) {
3244
3736
  });
3245
3737
  return;
3246
3738
  }
3247
- const child = spawn(spec.cmd, spec.args, {
3739
+ const child = spawn3(spec.cmd, spec.args, {
3248
3740
  cwd: spec.cwd ?? rootDir,
3249
3741
  stdio: ["ignore", "pipe", "pipe"]
3250
3742
  });
@@ -3267,7 +3759,7 @@ async function runNativeWorker(rootDir, language, opts) {
3267
3759
  const trimmed = line.trim();
3268
3760
  if (!trimmed) continue;
3269
3761
  if (opts.verbose) {
3270
- process.stderr.write(chalk9.dim(`[${language}] ${trimmed}
3762
+ process.stderr.write(chalk11.dim(`[${language}] ${trimmed}
3271
3763
  `));
3272
3764
  }
3273
3765
  if (trimmed.startsWith("[error]")) {
@@ -3279,9 +3771,9 @@ async function runNativeWorker(rootDir, language, opts) {
3279
3771
  clearTimeout(timer);
3280
3772
  if (stdoutBuf.trim()) facts.push(stdoutBuf.trim());
3281
3773
  if (killed) {
3282
- resolve9({ language, facts, errors: ["Worker killed: timeout exceeded"], exitCode: EXIT_TIMEOUT });
3774
+ resolve10({ language, facts, errors: ["Worker killed: timeout exceeded"], exitCode: EXIT_TIMEOUT });
3283
3775
  } else {
3284
- resolve9({ language, facts, errors, exitCode: code ?? 0 });
3776
+ resolve10({ language, facts, errors, exitCode: code ?? 0 });
3285
3777
  }
3286
3778
  });
3287
3779
  child.on("error", (err) => {
@@ -3290,21 +3782,21 @@ async function runNativeWorker(rootDir, language, opts) {
3290
3782
  errors.push(
3291
3783
  isNotFound ? `[error] '${spec.cmd}' not found. ${buildNativeInstallHint(language)}` : `[error] Failed to spawn native worker: ${err.message}`
3292
3784
  );
3293
- resolve9({ language, facts, errors, exitCode: EXIT_PARSE_FAILURE });
3785
+ resolve10({ language, facts, errors, exitCode: EXIT_PARSE_FAILURE });
3294
3786
  });
3295
3787
  });
3296
3788
  }
3297
3789
  async function pushFacts(facts, dsn, verbose) {
3298
3790
  const parsed = parseDsn2(dsn);
3299
3791
  if (!parsed) {
3300
- process.stderr.write(chalk9.red("Invalid DSN format.\n"));
3301
- process.stderr.write(chalk9.dim("Expected: vibgrate+https://<key_id>:<secret>@<host>/<workspace_id>\n"));
3792
+ process.stderr.write(chalk11.red("Invalid DSN format.\n"));
3793
+ process.stderr.write(chalk11.dim("Expected: vibgrate+https://<key_id>:<secret>@<host>/<workspace_id>\n"));
3302
3794
  return false;
3303
3795
  }
3304
3796
  const body = facts.join("\n") + "\n";
3305
3797
  const url = `${parsed.scheme}://${parsed.host}/v1/ingest/hcs`;
3306
3798
  if (verbose) {
3307
- process.stderr.write(chalk9.dim(`Pushing ${facts.length} facts to ${parsed.host}...
3799
+ process.stderr.write(chalk11.dim(`Pushing ${facts.length} facts to ${parsed.host}...
3308
3800
  `));
3309
3801
  }
3310
3802
  try {
@@ -3322,23 +3814,23 @@ async function pushFacts(facts, dsn, verbose) {
3322
3814
  });
3323
3815
  if (!response.ok) {
3324
3816
  const text = await response.text().catch(() => "");
3325
- process.stderr.write(chalk9.red(`Push failed: HTTP ${response.status} ${text}
3817
+ process.stderr.write(chalk11.red(`Push failed: HTTP ${response.status} ${text}
3326
3818
  `));
3327
3819
  return false;
3328
3820
  }
3329
3821
  if (verbose) {
3330
- process.stderr.write(chalk9.green(`\u2714 Pushed ${facts.length} facts successfully
3822
+ process.stderr.write(chalk11.green(`\u2714 Pushed ${facts.length} facts successfully
3331
3823
  `));
3332
3824
  }
3333
3825
  return true;
3334
3826
  } catch (err) {
3335
- process.stderr.write(chalk9.red(`Push failed: ${err instanceof Error ? err.message : String(err)}
3827
+ process.stderr.write(chalk11.red(`Push failed: ${err instanceof Error ? err.message : String(err)}
3336
3828
  `));
3337
3829
  return false;
3338
3830
  }
3339
3831
  }
3340
3832
  async function loadFeedback(filePath) {
3341
- const content = await fs2.readFile(filePath, "utf-8");
3833
+ const content = await fs4.readFile(filePath, "utf-8");
3342
3834
  const lines = content.split("\n").filter((l) => l.trim());
3343
3835
  for (const line of lines) {
3344
3836
  try {
@@ -3399,21 +3891,21 @@ var ProgressTracker = class {
3399
3891
  const parts = [];
3400
3892
  for (const [lang, st] of this.langStatus) {
3401
3893
  if (st.done) {
3402
- parts.push(chalk9.green(`${lang} \u2713`));
3894
+ parts.push(chalk11.green(`${lang} \u2713`));
3403
3895
  } else if (st.phase === "waiting") {
3404
- parts.push(chalk9.dim(`${lang} \xB7`));
3896
+ parts.push(chalk11.dim(`${lang} \xB7`));
3405
3897
  } else if (st.phase === "discovering") {
3406
- parts.push(chalk9.yellow(`${lang} \u2026`));
3898
+ parts.push(chalk11.yellow(`${lang} \u2026`));
3407
3899
  } else if (st.phase === "scanning" && st.fileCount > 0) {
3408
3900
  const pct = Math.round(st.fileIndex / st.fileCount * 100);
3409
- parts.push(chalk9.cyan(`${lang} ${st.fileIndex}/${st.fileCount} ${pct}%`));
3901
+ parts.push(chalk11.cyan(`${lang} ${st.fileIndex}/${st.fileCount} ${pct}%`));
3410
3902
  } else if (st.phase === "extracting") {
3411
- parts.push(chalk9.cyan(`${lang} extracting`));
3903
+ parts.push(chalk11.cyan(`${lang} extracting`));
3412
3904
  } else {
3413
- parts.push(chalk9.dim(`${lang} ${st.phase}`));
3905
+ parts.push(chalk11.dim(`${lang} ${st.phase}`));
3414
3906
  }
3415
3907
  }
3416
- const line = ` ${parts.join(" ")} ${chalk9.dim(`${this.totalFacts} facts`)}`;
3908
+ const line = ` ${parts.join(" ")} ${chalk11.dim(`${this.totalFacts} facts`)}`;
3417
3909
  const padding = Math.max(0, this.lastLineLen - line.length);
3418
3910
  process.stderr.write(`\r${line}${" ".repeat(padding)}`);
3419
3911
  this.lastLineLen = line.length;
@@ -3425,58 +3917,95 @@ var ProgressTracker = class {
3425
3917
  }
3426
3918
  }
3427
3919
  };
3428
- var extractCommand = new Command8("extract").description("Analyze source code and emit validated HCS facts (NDJSON)").argument("[path]", "Path to source directory", ".").option("-o, --out <file>", "Write NDJSON to file (default: stdout)").option("--language <langs>", "Comma-separated languages to analyze (default: auto-detect)").option("--include-tests", "Include test files in analysis").option("--push", "Stream validated facts to dashboard API").option("--dsn <dsn>", "DSN token for push (or use VIBGRATE_DSN env)").option("--concurrency <n>", "Number of parallel file workers").option("--timeout-mins <mins>", "Total analysis timeout in minutes", "60").option("--feedback <file>", "Load NDJSON diff artifact for refinement").option("--derive", "Derive behavioural spec facts (BehaviouralAssertion/BehaviouralSpec) from extracted facts").option("--scan-id <id>", 'Scan id for the derived BehaviouralSpec (default: "local")').option("--application-id <id>", "Application id for the derived BehaviouralSpec (default: directory name)").option("--verbose", "Print worker stderr and summary statistics").action(async (targetPath, opts) => {
3429
- const rootDir = path9.resolve(targetPath);
3430
- if (!await pathExists(rootDir)) {
3431
- process.stderr.write(chalk9.red(`Path does not exist: ${rootDir}
3920
+ var extractCommand = new Command10("extract").description("Analyze source code and emit validated HCS facts (NDJSON)").argument("[path-or-url]", "Path to source directory, or a public repository URL to clone", ".").option("--repo-url <url>", "Public repository URL to clone and analyze (alternative to passing a URL positionally)").option("--ref <ref>", "Branch, tag, or commit SHA to check out when cloning a repository URL").option("--keep-clone", "Do not delete the temporary clone after extraction (prints its path)").option("--clone-timeout-mins <mins>", "Clone/fetch timeout in minutes", "10").option("-o, --out <file>", "Write NDJSON to file (default: stdout)").option("--language <langs>", "Comma-separated languages to analyze (default: auto-detect)").option("--include-tests", "Include test files in analysis").option("--push", "Stream validated facts to dashboard API").option("--dsn <dsn>", "DSN token for push (or use VIBGRATE_DSN env)").option("--concurrency <n>", "Number of parallel file workers").option("--timeout-mins <mins>", "Total analysis timeout in minutes", "60").option("--feedback <file>", "Load NDJSON diff artifact for refinement").option("--derive", "Derive behavioural spec facts (BehaviouralAssertion/BehaviouralSpec) from extracted facts").option("--scan-id <id>", 'Scan id for the derived BehaviouralSpec (default: "local")').option("--application-id <id>", "Application id for the derived BehaviouralSpec (default: directory name)").option("--verbose", "Print worker stderr and summary statistics").action(async (targetPath, opts) => {
3921
+ const urlArg = opts.repoUrl ?? (looksLikeRepoUrl(targetPath) ? targetPath : void 0);
3922
+ const source = urlArg ? "url" : "local";
3923
+ let rootDir;
3924
+ let manifestRemoteUrl;
3925
+ if (urlArg) {
3926
+ const cloneTimeoutMins = parseInt(opts.cloneTimeoutMins, 10);
3927
+ if (isNaN(cloneTimeoutMins) || cloneTimeoutMins < 1) {
3928
+ process.stderr.write(chalk11.red("--clone-timeout-mins must be >= 1\n"));
3929
+ process.exit(EXIT_USAGE_ERROR);
3930
+ }
3931
+ manifestRemoteUrl = redactRemoteUrl(urlArg);
3932
+ try {
3933
+ const cloned = await cloneRepo(urlArg, {
3934
+ ref: opts.ref,
3935
+ timeoutMs: cloneTimeoutMins * 60 * 1e3,
3936
+ verbose: opts.verbose ?? false
3937
+ });
3938
+ rootDir = cloned.dir;
3939
+ if (opts.keepClone) {
3940
+ process.stderr.write(chalk11.dim(`Clone retained at: ${cloned.dir}
3432
3941
  `));
3433
- process.exit(EXIT_USAGE_ERROR);
3434
- }
3435
- let stat3;
3436
- try {
3437
- stat3 = await fs2.stat(rootDir);
3438
- } catch {
3439
- process.stderr.write(chalk9.red(`Cannot read path: ${rootDir}
3942
+ } else {
3943
+ process.once("exit", () => {
3944
+ try {
3945
+ rmSync2(cloned.dir, { recursive: true, force: true });
3946
+ } catch {
3947
+ }
3948
+ });
3949
+ }
3950
+ } catch (err) {
3951
+ const msg = err instanceof CloneError ? err.message : err instanceof Error ? err.message : String(err);
3952
+ process.stderr.write(chalk11.red(`Clone failed: ${msg}
3440
3953
  `));
3441
- process.exit(EXIT_USAGE_ERROR);
3442
- }
3443
- if (!stat3.isDirectory()) {
3444
- process.stderr.write(chalk9.red(`Path must be a directory: ${rootDir}
3954
+ process.exit(EXIT_USAGE_ERROR);
3955
+ }
3956
+ } else {
3957
+ rootDir = path12.resolve(targetPath);
3958
+ if (!await pathExists(rootDir)) {
3959
+ process.stderr.write(chalk11.red(`Path does not exist: ${rootDir}
3445
3960
  `));
3446
- process.exit(EXIT_USAGE_ERROR);
3961
+ process.exit(EXIT_USAGE_ERROR);
3962
+ }
3963
+ let stat4;
3964
+ try {
3965
+ stat4 = await fs4.stat(rootDir);
3966
+ } catch {
3967
+ process.stderr.write(chalk11.red(`Cannot read path: ${rootDir}
3968
+ `));
3969
+ process.exit(EXIT_USAGE_ERROR);
3970
+ }
3971
+ if (!stat4.isDirectory()) {
3972
+ process.stderr.write(chalk11.red(`Path must be a directory: ${rootDir}
3973
+ `));
3974
+ process.exit(EXIT_USAGE_ERROR);
3975
+ }
3447
3976
  }
3448
3977
  const dsn = opts.dsn || process.env.VIBGRATE_DSN;
3449
3978
  if (opts.push && !dsn) {
3450
- process.stderr.write(chalk9.red("--push requires --dsn or VIBGRATE_DSN environment variable\n"));
3979
+ process.stderr.write(chalk11.red("--push requires --dsn or VIBGRATE_DSN environment variable\n"));
3451
3980
  process.exit(EXIT_USAGE_ERROR);
3452
3981
  }
3453
3982
  if (dsn && !parseDsn2(dsn)) {
3454
- process.stderr.write(chalk9.red("Invalid DSN format.\n"));
3455
- process.stderr.write(chalk9.dim("Expected: vibgrate+https://<key_id>:<secret>@<host>/<workspace_id>\n"));
3983
+ process.stderr.write(chalk11.red("Invalid DSN format.\n"));
3984
+ process.stderr.write(chalk11.dim("Expected: vibgrate+https://<key_id>:<secret>@<host>/<workspace_id>\n"));
3456
3985
  process.exit(EXIT_USAGE_ERROR);
3457
3986
  }
3458
- const concurrency = opts.concurrency ? parseInt(opts.concurrency, 10) : os2.cpus().length;
3987
+ const concurrency = opts.concurrency ? parseInt(opts.concurrency, 10) : os4.cpus().length;
3459
3988
  if (isNaN(concurrency) || concurrency < 1) {
3460
- process.stderr.write(chalk9.red("--concurrency must be >= 1\n"));
3989
+ process.stderr.write(chalk11.red("--concurrency must be >= 1\n"));
3461
3990
  process.exit(EXIT_USAGE_ERROR);
3462
3991
  }
3463
3992
  const timeoutMins = parseInt(opts.timeoutMins, 10);
3464
3993
  if (isNaN(timeoutMins) || timeoutMins < 1) {
3465
- process.stderr.write(chalk9.red("--timeout-mins must be >= 1\n"));
3994
+ process.stderr.write(chalk11.red("--timeout-mins must be >= 1\n"));
3466
3995
  process.exit(EXIT_USAGE_ERROR);
3467
3996
  }
3468
3997
  const timeoutMs = timeoutMins * 60 * 1e3;
3469
3998
  if (opts.out) {
3470
- const outDir = path9.dirname(path9.resolve(opts.out));
3999
+ const outDir = path12.dirname(path12.resolve(opts.out));
3471
4000
  if (!await pathExists(outDir)) {
3472
- process.stderr.write(chalk9.red(`Output directory does not exist: ${outDir}
4001
+ process.stderr.write(chalk11.red(`Output directory does not exist: ${outDir}
3473
4002
  `));
3474
4003
  process.exit(EXIT_USAGE_ERROR);
3475
4004
  }
3476
4005
  try {
3477
- const outStat = await fs2.stat(path9.resolve(opts.out));
4006
+ const outStat = await fs4.stat(path12.resolve(opts.out));
3478
4007
  if (outStat.isDirectory()) {
3479
- process.stderr.write(chalk9.red(`--out cannot be a directory: ${opts.out}
4008
+ process.stderr.write(chalk11.red(`--out cannot be a directory: ${opts.out}
3480
4009
  `));
3481
4010
  process.exit(EXIT_USAGE_ERROR);
3482
4011
  }
@@ -3485,16 +4014,16 @@ var extractCommand = new Command8("extract").description("Analyze source code an
3485
4014
  }
3486
4015
  let feedbackLines;
3487
4016
  if (opts.feedback) {
3488
- const feedbackPath = path9.resolve(opts.feedback);
4017
+ const feedbackPath = path12.resolve(opts.feedback);
3489
4018
  if (!await pathExists(feedbackPath)) {
3490
- process.stderr.write(chalk9.red(`Feedback file not found: ${feedbackPath}
4019
+ process.stderr.write(chalk11.red(`Feedback file not found: ${feedbackPath}
3491
4020
  `));
3492
4021
  process.exit(EXIT_USAGE_ERROR);
3493
4022
  }
3494
4023
  try {
3495
4024
  feedbackLines = await loadFeedback(feedbackPath);
3496
4025
  } catch (err) {
3497
- process.stderr.write(chalk9.red(`Invalid feedback file: ${err instanceof Error ? err.message : String(err)}
4026
+ process.stderr.write(chalk11.red(`Invalid feedback file: ${err instanceof Error ? err.message : String(err)}
3498
4027
  `));
3499
4028
  process.exit(EXIT_USAGE_ERROR);
3500
4029
  }
@@ -3504,9 +4033,9 @@ var extractCommand = new Command8("extract").description("Analyze source code an
3504
4033
  targetLanguages = opts.language.split(",").map((l) => normalizeLanguageToken(l)).filter(Boolean);
3505
4034
  for (const lang of targetLanguages) {
3506
4035
  if (!SUPPORTED_LANGUAGES.has(lang)) {
3507
- process.stderr.write(chalk9.red(`Unknown language: "${lang}"
4036
+ process.stderr.write(chalk11.red(`Unknown language: "${lang}"
3508
4037
  `));
3509
- process.stderr.write(chalk9.dim(`Supported: ${[...SUPPORTED_LANGUAGES].sort().join(", ")}
4038
+ process.stderr.write(chalk11.dim(`Supported: ${[...SUPPORTED_LANGUAGES].sort().join(", ")}
3510
4039
  `));
3511
4040
  process.exit(EXIT_USAGE_ERROR);
3512
4041
  }
@@ -3514,14 +4043,14 @@ var extractCommand = new Command8("extract").description("Analyze source code an
3514
4043
  } else {
3515
4044
  const detected = await detectLanguages(rootDir, opts.includeTests ?? false);
3516
4045
  if (detected.length === 0) {
3517
- process.stderr.write(chalk9.yellow("No supported source files detected.\n"));
4046
+ process.stderr.write(chalk11.yellow("No supported source files detected.\n"));
3518
4047
  process.exit(EXIT_SUCCESS);
3519
4048
  }
3520
4049
  targetLanguages = detected.map((d) => d.language);
3521
4050
  if (opts.verbose) {
3522
- process.stderr.write(chalk9.dim("Detected languages:\n"));
4051
+ process.stderr.write(chalk11.dim("Detected languages:\n"));
3523
4052
  for (const d of detected) {
3524
- process.stderr.write(chalk9.dim(` ${d.language}: ${d.fileCount} files
4053
+ process.stderr.write(chalk11.dim(` ${d.language}: ${d.fileCount} files
3525
4054
  `));
3526
4055
  }
3527
4056
  }
@@ -3529,20 +4058,20 @@ var extractCommand = new Command8("extract").description("Analyze source code an
3529
4058
  const runnableLanguages = targetLanguages.filter((l) => ALL_WORKER_LANGS.has(l));
3530
4059
  const unknownWorkerLangs = targetLanguages.filter((l) => !ALL_WORKER_LANGS.has(l));
3531
4060
  if (unknownWorkerLangs.length > 0 && opts.verbose) {
3532
- process.stderr.write(chalk9.dim(
4061
+ process.stderr.write(chalk11.dim(
3533
4062
  `No worker registered for: ${unknownWorkerLangs.join(", ")} \u2014 skipping.
3534
4063
  `
3535
4064
  ));
3536
4065
  }
3537
4066
  if (runnableLanguages.length === 0) {
3538
- process.stderr.write(chalk9.yellow("No languages with available HCS workers found.\n"));
4067
+ process.stderr.write(chalk11.yellow("No languages with available HCS workers found.\n"));
3539
4068
  process.exit(EXIT_SUCCESS);
3540
4069
  }
3541
4070
  const startTime = Date.now();
3542
4071
  const globalDeadline = startTime + timeoutMs;
3543
4072
  process.stderr.write(
3544
- chalk9.bold(`Extracting HCS facts from ${rootDir}
3545
- `) + chalk9.dim(`Languages: ${runnableLanguages.join(", ")} Concurrency: ${concurrency} Timeout: ${timeoutMins}m
4073
+ chalk11.bold(`Extracting HCS facts from ${rootDir}
4074
+ `) + chalk11.dim(`Languages: ${runnableLanguages.join(", ")} Concurrency: ${concurrency} Timeout: ${timeoutMins}m
3546
4075
  `)
3547
4076
  );
3548
4077
  const progress = new ProgressTracker(runnableLanguages);
@@ -3589,7 +4118,7 @@ var extractCommand = new Command8("extract").description("Analyze source code an
3589
4118
  hasSchemaFailure = true;
3590
4119
  allErrors.push(`[${language}] Schema validation: ${validation.error}`);
3591
4120
  if (opts.verbose) {
3592
- process.stderr.write(chalk9.red(`[${language}] Invalid fact: ${validation.error}
4121
+ process.stderr.write(chalk11.red(`[${language}] Invalid fact: ${validation.error}
3593
4122
  `));
3594
4123
  }
3595
4124
  }
@@ -3615,7 +4144,7 @@ var extractCommand = new Command8("extract").description("Analyze source code an
3615
4144
  progress.finish(elapsed);
3616
4145
  if (opts.derive) {
3617
4146
  const scanId = opts.scanId || "local";
3618
- const applicationId = opts.applicationId || path9.basename(rootDir);
4147
+ const applicationId = opts.applicationId || path12.basename(rootDir);
3619
4148
  const { envelopes, summary } = deriveBehaviouralFacts(derivedInputs, { scanId, applicationId });
3620
4149
  for (const env of envelopes) {
3621
4150
  const line = JSON.stringify(env);
@@ -3626,18 +4155,42 @@ var extractCommand = new Command8("extract").description("Analyze source code an
3626
4155
  allErrors.push("[behavioural] derived fact failed schema validation");
3627
4156
  }
3628
4157
  }
3629
- process.stderr.write(chalk9.dim(
4158
+ process.stderr.write(chalk11.dim(
3630
4159
  `Derived ${summary.assertions} behavioural assertion(s) \xB7 confidence ${summary.behaviouralConfidence} (${summary.assertedFacts}/${summary.surfaceFacts} surface facts)
3631
4160
  `
3632
4161
  ));
3633
4162
  }
3634
4163
  allFacts.sort();
3635
- const allLines = [...allPreamble, ...allFacts];
4164
+ const manifestScanId = opts.scanId || "local";
4165
+ const manifestApplicationId = opts.applicationId || (source === "url" ? deriveRepoName(manifestRemoteUrl) : void 0) || path12.basename(rootDir);
4166
+ let manifestVcs;
4167
+ try {
4168
+ manifestVcs = await detectVcs2(rootDir);
4169
+ } catch {
4170
+ manifestVcs = void 0;
4171
+ }
4172
+ const manifest = buildScanManifest({
4173
+ // Prefer the redacted URL we cloned from; fall back to the local repo's
4174
+ // configured remote (also credential-redacted) for non-URL scans.
4175
+ remoteUrl: manifestRemoteUrl ?? (manifestVcs?.remoteUrl ? redactRemoteUrl(manifestVcs.remoteUrl) : void 0),
4176
+ sha: manifestVcs?.sha,
4177
+ shortSha: manifestVcs?.shortSha,
4178
+ branch: manifestVcs?.branch,
4179
+ ref: opts.ref,
4180
+ source,
4181
+ languages: runnableLanguages,
4182
+ scanId: manifestScanId,
4183
+ applicationId: manifestApplicationId,
4184
+ vibgrateVersion: VERSION,
4185
+ factLines: allFacts
4186
+ });
4187
+ const manifestLine = JSON.stringify(manifest);
4188
+ const allLines = [manifestLine, ...allPreamble, ...allFacts];
3636
4189
  const ndjsonOutput = allLines.join("\n") + (allLines.length > 0 ? "\n" : "");
3637
4190
  if (opts.out) {
3638
- const outPath = path9.resolve(opts.out);
3639
- await fs2.writeFile(outPath, ndjsonOutput, "utf-8");
3640
- process.stderr.write(chalk9.green(`\u2714 Wrote ${allFacts.length} facts to ${outPath}
4191
+ const outPath = path12.resolve(opts.out);
4192
+ await fs4.writeFile(outPath, ndjsonOutput, "utf-8");
4193
+ process.stderr.write(chalk11.green(`\u2714 Wrote ${allFacts.length} facts to ${outPath}
3641
4194
  `));
3642
4195
  } else {
3643
4196
  process.stdout.write(ndjsonOutput);
@@ -3649,48 +4202,48 @@ var extractCommand = new Command8("extract").description("Analyze source code an
3649
4202
  }
3650
4203
  }
3651
4204
  if (opts.verbose) {
3652
- process.stderr.write(chalk9.dim("\n\u2500\u2500 Summary \u2500\u2500\n"));
3653
- process.stderr.write(chalk9.dim(` Facts emitted : ${allFacts.length}
4205
+ process.stderr.write(chalk11.dim("\n\u2500\u2500 Summary \u2500\u2500\n"));
4206
+ process.stderr.write(chalk11.dim(` Facts emitted : ${allFacts.length}
3654
4207
  `));
3655
- process.stderr.write(chalk9.dim(` Languages : ${runnableLanguages.join(", ")}
4208
+ process.stderr.write(chalk11.dim(` Languages : ${runnableLanguages.join(", ")}
3656
4209
  `));
3657
- process.stderr.write(chalk9.dim(` Elapsed : ${elapsed}s
4210
+ process.stderr.write(chalk11.dim(` Elapsed : ${elapsed}s
3658
4211
  `));
3659
4212
  if (allErrors.length > 0) {
3660
- process.stderr.write(chalk9.dim(` Errors : ${allErrors.length}
4213
+ process.stderr.write(chalk11.dim(` Errors : ${allErrors.length}
3661
4214
  `));
3662
4215
  for (const err of allErrors.slice(0, 10)) {
3663
- process.stderr.write(chalk9.dim(` ${err}
4216
+ process.stderr.write(chalk11.dim(` ${err}
3664
4217
  `));
3665
4218
  }
3666
4219
  if (allErrors.length > 10) {
3667
- process.stderr.write(chalk9.dim(` ... and ${allErrors.length - 10} more
4220
+ process.stderr.write(chalk11.dim(` ... and ${allErrors.length - 10} more
3668
4221
  `));
3669
4222
  }
3670
4223
  }
3671
- process.stderr.write(chalk9.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n"));
4224
+ process.stderr.write(chalk11.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n"));
3672
4225
  }
3673
4226
  if (hasTimeout) {
3674
- process.stderr.write(chalk9.red(`Timeout exceeded (${timeoutMins} minutes)
4227
+ process.stderr.write(chalk11.red(`Timeout exceeded (${timeoutMins} minutes)
3675
4228
  `));
3676
4229
  process.exit(EXIT_TIMEOUT);
3677
4230
  }
3678
4231
  if (hasSchemaFailure) {
3679
- process.stderr.write(chalk9.red("Fact schema validation failures detected\n"));
4232
+ process.stderr.write(chalk11.red("Fact schema validation failures detected\n"));
3680
4233
  process.exit(EXIT_SCHEMA_FAILURE);
3681
4234
  }
3682
4235
  if (hasParseFailure) {
3683
- process.stderr.write(chalk9.red("Parsing failures detected\n"));
4236
+ process.stderr.write(chalk11.red("Parsing failures detected\n"));
3684
4237
  process.exit(EXIT_PARSE_FAILURE);
3685
4238
  }
3686
4239
  });
3687
4240
 
3688
4241
  // src/commands/diff.ts
3689
- import * as path10 from "path";
3690
- import * as fs3 from "fs/promises";
3691
- import * as crypto3 from "crypto";
3692
- import { Command as Command9 } from "commander";
3693
- import chalk10 from "chalk";
4242
+ import * as path13 from "path";
4243
+ import * as fs5 from "fs/promises";
4244
+ import * as crypto4 from "crypto";
4245
+ import { Command as Command11 } from "commander";
4246
+ import chalk12 from "chalk";
3694
4247
  var EXIT_INVALID_PATH = 2;
3695
4248
  var EXIT_USAGE_ERROR2 = 5;
3696
4249
  var VALID_FORMATS = /* @__PURE__ */ new Set(["ndjson", "json", "patch"]);
@@ -3706,14 +4259,14 @@ async function discoverFiles(root, includeGlobs, excludeGlobs) {
3706
4259
  async function walk(dir, relBase) {
3707
4260
  let entries;
3708
4261
  try {
3709
- entries = await fs3.readdir(dir, { withFileTypes: true });
4262
+ entries = await fs5.readdir(dir, { withFileTypes: true });
3710
4263
  } catch {
3711
4264
  return;
3712
4265
  }
3713
4266
  for (const entry of entries) {
3714
4267
  if (entry.name.startsWith(".") && SKIP_DIRS2.has(entry.name)) continue;
3715
- const absPath = path10.join(dir, entry.name);
3716
- const relPath = path10.join(relBase, entry.name);
4268
+ const absPath = path13.join(dir, entry.name);
4269
+ const relPath = path13.join(relBase, entry.name);
3717
4270
  if (entry.isDirectory()) {
3718
4271
  if (!SKIP_DIRS2.has(entry.name)) {
3719
4272
  await walk(absPath, relPath);
@@ -3884,7 +4437,7 @@ function lcsRatio(a, b) {
3884
4437
  return 2 * lcsLen / (n + m);
3885
4438
  }
3886
4439
  function generateDeltaId(kind, filePath) {
3887
- const hash = crypto3.createHash("sha256").update(`${kind}:${filePath}`).digest("hex").substring(0, 16);
4440
+ const hash = crypto4.createHash("sha256").update(`${kind}:${filePath}`).digest("hex").substring(0, 16);
3888
4441
  return `hcs:delta:${hash}`;
3889
4442
  }
3890
4443
  function formatNdjson(deltas) {
@@ -3896,8 +4449,8 @@ function formatJson(deltas) {
3896
4449
  function formatPatch(deltas, originalPath, generatedPath) {
3897
4450
  const lines = [];
3898
4451
  for (const delta of deltas) {
3899
- const aFile = delta.originalPath ? path10.join("a", delta.originalPath) : "/dev/null";
3900
- const bFile = delta.generatedPath ? path10.join("b", delta.generatedPath) : "/dev/null";
4452
+ const aFile = delta.originalPath ? path13.join("a", delta.originalPath) : "/dev/null";
4453
+ const bFile = delta.generatedPath ? path13.join("b", delta.generatedPath) : "/dev/null";
3901
4454
  if (delta.changeKind === "renamed" && delta.originalPath && delta.generatedPath) {
3902
4455
  lines.push(`diff --vibgrate ${aFile} ${bFile}`);
3903
4456
  lines.push(`similarity index ${Math.round((delta.similarity ?? 0) * 100)}%`);
@@ -3918,38 +4471,38 @@ function formatPatch(deltas, originalPath, generatedPath) {
3918
4471
  }
3919
4472
  return lines.join("\n");
3920
4473
  }
3921
- var diffCommand = new Command9("diff").description("Compare two directory trees and emit structured delta facts").argument("<original_path>", "Original source directory").argument("<generated_path>", "Generated source directory").option("-o, --out <file>", "Output file path", "vibgrate.diff.ndjson").option("--format <fmt>", "Output format (ndjson|json|patch)", "ndjson").option("--ignore-whitespace", "Ignore whitespace-only changes").option("--context <n>", "Context lines for patch output", "3").option("--include <globs>", "Only include matching files (comma-separated)").option("--exclude <globs>", "Exclude matching files (comma-separated)").option("--stats", "Include insertions/deletions summary per file").option("--verbose", "Print file match decisions and rename detection").action(async (originalPath, generatedPath, opts) => {
3922
- const origDir = path10.resolve(originalPath);
3923
- const genDir = path10.resolve(generatedPath);
4474
+ var diffCommand = new Command11("diff").description("Compare two directory trees and emit structured delta facts").argument("<original_path>", "Original source directory").argument("<generated_path>", "Generated source directory").option("-o, --out <file>", "Output file path", "vibgrate.diff.ndjson").option("--format <fmt>", "Output format (ndjson|json|patch)", "ndjson").option("--ignore-whitespace", "Ignore whitespace-only changes").option("--context <n>", "Context lines for patch output", "3").option("--include <globs>", "Only include matching files (comma-separated)").option("--exclude <globs>", "Exclude matching files (comma-separated)").option("--stats", "Include insertions/deletions summary per file").option("--verbose", "Print file match decisions and rename detection").action(async (originalPath, generatedPath, opts) => {
4475
+ const origDir = path13.resolve(originalPath);
4476
+ const genDir = path13.resolve(generatedPath);
3924
4477
  for (const [label, dir] of [["original_path", origDir], ["generated_path", genDir]]) {
3925
4478
  if (!await pathExists(dir)) {
3926
- process.stderr.write(chalk10.red(`${label} does not exist: ${dir}
4479
+ process.stderr.write(chalk12.red(`${label} does not exist: ${dir}
3927
4480
  `));
3928
4481
  process.exit(EXIT_INVALID_PATH);
3929
4482
  }
3930
- let stat3;
4483
+ let stat4;
3931
4484
  try {
3932
- stat3 = await fs3.stat(dir);
4485
+ stat4 = await fs5.stat(dir);
3933
4486
  } catch {
3934
- process.stderr.write(chalk10.red(`Cannot read ${label}: ${dir}
4487
+ process.stderr.write(chalk12.red(`Cannot read ${label}: ${dir}
3935
4488
  `));
3936
4489
  process.exit(EXIT_INVALID_PATH);
3937
4490
  }
3938
- if (!stat3.isDirectory()) {
3939
- process.stderr.write(chalk10.red(`${label} must be a directory: ${dir}
4491
+ if (!stat4.isDirectory()) {
4492
+ process.stderr.write(chalk12.red(`${label} must be a directory: ${dir}
3940
4493
  `));
3941
4494
  process.exit(EXIT_INVALID_PATH);
3942
4495
  }
3943
4496
  }
3944
4497
  const format = opts.format.toLowerCase();
3945
4498
  if (!VALID_FORMATS.has(format)) {
3946
- process.stderr.write(chalk10.red(`Invalid format: "${opts.format}". Allowed: ndjson, json, patch
4499
+ process.stderr.write(chalk12.red(`Invalid format: "${opts.format}". Allowed: ndjson, json, patch
3947
4500
  `));
3948
4501
  process.exit(EXIT_USAGE_ERROR2);
3949
4502
  }
3950
4503
  const contextLines = parseInt(opts.context, 10);
3951
4504
  if (isNaN(contextLines) || contextLines < 0) {
3952
- process.stderr.write(chalk10.red("--context must be >= 0\n"));
4505
+ process.stderr.write(chalk12.red("--context must be >= 0\n"));
3953
4506
  process.exit(EXIT_USAGE_ERROR2);
3954
4507
  }
3955
4508
  const includeGlobs = opts.include ? opts.include.split(",").map((g) => g.trim()).filter(Boolean) : void 0;
@@ -3961,9 +4514,9 @@ var diffCommand = new Command9("diff").description("Compare two directory trees
3961
4514
  const origMap = new Map(origFiles.map((f) => [f.relPath, f]));
3962
4515
  const genMap = new Map(genFiles.map((f) => [f.relPath, f]));
3963
4516
  if (opts.verbose) {
3964
- process.stderr.write(chalk10.dim(`Original: ${origFiles.length} files
4517
+ process.stderr.write(chalk12.dim(`Original: ${origFiles.length} files
3965
4518
  `));
3966
- process.stderr.write(chalk10.dim(`Generated: ${genFiles.length} files
4519
+ process.stderr.write(chalk12.dim(`Generated: ${genFiles.length} files
3967
4520
  `));
3968
4521
  }
3969
4522
  const deltas = [];
@@ -3974,8 +4527,8 @@ var diffCommand = new Command9("diff").description("Compare two directory trees
3974
4527
  if (genFile) {
3975
4528
  matchedOrig.add(relPath);
3976
4529
  matchedGen.add(relPath);
3977
- const origContent = await fs3.readFile(origFile.absPath, "utf-8");
3978
- const genContent = await fs3.readFile(genFile.absPath, "utf-8");
4530
+ const origContent = await fs5.readFile(origFile.absPath, "utf-8");
4531
+ const genContent = await fs5.readFile(genFile.absPath, "utf-8");
3979
4532
  const origCompare = opts.ignoreWhitespace ? origContent.replace(/\s+/g, " ").trim() : origContent;
3980
4533
  const genCompare = opts.ignoreWhitespace ? genContent.replace(/\s+/g, " ").trim() : genContent;
3981
4534
  if (origCompare !== genCompare) {
@@ -3997,7 +4550,7 @@ var diffCommand = new Command9("diff").description("Compare two directory trees
3997
4550
  }
3998
4551
  deltas.push(delta);
3999
4552
  if (opts.verbose) {
4000
- process.stderr.write(chalk10.dim(` modified: ${relPath} (+${diffResult.insertions} -${diffResult.deletions})
4553
+ process.stderr.write(chalk12.dim(` modified: ${relPath} (+${diffResult.insertions} -${diffResult.deletions})
4001
4554
  `));
4002
4555
  }
4003
4556
  }
@@ -4014,13 +4567,13 @@ var diffCommand = new Command9("diff").description("Compare two directory trees
4014
4567
  await Promise.all([
4015
4568
  ...unmatchedOrig.map(async (f) => {
4016
4569
  try {
4017
- origContents.set(f.relPath, await fs3.readFile(f.absPath, "utf-8"));
4570
+ origContents.set(f.relPath, await fs5.readFile(f.absPath, "utf-8"));
4018
4571
  } catch {
4019
4572
  }
4020
4573
  }),
4021
4574
  ...unmatchedGen.map(async (f) => {
4022
4575
  try {
4023
- genContents.set(f.relPath, await fs3.readFile(f.absPath, "utf-8"));
4576
+ genContents.set(f.relPath, await fs5.readFile(f.absPath, "utf-8"));
4024
4577
  } catch {
4025
4578
  }
4026
4579
  })
@@ -4028,7 +4581,7 @@ var diffCommand = new Command9("diff").description("Compare two directory trees
4028
4581
  const pairs = [];
4029
4582
  for (const [origPath, origContent] of origContents) {
4030
4583
  for (const [genPath, genContent] of genContents) {
4031
- if (path10.extname(origPath) !== path10.extname(genPath)) continue;
4584
+ if (path13.extname(origPath) !== path13.extname(genPath)) continue;
4032
4585
  const similarity = computeSimilarity(origContent, genContent);
4033
4586
  if (similarity >= RENAME_THRESHOLD) {
4034
4587
  pairs.push({ origPath, genPath, similarity });
@@ -4061,7 +4614,7 @@ var diffCommand = new Command9("diff").description("Compare two directory trees
4061
4614
  }
4062
4615
  deltas.push(delta);
4063
4616
  if (opts.verbose) {
4064
- process.stderr.write(chalk10.dim(
4617
+ process.stderr.write(chalk12.dim(
4065
4618
  ` renamed: ${pair.origPath} \u2192 ${pair.genPath} (${(pair.similarity * 100).toFixed(0)}% similar)
4066
4619
  `
4067
4620
  ));
@@ -4078,7 +4631,7 @@ var diffCommand = new Command9("diff").description("Compare two directory trees
4078
4631
  };
4079
4632
  if (opts.stats) {
4080
4633
  try {
4081
- const content = await fs3.readFile(file.absPath, "utf-8");
4634
+ const content = await fs5.readFile(file.absPath, "utf-8");
4082
4635
  delta.stats = { insertions: 0, deletions: content.split("\n").length };
4083
4636
  } catch {
4084
4637
  delta.stats = { insertions: 0, deletions: 0 };
@@ -4086,7 +4639,7 @@ var diffCommand = new Command9("diff").description("Compare two directory trees
4086
4639
  }
4087
4640
  deltas.push(delta);
4088
4641
  if (opts.verbose) {
4089
- process.stderr.write(chalk10.dim(` removed: ${file.relPath}
4642
+ process.stderr.write(chalk12.dim(` removed: ${file.relPath}
4090
4643
  `));
4091
4644
  }
4092
4645
  }
@@ -4100,7 +4653,7 @@ var diffCommand = new Command9("diff").description("Compare two directory trees
4100
4653
  };
4101
4654
  if (opts.stats) {
4102
4655
  try {
4103
- const content = await fs3.readFile(file.absPath, "utf-8");
4656
+ const content = await fs5.readFile(file.absPath, "utf-8");
4104
4657
  delta.stats = { insertions: content.split("\n").length, deletions: 0 };
4105
4658
  } catch {
4106
4659
  delta.stats = { insertions: 0, deletions: 0 };
@@ -4108,7 +4661,7 @@ var diffCommand = new Command9("diff").description("Compare two directory trees
4108
4661
  }
4109
4662
  deltas.push(delta);
4110
4663
  if (opts.verbose) {
4111
- process.stderr.write(chalk10.dim(` added: ${file.relPath}
4664
+ process.stderr.write(chalk12.dim(` added: ${file.relPath}
4112
4665
  `));
4113
4666
  }
4114
4667
  }
@@ -4129,16 +4682,16 @@ var diffCommand = new Command9("diff").description("Compare two directory trees
4129
4682
  output = formatPatch(deltas, originalPath, generatedPath);
4130
4683
  break;
4131
4684
  }
4132
- const outPath = path10.resolve(opts.out);
4133
- await fs3.writeFile(outPath, output, "utf-8");
4685
+ const outPath = path13.resolve(opts.out);
4686
+ await fs5.writeFile(outPath, output, "utf-8");
4134
4687
  const added = deltas.filter((d) => d.changeKind === "added").length;
4135
4688
  const removed = deltas.filter((d) => d.changeKind === "removed").length;
4136
4689
  const modified = deltas.filter((d) => d.changeKind === "modified").length;
4137
4690
  const renamed = deltas.filter((d) => d.changeKind === "renamed").length;
4138
4691
  process.stderr.write(
4139
- chalk10.green(`\u2714 `) + `${deltas.length} changes: ` + chalk10.green(`+${added} added`) + ", " + chalk10.red(`-${removed} removed`) + ", " + chalk10.yellow(`~${modified} modified`) + ", " + chalk10.blue(`\u2192${renamed} renamed`) + "\n"
4692
+ chalk12.green(`\u2714 `) + `${deltas.length} changes: ` + chalk12.green(`+${added} added`) + ", " + chalk12.red(`-${removed} removed`) + ", " + chalk12.yellow(`~${modified} modified`) + ", " + chalk12.blue(`\u2192${renamed} renamed`) + "\n"
4140
4693
  );
4141
- process.stderr.write(chalk10.dim(` Written to ${outPath}
4694
+ process.stderr.write(chalk12.dim(` Written to ${outPath}
4142
4695
  `));
4143
4696
  if (opts.verbose && opts.stats) {
4144
4697
  let totalIns = 0;
@@ -4149,193 +4702,193 @@ var diffCommand = new Command9("diff").description("Compare two directory trees
4149
4702
  totalDel += d.stats.deletions;
4150
4703
  }
4151
4704
  }
4152
- process.stderr.write(chalk10.dim(` Total: +${totalIns} insertions, -${totalDel} deletions
4705
+ process.stderr.write(chalk12.dim(` Total: +${totalIns} insertions, -${totalDel} deletions
4153
4706
  `));
4154
4707
  }
4155
4708
  });
4156
4709
 
4157
4710
  // src/commands/help.ts
4158
- import { Command as Command10 } from "commander";
4159
- import chalk11 from "chalk";
4711
+ import { Command as Command12 } from "commander";
4712
+ import chalk13 from "chalk";
4160
4713
  var HELP_URL = "https://vibgrate.com/help";
4161
4714
  function printFooter() {
4162
4715
  console.log("");
4163
- console.log(chalk11.dim(`See ${HELP_URL} for more guidance`));
4716
+ console.log(chalk13.dim(`See ${HELP_URL} for more guidance`));
4164
4717
  }
4165
4718
  var detailedHelp = {
4166
4719
  scan: () => {
4167
4720
  console.log("");
4168
- console.log(chalk11.bold.underline("vibgrate scan") + chalk11.dim(" \u2014 Scan a project for upgrade drift"));
4721
+ console.log(chalk13.bold.underline("vibgrate scan") + chalk13.dim(" \u2014 Scan a project for upgrade drift"));
4169
4722
  console.log("");
4170
- console.log(chalk11.bold("Usage:"));
4723
+ console.log(chalk13.bold("Usage:"));
4171
4724
  console.log(" vibgrate scan [path] [options]");
4172
4725
  console.log("");
4173
- console.log(chalk11.bold("Arguments:"));
4174
- console.log(` ${chalk11.cyan("[path]")} Path to scan (default: current directory)`);
4726
+ console.log(chalk13.bold("Arguments:"));
4727
+ console.log(` ${chalk13.cyan("[path]")} Path to scan (default: current directory)`);
4175
4728
  console.log("");
4176
- console.log(chalk11.bold("Output options:"));
4177
- console.log(` ${chalk11.cyan("--format <format>")} Output format: ${chalk11.white("text")} | json | sarif | md (default: text)`);
4178
- console.log(` ${chalk11.cyan("--out <file>")} Write output to a file instead of stdout`);
4729
+ console.log(chalk13.bold("Output options:"));
4730
+ console.log(` ${chalk13.cyan("--format <format>")} Output format: ${chalk13.white("text")} | json | sarif | md (default: text)`);
4731
+ console.log(` ${chalk13.cyan("--out <file>")} Write output to a file instead of stdout`);
4179
4732
  console.log("");
4180
- console.log(chalk11.bold("Baseline & gating:"));
4181
- console.log(` ${chalk11.cyan("--baseline <file>")} Compare results against a saved baseline`);
4182
- console.log(` ${chalk11.cyan("--drift-budget <score>")} Fail if drift score exceeds this value (0\u2013100)`);
4183
- console.log(` ${chalk11.cyan("--drift-worsening <percent>")} Fail if drift worsens by more than % since baseline`);
4184
- console.log(` ${chalk11.cyan("--fail-on <level>")} Fail exit code on warn or error findings`);
4733
+ console.log(chalk13.bold("Baseline & gating:"));
4734
+ console.log(` ${chalk13.cyan("--baseline <file>")} Compare results against a saved baseline`);
4735
+ console.log(` ${chalk13.cyan("--drift-budget <score>")} Fail if drift score exceeds this value (0\u2013100)`);
4736
+ console.log(` ${chalk13.cyan("--drift-worsening <percent>")} Fail if drift worsens by more than % since baseline`);
4737
+ console.log(` ${chalk13.cyan("--fail-on <level>")} Fail exit code on warn or error findings`);
4185
4738
  console.log("");
4186
- console.log(chalk11.bold("Performance:"));
4187
- console.log(` ${chalk11.cyan("--concurrency <n>")} Max concurrent registry calls (default: 8)`);
4188
- console.log(` ${chalk11.cyan("--changed-only")} Only scan files changed since last git commit`);
4739
+ console.log(chalk13.bold("Performance:"));
4740
+ console.log(` ${chalk13.cyan("--concurrency <n>")} Max concurrent registry calls (default: 8)`);
4741
+ console.log(` ${chalk13.cyan("--changed-only")} Only scan files changed since last git commit`);
4189
4742
  console.log("");
4190
- console.log(chalk11.bold("Privacy & offline:"));
4191
- console.log(` ${chalk11.cyan("--offline")} Run without any network calls; skip result upload`);
4192
- console.log(` ${chalk11.cyan("--package-manifest <file>")} Use a local package-version manifest (JSON or ZIP) for offline mode`);
4193
- console.log(` ${chalk11.cyan("--no-local-artifacts")} Do not write .vibgrate JSON artifacts to disk`);
4194
- console.log(` ${chalk11.cyan("--max-privacy")} Strongest privacy mode: minimal scanners + no local artifacts`);
4743
+ console.log(chalk13.bold("Privacy & offline:"));
4744
+ console.log(` ${chalk13.cyan("--offline")} Run without any network calls; skip result upload`);
4745
+ console.log(` ${chalk13.cyan("--package-manifest <file>")} Use a local package-version manifest (JSON or ZIP) for offline mode`);
4746
+ console.log(` ${chalk13.cyan("--no-local-artifacts")} Do not write .vibgrate JSON artifacts to disk`);
4747
+ console.log(` ${chalk13.cyan("--max-privacy")} Strongest privacy mode: minimal scanners + no local artifacts`);
4195
4748
  console.log("");
4196
- console.log(chalk11.bold("Tooling:"));
4197
- console.log(` ${chalk11.cyan("--install-tools")} Auto-install missing security scanners via Homebrew`);
4198
- console.log(` ${chalk11.cyan("--ui-purpose")} Enable UI purpose evidence extraction (slower)`);
4749
+ console.log(chalk13.bold("Tooling:"));
4750
+ console.log(` ${chalk13.cyan("--install-tools")} Auto-install missing security scanners via Homebrew`);
4751
+ console.log(` ${chalk13.cyan("--ui-purpose")} Enable UI purpose evidence extraction (slower)`);
4199
4752
  console.log("");
4200
- console.log(chalk11.bold("Uploading results:"));
4201
- console.log(` ${chalk11.cyan("--push")} Auto-push results to Vibgrate API after scan`);
4202
- console.log(` ${chalk11.cyan("--dsn <dsn>")} DSN token for push (or set VIBGRATE_DSN env var)`);
4203
- console.log(` ${chalk11.cyan("--region <region>")} Data residency region: us | eu (default: us)`);
4204
- console.log(` ${chalk11.cyan("--strict")} Fail if the upload to Vibgrate API fails`);
4753
+ console.log(chalk13.bold("Uploading results:"));
4754
+ console.log(` ${chalk13.cyan("--push")} Auto-push results to Vibgrate API after scan`);
4755
+ console.log(` ${chalk13.cyan("--dsn <dsn>")} DSN token for push (or set VIBGRATE_DSN env var)`);
4756
+ console.log(` ${chalk13.cyan("--region <region>")} Data residency region: us | eu (default: us)`);
4757
+ console.log(` ${chalk13.cyan("--strict")} Fail if the upload to Vibgrate API fails`);
4205
4758
  console.log("");
4206
- console.log(chalk11.bold("Examples:"));
4207
- console.log(` ${chalk11.dim("# Scan the current directory and display a text report")}`);
4759
+ console.log(chalk13.bold("Examples:"));
4760
+ console.log(` ${chalk13.dim("# Scan the current directory and display a text report")}`);
4208
4761
  console.log(" vibgrate scan .");
4209
4762
  console.log("");
4210
- console.log(` ${chalk11.dim("# Scan, fail if drift score > 40, and write SARIF for GitHub Actions")}`);
4763
+ console.log(` ${chalk13.dim("# Scan, fail if drift score > 40, and write SARIF for GitHub Actions")}`);
4211
4764
  console.log(" vibgrate scan . --drift-budget 40 --format sarif --out drift.sarif");
4212
4765
  console.log("");
4213
- console.log(` ${chalk11.dim("# Scan and automatically upload results via a DSN")}`);
4766
+ console.log(` ${chalk13.dim("# Scan and automatically upload results via a DSN")}`);
4214
4767
  console.log(" vibgrate scan . --push --dsn $VIBGRATE_DSN");
4215
4768
  console.log("");
4216
- console.log(` ${chalk11.dim("# Offline scan using a pre-downloaded package manifest")}`);
4769
+ console.log(` ${chalk13.dim("# Offline scan using a pre-downloaded package manifest")}`);
4217
4770
  console.log(" vibgrate scan . --offline --package-manifest ./manifest.zip");
4218
4771
  },
4219
4772
  init: () => {
4220
4773
  console.log("");
4221
- console.log(chalk11.bold.underline("vibgrate init") + chalk11.dim(" \u2014 Initialise vibgrate in a project directory"));
4774
+ console.log(chalk13.bold.underline("vibgrate init") + chalk13.dim(" \u2014 Initialise vibgrate in a project directory"));
4222
4775
  console.log("");
4223
- console.log(chalk11.bold("Usage:"));
4776
+ console.log(chalk13.bold("Usage:"));
4224
4777
  console.log(" vibgrate init [path] [options]");
4225
4778
  console.log("");
4226
- console.log(chalk11.bold("Arguments:"));
4227
- console.log(` ${chalk11.cyan("[path]")} Directory to initialise (default: current directory)`);
4779
+ console.log(chalk13.bold("Arguments:"));
4780
+ console.log(` ${chalk13.cyan("[path]")} Directory to initialise (default: current directory)`);
4228
4781
  console.log("");
4229
- console.log(chalk11.bold("Options:"));
4230
- console.log(` ${chalk11.cyan("--baseline")} Create an initial drift baseline after init`);
4231
- console.log(` ${chalk11.cyan("--yes")} Skip all confirmation prompts`);
4782
+ console.log(chalk13.bold("Options:"));
4783
+ console.log(` ${chalk13.cyan("--baseline")} Create an initial drift baseline after init`);
4784
+ console.log(` ${chalk13.cyan("--yes")} Skip all confirmation prompts`);
4232
4785
  console.log("");
4233
- console.log(chalk11.bold("What it does:"));
4786
+ console.log(chalk13.bold("What it does:"));
4234
4787
  console.log(" \u2022 Creates a .vibgrate/ directory");
4235
4788
  console.log(" \u2022 Writes a vibgrate.config.ts starter config");
4236
4789
  console.log(" \u2022 Optionally runs an initial baseline scan (--baseline)");
4237
4790
  console.log("");
4238
- console.log(chalk11.bold("Examples:"));
4791
+ console.log(chalk13.bold("Examples:"));
4239
4792
  console.log(" vibgrate init");
4240
4793
  console.log(" vibgrate init ./my-project --baseline");
4241
4794
  },
4242
4795
  baseline: () => {
4243
4796
  console.log("");
4244
- console.log(chalk11.bold.underline("vibgrate baseline") + chalk11.dim(" \u2014 Save a drift baseline snapshot"));
4797
+ console.log(chalk13.bold.underline("vibgrate baseline") + chalk13.dim(" \u2014 Save a drift baseline snapshot"));
4245
4798
  console.log("");
4246
- console.log(chalk11.bold("Usage:"));
4799
+ console.log(chalk13.bold("Usage:"));
4247
4800
  console.log(" vibgrate baseline [path]");
4248
4801
  console.log("");
4249
- console.log(chalk11.bold("Arguments:"));
4250
- console.log(` ${chalk11.cyan("[path]")} Path to baseline (default: current directory)`);
4802
+ console.log(chalk13.bold("Arguments:"));
4803
+ console.log(` ${chalk13.cyan("[path]")} Path to baseline (default: current directory)`);
4251
4804
  console.log("");
4252
- console.log(chalk11.bold("What it does:"));
4805
+ console.log(chalk13.bold("What it does:"));
4253
4806
  console.log(" Runs a full scan and saves the result as .vibgrate/baseline.json.");
4254
4807
  console.log(" Future scans can compare against this file using --baseline.");
4255
4808
  console.log("");
4256
- console.log(chalk11.bold("Examples:"));
4809
+ console.log(chalk13.bold("Examples:"));
4257
4810
  console.log(" vibgrate baseline .");
4258
4811
  console.log(" vibgrate scan . --baseline .vibgrate/baseline.json --drift-worsening 10");
4259
4812
  },
4260
4813
  report: () => {
4261
4814
  console.log("");
4262
- console.log(chalk11.bold.underline("vibgrate report") + chalk11.dim(" \u2014 Generate a report from a saved scan artifact"));
4815
+ console.log(chalk13.bold.underline("vibgrate report") + chalk13.dim(" \u2014 Generate a report from a saved scan artifact"));
4263
4816
  console.log("");
4264
- console.log(chalk11.bold("Usage:"));
4817
+ console.log(chalk13.bold("Usage:"));
4265
4818
  console.log(" vibgrate report [options]");
4266
4819
  console.log("");
4267
- console.log(chalk11.bold("Options:"));
4268
- console.log(` ${chalk11.cyan("--in <file>")} Input artifact file (default: .vibgrate/scan_result.json)`);
4269
- console.log(` ${chalk11.cyan("--format <format>")} Output format: ${chalk11.white("text")} | md | json (default: text)`);
4820
+ console.log(chalk13.bold("Options:"));
4821
+ console.log(` ${chalk13.cyan("--in <file>")} Input artifact file (default: .vibgrate/scan_result.json)`);
4822
+ console.log(` ${chalk13.cyan("--format <format>")} Output format: ${chalk13.white("text")} | md | json (default: text)`);
4270
4823
  console.log("");
4271
- console.log(chalk11.bold("Examples:"));
4824
+ console.log(chalk13.bold("Examples:"));
4272
4825
  console.log(" vibgrate report");
4273
4826
  console.log(" vibgrate report --format md > DRIFT-REPORT.md");
4274
4827
  console.log(" vibgrate report --in ./ci/scan_result.json --format json");
4275
4828
  },
4276
4829
  sbom: () => {
4277
4830
  console.log("");
4278
- console.log(chalk11.bold.underline("vibgrate sbom") + chalk11.dim(" \u2014 Export a Software Bill of Materials from a scan artifact"));
4831
+ console.log(chalk13.bold.underline("vibgrate sbom") + chalk13.dim(" \u2014 Export a Software Bill of Materials from a scan artifact"));
4279
4832
  console.log("");
4280
- console.log(chalk11.bold("Usage:"));
4833
+ console.log(chalk13.bold("Usage:"));
4281
4834
  console.log(" vibgrate sbom [options]");
4282
4835
  console.log("");
4283
- console.log(chalk11.bold("Options:"));
4284
- console.log(` ${chalk11.cyan("--in <file>")} Input artifact (default: .vibgrate/scan_result.json)`);
4285
- console.log(` ${chalk11.cyan("--format <format>")} SBOM format: ${chalk11.white("cyclonedx")} | spdx (default: cyclonedx)`);
4286
- console.log(` ${chalk11.cyan("--out <file>")} Write SBOM to file instead of stdout`);
4836
+ console.log(chalk13.bold("Options:"));
4837
+ console.log(` ${chalk13.cyan("--in <file>")} Input artifact (default: .vibgrate/scan_result.json)`);
4838
+ console.log(` ${chalk13.cyan("--format <format>")} SBOM format: ${chalk13.white("cyclonedx")} | spdx (default: cyclonedx)`);
4839
+ console.log(` ${chalk13.cyan("--out <file>")} Write SBOM to file instead of stdout`);
4287
4840
  console.log("");
4288
- console.log(chalk11.bold("Examples:"));
4841
+ console.log(chalk13.bold("Examples:"));
4289
4842
  console.log(" vibgrate sbom --format cyclonedx --out sbom.json");
4290
4843
  console.log(" vibgrate sbom --format spdx --out sbom.spdx.json");
4291
4844
  },
4292
4845
  push: () => {
4293
4846
  console.log("");
4294
- console.log(chalk11.bold.underline("vibgrate push") + chalk11.dim(" \u2014 Upload a scan artifact to the Vibgrate API"));
4847
+ console.log(chalk13.bold.underline("vibgrate push") + chalk13.dim(" \u2014 Upload a scan artifact to the Vibgrate API"));
4295
4848
  console.log("");
4296
- console.log(chalk11.bold("Usage:"));
4849
+ console.log(chalk13.bold("Usage:"));
4297
4850
  console.log(" vibgrate push [options]");
4298
4851
  console.log("");
4299
- console.log(chalk11.bold("Options:"));
4300
- console.log(` ${chalk11.cyan("--dsn <dsn>")} DSN token (or set VIBGRATE_DSN env var)`);
4301
- console.log(` ${chalk11.cyan("--file <file>")} Artifact to upload (default: .vibgrate/scan_result.json)`);
4302
- console.log(` ${chalk11.cyan("--region <region>")} Override data residency region: us | eu`);
4303
- console.log(` ${chalk11.cyan("--strict")} Fail with non-zero exit code on upload error`);
4852
+ console.log(chalk13.bold("Options:"));
4853
+ console.log(` ${chalk13.cyan("--dsn <dsn>")} DSN token (or set VIBGRATE_DSN env var)`);
4854
+ console.log(` ${chalk13.cyan("--file <file>")} Artifact to upload (default: .vibgrate/scan_result.json)`);
4855
+ console.log(` ${chalk13.cyan("--region <region>")} Override data residency region: us | eu`);
4856
+ console.log(` ${chalk13.cyan("--strict")} Fail with non-zero exit code on upload error`);
4304
4857
  console.log("");
4305
- console.log(chalk11.bold("Examples:"));
4858
+ console.log(chalk13.bold("Examples:"));
4306
4859
  console.log(" vibgrate push --dsn $VIBGRATE_DSN");
4307
4860
  console.log(" vibgrate push --file ./ci/scan_result.json --strict");
4308
4861
  },
4309
4862
  dsn: () => {
4310
4863
  console.log("");
4311
- console.log(chalk11.bold.underline("vibgrate dsn") + chalk11.dim(" \u2014 Manage DSN tokens for API authentication"));
4864
+ console.log(chalk13.bold.underline("vibgrate dsn") + chalk13.dim(" \u2014 Manage DSN tokens for API authentication"));
4312
4865
  console.log("");
4313
- console.log(chalk11.bold("Subcommands:"));
4314
- console.log(` ${chalk11.cyan("vibgrate dsn create")} Generate a new DSN token`);
4866
+ console.log(chalk13.bold("Subcommands:"));
4867
+ console.log(` ${chalk13.cyan("vibgrate dsn create")} Generate a new DSN token`);
4315
4868
  console.log("");
4316
- console.log(chalk11.bold("dsn create options:"));
4317
- console.log(` ${chalk11.cyan("--workspace <id>")} Workspace ID or "new" to auto-generate ${chalk11.red("(required)")}`);
4318
- console.log(` ${chalk11.cyan("--region <region>")} Data residency region: us | eu (default: us)`);
4319
- console.log(` ${chalk11.cyan("--ingest <url>")} Override ingest API URL`);
4320
- console.log(` ${chalk11.cyan("--write <path>")} Write the DSN to a file (add to .gitignore!)`);
4869
+ console.log(chalk13.bold("dsn create options:"));
4870
+ console.log(` ${chalk13.cyan("--workspace <id>")} Workspace ID or "new" to auto-generate ${chalk13.red("(required)")}`);
4871
+ console.log(` ${chalk13.cyan("--region <region>")} Data residency region: us | eu (default: us)`);
4872
+ console.log(` ${chalk13.cyan("--ingest <url>")} Override ingest API URL`);
4873
+ console.log(` ${chalk13.cyan("--write <path>")} Write the DSN to a file (add to .gitignore!)`);
4321
4874
  console.log("");
4322
- console.log(chalk11.bold("Examples:"));
4875
+ console.log(chalk13.bold("Examples:"));
4323
4876
  console.log(" vibgrate dsn create --workspace new");
4324
4877
  console.log(" vibgrate dsn create --workspace abc123");
4325
4878
  console.log(" vibgrate dsn create --workspace new --region eu --write .vibgrate/.dsn");
4326
4879
  },
4327
4880
  update: () => {
4328
4881
  console.log("");
4329
- console.log(chalk11.bold.underline("vibgrate update") + chalk11.dim(" \u2014 Update the vibgrate CLI to the latest version"));
4882
+ console.log(chalk13.bold.underline("vibgrate update") + chalk13.dim(" \u2014 Update the vibgrate CLI to the latest version"));
4330
4883
  console.log("");
4331
- console.log(chalk11.bold("Usage:"));
4884
+ console.log(chalk13.bold("Usage:"));
4332
4885
  console.log(" vibgrate update [options]");
4333
4886
  console.log("");
4334
- console.log(chalk11.bold("Options:"));
4335
- console.log(` ${chalk11.cyan("--check")} Check for a newer version without installing`);
4336
- console.log(` ${chalk11.cyan("--pm <manager>")} Force a package manager: npm | pnpm | yarn | bun`);
4887
+ console.log(chalk13.bold("Options:"));
4888
+ console.log(` ${chalk13.cyan("--check")} Check for a newer version without installing`);
4889
+ console.log(` ${chalk13.cyan("--pm <manager>")} Force a package manager: npm | pnpm | yarn | bun`);
4337
4890
  console.log("");
4338
- console.log(chalk11.bold("Examples:"));
4891
+ console.log(chalk13.bold("Examples:"));
4339
4892
  console.log(" vibgrate update");
4340
4893
  console.log(" vibgrate update --check");
4341
4894
  console.log(" vibgrate update --pm pnpm");
@@ -4343,40 +4896,42 @@ var detailedHelp = {
4343
4896
  };
4344
4897
  function printSummaryHelp() {
4345
4898
  console.log("");
4346
- console.log(chalk11.bold("vibgrate") + chalk11.dim(" \u2014 Continuous Drift Intelligence"));
4899
+ console.log(chalk13.bold("vibgrate") + chalk13.dim(" \u2014 Continuous Drift Intelligence"));
4347
4900
  console.log("");
4348
- console.log(chalk11.bold("Usage:"));
4901
+ console.log(chalk13.bold("Usage:"));
4349
4902
  console.log(" vibgrate <command> [options]");
4350
4903
  console.log(" vibgrate help [command] Show detailed help for a command");
4351
4904
  console.log("");
4352
- console.log(chalk11.bold("Getting started:"));
4353
- console.log(` ${chalk11.cyan("init")} Initialise vibgrate in a project (creates config & .vibgrate/ dir)`);
4905
+ console.log(chalk13.bold("Getting started:"));
4906
+ console.log(` ${chalk13.cyan("init")} Initialise vibgrate in a project (creates config & .vibgrate/ dir)`);
4907
+ console.log(` ${chalk13.cyan("login")} Sign the CLI into your workspace via the browser`);
4908
+ console.log(` ${chalk13.cyan("logout")} Clear stored login credentials`);
4354
4909
  console.log("");
4355
- console.log(chalk11.bold("Core scanning:"));
4356
- console.log(` ${chalk11.cyan("scan")} Scan a project for upgrade drift and generate a report`);
4357
- console.log(` ${chalk11.cyan("baseline")} Save a baseline snapshot to compare future scans against`);
4910
+ console.log(chalk13.bold("Core scanning:"));
4911
+ console.log(` ${chalk13.cyan("scan")} Scan a project for upgrade drift and generate a report`);
4912
+ console.log(` ${chalk13.cyan("baseline")} Save a baseline snapshot to compare future scans against`);
4358
4913
  console.log("");
4359
- console.log(chalk11.bold("Reporting & export:"));
4360
- console.log(` ${chalk11.cyan("report")} Re-generate a report from a previously saved scan artifact`);
4361
- console.log(` ${chalk11.cyan("sbom")} Export a Software Bill of Materials (CycloneDX or SPDX)`);
4914
+ console.log(chalk13.bold("Reporting & export:"));
4915
+ console.log(` ${chalk13.cyan("report")} Re-generate a report from a previously saved scan artifact`);
4916
+ console.log(` ${chalk13.cyan("sbom")} Export a Software Bill of Materials (CycloneDX or SPDX)`);
4362
4917
  console.log("");
4363
- console.log(chalk11.bold("CI/CD integration:"));
4364
- console.log(` ${chalk11.cyan("push")} Upload a scan artifact to the Vibgrate API`);
4365
- console.log(` ${chalk11.cyan("dsn")} Create and manage DSN tokens for API authentication`);
4918
+ console.log(chalk13.bold("CI/CD integration:"));
4919
+ console.log(` ${chalk13.cyan("push")} Upload a scan artifact to the Vibgrate API`);
4920
+ console.log(` ${chalk13.cyan("dsn")} Create and manage DSN tokens for API authentication`);
4366
4921
  console.log("");
4367
- console.log(chalk11.bold("Maintenance:"));
4368
- console.log(` ${chalk11.cyan("update")} Update the vibgrate CLI to the latest version`);
4922
+ console.log(chalk13.bold("Maintenance:"));
4923
+ console.log(` ${chalk13.cyan("update")} Update the vibgrate CLI to the latest version`);
4369
4924
  console.log("");
4370
- console.log(chalk11.dim("Run") + ` ${chalk11.cyan("vibgrate help <command>")} ` + chalk11.dim("for detailed options, e.g.") + ` ${chalk11.cyan("vibgrate help scan")}`);
4925
+ console.log(chalk13.dim("Run") + ` ${chalk13.cyan("vibgrate help <command>")} ` + chalk13.dim("for detailed options, e.g.") + ` ${chalk13.cyan("vibgrate help scan")}`);
4371
4926
  }
4372
- var helpCommand = new Command10("help").description("Show help for vibgrate commands").argument("[command]", "Command to show detailed help for").helpOption(false).action((cmd) => {
4927
+ var helpCommand = new Command12("help").description("Show help for vibgrate commands").argument("[command]", "Command to show detailed help for").helpOption(false).action((cmd) => {
4373
4928
  const name = cmd?.toLowerCase().trim();
4374
4929
  if (name && detailedHelp[name]) {
4375
4930
  detailedHelp[name]();
4376
4931
  } else if (name) {
4377
4932
  console.log("");
4378
- console.log(chalk11.red(`Unknown command: ${name}`));
4379
- console.log(chalk11.dim(`Available commands: ${Object.keys(detailedHelp).join(", ")}`));
4933
+ console.log(chalk13.red(`Unknown command: ${name}`));
4934
+ console.log(chalk13.dim(`Available commands: ${Object.keys(detailedHelp).join(", ")}`));
4380
4935
  printSummaryHelp();
4381
4936
  } else {
4382
4937
  printSummaryHelp();
@@ -4385,13 +4940,15 @@ var helpCommand = new Command10("help").description("Show help for vibgrate comm
4385
4940
  });
4386
4941
 
4387
4942
  // src/cli.ts
4388
- var program = new Command11();
4943
+ var program = new Command13();
4389
4944
  program.name("vibgrate").description("Continuous Drift Intelligence").version(VERSION).addHelpText("after", "\nSee https://vibgrate.com/help for more guidance");
4390
4945
  program.addCommand(helpCommand);
4391
4946
  program.addCommand(initCommand);
4392
4947
  program.addCommand(scanCommand);
4393
4948
  program.addCommand(baselineCommand);
4394
4949
  program.addCommand(reportCommand);
4950
+ program.addCommand(loginCommand);
4951
+ program.addCommand(logoutCommand);
4395
4952
  program.addCommand(dsnCommand);
4396
4953
  program.addCommand(pushCommand);
4397
4954
  program.addCommand(updateCommand);
@@ -4402,8 +4959,8 @@ function notifyIfUpdateAvailable() {
4402
4959
  void checkForUpdate().then((update) => {
4403
4960
  if (!update?.updateAvailable) return;
4404
4961
  console.error("");
4405
- console.error(chalk12.yellow(` Update available: ${update.current} \u2192 ${update.latest}`));
4406
- console.error(chalk12.dim(' Run "vibgrate update" to install the latest version.'));
4962
+ console.error(chalk14.yellow(` Update available: ${update.current} \u2192 ${update.latest}`));
4963
+ console.error(chalk14.dim(' Run "vibgrate update" to install the latest version.'));
4407
4964
  console.error("");
4408
4965
  }).catch(() => {
4409
4966
  });