claude-git-hooks 1.4.2 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -1
- package/LICENSE +0 -0
- package/README.md +164 -449
- package/bin/claude-hooks +271 -48
- package/package.json +1 -1
- package/templates/CLAUDE_ANALYSIS_PROMPT_SONAR.md +0 -0
- package/templates/CLAUDE_PRE_COMMIT_SONAR.md +0 -0
- package/templates/CLAUDE_RESOLUTION_PROMPT.md +0 -0
- package/templates/check-version.sh +0 -0
- package/templates/pre-commit +9 -15
- package/templates/prepare-commit-msg +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,12 +5,47 @@ Todos los cambios notables en este proyecto se documentarán en este archivo.
|
|
|
5
5
|
El formato está basado en [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
y este proyecto adhiere a [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.5.1] - 2025-09-11
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- 📋 Sección CHEATSHEET en README
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- 🔧 SKIP_ANALYSIS_BLOCK funciona correctamente
|
|
15
|
+
- 📝 Estructura del proyecto en documentación
|
|
16
|
+
|
|
17
|
+
## [1.5.0] - 2025-09-10
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- 🔍 Nuevo comando `claude-hooks analyze-diff [base]` para análisis de diferencias entre ramas
|
|
21
|
+
- Genera título de PR conciso (máx. 72 caracteres)
|
|
22
|
+
- Crea descripción detallada del PR con markdown
|
|
23
|
+
- Sugiere nombre de rama siguiendo formato tipo/descripcion
|
|
24
|
+
- Identifica tipo de cambio (feature/fix/refactor/docs/test/chore)
|
|
25
|
+
- Detecta breaking changes
|
|
26
|
+
- Proporciona notas de testing recomendado
|
|
27
|
+
- Guarda resultados en `.claude-pr-analysis.json`
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
- 🎯 Modo SonarQube ahora es el predeterminado (no hay selección interactiva)
|
|
31
|
+
- ⚡ Eliminada lógica de selección de modo del pre-commit hook
|
|
32
|
+
- 🗑️ Removidas referencias a guardado de preferencias `.claude-analysis-mode`
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
- 🐛 Eliminado prompt interactivo incompatible con algunas consolas
|
|
36
|
+
- 🔧 Pre-commit ahora va directo al análisis sin solicitar input del usuario
|
|
37
|
+
|
|
38
|
+
### Removed
|
|
39
|
+
- 🗑️ Eliminada selección interactiva de modo de análisis
|
|
40
|
+
- 🗑️ Removido comando `set-mode` (ya no es necesario)
|
|
41
|
+
- 🗑️ Eliminada variable de entorno `CLAUDE_ANALYSIS_MODE`
|
|
42
|
+
|
|
8
43
|
## [1.4.2] - 2025-09-08
|
|
9
44
|
|
|
10
45
|
### Added
|
|
11
46
|
- 🚫 Comentario `// SKIP-ANALYSIS` para excluir código del análisis
|
|
12
47
|
- Una línea: excluye la siguiente línea
|
|
13
|
-
- Bloque: código entre dos comentarios `//
|
|
48
|
+
- Bloque: código entre dos comentarios `// SKIP_ANALYSIS_BLOCK` es excluido
|
|
14
49
|
- 📝 Excepciones para Spring Framework en criterios de evaluación
|
|
15
50
|
- `@Autowired` no es considerado issue bloqueante (máximo severidad MAJOR)
|
|
16
51
|
- Inyección de dependencias con field injection es aceptable
|
package/LICENSE
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
@@ -1,22 +1,129 @@
|
|
|
1
1
|
# Pre-commit Hook con Claude CLI
|
|
2
2
|
|
|
3
3
|
Este directorio contiene un pre-commit hook que utiliza Claude CLI para revisar automáticamente el código antes de cada commit.
|
|
4
|
+
Otras funciones de interés:
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
- Generación de mensaje de commit.
|
|
7
|
+
- Generación de información de PR y tests de validación de PR - Utiliza análisis de diferencias entre contenido local y origin.
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
## 📋 CHEATSHEET
|
|
10
|
+
|
|
11
|
+
### 🚀 Comandos Frecuentes
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Commit rápido sin análisis + mensaje automático
|
|
15
|
+
git commit --no-verify -m "auto"
|
|
16
|
+
|
|
17
|
+
# Analizar diferencias con rama origin actual
|
|
18
|
+
claude-hooks analyze-diff
|
|
19
|
+
|
|
20
|
+
# Analizar diferencias para PR (comparar con develop)
|
|
21
|
+
claude-hooks analyze-diff develop
|
|
22
|
+
|
|
23
|
+
# Reinstalar durante desarrollo (después de npm link)
|
|
24
|
+
claude-hooks install --force --skip-auth
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 📦 Instalación y Gestión
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Instalación inicial
|
|
31
|
+
claude-hooks install
|
|
32
|
+
|
|
33
|
+
# Actualizar a última versión
|
|
34
|
+
claude-hooks update
|
|
35
|
+
|
|
36
|
+
# Desinstalar hooks
|
|
37
|
+
claude-hooks uninstall
|
|
38
|
+
|
|
39
|
+
# Ver estado actual
|
|
40
|
+
claude-hooks status
|
|
41
|
+
|
|
42
|
+
# Desactivar temporalmente
|
|
43
|
+
claude-hooks disable
|
|
44
|
+
|
|
45
|
+
# Reactivar hooks
|
|
46
|
+
claude-hooks enable
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 💻 Flujos de Commit
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Commit con análisis de código
|
|
53
|
+
git commit -m "feat: nueva funcionalidad"
|
|
54
|
+
|
|
55
|
+
# Generar mensaje de commit automático
|
|
56
|
+
git commit -m "auto"
|
|
57
|
+
|
|
58
|
+
# Saltar análisis (emergencias)
|
|
59
|
+
git commit --no-verify -m "hotfix: corrección urgente"
|
|
60
|
+
|
|
61
|
+
# Modificar último commit
|
|
62
|
+
git commit --amend
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 🚫 Exclusión de Código del Análisis
|
|
66
|
+
|
|
67
|
+
```java
|
|
68
|
+
// SKIP-ANALYSIS
|
|
69
|
+
private String legacyCode = "no analizar siguiente línea";
|
|
70
|
+
|
|
71
|
+
// SKIP_ANALYSIS_BLOCK
|
|
72
|
+
public void methodToIgnore() {
|
|
73
|
+
// Todo este bloque será ignorado
|
|
74
|
+
String oldCode = "legacy";
|
|
75
|
+
}
|
|
76
|
+
// SKIP_ANALYSIS_BLOCK
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 🔧 Configuración Avanzada
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# Archivos de configuración
|
|
83
|
+
.claude/
|
|
84
|
+
├── CLAUDE_PRE_COMMIT_SONAR.md # Personalizar criterios
|
|
85
|
+
├── CLAUDE_ANALYSIS_PROMPT_SONAR.md # Modificar prompt
|
|
86
|
+
└── settings.local.json # Configuración local
|
|
87
|
+
|
|
88
|
+
# Variables de entorno
|
|
89
|
+
export CLAUDE_ANALYSIS_MODE=sonarqube # Modo de análisis
|
|
90
|
+
export CLAUDE_DEBUG=true # Debug detallado
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 🎯 Casos de Uso Específicos
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Desarrollo local de claude-hooks
|
|
97
|
+
cd /path/to/claude-git-hooks
|
|
98
|
+
npm link
|
|
99
|
+
cd /path/to/your-project
|
|
100
|
+
claude-hooks install --force --skip-auth
|
|
101
|
+
|
|
102
|
+
# Preparar PR hacia develop
|
|
103
|
+
git checkout -b feature/nueva-funcionalidad
|
|
104
|
+
# ... hacer cambios ...
|
|
105
|
+
git add .
|
|
106
|
+
git commit -m "feat: implementar nueva funcionalidad"
|
|
107
|
+
claude-hooks analyze-diff develop # Genera título, descripción y tests
|
|
108
|
+
|
|
109
|
+
# Resolver issues bloqueantes
|
|
110
|
+
git commit -m "fix: resolver issues"
|
|
111
|
+
# Si falla, ver claude_resolution_prompt.md generado
|
|
112
|
+
# Copiar contenido y pegar en Claude para obtener solución
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### ⚡ Tips y Trucos
|
|
116
|
+
|
|
117
|
+
1. **Mensaje automático**: Usa `"auto"` como mensaje para que Claude lo genere
|
|
118
|
+
2. **Skip auth**: Usa `--skip-auth` en CI/CD o desarrollo local
|
|
119
|
+
3. **Force install**: Usa `--force` para reinstalar sin confirmación
|
|
120
|
+
4. **Debug**: Activa `CLAUDE_DEBUG=true` para ver detalles completos
|
|
121
|
+
5. **Archivos grandes**: Se omiten automáticamente archivos > 100KB
|
|
122
|
+
6. **Límite de archivos**: Máximo 10 archivos por commit
|
|
16
123
|
|
|
17
124
|
## 🔧 Configuración Previa Importante
|
|
18
125
|
|
|
19
|
-
### 1. Armonización de Line Endings (EOL)
|
|
126
|
+
### 1. Armonización de Line Endings (EOL) -- Automatizada
|
|
20
127
|
|
|
21
128
|
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:
|
|
22
129
|
|
|
@@ -53,8 +160,6 @@ git config --global credential.helper store
|
|
|
53
160
|
|
|
54
161
|
## 🚀 Instalación
|
|
55
162
|
|
|
56
|
-
### Paquete NPM Global (Recomendado)
|
|
57
|
-
|
|
58
163
|
**IMPORTANTE**: Claude CLI corre en WSL, por lo que toda la instalación y uso de git debe hacerse desde la terminal WSL.
|
|
59
164
|
|
|
60
165
|
```bash
|
|
@@ -80,35 +185,6 @@ El comando `claude-hooks install` ahora incluye:
|
|
|
80
185
|
- `--force`: Reinstala aunque los hooks ya existan
|
|
81
186
|
- `--skip-auth`: Omite la verificación de autenticación de Claude (útil para CI/CD)
|
|
82
187
|
|
|
83
|
-
#### Añadir como Dependencia de Desarrollo
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
# Instalar como devDependency
|
|
87
|
-
npm install --save-dev claude-git-hooks
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
Luego añade esto a tu `package.json`:
|
|
91
|
-
|
|
92
|
-
```json
|
|
93
|
-
{
|
|
94
|
-
"scripts": {
|
|
95
|
-
"postinstall": "claude-hooks install"
|
|
96
|
-
},
|
|
97
|
-
"devDependencies": {
|
|
98
|
-
"claude-git-hooks": "^1.2.0"
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
## 🤖 Características
|
|
104
|
-
|
|
105
|
-
**✨ Auto-actualización**: Verifica automáticamente si hay nuevas versiones disponibles antes de cada commit y ofrece actualizar con un simple prompt interactivo.
|
|
106
|
-
|
|
107
|
-
**Hooks disponibles**:
|
|
108
|
-
|
|
109
|
-
- `pre-commit`: Análisis de código con Claude (solo archivos Java/config) con verificación automática de versión
|
|
110
|
-
- `prepare-commit-msg`: Generación automática de mensajes de commit
|
|
111
|
-
|
|
112
188
|
## 📁 Gestión de Archivos
|
|
113
189
|
|
|
114
190
|
### Archivos creados durante la instalación
|
|
@@ -119,12 +195,9 @@ El comando `claude-hooks install` crea los siguientes archivos y directorios:
|
|
|
119
195
|
2. **`.git/hooks/prepare-commit-msg`** - Hook de generación de mensajes
|
|
120
196
|
3. **`.git/hooks/check-version.sh`** - Script de verificación de versión
|
|
121
197
|
4. **`.claude/`** - Directorio para archivos de configuración
|
|
122
|
-
- `CLAUDE_PRE_COMMIT.md` - Pautas de evaluación estándar
|
|
123
198
|
- `CLAUDE_PRE_COMMIT_SONAR.md` - Pautas de evaluación SonarQube
|
|
124
|
-
- `CLAUDE_ANALYSIS_PROMPT.md` - Template de prompt para análisis estándar
|
|
125
199
|
- `CLAUDE_ANALYSIS_PROMPT_SONAR.md` - Template de prompt para análisis SonarQube
|
|
126
200
|
- `CLAUDE_RESOLUTION_PROMPT.md` - Template para prompt de resolución AI
|
|
127
|
-
5. **`.claude-analysis-mode`** - Archivo de preferencia de modo (creado al primer uso)
|
|
128
201
|
|
|
129
202
|
### Actualización automática de .gitignore
|
|
130
203
|
|
|
@@ -133,9 +206,9 @@ Durante la instalación, Claude Hooks actualiza automáticamente tu `.gitignore`
|
|
|
133
206
|
```gitignore
|
|
134
207
|
# Claude Git Hooks
|
|
135
208
|
.claude/
|
|
136
|
-
.claude-analysis-mode
|
|
137
209
|
debug-claude-response.json
|
|
138
210
|
claude_resolution_prompt.md
|
|
211
|
+
.claude-pr-analysis.json
|
|
139
212
|
```
|
|
140
213
|
|
|
141
214
|
Esto asegura que:
|
|
@@ -155,26 +228,19 @@ Si no existe un `.gitignore`, se creará uno nuevo. Si ya existe, las entradas s
|
|
|
155
228
|
- Solo analiza: Java, XML, properties, yml, yaml
|
|
156
229
|
- Omite archivos mayores a 100KB
|
|
157
230
|
- Límite de 10 archivos por commit
|
|
158
|
-
3. **
|
|
159
|
-
- **Estándar**: Formato clásico con score y recomendaciones detalladas
|
|
160
|
-
- **SonarQube**: Simula salida de SonarQube con Quality Gate, métricas y clasificación de issues
|
|
161
|
-
4. **Construye prompt inteligente**:
|
|
231
|
+
3. **Construye prompt inteligente**:
|
|
162
232
|
- Usa template de prompt desde `.claude/CLAUDE_ANALYSIS_PROMPT*.md`
|
|
163
233
|
- Lee las pautas desde `.claude/CLAUDE_PRE_COMMIT*.md`
|
|
164
234
|
- Incluye el diff completo para archivos nuevos
|
|
165
235
|
- Muestra solo cambios para archivos existentes
|
|
166
|
-
|
|
167
|
-
|
|
236
|
+
4. **Envía a Claude CLI para revisión**
|
|
237
|
+
5. **Procesa respuesta JSON estructurada**:
|
|
168
238
|
- blockingIssues siempre como objetos con localización precisa
|
|
169
|
-
-
|
|
170
|
-
|
|
171
|
-
7. **Decisión final**:
|
|
239
|
+
- verifica `QUALITY_GATE`, muestra métricas y issues por severidad
|
|
240
|
+
6. **Decisión final**:
|
|
172
241
|
- Si hay problemas críticos → genera prompt AI de resolución y bloquea commit
|
|
173
242
|
- Si todo está bien → commit procede
|
|
174
|
-
|
|
175
|
-
#### 🤖 Generación de Prompt de Resolución AI
|
|
176
|
-
|
|
177
|
-
Cuando se detectan problemas críticos:
|
|
243
|
+
7. **Generación de Prompt de Resolución AI** (opcional): Cuando se detectan problemas críticos:
|
|
178
244
|
|
|
179
245
|
- Se genera automáticamente un archivo `claude_resolution_prompt.md`
|
|
180
246
|
- Contiene información estructurada y AI-friendly de todos los issues
|
|
@@ -198,42 +264,20 @@ Cuando se detectan problemas críticos:
|
|
|
198
264
|
|
|
199
265
|
### Características adicionales
|
|
200
266
|
|
|
267
|
+
- **Generación de información para Pull Requests**: `claude-hooks analyze-diffs {branch}` para comparar rama local con otra rama, propone nombre para rama actual, título y detalles para pull request, da tips para verificar trabajo. Este comando genera automáticamente:
|
|
268
|
+
- 📝 Título de PR conciso (máx. 72 caracteres)
|
|
269
|
+
- 📄 Descripción detallada con markdown
|
|
270
|
+
- 🌿 Nombre de rama sugerido (formato: tipo/descripcion)
|
|
271
|
+
- 📋 Tipo de cambio (feature/fix/refactor/docs/test/chore)
|
|
272
|
+
- ⚠️ Indicador de breaking changes
|
|
273
|
+
- 🧪 Notas de testing recomendado
|
|
274
|
+
- 📝 Archivo `.claude-pr-analysis.json`
|
|
201
275
|
- **Auto-actualización**: Verificación automática de versiones antes de cada commit con prompt interactivo para actualizar
|
|
202
276
|
- **Comando update**: `claude-hooks update` para actualizar manualmente a la última versión
|
|
203
277
|
- **Modo debug**: `DEBUG=1 git commit` guarda respuestas en `debug-claude-response.json`
|
|
204
|
-
- **
|
|
278
|
+
- **Análisis de PR**: Nuevo comando `analyze-diff` para generar información de Pull Requests
|
|
205
279
|
- **Validación de dependencias**: Verifica que Claude CLI esté autenticado antes de ejecutar
|
|
206
|
-
|
|
207
|
-
## 🛠️ Uso
|
|
208
|
-
|
|
209
|
-
**IMPORTANTE**: Todos los comandos git deben ejecutarse desde WSL.
|
|
210
|
-
|
|
211
|
-
### Casos de uso de commits
|
|
212
|
-
|
|
213
|
-
```bash
|
|
214
|
-
# Desde WSL
|
|
215
|
-
git add .
|
|
216
|
-
|
|
217
|
-
# 1. Mensaje manual + análisis bloqueante
|
|
218
|
-
git commit -m "feat: nueva funcionalidad"
|
|
219
|
-
|
|
220
|
-
# 2. Mensaje automático + análisis bloqueante
|
|
221
|
-
git commit -m "auto" # Claude generará el mensaje automáticamente
|
|
222
|
-
|
|
223
|
-
# 3. Mensaje manual sin análisis
|
|
224
|
-
git commit --no-verify -m "fix: corrección urgente"
|
|
225
|
-
|
|
226
|
-
# 4. Mensaje automático sin análisis
|
|
227
|
-
git commit --no-verify -m "auto"
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
Claude analizará los cambios y generará un mensaje de commit siguiendo las convenciones (feat, fix, docs, etc.).
|
|
231
|
-
|
|
232
|
-
**⚠️ Si la generación automática falla**: El commit se cancelará completamente. Simplemente ejecuta `git commit -m "tu mensaje"` manualmente.
|
|
233
|
-
|
|
234
|
-
### Excluir código del análisis con SKIP-ANALYSIS
|
|
235
|
-
|
|
236
|
-
Puedes excluir código específico del análisis usando comentarios `// SKIP-ANALYSIS`:
|
|
280
|
+
- **Exclusión de código del análisis con SKIP-ANALYSIS**: Puedes excluir código específico del análisis usando comentarios `// SKIP-ANALYSIS`:
|
|
237
281
|
|
|
238
282
|
```java
|
|
239
283
|
// Excluir una sola línea
|
|
@@ -241,180 +285,15 @@ Puedes excluir código específico del análisis usando comentarios `// SKIP-ANA
|
|
|
241
285
|
@Autowired private LegacyService legacyService; // Esta línea no será analizada
|
|
242
286
|
|
|
243
287
|
// Excluir un bloque de código
|
|
244
|
-
// SKIP-
|
|
288
|
+
// SKIP-ANALYSIS_BLOCK
|
|
245
289
|
@Deprecated
|
|
246
290
|
public void methodWithKnownIssues() {
|
|
247
291
|
// Código legacy que no queremos que sea analizado
|
|
248
292
|
System.out.println("Debug temporal");
|
|
249
293
|
}
|
|
250
|
-
// SKIP-
|
|
294
|
+
// SKIP-ANALYSIS_BLOCK
|
|
251
295
|
```
|
|
252
296
|
|
|
253
|
-
**Nota**: El código entre comentarios `SKIP-ANALYSIS` no será enviado a Claude para su análisis.
|
|
254
|
-
|
|
255
|
-
### Modos de análisis 📊
|
|
256
|
-
|
|
257
|
-
Puedes elegir entre dos formatos de análisis:
|
|
258
|
-
|
|
259
|
-
#### 🎯 Configurar Modo (Recomendado)
|
|
260
|
-
|
|
261
|
-
```bash
|
|
262
|
-
# Cambiar a modo SonarQube (métricas y quality gate)
|
|
263
|
-
claude-hooks set-mode sonar
|
|
264
|
-
|
|
265
|
-
# Cambiar a modo estándar (score y recomendaciones)
|
|
266
|
-
claude-hooks set-mode standard
|
|
267
|
-
|
|
268
|
-
# Ver modo actual
|
|
269
|
-
claude-hooks status
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
#### 🔧 Variable de Entorno (Temporal)
|
|
273
|
-
|
|
274
|
-
```bash
|
|
275
|
-
# Sobrescribir temporalmente el modo configurado
|
|
276
|
-
export CLAUDE_ANALYSIS_MODE=sonar
|
|
277
|
-
git commit -m "mensaje"
|
|
278
|
-
|
|
279
|
-
# Volver al modo configurado
|
|
280
|
-
unset CLAUDE_ANALYSIS_MODE
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
#### 📋 Diferencias entre Modos
|
|
284
|
-
|
|
285
|
-
**Modo Estándar**:
|
|
286
|
-
|
|
287
|
-
- Análisis con puntuación del 1-10
|
|
288
|
-
- Recomendaciones detalladas por categoría
|
|
289
|
-
- Formato tradicional fácil de leer
|
|
290
|
-
|
|
291
|
-
**Modo SonarQube**:
|
|
292
|
-
|
|
293
|
-
- Quality Gate (PASSED/FAILED)
|
|
294
|
-
- Métricas: Reliability, Security, Maintainability
|
|
295
|
-
- Issues clasificados por severidad (Blocker, Critical, Major, Minor, Info)
|
|
296
|
-
- Security hotspots
|
|
297
|
-
|
|
298
|
-
#### 🔄 Cambio de Modo Interactivo
|
|
299
|
-
|
|
300
|
-
```bash
|
|
301
|
-
# Sin parámetros muestra ayuda interactiva
|
|
302
|
-
claude-hooks set-mode
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
### Modo debug
|
|
306
|
-
|
|
307
|
-
```bash
|
|
308
|
-
# Desde WSL
|
|
309
|
-
DEBUG=1 git commit -m "mensaje"
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
## 📊 Formato de Respuesta
|
|
313
|
-
|
|
314
|
-
Claude responde con un JSON estructurado que incluye:
|
|
315
|
-
|
|
316
|
-
- `approved`: Si el commit es aprobado (boolean)
|
|
317
|
-
- `score`: Puntuación del 1-10 (integer)
|
|
318
|
-
- `blockingIssues`: Array de objetos con problemas críticos:
|
|
319
|
-
- `description`: Descripción del problema
|
|
320
|
-
- `file`: Archivo afectado
|
|
321
|
-
- `line`: Línea del problema
|
|
322
|
-
- `method`: Método o clase afectada
|
|
323
|
-
- `severity`: Severidad (critical, high, etc.)
|
|
324
|
-
- `recommendations`: Array de strings con sugerencias no bloqueantes
|
|
325
|
-
- `details`: Objeto con comentarios por categoría (security, performance, etc.)
|
|
326
|
-
|
|
327
|
-
### Ejemplos de Respuestas con Problemas Críticos
|
|
328
|
-
|
|
329
|
-
#### Modo Estándar - Commit Bloqueado
|
|
330
|
-
|
|
331
|
-
```json
|
|
332
|
-
{
|
|
333
|
-
"approved": false,
|
|
334
|
-
"score": 3,
|
|
335
|
-
"blockingIssues": [
|
|
336
|
-
{
|
|
337
|
-
"description": "SQL Injection vulnerability: concatenación directa de strings en query",
|
|
338
|
-
"file": "src/main/java/UserRepository.java",
|
|
339
|
-
"line": 45,
|
|
340
|
-
"method": "findUserByName",
|
|
341
|
-
"severity": "critical"
|
|
342
|
-
},
|
|
343
|
-
{
|
|
344
|
-
"description": "Posible NullPointerException: variable 'user' no validada",
|
|
345
|
-
"file": "src/main/java/UserService.java",
|
|
346
|
-
"line": 78,
|
|
347
|
-
"method": "processUser",
|
|
348
|
-
"severity": "high"
|
|
349
|
-
}
|
|
350
|
-
],
|
|
351
|
-
"recommendations": [
|
|
352
|
-
"Usar PreparedStatement en lugar de concatenación de strings",
|
|
353
|
-
"Agregar validación de null antes de acceder a propiedades"
|
|
354
|
-
]
|
|
355
|
-
}
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
#### Modo SonarQube - Quality Gate Failed
|
|
359
|
-
|
|
360
|
-
```json
|
|
361
|
-
{
|
|
362
|
-
"QUALITY_GATE": "FAILED",
|
|
363
|
-
"approved": false,
|
|
364
|
-
"score": 4,
|
|
365
|
-
"metrics": {
|
|
366
|
-
"reliability": "D",
|
|
367
|
-
"security": "E",
|
|
368
|
-
"maintainability": "C"
|
|
369
|
-
},
|
|
370
|
-
"issues": {
|
|
371
|
-
"blocker": 1,
|
|
372
|
-
"critical": 2,
|
|
373
|
-
"major": 3,
|
|
374
|
-
"minor": 1
|
|
375
|
-
},
|
|
376
|
-
"blockingIssues": [
|
|
377
|
-
{
|
|
378
|
-
"description": "Security Hotspot: Hardcoded credentials detected",
|
|
379
|
-
"file": "src/main/resources/application.yml",
|
|
380
|
-
"line": 23,
|
|
381
|
-
"method": "datasource.password",
|
|
382
|
-
"severity": "blocker"
|
|
383
|
-
},
|
|
384
|
-
{
|
|
385
|
-
"description": "Resource leak: Connection not closed in finally block",
|
|
386
|
-
"file": "src/main/java/DatabaseUtil.java",
|
|
387
|
-
"line": 112,
|
|
388
|
-
"method": "executeQuery",
|
|
389
|
-
"severity": "critical"
|
|
390
|
-
}
|
|
391
|
-
],
|
|
392
|
-
"details": [
|
|
393
|
-
{
|
|
394
|
-
"severity": "BLOCKER",
|
|
395
|
-
"type": "VULNERABILITY",
|
|
396
|
-
"file": "src/main/resources/application.yml",
|
|
397
|
-
"line": 23,
|
|
398
|
-
"message": "Never store passwords in plain text",
|
|
399
|
-
"rule": "java:S2068"
|
|
400
|
-
}
|
|
401
|
-
]
|
|
402
|
-
}
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
Cuando se detectan estos problemas críticos, se genera automáticamente un archivo `claude_resolution_prompt.md` con toda la información necesaria para que otra instancia de Claude pueda resolver los issues de forma eficiente.
|
|
406
|
-
|
|
407
|
-
## 🔄 Actualización y Gestión
|
|
408
|
-
|
|
409
|
-
### Actualizar a la última versión
|
|
410
|
-
|
|
411
|
-
```bash
|
|
412
|
-
# Actualizar manualmente a la última versión
|
|
413
|
-
claude-hooks update
|
|
414
|
-
```
|
|
415
|
-
|
|
416
|
-
El comando `update` descarga e instala automáticamente la última versión disponible desde NPM, y luego reinstala los hooks con las nuevas características.
|
|
417
|
-
|
|
418
297
|
### Desactivar/Activar hooks
|
|
419
298
|
|
|
420
299
|
```bash
|
|
@@ -438,103 +317,26 @@ claude-hooks status
|
|
|
438
317
|
|
|
439
318
|
## ⚙️ Configuración
|
|
440
319
|
|
|
441
|
-
###
|
|
442
|
-
|
|
443
|
-
- **`CLAUDE_ANALYSIS_MODE`**: Selecciona el modo de análisis
|
|
444
|
-
|
|
445
|
-
- Valores: `"standard"` o `"sonar"`
|
|
446
|
-
- Ejemplo: `CLAUDE_ANALYSIS_MODE=sonar git commit -m "mensaje"`
|
|
447
|
-
- Sobrescribe la preferencia guardada en `.claude-analysis-mode`
|
|
448
|
-
|
|
449
|
-
- **`DEBUG`**: Activa el modo debug
|
|
450
|
-
- Valores: `1` para activar
|
|
451
|
-
- Ejemplo: `DEBUG=1 git commit -m "mensaje"`
|
|
452
|
-
- Guarda las respuestas de Claude en `debug-claude-response.json`
|
|
453
|
-
|
|
454
|
-
### Variables modificables en los archivos de hooks
|
|
320
|
+
### Scripts
|
|
455
321
|
|
|
456
322
|
En el archivo `pre-commit`:
|
|
457
323
|
|
|
458
324
|
- **`MAX_FILE_SIZE`**: Tamaño máximo de archivo a analizar (default: 100KB)
|
|
459
325
|
- **`MAX_FILES`**: Número máximo de archivos por commit (default: 10)
|
|
460
326
|
- **`CLAUDE_CLI`**: Comando de Claude CLI (default: "claude")
|
|
461
|
-
- **`GUIDELINES_FILE`**: Archivo de pautas
|
|
462
|
-
- **`GUIDELINES_FILE_SONAR`**: Archivo de pautas para modo SonarQube (default: ".claude/CLAUDE_PRE_COMMIT_SONAR.md")
|
|
327
|
+
- **`GUIDELINES_FILE`**: Archivo de pautas (default: ".claude/CLAUDE_PRE_COMMIT_SONAR.md")
|
|
463
328
|
|
|
464
329
|
En el archivo `prepare-commit-msg`:
|
|
465
330
|
|
|
466
331
|
- **`MAX_FILE_SIZE`**: Tamaño máximo para incluir diff completo (default: 100KB)
|
|
467
332
|
- **`CLAUDE_CLI`**: Comando de Claude CLI (default: "claude")
|
|
468
333
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
### "Claude CLI no está instalado"
|
|
472
|
-
|
|
473
|
-
```bash
|
|
474
|
-
# Desde WSL
|
|
475
|
-
npm install -g @anthropic-ai/claude-cli
|
|
476
|
-
claude auth login
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
### "No se recibió respuesta JSON válida"
|
|
480
|
-
|
|
481
|
-
- Verifica que Claude CLI esté autenticado en WSL
|
|
482
|
-
- Ejecuta en modo DEBUG para ver la respuesta completa
|
|
483
|
-
- Revisa que el archivo de pautas esté bien formateado
|
|
484
|
-
|
|
485
|
-
### El hook no se ejecuta
|
|
486
|
-
|
|
487
|
-
- Verifica que estés ejecutando git desde WSL
|
|
488
|
-
- Verifica permisos: `ls -la .git/hooks/pre-commit`
|
|
489
|
-
- Debe mostrar permisos de ejecución (-rwxr-xr-x)
|
|
334
|
+
### Pautas de Evaluación
|
|
490
335
|
|
|
491
|
-
### Problemas con line endings
|
|
492
|
-
|
|
493
|
-
- Si ves errores de "^M" o caracteres extraños:
|
|
494
|
-
```bash
|
|
495
|
-
# En WSL
|
|
496
|
-
git config core.autocrlf input
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
### Los hooks se vacían o corrompen
|
|
500
|
-
|
|
501
|
-
Este problema suele ocurrir por conflictos de configuración de line endings:
|
|
502
|
-
|
|
503
|
-
1. **Verifica configuraciones**:
|
|
504
|
-
|
|
505
|
-
```bash
|
|
506
|
-
git config --local core.autocrlf # Debe ser 'input'
|
|
507
|
-
git config --global core.autocrlf # Debe ser 'input'
|
|
508
|
-
```
|
|
509
|
-
|
|
510
|
-
2. **Si están diferentes, corrige**:
|
|
511
|
-
|
|
512
|
-
```bash
|
|
513
|
-
git config --local core.autocrlf input
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
3. **Restaura los hooks**:
|
|
517
|
-
```bash
|
|
518
|
-
./git-hooks/setup-wsl.sh --sync
|
|
519
|
-
```
|
|
520
|
-
|
|
521
|
-
### Credenciales no funcionan en WSL
|
|
522
|
-
|
|
523
|
-
- Las credenciales de Windows no se comparten automáticamente con WSL
|
|
524
|
-
- Reconfigura tus credenciales git en WSL
|
|
525
|
-
|
|
526
|
-
## 📝 Personalización
|
|
527
|
-
|
|
528
|
-
### Archivos de Configuración en `.claude/`
|
|
529
|
-
|
|
530
|
-
#### Pautas de Evaluación
|
|
531
|
-
|
|
532
|
-
- **`CLAUDE_PRE_COMMIT.md`** - Criterios de evaluación para modo estándar
|
|
533
336
|
- **`CLAUDE_PRE_COMMIT_SONAR.md`** - Criterios para modo SonarQube
|
|
534
337
|
|
|
535
|
-
|
|
338
|
+
### Templates de Prompts
|
|
536
339
|
|
|
537
|
-
- **`CLAUDE_ANALYSIS_PROMPT.md`** - Estructura del prompt de análisis estándar
|
|
538
340
|
- **`CLAUDE_ANALYSIS_PROMPT_SONAR.md`** - Estructura del prompt SonarQube
|
|
539
341
|
- **`CLAUDE_RESOLUTION_PROMPT.md`** - Template para generar prompts de resolución
|
|
540
342
|
|
|
@@ -544,33 +346,9 @@ Todos estos archivos son personalizables y específicos de tu proyecto. Puedes:
|
|
|
544
346
|
- Ajustar la estructura de los prompts para obtener respuestas más precisas
|
|
545
347
|
- Personalizar el formato de salida del prompt de resolución
|
|
546
348
|
|
|
547
|
-
##
|
|
548
|
-
|
|
549
|
-
```
|
|
550
|
-
WSL Terminal
|
|
551
|
-
│
|
|
552
|
-
├─→ git add/commit
|
|
553
|
-
│
|
|
554
|
-
└─→ .git/hooks/pre-commit
|
|
555
|
-
│
|
|
556
|
-
├─→ Lee archivos modificados
|
|
557
|
-
├─→ Lee CLAUDE_PRE_COMMIT.md
|
|
558
|
-
├─→ Construye prompt
|
|
559
|
-
│
|
|
560
|
-
└─→ Claude CLI
|
|
561
|
-
│
|
|
562
|
-
└─→ Respuesta JSON
|
|
563
|
-
│
|
|
564
|
-
├─→ ✅ Approved → Commit procede
|
|
565
|
-
└─→ ❌ Rejected → Commit bloqueado
|
|
566
|
-
```
|
|
567
|
-
|
|
568
|
-
## 📌 Notas Importantes
|
|
349
|
+
## 🐛 Troubleshooting
|
|
569
350
|
|
|
570
|
-
|
|
571
|
-
2. **Line Endings**: La configuración de `core.autocrlf` es crítica para evitar problemas entre Windows y Linux
|
|
572
|
-
3. **Credenciales**: Necesitarás reconfigurar tus credenciales git en WSL
|
|
573
|
-
4. **Performance**: La revisión puede tardar unos segundos dependiendo del tamaño de los cambios
|
|
351
|
+
No hay... esto es la perfección hecha código.
|
|
574
352
|
|
|
575
353
|
## 🔧 Desarrollo y Contribución
|
|
576
354
|
|
|
@@ -579,19 +357,25 @@ WSL Terminal
|
|
|
579
357
|
```
|
|
580
358
|
claude-git-hooks/
|
|
581
359
|
├── bin/
|
|
582
|
-
│ └── claude-hooks
|
|
360
|
+
│ └── claude-hooks # CLI principal con verificación completa
|
|
583
361
|
├── templates/
|
|
584
|
-
│ ├── pre-commit
|
|
585
|
-
│ ├── prepare-commit-msg
|
|
586
|
-
│ ├──
|
|
587
|
-
│
|
|
588
|
-
├── .
|
|
589
|
-
│
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
├──
|
|
593
|
-
├──
|
|
594
|
-
|
|
362
|
+
│ ├── pre-commit # Hook de análisis de código
|
|
363
|
+
│ ├── prepare-commit-msg # Hook de generación de mensajes
|
|
364
|
+
│ ├── check-version.sh # Script de verificación de versión
|
|
365
|
+
│ ├── CLAUDE_PRE_COMMIT_SONAR.md # Pautas SonarQube para pre commit
|
|
366
|
+
│ ├── CLAUDE_ANALYSIS_PROMPT_SONAR.md # Template de prompt para análisis
|
|
367
|
+
│ └── CLAUDE_RESOLUTION_PROMPT.md # Template para resolución de issues
|
|
368
|
+
├── .claude/ # Directorio de configuración local (gitignore)
|
|
369
|
+
│ └── settings.local.json # Configuración local del usuario
|
|
370
|
+
├── package.json # Configuración NPM
|
|
371
|
+
├── package-lock.json # Lock file de NPM
|
|
372
|
+
├── README.md # Este archivo
|
|
373
|
+
├── README-NPM.md # Documentación para publicación NPM
|
|
374
|
+
├── CHANGELOG.md # Historial de versiones
|
|
375
|
+
├── PUBLISH.md # Guía de publicación
|
|
376
|
+
├── CLAUDE_PRE_COMMIT_SONAR.md # Pautas de evaluación (legacy)
|
|
377
|
+
├── pre-commit # Hook principal (legacy)
|
|
378
|
+
└── .gitignore # Archivos ignorados por git
|
|
595
379
|
```
|
|
596
380
|
|
|
597
381
|
### Configuración del Entorno de Desarrollo
|
|
@@ -624,23 +408,18 @@ Los hooks están en `templates/`:
|
|
|
624
408
|
#### 2. Probar Localmente
|
|
625
409
|
|
|
626
410
|
```bash
|
|
627
|
-
# Después de modificar archivos
|
|
411
|
+
# Después de modificar archivos, repo de claude-hooks
|
|
628
412
|
npm link
|
|
629
413
|
|
|
630
414
|
# En un repo de prueba
|
|
631
|
-
claude-hooks install --force # Fuerza reinstalación
|
|
632
|
-
|
|
633
|
-
# Probar funcionalidad
|
|
634
|
-
git add .
|
|
635
|
-
git commit -m "auto" # Probar generación automática
|
|
636
|
-
git commit -m "test" # Probar análisis
|
|
415
|
+
claude-hooks install --force # Fuerza reinstalación, luego probar lo que se desee
|
|
637
416
|
```
|
|
638
417
|
|
|
639
418
|
#### 3. Actualizar Documentación
|
|
640
419
|
|
|
641
420
|
- `README.md` - Documentación principal
|
|
421
|
+
- `CHANGELOG.md` - Documentación principal
|
|
642
422
|
- `README-NPM.md` - Para usuarios de NPM
|
|
643
|
-
- `templates/CLAUDE_PRE_COMMIT*.md` - Pautas de evaluación
|
|
644
423
|
|
|
645
424
|
#### 4. Versioning y Publicación
|
|
646
425
|
|
|
@@ -652,68 +431,4 @@ npm version major # 1.0.0 -> 2.0.0
|
|
|
652
431
|
|
|
653
432
|
# Publicar nueva versión
|
|
654
433
|
npm publish
|
|
655
|
-
|
|
656
|
-
# Tag en git
|
|
657
|
-
git push --tags
|
|
658
434
|
```
|
|
659
|
-
|
|
660
|
-
### Debugging
|
|
661
|
-
|
|
662
|
-
#### Modo Debug
|
|
663
|
-
|
|
664
|
-
```bash
|
|
665
|
-
# Activar debug en hooks
|
|
666
|
-
DEBUG=1 git commit -m "test"
|
|
667
|
-
|
|
668
|
-
# Ver logs del CLI
|
|
669
|
-
claude-hooks install --debug
|
|
670
|
-
```
|
|
671
|
-
|
|
672
|
-
#### Logs Útiles
|
|
673
|
-
|
|
674
|
-
- `debug-claude-response.json` - Respuestas de Claude en modo debug
|
|
675
|
-
- `~/.npm/_logs/` - Logs de npm install
|
|
676
|
-
- `.git/hooks/` - Verificar que hooks están instalados
|
|
677
|
-
|
|
678
|
-
### Testing
|
|
679
|
-
|
|
680
|
-
#### Test Manual
|
|
681
|
-
|
|
682
|
-
```bash
|
|
683
|
-
# 1. Crear repo de prueba
|
|
684
|
-
mkdir test-repo && cd test-repo
|
|
685
|
-
git init
|
|
686
|
-
|
|
687
|
-
# 2. Instalar hooks
|
|
688
|
-
claude-hooks install
|
|
689
|
-
|
|
690
|
-
# 3. Crear archivos de prueba
|
|
691
|
-
echo 'public class Test {}' > Test.java
|
|
692
|
-
git add .
|
|
693
|
-
|
|
694
|
-
# 4. Probar análisis
|
|
695
|
-
git commit -m "test: nueva clase"
|
|
696
|
-
|
|
697
|
-
# 5. Probar generación automática
|
|
698
|
-
echo 'public void newMethod() {}' >> Test.java
|
|
699
|
-
git add .
|
|
700
|
-
git commit -m "auto"
|
|
701
|
-
```
|
|
702
|
-
|
|
703
|
-
#### Casos de Prueba
|
|
704
|
-
|
|
705
|
-
- ✅ Análisis de código Java
|
|
706
|
-
- ✅ Generación automática de mensajes
|
|
707
|
-
- ✅ Modo SonarQube
|
|
708
|
-
- ✅ Archivos grandes (>100KB)
|
|
709
|
-
- ✅ Commits sin archivos Java
|
|
710
|
-
- ✅ Enable/disable hooks
|
|
711
|
-
- ✅ Modo debug
|
|
712
|
-
|
|
713
|
-
### Contribuir
|
|
714
|
-
|
|
715
|
-
1. **Fork** el repositorio
|
|
716
|
-
2. **Crear branch** para tu feature: `git checkout -b feature/nueva-funcionalidad`
|
|
717
|
-
3. **Commit** tus cambios: `git commit -m "feat: nueva funcionalidad"`
|
|
718
|
-
4. **Push** al branch: `git push origin feature/nueva-funcionalidad`
|
|
719
|
-
5. **Abrir Pull Request**
|
package/bin/claude-hooks
CHANGED
|
@@ -447,9 +447,9 @@ async function install(args) {
|
|
|
447
447
|
console.log(' git commit --no-verify # Omite el análisis completamente');
|
|
448
448
|
console.log('\nExcluir código del análisis:');
|
|
449
449
|
console.log(' // SKIP-ANALYSIS # Excluye la siguiente línea');
|
|
450
|
-
console.log(' //
|
|
450
|
+
console.log(' // SKIP_ANALYSIS_BLOCK # Excluye bloque hasta encontrar otro igual');
|
|
451
451
|
console.log(' ...código excluido...');
|
|
452
|
-
console.log(' //
|
|
452
|
+
console.log(' // SKIP_ANALYSIS_BLOCK');
|
|
453
453
|
console.log('\nPara más opciones: claude-hooks --help');
|
|
454
454
|
}
|
|
455
455
|
|
|
@@ -638,9 +638,9 @@ function updateGitignore() {
|
|
|
638
638
|
const claudeEntries = [
|
|
639
639
|
'# Claude Git Hooks',
|
|
640
640
|
'.claude/',
|
|
641
|
-
'.claude-analysis-mode',
|
|
642
641
|
'debug-claude-response.json',
|
|
643
|
-
'claude_resolution_prompt.md'
|
|
642
|
+
'claude_resolution_prompt.md',
|
|
643
|
+
'.claude-pr-analysis.json',
|
|
644
644
|
];
|
|
645
645
|
|
|
646
646
|
let gitignoreContent = '';
|
|
@@ -794,6 +794,258 @@ function disable(hookName) {
|
|
|
794
794
|
});
|
|
795
795
|
}
|
|
796
796
|
|
|
797
|
+
// Comando analyze-diff
|
|
798
|
+
function analyzeDiff(args) {
|
|
799
|
+
if (!checkGitRepo()) {
|
|
800
|
+
error('No estás en un repositorio Git.');
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
const currentBranch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();
|
|
805
|
+
|
|
806
|
+
if (!currentBranch) {
|
|
807
|
+
error('No estás en una rama válida.');
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
let baseBranch, compareWith, contextDescription;
|
|
812
|
+
|
|
813
|
+
if (args[0]) {
|
|
814
|
+
// Caso con argumento: comparar rama actual vs rama especificada
|
|
815
|
+
baseBranch = args[0];
|
|
816
|
+
compareWith = `${baseBranch}...HEAD`;
|
|
817
|
+
contextDescription = `${currentBranch} vs ${baseBranch}`;
|
|
818
|
+
|
|
819
|
+
// Verificar que la rama base existe
|
|
820
|
+
try {
|
|
821
|
+
execSync(`git rev-parse --verify ${baseBranch}`, { stdio: 'ignore' });
|
|
822
|
+
} catch (e) {
|
|
823
|
+
error(`La rama ${baseBranch} no existe.`);
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
} else {
|
|
827
|
+
// Caso sin argumento: comparar local vs remoto
|
|
828
|
+
try {
|
|
829
|
+
// Obtener la rama remota tracked
|
|
830
|
+
const remoteBranch = execSync(`git rev-parse --abbrev-ref ${currentBranch}@{upstream}`, { encoding: 'utf8' }).trim();
|
|
831
|
+
|
|
832
|
+
// Actualizar referencias remotas
|
|
833
|
+
execSync('git fetch', { stdio: 'ignore' });
|
|
834
|
+
|
|
835
|
+
baseBranch = remoteBranch;
|
|
836
|
+
compareWith = `HEAD..${remoteBranch}`;
|
|
837
|
+
contextDescription = `cambios locales sin pushear en ${currentBranch}`;
|
|
838
|
+
|
|
839
|
+
info(`Comparando cambios locales vs remoto: ${remoteBranch}`);
|
|
840
|
+
} catch (e) {
|
|
841
|
+
// Si no hay rama remota, usar develop como fallback
|
|
842
|
+
baseBranch = 'develop';
|
|
843
|
+
compareWith = `${baseBranch}...HEAD`;
|
|
844
|
+
contextDescription = `${currentBranch} vs ${baseBranch} (sin rama remota)`;
|
|
845
|
+
|
|
846
|
+
try {
|
|
847
|
+
execSync(`git rev-parse --verify ${baseBranch}`, { stdio: 'ignore' });
|
|
848
|
+
warning(`No hay rama remota configurada. Comparando con ${baseBranch}.`);
|
|
849
|
+
} catch (e2) {
|
|
850
|
+
baseBranch = 'main';
|
|
851
|
+
compareWith = `${baseBranch}...HEAD`;
|
|
852
|
+
contextDescription = `${currentBranch} vs ${baseBranch} (fallback)`;
|
|
853
|
+
|
|
854
|
+
try {
|
|
855
|
+
execSync(`git rev-parse --verify ${baseBranch}`, { stdio: 'ignore' });
|
|
856
|
+
warning(`No hay develop ni rama remota. Comparando con main.`);
|
|
857
|
+
} catch (e3) {
|
|
858
|
+
error('No se pudo encontrar una rama de comparación válida.');
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
info(`Analizando: ${contextDescription}...`);
|
|
866
|
+
|
|
867
|
+
// Obtener los archivos modificados
|
|
868
|
+
let diffFiles;
|
|
869
|
+
try {
|
|
870
|
+
// Para cambios locales sin pushear, invertir la comparación
|
|
871
|
+
if (!args[0] && compareWith.includes('HEAD..')) {
|
|
872
|
+
diffFiles = execSync(`git diff ${compareWith} --name-only`, { encoding: 'utf8' }).trim();
|
|
873
|
+
} else {
|
|
874
|
+
diffFiles = execSync(`git diff ${compareWith} --name-only`, { encoding: 'utf8' }).trim();
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
if (!diffFiles) {
|
|
878
|
+
// Verificar si hay cambios staged o unstaged
|
|
879
|
+
const stagedFiles = execSync('git diff --cached --name-only', { encoding: 'utf8' }).trim();
|
|
880
|
+
const unstagedFiles = execSync('git diff --name-only', { encoding: 'utf8' }).trim();
|
|
881
|
+
|
|
882
|
+
if (stagedFiles || unstagedFiles) {
|
|
883
|
+
warning('No hay diferencias con el remoto, pero tienes cambios locales sin commitear.');
|
|
884
|
+
console.log('Cambios staged:', stagedFiles || 'ninguno');
|
|
885
|
+
console.log('Cambios unstaged:', unstagedFiles || 'ninguno');
|
|
886
|
+
} else {
|
|
887
|
+
success('✅ No hay diferencias. Tu rama está sincronizada.');
|
|
888
|
+
}
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
} catch (e) {
|
|
892
|
+
error('Error al obtener las diferencias: ' + e.message);
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// Obtener el diff completo
|
|
897
|
+
let fullDiff, commits;
|
|
898
|
+
try {
|
|
899
|
+
if (!args[0] && compareWith.includes('HEAD..')) {
|
|
900
|
+
fullDiff = execSync(`git diff ${compareWith}`, { encoding: 'utf8' });
|
|
901
|
+
commits = execSync(`git log ${baseBranch}..HEAD --oneline`, { encoding: 'utf8' }).trim();
|
|
902
|
+
} else {
|
|
903
|
+
fullDiff = execSync(`git diff ${compareWith}`, { encoding: 'utf8' });
|
|
904
|
+
commits = execSync(`git log ${baseBranch}..HEAD --oneline`, { encoding: 'utf8' }).trim();
|
|
905
|
+
}
|
|
906
|
+
} catch (e) {
|
|
907
|
+
error('Error al obtener diff o commits: ' + e.message);
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// Crear el prompt para Claude
|
|
912
|
+
const tempDir = `/tmp/claude-analyze-${Date.now()}`;
|
|
913
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
914
|
+
|
|
915
|
+
const promptFile = path.join(tempDir, 'prompt.txt');
|
|
916
|
+
const prompt = `Analiza los siguientes cambios. CONTEXTO: ${contextDescription}
|
|
917
|
+
|
|
918
|
+
Por favor genera:
|
|
919
|
+
1. Un título de PR conciso y descriptivo (máximo 72 caracteres)
|
|
920
|
+
2. Una descripción detallada del PR que incluya:
|
|
921
|
+
- Resumen de los cambios
|
|
922
|
+
- Motivación/contexto
|
|
923
|
+
- Tipo de cambio (feature/fix/refactor/docs/etc)
|
|
924
|
+
- Testing recomendado
|
|
925
|
+
3. Un nombre de rama sugerido siguiendo el formato: tipo/descripcion-corta (ejemplo: feature/add-user-auth, fix/memory-leak)
|
|
926
|
+
|
|
927
|
+
IMPORTANTE: Si estos son cambios locales sin pushear, el nombre de rama sugerido debe ser para crear una nueva rama desde la actual.
|
|
928
|
+
|
|
929
|
+
Responde EXCLUSIVAMENTE con un JSON válido con esta estructura:
|
|
930
|
+
{
|
|
931
|
+
"prTitle": "título del PR",
|
|
932
|
+
"prDescription": "descripción detallada del PR con markdown",
|
|
933
|
+
"suggestedBranchName": "tipo/nombre-rama-sugerido",
|
|
934
|
+
"changeType": "feature|fix|refactor|docs|test|chore",
|
|
935
|
+
"breakingChanges": false,
|
|
936
|
+
"testingNotes": "notas sobre testing necesario"
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
=== COMMITS ===
|
|
940
|
+
${commits}
|
|
941
|
+
|
|
942
|
+
=== ARCHIVOS MODIFICADOS ===
|
|
943
|
+
${diffFiles}
|
|
944
|
+
|
|
945
|
+
=== DIFF COMPLETO ===
|
|
946
|
+
${fullDiff.substring(0, 50000)} ${fullDiff.length > 50000 ? '\n... (diff truncado)' : ''}`;
|
|
947
|
+
|
|
948
|
+
fs.writeFileSync(promptFile, prompt);
|
|
949
|
+
|
|
950
|
+
info('Enviando a Claude para análisis...');
|
|
951
|
+
|
|
952
|
+
try {
|
|
953
|
+
const response = execSync(`claude < "${promptFile}"`, { encoding: 'utf8', maxBuffer: 1024 * 1024 * 10 });
|
|
954
|
+
|
|
955
|
+
// Extraer el JSON de la respuesta
|
|
956
|
+
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
957
|
+
if (!jsonMatch) {
|
|
958
|
+
error('No se recibió una respuesta JSON válida de Claude.');
|
|
959
|
+
console.log('Respuesta completa:', response);
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
let result;
|
|
964
|
+
try {
|
|
965
|
+
result = JSON.parse(jsonMatch[0]);
|
|
966
|
+
} catch (e) {
|
|
967
|
+
error('Error al parsear la respuesta JSON: ' + e.message);
|
|
968
|
+
console.log('JSON recibido:', jsonMatch[0]);
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// Mostrar los resultados
|
|
973
|
+
console.log('');
|
|
974
|
+
console.log('═══════════════════════════════════════════════════════════════');
|
|
975
|
+
console.log(' ANÁLISIS DE DIFERENCIAS ');
|
|
976
|
+
console.log('═══════════════════════════════════════════════════════════════');
|
|
977
|
+
console.log('');
|
|
978
|
+
|
|
979
|
+
console.log(`🔍 ${colors.blue}Contexto:${colors.reset} ${contextDescription}`);
|
|
980
|
+
console.log(`📊 ${colors.blue}Archivos modificados:${colors.reset} ${diffFiles.split('\n').length}`);
|
|
981
|
+
console.log('');
|
|
982
|
+
|
|
983
|
+
console.log(`📝 ${colors.green}Título del PR:${colors.reset}`);
|
|
984
|
+
console.log(` ${result.prTitle}`);
|
|
985
|
+
console.log('');
|
|
986
|
+
|
|
987
|
+
console.log(`🌿 ${colors.green}Nombre de rama sugerido:${colors.reset}`);
|
|
988
|
+
console.log(` ${result.suggestedBranchName}`);
|
|
989
|
+
console.log('');
|
|
990
|
+
|
|
991
|
+
console.log(`📋 ${colors.green}Tipo de cambio:${colors.reset} ${result.changeType}`);
|
|
992
|
+
|
|
993
|
+
if (result.breakingChanges) {
|
|
994
|
+
console.log(`⚠️ ${colors.yellow}Breaking Changes: SÍ${colors.reset}`);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
console.log('');
|
|
998
|
+
console.log(`📄 ${colors.green}Descripción del PR:${colors.reset}`);
|
|
999
|
+
console.log('───────────────────────────────────────────────────────────────');
|
|
1000
|
+
console.log(result.prDescription);
|
|
1001
|
+
console.log('───────────────────────────────────────────────────────────────');
|
|
1002
|
+
|
|
1003
|
+
if (result.testingNotes) {
|
|
1004
|
+
console.log('');
|
|
1005
|
+
console.log(`🧪 ${colors.green}Notas de Testing:${colors.reset}`);
|
|
1006
|
+
console.log(result.testingNotes);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// Guardar los resultados en un archivo con contexto
|
|
1010
|
+
const outputData = {
|
|
1011
|
+
...result,
|
|
1012
|
+
context: {
|
|
1013
|
+
currentBranch,
|
|
1014
|
+
baseBranch,
|
|
1015
|
+
contextDescription,
|
|
1016
|
+
filesChanged: diffFiles.split('\n').length,
|
|
1017
|
+
timestamp: new Date().toISOString()
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
|
|
1021
|
+
const outputFile = '.claude-pr-analysis.json';
|
|
1022
|
+
fs.writeFileSync(outputFile, JSON.stringify(outputData, null, 2));
|
|
1023
|
+
console.log('');
|
|
1024
|
+
info(`Resultados guardados en ${outputFile}`);
|
|
1025
|
+
|
|
1026
|
+
// Sugerencias contextuales
|
|
1027
|
+
console.log('');
|
|
1028
|
+
if (!args[0] && contextDescription.includes('cambios locales sin pushear')) {
|
|
1029
|
+
// Caso de cambios locales sin pushear
|
|
1030
|
+
console.log(`💡 ${colors.yellow}Para crear nueva rama con estos cambios:${colors.reset}`);
|
|
1031
|
+
console.log(` git checkout -b ${result.suggestedBranchName}`);
|
|
1032
|
+
console.log(` git push -u origin ${result.suggestedBranchName}`);
|
|
1033
|
+
} else if (currentBranch !== result.suggestedBranchName) {
|
|
1034
|
+
// Caso normal de comparación entre ramas
|
|
1035
|
+
console.log(`💡 ${colors.yellow}Para renombrar tu rama actual:${colors.reset}`);
|
|
1036
|
+
console.log(` git branch -m ${result.suggestedBranchName}`);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
console.log(`💡 ${colors.yellow}Tip:${colors.reset} Usa esta información para crear tu PR en GitHub.`);
|
|
1040
|
+
|
|
1041
|
+
} catch (e) {
|
|
1042
|
+
error('Error al ejecutar Claude: ' + e.message);
|
|
1043
|
+
} finally {
|
|
1044
|
+
// Limpiar archivos temporales
|
|
1045
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
797
1049
|
// Comando status
|
|
798
1050
|
function status() {
|
|
799
1051
|
if (!checkGitRepo()) {
|
|
@@ -816,21 +1068,9 @@ function status() {
|
|
|
816
1068
|
}
|
|
817
1069
|
});
|
|
818
1070
|
|
|
819
|
-
// Verificar modo de análisis actual
|
|
820
|
-
console.log('\nModo de análisis:');
|
|
821
|
-
const analysisMode = fs.existsSync('.claude-analysis-mode')
|
|
822
|
-
? fs.readFileSync('.claude-analysis-mode', 'utf8').trim()
|
|
823
|
-
: 'standard (por defecto)';
|
|
824
|
-
|
|
825
|
-
if (analysisMode === 'sonar') {
|
|
826
|
-
info(`Modo actual: SonarQube`);
|
|
827
|
-
} else {
|
|
828
|
-
info(`Modo actual: Estándar`);
|
|
829
|
-
}
|
|
830
|
-
|
|
831
1071
|
// Verificar archivos de pautas
|
|
832
1072
|
console.log('\nArchivos de pautas:');
|
|
833
|
-
const guidelines = ['
|
|
1073
|
+
const guidelines = ['CLAUDE_PRE_COMMIT_SONAR.md'];
|
|
834
1074
|
guidelines.forEach(guideline => {
|
|
835
1075
|
const claudePath = path.join('.claude', guideline);
|
|
836
1076
|
if (fs.existsSync(claudePath)) {
|
|
@@ -847,7 +1087,7 @@ function status() {
|
|
|
847
1087
|
const gitignorePath = '.gitignore';
|
|
848
1088
|
if (fs.existsSync(gitignorePath)) {
|
|
849
1089
|
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
|
|
850
|
-
const claudeIgnores = ['.claude/', '
|
|
1090
|
+
const claudeIgnores = ['.claude/', 'debug-claude-response.json', '.claude-pr-analysis.json'];
|
|
851
1091
|
let allPresent = true;
|
|
852
1092
|
|
|
853
1093
|
claudeIgnores.forEach(entry => {
|
|
@@ -873,28 +1113,6 @@ function setMode(mode) {
|
|
|
873
1113
|
if (!checkGitRepo()) {
|
|
874
1114
|
error('No estás en un repositorio Git.');
|
|
875
1115
|
}
|
|
876
|
-
|
|
877
|
-
const validModes = ['sonar'];
|
|
878
|
-
|
|
879
|
-
if (!mode) {
|
|
880
|
-
// Modo interactivo
|
|
881
|
-
console.log('\nModo de análisis configurado:');
|
|
882
|
-
console.log('SonarQube - Formato con métricas y quality gate');
|
|
883
|
-
console.log('\nEjemplo de uso: claude-hooks set-mode sonar');
|
|
884
|
-
return;
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
if (!validModes.includes(mode)) {
|
|
888
|
-
error(`Modo inválido: ${mode}. Usa 'sonar'`);
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
// Guardar el modo
|
|
892
|
-
fs.writeFileSync('.claude-analysis-mode', mode);
|
|
893
|
-
|
|
894
|
-
if (mode === 'sonar') {
|
|
895
|
-
success('Modo configurado: SonarQube');
|
|
896
|
-
info('Los commits usarán formato SonarQube con métricas y quality gate');
|
|
897
|
-
}
|
|
898
1116
|
}
|
|
899
1117
|
|
|
900
1118
|
// Función para comparar versiones usando el script compartido
|
|
@@ -993,8 +1211,8 @@ Comandos:
|
|
|
993
1211
|
uninstall Desinstala los hooks del repositorio
|
|
994
1212
|
enable [hook] Habilita hooks (todos o uno específico)
|
|
995
1213
|
disable [hook] Deshabilita hooks (todos o uno específico)
|
|
996
|
-
set-mode [standard|sonar] Cambia el modo de análisis
|
|
997
1214
|
status Muestra el estado de los hooks
|
|
1215
|
+
analyze-diff [base] Analiza diferencias entre ramas y genera PR info
|
|
998
1216
|
help Muestra esta ayuda
|
|
999
1217
|
|
|
1000
1218
|
Hooks disponibles:
|
|
@@ -1005,11 +1223,10 @@ Ejemplos:
|
|
|
1005
1223
|
claude-hooks install # Instala todos los hooks
|
|
1006
1224
|
claude-hooks install --skip-auth # Instala sin verificar autenticación
|
|
1007
1225
|
claude-hooks update # Actualiza a la última versión
|
|
1008
|
-
claude-hooks set-mode sonar # Cambiar a modo SonarQube
|
|
1009
|
-
claude-hooks set-mode standard # Cambiar a modo estándar
|
|
1010
1226
|
claude-hooks disable pre-commit # Deshabilita solo pre-commit
|
|
1011
1227
|
claude-hooks enable # Habilita todos los hooks
|
|
1012
1228
|
claude-hooks status # Ver estado actual
|
|
1229
|
+
claude-hooks analyze-diff main # Analiza diferencias con main
|
|
1013
1230
|
|
|
1014
1231
|
Casos de uso de commits:
|
|
1015
1232
|
git commit -m "mensaje" # Mensaje manual + análisis bloqueante
|
|
@@ -1017,11 +1234,17 @@ Casos de uso de commits:
|
|
|
1017
1234
|
git commit --no-verify -m "auto" # Mensaje automático sin análisis
|
|
1018
1235
|
git commit --no-verify -m "msg" # Mensaje manual sin análisis
|
|
1019
1236
|
|
|
1237
|
+
Caso de uso analyze-diff:
|
|
1238
|
+
claude-hooks analyze-diff main # Analiza cambios vs main y genera:
|
|
1239
|
+
→ Título PR: "feat: add user authentication module"
|
|
1240
|
+
→ Descripción PR: "## Summary\n- Added JWT authentication..."
|
|
1241
|
+
→ Rama sugerida: "feature/user-authentication"
|
|
1242
|
+
|
|
1020
1243
|
Excluir código del análisis:
|
|
1021
1244
|
// SKIP-ANALYSIS # Excluye la siguiente línea del análisis
|
|
1022
|
-
//
|
|
1245
|
+
// SKIP_ANALYSIS_BLOCK # Excluye bloque hasta encontrar otro igual
|
|
1023
1246
|
...código excluido...
|
|
1024
|
-
//
|
|
1247
|
+
// SKIP_ANALYSIS_BLOCK
|
|
1025
1248
|
|
|
1026
1249
|
Más información: https://github.com/pablorovito/claude-git-hooks
|
|
1027
1250
|
`);
|
|
@@ -1048,12 +1271,12 @@ async function main() {
|
|
|
1048
1271
|
case 'disable':
|
|
1049
1272
|
disable(args[1]);
|
|
1050
1273
|
break;
|
|
1051
|
-
case 'set-mode':
|
|
1052
|
-
setMode(args[1]);
|
|
1053
|
-
break;
|
|
1054
1274
|
case 'status':
|
|
1055
1275
|
status();
|
|
1056
1276
|
break;
|
|
1277
|
+
case 'analyze-diff':
|
|
1278
|
+
analyzeDiff(args.slice(1));
|
|
1279
|
+
break;
|
|
1057
1280
|
case 'help':
|
|
1058
1281
|
case '--help':
|
|
1059
1282
|
case '-h':
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/templates/pre-commit
CHANGED
|
@@ -109,7 +109,6 @@ generate_resolution_prompt() {
|
|
|
109
109
|
sed -i "s|{{BRANCH_NAME}}|${BRANCH_NAME}|g" "$RESOLUTION_FILE"
|
|
110
110
|
sed -i "s|{{COMMIT_SHA}}|${COMMIT_SHA}|g" "$RESOLUTION_FILE"
|
|
111
111
|
sed -i "s|{{FILE_COUNT}}|${FILE_COUNT}|g" "$RESOLUTION_FILE"
|
|
112
|
-
sed -i "s|{{ANALYSIS_MODE}}|sonar|g" "$RESOLUTION_FILE"
|
|
113
112
|
|
|
114
113
|
# Crear archivo temporal para issues formateados
|
|
115
114
|
local TEMP_ISSUES_FILE=$(mktemp)
|
|
@@ -149,18 +148,10 @@ generate_resolution_prompt() {
|
|
|
149
148
|
echo
|
|
150
149
|
}
|
|
151
150
|
|
|
152
|
-
# Usar siempre modo SonarQube
|
|
153
|
-
ANALYSIS_MODE="sonar"
|
|
154
|
-
|
|
155
151
|
# Configurar archivos para modo SonarQube
|
|
156
152
|
GUIDELINES_FILE=".claude/CLAUDE_PRE_COMMIT_SONAR.md"
|
|
157
153
|
PROMPT_TEMPLATE=".claude/CLAUDE_ANALYSIS_PROMPT_SONAR.md"
|
|
158
|
-
log "Usando modo de análisis: SonarQube"
|
|
159
154
|
|
|
160
|
-
# Guardar preferencia si no existe
|
|
161
|
-
if [ ! -f ".claude-analysis-mode" ]; then
|
|
162
|
-
echo "sonar" > .claude-analysis-mode
|
|
163
|
-
fi
|
|
164
155
|
|
|
165
156
|
# Verificar que el template de prompt existe
|
|
166
157
|
if [ ! -f "$PROMPT_TEMPLATE" ]; then
|
|
@@ -214,15 +205,20 @@ filter_skip_analysis() {
|
|
|
214
205
|
local inside_skip_block=false
|
|
215
206
|
|
|
216
207
|
while IFS= read -r line; do
|
|
217
|
-
# Detectar
|
|
208
|
+
# Detectar SKIP-ANALYSIS para línea única
|
|
218
209
|
if echo "$line" | grep -q "// SKIP-ANALYSIS"; then
|
|
210
|
+
skip_next_line=true
|
|
211
|
+
continue
|
|
212
|
+
fi
|
|
213
|
+
|
|
214
|
+
# Detectar inicio/fin de bloque SKIP_ANALYSIS_BLOCK
|
|
215
|
+
if echo "$line" | grep -q "// SKIP_ANALYSIS_BLOCK"; then
|
|
219
216
|
if [ "$inside_skip_block" = true ]; then
|
|
220
217
|
# Fin del bloque
|
|
221
218
|
inside_skip_block=false
|
|
222
219
|
else
|
|
223
|
-
# Inicio del bloque
|
|
220
|
+
# Inicio del bloque
|
|
224
221
|
inside_skip_block=true
|
|
225
|
-
skip_next_line=true
|
|
226
222
|
fi
|
|
227
223
|
continue
|
|
228
224
|
fi
|
|
@@ -235,7 +231,6 @@ filter_skip_analysis() {
|
|
|
235
231
|
# Si debemos saltar la siguiente línea (comentario único)
|
|
236
232
|
if [ "$skip_next_line" = true ]; then
|
|
237
233
|
skip_next_line=false
|
|
238
|
-
inside_skip_block=false
|
|
239
234
|
continue
|
|
240
235
|
fi
|
|
241
236
|
|
|
@@ -337,10 +332,9 @@ if $CLAUDE_CLI < "$PROMPT_FILE" > "$RESPONSE_FILE" 2>&1; then
|
|
|
337
332
|
BLOCKING_ISSUES=$(echo "$JSON_RESPONSE" | jq -r '.blockingIssues[].description' 2>/dev/null | sed '/^$/d')
|
|
338
333
|
fi
|
|
339
334
|
|
|
340
|
-
# Verificar si estamos en modo SonarQube
|
|
341
335
|
QUALITY_GATE=$(echo "$JSON_RESPONSE" | jq -r '.QUALITY_GATE // ""' 2>/dev/null)
|
|
342
336
|
|
|
343
|
-
if [
|
|
337
|
+
if [ -n "$QUALITY_GATE" ] && [ "$QUALITY_GATE" != "null" ]; then
|
|
344
338
|
# Mostrar resultados estilo SonarQube
|
|
345
339
|
echo
|
|
346
340
|
echo "╔════════════════════════════════════════════════════════════════════╗"
|
|
File without changes
|