@ronaldjdevfs/forge 1.0.1 → 1.1.0
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/README.md +130 -8
- package/package.json +3 -3
- package/skills/forge/SKILL.md +86 -15
- package/skills/forge/profiles/express-drizzle.md +107 -0
- package/skills/forge/profiles/fastify-mongodb.md +103 -0
- package/skills/forge/profiles/fastify-prisma.md +81 -0
- package/skills/forge/profiles/nestjs-mongodb.md +92 -0
- package/skills/forge/profiles/nestjs-postgres.md +98 -0
- package/skills/forge/reference/api-design.md +62 -0
- package/skills/forge/reference/assay.md +82 -0
- package/skills/forge/reference/cast.md +81 -7
- package/skills/forge/reference/data-patterns.md +86 -0
- package/skills/forge/reference/di-strategies.md +50 -0
- package/skills/forge/reference/errors.md +65 -0
- package/skills/forge/reference/events.md +95 -0
- package/skills/forge/reference/help.md +40 -0
- package/skills/forge/reference/hooks.md +62 -0
- package/skills/forge/reference/observability.md +66 -0
- package/skills/forge/reference/patterns.md +52 -0
- package/skills/forge/reference/principles.md +6 -0
- package/skills/forge/reference/reforge.md +69 -5
- package/skills/forge/reference/relocate.md +15 -2
- package/skills/forge/reference/security-patterns.md +87 -0
- package/skills/forge/reference/testing-patterns.md +69 -0
- package/skills/forge/scripts/assay.mjs +481 -0
- package/skills/forge/scripts/context.mjs +147 -43
- package/skills/forge/scripts/detect.mjs +371 -22
- package/skills/forge/scripts/forge-api.mjs +373 -0
- package/skills/forge/scripts/forge-config.mjs +268 -0
- package/skills/forge/scripts/forge-signals.mjs +131 -0
- package/skills/forge/scripts/forge-state.mjs +97 -0
- package/skills/forge/scripts/formatter.mjs +133 -0
- package/skills/forge/scripts/graph.mjs +5 -21
- package/skills/forge/scripts/hook.mjs +250 -0
- package/skills/forge/scripts/inspect.mjs +171 -22
- package/skills/forge/scripts/parse-imports.mjs +249 -0
- package/skills/forge/scripts/pin.mjs +151 -0
- package/skills/forge/scripts/posttool.mjs +224 -0
- package/skills/forge/scripts/profile.mjs +124 -20
- package/skills/forge/scripts/registry/rules.mjs +344 -0
- package/skills/forge/scripts/rename.mjs +669 -0
- package/skills/forge/scripts/rollback.mjs +213 -0
- package/skills/forge/scripts/update.mjs +114 -0
- package/skills/forge/templates/feature/domain-error.ts.md +9 -0
- package/skills/forge/templates/feature/domain-event.ts.md +9 -0
- package/skills/forge/templates/feature/event-handler.ts.md +10 -0
- package/skills/forge/templates/feature/use-case.ts.md +10 -2
- package/skills/forge/tests/core.test.mjs +403 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Events — Eventos de dominio, outbox, sagas
|
|
2
|
+
|
|
3
|
+
## Principios
|
|
4
|
+
|
|
5
|
+
- Los eventos representan **hechos consumados** en el dominio (pasado simple).
|
|
6
|
+
- Se emiten desde el use case después de persistir, nunca antes.
|
|
7
|
+
- El event bus es infraestructura (platform/events/); el dominio solo define la interfaz.
|
|
8
|
+
- Los handlers están en `adapters/` (out) del feature que reacciona al evento.
|
|
9
|
+
|
|
10
|
+
## Estructura
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
features/<name>/
|
|
14
|
+
domain/
|
|
15
|
+
events/
|
|
16
|
+
UserCreated.event.ts
|
|
17
|
+
UserEmailChanged.event.ts
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Evento de dominio
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
// features/users/domain/events/UserCreated.event.ts
|
|
24
|
+
export class UserCreatedEvent {
|
|
25
|
+
constructor(
|
|
26
|
+
public readonly userId: string,
|
|
27
|
+
public readonly email: string,
|
|
28
|
+
public readonly occurredAt: Date = new Date(),
|
|
29
|
+
) {}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Emisión desde use case
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
// features/users/application/use-cases/CreateUser.uc.ts
|
|
37
|
+
export class CreateUserUseCase {
|
|
38
|
+
constructor(
|
|
39
|
+
private readonly userRepo: IUserRepository,
|
|
40
|
+
private readonly eventBus: IEventBus,
|
|
41
|
+
) {}
|
|
42
|
+
|
|
43
|
+
async execute(dto: CreateUserDTO): Promise<UserEntity> {
|
|
44
|
+
const user = UserEntity.create(dto);
|
|
45
|
+
const saved = await this.userRepo.save(user);
|
|
46
|
+
this.eventBus.publish(new UserCreatedEvent(saved.id, saved.email));
|
|
47
|
+
return saved;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Event Bus
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
// platform/events/EventBus.ts
|
|
56
|
+
export interface IEventBus {
|
|
57
|
+
publish(event: object): void;
|
|
58
|
+
subscribe<T>(eventType: new (...args: any[]) => T, handler: (event: T) => Promise<void>): void;
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Outbox Pattern
|
|
63
|
+
|
|
64
|
+
Para eventos que **deben** entregarse de forma confiable (ej: notificar a otro servicio).
|
|
65
|
+
|
|
66
|
+
1. El use case persiste el evento en una tabla `outbox` dentro de la misma transacción de BD.
|
|
67
|
+
2. Un worker independiente lee la outbox y publica los eventos al message broker.
|
|
68
|
+
3. Elimina o marca como enviado tras confirmación del broker.
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
features/<name>/adapters/out/persistence/
|
|
72
|
+
├── User.repository.ts
|
|
73
|
+
└── Outbox.repository.ts
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Sagas / Process Managers
|
|
77
|
+
|
|
78
|
+
Para flujos multi-paso que abarcan múltiples features.
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
// features/orders/application/sagas/CreateOrder.saga.ts
|
|
82
|
+
// 1. OrderCreated → PaymentRequired
|
|
83
|
+
// 2. PaymentConfirmed → InventoryReserved
|
|
84
|
+
// 3. InventoryReserved → OrderConfirmed
|
|
85
|
+
// 4. Si falla: compensar (rollback) cada paso
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Buenas prácticas
|
|
89
|
+
|
|
90
|
+
- Eventos en pasado (`UserCreated`, `EmailChanged`), nunca (`CreateUser`, `ChangeEmail`)
|
|
91
|
+
- Un evento por handler. Si un handler falla, no bloquea otros.
|
|
92
|
+
- Idempotencia en handlers: processar el mismo evento dos veces produce el mismo resultado.
|
|
93
|
+
- Eventos sin lógica: son datos, no comportamiento.
|
|
94
|
+
- Para integración entre features: el feature A emite evento, el feature B lo escucha. Nunca import directo.
|
|
95
|
+
- Outbox para garantía de entrega; in-memory event bus solo para tests o monolitos pequeños.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# forge — Ayuda
|
|
2
|
+
|
|
3
|
+
## Uso
|
|
4
|
+
forge <comando> [flags]
|
|
5
|
+
|
|
6
|
+
## Comandos
|
|
7
|
+
|
|
8
|
+
forge Inicializar proyecto (boot sequence completa)
|
|
9
|
+
cast Crear nuevo feature con estructura hexagonal
|
|
10
|
+
inspect Auditoría arquitectónica (110pts → 0-100)
|
|
11
|
+
relocate Migrar código legacy a features/platform/shared/infra
|
|
12
|
+
reforge Refactorizar arquitectura multi-capa
|
|
13
|
+
quench Validar reglas R1-R9 (--fix auto-corrige)
|
|
14
|
+
temper Endurecer inyección de dependencias
|
|
15
|
+
chain Grafo de dependencias multi-capa
|
|
16
|
+
graph Grafo arquitectónico con risk score
|
|
17
|
+
inscribe Generar/actualizar ARCHITECTURE.md
|
|
18
|
+
smelt Extraer código puro a shared/
|
|
19
|
+
assay Ensayo multi-persona (--persona=, --save)
|
|
20
|
+
nail Crear shortcut de navegación
|
|
21
|
+
unnail Eliminar shortcut de navegación
|
|
22
|
+
forge state Estado persistente post-auditoría
|
|
23
|
+
forge hook Git pre-commit hook
|
|
24
|
+
forge api Validación de contratos API
|
|
25
|
+
forge rollback Restauración de puntos de guardado
|
|
26
|
+
forge update Verificar actualizaciones
|
|
27
|
+
|
|
28
|
+
## Flags
|
|
29
|
+
|
|
30
|
+
--fix Auto-corregir violaciones (quench)
|
|
31
|
+
--show-ignores Mostrar inline ignores (quench)
|
|
32
|
+
--persona=<id> Filtrar ensayo por persona (assay)
|
|
33
|
+
--save Persistir ensayo en .forge/assay/ (assay)
|
|
34
|
+
--json Salida JSON (assay, forge state)
|
|
35
|
+
|
|
36
|
+
## Inline Ignores
|
|
37
|
+
|
|
38
|
+
// forge-ignore-next-line
|
|
39
|
+
// forge-ignore: R1
|
|
40
|
+
// forge-ignore: R1, R8
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Hook — Git pre-commit hook arquitectónico
|
|
2
|
+
|
|
3
|
+
## Propósito
|
|
4
|
+
|
|
5
|
+
Validar automáticamente la arquitectura del proyecto antes de cada commit,
|
|
6
|
+
bloqueando commits que introduzcan violaciones CRITICAL o ERROR en los
|
|
7
|
+
archivos staged.
|
|
8
|
+
|
|
9
|
+
## Uso
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Instalar el hook pre-commit
|
|
13
|
+
forge hook install
|
|
14
|
+
|
|
15
|
+
# Ver estado
|
|
16
|
+
forge hook status
|
|
17
|
+
|
|
18
|
+
# Ignorar una regla específica
|
|
19
|
+
forge hook ignore R1
|
|
20
|
+
|
|
21
|
+
# Dejar de ignorar
|
|
22
|
+
forge hook unignore R1
|
|
23
|
+
|
|
24
|
+
# Listar reglas ignoradas
|
|
25
|
+
forge hook list-ignored
|
|
26
|
+
|
|
27
|
+
# Ejecutar validación manual sobre staged files
|
|
28
|
+
forge hook check
|
|
29
|
+
|
|
30
|
+
# Desinstalar
|
|
31
|
+
forge hook uninstall
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Comportamiento
|
|
35
|
+
|
|
36
|
+
El hook ejecuta `detect.mjs` sobre los archivos staged con extensión
|
|
37
|
+
`.ts`, `.js`, `.mjs`, `.tsx`, `.jsx` dentro de `src/`. Si encuentra
|
|
38
|
+
violaciones de severidad CRITICAL o ERROR que afecten a archivos staged:
|
|
39
|
+
|
|
40
|
+
1. Muestra las violaciones con su severidad y fix sugerido
|
|
41
|
+
2. Bloquea el commit (exit code 1)
|
|
42
|
+
3. Sugiere cómo ignorar la regla si es necesario
|
|
43
|
+
|
|
44
|
+
El commit puede saltarse con `git commit --no-verify`.
|
|
45
|
+
|
|
46
|
+
## Archivos de configuración
|
|
47
|
+
|
|
48
|
+
| Archivo | Propósito |
|
|
49
|
+
|---------|-----------|
|
|
50
|
+
| `.forge/hooks-ignore.json` | Lista de reglas ignoradas por el hook |
|
|
51
|
+
| `.git/hooks/pre-commit` | Script instalado por `forge hook install` |
|
|
52
|
+
|
|
53
|
+
## Reglas ignoradas
|
|
54
|
+
|
|
55
|
+
Las reglas ignoradas se almacenan en `.forge/hooks-ignore.json`:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{ "ignored": ["R1", "R7", "R9"] }
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Cuando una regla está ignorada, el hook no bloquea el commit por
|
|
62
|
+
violaciones de esa regla, aunque el detector sigue reportándolas.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Observability — Logging, tracing, métricas, health checks
|
|
2
|
+
|
|
3
|
+
## Principios
|
|
4
|
+
|
|
5
|
+
- Observabilidad es infraestructura, no dominio. Toda instrumentación está en `platform/` o `adapters/`.
|
|
6
|
+
- El dominio nunca escribe logs ni emite métricas directamente.
|
|
7
|
+
- Usar OpenTelemetry como estándar unificado.
|
|
8
|
+
|
|
9
|
+
## Estructura recomendada
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
src/platform/observability/
|
|
13
|
+
├── Metrics.ts → Contadores, histogramas (Prometheus / OTel)
|
|
14
|
+
├── Tracing.ts → Trazas distribuidas (OpenTelemetry)
|
|
15
|
+
├── Health.ts → Health checks agregados
|
|
16
|
+
├── Logger.service.ts → Logger estructurado (pino, winston)
|
|
17
|
+
└── Logger.config.ts → Niveles, formatos, transports
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Logging estructurado
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
// platform/observability/Logger.service.ts
|
|
24
|
+
export class LoggerService {
|
|
25
|
+
info(msg: string, ctx?: Record<string, unknown>): void {
|
|
26
|
+
pino.info({ msg, ...ctx });
|
|
27
|
+
}
|
|
28
|
+
error(msg: string, err?: Error, ctx?: Record<string, unknown>): void {
|
|
29
|
+
pino.error({ msg, err: { message: err?.message, stack: err?.stack }, ...ctx });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Inyectar en use cases solo si es estrictamente necesario (auditoría). Preferir middlewares de logging.
|
|
35
|
+
|
|
36
|
+
## Health checks
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
// platform/observability/Health.ts
|
|
40
|
+
export interface IHealthCheck {
|
|
41
|
+
name: string;
|
|
42
|
+
check(): Promise<{ status: "ok" | "degraded" | "down"; detail?: string }>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// GET /health → { status: "ok", checks: [{ name: "db", status: "ok" }, ...] }
|
|
46
|
+
// GET /health/ready → readiness (dependencias listas)
|
|
47
|
+
// GET /health/live → liveness (proceso vivo)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Métricas clave
|
|
51
|
+
|
|
52
|
+
| Métrica | Tipo | Descripción |
|
|
53
|
+
|---------|------|-------------|
|
|
54
|
+
| `http_requests_total` | Counter | Total requests por método, ruta, status |
|
|
55
|
+
| `http_request_duration_seconds` | Histogram | Latencia por ruta |
|
|
56
|
+
| `db_query_duration_seconds` | Histogram | Latencia de queries por repositorio |
|
|
57
|
+
| `feature_uc_execution_seconds` | Histogram | Duración de use cases por feature |
|
|
58
|
+
|
|
59
|
+
## Buenas prácticas
|
|
60
|
+
|
|
61
|
+
- Correlation ID en cada request (inyectado en platform/http/)
|
|
62
|
+
- Logs en JSON siempre. Nivel info para flujo normal, debug para detalle, warn/error para anomalías.
|
|
63
|
+
- No loggear datos sensibles (PII, passwords, tokens)
|
|
64
|
+
- Tracing distribuido con baggage contextual
|
|
65
|
+
- Health checks sin autenticación (solo para orquestadores)
|
|
66
|
+
- Alertas basadas en métricas, no en logs
|
|
@@ -86,6 +86,58 @@ src/features/<feature-name>/
|
|
|
86
86
|
|
|
87
87
|
---
|
|
88
88
|
|
|
89
|
+
## Events
|
|
90
|
+
|
|
91
|
+
| Archivo | Formato | Ejemplo |
|
|
92
|
+
|---------|---------|---------|
|
|
93
|
+
| Eventos de dominio | `<Domain><Accion>.event.ts` | `UserCreated.event.ts`, `OrderPaid.event.ts` |
|
|
94
|
+
| Handlers | `<Domain><Accion>.handler.ts` | `SendWelcomeEmail.handler.ts` |
|
|
95
|
+
| Outbox record | `<Entidad>.outbox.ts` | `User.outbox.ts` |
|
|
96
|
+
|
|
97
|
+
Los eventos siempre en pasado (`Created`, `Updated`, `Deleted`, `Paid`, `Shipped`), nunca en imperativo.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Observability
|
|
102
|
+
|
|
103
|
+
| Archivo | Formato | Ejemplo |
|
|
104
|
+
|---------|---------|---------|
|
|
105
|
+
| Loggers | `Logger.service.ts`, `Logger.config.ts` | — |
|
|
106
|
+
| Métricas | `Metrics.ts`, `Metrics.middleware.ts` | — |
|
|
107
|
+
| Tracing | `Tracing.ts` | — |
|
|
108
|
+
| Health | `Health.ts`, `Health.controller.ts` | — |
|
|
109
|
+
|
|
110
|
+
Toda instrumentación vive en `platform/observability/`.
|
|
111
|
+
Para referencia completa ver `reference/observability.md`.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Errors
|
|
116
|
+
|
|
117
|
+
| Archivo | Formato | Ejemplo |
|
|
118
|
+
|---------|---------|---------|
|
|
119
|
+
| Error base | `<Name>Error.ts` | `AppError.ts` |
|
|
120
|
+
| Error específico | `<Name>Error.ts` | `NotFoundError.ts`, `ValidationError.ts`, `ConflictError.ts` |
|
|
121
|
+
|
|
122
|
+
Los errores transversales viven en `shared/errors/`. Errores específicos de feature en `features/<name>/domain/errors/`.
|
|
123
|
+
Para guía completa ver `reference/errors.md`.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## API
|
|
128
|
+
|
|
129
|
+
| Archivo | Formato | Ejemplo |
|
|
130
|
+
|---------|---------|---------|
|
|
131
|
+
| Esquemas de validación | `<name>.schema.ts` | `createUser.schema.ts` |
|
|
132
|
+
| Routes | `<Name>.routes.ts` | `User.routes.ts` |
|
|
133
|
+
| Controller | `<Name>.controller.ts` | `User.controller.ts` |
|
|
134
|
+
| DTOs request | `<Name>.req.ts` | `CreateUser.req.ts` |
|
|
135
|
+
| DTOs response | `<Name>.res.ts` | `User.res.ts` |
|
|
136
|
+
|
|
137
|
+
REST prefiere `/api/v1/resources`. GraphQL schema en `adapters/in/graphql/` dentro del feature.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
89
141
|
## Infra Layer
|
|
90
142
|
|
|
91
143
|
| Directorio | Archivos | Ejemplo |
|
|
@@ -27,3 +27,9 @@
|
|
|
27
27
|
11. **El sistema es un grafo arquitectónico vivo** — Todo componente es un nodo tipado (platform, feature, shared, infra, domain, adapter, application). Toda relación es un edge validado. Las violaciones son edges prohibidos. El grafo es la fuente de verdad del sistema y se regenera en cada auditoría. Riesgo, salud y ownership se derivan del grafo, no de opiniones.
|
|
28
28
|
|
|
29
29
|
12. **Cuatro dominios arquitectónicos con ownership estricto** — Todo backend se modela en cuatro capas: Platform (backbone técnico), Features (negocio), Shared (código puro reutilizable) e Infrastructure (implementaciones concretas). Cada componente tiene un único propietario arquitectónico. Los huérfanos, duplicados y componentes mal ubicados se detectan automáticamente. Las reglas de dependencia entre capas son obligatorias: `feature → platform → infra`, `feature → shared`, `adapter → infra`. Cualquier violación es una degradación arquitectónica.
|
|
30
|
+
|
|
31
|
+
13. **Errores tipados en el dominio** — Los errores de dominio son clases explícitas, no `throw Error()` genéricos. Viven en `shared/errors/` si son transversales o en `domain/` del feature si son específicos. Los adapters HTTP traducen errores de dominio a códigos HTTP. La capa de aplicación nunca sabe qué códigos HTTP existen. Ver `reference/errors.md`.
|
|
32
|
+
|
|
33
|
+
14. **Tests como ciudadanos de primera clase** — El dominio y los casos de uso se testean con unit tests sin infraestructura (mocks en las interfaces). Los adapters se testean con integration tests contra infraestructura real. La pirámide de tests es 70% unit / 20% integration / 10% e2e. Sin coverage de use cases no hay aprobación arquitectónica. Ver `reference/testing-patterns.md`.
|
|
34
|
+
|
|
35
|
+
15. **Seguridad como infraestructura transversal** — AuthN, AuthZ, rate limiting y validación son infraestructura, no dominio. Se implementan en `platform/security/` y `platform/http/` como middlewares. El dominio recibe `userId` o `role` si un caso de uso lo necesita, pero nunca implementa lógica de autenticación. Ver `reference/security-patterns.md` y `reference/api-design.md`.
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Refactoriza la arquitectura de un feature o de un componente de platform/shared/infra.
|
|
4
4
|
|
|
5
|
+
También renombra archivos para cumplir con las convenciones de naming (`reference/patterns.md`).
|
|
6
|
+
|
|
5
7
|
## Cuándo usarlo
|
|
6
8
|
|
|
7
9
|
- Un feature tiene violaciones arquitectónicas
|
|
@@ -9,20 +11,82 @@ Refactoriza la arquitectura de un feature o de un componente de platform/shared/
|
|
|
9
11
|
- Un componente de platform necesita refinarse
|
|
10
12
|
- Un componente de shared necesita rediseñarse
|
|
11
13
|
- El grafo arquitectónico muestra violaciones que requieren intervención
|
|
14
|
+
- Un archivo no cumple con las convenciones de naming (`patterns.md`)
|
|
15
|
+
- Quieres renombrar un archivo y actualizar todos sus imports automáticamente
|
|
16
|
+
|
|
17
|
+
## Caso A — `reforge <filename>` (solo renombrar)
|
|
18
|
+
|
|
19
|
+
Renombra un único archivo según las convenciones de `reference/patterns.md` y actualiza todos los imports que lo referencian.
|
|
20
|
+
|
|
21
|
+
**No ejecuta backup** (el rename no modifica lógica de negocio).
|
|
22
|
+
|
|
23
|
+
### Flujo
|
|
24
|
+
|
|
25
|
+
1. Identificar la ruta del archivo (relativa a la raíz del proyecto)
|
|
26
|
+
2. Ejecutar `node .opencode/skills/forge/scripts/rename.mjs --detect --file <path>` para detectar violación
|
|
27
|
+
3. Si el archivo ya cumple naming, informar y terminar
|
|
28
|
+
4. Mostrar preview del cambio al usuario
|
|
29
|
+
5. Ejecutar `node .opencode/skills/forge/scripts/rename.mjs --file <path>` que:
|
|
30
|
+
- Renombra físicamente el archivo
|
|
31
|
+
- Escanea todos los `.ts`/`.js` del proyecto
|
|
32
|
+
- Actualiza imports que referencian el path antiguo (relativos y absolutos)
|
|
33
|
+
6. Verificar con `forge quench`
|
|
34
|
+
7. Reportar resultado: archivo renombrado + cantidad de imports actualizados
|
|
35
|
+
|
|
36
|
+
### Ejemplo
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
$ forge reforge src/features/users/domain/userEntity.ts
|
|
40
|
+
Renombrar:
|
|
41
|
+
src/features/users/domain/userEntity.ts
|
|
42
|
+
→ src/features/users/domain/User.entity.ts
|
|
43
|
+
Regla: feature/users/domain: <Name>.entity.ts
|
|
12
44
|
|
|
13
|
-
|
|
45
|
+
✓ Archivo renombrado: userEntity.ts → User.entity.ts
|
|
46
|
+
✓ Imports actualizados en 3 archivo(s):
|
|
47
|
+
src/features/users/application/use-cases/CreateUser.uc.ts
|
|
48
|
+
src/features/users/application/mappers/User.mapper.ts
|
|
49
|
+
src/features/users/adapters/in/http/User.controller.ts
|
|
50
|
+
```
|
|
14
51
|
|
|
52
|
+
## Caso B — `reforge` (completo con auto-fix naming)
|
|
53
|
+
|
|
54
|
+
### Flujo
|
|
55
|
+
|
|
56
|
+
0. **Backup**: Ejecutar `forge rollback backup <target>` antes de cualquier cambio
|
|
15
57
|
1. Ejecutar `forge inspect` para obtener estado actual
|
|
16
58
|
2. Identificar violaciones en el grafo arquitectónico
|
|
17
59
|
3. Identificar ownership problemático (huérfanos, duplicados, mal ubicados)
|
|
18
|
-
4.
|
|
60
|
+
4. **Detectar naming violations**: Ejecutar `node .opencode/skills/forge/scripts/rename.mjs --detect --json`
|
|
61
|
+
5. Si hay naming violations, preguntar al usuario: "¿Corregir naming conventions automáticamente?"
|
|
62
|
+
- Si acepta, ejecutar `node .opencode/skills/forge/scripts/rename.mjs --all`
|
|
63
|
+
6. Decidir las acciones correctivas en orden:
|
|
19
64
|
- Violaciones CRITICAL primero (R1, R2, R5, R6)
|
|
20
65
|
- Violaciones ERROR después (R3, R4, R8, R9)
|
|
21
66
|
- Ownership problemático
|
|
67
|
+
- Naming violations (si no se corrigieron en paso 5)
|
|
22
68
|
- Warnings
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
69
|
+
7. Ejecutar cambios en el feature o componente
|
|
70
|
+
8. Verificar con `forge rollback verify` — si el score empeora, restaurar
|
|
71
|
+
9. Ejecutar `forge quench` para verificar
|
|
72
|
+
10. Actualizar `ARCHITECTURE.md`
|
|
73
|
+
|
|
74
|
+
### Flags
|
|
75
|
+
|
|
76
|
+
| Flag | Efecto |
|
|
77
|
+
|------|--------|
|
|
78
|
+
| `--dry-run` | Preview de naming violations sin hacer cambios |
|
|
79
|
+
| `--fix-naming` | Corregir naming violations sin preguntar |
|
|
80
|
+
| `--skip-naming` | Omitir detección y corrección de naming |
|
|
81
|
+
|
|
82
|
+
## Rollback
|
|
83
|
+
|
|
84
|
+
Si la refactorización empeora el score arquitectónico, restaurar automáticamente:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
forge rollback list
|
|
88
|
+
forge rollback restore <id>
|
|
89
|
+
```
|
|
26
90
|
|
|
27
91
|
## Refactorización multi-capa
|
|
28
92
|
|
|
@@ -15,6 +15,7 @@ También puede migrar componentes legacy a los layers Platform, Shared o Infrast
|
|
|
15
15
|
|
|
16
16
|
## Flujo
|
|
17
17
|
|
|
18
|
+
0. **Backup automático**: Ejecutar `forge rollback backup <target>` antes de cualquier cambio
|
|
18
19
|
1. Identificar el componente a migrar
|
|
19
20
|
2. Clasificarlo en el layer correcto:
|
|
20
21
|
- Configuración, servidor, logger, DI → **Platform**
|
|
@@ -25,8 +26,20 @@ También puede migrar componentes legacy a los layers Platform, Shared o Infrast
|
|
|
25
26
|
4. Migrar archivos manteniendo la lógica intacta
|
|
26
27
|
5. Actualizar imports
|
|
27
28
|
6. Eliminar estructura legacy
|
|
28
|
-
7.
|
|
29
|
-
8.
|
|
29
|
+
7. Verificar con `forge rollback verify` — si el score empeora, restaurar con `forge rollback restore <backup-id>`
|
|
30
|
+
8. Ejecutar `forge quench` para verificar
|
|
31
|
+
9. Actualizar `ARCHITECTURE.md`
|
|
32
|
+
|
|
33
|
+
## Rollback
|
|
34
|
+
|
|
35
|
+
Si la migración introduce violaciones (R1-R9), el agente DEBE restaurar automáticamente:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
forge rollback list # ver backups disponibles
|
|
39
|
+
forge rollback restore <id> # restaurar
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
El backup se almacena en `.forge/backups/<target>--<timestamp>/` y preserva la estructura original completa.
|
|
30
43
|
|
|
31
44
|
## Estrategias por layer
|
|
32
45
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Security Patterns — Autenticación, autorización, rate limiting, validación
|
|
2
|
+
|
|
3
|
+
## Principios arquitectónicos
|
|
4
|
+
|
|
5
|
+
- La seguridad es **infraestructura transversal**, no dominio de negocio.
|
|
6
|
+
- AuthN/AuthZ se implementan en `platform/security/` y se inyectan como middleware o decoradores.
|
|
7
|
+
- El dominio nunca contiene lógica de autenticación. Solo recibe `userId` si el caso de uso lo necesita.
|
|
8
|
+
- Validación de entrada en el controller o middleware de validación, nunca en la entidad de dominio.
|
|
9
|
+
|
|
10
|
+
## Capas de seguridad
|
|
11
|
+
|
|
12
|
+
| Capa | Responsabilidad | Ubicación |
|
|
13
|
+
|------|----------------|-----------|
|
|
14
|
+
| Transport | TLS, HSTS, CORS, CSRF | platform/http/ |
|
|
15
|
+
| AuthN | Verificar quién es el usuario | platform/security/ |
|
|
16
|
+
| AuthZ | Verificar qué puede hacer | platform/security/ + features |
|
|
17
|
+
| Input validation | Sanitizar entrada | platform/http/ middleware |
|
|
18
|
+
| Rate limiting | Proteger contra abuso | platform/http/ middleware |
|
|
19
|
+
| Audit logging | Registrar acciones sensibles | platform/observability/ |
|
|
20
|
+
|
|
21
|
+
## AuthN: Estrategias
|
|
22
|
+
|
|
23
|
+
| Estrategia | Cuándo usarla |
|
|
24
|
+
|------------|---------------|
|
|
25
|
+
| JWT (access + refresh) | SPAs, mobile, APIs stateless |
|
|
26
|
+
| Session + cookie | SSR tradicional, apps server-side |
|
|
27
|
+
| API Key | Service-to-service, CLIs |
|
|
28
|
+
| OAuth2 / OIDC | Delegación a terceros (Google, GitHub) |
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
// platform/security/Auth.middleware.ts
|
|
32
|
+
export class AuthMiddleware {
|
|
33
|
+
async authenticate(req: Request, res: Response, next: NextFunction) {
|
|
34
|
+
const token = req.headers.authorization?.replace("Bearer ", "");
|
|
35
|
+
const payload = await this.jwtService.verify(token);
|
|
36
|
+
req.user = payload;
|
|
37
|
+
next();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## AuthZ: RBAC y Policy-based
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
// features/users/application/use-cases/DeleteUser.uc.ts
|
|
46
|
+
export class DeleteUserUseCase {
|
|
47
|
+
constructor(
|
|
48
|
+
private readonly userRepo: IUserRepository,
|
|
49
|
+
private readonly authz: IAuthorizationService,
|
|
50
|
+
) {}
|
|
51
|
+
|
|
52
|
+
async execute(actorId: string, targetId: string): Promise<void> {
|
|
53
|
+
await this.authz.require(actorId, "user:delete", { targetId });
|
|
54
|
+
// … lógica del caso de uso
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Rate limiting
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
// platform/http/RateLimiter.middleware.ts
|
|
63
|
+
// Por IP, por ruta, por rol. Almacenamiento en Redis.
|
|
64
|
+
// Headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Validación
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
// shared/contracts/schemas.ts (zod)
|
|
71
|
+
export const createUserSchema = z.object({
|
|
72
|
+
email: z.string().email(),
|
|
73
|
+
name: z.string().min(2).max(100),
|
|
74
|
+
role: z.enum(["admin", "user"]).default("user"),
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Buenas prácticas
|
|
79
|
+
|
|
80
|
+
- Jamás almacenar passwords en texto plano. Usar bcrypt/argon2.
|
|
81
|
+
- JWT con corta expiración (15min access, 7d refresh)
|
|
82
|
+
- Refresh token rotation al usarse
|
|
83
|
+
- Rate limit por endpoint según criticidad: login (5/min), register (3/min), GET (100/min)
|
|
84
|
+
- CORS whitelist de origins conocidos, nunca `*`
|
|
85
|
+
- Helmet para headers de seguridad (CSP, X-Frame-Options, etc.)
|
|
86
|
+
- Secretos en variables de entorno, nunca en código
|
|
87
|
+
- Auditoría de acciones sensibles: login, delete, role change, export
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Testing Patterns — Tests en arquitectura hexagonal
|
|
2
|
+
|
|
3
|
+
## Pirámide de tests
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
╱╲
|
|
7
|
+
╱ E2E ╲ (2-5%) — happy paths críticos
|
|
8
|
+
╱────────╲
|
|
9
|
+
╱ Integration ╲ (15-25%) — adapters, rutas, repositorios
|
|
10
|
+
╱────────────────╲
|
|
11
|
+
╱ Unit Tests ╲ (70-80%) — use cases, entidades, mappers
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Qué testear en cada capa
|
|
15
|
+
|
|
16
|
+
| Capa | Tipo | Qué testear |
|
|
17
|
+
|------|------|-------------|
|
|
18
|
+
| `domain/entity` | Unit | Reglas de negocio, invariantes, validaciones |
|
|
19
|
+
| `application/use-cases` | Unit | Flujo completo con mocks de repositorios |
|
|
20
|
+
| `application/mappers` | Unit | Mapeo dominio ↔ DTO |
|
|
21
|
+
| `adapters/in/http` | Integration | HTTP requests, status codes, payloads |
|
|
22
|
+
| `adapters/out/persistence` | Integration | Queries reales (test DB) |
|
|
23
|
+
| `shared/errors` | Unit | Mensajes, códigos, instanceof |
|
|
24
|
+
| `platform/` | Integration | Middleware, server, logger, cache |
|
|
25
|
+
|
|
26
|
+
## Unit: Use case con mocks
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
// test/features/users/CreateUser.uc.test.ts
|
|
30
|
+
import { CreateUserUseCase } from "@/features/users/application/use-cases/CreateUser.uc.js";
|
|
31
|
+
|
|
32
|
+
const mockRepo = {
|
|
33
|
+
findByEmail: vi.fn(),
|
|
34
|
+
save: vi.fn(),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const uc = new CreateUserUseCase(mockRepo);
|
|
38
|
+
|
|
39
|
+
it("creates a user successfully", async () => {
|
|
40
|
+
mockRepo.findByEmail.mockResolvedValue(null);
|
|
41
|
+
mockRepo.save.mockResolvedValue({ id: "1", email: "test@test.com" });
|
|
42
|
+
|
|
43
|
+
const result = await uc.execute({ email: "test@test.com", name: "Test" });
|
|
44
|
+
expect(result).toHaveProperty("id");
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Integration: Controller
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
// test/features/users/User.controller.test.ts
|
|
52
|
+
import supertest from "supertest";
|
|
53
|
+
|
|
54
|
+
it("POST /users returns 201", async () => {
|
|
55
|
+
const res = await request(app)
|
|
56
|
+
.post("/users")
|
|
57
|
+
.send({ email: "test@test.com", name: "Test" });
|
|
58
|
+
expect(res.status).toBe(201);
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Buenas prácticas
|
|
63
|
+
|
|
64
|
+
- No testear frameworks, solo tu código
|
|
65
|
+
- Mocks en el puerto (interfaz), no en la implementación
|
|
66
|
+
- Tests de integración contra BD real o testcontainers
|
|
67
|
+
- Nombrar tests como unidades de comportamiento, no métodos
|
|
68
|
+
- Coverage mínimo sugerido: 85% use cases, 75% adapters
|
|
69
|
+
- Los mappers se testean con fixtures: entrada conocida → salida esperada
|