@vibgrate/cli 1.0.45 → 1.0.47

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,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  formatMarkdown
4
- } from "./chunk-GN3IWKSY.js";
4
+ } from "./chunk-PTMLMDZU.js";
5
5
  import {
6
6
  baselineCommand
7
- } from "./chunk-LO66M6OC.js";
7
+ } from "./chunk-NASGRGXK.js";
8
8
  import {
9
9
  VERSION,
10
10
  dsnCommand,
@@ -12,16 +12,17 @@ import {
12
12
  pushCommand,
13
13
  scanCommand,
14
14
  writeDefaultConfig
15
- } from "./chunk-YFJC5JSQ.js";
15
+ } from "./chunk-UVFIFNYG.js";
16
16
  import {
17
17
  ensureDir,
18
18
  pathExists,
19
- readJsonFile
19
+ readJsonFile,
20
+ writeTextFile
20
21
  } from "./chunk-RNVZIZNL.js";
21
22
 
22
23
  // src/cli.ts
23
- import { Command as Command4 } from "commander";
24
- import chalk4 from "chalk";
24
+ import { Command as Command5 } from "commander";
25
+ import chalk5 from "chalk";
25
26
 
26
27
  // src/commands/init.ts
27
28
  import * as path from "path";
@@ -40,7 +41,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
40
41
  console.log(chalk.green("\u2714") + ` Created ${chalk.bold("vibgrate.config.ts")}`);
41
42
  }
42
43
  if (opts.baseline) {
43
- const { runBaseline } = await import("./baseline-FDWMBM2O.js");
44
+ const { runBaseline } = await import("./baseline-K7V6GAP3.js");
44
45
  await runBaseline(rootDir);
45
46
  }
46
47
  console.log("");
@@ -222,8 +223,186 @@ var updateCommand = new Command3("update").description("Update vibgrate to the l
222
223
  }
223
224
  });
224
225
 
