claude-git-hooks 2.14.1 → 2.15.5
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 +118 -3
- package/LICENSE +20 -20
- package/README.md +4 -1
- package/bin/claude-hooks +84 -84
- package/lib/commands/analyze-diff.js +0 -1
- package/lib/commands/bump-version.js +23 -22
- package/lib/commands/create-pr.js +157 -15
- package/lib/commands/debug.js +1 -1
- package/lib/commands/helpers.js +11 -6
- package/lib/commands/install.js +7 -8
- package/lib/commands/migrate-config.js +24 -2
- package/lib/commands/setup-github.js +1 -1
- package/lib/commands/update.js +1 -1
- package/lib/config.js +0 -4
- package/lib/hooks/prepare-commit-msg.js +2 -2
- package/lib/utils/changelog-generator.js +6 -8
- package/lib/utils/claude-client.js +7 -6
- package/lib/utils/claude-diagnostics.js +14 -21
- package/lib/utils/file-operations.js +1 -1
- package/lib/utils/file-utils.js +0 -1
- package/lib/utils/git-operations.js +0 -1
- package/lib/utils/github-api.js +3 -3
- package/lib/utils/github-client.js +2 -2
- package/lib/utils/installation-diagnostics.js +1 -1
- package/lib/utils/prompt-builder.js +4 -6
- package/lib/utils/sanitize.js +13 -14
- package/lib/utils/task-id.js +18 -20
- package/lib/utils/telemetry.js +5 -7
- package/package.json +13 -5
- package/templates/config.advanced.example.json +113 -113
- package/templates/pre-commit +7 -0
- package/templates/presets/ai/config.json +5 -5
- package/templates/presets/backend/config.json +5 -5
- package/templates/presets/backend/preset.json +49 -49
- package/templates/presets/database/config.json +5 -5
- package/templates/presets/database/preset.json +38 -38
- package/templates/presets/default/config.json +5 -5
- package/templates/presets/default/preset.json +53 -53
- package/templates/presets/frontend/config.json +5 -5
- package/templates/presets/frontend/preset.json +50 -50
- package/templates/presets/fullstack/config.json +5 -5
- package/templates/presets/fullstack/preset.json +55 -55
|
@@ -43,7 +43,7 @@ export async function runCreatePr(args) {
|
|
|
43
43
|
|
|
44
44
|
// Import GitHub API module
|
|
45
45
|
logger.debug('create-pr', 'Importing GitHub API modules');
|
|
46
|
-
const { createPullRequest,
|
|
46
|
+
const { createPullRequest, validateToken, findExistingPR } = await import('../utils/github-api.js');
|
|
47
47
|
const { parseGitHubRepo } = await import('../utils/github-client.js');
|
|
48
48
|
|
|
49
49
|
showInfo('🚀 Creating Pull Request...');
|
|
@@ -300,39 +300,181 @@ export async function runCreatePr(args) {
|
|
|
300
300
|
}
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
-
// Step 5.7:
|
|
304
|
-
logger.debug('create-pr', 'Step 5.7: Checking
|
|
305
|
-
const {
|
|
303
|
+
// Step 5.7: Smart tag pushing (Issue #44)
|
|
304
|
+
logger.debug('create-pr', 'Step 5.7: Checking and pushing unpushed tags');
|
|
305
|
+
const {
|
|
306
|
+
compareLocalAndRemoteTags,
|
|
307
|
+
pushTags: pushTagsUtil,
|
|
308
|
+
getLatestLocalTag,
|
|
309
|
+
getLatestRemoteTag,
|
|
310
|
+
parseTagVersion
|
|
311
|
+
} = await import('../utils/git-tag-manager.js');
|
|
312
|
+
const { compareVersions } = await import('../utils/version-manager.js');
|
|
313
|
+
|
|
306
314
|
const tagComparison = await compareLocalAndRemoteTags();
|
|
307
315
|
|
|
308
316
|
if (tagComparison.localNewer.length > 0) {
|
|
309
|
-
|
|
310
|
-
|
|
317
|
+
// Get latest local and remote tags for comparison
|
|
318
|
+
const latestLocalTag = getLatestLocalTag();
|
|
319
|
+
const latestRemoteTag = await getLatestRemoteTag();
|
|
320
|
+
|
|
321
|
+
const localVersion = latestLocalTag ? parseTagVersion(latestLocalTag) : null;
|
|
322
|
+
const remoteVersion = latestRemoteTag ? parseTagVersion(latestRemoteTag) : null;
|
|
323
|
+
|
|
324
|
+
logger.debug('create-pr', 'Tag comparison details', {
|
|
325
|
+
localTag: latestLocalTag,
|
|
326
|
+
remoteTag: latestRemoteTag,
|
|
327
|
+
localVersion,
|
|
328
|
+
remoteVersion,
|
|
329
|
+
unpushedTags: tagComparison.localNewer
|
|
330
|
+
});
|
|
311
331
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
332
|
+
let shouldPush = false;
|
|
333
|
+
let userChoice = null;
|
|
334
|
+
|
|
335
|
+
// Case 1: Local tag > Remote tag → Auto-push
|
|
336
|
+
if (localVersion && remoteVersion && compareVersions(localVersion, remoteVersion) > 0) {
|
|
337
|
+
logger.debug('create-pr', 'Local version > remote version, auto-pushing', {
|
|
338
|
+
localVersion,
|
|
339
|
+
remoteVersion
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
showInfo(`Local tag ${latestLocalTag} is newer than remote ${latestRemoteTag}`);
|
|
343
|
+
showInfo('Auto-pushing tag to remote...');
|
|
344
|
+
shouldPush = true;
|
|
345
|
+
|
|
346
|
+
// Case 2: Local tag = Remote tag → Prompt with warning
|
|
347
|
+
} else if (localVersion && remoteVersion && compareVersions(localVersion, remoteVersion) === 0) {
|
|
348
|
+
logger.debug('create-pr', 'Local version = remote version, prompting user', {
|
|
349
|
+
localVersion,
|
|
350
|
+
remoteVersion
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
showWarning('Local and remote tags have the same version:');
|
|
354
|
+
console.log(` Local: ${latestLocalTag} (${localVersion})`);
|
|
355
|
+
console.log(` Remote: ${latestRemoteTag} (${remoteVersion})`);
|
|
356
|
+
console.log('');
|
|
357
|
+
console.log('This might indicate the tag was already pushed.');
|
|
358
|
+
console.log('');
|
|
359
|
+
|
|
360
|
+
userChoice = await promptMenu(
|
|
361
|
+
'What would you like to do?',
|
|
362
|
+
[
|
|
363
|
+
{ key: 'c', label: 'Continue without pushing' },
|
|
364
|
+
{ key: 'p', label: 'Force push tag' },
|
|
365
|
+
{ key: 'a', label: 'Abort PR creation' }
|
|
366
|
+
],
|
|
367
|
+
'c'
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
if (userChoice === 'a') {
|
|
371
|
+
showInfo('PR creation cancelled');
|
|
372
|
+
process.exit(0);
|
|
373
|
+
} else if (userChoice === 'p') {
|
|
374
|
+
shouldPush = true;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Case 3: Local tag < Remote tag → Prompt with error
|
|
378
|
+
} else if (localVersion && remoteVersion && compareVersions(localVersion, remoteVersion) < 0) {
|
|
379
|
+
logger.debug('create-pr', 'Local version < remote version, prompting user', {
|
|
380
|
+
localVersion,
|
|
381
|
+
remoteVersion
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
showError('Local tag is older than remote tag:');
|
|
385
|
+
console.log(` Local: ${latestLocalTag} (${localVersion})`);
|
|
386
|
+
console.log(` Remote: ${latestRemoteTag} (${remoteVersion})`);
|
|
387
|
+
console.log('');
|
|
388
|
+
console.log('This usually means you need to pull latest tags:');
|
|
389
|
+
console.log(' git fetch --tags');
|
|
390
|
+
console.log('');
|
|
391
|
+
|
|
392
|
+
userChoice = await promptMenu(
|
|
393
|
+
'What would you like to do?',
|
|
394
|
+
[
|
|
395
|
+
{ key: 'a', label: 'Abort PR creation' },
|
|
396
|
+
{ key: 'c', label: 'Continue anyway (not recommended)' }
|
|
397
|
+
],
|
|
398
|
+
'a'
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
if (userChoice === 'a') {
|
|
402
|
+
showInfo('PR creation cancelled');
|
|
403
|
+
process.exit(0);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Case 4: No version comparison possible → Prompt with info
|
|
407
|
+
} else {
|
|
408
|
+
logger.debug('create-pr', 'Cannot compare versions, prompting user', {
|
|
409
|
+
localTag: latestLocalTag,
|
|
410
|
+
remoteTag: latestRemoteTag,
|
|
411
|
+
hasLocalVersion: !!localVersion,
|
|
412
|
+
hasRemoteVersion: !!remoteVersion
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
showWarning('Unable to compare tag versions:');
|
|
416
|
+
console.log(` Local tag: ${latestLocalTag || 'none'}`);
|
|
417
|
+
console.log(` Remote tag: ${latestRemoteTag || 'none'}`);
|
|
418
|
+
console.log('');
|
|
419
|
+
console.log('Unpushed tags:', tagComparison.localNewer.join(', '));
|
|
420
|
+
console.log('');
|
|
421
|
+
|
|
422
|
+
userChoice = await promptMenu(
|
|
423
|
+
'What would you like to do?',
|
|
424
|
+
[
|
|
425
|
+
{ key: 'p', label: 'Push tags to remote' },
|
|
426
|
+
{ key: 'c', label: 'Continue without pushing' },
|
|
427
|
+
{ key: 'a', label: 'Abort PR creation' }
|
|
428
|
+
],
|
|
429
|
+
'p'
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
if (userChoice === 'a') {
|
|
433
|
+
showInfo('PR creation cancelled');
|
|
434
|
+
process.exit(0);
|
|
435
|
+
} else if (userChoice === 'p') {
|
|
436
|
+
shouldPush = true;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Execute push if decided
|
|
441
|
+
if (shouldPush) {
|
|
442
|
+
console.log('');
|
|
443
|
+
showInfo('Pushing tags to remote...');
|
|
316
444
|
|
|
317
|
-
if (shouldPushTags) {
|
|
318
|
-
showInfo('Pushing tags...');
|
|
319
445
|
const pushResult = pushTagsUtil(null, tagComparison.localNewer);
|
|
320
446
|
|
|
321
447
|
if (pushResult.success) {
|
|
322
|
-
showSuccess('Tags pushed successfully');
|
|
448
|
+
showSuccess('✓ Tags pushed successfully');
|
|
449
|
+
logger.debug('create-pr', 'Tags pushed', {
|
|
450
|
+
pushed: tagComparison.localNewer
|
|
451
|
+
});
|
|
323
452
|
} else {
|
|
324
453
|
showError(`Failed to push some tags: ${pushResult.error}`);
|
|
325
454
|
if (pushResult.failed.length > 0) {
|
|
326
455
|
console.log('Failed tags:');
|
|
327
456
|
pushResult.failed.forEach(f => console.log(` - ${f.tag}: ${f.error}`));
|
|
328
457
|
}
|
|
458
|
+
|
|
459
|
+
const shouldContinue = await promptConfirmation(
|
|
460
|
+
'Continue creating PR despite push failure?',
|
|
461
|
+
false
|
|
462
|
+
);
|
|
463
|
+
|
|
464
|
+
if (!shouldContinue) {
|
|
465
|
+
showInfo('PR creation cancelled');
|
|
466
|
+
process.exit(0);
|
|
467
|
+
}
|
|
329
468
|
}
|
|
330
469
|
|
|
331
470
|
console.log('');
|
|
332
|
-
} else {
|
|
333
|
-
|
|
471
|
+
} else if (userChoice !== 'c') {
|
|
472
|
+
// Auto-push failed or user chose to continue
|
|
473
|
+
logger.debug('create-pr', 'Tag push skipped', { reason: 'user choice or condition' });
|
|
334
474
|
console.log('');
|
|
335
475
|
}
|
|
476
|
+
} else {
|
|
477
|
+
logger.debug('create-pr', 'No unpushed tags found, continuing');
|
|
336
478
|
}
|
|
337
479
|
|
|
338
480
|
// Step 6: Generate PR metadata using engine
|
package/lib/commands/debug.js
CHANGED
|
@@ -30,7 +30,7 @@ export async function runSetDebug(value) {
|
|
|
30
30
|
const config = await getConfig();
|
|
31
31
|
const isEnabled = config.system.debug || false;
|
|
32
32
|
console.log('');
|
|
33
|
-
info(`Debug mode: ${isEnabled ? colors.green
|
|
33
|
+
info(`Debug mode: ${isEnabled ? `${colors.green }enabled${ colors.reset}` : `${colors.red }disabled${ colors.reset}`}`);
|
|
34
34
|
console.log('');
|
|
35
35
|
} catch (err) {
|
|
36
36
|
error(`Failed to check debug status: ${err.message}`);
|
package/lib/commands/helpers.js
CHANGED
|
@@ -193,10 +193,15 @@ export async function updateConfig(propertyPath, value, options = {}) {
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
// Set value at propertyPath (support dot notation like 'system.debug')
|
|
196
|
-
// For v2.8.0 format, write inside 'overrides'
|
|
196
|
+
// For v2.8.0 format, write inside 'overrides' EXCEPT for top-level fields
|
|
197
197
|
const pathParts = propertyPath.split('.');
|
|
198
198
|
let current = config;
|
|
199
|
-
|
|
199
|
+
|
|
200
|
+
// Top-level fields that should NOT go into overrides
|
|
201
|
+
const topLevelFields = ['preset', 'version'];
|
|
202
|
+
const isTopLevelField = pathParts.length === 1 && topLevelFields.includes(pathParts[0]);
|
|
203
|
+
|
|
204
|
+
if (config.version === '2.8.0' && !isTopLevelField) {
|
|
200
205
|
if (!config.overrides || typeof config.overrides !== 'object') {
|
|
201
206
|
config.overrides = {};
|
|
202
207
|
}
|
|
@@ -263,12 +268,12 @@ export function getLatestVersion(packageName) {
|
|
|
263
268
|
*/
|
|
264
269
|
export class Entertainment {
|
|
265
270
|
static jokes = [
|
|
266
|
-
|
|
267
|
-
|
|
271
|
+
'Why do programmers prefer dark mode? Because light attracts bugs!',
|
|
272
|
+
'A QA engineer walks into a bar. Orders 1 beer. Orders 0 beers. Orders -1 beers.',
|
|
268
273
|
"What's a pirate's favorite programming language? R!",
|
|
269
274
|
"There are 10 types of people: those who understand binary and those who don't.",
|
|
270
|
-
|
|
271
|
-
|
|
275
|
+
'Why do programmers confuse Halloween with Christmas? Because Oct 31 = Dec 25',
|
|
276
|
+
'What does one bit say to another? See you on the bus!',
|
|
272
277
|
"Why don't Java and C++ get along? Because they have different views on pointers.",
|
|
273
278
|
"My code doesn't have bugs, just undocumented features."
|
|
274
279
|
];
|
package/lib/commands/install.js
CHANGED
|
@@ -63,7 +63,7 @@ async function checkVersionAndPromptUpdate() {
|
|
|
63
63
|
success('Update completed. Please run your command again.');
|
|
64
64
|
process.exit(0); // Exit so user restarts the process
|
|
65
65
|
} catch (e) {
|
|
66
|
-
error(
|
|
66
|
+
error(`Error updating: ${ e.message}`);
|
|
67
67
|
resolve(false);
|
|
68
68
|
}
|
|
69
69
|
} else {
|
|
@@ -158,10 +158,9 @@ async function checkClaudeAuth() {
|
|
|
158
158
|
|
|
159
159
|
/**
|
|
160
160
|
* Check complete dependencies (like setup-wsl.sh)
|
|
161
|
-
* @param {string|null} sudoPassword - Not used in v2.0.0+
|
|
162
161
|
* @param {boolean} skipAuth - Skip Claude verification
|
|
163
162
|
*/
|
|
164
|
-
async function checkAndInstallDependencies(
|
|
163
|
+
async function checkAndInstallDependencies(skipAuth = false) {
|
|
165
164
|
info('Checking system dependencies...');
|
|
166
165
|
|
|
167
166
|
// Check Node.js
|
|
@@ -252,7 +251,7 @@ function updateGitignore() {
|
|
|
252
251
|
}
|
|
253
252
|
|
|
254
253
|
// Add the missing entries
|
|
255
|
-
gitignoreContent += missingEntries.join('\n')
|
|
254
|
+
gitignoreContent += `${missingEntries.join('\n') }\n`;
|
|
256
255
|
|
|
257
256
|
// Write the updated file
|
|
258
257
|
fs.writeFileSync(gitignorePath, gitignoreContent);
|
|
@@ -294,7 +293,7 @@ function configureGit() {
|
|
|
294
293
|
}
|
|
295
294
|
|
|
296
295
|
} catch (e) {
|
|
297
|
-
warning(
|
|
296
|
+
warning(`Error configuring Git: ${ e.message}`);
|
|
298
297
|
}
|
|
299
298
|
}
|
|
300
299
|
|
|
@@ -399,7 +398,7 @@ export async function runInstall(args) {
|
|
|
399
398
|
|
|
400
399
|
// v2.0.0+: No sudo needed (pure Node.js, no system packages required)
|
|
401
400
|
// Check dependencies
|
|
402
|
-
await checkAndInstallDependencies(
|
|
401
|
+
await checkAndInstallDependencies(skipAuth);
|
|
403
402
|
|
|
404
403
|
const templatesPath = getTemplatesPath();
|
|
405
404
|
const hooksPath = '.git/hooks';
|
|
@@ -641,8 +640,8 @@ export async function runInstall(args) {
|
|
|
641
640
|
const settingsLocalPath = path.join(claudeDir, 'settings.local.json');
|
|
642
641
|
if (!fs.existsSync(settingsLocalPath)) {
|
|
643
642
|
const settingsLocalContent = {
|
|
644
|
-
|
|
645
|
-
|
|
643
|
+
'_comment': 'Local settings - DO NOT COMMIT. This file is gitignored.',
|
|
644
|
+
'githubToken': ''
|
|
646
645
|
};
|
|
647
646
|
fs.writeFileSync(settingsLocalPath, JSON.stringify(settingsLocalContent, null, 2));
|
|
648
647
|
info('settings.local.json created (add your GitHub token here)');
|
|
@@ -36,7 +36,29 @@ export async function runMigrateConfig() {
|
|
|
36
36
|
|
|
37
37
|
// Check if already in v2.8.0 format
|
|
38
38
|
if (rawConfig.version === '2.8.0') {
|
|
39
|
-
|
|
39
|
+
// Bug fix: Check if preset was incorrectly placed in overrides
|
|
40
|
+
let fixed = false;
|
|
41
|
+
if (rawConfig.overrides?.preset) {
|
|
42
|
+
info('🔧 Fixing incorrectly placed preset field...');
|
|
43
|
+
|
|
44
|
+
// If top-level preset doesn't exist or differs, use the one from overrides
|
|
45
|
+
if (!rawConfig.preset || rawConfig.preset !== rawConfig.overrides.preset) {
|
|
46
|
+
info(` Moving preset '${rawConfig.overrides.preset}' from overrides to top level`);
|
|
47
|
+
rawConfig.preset = rawConfig.overrides.preset;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Remove from overrides
|
|
51
|
+
delete rawConfig.overrides.preset;
|
|
52
|
+
|
|
53
|
+
// Write back
|
|
54
|
+
fs.writeFileSync(configPath, JSON.stringify(rawConfig, null, 4));
|
|
55
|
+
fixed = true;
|
|
56
|
+
success('✅ Config fixed: preset moved to correct location.');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!fixed) {
|
|
60
|
+
success('✅ Config is already in v2.8.0 format.');
|
|
61
|
+
}
|
|
40
62
|
return;
|
|
41
63
|
}
|
|
42
64
|
|
|
@@ -87,7 +109,7 @@ export async function runMigrateConfig() {
|
|
|
87
109
|
info('📖 See .claude/config.advanced.example.json for documentation');
|
|
88
110
|
}
|
|
89
111
|
|
|
90
|
-
console.log(
|
|
112
|
+
console.log('\n✨ New config:');
|
|
91
113
|
console.log(JSON.stringify(newConfig, null, 2));
|
|
92
114
|
console.log(`\n💾 Old config backed up to: ${backupPath}`);
|
|
93
115
|
console.log('\n💡 Many parameters are now hardcoded with sensible defaults');
|
|
@@ -71,7 +71,7 @@ export async function runSetupGitHub() {
|
|
|
71
71
|
// Save token
|
|
72
72
|
const saveResult = saveGitHubToken(trimmedToken);
|
|
73
73
|
if (!saveResult.success) {
|
|
74
|
-
showError(
|
|
74
|
+
showError(`Failed to save token: ${ saveResult.error}`);
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
77
77
|
showSuccess(`Token saved to ${saveResult.path}`);
|
package/lib/commands/update.js
CHANGED
package/lib/config.js
CHANGED
|
@@ -26,12 +26,8 @@
|
|
|
26
26
|
|
|
27
27
|
import fs from 'fs';
|
|
28
28
|
import path from 'path';
|
|
29
|
-
import { fileURLToPath } from 'url';
|
|
30
29
|
import logger from './utils/logger.js';
|
|
31
30
|
|
|
32
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
33
|
-
const __dirname = path.dirname(__filename);
|
|
34
|
-
|
|
35
31
|
/**
|
|
36
32
|
* Hardcoded defaults (v3.0.0)
|
|
37
33
|
* These are NOT user-configurable - sensible defaults that work for everyone
|
|
@@ -184,7 +184,7 @@ const main = async () => {
|
|
|
184
184
|
const taskId = await getOrPromptTaskId({
|
|
185
185
|
prompt: false, // Don't prompt - just detect from branch
|
|
186
186
|
required: false,
|
|
187
|
-
config
|
|
187
|
+
config // Pass config for custom pattern
|
|
188
188
|
});
|
|
189
189
|
|
|
190
190
|
// Note: getOrPromptTaskId() already logs the task ID if found
|
|
@@ -302,7 +302,7 @@ const main = async () => {
|
|
|
302
302
|
}
|
|
303
303
|
|
|
304
304
|
// Write to commit message file
|
|
305
|
-
await fs.writeFile(commitMsgFile, message
|
|
305
|
+
await fs.writeFile(commitMsgFile, `${message }\n`, 'utf8');
|
|
306
306
|
|
|
307
307
|
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
308
308
|
console.error(`⏱️ Message generation completed in ${duration}s`);
|
|
@@ -175,7 +175,7 @@ export function formatChangelogSection(version, date, categories, isUnreleased =
|
|
|
175
175
|
});
|
|
176
176
|
|
|
177
177
|
const header = isUnreleased
|
|
178
|
-
?
|
|
178
|
+
? '## [Unreleased]\n'
|
|
179
179
|
: `## [${version}] - ${date}\n`;
|
|
180
180
|
|
|
181
181
|
const sections = [];
|
|
@@ -200,7 +200,7 @@ export function formatChangelogSection(version, date, categories, isUnreleased =
|
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
const content = header
|
|
203
|
+
const content = `${header }\n${ sections.join('')}`;
|
|
204
204
|
|
|
205
205
|
logger.debug('changelog-generator - formatChangelogSection', 'Section formatted');
|
|
206
206
|
|
|
@@ -215,7 +215,6 @@ export function formatChangelogSection(version, date, categories, isUnreleased =
|
|
|
215
215
|
* @param {string} options.version - Version string
|
|
216
216
|
* @param {boolean} options.isReleaseVersion - True if final version (no suffix)
|
|
217
217
|
* @param {string} options.baseBranch - Base branch for diff (default: 'main')
|
|
218
|
-
* @param {Object} options.config - Configuration object
|
|
219
218
|
* @returns {Promise<Object>} Generated entry: { content, categories }
|
|
220
219
|
*/
|
|
221
220
|
export async function generateChangelogEntry(options) {
|
|
@@ -223,7 +222,6 @@ export async function generateChangelogEntry(options) {
|
|
|
223
222
|
version,
|
|
224
223
|
isReleaseVersion = false,
|
|
225
224
|
baseBranch = 'main',
|
|
226
|
-
config = {}
|
|
227
225
|
} = options;
|
|
228
226
|
|
|
229
227
|
logger.debug('changelog-generator - generateChangelogEntry', 'Generating CHANGELOG entry', {
|
|
@@ -270,7 +268,7 @@ export async function generateChangelogEntry(options) {
|
|
|
270
268
|
|
|
271
269
|
// Truncate diff if too large
|
|
272
270
|
const truncatedDiff = fullDiff.length > 30000
|
|
273
|
-
? fullDiff.substring(0, 30000)
|
|
271
|
+
? `${fullDiff.substring(0, 30000) }\n... (truncated)`
|
|
274
272
|
: fullDiff;
|
|
275
273
|
|
|
276
274
|
// Load prompt template
|
|
@@ -359,14 +357,14 @@ y este proyecto adhiere a [Semantic Versioning](https://semver.org/spec/v2.0.0.h
|
|
|
359
357
|
if (headerEnd !== -1) {
|
|
360
358
|
const before = existingContent.substring(0, match.index + headerEnd);
|
|
361
359
|
const after = existingContent.substring(match.index + headerEnd);
|
|
362
|
-
updatedContent = before + newContent
|
|
360
|
+
updatedContent = `${before + newContent }\n${ after}`;
|
|
363
361
|
} else {
|
|
364
362
|
// No existing versions, append after header
|
|
365
|
-
updatedContent = existingContent
|
|
363
|
+
updatedContent = `${existingContent }\n${ newContent}`;
|
|
366
364
|
}
|
|
367
365
|
} else {
|
|
368
366
|
// No header found, prepend content
|
|
369
|
-
updatedContent = newContent
|
|
367
|
+
updatedContent = `${newContent }\n${ existingContent}`;
|
|
370
368
|
}
|
|
371
369
|
|
|
372
370
|
fs.writeFileSync(changelogPath, updatedContent, 'utf8');
|
|
@@ -246,6 +246,7 @@ const executeClaude = (prompt, { timeout = 120000, allowedTools = [] } = {}) =>
|
|
|
246
246
|
|
|
247
247
|
// Handle process completion
|
|
248
248
|
claude.on('close', (code) => {
|
|
249
|
+
clearTimeout(timeoutId);
|
|
249
250
|
const elapsedTime = Date.now() - startTime;
|
|
250
251
|
|
|
251
252
|
// Check for "Execution error" even when exit code is 0
|
|
@@ -342,6 +343,7 @@ const executeClaude = (prompt, { timeout = 120000, allowedTools = [] } = {}) =>
|
|
|
342
343
|
|
|
343
344
|
// Handle errors
|
|
344
345
|
claude.on('error', (error) => {
|
|
346
|
+
clearTimeout(timeoutId);
|
|
345
347
|
const elapsedTime = Date.now() - startTime;
|
|
346
348
|
|
|
347
349
|
logger.error(
|
|
@@ -390,9 +392,6 @@ const executeClaude = (prompt, { timeout = 120000, allowedTools = [] } = {}) =>
|
|
|
390
392
|
}));
|
|
391
393
|
}, timeout);
|
|
392
394
|
|
|
393
|
-
// Clear timeout if process completes
|
|
394
|
-
claude.on('close', () => clearTimeout(timeoutId));
|
|
395
|
-
|
|
396
395
|
// Write prompt to stdin
|
|
397
396
|
// Why: Claude CLI reads prompt from stdin, not command arguments
|
|
398
397
|
|
|
@@ -400,6 +399,7 @@ const executeClaude = (prompt, { timeout = 120000, allowedTools = [] } = {}) =>
|
|
|
400
399
|
// Why: write() failures can emit 'error' events asynchronously
|
|
401
400
|
// Common in parallel execution with large prompts
|
|
402
401
|
claude.stdin.on('error', (error) => {
|
|
402
|
+
clearTimeout(timeoutId);
|
|
403
403
|
logger.error(
|
|
404
404
|
'claude-client - executeClaude',
|
|
405
405
|
'stdin stream error (process may have terminated early)',
|
|
@@ -426,6 +426,7 @@ const executeClaude = (prompt, { timeout = 120000, allowedTools = [] } = {}) =>
|
|
|
426
426
|
claude.stdin.write(prompt);
|
|
427
427
|
claude.stdin.end();
|
|
428
428
|
} catch (error) {
|
|
429
|
+
clearTimeout(timeoutId);
|
|
429
430
|
logger.error(
|
|
430
431
|
'claude-client - executeClaude',
|
|
431
432
|
'Failed to write prompt to Claude CLI stdin (synchronous error)',
|
|
@@ -536,7 +537,7 @@ const executeClaudeInteractive = (prompt, { timeout = 300000 } = {}) => new Prom
|
|
|
536
537
|
* @returns {Object} Parsed JSON object
|
|
537
538
|
* @throws {ClaudeClientError} If no valid JSON found
|
|
538
539
|
*/
|
|
539
|
-
const extractJSON = (response
|
|
540
|
+
const extractJSON = (response) => {
|
|
540
541
|
logger.debug(
|
|
541
542
|
'claude-client - extractJSON',
|
|
542
543
|
'Extracting JSON from response',
|
|
@@ -614,7 +615,7 @@ const extractJSON = (response, telemetryContext = {}) => {
|
|
|
614
615
|
// Telemetry is now recorded by withRetry wrapper
|
|
615
616
|
throw new ClaudeClientError('No valid JSON found in Claude response', {
|
|
616
617
|
context: {
|
|
617
|
-
response
|
|
618
|
+
response,
|
|
618
619
|
errorInfo: {
|
|
619
620
|
type: 'JSON_PARSE_ERROR'
|
|
620
621
|
}
|
|
@@ -797,7 +798,7 @@ const executeClaudeWithRetry = async (prompt, options = {}) => {
|
|
|
797
798
|
() => executeClaude(prompt, executeOptions),
|
|
798
799
|
{
|
|
799
800
|
operationName: 'executeClaude',
|
|
800
|
-
telemetryContext
|
|
801
|
+
telemetryContext
|
|
801
802
|
}
|
|
802
803
|
);
|
|
803
804
|
};
|
|
@@ -150,27 +150,26 @@ const formatTimingInfo = (errorInfo) => {
|
|
|
150
150
|
* @returns {string} Formatted error message
|
|
151
151
|
*/
|
|
152
152
|
export const formatClaudeError = (errorInfo) => {
|
|
153
|
-
const lines = [];
|
|
154
153
|
|
|
155
154
|
switch (errorInfo.type) {
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
case ClaudeErrorType.EXECUTION_ERROR:
|
|
156
|
+
return formatExecutionError(errorInfo);
|
|
158
157
|
|
|
159
|
-
|
|
160
|
-
|
|
158
|
+
case ClaudeErrorType.RATE_LIMIT:
|
|
159
|
+
return formatRateLimitError(errorInfo);
|
|
161
160
|
|
|
162
|
-
|
|
163
|
-
|
|
161
|
+
case ClaudeErrorType.AUTH_FAILED:
|
|
162
|
+
return formatAuthError(errorInfo);
|
|
164
163
|
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
case ClaudeErrorType.NETWORK:
|
|
165
|
+
return formatNetworkError(errorInfo);
|
|
167
166
|
|
|
168
|
-
|
|
169
|
-
|
|
167
|
+
case ClaudeErrorType.INVALID_RESPONSE:
|
|
168
|
+
return formatInvalidResponseError(errorInfo);
|
|
170
169
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
170
|
+
case ClaudeErrorType.GENERIC:
|
|
171
|
+
default:
|
|
172
|
+
return formatGenericError(errorInfo);
|
|
174
173
|
}
|
|
175
174
|
};
|
|
176
175
|
|
|
@@ -178,7 +177,6 @@ export const formatClaudeError = (errorInfo) => {
|
|
|
178
177
|
* Format execution error
|
|
179
178
|
*/
|
|
180
179
|
const formatExecutionError = (errorInfo) => {
|
|
181
|
-
const { exitCode, stdout } = errorInfo;
|
|
182
180
|
const lines = [];
|
|
183
181
|
|
|
184
182
|
lines.push('❌ Claude CLI execution error');
|
|
@@ -243,7 +241,6 @@ const formatRateLimitError = (errorInfo) => {
|
|
|
243
241
|
* Format authentication error
|
|
244
242
|
*/
|
|
245
243
|
const formatAuthError = (errorInfo) => {
|
|
246
|
-
const { exitCode } = errorInfo;
|
|
247
244
|
const lines = [];
|
|
248
245
|
|
|
249
246
|
lines.push('❌ Claude CLI authentication failed');
|
|
@@ -266,7 +263,6 @@ const formatAuthError = (errorInfo) => {
|
|
|
266
263
|
* Format network error
|
|
267
264
|
*/
|
|
268
265
|
const formatNetworkError = (errorInfo) => {
|
|
269
|
-
const { exitCode } = errorInfo;
|
|
270
266
|
const lines = [];
|
|
271
267
|
|
|
272
268
|
lines.push('❌ Network error connecting to Claude API');
|
|
@@ -290,7 +286,6 @@ const formatNetworkError = (errorInfo) => {
|
|
|
290
286
|
* Format invalid response error
|
|
291
287
|
*/
|
|
292
288
|
const formatInvalidResponseError = (errorInfo) => {
|
|
293
|
-
const { exitCode } = errorInfo;
|
|
294
289
|
const lines = [];
|
|
295
290
|
|
|
296
291
|
lines.push('❌ Claude returned invalid response');
|
|
@@ -350,9 +345,7 @@ const formatGenericError = (errorInfo) => {
|
|
|
350
345
|
* @param {Object} errorInfo - Output from detectClaudeError()
|
|
351
346
|
* @returns {boolean} True if error might resolve with retry
|
|
352
347
|
*/
|
|
353
|
-
export const isRecoverableError = (errorInfo) =>
|
|
354
|
-
return errorInfo.type === ClaudeErrorType.EXECUTION_ERROR ||
|
|
348
|
+
export const isRecoverableError = (errorInfo) => errorInfo.type === ClaudeErrorType.EXECUTION_ERROR ||
|
|
355
349
|
errorInfo.type === ClaudeErrorType.RATE_LIMIT ||
|
|
356
350
|
errorInfo.type === ClaudeErrorType.NETWORK ||
|
|
357
351
|
errorInfo.type === ClaudeErrorType.TIMEOUT;
|
|
358
|
-
};
|
package/lib/utils/file-utils.js
CHANGED
|
@@ -374,7 +374,6 @@ const getStagedStats = () => {
|
|
|
374
374
|
|
|
375
375
|
try {
|
|
376
376
|
const shortstat = execGitCommand('diff --cached --shortstat');
|
|
377
|
-
const numstat = execGitCommand('diff --cached --numstat');
|
|
378
377
|
const nameStatus = execGitCommand('diff --cached --name-status');
|
|
379
378
|
|
|
380
379
|
// Parse shortstat: "X files changed, Y insertions(+), Z deletions(-)"
|
package/lib/utils/github-api.js
CHANGED
|
@@ -322,7 +322,7 @@ export const createPullRequest = async ({
|
|
|
322
322
|
logger.debug('github-api - createPullRequest', 'Starting PR creation', {
|
|
323
323
|
owner,
|
|
324
324
|
repo,
|
|
325
|
-
titlePreview: title.substring(0, 50)
|
|
325
|
+
titlePreview: `${title.substring(0, 50) }...`,
|
|
326
326
|
titleLength: title.length,
|
|
327
327
|
bodyLength: body.length,
|
|
328
328
|
head,
|
|
@@ -424,8 +424,8 @@ export const createPullRequest = async ({
|
|
|
424
424
|
title: pr.title,
|
|
425
425
|
head: pr.head.ref,
|
|
426
426
|
base: pr.base.ref,
|
|
427
|
-
labels
|
|
428
|
-
reviewers
|
|
427
|
+
labels,
|
|
428
|
+
reviewers
|
|
429
429
|
};
|
|
430
430
|
|
|
431
431
|
logger.debug('github-api - createPullRequest', 'PR creation completed', result);
|