@ronaldjdevfs/forge 1.3.6 → 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.
Files changed (31) hide show
  1. package/README.md +3 -3
  2. package/package.json +1 -1
  3. package/skills/forge/SKILL.md +24 -23
  4. package/skills/forge/command/forge.md +15 -13
  5. package/skills/forge/profiles/express-mongodb.md +11 -6
  6. package/skills/forge/profiles/express-prisma.md +1 -1
  7. package/skills/forge/reference/adr.md +4 -4
  8. package/skills/forge/reference/architectural-depth-checklist.md +4 -4
  9. package/skills/forge/reference/cast.md +5 -5
  10. package/skills/forge/reference/chain.md +2 -2
  11. package/skills/forge/reference/cohesion-checklist.md +2 -2
  12. package/skills/forge/reference/di-strategies.md +22 -5
  13. package/skills/forge/reference/evolutionary-architecture.md +7 -7
  14. package/skills/forge/reference/forge.md +18 -10
  15. package/skills/forge/reference/hooks.md +1 -1
  16. package/skills/forge/reference/inscribe.md +14 -14
  17. package/skills/forge/reference/inspect.md +4 -4
  18. package/skills/forge/reference/patterns.md +1 -1
  19. package/skills/forge/reference/quench.md +1 -1
  20. package/skills/forge/scripts/detect.mjs +64 -1
  21. package/skills/forge/scripts/forge-boot.mjs +3 -1
  22. package/skills/forge/scripts/forgeSentinel-lib.mjs +1 -1
  23. package/skills/forge/scripts/forgeSmith.mjs +1 -1
  24. package/skills/forge/scripts/inspect.mjs +5 -4
  25. package/skills/forge/scripts/update.mjs +21 -2
  26. package/skills/forge/templates/agents/SKILL.md.template +38 -38
  27. package/skills/forge/templates/agents/opencode/SKILL.md +13 -0
  28. package/skills/forge/templates/feature/controller.ts.md +1 -2
  29. package/skills/forge/templates/feature/di.ts.md +8 -4
  30. package/src/cli.js +14 -1
  31. 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.3.6** — Multi-Agent Path Resolution Fix
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, bootstrap.di.js |
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` (no existe) | CRITICAL |
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ronaldjdevfs/forge",
3
- "version": "1.3.6",
3
+ "version": "1.4.3",
4
4
  "description": "Forge — Architecture Operating System for backend systems. Arquitectura Hexagonal, DDD pragmático y vertical slices.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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 |
@@ -217,6 +217,7 @@ node --test {{AGENT_PATH}}/tests/core.test.mjs
217
217
  | Flag | Comando | Descripción |
218
218
  |------|---------|-------------|
219
219
  | `--fix` | `quench` | Auto-corrige violaciones WARNING/INFO (missing @injectable(), tsconfig, naming, container.resolve) |
220
+ | `--auto` | `quench` | Itera fix → re-detect → fix hasta estabilizar violaciones auto-corregibles |
220
221
  | `--show-ignores` | `quench` | Muestra los inline ignores encontrados en el código |
221
222
  | `--persona=<id>` | `assay` | Filtra ensayo por una persona (bezos, fowler, hacker, pm, senior) |
222
223
  | `--save` | `assay` | Persiste ensayo en `.forge/assay/` |
@@ -9,11 +9,11 @@ Si el subcomando NO tiene flags en $ARGUMENTS y tiene flags disponibles (ver tab
9
9
 
10
10
  | Comando | Flags disponibles |
11
11
  |---------|------------------|
12
- | `forge` | Sin flags |
12
+ | `forge` | `--force` |
13
13
  | `cast` | Sin flags (pide nombre del feature interactivamente) |
14
14
  | `inspect` | `--json`, `--diff`, `--full`, `--summary`, `--severity=<nivel>`, `--force` |
15
15
  | `assay` | `--persona=<id>`, `--json`, `--save`, `history` |
16
- | `quench` | `--fix`, `--show-ignores`, `--severity=<nivel>`, `--json` |
16
+ | `quench` | `--fix`, `--auto`, `--show-ignores`, `--severity=<nivel>`, `--json` |
17
17
  | `chain` | `--json` |
18
18
  | `graph` | `--json` |
19
19
  | `armorer` | Sin flags |
@@ -27,17 +27,19 @@ Si el subcomando NO tiene flags en $ARGUMENTS y tiene flags disponibles (ver tab
27
27
 
28
28
  ### forge
29
29
 
30
- Inicializa el proyecto arquitectónicamente. Ejecuta context + bootstrap + profile + armorer + graph + chain + inscribe.
31
-
32
- ```
33
- node {{AGENT_PATH}}/scripts/context.mjs
34
- node {{AGENT_PATH}}/scripts/bootstrap.mjs
35
- node {{AGENT_PATH}}/scripts/profile.mjs
36
- node {{AGENT_PATH}}/scripts/armorer.mjs
37
- node {{AGENT_PATH}}/scripts/graph.mjs
38
- node {{AGENT_PATH}}/scripts/chain.mjs
39
- node {{AGENT_PATH}}/scripts/architecture.mjs
40
- ```
30
+ Inicializa el proyecto arquitectónicamente con configuración persistente.
31
+
32
+ 1. `node {{AGENT_PATH}}/scripts/context.mjs` — detectar stack actual
33
+ 2. `node {{AGENT_PATH}}/scripts/bootstrap.mjs` — crear platform/, shared/, infra/
34
+ 3. Crear `src/features/` — si no existe
35
+ 4. `node {{AGENT_PATH}}/scripts/forge-config.mjs --init` — crear `.forge/config.json` + `.forge/state.json`
36
+ 5. `node {{AGENT_PATH}}/scripts/forge-config.mjs --update` — detectar y persistir perfil
37
+ 6. Verificar `tsconfig.json` — agregar `experimentalDecorators` y `emitDecoratorMetadata` si falta
38
+ 7. `node {{AGENT_PATH}}/scripts/armorer.mjs` — ownership
39
+ 8. `node {{AGENT_PATH}}/scripts/graph.mjs` — grafo arquitectónico
40
+ 9. `node {{AGENT_PATH}}/scripts/chain.mjs` — dependencias multi-capa
41
+ 10. `node {{AGENT_PATH}}/scripts/detect.mjs --summary` — auditoría base
42
+ 11. `node {{AGENT_PATH}}/scripts/architecture.mjs` — generar `ARCHITECTURE.md`
41
43
 
42
44
  ### cast
43
45
 
@@ -91,20 +91,25 @@ import { createApp } from "./app.js";
91
91
  ```typescript
