claude-git-hooks 1.5.1 → 1.5.2

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/bin/claude-hooks CHANGED
@@ -7,24 +7,24 @@ const os = require('os');
7
7
  const readline = require('readline');
8
8
  const https = require('https');
9
9
 
10
- // Función para obtener la última versión desde NPM
10
+ // Function to get the latest version from NPM
11
11
  function getLatestVersion(packageName) {
12
12
  return new Promise((resolve, reject) => {
13
- // Usar la API principal de NPM, no /latest
13
+ // Use the main NPM API, not /latest
14
14
  https.get(`https://registry.npmjs.org/${packageName}`, (res) => {
15
15
  let data = '';
16
16
  res.on('data', chunk => data += chunk);
17
17
  res.on('end', () => {
18
18
  try {
19
19
  const json = JSON.parse(data);
20
- // Obtener la versión del tag 'latest'
20
+ // Get the version from the 'latest' tag
21
21
  if (json['dist-tags'] && json['dist-tags'].latest) {
22
22
  resolve(json['dist-tags'].latest);
23
23
  } else {
24
- reject(new Error('No se pudo obtener la versión'));
24
+ reject(new Error('Could not get the version'));
25
25
  }
26
26
  } catch (e) {
27
- // Si falla, intentar con npm view
27
+ // If it fails, try with npm view
28
28
  try {
29
29
  const version = execSync(`npm view ${packageName} version`, { encoding: 'utf8' }).trim();
30
30
  resolve(version);
@@ -37,57 +37,57 @@ function getLatestVersion(packageName) {
37
37
  });
38
38
  }
39
39
 
40
- // Función para verificar versión (usada por los hooks)
40
+ // Function to check version (used by hooks)
41
41
  async function checkVersionAndPromptUpdate() {
42
42
  try {
43
43
  const currentVersion = require('../package.json').version;
44
44
  const latestVersion = await getLatestVersion('claude-git-hooks');
45
45
 
46
46
  if (currentVersion === latestVersion) {
47
- return true; // Ya actualizado
47
+ return true; // Already updated
48
48
  }
49
49
 
50
50
  console.log('');
51
- warning(`Nueva versión disponible: ${latestVersion} (actual: ${currentVersion})`);
51
+ warning(`New version available: ${latestVersion} (current: ${currentVersion})`);
52
52
 
53
- // Prompt interactivo compatible con todas las consolas
53
+ // Interactive prompt compatible with all consoles
54
54
  const rl = readline.createInterface({
55
55
  input: process.stdin,
56
56
  output: process.stdout
57
57
  });
58
58
 
59
59
  return new Promise((resolve) => {
60
- rl.question('¿Deseas actualizar ahora? (s/n): ', (answer) => {
60
+ rl.question('Do you want to update now? (y/n): ', (answer) => {
61
61
  rl.close();
62
62
 
63
- if (answer.toLowerCase() === 's' || answer.toLowerCase() === 'si' || answer.toLowerCase() === 'sí' || answer.toLowerCase() === 'yes' || answer.toLowerCase() === 'y') {
64
- info('Actualizando claude-git-hooks...');
63
+ if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
64
+ info('Updating claude-git-hooks...');
65
65
  try {
66
66
  execSync('npm install -g claude-git-hooks@latest', { stdio: 'inherit' });
67
- success('Actualización completada. Por favor, vuelve a ejecutar tu comando.');
68
- process.exit(0); // Salir para que el usuario reinicie el proceso
67
+ success('Update completed. Please run your command again.');
68
+ process.exit(0); // Exit so user restarts the process
69
69
  } catch (e) {
70
- error('Error al actualizar: ' + e.message);
70
+ error('Error updating: ' + e.message);
71
71
  resolve(false);
72
72
  }
73
73
  } else {
74
- info('Actualización pospuesta. Puedes actualizar más tarde con: claude-hooks update');
75
- resolve(true); // Continuar sin actualizar
74
+ info('Update postponed. You can update later with: claude-hooks update');
75
+ resolve(true); // Continue without updating
76
76
  }
77
77
  });
78
78
  });
79
79
  } catch (e) {
80
- // Si hay error verificando la versión, continuar sin bloquear
80
+ // If there's an error checking version, continue without blocking
81
81
  return true;
82
82
  }
83
83
  }
84
84
 
85
- // Exportar para uso en hooks
85
+ // Export for use in hooks
86
86
  if (typeof module !== 'undefined' && module.exports) {
87
87
  module.exports = { checkVersionAndPromptUpdate };
88
88
  }
89
89
 
90
- // Colores para output
90
+ // Colors for output
91
91
  const colors = {
92
92
  reset: '\x1b[0m',
93
93
  red: '\x1b[31m',
@@ -117,7 +117,7 @@ function warning(message) {
117
117
  log(`⚠️ ${message}`, 'yellow');
118
118
  }
119
119
 
120
- // Función para leer contraseña de forma segura
120
+ // Function to read password securely
121
121
  function readPassword(prompt) {
122
122
  return new Promise((resolve) => {
123
123
  const rl = readline.createInterface({
@@ -125,7 +125,7 @@ function readPassword(prompt) {
125
125
  output: process.stdout
126
126
  });
127
127
 
128
- // Deshabilitar echo
128
+ // Disable echo
129
129
  rl.stdoutMuted = true;
130
130
  rl._writeToOutput = function _writeToOutput(stringToWrite) {
131
131
  if (rl.stdoutMuted)
@@ -136,13 +136,13 @@ function readPassword(prompt) {
136
136
 
137
137
  rl.question(prompt, (password) => {
138
138
  rl.close();
139
- console.log(); // Nueva línea
139
+ console.log(); // New line
140
140
  resolve(password);
141
141
  });
142
142
  });
143
143
  }
144
144
 
145
- // Verificar si la contraseña sudo es correcta
145
+ // Check if sudo password is correct
146
146
  function testSudoPassword(password) {
147
147
  try {
148
148
  execSync('echo "' + password + '" | sudo -S true', {
@@ -155,7 +155,7 @@ function testSudoPassword(password) {
155
155
  }
156
156
  }
157
157
 
158
- // Instalar paquete con sudo automático
158
+ // Install package with automatic sudo
159
159
  function installPackage(packageName, sudoPassword = null) {
160
160
  try {
161
161
  if (sudoPassword) {
@@ -181,22 +181,22 @@ function installPackage(packageName, sudoPassword = null) {
181
181
  }
182
182
  }
183
183
 
184
- // Sistema de entretenimiento
184
+ // Entertainment system
185
185
  class Entertainment {
186
186
  static jokes = [
187
- "¿Por qué los programadores prefieren el modo oscuro? Porque la luz atrae bugs!",
188
- "Un QA entra a un bar. Pide 1 cerveza. Pide 0 cervezas. Pide -1 cervezas.",
189
- "¿Cuál es el lenguaje favorito de los piratas? R!",
190
- "Hay 10 tipos de personas: las que entienden binario y las que no.",
191
- "¿Por qué los programadores confunden Halloween con Navidad? Porque Oct 31 = Dec 25",
192
- "¿Qué le dice un bit al otro? Nos vemos en el bus!",
193
- "¿Por qué Java y C++ no se llevan bien? Porque tienen diferentes puntos de vista sobre los punteros.",
194
- "Mi código no tiene bugs, solo features no documentadas."
187
+ "Why do programmers prefer dark mode? Because light attracts bugs!",
188
+ "A QA engineer walks into a bar. Orders 1 beer. Orders 0 beers. Orders -1 beers.",
189
+ "What's a pirate's favorite programming language? R!",
190
+ "There are 10 types of people: those who understand binary and those who don't.",
191
+ "Why do programmers confuse Halloween with Christmas? Because Oct 31 = Dec 25",
192
+ "What does one bit say to another? See you on the bus!",
193
+ "Why don't Java and C++ get along? Because they have different views on pointers.",
194
+ "My code doesn't have bugs, just undocumented features."
195
195
  ];
196
196
 
197
197
  static async getJoke() {
198
198
  return new Promise((resolve) => {
199
- // Intentar obtener chiste de API
199
+ // Try to get joke from API
200
200
  const req = https.get('https://icanhazdadjoke.com/', {
201
201
  headers: { 'Accept': 'text/plain' },
202
202
  timeout: 3000
@@ -207,7 +207,7 @@ class Entertainment {
207
207
  });
208
208
 
209
209
  req.on('error', () => {
210
- // Si falla, usar chiste local
210
+ // If it fails, use local joke
211
211
  const randomJoke = this.jokes[Math.floor(Math.random() * this.jokes.length)];
212
212
  resolve(randomJoke);
213
213
  });
@@ -220,7 +220,7 @@ class Entertainment {
220
220
  });
221
221
  }
222
222
 
223
- static async showSpinner(promise, message = 'Procesando') {
223
+ static async showSpinner(promise, message = 'Processing') {
224
224
  const spinners = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
225
225
  let spinnerIndex = 0;
226
226
  let jokeCountdown = 10;
@@ -228,15 +228,15 @@ class Entertainment {
228
228
  let isFinished = false;
229
229
  let isFirstRender = true;
230
230
 
231
- // Obtener primer chiste de la API sin bloquear
231
+ // Get first joke from API without blocking
232
232
  this.getJoke().then(joke => {
233
233
  if (!isFinished) currentJoke = joke;
234
- }).catch(() => {}); // Si falla, mantener el local
234
+ }).catch(() => {}); // If it fails, keep the local one
235
235
 
236
- // Ocultar cursor
236
+ // Hide cursor
237
237
  process.stdout.write('\x1B[?25l');
238
238
 
239
- // Reservar espacio para las 3 líneas
239
+ // Reserve space for the 3 lines
240
240
  process.stdout.write('\n\n\n');
241
241
 
242
242
  const interval = setInterval(() => {
@@ -247,11 +247,11 @@ class Entertainment {
247
247
 
248
248
  spinnerIndex++;
249
249
 
250
- // Actualizar countdown cada segundo (10 iteraciones de 100ms)
250
+ // Update countdown every second (10 iterations of 100ms)
251
251
  if (spinnerIndex % 10 === 0) {
252
252
  jokeCountdown--;
253
253
 
254
- // Renovar chiste cada 10 segundos
254
+ // Refresh joke every 10 seconds
255
255
  if (jokeCountdown <= 0) {
256
256
  this.getJoke().then(joke => {
257
257
  if (!isFinished) currentJoke = joke;
@@ -264,20 +264,20 @@ class Entertainment {
264
264
  }
265
265
  }
266
266
 
267
- // Siempre volver exactamente 3 líneas arriba
267
+ // Always go back exactly 3 lines up
268
268
  process.stdout.write('\x1B[3A');
269
269
 
270
- // Renderizar las 3 líneas desde el inicio
270
+ // Render the 3 lines from the beginning
271
271
  const spinner = spinners[spinnerIndex % spinners.length];
272
272
 
273
- // Línea 1: Spinner
273
+ // Line 1: Spinner
274
274
  process.stdout.write('\r\x1B[2K' + `${colors.yellow}${spinner} ${message}${colors.reset}\n`);
275
275
 
276
- // Línea 2: Chiste
276
+ // Line 2: Joke
277
277
  process.stdout.write('\r\x1B[2K' + `${colors.green}🎭 ${currentJoke}${colors.reset}\n`);
278
278
 
279
- // Línea 3: Countdown
280
- process.stdout.write('\r\x1B[2K' + `${colors.yellow}⏱️ Próximo chiste en: ${jokeCountdown}s${colors.reset}\n`);
279
+ // Line 3: Countdown
280
+ process.stdout.write('\r\x1B[2K' + `${colors.yellow}⏱️ Next joke in: ${jokeCountdown}s${colors.reset}\n`);
281
281
  }, 100);
282
282
 
283
283
  try {
@@ -285,15 +285,15 @@ class Entertainment {
285
285
  isFinished = true;
286
286
  clearInterval(interval);
287
287
 
288
- // Limpiar exactamente 3 líneas completamente
289
- process.stdout.write('\x1B[3A'); // Subir 3 líneas
290
- process.stdout.write('\r\x1B[2K'); // Limpiar línea 1
291
- process.stdout.write('\n\r\x1B[2K'); // Bajar y limpiar línea 2
292
- process.stdout.write('\n\r\x1B[2K'); // Bajar y limpiar línea 3
293
- process.stdout.write('\x1B[2A'); // Subir 2 líneas para quedar en la primera
294
- process.stdout.write('\r'); // Ir al inicio de línea
288
+ // Clean exactly 3 lines completely
289
+ process.stdout.write('\x1B[3A'); // Go up 3 lines
290
+ process.stdout.write('\r\x1B[2K'); // Clean line 1
291
+ process.stdout.write('\n\r\x1B[2K'); // Go down and clean line 2
292
+ process.stdout.write('\n\r\x1B[2K'); // Go down and clean line 3
293
+ process.stdout.write('\x1B[2A'); // Go up 2 lines to end up on the first
294
+ process.stdout.write('\r'); // Go to beginning of line
295
295
 
296
- // Mostrar cursor
296
+ // Show cursor
297
297
  process.stdout.write('\x1B[?25h');
298
298
 
299
299
  return result;
@@ -301,15 +301,15 @@ class Entertainment {
301
301
  isFinished = true;
302
302
  clearInterval(interval);
303
303
 
304
- // Limpiar exactamente 3 líneas completamente
305
- process.stdout.write('\x1B[3A'); // Subir 3 líneas
306
- process.stdout.write('\r\x1B[2K'); // Limpiar línea 1
307
- process.stdout.write('\n\r\x1B[2K'); // Bajar y limpiar línea 2
308
- process.stdout.write('\n\r\x1B[2K'); // Bajar y limpiar línea 3
309
- process.stdout.write('\x1B[2A'); // Subir 2 líneas para quedar en la primera
310
- process.stdout.write('\r'); // Ir al inicio de línea
304
+ // Clean exactly 3 lines completely
305
+ process.stdout.write('\x1B[3A'); // Go up 3 lines
306
+ process.stdout.write('\r\x1B[2K'); // Clean line 1
307
+ process.stdout.write('\n\r\x1B[2K'); // Go down and clean line 2
308
+ process.stdout.write('\n\r\x1B[2K'); // Go down and clean line 3
309
+ process.stdout.write('\x1B[2A'); // Go up 2 lines to end up on the first
310
+ process.stdout.write('\r'); // Go to beginning of line
311
311
 
312
- // Mostrar cursor
312
+ // Show cursor
313
313
  process.stdout.write('\x1B[?25h');
314
314
 
315
315
  throw error;
@@ -317,7 +317,7 @@ class Entertainment {
317
317
  }
318
318
  }
319
319
 
320
- // Verificar si estamos en un repositorio git
320
+ // Check if we are in a git repository
321
321
  function checkGitRepo() {
322
322
  try {
323
323
  execSync('git rev-parse --git-dir', { stdio: 'ignore' });
@@ -327,92 +327,92 @@ function checkGitRepo() {
327
327
  }
328
328
  }
329
329
 
330
- // Obtener la ruta de los templates
330
+ // Get the templates path
331
331
  function getTemplatesPath() {
332
332
  return path.join(__dirname, '..', 'templates');
333
333
  }
334
334
 
335
- // Comando install
335
+ // Install command
336
336
  async function install(args) {
337
337
  if (!checkGitRepo()) {
338
- error('No estás en un repositorio Git. Por favor, ejecuta este comando desde la raíz de un repositorio.');
338
+ error('You are not in a Git repository. Please run this command from the root of a repository.');
339
339
  }
340
340
 
341
341
  const isForce = args.includes('--force');
342
342
  const skipAuth = args.includes('--skip-auth');
343
343
 
344
344
  if (isForce) {
345
- info('Instalando Claude Git Hooks (modo force)...');
345
+ info('Installing Claude Git Hooks (force mode)...');
346
346
  } else {
347
- info('Instalando Claude Git Hooks...');
347
+ info('Installing Claude Git Hooks...');
348
348
  }
349
349
 
350
- // Solicitar contraseña sudo al inicio si es necesario
350
+ // Request sudo password at the beginning if necessary
351
351
  let sudoPassword = null;
352
352
  if (os.platform() === 'linux') {
353
353
  const needsInstall = await checkIfInstallationNeeded();
354
354
  if (needsInstall) {
355
- info('Para la instalación automática de dependencias se necesita acceso sudo, por favor ingrese contraseña');
356
- sudoPassword = await readPassword('Introduce tu contraseña de Ubuntu para sudo: ');
355
+ info('Sudo access is needed for automatic dependency installation, please enter password');
356
+ sudoPassword = await readPassword('Enter your Ubuntu password for sudo: ');
357
357
 
358
358
  if (sudoPassword && !testSudoPassword(sudoPassword)) {
359
- warning('Contraseña incorrecta. Continuando sin instalación automática.');
359
+ warning('Incorrect password. Continuing without automatic installation.');
360
360
  sudoPassword = null;
361
361
  } else if (sudoPassword) {
362
- success('Contraseña verificada. Procediendo con instalación automática.');
362
+ success('Password verified. Proceeding with automatic installation.');
363
363
  }
364
364
  }
365
365
  }
366
366
 
367
- // Verificar dependencias con instalación automática
367
+ // Check dependencies with automatic installation
368
368
  await checkAndInstallDependencies(sudoPassword, skipAuth);
369
369
 
370
370
  const templatesPath = getTemplatesPath();
371
371
  const hooksPath = '.git/hooks';
372
372
 
373
- // Crear directorio hooks si no existe
373
+ // Create hooks directory if it doesn't exist
374
374
  if (!fs.existsSync(hooksPath)) {
375
375
  fs.mkdirSync(hooksPath, { recursive: true });
376
376
  }
377
377
 
378
- // Hooks a instalar
378
+ // Hooks to install
379
379
  const hooks = ['pre-commit', 'prepare-commit-msg'];
380
380
 
381
381
  hooks.forEach(hook => {
382
382
  const sourcePath = path.join(templatesPath, hook);
383
383
  const destPath = path.join(hooksPath, hook);
384
384
 
385
- // Hacer backup si existe
385
+ // Make backup if it exists
386
386
  if (fs.existsSync(destPath)) {
387
387
  const backupPath = `${destPath}.backup.${Date.now()}`;
388
388
  fs.copyFileSync(destPath, backupPath);
389
- info(`Backup creado: ${backupPath}`);
389
+ info(`Backup created: ${backupPath}`);
390
390
  }
391
391
 
392
- // Copiar hook
392
+ // Copy hook
393
393
  fs.copyFileSync(sourcePath, destPath);
394
394
  fs.chmodSync(destPath, '755');
395
- success(`${hook} instalado`);
395
+ success(`${hook} installed`);
396
396
  });
397
397
 
398
- // Copiar script de verificación de versión
398
+ // Copy version verification script
399
399
  const checkVersionSource = path.join(templatesPath, 'check-version.sh');
400
400
  const checkVersionDest = path.join(hooksPath, 'check-version.sh');
401
401
 
402
402
  if (fs.existsSync(checkVersionSource)) {
403
403
  fs.copyFileSync(checkVersionSource, checkVersionDest);
404
404
  fs.chmodSync(checkVersionDest, '755');
405
- success('Script de verificación de versión instalado');
405
+ success('Version verification script installed');
406
406
  }
407
407
 
408
- // Crear directorio .claude si no existe
408
+ // Create .claude directory if it doesn't exist
409
409
  const claudeDir = '.claude';
410
410
  if (!fs.existsSync(claudeDir)) {
411
411
  fs.mkdirSync(claudeDir, { recursive: true });
412
412
  success('.claude directory created');
413
413
  }
414
414
 
415
- // Copiar archivos de pautas y prompts a .claude
415
+ // Copy guidelines and prompts files to .claude
416
416
  const claudeFiles = [
417
417
  'CLAUDE_PRE_COMMIT_SONAR.md',
418
418
  'CLAUDE_ANALYSIS_PROMPT_SONAR.md',
@@ -423,101 +423,101 @@ async function install(args) {
423
423
  const destPath = path.join(claudeDir, file);
424
424
  const sourcePath = path.join(templatesPath, file);
425
425
 
426
- // En modo force o si no existe, copiar el archivo
426
+ // In force mode or if it doesn't exist, copy the file
427
427
  if (isForce || !fs.existsSync(destPath)) {
428
428
  if (fs.existsSync(sourcePath)) {
429
429
  fs.copyFileSync(sourcePath, destPath);
430
- success(`${file} instalado en .claude/`);
430
+ success(`${file} installed in .claude/`);
431
431
  } else {
432
- warning(`Archivo de template no encontrado: ${file}`);
432
+ warning(`Template file not found: ${file}`);
433
433
  }
434
434
  }
435
435
  });
436
436
 
437
- // Configurar Git
437
+ // Configure Git
438
438
  configureGit();
439
439
 
440
- // Actualizar .gitignore
440
+ // Update .gitignore
441
441
  updateGitignore();
442
442
 
443
- success('¡Claude Git Hooks instalado exitosamente! 🎉');
444
- console.log('\nUso:');
445
- console.log(' git commit -m "auto" # Genera mensaje automáticamente');
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_BLOCK # Excluye bloque hasta encontrar otro igual');
451
- console.log(' ...código excluido...');
443
+ success('Claude Git Hooks installed successfully! 🎉');
444
+ console.log('\nUsage:');
445
+ console.log(' git commit -m "auto" # Generate message automatically');
446
+ console.log(' git commit -m "message" # Analyze code before commit');
447
+ console.log(' git commit --no-verify # Skip analysis completely');
448
+ console.log('\nExclude code from analysis:');
449
+ console.log(' // SKIP-ANALYSIS # Exclude the next line');
450
+ console.log(' // SKIP_ANALYSIS_BLOCK # Exclude block until finding another equal one');
451
+ console.log(' ...excluded code...');
452
452
  console.log(' // SKIP_ANALYSIS_BLOCK');
453
- console.log('\nPara más opciones: claude-hooks --help');
453
+ console.log('\nFor more options: claude-hooks --help');
454
454
  }
455
455
 
456
- // Verificar dependencias completas (como setup-wsl.sh)
456
+ // Check complete dependencies (like setup-wsl.sh)
457
457
  async function checkAndInstallDependencies(sudoPassword = null, skipAuth = false) {
458
- info('Verificando dependencias del sistema...');
458
+ info('Checking system dependencies...');
459
459
 
460
- // Verificar Node.js
460
+ // Check Node.js
461
461
  try {
462
462
  const nodeVersion = execSync('node --version', { encoding: 'utf8' }).trim();
463
463
  success(`Node.js ${nodeVersion}`);
464
464
  } catch (e) {
465
- error('Node.js no está instalado. Instala Node.js e intenta de nuevo.');
465
+ error('Node.js is not installed. Install Node.js and try again.');
466
466
  }
467
467
 
468
- // Verificar npm
468
+ // Check npm
469
469
  try {
470
470
  const npmVersion = execSync('npm --version', { encoding: 'utf8' }).trim();
471
471
  success(`npm ${npmVersion}`);
472
472
  } catch (e) {
473
- error('npm no está instalado.');
473
+ error('npm is not installed.');
474
474
  }
475
475
 
476
- // Verificar e instalar jq
476
+ // Check and install jq
477
477
  try {
478
478
  const jqVersion = execSync('jq --version', { encoding: 'utf8' }).trim();
479
479
  success(`jq ${jqVersion}`);
480
480
  } catch (e) {
481
- warning('jq no está instalado. Instalando...');
481
+ warning('jq is not installed. Installing...');
482
482
  if (installPackage('jq', sudoPassword)) {
483
- success('jq instalado correctamente');
483
+ success('jq installed successfully');
484
484
  } else {
485
- warning('No se pudo instalar jq automáticamente');
485
+ warning('Could not install jq automatically');
486
486
  if (os.platform() === 'linux') {
487
- console.log('Instálalo manualmente con: sudo apt install jq');
487
+ console.log('Install it manually with: sudo apt install jq');
488
488
  } else if (os.platform() === 'darwin') {
489
- console.log('Instálalo manualmente con: brew install jq');
489
+ console.log('Install it manually with: brew install jq');
490
490
  }
491
491
  }
492
492
  }
493
493
 
494
- // Verificar e instalar curl
494
+ // Check and install curl
495
495
  try {
496
496
  const curlVersion = execSync('curl --version', { encoding: 'utf8' }).split('\n')[0];
497
497
  success(`curl ${curlVersion.split(' ')[1]}`);
498
498
  } catch (e) {
499
- warning('curl no está instalado. Instalando...');
499
+ warning('curl is not installed. Installing...');
500
500
  if (installPackage('curl', sudoPassword)) {
501
- success('curl instalado correctamente');
501
+ success('curl installed successfully');
502
502
  } else {
503
- warning('No se pudo instalar curl automáticamente');
503
+ warning('Could not install curl automatically');
504
504
  if (os.platform() === 'linux') {
505
- console.log('Instálalo manualmente con: sudo apt install curl');
505
+ console.log('Install it manually with: sudo apt install curl');
506
506
  } else if (os.platform() === 'darwin') {
507
- console.log('Instálalo manualmente con: brew install curl');
507
+ console.log('Install it manually with: brew install curl');
508
508
  }
509
509
  }
510
510
  }
511
511
 
512
- // Verificar Git
512
+ // Check Git
513
513
  try {
514
514
  const gitVersion = execSync('git --version', { encoding: 'utf8' }).trim();
515
515
  success(`${gitVersion}`);
516
516
  } catch (e) {
517
- error('Git no está instalado. Instala Git e intenta de nuevo.');
517
+ error('Git is not installed. Install Git and try again.');
518
518
  }
519
519
 
520
- // Verificar herramientas Unix estándar
520
+ // Check standard Unix tools
521
521
  const unixTools = ['sed', 'awk', 'grep', 'head', 'tail', 'stat', 'tput'];
522
522
  const missingTools = [];
523
523
 
@@ -530,26 +530,26 @@ async function checkAndInstallDependencies(sudoPassword = null, skipAuth = false
530
530
  });
531
531
 
532
532
  if (missingTools.length === 0) {
533
- success('Herramientas Unix estándar verificadas');
533
+ success('Standard Unix tools verified');
534
534
  } else {
535
- error(`Faltan herramientas Unix estándar: ${missingTools.join(', ')}. Reintenta instalación en una consola Ubuntu`);
535
+ error(`Missing standard Unix tools: ${missingTools.join(', ')}. Retry installation in an Ubuntu console`);
536
536
  }
537
537
 
538
- // Verificar e instalar Claude CLI
538
+ // Check and install Claude CLI
539
539
  await checkAndInstallClaude();
540
540
 
541
- // Verificar autenticación de Claude (si no se salta)
541
+ // Check Claude authentication (if not skipped)
542
542
  if (!skipAuth) {
543
543
  await checkClaudeAuth();
544
544
  } else {
545
- warning('Saltando verificación de autenticación de Claude (--skip-auth)');
545
+ warning('Skipping Claude authentication verification (--skip-auth)');
546
546
  }
547
547
 
548
- // Limpiar contraseña de memoria
548
+ // Clear password from memory
549
549
  sudoPassword = null;
550
550
  }
551
551
 
552
- // Verificar si necesitamos instalar dependencias
552
+ // Check if we need to install dependencies
553
553
  async function checkIfInstallationNeeded() {
554
554
  const dependencies = ['jq', 'curl'];
555
555
 
@@ -557,7 +557,7 @@ async function checkIfInstallationNeeded() {
557
557
  try {
558
558
  execSync(`which ${dep}`, { stdio: 'ignore' });
559
559
  } catch (e) {
560
- return true; // Necesita instalación
560
+ return true; // Needs installation
561
561
  }
562
562
  }
563
563
 
@@ -565,33 +565,33 @@ async function checkIfInstallationNeeded() {
565
565
  try {
566
566
  execSync('claude --version', { stdio: 'ignore' });
567
567
  } catch (e) {
568
- return true; // Necesita instalación de Claude
568
+ return true; // Needs Claude installation
569
569
  }
570
570
 
571
571
  return false;
572
572
  }
573
573
 
574
- // Verificar e instalar Claude CLI
574
+ // Check and install Claude CLI
575
575
  async function checkAndInstallClaude() {
576
576
  try {
577
577
  execSync('claude --version', { stdio: 'ignore' });
578
- success('Claude CLI detectado');
578
+ success('Claude CLI detected');
579
579
  } catch (e) {
580
- info('Claude CLI no detectado. Instalando...');
580
+ info('Claude CLI not detected. Installing...');
581
581
  try {
582
582
  execSync('npm install -g @anthropic-ai/claude-cli', { stdio: 'inherit' });
583
- success('Claude CLI instalado correctamente');
583
+ success('Claude CLI installed successfully');
584
584
  } catch (installError) {
585
- error('Error al instalar Claude CLI. Instálalo manualmente: npm install -g @anthropic-ai/claude-cli');
585
+ error('Error installing Claude CLI. Install manually: npm install -g @anthropic-ai/claude-cli');
586
586
  }
587
587
  }
588
588
  }
589
589
 
590
- // Verificar autenticación de Claude con entretenimiento
590
+ // Check Claude authentication with entertainment
591
591
  async function checkClaudeAuth() {
592
- info('Verificando autenticación de Claude...');
592
+ info('Checking Claude authentication...');
593
593
 
594
- // Usar spawn para no bloquear, pero con stdio: 'ignore' como el original
594
+ // Use spawn to not block, but with stdio: 'ignore' like the original
595
595
  const authPromise = new Promise((resolve, reject) => {
596
596
  const child = spawn('claude', ['auth', 'status'], {
597
597
  stdio: 'ignore', // Igual que el original
@@ -599,7 +599,7 @@ async function checkClaudeAuth() {
599
599
  windowsHide: true
600
600
  });
601
601
 
602
- // Timeout manual ya que spawn no tiene timeout nativo
602
+ // Manual timeout since spawn doesn't have native timeout
603
603
  const timeout = setTimeout(() => {
604
604
  child.kill();
605
605
  reject(new Error('timeout'));
@@ -621,18 +621,18 @@ async function checkClaudeAuth() {
621
621
  });
622
622
 
623
623
  try {
624
- await Entertainment.showSpinner(authPromise, 'Verificando autenticación de Claude');
625
- success('Autenticado en Claude');
624
+ await Entertainment.showSpinner(authPromise, 'Checking Claude authentication');
625
+ success('Authenticated in Claude');
626
626
  } catch (e) {
627
- warning('No estás autenticado en Claude');
628
- console.log('Ejecuta: claude auth login');
629
- console.log('Después vuelve a ejecutar este comando');
627
+ warning('You are not authenticated in Claude');
628
+ console.log('Run: claude auth login');
629
+ console.log('Then run this command again');
630
630
  }
631
631
  }
632
632
 
633
- // Actualizar .gitignore con las entradas de Claude
633
+ // Update .gitignore with Claude entries
634
634
  function updateGitignore() {
635
- info('Actualizando .gitignore...');
635
+ info('Updating .gitignore...');
636
636
 
637
637
  const gitignorePath = '.gitignore';
638
638
  const claudeEntries = [
@@ -646,22 +646,22 @@ function updateGitignore() {
646
646
  let gitignoreContent = '';
647
647
  let fileExists = false;
648
648
 
649
- // Leer .gitignore existente si existe
649
+ // Read existing .gitignore if it exists
650
650
  if (fs.existsSync(gitignorePath)) {
651
651
  gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
652
652
  fileExists = true;
653
653
  }
654
654
 
655
- // Verificar qué entradas faltan
655
+ // Check which entries are missing
656
656
  const missingEntries = [];
657
657
  claudeEntries.forEach(entry => {
658
658
  if (entry.startsWith('#')) {
659
- // Para comentarios, verificar si ya existe algún comentario de Claude
659
+ // For comments, check if any Claude comment already exists
660
660
  if (!gitignoreContent.includes('# Claude')) {
661
661
  missingEntries.push(entry);
662
662
  }
663
663
  } else {
664
- // Para entradas normales, verificar si ya están presentes
664
+ // For normal entries, check if they are already present
665
665
  const regex = new RegExp(`^${entry.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'm');
666
666
  if (!regex.test(gitignoreContent)) {
667
667
  missingEntries.push(entry);
@@ -669,70 +669,70 @@ function updateGitignore() {
669
669
  }
670
670
  });
671
671
 
672
- // Si hay entradas faltantes, agregarlas
672
+ // If there are missing entries, add them
673
673
  if (missingEntries.length > 0) {
674
- // Asegurar que hay una nueva línea al final si el archivo existe y no está vacío
674
+ // Ensure there's a newline at the end if the file exists and is not empty
675
675
  if (fileExists && gitignoreContent.length > 0 && !gitignoreContent.endsWith('\n')) {
676
676
  gitignoreContent += '\n';
677
677
  }
678
678
 
679
- // Si el archivo no está vacío, agregar una línea en blanco antes
679
+ // If the file is not empty, add a blank line before
680
680
  if (gitignoreContent.length > 0) {
681
681
  gitignoreContent += '\n';
682
682
  }
683
683
 
684
- // Agregar las entradas faltantes
684
+ // Add the missing entries
685
685
  gitignoreContent += missingEntries.join('\n') + '\n';
686
686
 
687
- // Escribir el archivo actualizado
687
+ // Write the updated file
688
688
  fs.writeFileSync(gitignorePath, gitignoreContent);
689
689
 
690
690
  if (fileExists) {
691
- success('.gitignore actualizado con entradas de Claude');
691
+ success('.gitignore updated with Claude entries');
692
692
  } else {
693
- success('.gitignore creado con entradas de Claude');
693
+ success('.gitignore created with Claude entries');
694
694
  }
695
695
 
696
- // Mostrar qué se agregó
696
+ // Show what was added
697
697
  missingEntries.forEach(entry => {
698
698
  if (!entry.startsWith('#')) {
699
699
  info(` + ${entry}`);
700
700
  }
701
701
  });
702
702
  } else {
703
- info('.gitignore ya contiene todas las entradas necesarias');
703
+ info('.gitignore already contains all necessary entries');
704
704
  }
705
705
  }
706
706
 
707
- // Configurar Git (line endings, etc.)
707
+ // Configure Git (line endings, etc.)
708
708
  function configureGit() {
709
- info('Configurando Git...');
709
+ info('Configuring Git...');
710
710
 
711
711
  try {
712
- // Configurar line endings para WSL
712
+ // Configure line endings for WSL
713
713
  execSync('git config core.autocrlf input', { stdio: 'ignore' });
714
- success('Line endings configurados para WSL (core.autocrlf = input)');
714
+ success('Line endings configured for WSL (core.autocrlf = input)');
715
715
 
716
- // Intentar configurar en Windows a través de PowerShell
716
+ // Try to configure on Windows through PowerShell
717
717
  try {
718
718
  execSync('powershell.exe -Command "git config core.autocrlf true"', { stdio: 'ignore' });
719
- success('Line endings configurados para Windows (core.autocrlf = true)');
719
+ success('Line endings configured for Windows (core.autocrlf = true)');
720
720
  } catch (psError) {
721
- info('No se pudo configurar automáticamente en Windows');
721
+ info('Could not configure automatically on Windows');
722
722
  }
723
723
 
724
724
  } catch (e) {
725
- warning('Error al configurar Git');
725
+ warning('Error configuring Git');
726
726
  }
727
727
  }
728
728
 
729
- // Comando uninstall
729
+ // Uninstall command
730
730
  function uninstall() {
731
731
  if (!checkGitRepo()) {
732
- error('No estás en un repositorio Git.');
732
+ error('You are not in a Git repository.');
733
733
  }
734
734
 
735
- info('Desinstalando Claude Git Hooks...');
735
+ info('Uninstalling Claude Git Hooks...');
736
736
 
737
737
  const hooksPath = '.git/hooks';
738
738
  const hooks = ['pre-commit', 'prepare-commit-msg'];
@@ -741,17 +741,17 @@ function uninstall() {
741
741
  const hookPath = path.join(hooksPath, hook);
742
742
  if (fs.existsSync(hookPath)) {
743
743
  fs.unlinkSync(hookPath);
744
- success(`${hook} eliminado`);
744
+ success(`${hook} removed`);
745
745
  }
746
746
  });
747
747
 
748
- success('Claude Git Hooks desinstalado');
748
+ success('Claude Git Hooks uninstalled');
749
749
  }
750
750
 
751
- // Comando enable
751
+ // Enable command
752
752
  function enable(hookName) {
753
753
  if (!checkGitRepo()) {
754
- error('No estás en un repositorio Git.');
754
+ error('You are not in a Git repository.');
755
755
  }
756
756
 
757
757
  const hooks = hookName ? [hookName] : ['pre-commit', 'prepare-commit-msg'];
@@ -762,19 +762,19 @@ function enable(hookName) {
762
762
 
763
763
  if (fs.existsSync(disabledPath)) {
764
764
  fs.renameSync(disabledPath, enabledPath);
765
- success(`${hook} habilitado`);
765
+ success(`${hook} enabled`);
766
766
  } else if (fs.existsSync(enabledPath)) {
767
- info(`${hook} ya está habilitado`);
767
+ info(`${hook} is already enabled`);
768
768
  } else {
769
- warning(`${hook} no encontrado`);
769
+ warning(`${hook} not found`);
770
770
  }
771
771
  });
772
772
  }
773
773
 
774
- // Comando disable
774
+ // Disable command
775
775
  function disable(hookName) {
776
776
  if (!checkGitRepo()) {
777
- error('No estás en un repositorio Git.');
777
+ error('You are not in a Git repository.');
778
778
  }
779
779
 
780
780
  const hooks = hookName ? [hookName] : ['pre-commit', 'prepare-commit-msg'];
@@ -785,67 +785,67 @@ function disable(hookName) {
785
785
 
786
786
  if (fs.existsSync(enabledPath)) {
787
787
  fs.renameSync(enabledPath, disabledPath);
788
- success(`${hook} deshabilitado`);
788
+ success(`${hook} disabled`);
789
789
  } else if (fs.existsSync(disabledPath)) {
790
- info(`${hook} ya está deshabilitado`);
790
+ info(`${hook} is already disabled`);
791
791
  } else {
792
- warning(`${hook} no encontrado`);
792
+ warning(`${hook} not found`);
793
793
  }
794
794
  });
795
795
  }
796
796
 
797
- // Comando analyze-diff
797
+ // Analyze-diff command
798
798
  function analyzeDiff(args) {
799
799
  if (!checkGitRepo()) {
800
- error('No estás en un repositorio Git.');
800
+ error('You are not in a Git repository.');
801
801
  return;
802
802
  }
803
803
 
804
804
  const currentBranch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();
805
805
 
806
806
  if (!currentBranch) {
807
- error('No estás en una rama válida.');
807
+ error('You are not in a valid branch.');
808
808
  return;
809
809
  }
810
810
 
811
811
  let baseBranch, compareWith, contextDescription;
812
812
 
813
813
  if (args[0]) {
814
- // Caso con argumento: comparar rama actual vs rama especificada
814
+ // Case with argument: compare current branch vs specified branch
815
815
  baseBranch = args[0];
816
816
  compareWith = `${baseBranch}...HEAD`;
817
817
  contextDescription = `${currentBranch} vs ${baseBranch}`;
818
818
 
819
- // Verificar que la rama base existe
819
+ // Check that the base branch exists
820
820
  try {
821
821
  execSync(`git rev-parse --verify ${baseBranch}`, { stdio: 'ignore' });
822
822
  } catch (e) {
823
- error(`La rama ${baseBranch} no existe.`);
823
+ error(`Branch ${baseBranch} does not exist.`);
824
824
  return;
825
825
  }
826
826
  } else {
827
- // Caso sin argumento: comparar local vs remoto
827
+ // Case without argument: compare local vs remote
828
828
  try {
829
- // Obtener la rama remota tracked
829
+ // Get the tracked remote branch
830
830
  const remoteBranch = execSync(`git rev-parse --abbrev-ref ${currentBranch}@{upstream}`, { encoding: 'utf8' }).trim();
831
831
 
832
- // Actualizar referencias remotas
832
+ // Update remote references
833
833
  execSync('git fetch', { stdio: 'ignore' });
834
834
 
835
835
  baseBranch = remoteBranch;
836
836
  compareWith = `HEAD..${remoteBranch}`;
837
- contextDescription = `cambios locales sin pushear en ${currentBranch}`;
837
+ contextDescription = `local changes without push in ${currentBranch}`;
838
838
 
839
- info(`Comparando cambios locales vs remoto: ${remoteBranch}`);
839
+ info(`Comparing local changes vs remote: ${remoteBranch}`);
840
840
  } catch (e) {
841
- // Si no hay rama remota, usar develop como fallback
841
+ // If there's no remote branch, use develop as fallback
842
842
  baseBranch = 'develop';
843
843
  compareWith = `${baseBranch}...HEAD`;
844
844
  contextDescription = `${currentBranch} vs ${baseBranch} (sin rama remota)`;
845
845
 
846
846
  try {
847
847
  execSync(`git rev-parse --verify ${baseBranch}`, { stdio: 'ignore' });
848
- warning(`No hay rama remota configurada. Comparando con ${baseBranch}.`);
848
+ warning(`No remote branch configured. Comparing with ${baseBranch}.`);
849
849
  } catch (e2) {
850
850
  baseBranch = 'main';
851
851
  compareWith = `${baseBranch}...HEAD`;
@@ -853,21 +853,21 @@ function analyzeDiff(args) {
853
853
 
854
854
  try {
855
855
  execSync(`git rev-parse --verify ${baseBranch}`, { stdio: 'ignore' });
856
- warning(`No hay develop ni rama remota. Comparando con main.`);
856
+ warning(`No develop or remote branch. Comparing with main.`);
857
857
  } catch (e3) {
858
- error('No se pudo encontrar una rama de comparación válida.');
858
+ error('Could not find a valid comparison branch.');
859
859
  return;
860
860
  }
861
861
  }
862
862
  }
863
863
  }
864
864
 
865
- info(`Analizando: ${contextDescription}...`);
865
+ info(`Analyzing: ${contextDescription}...`);
866
866
 
867
- // Obtener los archivos modificados
867
+ // Get modified files
868
868
  let diffFiles;
869
869
  try {
870
- // Para cambios locales sin pushear, invertir la comparación
870
+ // For local changes without push, invert the comparison
871
871
  if (!args[0] && compareWith.includes('HEAD..')) {
872
872
  diffFiles = execSync(`git diff ${compareWith} --name-only`, { encoding: 'utf8' }).trim();
873
873
  } else {
@@ -875,25 +875,25 @@ function analyzeDiff(args) {
875
875
  }
876
876
 
877
877
  if (!diffFiles) {
878
- // Verificar si hay cambios staged o unstaged
878
+ // Check if there are staged or unstaged changes
879
879
  const stagedFiles = execSync('git diff --cached --name-only', { encoding: 'utf8' }).trim();
880
880
  const unstagedFiles = execSync('git diff --name-only', { encoding: 'utf8' }).trim();
881
881
 
882
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');
883
+ warning('No differences with remote, but you have uncommitted local changes.');
884
+ console.log('Staged changes:', stagedFiles || 'none');
885
+ console.log('Unstaged changes:', unstagedFiles || 'none');
886
886
  } else {
887
- success('✅ No hay diferencias. Tu rama está sincronizada.');
887
+ success('✅ No differences. Your branch is synchronized.');
888
888
  }
889
889
  return;
890
890
  }
891
891
  } catch (e) {
892
- error('Error al obtener las diferencias: ' + e.message);
892
+ error('Error getting differences: ' + e.message);
893
893
  return;
894
894
  }
895
895
 
896
- // Obtener el diff completo
896
+ // Get the complete diff
897
897
  let fullDiff, commits;
898
898
  try {
899
899
  if (!args[0] && compareWith.includes('HEAD..')) {
@@ -904,50 +904,50 @@ function analyzeDiff(args) {
904
904
  commits = execSync(`git log ${baseBranch}..HEAD --oneline`, { encoding: 'utf8' }).trim();
905
905
  }
906
906
  } catch (e) {
907
- error('Error al obtener diff o commits: ' + e.message);
907
+ error('Error getting diff or commits: ' + e.message);
908
908
  return;
909
909
  }
910
910
 
911
- // Crear el prompt para Claude
911
+ // Create the prompt for Claude
912
912
  const tempDir = `/tmp/claude-analyze-${Date.now()}`;
913
913
  fs.mkdirSync(tempDir, { recursive: true });
914
914
 
915
915
  const promptFile = path.join(tempDir, 'prompt.txt');
916
- const prompt = `Analiza los siguientes cambios. CONTEXTO: ${contextDescription}
916
+ const prompt = `Analyze the following changes. CONTEXT: ${contextDescription}
917
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)
918
+ Please generate:
919
+ 1. A concise and descriptive PR title (maximum 72 characters)
920
+ 2. A detailed PR description that includes:
921
+ - Summary of changes
922
+ - Motivation/context
923
+ - Type of change (feature/fix/refactor/docs/etc)
924
+ - Recommended testing
925
+ 3. A suggested branch name following the format: type/short-description (example: feature/add-user-auth, fix/memory-leak)
926
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.
927
+ IMPORTANT: If these are local changes without push, the suggested branch name should be for creating a new branch from the current one.
928
928
 
929
- Responde EXCLUSIVAMENTE con un JSON válido con esta estructura:
929
+ Respond EXCLUSIVELY with a valid JSON with this structure:
930
930
  {
931
- "prTitle": "título del PR",
932
- "prDescription": "descripción detallada del PR con markdown",
933
- "suggestedBranchName": "tipo/nombre-rama-sugerido",
931
+ "prTitle": "Interesting PR title",
932
+ "prDescription": "detailed PR description with markdown",
933
+ "suggestedBranchName": "type/suggested-branch-name",
934
934
  "changeType": "feature|fix|refactor|docs|test|chore",
935
935
  "breakingChanges": false,
936
- "testingNotes": "notas sobre testing necesario"
936
+ "testingNotes": "notes on necessary testing or 'None'"
937
937
  }
938
938
 
939
939
  === COMMITS ===
940
940
  ${commits}
941
941
 
942
- === ARCHIVOS MODIFICADOS ===
942
+ === CHANGED FILES ===
943
943
  ${diffFiles}
944
944
 
945
- === DIFF COMPLETO ===
946
- ${fullDiff.substring(0, 50000)} ${fullDiff.length > 50000 ? '\n... (diff truncado)' : ''}`;
945
+ === FULL DIFF ===
946
+ ${fullDiff.substring(0, 50000)} ${fullDiff.length > 50000 ? '\n... (truncated diff)' : ''}`;
947
947
 
948
948
  fs.writeFileSync(promptFile, prompt);
949
949
 
950
- info('Enviando a Claude para análisis...');
950
+ info('Sending to Claude for analysis...');
951
951
 
952
952
  try {
953
953
  const response = execSync(`claude < "${promptFile}"`, { encoding: 'utf8', maxBuffer: 1024 * 1024 * 10 });
@@ -955,8 +955,8 @@ ${fullDiff.substring(0, 50000)} ${fullDiff.length > 50000 ? '\n... (diff truncad
955
955
  // Extraer el JSON de la respuesta
956
956
  const jsonMatch = response.match(/\{[\s\S]*\}/);
957
957
  if (!jsonMatch) {
958
- error('No se recibió una respuesta JSON válida de Claude.');
959
- console.log('Respuesta completa:', response);
958
+ error('Did not receive a valid JSON response from Claude.');
959
+ console.log('Complete response:', response);
960
960
  return;
961
961
  }
962
962
 
@@ -964,45 +964,45 @@ ${fullDiff.substring(0, 50000)} ${fullDiff.length > 50000 ? '\n... (diff truncad
964
964
  try {
965
965
  result = JSON.parse(jsonMatch[0]);
966
966
  } catch (e) {
967
- error('Error al parsear la respuesta JSON: ' + e.message);
968
- console.log('JSON recibido:', jsonMatch[0]);
967
+ error('Error parsing JSON response: ' + e.message);
968
+ console.log('JSON received:', jsonMatch[0]);
969
969
  return;
970
970
  }
971
971
 
972
- // Mostrar los resultados
972
+ // Show the results
973
973
  console.log('');
974
- console.log('═══════════════════════════════════════════════════════════════');
975
- console.log(' ANÁLISIS DE DIFERENCIAS ');
976
- console.log('═══════════════════════════════════════════════════════════════');
974
+ console.log('════════════════════════════════════════════════════════════════');
975
+ console.log(' DIFFERENCES ANALYSIS ');
976
+ console.log('════════════════════════════════════════════════════════════════');
977
977
  console.log('');
978
978
 
979
- console.log(`🔍 ${colors.blue}Contexto:${colors.reset} ${contextDescription}`);
980
- console.log(`📊 ${colors.blue}Archivos modificados:${colors.reset} ${diffFiles.split('\n').length}`);
979
+ console.log(`🔍 ${colors.blue}Context:${colors.reset} ${contextDescription}`);
980
+ console.log(`📊 ${colors.blue}Changed Files:${colors.reset} ${diffFiles.split('\n').length}`);
981
981
  console.log('');
982
982
 
983
- console.log(`📝 ${colors.green}Título del PR:${colors.reset}`);
983
+ console.log(`📝 ${colors.green}Pull Request Title:${colors.reset}`);
984
984
  console.log(` ${result.prTitle}`);
985
985
  console.log('');
986
986
 
987
- console.log(`🌿 ${colors.green}Nombre de rama sugerido:${colors.reset}`);
987
+ console.log(`🌿 ${colors.green}Suggested branch name:${colors.reset}`);
988
988
  console.log(` ${result.suggestedBranchName}`);
989
989
  console.log('');
990
990
 
991
- console.log(`📋 ${colors.green}Tipo de cambio:${colors.reset} ${result.changeType}`);
991
+ console.log(`📋 ${colors.green}Type of change:${colors.reset} ${result.changeType}`);
992
992
 
993
993
  if (result.breakingChanges) {
994
994
  console.log(`⚠️ ${colors.yellow}Breaking Changes: SÍ${colors.reset}`);
995
995
  }
996
996
 
997
997
  console.log('');
998
- console.log(`📄 ${colors.green}Descripción del PR:${colors.reset}`);
998
+ console.log(`📄 ${colors.green}Pull Request Description:${colors.reset}`);
999
999
  console.log('───────────────────────────────────────────────────────────────');
1000
1000
  console.log(result.prDescription);
1001
1001
  console.log('───────────────────────────────────────────────────────────────');
1002
1002
 
1003
1003
  if (result.testingNotes) {
1004
1004
  console.log('');
1005
- console.log(`🧪 ${colors.green}Notas de Testing:${colors.reset}`);
1005
+ console.log(`🧪 ${colors.green}Testing notes:${colors.reset}`);
1006
1006
  console.log(result.testingNotes);
1007
1007
  }
1008
1008
 
@@ -1021,27 +1021,27 @@ ${fullDiff.substring(0, 50000)} ${fullDiff.length > 50000 ? '\n... (diff truncad
1021
1021
  const outputFile = '.claude-pr-analysis.json';
1022
1022
  fs.writeFileSync(outputFile, JSON.stringify(outputData, null, 2));
1023
1023
  console.log('');
1024
- info(`Resultados guardados en ${outputFile}`);
1024
+ info(`Results saved in ${outputFile}`);
1025
1025
 
1026
1026
  // Sugerencias contextuales
1027
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}`);
1028
+ if (!args[0] && contextDescription.includes('local changes without push')) {
1029
+ // Case of local changes without push
1030
+ console.log(`💡 ${colors.yellow}To create new branch with these changes:${colors.reset}`);
1031
1031
  console.log(` git checkout -b ${result.suggestedBranchName}`);
1032
1032
  console.log(` git push -u origin ${result.suggestedBranchName}`);
1033
1033
  } else if (currentBranch !== result.suggestedBranchName) {
1034
1034
  // Caso normal de comparación entre ramas
1035
- console.log(`💡 ${colors.yellow}Para renombrar tu rama actual:${colors.reset}`);
1035
+ console.log(`💡 ${colors.yellow}For renaming your current branch:${colors.reset}`);
1036
1036
  console.log(` git branch -m ${result.suggestedBranchName}`);
1037
1037
  }
1038
1038
 
1039
- console.log(`💡 ${colors.yellow}Tip:${colors.reset} Usa esta información para crear tu PR en GitHub.`);
1039
+ console.log(`💡 ${colors.yellow}Tip:${colors.reset} Use this information to create your PR on GitHub.`);
1040
1040
 
1041
1041
  } catch (e) {
1042
- error('Error al ejecutar Claude: ' + e.message);
1042
+ error('Error executing Claude: ' + e.message);
1043
1043
  } finally {
1044
- // Limpiar archivos temporales
1044
+ // Clean temporary files
1045
1045
  fs.rmSync(tempDir, { recursive: true, force: true });
1046
1046
  }
1047
1047
  }
@@ -1049,10 +1049,10 @@ ${fullDiff.substring(0, 50000)} ${fullDiff.length > 50000 ? '\n... (diff truncad
1049
1049
  // Comando status
1050
1050
  function status() {
1051
1051
  if (!checkGitRepo()) {
1052
- error('No estás en un repositorio Git.');
1052
+ error('You are not in a Git repository.');
1053
1053
  }
1054
1054
 
1055
- info('Estado de Claude Git Hooks:\n');
1055
+ info('Claude Git Hooks status:\n');
1056
1056
 
1057
1057
  const hooks = ['pre-commit', 'prepare-commit-msg'];
1058
1058
  hooks.forEach(hook => {
@@ -1060,25 +1060,25 @@ function status() {
1060
1060
  const disabledPath = `.git/hooks/${hook}.disabled`;
1061
1061
 
1062
1062
  if (fs.existsSync(enabledPath)) {
1063
- success(`${hook}: habilitado`);
1063
+ success(`${hook}: enabled`);
1064
1064
  } else if (fs.existsSync(disabledPath)) {
1065
- warning(`${hook}: deshabilitado`);
1065
+ warning(`${hook}: disabled`);
1066
1066
  } else {
1067
- error(`${hook}: no instalado`);
1067
+ error(`${hook}: not installed`);
1068
1068
  }
1069
1069
  });
1070
1070
 
1071
- // Verificar archivos de pautas
1072
- console.log('\nArchivos de pautas:');
1071
+ // Check guidelines files
1072
+ console.log('\nGuidelines files:');
1073
1073
  const guidelines = ['CLAUDE_PRE_COMMIT_SONAR.md'];
1074
1074
  guidelines.forEach(guideline => {
1075
1075
  const claudePath = path.join('.claude', guideline);
1076
1076
  if (fs.existsSync(claudePath)) {
1077
- success(`${guideline}: presente en .claude/`);
1077
+ success(`${guideline}: present in .claude/`);
1078
1078
  } else if (fs.existsSync(guideline)) {
1079
- warning(`${guideline}: presente en raíz (debería estar en .claude/)`);
1079
+ warning(`${guideline}: present in root (should be in .claude/)`);
1080
1080
  } else {
1081
- warning(`${guideline}: faltante`);
1081
+ warning(`${guideline}: missing`);
1082
1082
  }
1083
1083
  });
1084
1084
 
@@ -1093,25 +1093,25 @@ function status() {
1093
1093
  claudeIgnores.forEach(entry => {
1094
1094
  const regex = new RegExp(`^${entry.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'm');
1095
1095
  if (regex.test(gitignoreContent)) {
1096
- success(`${entry}: incluido`);
1096
+ success(`${entry}: included`);
1097
1097
  } else {
1098
- warning(`${entry}: faltante`);
1098
+ warning(`${entry}: missing`);
1099
1099
  allPresent = false;
1100
1100
  }
1101
1101
  });
1102
1102
 
1103
1103
  if (!allPresent) {
1104
- info('\nEjecuta "claude-hooks install" para actualizar .gitignore');
1104
+ info('\nRun "claude-hooks install" to update .gitignore');
1105
1105
  }
1106
1106
  } else {
1107
- warning('.gitignore no existe');
1107
+ warning('.gitignore doesn´t exist');
1108
1108
  }
1109
1109
  }
1110
1110
 
1111
1111
  // Comando set-mode
1112
1112
  function setMode(mode) {
1113
1113
  if (!checkGitRepo()) {
1114
- error('No estás en un repositorio Git.');
1114
+ error('You are not in a Git repository.');
1115
1115
  }
1116
1116
  }
1117
1117
 
@@ -1147,9 +1147,9 @@ function compareVersions(v1, v2) {
1147
1147
  }
1148
1148
  }
1149
1149
 
1150
- // Comando update - actualizar a la última versión
1150
+ // Update command - update to the latest version
1151
1151
  async function update() {
1152
- info('Verificando última versión disponible...');
1152
+ info('Checking latest available version...');
1153
1153
 
1154
1154
  try {
1155
1155
  const currentVersion = require('../package.json').version;
@@ -1158,40 +1158,40 @@ async function update() {
1158
1158
  const comparison = compareVersions(currentVersion, latestVersion);
1159
1159
 
1160
1160
  if (comparison === 0) {
1161
- success(`Ya tienes la última versión instalada (${currentVersion})`);
1161
+ success(`You already have the latest version installed (${currentVersion})`);
1162
1162
  return;
1163
1163
  } else if (comparison > 0) {
1164
- info(`Estás usando una versión de desarrollo (${currentVersion})`);
1165
- info(`Última versión publicada: ${latestVersion}`);
1166
- success(`Ya tienes la última versión instalada (${currentVersion})`);
1164
+ info(`You are using a development version (${currentVersion})`);
1165
+ info(`Latest published version: ${latestVersion}`);
1166
+ success(`You already have the latest version installed (${currentVersion})`);
1167
1167
  return;
1168
1168
  }
1169
1169
 
1170
- info(`Versión actual: ${currentVersion}`);
1171
- info(`Versión disponible: ${latestVersion}`);
1170
+ info(`Current version: ${currentVersion}`);
1171
+ info(`Available version: ${latestVersion}`);
1172
1172
 
1173
1173
  // Actualizar el paquete
1174
- info('Actualizando claude-git-hooks...');
1174
+ info('Updating claude-git-hooks...');
1175
1175
  try {
1176
1176
  execSync('npm install -g claude-git-hooks@latest', { stdio: 'inherit' });
1177
- success(`Actualizado exitosamente a la versión ${latestVersion}`);
1177
+ success(`Successfully updated to version ${latestVersion}`);
1178
1178
 
1179
- // Reinstalar hooks con la nueva versión
1180
- info('Reinstalando hooks con la nueva versión...');
1179
+ // Reinstall hooks with the new version
1180
+ info('Reinstalling hooks with the new version...');
1181
1181
  await install(['--force']);
1182
1182
 
1183
1183
  } catch (updateError) {
1184
- error('Error al actualizar. Intenta ejecutar: npm install -g claude-git-hooks@latest');
1184
+ error('Error updating. Try running: npm install -g claude-git-hooks@latest');
1185
1185
  }
1186
1186
  } catch (e) {
1187
- warning('No se pudo verificar la última versión disponible');
1188
- warning('Intentando actualizar de todas formas...');
1187
+ warning('Could not check the latest available version');
1188
+ warning('Trying to update anyway...');
1189
1189
  try {
1190
1190
  execSync('npm install -g claude-git-hooks@latest', { stdio: 'inherit' });
1191
- success('Actualización completada');
1191
+ success('Update completed');
1192
1192
  await install(['--force']);
1193
1193
  } catch (updateError) {
1194
- error('Error al actualizar: ' + updateError.message);
1194
+ error('Error updating: ' + updateError.message);
1195
1195
  }
1196
1196
  }
1197
1197
  }
@@ -1199,54 +1199,54 @@ async function update() {
1199
1199
  // Comando help
1200
1200
  function showHelp() {
1201
1201
  console.log(`
1202
- Claude Git Hooks - Análisis de código y mensajes automáticos con Claude CLI
1203
-
1204
- Uso: claude-hooks <comando> [opciones]
1205
-
1206
- Comandos:
1207
- install [opciones] Instala los hooks en el repositorio actual
1208
- --force Reinstala aunque ya existan
1209
- --skip-auth Salta la verificación de autenticación de Claude
1210
- update Actualiza a la última versión disponible
1211
- uninstall Desinstala los hooks del repositorio
1212
- enable [hook] Habilita hooks (todos o uno específico)
1213
- disable [hook] Deshabilita hooks (todos o uno específico)
1214
- status Muestra el estado de los hooks
1215
- analyze-diff [base] Analiza diferencias entre ramas y genera PR info
1216
- help Muestra esta ayuda
1217
-
1218
- Hooks disponibles:
1219
- pre-commit Análisis de código antes del commit
1220
- prepare-commit-msg Generación automática de mensajes
1221
-
1222
- Ejemplos:
1223
- claude-hooks install # Instala todos los hooks
1224
- claude-hooks install --skip-auth # Instala sin verificar autenticación
1225
- claude-hooks update # Actualiza a la última versión
1226
- claude-hooks disable pre-commit # Deshabilita solo pre-commit
1227
- claude-hooks enable # Habilita todos los hooks
1228
- claude-hooks status # Ver estado actual
1229
- claude-hooks analyze-diff main # Analiza diferencias con main
1230
-
1231
- Casos de uso de commits:
1232
- git commit -m "mensaje" # Mensaje manual + análisis bloqueante
1233
- git commit -m "auto" # Mensaje automático + análisis bloqueante
1234
- git commit --no-verify -m "auto" # Mensaje automático sin análisis
1235
- git commit --no-verify -m "msg" # Mensaje manual sin análisis
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
-
1243
- Excluir código del análisis:
1244
- // SKIP-ANALYSIS # Excluye la siguiente línea del análisis
1245
- // SKIP_ANALYSIS_BLOCK # Excluye bloque hasta encontrar otro igual
1246
- ...código excluido...
1202
+ Claude Git Hooks - Code analysis and automatic messages with Claude CLI
1203
+
1204
+ Usage: claude-hooks <command> [options]
1205
+
1206
+ Commands:
1207
+ install [options] Install hooks in the current repository
1208
+ --force Reinstall even if they already exist
1209
+ --skip-auth Skip Claude authentication verification
1210
+ update Update to the latest available version
1211
+ uninstall Uninstall hooks from the repository
1212
+ enable [hook] Enable hooks (all or one specific)
1213
+ disable [hook] Disable hooks (all or one specific)
1214
+ status Show the status of hooks
1215
+ analyze-diff [base] Analyze differences between branches and generate PR info
1216
+ help Show this help
1217
+
1218
+ Available hooks:
1219
+ pre-commit Code analysis before commit
1220
+ prepare-commit-msg Automatic message generation
1221
+
1222
+ Examples:
1223
+ claude-hooks install # Install all hooks
1224
+ claude-hooks install --skip-auth # Install without verifying authentication
1225
+ claude-hooks update # Update to the latest version
1226
+ claude-hooks disable pre-commit # Disable only pre-commit
1227
+ claude-hooks enable # Enable all hooks
1228
+ claude-hooks status # View current status
1229
+ claude-hooks analyze-diff main # Analyze differences with main
1230
+
1231
+ Commit use cases:
1232
+ git commit -m "message" # Manual message + blocking analysis
1233
+ git commit -m "auto" # Automatic message + blocking analysis
1234
+ git commit --no-verify -m "auto" # Automatic message without analysis
1235
+ git commit --no-verify -m "msg" # Manual message without analysis
1236
+
1237
+ Analyze-diff use case:
1238
+ claude-hooks analyze-diff main # Analyze changes vs main and generate:
1239
+ PR Title: "feat: add user authentication module"
1240
+ PR Description: "## Summary\n- Added JWT authentication..."
1241
+ Suggested branch: "feature/user-authentication"
1242
+
1243
+ Exclude code from analysis:
1244
+ // SKIP-ANALYSIS # Exclude the next line from analysis
1245
+ // SKIP_ANALYSIS_BLOCK # Exclude block until finding another equal one
1246
+ ...excluded code...
1247
1247
  // SKIP_ANALYSIS_BLOCK
1248
1248
 
1249
- Más información: https://github.com/pablorovito/claude-git-hooks
1249
+ More information: https://github.com/pablorovito/claude-git-hooks
1250
1250
  `);
1251
1251
  }
1252
1252
 
@@ -1284,12 +1284,12 @@ async function main() {
1284
1284
  showHelp();
1285
1285
  break;
1286
1286
  default:
1287
- error(`Comando desconocido: ${command}`);
1287
+ error(`Unknown command: ${command}`);
1288
1288
  showHelp();
1289
1289
  }
1290
1290
  }
1291
1291
 
1292
- // Ejecutar main
1292
+ // Execute main
1293
1293
  main().catch(err => {
1294
- error(`Error inesperado: ${err.message}`);
1294
+ error(`Unexpected error: ${err.message}`);
1295
1295
  });