claude-git-hooks 1.0.0 → 1.2.1

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,97 @@
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.1] - 2024-07-24
9
+
10
+ ### Fixed
11
+ - 🐛 Corregido problema de bloqueo del spinner durante verificación de autenticación Claude
12
+ - 🎨 Arreglado sistema de entretenimiento para mostrar correctamente spinner, chistes y countdown
13
+ - 🔧 Solucionado problema de líneas duplicadas en consola después del segundo chiste
14
+ - 🧹 Mejorada limpieza de pantalla para evitar superposición de texto al finalizar spinner
15
+ - ⏱️ Aumentado timeout de verificación de autenticación a 2 minutos
16
+
17
+ ### Changed
18
+ - 🔄 Reemplazado `execSync` por `spawn` en verificación de autenticación para evitar bloqueos
19
+ - 📍 Mejorado sistema de posicionamiento del cursor para renderizado consistente
20
+ - 🎯 Optimizada lógica de actualización del spinner para evitar race conditions
21
+
22
+ ### Technical
23
+ - 🏗️ Uso de `spawn` con `stdio: 'ignore'` para mantener compatibilidad con comportamiento original
24
+ - 🎮 Sistema de reserva de espacio (3 líneas) para evitar problemas de renderizado
25
+ - 🧮 Contador de chistes cada 10 segundos real (sin drift de tiempo)
26
+ - 🔍 Limpieza precisa línea por línea al finalizar para evitar artefactos visuales
27
+
28
+ ## [1.2.0] - 2024-07-24
29
+
30
+ ### Added
31
+ - ✨ Sistema de entretenimiento con spinner animado y chistes durante operaciones largas
32
+ - 🔐 Lectura segura y no-persistente de contraseña sudo para instalación automática
33
+ - 🛠️ Verificación completa de dependencias del sistema (jq, curl, herramientas Unix)
34
+ - 🚀 Instalación automática de dependencias faltantes con contraseña sudo
35
+ - ⚙️ Configuración automática de Git (line endings para WSL/Windows)
36
+ - 🎭 Chistes rotativos cada 10 segundos durante verificación de autenticación Claude
37
+
38
+ ### Enhanced
39
+ - 📦 Función `install` ahora incluye verificación e instalación completa de dependencias
40
+ - 🔍 Verificación de autenticación Claude con entretenimiento visual
41
+ - 💻 Detección inteligente de plataforma (Linux/macOS) para instalación de paquetes
42
+
43
+ ### Removed
44
+ - 🗑️ Eliminados archivos `setup-wsl.sh` y `setup-wsl.js` (redundantes)
45
+ - 🧹 Simplificación de la arquitectura eliminando duplicación de código
46
+
47
+ ### Technical
48
+ - 🔧 Añadido módulo `https` para obtener chistes de API externa
49
+ - 🎨 Clase `Entertainment` para manejo de spinner y chistes
50
+ - 🔐 Funciones `readPassword()` y `testSudoPassword()` para manejo seguro de credenciales
51
+ - ⚡ Conversión a funciones async/await para mejor manejo de procesos asíncronos
52
+
53
+ ## [1.1.0] - 2024-07-24
54
+
55
+ ### Added
56
+ - 🎯 Comando `set-mode` para cambiar entre análisis estándar y SonarQube
57
+ - 📊 Modo SonarQube con métricas detalladas y quality gate
58
+ - 📈 Visualización del modo de análisis actual en comando `status`
59
+ - 🔧 Configuración persistente del modo de análisis en archivo `.claude-analysis-mode`
60
+
61
+ ### Enhanced
62
+ - 📋 Comando `status` ahora muestra modo de análisis y archivos de pautas
63
+ - 📚 Documentación mejorada con ejemplos de ambos modos
64
+ - 🎨 Mejor formateo de salida en comandos informativos
65
+
66
+ ### Technical
67
+ - 📁 Detección automática de archivos de pautas (CLAUDE_PRE_COMMIT.md, CLAUDE_PRE_COMMIT_SONAR.md)
68
+ - 🏗️ Estructura preparada para múltiples modos de análisis
69
+
70
+ ## [1.0.0] - 2024-07-24
71
+
72
+ ### Added
73
+ - 🎉 Primera versión estable del paquete npm `claude-git-hooks`
74
+ - 📦 CLI global `claude-hooks` para gestión de hooks de Git
75
+ - 🪝 Instalación automática de hooks `pre-commit` y `prepare-commit-msg`
76
+ - 🔍 Verificación de dependencias (Claude CLI, jq)
77
+ - 📋 Comandos principales: `install`, `uninstall`, `enable`, `disable`, `status`
78
+ - 📖 Sistema de ayuda completo con ejemplos
79
+ - 🎛️ Gestión individual de hooks (habilitar/deshabilitar por separado)
80
+ - 📂 Creación automática de archivos de pautas si no existen
81
+ - 🔄 Sistema de backup automático de hooks existentes
82
+
83
+ ### Technical
84
+ - 🏗️ Arquitectura modular con funciones especializadas
85
+ - 🎨 Sistema de colores para output legible
86
+ - 🛡️ Validaciones de entorno (repositorio Git, dependencias)
87
+ - 📝 Documentación completa en README
88
+
89
+ ---
90
+
91
+ ## Tipos de cambios
92
+ - `Added` - para nuevas funcionalidades
93
+ - `Changed` - para cambios en funcionalidades existentes
94
+ - `Deprecated` - para funcionalidades que serán removidas
95
+ - `Removed` - para funcionalidades removidas
96
+ - `Fixed` - para corrección de bugs
97
+ - `Security` - para vulnerabilidades corregidas
package/README.md CHANGED
@@ -7,7 +7,6 @@ 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
11
  - **`CLAUDE_PRE_COMMIT.md`** - Pautas de evaluación formato estándar
