@ronaldjdevfs/forge 1.2.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +36 -21
  2. package/package.json +7 -2
  3. package/skills/forge/SKILL.md +56 -122
  4. package/skills/forge/command/forge.md +59 -14
  5. package/skills/forge/reference/adr.md +242 -0
  6. package/skills/forge/reference/anti-corruption-layer.md +340 -0
  7. package/skills/forge/reference/api-design.md +7 -0
  8. package/skills/forge/reference/api-versioning.md +354 -0
  9. package/skills/forge/reference/architectural-depth-checklist.md +311 -0
  10. package/skills/forge/reference/architecture-template.md +41 -0
  11. package/skills/forge/reference/assay.md +6 -0
  12. package/skills/forge/reference/bounded-contexts.md +311 -0
  13. package/skills/forge/reference/chain.md +6 -0
  14. package/skills/forge/reference/cohesion-checklist.md +256 -0
  15. package/skills/forge/reference/cqrs.md +286 -0
  16. package/skills/forge/reference/data-patterns.md +6 -0
  17. package/skills/forge/reference/di-strategies.md +6 -0
  18. package/skills/forge/reference/errors.md +5 -0
  19. package/skills/forge/reference/events.md +8 -0
  20. package/skills/forge/reference/evolutionary-architecture.md +300 -0
  21. package/skills/forge/reference/forge.md +7 -0
  22. package/skills/forge/reference/hooks.md +6 -0
  23. package/skills/forge/reference/idempotency.md +283 -0
  24. package/skills/forge/reference/inscribe.md +5 -0
  25. package/skills/forge/reference/inspect.md +6 -0
  26. package/skills/forge/reference/modular-monolith.md +252 -0
  27. package/skills/forge/reference/observability.md +5 -0
  28. package/skills/forge/reference/quench.md +5 -0
  29. package/skills/forge/reference/relocate.md +6 -0
  30. package/skills/forge/reference/sagas.md +359 -0
  31. package/skills/forge/reference/security-patterns.md +6 -0
  32. package/skills/forge/reference/smelt.md +6 -0
  33. package/skills/forge/reference/temper.md +6 -0
  34. package/skills/forge/reference/testing-patterns.md +6 -0
  35. package/skills/forge/reference/transactional-outbox.md +311 -0
  36. package/skills/forge/scripts/architecture.mjs +10 -5
  37. package/skills/forge/scripts/assay.mjs +2 -2
  38. package/skills/forge/scripts/chain.mjs +31 -5
  39. package/skills/forge/scripts/context.mjs +24 -4
  40. package/skills/forge/scripts/detect.mjs +39 -30
  41. package/skills/forge/scripts/forge-boot.mjs +108 -0
  42. package/skills/forge/scripts/forge-config.mjs +182 -3
  43. package/skills/forge/scripts/forge-state.mjs +1 -1
  44. package/skills/forge/scripts/forgeSentinel-lib.mjs +86 -0
  45. package/skills/forge/scripts/forgeSentinel.mjs +184 -0
  46. package/skills/forge/scripts/forgeSmith-admin.mjs +104 -0
  47. package/skills/forge/scripts/forgeSmith.mjs +164 -0
  48. package/skills/forge/scripts/graph.mjs +65 -9
  49. package/skills/forge/scripts/hook.mjs +2 -2
  50. package/skills/forge/scripts/inspect.mjs +56 -48
  51. package/skills/forge/scripts/parse-imports.mjs +0 -2
  52. package/skills/forge/scripts/pin.mjs +10 -3
  53. package/skills/forge/scripts/posttool.mjs +2 -2
  54. package/skills/forge/scripts/recommendation-engine.mjs +125 -0
  55. package/skills/forge/scripts/rollback.mjs +5 -3
  56. package/skills/forge/templates/agents/SKILL.md.template +283 -0
  57. package/skills/forge/templates/agents/agents/hooks.json +18 -0
  58. package/skills/forge/templates/agents/claude/CLAUDE.md +17 -16
  59. package/skills/forge/templates/agents/claude/settings.local.json +18 -0
  60. package/skills/forge/templates/agents/codex/hooks.json +18 -0
  61. package/skills/forge/templates/agents/cursor/.cursorrules +22 -5
  62. package/skills/forge/templates/agents/cursor/hooks.json +11 -0
  63. package/skills/forge/templates/agents/gemini/SKILL.md +13 -0
  64. package/skills/forge/templates/feature/acl-gateway.ts.md +52 -0
  65. package/skills/forge/templates/feature/acl-repository.ts.md +36 -0
  66. package/skills/forge/templates/feature/acl-translator.ts.md +24 -0
  67. package/skills/forge/templates/feature/cqrs-query.ts.md +40 -0
  68. package/skills/forge/templates/feature/outbox-repository.ts.md +36 -0
  69. package/skills/forge/templates/feature/saga-orchestrator.ts.md +72 -0
  70. package/skills/forge/templates/platform/outbox-relayer.ts.md +80 -0
  71. package/skills/forge/tests/core.test.mjs +284 -0
  72. package/src/agents.mjs +35 -2
  73. package/src/cli.js +112 -39
  74. package/src/wizard.mjs +142 -90
