@vibgrate/cli 2026.4.10 → 2026.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,34 +1,28 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- baselineCommand
4
- } from "./chunk-2VJCLUTR.js";
5
- import {
6
- VERSION,
7
- computeHmac,
8
- dsnCommand,
9
- formatMarkdown,
10
- formatText,
11
- parseDsn,
12
- pushCommand,
13
- scanCommand,
14
- writeDefaultConfig
15
- } from "./chunk-XCIPC2J7.js";
16
2
  import {
17
3
  Semaphore,
4
+ baselineCommand,
18
5
  ensureDir,
19
6
  pathExists,
20
7
  readJsonFile,
21
8
  writeTextFile
22
- } from "./chunk-JQHUH6A3.js";
9
+ } from "./chunk-42GW43JE.js";
10
+ import {
11
+ require_semver
12
+ } from "./chunk-74ZJFYEM.js";
13
+ import {
14
+ __toESM
15
+ } from "./chunk-JSBRDJBE.js";
23
16
 
24
17
  // src/cli.ts
25
- import { Command as Command8 } from "commander";
26
- import chalk8 from "chalk";
18
+ import { Command as Command11 } from "commander";
19
+ import chalk12 from "chalk";
27
20
 
28
21
  // src/commands/init.ts
29
22
  import * as path from "path";
30
23
  import { Command } from "commander";
31
24
  import chalk from "chalk";
25
+ import { writeDefaultConfig } from "@vibgrate/core";
32
26
  var initCommand = new Command("init").description("Initialize vibgrate in a project").argument("[path]", "Path to initialize", ".").option("--baseline", "Create initial baseline after init").option("--yes", "Skip confirmation prompts").action(async (targetPath, opts) => {
33
27
  const rootDir = path.resolve(targetPath);
34
28
  const vibgrateDir = path.join(rootDir, ".vibgrate");
@@ -42,7 +36,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
42
36
  console.log(chalk.green("\u2714") + ` Created ${chalk.bold("vibgrate.config.ts")}`);
43
37
  }
44
38
  if (opts.baseline) {
45
- const { runBaseline } = await import("./baseline-QZZXBT74.js");
39
+ const { runBaseline } = await import("./baseline-FPO7HS7Y.js");
46
40
  await runBaseline(rootDir);
47
41
  }
48
42
  console.log("");
@@ -52,15 +46,1044 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
52
46
  console.log("");
53
47
  });
54
48
 
55
- // src/commands/report.ts
49
+ // src/commands/scan.ts
50
+ import * as path3 from "path";
51
+ import { Command as Command3 } from "commander";
52
+ import chalk3 from "chalk";
53
+ import {
54
+ runScan,
55
+ pathExists as pathExists2,
56
+ parseDsn,
57
+ prepareCompressedUpload,
58
+ fetchScanPreflight,
59
+ computeRepoFingerprint,
60
+ detectVcs,
61
+ resolveRepositoryName
62
+ } from "@vibgrate/core";
63
+
64
+ // src/version.ts
65
+ import { createRequire } from "module";
66
+ var require2 = createRequire(import.meta.url);
67
+ var pkg = require2("../package.json");
68
+ var VERSION = pkg.version;
69
+
70
+ // src/commands/dsn.ts
71
+ import * as crypto from "crypto";
56
72
  import * as path2 from "path";
57
73
  import { Command as Command2 } from "commander";
58
74
  import chalk2 from "chalk";
