@vibgrate/cli 2026.615.4 → 2026.618.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-73LQST7Q.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-BFNY2AXU.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 chalk15 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-YSQVPA3L.js");
52
+ const { runBaseline } = await import("./baseline-K2GQA6G5.js");
53
53
  await runBaseline(rootDir);
54
54
  }
55
55
  console.log("");
@@ -60,9 +60,9 @@ 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
- import chalk3 from "chalk";
65
+ import chalk4 from "chalk";
66
66
 
67
67
  // src/version.ts
68
68
  import { createRequire } from "module";
@@ -129,6 +129,9 @@ function dashHostForIngestHost(ingestHost) {
129
129
  const match = REGIONS.find((r) => r.ingestHost === ingestHost);
130
130
  return match?.dashHost ?? "dash.vibgrate.com";
131
131
  }
132
+ function ingestHostForRegionId(region) {
133
+ return findRegion(region.toLowerCase())?.ingestHost;
134
+ }
132
135
 
133
136
  // src/commands/dsn.ts
134
137
  async function provisionDsn(keyId, secret, workspaceId, ingestHost, region) {
@@ -210,6 +213,49 @@ dsnCommand.command("create").description("Create a new DSN token").option("--ing
210
213
  }
211
214
  });
212
215
 
216
+ // src/credentials.ts
217
+ import * as os from "os";
218
+ import * as path3 from "path";
219
+ import * as fs from "fs";
220
+ function credentialsDir() {
221
+ return path3.join(os.homedir(), ".vibgrate");
222
+ }
223
+ function credentialsPath() {
224
+ return path3.join(credentialsDir(), "credentials.json");
225
+ }
226
+ function readStoredCredentials() {
227
+ try {
228
+ const raw = fs.readFileSync(credentialsPath(), "utf8");
229
+ const parsed = JSON.parse(raw);
230
+ return parsed && typeof parsed.dsn === "string" && parsed.dsn ? parsed : null;
231
+ } catch {
232
+ return null;
233
+ }
234
+ }
235
+ function writeStoredCredentials(creds) {
236
+ const dir = credentialsDir();
237
+ fs.mkdirSync(dir, { recursive: true });
238
+ const file = credentialsPath();
239
+ fs.writeFileSync(file, JSON.stringify(creds, null, 2) + "\n", "utf8");
240
+ try {
241
+ fs.chmodSync(file, 384);
242
+ } catch {
243
+ }
244
+ }
245
+ function clearStoredCredentials() {
246
+ try {
247
+ fs.rmSync(credentialsPath());
248
+ return true;
249
+ } catch {
250
+ return false;
251
+ }
252
+ }
253
+ function resolveDsn(explicitDsn) {
254
+ if (explicitDsn) return explicitDsn;
255
+ if (process.env.VIBGRATE_DSN) return process.env.VIBGRATE_DSN;
256
+ return readStoredCredentials()?.dsn ?? void 0;
257
+ }
258
+
213
259
  // src/utils/ingest-id-output.ts
214
260
  function emitIngestIdLine(ingestId, options) {
215
261
  console.log(`VIBGRATE_INGEST_ID=${ingestId}`);
@@ -218,18 +264,62 @@ function emitIngestIdLine(ingestId, options) {
218
264
  }
219
265
  }
220
266
 
267
+ // src/utils/upload.ts
268
+ import chalk3 from "chalk";
269
+ function postOnce(input, host) {
270
+ const url = `${input.scheme}://${host}/v1/ingest/scan`;
271
+ return fetch(url, {
272
+ method: "POST",
273
+ headers: {
274
+ "Content-Type": "application/json",
275
+ "Content-Encoding": input.contentEncoding,
276
+ "X-Vibgrate-Timestamp": input.timestamp,
277
+ "Authorization": `VibgrateDSN ${input.keyId}:${input.secret}`,
278
+ "Connection": "close"
279
+ // Prevent keep-alive delays on exit
280
+ },
281
+ body: input.body
282
+ });
283
+ }
284
+ async function regionRedirectHost(response) {
285
+ try {
286
+ const payload = await response.clone().json();
287
+ if (payload?.code !== "REGION_MISMATCH" || !payload.region) return void 0;
288
+ return ingestHostForRegionId(payload.region);
289
+ } catch {
290
+ return void 0;
291
+ }
292
+ }
293
+ async function uploadScanArtifact(input) {
294
+ let host = input.host;
295
+ let response = await postOnce(input, host);
296
+ if (response.status === 409) {
297
+ const target = await regionRedirectHost(response);
298
+ if (target && target !== host) {
299
+ console.log(
300
+ chalk3.yellow(
301
+ `\u21BB Workspace is pinned to a different region \u2014 retrying upload to ${target}...`
302
+ )
303
+ );
304
+ host = target;
305
+ response = await postOnce(input, host);
306
+ }
307
+ }
308
+ return { response, host };
309
+ }
310
+
221
311
  // src/commands/scan.ts
