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.
- package/.agents/backend.md +419 -0
- package/.agents/frontend.md +577 -0
- package/.agents/producto.md +516 -0
- package/.commands/adoptar.md +323 -0
- package/.commands/ayuda.md +142 -0
- package/.commands/crear-tickets.md +55 -0
- package/.commands/documentar.md +285 -0
- package/.commands/explicar.md +234 -0
- package/.commands/implementar.md +383 -0
- package/.commands/inicio.md +824 -0
- package/.commands/nuevo/README.md +292 -0
- package/.commands/nuevo/questions-base.yaml +320 -0
- package/.commands/nuevo/responses-example.yaml +53 -0
- package/.commands/planificar.md +253 -0
- package/.commands/refinar.md +306 -0
- package/LICENSE +21 -0
- package/README.md +603 -0
- package/SETUP.md +351 -0
- package/install.sh +152 -0
- package/package.json +60 -0
- package/proyectos/_template/.gitkeep +1 -0
- package/proyectos/_template/ANEXOS.md +21 -0
- package/proyectos/_template/CONTRATO.md +26 -0
- package/proyectos/_template/context/.gitkeep +1 -0
- package/rules/development-rules.md +113 -0
- package/rules/environment-protection.md +97 -0
- package/rules/git-workflow.md +142 -0
- package/rules/session-protocol.md +121 -0
- package/scripts/README.md +129 -0
- package/scripts/analyze-project.sh +826 -0
- package/scripts/create-asana-tasks.sh +133 -0
- package/scripts/detect-project-type.sh +141 -0
- package/scripts/estimate-effort.sh +290 -0
- package/scripts/generate-asana-structure.sh +262 -0
- package/scripts/generate-contract.sh +360 -0
- package/scripts/generate-contrato.sh +555 -0
- package/scripts/install-git-hooks.sh +141 -0
- package/scripts/install-skills.sh +130 -0
- package/scripts/lib/asana-utils.sh +191 -0
- package/scripts/lib/jira-project-utils.sh +222 -0
- package/scripts/lib/questions.json +831 -0
- package/scripts/lib/render-contrato.py +195 -0
- package/scripts/lib/validate.sh +325 -0
- package/scripts/parse-contrato.sh +190 -0
- package/scripts/setup-mcp.sh +654 -0
- package/scripts/test-cuestionario.sh +428 -0
- 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\""
|