13
12
  - **`CLAUDE_PRE_COMMIT_SONAR.md`** - Pautas de evaluación formato SonarQube
@@ -49,35 +48,57 @@ git config --global credential.helper store
49
48
  # O configura tu credential helper específico
50
49
  ```
51
50
 
52
- ## 🚀 Instalación Rápida (WSL)
51
+ ## 🚀 Instalación
52
+
53
+ ### Paquete NPM Global (Recomendado)
53
54
 
54
55
  **IMPORTANTE**: Claude CLI corre en WSL, por lo que toda la instalación y uso de git debe hacerse desde la terminal WSL.
55
56
 
56
57
  ```bash
57
- # Desde WSL - Instalación completa
58
- ./git-hooks/setup-wsl.sh
58
+ # Instalar el paquete globalmente
59
+ npm install -g claude-git-hooks
60
+
61
+ # En cualquier repositorio, instalar los hooks
62
+ cd tu-proyecto
63
+ claude-hooks install
64
+ ```
59
65
 
60
- # Ver opciones disponibles
61
- ./git-hooks/setup-wsl.sh --help
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
62
72
 
63
- # Solo sincronizar hooks (útil después de modificar archivos fuente)
64
- ./git-hooks/setup-wsl.sh --sync
73
+ #### Añadir como Dependencia de Desarrollo
74
+
75
+ ```bash
76
+ # Instalar como devDependency
77
+ npm install --save-dev claude-git-hooks
78
+ ```
65
79
 
66
- # Habilitar/deshabilitar hooks
67
- ./git-hooks/setup-wsl.sh --enable-hooks # Habilita todos los hooks
68
- ./git-hooks/setup-wsl.sh --disable-hooks # Deshabilita todos los hooks
69
- ./git-hooks/setup-wsl.sh --enable-hooks pre-commit # Habilita solo pre-commit
70
- ./git-hooks/setup-wsl.sh --disable-hooks prepare-commit-msg # Deshabilita solo prepare-commit-msg
80
+ Luego añade esto a tu `package.json`:
81
+
82
+ ```json
83
+ {
84
+ "scripts": {
85
+ "postinstall": "claude-hooks install"
86
+ },
87
+ "devDependencies": {
88
+ "claude-git-hooks": "^1.2.0"
89
+ }
90
+ }
71
91
  ```
72
92
 
93
+ ## 🤖 Características
94
+
73
95
  **✨ Auto-actualización incorporada**: Los hooks se actualizan automáticamente en cada commit.
74
96
 
75
- **🤖 Hooks instalados**:
97
+ **Hooks disponibles**:
76
98
 
77
99
  - `pre-commit`: Análisis de código con Claude (solo archivos Java/config)
78
100
  - `prepare-commit-msg`: Generación automática de mensajes de commit
79
101
 
80
-
81
102
  ## 🎯 Funcionamiento
82
103
 
83
104
  ### Hook pre-commit (Análisis de código)
@@ -105,10 +126,7 @@ git config --global credential.helper store
105
126
  ### Hook prepare-commit-msg (Generación automática de mensajes)
106
127
 
107
128
  1. **Se activa cuando el mensaje es**:
108
- - Vacío (`""`)
109
129
  - `"auto"`
110
- - `"--"`
111
- - `"tmp"`
112
130
  2. **Analiza los cambios del staging area**:
113
131
  - Lista archivos modificados con estadísticas
114
132
  - Incluye diffs completos para archivos < 100KB
@@ -151,48 +169,54 @@ Claude analizará los cambios y generará un mensaje de commit siguiendo las con
151
169
 
152
170
  **⚠️ Si la generación automática falla**: El commit se cancelará completamente. Simplemente ejecuta `git commit -m "tu mensaje"` manualmente.
153
171
 
154
- ### Selección de formato de análisis 📊
172
+ ### Modos de análisis 📊
155
173
 
156
- **Opción 1: Con variable de entorno (recomendado)**
174
+ Puedes elegir entre dos formatos de análisis:
175
+
176
+ #### 🎯 Configurar Modo (Recomendado)
157
177
 
158
178
  ```bash
