specleap-framework 2.0.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 (47) hide show
  1. package/.agents/backend.md +419 -0
  2. package/.agents/frontend.md +577 -0
  3. package/.agents/producto.md +516 -0
  4. package/.commands/adoptar.md +323 -0
  5. package/.commands/ayuda.md +142 -0
  6. package/.commands/crear-tickets.md +55 -0
  7. package/.commands/documentar.md +285 -0
  8. package/.commands/explicar.md +234 -0
  9. package/.commands/implementar.md +383 -0
  10. package/.commands/inicio.md +824 -0
  11. package/.commands/nuevo/README.md +292 -0
  12. package/.commands/nuevo/questions-base.yaml +320 -0
  13. package/.commands/nuevo/responses-example.yaml +53 -0
  14. package/.commands/planificar.md +253 -0
  15. package/.commands/refinar.md +306 -0
  16. package/LICENSE +21 -0
  17. package/README.md +603 -0
  18. package/SETUP.md +351 -0
  19. package/install.sh +152 -0
  20. package/package.json +60 -0
  21. package/proyectos/_template/.gitkeep +1 -0
  22. package/proyectos/_template/ANEXOS.md +21 -0
  23. package/proyectos/_template/CONTRATO.md +26 -0
  24. package/proyectos/_template/context/.gitkeep +1 -0
  25. package/rules/development-rules.md +113 -0
  26. package/rules/environment-protection.md +97 -0
  27. package/rules/git-workflow.md +142 -0
  28. package/rules/session-protocol.md +121 -0
  29. package/scripts/README.md +129 -0
  30. package/scripts/analyze-project.sh +826 -0
  31. package/scripts/create-asana-tasks.sh +133 -0
  32. package/scripts/detect-project-type.sh +141 -0
  33. package/scripts/estimate-effort.sh +290 -0
  34. package/scripts/generate-asana-structure.sh +262 -0
  35. package/scripts/generate-contract.sh +360 -0
  36. package/scripts/generate-contrato.sh +555 -0
  37. package/scripts/install-git-hooks.sh +141 -0
  38. package/scripts/install-skills.sh +130 -0
  39. package/scripts/lib/asana-utils.sh +191 -0
  40. package/scripts/lib/jira-project-utils.sh +222 -0
  41. package/scripts/lib/questions.json +831 -0
  42. package/scripts/lib/render-contrato.py +195 -0
  43. package/scripts/lib/validate.sh +325 -0
  44. package/scripts/parse-contrato.sh +190 -0
  45. package/scripts/setup-mcp.sh +654 -0
  46. package/scripts/test-cuestionario.sh +428 -0
  47. package/setup.sh +458 -0
