@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.
- package/LICENSE +201 -0
- package/NOTICE +9 -0
- package/README.md +338 -0
- package/logo.png +0 -0
- package/package.json +47 -0
- package/skills/forge/SKILL.md +207 -0
- package/skills/forge/command/forge.md +110 -0
- package/skills/forge/profiles/express-mongodb.md +289 -0
- package/skills/forge/profiles/express-postgres.md +164 -0
- package/skills/forge/profiles/express-prisma.md +116 -0
- package/skills/forge/profiles/fastify-postgres.md +110 -0
- package/skills/forge/profiles/nestjs-prisma.md +160 -0
- package/skills/forge/reference/cast.md +77 -0
- package/skills/forge/reference/chain.md +73 -0
- package/skills/forge/reference/forge.md +44 -0
- package/skills/forge/reference/inscribe.md +129 -0
- package/skills/forge/reference/inspect.md +58 -0
- package/skills/forge/reference/patterns.md +108 -0
- package/skills/forge/reference/principles.md +29 -0
- package/skills/forge/reference/quench.md +74 -0
- package/skills/forge/reference/reforge.md +40 -0
- package/skills/forge/reference/relocate.md +38 -0
- package/skills/forge/reference/smelt.md +31 -0
- package/skills/forge/reference/temper.md +49 -0
- package/skills/forge/scripts/architecture.mjs +176 -0
- package/skills/forge/scripts/armorer.mjs +421 -0
- package/skills/forge/scripts/bootstrap.mjs +187 -0
- package/skills/forge/scripts/chain.mjs +258 -0
- package/skills/forge/scripts/context.mjs +237 -0
- package/skills/forge/scripts/detect.mjs +843 -0
- package/skills/forge/scripts/graph.mjs +594 -0
- package/skills/forge/scripts/inspect.mjs +193 -0
- package/skills/forge/scripts/profile.mjs +92 -0
- package/skills/forge/templates/feature/controller.ts.md +66 -0
- package/skills/forge/templates/feature/entity.ts.md +11 -0
- package/skills/forge/templates/feature/mapper.ts.md +18 -0
- package/skills/forge/templates/feature/repository-impl.ts.md +59 -0
- package/skills/forge/templates/feature/repository-interface.ts.md +12 -0
- package/skills/forge/templates/feature/routes.ts.md +17 -0
- package/skills/forge/templates/feature/schema.ts.md +16 -0
- package/skills/forge/templates/feature/use-case.ts.md +19 -0
- package/skills/forge/templates/infra/mail.ts.md +31 -0
- package/skills/forge/templates/infra/mongodb.ts.md +12 -0
- package/skills/forge/templates/infra/prisma.ts.md +21 -0
- package/skills/forge/templates/infra/redis.ts.md +30 -0
- package/skills/forge/templates/platform/config.ts.md +37 -0
- package/skills/forge/templates/platform/database.ts.md +40 -0
- package/skills/forge/templates/platform/di.ts.md +18 -0
- package/skills/forge/templates/platform/http.ts.md +29 -0
- package/skills/forge/templates/platform/logger.ts.md +38 -0
- package/skills/forge/templates/platform/server.ts.md +25 -0
- package/skills/forge/templates/shared/contract.ts.md +20 -0
- package/skills/forge/templates/shared/error.ts.md +27 -0
- package/skills/forge/templates/shared/type.ts.md +18 -0
- package/skills/forge/templates/shared/util.ts.md +23 -0
- 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();
|