elsabro 5.2.0 → 6.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/elsabro-executor.md +25 -0
- package/agents/elsabro-planner.md +10 -0
- package/bin/install.js +8 -4
- package/commands/elsabro/execute.md +156 -3
- package/commands/elsabro/plan.md +32 -0
- package/commands/elsabro/quick.md +21 -0
- package/commands/elsabro/verify-work.md +29 -0
- package/flow-engine/package.json +13 -0
- package/flow-engine/src/checkpoint.js +112 -0
- package/flow-engine/src/executors.js +406 -0
- package/flow-engine/src/graph.js +129 -0
- package/flow-engine/src/index.js +137 -0
- package/flow-engine/src/runner.js +184 -0
- package/flow-engine/src/template.js +290 -0
- package/flow-engine/tests/executors-complex.test.js +300 -0
- package/flow-engine/tests/executors.test.js +326 -0
- package/flow-engine/tests/graph.test.js +161 -0
- package/flow-engine/tests/integration.test.js +251 -0
- package/flow-engine/tests/runner.test.js +289 -0
- package/flow-engine/tests/template.test.js +272 -0
- package/flows/development-flow.json +134 -1
- package/package.json +4 -3
|
@@ -69,6 +69,31 @@ CHECKPOINT (preguntar):
|
|
|
69
69
|
```
|
|
70
70
|
</critical_rules>
|
|
71
71
|
|
|
72
|
+
<skill_awareness>
|
|
73
|
+
## Skill Awareness
|
|
74
|
+
|
|
75
|
+
### Si recibes skills en tu contexto (seccion "SKILLS DE REFERENCIA"):
|
|
76
|
+
|
|
77
|
+
1. **Lee las guias ANTES de implementar** — los skills contienen patrones verificados
|
|
78
|
+
2. **Sigue la estructura de archivos sugerida** en `<project_structure>`
|
|
79
|
+
3. **Usa los pre-requisitos** de `<pre_requisites>` para verificar dependencias
|
|
80
|
+
4. **Aplica los patrones de codigo** de las secciones de implementacion
|
|
81
|
+
5. **En tu output, menciona que skill usaste** para cada decision
|
|
82
|
+
|
|
83
|
+
### Si NO recibes skills pero la tarea parece relevante:
|
|
84
|
+
|
|
85
|
+
1. Busca en `skills/` con Glob("skills/*.md") por archivos relevantes
|
|
86
|
+
2. Lee el archivo con Read() y usa como referencia
|
|
87
|
+
3. Prioriza skills de: auth, database, testing, api
|
|
88
|
+
|
|
89
|
+
### Keywords que activan busqueda de skills:
|
|
90
|
+
- auth/login/registro → skills/auth-setup.md
|
|
91
|
+
- database/db/prisma → skills/database-setup.md
|
|
92
|
+
- test/testing/jest → skills/testing-setup.md
|
|
93
|
+
- api/endpoint/rest → skills/api-setup.md
|
|
94
|
+
- payments/stripe → skills/payments-setup.md
|
|
95
|
+
</skill_awareness>
|
|
96
|
+
|
|
72
97
|
<execution_flow>
|
|
73
98
|
## Flujo de Ejecución
|
|
74
99
|
|
|
@@ -46,6 +46,16 @@ Cada tarea debe:
|
|
|
46
46
|
- Generar un commit significativo
|
|
47
47
|
</critical_rules>
|
|
48
48
|
|
|
49
|
+
<skill_awareness>
|
|
50
|
+
## Skill Awareness (Planner)
|
|
51
|
+
|
|
52
|
+
Si recibes skills en tu contexto:
|
|
53
|
+
1. Referencia los skills relevantes en el PLAN.md que crees
|
|
54
|
+
2. Usa las estructuras de archivos sugeridas por los skills
|
|
55
|
+
3. Incorpora los pre-requisitos como dependencias del plan
|
|
56
|
+
4. Menciona que skill aplica a cada tarea del plan
|
|
57
|
+
</skill_awareness>
|
|
58
|
+
|
|
49
59
|
<research_first>
|
|
50
60
|
## Protocolo: Research-Before-Plan
|
|
51
61
|
|
package/bin/install.js
CHANGED
|
@@ -239,8 +239,8 @@ if (hasUpdate) {
|
|
|
239
239
|
console.log(` ${yellow}⚠${reset} No se pudo verificar la última versión`);
|
|
240
240
|
console.log(` ${dim}Intentando actualizar de todas formas...${reset}\n`);
|
|
241
241
|
} else if (latestVersion === pkg.version) {
|
|
242
|
-
console.log(` ${green}✓${reset}
|
|
243
|
-
|
|
242
|
+
console.log(` ${green}✓${reset} Versión actual: ${pkg.version} (ya es la última)`);
|
|
243
|
+
console.log(` ${dim}Reinstalando archivos locales...${reset}\n`);
|
|
244
244
|
} else {
|
|
245
245
|
console.log(` ${cyan}Nueva versión disponible: ${latestVersion}${reset}`);
|
|
246
246
|
console.log(` ${dim}Versión actual: ${pkg.version}${reset}\n`);
|
|
@@ -357,7 +357,9 @@ async function install() {
|
|
|
357
357
|
{ src: 'templates', dest: 'templates', name: 'Templates' },
|
|
358
358
|
{ src: 'workflows', dest: 'workflows', name: 'Workflows' },
|
|
359
359
|
{ src: 'references', dest: 'references', name: 'Referencias' },
|
|
360
|
-
{ src: 'hooks/dist', dest: 'hooks', name: 'Hooks' }
|
|
360
|
+
{ src: 'hooks/dist', dest: 'hooks', name: 'Hooks' },
|
|
361
|
+
{ src: 'flows', dest: 'flows', name: 'Flows' },
|
|
362
|
+
{ src: 'flow-engine', dest: 'flow-engine', name: 'Flow Engine' }
|
|
361
363
|
];
|
|
362
364
|
|
|
363
365
|
const failures = [];
|
|
@@ -465,7 +467,9 @@ async function uninstall() {
|
|
|
465
467
|
'skills',
|
|
466
468
|
'templates',
|
|
467
469
|
'workflows',
|
|
468
|
-
'references'
|
|
470
|
+
'references',
|
|
471
|
+
'flows',
|
|
472
|
+
'flow-engine'
|
|
469
473
|
];
|
|
470
474
|
|
|
471
475
|
for (const item of toRemove) {
|
|
@@ -162,6 +162,51 @@ Write(".elsabro/state.json", JSON.stringify(state, null, 2));
|
|
|
162
162
|
Los skills proveen patrones de código verificados, configuraciones de setup, y guides de integración durante la implementación.
|
|
163
163
|
</skill_discovery>
|
|
164
164
|
|
|
165
|
+
<skill_injection>
|
|
166
|
+
## Skill Injection (OBLIGATORIO despues de Discovery)
|
|
167
|
+
|
|
168
|
+
**Si skill discovery encontro skills relevantes, CARGAR su contenido como contexto.**
|
|
169
|
+
|
|
170
|
+
```javascript
|
|
171
|
+
// 1. Obtener skills recomendados
|
|
172
|
+
const recommendedSkills = state.context.available_skills || [];
|
|
173
|
+
|
|
174
|
+
if (recommendedSkills.length > 0) {
|
|
175
|
+
// 2. Cargar contenido de los top-3 skills con match
|
|
176
|
+
const loadedSkills = [];
|
|
177
|
+
for (const skill of recommendedSkills.slice(0, 3)) {
|
|
178
|
+
const content = Read(`skills/${skill.id}.md`);
|
|
179
|
+
if (content) {
|
|
180
|
+
loadedSkills.push({
|
|
181
|
+
name: skill.id,
|
|
182
|
+
content: content
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 3. Guardar skills cargados en state
|
|
188
|
+
state.context.loaded_skills = loadedSkills;
|
|
189
|
+
Write(".elsabro/state.json", JSON.stringify(state, null, 2));
|
|
190
|
+
|
|
191
|
+
// 4. Informar al usuario
|
|
192
|
+
output(`Skills cargados para esta ejecucion:`);
|
|
193
|
+
loadedSkills.forEach(s => output(` - ${s.name}`));
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**GATE**: Cuando lances agentes executors, INCLUIR los skills en el prompt:
|
|
198
|
+
```
|
|
199
|
+
Si state.context.loaded_skills tiene contenido, agregar al prompt de CADA executor:
|
|
200
|
+
|
|
201
|
+
"SKILLS DE REFERENCIA:
|
|
202
|
+
[Para cada skill cargado:]
|
|
203
|
+
### Skill: {name}
|
|
204
|
+
{contenido del archivo .md}
|
|
205
|
+
|
|
206
|
+
Usa estos skills como guia de patrones, estructura y mejores practicas."
|
|
207
|
+
```
|
|
208
|
+
</skill_injection>
|
|
209
|
+
|
|
165
210
|
<objective>
|
|
166
211
|
Ejecutar planes de una fase con:
|
|
167
212
|
- **Sistema de Tasks para tracking real** de waves y dependencias
|
|
@@ -528,6 +573,41 @@ Para cada wave:
|
|
|
528
573
|
TaskUpdate({ taskId: "wave-W-id", status: "in_progress" })
|
|
529
574
|
```
|
|
530
575
|
|
|
576
|
+
<IMPERATIVO_AGENT_TEAMS>
|
|
577
|
+
## ⛔ STOP — LEER ANTES DE LANZAR AGENTES PARALELOS
|
|
578
|
+
|
|
579
|
+
**REGLA NO NEGOCIABLE (Rule 8):** Si vas a lanzar 2+ Task() para implementacion o review:
|
|
580
|
+
|
|
581
|
+
1. **PRIMERO** → `TeamCreate({ team_name: "elsabro-impl", description: "..." })`
|
|
582
|
+
2. **DESPUES** → `Task({ ..., team_name: "elsabro-impl", name: "executor-1" })`
|
|
583
|
+
3. **AL FINAL** → `SendMessage({ type: "shutdown_request", ... })` para CADA teammate
|
|
584
|
+
4. **ULTIMO** → `TeamDelete()`
|
|
585
|
+
|
|
586
|
+
**Si ya lanzaste Task() sin TeamCreate: DETENTE AHORA. No continúes.**
|
|
587
|
+
Vuelve arriba, crea el team, y relanza los agentes CON team_name.
|
|
588
|
+
|
|
589
|
+
**NUNCA hagas esto:**
|
|
590
|
+
```javascript
|
|
591
|
+
// ⛔ PROHIBIDO — bare Task() sin team
|
|
592
|
+
Task({ subagent_type: "elsabro-executor", prompt: "..." })
|
|
593
|
+
Task({ subagent_type: "elsabro-qa", prompt: "..." })
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
**SIEMPRE haz esto:**
|
|
597
|
+
```javascript
|
|
598
|
+
// ✅ CORRECTO — con Agent Team
|
|
599
|
+
TeamCreate({ team_name: "elsabro-impl", description: "Implementation" })
|
|
600
|
+
Task({ subagent_type: "elsabro-executor", team_name: "elsabro-impl", name: "executor-1", prompt: "..." })
|
|
601
|
+
Task({ subagent_type: "elsabro-qa", team_name: "elsabro-impl", name: "qa-1", prompt: "..." })
|
|
602
|
+
// ... al completar ...
|
|
603
|
+
SendMessage({ type: "shutdown_request", recipient: "executor-1", content: "Done" })
|
|
604
|
+
SendMessage({ type: "shutdown_request", recipient: "qa-1", content: "Done" })
|
|
605
|
+
TeamDelete()
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
**Excepcion UNICA:** Exploracion read-only con HAIKU (Paso 0) puede usar subagents sueltos.
|
|
609
|
+
</IMPERATIVO_AGENT_TEAMS>
|
|
610
|
+
|
|
531
611
|
### Si hay múltiples planes en la wave → Paralelo (OPUS)
|
|
532
612
|
```javascript
|
|
533
613
|
// Marcar todos los planes como in_progress
|
|
@@ -548,13 +628,20 @@ Write(".elsabro/state.json", JSON.stringify(state, null, 2));
|
|
|
548
628
|
|
|
549
629
|
// Lanzar teammates OPUS en paralelo (UN SOLO MENSAJE)
|
|
550
630
|
// MODELO: OPUS porque escriben código
|
|
631
|
+
// SKILLS: Incluir loaded_skills en CADA prompt de executor
|
|
632
|
+
const skillContext = state.context.loaded_skills?.length > 0
|
|
633
|
+
? '\n\n## SKILLS DE REFERENCIA\n' + state.context.loaded_skills.map(s =>
|
|
634
|
+
'### Skill: ' + s.name + '\n' + s.content
|
|
635
|
+
).join('\n\n---\n\n') + '\n\nUsa los skills como referencia de patrones y estructura de archivos.'
|
|
636
|
+
: '';
|
|
637
|
+
|
|
551
638
|
Task({
|
|
552
639
|
subagent_type: "elsabro-executor",
|
|
553
640
|
model: "opus", // ← OPUS para implementación
|
|
554
641
|
team_name: "elsabro-impl",
|
|
555
642
|
name: "executor-1",
|
|
556
643
|
description: "Ejecutar Plan A",
|
|
557
|
-
prompt:
|
|
644
|
+
prompt: `Implementa el Plan A siguiendo TDD.${skillContext}`
|
|
558
645
|
}) |
|
|
559
646
|
Task({
|
|
560
647
|
subagent_type: "elsabro-executor",
|
|
@@ -562,7 +649,7 @@ Task({
|
|
|
562
649
|
team_name: "elsabro-impl",
|
|
563
650
|
name: "executor-2",
|
|
564
651
|
description: "Ejecutar Plan B",
|
|
565
|
-
prompt:
|
|
652
|
+
prompt: `Implementa el Plan B siguiendo TDD.${skillContext}`
|
|
566
653
|
}) |
|
|
567
654
|
Task({
|
|
568
655
|
subagent_type: "elsabro-executor",
|
|
@@ -570,7 +657,7 @@ Task({
|
|
|
570
657
|
team_name: "elsabro-impl",
|
|
571
658
|
name: "executor-3",
|
|
572
659
|
description: "Ejecutar Plan C",
|
|
573
|
-
prompt:
|
|
660
|
+
prompt: `Implementa el Plan C siguiendo TDD.${skillContext}`
|
|
574
661
|
})
|
|
575
662
|
|
|
576
663
|
// Al completar cada plan
|
|
@@ -763,6 +850,29 @@ Cuando termines, dime si funcionó o qué problemas encontraste.
|
|
|
763
850
|
|
|
764
851
|
</process>
|
|
765
852
|
|
|
853
|
+
<agent_teams_cleanup>
|
|
854
|
+
## Agent Teams Cleanup (OBLIGATORIO)
|
|
855
|
+
|
|
856
|
+
**Al completar TODAS las fases de implementacion y review:**
|
|
857
|
+
|
|
858
|
+
```javascript
|
|
859
|
+
// 1. Shutdown CADA teammate por nombre
|
|
860
|
+
const teammates = ["executor-1", "executor-2", "executor-3", "reviewer-1", "reviewer-2", "reviewer-3"];
|
|
861
|
+
for (const name of teammates) {
|
|
862
|
+
SendMessage({ type: "shutdown_request", recipient: name, content: "All phases complete" });
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// 2. Eliminar team
|
|
866
|
+
TeamDelete();
|
|
867
|
+
|
|
868
|
+
// 3. Registrar en state
|
|
869
|
+
state.current_flow.agent_teams_cleanup = true;
|
|
870
|
+
Write(".elsabro/state.json", JSON.stringify(state, null, 2));
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
**GATE**: No mostrar "Siguiente Paso" si agent_teams_cleanup !== true y hubo 2+ agentes.
|
|
874
|
+
</agent_teams_cleanup>
|
|
875
|
+
|
|
766
876
|
<code_review_gate>
|
|
767
877
|
## Code Review Gate (OBLIGATORIO - NO NEGOCIABLE)
|
|
768
878
|
|
|
@@ -786,6 +896,30 @@ Cuando termines, dime si funcionó o qué problemas encontraste.
|
|
|
786
896
|
<siguiente_paso>
|
|
787
897
|
## Siguiente Paso
|
|
788
898
|
|
|
899
|
+
### Self-Check (OBLIGATORIO antes de reportar resultado)
|
|
900
|
+
|
|
901
|
+
Antes de mostrar "Siguiente Paso", verifica CADA UNO de estos items:
|
|
902
|
+
|
|
903
|
+
```
|
|
904
|
+
[ ] Si lance 2+ Task() en paralelo → ¿use TeamCreate primero?
|
|
905
|
+
SI: Continuar
|
|
906
|
+
NO: DETENERME. Crear team y relanzar.
|
|
907
|
+
|
|
908
|
+
[ ] Si descubri skills relevantes → ¿los cargue con Read() y los pase al executor?
|
|
909
|
+
SI: Continuar
|
|
910
|
+
NO: DETENERME. Cargar skills y re-ejecutar.
|
|
911
|
+
|
|
912
|
+
[ ] Si escribi/modifique codigo → ¿ejecute code review?
|
|
913
|
+
SI: Continuar
|
|
914
|
+
NO: DETENERME. Ejecutar code review.
|
|
915
|
+
|
|
916
|
+
[ ] Si use Agent Teams → ¿ejecute SendMessage(shutdown) + TeamDelete?
|
|
917
|
+
SI: Continuar
|
|
918
|
+
NO: DETENERME. Hacer cleanup.
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
**Si alguna respuesta es NO: NO mostrar siguiente paso. Corregir primero.**
|
|
922
|
+
|
|
789
923
|
Al completar, establecer en state.json:
|
|
790
924
|
```javascript
|
|
791
925
|
// GATE CHECK 1: No escribir suggested_next sin code review
|
|
@@ -1237,3 +1371,22 @@ Al completar un plan:
|
|
|
1237
1371
|
- [x] Tests: npm run test
|
|
1238
1372
|
```
|
|
1239
1373
|
</summary_creation>
|
|
1374
|
+
|
|
1375
|
+
<engine_integration>
|
|
1376
|
+
## Flow Engine (v6.0.0)
|
|
1377
|
+
|
|
1378
|
+
The flow engine runtime is available at `flow-engine/src/index.js`.
|
|
1379
|
+
It can parse and execute `flows/development-flow.json`.
|
|
1380
|
+
|
|
1381
|
+
Current status: Engine handles implemented nodes (19/42).
|
|
1382
|
+
Not-implemented nodes throw NotImplementedError with gap descriptions.
|
|
1383
|
+
|
|
1384
|
+
For full engine-driven execution (future):
|
|
1385
|
+
```javascript
|
|
1386
|
+
const { FlowEngine } = require('./flow-engine/src/index.js');
|
|
1387
|
+
const flow = require('./flows/development-flow.json');
|
|
1388
|
+
const engine = new FlowEngine({ callbacks: { onAgent, onBash, onInterrupt, onCheckpoint, onParallel, onTeamRequired } });
|
|
1389
|
+
engine.loadFlow(flow);
|
|
1390
|
+
const result = await engine.run({ task, profile, complexity }, callbacks);
|
|
1391
|
+
```
|
|
1392
|
+
</engine_integration>
|
package/commands/elsabro/plan.md
CHANGED
|
@@ -11,6 +11,9 @@ allowed-tools:
|
|
|
11
11
|
- TaskCreate
|
|
12
12
|
- TaskUpdate
|
|
13
13
|
- TaskList
|
|
14
|
+
- TeamCreate
|
|
15
|
+
- TeamDelete
|
|
16
|
+
- SendMessage
|
|
14
17
|
- WebSearch
|
|
15
18
|
- AskUserQuestion
|
|
16
19
|
- mcp__plugin_context7_context7__*
|
|
@@ -157,6 +160,35 @@ Write(".elsabro/state.json", JSON.stringify(state, null, 2));
|
|
|
157
160
|
Los skills descubiertos informan la fase de research (Context7 queries) y la creación del PLAN.md.
|
|
158
161
|
</skill_discovery>
|
|
159
162
|
|
|
163
|
+
<skill_injection>
|
|
164
|
+
## Skill Injection (Para Planificacion)
|
|
165
|
+
|
|
166
|
+
Despues de skill discovery, cargar skills relevantes para informar al planner:
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
const recommendedSkills = state.context.available_skills || [];
|
|
170
|
+
if (recommendedSkills.length > 0) {
|
|
171
|
+
const loadedSkills = [];
|
|
172
|
+
for (const skill of recommendedSkills.slice(0, 3)) {
|
|
173
|
+
const content = Read(`skills/${skill.id}.md`);
|
|
174
|
+
if (content) {
|
|
175
|
+
loadedSkills.push({ name: skill.id, content: content });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
state.context.loaded_skills = loadedSkills;
|
|
179
|
+
Write(".elsabro/state.json", JSON.stringify(state, null, 2));
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Al lanzar el planner OPUS**: Incluir skills como contexto:
|
|
184
|
+
```
|
|
185
|
+
"Skills disponibles para esta tarea:
|
|
186
|
+
${loadedSkills.map(s => '- ' + s.name + ': ' + s.content.slice(0, 200)).join('\n')}
|
|
187
|
+
|
|
188
|
+
Referencia estos skills en tu plan cuando aplique."
|
|
189
|
+
```
|
|
190
|
+
</skill_injection>
|
|
191
|
+
|
|
160
192
|
<objective>
|
|
161
193
|
Crear un plan ejecutable para una fase o feature, con investigación previa usando Context7 y WebSearch.
|
|
162
194
|
|
|
@@ -42,6 +42,27 @@ Write(".elsabro/state.json", JSON.stringify(state, null, 2));
|
|
|
42
42
|
```
|
|
43
43
|
</skill_discovery>
|
|
44
44
|
|
|
45
|
+
<skill_injection>
|
|
46
|
+
## Skill Injection (Quick)
|
|
47
|
+
|
|
48
|
+
Si discovery encontro skills relevantes, cargar el top-1 skill como referencia rapida:
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
const skills = state.context.available_skills || [];
|
|
52
|
+
if (skills.length > 0) {
|
|
53
|
+
const topSkill = Read(`skills/${skills[0].id}.md`);
|
|
54
|
+
if (topSkill) {
|
|
55
|
+
state.context.loaded_skills = [{ name: skills[0].id, content: topSkill }];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Incluir en el prompt del agente quick:
|
|
61
|
+
```
|
|
62
|
+
"Skill de referencia: ${topSkill ? topSkill.name : 'ninguno'}"
|
|
63
|
+
```
|
|
64
|
+
</skill_injection>
|
|
65
|
+
|
|
45
66
|
<command-name>quick</command-name>
|
|
46
67
|
|
|
47
68
|
## Propósito
|
|
@@ -324,6 +324,19 @@ Agrega verificaciones extra:
|
|
|
324
324
|
- Compatible con CI/CD pipelines
|
|
325
325
|
- Genera reporte en `.planning/VERIFICATION-REPORT.md`
|
|
326
326
|
|
|
327
|
+
<IMPERATIVO_AGENT_TEAMS>
|
|
328
|
+
## ⛔ STOP — ANTES DE LANZAR REVIEWERS PARALELOS
|
|
329
|
+
|
|
330
|
+
**REGLA NO NEGOCIABLE (Rule 8):** Los 3 reviewers DEBEN ejecutarse como Agent Team.
|
|
331
|
+
|
|
332
|
+
1. **PRIMERO** → `TeamCreate({ team_name: "elsabro-verify", description: "Verification team" })`
|
|
333
|
+
2. **DESPUES** → `Task({ ..., team_name: "elsabro-verify", name: "reviewer-N" })` x3
|
|
334
|
+
3. **AL FINAL** → `SendMessage({ type: "shutdown_request", ... })` x3
|
|
335
|
+
4. **ULTIMO** → `TeamDelete()`
|
|
336
|
+
|
|
337
|
+
**NUNCA lances 3 Task() de review sin TeamCreate previo.**
|
|
338
|
+
</IMPERATIVO_AGENT_TEAMS>
|
|
339
|
+
|
|
327
340
|
<parallel_coordination>
|
|
328
341
|
## Coordinación de Verificación Paralela
|
|
329
342
|
|
|
@@ -612,6 +625,22 @@ TaskCreate({
|
|
|
612
625
|
<siguiente_paso>
|
|
613
626
|
## Siguiente Paso
|
|
614
627
|
|
|
628
|
+
### Self-Check (OBLIGATORIO antes de reportar resultado)
|
|
629
|
+
|
|
630
|
+
Antes de mostrar "Siguiente Paso", verifica CADA UNO de estos items:
|
|
631
|
+
|
|
632
|
+
```
|
|
633
|
+
[ ] Si lance 2+ Task() en paralelo → ¿use TeamCreate primero?
|
|
634
|
+
SI: Continuar
|
|
635
|
+
NO: DETENERME. Crear team y relanzar.
|
|
636
|
+
|
|
637
|
+
[ ] Si use Agent Teams → ¿ejecute SendMessage(shutdown) + TeamDelete?
|
|
638
|
+
SI: Continuar
|
|
639
|
+
NO: DETENERME. Hacer cleanup.
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
**Si alguna respuesta es NO: NO mostrar siguiente paso. Corregir primero.**
|
|
643
|
+
|
|
615
644
|
Al completar, establecer en state.json:
|
|
616
645
|
```javascript
|
|
617
646
|
// GATE CHECK: No proceder sin Agent Teams para fases con 2+ agentes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "elsabro-flow-engine",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Runtime engine for ELSABRO development-flow.json",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "node --test tests/"
|
|
8
|
+
},
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=18.0.0"
|
|
11
|
+
},
|
|
12
|
+
"license": "MIT"
|
|
13
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Checkpoint manager for the ELSABRO flow engine.
|
|
5
|
+
*
|
|
6
|
+
* Saves and loads flow execution state to/from the filesystem,
|
|
7
|
+
* enabling resume-from-checkpoint after interrupts or crashes.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
class CheckpointManager {
|
|
14
|
+
/**
|
|
15
|
+
* @param {object} options
|
|
16
|
+
* @param {string} [options.dir] – checkpoint directory (default: .elsabro/checkpoints/)
|
|
17
|
+
*/
|
|
18
|
+
constructor(options = {}) {
|
|
19
|
+
this.dir = options.dir || path.join(process.cwd(), '.elsabro', 'checkpoints');
|
|
20
|
+
this._seq = 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Save a checkpoint to disk.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} flowId – flow identifier
|
|
27
|
+
* @param {object} checkpointData – { currentNode, nextNode, context, nodesVisited }
|
|
28
|
+
* @returns {string} checkpoint filename
|
|
29
|
+
*/
|
|
30
|
+
save(flowId, checkpointData) {
|
|
31
|
+
fs.mkdirSync(this.dir, { recursive: true });
|
|
32
|
+
|
|
33
|
+
const timestamp = Date.now();
|
|
34
|
+
this._seq++;
|
|
35
|
+
const filename = `${flowId}-${timestamp}-${String(this._seq).padStart(4, '0')}.json`;
|
|
36
|
+
const filepath = path.join(this.dir, filename);
|
|
37
|
+
|
|
38
|
+
const payload = {
|
|
39
|
+
flowId,
|
|
40
|
+
timestamp,
|
|
41
|
+
createdAt: new Date().toISOString(),
|
|
42
|
+
...checkpointData
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
fs.writeFileSync(filepath, JSON.stringify(payload, null, 2));
|
|
46
|
+
return filename;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Load the latest checkpoint for a flow.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} flowId
|
|
53
|
+
* @returns {object|null} checkpoint data, or null if none found
|
|
54
|
+
*/
|
|
55
|
+
load(flowId) {
|
|
56
|
+
if (!fs.existsSync(this.dir)) return null;
|
|
57
|
+
|
|
58
|
+
const files = fs.readdirSync(this.dir)
|
|
59
|
+
.filter(f => f.startsWith(`${flowId}-`) && f.endsWith('.json'))
|
|
60
|
+
.sort()
|
|
61
|
+
.reverse();
|
|
62
|
+
|
|
63
|
+
if (files.length === 0) return null;
|
|
64
|
+
|
|
65
|
+
const filepath = path.join(this.dir, files[0]);
|
|
66
|
+
const data = JSON.parse(fs.readFileSync(filepath, 'utf8'));
|
|
67
|
+
return data;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* List all checkpoints for a flow.
|
|
72
|
+
*
|
|
73
|
+
* @param {string} flowId
|
|
74
|
+
* @returns {{ filename: string, timestamp: number }[]}
|
|
75
|
+
*/
|
|
76
|
+
list(flowId) {
|
|
77
|
+
if (!fs.existsSync(this.dir)) return [];
|
|
78
|
+
|
|
79
|
+
return fs.readdirSync(this.dir)
|
|
80
|
+
.filter(f => f.startsWith(`${flowId}-`) && f.endsWith('.json'))
|
|
81
|
+
.map(filename => {
|
|
82
|
+
const match = filename.match(/-(\d+)-\d+\.json$/);
|
|
83
|
+
return {
|
|
84
|
+
filename,
|
|
85
|
+
timestamp: match ? parseInt(match[1], 10) : 0
|
|
86
|
+
};
|
|
87
|
+
})
|
|
88
|
+
.sort((a, b) => b.timestamp - a.timestamp);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Remove old checkpoints, keeping only the most recent N.
|
|
93
|
+
*
|
|
94
|
+
* @param {string} flowId
|
|
95
|
+
* @param {number} [keepLast=5]
|
|
96
|
+
*/
|
|
97
|
+
clean(flowId, keepLast = 5) {
|
|
98
|
+
const checkpoints = this.list(flowId);
|
|
99
|
+
const toRemove = checkpoints.slice(keepLast);
|
|
100
|
+
|
|
101
|
+
for (const cp of toRemove) {
|
|
102
|
+
const filepath = path.join(this.dir, cp.filename);
|
|
103
|
+
if (fs.existsSync(filepath)) {
|
|
104
|
+
fs.unlinkSync(filepath);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return toRemove.length;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = { CheckpointManager };
|