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
@@ -7,12 +7,7 @@
7
7
 
8
8
  import fs from 'fs';
9
9
  import path from 'path';
10
- import {
11
- error,
12
- success,
13
- info,
14
- warning
15
- } from './helpers.js';
10
+ import { error, success, info, warning } from './helpers.js';
16
11
  import { extractLegacySettings } from './install.js';
17
12
 
18
13
  /**
@@ -43,7 +38,9 @@ export async function runMigrateConfig() {
43
38
 
44
39
  // If top-level preset doesn't exist or differs, use the one from overrides
45
40
  if (!rawConfig.preset || rawConfig.preset !== rawConfig.overrides.preset) {
46
- info(` Moving preset '${rawConfig.overrides.preset}' from overrides to top level`);
41
+ info(
42
+ ` Moving preset '${rawConfig.overrides.preset}' from overrides to top level`
43
+ );
47
44
  rawConfig.preset = rawConfig.overrides.preset;
48
45
  }
49
46
 
@@ -77,9 +74,10 @@ export async function runMigrateConfig() {
77
74
  const allowedOverrides = extractLegacySettings(rawConfig);
78
75
 
79
76
  // Check for advanced params
80
- const hasAdvancedParams = allowedOverrides.analysis?.ignoreExtensions ||
81
- allowedOverrides.commitMessage?.taskIdPattern ||
82
- allowedOverrides.subagents?.model;
77
+ const hasAdvancedParams =
78
+ allowedOverrides.analysis?.ignoreExtensions ||
79
+ allowedOverrides.commitMessage?.taskIdPattern ||
80
+ allowedOverrides.subagents?.model;
83
81
 
84
82
  // Build new config
85
83
  const newConfig = {
@@ -114,7 +112,6 @@ export async function runMigrateConfig() {
114
112
  console.log(`\nšŸ’¾ Old config backed up to: ${backupPath}`);
115
113
  console.log('\nšŸ’” Many parameters are now hardcoded with sensible defaults');
116
114
  console.log(' See CHANGELOG.md for full list of changes');
117
-
118
115
  } catch (err) {
119
116
  error(`Failed to migrate config: ${err.message}`);
120
117
  console.log('\nšŸ’” Manual migration:');
@@ -5,14 +5,7 @@
5
5
 
6
6
  import { listPresets } from '../utils/preset-loader.js';
7
7
  import { getConfig } from '../config.js';
8
- import {
9
- colors,
10
- error,
11
- success,
12
- info,
13
- warning,
14
- updateConfig
15
- } from './helpers.js';
8
+ import { colors, error, success, info, warning, updateConfig } from './helpers.js';
16
9
 
17
10
  /**
18
11
  * Shows all available presets
@@ -30,7 +23,7 @@ export async function runShowPresets() {
30
23
  info('Available presets:');
31
24
  console.log('');
32
25
 
33
- presets.forEach(preset => {
26
+ presets.forEach((preset) => {
34
27
  console.log(` ${colors.green}${preset.name}${colors.reset}`);
35
28
  console.log(` ${preset.displayName}`);
36
29
  console.log(` ${colors.blue}${preset.description}${colors.reset}`);
@@ -59,18 +52,18 @@ export async function runSetPreset(presetName) {
59
52
  await updateConfig('preset', presetName, {
60
53
  validator: async (name) => {
61
54
  const presets = await listPresets();
62
- const preset = presets.find(p => p.name === name);
55
+ const preset = presets.find((p) => p.name === name);
63
56
  if (!preset) {
64
57
  error(`Preset "${name}" not found`);
65
58
  info('Available presets:');
66
- presets.forEach(p => console.log(` - ${p.name}`));
59
+ presets.forEach((p) => console.log(` - ${p.name}`));
67
60
  throw new Error(`Invalid preset: ${name}`);
68
61
  }
69
62
  return preset;
70
63
  },
71
64
  successMessage: async (name) => {
72
65
  const presets = await listPresets();
73
- const preset = presets.find(p => p.name === name);
66
+ const preset = presets.find((p) => p.name === name);
74
67
  return `Preset '${preset.displayName}' activated`;
75
68
  }
76
69
  });
@@ -85,7 +78,7 @@ export async function runCurrentPreset() {
85
78
  const presetName = config.preset || 'default';
86
79
 
87
80
  const presets = await listPresets();
88
- const preset = presets.find(p => p.name === presetName);
81
+ const preset = presets.find((p) => p.name === presetName);
89
82
 
90
83
  if (preset) {
91
84
  console.log('');
@@ -10,7 +10,14 @@
10
10
  */