92
92
  import { container } from "tsyringe";
93
93
 
94
- // Interfaces se registran como singletons
95
- container.registerSingleton<ICreditRepository>(
96
- ICreditRepository as symbol,
97
- CreditRepository
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
- - `container.resolve()` solo en bootstrap (app.ts, routes)
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). Las interfaces se registran como singletons.
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. **Feature SIN DI propia**: si ya existe `src/platform/setting/dependencies/<name>.di.js`, los controllers deben importar desde allí en vez de `bootstrap.di.js`
162
- 3. **Controllers**: asegurar que el import en el controller apunte a `@/setting/dependencies/<name>.di.js` o `./di.js`, NUNCA a `bootstrap.di.js`
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 `di.ts` o `@/setting/dependencies/` — no desde `bootstrap.di.js`
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
- - `reference/detect.mjs` — script que el hook invoca
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/Container.ts
47
+ // src/features/users/di.ts — Registro del feature (fuente única)
36
48
  import { container } from "tsyringe";
37
- import { IUserRepository } from "@/features/users/domain/IUser.repository.js";
38
- import { PostgresUserRepository } from "@/features/users/adapters/out/persistence/PostgresUser.repository.js";
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,20 +11,28 @@ Inicializa un proyecto para trabajar con Forge como Backend Architecture Operati
11
11
 
12
12
  ## Flujo
13
13
 
