claude-git-hooks 2.6.3 → 2.8.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/lib/config.js CHANGED
@@ -1,101 +1,98 @@
1
1
  /**
2
2
  * File: config.js
3
- * Purpose: Centralized configuration management
3
+ * Purpose: Simplified configuration management (v2.8.0)
4
4
  *
5
- * Priority: preset config > .claude/config.json > defaults
5
+ * Priority: preset config > .claude/config.json > hardcoded defaults
6
6
  *
7
- * Key features:
8
- * - Single source of truth for all configurable values
9
- * - No environment variables (except OS for platform detection)
10
- * - Preset-aware (allowedExtensions come from preset templates)
11
- * - User config (.claude/config.json) provides general preferences per project
12
- * - Preset config provides tech-stack-specific overrides (highest priority)
7
+ * v2.8.0 Changes:
8
+ * - Removed 21 redundant parameters (hardcoded sensible defaults)
9
+ * - Only 5 user-configurable parameters remain
10
+ * - Preset can override: subagents.batchSize
11
+ * - User can override: github.pr.*, subagents.batchSize
12
+ * - Advanced params moved to config.advanced.example.json
13
+ *
14
+ * User-configurable:
15
+ * - preset (required)
16
+ * - github.pr.defaultBase
17
+ * - github.pr.reviewers
18
+ * - github.pr.labelRules
19
+ * - subagents.batchSize
20
+ *
21
+ * Advanced (in example file only):
22
+ * - analysis.ignoreExtensions
23
+ * - commitMessage.taskIdPattern
24
+ * - subagents.model
13
25
  */
14
26
 
15
27
  import fs from 'fs';
16
28
  import path from 'path';
17
29
  import { fileURLToPath } from 'url';
30
+ import logger from './utils/logger.js';
18
31
 
19
32
  const __filename = fileURLToPath(import.meta.url);
20
33
  const __dirname = path.dirname(__filename);
21
34
 
22
35
  /**
23
- * Default configuration
24
- * All values can be overridden via .claude/config.json
36
+ * Hardcoded defaults (v3.0.0)
37
+ * These are NOT user-configurable - sensible defaults that work for everyone
25
38
  */
