claude-git-hooks 2.18.0 → 2.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/CLAUDE.md +12 -8
- package/README.md +2 -1
- package/bin/claude-hooks +75 -89
- package/lib/cli-metadata.js +301 -0
- package/lib/commands/analyze-diff.js +12 -10
- package/lib/commands/analyze.js +9 -5
- package/lib/commands/bump-version.js +66 -43
- package/lib/commands/create-pr.js +71 -34
- package/lib/commands/debug.js +4 -7
- package/lib/commands/generate-changelog.js +11 -4
- package/lib/commands/help.js +47 -27
- package/lib/commands/helpers.js +66 -43
- package/lib/commands/hooks.js +15 -13
- package/lib/commands/install.js +546 -39
- package/lib/commands/migrate-config.js +8 -11
- package/lib/commands/presets.js +6 -13
- package/lib/commands/setup-github.js +12 -3
- package/lib/commands/telemetry-cmd.js +8 -6
- package/lib/commands/update.js +1 -2
- package/lib/config.js +36 -31
- package/lib/hooks/pre-commit.js +34 -54
- package/lib/hooks/prepare-commit-msg.js +39 -58
- package/lib/utils/analysis-engine.js +28 -21
- package/lib/utils/changelog-generator.js +162 -34
- package/lib/utils/claude-client.js +438 -377
- package/lib/utils/claude-diagnostics.js +20 -10
- package/lib/utils/file-operations.js +51 -79
- package/lib/utils/file-utils.js +46 -9
- package/lib/utils/git-operations.js +140 -123
- package/lib/utils/git-tag-manager.js +24 -23
- package/lib/utils/github-api.js +85 -61
- package/lib/utils/github-client.js +12 -14
- package/lib/utils/installation-diagnostics.js +4 -4
- package/lib/utils/interactive-ui.js +29 -17
- package/lib/utils/logger.js +4 -1
- package/lib/utils/pr-metadata-engine.js +67 -33
- package/lib/utils/preset-loader.js +20 -62
- package/lib/utils/prompt-builder.js +50 -55
- package/lib/utils/resolution-prompt.js +33 -44
- package/lib/utils/sanitize.js +20 -19
- package/lib/utils/task-id.js +27 -40
- package/lib/utils/telemetry.js +29 -17
- package/lib/utils/version-manager.js +173 -126
- package/lib/utils/which-command.js +23 -12
- package/package.json +69 -69
|
@@ -48,7 +48,7 @@ export const showPRPreview = (prData) => {
|
|
|
48
48
|
|
|
49
49
|
// Show first 5 lines of body
|
|
50
50
|
const bodyLines = body.split('\n').slice(0, 5);
|
|
51
|
-
bodyLines.forEach(line => {
|
|
51
|
+
bodyLines.forEach((line) => {
|
|
52
52
|
console.log(` ${truncate(line, 61)}`);
|
|
53
53
|
});
|
|
54
54
|
|
|
@@ -66,7 +66,7 @@ export const showPRPreview = (prData) => {
|
|
|
66
66
|
*/
|
|
67
67
|
const truncate = (str, maxLen) => {
|
|
68
68
|
const padded = str.padEnd(maxLen, ' ');
|
|
69
|
-
return padded.length > maxLen ? `${padded.substring(0, maxLen - 3)
|
|
69
|
+
return padded.length > maxLen ? `${padded.substring(0, maxLen - 3)}...` : padded;
|
|
70
70
|
};
|
|
71
71
|
|
|
72
72
|
/**
|
|
@@ -94,7 +94,9 @@ export const promptConfirmation = async (message, defaultValue = true) => {
|
|
|
94
94
|
|
|
95
95
|
// If empty, use default
|
|
96
96
|
if (!trimmed) {
|
|
97
|
-
logger.debug('interactive-ui - promptConfirmation', 'Using default', {
|
|
97
|
+
logger.debug('interactive-ui - promptConfirmation', 'Using default', {
|
|
98
|
+
defaultValue
|
|
99
|
+
});
|
|
98
100
|
resolve(defaultValue);
|
|
99
101
|
return;
|
|
100
102
|
}
|
|
@@ -111,10 +113,14 @@ export const promptConfirmation = async (message, defaultValue = true) => {
|
|
|
111
113
|
resolve(false);
|
|
112
114
|
} else {
|
|
113
115
|
// Invalid input, use default
|
|
114
|
-
logger.debug(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
logger.debug(
|
|
117
|
+
'interactive-ui - promptConfirmation',
|
|
118
|
+
'Invalid input, using default',
|
|
119
|
+
{
|
|
120
|
+
answer: trimmed,
|
|
121
|
+
defaultValue
|
|
122
|
+
}
|
|
123
|
+
);
|
|
118
124
|
resolve(defaultValue);
|
|
119
125
|
}
|
|
120
126
|
});
|
|
@@ -148,7 +154,9 @@ export const promptEditField = async (fieldName, currentValue) => {
|
|
|
148
154
|
const trimmed = answer.trim();
|
|
149
155
|
|
|
150
156
|
if (!trimmed) {
|
|
151
|
-
logger.debug('interactive-ui - promptEditField', 'Keeping current value', {
|
|
157
|
+
logger.debug('interactive-ui - promptEditField', 'Keeping current value', {
|
|
158
|
+
fieldName
|
|
159
|
+
});
|
|
152
160
|
resolve(currentValue);
|
|
153
161
|
} else {
|
|
154
162
|
logger.debug('interactive-ui - promptEditField', 'Updated value', { fieldName });
|
|
@@ -179,7 +187,7 @@ export const promptMenu = async (message, options, defaultKey = null) => {
|
|
|
179
187
|
console.log('');
|
|
180
188
|
|
|
181
189
|
// Display options
|
|
182
|
-
options.forEach(opt => {
|
|
190
|
+
options.forEach((opt) => {
|
|
183
191
|
const isDefault = opt.key === defaultKey;
|
|
184
192
|
const marker = isDefault ? '→' : ' ';
|
|
185
193
|
console.log(` ${marker} [${opt.key}] ${opt.label}`);
|
|
@@ -203,10 +211,12 @@ export const promptMenu = async (message, options, defaultKey = null) => {
|
|
|
203
211
|
}
|
|
204
212
|
|
|
205
213
|
// Check if valid option
|
|
206
|
-
const selectedOption = options.find(opt => opt.key.toLowerCase() === trimmed);
|
|
214
|
+
const selectedOption = options.find((opt) => opt.key.toLowerCase() === trimmed);
|
|
207
215
|
|
|
208
216
|
if (selectedOption) {
|
|
209
|
-
logger.debug('interactive-ui - promptMenu', 'Option selected', {
|
|
217
|
+
logger.debug('interactive-ui - promptMenu', 'Option selected', {
|
|
218
|
+
key: selectedOption.key
|
|
219
|
+
});
|
|
210
220
|
resolve(selectedOption.key);
|
|
211
221
|
} else {
|
|
212
222
|
// Invalid, use default or first option
|
|
@@ -358,7 +368,7 @@ export const promptToggleList = async (message, items) => {
|
|
|
358
368
|
});
|
|
359
369
|
|
|
360
370
|
// Clone items to track selection state
|
|
361
|
-
const state = items.map(item => ({
|
|
371
|
+
const state = items.map((item) => ({
|
|
362
372
|
key: item.key,
|
|
363
373
|
label: item.label,
|
|
364
374
|
selected: item.selected !== undefined ? item.selected : true
|
|
@@ -395,8 +405,8 @@ export const promptToggleList = async (message, items) => {
|
|
|
395
405
|
if (!trimmed) {
|
|
396
406
|
rl.close();
|
|
397
407
|
const selectedKeys = state
|
|
398
|
-
.filter(item => item.selected)
|
|
399
|
-
.map(item => item.key);
|
|
408
|
+
.filter((item) => item.selected)
|
|
409
|
+
.map((item) => item.key);
|
|
400
410
|
|
|
401
411
|
logger.debug('interactive-ui - promptToggleList', 'Selection confirmed', {
|
|
402
412
|
selectedKeys
|
|
@@ -425,7 +435,9 @@ export const promptToggleList = async (message, items) => {
|
|
|
425
435
|
askForInput();
|
|
426
436
|
} else {
|
|
427
437
|
// Invalid input, show message and ask again
|
|
428
|
-
console.log(
|
|
438
|
+
console.log(
|
|
439
|
+
`Invalid input. Enter a number between 1 and ${state.length}, or press Enter to confirm.`
|
|
440
|
+
);
|
|
429
441
|
console.log('');
|
|
430
442
|
askForInput();
|
|
431
443
|
}
|
|
@@ -452,7 +464,7 @@ export const promptUserConfirmation = async (result) => {
|
|
|
452
464
|
|
|
453
465
|
// Calculate total non-blocking issues
|
|
454
466
|
const totalIssues = major + minor + info;
|
|
455
|
-
const fileCount = new Set(details.map(d => d.file)).size;
|
|
467
|
+
const fileCount = new Set(details.map((d) => d.file)).size;
|
|
456
468
|
|
|
457
469
|
console.log('\n');
|
|
458
470
|
console.log('⚠️ Non-blocking issues detected');
|
|
@@ -475,7 +487,7 @@ export const promptUserConfirmation = async (result) => {
|
|
|
475
487
|
const choice = await promptMenu(
|
|
476
488
|
'Do you want to continue with the commit?',
|
|
477
489
|
options,
|
|
478
|
-
'n'
|
|
490
|
+
'n' // Default to abort (safer)
|
|
479
491
|
);
|
|
480
492
|
|
|
481
493
|
if (choice === 'y') {
|
package/lib/utils/logger.js
CHANGED
|
@@ -96,7 +96,10 @@ class Logger {
|
|
|
96
96
|
console.error(`${this.colors.red}❌ [${context}] ${message}${this.colors.reset}`);
|
|
97
97
|
|
|
98
98
|
if (error) {
|
|
99
|
-
console.error(
|
|
99
|
+
console.error(
|
|
100
|
+
`${this.colors.red}Error details:${this.colors.reset}`,
|
|
101
|
+
error.message || error
|
|
102
|
+
);
|
|
100
103
|
|
|
101
104
|
// Show stack trace only in debug mode to avoid overwhelming users
|
|
102
105
|
if (error.stack && this.debugMode) {
|
|
@@ -62,8 +62,8 @@ import logger from './logger.js';
|
|
|
62
62
|
* Why: Convention over configuration - sensible defaults for 95% of cases
|
|
63
63
|
*/
|
|
64
64
|
const DEFAULTS = {
|
|
65
|
-
timeout: 180000,
|
|
66
|
-
maxDiffSize: 50000
|
|
65
|
+
timeout: 180000, // 3 minutes
|
|
66
|
+
maxDiffSize: 50000 // 50KB
|
|
67
67
|
};
|
|
68
68
|
|
|
69
69
|
/**
|
|
@@ -93,7 +93,7 @@ const buildDiffPayload = (baseRef, headRef, files, maxSize = DEFAULTS.maxDiffSiz
|
|
|
93
93
|
// Always include file stats (never truncated)
|
|
94
94
|
const statsOutput = getDiffBetweenRefs(baseRef, headRef, { contextLines: 0 })
|
|
95
95
|
.split('\n')
|
|
96
|
-
.filter(line => line.match(/^\s*[\w/.-]+\s*\|\s*\d+\s*[+-]*$/))
|
|
96
|
+
.filter((line) => line.match(/^\s*[\w/.-]+\s*\|\s*\d+\s*[+-]*$/))
|
|
97
97
|
.join('\n');
|
|
98
98
|
|
|
99
99
|
const statsSummary = `Files changed: ${files.length}\n\n${statsOutput}\n\n`;
|
|
@@ -125,10 +125,14 @@ const buildDiffPayload = (baseRef, headRef, files, maxSize = DEFAULTS.maxDiffSiz
|
|
|
125
125
|
};
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
logger.debug(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
logger.debug(
|
|
129
|
+
'pr-metadata-engine - buildDiffPayload',
|
|
130
|
+
'Tier 1: Exceeded budget, applying Tier 2',
|
|
131
|
+
{
|
|
132
|
+
diffSize: fullDiff.length,
|
|
133
|
+
maxSize
|
|
134
|
+
}
|
|
135
|
+
);
|
|
132
136
|
|
|
133
137
|
// Tier 2: Split diff by file and allocate proportional budget
|
|
134
138
|
const diffPattern = /^diff --git a\/.+ b\/.+$/gm;
|
|
@@ -146,9 +150,13 @@ const buildDiffPayload = (baseRef, headRef, files, maxSize = DEFAULTS.maxDiffSiz
|
|
|
146
150
|
fileDiffs.push(fullDiff.substring(lastIndex));
|
|
147
151
|
}
|
|
148
152
|
|
|
149
|
-
logger.debug(
|
|
150
|
-
|
|
151
|
-
|
|
153
|
+
logger.debug(
|
|
154
|
+
'pr-metadata-engine - buildDiffPayload',
|
|
155
|
+
'Tier 2: Split diff into per-file chunks',
|
|
156
|
+
{
|
|
157
|
+
chunkCount: fileDiffs.length
|
|
158
|
+
}
|
|
159
|
+
);
|
|
152
160
|
|
|
153
161
|
const availableBudget = maxSize - summarySize;
|
|
154
162
|
const budgetPerFile = Math.floor(availableBudget / files.length);
|
|
@@ -185,7 +193,10 @@ const buildDiffPayload = (baseRef, headRef, files, maxSize = DEFAULTS.maxDiffSiz
|
|
|
185
193
|
|
|
186
194
|
// Tier 3: Replace oversized files with stat-only summary
|
|
187
195
|
if (oversizedFiles.length > 0) {
|
|
188
|
-
logger.debug(
|
|
196
|
+
logger.debug(
|
|
197
|
+
'pr-metadata-engine - buildDiffPayload',
|
|
198
|
+
'Tier 3: Replacing oversized files with stat-only'
|
|
199
|
+
);
|
|
189
200
|
|
|
190
201
|
for (const { diff } of oversizedFiles) {
|
|
191
202
|
// Extract file path and stats from diff header
|
|
@@ -193,7 +204,7 @@ const buildDiffPayload = (baseRef, headRef, files, maxSize = DEFAULTS.maxDiffSiz
|
|
|
193
204
|
if (filePathMatch) {
|
|
194
205
|
const filePath = filePathMatch[2];
|
|
195
206
|
// Extract stat line for this file
|
|
196
|
-
const statLine = statsOutput.split('\n').find(line => line.includes(filePath));
|
|
207
|
+
const statLine = statsOutput.split('\n').find((line) => line.includes(filePath));
|
|
197
208
|
if (statLine) {
|
|
198
209
|
finalDiff += `\n--- ${filePath} (truncated - stat only) ---\n${statLine}\n`;
|
|
199
210
|
statOnlyCount++;
|
|
@@ -201,9 +212,13 @@ const buildDiffPayload = (baseRef, headRef, files, maxSize = DEFAULTS.maxDiffSiz
|
|
|
201
212
|
}
|
|
202
213
|
}
|
|
203
214
|
|
|
204
|
-
logger.debug(
|
|
205
|
-
|
|
206
|
-
|
|
215
|
+
logger.debug(
|
|
216
|
+
'pr-metadata-engine - buildDiffPayload',
|
|
217
|
+
'Tier 3: Stat-only demotion complete',
|
|
218
|
+
{
|
|
219
|
+
statOnlyCount
|
|
220
|
+
}
|
|
221
|
+
);
|
|
207
222
|
}
|
|
208
223
|
|
|
209
224
|
const payload = statsSummary + finalDiff;
|
|
@@ -235,7 +250,9 @@ const buildDiffPayload = (baseRef, headRef, files, maxSize = DEFAULTS.maxDiffSiz
|
|
|
235
250
|
* @returns {Promise<{ success: boolean, context?: BranchContext, error?: string }>}
|
|
236
251
|
*/
|
|
237
252
|
export const getBranchContext = async (targetBranch) => {
|
|
238
|
-
logger.debug('pr-metadata-engine - getBranchContext', 'Getting branch context', {
|
|
253
|
+
logger.debug('pr-metadata-engine - getBranchContext', 'Getting branch context', {
|
|
254
|
+
targetBranch
|
|
255
|
+
});
|
|
239
256
|
|
|
240
257
|
try {
|
|
241
258
|
// Get current branch
|
|
@@ -248,14 +265,20 @@ export const getBranchContext = async (targetBranch) => {
|
|
|
248
265
|
};
|
|
249
266
|
}
|
|
250
267
|
|
|
251
|
-
logger.debug('pr-metadata-engine - getBranchContext', 'Current branch identified', {
|
|
268
|
+
logger.debug('pr-metadata-engine - getBranchContext', 'Current branch identified', {
|
|
269
|
+
currentBranch
|
|
270
|
+
});
|
|
252
271
|
|
|
253
272
|
// Fetch remote references
|
|
254
273
|
try {
|
|
255
274
|
fetchRemote();
|
|
256
275
|
logger.debug('pr-metadata-engine - getBranchContext', 'Remote fetched successfully');
|
|
257
276
|
} catch (fetchError) {
|
|
258
|
-
logger.warning(
|
|
277
|
+
logger.warning(
|
|
278
|
+
'pr-metadata-engine - getBranchContext',
|
|
279
|
+
'Failed to fetch remote',
|
|
280
|
+
fetchError
|
|
281
|
+
);
|
|
259
282
|
// Continue anyway - may work with cached remote refs
|
|
260
283
|
}
|
|
261
284
|
|
|
@@ -288,12 +311,11 @@ export const getBranchContext = async (targetBranch) => {
|
|
|
288
311
|
logger.debug('pr-metadata-engine - getBranchContext', 'Commits retrieved');
|
|
289
312
|
|
|
290
313
|
// Build diff payload with tiered reduction
|
|
291
|
-
const {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
);
|
|
314
|
+
const {
|
|
315
|
+
payload: diff,
|
|
316
|
+
isTruncated,
|
|
317
|
+
truncationDetails
|
|
318
|
+
} = buildDiffPayload(baseBranch, 'HEAD', files, DEFAULTS.maxDiffSize);
|
|
297
319
|
|
|
298
320
|
logger.debug('pr-metadata-engine - getBranchContext', 'Diff payload built', {
|
|
299
321
|
diffSize: diff.length,
|
|
@@ -321,9 +343,12 @@ export const getBranchContext = async (targetBranch) => {
|
|
|
321
343
|
success: true,
|
|
322
344
|
context
|
|
323
345
|
};
|
|
324
|
-
|
|
325
346
|
} catch (error) {
|
|
326
|
-
logger.error(
|
|
347
|
+
logger.error(
|
|
348
|
+
'pr-metadata-engine - getBranchContext',
|
|
349
|
+
'Failed to get branch context',
|
|
350
|
+
error
|
|
351
|
+
);
|
|
327
352
|
|
|
328
353
|
return {
|
|
329
354
|
success: false,
|
|
@@ -411,9 +436,12 @@ export const generatePRMetadata = async (context, options = {}) => {
|
|
|
411
436
|
testingNotes: result.testingNotes || '',
|
|
412
437
|
context
|
|
413
438
|
};
|
|
414
|
-
|
|
415
439
|
} catch (error) {
|
|
416
|
-
logger.error(
|
|
440
|
+
logger.error(
|
|
441
|
+
'pr-metadata-engine - generatePRMetadata',
|
|
442
|
+
'Failed to generate PR metadata',
|
|
443
|
+
error
|
|
444
|
+
);
|
|
417
445
|
throw error;
|
|
418
446
|
}
|
|
419
447
|
};
|
|
@@ -439,9 +467,13 @@ export const analyzeBranchForPR = async (targetBranch, options = {}) => {
|
|
|
439
467
|
const contextResult = await getBranchContext(targetBranch);
|
|
440
468
|
|
|
441
469
|
if (!contextResult.success) {
|
|
442
|
-
logger.error(
|
|
443
|
-
|
|
444
|
-
|
|
470
|
+
logger.error(
|
|
471
|
+
'pr-metadata-engine - analyzeBranchForPR',
|
|
472
|
+
'Failed to get branch context',
|
|
473
|
+
{
|
|
474
|
+
error: contextResult.error
|
|
475
|
+
}
|
|
476
|
+
);
|
|
445
477
|
|
|
446
478
|
return {
|
|
447
479
|
success: false,
|
|
@@ -456,13 +488,15 @@ export const analyzeBranchForPR = async (targetBranch, options = {}) => {
|
|
|
456
488
|
// Step 2: Generate PR metadata
|
|
457
489
|
const metadata = await generatePRMetadata(contextResult.context, options);
|
|
458
490
|
|
|
459
|
-
logger.debug(
|
|
491
|
+
logger.debug(
|
|
492
|
+
'pr-metadata-engine - analyzeBranchForPR',
|
|
493
|
+
'PR metadata generated successfully'
|
|
494
|
+
);
|
|
460
495
|
|
|
461
496
|
return {
|
|
462
497
|
success: true,
|
|
463
498
|
result: metadata
|
|
464
499
|
};
|
|
465
|
-
|
|
466
500
|
} catch (error) {
|
|
467
501
|
logger.error('pr-metadata-engine - analyzeBranchForPR', 'PR analysis failed', error);
|
|
468
502
|
|
|
@@ -40,11 +40,7 @@ class PresetError extends Error {
|
|
|
40
40
|
* @returns {Promise<Object>} { metadata, config, templates }
|
|
41
41
|
*/
|
|
42
42
|
export async function loadPreset(presetName) {
|
|
43
|
-
logger.debug(
|
|
44
|
-
'preset-loader - loadPreset',
|
|
45
|
-
'Loading preset',
|
|
46
|
-
{ presetName }
|
|
47
|
-
);
|
|
43
|
+
logger.debug('preset-loader - loadPreset', 'Loading preset', { presetName });
|
|
48
44
|
|
|
49
45
|
const repoRoot = getRepoRoot();
|
|
50
46
|
|
|
@@ -70,25 +66,16 @@ export async function loadPreset(presetName) {
|
|
|
70
66
|
templates[key] = path.join(presetDir, templatePath);
|
|
71
67
|
}
|
|
72
68
|
|
|
73
|
-
logger.debug(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
fileExtensions: metadata.fileExtensions,
|
|
80
|
-
templateCount: Object.keys(templates).length
|
|
81
|
-
}
|
|
82
|
-
);
|
|
69
|
+
logger.debug('preset-loader - loadPreset', 'Preset loaded successfully', {
|
|
70
|
+
presetName,
|
|
71
|
+
displayName: metadata.displayName,
|
|
72
|
+
fileExtensions: metadata.fileExtensions,
|
|
73
|
+
templateCount: Object.keys(templates).length
|
|
74
|
+
});
|
|
83
75
|
|
|
84
76
|
return { metadata, config, templates };
|
|
85
|
-
|
|
86
77
|
} catch (error) {
|
|
87
|
-
logger.error(
|
|
88
|
-
'preset-loader - loadPreset',
|
|
89
|
-
`Failed to load preset: ${presetName}`,
|
|
90
|
-
error
|
|
91
|
-
);
|
|
78
|
+
logger.error('preset-loader - loadPreset', `Failed to load preset: ${presetName}`, error);
|
|
92
79
|
|
|
93
80
|
throw new PresetError(`Preset "${presetName}" not found or invalid`, {
|
|
94
81
|
presetName,
|
|
@@ -104,44 +91,23 @@ export async function loadPreset(presetName) {
|
|
|
104
91
|
* @returns {Promise<string>} Template content with placeholders replaced
|
|
105
92
|
*/
|
|
106
93
|
export async function loadTemplate(templatePath, metadata) {
|
|
107
|
-
logger.debug(
|
|
108
|
-
'preset-loader - loadTemplate',
|
|
109
|
-
'Loading template',
|
|
110
|
-
{ templatePath }
|
|
111
|
-
);
|
|
94
|
+
logger.debug('preset-loader - loadTemplate', 'Loading template', { templatePath });
|
|
112
95
|
|
|
113
96
|
try {
|
|
114
97
|
let template = await fs.readFile(templatePath, 'utf8');
|
|
115
98
|
|
|
116
99
|
// Replace placeholders
|
|
117
|
-
template = template.replace(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
/{{FILE_EXTENSIONS}}/g,
|
|
127
|
-
metadata.fileExtensions.join(', ')
|
|
128
|
-
);
|
|
129
|
-
template = template.replace(
|
|
130
|
-
/{{PRESET_NAME}}/g,
|
|
131
|
-
metadata.name
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
logger.debug(
|
|
135
|
-
'preset-loader - loadTemplate',
|
|
136
|
-
'Template loaded and processed',
|
|
137
|
-
{
|
|
138
|
-
templatePath,
|
|
139
|
-
originalLength: template.length
|
|
140
|
-
}
|
|
141
|
-
);
|
|
100
|
+
template = template.replace(/{{TECH_STACK}}/g, metadata.techStack.join(', '));
|
|
101
|
+
template = template.replace(/{{FOCUS_AREAS}}/g, metadata.focusAreas.join(', '));
|
|
102
|
+
template = template.replace(/{{FILE_EXTENSIONS}}/g, metadata.fileExtensions.join(', '));
|
|
103
|
+
template = template.replace(/{{PRESET_NAME}}/g, metadata.name);
|
|
104
|
+
|
|
105
|
+
logger.debug('preset-loader - loadTemplate', 'Template loaded and processed', {
|
|
106
|
+
templatePath,
|
|
107
|
+
originalLength: template.length
|
|
108
|
+
});
|
|
142
109
|
|
|
143
110
|
return template;
|
|
144
|
-
|
|
145
111
|
} catch (error) {
|
|
146
112
|
logger.error(
|
|
147
113
|
'preset-loader - loadTemplate',
|
|
@@ -195,18 +161,10 @@ export async function listPresets() {
|
|
|
195
161
|
`Expected location: ${presetsDir}`
|
|
196
162
|
]);
|
|
197
163
|
logger.warning(errorMsg);
|
|
198
|
-
logger.debug(
|
|
199
|
-
'preset-loader - listPresets',
|
|
200
|
-
'Failed to read presets directory',
|
|
201
|
-
error
|
|
202
|
-
);
|
|
164
|
+
logger.debug('preset-loader - listPresets', 'Failed to read presets directory', error);
|
|
203
165
|
}
|
|
204
166
|
|
|
205
|
-
logger.debug(
|
|
206
|
-
'preset-loader - listPresets',
|
|
207
|
-
'Presets listed',
|
|
208
|
-
{ count: presets.length }
|
|
209
|
-
);
|
|
167
|
+
logger.debug('preset-loader - listPresets', 'Presets listed', { count: presets.length });
|
|
210
168
|
|
|
211
169
|
return presets;
|
|
212
170
|
}
|
|
@@ -55,29 +55,23 @@ const loadTemplate = async (templateName, baseDir = '.claude/prompts') => {
|
|
|
55
55
|
templatePath = path.join(repoRoot, 'templates', templateName);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
logger.debug(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
);
|
|
58
|
+
logger.debug('prompt-builder - loadTemplate', 'Loading template', {
|
|
59
|
+
templateName,
|
|
60
|
+
repoRoot,
|
|
61
|
+
templatePath
|
|
62
|
+
});
|
|
63
63
|
|
|
64
64
|
try {
|
|
65
65
|
const content = await fs.readFile(templatePath, 'utf8');
|
|
66
66
|
|
|
67
|
-
logger.debug(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
);
|
|
67
|
+
logger.debug('prompt-builder - loadTemplate', 'Template loaded successfully', {
|
|
68
|
+
templatePath,
|
|
69
|
+
contentLength: content.length
|
|
70
|
+
});
|
|
72
71
|
|
|
73
72
|
return content;
|
|
74
|
-
|
|
75
73
|
} catch (error) {
|
|
76
|
-
logger.error(
|
|
77
|
-
'prompt-builder - loadTemplate',
|
|
78
|
-
`Template not found: ${templatePath}`,
|
|
79
|
-
error
|
|
80
|
-
);
|
|
74
|
+
logger.error('prompt-builder - loadTemplate', `Template not found: ${templatePath}`, error);
|
|
81
75
|
|
|
82
76
|
throw new PromptBuilderError('Template file not found', {
|
|
83
77
|
templatePath,
|
|
@@ -99,15 +93,14 @@ const loadTemplate = async (templateName, baseDir = '.claude/prompts') => {
|
|
|
99
93
|
* => 'Repo: my-repo'
|
|
100
94
|
*/
|
|
101
95
|
const replaceTemplate = (template, variables) => {
|
|
102
|
-
logger.debug(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
{ variableCount: Object.keys(variables).length }
|
|
106
|
-
);
|
|
96
|
+
logger.debug('prompt-builder - replaceTemplate', 'Replacing template placeholders', {
|
|
97
|
+
variableCount: Object.keys(variables).length
|
|
98
|
+
});
|
|
107
99
|
|
|
108
|
-
return Object.entries(variables).reduce(
|
|
109
|
-
result.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value)
|
|
110
|
-
|
|
100
|
+
return Object.entries(variables).reduce(
|
|
101
|
+
(result, [key, value]) => result.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value),
|
|
102
|
+
template
|
|
103
|
+
);
|
|
111
104
|
};
|
|
112
105
|
|
|
113
106
|
/**
|
|
@@ -129,11 +122,10 @@ const replaceTemplate = (template, variables) => {
|
|
|
129
122
|
* });
|
|
130
123
|
*/
|
|
131
124
|
const loadPrompt = async (templateName, variables = {}, baseDir = '.claude/prompts') => {
|
|
132
|
-
logger.debug(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
);
|
|
125
|
+
logger.debug('prompt-builder - loadPrompt', 'Loading prompt with variables', {
|
|
126
|
+
templateName,
|
|
127
|
+
variableCount: Object.keys(variables).length
|
|
128
|
+
});
|
|
137
129
|
|
|
138
130
|
try {
|
|
139
131
|
// Load template
|
|
@@ -142,14 +134,12 @@ const loadPrompt = async (templateName, variables = {}, baseDir = '.claude/promp
|
|
|
142
134
|
// Replace variables
|
|
143
135
|
const prompt = replaceTemplate(template, variables);
|
|
144
136
|
|
|
145
|
-
logger.debug(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
);
|
|
137
|
+
logger.debug('prompt-builder - loadPrompt', 'Prompt loaded successfully', {
|
|
138
|
+
templateName,
|
|
139
|
+
promptLength: prompt.length
|
|
140
|
+
});
|
|
150
141
|
|
|
151
142
|
return prompt;
|
|
152
|
-
|
|
153
143
|
} catch (error) {
|
|
154
144
|
logger.error(
|
|
155
145
|
'prompt-builder - loadPrompt',
|
|
@@ -173,11 +163,12 @@ const loadPrompt = async (templateName, variables = {}, baseDir = '.claude/promp
|
|
|
173
163
|
* @returns {string} Formatted file section
|
|
174
164
|
*/
|
|
175
165
|
const formatFileSection = ({ path, diff, content, isNew }) => {
|
|
176
|
-
logger.debug(
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
166
|
+
logger.debug('prompt-builder - formatFileSection', 'Formatting file section', {
|
|
167
|
+
path,
|
|
168
|
+
isNew,
|
|
169
|
+
hasDiff: !!diff,
|
|
170
|
+
hasContent: !!content
|
|
171
|
+
});
|
|
181
172
|
|
|
182
173
|
let section = `\n--- Archivo: ${path} ---\n`;
|
|
183
174
|
|
|
@@ -220,11 +211,13 @@ const buildAnalysisPrompt = async ({
|
|
|
220
211
|
subagentConfig = null,
|
|
221
212
|
baseDir = '.claude/prompts'
|
|
222
213
|
} = {}) => {
|
|
223
|
-
logger.debug(
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
214
|
+
logger.debug('prompt-builder - buildAnalysisPrompt', 'Building analysis prompt', {
|
|
215
|
+
templateName,
|
|
216
|
+
guidelinesName,
|
|
217
|
+
fileCount: files.length,
|
|
218
|
+
subagentsEnabled: subagentConfig?.enabled,
|
|
219
|
+
baseDir
|
|
220
|
+
});
|
|
228
221
|
|
|
229
222
|
try {
|
|
230
223
|
// Load template and guidelines
|
|
@@ -244,11 +237,15 @@ const buildAnalysisPrompt = async ({
|
|
|
244
237
|
BATCH_SIZE: subagentConfig.batchSize || 3,
|
|
245
238
|
MODEL: subagentConfig.model || 'haiku'
|
|
246
239
|
};
|
|
247
|
-
prompt += `\n\n${
|
|
240
|
+
prompt += `\n\n${replaceTemplate(subagentInstruction, subagentVariables)}\n`;
|
|
248
241
|
|
|
249
|
-
logger.info(
|
|
242
|
+
logger.info(
|
|
243
|
+
`🚀 Batch optimization enabled: ${files.length} files, ${subagentVariables.BATCH_SIZE} per batch, ${subagentVariables.MODEL} model`
|
|
244
|
+
);
|
|
250
245
|
} catch (error) {
|
|
251
|
-
logger.warning(
|
|
246
|
+
logger.warning(
|
|
247
|
+
'Subagent instruction template not found, proceeding without parallel analysis'
|
|
248
|
+
);
|
|
252
249
|
}
|
|
253
250
|
}
|
|
254
251
|
|
|
@@ -260,7 +257,7 @@ const buildAnalysisPrompt = async ({
|
|
|
260
257
|
prompt += '\n\n=== CHANGES TO REVIEW ===\n';
|
|
261
258
|
|
|
262
259
|
// Add each file
|
|
263
|
-
files.forEach(fileData => {
|
|
260
|
+
files.forEach((fileData) => {
|
|
264
261
|
prompt += formatFileSection(fileData);
|
|
265
262
|
});
|
|
266
263
|
|
|
@@ -269,14 +266,12 @@ const buildAnalysisPrompt = async ({
|
|
|
269
266
|
prompt = replaceTemplate(prompt, metadata);
|
|
270
267
|
}
|
|
271
268
|
|
|
272
|
-
logger.debug(
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
);
|
|
269
|
+
logger.debug('prompt-builder - buildAnalysisPrompt', 'Prompt built successfully', {
|
|
270
|
+
promptLength: prompt.length,
|
|
271
|
+
fileCount: files.length
|
|
272
|
+
});
|
|
277
273
|
|
|
278
274
|
return prompt;
|
|
279
|
-
|
|
280
275
|
} catch (error) {
|
|
281
276
|
logger.error(
|
|
282
277
|
'prompt-builder - buildAnalysisPrompt',
|