14
- 1. Ejecutar `scripts/context.mjs` para detectar stack actual (incluye platform, features, shared, infra)
15
- 2. Ejecutar `scripts/bootstrap.mjs` para crear layers faltantes (platform, shared, infra)
16
- 3. Ejecutar `scripts/profile.mjs` para determinar perfil tecnológico
17
- 4. Ejecutar `scripts/armorer.mjs` para detectar ownership y huérfanos
18
- 5. Ejecutar `scripts/graph.mjs` para construir grafo arquitectónico global
19
- 6. Ejecutar `scripts/chain.mjs` para analizar dependencias multi-capa
20
- 7. Si `ARCHITECTURE.md` no existe, crearlo con `forge inscribe`
21
- 8. Si faltan dependencias clave (según perfil), listarlas
22
- 9. Si el proyecto tiene código legacy, sugerir `forge relocate`
23
- 10. Si el proyecto está listo, sugerir `forge cast`
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
+ 3. Crear `src/features/` directorio de features si no existe
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
+ 6. Verificar `tsconfig.json` agregar `experimentalDecorators` y `emitDecoratorMetadata` si falta
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
+ 12. Si faltan dependencias clave (según perfil), listarlas
26
+ 13. Si el proyecto tiene código legacy, sugerir `forge relocate`
27
+ 14. Si el proyecto está listo, sugerir `forge cast`
24
28
 
25
29
  ## Output esperado
26
30
 
27
31
  - `ARCHITECTURE.md` creado en la raíz del proyecto
32
+ - `.forge/config.json` con perfil tecnológico persistido
33
+ - `.forge/state.json` con estado inicial
34
+ - `src/features/` creado si no existía
35
+ - `tsconfig.json` con `experimentalDecorators` y `emitDecoratorMetadata` habilitados
28
36
  - Layers Platform, Shared e Infrastructure creados si no existían
29
37
  - Perfil tecnológico detectado y registrado
30
38
  - Ownership analizado