11
11
 
12
12
  import { validateToken, saveGitHubToken, validateTokenFormat } from '../utils/github-api.js';
13
- import { promptConfirmation, promptEditField, showSuccess, showError, showInfo, showWarning } from '../utils/interactive-ui.js';
13
+ import {
14
+ promptConfirmation,
15
+ promptEditField,
16
+ showSuccess,
17
+ showError,
18
+ showInfo,
19
+ showWarning
20
+ } from '../utils/interactive-ui.js';
14
21
  import logger from '../utils/logger.js';
15
22
 
16
23
  /**
@@ -39,7 +46,9 @@ export async function runSetupGitHub() {
39
46
  }
40
47
  }
41
48
  } catch (e) {
42
- logger.debug('setup-github', 'No existing token found or validation failed', { error: e.message });
49
+ logger.debug('setup-github', 'No existing token found or validation failed', {
50
+ error: e.message
51
+ });
43
52
  }
44
53
 
45
54
  // Show instructions
@@ -71,7 +80,7 @@ export async function runSetupGitHub() {
71
80
  // Save token
72
81
  const saveResult = saveGitHubToken(trimmedToken);
73
82
  if (!saveResult.success) {
74
- showError(`Failed to save token: ${ saveResult.error}`);
83
+ showError(`Failed to save token: ${saveResult.error}`);
75
84
  return;
76
85
  }
77
86
  showSuccess(`Token saved to ${saveResult.path}`);
@@ -4,13 +4,13 @@
4
4
  */
5
5
 
6
6
  import { getConfig } from '../config.js';
7
- import { displayStatistics as showTelemetryStats, clearTelemetry as clearTelemetryData } from '../utils/telemetry.js';
7
+ import {
8
+ displayStatistics as showTelemetryStats,
9
+ clearTelemetry as clearTelemetryData
10
+ } from '../utils/telemetry.js';
8
11
  import { promptConfirmation } from '../utils/interactive-ui.js';
9
12
  import logger from '../utils/logger.js';
10
- import {
11
- success,
12
- info
13
- } from './helpers.js';
13
+ import { success, info } from './helpers.js';
14
14
 
15
15
  /**
16
16
  * Show telemetry statistics
@@ -38,7 +38,9 @@ export async function runClearTelemetry() {
38
38
  return;
39
39
  }
40
40
 
41
- const confirmed = await promptConfirmation('Are you sure you want to clear all telemetry data?');
41
+ const confirmed = await promptConfirmation(
42
+ 'Are you sure you want to clear all telemetry data?'
43
+ );
42
44
  if (confirmed) {
43
45
  await clearTelemetryData();
44
46
  success('Telemetry data cleared successfully');
@@ -49,7 +49,6 @@ export async function runUpdate() {
49
49
  // Reinstall hooks with the new version
50
50
  info('Reinstalling hooks with the new version...');
51
51
  await runInstall(['--force']);
52
-
53
52
  } catch (updateError) {
54
53
  error('Error updating. Try running: npm install -g claude-git-hooks@latest');
55
54
  }
@@ -61,7 +60,7 @@ export async function runUpdate() {
61
60
  success('Update completed');
62
61
  await runInstall(['--force']);
63
62
  } catch (updateError) {
64
- error(`Error updating: ${ updateError.message}`);
63
+ error(`Error updating: ${updateError.message}`);
65
64
  }
66
65
  }
67
66
  }
package/lib/config.js CHANGED
@@ -34,21 +34,21 @@ import logger from './utils/logger.js';
34
34
  */
