elsabro 7.3.2 → 7.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -23
- package/agents/elsabro-executor.md +32 -0
- package/agents/elsabro-orchestrator.md +39 -0
- package/agents/elsabro-qa.md +37 -0
- package/agents/elsabro-verifier.md +37 -0
- package/bin/install.js +71 -0
- package/commands/elsabro/debug.md +54 -16
- package/commands/elsabro/execute.md +294 -7
- package/commands/elsabro/quick.md +32 -9
- package/flow-engine/src/graph.js +16 -4
- package/flow-engine/src/index.js +10 -0
- package/flow-engine/src/template.js +6 -5
- package/flow-engine/tests/cli.test.js +3 -2
- package/flow-engine/tests/execute-dispatcher.test.js +2 -1
- package/flow-engine/tests/graph.test.js +27 -26
- package/flow-engine/tests/integration.test.js +30 -34
- package/flows/development-flow.json +109 -12
- package/hooks/auto-sync-check.sh +238 -0
- package/hooks/check-review-skills.sh +45 -0
- package/hooks/hooks-config-updated.json +68 -10
- package/hooks/review-gate.sh +90 -0
- package/hooks/skill-gate.sh +107 -0
- package/package.json +1 -1
- package/references/enforcement-rules.md +43 -16
|
@@ -34,6 +34,78 @@ vive en el engine y en `flows/development-flow.json`. Este archivo solo define e
|
|
|
34
34
|
Leer `.elsabro/state.json` siguiendo el protocolo de @references/state-sync.md.
|
|
35
35
|
Verificar flujo en progreso. Actualizar phase a "stepping".
|
|
36
36
|
|
|
37
|
+
### Skill Discovery Gate (OBLIGATORIO)
|
|
38
|
+
|
|
39
|
+
Antes de cualquier otra accion, verificar que skill discovery fue ejecutado:
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
// GATE: Verificar skill discovery antes de ejecutar
|
|
43
|
+
const skillGateResult = Bash('bash hooks/skill-gate.sh status');
|
|
44
|
+
const skillGate = JSON.parse(skillGateResult);
|
|
45
|
+
|
|
46
|
+
if (skillGate.elsabro_active && !skillGate.done) {
|
|
47
|
+
// Skill discovery no ejecutado — ejecutar ahora
|
|
48
|
+
output("Ejecutando skill discovery antes de continuar...");
|
|
49
|
+
state.context = state.context || {};
|
|
50
|
+
try {
|
|
51
|
+
const discoveryResult = Bash(`bash ./hooks/skill-discovery.sh "${inputs.task || args}" "medium"`, { timeout: 30000 });
|
|
52
|
+
try {
|
|
53
|
+
state.context.available_skills = JSON.parse(discoveryResult).recommended || [];
|
|
54
|
+
} catch (e) {
|
|
55
|
+
output("Warning: skill-discovery devolvio JSON invalido, continuando sin skills");
|
|
56
|
+
state.context.available_skills = [];
|
|
57
|
+
}
|
|
58
|
+
} catch (e) {
|
|
59
|
+
output("Warning: skill-discovery fallo, continuando sin skills");
|
|
60
|
+
state.context.available_skills = [];
|
|
61
|
+
}
|
|
62
|
+
Write(".elsabro/state.json", JSON.stringify(state, null, 2));
|
|
63
|
+
Bash('bash hooks/skill-gate.sh set "execute"');
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Plan Verification (OBLIGATORIO)
|
|
68
|
+
|
|
69
|
+
Antes de ejecutar CUALQUIER codigo, verificar que existe un plan aprobado:
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
// GATE: No ejecutar sin plan previo
|
|
73
|
+
state.context = state.context || {};
|
|
74
|
+
const planFile = state.context.plan_file;
|
|
75
|
+
const hasPlan = planFile && fs.existsSync(planFile);
|
|
76
|
+
const hasPlanInPlanning = fs.readdirSync('.planning/').some(f => f.endsWith('-PLAN.md'));
|
|
77
|
+
|
|
78
|
+
if (!hasPlan && !hasPlanInPlanning) {
|
|
79
|
+
// No hay plan — forzar planificacion primero
|
|
80
|
+
output("⚠ No se encontro plan de implementacion.");
|
|
81
|
+
output("→ Ejecuta /elsabro:plan primero, o usa EnterPlanMode para crear un plan.");
|
|
82
|
+
output("→ ELSABRO requiere un plan aprobado antes de ejecutar codigo.");
|
|
83
|
+
|
|
84
|
+
// Ofrecer opciones al usuario
|
|
85
|
+
const answer = AskUserQuestion({
|
|
86
|
+
questions: [{
|
|
87
|
+
question: "No hay plan de implementacion. ¿Que deseas hacer?",
|
|
88
|
+
header: "Plan Check",
|
|
89
|
+
options: [
|
|
90
|
+
{ label: "Crear plan ahora (Recomendado)", description: "Entra a plan mode para disenar la implementacion" },
|
|
91
|
+
{ label: "Continuar sin plan", description: "Solo para tareas triviales - se registra como excepcion" }
|
|
92
|
+
],
|
|
93
|
+
multiSelect: false
|
|
94
|
+
}]
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (answer.includes("Crear plan")) {
|
|
98
|
+
EnterPlanMode();
|
|
99
|
+
return; // Exit execute — se reanuda despues de aprobar plan
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Usuario eligio continuar sin plan — registrar excepcion
|
|
103
|
+
state.context.plan_skipped = true;
|
|
104
|
+
state.context.plan_skip_reason = "user_override";
|
|
105
|
+
Write(".elsabro/state.json", JSON.stringify(state, null, 2));
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
37
109
|
```bash
|
|
38
110
|
FLOW="flows/development-flow.json"
|
|
39
111
|
TASK="[descripcion de la tarea del usuario]"
|
|
@@ -311,10 +383,39 @@ for (const member of instruction.team.members) {
|
|
|
311
383
|
})
|
|
312
384
|
}
|
|
313
385
|
|
|
314
|
-
// Al completar todos:
|
|
386
|
+
// Al completar todos - shutdown con verificacion:
|
|
315
387
|
for (const member of instruction.team.members) {
|
|
316
|
-
SendMessage({ type: "shutdown_request", recipient: member.name, content: "
|
|
388
|
+
SendMessage({ type: "shutdown_request", recipient: member.name, content: "Task complete" })
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Verificar que todos los teammates terminaron (max 30s timeout)
|
|
392
|
+
// Nota: sleep() es pseudocodigo. Implementar como: const sleep = ms => new Promise(r => setTimeout(r, ms));
|
|
393
|
+
const TEAM_SHUTDOWN_TIMEOUT = 30000;
|
|
394
|
+
const POLL_INTERVAL = 3000;
|
|
395
|
+
let elapsed = 0;
|
|
396
|
+
let allShutdown = false;
|
|
397
|
+
|
|
398
|
+
while (!allShutdown && elapsed < TEAM_SHUTDOWN_TIMEOUT) {
|
|
399
|
+
await sleep(POLL_INTERVAL); // ver nota arriba
|
|
400
|
+
elapsed += POLL_INTERVAL;
|
|
401
|
+
// Check if all members responded to shutdown
|
|
402
|
+
const teamStatus = TaskList(); // Check remaining active tasks
|
|
403
|
+
allShutdown = instruction.team.members.every(m =>
|
|
404
|
+
!teamStatus.some(t => t.owner === m.name && t.status === 'in_progress')
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (!allShutdown) {
|
|
409
|
+
// Emit telemetry for team lifecycle anomaly
|
|
410
|
+
emit_telemetry({
|
|
411
|
+
signal: "team_lifecycle_anomaly",
|
|
412
|
+
teamName: instruction.team.name,
|
|
413
|
+
timeout: TEAM_SHUTDOWN_TIMEOUT,
|
|
414
|
+
activeMembers: instruction.team.members.filter(m => /* still active */).map(m => m.name)
|
|
415
|
+
});
|
|
416
|
+
log_warn(`Team ${instruction.team.name}: ${elapsed}ms timeout - forcing cleanup`);
|
|
317
417
|
}
|
|
418
|
+
|
|
318
419
|
TeamDelete()
|
|
319
420
|
// result: team outputs aggregated
|
|
320
421
|
```
|
|
@@ -452,6 +553,41 @@ for (const step of instruction.steps) {
|
|
|
452
553
|
// result: stepOutputs with all step execution results
|
|
453
554
|
```
|
|
454
555
|
|
|
556
|
+
## 3.1. Skill Invocation Protocol
|
|
557
|
+
|
|
558
|
+
Cuando un agente recibe `availableSkills` en sus inputs, DEBE seguir este protocolo:
|
|
559
|
+
|
|
560
|
+
### Prioridad de Uso
|
|
561
|
+
1. **Skills instalados relevantes** — Usar primero los que ya estan instalados y matchean la tarea
|
|
562
|
+
2. **Skills recomendados** — Si `recommendedSkills` incluye skills no instalados pero criticos, sugerir instalacion
|
|
563
|
+
3. **Implementacion manual** — Solo si no hay skill apropiado disponible
|
|
564
|
+
|
|
565
|
+
### Reglas
|
|
566
|
+
- **Top 5**: Solo considerar los 5 skills mas relevantes para la tarea actual
|
|
567
|
+
- **Silencioso**: No mostrar al usuario la consulta de skills - solo usarlos
|
|
568
|
+
- **Fallback**: Si un skill falla o no aplica, continuar con implementacion manual sin bloquear
|
|
569
|
+
- **Context7**: SIEMPRE consultar Context7 silenciosamente antes de tocar codigo que use librerias externas
|
|
570
|
+
- **Reportar**: Al final, incluir lista de skills usados en el output del agente
|
|
571
|
+
|
|
572
|
+
### Context7 Integration
|
|
573
|
+
Antes de implementar codigo que use APIs externas:
|
|
574
|
+
1. Resolver library ID: `mcp__context7__resolve-library-id` con el nombre de la libreria
|
|
575
|
+
2. Consultar docs: `mcp__context7__query-docs` con la query relevante
|
|
576
|
+
3. Aplicar patterns encontrados — si difieren de lo que se conoce, ALERTAR
|
|
577
|
+
4. Todo esto es silencioso — el usuario no ve estas consultas
|
|
578
|
+
|
|
579
|
+
### Telemetria
|
|
580
|
+
Emitir signal despues de cada agente que use skills:
|
|
581
|
+
```json
|
|
582
|
+
{
|
|
583
|
+
"signal": "skill_usage",
|
|
584
|
+
"nodeId": "{{instruction.nodeId}}",
|
|
585
|
+
"skills_available": ["..."],
|
|
586
|
+
"skills_used": ["..."],
|
|
587
|
+
"skills_failed": ["..."]
|
|
588
|
+
}
|
|
589
|
+
```
|
|
590
|
+
|
|
455
591
|
## 4. Observabilidad (4 Senales)
|
|
456
592
|
|
|
457
593
|
Despues de cada `step` y `complete`, emitir al log:
|
|
@@ -484,15 +620,166 @@ Cuando el loop retorna `{ finished: true }`:
|
|
|
484
620
|
- Establecer `suggested_next: "verify-work"`
|
|
485
621
|
2. Actualizar `.elsabro/context.md` con resumen legible
|
|
486
622
|
|
|
623
|
+
<code_review_gate>
|
|
624
|
+
## 5.1. Code Review Gate (OBLIGATORIO - NO NEGOCIABLE)
|
|
625
|
+
|
|
626
|
+
**ANTES de mostrar resultado o ofrecer commit, VERIFICAR:**
|
|
627
|
+
|
|
628
|
+
```
|
|
629
|
+
¿Se escribió/modificó código durante la ejecución?
|
|
630
|
+
│
|
|
631
|
+
├─ SÍ → ¿Se ejecutó code review (parallel_review node)?
|
|
632
|
+
│ │
|
|
633
|
+
│ ├─ NO → EJECUTAR AHORA con Agent Teams FULL (5 agentes):
|
|
634
|
+
│ │ 1. bash hooks/review-gate.sh set (si no esta activo)
|
|
635
|
+
│ │ 2. TeamCreate("elsabro-review") + 5 teammates especializados
|
|
636
|
+
│ │ 3. Consolidar hallazgos de los 5 reviewers
|
|
637
|
+
│ │ 4. Si issues criticos > 0: fix y re-review (max 3 iteraciones)
|
|
638
|
+
│ │ 5. Solo cuando issues criticos == 0: bash hooks/review-gate.sh clear
|
|
639
|
+
│ │ 6. SendMessage(shutdown_request) x5 + TeamDelete()
|
|
640
|
+
│ │ 7. Continuar
|
|
641
|
+
│ │
|
|
642
|
+
│ └─ SÍ, issues == 0, gate cleared → Continuar
|
|
643
|
+
│
|
|
644
|
+
└─ NO → Continuar (no aplica)
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
```javascript
|
|
648
|
+
// Verificacion programatica del review gate
|
|
649
|
+
const gateStatus = Bash("bash hooks/review-gate.sh status");
|
|
650
|
+
const gate = JSON.parse(gateStatus);
|
|
651
|
+
|
|
652
|
+
state.current_flow = state.current_flow || {};
|
|
653
|
+
|
|
654
|
+
if (gate.pending) {
|
|
655
|
+
// Hay codigo modificado sin review — EJECUTAR REVIEW con Agent Teams FULL
|
|
656
|
+
state.current_flow.code_written = true;
|
|
657
|
+
output("⚠ Review gate activo: " + gate.count + " archivo(s) sin revisar");
|
|
658
|
+
output("→ Lanzando Agent Teams FULL review (5 agentes especializados)...");
|
|
659
|
+
|
|
660
|
+
// 1. Crear team de review
|
|
661
|
+
TeamCreate({ team_name: "elsabro-review", description: "Code review team - 5 agentes especializados" })
|
|
662
|
+
|
|
663
|
+
// 2. Lanzar 5 teammates en paralelo (UN SOLO MENSAJE con multiples Task calls)
|
|
664
|
+
Task({
|
|
665
|
+
subagent_type: "pr-review-toolkit:code-reviewer",
|
|
666
|
+
team_name: "elsabro-review",
|
|
667
|
+
name: "quality-reviewer",
|
|
668
|
+
model: "sonnet",
|
|
669
|
+
prompt: "Review code quality: naming conventions, design patterns, DRY, SOLID principles. Focus on recently modified files. DO NOT ask questions. Report only high-confidence issues."
|
|
670
|
+
})
|
|
671
|
+
Task({
|
|
672
|
+
subagent_type: "feature-dev:code-reviewer",
|
|
673
|
+
team_name: "elsabro-review",
|
|
674
|
+
name: "security-reviewer",
|
|
675
|
+
model: "sonnet",
|
|
676
|
+
prompt: "Review security: OWASP top 10, injection vulnerabilities, XSS, secrets exposure, token handling, input validation. Focus on recently modified files. DO NOT ask questions. Report only high-confidence issues."
|
|
677
|
+
})
|
|
678
|
+
Task({
|
|
679
|
+
subagent_type: "pr-review-toolkit:pr-test-analyzer",
|
|
680
|
+
team_name: "elsabro-review",
|
|
681
|
+
name: "test-analyzer",
|
|
682
|
+
model: "sonnet",
|
|
683
|
+
prompt: "Analyze test coverage: missing tests, edge cases, happy/error paths, test quality. Focus on recently modified files. DO NOT ask questions. Report only critical gaps."
|
|
684
|
+
})
|
|
685
|
+
Task({
|
|
686
|
+
subagent_type: "pr-review-toolkit:silent-failure-hunter",
|
|
687
|
+
team_name: "elsabro-review",
|
|
688
|
+
name: "performance-reviewer",
|
|
689
|
+
model: "sonnet",
|
|
690
|
+
prompt: "Review for silent failures, N+1 queries, memory leaks, inadequate error handling, dangerous fallback behavior. Focus on recently modified files. DO NOT ask questions. Report only high-confidence issues."
|
|
691
|
+
})
|
|
692
|
+
Task({
|
|
693
|
+
subagent_type: "pr-review-toolkit:type-design-analyzer",
|
|
694
|
+
team_name: "elsabro-review",
|
|
695
|
+
name: "type-analyzer",
|
|
696
|
+
model: "sonnet",
|
|
697
|
+
prompt: "Analyze type design: encapsulation, invariant expression, null safety, interface correctness. Focus on recently modified files. DO NOT ask questions. Report only high-confidence issues."
|
|
698
|
+
})
|
|
699
|
+
|
|
700
|
+
// 3. Consolidar resultados — esperar que todos terminen
|
|
701
|
+
// Agregar hallazgos de cada reviewer al reporte consolidado
|
|
702
|
+
// Si ANY reviewer reporta issues criticos: fix y re-review (max 3 iteraciones)
|
|
703
|
+
|
|
704
|
+
// 4. Shutdown team
|
|
705
|
+
for (const name of ["quality-reviewer", "security-reviewer", "test-analyzer", "performance-reviewer", "type-analyzer"]) {
|
|
706
|
+
SendMessage({ type: "shutdown_request", recipient: name, content: "Review complete" })
|
|
707
|
+
}
|
|
708
|
+
TeamDelete()
|
|
709
|
+
|
|
710
|
+
// 5. Si review pasa sin issues criticos, limpiar gate y setear flag
|
|
711
|
+
Bash("bash hooks/review-gate.sh clear");
|
|
712
|
+
state.current_flow.code_review_passed = true;
|
|
713
|
+
Write(".elsabro/state.json", JSON.stringify(state, null, 2));
|
|
714
|
+
}
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
**Donde se setean los flags:**
|
|
718
|
+
- `state.current_flow.code_written = true` — se setea aqui (5.1) cuando review-gate.sh detecta archivos pendientes, y tambien por el PostToolUse hook al hacer Write/Edit
|
|
719
|
+
- `state.current_flow.code_review_passed = true` — se setea aqui (5.1) despues de que los 5 reviewers pasan sin issues criticos y el gate se limpia
|
|
720
|
+
|
|
721
|
+
**Agent Teams FULL — 5 Reviewers:**
|
|
722
|
+
| Teammate | Plugin | Foco |
|
|
723
|
+
|----------|--------|------|
|
|
724
|
+
| quality-reviewer | pr-review-toolkit:code-reviewer | naming, patterns, DRY, SOLID |
|
|
725
|
+
| security-reviewer | feature-dev:code-reviewer | OWASP, injection, secrets |
|
|
726
|
+
| test-analyzer | pr-review-toolkit:pr-test-analyzer | coverage, edge cases |
|
|
727
|
+
| performance-reviewer | pr-review-toolkit:silent-failure-hunter | silent failures, N+1, memory |
|
|
728
|
+
| type-analyzer | pr-review-toolkit:type-design-analyzer | types, encapsulation, null safety |
|
|
729
|
+
|
|
730
|
+
**VIOLACIÓN CRÍTICA**: Reportar resultado o ofrecer commit sin code review = ABORTAR OPERACIÓN
|
|
731
|
+
</code_review_gate>
|
|
732
|
+
|
|
733
|
+
<siguiente_paso>
|
|
487
734
|
## 6. Siguiente Paso
|
|
488
735
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
736
|
+
Al completar la ejecucion, verificar gates programaticamente antes de sugerir siguiente paso:
|
|
737
|
+
|
|
738
|
+
```javascript
|
|
739
|
+
// GATE CHECK: Enforcement estricto antes de cerrar
|
|
740
|
+
state.current_flow = state.current_flow || {};
|
|
741
|
+
|
|
742
|
+
// Cross-check: review-gate.sh es la fuente de verdad para codigo pendiente
|
|
743
|
+
const gateStatus = Bash("bash hooks/review-gate.sh status");
|
|
744
|
+
const gate = JSON.parse(gateStatus);
|
|
745
|
+
|
|
746
|
+
// Si review-gate tiene archivos pendientes, code_written DEBE estar seteado
|
|
747
|
+
if (gate.pending && !state.current_flow.code_written) {
|
|
748
|
+
state.current_flow.code_written = true; // Auto-fix inconsistencia
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Flags verificados
|
|
752
|
+
const codeWritten = state.current_flow.code_written === true;
|
|
753
|
+
const codeReviewPassed = state.current_flow.code_review_passed === true;
|
|
754
|
+
|
|
755
|
+
// Rule 7: Si se escribio codigo, code review DEBE haber pasado
|
|
756
|
+
if (codeWritten && !codeReviewPassed) {
|
|
757
|
+
// VIOLACIÓN CRÍTICA — NO proceder. Volver a code_review_gate.
|
|
758
|
+
output("⛔ CRITICAL: Code written without review. Execute section 5.1 first.");
|
|
759
|
+
output("→ Volver a Code Review Gate (seccion 5.1) antes de continuar.");
|
|
760
|
+
return; // Bloquea — no se puede llegar a suggested_next sin review
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// Rule 7b: Double-check con review-gate.sh (fuente de verdad del sistema)
|
|
764
|
+
if (gate.pending) {
|
|
765
|
+
output("⛔ REVIEW GATE PENDING: " + gate.count + " file(s) sin revisar.");
|
|
766
|
+
output("→ Ejecutar code review y bash hooks/review-gate.sh clear primero.");
|
|
767
|
+
return; // Bloquea — gate aun activo
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Rule 8: Si se usaron 2+ agentes, Agent Teams debe haberse usado
|
|
771
|
+
// (callbacks.js lo garantiza, pero verificamos por seguridad)
|
|
772
|
+
|
|
773
|
+
// Todo limpio — establecer siguiente paso
|
|
774
|
+
state.suggested_next = "verify-work";
|
|
775
|
+
Write(".elsabro/state.json", JSON.stringify(state, null, 2));
|
|
776
|
+
```
|
|
492
777
|
|
|
778
|
+
Mostrar al usuario:
|
|
493
779
|
```
|
|
494
780
|
Siguiente Paso
|
|
495
781
|
|
|
496
|
-
|
|
497
|
-
|
|
782
|
+
→ /elsabro:verify-work — verificar el trabajo completado
|
|
783
|
+
→ /elsabro:progress — ver el progreso general del proyecto
|
|
498
784
|
```
|
|
785
|
+
</siguiente_paso>
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: quick
|
|
3
3
|
description: Modo de ejecución rápida para tareas simples - mínima ceremonia, máxima velocidad
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Read
|
|
6
|
+
- Write
|
|
7
|
+
- Edit
|
|
8
|
+
- Bash
|
|
9
|
+
- Glob
|
|
10
|
+
- Grep
|
|
11
|
+
- Task
|
|
12
|
+
- TeamCreate
|
|
13
|
+
- TeamDelete
|
|
14
|
+
- SendMessage
|
|
15
|
+
- AskUserQuestion
|
|
4
16
|
sync:
|
|
5
17
|
reads: [".elsabro/state.json"]
|
|
6
18
|
writes: [".elsabro/state.json", ".elsabro/context.md"]
|
|
@@ -126,9 +138,11 @@ ELSABRO: [Ejecuta en <30 segundos]
|
|
|
126
138
|
4. Verify (10 seg)
|
|
127
139
|
└─ Correr tests afectados
|
|
128
140
|
|
|
129
|
-
5. Code Review (OBLIGATORIO)
|
|
130
|
-
└─
|
|
131
|
-
└─
|
|
141
|
+
5. Code Review (OBLIGATORIO - Agent Teams LITE)
|
|
142
|
+
└─ TeamCreate("elsabro-review-lite") + 3 teammates especializados
|
|
143
|
+
└─ quality-reviewer + failure-hunter + test-analyzer (en paralelo)
|
|
144
|
+
└─ Consolidar hallazgos. Si issues criticos: fix y re-review
|
|
145
|
+
└─ SendMessage(shutdown_request) x3 + TeamDelete()
|
|
132
146
|
└─ Si issues == 0: state.current_flow.code_review_passed = true ← MARCAR
|
|
133
147
|
|
|
134
148
|
6. Report
|
|
@@ -141,7 +155,7 @@ ELSABRO: [Ejecuta en <30 segundos]
|
|
|
141
155
|
1. **Max 2 preguntas** - Si necesitas más info, usa `/elsabro:plan`
|
|
142
156
|
2. **Max 3 archivos** - Si afecta más, usa flujo normal
|
|
143
157
|
3. **Auto-test** - Siempre corre tests relacionados
|
|
144
|
-
4. **Auto-review** - SIEMPRE ejecutar code review con
|
|
158
|
+
4. **Auto-review** - SIEMPRE ejecutar code review con Agent Teams LITE (3 agentes) después de escribir código
|
|
145
159
|
5. **No docs** - Skip documentación para velocidad
|
|
146
160
|
6. **Offer commit** - Siempre pregunta si commitear
|
|
147
161
|
|
|
@@ -177,18 +191,27 @@ Task({
|
|
|
177
191
|
```
|
|
178
192
|
¿Se escribió/modificó código?
|
|
179
193
|
│
|
|
180
|
-
├─ SÍ → ¿Se ejecutó code review (Paso 5)?
|
|
194
|
+
├─ SÍ → ¿Se ejecutó code review con Agent Teams LITE (Paso 5)?
|
|
181
195
|
│ │
|
|
182
196
|
│ ├─ NO → EJECUTAR AHORA:
|
|
183
|
-
│ │
|
|
184
|
-
│ │
|
|
185
|
-
│ │
|
|
197
|
+
│ │ TeamCreate("elsabro-review-lite")
|
|
198
|
+
│ │ + 3 teammates: quality-reviewer, failure-hunter, test-analyzer
|
|
199
|
+
│ │ Consolidar hallazgos
|
|
200
|
+
│ │ Si issues criticos > 0: fix y re-review
|
|
201
|
+
│ │ Solo cuando issues == 0: shutdown + TeamDelete + continuar
|
|
186
202
|
│ │
|
|
187
|
-
│ └─ SÍ, issues == 0 → Continuar
|
|
203
|
+
│ └─ SÍ, issues == 0, team deleted → Continuar
|
|
188
204
|
│
|
|
189
205
|
└─ NO → Continuar (no aplica)
|
|
190
206
|
```
|
|
191
207
|
|
|
208
|
+
**Agent Teams LITE — 3 Reviewers:**
|
|
209
|
+
| Teammate | Plugin | Foco |
|
|
210
|
+
|----------|--------|------|
|
|
211
|
+
| quality-reviewer | pr-review-toolkit:code-reviewer | bugs, patterns, naming |
|
|
212
|
+
| failure-hunter | pr-review-toolkit:silent-failure-hunter | error handling, fallbacks |
|
|
213
|
+
| test-analyzer | pr-review-toolkit:pr-test-analyzer | coverage, edge cases |
|
|
214
|
+
|
|
192
215
|
**VIOLACIÓN CRÍTICA**: Reportar resultado sin code review = ABORTAR OPERACIÓN
|
|
193
216
|
</code_review_gate>
|
|
194
217
|
|
package/flow-engine/src/graph.js
CHANGED
|
@@ -66,11 +66,16 @@ function buildGraph(flowDefinition) {
|
|
|
66
66
|
* Validate that all node references (next, routes, true, false, onMaxIterations,
|
|
67
67
|
* onError) point to existing nodes, and detect orphaned nodes.
|
|
68
68
|
*
|
|
69
|
+
* Returns errors (dangling references — fatal) and warnings (orphaned nodes —
|
|
70
|
+
* informational). Deprecated and standalone nodes are reported as warnings,
|
|
71
|
+
* not errors, since they are intentionally disconnected.
|
|
72
|
+
*
|
|
69
73
|
* @param {{ nodes: Map<string, object>, entryNode: string }} graph
|
|
70
|
-
* @returns {{ valid: boolean, errors: string[] }}
|
|
74
|
+
* @returns {{ valid: boolean, errors: string[], warnings: string[] }}
|
|
71
75
|
*/
|
|
72
76
|
function validateGraph(graph) {
|
|
73
77
|
const errors = [];
|
|
78
|
+
const warnings = [];
|
|
74
79
|
const nodeIds = new Set(graph.nodes.keys());
|
|
75
80
|
|
|
76
81
|
// 1. Check for dangling references (invalid routes)
|
|
@@ -103,14 +108,21 @@ function validateGraph(graph) {
|
|
|
103
108
|
}
|
|
104
109
|
}
|
|
105
110
|
|
|
106
|
-
//
|
|
111
|
+
// Classify orphaned nodes: deprecated/standalone → warning, others → warning too
|
|
112
|
+
// (orphaned nodes are informational, not fatal — dangling refs are fatal)
|
|
107
113
|
for (const nodeId of nodeIds) {
|
|
108
114
|
if (!reachable.has(nodeId)) {
|
|
109
|
-
|
|
115
|
+
const node = graph.nodes.get(nodeId);
|
|
116
|
+
const status = node?.runtime_status || '';
|
|
117
|
+
if (status === 'deprecated') {
|
|
118
|
+
warnings.push(`Deprecated node "${nodeId}" is unreachable from entry point`);
|
|
119
|
+
} else {
|
|
120
|
+
warnings.push(`Orphaned node "${nodeId}" is unreachable from entry point`);
|
|
121
|
+
}
|
|
110
122
|
}
|
|
111
123
|
}
|
|
112
124
|
|
|
113
|
-
return { valid: errors.length === 0, errors };
|
|
125
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
114
126
|
}
|
|
115
127
|
|
|
116
128
|
/**
|
package/flow-engine/src/index.js
CHANGED
|
@@ -50,9 +50,19 @@ class FlowEngine {
|
|
|
50
50
|
);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
this._validationWarnings = validation.warnings || [];
|
|
53
54
|
return this;
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Get validation warnings from the last loadFlow() call.
|
|
59
|
+
* Warnings are informational (e.g. orphaned nodes) and do not prevent flow execution.
|
|
60
|
+
* @returns {string[]}
|
|
61
|
+
*/
|
|
62
|
+
getValidationWarnings() {
|
|
63
|
+
return this._validationWarnings || [];
|
|
64
|
+
}
|
|
65
|
+
|
|
56
66
|
/**
|
|
57
67
|
* Get a node by ID.
|
|
58
68
|
* @param {string} id
|
|
@@ -73,17 +73,18 @@ registerFunction('collectErrors', (context) => {
|
|
|
73
73
|
registerFunction('hasCriticalIssues', (context, obj) => {
|
|
74
74
|
if (!obj) return false;
|
|
75
75
|
const json = typeof obj === 'string' ? obj : JSON.stringify(obj);
|
|
76
|
+
const jsonLower = json.toLowerCase();
|
|
76
77
|
|
|
77
|
-
//
|
|
78
|
+
// Case-insensitive pattern detection for critical issues
|
|
78
79
|
const criticalPatterns = [
|
|
79
80
|
'"critical"',
|
|
80
81
|
'"blocking"',
|
|
81
|
-
'"
|
|
82
|
-
'"
|
|
83
|
-
'"
|
|
82
|
+
'"p0"',
|
|
83
|
+
'"must_fix"',
|
|
84
|
+
'"urgent"'
|
|
84
85
|
];
|
|
85
86
|
|
|
86
|
-
return criticalPatterns.some(pattern =>
|
|
87
|
+
return criticalPatterns.some(pattern => jsonLower.includes(pattern));
|
|
87
88
|
});
|
|
88
89
|
|
|
89
90
|
registerFunction('generateSummary', (context) => {
|
|
@@ -48,10 +48,11 @@ describe('CLI: helpers', () => {
|
|
|
48
48
|
// ---------- validate ----------
|
|
49
49
|
|
|
50
50
|
describe('CLI: validate', () => {
|
|
51
|
-
it('reports valid flow with
|
|
51
|
+
it('reports valid flow with correct node count', async () => {
|
|
52
52
|
const result = await main(['node', 'cli.js', 'validate', '--flow', FLOW_PATH]);
|
|
53
|
+
const flow = require('../../flows/development-flow.json');
|
|
53
54
|
assert.equal(result.valid, true);
|
|
54
|
-
assert.equal(result.nodeCount,
|
|
55
|
+
assert.equal(result.nodeCount, flow.nodes.length);
|
|
55
56
|
assert.ok(result.parallelNodes.length >= 4);
|
|
56
57
|
});
|
|
57
58
|
|
|
@@ -385,7 +385,8 @@ describe('Execute Dispatcher: Error Handling', () => {
|
|
|
385
385
|
|
|
386
386
|
it('condition node throws when branch is missing', async () => {
|
|
387
387
|
const executor = getExecutor('condition');
|
|
388
|
-
|
|
388
|
+
// Condition evaluates to false, but no "false" branch is defined
|
|
389
|
+
const ctx = makeContext({ inputs: { val: false } });
|
|
389
390
|
await assert.rejects(
|
|
390
391
|
executor(
|
|
391
392
|
{ id: 'cond', type: 'condition', condition: '{{inputs.val}}', true: 'next' },
|
|
@@ -160,8 +160,8 @@ describe('flow validation enhancements', () => {
|
|
|
160
160
|
]
|
|
161
161
|
});
|
|
162
162
|
const result = validateGraph(graph);
|
|
163
|
-
assert.equal(result.valid,
|
|
164
|
-
assert.ok(result.
|
|
163
|
+
assert.equal(result.valid, true); // orphans are warnings, not errors
|
|
164
|
+
assert.ok(result.warnings.some(e => e.includes('orphan') && e.includes('unreachable')));
|
|
165
165
|
});
|
|
166
166
|
|
|
167
167
|
it('detects orphaned nodes in complex branching', () => {
|
|
@@ -175,9 +175,9 @@ describe('flow validation enhancements', () => {
|
|
|
175
175
|
]
|
|
176
176
|
});
|
|
177
177
|
const result = validateGraph(graph);
|
|
178
|
-
assert.equal(result.valid,
|
|
179
|
-
assert.ok(result.
|
|
180
|
-
assert.ok(result.
|
|
178
|
+
assert.equal(result.valid, true); // orphans are warnings, not errors
|
|
179
|
+
assert.ok(result.warnings.some(e => e.includes('isolated_branch') && e.includes('unreachable')));
|
|
180
|
+
assert.ok(result.warnings.some(e => e.includes('isolated_end') && e.includes('unreachable')));
|
|
181
181
|
});
|
|
182
182
|
|
|
183
183
|
it('validates onMaxIterations handler references', () => {
|
|
@@ -229,11 +229,11 @@ describe('flow validation enhancements', () => {
|
|
|
229
229
|
]
|
|
230
230
|
});
|
|
231
231
|
const result = validateGraph(graph);
|
|
232
|
-
assert.equal(result.valid, false);
|
|
232
|
+
assert.equal(result.valid, false); // dangling ref is a real error
|
|
233
233
|
// Should have dangling reference error (missing_next)
|
|
234
234
|
assert.ok(result.errors.some(e => e.includes('missing_next')));
|
|
235
|
-
//
|
|
236
|
-
assert.ok(result.
|
|
235
|
+
// Orphaned nodes are warnings, not errors
|
|
236
|
+
assert.ok(result.warnings.some(e => e.includes('orphan')));
|
|
237
237
|
});
|
|
238
238
|
|
|
239
239
|
it('passes validation for graph with all error handlers', () => {
|
|
@@ -276,9 +276,9 @@ describe('flow validation enhancements', () => {
|
|
|
276
276
|
]
|
|
277
277
|
});
|
|
278
278
|
const result = validateGraph(graph);
|
|
279
|
-
assert.equal(result.valid,
|
|
280
|
-
assert.ok(result.
|
|
281
|
-
assert.ok(result.
|
|
279
|
+
assert.equal(result.valid, true); // orphans are warnings, not errors
|
|
280
|
+
assert.ok(result.warnings.some(e => e.includes('circular_a') && e.includes('unreachable')));
|
|
281
|
+
assert.ok(result.warnings.some(e => e.includes('circular_b') && e.includes('unreachable')));
|
|
282
282
|
});
|
|
283
283
|
|
|
284
284
|
it('validates condition node with both branches pointing to same target', () => {
|
|
@@ -316,12 +316,15 @@ describe('flow validation enhancements', () => {
|
|
|
316
316
|
]
|
|
317
317
|
});
|
|
318
318
|
const result = validateGraph(graph);
|
|
319
|
-
assert.equal(result.valid, false);
|
|
320
|
-
// Check error message format
|
|
319
|
+
assert.equal(result.valid, false); // dangling refs are real errors
|
|
320
|
+
// Check error message format for dangling references
|
|
321
|
+
assert.ok(result.errors.length > 0);
|
|
321
322
|
result.errors.forEach(err => {
|
|
322
323
|
assert.ok(typeof err === 'string');
|
|
323
324
|
assert.ok(err.length > 0);
|
|
324
325
|
});
|
|
326
|
+
// Orphaned node reported as warning
|
|
327
|
+
assert.ok(result.warnings.some(w => w.includes('orphan')));
|
|
325
328
|
});
|
|
326
329
|
});
|
|
327
330
|
|
|
@@ -329,7 +332,7 @@ describe('real flow loading', () => {
|
|
|
329
332
|
it('loads the full development-flow.json', () => {
|
|
330
333
|
const flow = require('../../flows/development-flow.json');
|
|
331
334
|
const graph = buildGraph(flow);
|
|
332
|
-
assert.equal(graph.nodes.size,
|
|
335
|
+
assert.equal(graph.nodes.size, flow.nodes.length);
|
|
333
336
|
assert.equal(graph.entryNode, 'start');
|
|
334
337
|
assert.equal(graph.meta.version, '5.3.0');
|
|
335
338
|
});
|
|
@@ -338,17 +341,15 @@ describe('real flow loading', () => {
|
|
|
338
341
|
const flow = require('../../flows/development-flow.json');
|
|
339
342
|
const graph = buildGraph(flow);
|
|
340
343
|
const result = validateGraph(graph);
|
|
341
|
-
//
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
assert.ok(hasTeamsNodes, 'Orphaned nodes should include known teams mode nodes');
|
|
352
|
-
}
|
|
344
|
+
// Flow should be valid (no dangling references)
|
|
345
|
+
assert.equal(result.valid, true, 'Flow should have no dangling reference errors');
|
|
346
|
+
assert.equal(result.errors.length, 0);
|
|
347
|
+
// Known orphaned nodes reported as warnings (deprecated + standalone subflows)
|
|
348
|
+
assert.ok(result.warnings.length > 0, 'Should have warnings for orphaned nodes');
|
|
349
|
+
const hasKnownOrphans = result.warnings.some(w =>
|
|
350
|
+
w.includes('teams_spawn') || w.includes('interrupt_teams') ||
|
|
351
|
+
w.includes('design_ui') || w.includes('interrupt_design')
|
|
352
|
+
);
|
|
353
|
+
assert.ok(hasKnownOrphans, 'Warnings should include known orphaned nodes');
|
|
353
354
|
});
|
|
354
355
|
});
|