59
- var reportCommand = new Command2("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) => {
60
- const artifactPath = path2.resolve(opts.in);
75
+ var REGION_HOSTS = {
76
+ us: "us.ingest.vibgrate.com",
77
+ eu: "eu.ingest.vibgrate.com"
78
+ };
79
+ function resolveIngestHost(region, ingest) {
80
+ if (ingest) {
81
+ try {
82
+ return new URL(ingest).host;
83
+ } catch {
84
+ throw new Error(`Invalid ingest URL: ${ingest}`);
85
+ }
86
+ }
87
+ const r = (region ?? "us").toLowerCase();
88
+ const host = REGION_HOSTS[r];
89
+ if (!host) {
90
+ throw new Error(`Unknown region "${r}". Supported: ${Object.keys(REGION_HOSTS).join(", ")}`);
91
+ }
92
+ return host;
93
+ }
94
+ async function provisionDsn(keyId, secret, workspaceId, ingestHost) {
95
+ const url = `https://${ingestHost}/v1/provision`;
96
+ try {
97
+ const response = await fetch(url, {
98
+ method: "POST",
99
+ headers: {
100
+ "Content-Type": "application/json",
101
+ "Connection": "close"
102
+ // Prevent keep-alive delays on exit
103
+ },
104
+ body: JSON.stringify({ keyId, secret, workspaceId })
105
+ });
106
+ if (!response.ok) {
107
+ const result = await response.json();
108
+ return { success: false, error: result.error || `HTTP ${response.status}` };
109
+ }
110
+ return { success: true };
111
+ } catch (e) {
112
+ return { success: false, error: e instanceof Error ? e.message : String(e) };
113
+ }
114
+ }
115
+ var dsnCommand = new Command2("dsn").description("Manage DSN tokens");
116
+ 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 (use "new" to auto-generate)').option("--write <path>", "Write DSN to file").action(async (opts) => {
117
+ const keyId = crypto.randomBytes(12).toString("hex");
118
+ const secret = crypto.randomBytes(32).toString("hex");
119
+ let ingestHost;
120
+ try {
121
+ ingestHost = resolveIngestHost(opts.region, opts.ingest);
122
+ } catch (e) {
123
+ console.error(chalk2.red(e instanceof Error ? e.message : String(e)));
124
+ process.exit(1);
125
+ }
126
+ let workspaceId = opts.workspace;
127
+ const isNewWorkspace = opts.workspace.toLowerCase() === "new";
128
+ if (isNewWorkspace) {
129
+ workspaceId = crypto.randomBytes(8).toString("hex");
130
+ console.log(chalk2.dim(`Provisioning new workspace ${workspaceId}...`));
131
+ }
132
+ if (isNewWorkspace) {
133
+ const result = await provisionDsn(keyId, secret, workspaceId, ingestHost);
134
+ if (!result.success) {
135
+ console.error(chalk2.red(`Failed to provision DSN: ${result.error}`));
136
+ process.exit(1);
137
+ }
138
+ }
139
+ const dsn = `vibgrate+https://${keyId}:${secret}@${ingestHost}/${workspaceId}`;
140
+ console.log(chalk2.green("\u2714") + " DSN created");
141
+ console.log("");
142
+ console.log(chalk2.bold("DSN:"));
143
+ console.log(` ${dsn}`);
144
+ console.log("");
145
+ console.log(chalk2.bold("Key ID:"));
146
+ console.log(` ${keyId}`);
147
+ if (isNewWorkspace) {
148
+ console.log("");
149
+ console.log(chalk2.bold("Workspace ID:"));
150
+ console.log(` ${workspaceId}`);
151
+ }
152
+ console.log("");
153
+ console.log(chalk2.dim("Set this as VIBGRATE_DSN in your CI environment."));
154
+ if (!isNewWorkspace) {
155
+ console.log(chalk2.dim("The secret must be registered on your Vibgrate ingest API."));
156
+ }
157
+ if (opts.write) {
158
+ const writePath = path2.resolve(opts.write);
159
+ await writeTextFile(writePath, dsn + "\n");
160
+ console.log("");
161
+ console.log(chalk2.green("\u2714") + ` DSN written to ${opts.write}`);
162
+ console.log(chalk2.yellow("\u26A0") + " Add this file to .gitignore!");
163
+ }
164
+ });
165
+
166
+ // src/utils/ingest-id-output.ts
167
+ function emitIngestIdLine(ingestId, options) {
168
+ console.log(`VIBGRATE_INGEST_ID=${ingestId}`);
169
+ if (options?.unchanged) {
170
+ console.log("VIBGRATE_INGEST_UNCHANGED=1");
171
+ }
172
+ }
173
+
174
+ // src/commands/scan.ts
175
+ async function autoPush(artifact, rootDir, opts) {
176
+ const dsn = opts.dsn || process.env.VIBGRATE_DSN;
177
+ if (!dsn) {
178
+ console.error(chalk3.red("No DSN provided for push."));
179
+ console.error(chalk3.dim("Set VIBGRATE_DSN environment variable or use --dsn flag."));
180
+ if (opts.strict) process.exit(1);
181
+ return;
182
+ }
183
+ const parsed = parseDsn(dsn);
184
+ if (!parsed) {
185
+ console.error(chalk3.red("Invalid DSN format."));
186
+ if (opts.strict) process.exit(1);
187
+ return;
188
+ }
189
+ const { body, contentEncoding } = await prepareCompressedUpload(artifact);
190
+ const timestamp = String(Date.now());
191
+ let host = parsed.host;
192
+ if (opts.region) {
193
+ try {
194
+ host = resolveIngestHost(opts.region);
195
+ } catch (e) {
196
+ console.error(chalk3.red(e instanceof Error ? e.message : String(e)));
197
+ if (opts.strict) process.exit(1);
198
+ return;
199
+ }
200
+ }
201
+ const url = `${parsed.scheme}://${host}/v1/ingest/scan`;
202
+ const originalSize = JSON.stringify(artifact).length;
203
+ const compressedSize = body.length;
204
+ const ratio = ((1 - compressedSize / originalSize) * 100).toFixed(0);
205
+ console.log(chalk3.dim(`Uploading to ${host}... (${(compressedSize / 1024).toFixed(0)} KB, ${ratio}% smaller)`));
206
+ try {
207
+ const response = await fetch(url, {
208
+ method: "POST",
209
+ headers: {
210
+ "Content-Type": "application/json",
211
+ "Content-Encoding": contentEncoding,
212
+ "X-Vibgrate-Timestamp": timestamp,
213
+ "Authorization": `VibgrateDSN ${parsed.keyId}:${parsed.secret}`,
214
+ "Connection": "close"
215
+ // Prevent keep-alive delays on exit
216
+ },
217
+ body
218
+ });
219
+ if (!response.ok) {
220
+ const text = await response.text();
221
+ throw new Error(`HTTP ${response.status}: ${text}`);
222
+ }
223
+ const result = await response.json();
224
+ if (result.unchanged) {
225
+ console.log(
226
+ chalk3.green("\u2714") + ` Repository unchanged since ${result.lastScannedAt ?? "last scan"} \u2014 skipped upload (no credit used).`
227
+ );
228
+ if (result.previousIngestId) {
229
+ emitIngestIdLine(result.previousIngestId, { unchanged: true });
230
+ const dashUrl = `https://dash.vibgrate.com/${parsed.workspaceId}/scan/${result.previousIngestId}`;
231
+ console.log(chalk3.dim(` Previous report: ${dashUrl}`));
232
+ } else if (opts.strict) {
233
+ console.error(chalk3.red("Repository unchanged but no previous ingest id returned."));
234
+ process.exit(1);
235
+ }
236
+ return;
237
+ }
238
+ console.log(chalk3.green("\u2714") + ` Scan queued for processing (${result.ingestId ?? "ok"})`);
239
+ if (result.ingestId) {
240
+ emitIngestIdLine(result.ingestId);
241
+ const dashUrl = `https://dash.vibgrate.com/${parsed.workspaceId}/scan/${result.ingestId}`;
242
+ const CLEAR_LINE = process.platform === "win32" ? "\x1B[0G\x1B[2K" : "";
243
+ console.log("");
244
+ console.log(CLEAR_LINE + chalk3.dim(" Processing continues in the background. Results available shortly."));
245
+ console.log("");
246
+ 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"));
247
+ console.log(CLEAR_LINE + chalk3.bold(" \u{1F4CA} View Scan Report"));
248
+ console.log(CLEAR_LINE + " " + chalk3.underline.cyan(dashUrl));
249
+ 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"));
250
+ console.log("");
251
+ console.log("");
252
+ }
253
+ } catch (e) {
254
+ const msg = e instanceof Error ? e.message : String(e);
255
+ console.error(chalk3.red(`Upload failed: ${msg}`));
256
+ if (opts.strict) process.exit(1);
257
+ }
258
+ }
259
+ function parseNonNegativeNumber(value, label) {
260
+ if (value === void 0) return void 0;
261
+ const parsed = Number(value);
262
+ if (!Number.isFinite(parsed) || parsed < 0) {
263
+ throw new Error(`${label} must be a non-negative number.`);
264
+ }
265
+ return parsed;
266
+ }
267
+ 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|md)", "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").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) => {
268
+ const rootDir = path3.resolve(targetPath);
269
+ if (!await pathExists2(rootDir)) {
270
+ console.error(chalk3.red(`Path does not exist: ${rootDir}`));
271
+ process.exit(1);
272
+ }
273
+ const hasDsn = !!(opts.dsn || process.env.VIBGRATE_DSN);
274
+ const willPush = !opts.offline && (opts.push || hasDsn);
275
+ if (willPush && hasDsn) {
276
+ const dsn = opts.dsn || process.env.VIBGRATE_DSN;
277
+ const parsed = parseDsn(dsn);
278
+ if (parsed) {
279
+ const ingestHost = opts.region ? resolveIngestHost(opts.region) : parsed.host;
280
+ const vcs = await detectVcs(rootDir);
281
+ const fingerprint = await computeRepoFingerprint(rootDir, vcs);
282
+ const repositoryName = await resolveRepositoryName(rootDir);
283
+ try {
284
+ const preflight = await fetchScanPreflight(parsed, ingestHost, {
285
+ repositoryName,
286
+ vcsSha: fingerprint.vcsSha
287
+ });
288
+ if (preflight.status === "error" || !preflight.scans.allowed) {
289
+ console.error(chalk3.red(preflight.error ?? "Scan ingestion not allowed for this workspace."));
290
+ console.error(
291
+ chalk3.dim(
292
+ `Credits: ${preflight.scans.used}/${preflight.scans.limit} (${preflight.plan.label} plan)`
293
+ )
294
+ );
295
+ if (opts.strict) process.exit(1);
296
+ process.exit(1);
297
+ }
298
+ console.log(
299
+ chalk3.dim(
300
+ `Plan: ${preflight.plan.label} \u2014 scan credits ${preflight.scans.used}/${preflight.scans.limit} this month`
301
+ )
302
+ );
303
+ if (preflight.repository?.unchanged) {
304
+ console.log(
305
+ chalk3.green("\u2714") + ` Repository unchanged at ${preflight.repository.lastVcsSha?.slice(0, 7) ?? "same revision"} \u2014 skipping scan.`
306
+ );
307
+ if (preflight.repository.lastIngestId) {
308
+ emitIngestIdLine(preflight.repository.lastIngestId, { unchanged: true });
309
+ const dashUrl = `https://dash.vibgrate.com/${parsed.workspaceId}/scan/${preflight.repository.lastIngestId}`;
310
+ console.log(chalk3.dim(` Latest report: ${dashUrl}`));
311
+ } else if (opts.strict) {
312
+ console.error(chalk3.red("Repository unchanged but no previous ingest id available."));
313
+ process.exit(1);
314
+ }
315
+ return;
316
+ }
317
+ } catch (e) {
318
+ const msg = e instanceof Error ? e.message : String(e);
319
+ console.error(chalk3.yellow(`Preflight check failed: ${msg}`));
320
+ if (opts.strict) process.exit(1);
321
+ }
322
+ }
323
+ }
324
+ const scanOpts = {
325
+ vibgrateVersion: VERSION,
326
+ out: opts.out,
327
+ format: opts.format || "text",
328
+ failOn: opts.failOn,
329
+ baseline: opts.baseline,
330
+ changedOnly: opts.changedOnly,
331
+ concurrency: parseInt(opts.concurrency, 10) || 8,
332
+ push: opts.push,
333
+ dsn: opts.dsn,
334
+ region: opts.region,
335
+ strict: opts.strict,
336
+ uiPurpose: opts.uiPurpose,
337
+ noLocalArtifacts: opts.noLocalArtifacts,
338
+ maxPrivacy: opts.maxPrivacy,
339
+ offline: opts.offline,
340
+ packageManifest: opts.packageManifest,
341
+ driftBudget: parseNonNegativeNumber(opts.driftBudget, "--drift-budget"),
342
+ driftWorseningPercent: parseNonNegativeNumber(opts.driftWorsening, "--drift-worsening"),
343
+ projectScanTimeout: opts.projectScanTimeout ? parseInt(opts.projectScanTimeout, 10) || void 0 : void 0
344
+ };
345
+ const artifact = await runScan(rootDir, scanOpts);
346
+ if (opts.failOn) {
347
+ const hasErrors = artifact.findings.some((f) => f.level === "error");
348
+ const hasWarnings = artifact.findings.some((f) => f.level === "warning");
349
+ if (opts.failOn === "error" && hasErrors) {
350
+ console.error(chalk3.red(`
351
+ Failing: ${artifact.findings.filter((f) => f.level === "error").length} error finding(s) detected.`));
352
+ process.exit(2);
353
+ }
354
+ if (opts.failOn === "warn" && (hasErrors || hasWarnings)) {
355
+ console.error(chalk3.red(`
356
+ Failing: findings detected at warn level or above.`));
357
+ process.exit(2);
358
+ }
359
+ }
360
+ if (scanOpts.driftBudget !== void 0 && artifact.drift.score > scanOpts.driftBudget) {
361
+ console.error(chalk3.red(`
362
+ Failing fitness function: drift score ${artifact.drift.score}/100 exceeds budget ${scanOpts.driftBudget}.`));
363
+ process.exit(2);
364
+ }
365
+ if (scanOpts.driftWorseningPercent !== void 0) {
366
+ if (artifact.delta === void 0) {
367
+ console.error(chalk3.red("\nFailing fitness function: --drift-worsening requires --baseline to compare against previous drift."));
368
+ process.exit(2);
369
+ }
370
+ if (artifact.delta > 0) {
371
+ const baselineScore = artifact.drift.score - artifact.delta;
372
+ const denominator = Math.max(Math.abs(baselineScore), 1e-4);
373
+ const worseningPercent = artifact.delta / denominator * 100;
374
+ if (worseningPercent > scanOpts.driftWorseningPercent) {
375
+ console.error(chalk3.red(`
376
+ Failing fitness function: drift worsened by ${worseningPercent.toFixed(2)}% (threshold ${scanOpts.driftWorseningPercent}%).`));
377
+ process.exit(2);
378
+ }
379
+ }
380
+ }
381
+ if (willPush) {
382
+ await autoPush(artifact, rootDir, scanOpts);
383
+ }
384
+ });
385
+
386
+ // src/commands/report.ts
387
+ import * as path4 from "path";
388
+ import { Command as Command4 } from "commander";
389
+ import chalk5 from "chalk";
390
+
391
+ // src/formatters/text.ts
392
+ import chalk4 from "chalk";
393
+ function formatText(artifact) {
394
+ const lines = [];
395
+ lines.push("");
396
+ lines.push(chalk4.cyan(" \u256D\u2500\u2500\u2500\u256E") + chalk4.greenBright("\u279C"));
397
+ lines.push(chalk4.cyan(" \u256D\u2524") + chalk4.greenBright("\u25C9 \u25C9") + chalk4.cyan("\u251C\u256E") + " " + chalk4.bold.white("V I B G R A T E"));
398
+ lines.push(chalk4.cyan(" \u2570\u2524") + chalk4.dim("\u2500\u2500\u2500") + chalk4.cyan("\u251C\u256F") + " " + chalk4.dim(`Drift Intelligence Engine v${VERSION}`));
399
+ lines.push(chalk4.cyan(" \u2570\u2500\u2500\u2500\u256F"));
400
+ lines.push("");
401
+ 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"));
402
+ lines.push(chalk4.bold.cyan("\u2551 Vibgrate Drift Report \u2551"));
403
+ 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"));
404
+ lines.push("");
405
+ for (const project of artifact.projects) {
406
+ lines.push(chalk4.bold(` \u2500\u2500 ${project.name} `) + chalk4.dim(`(${project.type}) ${project.path}`));
407
+ if (project.runtime) {
408
+ const behindStr = project.runtimeMajorsBehind !== void 0 && project.runtimeMajorsBehind > 0 ? chalk4.yellow(` (${project.runtimeMajorsBehind} major${project.runtimeMajorsBehind > 1 ? "s" : ""} behind)`) : chalk4.green(" (current)");
409
+ lines.push(` Runtime: ${project.runtime}${behindStr}`);
410
+ }
411
+ if (project.targetFramework) {
412
+ lines.push(` Target: ${project.targetFramework}`);
413
+ }
414
+ if (project.frameworks.length > 0) {
415
+ lines.push(" Frameworks:");
416
+ for (const fw of project.frameworks) {
417
+ const lag = fw.majorsBehind !== null ? fw.majorsBehind === 0 ? chalk4.green("current") : chalk4.yellow(`${fw.majorsBehind} behind`) : chalk4.dim("unknown");
418
+ lines.push(` ${fw.name}: ${fw.currentVersion ?? "?"} \u2192 ${fw.latestVersion ?? "?"} (${lag})`);
419
+ }
420
+ }
421
+ const b = project.dependencyAgeBuckets;
422
+ const total = b.current + b.oneBehind + b.twoPlusBehind + b.unknown;
423
+ if (total > 0) {
424
+ lines.push(" Dependencies:");
425
+ lines.push(` ${chalk4.green(`${b.current} current`)} ${chalk4.yellow(`${b.oneBehind} 1-behind`)} ${chalk4.red(`${b.twoPlusBehind} 2+ behind`)} ${chalk4.dim(`${b.unknown} unknown`)}`);
426
+ }
427
+ lines.push("");
428
+ }
429
+ if (artifact.delta !== void 0) {
430
+ const deltaStr = artifact.delta > 0 ? chalk4.green(`+${artifact.delta}`) : artifact.delta < 0 ? chalk4.red(`${artifact.delta}`) : chalk4.dim("0");
431
+ lines.push(chalk4.bold(" Drift Delta: ") + deltaStr + " (vs baseline)");
432
+ lines.push("");
433
+ }
434
+ if (artifact.extended) {
435
+ lines.push(...formatExtended(artifact.extended));
436
+ }
437
+ if (artifact.findings.length > 0) {
438
+ const errors = artifact.findings.filter((f) => f.level === "error");
439
+ const warnings = artifact.findings.filter((f) => f.level === "warning");
440
+ const notes = artifact.findings.filter((f) => f.level === "note");
441
+ const summary = [
442
+ errors.length > 0 ? chalk4.red(`${errors.length} error${errors.length !== 1 ? "s" : ""}`) : "",
443
+ warnings.length > 0 ? chalk4.yellow(`${warnings.length} warning${warnings.length !== 1 ? "s" : ""}`) : "",
444
+ notes.length > 0 ? chalk4.blue(`${notes.length} note${notes.length !== 1 ? "s" : ""}`) : ""
445
+ ].filter(Boolean).join(chalk4.dim(", "));
446
+ lines.push(chalk4.bold.underline(` Findings`) + chalk4.dim(` (${summary})`));
447
+ for (const f of artifact.findings) {
448
+ const icon = f.level === "error" ? chalk4.red("\u2716") : f.level === "warning" ? chalk4.yellow("\u26A0") : chalk4.blue("\u2139");
449
+ lines.push(` ${icon} ${f.message}`);
450
+ lines.push(chalk4.dim(` ${f.ruleId} in ${f.location}`));
451
+ }
452
+ lines.push("");
453
+ }
454
+ const actions = generatePriorityActions(artifact);
455
+ if (actions.length > 0) {
456
+ 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"));
457
+ lines.push(chalk4.bold.cyan("\u2551 Top Priority Actions \u2551"));
458
+ 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"));
459
+ lines.push("");
460
+ for (let i = 0; i < actions.length; i++) {
461
+ const a = actions[i];
462
+ const num = chalk4.bold.cyan(` ${i + 1}.`);
463
+ lines.push(`${num} ${chalk4.bold(a.title)}`);
464
+ lines.push(chalk4.dim(` ${a.explanation}`));
465
+ if (a.impact) lines.push(` Impact: ${chalk4.green(a.impact)}`);
466
+ lines.push("");
467
+ }
468
+ }
469
+ if (artifact.extended?.architecture) {
470
+ lines.push(...formatArchitectureDiagram(artifact.extended.architecture));
471
+ }
472
+ if (artifact.solutions && artifact.solutions.length > 0) {
473
+ 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"));
474
+ lines.push(chalk4.bold.cyan("\u2551 Solution Drift Summary \u2551"));
475
+ 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"));
476
+ lines.push("");
477
+ for (const solution of artifact.solutions) {
478
+ const solScore = solution.drift?.score;
479
+ const color = typeof solScore === "number" ? solScore >= 70 ? chalk4.green : solScore >= 40 ? chalk4.yellow : chalk4.red : chalk4.dim;
480
+ lines.push(` \u2022 ${solution.name} (${solution.projectPaths.length} projects) \u2014 ${typeof solScore === "number" ? color(`${solScore}/100`) : chalk4.dim("n/a")}`);
481
+ }
482
+ lines.push("");
483
+ }
484
+ const scoreColor = artifact.drift.score >= 70 ? chalk4.green : artifact.drift.score >= 40 ? chalk4.yellow : chalk4.red;
485
+ 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"));
486
+ lines.push(chalk4.bold.cyan("\u2551 Drift Score Summary \u2551"));
487
+ 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"));
488
+ lines.push("");
489
+ lines.push(chalk4.bold(" Drift Score: ") + scoreColor.bold(`${artifact.drift.score}/100`));
490
+ lines.push(chalk4.bold(" Risk Level: ") + riskBadge(artifact.drift.riskLevel));
491
+ lines.push(chalk4.bold(" Projects: ") + `${artifact.projects.length}`);
492
+ if (artifact.vcs) {
493
+ const vcsParts = [artifact.vcs.type];
494
+ if (artifact.vcs.branch) vcsParts.push(artifact.vcs.branch);
495
+ if (artifact.vcs.shortSha) vcsParts.push(chalk4.dim(artifact.vcs.shortSha));
496
+ lines.push(chalk4.bold(" VCS: ") + vcsParts.join(" "));
497
+ }
498
+ lines.push("");
499
+ const m = new Set(artifact.drift.measured ?? ["runtime", "framework", "dependency", "eol"]);
500
+ lines.push(" " + chalk4.bold.underline("Score Breakdown"));
501
+ lines.push(` Runtime: ${m.has("runtime") ? scoreBar(artifact.drift.components.runtimeScore) : chalk4.dim("n/a")}`);
502
+ lines.push(` Frameworks: ${m.has("framework") ? scoreBar(artifact.drift.components.frameworkScore) : chalk4.dim("n/a")}`);
503
+ lines.push(` Dependencies: ${m.has("dependency") ? scoreBar(artifact.drift.components.dependencyScore) : chalk4.dim("n/a")}`);
504
+ lines.push(` EOL Risk: ${m.has("eol") ? scoreBar(artifact.drift.components.eolScore) : chalk4.dim("n/a")}`);
505
+ lines.push("");
506
+ const scannedParts = [`Scanned at ${artifact.timestamp}`];
507
+ if (artifact.durationMs !== void 0) {
508
+ const secs = (artifact.durationMs / 1e3).toFixed(1);
509
+ scannedParts.push(`${secs}s`);
510
+ }
511
+ if (artifact.filesScanned !== void 0) {
512
+ scannedParts.push(`${artifact.filesScanned} file${artifact.filesScanned !== 1 ? "s" : ""} scanned`);
513
+ }
514
+ if (artifact.treeSummary) {
515
+ scannedParts.push(`${artifact.treeSummary.totalFiles.toLocaleString()} workspace files`);
516
+ scannedParts.push(`${artifact.treeSummary.totalDirs.toLocaleString()} dirs`);
517
+ }
518
+ lines.push(chalk4.dim(` ${scannedParts.join(" \xB7 ")}`));
519
+ lines.push("");
520
+ return lines.join("\n");
521
+ }
522
+ function riskBadge(level) {
523
+ switch (level) {
524
+ case "low":
525
+ return chalk4.bgGreen.black(" LOW ");
526
+ case "moderate":
527
+ return chalk4.bgYellow.black(" MODERATE ");
528
+ case "high":
529
+ return chalk4.bgRed.white(" HIGH ");
530
+ default:
531
+ return level;
532
+ }
533
+ }
534
+ function scoreBar(score) {
535
+ const width = 20;
536
+ const filled = Math.round(score / 100 * width);
537
+ const empty = width - filled;
538
+ const color = score >= 70 ? chalk4.green : score >= 40 ? chalk4.yellow : chalk4.red;
539
+ return color("\u2588".repeat(filled)) + chalk4.dim("\u2591".repeat(empty)) + ` ${Math.round(score)}`;
540
+ }
541
+ var CATEGORY_LABELS = {
542
+ frontend: "Frontend",
543
+ metaFrameworks: "Meta-frameworks",
544
+ bundlers: "Bundlers",
545
+ css: "CSS / UI",
546
+ backend: "Backend",
547
+ orm: "ORM / Database",
548
+ testing: "Testing",
549
+ lintFormat: "Lint & Format",
550
+ apiMessaging: "API & Messaging",
551
+ observability: "Observability",
552
+ payment: "Payment",
553
+ auth: "Auth",
554
+ email: "Email",
555
+ cloud: "Cloud",
556
+ databases: "Databases",
557
+ messaging: "Messaging",
558
+ crm: "CRM & Comms",
559
+ storage: "Storage",
560
+ search: "Search & AI"
561
+ };
562
+ function formatExtended(ext) {
563
+ const lines = [];
564
+ if (ext.toolingInventory) {
565
+ const inv = ext.toolingInventory;
566
+ const categories = Object.entries(inv).filter(([, items]) => items.length > 0);
567
+ if (categories.length > 0) {
568
+ lines.push(chalk4.bold.underline(" Tech Stack"));
569
+ for (const [cat, items] of categories) {
570
+ const label = CATEGORY_LABELS[cat] ?? cat;
571
+ const names = items.map((i) => chalk4.white(i.name)).join(chalk4.dim(", "));
572
+ lines.push(` ${chalk4.cyan(label)}: ${names}`);
573
+ }
574
+ lines.push("");
575
+ }
576
+ }
577
+ if (ext.serviceDependencies) {
578
+ const svc = ext.serviceDependencies;
579
+ const categories = Object.entries(svc).filter(([, items]) => items.length > 0);
580
+ if (categories.length > 0) {
581
+ lines.push(chalk4.bold.underline(" Services & Integrations"));
582
+ for (const [cat, items] of categories) {
583
+ const label = CATEGORY_LABELS[cat] ?? cat;
584
+ const names = items.map((i) => {
585
+ const ver = i.version ? chalk4.dim(` ${i.version}`) : "";
586
+ return chalk4.white(i.name) + ver;
587
+ }).join(chalk4.dim(", "));
588
+ lines.push(` ${chalk4.cyan(label)}: ${names}`);
589
+ }
590
+ lines.push("");
591
+ }
592
+ }
593
+ if (ext.breakingChangeExposure) {
594
+ const bc = ext.breakingChangeExposure;
595
+ if (bc.deprecatedPackages.length > 0 || bc.legacyPolyfills.length > 0) {
596
+ lines.push(chalk4.bold.underline(" Breaking Change Exposure"));
597
+ const exposureColor = bc.exposureScore >= 40 ? chalk4.red : bc.exposureScore >= 20 ? chalk4.yellow : chalk4.green;
598
+ lines.push(` Exposure Score: ${exposureColor.bold(`${bc.exposureScore}/100`)}`);
599
+ if (bc.deprecatedPackages.length > 0) {
600
+ lines.push(` ${chalk4.red("Deprecated")}: ${bc.deprecatedPackages.map((p) => chalk4.dim(p)).join(", ")}`);
601
+ }
602
+ if (bc.legacyPolyfills.length > 0) {
603
+ lines.push(` ${chalk4.yellow("Polyfills")}: ${bc.legacyPolyfills.map((p) => chalk4.dim(p)).join(", ")}`);
604
+ }
605
+ if (bc.peerConflictsDetected) {
606
+ lines.push(` ${chalk4.red("\u26A0")} Peer dependency conflicts detected`);
607
+ }
608
+ lines.push(` Recommendation: ${chalk4.bold(bc.overallRecommendation)}`);
609
+ const projectsWithPlans = bc.projectIntelligence.filter((p) => p.packages.length > 0).slice(0, 3);
610
+ if (projectsWithPlans.length > 0) {
611
+ lines.push(" Major Upgrade Intelligence:");
612
+ for (const p of projectsWithPlans) {
613
+ lines.push(` - ${p.project} (${p.recommendation})`);
614
+ for (const pkg2 of p.packages.slice(0, 2)) {
615
+ lines.push(` \xB7 ${pkg2.package} ${pkg2.currentVersion ?? "?"} \u2192 ${pkg2.targetVersion ?? "?"} | touched ~${pkg2.usage.touchedPercent}% | ${pkg2.automatable}`);
616
+ }
617
+ }
618
+ }
619
+ lines.push("");
620
+ }
621
+ }
622
+ if (ext.tsModernity && ext.tsModernity.typescriptVersion) {
623
+ const ts = ext.tsModernity;
624
+ lines.push(chalk4.bold.underline(" TypeScript"));
625
+ const parts = [];
626
+ parts.push(`v${ts.typescriptVersion}`);
627
+ if (ts.strict === true) parts.push(chalk4.green("strict \u2714"));
628
+ else if (ts.strict === false) parts.push(chalk4.yellow("strict \u2716"));
629
+ if (ts.moduleType) parts.push(ts.moduleType.toUpperCase());
630
+ if (ts.target) parts.push(`target: ${ts.target}`);
631
+ lines.push(` ${parts.join(chalk4.dim(" \xB7 "))}`);
632
+ lines.push("");
633
+ }
634
+ if (ext.buildDeploy) {
635
+ const bd = ext.buildDeploy;
636
+ const hasSomething = bd.ci.length > 0 || bd.docker.dockerfileCount > 0 || bd.packageManagers.length > 0;
637
+ if (hasSomething) {
638
+ lines.push(chalk4.bold.underline(" Build & Deploy"));
639
+ if (bd.ci.length > 0) lines.push(` CI: ${bd.ci.join(", ")}`);
640
+ if (bd.docker.dockerfileCount > 0) {
641
+ lines.push(` Docker: ${bd.docker.dockerfileCount} Dockerfile${bd.docker.dockerfileCount !== 1 ? "s" : ""} (${bd.docker.baseImages.join(", ")})`);
642
+ }
643
+ if (bd.packageManagers.length > 0) lines.push(` Package Managers: ${bd.packageManagers.join(", ")}`);
644
+ if (bd.monorepoTools.length > 0) lines.push(` Monorepo: ${bd.monorepoTools.join(", ")}`);
645
+ if (bd.iac.length > 0) lines.push(` IaC: ${bd.iac.join(", ")}`);
646
+ lines.push("");
647
+ }
648
+ }
649
+ if (ext.uiPurpose) {
650
+ const up = ext.uiPurpose;
651
+ lines.push(chalk4.bold.underline(" Product Purpose Signals"));
652
+ lines.push(` Frameworks: ${up.detectedFrameworks.length > 0 ? up.detectedFrameworks.join(", ") : chalk4.dim("unknown")}`);
653
+ lines.push(` Evidence: ${up.topEvidence.length}${up.capped ? chalk4.dim(` of ${up.evidenceCount} (capped)`) : ""}`);
654
+ const top = up.topEvidence.slice(0, 8);
655
+ if (top.length > 0) {
656
+ lines.push(" Top Signals:");
657
+ for (const item of top) {
658
+ lines.push(` - [${item.kind}] ${item.value} ${chalk4.dim(`(${item.file})`)}`);
659
+ }
660
+ }
661
+ if (up.unknownSignals.length > 0) {
662
+ lines.push(" Unknowns:");
663
+ for (const u of up.unknownSignals.slice(0, 4)) {
664
+ lines.push(` - ${chalk4.yellow(u)}`);
665
+ }
666
+ }
667
+ lines.push("");
668
+ }
669
+ if (ext.securityPosture) {
670
+ const sec = ext.securityPosture;
671
+ lines.push(chalk4.bold.underline(" Security Posture"));
672
+ const checks = [];
673
+ checks.push(sec.lockfilePresent ? chalk4.green("Lockfile \u2714") : chalk4.red("Lockfile \u2716"));
674
+ checks.push(sec.gitignoreCoversEnv ? chalk4.green(".env \u2714") : chalk4.red(".env \u2716"));
675
+ checks.push(sec.gitignoreCoversNodeModules ? chalk4.green("node_modules \u2714") : chalk4.yellow("node_modules \u2716"));
676
+ if (sec.multipleLockfileTypes) checks.push(chalk4.yellow("Multiple lockfiles \u26A0"));
677
+ if (sec.envFilesTracked) checks.push(chalk4.red("Env files tracked \u2716"));
678
+ lines.push(` ${checks.join(chalk4.dim(" \xB7 "))}`);
679
+ lines.push("");
680
+ }
681
+ if (ext.platformMatrix) {
682
+ const pm = ext.platformMatrix;
683
+ if (pm.nativeModules.length > 0 || pm.dockerBaseImages.length > 0) {
684
+ lines.push(chalk4.bold.underline(" Platform"));
685
+ if (pm.nativeModules.length > 0) {
686
+ lines.push(` Native modules: ${pm.nativeModules.map((m) => chalk4.dim(m)).join(", ")}`);
687
+ }
688
+ if (pm.osAssumptions.length > 0) {
689
+ lines.push(` OS assumptions: ${pm.osAssumptions.join(", ")}`);
690
+ }
691
+ lines.push("");
692
+ }
693
+ }
694
+ if (ext.codeQuality) {
695
+ const cq = ext.codeQuality;
696
+ lines.push(chalk4.bold.underline(" Code Quality"));
697
+ 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`);
698
+ lines.push(` Max nesting: ${cq.maxNestingDepth} \xB7 Circular deps: ${cq.circularDependencies} \xB7 Dead code: ${cq.deadCodePercent}%`);
699
+ if (cq.godFiles.length > 0) {
700
+ const preview = cq.godFiles.slice(0, 3).map((f) => `${f.path} (${f.lines} lines)`).join(", ");
701
+ lines.push(` ${chalk4.yellow("God files")}: ${preview}`);
702
+ }
703
+ lines.push("");
704
+ }
705
+ if (ext.dependencyGraph) {
706
+ const dg = ext.dependencyGraph;
707
+ if (dg.lockfileType) {
708
+ lines.push(chalk4.bold.underline(" Dependency Graph"));
709
+ lines.push(` ${dg.lockfileType}: ${chalk4.white(`${dg.totalUnique}`)} unique, ${chalk4.white(`${dg.totalInstalled}`)} installed`);
710
+ if (dg.duplicatedPackages.length > 0) {
711
+ lines.push(` ${chalk4.yellow(`${dg.duplicatedPackages.length} duplicated`)} packages`);
712
+ }
713
+ if (dg.phantomDependencies.length > 0) {
714
+ lines.push(` ${chalk4.red(`${dg.phantomDependencies.length} phantom`)} dependencies`);
715
+ }
716
+ lines.push("");
717
+ }
718
+ }
719
+ return lines;
720
+ }
721
+ function formatArchitectureDiagram(arch) {
722
+ const lines = [];
723
+ 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"));
724
+ lines.push(chalk4.bold.cyan("\u2551 Architecture Layers \u2551"));
725
+ 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"));
726
+ lines.push("");
727
+ lines.push(chalk4.bold(" Archetype: ") + `${arch.archetype}` + chalk4.dim(` (${Math.round(arch.archetypeConfidence * 100)}% confidence)`));
728
+ lines.push(` Files classified: ${arch.totalClassified}` + (arch.unclassified > 0 ? chalk4.dim(` (${arch.unclassified} unclassified)`) : ""));
729
+ lines.push("");
730
+ if (arch.layers.length > 0) {
731
+ for (const layer of arch.layers) {
732
+ const risk = layer.riskLevel === "none" ? chalk4.dim("none") : layer.riskLevel === "low" ? chalk4.green("low") : layer.riskLevel === "moderate" ? chalk4.yellow("moderate") : chalk4.red("high");
733
+ lines.push(` ${chalk4.bold(layer.layer)} ${layer.fileCount} file${layer.fileCount !== 1 ? "s" : ""} drift ${scoreBar(layer.driftScore)} risk ${risk}`);
734
+ }
735
+ lines.push("");
736
+ }
737
+ return lines;
738
+ }
739
+ function generatePriorityActions(artifact) {
740
+ const actions = [];
741
+ const eolProjects = artifact.projects.filter(
742
+ (p) => p.runtimeMajorsBehind !== void 0 && p.runtimeMajorsBehind >= 3
743
+ );
744
+ if (eolProjects.length > 0) {
745
+ const names = eolProjects.map((p) => p.name).join(", ");
746
+ let detail = `End-of-life runtimes no longer receive security patches and block ecosystem upgrades.`;
747
+ const fileLines = [];
748
+ for (const p of eolProjects) {
749
+ fileLines.push(`
750
+ ./${p.path}`);
751
+ fileLines.push(`
752
+ ${p.runtime} \u2192 ${p.runtimeLatest} (${p.runtimeMajorsBehind} major${p.runtimeMajorsBehind > 1 ? "s" : ""} behind)`);
753
+ }
754
+ detail += fileLines.join("");
755
+ actions.push({
756
+ title: `Upgrade EOL runtime${eolProjects.length > 1 ? "s" : ""} in ${names}`,
757
+ explanation: detail,
758
+ impact: `+${Math.min(eolProjects.length * 10, 30)} points (runtime & EOL scores)`,
759
+ severity: 100
760
+ });
761
+ }
762
+ const severeFrameworks = [];
763
+ for (const p of artifact.projects) {
764
+ for (const fw of p.frameworks) {
765
+ if (fw.majorsBehind !== null && fw.majorsBehind >= 3) {
766
+ severeFrameworks.push({ name: fw.name, fw: `${fw.currentVersion} \u2192 ${fw.latestVersion}`, behind: fw.majorsBehind, project: p.name, projectPath: p.path });
767
+ }
768
+ }
769
+ }
770
+ if (severeFrameworks.length > 0) {
771
+ const worst = severeFrameworks.sort((a, b) => b.behind - a.behind)[0];
772
+ const others = severeFrameworks.length > 1 ? ` (+${severeFrameworks.length - 1} more)` : "";
773
+ let detail = `${worst.behind} major versions behind. Major framework drift increases breaking change risk and blocks access to security fixes and performance improvements.`;
774
+ const fileLines = [];
775
+ let shown = 0;
776
+ for (const sf of severeFrameworks) {
777
+ if (shown >= 8) break;
778
+ fileLines.push(`
779
+ ./${sf.projectPath}`);
780
+ fileLines.push(`
781
+ ${sf.name}: ${sf.fw} (${sf.behind} major${sf.behind > 1 ? "s" : ""} behind)`);
782
+ shown++;
783
+ }
784
+ const remaining = severeFrameworks.length - shown;
785
+ detail += fileLines.join("");
786
+ if (remaining > 0) detail += `
787
+ ... and ${remaining} more`;
788
+ actions.push({
789
+ title: `Upgrade ${worst.name} ${worst.fw} in ${worst.project}${others}`,
790
+ explanation: detail,
791
+ impact: `+5\u201315 points`,
792
+ severity: 90
793
+ });
794
+ }
795
+ for (const p of artifact.projects) {
796
+ const b = p.dependencyAgeBuckets;
797
+ const total = b.current + b.oneBehind + b.twoPlusBehind;
798
+ if (total === 0) continue;
799
+ const twoPlusPct = Math.round(b.twoPlusBehind / total * 100);
800
+ if (twoPlusPct >= 40) {
801
+ let detail = `${b.twoPlusBehind} of ${total} dependencies are 2+ majors behind. Run \`npm outdated\` and prioritise packages with known CVEs or breaking API changes.`;
802
+ const worstDeps = p.dependencies.filter((d) => d.majorsBehind !== null && d.majorsBehind >= 2).sort((a, b2) => (b2.majorsBehind ?? 0) - (a.majorsBehind ?? 0));
803
+ if (worstDeps.length > 0) {
804
+ const depLines = [];
805
+ let shown = 0;
806
+ depLines.push(`
807
+ ./${p.path}`);
808
+ for (const dep of worstDeps) {
809
+ if (shown >= 8) break;
810
+ const current = dep.resolvedVersion ?? dep.currentSpec;
811
+ const latest = dep.latestStable ?? "?";
812
+ depLines.push(`
813
+ ${dep.package}: ${current} \u2192 ${latest} (${dep.majorsBehind} major${dep.majorsBehind > 1 ? "s" : ""} behind)`);
814
+ shown++;
815
+ }
816
+ const remaining = worstDeps.length - shown;
817
+ detail += depLines.join("");
818
+ if (remaining > 0) detail += `
819
+ ... and ${remaining} more`;
820
+ }
821
+ actions.push({
822
+ title: `Reduce dependency rot in ${p.name} (${twoPlusPct}% severely outdated)`,
823
+ explanation: detail,
824
+ impact: `+5\u201310 points`,
825
+ severity: 80 + twoPlusPct / 10
826
+ });
827
+ }
828
+ }
829
+ const twoMajorFrameworks = [];
830
+ for (const p of artifact.projects) {
831
+ for (const fw of p.frameworks) {
832
+ if (fw.majorsBehind === 2) {
833
+ twoMajorFrameworks.push({ name: fw.name, project: p.name, projectPath: p.path, fw: `${fw.currentVersion} \u2192 ${fw.latestVersion}` });
834
+ }
835
+ }
836
+ }
837
+ const uniqueTwo = [...new Map(twoMajorFrameworks.map((f) => [f.name, f])).values()];
838
+ if (uniqueTwo.length > 0) {
839
+ const list = uniqueTwo.slice(0, 3).map((f) => `${f.name} (${f.fw})`).join(", ");
840
+ const moreCount = uniqueTwo.length > 3 ? ` +${uniqueTwo.length - 3} more` : "";
841
+ 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.`;
842
+ const fileLines = [];
843
+ let shown = 0;
844
+ for (const tf of twoMajorFrameworks) {
845
+ if (shown >= 8) break;
846
+ fileLines.push(`
847
+ ./${tf.projectPath}`);
848
+ fileLines.push(`
849
+ ${tf.name}: ${tf.fw}`);
850
+ shown++;
851
+ }
852
+ const remaining = twoMajorFrameworks.length - shown;
853
+ detail += fileLines.join("");
854
+ if (remaining > 0) detail += `
855
+ ... and ${remaining} more`;
856
+ actions.push({
857
+ title: `Plan major framework upgrades: ${list}${moreCount}`,
858
+ explanation: detail,
859
+ impact: `+5\u201310 points`,
860
+ severity: 60
861
+ });
862
+ }
863
+ if (artifact.extended?.breakingChangeExposure) {
864
+ const bc = artifact.extended.breakingChangeExposure;
865
+ const total = bc.deprecatedPackages.length + bc.legacyPolyfills.length;
866
+ if (total > 0) {
867
+ const items = [...bc.deprecatedPackages, ...bc.legacyPolyfills].slice(0, 5).join(", ");
868
+ const moreCount = total > 5 ? ` +${total - 5} more` : "";
869
+ let detail = `${total} package${total !== 1 ? "s" : ""} are deprecated or legacy polyfills. These receive no updates and may have known vulnerabilities.`;
870
+ const allPkgNames = /* @__PURE__ */ new Set([...bc.deprecatedPackages, ...bc.legacyPolyfills]);
871
+ const fileLines = [];
872
+ let shown = 0;
873
+ for (const p of artifact.projects) {
874
+ const matches = p.dependencies.filter((d) => allPkgNames.has(d.package));
875
+ if (matches.length === 0) continue;
876
+ if (shown >= 10) break;
877
+ fileLines.push(`
878
+ ./${p.path}`);
879
+ for (const dep of matches) {
880
+ if (shown >= 10) break;
881
+ const ver = dep.resolvedVersion ?? dep.currentSpec;
882
+ const label = bc.deprecatedPackages.includes(dep.package) ? "deprecated" : "polyfill";
883
+ fileLines.push(`
884
+ ${dep.package}: ${ver} (${label})`);
885
+ shown++;
886
+ }
887
+ }
888
+ const remaining = total - shown;
889
+ detail += fileLines.join("");
890
+ if (remaining > 0) detail += `
891
+ ... and ${remaining} more`;
892
+ actions.push({
893
+ title: `Replace deprecated/legacy packages: ${items}${moreCount}`,
894
+ explanation: detail,
895
+ severity: 55
896
+ });
897
+ }
898
+ }
899
+ if (artifact.extended?.dependencyGraph) {
900
+ const dg = artifact.extended.dependencyGraph;
901
+ const phantomCount = dg.phantomDependencies.length;
902
+ if (phantomCount >= 10) {
903
+ let detail = `Packages used in code but not declared in package.json. These rely on transitive installs and can break unpredictably when other packages update.`;
904
+ const details = dg.phantomDependencyDetails;
905
+ if (details && details.length > 0) {
906
+ const byPath = /* @__PURE__ */ new Map();
907
+ for (const d of details) {
908
+ if (!byPath.has(d.sourcePath)) byPath.set(d.sourcePath, []);
909
+ byPath.get(d.sourcePath).push({ package: d.package, spec: d.spec });
910
+ }
911
+ const pathLines = [];
912
+ let shown = 0;
913
+ for (const [srcPath, pkgs] of byPath) {
914
+ if (shown >= 10) break;
915
+ pathLines.push(`
916
+ ./${srcPath}`);
917
+ for (const pkg2 of pkgs) {
918
+ if (shown >= 10) break;
919
+ pathLines.push(`
920
+ ${pkg2.package}: ${pkg2.spec}`);
921
+ shown++;
922
+ }
923
+ }
924
+ const remaining = phantomCount - shown;
925
+ detail += pathLines.join("");
926
+ if (remaining > 0) detail += `
927
+ ... and ${remaining} more`;
928
+ }
929
+ actions.push({
930
+ title: `Fix ${phantomCount} phantom dependencies`,
931
+ explanation: detail,
932
+ severity: 45
933
+ });
934
+ }
935
+ }
936
+ if (artifact.extended?.securityPosture) {
937
+ const sec = artifact.extended.securityPosture;
938
+ if (sec.envFilesTracked || !sec.lockfilePresent) {
939
+ const issues = [];
940
+ if (sec.envFilesTracked) issues.push(".env files are tracked in git");
941
+ if (!sec.lockfilePresent) issues.push("no lockfile found");
942
+ let detail;
943
+ if (sec.envFilesTracked) {
944
+ detail = "Environment files may contain secrets. Add them to .gitignore and rotate any exposed credentials immediately.";
945
+ detail += "\n ./.gitignore";
946
+ detail += "\n Add: .env, .env.*, .env.local";
947
+ } else {
948
+ detail = "Without a lockfile, installs are non-deterministic. Run the install command to generate one and commit it.";
949
+ detail += "\n ./";
950
+ detail += `
951
+ Missing: ${sec.lockfileTypes.length > 0 ? sec.lockfileTypes.join(", ") + " (multiple types detected)" : "package-lock.json, pnpm-lock.yaml, or yarn.lock"}`;
952
+ }
953
+ actions.push({
954
+ title: `Fix security posture: ${issues.join(", ")}`,
955
+ explanation: detail,
956
+ severity: 95
957
+ });
958
+ }
959
+ }
960
+ if (artifact.extended?.dependencyGraph) {
961
+ const dupes = artifact.extended.dependencyGraph.duplicatedPackages;
962
+ const highImpactDupes = dupes.filter((d) => d.versions.length >= 3);
963
+ if (highImpactDupes.length >= 3) {
964
+ const names = highImpactDupes.slice(0, 4).map((d) => `${d.name} (${d.versions.length}v)`).join(", ");
965
+ let detail = `${highImpactDupes.length} packages have 3+ versions installed. Run \`npm dedupe\` to reduce bundle size and install time.`;
966
+ const dupeLines = [];
967
+ let shown = 0;
968
+ for (const d of highImpactDupes) {
969
+ if (shown >= 8) break;
970
+ dupeLines.push(`
971
+ ${d.name}: ${d.versions.join(", ")} (${d.consumers} consumer${d.consumers !== 1 ? "s" : ""})`);
972
+ shown++;
973
+ }
974
+ const remaining = highImpactDupes.length - shown;
975
+ detail += dupeLines.join("");
976
+ if (remaining > 0) detail += `
977
+ ... and ${remaining} more`;
978
+ actions.push({
979
+ title: `Deduplicate heavily-versioned packages`,
980
+ explanation: detail,
981
+ severity: 35
982
+ });
983
+ }
984
+ }
985
+ actions.sort((a, b) => b.severity - a.severity);
986
+ return actions.slice(0, 5);
987
+ }
988
+
989
+ // src/formatters/markdown.ts
990
+ function formatMarkdown(artifact) {
991
+ const lines = [];
992
+ lines.push("# Vibgrate Drift Report");
993
+ lines.push("");
994
+ lines.push(`| Metric | Value |`);
995
+ lines.push(`|--------|-------|`);
996
+ lines.push(`| **Drift Score** | ${artifact.drift.score}/100 |`);
997
+ lines.push(`| **Risk Level** | ${artifact.drift.riskLevel.toUpperCase()} |`);
998
+ lines.push(`| **Projects** | ${artifact.projects.length} |`);
999
+ const scannedMeta = [artifact.timestamp];
1000
+ if (artifact.durationMs !== void 0) scannedMeta.push(`${(artifact.durationMs / 1e3).toFixed(1)}s`);
1001
+ if (artifact.filesScanned !== void 0) scannedMeta.push(`${artifact.filesScanned} files`);
1002
+ if (artifact.treeSummary) scannedMeta.push(`${artifact.treeSummary.totalFiles.toLocaleString()} workspace files \xB7 ${artifact.treeSummary.totalDirs.toLocaleString()} dirs`);
1003
+ lines.push(`| **Scanned** | ${scannedMeta.join(" \xB7 ")} |`);
1004
+ if (artifact.vcs) {
1005
+ lines.push(`| **VCS** | ${artifact.vcs.type} |`);
1006
+ if (artifact.vcs.branch) lines.push(`| **Branch** | ${artifact.vcs.branch} |`);
1007
+ if (artifact.vcs.sha) lines.push(`| **Commit** | \`${artifact.vcs.shortSha}\` |`);
1008
+ }
1009
+ lines.push("");
1010
+ lines.push("## Score Breakdown");
1011
+ lines.push("");
1012
+ lines.push(`| Component | Score |`);
1013
+ lines.push(`|-----------|-------|`);
1014
+ lines.push(`| Runtime | ${artifact.drift.components.runtimeScore} |`);
1015
+ lines.push(`| Frameworks | ${artifact.drift.components.frameworkScore} |`);
1016
+ lines.push(`| Dependencies | ${artifact.drift.components.dependencyScore} |`);
1017
+ lines.push(`| EOL Risk | ${artifact.drift.components.eolScore} |`);
1018
+ lines.push("");
1019
+ lines.push("## Projects");
1020
+ lines.push("");
1021
+ for (const project of artifact.projects) {
1022
+ lines.push(`### ${project.name} (${project.type})`);
1023
+ lines.push("");
1024
+ if (project.runtime) {
1025
+ const lag = project.runtimeMajorsBehind !== void 0 && project.runtimeMajorsBehind > 0 ? ` \u2014 ${project.runtimeMajorsBehind} major(s) behind` : " \u2014 current";
1026
+ lines.push(`- **Runtime:** ${project.runtime}${lag}`);
1027
+ }
1028
+ if (project.frameworks.length > 0) {
1029
+ lines.push("- **Frameworks:**");
1030
+ for (const fw of project.frameworks) {
1031
+ const lag = fw.majorsBehind !== null ? fw.majorsBehind === 0 ? "current" : `${fw.majorsBehind} behind` : "unknown";
1032
+ lines.push(` - ${fw.name}: ${fw.currentVersion ?? "?"} \u2192 ${fw.latestVersion ?? "?"} (${lag})`);
1033
+ }
1034
+ }
1035
+ const b = project.dependencyAgeBuckets;
1036
+ const total = b.current + b.oneBehind + b.twoPlusBehind + b.unknown;
1037
+ if (total > 0) {
1038
+ lines.push(`- **Dependencies:** ${b.current} current, ${b.oneBehind} 1-behind, ${b.twoPlusBehind} 2+ behind, ${b.unknown} unknown`);
1039
+ }
1040
+ lines.push("");
1041
+ }
1042
+ if (artifact.extended?.uiPurpose) {
1043
+ const up = artifact.extended.uiPurpose;
1044
+ lines.push("## Product Purpose Signals");
1045
+ lines.push("");
1046
+ lines.push(`- **Frameworks:** ${up.detectedFrameworks.length > 0 ? up.detectedFrameworks.join(", ") : "unknown"}`);
1047
+ lines.push(`- **Evidence Items:** ${up.topEvidence.length}${up.capped ? ` (capped from ${up.evidenceCount})` : ""}`);
1048
+ if (up.topEvidence.length > 0) {
1049
+ lines.push("- **Top Evidence:**");
1050
+ for (const item of up.topEvidence.slice(0, 10)) {
1051
+ lines.push(` - [${item.kind}] ${item.value} (${item.file})`);
1052
+ }
1053
+ }
1054
+ if (up.unknownSignals.length > 0) {
1055
+ lines.push("- **Unknowns:**");
1056
+ for (const u of up.unknownSignals.slice(0, 5)) {
1057
+ lines.push(` - ${u}`);
1058
+ }
1059
+ }
1060
+ lines.push("");
1061
+ }
1062
+ if (artifact.findings.length > 0) {
1063
+ lines.push("## Findings");
1064
+ lines.push("");
1065
+ lines.push(`| Level | Rule | Message | Location |`);
1066
+ lines.push(`|-------|------|---------|----------|`);
1067
+ for (const f of artifact.findings) {
1068
+ const emoji = f.level === "error" ? "\u{1F534}" : f.level === "warning" ? "\u{1F7E1}" : "\u{1F535}";
1069
+ lines.push(`| ${emoji} ${f.level} | ${f.ruleId} | ${f.message} | ${f.location} |`);
1070
+ }
1071
+ lines.push("");
1072
+ }
1073
+ if (artifact.delta !== void 0) {
1074
+ const dir = artifact.delta > 0 ? "\u{1F4C8}" : artifact.delta < 0 ? "\u{1F4C9}" : "\u27A1\uFE0F";
1075
+ lines.push(`## Drift Delta: ${dir} ${artifact.delta > 0 ? "+" : ""}${artifact.delta} vs baseline`);
1076
+ lines.push("");
1077
+ }
1078
+ return lines.join("\n");
1079
+ }
1080
+
1081
+ // src/commands/report.ts
1082
+ 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) => {
1083
+ const artifactPath = path4.resolve(opts.in);
61
1084
  if (!await pathExists(artifactPath)) {
62
- console.error(chalk2.red(`Artifact not found: ${artifactPath}`));
63
- console.error(chalk2.dim('Run "vibgrate scan" first to generate a scan artifact.'));
1085
+ console.error(chalk5.red(`Artifact not found: ${artifactPath}`));
1086
+ console.error(chalk5.dim('Run "vibgrate scan" first to generate a scan artifact.'));
64
1087
  process.exit(1);
65
1088
  }
66
1089
  const artifact = await readJsonFile(artifactPath);
@@ -78,20 +1101,370 @@ var reportCommand = new Command2("report").description("Generate a drift report
78
1101
  }
79
1102
  });
