@ronaldjdevfs/forge 1.4.0 → 1.4.3
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 +3 -3
- package/package.json +1 -1
- package/skills/forge/SKILL.md +23 -23
- package/skills/forge/profiles/express-mongodb.md +11 -6
- package/skills/forge/profiles/express-prisma.md +1 -1
- package/skills/forge/reference/adr.md +4 -4
- package/skills/forge/reference/architectural-depth-checklist.md +4 -4
- package/skills/forge/reference/cast.md +5 -5
- package/skills/forge/reference/chain.md +2 -2
- package/skills/forge/reference/cohesion-checklist.md +2 -2
- package/skills/forge/reference/di-strategies.md +22 -5
- package/skills/forge/reference/evolutionary-architecture.md +7 -7
- package/skills/forge/reference/forge.md +9 -9
- package/skills/forge/reference/hooks.md +1 -1
- package/skills/forge/reference/inscribe.md +14 -14
- package/skills/forge/reference/inspect.md +4 -4
- package/skills/forge/reference/patterns.md +1 -1
- package/skills/forge/reference/quench.md +1 -1
- package/skills/forge/scripts/detect.mjs +1 -1
- package/skills/forge/scripts/forgeSmith.mjs +1 -1
- package/skills/forge/templates/agents/SKILL.md.template +38 -38
- package/skills/forge/templates/agents/opencode/SKILL.md +13 -0
- package/skills/forge/templates/feature/controller.ts.md +1 -2
- package/skills/forge/templates/feature/di.ts.md +8 -4
- package/src/cli.js +14 -1
- package/src/wizard.mjs +40 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<img src="favicon.svg" alt="Forge Logo" width="100" height="100">
|
|
2
2
|
|
|
3
|
-
> **v1.4.
|
|
3
|
+
> **v1.4.3** — DI: feature/di.ts como fuente única de registro
|
|
4
4
|
|
|
5
5
|
## Forge — Backend Architecture Operating System
|
|
6
6
|
|
|
@@ -85,7 +85,7 @@ Evalúa 10 categorías contra un máximo de 180 puntos (normalizado a 0-100):
|
|
|
85
85
|
| Graph | 20 | Salud del grafo arquitectónico, risk score |
|
|
86
86
|
| Custom Rules | 5 | Reglas personalizadas desde `.forge/rules.json` |
|
|
87
87
|
| Naming | 10 | Convenciones de nomenclatura PascalCase/kebab |
|
|
88
|
-
| Import Conventions | 20 | R10-R12: bare specifiers, extensión .ts,
|
|
88
|
+
| Import Conventions | 20 | R10-R12: bare specifiers, extensión .ts, imports prohibidos |
|
|
89
89
|
|
|
90
90
|
**Resultado**: Score 0-100 con grado A-F y severidades por cada violación.
|
|
91
91
|
|
|
@@ -122,7 +122,7 @@ Ejecuta 9 reglas arquitectónicas (R1-R9) con severidad:
|
|
|
122
122
|
| R9 | Ciclos de dependencia | ERROR |
|
|
123
123
|
| R10 | Bare specifiers en imports locales | ERROR |
|
|
124
124
|
| R11 | Extensión `.ts` en imports (debe ser `.js`) | ERROR |
|
|
125
|
-
| R12 | Import a `bootstrap.di.js`
|
|
125
|
+
| R12 | Import a archivo DI inexistente (ej: `bootstrap.di.js`) | CRITICAL |
|
|
126
126
|
| R12b | `registerSingleton` con model() de Mongoose | CRITICAL |
|
|
127
127
|
|
|
128
128
|
### `temper` — Endurecimiento de DI
|
package/package.json
CHANGED
package/skills/forge/SKILL.md
CHANGED
|
@@ -72,7 +72,7 @@ En esencia:
|
|
|
72
72
|
|
|
73
73
|
## Boot Sequence
|
|
74
74
|
|
|
75
|
-
ANTES de cualquier acción, Forge DEBE ejecutar `forge-boot.mjs` con la profundidad adecuada al comando:
|
|
75
|
+
ANTES de cualquier acción, Forge DEBE ejecutar `{{AGENT_PATH}}/scripts/forge-boot.mjs` con la profundidad adecuada al comando:
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
78
|
boot=$(node {{AGENT_PATH}}/scripts/forge-boot.mjs --depth <depth> --json 2>/dev/null)
|
|
@@ -99,15 +99,15 @@ Si `$boot` contiene datos cacheados en `.forge/cache/` se reusan automáticament
|
|
|
99
99
|
| Refactorizar | `reforge` | `reference/reforge.md` |
|
|
100
100
|
| Verificar violaciones | `quench` | `reference/quench.md` |
|
|
101
101
|
| Endurecer | `temper` | `reference/temper.md` |
|
|
102
|
-
| Dependencias | `chain` | `scripts/chain.mjs` |
|
|
102
|
+
| Dependencias | `chain` | `{{AGENT_PATH}}/scripts/chain.mjs` |
|
|
103
103
|
| Inscribir ARCHITECTURE.md | `inscribe` | `reference/inscribe.md` |
|
|
104
|
-
| Grafo arquitectónico | `graph` | `scripts/graph.mjs` |
|
|
104
|
+
| Grafo arquitectónico | `graph` | `{{AGENT_PATH}}/scripts/graph.mjs` |
|
|
105
105
|
| Fundir a shared | `smelt` | `reference/smelt.md` |
|
|
106
|
-
| Atajo / pin | `nail` / `unnail` | `scripts/pin.mjs` |
|
|
106
|
+
| Atajo / pin | `nail` / `unnail` | `{{AGENT_PATH}}/scripts/pin.mjs` |
|
|
107
107
|
| Git hook | `forge hook` | `reference/hooks.md` |
|
|
108
|
-
| API design | `forge api` | `scripts/forge-api.mjs` |
|
|
109
|
-
| Rollback | `forge rollback` | `scripts/rollback.mjs` |
|
|
110
|
-
| Estado | `forge state` | `scripts/forge-state.mjs` |
|
|
108
|
+
| API design | `forge api` | `{{AGENT_PATH}}/scripts/forge-api.mjs` |
|
|
109
|
+
| Rollback | `forge rollback` | `{{AGENT_PATH}}/scripts/rollback.mjs` |
|
|
110
|
+
| Estado | `forge state` | `{{AGENT_PATH}}/scripts/forge-state.mjs` |
|
|
111
111
|
| Ensayo cualitativo | `assay` | `reference/assay.md` |
|
|
112
112
|
| Bounded context | `forge` | `reference/bounded-contexts.md` |
|
|
113
113
|
| Modular monolith | `forge` | `reference/modular-monolith.md` |
|
|
@@ -126,17 +126,17 @@ Si `$boot` contiene datos cacheados en `.forge/cache/` se reusan automáticament
|
|
|
126
126
|
|
|
127
127
|
Para cada comando, Forge sigue este flujo:
|
|
128
128
|
|
|
129
|
-
1. **Boot condicional**: Ejecutar `forge-boot.mjs --depth <depth>` donde depth es:
|
|
129
|
+
1. **Boot condicional**: Ejecutar `{{AGENT_PATH}}/scripts/forge-boot.mjs --depth <depth>` donde depth es:
|
|
130
130
|
- `minimal` para cast, temper, smelt, relocate, reforge, inscribe
|
|
131
131
|
- `standard` para chain, graph, forge hook
|
|
132
132
|
- `full` para inspect, quench, o cualquier otro comando
|
|
133
133
|
2. **Referencia**: Cargar `reference/<command>.md`
|
|
134
134
|
3. **Ejecutar**: Aplicar el flujo definido en la referencia
|
|
135
|
-
4. **Verificar**: Ejecutar `detect.mjs --summary` (resumen compacto)
|
|
136
|
-
5. **Actualizar ARCHITECTURE.md**: `architecture.mjs` (solo en full)
|
|
135
|
+
4. **Verificar**: Ejecutar `{{AGENT_PATH}}/scripts/detect.mjs --summary` (resumen compacto)
|
|
136
|
+
5. **Actualizar ARCHITECTURE.md**: `{{AGENT_PATH}}/scripts/architecture.mjs` (solo en full)
|
|
137
137
|
6. **Reportar**: Mostrar resultado al usuario con severidades
|
|
138
138
|
|
|
139
|
-
El boot usa caché de `.forge/cache/`. Si los archivos `src/` no cambiaron, los datos se reusan. Usa `forge-boot.mjs --force` para regenerar todo.
|
|
139
|
+
El boot usa caché de `.forge/cache/`. Si los archivos `src/` no cambiaron, los datos se reusan. Usa `{{AGENT_PATH}}/scripts/forge-boot.mjs --force` para regenerar todo.
|
|
140
140
|
|
|
141
141
|
---
|
|
142
142
|
|
|
@@ -180,9 +180,9 @@ import { crossFeature } from "../other-feature/domain/Entity"; // ← R1 y R8 ig
|
|
|
180
180
|
|
|
181
181
|
## ARCHITECTURE.md
|
|
182
182
|
|
|
183
|
-
Forge mantiene `ARCHITECTURE.md` en la raíz con el estado persistente del proyecto (framework, DB, features, ownership, graph). Se genera y actualiza con `architecture.mjs`.
|
|
183
|
+
Forge mantiene `ARCHITECTURE.md` en la raíz con el estado persistente del proyecto (framework, DB, features, ownership, graph). Se genera y actualiza con `{{AGENT_PATH}}/scripts/architecture.mjs`.
|
|
184
184
|
|
|
185
|
-
El agente DEBE leer este archivo al inicio de cada interacción y actualizarlo al finalizar cada comando. Ver `scripts/architecture.mjs` para el formato completo.
|
|
185
|
+
El agente DEBE leer este archivo al inicio de cada interacción y actualizarlo al finalizar cada comando. Ver `{{AGENT_PATH}}/scripts/architecture.mjs` para el formato completo.
|
|
186
186
|
|
|
187
187
|
---
|
|
188
188
|
|
|
@@ -198,16 +198,16 @@ node --test {{AGENT_PATH}}/tests/core.test.mjs
|
|
|
198
198
|
|
|
199
199
|
| Módulo | Tests | Descripción |
|
|
200
200
|
|--------|-------|-------------|
|
|
201
|
-
| `profile.mjs` | 8 | Detección de perfiles |
|
|
202
|
-
| `graph.mjs` | 1 | Grafo vacío |
|
|
203
|
-
| `armorer.mjs` | 1 | Ownership vacío |
|
|
204
|
-
| `forge-config.mjs` | 2 | Load/save state |
|
|
205
|
-
| `chain.mjs` | 1 | Grafo de dependencias vacío |
|
|
206
|
-
| `formatter.mjs` | 4 | Output format, colores, JSON |
|
|
207
|
-
| `registry/rules.mjs` | 4 | R1-R9, evaluación, custom rules |
|
|
208
|
-
| `detect.mjs` (inline ignores) | 5 | parseInlineIgnores, isIgnored |
|
|
209
|
-
| `posttool.mjs` | 1 | PostToolUse hook |
|
|
210
|
-
| `assay.mjs` | 4 | Personas, generateAssay, opiniones |
|
|
201
|
+
| `{{AGENT_PATH}}/scripts/profile.mjs` | 8 | Detección de perfiles |
|
|
202
|
+
| `{{AGENT_PATH}}/scripts/graph.mjs` | 1 | Grafo vacío |
|
|
203
|
+
| `{{AGENT_PATH}}/scripts/armorer.mjs` | 1 | Ownership vacío |
|
|
204
|
+
| `{{AGENT_PATH}}/scripts/forge-config.mjs` | 2 | Load/save state |
|
|
205
|
+
| `{{AGENT_PATH}}/scripts/chain.mjs` | 1 | Grafo de dependencias vacío |
|
|
206
|
+
| `{{AGENT_PATH}}/scripts/formatter.mjs` | 4 | Output format, colores, JSON |
|
|
207
|
+
| `{{AGENT_PATH}}/scripts/registry/rules.mjs` | 4 | R1-R9, evaluación, custom rules |
|
|
208
|
+
| `{{AGENT_PATH}}/scripts/detect.mjs` (inline ignores) | 5 | parseInlineIgnores, isIgnored |
|
|
209
|
+
| `{{AGENT_PATH}}/scripts/posttool.mjs` | 1 | PostToolUse hook |
|
|
210
|
+
| `{{AGENT_PATH}}/scripts/assay.mjs` | 4 | Personas, generateAssay, opiniones |
|
|
211
211
|
| transactional-outbox | 5 | Entry lifecycle, retry, DLQ, required fields, pending |
|
|
212
212
|
| idempotency | 5 | UUID validation, cached response, different keys, TTL, method filter |
|
|
213
213
|
| anti-corruption-layer | 5 | DTO mapping (2 dirs), null handling, 404, delegation order |
|
|
@@ -91,20 +91,25 @@ import { createApp } from "./app.js";
|
|
|
91
91
|
```typescript
|
|
92
92
|
import { container } from "tsyringe";
|
|
93
93
|
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
// Importar los di.ts de cada feature — cada uno registra sus propias dependencias.
|
|
95
|
+
// NUNCA registrar las mismas dependencias aquí directamente.
|
|
96
|
+
import "@/features/credit/di.js";
|
|
97
|
+
// import "@/features/users/di.js"; // más features...
|
|
98
|
+
|
|
99
|
+
// Solo registrar dependencias transversales (platform/shared) aquí:
|
|
100
|
+
// container.registerSingleton<ILogger>("ILogger", WinstonLogger);
|
|
99
101
|
```
|
|
100
102
|
|
|
101
103
|
## DI Rules
|
|
102
104
|
|
|
103
105
|
- `@injectable()` en toda clase con dependencias (use cases, controllers, repositories)
|
|
104
106
|
- `@inject(Token)` con tokens de clase, nunca strings
|
|
105
|
-
-
|
|
107
|
+
- Cada feature registra sus dependencias en `feature/di.ts` (fuente única)
|
|
108
|
+
- `app.ts` importa los `di.ts` de cada feature — no registra features directamente
|
|
109
|
+
- `container.resolve()` solo en routes (para resolver controllers)
|
|
106
110
|
- Prohibido `container.resolve()` en use cases, entities o adapters
|
|
107
111
|
- Prohibido `new UseCase(dep1, dep2)` en features migrados a DI
|
|
112
|
+
- Prohibido registrar la misma dependencia en `app.ts` y `feature/di.ts`
|
|
108
113
|
|
|
109
114
|
## Routes & Controllers
|
|
110
115
|
|
|
@@ -37,7 +37,7 @@ architecture: hexagonal-feature
|
|
|
37
37
|
|
|
38
38
|
## DI Setup
|
|
39
39
|
|
|
40
|
-
Igual que `express-mongodb` (tsyringe).
|
|
40
|
+
Igual que `express-mongodb` (tsyringe). Cada feature registra sus dependencias en `feature/di.ts`. `app.ts` importa esos archivos:
|
|
41
41
|
|
|
42
42
|
## Persistence (Prisma)
|
|
43
43
|
|
|
@@ -205,10 +205,10 @@ docs/
|
|
|
205
205
|
El `README.md` se genera automáticamente listando los ADRs activos:
|
|
206
206
|
|
|
207
207
|
```bash
|
|
208
|
-
# scripts/forge-adr.mjs (futuro)
|
|
209
|
-
node scripts/forge-adr.mjs list # lista todos los ADRs
|
|
210
|
-
node scripts/forge-adr.mjs new # crea nuevo ADR desde template
|
|
211
|
-
node scripts/forge-adr.mjs status # cambia estado de un ADR
|
|
208
|
+
# {{AGENT_PATH}}/scripts/forge-adr.mjs (futuro)
|
|
209
|
+
node {{AGENT_PATH}}/scripts/forge-adr.mjs list # lista todos los ADRs
|
|
210
|
+
node {{AGENT_PATH}}/scripts/forge-adr.mjs new # crea nuevo ADR desde template
|
|
211
|
+
node {{AGENT_PATH}}/scripts/forge-adr.mjs status # cambia estado de un ADR
|
|
212
212
|
```
|
|
213
213
|
|
|
214
214
|
---
|
|
@@ -87,7 +87,7 @@ Un equipo puede evaluar su arquitectura actual contra el marco de decisión y ob
|
|
|
87
87
|
- [ ] **Integración con `inscribe`**: anexar ADRs activos en ARCHITECTURE.md
|
|
88
88
|
- [ ] **Integración con `assay`**: las decisiones como insumo para el ensayo multi-persona
|
|
89
89
|
- [ ] **Cuándo escribir un ADR**: scoping, tecnología, patrón, estándar, cambio de regla, excepción
|
|
90
|
-
- [ ] **ADRs como fuente de verdad**: enlazar ADRs desde reglas de detect.mjs, desde inline ignores
|
|
90
|
+
- [ ] **ADRs como fuente de verdad**: enlazar ADRs desde reglas de {{AGENT_PATH}}/scripts/detect.mjs, desde inline ignores
|
|
91
91
|
- [ ] **Ejemplos**: ADRs reales del modelo Forge (ej. "usar capa Shared en vez de cross-feather imports", "adoptar Prisma como ORM")
|
|
92
92
|
- [ ] **Tooling**: script para crear, listar, cambiar estado de ADRs
|
|
93
93
|
- [ ] **Anti-patrones**: ADRs que nunca se leen, ADRs sin contexto, ADRs sin consecuencia, ADRs de frameworks
|
|
@@ -114,7 +114,7 @@ Un `forge inscribe` genera ARCHITECTURE.md con enlaces a ADRs activos. Un nuevo
|
|
|
114
114
|
- [ ] **Mapeo de integraciones legacy**: cómo modelar sistemas externos como bounded contexts con ACL
|
|
115
115
|
- [ ] **Estrategias de traducción**: event-based (publicar/suscribir), service-based (llamadas sincrónicas), repository-based (datos compartidos)
|
|
116
116
|
- [ ] **Conexión con reglas R1 y R7**: cómo ACL permite feature → infra sin violar la regla (porque el adapter traduce)
|
|
117
|
-
- [ ] **Detección automática**: qué patrones en detect.mjs indican necesidad de ACL
|
|
117
|
+
- [ ] **Detección automática**: qué patrones en {{AGENT_PATH}}/scripts/detect.mjs indican necesidad de ACL
|
|
118
118
|
- [ ] **Ejemplo**: feature de "Orders" migrando de legacy SQL a nuevo schema con ACL + Strangler Fig
|
|
119
119
|
- [ ] **Anti-patrones**: ACL que filtra sin traducir (leaky abstraction), ACL que muta el origen, ACL como pasamanos
|
|
120
120
|
|
|
@@ -136,8 +136,8 @@ Un desarrollador puede identificar cuándo necesita una ACL, modelarla dentro de
|
|
|
136
136
|
- [ ] **Definición**: arquitectura que evoluciona incrementalmente guiada por fitness functions
|
|
137
137
|
- [ ] **Fitness functions**: tests automatizados que validan características arquitectónicas (acoplamiento, modularidad, performance, seguridad)
|
|
138
138
|
- [ ] **Tipos de fitness functions**: estáticas (lint-level), dinámicas (runtime), periódicas (benchmark), trigger-based (CI)
|
|
139
|
-
- [ ] **Implementación en Forge**: las reglas R1-R9 como fitness functions gobernadas por detect.mjs
|
|
140
|
-
- [ ] **Fitness functions custom**: cómo el usuario define sus propias funciones y se registran en registry/rules.mjs
|
|
139
|
+
- [ ] **Implementación en Forge**: las reglas R1-R9 como fitness functions gobernadas por {{AGENT_PATH}}/scripts/detect.mjs
|
|
140
|
+
- [ ] **Fitness functions custom**: cómo el usuario define sus propias funciones y se registran en {{AGENT_PATH}}/scripts/registry/rules.mjs
|
|
141
141
|
- [ ] **Guía de cambio incremental**: smallest viable change, refactor patterns, scaffolding antes de feature completo
|
|
142
142
|
- [ ] **Evolución de boundaries**: cómo partir, fusionar o mover features sin reescribir
|
|
143
143
|
- [ ] **Integración con CI/CD**: fitness functions en pipeline, gate de deployment, alertas de regresión
|
|
@@ -21,7 +21,7 @@ Si alguno no existe, ejecutar `bootstrapPlatform()` automáticamente para crearl
|
|
|
21
21
|
|
|
22
22
|
**NO escribir código hasta pasar los 3 gates de aprobación.** Cast solía crear scaffolding directamente del nombre del feature. Esto producía features genéricos que requerían refactor posterior. Ahora todo `cast` requiere descubrimiento direccional multi-ronda.
|
|
23
23
|
|
|
24
|
-
Usar las **señales de `forge-signals.mjs`** para contextualizar el descubrimiento:
|
|
24
|
+
Usar las **señales de `{{AGENT_PATH}}/scripts/forge-signals.mjs`** para contextualizar el descubrimiento:
|
|
25
25
|
- Si hay features existentes, revisar sus entidades y casos de uso para mantener coherencia.
|
|
26
26
|
- Si el perfil está detectado, usarlo para preguntas específicas (ej: "Prisma detectado → schema primero").
|
|
27
27
|
- Si hay archivos modificados en git, considerarlos como contexto del nuevo feature.
|
|
@@ -157,9 +157,9 @@ Regla: si la entidad vive fuera del feature, todos los imports deben usar path a
|
|
|
157
157
|
|
|
158
158
|
Después de crear los archivos del feature, generar `di.ts` siguiendo el template `templates/feature/di.ts.md`:
|
|
159
159
|
|
|
160
|
-
1. **Feature con DI propia**: crear `src/features/<name>/di.ts` usando el template
|
|
161
|
-
2. **
|
|
162
|
-
3. **Controllers**: asegurar que el import en el controller apunte a
|
|
160
|
+
1. **Feature con DI propia (siempre)**: crear `src/features/<name>/di.ts` usando el template. Este archivo es la **fuente única** de registro para el feature.
|
|
161
|
+
2. **app.ts**: importar el `di.ts` del feature (ej: `import "@/features/<name>/di.js";`). No registrar las mismas dependencias en app.ts.
|
|
162
|
+
3. **Controllers**: asegurar que el import en el controller apunte a `./di.js` (del feature), NUNCA a `bootstrap.di.js`
|
|
163
163
|
4. **Mongoose model()**: si el schema exporta `export default model()` (objeto, no clase), el DI debe usar `container.register(..., { useValue: ... })`, NO `registerSingleton`
|
|
164
164
|
|
|
165
165
|
## ⚠️ Post-Cast: Tests
|
|
@@ -181,7 +181,7 @@ Antes de dar por terminado el feature, verificar CADA archivo generado:
|
|
|
181
181
|
- [ ] Todos los imports locales usan prefijo `./` o `../` — sin bare specifiers (`import X from "domain/..."` ❌)
|
|
182
182
|
- [ ] Todos los imports tienen extensión `.js` — sin extensión `.ts`
|
|
183
183
|
- [ ] Entidades compartidas usan `@/domain/` — sin paths relativos rotos
|
|
184
|
-
- [ ] Controllers importan desde
|
|
184
|
+
- [ ] Controllers importan desde `./di.js` — no desde `bootstrap.di.js`
|
|
185
185
|
- [ ] Nombres de método del controller coinciden con los de la ruta (ej: `createHandler` en controller → `controller.createHandler` en routes)
|
|
186
186
|
- [ ] DI usa `register({ useValue })` para modelos Mongoose — no `registerSingleton`
|
|
187
187
|
- [ ] Tests: `.js` extension, `as const`, `!`, `as any` para _id
|
|
@@ -63,7 +63,7 @@ node {{AGENT_PATH}}/scripts/graph.mjs
|
|
|
63
63
|
node {{AGENT_PATH}}/scripts/architecture.mjs
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
`chain.mjs` ahora es un wrapper sobre `graph.mjs`. Produce el mismo formato de salida para compatibilidad (nodos, edges, features, orden topológico). El nuevo `graph.mjs` amplía el análisis a todos los tipos de nodo (core, feature, domain, infra, adapter) y detecta violaciones de reglas arquitectónicas (R1-R6).
|
|
66
|
+
`{{AGENT_PATH}}/scripts/chain.mjs` ahora es un wrapper sobre `{{AGENT_PATH}}/scripts/graph.mjs`. Produce el mismo formato de salida para compatibilidad (nodos, edges, features, orden topológico). El nuevo `{{AGENT_PATH}}/scripts/graph.mjs` amplía el análisis a todos los tipos de nodo (core, feature, domain, infra, adapter) y detecta violaciones de reglas arquitectónicas (R1-R6).
|
|
67
67
|
|
|
68
68
|
## Buenas prácticas
|
|
69
69
|
|
|
@@ -74,6 +74,6 @@ node {{AGENT_PATH}}/scripts/architecture.mjs
|
|
|
74
74
|
|
|
75
75
|
## Ver también
|
|
76
76
|
|
|
77
|
-
- `scripts/graph.mjs` — el grafo que chain analiza topológicamente
|
|
77
|
+
- `{{AGENT_PATH}}/scripts/graph.mjs` — el grafo que chain analiza topológicamente
|
|
78
78
|
- `reference/evolutionary-architecture.md` — fitness functions de dependencias
|
|
79
79
|
- `reference/modular-monolith.md` — ciclo de dependencias como señal de split
|
|
@@ -30,7 +30,7 @@ Checklist para llevar la cohesión del corpus de referencias de ~8.5/10 a 10/10.
|
|
|
30
30
|
### F1.2 — `chain.md`
|
|
31
31
|
|
|
32
32
|
- [ ] Añadir "Ver también" al final:
|
|
33
|
-
- `scripts/graph.mjs` — el grafo que chain analiza topológicamente
|
|
33
|
+
- `{{AGENT_PATH}}/scripts/graph.mjs` — el grafo que chain analiza topológicamente
|
|
34
34
|
- `reference/evolutionary-architecture.md` — fitness functions de dependencias
|
|
35
35
|
- `reference/modular-monolith.md` — ciclo de dependencias como señal de split
|
|
36
36
|
|
|
@@ -55,7 +55,7 @@ Checklist para llevar la cohesión del corpus de referencias de ~8.5/10 a 10/10.
|
|
|
55
55
|
- `reference/quench.md` — validación que el hook ejecuta en pre-commit
|
|
56
56
|
- `reference/evolutionary-architecture.md` — fitness functions como hook
|
|
57
57
|
- `reference/adr.md` — ADRs como insumo para validación en hook
|
|
58
|
-
- `
|
|
58
|
+
- `{{AGENT_PATH}}/scripts/detect.mjs` — script que el hook invoca
|
|
59
59
|
|
|
60
60
|
### F1.6 — `smelt.md`
|
|
61
61
|
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
- Constructor injection sobre service locators o decoradores mágicos.
|
|
6
6
|
- El dominio nunca importa el contenedor. Solo application y adapters.
|
|
7
7
|
- Interfaces en domain/; implementaciones en adapters/out/ o platform/.
|
|
8
|
+
- **Cada feature es dueña de su registro DI** via `feature/di.ts`.
|
|
8
9
|
|
|
9
10
|
## Estrategias según tamaño del proyecto
|
|
10
11
|
|
|
@@ -29,23 +30,39 @@ const userController = new UserController(createUserUc);
|
|
|
29
30
|
router.post("/users", (req, res) => userController.create(req, res));
|
|
30
31
|
```
|
|
31
32
|
|
|
32
|
-
## Contenedor (tsyringe)
|
|
33
|
+
## Contenedor (tsyringe) — Patrón feature/di.ts
|
|
34
|
+
|
|
35
|
+
Cada feature tiene un `di.ts` que es la **fuente única** de registro para ese feature.
|
|
36
|
+
`app.ts` importa esos archivos como side-effect:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
// src/app.ts — Composition Root (solo imports)
|
|
40
|
+
import { container } from "tsyringe";
|
|
41
|
+
import "@/features/users/di.js";
|
|
42
|
+
import "@/features/credits/di.js";
|
|
43
|
+
// ...
|
|
44
|
+
```
|
|
33
45
|
|
|
34
46
|
```ts
|
|
35
|
-
// di
|
|
47
|
+
// src/features/users/di.ts — Registro del feature (fuente única)
|
|
36
48
|
import { container } from "tsyringe";
|
|
37
|
-
import { IUserRepository } from "
|
|
38
|
-
import { PostgresUserRepository } from "
|
|
49
|
+
import { IUserRepository } from "./domain/IUser.repository.js";
|
|
50
|
+
import { PostgresUserRepository } from "./adapters/out/persistence/PostgresUser.repository.js";
|
|
39
51
|
|
|
40
52
|
container.registerSingleton<IUserRepository>("IUserRepository", PostgresUserRepository);
|
|
41
53
|
```
|
|
42
54
|
|
|
55
|
+
**Regla clave**: Nunca registrar la misma dependencia en `app.ts` y `feature/di.ts`.
|
|
56
|
+
`app.ts` solo registra dependencias transversales (platform/shared).
|
|
57
|
+
|
|
43
58
|
## Buenas prácticas
|
|
44
59
|
|
|
45
60
|
- Composition Root único y lo más cercano al entrypoint posible
|
|
61
|
+
- Cada feature registra sus dependencias en su propio `di.ts`
|
|
62
|
+
- `app.ts` importa los `di.ts` — no registra features directamente
|
|
46
63
|
- Solo el Composition Root conoce todas las implementaciones concretas
|
|
47
64
|
- Preferir interfaces sobre clases abstractas en domain/
|
|
48
|
-
- Evitar `container.resolve()` fuera del Composition Root
|
|
65
|
+
- Evitar `container.resolve()` fuera del Composition Root y routes
|
|
49
66
|
- Usar tokens (strings o símbolos) para identificar dependencias
|
|
50
67
|
- Testear use cases con mocks manuales sin necesidad del contenedor
|
|
51
68
|
|
|
@@ -25,7 +25,7 @@ Una **fitness function** es un test automatizado que valida una característica
|
|
|
25
25
|
Las 9 reglas de Forge son fitness functions:
|
|
26
26
|
|
|
27
27
|
```ts
|
|
28
|
-
// scripts/registry/rules.mjs (conceptual)
|
|
28
|
+
// {{AGENT_PATH}}/scripts/registry/rules.mjs (conceptual)
|
|
29
29
|
const rules = {
|
|
30
30
|
R1: {
|
|
31
31
|
name: "feature → infra prohibited",
|
|
@@ -44,7 +44,7 @@ const rules = {
|
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
Estas fitness functions se ejecutan en:
|
|
47
|
-
- `node scripts/detect.mjs` — detección local
|
|
47
|
+
- `node {{AGENT_PATH}}/scripts/detect.mjs` — detección local
|
|
48
48
|
- `forge quench` — validación completa
|
|
49
49
|
- PostToolUse hook — después de cada escritura del agente
|
|
50
50
|
|
|
@@ -53,7 +53,7 @@ Estas fitness functions se ejecutan en:
|
|
|
53
53
|
Los usuarios pueden registrar sus propias funciones:
|
|
54
54
|
|
|
55
55
|
```ts
|
|
56
|
-
// scripts/registry/rules.mjs
|
|
56
|
+
// {{AGENT_PATH}}/scripts/registry/rules.mjs
|
|
57
57
|
import { registerRule } from "./registry/rules.mjs";
|
|
58
58
|
|
|
59
59
|
registerRule({
|
|
@@ -70,7 +70,7 @@ registerRule({
|
|
|
70
70
|
|
|
71
71
|
| Tipo | Ejecución | Ejemplo |
|
|
72
72
|
|---|---|---|
|
|
73
|
-
| **Estática** | Lint/build | `detect.mjs` analiza imports |
|
|
73
|
+
| **Estática** | Lint/build | `{{AGENT_PATH}}/scripts/detect.mjs` analiza imports |
|
|
74
74
|
| **Dinámica** | Runtime | Verificar que event bus no pierde eventos |
|
|
75
75
|
| **Benchmark** | CI periódico | Tiempo de respuesta de queries < 200ms |
|
|
76
76
|
| **Trigger-based** | Evento (PR, deploy) | No hay imports directos entre features |
|
|
@@ -100,7 +100,7 @@ El flujo de cambio guiado de Forge asegura que cada modificación arquitectónic
|
|
|
100
100
|
### 1. Estado actual (before)
|
|
101
101
|
|
|
102
102
|
```bash
|
|
103
|
-
node scripts/inspect.mjs --json
|
|
103
|
+
node {{AGENT_PATH}}/scripts/inspect.mjs --json
|
|
104
104
|
# Score: 85
|
|
105
105
|
# Violaciones: 2 WARNING (R9 borderline, naming)
|
|
106
106
|
```
|
|
@@ -130,7 +130,7 @@ forge reforge --apply
|
|
|
130
130
|
### 5. Estado actual (after)
|
|
131
131
|
|
|
132
132
|
```bash
|
|
133
|
-
node scripts/inspect.mjs --json
|
|
133
|
+
node {{AGENT_PATH}}/scripts/inspect.mjs --json
|
|
134
134
|
# Score: 92 (+7)
|
|
135
135
|
# Violaciones: 0
|
|
136
136
|
```
|
|
@@ -264,7 +264,7 @@ forge reforge --publish shared/contracts as @company/contracts
|
|
|
264
264
|
# 1. Extrae contracts/ a packages/contracts/
|
|
265
265
|
# 2. Configura build con tsc
|
|
266
266
|
# 3. Actualiza imports en todas las features
|
|
267
|
-
# 4. Verifica con detect.mjs que los nuevos imports son válidos
|
|
267
|
+
# 4. Verifica con {{AGENT_PATH}}/scripts/detect.mjs que los nuevos imports son válidos
|
|
268
268
|
```
|
|
269
269
|
|
|
270
270
|
---
|
|
@@ -11,17 +11,17 @@ Inicializa un proyecto para trabajar con Forge como Backend Architecture Operati
|
|
|
11
11
|
|
|
12
12
|
## Flujo
|
|
13
13
|
|
|
14
|
-
1. Ejecutar `scripts/context.mjs` — detectar stack actual (incluye platform, features, shared, infra)
|
|
15
|
-
2. Ejecutar `scripts/bootstrap.mjs` — crear layers faltantes (platform, shared, infra)
|
|
14
|
+
1. Ejecutar `{{AGENT_PATH}}/scripts/context.mjs` — detectar stack actual (incluye platform, features, shared, infra)
|
|
15
|
+
2. Ejecutar `{{AGENT_PATH}}/scripts/bootstrap.mjs` — crear layers faltantes (platform, shared, infra)
|
|
16
16
|
3. Crear `src/features/` — directorio de features si no existe
|
|
17
|
-
4. Ejecutar `forge-config.mjs --init` — crear `.forge/config.json` + `.forge/state.json`
|
|
18
|
-
5. Ejecutar `forge-config.mjs --update` — detectar y persistir perfil tecnológico
|
|
17
|
+
4. Ejecutar `{{AGENT_PATH}}/scripts/forge-config.mjs --init` — crear `.forge/config.json` + `.forge/state.json`
|
|
18
|
+
5. Ejecutar `{{AGENT_PATH}}/scripts/forge-config.mjs --update` — detectar y persistir perfil tecnológico
|
|
19
19
|
6. Verificar `tsconfig.json` — agregar `experimentalDecorators` y `emitDecoratorMetadata` si falta
|
|
20
|
-
7. Ejecutar `scripts/armorer.mjs` — detectar ownership y huérfanos
|
|
21
|
-
8. Ejecutar `scripts/graph.mjs` — construir grafo arquitectónico global
|
|
22
|
-
9. Ejecutar `scripts/chain.mjs` — analizar dependencias multi-capa
|
|
23
|
-
10. Ejecutar `detect.mjs --summary` — auditoría base
|
|
24
|
-
11. Ejecutar `architecture.mjs` — generar `ARCHITECTURE.md`
|
|
20
|
+
7. Ejecutar `{{AGENT_PATH}}/scripts/armorer.mjs` — detectar ownership y huérfanos
|
|
21
|
+
8. Ejecutar `{{AGENT_PATH}}/scripts/graph.mjs` — construir grafo arquitectónico global
|
|
22
|
+
9. Ejecutar `{{AGENT_PATH}}/scripts/chain.mjs` — analizar dependencias multi-capa
|
|
23
|
+
10. Ejecutar `{{AGENT_PATH}}/scripts/detect.mjs --summary` — auditoría base
|
|
24
|
+
11. Ejecutar `{{AGENT_PATH}}/scripts/architecture.mjs` — generar `ARCHITECTURE.md`
|
|
25
25
|
12. Si faltan dependencias clave (según perfil), listarlas
|
|
26
26
|
13. Si el proyecto tiene código legacy, sugerir `forge relocate`
|
|
27
27
|
14. Si el proyecto está listo, sugerir `forge cast`
|
|
@@ -33,7 +33,7 @@ forge hook uninstall
|
|
|
33
33
|
|
|
34
34
|
## Comportamiento
|
|
35
35
|
|
|
36
|
-
El hook ejecuta `detect.mjs` sobre los archivos staged con extensión
|
|
36
|
+
El hook ejecuta `{{AGENT_PATH}}/scripts/detect.mjs` sobre los archivos staged con extensión
|
|
37
37
|
`.ts`, `.js`, `.mjs`, `.tsx`, `.jsx` dentro de `src/`. Si encuentra
|
|
38
38
|
violaciones de severidad CRITICAL o ERROR que afecten a archivos staged:
|
|
39
39
|
|
|
@@ -104,20 +104,20 @@ Genera y mantiene el archivo `ARCHITECTURE.md` del proyecto.
|
|
|
104
104
|
|
|
105
105
|
| Campo | Fuente |
|
|
106
106
|
|---|---|
|
|
107
|
-
| Framework | `scripts/context.mjs` |
|
|
108
|
-
| Database | `scripts/context.mjs` |
|
|
109
|
-
| ORM | `scripts/context.mjs` |
|
|
110
|
-
| DI Strategy | `scripts/context.mjs` |
|
|
111
|
-
| Active Profile | `scripts/profile.mjs` |
|
|
112
|
-
| Platform | `scripts/context.mjs` → platform |
|
|
113
|
-
| Features | `scripts/context.mjs` → features |
|
|
114
|
-
| Shared | `scripts/context.mjs` → shared |
|
|
115
|
-
| Infra | `scripts/context.mjs` → infra |
|
|
116
|
-
| Ownership | `scripts/armorer.mjs` |
|
|
117
|
-
| Architecture Graph | `scripts/graph.mjs` |
|
|
118
|
-
| Risk Score | `scripts/graph.mjs` → stats.riskScore |
|
|
119
|
-
| Violations | `scripts/graph.mjs` → violations |
|
|
120
|
-
| Last Audit | `scripts/inspect.mjs` → fecha + score |
|
|
107
|
+
| Framework | `{{AGENT_PATH}}/scripts/context.mjs` |
|
|
108
|
+
| Database | `{{AGENT_PATH}}/scripts/context.mjs` |
|
|
109
|
+
| ORM | `{{AGENT_PATH}}/scripts/context.mjs` |
|
|
110
|
+
| DI Strategy | `{{AGENT_PATH}}/scripts/context.mjs` |
|
|
111
|
+
| Active Profile | `{{AGENT_PATH}}/scripts/profile.mjs` |
|
|
112
|
+
| Platform | `{{AGENT_PATH}}/scripts/context.mjs` → platform |
|
|
113
|
+
| Features | `{{AGENT_PATH}}/scripts/context.mjs` → features |
|
|
114
|
+
| Shared | `{{AGENT_PATH}}/scripts/context.mjs` → shared |
|
|
115
|
+
| Infra | `{{AGENT_PATH}}/scripts/context.mjs` → infra |
|
|
116
|
+
| Ownership | `{{AGENT_PATH}}/scripts/armorer.mjs` |
|
|
117
|
+
| Architecture Graph | `{{AGENT_PATH}}/scripts/graph.mjs` |
|
|
118
|
+
| Risk Score | `{{AGENT_PATH}}/scripts/graph.mjs` → stats.riskScore |
|
|
119
|
+
| Violations | `{{AGENT_PATH}}/scripts/graph.mjs` → violations |
|
|
120
|
+
| Last Audit | `{{AGENT_PATH}}/scripts/inspect.mjs` → fecha + score |
|
|
121
121
|
|
|
122
122
|
## Reglas
|
|
123
123
|
|
|
@@ -11,10 +11,10 @@ Inspecciona la conformidad arquitectónica del proyecto.
|
|
|
11
11
|
|
|
12
12
|
## Flujo
|
|
13
13
|
|
|
14
|
-
1. Ejecutar `scripts/context.mjs` → detectar stack y estructura
|
|
15
|
-
2. Ejecutar `scripts/profile.mjs` → determinar perfil activo
|
|
16
|
-
3. Ejecutar `scripts/chain.mjs` → construir cadena de dependencias
|
|
17
|
-
4. Ejecutar `scripts/detect.mjs` → detectar todas las violaciones
|
|
14
|
+
1. Ejecutar `{{AGENT_PATH}}/scripts/context.mjs` → detectar stack y estructura
|
|
15
|
+
2. Ejecutar `{{AGENT_PATH}}/scripts/profile.mjs` → determinar perfil activo
|
|
16
|
+
3. Ejecutar `{{AGENT_PATH}}/scripts/chain.mjs` → construir cadena de dependencias
|
|
17
|
+
4. Ejecutar `{{AGENT_PATH}}/scripts/detect.mjs` → detectar todas las violaciones
|
|
18
18
|
5. Construir reporte con puntuación y severidades
|
|
19
19
|
6. Mostrar resultado al usuario
|
|
20
20
|
|
|
@@ -200,7 +200,7 @@ import { Task } from "../../../../domain/entities/Task.js";
|
|
|
200
200
|
- Verificar existencia ANTES de generar el import
|
|
201
201
|
|
|
202
202
|
### 5. Controllers y DI
|
|
203
|
-
- Controllers importan desde `./di.js` (feature
|
|
203
|
+
- Controllers importan desde `./di.js` (feature di.ts)
|
|
204
204
|
- Prohibido importar desde `bootstrap.di.js` — ese archivo no existe en la arquitectura actual
|
|
205
205
|
|
|
206
206
|
### 6. Tests
|
|
@@ -33,7 +33,7 @@ Valida que el proyecto cumpla las reglas arquitectónicas.
|
|
|
33
33
|
| R5 | Ciclo de dependencias | ERROR | Ciclo detectado en el grafo de features |
|
|
34
34
|
| R6 | infra → domain/feature | WARNING | Infraestructura no debe importar dominio interno |
|
|
35
35
|
|
|
36
|
-
El grafo se construye automáticamente con `scripts/graph.mjs` y las violaciones se incluyen en `scripts/detect.mjs` (categoría `graph`).
|
|
36
|
+
El grafo se construye automáticamente con `{{AGENT_PATH}}/scripts/graph.mjs` y las violaciones se incluyen en `{{AGENT_PATH}}/scripts/detect.mjs` (categoría `graph`).
|
|
37
37
|
|
|
38
38
|
## Checklist pre-migración
|
|
39
39
|
|
|
@@ -1128,7 +1128,7 @@ export function checkImportConventions(features) {
|
|
|
1128
1128
|
label: `[R12] Import desde bootstrap.di.js — no existe en arquitectura actual`,
|
|
1129
1129
|
pass: false,
|
|
1130
1130
|
detail: `${relative(ROOT, f)}:${imp.line} → "${src}"`,
|
|
1131
|
-
fix: `Reemplazar por "./di.js" (feature
|
|
1131
|
+
fix: `Reemplazar por "./di.js" (feature di.ts — fuente única de registro)`,
|
|
1132
1132
|
});
|
|
1133
1133
|
score -= 3;
|
|
1134
1134
|
}
|
|
@@ -119,7 +119,7 @@ function checkProposedContentViolations(filePath, content) {
|
|
|
119
119
|
severity: "CRITICAL",
|
|
120
120
|
label: `[R12] Import a bootstrap.di.js — no existe en esta arquitectura`,
|
|
121
121
|
detail: `${filePath}:${lineNum} → "${src}"`,
|
|
122
|
-
fix: 'Usar "./di.js"
|
|
122
|
+
fix: 'Usar "./di.js" (feature di.ts — fuente única de registro)',
|
|
123
123
|
});
|
|
124
124
|
}
|
|
125
125
|
}
|
|
@@ -74,13 +74,13 @@ En esencia:
|
|
|
74
74
|
|
|
75
75
|
ANTES de cualquier acción, Forge DEBE ejecutar esta secuencia. Si no lo haces, puedes dar respuestas incorrectas:
|
|
76
76
|
|
|
77
|
-
1.
|
|
78
|
-
2.
|
|
79
|
-
3.
|
|
80
|
-
4.
|
|
81
|
-
5.
|
|
82
|
-
6.
|
|
83
|
-
7.
|
|
77
|
+
1. **`{{AGENT_PATH}}/scripts/context.mjs`** — Detectar stack, platform, features, shared, infra, grafo, estado
|
|
78
|
+
2. **`{{AGENT_PATH}}/scripts/armorer.mjs`** — Detectar ownership, huérfanos, duplicados, mal ubicados
|
|
79
|
+
3. **`{{AGENT_PATH}}/scripts/profile.mjs`** — Determinar perfil tecnológico
|
|
80
|
+
4. **`{{AGENT_PATH}}/scripts/graph.mjs`** — Construir grafo arquitectónico global (4 capas + 9 reglas)
|
|
81
|
+
5. **`{{AGENT_PATH}}/scripts/chain.mjs`** — Analizar dependencias multi-capa
|
|
82
|
+
6. **`{{AGENT_PATH}}/scripts/inspect.mjs`** — Auditoría completa con ownership + platform
|
|
83
|
+
7. **`{{AGENT_PATH}}/scripts/architecture.mjs`** — Generar/actualizar ARCHITECTURE.md
|
|
84
84
|
8. **Ejecutar comando solicitado** — cast, quench, temper, etc.
|
|
85
85
|
9. **forgeSentinel** — Verificar cambios tras escritura
|
|
86
86
|
10. **Actualizar ARCHITECTURE.md** — Reflejar nuevo estado
|
|
@@ -109,17 +109,17 @@ inspect=$(node {{AGENT_PATH}}/scripts/inspect.mjs --json 2>/dev/null)
|
|
|
109
109
|
| "refactorizar", "rediseñar", "cambiar estructura" | `reforge` | `reference/reforge.md` |
|
|
110
110
|
| "verificar", "quench", "checklist" | `quench` | `reference/quench.md` |
|
|
111
111
|
| "templar", "endurecer", "mejorar" | `temper` | `reference/temper.md` |
|
|
112
|
-
| "cadena", "grafo", "acoplamiento" | `chain` | `scripts/chain.mjs` |
|
|
112
|
+
| "cadena", "grafo", "acoplamiento" | `chain` | `{{AGENT_PATH}}/scripts/chain.mjs` |
|
|
113
113
|
| "inscribir", "grabar", "ARCHITECTURE.md" | `inscribe` | `reference/inscribe.md` |
|
|
114
|
-
| "grafo", "graph", "nodo", "violaciones", "risk score" | `graph` | `scripts/graph.mjs` |
|
|
114
|
+
| "grafo", "graph", "nodo", "violaciones", "risk score" | `graph` | `{{AGENT_PATH}}/scripts/graph.mjs` |
|
|
115
115
|
| "fundir", "compartir", "mover a shared" | `smelt` | `reference/smelt.md` |
|
|
116
116
|
| "ownership", "huérfanos", "armorer" | `inspect` | (incluido en auditoría) |
|
|
117
|
-
| "fijar", "pinar", "atajo", "shortcut" | `nail` | `scripts/pin.mjs` |
|
|
118
|
-
| "desfijar", "despinar", "remover atajo" | `unnail` | `scripts/pin.mjs` |
|
|
117
|
+
| "fijar", "pinar", "atajo", "shortcut" | `nail` | `{{AGENT_PATH}}/scripts/pin.mjs` |
|
|
118
|
+
| "desfijar", "despinar", "remover atajo" | `unnail` | `{{AGENT_PATH}}/scripts/pin.mjs` |
|
|
119
119
|
| "hook", "pre-commit", "githook", "validar commit" | `forge hook` | `reference/hooks.md` |
|
|
120
|
-
| "api", "contrato", "openapi", "swagger", "graphql" | `forge api` | `scripts/forge-api.mjs` |
|
|
121
|
-
| "rollback", "restaurar", "deshacer", "backup" | `forge rollback` | `scripts/rollback.mjs` |
|
|
122
|
-
| "estado", "state", "último audit" | `forge state --show` | `scripts/forge-state.mjs` |
|
|
120
|
+
| "api", "contrato", "openapi", "swagger", "graphql" | `forge api` | `{{AGENT_PATH}}/scripts/forge-api.mjs` |
|
|
121
|
+
| "rollback", "restaurar", "deshacer", "backup" | `forge rollback` | `{{AGENT_PATH}}/scripts/rollback.mjs` |
|
|
122
|
+
| "estado", "state", "último audit" | `forge state --show` | `{{AGENT_PATH}}/scripts/forge-state.mjs` |
|
|
123
123
|
| "examinar","calidad", "assay", "opinión", "personas", "critique", "evaluación cualitativa" | `assay` | `reference/assay.md` |
|
|
124
124
|
|
|
125
125
|
---
|
|
@@ -128,14 +128,14 @@ inspect=$(node {{AGENT_PATH}}/scripts/inspect.mjs --json 2>/dev/null)
|
|
|
128
128
|
|
|
129
129
|
Para cada comando, Forge sigue este flujo:
|
|
130
130
|
|
|
131
|
-
1. **Contexto**: Ejecutar `context.mjs` + `armorer.mjs` + `profile.mjs`
|
|
132
|
-
2. **Grafo**: Ejecutar `graph.mjs` + `chain.mjs`
|
|
133
|
-
3. **Auditoría**: Ejecutar `inspect.mjs`
|
|
131
|
+
1. **Contexto**: Ejecutar `{{AGENT_PATH}}/scripts/context.mjs` + `{{AGENT_PATH}}/scripts/armorer.mjs` + `{{AGENT_PATH}}/scripts/profile.mjs`
|
|
132
|
+
2. **Grafo**: Ejecutar `{{AGENT_PATH}}/scripts/graph.mjs` + `{{AGENT_PATH}}/scripts/chain.mjs`
|
|
133
|
+
3. **Auditoría**: Ejecutar `{{AGENT_PATH}}/scripts/inspect.mjs`
|
|
134
134
|
4. **Referencia**: Cargar `reference/<command>.md`
|
|
135
135
|
5. **Ejecutar**: Aplicar el flujo definido en la referencia, usando los scripts según corresponda
|
|
136
|
-
6. **Verificar**: Ejecutar `scripts/detect.mjs` para verificar que no se introdujeron violaciones
|
|
137
|
-
7. **forgeSentinel**: Ejecutar `scripts/forgeSentinel.mjs --reminder`
|
|
138
|
-
8. **Actualizar ARCHITECTURE.md**: Reflejar el nuevo estado (`architecture.mjs`)
|
|
136
|
+
6. **Verificar**: Ejecutar `{{AGENT_PATH}}/scripts/detect.mjs` para verificar que no se introdujeron violaciones
|
|
137
|
+
7. **forgeSentinel**: Ejecutar `{{AGENT_PATH}}/scripts/forgeSentinel.mjs --reminder`
|
|
138
|
+
8. **Actualizar ARCHITECTURE.md**: Reflejar el nuevo estado (`{{AGENT_PATH}}/scripts/architecture.mjs`)
|
|
139
139
|
9. **Reportar**: Mostrar resultado al usuario con severidades
|
|
140
140
|
|
|
141
141
|
---
|
|
@@ -237,14 +237,14 @@ El agente DEBE leer este archivo al inicio de cada interacción y actualizarlo a
|
|
|
237
237
|
| `reference/help.md` | Lista completa de comandos y flags de Forge |
|
|
238
238
|
| `reference/assay.md` | Ensayo arquitectónico multi-persona — interpretación cualitativa del audit |
|
|
239
239
|
| `profiles/` | Perfiles tecnológicos detallados (Express, Fastify, NestJS, etc.) |
|
|
240
|
-
| `scripts/` | Scripts: context, detect, inspect, chain, profile, graph, architecture, armorer, bootstrap, forge-config, forge-signals, forge-state, forge-api, pin, update, rollback, hook, forgeSentinel, forgeSmith, forgeSentinel-lib, forgeSmith-admin, formatter, assay, registry/rules |
|
|
241
|
-
| `scripts/registry/rules.mjs` | Anti-pattern rule registry (R1-R9 + custom rules desacopladas de detect.mjs) |
|
|
242
|
-
| `scripts/formatter.mjs` | Output formatter unificado (JSON, tabla, severidad coloreada, scoreBar, formatCheck, formatViolation) |
|
|
243
|
-
| `scripts/forgeSentinel.mjs` | PostToolUse hook — analiza archivos modificados tras escritura y reporta violaciones |
|
|
244
|
-
| `scripts/forgeSentinel-lib.mjs` | Lógica compartida para hooks forgeSentinel y forgeSmith |
|
|
245
|
-
| `scripts/forgeSmith.mjs` | preToolUse gate — previene escrituras con violaciones CRITICAL/ERROR (Cursor) |
|
|
246
|
-
| `scripts/forgeSmith-admin.mjs` | Gestión de hooks (on/off/status) |
|
|
247
|
-
| `scripts/assay.mjs` | Motor de ensayo arquitectónico multi-persona (Bezos, Fowler, Hacker, PM, Arquitecta Senior) |
|
|
240
|
+
| `{{AGENT_PATH}}/scripts/` | Scripts: context, detect, inspect, chain, profile, graph, architecture, armorer, bootstrap, forge-config, forge-signals, forge-state, forge-api, pin, update, rollback, hook, forgeSentinel, forgeSmith, forgeSentinel-lib, forgeSmith-admin, formatter, assay, registry/rules |
|
|
241
|
+
| `{{AGENT_PATH}}/scripts/registry/rules.mjs` | Anti-pattern rule registry (R1-R9 + custom rules desacopladas de detect.mjs) |
|
|
242
|
+
| `{{AGENT_PATH}}/scripts/formatter.mjs` | Output formatter unificado (JSON, tabla, severidad coloreada, scoreBar, formatCheck, formatViolation) |
|
|
243
|
+
| `{{AGENT_PATH}}/scripts/forgeSentinel.mjs` | PostToolUse hook — analiza archivos modificados tras escritura y reporta violaciones |
|
|
244
|
+
| `{{AGENT_PATH}}/scripts/forgeSentinel-lib.mjs` | Lógica compartida para hooks forgeSentinel y forgeSmith |
|
|
245
|
+
| `{{AGENT_PATH}}/scripts/forgeSmith.mjs` | preToolUse gate — previene escrituras con violaciones CRITICAL/ERROR (Cursor) |
|
|
246
|
+
| `{{AGENT_PATH}}/scripts/forgeSmith-admin.mjs` | Gestión de hooks (on/off/status) |
|
|
247
|
+
| `{{AGENT_PATH}}/scripts/assay.mjs` | Motor de ensayo arquitectónico multi-persona (Bezos, Fowler, Hacker, PM, Arquitecta Senior) |
|
|
248
248
|
| `templates/feature/` | Templates de feature (entity, repository, uc, controller, routes, schema, mapper) |
|
|
249
249
|
| `templates/platform/` | Templates de platform (config, server, database, logger, http, di) |
|
|
250
250
|
| `templates/shared/` | Templates de shared (errors, contracts, types, utils) |
|
|
@@ -261,16 +261,16 @@ node --test {{AGENT_PATH}}/tests/core.test.mjs
|
|
|
261
261
|
|
|
262
262
|
| Módulo | Tests | Descripción |
|
|
263
263
|
|--------|-------|-------------|
|
|
264
|
-
| `profile.mjs` | 8 | Detección de perfiles |
|
|
265
|
-
| `graph.mjs` | 1 | Grafo vacío |
|
|
266
|
-
| `armorer.mjs` | 1 | Ownership vacío |
|
|
267
|
-
| `forge-config.mjs` | 2 | Load/save state |
|
|
268
|
-
| `chain.mjs` | 1 | Grafo de dependencias vacío |
|
|
269
|
-
| `formatter.mjs` | 4 | Output format, colores, JSON |
|
|
270
|
-
| `registry/rules.mjs` | 4 | R1-R9, evaluación, custom rules |
|
|
271
|
-
| `detect.mjs` (inline ignores) | 5 | parseInlineIgnores, isIgnored |
|
|
272
|
-
| `forgeSentinel.mjs` | 1 | PostToolUse hook |
|
|
273
|
-
| `assay.mjs` | 4 | Personas, generateAssay, opiniones |
|
|
264
|
+
| `{{AGENT_PATH}}/scripts/profile.mjs` | 8 | Detección de perfiles |
|
|
265
|
+
| `{{AGENT_PATH}}/scripts/graph.mjs` | 1 | Grafo vacío |
|
|
266
|
+
| `{{AGENT_PATH}}/scripts/armorer.mjs` | 1 | Ownership vacío |
|
|
267
|
+
| `{{AGENT_PATH}}/scripts/forge-config.mjs` | 2 | Load/save state |
|
|
268
|
+
| `{{AGENT_PATH}}/scripts/chain.mjs` | 1 | Grafo de dependencias vacío |
|
|
269
|
+
| `{{AGENT_PATH}}/scripts/formatter.mjs` | 4 | Output format, colores, JSON |
|
|
270
|
+
| `{{AGENT_PATH}}/scripts/registry/rules.mjs` | 4 | R1-R9, evaluación, custom rules |
|
|
271
|
+
| `{{AGENT_PATH}}/scripts/detect.mjs` (inline ignores) | 5 | parseInlineIgnores, isIgnored |
|
|
272
|
+
| `{{AGENT_PATH}}/scripts/forgeSentinel.mjs` | 1 | PostToolUse hook |
|
|
273
|
+
| `{{AGENT_PATH}}/scripts/assay.mjs` | 4 | Personas, generateAssay, opiniones |
|
|
274
274
|
|
|
275
275
|
### Flags adicionales
|
|
276
276
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: forge
|
|
3
|
+
description: >
|
|
4
|
+
Architecture Operating System especializado en diseñar, construir, auditar,
|
|
5
|
+
proteger y evolucionar arquitecturas backend escalables basadas en features
|
|
6
|
+
(vertical slices), hexagonal architecture y DDD pragmático.
|
|
7
|
+
agent: build
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Forge — Backend Architecture Operating System
|
|
11
|
+
|
|
12
|
+
Los comandos `/forge-*` están en `{{AGENT_PATH}}/../commands/`.
|
|
13
|
+
La documentación completa está en `{{AGENT_PATH}}/SKILL.md`.
|
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
// 1. Nombres de métodos: usar createHandler (no "add", "store", etc.)
|
|
6
6
|
// 2. Si la entidad <Domain> es compartida desde platform/domain/,
|
|
7
7
|
// importar con path alias: import type { <Domain> } from "@/domain/entities/<Domain>.js";
|
|
8
|
-
// 3.
|
|
9
|
-
// @/setting/dependencies/<domain>.di.js en vez de bootstrap.di.js
|
|
8
|
+
// 3. Los use cases se resuelven via @inject() — el di.ts del feature registra las implementaciones
|
|
10
9
|
|
|
11
10
|
import { injectable, inject } from "tsyringe";
|
|
12
11
|
import type { Request, Response, NextFunction } from "express";
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
```typescript
|
|
2
2
|
// src/features/<domain>/di.ts
|
|
3
|
-
//
|
|
3
|
+
// ── FUENTE ÚNICA de registro DI para el feature <Domain> ──
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
5
|
+
// Este archivo es la ÚNICA lugar donde se registran las dependencias de este feature.
|
|
6
|
+
// app.ts importa este archivo para que el contenedor conozca las implementaciones.
|
|
7
|
+
// NUNCA registrar las mismas dependencias en app.ts directamente.
|
|
7
8
|
//
|
|
8
|
-
//
|
|
9
|
+
// Reglas:
|
|
10
|
+
// - Si el repositorio exporta un modelo Mongoose (model()), usar container.register() con useValue.
|
|
11
|
+
// - Para entidades compartidas desde platform/domain/, usar path alias @/domain/.
|
|
12
|
+
// - No registrar dependencias de otros features aquí.
|
|
9
13
|
|
|
10
14
|
import { container } from "tsyringe";
|
|
11
15
|
import type { I<Domain>Repository } from "./domain/repositories/I<Domain>.repository.js";
|
package/src/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { copyFileSync, mkdirSync, existsSync, writeFileSync, readFileSync, cpSync, readdirSync, statSync } from "fs";
|
|
3
|
+
import { copyFileSync, mkdirSync, existsSync, writeFileSync, readFileSync, cpSync, readdirSync, statSync, rmSync } from "fs";
|
|
4
4
|
import { join, dirname, relative } from "path";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
import { execSync } from "child_process";
|
|
@@ -43,6 +43,17 @@ function copyRecursive(src, dest) {
|
|
|
43
43
|
cpSync(src, dest, { recursive: true, force: true });
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
function cleanAgentTemplates(skillDest, keepAgent) {
|
|
47
|
+
const agentsDir = join(skillDest, "templates", "agents");
|
|
48
|
+
if (!existsSync(agentsDir)) return;
|
|
49
|
+
for (const entry of readdirSync(agentsDir)) {
|
|
50
|
+
const entryPath = join(agentsDir, entry);
|
|
51
|
+
if (statSync(entryPath).isDirectory() && entry !== keepAgent) {
|
|
52
|
+
rmSync(entryPath, { recursive: true, force: true });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
46
57
|
function renderSkillPaths(skillDest, skillPath) {
|
|
47
58
|
for (const entry of readdirSync(skillDest)) {
|
|
48
59
|
const fullPath = join(skillDest, entry);
|
|
@@ -161,6 +172,7 @@ async function installOpenCode(isGlobal = false) {
|
|
|
161
172
|
|
|
162
173
|
s.start("Copiando skill a " + rel);
|
|
163
174
|
copyRecursive(SKILL_SRC, target);
|
|
175
|
+
cleanAgentTemplates(target, "opencode");
|
|
164
176
|
renderSkillPaths(target, skillPath);
|
|
165
177
|
s.stop("Skill copiada a " + rel);
|
|
166
178
|
|
|
@@ -193,6 +205,7 @@ async function installAgentTemplates(agentDir, agentName) {
|
|
|
193
205
|
// Copy skill into <agentDir>/skills/forge/
|
|
194
206
|
const skillDest = join(agentDir, "skills", "forge");
|
|
195
207
|
cpSync(SKILL_SRC, skillDest, { recursive: true, force: true });
|
|
208
|
+
cleanAgentTemplates(skillDest, config.template);
|
|
196
209
|
|
|
197
210
|
// Copy template files (hooks, CLAUDE.md, .cursorrules, etc.)
|
|
198
211
|
for (const entry of readdirSync(templateDir)) {
|
package/src/wizard.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import pc from "picocolors";
|
|
2
2
|
import { intro, outro, select, multiselect, text, spinner, isCancel, cancel, log, tasks } from "@clack/prompts";
|
|
3
|
-
import { existsSync, mkdirSync, copyFileSync, readdirSync, cpSync, writeFileSync, readFileSync } from "fs";
|
|
3
|
+
import { existsSync, mkdirSync, copyFileSync, readdirSync, cpSync, writeFileSync, readFileSync, statSync, rmSync } from "fs";
|
|
4
4
|
import { join, dirname } from "path";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
import { execSync } from "child_process";
|
|
@@ -93,12 +93,38 @@ function copyAgentTemplate(name, dest) {
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
function renderSkillPaths(skillDest, skillPath) {
|
|
97
|
+
for (const entry of readdirSync(skillDest)) {
|
|
98
|
+
const fullPath = join(skillDest, entry);
|
|
99
|
+
if (statSync(fullPath).isDirectory()) {
|
|
100
|
+
renderSkillPaths(fullPath, skillPath);
|
|
101
|
+
} else if (entry.endsWith(".md")) {
|
|
102
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
103
|
+
if (content.includes("{{AGENT_PATH}}")) {
|
|
104
|
+
writeFileSync(fullPath, content.replace(/\{\{AGENT_PATH\}\}/g, skillPath), "utf-8");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function cleanAgentTemplates(skillDest, keepAgent) {
|
|
111
|
+
const agentsDir = join(skillDest, "templates", "agents");
|
|
112
|
+
if (!existsSync(agentsDir)) return;
|
|
113
|
+
for (const entry of readdirSync(agentsDir)) {
|
|
114
|
+
const entryPath = join(agentsDir, entry);
|
|
115
|
+
if (statSync(entryPath).isDirectory() && entry !== keepAgent) {
|
|
116
|
+
rmSync(entryPath, { recursive: true, force: true });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
96
121
|
const AGENT_SKILL_PATHS = {
|
|
97
122
|
claude: ".claude/skills/forge",
|
|
98
123
|
cursor: ".cursor/skills/forge",
|
|
99
124
|
codex: ".agents/skills/forge",
|
|
100
125
|
gemini: ".gemini/skills/forge",
|
|
101
126
|
agents: ".agents/skills/forge",
|
|
127
|
+
opencode: ".opencode/skills/forge",
|
|
102
128
|
};
|
|
103
129
|
|
|
104
130
|
function installAgentTemplates(agentDir, agentName) {
|
|
@@ -110,18 +136,23 @@ function installAgentTemplates(agentDir, agentName) {
|
|
|
110
136
|
// Copy skill into <agentDir>/skills/forge/
|
|
111
137
|
const skillDest = join(agentDir, "skills", "forge");
|
|
112
138
|
cpSync(SKILL_SRC, skillDest, { recursive: true, force: true });
|
|
139
|
+
cleanAgentTemplates(skillDest, agentName);
|
|
113
140
|
// Copy template files (hooks, CLAUDE.md, .cursorrules, etc.)
|
|
114
141
|
copyAgentTemplate(agentName, agentDir);
|
|
115
142
|
|
|
143
|
+
const skillPath = AGENT_SKILL_PATHS[agentName] || agentName;
|
|
144
|
+
|
|
116
145
|
// Render SKILL.md with agent-specific path
|
|
117
146
|
const templatePath = join(AGENTS_TEMPLATES, "SKILL.md.template");
|
|
118
147
|
if (existsSync(templatePath)) {
|
|
119
148
|
const template = readFileSync(templatePath, "utf-8");
|
|
120
|
-
const skillPath = AGENT_SKILL_PATHS[agentName] || agentName;
|
|
121
149
|
const rendered = template.replace(/\{\{AGENT_PATH\}\}/g, skillPath);
|
|
122
150
|
writeFileSync(join(skillDest, "SKILL.md"), rendered, "utf-8");
|
|
123
151
|
}
|
|
124
152
|
|
|
153
|
+
// Render {{AGENT_PATH}} in remaining .md files (command/forge.md, reference/*.md)
|
|
154
|
+
renderSkillPaths(skillDest, skillPath);
|
|
155
|
+
|
|
125
156
|
// Codex special case: also copy hooks.json to .codex/
|
|
126
157
|
if (agentName === "codex") {
|
|
127
158
|
const codexDir = join(process.cwd(), ".codex");
|
|
@@ -351,10 +382,13 @@ function buildAgentSteps(agent, cwd) {
|
|
|
351
382
|
if (agent.id === "opencode-global") {
|
|
352
383
|
const dest = join(HOME, ".config", "opencode", "skills", "forge");
|
|
353
384
|
const configDir = join(HOME, ".config", "opencode");
|
|
385
|
+
const skillPath = join(".config", "opencode", "skills", "forge");
|
|
354
386
|
const steps = [];
|
|
355
387
|
steps.push(s("Copiando Skill en opencode/", async () => {
|
|
356
388
|
mkdirSync(dest, { recursive: true });
|
|
357
389
|
cpSync(SKILL_SRC, dest, { recursive: true, force: true });
|
|
390
|
+
cleanAgentTemplates(dest, "opencode");
|
|
391
|
+
renderSkillPaths(dest, skillPath);
|
|
358
392
|
return "Skill copiada";
|
|
359
393
|
}));
|
|
360
394
|
steps.push(s("Registrando comandos + dependencias", async () => {
|
|
@@ -372,10 +406,13 @@ function buildAgentSteps(agent, cwd) {
|
|
|
372
406
|
if (agent.id === "opencode-project") {
|
|
373
407
|
const dest = join(cwd, ".opencode", "skills", "forge");
|
|
374
408
|
const configDir = join(cwd, ".opencode");
|
|
409
|
+
const skillPath = join(".opencode", "skills", "forge");
|
|
375
410
|
const steps = [];
|
|
376
411
|
steps.push(s("Copiando Skill en .opencode/", async () => {
|
|
377
412
|
mkdirSync(dest, { recursive: true });
|
|
378
413
|
cpSync(SKILL_SRC, dest, { recursive: true, force: true });
|
|
414
|
+
cleanAgentTemplates(dest, "opencode");
|
|
415
|
+
renderSkillPaths(dest, skillPath);
|
|
379
416
|
return "Skill copiada";
|
|
380
417
|
}));
|
|
381
418
|
steps.push(s("Registrando comandos + dependencias", async () => {
|
|
@@ -440,6 +477,7 @@ async function installPhase(result, cwd) {
|
|
|
440
477
|
steps.push(s("Instalando Forge en ruta personalizada", async () => {
|
|
441
478
|
mkdirSync(dest, { recursive: true });
|
|
442
479
|
cpSync(SKILL_SRC, join(dest, "forge"), { recursive: true, force: true });
|
|
480
|
+
cleanAgentTemplates(join(dest, "forge"), "claude");
|
|
443
481
|
copyAgentTemplate("claude", dest);
|
|
444
482
|
return "Forge instalado";
|
|
445
483
|
}));
|