35
35
  const HARDCODED = {
36
36
  analysis: {
37
- maxFileSize: 1000000, // 1MB - sufficient for most files
38
- maxFiles: 20, // Reasonable limit per commit
39
- timeout: 300000, // 5 minutes - adequate for Claude API
40
- contextLines: 3, // Git default
41
- ignoreExtensions: [], // Can be set in advanced config only
37
+ maxFileSize: 1000000, // 1MB - sufficient for most files
38
+ maxFiles: 20, // Reasonable limit per commit
39
+ timeout: 300000, // 5 minutes - adequate for Claude API
40
+ contextLines: 3, // Git default
41
+ ignoreExtensions: [] // Can be set in advanced config only
42
42
  },
43
43
  commitMessage: {
44
- autoKeyword: 'auto', // Standard keyword
45
- timeout: 300000, // Use same timeout as analysis
46
- taskIdPattern: '([A-Z]{1,3}[-\\s]\\d{3,5})', // Jira/GitHub/Linear pattern
44
+ autoKeyword: 'auto', // Standard keyword
45
+ timeout: 300000, // Use same timeout as analysis
46
+ taskIdPattern: '([A-Z]{1,3}[-\\s]\\d{3,5})' // Jira/GitHub/Linear pattern
47
47
  },
48
48
  subagents: {
49
- enabled: true, // Enable by default (faster analysis)
50
- model: 'haiku', // Fast and cost-effective
51
- batchSize: 3, // Reasonable parallelization
49
+ enabled: true, // Enable by default (faster analysis)
50
+ model: 'haiku', // Fast and cost-effective
51
+ batchSize: 3 // Reasonable parallelization
52
52
  },
53
53
  templates: {
54
54
  baseDir: '.claude/prompts',
@@ -58,25 +58,25 @@ const HARDCODED = {
58
58
  analyzeDiff: 'ANALYZE_DIFF.md',
59
59
  resolution: 'CLAUDE_RESOLUTION_PROMPT.md',
60
60
  subagentInstruction: 'SUBAGENT_INSTRUCTION.md',
61
- createGithubPR: 'CREATE_GITHUB_PR.md',
61
+ createGithubPR: 'CREATE_GITHUB_PR.md'
62
62
  },
63
63
  output: {
64
64
  outputDir: '.claude/out',
65
65
  debugFile: '.claude/out/debug-claude-response.json',
66
66
  resolutionFile: '.claude/out/claude_resolution_prompt.md',
67
- prAnalysisFile: '.claude/out/pr-analysis.json',
67
+ prAnalysisFile: '.claude/out/pr-analysis.json'
68
68
  },
69
69
  system: {
70
- debug: false, // Controlled by --debug flag
71
- telemetry: true, // Opt-out telemetry for debugging (local only, set to false to disable)
72
- wslCheckTimeout: 15000, // System behavior
70
+ debug: false, // Controlled by --debug flag
71
+ telemetry: true, // Opt-out telemetry for debugging (local only, set to false to disable)
72
+ wslCheckTimeout: 15000 // System behavior
73
73
  },
74
74
  git: {
75
- diffFilter: 'ACM', // Standard: Added, Copied, Modified
75
+ diffFilter: 'ACM' // Standard: Added, Copied, Modified
76
76
  },
77
77
  github: {
78
- enabled: true, // Always enabled
79
- },
78
+ enabled: true // Always enabled
79
+ }
80
80
  };
81
81
 