159
- # Para formato estándar
160
- export CLAUDE_ANALYSIS_MODE=standard
161
- git commit -m "mensaje"
179
+ # Cambiar a modo SonarQube (métricas y quality gate)
180
+ claude-hooks set-mode sonar
162
181
 
163
- # Para formato SonarQube
164
- export CLAUDE_ANALYSIS_MODE=sonar
165
- git commit -m "mensaje"
182
+ # Cambiar a modo estándar (score y recomendaciones)
183
+ claude-hooks set-mode standard
184
+
185
+ # Ver modo actual
186
+ claude-hooks status
166
187
  ```
167
188
 
168
- **Opción 2: Configuración interactiva**
189
+ #### 🔧 Variable de Entorno (Temporal)
169
190
 
170
191
  ```bash
192
+ # Sobrescribir temporalmente el modo configurado
193
+ export CLAUDE_ANALYSIS_MODE=sonar
171
194
  git commit -m "mensaje"
172
- # En la primera ejecución te preguntará:
173
- # 1) Estándar - Formato clásico con recomendaciones
174
- # 2) SonarQube - Formato similar a SonarQube con métricas
195
+
196
+ # Volver al modo configurado
197
+ unset CLAUDE_ANALYSIS_MODE
175
198
  ```
176
199
 
177
- **⚠️ Nota sobre compatibilidad**: La selección interactiva requiere una terminal con soporte TTY (terminal interactiva). En WSL generalmente funciona correctamente. Sin embargo, en algunos entornos como:
200
+ #### 📋 Diferencias entre Modos
178
201
 
179
- - Scripts automatizados
180
- - CI/CD pipelines
181
- - Algunos emuladores de terminal
182
- - Cuando no hay TTY disponible
202
+ **Modo Estándar**:
203
+ - Análisis con puntuación del 1-10
204
+ - Recomendaciones detalladas por categoría
205
+ - Formato tradicional fácil de leer
183
206
 
184
- El sistema automáticamente usará el modo **estándar** como fallback sin preguntar.
207
+ **Modo SonarQube**:
208
+ - Quality Gate (PASSED/FAILED)
209
+ - Métricas: Reliability, Security, Maintainability
210
+ - Issues clasificados por severidad (Blocker, Critical, Major, Minor, Info)
211
+ - Security hotspots
185
212
 
186
- **Opción 3: Cambiar configuración guardada**
213
+ #### 🔄 Cambio de Modo Interactivo
187
214
 
188
215
  ```bash
189
- .git/hooks/pre-commit --change-mode
216
+ # Sin parámetros muestra ayuda interactiva
217
+ claude-hooks set-mode
190
218
  ```
191
219
 
192
- **Formato Estándar**: Análisis tradicional con score, recomendaciones y detalles por categoría.
193
-
194
- **Formato SonarQube**: Análisis con Quality Gate, métricas (Reliability, Security, Maintainability), clasificación de issues por severidad (Blocker, Critical, Major, Minor, Info), y security hotspots.
195
-
196
220
  ### Saltar la revisión (usar con precaución)
197
221
 
198
222
  ```bash
