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.
- package/AGENTS.md +212 -0
- package/README.md +112 -0
- package/ZUGZ.md +91 -0
- package/agents/aux-handyman.md +36 -0
- package/agents/aux-oracle.md +39 -0
- package/agents/sdd-archiver.md +33 -0
- package/agents/sdd-builder.md +29 -0
- package/agents/sdd-deployer.md +43 -0
- package/agents/sdd-explorer.md +49 -0
- package/agents/sdd-planner.md +59 -0
- package/agents/sdd-tester.md +51 -0
- package/agents/zugzbot.md +84 -0
- package/bin/zugzbot.js +249 -0
- package/bun.lock +259 -0
- package/commands/sdd-archiver.md +11 -0
- package/commands/sdd-builder.md +11 -0
- package/commands/sdd-deployer.md +12 -0
- package/commands/sdd-explorer.md +11 -0
- package/commands/sdd-planner.md +11 -0
- package/commands/sdd-tester.md +12 -0
- package/commands/sdd.md +11 -0
- package/eslint.config.js +51 -0
- package/opencode.json +121 -0
- package/package.json +46 -0
- package/plugin.json +10 -0
- package/plugins/plugin_sdd_core.ts +54 -0
- package/plugins/plugin_tui.tsx +318 -0
- package/sdd +1228 -0
- package/skills/sdd-dependency-cooldown/SKILL.md +40 -0
- package/skills/sdd-tree-generator/SKILL.md +40 -0
- package/skills-lock.json +35 -0
- package/tests/static/dom_structure.test.js +57 -0
- package/tests/static/tag_balance.test.js +74 -0
- package/tests/unit/harness_structure.test.js +65 -0
- package/tools/brain-utils.ts +122 -0
- package/tools/check_dependency_cooldown.ts +134 -0
- package/tools/index.ts +14 -0
- package/tools/sdd_archive_and_commit.ts +207 -0
- package/tools/sdd_bdd_tester.ts +163 -0
- package/tools/sdd_brain_sync.ts +160 -0
- package/tools/sdd_checkpoint.ts +142 -0
- package/tools/sdd_compact_context.ts +122 -0
- package/tools/sdd_generate_tree.ts +64 -0
- package/tools/sdd_install_autoskills.ts +100 -0
- package/tools/sdd_regression_detector.ts +241 -0
- package/tools/sdd_requirement_tracker.ts +236 -0
- package/tools/sdd_secret_scanner.ts +205 -0
- package/tools/sdd_spec_validator.ts +139 -0
- package/tools/sdd_transition.ts +375 -0
- package/tools/sdd_ui_auditor.ts +310 -0
- package/tsconfig.json +28 -0
- 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
|