@vibgrate/cli 1.0.13 → 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 +1 -1
- package/dist/{baseline-OPUTCJOQ.js → baseline-M445KUZ4.js} +2 -2
- package/dist/{chunk-QHBZIYWP.js → chunk-OPEOSIRY.js} +1 -1
- package/dist/{chunk-S4C6KWCP.js → chunk-QZV77UWV.js} +714 -57
- package/dist/cli.js +4 -4
- package/dist/index.d.ts +44 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/DOCS.md
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
// src/utils/fs.ts
|
|
2
2
|
import * as fs from "fs/promises";
|
|
3
|
+
import * as os from "os";
|
|
3
4
|
import * as path from "path";
|
|
5
|
+
|
|
6
|
+
// src/utils/semaphore.ts
|
|
7
|
+
var Semaphore = class {
|
|
8
|
+
available;
|
|
9
|
+
queue = [];
|
|
10
|
+
constructor(max) {
|
|
11
|
+
this.available = max;
|
|
12
|
+
}
|
|
13
|
+
async run(fn) {
|
|
14
|
+
await this.acquire();
|
|
15
|
+
try {
|
|
16
|
+
return await fn();
|
|
17
|
+
} finally {
|
|
18
|
+
this.release();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
acquire() {
|
|
22
|
+
if (this.available > 0) {
|
|
23
|
+
this.available--;
|
|
24
|
+
return Promise.resolve();
|
|
25
|
+
}
|
|
26
|
+
return new Promise((resolve6) => this.queue.push(resolve6));
|
|
27
|
+
}
|
|
28
|
+
release() {
|
|
29
|
+
const next = this.queue.shift();
|
|
30
|
+
if (next) next();
|
|
31
|
+
else this.available++;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// src/utils/fs.ts
|
|
4
36
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
5
37
|
"node_modules",
|
|
6
38
|
".git",
|
|
@@ -20,28 +52,27 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
20
52
|
]);
|
|
21
53
|
async function findFiles(rootDir, predicate) {
|
|
22
54
|
const results = [];
|
|
55
|
+
const maxConcurrentReads = Math.max(8, Math.min(64, os.availableParallelism() * 4));
|
|
56
|
+
const readDirSemaphore = new Semaphore(maxConcurrentReads);
|
|
23
57
|
async function walk(dir) {
|
|
24
58
|
let entries;
|
|
25
59
|
try {
|
|
26
|
-
|
|
27
|
-
entries = dirents.map((d) => ({
|
|
28
|
-
name: d.name,
|
|
29
|
-
isDirectory: d.isDirectory(),
|
|
30
|
-
isFile: d.isFile()
|
|
31
|
-
}));
|
|
60
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
32
61
|
} catch {
|
|
33
62
|
return;
|
|
34
63
|
}
|
|
64
|
+
const subDirectoryWalks = [];
|
|
35
65
|
for (const e of entries) {
|
|
36
|
-
if (e.isDirectory) {
|
|
66
|
+
if (e.isDirectory()) {
|
|
37
67
|
if (SKIP_DIRS.has(e.name)) continue;
|
|
38
|
-
|
|
39
|
-
} else if (e.isFile && predicate(e.name)) {
|
|
68
|
+
subDirectoryWalks.push(readDirSemaphore.run(() => walk(path.join(dir, e.name))));
|
|
69
|
+
} else if (e.isFile() && predicate(e.name)) {
|
|
40
70
|
results.push(path.join(dir, e.name));
|
|
41
71
|
}
|
|
42
72
|
}
|
|
73
|
+
await Promise.all(subDirectoryWalks);
|
|
43
74
|
}
|
|
44
|
-
await walk(rootDir);
|
|
75
|
+
await readDirSemaphore.run(() => walk(rootDir));
|
|
45
76
|
return results;
|
|
46
77
|
}
|
|
47
78
|
async function findPackageJsonFiles(rootDir) {
|
|
@@ -156,12 +187,12 @@ function eolScore(projects) {
|
|
|
156
187
|
}
|
|
157
188
|
function computeDriftScore(projects) {
|
|
158
189
|
const rs = runtimeScore(projects);
|
|
159
|
-
const
|
|
190
|
+
const fs6 = frameworkScore(projects);
|
|
160
191
|
const ds = dependencyScore(projects);
|
|
161
192
|
const es = eolScore(projects);
|
|
162
193
|
const components = [
|
|
163
194
|
{ score: rs, weight: 0.25 },
|
|
164
|
-
{ score:
|
|
195
|
+
{ score: fs6, weight: 0.25 },
|
|
165
196
|
{ score: ds, weight: 0.3 },
|
|
166
197
|
{ score: es, weight: 0.2 }
|
|
167
198
|
];
|
|
@@ -172,7 +203,7 @@ function computeDriftScore(projects) {
|
|
|
172
203
|
riskLevel: "low",
|
|
173
204
|
components: {
|
|
174
205
|
runtimeScore: Math.round(rs ?? 100),
|
|
175
|
-
frameworkScore: Math.round(
|
|
206
|
+
frameworkScore: Math.round(fs6 ?? 100),
|
|
176
207
|
dependencyScore: Math.round(ds ?? 100),
|
|
177
208
|
eolScore: Math.round(es ?? 100)
|
|
178
209
|
}
|
|
@@ -190,7 +221,7 @@ function computeDriftScore(projects) {
|
|
|
190
221
|
else riskLevel = "high";
|
|
191
222
|
const measured = [];
|
|
192
223
|
if (rs !== null) measured.push("runtime");
|
|
193
|
-
if (
|
|
224
|
+
if (fs6 !== null) measured.push("framework");
|
|
194
225
|
if (ds !== null) measured.push("dependency");
|
|
195
226
|
if (es !== null) measured.push("eol");
|
|
196
227
|
return {
|
|
@@ -198,7 +229,7 @@ function computeDriftScore(projects) {
|
|
|
198
229
|
riskLevel,
|
|
199
230
|
components: {
|
|
200
231
|
runtimeScore: Math.round(rs ?? 100),
|
|
201
|
-
frameworkScore: Math.round(
|
|
232
|
+
frameworkScore: Math.round(fs6 ?? 100),
|
|
202
233
|
dependencyScore: Math.round(ds ?? 100),
|
|
203
234
|
eolScore: Math.round(es ?? 100)
|
|
204
235
|
},
|
|
@@ -554,6 +585,122 @@ function formatExtended(ext) {
|
|
|
554
585
|
lines.push("");
|
|
555
586
|
}
|
|
556
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("");
|
|
557
704
|
return lines;
|
|
558
705
|
}
|
|
559
706
|
function generatePriorityActions(artifact) {
|
|
@@ -1019,7 +1166,7 @@ var pushCommand = new Command2("push").description("Push scan results to Vibgrat
|
|
|
1019
1166
|
});
|
|
1020
1167
|
|
|
1021
1168
|
// src/commands/scan.ts
|
|
1022
|
-
import * as
|
|
1169
|
+
import * as path15 from "path";
|
|
1023
1170
|
import { Command as Command3 } from "commander";
|
|
1024
1171
|
import chalk5 from "chalk";
|
|
1025
1172
|
|
|
@@ -1620,35 +1767,6 @@ async function scanOneCsproj(csprojPath, rootDir) {
|
|
|
1620
1767
|
};
|
|
1621
1768
|
}
|
|
1622
1769
|
|
|
1623
|
-
// src/utils/semaphore.ts
|
|
1624
|
-
var Semaphore = class {
|
|
1625
|
-
available;
|
|
1626
|
-
queue = [];
|
|
1627
|
-
constructor(max) {
|
|
1628
|
-
this.available = max;
|
|
1629
|
-
}
|
|
1630
|
-
async run(fn) {
|
|
1631
|
-
await this.acquire();
|
|
1632
|
-
try {
|
|
1633
|
-
return await fn();
|
|
1634
|
-
} finally {
|
|
1635
|
-
this.release();
|
|
1636
|
-
}
|
|
1637
|
-
}
|
|
1638
|
-
acquire() {
|
|
1639
|
-
if (this.available > 0) {
|
|
1640
|
-
this.available--;
|
|
1641
|
-
return Promise.resolve();
|
|
1642
|
-
}
|
|
1643
|
-
return new Promise((resolve6) => this.queue.push(resolve6));
|
|
1644
|
-
}
|
|
1645
|
-
release() {
|
|
1646
|
-
const next = this.queue.shift();
|
|
1647
|
-
if (next) next();
|
|
1648
|
-
else this.available++;
|
|
1649
|
-
}
|
|
1650
|
-
};
|
|
1651
|
-
|
|
1652
1770
|
// src/config.ts
|
|
1653
1771
|
import * as path6 from "path";
|
|
1654
1772
|
import * as fs2 from "fs/promises";
|
|
@@ -3907,6 +4025,528 @@ function scanServiceDependencies(projects) {
|
|
|
3907
4025
|
return result;
|
|
3908
4026
|
}
|
|
3909
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
|
+
|
|
3910
4550
|
// src/commands/scan.ts
|
|
3911
4551
|
async function runScan(rootDir, opts) {
|
|
3912
4552
|
const scanStart = Date.now();
|
|
@@ -3931,7 +4571,8 @@ async function runScan(rootDir, opts) {
|
|
|
3931
4571
|
...scanners?.tsModernity?.enabled !== false ? [{ id: "ts", label: "TypeScript modernity" }] : [],
|
|
3932
4572
|
...scanners?.fileHotspots?.enabled !== false ? [{ id: "hotspots", label: "File hotspots" }] : [],
|
|
3933
4573
|
...scanners?.dependencyGraph?.enabled !== false ? [{ id: "depgraph", label: "Dependency graph" }] : [],
|
|
3934
|
-
...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" }] : []
|
|
3935
4576
|
] : [],
|
|
3936
4577
|
{ id: "drift", label: "Computing drift score" },
|
|
3937
4578
|
{ id: "findings", label: "Generating findings" }
|
|
@@ -4088,6 +4729,22 @@ async function runScan(rootDir, opts) {
|
|
|
4088
4729
|
);
|
|
4089
4730
|
}
|
|
4090
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
|
+
}
|
|
4091
4748
|
}
|
|
4092
4749
|
progress.startStep("drift");
|
|
4093
4750
|
const drift = computeDriftScore(allProjects);
|
|
@@ -4120,7 +4777,7 @@ async function runScan(rootDir, opts) {
|
|
|
4120
4777
|
schemaVersion: "1.0",
|
|
4121
4778
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4122
4779
|
vibgrateVersion: VERSION,
|
|
4123
|
-
rootPath:
|
|
4780
|
+
rootPath: path15.basename(rootDir),
|
|
4124
4781
|
...vcs.type !== "unknown" ? { vcs } : {},
|
|
4125
4782
|
projects: allProjects,
|
|
4126
4783
|
drift,
|
|
@@ -4130,7 +4787,7 @@ async function runScan(rootDir, opts) {
|
|
|
4130
4787
|
filesScanned
|
|
4131
4788
|
};
|
|
4132
4789
|
if (opts.baseline) {
|
|
4133
|
-
const baselinePath =
|
|
4790
|
+
const baselinePath = path15.resolve(opts.baseline);
|
|
4134
4791
|
if (await pathExists(baselinePath)) {
|
|
4135
4792
|
try {
|
|
4136
4793
|
const baseline = await readJsonFile(baselinePath);
|
|
@@ -4141,15 +4798,15 @@ async function runScan(rootDir, opts) {
|
|
|
4141
4798
|
}
|
|
4142
4799
|
}
|
|
4143
4800
|
}
|
|
4144
|
-
const vibgrateDir =
|
|
4801
|
+
const vibgrateDir = path15.join(rootDir, ".vibgrate");
|
|
4145
4802
|
await ensureDir(vibgrateDir);
|
|
4146
|
-
await writeJsonFile(
|
|
4803
|
+
await writeJsonFile(path15.join(vibgrateDir, "scan_result.json"), artifact);
|
|
4147
4804
|
for (const project of allProjects) {
|
|
4148
4805
|
if (project.drift && project.path) {
|
|
4149
|
-
const projectDir =
|
|
4150
|
-
const projectVibgrateDir =
|
|
4806
|
+
const projectDir = path15.resolve(rootDir, project.path);
|
|
4807
|
+
const projectVibgrateDir = path15.join(projectDir, ".vibgrate");
|
|
4151
4808
|
await ensureDir(projectVibgrateDir);
|
|
4152
|
-
await writeJsonFile(
|
|
4809
|
+
await writeJsonFile(path15.join(projectVibgrateDir, "project_score.json"), {
|
|
4153
4810
|
projectId: project.projectId,
|
|
4154
4811
|
name: project.name,
|
|
4155
4812
|
type: project.type,
|
|
@@ -4166,7 +4823,7 @@ async function runScan(rootDir, opts) {
|
|
|
4166
4823
|
if (opts.format === "json") {
|
|
4167
4824
|
const jsonStr = JSON.stringify(artifact, null, 2);
|
|
4168
4825
|
if (opts.out) {
|
|
4169
|
-
await writeTextFile(
|
|
4826
|
+
await writeTextFile(path15.resolve(opts.out), jsonStr);
|
|
4170
4827
|
console.log(chalk5.green("\u2714") + ` JSON written to ${opts.out}`);
|
|
4171
4828
|
} else {
|
|
4172
4829
|
console.log(jsonStr);
|
|
@@ -4175,7 +4832,7 @@ async function runScan(rootDir, opts) {
|
|
|
4175
4832
|
const sarif = formatSarif(artifact);
|
|
4176
4833
|
const sarifStr = JSON.stringify(sarif, null, 2);
|
|
4177
4834
|
if (opts.out) {
|
|
4178
|
-
await writeTextFile(
|
|
4835
|
+
await writeTextFile(path15.resolve(opts.out), sarifStr);
|
|
4179
4836
|
console.log(chalk5.green("\u2714") + ` SARIF written to ${opts.out}`);
|
|
4180
4837
|
} else {
|
|
4181
4838
|
console.log(sarifStr);
|
|
@@ -4184,7 +4841,7 @@ async function runScan(rootDir, opts) {
|
|
|
4184
4841
|
const text = formatText(artifact);
|
|
4185
4842
|
console.log(text);
|
|
4186
4843
|
if (opts.out) {
|
|
4187
|
-
await writeTextFile(
|
|
4844
|
+
await writeTextFile(path15.resolve(opts.out), text);
|
|
4188
4845
|
}
|
|
4189
4846
|
}
|
|
4190
4847
|
return artifact;
|
|
@@ -4243,7 +4900,7 @@ async function autoPush(artifact, rootDir, opts) {
|
|
|
4243
4900
|
}
|
|
4244
4901
|
}
|
|
4245
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) => {
|
|
4246
|
-
const rootDir =
|
|
4903
|
+
const rootDir = path15.resolve(targetPath);
|
|
4247
4904
|
if (!await pathExists(rootDir)) {
|
|
4248
4905
|
console.error(chalk5.red(`Path does not exist: ${rootDir}`));
|
|
4249
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-
|
|
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-
|
|
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-
|
|
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
|
|
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