@@ -221,18 +245,21 @@ Claude responde con un JSON que incluye:
221
245
 
222
246
  ```bash
223
247
  # Desactivar todos los hooks
224
- ./git-hooks/setup-wsl.sh --disable-hooks
248
+ claude-hooks disable
225
249
 
226
250
  # Desactivar un hook específico
227
- ./git-hooks/setup-wsl.sh --disable-hooks pre-commit
228
- ./git-hooks/setup-wsl.sh --disable-hooks prepare-commit-msg
251
+ claude-hooks disable pre-commit
252
+ claude-hooks disable prepare-commit-msg
229
253
 
230
254
  # Habilitar todos los hooks
231
- ./git-hooks/setup-wsl.sh --enable-hooks
255
+ claude-hooks enable
232
256
 
233
257
  # Habilitar un hook específico
234
- ./git-hooks/setup-wsl.sh --enable-hooks pre-commit
235
- ./git-hooks/setup-wsl.sh --enable-hooks prepare-commit-msg
258
+ claude-hooks enable pre-commit
259
+ claude-hooks enable prepare-commit-msg
260
+
261
+ # Ver estado actual
262
+ claude-hooks status
236
263
  ```
237
264
 
238
265
  ## ⚙️ Configuración
@@ -353,3 +380,154 @@ WSL Terminal
353
380
  2. **Line Endings**: La configuración de `core.autocrlf` es crítica para evitar problemas entre Windows y Linux
354
381
  3. **Credenciales**: Necesitarás reconfigurar tus credenciales git en WSL
355
382
  4. **Performance**: La revisión puede tardar unos segundos dependiendo del tamaño de los cambios