@@ -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 con DI propia) o `@/setting/dependencies/<name>.di.js` (feature sin DI propia)
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 con DI propia) o "@/setting/dependencies/<feature>.di.js" (feature sin DI propia)`,
1131
+ fix: `Reemplazar por "./di.js" (feature di.ts fuente única de registro)`,
1132
1132
  });
1133
1133
  score -= 3;
1134
1134
  }
@@ -1308,6 +1308,45 @@ export function applyFixes(checks, projectRoot = ROOT) {
1308
1308
  return { fixed, skipped, details };
1309
1309
  }
1310
1310
 
1311
+ /**
1312
+ * autoFixLoop — Iterative auto-fix: fix → re-detect → fix → ... until stable.
1313
+ * Returns { totalFixed, iterations, remaining } where remaining is the filtered
1314
+ * violations list from the last iteration.
1315
+ */
1316
+ async function autoFixLoop(initialFiltered, maxIterations = 10) {
1317
+ let totalFixed = 0;
1318
+ let filtered = initialFiltered;
1319
+ let iteration = 0;
1320
+
1321
+ for (; iteration < maxIterations; iteration++) {
1322
+ const result = applyFixes(filtered);
1323
+ if (result.fixed === 0) break;
1324
+
1325
+ totalFixed += result.fixed;
1326
+
1327
+ // Re-detect after fixes
1328
+ const { buildContext } = await import("./context.mjs");
1329
+ const ctx = await buildContext();
1330
+ const features = detectFeaturesOnSrc();
1331
+ const graph = ctx.graph || getGraph();
1332
+ const results = allChecks(features, graph, ctx);
1333
+
1334
+ let all = [];
1335
+ for (const [, cat] of Object.entries(results)) {
1336
+ all = all.concat(cat.checks);
1337
+ }
1338
+
1339
+ const allIgnores = loadAllInlineIgnores(join(ROOT, "src"));
1340
+ filtered = all.filter(c => {
1341
+ if (c.pass) return true;
1342
+ if (isIgnored(c, allIgnores)) return false;
1343
+ return true;
1344
+ });
1345
+ }
1346
+
1347
+ return { totalFixed, iterations: iteration, remaining: filtered };
1348
+ }
1349
+
1311
1350
  /* ── CLI ── */
1312
1351
  async function main() {
1313
1352
  const args = process.argv.slice(2);
@@ -1315,6 +1354,7 @@ async function main() {
1315
1354
  const filterSeverity = args.includes("--severity") ? args[args.indexOf("--severity") + 1] : null;
1316
1355
  const format = args.includes("--json") ? "json" : "text";
1317
1356
  const doFix = args.includes("--fix");
1357
+ const doAuto = args.includes("--auto");
1318
1358
  const showIgnores = args.includes("--show-ignores");
1319
1359
 
1320
1360
  const { buildContext } = await import("./context.mjs");
@@ -1356,6 +1396,29 @@ async function main() {
1356
1396
  return;
1357
1397
  }
1358
1398
 
1399
+ if (doAuto) {
1400
+ const result = await autoFixLoop(filtered);
1401
+ console.log(`\n${BOLD}Auto-Fix Iterativo${RESET}`);
1402
+ console.log(` ${GREEN}✔${RESET} ${result.totalFixed} violación(es) corregidas en ${result.iterations} iteración(es)`);
1403
+ console.log();
1404
+
1405
+ const remaining = result.remaining.filter(c => !c.pass);
1406
+ if (remaining.length === 0) {
1407
+ console.log(` ${GREEN}✔${RESET} No quedan violaciones.`);
1408
+ return;
1409
+ }
1410
+
1411
+ const critical = remaining.filter(c => c.severity === "CRITICAL").length;
1412
+ const errors = remaining.filter(c => c.severity === "ERROR").length;
1413
+ const warnings = remaining.filter(c => c.severity === "WARNING").length;
1414
+ console.log(` ${YELLOW}⚠${RESET} Quedan ${remaining.length} violación(es) sin corregir (${critical} CRITICAL, ${errors} ERROR, ${warnings} WARNING):\n`);
1415
+ for (const c of remaining) {
1416
+ console.log(formatCheck(c));
1417
+ }
1418
+ console.log();
1419
+ return;
1420
+ }
1421
+
1359
1422
  // Show inline ignores summary
1360
1423
  let totalIgnores = 0;
1361
1424
  for (const [, fileIgnores] of allIgnores) {
@@ -105,4 +105,6 @@ async function main() {
105
105
  console.log(`Boot [${depth}]: ${result.profile.profile} | features: ${ctx.features.total} | graph: ${result.graph.stats.totalNodes}n/${result.graph.stats.totalEdges}e | violations: ${result.inspect.totalChecks} [CRIT:${result.inspect.critical} ERR:${result.inspect.errors} WARN:${result.inspect.warnings}]`);
106
106
  }
107
107
 
108
- main().catch(console.error);
108
+ if (process.argv[1] && (process.argv[1].endsWith("forge-boot.mjs") || process.argv[1].endsWith("forge-boot.js"))) {
109
+ main().catch(console.error);
110
+ }
@@ -10,7 +10,7 @@ import { evaluateRules } from "./registry/rules.mjs";
10
10
  const ROOT = process.cwd();
11
11
 
12
12
  export async function runSentinelCheck(files, opts = {}) {
13
- const { strict = false, reminder = false } = opts;
13
+ const { reminder = false } = opts;
14
14
 
15
15
  if (!files || files.length === 0) {
16
16
  return { violations: [], graphViolations: [], total: 0, hasCritical: false, hasErrors: false, filesChecked: 0, summary: "Sin archivos fuente modificados" };
@@ -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" o "@/setting/dependencies/<feature>.di.js"',
122
+ fix: 'Usar "./di.js" (feature di.ts — fuente única de registro)',
123
123
  });
124
124
  }
125
125
  }
@@ -5,7 +5,7 @@ import { execFileSync } from "child_process";
5
5
  import { buildContext } from "./context.mjs";
6
6
  import { detectProfile, detectProfileExtended } from "./profile.mjs";
7
7
  import { buildDependencyGraph } from "./chain.mjs";
8
- import { allChecks, checkStructure, checkLayers, checkDecorators } from "./detect.mjs";
8
+ import { allChecks, checkStructure, checkLayers, checkDecorators, detectFeaturesOnSrc } from "./detect.mjs";
9
9
  import { saveHistory, updateStateFromAudit } from "./forge-config.mjs";
10
10
  import { buildPipeline, printPipeline } from "./recommendation-engine.mjs";
11
11
 
@@ -217,7 +217,7 @@ function getChangedFeatures(changedFiles, allFeatures) {
217
217
  async function main() {
218
218
  const args = process.argv.slice(2);
219
219
  const isJson = args.includes("--json");
220
- const isDiff = args.includes("--diff") || !args.includes("--full");
220
+ const isDiff = args.includes("--diff");
221
221
  const isSummary = args.includes("--summary");
222
222
  const force = args.includes("--force");
223
223
  const filterSeverity = args.includes("--severity") ? args[args.indexOf("--severity") + 1] : null;
@@ -227,7 +227,7 @@ async function main() {
227
227
  const profile = profileExtended.profile;
228
228
  const archGraph = ctx.graph;
229
229
  const chainGraph = buildDependencyGraph(process.cwd(), archGraph);
230
- const features = ctx.features.migrated;
230
+ const features = detectFeaturesOnSrc();
231
231
 
232
232
  if (isSummary) {
233
233
  const result = allChecks(features, archGraph, ctx);
@@ -267,7 +267,8 @@ async function main() {
267
267
  if (changedFeatures.length === 0) {
268
268
  if (!isJson) {
269
269
  console.log(`\n${YELLOW}⚠ No hay features afectados por los cambios.${RESET}`);
270
- console.log(`${DIM}Los cambios están fuera de src/features/ o no hay features migrados.${RESET}\n`);
270
+ console.log(`${DIM}Los cambios están fuera de src/features/ o no hay features migrados.${RESET}`);
271
+ console.log(`${DIM}Usá 'inspect --full' para un análisis completo de todo el proyecto.${RESET}\n`);
271
272
  } else {
272
273
  console.log(JSON.stringify({ diff: { changedFiles: changedFiles.length, changedFeatures: [], affectedFeatures: false } }));
273
274
  }
@@ -11,13 +11,32 @@
11
11
  */
12
12
 
13
13
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
14
- import { join } from "path";
14
+ import { join, dirname } from "path";
15
+ import { fileURLToPath } from "url";
15
16
 
17
+ const __dirname = dirname(fileURLToPath(import.meta.url));
16
18
  const ROOT = process.cwd();
17
19
  const CACHE_PATH = join(ROOT, ".forge", "version-cache.json");
18
20
  const CACHE_TTL = 86400000; // 24h
19
21
  const VERSION_URL = "https://forge.dev/latest";
20
- const CURRENT_VERSION = "1.3.6";
22
+
23
+ function readVersion() {
24
+ const candidates = [
25
+ join(__dirname, "..", "package.json"),
26
+ join(__dirname, "..", "..", "package.json"),
27
+ join(__dirname, "..", "..", "..", "package.json"),
28
+ join(__dirname, "..", "..", "..", "..", "package.json"),
29
+ join(__dirname, "..", "..", "..", "..", "..", "package.json"),
30
+ ];
31
+ for (const p of candidates) {
32
+ try {
33
+ const pkg = JSON.parse(readFileSync(p, "utf-8"));
34
+ if (pkg.version && pkg.name === "@ronaldjdevfs/forge") return pkg.version;
35
+ } catch {}
36
+ }
37
+ return "0.0.0";
38
+ }
39
+ const CURRENT_VERSION = readVersion();
21
40
 
22
41
  function readJson(path) {
23
42
  try {
@@ -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. **context.mjs** — Detectar stack, platform, features, shared, infra, grafo, estado
78
- 2. **armorer.mjs** — Detectar ownership, huérfanos, duplicados, mal ubicados
79
- 3. **profile.mjs** — Determinar perfil tecnológico
80
- 4. **graph.mjs** — Construir grafo arquitectónico global (4 capas + 9 reglas)
81
- 5. **chain.mjs** — Analizar dependencias multi-capa
82
- 6. **inspect.mjs** — Auditoría completa con ownership + platform
83
- 7. **architecture.mjs** — Generar/actualizar ARCHITECTURE.md
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. Si el feature NO tiene di.ts propio, el controller se importa desde
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
- // DI container wiring para el feature <Domain>.
3
+ // ── FUENTE ÚNICA de registro DI para el feature <Domain> ──
4
4
  //
5
- // NOTA: Si el repositorio exporta un modelo Mongoose (model()) en lugar de una clase,
6
- // usar container.register() con useValue en vez de registerSingleton.
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
- // Para entidades compartidas desde platform/domain/, usar path alias @/domain/ en vez de relativo.
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
  }));