zugzbot-sdd 1.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.
Files changed (52) hide show
  1. package/AGENTS.md +212 -0
  2. package/README.md +112 -0
  3. package/ZUGZ.md +91 -0
  4. package/agents/aux-handyman.md +36 -0
  5. package/agents/aux-oracle.md +39 -0
  6. package/agents/sdd-archiver.md +33 -0
  7. package/agents/sdd-builder.md +29 -0
  8. package/agents/sdd-deployer.md +43 -0
  9. package/agents/sdd-explorer.md +49 -0
  10. package/agents/sdd-planner.md +59 -0
  11. package/agents/sdd-tester.md +51 -0
  12. package/agents/zugzbot.md +84 -0
  13. package/bin/zugzbot.js +249 -0
  14. package/bun.lock +259 -0
  15. package/commands/sdd-archiver.md +11 -0
  16. package/commands/sdd-builder.md +11 -0
  17. package/commands/sdd-deployer.md +12 -0
  18. package/commands/sdd-explorer.md +11 -0
  19. package/commands/sdd-planner.md +11 -0
  20. package/commands/sdd-tester.md +12 -0
  21. package/commands/sdd.md +11 -0
  22. package/eslint.config.js +51 -0
  23. package/opencode.json +121 -0
  24. package/package.json +46 -0
  25. package/plugin.json +10 -0
  26. package/plugins/plugin_sdd_core.ts +54 -0
  27. package/plugins/plugin_tui.tsx +318 -0
  28. package/sdd +1228 -0
  29. package/skills/sdd-dependency-cooldown/SKILL.md +40 -0
  30. package/skills/sdd-tree-generator/SKILL.md +40 -0
  31. package/skills-lock.json +35 -0
  32. package/tests/static/dom_structure.test.js +57 -0
  33. package/tests/static/tag_balance.test.js +74 -0
  34. package/tests/unit/harness_structure.test.js +65 -0
  35. package/tools/brain-utils.ts +122 -0
  36. package/tools/check_dependency_cooldown.ts +134 -0
  37. package/tools/index.ts +14 -0
  38. package/tools/sdd_archive_and_commit.ts +207 -0
  39. package/tools/sdd_bdd_tester.ts +163 -0
  40. package/tools/sdd_brain_sync.ts +160 -0
  41. package/tools/sdd_checkpoint.ts +142 -0
  42. package/tools/sdd_compact_context.ts +122 -0
  43. package/tools/sdd_generate_tree.ts +64 -0
  44. package/tools/sdd_install_autoskills.ts +100 -0
  45. package/tools/sdd_regression_detector.ts +241 -0
  46. package/tools/sdd_requirement_tracker.ts +236 -0
  47. package/tools/sdd_secret_scanner.ts +205 -0
  48. package/tools/sdd_spec_validator.ts +139 -0
  49. package/tools/sdd_transition.ts +375 -0
  50. package/tools/sdd_ui_auditor.ts +310 -0
  51. package/tsconfig.json +28 -0
  52. package/zugz-models.json +23 -0
