claude-git-hooks 1.4.2 → 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 CHANGED
@@ -5,6 +5,32 @@ 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
+
8
34
  ## [1.4.2] - 2025-09-08
9
35
 
10
36
  ### Added
package/LICENSE CHANGED
File without changes
package/README.md CHANGED
@@ -201,7 +201,7 @@ 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
- - **Configuración persistente**: Guarda preferencias en `.claude-analysis-mode`
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
@@ -252,55 +252,27 @@ public void methodWithKnownIssues() {
252
252
 
253
253
  **Nota**: El código entre comentarios `SKIP-ANALYSIS` no será enviado a Claude para su análisis.
254
254
 
255
- ### Modos de análisis 📊
255
+ ### Análisis de diferencias entre ramas 🔍
256
256
 
257
- Puedes elegir entre dos formatos de análisis:
258
-
259
- #### 🎯 Configurar Modo (Recomendado)
257
+ Nueva funcionalidad para analizar cambios y generar información para Pull Requests:
260
258
 
261
259
  ```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
260
+ # Analizar diferencias con la rama main
261
+ claude-hooks analyze-diff
267
262
 
268
- # Ver modo actual
269
- claude-hooks status
263
+ # Analizar diferencias con otra rama base
264
+ claude-hooks analyze-diff develop
270
265
  ```
271
266
 
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**:
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
286
274
 
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
- ```
275
+ Los resultados se guardan en `.claude-pr-analysis.json` para fácil acceso.
304
276
 
305
277
  ### Modo debug
306
278
 
@@ -440,12 +412,6 @@ claude-hooks status
440
412
 
441
413
  ### Variables de entorno disponibles
442
414
 
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
415
  - **`DEBUG`**: Activa el modo debug
450
416
  - Valores: `1` para activar
451
417
  - Ejemplo: `DEBUG=1 git commit -m "mensaje"`
package/bin/claude-hooks CHANGED
@@ -640,7 +640,8 @@ function updateGitignore() {
640
640
  '.claude/',
641
641
  '.claude-analysis-mode',
642
642
  'debug-claude-response.json',
643
- 'claude_resolution_prompt.md'
643
+ 'claude_resolution_prompt.md',
644
+ '.claude-pr-analysis.json',
644
645
  ];
645
646
 
646
647
  let gitignoreContent = '';
@@ -794,6 +795,258 @@ function disable(hookName) {
794
795
  });
795
796
  }
796
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
+
797
1050
  // Comando status
798
1051
  function status() {
799
1052
  if (!checkGitRepo()) {
@@ -993,8 +1246,8 @@ Comandos:
993
1246
  uninstall Desinstala los hooks del repositorio
994
1247
  enable [hook] Habilita hooks (todos o uno específico)
995
1248
  disable [hook] Deshabilita hooks (todos o uno específico)
996
- set-mode [standard|sonar] Cambia el modo de análisis
997
1249
  status Muestra el estado de los hooks
1250
+ analyze-diff [base] Analiza diferencias entre ramas y genera PR info
998
1251
  help Muestra esta ayuda
999
1252
 
1000
1253
  Hooks disponibles:
@@ -1005,11 +1258,10 @@ Ejemplos:
1005
1258
  claude-hooks install # Instala todos los hooks
1006
1259
  claude-hooks install --skip-auth # Instala sin verificar autenticación
1007
1260
  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
1261
  claude-hooks disable pre-commit # Deshabilita solo pre-commit
1011
1262
  claude-hooks enable # Habilita todos los hooks
1012
1263
  claude-hooks status # Ver estado actual
1264
+ claude-hooks analyze-diff main # Analiza diferencias con main
1013
1265
 
1014
1266
  Casos de uso de commits:
1015
1267
  git commit -m "mensaje" # Mensaje manual + análisis bloqueante
@@ -1017,6 +1269,12 @@ Casos de uso de commits:
1017
1269
  git commit --no-verify -m "auto" # Mensaje automático sin análisis
1018
1270
  git commit --no-verify -m "msg" # Mensaje manual sin análisis
1019
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
+
1020
1278
  Excluir código del análisis:
1021
1279
  // SKIP-ANALYSIS # Excluye la siguiente línea del análisis
1022
1280
  // SKIP-ANALYSIS # Entre dos comentarios excluye el bloque
@@ -1048,12 +1306,12 @@ async function main() {
1048
1306
  case 'disable':
1049
1307
  disable(args[1]);
1050
1308
  break;
1051
- case 'set-mode':
1052
- setMode(args[1]);
1053
- break;
1054
1309
  case 'status':
1055
1310
  status();
1056
1311
  break;
1312
+ case 'analyze-diff':
1313
+ analyzeDiff(args.slice(1));
1314
+ break;
1057
1315
  case 'help':
1058
1316
  case '--help':
1059
1317
  case '-h':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-git-hooks",
3
- "version": "1.4.2",
3
+ "version": "1.5.0",
4
4
  "description": "Git hooks con Claude CLI para análisis de código y generación automática de mensajes de commit",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes