claude-git-hooks 1.1.0 → 1.2.4

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/CHANGELOG.md ADDED
@@ -0,0 +1,131 @@
1
+ # Changelog
2
+
3
+ Todos los cambios notables en este proyecto se documentarán en este archivo.
4
+
5
+ El formato está basado en [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ y este proyecto adhiere a [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.2.4] - 2025-08-22
9
+
10
+ ### Fixed
11
+ - 🐛 Corregido el análisis de SonarQube para mostrar correctamente el formato detallado
12
+ - 📊 Arreglado el parsing de JSON para buscar `QUALITY_GATE` en mayúsculas según las pautas
13
+ - 🔧 Actualizado el mapeo de campos JSON para coincidir con la estructura esperada
14
+
15
+ ### Changed
16
+ - 📈 Añadidas métricas de coverage, duplications y complexity al formato SonarQube
17
+ - 📝 Actualizado el archivo de pautas SonarQube para incluir todas las métricas
18
+
19
+ ## [1.2.3] - 2025-08-22
20
+
21
+ ### Added
22
+ - 🚀 Actualización automática de `.gitignore` durante la instalación
23
+ - 📝 Claude Hooks ahora agrega automáticamente las entradas necesarias a `.gitignore`
24
+ - 🔍 El comando `status` ahora muestra el estado de las entradas en `.gitignore`
25
+
26
+ ### Changed
27
+ - 🎯 Mejorado el proceso de instalación para ser más completo y automatizado
28
+ - 📊 El comando `status` ahora proporciona información más detallada sobre la configuración
29
+
30
+ ## [1.2.2] - 2025-08-22
31
+
32
+ ### Fixed
33
+ - 🐛 Corregido problema donde los archivos markdown de pautas no se instalaban correctamente desde npm
34
+ - 📁 Los archivos de pautas ahora se instalan en el directorio `.claude/` en lugar de la raíz del proyecto
35
+ - 🔧 Actualizado el hook pre-commit para buscar los archivos de pautas en la nueva ubicación `.claude/`
36
+
37
+ ### Changed
38
+ - 📂 Los archivos `CLAUDE_PRE_COMMIT.md` y `CLAUDE_PRE_COMMIT_SONAR.md` ahora se almacenan en `.claude/`
39
+ - 🎯 Mejorada la organización del proyecto manteniendo los archivos de configuración separados del código fuente
40
+ - 📝 Actualizada la documentación para reflejar la nueva estructura de directorios
41
+
42
+ ## [1.2.1] - 2024-07-24
43
+
44
+ ### Fixed
45
+ - 🐛 Corregido problema de bloqueo del spinner durante verificación de autenticación Claude
46
+ - 🎨 Arreglado sistema de entretenimiento para mostrar correctamente spinner, chistes y countdown
47
+ - 🔧 Solucionado problema de líneas duplicadas en consola después del segundo chiste
48
+ - 🧹 Mejorada limpieza de pantalla para evitar superposición de texto al finalizar spinner
49
+ - ⏱️ Aumentado timeout de verificación de autenticación a 2 minutos
50
+
51
+ ### Changed
52
+ - 🔄 Reemplazado `execSync` por `spawn` en verificación de autenticación para evitar bloqueos
53
+ - 📍 Mejorado sistema de posicionamiento del cursor para renderizado consistente
54
+ - 🎯 Optimizada lógica de actualización del spinner para evitar race conditions
55
+
56
+ ### Technical
57
+ - 🏗️ Uso de `spawn` con `stdio: 'ignore'` para mantener compatibilidad con comportamiento original
58
+ - 🎮 Sistema de reserva de espacio (3 líneas) para evitar problemas de renderizado
59
+ - 🧮 Contador de chistes cada 10 segundos real (sin drift de tiempo)
60
+ - 🔍 Limpieza precisa línea por línea al finalizar para evitar artefactos visuales
61
+
62
+ ## [1.2.0] - 2024-07-24
63
+
64
+ ### Added
65
+ - ✨ Sistema de entretenimiento con spinner animado y chistes durante operaciones largas
66
+ - 🔐 Lectura segura y no-persistente de contraseña sudo para instalación automática
67
+ - 🛠️ Verificación completa de dependencias del sistema (jq, curl, herramientas Unix)
68
+ - 🚀 Instalación automática de dependencias faltantes con contraseña sudo
69
+ - ⚙️ Configuración automática de Git (line endings para WSL/Windows)
70
+ - 🎭 Chistes rotativos cada 10 segundos durante verificación de autenticación Claude
71
+
72
+ ### Enhanced
73
+ - 📦 Función `install` ahora incluye verificación e instalación completa de dependencias
74
+ - 🔍 Verificación de autenticación Claude con entretenimiento visual
75
+ - 💻 Detección inteligente de plataforma (Linux/macOS) para instalación de paquetes
76
+
77
+ ### Removed
78
+ - 🗑️ Eliminados archivos `setup-wsl.sh` y `setup-wsl.js` (redundantes)
79
+ - 🧹 Simplificación de la arquitectura eliminando duplicación de código
80
+
81
+ ### Technical
82
+ - 🔧 Añadido módulo `https` para obtener chistes de API externa
83
+ - 🎨 Clase `Entertainment` para manejo de spinner y chistes
84
+ - 🔐 Funciones `readPassword()` y `testSudoPassword()` para manejo seguro de credenciales
85
+ - ⚡ Conversión a funciones async/await para mejor manejo de procesos asíncronos
86
+
87
+ ## [1.1.0] - 2024-07-24
88
+
89
+ ### Added
90
+ - 🎯 Comando `set-mode` para cambiar entre análisis estándar y SonarQube
91
+ - 📊 Modo SonarQube con métricas detalladas y quality gate
92
+ - 📈 Visualización del modo de análisis actual en comando `status`
93
+ - 🔧 Configuración persistente del modo de análisis en archivo `.claude-analysis-mode`
94
+
95
+ ### Enhanced
96
+ - 📋 Comando `status` ahora muestra modo de análisis y archivos de pautas
97
+ - 📚 Documentación mejorada con ejemplos de ambos modos
98
+ - 🎨 Mejor formateo de salida en comandos informativos
99
+
100
+ ### Technical
101
+ - 📁 Detección automática de archivos de pautas (CLAUDE_PRE_COMMIT.md, CLAUDE_PRE_COMMIT_SONAR.md)
102
+ - 🏗️ Estructura preparada para múltiples modos de análisis
103
+
104
+ ## [1.0.0] - 2024-07-24
105
+
106
+ ### Added
107
+ - 🎉 Primera versión estable del paquete npm `claude-git-hooks`
108
+ - 📦 CLI global `claude-hooks` para gestión de hooks de Git
109
+ - 🪝 Instalación automática de hooks `pre-commit` y `prepare-commit-msg`
110
+ - 🔍 Verificación de dependencias (Claude CLI, jq)
111
+ - 📋 Comandos principales: `install`, `uninstall`, `enable`, `disable`, `status`
112
+ - 📖 Sistema de ayuda completo con ejemplos
113
+ - 🎛️ Gestión individual de hooks (habilitar/deshabilitar por separado)
114
+ - 📂 Creación automática de archivos de pautas si no existen
115
+ - 🔄 Sistema de backup automático de hooks existentes
116
+
117
+ ### Technical
118
+ - 🏗️ Arquitectura modular con funciones especializadas
119
+ - 🎨 Sistema de colores para output legible
120
+ - 🛡️ Validaciones de entorno (repositorio Git, dependencias)
121
+ - 📝 Documentación completa en README
122
+
123
+ ---
124
+
125
+ ## Tipos de cambios
126
+ - `Added` - para nuevas funcionalidades
127
+ - `Changed` - para cambios en funcionalidades existentes
128
+ - `Deprecated` - para funcionalidades que serán removidas
129
+ - `Removed` - para funcionalidades removidas
130
+ - `Fixed` - para corrección de bugs
131
+ - `Security` - para vulnerabilidades corregidas
package/LICENSE CHANGED
File without changes
package/README.md CHANGED
@@ -7,10 +7,9 @@ Este directorio contiene un pre-commit hook que utiliza Claude CLI para revisar
7
7
  - **`README.md`** - Esta documentación
8
8
  - **`pre-commit`** - Hook principal para análisis de código (con auto-actualización)
9
9
  - **`prepare-commit-msg`** - Hook para generar mensajes de commit automáticos
10
- - **`setup-wsl.sh`** - Script de instalación inicial para WSL
11
10
  - **`.gitattributes`** - Configuración para mantener line endings correctos
12
- - **`CLAUDE_PRE_COMMIT.md`** - Pautas de evaluación formato estándar
13
- - **`CLAUDE_PRE_COMMIT_SONAR.md`** - Pautas de evaluación formato SonarQube
11
+ - **`.claude/CLAUDE_PRE_COMMIT.md`** - Pautas de evaluación formato estándar
12
+ - **`.claude/CLAUDE_PRE_COMMIT_SONAR.md`** - Pautas de evaluación formato SonarQube
14
13
 
15
14
  ## 🔧 Configuración Previa Importante
16
15
 
@@ -51,28 +50,32 @@ git config --global credential.helper store
51
50
 
52
51
  ## 🚀 Instalación
53
52
 
54
- ### Opción 1: Paquete NPM Global (Recomendado)
53
+ ### Paquete NPM Global (Recomendado)
55
54
 
56
55
  **IMPORTANTE**: Claude CLI corre en WSL, por lo que toda la instalación y uso de git debe hacerse desde la terminal WSL.
57
56
 
58
- #### Instalación Global
59
-
60
57
  ```bash
61
58
  # Instalar el paquete globalmente
62
- sudo npm install -g claude-git-hooks
59
+ npm install -g claude-git-hooks
63
60
 
64
61
  # En cualquier repositorio, instalar los hooks
65
62
  cd tu-proyecto
66
63
  claude-hooks install
67
64
  ```
68
65
 
66
+ El comando `claude-hooks install` ahora incluye:
67
+ - ✅ Verificación completa de dependencias del sistema
68
+ - ✅ Instalación automática de paquetes faltantes (jq, curl)
69
+ - ✅ Configuración de Git (line endings WSL/Windows)
70
+ - ✅ Verificación de autenticación Claude con entretenimiento
71
+ - ✅ Instalación de hooks y archivos de pautas
72
+ - ✅ Actualización automática de .gitignore con archivos de Claude
73
+
69
74
  #### Añadir como Dependencia de Desarrollo
70
75
 
71
76
  ```bash
72
77
  # Instalar como devDependency
73
78
  npm install --save-dev claude-git-hooks
74
-
75
- # Añadir script al package.json
76
79
  ```
77
80
 
78
81
  Luego añade esto a tu `package.json`:
@@ -83,30 +86,11 @@ Luego añade esto a tu `package.json`:
83
86
  "postinstall": "claude-hooks install"
84
87
  },
