claude-git-hooks 2.14.1 → 2.15.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +118 -3
  2. package/LICENSE +20 -20
  3. package/README.md +4 -1
  4. package/bin/claude-hooks +84 -84
  5. package/lib/commands/analyze-diff.js +0 -1
  6. package/lib/commands/bump-version.js +23 -22
  7. package/lib/commands/create-pr.js +157 -15
  8. package/lib/commands/debug.js +1 -1
  9. package/lib/commands/helpers.js +11 -6
  10. package/lib/commands/install.js +7 -8
  11. package/lib/commands/migrate-config.js +24 -2
  12. package/lib/commands/setup-github.js +1 -1
  13. package/lib/commands/update.js +1 -1
  14. package/lib/config.js +0 -4
  15. package/lib/hooks/prepare-commit-msg.js +2 -2
  16. package/lib/utils/changelog-generator.js +6 -8
  17. package/lib/utils/claude-client.js +7 -6
  18. package/lib/utils/claude-diagnostics.js +14 -21
  19. package/lib/utils/file-operations.js +1 -1
  20. package/lib/utils/file-utils.js +0 -1
  21. package/lib/utils/git-operations.js +0 -1
  22. package/lib/utils/github-api.js +3 -3
  23. package/lib/utils/github-client.js +2 -2
  24. package/lib/utils/installation-diagnostics.js +1 -1
  25. package/lib/utils/prompt-builder.js +4 -6
  26. package/lib/utils/sanitize.js +13 -14
  27. package/lib/utils/task-id.js +18 -20
  28. package/lib/utils/telemetry.js +5 -7
  29. package/package.json +13 -5
  30. package/templates/config.advanced.example.json +113 -113
  31. package/templates/pre-commit +7 -0
  32. package/templates/presets/ai/config.json +5 -5
  33. package/templates/presets/backend/config.json +5 -5
  34. package/templates/presets/backend/preset.json +49 -49
  35. package/templates/presets/database/config.json +5 -5
  36. package/templates/presets/database/preset.json +38 -38
  37. package/templates/presets/default/config.json +5 -5
  38. package/templates/presets/default/preset.json +53 -53
  39. package/templates/presets/frontend/config.json +5 -5
  40. package/templates/presets/frontend/preset.json +50 -50
  41. package/templates/presets/fullstack/config.json +5 -5
  42. package/templates/presets/fullstack/preset.json +55 -55
@@ -47,14 +47,14 @@ export const parseGitHubRepo = () => {
47
47
  };
48
48
  }
49
49
 
50
- throw new Error('Could not parse GitHub repository from remote URL: ' + remoteUrl);
50
+ throw new Error(`Could not parse GitHub repository from remote URL: ${ remoteUrl}`);
51
51
 
52
52
  } catch (error) {
53
53
  if (error.message.includes('Could not parse')) {
54
54
  throw error;
55
55
  }
56
56
 
57
- throw new Error('Failed to get git remote URL: ' + error.message);
57
+ throw new Error(`Failed to get git remote URL: ${ error.message}`);
58
58
  }
59
59
  };
60
60
 
@@ -98,7 +98,7 @@ export const formatError = (errorMessage, additionalContext = []) => {
98
98
  lines.push(` .claude/ exists: ${diagnostics.claudeDirExists ? '✓' : '✗'}`);
99
99
  lines.push(` presets/ exists: ${diagnostics.presetsDirExists ? '✓' : '✗'}`);
100
100
  } else {
101
- lines.push(` Repository root: [Not in a git repository]`);
101
+ lines.push(' Repository root: [Not in a git repository]');
102
102
  }
103
103
  lines.push('');
104
104
 
@@ -105,11 +105,9 @@ const replaceTemplate = (template, variables) => {
105
105
  { variableCount: Object.keys(variables).length }
106
106
  );
107
107
 
108
- return Object.entries(variables).reduce((result, [key, value]) => {
109
- // Why: Escape special regex characters to prevent regex injection
110
- const escapedValue = String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
111
- return result.replace(new RegExp(`{{${key}}}`, 'g'), value);
112
- }, template);
108
+ return Object.entries(variables).reduce((result, [key, value]) =>
109
+ result.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value)
110
+ , template);
113
111
  };
