claude-git-hooks 1.5.1 → 1.5.3
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 +64 -3
- package/README.md +22 -30
- package/bin/claude-hooks +345 -350
- package/package.json +1 -2
- package/templates/pre-commit +91 -91
- package/templates/prepare-commit-msg +32 -32
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
|
-
//
|
|
10
|
+
// Function to get the latest version from NPM
|
|
11
11
|
function getLatestVersion(packageName) {
|
|
12
12
|
return new Promise((resolve, reject) => {
|
|
13
|
-
//
|
|
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
|
-
//
|
|
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('
|
|
24
|
+
reject(new Error('Could not get the version'));
|
|
25
25
|
}
|
|
26
26
|
} catch (e) {
|
|
27
|
-
//
|
|
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
|
-
//
|
|
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; //
|
|
47
|
+
return true; // Already updated
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
console.log('');
|
|
51
|
-
warning(`
|
|
51
|
+
warning(`New version available: ${latestVersion} (current: ${currentVersion})`);
|
|
52
52
|
|
|
53
|
-
//
|
|
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('
|
|
60
|
+
rl.question('Do you want to update now? (y/n): ', (answer) => {
|
|
61
61
|
rl.close();
|
|
62
62
|
|
|
63
|
-
if (answer.toLowerCase() === '
|
|
64
|
-
info('
|
|
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('
|
|
68
|
-
process.exit(0); //
|
|
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
|
|
70
|
+
error('Error updating: ' + e.message);
|
|
71
71
|
resolve(false);
|
|
72
72
|
}
|
|
73
73
|
} else {
|
|
74
|
-
info('
|
|
75
|
-
resolve(true); //
|
|
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
|
-
//
|
|
80
|
+
// If there's an error checking version, continue without blocking
|
|
81
81
|
return true;
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
//
|
|
85
|
+
// Export for use in hooks
|
|
86
86
|
if (typeof module !== 'undefined' && module.exports) {
|
|
87
87
|
module.exports = { checkVersionAndPromptUpdate };
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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(); //
|
|
139
|
+
console.log(); // New line
|
|
140
140
|
resolve(password);
|
|
141
141
|
});
|
|
142
142
|
});
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
184
|
+
// Entertainment system
|
|
185
185
|
class Entertainment {
|
|
186
186
|
static jokes = [
|
|
187
|
-
"
|
|
188
|
-
"
|
|
189
|
-
"
|
|
190
|
-
"
|
|
191
|
-
"
|
|
192
|
-
"
|
|
193
|
-
"
|
|
194
|
-
"
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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 = '
|
|
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
|
-
//
|
|
231
|
+
// Get first joke from API without blocking
|
|
232
232
|
this.getJoke().then(joke => {
|
|
233
233
|
if (!isFinished) currentJoke = joke;
|
|
234
|
-
}).catch(() => {}); //
|
|
234
|
+
}).catch(() => {}); // If it fails, keep the local one
|
|
235
235
|
|
|
236
|
-
//
|
|
236
|
+
// Hide cursor
|
|
237
237
|
process.stdout.write('\x1B[?25l');
|
|
238
238
|
|
|
239
|
-
//
|
|
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
|
-
//
|
|
250
|
+
// Update countdown every second (10 iterations of 100ms)
|
|
251
251
|
if (spinnerIndex % 10 === 0) {
|
|
252
252
|
jokeCountdown--;
|
|
253
253
|
|
|
254
|
-
//
|
|
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
|
-
//
|
|
267
|
+
// Always go back exactly 3 lines up
|
|
268
268
|
process.stdout.write('\x1B[3A');
|
|
269
269
|
|
|
270
|
-
//
|
|
270
|
+
// Render the 3 lines from the beginning
|
|
271
271
|
const spinner = spinners[spinnerIndex % spinners.length];
|
|
272
272
|
|
|
273
|
-
//
|
|
273
|
+
// Line 1: Spinner
|
|
274
274
|
process.stdout.write('\r\x1B[2K' + `${colors.yellow}${spinner} ${message}${colors.reset}\n`);
|
|
275
275
|
|
|
276
|
-
//
|
|
276
|
+
// Line 2: Joke
|
|
277
277
|
process.stdout.write('\r\x1B[2K' + `${colors.green}🎭 ${currentJoke}${colors.reset}\n`);
|
|
278
278
|
|
|
279
|
-
//
|
|
280
|
-
process.stdout.write('\r\x1B[2K' + `${colors.yellow}⏱️
|
|
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
|
-
//
|
|
289
|
-
process.stdout.write('\x1B[3A'); //
|
|
290
|
-
process.stdout.write('\r\x1B[2K'); //
|
|
291
|
-
process.stdout.write('\n\r\x1B[2K'); //
|
|
292
|
-
process.stdout.write('\n\r\x1B[2K'); //
|
|
293
|
-
process.stdout.write('\x1B[2A'); //
|
|
294
|
-
process.stdout.write('\r'); //
|
|
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
|
-
//
|
|
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
|
-
//
|
|
305
|
-
process.stdout.write('\x1B[3A'); //
|
|
306
|
-
process.stdout.write('\r\x1B[2K'); //
|
|
307
|
-
process.stdout.write('\n\r\x1B[2K'); //
|
|
308
|
-
process.stdout.write('\n\r\x1B[2K'); //
|
|
309
|
-
process.stdout.write('\x1B[2A'); //
|
|
310
|
-
process.stdout.write('\r'); //
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
330
|
+
// Get the templates path
|
|
331
331
|
function getTemplatesPath() {
|
|
332
332
|
return path.join(__dirname, '..', 'templates');
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
-
//
|
|
335
|
+
// Install command
|
|
336
336
|
async function install(args) {
|
|
337
337
|
if (!checkGitRepo()) {
|
|
338
|
-
error('
|
|
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('
|
|
345
|
+
info('Installing Claude Git Hooks (force mode)...');
|
|
346
346
|
} else {
|
|
347
|
-
info('
|
|
347
|
+
info('Installing Claude Git Hooks...');
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
-
//
|
|
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('
|
|
356
|
-
sudoPassword = await readPassword('
|
|
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('
|
|
359
|
+
warning('Incorrect password. Continuing without automatic installation.');
|
|
360
360
|
sudoPassword = null;
|
|
361
361
|
} else if (sudoPassword) {
|
|
362
|
-
success('
|
|
362
|
+
success('Password verified. Proceeding with automatic installation.');
|
|
363
363
|
}
|
|
364
364
|
}
|
|
365
365
|
}
|
|
366
366
|
|
|
367
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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
|
|
389
|
+
info(`Backup created: ${backupPath}`);
|
|
390
390
|
}
|
|
391
391
|
|
|
392
|
-
//
|
|
392
|
+
// Copy hook
|
|
393
393
|
fs.copyFileSync(sourcePath, destPath);
|
|
394
394
|
fs.chmodSync(destPath, '755');
|
|
395
|
-
success(`${hook}
|
|
395
|
+
success(`${hook} installed`);
|
|
396
396
|
});
|
|
397
397
|
|
|
398
|
-
//
|
|
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('
|
|
405
|
+
success('Version verification script installed');
|
|
406
406
|
}
|
|
407
407
|
|
|
408
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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}
|
|
430
|
+
success(`${file} installed in .claude/`);
|
|
431
431
|
} else {
|
|
432
|
-
warning(`
|
|
432
|
+
warning(`Template file not found: ${file}`);
|
|
433
433
|
}
|
|
434
434
|
}
|
|
435
435
|
});
|
|
436
436
|
|
|
437
|
-
//
|
|
437
|
+
// Configure Git
|
|
438
438
|
configureGit();
|
|
439
439
|
|
|
440
|
-
//
|
|
440
|
+
// Update .gitignore
|
|
441
441
|
updateGitignore();
|
|
442
442
|
|
|
443
|
-
success('
|
|
444
|
-
console.log('\
|
|
445
|
-
console.log(' git commit -m "auto" #
|
|
446
|
-
console.log(' git commit -m "
|
|
447
|
-
console.log(' git commit --no-verify #
|
|
448
|
-
console.log('\
|
|
449
|
-
console.log(' // SKIP-ANALYSIS #
|
|
450
|
-
console.log(' // SKIP_ANALYSIS_BLOCK #
|
|
451
|
-
console.log(' ...
|
|
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('\
|
|
453
|
+
console.log('\nFor more options: claude-hooks --help');
|
|
454
454
|
}
|
|
455
455
|
|
|
456
|
-
//
|
|
456
|
+
// Check complete dependencies (like setup-wsl.sh)
|
|
457
457
|
async function checkAndInstallDependencies(sudoPassword = null, skipAuth = false) {
|
|
458
|
-
info('
|
|
458
|
+
info('Checking system dependencies...');
|
|
459
459
|
|
|
460
|
-
//
|
|
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
|
|
465
|
+
error('Node.js is not installed. Install Node.js and try again.');
|
|
466
466
|
}
|
|
467
467
|
|
|
468
|
-
//
|
|
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
|
|
473
|
+
error('npm is not installed.');
|
|
474
474
|
}
|
|
475
475
|
|
|
476
|
-
//
|
|
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
|
|
481
|
+
warning('jq is not installed. Installing...');
|
|
482
482
|
if (installPackage('jq', sudoPassword)) {
|
|
483
|
-
success('jq
|
|
483
|
+
success('jq installed successfully');
|
|
484
484
|
} else {
|
|
485
|
-
warning('
|
|
485
|
+
warning('Could not install jq automatically');
|
|
486
486
|
if (os.platform() === 'linux') {
|
|
487
|
-
console.log('
|
|
487
|
+
console.log('Install it manually with: sudo apt install jq');
|
|
488
488
|
} else if (os.platform() === 'darwin') {
|
|
489
|
-
console.log('
|
|
489
|
+
console.log('Install it manually with: brew install jq');
|
|
490
490
|
}
|
|
491
491
|
}
|
|
492
492
|
}
|
|
493
493
|
|
|
494
|
-
//
|
|
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
|
|
499
|
+
warning('curl is not installed. Installing...');
|
|
500
500
|
if (installPackage('curl', sudoPassword)) {
|
|
501
|
-
success('curl
|
|
501
|
+
success('curl installed successfully');
|
|
502
502
|
} else {
|
|
503
|
-
warning('
|
|
503
|
+
warning('Could not install curl automatically');
|
|
504
504
|
if (os.platform() === 'linux') {
|
|
505
|
-
console.log('
|
|
505
|
+
console.log('Install it manually with: sudo apt install curl');
|
|
506
506
|
} else if (os.platform() === 'darwin') {
|
|
507
|
-
console.log('
|
|
507
|
+
console.log('Install it manually with: brew install curl');
|
|
508
508
|
}
|
|
509
509
|
}
|
|
510
510
|
}
|
|
511
511
|
|
|
512
|
-
//
|
|
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
|
|
517
|
+
error('Git is not installed. Install Git and try again.');
|
|
518
518
|
}
|
|
519
519
|
|
|
520
|
-
//
|
|
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('
|
|
533
|
+
success('Standard Unix tools verified');
|
|
534
534
|
} else {
|
|
535
|
-
error(`
|
|
535
|
+
error(`Missing standard Unix tools: ${missingTools.join(', ')}. Retry installation in an Ubuntu console`);
|
|
536
536
|
}
|
|
537
537
|
|
|
538
|
-
//
|
|
538
|
+
// Check and install Claude CLI
|
|
539
539
|
await checkAndInstallClaude();
|
|
540
540
|
|
|
541
|
-
//
|
|
541
|
+
// Check Claude authentication (if not skipped)
|
|
542
542
|
if (!skipAuth) {
|
|
543
543
|
await checkClaudeAuth();
|
|
544
544
|
} else {
|
|
545
|
-
warning('
|
|
545
|
+
warning('Skipping Claude authentication verification (--skip-auth)');
|
|
546
546
|
}
|
|
547
547
|
|
|
548
|
-
//
|
|
548
|
+
// Clear password from memory
|
|
549
549
|
sudoPassword = null;
|
|
550
550
|
}
|
|
551
551
|
|
|
552
|
-
//
|
|
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; //
|
|
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; //
|
|
568
|
+
return true; // Needs Claude installation
|
|
569
569
|
}
|
|
570
570
|
|
|
571
571
|
return false;
|
|
572
572
|
}
|
|
573
573
|
|
|
574
|
-
//
|
|
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
|
|
578
|
+
success('Claude CLI detected');
|
|
579
579
|
} catch (e) {
|
|
580
|
-
info('Claude CLI
|
|
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
|
|
583
|
+
success('Claude CLI installed successfully');
|
|
584
584
|
} catch (installError) {
|
|
585
|
-
error('Error
|
|
585
|
+
error('Error installing Claude CLI. Install manually: npm install -g @anthropic-ai/claude-cli');
|
|
586
586
|
}
|
|
587
587
|
}
|
|
588
588
|
}
|
|
589
589
|
|
|
590
|
-
//
|
|
590
|
+
// Check Claude authentication with entertainment
|
|
591
591
|
async function checkClaudeAuth() {
|
|
592
|
-
info('
|
|
592
|
+
info('Checking Claude authentication...');
|
|
593
593
|
|
|
594
|
-
//
|
|
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
|
-
//
|
|
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, '
|
|
625
|
-
success('
|
|
624
|
+
await Entertainment.showSpinner(authPromise, 'Checking Claude authentication');
|
|
625
|
+
success('Authenticated in Claude');
|
|
626
626
|
} catch (e) {
|
|
627
|
-
warning('
|
|
628
|
-
console.log('
|
|
629
|
-
console.log('
|
|
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
|
-
//
|
|
633
|
+
// Update .gitignore with Claude entries
|
|
634
634
|
function updateGitignore() {
|
|
635
|
-
info('
|
|
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
|
-
//
|
|
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
|
-
//
|
|
655
|
+
// Check which entries are missing
|
|
656
656
|
const missingEntries = [];
|
|
657
657
|
claudeEntries.forEach(entry => {
|
|
658
658
|
if (entry.startsWith('#')) {
|
|
659
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
672
|
+
// If there are missing entries, add them
|
|
673
673
|
if (missingEntries.length > 0) {
|
|
674
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
684
|
+
// Add the missing entries
|
|
685
685
|
gitignoreContent += missingEntries.join('\n') + '\n';
|
|
686
686
|
|
|
687
|
-
//
|
|
687
|
+
// Write the updated file
|
|
688
688
|
fs.writeFileSync(gitignorePath, gitignoreContent);
|
|
689
689
|
|
|
690
690
|
if (fileExists) {
|
|
691
|
-
success('.gitignore
|
|
691
|
+
success('.gitignore updated with Claude entries');
|
|
692
692
|
} else {
|
|
693
|
-
success('.gitignore
|
|
693
|
+
success('.gitignore created with Claude entries');
|
|
694
694
|
}
|
|
695
695
|
|
|
696
|
-
//
|
|
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
|
|
703
|
+
info('.gitignore already contains all necessary entries');
|
|
704
704
|
}
|
|
705
705
|
}
|
|
706
706
|
|
|
707
|
-
//
|
|
707
|
+
// Configure Git (line endings, etc.)
|
|
708
708
|
function configureGit() {
|
|
709
|
-
info('
|
|
709
|
+
info('Configuring Git...');
|
|
710
710
|
|
|
711
711
|
try {
|
|
712
|
-
//
|
|
712
|
+
// Configure line endings for WSL
|
|
713
713
|
execSync('git config core.autocrlf input', { stdio: 'ignore' });
|
|
714
|
-
success('Line endings
|
|
714
|
+
success('Line endings configured for WSL (core.autocrlf = input)');
|
|
715
715
|
|
|
716
|
-
//
|
|
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
|
|
719
|
+
success('Line endings configured for Windows (core.autocrlf = true)');
|
|
720
720
|
} catch (psError) {
|
|
721
|
-
info('
|
|
721
|
+
info('Could not configure automatically on Windows');
|
|
722
722
|
}
|
|
723
723
|
|
|
724
724
|
} catch (e) {
|
|
725
|
-
warning('Error
|
|
725
|
+
warning('Error configuring Git');
|
|
726
726
|
}
|
|
727
727
|
}
|
|
728
728
|
|
|
729
|
-
//
|
|
729
|
+
// Uninstall command
|
|
730
730
|
function uninstall() {
|
|
731
731
|
if (!checkGitRepo()) {
|
|
732
|
-
error('
|
|
732
|
+
error('You are not in a Git repository.');
|
|
733
733
|
}
|
|
734
734
|
|
|
735
|
-
info('
|
|
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}
|
|
744
|
+
success(`${hook} removed`);
|
|
745
745
|
}
|
|
746
746
|
});
|
|
747
747
|
|
|
748
|
-
success('Claude Git Hooks
|
|
748
|
+
success('Claude Git Hooks uninstalled');
|
|
749
749
|
}
|
|
750
750
|
|
|
751
|
-
//
|
|
751
|
+
// Enable command
|
|
752
752
|
function enable(hookName) {
|
|
753
753
|
if (!checkGitRepo()) {
|
|
754
|
-
error('
|
|
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}
|
|
765
|
+
success(`${hook} enabled`);
|
|
766
766
|
} else if (fs.existsSync(enabledPath)) {
|
|
767
|
-
info(`${hook}
|
|
767
|
+
info(`${hook} is already enabled`);
|
|
768
768
|
} else {
|
|
769
|
-
warning(`${hook}
|
|
769
|
+
warning(`${hook} not found`);
|
|
770
770
|
}
|
|
771
771
|
});
|
|
772
772
|
}
|
|
773
773
|
|
|
774
|
-
//
|
|
774
|
+
// Disable command
|
|
775
775
|
function disable(hookName) {
|
|
776
776
|
if (!checkGitRepo()) {
|
|
777
|
-
error('
|
|
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,115 +785,110 @@ function disable(hookName) {
|
|
|
785
785
|
|
|
786
786
|
if (fs.existsSync(enabledPath)) {
|
|
787
787
|
fs.renameSync(enabledPath, disabledPath);
|
|
788
|
-
success(`${hook}
|
|
788
|
+
success(`${hook} disabled`);
|
|
789
789
|
} else if (fs.existsSync(disabledPath)) {
|
|
790
|
-
info(`${hook}
|
|
790
|
+
info(`${hook} is already disabled`);
|
|
791
791
|
} else {
|
|
792
|
-
warning(`${hook}
|
|
792
|
+
warning(`${hook} not found`);
|
|
793
793
|
}
|
|
794
794
|
});
|
|
795
795
|
}
|
|
796
796
|
|
|
797
|
-
//
|
|
797
|
+
// Analyze-diff command
|
|
798
798
|
function analyzeDiff(args) {
|
|
799
799
|
if (!checkGitRepo()) {
|
|
800
|
-
error('
|
|
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('
|
|
807
|
+
error('You are not in a valid branch.');
|
|
808
808
|
return;
|
|
809
809
|
}
|
|
810
810
|
|
|
811
|
+
// Update remote references
|
|
812
|
+
execSync('git fetch', { stdio: 'ignore' });
|
|
813
|
+
|
|
811
814
|
let baseBranch, compareWith, contextDescription;
|
|
812
815
|
|
|
813
816
|
if (args[0]) {
|
|
814
|
-
//
|
|
815
|
-
|
|
817
|
+
// Case with argument: compare current branch vs origin/specified-branch
|
|
818
|
+
const targetBranch = args[0];
|
|
819
|
+
baseBranch = `origin/${targetBranch}`;
|
|
816
820
|
compareWith = `${baseBranch}...HEAD`;
|
|
817
821
|
contextDescription = `${currentBranch} vs ${baseBranch}`;
|
|
818
822
|
|
|
819
|
-
//
|
|
823
|
+
// Check that the origin branch exists
|
|
820
824
|
try {
|
|
821
825
|
execSync(`git rev-parse --verify ${baseBranch}`, { stdio: 'ignore' });
|
|
822
826
|
} catch (e) {
|
|
823
|
-
error(`
|
|
827
|
+
error(`Branch ${baseBranch} does not exist.`);
|
|
824
828
|
return;
|
|
825
829
|
}
|
|
826
830
|
} else {
|
|
827
|
-
//
|
|
831
|
+
// Case without argument: compare current branch vs origin/current-branch
|
|
832
|
+
baseBranch = `origin/${currentBranch}`;
|
|
833
|
+
compareWith = `${baseBranch}...HEAD`;
|
|
834
|
+
contextDescription = `${currentBranch} vs ${baseBranch}`;
|
|
835
|
+
|
|
836
|
+
// Check that the origin branch exists
|
|
828
837
|
try {
|
|
829
|
-
|
|
830
|
-
const remoteBranch = execSync(`git rev-parse --abbrev-ref ${currentBranch}@{upstream}`, { encoding: 'utf8' }).trim();
|
|
831
|
-
|
|
832
|
-
// Actualizar referencias remotas
|
|
833
|
-
execSync('git fetch', { stdio: 'ignore' });
|
|
834
|
-
|
|
835
|
-
baseBranch = remoteBranch;
|
|
836
|
-
compareWith = `HEAD..${remoteBranch}`;
|
|
837
|
-
contextDescription = `cambios locales sin pushear en ${currentBranch}`;
|
|
838
|
-
|
|
839
|
-
info(`Comparando cambios locales vs remoto: ${remoteBranch}`);
|
|
838
|
+
execSync(`git rev-parse --verify ${baseBranch}`, { stdio: 'ignore' });
|
|
840
839
|
} catch (e) {
|
|
841
|
-
//
|
|
842
|
-
baseBranch = 'develop';
|
|
840
|
+
// Try fallback to origin/develop
|
|
841
|
+
baseBranch = 'origin/develop';
|
|
843
842
|
compareWith = `${baseBranch}...HEAD`;
|
|
844
|
-
contextDescription = `${currentBranch} vs ${baseBranch} (
|
|
843
|
+
contextDescription = `${currentBranch} vs ${baseBranch} (fallback)`;
|
|
845
844
|
|
|
846
845
|
try {
|
|
847
846
|
execSync(`git rev-parse --verify ${baseBranch}`, { stdio: 'ignore' });
|
|
848
|
-
warning(`
|
|
847
|
+
warning(`Branch origin/${currentBranch} does not exist. Using ${baseBranch} as fallback.`);
|
|
849
848
|
} catch (e2) {
|
|
850
|
-
|
|
849
|
+
// Try fallback to origin/main
|
|
850
|
+
baseBranch = 'origin/main';
|
|
851
851
|
compareWith = `${baseBranch}...HEAD`;
|
|
852
852
|
contextDescription = `${currentBranch} vs ${baseBranch} (fallback)`;
|
|
853
853
|
|
|
854
854
|
try {
|
|
855
855
|
execSync(`git rev-parse --verify ${baseBranch}`, { stdio: 'ignore' });
|
|
856
|
-
warning(`No
|
|
856
|
+
warning(`No origin/develop branch. Using ${baseBranch} as fallback.`);
|
|
857
857
|
} catch (e3) {
|
|
858
|
-
error('
|
|
858
|
+
error('Could not find a valid comparison branch (tried origin/current, origin/develop, origin/main).');
|
|
859
859
|
return;
|
|
860
860
|
}
|
|
861
861
|
}
|
|
862
862
|
}
|
|
863
863
|
}
|
|
864
864
|
|
|
865
|
-
info(`
|
|
865
|
+
info(`Analyzing: ${contextDescription}...`);
|
|
866
866
|
|
|
867
|
-
//
|
|
867
|
+
// Get modified files
|
|
868
868
|
let diffFiles;
|
|
869
869
|
try {
|
|
870
|
-
|
|
871
|
-
if (!args[0] && compareWith.includes('HEAD..')) {
|
|
872
|
-
diffFiles = execSync(`git diff ${compareWith} --name-only`, { encoding: 'utf8' }).trim();
|
|
873
|
-
} else {
|
|
874
|
-
diffFiles = execSync(`git diff ${compareWith} --name-only`, { encoding: 'utf8' }).trim();
|
|
875
|
-
}
|
|
870
|
+
diffFiles = execSync(`git diff ${compareWith} --name-only`, { encoding: 'utf8' }).trim();
|
|
876
871
|
|
|
877
872
|
if (!diffFiles) {
|
|
878
|
-
//
|
|
873
|
+
// Check if there are staged or unstaged changes
|
|
879
874
|
const stagedFiles = execSync('git diff --cached --name-only', { encoding: 'utf8' }).trim();
|
|
880
875
|
const unstagedFiles = execSync('git diff --name-only', { encoding: 'utf8' }).trim();
|
|
881
876
|
|
|
882
877
|
if (stagedFiles || unstagedFiles) {
|
|
883
|
-
warning('No
|
|
884
|
-
console.log('
|
|
885
|
-
console.log('
|
|
878
|
+
warning('No differences with remote, but you have uncommitted local changes.');
|
|
879
|
+
console.log('Staged changes:', stagedFiles || 'none');
|
|
880
|
+
console.log('Unstaged changes:', unstagedFiles || 'none');
|
|
886
881
|
} else {
|
|
887
|
-
success('✅ No
|
|
882
|
+
success('✅ No differences. Your branch is synchronized.');
|
|
888
883
|
}
|
|
889
884
|
return;
|
|
890
885
|
}
|
|
891
886
|
} catch (e) {
|
|
892
|
-
error('Error
|
|
887
|
+
error('Error getting differences: ' + e.message);
|
|
893
888
|
return;
|
|
894
889
|
}
|
|
895
890
|
|
|
896
|
-
//
|
|
891
|
+
// Get the complete diff
|
|
897
892
|
let fullDiff, commits;
|
|
898
893
|
try {
|
|
899
894
|
if (!args[0] && compareWith.includes('HEAD..')) {
|
|
@@ -904,50 +899,50 @@ function analyzeDiff(args) {
|
|
|
904
899
|
commits = execSync(`git log ${baseBranch}..HEAD --oneline`, { encoding: 'utf8' }).trim();
|
|
905
900
|
}
|
|
906
901
|
} catch (e) {
|
|
907
|
-
error('Error
|
|
902
|
+
error('Error getting diff or commits: ' + e.message);
|
|
908
903
|
return;
|
|
909
904
|
}
|
|
910
905
|
|
|
911
|
-
//
|
|
906
|
+
// Create the prompt for Claude
|
|
912
907
|
const tempDir = `/tmp/claude-analyze-${Date.now()}`;
|
|
913
908
|
fs.mkdirSync(tempDir, { recursive: true });
|
|
914
909
|
|
|
915
910
|
const promptFile = path.join(tempDir, 'prompt.txt');
|
|
916
|
-
const prompt = `
|
|
911
|
+
const prompt = `Analyze the following changes. CONTEXT: ${contextDescription}
|
|
917
912
|
|
|
918
|
-
|
|
919
|
-
1.
|
|
920
|
-
2.
|
|
921
|
-
-
|
|
922
|
-
-
|
|
923
|
-
-
|
|
924
|
-
-
|
|
925
|
-
3.
|
|
913
|
+
Please generate:
|
|
914
|
+
1. A concise and descriptive PR title (maximum 72 characters)
|
|
915
|
+
2. A detailed PR description that includes:
|
|
916
|
+
- Summary of changes
|
|
917
|
+
- Motivation/context
|
|
918
|
+
- Type of change (feature/fix/refactor/docs/etc)
|
|
919
|
+
- Recommended testing
|
|
920
|
+
3. A suggested branch name following the format: type/short-description (example: feature/add-user-auth, fix/memory-leak)
|
|
926
921
|
|
|
927
|
-
|
|
922
|
+
IMPORTANT: If these are local changes without push, the suggested branch name should be for creating a new branch from the current one.
|
|
928
923
|
|
|
929
|
-
|
|
924
|
+
Respond EXCLUSIVELY with a valid JSON with this structure:
|
|
930
925
|
{
|
|
931
|
-
"prTitle": "
|
|
932
|
-
"prDescription": "
|
|
933
|
-
"suggestedBranchName": "
|
|
926
|
+
"prTitle": "Interesting PR title",
|
|
927
|
+
"prDescription": "detailed PR description with markdown",
|
|
928
|
+
"suggestedBranchName": "type/suggested-branch-name",
|
|
934
929
|
"changeType": "feature|fix|refactor|docs|test|chore",
|
|
935
930
|
"breakingChanges": false,
|
|
936
|
-
"testingNotes": "
|
|
931
|
+
"testingNotes": "notes on necessary testing or 'None'"
|
|
937
932
|
}
|
|
938
933
|
|
|
939
934
|
=== COMMITS ===
|
|
940
935
|
${commits}
|
|
941
936
|
|
|
942
|
-
===
|
|
937
|
+
=== CHANGED FILES ===
|
|
943
938
|
${diffFiles}
|
|
944
939
|
|
|
945
|
-
=== DIFF
|
|
946
|
-
${fullDiff.substring(0, 50000)} ${fullDiff.length > 50000 ? '\n... (diff
|
|
940
|
+
=== FULL DIFF ===
|
|
941
|
+
${fullDiff.substring(0, 50000)} ${fullDiff.length > 50000 ? '\n... (truncated diff)' : ''}`;
|
|
947
942
|
|
|
948
943
|
fs.writeFileSync(promptFile, prompt);
|
|
949
944
|
|
|
950
|
-
info('
|
|
945
|
+
info('Sending to Claude for analysis...');
|
|
951
946
|
|
|
952
947
|
try {
|
|
953
948
|
const response = execSync(`claude < "${promptFile}"`, { encoding: 'utf8', maxBuffer: 1024 * 1024 * 10 });
|
|
@@ -955,8 +950,8 @@ ${fullDiff.substring(0, 50000)} ${fullDiff.length > 50000 ? '\n... (diff truncad
|
|
|
955
950
|
// Extraer el JSON de la respuesta
|
|
956
951
|
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
957
952
|
if (!jsonMatch) {
|
|
958
|
-
error('
|
|
959
|
-
console.log('
|
|
953
|
+
error('Did not receive a valid JSON response from Claude.');
|
|
954
|
+
console.log('Complete response:', response);
|
|
960
955
|
return;
|
|
961
956
|
}
|
|
962
957
|
|
|
@@ -964,45 +959,45 @@ ${fullDiff.substring(0, 50000)} ${fullDiff.length > 50000 ? '\n... (diff truncad
|
|
|
964
959
|
try {
|
|
965
960
|
result = JSON.parse(jsonMatch[0]);
|
|
966
961
|
} catch (e) {
|
|
967
|
-
error('Error
|
|
968
|
-
console.log('JSON
|
|
962
|
+
error('Error parsing JSON response: ' + e.message);
|
|
963
|
+
console.log('JSON received:', jsonMatch[0]);
|
|
969
964
|
return;
|
|
970
965
|
}
|
|
971
966
|
|
|
972
|
-
//
|
|
967
|
+
// Show the results
|
|
973
968
|
console.log('');
|
|
974
|
-
console.log('
|
|
975
|
-
console.log('
|
|
976
|
-
console.log('
|
|
969
|
+
console.log('════════════════════════════════════════════════════════════════');
|
|
970
|
+
console.log(' DIFFERENCES ANALYSIS ');
|
|
971
|
+
console.log('════════════════════════════════════════════════════════════════');
|
|
977
972
|
console.log('');
|
|
978
973
|
|
|
979
|
-
console.log(`🔍 ${colors.blue}
|
|
980
|
-
console.log(`📊 ${colors.blue}
|
|
974
|
+
console.log(`🔍 ${colors.blue}Context:${colors.reset} ${contextDescription}`);
|
|
975
|
+
console.log(`📊 ${colors.blue}Changed Files:${colors.reset} ${diffFiles.split('\n').length}`);
|
|
981
976
|
console.log('');
|
|
982
977
|
|
|
983
|
-
console.log(`📝 ${colors.green}
|
|
978
|
+
console.log(`📝 ${colors.green}Pull Request Title:${colors.reset}`);
|
|
984
979
|
console.log(` ${result.prTitle}`);
|
|
985
980
|
console.log('');
|
|
986
981
|
|
|
987
|
-
console.log(`🌿 ${colors.green}
|
|
982
|
+
console.log(`🌿 ${colors.green}Suggested branch name:${colors.reset}`);
|
|
988
983
|
console.log(` ${result.suggestedBranchName}`);
|
|
989
984
|
console.log('');
|
|
990
985
|
|
|
991
|
-
console.log(`📋 ${colors.green}
|
|
986
|
+
console.log(`📋 ${colors.green}Type of change:${colors.reset} ${result.changeType}`);
|
|
992
987
|
|
|
993
988
|
if (result.breakingChanges) {
|
|
994
989
|
console.log(`⚠️ ${colors.yellow}Breaking Changes: SÍ${colors.reset}`);
|
|
995
990
|
}
|
|
996
991
|
|
|
997
992
|
console.log('');
|
|
998
|
-
console.log(`📄 ${colors.green}
|
|
993
|
+
console.log(`📄 ${colors.green}Pull Request Description:${colors.reset}`);
|
|
999
994
|
console.log('───────────────────────────────────────────────────────────────');
|
|
1000
995
|
console.log(result.prDescription);
|
|
1001
996
|
console.log('───────────────────────────────────────────────────────────────');
|
|
1002
997
|
|
|
1003
998
|
if (result.testingNotes) {
|
|
1004
999
|
console.log('');
|
|
1005
|
-
console.log(`🧪 ${colors.green}
|
|
1000
|
+
console.log(`🧪 ${colors.green}Testing notes:${colors.reset}`);
|
|
1006
1001
|
console.log(result.testingNotes);
|
|
1007
1002
|
}
|
|
1008
1003
|
|
|
@@ -1021,27 +1016,27 @@ ${fullDiff.substring(0, 50000)} ${fullDiff.length > 50000 ? '\n... (diff truncad
|
|
|
1021
1016
|
const outputFile = '.claude-pr-analysis.json';
|
|
1022
1017
|
fs.writeFileSync(outputFile, JSON.stringify(outputData, null, 2));
|
|
1023
1018
|
console.log('');
|
|
1024
|
-
info(`
|
|
1019
|
+
info(`Results saved in ${outputFile}`);
|
|
1025
1020
|
|
|
1026
1021
|
// Sugerencias contextuales
|
|
1027
1022
|
console.log('');
|
|
1028
|
-
if (!args[0] && contextDescription.includes('
|
|
1029
|
-
//
|
|
1030
|
-
console.log(`💡 ${colors.yellow}
|
|
1023
|
+
if (!args[0] && contextDescription.includes('local changes without push')) {
|
|
1024
|
+
// Case of local changes without push
|
|
1025
|
+
console.log(`💡 ${colors.yellow}To create new branch with these changes:${colors.reset}`);
|
|
1031
1026
|
console.log(` git checkout -b ${result.suggestedBranchName}`);
|
|
1032
1027
|
console.log(` git push -u origin ${result.suggestedBranchName}`);
|
|
1033
1028
|
} else if (currentBranch !== result.suggestedBranchName) {
|
|
1034
1029
|
// Caso normal de comparación entre ramas
|
|
1035
|
-
console.log(`💡 ${colors.yellow}
|
|
1030
|
+
console.log(`💡 ${colors.yellow}For renaming your current branch:${colors.reset}`);
|
|
1036
1031
|
console.log(` git branch -m ${result.suggestedBranchName}`);
|
|
1037
1032
|
}
|
|
1038
1033
|
|
|
1039
|
-
console.log(`💡 ${colors.yellow}Tip:${colors.reset}
|
|
1034
|
+
console.log(`💡 ${colors.yellow}Tip:${colors.reset} Use this information to create your PR on GitHub.`);
|
|
1040
1035
|
|
|
1041
1036
|
} catch (e) {
|
|
1042
|
-
error('Error
|
|
1037
|
+
error('Error executing Claude: ' + e.message);
|
|
1043
1038
|
} finally {
|
|
1044
|
-
//
|
|
1039
|
+
// Clean temporary files
|
|
1045
1040
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
1046
1041
|
}
|
|
1047
1042
|
}
|
|
@@ -1049,10 +1044,10 @@ ${fullDiff.substring(0, 50000)} ${fullDiff.length > 50000 ? '\n... (diff truncad
|
|
|
1049
1044
|
// Comando status
|
|
1050
1045
|
function status() {
|
|
1051
1046
|
if (!checkGitRepo()) {
|
|
1052
|
-
error('
|
|
1047
|
+
error('You are not in a Git repository.');
|
|
1053
1048
|
}
|
|
1054
1049
|
|
|
1055
|
-
info('
|
|
1050
|
+
info('Claude Git Hooks status:\n');
|
|
1056
1051
|
|
|
1057
1052
|
const hooks = ['pre-commit', 'prepare-commit-msg'];
|
|
1058
1053
|
hooks.forEach(hook => {
|
|
@@ -1060,25 +1055,25 @@ function status() {
|
|
|
1060
1055
|
const disabledPath = `.git/hooks/${hook}.disabled`;
|
|
1061
1056
|
|
|
1062
1057
|
if (fs.existsSync(enabledPath)) {
|
|
1063
|
-
success(`${hook}:
|
|
1058
|
+
success(`${hook}: enabled`);
|
|
1064
1059
|
} else if (fs.existsSync(disabledPath)) {
|
|
1065
|
-
warning(`${hook}:
|
|
1060
|
+
warning(`${hook}: disabled`);
|
|
1066
1061
|
} else {
|
|
1067
|
-
error(`${hook}:
|
|
1062
|
+
error(`${hook}: not installed`);
|
|
1068
1063
|
}
|
|
1069
1064
|
});
|
|
1070
1065
|
|
|
1071
|
-
//
|
|
1072
|
-
console.log('\
|
|
1066
|
+
// Check guidelines files
|
|
1067
|
+
console.log('\nGuidelines files:');
|
|
1073
1068
|
const guidelines = ['CLAUDE_PRE_COMMIT_SONAR.md'];
|
|
1074
1069
|
guidelines.forEach(guideline => {
|
|
1075
1070
|
const claudePath = path.join('.claude', guideline);
|
|
1076
1071
|
if (fs.existsSync(claudePath)) {
|
|
1077
|
-
success(`${guideline}:
|
|
1072
|
+
success(`${guideline}: present in .claude/`);
|
|
1078
1073
|
} else if (fs.existsSync(guideline)) {
|
|
1079
|
-
warning(`${guideline}:
|
|
1074
|
+
warning(`${guideline}: present in root (should be in .claude/)`);
|
|
1080
1075
|
} else {
|
|
1081
|
-
warning(`${guideline}:
|
|
1076
|
+
warning(`${guideline}: missing`);
|
|
1082
1077
|
}
|
|
1083
1078
|
});
|
|
1084
1079
|
|
|
@@ -1093,25 +1088,25 @@ function status() {
|
|
|
1093
1088
|
claudeIgnores.forEach(entry => {
|
|
1094
1089
|
const regex = new RegExp(`^${entry.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'm');
|
|
1095
1090
|
if (regex.test(gitignoreContent)) {
|
|
1096
|
-
success(`${entry}:
|
|
1091
|
+
success(`${entry}: included`);
|
|
1097
1092
|
} else {
|
|
1098
|
-
warning(`${entry}:
|
|
1093
|
+
warning(`${entry}: missing`);
|
|
1099
1094
|
allPresent = false;
|
|
1100
1095
|
}
|
|
1101
1096
|
});
|
|
1102
1097
|
|
|
1103
1098
|
if (!allPresent) {
|
|
1104
|
-
info('\
|
|
1099
|
+
info('\nRun "claude-hooks install" to update .gitignore');
|
|
1105
1100
|
}
|
|
1106
1101
|
} else {
|
|
1107
|
-
warning('.gitignore
|
|
1102
|
+
warning('.gitignore doesn´t exist');
|
|
1108
1103
|
}
|
|
1109
1104
|
}
|
|
1110
1105
|
|
|
1111
1106
|
// Comando set-mode
|
|
1112
1107
|
function setMode(mode) {
|
|
1113
1108
|
if (!checkGitRepo()) {
|
|
1114
|
-
error('
|
|
1109
|
+
error('You are not in a Git repository.');
|
|
1115
1110
|
}
|
|
1116
1111
|
}
|
|
1117
1112
|
|
|
@@ -1147,9 +1142,9 @@ function compareVersions(v1, v2) {
|
|
|
1147
1142
|
}
|
|
1148
1143
|
}
|
|
1149
1144
|
|
|
1150
|
-
//
|
|
1145
|
+
// Update command - update to the latest version
|
|
1151
1146
|
async function update() {
|
|
1152
|
-
info('
|
|
1147
|
+
info('Checking latest available version...');
|
|
1153
1148
|
|
|
1154
1149
|
try {
|
|
1155
1150
|
const currentVersion = require('../package.json').version;
|
|
@@ -1158,40 +1153,40 @@ async function update() {
|
|
|
1158
1153
|
const comparison = compareVersions(currentVersion, latestVersion);
|
|
1159
1154
|
|
|
1160
1155
|
if (comparison === 0) {
|
|
1161
|
-
success(`
|
|
1156
|
+
success(`You already have the latest version installed (${currentVersion})`);
|
|
1162
1157
|
return;
|
|
1163
1158
|
} else if (comparison > 0) {
|
|
1164
|
-
info(`
|
|
1165
|
-
info(
|
|
1166
|
-
success(`
|
|
1159
|
+
info(`You are using a development version (${currentVersion})`);
|
|
1160
|
+
info(`Latest published version: ${latestVersion}`);
|
|
1161
|
+
success(`You already have the latest version installed (${currentVersion})`);
|
|
1167
1162
|
return;
|
|
1168
1163
|
}
|
|
1169
1164
|
|
|
1170
|
-
info(`
|
|
1171
|
-
info(`
|
|
1165
|
+
info(`Current version: ${currentVersion}`);
|
|
1166
|
+
info(`Available version: ${latestVersion}`);
|
|
1172
1167
|
|
|
1173
1168
|
// Actualizar el paquete
|
|
1174
|
-
info('
|
|
1169
|
+
info('Updating claude-git-hooks...');
|
|
1175
1170
|
try {
|
|
1176
1171
|
execSync('npm install -g claude-git-hooks@latest', { stdio: 'inherit' });
|
|
1177
|
-
success(`
|
|
1172
|
+
success(`Successfully updated to version ${latestVersion}`);
|
|
1178
1173
|
|
|
1179
|
-
//
|
|
1180
|
-
info('
|
|
1174
|
+
// Reinstall hooks with the new version
|
|
1175
|
+
info('Reinstalling hooks with the new version...');
|
|
1181
1176
|
await install(['--force']);
|
|
1182
1177
|
|
|
1183
1178
|
} catch (updateError) {
|
|
1184
|
-
error('Error
|
|
1179
|
+
error('Error updating. Try running: npm install -g claude-git-hooks@latest');
|
|
1185
1180
|
}
|
|
1186
1181
|
} catch (e) {
|
|
1187
|
-
warning('
|
|
1188
|
-
warning('
|
|
1182
|
+
warning('Could not check the latest available version');
|
|
1183
|
+
warning('Trying to update anyway...');
|
|
1189
1184
|
try {
|
|
1190
1185
|
execSync('npm install -g claude-git-hooks@latest', { stdio: 'inherit' });
|
|
1191
|
-
success('
|
|
1186
|
+
success('Update completed');
|
|
1192
1187
|
await install(['--force']);
|
|
1193
1188
|
} catch (updateError) {
|
|
1194
|
-
error('Error
|
|
1189
|
+
error('Error updating: ' + updateError.message);
|
|
1195
1190
|
}
|
|
1196
1191
|
}
|
|
1197
1192
|
}
|
|
@@ -1199,54 +1194,54 @@ async function update() {
|
|
|
1199
1194
|
// Comando help
|
|
1200
1195
|
function showHelp() {
|
|
1201
1196
|
console.log(`
|
|
1202
|
-
Claude Git Hooks -
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
install [
|
|
1208
|
-
--force
|
|
1209
|
-
--skip-auth
|
|
1210
|
-
update
|
|
1211
|
-
uninstall
|
|
1212
|
-
enable [hook]
|
|
1213
|
-
disable [hook]
|
|
1214
|
-
status
|
|
1215
|
-
analyze-diff [base]
|
|
1216
|
-
help
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
pre-commit
|
|
1220
|
-
prepare-commit-msg
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
claude-hooks install #
|
|
1224
|
-
claude-hooks install --skip-auth #
|
|
1225
|
-
claude-hooks update #
|
|
1226
|
-
claude-hooks disable pre-commit #
|
|
1227
|
-
claude-hooks enable #
|
|
1228
|
-
claude-hooks status #
|
|
1229
|
-
claude-hooks analyze-diff main #
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
git commit -m "
|
|
1233
|
-
git commit -m "auto" #
|
|
1234
|
-
git commit --no-verify -m "auto" #
|
|
1235
|
-
git commit --no-verify -m "msg" #
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
claude-hooks analyze-diff main #
|
|
1239
|
-
→
|
|
1240
|
-
→
|
|
1241
|
-
→
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
// SKIP-ANALYSIS #
|
|
1245
|
-
// SKIP_ANALYSIS_BLOCK #
|
|
1246
|
-
...
|
|
1197
|
+
Claude Git Hooks - Code analysis and automatic messages with Claude CLI
|
|
1198
|
+
|
|
1199
|
+
Usage: claude-hooks <command> [options]
|
|
1200
|
+
|
|
1201
|
+
Commands:
|
|
1202
|
+
install [options] Install hooks in the current repository
|
|
1203
|
+
--force Reinstall even if they already exist
|
|
1204
|
+
--skip-auth Skip Claude authentication verification
|
|
1205
|
+
update Update to the latest available version
|
|
1206
|
+
uninstall Uninstall hooks from the repository
|
|
1207
|
+
enable [hook] Enable hooks (all or one specific)
|
|
1208
|
+
disable [hook] Disable hooks (all or one specific)
|
|
1209
|
+
status Show the status of hooks
|
|
1210
|
+
analyze-diff [base] Analyze differences between branches and generate PR info
|
|
1211
|
+
help Show this help
|
|
1212
|
+
|
|
1213
|
+
Available hooks:
|
|
1214
|
+
pre-commit Code analysis before commit
|
|
1215
|
+
prepare-commit-msg Automatic message generation
|
|
1216
|
+
|
|
1217
|
+
Examples:
|
|
1218
|
+
claude-hooks install # Install all hooks
|
|
1219
|
+
claude-hooks install --skip-auth # Install without verifying authentication
|
|
1220
|
+
claude-hooks update # Update to the latest version
|
|
1221
|
+
claude-hooks disable pre-commit # Disable only pre-commit
|
|
1222
|
+
claude-hooks enable # Enable all hooks
|
|
1223
|
+
claude-hooks status # View current status
|
|
1224
|
+
claude-hooks analyze-diff main # Analyze differences with main
|
|
1225
|
+
|
|
1226
|
+
Commit use cases:
|
|
1227
|
+
git commit -m "message" # Manual message + blocking analysis
|
|
1228
|
+
git commit -m "auto" # Automatic message + blocking analysis
|
|
1229
|
+
git commit --no-verify -m "auto" # Automatic message without analysis
|
|
1230
|
+
git commit --no-verify -m "msg" # Manual message without analysis
|
|
1231
|
+
|
|
1232
|
+
Analyze-diff use case:
|
|
1233
|
+
claude-hooks analyze-diff main # Analyze changes vs main and generate:
|
|
1234
|
+
→ PR Title: "feat: add user authentication module"
|
|
1235
|
+
→ PR Description: "## Summary\n- Added JWT authentication..."
|
|
1236
|
+
→ Suggested branch: "feature/user-authentication"
|
|
1237
|
+
|
|
1238
|
+
Exclude code from analysis:
|
|
1239
|
+
// SKIP-ANALYSIS # Exclude the next line from analysis
|
|
1240
|
+
// SKIP_ANALYSIS_BLOCK # Exclude block until finding another equal one
|
|
1241
|
+
...excluded code...
|
|
1247
1242
|
// SKIP_ANALYSIS_BLOCK
|
|
1248
1243
|
|
|
1249
|
-
|
|
1244
|
+
More information: https://github.com/pablorovito/claude-git-hooks
|
|
1250
1245
|
`);
|
|
1251
1246
|
}
|
|
1252
1247
|
|
|
@@ -1284,12 +1279,12 @@ async function main() {
|
|
|
1284
1279
|
showHelp();
|
|
1285
1280
|
break;
|
|
1286
1281
|
default:
|
|
1287
|
-
error(`
|
|
1282
|
+
error(`Unknown command: ${command}`);
|
|
1288
1283
|
showHelp();
|
|
1289
1284
|
}
|
|
1290
1285
|
}
|
|
1291
1286
|
|
|
1292
|
-
//
|
|
1287
|
+
// Execute main
|
|
1293
1288
|
main().catch(err => {
|
|
1294
|
-
error(`
|
|
1289
|
+
error(`Unexpected error: ${err.message}`);
|
|
1295
1290
|
});
|