package/sdd ADDED
@@ -0,0 +1,1228 @@
1
+ #!/bin/bash
2
+ # ==============================================================================
3
+ # sdd
4
+ # Spec-Driven Development (SDD) Local Control Utility
5
+ # ==============================================================================
6
+ #
7
+ # Scoped local command-line helper for SDD.
8
+ #
9
+ # Version: 1.4.0
10
+
11
+ set -e
12
+
13
+ # Ensure we run from the project root directory (parent of .openspec)
14
+ SCRIPT_DIR="$(pwd)"
15
+ cd "$SCRIPT_DIR"
16
+
17
+ # Curated HSL-tailored premium ANSI colors
18
+ COLOR_BORDER='\033[38;5;239m' # Charcoal grey
19
+ COLOR_HEADER='\033[38;5;81m' # Premium electric cyan
20
+ COLOR_MUTED='\033[38;5;244m' # Soft grey
21
+ COLOR_SUCCESS='\033[38;5;120m' # Bright vivid green
22
+ COLOR_WARNING='\033[38;5;214m' # Warm amber orange
23
+ COLOR_ERROR='\033[38;5;196m' # Deep crimson red
24
+ COLOR_ACTIVE='\033[38;5;220m' # Vibrant bright yellow
25
+ NC='\033[0m' # Reset
26
+
27
+ LOCKFILE=".openspec/sdd-lock.json"
28
+ # Fallback to older name if hidden openspec doesn't exist yet
29
+ if [ ! -f "$LOCKFILE" ]; then
30
+ LOCKFILE="openspec/sdd-lock.json"
31
+ fi
32
+
33
+ # Helper functions to parse JSON without jq dependency (maximum portability)
34
+ get_json_val() {
35
+ local key=$1
36
+ local file=$2
37
+ if [ -f "$file" ] && [ -s "$file" ]; then
38
+ local val=$(grep -o '"'"$key"'"[[:space:]]*:[[:space:]]*"[^"]*"' "$file" 2>/dev/null | head -n1 | cut -d'"' -f4)
39
+ echo "${val:-}"
40
+ else
41
+ echo ""
42
+ fi
43
+ }
44
+
45
+ get_json_num() {
46
+ local key=$1
47
+ local file=$2
48
+ if [ -f "$file" ] && [ -s "$file" ]; then
49
+ local val=$(grep -o '"'"$key"'"[[:space:]]*:[[:space:]]*[-0-9]*' "$file" 2>/dev/null | head -n1 | grep -o '[-0-9]*')
50
+ if [ -n "$val" ] && [ "$val" != "-" ]; then
51
+ echo "$val"
52
+ else
53
+ echo "0"
54
+ fi
55
+ else
56
+ echo "0"
57
+ fi
58
+ }
59
+
60
+ get_json_bool() {
61
+ local key=$1
62
+ local file=$2
63
+ if [ -f "$file" ] && [ -s "$file" ]; then
64
+ local val=$(grep -o '"'"$key"'"[[:space:]]*:[[:space:]]*[a-zA-Z]*' "$file" 2>/dev/null | head -n1 | awk -F: '{print $2}' | xargs 2>/dev/null || echo "")
65
+ if [ "$val" = "true" ] || [ "$val" = "false" ] || [ "$val" = "null" ]; then
66
+ echo "$val"
67
+ else
68
+ echo "false"
69
+ fi
70
+ else
71
+ echo "false"
72
+ fi
73
+ }
74
+
75
+ print_header() {
76
+ echo -e "${COLOR_BORDER}┌──────────────────────────────────────────────────────────────┐${NC}"
77
+ echo -e "${COLOR_BORDER}│${NC} ${COLOR_HEADER}Zugzbot SDD Monitor${NC} ${COLOR_MUTED}• Control de Metodología Local${NC} ${COLOR_BORDER}│${NC}"
78
+ echo -e "${COLOR_BORDER}└──────────────────────────────────────────────────────────────┘${NC}"
79
+ }
80
+
81
+ show_status() {
82
+ if [ ! -f "$LOCKFILE" ]; then
83
+ echo -e " ${COLOR_ERROR}❌ Error: No se encuentra el archivo de bloqueo '${LOCKFILE}'.${NC}"
84
+ echo -e " Asegúrate de inicializar el arnés o iniciar un ciclo SDD.\n"
85
+ exit 1
86
+ fi
87
+
88
+ # Read status variables
89
+ local change_name=$(get_json_val "change_name" "$LOCKFILE")
90
+ local phase_num=$(get_json_num "active_phase" "$LOCKFILE")
91
+ local subagent=$(get_json_val "active_subagent" "$LOCKFILE")
92
+ local status=$(get_json_val "status" "$LOCKFILE")
93
+ local auto_pilot=$(get_json_bool "auto_pilot" "$LOCKFILE")
94
+
95
+ echo -e " ${COLOR_MUTED}▪ Cambio Activo:${NC} ${COLOR_SUCCESS}${change_name:-Ninguno}${NC}"
96
+ echo -e " ${COLOR_MUTED}▪ Modo de Vuelo:${NC} $( [ "$auto_pilot" = "true" ] && echo -e "${COLOR_WARNING}✈ PILOTO AUTOMÁTICO (--auto)${NC}" || echo -e "${COLOR_HEADER}⚓ INTERACTIVO / ESTÁNDAR${NC}" )"
97
+ local status_upper=$(echo "${status:-idle}" | tr '[:lower:]' '[:upper:]')
98
+ echo -e " ${COLOR_MUTED}▪ Estado Actual:${NC} ${COLOR_SUCCESS}${status_upper}${NC}\n"
99
+
100
+ echo -e " ${COLOR_HEADER}📈 PROGRESO DE LOS 5 HITOS (SDD: F0 + 4 FASES):${NC}"
101
+ echo -e " ${COLOR_BORDER}──────────────────────────────────────────────────────────────${NC}"
102
+
103
+ # Define phases mapping (index 0-4 → Fase 0 to Fase 4)
104
+ local phases=(
105
+ "F0: Diagnóstico e Indexación (@sdd-explorer)"
106
+ "F1: Planificación e Interrogación (@sdd-planner)"
107
+ "F2: Construcción Lógica/Estética (@sdd-builder)"
108
+ "F3: Pruebas y Despliegue (@sdd-tester)"
109
+ "F4: Documentación y Cierre (@sdd-archiver)"
110
+ )
111
+
112
+ print_phase() {
113
+ local idx=$1
114
+ local active=$2
115
+ if [ "$idx" -lt "$active" ]; then
116
+ echo -e " ${COLOR_SUCCESS}✓${NC} ${COLOR_MUTED}${phases[$idx]}${NC}"
117
+ elif [ "$idx" -eq "$active" ]; then
118
+ echo -e " ${COLOR_ACTIVE}➡️ ⚡${NC} ${COLOR_ACTIVE}${phases[$idx]}${NC}"
119
+ else
120
+ echo -e " ${COLOR_MUTED}▪${NC} ${COLOR_MUTED}${phases[$idx]}${NC}"
121
+ fi
122
+ }
123
+
124
+ # Hito 0 (Diagnóstico e Indexación)
125
+ if [ "$phase_num" -le 0 ]; then
126
+ echo -e " ${COLOR_ACTIVE}➡️ ⚡ [Hito 0: Diagnóstico e Indexación]${NC}"
127
+ else
128
+ echo -e " ${COLOR_SUCCESS}✓ [Hito 0: Diagnóstico e Indexación]${NC}"
129
+ fi
130
+ print_phase 0 "$phase_num"
131
+ echo ""
132
+
133
+ # Hito A (Planificación e Interrogación)
134
+ if [ "$phase_num" -eq 1 ]; then
135
+ echo -e " ${COLOR_ACTIVE}➡️ ⚡ [Hito A: Planificación e Interrogatorio]${NC}"
136
+ elif [ "$phase_num" -gt 1 ]; then
137
+ echo -e " ${COLOR_SUCCESS}✓ [Hito A: Planificación e Interrogatorio]${NC}"
138
+ else
139
+ echo -e " ${COLOR_BORDER}▪ [Hito A: Planificación e Interrogatorio]${NC}"
140
+ fi
141
+ print_phase 1 "$phase_num"
142
+ echo ""
143
+
144
+ # Hito B (Construcción Lógica/Estética)
145
+ if [ "$phase_num" -eq 2 ]; then
146
+ echo -e " ${COLOR_ACTIVE}➡️ ⚡ [Hito B: Construcción Lógica/Estética]${NC}"
147
+ elif [ "$phase_num" -gt 2 ]; then
148
+ echo -e " ${COLOR_SUCCESS}✓ [Hito B: Construcción Lógica/Estética]${NC}"
149
+ else
150
+ echo -e " ${COLOR_BORDER}▪ [Hito B: Construcción Lógica/Estética]${NC}"
151
+ fi
152
+ print_phase 2 "$phase_num"
153
+ echo ""
154
+
155
+ # Hito C (Pruebas y Despliegue)
156
+ if [ "$phase_num" -eq 3 ]; then
157
+ echo -e " ${COLOR_ACTIVE}➡️ ⚡ [Hito C: Pruebas y Despliegue]${NC}"
158
+ elif [ "$phase_num" -gt 3 ]; then
159
+ echo -e " ${COLOR_SUCCESS}✓ [Hito C: Pruebas y Despliegue]${NC}"
160
+ else
161
+ echo -e " ${COLOR_BORDER}▪ [Hito C: Pruebas y Despliegue]${NC}"
162
+ fi
163
+ print_phase 3 "$phase_num"
164
+ echo ""
165
+
166
+ # Hito D (Documentación y Cierre)
167
+ if [ "$phase_num" -ge 4 ]; then
168
+ echo -e " ${COLOR_ACTIVE}➡️ ⚡ [Hito D: Documentación y Cierre]${NC}"
169
+ else
170
+ echo -e " ${COLOR_BORDER}▪ [Hito D: Documentación y Cierre]${NC}"
171
+ fi
172
+ print_phase 4 "$phase_num"
173
+
174
+ echo -e "${COLOR_BORDER} ──────────────────────────────────────────────────────────────${NC}\n"
175
+
176
+ # Print dynamic tasks if they exist in sdd-lock.json
177
+ node -e '
178
+ const fs = require("fs");
179
+ if (!fs.existsSync("'"$LOCKFILE"'")) process.exit(0);
180
+ try {
181
+ const lock = JSON.parse(fs.readFileSync("'"$LOCKFILE"'", "utf-8"));
182
+ if (!lock.tasks || lock.tasks.length === 0) process.exit(0);
183
+ console.log(" \x1b[38;5;81m📋 TAREAS DEL CAMBIO ACTIVO (Criterios de Aceptación/QA):\x1b[0m");
184
+ console.log(" \x1b[38;5;239m──────────────────────────────────────────────────────────────\x1b[0m");
185
+ lock.tasks.forEach(t => {
186
+ const statusIcon = t.status === "completed" ? "\x1b[38;5;120m[✓]\x1b[0m" : "\x1b[38;5;244m[ ]\x1b[0m";
187
+ const textStyle = t.status === "completed" ? "\x1b[38;5;244m" : "\x1b[38;5;220m";
188
+ console.log(` ${statusIcon} ${textStyle}${t.desc}\x1b[0m`);
189
+ });
190
+ console.log(" \x1b[38;5;239m──────────────────────────────────────────────────────────────\x1b[0m\n");
191
+ } catch(e) {}
192
+ '
193
+ }
194
+
195
+ perform_rollback() {
196
+ echo -e " ${COLOR_WARNING}⚠ ATENCIÓN: Se procederá a descartar todos los cambios locales${NC}"
197
+ echo -e " no guardados en Git para regresar al checkpoint limpio del ciclo SDD."
198
+ echo -n " ¿Estás seguro de continuar con el rollback? (s/n): "
199
+ read -r confirmation
200
+ if [[ "$confirmation" =~ ^[sS]$ ]]; then
201
+ echo -e " ${COLOR_MUTED}▪ Descartando modificaciones de espacio de trabajo...${NC}"
202
+ set +e
203
+ git reset --hard HEAD 2>/dev/null && git clean -fd 2>/dev/null
204
+ local git_status=$?
205
+ set -e
206
+ if [ $git_status -eq 0 ]; then
207
+ echo -e " ${COLOR_SUCCESS}✓ Espacio de trabajo restaurado limpiamente.${NC}\n"
208
+ else
209
+ echo -e " ${COLOR_MUTED}▪ No había cambios para descartar orepo no es un git repo.${NC}\n"
210
+ fi
211
+ else
212
+ echo -e " ${COLOR_MUTED}▪ Rollback abortado por el usuario.${NC}\n"
213
+ fi
214
+ }
215
+
216
+ perform_clean() {
217
+ echo -e " ${COLOR_MUTED}▪ Purgando archivos de registro temporal y logs...${NC}"
218
+ rm -f .openspec/changes/*/failure_log.md 2>/dev/null || true
219
+ rm -rf .tmp/ 2>/dev/null || true
220
+
221
+ # Reset lockfile to Phase 0 (ready for sdd-explorer)
222
+ if [ -f "$LOCKFILE" ]; then
223
+ cat > "$LOCKFILE" << 'EOF'
224
+ {
225
+ "change_name": "nuevo-cambio",
226
+ "active_phase": 0,
227
+ "active_subagent": "sdd-explorer",
228
+ "status": "idle",
229
+ "auto_pilot": false,
230
+ "iteration": 0,
231
+ "last_updated": "",
232
+ "orchestrator_mode": "delegation_only",
233
+ "direction": "forward",
234
+ "last_successful_phase": 0,
235
+ "retry_count": 0,
236
+ "corrective_loop_active": false,
237
+ "fresh_task": false,
238
+ "checkpoints": [],
239
+ "tasks": [],
240
+ "complexity": "medium",
241
+ "last_checkpoint_id": null,
242
+ "last_restored_from": null
243
+ }
244
+ EOF
245
+ fi
246
+ echo -e " ${COLOR_SUCCESS}✓ Caché SDD purgada y reseteada a Fase 0 (Diagnóstico).${NC}\n"
247
+ }
248
+
249
+ validate_schemas() {
250
+ echo -e " ${COLOR_HEADER}🔍 Validando Artefactos del Cambio Activo...${NC}"
251
+ local active_change=$(get_json_val "change_name" "$LOCKFILE")
252
+ if [ -z "$active_change" ] || [ "$active_change" = "nuevo-cambio" ]; then
253
+ echo -e " ${COLOR_WARNING}⚠ No hay un cambio de desarrollo activo registrado en el lockfile.${NC}\n"
254
+ exit 0
255
+ fi
256
+
257
+ local change_path=".openspec/changes/$active_change"
258
+ local has_errors=false
259
+
260
+ echo -ne " ▪ Validando carpeta del cambio ($change_path)... "
261
+ if [ -d "$change_path" ]; then
262
+ echo -e "${COLOR_SUCCESS}OK${NC}"
263
+ else
264
+ echo -e "${COLOR_ERROR}FALLIDO (Directorio no existe)${NC}"
265
+ has_errors=true
266
+ fi
267
+
268
+ # Check proposal.md
269
+ if [ -f "$change_path/proposal.md" ]; then
270
+ echo -ne " ▪ Validando propuesta (proposal.md)... "
271
+ if grep -q "## " "$change_path/proposal.md" 2>/dev/null; then
272
+ echo -e "${COLOR_SUCCESS}OK (Secciones estructuradas)${NC}"
273
+ else
274
+ echo -e "${COLOR_WARNING}ADVERTENCIA (Falta estructura de títulos)${NC}"
275
+ fi
276
+ fi
277
+
278
+ # Check specs/spec.md
279
+ if [ -f "$change_path/specs/spec.md" ]; then
280
+ echo -ne " ▪ Validando especificación BDD (spec.md)... "
281
+ if grep -q "Scenario:" "$change_path/specs/spec.md" 2>/dev/null || grep -q "Escenario:" "$change_path/specs/spec.md" 2>/dev/null; then
282
+ echo -e "${COLOR_SUCCESS}OK (Escenarios BDD detectados)${NC}"
283
+ else
284
+ echo -e "${COLOR_WARNING}ADVERTENCIA (Faltan palabras clave 'Scenario/Escenario')${NC}"
285
+ fi
286
+ fi
287
+
288
+ if [ "$has_errors" = "true" ]; then
289
+ echo -e " ${COLOR_ERROR}❌ Se encontraron inconsistencias estructurales en los artefactos.${NC}\n"
290
+ else
291
+ echo -e " ${COLOR_SUCCESS}✓ Todos los artefactos detectados están en orden estructural.${NC}\n"
292
+ fi
293
+ }
294
+
295
+ run_test() {
296
+ echo -e " ${COLOR_HEADER}🧪 Ejecutando Suite de Pruebas...${NC}"
297
+ echo -e " ${COLOR_BORDER}──────────────────────────────────────────────────────────────${NC}"
298
+
299
+ # Ejecutar validadores estáticos universales si existen
300
+ if [ -f "tests/static/tag_balance.js" ]; then
301
+ echo -e " ${COLOR_MUTED}▪ [Estático] Ejecutando tests/static/tag_balance.js...${NC}"
302
+ node tests/static/tag_balance.js
303
+ fi
304
+ if [ -f "tests/static/dom_structure.js" ]; then
305
+ echo -e " ${COLOR_MUTED}▪ [Estático] Ejecutando tests/static/dom_structure.js...${NC}"
306
+ node tests/static/dom_structure.js
307
+ fi
308
+
309
+ if [ -f "package.json" ]; then
310
+ if grep -q '"test"' package.json; then
311
+ npm run test
312
+ elif [ -d "node_modules/vitest" ] || grep -q '"vitest"' package.json; then
313
+ npx vitest run
314
+ elif [ -d "node_modules/jest" ] || grep -q '"jest"' package.json; then
315
+ npx jest
316
+ else
317
+ echo -e " ${COLOR_WARNING}⚠ package.json detectado pero no hay framework de test estándar configurado.${NC}"
318
+ fi
319
+ elif [ -f "requirements.txt" ] || [ -f "pyproject.toml" ] || [ -f "Pipfile" ]; then
320
+ if command -v pytest &>/dev/null; then
321
+ pytest
322
+ else
323
+ python -m unittest discover
324
+ fi
325
+ elif [ -f "go.mod" ]; then
326
+ go test ./...
327
+ elif [ -f "Cargo.toml" ]; then
328
+ cargo test
329
+ else
330
+ echo -e " ${COLOR_WARNING}⚠ No se detectó suite de pruebas automatizadas estandarizada para este stack.${NC}"
331
+ echo -e " Configura un script en tu proyecto o actualiza el Cerebro.${NC}"
332
+ fi
333
+ echo -e " ${COLOR_BORDER}──────────────────────────────────────────────────────────────${NC}"
334
+ echo -e " ${COLOR_SUCCESS}✓ Pruebas completadas exitosamente.${NC}\n"
335
+ }
336
+
337
+ run_lint() {
338
+ echo -e " ${COLOR_HEADER}🔍 Ejecutando Auditoría Estática (Linter)...${NC}"
339
+ echo -e " ${COLOR_BORDER}──────────────────────────────────────────────────────────────${NC}"
340
+
341
+ # Ejecutar validadores estáticos de balance de etiquetas e IDs duplicados si existen
342
+ if [ -f "tests/static/tag_balance.js" ]; then
343
+ echo -e " ${COLOR_MUTED}▪ [Estático] Ejecutando tests/static/tag_balance.js...${NC}"
344
+ node tests/static/tag_balance.js
345
+ fi
346
+ if [ -f "tests/static/dom_structure.js" ]; then
347
+ echo -e " ${COLOR_MUTED}▪ [Estático] Ejecutando tests/static/dom_structure.js...${NC}"
348
+ node tests/static/dom_structure.js
349
+ fi
350
+
351
+ if [ -f "package.json" ]; then
352
+ if grep -q '"lint"' package.json; then
353
+ npm run lint
354
+ elif [ -d "node_modules/eslint" ] || grep -q '"eslint"' package.json; then
355
+ npx eslint . --max-warnings=0
356
+ else
357
+ echo -e " ${COLOR_SUCCESS}✓ package.json detectado pero linter no está preconfigurado (saltando linter estático).${NC}"
358
+ fi
359
+ elif [ -f "requirements.txt" ] || [ -f "pyproject.toml" ]; then
360
+ if command -v flake8 &>/dev/null; then
361
+ flake8 .
362
+ elif command -v pylint &>/dev/null; then
363
+ pylint src/
364
+ else
365
+ echo -e " ${COLOR_SUCCESS}✓ Entorno Python sin linters estáticos instalados (saltando linter estático).${NC}"
366
+ fi
367
+ elif [ -f "go.mod" ]; then
368
+ if command -v golangci-lint &>/dev/null; then
369
+ golangci-lint run
370
+ else
371
+ go vet ./...
372
+ fi
373
+ else
374
+ echo -e " ${COLOR_SUCCESS}✓ No se requiere linter específico para este entorno.${NC}"
375
+ fi
376
+ echo -e " ${COLOR_BORDER}──────────────────────────────────────────────────────────────${NC}"
377
+ echo -e " ${COLOR_SUCCESS}✓ Linter completado sin errores críticos.${NC}\n"
378
+ }
379
+
380
+ generate_and_open_dashboard() {
381
+ echo -e " ${COLOR_HEADER}📺 Generando Dashboard Web Local Premium...${NC}"
382
+
383
+ if [ ! -f "$LOCKFILE" ]; then
384
+ echo -e " ${COLOR_ERROR}❌ Error: No se encuentra el archivo de bloqueo '${LOCKFILE}'.${NC}"
385
+ exit 1
386
+ fi
387
+
388
+ # Read status variables
389
+ local change_name=$(get_json_val "change_name" "$LOCKFILE")
390
+ local phase_num=$(get_json_num "active_phase" "$LOCKFILE")
391
+ local subagent=$(get_json_val "active_subagent" "$LOCKFILE")
392
+ local status=$(get_json_val "status" "$LOCKFILE")
393
+ local auto_pilot=$(get_json_bool "auto_pilot" "$LOCKFILE")
394
+
395
+ local dest_dir=$(dirname "$LOCKFILE")
396
+ local dashboard_file="$dest_dir/dashboard.html"
397
+
398
+ # Define phases mapping for the HTML
399
+ local phases_json=(
400
+ "F0: Diagnóstico e Indexación"
401
+ "F1: Planificación e Interrogación"
402
+ "F2: Construcción"
403
+ "F3: Pruebas"
404
+ "F4: Despliegue"
405
+ "F5: Documentación y Cierre"
406
+ )
407
+
408
+ local phases_agents=(
409
+ "@sdd-explorer"
410
+ "@sdd-planner"
411
+ "@sdd-builder"
412
+ "@sdd-tester"
413
+ "@sdd-deployer"
414
+ "@sdd-archiver"
415
+ )
416
+
417
+ # Build the phases list dynamically in HTML
418
+ local phases_html=""
419
+ local idx=0
420
+ for ((i=0; i<4; i++)); do
421
+ local phase_title="${phases_json[$i]}"
422
+ local agent_name="${phases_agents[$i]}"
423
+ local status_class="pending"
424
+ local status_icon="○"
425
+ local phase_val=$((i))
426
+
427
+ if [ "$phase_val" -lt "$phase_num" ]; then
428
+ status_class="completed"
429
+ status_icon="✓"
430
+ elif [ "$phase_val" -eq "$phase_num" ]; then
431
+ status_class="active"
432
+ status_icon="⚡"
433
+ fi
434
+
435
+ phases_html+='<div class="phase-card '$status_class'">
436
+ <div class="phase-icon">'$status_icon'</div>
437
+ <div class="phase-details">
438
+ <span class="phase-title">'$phase_title'</span>
439
+ <span class="phase-subagent">'$agent_name'</span>
440
+ </div>
441
+ </div>'
442
+ done
443
+
444
+ # Percentage calculation (6 phases: 0-5)
445
+ local percentage=$(( (phase_num * 100) / 5 ))
446
+ if [ $percentage -gt 100 ]; then percentage=100; fi
447
+
448
+ # Autopilot text & class
449
+ local autopilot_badge=""
450
+ if [ "$auto_pilot" = "true" ]; then
451
+ autopilot_badge='<span class="badge autopilot">✈ PILOTO AUTOMÁTICO</span>'
452
+ else
453
+ autopilot_badge='<span class="badge standard">⚓ INTERACTIVO</span>'
454
+ fi
455
+
456
+ # Read phase_history if available, format as HTML logs
457
+ local history_html=""
458
+ local history_file="$dest_dir/phase_history.jsonl"
459
+ if [ -f "$history_file" ]; then
460
+ history_html=$(tail -n 10 "$history_file" | while read -r line; do
461
+ local ts=$(echo "$line" | grep -o '"timestamp"[[:space:]]*:[[:space:]]*"[^"]*"' | head -n1 | cut -d'"' -f4 | cut -d'T' -f2 | cut -d'.' -f1 || echo "")
462
+ local ph=$(echo "$line" | grep -o '"phase"[[:space:]]*:[[:space:]]*[0-9]*' | head -n1 | grep -o '[0-9]*' || echo "")
463
+ local reas=$(echo "$line" | grep -o '"reason"[[:space:]]*:[[:space:]]*"[^"]*"' | head -n1 | cut -d'"' -f4 || echo "")
464
+ if [ -n "$reas" ]; then
465
+ echo '<div class="log-item"><span class="log-time">['$ts']</span> Fase '$ph': '$reas'</div>'
466
+ fi
467
+ done)
468
+ fi
469
+
470
+ if [ -z "$history_html" ]; then
471
+ history_html='<div class="log-item empty">Aún no se registran transiciones de fases en este ciclo.</div>'
472
+ fi
473
+
474
+ # Write the premium HTML dashboard
475
+ cat > "$dashboard_file" <<EOF
476
+ <!DOCTYPE html>
477
+ <html lang="es">
478
+ <head>
479
+ <meta charset="UTF-8">
480
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
481
+ <title>Zugzbot SDD Monitor & Dashboard</title>
482
+ <link rel="preconnect" href="https://fonts.googleapis.com">
483
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
484
+ <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700&display=swap" rel="stylesheet">
485
+ <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
486
+ <script>
487
+ mermaid.initialize({
488
+ startOnLoad: true,
489
+ theme: 'dark',
490
+ themeVariables: {
491
+ background: '#0f172a',
492
+ primaryColor: '#38bdf8',
493
+ primaryTextColor: '#f8fafc',
494
+ lineColor: '#475569'
495
+ }
496
+ });
497
+ </script>
498
+ <style>
499
+ :root {
500
+ --bg-main: #0b0f19;
501
+ --bg-card: #151c2c;
502
+ --bg-active: rgba(56, 189, 248, 0.08);
503
+ --border-color: #223049;
504
+ --color-primary: #38bdf8;
505
+ --color-success: #4ade80;
506
+ --color-warning: #fbbf24;
507
+ --color-text-main: #f1f5f9;
508
+ --color-text-muted: #94a3b8;
509
+ --font-family: 'Outfit', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
510
+ }
511
+
512
+ * {
513
+ box-sizing: border-box;
514
+ margin: 0;
515
+ padding: 0;
516
+ }
517
+
518
+ body {
519
+ background-color: var(--bg-main);
520
+ color: var(--color-text-main);
521
+ font-family: var(--font-family);
522
+ line-height: 1.5;
523
+ padding: 2rem;
524
+ }
525
+
526
+ header {
527
+ display: flex;
528
+ justify-content: space-between;
529
+ align-items: center;
530
+ border-bottom: 1px solid var(--border-color);
531
+ padding-bottom: 1.5rem;
532
+ margin-bottom: 2rem;
533
+ }
534
+
535
+ .header-title h1 {
536
+ font-size: 1.8rem;
537
+ font-weight: 700;
538
+ background: linear-gradient(135deg, var(--color-primary), #a855f7);
539
+ -webkit-background-clip: text;
540
+ -webkit-text-fill-color: transparent;
541
+ }
542
+
543
+ .header-title p {
544
+ color: var(--color-text-muted);
545
+ font-size: 0.95rem;
546
+ margin-top: 0.25rem;
547
+ }
548
+
549
+ .badges {
550
+ display: flex;
551
+ gap: 0.75rem;
552
+ align-items: center;
553
+ }
554
+
555
+ .badge {
556
+ padding: 0.35rem 0.75rem;
557
+ border-radius: 9999px;
558
+ font-size: 0.75rem;
559
+ font-weight: 600;
560
+ text-transform: uppercase;
561
+ letter-spacing: 0.05em;
562
+ }
563
+
564
+ .badge.autopilot {
565
+ background-color: rgba(251, 191, 36, 0.15);
566
+ color: var(--color-warning);
567
+ border: 1px solid rgba(251, 191, 36, 0.3);
568
+ }
569
+
570
+ .badge.standard {
571
+ background-color: rgba(56, 189, 248, 0.15);
572
+ color: var(--color-primary);
573
+ border: 1px solid rgba(56, 189, 248, 0.3);
574
+ }
575
+
576
+ .grid-container {
577
+ display: grid;
578
+ grid-template-columns: 1fr 1fr;
579
+ gap: 2rem;
580
+ margin-bottom: 2rem;
581
+ }
582
+
583
+ @media (max-width: 1024px) {
584
+ .grid-container {
585
+ grid-template-columns: 1fr;
586
+ }
587
+ }
588
+
589
+ .card {
590
+ background-color: var(--bg-card);
591
+ border: 1px solid var(--border-color);
592
+ border-radius: 16px;
593
+ padding: 1.5rem;
594
+ box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3);
595
+ }
596
+
597
+ .card h2 {
598
+ font-size: 1.25rem;
599
+ font-weight: 600;
600
+ margin-bottom: 1.25rem;
601
+ color: var(--color-text-main);
602
+ display: flex;
603
+ justify-content: space-between;
604
+ align-items: center;
605
+ border-bottom: 1px solid var(--border-color);
606
+ padding-bottom: 0.75rem;
607
+ }
608
+
609
+ /* Progress Bar */
610
+ .progress-section {
611
+ margin-bottom: 1.5rem;
612
+ }
613
+
614
+ .progress-info {
615
+ display: flex;
616
+ justify-content: space-between;
617
+ margin-bottom: 0.5rem;
618
+ font-size: 0.9rem;
619
+ }
620
+
621
+ .progress-bar-container {
622
+ background-color: var(--bg-main);
623
+ border-radius: 9999px;
624
+ height: 10px;
625
+ overflow: hidden;
626
+ border: 1px solid var(--border-color);
627
+ }
628
+
629
+ .progress-bar-fill {
630
+ background: linear-gradient(90deg, var(--color-primary), var(--color-success));
631
+ height: 100%;
632
+ border-radius: 9999px;
633
+ width: ${percentage}%;
634
+ transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1);
635
+ }
636
+
637
+ /* Phase Cards */
638
+ .phases-list {
639
+ display: flex;
640
+ flex-direction: column;
641
+ gap: 0.5rem;
642
+ }
643
+
644
+ .phase-card {
645
+ display: flex;
646
+ align-items: center;
647
+ padding: 0.75rem 1rem;
648
+ border-radius: 8px;
649
+ border: 1px solid transparent;
650
+ background-color: var(--bg-main);
651
+ transition: all 0.2s ease;
652
+ }
653
+
654
+ .phase-card.completed {
655
+ border-color: rgba(74, 222, 128, 0.15);
656
+ background-color: rgba(74, 222, 128, 0.02);
657
+ }
658
+
659
+ .phase-card.completed .phase-icon {
660
+ color: var(--color-success);
661
+ background-color: rgba(74, 222, 128, 0.1);
662
+ border-color: var(--color-success);
663
+ }
664
+
665
+ .phase-card.completed .phase-title {
666
+ color: var(--color-text-muted);
667
+ text-decoration: line-through;
668
+ }
669
+
670
+ .phase-card.active {
671
+ border-color: var(--color-primary);
672
+ background-color: var(--bg-active);
673
+ box-shadow: 0 0 15px rgba(56, 189, 248, 0.1);
674
+ }
675
+
676
+ .phase-card.active .phase-icon {
677
+ color: var(--color-primary);
678
+ background-color: rgba(56, 189, 248, 0.15);
679
+ border-color: var(--color-primary);
680
+ animation: pulse 2s infinite;
681
+ }
682
+
683
+ .phase-card.active .phase-title {
684
+ color: var(--color-text-main);
685
+ font-weight: 600;
686
+ }
687
+
688
+ .phase-card.pending {
689
+ opacity: 0.5;
690
+ border-color: var(--border-color);
691
+ }
692
+
693
+ .phase-icon {
694
+ display: flex;
695
+ align-items: center;
696
+ justify-content: center;
697
+ width: 28px;
698
+ height: 28px;
699
+ border-radius: 50%;
700
+ border: 1px solid var(--border-color);
701
+ margin-right: 1rem;
702
+ font-size: 0.8rem;
703
+ font-weight: 600;
704
+ color: var(--color-text-muted);
705
+ }
706
+
707
+ .phase-details {
708
+ display: flex;
709
+ flex-direction: column;
710
+ }
711
+
712
+ .phase-title {
713
+ font-size: 0.925rem;
714
+ color: var(--color-text-main);
715
+ }
716
+
717
+ .phase-subagent {
718
+ font-size: 0.75rem;
719
+ color: var(--color-text-muted);
720
+ margin-top: 0.1rem;
721
+ }
722
+
723
+ /* Log / History Styles */
724
+ .logs-container {
725
+ display: flex;
726
+ flex-direction: column;
727
+ gap: 0.5rem;
728
+ max-height: 380px;
729
+ overflow-y: auto;
730
+ padding-right: 0.5rem;
731
+ }
732
+
733
+ .log-item {
734
+ font-family: monospace;
735
+ font-size: 0.825rem;
736
+ padding: 0.6rem 0.8rem;
737
+ background-color: var(--bg-main);
738
+ border-left: 3px solid var(--color-primary);
739
+ border-radius: 0 6px 6px 0;
740
+ color: var(--color-text-main);
741
+ }
742
+
743
+ .log-item.empty {
744
+ border-left-color: var(--border-color);
745
+ color: var(--color-text-muted);
746
+ text-align: center;
747
+ padding: 2rem;
748
+ }
749
+
750
+ .log-time {
751
+ color: var(--color-primary);
752
+ margin-right: 0.5rem;
753
+ }
754
+
755
+ /* Mermaid Graph Styles */
756
+ .mermaid-card {
757
+ grid-column: 1 / -1;
758
+ }
759
+
760
+ .mermaid-container {
761
+ background-color: var(--bg-main);
762
+ border-radius: 12px;
763
+ padding: 1.5rem;
764
+ border: 1px solid var(--border-color);
765
+ display: flex;
766
+ justify-content: center;
767
+ overflow-x: auto;
768
+ }
769
+
770
+ @keyframes pulse {
771
+ 0% {
772
+ box-shadow: 0 0 0 0 rgba(56, 189, 248, 0.4);
773
+ }
774
+ 70% {
775
+ box-shadow: 0 0 0 10px rgba(56, 189, 248, 0);
776
+ }
777
+ 100% {
778
+ box-shadow: 0 0 0 0 rgba(56, 189, 248, 0);
779
+ }
780
+ }
781
+ </style>
782
+ </head>
783
+ <body>
784
+ <header>
785
+ <div class="header-title">
786
+ <h1>Zugzbot SDD Monitor</h1>
787
+ <p>Visualización del ciclo de vida Spec-Driven Development local</p>
788
+ </div>
789
+ <div class="badges">
790
+ <span class="badge" style="background-color: rgba(74, 222, 128, 0.15); color: var(--color-success); border: 1px solid rgba(74, 222, 128, 0.3);">Cambio: ${change_name:-Ninguno}</span>
791
+ ${autopilot_badge}
792
+ </div>
793
+ </header>
794
+
795
+ <div class="grid-container">
796
+ <!-- Fases del Flujo SDD -->
797
+ <div class="card">
798
+ <h2>
799
+ <span>Progreso del Flujo</span>
800
+ <span style="font-size: 0.9rem; color: var(--color-primary); font-weight: normal;">Fase ${phase_num} de 3</span>
801
+ </h2>
802
+ <div class="progress-section">
803
+ <div class="progress-info">
804
+ <span>Completado</span>
805
+ <span>${percentage}%</span>
806
+ </div>
807
+ <div class="progress-bar-container">
808
+ <div class="progress-bar-fill"></div>
809
+ </div>
810
+ </div>
811
+ <div class="phases-list">
812
+ ${phases_html}
813
+ </div>
814
+ </div>
815
+
816
+ <!-- Historial y Métricas -->
817
+ <div class="card" style="display: flex; flex-direction: column;">
818
+ <h2>Historial de Fase (phase_history.jsonl)</h2>
819
+ <div class="logs-container" style="flex-grow: 1;">
820
+ ${history_html}
821
+ </div>
822
+ </div>
823
+
824
+ <!-- Arquitectura del Ciclo SDD en Mermaid -->
825
+ <div class="card mermaid-card">
826
+ <h2>Arquitectura y Roles del Ciclo SDD</h2>
827
+ <div class="mermaid-container">
828
+ <pre class="mermaid">
829
+ graph TD
830
+ subgraph HitoA [Hito A: Planificación e Interrogación]
831
+ F1[F1: Planificación e Interrogatorio]
832
+ end
833
+ subgraph HitoB [Hito B: Construcción y Despliegue]
834
+ F1 --> F2[F2: Construcción y Despliegue]
835
+ end
836
+ subgraph HitoC [Hito C: Documentación y Cierre]
837
+ F2 --> F3[F3: Documentación y Cierre]
838
+ end
839
+
840
+ style HitoA fill:#111827,stroke:#374151,stroke-width:2px
841
+ style HitoB fill:#111827,stroke:#374151,stroke-width:2px
842
+ style HitoC fill:#111827,stroke:#374151,stroke-width:2px
843
+
844
+ classDef default fill:#1f2937,stroke:#4b5563,color:#f3f4f6;
845
+ classDef activePhase fill:#1e3a8a,stroke:#3b82f6,stroke-width:2px,color:#eff6ff;
846
+ </pre>
847
+ </div>
848
+ </div>
849
+ </div>
850
+ </body>
851
+ </html>
852
+ EOF
853
+
854
+ echo -e " ${COLOR_SUCCESS}✓ Dashboard generado en '${dashboard_file}'.${NC}"
855
+ echo -e " ${COLOR_MUTED}▪ Abriendo en tu navegador por defecto...${NC}\n"
856
+
857
+ # Open on macOS
858
+ open "$dashboard_file"
859
+ }
860
+
861
+ perform_init() {
862
+ echo -e " ${COLOR_HEADER}⚙️ Inicializando el Arnés de Agentes SDD...${NC}"
863
+ echo -e " ${COLOR_BORDER}──────────────────────────────────────────────────────────────${NC}"
864
+
865
+ # 1. Ejecutar el instalador install-plugin.sh de forma automática en el directorio actual
866
+ local installer_path="./install-plugin.sh"
867
+ if [ ! -f "$installer_path" ]; then
868
+ # Intentar buscarlo en el directorio del script
869
+ installer_path="$(dirname "$0")/../../install-plugin.sh"
870
+ fi
871
+
872
+ if [ -f "$installer_path" ]; then
873
+ echo -e " ${COLOR_MUTED}▪ Ejecutando instalador...${NC}"
874
+ bash "$installer_path" "$(pwd)"
875
+ else
876
+ echo -e " ${COLOR_ERROR}❌ Error: No se encontró 'install-plugin.sh' para la instalación automática.${NC}"
877
+ exit 1
878
+ fi
879
+
880
+ # 2. Inicializar .openspec/ e indexar en Fase 0
881
+ echo -e "\n ${COLOR_HEADER}📈 Iniciando Paso 0 (Diagnóstico e Indexación)...${NC}"
882
+ echo -e " ${COLOR_BORDER}──────────────────────────────────────────────────────────────${NC}"
883
+
884
+ # Reset lockfile a Fase 0
885
+ if [ -f "$LOCKFILE" ]; then
886
+ cat > "$LOCKFILE" << 'EOF'
887
+ {
888
+ "change_name": "nuevo-cambio",
889
+ "active_phase": 0,
890
+ "active_subagent": "sdd-explorer",
891
+ "status": "in_progress",
892
+ "auto_pilot": false,
893
+ "iteration": 0,
894
+ "last_updated": "",
895
+ "orchestrator_mode": "delegation_only",
896
+ "direction": "forward",
897
+ "last_successful_phase": 0,
898
+ "retry_count": 0,
899
+ "corrective_loop_active": false,
900
+ "fresh_task": false,
901
+ "checkpoints": [],
902
+ "tasks": [],
903
+ "complexity": "medium",
904
+ "last_checkpoint_id": null,
905
+ "last_restored_from": null
906
+ }
907
+ EOF
908
+ fi
909
+
910
+ # Crear o actualizar diagnostics.md simulando o guiando al agente sdd-explorer
911
+ local dest_dir=$(dirname "$LOCKFILE")
912
+ mkdir -p "$dest_dir/changes/nuevo-cambio"
913
+
914
+ # Generar un diagnostics.md básico si no existe para que sdd-explorer / el proyecto esté listo
915
+ local diagnostics_file="$dest_dir/changes/nuevo-cambio/diagnostics.md"
916
+ if [ ! -f "$diagnostics_file" ]; then
917
+ cat > "$diagnostics_file" << 'EOF'
918
+ # Diagnóstico Técnico del Proyecto: nuevo-cambio
919
+
920
+ Este diagnóstico fue inicializado automáticamente con `sdd init`.
921
+
922
+ ## 1. Estado del Proyecto
923
+ - Arnés de agentes SDD de Zugzbot instalado y configurado de forma óptima.
924
+ - Linters (`eslint`, `typescript`, etc.) preconfigurados e instalados en la raíz.
925
+ - El script de utilidad local `./sdd` está activo y listo para ser utilizado.
926
+
927
+ ## 2. Archivos e Infraestructura Detectados
928
+ - `package.json` (Raíz)
929
+ - `tsconfig.json` (Raíz)
930
+ - `eslint.config.js` (Raíz)
931
+ - `zugz-models.json` (Raíz)
932
+ EOF
933
+ fi
934
+
935
+ echo -e " ${COLOR_SUCCESS}✓ Arnés SDD inicializado con éxito y en Fase 0 (Diagnóstico).${NC}"
936
+ echo -e " ${COLOR_MUTED}▪ Puedes consultar el estado actual con:${NC} ${COLOR_ACTIVE}./sdd status${NC}"
937
+ echo -e " ${COLOR_MUTED}▪ Y comenzar tu ciclo de desarrollo en OpenCode llamando a:${NC} ${COLOR_ACTIVE}@sdd-explorer o @zugzbot${NC}\n"
938
+ }
939
+
940
+ show_interactive_menu() {
941
+ while true; do
942
+ clear 2>/dev/null || true
943
+ print_header
944
+ echo -e " ${COLOR_ACTIVE}Selecciona una opción del menú interactivo:${NC}\n"
945
+ echo -e " ${COLOR_HEADER}[1]${NC} 📊 Ver Estado y Progreso de Hitos"
946
+ echo -e " ${COLOR_HEADER}[2]${NC} 🔍 Validar Artefactos (BDD / Propuestas)"
947
+ echo -e " ${COLOR_HEADER}[3]${NC} 🧪 Ejecutar Pruebas Automatizadas (sdd test)"
948
+ echo -e " ${COLOR_HEADER}[4]${NC} 🧹 Ejecutar Auditoría Estática (sdd lint)"
949
+ echo -e " ${COLOR_HEADER}[5]${NC} 📺 Abrir Dashboard Web Local (sdd dashboard)"
950
+ echo -e " ${COLOR_HEADER}[6]${NC} 🔄 Hacer Rollback de Cambios Locales"
951
+ echo -e " ${COLOR_HEADER}[7]${NC} 🧹 Limpiar Caché y Reiniciar a Fase 0"
952
+ echo -e " ${COLOR_HEADER}[8]${NC} ❓ Ver Tarjeta Informativa de Comandos"
953
+ echo -e " ${COLOR_HEADER}[9]${NC} ❌ Salir del Monitor"
954
+ echo ""
955
+ echo -ne " ${COLOR_MUTED}Ingresa tu opción [1-9]:${NC} "
956
+ read -r choice
957
+ echo ""
958
+
959
+ case "$choice" in
960
+ 1)
961
+ clear 2>/dev/null || true
962
+ print_header
963
+ show_status
964
+ echo -ne " ${COLOR_MUTED}Presiona [Enter] para continuar...${NC}"
965
+ read -r
966
+ ;;
967
+ 2)
968
+ clear 2>/dev/null || true
969
+ print_header
970
+ validate_schemas
971
+ echo -ne " ${COLOR_MUTED}Presiona [Enter] para continuar...${NC}"
972
+ read -r
973
+ ;;
974
+ 3)
975
+ clear 2>/dev/null || true
976
+ print_header
977
+ run_test || true
978
+ echo -ne " ${COLOR_MUTED}Presiona [Enter] para continuar...${NC}"
979
+ read -r
980
+ ;;
981
+ 4)
982
+ clear 2>/dev/null || true
983
+ print_header
984
+ run_lint || true
985
+ echo -ne " ${COLOR_MUTED}Presiona [Enter] para continuar...${NC}"
986
+ read -r
987
+ ;;
988
+ 5)
989
+ clear 2>/dev/null || true
990
+ print_header
991
+ generate_and_open_dashboard || true
992
+ echo -ne " ${COLOR_MUTED}Presiona [Enter] para continuar...${NC}"
993
+ read -r
994
+ ;;
995
+ 6)
996
+ clear 2>/dev/null || true
997
+ print_header
998
+ perform_rollback
999
+ echo -ne " ${COLOR_MUTED}Presiona [Enter] para continuar...${NC}"
1000
+ read -r
1001
+ ;;
1002
+ 7)
1003
+ clear 2>/dev/null || true
1004
+ print_header
1005
+ perform_clean
1006
+ echo -ne " ${COLOR_MUTED}Presiona [Enter] para continuar...${NC}"
1007
+ read -r
1008
+ ;;
1009
+ 8)
1010
+ clear 2>/dev/null || true
1011
+ print_header
1012
+ echo -e " ${COLOR_HEADER}Uso no interactivo:${NC} ./sdd [comando] u oculto: ./.openspec/sdd [comando]"
1013
+ echo ""
1014
+ echo -e " ${COLOR_HEADER}Comandos r\u00e1pidos directos:${NC}"
1015
+ echo -e " ${COLOR_SUCCESS}status${NC} - Muestra el progreso del ciclo agrupado en hitos directamente"
1016
+ echo -e " ${COLOR_SUCCESS}validate${NC} - Audita estructuralmente las especificaciones"
1017
+ echo -e " ${COLOR_SUCCESS}test${NC} - Corre las pruebas locales de forma automatizada"
1018
+ echo -e " ${COLOR_SUCCESS}lint${NC} - Audita el linter de c\u00f3digo local"
1019
+ echo -e " ${COLOR_SUCCESS}dashboard${NC} - Abre el dashboard interactivo en tu navegador"
1020
+ echo -e " ${COLOR_SUCCESS}rollback${NC} - Descarta cambios Git locales no confirmados"
1021
+ echo -e " ${COLOR_SUCCESS}clean${NC} - Puga logs, archivos temporales y resetea a Fase 0"
1022
+ echo -e " ${COLOR_SUCCESS}models${NC} - Gestiona modelos de IA (apply/status/preset)"
1023
+ echo -e " ${COLOR_SUCCESS}help${NC} - Muestra esta tarjeta de comandos"
1024
+ echo ""
1025
+ echo -ne " ${COLOR_MUTED}Presiona [Enter] para continuar...${NC}"
1026
+ read -r
1027
+ ;;
1028
+ 9)
1029
+ echo -e " ${COLOR_SUCCESS}\u00a1Nos vemos! \u00c9xito en tu ciclo de desarrollo. \ud83d\ude80${NC}\n"
1030
+ break
1031
+ ;;
1032
+ *)
1033
+ echo -e " ${COLOR_ERROR}\u274c Opci\u00f3n no v\u00e1lida. Int\u00e9ntalo de nuevo.${NC}"
1034
+ sleep 1
1035
+ ;;
1036
+ esac
1037
+ done
1038
+ }
1039
+
1040
+ manage_models() {
1041
+ local subcommand="${1:-status}"
1042
+ # Resolve models file — look in project root or script dir
1043
+ local models_file="zugz-models.json"
1044
+ if [ ! -f "$models_file" ]; then
1045
+ models_file="$(dirname "$0")/../../zugz-models.json"
1046
+ fi
1047
+ if [ ! -f "$models_file" ]; then
1048
+ echo -e " ${COLOR_ERROR}\u274c No se encontró 'zugz-models.json'.${NC}"
1049
+ echo -e " Crea uno en la raíz del proyecto con el template de 8 agentes:\n"
1050
+ echo -e ' {\n "presets": {\n "default": {\n "zugzbot": "google/gemini-3.5-flash",\n "sdd-explorer": "google/gemini-3.5-flash",\n "sdd-planner": "google/gemini-3.5-flash",\n "sdd-builder": "deepseek/deepseek-v4-pro",\n "sdd-tester": "google/gemini-3.5-flash",\n "sdd-archiver": "google/gemini-3.5-flash",\n "aux-handyman": "google/gemini-3.5-flash",\n "aux-oracle": "google/gemini-3.5-flash"\n },\n "free": {\n "zugzbot": "opencode/deepseek-v4-flash-free",\n "sdd-explorer": "opencode/deepseek-v4-flash-free",\n "sdd-planner": "opencode/deepseek-v4-flash-free",\n "sdd-builder": "opencode/deepseek-v4-flash-free",\n "sdd-tester": "opencode/deepseek-v4-flash-free",\n "sdd-archiver": "opencode/deepseek-v4-flash-free",\n "aux-handyman": "opencode/deepseek-v4-flash-free",\n "aux-oracle": "opencode/deepseek-v4-flash-free"\n },\n "balanced": {\n "zugzbot": "google/gemini-3.5-flash",\n "sdd-explorer": "google/gemini-3.5-flash",\n "sdd-planner": "google/gemini-3.5-flash",\n "sdd-builder": "deepseek/deepseek-v4-pro",\n "sdd-tester": "google/gemini-3.5-flash",\n "sdd-archiver": "google/gemini-3.5-flash",\n "aux-handyman": "google/gemini-3.5-flash",\n "aux-oracle": "google/gemini-3.5-flash"\n },\n "turbo": {\n "zugzbot": "anthropic/claude-3.5-sonnet",\n "sdd-explorer": "anthropic/claude-3.5-sonnet",\n "sdd-planner": "anthropic/claude-3.5-sonnet",\n "sdd-builder": "anthropic/claude-3.5-sonnet",\n "sdd-tester": "google/gemini-3.5-flash",\n "sdd-archiver": "google/gemini-3.5-flash",\n "aux-handyman": "google/gemini-3.5-flash",\n "aux-oracle": "google/gemini-3.5-flash"\n }\n }\n }'
1051
+ exit 1
1052
+ fi
1053
+
1054
+ # Resolve agents dir — prefer local .opencode, fallback to zugz-plugin
1055
+ local agents_dir=".opencode/agents"
1056
+ if [ ! -d "$agents_dir" ]; then
1057
+ agents_dir="zugz-plugin/agents"
1058
+ fi
1059
+ if [ ! -d "$agents_dir" ]; then
1060
+ echo -e " ${COLOR_ERROR}\u274c No se encontr\u00f3 el directorio de agentes.${NC}"
1061
+ exit 1
1062
+ fi
1063
+
1064
+ case "$subcommand" in
1065
+ apply)
1066
+ echo -e " ${COLOR_HEADER}🤖 Aplicando modelos desde ${models_file}...${NC}"
1067
+ echo -e " ${COLOR_BORDER}──────────────────────────────────────────────────────────────${NC}"
1068
+ node -e '
1069
+ const fs = require("fs");
1070
+ const path = require("path");
1071
+ const modelsFile = "'"$models_file"'";
1072
+ const destAgentsDir = "'"$agents_dir"'";
1073
+ try {
1074
+ const data = JSON.parse(fs.readFileSync(modelsFile, "utf-8"));
1075
+ const models = data.presets?.default || data.presets?.balanced || data.agents || {};
1076
+ let changed = 0;
1077
+ Object.entries(models).forEach(([agentKey, modelVal]) => {
1078
+ const agentFile = path.join(destAgentsDir, `${agentKey}.md`);
1079
+ if (fs.existsSync(agentFile)) {
1080
+ let content = fs.readFileSync(agentFile, "utf-8");
1081
+ if (content.match(/^model:/m)) {
1082
+ content = content.replace(/^model:.*/m, `model: ${modelVal}`);
1083
+ fs.writeFileSync(agentFile, content, "utf-8");
1084
+ console.log(` \x1b[32m✓\x1b[0m \x1b[90m${agentKey}\x1b[0m \x1b[34m→\x1b[0m \x1b[1;36m${modelVal}\x1b[0m`);
1085
+ changed++;
1086
+ }
1087
+ } else {
1088
+ console.log(` \x1b[90m▪ ${agentKey}.md no encontrado (saltando)\x1b[0m`);
1089
+ }
1090
+ });
1091
+ console.log(" \x1b[38;5;239m──────────────────────────────────────────────────────────────\x1b[0m");
1092
+ console.log(` \x1b[32m✓ ${changed} agente(s) actualizados correctamente.\x1b[0m\n`);
1093
+ } catch (e) {
1094
+ console.log(` \x1b[31m⚠ Error aplicando modelos: ${e.message}\x1b[0m`);
1095
+ }
1096
+ '
1097
+ ;;
1098
+ status)
1099
+ echo -e " ${COLOR_HEADER}📊 Modelos actuales por agente:${NC}"
1100
+ echo -e " ${COLOR_BORDER}──────────────────────────────────────────────────────────────${NC}"
1101
+ for f in "${agents_dir}"/*.md; do
1102
+ local name model
1103
+ name=$(basename "$f" .md)
1104
+ model=$(grep '^model:' "$f" 2>/dev/null | head -n1 | sed 's/^model:[[:space:]]*//')
1105
+ if [ -n "$model" ]; then
1106
+ printf " ${COLOR_MUTED}%-20s${NC} ${COLOR_HEADER}%s${NC}\n" "$name" "$model"
1107
+ fi
1108
+ done
1109
+ echo -e " ${COLOR_BORDER}──────────────────────────────────────────────────────────────${NC}"
1110
+ echo -e " ${COLOR_MUTED}Archivo de config:${NC} ${models_file}\n"
1111
+ ;;
1112
+ preset)
1113
+ local preset_name="${2:-default}"
1114
+ echo -e " ${COLOR_HEADER}🎨 Aplicando preset '${preset_name}' desde ${models_file}...${NC}"
1115
+ echo -e " ${COLOR_BORDER}──────────────────────────────────────────────────────────────${NC}"
1116
+ node -e '
1117
+ const fs = require("fs");
1118
+ const path = require("path");
1119
+ const modelsFile = "'"$models_file"'";
1120
+ const destAgentsDir = "'"$agents_dir"'";
1121
+ const presetName = "'"$preset_name"'";
1122
+ try {
1123
+ const data = JSON.parse(fs.readFileSync(modelsFile, "utf-8"));
1124
+ const models = data.presets?.[presetName] || {};
1125
+ if (Object.keys(models).length === 0) {
1126
+ throw new Error(`Preset "${presetName}" no encontrado en el archivo de modelos.`);
1127
+ }
1128
+ let changed = 0;
1129
+ Object.entries(models).forEach(([agentKey, modelVal]) => {
1130
+ const agentFile = path.join(destAgentsDir, `${agentKey}.md`);
1131
+ if (fs.existsSync(agentFile)) {
1132
+ let content = fs.readFileSync(agentFile, "utf-8");
1133
+ if (content.match(/^model:/m)) {
1134
+ content = content.replace(/^model:.*/m, `model: ${modelVal}`);
1135
+ fs.writeFileSync(agentFile, content, "utf-8");
1136
+ console.log(` \x1b[32m✓\x1b[0m \x1b[90m${agentKey}\x1b[0m \x1b[34m→\x1b[0m \x1b[1;36m${modelVal}\x1b[0m`);
1137
+ changed++;
1138
+ }
1139
+ } else {
1140
+ console.log(` \x1b[90m▪ ${agentKey}.md no encontrado (saltando)\x1b[0m`);
1141
+ }
1142
+ });
1143
+ console.log(" \x1b[38;5;239m──────────────────────────────────────────────────────────────\x1b[0m");
1144
+ console.log(` \x1b[32m✓ Preset "${presetName}" aplicado correctamente en ${changed} agente(s).\x1b[0m\n`);
1145
+ } catch (e) {
1146
+ console.log(` \x1b[31m⚠ Error aplicando preset: ${e.message}\x1b[0m`);
1147
+ }
1148
+ '
1149
+ ;;
1150
+ *)
1151
+ echo -e " ${COLOR_ERROR}\u274c Subcomando desconocido: '${subcommand}'${NC}"
1152
+ echo -e " Uso: ./sdd models [apply|status|preset <nombre>]\n"
1153
+ exit 1
1154
+ ;;
1155
+ esac
1156
+ }
1157
+
1158
+ # Entry point
1159
+ case "$1" in
1160
+ status)
1161
+ print_header
1162
+ show_status
1163
+ ;;
1164
+ init)
1165
+ print_header
1166
+ perform_init
1167
+ ;;
1168
+ "")
1169
+ if [ -t 0 ] && [ -t 1 ]; then
1170
+ show_interactive_menu
1171
+ else
1172
+ print_header
1173
+ show_status
1174
+ fi
1175
+ ;;
1176
+ rollback)
1177
+ print_header
1178
+ perform_rollback
1179
+ ;;
1180
+ clean)
1181
+ print_header
1182
+ perform_clean
1183
+ ;;
1184
+ validate)
1185
+ print_header
1186
+ validate_schemas
1187
+ ;;
1188
+ test)
1189
+ print_header
1190
+ run_test
1191
+ ;;
1192
+ lint)
1193
+ print_header
1194
+ run_lint
1195
+ ;;
1196
+ dashboard)
1197
+ print_header
1198
+ generate_and_open_dashboard
1199
+ ;;
1200
+ models)
1201
+ print_header
1202
+ manage_models "${2:-status}" "$3"
1203
+ ;;
1204
+ help|--help|-h)
1205
+ print_header
1206
+ echo -e " ${COLOR_HEADER}Uso:${NC} ./sdd [comando] o ./.openspec/sdd [comando]"
1207
+ echo ""
1208
+ echo -e " ${COLOR_HEADER}Comandos v\u00e1lidos:${NC}"
1209
+ echo -e " ${COLOR_SUCCESS}init${NC} - Inicializa el arnés, instala dependencias/linters y corre el Paso 0"
1210
+ echo -e " ${COLOR_SUCCESS}status${NC} - Muestra la barra de progreso agrupada en hitos (por defecto)"
1211
+ echo -e " ${COLOR_SUCCESS}validate${NC} - Audita estructuralmente las especificaciones y archivos de cambio"
1212
+ echo -e " ${COLOR_SUCCESS}test${NC} - Ejecuta de forma nativa la suite de pruebas del proyecto"
1213
+ echo -e " ${COLOR_SUCCESS}lint${NC} - Ejecuta el linter o verificador est\u00e1tico de c\u00f3digo local"
1214
+ echo -e " ${COLOR_SUCCESS}dashboard${NC} - Abre el dashboard interactivo en tu navegador"
1215
+ echo -e " ${COLOR_SUCCESS}rollback${NC} - Descarta de forma segura cambios Git locales no confirmados"
1216
+ echo -e " ${COLOR_SUCCESS}clean${NC} - Purga logs de fallos, registros temporales y resetea a Fase 0"
1217
+ echo -e " ${COLOR_SUCCESS}models status${NC} - Muestra el modelo activo de cada agente"
1218
+ echo -e " ${COLOR_SUCCESS}models apply${NC} - Aplica los modelos de zugz-models.json a los agentes"
1219
+ echo -e " ${COLOR_SUCCESS}models preset X${NC} - Aplica un preset (free / balanced / turbo)"
1220
+ echo -e " ${COLOR_SUCCESS}help${NC} - Muestra esta tarjeta informativa de comandos"
1221
+ echo ""
1222
+ ;;
1223
+ *)
1224
+ echo -e "${COLOR_ERROR}Comando no reconocido: '$1'${NC}"
1225
+ echo -e "Usa './sdd help' para ver la lista de comandos disponibles."
1226
+ exit 1
1227
+ ;;
1228
+ esac