85
88
  "devDependencies": {
86
- "claude-git-hooks": "^1.0.0"
89
+ "claude-git-hooks": "^1.2.0"
87
90
  }
88
91
  }
89
92
  ```
90
93
 
91
- ### Opción 2: Instalación Local con Scripts
92
-
93
- ```bash
94
- # Desde WSL - Instalación completa
95
- ./git-hooks/setup-wsl.sh
96
-
97
- # Ver opciones disponibles
98
- ./git-hooks/setup-wsl.sh --help
99
-
100
- # Solo sincronizar hooks (útil después de modificar archivos fuente)
101
- ./git-hooks/setup-wsl.sh --sync
102
-
103
- # Habilitar/deshabilitar hooks
104
- ./git-hooks/setup-wsl.sh --enable-hooks # Habilita todos los hooks
105
- ./git-hooks/setup-wsl.sh --disable-hooks # Deshabilita todos los hooks
106
- ./git-hooks/setup-wsl.sh --enable-hooks pre-commit # Habilita solo pre-commit
107
- ./git-hooks/setup-wsl.sh --disable-hooks prepare-commit-msg # Deshabilita solo prepare-commit-msg
108
- ```
109
-
110
94
  ## 🤖 Características
111
95
 
112
96
  **✨ Auto-actualización incorporada**: Los hooks se actualizan automáticamente en cada commit.
@@ -116,6 +100,37 @@ Luego añade esto a tu `package.json`:
116
100
  - `pre-commit`: Análisis de código con Claude (solo archivos Java/config)
117
101
  - `prepare-commit-msg`: Generación automática de mensajes de commit
118
102
 
103
+ ## 📁 Gestión de Archivos
104
+
105
+ ### Archivos creados durante la instalación
106
+
107
+ El comando `claude-hooks install` crea los siguientes archivos y directorios:
108
+
109
+ 1. **`.git/hooks/pre-commit`** - Hook de análisis de código
110
+ 2. **`.git/hooks/prepare-commit-msg`** - Hook de generación de mensajes
111
+ 3. **`.claude/`** - Directorio para archivos de configuración
112
+ - `CLAUDE_PRE_COMMIT.md` - Pautas de evaluación estándar
113
+ - `CLAUDE_PRE_COMMIT_SONAR.md` - Pautas de evaluación SonarQube
114
+ 4. **`.claude-analysis-mode`** - Archivo de preferencia de modo (creado al primer uso)
115
+
116
+ ### Actualización automática de .gitignore
117
+
118
+ Durante la instalación, Claude Hooks actualiza automáticamente tu `.gitignore` para incluir:
119
+
120
+ ```gitignore
121
+ # Claude Git Hooks
122
+ .claude/
123
+ .claude-analysis-mode
124
+ debug-claude-response.json
125
+ ```
126
+
127
+ Esto asegura que:
128
+ - Los archivos de configuración de Claude específicos del proyecto no se suban al repositorio
129
+ - Los archivos de debug temporales se ignoren
130
+ - Cada desarrollador pueda tener sus propias preferencias de análisis
131
+
132
+ Si no existe un `.gitignore`, se creará uno nuevo. Si ya existe, las entradas se agregarán al final solo si no están presentes.
133
+
119
134
  ## 🎯 Funcionamiento
120
135
 
121
136
  ### Hook pre-commit (Análisis de código)
@@ -129,7 +144,7 @@ Luego añade esto a tu `package.json`:
129
144
  - **Estándar**: Formato clásico con score y recomendaciones detalladas
130
145
  - **SonarQube**: Simula salida de SonarQube con Quality Gate, métricas y clasificación de issues
131
146
  4. **Construye prompt inteligente**:
132
- - Lee las pautas desde `CLAUDE_PRE_COMMIT.md` o `CLAUDE_PRE_COMMIT_SONAR.md`
147
+ - Lee las pautas desde `.claude/CLAUDE_PRE_COMMIT.md` o `.claude/CLAUDE_PRE_COMMIT_SONAR.md`
133
148
  - Incluye el diff completo para archivos nuevos
134
149
  - Muestra solo cambios para archivos existentes
135
150
  5. **Envía a Claude CLI para revisión**
@@ -301,8 +316,8 @@ En el archivo `pre-commit`:
301
316
  - **`MAX_FILE_SIZE`**: Tamaño máximo de archivo a analizar (default: 100KB)
302
317
  - **`MAX_FILES`**: Número máximo de archivos por commit (default: 10)
303
318
  - **`CLAUDE_CLI`**: Comando de Claude CLI (default: "claude")
304
- - **`GUIDELINES_FILE`**: Archivo de pautas para modo estándar (default: "CLAUDE_PRE_COMMIT.md")
305
- - **`GUIDELINES_FILE_SONAR`**: Archivo de pautas para modo SonarQube (default: "CLAUDE_PRE_COMMIT_SONAR.md")
319
+ - **`GUIDELINES_FILE`**: Archivo de pautas para modo estándar (default: ".claude/CLAUDE_PRE_COMMIT.md")
320
+ - **`GUIDELINES_FILE_SONAR`**: Archivo de pautas para modo SonarQube (default: ".claude/CLAUDE_PRE_COMMIT_SONAR.md")
306
321
 
307
322
  En el archivo `prepare-commit-msg`:
308
323
 
@@ -368,7 +383,11 @@ Este problema suele ocurrir por conflictos de configuración de line endings:
368
383
 
369
384
  ## 📝 Personalización
370
385
 
371
- Puedes modificar las pautas de evaluación editando `CLAUDE_PRE_COMMIT.md` para adaptarlas a los estándares de tu equipo.
386
+ Puedes modificar las pautas de evaluación editando los archivos en el directorio `.claude/`:
387
+ - `.claude/CLAUDE_PRE_COMMIT.md` - Para el modo estándar
388
+ - `.claude/CLAUDE_PRE_COMMIT_SONAR.md` - Para el modo SonarQube
389
+
390
+ Estos archivos son específicos de tu proyecto y puedes adaptarlos a los estándares de tu equipo.
372
391
 
373
392
  ## 🔄 Arquitectura del Sistema
374
393
 
@@ -405,16 +424,18 @@ WSL Terminal
405
424
  ```
