@ronaldjdevfs/forge 1.0.1

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.
Files changed (56) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +9 -0
  3. package/README.md +338 -0
  4. package/logo.png +0 -0
  5. package/package.json +47 -0
  6. package/skills/forge/SKILL.md +207 -0
  7. package/skills/forge/command/forge.md +110 -0
  8. package/skills/forge/profiles/express-mongodb.md +289 -0
  9. package/skills/forge/profiles/express-postgres.md +164 -0
  10. package/skills/forge/profiles/express-prisma.md +116 -0
  11. package/skills/forge/profiles/fastify-postgres.md +110 -0
  12. package/skills/forge/profiles/nestjs-prisma.md +160 -0
  13. package/skills/forge/reference/cast.md +77 -0
  14. package/skills/forge/reference/chain.md +73 -0
  15. package/skills/forge/reference/forge.md +44 -0
  16. package/skills/forge/reference/inscribe.md +129 -0
  17. package/skills/forge/reference/inspect.md +58 -0
  18. package/skills/forge/reference/patterns.md +108 -0
  19. package/skills/forge/reference/principles.md +29 -0
  20. package/skills/forge/reference/quench.md +74 -0
  21. package/skills/forge/reference/reforge.md +40 -0
  22. package/skills/forge/reference/relocate.md +38 -0
  23. package/skills/forge/reference/smelt.md +31 -0
  24. package/skills/forge/reference/temper.md +49 -0
  25. package/skills/forge/scripts/architecture.mjs +176 -0
  26. package/skills/forge/scripts/armorer.mjs +421 -0
  27. package/skills/forge/scripts/bootstrap.mjs +187 -0
  28. package/skills/forge/scripts/chain.mjs +258 -0
  29. package/skills/forge/scripts/context.mjs +237 -0
  30. package/skills/forge/scripts/detect.mjs +843 -0
  31. package/skills/forge/scripts/graph.mjs +594 -0
  32. package/skills/forge/scripts/inspect.mjs +193 -0
  33. package/skills/forge/scripts/profile.mjs +92 -0
  34. package/skills/forge/templates/feature/controller.ts.md +66 -0
  35. package/skills/forge/templates/feature/entity.ts.md +11 -0
  36. package/skills/forge/templates/feature/mapper.ts.md +18 -0
  37. package/skills/forge/templates/feature/repository-impl.ts.md +59 -0
  38. package/skills/forge/templates/feature/repository-interface.ts.md +12 -0
  39. package/skills/forge/templates/feature/routes.ts.md +17 -0
  40. package/skills/forge/templates/feature/schema.ts.md +16 -0
  41. package/skills/forge/templates/feature/use-case.ts.md +19 -0
  42. package/skills/forge/templates/infra/mail.ts.md +31 -0
  43. package/skills/forge/templates/infra/mongodb.ts.md +12 -0
  44. package/skills/forge/templates/infra/prisma.ts.md +21 -0
  45. package/skills/forge/templates/infra/redis.ts.md +30 -0
  46. package/skills/forge/templates/platform/config.ts.md +37 -0
  47. package/skills/forge/templates/platform/database.ts.md +40 -0
  48. package/skills/forge/templates/platform/di.ts.md +18 -0
  49. package/skills/forge/templates/platform/http.ts.md +29 -0
  50. package/skills/forge/templates/platform/logger.ts.md +38 -0
  51. package/skills/forge/templates/platform/server.ts.md +25 -0
  52. package/skills/forge/templates/shared/contract.ts.md +20 -0
  53. package/skills/forge/templates/shared/error.ts.md +27 -0
  54. package/skills/forge/templates/shared/type.ts.md +18 -0
  55. package/skills/forge/templates/shared/util.ts.md +23 -0
  56. package/src/cli.js +179 -0
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync, mkdirSync, writeFileSync, readdirSync, statSync, renameSync } from "fs";
4
+ import { join } from "path";
5
+ import { buildContext } from "./context.mjs";
6
+ import { detectProfile } from "./profile.mjs";
7
+
8
+ const ROOT = process.cwd();
9
+ const SRC = join(ROOT, "src");
10
+
11
+ function isDir(path) {
12
+ try { return existsSync(path) && statSync(path).isDirectory(); }
13
+ catch { return false; }
14
+ }
15
+
16
+ function listDir(path) {
17
+ try { return readdirSync(path).filter(e => isDir(join(path, e))); }
18
+ catch { return []; }
19
+ }
20
+
21
+ function ensureDir(path) {
22
+ if (!existsSync(path)) {
23
+ mkdirSync(path, { recursive: true });
24
+ return true;
25
+ }
26
+ return false;
27
+ }
28
+
29
+ function writeIndex(path, content = "") {
30
+ const indexFile = join(path, "index.ts");
31
+ if (!existsSync(indexFile)) {
32
+ writeFileSync(indexFile, content, "utf-8");
33
+ }
34
+ }
35
+
36
+ const PLATFORM_COMPONENTS = {
37
+ config: 'export * from "./config";\n',
38
+ database: 'export * from "./database";\n',
39
+ http: 'export * from "./http";\n',
40
+ server: 'export * from "./server";\n',
41
+ logger: 'export * from "./logger";\n',
42
+ cache: 'export * from "./cache";\n',
43
+ security: 'export * from "./security";\n',
44
+ events: 'export * from "./events";\n',
45
+ scheduler: 'export * from "./scheduler";\n',
46
+ observability: 'export * from "./observability";\n',
47
+ di: 'export * from "./di";\n',
48
+ };
49
+
50
+ const SHARED_COMPONENTS = {
51
+ errors: 'export * from "./errors";\n',
52
+ contracts: 'export * from "./contracts";\n',
53
+ types: 'export * from "./types";\n',
54
+ utils: 'export * from "./utils";\n',
55
+ };
56
+
57
+ function getProfileComponents(profile) {
58
+ const base = ["config", "server", "logger", "di"];
59
+
60
+ if (profile.includes("express") || profile.includes("fastify") || profile.includes("nestjs")) {
61
+ base.push("http");
62
+ }
63
+
64
+ if (profile.includes("prisma") || profile.includes("mongodb") || profile.includes("postgres") || profile.includes("pg")) {
65
+ base.push("database");
66
+ }
67
+
68
+ if (profile.includes("redis")) {
69
+ base.push("cache");
70
+ }
71
+
72
+ return [...new Set(base)];
73
+ }
74
+
75
+ export async function bootstrapPlatform(projectRoot = ROOT) {
76
+ const src = join(projectRoot, "src");
77
+ const ctx = await buildContext(projectRoot);
78
+ const profile = detectProfile(ctx);
79
+ const created = { platform: [], shared: [], infra: [] };
80
+
81
+ /* ── 1. Ensure src/ exists ── */
82
+ ensureDir(src);
83
+
84
+ /* ── 2. Platform Layer ── */
85
+ const platformDir = join(src, "platform");
86
+ const wasCreated = ensureDir(platformDir);
87
+
88
+ const profileComps = getProfileComponents(profile);
89
+ for (const comp of profileComps) {
90
+ const compDir = join(platformDir, comp);
91
+ if (ensureDir(compDir)) {
92
+ writeIndex(compDir, `// ${comp} — Platform Component\n`);
93
+ created.platform.push(comp);
94
+ }
95
+ }
96
+
97
+ if (wasCreated || created.platform.length > 0) {
98
+ writeIndex(platformDir);
99
+ }
100
+
101
+ /* ── 3. Shared Layer ── */
102
+ const sharedDir = join(src, "shared");
103
+ if (ensureDir(sharedDir)) {
104
+ for (const [comp, content] of Object.entries(SHARED_COMPONENTS)) {
105
+ const compDir = join(sharedDir, comp);
106
+ if (ensureDir(compDir)) {
107
+ writeIndex(compDir, `// ${comp} — Shared Component\n`);
108
+ created.shared.push(comp);
109
+ }
110
+ }
111
+ writeIndex(sharedDir);
112
+ }
113
+
114
+ /* ── 4. Infra Layer ── */
115
+ const infraDir = join(src, "infra");
116
+ if (ensureDir(infraDir)) {
117
+ if (profile.includes("prisma")) {
118
+ const prismaDir = join(infraDir, "prisma");
119
+ if (ensureDir(prismaDir)) {
120
+ writeIndex(prismaDir, "// Prisma — ORM\n");
121
+ created.infra.push("prisma");
122
+ }
123
+ }
124
+ if (profile.includes("mongodb") || profile.includes("mongoose")) {
125
+ const mongoDir = join(infraDir, "mongodb");
126
+ if (ensureDir(mongoDir)) {
127
+ writeIndex(mongoDir, "// MongoDB — Database\n");
128
+ created.infra.push("mongodb");
129
+ }
130
+ }
131
+ if (profile.includes("redis")) {
132
+ const redisDir = join(infraDir, "redis");
133
+ if (ensureDir(redisDir)) {
134
+ writeIndex(redisDir, "// Redis — Cache/Queue\n");
135
+ created.infra.push("redis");
136
+ }
137
+ }
138
+ writeIndex(infraDir);
139
+ }
140
+
141
+ /* ── 5. Organize existing orphan dirs ── */
142
+ const orphanDirs = listDir(src).filter(d =>
143
+ !["platform", "features", "shared", "infra", "infrastructure", "core", "lib", "adapters", "application", "domain", "setting"].includes(d)
144
+ && !d.startsWith(".")
145
+ );
146
+
147
+ for (const dir of orphanDirs) {
148
+ const srcPath = join(src, dir);
149
+ if (!isDir(srcPath)) continue;
150
+
151
+ const lower = dir.toLowerCase();
152
+ let target = null;
153
+
154
+ if (["config", "database", "http", "server", "logger", "cache", "security", "events", "di", "middleware"].includes(lower)) {
155
+ target = join(platformDir, lower);
156
+ } else if (["errors", "contracts", "types", "utils", "helpers", "constants", "enums"].includes(lower)) {
157
+ target = join(sharedDir, lower);
158
+ } else if (["prisma", "mongodb", "postgres", "redis", "mail", "s3", "stripe", "queue"].includes(lower)) {
159
+ target = join(infraDir, lower);
160
+ }
161
+
162
+ if (target && !existsSync(target)) {
163
+ try {
164
+ renameSync(srcPath, target);
165
+ console.log(` → Movido ${dir}/ → ${target.replace(projectRoot + "/", "")}`);
166
+ } catch (e) {
167
+ console.log(` ⚠ No se pudo mover ${dir}/: ${e.message}`);
168
+ }
169
+ }
170
+ }
171
+
172
+ return { created, profile, hasPlatform: isDir(platformDir), hasShared: isDir(sharedDir), hasInfra: isDir(infraDir) };
173
+ }
174
+
175
+ async function main() {
176
+ console.log("\n Bootstrap — Platform Initialization\n");
177
+ const result = await bootstrapPlatform();
178
+ console.log(` Profile: ${result.profile}`);
179
+ console.log(` Platform: ${result.created.platform.join(", ") || "(existing)"}`);
180
+ console.log(` Shared: ${result.created.shared.join(", ") || "(existing)"}`);
181
+ console.log(` Infra: ${result.created.infra.join(", ") || "(existing)"}`);
182
+ console.log();
183
+ }
184
+
185
+ if (process.argv[1] && (process.argv[1].endsWith("bootstrap.mjs") || process.argv[1].endsWith("bootstrap.js"))) {
186
+ main().catch(console.error);
187
+ }
@@ -0,0 +1,258 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { buildGraph } from "./graph.mjs";
4
+
5
+ export function buildDependencyGraph(projectRoot) {
6
+ const graph = buildGraph(projectRoot);
7
+
8
+ const layers = {
9
+ platform: [],
10
+ features: [],
11
+ shared: [],
12
+ infra: [],
13
+ };
14
+
15
+ for (const n of graph.nodes) {
16
+ if (n.type === "platform") layers.platform.push(n);
17
+ else if (n.type === "feature") layers.features.push(n);
18
+ else if (n.type === "shared") layers.shared.push(n);
19
+ else if (n.type === "infra") layers.infra.push(n);
20
+ }
21
+
22
+ /* Layer-specific edges (excluding violations) */
23
+ function layerEdges(type) {
24
+ return graph.edges.filter(e =>
25
+ e.type !== "violates" &&
26
+ e.from.startsWith(`${type}:`) &&
27
+ e.to.startsWith(`${type}:`)
28
+ );
29
+ }
30
+
31
+ const platformEdges = layerEdges("platform");
32
+ const featureEdges = layerEdges("feature");
33
+ const sharedEdges = layerEdges("shared");
34
+ const infraEdges = layerEdges("infra");
35
+
36
+ /* Topological sort helper */
37
+ function topoSort(nodes, edges) {
38
+ const adj = {};
39
+ const inDegree = {};
40
+ const ids = nodes.map(n => n.id);
41
+ for (const id of ids) {
42
+ adj[id] = [];
43
+ inDegree[id] = 0;
44
+ }
45
+ for (const e of edges) {
46
+ if (adj[e.from]) {
47
+ adj[e.from].push(e.to);
48
+ inDegree[e.to] = (inDegree[e.to] || 0) + 1;
49
+ }
50
+ }
51
+ const queue = ids.filter(id => inDegree[id] === 0);
52
+ const order = [];
53
+ while (queue.length > 0) {
54
+ const node = queue.shift();
55
+ order.push(node);
56
+ for (const nb of adj[node] || []) {
57
+ inDegree[nb]--;
58
+ if (inDegree[nb] === 0) queue.push(nb);
59
+ }
60
+ }
61
+ return {
62
+ order,
63
+ hasCycles: order.length < ids.length,
64
+ cycleFree: order.length === ids.length,
65
+ };
66
+ }
67
+
68
+ const featTopo = topoSort(layers.features, featureEdges);
69
+ const platTopo = layers.platform.length > 0
70
+ ? topoSort(layers.platform, platformEdges)
71
+ : { order: [], hasCycles: false, cycleFree: true };
72
+ const sharedTopo = layers.shared.length > 0
73
+ ? topoSort(layers.shared, sharedEdges)
74
+ : { order: [], hasCycles: false, cycleFree: true };
75
+ const infraTopo = layers.infra.length > 0
76
+ ? topoSort(layers.infra, infraEdges)
77
+ : { order: [], hasCycles: false, cycleFree: true };
78
+
79
+ /* Global cycle detection across all layers */
80
+ const globalEdges = graph.edges.filter(e => e.type !== "violates");
81
+ const globalAdj = {};
82
+ const allIds = [...new Set(globalEdges.flatMap(e => [e.from, e.to]))];
83
+ for (const id of allIds) globalAdj[id] = [];
84
+ for (const e of globalEdges) {
85
+ if (globalAdj[e.from]) globalAdj[e.from].push(e.to);
86
+ }
87
+
88
+ let hasGlobalCycle = false;
89
+ function dfs(node, visited, stack) {
90
+ if (stack.has(node)) return true;
91
+ if (visited.has(node)) return false;
92
+ visited.add(node);
93
+ stack.add(node);
94
+ for (const nb of globalAdj[node] || []) {
95
+ if (dfs(nb, visited, stack)) return true;
96
+ }
97
+ stack.delete(node);
98
+ return false;
99
+ }
100
+ const gVisited = new Set();
101
+ for (const n of allIds) {
102
+ if (hasGlobalCycle) break;
103
+ if (!gVisited.has(n)) {
104
+ if (dfs(n, gVisited, new Set())) hasGlobalCycle = true;
105
+ }
106
+ }
107
+
108
+ /* Illegal chains (transitive violations) */
109
+ const illegalChains = [];
110
+ const allowedDirections = {
111
+ feature: ["platform", "shared", "domain"],
112
+ platform: ["infra", "shared"],
113
+ shared: [],
114
+ infra: [],
115
+ domain: ["shared"],
116
+ adapter: ["domain", "infra", "platform", "shared"],
117
+ };
118
+
119
+ for (const e of graph.edges) {
120
+ if (e.type === "violates") continue;
121
+ const fromType = e.from.split(":")[0];
122
+ const toType = e.to.split(":")[0];
123
+ if (!allowedDirections[fromType]) continue;
124
+ if (!allowedDirections[fromType].includes(toType)) {
125
+ illegalChains.push({
126
+ from: e.from,
127
+ to: e.to,
128
+ type: e.type,
129
+ file: e.file,
130
+ reason: `${fromType} → ${toType} no es una dirección permitida`,
131
+ });
132
+ }
133
+ }
134
+
135
+ /* Isolated components (no edges at all) */
136
+ const connected = new Set();
137
+ for (const e of graph.edges) {
138
+ connected.add(e.from);
139
+ connected.add(e.to);
140
+ }
141
+ const isolated = graph.nodes.filter(n => !connected.has(n.id));
142
+
143
+ /* Format feature edges as simple source/target (backward compat) */
144
+ const simpleFeatEdges = featureEdges.map(e => ({
145
+ source: e.from.replace("feature:", ""),
146
+ target: e.to.replace("feature:", ""),
147
+ file: e.file,
148
+ }));
149
+
150
+ const allFeatIds = [...new Set([
151
+ ...simpleFeatEdges.map(e => e.source),
152
+ ...simpleFeatEdges.map(e => e.target),
153
+ ...layers.features.map(n => n.id.replace("feature:", "")),
154
+ ])];
155
+
156
+ return {
157
+ nodes: allFeatIds.map(id => ({ id, type: "feature" })),
158
+ edges: simpleFeatEdges,
159
+ features: allFeatIds,
160
+ topologicalOrder: featTopo.order,
161
+ hasCycles: featTopo.hasCycles,
162
+ cycleFree: featTopo.cycleFree,
163
+ layers: {
164
+ platform: {
165
+ nodes: layers.platform.map(n => n.id),
166
+ edges: platformEdges,
167
+ order: platTopo.order,
168
+ hasCycles: platTopo.hasCycles,
169
+ },
170
+ features: {
171
+ nodes: allFeatIds,
172
+ edges: simpleFeatEdges,
173
+ order: featTopo.order,
174
+ hasCycles: featTopo.hasCycles,
175
+ },
176
+ shared: {
177
+ nodes: layers.shared.map(n => n.id),
178
+ edges: sharedEdges,
179
+ order: sharedTopo.order,
180
+ hasCycles: sharedTopo.hasCycles,
181
+ },
182
+ infra: {
183
+ nodes: layers.infra.map(n => n.id),
184
+ edges: infraEdges,
185
+ order: infraTopo.order,
186
+ hasCycles: infraTopo.hasCycles,
187
+ },
188
+ },
189
+ globalCycles: hasGlobalCycle,
190
+ illegalChains,
191
+ isolated: isolated.map(n => n.id),
192
+ graph,
193
+ };
194
+ }
195
+
196
+ async function main() {
197
+ const depGraph = buildDependencyGraph();
198
+ const args = process.argv.slice(2);
199
+ if (args.includes("--json")) {
200
+ console.log(JSON.stringify(depGraph, null, 2));
201
+ } else {
202
+ console.log("## Chain — Multi-layer Dependency Analysis\n");
203
+ console.log(`### Features (${depGraph.features.length})`);
204
+ console.log(` Order: ${depGraph.topologicalOrder.join(" → ") || "(none)"}`);
205
+ console.log(` Cycles: ${depGraph.hasCycles ? "YES ⚠" : "None ✓"}`);
206
+ console.log();
207
+
208
+ console.log(`### Platform (${depGraph.layers.platform.nodes.length})`);
209
+ if (depGraph.layers.platform.nodes.length > 0) {
210
+ console.log(` Order: ${depGraph.layers.platform.order.join(" → ") || "(independent)"}`);
211
+ console.log(` Cycles: ${depGraph.layers.platform.hasCycles ? "YES ⚠" : "None ✓"}`);
212
+ } else {
213
+ console.log(" (none detected)");
214
+ }
215
+ console.log();
216
+
217
+ console.log(`### Shared (${depGraph.layers.shared.nodes.length})`);
218
+ if (depGraph.layers.shared.nodes.length > 0) {
219
+ console.log(` Cycles: ${depGraph.layers.shared.hasCycles ? "YES ⚠" : "None ✓"}`);
220
+ } else {
221
+ console.log(" (none detected)");
222
+ }
223
+ console.log();
224
+
225
+ console.log(`### Infra (${depGraph.layers.infra.nodes.length})`);
226
+ if (depGraph.layers.infra.nodes.length > 0) {
227
+ console.log(` Cycles: ${depGraph.layers.infra.hasCycles ? "YES ⚠" : "None ✓"}`);
228
+ } else {
229
+ console.log(" (none detected)");
230
+ }
231
+ console.log();
232
+
233
+ console.log(`### Global Cycles: ${depGraph.globalCycles ? "YES ⚠" : "None ✓"}`);
234
+ console.log();
235
+
236
+ if (depGraph.illegalChains.length > 0) {
237
+ console.log("### Illegal Chains");
238
+ for (const c of depGraph.illegalChains) {
239
+ console.log(` ⚠ ${c.from} → ${c.to} (${c.reason})${c.file ? ` — ${c.file}` : ""}`);
240
+ }
241
+ console.log();
242
+ }
243
+
244
+ if (depGraph.isolated.length > 0) {
245
+ console.log("### Isolated Components");
246
+ for (const id of depGraph.isolated) {
247
+ console.log(` ○ ${id}`);
248
+ }
249
+ console.log();
250
+ }
251
+ }
252
+ }
253
+
254
+ if (process.argv[1] && (process.argv[1].endsWith("chain.mjs") || process.argv[1].endsWith("chain.js"))) {
255
+ main().catch(console.error);
256
+ }
257
+
258
+
@@ -0,0 +1,237 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync, existsSync, readdirSync, statSync } from "fs";
4
+ import { join, basename } from "path";
5
+ import { buildGraph } from "./graph.mjs";
6
+ import { buildOwnershipReport } from "./armorer.mjs";
7
+
8
+ const ROOT = process.cwd();
9
+ const SRC = join(ROOT, "src");
10
+ const FEATURES = join(SRC, "features");
11
+
12
+ const PLATFORM_KNOWN = ["config", "database", "http", "server", "logger", "cache", "security", "events", "scheduler", "observability", "di"];
13
+ const SHARED_KNOWN = ["errors", "contracts", "types", "utils", "helpers", "constants", "enums"];
14
+ const INFRA_KNOWN = ["prisma", "mongodb", "postgres", "redis", "mail", "s3", "cloudinary", "stripe", "sqs", "rabbitmq", "kafka", "smtp"];
15
+
16
+ function read(path) {
17
+ try {
18
+ return readFileSync(path, "utf-8");
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+
24
+ function exists(path) {
25
+ return existsSync(path);
26
+ }
27
+
28
+ function isDir(path) {
29
+ return existsSync(path) && statSync(path).isDirectory();
30
+ }
31
+
32
+ function listDir(path) {
33
+ try {
34
+ return readdirSync(path);
35
+ } catch {
36
+ return [];
37
+ }
38
+ }
39
+
40
+ function detectFramework(pkg) {
41
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
42
+ if (deps["@nestjs/core"]) return "NestJS";
43
+ if (deps.fastify) return "Fastify";
44
+ if (deps.express) return "Express";
45
+ return "unknown";
46
+ }
47
+
48
+ function detectDatabase(pkg) {
49
+ const deps = Object.keys({ ...pkg.dependencies, ...pkg.devDependencies });
50
+ if (deps.some((d) => d.includes("mongodb") || d === "mongoose")) return "MongoDB";
51
+ if (deps.includes("pg") || deps.includes("postgres")) return "PostgreSQL";
52
+ if (deps.includes("mysql") || deps.includes("mysql2")) return "MySQL";
53
+ if (deps.includes("sqlite3") || deps.includes("better-sqlite3")) return "SQLite";
54
+ return "unknown";
55
+ }
56
+
57
+ function detectOrm(pkg) {
58
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
59
+ if (deps.mongoose) return "Mongoose";
60
+ if (deps["@prisma/client"] || deps.prisma) return "Prisma";
61
+ if (deps["typeorm"]) return "TypeORM";
62
+ if (deps["drizzle-orm"]) return "Drizzle";
63
+ if (deps["pg"] || deps["mysql2"]) return "native";
64
+ return "none";
65
+ }
66
+
67
+ function detectDI(pkg) {
68
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
69
+ if (deps["tsyringe"]) return "tsyringe";
70
+ if (deps["@nestjs/core"]) return "framework";
71
+ if (deps["typedi"]) return "typedi";
72
+ if (deps["awilix"]) return "awilix";
73
+ return "manual";
74
+ }
75
+
76
+ function detectFeatures() {
77
+ if (!isDir(FEATURES)) return [];
78
+ return listDir(FEATURES).filter((f) => isDir(join(FEATURES, f)));
79
+ }
80
+
81
+ function detectLegacyFeatures() {
82
+ const legacy = [];
83
+ const ucBase = join(SRC, "application", "use-cases");
84
+ const ctrlDir = join(SRC, "adapters", "in", "http", "controllers");
85
+
86
+ const seen = new Set();
87
+ for (const sub of listDir(ucBase)) {
88
+ if (isDir(join(ucBase, sub))) {
89
+ legacy.push(sub);
90
+ seen.add(sub);
91
+ }
92
+ }
93
+ for (const file of listDir(ctrlDir)) {
94
+ const m = file.match(/^(.+)\.controller\.(ts|js)$/);
95
+ if (m && !seen.has(m[1])) {
96
+ legacy.push(m[1]);
97
+ seen.add(m[1]);
98
+ }
99
+ }
100
+
101
+ return legacy;
102
+ }
103
+
104
+ function detectPlatform() {
105
+ const platformDir = join(SRC, "platform");
106
+ if (!isDir(platformDir)) return { components: [], exists: false };
107
+ const components = listDir(platformDir).filter((d) => isDir(join(platformDir, d)) || d.endsWith(".ts") || d.endsWith(".js"));
108
+ const known = components.filter((c) => PLATFORM_KNOWN.includes(c.replace(/\.(ts|js)$/, "")));
109
+ const unknown = components.filter((c) => !PLATFORM_KNOWN.includes(c.replace(/\.(ts|js)$/, "")));
110
+ return { components, exists: true, known, unknown };
111
+ }
112
+
113
+ function detectShared() {
114
+ const sharedDir = join(SRC, "shared");
115
+ if (!isDir(sharedDir)) return { components: [], exists: false };
116
+ const components = listDir(sharedDir).filter((d) => isDir(join(sharedDir, d)) || d.endsWith(".ts") || d.endsWith(".js"));
117
+ const known = components.filter((c) => SHARED_KNOWN.includes(c.replace(/\.(ts|js)$/, "")));
118
+ const unknown = components.filter((c) => !SHARED_KNOWN.includes(c.replace(/\.(ts|js)$/, "")));
119
+ return { components, exists: true, known, unknown };
120
+ }
121
+
122
+ function detectInfra() {
123
+ const infraDir = join(SRC, "infra");
124
+ const infraDir2 = join(SRC, "infrastructure");
125
+ const dir = isDir(infraDir) ? infraDir : isDir(infraDir2) ? infraDir2 : null;
126
+ if (!dir) return { components: [], exists: false };
127
+ const components = listDir(dir).filter((d) => isDir(join(dir, d)) || d.endsWith(".ts") || d.endsWith(".js"));
128
+ const known = components.filter((c) => INFRA_KNOWN.includes(c.replace(/\.(ts|js)$/, "")));
129
+ const unknown = components.filter((c) => !INFRA_KNOWN.includes(c.replace(/\.(ts|js)$/, "")));
130
+ return { components, exists: true, known, unknown };
131
+ }
132
+
133
+ function detectOrphans() {
134
+ const report = buildOwnershipReport();
135
+ return report.orphans;
136
+ }
137
+
138
+ function stripJsonComments(raw) {
139
+ const lines = raw.split("\n");
140
+ const out = [];
141
+ for (let line of lines) {
142
+ const trimmed = line.trim();
143
+ if (trimmed.startsWith("/*") && trimmed.endsWith("*/")) continue;
144
+ const slashSlash = trimmed.indexOf("//");
145
+ if (slashSlash === 0) continue;
146
+ const ci = line.indexOf("/*");
147
+ if (ci > 0 && line.slice(ci).trim().endsWith("*/")) {
148
+ out.push(line.slice(0, ci));
149
+ continue;
150
+ }
151
+ out.push(line);
152
+ }
153
+ return out.join("\n").replace(/,\s*([}\]])/g, "$1");
154
+ }
155
+
156
+ function readJson(path) {
157
+ const raw = read(path);
158
+ if (!raw) return null;
159
+ try {
160
+ return JSON.parse(stripJsonComments(raw));
161
+ } catch {
162
+ return null;
163
+ }
164
+ }
165
+
166
+ export async function buildContext(projectRoot = ROOT) {
167
+ const root = projectRoot;
168
+ const src = join(root, "src");
169
+
170
+ const pkg = readJson(join(root, "package.json")) || {};
171
+ const tsconfig = readJson(join(root, "tsconfig.json"));
172
+
173
+ const framework = detectFramework(pkg);
174
+ const database = detectDatabase(pkg);
175
+ const orm = detectOrm(pkg);
176
+ const diStrategy = detectDI(pkg);
177
+
178
+ const hasDecorators =
179
+ tsconfig?.compilerOptions?.experimentalDecorators === true;
180
+ const hasDecoratorMetadata =
181
+ tsconfig?.compilerOptions?.emitDecoratorMetadata === true;
182
+
183
+ const migratedFeatures = detectFeatures();
184
+ const legacyFeatures = detectLegacyFeatures();
185
+ const platform = detectPlatform();
186
+ const shared = detectShared();
187
+ const infra = detectInfra();
188
+ const orphans = detectOrphans();
189
+ const graph = buildGraph(root);
190
+ const ownership = buildOwnershipReport(root);
191
+
192
+ const hasFeatures = migratedFeatures.length > 0;
193
+ const hasLegacy = legacyFeatures.length > 0;
194
+
195
+ return {
196
+ projectName: basename(root),
197
+ framework,
198
+ runtime: `Node ${process.version}`,
199
+ database,
200
+ orm,
201
+ diStrategy,
202
+ tsconfig: {
203
+ experimentalDecorators: hasDecorators,
204
+ emitDecoratorMetadata: hasDecoratorMetadata,
205
+ },
206
+ platform,
207
+ shared,
208
+ infra,
209
+ features: {
210
+ migrated: migratedFeatures,
211
+ legacy: legacyFeatures,
212
+ total: migratedFeatures.length + legacyFeatures.length,
213
+ },
214
+ orphans,
215
+ ownership,
216
+ hasSrc: isDir(src),
217
+ hasFeaturesDir: isDir(join(src, "features")),
218
+ hasPlatformDir: platform.exists,
219
+ hasSharedDir: shared.exists,
220
+ hasInfraDir: infra.exists,
221
+ isMigrating: hasLegacy && hasFeatures,
222
+ isFullyMigrated: hasFeatures && !hasLegacy,
223
+ isLegacy: hasLegacy && !hasFeatures,
224
+ isGreenfield: !hasLegacy && !hasFeatures && isDir(src),
225
+ graph,
226
+ dependencies: {},
227
+ };
228
+ }
229
+
230
+ async function main() {
231
+ const ctx = await buildContext();
232
+ console.log(JSON.stringify(ctx, null, 2));
233
+ }
234
+
235
+ if (process.argv[1] && (process.argv[1].endsWith("context.mjs") || process.argv[1].endsWith("context.js"))) {
236
+ main().catch(console.error);
237
+ }