226
+ // src/commands/sbom.ts
227
+ import * as path5 from "path";
228
+ import { randomUUID } from "crypto";
229
+ import { Command as Command4 } from "commander";
230
+ import chalk4 from "chalk";
231
+ function flattenDependencies(artifact) {
232
+ const rows = [];
233
+ for (const project of artifact.projects) {
234
+ for (const dep of project.dependencies) {
235
+ rows.push({
236
+ project: project.name,
237
+ package: dep.package,
238
+ version: dep.resolvedVersion ?? dep.currentSpec,
239
+ currentSpec: dep.currentSpec,
240
+ drift: dep.drift,
241
+ majorsBehind: dep.majorsBehind
242
+ });
243
+ }
244
+ }
245
+ return rows;
246
+ }
247
+ function toCycloneDx(artifact) {
248
+ const dependencies = flattenDependencies(artifact);
249
+ return {
250
+ bomFormat: "CycloneDX",
251
+ specVersion: "1.5",
252
+ serialNumber: `urn:uuid:${randomUUID()}`,
253
+ version: 1,
254
+ metadata: {
255
+ timestamp: artifact.timestamp,
256
+ tools: [
257
+ {
258
+ vendor: "Vibgrate",
259
+ name: "@vibgrate/cli",
260
+ version: artifact.vibgrateVersion
261
+ }
262
+ ],
263
+ component: {
264
+ type: "application",
265
+ name: artifact.rootPath
266
+ }
267
+ },
268
+ components: dependencies.map((dep) => ({
269
+ type: "library",
270
+ name: dep.package,
271
+ version: dep.version,
272
+ properties: [
273
+ { name: "vibgrate:project", value: dep.project },
274
+ { name: "vibgrate:currentSpec", value: dep.currentSpec },
275
+ { name: "vibgrate:drift", value: dep.drift },
276
+ { name: "vibgrate:majorsBehind", value: String(dep.majorsBehind ?? "unknown") }
277
+ ]
278
+ }))
279
+ };
280
+ }
281
+ function toSpdx(artifact) {
282
+ const dependencies = flattenDependencies(artifact);
283
+ return {
284
+ spdxVersion: "SPDX-2.3",
285
+ dataLicense: "CC0-1.0",
286
+ SPDXID: "SPDXRef-DOCUMENT",
287
+ name: `${artifact.rootPath}-sbom`,
288
+ documentNamespace: `https://vibgrate.com/spdx/${artifact.rootPath}/${randomUUID()}`,
289
+ creationInfo: {
290
+ created: artifact.timestamp,
291
+ creators: [`Tool: @vibgrate/cli-${artifact.vibgrateVersion}`]
292
+ },
293
+ packages: dependencies.map((dep, i) => ({
294
+ name: dep.package,
295
+ SPDXID: `SPDXRef-Package-${i + 1}`,
296
+ versionInfo: dep.version,
297
+ downloadLocation: "NOASSERTION",
298
+ filesAnalyzed: false,
299
+ externalRefs: [
300
+ {
301
+ referenceCategory: "PACKAGE-MANAGER",
302
+ referenceType: "purl",
303
+ referenceLocator: `pkg:npm/${encodeURIComponent(dep.package)}@${encodeURIComponent(dep.version)}`
304
+ }
305
+ ],
306
+ annotations: [
307
+ {
308
+ annotationType: "OTHER",
309
+ annotator: "Tool: @vibgrate/cli",
310
+ annotationDate: artifact.timestamp,
311
+ comment: `project=${dep.project}; drift=${dep.drift}; majorsBehind=${dep.majorsBehind ?? "unknown"}`
312
+ }
313
+ ]
314
+ }))
315
+ };
316
+ }
317
+ function projectDependencyMap(artifact) {
318
+ const map = /* @__PURE__ */ new Map();
319
+ for (const project of artifact.projects) {
320
+ for (const dep of project.dependencies) {
321
+ map.set(`${project.name}:${dep.package}`, dep);
322
+ }
323
+ }
324
+ return map;
325
+ }
326
+ function formatDeltaText(base, current) {
327
+ const baseMap = projectDependencyMap(base);
328
+ const currentMap = projectDependencyMap(current);
329
+ const added = [];
330
+ const removed = [];
331
+ const changed = [];
332
+ for (const [key, dep] of currentMap.entries()) {
333
+ if (!baseMap.has(key)) {
334
+ added.push(`${key} @ ${dep.resolvedVersion ?? dep.currentSpec}`);
335
+ continue;
336
+ }
337
+ const prev = baseMap.get(key);
338
+ const prevVersion = prev.resolvedVersion ?? prev.currentSpec;
339
+ const nowVersion = dep.resolvedVersion ?? dep.currentSpec;
340
+ if (prevVersion !== nowVersion || prev.majorsBehind !== dep.majorsBehind) {
341
+ changed.push(`${key} ${prevVersion} -> ${nowVersion} (majorsBehind ${prev.majorsBehind ?? "unknown"} -> ${dep.majorsBehind ?? "unknown"})`);
342
+ }
343
+ }
344
+ for (const [key, dep] of baseMap.entries()) {
345
+ if (!currentMap.has(key)) {
346
+ removed.push(`${key} @ ${dep.resolvedVersion ?? dep.currentSpec}`);
347
+ }
348
+ }
349
+ const lines = [
350
+ "Vibgrate SBOM Delta",
351
+ "===================",
352
+ `Baseline: ${base.timestamp}`,
353
+ `Current: ${current.timestamp}`,
354
+ `Drift score delta: ${(current.drift.score - base.drift.score).toFixed(2)} points`,
355
+ "",
356
+ `Added dependencies (${added.length})`,
357
+ ...added.map((d) => ` + ${d}`),
358
+ "",
359
+ `Removed dependencies (${removed.length})`,
360
+ ...removed.map((d) => ` - ${d}`),
361
+ "",
362
+ `Changed dependencies (${changed.length})`,
363
+ ...changed.map((d) => ` * ${d}`)
364
+ ];
365
+ return lines.join("\n");
366
+ }
367
+ async function readArtifactOrExit(filePath) {
368
+ const absolutePath = path5.resolve(filePath);
369
+ if (!await pathExists(absolutePath)) {
370
+ console.error(chalk4.red(`Artifact not found: ${absolutePath}`));
371
+ process.exit(1);
372
+ }
373
+ return readJsonFile(absolutePath);
374
+ }
375
+ 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) => {
376
+ const artifact = await readArtifactOrExit(opts.in);
377
+ const format = opts.format.toLowerCase();
378
+ if (format !== "cyclonedx" && format !== "spdx") {
379
+ console.error(chalk4.red("Invalid SBOM format. Use cyclonedx or spdx."));
380
+ process.exit(1);
381
+ }
382
+ const sbom = format === "cyclonedx" ? toCycloneDx(artifact) : toSpdx(artifact);
383
+ const body = JSON.stringify(sbom, null, 2);
384
+ if (opts.out) {
385
+ await writeTextFile(path5.resolve(opts.out), body);
386
+ console.log(chalk4.green("\u2714") + ` SBOM written to ${opts.out}`);
387
+ } else {
388
+ console.log(body);
389
+ }
390
+ });
391
+ 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) => {
392
+ const base = await readArtifactOrExit(opts.from);
393
+ const current = await readArtifactOrExit(opts.to);
394
+ const report = formatDeltaText(base, current);
395
+ if (opts.out) {
396
+ await writeTextFile(path5.resolve(opts.out), report);
397
+ console.log(chalk4.green("\u2714") + ` SBOM delta report written to ${opts.out}`);
398
+ } else {
399
+ console.log(report);
400
+ }
401
+ });
402
+ var sbomCommand = new Command4("sbom").description("SBOM export and delta reports for dependency drift tracking").addCommand(exportCommand).addCommand(deltaCommand);
403
+
225
404
  // src/cli.ts
