@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,18 @@
1
+ ```typescript
2
+ // src/platform/di/Tokens.ts
3
+
4
+ export const TOKENS = {
5
+ Logger: Symbol("Logger"),
6
+ Database: Symbol("Database"),
7
+ Cache: Symbol("Cache"),
8
+ EventBus: Symbol("EventBus"),
9
+ } as const;
10
+ ```
11
+
12
+ ```typescript
13
+ // src/platform/di/Container.ts
14
+ // Configuración del contenedor de DI según el perfil activo
15
+ // tsyringe: import { container } from "tsyringe";
16
+ // awilix: import { createContainer } from "awilix";
17
+ // manual: mantener un Map de registros
18
+ ```
@@ -0,0 +1,29 @@
1
+ ```typescript
2
+ // src/platform/http/Router.ts
3
+ import { Router as ExpressRouter } from "express";
4
+
5
+ const router = ExpressRouter();
6
+
7
+ // registrar rutas globales (health, metrics, etc.)
8
+ router.get("/health", (_req, res) => {
9
+ res.json({ status: "ok", timestamp: new Date().toISOString() });
10
+ });
11
+
12
+ export { router };
13
+ ```
14
+
15
+ ```typescript
16
+ // src/platform/http/Error.middleware.ts
17
+ import type { Request, Response, NextFunction } from "express";
18
+
19
+ export function errorHandler(
20
+ err: Error,
21
+ _req: Request,
22
+ res: Response,
23
+ _next: NextFunction
24
+ ): void {
25
+ const status = (err as any).statusCode || 500;
26
+ const message = err.message || "Internal Server Error";
27
+ res.status(status).json({ error: message });
28
+ }
29
+ ```
@@ -0,0 +1,38 @@
1
+ ```typescript
2
+ // src/platform/logger/Logger.config.ts
3
+
4
+ export type LogLevel = "debug" | "info" | "warn" | "error";
5
+
6
+ export interface LoggerConfig {
7
+ level: LogLevel;
8
+ prettyPrint: boolean;
9
+ }
10
+
11
+ export function loadLoggerConfig(): LoggerConfig {
12
+ return {
13
+ level: (process.env.LOG_LEVEL as LogLevel) || "info",
14
+ prettyPrint: process.env.NODE_ENV !== "production",
15
+ };
16
+ }
17
+ ```
18
+
19
+ ```typescript
20
+ // src/platform/logger/Logger.service.ts
21
+ import { loadLoggerConfig, type LogLevel } from "./Logger.config.js";
22
+
23
+ const config = loadLoggerConfig();
24
+
25
+ export const logger = {
26
+ debug: (msg: string, ...args: unknown[]) => log("debug", msg, ...args),
27
+ info: (msg: string, ...args: unknown[]) => log("info", msg, ...args),
28
+ warn: (msg: string, ...args: unknown[]) => log("warn", msg, ...args),
29
+ error: (msg: string, ...args: unknown[]) => log("error", msg, ...args),
30
+ };
31
+
32
+ function log(level: LogLevel, msg: string, ...args: unknown[]): void {
33
+ const levels: LogLevel[] = ["debug", "info", "warn", "error"];
34
+ if (levels.indexOf(level) < levels.indexOf(config.level)) return;
35
+ const timestamp = new Date().toISOString();
36
+ console[level](`[${timestamp}] [${level.toUpperCase()}] ${msg}`, ...args);
37
+ }
38
+ ```
@@ -0,0 +1,25 @@
1
+ ```typescript
2
+ // src/platform/server/App.ts
3
+ import "reflect-metadata";
4
+ import express from "express";
5
+ import { loadAppConfig } from "../config/App.config.js";
6
+
7
+ const app = express();
8
+ const config = loadAppConfig();
9
+
10
+ app.use(express.json());
11
+ app.use(express.urlencoded({ extended: true }));
12
+
13
+ export { app, config };
14
+ ```
15
+
16
+ ```typescript
17
+ // src/platform/server/Server.ts
18
+ import { app, config } from "./App.js";
19
+
20
+ export async function startServer(): Promise<void> {
21
+ app.listen(config.port, () => {
22
+ console.log(`Server running on port ${config.port}`);
23
+ });
24
+ }
25
+ ```
@@ -0,0 +1,20 @@
1
+ ```typescript
2
+ // src/shared/contracts/IPaginatedResponse.ts
3
+
4
+ export interface IPaginatedResponse<T> {
5
+ data: T[];
6
+ total: number;
7
+ page: number;
8
+ limit: number;
9
+ totalPages: number;
10
+ }
11
+ ```
12
+
13
+ ```typescript
14
+ // src/shared/contracts/IResponse.ts
15
+
16
+ export interface IResponse<T> {
17
+ data: T;
18
+ message?: string;
19
+ }
20
+ ```
@@ -0,0 +1,27 @@
1
+ ```typescript
2
+ // src/shared/errors/NotFoundError.ts
3
+
4
+ export class NotFoundError extends Error {
5
+ public readonly statusCode = 404;
6
+
7
+ constructor(message = "Resource not found") {
8
+ super(message);
9
+ this.name = "NotFoundError";
10
+ }
11
+ }
12
+ ```
13
+
14
+ ```typescript
15
+ // src/shared/errors/ValidationError.ts
16
+
17
+ export class ValidationError extends Error {
18
+ public readonly statusCode = 400;
19
+ public readonly details: Record<string, string[]>;
20
+
21
+ constructor(details: Record<string, string[]>) {
22
+ super("Validation failed");
23
+ this.name = "ValidationError";
24
+ this.details = details;
25
+ }
26
+ }
27
+ ```
@@ -0,0 +1,18 @@
1
+ ```typescript
2
+ // src/shared/types/api.types.ts
3
+
4
+ export type SortOrder = "asc" | "desc";
5
+
6
+ export interface PaginationQuery {
7
+ page?: number;
8
+ limit?: number;
9
+ sort?: string;
10
+ order?: SortOrder;
11
+ }
12
+
13
+ export interface ApiResponse<T> {
14
+ success: boolean;
15
+ data?: T;
16
+ error?: string;
17
+ }
18
+ ```
@@ -0,0 +1,23 @@
1
+ ```typescript
2
+ // src/shared/utils/formatDate.ts
3
+
4
+ export function formatDate(date: Date, locale = "es-ES"): string {
5
+ return new Intl.DateTimeFormat(locale, {
6
+ year: "numeric",
7
+ month: "2-digit",
8
+ day: "2-digit",
9
+ }).format(date);
10
+ }
11
+ ```
12
+
13
+ ```typescript
14
+ // src/shared/utils/pagination.ts
15
+ import type { PaginationQuery } from "../types/api.types.js";
16
+
17
+ export function buildPagination(query: PaginationQuery) {
18
+ const page = Math.max(1, query.page || 1);
19
+ const limit = Math.min(100, Math.max(1, query.limit || 10));
20
+ const skip = (page - 1) * limit;
21
+ return { page, limit, skip };
22
+ }
23
+ ```
package/src/cli.js ADDED
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { copyFileSync, mkdirSync, existsSync, readdirSync, statSync, writeFileSync, readFileSync } from "fs";
4
+ import { join, dirname, relative } from "path";
5
+ import { fileURLToPath } from "url";
6
+ import { execSync } from "child_process";
7
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const PKG_DIR = join(__dirname, "..");
10
+ const SKILL_SRC = join(PKG_DIR, "skills", "forge");
11
+
12
+ function getTargetDir(global) {
13
+ if (global) {
14
+ const home = process.env.HOME || process.env.USERPROFILE;
15
+ if (!home) {
16
+ console.error("[ERROR] No se pudo determinar el HOME del usuario");
17
+ process.exit(1);
18
+ }
19
+ return join(home, ".config", "opencode", "skills", "forge");
20
+ }
21
+ return join(process.cwd(), ".opencode", "skills", "forge");
22
+ }
23
+
24
+ function getConfigDir(global) {
25
+ if (global) {
26
+ const home = process.env.HOME || process.env.USERPROFILE;
27
+ return join(home, ".config", "opencode");
28
+ }
29
+ return join(process.cwd(), ".opencode");
30
+ }
31
+
32
+ function copyRecursive(src, dest) {
33
+ mkdirSync(dest, { recursive: true });
34
+ for (const entry of readdirSync(src)) {
35
+ const srcPath = join(src, entry);
36
+ const destPath = join(dest, entry);
37
+ if (statSync(srcPath).isDirectory()) {
38
+ copyRecursive(srcPath, destPath);
39
+ } else {
40
+ copyFileSync(srcPath, destPath);
41
+ }
42
+ }
43
+ }
44
+
45
+ function detectPM(configDir) {
46
+ if (existsSync(join(configDir, "pnpm-lock.yaml")) || existsSync(join(process.cwd(), "pnpm-lock.yaml"))) return "pnpm";
47
+ if (existsSync(join(configDir, "bun.lock")) || existsSync(join(configDir, "bun.lockb"))) return "bun";
48
+ return "npm";
49
+ }
50
+
51
+ function ensureDependencies(configDir) {
52
+ const pkgPath = join(configDir, "package.json");
53
+ let pkg = {};
54
+ if (existsSync(pkgPath)) {
55
+ try {
56
+ pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
57
+ } catch {
58
+ pkg = {};
59
+ }
60
+ }
61
+ if (!pkg.dependencies) pkg.dependencies = {};
62
+ if (!pkg.dependencies["@opencode-ai/plugin"]) {
63
+ pkg.dependencies["@opencode-ai/plugin"] = "1.17.9";
64
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
65
+ console.log(` ✓ Actualizado ${relative(process.cwd(), pkgPath)}`);
66
+ }
67
+
68
+ const pm = detectPM(configDir);
69
+ const order = { pnpm: ["npm", "bun"], npm: ["bun", "pnpm"], bun: ["npm", "pnpm"] };
70
+
71
+ function runPM(bin) {
72
+ execSync(`${bin} install`, { cwd: configDir, stdio: "pipe" });
73
+ }
74
+
75
+ try {
76
+ runPM(pm);
77
+ console.log(` ✓ Dependencias instaladas (${pm})`);
78
+ } catch {
79
+ for (const fallback of order[pm]) {
80
+ try {
81
+ runPM(fallback);
82
+ console.log(` ✓ Dependencias instaladas (${fallback})`);
83
+ return;
84
+ } catch {}
85
+ }
86
+ console.warn(" ⚠ No se pudieron instalar dependencias. Ejecuta 'pnpm install' | 'npm install' manualmente en " + configDir);
87
+ }
88
+ }
89
+
90
+ const COMMANDS = [
91
+ { name: "forge-forge", desc: "Forge — inicializar proyecto arquitectónicamente" },
92
+ { name: "forge-cast", desc: "Cast — crear un nuevo feature hexagonal desde cero" },
93
+ { name: "forge-inspect", desc: "Inspect — inspeccionar la conformidad arquitectónica" },
94
+ { name: "forge-relocate",desc: "Relocate — migrar feature legacy a la nueva estructura" },
95
+ { name: "forge-reforge", desc: "Reforge — refactorizar la arquitectura de un feature" },
96
+ { name: "forge-quench", desc: "Quench — verificar reglas arquitectónicas del proyecto" },
97
+ { name: "forge-temper", desc: "Temper — endurecer la arquitectura (DI, seguridad)" },
98
+ { name: "forge-chain", desc: "Chain — analizar cadena de dependencias entre features" },
99
+ { name: "forge-inscribe",desc: "Inscribe — generar y mantener ARCHITECTURE.md" },
100
+ { name: "forge-smelt", desc: "Smelt — extraer código reutilizable a shared/" },
101
+ ];
102
+
103
+ function generateCommands(configDir) {
104
+ const cmdsDir = join(configDir, "commands");
105
+ mkdirSync(cmdsDir, { recursive: true });
106
+ for (const cmd of COMMANDS) {
107
+ const sub = cmd.name.replace("forge-", "");
108
+ const content = [
109
+ "---",
110
+ `description: ${cmd.desc}`,
111
+ "agent: build",
112
+ "---",
113
+ "",
114
+ `Ejecuta el subcomando ${sub} de Forge con los argumentos: $ARGUMENTS`,
115
+ "",
116
+ ].join("\n");
117
+ writeFileSync(join(cmdsDir, `${cmd.name}.md`), content);
118
+ }
119
+ console.log(` ✓ Comandos /forge-* generados en .opencode/commands/`);
120
+ }
121
+
122
+ function printHelp() {
123
+ console.log(`
124
+ Forge — Architecture OS
125
+
126
+ USO
127
+ forge install Instalar skill en el proyecto actual
128
+ forge install --global Instalar globalmente (~/.config/opencode/)
129
+ forge skills install (alias del comando install)
130
+
131
+ OPCIONES
132
+ -g, --global Instalar en ~/.config/opencode/skills/forge/
133
+ -h, --help Mostrar esta ayuda
134
+ `);
135
+ }
136
+
137
+ function install(global) {
138
+ if (!existsSync(SKILL_SRC)) {
139
+ console.error("[ERROR] No se encuentra skills/forge/ en el paquete. ¿Corrupto?");
140
+ process.exit(1);
141
+ }
142
+
143
+ const target = getTargetDir(global);
144
+ const configDir = getConfigDir(global);
145
+
146
+ console.log(`\n Forge — Architecture OS`);
147
+ console.log(` ${"=".repeat(40)}`);
148
+ console.log(` Destino: ${target}\n`);
149
+
150
+ copyRecursive(SKILL_SRC, target);
151
+ console.log(` ✓ Skill copiada a ${relative(process.cwd(), target)}`);
152
+ generateCommands(configDir);
153
+ ensureDependencies(configDir);
154
+ console.log(`\n ✓ Forge instalado correctamente`);
155
+ console.log(` ${"=".repeat(40)}\n`);
156
+ }
157
+
158
+ function main() {
159
+ const args = process.argv.slice(2);
160
+ const isHelp = args.includes("-h") || args.includes("--help");
161
+ const isGlobal = args.includes("-g") || args.includes("--global");
162
+
163
+ if (isHelp) {
164
+ printHelp();
165
+ return;
166
+ }
167
+
168
+ const subcommandIndex = args.indexOf("skills");
169
+ const command = subcommandIndex !== -1 ? args[subcommandIndex + 1] : args[0];
170
+
171
+ if (command === "install") {
172
+ install(isGlobal);
173
+ } else {
174
+ printHelp();
175
+ process.exit(1);
176
+ }
177
+ }
178
+
179
+ main();