383
+
384
+ ## 🔧 Desarrollo y Contribución
385
+
386
+ ### Estructura del Proyecto
387
+
388
+ ```
389
+ claude-git-hooks/
390
+ ├── bin/
391
+ │ └── claude-hooks # CLI principal con verificación completa
392
+ ├── templates/
393
+ │ ├── pre-commit # Hook de análisis de código
394
+ │ ├── prepare-commit-msg # Hook de generación de mensajes
395
+ │ ├── CLAUDE_PRE_COMMIT.md # Pautas estándar
396
+ │ └── CLAUDE_PRE_COMMIT_SONAR.md # Pautas SonarQube
397
+ ├── package.json # Configuración NPM
398
+ ├── README.md # Este archivo
399
+ ├── CHANGELOG.md # Historial de versiones
400
+ └── PUBLISH.md # Guía de publicación
401
+ ```
402
+
403
+ ### Configuración del Entorno de Desarrollo
404
+
405
+ ```bash
406
+ # 1. Clonar el repositorio
407
+ git clone https://github.com/pablorovito/claude-git-hooks.git
408
+ cd claude-git-hooks
409
+
410
+ # 2. Instalar dependencias (si las hubiera)
411
+ npm install
412
+
413
+ # 3. Enlazar para desarrollo local
414
+ npm link
415
+
416
+ # 4. Probar en un repositorio de prueba
417
+ cd /path/to/test-repo
418
+ claude-hooks install
419
+ ```
420
+
421
+ ### Workflow de Desarrollo
422
+
423
+ #### 1. Modificar Hooks
424
+
425
+ Los hooks están en `templates/`:
426
+
427
+ - `templates/pre-commit` - Lógica de análisis de código
428
+ - `templates/prepare-commit-msg` - Lógica de generación de mensajes
429
+
430
+ #### 2. Probar Localmente
431
+
432
+ ```bash
433
+ # Después de modificar archivos
434
+ npm link
435
+
436
+ # En un repo de prueba
437
+ claude-hooks install --force # Fuerza reinstalación
438
+
439
+ # Probar funcionalidad
440
+ git add .
441
+ git commit -m "auto" # Probar generación automática
442
+ git commit -m "test" # Probar análisis
443
+ ```
444
+
445
+ #### 3. Actualizar Documentación
446
+
447
+ - `README.md` - Documentación principal
448
+ - `README-NPM.md` - Para usuarios de NPM
449
+ - `templates/CLAUDE_PRE_COMMIT*.md` - Pautas de evaluación
450
+
451
+ #### 4. Versioning y Publicación
452
+
453
+ ```bash
454
+ # Actualizar versión
455
+ npm version patch # 1.0.0 -> 1.0.1
456
+ npm version minor # 1.0.0 -> 1.1.0
457
+ npm version major # 1.0.0 -> 2.0.0
458
+
459
+ # Publicar nueva versión
460
+ npm publish
461
+
462
+ # Tag en git
463
+ git push --tags
464
+ ```
465
+
466
+ ### Debugging
467
+
468
+ #### Modo Debug
469
+
470
+ ```bash
471
+ # Activar debug en hooks
472
+ DEBUG=1 git commit -m "test"
473
+
474
+ # Ver logs del CLI
475
+ claude-hooks install --debug
476
+ ```
477
+
478
+ #### Logs Útiles
479
+
480
+ - `debug-claude-response.json` - Respuestas de Claude en modo debug
481
+ - `~/.npm/_logs/` - Logs de npm install
482
+ - `.git/hooks/` - Verificar que hooks están instalados
483
+
484
+ ### Testing
485
+
486
+ #### Test Manual
487
+
488
+ ```bash
489
+ # 1. Crear repo de prueba
490
+ mkdir test-repo && cd test-repo
491
+ git init
492
+
493
+ # 2. Instalar hooks
494
+ claude-hooks install
495
+
496
+ # 3. Crear archivos de prueba
497
+ echo 'public class Test {}' > Test.java
498
+ git add .
499
+
500
+ # 4. Probar análisis
501
+ git commit -m "test: nueva clase"
502
+
503
+ # 5. Probar generación automática
504
+ echo 'public void newMethod() {}' >> Test.java
505
+ git add .
506
+ git commit -m "auto"
507
+ ```
508
+
509
+ #### Casos de Prueba
510
+
511
+ - ✅ Análisis de código Java
512
+ - ✅ Generación automática de mensajes
513
+ - ✅ Modo SonarQube
514
+ - ✅ Archivos grandes (>100KB)
515
+ - ✅ Commits sin archivos Java
516
+ - ✅ Enable/disable hooks
517
+ - ✅ Modo debug
518
+
519
+ ### Contribuir
520
+
521
+ 1. **Fork** el repositorio
522
+ 2. **Crear branch** para tu feature: `git checkout -b feature/nueva-funcionalidad`
523
+ 3. **Commit** tus cambios: `git commit -m "feat: nueva funcionalidad"`
524
+ 4. **Push** al branch: `git push origin feature/nueva-funcionalidad`
525
+ 5. **Abrir Pull Request**
526
+
527
+ ### Roadmap
528
+
529
+ - [ ] Soporte para más lenguajes (Python, JavaScript, etc.)
530
+ - [ ] Configuración más granular por proyecto
531
+ - [ ] Integration con IDEs populares
532
+ - [ ] Métricas de uso y performance
533
+ - [ ] Tests automatizados
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
 