80
1103
 
1104
+ // src/commands/push.ts
1105
+ import * as crypto2 from "crypto";
1106
+ import * as path5 from "path";
1107
+ import { Command as Command5 } from "commander";
1108
+ import chalk6 from "chalk";
1109
+
1110
+ // src/utils/compact-artifact.ts
1111
+ import * as zlib from "zlib";
1112
+ import { promisify } from "util";
1113
+
1114
+ // src/utils/compact-evidence.ts
1115
+ var CATEGORY_PATTERNS = [
1116
+ { category: "pricing", pattern: /price|pricing|billing|subscri|trial|credit|plan|tier|upgrade|premium|pro|enterprise/i },
1117
+ { category: "auth", pattern: /sign[- ]?in|sign[- ]?up|log[- ]?in|log[- ]?out|auth|sso|oauth|password|register|invite|onboard/i },
1118
+ { category: "dashboard", pattern: /dashboard|overview|home|main|summary|stats/i },
1119
+ { category: "settings", pattern: /setting|config|preference|option|profile|account/i },
1120
+ { category: "users", pattern: /user|member|team|role|permission|access|admin|owner/i },
1121
+ { category: "integrations", pattern: /integrat|connect|webhook|api[- ]?key|sync|import|export/i },
1122
+ { category: "reports", pattern: /report|analy|metric|chart|graph|insight|track/i },
1123
+ { category: "workflows", pattern: /workflow|automat|schedule|trigger|action|job|task|pipeline/i },
1124
+ { category: "projects", pattern: /project|workspace|organization|folder|repo/i },
1125
+ { category: "navigation", pattern: /menu|nav|sidebar|header|footer|breadcrumb/i }
1126
+ ];
1127
+ function compactUiPurpose(result, maxSamplesPerCategory = 3) {
1128
+ const evidence = result.topEvidence;
1129
+ const dependencies = evidence.filter((e) => e.kind === "dependency").map((e) => e.value).slice(0, 10);
1130
+ const routes = dedupeRoutes(
1131
+ evidence.filter((e) => e.kind === "route").map((e) => e.value)
1132
+ ).slice(0, 15);
1133
+ const textEvidence = evidence.filter(
1134
+ (e) => e.kind !== "dependency" && e.kind !== "route" && e.kind !== "feature_flag"
1135
+ );
1136
+ const byCategory = /* @__PURE__ */ new Map();
1137
+ const categoryCounts = {};
1138
+ for (const item of textEvidence) {
1139
+ const category = categorize(item.value);
1140
+ if (!byCategory.has(category)) {
1141
+ byCategory.set(category, /* @__PURE__ */ new Set());
1142
+ }
1143
+ const normalized = normalizeValue(item.value);
1144
+ if (normalized.length >= 3) {
1145
+ byCategory.get(category).add(normalized);
1146
+ }
1147
+ }
1148
+ const samples = [];
1149
+ for (const [category, values] of byCategory) {
1150
+ const deduped = dedupeStrings([...values]);
1151
+ categoryCounts[category] = deduped.length;
1152
+ for (const value of deduped.slice(0, maxSamplesPerCategory)) {
1153
+ samples.push({ kind: "text", value, category });
1154
+ }
1155
+ }
1156
+ const featureFlags = evidence.filter((e) => e.kind === "feature_flag");
1157
+ if (featureFlags.length > 0) {
1158
+ categoryCounts["feature_flags"] = featureFlags.length;
1159
+ samples.push({ kind: "feature_flag", value: "feature flags detected", category: "feature_flags" });
1160
+ }
1161
+ return {
1162
+ samples,
1163
+ categoryCounts,
1164
+ originalCount: evidence.length,
1165
+ dependencies,
1166
+ routes,
1167
+ detectedFrameworks: result.detectedFrameworks
1168
+ };
1169
+ }
1170
+ function categorize(value) {
1171
+ for (const { category, pattern } of CATEGORY_PATTERNS) {
1172
+ if (pattern.test(value)) return category;
1173
+ }
1174
+ return "general";
1175
+ }
1176
+ function normalizeValue(value) {
1177
+ return value.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").replace(/\s+/g, " ").trim().slice(0, 60);
1178
+ }
1179
+ function dedupeStrings(values) {
1180
+ const sorted = values.sort((a, b) => b.length - a.length);
1181
+ const kept = [];
1182
+ for (const value of sorted) {
1183
+ const isDupe = kept.some((k) => {
1184
+ const stem = value.slice(0, 6);
1185
+ return k.startsWith(stem) || k.includes(value) || value.includes(k);
1186
+ });
1187
+ if (!isDupe) {
1188
+ kept.push(value);
1189
+ }
1190
+ }
1191
+ return kept;
1192
+ }
1193
+ function dedupeRoutes(routes) {
1194
+ const seen = /* @__PURE__ */ new Set();
1195
+ const result = [];
1196
+ for (const route of routes) {
1197
+ const normalized = route.replace(/:[a-z_]+/gi, ":param").replace(/\[\[*\.*\.*[a-z_]+\]*\]/gi, ":param").replace(/\/+$/, "").toLowerCase();
1198
+ if (!seen.has(normalized)) {
1199
+ seen.add(normalized);
1200
+ result.push(route);
1201
+ }
1202
+ }
1203
+ return result;
1204
+ }
1205
+
1206
+ // src/utils/compact-artifact.ts
1207
+ var gzip2 = promisify(zlib.gzip);
1208
+ var MAX_ITEMS = 50;
1209
+ function extractName(entry) {
1210
+ const match = entry.match(/^(.+?)\s*\(/);
1211
+ return match ? match[1].trim() : entry.trim();
1212
+ }
1213
+ function compactDataStores(result) {
1214
+ return {
1215
+ databaseTechnologies: result.databaseTechnologies.slice(0, 10),
1216
+ connectionStrings: [],
1217
+ // Don't include connection strings in upload
1218
+ connectionPoolSettings: result.connectionPoolSettings.slice(0, MAX_ITEMS),
1219
+ replicationSettings: result.replicationSettings.slice(0, 20),
1220
+ readReplicaSettings: result.readReplicaSettings.slice(0, 20),
1221
+ failoverSettings: result.failoverSettings.slice(0, 20),
1222
+ collationAndEncoding: result.collationAndEncoding.slice(0, 20),
1223
+ queryTimeoutDefaults: result.queryTimeoutDefaults.slice(0, 20),
1224
+ manualIndexes: result.manualIndexes.map(extractName).slice(0, MAX_ITEMS),
1225
+ tables: result.tables.map(extractName).slice(0, MAX_ITEMS),
1226
+ views: result.views.map(extractName).slice(0, MAX_ITEMS),
1227
+ storedProcedures: result.storedProcedures.map(extractName).slice(0, MAX_ITEMS),
1228
+ triggers: result.triggers.map(extractName).slice(0, MAX_ITEMS),
1229
+ rowLevelSecurityPolicies: result.rowLevelSecurityPolicies.slice(0, 20),
1230
+ otherServices: result.otherServices.slice(0, 20)
1231
+ };
1232
+ }
1233
+ function compactApiSurface(result) {
1234
+ const seenProviders = /* @__PURE__ */ new Set();
1235
+ const uniqueIntegrations = result.integrations.filter((i) => {
1236
+ const domain = i.provider.split(":")[0];
1237
+ if (seenProviders.has(domain)) return false;
1238
+ seenProviders.add(domain);
1239
+ return true;
1240
+ }).slice(0, MAX_ITEMS).map((i) => ({
1241
+ provider: i.provider,
1242
+ endpoint: "",
1243
+ // Don't include full endpoints
1244
+ version: i.version,
1245
+ parameters: [],
1246
+ // Don't include params
1247
+ configOptions: [],
1248
+ authHints: [],
1249
+ files: []
1250
+ // Don't include file paths
1251
+ }));
1252
+ return {
1253
+ integrations: uniqueIntegrations,
1254
+ openApiSpecifications: result.openApiSpecifications.slice(0, 10),
1255
+ webhookUrls: result.webhookUrls.slice(0, 20),
1256
+ callbackEndpoints: result.callbackEndpoints.slice(0, 20),
1257
+ apiVersionPins: result.apiVersionPins.slice(0, 20),
1258
+ tokenExpirationPolicies: result.tokenExpirationPolicies.slice(0, 20),
1259
+ rateLimitOverrides: result.rateLimitOverrides.slice(0, 20),
1260
+ customHeaders: result.customHeaders.slice(0, 20),
1261
+ corsPolicies: result.corsPolicies.slice(0, 20),
1262
+ oauthScopes: result.oauthScopes.slice(0, 20),
1263
+ apiTokens: []
1264
+ // Don't include token references
1265
+ };
1266
+ }
1267
+ function compactAssetBranding(result) {
1268
+ return {
1269
+ faviconFiles: result.faviconFiles.slice(0, 1),
1270
+ productLogos: []
1271
+ // Don't include logos
1272
+ };
1273
+ }
1274
+ function prepareArtifactForUpload(artifact) {
1275
+ const compacted = { ...artifact };
1276
+ if (compacted.extended) {
1277
+ const ext = { ...compacted.extended };
1278
+ if (ext.dataStores) {
1279
+ ext.dataStores = compactDataStores(ext.dataStores);
1280
+ }
1281
+ if (ext.apiSurface) {
1282
+ ext.apiSurface = compactApiSurface(ext.apiSurface);
1283
+ }
1284
+ if (ext.assetBranding) {
1285
+ ext.assetBranding = compactAssetBranding(ext.assetBranding);
1286
+ }
1287
+ if (ext.uiPurpose) {
1288
+ const compactedUi = compactUiPurpose(ext.uiPurpose);
1289
+ ext.uiPurpose = {
1290
+ enabled: ext.uiPurpose.enabled,
1291
+ detectedFrameworks: compactedUi.detectedFrameworks,
1292
+ evidenceCount: compactedUi.originalCount,
1293
+ capped: ext.uiPurpose.capped,
1294
+ topEvidence: [],
1295
+ // Clear full evidence
1296
+ unknownSignals: [],
1297
+ // Add compacted data under extended properties
1298
+ ...{ compacted: compactedUi }
1299
+ };
1300
+ }
1301
+ if (ext.runtimeConfiguration) {
1302
+ ext.runtimeConfiguration = {
1303
+ ...ext.runtimeConfiguration,
1304
+ environmentVariables: ext.runtimeConfiguration.environmentVariables.slice(0, 100),
1305
+ hiddenConfigFiles: ext.runtimeConfiguration.hiddenConfigFiles.slice(0, MAX_ITEMS),
1306
+ startupArguments: ext.runtimeConfiguration.startupArguments.slice(0, 100)
1307
+ };
1308
+ }
1309
+ if (ext.operationalResilience) {
1310
+ const ops = ext.operationalResilience;
1311
+ ext.operationalResilience = {
1312
+ implicitTimeouts: ops.implicitTimeouts.slice(0, 30),
1313
+ defaultPaginationSize: ops.defaultPaginationSize.slice(0, 30),
1314
+ implicitRetryLogic: ops.implicitRetryLogic.slice(0, 30),
1315
+ defaultLocale: ops.defaultLocale.slice(0, 20),
1316
+ defaultCurrency: ops.defaultCurrency.slice(0, 20),
1317
+ implicitTimezone: ops.implicitTimezone.slice(0, 20),
1318
+ defaultCharacterEncoding: ops.defaultCharacterEncoding.slice(0, 20),
1319
+ sessionStores: ops.sessionStores.slice(0, 20),
1320
+ distributedLocks: ops.distributedLocks.slice(0, 20),
1321
+ jobSchedulers: ops.jobSchedulers.slice(0, 30),
1322
+ idempotencyKeys: ops.idempotencyKeys.slice(0, 20),
1323
+ rateLimitingCounters: ops.rateLimitingCounters.slice(0, 20),
1324
+ circuitBreakerState: ops.circuitBreakerState.slice(0, 20),
1325
+ abTestToggles: ops.abTestToggles.slice(0, 20),
1326
+ regionalEnablementRules: ops.regionalEnablementRules.slice(0, 20),
1327
+ betaAccessGroups: ops.betaAccessGroups.slice(0, 20),
1328
+ licensingEnforcementLogic: ops.licensingEnforcementLogic.slice(0, 20),
1329
+ killSwitches: ops.killSwitches.slice(0, 20),
1330
+ connectorRetryLogic: ops.connectorRetryLogic.slice(0, 20),
1331
+ apiPollingIntervals: ops.apiPollingIntervals.slice(0, 20),
1332
+ fieldMappings: ops.fieldMappings.slice(0, 20),
1333
+ schemaRegistryRules: ops.schemaRegistryRules.slice(0, 20),
1334
+ deadLetterQueueBehavior: ops.deadLetterQueueBehavior.slice(0, 20),
1335
+ dataMaskingRules: ops.dataMaskingRules.slice(0, 20),
1336
+ transformationLogic: ops.transformationLogic.slice(0, 20),
1337
+ timezoneHandling: ops.timezoneHandling.slice(0, 20),
1338
+ encryptionSettings: ops.encryptionSettings.slice(0, 30),
1339
+ hardcodedSecretSignals: ops.hardcodedSecretSignals.slice(0, 20)
1340
+ };
1341
+ }
1342
+ if (ext.dependencyGraph) {
1343
+ ext.dependencyGraph = {
1344
+ ...ext.dependencyGraph,
1345
+ phantomDependencies: ext.dependencyGraph.phantomDependencies.slice(0, MAX_ITEMS),
1346
+ phantomDependencyDetails: ext.dependencyGraph.phantomDependencyDetails?.slice(0, MAX_ITEMS),
1347
+ duplicatedPackages: ext.dependencyGraph.duplicatedPackages.slice(0, MAX_ITEMS)
1348
+ };
1349
+ }
1350
+ compacted.extended = ext;
1351
+ }
1352
+ return compacted;
1353
+ }
1354
+ async function compressArtifact(artifact) {
1355
+ const json = JSON.stringify(artifact);
1356
+ return gzip2(json, { level: 9 });
1357
+ }
1358
+ async function prepareCompressedUpload2(artifact) {
1359
+ const compacted = prepareArtifactForUpload(artifact);
1360
+ const compressed = await compressArtifact(compacted);
1361
+ return { body: compressed, contentEncoding: "gzip" };
1362
+ }
1363
+
1364
+ // src/commands/push.ts
1365
+ function parseDsn2(dsn) {
1366
+ const cleaned = dsn.replace(/[\x00-\x1F\x7F\uFEFF\u200B-\u200D\u2060]/g, "").trim();
1367
+ const match = cleaned.match(/^vibgrate\+(https?):?\/\/([^:]+):([^@]+)@([^/]+)\/(.+)$/);
1368
+ if (!match) return null;
1369
+ return {
1370
+ scheme: match[1],
1371
+ keyId: match[2],
1372
+ secret: match[3],
1373
+ host: match[4],
1374
+ workspaceId: match[5]
1375
+ };
1376
+ }
1377
+ function computeHmac(body, secret) {
1378
+ return crypto2.createHmac("sha256", secret).update(body).digest("base64");
1379
+ }
1380
+ 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 (us, eu)").option("--file <file>", "Scan artifact file", ".vibgrate/scan_result.json").option("--strict", "Fail on upload errors").action(async (opts) => {
1381
+ const dsn = opts.dsn || process.env.VIBGRATE_DSN;
1382
+ if (!dsn) {
1383
+ console.error(chalk6.red("No DSN provided."));
1384
+ console.error(chalk6.dim("Set VIBGRATE_DSN environment variable or use --dsn flag."));
1385
+ if (opts.strict) process.exit(1);
1386
+ return;
1387
+ }
1388
+ const parsed = parseDsn2(dsn);
1389
+ if (!parsed) {
1390
+ console.error(chalk6.red("Invalid DSN format."));
1391
+ console.error(chalk6.dim("Expected: vibgrate+https://<key_id>:<secret>@<host>/<workspace_id>"));
1392
+ if (opts.strict) process.exit(1);
1393
+ return;
1394
+ }
1395
+ const filePath = path5.resolve(opts.file);
1396
+ if (!await pathExists(filePath)) {
1397
+ console.error(chalk6.red(`Scan artifact not found: ${filePath}`));
1398
+ console.error(chalk6.dim('Run "vibgrate scan" first.'));
1399
+ if (opts.strict) process.exit(1);
1400
+ return;
1401
+ }
1402
+ const artifact = await readJsonFile(filePath);
1403
+ const { body, contentEncoding } = await prepareCompressedUpload2(artifact);
1404
+ const timestamp = String(Date.now());
1405
+ let host = parsed.host;
1406
+ if (opts.region) {
1407
+ try {
1408
+ host = resolveIngestHost(opts.region);
1409
+ } catch (e) {
1410
+ console.error(chalk6.red(e instanceof Error ? e.message : String(e)));
1411
+ if (opts.strict) process.exit(1);
1412
+ return;
1413
+ }
1414
+ }
1415
+ const url = `${parsed.scheme}://${host}/v1/ingest/scan`;
1416
+ const originalSize = JSON.stringify(artifact).length;
1417
+ const compressedSize = body.length;
1418
+ const ratio = ((1 - compressedSize / originalSize) * 100).toFixed(0);
1419
+ console.log(chalk6.dim(`Uploading to ${host}... (${(compressedSize / 1024).toFixed(0)} KB, ${ratio}% smaller)`));
1420
+ try {
1421
+ const response = await fetch(url, {
1422
+ method: "POST",
1423
+ headers: {
1424
+ "Content-Type": "application/json",
1425
+ "Content-Encoding": contentEncoding,
1426
+ "X-Vibgrate-Timestamp": timestamp,
1427
+ "Authorization": `VibgrateDSN ${parsed.keyId}:${parsed.secret}`,
1428
+ "Connection": "close"
1429
+ // Prevent keep-alive delays on exit
1430
+ },
1431
+ body
1432
+ });
1433
+ if (!response.ok) {
1434
+ const text = await response.text();
1435
+ throw new Error(`HTTP ${response.status}: ${text}`);
1436
+ }
1437
+ const result = await response.json();
1438
+ console.log(chalk6.green("\u2714") + ` Scan queued for processing (${result.ingestId ?? "ok"})`);
1439
+ console.log();
1440
+ console.log(chalk6.dim("Processing continues in the background. Results available shortly."));
1441
+ console.log();
1442
+ if (result.ingestId) {
1443
+ const dashHost = host.includes("eu.") ? "dash.vibgrate.eu" : "dash.vibgrate.com";
1444
+ const reportUrl = `https://${dashHost}/${parsed.workspaceId}/scan/${result.ingestId}`;
1445
+ console.log(chalk6.dim("View report: ") + chalk6.underline(reportUrl));
1446
+ }
1447
+ } catch (e) {
1448
+ const msg = e instanceof Error ? e.message : String(e);
1449
+ console.error(chalk6.red(`Upload failed: ${msg}`));
1450
+ if (opts.strict) process.exit(1);
1451
+ }
1452
+ });
1453
+
81
1454
  // src/commands/update.ts
