claude-git-hooks 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Pablo Rovito
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,355 @@
1
+ # Pre-commit Hook con Claude CLI
2
+
3
+ Este directorio contiene un pre-commit hook que utiliza Claude CLI para revisar automáticamente el código antes de cada commit.
4
+
5
+ ## 📁 Archivos en este Directorio
6
+
7
+ - **`README.md`** - Esta documentación
8
+ - **`pre-commit`** - Hook principal para análisis de código (con auto-actualización)
9
+ - **`prepare-commit-msg`** - Hook para generar mensajes de commit automáticos
10
+ - **`setup-wsl.sh`** - Script de instalación inicial para WSL
11
+ - **`.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
14
+
15
+ ## 🔧 Configuración Previa Importante
16
+
17
+ ### 1. Armonización de Line Endings (EOL)
18
+
19
+ Debido a que Claude CLI corre en WSL y el desarrollo puede hacerse en Windows, es crucial configurar correctamente los line endings para evitar que los archivos de hooks se corrompan:
20
+
21
+ ```bash
22
+ # IMPORTANTE: Usar la misma configuración en WSL y Windows
23
+ # Recomendación: usar 'input' en ambos entornos
24
+
25
+ # En WSL
26
+ git config core.autocrlf input
27
+
28
+ # En PowerShell/Windows
29
+ git config core.autocrlf input
30
+
31
+ # Verificar configuración actual
32
+ git config --local core.autocrlf
33
+ git config --global core.autocrlf
34
+ ```
35
+
36
+ **⚠️ ADVERTENCIA**: Si tienes `core.autocrlf = true` en local e `input` en global, esto puede causar que los archivos de los hooks se vacíen. Asegúrate de que ambas configuraciones sean consistentes.
37
+
38
+ ### 2. Credenciales Git en WSL
39
+
40
+ Debes configurar tus credenciales de git nuevamente en WSL:
41
+
42
+ ```bash
43
+ # En WSL
44
+ git config --global user.name "Tu Nombre"
45
+ git config --global user.email "tu.email@ejemplo.com"
46
+
47
+ # Si usas AWS CodeCommit o similar, reconfigura las credenciales
48
+ git config --global credential.helper store
49
+ # O configura tu credential helper específico
50
+ ```
51
+
52
+ ## 🚀 Instalación Rápida (WSL)
53
+
54
+ **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
+ ```bash
57
+ # Desde WSL - Instalación completa
58
+ ./git-hooks/setup-wsl.sh
59
+
60
+ # Ver opciones disponibles
61
+ ./git-hooks/setup-wsl.sh --help
62
+
63
+ # Solo sincronizar hooks (útil después de modificar archivos fuente)
64
+ ./git-hooks/setup-wsl.sh --sync
65
+
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
71
+ ```
72
+
73
+ **✨ Auto-actualización incorporada**: Los hooks se actualizan automáticamente en cada commit.
74
+
75
+ **🤖 Hooks instalados**:
76
+
77
+ - `pre-commit`: Análisis de código con Claude (solo archivos Java/config)
78
+ - `prepare-commit-msg`: Generación automática de mensajes de commit
79
+
80
+
81
+ ## 🎯 Funcionamiento
82
+
83
+ ### Hook pre-commit (Análisis de código)
84
+
85
+ 1. **Intercepta cada intento de commit** (en WSL)
86
+ 2. **Extrae y filtra archivos modificados**:
87
+ - Solo analiza: Java, XML, properties, yml, yaml
88
+ - Omite archivos mayores a 100KB
89
+ - Límite de 10 archivos por commit
90
+ 3. **Modos de análisis disponibles**:
91
+ - **Estándar**: Formato clásico con score y recomendaciones detalladas
92
+ - **SonarQube**: Simula salida de SonarQube con Quality Gate, métricas y clasificación de issues
93
+ 4. **Construye prompt inteligente**:
94
+ - Lee las pautas desde `CLAUDE_PRE_COMMIT.md` o `CLAUDE_PRE_COMMIT_SONAR.md`
95
+ - Incluye el diff completo para archivos nuevos
96
+ - Muestra solo cambios para archivos existentes
97
+ 5. **Envía a Claude CLI para revisión**
98
+ 6. **Procesa respuesta JSON**:
99
+ - En modo estándar: evalúa `approved`, `score`, `recommendations`
100
+ - En modo SonarQube: verifica `QUALITY_GATE`, muestra métricas y issues por severidad
101
+ 7. **Decisión final**:
102
+ - Si hay problemas críticos o quality gate falla → commit bloqueado
103
+ - Si todo está bien → commit procede
104
+
105
+ ### Hook prepare-commit-msg (Generación automática de mensajes)
106
+
107
+ 1. **Se activa cuando el mensaje es**:
108
+ - Vacío (`""`)
109
+ - `"auto"`
110
+ - `"--"`
111
+ - `"tmp"`
112
+ 2. **Analiza los cambios del staging area**:
113
+ - Lista archivos modificados con estadísticas
114
+ - Incluye diffs completos para archivos < 100KB
115
+ 3. **Genera mensaje en formato Conventional Commits**:
116
+ - Determina tipo: feat, fix, docs, style, refactor, test, chore
117
+ - Crea título conciso y descriptivo
118
+ - Añade body con detalles si es necesario
119
+ 4. **Manejo de errores**:
120
+ - Si falla la generación → cancela el commit completamente
121
+ - No usa mensajes genéricos de fallback
122
+
123
+ ### Características adicionales
124
+
125
+ - **Auto-actualización**: Los hooks se sincronizan automáticamente con las versiones en `git-hooks/`
126
+ - **Modo debug**: `DEBUG=1 git commit` guarda respuestas en `debug-claude-response.json`
127
+ - **Configuración persistente**: Guarda preferencias en `.claude-analysis-mode`
128
+ - **Validación de dependencias**: Verifica que Claude CLI esté autenticado antes de ejecutar
129
+
130
+ ## 🛠️ Uso
131
+
132
+ **IMPORTANTE**: Todos los comandos git deben ejecutarse desde WSL.
133
+
134
+ ### Commit normal
135
+
136
+ ```bash
137
+ # Desde WSL
138
+ git add .
139
+ git commit -m "feat: nueva funcionalidad"
140
+ ```
141
+
142
+ ### Commit con mensaje automático 🤖
143
+
144
+ ```bash
145
+ # Desde WSL
146
+ git add .
147
+ git commit -m "auto" # Claude generará el mensaje automáticamente
148
+ ```
149
+
150
+ Claude analizará los cambios y generará un mensaje de commit siguiendo las convenciones (feat, fix, docs, etc.).
151
+
152
+ **⚠️ Si la generación automática falla**: El commit se cancelará completamente. Simplemente ejecuta `git commit -m "tu mensaje"` manualmente.
153
+
154
+ ### Selección de formato de análisis 📊
155
+
156
+ **Opción 1: Con variable de entorno (recomendado)**
157
+
158
+ ```bash
159
+ # Para formato estándar
160
+ export CLAUDE_ANALYSIS_MODE=standard
161
+ git commit -m "mensaje"
162
+
163
+ # Para formato SonarQube
164
+ export CLAUDE_ANALYSIS_MODE=sonar
165
+ git commit -m "mensaje"
166
+ ```
167
+
168
+ **Opción 2: Configuración interactiva**
169
+
170
+ ```bash
171
+ 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
175
+ ```
176
+
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:
178
+
179
+ - Scripts automatizados
180
+ - CI/CD pipelines
181
+ - Algunos emuladores de terminal
182
+ - Cuando no hay TTY disponible
183
+
184
+ El sistema automáticamente usará el modo **estándar** como fallback sin preguntar.
185
+
186
+ **Opción 3: Cambiar configuración guardada**
187
+
188
+ ```bash
189
+ .git/hooks/pre-commit --change-mode
190
+ ```
191
+
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
+ ### Saltar la revisión (usar con precaución)
197
+
198
+ ```bash
199
+ # Desde WSL
200
+ git commit --no-verify -m "fix: corrección urgente"
201
+ ```
202
+
203
+ ### Modo debug
204
+
205
+ ```bash
206
+ # Desde WSL
207
+ DEBUG=1 git commit -m "mensaje"
208
+ ```
209
+
210
+ ## 📊 Formato de Respuesta
211
+
212
+ Claude responde con un JSON que incluye:
213
+
214
+ - `approved`: Si el commit es aprobado
215
+ - `score`: Puntuación del 1-10
216
+ - `commitMessage`: Mensaje de commit generado (type, title, body)
217
+ - `recommendations`: Recomendaciones de mejora
218
+ - `blockingIssues`: Problemas que bloquean el commit
219
+
220
+ ## 🔄 Desactivar/Activar
221
+
222
+ ```bash
223
+ # Desactivar todos los hooks
224
+ ./git-hooks/setup-wsl.sh --disable-hooks
225
+
226
+ # 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
229
+
230
+ # Habilitar todos los hooks
231
+ ./git-hooks/setup-wsl.sh --enable-hooks
232
+
233
+ # 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
236
+ ```
237
+
238
+ ## ⚙️ Configuración
239
+
240
+ ### Variables de entorno disponibles
241
+
242
+ - **`CLAUDE_ANALYSIS_MODE`**: Selecciona el modo de análisis
243
+
244
+ - Valores: `"standard"` o `"sonar"`
245
+ - Ejemplo: `CLAUDE_ANALYSIS_MODE=sonar git commit -m "mensaje"`
246
+ - Sobrescribe la preferencia guardada en `.claude-analysis-mode`
247
+
248
+ - **`DEBUG`**: Activa el modo debug
249
+ - Valores: `1` para activar
250
+ - Ejemplo: `DEBUG=1 git commit -m "mensaje"`
251
+ - Guarda las respuestas de Claude en `debug-claude-response.json`
252
+
253
+ ### Variables modificables en los archivos de hooks
254
+
255
+ En el archivo `pre-commit`:
256
+
257
+ - **`MAX_FILE_SIZE`**: Tamaño máximo de archivo a analizar (default: 100KB)
258
+ - **`MAX_FILES`**: Número máximo de archivos por commit (default: 10)
259
+ - **`CLAUDE_CLI`**: Comando de Claude CLI (default: "claude")
260
+ - **`GUIDELINES_FILE`**: Archivo de pautas para modo estándar (default: "CLAUDE_PRE_COMMIT.md")
261
+ - **`GUIDELINES_FILE_SONAR`**: Archivo de pautas para modo SonarQube (default: "CLAUDE_PRE_COMMIT_SONAR.md")
262
+
263
+ En el archivo `prepare-commit-msg`:
264
+
265
+ - **`MAX_FILE_SIZE`**: Tamaño máximo para incluir diff completo (default: 100KB)
266
+ - **`CLAUDE_CLI`**: Comando de Claude CLI (default: "claude")
267
+
268
+ ## 🐛 Troubleshooting
269
+
270
+ ### "Claude CLI no está instalado"
271
+
272
+ ```bash
273
+ # Desde WSL
274
+ npm install -g @anthropic-ai/claude-cli
275
+ claude auth login
276
+ ```
277
+
278
+ ### "No se recibió respuesta JSON válida"
279
+
280
+ - Verifica que Claude CLI esté autenticado en WSL
281
+ - Ejecuta en modo DEBUG para ver la respuesta completa
282
+ - Revisa que el archivo de pautas esté bien formateado
283
+
284
+ ### El hook no se ejecuta
285
+
286
+ - Verifica que estés ejecutando git desde WSL
287
+ - Verifica permisos: `ls -la .git/hooks/pre-commit`
288
+ - Debe mostrar permisos de ejecución (-rwxr-xr-x)
289
+
290
+ ### Problemas con line endings
291
+
292
+ - Si ves errores de "^M" o caracteres extraños:
293
+ ```bash
294
+ # En WSL
295
+ git config core.autocrlf input
296
+ ```
297
+
298
+ ### Los hooks se vacían o corrompen
299
+
300
+ Este problema suele ocurrir por conflictos de configuración de line endings:
301
+
302
+ 1. **Verifica configuraciones**:
303
+
304
+ ```bash
305
+ git config --local core.autocrlf # Debe ser 'input'
306
+ git config --global core.autocrlf # Debe ser 'input'
307
+ ```
308
+
309
+ 2. **Si están diferentes, corrige**:
310
+
311
+ ```bash
312
+ git config --local core.autocrlf input
313
+ ```
314
+
315
+ 3. **Restaura los hooks**:
316
+ ```bash
317
+ ./git-hooks/setup-wsl.sh --sync
318
+ ```
319
+
320
+ ### Credenciales no funcionan en WSL
321
+
322
+ - Las credenciales de Windows no se comparten automáticamente con WSL
323
+ - Reconfigura tus credenciales git en WSL
324
+
325
+ ## 📝 Personalización
326
+
327
+ Puedes modificar las pautas de evaluación editando `CLAUDE_PRE_COMMIT.md` para adaptarlas a los estándares de tu equipo.
328
+
329
+ ## 🔄 Arquitectura del Sistema
330
+
331
+ ```
332
+ WSL Terminal
333
+
334
+ ├─→ git add/commit
335
+
336
+ └─→ .git/hooks/pre-commit
337
+
338
+ ├─→ Lee archivos modificados
339
+ ├─→ Lee CLAUDE_PRE_COMMIT.md
340
+ ├─→ Construye prompt
341
+
342
+ └─→ Claude CLI
343
+
344
+ └─→ Respuesta JSON
345
+
346
+ ├─→ ✅ Approved → Commit procede
347
+ └─→ ❌ Rejected → Commit bloqueado
348
+ ```
349
+
350
+ ## 📌 Notas Importantes
351
+
352
+ 1. **Todo en WSL**: Claude CLI solo funciona en WSL, por lo que todos los comandos git deben ejecutarse desde ahí
353
+ 2. **Line Endings**: La configuración de `core.autocrlf` es crítica para evitar problemas entre Windows y Linux
354
+ 3. **Credenciales**: Necesitarás reconfigurar tus credenciales git en WSL
355
+ 4. **Performance**: La revisión puede tardar unos segundos dependiendo del tamaño de los cambios
@@ -0,0 +1,297 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { execSync } = require('child_process');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const os = require('os');
7
+
8
+ // Colores para output
9
+ const colors = {
10
+ reset: '\x1b[0m',
11
+ red: '\x1b[31m',
12
+ green: '\x1b[32m',
13
+ yellow: '\x1b[33m',
14
+ blue: '\x1b[34m'
15
+ };
16
+
17
+ function log(message, color = 'reset') {
18
+ console.log(`${colors[color]}${message}${colors.reset}`);
19
+ }
20
+
21
+ function error(message) {
22
+ console.error(`${colors.red}❌ ${message}${colors.reset}`);
23
+ process.exit(1);
24
+ }
25
+
26
+ function success(message) {
27
+ log(`✅ ${message}`, 'green');
28
+ }
29
+
30
+ function info(message) {
31
+ log(`ℹ️ ${message}`, 'blue');
32
+ }
33
+
34
+ function warning(message) {
35
+ log(`⚠️ ${message}`, 'yellow');
36
+ }
37
+
38
+ // Verificar si estamos en un repositorio git
39
+ function checkGitRepo() {
40
+ try {
41
+ execSync('git rev-parse --git-dir', { stdio: 'ignore' });
42
+ return true;
43
+ } catch (e) {
44
+ return false;
45
+ }
46
+ }
47
+
48
+ // Obtener la ruta de los templates
49
+ function getTemplatesPath() {
50
+ return path.join(__dirname, '..', 'templates');
51
+ }
52
+
53
+ // Comando install
54
+ function install(args) {
55
+ if (!checkGitRepo()) {
56
+ error('No estás en un repositorio Git. Por favor, ejecuta este comando desde la raíz de un repositorio.');
57
+ }
58
+
59
+ info('Instalando Claude Git Hooks...');
60
+
61
+ const templatesPath = getTemplatesPath();
62
+ const hooksPath = '.git/hooks';
63
+
64
+ // Crear directorio hooks si no existe
65
+ if (!fs.existsSync(hooksPath)) {
66
+ fs.mkdirSync(hooksPath, { recursive: true });
67
+ }
68
+
69
+ // Hooks a instalar
70
+ const hooks = ['pre-commit', 'prepare-commit-msg'];
71
+
72
+ hooks.forEach(hook => {
73
+ const sourcePath = path.join(templatesPath, hook);
74
+ const destPath = path.join(hooksPath, hook);
75
+
76
+ // Hacer backup si existe
77
+ if (fs.existsSync(destPath)) {
78
+ const backupPath = `${destPath}.backup.${Date.now()}`;
79
+ fs.copyFileSync(destPath, backupPath);
80
+ info(`Backup creado: ${backupPath}`);
81
+ }
82
+
83
+ // Copiar hook
84
+ fs.copyFileSync(sourcePath, destPath);
85
+ fs.chmodSync(destPath, '755');
86
+ success(`${hook} instalado`);
87
+ });
88
+
89
+ // Copiar archivos de pautas si no existen
90
+ const guidelines = ['CLAUDE_PRE_COMMIT.md', 'CLAUDE_PRE_COMMIT_SONAR.md'];
91
+ guidelines.forEach(guideline => {
92
+ if (!fs.existsSync(guideline)) {
93
+ const sourcePath = path.join(templatesPath, guideline);
94
+ fs.copyFileSync(sourcePath, guideline);
95
+ success(`${guideline} creado`);
96
+ }
97
+ });
98
+
99
+ // Verificar dependencias
100
+ checkDependencies();
101
+
102
+ success('¡Claude Git Hooks instalado exitosamente! 🎉');
103
+ console.log('\nUso:');
104
+ console.log(' git commit -m "auto" # Genera mensaje automáticamente');
105
+ console.log(' git commit -m "mensaje" # Analiza código antes del commit');
106
+ console.log('\nPara más opciones: claude-hooks --help');
107
+ }
108
+
109
+ // Verificar dependencias
110
+ function checkDependencies() {
111
+ info('\nVerificando dependencias...');
112
+
113
+ // Verificar Claude CLI
114
+ try {
115
+ execSync('claude --version', { stdio: 'ignore' });
116
+ success('Claude CLI encontrado');
117
+ } 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');
121
+ }
122
+
123
+ // Verificar jq
124
+ try {
125
+ execSync('jq --version', { stdio: 'ignore' });
126
+ success('jq encontrado');
127
+ } 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');
133
+ }
134
+ }
135
+ }
136
+
137
+ // Comando uninstall
138
+ function uninstall() {
139
+ if (!checkGitRepo()) {
140
+ error('No estás en un repositorio Git.');
141
+ }
142
+
143
+ info('Desinstalando Claude Git Hooks...');
144
+
145
+ const hooksPath = '.git/hooks';
146
+ const hooks = ['pre-commit', 'prepare-commit-msg'];
147
+
148
+ hooks.forEach(hook => {
149
+ const hookPath = path.join(hooksPath, hook);
150
+ if (fs.existsSync(hookPath)) {
151
+ fs.unlinkSync(hookPath);
152
+ success(`${hook} eliminado`);
153
+ }
154
+ });
155
+
156
+ success('Claude Git Hooks desinstalado');
157
+ }
158
+
159
+ // Comando enable
160
+ function enable(hookName) {
161
+ if (!checkGitRepo()) {
162
+ error('No estás en un repositorio Git.');
163
+ }
164
+
165
+ const hooks = hookName ? [hookName] : ['pre-commit', 'prepare-commit-msg'];
166
+
167
+ hooks.forEach(hook => {
168
+ const disabledPath = `.git/hooks/${hook}.disabled`;
169
+ const enabledPath = `.git/hooks/${hook}`;
170
+
171
+ if (fs.existsSync(disabledPath)) {
172
+ fs.renameSync(disabledPath, enabledPath);
173
+ success(`${hook} habilitado`);
174
+ } else if (fs.existsSync(enabledPath)) {
175
+ info(`${hook} ya está habilitado`);
176
+ } else {
177
+ warning(`${hook} no encontrado`);
178
+ }
179
+ });
180
+ }
181
+
182
+ // Comando disable
183
+ function disable(hookName) {
184
+ if (!checkGitRepo()) {
185
+ error('No estás en un repositorio Git.');
186
+ }
187
+
188
+ const hooks = hookName ? [hookName] : ['pre-commit', 'prepare-commit-msg'];
189
+
190
+ hooks.forEach(hook => {
191
+ const enabledPath = `.git/hooks/${hook}`;
192
+ const disabledPath = `.git/hooks/${hook}.disabled`;
193
+
194
+ if (fs.existsSync(enabledPath)) {
195
+ fs.renameSync(enabledPath, disabledPath);
196
+ success(`${hook} deshabilitado`);
197
+ } else if (fs.existsSync(disabledPath)) {
198
+ info(`${hook} ya está deshabilitado`);
199
+ } else {
200
+ warning(`${hook} no encontrado`);
201
+ }
202
+ });
203
+ }
204
+
205
+ // Comando status
206
+ function status() {
207
+ if (!checkGitRepo()) {
208
+ error('No estás en un repositorio Git.');
209
+ }
210
+
211
+ info('Estado de Claude Git Hooks:\n');
212
+
213
+ const hooks = ['pre-commit', 'prepare-commit-msg'];
214
+ hooks.forEach(hook => {
215
+ const enabledPath = `.git/hooks/${hook}`;
216
+ const disabledPath = `.git/hooks/${hook}.disabled`;
217
+
218
+ if (fs.existsSync(enabledPath)) {
219
+ success(`${hook}: habilitado`);
220
+ } else if (fs.existsSync(disabledPath)) {
221
+ warning(`${hook}: deshabilitado`);
222
+ } else {
223
+ error(`${hook}: no instalado`);
224
+ }
225
+ });
226
+
227
+ // Verificar archivos de pautas
228
+ console.log('\nArchivos de pautas:');
229
+ const guidelines = ['CLAUDE_PRE_COMMIT.md', 'CLAUDE_PRE_COMMIT_SONAR.md'];
230
+ guidelines.forEach(guideline => {
231
+ if (fs.existsSync(guideline)) {
232
+ success(`${guideline}: presente`);
233
+ } else {
234
+ warning(`${guideline}: faltante`);
235
+ }
236
+ });
237
+ }
238
+
239
+ // Comando help
240
+ function showHelp() {
241
+ console.log(`
242
+ Claude Git Hooks - Análisis de código y mensajes automáticos con Claude CLI
243
+
244
+ Uso: claude-hooks <comando> [opciones]
245
+
246
+ Comandos:
247
+ install Instala los hooks en el repositorio actual
248
+ uninstall Desinstala los hooks del repositorio
249
+ enable [hook] Habilita hooks (todos o uno específico)
250
+ disable [hook] Deshabilita hooks (todos o uno específico)
251
+ status Muestra el estado de los hooks
252
+ help Muestra esta ayuda
253
+
254
+ Hooks disponibles:
255
+ pre-commit Análisis de código antes del commit
256
+ prepare-commit-msg Generación automática de mensajes
257
+
258
+ Ejemplos:
259
+ claude-hooks install # Instala todos los hooks
260
+ claude-hooks disable pre-commit # Deshabilita solo pre-commit
261
+ claude-hooks enable # Habilita todos los hooks
262
+ claude-hooks status # Ver estado actual
263
+
264
+ Más información: https://github.com/tresmares/claude-git-hooks
265
+ `);
266
+ }
267
+
268
+ // 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
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "claude-git-hooks",
3
+ "version": "1.0.0",
4
+ "description": "Git hooks con Claude CLI para análisis de código y generación automática de mensajes de commit",
5
+ "main": "lib/index.js",
6
+ "bin": {
7
+ "claude-hooks": "./bin/claude-hooks"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ },
12
+ "keywords": [
13
+ "git",
14
+ "hooks",
15
+ "claude",
16
+ "ai",
17
+ "code-review",
18
+ "commit-messages",
19
+ "pre-commit",
20
+ "automation"
21
+ ],
22
+ "author": "Pablo Rovito",
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/pablorovito/claude-git-hooks.git"
27
+ },
28
+ "engines": {
29
+ "node": ">=14.0.0"
30
+ },
31
+ "dependencies": {},
32
+ "preferGlobal": true,
33
+ "files": [
34
+ "bin/",
35
+ "lib/",
36
+ "templates/",
37
+ "README.md",
38
+ "LICENSE"
39
+ ]
40
+ }
@@ -0,0 +1,62 @@
1
+ # Pautas de Evaluación de Código - Claude Pre-commit Hook
2
+
3
+ ## Objetivo
4
+ Evaluar el código modificado antes del commit para asegurar calidad, mantenibilidad y adherencia a buenas prácticas.
5
+
6
+ ## Criterios de Evaluación
7
+
8
+ ### 1. Calidad del Código (40%)
9
+ - **Claridad y legibilidad**: El código es fácil de entender
10
+ - **Nombres descriptivos**: Variables, métodos y clases tienen nombres significativos
11
+ - **Estructura lógica**: El código está bien organizado
12
+ - **Principio DRY**: No hay duplicación innecesaria
13
+ - **Complejidad**: Funciones y métodos no son excesivamente complejos
14
+
15
+ ### 2. Buenas Prácticas (30%)
16
+ - **SOLID principles**: Adherencia cuando aplique
17
+ - **Patrones de diseño**: Uso apropiado cuando sea necesario
18
+ - **Manejo de errores**: Excepciones manejadas correctamente
19
+ - **Validación de inputs**: Datos de entrada validados
20
+ - **Separación de concerns**: Responsabilidades bien definidas
21
+
22
+ ### 3. Seguridad (20%)
23
+ - **No hay credenciales hardcodeadas**
24
+ - **Validación de datos de usuario**
25
+ - **No hay vulnerabilidades obvias** (SQL injection, XSS, etc.)
26
+ - **Manejo seguro de datos sensibles**
27
+
28
+ ### 4. Performance (10%)
29
+ - **No hay operaciones obviamente ineficientes**
30
+ - **Uso apropiado de estructuras de datos**
31
+ - **Queries optimizadas** (cuando aplique)
32
+
33
+ ## Formato de Respuesta Esperado
34
+
35
+ Responde ÚNICAMENTE con un JSON válido siguiendo esta estructura:
36
+
37
+ ```json
38
+ {
39
+ "approved": true/false,
40
+ "score": 8,
41
+ "recommendations": [
42
+ "Sugerencia 1",
43
+ "Sugerencia 2"
44
+ ],
45
+ "blockingIssues": [
46
+ "Problema crítico 1 (si existe)"
47
+ ]
48
+ }
49
+ ```
50
+
51
+ ## Reglas de Aprobación
52
+
53
+ - **approved = true** si score >= 7 Y no hay blockingIssues
54
+ - **approved = false** si score < 7 O hay blockingIssues
55
+
56
+ ## Problemas que SIEMPRE son Blocking Issues
57
+
58
+ 1. Credenciales o tokens hardcodeados
59
+ 2. Vulnerabilidades de seguridad evidentes
60
+ 3. Código que podría causar pérdida de datos
61
+ 4. Errores de sintaxis o código que no compilaría
62
+ 5. Eliminación de tests sin justificación
@@ -0,0 +1,86 @@
1
+ # Pautas de Evaluación de Código - Formato SonarQube
2
+
3
+ ## Objetivo
4
+ Evaluar el código modificado siguiendo métricas similares a SonarQube para asegurar calidad, confiabilidad y mantenibilidad.
5
+
6
+ ## Métricas de Evaluación
7
+
8
+ ### Reliability (Confiabilidad)
9
+ - Bugs potenciales
10
+ - Código que podría fallar en runtime
11
+ - Manejo inadecuado de excepciones
12
+ - Condiciones de carrera
13
+
14
+ ### Security (Seguridad)
15
+ - Vulnerabilidades de seguridad
16
+ - Credenciales expuestas
17
+ - Validación de inputs
18
+ - Inyección de código
19
+
20
+ ### Maintainability (Mantenibilidad)
21
+ - Code smells
22
+ - Complejidad ciclomática
23
+ - Duplicación de código
24
+ - Deuda técnica
25
+
26
+ ## Clasificación de Issues
27
+
28
+ ### Severidad
29
+ - **BLOCKER**: Debe corregirse inmediatamente
30
+ - **CRITICAL**: Debe corregirse antes del release
31
+ - **MAJOR**: Debe corregirse pronto
32
+ - **MINOR**: Debería corregirse eventualmente
33
+ - **INFO**: Sugerencia o mejora opcional
34
+
35
+ ## Formato de Respuesta Esperado
36
+
37
+ Responde ÚNICAMENTE con un JSON válido siguiendo esta estructura:
38
+
39
+ ```json
40
+ {
41
+ "QUALITY_GATE": "PASSED/FAILED",
42
+ "metrics": {
43
+ "reliability": "A/B/C/D/E",
44
+ "security": "A/B/C/D/E",
45
+ "maintainability": "A/B/C/D/E"
46
+ },
47
+ "issues": {
48
+ "blocker": 0,
49
+ "critical": 0,
50
+ "major": 0,
51
+ "minor": 0,
52
+ "info": 0
53
+ },
54
+ "details": [
55
+ {
56
+ "severity": "CRITICAL",
57
+ "type": "BUG/VULNERABILITY/CODE_SMELL",
58
+ "message": "Descripción del problema",
59
+ "file": "archivo.java",
60
+ "line": 42
61
+ }
62
+ ],
63
+ "securityHotspots": 0
64
+ }
65
+ ```
66
+
67
+ ## Reglas para Quality Gate
68
+
69
+ - **PASSED** si:
70
+ - No hay issues BLOCKER
71
+ - Issues CRITICAL <= 1
72
+ - Todas las métricas son A o B
73
+
74
+ - **FAILED** si:
75
+ - Hay al menos 1 issue BLOCKER
76
+ - Issues CRITICAL > 1
77
+ - Alguna métrica es D o E
78
+ - Security hotspots > 0
79
+
80
+ ## Rating de Métricas
81
+
82
+ - **A**: Excelente (0 issues)
83
+ - **B**: Bueno (1-2 issues minor)
84
+ - **C**: Aceptable (1 issue major o 3-5 minor)
85
+ - **D**: Pobre (2+ major o 1 critical)
86
+ - **E**: Muy pobre (1+ blocker o 2+ critical)
@@ -0,0 +1,352 @@
1
+ #!/bin/bash
2
+
3
+ # Git Pre-commit Hook para evaluación de código con Claude CLI
4
+ # Archivo: .git/hooks/pre-commit
5
+
6
+ set -e
7
+
8
+ # Configuración
9
+ CLAUDE_CLI="claude"
10
+ TEMP_DIR="/tmp/code-review-$$"
11
+ MAX_FILE_SIZE=100000 # 100KB máximo por archivo
12
+
13
+ # Colores para output
14
+ RED='\033[0;31m'
15
+ GREEN='\033[0;32m'
16
+ YELLOW='\033[1;33m'
17
+ NC='\033[0m' # No Color
18
+
19
+ # Función para logging
20
+ log() {
21
+ echo -e "${GREEN}[PRE-COMMIT]${NC} $1"
22
+ }
23
+
24
+ error() {
25
+ echo -e "${RED}[ERROR]${NC} $1"
26
+ }
27
+
28
+ warning() {
29
+ echo -e "${YELLOW}[WARNING]${NC} $1"
30
+ }
31
+
32
+ # Detectar qué archivo de pautas usar
33
+ # Prioridad: variable de entorno > archivo > pregunta interactiva
34
+ if [ -n "$CLAUDE_ANALYSIS_MODE" ]; then
35
+ ANALYSIS_MODE="$CLAUDE_ANALYSIS_MODE"
36
+ elif [ -f ".claude-analysis-mode" ]; then
37
+ ANALYSIS_MODE=$(cat .claude-analysis-mode)
38
+ else
39
+ ANALYSIS_MODE=""
40
+ fi
41
+
42
+ # Verificar si el usuario quiere cambiar el modo (con --change-mode)
43
+ if [ "$1" = "--change-mode" ]; then
44
+ ANALYSIS_MODE=""
45
+ fi
46
+
47
+ # Si no hay modo guardado o es inválido, preguntar
48
+ if [ "$ANALYSIS_MODE" != "standard" ] && [ "$ANALYSIS_MODE" != "sonar" ]; then
49
+ echo
50
+ echo "🔍 Selecciona el formato de análisis de código:"
51
+ echo "1) Estándar - Formato clásico con recomendaciones"
52
+ echo "2) SonarQube - Formato similar a SonarQube con métricas"
53
+ echo
54
+
55
+ # Intentar leer desde terminal
56
+ if [ -t 0 ] && [ -c /dev/tty ]; then
57
+ read -p "Opción (1 o 2): " -n 1 -r </dev/tty
58
+ echo
59
+ else
60
+ # Si no hay terminal interactiva, usar modo estándar por defecto
61
+ echo "No se puede leer input interactivo, usando modo estándar por defecto"
62
+ REPLY="1"
63
+ fi
64
+
65
+ if [ "$REPLY" = "2" ]; then
66
+ ANALYSIS_MODE="sonar"
67
+ GUIDELINES_FILE="CLAUDE_PRE_COMMIT_SONAR.md"
68
+ else
69
+ ANALYSIS_MODE="standard"
70
+ GUIDELINES_FILE="CLAUDE_PRE_COMMIT.md"
71
+ fi
72
+
73
+ # Guardar preferencia para futuros commits
74
+ echo "$ANALYSIS_MODE" > .claude-analysis-mode
75
+ echo -e "${GREEN}✓ Modo $ANALYSIS_MODE guardado para futuros commits${NC}"
76
+ echo -e "${YELLOW}Tip: Para cambiar el modo, ejecuta: .git/hooks/pre-commit --change-mode${NC}"
77
+ echo
78
+ else
79
+ # Usar modo guardado
80
+ if [ "$ANALYSIS_MODE" = "sonar" ]; then
81
+ GUIDELINES_FILE="CLAUDE_PRE_COMMIT_SONAR.md"
82
+ log "Usando modo de análisis: SonarQube"
83
+ else
84
+ GUIDELINES_FILE="CLAUDE_PRE_COMMIT.md"
85
+ log "Usando modo de análisis: Estándar"
86
+ fi
87
+ fi
88
+ # Función para limpiar archivos temporales
89
+ cleanup() {
90
+ rm -rf "$TEMP_DIR"
91
+ }
92
+
93
+ # Configurar limpieza al salir
94
+ trap cleanup EXIT
95
+
96
+ # Crear directorio temporal
97
+ mkdir -p "$TEMP_DIR"
98
+
99
+ # Verificar si Claude CLI está instalado (solo para análisis de código)
100
+ if ! command -v "$CLAUDE_CLI" &> /dev/null; then
101
+ error "Claude CLI no está instalado o no se encuentra en el PATH"
102
+ error "Instala Claude CLI desde: https://github.com/anthropics/claude-cli"
103
+ exit 1
104
+ fi
105
+
106
+ # Ahora verificar si hay archivos Java para analizar
107
+ JAVA_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(java|xml|properties|yml|yaml)$' || true)
108
+
109
+ if [ -z "$JAVA_FILES" ]; then
110
+ log "No hay archivos Java/configuración para revisar"
111
+ exit 0
112
+ fi
113
+
114
+ # Verificar si existe el archivo de pautas
115
+ if [ ! -f "$GUIDELINES_FILE" ]; then
116
+ warning "No se encontró el archivo de pautas: $GUIDELINES_FILE"
117
+ warning "Saltando análisis de código"
118
+ exit 0
119
+ fi
120
+
121
+ log "Archivos Java/config a revisar: $(echo "$JAVA_FILES" | wc -l)"
122
+
123
+ # Construir el prompt para análisis de código
124
+ PROMPT_FILE="$TEMP_DIR/code_review_prompt.txt"
125
+
126
+ cat > "$PROMPT_FILE" << 'EOF'
127
+ Eres un revisor de código senior con experiencia en proyectos Spring Boot empresariales.
128
+
129
+ Tu tarea es analizar los siguientes cambios de código y responder exclusivamente con un JSON válido, **sin ningún texto fuera del JSON**, sin introducciones ni explicaciones adicionales.
130
+
131
+ ⚠️ La respuesta debe ser un objeto JSON con esta estructura exacta:
132
+
133
+ {
134
+ "approved": true, // true si el commit puede aceptarse, false si debe bloquearse
135
+ "score": 1-10, // calificación de calidad (entero)
136
+ "blockingIssues": [ // lista de errores críticos (vacía si no hay)
137
+ "texto descriptivo 1",
138
+ "texto descriptivo 2"
139
+ ],
140
+ "recommendations": [ // sugerencias no bloqueantes
141
+ "mejora 1",
142
+ "mejora 2"
143
+ ],
144
+ "details": {
145
+ "security": [ // comentarios específicos por área
146
+ "observación sobre seguridad"
147
+ ],
148
+ "architecture": [ "..." ],
149
+ "performance": [ "..." ],
150
+ "maintainability": [ "..." ]
151
+ }
152
+ }
153
+
154
+ ✅ Importante:
155
+ - Usa listas aunque estén vacías (por ejemplo `"blockingIssues": []`)
156
+ - No uses texto introductorio o explicaciones fuera del JSON
157
+ - No omitas claves aunque estén vacías
158
+ - Sé directo, claro y profesional en tus observaciones
159
+
160
+ A continuación se detallan las pautas y los cambios:
161
+ EOF
162
+
163
+ # Agregar las pautas
164
+ echo "=== PAUTAS DE EVALUACIÓN ===" >> "$PROMPT_FILE"
165
+ cat "$GUIDELINES_FILE" >> "$PROMPT_FILE"
166
+ echo -e "\n\n=== CAMBIOS A REVISAR ===\n" >> "$PROMPT_FILE"
167
+
168
+ # Procesar cada archivo Java
169
+ FILE_COUNT=0
170
+ for FILE in $JAVA_FILES; do
171
+ if [ -f "$FILE" ]; then
172
+ FILE_SIZE=$(stat -c%s "$FILE" 2>/dev/null || stat -f%z "$FILE" 2>/dev/null || echo "0")
173
+
174
+ if [ "$FILE_SIZE" -gt "$MAX_FILE_SIZE" ]; then
175
+ warning "Archivo $FILE demasiado grande ($FILE_SIZE bytes), saltando..."
176
+ continue
177
+ fi
178
+
179
+ echo -e "\n--- Archivo: $FILE ---" >> "$PROMPT_FILE"
180
+
181
+ # Mostrar el diff del archivo
182
+ echo -e "\nDiff:" >> "$PROMPT_FILE"
183
+ git diff --cached "$FILE" >> "$PROMPT_FILE" 2>/dev/null || echo "No se pudo obtener diff"
184
+
185
+ # Si es un archivo nuevo, mostrar contenido completo
186
+ if git diff --cached --name-status | grep "^A.*$FILE" > /dev/null; then
187
+ echo -e "\nContenido completo (archivo nuevo):" >> "$PROMPT_FILE"
188
+ git show ":$FILE" >> "$PROMPT_FILE" 2>/dev/null || cat "$FILE" >> "$PROMPT_FILE"
189
+ fi
190
+
191
+ FILE_COUNT=$((FILE_COUNT + 1))
192
+ fi
193
+ done
194
+
195
+ if [ "$FILE_COUNT" -eq 0 ]; then
196
+ log "No se encontraron archivos válidos para revisar"
197
+ exit 0
198
+ fi
199
+
200
+ if [ "$FILE_COUNT" -gt 10 ]; then
201
+ warning "Demasiados archivos para revisar ($FILE_COUNT)"
202
+ warning "Considera dividir el commit en partes más pequeñas"
203
+ exit 0
204
+ fi
205
+
206
+ log "Enviando $FILE_COUNT archivos para revisión con Claude..."
207
+
208
+ # Enviar a Claude y capturar respuesta
209
+ RESPONSE_FILE="$TEMP_DIR/code_review_response.txt"
210
+
211
+ # Ejecutar Claude CLI y capturar la respuesta
212
+ if $CLAUDE_CLI < "$PROMPT_FILE" > "$RESPONSE_FILE" 2>&1; then
213
+ # Extraer el JSON de la respuesta
214
+ JSON_RESPONSE=$(sed -n '/^{/,/^}/p' "$RESPONSE_FILE" | head -n 1000)
215
+
216
+ if [ -z "$JSON_RESPONSE" ]; then
217
+ error "No se recibió una respuesta JSON válida de Claude"
218
+ error "Respuesta completa:"
219
+ cat "$RESPONSE_FILE"
220
+ exit 1
221
+ fi
222
+
223
+ # Guardar JSON para debug si está activado
224
+ if [ -n "$DEBUG" ]; then
225
+ echo "$JSON_RESPONSE" > ./debug-claude-response.json
226
+ log "Respuesta guardada en debug-claude-response.json"
227
+ fi
228
+
229
+ # Parsear la respuesta usando jq
230
+ APPROVED=$(echo "$JSON_RESPONSE" | jq -r '.approved // false')
231
+ SCORE=$(echo "$JSON_RESPONSE" | jq -r '.score // 0')
232
+ RECOMMENDATIONS=$(echo "$JSON_RESPONSE" | jq -r '.recommendations[]?' 2>/dev/null | sed '/^$/d')
233
+ BLOCKING_ISSUES=$(echo "$JSON_RESPONSE" | jq -r '.blockingIssues[]?' 2>/dev/null | sed '/^$/d')
234
+
235
+ # Verificar si estamos en modo SonarQube
236
+ QUALITY_GATE=$(echo "$JSON_RESPONSE" | jq -r '.qualityGate // ""' 2>/dev/null)
237
+
238
+ if [ "$ANALYSIS_MODE" = "sonar" ] && [ -n "$QUALITY_GATE" ] && [ "$QUALITY_GATE" != "null" ]; then
239
+ # Mostrar resultados estilo SonarQube
240
+ echo
241
+ echo "╔════════════════════════════════════════════════════════════════════╗"
242
+ echo "║ CODE QUALITY ANALYSIS ║"
243
+ echo "╚════════════════════════════════════════════════════════════════════╝"
244
+ echo
245
+
246
+ # Quality Gate Status
247
+ if [ "$QUALITY_GATE" = "PASSED" ]; then
248
+ echo -e "${GREEN}✓ Quality Gate: PASSED${NC}"
249
+ else
250
+ echo -e "${RED}✗ Quality Gate: FAILED${NC}"
251
+ fi
252
+ echo
253
+
254
+ # Metrics
255
+ METRICS=$(echo "$JSON_RESPONSE" | jq -r '.metrics // {}' 2>/dev/null)
256
+ if [ "$METRICS" != "{}" ] && [ "$METRICS" != "null" ]; then
257
+ echo "📊 METRICS"
258
+ echo "├─ Reliability: $(echo "$METRICS" | jq -r '.reliability // "?"' 2>/dev/null)"
259
+ echo "├─ Security: $(echo "$METRICS" | jq -r '.security // "?"' 2>/dev/null)"
260
+ echo "├─ Maintainability: $(echo "$METRICS" | jq -r '.maintainability // "?"' 2>/dev/null)"
261
+ echo "├─ Coverage: $(echo "$METRICS" | jq -r '.coverage // "?"' 2>/dev/null)%"
262
+ echo "├─ Duplications: $(echo "$METRICS" | jq -r '.duplications // "?"' 2>/dev/null)%"
263
+ echo "└─ Complexity: $(echo "$METRICS" | jq -r '.complexity // "?"' 2>/dev/null)"
264
+ echo
265
+ fi
266
+
267
+ # Issues Summary
268
+ SUMMARY=$(echo "$JSON_RESPONSE" | jq -r '.summary // {}' 2>/dev/null)
269
+ if [ "$SUMMARY" != "{}" ] && [ "$SUMMARY" != "null" ]; then
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)
277
+
278
+ echo "Total: $TOTAL issues found"
279
+ [ "$BLOCKER" -gt 0 ] && echo -e " ${RED}🔴 Blocker: $BLOCKER${NC}"
280
+ [ "$CRITICAL" -gt 0 ] && echo -e " ${RED}🟠 Critical: $CRITICAL${NC}"
281
+ [ "$MAJOR" -gt 0 ] && echo -e " ${YELLOW}🟡 Major: $MAJOR${NC}"
282
+ [ "$MINOR" -gt 0 ] && echo " 🔵 Minor: $MINOR"
283
+ [ "$INFO" -gt 0 ] && echo " ⚪ Info: $INFO"
284
+ echo
285
+ fi
286
+
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
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
293
+ fi
294
+
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
300
+ echo
301
+ fi
302
+
303
+ # Check if commit should be blocked
304
+ if [ "$QUALITY_GATE" = "FAILED" ] || [ "$APPROVED" = "false" ]; then
305
+ echo
306
+ error "❌ Commit blocked due to quality gate failure"
307
+ exit 1
308
+ fi
309
+
310
+ echo
311
+ log "✅ Code analysis completed. Quality gate passed."
312
+ else
313
+ # Mostrar resultados en formato clásico
314
+ echo
315
+ echo "=== RESULTADO DE LA REVISIÓN ==="
316
+ echo "Score: $SCORE/10"
317
+
318
+ if [ -n "$RECOMMENDATIONS" ] && [ "$RECOMMENDATIONS" != "null" ]; then
319
+ echo
320
+ echo "=== RECOMENDACIONES ==="
321
+ echo "$RECOMMENDATIONS" | sed 's/^/- /'
322
+ fi
323
+
324
+ # Verificar si el commit debe ser bloqueado
325
+ if [ "$APPROVED" = "false" ]; then
326
+ error "❌ Commit rechazado por problemas críticos"
327
+ if [ -n "$BLOCKING_ISSUES" ] && [ "$BLOCKING_ISSUES" != "null" ]; then
328
+ echo
329
+ echo "=== PROBLEMAS CRÍTICOS ==="
330
+ echo "$BLOCKING_ISSUES" | sed 's/^/- /'
331
+ fi
332
+ exit 1
333
+ fi
334
+
335
+ DETAILS=$(echo "$JSON_RESPONSE" | jq -r '.details // {}')
336
+ if [ -n "$DETAILS" ] && [ "$DETAILS" != "{}" ] && [ "$DETAILS" != "null" ]; then
337
+ echo
338
+ echo "=== DETALLES POR CATEGORÍA ==="
339
+ echo "$DETAILS" | jq
340
+ fi
341
+
342
+ log "✅ Revisión completada. Commit aprobado (Score: $SCORE/10)"
343
+ fi
344
+
345
+ else
346
+ error "Error al ejecutar Claude CLI"
347
+ error "Verifica que Claude CLI esté configurado correctamente"
348
+ cat "$RESPONSE_FILE"
349
+ exit 1
350
+ fi
351
+
352
+ exit 0
@@ -0,0 +1,138 @@
1
+ #!/bin/bash
2
+
3
+ # Git Prepare-commit-msg Hook para generar mensajes automáticos con Claude
4
+ # Archivo: .git/hooks/prepare-commit-msg
5
+
6
+ set -e
7
+
8
+ # Configuración
9
+ CLAUDE_CLI="claude"
10
+ TEMP_DIR="/tmp/commit-msg-$$"
11
+ MAX_FILE_SIZE=100000
12
+ AUTO_COMMIT_ENABLED=true
13
+
14
+ # Colores para output
15
+ RED='\033[0;31m'
16
+ GREEN='\033[0;32m'
17
+ YELLOW='\033[1;33m'
18
+ NC='\033[0m'
19
+
20
+ # Función para logging
21
+ log() {
22
+ printf "${GREEN}[PREPARE-MSG]${NC} %s\n" "$1" >&2
23
+ }
24
+
25
+ warning() {
26
+ printf "${YELLOW}[WARNING]${NC} %s\n" "$1" >&2
27
+ }
28
+
29
+ # Función para limpiar archivos temporales
30
+ cleanup() {
31
+ rm -rf "$TEMP_DIR"
32
+ }
33
+ trap cleanup EXIT
34
+
35
+ # Argumentos del hook
36
+ COMMIT_MSG_FILE="$1"
37
+ COMMIT_SOURCE="$2"
38
+
39
+ # Solo procesar si es un commit normal
40
+ if [ "$COMMIT_SOURCE" != "" ] && [ "$COMMIT_SOURCE" != "message" ]; then
41
+ exit 0
42
+ fi
43
+
44
+ # Leer el mensaje actual
45
+ CURRENT_MSG=$(head -n 1 "$COMMIT_MSG_FILE" 2>/dev/null || echo "")
46
+
47
+ # Verificar si necesitamos generar mensaje
48
+ if [ "$CURRENT_MSG" = "auto" ]; then
49
+ log "Intentando generar mensaje..."
50
+ else
51
+ exit 0
52
+ fi
53
+
54
+ # Verificar si Claude CLI está instalado
55
+ if ! command -v "$CLAUDE_CLI" &> /dev/null; then
56
+ warning "Claude CLI no está instalado"
57
+ warning "Commit cancelado. Ejecuta nuevamente sin 'auto' para escribir mensaje manual"
58
+ exit 1
59
+ fi
60
+
61
+ mkdir -p "$TEMP_DIR"
62
+ PROMPT_FILE="$TEMP_DIR/commit_msg_prompt.txt"
63
+
64
+ cat > "$PROMPT_FILE" << 'EOF'
65
+ Analiza los siguientes cambios y genera un mensaje de commit siguiendo el formato Conventional Commits.
66
+
67
+ Responde SOLO con un JSON válido:
68
+
69
+ {
70
+ "type": "feat|fix|docs|style|refactor|test|chore|ci|perf",
71
+ "scope": "alcance opcional (ej: api, frontend, db)",
72
+ "title": "descripción corta en presente (max 50 chars)",
73
+ "body": "descripción detallada opcional"
74
+ }
75
+
76
+ CAMBIOS A ANALIZAR:
77
+ EOF
78
+
79
+ # Obtener archivos staged
80
+ ALL_STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null || echo "")
81
+
82
+ if [ -z "$ALL_STAGED_FILES" ]; then
83
+ warning "No hay archivos staged"
84
+ warning "Commit cancelado. Ejecuta nuevamente sin 'auto' para escribir mensaje manual"
85
+ exit 1
86
+ fi
87
+
88
+ printf "\nArchivos modificados:\n" >> "$PROMPT_FILE"
89
+ echo "$ALL_STAGED_FILES" >> "$PROMPT_FILE"
90
+
91
+ printf "\nResumen de cambios:\n" >> "$PROMPT_FILE"
92
+ git diff --cached --stat >> "$PROMPT_FILE" 2>/dev/null || echo "No se pudo obtener estadísticas"
93
+
94
+ # Mostrar diffs de archivos pequeños
95
+ for FILE in $ALL_STAGED_FILES; do
96
+ if [ -f "$FILE" ]; then
97
+ FILE_SIZE=$(stat -c%s "$FILE" 2>/dev/null || stat -f%z "$FILE" 2>/dev/null || echo "0")
98
+
99
+ if [ "$FILE_SIZE" -lt "$MAX_FILE_SIZE" ]; then
100
+ printf "\n--- Diff de %s ---\n" "$FILE" >> "$PROMPT_FILE"
101
+ git diff --cached "$FILE" >> "$PROMPT_FILE" 2>/dev/null || echo "No se pudo obtener diff"
102
+ fi
103
+ fi
104
+ done
105
+
106
+ RESPONSE_FILE="$TEMP_DIR/commit_msg_response.txt"
107
+ log "Generando mensaje de commit con Claude..."
108
+
109
+ if $CLAUDE_CLI < "$PROMPT_FILE" > "$RESPONSE_FILE" 2>&1; then
110
+ JSON_MSG=$(sed -n '/^{/,/^}/p' "$RESPONSE_FILE" | head -n 1000)
111
+
112
+ if [ -n "$JSON_MSG" ]; then
113
+ MSG_TYPE=$(echo "$JSON_MSG" | jq -r '.type // "feat"' 2>/dev/null || echo "feat")
114
+ MSG_SCOPE=$(echo "$JSON_MSG" | jq -r '.scope // ""' 2>/dev/null || echo "")
115
+ MSG_TITLE=$(echo "$JSON_MSG" | jq -r '.title // ""' 2>/dev/null || echo "")
116
+ MSG_BODY=$(echo "$JSON_MSG" | jq -r '.body // ""' 2>/dev/null || echo "")
117
+
118
+ if [ -n "$MSG_TITLE" ]; then
119
+ FULL_MESSAGE="$MSG_TYPE"
120
+ if [ -n "$MSG_SCOPE" ] && [ "$MSG_SCOPE" != "null" ]; then
121
+ FULL_MESSAGE="${FULL_MESSAGE}(${MSG_SCOPE})"
122
+ fi
123
+ FULL_MESSAGE="${FULL_MESSAGE}: ${MSG_TITLE}"
124
+
125
+ if [ -n "$MSG_BODY" ] && [ "$MSG_BODY" != "null" ]; then
126
+ FULL_MESSAGE="${FULL_MESSAGE}\n\n${MSG_BODY}"
127
+ fi
128
+
129
+ printf "%s\n" "$FULL_MESSAGE" > "$COMMIT_MSG_FILE"
130
+ log "📝 Mensaje generado: $(echo "$FULL_MESSAGE" | head -n 1)"
131
+ exit 0
132
+ fi
133
+ fi
134
+ fi
135
+
136
+ warning "No se pudo generar el mensaje automáticamente con Claude"
137
+ warning "Commit cancelado. Ejecuta nuevamente sin 'auto' para escribir mensaje manual"
138
+ exit 1