226
- var program = new Command4();
405
+ var program = new Command5();
227
406
  program.name("vibgrate").description("Continuous Drift Intelligence for Node & .NET").version(VERSION);
228
407
  program.addCommand(initCommand);
229
408
  program.addCommand(scanCommand);
@@ -232,12 +411,13 @@ program.addCommand(reportCommand);
232
411
  program.addCommand(dsnCommand);
233
412
  program.addCommand(pushCommand);
234
413
  program.addCommand(updateCommand);
414
+ program.addCommand(sbomCommand);
235
415
  program.parseAsync().then(async () => {
236
416
  const update = await checkForUpdate();
237
417
  if (update?.updateAvailable) {
238
418
  console.error("");
239
- console.error(chalk4.yellow(` Update available: ${update.current} \u2192 ${update.latest}`));
240
- console.error(chalk4.dim(' Run "vibgrate update" to install the latest version.'));
419
+ console.error(chalk5.yellow(` Update available: ${update.current} \u2192 ${update.latest}`));
420
+ console.error(chalk5.dim(' Run "vibgrate update" to install the latest version.'));
241
421
  console.error("");
242
422
  }
243
423
  }).catch((err) => {
package/dist/index.d.ts CHANGED
@@ -31,6 +31,10 @@ interface ProjectScan {
31
31
  name: string;
32
32
  /** Deterministic project ID: SHA-256 hash of `${path}:${name}:${workspaceId}` */
33
33
  projectId?: string;
34
+ /** Optional solution identifier when project belongs to a solution/workspace file */
35
+ solutionId?: string;
36
+ /** Optional solution name resolved from solution/workspace metadata */
37
+ solutionName?: string;
34
38
  runtime?: string;
35
39
  runtimeLatest?: string;
36
40
  runtimeMajorsBehind?: number;
@@ -49,6 +53,30 @@ interface ProjectScan {
49
53
  projectReferences?: ProjectReference[];
50
54
  /** Number of source files in the project directory */
51
55
  fileCount?: number;
56
+ /** Project-level architecture layer diagram (Mermaid flowchart) */
57
+ architectureMermaid?: string;
58
+ /** Project-level relationship diagram (first-level parents + children) */
59
+ relationshipDiagram?: MermaidDiagram;
60
+ }
61
+ interface SolutionScan {
62
+ /** Deterministic solution ID: SHA-256 hash of `${path}:${name}:${workspaceId}` */
63
+ solutionId: string;
64
+ /** Relative path to solution file */
65
+ path: string;
66
+ /** Solution display name */
67
+ name: string;
68
+ /** Solution file type */
69
+ type: 'dotnet-sln';
70
+ /** Projects resolved as belonging to this solution (by relative project path) */
71
+ projectPaths: string[];
72
+ /** Aggregate drift score for all resolved projects in this solution */
73
+ drift?: DriftScore;
74
+ /** Solution relationship diagram with top-level solution node and project links */
75
+ relationshipDiagram?: MermaidDiagram;
76
+ }
77
+ interface MermaidDiagram {
78
+ mermaid: string;
79
+ svg?: string;
52
80
  }
53
81
  interface DriftScore {
54
82
  score: number;
@@ -75,6 +103,13 @@ interface VcsInfo {
75
103
  sha?: string;
76
104
  shortSha?: string;
77
105
  branch?: string;
106
+ remoteUrl?: string;
107
+ }
108
+ interface RepositoryInfo {
109
+ name: string;
110
+ version?: string;
111
+ pipeline?: string;
112
+ remoteUrl?: string;
78
113
  }
79
114
  interface TreeCount {
80
115
  /** Total files discovered (excluding skipped dirs like node_modules, .git, dist) */
@@ -88,7 +123,9 @@ interface ScanArtifact {
88
123
  vibgrateVersion: string;
89
124
  rootPath: string;
90
125
  vcs?: VcsInfo;
126
+ repository?: RepositoryInfo;
91
127
  projects: ProjectScan[];
128
+ solutions?: SolutionScan[];
92
129
  drift: DriftScore;
93
130
  findings: Finding[];
94
131
  baseline?: string;
@@ -100,6 +137,8 @@ interface ScanArtifact {
100
137
  filesScanned?: number;
101
138
  /** Workspace tree summary (file & directory counts from discovery) */
102
139
  treeSummary?: TreeCount;
140
+ /** Workspace-level relationship diagram */
141
+ relationshipDiagram?: MermaidDiagram;
103
142
  }
104
143
  interface ScanOptions {
105
144
  out?: string;
@@ -117,6 +156,20 @@ interface ScanOptions {
117
156
  strict?: boolean;
118
157
  /** Auto-install missing security tools via Homebrew */
119
158
  installTools?: boolean;
159
+ /** Enable optional UI-purpose evidence extraction (slower, richer context for dashboard) */
160
+ uiPurpose?: boolean;
161
+ /** Prevent writing .vibgrate JSON artifacts to disk */
162
+ noLocalArtifacts?: boolean;
163
+ /** Enable strongest privacy profile: minimize scanners and suppress local artifacts */
164
+ maxPrivacy?: boolean;
165
+ /** Run without any network calls; drift may be partial without a package manifest */
166
+ offline?: boolean;
167
+ /** Path to package-version manifest JSON or ZIP used in offline/privacy workflows */
168
+ packageManifest?: string;
169
+ /** Fail the run if drift score is above this absolute budget */
170
+ driftBudget?: number;
171
+ /** Fail when drift worsens by more than this percentage vs baseline */
172
+ driftWorseningPercent?: number;
120
173
  }
121
174
  interface ScannerToggle {
122
175
  enabled: boolean;
@@ -140,6 +193,7 @@ interface ScannersConfig {
140
193
  architecture?: ScannerToggle;
141
194
  codeQuality?: ScannerToggle;
142
195
  owaspCategoryMapping?: OwaspScannerConfig;
196
+ uiPurpose?: ScannerToggle;
143
197
  }
144
198
  interface VibgrateConfig {
145
199
  include?: string[];
@@ -363,6 +417,20 @@ interface CodeQualityResult {
363
417
  circularDependencies: number;
364
418
  deadCodePercent: number;
365
419
  }
420
+ interface UiPurposeEvidenceItem {
421
+ kind: 'route' | 'nav' | 'title' | 'heading' | 'cta' | 'copy' | 'dependency' | 'feature_flag';
422
+ value: string;
423
+ file: string;
424
+ weight: number;
425
+ }
426
+ interface UiPurposeResult {
427
+ enabled: boolean;
428
+ detectedFrameworks: string[];
429
+ evidenceCount: number;
430
+ capped: boolean;
431
+ topEvidence: UiPurposeEvidenceItem[];
432
+ unknownSignals: string[];
433
+ }
366
434
  interface ExtendedScanResults {
367
435
  platformMatrix?: PlatformMatrixResult;
368
436
  dependencyRisk?: DependencyRiskResult;
@@ -378,6 +446,7 @@ interface ExtendedScanResults {
378
446
  architecture?: ArchitectureResult;
379
447
  codeQuality?: CodeQualityResult;
380
448
  owaspCategoryMapping?: OwaspCategoryMappingResult;
449
+ uiPurpose?: UiPurposeResult;
381
450
  }
382
451
  interface OwaspFinding {
383
452
  ruleId: string;
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  formatMarkdown
3
- } from "./chunk-GN3IWKSY.js";
3
+ } from "./chunk-PTMLMDZU.js";
4
4
  import {
5
5
  computeDriftScore,
6
6
  formatSarif,
7
7
  formatText,
8
8
  generateFindings,
9
9
  runScan
10
- } from "./chunk-YFJC5JSQ.js";
10
+ } from "./chunk-UVFIFNYG.js";
11
11
  import "./chunk-RNVZIZNL.js";
12
12
  export {
13
13
  computeDriftScore,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibgrate/cli",
3
- "version": "1.0.45",
3
+ "version": "1.0.47",
4
4
  "description": "CLI for measuring upgrade drift across Node, .NET, Python & Java projects",
5
5
  "type": "module",
6
6
  "bin": {