@@ -91,13 +313,15 @@ function install(args) {
91
313
  guidelines.forEach(guideline => {
92
314
  if (!fs.existsSync(guideline)) {
93
315
  const sourcePath = path.join(templatesPath, guideline);
94
- fs.copyFileSync(sourcePath, guideline);
95
- success(`${guideline} creado`);
316
+ if (fs.existsSync(sourcePath)) {
317
+ fs.copyFileSync(sourcePath, guideline);
318
+ success(`${guideline} creado`);
319
+ }
96
320
  }
97
321
  });
98
322
 
99
- // Verificar dependencias
100
- checkDependencies();
323
+ // Configurar Git
324
+ configureGit();
101
325
 
102
326
  success('¡Claude Git Hooks instalado exitosamente! 🎉');
103
327
  console.log('\nUso:');
@@ -106,31 +330,198 @@ function install(args) {
106
330
  console.log('\nPara más opciones: claude-hooks --help');
107
331
  }
108
332
 
109
- // Verificar dependencias
110
- function checkDependencies() {
111
- info('\nVerificando dependencias...');
333
+ // Verificar dependencias completas (como setup-wsl.sh)
334
+ async function checkAndInstallDependencies(sudoPassword = null) {
335
+ info('Verificando dependencias del sistema...');
336
+
337
+ // Verificar Node.js
338
+ try {
339
+ const nodeVersion = execSync('node --version', { encoding: 'utf8' }).trim();
340
+ success(`Node.js ${nodeVersion}`);
341
+ } catch (e) {
342
+ error('Node.js no está instalado. Instala Node.js e intenta de nuevo.');
343
+ }
344
+
345
+ // Verificar npm
346
+ try {
347
+ const npmVersion = execSync('npm --version', { encoding: 'utf8' }).trim();
348
+ success(`npm ${npmVersion}`);
349
+ } catch (e) {
350
+ error('npm no está instalado.');
351
+ }
352
+
353
+ // Verificar e instalar jq
354
+ try {
355
+ const jqVersion = execSync('jq --version', { encoding: 'utf8' }).trim();
356
+ success(`jq ${jqVersion}`);
357
+ } catch (e) {
358
+ warning('jq no está instalado. Instalando...');
359
+ if (installPackage('jq', sudoPassword)) {
360
+ success('jq instalado correctamente');
361
+ } else {
362
+ warning('No se pudo instalar jq automáticamente');
363
+ if (os.platform() === 'linux') {
364
+ console.log('Instálalo manualmente con: sudo apt install jq');
365
+ } else if (os.platform() === 'darwin') {
366
+ console.log('Instálalo manualmente con: brew install jq');
367
+ }
368
+ }
369
+ }
370
+
371
+ // Verificar e instalar curl
372
+ try {
373
+ const curlVersion = execSync('curl --version', { encoding: 'utf8' }).split('\n')[0];
374
+ success(`curl ${curlVersion.split(' ')[1]}`);
375
+ } catch (e) {
376
+ warning('curl no está instalado. Instalando...');
377
+ if (installPackage('curl', sudoPassword)) {
378
+ success('curl instalado correctamente');
379
+ } else {
380
+ warning('No se pudo instalar curl automáticamente');
381
+ if (os.platform() === 'linux') {
382
+ console.log('Instálalo manualmente con: sudo apt install curl');
383
+ } else if (os.platform() === 'darwin') {
384
+ console.log('Instálalo manualmente con: brew install curl');
385
+ }
386
+ }
387
+ }
388
+
389
+ // Verificar Git
390
+ try {
391
+ const gitVersion = execSync('git --version', { encoding: 'utf8' }).trim();
392
+ success(`${gitVersion}`);
393
+ } catch (e) {
394
+ error('Git no está instalado. Instala Git e intenta de nuevo.');
395
+ }
396
+
397
+ // Verificar herramientas Unix estándar
398
+ const unixTools = ['sed', 'awk', 'grep', 'head', 'tail', 'stat', 'tput'];
399
+ const missingTools = [];
400
+
401
+ unixTools.forEach(tool => {
402
+ try {
403
+ execSync(`which ${tool}`, { stdio: 'ignore' });
404
+ } catch (e) {
405
+ missingTools.push(tool);
406
+ }
407
+ });
408
+
409
+ if (missingTools.length === 0) {
410
+ success('Herramientas Unix estándar verificadas');
411
+ } else {
412
+ error(`Faltan herramientas Unix estándar: ${missingTools.join(', ')}`);
413
+ }
414
+
415
+ // Verificar e instalar Claude CLI
416
+ await checkAndInstallClaude();
417
+
418
+ // Verificar autenticación de Claude
419
+ await checkClaudeAuth();
420
+
421
+ // Limpiar contraseña de memoria
422
+ sudoPassword = null;
423
+ }
424
+
425
+ // Verificar si necesitamos instalar dependencias
426
+ async function checkIfInstallationNeeded() {
427
+ const dependencies = ['jq', 'curl'];
428
+
429
+ for (const dep of dependencies) {
430
+ try {
431
+ execSync(`which ${dep}`, { stdio: 'ignore' });
432
+ } catch (e) {
433
+ return true; // Necesita instalación
434
+ }
435
+ }
112
436
 
113
437
  // Verificar Claude CLI
114
438
  try {
115
439
  execSync('claude --version', { stdio: 'ignore' });
116
- success('Claude CLI encontrado');
117
440
  } 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');
441
+ return true; // Necesita instalación de Claude
121
442
  }
122
443
 
123
- // Verificar jq
444
+ return false;
445
+ }
446
+
447
+ // Verificar e instalar Claude CLI
448
+ async function checkAndInstallClaude() {
449
+ try {
450
+ execSync('claude --version', { stdio: 'ignore' });
451
+ success('Claude CLI detectado');
452
+ } catch (e) {
453
+ info('Claude CLI no detectado. Instalando...');
454
+ try {
455
+ execSync('npm install -g @anthropic-ai/claude-cli', { stdio: 'inherit' });
456
+ success('Claude CLI instalado correctamente');
457
+ } catch (installError) {
458
+ error('Error al instalar Claude CLI. Instálalo manualmente: npm install -g @anthropic-ai/claude-cli');
459
+ }
460
+ }
461
+ }
462
+
463
+ // Verificar autenticación de Claude con entretenimiento
464
+ async function checkClaudeAuth() {
465
+ info('Verificando autenticación de Claude...');
466
+
467
+ // Usar spawn para no bloquear, pero con stdio: 'ignore' como el original
468
+ const authPromise = new Promise((resolve, reject) => {
469
+ const child = spawn('claude', ['auth', 'status'], {
470
+ stdio: 'ignore', // Igual que el original
471
+ detached: false,
472
+ windowsHide: true
473
+ });
474
+
475
+ // Timeout manual ya que spawn no tiene timeout nativo
476
+ const timeout = setTimeout(() => {
477
+ child.kill();
478
+ reject(new Error('timeout'));
479
+ }, 120000); // 2 minutos
480
+
481
+ child.on('exit', (code) => {
482
+ clearTimeout(timeout);
483
+ if (code === 0) {
484
+ resolve('success');
485
+ } else {
486
+ reject(new Error('not_authenticated'));
487
+ }
488
+ });
489
+
490
+ child.on('error', (err) => {
491
+ clearTimeout(timeout);
492
+ reject(err);
493
+ });
494
+ });
495
+
124
496
  try {
125
- execSync('jq --version', { stdio: 'ignore' });
126
- success('jq encontrado');
497
+ await Entertainment.showSpinner(authPromise, 'Verificando autenticación de Claude');
498
+ success('Autenticado en Claude');
127
499
  } 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');
500
+ warning('No estás autenticado en Claude');
501
+ console.log('Ejecuta: claude auth login');
502
+ console.log('Después vuelve a ejecutar este comando');
503
+ }
504
+ }
505
+
506
+ // Configurar Git (line endings, etc.)
507
+ function configureGit() {
508
+ info('Configurando Git...');
509
+
510
+ try {
511
+ // Configurar line endings para WSL
512
+ execSync('git config core.autocrlf input', { stdio: 'ignore' });
513
+ success('Line endings configurados para WSL (core.autocrlf = input)');
514
+
515
+ // Intentar configurar en Windows a través de PowerShell
516
+ try {
517
+ execSync('powershell.exe -Command "git config core.autocrlf true"', { stdio: 'ignore' });
518
+ success('Line endings configurados para Windows (core.autocrlf = true)');
519
+ } catch (psError) {
520
+ info('No se pudo configurar automáticamente en Windows');
133
521
  }
522
+
523
+ } catch (e) {
524
+ warning('Error al configurar Git');
134
525
  }
135
526
  }
