claude-git-hooks 1.2.1 → 1.3.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/CHANGELOG.md +59 -0
- package/LICENSE +0 -0
- package/README.md +61 -10
- package/bin/claude-hooks +302 -8
- package/package.json +1 -1
- package/templates/CLAUDE_PRE_COMMIT.md +2 -1
- package/templates/CLAUDE_PRE_COMMIT_SONAR.md +12 -2
- package/templates/check-version.sh +266 -0
- package/templates/pre-commit +60 -25
- package/templates/prepare-commit-msg +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,65 @@ Todos los cambios notables en este proyecto se documentarán en este archivo.
|
|
|
5
5
|
El formato está basado en [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
y este proyecto adhiere a [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.3.0] - 2025-08-28
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- 🚀 Nuevo comando `claude-hooks update` para actualizar a la última versión disponible
|
|
12
|
+
- 🔄 Verificación automática de versión antes de cada commit con prompt interactivo
|
|
13
|
+
- 📜 Script compartido `check-version.sh` para evitar duplicación de código
|
|
14
|
+
- ✨ Prompt interactivo compatible con todas las consolas para aceptar actualizaciones
|
|
15
|
+
- 📊 Mensajes informativos detallados durante el proceso de verificación y actualización
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- 🎯 El hook `pre-commit` ahora verifica automáticamente si hay actualizaciones disponibles
|
|
19
|
+
- 📦 La instalación ahora incluye el script `check-version.sh` en `.git/hooks/`
|
|
20
|
+
- 🔧 Mejorada la UX con mensajes claros sobre el estado de la versión
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- 🐛 Corregido el parsing de versión desde NPM API (usaba endpoint incorrecto)
|
|
24
|
+
- 🔧 Mejorada la compatibilidad del prompt interactivo para consolas IntelliJ/WSL
|
|
25
|
+
- 📝 Múltiples métodos de fallback para leer input del usuario
|
|
26
|
+
|
|
27
|
+
### Technical
|
|
28
|
+
- 🏗️ Función `getLatestVersion()` ahora usa `dist-tags.latest` de NPM API
|
|
29
|
+
- 🔄 Función `checkVersionAndPromptUpdate()` reutilizable para verificación
|
|
30
|
+
- 📁 Script compartido para evitar duplicación de lógica de verificación
|
|
31
|
+
- 🎯 Fallback a `npm view` si la API de NPM falla
|
|
32
|
+
|
|
33
|
+
## [1.2.4] - 2025-08-22
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
- 🐛 Corregido el análisis de SonarQube para mostrar correctamente el formato detallado
|
|
37
|
+
- 📊 Arreglado el parsing de JSON para buscar `QUALITY_GATE` en mayúsculas según las pautas
|
|
38
|
+
- 🔧 Actualizado el mapeo de campos JSON para coincidir con la estructura esperada
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
- 📈 Añadidas métricas de coverage, duplications y complexity al formato SonarQube
|
|
42
|
+
- 📝 Actualizado el archivo de pautas SonarQube para incluir todas las métricas
|
|
43
|
+
|
|
44
|
+
## [1.2.3] - 2025-08-22
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
- 🚀 Actualización automática de `.gitignore` durante la instalación
|
|
48
|
+
- 📝 Claude Hooks ahora agrega automáticamente las entradas necesarias a `.gitignore`
|
|
49
|
+
- 🔍 El comando `status` ahora muestra el estado de las entradas en `.gitignore`
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
- 🎯 Mejorado el proceso de instalación para ser más completo y automatizado
|
|
53
|
+
- 📊 El comando `status` ahora proporciona información más detallada sobre la configuración
|
|
54
|
+
|
|
55
|
+
## [1.2.2] - 2025-08-22
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
- 🐛 Corregido problema donde los archivos markdown de pautas no se instalaban correctamente desde npm
|
|
59
|
+
- 📁 Los archivos de pautas ahora se instalan en el directorio `.claude/` en lugar de la raíz del proyecto
|
|
60
|
+
- 🔧 Actualizado el hook pre-commit para buscar los archivos de pautas en la nueva ubicación `.claude/`
|
|
61
|
+
|
|
62
|
+
### Changed
|
|
63
|
+
- 📂 Los archivos `CLAUDE_PRE_COMMIT.md` y `CLAUDE_PRE_COMMIT_SONAR.md` ahora se almacenan en `.claude/`
|
|
64
|
+
- 🎯 Mejorada la organización del proyecto manteniendo los archivos de configuración separados del código fuente
|
|
65
|
+
- 📝 Actualizada la documentación para reflejar la nueva estructura de directorios
|
|
66
|
+
|
|
8
67
|
## [1.2.1] - 2024-07-24
|
|
9
68
|
|
|
10
69
|
### Fixed
|
package/LICENSE
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
@@ -8,8 +8,8 @@ Este directorio contiene un pre-commit hook que utiliza Claude CLI para revisar
|
|
|
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
10
|
- **`.gitattributes`** - Configuración para mantener line endings correctos
|
|
11
|
-
-
|
|
12
|
-
-
|
|
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
|
|
13
13
|
|
|
14
14
|
## 🔧 Configuración Previa Importante
|
|
15
15
|
|
|
@@ -69,6 +69,7 @@ El comando `claude-hooks install` ahora incluye:
|
|
|
69
69
|
- ✅ Configuración de Git (line endings WSL/Windows)
|
|
70
70
|
- ✅ Verificación de autenticación Claude con entretenimiento
|
|
71
71
|
- ✅ Instalación de hooks y archivos de pautas
|
|
72
|
+
- ✅ Actualización automática de .gitignore con archivos de Claude
|
|
72
73
|
|
|
73
74
|
#### Añadir como Dependencia de Desarrollo
|
|
74
75
|
|
|
@@ -92,13 +93,44 @@ Luego añade esto a tu `package.json`:
|
|
|
92
93
|
|
|
93
94
|
## 🤖 Características
|
|
94
95
|
|
|
95
|
-
**✨ Auto-actualización
|
|
96
|
+
**✨ Auto-actualización**: Verifica automáticamente si hay nuevas versiones disponibles antes de cada commit y ofrece actualizar con un simple prompt interactivo.
|
|
96
97
|
|
|
97
98
|
**Hooks disponibles**:
|
|
98
99
|
|
|
99
|
-
- `pre-commit`: Análisis de código con Claude (solo archivos Java/config)
|
|
100
|
+
- `pre-commit`: Análisis de código con Claude (solo archivos Java/config) con verificación automática de versión
|
|
100
101
|
- `prepare-commit-msg`: Generación automática de mensajes de commit
|
|
101
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
|
+
|
|
102
134
|
## 🎯 Funcionamiento
|
|
103
135
|
|
|
104
136
|
### Hook pre-commit (Análisis de código)
|
|
@@ -112,7 +144,7 @@ Luego añade esto a tu `package.json`:
|
|
|
112
144
|
- **Estándar**: Formato clásico con score y recomendaciones detalladas
|
|
113
145
|
- **SonarQube**: Simula salida de SonarQube con Quality Gate, métricas y clasificación de issues
|
|
114
146
|
4. **Construye prompt inteligente**:
|
|
115
|
-
- Lee las pautas desde
|
|
147
|
+
- Lee las pautas desde `.claude/CLAUDE_PRE_COMMIT.md` o `.claude/CLAUDE_PRE_COMMIT_SONAR.md`
|
|
116
148
|
- Incluye el diff completo para archivos nuevos
|
|
117
149
|
- Muestra solo cambios para archivos existentes
|
|
118
150
|
5. **Envía a Claude CLI para revisión**
|
|
@@ -140,7 +172,8 @@ Luego añade esto a tu `package.json`:
|
|
|
140
172
|
|
|
141
173
|
### Características adicionales
|
|
142
174
|
|
|
143
|
-
- **Auto-actualización**:
|
|
175
|
+
- **Auto-actualización**: Verificación automática de versiones antes de cada commit con prompt interactivo para actualizar
|
|
176
|
+
- **Comando update**: `claude-hooks update` para actualizar manualmente a la última versión
|
|
144
177
|
- **Modo debug**: `DEBUG=1 git commit` guarda respuestas en `debug-claude-response.json`
|
|
145
178
|
- **Configuración persistente**: Guarda preferencias en `.claude-analysis-mode`
|
|
146
179
|
- **Validación de dependencias**: Verifica que Claude CLI esté autenticado antes de ejecutar
|
|
@@ -241,7 +274,18 @@ Claude responde con un JSON que incluye:
|
|
|
241
274
|
- `recommendations`: Recomendaciones de mejora
|
|
242
275
|
- `blockingIssues`: Problemas que bloquean el commit
|
|
243
276
|
|
|
244
|
-
## 🔄
|
|
277
|
+
## 🔄 Actualización y Gestión
|
|
278
|
+
|
|
279
|
+
### Actualizar a la última versión
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
# Actualizar manualmente a la última versión
|
|
283
|
+
claude-hooks update
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
El comando `update` descarga e instala automáticamente la última versión disponible desde NPM, y luego reinstala los hooks con las nuevas características.
|
|
287
|
+
|
|
288
|
+
### Desactivar/Activar hooks
|
|
245
289
|
|
|
246
290
|
```bash
|
|
247
291
|
# Desactivar todos los hooks
|
|
@@ -284,8 +328,8 @@ En el archivo `pre-commit`:
|
|
|
284
328
|
- **`MAX_FILE_SIZE`**: Tamaño máximo de archivo a analizar (default: 100KB)
|
|
285
329
|
- **`MAX_FILES`**: Número máximo de archivos por commit (default: 10)
|
|
286
330
|
- **`CLAUDE_CLI`**: Comando de Claude CLI (default: "claude")
|
|
287
|
-
- **`GUIDELINES_FILE`**: Archivo de pautas para modo estándar (default: "CLAUDE_PRE_COMMIT.md")
|
|
288
|
-
- **`GUIDELINES_FILE_SONAR`**: Archivo de pautas para modo SonarQube (default: "CLAUDE_PRE_COMMIT_SONAR.md")
|
|
331
|
+
- **`GUIDELINES_FILE`**: Archivo de pautas para modo estándar (default: ".claude/CLAUDE_PRE_COMMIT.md")
|
|
332
|
+
- **`GUIDELINES_FILE_SONAR`**: Archivo de pautas para modo SonarQube (default: ".claude/CLAUDE_PRE_COMMIT_SONAR.md")
|
|
289
333
|
|
|
290
334
|
En el archivo `prepare-commit-msg`:
|
|
291
335
|
|
|
@@ -351,7 +395,11 @@ Este problema suele ocurrir por conflictos de configuración de line endings:
|
|
|
351
395
|
|
|
352
396
|
## 📝 Personalización
|
|
353
397
|
|
|
354
|
-
Puedes modificar las pautas de evaluación editando
|
|
398
|
+
Puedes modificar las pautas de evaluación editando los archivos en el directorio `.claude/`:
|
|
399
|
+
- `.claude/CLAUDE_PRE_COMMIT.md` - Para el modo estándar
|
|
400
|
+
- `.claude/CLAUDE_PRE_COMMIT_SONAR.md` - Para el modo SonarQube
|
|
401
|
+
|
|
402
|
+
Estos archivos son específicos de tu proyecto y puedes adaptarlos a los estándares de tu equipo.
|
|
355
403
|
|
|
356
404
|
## 🔄 Arquitectura del Sistema
|
|
357
405
|
|
|
@@ -394,6 +442,9 @@ claude-git-hooks/
|
|
|
394
442
|
│ ├── prepare-commit-msg # Hook de generación de mensajes
|
|
395
443
|
│ ├── CLAUDE_PRE_COMMIT.md # Pautas estándar
|
|
396
444
|
│ └── CLAUDE_PRE_COMMIT_SONAR.md # Pautas SonarQube
|
|
445
|
+
├── .claude/ # Directorio creado en el repo del usuario
|
|
446
|
+
│ ├── CLAUDE_PRE_COMMIT.md # Pautas copiadas aquí durante la instalación
|
|
447
|
+
│ └── CLAUDE_PRE_COMMIT_SONAR.md
|
|
397
448
|
├── package.json # Configuración NPM
|
|
398
449
|
├── README.md # Este archivo
|
|
399
450
|
├── CHANGELOG.md # Historial de versiones
|
package/bin/claude-hooks
CHANGED
|
@@ -7,6 +7,86 @@ const os = require('os');
|
|
|
7
7
|
const readline = require('readline');
|
|
8
8
|
const https = require('https');
|
|
9
9
|
|
|
10
|
+
// Función para obtener la última versión desde NPM
|
|
11
|
+
function getLatestVersion(packageName) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
// Usar la API principal de NPM, no /latest
|
|
14
|
+
https.get(`https://registry.npmjs.org/${packageName}`, (res) => {
|
|
15
|
+
let data = '';
|
|
16
|
+
res.on('data', chunk => data += chunk);
|
|
17
|
+
res.on('end', () => {
|
|
18
|
+
try {
|
|
19
|
+
const json = JSON.parse(data);
|
|
20
|
+
// Obtener la versión del tag 'latest'
|
|
21
|
+
if (json['dist-tags'] && json['dist-tags'].latest) {
|
|
22
|
+
resolve(json['dist-tags'].latest);
|
|
23
|
+
} else {
|
|
24
|
+
reject(new Error('No se pudo obtener la versión'));
|
|
25
|
+
}
|
|
26
|
+
} catch (e) {
|
|
27
|
+
// Si falla, intentar con npm view
|
|
28
|
+
try {
|
|
29
|
+
const version = execSync(`npm view ${packageName} version`, { encoding: 'utf8' }).trim();
|
|
30
|
+
resolve(version);
|
|
31
|
+
} catch (npmError) {
|
|
32
|
+
reject(e);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}).on('error', reject);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Función para verificar versión (usada por los hooks)
|
|
41
|
+
async function checkVersionAndPromptUpdate() {
|
|
42
|
+
try {
|
|
43
|
+
const currentVersion = require('../package.json').version;
|
|
44
|
+
const latestVersion = await getLatestVersion('claude-git-hooks');
|
|
45
|
+
|
|
46
|
+
if (currentVersion === latestVersion) {
|
|
47
|
+
return true; // Ya actualizado
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log('');
|
|
51
|
+
warning(`Nueva versión disponible: ${latestVersion} (actual: ${currentVersion})`);
|
|
52
|
+
|
|
53
|
+
// Prompt interactivo compatible con todas las consolas
|
|
54
|
+
const rl = readline.createInterface({
|
|
55
|
+
input: process.stdin,
|
|
56
|
+
output: process.stdout
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return new Promise((resolve) => {
|
|
60
|
+
rl.question('¿Deseas actualizar ahora? (s/n): ', (answer) => {
|
|
61
|
+
rl.close();
|
|
62
|
+
|
|
63
|
+
if (answer.toLowerCase() === 's' || answer.toLowerCase() === 'si' || answer.toLowerCase() === 'sí' || answer.toLowerCase() === 'yes' || answer.toLowerCase() === 'y') {
|
|
64
|
+
info('Actualizando claude-git-hooks...');
|
|
65
|
+
try {
|
|
66
|
+
execSync('npm install -g claude-git-hooks@latest', { stdio: 'inherit' });
|
|
67
|
+
success('Actualización completada. Por favor, vuelve a ejecutar tu comando.');
|
|
68
|
+
process.exit(0); // Salir para que el usuario reinicie el proceso
|
|
69
|
+
} catch (e) {
|
|
70
|
+
error('Error al actualizar: ' + e.message);
|
|
71
|
+
resolve(false);
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
info('Actualización pospuesta. Puedes actualizar más tarde con: claude-hooks update');
|
|
75
|
+
resolve(true); // Continuar sin actualizar
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
} catch (e) {
|
|
80
|
+
// Si hay error verificando la versión, continuar sin bloquear
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Exportar para uso en hooks
|
|
86
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
87
|
+
module.exports = { checkVersionAndPromptUpdate };
|
|
88
|
+
}
|
|
89
|
+
|
|
10
90
|
// Colores para output
|
|
11
91
|
const colors = {
|
|
12
92
|
reset: '\x1b[0m',
|
|
@@ -258,7 +338,13 @@ async function install(args) {
|
|
|
258
338
|
error('No estás en un repositorio Git. Por favor, ejecuta este comando desde la raíz de un repositorio.');
|
|
259
339
|
}
|
|
260
340
|
|
|
261
|
-
|
|
341
|
+
const isForce = args.includes('--force');
|
|
342
|
+
|
|
343
|
+
if (isForce) {
|
|
344
|
+
info('Instalando Claude Git Hooks (modo force)...');
|
|
345
|
+
} else {
|
|
346
|
+
info('Instalando Claude Git Hooks...');
|
|
347
|
+
}
|
|
262
348
|
|
|
263
349
|
// Solicitar contraseña sudo al inicio si es necesario
|
|
264
350
|
let sudoPassword = null;
|
|
@@ -308,14 +394,32 @@ async function install(args) {
|
|
|
308
394
|
success(`${hook} instalado`);
|
|
309
395
|
});
|
|
310
396
|
|
|
311
|
-
// Copiar
|
|
397
|
+
// Copiar script de verificación de versión
|
|
398
|
+
const checkVersionSource = path.join(templatesPath, 'check-version.sh');
|
|
399
|
+
const checkVersionDest = path.join(hooksPath, 'check-version.sh');
|
|
400
|
+
|
|
401
|
+
if (fs.existsSync(checkVersionSource)) {
|
|
402
|
+
fs.copyFileSync(checkVersionSource, checkVersionDest);
|
|
403
|
+
fs.chmodSync(checkVersionDest, '755');
|
|
404
|
+
success('Script de verificación de versión instalado');
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Crear directorio .claude si no existe
|
|
408
|
+
const claudeDir = '.claude';
|
|
409
|
+
if (!fs.existsSync(claudeDir)) {
|
|
410
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
411
|
+
success('.claude directory created');
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Copiar archivos de pautas a .claude si no existen
|
|
312
415
|
const guidelines = ['CLAUDE_PRE_COMMIT.md', 'CLAUDE_PRE_COMMIT_SONAR.md'];
|
|
313
416
|
guidelines.forEach(guideline => {
|
|
314
|
-
|
|
417
|
+
const destPath = path.join(claudeDir, guideline);
|
|
418
|
+
if (!fs.existsSync(destPath)) {
|
|
315
419
|
const sourcePath = path.join(templatesPath, guideline);
|
|
316
420
|
if (fs.existsSync(sourcePath)) {
|
|
317
|
-
fs.copyFileSync(sourcePath,
|
|
318
|
-
success(`${guideline} creado
|
|
421
|
+
fs.copyFileSync(sourcePath, destPath);
|
|
422
|
+
success(`${guideline} creado en .claude/`);
|
|
319
423
|
}
|
|
320
424
|
}
|
|
321
425
|
});
|
|
@@ -323,6 +427,9 @@ async function install(args) {
|
|
|
323
427
|
// Configurar Git
|
|
324
428
|
configureGit();
|
|
325
429
|
|
|
430
|
+
// Actualizar .gitignore
|
|
431
|
+
updateGitignore();
|
|
432
|
+
|
|
326
433
|
success('¡Claude Git Hooks instalado exitosamente! 🎉');
|
|
327
434
|
console.log('\nUso:');
|
|
328
435
|
console.log(' git commit -m "auto" # Genera mensaje automáticamente');
|
|
@@ -409,7 +516,7 @@ async function checkAndInstallDependencies(sudoPassword = null) {
|
|
|
409
516
|
if (missingTools.length === 0) {
|
|
410
517
|
success('Herramientas Unix estándar verificadas');
|
|
411
518
|
} else {
|
|
412
|
-
error(`Faltan herramientas Unix estándar: ${missingTools.join(', ')}`);
|
|
519
|
+
error(`Faltan herramientas Unix estándar: ${missingTools.join(', ')}. Reintenta instalación en una consola Ubuntu`);
|
|
413
520
|
}
|
|
414
521
|
|
|
415
522
|
// Verificar e instalar Claude CLI
|
|
@@ -503,6 +610,79 @@ async function checkClaudeAuth() {
|
|
|
503
610
|
}
|
|
504
611
|
}
|
|
505
612
|
|
|
613
|
+
// Actualizar .gitignore con las entradas de Claude
|
|
614
|
+
function updateGitignore() {
|
|
615
|
+
info('Actualizando .gitignore...');
|
|
616
|
+
|
|
617
|
+
const gitignorePath = '.gitignore';
|
|
618
|
+
const claudeEntries = [
|
|
619
|
+
'# Claude Git Hooks',
|
|
620
|
+
'.claude/',
|
|
621
|
+
'.claude-analysis-mode',
|
|
622
|
+
'debug-claude-response.json'
|
|
623
|
+
];
|
|
624
|
+
|
|
625
|
+
let gitignoreContent = '';
|
|
626
|
+
let fileExists = false;
|
|
627
|
+
|
|
628
|
+
// Leer .gitignore existente si existe
|
|
629
|
+
if (fs.existsSync(gitignorePath)) {
|
|
630
|
+
gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
|
|
631
|
+
fileExists = true;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Verificar qué entradas faltan
|
|
635
|
+
const missingEntries = [];
|
|
636
|
+
claudeEntries.forEach(entry => {
|
|
637
|
+
if (entry.startsWith('#')) {
|
|
638
|
+
// Para comentarios, verificar si ya existe algún comentario de Claude
|
|
639
|
+
if (!gitignoreContent.includes('# Claude')) {
|
|
640
|
+
missingEntries.push(entry);
|
|
641
|
+
}
|
|
642
|
+
} else {
|
|
643
|
+
// Para entradas normales, verificar si ya están presentes
|
|
644
|
+
const regex = new RegExp(`^${entry.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'm');
|
|
645
|
+
if (!regex.test(gitignoreContent)) {
|
|
646
|
+
missingEntries.push(entry);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
// Si hay entradas faltantes, agregarlas
|
|
652
|
+
if (missingEntries.length > 0) {
|
|
653
|
+
// Asegurar que hay una nueva línea al final si el archivo existe y no está vacío
|
|
654
|
+
if (fileExists && gitignoreContent.length > 0 && !gitignoreContent.endsWith('\n')) {
|
|
655
|
+
gitignoreContent += '\n';
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Si el archivo no está vacío, agregar una línea en blanco antes
|
|
659
|
+
if (gitignoreContent.length > 0) {
|
|
660
|
+
gitignoreContent += '\n';
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Agregar las entradas faltantes
|
|
664
|
+
gitignoreContent += missingEntries.join('\n') + '\n';
|
|
665
|
+
|
|
666
|
+
// Escribir el archivo actualizado
|
|
667
|
+
fs.writeFileSync(gitignorePath, gitignoreContent);
|
|
668
|
+
|
|
669
|
+
if (fileExists) {
|
|
670
|
+
success('.gitignore actualizado con entradas de Claude');
|
|
671
|
+
} else {
|
|
672
|
+
success('.gitignore creado con entradas de Claude');
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Mostrar qué se agregó
|
|
676
|
+
missingEntries.forEach(entry => {
|
|
677
|
+
if (!entry.startsWith('#')) {
|
|
678
|
+
info(` + ${entry}`);
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
} else {
|
|
682
|
+
info('.gitignore ya contiene todas las entradas necesarias');
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
506
686
|
// Configurar Git (line endings, etc.)
|
|
507
687
|
function configureGit() {
|
|
508
688
|
info('Configurando Git...');
|
|
@@ -631,12 +811,40 @@ function status() {
|
|
|
631
811
|
console.log('\nArchivos de pautas:');
|
|
632
812
|
const guidelines = ['CLAUDE_PRE_COMMIT.md', 'CLAUDE_PRE_COMMIT_SONAR.md'];
|
|
633
813
|
guidelines.forEach(guideline => {
|
|
634
|
-
|
|
635
|
-
|
|
814
|
+
const claudePath = path.join('.claude', guideline);
|
|
815
|
+
if (fs.existsSync(claudePath)) {
|
|
816
|
+
success(`${guideline}: presente en .claude/`);
|
|
817
|
+
} else if (fs.existsSync(guideline)) {
|
|
818
|
+
warning(`${guideline}: presente en raíz (debería estar en .claude/)`);
|
|
636
819
|
} else {
|
|
637
820
|
warning(`${guideline}: faltante`);
|
|
638
821
|
}
|
|
639
822
|
});
|
|
823
|
+
|
|
824
|
+
// Verificar entradas en .gitignore
|
|
825
|
+
console.log('\n.gitignore:');
|
|
826
|
+
const gitignorePath = '.gitignore';
|
|
827
|
+
if (fs.existsSync(gitignorePath)) {
|
|
828
|
+
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
|
|
829
|
+
const claudeIgnores = ['.claude/', '.claude-analysis-mode', 'debug-claude-response.json'];
|
|
830
|
+
let allPresent = true;
|
|
831
|
+
|
|
832
|
+
claudeIgnores.forEach(entry => {
|
|
833
|
+
const regex = new RegExp(`^${entry.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'm');
|
|
834
|
+
if (regex.test(gitignoreContent)) {
|
|
835
|
+
success(`${entry}: incluido`);
|
|
836
|
+
} else {
|
|
837
|
+
warning(`${entry}: faltante`);
|
|
838
|
+
allPresent = false;
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
if (!allPresent) {
|
|
843
|
+
info('\nEjecuta "claude-hooks install" para actualizar .gitignore');
|
|
844
|
+
}
|
|
845
|
+
} else {
|
|
846
|
+
warning('.gitignore no existe');
|
|
847
|
+
}
|
|
640
848
|
}
|
|
641
849
|
|
|
642
850
|
// Comando set-mode
|
|
@@ -673,6 +881,87 @@ function setMode(mode) {
|
|
|
673
881
|
}
|
|
674
882
|
}
|
|
675
883
|
|
|
884
|
+
// Función para comparar versiones usando el script compartido
|
|
885
|
+
function compareVersions(v1, v2) {
|
|
886
|
+
try {
|
|
887
|
+
// Usar el script compartido para mantener consistencia
|
|
888
|
+
const result = execSync(`bash -c 'source "${getTemplatesPath()}/check-version.sh" && compare_versions "${v1}" "${v2}"; echo $?'`, { encoding: 'utf8' }).trim();
|
|
889
|
+
const exitCode = parseInt(result);
|
|
890
|
+
|
|
891
|
+
// Convertir los códigos de retorno del script bash a valores JS
|
|
892
|
+
if (exitCode === 0) return 0; // iguales
|
|
893
|
+
if (exitCode === 1) return 1; // v1 > v2
|
|
894
|
+
if (exitCode === 2) return -1; // v1 < v2
|
|
895
|
+
|
|
896
|
+
// Fallback: comparación simple si el script falla
|
|
897
|
+
if (v1 === v2) return 0;
|
|
898
|
+
const sorted = [v1, v2].sort((a, b) => {
|
|
899
|
+
const aParts = a.split('.').map(Number);
|
|
900
|
+
const bParts = b.split('.').map(Number);
|
|
901
|
+
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
|
|
902
|
+
const aPart = aParts[i] || 0;
|
|
903
|
+
const bPart = bParts[i] || 0;
|
|
904
|
+
if (aPart !== bPart) return aPart - bPart;
|
|
905
|
+
}
|
|
906
|
+
return 0;
|
|
907
|
+
});
|
|
908
|
+
return v1 === sorted[1] ? 1 : -1;
|
|
909
|
+
} catch (e) {
|
|
910
|
+
// Si falla todo, usar comparación simple
|
|
911
|
+
if (v1 === v2) return 0;
|
|
912
|
+
return v1 > v2 ? 1 : -1;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// Comando update - actualizar a la última versión
|
|
917
|
+
async function update() {
|
|
918
|
+
info('Verificando última versión disponible...');
|
|
919
|
+
|
|
920
|
+
try {
|
|
921
|
+
const currentVersion = require('../package.json').version;
|
|
922
|
+
const latestVersion = await getLatestVersion('claude-git-hooks');
|
|
923
|
+
|
|
924
|
+
const comparison = compareVersions(currentVersion, latestVersion);
|
|
925
|
+
|
|
926
|
+
if (comparison === 0) {
|
|
927
|
+
success(`Ya tienes la última versión instalada (${currentVersion})`);
|
|
928
|
+
return;
|
|
929
|
+
} else if (comparison > 0) {
|
|
930
|
+
info(`Estás usando una versión de desarrollo (${currentVersion})`);
|
|
931
|
+
info(`Última versión publicada: ${latestVersion}`);
|
|
932
|
+
success(`Ya tienes la última versión instalada (${currentVersion})`);
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
info(`Versión actual: ${currentVersion}`);
|
|
937
|
+
info(`Versión disponible: ${latestVersion}`);
|
|
938
|
+
|
|
939
|
+
// Actualizar el paquete
|
|
940
|
+
info('Actualizando claude-git-hooks...');
|
|
941
|
+
try {
|
|
942
|
+
execSync('npm install -g claude-git-hooks@latest', { stdio: 'inherit' });
|
|
943
|
+
success(`Actualizado exitosamente a la versión ${latestVersion}`);
|
|
944
|
+
|
|
945
|
+
// Reinstalar hooks con la nueva versión
|
|
946
|
+
info('Reinstalando hooks con la nueva versión...');
|
|
947
|
+
await install(['--force']);
|
|
948
|
+
|
|
949
|
+
} catch (updateError) {
|
|
950
|
+
error('Error al actualizar. Intenta ejecutar: npm install -g claude-git-hooks@latest');
|
|
951
|
+
}
|
|
952
|
+
} catch (e) {
|
|
953
|
+
warning('No se pudo verificar la última versión disponible');
|
|
954
|
+
warning('Intentando actualizar de todas formas...');
|
|
955
|
+
try {
|
|
956
|
+
execSync('npm install -g claude-git-hooks@latest', { stdio: 'inherit' });
|
|
957
|
+
success('Actualización completada');
|
|
958
|
+
await install(['--force']);
|
|
959
|
+
} catch (updateError) {
|
|
960
|
+
error('Error al actualizar: ' + updateError.message);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
|
|
676
965
|
// Comando help
|
|
677
966
|
function showHelp() {
|
|
678
967
|
console.log(`
|
|
@@ -682,6 +971,7 @@ Uso: claude-hooks <comando> [opciones]
|
|
|
682
971
|
|
|
683
972
|
Comandos:
|
|
684
973
|
install Instala los hooks en el repositorio actual
|
|
974
|
+
update Actualiza a la última versión disponible
|
|
685
975
|
uninstall Desinstala los hooks del repositorio
|
|
686
976
|
enable [hook] Habilita hooks (todos o uno específico)
|
|
687
977
|
disable [hook] Deshabilita hooks (todos o uno específico)
|
|
@@ -695,6 +985,7 @@ Hooks disponibles:
|
|
|
695
985
|
|
|
696
986
|
Ejemplos:
|
|
697
987
|
claude-hooks install # Instala todos los hooks
|
|
988
|
+
claude-hooks update # Actualiza a la última versión
|
|
698
989
|
claude-hooks set-mode sonar # Cambiar a modo SonarQube
|
|
699
990
|
claude-hooks set-mode standard # Cambiar a modo estándar
|
|
700
991
|
claude-hooks disable pre-commit # Deshabilita solo pre-commit
|
|
@@ -714,6 +1005,9 @@ async function main() {
|
|
|
714
1005
|
case 'install':
|
|
715
1006
|
await install(args.slice(1));
|
|
716
1007
|
break;
|
|
1008
|
+
case 'update':
|
|
1009
|
+
await update();
|
|
1010
|
+
break;
|
|
717
1011
|
case 'uninstall':
|
|
718
1012
|
uninstall();
|
|
719
1013
|
break;
|
package/package.json
CHANGED
|
@@ -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
|
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Script compartido para verificar versión de claude-git-hooks
|
|
4
|
+
# Este script puede ser invocado desde cualquier hook
|
|
5
|
+
|
|
6
|
+
# Colores para output
|
|
7
|
+
RED='\033[0;31m'
|
|
8
|
+
GREEN='\033[0;32m'
|
|
9
|
+
YELLOW='\033[1;33m'
|
|
10
|
+
BLUE='\033[0;34m'
|
|
11
|
+
NC='\033[0m' # No Color
|
|
12
|
+
|
|
13
|
+
# Función para logging
|
|
14
|
+
log() {
|
|
15
|
+
echo -e "${GREEN}[VERSION-CHECK]${NC} $1"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
error() {
|
|
19
|
+
echo -e "${RED}[ERROR]${NC} $1"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
warning() {
|
|
23
|
+
echo -e "${YELLOW}[WARNING]${NC} $1"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
info() {
|
|
27
|
+
echo -e "${BLUE}[INFO]${NC} $1"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Función compartida para comparar versiones semánticas
|
|
31
|
+
# Retorna: 1 si v1 > v2, 2 si v1 < v2, 0 si son iguales
|
|
32
|
+
compare_versions() {
|
|
33
|
+
local v1="$1"
|
|
34
|
+
local v2="$2"
|
|
35
|
+
|
|
36
|
+
if [ -n "$DEBUG_VERSION" ]; then
|
|
37
|
+
echo "[DEBUG] compare_versions: comparando '$v1' con '$v2'"
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# Si son exactamente iguales
|
|
41
|
+
if [ "$v1" = "$v2" ]; then
|
|
42
|
+
if [ -n "$DEBUG_VERSION" ]; then
|
|
43
|
+
echo "[DEBUG] compare_versions: versiones iguales, retornando 0"
|
|
44
|
+
fi
|
|
45
|
+
return 0
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# Usar sort -V que entiende versiones semánticas correctamente
|
|
49
|
+
local sorted=$(printf "%s\n%s" "$v1" "$v2" | sort -V)
|
|
50
|
+
local smaller=$(echo "$sorted" | head -1)
|
|
51
|
+
local larger=$(echo "$sorted" | tail -1)
|
|
52
|
+
|
|
53
|
+
if [ -n "$DEBUG_VERSION" ]; then
|
|
54
|
+
echo "[DEBUG] compare_versions: sorted='$sorted', smaller='$smaller', larger='$larger'"
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
if [ "$v1" = "$larger" ]; then
|
|
58
|
+
if [ -n "$DEBUG_VERSION" ]; then
|
|
59
|
+
echo "[DEBUG] compare_versions: v1 es mayor, retornando 1"
|
|
60
|
+
fi
|
|
61
|
+
return 1 # v1 es mayor que v2
|
|
62
|
+
else
|
|
63
|
+
if [ -n "$DEBUG_VERSION" ]; then
|
|
64
|
+
echo "[DEBUG] compare_versions: v1 es menor, retornando 2"
|
|
65
|
+
fi
|
|
66
|
+
return 2 # v1 es menor que v2 (cambié de 255 a 2 para evitar problemas con set -e)
|
|
67
|
+
fi
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# Función principal de verificación
|
|
71
|
+
check_version() {
|
|
72
|
+
# Debug mode si está activado
|
|
73
|
+
if [ -n "$DEBUG_VERSION" ]; then
|
|
74
|
+
echo "[DEBUG] Iniciando verificación de versión..."
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
info "Verificando versión instalada..."
|
|
78
|
+
|
|
79
|
+
# Intentar obtener versión actual desde package.json global
|
|
80
|
+
CURRENT_VERSION=""
|
|
81
|
+
NPM_PREFIX=$(npm prefix -g 2>/dev/null || echo "/usr/local")
|
|
82
|
+
PACKAGE_JSON_PATHS=(
|
|
83
|
+
"$NPM_PREFIX/lib/node_modules/claude-git-hooks/package.json"
|
|
84
|
+
"/usr/lib/node_modules/claude-git-hooks/package.json"
|
|
85
|
+
"/usr/local/lib/node_modules/claude-git-hooks/package.json"
|
|
86
|
+
"$HOME/.npm-global/lib/node_modules/claude-git-hooks/package.json"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
for path in "${PACKAGE_JSON_PATHS[@]}"; do
|
|
90
|
+
if [ -f "$path" ]; then
|
|
91
|
+
CURRENT_VERSION=$(grep '"version"' "$path" 2>/dev/null | sed 's/.*"version".*"\(.*\)".*/\1/')
|
|
92
|
+
break
|
|
93
|
+
fi
|
|
94
|
+
done
|
|
95
|
+
|
|
96
|
+
if [ -z "$CURRENT_VERSION" ]; then
|
|
97
|
+
warning "No se pudo determinar la versión actual"
|
|
98
|
+
return 0 # Continuar sin bloquear
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
if [ -n "$DEBUG_VERSION" ]; then
|
|
102
|
+
echo "[DEBUG] Versión actual encontrada: $CURRENT_VERSION"
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
# Obtener última versión desde NPM (usando npm view que es más confiable)
|
|
106
|
+
info "Verificando última versión disponible..."
|
|
107
|
+
|
|
108
|
+
# Método principal: usar npm view que es más confiable
|
|
109
|
+
if [ -n "$DEBUG_VERSION" ]; then
|
|
110
|
+
echo "[DEBUG] Ejecutando: npm view claude-git-hooks version"
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
LATEST_VERSION=$(npm view claude-git-hooks version 2>/dev/null || echo "")
|
|
114
|
+
|
|
115
|
+
if [ -n "$DEBUG_VERSION" ]; then
|
|
116
|
+
echo "[DEBUG] npm view retornó: '$LATEST_VERSION'"
|
|
117
|
+
echo "[DEBUG] Longitud de LATEST_VERSION: ${#LATEST_VERSION}"
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# Si npm view falla, intentar con la API de NPM
|
|
121
|
+
if [ -z "$LATEST_VERSION" ]; then
|
|
122
|
+
# Obtener el JSON completo y extraer dist-tags.latest con jq si está disponible
|
|
123
|
+
if command -v jq &> /dev/null; then
|
|
124
|
+
LATEST_VERSION=$(curl -s https://registry.npmjs.org/claude-git-hooks 2>/dev/null | jq -r '."dist-tags".latest' 2>/dev/null || echo "")
|
|
125
|
+
if [ -n "$DEBUG_VERSION" ]; then
|
|
126
|
+
echo "[DEBUG] jq parsing retornó: '$LATEST_VERSION'"
|
|
127
|
+
fi
|
|
128
|
+
else
|
|
129
|
+
# Sin jq, usar método más robusto con sed
|
|
130
|
+
LATEST_VERSION=$(curl -s https://registry.npmjs.org/claude-git-hooks 2>/dev/null | sed -n 's/.*"dist-tags":{[^}]*"latest":"\([^"]*\)".*/\1/p' | head -1)
|
|
131
|
+
if [ -n "$DEBUG_VERSION" ]; then
|
|
132
|
+
echo "[DEBUG] sed parsing retornó: '$LATEST_VERSION'"
|
|
133
|
+
fi
|
|
134
|
+
fi
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
# Validar que lo obtenido parece una versión (formato X.Y.Z)
|
|
138
|
+
if [ -n "$DEBUG_VERSION" ]; then
|
|
139
|
+
echo "[DEBUG] Validando versión: '$LATEST_VERSION'"
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
if ! echo "$LATEST_VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+' ; then
|
|
143
|
+
if [ -n "$DEBUG_VERSION" ]; then
|
|
144
|
+
echo "[DEBUG] Versión obtenida no es válida: '$LATEST_VERSION'"
|
|
145
|
+
fi
|
|
146
|
+
warning "No se pudo obtener una versión válida del registro NPM"
|
|
147
|
+
return 0
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
if [ -n "$DEBUG_VERSION" ]; then
|
|
151
|
+
echo "[DEBUG] Versión válida confirmada: '$LATEST_VERSION'"
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
if [ -z "$LATEST_VERSION" ]; then
|
|
155
|
+
# Si no se puede obtener la versión, continuar sin bloquear
|
|
156
|
+
return 0
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
if [ "$CURRENT_VERSION" = "$LATEST_VERSION" ]; then
|
|
160
|
+
log "Versión actual ($CURRENT_VERSION) está actualizada ✓"
|
|
161
|
+
return 0
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
# Comparar versiones usando la función compartida
|
|
165
|
+
if [ -n "$DEBUG_VERSION" ]; then
|
|
166
|
+
echo "[DEBUG] Comparando versiones: '$CURRENT_VERSION' vs '$LATEST_VERSION'"
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
# Deshabilitar set -e temporalmente para capturar el código de retorno
|
|
170
|
+
set +e
|
|
171
|
+
compare_versions "$CURRENT_VERSION" "$LATEST_VERSION"
|
|
172
|
+
comparison_result=$?
|
|
173
|
+
set -e
|
|
174
|
+
|
|
175
|
+
if [ -n "$DEBUG_VERSION" ]; then
|
|
176
|
+
echo "[DEBUG] Resultado de comparación: $comparison_result"
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
if [ $comparison_result -eq 0 ]; then
|
|
180
|
+
# Las versiones son iguales
|
|
181
|
+
if [ -n "$DEBUG_VERSION" ]; then
|
|
182
|
+
echo "[DEBUG] Las versiones son iguales"
|
|
183
|
+
fi
|
|
184
|
+
log "Versión actual ($CURRENT_VERSION) está actualizada ✓"
|
|
185
|
+
return 0
|
|
186
|
+
elif [ $comparison_result -eq 1 ]; then
|
|
187
|
+
# La versión actual es mayor que la publicada (desarrollo local)
|
|
188
|
+
if [ -n "$DEBUG_VERSION" ]; then
|
|
189
|
+
echo "[DEBUG] Versión local ($CURRENT_VERSION) es más nueva que la publicada ($LATEST_VERSION)"
|
|
190
|
+
fi
|
|
191
|
+
log "Versión de desarrollo ($CURRENT_VERSION) > versión publicada ($LATEST_VERSION) ✓"
|
|
192
|
+
return 0
|
|
193
|
+
elif [ $comparison_result -eq 2 ]; then
|
|
194
|
+
# La versión publicada es mayor, continuar con prompt de actualización
|
|
195
|
+
if [ -n "$DEBUG_VERSION" ]; then
|
|
196
|
+
echo "[DEBUG] La versión publicada ($LATEST_VERSION) es más nueva que la local ($CURRENT_VERSION)"
|
|
197
|
+
fi
|
|
198
|
+
# Continuar con el prompt de actualización (no return aquí)
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
if [ -n "$DEBUG_VERSION" ]; then
|
|
202
|
+
echo "[DEBUG] La versión publicada es más nueva, procediendo con prompt de actualización"
|
|
203
|
+
fi
|
|
204
|
+
|
|
205
|
+
# Solo mostrar actualización si la versión publicada es realmente más nueva
|
|
206
|
+
echo
|
|
207
|
+
warning "Nueva versión disponible: $LATEST_VERSION (actual: $CURRENT_VERSION)"
|
|
208
|
+
echo -e "${YELLOW}╭─────────────────────────────────────────────╮${NC}"
|
|
209
|
+
echo -e "${YELLOW}│${NC} Una nueva versión de claude-git-hooks está ${YELLOW}│${NC}"
|
|
210
|
+
echo -e "${YELLOW}│${NC} disponible con mejoras y correcciones ${YELLOW}│${NC}"
|
|
211
|
+
echo -e "${YELLOW}╰─────────────────────────────────────────────╯${NC}"
|
|
212
|
+
echo
|
|
213
|
+
|
|
214
|
+
# Leer respuesta del usuario de forma compatible con todas las consolas
|
|
215
|
+
echo -n "¿Deseas actualizar ahora? (s/n): "
|
|
216
|
+
|
|
217
|
+
# Intentar varios métodos de lectura para máxima compatibilidad
|
|
218
|
+
REPLY=""
|
|
219
|
+
|
|
220
|
+
# Método 1: Leer desde /dev/tty si existe
|
|
221
|
+
if [ -c /dev/tty ]; then
|
|
222
|
+
read -r REPLY </dev/tty 2>/dev/null || REPLY=""
|
|
223
|
+
fi
|
|
224
|
+
|
|
225
|
+
# Método 2: Si falla, intentar leer desde stdin estándar
|
|
226
|
+
if [ -z "$REPLY" ]; then
|
|
227
|
+
read -r REPLY 2>/dev/null || REPLY=""
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
# Método 3: Si aún no tenemos respuesta, usar bash -c
|
|
231
|
+
if [ -z "$REPLY" ]; then
|
|
232
|
+
REPLY=$(bash -c 'read -r reply && echo "$reply"' 2>/dev/null || echo "")
|
|
233
|
+
fi
|
|
234
|
+
|
|
235
|
+
# Si ningún método funcionó, informar y continuar
|
|
236
|
+
if [ -z "$REPLY" ]; then
|
|
237
|
+
echo
|
|
238
|
+
info "No se pudo leer la respuesta. Continuando sin actualizar."
|
|
239
|
+
info "Para actualizar manualmente, ejecuta: claude-hooks update"
|
|
240
|
+
return 0
|
|
241
|
+
fi
|
|
242
|
+
|
|
243
|
+
if [ "$REPLY" = "s" ] || [ "$REPLY" = "S" ] || [ "$REPLY" = "si" ] || [ "$REPLY" = "SI" ] || [ "$REPLY" = "sí" ] || [ "$REPLY" = "SÍ" ] || [ "$REPLY" = "y" ] || [ "$REPLY" = "Y" ] || [ "$REPLY" = "yes" ] || [ "$REPLY" = "YES" ]; then
|
|
244
|
+
echo
|
|
245
|
+
info "Actualizando claude-git-hooks..."
|
|
246
|
+
|
|
247
|
+
if npm install -g claude-git-hooks@latest; then
|
|
248
|
+
echo
|
|
249
|
+
log "✅ Actualización completada exitosamente"
|
|
250
|
+
info "Por favor, vuelve a ejecutar tu comando git commit"
|
|
251
|
+
exit 0 # Salir para que el usuario reinicie con la nueva versión
|
|
252
|
+
else
|
|
253
|
+
error "Error al actualizar. Intenta manualmente con: npm install -g claude-git-hooks@latest"
|
|
254
|
+
return 1
|
|
255
|
+
fi
|
|
256
|
+
else
|
|
257
|
+
info "Actualización pospuesta. Puedes actualizar más tarde con: claude-hooks update"
|
|
258
|
+
echo
|
|
259
|
+
return 0
|
|
260
|
+
fi
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
# Si se ejecuta directamente (no como source), ejecutar la verificación
|
|
264
|
+
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
|
|
265
|
+
check_version
|
|
266
|
+
fi
|
package/templates/pre-commit
CHANGED
|
@@ -14,6 +14,7 @@ MAX_FILE_SIZE=100000 # 100KB máximo por archivo
|
|
|
14
14
|
RED='\033[0;31m'
|
|
15
15
|
GREEN='\033[0;32m'
|
|
16
16
|
YELLOW='\033[1;33m'
|
|
17
|
+
BLUE='\033[0;34m'
|
|
17
18
|
NC='\033[0m' # No Color
|
|
18
19
|
|
|
19
20
|
# Función para logging
|
|
@@ -29,6 +30,32 @@ warning() {
|
|
|
29
30
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
# Verificar versión al inicio (antes de cualquier análisis)
|
|
34
|
+
# Intentar encontrar el script check-version.sh
|
|
35
|
+
CHECK_VERSION_SCRIPT=""
|
|
36
|
+
# Buscar en múltiples ubicaciones posibles
|
|
37
|
+
SCRIPT_PATHS=(
|
|
38
|
+
"$(dirname "$0")/check-version.sh"
|
|
39
|
+
"/usr/local/lib/node_modules/claude-git-hooks/templates/check-version.sh"
|
|
40
|
+
"/usr/lib/node_modules/claude-git-hooks/templates/check-version.sh"
|
|
41
|
+
"$HOME/.npm-global/lib/node_modules/claude-git-hooks/templates/check-version.sh"
|
|
42
|
+
"$(npm prefix -g 2>/dev/null)/lib/node_modules/claude-git-hooks/templates/check-version.sh"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
for path in "${SCRIPT_PATHS[@]}"; do
|
|
46
|
+
if [ -f "$path" ]; then
|
|
47
|
+
CHECK_VERSION_SCRIPT="$path"
|
|
48
|
+
break
|
|
49
|
+
fi
|
|
50
|
+
done
|
|
51
|
+
|
|
52
|
+
# Si encontramos el script, ejecutarlo
|
|
53
|
+
if [ -n "$CHECK_VERSION_SCRIPT" ]; then
|
|
54
|
+
# Source el script para tener acceso a la función
|
|
55
|
+
source "$CHECK_VERSION_SCRIPT"
|
|
56
|
+
check_version
|
|
57
|
+
fi
|
|
58
|
+
|
|
32
59
|
# Detectar qué archivo de pautas usar
|
|
33
60
|
# Prioridad: variable de entorno > archivo > pregunta interactiva
|
|
34
61
|
if [ -n "$CLAUDE_ANALYSIS_MODE" ]; then
|
|
@@ -64,10 +91,10 @@ if [ "$ANALYSIS_MODE" != "standard" ] && [ "$ANALYSIS_MODE" != "sonar" ]; then
|
|
|
64
91
|
|
|
65
92
|
if [ "$REPLY" = "2" ]; then
|
|
66
93
|
ANALYSIS_MODE="sonar"
|
|
67
|
-
GUIDELINES_FILE="CLAUDE_PRE_COMMIT_SONAR.md"
|
|
94
|
+
GUIDELINES_FILE=".claude/CLAUDE_PRE_COMMIT_SONAR.md"
|
|
68
95
|
else
|
|
69
96
|
ANALYSIS_MODE="standard"
|
|
70
|
-
GUIDELINES_FILE="CLAUDE_PRE_COMMIT.md"
|
|
97
|
+
GUIDELINES_FILE=".claude/CLAUDE_PRE_COMMIT.md"
|
|
71
98
|
fi
|
|
72
99
|
|
|
73
100
|
# Guardar preferencia para futuros commits
|
|
@@ -78,10 +105,10 @@ if [ "$ANALYSIS_MODE" != "standard" ] && [ "$ANALYSIS_MODE" != "sonar" ]; then
|
|
|
78
105
|
else
|
|
79
106
|
# Usar modo guardado
|
|
80
107
|
if [ "$ANALYSIS_MODE" = "sonar" ]; then
|
|
81
|
-
GUIDELINES_FILE="CLAUDE_PRE_COMMIT_SONAR.md"
|
|
108
|
+
GUIDELINES_FILE=".claude/CLAUDE_PRE_COMMIT_SONAR.md"
|
|
82
109
|
log "Usando modo de análisis: SonarQube"
|
|
83
110
|
else
|
|
84
|
-
GUIDELINES_FILE="CLAUDE_PRE_COMMIT.md"
|
|
111
|
+
GUIDELINES_FILE=".claude/CLAUDE_PRE_COMMIT.md"
|
|
85
112
|
log "Usando modo de análisis: Estándar"
|
|
86
113
|
fi
|
|
87
114
|
fi
|
|
@@ -233,7 +260,7 @@ if $CLAUDE_CLI < "$PROMPT_FILE" > "$RESPONSE_FILE" 2>&1; then
|
|
|
233
260
|
BLOCKING_ISSUES=$(echo "$JSON_RESPONSE" | jq -r '.blockingIssues[]?' 2>/dev/null | sed '/^$/d')
|
|
234
261
|
|
|
235
262
|
# Verificar si estamos en modo SonarQube
|
|
236
|
-
QUALITY_GATE=$(echo "$JSON_RESPONSE" | jq -r '.
|
|
263
|
+
QUALITY_GATE=$(echo "$JSON_RESPONSE" | jq -r '.QUALITY_GATE // ""' 2>/dev/null)
|
|
237
264
|
|
|
238
265
|
if [ "$ANALYSIS_MODE" = "sonar" ] && [ -n "$QUALITY_GATE" ] && [ "$QUALITY_GATE" != "null" ]; then
|
|
239
266
|
# Mostrar resultados estilo SonarQube
|
|
@@ -265,15 +292,15 @@ if $CLAUDE_CLI < "$PROMPT_FILE" > "$RESPONSE_FILE" 2>&1; then
|
|
|
265
292
|
fi
|
|
266
293
|
|
|
267
294
|
# Issues Summary
|
|
268
|
-
|
|
269
|
-
if [ "$
|
|
295
|
+
ISSUES=$(echo "$JSON_RESPONSE" | jq -r '.issues // {}' 2>/dev/null)
|
|
296
|
+
if [ "$ISSUES" != "{}" ] && [ "$ISSUES" != "null" ]; then
|
|
270
297
|
echo "📋 ISSUES SUMMARY"
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
298
|
+
BLOCKER=$(echo "$ISSUES" | jq -r '.blocker // 0' 2>/dev/null)
|
|
299
|
+
CRITICAL=$(echo "$ISSUES" | jq -r '.critical // 0' 2>/dev/null)
|
|
300
|
+
MAJOR=$(echo "$ISSUES" | jq -r '.major // 0' 2>/dev/null)
|
|
301
|
+
MINOR=$(echo "$ISSUES" | jq -r '.minor // 0' 2>/dev/null)
|
|
302
|
+
INFO=$(echo "$ISSUES" | jq -r '.info // 0' 2>/dev/null)
|
|
303
|
+
TOTAL=$((BLOCKER + CRITICAL + MAJOR + MINOR + INFO))
|
|
277
304
|
|
|
278
305
|
echo "Total: $TOTAL issues found"
|
|
279
306
|
[ "$BLOCKER" -gt 0 ] && echo -e " ${RED}🔴 Blocker: $BLOCKER${NC}"
|
|
@@ -285,18 +312,19 @@ if $CLAUDE_CLI < "$PROMPT_FILE" > "$RESPONSE_FILE" 2>&1; then
|
|
|
285
312
|
fi
|
|
286
313
|
|
|
287
314
|
# Detailed Issues
|
|
288
|
-
|
|
289
|
-
if [ "$
|
|
315
|
+
DETAILS_COUNT=$(echo "$JSON_RESPONSE" | jq -r '.details | length' 2>/dev/null)
|
|
316
|
+
if [ "$DETAILS_COUNT" -gt 0 ] 2>/dev/null; then
|
|
290
317
|
echo "🔍 DETAILED ISSUES"
|
|
291
|
-
echo "$JSON_RESPONSE" | jq -r '.
|
|
292
|
-
"[\(.severity)] \(.type) in \(.file):\(.line // "?")\n \(.message)\n
|
|
318
|
+
echo "$JSON_RESPONSE" | jq -r '.details[]? |
|
|
319
|
+
"[\(.severity)] \(.type) in \(.file):\(.line // "?")\n \(.message)\n"' 2>/dev/null
|
|
293
320
|
fi
|
|
294
321
|
|
|
295
322
|
# Security Hotspots
|
|
296
|
-
|
|
297
|
-
if [ "$
|
|
298
|
-
echo "🔥 SECURITY HOTSPOTS"
|
|
299
|
-
echo "
|
|
323
|
+
HOTSPOTS=$(echo "$JSON_RESPONSE" | jq -r '.securityHotspots // 0' 2>/dev/null)
|
|
324
|
+
if [ "$HOTSPOTS" -gt 0 ] 2>/dev/null; then
|
|
325
|
+
echo "🔥 SECURITY HOTSPOTS: $HOTSPOTS found"
|
|
326
|
+
echo " Review security-sensitive code carefully"
|
|
327
|
+
|
|
300
328
|
echo
|
|
301
329
|
fi
|
|
302
330
|
|
|
@@ -332,11 +360,18 @@ if $CLAUDE_CLI < "$PROMPT_FILE" > "$RESPONSE_FILE" 2>&1; then
|
|
|
332
360
|
exit 1
|
|
333
361
|
fi
|
|
334
362
|
|
|
335
|
-
|
|
336
|
-
|
|
363
|
+
# Mostrar detalles adicionales si existen
|
|
364
|
+
DETAILS=$(echo "$JSON_RESPONSE" | jq -r '.details // null')
|
|
365
|
+
if [ -n "$DETAILS" ] && [ "$DETAILS" != "null" ]; then
|
|
337
366
|
echo
|
|
338
|
-
echo "=== DETALLES
|
|
339
|
-
|
|
367
|
+
echo "=== DETALLES ADICIONALES ==="
|
|
368
|
+
# Si details es un string, imprimirlo directamente
|
|
369
|
+
if echo "$DETAILS" | jq -e 'type == "string"' >/dev/null 2>&1; then
|
|
370
|
+
echo "$DETAILS" | jq -r '.'
|
|
371
|
+
# Si es un objeto o array, formatearlo
|
|
372
|
+
else
|
|
373
|
+
echo "$DETAILS" | jq '.'
|
|
374
|
+
fi
|
|
340
375
|
fi
|
|
341
376
|
|
|
342
377
|
log "✅ Revisión completada. Commit aprobado (Score: $SCORE/10)"
|
|
File without changes
|