elsabro 4.0.0 → 4.2.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 (44) hide show
  1. package/README.md +61 -7
  2. package/agents/elsabro-orchestrator.md +70 -20
  3. package/agents/elsabro-ux-designer.md +70 -0
  4. package/commands/elsabro/add-phase.md +20 -0
  5. package/commands/elsabro/add-todo.md +30 -0
  6. package/commands/elsabro/audit-milestone.md +20 -0
  7. package/commands/elsabro/check-todos.md +29 -0
  8. package/commands/elsabro/complete-milestone.md +20 -0
  9. package/commands/elsabro/debug.md +76 -1
  10. package/commands/elsabro/design-ui.md +407 -0
  11. package/commands/elsabro/discuss-phase.md +20 -0
  12. package/commands/elsabro/execute.md +166 -30
  13. package/commands/elsabro/exit.md +97 -0
  14. package/commands/elsabro/help.md +7 -1
  15. package/commands/elsabro/insert-phase.md +20 -0
  16. package/commands/elsabro/list-phase-assumptions.md +20 -0
  17. package/commands/elsabro/map-codebase.md +30 -0
  18. package/commands/elsabro/new-milestone.md +23 -0
  19. package/commands/elsabro/new.md +64 -0
  20. package/commands/elsabro/pause-work.md +28 -5
  21. package/commands/elsabro/plan-milestone-gaps.md +20 -0
  22. package/commands/elsabro/plan.md +20 -0
  23. package/commands/elsabro/progress.md +8 -0
  24. package/commands/elsabro/quick.md +58 -3
  25. package/commands/elsabro/remove-phase.md +20 -0
  26. package/commands/elsabro/research-phase.md +20 -0
  27. package/commands/elsabro/resume-work.md +21 -0
  28. package/commands/elsabro/set-profile.md +38 -0
  29. package/commands/elsabro/settings.md +38 -0
  30. package/commands/elsabro/start.md +31 -1
  31. package/commands/elsabro/update.md +28 -0
  32. package/commands/elsabro/verify-work.md +94 -7
  33. package/commands/elsabro/verify.md +30 -0
  34. package/hooks/elsabro-mode.sh +102 -0
  35. package/hooks/hooks-config-updated.json +43 -7
  36. package/hooks/skill-discovery.sh +38 -8
  37. package/package.json +5 -5
  38. package/references/SYSTEM_INDEX.md +9 -0
  39. package/references/agent-teams-integration.md +99 -36
  40. package/references/enforcement-rules.md +177 -62
  41. package/references/flow-orchestration.md +127 -32
  42. package/references/next-step-engine.md +134 -0
  43. package/skills/stitch-ui-design.md +242 -0
  44. package/templates/session-state.json +3 -1
@@ -12,6 +12,9 @@ allowed-tools:
12
12
  - TaskUpdate
13
13
  - TaskList
14
14
  - TaskGet
15
+ - TeamCreate
16
+ - TeamDelete
17
+ - SendMessage
15
18
  - AskUserQuestion
16
19
  sync:
17
20
  reads: [".elsabro/state.json"]
@@ -24,6 +27,9 @@ dispatcher:
24
27
  model: opus
25
28
  parallel: true
26
29
  min_agents: 3
30
+ method: "agent-team"
31
+ blocking: true
32
+ max_review_iterations: 5
27
33
  ---
28
34
 
29
35
  # /elsabro:verify-work
@@ -50,6 +56,7 @@ TaskUpdate(id, status: "in_progress")
50
56
  const state = Read(".elsabro/state.json") || createInitialState();
51
57
 
52
58
  // 3. Verificar contexto de execute (archivos a verificar)