82
1455
  import { execSync } from "child_process";
83
- import * as path4 from "path";
84
- import { Command as Command3 } from "commander";
85
- import chalk3 from "chalk";
1456
+ import * as path7 from "path";
1457
+ import { Command as Command6 } from "commander";
1458
+ import chalk7 from "chalk";
86
1459
 
87
1460
  // src/utils/update-check.ts
1461
+ var import_semver = __toESM(require_semver(), 1);
88
1462
  import * as fs from "fs/promises";
89
- import * as path3 from "path";
1463
+ import * as path6 from "path";
90
1464
  import * as os from "os";
91
- import semver from "semver";
92
1465
  var REGISTRY_URL = "https://registry.npmjs.org/@vibgrate%2fcli/latest";
93
- var CACHE_DIR = path3.join(os.homedir(), ".vibgrate");
94
- var CACHE_FILE = path3.join(CACHE_DIR, "update-check.json");
1466
+ var CACHE_DIR = path6.join(os.homedir(), ".vibgrate");
1467
+ var CACHE_FILE = path6.join(CACHE_DIR, "update-check.json");
95
1468
  var CHECK_INTERVAL_MS = 12 * 60 * 60 * 1e3;
96
1469
  async function checkForUpdate() {
97
1470
  try {
@@ -100,7 +1473,7 @@ async function checkForUpdate() {
100
1473
  return {
101
1474
  current: VERSION,
102
1475
  latest: cached.latest,
103
- updateAvailable: semver.gt(cached.latest, VERSION)
1476
+ updateAvailable: import_semver.default.gt(cached.latest, VERSION)
104
1477
  };
105
1478
  }
106
1479
  const controller = new AbortController();
@@ -118,12 +1491,12 @@ async function checkForUpdate() {
118
1491
  if (!response.ok) return null;
119
1492
  const data = await response.json();
120
1493
  const latest = data.version;
121
- if (!latest || !semver.valid(latest)) return null;
1494
+ if (!latest || !import_semver.default.valid(latest)) return null;
122
1495
  await writeCache({ latest, checkedAt: Date.now() });
123
1496
  return {
124
1497
  current: VERSION,
125
1498
  latest,
126
- updateAvailable: semver.gt(latest, VERSION)
1499
+ updateAvailable: import_semver.default.gt(latest, VERSION)
127
1500
  };
128
1501
  } catch {
129
1502
  return null;
@@ -146,7 +1519,7 @@ async function fetchLatestVersion() {
146
1519
  if (!response.ok) return null;
147
1520
  const data = await response.json();
148
1521
  const latest = data.version;
149
- if (!latest || !semver.valid(latest)) return null;
1522
+ if (!latest || !import_semver.default.valid(latest)) return null;
150
1523
  await writeCache({ latest, checkedAt: Date.now() });
151
1524
  return latest;
152
1525
  } catch {
@@ -184,8 +1557,8 @@ function detectGlobalInstall() {
184
1557
  }
185
1558
  return null;
186
1559
  }
187
- function getGlobalUpdateCommand(pm, pkg, version) {
188
- const spec = `${pkg}@${version}`;
1560
+ function getGlobalUpdateCommand(pm, pkg2, version) {
1561
+ const spec = `${pkg2}@${version}`;
189
1562
  switch (pm) {
190
1563
  case "pnpm":
191
1564
  return `pnpm add -g ${spec}`;
@@ -199,13 +1572,13 @@ function getGlobalUpdateCommand(pm, pkg, version) {
199
1572
  }
200
1573
  }
201
1574
  async function detectPackageManager(cwd) {
202
- if (await pathExists(path4.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
203
- if (await pathExists(path4.join(cwd, "bun.lockb"))) return "bun";
204
- if (await pathExists(path4.join(cwd, "yarn.lock"))) return "yarn";
1575
+ if (await pathExists(path7.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
1576
+ if (await pathExists(path7.join(cwd, "bun.lockb"))) return "bun";
1577
+ if (await pathExists(path7.join(cwd, "yarn.lock"))) return "yarn";
205
1578
  return "npm";
206
1579
  }
207
- function getInstallCommand(pm, pkg, version, isDev) {
208
- const spec = `${pkg}@${version}`;
1580
+ function getInstallCommand(pm, pkg2, version, isDev) {
1581
+ const spec = `${pkg2}@${version}`;
209
1582
  switch (pm) {
210
1583
  case "pnpm":
211
1584
  return isDev ? `pnpm add -D ${spec}` : `pnpm add ${spec}`;
@@ -220,30 +1593,30 @@ function getInstallCommand(pm, pkg, version, isDev) {
220
1593
  }
221
1594
  async function isDevDependency(cwd) {
222
1595
  try {
223
- const pkgPath = path4.join(cwd, "package.json");
1596
+ const pkgPath = path7.join(cwd, "package.json");
224
1597
  const raw = await (await import("fs/promises")).readFile(pkgPath, "utf-8");
225
- const pkg = JSON.parse(raw);
226
- return Boolean(pkg.devDependencies?.["@vibgrate/cli"]);
1598
+ const pkg2 = JSON.parse(raw);
1599
+ return Boolean(pkg2.devDependencies?.["@vibgrate/cli"]);
227
1600
  } catch {
228
1601
  return true;
229
1602
  }
230
1603
  }
231
- var updateCommand = new Command3("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) => {
232
- console.log(chalk3.dim(`Current version: ${VERSION}`));
233
- console.log(chalk3.dim("Checking npm registry..."));
1604
+ 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) => {
1605
+ console.log(chalk7.dim(`Current version: ${VERSION}`));
1606
+ console.log(chalk7.dim("Checking npm registry..."));
234
1607
  const latest = await fetchLatestVersion();
235
1608
  if (!latest) {
236
- console.error(chalk3.red("Could not reach the npm registry. Check your network connection."));
1609
+ console.error(chalk7.red("Could not reach the npm registry. Check your network connection."));
237
1610
  process.exit(1);
238
1611
  }
239
- const semver2 = await import("semver");
1612
+ const semver2 = await import("./semver-JBJZTHUX.js");
240
1613
  if (!semver2.gt(latest, VERSION)) {
241
- console.log(chalk3.green("\u2714") + ` You are on the latest version (${VERSION}).`);
1614
+ console.log(chalk7.green("\u2714") + ` You are on the latest version (${VERSION}).`);
242
1615
  return;
243
1616
  }
244
- console.log(chalk3.yellow(`Update available: ${VERSION} \u2192 ${latest}`));
1617
+ console.log(chalk7.yellow(`Update available: ${VERSION} \u2192 ${latest}`));
245
1618
  if (opts.check) {
246
- console.log(chalk3.dim('Run "vibgrate update" to install.'));
1619
+ console.log(chalk7.dim('Run "vibgrate update" to install.'));
247
1620
  return;
248
1621
  }
249
1622
  const cwd = process.cwd();
@@ -253,26 +1626,26 @@ var updateCommand = new Command3("update").description("Update vibgrate to the l
253
1626
  let cmd;
254
1627
  if (isGlobal) {
255
1628
  cmd = getGlobalUpdateCommand(pm, "@vibgrate/cli", latest);
256
- console.log(chalk3.dim(`Updating global installation with ${pm}: ${cmd}`));
1629
+ console.log(chalk7.dim(`Updating global installation with ${pm}: ${cmd}`));
257
1630
  } else {
258
1631
  const isDev = await isDevDependency(cwd);
259
1632
  cmd = getInstallCommand(pm, "@vibgrate/cli", latest, isDev);
260
- console.log(chalk3.dim(`Using ${pm}: ${cmd}`));
1633
+ console.log(chalk7.dim(`Using ${pm}: ${cmd}`));
261
1634
  }
262
1635
  try {
263
1636
  execSync(cmd, { cwd, stdio: "inherit" });
264
- console.log(chalk3.green("\u2714") + ` Updated to @vibgrate/cli@${latest}`);
1637
+ console.log(chalk7.green("\u2714") + ` Updated to @vibgrate/cli@${latest}`);
265
1638
  } catch {
266
- console.error(chalk3.red(`Update failed. Run manually: ${cmd}`));
1639
+ console.error(chalk7.red(`Update failed. Run manually: ${cmd}`));
267
1640
  process.exit(1);
268
1641
  }
269
1642
  });
270
1643
 
271
1644
  // src/commands/sbom.ts
272
- import * as path5 from "path";
1645
+ import * as path8 from "path";
273
1646
  import { randomUUID } from "crypto";
274
- import { Command as Command4 } from "commander";
275
- import chalk4 from "chalk";
1647
+ import { Command as Command7 } from "commander";
1648
+ import chalk8 from "chalk";
276
1649
  function flattenDependencies(artifact) {
277
1650
  const rows = [];
278
1651
  for (const project of artifact.projects) {
@@ -410,50 +1783,50 @@ function formatDeltaText(base, current) {
410
1783
  return lines.join("\n");
411
1784
  }
412
1785
  async function readArtifactOrExit(filePath) {
413
- const absolutePath = path5.resolve(filePath);
1786
+ const absolutePath = path8.resolve(filePath);
414
1787
  if (!await pathExists(absolutePath)) {
415
- console.error(chalk4.red(`Artifact not found: ${absolutePath}`));
1788
+ console.error(chalk8.red(`Artifact not found: ${absolutePath}`));
416
1789
  process.exit(1);
417
1790
  }
418
1791
  return readJsonFile(absolutePath);
419
1792
  }
420
- var exportCommand = new Command4("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) => {
1793
+ 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) => {
421
1794
  const artifact = await readArtifactOrExit(opts.in);
422
1795
  const format = opts.format.toLowerCase();
423
1796
  if (format !== "cyclonedx" && format !== "spdx") {
424
- console.error(chalk4.red("Invalid SBOM format. Use cyclonedx or spdx."));
1797
+ console.error(chalk8.red("Invalid SBOM format. Use cyclonedx or spdx."));
425
1798
  process.exit(1);
426
1799
  }
427
1800
  const sbom = format === "cyclonedx" ? toCycloneDx(artifact) : toSpdx(artifact);
428
1801
  const body = JSON.stringify(sbom, null, 2);
429
1802
  if (opts.out) {
430
- await writeTextFile(path5.resolve(opts.out), body);
431
- console.log(chalk4.green("\u2714") + ` SBOM written to ${opts.out}`);
1803
+ await writeTextFile(path8.resolve(opts.out), body);
1804
+ console.log(chalk8.green("\u2714") + ` SBOM written to ${opts.out}`);
432
1805
  } else {
433
1806
  console.log(body);
434
1807
  }
435
1808
  });
436
- var deltaCommand = new Command4("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) => {
1809
+ 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) => {
437
1810
  const base = await readArtifactOrExit(opts.from);
438
1811
  const current = await readArtifactOrExit(opts.to);
439
1812
  const report = formatDeltaText(base, current);
440
1813
  if (opts.out) {
441
- await writeTextFile(path5.resolve(opts.out), report);
442
- console.log(chalk4.green("\u2714") + ` SBOM delta report written to ${opts.out}`);
1814
+ await writeTextFile(path8.resolve(opts.out), report);
1815
+ console.log(chalk8.green("\u2714") + ` SBOM delta report written to ${opts.out}`);
443
1816
  } else {
444
1817
  console.log(report);
445
1818
  }
446
1819
  });
447
- var sbomCommand = new Command4("sbom").description("SBOM export and delta reports for dependency drift tracking").addCommand(exportCommand).addCommand(deltaCommand);
1820
+ var sbomCommand = new Command7("sbom").description("SBOM export and delta reports for dependency drift tracking").addCommand(exportCommand).addCommand(deltaCommand);
448
1821
 
449
1822
  // src/commands/extract.ts
450
- import * as path6 from "path";
1823
+ import * as path9 from "path";
451
1824
  import * as os2 from "os";
452
1825
  import * as fs2 from "fs/promises";
453
1826
  import { existsSync } from "fs";
454
1827
  import { spawn, spawnSync } from "child_process";
455
- import { Command as Command5 } from "commander";
456
- import chalk5 from "chalk";
1828
+ import { Command as Command8 } from "commander";
1829
+ import chalk9 from "chalk";
457
1830
  var EXIT_SUCCESS = 0;
458
1831
  var EXIT_SCHEMA_FAILURE = 1;
459
1832
  var EXIT_PARSE_FAILURE = 2;
@@ -543,8 +1916,8 @@ async function detectLanguages(rootDir, includeTests) {
543
1916
  }
544
1917
  for (const entry of entries) {
545
1918
  if (entry.name.startsWith(".")) continue;
546
- const absPath = path6.join(dir, entry.name);
547
- const relPath = path6.join(relBase, entry.name);
1919
+ const absPath = path9.join(dir, entry.name);
1920
+ const relPath = path9.join(relBase, entry.name);
548
1921
  if (entry.isDirectory()) {
549
1922
  if (!SKIP_DIRS.has(entry.name)) {
550
1923
  await walk(absPath, relPath);
@@ -553,7 +1926,7 @@ async function detectLanguages(rootDir, includeTests) {
553
1926
  }
554
1927
  if (!entry.isFile()) continue;
555
1928
  if (!includeTests && isTestPath(relPath)) continue;
556
- const ext = path6.extname(entry.name).toLowerCase();
1929
+ const ext = path9.extname(entry.name).toLowerCase();
557
1930
  for (const [lang, exts] of Object.entries(LANGUAGE_EXTENSIONS)) {
558
1931
  if (exts.has(ext)) {
559
1932
  counts.set(lang, (counts.get(lang) ?? 0) + 1);
@@ -572,119 +1945,67 @@ function validateFactLine(line) {
572
1945
  } catch {
573
1946
  return { valid: false, error: `Invalid JSON: ${line.substring(0, 80)}...` };
574
1947
  }
575
- const envelope = parsed;
576
- if (typeof envelope.factId !== "string" || !envelope.factId) {
1948
+ const obj = parsed;
1949
+ if (obj.factType === "Models" || obj.factType === "References") {
1950
+ if (obj.payload === void 0 || obj.payload === null) {
1951
+ return { valid: false, error: `Preamble ${obj.factType} missing payload` };
1952
+ }
1953
+ return { valid: true };
1954
+ }
1955
+ if (typeof obj.factId !== "string" || !obj.factId) {
577
1956
  return { valid: false, error: "Missing or invalid factId" };
578
1957
  }
579
- if (typeof envelope.factType !== "string" || !envelope.factType) {
1958
+ if (typeof obj.m === "string") {
1959
+ if (obj.payload === void 0 || obj.payload === null) {
1960
+ return { valid: false, error: "Compressed fact missing payload" };
1961
+ }
1962
+ return { valid: true };
1963
+ }
1964
+ if (typeof obj.factType !== "string" || !obj.factType) {
580
1965
  return { valid: false, error: "Missing or invalid factType" };
581
1966
  }
582
- if (typeof envelope.language !== "string") {
1967
+ if (typeof obj.language !== "string") {
583
1968
  return { valid: false, error: "Missing or invalid language" };
584
1969
  }
585
- if (typeof envelope.scanner !== "string") {
1970
+ if (typeof obj.scanner !== "string") {
586
1971
  return { valid: false, error: "Missing or invalid scanner" };
587
1972
  }
588
- if (typeof envelope.scannerVersion !== "string") {
1973
+ if (typeof obj.scannerVersion !== "string") {
589
1974
  return { valid: false, error: "Missing or invalid scannerVersion" };
590
1975
  }
591
- if (typeof envelope.emittedAt !== "string") {
1976
+ if (typeof obj.emittedAt !== "string") {
592
1977
  return { valid: false, error: "Missing or invalid emittedAt" };
593
1978
  }
594
- if (envelope.payload === void 0 || envelope.payload === null) {
1979
+ if (obj.payload === void 0 || obj.payload === null) {
595
1980
  return { valid: false, error: "Missing payload" };
596
1981
  }
597
- return { valid: true, fact: envelope };
1982
+ return { valid: true };
598
1983
  }
599
- function decompressHcsLines(lines) {
600
- if (lines.length === 0) return lines;
601
- let modelsLine = null;
602
- let refsLine = null;
603
- const contentLines = [];
1984
+ function splitHcsOutput(lines) {
1985
+ const preamble = [];
1986
+ const facts = [];
604
1987
  for (const line of lines) {
605
1988
  let parsed;
606
1989
  try {
607
1990
  parsed = JSON.parse(line);
608
1991
  } catch {
609
- contentLines.push(line);
1992
+ facts.push(line);
610
1993
  continue;
611
1994
  }
612
1995
  const obj = parsed;
613
- if (obj.factType === "Models" && !modelsLine) {
614
- modelsLine = obj;
615
- } else if (obj.factType === "References" && !refsLine) {
616
- refsLine = obj;
1996
+ if (obj.factType === "Models" || obj.factType === "References") {
1997
+ preamble.push(line);
617
1998
  } else {
618
- contentLines.push(line);
619
- }
620
- }
621
- if (!modelsLine) return lines;
622
- const emittedAt = typeof modelsLine.emittedAt === "string" ? modelsLine.emittedAt : (/* @__PURE__ */ new Date()).toISOString();
623
- const modelsPayload = modelsLine.payload;
624
- const modelMap = /* @__PURE__ */ new Map();
625
- for (const m of modelsPayload?.models ?? []) {
626
- if (m.id) modelMap.set(m.id, m);
627
- }
628
- const refsPayload = refsLine?.payload;
629
- const refMap = /* @__PURE__ */ new Map();
630
- for (const r of refsPayload?.references ?? []) {
631
- if (r.id && r.value) refMap.set(r.id, r.value);
632
- }
633
- const sortedRefs = [...refMap.entries()].sort((a, b) => b[0].length - a[0].length);
634
- function expandRefs(text) {
635
- for (const [token, value] of sortedRefs) {
636
- text = text.split(token).join(value);
1999
+ facts.push(line);
637
2000
  }
638
- return text;
639
2001
  }
640
- const expanded = [];
641
- for (const line of contentLines) {
642
- let parsed;
643
- try {
644
- parsed = JSON.parse(line);
645
- } catch {
646
- expanded.push(line);
647
- continue;
648
- }
649
- const obj = parsed;
650
- if (typeof obj.factType === "string") {
651
- expanded.push(line);
652
- continue;
653
- }
654
- if (typeof obj.factId !== "string" || typeof obj.m !== "string") {
655
- expanded.push(line);
656
- continue;
657
- }
658
- const model = modelMap.get(obj.m);
659
- if (!model) {
660
- expanded.push(line);
661
- continue;
662
- }
663
- const payloadJson = expandRefs(JSON.stringify(obj.payload));
664
- let payload;
665
- try {
666
- payload = JSON.parse(payloadJson);
667
- } catch {
668
- payload = obj.payload;
669
- }
670
- const fullEnvelope = {
671
- factId: obj.factId,
672
- factType: model.factType ?? "",
673
- language: model.language ?? "",
674
- scanner: model.scanner ?? "",
675
- scannerVersion: model.scannerVersion ?? "",
676
- emittedAt: model.emittedAt ?? emittedAt,
677
- payload
678
- };
679
- expanded.push(JSON.stringify(fullEnvelope));
680
- }
681
- return expanded;
2002
+ return { preamble, facts };
682
2003
  }
683
2004
  function resolveHcsWorkerBin() {
684
- const base = import.meta.dirname ?? path6.dirname(new URL(import.meta.url).pathname);
685
- const bundledPath = path6.resolve(base, "..", "dist", "hcs-worker.js");
2005
+ const base = import.meta.dirname ?? path9.dirname(new URL(import.meta.url).pathname);
2006
+ const bundledPath = path9.resolve(base, "..", "dist", "hcs-worker.js");
686
2007
  if (existsSync(bundledPath)) return bundledPath;
687
- const siblingPath = path6.resolve(base, "hcs-worker.js");
2008
+ const siblingPath = path9.resolve(base, "hcs-worker.js");
688
2009
  if (existsSync(siblingPath)) return siblingPath;
689
2010
  try {
690
2011
  const resolved = import.meta.resolve("@vibgrate/hcs-node-worker");
@@ -692,9 +2013,9 @@ function resolveHcsWorkerBin() {
692
2013
  if (existsSync(resolvedPath)) return resolvedPath;
693
2014
  } catch {
694
2015
  }
695
- const monorepoPath = path6.resolve(base, "..", "..", "..", "vibgrate-hcs", "node", "dist", "main.js");
2016
+ const monorepoPath = path9.resolve(base, "..", "..", "..", "vibgrate-hcs", "node", "dist", "main.js");
696
2017
  if (existsSync(monorepoPath)) return monorepoPath;
697
- const devPath = path6.resolve(base, "..", "..", "..", "vibgrate-hcs", "node", "src", "main.ts");
2018
+ const devPath = path9.resolve(base, "..", "..", "..", "vibgrate-hcs", "node", "src", "main.ts");
698
2019
  if (existsSync(devPath)) return devPath;
699
2020
  throw new Error(
700
2021
  'Cannot locate HCS worker. Run "pnpm build" in the CLI package to bundle the worker into dist/.'
@@ -797,7 +2118,7 @@ async function runNodeWorker(rootDir, language, opts) {
797
2118
  args.push("--cobol-copybook-paths", opts.copybookPaths);
798
2119
  }
799
2120
  }
800
- return new Promise((resolve6) => {
2121
+ return new Promise((resolve9) => {
801
2122
  const facts = [];
802
2123
  const errors = [];
803
2124
  let stdoutBuf = "";
@@ -834,7 +2155,7 @@ async function runNodeWorker(rootDir, language, opts) {
834
2155
  continue;
835
2156
  }
836
2157
  if (opts.verbose) {
837
- process.stderr.write(chalk5.dim(`[${language}] ${trimmed}
2158
+ process.stderr.write(chalk9.dim(`[${language}] ${trimmed}
838
2159
  `));
839
2160
  }
840
2161
  if (trimmed.startsWith("[error]")) {
@@ -848,15 +2169,15 @@ async function runNodeWorker(rootDir, language, opts) {
848
2169
  facts.push(stdoutBuf.trim());
849
2170
  }
850
2171
  if (killed) {
851
- resolve6({ language, facts, errors: ["Worker killed: timeout exceeded"], exitCode: EXIT_TIMEOUT });
2172
+ resolve9({ language, facts, errors: ["Worker killed: timeout exceeded"], exitCode: EXIT_TIMEOUT });
852
2173
  } else {
853
- resolve6({ language, facts, errors, exitCode: code ?? 0 });
2174
+ resolve9({ language, facts, errors, exitCode: code ?? 0 });
854
2175
  }
855
2176
  });
856
2177
  child.on("error", (err) => {
857
2178
  clearTimeout(timer);
858
2179
  errors.push(`Failed to spawn worker: ${err.message}`);
859
- resolve6({ language, facts, errors, exitCode: EXIT_PARSE_FAILURE });
2180
+ resolve9({ language, facts, errors, exitCode: EXIT_PARSE_FAILURE });
860
2181
  });
861
2182
  });
862
2183
  }
@@ -876,7 +2197,7 @@ function resolveDotnetPublishedWorker(workersDir) {
876
2197
  };
877
2198
  const candidates = candidatesByPlatform[process.platform] ?? [];
878
2199
  for (const filename of candidates) {
879
- const fullPath = path6.join(workersDir, filename);
2200
+ const fullPath = path9.join(workersDir, filename);
880
2201
  if (existsSync(fullPath)) {
881
2202
  return fullPath;
882
2203
  }
@@ -884,30 +2205,30 @@ function resolveDotnetPublishedWorker(workersDir) {
884
2205
  return null;
885
2206
  }
886
2207
  function resolveNativeWorker(language, projectDir) {
887
- const base = import.meta.dirname ?? path6.dirname(new URL(import.meta.url).pathname);
888
- const hcsFromBundle = path6.resolve(base, "..", "..", "vibgrate-hcs");
889
- const hcsFromSrc = path6.resolve(base, "..", "..", "..", "vibgrate-hcs");
2208
+ const base = import.meta.dirname ?? path9.dirname(new URL(import.meta.url).pathname);
2209
+ const hcsFromBundle = path9.resolve(base, "..", "..", "vibgrate-hcs");
2210
+ const hcsFromSrc = path9.resolve(base, "..", "..", "..", "vibgrate-hcs");
890
2211
  const hcsRoot = existsSync(hcsFromBundle) ? hcsFromBundle : hcsFromSrc;
891
- const workersDir = path6.resolve(base, "workers");
2212
+ const workersDir = path9.resolve(base, "workers");
892
2213
  switch (language) {
893
2214
  case "go": {
894
- const bin = path6.join(workersDir, process.platform === "win32" ? "vibgrate-hcs-go.exe" : "vibgrate-hcs-go");
2215
+ const bin = path9.join(workersDir, process.platform === "win32" ? "vibgrate-hcs-go.exe" : "vibgrate-hcs-go");
895
2216
  if (existsSync(bin)) {
896
2217
  return { cmd: bin, args: ["--project", projectDir, "--output", "ndjson"] };
897
2218
  }
898
- const src = path6.join(hcsRoot, "go");
899
- if (existsSync(path6.join(src, "main.go"))) {
2219
+ const src = path9.join(hcsRoot, "go");
2220
+ if (existsSync(path9.join(src, "main.go"))) {
900
2221
  return { cmd: "go", args: ["run", ".", "--project", projectDir, "--output", "ndjson"], cwd: src };
901
2222
  }
902
2223
  return null;
903
2224
  }
904
2225
  case "python": {
905
- const bin = path6.join(workersDir, "vibgrate-hcs-python");
2226
+ const bin = path9.join(workersDir, "vibgrate-hcs-python");
906
2227
  if (existsSync(bin)) {
907
2228
  return { cmd: bin, args: ["--project", projectDir, "--output", "ndjson"] };
908
2229
  }
909
- const src = path6.join(hcsRoot, "python");
910
- if (existsSync(path6.join(src, "pyproject.toml"))) {
2230
+ const src = path9.join(hcsRoot, "python");
2231
+ if (existsSync(path9.join(src, "pyproject.toml"))) {
911
2232
  return {
912
2233
  cmd: "python3",
913
2234
  args: ["-m", "vibgrate_hcs_python.main", "--project", projectDir, "--output", "ndjson"],
@@ -917,13 +2238,13 @@ function resolveNativeWorker(language, projectDir) {
917
2238
  return null;
918
2239
  }
919
2240
  case "java": {
920
- const jar = path6.join(workersDir, "vibgrate-hcs-jvm.jar");
2241
+ const jar = path9.join(workersDir, "vibgrate-hcs-jvm.jar");
921
2242
  if (existsSync(jar)) {
922
2243
  return { cmd: "java", args: ["-jar", jar, "--project", projectDir, "--output", "ndjson"] };
923
2244
  }
924
- const src = path6.join(hcsRoot, "jvm");
925
- if (existsSync(path6.join(src, "build.gradle.kts"))) {
926
- const gradlew = path6.join(src, process.platform === "win32" ? "gradlew.bat" : "gradlew");
2245
+ const src = path9.join(hcsRoot, "jvm");
2246
+ if (existsSync(path9.join(src, "build.gradle.kts"))) {
2247
+ const gradlew = path9.join(src, process.platform === "win32" ? "gradlew.bat" : "gradlew");
927
2248
  const launcher = existsSync(gradlew) ? gradlew : "gradle";
928
2249
  return {
929
2250
  cmd: launcher,
@@ -939,11 +2260,11 @@ function resolveNativeWorker(language, projectDir) {
939
2260
  if (publishedWorker) {
940
2261
  return { cmd: publishedWorker, args: ["--project", projectDir, "--output", "ndjson"] };
941
2262
  }
942
- const dll = path6.join(workersDir, "VibgrateHcsWorker.dll");
2263
+ const dll = path9.join(workersDir, "VibgrateHcsWorker.dll");
943
2264
  if (existsSync(dll)) {
944
2265
  return { cmd: "dotnet", args: [dll, "--project", projectDir, "--output", "ndjson"] };
945
2266
  }
946
- const csproj = path6.join(hcsRoot, "dotnet", "src", "VibgrateHcsWorker", "VibgrateHcsWorker.csproj");
2267
+ const csproj = path9.join(hcsRoot, "dotnet", "src", "VibgrateHcsWorker", "VibgrateHcsWorker.csproj");
947
2268
  if (existsSync(csproj)) {
948
2269
  return {
949
2270
  cmd: "dotnet",
@@ -969,19 +2290,19 @@ async function runNativeWorker(rootDir, language, opts) {
969
2290
  exitCode: EXIT_PARSE_FAILURE
970
2291
  };
971
2292
  }
972
- return new Promise((resolve6) => {
2293
+ return new Promise((resolve9) => {
973
2294
  const facts = [];
974
2295
  const errors = [];
975
2296
  let stdoutBuf = "";
976
2297
  let killed = false;
977
2298
  if (opts.verbose) {
978
- process.stderr.write(chalk5.dim(`[${language}] Spawning: ${spec.cmd} ${spec.args.join(" ")}
2299
+ process.stderr.write(chalk9.dim(`[${language}] Spawning: ${spec.cmd} ${spec.args.join(" ")}
979
2300
  `));
980
2301
  }
981
2302
  const runtimeReq = NATIVE_RUNTIME_REQUIREMENTS[language];
982
2303
  const usesPathCommand = runtimeReq && spec.cmd === runtimeReq.command;
983
2304
  if (usesPathCommand && !commandExistsOnPath(spec.cmd)) {
984
- resolve6({
2305
+ resolve9({
985
2306
  language,
986
2307
  facts: [],
987
2308
  errors: [
@@ -1016,7 +2337,7 @@ async function runNativeWorker(rootDir, language, opts) {
1016
2337
  const trimmed = line.trim();
1017
2338
  if (!trimmed) continue;
1018
2339
  if (opts.verbose) {
1019
- process.stderr.write(chalk5.dim(`[${language}] ${trimmed}
2340
+ process.stderr.write(chalk9.dim(`[${language}] ${trimmed}
1020
2341
  `));
1021
2342
  }
1022
2343
  if (trimmed.startsWith("[error]")) {
@@ -1028,9 +2349,9 @@ async function runNativeWorker(rootDir, language, opts) {
1028
2349
  clearTimeout(timer);
1029
2350
  if (stdoutBuf.trim()) facts.push(stdoutBuf.trim());
1030
2351
  if (killed) {
1031
- resolve6({ language, facts, errors: ["Worker killed: timeout exceeded"], exitCode: EXIT_TIMEOUT });
2352
+ resolve9({ language, facts, errors: ["Worker killed: timeout exceeded"], exitCode: EXIT_TIMEOUT });
1032
2353
  } else {
1033
- resolve6({ language, facts, errors, exitCode: code ?? 0 });
2354
+ resolve9({ language, facts, errors, exitCode: code ?? 0 });
1034
2355
  }
1035
2356
  });
1036
2357
  child.on("error", (err) => {
@@ -1039,21 +2360,21 @@ async function runNativeWorker(rootDir, language, opts) {
1039
2360
  errors.push(
1040
2361
  isNotFound ? `[error] '${spec.cmd}' not found. ${buildNativeInstallHint(language)}` : `[error] Failed to spawn native worker: ${err.message}`
1041
2362
  );
1042
- resolve6({ language, facts, errors, exitCode: EXIT_PARSE_FAILURE });
2363
+ resolve9({ language, facts, errors, exitCode: EXIT_PARSE_FAILURE });
1043
2364
  });
1044
2365
  });
1045
2366
  }
1046
2367
  async function pushFacts(facts, dsn, verbose) {
1047
- const parsed = parseDsn(dsn);
2368
+ const parsed = parseDsn2(dsn);
1048
2369
  if (!parsed) {
1049
- process.stderr.write(chalk5.red("Invalid DSN format.\n"));
1050
- process.stderr.write(chalk5.dim("Expected: vibgrate+https://<key_id>:<secret>@<host>/<workspace_id>\n"));
2370
+ process.stderr.write(chalk9.red("Invalid DSN format.\n"));
2371
+ process.stderr.write(chalk9.dim("Expected: vibgrate+https://<key_id>:<secret>@<host>/<workspace_id>\n"));
1051
2372
  return false;
1052
2373
  }
1053
2374
  const body = facts.join("\n") + "\n";
1054
2375
  const url = `${parsed.scheme}://${parsed.host}/v1/ingest/hcs`;
1055
2376
  if (verbose) {
1056
- process.stderr.write(chalk5.dim(`Pushing ${facts.length} facts to ${parsed.host}...
2377
+ process.stderr.write(chalk9.dim(`Pushing ${facts.length} facts to ${parsed.host}...
1057
2378
  `));
1058
2379
  }
1059
2380
  try {
@@ -1071,17 +2392,17 @@ async function pushFacts(facts, dsn, verbose) {
1071
2392
  });
1072
2393
  if (!response.ok) {
1073
2394
  const text = await response.text().catch(() => "");
1074
- process.stderr.write(chalk5.red(`Push failed: HTTP ${response.status} ${text}
2395
+ process.stderr.write(chalk9.red(`Push failed: HTTP ${response.status} ${text}
1075
2396
  `));
1076
2397
  return false;
1077
2398
  }
1078
2399
  if (verbose) {
1079
- process.stderr.write(chalk5.green(`\u2714 Pushed ${facts.length} facts successfully
2400
+ process.stderr.write(chalk9.green(`\u2714 Pushed ${facts.length} facts successfully
1080
2401
  `));
1081
2402
  }
1082
2403
  return true;
1083
2404
  } catch (err) {
1084
- process.stderr.write(chalk5.red(`Push failed: ${err instanceof Error ? err.message : String(err)}
2405
+ process.stderr.write(chalk9.red(`Push failed: ${err instanceof Error ? err.message : String(err)}
1085
2406
  `));
1086
2407
  return false;
1087
2408
  }
@@ -1148,21 +2469,21 @@ var ProgressTracker = class {
1148
2469
  const parts = [];
1149
2470
  for (const [lang, st] of this.langStatus) {
1150
2471
  if (st.done) {
1151
- parts.push(chalk5.green(`${lang} \u2713`));
2472
+ parts.push(chalk9.green(`${lang} \u2713`));
1152
2473
  } else if (st.phase === "waiting") {
1153
- parts.push(chalk5.dim(`${lang} \xB7`));
2474
+ parts.push(chalk9.dim(`${lang} \xB7`));
1154
2475
  } else if (st.phase === "discovering") {
1155
- parts.push(chalk5.yellow(`${lang} \u2026`));
2476
+ parts.push(chalk9.yellow(`${lang} \u2026`));
1156
2477
  } else if (st.phase === "scanning" && st.fileCount > 0) {
1157
2478
  const pct = Math.round(st.fileIndex / st.fileCount * 100);
1158
- parts.push(chalk5.cyan(`${lang} ${st.fileIndex}/${st.fileCount} ${pct}%`));
2479
+ parts.push(chalk9.cyan(`${lang} ${st.fileIndex}/${st.fileCount} ${pct}%`));
1159
2480
  } else if (st.phase === "extracting") {
1160
- parts.push(chalk5.cyan(`${lang} extracting`));
2481
+ parts.push(chalk9.cyan(`${lang} extracting`));
1161
2482
  } else {
1162
- parts.push(chalk5.dim(`${lang} ${st.phase}`));
2483
+ parts.push(chalk9.dim(`${lang} ${st.phase}`));
1163
2484
  }
1164
2485
  }
1165
- const line = ` ${parts.join(" ")} ${chalk5.dim(`${this.totalFacts} facts`)}`;
2486
+ const line = ` ${parts.join(" ")} ${chalk9.dim(`${this.totalFacts} facts`)}`;
1166
2487
  const padding = Math.max(0, this.lastLineLen - line.length);
1167
2488
  process.stderr.write(`\r${line}${" ".repeat(padding)}`);
1168
2489
  this.lastLineLen = line.length;
@@ -1174,10 +2495,10 @@ var ProgressTracker = class {
1174
2495
  }
1175
2496
  }
1176
2497
  };
1177
- var extractCommand = new Command5("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("--verbose", "Print worker stderr and summary statistics").action(async (targetPath, opts) => {
1178
- const rootDir = path6.resolve(targetPath);
2498
+ 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("--verbose", "Print worker stderr and summary statistics").action(async (targetPath, opts) => {
2499
+ const rootDir = path9.resolve(targetPath);
1179
2500
  if (!await pathExists(rootDir)) {
1180
- process.stderr.write(chalk5.red(`Path does not exist: ${rootDir}
2501
+ process.stderr.write(chalk9.red(`Path does not exist: ${rootDir}
1181
2502
  `));
1182
2503
  process.exit(EXIT_USAGE_ERROR);
1183
2504
  }
@@ -1185,47 +2506,47 @@ var extractCommand = new Command5("extract").description("Analyze source code an
1185
2506
  try {
1186
2507
  stat3 = await fs2.stat(rootDir);
1187
2508
  } catch {
1188
- process.stderr.write(chalk5.red(`Cannot read path: ${rootDir}
2509
+ process.stderr.write(chalk9.red(`Cannot read path: ${rootDir}
1189
2510
  `));
1190
2511
  process.exit(EXIT_USAGE_ERROR);
1191
2512
  }
1192
2513
  if (!stat3.isDirectory()) {
1193
- process.stderr.write(chalk5.red(`Path must be a directory: ${rootDir}
2514
+ process.stderr.write(chalk9.red(`Path must be a directory: ${rootDir}
1194
2515
  `));
1195
2516
  process.exit(EXIT_USAGE_ERROR);
1196
2517
  }
1197
2518
  const dsn = opts.dsn || process.env.VIBGRATE_DSN;
1198
2519
  if (opts.push && !dsn) {
1199
- process.stderr.write(chalk5.red("--push requires --dsn or VIBGRATE_DSN environment variable\n"));
2520
+ process.stderr.write(chalk9.red("--push requires --dsn or VIBGRATE_DSN environment variable\n"));
1200
2521
  process.exit(EXIT_USAGE_ERROR);
1201
2522
  }
1202
- if (dsn && !parseDsn(dsn)) {
1203
- process.stderr.write(chalk5.red("Invalid DSN format.\n"));
1204
- process.stderr.write(chalk5.dim("Expected: vibgrate+https://<key_id>:<secret>@<host>/<workspace_id>\n"));
2523
+ if (dsn && !parseDsn2(dsn)) {
2524
+ process.stderr.write(chalk9.red("Invalid DSN format.\n"));
2525
+ process.stderr.write(chalk9.dim("Expected: vibgrate+https://<key_id>:<secret>@<host>/<workspace_id>\n"));
1205
2526
  process.exit(EXIT_USAGE_ERROR);
1206
2527
  }
1207
2528
  const concurrency = opts.concurrency ? parseInt(opts.concurrency, 10) : os2.cpus().length;
1208
2529
  if (isNaN(concurrency) || concurrency < 1) {
1209
- process.stderr.write(chalk5.red("--concurrency must be >= 1\n"));
2530
+ process.stderr.write(chalk9.red("--concurrency must be >= 1\n"));
1210
2531
  process.exit(EXIT_USAGE_ERROR);
1211
2532
  }
1212
2533
  const timeoutMins = parseInt(opts.timeoutMins, 10);
1213
2534
  if (isNaN(timeoutMins) || timeoutMins < 1) {
1214
- process.stderr.write(chalk5.red("--timeout-mins must be >= 1\n"));
2535
+ process.stderr.write(chalk9.red("--timeout-mins must be >= 1\n"));
1215
2536
  process.exit(EXIT_USAGE_ERROR);
1216
2537
  }
1217
2538
  const timeoutMs = timeoutMins * 60 * 1e3;
1218
2539
  if (opts.out) {
1219
- const outDir = path6.dirname(path6.resolve(opts.out));
2540
+ const outDir = path9.dirname(path9.resolve(opts.out));
1220
2541
  if (!await pathExists(outDir)) {
1221
- process.stderr.write(chalk5.red(`Output directory does not exist: ${outDir}
2542
+ process.stderr.write(chalk9.red(`Output directory does not exist: ${outDir}
1222
2543
  `));
1223
2544
  process.exit(EXIT_USAGE_ERROR);
1224
2545
  }
1225
2546
  try {
1226
- const outStat = await fs2.stat(path6.resolve(opts.out));
2547
+ const outStat = await fs2.stat(path9.resolve(opts.out));
1227
2548
  if (outStat.isDirectory()) {
1228
- process.stderr.write(chalk5.red(`--out cannot be a directory: ${opts.out}
2549
+ process.stderr.write(chalk9.red(`--out cannot be a directory: ${opts.out}
1229
2550
  `));
1230
2551
  process.exit(EXIT_USAGE_ERROR);
1231
2552
  }
@@ -1234,16 +2555,16 @@ var extractCommand = new Command5("extract").description("Analyze source code an
1234
2555
  }
1235
2556
  let feedbackLines;
1236
2557
  if (opts.feedback) {
1237
- const feedbackPath = path6.resolve(opts.feedback);
2558
+ const feedbackPath = path9.resolve(opts.feedback);
1238
2559
  if (!await pathExists(feedbackPath)) {
1239
- process.stderr.write(chalk5.red(`Feedback file not found: ${feedbackPath}
2560
+ process.stderr.write(chalk9.red(`Feedback file not found: ${feedbackPath}
1240
2561
  `));
1241
2562
  process.exit(EXIT_USAGE_ERROR);
1242
2563
  }
1243
2564
  try {
1244
2565
  feedbackLines = await loadFeedback(feedbackPath);
1245
2566
  } catch (err) {
1246
- process.stderr.write(chalk5.red(`Invalid feedback file: ${err instanceof Error ? err.message : String(err)}
2567
+ process.stderr.write(chalk9.red(`Invalid feedback file: ${err instanceof Error ? err.message : String(err)}
1247
2568
  `));
1248
2569
  process.exit(EXIT_USAGE_ERROR);
1249
2570
  }
@@ -1253,9 +2574,9 @@ var extractCommand = new Command5("extract").description("Analyze source code an
1253
2574
  targetLanguages = opts.language.split(",").map((l) => normalizeLanguageToken(l)).filter(Boolean);
1254
2575
  for (const lang of targetLanguages) {
1255
2576
  if (!SUPPORTED_LANGUAGES.has(lang)) {
1256
- process.stderr.write(chalk5.red(`Unknown language: "${lang}"
2577
+ process.stderr.write(chalk9.red(`Unknown language: "${lang}"
1257
2578
  `));
1258
- process.stderr.write(chalk5.dim(`Supported: ${[...SUPPORTED_LANGUAGES].sort().join(", ")}
2579
+ process.stderr.write(chalk9.dim(`Supported: ${[...SUPPORTED_LANGUAGES].sort().join(", ")}
1259
2580
  `));
1260
2581
  process.exit(EXIT_USAGE_ERROR);
1261
2582
  }
@@ -1263,14 +2584,14 @@ var extractCommand = new Command5("extract").description("Analyze source code an
1263
2584
  } else {
1264
2585
  const detected = await detectLanguages(rootDir, opts.includeTests ?? false);
1265
2586
  if (detected.length === 0) {
1266
- process.stderr.write(chalk5.yellow("No supported source files detected.\n"));
2587
+ process.stderr.write(chalk9.yellow("No supported source files detected.\n"));
1267
2588
  process.exit(EXIT_SUCCESS);
1268
2589
  }
1269
2590
  targetLanguages = detected.map((d) => d.language);
1270
2591
  if (opts.verbose) {
1271
- process.stderr.write(chalk5.dim("Detected languages:\n"));
2592
+ process.stderr.write(chalk9.dim("Detected languages:\n"));
1272
2593
  for (const d of detected) {
1273
- process.stderr.write(chalk5.dim(` ${d.language}: ${d.fileCount} files
2594
+ process.stderr.write(chalk9.dim(` ${d.language}: ${d.fileCount} files
1274
2595
  `));
1275
2596
  }
1276
2597
  }
@@ -1278,24 +2599,25 @@ var extractCommand = new Command5("extract").description("Analyze source code an
1278
2599
  const runnableLanguages = targetLanguages.filter((l) => ALL_WORKER_LANGS.has(l));
1279
2600
  const unknownWorkerLangs = targetLanguages.filter((l) => !ALL_WORKER_LANGS.has(l));
1280
2601
  if (unknownWorkerLangs.length > 0 && opts.verbose) {
1281
- process.stderr.write(chalk5.dim(
2602
+ process.stderr.write(chalk9.dim(
1282
2603
  `No worker registered for: ${unknownWorkerLangs.join(", ")} \u2014 skipping.
1283
2604
  `
1284
2605
  ));
1285
2606
  }
1286
2607
  if (runnableLanguages.length === 0) {
1287
- process.stderr.write(chalk5.yellow("No languages with available HCS workers found.\n"));
2608
+ process.stderr.write(chalk9.yellow("No languages with available HCS workers found.\n"));
1288
2609
  process.exit(EXIT_SUCCESS);
1289
2610
  }
1290
2611
  const startTime = Date.now();
1291
2612
  const globalDeadline = startTime + timeoutMs;
1292
2613
  process.stderr.write(
1293
- chalk5.bold(`Extracting HCS facts from ${rootDir}
1294
- `) + chalk5.dim(`Languages: ${runnableLanguages.join(", ")} Concurrency: ${concurrency} Timeout: ${timeoutMins}m
2614
+ chalk9.bold(`Extracting HCS facts from ${rootDir}
2615
+ `) + chalk9.dim(`Languages: ${runnableLanguages.join(", ")} Concurrency: ${concurrency} Timeout: ${timeoutMins}m
1295
2616
  `)
1296
2617
  );
1297
2618
  const progress = new ProgressTracker(runnableLanguages);
1298
2619
  const sem = new Semaphore(concurrency);
2620
+ const allPreamble = [];
1299
2621
  const allFacts = [];
1300
2622
  const allErrors = [];
1301
2623
  let hasSchemaFailure = false;
@@ -1321,23 +2643,27 @@ var extractCommand = new Command5("extract").description("Analyze source code an
1321
2643
  if (result.exitCode === EXIT_TIMEOUT) {
1322
2644
  hasTimeout = true;
1323
2645
  }
1324
- const decompressedFacts = decompressHcsLines(result.facts);
2646
+ const { preamble, facts } = splitHcsOutput(result.facts);
2647
+ allPreamble.push(...preamble);
1325
2648
  let langFactCount = 0;
1326
- for (const line of decompressedFacts) {
2649
+ for (const line of [...preamble, ...facts]) {
1327
2650
  const validation = validateFactLine(line);
1328
- if (validation.valid) {
1329
- allFacts.push(line);
1330
- langFactCount++;
1331
- progress.onFact();
1332
- } else {
2651
+ if (!validation.valid) {
1333
2652
  hasSchemaFailure = true;
1334
2653
  allErrors.push(`[${language}] Schema validation: ${validation.error}`);
1335
2654
  if (opts.verbose) {
1336
- process.stderr.write(chalk5.red(`[${language}] Invalid fact: ${validation.error}
2655
+ process.stderr.write(chalk9.red(`[${language}] Invalid fact: ${validation.error}
1337
2656
  `));
1338
2657
  }
1339
2658
  }
1340
2659
  }
2660
+ for (const line of facts) {
2661
+ if (validateFactLine(line).valid) {
2662
+ allFacts.push(line);
2663
+ langFactCount++;
2664
+ progress.onFact();
2665
+ }
2666
+ }
1341
2667
  progress.setLanguageFactCount(language, langFactCount);
1342
2668
  if (result.errors.length > 0) {
1343
2669
  allErrors.push(...result.errors.map((e) => `[${language}] ${e}`));
@@ -1351,64 +2677,65 @@ var extractCommand = new Command5("extract").description("Analyze source code an
1351
2677
  const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
1352
2678
  progress.finish(elapsed);
1353
2679
  allFacts.sort();
1354
- const ndjsonOutput = allFacts.map((f) => f).join("\n") + (allFacts.length > 0 ? "\n" : "");
2680
+ const allLines = [...allPreamble, ...allFacts];
2681
+ const ndjsonOutput = allLines.join("\n") + (allLines.length > 0 ? "\n" : "");
1355
2682
  if (opts.out) {
1356
- const outPath = path6.resolve(opts.out);
2683
+ const outPath = path9.resolve(opts.out);
1357
2684
  await fs2.writeFile(outPath, ndjsonOutput, "utf-8");
1358
- process.stderr.write(chalk5.green(`\u2714 Wrote ${allFacts.length} facts to ${outPath}
2685
+ process.stderr.write(chalk9.green(`\u2714 Wrote ${allFacts.length} facts to ${outPath}
1359
2686
  `));
1360
2687
  } else {
1361
2688
  process.stdout.write(ndjsonOutput);
1362
2689
  }
1363
2690
  if (opts.push && dsn && allFacts.length > 0) {
1364
- const pushOk = await pushFacts(allFacts, dsn, opts.verbose ?? false);
2691
+ const pushOk = await pushFacts(allLines, dsn, opts.verbose ?? false);
1365
2692
  if (!pushOk) {
1366
2693
  process.exit(EXIT_PUSH_FAILURE);
1367
2694
  }
1368
2695
  }
1369
2696
  if (opts.verbose) {
1370
- process.stderr.write(chalk5.dim("\n\u2500\u2500 Summary \u2500\u2500\n"));
1371
- process.stderr.write(chalk5.dim(` Facts emitted : ${allFacts.length}
2697
+ process.stderr.write(chalk9.dim("\n\u2500\u2500 Summary \u2500\u2500\n"));
2698
+ process.stderr.write(chalk9.dim(` Facts emitted : ${allFacts.length}
1372
2699
  `));
1373
- process.stderr.write(chalk5.dim(` Languages : ${runnableLanguages.join(", ")}
2700
+ process.stderr.write(chalk9.dim(` Languages : ${runnableLanguages.join(", ")}
1374
2701
  `));
1375
- process.stderr.write(chalk5.dim(` Elapsed : ${elapsed}s
2702
+ process.stderr.write(chalk9.dim(` Elapsed : ${elapsed}s
1376
2703
  `));
1377
2704
  if (allErrors.length > 0) {
1378
- process.stderr.write(chalk5.dim(` Errors : ${allErrors.length}
2705
+ process.stderr.write(chalk9.dim(` Errors : ${allErrors.length}
1379
2706
  `));
1380
2707
  for (const err of allErrors.slice(0, 10)) {
1381
- process.stderr.write(chalk5.dim(` ${err}
2708
+ process.stderr.write(chalk9.dim(` ${err}
1382
2709
  `));
1383
2710
  }
1384
2711
  if (allErrors.length > 10) {
1385
- process.stderr.write(chalk5.dim(` ... and ${allErrors.length - 10} more
2712
+ process.stderr.write(chalk9.dim(` ... and ${allErrors.length - 10} more
1386
2713
  `));
1387
2714
  }
1388
2715
  }
1389
- process.stderr.write(chalk5.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n"));
2716
+ process.stderr.write(chalk9.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n"));
1390
2717
  }
1391
2718
  if (hasTimeout) {
1392
- process.stderr.write(chalk5.red(`Timeout exceeded (${timeoutMins} minutes)
2719
+ process.stderr.write(chalk9.red(`Timeout exceeded (${timeoutMins} minutes)
1393
2720
  `));
1394
2721
  process.exit(EXIT_TIMEOUT);
1395
2722
  }
1396
2723
  if (hasSchemaFailure) {
1397
- process.stderr.write(chalk5.red("Fact schema validation failures detected\n"));
2724
+ process.stderr.write(chalk9.red("Fact schema validation failures detected\n"));
1398
2725
  process.exit(EXIT_SCHEMA_FAILURE);
1399
2726
  }
1400
2727
  if (hasParseFailure) {
1401
- process.stderr.write(chalk5.red("Parsing failures detected\n"));
2728
+ process.stderr.write(chalk9.red("Parsing failures detected\n"));
1402
2729
  process.exit(EXIT_PARSE_FAILURE);
1403
2730
  }
1404
2731
  });
1405
2732
 
1406
2733
  // src/commands/diff.ts
1407
- import * as path7 from "path";
2734
+ import * as path10 from "path";
1408
2735
  import * as fs3 from "fs/promises";
1409
- import * as crypto from "crypto";
1410
- import { Command as Command6 } from "commander";
1411
- import chalk6 from "chalk";
2736
+ import * as crypto3 from "crypto";
2737
+ import { Command as Command9 } from "commander";
2738
+ import chalk10 from "chalk";
1412
2739
  var EXIT_INVALID_PATH = 2;
1413
2740
  var EXIT_USAGE_ERROR2 = 5;
1414
2741
  var VALID_FORMATS = /* @__PURE__ */ new Set(["ndjson", "json", "patch"]);
@@ -1430,8 +2757,8 @@ async function discoverFiles(root, includeGlobs, excludeGlobs) {
1430
2757
  }
1431
2758
  for (const entry of entries) {
1432
2759
  if (entry.name.startsWith(".") && SKIP_DIRS2.has(entry.name)) continue;
1433
- const absPath = path7.join(dir, entry.name);
1434
- const relPath = path7.join(relBase, entry.name);
2760
+ const absPath = path10.join(dir, entry.name);
2761
+ const relPath = path10.join(relBase, entry.name);
1435
2762
  if (entry.isDirectory()) {
1436
2763
  if (!SKIP_DIRS2.has(entry.name)) {
1437
2764
  await walk(absPath, relPath);
@@ -1602,7 +2929,7 @@ function lcsRatio(a, b) {
1602
2929
  return 2 * lcsLen / (n + m);
1603
2930
  }
1604
2931
  function generateDeltaId(kind, filePath) {
1605
- const hash = crypto.createHash("sha256").update(`${kind}:${filePath}`).digest("hex").substring(0, 16);
2932
+ const hash = crypto3.createHash("sha256").update(`${kind}:${filePath}`).digest("hex").substring(0, 16);
1606
2933
  return `hcs:delta:${hash}`;
1607
2934
  }
1608
2935
  function formatNdjson(deltas) {
@@ -1614,8 +2941,8 @@ function formatJson(deltas) {
1614
2941
  function formatPatch(deltas, originalPath, generatedPath) {
1615
2942
  const lines = [];
1616
2943
  for (const delta of deltas) {
1617
- const aFile = delta.originalPath ? path7.join("a", delta.originalPath) : "/dev/null";
1618
- const bFile = delta.generatedPath ? path7.join("b", delta.generatedPath) : "/dev/null";
2944
+ const aFile = delta.originalPath ? path10.join("a", delta.originalPath) : "/dev/null";
2945
+ const bFile = delta.generatedPath ? path10.join("b", delta.generatedPath) : "/dev/null";
1619
2946
  if (delta.changeKind === "renamed" && delta.originalPath && delta.generatedPath) {
1620
2947
  lines.push(`diff --vibgrate ${aFile} ${bFile}`);
1621
2948
  lines.push(`similarity index ${Math.round((delta.similarity ?? 0) * 100)}%`);
@@ -1636,12 +2963,12 @@ function formatPatch(deltas, originalPath, generatedPath) {
1636
2963
  }
1637
2964
  return lines.join("\n");
1638
2965
  }
1639
- var diffCommand = new Command6("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) => {
1640
- const origDir = path7.resolve(originalPath);
1641
- const genDir = path7.resolve(generatedPath);
2966
+ 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) => {
2967
+ const origDir = path10.resolve(originalPath);
2968
+ const genDir = path10.resolve(generatedPath);
1642
2969
  for (const [label, dir] of [["original_path", origDir], ["generated_path", genDir]]) {
1643
2970
  if (!await pathExists(dir)) {
1644
- process.stderr.write(chalk6.red(`${label} does not exist: ${dir}
2971
+ process.stderr.write(chalk10.red(`${label} does not exist: ${dir}
1645
2972
  `));
1646
2973
  process.exit(EXIT_INVALID_PATH);
1647
2974
  }
@@ -1649,25 +2976,25 @@ var diffCommand = new Command6("diff").description("Compare two directory trees
1649
2976
  try {
1650
2977
  stat3 = await fs3.stat(dir);
1651
2978
  } catch {
1652
- process.stderr.write(chalk6.red(`Cannot read ${label}: ${dir}
2979
+ process.stderr.write(chalk10.red(`Cannot read ${label}: ${dir}
1653
2980
  `));
1654
2981
  process.exit(EXIT_INVALID_PATH);
1655
2982
  }
1656
2983
  if (!stat3.isDirectory()) {
1657
- process.stderr.write(chalk6.red(`${label} must be a directory: ${dir}
2984
+ process.stderr.write(chalk10.red(`${label} must be a directory: ${dir}
1658
2985
  `));
1659
2986
  process.exit(EXIT_INVALID_PATH);
1660
2987
  }
1661
2988
  }
1662
2989
  const format = opts.format.toLowerCase();
1663
2990
  if (!VALID_FORMATS.has(format)) {
1664
- process.stderr.write(chalk6.red(`Invalid format: "${opts.format}". Allowed: ndjson, json, patch
2991
+ process.stderr.write(chalk10.red(`Invalid format: "${opts.format}". Allowed: ndjson, json, patch
1665
2992
  `));
1666
2993
  process.exit(EXIT_USAGE_ERROR2);
1667
2994
  }
1668
2995
  const contextLines = parseInt(opts.context, 10);
1669
2996
  if (isNaN(contextLines) || contextLines < 0) {
1670
- process.stderr.write(chalk6.red("--context must be >= 0\n"));
2997
+ process.stderr.write(chalk10.red("--context must be >= 0\n"));
1671
2998
  process.exit(EXIT_USAGE_ERROR2);
1672
2999
  }
1673
3000
  const includeGlobs = opts.include ? opts.include.split(",").map((g) => g.trim()).filter(Boolean) : void 0;
@@ -1679,9 +3006,9 @@ var diffCommand = new Command6("diff").description("Compare two directory trees
1679
3006
  const origMap = new Map(origFiles.map((f) => [f.relPath, f]));
1680
3007
  const genMap = new Map(genFiles.map((f) => [f.relPath, f]));
1681
3008
  if (opts.verbose) {
1682
- process.stderr.write(chalk6.dim(`Original: ${origFiles.length} files
3009
+ process.stderr.write(chalk10.dim(`Original: ${origFiles.length} files
1683
3010
  `));
1684
- process.stderr.write(chalk6.dim(`Generated: ${genFiles.length} files
3011
+ process.stderr.write(chalk10.dim(`Generated: ${genFiles.length} files
1685
3012
  `));
1686
3013
  }
1687
3014
  const deltas = [];
@@ -1715,7 +3042,7 @@ var diffCommand = new Command6("diff").description("Compare two directory trees
1715
3042
  }
1716
3043
  deltas.push(delta);
1717
3044
  if (opts.verbose) {
1718
- process.stderr.write(chalk6.dim(` modified: ${relPath} (+${diffResult.insertions} -${diffResult.deletions})
3045
+ process.stderr.write(chalk10.dim(` modified: ${relPath} (+${diffResult.insertions} -${diffResult.deletions})
1719
3046
  `));
1720
3047
  }
1721
3048
  }
@@ -1746,7 +3073,7 @@ var diffCommand = new Command6("diff").description("Compare two directory trees
1746
3073
  const pairs = [];
1747
3074
  for (const [origPath, origContent] of origContents) {
1748
3075
  for (const [genPath, genContent] of genContents) {
1749
- if (path7.extname(origPath) !== path7.extname(genPath)) continue;
3076
+ if (path10.extname(origPath) !== path10.extname(genPath)) continue;
1750
3077
  const similarity = computeSimilarity(origContent, genContent);
1751
3078
  if (similarity >= RENAME_THRESHOLD) {
1752
3079
  pairs.push({ origPath, genPath, similarity });
@@ -1779,7 +3106,7 @@ var diffCommand = new Command6("diff").description("Compare two directory trees
1779
3106
  }
1780
3107
  deltas.push(delta);
1781
3108
  if (opts.verbose) {
1782
- process.stderr.write(chalk6.dim(
3109
+ process.stderr.write(chalk10.dim(
1783
3110
  ` renamed: ${pair.origPath} \u2192 ${pair.genPath} (${(pair.similarity * 100).toFixed(0)}% similar)
1784
3111
  `
1785
3112
  ));
@@ -1804,7 +3131,7 @@ var diffCommand = new Command6("diff").description("Compare two directory trees
1804
3131
  }
1805
3132
  deltas.push(delta);
1806
3133
  if (opts.verbose) {
1807
- process.stderr.write(chalk6.dim(` removed: ${file.relPath}
3134
+ process.stderr.write(chalk10.dim(` removed: ${file.relPath}
1808
3135
  `));
1809
3136
  }
1810
3137
  }
@@ -1826,7 +3153,7 @@ var diffCommand = new Command6("diff").description("Compare two directory trees
1826
3153
  }
1827
3154
  deltas.push(delta);
1828
3155
  if (opts.verbose) {
1829
- process.stderr.write(chalk6.dim(` added: ${file.relPath}
3156
+ process.stderr.write(chalk10.dim(` added: ${file.relPath}
1830
3157
  `));
1831
3158
  }
1832
3159
  }
@@ -1847,16 +3174,16 @@ var diffCommand = new Command6("diff").description("Compare two directory trees
1847
3174
  output = formatPatch(deltas, originalPath, generatedPath);
1848
3175
  break;
1849
3176
  }
1850
- const outPath = path7.resolve(opts.out);
3177
+ const outPath = path10.resolve(opts.out);
1851
3178
  await fs3.writeFile(outPath, output, "utf-8");
1852
3179
  const added = deltas.filter((d) => d.changeKind === "added").length;
1853
3180
  const removed = deltas.filter((d) => d.changeKind === "removed").length;
1854
3181
  const modified = deltas.filter((d) => d.changeKind === "modified").length;
1855
3182
  const renamed = deltas.filter((d) => d.changeKind === "renamed").length;
1856
3183
  process.stderr.write(
1857
- chalk6.green(`\u2714 `) + `${deltas.length} changes: ` + chalk6.green(`+${added} added`) + ", " + chalk6.red(`-${removed} removed`) + ", " + chalk6.yellow(`~${modified} modified`) + ", " + chalk6.blue(`\u2192${renamed} renamed`) + "\n"
3184
+ chalk10.green(`\u2714 `) + `${deltas.length} changes: ` + chalk10.green(`+${added} added`) + ", " + chalk10.red(`-${removed} removed`) + ", " + chalk10.yellow(`~${modified} modified`) + ", " + chalk10.blue(`\u2192${renamed} renamed`) + "\n"
1858
3185
  );
1859
- process.stderr.write(chalk6.dim(` Written to ${outPath}
3186
+ process.stderr.write(chalk10.dim(` Written to ${outPath}
1860
3187
  `));
1861
3188
  if (opts.verbose && opts.stats) {
1862
3189
  let totalIns = 0;
@@ -1867,193 +3194,193 @@ var diffCommand = new Command6("diff").description("Compare two directory trees
1867
3194
  totalDel += d.stats.deletions;
1868
3195
  }
1869
3196
  }
1870
- process.stderr.write(chalk6.dim(` Total: +${totalIns} insertions, -${totalDel} deletions
3197
+ process.stderr.write(chalk10.dim(` Total: +${totalIns} insertions, -${totalDel} deletions
1871
3198
  `));
1872
3199
  }
1873
3200
  });
1874
3201
 
1875
3202
  // src/commands/help.ts
1876
- import { Command as Command7 } from "commander";
1877
- import chalk7 from "chalk";
3203
+ import { Command as Command10 } from "commander";
3204
+ import chalk11 from "chalk";
1878
3205
  var HELP_URL = "https://vibgrate.com/help";
1879
3206
  function printFooter() {
1880
3207
  console.log("");
1881
- console.log(chalk7.dim(`See ${HELP_URL} for more guidance`));
3208
+ console.log(chalk11.dim(`See ${HELP_URL} for more guidance`));
1882
3209
  }
1883
3210
  var detailedHelp = {
1884
3211
  scan: () => {
1885
3212
  console.log("");
1886
- console.log(chalk7.bold.underline("vibgrate scan") + chalk7.dim(" \u2014 Scan a project for upgrade drift"));
3213
+ console.log(chalk11.bold.underline("vibgrate scan") + chalk11.dim(" \u2014 Scan a project for upgrade drift"));
1887
3214
  console.log("");
1888
- console.log(chalk7.bold("Usage:"));
3215
+ console.log(chalk11.bold("Usage:"));
1889
3216
  console.log(" vibgrate scan [path] [options]");
1890
3217
  console.log("");
1891
- console.log(chalk7.bold("Arguments:"));
1892
- console.log(` ${chalk7.cyan("[path]")} Path to scan (default: current directory)`);
3218
+ console.log(chalk11.bold("Arguments:"));
3219
+ console.log(` ${chalk11.cyan("[path]")} Path to scan (default: current directory)`);
1893
3220
  console.log("");
1894
- console.log(chalk7.bold("Output options:"));
1895
- console.log(` ${chalk7.cyan("--format <format>")} Output format: ${chalk7.white("text")} | json | sarif | md (default: text)`);
1896
- console.log(` ${chalk7.cyan("--out <file>")} Write output to a file instead of stdout`);
3221
+ console.log(chalk11.bold("Output options:"));
3222
+ console.log(` ${chalk11.cyan("--format <format>")} Output format: ${chalk11.white("text")} | json | sarif | md (default: text)`);
3223
+ console.log(` ${chalk11.cyan("--out <file>")} Write output to a file instead of stdout`);
1897
3224
  console.log("");
1898
- console.log(chalk7.bold("Baseline & gating:"));
1899
- console.log(` ${chalk7.cyan("--baseline <file>")} Compare results against a saved baseline`);
1900
- console.log(` ${chalk7.cyan("--drift-budget <score>")} Fail if drift score exceeds this value (0\u2013100)`);
1901
- console.log(` ${chalk7.cyan("--drift-worsening <percent>")} Fail if drift worsens by more than % since baseline`);
1902
- console.log(` ${chalk7.cyan("--fail-on <level>")} Fail exit code on warn or error findings`);
3225
+ console.log(chalk11.bold("Baseline & gating:"));
3226
+ console.log(` ${chalk11.cyan("--baseline <file>")} Compare results against a saved baseline`);
3227
+ console.log(` ${chalk11.cyan("--drift-budget <score>")} Fail if drift score exceeds this value (0\u2013100)`);
3228
+ console.log(` ${chalk11.cyan("--drift-worsening <percent>")} Fail if drift worsens by more than % since baseline`);
3229
+ console.log(` ${chalk11.cyan("--fail-on <level>")} Fail exit code on warn or error findings`);
1903
3230
  console.log("");
1904
- console.log(chalk7.bold("Performance:"));
1905
- console.log(` ${chalk7.cyan("--concurrency <n>")} Max concurrent registry calls (default: 8)`);
1906
- console.log(` ${chalk7.cyan("--changed-only")} Only scan files changed since last git commit`);
3231
+ console.log(chalk11.bold("Performance:"));
3232
+ console.log(` ${chalk11.cyan("--concurrency <n>")} Max concurrent registry calls (default: 8)`);
3233
+ console.log(` ${chalk11.cyan("--changed-only")} Only scan files changed since last git commit`);
1907
3234
  console.log("");
1908
- console.log(chalk7.bold("Privacy & offline:"));
1909
- console.log(` ${chalk7.cyan("--offline")} Run without any network calls; skip result upload`);
1910
- console.log(` ${chalk7.cyan("--package-manifest <file>")} Use a local package-version manifest (JSON or ZIP) for offline mode`);
1911
- console.log(` ${chalk7.cyan("--no-local-artifacts")} Do not write .vibgrate JSON artifacts to disk`);
1912
- console.log(` ${chalk7.cyan("--max-privacy")} Strongest privacy mode: minimal scanners + no local artifacts`);
3235
+ console.log(chalk11.bold("Privacy & offline:"));
3236
+ console.log(` ${chalk11.cyan("--offline")} Run without any network calls; skip result upload`);
3237
+ console.log(` ${chalk11.cyan("--package-manifest <file>")} Use a local package-version manifest (JSON or ZIP) for offline mode`);
3238
+ console.log(` ${chalk11.cyan("--no-local-artifacts")} Do not write .vibgrate JSON artifacts to disk`);
3239
+ console.log(` ${chalk11.cyan("--max-privacy")} Strongest privacy mode: minimal scanners + no local artifacts`);
1913
3240
  console.log("");
1914
- console.log(chalk7.bold("Tooling:"));
1915
- console.log(` ${chalk7.cyan("--install-tools")} Auto-install missing security scanners via Homebrew`);
1916
- console.log(` ${chalk7.cyan("--ui-purpose")} Enable UI purpose evidence extraction (slower)`);
3241
+ console.log(chalk11.bold("Tooling:"));
3242
+ console.log(` ${chalk11.cyan("--install-tools")} Auto-install missing security scanners via Homebrew`);
3243
+ console.log(` ${chalk11.cyan("--ui-purpose")} Enable UI purpose evidence extraction (slower)`);
1917
3244
  console.log("");
1918
- console.log(chalk7.bold("Uploading results:"));
1919
- console.log(` ${chalk7.cyan("--push")} Auto-push results to Vibgrate API after scan`);
1920
- console.log(` ${chalk7.cyan("--dsn <dsn>")} DSN token for push (or set VIBGRATE_DSN env var)`);
1921
- console.log(` ${chalk7.cyan("--region <region>")} Data residency region: us | eu (default: us)`);
1922
- console.log(` ${chalk7.cyan("--strict")} Fail if the upload to Vibgrate API fails`);
3245
+ console.log(chalk11.bold("Uploading results:"));
3246
+ console.log(` ${chalk11.cyan("--push")} Auto-push results to Vibgrate API after scan`);
3247
+ console.log(` ${chalk11.cyan("--dsn <dsn>")} DSN token for push (or set VIBGRATE_DSN env var)`);
3248
+ console.log(` ${chalk11.cyan("--region <region>")} Data residency region: us | eu (default: us)`);
3249
+ console.log(` ${chalk11.cyan("--strict")} Fail if the upload to Vibgrate API fails`);
1923
3250
  console.log("");
1924
- console.log(chalk7.bold("Examples:"));
1925
- console.log(` ${chalk7.dim("# Scan the current directory and display a text report")}`);
3251
+ console.log(chalk11.bold("Examples:"));
3252
+ console.log(` ${chalk11.dim("# Scan the current directory and display a text report")}`);
1926
3253
  console.log(" vibgrate scan .");
1927
3254
  console.log("");
1928
- console.log(` ${chalk7.dim("# Scan, fail if drift score > 40, and write SARIF for GitHub Actions")}`);
3255
+ console.log(` ${chalk11.dim("# Scan, fail if drift score > 40, and write SARIF for GitHub Actions")}`);
1929
3256
  console.log(" vibgrate scan . --drift-budget 40 --format sarif --out drift.sarif");
1930
3257
  console.log("");
1931
- console.log(` ${chalk7.dim("# Scan and automatically upload results via a DSN")}`);
3258
+ console.log(` ${chalk11.dim("# Scan and automatically upload results via a DSN")}`);
1932
3259
  console.log(" vibgrate scan . --push --dsn $VIBGRATE_DSN");
1933
3260
  console.log("");
1934
- console.log(` ${chalk7.dim("# Offline scan using a pre-downloaded package manifest")}`);
3261
+ console.log(` ${chalk11.dim("# Offline scan using a pre-downloaded package manifest")}`);
1935
3262
  console.log(" vibgrate scan . --offline --package-manifest ./manifest.zip");
1936
3263
  },
1937
3264
  init: () => {
1938
3265
  console.log("");
1939
- console.log(chalk7.bold.underline("vibgrate init") + chalk7.dim(" \u2014 Initialise vibgrate in a project directory"));
3266
+ console.log(chalk11.bold.underline("vibgrate init") + chalk11.dim(" \u2014 Initialise vibgrate in a project directory"));
1940
3267
  console.log("");
1941
- console.log(chalk7.bold("Usage:"));
3268
+ console.log(chalk11.bold("Usage:"));
1942
3269
  console.log(" vibgrate init [path] [options]");
1943
3270
  console.log("");
1944
- console.log(chalk7.bold("Arguments:"));
1945
- console.log(` ${chalk7.cyan("[path]")} Directory to initialise (default: current directory)`);
3271
+ console.log(chalk11.bold("Arguments:"));
3272
+ console.log(` ${chalk11.cyan("[path]")} Directory to initialise (default: current directory)`);
1946
3273
  console.log("");
1947
- console.log(chalk7.bold("Options:"));
1948
- console.log(` ${chalk7.cyan("--baseline")} Create an initial drift baseline after init`);
1949
- console.log(` ${chalk7.cyan("--yes")} Skip all confirmation prompts`);
3274
+ console.log(chalk11.bold("Options:"));
3275
+ console.log(` ${chalk11.cyan("--baseline")} Create an initial drift baseline after init`);
3276
+ console.log(` ${chalk11.cyan("--yes")} Skip all confirmation prompts`);
1950
3277
  console.log("");
1951
- console.log(chalk7.bold("What it does:"));
3278
+ console.log(chalk11.bold("What it does:"));
1952
3279
  console.log(" \u2022 Creates a .vibgrate/ directory");
1953
3280
  console.log(" \u2022 Writes a vibgrate.config.ts starter config");
1954
3281
  console.log(" \u2022 Optionally runs an initial baseline scan (--baseline)");
1955
3282
  console.log("");
1956
- console.log(chalk7.bold("Examples:"));
3283
+ console.log(chalk11.bold("Examples:"));
1957
3284
  console.log(" vibgrate init");
1958
3285
  console.log(" vibgrate init ./my-project --baseline");
1959
3286
  },
1960
3287
  baseline: () => {
1961
3288
  console.log("");
1962
- console.log(chalk7.bold.underline("vibgrate baseline") + chalk7.dim(" \u2014 Save a drift baseline snapshot"));
3289
+ console.log(chalk11.bold.underline("vibgrate baseline") + chalk11.dim(" \u2014 Save a drift baseline snapshot"));
1963
3290
  console.log("");
1964
- console.log(chalk7.bold("Usage:"));
3291
+ console.log(chalk11.bold("Usage:"));
1965
3292
  console.log(" vibgrate baseline [path]");
1966
3293
  console.log("");
1967
- console.log(chalk7.bold("Arguments:"));
1968
- console.log(` ${chalk7.cyan("[path]")} Path to baseline (default: current directory)`);
3294
+ console.log(chalk11.bold("Arguments:"));
3295
+ console.log(` ${chalk11.cyan("[path]")} Path to baseline (default: current directory)`);
1969
3296
  console.log("");
1970
- console.log(chalk7.bold("What it does:"));
3297
+ console.log(chalk11.bold("What it does:"));
1971
3298
  console.log(" Runs a full scan and saves the result as .vibgrate/baseline.json.");
1972
3299
  console.log(" Future scans can compare against this file using --baseline.");
1973
3300
  console.log("");
1974
- console.log(chalk7.bold("Examples:"));
3301
+ console.log(chalk11.bold("Examples:"));
1975
3302
  console.log(" vibgrate baseline .");
1976
3303
  console.log(" vibgrate scan . --baseline .vibgrate/baseline.json --drift-worsening 10");
1977
3304
  },
1978
3305
  report: () => {
1979
3306
  console.log("");
1980
- console.log(chalk7.bold.underline("vibgrate report") + chalk7.dim(" \u2014 Generate a report from a saved scan artifact"));
3307
+ console.log(chalk11.bold.underline("vibgrate report") + chalk11.dim(" \u2014 Generate a report from a saved scan artifact"));
1981
3308
  console.log("");
1982
- console.log(chalk7.bold("Usage:"));
3309
+ console.log(chalk11.bold("Usage:"));
1983
3310
  console.log(" vibgrate report [options]");
1984
3311
  console.log("");
1985
- console.log(chalk7.bold("Options:"));
1986
- console.log(` ${chalk7.cyan("--in <file>")} Input artifact file (default: .vibgrate/scan_result.json)`);
1987
- console.log(` ${chalk7.cyan("--format <format>")} Output format: ${chalk7.white("text")} | md | json (default: text)`);
3312
+ console.log(chalk11.bold("Options:"));
3313
+ console.log(` ${chalk11.cyan("--in <file>")} Input artifact file (default: .vibgrate/scan_result.json)`);
3314
+ console.log(` ${chalk11.cyan("--format <format>")} Output format: ${chalk11.white("text")} | md | json (default: text)`);
1988
3315
  console.log("");
1989
- console.log(chalk7.bold("Examples:"));
3316
+ console.log(chalk11.bold("Examples:"));
1990
3317
  console.log(" vibgrate report");
1991
3318
  console.log(" vibgrate report --format md > DRIFT-REPORT.md");
1992
3319
  console.log(" vibgrate report --in ./ci/scan_result.json --format json");
1993
3320
  },
1994
3321
  sbom: () => {
1995
3322
  console.log("");
1996
- console.log(chalk7.bold.underline("vibgrate sbom") + chalk7.dim(" \u2014 Export a Software Bill of Materials from a scan artifact"));
3323
+ console.log(chalk11.bold.underline("vibgrate sbom") + chalk11.dim(" \u2014 Export a Software Bill of Materials from a scan artifact"));
1997
3324
  console.log("");
1998
- console.log(chalk7.bold("Usage:"));
3325
+ console.log(chalk11.bold("Usage:"));
1999
3326
  console.log(" vibgrate sbom [options]");
2000
3327
  console.log("");
2001
- console.log(chalk7.bold("Options:"));
2002
- console.log(` ${chalk7.cyan("--in <file>")} Input artifact (default: .vibgrate/scan_result.json)`);
2003
- console.log(` ${chalk7.cyan("--format <format>")} SBOM format: ${chalk7.white("cyclonedx")} | spdx (default: cyclonedx)`);
2004
- console.log(` ${chalk7.cyan("--out <file>")} Write SBOM to file instead of stdout`);
3328
+ console.log(chalk11.bold("Options:"));
3329
+ console.log(` ${chalk11.cyan("--in <file>")} Input artifact (default: .vibgrate/scan_result.json)`);
3330
+ console.log(` ${chalk11.cyan("--format <format>")} SBOM format: ${chalk11.white("cyclonedx")} | spdx (default: cyclonedx)`);
3331
+ console.log(` ${chalk11.cyan("--out <file>")} Write SBOM to file instead of stdout`);
2005
3332
  console.log("");
2006
- console.log(chalk7.bold("Examples:"));
3333
+ console.log(chalk11.bold("Examples:"));
2007
3334
  console.log(" vibgrate sbom --format cyclonedx --out sbom.json");
2008
3335
  console.log(" vibgrate sbom --format spdx --out sbom.spdx.json");
2009
3336
  },
2010
3337
  push: () => {
2011
3338
  console.log("");
2012
- console.log(chalk7.bold.underline("vibgrate push") + chalk7.dim(" \u2014 Upload a scan artifact to the Vibgrate API"));
3339
+ console.log(chalk11.bold.underline("vibgrate push") + chalk11.dim(" \u2014 Upload a scan artifact to the Vibgrate API"));
2013
3340
  console.log("");
2014
- console.log(chalk7.bold("Usage:"));
3341
+ console.log(chalk11.bold("Usage:"));
2015
3342
  console.log(" vibgrate push [options]");
2016
3343
  console.log("");
2017
- console.log(chalk7.bold("Options:"));
2018
- console.log(` ${chalk7.cyan("--dsn <dsn>")} DSN token (or set VIBGRATE_DSN env var)`);
2019
- console.log(` ${chalk7.cyan("--file <file>")} Artifact to upload (default: .vibgrate/scan_result.json)`);
2020
- console.log(` ${chalk7.cyan("--region <region>")} Override data residency region: us | eu`);
2021
- console.log(` ${chalk7.cyan("--strict")} Fail with non-zero exit code on upload error`);
3344
+ console.log(chalk11.bold("Options:"));
3345
+ console.log(` ${chalk11.cyan("--dsn <dsn>")} DSN token (or set VIBGRATE_DSN env var)`);
3346
+ console.log(` ${chalk11.cyan("--file <file>")} Artifact to upload (default: .vibgrate/scan_result.json)`);
3347
+ console.log(` ${chalk11.cyan("--region <region>")} Override data residency region: us | eu`);
3348
+ console.log(` ${chalk11.cyan("--strict")} Fail with non-zero exit code on upload error`);
2022
3349
  console.log("");
2023
- console.log(chalk7.bold("Examples:"));
3350
+ console.log(chalk11.bold("Examples:"));
2024
3351
  console.log(" vibgrate push --dsn $VIBGRATE_DSN");
2025
3352
  console.log(" vibgrate push --file ./ci/scan_result.json --strict");
2026
3353
  },
2027
3354
  dsn: () => {
2028
3355
  console.log("");
2029
- console.log(chalk7.bold.underline("vibgrate dsn") + chalk7.dim(" \u2014 Manage DSN tokens for API authentication"));
3356
+ console.log(chalk11.bold.underline("vibgrate dsn") + chalk11.dim(" \u2014 Manage DSN tokens for API authentication"));
2030
3357
  console.log("");
2031
- console.log(chalk7.bold("Subcommands:"));
2032
- console.log(` ${chalk7.cyan("vibgrate dsn create")} Generate a new DSN token`);
3358
+ console.log(chalk11.bold("Subcommands:"));
3359
+ console.log(` ${chalk11.cyan("vibgrate dsn create")} Generate a new DSN token`);
2033
3360
  console.log("");
2034
- console.log(chalk7.bold("dsn create options:"));
2035
- console.log(` ${chalk7.cyan("--workspace <id>")} Workspace ID or "new" to auto-generate ${chalk7.red("(required)")}`);
2036
- console.log(` ${chalk7.cyan("--region <region>")} Data residency region: us | eu (default: us)`);
2037
- console.log(` ${chalk7.cyan("--ingest <url>")} Override ingest API URL`);
2038
- console.log(` ${chalk7.cyan("--write <path>")} Write the DSN to a file (add to .gitignore!)`);
3361
+ console.log(chalk11.bold("dsn create options:"));
3362
+ console.log(` ${chalk11.cyan("--workspace <id>")} Workspace ID or "new" to auto-generate ${chalk11.red("(required)")}`);
3363
+ console.log(` ${chalk11.cyan("--region <region>")} Data residency region: us | eu (default: us)`);
3364
+ console.log(` ${chalk11.cyan("--ingest <url>")} Override ingest API URL`);
3365
+ console.log(` ${chalk11.cyan("--write <path>")} Write the DSN to a file (add to .gitignore!)`);
2039
3366
  console.log("");
2040
- console.log(chalk7.bold("Examples:"));
3367
+ console.log(chalk11.bold("Examples:"));
2041
3368
  console.log(" vibgrate dsn create --workspace new");
2042
3369
  console.log(" vibgrate dsn create --workspace abc123");
2043
3370
  console.log(" vibgrate dsn create --workspace new --region eu --write .vibgrate/.dsn");
2044
3371
  },
2045
3372
  update: () => {
2046
3373
  console.log("");
2047
- console.log(chalk7.bold.underline("vibgrate update") + chalk7.dim(" \u2014 Update the vibgrate CLI to the latest version"));
3374
+ console.log(chalk11.bold.underline("vibgrate update") + chalk11.dim(" \u2014 Update the vibgrate CLI to the latest version"));
2048
3375
  console.log("");
2049
- console.log(chalk7.bold("Usage:"));
3376
+ console.log(chalk11.bold("Usage:"));
2050
3377
  console.log(" vibgrate update [options]");
2051
3378
  console.log("");
2052
- console.log(chalk7.bold("Options:"));
2053
- console.log(` ${chalk7.cyan("--check")} Check for a newer version without installing`);
2054
- console.log(` ${chalk7.cyan("--pm <manager>")} Force a package manager: npm | pnpm | yarn | bun`);
3379
+ console.log(chalk11.bold("Options:"));
3380
+ console.log(` ${chalk11.cyan("--check")} Check for a newer version without installing`);
3381
+ console.log(` ${chalk11.cyan("--pm <manager>")} Force a package manager: npm | pnpm | yarn | bun`);
2055
3382
  console.log("");
2056
- console.log(chalk7.bold("Examples:"));
3383
+ console.log(chalk11.bold("Examples:"));
2057
3384
  console.log(" vibgrate update");
2058
3385
  console.log(" vibgrate update --check");
2059
3386
  console.log(" vibgrate update --pm pnpm");
@@ -2061,40 +3388,40 @@ var detailedHelp = {
2061
3388
  };
2062
3389
  function printSummaryHelp() {
2063
3390
  console.log("");
2064
- console.log(chalk7.bold("vibgrate") + chalk7.dim(" \u2014 Continuous Drift Intelligence"));
3391
+ console.log(chalk11.bold("vibgrate") + chalk11.dim(" \u2014 Continuous Drift Intelligence"));
2065
3392
  console.log("");
2066
- console.log(chalk7.bold("Usage:"));
3393
+ console.log(chalk11.bold("Usage:"));
2067
3394
  console.log(" vibgrate <command> [options]");
2068
3395
  console.log(" vibgrate help [command] Show detailed help for a command");
2069
3396
  console.log("");
2070
- console.log(chalk7.bold("Getting started:"));
2071
- console.log(` ${chalk7.cyan("init")} Initialise vibgrate in a project (creates config & .vibgrate/ dir)`);
3397
+ console.log(chalk11.bold("Getting started:"));
3398
+ console.log(` ${chalk11.cyan("init")} Initialise vibgrate in a project (creates config & .vibgrate/ dir)`);
2072
3399
  console.log("");
2073
- console.log(chalk7.bold("Core scanning:"));
2074
- console.log(` ${chalk7.cyan("scan")} Scan a project for upgrade drift and generate a report`);
2075
- console.log(` ${chalk7.cyan("baseline")} Save a baseline snapshot to compare future scans against`);
3400
+ console.log(chalk11.bold("Core scanning:"));
3401
+ console.log(` ${chalk11.cyan("scan")} Scan a project for upgrade drift and generate a report`);
3402
+ console.log(` ${chalk11.cyan("baseline")} Save a baseline snapshot to compare future scans against`);
2076
3403
  console.log("");
2077
- console.log(chalk7.bold("Reporting & export:"));
2078
- console.log(` ${chalk7.cyan("report")} Re-generate a report from a previously saved scan artifact`);
2079
- console.log(` ${chalk7.cyan("sbom")} Export a Software Bill of Materials (CycloneDX or SPDX)`);
3404
+ console.log(chalk11.bold("Reporting & export:"));
3405
+ console.log(` ${chalk11.cyan("report")} Re-generate a report from a previously saved scan artifact`);
3406
+ console.log(` ${chalk11.cyan("sbom")} Export a Software Bill of Materials (CycloneDX or SPDX)`);
2080
3407
  console.log("");
2081
- console.log(chalk7.bold("CI/CD integration:"));
2082
- console.log(` ${chalk7.cyan("push")} Upload a scan artifact to the Vibgrate API`);
2083
- console.log(` ${chalk7.cyan("dsn")} Create and manage DSN tokens for API authentication`);
3408
+ console.log(chalk11.bold("CI/CD integration:"));
3409
+ console.log(` ${chalk11.cyan("push")} Upload a scan artifact to the Vibgrate API`);
3410
+ console.log(` ${chalk11.cyan("dsn")} Create and manage DSN tokens for API authentication`);
2084
3411
  console.log("");
2085
- console.log(chalk7.bold("Maintenance:"));
2086
- console.log(` ${chalk7.cyan("update")} Update the vibgrate CLI to the latest version`);
3412
+ console.log(chalk11.bold("Maintenance:"));
3413
+ console.log(` ${chalk11.cyan("update")} Update the vibgrate CLI to the latest version`);
2087
3414
  console.log("");
2088
- console.log(chalk7.dim("Run") + ` ${chalk7.cyan("vibgrate help <command>")} ` + chalk7.dim("for detailed options, e.g.") + ` ${chalk7.cyan("vibgrate help scan")}`);
3415
+ console.log(chalk11.dim("Run") + ` ${chalk11.cyan("vibgrate help <command>")} ` + chalk11.dim("for detailed options, e.g.") + ` ${chalk11.cyan("vibgrate help scan")}`);
2089
3416
  }
2090
- var helpCommand = new Command7("help").description("Show help for vibgrate commands").argument("[command]", "Command to show detailed help for").helpOption(false).action((cmd) => {
3417
+ var helpCommand = new Command10("help").description("Show help for vibgrate commands").argument("[command]", "Command to show detailed help for").helpOption(false).action((cmd) => {
2091
3418
  const name = cmd?.toLowerCase().trim();
2092
3419
  if (name && detailedHelp[name]) {
2093
3420
  detailedHelp[name]();
2094
3421
  } else if (name) {
2095
3422
  console.log("");
2096
- console.log(chalk7.red(`Unknown command: ${name}`));
2097
- console.log(chalk7.dim(`Available commands: ${Object.keys(detailedHelp).join(", ")}`));
3423
+ console.log(chalk11.red(`Unknown command: ${name}`));
3424
+ console.log(chalk11.dim(`Available commands: ${Object.keys(detailedHelp).join(", ")}`));
2098
3425
  printSummaryHelp();
2099
3426
  } else {
2100
3427
  printSummaryHelp();
@@ -2103,7 +3430,7 @@ var helpCommand = new Command7("help").description("Show help for vibgrate comma
2103
3430
  });
2104
3431
 
2105
3432
  // src/cli.ts
2106
- var program = new Command8();
3433
+ var program = new Command11();
2107
3434
  program.name("vibgrate").description("Continuous Drift Intelligence").version(VERSION).addHelpText("after", "\nSee https://vibgrate.com/help for more guidance");
2108
3435
  program.addCommand(helpCommand);
2109
3436
  program.addCommand(initCommand);
@@ -2120,8 +3447,8 @@ function notifyIfUpdateAvailable() {
2120
3447
  void checkForUpdate().then((update) => {
2121
3448
  if (!update?.updateAvailable) return;
2122
3449
  console.error("");
2123
- console.error(chalk8.yellow(` Update available: ${update.current} \u2192 ${update.latest}`));
2124
- console.error(chalk8.dim(' Run "vibgrate update" to install the latest version.'));
3450
+ console.error(chalk12.yellow(` Update available: ${update.current} \u2192 ${update.latest}`));
3451
+ console.error(chalk12.dim(' Run "vibgrate update" to install the latest version.'));
2125
3452
  console.error("");
2126
3453
  }).catch(() => {
2127
3454
  });