406
425
  claude-git-hooks/
407
426
  ├── bin/
408
- │ └── claude-hooks # CLI principal
427
+ │ └── claude-hooks # CLI principal con verificación completa
409
428
  ├── templates/
410
429
  │ ├── pre-commit # Hook de análisis de código
411
430
  │ ├── prepare-commit-msg # Hook de generación de mensajes
412
431
  │ ├── CLAUDE_PRE_COMMIT.md # Pautas estándar
413
432
  │ └── CLAUDE_PRE_COMMIT_SONAR.md # Pautas SonarQube
414
- ├── setup-wsl.sh # Script de instalación local (legacy)
433
+ ├── .claude/ # Directorio creado en el repo del usuario
434
+ │ ├── CLAUDE_PRE_COMMIT.md # Pautas copiadas aquí durante la instalación
435
+ │ └── CLAUDE_PRE_COMMIT_SONAR.md
415
436
  ├── package.json # Configuración NPM
416
437
  ├── README.md # Este archivo
417
- ├── README-NPM.md # Documentación para NPM
438
+ ├── CHANGELOG.md # Historial de versiones
418
439
  └── PUBLISH.md # Guía de publicación
419
440
  ```
420
441
 
package/bin/claude-hooks CHANGED
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { execSync } = require('child_process');
3
+ const { execSync, spawn } = require('child_process');
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
6
  const os = require('os');
7
+ const readline = require('readline');
8
+ const https = require('https');
7
9
 
8
10
  // Colores para output
9
11
  const colors = {
@@ -35,6 +37,206 @@ function warning(message) {
35
37
  log(`⚠️ ${message}`, 'yellow');
36
38
  }
37
39
 
40
+ // Función para leer contraseña de forma segura
41
+ function readPassword(prompt) {
42
+ return new Promise((resolve) => {
43
+ const rl = readline.createInterface({
44
+ input: process.stdin,
45
+ output: process.stdout
46
+ });
47
+
48
+ // Deshabilitar echo
49
+ rl.stdoutMuted = true;
50
+ rl._writeToOutput = function _writeToOutput(stringToWrite) {
51
+ if (rl.stdoutMuted)
52
+ rl.output.write("*");
53
+ else
54
+ rl.output.write(stringToWrite);
55
+ };
56
+
57
+ rl.question(prompt, (password) => {
58
+ rl.close();
59
+ console.log(); // Nueva línea
60
+ resolve(password);
61
+ });
62
+ });
63
+ }
64
+
65
+ // Verificar si la contraseña sudo es correcta
66
+ function testSudoPassword(password) {
67
+ try {
68
+ execSync('echo "' + password + '" | sudo -S true', {
69
+ stdio: 'ignore',
70
+ timeout: 5000
71
+ });
72
+ return true;
73
+ } catch (e) {
74
+ return false;
75
+ }
76
+ }
77
+
78
+ // Instalar paquete con sudo automático
79
+ function installPackage(packageName, sudoPassword = null) {
80
+ try {
81
+ if (sudoPassword) {
82
+ if (os.platform() === 'linux') {
83
+ execSync(`echo "${sudoPassword}" | sudo -S apt-get update && echo "${sudoPassword}" | sudo -S apt-get install -y ${packageName}`, {
84
+ stdio: 'inherit'
85
+ });
86
+ }
87
+ } else {
88
+ if (os.platform() === 'linux') {
89
+ execSync(`sudo apt-get update && sudo apt-get install -y ${packageName}`, {
90
+ stdio: 'inherit'
91
+ });
92
+ } else if (os.platform() === 'darwin') {
93
+ execSync(`brew install ${packageName}`, {
94
+ stdio: 'inherit'
95
+ });
96
+ }
97
+ }
98
+ return true;
99
+ } catch (e) {
100
+ return false;
101
+ }
102
+ }
103
+
104
+ // Sistema de entretenimiento
105
+ class Entertainment {
106
+ static jokes = [
107
+ "¿Por qué los programadores prefieren el modo oscuro? Porque la luz atrae bugs!",
108
+ "Un QA entra a un bar. Pide 1 cerveza. Pide 0 cervezas. Pide -1 cervezas.",
109
+ "¿Cuál es el lenguaje favorito de los piratas? R!",
110
+ "Hay 10 tipos de personas: las que entienden binario y las que no.",
111
+ "¿Por qué los programadores confunden Halloween con Navidad? Porque Oct 31 = Dec 25",
112
+ "¿Qué le dice un bit al otro? Nos vemos en el bus!",
113
+ "¿Por qué Java y C++ no se llevan bien? Porque tienen diferentes puntos de vista sobre los punteros.",
114
+ "Mi código no tiene bugs, solo features no documentadas."
115
+ ];
116
+
117
+ static async getJoke() {
118
+ return new Promise((resolve) => {
119
+ // Intentar obtener chiste de API
120
+ const req = https.get('https://icanhazdadjoke.com/', {
121
+ headers: { 'Accept': 'text/plain' },
122
+ timeout: 3000
123
+ }, (res) => {
124
+ let data = '';
125
+ res.on('data', chunk => data += chunk);
126
+ res.on('end', () => resolve(data.trim()));
127
+ });
128
+
129
+ req.on('error', () => {
130
+ // Si falla, usar chiste local
131
+ const randomJoke = this.jokes[Math.floor(Math.random() * this.jokes.length)];
132
+ resolve(randomJoke);
133
+ });
134
+
135
+ req.on('timeout', () => {
136
+ req.abort();
137
+ const randomJoke = this.jokes[Math.floor(Math.random() * this.jokes.length)];
138
+ resolve(randomJoke);
139
+ });
140
+ });
141
+ }
142
+
143
+ static async showSpinner(promise, message = 'Procesando') {
144
+ const spinners = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
145
+ let spinnerIndex = 0;
146
+ let jokeCountdown = 10;
147
+ let currentJoke = this.jokes[Math.floor(Math.random() * this.jokes.length)];
148
+ let isFinished = false;
149
+ let isFirstRender = true;
150
+
151
+ // Obtener primer chiste de la API sin bloquear
152
+ this.getJoke().then(joke => {
153
+ if (!isFinished) currentJoke = joke;
154
+ }).catch(() => {}); // Si falla, mantener el local
155
+
156
+ // Ocultar cursor
157
+ process.stdout.write('\x1B[?25l');
158
+
159
+ // Reservar espacio para las 3 líneas
160
+ process.stdout.write('\n\n\n');
161
+
162
+ const interval = setInterval(() => {
163
+ if (isFinished) {
164
+ clearInterval(interval);
165
+ return;
166
+ }
167
+
168
+ spinnerIndex++;
169
+
170
+ // Actualizar countdown cada segundo (10 iteraciones de 100ms)
171
+ if (spinnerIndex % 10 === 0) {
172
+ jokeCountdown--;
173
+
174
+ // Renovar chiste cada 10 segundos
175
+ if (jokeCountdown <= 0) {
176
+ this.getJoke().then(joke => {
177
+ if (!isFinished) currentJoke = joke;
178
+ }).catch(() => {
179
+ if (!isFinished) {
180
+ currentJoke = this.jokes[Math.floor(Math.random() * this.jokes.length)];
181
+ }
182
+ });
183
+ jokeCountdown = 10;
184
+ }
185
+ }
186
+
187
+ // Siempre volver exactamente 3 líneas arriba
188
+ process.stdout.write('\x1B[3A');
189
+
190
+ // Renderizar las 3 líneas desde el inicio
191
+ const spinner = spinners[spinnerIndex % spinners.length];
192
+
193
+ // Línea 1: Spinner
194
+ process.stdout.write('\r\x1B[2K' + `${colors.yellow}${spinner} ${message}${colors.reset}\n`);
195
+
196
+ // Línea 2: Chiste
197
+ process.stdout.write('\r\x1B[2K' + `${colors.green}🎭 ${currentJoke}${colors.reset}\n`);
198
+
199
+ // Línea 3: Countdown
200
+ process.stdout.write('\r\x1B[2K' + `${colors.yellow}⏱️ Próximo chiste en: ${jokeCountdown}s${colors.reset}\n`);
201
+ }, 100);
202
+
203
+ try {
204
+ const result = await promise;
205
+ isFinished = true;
206
+ clearInterval(interval);
207
+
208
+ // Limpiar exactamente 3 líneas completamente
209
+ process.stdout.write('\x1B[3A'); // Subir 3 líneas
210
+ process.stdout.write('\r\x1B[2K'); // Limpiar línea 1
211
+ process.stdout.write('\n\r\x1B[2K'); // Bajar y limpiar línea 2
212
+ process.stdout.write('\n\r\x1B[2K'); // Bajar y limpiar línea 3
213
+ process.stdout.write('\x1B[2A'); // Subir 2 líneas para quedar en la primera
214
+ process.stdout.write('\r'); // Ir al inicio de línea
215
+
216
+ // Mostrar cursor
217
+ process.stdout.write('\x1B[?25h');
218
+
219
+ return result;
220
+ } catch (error) {
221
+ isFinished = true;
222
+ clearInterval(interval);
223
+
224
+ // Limpiar exactamente 3 líneas completamente
225
+ process.stdout.write('\x1B[3A'); // Subir 3 líneas
226
+ process.stdout.write('\r\x1B[2K'); // Limpiar línea 1
227
+ process.stdout.write('\n\r\x1B[2K'); // Bajar y limpiar línea 2
228
+ process.stdout.write('\n\r\x1B[2K'); // Bajar y limpiar línea 3
229
+ process.stdout.write('\x1B[2A'); // Subir 2 líneas para quedar en la primera
230
+ process.stdout.write('\r'); // Ir al inicio de línea
231
+
232
+ // Mostrar cursor
233
+ process.stdout.write('\x1B[?25h');
234
+
235
+ throw error;
236
+ }
237
+ }
238
+ }
239
+
38
240
  // Verificar si estamos en un repositorio git
39
241
  function checkGitRepo() {
40
242
  try {
@@ -51,13 +253,33 @@ function getTemplatesPath() {
51
253
  }
52
254
 
53
255
  // Comando install
54
- function install(args) {
256
+ async function install(args) {
55
257
  if (!checkGitRepo()) {
56
258
  error('No estás en un repositorio Git. Por favor, ejecuta este comando desde la raíz de un repositorio.');
57
259
  }
58
260
 
59
261
  info('Instalando Claude Git Hooks...');
60
262
 
263
+ // Solicitar contraseña sudo al inicio si es necesario
264
+ let sudoPassword = null;
265
+ if (os.platform() === 'linux') {
266
+ const needsInstall = await checkIfInstallationNeeded();
267
+ if (needsInstall) {
268
+ info('Para la instalación automática de dependencias se necesita acceso sudo, por favor ingrese contraseña');
269
+ sudoPassword = await readPassword('Introduce tu contraseña de Ubuntu para sudo: ');
270
+
271
+ if (sudoPassword && !testSudoPassword(sudoPassword)) {
272
+ warning('Contraseña incorrecta. Continuando sin instalación automática.');
273
+ sudoPassword = null;
274
+ } else if (sudoPassword) {
275
+ success('Contraseña verificada. Procediendo con instalación automática.');
276
+ }
277
+ }
278
+ }
279
+
280
+ // Verificar dependencias con instalación automática
281
+ await checkAndInstallDependencies(sudoPassword);
282
+
61
283
  const templatesPath = getTemplatesPath();
62
284
  const hooksPath = '.git/hooks';
63
285
 
@@ -86,18 +308,31 @@ function install(args) {
86
308
  success(`${hook} instalado`);
87
309
  });
88
310
 
89
- // Copiar archivos de pautas si no existen
311
+ // Crear directorio .claude si no existe
312
+ const claudeDir = '.claude';
313
+ if (!fs.existsSync(claudeDir)) {
314
+ fs.mkdirSync(claudeDir, { recursive: true });
315
+ success('.claude directory created');
316
+ }
317
+
318
+ // Copiar archivos de pautas a .claude si no existen
90
319
  const guidelines = ['CLAUDE_PRE_COMMIT.md', 'CLAUDE_PRE_COMMIT_SONAR.md'];
91
320
  guidelines.forEach(guideline => {
92
- if (!fs.existsSync(guideline)) {
321
+ const destPath = path.join(claudeDir, guideline);
322
+ if (!fs.existsSync(destPath)) {
93
323
  const sourcePath = path.join(templatesPath, guideline);
94
- fs.copyFileSync(sourcePath, guideline);
95
- success(`${guideline} creado`);
324
+ if (fs.existsSync(sourcePath)) {
325
+ fs.copyFileSync(sourcePath, destPath);
326
+ success(`${guideline} creado en .claude/`);
327
+ }
96
328
  }
97
329
  });
98
330
 
99
- // Verificar dependencias
100
- checkDependencies();
331
+ // Configurar Git
332
+ configureGit();
333
+
334
+ // Actualizar .gitignore
335
+ updateGitignore();
101
336
 
102
337
  success('¡Claude Git Hooks instalado exitosamente! 🎉');
103
338
  console.log('\nUso:');
@@ -106,31 +341,271 @@ function install(args) {
106
341
  console.log('\nPara más opciones: claude-hooks --help');
107
342
  }
108
343
 
109
- // Verificar dependencias
110
- function checkDependencies() {
111
- info('\nVerificando dependencias...');
344
+ // Verificar dependencias completas (como setup-wsl.sh)
345
+ async function checkAndInstallDependencies(sudoPassword = null) {
346
+ info('Verificando dependencias del sistema...');
347
+
348
+ // Verificar Node.js
349
+ try {
350
+ const nodeVersion = execSync('node --version', { encoding: 'utf8' }).trim();
351
+ success(`Node.js ${nodeVersion}`);
352
+ } catch (e) {
353
+ error('Node.js no está instalado. Instala Node.js e intenta de nuevo.');
354
+ }
355
+
356
+ // Verificar npm
357
+ try {
358
+ const npmVersion = execSync('npm --version', { encoding: 'utf8' }).trim();
359
+ success(`npm ${npmVersion}`);
360
+ } catch (e) {
361
+ error('npm no está instalado.');
362
+ }
363
+
364
+ // Verificar e instalar jq
365
+ try {
366
+ const jqVersion = execSync('jq --version', { encoding: 'utf8' }).trim();
367
+ success(`jq ${jqVersion}`);
368
+ } catch (e) {
369
+ warning('jq no está instalado. Instalando...');
370
+ if (installPackage('jq', sudoPassword)) {
371
+ success('jq instalado correctamente');
372
+ } else {
373
+ warning('No se pudo instalar jq automáticamente');
374
+ if (os.platform() === 'linux') {
375
+ console.log('Instálalo manualmente con: sudo apt install jq');
376
+ } else if (os.platform() === 'darwin') {
377
+ console.log('Instálalo manualmente con: brew install jq');
378
+ }
379
+ }
380
+ }
381
+
382
+ // Verificar e instalar curl
383
+ try {
384
+ const curlVersion = execSync('curl --version', { encoding: 'utf8' }).split('\n')[0];
385
+ success(`curl ${curlVersion.split(' ')[1]}`);
386
+ } catch (e) {
387
+ warning('curl no está instalado. Instalando...');
388
+ if (installPackage('curl', sudoPassword)) {
389
+ success('curl instalado correctamente');
390
+ } else {
391
+ warning('No se pudo instalar curl automáticamente');
392
+ if (os.platform() === 'linux') {
393
+ console.log('Instálalo manualmente con: sudo apt install curl');
394
+ } else if (os.platform() === 'darwin') {
395
+ console.log('Instálalo manualmente con: brew install curl');
396
+ }
397
+ }
398
+ }
399
+
400
+ // Verificar Git
401
+ try {
402
+ const gitVersion = execSync('git --version', { encoding: 'utf8' }).trim();
403
+ success(`${gitVersion}`);
404
+ } catch (e) {
405
+ error('Git no está instalado. Instala Git e intenta de nuevo.');
406
+ }
407
+
408
+ // Verificar herramientas Unix estándar
409
+ const unixTools = ['sed', 'awk', 'grep', 'head', 'tail', 'stat', 'tput'];
410
+ const missingTools = [];
411
+
412
+ unixTools.forEach(tool => {
413
+ try {
414
+ execSync(`which ${tool}`, { stdio: 'ignore' });
415
+ } catch (e) {
416
+ missingTools.push(tool);
417
+ }
418
+ });
419
+
420
+ if (missingTools.length === 0) {
421
+ success('Herramientas Unix estándar verificadas');
422
+ } else {
423
+ error(`Faltan herramientas Unix estándar: ${missingTools.join(', ')}`);
424
+ }
425
+
426
+ // Verificar e instalar Claude CLI
427
+ await checkAndInstallClaude();
428
+
429
+ // Verificar autenticación de Claude
430
+ await checkClaudeAuth();
431
+
432
+ // Limpiar contraseña de memoria
433
+ sudoPassword = null;
434
+ }
435
+
436
+ // Verificar si necesitamos instalar dependencias
437
+ async function checkIfInstallationNeeded() {
438
+ const dependencies = ['jq', 'curl'];
439
+
440
+ for (const dep of dependencies) {
441
+ try {
442
+ execSync(`which ${dep}`, { stdio: 'ignore' });
443
+ } catch (e) {
444
+ return true; // Necesita instalación
445
+ }
446
+ }
112
447
 
113
448
  // Verificar Claude CLI
114
449
  try {
115
450
  execSync('claude --version', { stdio: 'ignore' });
116
- success('Claude CLI encontrado');
117
451
  } catch (e) {
118
- warning('Claude CLI no está instalado');
119
- console.log('Instálalo con: npm install -g @anthropic-ai/claude-cli');
120
- console.log('Luego ejecuta: claude auth login');
452
+ return true; // Necesita instalación de Claude
453
+ }
454
+
455
+ return false;
456
+ }
457
+
458
+ // Verificar e instalar Claude CLI
459
+ async function checkAndInstallClaude() {
460
+ try {
461
+ execSync('claude --version', { stdio: 'ignore' });
462
+ success('Claude CLI detectado');
463
+ } catch (e) {
464
+ info('Claude CLI no detectado. Instalando...');
465
+ try {
466
+ execSync('npm install -g @anthropic-ai/claude-cli', { stdio: 'inherit' });
467
+ success('Claude CLI instalado correctamente');
468
+ } catch (installError) {
469
+ error('Error al instalar Claude CLI. Instálalo manualmente: npm install -g @anthropic-ai/claude-cli');
470
+ }
121
471
  }
472
+ }
473
+
474
+ // Verificar autenticación de Claude con entretenimiento
475
+ async function checkClaudeAuth() {
476
+ info('Verificando autenticación de Claude...');
122
477
 
123
- // Verificar jq
478
+ // Usar spawn para no bloquear, pero con stdio: 'ignore' como el original
479
+ const authPromise = new Promise((resolve, reject) => {
480
+ const child = spawn('claude', ['auth', 'status'], {
481
+ stdio: 'ignore', // Igual que el original
482
+ detached: false,
483
+ windowsHide: true
484
+ });
485
+
486
+ // Timeout manual ya que spawn no tiene timeout nativo
487
+ const timeout = setTimeout(() => {
488
+ child.kill();
489
+ reject(new Error('timeout'));
490
+ }, 120000); // 2 minutos
491
+
492
+ child.on('exit', (code) => {
493
+ clearTimeout(timeout);
494
+ if (code === 0) {
495
+ resolve('success');
496
+ } else {
497
+ reject(new Error('not_authenticated'));
498
+ }
499
+ });
500
+
501
+ child.on('error', (err) => {
502
+ clearTimeout(timeout);
503
+ reject(err);
504
+ });
505
+ });
506
+
124
507
  try {
125
- execSync('jq --version', { stdio: 'ignore' });
126
- success('jq encontrado');
508
+ await Entertainment.showSpinner(authPromise, 'Verificando autenticación de Claude');
509
+ success('Autenticado en Claude');
127
510
  } catch (e) {
128
- warning('jq no está instalado');
129
- if (os.platform() === 'linux') {
130
- console.log('Instálalo con: sudo apt install jq');
131
- } else if (os.platform() === 'darwin') {
132
- console.log('Instálalo con: brew install jq');
511
+ warning('No estás autenticado en Claude');
512
+ console.log('Ejecuta: claude auth login');
513
+ console.log('Después vuelve a ejecutar este comando');
514
+ }
515
+ }
516
+
517
+ // Actualizar .gitignore con las entradas de Claude
518
+ function updateGitignore() {
519
+ info('Actualizando .gitignore...');
520
+
521
+ const gitignorePath = '.gitignore';
522
+ const claudeEntries = [
523
+ '# Claude Git Hooks',
524
+ '.claude/',
525
+ '.claude-analysis-mode',
526
+ 'debug-claude-response.json'
527
+ ];
528
+
529
+ let gitignoreContent = '';
530
+ let fileExists = false;
531
+
532
+ // Leer .gitignore existente si existe
533
+ if (fs.existsSync(gitignorePath)) {
534
+ gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
535
+ fileExists = true;
536
+ }
537
+
538
+ // Verificar qué entradas faltan
539
+ const missingEntries = [];
540
+ claudeEntries.forEach(entry => {
541
+ if (entry.startsWith('#')) {
542
+ // Para comentarios, verificar si ya existe algún comentario de Claude
543
+ if (!gitignoreContent.includes('# Claude')) {
544
+ missingEntries.push(entry);
545
+ }
546
+ } else {
547
+ // Para entradas normales, verificar si ya están presentes
548
+ const regex = new RegExp(`^${entry.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'm');
549
+ if (!regex.test(gitignoreContent)) {
550
+ missingEntries.push(entry);
551
+ }
552
+ }
553
+ });
554
+
555
+ // Si hay entradas faltantes, agregarlas
556
+ if (missingEntries.length > 0) {
557
+ // Asegurar que hay una nueva línea al final si el archivo existe y no está vacío
558
+ if (fileExists && gitignoreContent.length > 0 && !gitignoreContent.endsWith('\n')) {
559
+ gitignoreContent += '\n';
560
+ }
561
+
562
+ // Si el archivo no está vacío, agregar una línea en blanco antes
563
+ if (gitignoreContent.length > 0) {
564
+ gitignoreContent += '\n';
565
+ }
566
+
567
+ // Agregar las entradas faltantes
568
+ gitignoreContent += missingEntries.join('\n') + '\n';
569
+
570
+ // Escribir el archivo actualizado
571
+ fs.writeFileSync(gitignorePath, gitignoreContent);
572
+
573
+ if (fileExists) {
574
+ success('.gitignore actualizado con entradas de Claude');
575
+ } else {
576
+ success('.gitignore creado con entradas de Claude');
577
+ }
578
+
579
+ // Mostrar qué se agregó
580
+ missingEntries.forEach(entry => {
581
+ if (!entry.startsWith('#')) {
582
+ info(` + ${entry}`);
583
+ }
584
+ });
585
+ } else {
586
+ info('.gitignore ya contiene todas las entradas necesarias');
587
+ }
588
+ }
589
+
590
+ // Configurar Git (line endings, etc.)
591
+ function configureGit() {
592
+ info('Configurando Git...');
593
+
594
+ try {
595
+ // Configurar line endings para WSL
596
+ execSync('git config core.autocrlf input', { stdio: 'ignore' });
597
+ success('Line endings configurados para WSL (core.autocrlf = input)');
598
+
599
+ // Intentar configurar en Windows a través de PowerShell
600
+ try {
601
+ execSync('powershell.exe -Command "git config core.autocrlf true"', { stdio: 'ignore' });
602
+ success('Line endings configurados para Windows (core.autocrlf = true)');
603
+ } catch (psError) {
604
+ info('No se pudo configurar automáticamente en Windows');
133
605
  }
606
+
607
+ } catch (e) {
608
+ warning('Error al configurar Git');
134
609
  }
135
610
  }
