elsabro 2.2.0 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +668 -20
  2. package/agents/elsabro-orchestrator.md +113 -0
  3. package/bin/install.js +0 -0
  4. package/commands/elsabro/execute.md +223 -46
  5. package/commands/elsabro/start.md +34 -0
  6. package/commands/elsabro/verify-work.md +29 -0
  7. package/flows/development-flow.json +452 -0
  8. package/flows/quick-flow.json +118 -0
  9. package/hooks/confirm-destructive.sh +145 -0
  10. package/hooks/hooks-config.json +81 -0
  11. package/hooks/lint-check.sh +238 -0
  12. package/hooks/post-edit-test.sh +189 -0
  13. package/package.json +5 -3
  14. package/references/SYSTEM_INDEX.md +379 -5
  15. package/references/agent-marketplace.md +2274 -0
  16. package/references/agent-protocol.md +1126 -0
  17. package/references/ai-code-suggestions.md +2413 -0
  18. package/references/checkpointing.md +595 -0
  19. package/references/collaboration-patterns.md +851 -0
  20. package/references/collaborative-sessions.md +1081 -0
  21. package/references/configuration-management.md +1810 -0
  22. package/references/cost-tracking.md +1095 -0
  23. package/references/enterprise-sso.md +2001 -0
  24. package/references/error-contracts-tests.md +1171 -0
  25. package/references/error-contracts-v2.md +968 -0
  26. package/references/error-contracts.md +3102 -0
  27. package/references/event-driven.md +1031 -0
  28. package/references/flow-orchestration.md +940 -0
  29. package/references/flow-visualization.md +1557 -0
  30. package/references/ide-integrations.md +3513 -0
  31. package/references/interrupt-system.md +681 -0
  32. package/references/kubernetes-deployment.md +3099 -0
  33. package/references/memory-system.md +683 -0
  34. package/references/mobile-companion.md +3236 -0
  35. package/references/multi-llm-providers.md +2494 -0
  36. package/references/multi-project-memory.md +1182 -0
  37. package/references/observability.md +793 -0
  38. package/references/output-schemas.md +858 -0
  39. package/references/parallel-worktrees.md +293 -0
  40. package/references/performance-profiler.md +955 -0
  41. package/references/plugin-system.md +1526 -0
  42. package/references/prompt-management.md +292 -0
  43. package/references/sandbox-execution.md +303 -0
  44. package/references/security-system.md +1253 -0
  45. package/references/streaming.md +696 -0
  46. package/references/testing-framework.md +1151 -0
  47. package/references/time-travel.md +802 -0
  48. package/references/tool-registry.md +886 -0
  49. package/references/voice-commands.md +3296 -0
  50. package/scripts/setup-parallel-worktrees.sh +319 -0
  51. package/skills/memory-update.md +207 -0
  52. package/skills/review.md +331 -0
  53. package/skills/techdebt.md +289 -0
  54. package/skills/tutor.md +219 -0
  55. package/templates/.planning/notes/.gitkeep +0 -0
  56. package/templates/CLAUDE.md.template +48 -0
  57. package/templates/agent-marketplace-config.json +220 -0
  58. package/templates/agent-protocol-config.json +136 -0
  59. package/templates/ai-suggestions-config.json +100 -0
  60. package/templates/checkpoint-state.json +61 -0
  61. package/templates/collaboration-config.json +157 -0
  62. package/templates/collaborative-sessions-config.json +153 -0
  63. package/templates/configuration-config.json +245 -0
  64. package/templates/cost-tracking-config.json +148 -0
  65. package/templates/enterprise-sso-config.json +438 -0
  66. package/templates/error-handling-config.json +79 -2
  67. package/templates/events-config.json +148 -0
  68. package/templates/flow-visualization-config.json +196 -0
  69. package/templates/ide-integrations-config.json +442 -0
  70. package/templates/kubernetes-config.json +764 -0
  71. package/templates/memory-state.json +84 -0
  72. package/templates/mistakes.md.template +52 -0
  73. package/templates/mobile-companion-config.json +600 -0
  74. package/templates/multi-llm-config.json +544 -0
  75. package/templates/multi-project-memory-config.json +145 -0
  76. package/templates/observability-config.json +109 -0
  77. package/templates/patterns.md.template +114 -0
  78. package/templates/performance-profiler-config.json +125 -0
  79. package/templates/plugin-config.json +170 -0
  80. package/templates/prompt-management-config.json +86 -0
  81. package/templates/sandbox-config.json +185 -0
  82. package/templates/schemas-config.json +65 -0
  83. package/templates/security-config.json +120 -0
  84. package/templates/streaming-config.json +72 -0
  85. package/templates/testing-config.json +81 -0
  86. package/templates/timetravel-config.json +62 -0
  87. package/templates/tool-registry-config.json +109 -0
  88. package/templates/voice-commands-config.json +658 -0
