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
|
@@ -76,8 +76,12 @@ export const detectClaudeError = (stdout = '', stderr = '', exitCode = 1) => {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
// 3. Authentication failure detection
|
|
79
|
-
if (
|
|
80
|
-
stdout.includes('
|
|
79
|
+
if (
|
|
80
|
+
stdout.includes('not authenticated') ||
|
|
81
|
+
stderr.includes('not authenticated') ||
|
|
82
|
+
stdout.includes('authentication failed') ||
|
|
83
|
+
stderr.includes('authentication failed')
|
|
84
|
+
) {
|
|
81
85
|
return {
|
|
82
86
|
type: ClaudeErrorType.AUTH_FAILED,
|
|
83
87
|
exitCode
|
|
@@ -85,8 +89,12 @@ export const detectClaudeError = (stdout = '', stderr = '', exitCode = 1) => {
|
|
|
85
89
|
}
|
|
86
90
|
|
|
87
91
|
// 4. Network errors
|
|
88
|
-
if (
|
|
89
|
-
stderr.includes('
|
|
92
|
+
if (
|
|
93
|
+
stderr.includes('ENOTFOUND') ||
|
|
94
|
+
stderr.includes('ECONNREFUSED') ||
|
|
95
|
+
stderr.includes('network error') ||
|
|
96
|
+
stderr.includes('connection refused')
|
|
97
|
+
) {
|
|
90
98
|
return {
|
|
91
99
|
type: ClaudeErrorType.NETWORK,
|
|
92
100
|
exitCode
|
|
@@ -150,7 +158,6 @@ const formatTimingInfo = (errorInfo) => {
|
|
|
150
158
|
* @returns {string} Formatted error message
|
|
151
159
|
*/
|
|
152
160
|
export const formatClaudeError = (errorInfo) => {
|
|
153
|
-
|
|
154
161
|
switch (errorInfo.type) {
|
|
155
162
|
case ClaudeErrorType.EXECUTION_ERROR:
|
|
156
163
|
return formatExecutionError(errorInfo);
|
|
@@ -219,7 +226,9 @@ const formatRateLimitError = (errorInfo) => {
|
|
|
219
226
|
const hours = Math.ceil(minutesUntilReset / 60);
|
|
220
227
|
lines.push(` Time until reset: ~${hours} hour${hours > 1 ? 's' : ''}`);
|
|
221
228
|
} else if (minutesUntilReset > 0) {
|
|
222
|
-
lines.push(
|
|
229
|
+
lines.push(
|
|
230
|
+
` Time until reset: ~${minutesUntilReset} minute${minutesUntilReset !== 1 ? 's' : ''}`
|
|
231
|
+
);
|
|
223
232
|
} else {
|
|
224
233
|
lines.push(' Limit should be reset now');
|
|
225
234
|
}
|
|
@@ -345,7 +354,8 @@ const formatGenericError = (errorInfo) => {
|
|
|
345
354
|
* @param {Object} errorInfo - Output from detectClaudeError()
|
|
346
355
|
* @returns {boolean} True if error might resolve with retry
|
|
347
356
|
*/
|
|
348
|
-
export const isRecoverableError = (errorInfo) =>
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
357
|
+
export const isRecoverableError = (errorInfo) =>
|
|
358
|
+
errorInfo.type === ClaudeErrorType.EXECUTION_ERROR ||
|
|
359
|
+
errorInfo.type === ClaudeErrorType.RATE_LIMIT ||
|
|
360
|
+
errorInfo.type === ClaudeErrorType.NETWORK ||
|
|
361
|
+
errorInfo.type === ClaudeErrorType.TIMEOUT;
|
|
@@ -78,11 +78,7 @@ const fileExists = async (filePath) => {
|
|
|
78
78
|
* @throws {FileOperationError} If file too large or read fails
|
|
79
79
|
*/
|
|
80
80
|
const readFile = async (filePath, { maxSize = 100000, encoding = 'utf8' } = {}) => {
|
|
81
|
-
logger.debug(
|
|
82
|
-
'file-operations - readFile',
|
|
83
|
-
'Reading file',
|
|
84
|
-
{ filePath, maxSize, encoding }
|
|
85
|
-
);
|
|
81
|
+
logger.debug('file-operations - readFile', 'Reading file', { filePath, maxSize, encoding });
|
|
86
82
|
|
|
87
83
|
try {
|
|
88
84
|
const size = await getFileSize(filePath);
|
|
@@ -97,14 +93,12 @@ const readFile = async (filePath, { maxSize = 100000, encoding = 'utf8' } = {})
|
|
|
97
93
|
|
|
98
94
|
const content = await fs.readFile(filePath, encoding);
|
|
99
95
|
|
|
100
|
-
logger.debug(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
);
|
|
96
|
+
logger.debug('file-operations - readFile', 'File read successfully', {
|
|
97
|
+
filePath,
|
|
98
|
+
contentLength: content.length
|
|
99
|
+
});
|
|
105
100
|
|
|
106
101
|
return content;
|
|
107
|
-
|
|
108
102
|
} catch (error) {
|
|
109
103
|
if (error instanceof FileOperationError) {
|
|
110
104
|
throw error;
|
|
@@ -132,15 +126,14 @@ const hasAllowedExtension = (filePath, allowedExtensions = []) => {
|
|
|
132
126
|
}
|
|
133
127
|
|
|
134
128
|
const ext = path.extname(filePath).toLowerCase();
|
|
135
|
-
const isAllowed = allowedExtensions.some(allowed =>
|
|
136
|
-
ext === allowed.toLowerCase()
|
|
137
|
-
);
|
|
129
|
+
const isAllowed = allowedExtensions.some((allowed) => ext === allowed.toLowerCase());
|
|
138
130
|
|
|
139
|
-
logger.debug(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
131
|
+
logger.debug('file-operations - hasAllowedExtension', 'Extension check', {
|
|
132
|
+
filePath,
|
|
133
|
+
ext,
|
|
134
|
+
allowedExtensions,
|
|
135
|
+
isAllowed
|
|
136
|
+
});
|
|
144
137
|
|
|
145
138
|
return isAllowed;
|
|
146
139
|
};
|
|
@@ -166,27 +159,19 @@ const hasAllowedExtension = (filePath, allowedExtensions = []) => {
|
|
|
166
159
|
* ]
|
|
167
160
|
*/
|
|
168
161
|
const filterFiles = async (files, { maxSize = 100000, extensions = [] } = {}) => {
|
|
169
|
-
logger.debug(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
'process.cwd()': process.cwd(),
|
|
177
|
-
files
|
|
178
|
-
}
|
|
179
|
-
);
|
|
162
|
+
logger.debug('file-operations - filterFiles', 'Filtering files', {
|
|
163
|
+
fileCount: files.length,
|
|
164
|
+
maxSize,
|
|
165
|
+
extensions,
|
|
166
|
+
'process.cwd()': process.cwd(),
|
|
167
|
+
files
|
|
168
|
+
});
|
|
180
169
|
|
|
181
170
|
const results = await Promise.allSettled(
|
|
182
171
|
files.map(async (filePath) => {
|
|
183
172
|
// Check extension first (fast)
|
|
184
173
|
if (!hasAllowedExtension(filePath, extensions)) {
|
|
185
|
-
logger.debug(
|
|
186
|
-
'file-operations - filterFiles',
|
|
187
|
-
'Extension rejected',
|
|
188
|
-
{ filePath }
|
|
189
|
-
);
|
|
174
|
+
logger.debug('file-operations - filterFiles', 'Extension rejected', { filePath });
|
|
190
175
|
return {
|
|
191
176
|
path: filePath,
|
|
192
177
|
size: 0,
|
|
@@ -197,11 +182,10 @@ const filterFiles = async (files, { maxSize = 100000, extensions = [] } = {}) =>
|
|
|
197
182
|
|
|
198
183
|
// Check if file exists
|
|
199
184
|
const exists = await fileExists(filePath);
|
|
200
|
-
logger.debug(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
);
|
|
185
|
+
logger.debug('file-operations - filterFiles', 'File exists check', {
|
|
186
|
+
filePath,
|
|
187
|
+
exists
|
|
188
|
+
});
|
|
205
189
|
if (!exists) {
|
|
206
190
|
return {
|
|
207
191
|
path: filePath,
|
|
@@ -216,11 +200,13 @@ const filterFiles = async (files, { maxSize = 100000, extensions = [] } = {}) =>
|
|
|
216
200
|
const size = await getFileSize(filePath);
|
|
217
201
|
|
|
218
202
|
if (size > maxSize) {
|
|
219
|
-
logger.debug(
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
203
|
+
logger.debug('file-operations - filterFiles', 'File too large', {
|
|
204
|
+
filePath,
|
|
205
|
+
size,
|
|
206
|
+
maxSize,
|
|
207
|
+
'size (KB)': Math.round(size / 1024),
|
|
208
|
+
'maxSize (KB)': Math.round(maxSize / 1024)
|
|
209
|
+
});
|
|
224
210
|
return {
|
|
225
211
|
path: filePath,
|
|
226
212
|
size,
|
|
@@ -229,11 +215,12 @@ const filterFiles = async (files, { maxSize = 100000, extensions = [] } = {}) =>
|
|
|
229
215
|
};
|
|
230
216
|
}
|
|
231
217
|
|
|
232
|
-
logger.debug(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
218
|
+
logger.debug('file-operations - filterFiles', 'File passed size check', {
|
|
219
|
+
filePath,
|
|
220
|
+
size,
|
|
221
|
+
maxSize,
|
|
222
|
+
'size (KB)': Math.round(size / 1024)
|
|
223
|
+
});
|
|
237
224
|
|
|
238
225
|
return {
|
|
239
226
|
path: filePath,
|
|
@@ -241,13 +228,11 @@ const filterFiles = async (files, { maxSize = 100000, extensions = [] } = {}) =>
|
|
|
241
228
|
valid: true,
|
|
242
229
|
reason: null
|
|
243
230
|
};
|
|
244
|
-
|
|
245
231
|
} catch (error) {
|
|
246
|
-
logger.debug(
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
);
|
|
232
|
+
logger.debug('file-operations - filterFiles', 'Error reading file', {
|
|
233
|
+
filePath,
|
|
234
|
+
error: error.message
|
|
235
|
+
});
|
|
251
236
|
return {
|
|
252
237
|
path: filePath,
|
|
253
238
|
size: 0,
|
|
@@ -259,32 +244,19 @@ const filterFiles = async (files, { maxSize = 100000, extensions = [] } = {}) =>
|
|
|
259
244
|
);
|
|
260
245
|
|
|
261
246
|
// Extract successful results
|
|
262
|
-
const fileMetadata = results
|
|
263
|
-
.filter(r => r.status === 'fulfilled')
|
|
264
|
-
.map(r => r.value);
|
|
247
|
+
const fileMetadata = results.filter((r) => r.status === 'fulfilled').map((r) => r.value);
|
|
265
248
|
|
|
266
|
-
const validFiles = fileMetadata.filter(f => f.valid);
|
|
267
|
-
const invalidFiles = fileMetadata.filter(f => !f.valid);
|
|
249
|
+
const validFiles = fileMetadata.filter((f) => f.valid);
|
|
250
|
+
const invalidFiles = fileMetadata.filter((f) => !f.valid);
|
|
268
251
|
|
|
269
|
-
logger.debug(
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
invalidFiles: invalidFiles.length,
|
|
276
|
-
rejectedFiles: invalidFiles.map(f => ({ path: f.path, reason: f.reason }))
|
|
277
|
-
}
|
|
278
|
-
);
|
|
252
|
+
logger.debug('file-operations - filterFiles', 'Filtering complete', {
|
|
253
|
+
totalFiles: files.length,
|
|
254
|
+
validFiles: validFiles.length,
|
|
255
|
+
invalidFiles: invalidFiles.length,
|
|
256
|
+
rejectedFiles: invalidFiles.map((f) => ({ path: f.path, reason: f.reason }))
|
|
257
|
+
});
|
|
279
258
|
|
|
280
259
|
return fileMetadata;
|
|
281
260
|
};
|
|
282
261
|
|
|
283
|
-
export {
|
|
284
|
-
FileOperationError,
|
|
285
|
-
getFileSize,
|
|
286
|
-
fileExists,
|
|
287
|
-
readFile,
|
|
288
|
-
hasAllowedExtension,
|
|
289
|
-
filterFiles
|
|
290
|
-
};
|
|
262
|
+
export { FileOperationError, getFileSize, fileExists, readFile, hasAllowedExtension, filterFiles };
|
package/lib/utils/file-utils.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* Purpose: Utility functions for file system operations
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
import fsPromises from 'fs/promises';
|
|
7
|
+
import fs from 'fs';
|
|
7
8
|
import path from 'path';
|
|
8
9
|
import { getRepoRoot } from './git-operations.js';
|
|
9
10
|
import logger from './logger.js';
|
|
@@ -15,12 +16,10 @@ import logger from './logger.js';
|
|
|
15
16
|
* @returns {Promise<void>}
|
|
16
17
|
*/
|
|
17
18
|
export const ensureDir = async (dirPath) => {
|
|
18
|
-
const absolutePath = path.isAbsolute(dirPath)
|
|
19
|
-
? dirPath
|
|
20
|
-
: path.join(getRepoRoot(), dirPath);
|
|
19
|
+
const absolutePath = path.isAbsolute(dirPath) ? dirPath : path.join(getRepoRoot(), dirPath);
|
|
21
20
|
|
|
22
21
|
try {
|
|
23
|
-
await
|
|
22
|
+
await fsPromises.mkdir(absolutePath, { recursive: true });
|
|
24
23
|
logger.debug('file-utils - ensureDir', 'Directory ensured', { path: absolutePath });
|
|
25
24
|
} catch (error) {
|
|
26
25
|
logger.error('file-utils - ensureDir', 'Failed to create directory', error);
|
|
@@ -51,14 +50,52 @@ export const ensureOutputDir = async (config) => {
|
|
|
51
50
|
export const writeOutputFile = async (filePath, content, config) => {
|
|
52
51
|
await ensureOutputDir(config);
|
|
53
52
|
|
|
54
|
-
const absolutePath = path.isAbsolute(filePath)
|
|
55
|
-
? filePath
|
|
56
|
-
: path.join(getRepoRoot(), filePath);
|
|
53
|
+
const absolutePath = path.isAbsolute(filePath) ? filePath : path.join(getRepoRoot(), filePath);
|
|
57
54
|
|
|
58
|
-
await
|
|
55
|
+
await fsPromises.writeFile(absolutePath, content, 'utf8');
|
|
59
56
|
|
|
60
57
|
logger.debug('file-utils - writeOutputFile', 'File written', {
|
|
61
58
|
path: absolutePath,
|
|
62
59
|
size: content.length
|
|
63
60
|
});
|
|
64
61
|
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Walks a directory tree recursively, invoking callbacks for each file
|
|
65
|
+
* Why: Shared traversal logic for discoverVersionFiles and discoverChangelogFiles
|
|
66
|
+
*
|
|
67
|
+
* @param {string} rootDir - Starting directory path
|
|
68
|
+
* @param {Object} options
|
|
69
|
+
* @param {number} options.maxDepth - Maximum recursion depth (default: 3)
|
|
70
|
+
* @param {Set<string>} options.ignoreSet - Directory names to skip
|
|
71
|
+
* @param {Function} options.onFile - Called as onFile(entry, fullPath) for each file entry
|
|
72
|
+
* @param {Function} options.onError - Called as onError(dir, error) on readdirSync failures
|
|
73
|
+
*/
|
|
74
|
+
export function walkDirectoryTree(
|
|
75
|
+
rootDir,
|
|
76
|
+
{ maxDepth = 3, ignoreSet = new Set(), onFile, onError } = {}
|
|
77
|
+
) {
|
|
78
|
+
function walk(dir, depth) {
|
|
79
|
+
if (depth > maxDepth) return;
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
83
|
+
|
|
84
|
+
for (const entry of entries) {
|
|
85
|
+
if (entry.name.startsWith('.') || ignoreSet.has(entry.name)) continue;
|
|
86
|
+
|
|
87
|
+
const fullPath = path.join(dir, entry.name);
|
|
88
|
+
|
|
89
|
+
if (entry.isDirectory()) {
|
|
90
|
+
walk(fullPath, depth + 1);
|
|
91
|
+
} else if (entry.isFile()) {
|
|
92
|
+
onFile?.(entry, fullPath);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
onError?.(dir, error);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
walk(rootDir, 0);
|
|
101
|
+
}
|