114
112
 
115
113
  /**
@@ -246,7 +244,7 @@ const buildAnalysisPrompt = async ({
246
244
  BATCH_SIZE: subagentConfig.batchSize || 3,
247
245
  MODEL: subagentConfig.model || 'haiku'
248
246
  };
249
- prompt += '\n\n' + replaceTemplate(subagentInstruction, subagentVariables) + '\n';
247
+ prompt += `\n\n${ replaceTemplate(subagentInstruction, subagentVariables) }\n`;
250
248
 
251
249
  logger.info(`🚀 Batch optimization enabled: ${files.length} files, ${subagentVariables.BATCH_SIZE} per batch, ${subagentVariables.MODEL} model`);
252
250
  } catch (error) {
@@ -34,9 +34,11 @@ export const sanitizeInput = (input, {
34
34
  if (stripControlChars) {
35
35
  if (allowNewlines) {
36
36
  // Keep \n, \r, \t but remove other control chars
37
+ // eslint-disable-next-line no-control-regex
37
38
  sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
38
39
  } else {
39
40
  // Remove all control characters including newlines
41
+ // eslint-disable-next-line no-control-regex
40
42
  sanitized = sanitized.replace(/[\x00-\x1F\x7F]/g, ' ');
41
43
  }
42
44
  }
@@ -60,13 +62,11 @@ export const sanitizeInput = (input, {
60
62
  * @param {string} title - Raw title
61
63
  * @returns {string} - Sanitized title
62
64
  */
63
- export const sanitizePRTitle = (title) => {
64
- return sanitizeInput(title, {
65
- maxLength: 256,
66
- allowNewlines: false,
67
- stripControlChars: true
68
- });
69
- };
65
+ export const sanitizePRTitle = (title) => sanitizeInput(title, {
66
+ maxLength: 256,
67
+ allowNewlines: false,
68
+ stripControlChars: true
69
+ });
70
70
 
71
71
  /**
72
72
  * Sanitize PR body/description for GitHub API
@@ -75,13 +75,11 @@ export const sanitizePRTitle = (title) => {
75
75
  * @param {string} body - Raw body text
76
76
  * @returns {string} - Sanitized body
77
77
  */
78
- export const sanitizePRBody = (body) => {
79
- return sanitizeInput(body, {
80
- maxLength: 65536, // GitHub's limit is 65536 chars
81
- allowNewlines: true,
82
- stripControlChars: true
83
- });
84
- };
78
+ export const sanitizePRBody = (body) => sanitizeInput(body, {
79
+ maxLength: 65536, // GitHub's limit is 65536 chars
80
+ allowNewlines: true,
81
+ stripControlChars: true
82
+ });
85
83
 
86
84
  /**
87
85
  * Sanitize array of strings (labels, reviewers)
@@ -149,6 +147,7 @@ export const sanitizeLabel = (label) => {
149
147
  }
150
148
 
151
149
  // Remove problematic characters
150
+ // eslint-disable-next-line no-control-regex
152
151
  return trimmed.replace(/[,\x00-\x1F\x7F]/g, '');
153
152
  };
154
153
 
@@ -405,26 +405,24 @@ export const parseTaskIdArg = async (argTaskId, { prompt = true, required = fals
405
405
  *
406
406
  * @returns {Array<Object>} - Array of pattern info
407
407
  */
408
- export const getSupportedPatterns = () => {
409
- return [
410
- {
411
- name: 'Jira-style',
412
- examples: getExamplesForPattern('Jira-style')
413
- },
414
- {
415
- name: 'GitHub issue',
416
- examples: getExamplesForPattern('GitHub issue')
417
- },
418
- {
419
- name: 'Linear',
420
- examples: getExamplesForPattern('Linear')
421
- },
422
- {
423
- name: 'Generic',
424
- examples: getExamplesForPattern('Generic')
425
- }
426
- ];
427
- };
408
+ export const getSupportedPatterns = () => [
409
+ {
410
+ name: 'Jira-style',
411
+ examples: getExamplesForPattern('Jira-style')
412
+ },
413
+ {
414
+ name: 'GitHub issue',
415
+ examples: getExamplesForPattern('GitHub issue')
416
+ },
417
+ {
418
+ name: 'Linear',
419
+ examples: getExamplesForPattern('Linear')
420
+ },
421
+ {
422
+ name: 'Generic',
423
+ examples: getExamplesForPattern('Generic')
424
+ }
425
+ ];
428
426
 
