@vibgrate/cli 1.0.14 → 1.0.15

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/DOCS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Vibgrate CLI — Full Documentation
2
2
 
3
- > Continuous Upgrade Drift Intelligence for Node & .NET
3
+ > Continuous Drift Intelligence for Node & .NET
4
4
 
5
5
  For a quick overview, see the [README](./README.md). This document covers everything in detail.
6
6
 
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  baselineCommand,
3
3
  runBaseline
4
- } from "./chunk-POMRKRQN.js";
5
- import "./chunk-4N4BALQQ.js";
4
+ } from "./chunk-OPEOSIRY.js";
5
+ import "./chunk-QZV77UWV.js";
6
6
  export {
7
7
  baselineCommand,
8
8
  runBaseline
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  runScan,
3
3
  writeJsonFile
4
- } from "./chunk-4N4BALQQ.js";
4
+ } from "./chunk-QZV77UWV.js";
5
5
 
6
6
  // src/commands/baseline.ts
7
7
  import * as path from "path";
@@ -187,12 +187,12 @@ function eolScore(projects) {
187
187
  }
188
188
  function computeDriftScore(projects) {
189
189
  const rs = runtimeScore(projects);
190
- const fs5 = frameworkScore(projects);
190
+ const fs6 = frameworkScore(projects);
191
191
  const ds = dependencyScore(projects);
192
192
  const es = eolScore(projects);
193
193
  const components = [
194
194
  { score: rs, weight: 0.25 },
195
- { score: fs5, weight: 0.25 },
195
+ { score: fs6, weight: 0.25 },
196
196
  { score: ds, weight: 0.3 },
197
197
  { score: es, weight: 0.2 }
198
198
  ];
@@ -203,7 +203,7 @@ function computeDriftScore(projects) {
203
203
  riskLevel: "low",
204
204
  components: {
205
205
  runtimeScore: Math.round(rs ?? 100),
206
- frameworkScore: Math.round(fs5 ?? 100),
206
+ frameworkScore: Math.round(fs6 ?? 100),
207
207
  dependencyScore: Math.round(ds ?? 100),
208
208
  eolScore: Math.round(es ?? 100)
209
209
  }
@@ -221,7 +221,7 @@ function computeDriftScore(projects) {
221
221
  else riskLevel = "high";
222
222
  const measured = [];
223
223
  if (rs !== null) measured.push("runtime");
224
- if (fs5 !== null) measured.push("framework");
224
+ if (fs6 !== null) measured.push("framework");
225
225
  if (ds !== null) measured.push("dependency");
226
226
  if (es !== null) measured.push("eol");
227
227
  return {
@@ -229,7 +229,7 @@ function computeDriftScore(projects) {
229
229
  riskLevel,
230
230
  components: {
231
231
  runtimeScore: Math.round(rs ?? 100),
232
- frameworkScore: Math.round(fs5 ?? 100),
232
+ frameworkScore: Math.round(fs6 ?? 100),
233
233
  dependencyScore: Math.round(ds ?? 100),
234
234
  eolScore: Math.round(es ?? 100)
235
235
  },
@@ -585,6 +585,122 @@ function formatExtended(ext) {
585
585
  lines.push("");
586
586
  }
587
587
  }
588
+ if (ext.architecture) {
589
+ lines.push(...formatArchitectureDiagram(ext.architecture));
590
+ }
591
+ return lines;
592
+ }
593
+ var LAYER_LABELS = {
594
+ "presentation": "Presentation",
595
+ "routing": "Routing",
596
+ "middleware": "Middleware",
597
+ "services": "Services",
598
+ "domain": "Domain",
599
+ "data-access": "Data Access",
600
+ "infrastructure": "Infrastructure",
601
+ "config": "Config",
602
+ "shared": "Shared",
603
+ "testing": "Testing"
604
+ };
605
+ var LAYER_ICONS = {
606
+ "presentation": "\u{1F5A5}",
607
+ "routing": "\u{1F500}",
608
+ "middleware": "\u{1F517}",
609
+ "services": "\u2699",
610
+ "domain": "\u{1F48E}",
611
+ "data-access": "\u{1F5C4}",
612
+ "infrastructure": "\u{1F3D7}",
613
+ "config": "\u2699",
614
+ "shared": "\u{1F4E6}",
615
+ "testing": "\u{1F9EA}"
616
+ };
617
+ function formatArchitectureDiagram(arch) {
618
+ const lines = [];
619
+ lines.push(chalk.bold.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
620
+ lines.push(chalk.bold.cyan("\u2551 Architecture Layers \u2551"));
621
+ lines.push(chalk.bold.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
622
+ lines.push("");
623
+ const archetypeDisplay = arch.archetype === "unknown" ? "Unknown" : arch.archetype;
624
+ const confPct = Math.round(arch.archetypeConfidence * 100);
625
+ lines.push(chalk.bold(" Archetype: ") + chalk.cyan.bold(archetypeDisplay) + chalk.dim(` (${confPct}% confidence)`));
626
+ lines.push(chalk.dim(` ${arch.totalClassified} files classified \xB7 ${arch.unclassified} unclassified`));
627
+ lines.push("");
628
+ const boxWidth = 44;
629
+ const visibleLayers = arch.layers.filter((l) => l.fileCount > 0 || l.techStack.length > 0 || l.services.length > 0);
630
+ if (visibleLayers.length === 0) {
631
+ lines.push(chalk.dim(" No layers detected"));
632
+ lines.push("");
633
+ return lines;
634
+ }
635
+ for (let i = 0; i < visibleLayers.length; i++) {
636
+ const layer = visibleLayers[i];
637
+ const icon = LAYER_ICONS[layer.layer] ?? "\xB7";
638
+ const label = LAYER_LABELS[layer.layer] ?? layer.layer;
639
+ const scoreColor = layer.driftScore >= 70 ? chalk.green : layer.driftScore >= 40 ? chalk.yellow : chalk.red;
640
+ const riskBadgeStr = layer.riskLevel === "low" ? chalk.bgGreen.black(" LOW ") : layer.riskLevel === "moderate" ? chalk.bgYellow.black(" MOD ") : chalk.bgRed.white(" HIGH ");
641
+ if (i === 0) {
642
+ lines.push(chalk.cyan(` \u250C${"\u2500".repeat(boxWidth)}\u2510`));
643
+ }
644
+ const nameStr = `${icon} ${label}`;
645
+ const scoreStr = `${layer.driftScore}/100`;
646
+ const fileSuffix = `${layer.fileCount} file${layer.fileCount !== 1 ? "s" : ""}`;
647
+ const leftContent = ` ${nameStr}`;
648
+ const rightContent = `${fileSuffix} ${scoreStr} `;
649
+ const leftLen = nameStr.length + 2;
650
+ const rightLen = rightContent.length;
651
+ const padLen = Math.max(1, boxWidth - leftLen - rightLen);
652
+ lines.push(
653
+ chalk.cyan(" \u2502") + ` ${icon} ${chalk.bold(label)}` + " ".repeat(padLen) + chalk.dim(fileSuffix) + " " + scoreColor.bold(scoreStr) + " " + chalk.cyan("\u2502")
654
+ );
655
+ const barWidth = boxWidth - 8;
656
+ const filled = Math.round(layer.driftScore / 100 * barWidth);
657
+ const empty = barWidth - filled;
658
+ lines.push(
659
+ chalk.cyan(" \u2502") + " " + scoreColor("\u2588".repeat(filled)) + chalk.dim("\u2591".repeat(empty)) + " " + chalk.cyan("\u2502")
660
+ );
661
+ if (layer.techStack.length > 0) {
662
+ const techNames = layer.techStack.slice(0, 6).map((t) => t.name);
663
+ const moreCount = layer.techStack.length > 6 ? ` +${layer.techStack.length - 6}` : "";
664
+ const techLine = `Tech: ${techNames.join(", ")}${moreCount}`;
665
+ const truncated = techLine.length > boxWidth - 6 ? techLine.slice(0, boxWidth - 9) + "..." : techLine;
666
+ const techPad = Math.max(0, boxWidth - truncated.length - 4);
667
+ lines.push(
668
+ chalk.cyan(" \u2502") + " " + chalk.dim(truncated) + " ".repeat(techPad) + chalk.cyan("\u2502")
669
+ );
670
+ }
671
+ if (layer.services.length > 0) {
672
+ const svcNames = layer.services.slice(0, 5).map((s) => s.name);
673
+ const moreCount = layer.services.length > 5 ? ` +${layer.services.length - 5}` : "";
674
+ const svcLine = `Services: ${svcNames.join(", ")}${moreCount}`;
675
+ const truncated = svcLine.length > boxWidth - 6 ? svcLine.slice(0, boxWidth - 9) + "..." : svcLine;
676
+ const svcPad = Math.max(0, boxWidth - truncated.length - 4);
677
+ lines.push(
678
+ chalk.cyan(" \u2502") + " " + chalk.dim(truncated) + " ".repeat(svcPad) + chalk.cyan("\u2502")
679
+ );
680
+ }
681
+ const driftPkgs = layer.packages.filter((p) => p.majorsBehind !== null && p.majorsBehind > 0);
682
+ if (driftPkgs.length > 0) {
683
+ const worst = driftPkgs.sort((a, b) => (b.majorsBehind ?? 0) - (a.majorsBehind ?? 0));
684
+ const shown = worst.slice(0, 3);
685
+ const pkgStrs = shown.map((p) => {
686
+ const color = (p.majorsBehind ?? 0) >= 2 ? chalk.red : chalk.yellow;
687
+ return color(`${p.name} -${p.majorsBehind}`);
688
+ });
689
+ const moreCount = worst.length > 3 ? chalk.dim(` +${worst.length - 3}`) : "";
690
+ const pkgLine = pkgStrs.join(chalk.dim(", ")) + moreCount;
691
+ const roughLen = shown.map((p) => `${p.name} -${p.majorsBehind}`).join(", ").length + (worst.length > 3 ? ` +${worst.length - 3}`.length : 0);
692
+ const pkgPad = Math.max(0, boxWidth - roughLen - 4);
693
+ lines.push(
694
+ chalk.cyan(" \u2502") + " " + pkgLine + " ".repeat(pkgPad) + chalk.cyan("\u2502")
695
+ );
696
+ }
697
+ if (i < visibleLayers.length - 1) {
698
+ lines.push(chalk.cyan(` \u251C${"\u2500".repeat(boxWidth)}\u2524`));
699
+ } else {
700
+ lines.push(chalk.cyan(` \u2514${"\u2500".repeat(boxWidth)}\u2518`));
701
+ }
702
+ }
703
+ lines.push("");
588
704
  return lines;
589
705
  }
590
706
  function generatePriorityActions(artifact) {
@@ -1050,7 +1166,7 @@ var pushCommand = new Command2("push").description("Push scan results to Vibgrat
1050
1166
  });
1051
1167
 
1052
1168
  // src/commands/scan.ts
1053
- import * as path14 from "path";
1169
+ import * as path15 from "path";
1054
1170
  import { Command as Command3 } from "commander";
1055
1171
  import chalk5 from "chalk";
1056
1172
 
@@ -3909,6 +4025,528 @@ function scanServiceDependencies(projects) {
3909
4025
  return result;
3910
4026
  }
3911
4027
 
4028
+ // src/scanners/architecture.ts
4029
+ import * as path14 from "path";
4030
+ import * as fs5 from "fs/promises";
4031
+ var ARCHETYPE_SIGNALS = [
4032
+ // Meta-frameworks (highest priority — they imply routing patterns)
4033
+ { packages: ["next", "@next/core"], archetype: "nextjs", weight: 10 },
4034
+ { packages: ["@remix-run/react", "@remix-run/node", "@remix-run/dev"], archetype: "remix", weight: 10 },
4035
+ { packages: ["@sveltejs/kit"], archetype: "sveltekit", weight: 10 },
4036
+ { packages: ["nuxt"], archetype: "nuxt", weight: 10 },
4037
+ // Backend frameworks
4038
+ { packages: ["@nestjs/core", "@nestjs/common"], archetype: "nestjs", weight: 9 },
4039
+ { packages: ["fastify"], archetype: "fastify", weight: 8 },
4040
+ { packages: ["hono"], archetype: "hono", weight: 8 },
4041
+ { packages: ["koa"], archetype: "koa", weight: 8 },
4042
+ { packages: ["express"], archetype: "express", weight: 7 },
4043
+ // Serverless
4044
+ { packages: ["serverless", "aws-lambda", "@aws-sdk/client-lambda", "middy", "@cloudflare/workers-types"], archetype: "serverless", weight: 6 },
4045
+ // CLI
4046
+ { packages: ["commander", "yargs", "meow", "cac", "clipanion", "oclif"], archetype: "cli", weight: 5 }
4047
+ ];
4048
+ function detectArchetype(projects) {
4049
+ const allPackages = /* @__PURE__ */ new Set();
4050
+ for (const p of projects) {
4051
+ for (const d of p.dependencies) {
4052
+ allPackages.add(d.package);
4053
+ }
4054
+ }
4055
+ if (projects.length > 2) {
4056
+ return { archetype: "monorepo", confidence: 0.8 };
4057
+ }
4058
+ let bestArchetype = "unknown";
4059
+ let bestScore = 0;
4060
+ for (const signal of ARCHETYPE_SIGNALS) {
4061
+ const matched = signal.packages.filter((p) => allPackages.has(p)).length;
4062
+ if (matched > 0) {
4063
+ const score = matched * signal.weight;
4064
+ if (score > bestScore) {
4065
+ bestScore = score;
4066
+ bestArchetype = signal.archetype;
4067
+ }
4068
+ }
4069
+ }
4070
+ if (bestArchetype === "unknown") {
4071
+ bestArchetype = "library";
4072
+ bestScore = 3;
4073
+ }
4074
+ const confidence = Math.min(bestScore / 15, 1);
4075
+ return { archetype: bestArchetype, confidence: Math.round(confidence * 100) / 100 };
4076
+ }
4077
+ var PATH_RULES = [
4078
+ // ── Testing (high precision) ──
4079
+ { pattern: /\/__tests__\//, layer: "testing", confidence: 0.95, signal: "__tests__ directory" },
4080
+ { pattern: /\.test\.[jt]sx?$/, layer: "testing", confidence: 0.95, signal: ".test.* file" },
4081
+ { pattern: /\.spec\.[jt]sx?$/, layer: "testing", confidence: 0.95, signal: ".spec.* file" },
4082
+ { pattern: /\/test\//, layer: "testing", confidence: 0.85, signal: "test/ directory" },
4083
+ { pattern: /\/tests\//, layer: "testing", confidence: 0.85, signal: "tests/ directory" },
4084
+ { pattern: /\/__mocks__\//, layer: "testing", confidence: 0.9, signal: "__mocks__ directory" },
4085
+ { pattern: /\/fixtures\//, layer: "testing", confidence: 0.8, signal: "fixtures/ directory" },
4086
+ // ── Config/Infrastructure (high precision) ──
4087
+ { pattern: /\/config\.[jt]sx?$/, layer: "config", confidence: 0.85, signal: "config.* file" },
4088
+ { pattern: /\/config\//, layer: "config", confidence: 0.8, signal: "config/ directory" },
4089
+ { pattern: /\.config\.[jt]sx?$/, layer: "config", confidence: 0.9, signal: ".config.* file" },
4090
+ { pattern: /\/env\.[jt]sx?$/, layer: "config", confidence: 0.85, signal: "env.* file" },
4091
+ { pattern: /\/bootstrap\.[jt]sx?$/, layer: "config", confidence: 0.85, signal: "bootstrap file" },
4092
+ { pattern: /\/setup\.[jt]sx?$/, layer: "config", confidence: 0.8, signal: "setup file" },
4093
+ // ── Next.js (archetype-specific) ──
4094
+ { pattern: /(^|\/)app\/.*\/route\.[jt]sx?$/, layer: "routing", confidence: 0.95, signal: "Next.js App Router route", archetypes: ["nextjs"] },
4095
+ { pattern: /(^|\/)pages\/api\//, layer: "routing", confidence: 0.95, signal: "Next.js Pages API route", archetypes: ["nextjs"] },
4096
+ { pattern: /(^|\/)app\/.*page\.[jt]sx?$/, layer: "presentation", confidence: 0.9, signal: "Next.js page component", archetypes: ["nextjs"] },
4097
+ { pattern: /(^|\/)app\/.*layout\.[jt]sx?$/, layer: "presentation", confidence: 0.9, signal: "Next.js layout component", archetypes: ["nextjs"] },
4098
+ { pattern: /(^|\/)app\/.*loading\.[jt]sx?$/, layer: "presentation", confidence: 0.85, signal: "Next.js loading component", archetypes: ["nextjs"] },
4099
+ { pattern: /(^|\/)app\/.*error\.[jt]sx?$/, layer: "presentation", confidence: 0.85, signal: "Next.js error component", archetypes: ["nextjs"] },
4100
+ { pattern: /(^|\/)middleware\.[jt]sx?$/, layer: "middleware", confidence: 0.9, signal: "Next.js middleware", archetypes: ["nextjs"] },
4101
+ // ── Remix (archetype-specific) ──
4102
+ { pattern: /\/app\/routes\//, layer: "routing", confidence: 0.95, signal: "Remix route file", archetypes: ["remix"] },
4103
+ { pattern: /\/app\/root\.[jt]sx?$/, layer: "presentation", confidence: 0.9, signal: "Remix root", archetypes: ["remix"] },
4104
+ // ── SvelteKit (archetype-specific) ──
4105
+ { pattern: /\/src\/routes\/.*\+server\.[jt]s$/, layer: "routing", confidence: 0.95, signal: "SvelteKit API route", archetypes: ["sveltekit"] },
4106
+ { pattern: /\/src\/routes\/.*\+page\.svelte$/, layer: "presentation", confidence: 0.9, signal: "SvelteKit page", archetypes: ["sveltekit"] },
4107
+ { pattern: /\/src\/routes\/.*\+layout\.svelte$/, layer: "presentation", confidence: 0.9, signal: "SvelteKit layout", archetypes: ["sveltekit"] },
4108
+ { pattern: /\/src\/hooks\.server\.[jt]s$/, layer: "middleware", confidence: 0.9, signal: "SvelteKit server hooks", archetypes: ["sveltekit"] },
4109
+ // ── Nuxt (archetype-specific) ──
4110
+ { pattern: /\/server\/api\//, layer: "routing", confidence: 0.95, signal: "Nuxt server API", archetypes: ["nuxt"] },
4111
+ { pattern: /\/server\/routes\//, layer: "routing", confidence: 0.95, signal: "Nuxt server route", archetypes: ["nuxt"] },
4112
+ { pattern: /\/server\/middleware\//, layer: "middleware", confidence: 0.95, signal: "Nuxt server middleware", archetypes: ["nuxt"] },
4113
+ { pattern: /\/pages\//, layer: "presentation", confidence: 0.85, signal: "Nuxt pages directory", archetypes: ["nuxt"] },
4114
+ // ── NestJS (archetype-specific) ──
4115
+ { pattern: /\.controller\.[jt]sx?$/, layer: "routing", confidence: 0.95, signal: "NestJS controller", archetypes: ["nestjs"] },
4116
+ { pattern: /\.service\.[jt]sx?$/, layer: "services", confidence: 0.95, signal: "NestJS service", archetypes: ["nestjs"] },
4117
+ { pattern: /\.module\.[jt]sx?$/, layer: "config", confidence: 0.9, signal: "NestJS module", archetypes: ["nestjs"] },
4118
+ { pattern: /\.guard\.[jt]sx?$/, layer: "middleware", confidence: 0.9, signal: "NestJS guard", archetypes: ["nestjs"] },
4119
+ { pattern: /\.interceptor\.[jt]sx?$/, layer: "middleware", confidence: 0.9, signal: "NestJS interceptor", archetypes: ["nestjs"] },
4120
+ { pattern: /\.pipe\.[jt]sx?$/, layer: "middleware", confidence: 0.85, signal: "NestJS pipe", archetypes: ["nestjs"] },
4121
+ { pattern: /\.middleware\.[jt]sx?$/, layer: "middleware", confidence: 0.9, signal: "NestJS middleware", archetypes: ["nestjs"] },
4122
+ { pattern: /\.entity\.[jt]sx?$/, layer: "domain", confidence: 0.9, signal: "NestJS entity", archetypes: ["nestjs"] },
4123
+ { pattern: /\.dto\.[jt]sx?$/, layer: "domain", confidence: 0.85, signal: "NestJS DTO", archetypes: ["nestjs"] },
4124
+ { pattern: /\.repository\.[jt]sx?$/, layer: "data-access", confidence: 0.9, signal: "NestJS repository", archetypes: ["nestjs"] },
4125
+ // ── Generic routing patterns ──
4126
+ { pattern: /\/routes\//, layer: "routing", confidence: 0.8, signal: "routes/ directory" },
4127
+ { pattern: /\/router\//, layer: "routing", confidence: 0.8, signal: "router/ directory" },
4128
+ { pattern: /\/controllers\//, layer: "routing", confidence: 0.8, signal: "controllers/ directory" },
4129
+ { pattern: /\/handlers\//, layer: "routing", confidence: 0.75, signal: "handlers/ directory" },
4130
+ { pattern: /\/api\//, layer: "routing", confidence: 0.7, signal: "api/ directory" },
4131
+ { pattern: /\/endpoints\//, layer: "routing", confidence: 0.8, signal: "endpoints/ directory" },
4132
+ // ── Middleware ──
4133
+ { pattern: /\/middleware\//, layer: "middleware", confidence: 0.85, signal: "middleware/ directory" },
4134
+ { pattern: /\/middlewares\//, layer: "middleware", confidence: 0.85, signal: "middlewares/ directory" },
4135
+ { pattern: /\/hooks\//, layer: "middleware", confidence: 0.6, signal: "hooks/ directory" },
4136
+ { pattern: /\/plugins\//, layer: "middleware", confidence: 0.6, signal: "plugins/ directory" },
4137
+ { pattern: /\/guards\//, layer: "middleware", confidence: 0.85, signal: "guards/ directory" },
4138
+ { pattern: /\/interceptors\//, layer: "middleware", confidence: 0.85, signal: "interceptors/ directory" },
4139
+ // ── Services / application layer ──
4140
+ { pattern: /\/services\//, layer: "services", confidence: 0.85, signal: "services/ directory" },
4141
+ { pattern: /\/service\//, layer: "services", confidence: 0.8, signal: "service/ directory" },
4142
+ { pattern: /\/usecases\//, layer: "services", confidence: 0.85, signal: "usecases/ directory" },
4143
+ { pattern: /\/use-cases\//, layer: "services", confidence: 0.85, signal: "use-cases/ directory" },
4144
+ { pattern: /\/application\//, layer: "services", confidence: 0.7, signal: "application/ directory" },
4145
+ { pattern: /\/actions\//, layer: "services", confidence: 0.65, signal: "actions/ directory" },
4146
+ // ── Domain / models ──
4147
+ { pattern: /\/domain\//, layer: "domain", confidence: 0.85, signal: "domain/ directory" },
4148
+ { pattern: /\/models\//, layer: "domain", confidence: 0.8, signal: "models/ directory" },
4149
+ { pattern: /\/entities\//, layer: "domain", confidence: 0.85, signal: "entities/ directory" },
4150
+ { pattern: /\/types\//, layer: "domain", confidence: 0.7, signal: "types/ directory" },
4151
+ { pattern: /\/schemas\//, layer: "domain", confidence: 0.7, signal: "schemas/ directory" },
4152
+ { pattern: /\/validators\//, layer: "domain", confidence: 0.7, signal: "validators/ directory" },
4153
+ // ── Data access ──
4154
+ { pattern: /\/repositories\//, layer: "data-access", confidence: 0.9, signal: "repositories/ directory" },
4155
+ { pattern: /\/repository\//, layer: "data-access", confidence: 0.85, signal: "repository/ directory" },
4156
+ { pattern: /\/dao\//, layer: "data-access", confidence: 0.9, signal: "dao/ directory" },
4157
+ { pattern: /\/db\//, layer: "data-access", confidence: 0.8, signal: "db/ directory" },
4158
+ { pattern: /\/database\//, layer: "data-access", confidence: 0.8, signal: "database/ directory" },
4159
+ { pattern: /\/persistence\//, layer: "data-access", confidence: 0.85, signal: "persistence/ directory" },
4160
+ { pattern: /\/migrations\//, layer: "data-access", confidence: 0.9, signal: "migrations/ directory" },
4161
+ { pattern: /\/seeds\//, layer: "data-access", confidence: 0.85, signal: "seeds/ directory" },
4162
+ { pattern: /\/prisma\//, layer: "data-access", confidence: 0.85, signal: "prisma/ directory" },
4163
+ { pattern: /\/drizzle\//, layer: "data-access", confidence: 0.85, signal: "drizzle/ directory" },
4164
+ // ── Infrastructure ──
4165
+ { pattern: /\/infra\//, layer: "infrastructure", confidence: 0.85, signal: "infra/ directory" },
4166
+ { pattern: /\/infrastructure\//, layer: "infrastructure", confidence: 0.85, signal: "infrastructure/ directory" },
4167
+ { pattern: /\/adapters\//, layer: "infrastructure", confidence: 0.8, signal: "adapters/ directory" },
4168
+ { pattern: /\/clients\//, layer: "infrastructure", confidence: 0.75, signal: "clients/ directory" },
4169
+ { pattern: /\/integrations\//, layer: "infrastructure", confidence: 0.8, signal: "integrations/ directory" },
4170
+ { pattern: /\/external\//, layer: "infrastructure", confidence: 0.75, signal: "external/ directory" },
4171
+ { pattern: /\/queue\//, layer: "infrastructure", confidence: 0.8, signal: "queue/ directory" },
4172
+ { pattern: /\/jobs\//, layer: "infrastructure", confidence: 0.75, signal: "jobs/ directory" },
4173
+ { pattern: /\/workers\//, layer: "infrastructure", confidence: 0.75, signal: "workers/ directory" },
4174
+ { pattern: /\/cron\//, layer: "infrastructure", confidence: 0.8, signal: "cron/ directory" },
4175
+ // ── Presentation (UI layer) ──
4176
+ { pattern: /\/components\//, layer: "presentation", confidence: 0.85, signal: "components/ directory" },
4177
+ { pattern: /\/views\//, layer: "presentation", confidence: 0.85, signal: "views/ directory" },
4178
+ { pattern: /\/pages\//, layer: "presentation", confidence: 0.8, signal: "pages/ directory" },
4179
+ { pattern: /\/layouts\//, layer: "presentation", confidence: 0.85, signal: "layouts/ directory" },
4180
+ { pattern: /\/templates\//, layer: "presentation", confidence: 0.8, signal: "templates/ directory" },
4181
+ { pattern: /\/widgets\//, layer: "presentation", confidence: 0.8, signal: "widgets/ directory" },
4182
+ { pattern: /\/ui\//, layer: "presentation", confidence: 0.75, signal: "ui/ directory" },
4183
+ // ── Shared / utils ──
4184
+ { pattern: /\/utils\//, layer: "shared", confidence: 0.7, signal: "utils/ directory" },
4185
+ { pattern: /\/helpers\//, layer: "shared", confidence: 0.7, signal: "helpers/ directory" },
4186
+ { pattern: /\/lib\//, layer: "shared", confidence: 0.6, signal: "lib/ directory" },
4187
+ { pattern: /\/common\//, layer: "shared", confidence: 0.65, signal: "common/ directory" },
4188
+ { pattern: /\/shared\//, layer: "shared", confidence: 0.75, signal: "shared/ directory" },
4189
+ { pattern: /\/constants\//, layer: "shared", confidence: 0.7, signal: "constants/ directory" },
4190
+ // ── CLI-specific (command layer → routing) ──
4191
+ { pattern: /\/commands\//, layer: "routing", confidence: 0.8, signal: "commands/ directory", archetypes: ["cli"] },
4192
+ { pattern: /\/formatters\//, layer: "presentation", confidence: 0.8, signal: "formatters/ directory", archetypes: ["cli"] },
4193
+ { pattern: /\/scanners\//, layer: "services", confidence: 0.8, signal: "scanners/ directory", archetypes: ["cli"] },
4194
+ { pattern: /\/scoring\//, layer: "domain", confidence: 0.8, signal: "scoring/ directory", archetypes: ["cli"] },
4195
+ // ── Serverless-specific ──
4196
+ { pattern: /\/functions\//, layer: "routing", confidence: 0.8, signal: "functions/ directory", archetypes: ["serverless"] },
4197
+ { pattern: /\/lambdas\//, layer: "routing", confidence: 0.85, signal: "lambdas/ directory", archetypes: ["serverless"] },
4198
+ { pattern: /\/layers\//, layer: "shared", confidence: 0.7, signal: "Lambda layers/ directory", archetypes: ["serverless"] }
4199
+ ];
4200
+ var SUFFIX_RULES = [
4201
+ { suffix: ".controller", layer: "routing", confidence: 0.85, signal: "controller suffix" },
4202
+ { suffix: ".route", layer: "routing", confidence: 0.85, signal: "route suffix" },
4203
+ { suffix: ".router", layer: "routing", confidence: 0.85, signal: "router suffix" },
4204
+ { suffix: ".handler", layer: "routing", confidence: 0.8, signal: "handler suffix" },
4205
+ { suffix: ".middleware", layer: "middleware", confidence: 0.85, signal: "middleware suffix" },
4206
+ { suffix: ".guard", layer: "middleware", confidence: 0.85, signal: "guard suffix" },
4207
+ { suffix: ".interceptor", layer: "middleware", confidence: 0.85, signal: "interceptor suffix" },
4208
+ { suffix: ".service", layer: "services", confidence: 0.85, signal: "service suffix" },
4209
+ { suffix: ".usecase", layer: "services", confidence: 0.85, signal: "usecase suffix" },
4210
+ { suffix: ".model", layer: "domain", confidence: 0.8, signal: "model suffix" },
4211
+ { suffix: ".entity", layer: "domain", confidence: 0.85, signal: "entity suffix" },
4212
+ { suffix: ".dto", layer: "domain", confidence: 0.8, signal: "DTO suffix" },
4213
+ { suffix: ".schema", layer: "domain", confidence: 0.75, signal: "schema suffix" },
4214
+ { suffix: ".validator", layer: "domain", confidence: 0.75, signal: "validator suffix" },
4215
+ { suffix: ".repository", layer: "data-access", confidence: 0.9, signal: "repository suffix" },
4216
+ { suffix: ".repo", layer: "data-access", confidence: 0.85, signal: "repo suffix" },
4217
+ { suffix: ".dao", layer: "data-access", confidence: 0.9, signal: "dao suffix" },
4218
+ { suffix: ".migration", layer: "data-access", confidence: 0.85, signal: "migration suffix" },
4219
+ { suffix: ".adapter", layer: "infrastructure", confidence: 0.8, signal: "adapter suffix" },
4220
+ { suffix: ".client", layer: "infrastructure", confidence: 0.75, signal: "client suffix" },
4221
+ { suffix: ".provider", layer: "infrastructure", confidence: 0.7, signal: "provider suffix" },
4222
+ { suffix: ".config", layer: "config", confidence: 0.8, signal: "config suffix" },
4223
+ { suffix: ".component", layer: "presentation", confidence: 0.8, signal: "component suffix" },
4224
+ { suffix: ".page", layer: "presentation", confidence: 0.85, signal: "page suffix" },
4225
+ { suffix: ".view", layer: "presentation", confidence: 0.8, signal: "view suffix" },
4226
+ { suffix: ".layout", layer: "presentation", confidence: 0.85, signal: "layout suffix" },
4227
+ { suffix: ".util", layer: "shared", confidence: 0.7, signal: "util suffix" },
4228
+ { suffix: ".helper", layer: "shared", confidence: 0.7, signal: "helper suffix" },
4229
+ { suffix: ".constant", layer: "shared", confidence: 0.7, signal: "constant suffix" }
4230
+ ];
4231
+ var PACKAGE_LAYER_MAP = {
4232
+ // Routing/controllers
4233
+ "express": "routing",
4234
+ "fastify": "routing",
4235
+ "@nestjs/core": "routing",
4236
+ "hono": "routing",
4237
+ "koa": "routing",
4238
+ "koa-router": "routing",
4239
+ "@hapi/hapi": "routing",
4240
+ "h3": "routing",
4241
+ // Middleware
4242
+ "cors": "middleware",
4243
+ "helmet": "middleware",
4244
+ "passport": "middleware",
4245
+ "express-rate-limit": "middleware",
4246
+ "cookie-parser": "middleware",
4247
+ "body-parser": "middleware",
4248
+ "multer": "middleware",
4249
+ "morgan": "middleware",
4250
+ "compression": "middleware",
4251
+ "express-session": "middleware",
4252
+ // Services / application
4253
+ "bullmq": "services",
4254
+ "bull": "services",
4255
+ "agenda": "services",
4256
+ "pg-boss": "services",
4257
+ "inngest": "services",
4258
+ // Domain / validation
4259
+ "zod": "domain",
4260
+ "joi": "domain",
4261
+ "yup": "domain",
4262
+ "class-validator": "domain",
4263
+ "class-transformer": "domain",
4264
+ "superstruct": "domain",
4265
+ "valibot": "domain",
4266
+ // Data access / ORM
4267
+ "prisma": "data-access",
4268
+ "@prisma/client": "data-access",
4269
+ "drizzle-orm": "data-access",
4270
+ "typeorm": "data-access",
4271
+ "sequelize": "data-access",
4272
+ "knex": "data-access",
4273
+ "pg": "data-access",
4274
+ "mysql2": "data-access",
4275
+ "mongodb": "data-access",
4276
+ "mongoose": "data-access",
4277
+ "ioredis": "data-access",
4278
+ "redis": "data-access",
4279
+ "better-sqlite3": "data-access",
4280
+ "kysely": "data-access",
4281
+ "@mikro-orm/core": "data-access",
4282
+ // Infrastructure
4283
+ "@aws-sdk/client-s3": "infrastructure",
4284
+ "@aws-sdk/client-sqs": "infrastructure",
4285
+ "@aws-sdk/client-sns": "infrastructure",
4286
+ "@aws-sdk/client-ses": "infrastructure",
4287
+ "@aws-sdk/client-lambda": "infrastructure",
4288
+ "@google-cloud/storage": "infrastructure",
4289
+ "@azure/storage-blob": "infrastructure",
4290
+ "nodemailer": "infrastructure",
4291
+ "@sendgrid/mail": "infrastructure",
4292
+ "stripe": "infrastructure",
4293
+ "kafkajs": "infrastructure",
4294
+ "amqplib": "infrastructure",
4295
+ // Presentation
4296
+ "react": "presentation",
4297
+ "react-dom": "presentation",
4298
+ "vue": "presentation",
4299
+ "@angular/core": "presentation",
4300
+ "svelte": "presentation",
4301
+ // Shared
4302
+ "lodash": "shared",
4303
+ "dayjs": "shared",
4304
+ "date-fns": "shared",
4305
+ "uuid": "shared",
4306
+ "nanoid": "shared",
4307
+ // Testing
4308
+ "vitest": "testing",
4309
+ "jest": "testing",
4310
+ "mocha": "testing",
4311
+ "@playwright/test": "testing",
4312
+ "cypress": "testing",
4313
+ "supertest": "testing",
4314
+ // Observability → infrastructure
4315
+ "@sentry/node": "infrastructure",
4316
+ "@opentelemetry/api": "infrastructure",
4317
+ "pino": "infrastructure",
4318
+ "winston": "infrastructure",
4319
+ "dd-trace": "infrastructure"
4320
+ };
4321
+ var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mts", ".mjs", ".cts", ".cjs", ".svelte", ".vue"]);
4322
+ var IGNORE_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", ".nuxt", ".output", ".svelte-kit", "coverage", ".vibgrate"]);
4323
+ async function walkSourceFiles(rootDir) {
4324
+ const files = [];
4325
+ async function walk(dir) {
4326
+ let entries;
4327
+ try {
4328
+ entries = await fs5.readdir(dir, { withFileTypes: true });
4329
+ } catch {
4330
+ return;
4331
+ }
4332
+ for (const entry of entries) {
4333
+ if (entry.name.startsWith(".") && entry.name !== ".") continue;
4334
+ const fullPath = path14.join(dir, entry.name);
4335
+ if (entry.isDirectory()) {
4336
+ if (!IGNORE_DIRS.has(entry.name)) {
4337
+ await walk(fullPath);
4338
+ }
4339
+ } else if (entry.isFile()) {
4340
+ const ext = path14.extname(entry.name);
4341
+ if (SOURCE_EXTENSIONS.has(ext)) {
4342
+ files.push(path14.relative(rootDir, fullPath));
4343
+ }
4344
+ }
4345
+ }
4346
+ }
4347
+ await walk(rootDir);
4348
+ return files;
4349
+ }
4350
+ function classifyFile(filePath, archetype) {
4351
+ const normalised = filePath.replace(/\\/g, "/");
4352
+ let bestMatch = null;
4353
+ for (const rule of PATH_RULES) {
4354
+ if (rule.archetypes && rule.archetypes.length > 0 && !rule.archetypes.includes(archetype)) {
4355
+ continue;
4356
+ }
4357
+ if (rule.pattern.test(normalised)) {
4358
+ const boost = rule.archetypes ? 0.05 : 0;
4359
+ const adjustedConfidence = Math.min(rule.confidence + boost, 1);
4360
+ if (!bestMatch || adjustedConfidence > bestMatch.confidence) {
4361
+ bestMatch = { layer: rule.layer, confidence: adjustedConfidence, signal: rule.signal };
4362
+ }
4363
+ }
4364
+ }
4365
+ if (!bestMatch || bestMatch.confidence < 0.7) {
4366
+ const baseName = path14.basename(filePath, path14.extname(filePath));
4367
+ const cleanBase = baseName.replace(/\.(test|spec)$/, "");
4368
+ for (const rule of SUFFIX_RULES) {
4369
+ if (cleanBase.endsWith(rule.suffix)) {
4370
+ if (!bestMatch || rule.confidence > bestMatch.confidence) {
4371
+ bestMatch = { layer: rule.layer, confidence: rule.confidence, signal: rule.signal };
4372
+ }
4373
+ }
4374
+ }
4375
+ }
4376
+ if (bestMatch) {
4377
+ return {
4378
+ filePath,
4379
+ layer: bestMatch.layer,
4380
+ confidence: bestMatch.confidence,
4381
+ signals: [bestMatch.signal]
4382
+ };
4383
+ }
4384
+ return null;
4385
+ }
4386
+ function computeLayerDrift(packages) {
4387
+ if (packages.length === 0) {
4388
+ return { score: 100, riskLevel: "low" };
4389
+ }
4390
+ let current = 0;
4391
+ let oneBehind = 0;
4392
+ let twoPlusBehind = 0;
4393
+ let unknown = 0;
4394
+ for (const pkg2 of packages) {
4395
+ if (pkg2.majorsBehind === null) {
4396
+ unknown++;
4397
+ } else if (pkg2.majorsBehind === 0) {
4398
+ current++;
4399
+ } else if (pkg2.majorsBehind === 1) {
4400
+ oneBehind++;
4401
+ } else {
4402
+ twoPlusBehind++;
4403
+ }
4404
+ }
4405
+ const known = current + oneBehind + twoPlusBehind;
4406
+ if (known === 0) return { score: 100, riskLevel: "low" };
4407
+ const currentPct = current / known;
4408
+ const onePct = oneBehind / known;
4409
+ const twoPct = twoPlusBehind / known;
4410
+ const score = Math.round(Math.max(0, Math.min(100, currentPct * 100 - onePct * 10 - twoPct * 40)));
4411
+ const riskLevel = score >= 70 ? "low" : score >= 40 ? "moderate" : "high";
4412
+ return { score, riskLevel };
4413
+ }
4414
+ function mapToolingToLayers(tooling, services, depsByLayer) {
4415
+ const layerTooling = /* @__PURE__ */ new Map();
4416
+ const layerServices = /* @__PURE__ */ new Map();
4417
+ const pkgLayerLookup = /* @__PURE__ */ new Map();
4418
+ for (const [layer, packages] of depsByLayer) {
4419
+ for (const pkg2 of packages) {
4420
+ pkgLayerLookup.set(pkg2, layer);
4421
+ }
4422
+ }
4423
+ if (tooling) {
4424
+ for (const [, items] of Object.entries(tooling)) {
4425
+ for (const item of items) {
4426
+ const layer = pkgLayerLookup.get(item.package) ?? PACKAGE_LAYER_MAP[item.package] ?? "shared";
4427
+ if (!layerTooling.has(layer)) layerTooling.set(layer, []);
4428
+ const existing = layerTooling.get(layer);
4429
+ if (!existing.some((t) => t.package === item.package)) {
4430
+ existing.push(item);
4431
+ }
4432
+ }
4433
+ }
4434
+ }
4435
+ if (services) {
4436
+ for (const [, items] of Object.entries(services)) {
4437
+ for (const item of items) {
4438
+ const layer = pkgLayerLookup.get(item.package) ?? PACKAGE_LAYER_MAP[item.package] ?? "infrastructure";
4439
+ if (!layerServices.has(layer)) layerServices.set(layer, []);
4440
+ const existing = layerServices.get(layer);
4441
+ if (!existing.some((s) => s.package === item.package)) {
4442
+ existing.push(item);
4443
+ }
4444
+ }
4445
+ }
4446
+ }
4447
+ return { layerTooling, layerServices };
4448
+ }
4449
+ async function scanArchitecture(rootDir, projects, tooling, services) {
4450
+ const { archetype, confidence: archetypeConfidence } = detectArchetype(projects);
4451
+ const sourceFiles = await walkSourceFiles(rootDir);
4452
+ const classifications = [];
4453
+ let unclassified = 0;
4454
+ for (const file of sourceFiles) {
4455
+ const classification = classifyFile(file, archetype);
4456
+ if (classification) {
4457
+ classifications.push(classification);
4458
+ } else {
4459
+ unclassified++;
4460
+ }
4461
+ }
4462
+ const allDeps = /* @__PURE__ */ new Map();
4463
+ for (const p of projects) {
4464
+ for (const d of p.dependencies) {
4465
+ if (!allDeps.has(d.package)) {
4466
+ allDeps.set(d.package, d);
4467
+ }
4468
+ }
4469
+ }
4470
+ const depsByLayer = /* @__PURE__ */ new Map();
4471
+ for (const [pkg2] of allDeps) {
4472
+ const layer = PACKAGE_LAYER_MAP[pkg2];
4473
+ if (layer) {
4474
+ if (!depsByLayer.has(layer)) depsByLayer.set(layer, /* @__PURE__ */ new Set());
4475
+ depsByLayer.get(layer).add(pkg2);
4476
+ }
4477
+ }
4478
+ const { layerTooling, layerServices } = mapToolingToLayers(tooling, services, depsByLayer);
4479
+ const ALL_LAYERS = [
4480
+ "routing",
4481
+ "middleware",
4482
+ "services",
4483
+ "domain",
4484
+ "data-access",
4485
+ "infrastructure",
4486
+ "presentation",
4487
+ "config",
4488
+ "testing",
4489
+ "shared"
4490
+ ];
4491
+ const layerFileCounts = /* @__PURE__ */ new Map();
4492
+ for (const c of classifications) {
4493
+ layerFileCounts.set(c.layer, (layerFileCounts.get(c.layer) ?? 0) + 1);
4494
+ }
4495
+ const layers = [];
4496
+ for (const layer of ALL_LAYERS) {
4497
+ const fileCount = layerFileCounts.get(layer) ?? 0;
4498
+ const layerPkgs = depsByLayer.get(layer) ?? /* @__PURE__ */ new Set();
4499
+ const tech = layerTooling.get(layer) ?? [];
4500
+ const svc = layerServices.get(layer) ?? [];
4501
+ if (fileCount === 0 && layerPkgs.size === 0 && tech.length === 0 && svc.length === 0) {
4502
+ continue;
4503
+ }
4504
+ const packages = [];
4505
+ for (const pkg2 of layerPkgs) {
4506
+ const dep = allDeps.get(pkg2);
4507
+ if (dep) {
4508
+ packages.push({
4509
+ name: dep.package,
4510
+ version: dep.resolvedVersion,
4511
+ latestStable: dep.latestStable,
4512
+ majorsBehind: dep.majorsBehind,
4513
+ drift: dep.drift
4514
+ });
4515
+ }
4516
+ }
4517
+ const { score, riskLevel } = computeLayerDrift(packages);
4518
+ layers.push({
4519
+ layer,
4520
+ fileCount,
4521
+ driftScore: score,
4522
+ riskLevel,
4523
+ techStack: tech,
4524
+ services: svc,
4525
+ packages
4526
+ });
4527
+ }
4528
+ const LAYER_ORDER = {
4529
+ "presentation": 0,
4530
+ "routing": 1,
4531
+ "middleware": 2,
4532
+ "services": 3,
4533
+ "domain": 4,
4534
+ "data-access": 5,
4535
+ "infrastructure": 6,
4536
+ "config": 7,
4537
+ "shared": 8,
4538
+ "testing": 9
4539
+ };
4540
+ layers.sort((a, b) => (LAYER_ORDER[a.layer] ?? 99) - (LAYER_ORDER[b.layer] ?? 99));
4541
+ return {
4542
+ archetype,
4543
+ archetypeConfidence,
4544
+ layers,
4545
+ totalClassified: classifications.length,
4546
+ unclassified
4547
+ };
4548
+ }
4549
+
3912
4550
  // src/commands/scan.ts
3913
4551
  async function runScan(rootDir, opts) {
3914
4552
  const scanStart = Date.now();
@@ -3933,7 +4571,8 @@ async function runScan(rootDir, opts) {
3933
4571
  ...scanners?.tsModernity?.enabled !== false ? [{ id: "ts", label: "TypeScript modernity" }] : [],
3934
4572
  ...scanners?.fileHotspots?.enabled !== false ? [{ id: "hotspots", label: "File hotspots" }] : [],
3935
4573
  ...scanners?.dependencyGraph?.enabled !== false ? [{ id: "depgraph", label: "Dependency graph" }] : [],
3936
- ...scanners?.dependencyRisk?.enabled !== false ? [{ id: "deprisk", label: "Dependency risk" }] : []
4574
+ ...scanners?.dependencyRisk?.enabled !== false ? [{ id: "deprisk", label: "Dependency risk" }] : [],
4575
+ ...scanners?.architecture?.enabled !== false ? [{ id: "architecture", label: "Architecture layers" }] : []
3937
4576
  ] : [],
3938
4577
  { id: "drift", label: "Computing drift score" },
3939
4578
  { id: "findings", label: "Generating findings" }
@@ -4090,6 +4729,22 @@ async function runScan(rootDir, opts) {
4090
4729
  );
4091
4730
  }
4092
4731
  await Promise.all(scannerTasks);
4732
+ if (scanners?.architecture?.enabled !== false) {
4733
+ progress.startStep("architecture");
4734
+ extended.architecture = await scanArchitecture(
4735
+ rootDir,
4736
+ allProjects,
4737
+ extended.toolingInventory,
4738
+ extended.serviceDependencies
4739
+ );
4740
+ const arch = extended.architecture;
4741
+ const layerCount = arch.layers.filter((l) => l.fileCount > 0).length;
4742
+ progress.completeStep(
4743
+ "architecture",
4744
+ `${arch.archetype} \xB7 ${layerCount} layer${layerCount !== 1 ? "s" : ""} \xB7 ${arch.totalClassified} files`,
4745
+ layerCount
4746
+ );
4747
+ }
4093
4748
  }
4094
4749
  progress.startStep("drift");
4095
4750
  const drift = computeDriftScore(allProjects);
@@ -4122,7 +4777,7 @@ async function runScan(rootDir, opts) {
4122
4777
  schemaVersion: "1.0",
4123
4778
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4124
4779
  vibgrateVersion: VERSION,
4125
- rootPath: path14.basename(rootDir),
4780
+ rootPath: path15.basename(rootDir),
4126
4781
  ...vcs.type !== "unknown" ? { vcs } : {},
4127
4782
  projects: allProjects,
4128
4783
  drift,
@@ -4132,7 +4787,7 @@ async function runScan(rootDir, opts) {
4132
4787
  filesScanned
4133
4788
  };
4134
4789
  if (opts.baseline) {
4135
- const baselinePath = path14.resolve(opts.baseline);
4790
+ const baselinePath = path15.resolve(opts.baseline);
4136
4791
  if (await pathExists(baselinePath)) {
4137
4792
  try {
4138
4793
  const baseline = await readJsonFile(baselinePath);
@@ -4143,15 +4798,15 @@ async function runScan(rootDir, opts) {
4143
4798
  }
4144
4799
  }
4145
4800
  }
4146
- const vibgrateDir = path14.join(rootDir, ".vibgrate");
4801
+ const vibgrateDir = path15.join(rootDir, ".vibgrate");
4147
4802
  await ensureDir(vibgrateDir);
4148
- await writeJsonFile(path14.join(vibgrateDir, "scan_result.json"), artifact);
4803
+ await writeJsonFile(path15.join(vibgrateDir, "scan_result.json"), artifact);
4149
4804
  for (const project of allProjects) {
4150
4805
  if (project.drift && project.path) {
4151
- const projectDir = path14.resolve(rootDir, project.path);
4152
- const projectVibgrateDir = path14.join(projectDir, ".vibgrate");
4806
+ const projectDir = path15.resolve(rootDir, project.path);
4807
+ const projectVibgrateDir = path15.join(projectDir, ".vibgrate");
4153
4808
  await ensureDir(projectVibgrateDir);
4154
- await writeJsonFile(path14.join(projectVibgrateDir, "project_score.json"), {
4809
+ await writeJsonFile(path15.join(projectVibgrateDir, "project_score.json"), {
4155
4810
  projectId: project.projectId,
4156
4811
  name: project.name,
4157
4812
  type: project.type,
@@ -4168,7 +4823,7 @@ async function runScan(rootDir, opts) {
4168
4823
  if (opts.format === "json") {
4169
4824
  const jsonStr = JSON.stringify(artifact, null, 2);
4170
4825
  if (opts.out) {
4171
- await writeTextFile(path14.resolve(opts.out), jsonStr);
4826
+ await writeTextFile(path15.resolve(opts.out), jsonStr);
4172
4827
  console.log(chalk5.green("\u2714") + ` JSON written to ${opts.out}`);
4173
4828
  } else {
4174
4829
  console.log(jsonStr);
@@ -4177,7 +4832,7 @@ async function runScan(rootDir, opts) {
4177
4832
  const sarif = formatSarif(artifact);
4178
4833
  const sarifStr = JSON.stringify(sarif, null, 2);
4179
4834
  if (opts.out) {
4180
- await writeTextFile(path14.resolve(opts.out), sarifStr);
4835
+ await writeTextFile(path15.resolve(opts.out), sarifStr);
4181
4836
  console.log(chalk5.green("\u2714") + ` SARIF written to ${opts.out}`);
4182
4837
  } else {
4183
4838
  console.log(sarifStr);
@@ -4186,7 +4841,7 @@ async function runScan(rootDir, opts) {
4186
4841
  const text = formatText(artifact);
4187
4842
  console.log(text);
4188
4843
  if (opts.out) {
4189
- await writeTextFile(path14.resolve(opts.out), text);
4844
+ await writeTextFile(path15.resolve(opts.out), text);
4190
4845
  }
4191
4846
  }
4192
4847
  return artifact;
@@ -4245,7 +4900,7 @@ async function autoPush(artifact, rootDir, opts) {
4245
4900
  }
4246
4901
  }
4247
4902
  var scanCommand = new Command3("scan").description("Scan a project for upgrade drift").argument("[path]", "Path to scan", ".").option("--out <file>", "Output file path").option("--format <format>", "Output format (text|json|sarif)", "text").option("--fail-on <level>", "Fail on warn or error").option("--baseline <file>", "Compare against baseline").option("--changed-only", "Only scan changed files").option("--concurrency <n>", "Max concurrent npm calls", "8").option("--push", "Auto-push results to Vibgrate API after scan").option("--dsn <dsn>", "DSN token for push (or use VIBGRATE_DSN env)").option("--region <region>", "Override data residency region for push (us, eu)").option("--strict", "Fail on push errors").action(async (targetPath, opts) => {
4248
- const rootDir = path14.resolve(targetPath);
4903
+ const rootDir = path15.resolve(targetPath);
4249
4904
  if (!await pathExists(rootDir)) {
4250
4905
  console.error(chalk5.red(`Path does not exist: ${rootDir}`));
4251
4906
  process.exit(1);
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-VXZT34Y5.js";
5
5
  import {
6
6
  baselineCommand
7
- } from "./chunk-POMRKRQN.js";
7
+ } from "./chunk-OPEOSIRY.js";
8
8
  import {
9
9
  VERSION,
10
10
  dsnCommand,
@@ -15,7 +15,7 @@ import {
15
15
  readJsonFile,
16
16
  scanCommand,
17
17
  writeDefaultConfig
18
- } from "./chunk-4N4BALQQ.js";
18
+ } from "./chunk-QZV77UWV.js";
19
19
 
20
20
  // src/cli.ts
21
21
  import { Command as Command4 } from "commander";
@@ -38,7 +38,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
38
38
  console.log(chalk.green("\u2714") + ` Created ${chalk.bold("vibgrate.config.ts")}`);
39
39
  }
40
40
  if (opts.baseline) {
41
- const { runBaseline } = await import("./baseline-RV3GAW72.js");
41
+ const { runBaseline } = await import("./baseline-M445KUZ4.js");
42
42
  await runBaseline(rootDir);
43
43
  }
44
44
  console.log("");
@@ -222,7 +222,7 @@ var updateCommand = new Command3("update").description("Update vibgrate to the l
222
222
 
223
223
  // src/cli.ts
224
224
  var program = new Command4();
225
- program.name("vibgrate").description("Continuous Upgrade Drift Intelligence for Node & .NET").version(VERSION);
225
+ program.name("vibgrate").description("Continuous Drift Intelligence for Node & .NET").version(VERSION);
226
226
  program.addCommand(initCommand);
227
227
  program.addCommand(scanCommand);
228
228
  program.addCommand(baselineCommand);
package/dist/index.d.ts CHANGED
@@ -110,6 +110,7 @@ interface ScannersConfig {
110
110
  fileHotspots?: ScannerToggle;
111
111
  securityPosture?: ScannerToggle;
112
112
  serviceDependencies?: ScannerToggle;
113
+ architecture?: ScannerToggle;
113
114
  }
114
115
  interface VibgrateConfig {
115
116
  include?: string[];
@@ -246,6 +247,48 @@ interface ServiceDependenciesResult {
246
247
  storage: ServiceDependencyItem[];
247
248
  search: ServiceDependencyItem[];
248
249
  }
250
+ /** Detected project archetype (fingerprint) */
251
+ type ProjectArchetype = 'nextjs' | 'remix' | 'sveltekit' | 'nuxt' | 'nestjs' | 'express' | 'fastify' | 'hono' | 'koa' | 'serverless' | 'library' | 'cli' | 'monorepo' | 'unknown';
252
+ /** Architectural layer classification */
253
+ type ArchitectureLayer = 'routing' | 'middleware' | 'services' | 'domain' | 'data-access' | 'infrastructure' | 'presentation' | 'config' | 'testing' | 'shared';
254
+ /** Per-layer aggregated data */
255
+ interface LayerSummary {
256
+ /** The layer name */
257
+ layer: ArchitectureLayer;
258
+ /** Number of files in this layer */
259
+ fileCount: number;
260
+ /** Drift score for dependencies used in this layer (0–100) */
261
+ driftScore: number;
262
+ /** Risk level derived from drift score */
263
+ riskLevel: RiskLevel;
264
+ /** Tech stack components detected in this layer */
265
+ techStack: InventoryItem[];
266
+ /** Services/integrations used in this layer */
267
+ services: ServiceDependencyItem[];
268
+ /** Packages referenced in this layer with their drift status */
269
+ packages: LayerPackageRef[];
270
+ }
271
+ /** Package reference within a layer */
272
+ interface LayerPackageRef {
273
+ name: string;
274
+ version: string | null;
275
+ latestStable: string | null;
276
+ majorsBehind: number | null;
277
+ drift: 'current' | 'minor-behind' | 'major-behind' | 'unknown';
278
+ }
279
+ /** Full architecture detection result */
280
+ interface ArchitectureResult {
281
+ /** Detected project archetype */
282
+ archetype: ProjectArchetype;
283
+ /** Confidence of archetype detection (0–1) */
284
+ archetypeConfidence: number;
285
+ /** Per-layer summaries with drift + tech data */
286
+ layers: LayerSummary[];
287
+ /** Total files classified */
288
+ totalClassified: number;
289
+ /** Files that could not be classified */
290
+ unclassified: number;
291
+ }
249
292
  interface ExtendedScanResults {
250
293
  platformMatrix?: PlatformMatrixResult;
251
294
  dependencyRisk?: DependencyRiskResult;
@@ -257,6 +300,7 @@ interface ExtendedScanResults {
257
300
  fileHotspots?: FileHotspotsResult;
258
301
  securityPosture?: SecurityPostureResult;
259
302
  serviceDependencies?: ServiceDependenciesResult;
303
+ architecture?: ArchitectureResult;
260
304
  }
261
305
 
262
306
  declare function runScan(rootDir: string, opts: ScanOptions): Promise<ScanArtifact>;
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  formatText,
8
8
  generateFindings,
9
9
  runScan
10
- } from "./chunk-4N4BALQQ.js";
10
+ } from "./chunk-QZV77UWV.js";
11
11
  export {
12
12
  computeDriftScore,
13
13
  formatMarkdown,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibgrate/cli",
3
- "version": "1.0.14",
3
+ "version": "1.0.15",
4
4
  "description": "CLI for measuring upgrade drift across Node & .NET projects",
5
5
  "type": "module",
6
6
  "bin": {