claude-git-hooks 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/LICENSE +0 -0
- package/README.md +41 -58
- package/bin/claude-hooks +289 -20
- package/package.json +1 -1
- package/templates/CLAUDE_ANALYSIS_PROMPT_SONAR.md +47 -52
- package/templates/CLAUDE_PRE_COMMIT_SONAR.md +41 -96
- package/templates/CLAUDE_RESOLUTION_PROMPT.md +24 -37
- package/templates/check-version.sh +0 -0
- package/templates/pre-commit +60 -62
- package/templates/prepare-commit-msg +0 -0
- package/templates/CLAUDE_ANALYSIS_PROMPT.md +0 -44
- package/templates/CLAUDE_PRE_COMMIT.md +0 -63
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,65 @@ Todos los cambios notables en este proyecto se documentarán en este archivo.
|
|
|
5
5
|
El formato está basado en [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
y este proyecto adhiere a [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.5.0] - 2025-09-10
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- 🔍 Nuevo comando `claude-hooks analyze-diff [base]` para análisis de diferencias entre ramas
|
|
12
|
+
- Genera título de PR conciso (máx. 72 caracteres)
|
|
13
|
+
- Crea descripción detallada del PR con markdown
|
|
14
|
+
- Sugiere nombre de rama siguiendo formato tipo/descripcion
|
|
15
|
+
- Identifica tipo de cambio (feature/fix/refactor/docs/test/chore)
|
|
16
|
+
- Detecta breaking changes
|
|
17
|
+
- Proporciona notas de testing recomendado
|
|
18
|
+
- Guarda resultados en `.claude-pr-analysis.json`
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- 🎯 Modo SonarQube ahora es el predeterminado (no hay selección interactiva)
|
|
22
|
+
- ⚡ Eliminada lógica de selección de modo del pre-commit hook
|
|
23
|
+
- 🗑️ Removidas referencias a guardado de preferencias `.claude-analysis-mode`
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- 🐛 Eliminado prompt interactivo incompatible con algunas consolas
|
|
27
|
+
- 🔧 Pre-commit ahora va directo al análisis sin solicitar input del usuario
|
|
28
|
+
|
|
29
|
+
### Removed
|
|
30
|
+
- 🗑️ Eliminada selección interactiva de modo de análisis
|
|
31
|
+
- 🗑️ Removido comando `set-mode` (ya no es necesario)
|
|
32
|
+
- 🗑️ Eliminada variable de entorno `CLAUDE_ANALYSIS_MODE`
|
|
33
|
+
|
|
34
|
+
## [1.4.2] - 2025-09-08
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
- 🚫 Comentario `// SKIP-ANALYSIS` para excluir código del análisis
|
|
38
|
+
- Una línea: excluye la siguiente línea
|
|
39
|
+
- Bloque: código entre dos comentarios `// SKIP-ANALYSIS` es excluido
|
|
40
|
+
- 📝 Excepciones para Spring Framework en criterios de evaluación
|
|
41
|
+
- `@Autowired` no es considerado issue bloqueante (máximo severidad MAJOR)
|
|
42
|
+
- Inyección de dependencias con field injection es aceptable
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
- 📋 Actualizada documentación con casos de uso de commits clarificados
|
|
46
|
+
- 🎯 Templates de prompts actualizados con reglas de Spring Framework
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
- 🔧 Clarificado que `git commit --no-verify -m "auto"` genera mensaje automático sin análisis
|
|
50
|
+
|
|
51
|
+
## [1.4.1] - 2025-09-04
|
|
52
|
+
|
|
53
|
+
### Changed
|
|
54
|
+
- 🚀 **BREAKING**: Eliminado modo estándar, ahora solo existe modo SonarQube
|
|
55
|
+
- ⚡ Prompts optimizados para Claude: reducción de ~80% en tokens manteniendo eficacia
|
|
56
|
+
- 🎯 Nuevo formato ultra-compacto de prompts usando notación concisa y directivas claras
|
|
57
|
+
- 📝 Templates simplificados: CLAUDE_ANALYSIS_PROMPT_SONAR.md, CLAUDE_PRE_COMMIT_SONAR.md, CLAUDE_RESOLUTION_PROMPT.md
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
- 🐛 Mejorada la lógica de escritura para mejor formación del prompt de resolución de problemas críticos
|
|
61
|
+
- 🔧 Corregido el formato del prompt AI-friendly para resolución de issues
|
|
62
|
+
|
|
63
|
+
### Removed
|
|
64
|
+
- 🗑️ Eliminados archivos del modo estándar: CLAUDE_PRE_COMMIT.md, CLAUDE_ANALYSIS_PROMPT.md
|
|
65
|
+
- 🗑️ Eliminada lógica de selección de modo (ahora siempre usa SonarQube)
|
|
66
|
+
|
|
8
67
|
## [1.4.0] - 2025-08-29
|
|
9
68
|
|
|
10
69
|
### Added
|
package/LICENSE
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
@@ -201,89 +201,78 @@ Cuando se detectan problemas críticos:
|
|
|
201
201
|
- **Auto-actualización**: Verificación automática de versiones antes de cada commit con prompt interactivo para actualizar
|
|
202
202
|
- **Comando update**: `claude-hooks update` para actualizar manualmente a la última versión
|
|
203
203
|
- **Modo debug**: `DEBUG=1 git commit` guarda respuestas en `debug-claude-response.json`
|
|
204
|
-
- **
|
|
204
|
+
- **Análisis de PR**: Nuevo comando `analyze-diff` para generar información de Pull Requests
|
|
205
205
|
- **Validación de dependencias**: Verifica que Claude CLI esté autenticado antes de ejecutar
|
|
206
206
|
|
|
207
207
|
## 🛠️ Uso
|
|
208
208
|
|
|
209
209
|
**IMPORTANTE**: Todos los comandos git deben ejecutarse desde WSL.
|
|
210
210
|
|
|
211
|
-
###
|
|
211
|
+
### Casos de uso de commits
|
|
212
212
|
|
|
213
213
|
```bash
|
|
214
214
|
# Desde WSL
|
|
215
215
|
git add .
|
|
216
|
-
git commit -m "feat: nueva funcionalidad"
|
|
217
|
-
```
|
|
218
216
|
|
|
219
|
-
|
|
217
|
+
# 1. Mensaje manual + análisis bloqueante
|
|
218
|
+
git commit -m "feat: nueva funcionalidad"
|
|
220
219
|
|
|
221
|
-
|
|
222
|
-
# Desde WSL
|
|
223
|
-
git add .
|
|
220
|
+
# 2. Mensaje automático + análisis bloqueante
|
|
224
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"
|
|
225
228
|
```
|
|
226
229
|
|
|
227
230
|
Claude analizará los cambios y generará un mensaje de commit siguiendo las convenciones (feat, fix, docs, etc.).
|
|
228
231
|
|
|
229
232
|
**⚠️ Si la generación automática falla**: El commit se cancelará completamente. Simplemente ejecuta `git commit -m "tu mensaje"` manualmente.
|
|
230
233
|
|
|
231
|
-
###
|
|
232
|
-
|
|
233
|
-
Puedes elegir entre dos formatos de análisis:
|
|
234
|
+
### Excluir código del análisis con SKIP-ANALYSIS
|
|
234
235
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
```bash
|
|
238
|
-
# Cambiar a modo SonarQube (métricas y quality gate)
|
|
239
|
-
claude-hooks set-mode sonar
|
|
236
|
+
Puedes excluir código específico del análisis usando comentarios `// SKIP-ANALYSIS`:
|
|
240
237
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
claude-hooks status
|
|
246
|
-
```
|
|
238
|
+
```java
|
|
239
|
+
// Excluir una sola línea
|
|
240
|
+
// SKIP-ANALYSIS
|
|
241
|
+
@Autowired private LegacyService legacyService; // Esta línea no será analizada
|
|
247
242
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
unset CLAUDE_ANALYSIS_MODE
|
|
243
|
+
// Excluir un bloque de código
|
|
244
|
+
// SKIP-ANALYSIS
|
|
245
|
+
@Deprecated
|
|
246
|
+
public void methodWithKnownIssues() {
|
|
247
|
+
// Código legacy que no queremos que sea analizado
|
|
248
|
+
System.out.println("Debug temporal");
|
|
249
|
+
}
|
|
250
|
+
// SKIP-ANALYSIS
|
|
257
251
|
```
|
|
258
252
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
**Modo Estándar**:
|
|
262
|
-
|
|
263
|
-
- Análisis con puntuación del 1-10
|
|
264
|
-
- Recomendaciones detalladas por categoría
|
|
265
|
-
- Formato tradicional fácil de leer
|
|
266
|
-
|
|
267
|
-
**Modo SonarQube**:
|
|
253
|
+
**Nota**: El código entre comentarios `SKIP-ANALYSIS` no será enviado a Claude para su análisis.
|
|
268
254
|
|
|
269
|
-
|
|
270
|
-
- Métricas: Reliability, Security, Maintainability
|
|
271
|
-
- Issues clasificados por severidad (Blocker, Critical, Major, Minor, Info)
|
|
272
|
-
- Security hotspots
|
|
255
|
+
### Análisis de diferencias entre ramas 🔍
|
|
273
256
|
|
|
274
|
-
|
|
257
|
+
Nueva funcionalidad para analizar cambios y generar información para Pull Requests:
|
|
275
258
|
|
|
276
259
|
```bash
|
|
277
|
-
#
|
|
278
|
-
claude-hooks
|
|
260
|
+
# Analizar diferencias con la rama main
|
|
261
|
+
claude-hooks analyze-diff
|
|
262
|
+
|
|
263
|
+
# Analizar diferencias con otra rama base
|
|
264
|
+
claude-hooks analyze-diff develop
|
|
279
265
|
```
|
|
280
266
|
|
|
281
|
-
|
|
267
|
+
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
|
|
282
274
|
|
|
283
|
-
|
|
284
|
-
# Desde WSL
|
|
285
|
-
git commit --no-verify -m "fix: corrección urgente"
|
|
286
|
-
```
|
|
275
|
+
Los resultados se guardan en `.claude-pr-analysis.json` para fácil acceso.
|
|
287
276
|
|
|
288
277
|
### Modo debug
|
|
289
278
|
|
|
@@ -423,12 +412,6 @@ claude-hooks status
|
|
|
423
412
|
|
|
424
413
|
### Variables de entorno disponibles
|
|
425
414
|
|
|
426
|
-
- **`CLAUDE_ANALYSIS_MODE`**: Selecciona el modo de análisis
|
|
427
|
-
|
|
428
|
-
- Valores: `"standard"` o `"sonar"`
|
|
429
|
-
- Ejemplo: `CLAUDE_ANALYSIS_MODE=sonar git commit -m "mensaje"`
|
|
430
|
-
- Sobrescribe la preferencia guardada en `.claude-analysis-mode`
|
|
431
|
-
|
|
432
415
|
- **`DEBUG`**: Activa el modo debug
|
|
433
416
|
- Valores: `1` para activar
|
|
434
417
|
- Ejemplo: `DEBUG=1 git commit -m "mensaje"`
|
package/bin/claude-hooks
CHANGED
|
@@ -414,9 +414,7 @@ async function install(args) {
|
|
|
414
414
|
|
|
415
415
|
// Copiar archivos de pautas y prompts a .claude
|
|
416
416
|
const claudeFiles = [
|
|
417
|
-
'CLAUDE_PRE_COMMIT.md',
|
|
418
417
|
'CLAUDE_PRE_COMMIT_SONAR.md',
|
|
419
|
-
'CLAUDE_ANALYSIS_PROMPT.md',
|
|
420
418
|
'CLAUDE_ANALYSIS_PROMPT_SONAR.md',
|
|
421
419
|
'CLAUDE_RESOLUTION_PROMPT.md'
|
|
422
420
|
];
|
|
@@ -446,6 +444,12 @@ async function install(args) {
|
|
|
446
444
|
console.log('\nUso:');
|
|
447
445
|
console.log(' git commit -m "auto" # Genera mensaje automáticamente');
|
|
448
446
|
console.log(' git commit -m "mensaje" # Analiza código antes del commit');
|
|
447
|
+
console.log(' git commit --no-verify # Omite el análisis completamente');
|
|
448
|
+
console.log('\nExcluir código del análisis:');
|
|
449
|
+
console.log(' // SKIP-ANALYSIS # Excluye la siguiente línea');
|
|
450
|
+
console.log(' // SKIP-ANALYSIS # Entre dos comentarios excluye el bloque');
|
|
451
|
+
console.log(' ...código excluido...');
|
|
452
|
+
console.log(' // SKIP-ANALYSIS');
|
|
449
453
|
console.log('\nPara más opciones: claude-hooks --help');
|
|
450
454
|
}
|
|
451
455
|
|
|
@@ -636,7 +640,8 @@ function updateGitignore() {
|
|
|
636
640
|
'.claude/',
|
|
637
641
|
'.claude-analysis-mode',
|
|
638
642
|
'debug-claude-response.json',
|
|
639
|
-
'claude_resolution_prompt.md'
|
|
643
|
+
'claude_resolution_prompt.md',
|
|
644
|
+
'.claude-pr-analysis.json',
|
|
640
645
|
];
|
|
641
646
|
|
|
642
647
|
let gitignoreContent = '';
|
|
@@ -790,6 +795,258 @@ function disable(hookName) {
|
|
|
790
795
|
});
|
|
791
796
|
}
|
|
792
797
|
|
|
798
|
+
// Comando analyze-diff
|
|
799
|
+
function analyzeDiff(args) {
|
|
800
|
+
if (!checkGitRepo()) {
|
|
801
|
+
error('No estás en un repositorio Git.');
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
const currentBranch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();
|
|
806
|
+
|
|
807
|
+
if (!currentBranch) {
|
|
808
|
+
error('No estás en una rama válida.');
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
let baseBranch, compareWith, contextDescription;
|
|
813
|
+
|
|
814
|
+
if (args[0]) {
|
|
815
|
+
// Caso con argumento: comparar rama actual vs rama especificada
|
|
816
|
+
baseBranch = args[0];
|
|
817
|
+
compareWith = `${baseBranch}...HEAD`;
|
|
818
|
+
contextDescription = `${currentBranch} vs ${baseBranch}`;
|
|
819
|
+
|
|
820
|
+
// Verificar que la rama base existe
|
|
821
|
+
try {
|
|
822
|
+
execSync(`git rev-parse --verify ${baseBranch}`, { stdio: 'ignore' });
|
|
823
|
+
} catch (e) {
|
|
824
|
+
error(`La rama ${baseBranch} no existe.`);
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
} else {
|
|
828
|
+
// Caso sin argumento: comparar local vs remoto
|
|
829
|
+
try {
|
|
830
|
+
// Obtener la rama remota tracked
|
|
831
|
+
const remoteBranch = execSync(`git rev-parse --abbrev-ref ${currentBranch}@{upstream}`, { encoding: 'utf8' }).trim();
|
|
832
|
+
|
|
833
|
+
// Actualizar referencias remotas
|
|
834
|
+
execSync('git fetch', { stdio: 'ignore' });
|
|
835
|
+
|
|
836
|
+
baseBranch = remoteBranch;
|
|
837
|
+
compareWith = `HEAD..${remoteBranch}`;
|
|
838
|
+
contextDescription = `cambios locales sin pushear en ${currentBranch}`;
|
|
839
|
+
|
|
840
|
+
info(`Comparando cambios locales vs remoto: ${remoteBranch}`);
|
|
841
|
+
} catch (e) {
|
|
842
|
+
// Si no hay rama remota, usar develop como fallback
|
|
843
|
+
baseBranch = 'develop';
|
|
844
|
+
compareWith = `${baseBranch}...HEAD`;
|
|
845
|
+
contextDescription = `${currentBranch} vs ${baseBranch} (sin rama remota)`;
|
|
846
|
+
|
|
847
|
+
try {
|
|
848
|
+
execSync(`git rev-parse --verify ${baseBranch}`, { stdio: 'ignore' });
|
|
849
|
+
warning(`No hay rama remota configurada. Comparando con ${baseBranch}.`);
|
|
850
|
+
} catch (e2) {
|
|
851
|
+
baseBranch = 'main';
|
|
852
|
+
compareWith = `${baseBranch}...HEAD`;
|
|
853
|
+
contextDescription = `${currentBranch} vs ${baseBranch} (fallback)`;
|
|
854
|
+
|
|
855
|
+
try {
|
|
856
|
+
execSync(`git rev-parse --verify ${baseBranch}`, { stdio: 'ignore' });
|
|
857
|
+
warning(`No hay develop ni rama remota. Comparando con main.`);
|
|
858
|
+
} catch (e3) {
|
|
859
|
+
error('No se pudo encontrar una rama de comparación válida.');
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
info(`Analizando: ${contextDescription}...`);
|
|
867
|
+
|
|
868
|
+
// Obtener los archivos modificados
|
|
869
|
+
let diffFiles;
|
|
870
|
+
try {
|
|
871
|
+
// Para cambios locales sin pushear, invertir la comparación
|
|
872
|
+
if (!args[0] && compareWith.includes('HEAD..')) {
|
|
873
|
+
diffFiles = execSync(`git diff ${compareWith} --name-only`, { encoding: 'utf8' }).trim();
|
|
874
|
+
} else {
|
|
875
|
+
diffFiles = execSync(`git diff ${compareWith} --name-only`, { encoding: 'utf8' }).trim();
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
if (!diffFiles) {
|
|
879
|
+
// Verificar si hay cambios staged o unstaged
|
|
880
|
+
const stagedFiles = execSync('git diff --cached --name-only', { encoding: 'utf8' }).trim();
|
|
881
|
+
const unstagedFiles = execSync('git diff --name-only', { encoding: 'utf8' }).trim();
|
|
882
|
+
|
|
883
|
+
if (stagedFiles || unstagedFiles) {
|
|
884
|
+
warning('No hay diferencias con el remoto, pero tienes cambios locales sin commitear.');
|
|
885
|
+
console.log('Cambios staged:', stagedFiles || 'ninguno');
|
|
886
|
+
console.log('Cambios unstaged:', unstagedFiles || 'ninguno');
|
|
887
|
+
} else {
|
|
888
|
+
success('✅ No hay diferencias. Tu rama está sincronizada.');
|
|
889
|
+
}
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
} catch (e) {
|
|
893
|
+
error('Error al obtener las diferencias: ' + e.message);
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// Obtener el diff completo
|
|
898
|
+
let fullDiff, commits;
|
|
899
|
+
try {
|
|
900
|
+
if (!args[0] && compareWith.includes('HEAD..')) {
|
|
901
|
+
fullDiff = execSync(`git diff ${compareWith}`, { encoding: 'utf8' });
|
|
902
|
+
commits = execSync(`git log ${baseBranch}..HEAD --oneline`, { encoding: 'utf8' }).trim();
|
|
903
|
+
} else {
|
|
904
|
+
fullDiff = execSync(`git diff ${compareWith}`, { encoding: 'utf8' });
|
|
905
|
+
commits = execSync(`git log ${baseBranch}..HEAD --oneline`, { encoding: 'utf8' }).trim();
|
|
906
|
+
}
|
|
907
|
+
} catch (e) {
|
|
908
|
+
error('Error al obtener diff o commits: ' + e.message);
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// Crear el prompt para Claude
|
|
913
|
+
const tempDir = `/tmp/claude-analyze-${Date.now()}`;
|
|
914
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
915
|
+
|
|
916
|
+
const promptFile = path.join(tempDir, 'prompt.txt');
|
|
917
|
+
const prompt = `Analiza los siguientes cambios. CONTEXTO: ${contextDescription}
|
|
918
|
+
|
|
919
|
+
Por favor genera:
|
|
920
|
+
1. Un título de PR conciso y descriptivo (máximo 72 caracteres)
|
|
921
|
+
2. Una descripción detallada del PR que incluya:
|
|
922
|
+
- Resumen de los cambios
|
|
923
|
+
- Motivación/contexto
|
|
924
|
+
- Tipo de cambio (feature/fix/refactor/docs/etc)
|
|
925
|
+
- Testing recomendado
|
|
926
|
+
3. Un nombre de rama sugerido siguiendo el formato: tipo/descripcion-corta (ejemplo: feature/add-user-auth, fix/memory-leak)
|
|
927
|
+
|
|
928
|
+
IMPORTANTE: Si estos son cambios locales sin pushear, el nombre de rama sugerido debe ser para crear una nueva rama desde la actual.
|
|
929
|
+
|
|
930
|
+
Responde EXCLUSIVAMENTE con un JSON válido con esta estructura:
|
|
931
|
+
{
|
|
932
|
+
"prTitle": "título del PR",
|
|
933
|
+
"prDescription": "descripción detallada del PR con markdown",
|
|
934
|
+
"suggestedBranchName": "tipo/nombre-rama-sugerido",
|
|
935
|
+
"changeType": "feature|fix|refactor|docs|test|chore",
|
|
936
|
+
"breakingChanges": false,
|
|
937
|
+
"testingNotes": "notas sobre testing necesario"
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
=== COMMITS ===
|
|
941
|
+
${commits}
|
|
942
|
+
|
|
943
|
+
=== ARCHIVOS MODIFICADOS ===
|
|
944
|
+
${diffFiles}
|
|
945
|
+
|
|
946
|
+
=== DIFF COMPLETO ===
|
|
947
|
+
${fullDiff.substring(0, 50000)} ${fullDiff.length > 50000 ? '\n... (diff truncado)' : ''}`;
|
|
948
|
+
|
|
949
|
+
fs.writeFileSync(promptFile, prompt);
|
|
950
|
+
|
|
951
|
+
info('Enviando a Claude para análisis...');
|
|
952
|
+
|
|
953
|
+
try {
|
|
954
|
+
const response = execSync(`claude < "${promptFile}"`, { encoding: 'utf8', maxBuffer: 1024 * 1024 * 10 });
|
|
955
|
+
|
|
956
|
+
// Extraer el JSON de la respuesta
|
|
957
|
+
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
958
|
+
if (!jsonMatch) {
|
|
959
|
+
error('No se recibió una respuesta JSON válida de Claude.');
|
|
960
|
+
console.log('Respuesta completa:', response);
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
let result;
|
|
965
|
+
try {
|
|
966
|
+
result = JSON.parse(jsonMatch[0]);
|
|
967
|
+
} catch (e) {
|
|
968
|
+
error('Error al parsear la respuesta JSON: ' + e.message);
|
|
969
|
+
console.log('JSON recibido:', jsonMatch[0]);
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// Mostrar los resultados
|
|
974
|
+
console.log('');
|
|
975
|
+
console.log('═══════════════════════════════════════════════════════════════');
|
|
976
|
+
console.log(' ANÁLISIS DE DIFERENCIAS ');
|
|
977
|
+
console.log('═══════════════════════════════════════════════════════════════');
|
|
978
|
+
console.log('');
|
|
979
|
+
|
|
980
|
+
console.log(`🔍 ${colors.blue}Contexto:${colors.reset} ${contextDescription}`);
|
|
981
|
+
console.log(`📊 ${colors.blue}Archivos modificados:${colors.reset} ${diffFiles.split('\n').length}`);
|
|
982
|
+
console.log('');
|
|
983
|
+
|
|
984
|
+
console.log(`📝 ${colors.green}Título del PR:${colors.reset}`);
|
|
985
|
+
console.log(` ${result.prTitle}`);
|
|
986
|
+
console.log('');
|
|
987
|
+
|
|
988
|
+
console.log(`🌿 ${colors.green}Nombre de rama sugerido:${colors.reset}`);
|
|
989
|
+
console.log(` ${result.suggestedBranchName}`);
|
|
990
|
+
console.log('');
|
|
991
|
+
|
|
992
|
+
console.log(`📋 ${colors.green}Tipo de cambio:${colors.reset} ${result.changeType}`);
|
|
993
|
+
|
|
994
|
+
if (result.breakingChanges) {
|
|
995
|
+
console.log(`⚠️ ${colors.yellow}Breaking Changes: SÍ${colors.reset}`);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
console.log('');
|
|
999
|
+
console.log(`📄 ${colors.green}Descripción del PR:${colors.reset}`);
|
|
1000
|
+
console.log('───────────────────────────────────────────────────────────────');
|
|
1001
|
+
console.log(result.prDescription);
|
|
1002
|
+
console.log('───────────────────────────────────────────────────────────────');
|
|
1003
|
+
|
|
1004
|
+
if (result.testingNotes) {
|
|
1005
|
+
console.log('');
|
|
1006
|
+
console.log(`🧪 ${colors.green}Notas de Testing:${colors.reset}`);
|
|
1007
|
+
console.log(result.testingNotes);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// Guardar los resultados en un archivo con contexto
|
|
1011
|
+
const outputData = {
|
|
1012
|
+
...result,
|
|
1013
|
+
context: {
|
|
1014
|
+
currentBranch,
|
|
1015
|
+
baseBranch,
|
|
1016
|
+
contextDescription,
|
|
1017
|
+
filesChanged: diffFiles.split('\n').length,
|
|
1018
|
+
timestamp: new Date().toISOString()
|
|
1019
|
+
}
|
|
1020
|
+
};
|
|
1021
|
+
|
|
1022
|
+
const outputFile = '.claude-pr-analysis.json';
|
|
1023
|
+
fs.writeFileSync(outputFile, JSON.stringify(outputData, null, 2));
|
|
1024
|
+
console.log('');
|
|
1025
|
+
info(`Resultados guardados en ${outputFile}`);
|
|
1026
|
+
|
|
1027
|
+
// Sugerencias contextuales
|
|
1028
|
+
console.log('');
|
|
1029
|
+
if (!args[0] && contextDescription.includes('cambios locales sin pushear')) {
|
|
1030
|
+
// Caso de cambios locales sin pushear
|
|
1031
|
+
console.log(`💡 ${colors.yellow}Para crear nueva rama con estos cambios:${colors.reset}`);
|
|
1032
|
+
console.log(` git checkout -b ${result.suggestedBranchName}`);
|
|
1033
|
+
console.log(` git push -u origin ${result.suggestedBranchName}`);
|
|
1034
|
+
} else if (currentBranch !== result.suggestedBranchName) {
|
|
1035
|
+
// Caso normal de comparación entre ramas
|
|
1036
|
+
console.log(`💡 ${colors.yellow}Para renombrar tu rama actual:${colors.reset}`);
|
|
1037
|
+
console.log(` git branch -m ${result.suggestedBranchName}`);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
console.log(`💡 ${colors.yellow}Tip:${colors.reset} Usa esta información para crear tu PR en GitHub.`);
|
|
1041
|
+
|
|
1042
|
+
} catch (e) {
|
|
1043
|
+
error('Error al ejecutar Claude: ' + e.message);
|
|
1044
|
+
} finally {
|
|
1045
|
+
// Limpiar archivos temporales
|
|
1046
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
|
|
793
1050
|
// Comando status
|
|
794
1051
|
function status() {
|
|
795
1052
|
if (!checkGitRepo()) {
|
|
@@ -870,31 +1127,26 @@ function setMode(mode) {
|
|
|
870
1127
|
error('No estás en un repositorio Git.');
|
|
871
1128
|
}
|
|
872
1129
|
|
|
873
|
-
const validModes = ['
|
|
1130
|
+
const validModes = ['sonar'];
|
|
874
1131
|
|
|
875
1132
|
if (!mode) {
|
|
876
1133
|
// Modo interactivo
|
|
877
|
-
console.log('\
|
|
878
|
-
console.log('
|
|
879
|
-
console.log('
|
|
880
|
-
console.log('\nEjemplo de uso: claude-hooks set-mode standard');
|
|
881
|
-
console.log(' claude-hooks set-mode sonar');
|
|
1134
|
+
console.log('\nModo de análisis configurado:');
|
|
1135
|
+
console.log('SonarQube - Formato con métricas y quality gate');
|
|
1136
|
+
console.log('\nEjemplo de uso: claude-hooks set-mode sonar');
|
|
882
1137
|
return;
|
|
883
1138
|
}
|
|
884
1139
|
|
|
885
1140
|
if (!validModes.includes(mode)) {
|
|
886
|
-
error(`Modo inválido: ${mode}. Usa '
|
|
1141
|
+
error(`Modo inválido: ${mode}. Usa 'sonar'`);
|
|
887
1142
|
}
|
|
888
1143
|
|
|
889
1144
|
// Guardar el modo
|
|
890
1145
|
fs.writeFileSync('.claude-analysis-mode', mode);
|
|
891
1146
|
|
|
892
1147
|
if (mode === 'sonar') {
|
|
893
|
-
success('Modo
|
|
1148
|
+
success('Modo configurado: SonarQube');
|
|
894
1149
|
info('Los commits usarán formato SonarQube con métricas y quality gate');
|
|
895
|
-
} else {
|
|
896
|
-
success('Modo cambiado a: Estándar');
|
|
897
|
-
info('Los commits usarán formato clásico con score y recomendaciones');
|
|
898
1150
|
}
|
|
899
1151
|
}
|
|
900
1152
|
|
|
@@ -994,8 +1246,8 @@ Comandos:
|
|
|
994
1246
|
uninstall Desinstala los hooks del repositorio
|
|
995
1247
|
enable [hook] Habilita hooks (todos o uno específico)
|
|
996
1248
|
disable [hook] Deshabilita hooks (todos o uno específico)
|
|
997
|
-
set-mode [standard|sonar] Cambia el modo de análisis
|
|
998
1249
|
status Muestra el estado de los hooks
|
|
1250
|
+
analyze-diff [base] Analiza diferencias entre ramas y genera PR info
|
|
999
1251
|
help Muestra esta ayuda
|
|
1000
1252
|
|
|
1001
1253
|
Hooks disponibles:
|
|
@@ -1006,11 +1258,28 @@ Ejemplos:
|
|
|
1006
1258
|
claude-hooks install # Instala todos los hooks
|
|
1007
1259
|
claude-hooks install --skip-auth # Instala sin verificar autenticación
|
|
1008
1260
|
claude-hooks update # Actualiza a la última versión
|
|
1009
|
-
claude-hooks set-mode sonar # Cambiar a modo SonarQube
|
|
1010
|
-
claude-hooks set-mode standard # Cambiar a modo estándar
|
|
1011
1261
|
claude-hooks disable pre-commit # Deshabilita solo pre-commit
|
|
1012
1262
|
claude-hooks enable # Habilita todos los hooks
|
|
1013
1263
|
claude-hooks status # Ver estado actual
|
|
1264
|
+
claude-hooks analyze-diff main # Analiza diferencias con main
|
|
1265
|
+
|
|
1266
|
+
Casos de uso de commits:
|
|
1267
|
+
git commit -m "mensaje" # Mensaje manual + análisis bloqueante
|
|
1268
|
+
git commit -m "auto" # Mensaje automático + análisis bloqueante
|
|
1269
|
+
git commit --no-verify -m "auto" # Mensaje automático sin análisis
|
|
1270
|
+
git commit --no-verify -m "msg" # Mensaje manual sin análisis
|
|
1271
|
+
|
|
1272
|
+
Caso de uso analyze-diff:
|
|
1273
|
+
claude-hooks analyze-diff main # Analiza cambios vs main y genera:
|
|
1274
|
+
→ Título PR: "feat: add user authentication module"
|
|
1275
|
+
→ Descripción PR: "## Summary\n- Added JWT authentication..."
|
|
1276
|
+
→ Rama sugerida: "feature/user-authentication"
|
|
1277
|
+
|
|
1278
|
+
Excluir código del análisis:
|
|
1279
|
+
// SKIP-ANALYSIS # Excluye la siguiente línea del análisis
|
|
1280
|
+
// SKIP-ANALYSIS # Entre dos comentarios excluye el bloque
|
|
1281
|
+
...código excluido...
|
|
1282
|
+
// SKIP-ANALYSIS
|
|
1014
1283
|
|
|
1015
1284
|
Más información: https://github.com/pablorovito/claude-git-hooks
|
|
1016
1285
|
`);
|
|
@@ -1037,12 +1306,12 @@ async function main() {
|
|
|
1037
1306
|
case 'disable':
|
|
1038
1307
|
disable(args[1]);
|
|
1039
1308
|
break;
|
|
1040
|
-
case 'set-mode':
|
|
1041
|
-
setMode(args[1]);
|
|
1042
|
-
break;
|
|
1043
1309
|
case 'status':
|
|
1044
1310
|
status();
|
|
1045
1311
|
break;
|
|
1312
|
+
case 'analyze-diff':
|
|
1313
|
+
analyzeDiff(args.slice(1));
|
|
1314
|
+
break;
|
|
1046
1315
|
case 'help':
|
|
1047
1316
|
case '--help':
|
|
1048
1317
|
case '-h':
|
package/package.json
CHANGED
|
@@ -1,61 +1,56 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
⚠️ La respuesta debe ser un objeto JSON con esta estructura exacta de SonarQube:
|
|
1
|
+
ROLE:SeniorCodeReviewer,SonarQube-experienced
|
|
2
|
+
TASK:Analyze code->JSON only
|
|
3
|
+
CRITICAL:NoTextOutsideJSON,ValidJSON
|
|
6
4
|
|
|
5
|
+
OUTPUT_SCHEMA:
|
|
7
6
|
```json
|
|
8
7
|
{
|
|
9
|
-
"QUALITY_GATE":
|
|
10
|
-
"approved":
|
|
11
|
-
"score":
|
|
12
|
-
"metrics":
|
|
13
|
-
"reliability":
|
|
14
|
-
"security":
|
|
15
|
-
"maintainability":
|
|
16
|
-
"coverage":
|
|
17
|
-
"duplications":
|
|
18
|
-
"complexity":
|
|
8
|
+
"QUALITY_GATE":"PASSED|FAILED",
|
|
9
|
+
"approved":bool,
|
|
10
|
+
"score":1-10,
|
|
11
|
+
"metrics":{
|
|
12
|
+
"reliability":"A-E",
|
|
13
|
+
"security":"A-E",
|
|
14
|
+
"maintainability":"A-E",
|
|
15
|
+
"coverage":0-100,
|
|
16
|
+
"duplications":0-100,
|
|
17
|
+
"complexity":int
|
|
19
18
|
},
|
|
20
|
-
"issues":
|
|
21
|
-
"blocker":
|
|
22
|
-
"critical":
|
|
23
|
-
"major":
|
|
24
|
-
"minor":
|
|
25
|
-
"info":
|
|
19
|
+
"issues":{
|
|
20
|
+
"blocker":int,
|
|
21
|
+
"critical":int,
|
|
22
|
+
"major":int,
|
|
23
|
+
"minor":int,
|
|
24
|
+
"info":int
|
|
26
25
|
},
|
|
27
|
-
"details":
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"severity": "critical" // solo "blocker" o "critical" aquí
|
|
45
|
-
}
|
|
46
|
-
],
|
|
47
|
-
"securityHotspots": 0 // cantidad de security hotspots
|
|
26
|
+
"details":[{
|
|
27
|
+
"severity":"BLOCKER|CRITICAL|MAJOR|MINOR|INFO",
|
|
28
|
+
"type":"BUG|VULNERABILITY|CODE_SMELL",
|
|
29
|
+
"file":"path",
|
|
30
|
+
"line":int,
|
|
31
|
+
"method":"name",
|
|
32
|
+
"message":"desc",
|
|
33
|
+
"rule":"optional"
|
|
34
|
+
}],
|
|
35
|
+
"blockingIssues":[{
|
|
36
|
+
"description":"text",
|
|
37
|
+
"file":"path",
|
|
38
|
+
"line":int,
|
|
39
|
+
"method":"name",
|
|
40
|
+
"severity":"blocker|critical"
|
|
41
|
+
}],
|
|
42
|
+
"securityHotspots":int
|
|
48
43
|
}
|
|
49
44
|
```
|
|
50
45
|
|
|
51
|
-
|
|
52
|
-
- blockingIssues
|
|
53
|
-
- blockingIssues
|
|
54
|
-
-
|
|
55
|
-
-
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
46
|
+
RULES:
|
|
47
|
+
- blockingIssues=ALWAYS_ARRAY_OF_OBJECTS(never_strings)
|
|
48
|
+
- blockingIssues=ALL(details.severity∈{BLOCKER,CRITICAL})
|
|
49
|
+
- Each_blockingIssue_MUST_have:description,file,line,method,severity
|
|
50
|
+
- QUALITY_GATE=FAILED if(blocker>0||critical>0)
|
|
51
|
+
- details:ALWAYS_include:file,line,method
|
|
52
|
+
- ratings:A(0issues),B(1-2minor),C(1major|3-5minor),D(2+major|1critical),E(1+blocker|2+critical)
|
|
53
|
+
- IMPORTANT: @Autowired usage in Spring is NOT a BLOCKER/CRITICAL issue (max severity: MAJOR)
|
|
54
|
+
- Spring dependency injection patterns (@Autowired) should NOT block commits
|
|
60
55
|
|
|
61
|
-
|
|
56
|
+
ANALYZE_BELOW:
|
|
@@ -1,96 +1,41 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
##
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
###
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
Responde ÚNICAMENTE con un JSON válido siguiendo esta estructura:
|
|
43
|
-
|
|
44
|
-
```json
|
|
45
|
-
{
|
|
46
|
-
"QUALITY_GATE": "PASSED/FAILED",
|
|
47
|
-
"metrics": {
|
|
48
|
-
"reliability": "A/B/C/D/E",
|
|
49
|
-
"security": "A/B/C/D/E",
|
|
50
|
-
"maintainability": "A/B/C/D/E",
|
|
51
|
-
"coverage": 75,
|
|
52
|
-
"duplications": 5,
|
|
53
|
-
"complexity": 15
|
|
54
|
-
},
|
|
55
|
-
"issues": {
|
|
56
|
-
"blocker": 0,
|
|
57
|
-
"critical": 0,
|
|
58
|
-
"major": 0,
|
|
59
|
-
"minor": 0,
|
|
60
|
-
"info": 0
|
|
61
|
-
},
|
|
62
|
-
"details": [
|
|
63
|
-
{
|
|
64
|
-
"severity": "CRITICAL",
|
|
65
|
-
"type": "BUG/VULNERABILITY/CODE_SMELL",
|
|
66
|
-
"message": "Descripción del problema",
|
|
67
|
-
"file": "archivo.java",
|
|
68
|
-
"line": 42
|
|
69
|
-
}
|
|
70
|
-
],
|
|
71
|
-
"securityHotspots": 0,
|
|
72
|
-
"approved": true/false,
|
|
73
|
-
"blockingIssues": ["Lista de problemas bloqueantes si existen"]
|
|
74
|
-
}
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## Reglas para Quality Gate
|
|
78
|
-
|
|
79
|
-
- **PASSED** si:
|
|
80
|
-
- No hay issues BLOCKER
|
|
81
|
-
- Issues CRITICAL <= 1
|
|
82
|
-
- Todas las métricas son A o B
|
|
83
|
-
|
|
84
|
-
- **FAILED** si:
|
|
85
|
-
- Hay al menos 1 issue BLOCKER
|
|
86
|
-
- Issues CRITICAL > 1
|
|
87
|
-
- Alguna métrica es D o E
|
|
88
|
-
- Security hotspots > 0
|
|
89
|
-
|
|
90
|
-
## Rating de Métricas
|
|
91
|
-
|
|
92
|
-
- **A**: Excelente (0 issues)
|
|
93
|
-
- **B**: Bueno (1-2 issues minor)
|
|
94
|
-
- **C**: Aceptable (1 issue major o 3-5 minor)
|
|
95
|
-
- **D**: Pobre (2+ major o 1 critical)
|
|
96
|
-
- **E**: Muy pobre (1+ blocker o 2+ critical)
|
|
1
|
+
# SONARQUBE_EVAL_CRITERIA
|
|
2
|
+
|
|
3
|
+
## EXCEPTIONS_AND_ALLOWED_PATTERNS
|
|
4
|
+
### Spring Framework Patterns (NOT blocking issues)
|
|
5
|
+
- @Autowired for dependency injection is ALLOWED and NOT a blocking issue
|
|
6
|
+
- Field injection with @Autowired is acceptable in Spring Boot projects
|
|
7
|
+
- Constructor injection is preferred but @Autowired is NOT a blocker
|
|
8
|
+
|
|
9
|
+
## METRICS
|
|
10
|
+
### Reliability
|
|
11
|
+
- bugs,runtime_failures,exception_handling,race_conditions
|
|
12
|
+
|
|
13
|
+
### Security
|
|
14
|
+
- vulns,exposed_creds,input_validation,injection
|
|
15
|
+
|
|
16
|
+
### Maintainability
|
|
17
|
+
- code_smells,cyclomatic_complexity,duplication,tech_debt
|
|
18
|
+
- NOTE: @Autowired usage is NOT considered a code smell for blocking purposes
|
|
19
|
+
|
|
20
|
+
### Additional
|
|
21
|
+
- coverage:estimated_test_coverage(0-100)
|
|
22
|
+
- duplications:estimated_duplication(0-100)
|
|
23
|
+
- complexity:avg_cyclomatic_complexity
|
|
24
|
+
|
|
25
|
+
## SEVERITIES
|
|
26
|
+
- BLOCKER:fix_immediately
|
|
27
|
+
- CRITICAL:fix_before_release
|
|
28
|
+
- MAJOR:fix_soon
|
|
29
|
+
- MINOR:fix_eventually
|
|
30
|
+
- INFO:optional_improvement
|
|
31
|
+
|
|
32
|
+
## QUALITY_GATE
|
|
33
|
+
PASSED:no_blockers AND critical<=1 AND all_metrics∈{A,B}
|
|
34
|
+
FAILED:blocker>=1 OR critical>1 OR any_metric∈{D,E} OR security_hotspots>0
|
|
35
|
+
|
|
36
|
+
## RATINGS
|
|
37
|
+
A:0_issues
|
|
38
|
+
B:1-2_minor
|
|
39
|
+
C:1_major|3-5_minor
|
|
40
|
+
D:2+_major|1_critical
|
|
41
|
+
E:1+_blocker|2+_critical
|
|
@@ -1,46 +1,33 @@
|
|
|
1
|
-
#
|
|
1
|
+
# FIX_CRITICAL_ISSUES
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Files analyzed: {{FILE_COUNT}}
|
|
10
|
-
Analysis mode: {{ANALYSIS_MODE}}
|
|
11
|
-
|
|
12
|
-
## Critical Issues to Resolve
|
|
3
|
+
CONTEXT:
|
|
4
|
+
repo:{{REPO_NAME}}
|
|
5
|
+
branch:{{BRANCH_NAME}}
|
|
6
|
+
commit:{{COMMIT_SHA}}
|
|
7
|
+
files:{{FILE_COUNT}}
|
|
8
|
+
mode:{{ANALYSIS_MODE}}
|
|
13
9
|
|
|
10
|
+
CRITICAL_ISSUES:
|
|
14
11
|
{{BLOCKING_ISSUES}}
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
AFFECTED_FILES:
|
|
18
14
|
{{FILE_CONTENTS}}
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
- Apply the minimal fix required
|
|
26
|
-
- Ensure the fix doesn't introduce new issues
|
|
27
|
-
|
|
28
|
-
2. Fix requirements:
|
|
29
|
-
- Make the smallest possible change to resolve the issue
|
|
30
|
-
- Maintain existing code style and conventions
|
|
31
|
-
- Don't refactor unrelated code
|
|
32
|
-
- Preserve all existing functionality
|
|
16
|
+
INSTRUCTIONS:
|
|
17
|
+
1.Navigate→exact_file:line
|
|
18
|
+
2.Apply_minimal_fix
|
|
19
|
+
3.Maintain_style/conventions
|
|
20
|
+
4.No_unrelated_refactoring
|
|
33
21
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- Explain each fix briefly (one line max)
|
|
22
|
+
OUTPUT:
|
|
23
|
+
- diff_format
|
|
24
|
+
- one_line_explanation_max
|
|
38
25
|
|
|
39
|
-
|
|
40
|
-
1.
|
|
41
|
-
2.
|
|
42
|
-
3.
|
|
43
|
-
4.
|
|
44
|
-
5.
|
|
26
|
+
PRIORITY:
|
|
27
|
+
1.security_vulns
|
|
28
|
+
2.null_ptr/runtime
|
|
29
|
+
3.logic_errors
|
|
30
|
+
4.performance
|
|
31
|
+
5.code_quality
|
|
45
32
|
|
|
46
|
-
|
|
33
|
+
START_FIXING:
|
|
File without changes
|
package/templates/pre-commit
CHANGED
|
@@ -109,7 +109,7 @@ 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}}
|
|
112
|
+
sed -i "s|{{ANALYSIS_MODE}}|sonar|g" "$RESOLUTION_FILE"
|
|
113
113
|
|
|
114
114
|
# Crear archivo temporal para issues formateados
|
|
115
115
|
local TEMP_ISSUES_FILE=$(mktemp)
|
|
@@ -149,65 +149,17 @@ generate_resolution_prompt() {
|
|
|
149
149
|
echo
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
#
|
|
153
|
-
|
|
154
|
-
if [ -n "$CLAUDE_ANALYSIS_MODE" ]; then
|
|
155
|
-
ANALYSIS_MODE="$CLAUDE_ANALYSIS_MODE"
|
|
156
|
-
elif [ -f ".claude-analysis-mode" ]; then
|
|
157
|
-
ANALYSIS_MODE=$(cat .claude-analysis-mode)
|
|
158
|
-
else
|
|
159
|
-
ANALYSIS_MODE=""
|
|
160
|
-
fi
|
|
152
|
+
# Usar siempre modo SonarQube
|
|
153
|
+
ANALYSIS_MODE="sonar"
|
|
161
154
|
|
|
162
|
-
#
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
155
|
+
# Configurar archivos para modo SonarQube
|
|
156
|
+
GUIDELINES_FILE=".claude/CLAUDE_PRE_COMMIT_SONAR.md"
|
|
157
|
+
PROMPT_TEMPLATE=".claude/CLAUDE_ANALYSIS_PROMPT_SONAR.md"
|
|
158
|
+
log "Usando modo de análisis: SonarQube"
|
|
166
159
|
|
|
167
|
-
#
|
|
168
|
-
if [
|
|
169
|
-
echo
|
|
170
|
-
echo "🔍 Selecciona el formato de análisis de código:"
|
|
171
|
-
echo "1) Estándar - Formato clásico con recomendaciones"
|
|
172
|
-
echo "2) SonarQube - Formato similar a SonarQube con métricas"
|
|
173
|
-
echo
|
|
174
|
-
|
|
175
|
-
# Intentar leer desde terminal
|
|
176
|
-
if [ -t 0 ] && [ -c /dev/tty ]; then
|
|
177
|
-
read -p "Opción (1 o 2): " -n 1 -r </dev/tty
|
|
178
|
-
echo
|
|
179
|
-
else
|
|
180
|
-
# Si no hay terminal interactiva, usar modo estándar por defecto
|
|
181
|
-
echo "No se puede leer input interactivo, usando modo estándar por defecto"
|
|
182
|
-
REPLY="1"
|
|
183
|
-
fi
|
|
184
|
-
|
|
185
|
-
if [ "$REPLY" = "2" ]; then
|
|
186
|
-
ANALYSIS_MODE="sonar"
|
|
187
|
-
GUIDELINES_FILE=".claude/CLAUDE_PRE_COMMIT_SONAR.md"
|
|
188
|
-
PROMPT_TEMPLATE=".claude/CLAUDE_ANALYSIS_PROMPT_SONAR.md"
|
|
189
|
-
else
|
|
190
|
-
ANALYSIS_MODE="standard"
|
|
191
|
-
GUIDELINES_FILE=".claude/CLAUDE_PRE_COMMIT.md"
|
|
192
|
-
PROMPT_TEMPLATE=".claude/CLAUDE_ANALYSIS_PROMPT.md"
|
|
193
|
-
fi
|
|
194
|
-
|
|
195
|
-
# Guardar preferencia para futuros commits
|
|
196
|
-
echo "$ANALYSIS_MODE" > .claude-analysis-mode
|
|
197
|
-
echo -e "${GREEN}✓ Modo $ANALYSIS_MODE guardado para futuros commits${NC}"
|
|
198
|
-
echo -e "${YELLOW}Tip: Para cambiar el modo, ejecuta: .git/hooks/pre-commit --change-mode${NC}"
|
|
199
|
-
echo
|
|
200
|
-
else
|
|
201
|
-
# Usar modo guardado
|
|
202
|
-
if [ "$ANALYSIS_MODE" = "sonar" ]; then
|
|
203
|
-
GUIDELINES_FILE=".claude/CLAUDE_PRE_COMMIT_SONAR.md"
|
|
204
|
-
PROMPT_TEMPLATE=".claude/CLAUDE_ANALYSIS_PROMPT_SONAR.md"
|
|
205
|
-
log "Usando modo de análisis: SonarQube"
|
|
206
|
-
else
|
|
207
|
-
GUIDELINES_FILE=".claude/CLAUDE_PRE_COMMIT.md"
|
|
208
|
-
PROMPT_TEMPLATE=".claude/CLAUDE_ANALYSIS_PROMPT.md"
|
|
209
|
-
log "Usando modo de análisis: Estándar"
|
|
210
|
-
fi
|
|
160
|
+
# Guardar preferencia si no existe
|
|
161
|
+
if [ ! -f ".claude-analysis-mode" ]; then
|
|
162
|
+
echo "sonar" > .claude-analysis-mode
|
|
211
163
|
fi
|
|
212
164
|
|
|
213
165
|
# Verificar que el template de prompt existe
|
|
@@ -254,6 +206,46 @@ fi
|
|
|
254
206
|
|
|
255
207
|
log "Archivos Java/config a revisar: $(echo "$JAVA_FILES" | wc -l)"
|
|
256
208
|
|
|
209
|
+
# Función para filtrar contenido con SKIP-ANALYSIS
|
|
210
|
+
filter_skip_analysis() {
|
|
211
|
+
local file_content="$1"
|
|
212
|
+
local filtered_content=""
|
|
213
|
+
local skip_next_line=false
|
|
214
|
+
local inside_skip_block=false
|
|
215
|
+
|
|
216
|
+
while IFS= read -r line; do
|
|
217
|
+
# Detectar inicio de bloque SKIP-ANALYSIS
|
|
218
|
+
if echo "$line" | grep -q "// SKIP-ANALYSIS"; then
|
|
219
|
+
if [ "$inside_skip_block" = true ]; then
|
|
220
|
+
# Fin del bloque
|
|
221
|
+
inside_skip_block=false
|
|
222
|
+
else
|
|
223
|
+
# Inicio del bloque o línea única
|
|
224
|
+
inside_skip_block=true
|
|
225
|
+
skip_next_line=true
|
|
226
|
+
fi
|
|
227
|
+
continue
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
# Si estamos dentro de un bloque, saltar la línea
|
|
231
|
+
if [ "$inside_skip_block" = true ]; then
|
|
232
|
+
continue
|
|
233
|
+
fi
|
|
234
|
+
|
|
235
|
+
# Si debemos saltar la siguiente línea (comentario único)
|
|
236
|
+
if [ "$skip_next_line" = true ]; then
|
|
237
|
+
skip_next_line=false
|
|
238
|
+
inside_skip_block=false
|
|
239
|
+
continue
|
|
240
|
+
fi
|
|
241
|
+
|
|
242
|
+
# Agregar línea al contenido filtrado
|
|
243
|
+
filtered_content="${filtered_content}${line}"$'\n'
|
|
244
|
+
done <<< "$file_content"
|
|
245
|
+
|
|
246
|
+
echo "$filtered_content"
|
|
247
|
+
}
|
|
248
|
+
|
|
257
249
|
# Construir el prompt para análisis de código
|
|
258
250
|
PROMPT_FILE="$TEMP_DIR/code_review_prompt.txt"
|
|
259
251
|
|
|
@@ -278,14 +270,20 @@ for FILE in $JAVA_FILES; do
|
|
|
278
270
|
|
|
279
271
|
echo -e "\n--- Archivo: $FILE ---" >> "$PROMPT_FILE"
|
|
280
272
|
|
|
281
|
-
#
|
|
273
|
+
# Obtener el diff y filtrarlo
|
|
274
|
+
DIFF_CONTENT=$(git diff --cached "$FILE" 2>/dev/null || echo "No se pudo obtener diff")
|
|
275
|
+
FILTERED_DIFF=$(filter_skip_analysis "$DIFF_CONTENT")
|
|
276
|
+
|
|
277
|
+
# Mostrar el diff filtrado del archivo
|
|
282
278
|
echo -e "\nDiff:" >> "$PROMPT_FILE"
|
|
283
|
-
|
|
279
|
+
echo "$FILTERED_DIFF" >> "$PROMPT_FILE"
|
|
284
280
|
|
|
285
|
-
# Si es un archivo nuevo, mostrar contenido completo
|
|
281
|
+
# Si es un archivo nuevo, mostrar contenido completo filtrado
|
|
286
282
|
if git diff --cached --name-status | grep "^A.*$FILE" > /dev/null; then
|
|
287
283
|
echo -e "\nContenido completo (archivo nuevo):" >> "$PROMPT_FILE"
|
|
288
|
-
git show ":$FILE"
|
|
284
|
+
FILE_CONTENT=$(git show ":$FILE" 2>/dev/null || cat "$FILE")
|
|
285
|
+
FILTERED_CONTENT=$(filter_skip_analysis "$FILE_CONTENT")
|
|
286
|
+
echo "$FILTERED_CONTENT" >> "$PROMPT_FILE"
|
|
289
287
|
fi
|
|
290
288
|
|
|
291
289
|
FILE_COUNT=$((FILE_COUNT + 1))
|
|
File without changes
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
Eres un revisor de código senior con experiencia en proyectos Spring Boot empresariales.
|
|
2
|
-
|
|
3
|
-
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.
|
|
4
|
-
|
|
5
|
-
⚠️ La respuesta debe ser un objeto JSON con esta estructura exacta:
|
|
6
|
-
|
|
7
|
-
```json
|
|
8
|
-
{
|
|
9
|
-
"approved": true, // true si el commit puede aceptarse, false si debe bloquearse
|
|
10
|
-
"score": 1-10, // calificación de calidad (entero)
|
|
11
|
-
"blockingIssues": [ // SIEMPRE array de objetos, nunca strings
|
|
12
|
-
{
|
|
13
|
-
"description": "texto descriptivo del problema",
|
|
14
|
-
"file": "path/to/file.java",
|
|
15
|
-
"line": 123, // línea donde se detectó el problema
|
|
16
|
-
"method": "methodName", // método o clase afectada
|
|
17
|
-
"severity": "critical" // critical, high, medium, low
|
|
18
|
-
}
|
|
19
|
-
],
|
|
20
|
-
"recommendations": [ // sugerencias no bloqueantes (array de strings)
|
|
21
|
-
"mejora 1",
|
|
22
|
-
"mejora 2"
|
|
23
|
-
],
|
|
24
|
-
"details": {
|
|
25
|
-
"security": [ // comentarios específicos por área
|
|
26
|
-
"observación sobre seguridad"
|
|
27
|
-
],
|
|
28
|
-
"architecture": [ "..." ],
|
|
29
|
-
"performance": [ "..." ],
|
|
30
|
-
"maintainability": [ "..." ]
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
✅ Importante:
|
|
36
|
-
- blockingIssues SIEMPRE debe ser un array de objetos, NUNCA un array de strings
|
|
37
|
-
- Cada blockingIssue DEBE incluir: description, file, line, method, severity
|
|
38
|
-
- Usa listas vacías si no hay issues (ejemplo: `"blockingIssues": []`)
|
|
39
|
-
- No uses texto introductorio o explicaciones fuera del JSON
|
|
40
|
-
- No omitas claves aunque estén vacías
|
|
41
|
-
- Sé directo, claro y profesional en tus observaciones
|
|
42
|
-
- Los problemas críticos deben incluir contexto suficiente para que otra IA pueda resolverlos
|
|
43
|
-
|
|
44
|
-
A continuación se detallan las pautas y los cambios:
|
|
@@ -1,63 +0,0 @@
|
|
|
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
|
-
"details": "Información adicional detallada (opcional)"
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## Reglas de Aprobación
|
|
53
|
-
|
|
54
|
-
- **approved = true** si score >= 7 Y no hay blockingIssues
|
|
55
|
-
- **approved = false** si score < 7 O hay blockingIssues
|
|
56
|
-
|
|
57
|
-
## Problemas que SIEMPRE son Blocking Issues
|
|
58
|
-
|
|
59
|
-
1. Credenciales o tokens hardcodeados
|
|
60
|
-
2. Vulnerabilidades de seguridad evidentes
|
|
61
|
-
3. Código que podría causar pérdida de datos
|
|
62
|
-
4. Errores de sintaxis o código que no compilaría
|
|
63
|
-
5. Eliminación de tests sin justificación
|