429
427
  /**
430
428
  * Get example task IDs for a pattern
@@ -69,9 +69,7 @@ const generateEventId = () => {
69
69
  *
70
70
  * @returns {string} Telemetry directory path
71
71
  */
72
- const getTelemetryDir = () => {
73
- return path.join(process.cwd(), '.claude', 'telemetry');
74
- };
72
+ const getTelemetryDir = () => path.join(process.cwd(), '.claude', 'telemetry');
75
73
 
76
74
  /**
77
75
  * Get current telemetry log file path
@@ -90,10 +88,10 @@ const getCurrentLogFile = () => {
90
88
  *
91
89
  * @returns {boolean} True if telemetry is enabled (default: true)
92
90
  */
93
- const isTelemetryEnabled = () => {
91
+ const isTelemetryEnabled = () =>
94
92
  // Enabled by default - only disabled if explicitly set to false
95
- return config.system?.telemetry !== false;
96
- };
93
+ config.system?.telemetry !== false
94
+ ;
97
95
 
98
96
  /**
99
97
  * Ensure telemetry directory exists
@@ -117,7 +115,7 @@ const ensureTelemetryDir = async () => {
117
115
  const appendEvent = async (event) => {
118
116
  try {
119
117
  const logFile = getCurrentLogFile();
120
- const line = JSON.stringify(event) + '\n';
118
+ const line = `${JSON.stringify(event) }\n`;
121
119
 
122
120
  // Append to file (create if doesn't exist)
123
121
  await fs.appendFile(logFile, line, 'utf8');
package/package.json CHANGED
@@ -1,18 +1,26 @@
1
1
  {
2
2
  "name": "claude-git-hooks",
3
- "version": "2.14.1",
3
+ "version": "2.15.5",
4
4
  "description": "Git hooks with Claude CLI for code analysis and automatic commit messages",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "claude-hooks": "./bin/claude-hooks"
8
8
  },
9
9
  "scripts": {
10
- "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
10
+ "test": "npm run test:all",
11
+ "test:all": "npm run lint && npm run test:smoke && npm run test:unit && npm run test:integration",
12
+ "test:smoke": "node --experimental-vm-modules node_modules/jest/bin/jest.js test/smoke --maxWorkers=1",
13
+ "test:unit": "node --experimental-vm-modules node_modules/jest/bin/jest.js test/unit --forceExit",
14
+ "test:integration": "node --experimental-vm-modules node_modules/jest/bin/jest.js test/integration --maxWorkers=1 --testTimeout=30000 --forceExit",
15
+ "test:integration:ci": "node --experimental-vm-modules node_modules/jest/bin/jest.js test/integration/ci-safe.test.js --maxWorkers=1 --testTimeout=30000 --forceExit",
16
+ "test:changed": "node --experimental-vm-modules node_modules/jest/bin/jest.js test/unit --changedSince=main --forceExit",
11
17
  "test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch",
12
18
  "test:coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
13
- "lint": "eslint lib/ bin/",
14
- "lint:fix": "eslint lib/ bin/ --fix",
15
- "format": "prettier --write \"lib/**/*.js\" \"bin/**\""
19
+ "lint": "eslint lib/ bin/claude-hooks",
20
+ "lint:fix": "eslint lib/ bin/claude-hooks --fix",
21
+ "format": "prettier --write \"lib/**/*.js\" \"bin/**\" \"test/**/*.js\"",
22
+ "precommit": "npm run lint && npm run test:smoke",
23
+ "prepublishOnly": "npm run test:all"
16
24
  },