136
527
 
@@ -224,6 +615,18 @@ function status() {
224
615
  }
225
616
  });
226
617
 
618
+ // Verificar modo de análisis actual
619
+ console.log('\nModo de análisis:');
620
+ const analysisMode = fs.existsSync('.claude-analysis-mode')
621
+ ? fs.readFileSync('.claude-analysis-mode', 'utf8').trim()
622
+ : 'standard (por defecto)';
623
+
624
+ if (analysisMode === 'sonar') {
625
+ info(`Modo actual: SonarQube`);
626
+ } else {
627
+ info(`Modo actual: Estándar`);
628
+ }
629
+
227
630
  // Verificar archivos de pautas
228
631
  console.log('\nArchivos de pautas:');
229
632
  const guidelines = ['CLAUDE_PRE_COMMIT.md', 'CLAUDE_PRE_COMMIT_SONAR.md'];
@@ -236,6 +639,40 @@ function status() {
236
639
  });
237
640
  }
238
641
 
642
+ // Comando set-mode
643
+ function setMode(mode) {
644
+ if (!checkGitRepo()) {
645
+ error('No estás en un repositorio Git.');
646
+ }
647
+
648
+ const validModes = ['standard', 'sonar'];
649
+
650
+ if (!mode) {
651
+ // Modo interactivo
652
+ console.log('\nSelecciona el modo de análisis:');
653
+ console.log('1) Standard - Formato clásico con score y recomendaciones');
654
+ console.log('2) SonarQube - Formato similar a SonarQube con métricas');
655
+ console.log('\nEjemplo de uso: claude-hooks set-mode standard');
656
+ console.log(' claude-hooks set-mode sonar');
657
+ return;
658
+ }
659
+
660
+ if (!validModes.includes(mode)) {
661
+ error(`Modo inválido: ${mode}. Usa 'standard' o 'sonar'`);
662
+ }
663
+
664
+ // Guardar el modo
665
+ fs.writeFileSync('.claude-analysis-mode', mode);
666
+
667
+ if (mode === 'sonar') {
668
+ success('Modo cambiado a: SonarQube');
669
+ info('Los commits usarán formato SonarQube con métricas y quality gate');
670
+ } else {
671
+ success('Modo cambiado a: Estándar');
672
+ info('Los commits usarán formato clásico con score y recomendaciones');
673
+ }
674
+ }
675
+
239
676
  // Comando help