@@ -0,0 +1,359 @@
1
+ # Sagas — Transacciones Multi-Feature
2
+
3
+ Los procesos de negocio rara vez caben en una sola feature. Una saga coordina múltiples pasos a través de bounded contexts, garantizando consistencia eventual sin ACID distribuido.
4
+
5
+ ---
6
+
7
+ ## El Problema
8
+
9
+ Una transacción como "realizar un pedido" cruza múltiples features:
10
+
11
+ ```
12
+ Orders: crear pedido → Inventory: reservar stock → Payments: cobrar → Shipping: preparar envío
13
+ ```
14
+
15
+ En un monolith con una sola BD, esto sería una transacción ACID. Cuando cada feature tiene su propio schema o BD, no hay transacción distribuida. Las sagas resuelven este problema.
16
+
17
+ ### Una saga no es:
18
+ - Una transacción ACID (no hay rollback, hay compensación)
19
+ - Un workflow BPMN con motor externo (aunque puede parecerse)
20
+ - Un event pipeline unidireccional (tiene lógica de decisión)
21
+
22
+ ### Una saga es:
23
+ - Una secuencia de transacciones locales
24
+ - Cada paso tiene una acción compensatoria si falla
25
+ - La saga completa tiene consistencia eventual garantizada
26
+
27
+ ---
28
+
29
+ ## Coreografía (Choreography)
30
+
31
+ Cada participante publica eventos y reacciona a eventos de otros. No hay coordinador central.
32
+
33
+ ```mermaid
34
+ sequenceDiagram
35
+ participant Orders
36
+ participant Inventory
37
+ participant Payments
38
+ participant Shipping
39
+
40
+ Orders->>Orders: crear pedido
41
+ Orders->>Inventory: OrderPlaced (event)
42
+ Inventory->>Inventory: reservar stock
43
+ alt stock suficiente
44
+ Inventory->>Orders: StockReserved (event)
45
+ Orders->>Payments: cobrar (event)
46
+ Payments->>Payments: procesar pago
47
+ else stock insuficiente
48
+ Inventory->>Orders: StockReservationFailed (event)
49
+ Orders->>Orders: cancelar pedido
50
+ end
51
+ ```
52
+
53
+ ### Implementación coreografía
54
+
55
+ ```ts
56
+ // Cada feature publica y escucha eventos
57
+ // features/orders/adapters/in/events/OrderSaga.ts
58
+ export class OrderSaga {
59
+ constructor(
60
+ private readonly orderRepo: IOrderRepository,
61
+ private readonly eventBus: IEventBus,
62
+ ) {
63
+ // Escucha respuestas de otros features
64
+ this.eventBus.subscribe("StockReserved", this.onStockReserved.bind(this));
65
+ this.eventBus.subscribe("StockReservationFailed", this.onStockFailed.bind(this));
66
+ this.eventBus.subscribe("PaymentProcessed", this.onPaymentProcessed.bind(this));
67
+ this.eventBus.subscribe("PaymentFailed", this.onPaymentFailed.bind(this));
68
+ }
69
+
70
+ async onStockReserved(event: StockReservedEvent): Promise<void> {
71
+ await this.orderRepo.updateStatus(event.orderId, "stock_reserved");
72
+ this.eventBus.publish(new RequestPaymentCommand(event.orderId, event.amount));
73
+ }
74
+
75
+ async onStockFailed(event: StockReservationFailedEvent): Promise<void> {
76
+ await this.orderRepo.updateStatus(event.orderId, "cancelled_no_stock");
77
+ // No hay compensación adicional: el pedido nunca se confirmó
78
+ }
79
+
80
+ async onPaymentProcessed(event: PaymentProcessedEvent): Promise<void> {
81
+ await this.orderRepo.updateStatus(event.orderId, "paid");
82
+ this.eventBus.publish(new RequestShippingCommand(event.orderId));
83
+ }
84
+
85
+ async onPaymentFailed(event: PaymentFailedEvent): Promise<void> {
86
+ await this.orderRepo.updateStatus(event.orderId, "payment_failed");
87
+ // Compensación: liberar stock
88
+ this.eventBus.publish(new ReleaseStockCommand(event.orderId));
89
+ }
90
+ }
91
+ ```
92
+
93
+ ### Pros y contras
94
+
95
+ | Pro | Contra |
96
+ |---|---|
97
+ | Sin punto central de fallo | Lógica de saga distribuida: difícil de entender el flujo completo |
98
+ | Cada feature es autónomo | Seguimiento complejo (¿en qué paso está cada pedido?) |
99
+ | Fácil de añadir nuevos participantes | Riesgo de ciclos de eventos |
100
+ | Escala horizontalmente | Testing de integración más complejo |
101
+
102
+ ---
103
+
104
+ ## Orquestación (Orchestration)
105
+
106
+ Un coordinador central (orchestrator) dirige cada paso y decide el flujo.
107
+
108
+ ```mermaid
109
+ sequenceDiagram
110
+ participant CheckoutSaga as Orchestrator
111
+ participant Orders
112
+ participant Inventory
113
+ participant Payments
114
+ participant Shipping
115
+
116
+ CheckoutSaga->>Orders: crearPedido()
117
+ Orders-->>CheckoutSaga: ok
118
+ CheckoutSaga->>Inventory: reservarStock()
119
+ Inventory-->>CheckoutSaga: ok
120
+ CheckoutSaga->>Payments: procesarPago()
121
+ alt pago exitoso
122
+ Payments-->>CheckoutSaga: ok
123
+ CheckoutSaga->>Shipping: prepararEnvio()
124
+ else pago falla
125
+ Payments-->>CheckoutSaga: failed
126
+ CheckoutSaga->>Inventory: liberarStock() ← compensación
127
+ CheckoutSaga->>Orders: cancelarPedido() ← compensación
128
+ end
129
+ ```
130
+
131
+ ### Implementación orquestación
132
+
133
+ El orchestrator es un feature independiente:
134
+
135
+ ```
136
+ src/features/checkout-saga/
137
+ domain/
138
+ SagaInstance.entity.ts ← estado de cada saga activa
139
+ ISagaStepExecutor.ts ← interfaz para ejecutar pasos
140
+ application/
141
+ use-cases/
142
+ ExecuteCheckoutSaga.ts ← orquesta la saga
143
+ adapters/
144
+ out/
145
+ saga-steps/
146
+ CreateOrderStep.ts ← llama a Orders
147
+ ReserveStockStep.ts ← llama a Inventory
148
+ ProcessPaymentStep.ts ← llama a Payments
149
+ PrepareShippingStep.ts ← llama a Shipping
150
+ CancelOrderStep.ts ← compensación de Orders
151
+ ReleaseStockStep.ts ← compensación de Inventory
152
+ ```
153
+
154
+ ```ts
155
+ // features/checkout-saga/application/use-cases/ExecuteCheckoutSaga.ts
156
+ export class ExecuteCheckoutSaga {
157
+ constructor(
158
+ private readonly sagaRepo: ISagaInstanceRepository,
159
+ private readonly stepFactory: SagaStepFactory,
160
+ ) {}
161
+
162
+ async execute(command: StartCheckoutCommand): Promise<void> {
163
+ const saga = SagaInstance.start("CHECKOUT", {
164
+ orderId: command.orderId,
165
+ customerId: command.customerId,
166
+ items: command.items,
167
+ });
168
+
169
+ const steps = [
170
+ this.stepFactory.createOrderStep(),
171
+ this.stepFactory.reserveStockStep(),
172
+ this.stepFactory.processPaymentStep(),
173
+ this.stepFactory.prepareShippingStep(),
174
+ ];
175
+
176
+ for (const step of steps) {
177
+ try {
178
+ await step.execute(saga.context);
179
+ saga.advance(step.name);
180
+ await this.sagaRepo.save(saga);
181
+ } catch (error) {
182
+ saga.fail(step.name, error.message);
183
+ await this.compensate(saga, steps);
184
+ return;
185
+ }
186
+ }
187
+
188
+ saga.complete();
189
+ await this.sagaRepo.save(saga);
190
+ }
191
+
192
+ private async compensate(saga: SagaInstance, steps: SagaStep[]): Promise<void> {
193
+ // Ejecuta compensaciones en orden inverso
194
+ const executedSteps = steps.slice(0, saga.currentStepIndex);
195
+ for (const step of executedSteps.reverse()) {
196
+ try {
197
+ await step.compensate(saga.context);
198
+ } catch (err) {
199
+ // La compensación falló — requiere intervención manual
200
+ saga.requireManualIntervention();
201
+ }
202
+ }
203
+ }
204
+ }
205
+ ```
206
+
207
+ ### Pros y contras
208
+
209
+ | Pro | Contra |
210
+ |---|---|
211
+ | Flujo completo visible en un lugar | Punto central de fallo |
212
+ | Fácil de testear el orchestrator | El orchestrator conoce todos los participantes |
213
+ | Compensaciones explícitas y centralizadas | Riesgo de orchestrator con lógica de negocio |
214
+ | Trazabilidad: cada saga tiene estado | Más boilerplate que coreografía |
215
+
216
+ ---
217
+
218
+ ## Cuándo Elegir Cada Una
219
+
220
+ | Situación | Recomendación |
221
+ |---|---|
222
+ | 2-3 features, flujo simple | Coreografía |
223
+ | 4+ features, flujo con ramas | Orquestación |
224
+ | Equipos autónomos que no quieren coordinar | Coreografía |
225
+ | Requisito de trazabilidad y monitoreo | Orquestación |
226
+ | Flujo con muchas compensaciones | Orquestación |
227
+ | Feature nuevo que se suma al flujo existente | Coreografía (solo escucha/publica) |
228
+
229
+ ---
230
+
231
+ ## Manejo de Fallos
232
+
233
+ ### Retry con Backoff
234
+
235
+ ```ts
236
+ export class RetryPolicy {
237
+ constructor(
238
+ private readonly maxRetries: number = 3,
239
+ private readonly baseDelay: number = 100,
240
+ ) {}
241
+
242
+ async execute<T>(fn: () => Promise<T>): Promise<T> {
243
+ for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
244
+ try {
245
+ return await fn();
246
+ } catch (error) {
247
+ if (attempt === this.maxRetries) throw error;
248
+ const delay = this.baseDelay * Math.pow(2, attempt - 1);
249
+ await new Promise((resolve) => setTimeout(resolve, delay));
250
+ }
251
+ }
252
+ }
253
+ }
254
+ ```
255
+
256
+ ### Dead Letter Queue (DLQ)
257
+
258
+ Cuando una saga no puede completarse ni compensarse después de N reintentos:
259
+
260
+ ```ts
261
+ if (saga.retryCount >= 5) {
262
+ saga.sendToDLQ({
263
+ reason: "Payment service unavailable after 5 retries",
264
+ context: saga.context,
265
+ lastError: error.message,
266
+ });
267
+ // Requiere intervención humana
268
+ await this.sagaRepo.markFailed(saga);
269
+ await this.notificationService.alertOperations(
270
+ `Saga ${saga.id} requiere intervención manual`
271
+ );
272
+ }
273
+ ```
274
+
275
+ ### Fallback Humano
276
+
277
+ Para casos donde la compensación automática no es posible (ej. un pago ya procesado que no puede revertirse automáticamente):
278
+
279
+ ```ts
280
+ if (error.name === "IrreversibleError") {
281
+ saga.requireManualIntervention({
282
+ reason: "Payment already captured, manual refund required",
283
+ instructions: `Login to payment dashboard, refund transaction ${event.paymentId}, then resume saga ${saga.id}`,
284
+ severity: "HIGH",
285
+ });
286
+ }
287
+ ```
288
+
289
+ ---
290
+
291
+ ## Conexión con el Modelo de Forge
292
+
293
+ ### Reglas y Sagas
294
+
295
+ | Regla | Aplicación |
296
+ |---|---|
297
+ | **R8** (no cross-feature imports) | La saga se comunica con features mediante eventos o interfaces. Nunca importa directamente. |
298
+ | **R5** (domain → infra) | La saga opera en application/use-cases/. No hay infra en dominio de saga. |
299
+ | **R9** (no ciclos) | Las sagas coreografiadas deben auditarse para evitar ciclos de eventos (A→B→C→A). |
300
+
301
+ ### Ubicación de la saga en la arquitectura
302
+
303
+ ```
304
+ src/features/
305
+ checkout-saga/ ← orchestrator como feature
306
+ domain/
307
+ SagaInstance.entity.ts
308
+ ISagaStepExecutor.ts
309
+ application/
310
+ use-cases/
311
+ ExecuteCheckoutSaga.ts
312
+ adapters/
313
+ out/
314
+ saga-steps/
315
+ CreateOrderStep.ts ← llama a contracts de Orders
316
+ ReserveStockStep.ts ← llama a contracts de Inventory
317
+ ProcessPaymentStep.ts ← llama a contracts de Payments
318
+ saga-persistence/
319
+ PostgresSagaRepository.ts
320
+ orders/
321
+ adapters/
322
+ in/
323
+ events/ ← escucha eventos de la saga
324
+ http/
325
+ commands/ ← la saga llama aquí via contract
326
+ ```
327
+
328
+ ---
329
+
330
+ ## Anti-patrones
331
+
332
+ | Anti-patrón | Problema | Solución |
333
+ |---|---|---|
334
+ | **Saga sin compensación** | Un paso falla y el sistema queda inconsistente. | Cada paso debe tener compensación definida explícitamente. |
335
+ | **Orchestrator con lógica de negocio** | El orchestrator decide descuentos, valida reglas, calcula montos. | El orchestrator solo coordina. La lógica de negocio vive en los use cases de cada feature. |
336
+ | **Coreografía sin trazabilidad** | Múltiples eventos volando, nadie sabe el estado global de un proceso. | Usar saga log + event store. Monitorear con `forge chain`. |
337
+ | **Timeout único** | Todas las operaciones tienen el mismo timeout. Operaciones lentas (pagos) fallan donde rápidas (stock) no. | Timeout configurable por paso de saga. |
338
+ | **Compensación que falla sin alerta** | La compensación falla silenciosamente. El sistema cree que compensó pero no lo hizo. | Alertas de operaciones para compensaciones fallidas. Siempre. |
339
+ | **Saga como transacción ACID** | Se intenta revertir un paso que ya tuvo efectos visibles para el usuario. | Las sagas ofrecen consistencia eventual. Los efectos visibles deben diseñarse para ser reversibles (o aceptar que no lo son). |
340
+
341
+ ---
342
+
343
+ ## Conexión con Forge
344
+
345
+ | Comando | Acción |
346
+ |---|---|
347
+ | `forge cast checkout-saga` | Crea feature orchestrator con estructura de saga |
348
+ | `forge inspect` | Detecta features que se comunican sincrónicamente (candidatos a saga) |
349
+ | `forge graph` | Visualiza el flujo de la saga como grafo de dependencias entre features |
350
+ | `forge chain` | Verifica que la saga no introduce ciclos (R9) |
351
+ | `forge assay` | Evalúa si la estrategia de coordinación (coreografía vs orquestación) es la adecuada |
352
+
353
+ ## Ver también
354
+
355
+ - `reference/bounded-contexts.md` — contexts que la saga coordina
356
+ - `reference/events.md` — eventos como unidad de la saga
357
+ - `reference/cqrs.md` — commands como pasos de la saga
358
+ - `reference/transactional-outbox.md` — entrega confiable de eventos de saga
359
+ - `reference/idempotency.md` — retry seguro de pasos de saga
@@ -85,3 +85,9 @@ export const createUserSchema = z.object({
85
85
  - Helmet para headers de seguridad (CSP, X-Frame-Options, etc.)
86
86
  - Secretos en variables de entorno, nunca en código
87
87
  - Auditoría de acciones sensibles: login, delete, role change, export
88
+
89
+ ## Ver también
90
+
91
+ - `reference/api-design.md` — middleware de seguridad, validación de entrada
92
+ - `reference/observability.md` — audit logging como práctica de observabilidad
93
+ - `reference/testing-patterns.md` — tests de middleware y seguridad
@@ -29,3 +29,9 @@ Extrae código reutilizable desde features hacia `shared/`.
29
29
  - El código extraído NO debe importar de infraestructura
30
30
  - El código extraído debe ser puro o depender solo de otros componentes shared
31
31
  - Si el código depende de platform, considerar moverlo a platform en lugar de shared
32
+
33
+ ## Ver también
34
+
35
+ - `reference/relocate.md` — operación similar de extracción
36
+ - `reference/data-patterns.md` — identificación de qué extraer a shared
37
+ - `reference/errors.md` — errores tipados como candidatos a smelt
@@ -47,3 +47,9 @@ Templa la arquitectura aplicando reglas de inyección de dependencias, seguridad
47
47
  - ❌ Importar tsyringe en archivos de dominio
48
48
  - ❌ Mezclar DI manual y contenedor en el mismo feature
49
49
  - ❌ Proxies automáticos o decoradores ocultos que dificulten el rastreo
50
+
51
+ ## Ver también
52
+
53
+ - `reference/di-strategies.md` — selección de estrategia DI antes de temperar
54
+ - `reference/patterns.md` — convenciones de naming en DI
55
+ - `reference/testing-patterns.md` — testabilidad que DI disciplinada habilita
@@ -67,3 +67,9 @@ it("POST /users returns 201", async () => {
67
67
  - Nombrar tests como unidades de comportamiento, no métodos
68
68
  - Coverage mínimo sugerido: 85% use cases, 75% adapters
69
69
  - Los mappers se testean con fixtures: entrada conocida → salida esperada
70
+
71
+ ## Ver también
72
+
73
+ - `reference/anti-corruption-layer.md` — tests de ACL y traducción entre contexts
74
+ - `reference/di-strategies.md` — DI testing con mocks manuales
75
+ - `reference/temper.md` — DI disciplinada que habilita testabilidad