59
+ state.context = state.context || {};
53
60
  if (state.context.changed_files && state.context.changed_files.length > 0) {
54
61
  console.log("Archivos a verificar:", state.context.changed_files);
55
62
  console.log("Commits a revisar:", state.context.commits);
@@ -60,6 +67,7 @@ if (state.context.changed_files && state.context.changed_files.length > 0) {
60
67
 
61
68
  // 4. Actualizar estado
62
69
  state.current_flow = { command: "verify-work", phase: "initializing", started_at: new Date().toISOString() };
70
+ state.elsabro_mode = true;
63
71
  Write(".elsabro/state.json", JSON.stringify(state, null, 2));
64
72
 
65
73
  // 5. Completar task de inicialización
@@ -90,12 +98,13 @@ state.history.push({
90
98
  });
91
99
 
92
100
  // Actualizar contexto
101
+ state.context = state.context || {};
93
102
  state.context.verification_result = verificationResult;
94
103
  state.context.ready_for_merge = verificationResult === "PASS";
95
104
 
96
105
  // Limpiar flujo y sugerir siguiente
97
106
  state.current_flow = null;
98
- state.suggested_next = verificationResult === "PASS" ? null : "execute"; // null = done
107
+ state.suggested_next = verificationResult === "PASS" ? "plan" : "execute"; // plan = next phase
99
108
 
100
109
  Write(".elsabro/state.json", JSON.stringify(state, null, 2));
101
110
 
@@ -133,6 +142,26 @@ Además de los checks estándar, verificar contra:
133
142
  - `.planning/CLAUDE.md` - ¿Se respetaron las reglas del proyecto?
134
143
  </review_integration>
135
144
 
145
+ <agent_teams_gate>
146
+ ## Agent Teams Gate (OBLIGATORIO)
147
+
148
+ verify-work usa 3 agentes OPUS en paralelo → Agent Teams es OBLIGATORIO.
149
+
150
+ ```
151
+ ¿3 agentes de verificación necesarios?
152
+
153
+ └─ SÍ → TeamCreate("elsabro-verify")
154
+ Task(team_name: "elsabro-verify", name: "reviewer-1")
155
+ Task(team_name: "elsabro-verify", name: "reviewer-2")
156
+ Task(team_name: "elsabro-verify", name: "reviewer-3")
157
+ ... verificación ...
158
+ SendMessage(shutdown_request) x3
159
+ TeamDelete()
160
+ ```
161
+
162
+ **VIOLACIÓN CRÍTICA**: Lanzar 3 verificadores sin Agent Teams = ABORTAR
163
+ </agent_teams_gate>
164
+
136
165
  ## Propósito
137
166
 
138
167
  Verificar que el trabajo completado cumple con los requirements, pasa todos los tests, y está listo para merge/deploy.
@@ -322,27 +351,50 @@ TaskUpdate({ taskId: "functional-id", status: "in_progress" })
322
351
  TaskUpdate({ taskId: "quality-id", status: "in_progress" })
323
352
  TaskUpdate({ taskId: "security-id", status: "in_progress" })
324
353
 
325
- // 2. Lanzar 3 agentes OPUS EN UN SOLO MENSAJE (paralelo real)
354
+ // 2. OBLIGATORIO: Crear Agent Team para verificación paralela (Rule 8)
355
+ TeamCreate({
356
+ team_name: "elsabro-verify",
357
+ description: "Verification team for phase " + phaseNumber
358
+ })
359
+
360
+ // MARCAR: Agent Teams fue usado correctamente (para GATE CHECK en siguiente_paso)
361
+ state.current_flow.parallel_agents_used = 3;
362
+ state.current_flow.agent_teams_used = true;
363
+ Write(".elsabro/state.json", JSON.stringify(state, null, 2));
364
+
365
+ // 3. Spawn 3 teammates OPUS EN PARALELO (un solo mensaje)
326
366
  Task({
327
367
  subagent_type: "pr-review-toolkit:code-reviewer",
328
- model: "opus", // ← OPUS para análisis profundo
368
+ model: "opus",
369
+ team_name: "elsabro-verify",
370
+ name: "reviewer-1",
329
371
  description: "Code review completo",
330
372
  prompt: "Revisa los cambios buscando bugs, code smells, mejores prácticas, seguridad..."
331
373
  }) |
332
374
  Task({
333
375
  subagent_type: "pr-review-toolkit:silent-failure-hunter",
334
- model: "opus", // ← OPUS para edge cases
376
+ model: "opus",
377
+ team_name: "elsabro-verify",
378
+ name: "reviewer-2",
335
379
  description: "Buscar errores ocultos",
336
380
  prompt: "Busca errores silenciosos, try/catch que ocultan errores, fallbacks problemáticos..."
337
381
  }) |
338
382
  Task({
339
383
  subagent_type: "pr-review-toolkit:pr-test-analyzer",
340
- model: "opus", // ← OPUS para análisis de tests
384
+ model: "opus",
385
+ team_name: "elsabro-verify",
386
+ name: "reviewer-3",
341
387
  description: "Analizar cobertura de tests",
342
388
  prompt: "Verifica que los tests cubren los cambios, busca casos críticos sin test..."
343
389
  })
344
390
 
345
- // 3. Al completar cada uno, marcar completed
391
+ // 4. Al completar verificación, shutdown teammates y cleanup
392
+ SendMessage({ type: "shutdown_request", recipient: "reviewer-1", content: "Verification complete" })
393
+ SendMessage({ type: "shutdown_request", recipient: "reviewer-2", content: "Verification complete" })
394
+ SendMessage({ type: "shutdown_request", recipient: "reviewer-3", content: "Verification complete" })
395
+ TeamDelete()
396
+
397
+ // 5. Al completar cada uno, marcar completed
346
398
  TaskUpdate({
347
399
  taskId: "functional-id",
348
400
  status: "completed",
@@ -434,8 +486,9 @@ Si cualquier verificador falla:
434
486
  ║ Opciones: ║
435
487
  ║ [d] /elsabro:debug - Investigar fallos ║
436
488
  ║ [f] Fix y re-verificar ║
437
- ║ [s] Saltar verificación (no recomendado) ║
438
489
  ║ [a] Abortar ║
490
+ ║ ║
491
+ ║ (NO hay opción "saltar" — v4.0.0 policy) ║
439
492
  ╚══════════════════════════════════════════════════╝
440
493
  ```
441
494
 
@@ -555,3 +608,37 @@ TaskCreate({
555
608
  }
556
609
  ```
557
610
  </parallel_coordination>
611
+
612
+ <siguiente_paso>
613
+ ## Siguiente Paso
614
+
615
+ Al completar, establecer en state.json:
616
+ ```javascript
617
+ // GATE CHECK: No proceder sin Agent Teams para fases con 2+ agentes
618
+ if (state.current_flow?.parallel_agents_used >= 2 && !state.current_flow?.agent_teams_used) {
619
+ throw new Error("AGENT_TEAMS_GATE: Cannot proceed without Agent Teams for 2+ agents");
620
+ }
621
+ if (verificationResult === "PASS") {
622
+ state.suggested_next = "plan"; // next phase
623
+ } else {
624
+ state.suggested_next = "execute"; // re-execute to fix
625
+ }
626
+ Write(".elsabro/state.json", JSON.stringify(state, null, 2));
627
+ ```
628
+
629
+ Si PASS:
630
+ ```
631
+ ## Siguiente Paso
632
+
633
+ → /elsabro:plan [N+1] — planificar la siguiente fase
634
+ → /elsabro:progress — ver el estado general
635
+ ```
636
+
637
+ Si FAIL/PARTIAL:
638
+ ```
639
+ ## Siguiente Paso
640
+
641
+ → /elsabro:execute — re-ejecutar para corregir los gaps
642
+ → /elsabro:debug — investigar problemas encontrados
643
+ ```
644
+ </siguiente_paso>
@@ -14,6 +14,18 @@ argument-hint: "[número de fase o 'manual']"
14
14
 
15
15
  # ELSABRO: Verify
16
16
 
17
+ <state_sync>
18
+ ## SINCRONIZACIÓN DE ESTADO
19
+
20
+ ### Al Iniciar
21
+ - Leer `.elsabro/state.json` (si existe)
22
+ - Activar persistent mode: `state.elsabro_mode = true`
23
+
24
+ ### Al Completar
25
+ - Registrar en `history`
26
+ - Establecer `state.suggested_next = "progress"`
27
+ </state_sync>
28
+
17
29
  <objective>
18
30
  Verificar que una fase o feature cumple con sus objetivos usando:
19
31
  - Tests automáticos
@@ -205,3 +217,21 @@ gaps:
205
217
  | medium | Documentar para después |
206
218
  | low | Opcional arreglar |
207
219
  </gap_handling>
220
+
221
+ <siguiente_paso>
222
+ ## Siguiente Paso
223
+
224
+ Al completar la verificación, establecer en state.json:
225
+ ```javascript
226
+ state.suggested_next = "progress";
227
+ Write(".elsabro/state.json", JSON.stringify(state, null, 2));
228
+ ```
229
+
230
+ Mostrar al usuario:
231
+ ```
232
+ ## Siguiente Paso
233
+
234
+ → /elsabro:progress — ver el estado general del proyecto
235
+ → /elsabro:plan [N+1] — planificar la siguiente fase
236
+ ```
237
+ </siguiente_paso>
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env bash
2
+ # ELSABRO Persistent Mode Hook - UserPromptSubmit
3
+ # Runs on every user message. Fast path: 2 checks then exit 0.
4
+ # When active: outputs routing instructions for Claude to classify intent.
5
+
6
+ set -euo pipefail
7
+
8
+ STATE_FILE=".elsabro/state.json"
9
+
10
+ # ─── Dependency Check ─────────────────────────────────────────────────
11
+
12
+ if ! command -v jq &>/dev/null; then
13
+ echo "[ELSABRO] 'jq' no instalado. Persistent mode requiere jq. Instalar: brew install jq (macOS) o apt install jq (Linux)" >&2
14
+ exit 0
15
+ fi
16
+
17
+ # ─── Consume stdin (required - hook protocol sends JSON on stdin) ─────
18
+
19
+ INPUT_JSON=$(cat)
20
+
21
+ # ─── Fast Path (< 5ms) ───────────────────────────────────────────────
22
+
23
+ # 1. No state file? Not in ELSABRO → pass through
24
+ if [ ! -f "$STATE_FILE" ]; then
25
+ exit 0
26
+ fi
27
+
28
+ # 2. Check elsabro_mode (with validation for empty/corrupt files)
29
+ ELSABRO_MODE=$(jq -r '.elsabro_mode // false' "$STATE_FILE" 2>/dev/null || echo "false")
30
+ if [ -z "$ELSABRO_MODE" ]; then
31
+ echo "[ELSABRO] state.json existe pero no se pudo leer elsabro_mode. Archivo corrupto?" >&2
32
+ exit 0
33
+ fi
34
+ if [ "$ELSABRO_MODE" != "true" ]; then
35
+ exit 0
36
+ fi
37
+
38
+ # 3. Parse user message from stdin JSON (only when active - avoids jq on fast path)
39
+ USER_MESSAGE=$(echo "$INPUT_JSON" | jq -r '.prompt // ""' 2>/dev/null || echo "")
40
+
41
+ # 4. Slash command? Match /command or /command:sub pattern, not file paths like /api/users
42
+ # Slash commands: /commit, /elsabro:plan, /help (single token, no embedded /)
43
+ # NOT slash commands: /api/users is broken, /usr/bin/node
44
+ if [[ "$USER_MESSAGE" =~ ^/[a-zA-Z][a-zA-Z0-9_:-]*($|[[:space:]]) ]]; then
45
+ exit 0
46
+ fi
47
+
48
+ # ─── Active Path: ELSABRO is ON ──────────────────────────────────────
49
+
50
+ # Read session context for routing
51
+ # Use single jq call to extract all fields at once (performance + atomic read)
52
+ CONTEXT_JSON=$(jq -r '{
53
+ cmd: (.current_flow.command // "none"),
54
+ ptype: (.context.project_type // "unknown"),
55
+ stack: ((.context.tech_stack // []) | if type == "array" then join(", ") elif type == "string" then . else "" end)
56
+ }' "$STATE_FILE" 2>/dev/null || echo '{"cmd":"none","ptype":"unknown","stack":""}')
57
+
58
+ CURRENT_COMMAND=$(echo "$CONTEXT_JSON" | jq -r '.cmd // "none"' 2>/dev/null || echo "none")
59
+ PROJECT_TYPE=$(echo "$CONTEXT_JSON" | jq -r '.ptype // "unknown"' 2>/dev/null || echo "unknown")
60
+ TECH_STACK=$(echo "$CONTEXT_JSON" | jq -r '.stack // ""' 2>/dev/null || echo "")
61
+
62
+ # Output routing instructions (quoted heredoc prevents shell expansion for security)
63
+ cat <<'ROUTING_HEADER'
64
+ ELSABRO PERSISTENT MODE ACTIVE
65
+
66
+ You are in ELSABRO persistent mode. The user's message should be routed to the appropriate ELSABRO command based on intent classification.
67
+
68
+ ## Current Session
69
+ ROUTING_HEADER
70
+ # Variable lines use printf to avoid shell expansion attacks from state.json content
71
+ printf -- '- Active command: %s\n' "$CURRENT_COMMAND"
72
+ printf -- '- Project type: %s\n' "$PROJECT_TYPE"
73
+ printf -- '- Tech stack: %s\n' "$TECH_STACK"
74
+ cat <<'ROUTING_BODY'
75
+
76
+ ## Intent Routing Table
77
+
78
+ Classify the user's message and invoke the matching ELSABRO skill using the Skill tool:
79
+
80
+ | If the user wants to... | Invoke |
81
+ |--------------------------------------------------|---------------------------------------|
82
+ | Fix a bug, error, crash, or investigate a problem | Skill("elsabro:debug") |
83
+ | Create a new feature, add something, build | Skill("elsabro:plan") |
84
+ | Understand, explain, or explore the codebase | Skill("elsabro:map-codebase") |
85
+ | Make a quick/small/simple change | Skill("elsabro:quick") |
86
+ | Refactor, improve, or clean up code | Skill("elsabro:plan") |
87
+ | Verify, test, check, or validate work | Skill("elsabro:verify-work") |
88
+ | Resume, continue, or pick up where they left off | Skill("elsabro:resume-work") |
89
+ | Design UI, create mockup, wireframe, or screen | Skill("elsabro:design-ui") |
90
+ | Exit, leave, stop, or deactivate ELSABRO | Skill("elsabro:exit") |
91
+ | See progress, status, or what's next | Skill("elsabro:progress") |
92
+ | Pause work or take a break | Skill("elsabro:pause-work") |
93
+
94
+ ## Rules
95
+
96
+ 1. ALWAYS route through a Skill() call - never answer directly without invoking the appropriate ELSABRO command
97
+ 2. If the intent is ambiguous, ask the user to clarify with 2-3 options
98
+ 3. If the user is clearly having a casual conversation (greeting, thanks, etc.), respond naturally but remind them ELSABRO mode is active
99
+ 4. Pass the user's original message as the argument to the skill when relevant
100
+ ROUTING_BODY
101
+
102
+ exit 0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "description": "ELSABRO Hooks Configuration v3.1 - Agent Teams + Blocking Review + Skill Discovery (npx skills)",
4
- "version": "3.1.0",
3
+ "description": "ELSABRO Hooks Configuration v3.2 (hook config version) for ELSABRO v4.0.0 - Agent Teams + Blocking Review + Skill Discovery + Persistent Mode",
4
+ "version": "3.2.0",
5
5
 
6
6
  "hooks": {
7
7
  "PreFlow": [
@@ -14,10 +14,8 @@
14
14
  "command": "bash ./hooks/skill-discovery.sh \"{{inputs.task}}\" \"{{inputs.complexity}}\"",
15
15
  "timeout": 30000,
16
16
  "enabled": true,
17
- "output": {
18
- "type": "json",
19
- "inject_into": ["state.discoveredSkills", "state.skillContext"]
20
- },
17
+ "outputFormat": "json",
18
+ "outputNote": "stdout JSON is consumed by flow engine to populate state.discoveredSkills and state.skillContext",
21
19
  "errorPolicy": "continue",
22
20
  "cacheable": true,
23
21
  "cache_ttl": 3600
@@ -58,6 +56,23 @@
58
56
  }
59
57
  ],
60
58
 
59
+ "UserPromptSubmit": [
60
+ {
61
+ "id": "elsabro-persistent-mode",
62
+ "name": "elsabro-persistent-mode",
63
+ "description": "Ruteo automático de mensajes a comandos ELSABRO cuando persistent mode está activo",
64
+ "command": "bash \"$CLAUDE_PROJECT_DIR\"/hooks/elsabro-mode.sh",
65
+ "timeout": 5000,
66
+ "enabled": true,
67
+ "errorPolicy": "continue",
68
+ "errorPolicyRationale": "Hook debe fallar silenciosamente para no bloquear input del usuario. Errores se reportan via stderr.",
69
+ "fastPath": {
70
+ "description": "< 5ms cuando inactivo: no state.json, elsabro_mode=false, o mensaje empieza con /",
71
+ "checks": ["state_file_exists", "elsabro_mode_true", "not_slash_command"]
72
+ }
73
+ }
74
+ ],
75
+
61
76
  "Notification": [
62
77
  {
63
78
  "id": "task-complete-notify",
@@ -112,11 +127,32 @@
112
127
  },
113
128
 
114
129
  "teams": {
115
- "description": "Perfil para Agent Teams - coordinación peer-to-peer con Teammate/SendMessage",
130
+ "description": "Perfil para Agent Teams - coordinacion peer-to-peer con TeamCreate/TeamDelete/SendMessage",
131
+ "auto-test-on-edit": true,
132
+ "lint-on-save": true,
133
+ "confirm-destructive": true,
134
+ "skill-discovery": true,
135
+ "agent-teams": true,
136
+ "blocking-review": true,
137
+ "max-review-iterations": 5
138
+ },
139
+
140
+ "persistent-mode": {
141
+ "description": "Perfil con persistent mode habilitado - ELSABRO siempre activo, ruteo automático de mensajes",
142
+ "auto-test-on-edit": true,
143
+ "lint-on-save": true,
144
+ "confirm-destructive": true,
145
+ "skill-discovery": true,
146
+ "elsabro-persistent-mode": true
147
+ },
148
+
149
+ "persistent-teams": {
150
+ "description": "Perfil completo - persistent mode + Agent Teams + blocking review",
116
151
  "auto-test-on-edit": true,
117
152
  "lint-on-save": true,
118
153
  "confirm-destructive": true,
119
154
  "skill-discovery": true,
155
+ "elsabro-persistent-mode": true,
120
156
  "agent-teams": true,
121
157
  "blocking-review": true,
122
158
  "max-review-iterations": 5
@@ -13,6 +13,10 @@
13
13
  # 3. npx skills find <query> (vercel-labs/skills registry)
14
14
  #
15
15
  # Requiere: bash 4+, jq, node/npm (para npx)
16
+ #
17
+ # NOTA: Este script NO es safe para ejecución paralela del mismo archivo.
18
+ # Si se ejecuta concurrentemente, las escrituras a CACHE_FILE pueden colisionar.
19
+ # El flow engine debe serializar llamadas a este script.
16
20
 
17
21
  set -euo pipefail
18
22
 
@@ -53,8 +57,12 @@ log_error() { echo -e "${RED}${PREFIX}${NC} ✗ $1" >&2; }
53
57
  # ============================================================================
54
58
 
55
59
  ensure_dirs() {
56
- mkdir -p "$CACHE_DIR" 2>/dev/null || true
57
- mkdir -p "$GLOBAL_SKILLS_DIR" 2>/dev/null || true
60
+ if ! mkdir -p "$CACHE_DIR" 2>/dev/null; then
61
+ log_warn "Could not create cache directory: $CACHE_DIR"
62
+ fi
63
+ if ! mkdir -p "$GLOBAL_SKILLS_DIR" 2>/dev/null; then
64
+ log_warn "Could not create skills directory: $GLOBAL_SKILLS_DIR"
65
+ fi
58
66
  }
59
67
 
60
68
  has_command() {
@@ -80,7 +88,13 @@ is_cache_valid() {
80
88
 
81
89
  get_from_cache() {
82
90
  local cache_key="$1"
83
- jq --arg key "$cache_key" '.[$key].data' "$CACHE_FILE" 2>/dev/null
91
+ local data
92
+ data=$(jq --arg key "$cache_key" '.[$key].data' "$CACHE_FILE" 2>/dev/null)
93
+ # Guard against null/empty data from corrupt cache
94
+ if [ -z "$data" ] || [ "$data" = "null" ]; then
95
+ return 1
96
+ fi
97
+ echo "$data"
84
98
  }
85
99
 
86
100
  save_to_cache() {
@@ -89,6 +103,9 @@ save_to_cache() {
89
103
  ensure_dirs
90
104
  local now
91
105
  now=$(date +%s)
106
+ # NOTE: PID-unique tmp file mitigates but doesn't eliminate the read-modify-write
107
+ # race condition on concurrent writes. This is acceptable because skill discovery
108
+ # runs sequentially in the flow engine. Worst case: one cache entry is lost.
92
109
  local tmp_file="${CACHE_FILE}.tmp.$$"
93
110
  if [ -f "$CACHE_FILE" ] && jq empty "$CACHE_FILE" 2>/dev/null; then
94
111
  jq --arg key "$cache_key" --argjson data "$data" --argjson ts "$now" \
@@ -208,7 +225,7 @@ scan_elsabro_skills() {
208
225
 
209
226
  # Extraer metadata básica
210
227
  local description=""
211
- description=$(sed -n '/^description:/s/^description: *//p' "$skill_file" | head -1 | sed 's/"/\\"/g')
228
+ description=$(sed -n '/^description:/s/^description: *//p' "$skill_file" | head -1)
212
229
  description="${description:0:100}"
213
230
 
214
231
  local tags=""
@@ -319,6 +336,7 @@ check_skill_updates() {
319
336
 
320
337
  local check_output=""
321
338
  check_output=$(timeout "$NPX_TIMEOUT" npx -y skills check 2>/dev/null) || {
339
+ log_warn "npx skills check failed or timed out"
322
340
  echo "[]"
323
341
  return
324
342
  }
@@ -341,7 +359,11 @@ check_skill_updates() {
341
359
  local name
342
360
  name=$(echo "$line" | grep -oE '[a-z][a-z0-9_-]+' | head -1)
343
361
  [ -z "$name" ] && continue
344
- updates+=("{\"id\":\"$name\",\"update_available\":true,\"update_cmd\":\"npx skills update $name\"}")
362
+ # Use jq for safe JSON construction (avoids injection from special chars)
363
+ local update_obj
364
+ update_obj=$(jq -n --arg id "$name" --arg cmd "npx skills update $name" \
365
+ '{id: $id, update_available: true, update_cmd: $cmd}')
366
+ updates+=("$update_obj")
345
367
  done <<< "$check_output"
346
368
 
347
369
  if [ ${#updates[@]} -eq 0 ]; then
@@ -484,6 +506,12 @@ main() {
484
506
  local task="${1:-}"
485
507
  local complexity="${2:-medium}"
486
508
 
509
+ # Check required dependencies
510
+ if ! has_command jq; then
511
+ echo '{"status":"error","message":"jq is required but not installed. Install with: brew install jq (macOS) or apt-get install jq (Linux)","timestamp":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}'
512
+ exit 1
513
+ fi
514
+
487
515
  # Validate complexity
488
516
  case "$complexity" in
489
517
  low|medium|high) ;;
@@ -506,7 +534,7 @@ main() {
506
534
 
507
535
  # --- Fase 2: Cache check ---
508
536
  local cache_key
509
- cache_key=$(echo -n "${task}|${complexity}" | md5 2>/dev/null || echo -n "${task}|${complexity}" | md5sum 2>/dev/null | awk '{print $1}' || echo "nocache")
537
+ cache_key=$(echo -n "${task}|${complexity}" | md5 2>/dev/null || echo -n "${task}|${complexity}" | md5sum 2>/dev/null | awk '{print $1}' || echo -n "${task}|${complexity}" | cksum | awk '{print $1}')
510
538
 
511
539
  if is_cache_valid "$cache_key"; then
512
540
  log_success "Cache hit"
@@ -550,8 +578,10 @@ main() {
550
578
  local output
551
579
  output=$(generate_output "$keywords" "$installed" "$elsabro" "$external" "$recommended" "$updates")
552
580
 
553
- # Guardar en cache
554
- save_to_cache "$cache_key" "$output" 2>/dev/null || true
581
+ # Guardar en cache (log warning on failure, don't mask exit code)
582
+ if ! save_to_cache "$cache_key" "$output"; then
583
+ log_warn "Failed to save results to cache"
584
+ fi
555
585
 
556
586
  # Output final a STDOUT (esto captura el Flow Engine)
557
587
  echo "$output"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "elsabro",
3
- "version": "4.0.0",
4
- "description": "Sistema de desarrollo AI-powered para Claude Code - Agent Teams, blocking code review, orquestación avanzada con checkpointing, memoria multi-nivel y flows declarativos",
3
+ "version": "4.2.0",
4
+ "description": "Sistema de desarrollo AI-powered para Claude Code - Next Step Suggestions, Stitch UI Design, Agent Teams, blocking code review, orquestación avanzada con checkpointing, memoria multi-nivel y flows declarativos",
5
5
  "bin": {
6
6
  "elsabro": "bin/install.js"
7
7
  },
@@ -32,11 +32,11 @@
32
32
  "license": "MIT",
33
33
  "repository": {
34
34
  "type": "git",
35
- "url": "git+https://github.com/goat1990/elsabro.git"
35
+ "url": "git+https://github.com/goat1990/elsabro-gsd.git"
36
36
  },
37
- "homepage": "https://github.com/goat1990/elsabro",
37
+ "homepage": "https://github.com/goat1990/elsabro-gsd",
38
38
  "bugs": {
39
- "url": "https://github.com/goat1990/elsabro/issues"
39
+ "url": "https://github.com/goat1990/elsabro-gsd/issues"
40
40
  },
41
41
  "engines": {
42
42
  "node": ">=18.0.0"
@@ -425,6 +425,15 @@
425
425
  - API endpoint: `https://add-skill.vercel.sh/check-updates`
426
426
  - Comandos: /elsabro:skills search|install|update|list|config
427
427
 
428
+ ### 41. `next-step-engine.md` (v4.2)
429
+ **Propósito**: Decision matrix para sugerencias de siguiente paso
430
+ **Contiene**:
431
+ - Decision Matrix (28 commands mapped to suggested_next)
432
+ - Contextual overrides (blocked tasks, TODOs, verification results)
433
+ - Output template for "Siguiente Paso" blocks
434
+ - Fallback rule (progress as safe default)
435
+ - Integration with persistent mode routing
436
+
428
437
  ---
429
438
 
430
439
  ## ESTADO UNIFICADO