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.
Files changed (46) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/CLAUDE.md +12 -8
  3. package/README.md +2 -1
  4. package/bin/claude-hooks +75 -89
  5. package/lib/cli-metadata.js +301 -0
  6. package/lib/commands/analyze-diff.js +12 -10
  7. package/lib/commands/analyze.js +9 -5
  8. package/lib/commands/bump-version.js +66 -43
  9. package/lib/commands/create-pr.js +71 -34
  10. package/lib/commands/debug.js +4 -7
  11. package/lib/commands/generate-changelog.js +11 -4
  12. package/lib/commands/help.js +47 -27
  13. package/lib/commands/helpers.js +66 -43
  14. package/lib/commands/hooks.js +15 -13
  15. package/lib/commands/install.js +546 -39
  16. package/lib/commands/migrate-config.js +8 -11
  17. package/lib/commands/presets.js +6 -13
  18. package/lib/commands/setup-github.js +12 -3
  19. package/lib/commands/telemetry-cmd.js +8 -6
  20. package/lib/commands/update.js +1 -2
  21. package/lib/config.js +36 -31
  22. package/lib/hooks/pre-commit.js +34 -54
  23. package/lib/hooks/prepare-commit-msg.js +39 -58
  24. package/lib/utils/analysis-engine.js +28 -21
  25. package/lib/utils/changelog-generator.js +162 -34
  26. package/lib/utils/claude-client.js +438 -377
  27. package/lib/utils/claude-diagnostics.js +20 -10
  28. package/lib/utils/file-operations.js +51 -79
  29. package/lib/utils/file-utils.js +46 -9
  30. package/lib/utils/git-operations.js +140 -123
  31. package/lib/utils/git-tag-manager.js +24 -23
  32. package/lib/utils/github-api.js +85 -61
  33. package/lib/utils/github-client.js +12 -14
  34. package/lib/utils/installation-diagnostics.js +4 -4
  35. package/lib/utils/interactive-ui.js +29 -17
  36. package/lib/utils/logger.js +4 -1
  37. package/lib/utils/pr-metadata-engine.js +67 -33
  38. package/lib/utils/preset-loader.js +20 -62
  39. package/lib/utils/prompt-builder.js +50 -55
  40. package/lib/utils/resolution-prompt.js +33 -44
  41. package/lib/utils/sanitize.js +20 -19
  42. package/lib/utils/task-id.js +27 -40
  43. package/lib/utils/telemetry.js +29 -17
  44. package/lib/utils/version-manager.js +173 -126
  45. package/lib/utils/which-command.js +23 -12
  46. 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) }...` : padded;
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', { defaultValue });
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('interactive-ui - promptConfirmation', 'Invalid input, using default', {
115
- answer: trimmed,
116
- defaultValue
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', { fieldName });
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', { key: selectedOption.key });
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(`Invalid input. Enter a number between 1 and ${state.length}, or press Enter to confirm.`);
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' // Default to abort (safer)
490
+ 'n' // Default to abort (safer)
479
491
  );
480
492
 
481
493
  if (choice === 'y') {
@@ -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(`${this.colors.red}Error details:${this.colors.reset}`, error.message || 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, // 3 minutes
66
- maxDiffSize: 50000, // 50KB
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('pr-metadata-engine - buildDiffPayload', 'Tier 1: Exceeded budget, applying Tier 2', {
129
- diffSize: fullDiff.length,
130
- maxSize
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('pr-metadata-engine - buildDiffPayload', 'Tier 2: Split diff into per-file chunks', {
150
- chunkCount: fileDiffs.length
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('pr-metadata-engine - buildDiffPayload', 'Tier 3: Replacing oversized files with stat-only');
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('pr-metadata-engine - buildDiffPayload', 'Tier 3: Stat-only demotion complete', {
205
- statOnlyCount
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', { targetBranch });
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', { currentBranch });
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('pr-metadata-engine - getBranchContext', 'Failed to fetch remote', fetchError);
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 { payload: diff, isTruncated, truncationDetails } = buildDiffPayload(
292
- baseBranch,
293
- 'HEAD',
294
- files,
295
- DEFAULTS.maxDiffSize
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('pr-metadata-engine - getBranchContext', 'Failed to get branch context', 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('pr-metadata-engine - generatePRMetadata', 'Failed to generate PR metadata', 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('pr-metadata-engine - analyzeBranchForPR', 'Failed to get branch context', {
443
- error: contextResult.error
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('pr-metadata-engine - analyzeBranchForPR', 'PR metadata generated successfully');
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
- 'preset-loader - loadPreset',
75
- 'Preset loaded successfully',
76
- {
77
- presetName,
78
- displayName: metadata.displayName,
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
- /{{TECH_STACK}}/g,
119
- metadata.techStack.join(', ')
120
- );
121
- template = template.replace(
122
- /{{FOCUS_AREAS}}/g,
123
- metadata.focusAreas.join(', ')
124
- );
125
- template = template.replace(
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
- 'prompt-builder - loadTemplate',
60
- 'Loading template',
61
- { templateName, repoRoot, templatePath }
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
- 'prompt-builder - loadTemplate',
69
- 'Template loaded successfully',
70
- { templatePath, contentLength: content.length }
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
- 'prompt-builder - replaceTemplate',
104
- 'Replacing template placeholders',
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((result, [key, value]) =>
109
- result.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value)
110
- , template);
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
- 'prompt-builder - loadPrompt',
134
- 'Loading prompt with variables',
135
- { templateName, variableCount: Object.keys(variables).length }
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
- 'prompt-builder - loadPrompt',
147
- 'Prompt loaded successfully',
148
- { templateName, promptLength: prompt.length }
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
- 'prompt-builder - formatFileSection',
178
- 'Formatting file section',
179
- { path, isNew, hasDiff: !!diff, hasContent: !!content }
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
- 'prompt-builder - buildAnalysisPrompt',
225
- 'Building analysis prompt',
226
- { templateName, guidelinesName, fileCount: files.length, subagentsEnabled: subagentConfig?.enabled, baseDir }
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${ replaceTemplate(subagentInstruction, subagentVariables) }\n`;
240
+ prompt += `\n\n${replaceTemplate(subagentInstruction, subagentVariables)}\n`;
248
241
 
249
- logger.info(`🚀 Batch optimization enabled: ${files.length} files, ${subagentVariables.BATCH_SIZE} per batch, ${subagentVariables.MODEL} model`);
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('Subagent instruction template not found, proceeding without parallel analysis');
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
- 'prompt-builder - buildAnalysisPrompt',
274
- 'Prompt built successfully',
275
- { promptLength: prompt.length, fileCount: files.length }
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',