17
25
  "keywords": [
18
26
  "git",
@@ -1,113 +1,113 @@
1
- {
2
- "$schema": "Configuration v2.8.0 - Advanced parameters",
3
- "$comment": "Advanced configuration for power users and tinkerers",
4
- "$warning": "Most users don't need these settings. Use at your own risk.",
5
-
6
- "version": "2.8.0",
7
- "preset": "backend",
8
-
9
- "overrides": {
10
- "analysis": {
11
- "ignoreExtensions": [
12
- ".min.js",
13
- ".map",
14
- "package-lock.json",
15
- "yarn.lock",
16
- ".generated.ts"
17
- ]
18
- },
19
-
20
- "commitMessage": {
21
- "taskIdPattern": "(CUSTOM-[0-9]{4})"
22
- },
23
-
24
- "subagents": {
25
- "model": "sonnet",
26
- "batchSize": 2
27
- },
28
-
29
- "github": {
30
- "pr": {
31
- "defaultBase": "develop",
32
- "reviewers": ["tech-lead"]
33
- }
34
- }
35
- },
36
-
37
- "_documentation": {
38
- "analysis.ignoreExtensions": {
39
- "description": "File extensions to exclude from analysis",
40
- "default": "[]",
41
- "examples": [".min.js", ".lock", ".map"],
42
- "use_case": "Skip minified files, lock files, or generated code"
43
- },
44
-
45
- "commitMessage.taskIdPattern": {
46
- "description": "Regex pattern to extract task-id from branch name",
47
- "default": "([A-Z]{1,3}[-\\s]\\d{3,5})",
48
- "examples": [
49
- "IX-123 -> IX-123",
50
- "PROJ-4567 -> PROJ-4567",
51
- "(CUSTOM-[0-9]{4}) for CUSTOM-1234"
52
- ],
53
- "use_case": "Custom task-id format (not Jira/GitHub/Linear)"
54
- },
55
-
56
- "subagents.model": {
57
- "description": "Claude model for parallel analysis",
58
- "default": "haiku",
59
- "options": ["haiku", "sonnet", "opus"],
60
- "trade_offs": {
61
- "haiku": "Fast and cheap, good for most code",
62
- "sonnet": "Balanced speed and quality",
63
- "opus": "Slowest but most thorough analysis"
64
- },
65
- "use_case": "Trade speed/cost vs analysis quality"
66
- },
67
-
68
- "subagents.batchSize": {
69
- "description": "Number of files per parallel batch",
70
- "default": "Preset-specific (1-4)",
71
- "range": "1-10",
72
- "trade_offs": {
73
- "1": "Maximum parallelization, fastest but most expensive",
74
- "2-4": "Balanced parallelization (preset defaults)",
75
- "5+": "Less parallel, slower but cheaper"
76
- },
77
- "use_case": "Fine-tune cost vs speed for your project size"
78
- }
79
- },
80
-
81
- "_examples": {
82
- "skip_generated_code": {
83
- "version": "2.8.0",
84
- "preset": "frontend",
85
- "overrides": {
86
- "analysis": {
87
- "ignoreExtensions": [".generated.ts", ".d.ts", ".min.js"]
88
- }
89
- }
90
- },
91
-
92
- "custom_jira_format": {
93
- "version": "2.8.0",
94
- "preset": "backend",
95
- "overrides": {
96
- "commitMessage": {
97
- "taskIdPattern": "(MYPROJ-[0-9]{1,5})"
98
- }
99
- }
100
- },
101
-
102
- "high_quality_analysis": {
103
- "version": "2.8.0",
104
- "preset": "fullstack",
105
- "overrides": {
106
- "subagents": {
107
- "model": "sonnet",
108
- "batchSize": 2
109
- }
110
- }
111
- }
112
- }
113
- }
1
+ {
2
+ "$schema": "Configuration v2.8.0 - Advanced parameters",
3
+ "$comment": "Advanced configuration for power users and tinkerers",
4
+ "$warning": "Most users don't need these settings. Use at your own risk.",
5
+
6
+ "version": "2.8.0",
7
+ "preset": "backend",
8
+
9
+ "overrides": {
10
+ "analysis": {
11
+ "ignoreExtensions": [
12
+ ".min.js",
13
+ ".map",
14
+ "package-lock.json",
15
+ "yarn.lock",
16
+ ".generated.ts"
17
+ ]
18
+ },
19
+
20
+ "commitMessage": {
21
+ "taskIdPattern": "(CUSTOM-[0-9]{4})"
22
+ },
23
+
24
+ "subagents": {
25
+ "model": "sonnet",
26
+ "batchSize": 2
27
+ },
28
+
29
+ "github": {
30
+ "pr": {
31
+ "defaultBase": "develop",
32
+ "reviewers": ["tech-lead"]
33
+ }
34
+ }
35
+ },
36
+
37
+ "_documentation": {
38
+ "analysis.ignoreExtensions": {
39
+ "description": "File extensions to exclude from analysis",
40
+ "default": "[]",
41
+ "examples": [".min.js", ".lock", ".map"],
42
+ "use_case": "Skip minified files, lock files, or generated code"
43
+ },
44
+
45
+ "commitMessage.taskIdPattern": {
46
+ "description": "Regex pattern to extract task-id from branch name",
47
+ "default": "([A-Z]{1,3}[-\\s]\\d{3,5})",
48
+ "examples": [
49
+ "IX-123 -> IX-123",
50
+ "PROJ-4567 -> PROJ-4567",
51
+ "(CUSTOM-[0-9]{4}) for CUSTOM-1234"
52
+ ],
53
+ "use_case": "Custom task-id format (not Jira/GitHub/Linear)"
54
+ },
55
+
56
+ "subagents.model": {
57
+ "description": "Claude model for parallel analysis",
58
+ "default": "haiku",
59
+ "options": ["haiku", "sonnet", "opus"],
60
+ "trade_offs": {
61
+ "haiku": "Fast and cheap, good for most code",
62
+ "sonnet": "Balanced speed and quality",
63
+ "opus": "Slowest but most thorough analysis"
64
+ },
65
+ "use_case": "Trade speed/cost vs analysis quality"
66
+ },
67
+
68
+ "subagents.batchSize": {
69
+ "description": "Number of files per parallel batch",
70
+ "default": "Preset-specific (1-4)",
71
+ "range": "1-10",
72
+ "trade_offs": {
73
+ "1": "Maximum parallelization, fastest but most expensive",
74
+ "2-4": "Balanced parallelization (preset defaults)",
75
+ "5+": "Less parallel, slower but cheaper"
76
+ },
77
+ "use_case": "Fine-tune cost vs speed for your project size"
78
+ }
79
+ },
80
+
81
+ "_examples": {
82
+ "skip_generated_code": {
83
+ "version": "2.8.0",
84
+ "preset": "frontend",
85
+ "overrides": {
86
+ "analysis": {
87
+ "ignoreExtensions": [".generated.ts", ".d.ts", ".min.js"]
88
+ }
89
+ }
90
+ },
91
+
92
+ "custom_jira_format": {
93
+ "version": "2.8.0",
94
+ "preset": "backend",
95
+ "overrides": {
96
+ "commitMessage": {
97
+ "taskIdPattern": "(MYPROJ-[0-9]{1,5})"
98
+ }
99
+ }
100
+ },
101
+
102
+ "high_quality_analysis": {
103
+ "version": "2.8.0",
104
+ "preset": "fullstack",
105
+ "overrides": {
106
+ "subagents": {
107
+ "model": "sonnet",
108
+ "batchSize": 2
109
+ }
110
+ }
111
+ }
112
+ }
113
+ }
@@ -112,6 +112,13 @@ if [ -z "$NODE_HOOK" ]; then
112
112
  exit 1
113
113
  fi
114
114
 
115
+ # Run tests before Claude analysis (optional - uncomment to enable)
116
+ # echo "Running tests..."
117
+ # npm run test:unit || {
118
+ # echo -e "${RED}Tests failed! Fix tests before committing.${NC}"
119
+ # exit 1
120
+ # }
121
+
115
122
  # Execute the Node.js script
116
123
  # Why: Pass all arguments ($@) to support future hook extensions
117
124
  exec node "$NODE_HOOK" "$@"
@@ -1,5 +1,5 @@
1
- {
2
- "subagents": {
3
- "batchSize": 1
4
- }
5
- }
1
+ {
2
+ "subagents": {
3
+ "batchSize": 1
4
+ }
5
+ }
@@ -1,5 +1,5 @@
1
- {
2
- "subagents": {
3
- "batchSize": 3
4
- }
5
- }
1
+ {
2
+ "subagents": {
3
+ "batchSize": 3
4
+ }
5
+ }
@@ -1,49 +1,49 @@
1
- {
2
- "name": "backend",
3
- "displayName": "Backend (Spring Boot + SQL Server)",
4
- "description": "Java backend with Spring Boot, JPA, SQL Server, AWS",
5
- "version": "1.0.0",
6
-
7
- "techStack": [
8
- "Spring Boot 2.6+",
9
- "JPA",
10
- "Hibernate",
11
- "SQL Server",
12
- "Spring Security",
13
- "JWT",
14
- "MapStruct",
15
- "Lombok",
16
- "AWS SDK",
17
- "Maven",
18
- "Cucumber",
19
- "JUnit",
20
- "JaCoCo"
21
- ],
22
-
23
- "fileExtensions": [
24
- ".java",
25
- ".xml",
26
- ".yml",
27
- ".yaml"
28
- ],
29
-
30
- "focusAreas": [
31
- "REST API design and best practices",
32
- "JPA entities and repositories",
33
- "Service layer patterns",
34
- "Security vulnerabilities (OWASP Top 10)",
35
- "SQL injection prevention",
36
- "Performance (threads, async operations)",
37
- "Transaction management",
38
- "DTO mappings",
39
- "Test coverage"
40
- ],
41
-
42
- "templates": {
43
- "analysis": "ANALYSIS_PROMPT.md",
44
- "guidelines": "PRE_COMMIT_GUIDELINES.md",
45
- "commitMessage": "../shared/COMMIT_MESSAGE.md",
46
- "analyzeDiff": "../shared/ANALYZE_DIFF.md",
47
- "resolution": "../shared/RESOLUTION_PROMPT.md"
48
- }
49
- }
1
+ {
2
+ "name": "backend",
3
+ "displayName": "Backend (Spring Boot + SQL Server)",
4
+ "description": "Java backend with Spring Boot, JPA, SQL Server, AWS",
5
+ "version": "1.0.0",
6
+
7
+ "techStack": [
8
+ "Spring Boot 2.6+",
9
+ "JPA",
10
+ "Hibernate",
11
+ "SQL Server",
12
+ "Spring Security",
13
+ "JWT",
14
+ "MapStruct",
15
+ "Lombok",
16
+ "AWS SDK",
17
+ "Maven",
18
+ "Cucumber",
19
+ "JUnit",
20
+ "JaCoCo"
21
+ ],
22
+
23
+ "fileExtensions": [
24
+ ".java",
25
+ ".xml",
26
+ ".yml",
27
+ ".yaml"
28
+ ],
29
+
30
+ "focusAreas": [
31
+ "REST API design and best practices",
32
+ "JPA entities and repositories",
33
+ "Service layer patterns",
34
+ "Security vulnerabilities (OWASP Top 10)",
35
+ "SQL injection prevention",
36
+ "Performance (threads, async operations)",
37
+ "Transaction management",
38
+ "DTO mappings",
39
+ "Test coverage"
40
+ ],
41
+
42
+ "templates": {
43
+ "analysis": "ANALYSIS_PROMPT.md",
44
+ "guidelines": "PRE_COMMIT_GUIDELINES.md",
45
+ "commitMessage": "../shared/COMMIT_MESSAGE.md",
46
+ "analyzeDiff": "../shared/ANALYZE_DIFF.md",
47
+ "resolution": "../shared/RESOLUTION_PROMPT.md"
48
+ }
49
+ }
@@ -1,5 +1,5 @@
1
- {
2
- "subagents": {
3
- "batchSize": 2
4
- }
5
- }
1
+ {
2
+ "subagents": {
3
+ "batchSize": 2
4
+ }
5
+ }