@@ -0,0 +1,555 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # SpecLeap — Generador de CONTRATO.md interactivo
4
+ # Fase 2.1: Cuestionario básico con guardado parcial
5
+
6
+ set -euo pipefail
7
+
8
+ # ============================================================================
9
+ # CONFIGURACIÓN
10
+ # ============================================================================
11
+
12
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
+ QUESTIONS_FILE="$SCRIPT_DIR/lib/questions.json"
14
+ SESSIONS_DIR="$SCRIPT_DIR/.sessions"
15
+ TEMPLATE_FILE="$SCRIPT_DIR/../proyectos/_template/CONTRATO.md"
16
+ TEMPLATE_LEGACY_FILE="$SCRIPT_DIR/../proyectos/_template/CONTRATO-LEGACY.md"
17
+ VALIDATE_LIB="$SCRIPT_DIR/lib/validate.sh"
18
+ ANALYZE_SCRIPT="$SCRIPT_DIR/analyze-project.sh"
19
+
20
+ # Cargar sistema i18n
21
+ source "$SCRIPT_DIR/../.specleap/i18n.sh"
22
+
23
+ # Cargar funciones de validación
24
+ source "$VALIDATE_LIB"
25
+
26
+ # Colores (si terminal lo soporta)
27
+ if [[ -t 1 ]]; then
28
+ RED='\033[0;31m'
29
+ GREEN='\033[0;32m'
30
+ YELLOW='\033[1;33m'
31
+ BLUE='\033[0;34m'
32
+ MAGENTA='\033[0;35m'
33
+ CYAN='\033[0;36m'
34
+ BOLD='\033[1m'
35
+ RESET='\033[0m'
36
+ else
37
+ RED=''
38
+ GREEN=''
39
+ YELLOW=''
40
+ BLUE=''
41
+ MAGENTA=''
42
+ CYAN=''
43
+ BOLD=''
44
+ RESET=''
45
+ fi
46
+
47
+ # Variables globales
48
+ SESSION_ID=""
49
+ SESSION_FILE=""
50
+ ANSWERS_FILE=""
51
+ PROJECT_NAME=""
52
+ PROJECT_TYPE=""
53
+ PROJECT_PATH=""
54
+ ANALYSIS_FILE=""
55
+ CURRENT_QUESTION=1
56
+ TOTAL_QUESTIONS=58
57
+ CHECKPOINT_INTERVAL=10
58
+
59
+ # Crear directorio de sesiones si no existe
60
+ mkdir -p "$SESSIONS_DIR"
61
+
62
+ # ============================================================================
63
+ # UTILIDADES
64
+ # ============================================================================
65
+
66
+ print_header() {
67
+ echo -e "${CYAN}${BOLD}"
68
+ echo "════════════════════════════════════════════════════════════════"
69
+ echo " $(t 'questionnaire.title')"
70
+ echo "════════════════════════════════════════════════════════════════"
71
+ echo -e "${RESET}"
72
+ }
73
+
74
+ print_section() {
75
+ echo -e "\n${MAGENTA}${BOLD}━━━ $1 ━━━${RESET}\n"
76
+ }
77
+
78
+ print_progress() {
79
+ local current=$1
80
+ local total=$2
81
+ local percentage=$((current * 100 / total))
82
+ local filled=$((percentage / 2))
83
+ local empty=$((50 - filled))
84
+
85
+ printf "\r${BLUE}$(t 'questionnaire.progress'): ["
86
+ printf "%${filled}s" | tr ' ' '█'
87
+ printf "%${empty}s" | tr ' ' '░'
88
+ printf "] %d/%d (%d%%)${RESET}" "$current" "$total" "$percentage"
89
+ }
90
+
91
+ print_success() {
92
+ echo -e "${GREEN}✅ $1${RESET}"
93
+ }
94
+
95
+ print_error() {
96
+ echo -e "${RED}❌ Error: $1${RESET}"
97
+ }
98
+
99
+ print_warning() {
100
+ echo -e "${YELLOW}⚠️ $1${RESET}"
101
+ }
102
+
103
+ print_info() {
104
+ echo -e "${CYAN}ℹ️ $1${RESET}"
105
+ }
106
+
107
+ # ============================================================================
108
+ # GESTIÓN DE SESIONES
109
+ # ============================================================================
110
+
111
+ generate_session_id() {
112
+ echo "contrato-$(date +%s)"
113
+ }
114
+
115
+ save_session() {
116
+ local session_data=$(cat <<EOF
117
+ {
118
+ "session_id": "$SESSION_ID",
119
+ "project_name": "$PROJECT_NAME",
120
+ "started_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
121
+ "last_checkpoint": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
122
+ "progress": {
123
+ "current_question": $CURRENT_QUESTION,
124
+ "total_questions": $TOTAL_QUESTIONS,
125
+ "percentage": $((CURRENT_QUESTION * 100 / TOTAL_QUESTIONS))
126
+ },
127
+ "answers": $(cat "$ANSWERS_FILE")
128
+ }
129
+ EOF
130
+ )
131
+ echo "$session_data" > "$SESSION_FILE"
132
+ }
133
+
134
+ save_checkpoint() {
135
+ save_session
136
+ print_success "$(t "questionnaire.checkpoint_saved") $CURRENT_QUESTION/$TOTAL_QUESTIONS)"
137
+ }
138
+
139
+ detect_incomplete_session() {
140
+ local sessions=($(ls -t "$SESSIONS_DIR"/*.json 2>/dev/null || true))
141
+
142
+ if [[ ${#sessions[@]} -eq 0 ]]; then
143
+ return 1
144
+ fi
145
+
146
+ local latest="${sessions[0]}"
147
+ local progress=$(jq -r '.progress.current_question' "$latest")
148
+ local total=$(jq -r '.progress.total_questions' "$latest")
149
+ local project=$(jq -r '.project_name' "$latest")
150
+ local session_id=$(jq -r '.session_id' "$latest")
151
+
152
+ if [[ $progress -lt $total ]]; then
153
+ echo -e "\n${YELLOW}$(t "questionnaire.session_incomplete")${RESET}"
154
+ echo -e " $(t "questionnaire.project_label") ${BOLD}$project${RESET}"
155
+ echo -e " $(t "questionnaire.progress_label") ${BOLD}$progress/$total${RESET} $(t "questionnaire.questions_label") ($(( progress * 100 / total ))%)"
156
+ echo -e " $(t "questionnaire.last_update"): $(stat -f "%Sm" -t "%Y-%m-%d %H:%M" "$latest")"
157
+ echo ""
158
+
159
+ read -p "$(t "questionnaire.continue_prompt") $((progress + 1))? $(t "questionnaire.continue_yes_no") " -n 1 -r
160
+ echo
161
+
162
+ if [[ $REPLY =~ ^[Ss]$ ]]; then
163
+ SESSION_ID="$session_id"
164
+ SESSION_FILE="$latest"
165
+ ANSWERS_FILE="$SESSIONS_DIR/$SESSION_ID-answers.json"
166
+ PROJECT_NAME="$project"
167
+ CURRENT_QUESTION=$((progress + 1))
168
+
169
+ # Cargar respuestas
170
+ jq -r '.answers' "$SESSION_FILE" > "$ANSWERS_FILE"
171
+
172
+ print_success "$(t "questionnaire.session_restored") $CURRENT_QUESTION..."
173
+ return 0
174
+ else
175
+ print_info "$(t "questionnaire.new_session")"
176
+ return 1
177
+ fi
178
+ fi
179
+
180
+ return 1
181
+ }
182
+
183
+ cleanup_old_sessions() {
184
+ # Limpiar sesiones de hace más de 7 días
185
+ find "$SESSIONS_DIR" -name "*.json" -mtime +7 -delete 2>/dev/null || true
186
+ }
187
+
188
+ # ============================================================================
189
+ # ANÁLISIS DE PROYECTO LEGACY
190
+ # ============================================================================
191
+
192
+ analyze_legacy_project() {
193
+ local project_path="$1"
194
+
195
+ print_section "$(t "session.analyzing_legacy")"
196
+
197
+ if [[ ! -d "$project_path" ]]; then
198
+ print_error "$(t "session.dir_not_found") $project_path"
199
+ return 1
200
+ fi
201
+
202
+ print_info "Analizando código en: $project_path"
203
+ print_info "Esto puede tardar unos minutos..."
204
+ echo ""
205
+
206
+ # Ejecutar analyze-project.sh
207
+ local analysis_output
208
+ if analysis_output=$(bash "$ANALYZE_SCRIPT" "$project_path" 2>&1); then
209
+ # Guardar análisis en archivo temporal
210
+ ANALYSIS_FILE="$SESSIONS_DIR/$SESSION_ID-analysis.json"
211
+ echo "$analysis_output" > "$ANALYSIS_FILE"
212
+
213
+ print_success "Análisis completado"
214
+
215
+ # Mostrar resumen
216
+ echo ""
217
+ print_info "Resumen del análisis:"
218
+ echo "$analysis_output" | head -20
219
+ echo ""
220
+
221
+ return 0
222
+ else
223
+ print_error "Fallo al analizar proyecto"
224
+ print_warning "Continuando sin análisis automático..."
225
+ return 1
226
+ fi
227
+ }
228
+
229
+ # ============================================================================
230
+ # CUESTIONARIO
231
+ # ============================================================================
232
+
233
+ ask_question() {
234
+ local q_json=$1
235
+ local q_number=$(echo "$q_json" | jq -r '.number')
236
+ local q_id=$(echo "$q_json" | jq -r '.id')
237
+ local q_text=$(echo "$q_json" | jq -r '.text')
238
+ local q_type=$(echo "$q_json" | jq -r '.type')
239
+ local q_required=$(echo "$q_json" | jq -r '.required')
240
+ local q_default=$(echo "$q_json" | jq -r '.default // ""')
241
+ local q_help=$(echo "$q_json" | jq -r '.help // ""')
242
+ local q_example=$(echo "$q_json" | jq -r '.example // ""')
243
+ local q_options=$(echo "$q_json" | jq -r '.options // [] | join(", ")')
244
+ local q_skip_if=$(echo "$q_json" | jq -c '.skip_if // null')
245
+ local q_auto_suggest=$(echo "$q_json" | jq -c '.auto_suggest // null')
246
+
247
+ # Verificar si debemos skipear esta pregunta
248
+ if should_skip_question "$q_skip_if" "$ANSWERS_FILE"; then
249
+ print_info "Pregunta $q_number skipeada (condición cumplida)"
250
+ return 0
251
+ fi
252
+
253
+ local answer=""
254
+ local valid=false
255
+ local auto_suggested=""
256
+
257
+ # Obtener auto-sugerencia si existe
258
+ if [[ "$q_auto_suggest" != "null" ]]; then
259
+ # Determinar de qué campo depende
260
+ local depends_on=""
261
+ case "$q_id" in
262
+ "stack.backend.language") depends_on="stack.backend.framework" ;;
263
+ "stack.backend.version") depends_on="stack.backend.framework" ;;
264
+ "stack.database.version") depends_on="stack.database.engine" ;;
265
+ esac
266
+
267
+ if [[ -n "$depends_on" ]]; then
268
+ auto_suggested=$(get_auto_suggest "$q_auto_suggest" "$depends_on" "$ANSWERS_FILE")
269
+ fi
270
+ fi
271
+
272
+ while [[ "$valid" == false ]]; do
273
+ echo -e "\n${BOLD}Pregunta $q_number/$TOTAL_QUESTIONS:${RESET} $q_text"
274
+
275
+ if [[ -n "$q_help" ]] && [[ "$q_help" != "null" ]]; then
276
+ echo -e "${CYAN}💡 $q_help${RESET}"
277
+ fi
278
+
279
+ if [[ -n "$q_options" ]]; then
280
+ echo -e "${CYAN}$(t 'questionnaire.options_label') $q_options${RESET}"
281
+ fi
282
+
283
+ if [[ -n "$q_example" ]] && [[ "$q_example" != "null" ]]; then
284
+ echo -e "${CYAN}$(t 'questionnaire.example_label') $q_example${RESET}"
285
+ fi
286
+
287
+ if [[ -n "$auto_suggested" ]]; then
288
+ echo -e "${GREEN}✨ Sugerencia: $auto_suggested${RESET}"
289
+ fi
290
+
291
+ if [[ "$q_required" == "false" ]] && [[ -n "$q_default" ]] && [[ "$q_default" != "null" ]] && [[ "$q_default" != "[]" ]]; then
292
+ echo -e "${CYAN}[Opcional, default: $q_default]${RESET}"
293
+ fi
294
+
295
+ read -p "> " answer
296
+
297
+ # Si está vacío y hay auto-sugerencia, usarla
298
+ if [[ -z "$answer" ]] && [[ -n "$auto_suggested" ]]; then
299
+ answer="$auto_suggested"
300
+ print_info "Usando sugerencia: $auto_suggested"
301
+ fi
302
+
303
+ # Si es opcional y está vacío, usar default
304
+ if [[ -z "$answer" ]] && [[ "$q_required" == "false" ]]; then
305
+ if [[ -n "$q_default" ]] && [[ "$q_default" != "null" ]] && [[ "$q_default" != "[]" ]]; then
306
+ answer="$q_default"
307
+ fi
308
+ valid=true
309
+ continue
310
+ fi
311
+
312
+ # Validar con validate.sh (fase 2.2)
313
+ local validation_result
314
+ validation_result=$(validate_answer "$answer" "$q_json" "$ANSWERS_FILE")
315
+ local validation_status=$?
316
+
317
+ if [[ $validation_status -eq 0 ]]; then
318
+ # Extraer valor normalizado si existe
319
+ if [[ "$validation_result" =~ ^OK:(.+)$ ]]; then
320
+ answer="${BASH_REMATCH[1]}"
321
+ fi
322
+ valid=true
323
+ else
324
+ # Extraer mensaje de error
325
+ if [[ "$validation_result" =~ ^ERROR:(.+)$ ]]; then
326
+ print_error "${BASH_REMATCH[1]}"
327
+ else
328
+ print_error "$validation_result"
329
+ fi
330
+ continue
331
+ fi
332
+ done
333
+
334
+ # Guardar respuesta
335
+ # Si el tipo es array/multiselect, convertir a JSON array
336
+ if [[ "$q_type" == "array" ]] || [[ "$q_type" == "multiselect" ]]; then
337
+ if [[ -z "$answer" ]]; then
338
+ answer="[]"
339
+ else
340
+ # Separar por comas y crear array JSON
341
+ IFS=',' read -ra items <<< "$answer"
342
+ answer="["
343
+ for i in "${!items[@]}"; do
344
+ item="${items[$i]}"
345
+ # Trim espacios
346
+ item="$(echo "$item" | xargs)"
347
+ answer+="\"$item\""
348
+ if [[ $i -lt $((${#items[@]} - 1)) ]]; then
349
+ answer+=","
350
+ fi
351
+ done
352
+ answer+="]"
353
+ fi
354
+ fi
355
+
356
+ # Guardar respuesta en archivo
357
+ if [[ "$answer" =~ ^\[.*\]$ ]]; then
358
+ jq --arg key "$q_id" --argjson val "$answer" '.[$key] = $val' "$ANSWERS_FILE" > "$ANSWERS_FILE.tmp" && mv "$ANSWERS_FILE.tmp" "$ANSWERS_FILE"
359
+ else
360
+ jq --arg key "$q_id" --arg val "$answer" '.[$key] = $val' "$ANSWERS_FILE" > "$ANSWERS_FILE.tmp" && mv "$ANSWERS_FILE.tmp" "$ANSWERS_FILE"
361
+ fi
362
+
363
+ print_progress "$CURRENT_QUESTION" "$TOTAL_QUESTIONS"
364
+ }
365
+
366
+ run_questionnaire() {
367
+ local questions=$(jq -c '.questions[]' "$QUESTIONS_FILE")
368
+ local section_current=""
369
+
370
+ while IFS= read -r question; do
371
+ local number=$(echo "$question" | jq -r '.number')
372
+
373
+ # Skip si ya pasamos esta pregunta (sesión restaurada)
374
+ if [[ $number -lt $CURRENT_QUESTION ]]; then
375
+ continue
376
+ fi
377
+
378
+ local section=$(echo "$question" | jq -r '.section')
379
+ local id=$(echo "$question" | jq -r '.id')
380
+
381
+ # Imprimir sección si cambió
382
+ if [[ "$section" != "$section_current" ]]; then
383
+ print_section "$section"
384
+ section_current="$section"
385
+ fi
386
+
387
+ # Hacer pregunta (pasar JSON completo)
388
+ ask_question "$question"
389
+
390
+ CURRENT_QUESTION=$((number + 1))
391
+
392
+ # Checkpoint cada 10 preguntas
393
+ if [[ $((number % CHECKPOINT_INTERVAL)) -eq 0 ]]; then
394
+ save_checkpoint
395
+ fi
396
+
397
+ # Acciones especiales según pregunta
398
+ case "$id" in
399
+ "project.type")
400
+ PROJECT_TYPE=$(jq -r '.["project.type"]' "$ANSWERS_FILE")
401
+ ;;
402
+ "project.path")
403
+ # Si es proyecto existente y se proporcionó ruta, analizar
404
+ if [[ "$PROJECT_TYPE" == "existente" ]]; then
405
+ PROJECT_PATH=$(jq -r '.["project.path"]' "$ANSWERS_FILE")
406
+ if [[ -n "$PROJECT_PATH" ]] && [[ "$PROJECT_PATH" != "null" ]]; then
407
+ analyze_legacy_project "$PROJECT_PATH"
408
+ fi
409
+ fi
410
+ ;;
411
+ "project.name")
412
+ PROJECT_NAME=$(jq -r '.["project.name"]' "$ANSWERS_FILE")
413
+ ;;
414
+ esac
415
+
416
+ done <<< "$questions"
417
+
418
+ # Guardar sesión final
419
+ save_session
420
+ }
421
+
422
+ # ============================================================================
423
+ # GENERACIÓN CONTRATO.md
424
+ # ============================================================================
425
+
426
+ generate_contrato() {
427
+ local project_type=$(jq -r '.["project.type"]' "$ANSWERS_FILE")
428
+
429
+ if [[ "$project_type" == "existente" ]]; then
430
+ print_section "Generando CONTRATO-LEGACY.md"
431
+ else
432
+ print_section "Generando CONTRATO.md"
433
+ fi
434
+
435
+ local project_name=$(jq -r '.["project.name"]' "$ANSWERS_FILE")
436
+ local project_dir="$SCRIPT_DIR/../proyectos/$project_name"
437
+
438
+ mkdir -p "$project_dir"
439
+ mkdir -p "$project_dir/context"
440
+
441
+ # Elegir template según tipo de proyecto
442
+ local template_to_use="$TEMPLATE_FILE"
443
+ local output_filename="CONTRATO.md"
444
+
445
+ if [[ "$project_type" == "existente" ]]; then
446
+ template_to_use="$TEMPLATE_LEGACY_FILE"
447
+ output_filename="CONTRATO-LEGACY.md"
448
+
449
+ # Copiar análisis si existe
450
+ if [[ -f "$ANALYSIS_FILE" ]]; then
451
+ cp "$ANALYSIS_FILE" "$project_dir/analisis-codigo.json"
452
+ print_info "Análisis guardado en: $project_dir/analisis-codigo.json"
453
+ fi
454
+ fi
455
+
456
+ local output_file="$project_dir/$output_filename"
457
+ local render_script="$SCRIPT_DIR/lib/render-contrato.py"
458
+
459
+ # Usar renderizador Python con Jinja2
460
+ if python3 "$render_script" "$ANSWERS_FILE" "$QUESTIONS_FILE" "$template_to_use" "$output_file" 2>&1; then
461
+ print_success "$output_filename generado exitosamente"
462
+ echo ""
463
+ echo -e "${CYAN}$(t 'questionnaire.location_label') ${BOLD}$output_file${RESET}"
464
+
465
+ if [[ "$project_type" == "existente" ]]; then
466
+ echo -e "${CYAN}$(t 'questionnaire.analysis_label') ${BOLD}$project_dir/analisis-codigo.json${RESET}"
467
+ fi
468
+
469
+ echo ""
470
+ echo -e "${CYAN}Para ver el archivo:${RESET}"
471
+ echo -e " ${BOLD}cat $output_file${RESET}"
472
+ echo ""
473
+ echo -e "${CYAN}Para editarlo:${RESET}"
474
+ echo -e " ${BOLD}code $output_file${RESET}"
475
+ echo ""
476
+ else
477
+ print_error "Fallo al generar $output_filename"
478
+ print_info "Verifica que Python3 y Jinja2 estén instalados"
479
+ return 1
480
+ fi
481
+ }
482
+
483
+ # ============================================================================
484
+ # MANEJO DE SEÑALES
485
+ # ============================================================================
486
+
487
+ trap_exit() {
488
+ echo ""
489
+ print_warning "Interrupción detectada"
490
+
491
+ if [[ $CURRENT_QUESTION -gt 1 ]] && [[ $CURRENT_QUESTION -le $TOTAL_QUESTIONS ]]; then
492
+ print_info "Guardando progreso..."
493
+ save_session
494
+ print_success "Sesión guardada. Puedes continuar más tarde ejecutando este script de nuevo."
495
+ fi
496
+
497
+ exit 0
498
+ }
499
+
500
+ trap trap_exit INT TERM
501
+
502
+ # ============================================================================
503
+ # MAIN
504
+ # ============================================================================
505
+
506
+ main() {
507
+ print_header
508
+
509
+ cleanup_old_sessions
510
+
511
+ # Detectar sesión incompleta
512
+ if ! detect_incomplete_session; then
513
+ # Nueva sesión
514
+ SESSION_ID=$(generate_session_id)
515
+ SESSION_FILE="$SESSIONS_DIR/$SESSION_ID.json"
516
+ ANSWERS_FILE="$SESSIONS_DIR/$SESSION_ID-answers.json"
517
+
518
+ echo -e "$(t "questionnaire.intro_line1") ${BOLD}$TOTAL_QUESTIONS $(t "questionnaire.questions_label")${RESET}"
519
+ echo -e "$(t "questionnaire.intro_line2")"
520
+ echo ""
521
+ echo -e "$(t 'questionnaire.estimated_time')"
522
+ echo -e "$(t 'questionnaire.auto_save')"
523
+ echo -e "$(t 'questionnaire.interrupt_msg')"
524
+ echo ""
525
+
526
+ read -p "¿Listo para comenzar? [s/N]: " -n 1 -r
527
+ echo
528
+
529
+ if ! [[ $REPLY =~ ^[Ss]$ ]]; then
530
+ echo "Cancelado."
531
+ exit 0
532
+ fi
533
+
534
+ # Inicializar archivo de respuestas
535
+ echo "{}" > "$ANSWERS_FILE"
536
+ fi
537
+
538
+ # Ejecutar cuestionario
539
+ run_questionnaire
540
+
541
+ echo ""
542
+ print_success "¡Cuestionario completado! 🎉"
543
+ echo ""
544
+
545
+ # Generar CONTRATO.md
546
+ generate_contrato
547
+
548
+ # Limpiar sesión
549
+ rm -f "$SESSION_FILE" "$ANSWERS_FILE"
550
+
551
+ print_success "¡Listo! 🚀"
552
+ }
553
+
554
+ # Ejecutar
555
+ main "$@"
@@ -0,0 +1,141 @@
1
+ #!/bin/bash
2
+ # Install Git hooks for SpecLeap projects
3
+ # Usage: bash scripts/install-git-hooks.sh [project-path]
4
+
5
+ set -e
6
+
7
+ PROJECT_PATH="${1:-.}"
8
+ HOOKS_DIR="$PROJECT_PATH/.git/hooks"
9
+
10
+ if [ ! -d "$PROJECT_PATH/.git" ]; then
11
+ echo "❌ Error: No es un repositorio Git"
12
+ echo " Ejecuta desde la raíz del proyecto o pasa la ruta como argumento"
13
+ exit 1
14
+ fi
15
+
16
+ echo "🔧 Instalando Git hooks en: $PROJECT_PATH"
17
+
18
+ # Crear pre-commit hook
19
+ cat > "$HOOKS_DIR/pre-commit" << 'EOF'
20
+ #!/bin/bash
21
+ # SpecLeap Pre-Commit Hook
22
+ # Validación rápida antes de cada commit
23
+
24
+ RED='\033[0;31m'
25
+ GREEN='\033[0;32m'
26
+ YELLOW='\033[1;33m'
27
+ NC='\033[0m'
28
+
29
+ echo "🔍 SpecLeap: Validando commit..."
30
+
31
+ ERRORS=0
32
+
33
+ # 1. Detectar archivos staged
34
+ STAGED_JS=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|ts|jsx|tsx)$' || true)
35
+ STAGED_PHP=$(git diff --cached --name-only --diff-filter=ACM | grep '\.php$' || true)
36
+ STAGED_SPECS=$(git diff --cached --name-only --diff-filter=ACM | grep -E 'CONTRATO\.md|ANEXOS\.md|openspec/.*\.md$' || true)
37
+
38
+ # 2. Linters JavaScript/TypeScript
39
+ if [ -n "$STAGED_JS" ]; then
40
+ echo " → Validando JS/TS..."
41
+
42
+ if command -v eslint &> /dev/null; then
43
+ if ! eslint $STAGED_JS; then
44
+ echo -e "${RED}❌ ESLint encontró errores${NC}"
45
+ ERRORS=$((ERRORS + 1))
46
+ fi
47
+ fi
48
+
49
+ if command -v prettier &> /dev/null; then
50
+ if ! prettier --check $STAGED_JS 2>/dev/null; then
51
+ echo -e "${YELLOW}⚠️ Prettier: código no formateado${NC}"
52
+ echo " Ejecuta: prettier --write $STAGED_JS"
53
+ ERRORS=$((ERRORS + 1))
54
+ fi
55
+ fi
56
+ fi
57
+
58
+ # 3. Linters PHP
59
+ if [ -n "$STAGED_PHP" ]; then
60
+ echo " → Validando PHP..."
61
+
62
+ # PHP Syntax Check
63
+ for file in $STAGED_PHP; do
64
+ if ! php -l "$file" > /dev/null 2>&1; then
65
+ echo -e "${RED}❌ Error de sintaxis PHP: $file${NC}"
66
+ ERRORS=$((ERRORS + 1))
67
+ fi
68
+ done
69
+
70
+ # PHPStan (si está instalado)
71
+ if command -v phpstan &> /dev/null || [ -f vendor/bin/phpstan ]; then
72
+ PHPSTAN_CMD=$(command -v phpstan || echo "vendor/bin/phpstan")
73
+ if ! $PHPSTAN_CMD analyze $STAGED_PHP --level=5 --no-progress 2>/dev/null; then
74
+ echo -e "${RED}❌ PHPStan encontró errores${NC}"
75
+ ERRORS=$((ERRORS + 1))
76
+ fi
77
+ fi
78
+
79
+ # PHP CS Fixer (si está instalado)
80
+ if command -v php-cs-fixer &> /dev/null || [ -f vendor/bin/php-cs-fixer ]; then
81
+ FIXER_CMD=$(command -v php-cs-fixer || echo "vendor/bin/php-cs-fixer")
82
+ if ! $FIXER_CMD fix $STAGED_PHP --dry-run --diff 2>/dev/null; then
83
+ echo -e "${YELLOW}⚠️ PHP CS Fixer: código no formateado${NC}"
84
+ echo " Ejecuta: $FIXER_CMD fix $STAGED_PHP"
85
+ ERRORS=$((ERRORS + 1))
86
+ fi
87
+ fi
88
+ fi
89
+
90
+ # 4. Validar specs
91
+ if [ -n "$STAGED_SPECS" ]; then
92
+ echo " → Validando specs..."
93
+
94
+ # Verificar que CONTRATO.md no se modifica (debe ser inmutable)
95
+ if echo "$STAGED_SPECS" | grep -q "CONTRATO\.md"; then
96
+ echo -e "${RED}❌ CONTRATO.md es inmutable${NC}"
97
+ echo " Usa ANEXOS.md para agregar funcionalidades"
98
+ ERRORS=$((ERRORS + 1))
99
+ fi
100
+ fi
101
+
102
+ # 5. Prevenir commits con TODOs críticos
103
+ TODO_FILES=$(git diff --cached --name-only --diff-filter=ACM | xargs grep -l "TODO.*CRITICAL\|FIXME.*URGENT" 2>/dev/null || true)
104
+ if [ -n "$TODO_FILES" ]; then
105
+ echo -e "${RED}❌ Commits contienen TODOs críticos:${NC}"
106
+ echo "$TODO_FILES"
107
+ ERRORS=$((ERRORS + 1))
108
+ fi
109
+
110
+ # 6. Prevenir commits con console.log / var_dump
111
+ DEBUG_FILES=$(git diff --cached --name-only --diff-filter=ACM | xargs grep -l "console\.log\|var_dump\|dd(" 2>/dev/null || true)
112
+ if [ -n "$DEBUG_FILES" ]; then
113
+ echo -e "${YELLOW}⚠️ Archivos contienen código debug:${NC}"
114
+ echo "$DEBUG_FILES"
115
+ echo " ¿Estás seguro? (Los tests pueden fallar)"
116
+ fi
117
+
118
+ # Resultado
119
+ if [ $ERRORS -gt 0 ]; then
120
+ echo ""
121
+ echo -e "${RED}❌ Pre-commit falló con $ERRORS error(es)${NC}"
122
+ echo " Corrige los errores y vuelve a hacer commit"
123
+ echo ""
124
+ echo " Si necesitas saltarte esta validación (NO recomendado):"
125
+ echo " git commit --no-verify -m \"mensaje\""
126
+ exit 1
127
+ fi
128
+
129
+ echo -e "${GREEN}✅ Validación pre-commit exitosa${NC}"
130
+ exit 0
131
+ EOF
132
+
133
+ chmod +x "$HOOKS_DIR/pre-commit"
134
+
135
+ echo "✅ Git hooks instalados exitosamente"
136
+ echo ""
137
+ echo "📝 Hook instalado: pre-commit"
138
+ echo " → Valida linters, formatters y specs antes de cada commit"
139
+ echo ""
140
+ echo "💡 Para saltarte la validación (NO recomendado):"
141
+ echo " git commit --no-verify -m \"mensaje\""