82
82
  /**
@@ -87,9 +87,10 @@ const defaults = {
87
87
  // GitHub PR configuration (user-specific)
88
88
  github: {
89
89
  pr: {
90
- defaultBase: 'develop', // Project default branch
91
- reviewers: [], // Project reviewers
92
- labelRules: { // Labels by preset
90
+ defaultBase: 'develop', // Project default branch
91
+ reviewers: [], // Project reviewers
92
+ labelRules: {
93
+ // Labels by preset
93
94
  backend: ['backend', 'java'],
94
95
  frontend: ['frontend', 'react'],
95
96
  fullstack: ['fullstack'],
@@ -98,17 +99,17 @@ const defaults = {
98
99
  default: []
99
100
  },
100
101
  // Auto-push configuration (v2.11.0)
101
- autoPush: true, // Auto-push unpublished branches
102
- pushConfirm: true, // Prompt for confirmation before push
103
- verifyRemote: true, // Verify remote exists before push
104
- showCommits: true, // Show commit preview before push
105
- },
102
+ autoPush: true, // Auto-push unpublished branches
103
+ pushConfirm: true, // Prompt for confirmation before push
104
+ verifyRemote: true, // Verify remote exists before push
105
+ showCommits: true // Show commit preview before push
106
+ }
106
107
  },
107
108
 
108
109
  // Subagent configuration (preset can override)
109
110
  subagents: {
110
- batchSize: 3, // Files per parallel batch
111
- },
111
+ batchSize: 3 // Files per parallel batch
112
+ }
112
113
  };
113
114
 
114
115
  /**
@@ -137,10 +138,14 @@ const loadUserConfig = async (baseDir = process.cwd()) => {
137
138
  if (rawConfig.version === '2.8.0') {
138
139
  configFormat = 'v3';
139
140
  } else if (rawConfig.version) {
140
- throw new Error(`Unsupported config version: ${rawConfig.version}. Please run 'claude-hooks migrate-config'`);
141
+ throw new Error(
142
+ `Unsupported config version: ${rawConfig.version}. Please run 'claude-hooks migrate-config'`
143
+ );
141
144
  } else if (Object.keys(rawConfig).length > 0 && rawConfig.preset) {
142
145
  configFormat = 'legacy';
143
- logger.warning('āš ļø Legacy config format detected. Run "claude-hooks migrate-config" to update to v2.8.0');
146
+ logger.warning(
147
+ 'āš ļø Legacy config format detected. Run "claude-hooks migrate-config" to update to v2.8.0'
148
+ );
144
149
  }
145
150
  }
146
151
  } catch (error) {
@@ -19,22 +19,10 @@
19
19
  * - resolution-prompt: Issue resolution generation
20
20
  */
21
21
 
22
- import {
23
- getStagedFiles,
24
- getRepoRoot
25
- } from '../utils/git-operations.js';
26
- import {
27
- filterFiles,
28
- } from '../utils/file-operations.js';
29
- import {
30
- buildFilesData,
31
- runAnalysis,
32
- displayResults
33
- } from '../utils/analysis-engine.js';
34
- import {
35
- generateResolutionPrompt,
36
- shouldGeneratePrompt
37
- } from '../utils/resolution-prompt.js';
22
+ import { getStagedFiles, getRepoRoot } from '../utils/git-operations.js';
23
+ import { filterFiles } from '../utils/file-operations.js';
24
+ import { buildFilesData, runAnalysis, displayResults } from '../utils/analysis-engine.js';
25
+ import { generateResolutionPrompt, shouldGeneratePrompt } from '../utils/resolution-prompt.js';
38
26
  import { loadPreset } from '../utils/preset-loader.js';
39
27
  import { getVersion, calculateBatches } from '../utils/package-info.js';
40
28
  import logger from '../utils/logger.js';
@@ -81,38 +69,26 @@ const main = async () => {
81
69
  const cwdNormalized = normalizePath(process.cwd());
82
70
  const repoRootNormalized = normalizePath(repoRoot);
83
71
 
84
- logger.debug(
85
- 'pre-commit - main',
86
- 'Working directory info',
87
- {
88
- 'process.cwd()': process.cwd(),
89
- 'repo root': repoRoot,
90
- 'cwd (normalized)': cwdNormalized,
91
- 'repo root (normalized)': repoRootNormalized,
92
- 'match': cwdNormalized === repoRootNormalized
93
- }
94
- );
72
+ logger.debug('pre-commit - main', 'Working directory info', {
73
+ 'process.cwd()': process.cwd(),
74
+ 'repo root': repoRoot,
75
+ 'cwd (normalized)': cwdNormalized,
76
+ 'repo root (normalized)': repoRootNormalized,
77
+ match: cwdNormalized === repoRootNormalized
78
+ });
95
79
 
96
- logger.debug(
97
- 'pre-commit - main',
98
- 'Configuration',
99
- { ...config }
100
- );
80
+ logger.debug('pre-commit - main', 'Configuration', { ...config });
101
81
 
102
82
  // Load active preset
103
83
  const presetName = config.preset || 'default';
104
84
  const { metadata } = await loadPreset(presetName);
105
85
 
106
86
  logger.info(`šŸŽÆ Analyzing with '${metadata.displayName}' preset`);
107
- logger.debug(
108
- 'pre-commit - main',
109
- 'Preset loaded',
110
- {
111
- preset: presetName,
112
- fileExtensions: metadata.fileExtensions,
113
- techStack: metadata.techStack
114
- }
115
- );
87
+ logger.debug('pre-commit - main', 'Preset loaded', {
88
+ preset: presetName,
89
+ fileExtensions: metadata.fileExtensions,
90
+ techStack: metadata.techStack
91
+ });
116
92
 
117
93
  // Use preset's file extensions
118
94
  const allowedExtensions = metadata.fileExtensions;
@@ -129,11 +105,10 @@ const main = async () => {
129
105
  }
130
106
 
131
107
  logger.info(`Files to review: ${stagedFiles.length}`);
132
- logger.debug(
133
- 'pre-commit - main',
134
- 'Files matched by preset',
135
- { count: stagedFiles.length, extensions: allowedExtensions }
136
- );
108
+ logger.debug('pre-commit - main', 'Files matched by preset', {
109
+ count: stagedFiles.length,
110
+ extensions: allowedExtensions
111
+ });
137
112
 
138
113
  // Step 2: Filter files by size
139
114
  logger.debug('pre-commit - main', 'Filtering files by size');
@@ -142,12 +117,12 @@ const main = async () => {
142
117
  extensions: allowedExtensions
143
118
  });
