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 +21 -0
- package/README.md +355 -0
- package/bin/claude-hooks +297 -0
- package/package.json +40 -0
- package/templates/CLAUDE_PRE_COMMIT.md +62 -0
- package/templates/CLAUDE_PRE_COMMIT_SONAR.md +86 -0
- package/templates/pre-commit +352 -0
- package/templates/prepare-commit-msg +138 -0
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
|
package/bin/claude-hooks
ADDED
|
@@ -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
|