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,133 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
# SpecLeap — Crear tareas individuales en Asana
|
|
4
|
+
# Permite crear tareas rápidamente desde línea de comandos
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
ASANA_UTILS="$SCRIPT_DIR/lib/asana-utils.sh"
|
|
10
|
+
|
|
11
|
+
source "$ASANA_UTILS"
|
|
12
|
+
|
|
13
|
+
# Colores
|
|
14
|
+
GREEN='\033[0;32m'
|
|
15
|
+
RED='\033[0;31m'
|
|
16
|
+
YELLOW='\033[1;33m'
|
|
17
|
+
CYAN='\033[0;36m'
|
|
18
|
+
BOLD='\033[1m'
|
|
19
|
+
RESET='\033[0m'
|
|
20
|
+
|
|
21
|
+
usage() {
|
|
22
|
+
cat << EOF
|
|
23
|
+
Uso: $(basename "$0") [OPCIONES]
|
|
24
|
+
|
|
25
|
+
Crea tareas individuales en Asana
|
|
26
|
+
|
|
27
|
+
OPCIONES:
|
|
28
|
+
-p, --proyecto GID ID del proyecto (obligatorio)
|
|
29
|
+
-t, --titulo "TEXTO" Título de la tarea (obligatorio)
|
|
30
|
+
-s, --seccion GID ID de la sección (opcional)
|
|
31
|
+
-P, --puntos NUM Story points (default: 3)
|
|
32
|
+
-l, --listar Listar proyectos disponibles
|
|
33
|
+
-h, --help Muestra esta ayuda
|
|
34
|
+
|
|
35
|
+
EJEMPLOS:
|
|
36
|
+
# Listar proyectos:
|
|
37
|
+
$(basename "$0") --listar
|
|
38
|
+
|
|
39
|
+
# Crear tarea simple:
|
|
40
|
+
$(basename "$0") -p 1234567890 -t "Implementar login"
|
|
41
|
+
|
|
42
|
+
# Crear tarea con sección y puntos:
|
|
43
|
+
$(basename "$0") -p 1234567890 -t "Setup CI/CD" -s 9876543210 -P 5
|
|
44
|
+
|
|
45
|
+
EOF
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# Parsear argumentos
|
|
49
|
+
PROJECT_GID=""
|
|
50
|
+
TASK_NAME=""
|
|
51
|
+
SECTION_GID=""
|
|
52
|
+
POINTS=3
|
|
53
|
+
LIST_MODE=false
|
|
54
|
+
|
|
55
|
+
while [[ $# -gt 0 ]]; do
|
|
56
|
+
case $1 in
|
|
57
|
+
-p|--proyecto)
|
|
58
|
+
PROJECT_GID="$2"
|
|
59
|
+
shift 2
|
|
60
|
+
;;
|
|
61
|
+
-t|--titulo)
|
|
62
|
+
TASK_NAME="$2"
|
|
63
|
+
shift 2
|
|
64
|
+
;;
|
|
65
|
+
-s|--seccion)
|
|
66
|
+
SECTION_GID="$2"
|
|
67
|
+
shift 2
|
|
68
|
+
;;
|
|
69
|
+
-P|--puntos)
|
|
70
|
+
POINTS="$2"
|
|
71
|
+
shift 2
|
|
72
|
+
;;
|
|
73
|
+
-l|--listar)
|
|
74
|
+
LIST_MODE=true
|
|
75
|
+
shift
|
|
76
|
+
;;
|
|
77
|
+
-h|--help)
|
|
78
|
+
usage
|
|
79
|
+
exit 0
|
|
80
|
+
;;
|
|
81
|
+
*)
|
|
82
|
+
echo -e "${RED}❌ Opción desconocida: $1${RESET}"
|
|
83
|
+
usage
|
|
84
|
+
exit 1
|
|
85
|
+
;;
|
|
86
|
+
esac
|
|
87
|
+
done
|
|
88
|
+
|
|
89
|
+
# Verificar token
|
|
90
|
+
if [[ -z "${ASANA_ACCESS_TOKEN:-}" ]]; then
|
|
91
|
+
echo -e "${RED}❌ Error: ASANA_ACCESS_TOKEN no configurado${RESET}"
|
|
92
|
+
echo ""
|
|
93
|
+
echo "Configúralo en ~/.zshrc o ~/.bashrc:"
|
|
94
|
+
echo " export ASANA_ACCESS_TOKEN=\"tu-token-aqui\""
|
|
95
|
+
exit 1
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
# Modo listar
|
|
99
|
+
if [[ "$LIST_MODE" == true ]]; then
|
|
100
|
+
echo -e "${CYAN}${BOLD}📋 Proyectos disponibles:${RESET}\n"
|
|
101
|
+
asana_list_projects | while IFS=$'\t' read -r gid name; do
|
|
102
|
+
echo -e " ${BOLD}$gid${RESET} $name"
|
|
103
|
+
done
|
|
104
|
+
exit 0
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
# Validar argumentos obligatorios
|
|
108
|
+
if [[ -z "$PROJECT_GID" ]]; then
|
|
109
|
+
echo -e "${RED}❌ Error: --proyecto es obligatorio${RESET}"
|
|
110
|
+
usage
|
|
111
|
+
exit 1
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
if [[ -z "$TASK_NAME" ]]; then
|
|
115
|
+
echo -e "${RED}❌ Error: --titulo es obligatorio${RESET}"
|
|
116
|
+
usage
|
|
117
|
+
exit 1
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# Crear tarea
|
|
121
|
+
echo -e "${CYAN}Creando tarea en Asana...${RESET}"
|
|
122
|
+
TASK_GID=$(asana_create_task "$PROJECT_GID" "$TASK_NAME" "$SECTION_GID" "$POINTS")
|
|
123
|
+
|
|
124
|
+
if [[ -n "$TASK_GID" ]]; then
|
|
125
|
+
echo -e "${GREEN}${BOLD}✅ Tarea creada exitosamente${RESET}"
|
|
126
|
+
echo -e " ${BOLD}ID:${RESET} $TASK_GID"
|
|
127
|
+
echo -e " ${BOLD}Título:${RESET} $TASK_NAME"
|
|
128
|
+
echo -e " ${BOLD}Points:${RESET} $POINTS"
|
|
129
|
+
echo -e "\n📊 Ver tarea: https://app.asana.com/0/$PROJECT_GID/$TASK_GID"
|
|
130
|
+
else
|
|
131
|
+
echo -e "${RED}❌ Error al crear tarea${RESET}"
|
|
132
|
+
exit 1
|
|
133
|
+
fi
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# detect-project-type.sh
|
|
3
|
+
# Analiza las respuestas del cuestionario base y determina qué preguntas condicionales cargar
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
SPECLEAP_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
9
|
+
RESPONSES_FILE="${1:-}"
|
|
10
|
+
|
|
11
|
+
if [[ -z "$RESPONSES_FILE" || ! -f "$RESPONSES_FILE" ]]; then
|
|
12
|
+
echo "❌ Error: Archivo de respuestas no encontrado"
|
|
13
|
+
echo "Uso: $0 <responses.yaml>"
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
# Colores
|
|
18
|
+
GREEN='\033[0;32m'
|
|
19
|
+
BLUE='\033[0;34m'
|
|
20
|
+
YELLOW='\033[1;33m'
|
|
21
|
+
NC='\033[0m' # No Color
|
|
22
|
+
|
|
23
|
+
echo -e "${BLUE}🔍 Analizando tipo de proyecto...${NC}"
|
|
24
|
+
echo ""
|
|
25
|
+
|
|
26
|
+
# ===== EXTRAER RESPUESTAS CLAVE =====
|
|
27
|
+
BACKEND=$(grep "^backend_framework:" "$RESPONSES_FILE" | cut -d: -f2 | xargs)
|
|
28
|
+
FRONTEND=$(grep "^frontend_framework:" "$RESPONSES_FILE" | cut -d: -f2 | xargs)
|
|
29
|
+
NEEDS_AUTH=$(grep "^needs_auth:" "$RESPONSES_FILE" | cut -d: -f2 | xargs)
|
|
30
|
+
NEEDS_ADMIN=$(grep "^needs_admin_panel:" "$RESPONSES_FILE" | cut -d: -f2 | xargs)
|
|
31
|
+
FILE_UPLOADS=$(grep "^file_uploads:" "$RESPONSES_FILE" | cut -d: -f2 | xargs)
|
|
32
|
+
HOSTING=$(grep "^hosting:" "$RESPONSES_FILE" | cut -d: -f2 | xargs)
|
|
33
|
+
|
|
34
|
+
# Extraer funcionalidades (multilínea)
|
|
35
|
+
CORE_FEATURES=$(sed -n '/^core_features:/,/^[a-z_]*:/p' "$RESPONSES_FILE" | sed '1d;$d' | sed 's/^[[:space:]]*-//' | xargs)
|
|
36
|
+
|
|
37
|
+
# ===== DETECTAR PATRONES =====
|
|
38
|
+
DETECTED_TYPES=()
|
|
39
|
+
|
|
40
|
+
# E-commerce
|
|
41
|
+
if echo "$CORE_FEATURES" | grep -qiE "(pago|compra|carrito|stripe|paypal|producto|inventario|pedido)"; then
|
|
42
|
+
DETECTED_TYPES+=("ecommerce")
|
|
43
|
+
echo -e "${GREEN}✓${NC} Detectado: E-commerce"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# SaaS
|
|
47
|
+
if echo "$CORE_FEATURES" | grep -qiE "(suscripción|plan|billing|mensualidad|trial|premium)"; then
|
|
48
|
+
DETECTED_TYPES+=("saas")
|
|
49
|
+
echo -e "${GREEN}✓${NC} Detectado: SaaS"
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# API
|
|
53
|
+
if [[ "$FRONTEND" == "none" ]] || echo "$CORE_FEATURES" | grep -qiE "(api|endpoint|rest|graphql|webhook)"; then
|
|
54
|
+
DETECTED_TYPES+=("api")
|
|
55
|
+
echo -e "${GREEN}✓${NC} Detectado: API/Backend"
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# CMS/Blog
|
|
59
|
+
if echo "$CORE_FEATURES" | grep -qiE "(blog|post|artículo|contenido|cms|publicar|editor)"; then
|
|
60
|
+
DETECTED_TYPES+=("cms")
|
|
61
|
+
echo -e "${GREEN}✓${NC} Detectado: CMS/Blog"
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# Tiempo Real
|
|
65
|
+
if echo "$CORE_FEATURES" | grep -qiE "(chat|mensaje|notificación|tiempo real|websocket|live)"; then
|
|
66
|
+
DETECTED_TYPES+=("realtime")
|
|
67
|
+
echo -e "${GREEN}✓${NC} Detectado: Funcionalidad en tiempo real"
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# Autenticación compleja
|
|
71
|
+
if [[ "$NEEDS_AUTH" != "none" ]]; then
|
|
72
|
+
DETECTED_TYPES+=("auth")
|
|
73
|
+
echo -e "${GREEN}✓${NC} Detectado: Autenticación de usuarios"
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
# Admin Panel
|
|
77
|
+
if [[ "$NEEDS_ADMIN" != "none" ]]; then
|
|
78
|
+
DETECTED_TYPES+=("admin")
|
|
79
|
+
echo -e "${GREEN}✓${NC} Detectado: Panel de administración"
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# Storage
|
|
83
|
+
if [[ "$FILE_UPLOADS" == "cloud" ]]; then
|
|
84
|
+
DETECTED_TYPES+=("storage")
|
|
85
|
+
echo -e "${GREEN}✓${NC} Detectado: Almacenamiento en la nube"
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
# AWS
|
|
89
|
+
if [[ "$HOSTING" == "aws" ]]; then
|
|
90
|
+
DETECTED_TYPES+=("aws")
|
|
91
|
+
echo -e "${GREEN}✓${NC} Detectado: Despliegue en AWS"
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# OAuth
|
|
95
|
+
if [[ "$NEEDS_AUTH" == "oauth" ]] || [[ "$NEEDS_AUTH" == "both" ]]; then
|
|
96
|
+
DETECTED_TYPES+=("oauth")
|
|
97
|
+
echo -e "${GREEN}✓${NC} Detectado: OAuth"
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# Stack-specific
|
|
101
|
+
case "$BACKEND" in
|
|
102
|
+
laravel)
|
|
103
|
+
DETECTED_TYPES+=("php")
|
|
104
|
+
echo -e "${GREEN}✓${NC} Detectado: Stack PHP/Laravel"
|
|
105
|
+
;;
|
|
106
|
+
nodejs)
|
|
107
|
+
DETECTED_TYPES+=("nodejs")
|
|
108
|
+
echo -e "${GREEN}✓${NC} Detectado: Stack Node.js"
|
|
109
|
+
;;
|
|
110
|
+
python)
|
|
111
|
+
DETECTED_TYPES+=("python")
|
|
112
|
+
echo -e "${GREEN}✓${NC} Detectado: Stack Python"
|
|
113
|
+
;;
|
|
114
|
+
esac
|
|
115
|
+
|
|
116
|
+
case "$FRONTEND" in
|
|
117
|
+
react)
|
|
118
|
+
DETECTED_TYPES+=("react")
|
|
119
|
+
echo -e "${GREEN}✓${NC} Detectado: Frontend React"
|
|
120
|
+
;;
|
|
121
|
+
vue)
|
|
122
|
+
DETECTED_TYPES+=("vue")
|
|
123
|
+
echo -e "${GREEN}✓${NC} Detectado: Frontend Vue.js"
|
|
124
|
+
;;
|
|
125
|
+
esac
|
|
126
|
+
|
|
127
|
+
# ===== GUARDAR RESULTADO =====
|
|
128
|
+
OUTPUT_FILE="${RESPONSES_FILE%.yaml}.types.txt"
|
|
129
|
+
printf "%s\n" "${DETECTED_TYPES[@]}" > "$OUTPUT_FILE"
|
|
130
|
+
|
|
131
|
+
echo ""
|
|
132
|
+
echo -e "${BLUE}📋 Tipos detectados guardados en:${NC} $OUTPUT_FILE"
|
|
133
|
+
echo ""
|
|
134
|
+
echo -e "${YELLOW}Preguntas condicionales que se cargarán:${NC}"
|
|
135
|
+
for type in "${DETECTED_TYPES[@]}"; do
|
|
136
|
+
echo " - questions-${type}.yaml"
|
|
137
|
+
done
|
|
138
|
+
echo ""
|
|
139
|
+
|
|
140
|
+
# ===== RETORNAR TIPOS (para captura en scripts) =====
|
|
141
|
+
echo "${DETECTED_TYPES[*]}"
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# estimate-effort.sh
|
|
4
|
+
# Estima el esfuerzo requerido para resolver issues de deuda técnica
|
|
5
|
+
# Uso: bash estimate-effort.sh /tmp/specleap-analysis.json
|
|
6
|
+
|
|
7
|
+
set -e
|
|
8
|
+
|
|
9
|
+
ANALYSIS_FILE="$1"
|
|
10
|
+
|
|
11
|
+
if [ -z "$ANALYSIS_FILE" ]; then
|
|
12
|
+
echo "Error: Debes proporcionar el archivo de análisis JSON"
|
|
13
|
+
echo "Uso: bash estimate-effort.sh /tmp/specleap-analysis.json"
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
if [ ! -f "$ANALYSIS_FILE" ]; then
|
|
18
|
+
echo "Error: Archivo '$ANALYSIS_FILE' no encontrado"
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
echo "📊 Estimando esfuerzo de deuda técnica..."
|
|
23
|
+
echo ""
|
|
24
|
+
|
|
25
|
+
# Extraer métricas clave del JSON
|
|
26
|
+
php_files=$(grep '"php_files"' "$ANALYSIS_FILE" | grep -o '[0-9]*')
|
|
27
|
+
js_files=$(grep '"js_files"' "$ANALYSIS_FILE" | grep -o '[0-9]*')
|
|
28
|
+
total_lines=$(grep '"total_lines"' "$ANALYSIS_FILE" | grep -o '[0-9]*')
|
|
29
|
+
controllers=$(grep '"controllers"' "$ANALYSIS_FILE" | grep -o '[0-9]*')
|
|
30
|
+
models=$(grep '"models"' "$ANALYSIS_FILE" | grep -o '[0-9]*')
|
|
31
|
+
components=$(grep '"components"' "$ANALYSIS_FILE" | grep -o '[0-9]*')
|
|
32
|
+
|
|
33
|
+
php_coverage=$(grep '"php":' "$ANALYSIS_FILE" | grep -o '[0-9]*%' | head -1 | sed 's/%//')
|
|
34
|
+
js_coverage=$(grep '"js":' "$ANALYSIS_FILE" | grep -o '[0-9]*%' | head -1 | sed 's/%//')
|
|
35
|
+
|
|
36
|
+
phpstan_errors=$(grep -A 2 '"phpstan":' "$ANALYSIS_FILE" | grep '"errors"' | grep -o '[0-9]*')
|
|
37
|
+
eslint_errors=$(grep -A 2 '"eslint":' "$ANALYSIS_FILE" | grep '"errors"' | grep -o '[0-9]*')
|
|
38
|
+
|
|
39
|
+
# Valores por defecto si no se encontraron
|
|
40
|
+
php_files=${php_files:-0}
|
|
41
|
+
js_files=${js_files:-0}
|
|
42
|
+
total_lines=${total_lines:-0}
|
|
43
|
+
controllers=${controllers:-0}
|
|
44
|
+
models=${models:-0}
|
|
45
|
+
components=${components:-0}
|
|
46
|
+
php_coverage=${php_coverage:-0}
|
|
47
|
+
js_coverage=${js_coverage:-0}
|
|
48
|
+
phpstan_errors=${phpstan_errors:-0}
|
|
49
|
+
eslint_errors=${eslint_errors:-0}
|
|
50
|
+
|
|
51
|
+
# ==============================================================================
|
|
52
|
+
# ESTIMACIÓN POR TIPO DE ISSUE
|
|
53
|
+
# ==============================================================================
|
|
54
|
+
|
|
55
|
+
# TD-001: Tests (cobertura insuficiente)
|
|
56
|
+
estimate_tests() {
|
|
57
|
+
local effort=0
|
|
58
|
+
|
|
59
|
+
# PHP tests
|
|
60
|
+
if [ "$php_coverage" -lt 90 ] && [ "$php_files" -gt 0 ]; then
|
|
61
|
+
local missing_coverage=$((90 - php_coverage))
|
|
62
|
+
local files_to_test=$((php_files * missing_coverage / 100))
|
|
63
|
+
|
|
64
|
+
# Estimación: 30 min por archivo simple, 1h por controller, 2h por servicio
|
|
65
|
+
local controller_effort=$((controllers * 60)) # 1h por controller
|
|
66
|
+
local model_effort=$((models * 30)) # 30 min por model
|
|
67
|
+
local other_effort=$(((files_to_test - controllers - models) * 30)) # 30 min otros
|
|
68
|
+
|
|
69
|
+
effort=$((controller_effort + model_effort + other_effort))
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# JS tests
|
|
73
|
+
if [ "$js_coverage" -lt 90 ] && [ "$js_files" -gt 0 ]; then
|
|
74
|
+
local missing_coverage=$((90 - js_coverage))
|
|
75
|
+
local files_to_test=$((js_files * missing_coverage / 100))
|
|
76
|
+
|
|
77
|
+
# Estimación: 45 min por componente, 30 min por utility
|
|
78
|
+
local component_effort=$((components * 45)) # 45 min por componente
|
|
79
|
+
local other_effort=$(((files_to_test - components) * 30)) # 30 min otros
|
|
80
|
+
|
|
81
|
+
effort=$((effort + component_effort + other_effort))
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# Convertir minutos a horas (redondear hacia arriba)
|
|
85
|
+
effort=$(((effort + 59) / 60))
|
|
86
|
+
|
|
87
|
+
# Mínimo 8 horas, máximo 80 horas
|
|
88
|
+
if [ "$effort" -lt 8 ]; then
|
|
89
|
+
effort=8
|
|
90
|
+
elif [ "$effort" -gt 80 ]; then
|
|
91
|
+
effort=80
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
echo "$effort"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# TD-002: PHPStan errors
|
|
98
|
+
estimate_phpstan() {
|
|
99
|
+
local effort=0
|
|
100
|
+
|
|
101
|
+
if [ "$phpstan_errors" -gt 0 ]; then
|
|
102
|
+
# Estimación: 10 min por error simple, 30 min por error complejo
|
|
103
|
+
# Asumimos 70% simples, 30% complejos
|
|
104
|
+
local simple_errors=$((phpstan_errors * 70 / 100))
|
|
105
|
+
local complex_errors=$((phpstan_errors * 30 / 100))
|
|
106
|
+
|
|
107
|
+
effort=$(((simple_errors * 10 + complex_errors * 30 + 59) / 60))
|
|
108
|
+
|
|
109
|
+
# Mínimo 2 horas, máximo 40 horas
|
|
110
|
+
if [ "$effort" -lt 2 ]; then
|
|
111
|
+
effort=2
|
|
112
|
+
elif [ "$effort" -gt 40 ]; then
|
|
113
|
+
effort=40
|
|
114
|
+
fi
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
echo "$effort"
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
# TD-003: N+1 queries
|
|
121
|
+
estimate_n_plus_one() {
|
|
122
|
+
local effort=0
|
|
123
|
+
|
|
124
|
+
# Estimación basada en número de controllers
|
|
125
|
+
# Asumimos que ~30% de controllers tienen N+1
|
|
126
|
+
local affected_controllers=$((controllers * 30 / 100))
|
|
127
|
+
|
|
128
|
+
if [ "$affected_controllers" -gt 0 ]; then
|
|
129
|
+
# Estimación: 1-2 horas por controller
|
|
130
|
+
effort=$((affected_controllers * 90 / 60)) # 1.5h promedio
|
|
131
|
+
|
|
132
|
+
# Mínimo 3 horas, máximo 20 horas
|
|
133
|
+
if [ "$effort" -lt 3 ]; then
|
|
134
|
+
effort=3
|
|
135
|
+
elif [ "$effort" -gt 20 ]; then
|
|
136
|
+
effort=20
|
|
137
|
+
fi
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
echo "$effort"
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# TD-004: Código duplicado
|
|
144
|
+
estimate_duplication() {
|
|
145
|
+
local effort=0
|
|
146
|
+
|
|
147
|
+
# Estimación basada en tamaño del proyecto
|
|
148
|
+
if [ "$total_lines" -gt 50000 ]; then
|
|
149
|
+
effort=12
|
|
150
|
+
elif [ "$total_lines" -gt 20000 ]; then
|
|
151
|
+
effort=8
|
|
152
|
+
elif [ "$total_lines" -gt 10000 ]; then
|
|
153
|
+
effort=4
|
|
154
|
+
else
|
|
155
|
+
effort=2
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
echo "$effort"
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
# TD-005: Rate limiting
|
|
162
|
+
estimate_rate_limiting() {
|
|
163
|
+
# Esfuerzo fijo: configuración middleware + testing
|
|
164
|
+
echo "3"
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
# TD-006: Dependencias obsoletas
|
|
168
|
+
estimate_outdated_deps() {
|
|
169
|
+
local composer_outdated=$(grep -A 2 '"outdated_dependencies":' "$ANALYSIS_FILE" | grep '"composer"' | grep -o '[0-9]*')
|
|
170
|
+
local npm_outdated=$(grep -A 2 '"outdated_dependencies":' "$ANALYSIS_FILE" | grep '"npm"' | grep -o '[0-9]*')
|
|
171
|
+
|
|
172
|
+
composer_outdated=${composer_outdated:-0}
|
|
173
|
+
npm_outdated=${npm_outdated:-0}
|
|
174
|
+
|
|
175
|
+
local total_outdated=$((composer_outdated + npm_outdated))
|
|
176
|
+
local effort=0
|
|
177
|
+
|
|
178
|
+
if [ "$total_outdated" -gt 0 ]; then
|
|
179
|
+
# Estimación: 20 min por paquete (testing + resolución conflictos)
|
|
180
|
+
effort=$(((total_outdated * 20 + 59) / 60))
|
|
181
|
+
|
|
182
|
+
# Mínimo 2 horas, máximo 20 horas
|
|
183
|
+
if [ "$effort" -lt 2 ]; then
|
|
184
|
+
effort=2
|
|
185
|
+
elif [ "$effort" -gt 20 ]; then
|
|
186
|
+
effort=20
|
|
187
|
+
fi
|
|
188
|
+
fi
|
|
189
|
+
|
|
190
|
+
echo "$effort"
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
# TD-007: Complejidad ciclomática
|
|
194
|
+
estimate_complexity() {
|
|
195
|
+
# Estimación basada en número de controllers
|
|
196
|
+
# Asumimos que ~20% tienen complejidad alta
|
|
197
|
+
local affected_controllers=$((controllers * 20 / 100))
|
|
198
|
+
|
|
199
|
+
local effort=0
|
|
200
|
+
if [ "$affected_controllers" -gt 0 ]; then
|
|
201
|
+
# Estimación: 2 horas por controller (refactor + tests)
|
|
202
|
+
effort=$((affected_controllers * 2))
|
|
203
|
+
|
|
204
|
+
# Mínimo 2 horas, máximo 15 horas
|
|
205
|
+
if [ "$effort" -lt 2 ]; then
|
|
206
|
+
effort=2
|
|
207
|
+
elif [ "$effort" -gt 15 ]; then
|
|
208
|
+
effort=15
|
|
209
|
+
fi
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
echo "$effort"
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
# TD-008: Documentación API
|
|
216
|
+
estimate_api_docs() {
|
|
217
|
+
# Buscar routes/api.php para contar endpoints
|
|
218
|
+
# (Este script asume que el análisis ya corrió en el directorio del proyecto)
|
|
219
|
+
|
|
220
|
+
local effort=0
|
|
221
|
+
|
|
222
|
+
# Estimación fija basada en tamaño del proyecto
|
|
223
|
+
if [ "$total_lines" -gt 30000 ]; then
|
|
224
|
+
effort=12 # Proyecto grande, muchos endpoints
|
|
225
|
+
elif [ "$total_lines" -gt 10000 ]; then
|
|
226
|
+
effort=8 # Proyecto mediano
|
|
227
|
+
else
|
|
228
|
+
effort=4 # Proyecto pequeño
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
echo "$effort"
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
# ==============================================================================
|
|
235
|
+
# CÁLCULO TOTAL
|
|
236
|
+
# ==============================================================================
|
|
237
|
+
|
|
238
|
+
effort_tests=$(estimate_tests)
|
|
239
|
+
effort_phpstan=$(estimate_phpstan)
|
|
240
|
+
effort_n_plus_one=$(estimate_n_plus_one)
|
|
241
|
+
effort_duplication=$(estimate_duplication)
|
|
242
|
+
effort_rate_limiting=$(estimate_rate_limiting)
|
|
243
|
+
effort_outdated=$(estimate_outdated_deps)
|
|
244
|
+
effort_complexity=$(estimate_complexity)
|
|
245
|
+
effort_api_docs=$(estimate_api_docs)
|
|
246
|
+
|
|
247
|
+
total_effort=$((effort_tests + effort_phpstan + effort_n_plus_one + effort_duplication + effort_rate_limiting + effort_outdated + effort_complexity + effort_api_docs))
|
|
248
|
+
|
|
249
|
+
# ==============================================================================
|
|
250
|
+
# OUTPUT
|
|
251
|
+
# ==============================================================================
|
|
252
|
+
|
|
253
|
+
echo "📊 Estimación de esfuerzo por issue:"
|
|
254
|
+
echo ""
|
|
255
|
+
echo " TD-001 (Tests): $effort_tests horas"
|
|
256
|
+
echo " TD-002 (PHPStan): $effort_phpstan horas"
|
|
257
|
+
echo " TD-003 (N+1 queries): $effort_n_plus_one horas"
|
|
258
|
+
echo " TD-004 (Duplicación): $effort_duplication horas"
|
|
259
|
+
echo " TD-005 (Rate limiting): $effort_rate_limiting horas"
|
|
260
|
+
echo " TD-006 (Deps obsoletas): $effort_outdated horas"
|
|
261
|
+
echo " TD-007 (Complejidad): $effort_complexity horas"
|
|
262
|
+
echo " TD-008 (API docs): $effort_api_docs horas"
|
|
263
|
+
echo ""
|
|
264
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
265
|
+
echo " TOTAL ESTIMADO: $total_effort horas"
|
|
266
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
267
|
+
echo ""
|
|
268
|
+
|
|
269
|
+
# Convertir a semanas (40 horas/semana)
|
|
270
|
+
weeks=$(echo "scale=1; $total_effort / 40" | bc)
|
|
271
|
+
echo " Equivalente a: ~$weeks semanas de trabajo"
|
|
272
|
+
echo ""
|
|
273
|
+
|
|
274
|
+
# Guardar en archivo temporal para que la IA lo lea
|
|
275
|
+
cat > /tmp/specleap-effort-estimate.txt <<EOF
|
|
276
|
+
Total effort: $total_effort hours
|
|
277
|
+
Equivalent: ~$weeks weeks
|
|
278
|
+
|
|
279
|
+
Breakdown:
|
|
280
|
+
- TD-001 Tests: $effort_tests hours
|
|
281
|
+
- TD-002 PHPStan: $effort_phpstan hours
|
|
282
|
+
- TD-003 N+1: $effort_n_plus_one hours
|
|
283
|
+
- TD-004 Duplication: $effort_duplication hours
|
|
284
|
+
- TD-005 Rate limiting: $effort_rate_limiting hours
|
|
285
|
+
- TD-006 Outdated deps: $effort_outdated hours
|
|
286
|
+
- TD-007 Complexity: $effort_complexity hours
|
|
287
|
+
- TD-008 API docs: $effort_api_docs hours
|
|
288
|
+
EOF
|
|
289
|
+
|
|
290
|
+
echo "✅ Estimación guardada en /tmp/specleap-effort-estimate.txt"
|