222
312
  async function autoPush(artifact, rootDir, opts) {
223
- const dsn = opts.dsn || process.env.VIBGRATE_DSN;
313
+ const dsn = resolveDsn(opts.dsn);
224
314
  if (!dsn) {
225
- console.error(chalk3.red("No DSN provided for push."));
226
- console.error(chalk3.dim("Set VIBGRATE_DSN environment variable or use --dsn flag."));
315
+ console.error(chalk4.red("No DSN provided for push."));
316
+ console.error(chalk4.dim('Run "vibgrate login", set VIBGRATE_DSN, or use the --dsn flag.'));
227
317
  if (opts.strict) process.exit(1);
228
318
  return;
229
319
  }
230
320
  const parsed = parseDsn(dsn);
231
321
  if (!parsed) {
232
- console.error(chalk3.red("Invalid DSN format."));
322
+ console.error(chalk4.red("Invalid DSN format."));
233
323
  if (opts.strict) process.exit(1);
234
324
  return;
235
325
  }
@@ -240,29 +330,26 @@ async function autoPush(artifact, rootDir, opts) {
240
330
  try {
241
331
  host = resolveIngestHost(opts.region);
242
332
  } catch (e) {
243
- console.error(chalk3.red(e instanceof Error ? e.message : String(e)));
333
+ console.error(chalk4.red(e instanceof Error ? e.message : String(e)));
244
334
  if (opts.strict) process.exit(1);
245
335
  return;
246
336
  }
247
337
  }
248
- const url = `${parsed.scheme}://${host}/v1/ingest/scan`;
249
338
  const originalSize = JSON.stringify(artifact).length;
250
339
  const compressedSize = body.length;
251
340
  const ratio = ((1 - compressedSize / originalSize) * 100).toFixed(0);
252
- console.log(chalk3.dim(`Uploading to ${host}... (${(compressedSize / 1024).toFixed(0)} KB, ${ratio}% smaller)`));
341
+ console.log(chalk4.dim(`Uploading to ${host}... (${(compressedSize / 1024).toFixed(0)} KB, ${ratio}% smaller)`));
253
342
  try {
254
- const response = await fetch(url, {
255
- method: "POST",
256
- headers: {
257
- "Content-Type": "application/json",
258
- "Content-Encoding": contentEncoding,
259
- "X-Vibgrate-Timestamp": timestamp,
260
- "Authorization": `VibgrateDSN ${parsed.keyId}:${parsed.secret}`,
261
- "Connection": "close"
262
- // Prevent keep-alive delays on exit
263
- },
264
- body
343
+ const { response, host: uploadedHost } = await uploadScanArtifact({
344
+ scheme: parsed.scheme,
345
+ host,
346
+ keyId: parsed.keyId,
347
+ secret: parsed.secret,
348
+ body,
349
+ contentEncoding,
350
+ timestamp
265
351
  });
352
+ host = uploadedHost;
266
353
  if (!response.ok) {
267
354
  const text = await response.text();
268
355
  throw new Error(`HTTP ${response.status}: ${text}`);
@@ -270,36 +357,36 @@ async function autoPush(artifact, rootDir, opts) {
270
357
  const result = await response.json();
271
358
  if (result.unchanged) {
272
359
  console.log(
273
- chalk3.green("\u2714") + ` Repository unchanged since ${result.lastScannedAt ?? "last scan"} \u2014 skipped upload (no credit used).`
360
+ chalk4.green("\u2714") + ` Repository unchanged since ${result.lastScannedAt ?? "last scan"} \u2014 skipped upload (no credit used).`
274
361
  );
275
362
  if (result.previousIngestId) {
276
363
  emitIngestIdLine(result.previousIngestId, { unchanged: true });
277
- const dashUrl = `https://dash.vibgrate.com/${parsed.workspaceId}/scan/${result.previousIngestId}`;
278
- console.log(chalk3.dim(` Previous report: ${dashUrl}`));
364
+ const dashUrl = `https://${dashHostForIngestHost(host)}/${parsed.workspaceId}/scan/${result.previousIngestId}`;
365
+ console.log(chalk4.dim(` Previous report: ${dashUrl}`));
279
366
  } else if (opts.strict) {
280
- console.error(chalk3.red("Repository unchanged but no previous ingest id returned."));
367
+ console.error(chalk4.red("Repository unchanged but no previous ingest id returned."));
281
368
  process.exit(1);
282
369
  }
283
370
  return;
284
371
  }
285
- console.log(chalk3.green("\u2714") + ` Scan queued for processing (${result.ingestId ?? "ok"})`);
372
+ console.log(chalk4.green("\u2714") + ` Scan queued for processing (${result.ingestId ?? "ok"})`);
286
373
  if (result.ingestId) {
287
374
  emitIngestIdLine(result.ingestId);
288
- const dashUrl = `https://dash.vibgrate.com/${parsed.workspaceId}/scan/${result.ingestId}`;
375
+ const dashUrl = `https://${dashHostForIngestHost(host)}/${parsed.workspaceId}/scan/${result.ingestId}`;
289
376
  const CLEAR_LINE = process.platform === "win32" ? "\x1B[0G\x1B[2K" : "";
290
377
  console.log("");
291
- console.log(CLEAR_LINE + chalk3.dim(" Processing continues in the background. Results available shortly."));
378
+ console.log(CLEAR_LINE + chalk4.dim(" Processing continues in the background. Results available shortly."));
292
379
  console.log("");
293
- console.log(CLEAR_LINE + chalk3.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
294
- console.log(CLEAR_LINE + chalk3.bold(" \u{1F4CA} View Scan Report"));
295
- console.log(CLEAR_LINE + " " + chalk3.underline.cyan(dashUrl));
296
- console.log(CLEAR_LINE + chalk3.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
380
+ console.log(CLEAR_LINE + chalk4.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
381
+ console.log(CLEAR_LINE + chalk4.bold(" \u{1F4CA} View Scan Report"));
382
+ console.log(CLEAR_LINE + " " + chalk4.underline.cyan(dashUrl));
383
+ console.log(CLEAR_LINE + chalk4.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
297
384
  console.log("");
298
385
  console.log("");
299
386
  }
300
387
  } catch (e) {
301
388
  const msg = e instanceof Error ? e.message : String(e);
302
- console.error(chalk3.red(`Upload failed: ${msg}`));
389
+ console.error(chalk4.red(`Upload failed: ${msg}`));
303
390
  if (opts.strict) process.exit(1);
304
391
  }
305
392
  }
@@ -320,15 +407,16 @@ var scanCommand = new Command3("scan").description("Scan a project for upgrade d
320
407
  collectExcludes,
321
408
  []
322
409
  ).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);
410
+ const rootDir = path4.resolve(targetPath);
324
411
  if (!await pathExists2(rootDir)) {
325
- console.error(chalk3.red(`Path does not exist: ${rootDir}`));
412
+ console.error(chalk4.red(`Path does not exist: ${rootDir}`));
326
413
  process.exit(1);
327
414
  }
328
- const hasDsn = !!(opts.dsn || process.env.VIBGRATE_DSN);
415
+ const hasDsn = !!resolveDsn(opts.dsn);
329
416
  const willPush = !opts.offline && (opts.push || hasDsn);
417
+ let pinnedRegion;
330
418
  if (willPush && hasDsn) {
331
- const dsn = opts.dsn || process.env.VIBGRATE_DSN;
419
+ const dsn = resolveDsn(opts.dsn);
332
420
  const parsed = parseDsn(dsn);
333
421
  if (parsed) {
334
422
  const ingestHost = opts.region ? resolveIngestHost(opts.region) : parsed.host;
@@ -340,10 +428,20 @@ var scanCommand = new Command3("scan").description("Scan a project for upgrade d
340
428
  repositoryName,
341
429
  vcsSha: fingerprint.vcsSha
342
430
  });
431
+ pinnedRegion = preflight.region;
432
+ if (preflight.vm && !preflight.vm.allowed) {
433
+ console.error(chalk4.red(preflight.error ?? "VM meter usage exhausted"));
434
+ console.error(
435
+ chalk4.dim(
436
+ `VM minutes: ${preflight.vm.used}/${preflight.vm.limit} (${preflight.plan.label} plan) \u2014 enable overages or upgrade your plan.`
437
+ )
438
+ );
439
+ process.exit(1);
440
+ }
343
441
  if (preflight.status === "error" || !preflight.scans.allowed) {
344
- console.error(chalk3.red(preflight.error ?? "Scan ingestion not allowed for this workspace."));
442
+ console.error(chalk4.red(preflight.error ?? "Scan ingestion not allowed for this workspace."));
345
443
  console.error(
346
- chalk3.dim(
444
+ chalk4.dim(
347
445
  `Credits: ${preflight.scans.used}/${preflight.scans.limit} (${preflight.plan.label} plan)`
348
446
  )
349
447
  );
@@ -351,27 +449,27 @@ var scanCommand = new Command3("scan").description("Scan a project for upgrade d
351
449
  process.exit(1);
352
450
  }
353
451
  console.log(
354
- chalk3.dim(
452
+ chalk4.dim(
355
453
  `Plan: ${preflight.plan.label} \u2014 scan credits ${preflight.scans.used}/${preflight.scans.limit} this month`
356
454
  )
357
455
  );
358
456
  if (preflight.repository?.unchanged) {
359
457
  console.log(
360
- chalk3.green("\u2714") + ` Repository unchanged at ${preflight.repository.lastVcsSha?.slice(0, 7) ?? "same revision"} \u2014 skipping scan.`
458
+ chalk4.green("\u2714") + ` Repository unchanged at ${preflight.repository.lastVcsSha?.slice(0, 7) ?? "same revision"} \u2014 skipping scan.`
361
459
  );
362
460
  if (preflight.repository.lastIngestId) {
363
461
  emitIngestIdLine(preflight.repository.lastIngestId, { unchanged: true });
364
- const dashUrl = `https://dash.vibgrate.com/${parsed.workspaceId}/scan/${preflight.repository.lastIngestId}`;
365
- console.log(chalk3.dim(` Latest report: ${dashUrl}`));
462
+ const dashUrl = `https://${dashHostForIngestHost(ingestHost)}/${parsed.workspaceId}/scan/${preflight.repository.lastIngestId}`;
463
+ console.log(chalk4.dim(` Latest report: ${dashUrl}`));
366
464
  } else if (opts.strict) {
367
- console.error(chalk3.red("Repository unchanged but no previous ingest id available."));
465
+ console.error(chalk4.red("Repository unchanged but no previous ingest id available."));
368
466
  process.exit(1);
369
467
  }
370
468
  return;
371
469
  }
372
470
  } catch (e) {
373
471
  const msg = e instanceof Error ? e.message : String(e);
374
- console.error(chalk3.yellow(`Preflight check failed: ${msg}`));
472
+ console.error(chalk4.yellow(`Preflight check failed: ${msg}`));
375
473
  if (opts.strict) process.exit(1);
376
474
  }
377
475
  }
@@ -387,7 +485,9 @@ var scanCommand = new Command3("scan").description("Scan a project for upgrade d
387
485
  concurrency: parseInt(opts.concurrency, 10) || 8,
388
486
  push: opts.push,
389
487
  dsn: opts.dsn,
390
- region: opts.region,
488
+ // An explicit --region wins; otherwise route to the workspace's pinned
489
+ // region as reported by preflight.
490
+ region: opts.region ?? pinnedRegion,
391
491
  strict: opts.strict,
392
492
  uiPurpose: opts.uiPurpose,
393
493
  noLocalArtifacts: opts.noLocalArtifacts,
@@ -403,24 +503,24 @@ var scanCommand = new Command3("scan").description("Scan a project for upgrade d
403
503
  const hasErrors = artifact.findings.some((f) => f.level === "error");
404
504
  const hasWarnings = artifact.findings.some((f) => f.level === "warning");
405
505
  if (opts.failOn === "error" && hasErrors) {
406
- console.error(chalk3.red(`
506
+ console.error(chalk4.red(`
407
507
  Failing: ${artifact.findings.filter((f) => f.level === "error").length} error finding(s) detected.`));
408
508
  process.exit(2);
409
509
  }
410
510
  if (opts.failOn === "warn" && (hasErrors || hasWarnings)) {
411
- console.error(chalk3.red(`
511
+ console.error(chalk4.red(`
412
512
  Failing: findings detected at warn level or above.`));
413
513
  process.exit(2);
414
514
  }
415
515
  }
416
516
  if (scanOpts.driftBudget !== void 0 && artifact.drift.score > scanOpts.driftBudget) {
417
- console.error(chalk3.red(`
517
+ console.error(chalk4.red(`
418
518
  Failing fitness function: drift score ${artifact.drift.score}/100 exceeds budget ${scanOpts.driftBudget}.`));
419
519
  process.exit(2);
420
520
  }
421
521
  if (scanOpts.driftWorseningPercent !== void 0) {
422
522
  if (artifact.delta === void 0) {
423
- console.error(chalk3.red("\nFailing fitness function: --drift-worsening requires --baseline to compare against previous drift."));
523
+ console.error(chalk4.red("\nFailing fitness function: --drift-worsening requires --baseline to compare against previous drift."));
424
524
  process.exit(2);
425
525
  }
426
526
  if (artifact.delta > 0) {
@@ -428,7 +528,7 @@ Failing fitness function: drift score ${artifact.drift.score}/100 exceeds budget
428
528
  const denominator = Math.max(Math.abs(baselineScore), 1e-4);
429
529
  const worseningPercent = artifact.delta / denominator * 100;
430
530
  if (worseningPercent > scanOpts.driftWorseningPercent) {
431
- console.error(chalk3.red(`
531
+ console.error(chalk4.red(`
432
532
  Failing fitness function: drift worsened by ${worseningPercent.toFixed(2)}% (threshold ${scanOpts.driftWorseningPercent}%).`));
433
533
  process.exit(2);
434
534
  }
@@ -440,30 +540,30 @@ Failing fitness function: drift worsened by ${worseningPercent.toFixed(2)}% (thr
440
540
  });
441
541
 
442
542
  // src/commands/report.ts
443
- import * as path4 from "path";
543
+ import * as path5 from "path";
444
544
  import { Command as Command4 } from "commander";
445
- import chalk5 from "chalk";
545
+ import chalk6 from "chalk";
446
546
 
447
547
  // src/formatters/text.ts
448
- import chalk4 from "chalk";
548
+ import chalk5 from "chalk";
449
549
  function formatText(artifact) {
450
550
  const lines = [];
451
- const teal = chalk4.hex("#3FB0A4");
452
- const mint = chalk4.hex("#4FE3C1");
551
+ const teal = chalk5.hex("#3FB0A4");
552
+ const mint = chalk5.hex("#4FE3C1");
453
553
  lines.push("");
454
554
  lines.push(" " + teal("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u256E") + mint("\u279C"));
455
- lines.push(" " + chalk4.dim("\u2524") + teal("\u2502") + " " + mint("\u25FC") + " " + mint("\u25FC") + " " + teal("\u2502") + chalk4.dim("\u251C") + " " + chalk4.bold.white("vibgrate"));
456
- lines.push(" " + chalk4.dim("\u2524") + teal("\u2502") + " " + chalk4.dim("\u2581\u2581") + " " + teal("\u2502") + chalk4.dim("\u251C") + " " + chalk4.dim(`Drift Intelligence Engine v${VERSION}`));
555
+ lines.push(" " + chalk5.dim("\u2524") + teal("\u2502") + " " + mint("\u25FC") + " " + mint("\u25FC") + " " + teal("\u2502") + chalk5.dim("\u251C") + " " + chalk5.bold.white("vibgrate"));
556
+ lines.push(" " + chalk5.dim("\u2524") + teal("\u2502") + " " + chalk5.dim("\u2581\u2581") + " " + teal("\u2502") + chalk5.dim("\u251C") + " " + chalk5.dim(`Drift Intelligence Engine v${VERSION}`));
457
557
  lines.push(" " + teal("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
458
558
  lines.push("");
459
559
  lines.push(teal("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
460
- lines.push(teal("\u2551 ") + chalk4.bold.white("Vibgrate Drift Report") + teal(" \u2551"));
560
+ lines.push(teal("\u2551 ") + chalk5.bold.white("Vibgrate Drift Report") + teal(" \u2551"));
461
561
  lines.push(teal("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
462
562
  lines.push("");
463
563
  for (const project of artifact.projects) {
464
- lines.push(chalk4.bold(` \u2500\u2500 ${project.name} `) + chalk4.dim(`(${project.type}) ${project.path}`));
564
+ lines.push(chalk5.bold(` \u2500\u2500 ${project.name} `) + chalk5.dim(`(${project.type}) ${project.path}`));
465
565
  if (project.runtime) {
466
- const behindStr = project.runtimeMajorsBehind !== void 0 && project.runtimeMajorsBehind > 0 ? chalk4.yellow(` (${project.runtimeMajorsBehind} major${project.runtimeMajorsBehind > 1 ? "s" : ""} behind)`) : chalk4.green(" (current)");
566
+ const behindStr = project.runtimeMajorsBehind !== void 0 && project.runtimeMajorsBehind > 0 ? chalk5.yellow(` (${project.runtimeMajorsBehind} major${project.runtimeMajorsBehind > 1 ? "s" : ""} behind)`) : chalk5.green(" (current)");
467
567
  lines.push(` Runtime: ${project.runtime}${behindStr}`);
468
568
  }
469
569
  if (project.targetFramework) {
@@ -472,7 +572,7 @@ function formatText(artifact) {
472
572
  if (project.frameworks.length > 0) {
473
573
  lines.push(" Frameworks:");
474
574
  for (const fw of project.frameworks) {
475
- const lag = fw.majorsBehind !== null ? fw.majorsBehind === 0 ? chalk4.green("current") : chalk4.yellow(`${fw.majorsBehind} behind`) : chalk4.dim("unknown");
575
+ const lag = fw.majorsBehind !== null ? fw.majorsBehind === 0 ? chalk5.green("current") : chalk5.yellow(`${fw.majorsBehind} behind`) : chalk5.dim("unknown");
476
576
  lines.push(` ${fw.name}: ${fw.currentVersion ?? "?"} \u2192 ${fw.latestVersion ?? "?"} (${lag})`);
477
577
  }
478
578
  }
@@ -480,13 +580,13 @@ function formatText(artifact) {
480
580
  const total = b.current + b.oneBehind + b.twoPlusBehind + b.unknown;
481
581
  if (total > 0) {
482
582
  lines.push(" Dependencies:");
483
- lines.push(` ${chalk4.green(`${b.current} current`)} ${chalk4.yellow(`${b.oneBehind} 1-behind`)} ${chalk4.red(`${b.twoPlusBehind} 2+ behind`)} ${chalk4.dim(`${b.unknown} unknown`)}`);
583
+ lines.push(` ${chalk5.green(`${b.current} current`)} ${chalk5.yellow(`${b.oneBehind} 1-behind`)} ${chalk5.red(`${b.twoPlusBehind} 2+ behind`)} ${chalk5.dim(`${b.unknown} unknown`)}`);
484
584
  }
485
585
  lines.push("");
486
586
  }
487
587
  if (artifact.delta !== void 0) {
488
- const deltaStr = artifact.delta > 0 ? chalk4.green(`+${artifact.delta}`) : artifact.delta < 0 ? chalk4.red(`${artifact.delta}`) : chalk4.dim("0");
489
- lines.push(chalk4.bold(" Drift Delta: ") + deltaStr + " (vs baseline)");
588
+ const deltaStr = artifact.delta > 0 ? chalk5.green(`+${artifact.delta}`) : artifact.delta < 0 ? chalk5.red(`${artifact.delta}`) : chalk5.dim("0");
589
+ lines.push(chalk5.bold(" Drift Delta: ") + deltaStr + " (vs baseline)");
490
590
  lines.push("");
491
591
  }
492
592
  if (artifact.extended) {
@@ -497,30 +597,30 @@ function formatText(artifact) {
497
597
  const warnings = artifact.findings.filter((f) => f.level === "warning");
498
598
  const notes = artifact.findings.filter((f) => f.level === "note");
499
599
  const summary = [
500
- errors.length > 0 ? chalk4.red(`${errors.length} error${errors.length !== 1 ? "s" : ""}`) : "",
501
- warnings.length > 0 ? chalk4.yellow(`${warnings.length} warning${warnings.length !== 1 ? "s" : ""}`) : "",
502
- notes.length > 0 ? chalk4.blue(`${notes.length} note${notes.length !== 1 ? "s" : ""}`) : ""
503
- ].filter(Boolean).join(chalk4.dim(", "));
504
- lines.push(chalk4.bold.underline(` Findings`) + chalk4.dim(` (${summary})`));
600
+ errors.length > 0 ? chalk5.red(`${errors.length} error${errors.length !== 1 ? "s" : ""}`) : "",
601
+ warnings.length > 0 ? chalk5.yellow(`${warnings.length} warning${warnings.length !== 1 ? "s" : ""}`) : "",
602
+ notes.length > 0 ? chalk5.blue(`${notes.length} note${notes.length !== 1 ? "s" : ""}`) : ""
603
+ ].filter(Boolean).join(chalk5.dim(", "));
604
+ lines.push(chalk5.bold.underline(` Findings`) + chalk5.dim(` (${summary})`));
505
605
  for (const f of artifact.findings) {
506
- const icon = f.level === "error" ? chalk4.red("\u2716") : f.level === "warning" ? chalk4.yellow("\u26A0") : chalk4.blue("\u2139");
606
+ const icon = f.level === "error" ? chalk5.red("\u2716") : f.level === "warning" ? chalk5.yellow("\u26A0") : chalk5.blue("\u2139");
507
607
  lines.push(` ${icon} ${f.message}`);
508
- lines.push(chalk4.dim(` ${f.ruleId} in ${f.location}`));
608
+ lines.push(chalk5.dim(` ${f.ruleId} in ${f.location}`));
509
609
  }
510
610
  lines.push("");
511
611
  }
512
612
  const actions = generatePriorityActions(artifact);
513
613
  if (actions.length > 0) {
514
- lines.push(chalk4.bold.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
515
- lines.push(chalk4.bold.cyan("\u2551 Top Priority Actions \u2551"));
516
- lines.push(chalk4.bold.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
614
+ lines.push(chalk5.bold.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
615
+ lines.push(chalk5.bold.cyan("\u2551 Top Priority Actions \u2551"));
616
+ lines.push(chalk5.bold.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
517
617
  lines.push("");
518
618
  for (let i = 0; i < actions.length; i++) {
519
619
  const a = actions[i];
520
- const num = chalk4.bold.cyan(` ${i + 1}.`);
521
- lines.push(`${num} ${chalk4.bold(a.title)}`);
522
- lines.push(chalk4.dim(` ${a.explanation}`));
523
- if (a.impact) lines.push(` Impact: ${chalk4.green(a.impact)}`);
620
+ const num = chalk5.bold.cyan(` ${i + 1}.`);
621
+ lines.push(`${num} ${chalk5.bold(a.title)}`);
622
+ lines.push(chalk5.dim(` ${a.explanation}`));
623
+ if (a.impact) lines.push(` Impact: ${chalk5.green(a.impact)}`);
524
624
  lines.push("");
525
625
  }
526
626
  }
@@ -528,38 +628,38 @@ function formatText(artifact) {
528
628
  lines.push(...formatArchitectureDiagram(artifact.extended.architecture));
529
629
  }
530
630
  if (artifact.solutions && artifact.solutions.length > 0) {
531
- lines.push(chalk4.bold.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
532
- lines.push(chalk4.bold.cyan("\u2551 Solution Drift Summary \u2551"));
533
- lines.push(chalk4.bold.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
631
+ lines.push(chalk5.bold.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
632
+ lines.push(chalk5.bold.cyan("\u2551 Solution Drift Summary \u2551"));
633
+ lines.push(chalk5.bold.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
534
634
  lines.push("");
535
635
  for (const solution of artifact.solutions) {
536
636
  const solScore = solution.drift?.score;
537
- const color = typeof solScore === "number" ? solScore >= 70 ? chalk4.green : solScore >= 40 ? chalk4.yellow : chalk4.red : chalk4.dim;
538
- lines.push(` \u2022 ${solution.name} (${solution.projectPaths.length} projects) \u2014 ${typeof solScore === "number" ? color(`${solScore}/100`) : chalk4.dim("n/a")}`);
637
+ const color = typeof solScore === "number" ? solScore >= 70 ? chalk5.green : solScore >= 40 ? chalk5.yellow : chalk5.red : chalk5.dim;
638
+ lines.push(` \u2022 ${solution.name} (${solution.projectPaths.length} projects) \u2014 ${typeof solScore === "number" ? color(`${solScore}/100`) : chalk5.dim("n/a")}`);
539
639
  }
540
640
  lines.push("");
541
641
  }
542
- const scoreColor = artifact.drift.score >= 70 ? chalk4.green : artifact.drift.score >= 40 ? chalk4.yellow : chalk4.red;
543
- lines.push(chalk4.bold.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
544
- lines.push(chalk4.bold.cyan("\u2551 Drift Score Summary \u2551"));
545
- lines.push(chalk4.bold.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
642
+ const scoreColor = artifact.drift.score >= 70 ? chalk5.green : artifact.drift.score >= 40 ? chalk5.yellow : chalk5.red;
643
+ lines.push(chalk5.bold.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
644
+ lines.push(chalk5.bold.cyan("\u2551 Drift Score Summary \u2551"));
645
+ lines.push(chalk5.bold.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
546
646
  lines.push("");
547
- lines.push(chalk4.bold(" Drift Score: ") + scoreColor.bold(`${artifact.drift.score}/100`));
548
- lines.push(chalk4.bold(" Risk Level: ") + riskBadge(artifact.drift.riskLevel));
549
- lines.push(chalk4.bold(" Projects: ") + `${artifact.projects.length}`);
647
+ lines.push(chalk5.bold(" Drift Score: ") + scoreColor.bold(`${artifact.drift.score}/100`));
648
+ lines.push(chalk5.bold(" Risk Level: ") + riskBadge(artifact.drift.riskLevel));
649
+ lines.push(chalk5.bold(" Projects: ") + `${artifact.projects.length}`);
550
650
  if (artifact.vcs) {
551
651
  const vcsParts = [artifact.vcs.type];
552
652
  if (artifact.vcs.branch) vcsParts.push(artifact.vcs.branch);
553
- if (artifact.vcs.shortSha) vcsParts.push(chalk4.dim(artifact.vcs.shortSha));
554
- lines.push(chalk4.bold(" VCS: ") + vcsParts.join(" "));
653
+ if (artifact.vcs.shortSha) vcsParts.push(chalk5.dim(artifact.vcs.shortSha));
654
+ lines.push(chalk5.bold(" VCS: ") + vcsParts.join(" "));
555
655
  }
556
656
  lines.push("");
557
657
  const m = new Set(artifact.drift.measured ?? ["runtime", "framework", "dependency", "eol"]);
558
- lines.push(" " + chalk4.bold.underline("Score Breakdown"));
559
- lines.push(` Runtime: ${m.has("runtime") ? scoreBar(artifact.drift.components.runtimeScore) : chalk4.dim("n/a")}`);
560
- lines.push(` Frameworks: ${m.has("framework") ? scoreBar(artifact.drift.components.frameworkScore) : chalk4.dim("n/a")}`);
561
- lines.push(` Dependencies: ${m.has("dependency") ? scoreBar(artifact.drift.components.dependencyScore) : chalk4.dim("n/a")}`);
562
- lines.push(` EOL Risk: ${m.has("eol") ? scoreBar(artifact.drift.components.eolScore) : chalk4.dim("n/a")}`);
658
+ lines.push(" " + chalk5.bold.underline("Score Breakdown"));
659
+ lines.push(` Runtime: ${m.has("runtime") ? scoreBar(artifact.drift.components.runtimeScore) : chalk5.dim("n/a")}`);
660
+ lines.push(` Frameworks: ${m.has("framework") ? scoreBar(artifact.drift.components.frameworkScore) : chalk5.dim("n/a")}`);
661
+ lines.push(` Dependencies: ${m.has("dependency") ? scoreBar(artifact.drift.components.dependencyScore) : chalk5.dim("n/a")}`);
662
+ lines.push(` EOL Risk: ${m.has("eol") ? scoreBar(artifact.drift.components.eolScore) : chalk5.dim("n/a")}`);
563
663
  lines.push("");
564
664
  const scannedParts = [`Scanned at ${artifact.timestamp}`];
565
665
  if (artifact.durationMs !== void 0) {
@@ -573,18 +673,18 @@ function formatText(artifact) {
573
673
  scannedParts.push(`${artifact.treeSummary.totalFiles.toLocaleString()} workspace files`);
574
674
  scannedParts.push(`${artifact.treeSummary.totalDirs.toLocaleString()} dirs`);
575
675
  }
576
- lines.push(chalk4.dim(` ${scannedParts.join(" \xB7 ")}`));
676
+ lines.push(chalk5.dim(` ${scannedParts.join(" \xB7 ")}`));
577
677
  lines.push("");
578
678
  return lines.join("\n");
579
679
  }
580
680
  function riskBadge(level) {
581
681
  switch (level) {
582
682
  case "low":
583
- return chalk4.bgGreen.black(" LOW ");
683
+ return chalk5.bgGreen.black(" LOW ");
584
684
  case "moderate":
585
- return chalk4.bgYellow.black(" MODERATE ");
685
+ return chalk5.bgYellow.black(" MODERATE ");
586
686
  case "high":
587
- return chalk4.bgRed.white(" HIGH ");
687
+ return chalk5.bgRed.white(" HIGH ");
588
688
  default:
589
689
  return level;
590
690
  }
@@ -593,8 +693,8 @@ function scoreBar(score) {
593
693
  const width = 20;
594
694
  const filled = Math.round(score / 100 * width);
595
695
  const empty = width - filled;
596
- const color = score >= 70 ? chalk4.green : score >= 40 ? chalk4.yellow : chalk4.red;
597
- return color("\u2588".repeat(filled)) + chalk4.dim("\u2591".repeat(empty)) + ` ${Math.round(score)}`;
696
+ const color = score >= 70 ? chalk5.green : score >= 40 ? chalk5.yellow : chalk5.red;
697
+ return color("\u2588".repeat(filled)) + chalk5.dim("\u2591".repeat(empty)) + ` ${Math.round(score)}`;
598
698
  }
599
699
  var CATEGORY_LABELS = {
600
700
  frontend: "Frontend",
@@ -623,11 +723,11 @@ function formatExtended(ext) {
623
723
  const inv = ext.toolingInventory;
624
724
  const categories = Object.entries(inv).filter(([, items]) => items.length > 0);
625
725
  if (categories.length > 0) {
626
- lines.push(chalk4.bold.underline(" Tech Stack"));
726
+ lines.push(chalk5.bold.underline(" Tech Stack"));
627
727
  for (const [cat, items] of categories) {
628
728
  const label = CATEGORY_LABELS[cat] ?? cat;
629
- const names = items.map((i) => chalk4.white(i.name)).join(chalk4.dim(", "));
630
- lines.push(` ${chalk4.cyan(label)}: ${names}`);
729
+ const names = items.map((i) => chalk5.white(i.name)).join(chalk5.dim(", "));
730
+ lines.push(` ${chalk5.cyan(label)}: ${names}`);
631
731
  }
632
732
  lines.push("");
633
733
  }
@@ -636,14 +736,14 @@ function formatExtended(ext) {
636
736
  const svc = ext.serviceDependencies;
637
737
  const categories = Object.entries(svc).filter(([, items]) => items.length > 0);
638
738
  if (categories.length > 0) {
639
- lines.push(chalk4.bold.underline(" Services & Integrations"));
739
+ lines.push(chalk5.bold.underline(" Services & Integrations"));
640
740
  for (const [cat, items] of categories) {
641
741
  const label = CATEGORY_LABELS[cat] ?? cat;
642
742
  const names = items.map((i) => {
643
- const ver = i.version ? chalk4.dim(` ${i.version}`) : "";
644
- return chalk4.white(i.name) + ver;
645
- }).join(chalk4.dim(", "));
646
- lines.push(` ${chalk4.cyan(label)}: ${names}`);
743
+ const ver = i.version ? chalk5.dim(` ${i.version}`) : "";
744
+ return chalk5.white(i.name) + ver;
745
+ }).join(chalk5.dim(", "));
746
+ lines.push(` ${chalk5.cyan(label)}: ${names}`);
647
747
  }
648
748
  lines.push("");
649
749
  }
@@ -651,19 +751,19 @@ function formatExtended(ext) {
651
751
  if (ext.breakingChangeExposure) {
652
752
  const bc = ext.breakingChangeExposure;
653
753
  if (bc.deprecatedPackages.length > 0 || bc.legacyPolyfills.length > 0) {
654
- lines.push(chalk4.bold.underline(" Breaking Change Exposure"));
655
- const exposureColor = bc.exposureScore >= 40 ? chalk4.red : bc.exposureScore >= 20 ? chalk4.yellow : chalk4.green;
754
+ lines.push(chalk5.bold.underline(" Breaking Change Exposure"));
755
+ const exposureColor = bc.exposureScore >= 40 ? chalk5.red : bc.exposureScore >= 20 ? chalk5.yellow : chalk5.green;
656
756
  lines.push(` Exposure Score: ${exposureColor.bold(`${bc.exposureScore}/100`)}`);
657
757
  if (bc.deprecatedPackages.length > 0) {
658
- lines.push(` ${chalk4.red("Deprecated")}: ${bc.deprecatedPackages.map((p) => chalk4.dim(p)).join(", ")}`);
758
+ lines.push(` ${chalk5.red("Deprecated")}: ${bc.deprecatedPackages.map((p) => chalk5.dim(p)).join(", ")}`);
659
759
  }
660
760
  if (bc.legacyPolyfills.length > 0) {
661
- lines.push(` ${chalk4.yellow("Polyfills")}: ${bc.legacyPolyfills.map((p) => chalk4.dim(p)).join(", ")}`);
761
+ lines.push(` ${chalk5.yellow("Polyfills")}: ${bc.legacyPolyfills.map((p) => chalk5.dim(p)).join(", ")}`);
662
762
  }
663
763
  if (bc.peerConflictsDetected) {
664
- lines.push(` ${chalk4.red("\u26A0")} Peer dependency conflicts detected`);
764
+ lines.push(` ${chalk5.red("\u26A0")} Peer dependency conflicts detected`);
665
765
  }
666
- lines.push(` Recommendation: ${chalk4.bold(bc.overallRecommendation)}`);
766
+ lines.push(` Recommendation: ${chalk5.bold(bc.overallRecommendation)}`);
667
767
  const projectsWithPlans = bc.projectIntelligence.filter((p) => p.packages.length > 0).slice(0, 3);
668
768
  if (projectsWithPlans.length > 0) {
669
769
  lines.push(" Major Upgrade Intelligence:");
@@ -679,21 +779,21 @@ function formatExtended(ext) {
679
779
  }
680
780
  if (ext.tsModernity && ext.tsModernity.typescriptVersion) {
681
781
  const ts = ext.tsModernity;
682
- lines.push(chalk4.bold.underline(" TypeScript"));
782
+ lines.push(chalk5.bold.underline(" TypeScript"));
683
783
  const parts = [];
684
784
  parts.push(`v${ts.typescriptVersion}`);
685
- if (ts.strict === true) parts.push(chalk4.green("strict \u2714"));
686
- else if (ts.strict === false) parts.push(chalk4.yellow("strict \u2716"));
785
+ if (ts.strict === true) parts.push(chalk5.green("strict \u2714"));
786
+ else if (ts.strict === false) parts.push(chalk5.yellow("strict \u2716"));
687
787
  if (ts.moduleType) parts.push(ts.moduleType.toUpperCase());
688
788
  if (ts.target) parts.push(`target: ${ts.target}`);
689
- lines.push(` ${parts.join(chalk4.dim(" \xB7 "))}`);
789
+ lines.push(` ${parts.join(chalk5.dim(" \xB7 "))}`);
690
790
  lines.push("");
691
791
  }
692
792
  if (ext.buildDeploy) {
693
793
  const bd = ext.buildDeploy;
694
794
  const hasSomething = bd.ci.length > 0 || bd.docker.dockerfileCount > 0 || bd.packageManagers.length > 0;
695
795
  if (hasSomething) {
696
- lines.push(chalk4.bold.underline(" Build & Deploy"));
796
+ lines.push(chalk5.bold.underline(" Build & Deploy"));
697
797
  if (bd.ci.length > 0) lines.push(` CI: ${bd.ci.join(", ")}`);
698
798
  if (bd.docker.dockerfileCount > 0) {
699
799
  lines.push(` Docker: ${bd.docker.dockerfileCount} Dockerfile${bd.docker.dockerfileCount !== 1 ? "s" : ""} (${bd.docker.baseImages.join(", ")})`);
@@ -706,42 +806,42 @@ function formatExtended(ext) {
706
806
  }
707
807
  if (ext.uiPurpose) {
708
808
  const up = ext.uiPurpose;
709
- lines.push(chalk4.bold.underline(" Product Purpose Signals"));
710
- lines.push(` Frameworks: ${up.detectedFrameworks.length > 0 ? up.detectedFrameworks.join(", ") : chalk4.dim("unknown")}`);
711
- lines.push(` Evidence: ${up.topEvidence.length}${up.capped ? chalk4.dim(` of ${up.evidenceCount} (capped)`) : ""}`);
809
+ lines.push(chalk5.bold.underline(" Product Purpose Signals"));
810
+ lines.push(` Frameworks: ${up.detectedFrameworks.length > 0 ? up.detectedFrameworks.join(", ") : chalk5.dim("unknown")}`);
811
+ lines.push(` Evidence: ${up.topEvidence.length}${up.capped ? chalk5.dim(` of ${up.evidenceCount} (capped)`) : ""}`);
712
812
  const top = up.topEvidence.slice(0, 8);
713
813
  if (top.length > 0) {
714
814
  lines.push(" Top Signals:");
715
815
  for (const item of top) {
716
- lines.push(` - [${item.kind}] ${item.value} ${chalk4.dim(`(${item.file})`)}`);
816
+ lines.push(` - [${item.kind}] ${item.value} ${chalk5.dim(`(${item.file})`)}`);
717
817
  }
718
818
  }
719
819
  if (up.unknownSignals.length > 0) {
720
820
  lines.push(" Unknowns:");
721
821
  for (const u of up.unknownSignals.slice(0, 4)) {
722
- lines.push(` - ${chalk4.yellow(u)}`);
822
+ lines.push(` - ${chalk5.yellow(u)}`);
723
823
  }
724
824
  }
725
825
  lines.push("");
726
826
  }
727
827
  if (ext.securityPosture) {
728
828
  const sec = ext.securityPosture;
729
- lines.push(chalk4.bold.underline(" Security Posture"));
829
+ lines.push(chalk5.bold.underline(" Security Posture"));
730
830
  const checks = [];
731
- checks.push(sec.lockfilePresent ? chalk4.green("Lockfile \u2714") : chalk4.red("Lockfile \u2716"));
732
- checks.push(sec.gitignoreCoversEnv ? chalk4.green(".env \u2714") : chalk4.red(".env \u2716"));
733
- checks.push(sec.gitignoreCoversNodeModules ? chalk4.green("node_modules \u2714") : chalk4.yellow("node_modules \u2716"));
734
- if (sec.multipleLockfileTypes) checks.push(chalk4.yellow("Multiple lockfiles \u26A0"));
735
- if (sec.envFilesTracked) checks.push(chalk4.red("Env files tracked \u2716"));
736
- lines.push(` ${checks.join(chalk4.dim(" \xB7 "))}`);
831
+ checks.push(sec.lockfilePresent ? chalk5.green("Lockfile \u2714") : chalk5.red("Lockfile \u2716"));
832
+ checks.push(sec.gitignoreCoversEnv ? chalk5.green(".env \u2714") : chalk5.red(".env \u2716"));
833
+ checks.push(sec.gitignoreCoversNodeModules ? chalk5.green("node_modules \u2714") : chalk5.yellow("node_modules \u2716"));
834
+ if (sec.multipleLockfileTypes) checks.push(chalk5.yellow("Multiple lockfiles \u26A0"));
835
+ if (sec.envFilesTracked) checks.push(chalk5.red("Env files tracked \u2716"));
836
+ lines.push(` ${checks.join(chalk5.dim(" \xB7 "))}`);
737
837
  lines.push("");
738
838
  }
739
839
  if (ext.platformMatrix) {
740
840
  const pm = ext.platformMatrix;
741
841
  if (pm.nativeModules.length > 0 || pm.dockerBaseImages.length > 0) {
742
- lines.push(chalk4.bold.underline(" Platform"));
842
+ lines.push(chalk5.bold.underline(" Platform"));
743
843
  if (pm.nativeModules.length > 0) {
744
- lines.push(` Native modules: ${pm.nativeModules.map((m) => chalk4.dim(m)).join(", ")}`);
844
+ lines.push(` Native modules: ${pm.nativeModules.map((m) => chalk5.dim(m)).join(", ")}`);
745
845
  }
746
846
  if (pm.osAssumptions.length > 0) {
747
847
  lines.push(` OS assumptions: ${pm.osAssumptions.join(", ")}`);
@@ -751,25 +851,25 @@ function formatExtended(ext) {
751
851
  }
752
852
  if (ext.codeQuality) {
753
853
  const cq = ext.codeQuality;
754
- lines.push(chalk4.bold.underline(" Code Quality"));
755
- lines.push(` Files: ${chalk4.white(`${cq.filesAnalyzed}`)} \xB7 Functions: ${chalk4.white(`${cq.functionsAnalyzed}`)} \xB7 Avg complexity: ${chalk4.white(`${cq.avgCyclomaticComplexity}`)} \xB7 Avg length: ${chalk4.white(`${cq.avgFunctionLength}`)} lines`);
854
+ lines.push(chalk5.bold.underline(" Code Quality"));
855
+ lines.push(` Files: ${chalk5.white(`${cq.filesAnalyzed}`)} \xB7 Functions: ${chalk5.white(`${cq.functionsAnalyzed}`)} \xB7 Avg complexity: ${chalk5.white(`${cq.avgCyclomaticComplexity}`)} \xB7 Avg length: ${chalk5.white(`${cq.avgFunctionLength}`)} lines`);
756
856
  lines.push(` Max nesting: ${cq.maxNestingDepth} \xB7 Circular deps: ${cq.circularDependencies} \xB7 Dead code: ${cq.deadCodePercent}%`);
757
857
  if (cq.godFiles.length > 0) {
758
858
  const preview = cq.godFiles.slice(0, 3).map((f) => `${f.path} (${f.lines} lines)`).join(", ");
759
- lines.push(` ${chalk4.yellow("God files")}: ${preview}`);
859
+ lines.push(` ${chalk5.yellow("God files")}: ${preview}`);
760
860
  }
761
861
  lines.push("");
762
862
  }
763
863
  if (ext.dependencyGraph) {
764
864
  const dg = ext.dependencyGraph;
765
865
  if (dg.lockfileType) {
766
- lines.push(chalk4.bold.underline(" Dependency Graph"));
767
- lines.push(` ${dg.lockfileType}: ${chalk4.white(`${dg.totalUnique}`)} unique, ${chalk4.white(`${dg.totalInstalled}`)} installed`);
866
+ lines.push(chalk5.bold.underline(" Dependency Graph"));
867
+ lines.push(` ${dg.lockfileType}: ${chalk5.white(`${dg.totalUnique}`)} unique, ${chalk5.white(`${dg.totalInstalled}`)} installed`);
768
868
  if (dg.duplicatedPackages.length > 0) {
769
- lines.push(` ${chalk4.yellow(`${dg.duplicatedPackages.length} duplicated`)} packages`);
869
+ lines.push(` ${chalk5.yellow(`${dg.duplicatedPackages.length} duplicated`)} packages`);
770
870
  }
771
871
  if (dg.phantomDependencies.length > 0) {
772
- lines.push(` ${chalk4.red(`${dg.phantomDependencies.length} phantom`)} dependencies`);
872
+ lines.push(` ${chalk5.red(`${dg.phantomDependencies.length} phantom`)} dependencies`);
773
873
  }
774
874
  lines.push("");
775
875
  }
@@ -778,17 +878,17 @@ function formatExtended(ext) {
778
878
  }
779
879
  function formatArchitectureDiagram(arch) {
780
880
  const lines = [];
781
- lines.push(chalk4.bold.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
782
- lines.push(chalk4.bold.cyan("\u2551 Architecture Layers \u2551"));
783
- lines.push(chalk4.bold.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
881
+ lines.push(chalk5.bold.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
882
+ lines.push(chalk5.bold.cyan("\u2551 Architecture Layers \u2551"));
883
+ lines.push(chalk5.bold.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
784
884
  lines.push("");
785
- lines.push(chalk4.bold(" Archetype: ") + `${arch.archetype}` + chalk4.dim(` (${Math.round(arch.archetypeConfidence * 100)}% confidence)`));
786
- lines.push(` Files classified: ${arch.totalClassified}` + (arch.unclassified > 0 ? chalk4.dim(` (${arch.unclassified} unclassified)`) : ""));
885
+ lines.push(chalk5.bold(" Archetype: ") + `${arch.archetype}` + chalk5.dim(` (${Math.round(arch.archetypeConfidence * 100)}% confidence)`));
886
+ lines.push(` Files classified: ${arch.totalClassified}` + (arch.unclassified > 0 ? chalk5.dim(` (${arch.unclassified} unclassified)`) : ""));
787
887
  lines.push("");
788
888
  if (arch.layers.length > 0) {
789
889
  for (const layer of arch.layers) {
790
- const risk = layer.riskLevel === "none" ? chalk4.dim("none") : layer.riskLevel === "low" ? chalk4.green("low") : layer.riskLevel === "moderate" ? chalk4.yellow("moderate") : chalk4.red("high");
791
- lines.push(` ${chalk4.bold(layer.layer)} ${layer.fileCount} file${layer.fileCount !== 1 ? "s" : ""} drift ${scoreBar(layer.driftScore)} risk ${risk}`);
890
+ const risk = layer.riskLevel === "none" ? chalk5.dim("none") : layer.riskLevel === "low" ? chalk5.green("low") : layer.riskLevel === "moderate" ? chalk5.yellow("moderate") : chalk5.red("high");
891
+ lines.push(` ${chalk5.bold(layer.layer)} ${layer.fileCount} file${layer.fileCount !== 1 ? "s" : ""} drift ${scoreBar(layer.driftScore)} risk ${risk}`);
792
892
  }
793
893
  lines.push("");
794
894
  }
@@ -1157,10 +1257,10 @@ function formatMarkdown(artifact) {
1157
1257
 
1158
1258
  // src/commands/report.ts
1159
1259
  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);
1260
+ const artifactPath = path5.resolve(opts.in);
1161
1261
  if (!await pathExists(artifactPath)) {
1162
- console.error(chalk5.red(`Artifact not found: ${artifactPath}`));
1163
- console.error(chalk5.dim('Run "vibgrate scan" first to generate a scan artifact.'));
1262
+ console.error(chalk6.red(`Artifact not found: ${artifactPath}`));
1263
+ console.error(chalk6.dim('Run "vibgrate scan" first to generate a scan artifact.'));
1164
1264
  process.exit(1);
1165
1265
  }
1166
1266
  const artifact = await readJsonFile(artifactPath);
@@ -1178,11 +1278,145 @@ var reportCommand = new Command4("report").description("Generate a drift report
1178
1278
  }
1179
1279
  });
1180
1280
 
1281
+ // src/commands/login.ts
1282
+ import { Command as Command5 } from "commander";
1283
+ import chalk7 from "chalk";
1284
+
1285
+ // src/utils/open-url.ts
1286
+ import { spawn } from "child_process";
1287
+ function openUrl(url) {
1288
+ const platform = process.platform;
1289
+ let command;
1290
+ let args;
1291
+ if (platform === "darwin") {
1292
+ command = "open";
1293
+ args = [url];
1294
+ } else if (platform === "win32") {
1295
+ command = "cmd";
1296
+ args = ["/c", "start", "", url];
1297
+ } else {
1298
+ command = "xdg-open";
1299
+ args = [url];
1300
+ }
1301
+ try {
1302
+ const child = spawn(command, args, { stdio: "ignore", detached: true });
1303
+ child.on("error", () => {
1304
+ });
1305
+ child.unref();
1306
+ return true;
1307
+ } catch {
1308
+ return false;
1309
+ }
1310
+ }
1311
+
1312
+ // src/commands/login.ts
1313
+ var delay = (ms) => new Promise((resolve10) => setTimeout(resolve10, ms));
1314
+ 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) => {
1315
+ let ingestHost;
1316
+ try {
1317
+ ingestHost = resolveIngestHost(opts.region, opts.ingest);
1318
+ } catch (e) {
1319
+ console.error(chalk7.red(e instanceof Error ? e.message : String(e)));
1320
+ process.exit(1);
1321
+ }
1322
+ const base = `https://${ingestHost}/v1/auth/device`;
1323
+ let start;
1324
+ try {
1325
+ const res = await fetch(`${base}/start`, {
1326
+ method: "POST",
1327
+ headers: { "Content-Type": "application/json", Connection: "close" },
1328
+ body: "{}"
1329
+ });
1330
+ if (!res.ok) {
1331
+ console.error(chalk7.red(`Failed to start login (HTTP ${res.status}).`));
1332
+ process.exit(1);
1333
+ }
1334
+ start = await res.json();
1335
+ } catch (e) {
1336
+ console.error(chalk7.red(`Could not reach ${ingestHost}: ${e instanceof Error ? e.message : String(e)}`));
1337
+ process.exit(1);
1338
+ }
1339
+ console.log("");
1340
+ console.log("To finish signing in, open this URL and approve the request:");
1341
+ console.log("");
1342
+ console.log(" " + chalk7.cyan(start.verificationUri));
1343
+ console.log("");
1344
+ console.log(" Your code: " + chalk7.bold(start.userCode));
1345
+ console.log("");
1346
+ if (opts.browser) {
1347
+ const opened = openUrl(start.verificationUriComplete);
1348
+ if (opened) {
1349
+ console.log(chalk7.dim("Opening your browser\u2026 (if it does not open, use the URL above)"));
1350
+ }
1351
+ }
1352
+ console.log(chalk7.dim("Waiting for approval\u2026"));
1353
+ const intervalMs = Math.max(2, start.interval || 5) * 1e3;
1354
+ const deadline = Date.now() + (start.expiresIn || 900) * 1e3;
1355
+ while (Date.now() < deadline) {
1356
+ await delay(intervalMs);
1357
+ let token;
1358
+ try {
1359
+ const res = await fetch(`${base}/token`, {
1360
+ method: "POST",
1361
+ headers: { "Content-Type": "application/json", Connection: "close" },
1362
+ body: JSON.stringify({ deviceCode: start.deviceCode })
1363
+ });
1364
+ token = await res.json();
1365
+ } catch {
1366
+ continue;
1367
+ }
1368
+ if (token.status === "authorization_pending") continue;
1369
+ if (token.status === "complete" && token.dsn) {
1370
+ writeStoredCredentials({
1371
+ dsn: token.dsn,
1372
+ workspaceId: token.workspaceId,
1373
+ keyId: token.keyId,
1374
+ ingestHost: token.ingestHost ?? ingestHost,
1375
+ savedAt: (/* @__PURE__ */ new Date()).toISOString()
1376
+ });
1377
+ console.log("");
1378
+ console.log(chalk7.green("\u2714") + " Logged in.");
1379
+ if (token.workspaceId) {
1380
+ console.log(" Workspace: " + chalk7.bold(token.workspaceId));
1381
+ }
1382
+ console.log(chalk7.dim(` Credentials saved to ${credentialsPath()}`));
1383
+ console.log(chalk7.dim(' You can now run "vibgrate scan --push".'));
1384
+ return;
1385
+ }
1386
+ if (token.status === "access_denied") {
1387
+ console.error(chalk7.red("\u2716 Login was denied in the browser."));
1388
+ process.exit(1);
1389
+ }
1390
+ if (token.status === "expired" || token.status === "invalid") {
1391
+ console.error(chalk7.red('\u2716 Login request expired. Run "vibgrate login" again.'));
1392
+ process.exit(1);
1393
+ }
1394
+ if (token.status === "error") {
1395
+ console.error(chalk7.red(`\u2716 ${token.error ?? "Login failed."}`));
1396
+ process.exit(1);
1397
+ }
1398
+ }
1399
+ console.error(chalk7.red('\u2716 Timed out waiting for approval. Run "vibgrate login" again.'));
1400
+ process.exit(1);
1401
+ });
1402
+
1403
+ // src/commands/logout.ts
1404
+ import { Command as Command6 } from "commander";
1405
+ import chalk8 from "chalk";
1406
+ var logoutCommand = new Command6("logout").description("Clear stored Vibgrate login credentials").action(() => {
1407
+ const cleared = clearStoredCredentials();
1408
+ if (cleared) {
1409
+ console.log(chalk8.green("\u2714") + " Logged out. Stored credentials removed.");
1410
+ } else {
1411
+ console.log(chalk8.dim(`No stored credentials found at ${credentialsPath()}.`));
1412
+ }
1413
+ });
1414
+
1181
1415
  // src/commands/push.ts
1182
1416
  import * as crypto2 from "crypto";
1183
- import * as path5 from "path";
1184
- import { Command as Command5 } from "commander";
1185
- import chalk6 from "chalk";
1417
+ import * as path6 from "path";
1418
+ import { Command as Command7 } from "commander";
1419
+ import chalk9 from "chalk";
1186
1420
 
1187
1421
  // src/utils/compact-artifact.ts
1188
1422
  import * as zlib from "zlib";
@@ -1454,25 +1688,25 @@ function parseDsn2(dsn) {
1454
1688
  function computeHmac(body, secret) {
1455
1689
  return crypto2.createHmac("sha256", secret).update(body).digest("base64");
1456
1690
  }
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;
1691
+ 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) => {
1692
+ const dsn = resolveDsn(opts.dsn);
1459
1693
  if (!dsn) {
1460
- console.error(chalk6.red("No DSN provided."));
1461
- console.error(chalk6.dim("Set VIBGRATE_DSN environment variable or use --dsn flag."));
1694
+ console.error(chalk9.red("No DSN provided."));
1695
+ console.error(chalk9.dim('Run "vibgrate login", set VIBGRATE_DSN, or use the --dsn flag.'));
1462
1696
  if (opts.strict) process.exit(1);
1463
1697
  return;
1464
1698
  }
1465
1699
  const parsed = parseDsn2(dsn);
1466
1700
  if (!parsed) {
1467
- console.error(chalk6.red("Invalid DSN format."));
1468
- console.error(chalk6.dim("Expected: vibgrate+https://<key_id>:<secret>@<host>/<workspace_id>"));
1701
+ console.error(chalk9.red("Invalid DSN format."));
1702
+ console.error(chalk9.dim("Expected: vibgrate+https://<key_id>:<secret>@<host>/<workspace_id>"));
1469
1703
  if (opts.strict) process.exit(1);
1470
1704
  return;
1471
1705
  }
1472
- const filePath = path5.resolve(opts.file);
1706
+ const filePath = path6.resolve(opts.file);
1473
1707
  if (!await pathExists(filePath)) {
1474
- console.error(chalk6.red(`Scan artifact not found: ${filePath}`));
1475
- console.error(chalk6.dim('Run "vibgrate scan" first.'));
1708
+ console.error(chalk9.red(`Scan artifact not found: ${filePath}`));
1709
+ console.error(chalk9.dim('Run "vibgrate scan" first.'));
1476
1710
  if (opts.strict) process.exit(1);
1477
1711
  return;
1478
1712
  }
@@ -1484,64 +1718,61 @@ var pushCommand = new Command5("push").description("Push scan results to Vibgrat
1484
1718
  try {
1485
1719
  host = resolveIngestHost(opts.region);
1486
1720
  } catch (e) {
1487
- console.error(chalk6.red(e instanceof Error ? e.message : String(e)));
1721
+ console.error(chalk9.red(e instanceof Error ? e.message : String(e)));
1488
1722
  if (opts.strict) process.exit(1);
1489
1723
  return;
1490
1724
  }
1491
1725
  }
1492
- const url = `${parsed.scheme}://${host}/v1/ingest/scan`;
1493
1726
  const originalSize = JSON.stringify(artifact).length;
1494
1727
  const compressedSize = body.length;
1495
1728
  const ratio = ((1 - compressedSize / originalSize) * 100).toFixed(0);
1496
- console.log(chalk6.dim(`Uploading to ${host}... (${(compressedSize / 1024).toFixed(0)} KB, ${ratio}% smaller)`));
1729
+ console.log(chalk9.dim(`Uploading to ${host}... (${(compressedSize / 1024).toFixed(0)} KB, ${ratio}% smaller)`));
1497
1730
  try {
1498
- const response = await fetch(url, {
1499
- method: "POST",
1500
- headers: {
1501
- "Content-Type": "application/json",
1502
- "Content-Encoding": contentEncoding,
1503
- "X-Vibgrate-Timestamp": timestamp,
1504
- "Authorization": `VibgrateDSN ${parsed.keyId}:${parsed.secret}`,
1505
- "Connection": "close"
1506
- // Prevent keep-alive delays on exit
1507
- },
1508
- body
1731
+ const { response, host: uploadedHost } = await uploadScanArtifact({
1732
+ scheme: parsed.scheme,
1733
+ host,
1734
+ keyId: parsed.keyId,
1735
+ secret: parsed.secret,
1736
+ body,
1737
+ contentEncoding,
1738
+ timestamp
1509
1739
  });
1740
+ host = uploadedHost;
1510
1741
  if (!response.ok) {
1511
1742
  const text = await response.text();
1512
1743
  throw new Error(`HTTP ${response.status}: ${text}`);
1513
1744
  }
1514
1745
  const result = await response.json();
1515
- console.log(chalk6.green("\u2714") + ` Scan queued for processing (${result.ingestId ?? "ok"})`);
1746
+ console.log(chalk9.green("\u2714") + ` Scan queued for processing (${result.ingestId ?? "ok"})`);
1516
1747
  console.log();
1517
- console.log(chalk6.dim("Processing continues in the background. Results available shortly."));
1748
+ console.log(chalk9.dim("Processing continues in the background. Results available shortly."));
1518
1749
  console.log();
1519
1750
  if (result.ingestId) {
1520
1751
  const dashHost = dashHostForIngestHost(host);
1521
1752
  const reportUrl = `https://${dashHost}/${parsed.workspaceId}/scan/${result.ingestId}`;
1522
- console.log(chalk6.dim("View report: ") + chalk6.underline(reportUrl));
1753
+ console.log(chalk9.dim("View report: ") + chalk9.underline(reportUrl));
1523
1754
  }
1524
1755
  } catch (e) {
1525
1756
  const msg = e instanceof Error ? e.message : String(e);
1526
- console.error(chalk6.red(`Upload failed: ${msg}`));
1757
+ console.error(chalk9.red(`Upload failed: ${msg}`));
1527
1758
  if (opts.strict) process.exit(1);
1528
1759
  }
1529
1760
  });
1530
1761
 
1531
1762
  // src/commands/update.ts
1532
1763
  import { execSync } from "child_process";
1533
- import * as path7 from "path";
1534
- import { Command as Command6 } from "commander";
1535
- import chalk7 from "chalk";
1764
+ import * as path8 from "path";
1765
+ import { Command as Command8 } from "commander";
1766
+ import chalk10 from "chalk";
1536
1767
 
1537
1768
  // src/utils/update-check.ts
1538
1769
  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";
1770
+ import * as fs2 from "fs/promises";
1771
+ import * as path7 from "path";
1772
+ import * as os2 from "os";
1542
1773
  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");
1774
+ var CACHE_DIR = path7.join(os2.homedir(), ".vibgrate");
1775
+ var CACHE_FILE = path7.join(CACHE_DIR, "update-check.json");
1545
1776
  var CHECK_INTERVAL_MS = 12 * 60 * 60 * 1e3;
1546
1777
  async function checkForUpdate() {
1547
1778
  try {
@@ -1605,7 +1836,7 @@ async function fetchLatestVersion() {
1605
1836
  }
1606
1837
  async function readCache() {
1607
1838
  try {
1608
- const raw = await fs.readFile(CACHE_FILE, "utf-8");
1839
+ const raw = await fs2.readFile(CACHE_FILE, "utf-8");
1609
1840
  const data = JSON.parse(raw);
1610
1841
  if (data.latest && typeof data.checkedAt === "number") return data;
1611
1842
  return null;
@@ -1615,8 +1846,8 @@ async function readCache() {
1615
1846
  }
1616
1847
  async function writeCache(data) {
1617
1848
  try {
1618
- await fs.mkdir(CACHE_DIR, { recursive: true });
1619
- await fs.writeFile(CACHE_FILE, JSON.stringify(data), "utf-8");
1849
+ await fs2.mkdir(CACHE_DIR, { recursive: true });
1850
+ await fs2.writeFile(CACHE_FILE, JSON.stringify(data), "utf-8");
1620
1851
  } catch {
1621
1852
  }
1622
1853
  }
@@ -1649,9 +1880,9 @@ function getGlobalUpdateCommand(pm, pkg2, version) {
1649
1880
  }
1650
1881
  }
1651
1882
  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";
1883
+ if (await pathExists(path8.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
1884
+ if (await pathExists(path8.join(cwd, "bun.lockb"))) return "bun";
1885
+ if (await pathExists(path8.join(cwd, "yarn.lock"))) return "yarn";
1655
1886
  return "npm";
1656
1887
  }
1657
1888
  function getInstallCommand(pm, pkg2, version, isDev) {
@@ -1670,7 +1901,7 @@ function getInstallCommand(pm, pkg2, version, isDev) {
1670
1901
  }
1671
1902
  async function isDevDependency(cwd) {
1672
1903
  try {
1673
- const pkgPath = path7.join(cwd, "package.json");
1904
+ const pkgPath = path8.join(cwd, "package.json");
1674
1905
  const raw = await (await import("fs/promises")).readFile(pkgPath, "utf-8");
1675
1906
  const pkg2 = JSON.parse(raw);
1676
1907
  return Boolean(pkg2.devDependencies?.["@vibgrate/cli"]);
@@ -1678,22 +1909,22 @@ async function isDevDependency(cwd) {
1678
1909
  return true;
1679
1910
  }
1680
1911
  }
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..."));
1912
+ 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) => {
1913
+ console.log(chalk10.dim(`Current version: ${VERSION}`));
1914
+ console.log(chalk10.dim("Checking npm registry..."));
1684
1915
  const latest = await fetchLatestVersion();
1685
1916
  if (!latest) {
1686
- console.error(chalk7.red("Could not reach the npm registry. Check your network connection."));
1917
+ console.error(chalk10.red("Could not reach the npm registry. Check your network connection."));
1687
1918
  process.exit(1);
1688
1919
  }
1689
1920
  const semver2 = await import("./semver-2FJFIYVN.js");
1690
1921
  if (!semver2.gt(latest, VERSION)) {
1691
- console.log(chalk7.green("\u2714") + ` You are on the latest version (${VERSION}).`);
1922
+ console.log(chalk10.green("\u2714") + ` You are on the latest version (${VERSION}).`);
1692
1923
  return;
1693
1924
  }
1694
- console.log(chalk7.yellow(`Update available: ${VERSION} \u2192 ${latest}`));
1925
+ console.log(chalk10.yellow(`Update available: ${VERSION} \u2192 ${latest}`));
1695
1926
  if (opts.check) {
1696
- console.log(chalk7.dim('Run "vibgrate update" to install.'));
1927
+ console.log(chalk10.dim('Run "vibgrate update" to install.'));
1697
1928
  return;
1698
1929
  }
1699
1930
  const cwd = process.cwd();
@@ -1703,26 +1934,26 @@ var updateCommand = new Command6("update").description("Update vibgrate to the l
1703
1934
  let cmd;
1704
1935
  if (isGlobal) {
1705
1936
  cmd = getGlobalUpdateCommand(pm, "@vibgrate/cli", latest);
1706
- console.log(chalk7.dim(`Updating global installation with ${pm}: ${cmd}`));
1937
+ console.log(chalk10.dim(`Updating global installation with ${pm}: ${cmd}`));
1707
1938
  } else {
1708
1939
  const isDev = await isDevDependency(cwd);
1709
1940
  cmd = getInstallCommand(pm, "@vibgrate/cli", latest, isDev);
1710
- console.log(chalk7.dim(`Using ${pm}: ${cmd}`));
1941
+ console.log(chalk10.dim(`Using ${pm}: ${cmd}`));
1711
1942
  }
1712
1943
  try {
1713
1944
  execSync(cmd, { cwd, stdio: "inherit" });
1714
- console.log(chalk7.green("\u2714") + ` Updated to @vibgrate/cli@${latest}`);
1945
+ console.log(chalk10.green("\u2714") + ` Updated to @vibgrate/cli@${latest}`);
1715
1946
  } catch {
1716
- console.error(chalk7.red(`Update failed. Run manually: ${cmd}`));
1947
+ console.error(chalk10.red(`Update failed. Run manually: ${cmd}`));
1717
1948
  process.exit(1);
1718
1949
  }
1719
1950
  });
1720
1951
 
1721
1952
  // src/commands/sbom.ts
1722
- import * as path8 from "path";
1953
+ import * as path9 from "path";
1723
1954
  import { randomUUID } from "crypto";
1724
- import { Command as Command7 } from "commander";
1725
- import chalk8 from "chalk";
1955
+ import { Command as Command9 } from "commander";
1956
+ import chalk11 from "chalk";
1726
1957
  function flattenDependencies(artifact) {
1727
1958
  const rows = [];
1728
1959
  for (const project of artifact.projects) {
@@ -1860,50 +2091,50 @@ function formatDeltaText(base, current) {
1860
2091
  return lines.join("\n");
1861
2092
  }
1862
2093
  async function readArtifactOrExit(filePath) {
1863
- const absolutePath = path8.resolve(filePath);
2094
+ const absolutePath = path9.resolve(filePath);
1864
2095
  if (!await pathExists(absolutePath)) {
1865
- console.error(chalk8.red(`Artifact not found: ${absolutePath}`));
2096
+ console.error(chalk11.red(`Artifact not found: ${absolutePath}`));
1866
2097
  process.exit(1);
1867
2098
  }
1868
2099
  return readJsonFile(absolutePath);
1869
2100
  }
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) => {
2101
+ 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
2102
  const artifact = await readArtifactOrExit(opts.in);
1872
2103
  const format = opts.format.toLowerCase();
1873
2104
  if (format !== "cyclonedx" && format !== "spdx") {
1874
- console.error(chalk8.red("Invalid SBOM format. Use cyclonedx or spdx."));
2105
+ console.error(chalk11.red("Invalid SBOM format. Use cyclonedx or spdx."));
1875
2106
  process.exit(1);
1876
2107
  }
1877
2108
  const sbom = format === "cyclonedx" ? toCycloneDx(artifact) : toSpdx(artifact);
1878
2109
  const body = JSON.stringify(sbom, null, 2);
1879
2110
  if (opts.out) {
1880
- await writeTextFile(path8.resolve(opts.out), body);
1881
- console.log(chalk8.green("\u2714") + ` SBOM written to ${opts.out}`);
2111
+ await writeTextFile(path9.resolve(opts.out), body);
2112
+ console.log(chalk11.green("\u2714") + ` SBOM written to ${opts.out}`);
1882
2113
  } else {
1883
2114
  console.log(body);
1884
2115
  }
1885
2116
  });
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) => {
2117
+ 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
2118
  const base = await readArtifactOrExit(opts.from);
1888
2119
  const current = await readArtifactOrExit(opts.to);
1889
2120
  const report = formatDeltaText(base, current);
1890
2121
  if (opts.out) {
1891
- await writeTextFile(path8.resolve(opts.out), report);
1892
- console.log(chalk8.green("\u2714") + ` SBOM delta report written to ${opts.out}`);
2122
+ await writeTextFile(path9.resolve(opts.out), report);
2123
+ console.log(chalk11.green("\u2714") + ` SBOM delta report written to ${opts.out}`);
1893
2124
  } else {
1894
2125
  console.log(report);
1895
2126
  }
1896
2127
  });
1897
- var sbomCommand = new Command7("sbom").description("SBOM export and delta reports for dependency drift tracking").addCommand(exportCommand).addCommand(deltaCommand);
2128
+ var sbomCommand = new Command9("sbom").description("SBOM export and delta reports for dependency drift tracking").addCommand(exportCommand).addCommand(deltaCommand);
1898
2129
 
1899
2130
  // 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";
2131
+ import * as path12 from "path";
2132
+ import * as os4 from "os";
2133
+ import * as fs4 from "fs/promises";
2134
+ import { existsSync, rmSync as rmSync2 } from "fs";
2135
+ import { spawn as spawn3, spawnSync as spawnSync2 } from "child_process";
2136
+ import { Command as Command10 } from "commander";
2137
+ import chalk12 from "chalk";
1907
2138
 
1908
2139
  // src/behavioural/derive.ts
1909
2140
  import { createHash as createHash2 } from "crypto";
@@ -2756,6 +2987,303 @@ function deriveBehaviouralFacts(facts, opts) {
2756
2987
  return { envelopes, summary: { assertions: unique.length, surfaceFacts, assertedFacts, behaviouralConfidence } };
2757
2988
  }
2758
2989
 
2990
+ // src/utils/vcs.ts
2991
+ import * as path10 from "path";
2992
+ import * as fs3 from "fs/promises";
2993
+ async function detectVcs2(rootDir) {
2994
+ try {
2995
+ return await detectGit(rootDir);
2996
+ } catch {
2997
+ return { type: "unknown" };
2998
+ }
2999
+ }
3000
+ async function detectGit(rootDir) {
3001
+ const gitDir = await findGitDir(rootDir);
3002
+ if (!gitDir) {
3003
+ return { type: "unknown" };
3004
+ }
3005
+ const headPath = path10.join(gitDir, "HEAD");
3006
+ let headContent;
3007
+ try {
3008
+ headContent = (await fs3.readFile(headPath, "utf8")).trim();
3009
+ } catch {
3010
+ return { type: "unknown" };
3011
+ }
3012
+ let sha;
3013
+ let branch;
3014
+ if (headContent.startsWith("ref: ")) {
3015
+ const refPath = headContent.slice(5);
3016
+ branch = refPath.startsWith("refs/heads/") ? refPath.slice(11) : refPath;
3017
+ sha = await resolveRef(gitDir, refPath);
3018
+ } else if (/^[0-9a-f]{40}$/i.test(headContent)) {
3019
+ sha = headContent;
3020
+ }
3021
+ const remoteUrl = await readGitRemoteUrl(gitDir);
3022
+ return {
3023
+ type: "git",
3024
+ sha: sha ?? void 0,
3025
+ shortSha: sha ? sha.slice(0, 7) : void 0,
3026
+ branch: branch ?? void 0,
3027
+ remoteUrl
3028
+ };
3029
+ }
3030
+ async function findGitDir(startDir) {
3031
+ let dir = path10.resolve(startDir);
3032
+ const root = path10.parse(dir).root;
3033
+ while (dir !== root) {
3034
+ const gitPath = path10.join(dir, ".git");
3035
+ try {
3036
+ const stat4 = await fs3.stat(gitPath);
3037
+ if (stat4.isDirectory()) {
3038
+ return gitPath;
3039
+ }
3040
+ if (stat4.isFile()) {
3041
+ const content = (await fs3.readFile(gitPath, "utf8")).trim();
3042
+ if (content.startsWith("gitdir: ")) {
3043
+ const resolved = path10.resolve(dir, content.slice(8));
3044
+ return resolved;
3045
+ }
3046
+ }
3047
+ } catch {
3048
+ }
3049
+ dir = path10.dirname(dir);
3050
+ }
3051
+ return null;
3052
+ }
3053
+ async function resolveRef(gitDir, refPath) {
3054
+ const loosePath = path10.join(gitDir, refPath);
3055
+ try {
3056
+ const sha = (await fs3.readFile(loosePath, "utf8")).trim();
3057
+ if (/^[0-9a-f]{40}$/i.test(sha)) {
3058
+ return sha;
3059
+ }
3060
+ } catch {
3061
+ }
3062
+ const packedPath = path10.join(gitDir, "packed-refs");
3063
+ try {
3064
+ const packed = await fs3.readFile(packedPath, "utf8");
3065
+ for (const line of packed.split("\n")) {
3066
+ if (line.startsWith("#") || line.startsWith("^")) continue;
3067
+ const parts = line.trim().split(" ");
3068
+ if (parts.length >= 2 && parts[1] === refPath) {
3069
+ return parts[0];
3070
+ }
3071
+ }
3072
+ } catch {
3073
+ }
3074
+ return void 0;
3075
+ }
3076
+ async function readGitRemoteUrl(gitDir) {
3077
+ const configPath = await resolveGitConfigPath(gitDir);
3078
+ if (!configPath) return void 0;
3079
+ try {
3080
+ const config = await fs3.readFile(configPath, "utf8");
3081
+ const originBlock = config.match(/\[remote\s+"origin"\]([\s\S]*?)(?=\n\[|$)/);
3082
+ if (!originBlock) return void 0;
3083
+ const urlMatch = originBlock[1]?.match(/\n\s*url\s*=\s*(.+)\s*/);
3084
+ return urlMatch?.[1]?.trim();
3085
+ } catch {
3086
+ return void 0;
3087
+ }
3088
+ }
3089
+ async function resolveGitConfigPath(gitDir) {
3090
+ const directConfig = path10.join(gitDir, "config");
3091
+ try {
3092
+ const stat4 = await fs3.stat(directConfig);
3093
+ if (stat4.isFile()) return directConfig;
3094
+ } catch {
3095
+ }
3096
+ const commonDirFile = path10.join(gitDir, "commondir");
3097
+ try {
3098
+ const commonDir = (await fs3.readFile(commonDirFile, "utf8")).trim();
3099
+ if (!commonDir) return void 0;
3100
+ const resolvedCommonDir = path10.resolve(gitDir, commonDir);
3101
+ const commonConfig = path10.join(resolvedCommonDir, "config");
3102
+ const stat4 = await fs3.stat(commonConfig);
3103
+ if (stat4.isFile()) return commonConfig;
3104
+ } catch {
3105
+ return void 0;
3106
+ }
3107
+ return void 0;
3108
+ }
3109
+
3110
+ // src/utils/clone.ts
3111
+ import * as path11 from "path";
3112
+ import * as os3 from "os";
3113
+ import { mkdtemp, rm } from "fs/promises";
3114
+ import { spawn as spawn2, spawnSync } from "child_process";
3115
+ var SCP_LIKE = /^[A-Za-z0-9._-]+@[A-Za-z0-9._-]+:.+$/;
3116
+ function looksLikeRepoUrl(arg) {
3117
+ if (!arg) return false;
3118
+ if (/^(https?|ssh|git):\/\//i.test(arg)) return true;
3119
+ if (SCP_LIKE.test(arg)) return true;
3120
+ return false;
3121
+ }
3122
+ function redactRemoteUrl(url) {
3123
+ if (!url) return url;
3124
+ if (SCP_LIKE.test(url) && !url.includes("://")) return url;
3125
+ try {
3126
+ const parsed = new URL(url);
3127
+ if (parsed.username || parsed.password) {
3128
+ parsed.username = "";
3129
+ parsed.password = "";
3130
+ }
3131
+ return parsed.toString();
3132
+ } catch {
3133
+ return url.replace(/^([a-z]+:\/\/)[^/@]+@/i, "$1");
3134
+ }
3135
+ }
3136
+ var CloneError = class extends Error {
3137
+ constructor(message) {
3138
+ super(message);
3139
+ this.name = "CloneError";
3140
+ }
3141
+ };
3142
+ function isGitAvailable() {
3143
+ const result = spawnSync("git", ["--version"], { stdio: "ignore" });
3144
+ return !result.error && result.status === 0;
3145
+ }
3146
+ function gitInstallHint() {
3147
+ switch (process.platform) {
3148
+ case "darwin":
3149
+ return "Install git with: brew install git (or xcode-select --install).";
3150
+ case "win32":
3151
+ return "Install git with: winget install Git.Git";
3152
+ default:
3153
+ return "Install git via your distro package manager (e.g. apt install git).";
3154
+ }
3155
+ }
3156
+ function runGit(args, opts) {
3157
+ return new Promise((resolve10, reject) => {
3158
+ const child = spawn2("git", args, {
3159
+ stdio: ["ignore", "ignore", "pipe"],
3160
+ // Never prompt for credentials on a private/forbidden repo — fail fast.
3161
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "echo" }
3162
+ });
3163
+ let stderr = "";
3164
+ let killed = false;
3165
+ const timer = setTimeout(() => {
3166
+ killed = true;
3167
+ child.kill("SIGKILL");
3168
+ }, opts.timeoutMs);
3169
+ child.stderr.on("data", (d) => {
3170
+ if (stderr.length < 8192) stderr += scrubCredentials(String(d));
3171
+ });
3172
+ child.on("error", (err) => {
3173
+ clearTimeout(timer);
3174
+ reject(new CloneError(`Failed to run git: ${err.message}`));
3175
+ });
3176
+ child.on("close", (code) => {
3177
+ clearTimeout(timer);
3178
+ if (killed) {
3179
+ reject(new CloneError(`git clone timed out for ${opts.redacted}`));
3180
+ return;
3181
+ }
3182
+ resolve10({ code: code ?? 0, stderr: stderr.trim() });
3183
+ });
3184
+ });
3185
+ }
3186
+ function scrubCredentials(text) {
3187
+ return text.replace(/([a-z]+:\/\/)[^/\s@]+@/gi, "$1");
3188
+ }
3189
+ async function cloneRepo(url, opts) {
3190
+ const redacted = redactRemoteUrl(url);
3191
+ if (!looksLikeRepoUrl(url)) {
3192
+ throw new CloneError(
3193
+ `Not a supported repository URL: ${redacted}. Use an http(s), ssh, git://, or scp-like (git@host:owner/repo.git) URL.`
3194
+ );
3195
+ }
3196
+ if (/^file:\/\//i.test(url)) {
3197
+ throw new CloneError("file:// URLs are not allowed for remote clone.");
3198
+ }
3199
+ if (!isGitAvailable()) {
3200
+ throw new CloneError(`git is required to clone ${redacted} but was not found on PATH. ${gitInstallHint()}`);
3201
+ }
3202
+ const dir = await mkdtemp(path11.join(os3.tmpdir(), "vibgrate-hcs-clone-"));
3203
+ const cleanup = async () => {
3204
+ await rm(dir, { recursive: true, force: true }).catch(() => {
3205
+ });
3206
+ };
3207
+ try {
3208
+ if (opts.verbose) process.stderr.write(`Cloning ${redacted} \u2026
3209
+ `);
3210
+ const clone = await runGit(
3211
+ ["clone", "--depth", "1", "--quiet", "--", url, dir],
3212
+ { timeoutMs: opts.timeoutMs, redacted }
3213
+ );
3214
+ if (clone.code !== 0) {
3215
+ throw new CloneError(`git clone failed for ${redacted}: ${clone.stderr || `exit ${clone.code}`}`);
3216
+ }
3217
+ if (opts.ref) {
3218
+ if (opts.verbose) process.stderr.write(`Checking out ${opts.ref} \u2026
3219
+ `);
3220
+ const fetch2 = await runGit(
3221
+ ["-C", dir, "fetch", "--depth", "1", "--quiet", "origin", opts.ref],
3222
+ { timeoutMs: opts.timeoutMs, redacted }
3223
+ );
3224
+ const checkoutTarget = fetch2.code === 0 ? "FETCH_HEAD" : opts.ref;
3225
+ const checkout = await runGit(
3226
+ ["-C", dir, "checkout", "--quiet", "--detach", checkoutTarget],
3227
+ { timeoutMs: opts.timeoutMs, redacted }
3228
+ );
3229
+ if (checkout.code !== 0) {
3230
+ throw new CloneError(
3231
+ `Could not check out ref "${opts.ref}" from ${redacted}: ${checkout.stderr || `exit ${checkout.code}`}`
3232
+ );
3233
+ }
3234
+ }
3235
+ return { dir, cleanup };
3236
+ } catch (err) {
3237
+ await cleanup();
3238
+ throw err instanceof CloneError ? err : new CloneError(String(err));
3239
+ }
3240
+ }
3241
+
3242
+ // src/utils/scan-manifest.ts
3243
+ import * as crypto3 from "crypto";
3244
+ function computeFactContentHash(factLines) {
3245
+ const hash = crypto3.createHash("sha256");
3246
+ for (const line of factLines) {
3247
+ hash.update(line);
3248
+ hash.update("\n");
3249
+ }
3250
+ return `sha256:${hash.digest("hex")}`;
3251
+ }
3252
+ function deriveRepoName(remoteUrl) {
3253
+ if (!remoteUrl) return void 0;
3254
+ const withoutScheme = remoteUrl.replace(/^[a-z]+:\/\//i, "").replace(/^[^/@]+@/, "");
3255
+ const pathPart = withoutScheme.replace(/^[^/:]+[/:]/, "");
3256
+ const segments = pathPart.split("/").filter(Boolean);
3257
+ if (segments.length === 0) return void 0;
3258
+ const repo = segments[segments.length - 1].replace(/\.git$/i, "");
3259
+ const owner = segments.length >= 2 ? segments[segments.length - 2] : void 0;
3260
+ return owner ? `${owner}/${repo}` : repo;
3261
+ }
3262
+ function buildScanManifest(input) {
3263
+ return {
3264
+ factType: "ScanManifest",
3265
+ manifestVersion: "1.0",
3266
+ emittedAt: input.emittedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
3267
+ repository: {
3268
+ remoteUrl: input.remoteUrl,
3269
+ name: input.repositoryName ?? deriveRepoName(input.remoteUrl)
3270
+ },
3271
+ vcs: {
3272
+ sha: input.sha,
3273
+ shortSha: input.shortSha,
3274
+ branch: input.branch,
3275
+ ref: input.ref
3276
+ },
3277
+ source: input.source,
3278
+ languages: [...input.languages].sort(),
3279
+ scanId: input.scanId,
3280
+ applicationId: input.applicationId,
3281
+ vibgrateVersion: input.vibgrateVersion,
3282
+ factCount: input.factLines.length,
3283
+ contentHash: computeFactContentHash(input.factLines)
3284
+ };
3285
+ }
3286
+
2759
3287
  // src/commands/extract.ts
2760
3288
  var EXIT_SUCCESS = 0;
2761
3289
  var EXIT_SCHEMA_FAILURE = 1;
@@ -2840,14 +3368,14 @@ async function detectLanguages(rootDir, includeTests) {
2840
3368
  async function walk(dir, relBase) {
2841
3369
  let entries;
2842
3370
  try {
2843
- entries = await fs2.readdir(dir, { withFileTypes: true });
3371
+ entries = await fs4.readdir(dir, { withFileTypes: true });
2844
3372
  } catch {
2845
3373
  return;
2846
3374
  }
2847
3375
  for (const entry of entries) {
2848
3376
  if (entry.name.startsWith(".")) continue;
2849
- const absPath = path9.join(dir, entry.name);
2850
- const relPath = path9.join(relBase, entry.name);
3377
+ const absPath = path12.join(dir, entry.name);
3378
+ const relPath = path12.join(relBase, entry.name);
2851
3379
  if (entry.isDirectory()) {
2852
3380
  if (!SKIP_DIRS.has(entry.name)) {
2853
3381
  await walk(absPath, relPath);
@@ -2856,7 +3384,7 @@ async function detectLanguages(rootDir, includeTests) {
2856
3384
  }
2857
3385
  if (!entry.isFile()) continue;
2858
3386
  if (!includeTests && isTestPath(relPath)) continue;
2859
- const ext = path9.extname(entry.name).toLowerCase();
3387
+ const ext = path12.extname(entry.name).toLowerCase();
2860
3388
  for (const [lang, exts] of Object.entries(LANGUAGE_EXTENSIONS)) {
2861
3389
  if (exts.has(ext)) {
2862
3390
  counts.set(lang, (counts.get(lang) ?? 0) + 1);
@@ -2882,6 +3410,15 @@ function validateFactLine(line) {
2882
3410
  }
2883
3411
  return { valid: true };
2884
3412
  }
3413
+ if (obj.factType === "ScanManifest") {
3414
+ if (typeof obj.manifestVersion !== "string") {
3415
+ return { valid: false, error: "ScanManifest missing manifestVersion" };
3416
+ }
3417
+ if (typeof obj.emittedAt !== "string") {
3418
+ return { valid: false, error: "ScanManifest missing emittedAt" };
3419
+ }
3420
+ return { valid: true };
3421
+ }
2885
3422
  if (typeof obj.factId !== "string" || !obj.factId) {
2886
3423
  return { valid: false, error: "Missing or invalid factId" };
2887
3424
  }
@@ -2923,7 +3460,7 @@ function splitHcsOutput(lines) {
2923
3460
  continue;
2924
3461
  }
2925
3462
  const obj = parsed;
2926
- if (obj.factType === "Models" || obj.factType === "References") {
3463
+ if (obj.factType === "Models" || obj.factType === "References" || obj.factType === "ScanManifest") {
2927
3464
  preamble.push(line);
2928
3465
  } else {
2929
3466
  facts.push(line);
@@ -2932,10 +3469,10 @@ function splitHcsOutput(lines) {
2932
3469
  return { preamble, facts };
2933
3470
  }
2934
3471
  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");
3472
+ const base = import.meta.dirname ?? path12.dirname(new URL(import.meta.url).pathname);
3473
+ const bundledPath = path12.resolve(base, "..", "dist", "hcs-worker.js");
2937
3474
  if (existsSync(bundledPath)) return bundledPath;
2938
- const siblingPath = path9.resolve(base, "hcs-worker.js");
3475
+ const siblingPath = path12.resolve(base, "hcs-worker.js");
2939
3476
  if (existsSync(siblingPath)) return siblingPath;
2940
3477
  try {
2941
3478
  const resolved = import.meta.resolve("@vibgrate/hcs-node-worker");
@@ -2943,9 +3480,9 @@ function resolveHcsWorkerBin() {
2943
3480
  if (existsSync(resolvedPath)) return resolvedPath;
2944
3481
  } catch {
2945
3482
  }
2946
- const monorepoPath = path9.resolve(base, "..", "..", "..", "vibgrate-hcs", "node", "dist", "main.js");
3483
+ const monorepoPath = path12.resolve(base, "..", "..", "..", "vibgrate-hcs", "node", "dist", "main.js");
2947
3484
  if (existsSync(monorepoPath)) return monorepoPath;
2948
- const devPath = path9.resolve(base, "..", "..", "..", "vibgrate-hcs", "node", "src", "main.ts");
3485
+ const devPath = path12.resolve(base, "..", "..", "..", "vibgrate-hcs", "node", "src", "main.ts");
2949
3486
  if (existsSync(devPath)) return devPath;
2950
3487
  throw new Error(
2951
3488
  'Cannot locate HCS worker. Run "pnpm build" in the CLI package to bundle the worker into dist/.'
@@ -3024,7 +3561,7 @@ var NATIVE_RUNTIME_REQUIREMENTS = {
3024
3561
  function commandExistsOnPath(command) {
3025
3562
  const probeArgs = process.platform === "win32" ? ["/c", command, "--version"] : ["--version"];
3026
3563
  const probeCmd = process.platform === "win32" ? "cmd" : command;
3027
- const result = spawnSync(probeCmd, probeArgs, { stdio: "ignore" });
3564
+ const result = spawnSync2(probeCmd, probeArgs, { stdio: "ignore" });
3028
3565
  return !result.error && result.status === 0;
3029
3566
  }
3030
3567
  function buildNativeInstallHint(language) {
@@ -3048,12 +3585,12 @@ async function runNodeWorker(rootDir, language, opts) {
3048
3585
  args.push("--cobol-copybook-paths", opts.copybookPaths);
3049
3586
  }
3050
3587
  }
3051
- return new Promise((resolve9) => {
3588
+ return new Promise((resolve10) => {
3052
3589
  const facts = [];
3053
3590
  const errors = [];
3054
3591
  let stdoutBuf = "";
3055
3592
  let killed = false;
3056
- const child = spawn("node", ["--enable-source-maps", workerBin, ...args], {
3593
+ const child = spawn3("node", ["--enable-source-maps", workerBin, ...args], {
3057
3594
  cwd: rootDir,
3058
3595
  stdio: ["ignore", "pipe", "pipe"],
3059
3596
  env: { ...process.env, NODE_OPTIONS: "--max-old-space-size=4096" }
@@ -3085,7 +3622,7 @@ async function runNodeWorker(rootDir, language, opts) {
3085
3622
  continue;
3086
3623
  }
3087
3624
  if (opts.verbose) {
3088
- process.stderr.write(chalk9.dim(`[${language}] ${trimmed}
3625
+ process.stderr.write(chalk12.dim(`[${language}] ${trimmed}
3089
3626
  `));
3090
3627
  }
3091
3628
  if (trimmed.startsWith("[error]")) {
@@ -3099,15 +3636,15 @@ async function runNodeWorker(rootDir, language, opts) {
3099
3636
  facts.push(stdoutBuf.trim());
3100
3637
  }
3101
3638
  if (killed) {
3102
- resolve9({ language, facts, errors: ["Worker killed: timeout exceeded"], exitCode: EXIT_TIMEOUT });
3639
+ resolve10({ language, facts, errors: ["Worker killed: timeout exceeded"], exitCode: EXIT_TIMEOUT });
3103
3640
  } else {
3104
- resolve9({ language, facts, errors, exitCode: code ?? 0 });
3641
+ resolve10({ language, facts, errors, exitCode: code ?? 0 });
3105
3642
  }
3106
3643
  });
3107
3644
  child.on("error", (err) => {
3108
3645
  clearTimeout(timer);
3109
3646
  errors.push(`Failed to spawn worker: ${err.message}`);
3110
- resolve9({ language, facts, errors, exitCode: EXIT_PARSE_FAILURE });
3647
+ resolve10({ language, facts, errors, exitCode: EXIT_PARSE_FAILURE });
3111
3648
  });
3112
3649
  });
3113
3650
  }
@@ -3127,7 +3664,7 @@ function resolveDotnetPublishedWorker(workersDir) {
3127
3664
  };
3128
3665
  const candidates = candidatesByPlatform[process.platform] ?? [];
3129
3666
  for (const filename of candidates) {
3130
- const fullPath = path9.join(workersDir, filename);
3667
+ const fullPath = path12.join(workersDir, filename);
3131
3668
  if (existsSync(fullPath)) {
3132
3669
  return fullPath;
3133
3670
  }
@@ -3135,30 +3672,30 @@ function resolveDotnetPublishedWorker(workersDir) {
3135
3672
  return null;
3136
3673
  }
3137
3674
  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");
3675
+ const base = import.meta.dirname ?? path12.dirname(new URL(import.meta.url).pathname);
3676
+ const hcsFromBundle = path12.resolve(base, "..", "..", "vibgrate-hcs");
3677
+ const hcsFromSrc = path12.resolve(base, "..", "..", "..", "vibgrate-hcs");
3141
3678
  const hcsRoot = existsSync(hcsFromBundle) ? hcsFromBundle : hcsFromSrc;
3142
- const workersDir = path9.resolve(base, "workers");
3679
+ const workersDir = path12.resolve(base, "workers");
3143
3680
  switch (language) {
3144
3681
  case "go": {
3145
- const bin = path9.join(workersDir, process.platform === "win32" ? "vibgrate-hcs-go.exe" : "vibgrate-hcs-go");
3682
+ const bin = path12.join(workersDir, process.platform === "win32" ? "vibgrate-hcs-go.exe" : "vibgrate-hcs-go");
3146
3683
  if (existsSync(bin)) {
3147
3684
  return { cmd: bin, args: ["--project", projectDir, "--output", "ndjson"] };
3148
3685
  }
3149
- const src = path9.join(hcsRoot, "go");
3150
- if (existsSync(path9.join(src, "main.go"))) {
3686
+ const src = path12.join(hcsRoot, "go");
3687
+ if (existsSync(path12.join(src, "main.go"))) {
3151
3688
  return { cmd: "go", args: ["run", ".", "--project", projectDir, "--output", "ndjson"], cwd: src };
3152
3689
  }
3153
3690
  return null;
3154
3691
  }
3155
3692
  case "python": {
3156
- const bin = path9.join(workersDir, "vibgrate-hcs-python");
3693
+ const bin = path12.join(workersDir, "vibgrate-hcs-python");
3157
3694
  if (existsSync(bin)) {
3158
3695
  return { cmd: bin, args: ["--project", projectDir, "--output", "ndjson"] };
3159
3696
  }
3160
- const src = path9.join(hcsRoot, "python");
3161
- if (existsSync(path9.join(src, "pyproject.toml"))) {
3697
+ const src = path12.join(hcsRoot, "python");
3698
+ if (existsSync(path12.join(src, "pyproject.toml"))) {
3162
3699
  return {
3163
3700
  cmd: "python3",
3164
3701
  args: ["-m", "vibgrate_hcs_python.main", "--project", projectDir, "--output", "ndjson"],
@@ -3168,13 +3705,13 @@ function resolveNativeWorker(language, projectDir) {
3168
3705
  return null;
3169
3706
  }
3170
3707
  case "java": {
3171
- const jar = path9.join(workersDir, "vibgrate-hcs-jvm.jar");
3708
+ const jar = path12.join(workersDir, "vibgrate-hcs-jvm.jar");
3172
3709
  if (existsSync(jar)) {
3173
3710
  return { cmd: "java", args: ["-jar", jar, "--project", projectDir, "--output", "ndjson"] };
3174
3711
  }
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");
3712
+ const src = path12.join(hcsRoot, "jvm");
3713
+ if (existsSync(path12.join(src, "build.gradle.kts"))) {
3714
+ const gradlew = path12.join(src, process.platform === "win32" ? "gradlew.bat" : "gradlew");
3178
3715
  const launcher = existsSync(gradlew) ? gradlew : "gradle";
3179
3716
  return {
3180
3717
  cmd: launcher,
@@ -3190,11 +3727,11 @@ function resolveNativeWorker(language, projectDir) {
3190
3727
  if (publishedWorker) {
3191
3728
  return { cmd: publishedWorker, args: ["--project", projectDir, "--output", "ndjson"] };
3192
3729
  }
3193
- const dll = path9.join(workersDir, "VibgrateHcsWorker.dll");
3730
+ const dll = path12.join(workersDir, "VibgrateHcsWorker.dll");
3194
3731
  if (existsSync(dll)) {
3195
3732
  return { cmd: "dotnet", args: [dll, "--project", projectDir, "--output", "ndjson"] };
3196
3733
  }
3197
- const csproj = path9.join(hcsRoot, "dotnet", "src", "VibgrateHcsWorker", "VibgrateHcsWorker.csproj");
3734
+ const csproj = path12.join(hcsRoot, "dotnet", "src", "VibgrateHcsWorker", "VibgrateHcsWorker.csproj");
3198
3735
  if (existsSync(csproj)) {
3199
3736
  return {
3200
3737
  cmd: "dotnet",
@@ -3220,19 +3757,19 @@ async function runNativeWorker(rootDir, language, opts) {
3220
3757
  exitCode: EXIT_PARSE_FAILURE
3221
3758
  };
3222
3759
  }
3223
- return new Promise((resolve9) => {
3760
+ return new Promise((resolve10) => {
3224
3761
  const facts = [];
3225
3762
  const errors = [];
3226
3763
  let stdoutBuf = "";
3227
3764
  let killed = false;
3228
3765
  if (opts.verbose) {
3229
- process.stderr.write(chalk9.dim(`[${language}] Spawning: ${spec.cmd} ${spec.args.join(" ")}
3766
+ process.stderr.write(chalk12.dim(`[${language}] Spawning: ${spec.cmd} ${spec.args.join(" ")}
3230
3767
  `));
3231
3768
  }
3232
3769
  const runtimeReq = NATIVE_RUNTIME_REQUIREMENTS[language];
3233
3770
  const usesPathCommand = runtimeReq && spec.cmd === runtimeReq.command;
3234
3771
  if (usesPathCommand && !commandExistsOnPath(spec.cmd)) {
3235
- resolve9({
3772
+ resolve10({
3236
3773
  language,
3237
3774
  facts: [],
3238
3775
  errors: [
@@ -3244,7 +3781,7 @@ async function runNativeWorker(rootDir, language, opts) {
3244
3781
  });
3245
3782
  return;
3246
3783
  }
3247
- const child = spawn(spec.cmd, spec.args, {
3784
+ const child = spawn3(spec.cmd, spec.args, {
3248
3785
  cwd: spec.cwd ?? rootDir,
3249
3786
  stdio: ["ignore", "pipe", "pipe"]
3250
3787
  });
@@ -3267,7 +3804,7 @@ async function runNativeWorker(rootDir, language, opts) {
3267
3804
  const trimmed = line.trim();
3268
3805
  if (!trimmed) continue;
3269
3806
  if (opts.verbose) {
3270
- process.stderr.write(chalk9.dim(`[${language}] ${trimmed}
3807
+ process.stderr.write(chalk12.dim(`[${language}] ${trimmed}
3271
3808
  `));
3272
3809
  }
3273
3810
  if (trimmed.startsWith("[error]")) {
@@ -3279,9 +3816,9 @@ async function runNativeWorker(rootDir, language, opts) {
3279
3816
  clearTimeout(timer);
3280
3817
  if (stdoutBuf.trim()) facts.push(stdoutBuf.trim());
3281
3818
  if (killed) {
3282
- resolve9({ language, facts, errors: ["Worker killed: timeout exceeded"], exitCode: EXIT_TIMEOUT });
3819
+ resolve10({ language, facts, errors: ["Worker killed: timeout exceeded"], exitCode: EXIT_TIMEOUT });
3283
3820
  } else {
3284
- resolve9({ language, facts, errors, exitCode: code ?? 0 });
3821
+ resolve10({ language, facts, errors, exitCode: code ?? 0 });
3285
3822
  }
3286
3823
  });
3287
3824
  child.on("error", (err) => {
@@ -3290,21 +3827,21 @@ async function runNativeWorker(rootDir, language, opts) {
3290
3827
  errors.push(
3291
3828
  isNotFound ? `[error] '${spec.cmd}' not found. ${buildNativeInstallHint(language)}` : `[error] Failed to spawn native worker: ${err.message}`
3292
3829
  );
3293
- resolve9({ language, facts, errors, exitCode: EXIT_PARSE_FAILURE });
3830
+ resolve10({ language, facts, errors, exitCode: EXIT_PARSE_FAILURE });
3294
3831
  });
3295
3832
  });
3296
3833
  }
3297
3834
  async function pushFacts(facts, dsn, verbose) {
3298
3835
  const parsed = parseDsn2(dsn);
3299
3836
  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"));
3837
+ process.stderr.write(chalk12.red("Invalid DSN format.\n"));
3838
+ process.stderr.write(chalk12.dim("Expected: vibgrate+https://<key_id>:<secret>@<host>/<workspace_id>\n"));
3302
3839
  return false;
3303
3840
  }
3304
3841
  const body = facts.join("\n") + "\n";
3305
3842
  const url = `${parsed.scheme}://${parsed.host}/v1/ingest/hcs`;
3306
3843
  if (verbose) {
3307
- process.stderr.write(chalk9.dim(`Pushing ${facts.length} facts to ${parsed.host}...
3844
+ process.stderr.write(chalk12.dim(`Pushing ${facts.length} facts to ${parsed.host}...
3308
3845
  `));
3309
3846
  }
3310
3847
  try {
@@ -3322,23 +3859,23 @@ async function pushFacts(facts, dsn, verbose) {
3322
3859
  });
3323
3860
  if (!response.ok) {
3324
3861
  const text = await response.text().catch(() => "");
3325
- process.stderr.write(chalk9.red(`Push failed: HTTP ${response.status} ${text}
3862
+ process.stderr.write(chalk12.red(`Push failed: HTTP ${response.status} ${text}
3326
3863
  `));
3327
3864
  return false;
3328
3865
  }
3329
3866
  if (verbose) {
3330
- process.stderr.write(chalk9.green(`\u2714 Pushed ${facts.length} facts successfully
3867
+ process.stderr.write(chalk12.green(`\u2714 Pushed ${facts.length} facts successfully
3331
3868
  `));
3332
3869
  }
3333
3870
  return true;
3334
3871
  } catch (err) {
3335
- process.stderr.write(chalk9.red(`Push failed: ${err instanceof Error ? err.message : String(err)}
3872
+ process.stderr.write(chalk12.red(`Push failed: ${err instanceof Error ? err.message : String(err)}
3336
3873
  `));
3337
3874
  return false;
3338
3875
  }
3339
3876
  }
3340
3877
  async function loadFeedback(filePath) {
3341
- const content = await fs2.readFile(filePath, "utf-8");
3878
+ const content = await fs4.readFile(filePath, "utf-8");
3342
3879
  const lines = content.split("\n").filter((l) => l.trim());
3343
3880
  for (const line of lines) {
3344
3881
  try {
@@ -3399,21 +3936,21 @@ var ProgressTracker = class {
3399
3936
  const parts = [];
3400
3937
  for (const [lang, st] of this.langStatus) {
3401
3938
  if (st.done) {
3402
- parts.push(chalk9.green(`${lang} \u2713`));
3939
+ parts.push(chalk12.green(`${lang} \u2713`));
3403
3940
  } else if (st.phase === "waiting") {
3404
- parts.push(chalk9.dim(`${lang} \xB7`));
3941
+ parts.push(chalk12.dim(`${lang} \xB7`));
3405
3942
  } else if (st.phase === "discovering") {
3406
- parts.push(chalk9.yellow(`${lang} \u2026`));
3943
+ parts.push(chalk12.yellow(`${lang} \u2026`));
3407
3944
  } else if (st.phase === "scanning" && st.fileCount > 0) {
3408
3945
  const pct = Math.round(st.fileIndex / st.fileCount * 100);
3409
- parts.push(chalk9.cyan(`${lang} ${st.fileIndex}/${st.fileCount} ${pct}%`));
3946
+ parts.push(chalk12.cyan(`${lang} ${st.fileIndex}/${st.fileCount} ${pct}%`));
3410
3947
  } else if (st.phase === "extracting") {
3411
- parts.push(chalk9.cyan(`${lang} extracting`));
3948
+ parts.push(chalk12.cyan(`${lang} extracting`));
3412
3949
  } else {
3413
- parts.push(chalk9.dim(`${lang} ${st.phase}`));
3950
+ parts.push(chalk12.dim(`${lang} ${st.phase}`));
3414
3951
  }
3415
3952
  }
3416
- const line = ` ${parts.join(" ")} ${chalk9.dim(`${this.totalFacts} facts`)}`;
3953
+ const line = ` ${parts.join(" ")} ${chalk12.dim(`${this.totalFacts} facts`)}`;
3417
3954
  const padding = Math.max(0, this.lastLineLen - line.length);
3418
3955
  process.stderr.write(`\r${line}${" ".repeat(padding)}`);
3419
3956
  this.lastLineLen = line.length;
@@ -3425,58 +3962,95 @@ var ProgressTracker = class {
3425
3962
  }
3426
3963
  }
3427
3964
  };
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}
3965
+ 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) => {
3966
+ const urlArg = opts.repoUrl ?? (looksLikeRepoUrl(targetPath) ? targetPath : void 0);
3967
+ const source = urlArg ? "url" : "local";
3968
+ let rootDir;
3969
+ let manifestRemoteUrl;
3970
+ if (urlArg) {
3971
+ const cloneTimeoutMins = parseInt(opts.cloneTimeoutMins, 10);
3972
+ if (isNaN(cloneTimeoutMins) || cloneTimeoutMins < 1) {
3973
+ process.stderr.write(chalk12.red("--clone-timeout-mins must be >= 1\n"));
3974
+ process.exit(EXIT_USAGE_ERROR);
3975
+ }
3976
+ manifestRemoteUrl = redactRemoteUrl(urlArg);
3977
+ try {
3978
+ const cloned = await cloneRepo(urlArg, {
3979
+ ref: opts.ref,
3980
+ timeoutMs: cloneTimeoutMins * 60 * 1e3,
3981
+ verbose: opts.verbose ?? false
3982
+ });
3983
+ rootDir = cloned.dir;
3984
+ if (opts.keepClone) {
3985
+ process.stderr.write(chalk12.dim(`Clone retained at: ${cloned.dir}
3432
3986
  `));
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}
3987
+ } else {
3988
+ process.once("exit", () => {
3989
+ try {
3990
+ rmSync2(cloned.dir, { recursive: true, force: true });
3991
+ } catch {
3992
+ }
3993
+ });
3994
+ }
3995
+ } catch (err) {
3996
+ const msg = err instanceof CloneError ? err.message : err instanceof Error ? err.message : String(err);
3997
+ process.stderr.write(chalk12.red(`Clone failed: ${msg}
3440
3998
  `));
3441
- process.exit(EXIT_USAGE_ERROR);
3442
- }
3443
- if (!stat3.isDirectory()) {
3444
- process.stderr.write(chalk9.red(`Path must be a directory: ${rootDir}
3999
+ process.exit(EXIT_USAGE_ERROR);
4000
+ }
4001
+ } else {
4002
+ rootDir = path12.resolve(targetPath);
4003
+ if (!await pathExists(rootDir)) {
4004
+ process.stderr.write(chalk12.red(`Path does not exist: ${rootDir}
3445
4005
  `));
3446
- process.exit(EXIT_USAGE_ERROR);
4006
+ process.exit(EXIT_USAGE_ERROR);
4007
+ }
4008
+ let stat4;
4009
+ try {
4010
+ stat4 = await fs4.stat(rootDir);
4011
+ } catch {
4012
+ process.stderr.write(chalk12.red(`Cannot read path: ${rootDir}
4013
+ `));
4014
+ process.exit(EXIT_USAGE_ERROR);
4015
+ }
4016
+ if (!stat4.isDirectory()) {
4017
+ process.stderr.write(chalk12.red(`Path must be a directory: ${rootDir}
4018
+ `));
4019
+ process.exit(EXIT_USAGE_ERROR);
4020
+ }
3447
4021
  }
3448
4022
  const dsn = opts.dsn || process.env.VIBGRATE_DSN;
3449
4023
  if (opts.push && !dsn) {
3450
- process.stderr.write(chalk9.red("--push requires --dsn or VIBGRATE_DSN environment variable\n"));
4024
+ process.stderr.write(chalk12.red("--push requires --dsn or VIBGRATE_DSN environment variable\n"));
3451
4025
  process.exit(EXIT_USAGE_ERROR);
3452
4026
  }
3453
4027
  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"));
4028
+ process.stderr.write(chalk12.red("Invalid DSN format.\n"));
4029
+ process.stderr.write(chalk12.dim("Expected: vibgrate+https://<key_id>:<secret>@<host>/<workspace_id>\n"));
3456
4030
  process.exit(EXIT_USAGE_ERROR);
3457
4031
  }
3458
- const concurrency = opts.concurrency ? parseInt(opts.concurrency, 10) : os2.cpus().length;
4032
+ const concurrency = opts.concurrency ? parseInt(opts.concurrency, 10) : os4.cpus().length;
3459
4033
  if (isNaN(concurrency) || concurrency < 1) {
3460
- process.stderr.write(chalk9.red("--concurrency must be >= 1\n"));
4034
+ process.stderr.write(chalk12.red("--concurrency must be >= 1\n"));
3461
4035
  process.exit(EXIT_USAGE_ERROR);
3462
4036
  }
3463
4037
  const timeoutMins = parseInt(opts.timeoutMins, 10);
3464
4038
  if (isNaN(timeoutMins) || timeoutMins < 1) {
3465
- process.stderr.write(chalk9.red("--timeout-mins must be >= 1\n"));
4039
+ process.stderr.write(chalk12.red("--timeout-mins must be >= 1\n"));
3466
4040
  process.exit(EXIT_USAGE_ERROR);
3467
4041
  }
3468
4042
  const timeoutMs = timeoutMins * 60 * 1e3;
3469
4043
  if (opts.out) {
3470
- const outDir = path9.dirname(path9.resolve(opts.out));
4044
+ const outDir = path12.dirname(path12.resolve(opts.out));
3471
4045
  if (!await pathExists(outDir)) {
3472
- process.stderr.write(chalk9.red(`Output directory does not exist: ${outDir}
4046
+ process.stderr.write(chalk12.red(`Output directory does not exist: ${outDir}
3473
4047
  `));
3474
4048
  process.exit(EXIT_USAGE_ERROR);
3475
4049
  }
3476
4050
  try {
3477
- const outStat = await fs2.stat(path9.resolve(opts.out));
4051
+ const outStat = await fs4.stat(path12.resolve(opts.out));
3478
4052
  if (outStat.isDirectory()) {
3479
- process.stderr.write(chalk9.red(`--out cannot be a directory: ${opts.out}
4053
+ process.stderr.write(chalk12.red(`--out cannot be a directory: ${opts.out}
3480
4054
  `));
3481
4055
  process.exit(EXIT_USAGE_ERROR);
3482
4056
  }
@@ -3485,16 +4059,16 @@ var extractCommand = new Command8("extract").description("Analyze source code an
3485
4059
  }
3486
4060
  let feedbackLines;
3487
4061
  if (opts.feedback) {
3488
- const feedbackPath = path9.resolve(opts.feedback);
4062
+ const feedbackPath = path12.resolve(opts.feedback);
3489
4063
  if (!await pathExists(feedbackPath)) {
3490
- process.stderr.write(chalk9.red(`Feedback file not found: ${feedbackPath}
4064
+ process.stderr.write(chalk12.red(`Feedback file not found: ${feedbackPath}
3491
4065
  `));
3492
4066
  process.exit(EXIT_USAGE_ERROR);
3493
4067
  }
3494
4068
  try {
3495
4069
  feedbackLines = await loadFeedback(feedbackPath);
3496
4070
  } catch (err) {
3497
- process.stderr.write(chalk9.red(`Invalid feedback file: ${err instanceof Error ? err.message : String(err)}
4071
+ process.stderr.write(chalk12.red(`Invalid feedback file: ${err instanceof Error ? err.message : String(err)}
3498
4072
  `));
3499
4073
  process.exit(EXIT_USAGE_ERROR);
3500
4074
  }
@@ -3504,9 +4078,9 @@ var extractCommand = new Command8("extract").description("Analyze source code an
3504
4078
  targetLanguages = opts.language.split(",").map((l) => normalizeLanguageToken(l)).filter(Boolean);
3505
4079
  for (const lang of targetLanguages) {
3506
4080
  if (!SUPPORTED_LANGUAGES.has(lang)) {
3507
- process.stderr.write(chalk9.red(`Unknown language: "${lang}"
4081
+ process.stderr.write(chalk12.red(`Unknown language: "${lang}"
3508
4082
  `));
3509
- process.stderr.write(chalk9.dim(`Supported: ${[...SUPPORTED_LANGUAGES].sort().join(", ")}
4083
+ process.stderr.write(chalk12.dim(`Supported: ${[...SUPPORTED_LANGUAGES].sort().join(", ")}
3510
4084
  `));
3511
4085
  process.exit(EXIT_USAGE_ERROR);
3512
4086
  }
@@ -3514,14 +4088,14 @@ var extractCommand = new Command8("extract").description("Analyze source code an
3514
4088
  } else {
3515
4089
  const detected = await detectLanguages(rootDir, opts.includeTests ?? false);
3516
4090
  if (detected.length === 0) {
3517
- process.stderr.write(chalk9.yellow("No supported source files detected.\n"));
4091
+ process.stderr.write(chalk12.yellow("No supported source files detected.\n"));
3518
4092
  process.exit(EXIT_SUCCESS);
3519
4093
  }
3520
4094
  targetLanguages = detected.map((d) => d.language);
3521
4095
  if (opts.verbose) {
3522
- process.stderr.write(chalk9.dim("Detected languages:\n"));
4096
+ process.stderr.write(chalk12.dim("Detected languages:\n"));
3523
4097
  for (const d of detected) {
3524
- process.stderr.write(chalk9.dim(` ${d.language}: ${d.fileCount} files
4098
+ process.stderr.write(chalk12.dim(` ${d.language}: ${d.fileCount} files
3525
4099
  `));
3526
4100
  }
3527
4101
  }
@@ -3529,20 +4103,20 @@ var extractCommand = new Command8("extract").description("Analyze source code an
3529
4103
  const runnableLanguages = targetLanguages.filter((l) => ALL_WORKER_LANGS.has(l));
3530
4104
  const unknownWorkerLangs = targetLanguages.filter((l) => !ALL_WORKER_LANGS.has(l));
3531
4105
  if (unknownWorkerLangs.length > 0 && opts.verbose) {
3532
- process.stderr.write(chalk9.dim(
4106
+ process.stderr.write(chalk12.dim(
3533
4107
  `No worker registered for: ${unknownWorkerLangs.join(", ")} \u2014 skipping.
3534
4108
  `
3535
4109
  ));
3536
4110
  }
3537
4111
  if (runnableLanguages.length === 0) {
3538
- process.stderr.write(chalk9.yellow("No languages with available HCS workers found.\n"));
4112
+ process.stderr.write(chalk12.yellow("No languages with available HCS workers found.\n"));
3539
4113
  process.exit(EXIT_SUCCESS);
3540
4114
  }
3541
4115
  const startTime = Date.now();
3542
4116
  const globalDeadline = startTime + timeoutMs;
3543
4117
  process.stderr.write(
3544
- chalk9.bold(`Extracting HCS facts from ${rootDir}
3545
- `) + chalk9.dim(`Languages: ${runnableLanguages.join(", ")} Concurrency: ${concurrency} Timeout: ${timeoutMins}m
4118
+ chalk12.bold(`Extracting HCS facts from ${rootDir}
4119
+ `) + chalk12.dim(`Languages: ${runnableLanguages.join(", ")} Concurrency: ${concurrency} Timeout: ${timeoutMins}m
3546
4120
  `)
3547
4121
  );
3548
4122
  const progress = new ProgressTracker(runnableLanguages);
@@ -3589,7 +4163,7 @@ var extractCommand = new Command8("extract").description("Analyze source code an
3589
4163
  hasSchemaFailure = true;
3590
4164
  allErrors.push(`[${language}] Schema validation: ${validation.error}`);
3591
4165
  if (opts.verbose) {
3592
- process.stderr.write(chalk9.red(`[${language}] Invalid fact: ${validation.error}
4166
+ process.stderr.write(chalk12.red(`[${language}] Invalid fact: ${validation.error}
3593
4167
  `));
3594
4168
  }
3595
4169
  }
@@ -3615,7 +4189,7 @@ var extractCommand = new Command8("extract").description("Analyze source code an
3615
4189
  progress.finish(elapsed);
3616
4190
  if (opts.derive) {
3617
4191
  const scanId = opts.scanId || "local";
3618
- const applicationId = opts.applicationId || path9.basename(rootDir);
4192
+ const applicationId = opts.applicationId || path12.basename(rootDir);
3619
4193
  const { envelopes, summary } = deriveBehaviouralFacts(derivedInputs, { scanId, applicationId });
3620
4194
  for (const env of envelopes) {
3621
4195
  const line = JSON.stringify(env);
@@ -3626,18 +4200,42 @@ var extractCommand = new Command8("extract").description("Analyze source code an
3626
4200
  allErrors.push("[behavioural] derived fact failed schema validation");
3627
4201
  }
3628
4202
  }
3629
- process.stderr.write(chalk9.dim(
4203
+ process.stderr.write(chalk12.dim(
3630
4204
  `Derived ${summary.assertions} behavioural assertion(s) \xB7 confidence ${summary.behaviouralConfidence} (${summary.assertedFacts}/${summary.surfaceFacts} surface facts)
3631
4205
  `
3632
4206
  ));
3633
4207
  }
3634
4208
  allFacts.sort();
3635
- const allLines = [...allPreamble, ...allFacts];
4209
+ const manifestScanId = opts.scanId || "local";
4210
+ const manifestApplicationId = opts.applicationId || (source === "url" ? deriveRepoName(manifestRemoteUrl) : void 0) || path12.basename(rootDir);
4211
+ let manifestVcs;
4212
+ try {
4213
+ manifestVcs = await detectVcs2(rootDir);
4214
+ } catch {
4215
+ manifestVcs = void 0;
4216
+ }
4217
+ const manifest = buildScanManifest({
4218
+ // Prefer the redacted URL we cloned from; fall back to the local repo's
4219
+ // configured remote (also credential-redacted) for non-URL scans.
4220
+ remoteUrl: manifestRemoteUrl ?? (manifestVcs?.remoteUrl ? redactRemoteUrl(manifestVcs.remoteUrl) : void 0),
4221
+ sha: manifestVcs?.sha,
4222
+ shortSha: manifestVcs?.shortSha,
4223
+ branch: manifestVcs?.branch,
4224
+ ref: opts.ref,
4225
+ source,
4226
+ languages: runnableLanguages,
4227
+ scanId: manifestScanId,
4228
+ applicationId: manifestApplicationId,
4229
+ vibgrateVersion: VERSION,
4230
+ factLines: allFacts
4231
+ });
4232
+ const manifestLine = JSON.stringify(manifest);
4233
+ const allLines = [manifestLine, ...allPreamble, ...allFacts];
3636
4234
  const ndjsonOutput = allLines.join("\n") + (allLines.length > 0 ? "\n" : "");
3637
4235
  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}
4236
+ const outPath = path12.resolve(opts.out);
4237
+ await fs4.writeFile(outPath, ndjsonOutput, "utf-8");
4238
+ process.stderr.write(chalk12.green(`\u2714 Wrote ${allFacts.length} facts to ${outPath}
3641
4239
  `));
3642
4240
  } else {
3643
4241
  process.stdout.write(ndjsonOutput);
@@ -3649,48 +4247,48 @@ var extractCommand = new Command8("extract").description("Analyze source code an
3649
4247
  }
3650
4248
  }
3651
4249
  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}
4250
+ process.stderr.write(chalk12.dim("\n\u2500\u2500 Summary \u2500\u2500\n"));
4251
+ process.stderr.write(chalk12.dim(` Facts emitted : ${allFacts.length}
3654
4252
  `));
3655
- process.stderr.write(chalk9.dim(` Languages : ${runnableLanguages.join(", ")}
4253
+ process.stderr.write(chalk12.dim(` Languages : ${runnableLanguages.join(", ")}
3656
4254
  `));
3657
- process.stderr.write(chalk9.dim(` Elapsed : ${elapsed}s
4255
+ process.stderr.write(chalk12.dim(` Elapsed : ${elapsed}s
3658
4256
  `));
3659
4257
  if (allErrors.length > 0) {
3660
- process.stderr.write(chalk9.dim(` Errors : ${allErrors.length}
4258
+ process.stderr.write(chalk12.dim(` Errors : ${allErrors.length}
3661
4259
  `));
3662
4260
  for (const err of allErrors.slice(0, 10)) {
3663
- process.stderr.write(chalk9.dim(` ${err}
4261
+ process.stderr.write(chalk12.dim(` ${err}
3664
4262
  `));
3665
4263
  }
3666
4264
  if (allErrors.length > 10) {
3667
- process.stderr.write(chalk9.dim(` ... and ${allErrors.length - 10} more
4265
+ process.stderr.write(chalk12.dim(` ... and ${allErrors.length - 10} more
3668
4266
  `));
3669
4267
  }
3670
4268
  }
3671
- process.stderr.write(chalk9.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n"));
4269
+ process.stderr.write(chalk12.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n"));
3672
4270
  }
3673
4271
  if (hasTimeout) {
3674
- process.stderr.write(chalk9.red(`Timeout exceeded (${timeoutMins} minutes)
4272
+ process.stderr.write(chalk12.red(`Timeout exceeded (${timeoutMins} minutes)
3675
4273
  `));
3676
4274
  process.exit(EXIT_TIMEOUT);
3677
4275
  }
3678
4276
  if (hasSchemaFailure) {
3679
- process.stderr.write(chalk9.red("Fact schema validation failures detected\n"));
4277
+ process.stderr.write(chalk12.red("Fact schema validation failures detected\n"));
3680
4278
  process.exit(EXIT_SCHEMA_FAILURE);
3681
4279
  }
3682
4280
  if (hasParseFailure) {
3683
- process.stderr.write(chalk9.red("Parsing failures detected\n"));
4281
+ process.stderr.write(chalk12.red("Parsing failures detected\n"));
3684
4282
  process.exit(EXIT_PARSE_FAILURE);
3685
4283
  }
3686
4284
  });
3687
4285
 
3688
4286
  // 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";
4287
+ import * as path13 from "path";
4288
+ import * as fs5 from "fs/promises";
4289
+ import * as crypto4 from "crypto";
4290
+ import { Command as Command11 } from "commander";
4291
+ import chalk13 from "chalk";
3694
4292
  var EXIT_INVALID_PATH = 2;
3695
4293
  var EXIT_USAGE_ERROR2 = 5;
3696
4294
  var VALID_FORMATS = /* @__PURE__ */ new Set(["ndjson", "json", "patch"]);
@@ -3706,14 +4304,14 @@ async function discoverFiles(root, includeGlobs, excludeGlobs) {
3706
4304
  async function walk(dir, relBase) {
3707
4305
  let entries;
3708
4306
  try {
3709
- entries = await fs3.readdir(dir, { withFileTypes: true });
4307
+ entries = await fs5.readdir(dir, { withFileTypes: true });
3710
4308
  } catch {
3711
4309
  return;
3712
4310
  }
3713
4311
  for (const entry of entries) {
3714
4312
  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);
4313
+ const absPath = path13.join(dir, entry.name);
4314
+ const relPath = path13.join(relBase, entry.name);
3717
4315
  if (entry.isDirectory()) {
3718
4316
  if (!SKIP_DIRS2.has(entry.name)) {
3719
4317
  await walk(absPath, relPath);
@@ -3884,7 +4482,7 @@ function lcsRatio(a, b) {
3884
4482
  return 2 * lcsLen / (n + m);
3885
4483
  }
3886
4484
  function generateDeltaId(kind, filePath) {
3887
- const hash = crypto3.createHash("sha256").update(`${kind}:${filePath}`).digest("hex").substring(0, 16);
4485
+ const hash = crypto4.createHash("sha256").update(`${kind}:${filePath}`).digest("hex").substring(0, 16);
3888
4486
  return `hcs:delta:${hash}`;
3889
4487
  }
3890
4488
  function formatNdjson(deltas) {
@@ -3896,8 +4494,8 @@ function formatJson(deltas) {
3896
4494
  function formatPatch(deltas, originalPath, generatedPath) {
3897
4495
  const lines = [];
3898
4496
  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";
4497
+ const aFile = delta.originalPath ? path13.join("a", delta.originalPath) : "/dev/null";
4498
+ const bFile = delta.generatedPath ? path13.join("b", delta.generatedPath) : "/dev/null";
3901
4499
  if (delta.changeKind === "renamed" && delta.originalPath && delta.generatedPath) {
3902
4500
  lines.push(`diff --vibgrate ${aFile} ${bFile}`);
3903
4501
  lines.push(`similarity index ${Math.round((delta.similarity ?? 0) * 100)}%`);
@@ -3918,38 +4516,38 @@ function formatPatch(deltas, originalPath, generatedPath) {
3918
4516
  }
3919
4517
  return lines.join("\n");
3920
4518
  }
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);
4519
+ 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) => {
4520
+ const origDir = path13.resolve(originalPath);
4521
+ const genDir = path13.resolve(generatedPath);
3924
4522
  for (const [label, dir] of [["original_path", origDir], ["generated_path", genDir]]) {
3925
4523
  if (!await pathExists(dir)) {
3926
- process.stderr.write(chalk10.red(`${label} does not exist: ${dir}
4524
+ process.stderr.write(chalk13.red(`${label} does not exist: ${dir}
3927
4525
  `));
3928
4526
  process.exit(EXIT_INVALID_PATH);
3929
4527
  }
3930
- let stat3;
4528
+ let stat4;
3931
4529
  try {
3932
- stat3 = await fs3.stat(dir);
4530
+ stat4 = await fs5.stat(dir);
3933
4531
  } catch {
3934
- process.stderr.write(chalk10.red(`Cannot read ${label}: ${dir}
4532
+ process.stderr.write(chalk13.red(`Cannot read ${label}: ${dir}
3935
4533
  `));
3936
4534
  process.exit(EXIT_INVALID_PATH);
3937
4535
  }
3938
- if (!stat3.isDirectory()) {
3939
- process.stderr.write(chalk10.red(`${label} must be a directory: ${dir}
4536
+ if (!stat4.isDirectory()) {
4537
+ process.stderr.write(chalk13.red(`${label} must be a directory: ${dir}
3940
4538
  `));
3941
4539
  process.exit(EXIT_INVALID_PATH);
3942
4540
  }
3943
4541
  }
3944
4542
  const format = opts.format.toLowerCase();
3945
4543
  if (!VALID_FORMATS.has(format)) {
3946
- process.stderr.write(chalk10.red(`Invalid format: "${opts.format}". Allowed: ndjson, json, patch
4544
+ process.stderr.write(chalk13.red(`Invalid format: "${opts.format}". Allowed: ndjson, json, patch
3947
4545
  `));
3948
4546
  process.exit(EXIT_USAGE_ERROR2);
3949
4547
  }
3950
4548
  const contextLines = parseInt(opts.context, 10);
3951
4549
  if (isNaN(contextLines) || contextLines < 0) {
3952
- process.stderr.write(chalk10.red("--context must be >= 0\n"));
4550
+ process.stderr.write(chalk13.red("--context must be >= 0\n"));
3953
4551
  process.exit(EXIT_USAGE_ERROR2);
3954
4552
  }
3955
4553
  const includeGlobs = opts.include ? opts.include.split(",").map((g) => g.trim()).filter(Boolean) : void 0;
@@ -3961,9 +4559,9 @@ var diffCommand = new Command9("diff").description("Compare two directory trees
3961
4559
  const origMap = new Map(origFiles.map((f) => [f.relPath, f]));
3962
4560
  const genMap = new Map(genFiles.map((f) => [f.relPath, f]));
3963
4561
  if (opts.verbose) {
3964
- process.stderr.write(chalk10.dim(`Original: ${origFiles.length} files
4562
+ process.stderr.write(chalk13.dim(`Original: ${origFiles.length} files
3965
4563
  `));
3966
- process.stderr.write(chalk10.dim(`Generated: ${genFiles.length} files
4564
+ process.stderr.write(chalk13.dim(`Generated: ${genFiles.length} files
3967
4565
  `));
3968
4566
  }
3969
4567
  const deltas = [];
@@ -3974,8 +4572,8 @@ var diffCommand = new Command9("diff").description("Compare two directory trees
3974
4572
  if (genFile) {
3975
4573
  matchedOrig.add(relPath);
3976
4574
  matchedGen.add(relPath);
3977
- const origContent = await fs3.readFile(origFile.absPath, "utf-8");
3978
- const genContent = await fs3.readFile(genFile.absPath, "utf-8");
4575
+ const origContent = await fs5.readFile(origFile.absPath, "utf-8");
4576
+ const genContent = await fs5.readFile(genFile.absPath, "utf-8");
3979
4577
  const origCompare = opts.ignoreWhitespace ? origContent.replace(/\s+/g, " ").trim() : origContent;
3980
4578
  const genCompare = opts.ignoreWhitespace ? genContent.replace(/\s+/g, " ").trim() : genContent;
3981
4579
  if (origCompare !== genCompare) {
@@ -3997,7 +4595,7 @@ var diffCommand = new Command9("diff").description("Compare two directory trees
3997
4595
  }
3998
4596
  deltas.push(delta);
3999
4597
  if (opts.verbose) {
4000
- process.stderr.write(chalk10.dim(` modified: ${relPath} (+${diffResult.insertions} -${diffResult.deletions})
4598
+ process.stderr.write(chalk13.dim(` modified: ${relPath} (+${diffResult.insertions} -${diffResult.deletions})
4001
4599
  `));
4002
4600
  }
4003
4601
  }
@@ -4014,13 +4612,13 @@ var diffCommand = new Command9("diff").description("Compare two directory trees
4014
4612
  await Promise.all([
4015
4613
  ...unmatchedOrig.map(async (f) => {
4016
4614
  try {
4017
- origContents.set(f.relPath, await fs3.readFile(f.absPath, "utf-8"));
4615
+ origContents.set(f.relPath, await fs5.readFile(f.absPath, "utf-8"));
4018
4616
  } catch {
4019
4617
  }
4020
4618
  }),
4021
4619
  ...unmatchedGen.map(async (f) => {
4022
4620
  try {
4023
- genContents.set(f.relPath, await fs3.readFile(f.absPath, "utf-8"));
4621
+ genContents.set(f.relPath, await fs5.readFile(f.absPath, "utf-8"));
4024
4622
  } catch {
4025
4623
  }
4026
4624
  })
@@ -4028,7 +4626,7 @@ var diffCommand = new Command9("diff").description("Compare two directory trees
4028
4626
  const pairs = [];
4029
4627
  for (const [origPath, origContent] of origContents) {
4030
4628
  for (const [genPath, genContent] of genContents) {
4031
- if (path10.extname(origPath) !== path10.extname(genPath)) continue;
4629
+ if (path13.extname(origPath) !== path13.extname(genPath)) continue;
4032
4630
  const similarity = computeSimilarity(origContent, genContent);
4033
4631
  if (similarity >= RENAME_THRESHOLD) {
4034
4632
  pairs.push({ origPath, genPath, similarity });
@@ -4061,7 +4659,7 @@ var diffCommand = new Command9("diff").description("Compare two directory trees
4061
4659
  }
4062
4660
  deltas.push(delta);
4063
4661
  if (opts.verbose) {
4064
- process.stderr.write(chalk10.dim(
4662
+ process.stderr.write(chalk13.dim(
4065
4663
  ` renamed: ${pair.origPath} \u2192 ${pair.genPath} (${(pair.similarity * 100).toFixed(0)}% similar)
4066
4664
  `
4067
4665
  ));
@@ -4078,7 +4676,7 @@ var diffCommand = new Command9("diff").description("Compare two directory trees
4078
4676
  };
4079
4677
  if (opts.stats) {
4080
4678
  try {
4081
- const content = await fs3.readFile(file.absPath, "utf-8");
4679
+ const content = await fs5.readFile(file.absPath, "utf-8");
4082
4680
  delta.stats = { insertions: 0, deletions: content.split("\n").length };
4083
4681
  } catch {
4084
4682
  delta.stats = { insertions: 0, deletions: 0 };
@@ -4086,7 +4684,7 @@ var diffCommand = new Command9("diff").description("Compare two directory trees
4086
4684
  }
4087
4685
  deltas.push(delta);
4088
4686
  if (opts.verbose) {
4089
- process.stderr.write(chalk10.dim(` removed: ${file.relPath}
4687
+ process.stderr.write(chalk13.dim(` removed: ${file.relPath}
4090
4688
  `));
4091
4689
  }
4092
4690
  }
@@ -4100,7 +4698,7 @@ var diffCommand = new Command9("diff").description("Compare two directory trees
4100
4698
  };
4101
4699
  if (opts.stats) {
4102
4700
  try {
4103
- const content = await fs3.readFile(file.absPath, "utf-8");
4701
+ const content = await fs5.readFile(file.absPath, "utf-8");
4104
4702
  delta.stats = { insertions: content.split("\n").length, deletions: 0 };
4105
4703
  } catch {
4106
4704
  delta.stats = { insertions: 0, deletions: 0 };
@@ -4108,7 +4706,7 @@ var diffCommand = new Command9("diff").description("Compare two directory trees
4108
4706
  }
4109
4707
  deltas.push(delta);
4110
4708
  if (opts.verbose) {
4111
- process.stderr.write(chalk10.dim(` added: ${file.relPath}
4709
+ process.stderr.write(chalk13.dim(` added: ${file.relPath}
4112
4710
  `));
4113
4711
  }
4114
4712
  }
@@ -4129,16 +4727,16 @@ var diffCommand = new Command9("diff").description("Compare two directory trees
4129
4727
  output = formatPatch(deltas, originalPath, generatedPath);
4130
4728
  break;
4131
4729
  }
4132
- const outPath = path10.resolve(opts.out);
4133
- await fs3.writeFile(outPath, output, "utf-8");
4730
+ const outPath = path13.resolve(opts.out);
4731
+ await fs5.writeFile(outPath, output, "utf-8");
4134
4732
  const added = deltas.filter((d) => d.changeKind === "added").length;
4135
4733
  const removed = deltas.filter((d) => d.changeKind === "removed").length;
4136
4734
  const modified = deltas.filter((d) => d.changeKind === "modified").length;
4137
4735
  const renamed = deltas.filter((d) => d.changeKind === "renamed").length;
4138
4736
  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"
4737
+ chalk13.green(`\u2714 `) + `${deltas.length} changes: ` + chalk13.green(`+${added} added`) + ", " + chalk13.red(`-${removed} removed`) + ", " + chalk13.yellow(`~${modified} modified`) + ", " + chalk13.blue(`\u2192${renamed} renamed`) + "\n"
4140
4738
  );
4141
- process.stderr.write(chalk10.dim(` Written to ${outPath}
4739
+ process.stderr.write(chalk13.dim(` Written to ${outPath}
4142
4740
  `));
4143
4741
  if (opts.verbose && opts.stats) {
4144
4742
  let totalIns = 0;
@@ -4149,193 +4747,193 @@ var diffCommand = new Command9("diff").description("Compare two directory trees
4149
4747
  totalDel += d.stats.deletions;
4150
4748
  }
4151
4749
  }
4152
- process.stderr.write(chalk10.dim(` Total: +${totalIns} insertions, -${totalDel} deletions
4750
+ process.stderr.write(chalk13.dim(` Total: +${totalIns} insertions, -${totalDel} deletions
4153
4751
  `));
4154
4752
  }
4155
4753
  });
4156
4754
 
4157
4755
  // src/commands/help.ts
4158
- import { Command as Command10 } from "commander";
4159
- import chalk11 from "chalk";
4756
+ import { Command as Command12 } from "commander";
4757
+ import chalk14 from "chalk";
4160
4758
  var HELP_URL = "https://vibgrate.com/help";
4161
4759
  function printFooter() {
4162
4760
  console.log("");
4163
- console.log(chalk11.dim(`See ${HELP_URL} for more guidance`));
4761
+ console.log(chalk14.dim(`See ${HELP_URL} for more guidance`));
4164
4762
  }
4165
4763
  var detailedHelp = {
4166
4764
  scan: () => {
4167
4765
  console.log("");
4168
- console.log(chalk11.bold.underline("vibgrate scan") + chalk11.dim(" \u2014 Scan a project for upgrade drift"));
4766
+ console.log(chalk14.bold.underline("vibgrate scan") + chalk14.dim(" \u2014 Scan a project for upgrade drift"));
4169
4767
  console.log("");
4170
- console.log(chalk11.bold("Usage:"));
4768
+ console.log(chalk14.bold("Usage:"));
4171
4769
  console.log(" vibgrate scan [path] [options]");
4172
4770
  console.log("");
4173
- console.log(chalk11.bold("Arguments:"));
4174
- console.log(` ${chalk11.cyan("[path]")} Path to scan (default: current directory)`);
4771
+ console.log(chalk14.bold("Arguments:"));
4772
+ console.log(` ${chalk14.cyan("[path]")} Path to scan (default: current directory)`);
4175
4773
  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`);
4774
+ console.log(chalk14.bold("Output options:"));
4775
+ console.log(` ${chalk14.cyan("--format <format>")} Output format: ${chalk14.white("text")} | json | sarif | md (default: text)`);
4776
+ console.log(` ${chalk14.cyan("--out <file>")} Write output to a file instead of stdout`);
4179
4777
  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`);
4778
+ console.log(chalk14.bold("Baseline & gating:"));
4779
+ console.log(` ${chalk14.cyan("--baseline <file>")} Compare results against a saved baseline`);
4780
+ console.log(` ${chalk14.cyan("--drift-budget <score>")} Fail if drift score exceeds this value (0\u2013100)`);
4781
+ console.log(` ${chalk14.cyan("--drift-worsening <percent>")} Fail if drift worsens by more than % since baseline`);
4782
+ console.log(` ${chalk14.cyan("--fail-on <level>")} Fail exit code on warn or error findings`);
4185
4783
  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`);
4784
+ console.log(chalk14.bold("Performance:"));
4785
+ console.log(` ${chalk14.cyan("--concurrency <n>")} Max concurrent registry calls (default: 8)`);
4786
+ console.log(` ${chalk14.cyan("--changed-only")} Only scan files changed since last git commit`);
4189
4787
  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`);
4788
+ console.log(chalk14.bold("Privacy & offline:"));
4789
+ console.log(` ${chalk14.cyan("--offline")} Run without any network calls; skip result upload`);
4790
+ console.log(` ${chalk14.cyan("--package-manifest <file>")} Use a local package-version manifest (JSON or ZIP) for offline mode`);
4791
+ console.log(` ${chalk14.cyan("--no-local-artifacts")} Do not write .vibgrate JSON artifacts to disk`);
4792
+ console.log(` ${chalk14.cyan("--max-privacy")} Strongest privacy mode: minimal scanners + no local artifacts`);
4195
4793
  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)`);
4794
+ console.log(chalk14.bold("Tooling:"));
4795
+ console.log(` ${chalk14.cyan("--install-tools")} Auto-install missing security scanners via Homebrew`);
4796
+ console.log(` ${chalk14.cyan("--ui-purpose")} Enable UI purpose evidence extraction (slower)`);
4199
4797
  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`);
4798
+ console.log(chalk14.bold("Uploading results:"));
4799
+ console.log(` ${chalk14.cyan("--push")} Auto-push results to Vibgrate API after scan`);
4800
+ console.log(` ${chalk14.cyan("--dsn <dsn>")} DSN token for push (or set VIBGRATE_DSN env var)`);
4801
+ console.log(` ${chalk14.cyan("--region <region>")} Data residency region: us | eu (default: us)`);
4802
+ console.log(` ${chalk14.cyan("--strict")} Fail if the upload to Vibgrate API fails`);
4205
4803
  console.log("");
4206
- console.log(chalk11.bold("Examples:"));
4207
- console.log(` ${chalk11.dim("# Scan the current directory and display a text report")}`);
4804
+ console.log(chalk14.bold("Examples:"));
4805
+ console.log(` ${chalk14.dim("# Scan the current directory and display a text report")}`);
4208
4806
  console.log(" vibgrate scan .");
4209
4807
  console.log("");
4210
- console.log(` ${chalk11.dim("# Scan, fail if drift score > 40, and write SARIF for GitHub Actions")}`);
4808
+ console.log(` ${chalk14.dim("# Scan, fail if drift score > 40, and write SARIF for GitHub Actions")}`);
4211
4809
  console.log(" vibgrate scan . --drift-budget 40 --format sarif --out drift.sarif");
4212
4810
  console.log("");
4213
- console.log(` ${chalk11.dim("# Scan and automatically upload results via a DSN")}`);
4811
+ console.log(` ${chalk14.dim("# Scan and automatically upload results via a DSN")}`);
4214
4812
  console.log(" vibgrate scan . --push --dsn $VIBGRATE_DSN");
4215
4813
  console.log("");
4216
- console.log(` ${chalk11.dim("# Offline scan using a pre-downloaded package manifest")}`);
4814
+ console.log(` ${chalk14.dim("# Offline scan using a pre-downloaded package manifest")}`);
4217
4815
  console.log(" vibgrate scan . --offline --package-manifest ./manifest.zip");
4218
4816
  },
4219
4817
  init: () => {
4220
4818
  console.log("");
4221
- console.log(chalk11.bold.underline("vibgrate init") + chalk11.dim(" \u2014 Initialise vibgrate in a project directory"));
4819
+ console.log(chalk14.bold.underline("vibgrate init") + chalk14.dim(" \u2014 Initialise vibgrate in a project directory"));
4222
4820
  console.log("");
4223
- console.log(chalk11.bold("Usage:"));
4821
+ console.log(chalk14.bold("Usage:"));
4224
4822
  console.log(" vibgrate init [path] [options]");
4225
4823
  console.log("");
4226
- console.log(chalk11.bold("Arguments:"));
4227
- console.log(` ${chalk11.cyan("[path]")} Directory to initialise (default: current directory)`);
4824
+ console.log(chalk14.bold("Arguments:"));
4825
+ console.log(` ${chalk14.cyan("[path]")} Directory to initialise (default: current directory)`);
4228
4826
  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`);
4827
+ console.log(chalk14.bold("Options:"));
4828
+ console.log(` ${chalk14.cyan("--baseline")} Create an initial drift baseline after init`);
4829
+ console.log(` ${chalk14.cyan("--yes")} Skip all confirmation prompts`);
4232
4830
  console.log("");
4233
- console.log(chalk11.bold("What it does:"));
4831
+ console.log(chalk14.bold("What it does:"));
4234
4832
  console.log(" \u2022 Creates a .vibgrate/ directory");
4235
4833
  console.log(" \u2022 Writes a vibgrate.config.ts starter config");
4236
4834
  console.log(" \u2022 Optionally runs an initial baseline scan (--baseline)");
4237
4835
  console.log("");
4238
- console.log(chalk11.bold("Examples:"));
4836
+ console.log(chalk14.bold("Examples:"));
4239
4837
  console.log(" vibgrate init");
4240
4838
  console.log(" vibgrate init ./my-project --baseline");
4241
4839
  },
4242
4840
  baseline: () => {
4243
4841
  console.log("");
4244
- console.log(chalk11.bold.underline("vibgrate baseline") + chalk11.dim(" \u2014 Save a drift baseline snapshot"));
4842
+ console.log(chalk14.bold.underline("vibgrate baseline") + chalk14.dim(" \u2014 Save a drift baseline snapshot"));
4245
4843
  console.log("");
4246
- console.log(chalk11.bold("Usage:"));
4844
+ console.log(chalk14.bold("Usage:"));
4247
4845
  console.log(" vibgrate baseline [path]");
4248
4846
  console.log("");
4249
- console.log(chalk11.bold("Arguments:"));
4250
- console.log(` ${chalk11.cyan("[path]")} Path to baseline (default: current directory)`);
4847
+ console.log(chalk14.bold("Arguments:"));
4848
+ console.log(` ${chalk14.cyan("[path]")} Path to baseline (default: current directory)`);
4251
4849
  console.log("");
4252
- console.log(chalk11.bold("What it does:"));
4850
+ console.log(chalk14.bold("What it does:"));
4253
4851
  console.log(" Runs a full scan and saves the result as .vibgrate/baseline.json.");
4254
4852
  console.log(" Future scans can compare against this file using --baseline.");
4255
4853
  console.log("");
4256
- console.log(chalk11.bold("Examples:"));
4854
+ console.log(chalk14.bold("Examples:"));
4257
4855
  console.log(" vibgrate baseline .");
4258
4856
  console.log(" vibgrate scan . --baseline .vibgrate/baseline.json --drift-worsening 10");
4259
4857
  },
4260
4858
  report: () => {
4261
4859
  console.log("");
4262
- console.log(chalk11.bold.underline("vibgrate report") + chalk11.dim(" \u2014 Generate a report from a saved scan artifact"));
4860
+ console.log(chalk14.bold.underline("vibgrate report") + chalk14.dim(" \u2014 Generate a report from a saved scan artifact"));
4263
4861
  console.log("");
4264
- console.log(chalk11.bold("Usage:"));
4862
+ console.log(chalk14.bold("Usage:"));
4265
4863
  console.log(" vibgrate report [options]");
4266
4864
  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)`);
4865
+ console.log(chalk14.bold("Options:"));
4866
+ console.log(` ${chalk14.cyan("--in <file>")} Input artifact file (default: .vibgrate/scan_result.json)`);
4867
+ console.log(` ${chalk14.cyan("--format <format>")} Output format: ${chalk14.white("text")} | md | json (default: text)`);
4270
4868
  console.log("");
4271
- console.log(chalk11.bold("Examples:"));
4869
+ console.log(chalk14.bold("Examples:"));
4272
4870
  console.log(" vibgrate report");
4273
4871
  console.log(" vibgrate report --format md > DRIFT-REPORT.md");
4274
4872
  console.log(" vibgrate report --in ./ci/scan_result.json --format json");
4275
4873
  },
4276
4874
  sbom: () => {
4277
4875
  console.log("");
4278
- console.log(chalk11.bold.underline("vibgrate sbom") + chalk11.dim(" \u2014 Export a Software Bill of Materials from a scan artifact"));
4876
+ console.log(chalk14.bold.underline("vibgrate sbom") + chalk14.dim(" \u2014 Export a Software Bill of Materials from a scan artifact"));
4279
4877
  console.log("");
4280
- console.log(chalk11.bold("Usage:"));
4878
+ console.log(chalk14.bold("Usage:"));
4281
4879
  console.log(" vibgrate sbom [options]");
4282
4880
  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`);
4881
+ console.log(chalk14.bold("Options:"));
4882
+ console.log(` ${chalk14.cyan("--in <file>")} Input artifact (default: .vibgrate/scan_result.json)`);
4883
+ console.log(` ${chalk14.cyan("--format <format>")} SBOM format: ${chalk14.white("cyclonedx")} | spdx (default: cyclonedx)`);
4884
+ console.log(` ${chalk14.cyan("--out <file>")} Write SBOM to file instead of stdout`);
4287
4885
  console.log("");
4288
- console.log(chalk11.bold("Examples:"));
4886
+ console.log(chalk14.bold("Examples:"));
4289
4887
  console.log(" vibgrate sbom --format cyclonedx --out sbom.json");
4290
4888
  console.log(" vibgrate sbom --format spdx --out sbom.spdx.json");
4291
4889
  },
4292
4890
  push: () => {
4293
4891
  console.log("");
4294
- console.log(chalk11.bold.underline("vibgrate push") + chalk11.dim(" \u2014 Upload a scan artifact to the Vibgrate API"));
4892
+ console.log(chalk14.bold.underline("vibgrate push") + chalk14.dim(" \u2014 Upload a scan artifact to the Vibgrate API"));
4295
4893
  console.log("");
4296
- console.log(chalk11.bold("Usage:"));
4894
+ console.log(chalk14.bold("Usage:"));
4297
4895
  console.log(" vibgrate push [options]");
4298
4896
  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`);
4897
+ console.log(chalk14.bold("Options:"));
4898
+ console.log(` ${chalk14.cyan("--dsn <dsn>")} DSN token (or set VIBGRATE_DSN env var)`);
4899
+ console.log(` ${chalk14.cyan("--file <file>")} Artifact to upload (default: .vibgrate/scan_result.json)`);
4900
+ console.log(` ${chalk14.cyan("--region <region>")} Override data residency region: us | eu`);
4901
+ console.log(` ${chalk14.cyan("--strict")} Fail with non-zero exit code on upload error`);
4304
4902
  console.log("");
4305
- console.log(chalk11.bold("Examples:"));
4903
+ console.log(chalk14.bold("Examples:"));
4306
4904
  console.log(" vibgrate push --dsn $VIBGRATE_DSN");
4307
4905
  console.log(" vibgrate push --file ./ci/scan_result.json --strict");
4308
4906
  },
4309
4907
  dsn: () => {
4310
4908
  console.log("");
4311
- console.log(chalk11.bold.underline("vibgrate dsn") + chalk11.dim(" \u2014 Manage DSN tokens for API authentication"));
4909
+ console.log(chalk14.bold.underline("vibgrate dsn") + chalk14.dim(" \u2014 Manage DSN tokens for API authentication"));
4312
4910
  console.log("");
4313
- console.log(chalk11.bold("Subcommands:"));
4314
- console.log(` ${chalk11.cyan("vibgrate dsn create")} Generate a new DSN token`);
4911
+ console.log(chalk14.bold("Subcommands:"));
4912
+ console.log(` ${chalk14.cyan("vibgrate dsn create")} Generate a new DSN token`);
4315
4913
  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!)`);
4914
+ console.log(chalk14.bold("dsn create options:"));
4915
+ console.log(` ${chalk14.cyan("--workspace <id>")} Workspace ID or "new" to auto-generate ${chalk14.red("(required)")}`);
4916
+ console.log(` ${chalk14.cyan("--region <region>")} Data residency region: us | eu (default: us)`);
4917
+ console.log(` ${chalk14.cyan("--ingest <url>")} Override ingest API URL`);
4918
+ console.log(` ${chalk14.cyan("--write <path>")} Write the DSN to a file (add to .gitignore!)`);
4321
4919
  console.log("");
4322
- console.log(chalk11.bold("Examples:"));
4920
+ console.log(chalk14.bold("Examples:"));
4323
4921
  console.log(" vibgrate dsn create --workspace new");
4324
4922
  console.log(" vibgrate dsn create --workspace abc123");
4325
4923
  console.log(" vibgrate dsn create --workspace new --region eu --write .vibgrate/.dsn");
4326
4924
  },
4327
4925
  update: () => {
4328
4926
  console.log("");
4329
- console.log(chalk11.bold.underline("vibgrate update") + chalk11.dim(" \u2014 Update the vibgrate CLI to the latest version"));
4927
+ console.log(chalk14.bold.underline("vibgrate update") + chalk14.dim(" \u2014 Update the vibgrate CLI to the latest version"));
4330
4928
  console.log("");
4331
- console.log(chalk11.bold("Usage:"));
4929
+ console.log(chalk14.bold("Usage:"));
4332
4930
  console.log(" vibgrate update [options]");
4333
4931
  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`);
4932
+ console.log(chalk14.bold("Options:"));
4933
+ console.log(` ${chalk14.cyan("--check")} Check for a newer version without installing`);
4934
+ console.log(` ${chalk14.cyan("--pm <manager>")} Force a package manager: npm | pnpm | yarn | bun`);
4337
4935
  console.log("");
4338
- console.log(chalk11.bold("Examples:"));
4936
+ console.log(chalk14.bold("Examples:"));
4339
4937
  console.log(" vibgrate update");
4340
4938
  console.log(" vibgrate update --check");
4341
4939
  console.log(" vibgrate update --pm pnpm");
@@ -4343,40 +4941,42 @@ var detailedHelp = {
4343
4941
  };
4344
4942
  function printSummaryHelp() {
4345
4943
  console.log("");
4346
- console.log(chalk11.bold("vibgrate") + chalk11.dim(" \u2014 Continuous Drift Intelligence"));
4944
+ console.log(chalk14.bold("vibgrate") + chalk14.dim(" \u2014 Continuous Drift Intelligence"));
4347
4945
  console.log("");
4348
- console.log(chalk11.bold("Usage:"));
4946
+ console.log(chalk14.bold("Usage:"));
4349
4947
  console.log(" vibgrate <command> [options]");
4350
4948
  console.log(" vibgrate help [command] Show detailed help for a command");
4351
4949
  console.log("");
4352
- console.log(chalk11.bold("Getting started:"));
4353
- console.log(` ${chalk11.cyan("init")} Initialise vibgrate in a project (creates config & .vibgrate/ dir)`);
4950
+ console.log(chalk14.bold("Getting started:"));
4951
+ console.log(` ${chalk14.cyan("init")} Initialise vibgrate in a project (creates config & .vibgrate/ dir)`);
4952
+ console.log(` ${chalk14.cyan("login")} Sign the CLI into your workspace via the browser`);
4953
+ console.log(` ${chalk14.cyan("logout")} Clear stored login credentials`);
4354
4954
  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`);
4955
+ console.log(chalk14.bold("Core scanning:"));
4956
+ console.log(` ${chalk14.cyan("scan")} Scan a project for upgrade drift and generate a report`);
4957
+ console.log(` ${chalk14.cyan("baseline")} Save a baseline snapshot to compare future scans against`);
4358
4958
  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)`);
4959
+ console.log(chalk14.bold("Reporting & export:"));
4960
+ console.log(` ${chalk14.cyan("report")} Re-generate a report from a previously saved scan artifact`);
4961
+ console.log(` ${chalk14.cyan("sbom")} Export a Software Bill of Materials (CycloneDX or SPDX)`);
4362
4962
  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`);
4963
+ console.log(chalk14.bold("CI/CD integration:"));
4964
+ console.log(` ${chalk14.cyan("push")} Upload a scan artifact to the Vibgrate API`);
4965
+ console.log(` ${chalk14.cyan("dsn")} Create and manage DSN tokens for API authentication`);
4366
4966
  console.log("");
4367
- console.log(chalk11.bold("Maintenance:"));
4368
- console.log(` ${chalk11.cyan("update")} Update the vibgrate CLI to the latest version`);
4967
+ console.log(chalk14.bold("Maintenance:"));
4968
+ console.log(` ${chalk14.cyan("update")} Update the vibgrate CLI to the latest version`);
4369
4969
  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")}`);
4970
+ console.log(chalk14.dim("Run") + ` ${chalk14.cyan("vibgrate help <command>")} ` + chalk14.dim("for detailed options, e.g.") + ` ${chalk14.cyan("vibgrate help scan")}`);
4371
4971
  }
4372
- var helpCommand = new Command10("help").description("Show help for vibgrate commands").argument("[command]", "Command to show detailed help for").helpOption(false).action((cmd) => {
4972
+ var helpCommand = new Command12("help").description("Show help for vibgrate commands").argument("[command]", "Command to show detailed help for").helpOption(false).action((cmd) => {
4373
4973
  const name = cmd?.toLowerCase().trim();
4374
4974
  if (name && detailedHelp[name]) {
4375
4975
  detailedHelp[name]();
4376
4976
  } else if (name) {
4377
4977
  console.log("");
4378
- console.log(chalk11.red(`Unknown command: ${name}`));
4379
- console.log(chalk11.dim(`Available commands: ${Object.keys(detailedHelp).join(", ")}`));
4978
+ console.log(chalk14.red(`Unknown command: ${name}`));
4979
+ console.log(chalk14.dim(`Available commands: ${Object.keys(detailedHelp).join(", ")}`));
4380
4980
  printSummaryHelp();
4381
4981
  } else {
4382
4982
  printSummaryHelp();
@@ -4385,13 +4985,15 @@ var helpCommand = new Command10("help").description("Show help for vibgrate comm
4385
4985
  });
4386
4986
 
4387
4987
  // src/cli.ts
4388
- var program = new Command11();
4988
+ var program = new Command13();
4389
4989
  program.name("vibgrate").description("Continuous Drift Intelligence").version(VERSION).addHelpText("after", "\nSee https://vibgrate.com/help for more guidance");
4390
4990
  program.addCommand(helpCommand);
4391
4991
  program.addCommand(initCommand);
4392
4992
  program.addCommand(scanCommand);
4393
4993
  program.addCommand(baselineCommand);
4394
4994
  program.addCommand(reportCommand);
4995
+ program.addCommand(loginCommand);
4996
+ program.addCommand(logoutCommand);
4395
4997
  program.addCommand(dsnCommand);
4396
4998
  program.addCommand(pushCommand);
4397
4999
  program.addCommand(updateCommand);
@@ -4402,8 +5004,8 @@ function notifyIfUpdateAvailable() {
4402
5004
  void checkForUpdate().then((update) => {
4403
5005
  if (!update?.updateAvailable) return;
4404
5006
  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.'));
5007
+ console.error(chalk15.yellow(` Update available: ${update.current} \u2192 ${update.latest}`));
5008
+ console.error(chalk15.dim(' Run "vibgrate update" to install the latest version.'));
4407
5009
  console.error("");
4408
5010
  }).catch(() => {
4409
5011
  });