@@ -0,0 +1,3102 @@
1
+ ---
2
+ name: error-contracts
3
+ description: Contratos de error para ejecucion paralela confiable
4
+ version: 1.0.0
5
+ ---
6
+
7
+ # ELSABRO Error Contracts
8
+
9
+ ## Vision General
10
+
11
+ Este documento define los 7 contratos de error que garantizan ejecucion paralela confiable en ELSABRO. Cada contrato actua como una capa defensiva en cascada.
12
+
13
+ ```
14
+ +============================================================================+
15
+ | CASCADA DEFENSIVA DE CONTRATOS |
16
+ +============================================================================+
17
+ | |
18
+ | [1] REGISTRY VALIDATOR |
19
+ | | |
20
+ | v Agentes/comandos existen? |
21
+ | [2] TASK LIFECYCLE -----> NO --> ERROR: AGENT_NOT_FOUND |
22
+ | | |
23
+ | v Estado valido? |
24
+ | [3] TIMEOUT HANDLER -----> NO --> ERROR: INVALID_STATE_TRANSITION |
25
+ | | |
26
+ | v Dentro de tiempo? |
27
+ | [4] RETRY POLICY --------> NO --> ERROR: TASK_TIMEOUT |
28
+ | | |
29
+ | v Reintentos agotados? |
30
+ | [5] ERROR AGGREGATOR ----> SI --> ERROR: RETRY_EXHAUSTED |
31
+ | | |
32
+ | v Errores recolectados |
33
+ | [6] SEVERITY CLASSIFIER -> Clasificar cada error |
34
+ | | |
35
+ | v Decidir accion |
36
+ | [7] SESSION VALIDATOR ---> Validar integridad de estado |
37
+ | | |
38
+ | v |
39
+ | [OK] CONTINUAR o [STOP] ABORTAR |
40
+ | |
41
+ +============================================================================+
42
+ ```
43
+
44
+ ---
45
+
46
+ ## Contrato 1: ContractRegistryValidator
47
+
48
+ ### Proposito
49
+
50
+ Validar que todos los agentes, comandos y recursos referenciados existan antes de ejecutar cualquier operacion paralela.
51
+
52
+ ### Cuando se Activa
53
+
54
+ - Antes de lanzar cualquier Task() con subagente
55
+ - Antes de invocar comandos /elsabro:*
56
+ - Antes de referenciar recursos externos (skills, profiles)
57
+
58
+ ### Comportamiento en Fallo
59
+
60
+ ```
61
+ SI recurso no existe:
62
+ 1. NO lanzar la tarea
63
+ 2. Generar ERROR con codigo especifico
64
+ 3. Sugerir alternativas si existen
65
+ 4. Abortar operacion paralela si es critico
66
+ ```
67
+
68
+ ### Notificacion al Usuario
69
+
70
+ ```
71
+ +======================================================+
72
+ | ERROR: AGENT_NOT_FOUND |
73
+ | Severity: CRITICAL |
74
+ +======================================================+
75
+ | |
76
+ | El agente "elsabro-nonexistent" no existe |
77
+ | |
78
+ | Agentes similares disponibles: |
79
+ | - elsabro-executor |
80
+ | - elsabro-verifier |
81
+ | - elsabro-analyst |
82
+ | |
83
+ +------------------------------------------------------+
84
+ | ACCION REQUERIDA: |
85
+ | -> Corregir nombre del agente en la configuracion |
86
+ +======================================================+
87
+ ```
88
+
89
+ ### Codigo Ejecutable
90
+
91
+ ```javascript
92
+ /**
93
+ * ContractRegistryValidator
94
+ * Valida existencia de agentes y recursos antes de ejecucion
95
+ */
96
+ class ContractRegistryValidator {
97
+ constructor() {
98
+ // Registro de agentes validos por categoria
99
+ this.validAgents = {
100
+ exploration: [
101
+ 'Explore',
102
+ 'Plan',
103
+ 'feature-dev:code-explorer'
104
+ ],
105
+ implementation: [
106
+ 'elsabro-executor',
107
+ 'elsabro-analyst',
108
+ 'elsabro-planner',
109
+ 'elsabro-debugger',
110
+ 'feature-dev:code-architect'
111
+ ],
112
+ verification: [
113
+ 'elsabro-verifier',
114
+ 'pr-review-toolkit:code-reviewer',
115
+ 'pr-review-toolkit:silent-failure-hunter',
116
+ 'pr-review-toolkit:pr-test-analyzer'
117
+ ],
118
+ utility: [
119
+ 'elsabro-qa',
120
+ 'elsabro-tech-writer',
121
+ 'elsabro-scrum-master',
122
+ 'elsabro-ux-designer',
123
+ 'elsabro-quick-dev',
124
+ 'elsabro-yolo-dev'
125
+ ]
126
+ };
127
+
128
+ // Comandos validos
129
+ this.validCommands = [
130
+ 'start', 'plan', 'execute', 'verify-work', 'debug', 'quick', 'new',
131
+ 'pause-work', 'resume-work', 'progress',
132
+ 'new-milestone', 'complete-milestone', 'audit-milestone', 'plan-milestone-gaps',
133
+ 'add-phase', 'insert-phase', 'remove-phase', 'discuss-phase', 'research-phase',
134
+ 'add-todo', 'check-todos',
135
+ 'help', 'settings', 'set-profile', 'update', 'map-codebase', 'verify'
136
+ ];
137
+ }
138
+
139
+ /**
140
+ * Valida un agente individual
141
+ * @param {string} agentName - Nombre del agente
142
+ * @returns {Object} Resultado de validacion
143
+ */
144
+ validateAgent(agentName) {
145
+ const allAgents = Object.values(this.validAgents).flat();
146
+
147
+ if (allAgents.includes(agentName)) {
148
+ return {
149
+ valid: true,
150
+ agent: agentName,
151
+ category: this.getAgentCategory(agentName)
152
+ };
153
+ }
154
+
155
+ // Buscar sugerencias similares
156
+ const suggestions = this.findSimilarAgents(agentName, allAgents);
157
+
158
+ return {
159
+ valid: false,
160
+ agent: agentName,
161
+ error: {
162
+ code: 'AGENT_NOT_FOUND',
163
+ severity: 'CRITICAL',
164
+ message: `El agente "${agentName}" no existe en el registro`,
165
+ suggestions: suggestions,
166
+ canContinue: false
167
+ }
168
+ };
169
+ }
170
+
171
+ /**
172
+ * Valida multiples agentes para ejecucion paralela
173
+ * @param {string[]} agentNames - Lista de agentes
174
+ * @returns {Object} Resultado agregado
175
+ */
176
+ validateAgents(agentNames) {
177
+ const results = agentNames.map(name => this.validateAgent(name));
178
+ const invalid = results.filter(r => !r.valid);
179
+
180
+ if (invalid.length === 0) {
181
+ return {
182
+ valid: true,
183
+ agents: results,
184
+ readyForParallel: true
185
+ };
186
+ }
187
+
188
+ return {
189
+ valid: false,
190
+ agents: results,
191
+ errors: invalid.map(r => r.error),
192
+ readyForParallel: false,
193
+ aggregatedError: {
194
+ code: 'AGENTS_VALIDATION_FAILED',
195
+ severity: 'CRITICAL',
196
+ message: `${invalid.length} de ${agentNames.length} agentes no son validos`,
197
+ details: invalid
198
+ }
199
+ };
200
+ }
201
+
202
+ /**
203
+ * Valida un comando ELSABRO
204
+ * @param {string} commandName - Nombre del comando
205
+ * @returns {Object} Resultado de validacion
206
+ */
207
+ validateCommand(commandName) {
208
+ const normalizedName = commandName.replace(/^\/elsabro:/, '');
209
+
210
+ if (this.validCommands.includes(normalizedName)) {
211
+ return {
212
+ valid: true,
213
+ command: normalizedName
214
+ };
215
+ }
216
+
217
+ const suggestions = this.findSimilarCommands(normalizedName);
218
+
219
+ return {
220
+ valid: false,
221
+ command: commandName,
222
+ error: {
223
+ code: 'COMMAND_NOT_FOUND',
224
+ severity: 'CRITICAL',
225
+ message: `El comando "${commandName}" no existe`,
226
+ suggestions: suggestions.map(s => `/elsabro:${s}`),
227
+ canContinue: false
228
+ }
229
+ };
230
+ }
231
+
232
+ /**
233
+ * Obtiene la categoria de un agente
234
+ */
235
+ getAgentCategory(agentName) {
236
+ for (const [category, agents] of Object.entries(this.validAgents)) {
237
+ if (agents.includes(agentName)) {
238
+ return category;
239
+ }
240
+ }
241
+ return null;
242
+ }
243
+
244
+ /**
245
+ * Encuentra agentes similares usando distancia de Levenshtein
246
+ */
247
+ findSimilarAgents(input, agents) {
248
+ return agents
249
+ .map(agent => ({
250
+ name: agent,
251
+ distance: this.levenshteinDistance(input.toLowerCase(), agent.toLowerCase())
252
+ }))
253
+ .filter(a => a.distance <= 5)
254
+ .sort((a, b) => a.distance - b.distance)
255
+ .slice(0, 3)
256
+ .map(a => a.name);
257
+ }
258
+
259
+ /**
260
+ * Encuentra comandos similares
261
+ */
262
+ findSimilarCommands(input) {
263
+ return this.validCommands
264
+ .map(cmd => ({
265
+ name: cmd,
266
+ distance: this.levenshteinDistance(input.toLowerCase(), cmd.toLowerCase())
267
+ }))
268
+ .filter(c => c.distance <= 4)
269
+ .sort((a, b) => a.distance - b.distance)
270
+ .slice(0, 3)
271
+ .map(c => c.name);
272
+ }
273
+
274
+ /**
275
+ * Calcula distancia de Levenshtein entre dos strings
276
+ */
277
+ levenshteinDistance(str1, str2) {
278
+ const m = str1.length;
279
+ const n = str2.length;
280
+ const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
281
+
282
+ for (let i = 0; i <= m; i++) dp[i][0] = i;
283
+ for (let j = 0; j <= n; j++) dp[0][j] = j;
284
+
285
+ for (let i = 1; i <= m; i++) {
286
+ for (let j = 1; j <= n; j++) {
287
+ if (str1[i - 1] === str2[j - 1]) {
288
+ dp[i][j] = dp[i - 1][j - 1];
289
+ } else {
290
+ dp[i][j] = 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
291
+ }
292
+ }
293
+ }
294
+
295
+ return dp[m][n];
296
+ }
297
+
298
+ /**
299
+ * Valida un batch de agentes
300
+ * @param {Object[]} agents - Lista de objetos agente a validar
301
+ * @returns {Object} Resultado de validacion batch
302
+ */
303
+ async validateBatch(agents) {
304
+ const results = await Promise.all(
305
+ agents.map(agent => this.validate("agent", agent))
306
+ );
307
+ const failures = results.filter(r => !r.valid);
308
+ return {
309
+ total: agents.length,
310
+ valid: results.length - failures.length,
311
+ invalid: failures.length,
312
+ details: failures,
313
+ canProceed: failures.length === 0
314
+ };
315
+ }
316
+ }
317
+
318
+ // Ejemplo de uso
319
+ const validator = new ContractRegistryValidator();
320
+
321
+ // Validar agentes para ejecucion paralela
322
+ const result = validator.validateAgents([
323
+ 'elsabro-executor',
324
+ 'feature-dev:code-explorer',
325
+ 'elsabro-nonexistent' // Este fallara
326
+ ]);
327
+
328
+ if (!result.valid) {
329
+ console.log('Validacion fallida:', result.aggregatedError);
330
+ }
331
+ ```
332
+
333
+ ---
334
+
335
+ ## Contrato 2: ContractTaskLifecycle
336
+
337
+ ### Proposito
338
+
339
+ Garantizar que todas las tareas sigan el ciclo de vida correcto: INIT -> ACTIVE -> COMPLETE (o ERROR).
340
+
341
+ ### Cuando se Activa
342
+
343
+ - Al crear una Task con TaskCreate
344
+ - Al cambiar estado con TaskUpdate
345
+ - Al completar o fallar una tarea
346
+
347
+ ### Comportamiento en Fallo
348
+
349
+ ```
350
+ SI transicion invalida:
351
+ 1. Rechazar la operacion
352
+ 2. Mantener estado anterior
353
+ 3. Generar ERROR con transicion invalida
354
+ 4. Sugerir transicion correcta
355
+ ```
356
+
357
+ ### Notificacion al Usuario
358
+
359
+ ```
360
+ +======================================================+
361
+ | ERROR: INVALID_STATE_TRANSITION |
362
+ | Severity: HIGH |
363
+ +======================================================+
364
+ | |
365
+ | Transicion invalida detectada: |
366
+ | INIT -> COMPLETED (saltando ACTIVE) |
367
+ | |
368
+ | Flujo correcto: |
369
+ | INIT -> ACTIVE -> COMPLETED |
370
+ | INIT -> ACTIVE -> ERROR |
371
+ | |
372
+ +------------------------------------------------------+
373
+ | Opciones: |
374
+ | [f] Forzar transicion (no recomendado) |
375
+ | [c] Corregir flujo automaticamente |
376
+ | [a] Abortar operacion |
377
+ +======================================================+
378
+ ```
379
+
380
+ ### Codigo Ejecutable
381
+
382
+ ```javascript
383
+ /**
384
+ * ContractTaskLifecycle
385
+ * Garantiza transiciones de estado validas para tareas
386
+ */
387
+ class ContractTaskLifecycle {
388
+ // Estados consistentes con config.json
389
+ static STATES = {
390
+ INIT: "INIT",
391
+ ACTIVE: "ACTIVE",
392
+ COMPLETE: "COMPLETE",
393
+ FAILED: "FAILED",
394
+ ROLLED_BACK: "ROLLED_BACK",
395
+ DELETED: "DELETED"
396
+ };
397
+
398
+ static VALID_TRANSITIONS = {
399
+ INIT: ["ACTIVE", "FAILED", "DELETED"],
400
+ ACTIVE: ["COMPLETE", "FAILED", "ROLLED_BACK"],
401
+ COMPLETE: ["ROLLED_BACK"],
402
+ FAILED: ["ACTIVE", "DELETED"],
403
+ ROLLED_BACK: ["INIT"],
404
+ DELETED: []
405
+ };
406
+
407
+ constructor() {
408
+ // Estados validos (usando static para consistencia)
409
+ this.states = ContractTaskLifecycle.STATES;
410
+
411
+ // Transiciones validas: desde -> [hacia]
412
+ this.validTransitions = ContractTaskLifecycle.VALID_TRANSITIONS;
413
+
414
+ // Historial de tareas para tracking
415
+ this.taskHistory = new Map();
416
+ }
417
+
418
+ /**
419
+ * Registra una nueva tarea
420
+ * @param {string} taskId - ID de la tarea
421
+ * @param {Object} metadata - Metadata de la tarea
422
+ */
423
+ registerTask(taskId, metadata = {}) {
424
+ const task = {
425
+ id: taskId,
426
+ state: this.states.INIT,
427
+ createdAt: new Date().toISOString(),
428
+ updatedAt: new Date().toISOString(),
429
+ transitions: [{
430
+ from: null,
431
+ to: this.states.INIT,
432
+ at: new Date().toISOString()
433
+ }],
434
+ metadata: metadata
435
+ };
436
+
437
+ this.taskHistory.set(taskId, task);
438
+
439
+ return {
440
+ valid: true,
441
+ task: task
442
+ };
443
+ }
444
+
445
+ /**
446
+ * Valida y ejecuta una transicion de estado
447
+ * @param {string} taskId - ID de la tarea
448
+ * @param {string} newState - Nuevo estado
449
+ * @returns {Object} Resultado de la transicion
450
+ */
451
+ transition(taskId, newState) {
452
+ const task = this.taskHistory.get(taskId);
453
+
454
+ if (!task) {
455
+ return {
456
+ valid: false,
457
+ error: {
458
+ code: 'TASK_NOT_FOUND',
459
+ severity: 'CRITICAL',
460
+ message: `Tarea "${taskId}" no encontrada en el registro`,
461
+ canContinue: false
462
+ }
463
+ };
464
+ }
465
+
466
+ const currentState = task.state;
467
+ const allowedTransitions = this.validTransitions[currentState] || [];
468
+
469
+ if (!allowedTransitions.includes(newState)) {
470
+ return {
471
+ valid: false,
472
+ error: {
473
+ code: 'INVALID_STATE_TRANSITION',
474
+ severity: 'HIGH',
475
+ message: `Transicion invalida: ${currentState} -> ${newState}`,
476
+ currentState: currentState,
477
+ attemptedState: newState,
478
+ allowedTransitions: allowedTransitions,
479
+ canContinue: true,
480
+ suggestedFix: this.suggestCorrectTransition(currentState, newState)
481
+ }
482
+ };
483
+ }
484
+
485
+ // Ejecutar transicion
486
+ task.transitions.push({
487
+ from: currentState,
488
+ to: newState,
489
+ at: new Date().toISOString()
490
+ });
491
+ task.state = newState;
492
+ task.updatedAt = new Date().toISOString();
493
+
494
+ return {
495
+ valid: true,
496
+ task: task,
497
+ transition: {
498
+ from: currentState,
499
+ to: newState
500
+ }
501
+ };
502
+ }
503
+
504
+ /**
505
+ * Valida el ciclo completo de una tarea
506
+ * @param {string} taskId - ID de la tarea
507
+ */
508
+ validateLifecycle(taskId) {
509
+ const task = this.taskHistory.get(taskId);
510
+
511
+ if (!task) {
512
+ return { valid: false, error: 'Task not found' };
513
+ }
514
+
515
+ const requiredFlow = ['init', 'in_progress'];
516
+ const actualFlow = task.transitions.map(t => t.to);
517
+
518
+ // Verificar que paso por INIT y ACTIVE
519
+ const hasInit = actualFlow.includes('init');
520
+ const hasActive = actualFlow.includes('in_progress');
521
+
522
+ if (!hasInit || !hasActive) {
523
+ return {
524
+ valid: false,
525
+ error: {
526
+ code: 'INCOMPLETE_LIFECYCLE',
527
+ severity: 'MEDIUM',
528
+ message: 'La tarea no siguio el ciclo de vida completo',
529
+ expectedFlow: requiredFlow,
530
+ actualFlow: actualFlow
531
+ }
532
+ };
533
+ }
534
+
535
+ return {
536
+ valid: true,
537
+ lifecycle: actualFlow,
538
+ duration: this.calculateDuration(task)
539
+ };
540
+ }
541
+
542
+ /**
543
+ * Sugiere la transicion correcta
544
+ */
545
+ suggestCorrectTransition(currentState, attemptedState) {
546
+ // Si intenta ir a COMPLETED desde INIT, sugerir pasar por ACTIVE
547
+ if (currentState === 'init' && attemptedState === 'completed') {
548
+ return {
549
+ suggestion: 'Primero transicionar a in_progress, luego a completed',
550
+ steps: ['in_progress', 'completed']
551
+ };
552
+ }
553
+
554
+ return {
555
+ suggestion: `Transicionar a uno de: ${this.validTransitions[currentState].join(', ')}`,
556
+ steps: this.validTransitions[currentState]
557
+ };
558
+ }
559
+
560
+ /**
561
+ * Calcula la duracion de una tarea
562
+ */
563
+ calculateDuration(task) {
564
+ const start = new Date(task.createdAt);
565
+ const end = new Date(task.updatedAt);
566
+ return end - start;
567
+ }
568
+
569
+ /**
570
+ * Obtiene el estado actual de todas las tareas
571
+ */
572
+ getTasksStatus() {
573
+ const status = {
574
+ total: this.taskHistory.size,
575
+ byState: {},
576
+ active: [],
577
+ failed: []
578
+ };
579
+
580
+ for (const [id, task] of this.taskHistory) {
581
+ status.byState[task.state] = (status.byState[task.state] || 0) + 1;
582
+
583
+ if (task.state === 'in_progress') {
584
+ status.active.push(id);
585
+ }
586
+ if (task.state === 'error') {
587
+ status.failed.push(id);
588
+ }
589
+ }
590
+
591
+ return status;
592
+ }
593
+ }
594
+
595
+ // Ejemplo de uso
596
+ const lifecycle = new ContractTaskLifecycle();
597
+
598
+ // Flujo correcto
599
+ lifecycle.registerTask('task-001', { type: 'exploration' });
600
+ lifecycle.transition('task-001', 'in_progress');
601
+ lifecycle.transition('task-001', 'completed');
602
+
603
+ // Flujo incorrecto (detectado)
604
+ lifecycle.registerTask('task-002', { type: 'implementation' });
605
+ const badResult = lifecycle.transition('task-002', 'completed'); // Error!
606
+ if (!badResult.valid) {
607
+ console.log('Error detectado:', badResult.error);
608
+ }
609
+ ```
610
+
611
+ ---
612
+
613
+ ## Contrato 3: ContractTimeoutHandler
614
+
615
+ ### Proposito
616
+
617
+ Manejar timeouts de tareas paralelas con health checks periodicos y terminacion controlada.
618
+
619
+ ### Cuando se Activa
620
+
621
+ - Al lanzar cualquier Task() paralela
622
+ - Durante health checks periodicos (cada 5 segundos)
623
+ - Cuando una tarea excede su tiempo maximo (30 minutos default)
624
+
625
+ ### Comportamiento en Fallo
626
+
627
+ ```
628
+ SI timeout excedido:
629
+ 1. Intentar terminacion graceful (10s)
630
+ 2. Si no responde, terminacion forzada
631
+ 3. Marcar tarea como TIMEOUT
632
+ 4. Notificar al agregador de errores
633
+ 5. Decidir si continuar o abortar segun policy
634
+ ```
635
+
636
+ ### Notificacion al Usuario
637
+
638
+ ```
639
+ +======================================================+
640
+ | WARNING: TASK_TIMEOUT |
641
+ | Severity: HIGH |
642
+ +======================================================+
643
+ | |
644
+ | La tarea "explore-codebase" excedio el timeout |
645
+ | |
646
+ | Tiempo configurado: 30 minutos |
647
+ | Tiempo transcurrido: 31:45 |
648
+ | Ultimo health check: hace 35 segundos |
649
+ | |
650
+ +------------------------------------------------------+
651
+ | Estado actual: |
652
+ | - Agente: elsabro-executor |
653
+ | - Fase: in_progress |
654
+ | - Output parcial: 1,234 tokens |
655
+ | |
656
+ +------------------------------------------------------+
657
+ | Opciones: |
658
+ | [e] Extender timeout (+15 min) |
659
+ | [t] Terminar y usar output parcial |
660
+ | [r] Reintentar desde inicio |
661
+ | [a] Abortar toda la operacion |
662
+ +======================================================+
663
+ ```
664
+
665
+ ### Codigo Ejecutable
666
+
667
+ ```javascript
668
+ /**
669
+ * ContractTimeoutHandler
670
+ * Maneja timeouts con health checks y terminacion controlada
671
+ */
672
+ class ContractTimeoutHandler {
673
+ constructor(options = {}) {
674
+ this.defaultTimeout = options.defaultTimeout || 30 * 60 * 1000; // 30 min
675
+ this.healthCheckInterval = options.healthCheckInterval || 5000; // 5 seg
676
+ this.gracefulShutdownTime = options.gracefulShutdownTime || 10000; // 10 seg
677
+
678
+ this.activeTasks = new Map();
679
+ this.healthCheckTimers = new Map();
680
+ }
681
+
682
+ /**
683
+ * Inicia el tracking de una tarea con timeout
684
+ * @param {string} taskId - ID de la tarea
685
+ * @param {Object} options - Opciones de timeout
686
+ */
687
+ startTracking(taskId, options = {}) {
688
+ const timeout = options.timeout || this.defaultTimeout;
689
+ const startTime = Date.now();
690
+
691
+ const taskTracker = {
692
+ taskId: taskId,
693
+ startTime: startTime,
694
+ timeout: timeout,
695
+ lastHealthCheck: startTime,
696
+ healthCheckCount: 0,
697
+ status: 'active',
698
+ partialOutput: null
699
+ };
700
+
701
+ this.activeTasks.set(taskId, taskTracker);
702
+
703
+ // Iniciar health checks periodicos
704
+ const healthTimer = setInterval(() => {
705
+ this.performHealthCheck(taskId);
706
+ }, this.healthCheckInterval);
707
+
708
+ this.healthCheckTimers.set(taskId, healthTimer);
709
+
710
+ // Timer de timeout principal
711
+ setTimeout(() => {
712
+ this.handleTimeout(taskId);
713
+ }, timeout);
714
+
715
+ return {
716
+ taskId: taskId,
717
+ timeout: timeout,
718
+ startTime: new Date(startTime).toISOString()
719
+ };
720
+ }
721
+
722
+ /**
723
+ * Realiza un health check de la tarea
724
+ * @param {string} taskId - ID de la tarea
725
+ */
726
+ performHealthCheck(taskId) {
727
+ const tracker = this.activeTasks.get(taskId);
728
+
729
+ if (!tracker || tracker.status !== 'active') {
730
+ this.stopTracking(taskId);
731
+ return;
732
+ }
733
+
734
+ const now = Date.now();
735
+ const elapsed = now - tracker.startTime;
736
+ const remaining = tracker.timeout - elapsed;
737
+
738
+ tracker.lastHealthCheck = now;
739
+ tracker.healthCheckCount++;
740
+
741
+ // Calcular progreso estimado
742
+ const progress = {
743
+ elapsed: elapsed,
744
+ remaining: remaining,
745
+ percentage: Math.min(100, (elapsed / tracker.timeout) * 100),
746
+ healthChecks: tracker.healthCheckCount
747
+ };
748
+
749
+ // Emitir evento de health check
750
+ this.emitHealthStatus(taskId, progress);
751
+
752
+ // Warning si queda poco tiempo
753
+ if (remaining < 60000 && remaining > 0) { // Menos de 1 minuto
754
+ this.emitWarning(taskId, {
755
+ code: 'TIMEOUT_IMMINENT',
756
+ severity: 'MEDIUM',
757
+ message: `Tarea ${taskId} tiene menos de 1 minuto antes del timeout`,
758
+ remaining: remaining
759
+ });
760
+ }
761
+
762
+ return progress;
763
+ }
764
+
765
+ /**
766
+ * Maneja el timeout de una tarea
767
+ * @param {string} taskId - ID de la tarea
768
+ */
769
+ handleTimeout(taskId) {
770
+ const tracker = this.activeTasks.get(taskId);
771
+
772
+ if (!tracker || tracker.status !== 'active') {
773
+ return; // Ya fue manejado
774
+ }
775
+
776
+ tracker.status = 'timeout';
777
+
778
+ // Intentar terminacion graceful
779
+ const gracefulResult = this.attemptGracefulShutdown(taskId);
780
+
781
+ if (!gracefulResult.success) {
782
+ // Terminacion forzada
783
+ this.forceTerminate(taskId);
784
+ }
785
+
786
+ // Generar error de timeout
787
+ const timeoutError = {
788
+ code: 'TASK_TIMEOUT',
789
+ severity: 'HIGH',
790
+ taskId: taskId,
791
+ configuredTimeout: tracker.timeout,
792
+ elapsedTime: Date.now() - tracker.startTime,
793
+ lastHealthCheck: new Date(tracker.lastHealthCheck).toISOString(),
794
+ partialOutput: tracker.partialOutput,
795
+ canContinue: true,
796
+ options: ['extend', 'terminate', 'retry', 'abort']
797
+ };
798
+
799
+ this.stopTracking(taskId);
800
+
801
+ return timeoutError;
802
+ }
803
+
804
+ /**
805
+ * Intenta terminacion graceful
806
+ */
807
+ attemptGracefulShutdown(taskId) {
808
+ // Simular intento de terminacion graceful
809
+ // En implementacion real, enviaria senal a la tarea
810
+ return {
811
+ success: true,
812
+ taskId: taskId,
813
+ method: 'graceful'
814
+ };
815
+ }
816
+
817
+ /**
818
+ * Fuerza terminacion de la tarea
819
+ */
820
+ forceTerminate(taskId) {
821
+ const tracker = this.activeTasks.get(taskId);
822
+ if (tracker) {
823
+ tracker.status = 'terminated';
824
+ }
825
+
826
+ return {
827
+ taskId: taskId,
828
+ method: 'forced',
829
+ timestamp: new Date().toISOString()
830
+ };
831
+ }
832
+
833
+ /**
834
+ * Extiende el timeout de una tarea
835
+ * @param {string} taskId - ID de la tarea
836
+ * @param {number} additionalTime - Tiempo adicional en ms
837
+ */
838
+ extendTimeout(taskId, additionalTime = 15 * 60 * 1000) {
839
+ const tracker = this.activeTasks.get(taskId);
840
+
841
+ if (!tracker) {
842
+ return {
843
+ success: false,
844
+ error: 'Task not found'
845
+ };
846
+ }
847
+
848
+ tracker.timeout += additionalTime;
849
+ tracker.status = 'active';
850
+
851
+ return {
852
+ success: true,
853
+ taskId: taskId,
854
+ newTimeout: tracker.timeout,
855
+ additionalTime: additionalTime
856
+ };
857
+ }
858
+
859
+ /**
860
+ * Detiene el tracking de una tarea
861
+ */
862
+ stopTracking(taskId) {
863
+ const timer = this.healthCheckTimers.get(taskId);
864
+ if (timer) {
865
+ clearInterval(timer);
866
+ this.healthCheckTimers.delete(taskId);
867
+ }
868
+ this.activeTasks.delete(taskId);
869
+ }
870
+
871
+ /**
872
+ * Marca una tarea como completada
873
+ */
874
+ markComplete(taskId, result = null) {
875
+ const tracker = this.activeTasks.get(taskId);
876
+
877
+ if (tracker) {
878
+ tracker.status = 'completed';
879
+ tracker.partialOutput = result;
880
+ }
881
+
882
+ this.stopTracking(taskId);
883
+
884
+ return {
885
+ taskId: taskId,
886
+ status: 'completed',
887
+ duration: tracker ? Date.now() - tracker.startTime : null
888
+ };
889
+ }
890
+
891
+ /**
892
+ * Emite estado de health check (para logging/UI)
893
+ */
894
+ emitHealthStatus(taskId, progress) {
895
+ // En implementacion real, emitir a event bus o logger
896
+ console.log(`[HEALTH] Task ${taskId}: ${progress.percentage.toFixed(1)}% - ${progress.healthChecks} checks`);
897
+ }
898
+
899
+ /**
900
+ * Emite warning
901
+ */
902
+ emitWarning(taskId, warning) {
903
+ console.log(`[WARNING] ${warning.message}`);
904
+ }
905
+
906
+ /**
907
+ * Obtiene estado de todas las tareas activas
908
+ */
909
+ getActiveTasksStatus() {
910
+ const status = [];
911
+
912
+ for (const [taskId, tracker] of this.activeTasks) {
913
+ const elapsed = Date.now() - tracker.startTime;
914
+ status.push({
915
+ taskId: taskId,
916
+ status: tracker.status,
917
+ elapsed: elapsed,
918
+ remaining: tracker.timeout - elapsed,
919
+ healthChecks: tracker.healthCheckCount
920
+ });
921
+ }
922
+
923
+ return status;
924
+ }
925
+
926
+ /**
927
+ * Alias para startTracking - para compatibilidad con contratos
928
+ * @param {string} operationId - ID de la operacion
929
+ * @param {number} limitMs - Limite de tiempo en ms
930
+ * @param {Object} metadata - Metadata adicional
931
+ */
932
+ startTimeout(operationId, limitMs, metadata) {
933
+ return this.startTracking(operationId, { timeout: limitMs, ...metadata });
934
+ }
935
+
936
+ /**
937
+ * Alias para markComplete - para compatibilidad con contratos
938
+ * @param {string} operationId - ID de la operacion
939
+ * @param {number} actualDuration - Duracion real de la operacion
940
+ */
941
+ completeTimeout(operationId, actualDuration) {
942
+ return this.markComplete(operationId, { duration: actualDuration });
943
+ }
944
+ }
945
+
946
+ // Ejemplo de uso
947
+ const timeoutHandler = new ContractTimeoutHandler({
948
+ defaultTimeout: 5 * 60 * 1000, // 5 minutos para testing
949
+ healthCheckInterval: 1000 // 1 segundo
950
+ });
951
+
952
+ // Iniciar tracking
953
+ timeoutHandler.startTracking('task-parallel-001');
954
+ timeoutHandler.startTracking('task-parallel-002');
955
+
956
+ // Obtener estado
957
+ const status = timeoutHandler.getActiveTasksStatus();
958
+ console.log('Active tasks:', status);
959
+ ```
960
+
961
+ ---
962
+
963
+ ## Contrato 4: ContractRetryPolicy
964
+
965
+ ### Proposito
966
+
967
+ Implementar politica de reintentos con backoff exponencial para operaciones fallidas.
968
+
969
+ ### Cuando se Activa
970
+
971
+ - Cuando una tarea falla con error transitorio
972
+ - Despues de timeout
973
+ - En errores de red o rate limit
974
+
975
+ ### Comportamiento en Fallo
976
+
977
+ ```
978
+ Intento 1: Ejecutar inmediatamente
979
+ |
980
+ v FALLO
981
+ Intento 2: Esperar 1s, reintentar
982
+ |
983
+ v FALLO
984
+ Intento 3: Esperar 2s, reintentar
985
+ |
986
+ v FALLO
987
+ Intento 4: Esperar 4s, reintentar
988
+ |
989
+ v FALLO
990
+ -> RETRY_EXHAUSTED: Escalar a usuario
991
+ ```
992
+
993
+ ### Notificacion al Usuario
994
+
995
+ ```
996
+ +------------------------------------------------------+
997
+ | RETRY: npm test |
998
+ +------------------------------------------------------+
999
+ | Attempt 1/3: FAILED (timeout) |
1000
+ | Waiting 1s before retry... |
1001
+ | |
1002
+ | Attempt 2/3: FAILED (exit code 1) |
1003
+ | Waiting 2s before retry... |
1004
+ | |
1005
+ | Attempt 3/3: FAILED (exit code 1) |
1006
+ | |
1007
+ | X All attempts exhausted |
1008
+ | -> Error escalated to user |
1009
+ +------------------------------------------------------+
1010
+
1011
+ +======================================================+
1012
+ | ERROR: RETRY_EXHAUSTED |
1013
+ | Severity: HIGH |
1014
+ +======================================================+
1015
+ | |
1016
+ | Intentos: 3/3 agotados |
1017
+ | Tiempo total: 8.5s |
1018
+ | |
1019
+ | Ultimo error: |
1020
+ | > Test suite failed to run |
1021
+ | > Cannot find module '@/utils' |
1022
+ | |
1023
+ +------------------------------------------------------+
1024
+ | El problema parece ser: |
1025
+ | -> Dependencia faltante o path alias incorrecto |
1026
+ | |
1027
+ | Opciones: |
1028
+ | [d] Investigar con /elsabro:debug |
1029
+ | [r] Reintentar despues de arreglar |
1030
+ | [s] Saltar esta tarea |
1031
+ | [a] Abortar |
1032
+ +======================================================+
1033
+ ```
1034
+
1035
+ ### Codigo Ejecutable
1036
+
1037
+ ```javascript
1038
+ /**
1039
+ * ContractRetryPolicy
1040
+ * Implementa reintentos con backoff exponencial
1041
+ */
1042
+ class ContractRetryPolicy {
1043
+ constructor(options = {}) {
1044
+ this.maxAttempts = options.maxAttempts || 3;
1045
+ this.baseDelayMs = options.baseDelayMs || 1000;
1046
+ this.backoffMultiplier = options.backoffMultiplier || 2;
1047
+ this.maxDelayMs = options.maxDelayMs || 30000;
1048
+ this.timeoutPerAttemptMs = options.timeoutPerAttemptMs || 30000;
1049
+ this.totalTimeoutMs = options.totalTimeoutMs || 120000;
1050
+
1051
+ // Errores que SI deben reintentarse
1052
+ this.retryableErrors = [
1053
+ 'TIMEOUT',
1054
+ 'NETWORK_ERROR',
1055
+ 'RATE_LIMIT',
1056
+ 'SERVICE_UNAVAILABLE',
1057
+ 'CONNECTION_RESET',
1058
+ 'ECONNREFUSED',
1059
+ 'ETIMEDOUT'
1060
+ ];
1061
+
1062
+ // Errores que NO deben reintentarse
1063
+ this.nonRetryableErrors = [
1064
+ 'PARSE_ERROR',
1065
+ 'VALIDATION_ERROR',
1066
+ 'LOGIC_ERROR',
1067
+ 'FILE_NOT_FOUND',
1068
+ 'PERMISSION_DENIED',
1069
+ 'INVALID_ARGUMENT'
1070
+ ];
1071
+ }
1072
+
1073
+ /**
1074
+ * Determina si un error es reintentable
1075
+ * @param {Object} error - Error a evaluar
1076
+ */
1077
+ isRetryable(error) {
1078
+ const errorCode = error.code || error.type || '';
1079
+
1080
+ // Verificar lista negra primero
1081
+ if (this.nonRetryableErrors.some(e => errorCode.includes(e))) {
1082
+ return false;
1083
+ }
1084
+
1085
+ // Verificar lista blanca
1086
+ if (this.retryableErrors.some(e => errorCode.includes(e))) {
1087
+ return true;
1088
+ }
1089
+
1090
+ // Por defecto, no reintentar errores desconocidos
1091
+ return false;
1092
+ }
1093
+
1094
+ /**
1095
+ * Calcula el delay para el siguiente intento
1096
+ * @param {number} attempt - Numero de intento (1-based)
1097
+ */
1098
+ calculateDelay(attempt) {
1099
+ const delay = this.baseDelayMs * Math.pow(this.backoffMultiplier, attempt - 1);
1100
+ return Math.min(delay, this.maxDelayMs);
1101
+ }
1102
+
1103
+ /**
1104
+ * Ejecuta una operacion con reintentos
1105
+ * @param {Function} operation - Operacion a ejecutar
1106
+ * @param {Object} context - Contexto de la operacion
1107
+ */
1108
+ async executeWithRetry(operation, context = {}) {
1109
+ const startTime = Date.now();
1110
+ const attempts = [];
1111
+ let lastError = null;
1112
+
1113
+ for (let attempt = 1; attempt <= this.maxAttempts; attempt++) {
1114
+ const attemptStart = Date.now();
1115
+
1116
+ try {
1117
+ // Verificar timeout total
1118
+ if (Date.now() - startTime > this.totalTimeoutMs) {
1119
+ throw {
1120
+ code: 'TOTAL_TIMEOUT_EXCEEDED',
1121
+ message: `Timeout total de ${this.totalTimeoutMs}ms excedido`
1122
+ };
1123
+ }
1124
+
1125
+ // Ejecutar operacion
1126
+ const result = await this.executeWithTimeout(operation, this.timeoutPerAttemptMs);
1127
+
1128
+ // Exito!
1129
+ attempts.push({
1130
+ attempt: attempt,
1131
+ status: 'SUCCESS',
1132
+ duration: Date.now() - attemptStart
1133
+ });
1134
+
1135
+ return {
1136
+ success: true,
1137
+ result: result,
1138
+ attempts: attempts,
1139
+ totalDuration: Date.now() - startTime
1140
+ };
1141
+
1142
+ } catch (error) {
1143
+ lastError = error;
1144
+
1145
+ attempts.push({
1146
+ attempt: attempt,
1147
+ status: 'FAILED',
1148
+ error: error.code || error.message,
1149
+ duration: Date.now() - attemptStart
1150
+ });
1151
+
1152
+ // Verificar si debe reintentar
1153
+ if (!this.isRetryable(error)) {
1154
+ return {
1155
+ success: false,
1156
+ error: this.createNonRetryableError(error, attempts),
1157
+ attempts: attempts,
1158
+ totalDuration: Date.now() - startTime
1159
+ };
1160
+ }
1161
+
1162
+ // Si hay mas intentos, esperar y reintentar
1163
+ if (attempt < this.maxAttempts) {
1164
+ const delay = this.calculateDelay(attempt);
1165
+ await this.sleep(delay);
1166
+ }
1167
+ }
1168
+ }
1169
+
1170
+ // Todos los intentos fallaron
1171
+ return {
1172
+ success: false,
1173
+ error: this.createExhaustedError(lastError, attempts),
1174
+ attempts: attempts,
1175
+ totalDuration: Date.now() - startTime
1176
+ };
1177
+ }
1178
+
1179
+ /**
1180
+ * Ejecuta operacion con timeout
1181
+ */
1182
+ async executeWithTimeout(operation, timeout) {
1183
+ return new Promise((resolve, reject) => {
1184
+ const timer = setTimeout(() => {
1185
+ reject({ code: 'TIMEOUT', message: `Operation timed out after ${timeout}ms` });
1186
+ }, timeout);
1187
+
1188
+ Promise.resolve(operation())
1189
+ .then(result => {
1190
+ clearTimeout(timer);
1191
+ resolve(result);
1192
+ })
1193
+ .catch(error => {
1194
+ clearTimeout(timer);
1195
+ reject(error);
1196
+ });
1197
+ });
1198
+ }
1199
+
1200
+ /**
1201
+ * Crea error de reintentos agotados
1202
+ */
1203
+ createExhaustedError(lastError, attempts) {
1204
+ return {
1205
+ code: 'RETRY_EXHAUSTED',
1206
+ severity: 'HIGH',
1207
+ message: `Todos los ${this.maxAttempts} intentos fallaron`,
1208
+ lastError: lastError,
1209
+ attempts: attempts,
1210
+ canContinue: true,
1211
+ options: ['debug', 'retry_manual', 'skip', 'abort'],
1212
+ suggestedAction: this.suggestAction(lastError)
1213
+ };
1214
+ }
1215
+
1216
+ /**
1217
+ * Crea error no reintentable
1218
+ */
1219
+ createNonRetryableError(error, attempts) {
1220
+ return {
1221
+ code: 'NON_RETRYABLE_ERROR',
1222
+ severity: 'HIGH',
1223
+ message: `Error no reintentable: ${error.code || error.message}`,
1224
+ originalError: error,
1225
+ attempts: attempts,
1226
+ reason: 'El tipo de error indica un problema que no se resolvera con reintentos',
1227
+ canContinue: true,
1228
+ options: ['fix_manual', 'skip', 'abort']
1229
+ };
1230
+ }
1231
+
1232
+ /**
1233
+ * Sugiere accion basada en el error
1234
+ */
1235
+ suggestAction(error) {
1236
+ const errorCode = error.code || '';
1237
+
1238
+ if (errorCode.includes('TIMEOUT')) {
1239
+ return 'Considerar extender el timeout o revisar la carga del sistema';
1240
+ }
1241
+ if (errorCode.includes('NETWORK')) {
1242
+ return 'Verificar conexion de red y disponibilidad del servicio';
1243
+ }
1244
+ if (errorCode.includes('RATE_LIMIT')) {
1245
+ return 'Esperar antes de reintentar o reducir frecuencia de requests';
1246
+ }
1247
+
1248
+ return 'Investigar la causa raiz con /elsabro:debug';
1249
+ }
1250
+
1251
+ /**
1252
+ * Helper para sleep
1253
+ */
1254
+ sleep(ms) {
1255
+ return new Promise(resolve => setTimeout(resolve, ms));
1256
+ }
1257
+
1258
+ /**
1259
+ * Genera reporte visual de reintentos
1260
+ */
1261
+ generateRetryReport(attempts, context = {}) {
1262
+ const lines = [
1263
+ '+------------------------------------------------------+',
1264
+ `| RETRY: ${context.operation || 'operation'}`.padEnd(55) + '|',
1265
+ '+------------------------------------------------------+'
1266
+ ];
1267
+
1268
+ for (const attempt of attempts) {
1269
+ const status = attempt.status === 'SUCCESS' ? 'SUCCESS' : 'FAILED';
1270
+ const statusLine = `| Attempt ${attempt.attempt}/${this.maxAttempts}: ${status}`;
1271
+
1272
+ if (attempt.error) {
1273
+ lines.push(statusLine + ` (${attempt.error})`.padEnd(55 - statusLine.length) + '|');
1274
+ } else {
1275
+ lines.push(statusLine.padEnd(55) + '|');
1276
+ }
1277
+
1278
+ if (attempt.attempt < attempts.length && attempt.status === 'FAILED') {
1279
+ const delay = this.calculateDelay(attempt.attempt) / 1000;
1280
+ lines.push(`| Waiting ${delay}s before retry...`.padEnd(55) + '|');
1281
+ lines.push('|'.padEnd(55) + '|');
1282
+ }
1283
+ }
1284
+
1285
+ const lastAttempt = attempts[attempts.length - 1];
1286
+ if (lastAttempt.status === 'FAILED' && attempts.length >= this.maxAttempts) {
1287
+ lines.push('|'.padEnd(55) + '|');
1288
+ lines.push('| X All attempts exhausted'.padEnd(55) + '|');
1289
+ lines.push('| -> Error escalated to user'.padEnd(55) + '|');
1290
+ }
1291
+
1292
+ lines.push('+------------------------------------------------------+');
1293
+
1294
+ return lines.join('\n');
1295
+ }
1296
+ }
1297
+
1298
+ // Ejemplo de uso
1299
+ const retryPolicy = new ContractRetryPolicy({
1300
+ maxAttempts: 3,
1301
+ baseDelayMs: 1000,
1302
+ backoffMultiplier: 2
1303
+ });
1304
+
1305
+ // Operacion que podria fallar
1306
+ async function unreliableOperation() {
1307
+ // Simular 60% de fallos
1308
+ if (Math.random() < 0.6) {
1309
+ throw { code: 'NETWORK_ERROR', message: 'Connection failed' };
1310
+ }
1311
+ return { success: true, data: 'result' };
1312
+ }
1313
+
1314
+ // Ejecutar con reintentos
1315
+ retryPolicy.executeWithRetry(unreliableOperation)
1316
+ .then(result => {
1317
+ console.log(retryPolicy.generateRetryReport(result.attempts, { operation: 'api-call' }));
1318
+ });
1319
+ ```
1320
+
1321
+ ---
1322
+
1323
+ ## Contrato 5: ContractErrorAggregator
1324
+
1325
+ ### Proposito
1326
+
1327
+ Colectar, agregar y aplicar politicas a errores de multiples tareas paralelas.
1328
+
1329
+ ### Cuando se Activa
1330
+
1331
+ - Al finalizar una wave de tareas paralelas
1332
+ - Cuando cualquier tarea individual falla
1333
+ - Para decidir si continuar o abortar la operacion general
1334
+
1335
+ ### Comportamiento en Fallo
1336
+
1337
+ ```
1338
+ Politicas disponibles:
1339
+ - fail_fast: Un fallo = abortar todo
1340
+ - quorum: Continuar si >50% exito
1341
+ - continue_all: Continuar siempre, reportar fallos
1342
+ - critical_path: Abortar solo si falla tarea critica
1343
+ ```
1344
+
1345
+ ### Notificacion al Usuario
1346
+
1347
+ ```
1348
+ +======================================================+
1349
+ | PARALLEL EXECUTION COMPLETE |
1350
+ +======================================================+
1351
+ | Policy: quorum |
1352
+ | Total: 4 agents |
1353
+ | |
1354
+ | OK Agent 1 (code-reviewer): SUCCESS (45s) |
1355
+ | OK Agent 2 (typescript-pro): SUCCESS (32s) |
1356
+ | X Agent 3 (silent-failure): FAILED (timeout) |
1357
+ | OK Agent 4 (test-analyzer): SUCCESS (28s) |
1358
+ | |
1359
+ | Result: 3/4 (75%) - QUORUM MET |
1360
+ | Status: CONTINUING |
1361
+ +======================================================+
1362
+ ```
1363
+
1364
+ ### Codigo Ejecutable
1365
+
1366
+ ```javascript
1367
+ /**
1368
+ * ContractErrorAggregator
1369
+ * Colecta y aplica politicas a errores de ejecucion paralela
1370
+ */
1371
+ class ContractErrorAggregator {
1372
+ constructor() {
1373
+ this.errors = [];
1374
+ this.results = [];
1375
+ this.policy = 'quorum'; // default
1376
+ this.criticalAgents = [];
1377
+ }
1378
+
1379
+ /**
1380
+ * Configura la politica de agregacion
1381
+ * @param {string} policy - Politica a usar
1382
+ * @param {Object} options - Opciones adicionales
1383
+ */
1384
+ setPolicy(policy, options = {}) {
1385
+ const validPolicies = ['fail_fast', 'quorum', 'continue_all', 'critical_path'];
1386
+
1387
+ if (!validPolicies.includes(policy)) {
1388
+ throw new Error(`Politica invalida: ${policy}. Validas: ${validPolicies.join(', ')}`);
1389
+ }
1390
+
1391
+ this.policy = policy;
1392
+ this.criticalAgents = options.criticalAgents || [];
1393
+ this.quorumThreshold = options.quorumThreshold || 0.5; // 50%
1394
+ }
1395
+
1396
+ /**
1397
+ * Registra un resultado de tarea
1398
+ * @param {Object} result - Resultado de la tarea
1399
+ */
1400
+ addResult(result) {
1401
+ this.results.push({
1402
+ taskId: result.taskId,
1403
+ agent: result.agent,
1404
+ status: result.status,
1405
+ duration: result.duration,
1406
+ output: result.output,
1407
+ error: result.error,
1408
+ timestamp: new Date().toISOString()
1409
+ });
1410
+
1411
+ if (result.status === 'error' || result.status === 'timeout') {
1412
+ this.errors.push({
1413
+ taskId: result.taskId,
1414
+ agent: result.agent,
1415
+ error: result.error,
1416
+ severity: this.determineSeverity(result),
1417
+ isCritical: this.criticalAgents.includes(result.agent)
1418
+ });
1419
+ }
1420
+
1421
+ return this.evaluatePolicy();
1422
+ }
1423
+
1424
+ /**
1425
+ * Evalua si debe continuar segun la politica
1426
+ */
1427
+ evaluatePolicy() {
1428
+ switch (this.policy) {
1429
+ case 'fail_fast':
1430
+ return this.evaluateFailFast();
1431
+ case 'quorum':
1432
+ return this.evaluateQuorum();
1433
+ case 'continue_all':
1434
+ return this.evaluateContinueAll();
1435
+ case 'critical_path':
1436
+ return this.evaluateCriticalPath();
1437
+ default:
1438
+ return this.evaluateQuorum();
1439
+ }
1440
+ }
1441
+
1442
+ /**
1443
+ * Politica: fail_fast
1444
+ * Un solo fallo = abortar todo
1445
+ */
1446
+ evaluateFailFast() {
1447
+ if (this.errors.length > 0) {
1448
+ return {
1449
+ shouldContinue: false,
1450
+ policy: 'fail_fast',
1451
+ reason: 'Fallo detectado - abortando segun politica fail_fast',
1452
+ failedAgent: this.errors[0].agent,
1453
+ error: this.errors[0].error
1454
+ };
1455
+ }
1456
+
1457
+ return {
1458
+ shouldContinue: true,
1459
+ policy: 'fail_fast',
1460
+ reason: 'Sin fallos detectados'
1461
+ };
1462
+ }
1463
+
1464
+ /**
1465
+ * Politica: quorum
1466
+ * Continuar si >50% exito
1467
+ */
1468
+ evaluateQuorum() {
1469
+ const total = this.results.length;
1470
+ const successes = this.results.filter(r => r.status === 'success').length;
1471
+ const successRate = total > 0 ? successes / total : 0;
1472
+ const quorumMet = successRate >= this.quorumThreshold;
1473
+
1474
+ return {
1475
+ shouldContinue: quorumMet,
1476
+ policy: 'quorum',
1477
+ successRate: successRate,
1478
+ threshold: this.quorumThreshold,
1479
+ quorumMet: quorumMet,
1480
+ reason: quorumMet
1481
+ ? `Quorum alcanzado: ${successes}/${total} (${(successRate * 100).toFixed(0)}%)`
1482
+ : `Quorum NO alcanzado: ${successes}/${total} (${(successRate * 100).toFixed(0)}% < ${this.quorumThreshold * 100}%)`,
1483
+ failedAgents: this.errors.map(e => e.agent)
1484
+ };
1485
+ }
1486
+
1487
+ /**
1488
+ * Politica: continue_all
1489
+ * Continuar siempre, solo reportar fallos
1490
+ */
1491
+ evaluateContinueAll() {
1492
+ return {
1493
+ shouldContinue: true,
1494
+ policy: 'continue_all',
1495
+ reason: 'Continuando segun politica continue_all',
1496
+ errors: this.errors,
1497
+ errorCount: this.errors.length
1498
+ };
1499
+ }
1500
+
1501
+ /**
1502
+ * Politica: critical_path
1503
+ * Abortar solo si falla un agente critico
1504
+ */
1505
+ evaluateCriticalPath() {
1506
+ const criticalFailures = this.errors.filter(e => e.isCritical);
1507
+
1508
+ if (criticalFailures.length > 0) {
1509
+ return {
1510
+ shouldContinue: false,
1511
+ policy: 'critical_path',
1512
+ reason: 'Agente critico fallo',
1513
+ criticalFailures: criticalFailures,
1514
+ optionalFailures: this.errors.filter(e => !e.isCritical)
1515
+ };
1516
+ }
1517
+
1518
+ return {
1519
+ shouldContinue: true,
1520
+ policy: 'critical_path',
1521
+ reason: 'Agentes criticos OK (fallos en opcionales ignorados)',
1522
+ optionalFailures: this.errors
1523
+ };
1524
+ }
1525
+
1526
+ /**
1527
+ * Determina la severidad de un error
1528
+ */
1529
+ determineSeverity(result) {
1530
+ if (result.error?.severity) {
1531
+ return result.error.severity;
1532
+ }
1533
+
1534
+ if (result.status === 'timeout') {
1535
+ return 'HIGH';
1536
+ }
1537
+
1538
+ if (this.criticalAgents.includes(result.agent)) {
1539
+ return 'CRITICAL';
1540
+ }
1541
+
1542
+ return 'MEDIUM';
1543
+ }
1544
+
1545
+ /**
1546
+ * Genera reporte visual de ejecucion paralela
1547
+ */
1548
+ generateReport() {
1549
+ const total = this.results.length;
1550
+ const successes = this.results.filter(r => r.status === 'success').length;
1551
+ const failures = this.errors.length;
1552
+ const successRate = total > 0 ? (successes / total * 100).toFixed(0) : 0;
1553
+
1554
+ const evaluation = this.evaluatePolicy();
1555
+
1556
+ const lines = [
1557
+ '+======================================================+',
1558
+ '| PARALLEL EXECUTION COMPLETE |',
1559
+ '+======================================================+',
1560
+ `| Policy: ${this.policy}`.padEnd(55) + '|',
1561
+ `| Total: ${total} agents`.padEnd(55) + '|',
1562
+ '|'.padEnd(55) + '|'
1563
+ ];
1564
+
1565
+ // Resultados por agente
1566
+ for (const result of this.results) {
1567
+ const icon = result.status === 'success' ? 'OK' : 'X ';
1568
+ const duration = result.duration ? `(${(result.duration / 1000).toFixed(0)}s)` : '';
1569
+ const status = result.status.toUpperCase();
1570
+ const errorInfo = result.error ? ` (${result.error.code || 'error'})` : '';
1571
+
1572
+ const line = `| ${icon} Agent (${result.agent}): ${status}${errorInfo} ${duration}`;
1573
+ lines.push(line.padEnd(55) + '|');
1574
+ }
1575
+
1576
+ lines.push('|'.padEnd(55) + '|');
1577
+
1578
+ // Resumen
1579
+ const quorumStatus = evaluation.shouldContinue ? 'MET' : 'NOT MET';
1580
+ lines.push(`| Result: ${successes}/${total} (${successRate}%) - ${this.policy.toUpperCase()} ${quorumStatus}`.padEnd(55) + '|');
1581
+ lines.push(`| Status: ${evaluation.shouldContinue ? 'CONTINUING' : 'STOPPING'}`.padEnd(55) + '|');
1582
+ lines.push('+======================================================+');
1583
+
1584
+ return lines.join('\n');
1585
+ }
1586
+
1587
+ /**
1588
+ * Obtiene resumen de errores
1589
+ */
1590
+ getErrorSummary() {
1591
+ const bySeverity = {};
1592
+
1593
+ for (const error of this.errors) {
1594
+ bySeverity[error.severity] = (bySeverity[error.severity] || 0) + 1;
1595
+ }
1596
+
1597
+ return {
1598
+ total: this.errors.length,
1599
+ bySeverity: bySeverity,
1600
+ criticalCount: this.errors.filter(e => e.isCritical).length,
1601
+ errors: this.errors
1602
+ };
1603
+ }
1604
+
1605
+ /**
1606
+ * Limpia el estado para nueva ejecucion
1607
+ */
1608
+ reset() {
1609
+ this.errors = [];
1610
+ this.results = [];
1611
+ }
1612
+ }
1613
+
1614
+ // Ejemplo de uso
1615
+ const aggregator = new ContractErrorAggregator();
1616
+
1617
+ // Configurar politica
1618
+ aggregator.setPolicy('quorum', {
1619
+ quorumThreshold: 0.5,
1620
+ criticalAgents: ['elsabro-executor', 'elsabro-verifier']
1621
+ });
1622
+
1623
+ // Simular resultados de ejecucion paralela
1624
+ aggregator.addResult({
1625
+ taskId: 'task-001',
1626
+ agent: 'code-reviewer',
1627
+ status: 'success',
1628
+ duration: 45000
1629
+ });
1630
+
1631
+ aggregator.addResult({
1632
+ taskId: 'task-002',
1633
+ agent: 'typescript-pro',
1634
+ status: 'success',
1635
+ duration: 32000
1636
+ });
1637
+
1638
+ aggregator.addResult({
1639
+ taskId: 'task-003',
1640
+ agent: 'silent-failure-hunter',
1641
+ status: 'timeout',
1642
+ error: { code: 'TIMEOUT' }
1643
+ });
1644
+
1645
+ aggregator.addResult({
1646
+ taskId: 'task-004',
1647
+ agent: 'test-analyzer',
1648
+ status: 'success',
1649
+ duration: 28000
1650
+ });
1651
+
1652
+ // Generar reporte
1653
+ console.log(aggregator.generateReport());
1654
+ ```
1655
+
1656
+ ---
1657
+
1658
+ ## Contrato 6: ContractSeverityClassifier
1659
+
1660
+ ### Proposito
1661
+
1662
+ Clasificar errores consistentemente en CRITICAL, HIGH, MEDIUM, LOW para tomar decisiones apropiadas.
1663
+
1664
+ ### Cuando se Activa
1665
+
1666
+ - Al recibir cualquier error
1667
+ - Al agregar errores en el ContractErrorAggregator
1668
+ - Para decidir notificaciones y acciones
1669
+
1670
+ ### Comportamiento en Fallo
1671
+
1672
+ ```
1673
+ CRITICAL: Estado corrupto, no puede continuar
1674
+ -> Abortar inmediatamente
1675
+ -> Notificar con urgencia
1676
+ -> Sugerir recuperacion
1677
+
1678
+ HIGH: Problema serio que deberia parar
1679
+ -> Ofrecer opciones al usuario
1680
+ -> Permitir continuar con riesgo
1681
+
1682
+ MEDIUM: Warning, puede continuar
1683
+ -> Registrar
1684
+ -> Continuar automaticamente
1685
+ -> Notificar al final
1686
+
1687
+ LOW: Informativo
1688
+ -> Solo registrar
1689
+ -> Continuar sin interrupcion
1690
+ ```
1691
+
1692
+ ### Notificacion al Usuario
1693
+
1694
+ ```
1695
+ CRITICAL:
1696
+ +======================================================+
1697
+ | ERROR: STATE_CORRUPTED |
1698
+ | Severity: CRITICAL |
1699
+ +======================================================+
1700
+ | |
1701
+ | El archivo de estado esta corrupto |
1702
+ | |
1703
+ | ACCION REQUERIDA: |
1704
+ | -> Ejecutar /elsabro:start para reinicializar |
1705
+ +======================================================+
1706
+
1707
+ HIGH:
1708
+ +======================================================+
1709
+ | ERROR: TESTS_FAILED |
1710
+ | Severity: HIGH |
1711
+ +======================================================+
1712
+ | |
1713
+ | 3 tests fallaron |
1714
+ | |
1715
+ | Opciones: |
1716
+ | [d] Debug con /elsabro:debug |
1717
+ | [s] Saltar (no recomendado) |
1718
+ | [a] Abortar |
1719
+ +======================================================+
1720
+
1721
+ MEDIUM:
1722
+ +------------------------------------------------------+
1723
+ | WARNING: LINT_WARNINGS |
1724
+ +------------------------------------------------------+
1725
+ | 5 lint warnings encontrados |
1726
+ | Continuando automaticamente... |
1727
+ +------------------------------------------------------+
1728
+
1729
+ LOW:
1730
+ [INFO] Retry exitoso despues de 1 intento fallido
1731
+ ```
1732
+
1733
+ ### Codigo Ejecutable
1734
+
1735
+ ```javascript
1736
+ /**
1737
+ * ContractSeverityClassifier
1738
+ * Clasifica errores en niveles de severidad consistentes
1739
+ */
1740
+ class ContractSeverityClassifier {
1741
+ constructor() {
1742
+ // Definicion de severidades
1743
+ this.severities = {
1744
+ CRITICAL: {
1745
+ level: 4,
1746
+ icon: 'ERROR',
1747
+ canContinue: false,
1748
+ notifyImmediately: true,
1749
+ requiresUserAction: true
1750
+ },
1751
+ HIGH: {
1752
+ level: 3,
1753
+ icon: 'ERROR',
1754
+ canContinue: true,
1755
+ notifyImmediately: true,
1756
+ requiresUserAction: true
1757
+ },
1758
+ MEDIUM: {
1759
+ level: 2,
1760
+ icon: 'WARNING',
1761
+ canContinue: true,
1762
+ notifyImmediately: false,
1763
+ requiresUserAction: false
1764
+ },
1765
+ LOW: {
1766
+ level: 1,
1767
+ icon: 'INFO',
1768
+ canContinue: true,
1769
+ notifyImmediately: false,
1770
+ requiresUserAction: false
1771
+ }
1772
+ };
1773
+
1774
+ // Mapeo de codigos de error a severidades
1775
+ this.errorSeverityMap = {
1776
+ // CRITICAL - No puede continuar
1777
+ 'STATE_CORRUPTED': 'CRITICAL',
1778
+ 'STATE_MISSING': 'CRITICAL',
1779
+ 'CONFIG_INVALID': 'CRITICAL',
1780
+ 'AGENT_NOT_FOUND': 'CRITICAL',
1781
+ 'INCOMPATIBLE_VERSION': 'CRITICAL',
1782
+ 'GIT_DETACHED_HEAD': 'CRITICAL',
1783
+ 'GIT_CONFLICTS': 'CRITICAL',
1784
+ 'SESSION_CORRUPTED': 'CRITICAL',
1785
+
1786
+ // HIGH - Deberia parar
1787
+ 'TESTS_FAILED': 'HIGH',
1788
+ 'BUILD_FAILED': 'HIGH',
1789
+ 'VALIDATION_FAILED': 'HIGH',
1790
+ 'TASK_TIMEOUT': 'HIGH',
1791
+ 'RETRY_EXHAUSTED': 'HIGH',
1792
+ 'API_ERROR': 'HIGH',
1793
+ 'PERMISSION_DENIED': 'HIGH',
1794
+ 'QUORUM_NOT_MET': 'HIGH',
1795
+ 'CRITICAL_AGENT_FAILED': 'HIGH',
1796
+ 'INVALID_STATE_TRANSITION': 'HIGH',
1797
+
1798
+ // MEDIUM - Warning
1799
+ 'LINT_WARNINGS': 'MEDIUM',
1800
+ 'DEPRECATION_WARNING': 'MEDIUM',
1801
+ 'COVERAGE_LOW': 'MEDIUM',
1802
+ 'UNUSED_FILES': 'MEDIUM',
1803
+ 'OPTIONAL_AGENT_FAILED': 'MEDIUM',
1804
+ 'SLOW_OPERATION': 'MEDIUM',
1805
+ 'PARTIAL_SUCCESS': 'MEDIUM',
1806
+
1807
+ // LOW - Informativo
1808
+ 'RETRY_SUCCESS': 'LOW',
1809
+ 'OPERATION_COMPLETE': 'LOW',
1810
+ 'SUGGESTION': 'LOW',
1811
+ 'IMPROVEMENT_HINT': 'LOW'
1812
+ };
1813
+
1814
+ // Patrones de error para clasificacion dinamica
1815
+ this.errorPatterns = [
1816
+ { pattern: /corrupt|invalid.*state|missing.*critical/i, severity: 'CRITICAL' },
1817
+ { pattern: /test.*fail|build.*fail|timeout|exhausted/i, severity: 'HIGH' },
1818
+ { pattern: /warning|deprecated|slow/i, severity: 'MEDIUM' },
1819
+ { pattern: /info|success|hint/i, severity: 'LOW' }
1820
+ ];
1821
+ }
1822
+
1823
+ /**
1824
+ * Clasifica un error
1825
+ * @param {Object|string} error - Error a clasificar
1826
+ * @returns {Object} Error clasificado con severidad
1827
+ */
1828
+ classify(error) {
1829
+ const errorCode = this.extractErrorCode(error);
1830
+ const errorMessage = this.extractErrorMessage(error);
1831
+
1832
+ // Buscar en mapa de errores conocidos
1833
+ let severity = this.errorSeverityMap[errorCode];
1834
+
1835
+ // Si no esta en el mapa, usar patrones
1836
+ if (!severity) {
1837
+ severity = this.classifyByPattern(errorMessage);
1838
+ }
1839
+
1840
+ // Fail-safe: errores desconocidos se tratan como HIGH
1841
+ if (!severity) {
1842
+ severity = 'HIGH';
1843
+ console.warn(`[SEVERITY] Error desconocido clasificado como HIGH: ${errorMessage}`);
1844
+ }
1845
+
1846
+ const severityConfig = this.severities[severity];
1847
+
1848
+ return {
1849
+ code: errorCode,
1850
+ message: errorMessage,
1851
+ severity: severity,
1852
+ level: severityConfig.level,
1853
+ canContinue: severityConfig.canContinue,
1854
+ notifyImmediately: severityConfig.notifyImmediately,
1855
+ requiresUserAction: severityConfig.requiresUserAction,
1856
+ timestamp: new Date().toISOString()
1857
+ };
1858
+ }
1859
+
1860
+ /**
1861
+ * Extrae codigo de error
1862
+ */
1863
+ extractErrorCode(error) {
1864
+ if (typeof error === 'string') {
1865
+ return error.toUpperCase().replace(/\s+/g, '_');
1866
+ }
1867
+ return error.code || error.type || 'UNKNOWN_ERROR';
1868
+ }
1869
+
1870
+ /**
1871
+ * Extrae mensaje de error
1872
+ */
1873
+ extractErrorMessage(error) {
1874
+ if (typeof error === 'string') {
1875
+ return error;
1876
+ }
1877
+ return error.message || error.description || String(error);
1878
+ }
1879
+
1880
+ /**
1881
+ * Clasifica por patron de mensaje
1882
+ */
1883
+ classifyByPattern(message) {
1884
+ for (const { pattern, severity } of this.errorPatterns) {
1885
+ if (pattern.test(message)) {
1886
+ return severity;
1887
+ }
1888
+ }
1889
+ return null;
1890
+ }
1891
+
1892
+ /**
1893
+ * Genera display visual del error
1894
+ * @param {Object} classifiedError - Error ya clasificado
1895
+ * @returns {string} Display ASCII
1896
+ */
1897
+ generateDisplay(classifiedError) {
1898
+ const severity = classifiedError.severity;
1899
+ const severityConfig = this.severities[severity];
1900
+
1901
+ if (severity === 'CRITICAL' || severity === 'HIGH') {
1902
+ return this.generateBoxDisplay(classifiedError);
1903
+ }
1904
+
1905
+ if (severity === 'MEDIUM') {
1906
+ return this.generateWarningDisplay(classifiedError);
1907
+ }
1908
+
1909
+ return this.generateInfoDisplay(classifiedError);
1910
+ }
1911
+
1912
+ /**
1913
+ * Display para errores CRITICAL/HIGH
1914
+ */
1915
+ generateBoxDisplay(error) {
1916
+ const icon = error.severity === 'CRITICAL' ? 'ERROR' : 'ERROR';
1917
+ const lines = [
1918
+ '+======================================================+',
1919
+ `| ${icon}: ${error.code}`.padEnd(55) + '|',
1920
+ `| Severity: ${error.severity}`.padEnd(55) + '|',
1921
+ '+======================================================+',
1922
+ '|'.padEnd(55) + '|'
1923
+ ];
1924
+
1925
+ // Dividir mensaje en lineas de 50 caracteres
1926
+ const messageLines = this.wrapText(error.message, 50);
1927
+ for (const line of messageLines) {
1928
+ lines.push(`| ${line}`.padEnd(55) + '|');
1929
+ }
1930
+
1931
+ lines.push('|'.padEnd(55) + '|');
1932
+
1933
+ if (error.requiresUserAction) {
1934
+ lines.push('+------------------------------------------------------+');
1935
+
1936
+ if (error.severity === 'CRITICAL') {
1937
+ lines.push('| ACCION REQUERIDA:'.padEnd(55) + '|');
1938
+ lines.push('| -> Resolver el error antes de continuar'.padEnd(55) + '|');
1939
+ } else {
1940
+ lines.push('| Opciones:'.padEnd(55) + '|');
1941
+ lines.push('| [d] Debug con /elsabro:debug'.padEnd(55) + '|');
1942
+ lines.push('| [s] Saltar (no recomendado)'.padEnd(55) + '|');
1943
+ lines.push('| [a] Abortar'.padEnd(55) + '|');
1944
+ }
1945
+ }
1946
+
1947
+ lines.push('+======================================================+');
1948
+
1949
+ return lines.join('\n');
1950
+ }
1951
+
1952
+ /**
1953
+ * Display para warnings (MEDIUM)
1954
+ */
1955
+ generateWarningDisplay(error) {
1956
+ const lines = [
1957
+ '+------------------------------------------------------+',
1958
+ `| WARNING: ${error.code}`.padEnd(55) + '|',
1959
+ '+------------------------------------------------------+'
1960
+ ];
1961
+
1962
+ const messageLines = this.wrapText(error.message, 50);
1963
+ for (const line of messageLines) {
1964
+ lines.push(`| ${line}`.padEnd(55) + '|');
1965
+ }
1966
+
1967
+ lines.push('| Continuando automaticamente...'.padEnd(55) + '|');
1968
+ lines.push('+------------------------------------------------------+');
1969
+
1970
+ return lines.join('\n');
1971
+ }
1972
+
1973
+ /**
1974
+ * Display para info (LOW)
1975
+ */
1976
+ generateInfoDisplay(error) {
1977
+ return `[INFO] ${error.message}`;
1978
+ }
1979
+
1980
+ /**
1981
+ * Wrap texto a ancho especifico
1982
+ */
1983
+ wrapText(text, width) {
1984
+ const words = text.split(' ');
1985
+ const lines = [];
1986
+ let currentLine = '';
1987
+
1988
+ for (const word of words) {
1989
+ if ((currentLine + ' ' + word).trim().length <= width) {
1990
+ currentLine = (currentLine + ' ' + word).trim();
1991
+ } else {
1992
+ if (currentLine) lines.push(currentLine);
1993
+ currentLine = word;
1994
+ }
1995
+ }
1996
+
1997
+ if (currentLine) lines.push(currentLine);
1998
+
1999
+ return lines;
2000
+ }
2001
+
2002
+ /**
2003
+ * Compara severidades
2004
+ * @param {string} sev1 - Primera severidad
2005
+ * @param {string} sev2 - Segunda severidad
2006
+ * @returns {number} -1, 0, o 1
2007
+ */
2008
+ compareSeverity(sev1, sev2) {
2009
+ const level1 = this.severities[sev1]?.level || 0;
2010
+ const level2 = this.severities[sev2]?.level || 0;
2011
+ return level2 - level1; // Mayor severidad primero
2012
+ }
2013
+
2014
+ /**
2015
+ * Obtiene la severidad mas alta de una lista
2016
+ */
2017
+ getHighestSeverity(errors) {
2018
+ let highest = 'LOW';
2019
+
2020
+ for (const error of errors) {
2021
+ const classified = this.classify(error);
2022
+ if (this.severities[classified.severity].level > this.severities[highest].level) {
2023
+ highest = classified.severity;
2024
+ }
2025
+ }
2026
+
2027
+ return highest;
2028
+ }
2029
+ }
2030
+
2031
+ // Ejemplo de uso
2032
+ const classifier = new ContractSeverityClassifier();
2033
+
2034
+ // Clasificar varios errores
2035
+ const errors = [
2036
+ { code: 'STATE_CORRUPTED', message: 'El archivo state.json esta corrupto' },
2037
+ { code: 'TESTS_FAILED', message: '3 tests fallaron en el modulo de auth' },
2038
+ { code: 'LINT_WARNINGS', message: '5 lint warnings encontrados' },
2039
+ { code: 'RETRY_SUCCESS', message: 'Operacion exitosa despues de 2 reintentos' }
2040
+ ];
2041
+
2042
+ for (const error of errors) {
2043
+ const classified = classifier.classify(error);
2044
+ console.log(classifier.generateDisplay(classified));
2045
+ console.log('');
2046
+ }
2047
+ ```
2048
+
2049
+ ---
2050
+
2051
+ ## Contrato 7: ContractSessionValidator
2052
+
2053
+ ### Proposito
2054
+
2055
+ Validar la integridad del archivo de estado `.elsabro/state.json` y detectar corrupcion o inconsistencias.
2056
+
2057
+ ### Cuando se Activa
2058
+
2059
+ - Al inicio de cualquier comando ELSABRO
2060
+ - Al escribir estado
2061
+ - Periodicamente durante operaciones largas
2062
+
2063
+ ### Comportamiento en Fallo
2064
+
2065
+ ```
2066
+ SI estado corrupto:
2067
+ 1. Intentar recuperar de backup
2068
+ 2. Si no hay backup, reinicializar
2069
+ 3. Preservar contexto que se pueda salvar
2070
+ 4. Notificar al usuario
2071
+ ```
2072
+
2073
+ ### Notificacion al Usuario
2074
+
2075
+ ```
2076
+ +======================================================+
2077
+ | ERROR: SESSION_CORRUPTED |
2078
+ | Severity: CRITICAL |
2079
+ +======================================================+
2080
+ | |
2081
+ | El archivo .elsabro/state.json esta corrupto |
2082
+ | o tiene estructura invalida |
2083
+ | |
2084
+ | Detalles: |
2085
+ | - Campo 'version' faltante |
2086
+ | - Campo 'current_flow' tiene tipo invalido |
2087
+ | |
2088
+ +------------------------------------------------------+
2089
+ | Opciones de recuperacion: |
2090
+ | [b] Restaurar desde backup |
2091
+ | [r] Reinicializar estado (perdera historial) |
2092
+ | [m] Editar manualmente |
2093
+ +======================================================+
2094
+ ```
2095
+
2096
+ ### Codigo Ejecutable
2097
+
2098
+ ```javascript
2099
+ /**
2100
+ * ContractSessionValidator
2101
+ * Valida integridad del estado de sesion ELSABRO
2102
+ */
2103
+ class ContractSessionValidator {
2104
+ constructor() {
2105
+ // Schema de validacion para state.json
2106
+ this.stateSchema = {
2107
+ required: ['version', 'updated_at'],
2108
+ optional: ['created_at', 'current_flow', 'context', 'history', 'pending_tasks', 'blocked_on', 'errors', 'suggested_next'],
2109
+ types: {
2110
+ version: 'string',
2111
+ created_at: 'string',
2112
+ updated_at: 'string',
2113
+ current_flow: ['object', 'null'],
2114
+ context: 'object',
2115
+ history: 'array',
2116
+ pending_tasks: 'array',
2117
+ blocked_on: ['string', 'null'],
2118
+ errors: 'object',
2119
+ suggested_next: ['string', 'null']
2120
+ },
2121
+ nested: {
2122
+ current_flow: {
2123
+ required: ['command', 'phase', 'started_at'],
2124
+ types: {
2125
+ command: 'string',
2126
+ phase: 'string',
2127
+ started_at: 'string'
2128
+ }
2129
+ },
2130
+ context: {
2131
+ optional: ['project_type', 'tech_stack', 'current_feature', 'plan_file'],
2132
+ types: {
2133
+ project_type: 'string',
2134
+ tech_stack: 'array',
2135
+ current_feature: 'string',
2136
+ plan_file: 'string'
2137
+ }
2138
+ }
2139
+ }
2140
+ };
2141
+
2142
+ // Fases validas por comando
2143
+ this.validPhases = {
2144
+ start: ['initializing', 'detecting_context', 'greeting', 'transitioning', 'done'],
2145
+ plan: ['initializing', 'exploring', 'researching', 'planning', 'validating', 'done'],
2146
+ execute: ['initializing', 'exploring', 'executing_wave_1', 'executing_wave_2', 'executing_wave_3', 'verifying', 'done'],
2147
+ 'verify-work': ['initializing', 'verifying', 'aggregating', 'done'],
2148
+ debug: ['initializing', 'diagnosing', 'fixing', 'verifying', 'done'],
2149
+ quick: ['initializing', 'executing', 'done']
2150
+ };
2151
+
2152
+ // Path al estado
2153
+ this.statePath = '.elsabro/state.json';
2154
+ this.backupPath = '.elsabro/state.backup.json';
2155
+ }
2156
+
2157
+ /**
2158
+ * Valida el estado completo
2159
+ * @param {Object|string} state - Estado a validar (objeto o JSON string)
2160
+ * @returns {Object} Resultado de validacion
2161
+ */
2162
+ validate(state) {
2163
+ const errors = [];
2164
+
2165
+ // Parsear si es string
2166
+ if (typeof state === 'string') {
2167
+ try {
2168
+ state = JSON.parse(state);
2169
+ } catch (e) {
2170
+ return {
2171
+ valid: false,
2172
+ error: {
2173
+ code: 'JSON_PARSE_ERROR',
2174
+ severity: 'CRITICAL',
2175
+ message: 'El estado no es JSON valido',
2176
+ parseError: e.message
2177
+ }
2178
+ };
2179
+ }
2180
+ }
2181
+
2182
+ // Validar tipo base
2183
+ if (typeof state !== 'object' || state === null) {
2184
+ return {
2185
+ valid: false,
2186
+ error: {
2187
+ code: 'INVALID_STATE_TYPE',
2188
+ severity: 'CRITICAL',
2189
+ message: 'El estado debe ser un objeto'
2190
+ }
2191
+ };
2192
+ }
2193
+
2194
+ // Validar campos requeridos
2195
+ for (const field of this.stateSchema.required) {
2196
+ if (!(field in state)) {
2197
+ errors.push({
2198
+ field: field,
2199
+ type: 'MISSING_REQUIRED',
2200
+ message: `Campo requerido '${field}' faltante`
2201
+ });
2202
+ }
2203
+ }
2204
+
2205
+ // Validar tipos
2206
+ for (const [field, expectedType] of Object.entries(this.stateSchema.types)) {
2207
+ if (field in state) {
2208
+ const typeError = this.validateType(state[field], expectedType, field);
2209
+ if (typeError) {
2210
+ errors.push(typeError);
2211
+ }
2212
+ }
2213
+ }
2214
+
2215
+ // Validar current_flow si existe
2216
+ if (state.current_flow !== null && state.current_flow !== undefined) {
2217
+ const flowErrors = this.validateCurrentFlow(state.current_flow);
2218
+ errors.push(...flowErrors);
2219
+ }
2220
+
2221
+ // Validar historial si existe
2222
+ if (state.history) {
2223
+ const historyErrors = this.validateHistory(state.history);
2224
+ errors.push(...historyErrors);
2225
+ }
2226
+
2227
+ // Validar timestamps
2228
+ const timestampErrors = this.validateTimestamps(state);
2229
+ errors.push(...timestampErrors);
2230
+
2231
+ if (errors.length > 0) {
2232
+ return {
2233
+ valid: false,
2234
+ errors: errors,
2235
+ error: {
2236
+ code: 'SESSION_VALIDATION_FAILED',
2237
+ severity: errors.some(e => e.type === 'MISSING_REQUIRED') ? 'CRITICAL' : 'HIGH',
2238
+ message: `${errors.length} errores de validacion encontrados`,
2239
+ details: errors
2240
+ }
2241
+ };
2242
+ }
2243
+
2244
+ return {
2245
+ valid: true,
2246
+ state: state
2247
+ };
2248
+ }
2249
+
2250
+ /**
2251
+ * Valida el tipo de un valor
2252
+ */
2253
+ validateType(value, expectedType, field) {
2254
+ const types = Array.isArray(expectedType) ? expectedType : [expectedType];
2255
+
2256
+ for (const type of types) {
2257
+ if (type === 'null' && value === null) return null;
2258
+ if (type === 'array' && Array.isArray(value)) return null;
2259
+ if (type === 'object' && typeof value === 'object' && value !== null && !Array.isArray(value)) return null;
2260
+ if (typeof value === type) return null;
2261
+ }
2262
+
2263
+ return {
2264
+ field: field,
2265
+ type: 'TYPE_MISMATCH',
2266
+ message: `Campo '${field}' tiene tipo invalido. Esperado: ${types.join(' | ')}, recibido: ${typeof value}`
2267
+ };
2268
+ }
2269
+
2270
+ /**
2271
+ * Valida current_flow
2272
+ */
2273
+ validateCurrentFlow(flow) {
2274
+ const errors = [];
2275
+ const schema = this.stateSchema.nested.current_flow;
2276
+
2277
+ // Validar campos requeridos
2278
+ for (const field of schema.required) {
2279
+ if (!(field in flow)) {
2280
+ errors.push({
2281
+ field: `current_flow.${field}`,
2282
+ type: 'MISSING_REQUIRED',
2283
+ message: `Campo requerido 'current_flow.${field}' faltante`
2284
+ });
2285
+ }
2286
+ }
2287
+
2288
+ // Validar fase valida para el comando
2289
+ if (flow.command && flow.phase) {
2290
+ const validPhases = this.validPhases[flow.command];
2291
+ if (validPhases && !validPhases.includes(flow.phase)) {
2292
+ errors.push({
2293
+ field: 'current_flow.phase',
2294
+ type: 'INVALID_PHASE',
2295
+ message: `Fase '${flow.phase}' invalida para comando '${flow.command}'. Validas: ${validPhases.join(', ')}`
2296
+ });
2297
+ }
2298
+ }
2299
+
2300
+ return errors;
2301
+ }
2302
+
2303
+ /**
2304
+ * Valida el historial
2305
+ */
2306
+ validateHistory(history) {
2307
+ const errors = [];
2308
+
2309
+ if (!Array.isArray(history)) {
2310
+ errors.push({
2311
+ field: 'history',
2312
+ type: 'TYPE_MISMATCH',
2313
+ message: 'El historial debe ser un array'
2314
+ });
2315
+ return errors;
2316
+ }
2317
+
2318
+ for (let i = 0; i < history.length; i++) {
2319
+ const entry = history[i];
2320
+
2321
+ if (!entry.command) {
2322
+ errors.push({
2323
+ field: `history[${i}].command`,
2324
+ type: 'MISSING_REQUIRED',
2325
+ message: `Entrada de historial ${i} sin campo 'command'`
2326
+ });
2327
+ }
2328
+
2329
+ if (!entry.completed_at) {
2330
+ errors.push({
2331
+ field: `history[${i}].completed_at`,
2332
+ type: 'MISSING_REQUIRED',
2333
+ message: `Entrada de historial ${i} sin campo 'completed_at'`
2334
+ });
2335
+ }
2336
+ }
2337
+
2338
+ return errors;
2339
+ }
2340
+
2341
+ /**
2342
+ * Valida timestamps
2343
+ */
2344
+ validateTimestamps(state) {
2345
+ const errors = [];
2346
+
2347
+ const timestamps = ['created_at', 'updated_at'];
2348
+
2349
+ for (const field of timestamps) {
2350
+ if (state[field]) {
2351
+ const date = new Date(state[field]);
2352
+ if (isNaN(date.getTime())) {
2353
+ errors.push({
2354
+ field: field,
2355
+ type: 'INVALID_TIMESTAMP',
2356
+ message: `Timestamp '${field}' no es una fecha valida: ${state[field]}`
2357
+ });
2358
+ }
2359
+ }
2360
+ }
2361
+
2362
+ // Verificar que updated_at >= created_at
2363
+ if (state.created_at && state.updated_at) {
2364
+ const created = new Date(state.created_at);
2365
+ const updated = new Date(state.updated_at);
2366
+
2367
+ if (updated < created) {
2368
+ errors.push({
2369
+ field: 'timestamps',
2370
+ type: 'INVALID_TIMESTAMP_ORDER',
2371
+ message: 'updated_at es anterior a created_at'
2372
+ });
2373
+ }
2374
+ }
2375
+
2376
+ return errors;
2377
+ }
2378
+
2379
+ /**
2380
+ * Intenta reparar un estado corrupto
2381
+ * @param {Object} corruptState - Estado corrupto
2382
+ * @returns {Object} Estado reparado o error
2383
+ */
2384
+ attemptRepair(corruptState) {
2385
+ const repaired = {};
2386
+ const repairs = [];
2387
+
2388
+ // Asegurar version
2389
+ repaired.version = corruptState?.version || '1.0.0';
2390
+ if (!corruptState?.version) {
2391
+ repairs.push('Agregado version: 1.0.0');
2392
+ }
2393
+
2394
+ // Asegurar timestamps
2395
+ const now = new Date().toISOString();
2396
+ repaired.created_at = corruptState?.created_at || now;
2397
+ repaired.updated_at = now;
2398
+ if (!corruptState?.created_at) {
2399
+ repairs.push('Agregado created_at');
2400
+ }
2401
+ repairs.push('Actualizado updated_at');
2402
+
2403
+ // Preservar o inicializar current_flow
2404
+ repaired.current_flow = null; // Limpiar flujo corrupto
2405
+
2406
+ // Preservar contexto si es valido
2407
+ if (corruptState?.context && typeof corruptState.context === 'object') {
2408
+ repaired.context = corruptState.context;
2409
+ } else {
2410
+ repaired.context = {};
2411
+ repairs.push('Reinicializado context');
2412
+ }
2413
+
2414
+ // Preservar historial si es valido
2415
+ if (Array.isArray(corruptState?.history)) {
2416
+ repaired.history = corruptState.history.filter(h => h.command && h.completed_at);
2417
+ if (repaired.history.length < corruptState.history.length) {
2418
+ repairs.push(`Filtradas ${corruptState.history.length - repaired.history.length} entradas de historial invalidas`);
2419
+ }
2420
+ } else {
2421
+ repaired.history = [];
2422
+ repairs.push('Reinicializado history');
2423
+ }
2424
+
2425
+ // Inicializar campos opcionales
2426
+ repaired.pending_tasks = [];
2427
+ repaired.blocked_on = null;
2428
+ repaired.errors = { count: 0 };
2429
+ repaired.suggested_next = null;
2430
+
2431
+ // Validar estado reparado
2432
+ const validation = this.validate(repaired);
2433
+
2434
+ if (validation.valid) {
2435
+ return {
2436
+ success: true,
2437
+ state: repaired,
2438
+ repairs: repairs
2439
+ };
2440
+ }
2441
+
2442
+ return {
2443
+ success: false,
2444
+ error: {
2445
+ code: 'REPAIR_FAILED',
2446
+ severity: 'CRITICAL',
2447
+ message: 'No se pudo reparar el estado',
2448
+ validationErrors: validation.errors
2449
+ }
2450
+ };
2451
+ }
2452
+
2453
+ /**
2454
+ * Crea un estado inicial valido
2455
+ */
2456
+ createInitialState() {
2457
+ const now = new Date().toISOString();
2458
+
2459
+ return {
2460
+ version: '1.0.0',
2461
+ created_at: now,
2462
+ updated_at: now,
2463
+ current_flow: null,
2464
+ context: {},
2465
+ history: [],
2466
+ pending_tasks: [],
2467
+ blocked_on: null,
2468
+ errors: { count: 0 },
2469
+ suggested_next: null
2470
+ };
2471
+ }
2472
+
2473
+ /**
2474
+ * Genera display de error de validacion
2475
+ */
2476
+ generateValidationErrorDisplay(validationResult) {
2477
+ if (validationResult.valid) {
2478
+ return '[OK] Estado valido';
2479
+ }
2480
+
2481
+ const error = validationResult.error;
2482
+ const details = validationResult.errors || [];
2483
+
2484
+ const lines = [
2485
+ '+======================================================+',
2486
+ `| ERROR: ${error.code}`.padEnd(55) + '|',
2487
+ `| Severity: ${error.severity}`.padEnd(55) + '|',
2488
+ '+======================================================+',
2489
+ '|'.padEnd(55) + '|',
2490
+ `| ${error.message}`.padEnd(55) + '|',
2491
+ '|'.padEnd(55) + '|'
2492
+ ];
2493
+
2494
+ if (details.length > 0) {
2495
+ lines.push('| Detalles:'.padEnd(55) + '|');
2496
+ for (const detail of details.slice(0, 5)) { // Mostrar max 5
2497
+ lines.push(`| - ${detail.field}: ${detail.type}`.padEnd(55) + '|');
2498
+ }
2499
+ if (details.length > 5) {
2500
+ lines.push(`| ... y ${details.length - 5} errores mas`.padEnd(55) + '|');
2501
+ }
2502
+ }
2503
+
2504
+ lines.push('|'.padEnd(55) + '|');
2505
+ lines.push('+------------------------------------------------------+');
2506
+ lines.push('| Opciones de recuperacion:'.padEnd(55) + '|');
2507
+ lines.push('| [b] Restaurar desde backup'.padEnd(55) + '|');
2508
+ lines.push('| [r] Reinicializar estado (perdera historial)'.padEnd(55) + '|');
2509
+ lines.push('| [m] Editar manualmente'.padEnd(55) + '|');
2510
+ lines.push('+======================================================+');
2511
+
2512
+ return lines.join('\n');
2513
+ }
2514
+
2515
+ /**
2516
+ * Carga y valida el estado de sesion desde el archivo
2517
+ * @returns {Object} Resultado con session y validacion
2518
+ */
2519
+ async load() {
2520
+ try {
2521
+ const content = await Read(this.statePath);
2522
+ const session = JSON.parse(content);
2523
+ const validation = await this.validate(session);
2524
+ return { success: true, session, validation };
2525
+ } catch (error) {
2526
+ return {
2527
+ success: false,
2528
+ reason: error.code === 'ENOENT' ? 'SESSION_NOT_FOUND' : 'SESSION_CORRUPTED',
2529
+ error: error.message
2530
+ };
2531
+ }
2532
+ }
2533
+
2534
+ /**
2535
+ * Valida y guarda el estado de sesion
2536
+ * @param {Object} session - Estado de sesion a guardar
2537
+ * @returns {Object} Resultado de la operacion
2538
+ */
2539
+ async save(session) {
2540
+ const validation = await this.validate(session);
2541
+ if (!validation.valid) {
2542
+ return { success: false, reason: "SESSION_INVALID", errors: validation.errors };
2543
+ }
2544
+ session.updatedAt = new Date().toISOString();
2545
+ await Write(this.statePath, JSON.stringify(session, null, 2));
2546
+ return { success: true, savedAt: session.updatedAt };
2547
+ }
2548
+ }
2549
+
2550
+ // Ejemplo de uso
2551
+ const sessionValidator = new ContractSessionValidator();
2552
+
2553
+ // Estado valido
2554
+ const validState = {
2555
+ version: '1.0.0',
2556
+ created_at: '2024-01-20T10:00:00Z',
2557
+ updated_at: '2024-01-20T15:30:00Z',
2558
+ current_flow: {
2559
+ command: 'execute',
2560
+ phase: 'executing_wave_1',
2561
+ started_at: '2024-01-20T15:00:00Z'
2562
+ },
2563
+ context: {
2564
+ project_type: 'brownfield',
2565
+ tech_stack: ['nextjs', 'prisma']
2566
+ },
2567
+ history: [
2568
+ { command: 'plan', completed_at: '2024-01-20T14:50:00Z', result: 'created' }
2569
+ ]
2570
+ };
2571
+
2572
+ const validResult = sessionValidator.validate(validState);
2573
+ console.log('Estado valido:', validResult.valid);
2574
+
2575
+ // Estado corrupto
2576
+ const corruptState = {
2577
+ version: '1.0.0',
2578
+ // Falta updated_at
2579
+ current_flow: {
2580
+ command: 'execute',
2581
+ phase: 'invalid_phase' // Fase invalida
2582
+ // Falta started_at
2583
+ }
2584
+ };
2585
+
2586
+ const corruptResult = sessionValidator.validate(corruptState);
2587
+ if (!corruptResult.valid) {
2588
+ console.log(sessionValidator.generateValidationErrorDisplay(corruptResult));
2589
+
2590
+ // Intentar reparar
2591
+ const repairResult = sessionValidator.attemptRepair(corruptState);
2592
+ if (repairResult.success) {
2593
+ console.log('Estado reparado:', repairResult.state);
2594
+ console.log('Reparaciones:', repairResult.repairs);
2595
+ }
2596
+ }
2597
+ ```
2598
+
2599
+ ---
2600
+
2601
+ ## Integracion
2602
+
2603
+ ### Flujo de Ejecucion Completo
2604
+
2605
+ ```
2606
+ +============================================================================+
2607
+ | FLUJO DE CONTRATOS EN EJECUCION |
2608
+ +============================================================================+
2609
+ | |
2610
+ | 1. Usuario ejecuta /elsabro:execute |
2611
+ | |
2612
+ | 2. [ContractSessionValidator] |
2613
+ | Validar .elsabro/state.json |
2614
+ | -> OK: Continuar |
2615
+ | -> ERROR: Reparar o abortar |
2616
+ | |
2617
+ | 3. [ContractRegistryValidator] |
2618
+ | Validar agentes necesarios existen |
2619
+ | -> OK: Continuar |
2620
+ | -> ERROR: AGENT_NOT_FOUND - abortar |
2621
+ | |
2622
+ | 4. [ContractTaskLifecycle] |
2623
+ | Crear Tasks para cada agente |
2624
+ | INIT -> IN_PROGRESS |
2625
+ | |
2626
+ | 5. [ContractTimeoutHandler] |
2627
+ | Iniciar tracking de timeout |
2628
+ | Health checks cada 5s |
2629
+ | |
2630
+ | 6. EJECUTAR AGENTES EN PARALELO |
2631
+ | Task(haiku) | Task(haiku) | Task(haiku) |
2632
+ | |
2633
+ | 7. [ContractRetryPolicy] (por cada agente) |
2634
+ | Si falla: retry con backoff |
2635
+ | 1s -> 2s -> 4s |
2636
+ | |
2637
+ | 8. [ContractSeverityClassifier] |
2638
+ | Clasificar errores: CRITICAL/HIGH/MEDIUM/LOW |
2639
+ | |
2640
+ | 9. [ContractErrorAggregator] |
2641
+ | Colectar resultados |
2642
+ | Aplicar politica (quorum) |
2643
+ | -> QUORUM MET: Continuar |
2644
+ | -> QUORUM NOT MET: Abortar |
2645
+ | |
2646
+ | 10. [ContractTaskLifecycle] |
2647
+ | Marcar tasks: COMPLETED o ERROR |
2648
+ | |
2649
+ | 11. [ContractSessionValidator] |
2650
+ | Actualizar .elsabro/state.json |
2651
+ | |
2652
+ +============================================================================+
2653
+ ```
2654
+
2655
+ ### Dependencias entre Contratos
2656
+
2657
+ ```
2658
+ ContractSessionValidator
2659
+ |
2660
+ v (estado valido)
2661
+ ContractRegistryValidator
2662
+ |
2663
+ v (agentes validos)
2664
+ ContractTaskLifecycle
2665
+ |
2666
+ v (tasks creadas)
2667
+ ContractTimeoutHandler
2668
+ |
2669
+ v (tracking activo)
2670
+ |
2671
+ +--> ContractRetryPolicy (por agente)
2672
+ | |
2673
+ | v (resultado o retry exhausted)
2674
+ |
2675
+ +--> ContractSeverityClassifier
2676
+ |
2677
+ v (errores clasificados)
2678
+ ContractErrorAggregator
2679
+ |
2680
+ v (decision de continuar/abortar)
2681
+ ContractTaskLifecycle
2682
+ |
2683
+ v (tasks actualizadas)
2684
+ ContractSessionValidator
2685
+ |
2686
+ v (estado persistido)
2687
+ ```
2688
+
2689
+ ### Ejemplo Completo de Integracion
2690
+
2691
+ ```javascript
2692
+ /**
2693
+ * Ejemplo completo de integracion de todos los contratos
2694
+ */
2695
+ class ElsabroErrorContracts {
2696
+ constructor() {
2697
+ this.sessionValidator = new ContractSessionValidator();
2698
+ this.registryValidator = new ContractRegistryValidator();
2699
+ this.taskLifecycle = new ContractTaskLifecycle();
2700
+ this.timeoutHandler = new ContractTimeoutHandler();
2701
+ this.retryPolicy = new ContractRetryPolicy();
2702
+ this.severityClassifier = new ContractSeverityClassifier();
2703
+ this.errorAggregator = new ContractErrorAggregator();
2704
+ }
2705
+
2706
+ /**
2707
+ * Ejecuta una operacion paralela con todos los contratos
2708
+ */
2709
+ async executeParallelOperation(agents, operations, policy = 'quorum') {
2710
+ // 1. Validar sesion
2711
+ const sessionResult = this.sessionValidator.validate(
2712
+ this.readState()
2713
+ );
2714
+
2715
+ if (!sessionResult.valid) {
2716
+ const repair = this.sessionValidator.attemptRepair(sessionResult);
2717
+ if (!repair.success) {
2718
+ return this.handleCriticalError(sessionResult.error);
2719
+ }
2720
+ }
2721
+
2722
+ // 2. Validar agentes
2723
+ const registryResult = this.registryValidator.validateAgents(agents);
2724
+ if (!registryResult.valid) {
2725
+ return this.handleCriticalError(registryResult.aggregatedError);
2726
+ }
2727
+
2728
+ // 3. Crear tasks
2729
+ const tasks = [];
2730
+ for (let i = 0; i < agents.length; i++) {
2731
+ const taskResult = this.taskLifecycle.registerTask(
2732
+ `task-${Date.now()}-${i}`,
2733
+ { agent: agents[i] }
2734
+ );
2735
+ tasks.push(taskResult.task);
2736
+ this.taskLifecycle.transition(taskResult.task.id, 'in_progress');
2737
+ }
2738
+
2739
+ // 4. Configurar agregador
2740
+ this.errorAggregator.reset();
2741
+ this.errorAggregator.setPolicy(policy);
2742
+
2743
+ // 5. Ejecutar operaciones en paralelo
2744
+ const promises = tasks.map(async (task, index) => {
2745
+ // Iniciar timeout tracking
2746
+ this.timeoutHandler.startTracking(task.id);
2747
+
2748
+ try {
2749
+ // Ejecutar con retry
2750
+ const result = await this.retryPolicy.executeWithRetry(
2751
+ () => operations[index]()
2752
+ );
2753
+
2754
+ // Marcar completado
2755
+ this.timeoutHandler.markComplete(task.id, result);
2756
+ this.taskLifecycle.transition(task.id, 'completed');
2757
+
2758
+ // Agregar resultado
2759
+ this.errorAggregator.addResult({
2760
+ taskId: task.id,
2761
+ agent: agents[index],
2762
+ status: result.success ? 'success' : 'error',
2763
+ duration: result.totalDuration,
2764
+ error: result.error
2765
+ });
2766
+
2767
+ return result;
2768
+
2769
+ } catch (error) {
2770
+ // Clasificar error
2771
+ const classified = this.severityClassifier.classify(error);
2772
+
2773
+ // Marcar error
2774
+ this.taskLifecycle.transition(task.id, 'error');
2775
+
2776
+ // Agregar al agregador
2777
+ this.errorAggregator.addResult({
2778
+ taskId: task.id,
2779
+ agent: agents[index],
2780
+ status: 'error',
2781
+ error: classified
2782
+ });
2783
+
2784
+ return { success: false, error: classified };
2785
+ }
2786
+ });
2787
+
2788
+ // 6. Esperar todos los resultados
2789
+ const results = await Promise.all(promises);
2790
+
2791
+ // 7. Evaluar politica
2792
+ const evaluation = this.errorAggregator.evaluatePolicy();
2793
+
2794
+ // 8. Generar reporte
2795
+ const report = this.errorAggregator.generateReport();
2796
+ console.log(report);
2797
+
2798
+ // 9. Persistir estado
2799
+ this.persistState(evaluation);
2800
+
2801
+ return {
2802
+ success: evaluation.shouldContinue,
2803
+ results: results,
2804
+ evaluation: evaluation,
2805
+ report: report
2806
+ };
2807
+ }
2808
+
2809
+ handleCriticalError(error) {
2810
+ const display = this.severityClassifier.generateDisplay(
2811
+ this.severityClassifier.classify(error)
2812
+ );
2813
+ console.log(display);
2814
+
2815
+ return {
2816
+ success: false,
2817
+ error: error,
2818
+ aborted: true
2819
+ };
2820
+ }
2821
+
2822
+ readState() {
2823
+ // En implementacion real, leer de .elsabro/state.json
2824
+ return this.sessionValidator.createInitialState();
2825
+ }
2826
+
2827
+ persistState(evaluation) {
2828
+ // En implementacion real, escribir a .elsabro/state.json
2829
+ console.log('Estado persistido:', evaluation);
2830
+ }
2831
+ }
2832
+
2833
+ // Uso
2834
+ const contracts = new ElsabroErrorContracts();
2835
+
2836
+ // Simular operacion paralela
2837
+ contracts.executeParallelOperation(
2838
+ ['elsabro-executor', 'feature-dev:code-explorer', 'elsabro-verifier'],
2839
+ [
2840
+ async () => ({ data: 'result1' }),
2841
+ async () => ({ data: 'result2' }),
2842
+ async () => { throw { code: 'TIMEOUT' }; }
2843
+ ],
2844
+ 'quorum'
2845
+ ).then(result => {
2846
+ console.log('Resultado final:', result.success ? 'EXITO' : 'FALLO');
2847
+ });
2848
+ ```
2849
+
2850
+ ---
2851
+
2852
+ ## Escenarios de Fallo Exhaustivos
2853
+
2854
+ ### Escenario 1: Estado Corrupto al Inicio
2855
+
2856
+ ```
2857
+ SITUACION:
2858
+ Usuario ejecuta /elsabro:execute
2859
+ .elsabro/state.json tiene JSON invalido
2860
+
2861
+ CONTRATOS ACTIVADOS:
2862
+ 1. ContractSessionValidator -> JSON_PARSE_ERROR
2863
+
2864
+ FLUJO:
2865
+ 1. Detectar corrupcion
2866
+ 2. Intentar leer backup
2867
+ 3. Si backup existe -> restaurar
2868
+ 4. Si no hay backup -> reinicializar
2869
+ 5. Notificar al usuario
2870
+ 6. Continuar con estado limpio
2871
+
2872
+ DISPLAY:
2873
+ +======================================================+
2874
+ | ERROR: SESSION_CORRUPTED |
2875
+ | Severity: CRITICAL |
2876
+ +======================================================+
2877
+ | El archivo .elsabro/state.json esta corrupto |
2878
+ | Restaurando desde backup... |
2879
+ +======================================================+
2880
+ ```
2881
+
2882
+ ### Escenario 2: Agente No Encontrado
2883
+
2884
+ ```
2885
+ SITUACION:
2886
+ Plan referencia agente "elsabro-nonexistent"
2887
+
2888
+ CONTRATOS ACTIVADOS:
2889
+ 1. ContractRegistryValidator -> AGENT_NOT_FOUND
2890
+
2891
+ FLUJO:
2892
+ 1. Validar agentes antes de lanzar
2893
+ 2. Detectar agente invalido
2894
+ 3. Buscar sugerencias similares
2895
+ 4. Abortar operacion
2896
+ 5. Mostrar alternativas al usuario
2897
+
2898
+ DISPLAY:
2899
+ +======================================================+
2900
+ | ERROR: AGENT_NOT_FOUND |
2901
+ | Severity: CRITICAL |
2902
+ +======================================================+
2903
+ | El agente "elsabro-nonexistent" no existe |
2904
+ | |
2905
+ | Agentes similares: |
2906
+ | - elsabro-executor |
2907
+ | - elsabro-verifier |
2908
+ +======================================================+
2909
+ ```
2910
+
2911
+ ### Escenario 3: Timeout en Agente
2912
+
2913
+ ```
2914
+ SITUACION:
2915
+ Agente tarda mas de 30 minutos
2916
+
2917
+ CONTRATOS ACTIVADOS:
2918
+ 1. ContractTimeoutHandler -> TASK_TIMEOUT
2919
+ 2. ContractSeverityClassifier -> HIGH
2920
+ 3. ContractErrorAggregator -> Evaluar quorum
2921
+
2922
+ FLUJO:
2923
+ 1. Health check detecta timeout inminente
2924
+ 2. Warning al usuario
2925
+ 3. Timeout alcanzado
2926
+ 4. Terminacion graceful
2927
+ 5. Clasificar como HIGH
2928
+ 6. Agregar al agregador
2929
+ 7. Evaluar si quorum se mantiene
2930
+
2931
+ DISPLAY:
2932
+ +======================================================+
2933
+ | WARNING: TASK_TIMEOUT |
2934
+ | Severity: HIGH |
2935
+ +======================================================+
2936
+ | La tarea "explore-codebase" excedio el timeout |
2937
+ | |
2938
+ | Opciones: |
2939
+ | [e] Extender timeout (+15 min) |
2940
+ | [t] Terminar y usar output parcial |
2941
+ +======================================================+
2942
+ ```
2943
+
2944
+ ### Escenario 4: Retry Exhausted
2945
+
2946
+ ```
2947
+ SITUACION:
2948
+ Operacion falla 3 veces consecutivas
2949
+
2950
+ CONTRATOS ACTIVADOS:
2951
+ 1. ContractRetryPolicy -> RETRY_EXHAUSTED
2952
+ 2. ContractSeverityClassifier -> HIGH
2953
+ 3. ContractErrorAggregator -> Agregar error
2954
+
2955
+ FLUJO:
2956
+ 1. Intento 1: Fallo (1s delay)
2957
+ 2. Intento 2: Fallo (2s delay)
2958
+ 3. Intento 3: Fallo
2959
+ 4. Crear error RETRY_EXHAUSTED
2960
+ 5. Clasificar severidad
2961
+ 6. Escalar a usuario
2962
+
2963
+ DISPLAY:
2964
+ +------------------------------------------------------+
2965
+ | RETRY: npm test |
2966
+ +------------------------------------------------------+
2967
+ | Attempt 1/3: FAILED (timeout) |
2968
+ | Waiting 1s... |
2969
+ | Attempt 2/3: FAILED (exit code 1) |
2970
+ | Waiting 2s... |
2971
+ | Attempt 3/3: FAILED (exit code 1) |
2972
+ | |
2973
+ | X All attempts exhausted |
2974
+ +------------------------------------------------------+
2975
+ ```
2976
+
2977
+ ### Escenario 5: Quorum No Alcanzado
2978
+
2979
+ ```
2980
+ SITUACION:
2981
+ 3 de 4 agentes fallan
2982
+
2983
+ CONTRATOS ACTIVADOS:
2984
+ 1. ContractErrorAggregator -> QUORUM_NOT_MET
2985
+ 2. ContractSeverityClassifier -> HIGH
2986
+
2987
+ FLUJO:
2988
+ 1. Esperar todos los agentes
2989
+ 2. Contar exitos: 1/4 (25%)
2990
+ 3. Umbral quorum: 50%
2991
+ 4. Quorum NO alcanzado
2992
+ 5. Abortar operacion
2993
+ 6. Reportar fallos
2994
+
2995
+ DISPLAY:
2996
+ +======================================================+
2997
+ | PARALLEL EXECUTION COMPLETE |
2998
+ +======================================================+
2999
+ | Policy: quorum |
3000
+ | Total: 4 agents |
3001
+ | |
3002
+ | OK Agent 1 (code-reviewer): SUCCESS |
3003
+ | X Agent 2 (typescript-pro): FAILED (timeout) |
3004
+ | X Agent 3 (silent-failure): FAILED (error) |
3005
+ | X Agent 4 (test-analyzer): FAILED (timeout) |
3006
+ | |
3007
+ | Result: 1/4 (25%) - QUORUM NOT MET |
3008
+ | Status: STOPPING |
3009
+ +======================================================+
3010
+ ```
3011
+
3012
+ ### Escenario 6: Transicion de Estado Invalida
3013
+
3014
+ ```
3015
+ SITUACION:
3016
+ Intento de marcar task como COMPLETED sin pasar por IN_PROGRESS
3017
+
3018
+ CONTRATOS ACTIVADOS:
3019
+ 1. ContractTaskLifecycle -> INVALID_STATE_TRANSITION
3020
+
3021
+ FLUJO:
3022
+ 1. Detectar transicion invalida
3023
+ 2. Rechazar operacion
3024
+ 3. Sugerir transicion correcta
3025
+ 4. Mantener estado anterior
3026
+
3027
+ DISPLAY:
3028
+ +======================================================+
3029
+ | ERROR: INVALID_STATE_TRANSITION |
3030
+ | Severity: HIGH |
3031
+ +======================================================+
3032
+ | Transicion invalida: INIT -> COMPLETED |
3033
+ | |
3034
+ | Flujo correcto: |
3035
+ | INIT -> IN_PROGRESS -> COMPLETED |
3036
+ +======================================================+
3037
+ ```
3038
+
3039
+ ### Escenario 7: Multiples Errores Criticos Simultaneos
3040
+
3041
+ ```
3042
+ SITUACION:
3043
+ Estado corrupto + Agente critico falla + Timeout
3044
+
3045
+ CONTRATOS ACTIVADOS:
3046
+ Todos los contratos en cascada
3047
+
3048
+ FLUJO:
3049
+ 1. SessionValidator detecta corrupcion -> reparar
3050
+ 2. Continuar con estado reparado
3051
+ 3. Agente critico falla
3052
+ 4. CriticalPath policy activada
3053
+ 5. Abortar toda la operacion
3054
+ 6. Persistir estado de error
3055
+ 7. Reportar al usuario
3056
+
3057
+ DISPLAY:
3058
+ +======================================================+
3059
+ | MULTIPLE CRITICAL ERRORS |
3060
+ | Severity: CRITICAL |
3061
+ +======================================================+
3062
+ | |
3063
+ | Errores encontrados: |
3064
+ | 1. SESSION_CORRUPTED (reparado) |
3065
+ | 2. CRITICAL_AGENT_FAILED: elsabro-executor |
3066
+ | 3. TASK_TIMEOUT: explore-codebase |
3067
+ | |
3068
+ | La operacion fue abortada por errores criticos |
3069
+ | |
3070
+ +------------------------------------------------------+
3071
+ | Estado guardado para recuperacion: |
3072
+ | -> /elsabro:resume-work para continuar |
3073
+ +======================================================+
3074
+ ```
3075
+
3076
+ ---
3077
+
3078
+ ## Checklist de Implementacion
3079
+
3080
+ Para implementar los contratos en un comando ELSABRO:
3081
+
3082
+ - [ ] Importar/instanciar ContractSessionValidator
3083
+ - [ ] Validar estado al inicio del comando
3084
+ - [ ] Importar/instanciar ContractRegistryValidator
3085
+ - [ ] Validar agentes antes de lanzar
3086
+ - [ ] Importar/instanciar ContractTaskLifecycle
3087
+ - [ ] Crear Tasks para cada agente
3088
+ - [ ] Respetar transiciones INIT -> ACTIVE -> COMPLETE
3089
+ - [ ] Importar/instanciar ContractTimeoutHandler
3090
+ - [ ] Configurar timeout apropiado
3091
+ - [ ] Iniciar tracking para cada tarea
3092
+ - [ ] Importar/instanciar ContractRetryPolicy
3093
+ - [ ] Configurar reintentos para operaciones fallibles
3094
+ - [ ] Importar/instanciar ContractSeverityClassifier
3095
+ - [ ] Clasificar todos los errores
3096
+ - [ ] Generar displays apropiados
3097
+ - [ ] Importar/instanciar ContractErrorAggregator
3098
+ - [ ] Configurar politica (quorum, fail_fast, etc)
3099
+ - [ ] Agregar todos los resultados
3100
+ - [ ] Evaluar politica para decidir continuar/abortar
3101
+ - [ ] Persistir estado al finalizar
3102
+ - [ ] Generar reporte final