240
677
  function showHelp() {
241
678
  console.log(`
@@ -248,6 +685,7 @@ Comandos:
248
685
  uninstall Desinstala los hooks del repositorio
249
686
  enable [hook] Habilita hooks (todos o uno específico)
250
687
  disable [hook] Deshabilita hooks (todos o uno específico)
688
+ set-mode [standard|sonar] Cambia el modo de análisis
251
689
  status Muestra el estado de los hooks
252
690
  help Muestra esta ayuda
253
691
 
@@ -257,41 +695,53 @@ Hooks disponibles:
257
695
 
258
696
  Ejemplos:
259
697
  claude-hooks install # Instala todos los hooks
698
+ claude-hooks set-mode sonar # Cambiar a modo SonarQube
699
+ claude-hooks set-mode standard # Cambiar a modo estándar
260
700
  claude-hooks disable pre-commit # Deshabilita solo pre-commit
261
701
  claude-hooks enable # Habilita todos los hooks
262
702
  claude-hooks status # Ver estado actual
263
703
 
264
- Más información: https://github.com/tresmares/claude-git-hooks
704
+ Más información: https://github.com/pablorovito/claude-git-hooks
265
705
  `);
266
706
  }
267
707
 
268
708
  // Main
269
- const args = process.argv.slice(2);
270
- const command = args[0];
271
-
272
- switch (command) {
273
- case 'install':
274
- install(args.slice(1));
275
- break;
276
- case 'uninstall':
277
- uninstall();
278
- break;
279
- case 'enable':
280
- enable(args[1]);
281
- break;
282
- case 'disable':
283
- disable(args[1]);
284
- break;
285
- case 'status':
286
- status();
287
- break;
288
- case 'help':
289
- case '--help':
290
- case '-h':
291
- case undefined:
292
- showHelp();
293
- break;
294
- default:
295
- error(`Comando desconocido: ${command}`);
296
- showHelp();
297
- }
709
+ async function main() {
710
+ const args = process.argv.slice(2);
711
+ const command = args[0];
712
+
713
+ switch (command) {
714
+ case 'install':
715
+ await install(args.slice(1));
716
+ break;
717
+ case 'uninstall':
718
+ uninstall();
719
+ break;
720
+ case 'enable':
721
+ enable(args[1]);
722
+ break;
723
+ case 'disable':
724
+ disable(args[1]);
725
+ break;
726
+ case 'set-mode':
727
+ setMode(args[1]);
728
+ break;
729
+ case 'status':
730
+ status();
731
+ break;
732
+ case 'help':
733
+ case '--help':
734
+ case '-h':
735
+ case undefined:
736
+ showHelp();
737
+ break;
738
+ default:
739
+ error(`Comando desconocido: ${command}`);
740
+ showHelp();
741
+ }
742
+ }
743
+
744
+ // Ejecutar main
745
+ main().catch(err => {
746
+ error(`Error inesperado: ${err.message}`);
747
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-git-hooks",
3
- "version": "1.0.0",
3
+ "version": "1.2.1",
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
  }