144
119
 
145
- const validFiles = filteredFiles.filter(f => f.valid);
146
- const invalidFiles = filteredFiles.filter(f => !f.valid);
120
+ const validFiles = filteredFiles.filter((f) => f.valid);
121
+ const invalidFiles = filteredFiles.filter((f) => !f.valid);
147
122
 
148
123
  // Show user-facing warnings for rejected files
149
124
  if (invalidFiles.length > 0) {
150
- invalidFiles.forEach(file => {
125
+ invalidFiles.forEach((file) => {
151
126
  logger.warning(`Skipping ${file.path}: ${file.reason}`);
152
127
  });
153
128
  }
@@ -177,9 +152,13 @@ const main = async () => {
177
152
 
178
153
  if (subagentsEnabled && filesData.length >= 3) {
179
154
  const { numBatches, shouldShowBatches } = calculateBatches(filesData.length, batchSize);
180
- console.log(`⚔ Batch optimization: ${subagentModel} model, ${batchSize} files per batch`);
155
+ console.log(
156
+ `⚔ Batch optimization: ${subagentModel} model, ${batchSize} files per batch`
157
+ );
181
158
  if (shouldShowBatches) {
182
- console.log(`šŸ“Š Analyzing ${filesData.length} files in ${numBatches} batch${numBatches > 1 ? 'es' : ''}`);
159
+ console.log(
160
+ `šŸ“Š Analyzing ${filesData.length} files in ${numBatches} batch${numBatches > 1 ? 'es' : ''}`
161
+ );
183
162
  }
184
163
  }
185
164
 
@@ -205,7 +184,7 @@ const main = async () => {
205
184
  // Show blocking issues
206
185
  if (Array.isArray(result.blockingIssues) && result.blockingIssues.length > 0) {
207
186
  console.log('=== CRITICAL ISSUES ===');
208
- result.blockingIssues.forEach(issue => {
187
+ result.blockingIssues.forEach((issue) => {
209
188
  console.log(`- ${issue.description || 'Unknown issue'}`);
210
189
  });
211
190
  console.log();
@@ -219,7 +198,9 @@ const main = async () => {
219
198
 
220
199
  console.log('═══ AI RESOLUTION PROMPT GENERATED ═══');
221
200
  logger.info(`An AI-friendly prompt has been generated at: ${resolutionPath}`);
222
- logger.warning('Copy this file to a new Claude instance to resolve problems automatically.');
201
+ logger.warning(
202
+ 'Copy this file to a new Claude instance to resolve problems automatically.'
203
+ );
223
204
  console.log();
224
205
  }
225
206
 
@@ -232,7 +213,6 @@ const main = async () => {
232
213
  logger.success('Code analysis completed. Quality gate passed.');
233
214
 
234
215
  process.exit(0);
235
-
236
216
  } catch (error) {
237
217
  const duration = ((Date.now() - startTime) / 1000).toFixed(2);
238
218
  console.log(`\nā±ļø Analysis time: ${duration}s`);
@@ -35,11 +35,9 @@ import { getOrPromptTaskId, formatWithTaskId } from '../utils/task-id.js';
35
35
  * @returns {Promise<string>} Complete prompt
36
36
  */
37
37
  const buildCommitPrompt = async (filesData, stats) => {
38
- logger.debug(
39
- 'prepare-commit-msg - buildCommitPrompt',
40
- 'Building commit message prompt',
41
- { fileCount: filesData.length }
42
- );
38
+ logger.debug('prepare-commit-msg - buildCommitPrompt', 'Building commit message prompt', {
39
+ fileCount: filesData.length
40
+ });
43
41
 
44
42
  // Build file list
45
43
  const fileList = filesData.map(({ path }) => path).join('\n');
@@ -81,11 +79,11 @@ const buildCommitPrompt = async (filesData, stats) => {
81
79
  * }
82
80
  */
83
81
  const formatCommitMessage = (json) => {
84
- logger.debug(
85
- 'prepare-commit-msg - formatCommitMessage',
86
- 'Formatting commit message',
87
- { type: json.type, hasScope: !!json.scope, hasBody: !!json.body }
88
- );
82
+ logger.debug('prepare-commit-msg - formatCommitMessage', 'Formatting commit message', {
83
+ type: json.type,
84
+ hasScope: !!json.scope,
85
+ hasBody: !!json.body
86
+ });
89
87
 
90
88
  const type = json.type || 'feat';
91
89
  const scope = json.scope && json.scope !== 'null' ? json.scope : null;
@@ -131,19 +129,12 @@ const main = async () => {
131
129
  const commitMsgFile = args[0];
132
130
  const commitSource = args[1];
133
131
 
134
- logger.debug(
135
- 'prepare-commit-msg - main',
136
- 'Hook started',
137
- { commitMsgFile, commitSource }
138
- );
132
+ logger.debug('prepare-commit-msg - main', 'Hook started', { commitMsgFile, commitSource });
139
133
 
140
134
  // Only process normal commits
141
135
  // Why: Don't interfere with merge commits, amend, squash, etc.
142
136
  if (commitSource && commitSource !== 'message') {
143
- logger.debug(
144
- 'prepare-commit-msg - main',
145
- `Skipping: commit source is ${commitSource}`
146
- );
137
+ logger.debug('prepare-commit-msg - main', `Skipping: commit source is ${commitSource}`);
147
138
  process.exit(0);
148
139
  }
149
140
 
@@ -151,18 +142,11 @@ const main = async () => {
151
142
  const currentMsg = await fs.readFile(commitMsgFile, 'utf8');
152
143
  const firstLine = currentMsg.split('\n')[0].trim();
153
144
 
154
- logger.debug(
155
- 'prepare-commit-msg - main',
156
- 'Current commit message',
157
- { firstLine }
158
- );
145
+ logger.debug('prepare-commit-msg - main', 'Current commit message', { firstLine });
159
146
 
160
147
  // Check if message is "auto"
161
148
  if (firstLine !== config.commitMessage.autoKeyword) {
162
- logger.debug(
163
- 'prepare-commit-msg - main',
164
- 'Not generating: message is not "auto"'
165
- );
149
+ logger.debug('prepare-commit-msg - main', 'Not generating: message is not "auto"');
166
150
  process.exit(0);
167
151
  }
168
152
 
@@ -182,14 +166,17 @@ const main = async () => {
182
166
  // Step 1: Auto-detect task ID from branch (no prompt)
183
167
  logger.debug('prepare-commit-msg - main', 'Detecting task ID from branch');
184
168
  const taskId = await getOrPromptTaskId({
185
- prompt: false, // Don't prompt - just detect from branch
169
+ prompt: false, // Don't prompt - just detect from branch
186
170
  required: false,
187
- config // Pass config for custom pattern
171
+ config // Pass config for custom pattern
188
172
  });
189
173
 
190
174
  // Note: getOrPromptTaskId() already logs the task ID if found
191
175
  if (!taskId) {
192
- logger.debug('prepare-commit-msg - main', 'No task ID found in branch, continuing without it');
176
+ logger.debug(
177
+ 'prepare-commit-msg - main',
178
+ 'No task ID found in branch, continuing without it'
179
+ );
193
180
  }
194
181
 
195
182
  // Step 2: Get staged files
@@ -204,11 +191,10 @@ const main = async () => {
204
191
  // Get statistics
205
192
  const stats = getStagedStats();
206
193
 
207
- logger.debug(
208
- 'prepare-commit-msg - main',
209
- 'Staged files',
210
- { fileCount: stagedFiles.length, stats }
211
- );
194
+ logger.debug('prepare-commit-msg - main', 'Staged files', {
195
+ fileCount: stagedFiles.length,
196
+ stats
197
+ });
212
198
 
213
199
  // Build file data with diffs
214
200
  const filesData = await Promise.all(
@@ -228,7 +214,6 @@ const main = async () => {
228
214
  diff,
229
215
  size: fileStats.size
230
216
  };
231
-
232
217
  } catch (error) {
233
218
  logger.error(
234
219
  'prepare-commit-msg - main',
@@ -248,17 +233,15 @@ const main = async () => {
248
233
  // Build prompt
249
234
  const prompt = await buildCommitPrompt(filesData, stats);
250
235
 
251
- logger.debug(
252
- 'prepare-commit-msg - main',
253
- 'Prompt built',
254
- { promptLength: prompt.length }
255
- );
236
+ logger.debug('prepare-commit-msg - main', 'Prompt built', { promptLength: prompt.length });
256
237
 
257
238
  // Calculate batches if subagents enabled and applicable
258
239
  if (subagentsEnabled && filesData.length >= 3) {
259
240
  const { numBatches, shouldShowBatches } = calculateBatches(filesData.length, batchSize);
260
241
  if (shouldShowBatches) {
261
- console.log(`šŸ“Š Analyzing ${filesData.length} files in ${numBatches} batch${numBatches > 1 ? 'es' : ''}`);
242
+ console.log(
243
+ `šŸ“Š Analyzing ${filesData.length} files in ${numBatches} batch${numBatches > 1 ? 'es' : ''}`
244
+ );
262
245
  }
263
246
  }
264
247
 
@@ -269,9 +252,10 @@ const main = async () => {
269
252
  const telemetryContext = {
270
253
  fileCount: filesData.length,
271
254
  batchSize: config.subagents?.batchSize || 3,
272
- totalBatches: subagentsEnabled && filesData.length >= 3
273
- ? Math.ceil(filesData.length / (config.subagents?.batchSize || 3))
274
- : 1,
255
+ totalBatches:
256
+ subagentsEnabled && filesData.length >= 3
257
+ ? Math.ceil(filesData.length / (config.subagents?.batchSize || 3))
258
+ : 1,
275
259
  model: subagentModel,
276
260
  hook: 'prepare-commit-msg'
277
261
  };
@@ -282,11 +266,10 @@ const main = async () => {
282
266
  telemetryContext
283
267
  });
284
268
 
285
- logger.debug(
286
- 'prepare-commit-msg - main',
287
- 'Response received',
288
- { hasType: !!response.type, hasTitle: !!response.title }
289
- );
269
+ logger.debug('prepare-commit-msg - main', 'Response received', {
270
+ hasType: !!response.type,
271
+ hasTitle: !!response.title
272
+ });
290
273
 
291
274
  // Format message
292
275
  let message = formatCommitMessage(response);
@@ -294,15 +277,14 @@ const main = async () => {
294
277
  // Add task ID prefix if available
295
278
  if (taskId) {
296
279
  message = formatWithTaskId(message, taskId);
297
- logger.debug(
298
- 'prepare-commit-msg - main',
299
- 'Task ID added to message',
300
- { taskId, message: message.split('\n')[0] }
301
- );
280
+ logger.debug('prepare-commit-msg - main', 'Task ID added to message', {
281
+ taskId,
282
+ message: message.split('\n')[0]
283
+ });
302
284
  }
303
285
 
304
286
  // Write to commit message file
305
- await fs.writeFile(commitMsgFile, `${message }\n`, 'utf8');
287
+ await fs.writeFile(commitMsgFile, `${message}\n`, 'utf8');
306
288
 
307
289
  const duration = ((Date.now() - startTime) / 1000).toFixed(2);
308
290
  console.error(`ā±ļø Message generation completed in ${duration}s`);
@@ -310,7 +292,6 @@ const main = async () => {
310
292
  logger.success(`Message generated: ${message.split('\n')[0]}`);
311
293
 
312
294
  process.exit(0);
313
-
314
295
  } catch (error) {
315
296
  const duration = ((Date.now() - startTime) / 1000).toFixed(2);
316
297
  console.error(`ā±ļø Message generation failed after ${duration}s`);