136
611
 
@@ -240,12 +715,40 @@ function status() {
240
715
  console.log('\nArchivos de pautas:');
241
716
  const guidelines = ['CLAUDE_PRE_COMMIT.md', 'CLAUDE_PRE_COMMIT_SONAR.md'];
242
717
  guidelines.forEach(guideline => {
243
- if (fs.existsSync(guideline)) {
244
- success(`${guideline}: presente`);
718
+ const claudePath = path.join('.claude', guideline);
719
+ if (fs.existsSync(claudePath)) {
720
+ success(`${guideline}: presente en .claude/`);
721
+ } else if (fs.existsSync(guideline)) {
722
+ warning(`${guideline}: presente en raíz (debería estar en .claude/)`);
245
723
  } else {
246
724
  warning(`${guideline}: faltante`);
247
725
  }
248
726
  });
727
+
728
+ // Verificar entradas en .gitignore
729
+ console.log('\n.gitignore:');
730
+ const gitignorePath = '.gitignore';
731
+ if (fs.existsSync(gitignorePath)) {
732
+ const gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
733
+ const claudeIgnores = ['.claude/', '.claude-analysis-mode', 'debug-claude-response.json'];
734
+ let allPresent = true;
735
+
736
+ claudeIgnores.forEach(entry => {
737
+ const regex = new RegExp(`^${entry.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'm');
738
+ if (regex.test(gitignoreContent)) {
739
+ success(`${entry}: incluido`);
740
+ } else {
741
+ warning(`${entry}: faltante`);
742
+ allPresent = false;
743
+ }
744
+ });
745
+
746
+ if (!allPresent) {
747
+ info('\nEjecuta "claude-hooks install" para actualizar .gitignore');
748
+ }
749
+ } else {
750
+ warning('.gitignore no existe');
751
+ }
249
752
  }
250
753
 
251
754
  // Comando set-mode
@@ -315,35 +818,42 @@ Más información: https://github.com/pablorovito/claude-git-hooks
315
818
  }
316
819
 
317
820
  // Main
318
- const args = process.argv.slice(2);
319
- const command = args[0];
320
-
321
- switch (command) {
322
- case 'install':
323
- install(args.slice(1));
324
- break;
325
- case 'uninstall':
326
- uninstall();
327
- break;
328
- case 'enable':
329
- enable(args[1]);
330
- break;
331
- case 'disable':
332
- disable(args[1]);
333
- break;
334
- case 'set-mode':
335
- setMode(args[1]);
336
- break;
337
- case 'status':
338
- status();
339
- break;
340
- case 'help':
341
- case '--help':
342
- case '-h':
343
- case undefined:
344
- showHelp();
345
- break;
346
- default:
347
- error(`Comando desconocido: ${command}`);
348
- showHelp();
349
- }
821
+ async function main() {
822
+ const args = process.argv.slice(2);
823
+ const command = args[0];
824
+
825
+ switch (command) {
826
+ case 'install':
827
+ await install(args.slice(1));
828
+ break;
829
+ case 'uninstall':
830
+ uninstall();
831
+ break;
832
+ case 'enable':
833
+ enable(args[1]);
834
+ break;
835
+ case 'disable':
836
+ disable(args[1]);
837
+ break;
838
+ case 'set-mode':
839
+ setMode(args[1]);
840
+ break;
841
+ case 'status':
842
+ status();
843
+ break;
844
+ case 'help':
845
+ case '--help':
846
+ case '-h':
847
+ case undefined:
848
+ showHelp();
849
+ break;
850
+ default:
851
+ error(`Comando desconocido: ${command}`);
852
+ showHelp();
853
+ }
854
+ }
855
+
856
+ // Ejecutar main
857
+ main().catch(err => {
858
+ error(`Error inesperado: ${err.message}`);
859
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-git-hooks",
3
- "version": "1.1.0",
3
+ "version": "1.2.4",
4
4
  "description": "Git hooks con Claude CLI para análisis de código y generación automática de mensajes de commit",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -35,6 +35,7 @@
35
35
  "lib/",
36
36
  "templates/",
37
37
  "README.md",
38
+ "CHANGELOG.md",
38
39
  "LICENSE"
39
40
  ]
40
41
  }
@@ -44,7 +44,8 @@ Responde ÚNICAMENTE con un JSON válido siguiendo esta estructura:
44
44
  ],
45
45
  "blockingIssues": [
46
46
  "Problema crítico 1 (si existe)"
47
- ]
47
+ ],
48
+ "details": "Información adicional detallada (opcional)"
48
49
  }
49
50
  ```
50
51
 
@@ -23,6 +23,11 @@ Evaluar el código modificado siguiendo métricas similares a SonarQube para ase
23
23
  - Duplicación de código
24
24
  - Deuda técnica
25
25
 
26
+ ### Métricas Adicionales
27
+ - **Coverage**: Porcentaje estimado de cobertura de pruebas (0-100)
28
+ - **Duplications**: Porcentaje estimado de código duplicado (0-100)
29
+ - **Complexity**: Complejidad ciclomática promedio del código
30
+
26
31
  ## Clasificación de Issues
27
32
 
28
33
  ### Severidad
@@ -42,7 +47,10 @@ Responde ÚNICAMENTE con un JSON válido siguiendo esta estructura:
42
47
  "metrics": {
43
48
  "reliability": "A/B/C/D/E",
44
49
  "security": "A/B/C/D/E",
45
- "maintainability": "A/B/C/D/E"
50
+ "maintainability": "A/B/C/D/E",
51
+ "coverage": 75,
52
+ "duplications": 5,
53
+ "complexity": 15
46
54
  },
47
55
  "issues": {
48
56
  "blocker": 0,
@@ -60,7 +68,9 @@ Responde ÚNICAMENTE con un JSON válido siguiendo esta estructura:
60
68
  "line": 42
61
69
  }
62
70
  ],
63
- "securityHotspots": 0
71
+ "securityHotspots": 0,
72
+ "approved": true/false,
73
+ "blockingIssues": ["Lista de problemas bloqueantes si existen"]
64
74
  }
65
75
  ```
66
76
 
@@ -64,10 +64,10 @@ if [ "$ANALYSIS_MODE" != "standard" ] && [ "$ANALYSIS_MODE" != "sonar" ]; then
64
64
 
65
65
  if [ "$REPLY" = "2" ]; then
66
66
  ANALYSIS_MODE="sonar"
67
- GUIDELINES_FILE="CLAUDE_PRE_COMMIT_SONAR.md"
67
+ GUIDELINES_FILE=".claude/CLAUDE_PRE_COMMIT_SONAR.md"
68
68
  else
69
69
  ANALYSIS_MODE="standard"
70
- GUIDELINES_FILE="CLAUDE_PRE_COMMIT.md"
70
+ GUIDELINES_FILE=".claude/CLAUDE_PRE_COMMIT.md"
71
71
  fi
72
72
 
73
73
  # Guardar preferencia para futuros commits
@@ -78,10 +78,10 @@ if [ "$ANALYSIS_MODE" != "standard" ] && [ "$ANALYSIS_MODE" != "sonar" ]; then
78
78
  else
79
79
  # Usar modo guardado
80
80
  if [ "$ANALYSIS_MODE" = "sonar" ]; then
81
- GUIDELINES_FILE="CLAUDE_PRE_COMMIT_SONAR.md"
81
+ GUIDELINES_FILE=".claude/CLAUDE_PRE_COMMIT_SONAR.md"
82
82
  log "Usando modo de análisis: SonarQube"
83
83
  else
84
- GUIDELINES_FILE="CLAUDE_PRE_COMMIT.md"
84
+ GUIDELINES_FILE=".claude/CLAUDE_PRE_COMMIT.md"
85
85
  log "Usando modo de análisis: Estándar"
86
86
  fi
87
87
  fi
@@ -233,7 +233,7 @@ if $CLAUDE_CLI < "$PROMPT_FILE" > "$RESPONSE_FILE" 2>&1; then
233
233
  BLOCKING_ISSUES=$(echo "$JSON_RESPONSE" | jq -r '.blockingIssues[]?' 2>/dev/null | sed '/^$/d')
234
234
 
235
235
  # Verificar si estamos en modo SonarQube
236
- QUALITY_GATE=$(echo "$JSON_RESPONSE" | jq -r '.qualityGate // ""' 2>/dev/null)
236
+ QUALITY_GATE=$(echo "$JSON_RESPONSE" | jq -r '.QUALITY_GATE // ""' 2>/dev/null)
237
237
 
238
238
  if [ "$ANALYSIS_MODE" = "sonar" ] && [ -n "$QUALITY_GATE" ] && [ "$QUALITY_GATE" != "null" ]; then
239
239
  # Mostrar resultados estilo SonarQube
@@ -265,15 +265,15 @@ if $CLAUDE_CLI < "$PROMPT_FILE" > "$RESPONSE_FILE" 2>&1; then
265
265
  fi
266
266
 
267
267
  # Issues Summary
268
- SUMMARY=$(echo "$JSON_RESPONSE" | jq -r '.summary // {}' 2>/dev/null)
269
- if [ "$SUMMARY" != "{}" ] && [ "$SUMMARY" != "null" ]; then
268
+ ISSUES=$(echo "$JSON_RESPONSE" | jq -r '.issues // {}' 2>/dev/null)
269
+ if [ "$ISSUES" != "{}" ] && [ "$ISSUES" != "null" ]; then
270
270
  echo "📋 ISSUES SUMMARY"
271
- TOTAL=$(echo "$SUMMARY" | jq -r '.total // 0' 2>/dev/null)
272
- BLOCKER=$(echo "$SUMMARY" | jq -r '.blocker // 0' 2>/dev/null)
273
- CRITICAL=$(echo "$SUMMARY" | jq -r '.critical // 0' 2>/dev/null)
274
- MAJOR=$(echo "$SUMMARY" | jq -r '.major // 0' 2>/dev/null)
275
- MINOR=$(echo "$SUMMARY" | jq -r '.minor // 0' 2>/dev/null)
276
- INFO=$(echo "$SUMMARY" | jq -r '.info // 0' 2>/dev/null)
271
+ BLOCKER=$(echo "$ISSUES" | jq -r '.blocker // 0' 2>/dev/null)
272
+ CRITICAL=$(echo "$ISSUES" | jq -r '.critical // 0' 2>/dev/null)
273
+ MAJOR=$(echo "$ISSUES" | jq -r '.major // 0' 2>/dev/null)
274
+ MINOR=$(echo "$ISSUES" | jq -r '.minor // 0' 2>/dev/null)
275
+ INFO=$(echo "$ISSUES" | jq -r '.info // 0' 2>/dev/null)
276
+ TOTAL=$((BLOCKER + CRITICAL + MAJOR + MINOR + INFO))
277
277
 
278
278
  echo "Total: $TOTAL issues found"
279
279
  [ "$BLOCKER" -gt 0 ] && echo -e " ${RED}🔴 Blocker: $BLOCKER${NC}"
@@ -285,18 +285,19 @@ if $CLAUDE_CLI < "$PROMPT_FILE" > "$RESPONSE_FILE" 2>&1; then
285
285
  fi
286
286
 
287
287
  # Detailed Issues
288
- ISSUES_COUNT=$(echo "$JSON_RESPONSE" | jq -r '.issues | length' 2>/dev/null)
289
- if [ "$ISSUES_COUNT" -gt 0 ] 2>/dev/null; then
288
+ DETAILS_COUNT=$(echo "$JSON_RESPONSE" | jq -r '.details | length' 2>/dev/null)
289
+ if [ "$DETAILS_COUNT" -gt 0 ] 2>/dev/null; then
290
290
  echo "🔍 DETAILED ISSUES"
291
- echo "$JSON_RESPONSE" | jq -r '.issues[]? |
292
- "[\(.severity)] \(.type) in \(.file):\(.line // "?")\n \(.message)\n Rule: \(.rule // "N/A") | Effort: \(.effort // "?")\n"' 2>/dev/null
291
+ echo "$JSON_RESPONSE" | jq -r '.details[]? |
292
+ "[\(.severity)] \(.type) in \(.file):\(.line // "?")\n \(.message)\n"' 2>/dev/null
293
293
  fi
294
294
 
295
295
  # Security Hotspots
296
- HOTSPOTS_COUNT=$(echo "$JSON_RESPONSE" | jq -r '.hotspots | length' 2>/dev/null)
297
- if [ "$HOTSPOTS_COUNT" -gt 0 ] 2>/dev/null; then
298
- echo "🔥 SECURITY HOTSPOTS"
299
- echo "$JSON_RESPONSE" | jq -r '.hotspots[]? | " • \(.file): \(.message)"' 2>/dev/null
296
+ HOTSPOTS=$(echo "$JSON_RESPONSE" | jq -r '.securityHotspots // 0' 2>/dev/null)
297
+ if [ "$HOTSPOTS" -gt 0 ] 2>/dev/null; then
298
+ echo "🔥 SECURITY HOTSPOTS: $HOTSPOTS found"
299
+ echo " Review security-sensitive code carefully"
300
+
300
301
  echo
301
302
  fi
302
303
 
@@ -332,11 +333,18 @@ if $CLAUDE_CLI < "$PROMPT_FILE" > "$RESPONSE_FILE" 2>&1; then
332
333
  exit 1
333
334
  fi
334
335
 
335
- DETAILS=$(echo "$JSON_RESPONSE" | jq -r '.details // {}')
336
- if [ -n "$DETAILS" ] && [ "$DETAILS" != "{}" ] && [ "$DETAILS" != "null" ]; then
336
+ # Mostrar detalles adicionales si existen
337
+ DETAILS=$(echo "$JSON_RESPONSE" | jq -r '.details // null')
338
+ if [ -n "$DETAILS" ] && [ "$DETAILS" != "null" ]; then
337
339
  echo
338
- echo "=== DETALLES POR CATEGORÍA ==="
339
- echo "$DETAILS" | jq
340
+ echo "=== DETALLES ADICIONALES ==="
341
+ # Si details es un string, imprimirlo directamente
342
+ if echo "$DETAILS" | jq -e 'type == "string"' >/dev/null 2>&1; then
343
+ echo "$DETAILS" | jq -r '.'
344
+ # Si es un objeto o array, formatearlo
345
+ else
346
+ echo "$DETAILS" | jq '.'
347
+ fi
340
348
  fi
341
349
 
342
350
  log "✅ Revisión completada. Commit aprobado (Score: $SCORE/10)"
File without changes