26
- const defaults = {
27
- // Analysis configuration
39
+ const HARDCODED = {
28
40
  analysis: {
29
- maxFileSize: 1000000, // 1MB - max file size to analyze
30
- maxFiles: 20, // Max number of files per commit
31
- timeout: 300000, // 3 minutes - Claude analysis timeout
32
- contextLines: 3, // Git diff context lines
33
- ignoreExtensions: [], // Extensions to exclude (e.g., ['.min.js', '.lock'])
34
- // NOTE: allowedExtensions comes from preset/template, not here
41
+ maxFileSize: 1000000, // 1MB - sufficient for most files
42
+ maxFiles: 20, // Reasonable limit per commit
43
+ timeout: 300000, // 5 minutes - adequate for Claude API
44
+ contextLines: 3, // Git default
45
+ ignoreExtensions: [], // Can be set in advanced config only
35
46
  },
36
-
37
- // Commit message generation
38
47
  commitMessage: {
39
- autoKeyword: 'auto', // Keyword to trigger auto-generation
40
- timeout: 300000,
41
- // Task-ID detection pattern (from branch name)
42
- // Default: 1-3 uppercase letters + separator + 3-5 digits
43
- // Examples: "ABC-12345", "IX-123", "DE 4567"
44
- // Regex is case-insensitive and extracted from branch name
45
- taskIdPattern: '([A-Z]{1,3}[-\\s]\\d{3,5})'
48
+ autoKeyword: 'auto', // Standard keyword
49
+ timeout: 300000, // Use same timeout as analysis
50
+ taskIdPattern: '([A-Z]{1,3}[-\\s]\\d{3,5})', // Jira/GitHub/Linear pattern
46
51
  },
47
-
48
- // Subagent configuration (parallel analysis)
49
52
  subagents: {
50
- enabled: false, // Enable parallel file analysis
51
- model: 'haiku', // haiku (fast), sonnet (balanced), opus (thorough)
52
- batchSize: 1, // Parallel subagents per batch
53
+ enabled: true, // Enable by default (faster analysis)
54
+ model: 'haiku', // Fast and cost-effective
55
+ batchSize: 3, // Reasonable parallelization
53
56
  },
54
-
55
- // Template paths
56
57
  templates: {
57
- baseDir: '.claude', // Base directory for all templates
58
- analysis: 'CLAUDE_ANALYSIS_PROMPT_SONAR.md', // Pre-commit analysis prompt
59
- guidelines: 'CLAUDE_PRE_COMMIT_SONAR.md', // Analysis guidelines
60
- commitMessage: 'COMMIT_MESSAGE.md', // Commit message prompt
61
- analyzeDiff: 'ANALYZE_DIFF.md', // PR analysis prompt
62
- resolution: 'CLAUDE_RESOLUTION_PROMPT.md', // Issue resolution prompt
63
- subagentInstruction: 'SUBAGENT_INSTRUCTION.md', // Parallel analysis instruction
64
- createGithubPR: 'CREATE_GITHUB_PR.md', // GitHub PR creation via MCP
58
+ baseDir: '.claude/prompts',
59
+ analysis: 'CLAUDE_ANALYSIS_PROMPT.md',
60
+ guidelines: 'CLAUDE_PRE_COMMIT.md',
61
+ commitMessage: 'COMMIT_MESSAGE.md',
62
+ analyzeDiff: 'ANALYZE_DIFF.md',
63
+ resolution: 'CLAUDE_RESOLUTION_PROMPT.md',
64
+ subagentInstruction: 'SUBAGENT_INSTRUCTION.md',
65
+ createGithubPR: 'CREATE_GITHUB_PR.md',
65
66
  },
66
-
67
- // Output file paths (relative to repo root)
68
67
  output: {
69
- outputDir: '.claude/out', // Output directory for all generated files
70
- debugFile: '.claude/out/debug-claude-response.json', // Debug response dump
71
- resolutionFile: '.claude/out/claude_resolution_prompt.md', // Generated resolution prompt
72
- prAnalysisFile: '.claude/out/pr-analysis.json', // PR analysis result
68
+ outputDir: '.claude/out',
69
+ debugFile: '.claude/out/debug-claude-response.json',
70
+ resolutionFile: '.claude/out/claude_resolution_prompt.md',
71
+ prAnalysisFile: '.claude/out/pr-analysis.json',
73
72
  },
74
-
75
- // System behavior
76
73
  system: {
77
- debug: false, // Enable debug logging and file dumps
78
- wslCheckTimeout: 3000, // 3 seconds - quick WSL availability check
74
+ debug: false, // Controlled by --debug flag
75
+ wslCheckTimeout: 15000, // System behavior
79
76
  },
80
-
81
- // Git operations
82
77
  git: {
83
- diffFilter: 'ACM', // Added, Copied, Modified (excludes Deleted)
78
+ diffFilter: 'ACM', // Standard: Added, Copied, Modified
84
79
  },
85
-
86
- // GitHub integration (v2.5.0+)
87
80
  github: {
88
- enabled: true, // Changed from false - enable by default since Octokit works without MCP
81
+ enabled: true, // Always enabled
82
+ },
83
+ };
89
84
 
90
- // Pull Request configuration
85
+ /**
86
+ * Default user-configurable values (v2.8.0)
87
+ * Only these can be overridden in .claude/config.json
88
+ */
89
+ const defaults = {
90
+ // GitHub PR configuration (user-specific)
91
+ github: {
91
92
  pr: {
92
- defaultBase: 'develop', // Default base branch for PRs
93
-
94
- // Reviewers (usernames without @)
95
- reviewers: [], // Example: ["juan.perez", "maria.garcia"]
96
-
97
- // Labels by preset
98
- labelRules: {
93
+ defaultBase: 'develop', // Project default branch
94
+ reviewers: [], // Project reviewers
95
+ labelRules: { // Labels by preset
99
96
  backend: ['backend', 'java'],
100
97
  frontend: ['frontend', 'react'],
101
98
  fullstack: ['fullstack'],
@@ -105,13 +102,21 @@ const defaults = {
105
102
  },
106
103
  },
107
104
  },
105
+
106
+ // Subagent configuration (preset can override)
107
+ subagents: {
108
+ batchSize: 3, // Files per parallel batch
109
+ },
108
110
  };
109
111
 
110
112
  /**
111
- * Loads user configuration from .claude/config.json
112
- * Merges with defaults and preset config (preset takes highest priority)
113
+ * Loads user configuration from .claude/config.json (v2.8.0)
114
+ *
115
+ * Format detection:
116
+ * - v2.8.0: { version: "2.8.0", preset: "...", overrides: {...} }
117
+ * - Legacy: { preset: "...", analysis: {...}, ... } (auto-migrates with warning)
113
118
  *
114
- * Priority: defaults < user config < preset config
119
+ * Merge priority: HARDCODED < defaults < preset config < user overrides
115
120
  *
116
121
  * @param {string} baseDir - Base directory to search for config (default: cwd)
117
122
  * @returns {Promise<Object>} Merged configuration
@@ -119,31 +124,119 @@ const defaults = {
119
124
  const loadUserConfig = async (baseDir = process.cwd()) => {
120
125
  const configPath = path.join(baseDir, '.claude', 'config.json');
121
126
 
122
- let userConfig = {};
127
+ let rawConfig = {};
128
+ let configFormat = 'default';
129
+
123
130
  try {
124
131
  if (fs.existsSync(configPath)) {
125
- userConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
132
+ rawConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
133
+
134
+ // Detect format
135
+ if (rawConfig.version === '2.8.0') {
136
+ configFormat = 'v3';
137
+ } else if (rawConfig.version) {
138
+ throw new Error(`Unsupported config version: ${rawConfig.version}. Please run 'claude-hooks migrate-config'`);
139
+ } else if (Object.keys(rawConfig).length > 0 && rawConfig.preset) {
140
+ configFormat = 'legacy';
141
+ logger.warning('⚠️ Legacy config format detected. Run "claude-hooks migrate-config" to update to v2.8.0');
142
+ }
126
143
  }
127
144
  } catch (error) {
128
- console.warn(`⚠️ Warning: Could not load .claude/config.json: ${error.message}`);
129
- console.warn('Using default configuration');
145
+ if (error.message.includes('Unsupported config version')) {
146
+ throw error;
147
+ }
148
+ logger.warning(`⚠️ Warning: Could not load .claude/config.json: ${error.message}`);
149
+ logger.warning('Using default configuration');
150
+ }
151
+
152
+ // Extract user overrides based on format
153
+ let userOverrides = {};
154
+ let presetName = null;
155
+
156
+ if (configFormat === 'v3') {
157
+ presetName = rawConfig.preset;
158
+ userOverrides = rawConfig.overrides || {};
159
+ } else if (configFormat === 'legacy') {
160
+ presetName = rawConfig.preset;
161
+ // Legacy format: extract only allowed parameters
162
+ userOverrides = extractAllowedParams(rawConfig);
130
163
  }
131
164
 
132
165
  // Load preset if specified
133
166
  let presetConfig = {};
134
- if (userConfig.preset) {
167
+ if (presetName) {
135
168
  try {
136
- // Dynamic import to avoid circular dependency
137
169
  const { loadPreset } = await import('./utils/preset-loader.js');
138
- const { config } = await loadPreset(userConfig.preset);
170
+ const { config } = await loadPreset(presetName);
139
171
  presetConfig = config;
140
172
  } catch (error) {
141
- console.warn(`⚠️ Warning: Preset "${userConfig.preset}" not found, using defaults`);
173
+ logger.warning(`⚠️ Warning: Preset "${presetName}" not found, using defaults`);
142
174
  }
143
175
  }
144
176
 
145
- // Merge: defaults < user < preset
146
- return deepMerge(deepMerge(defaults, userConfig), presetConfig);
177
+ // Merge priority: HARDCODED < defaults < preset < user overrides
178
+ const baseConfig = deepMerge(HARDCODED, defaults);
179
+ const withPreset = deepMerge(baseConfig, presetConfig);
180
+ const final = deepMerge(withPreset, userOverrides);
181
+
182
+ // Add preset name to final config (so hooks can display it)
183
+ if (presetName) {
184
+ final.preset = presetName;
185
+ }
186
+
187
+ return final;
188
+ };
189
+
190
+ /**
191
+ * Extracts only allowed parameters from legacy config
192
+ * v2.8.0 only allows: github.pr.*, subagents.batchSize
193
+ * Advanced: analysis.ignoreExtensions, commitMessage.taskIdPattern, subagents.model
194
+ *
195
+ * @param {Object} legacyConfig - Legacy format config
196
+ * @returns {Object} Allowed parameters only
197
+ */
198
+ const extractAllowedParams = (legacyConfig) => {
199
+ const allowed = {};
200
+
201
+ // GitHub PR config (fully allowed)
202
+ if (legacyConfig.github?.pr) {
203
+ allowed.github = { pr: {} };
204
+ if (legacyConfig.github.pr.defaultBase !== undefined) {
205
+ allowed.github.pr.defaultBase = legacyConfig.github.pr.defaultBase;
206
+ }
207
+ if (legacyConfig.github.pr.reviewers !== undefined) {
208
+ allowed.github.pr.reviewers = legacyConfig.github.pr.reviewers;
209
+ }
210
+ if (legacyConfig.github.pr.labelRules !== undefined) {
211
+ allowed.github.pr.labelRules = legacyConfig.github.pr.labelRules;
212
+ }
213
+ }
214
+
215
+ // Subagent batchSize (allowed)
216
+ if (legacyConfig.subagents?.batchSize !== undefined) {
217
+ allowed.subagents = { batchSize: legacyConfig.subagents.batchSize };
218
+ }
219
+
220
+ // Advanced params (allowed but warn)
221
+ if (legacyConfig.analysis?.ignoreExtensions !== undefined) {
222
+ if (!allowed.analysis) allowed.analysis = {};
223
+ allowed.analysis.ignoreExtensions = legacyConfig.analysis.ignoreExtensions;
224
+ logger.warning('ℹ️ Using advanced parameter: analysis.ignoreExtensions');
225
+ }
226
+
227
+ if (legacyConfig.commitMessage?.taskIdPattern !== undefined) {
228
+ if (!allowed.commitMessage) allowed.commitMessage = {};
229
+ allowed.commitMessage.taskIdPattern = legacyConfig.commitMessage.taskIdPattern;
230
+ logger.warning('ℹ️ Using advanced parameter: commitMessage.taskIdPattern');
231
+ }
232
+
233
+ if (legacyConfig.subagents?.model !== undefined) {
234
+ if (!allowed.subagents) allowed.subagents = {};
235
+ allowed.subagents.model = legacyConfig.subagents.model;
236
+ logger.warning('ℹ️ Using advanced parameter: subagents.model');
237
+ }
238
+
239
+ return allowed;
147
240
  };
148
241
 
149
242
  /**
@@ -185,9 +278,9 @@ const getConfig = async () => {
185
278
  return configInstance;
186
279
  };
187
280
 
188
- // Export async loader
189
- export { getConfig, loadUserConfig, defaults };
281
+ // Export async loader and constants
282
+ export { getConfig, loadUserConfig, defaults, HARDCODED };
190
283
 
191
284
  // Export a sync default for backwards compatibility (loads without preset)
192
- // NOTE: This will be deprecated - prefer using getConfig() directly
193
- export default defaults;
285
+ // NOTE: Returns HARDCODED merged with defaults
286
+ export default deepMerge(HARDCODED, defaults);
@@ -9,7 +9,7 @@
9
9
  * 2. Build analysis prompt with file diffs
10
10
  * 3. Send to Claude CLI for analysis
11
11
  * 4. Parse JSON response
12
- * 5. Display SonarQube-style results
12
+ * 5. Display structured analysis results
13
13
  * 6. Generate resolution prompt if issues found
14
14
  * 7. Block commit if quality gate fails
15
15
  *
@@ -52,8 +52,8 @@ import { getConfig } from '../config.js';
52
52
  */
53
53
 
54
54
  /**
55
- * Displays SonarQube-style analysis results
56
- * Why: Provides structured, visual feedback about code quality
55
+ * Displays structured analysis results
56
+ * Why: Provides clear, visual feedback about code quality
57
57
  *
58
58
  * @param {Object} result - Analysis result from Claude
59
59
  */
@@ -73,19 +73,16 @@ const displayResults = (result) => {
73
73
  }
74
74
  console.log();
75
75
 
76
- // Metrics
77
- if (result.metrics && typeof result.metrics === 'object') {
78
- console.log('📊 METRICS');
79
- console.log(`├─ Reliability: ${result.metrics.reliability || '?'}`);
80
- console.log(`├─ Security: ${result.metrics.security || '?'}`);
81
- console.log(`├─ Maintainability: ${result.metrics.maintainability || '?'}`);
82
- console.log(`├─ Coverage: ${result.metrics.coverage || '?'}%`);
83
- console.log(`├─ Duplications: ${result.metrics.duplications || '?'}%`);
84
- console.log(`└─ Complexity: ${result.metrics.complexity || '?'}`);
85
- console.log();
76
+ // Issues Summary - Simple count
77
+ if (Array.isArray(result.details) && result.details.length > 0) {
78
+ const fileCount = new Set(result.details.map(i => i.file)).size;
79
+ console.log(`📊 ${result.details.length} issue(s) found across ${fileCount} file(s)`);
80
+ } else {
81
+ console.log('✅ No issues found!');
86
82
  }
83
+ console.log();
87
84
 
88
- // Issues Summary
85
+ // Issues Breakdown (severity counts)
89
86
  if (result.issues && typeof result.issues === 'object') {
90
87
  console.log('📋 ISSUES SUMMARY');
91
88
 
@@ -187,9 +187,8 @@ const main = async () => {
187
187
  config: config // Pass config for custom pattern
188
188
  });
189
189
 
190
- if (taskId) {
191
- logger.info(`📋 Task ID detected: ${taskId}`);
192
- } else {
190
+ // Note: getOrPromptTaskId() already logs the task ID if found
191
+ if (!taskId) {
193
192
  logger.debug('prepare-commit-msg - main', 'No task ID found in branch, continuing without it');
194
193
  }
195
194