@vibgrate/cli 0.1.4 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,7 @@ import * as path from "path";
4
4
  var SKIP_DIRS = /* @__PURE__ */ new Set([
5
5
  "node_modules",
6
6
  ".git",
7
+ ".vibgrate",
7
8
  ".next",
8
9
  "dist",
9
10
  "build",
@@ -80,6 +81,7 @@ async function writeTextFile(filePath, content) {
80
81
  }
81
82
 
82
83
  // src/scoring/drift-score.ts
84
+ import * as crypto from "crypto";
83
85
  var DEFAULT_THRESHOLDS = {
84
86
  failOnError: {
85
87
  eolDays: 180,
@@ -274,12 +276,27 @@ function generateFindings(projects, config) {
274
276
  }
275
277
  return findings;
276
278
  }
279
+ function computeProjectId(relativePath, projectName, workspaceId) {
280
+ const input = workspaceId ? `${relativePath}:${projectName}:${workspaceId}` : `${relativePath}:${projectName}`;
281
+ return crypto.createHash("sha256").update(input).digest("hex").slice(0, 16);
282
+ }
283
+
284
+ // src/version.ts
285
+ import { createRequire } from "module";
286
+ var require2 = createRequire(import.meta.url);
287
+ var pkg = require2("../package.json");
288
+ var VERSION = pkg.version;
277
289
 
278
290
  // src/formatters/text.ts
279
291
  import chalk from "chalk";
280
292
  function formatText(artifact) {
281
293
  const lines = [];
282
294
  lines.push("");
295
+ lines.push(chalk.cyan(" \u256D\u2500\u2500\u2500\u256E") + chalk.greenBright("\u279C"));
296
+ lines.push(chalk.cyan(" \u256D\u2524") + chalk.greenBright("\u25C9 \u25C9") + chalk.cyan("\u251C\u256E") + " " + chalk.bold.white("V I B G R A T E"));
297
+ lines.push(chalk.cyan(" \u2570\u2524") + chalk.dim("\u2500\u2500\u2500") + chalk.cyan("\u251C\u256F") + " " + chalk.dim(`Drift Intelligence Engine v${VERSION}`));
298
+ lines.push(chalk.cyan(" \u2570\u2500\u2500\u2500\u256F"));
299
+ lines.push("");
283
300
  lines.push(chalk.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"));
284
301
  lines.push(chalk.bold.cyan("\u2551 Vibgrate Drift Report \u2551"));
285
302
  lines.push(chalk.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"));
@@ -366,7 +383,15 @@ function formatText(artifact) {
366
383
  lines.push("");
367
384
  }
368
385
  }
369
- lines.push(chalk.dim(` Scanned at ${artifact.timestamp}`));
386
+ const scannedParts = [`Scanned at ${artifact.timestamp}`];
387
+ if (artifact.durationMs !== void 0) {
388
+ const secs = (artifact.durationMs / 1e3).toFixed(1);
389
+ scannedParts.push(`${secs}s`);
390
+ }
391
+ if (artifact.filesScanned !== void 0) {
392
+ scannedParts.push(`${artifact.filesScanned} file${artifact.filesScanned !== 1 ? "s" : ""} scanned`);
393
+ }
394
+ lines.push(chalk.dim(` ${scannedParts.join(" \xB7 ")}`));
370
395
  lines.push("");
371
396
  return lines.join("\n");
372
397
  }
@@ -534,10 +559,17 @@ function generatePriorityActions(artifact) {
534
559
  );
535
560
  if (eolProjects.length > 0) {
536
561
  const names = eolProjects.map((p) => p.name).join(", ");
537
- const runtimes = eolProjects.map((p) => `${p.runtime} \u2192 ${p.runtimeLatest}`).join(", ");
562
+ let detail = `End-of-life runtimes no longer receive security patches and block ecosystem upgrades.`;
563
+ const fileLines = [];
564
+ for (const p of eolProjects) {
565
+ fileLines.push(`
566
+ ./${p.path}`);
567
+ fileLines.push(` ${p.runtime} \u2192 ${p.runtimeLatest} (${p.runtimeMajorsBehind} major${p.runtimeMajorsBehind > 1 ? "s" : ""} behind)`);
568
+ }
569
+ detail += fileLines.join("");
538
570
  actions.push({
539
571
  title: `Upgrade EOL runtime${eolProjects.length > 1 ? "s" : ""} in ${names}`,
540
- explanation: `${runtimes}. End-of-life runtimes no longer receive security patches and block ecosystem upgrades.`,
572
+ explanation: detail,
541
573
  impact: `+${Math.min(eolProjects.length * 10, 30)} points (runtime & EOL scores)`,
542
574
  severity: 100
543
575
  });
@@ -546,16 +578,30 @@ function generatePriorityActions(artifact) {
546
578
  for (const p of artifact.projects) {
547
579
  for (const fw of p.frameworks) {
548
580
  if (fw.majorsBehind !== null && fw.majorsBehind >= 3) {
549
- severeFrameworks.push({ name: fw.name, fw: `${fw.currentVersion} \u2192 ${fw.latestVersion}`, behind: fw.majorsBehind, project: p.name });
581
+ severeFrameworks.push({ name: fw.name, fw: `${fw.currentVersion} \u2192 ${fw.latestVersion}`, behind: fw.majorsBehind, project: p.name, projectPath: p.path });
550
582
  }
551
583
  }
552
584
  }
553
585
  if (severeFrameworks.length > 0) {
554
586
  const worst = severeFrameworks.sort((a, b) => b.behind - a.behind)[0];
555
587
  const others = severeFrameworks.length > 1 ? ` (+${severeFrameworks.length - 1} more)` : "";
588
+ let detail = `${worst.behind} major versions behind. Major framework drift increases breaking change risk and blocks access to security fixes and performance improvements.`;
589
+ const fileLines = [];
590
+ let shown = 0;
591
+ for (const sf of severeFrameworks) {
592
+ if (shown >= 8) break;
593
+ fileLines.push(`
594
+ ./${sf.projectPath}`);
595
+ fileLines.push(` ${sf.name}: ${sf.fw} (${sf.behind} major${sf.behind > 1 ? "s" : ""} behind)`);
596
+ shown++;
597
+ }
598
+ const remaining = severeFrameworks.length - shown;
599
+ detail += fileLines.join("");
600
+ if (remaining > 0) detail += `
601
+ ... and ${remaining} more`;
556
602
  actions.push({
557
603
  title: `Upgrade ${worst.name} ${worst.fw} in ${worst.project}${others}`,
558
- explanation: `${worst.behind} major versions behind. Major framework drift increases breaking change risk and blocks access to security fixes and performance improvements.`,
604
+ explanation: detail,
559
605
  impact: `+5\u201315 points (framework score)`,
560
606
  severity: 90
561
607
  });
@@ -566,9 +612,28 @@ function generatePriorityActions(artifact) {
566
612
  if (total === 0) continue;
567
613
  const twoPlusPct = Math.round(b.twoPlusBehind / total * 100);
568
614
  if (twoPlusPct >= 40) {
615
+ let detail = `${b.twoPlusBehind} of ${total} dependencies are 2+ majors behind. Run \`npm outdated\` and prioritise packages with known CVEs or breaking API changes.`;
616
+ const worstDeps = p.dependencies.filter((d) => d.majorsBehind !== null && d.majorsBehind >= 2).sort((a, b2) => (b2.majorsBehind ?? 0) - (a.majorsBehind ?? 0));
617
+ if (worstDeps.length > 0) {
618
+ const depLines = [];
619
+ let shown = 0;
620
+ depLines.push(`
621
+ ./${p.path}`);
622
+ for (const dep of worstDeps) {
623
+ if (shown >= 8) break;
624
+ const current = dep.resolvedVersion ?? dep.currentSpec;
625
+ const latest = dep.latestStable ?? "?";
626
+ depLines.push(` ${dep.package}: ${current} \u2192 ${latest} (${dep.majorsBehind} major${dep.majorsBehind > 1 ? "s" : ""} behind)`);
627
+ shown++;
628
+ }
629
+ const remaining = worstDeps.length - shown;
630
+ detail += depLines.join("");
631
+ if (remaining > 0) detail += `
632
+ ... and ${remaining} more`;
633
+ }
569
634
  actions.push({
570
635
  title: `Reduce dependency rot in ${p.name} (${twoPlusPct}% severely outdated)`,
571
- explanation: `${b.twoPlusBehind} of ${total} dependencies are 2+ majors behind. Run \`npm outdated\` and prioritise packages with known CVEs or breaking API changes.`,
636
+ explanation: detail,
572
637
  impact: `+5\u201310 points (dependency score)`,
573
638
  severity: 80 + twoPlusPct / 10
574
639
  });
@@ -578,7 +643,7 @@ function generatePriorityActions(artifact) {
578
643
  for (const p of artifact.projects) {
579
644
  for (const fw of p.frameworks) {
580
645
  if (fw.majorsBehind === 2) {
581
- twoMajorFrameworks.push({ name: fw.name, project: p.name, fw: `${fw.currentVersion} \u2192 ${fw.latestVersion}` });
646
+ twoMajorFrameworks.push({ name: fw.name, project: p.name, projectPath: p.path, fw: `${fw.currentVersion} \u2192 ${fw.latestVersion}` });
582
647
  }
583
648
  }
584
649
  }
@@ -586,9 +651,23 @@ function generatePriorityActions(artifact) {
586
651
  if (uniqueTwo.length > 0) {
587
652
  const list = uniqueTwo.slice(0, 3).map((f) => `${f.name} (${f.fw})`).join(", ");
588
653
  const moreCount = uniqueTwo.length > 3 ? ` +${uniqueTwo.length - 3} more` : "";
654
+ let detail = `These frameworks are 2 major versions behind. Create upgrade tickets and check migration guides \u2014 the gap will widen with each new release.`;
655
+ const fileLines = [];
656
+ let shown = 0;
657
+ for (const tf of twoMajorFrameworks) {
658
+ if (shown >= 8) break;
659
+ fileLines.push(`
660
+ ./${tf.projectPath}`);
661
+ fileLines.push(` ${tf.name}: ${tf.fw}`);
662
+ shown++;
663
+ }
664
+ const remaining = twoMajorFrameworks.length - shown;
665
+ detail += fileLines.join("");
666
+ if (remaining > 0) detail += `
667
+ ... and ${remaining} more`;
589
668
  actions.push({
590
669
  title: `Plan major framework upgrades: ${list}${moreCount}`,
591
- explanation: `These frameworks are 2 major versions behind. Create upgrade tickets and check migration guides \u2014 the gap will widen with each new release.`,
670
+ explanation: detail,
592
671
  impact: `+5\u201310 points (framework score)`,
593
672
  severity: 60
594
673
  });
@@ -599,9 +678,31 @@ function generatePriorityActions(artifact) {
599
678
  if (total > 0) {
600
679
  const items = [...bc.deprecatedPackages, ...bc.legacyPolyfills].slice(0, 5).join(", ");
601
680
  const moreCount = total > 5 ? ` +${total - 5} more` : "";
681
+ let detail = `${total} package${total !== 1 ? "s" : ""} are deprecated or legacy polyfills. These receive no updates and may have known vulnerabilities.`;
682
+ const allPkgNames = /* @__PURE__ */ new Set([...bc.deprecatedPackages, ...bc.legacyPolyfills]);
683
+ const fileLines = [];
684
+ let shown = 0;
685
+ for (const p of artifact.projects) {
686
+ const matches = p.dependencies.filter((d) => allPkgNames.has(d.package));
687
+ if (matches.length === 0) continue;
688
+ if (shown >= 10) break;
689
+ fileLines.push(`
690
+ ./${p.path}`);
691
+ for (const dep of matches) {
692
+ if (shown >= 10) break;
693
+ const ver = dep.resolvedVersion ?? dep.currentSpec;
694
+ const label = bc.deprecatedPackages.includes(dep.package) ? "deprecated" : "polyfill";
695
+ fileLines.push(` ${dep.package}: ${ver} (${label})`);
696
+ shown++;
697
+ }
698
+ }
699
+ const remaining = total - shown;
700
+ detail += fileLines.join("");
701
+ if (remaining > 0) detail += `
702
+ ... and ${remaining} more`;
602
703
  actions.push({
603
704
  title: `Replace deprecated/legacy packages: ${items}${moreCount}`,
604
- explanation: `${total} package${total !== 1 ? "s" : ""} are deprecated or legacy polyfills. These receive no updates and may have known vulnerabilities.`,
705
+ explanation: detail,
605
706
  severity: 55
606
707
  });
607
708
  }
@@ -648,9 +749,20 @@ function generatePriorityActions(artifact) {
648
749
  const issues = [];
649
750
  if (sec.envFilesTracked) issues.push(".env files are tracked in git");
650
751
  if (!sec.lockfilePresent) issues.push("no lockfile found");
752
+ let detail;
753
+ if (sec.envFilesTracked) {
754
+ detail = "Environment files may contain secrets. Add them to .gitignore and rotate any exposed credentials immediately.";
755
+ detail += "\n ./.gitignore";
756
+ detail += "\n Add: .env, .env.*, .env.local";
757
+ } else {
758
+ detail = "Without a lockfile, installs are non-deterministic. Run the install command to generate one and commit it.";
759
+ detail += "\n ./";
760
+ detail += `
761
+ Missing: ${sec.lockfileTypes.length > 0 ? sec.lockfileTypes.join(", ") + " (multiple types detected)" : "package-lock.json, pnpm-lock.yaml, or yarn.lock"}`;
762
+ }
651
763
  actions.push({
652
764
  title: `Fix security posture: ${issues.join(", ")}`,
653
- explanation: sec.envFilesTracked ? "Environment files may contain secrets. Add them to .gitignore and rotate any exposed credentials immediately." : "Without a lockfile, installs are non-deterministic. Run the install command to generate one and commit it.",
765
+ explanation: detail,
654
766
  severity: 95
655
767
  });
656
768
  }
@@ -660,9 +772,22 @@ function generatePriorityActions(artifact) {
660
772
  const highImpactDupes = dupes.filter((d) => d.versions.length >= 3);
661
773
  if (highImpactDupes.length >= 3) {
662
774
  const names = highImpactDupes.slice(0, 4).map((d) => `${d.name} (${d.versions.length}v)`).join(", ");
775
+ let detail = `${highImpactDupes.length} packages have 3+ versions installed. Run \`npm dedupe\` to reduce bundle size and install time.`;
776
+ const dupeLines = [];
777
+ let shown = 0;
778
+ for (const d of highImpactDupes) {
779
+ if (shown >= 8) break;
780
+ dupeLines.push(`
781
+ ${d.name}: ${d.versions.join(", ")} (${d.consumers} consumer${d.consumers !== 1 ? "s" : ""})`);
782
+ shown++;
783
+ }
784
+ const remaining = highImpactDupes.length - shown;
785
+ detail += dupeLines.join("");
786
+ if (remaining > 0) detail += `
787
+ ... and ${remaining} more`;
663
788
  actions.push({
664
789
  title: `Deduplicate heavily-versioned packages`,
665
- explanation: `${highImpactDupes.length} packages have 3+ versions installed: ${names}. Run \`npm dedupe\` to reduce bundle size and install time.`,
790
+ explanation: detail,
666
791
  severity: 35
667
792
  });
668
793
  }
@@ -753,19 +878,146 @@ function toSarifResult(finding) {
753
878
  };
754
879
  }
755
880
 
756
- // src/version.ts
757
- import { createRequire } from "module";
758
- var require2 = createRequire(import.meta.url);
759
- var pkg = require2("../package.json");
760
- var VERSION = pkg.version;
761
-
762
- // src/commands/scan.ts
763
- import * as path12 from "path";
881
+ // src/commands/dsn.ts
882
+ import * as crypto2 from "crypto";
883
+ import * as path2 from "path";
764
884
  import { Command } from "commander";
885
+ import chalk2 from "chalk";
886
+ var REGION_HOSTS = {
887
+ us: "us.ingest.vibgrate.com",
888
+ eu: "eu.ingest.vibgrate.com"
889
+ };
890
+ function resolveIngestHost(region, ingest) {
891
+ if (ingest) {
892
+ try {
893
+ return new URL(ingest).host;
894
+ } catch {
895
+ throw new Error(`Invalid ingest URL: ${ingest}`);
896
+ }
897
+ }
898
+ const r = (region ?? "us").toLowerCase();
899
+ const host = REGION_HOSTS[r];
900
+ if (!host) {
901
+ throw new Error(`Unknown region "${r}". Supported: ${Object.keys(REGION_HOSTS).join(", ")}`);
902
+ }
903
+ return host;
904
+ }
905
+ var dsnCommand = new Command("dsn").description("Manage DSN tokens");
906
+ dsnCommand.command("create").description("Create a new DSN token").option("--ingest <url>", "Ingest API URL (overrides --region)").option("--region <region>", "Data residency region (us, eu)", "us").requiredOption("--workspace <id>", "Workspace ID").option("--write <path>", "Write DSN to file").action(async (opts) => {
907
+ const keyId = crypto2.randomBytes(8).toString("hex");
908
+ const secret = crypto2.randomBytes(32).toString("hex");
909
+ let ingestHost;
910
+ try {
911
+ ingestHost = resolveIngestHost(opts.region, opts.ingest);
912
+ } catch (e) {
913
+ console.error(chalk2.red(e instanceof Error ? e.message : String(e)));
914
+ process.exit(1);
915
+ }
916
+ const dsn = `vibgrate+https://${keyId}:${secret}@${ingestHost}/${opts.workspace}`;
917
+ console.log(chalk2.green("\u2714") + " DSN created");
918
+ console.log("");
919
+ console.log(chalk2.bold("DSN:"));
920
+ console.log(` ${dsn}`);
921
+ console.log("");
922
+ console.log(chalk2.bold("Key ID:"));
923
+ console.log(` ${keyId}`);
924
+ console.log("");
925
+ console.log(chalk2.dim("Set this as VIBGRATE_DSN in your CI environment."));
926
+ console.log(chalk2.dim("The secret must be registered on your Vibgrate ingest API."));
927
+ if (opts.write) {
928
+ const writePath = path2.resolve(opts.write);
929
+ await writeTextFile(writePath, dsn + "\n");
930
+ console.log("");
931
+ console.log(chalk2.green("\u2714") + ` DSN written to ${opts.write}`);
932
+ console.log(chalk2.yellow("\u26A0") + " Add this file to .gitignore!");
933
+ }
934
+ });
935
+
936
+ // src/commands/push.ts
937
+ import * as crypto3 from "crypto";
938
+ import * as path3 from "path";
939
+ import { Command as Command2 } from "commander";
765
940
  import chalk3 from "chalk";
941
+ function parseDsn(dsn) {
942
+ const match = dsn.match(/^vibgrate\+https:\/\/([^:]+):([^@]+)@([^/]+)\/(.+)$/);
943
+ if (!match) return null;
944
+ return {
945
+ keyId: match[1],
946
+ secret: match[2],
947
+ host: match[3],
948
+ workspaceId: match[4]
949
+ };
950
+ }
951
+ function computeHmac(body, secret) {
952
+ return crypto3.createHmac("sha256", secret).update(body).digest("base64");
953
+ }
954
+ var pushCommand = new Command2("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 (us, eu)").option("--file <file>", "Scan artifact file", ".vibgrate/scan_result.json").option("--strict", "Fail on upload errors").action(async (opts) => {
955
+ const dsn = opts.dsn || process.env.VIBGRATE_DSN;
956
+ if (!dsn) {
957
+ console.error(chalk3.red("No DSN provided."));
958
+ console.error(chalk3.dim("Set VIBGRATE_DSN environment variable or use --dsn flag."));
959
+ if (opts.strict) process.exit(1);
960
+ return;
961
+ }
962
+ const parsed = parseDsn(dsn);
963
+ if (!parsed) {
964
+ console.error(chalk3.red("Invalid DSN format."));
965
+ console.error(chalk3.dim("Expected: vibgrate+https://<key_id>:<secret>@<host>/<workspace_id>"));
966
+ if (opts.strict) process.exit(1);
967
+ return;
968
+ }
969
+ const filePath = path3.resolve(opts.file);
970
+ if (!await pathExists(filePath)) {
971
+ console.error(chalk3.red(`Scan artifact not found: ${filePath}`));
972
+ console.error(chalk3.dim('Run "vibgrate scan" first.'));
973
+ if (opts.strict) process.exit(1);
974
+ return;
975
+ }
976
+ const body = await readTextFile(filePath);
977
+ const timestamp = String(Date.now());
978
+ const hmac = computeHmac(body, parsed.secret);
979
+ let host = parsed.host;
980
+ if (opts.region) {
981
+ try {
982
+ host = resolveIngestHost(opts.region);
983
+ } catch (e) {
984
+ console.error(chalk3.red(e instanceof Error ? e.message : String(e)));
985
+ if (opts.strict) process.exit(1);
986
+ return;
987
+ }
988
+ }
989
+ const url = `https://${host}/v1/ingest/scan`;
990
+ console.log(chalk3.dim(`Uploading to ${host}...`));
991
+ try {
992
+ const response = await fetch(url, {
993
+ method: "POST",
994
+ headers: {
995
+ "Content-Type": "application/json",
996
+ "X-Vibgrate-Timestamp": timestamp,
997
+ "Authorization": `VibgrateDSN ${parsed.keyId}:${hmac}`
998
+ },
999
+ body
1000
+ });
1001
+ if (!response.ok) {
1002
+ const text = await response.text();
1003
+ throw new Error(`HTTP ${response.status}: ${text}`);
1004
+ }
1005
+ const result = await response.json();
1006
+ console.log(chalk3.green("\u2714") + ` Uploaded successfully (${result.ingestId ?? "ok"})`);
1007
+ } catch (e) {
1008
+ const msg = e instanceof Error ? e.message : String(e);
1009
+ console.error(chalk3.red(`Upload failed: ${msg}`));
1010
+ if (opts.strict) process.exit(1);
1011
+ }
1012
+ });
1013
+
1014
+ // src/commands/scan.ts
1015
+ import * as path14 from "path";
1016
+ import { Command as Command3 } from "commander";
1017
+ import chalk5 from "chalk";
766
1018
 
767
1019
  // src/scanners/node-scanner.ts
768
- import * as path2 from "path";
1020
+ import * as path4 from "path";
769
1021
  import * as semver2 from "semver";
770
1022
 
771
1023
  // src/scanners/npm-cache.ts
@@ -780,7 +1032,7 @@ function maxStable(versions) {
780
1032
  return stable.sort(semver.rcompare)[0] ?? null;
781
1033
  }
782
1034
  async function npmViewJson(args, cwd) {
783
- return new Promise((resolve4, reject) => {
1035
+ return new Promise((resolve6, reject) => {
784
1036
  const child = spawn("npm", ["view", ...args, "--json"], {
785
1037
  cwd,
786
1038
  stdio: ["ignore", "pipe", "pipe"]
@@ -797,13 +1049,13 @@ async function npmViewJson(args, cwd) {
797
1049
  }
798
1050
  const trimmed = out.trim();
799
1051
  if (!trimmed) {
800
- resolve4(null);
1052
+ resolve6(null);
801
1053
  return;
802
1054
  }
803
1055
  try {
804
- resolve4(JSON.parse(trimmed));
1056
+ resolve6(JSON.parse(trimmed));
805
1057
  } catch {
806
- resolve4(trimmed.replace(/^"|"$/g, ""));
1058
+ resolve6(trimmed.replace(/^"|"$/g, ""));
807
1059
  }
808
1060
  });
809
1061
  });
@@ -955,8 +1207,8 @@ async function scanNodeProjects(rootDir, npmCache) {
955
1207
  }
956
1208
  async function scanOnePackageJson(packageJsonPath, rootDir, npmCache) {
957
1209
  const pj = await readJsonFile(packageJsonPath);
958
- const absProjectPath = path2.dirname(packageJsonPath);
959
- const projectPath = path2.relative(rootDir, absProjectPath) || ".";
1210
+ const absProjectPath = path4.dirname(packageJsonPath);
1211
+ const projectPath = path4.relative(rootDir, absProjectPath) || ".";
960
1212
  const nodeEngine = pj.engines?.node ?? void 0;
961
1213
  let runtimeLatest;
962
1214
  let runtimeMajorsBehind;
@@ -1038,7 +1290,7 @@ async function scanOnePackageJson(packageJsonPath, rootDir, npmCache) {
1038
1290
  return {
1039
1291
  type: "node",
1040
1292
  path: projectPath,
1041
- name: pj.name ?? path2.basename(absProjectPath),
1293
+ name: pj.name ?? path4.basename(absProjectPath),
1042
1294
  runtime: nodeEngine,
1043
1295
  runtimeLatest,
1044
1296
  runtimeMajorsBehind,
@@ -1049,7 +1301,7 @@ async function scanOnePackageJson(packageJsonPath, rootDir, npmCache) {
1049
1301
  }
1050
1302
 
1051
1303
  // src/scanners/dotnet-scanner.ts
1052
- import * as path3 from "path";
1304
+ import * as path5 from "path";
1053
1305
  import { XMLParser } from "fast-xml-parser";
1054
1306
  var parser = new XMLParser({
1055
1307
  ignoreAttributes: false,
@@ -1250,7 +1502,7 @@ function parseCsproj(xml, filePath) {
1250
1502
  const parsed = parser.parse(xml);
1251
1503
  const project = parsed?.Project;
1252
1504
  if (!project) {
1253
- return { targetFrameworks: [], packageReferences: [], projectName: path3.basename(filePath, ".csproj") };
1505
+ return { targetFrameworks: [], packageReferences: [], projectName: path5.basename(filePath, ".csproj") };
1254
1506
  }
1255
1507
  const propertyGroups = Array.isArray(project.PropertyGroup) ? project.PropertyGroup : project.PropertyGroup ? [project.PropertyGroup] : [];
1256
1508
  const targetFrameworks = [];
@@ -1278,7 +1530,7 @@ function parseCsproj(xml, filePath) {
1278
1530
  return {
1279
1531
  targetFrameworks: [...new Set(targetFrameworks)],
1280
1532
  packageReferences,
1281
- projectName: path3.basename(filePath, ".csproj")
1533
+ projectName: path5.basename(filePath, ".csproj")
1282
1534
  };
1283
1535
  }
1284
1536
  async function scanDotnetProjects(rootDir) {
@@ -1288,12 +1540,12 @@ async function scanDotnetProjects(rootDir) {
1288
1540
  for (const slnPath of slnFiles) {
1289
1541
  try {
1290
1542
  const slnContent = await readTextFile(slnPath);
1291
- const slnDir = path3.dirname(slnPath);
1543
+ const slnDir = path5.dirname(slnPath);
1292
1544
  const projectRegex = /Project\("[^"]*"\)\s*=\s*"[^"]*",\s*"([^"]+\.csproj)"/g;
1293
1545
  let match;
1294
1546
  while ((match = projectRegex.exec(slnContent)) !== null) {
1295
1547
  if (match[1]) {
1296
- const csprojPath = path3.resolve(slnDir, match[1].replace(/\\/g, "/"));
1548
+ const csprojPath = path5.resolve(slnDir, match[1].replace(/\\/g, "/"));
1297
1549
  slnCsprojPaths.add(csprojPath);
1298
1550
  }
1299
1551
  }
@@ -1349,7 +1601,7 @@ async function scanOneCsproj(csprojPath, rootDir) {
1349
1601
  const buckets = { current: 0, oneBehind: 0, twoPlusBehind: 0, unknown: dependencies.length };
1350
1602
  return {
1351
1603
  type: "dotnet",
1352
- path: path3.relative(rootDir, path3.dirname(csprojPath)) || ".",
1604
+ path: path5.relative(rootDir, path5.dirname(csprojPath)) || ".",
1353
1605
  name: data.projectName,
1354
1606
  targetFramework,
1355
1607
  runtime: primaryTfm,
@@ -1381,7 +1633,7 @@ var Semaphore = class {
1381
1633
  this.available--;
1382
1634
  return Promise.resolve();
1383
1635
  }
1384
- return new Promise((resolve4) => this.queue.push(resolve4));
1636
+ return new Promise((resolve6) => this.queue.push(resolve6));
1385
1637
  }
1386
1638
  release() {
1387
1639
  const next = this.queue.shift();
@@ -1391,7 +1643,7 @@ var Semaphore = class {
1391
1643
  };
1392
1644
 
1393
1645
  // src/config.ts
1394
- import * as path4 from "path";
1646
+ import * as path6 from "path";
1395
1647
  import * as fs2 from "fs/promises";
1396
1648
  var CONFIG_FILES = [
1397
1649
  "vibgrate.config.ts",
@@ -1414,7 +1666,7 @@ var DEFAULT_CONFIG = {
1414
1666
  };
1415
1667
  async function loadConfig(rootDir) {
1416
1668
  for (const file of CONFIG_FILES) {
1417
- const configPath = path4.join(rootDir, file);
1669
+ const configPath = path6.join(rootDir, file);
1418
1670
  if (await pathExists(configPath)) {
1419
1671
  if (file.endsWith(".json")) {
1420
1672
  const txt = await readTextFile(configPath);
@@ -1430,7 +1682,7 @@ async function loadConfig(rootDir) {
1430
1682
  return DEFAULT_CONFIG;
1431
1683
  }
1432
1684
  async function writeDefaultConfig(rootDir) {
1433
- const configPath = path4.join(rootDir, "vibgrate.config.ts");
1685
+ const configPath = path6.join(rootDir, "vibgrate.config.ts");
1434
1686
  const content = `import type { VibgrateConfig } from '@vibgrate/cli';
1435
1687
 
1436
1688
  const config: VibgrateConfig = {
@@ -1455,7 +1707,7 @@ export default config;
1455
1707
  }
1456
1708
 
1457
1709
  // src/utils/vcs.ts
1458
- import * as path5 from "path";
1710
+ import * as path7 from "path";
1459
1711
  import * as fs3 from "fs/promises";
1460
1712
  async function detectVcs(rootDir) {
1461
1713
  try {
@@ -1469,7 +1721,7 @@ async function detectGit(rootDir) {
1469
1721
  if (!gitDir) {
1470
1722
  return { type: "unknown" };
1471
1723
  }
1472
- const headPath = path5.join(gitDir, "HEAD");
1724
+ const headPath = path7.join(gitDir, "HEAD");
1473
1725
  let headContent;
1474
1726
  try {
1475
1727
  headContent = (await fs3.readFile(headPath, "utf8")).trim();
@@ -1493,10 +1745,10 @@ async function detectGit(rootDir) {
1493
1745
  };
1494
1746
  }
1495
1747
  async function findGitDir(startDir) {
1496
- let dir = path5.resolve(startDir);
1497
- const root = path5.parse(dir).root;
1748
+ let dir = path7.resolve(startDir);
1749
+ const root = path7.parse(dir).root;
1498
1750
  while (dir !== root) {
1499
- const gitPath = path5.join(dir, ".git");
1751
+ const gitPath = path7.join(dir, ".git");
1500
1752
  try {
1501
1753
  const stat3 = await fs3.stat(gitPath);
1502
1754
  if (stat3.isDirectory()) {
@@ -1505,18 +1757,18 @@ async function findGitDir(startDir) {
1505
1757
  if (stat3.isFile()) {
1506
1758
  const content = (await fs3.readFile(gitPath, "utf8")).trim();
1507
1759
  if (content.startsWith("gitdir: ")) {
1508
- const resolved = path5.resolve(dir, content.slice(8));
1760
+ const resolved = path7.resolve(dir, content.slice(8));
1509
1761
  return resolved;
1510
1762
  }
1511
1763
  }
1512
1764
  } catch {
1513
1765
  }
1514
- dir = path5.dirname(dir);
1766
+ dir = path7.dirname(dir);
1515
1767
  }
1516
1768
  return null;
1517
1769
  }
1518
1770
  async function resolveRef(gitDir, refPath) {
1519
- const loosePath = path5.join(gitDir, refPath);
1771
+ const loosePath = path7.join(gitDir, refPath);
1520
1772
  try {
1521
1773
  const sha = (await fs3.readFile(loosePath, "utf8")).trim();
1522
1774
  if (/^[0-9a-f]{40}$/i.test(sha)) {
@@ -1524,7 +1776,7 @@ async function resolveRef(gitDir, refPath) {
1524
1776
  }
1525
1777
  } catch {
1526
1778
  }
1527
- const packedPath = path5.join(gitDir, "packed-refs");
1779
+ const packedPath = path7.join(gitDir, "packed-refs");
1528
1780
  try {
1529
1781
  const packed = await fs3.readFile(packedPath, "utf8");
1530
1782
  for (const line of packed.split("\n")) {
@@ -1540,16 +1792,16 @@ async function resolveRef(gitDir, refPath) {
1540
1792
  }
1541
1793
 
1542
1794
  // src/ui/progress.ts
1543
- import chalk2 from "chalk";
1795
+ import chalk4 from "chalk";
1544
1796
  var ROBOT = [
1545
- chalk2.cyan(" \u256D\u2500\u2500\u2500\u256E") + chalk2.greenBright("\u279C"),
1546
- chalk2.cyan(" \u256D\u2524") + chalk2.greenBright("\u25C9 \u25C9") + chalk2.cyan("\u251C\u256E"),
1547
- chalk2.cyan(" \u2570\u2524") + chalk2.dim("\u2500\u2500\u2500") + chalk2.cyan("\u251C\u256F"),
1548
- chalk2.cyan(" \u2570\u2500\u2500\u2500\u256F")
1797
+ chalk4.cyan(" \u256D\u2500\u2500\u2500\u256E") + chalk4.greenBright("\u279C"),
1798
+ chalk4.cyan(" \u256D\u2524") + chalk4.greenBright("\u25C9 \u25C9") + chalk4.cyan("\u251C\u256E"),
1799
+ chalk4.cyan(" \u2570\u2524") + chalk4.dim("\u2500\u2500\u2500") + chalk4.cyan("\u251C\u256F"),
1800
+ chalk4.cyan(" \u2570\u2500\u2500\u2500\u256F")
1549
1801
  ];
1550
1802
  var BRAND = [
1551
- chalk2.bold.white(" V I B G R A T E"),
1552
- chalk2.dim(" Drift Intelligence Engine")
1803
+ chalk4.bold.white(" V I B G R A T E"),
1804
+ chalk4.dim(` Drift Intelligence Engine`) + chalk4.dim(` v${VERSION}`)
1553
1805
  ];
1554
1806
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1555
1807
  var ScanProgress = class {
@@ -1643,7 +1895,7 @@ var ScanProgress = class {
1643
1895
  const elapsed = ((Date.now() - this.startTime) / 1e3).toFixed(1);
1644
1896
  const doneCount = this.steps.filter((s) => s.status === "done").length;
1645
1897
  process.stderr.write(
1646
- chalk2.dim(` \u2714 ${doneCount} scanners completed in ${elapsed}s
1898
+ chalk4.dim(` \u2714 ${doneCount} scanners completed in ${elapsed}s
1647
1899
 
1648
1900
  `)
1649
1901
  );
@@ -1675,16 +1927,16 @@ var ScanProgress = class {
1675
1927
  lines.push(` ${ROBOT[0]} ${BRAND[0]}`);
1676
1928
  lines.push(` ${ROBOT[1]} ${BRAND[1]}`);
1677
1929
  lines.push(` ${ROBOT[2]}`);
1678
- lines.push(` ${ROBOT[3]} ${chalk2.dim(this.rootDir)}`);
1930
+ lines.push(` ${ROBOT[3]} ${chalk4.dim(this.rootDir)}`);
1679
1931
  lines.push("");
1680
1932
  const totalSteps = this.steps.length;
1681
1933
  const doneSteps = this.steps.filter((s) => s.status === "done" || s.status === "skipped").length;
1682
1934
  const pct = totalSteps > 0 ? Math.round(doneSteps / totalSteps * 100) : 0;
1683
1935
  const barWidth = 30;
1684
1936
  const filled = Math.round(doneSteps / Math.max(totalSteps, 1) * barWidth);
1685
- const bar = chalk2.greenBright("\u2501".repeat(filled)) + chalk2.dim("\u254C".repeat(barWidth - filled));
1937
+ const bar = chalk4.greenBright("\u2501".repeat(filled)) + chalk4.dim("\u254C".repeat(barWidth - filled));
1686
1938
  const elapsed = ((Date.now() - this.startTime) / 1e3).toFixed(1);
1687
- lines.push(` ${bar} ${chalk2.bold.white(`${pct}%`)} ${chalk2.dim(`${elapsed}s`)}`);
1939
+ lines.push(` ${bar} ${chalk4.bold.white(`${pct}%`)} ${chalk4.dim(`${elapsed}s`)}`);
1688
1940
  lines.push("");
1689
1941
  for (const step of this.steps) {
1690
1942
  lines.push(this.renderStep(step));
@@ -1703,27 +1955,27 @@ var ScanProgress = class {
1703
1955
  let detail = "";
1704
1956
  switch (step.status) {
1705
1957
  case "done":
1706
- icon = chalk2.green("\u2714");
1707
- label = chalk2.white(step.label);
1958
+ icon = chalk4.green("\u2714");
1959
+ label = chalk4.white(step.label);
1708
1960
  break;
1709
1961
  case "active":
1710
- icon = chalk2.cyan(spinner);
1711
- label = chalk2.bold.white(step.label);
1962
+ icon = chalk4.cyan(spinner);
1963
+ label = chalk4.bold.white(step.label);
1712
1964
  break;
1713
1965
  case "skipped":
1714
- icon = chalk2.dim("\u25CC");
1715
- label = chalk2.dim.strikethrough(step.label);
1966
+ icon = chalk4.dim("\u25CC");
1967
+ label = chalk4.dim.strikethrough(step.label);
1716
1968
  break;
1717
1969
  default:
1718
- icon = chalk2.dim("\u25CB");
1719
- label = chalk2.dim(step.label);
1970
+ icon = chalk4.dim("\u25CB");
1971
+ label = chalk4.dim(step.label);
1720
1972
  break;
1721
1973
  }
1722
1974
  if (step.detail) {
1723
- detail = chalk2.dim(` \xB7 ${step.detail}`);
1975
+ detail = chalk4.dim(` \xB7 ${step.detail}`);
1724
1976
  }
1725
1977
  if (step.count !== void 0 && step.count > 0) {
1726
- detail += chalk2.cyan(` (${step.count})`);
1978
+ detail += chalk4.cyan(` (${step.count})`);
1727
1979
  }
1728
1980
  return ` ${icon} ${label}${detail}`;
1729
1981
  }
@@ -1735,18 +1987,18 @@ var ScanProgress = class {
1735
1987
  const e = this.stats.findings.errors;
1736
1988
  const n = this.stats.findings.notes;
1737
1989
  const parts = [
1738
- chalk2.bold.white(` ${p}`) + chalk2.dim(` project${p !== 1 ? "s" : ""}`),
1739
- chalk2.white(`${d}`) + chalk2.dim(` dep${d !== 1 ? "s" : ""}`),
1740
- chalk2.white(`${f}`) + chalk2.dim(` framework${f !== 1 ? "s" : ""}`)
1990
+ chalk4.bold.white(` ${p}`) + chalk4.dim(` project${p !== 1 ? "s" : ""}`),
1991
+ chalk4.white(`${d}`) + chalk4.dim(` dep${d !== 1 ? "s" : ""}`),
1992
+ chalk4.white(`${f}`) + chalk4.dim(` framework${f !== 1 ? "s" : ""}`)
1741
1993
  ];
1742
1994
  const findingParts = [];
1743
- if (e > 0) findingParts.push(chalk2.red(`${e} \u2716`));
1744
- if (w > 0) findingParts.push(chalk2.yellow(`${w} \u26A0`));
1745
- if (n > 0) findingParts.push(chalk2.blue(`${n} \u2139`));
1995
+ if (e > 0) findingParts.push(chalk4.red(`${e} \u2716`));
1996
+ if (w > 0) findingParts.push(chalk4.yellow(`${w} \u26A0`));
1997
+ if (n > 0) findingParts.push(chalk4.blue(`${n} \u2139`));
1746
1998
  if (findingParts.length > 0) {
1747
- parts.push(findingParts.join(chalk2.dim(" \xB7 ")));
1999
+ parts.push(findingParts.join(chalk4.dim(" \xB7 ")));
1748
2000
  }
1749
- return ` ${chalk2.dim("\u2503")} ${parts.join(chalk2.dim(" \u2502 "))}`;
2001
+ return ` ${chalk4.dim("\u2503")} ${parts.join(chalk4.dim(" \u2502 "))}`;
1750
2002
  }
1751
2003
  /** Simple CI-friendly output (no ANSI rewriting) */
1752
2004
  lastCIStep = null;
@@ -1765,7 +2017,7 @@ var ScanProgress = class {
1765
2017
  };
1766
2018
 
1767
2019
  // src/scanners/platform-matrix.ts
1768
- import * as path6 from "path";
2020
+ import * as path8 from "path";
1769
2021
  var NATIVE_MODULE_PACKAGES = /* @__PURE__ */ new Set([
1770
2022
  // Image / media processing
1771
2023
  "sharp",
@@ -2042,7 +2294,7 @@ async function scanPlatformMatrix(rootDir) {
2042
2294
  }
2043
2295
  result.dockerBaseImages = [...baseImages].sort();
2044
2296
  for (const file of [".nvmrc", ".node-version", ".tool-versions"]) {
2045
- if (await pathExists(path6.join(rootDir, file))) {
2297
+ if (await pathExists(path8.join(rootDir, file))) {
2046
2298
  result.nodeVersionFiles.push(file);
2047
2299
  }
2048
2300
  }
@@ -2118,7 +2370,7 @@ function scanDependencyRisk(projects) {
2118
2370
  }
2119
2371
 
2120
2372
  // src/scanners/dependency-graph.ts
2121
- import * as path7 from "path";
2373
+ import * as path9 from "path";
2122
2374
  function parsePnpmLock(content) {
2123
2375
  const entries = [];
2124
2376
  const regex = /^\s+\/?(@?[^@\s][^@\s]*?)@(\d+\.\d+\.\d+[^:\s]*)\s*:/gm;
@@ -2177,9 +2429,9 @@ async function scanDependencyGraph(rootDir) {
2177
2429
  phantomDependencies: []
2178
2430
  };
2179
2431
  let entries = [];
2180
- const pnpmLock = path7.join(rootDir, "pnpm-lock.yaml");
2181
- const npmLock = path7.join(rootDir, "package-lock.json");
2182
- const yarnLock = path7.join(rootDir, "yarn.lock");
2432
+ const pnpmLock = path9.join(rootDir, "pnpm-lock.yaml");
2433
+ const npmLock = path9.join(rootDir, "package-lock.json");
2434
+ const yarnLock = path9.join(rootDir, "yarn.lock");
2183
2435
  if (await pathExists(pnpmLock)) {
2184
2436
  result.lockfileType = "pnpm";
2185
2437
  const content = await readTextFile(pnpmLock);
@@ -2224,7 +2476,7 @@ async function scanDependencyGraph(rootDir) {
2224
2476
  for (const pjPath of pkgFiles) {
2225
2477
  try {
2226
2478
  const pj = await readJsonFile(pjPath);
2227
- const relPath = path7.relative(rootDir, pjPath);
2479
+ const relPath = path9.relative(rootDir, pjPath);
2228
2480
  for (const section of ["dependencies", "devDependencies"]) {
2229
2481
  const deps = pj[section];
2230
2482
  if (!deps) continue;
@@ -2570,7 +2822,7 @@ function scanToolingInventory(projects) {
2570
2822
  }
2571
2823
 
2572
2824
  // src/scanners/build-deploy.ts
2573
- import * as path8 from "path";
2825
+ import * as path10 from "path";
2574
2826
  var CI_FILES = {
2575
2827
  ".github/workflows": "github-actions",
2576
2828
  ".gitlab-ci.yml": "gitlab-ci",
@@ -2620,12 +2872,12 @@ async function scanBuildDeploy(rootDir) {
2620
2872
  };
2621
2873
  const ciSystems = /* @__PURE__ */ new Set();
2622
2874
  for (const [file, system] of Object.entries(CI_FILES)) {
2623
- const fullPath = path8.join(rootDir, file);
2875
+ const fullPath = path10.join(rootDir, file);
2624
2876
  if (await pathExists(fullPath)) {
2625
2877
  ciSystems.add(system);
2626
2878
  }
2627
2879
  }
2628
- const ghWorkflowDir = path8.join(rootDir, ".github", "workflows");
2880
+ const ghWorkflowDir = path10.join(rootDir, ".github", "workflows");
2629
2881
  if (await pathExists(ghWorkflowDir)) {
2630
2882
  try {
2631
2883
  const files = await findFiles(
@@ -2673,11 +2925,11 @@ async function scanBuildDeploy(rootDir) {
2673
2925
  (name) => name.endsWith(".cfn.json") || name.endsWith(".cfn.yaml")
2674
2926
  );
2675
2927
  if (cfnFiles.length > 0) iacSystems.add("cloudformation");
2676
- if (await pathExists(path8.join(rootDir, "Pulumi.yaml"))) iacSystems.add("pulumi");
2928
+ if (await pathExists(path10.join(rootDir, "Pulumi.yaml"))) iacSystems.add("pulumi");
2677
2929
  result.iac = [...iacSystems].sort();
2678
2930
  const releaseTools = /* @__PURE__ */ new Set();
2679
2931
  for (const [file, tool] of Object.entries(RELEASE_FILES)) {
2680
- if (await pathExists(path8.join(rootDir, file))) releaseTools.add(tool);
2932
+ if (await pathExists(path10.join(rootDir, file))) releaseTools.add(tool);
2681
2933
  }
2682
2934
  const pkgFiles = await findPackageJsonFiles(rootDir);
2683
2935
  for (const pjPath of pkgFiles) {
@@ -2702,19 +2954,19 @@ async function scanBuildDeploy(rootDir) {
2702
2954
  };
2703
2955
  const managers = /* @__PURE__ */ new Set();
2704
2956
  for (const [file, manager] of Object.entries(lockfileMap)) {
2705
- if (await pathExists(path8.join(rootDir, file))) managers.add(manager);
2957
+ if (await pathExists(path10.join(rootDir, file))) managers.add(manager);
2706
2958
  }
2707
2959
  result.packageManagers = [...managers].sort();
2708
2960
  const monoTools = /* @__PURE__ */ new Set();
2709
2961
  for (const [file, tool] of Object.entries(MONOREPO_FILES)) {
2710
- if (await pathExists(path8.join(rootDir, file))) monoTools.add(tool);
2962
+ if (await pathExists(path10.join(rootDir, file))) monoTools.add(tool);
2711
2963
  }
2712
2964
  result.monorepoTools = [...monoTools].sort();
2713
2965
  return result;
2714
2966
  }
2715
2967
 
2716
2968
  // src/scanners/ts-modernity.ts
2717
- import * as path9 from "path";
2969
+ import * as path11 from "path";
2718
2970
  async function scanTsModernity(rootDir) {
2719
2971
  const result = {
2720
2972
  typescriptVersion: null,
@@ -2752,7 +3004,7 @@ async function scanTsModernity(rootDir) {
2752
3004
  if (hasEsm && hasCjs) result.moduleType = "mixed";
2753
3005
  else if (hasEsm) result.moduleType = "esm";
2754
3006
  else if (hasCjs) result.moduleType = "cjs";
2755
- let tsConfigPath = path9.join(rootDir, "tsconfig.json");
3007
+ let tsConfigPath = path11.join(rootDir, "tsconfig.json");
2756
3008
  if (!await pathExists(tsConfigPath)) {
2757
3009
  const tsConfigs = await findFiles(rootDir, (name) => name === "tsconfig.json");
2758
3010
  if (tsConfigs.length > 0) {
@@ -3098,7 +3350,7 @@ function scanBreakingChangeExposure(projects) {
3098
3350
 
3099
3351
  // src/scanners/file-hotspots.ts
3100
3352
  import * as fs4 from "fs/promises";
3101
- import * as path10 from "path";
3353
+ import * as path12 from "path";
3102
3354
  var SKIP_DIRS2 = /* @__PURE__ */ new Set([
3103
3355
  "node_modules",
3104
3356
  ".git",
@@ -3153,15 +3405,15 @@ async function scanFileHotspots(rootDir) {
3153
3405
  for (const e of entries) {
3154
3406
  if (e.isDirectory) {
3155
3407
  if (SKIP_DIRS2.has(e.name)) continue;
3156
- await walk(path10.join(dir, e.name), depth + 1);
3408
+ await walk(path12.join(dir, e.name), depth + 1);
3157
3409
  } else if (e.isFile) {
3158
- const ext = path10.extname(e.name).toLowerCase();
3410
+ const ext = path12.extname(e.name).toLowerCase();
3159
3411
  if (SKIP_EXTENSIONS.has(ext)) continue;
3160
3412
  extensionCounts[ext] = (extensionCounts[ext] ?? 0) + 1;
3161
3413
  try {
3162
- const stat3 = await fs4.stat(path10.join(dir, e.name));
3414
+ const stat3 = await fs4.stat(path12.join(dir, e.name));
3163
3415
  allFiles.push({
3164
- path: path10.relative(rootDir, path10.join(dir, e.name)),
3416
+ path: path12.relative(rootDir, path12.join(dir, e.name)),
3165
3417
  bytes: stat3.size
3166
3418
  });
3167
3419
  } catch {
@@ -3183,7 +3435,7 @@ async function scanFileHotspots(rootDir) {
3183
3435
  }
3184
3436
 
3185
3437
  // src/scanners/security-posture.ts
3186
- import * as path11 from "path";
3438
+ import * as path13 from "path";
3187
3439
  var LOCKFILES = {
3188
3440
  "pnpm-lock.yaml": "pnpm",
3189
3441
  "package-lock.json": "npm",
@@ -3202,14 +3454,14 @@ async function scanSecurityPosture(rootDir) {
3202
3454
  };
3203
3455
  const foundLockfiles = [];
3204
3456
  for (const [file, type] of Object.entries(LOCKFILES)) {
3205
- if (await pathExists(path11.join(rootDir, file))) {
3457
+ if (await pathExists(path13.join(rootDir, file))) {
3206
3458
  foundLockfiles.push(type);
3207
3459
  }
3208
3460
  }
3209
3461
  result.lockfilePresent = foundLockfiles.length > 0;
3210
3462
  result.multipleLockfileTypes = foundLockfiles.length > 1;
3211
3463
  result.lockfileTypes = foundLockfiles.sort();
3212
- const gitignorePath = path11.join(rootDir, ".gitignore");
3464
+ const gitignorePath = path13.join(rootDir, ".gitignore");
3213
3465
  if (await pathExists(gitignorePath)) {
3214
3466
  try {
3215
3467
  const content = await readTextFile(gitignorePath);
@@ -3224,7 +3476,7 @@ async function scanSecurityPosture(rootDir) {
3224
3476
  }
3225
3477
  }
3226
3478
  for (const envFile of [".env", ".env.local", ".env.development", ".env.production"]) {
3227
- if (await pathExists(path11.join(rootDir, envFile))) {
3479
+ if (await pathExists(path13.join(rootDir, envFile))) {
3228
3480
  if (!result.gitignoreCoversEnv) {
3229
3481
  result.envFilesTracked = true;
3230
3482
  break;
@@ -3650,10 +3902,12 @@ function scanServiceDependencies(projects) {
3650
3902
 
3651
3903
  // src/commands/scan.ts
3652
3904
  async function runScan(rootDir, opts) {
3905
+ const scanStart = Date.now();
3653
3906
  const config = await loadConfig(rootDir);
3654
3907
  const sem = new Semaphore(opts.concurrency);
3655
3908
  const npmCache = new NpmCache(rootDir, sem);
3656
3909
  const scanners = config.scanners;
3910
+ let filesScanned = 0;
3657
3911
  const progress = new ScanProgress(rootDir);
3658
3912
  const steps = [
3659
3913
  { id: "config", label: "Loading configuration" },
@@ -3687,6 +3941,7 @@ async function runScan(rootDir, opts) {
3687
3941
  progress.addDependencies(p.dependencies.length);
3688
3942
  progress.addFrameworks(p.frameworks.length);
3689
3943
  }
3944
+ filesScanned += nodeProjects.length;
3690
3945
  progress.addProjects(nodeProjects.length);
3691
3946
  progress.completeStep("node", `${nodeProjects.length} project${nodeProjects.length !== 1 ? "s" : ""}`, nodeProjects.length);
3692
3947
  progress.startStep("dotnet");
@@ -3695,91 +3950,137 @@ async function runScan(rootDir, opts) {
3695
3950
  progress.addDependencies(p.dependencies.length);
3696
3951
  progress.addFrameworks(p.frameworks.length);
3697
3952
  }
3953
+ filesScanned += dotnetProjects.length;
3698
3954
  progress.addProjects(dotnetProjects.length);
3699
3955
  progress.completeStep("dotnet", `${dotnetProjects.length} project${dotnetProjects.length !== 1 ? "s" : ""}`, dotnetProjects.length);
3700
3956
  const allProjects = [...nodeProjects, ...dotnetProjects];
3957
+ const dsn = opts.dsn || process.env.VIBGRATE_DSN;
3958
+ const parsedDsn = dsn ? parseDsn(dsn) : null;
3959
+ const workspaceId = parsedDsn?.workspaceId;
3960
+ for (const project of allProjects) {
3961
+ project.drift = computeDriftScore([project]);
3962
+ project.projectId = computeProjectId(project.path, project.name, workspaceId);
3963
+ }
3701
3964
  const extended = {};
3702
3965
  if (scanners !== false) {
3966
+ const scannerTasks = [];
3703
3967
  if (scanners?.platformMatrix?.enabled !== false) {
3704
3968
  progress.startStep("platform");
3705
- extended.platformMatrix = await scanPlatformMatrix(rootDir);
3706
- const nativeCount = extended.platformMatrix.nativeModules.length;
3707
- const dockerCount = extended.platformMatrix.dockerBaseImages.length;
3708
- const parts = [];
3709
- if (nativeCount > 0) parts.push(`${nativeCount} native`);
3710
- if (dockerCount > 0) parts.push(`${dockerCount} docker`);
3711
- progress.completeStep("platform", parts.join(", ") || "clean", nativeCount + dockerCount);
3969
+ scannerTasks.push(
3970
+ scanPlatformMatrix(rootDir).then((result) => {
3971
+ extended.platformMatrix = result;
3972
+ const nativeCount = result.nativeModules.length;
3973
+ const dockerCount = result.dockerBaseImages.length;
3974
+ const parts = [];
3975
+ if (nativeCount > 0) parts.push(`${nativeCount} native`);
3976
+ if (dockerCount > 0) parts.push(`${dockerCount} docker`);
3977
+ progress.completeStep("platform", parts.join(", ") || "clean", nativeCount + dockerCount);
3978
+ })
3979
+ );
3712
3980
  }
3713
3981
  if (scanners?.toolingInventory?.enabled !== false) {
3714
3982
  progress.startStep("tooling");
3715
- extended.toolingInventory = scanToolingInventory(allProjects);
3716
- const toolCount = Object.values(extended.toolingInventory).reduce((sum, arr) => sum + arr.length, 0);
3717
- progress.completeStep("tooling", `${toolCount} tool${toolCount !== 1 ? "s" : ""} mapped`, toolCount);
3983
+ scannerTasks.push(
3984
+ Promise.resolve().then(() => {
3985
+ extended.toolingInventory = scanToolingInventory(allProjects);
3986
+ const toolCount = Object.values(extended.toolingInventory).reduce((sum, arr) => sum + arr.length, 0);
3987
+ progress.completeStep("tooling", `${toolCount} tool${toolCount !== 1 ? "s" : ""} mapped`, toolCount);
3988
+ })
3989
+ );
3718
3990
  }
3719
3991
  if (scanners?.serviceDependencies?.enabled !== false) {
3720
3992
  progress.startStep("services");
3721
- extended.serviceDependencies = scanServiceDependencies(allProjects);
3722
- const svcCount = Object.values(extended.serviceDependencies).reduce((sum, arr) => sum + arr.length, 0);
3723
- progress.completeStep("services", `${svcCount} service${svcCount !== 1 ? "s" : ""} detected`, svcCount);
3993
+ scannerTasks.push(
3994
+ Promise.resolve().then(() => {
3995
+ extended.serviceDependencies = scanServiceDependencies(allProjects);
3996
+ const svcCount = Object.values(extended.serviceDependencies).reduce((sum, arr) => sum + arr.length, 0);
3997
+ progress.completeStep("services", `${svcCount} service${svcCount !== 1 ? "s" : ""} detected`, svcCount);
3998
+ })
3999
+ );
3724
4000
  }
3725
4001
  if (scanners?.breakingChangeExposure?.enabled !== false) {
3726
4002
  progress.startStep("breaking");
3727
- extended.breakingChangeExposure = scanBreakingChangeExposure(allProjects);
3728
- const bc = extended.breakingChangeExposure;
3729
- const bcTotal = bc.deprecatedPackages.length + bc.legacyPolyfills.length;
3730
- progress.completeStep(
3731
- "breaking",
3732
- bcTotal > 0 ? `${bc.deprecatedPackages.length} deprecated, ${bc.legacyPolyfills.length} polyfills` : "none found",
3733
- bcTotal
4003
+ scannerTasks.push(
4004
+ Promise.resolve().then(() => {
4005
+ extended.breakingChangeExposure = scanBreakingChangeExposure(allProjects);
4006
+ const bc = extended.breakingChangeExposure;
4007
+ const bcTotal = bc.deprecatedPackages.length + bc.legacyPolyfills.length;
4008
+ progress.completeStep(
4009
+ "breaking",
4010
+ bcTotal > 0 ? `${bc.deprecatedPackages.length} deprecated, ${bc.legacyPolyfills.length} polyfills` : "none found",
4011
+ bcTotal
4012
+ );
4013
+ })
3734
4014
  );
3735
4015
  }
3736
4016
  if (scanners?.securityPosture?.enabled !== false) {
3737
4017
  progress.startStep("security");
3738
- extended.securityPosture = await scanSecurityPosture(rootDir);
3739
- const sec = extended.securityPosture;
3740
- const secDetail = sec.lockfilePresent ? `lockfile \u2714${sec.gitignoreCoversEnv ? " \xB7 .env \u2714" : " \xB7 .env \u2716"}` : "no lockfile";
3741
- progress.completeStep("security", secDetail);
4018
+ scannerTasks.push(
4019
+ scanSecurityPosture(rootDir).then((result) => {
4020
+ extended.securityPosture = result;
4021
+ const secDetail = result.lockfilePresent ? `lockfile \u2714${result.gitignoreCoversEnv ? " \xB7 .env \u2714" : " \xB7 .env \u2716"}` : "no lockfile";
4022
+ progress.completeStep("security", secDetail);
4023
+ })
4024
+ );
3742
4025
  }
3743
4026
  if (scanners?.buildDeploy?.enabled !== false) {
3744
4027
  progress.startStep("build");
3745
- extended.buildDeploy = await scanBuildDeploy(rootDir);
3746
- const bd = extended.buildDeploy;
3747
- const bdParts = [];
3748
- if (bd.ci.length > 0) bdParts.push(bd.ci.join(", "));
3749
- if (bd.docker.dockerfileCount > 0) bdParts.push(`${bd.docker.dockerfileCount} Dockerfile${bd.docker.dockerfileCount !== 1 ? "s" : ""}`);
3750
- progress.completeStep("build", bdParts.join(" \xB7 ") || "none detected");
4028
+ scannerTasks.push(
4029
+ scanBuildDeploy(rootDir).then((result) => {
4030
+ extended.buildDeploy = result;
4031
+ const bdParts = [];
4032
+ if (result.ci.length > 0) bdParts.push(result.ci.join(", "));
4033
+ if (result.docker.dockerfileCount > 0) bdParts.push(`${result.docker.dockerfileCount} Dockerfile${result.docker.dockerfileCount !== 1 ? "s" : ""}`);
4034
+ progress.completeStep("build", bdParts.join(" \xB7 ") || "none detected");
4035
+ })
4036
+ );
3751
4037
  }
3752
4038
  if (scanners?.tsModernity?.enabled !== false) {
3753
4039
  progress.startStep("ts");
3754
- extended.tsModernity = await scanTsModernity(rootDir);
3755
- const ts = extended.tsModernity;
3756
- const tsParts = [];
3757
- if (ts.typescriptVersion) tsParts.push(`v${ts.typescriptVersion}`);
3758
- if (ts.strict === true) tsParts.push("strict");
3759
- if (ts.moduleType) tsParts.push(ts.moduleType.toUpperCase());
3760
- progress.completeStep("ts", tsParts.join(" \xB7 ") || "no tsconfig");
4040
+ scannerTasks.push(
4041
+ scanTsModernity(rootDir).then((result) => {
4042
+ extended.tsModernity = result;
4043
+ const tsParts = [];
4044
+ if (result.typescriptVersion) tsParts.push(`v${result.typescriptVersion}`);
4045
+ if (result.strict === true) tsParts.push("strict");
4046
+ if (result.moduleType) tsParts.push(result.moduleType.toUpperCase());
4047
+ progress.completeStep("ts", tsParts.join(" \xB7 ") || "no tsconfig");
4048
+ })
4049
+ );
3761
4050
  }
3762
4051
  if (scanners?.fileHotspots?.enabled !== false) {
3763
4052
  progress.startStep("hotspots");
3764
- extended.fileHotspots = await scanFileHotspots(rootDir);
3765
- progress.completeStep("hotspots", `${extended.fileHotspots.totalFiles} files`, extended.fileHotspots.totalFiles);
4053
+ scannerTasks.push(
4054
+ scanFileHotspots(rootDir).then((result) => {
4055
+ extended.fileHotspots = result;
4056
+ progress.completeStep("hotspots", `${result.totalFiles} files`, result.totalFiles);
4057
+ })
4058
+ );
3766
4059
  }
3767
4060
  if (scanners?.dependencyGraph?.enabled !== false) {
3768
4061
  progress.startStep("depgraph");
3769
- extended.dependencyGraph = await scanDependencyGraph(rootDir);
3770
- const dg = extended.dependencyGraph;
3771
- const dgDetail = dg.lockfileType ? `${dg.lockfileType} \xB7 ${dg.totalUnique} unique` : "no lockfile";
3772
- progress.completeStep("depgraph", dgDetail, dg.totalUnique);
4062
+ scannerTasks.push(
4063
+ scanDependencyGraph(rootDir).then((result) => {
4064
+ extended.dependencyGraph = result;
4065
+ const dgDetail = result.lockfileType ? `${result.lockfileType} \xB7 ${result.totalUnique} unique` : "no lockfile";
4066
+ progress.completeStep("depgraph", dgDetail, result.totalUnique);
4067
+ })
4068
+ );
3773
4069
  }
3774
4070
  if (scanners?.dependencyRisk?.enabled !== false) {
3775
4071
  progress.startStep("deprisk");
3776
- extended.dependencyRisk = scanDependencyRisk(allProjects);
3777
- const dr = extended.dependencyRisk;
3778
- const drParts = [];
3779
- if (dr.deprecatedPackages.length > 0) drParts.push(`${dr.deprecatedPackages.length} deprecated`);
3780
- if (dr.nativeModulePackages.length > 0) drParts.push(`${dr.nativeModulePackages.length} native`);
3781
- progress.completeStep("deprisk", drParts.join(", ") || "low risk");
4072
+ scannerTasks.push(
4073
+ Promise.resolve().then(() => {
4074
+ extended.dependencyRisk = scanDependencyRisk(allProjects);
4075
+ const dr = extended.dependencyRisk;
4076
+ const drParts = [];
4077
+ if (dr.deprecatedPackages.length > 0) drParts.push(`${dr.deprecatedPackages.length} deprecated`);
4078
+ if (dr.nativeModulePackages.length > 0) drParts.push(`${dr.nativeModulePackages.length} native`);
4079
+ progress.completeStep("deprisk", drParts.join(", ") || "low risk");
4080
+ })
4081
+ );
3782
4082
  }
4083
+ await Promise.all(scannerTasks);
3783
4084
  }
3784
4085
  progress.startStep("drift");
3785
4086
  const drift = computeDriftScore(allProjects);
@@ -3797,39 +4098,69 @@ async function runScan(rootDir, opts) {
3797
4098
  progress.completeStep("findings", findingParts.join(", ") || "none");
3798
4099
  progress.finish();
3799
4100
  if (allProjects.length === 0) {
3800
- console.log(chalk3.yellow("No projects found."));
4101
+ console.log(chalk5.yellow("No projects found."));
4102
+ }
4103
+ if (extended.fileHotspots) filesScanned += extended.fileHotspots.totalFiles;
4104
+ if (extended.securityPosture) filesScanned += 1;
4105
+ if (extended.tsModernity?.typescriptVersion) filesScanned += 1;
4106
+ if (extended.dependencyGraph?.lockfileType) filesScanned += 1;
4107
+ if (extended.buildDeploy) {
4108
+ filesScanned += extended.buildDeploy.docker.dockerfileCount;
4109
+ filesScanned += extended.buildDeploy.ci.length;
3801
4110
  }
4111
+ const durationMs = Date.now() - scanStart;
3802
4112
  const artifact = {
3803
4113
  schemaVersion: "1.0",
3804
4114
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3805
4115
  vibgrateVersion: VERSION,
3806
- rootPath: path12.basename(rootDir),
4116
+ rootPath: path14.basename(rootDir),
3807
4117
  ...vcs.type !== "unknown" ? { vcs } : {},
3808
4118
  projects: allProjects,
3809
4119
  drift,
3810
4120
  findings,
3811
- ...Object.keys(extended).length > 0 ? { extended } : {}
4121
+ ...Object.keys(extended).length > 0 ? { extended } : {},
4122
+ durationMs,
4123
+ filesScanned
3812
4124
  };
3813
4125
  if (opts.baseline) {
3814
- const baselinePath = path12.resolve(opts.baseline);
4126
+ const baselinePath = path14.resolve(opts.baseline);
3815
4127
  if (await pathExists(baselinePath)) {
3816
4128
  try {
3817
4129
  const baseline = await readJsonFile(baselinePath);
3818
4130
  artifact.baseline = baselinePath;
3819
4131
  artifact.delta = artifact.drift.score - baseline.drift.score;
3820
4132
  } catch {
3821
- console.error(chalk3.yellow(`Warning: Could not read baseline file: ${baselinePath}`));
4133
+ console.error(chalk5.yellow(`Warning: Could not read baseline file: ${baselinePath}`));
3822
4134
  }
3823
4135
  }
3824
4136
  }
3825
- const vibgrateDir = path12.join(rootDir, ".vibgrate");
4137
+ const vibgrateDir = path14.join(rootDir, ".vibgrate");
3826
4138
  await ensureDir(vibgrateDir);
3827
- await writeJsonFile(path12.join(vibgrateDir, "scan_result.json"), artifact);
4139
+ await writeJsonFile(path14.join(vibgrateDir, "scan_result.json"), artifact);
4140
+ for (const project of allProjects) {
4141
+ if (project.drift && project.path) {
4142
+ const projectDir = path14.resolve(rootDir, project.path);
4143
+ const projectVibgrateDir = path14.join(projectDir, ".vibgrate");
4144
+ await ensureDir(projectVibgrateDir);
4145
+ await writeJsonFile(path14.join(projectVibgrateDir, "project_score.json"), {
4146
+ projectId: project.projectId,
4147
+ name: project.name,
4148
+ type: project.type,
4149
+ path: project.path,
4150
+ score: project.drift.score,
4151
+ riskLevel: project.drift.riskLevel,
4152
+ components: project.drift.components,
4153
+ measured: project.drift.measured,
4154
+ scannedAt: artifact.timestamp,
4155
+ vibgrateVersion: VERSION
4156
+ });
4157
+ }
4158
+ }
3828
4159
  if (opts.format === "json") {
3829
4160
  const jsonStr = JSON.stringify(artifact, null, 2);
3830
4161
  if (opts.out) {
3831
- await writeTextFile(path12.resolve(opts.out), jsonStr);
3832
- console.log(chalk3.green("\u2714") + ` JSON written to ${opts.out}`);
4162
+ await writeTextFile(path14.resolve(opts.out), jsonStr);
4163
+ console.log(chalk5.green("\u2714") + ` JSON written to ${opts.out}`);
3833
4164
  } else {
3834
4165
  console.log(jsonStr);
3835
4166
  }
@@ -3837,8 +4168,8 @@ async function runScan(rootDir, opts) {
3837
4168
  const sarif = formatSarif(artifact);
3838
4169
  const sarifStr = JSON.stringify(sarif, null, 2);
3839
4170
  if (opts.out) {
3840
- await writeTextFile(path12.resolve(opts.out), sarifStr);
3841
- console.log(chalk3.green("\u2714") + ` SARIF written to ${opts.out}`);
4171
+ await writeTextFile(path14.resolve(opts.out), sarifStr);
4172
+ console.log(chalk5.green("\u2714") + ` SARIF written to ${opts.out}`);
3842
4173
  } else {
3843
4174
  console.log(sarifStr);
3844
4175
  }
@@ -3846,15 +4177,66 @@ async function runScan(rootDir, opts) {
3846
4177
  const text = formatText(artifact);
3847
4178
  console.log(text);
3848
4179
  if (opts.out) {
3849
- await writeTextFile(path12.resolve(opts.out), text);
4180
+ await writeTextFile(path14.resolve(opts.out), text);
3850
4181
  }
3851
4182
  }
3852
4183
  return artifact;
3853
4184
  }
3854
- var scanCommand = new Command("scan").description("Scan a project for upgrade drift").argument("[path]", "Path to scan", ".").option("--out <file>", "Output file path").option("--format <format>", "Output format (text|json|sarif)", "text").option("--fail-on <level>", "Fail on warn or error").option("--baseline <file>", "Compare against baseline").option("--changed-only", "Only scan changed files").option("--concurrency <n>", "Max concurrent npm calls", "8").action(async (targetPath, opts) => {
3855
- const rootDir = path12.resolve(targetPath);
4185
+ async function autoPush(artifact, rootDir, opts) {
4186
+ const dsn = opts.dsn || process.env.VIBGRATE_DSN;
4187
+ if (!dsn) {
4188
+ console.error(chalk5.red("No DSN provided for push."));
4189
+ console.error(chalk5.dim("Set VIBGRATE_DSN environment variable or use --dsn flag."));
4190
+ if (opts.strict) process.exit(1);
4191
+ return;
4192
+ }
4193
+ const parsed = parseDsn(dsn);
4194
+ if (!parsed) {
4195
+ console.error(chalk5.red("Invalid DSN format."));
4196
+ if (opts.strict) process.exit(1);
4197
+ return;
4198
+ }
4199
+ const body = JSON.stringify(artifact);
4200
+ const timestamp = String(Date.now());
4201
+ const hmac = computeHmac(body, parsed.secret);
4202
+ let host = parsed.host;
4203
+ if (opts.region) {
4204
+ try {
4205
+ host = resolveIngestHost(opts.region);
4206
+ } catch (e) {
4207
+ console.error(chalk5.red(e instanceof Error ? e.message : String(e)));
4208
+ if (opts.strict) process.exit(1);
4209
+ return;
4210
+ }
4211
+ }
4212
+ const url = `https://${host}/v1/ingest/scan`;
4213
+ console.log(chalk5.dim(`Uploading to ${host}...`));
4214
+ try {
4215
+ const response = await fetch(url, {
4216
+ method: "POST",
4217
+ headers: {
4218
+ "Content-Type": "application/json",
4219
+ "X-Vibgrate-Timestamp": timestamp,
4220
+ "Authorization": `VibgrateDSN ${parsed.keyId}:${hmac}`
4221
+ },
4222
+ body
4223
+ });
4224
+ if (!response.ok) {
4225
+ const text = await response.text();
4226
+ throw new Error(`HTTP ${response.status}: ${text}`);
4227
+ }
4228
+ const result = await response.json();
4229
+ console.log(chalk5.green("\u2714") + ` Uploaded successfully (${result.ingestId ?? "ok"})`);
4230
+ } catch (e) {
4231
+ const msg = e instanceof Error ? e.message : String(e);
4232
+ console.error(chalk5.red(`Upload failed: ${msg}`));
4233
+ if (opts.strict) process.exit(1);
4234
+ }
4235
+ }
4236
+ var scanCommand = new Command3("scan").description("Scan a project for upgrade drift").argument("[path]", "Path to scan", ".").option("--out <file>", "Output file path").option("--format <format>", "Output format (text|json|sarif)", "text").option("--fail-on <level>", "Fail on warn or error").option("--baseline <file>", "Compare against baseline").option("--changed-only", "Only scan changed files").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").action(async (targetPath, opts) => {
4237
+ const rootDir = path14.resolve(targetPath);
3856
4238
  if (!await pathExists(rootDir)) {
3857
- console.error(chalk3.red(`Path does not exist: ${rootDir}`));
4239
+ console.error(chalk5.red(`Path does not exist: ${rootDir}`));
3858
4240
  process.exit(1);
3859
4241
  }
3860
4242
  const scanOpts = {
@@ -3863,38 +4245,45 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
3863
4245
  failOn: opts.failOn,
3864
4246
  baseline: opts.baseline,
3865
4247
  changedOnly: opts.changedOnly,
3866
- concurrency: parseInt(opts.concurrency, 10) || 8
4248
+ concurrency: parseInt(opts.concurrency, 10) || 8,
4249
+ push: opts.push,
4250
+ dsn: opts.dsn,
4251
+ region: opts.region,
4252
+ strict: opts.strict
3867
4253
  };
3868
4254
  const artifact = await runScan(rootDir, scanOpts);
3869
4255
  if (opts.failOn) {
3870
4256
  const hasErrors = artifact.findings.some((f) => f.level === "error");
3871
4257
  const hasWarnings = artifact.findings.some((f) => f.level === "warning");
3872
4258
  if (opts.failOn === "error" && hasErrors) {
3873
- console.error(chalk3.red(`
4259
+ console.error(chalk5.red(`
3874
4260
  Failing: ${artifact.findings.filter((f) => f.level === "error").length} error finding(s) detected.`));
3875
4261
  process.exit(2);
3876
4262
  }
3877
4263
  if (opts.failOn === "warn" && (hasErrors || hasWarnings)) {
3878
- console.error(chalk3.red(`
4264
+ console.error(chalk5.red(`
3879
4265
  Failing: findings detected at warn level or above.`));
3880
4266
  process.exit(2);
3881
4267
  }
3882
4268
  }
4269
+ if (opts.push) {
4270
+ await autoPush(artifact, rootDir, scanOpts);
4271
+ }
3883
4272
  });
3884
4273
 
3885
4274
  export {
3886
4275
  readJsonFile,
3887
- readTextFile,
3888
4276
  pathExists,
3889
4277
  ensureDir,
3890
4278
  writeJsonFile,
3891
- writeTextFile,
3892
4279
  writeDefaultConfig,
3893
4280
  computeDriftScore,
3894
4281
  generateFindings,
4282
+ VERSION,
3895
4283
  formatText,
3896
4284
  formatSarif,
3897
- VERSION,
4285
+ dsnCommand,
4286
+ pushCommand,
3898
4287
  runScan,
3899
4288
  scanCommand
3900
4289
  };