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
|
@@ -46,11 +46,9 @@ class ResolutionPromptError extends Error {
|
|
|
46
46
|
* Detailed: { message, file, line, method, severity, type, rule }
|
|
47
47
|
*/
|
|
48
48
|
const formatBlockingIssues = (issues) => {
|
|
49
|
-
logger.debug(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
{ issueCount: issues?.length || 0 }
|
|
53
|
-
);
|
|
49
|
+
logger.debug('resolution-prompt - formatBlockingIssues', 'Formatting issues', {
|
|
50
|
+
issueCount: issues?.length || 0
|
|
51
|
+
});
|
|
54
52
|
|
|
55
53
|
if (!Array.isArray(issues) || issues.length === 0) {
|
|
56
54
|
return 'No issues found.';
|
|
@@ -94,16 +92,15 @@ const getAffectedFiles = (issues) => {
|
|
|
94
92
|
|
|
95
93
|
// Extract unique file paths
|
|
96
94
|
const filePaths = issues
|
|
97
|
-
.map(issue => issue.file)
|
|
98
|
-
.filter(file => file && typeof file === 'string');
|
|
95
|
+
.map((issue) => issue.file)
|
|
96
|
+
.filter((file) => file && typeof file === 'string');
|
|
99
97
|
|
|
100
98
|
const uniqueFiles = [...new Set(filePaths)];
|
|
101
99
|
|
|
102
|
-
logger.debug(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
);
|
|
100
|
+
logger.debug('resolution-prompt - getAffectedFiles', 'Extracted affected files', {
|
|
101
|
+
totalIssues: issues.length,
|
|
102
|
+
uniqueFiles: uniqueFiles.length
|
|
103
|
+
});
|
|
107
104
|
|
|
108
105
|
return uniqueFiles;
|
|
109
106
|
};
|
|
@@ -117,11 +114,9 @@ const getAffectedFiles = (issues) => {
|
|
|
117
114
|
* @returns {Promise<string>} Formatted file contents in markdown
|
|
118
115
|
*/
|
|
119
116
|
const formatFileContents = async (filePaths) => {
|
|
120
|
-
logger.debug(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
{ fileCount: filePaths.length }
|
|
124
|
-
);
|
|
117
|
+
logger.debug('resolution-prompt - formatFileContents', 'Reading file contents', {
|
|
118
|
+
fileCount: filePaths.length
|
|
119
|
+
});
|
|
125
120
|
|
|
126
121
|
// Why: Use repo root for absolute paths (works on Windows/PowerShell/Git Bash)
|
|
127
122
|
const repoRoot = getRepoRoot();
|
|
@@ -154,15 +149,13 @@ ${content}
|
|
|
154
149
|
);
|
|
155
150
|
|
|
156
151
|
const formatted = fileContents
|
|
157
|
-
.filter(result => result.status === 'fulfilled')
|
|
158
|
-
.map(result => result.value)
|
|
152
|
+
.filter((result) => result.status === 'fulfilled')
|
|
153
|
+
.map((result) => result.value)
|
|
159
154
|
.join('\n');
|
|
160
155
|
|
|
161
|
-
logger.debug(
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
{ successfulReads: fileContents.filter(r => r.status === 'fulfilled').length }
|
|
165
|
-
);
|
|
156
|
+
logger.debug('resolution-prompt - formatFileContents', 'File contents formatted', {
|
|
157
|
+
successfulReads: fileContents.filter((r) => r.status === 'fulfilled').length
|
|
158
|
+
});
|
|
166
159
|
|
|
167
160
|
return formatted;
|
|
168
161
|
};
|
|
@@ -202,21 +195,18 @@ const generateResolutionPrompt = async (
|
|
|
202
195
|
|
|
203
196
|
// Use details array (all issues) if available, fallback to blockingIssues
|
|
204
197
|
// Why: analyze command provides full details, pre-commit may only have blockingIssues
|
|
205
|
-
const allIssues =
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
usingDetails: Array.isArray(analysisResult.details) && analysisResult.details.length > 0
|
|
218
|
-
}
|
|
219
|
-
);
|
|
198
|
+
const allIssues =
|
|
199
|
+
Array.isArray(analysisResult.details) && analysisResult.details.length > 0
|
|
200
|
+
? analysisResult.details
|
|
201
|
+
: analysisResult.blockingIssues || [];
|
|
202
|
+
|
|
203
|
+
logger.debug('resolution-prompt - generateResolutionPrompt', 'Generating resolution prompt', {
|
|
204
|
+
repoRoot,
|
|
205
|
+
outputPath: absoluteOutputPath,
|
|
206
|
+
templatePath: absoluteTemplatePath,
|
|
207
|
+
issueCount: allIssues.length,
|
|
208
|
+
usingDetails: Array.isArray(analysisResult.details) && analysisResult.details.length > 0
|
|
209
|
+
});
|
|
220
210
|
|
|
221
211
|
try {
|
|
222
212
|
// Load template
|
|
@@ -265,7 +255,6 @@ const generateResolutionPrompt = async (
|
|
|
265
255
|
);
|
|
266
256
|
|
|
267
257
|
return absoluteOutputPath;
|
|
268
|
-
|
|
269
258
|
} catch (error) {
|
|
270
259
|
logger.error(
|
|
271
260
|
'resolution-prompt - generateResolutionPrompt',
|
|
@@ -295,10 +284,10 @@ const generateResolutionPrompt = async (
|
|
|
295
284
|
* @returns {boolean} True if should generate prompt
|
|
296
285
|
*/
|
|
297
286
|
const shouldGeneratePrompt = (analysisResult) => {
|
|
298
|
-
const hasBlockingIssues =
|
|
299
|
-
|
|
300
|
-
const hasDetailedIssues =
|
|
301
|
-
|
|
287
|
+
const hasBlockingIssues =
|
|
288
|
+
Array.isArray(analysisResult.blockingIssues) && analysisResult.blockingIssues.length > 0;
|
|
289
|
+
const hasDetailedIssues =
|
|
290
|
+
Array.isArray(analysisResult.details) && analysisResult.details.length > 0;
|
|
302
291
|
|
|
303
292
|
logger.debug(
|
|
304
293
|
'resolution-prompt - shouldGeneratePrompt',
|
package/lib/utils/sanitize.js
CHANGED
|
@@ -19,11 +19,10 @@ import logger from './logger.js';
|
|
|
19
19
|
* @param {boolean} options.stripControlChars - Remove control characters (default: true)
|
|
20
20
|
* @returns {string} - Sanitized string
|
|
21
21
|
*/
|
|
22
|
-
export const sanitizeInput = (
|
|
23
|
-
|
|
24
|
-
allowNewlines = true,
|
|
25
|
-
|
|
26
|
-
} = {}) => {
|
|
22
|
+
export const sanitizeInput = (
|
|
23
|
+
input,
|
|
24
|
+
{ maxLength = 65536, allowNewlines = true, stripControlChars = true } = {}
|
|
25
|
+
) => {
|
|
27
26
|
if (typeof input !== 'string') {
|
|
28
27
|
return '';
|
|
29
28
|
}
|
|
@@ -62,11 +61,12 @@ export const sanitizeInput = (input, {
|
|
|
62
61
|
* @param {string} title - Raw title
|
|
63
62
|
* @returns {string} - Sanitized title
|
|
64
63
|
*/
|
|
65
|
-
export const sanitizePRTitle = (title) =>
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
export const sanitizePRTitle = (title) =>
|
|
65
|
+
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,11 +75,12 @@ export const sanitizePRTitle = (title) => sanitizeInput(title, {
|
|
|
75
75
|
* @param {string} body - Raw body text
|
|
76
76
|
* @returns {string} - Sanitized body
|
|
77
77
|
*/
|
|
78
|
-
export const sanitizePRBody = (body) =>
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
export const sanitizePRBody = (body) =>
|
|
79
|
+
sanitizeInput(body, {
|
|
80
|
+
maxLength: 65536, // GitHub's limit is 65536 chars
|
|
81
|
+
allowNewlines: true,
|
|
82
|
+
stripControlChars: true
|
|
83
|
+
});
|
|
83
84
|
|
|
84
85
|
/**
|
|
85
86
|
* Sanitize array of strings (labels, reviewers)
|
|
@@ -95,10 +96,10 @@ export const sanitizeStringArray = (items, validPattern = /^[\w.-]+$/) => {
|
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
return items
|
|
98
|
-
.filter(item => typeof item === 'string')
|
|
99
|
-
.map(item => item.trim())
|
|
100
|
-
.filter(item => item.length > 0 && item.length <= 100)
|
|
101
|
-
.filter(item => validPattern.test(item));
|
|
99
|
+
.filter((item) => typeof item === 'string')
|
|
100
|
+
.map((item) => item.trim())
|
|
101
|
+
.filter((item) => item.length > 0 && item.length <= 100)
|
|
102
|
+
.filter((item) => validPattern.test(item));
|
|
102
103
|
};
|
|
103
104
|
|
|
104
105
|
/**
|
package/lib/utils/task-id.js
CHANGED
|
@@ -86,11 +86,7 @@ export const extractTaskId = (branchName, config = null) => {
|
|
|
86
86
|
// Get first capture group
|
|
87
87
|
const taskId = match[1];
|
|
88
88
|
if (taskId) {
|
|
89
|
-
logger.debug(
|
|
90
|
-
'task-id - extractTaskId',
|
|
91
|
-
'Task ID found',
|
|
92
|
-
{ branchName, taskId }
|
|
93
|
-
);
|
|
89
|
+
logger.debug('task-id - extractTaskId', 'Task ID found', { branchName, taskId });
|
|
94
90
|
return taskId.toUpperCase(); // Normalize to uppercase
|
|
95
91
|
}
|
|
96
92
|
}
|
|
@@ -114,11 +110,9 @@ export const extractTaskIdFromCurrentBranch = (config = null) => {
|
|
|
114
110
|
|
|
115
111
|
return extractTaskId(branchName, config);
|
|
116
112
|
} catch (error) {
|
|
117
|
-
logger.debug(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
{ error: error.message }
|
|
121
|
-
);
|
|
113
|
+
logger.debug('task-id - extractTaskIdFromCurrentBranch', 'Failed to get current branch', {
|
|
114
|
+
error: error.message
|
|
115
|
+
});
|
|
122
116
|
return null;
|
|
123
117
|
}
|
|
124
118
|
};
|
|
@@ -173,11 +167,10 @@ export const formatWithTaskId = (message, taskId) => {
|
|
|
173
167
|
// Check if message already has task ID prefix (idempotent)
|
|
174
168
|
const taskIdPrefix = `[${normalizedTaskId}]`;
|
|
175
169
|
if (message.startsWith(taskIdPrefix)) {
|
|
176
|
-
logger.debug(
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
);
|
|
170
|
+
logger.debug('task-id - formatWithTaskId', 'Message already has task ID prefix', {
|
|
171
|
+
message,
|
|
172
|
+
taskId: normalizedTaskId
|
|
173
|
+
});
|
|
181
174
|
return message;
|
|
182
175
|
}
|
|
183
176
|
|
|
@@ -186,25 +179,21 @@ export const formatWithTaskId = (message, taskId) => {
|
|
|
186
179
|
if (existingTaskIdMatch) {
|
|
187
180
|
// Replace existing task ID with new one
|
|
188
181
|
const formattedMessage = message.replace(existingTaskIdMatch[0], `${taskIdPrefix} `);
|
|
189
|
-
logger.debug(
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
newTaskId: normalizedTaskId,
|
|
195
|
-
formattedMessage
|
|
196
|
-
}
|
|
197
|
-
);
|
|
182
|
+
logger.debug('task-id - formatWithTaskId', 'Replaced existing task ID', {
|
|
183
|
+
oldTaskId: existingTaskIdMatch[1],
|
|
184
|
+
newTaskId: normalizedTaskId,
|
|
185
|
+
formattedMessage
|
|
186
|
+
});
|
|
198
187
|
return formattedMessage;
|
|
199
188
|
}
|
|
200
189
|
|
|
201
190
|
// Prepend task ID
|
|
202
191
|
const formattedMessage = `${taskIdPrefix} ${message}`;
|
|
203
|
-
logger.debug(
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
);
|
|
192
|
+
logger.debug('task-id - formatWithTaskId', 'Added task ID prefix', {
|
|
193
|
+
message,
|
|
194
|
+
taskId: normalizedTaskId,
|
|
195
|
+
formattedMessage
|
|
196
|
+
});
|
|
208
197
|
|
|
209
198
|
return formattedMessage;
|
|
210
199
|
};
|
|
@@ -246,7 +235,9 @@ export const promptForTaskId = async ({
|
|
|
246
235
|
config = null
|
|
247
236
|
} = {}) => {
|
|
248
237
|
// Try to use /dev/tty for Unix (git hooks), fallback to stdin for Windows
|
|
249
|
-
let input,
|
|
238
|
+
let input,
|
|
239
|
+
output,
|
|
240
|
+
usingTty = false;
|
|
250
241
|
|
|
251
242
|
// Check platform
|
|
252
243
|
if (process.platform !== 'win32') {
|
|
@@ -281,9 +272,7 @@ export const promptForTaskId = async ({
|
|
|
281
272
|
|
|
282
273
|
return new Promise((resolve) => {
|
|
283
274
|
const askQuestion = () => {
|
|
284
|
-
const promptMessage = allowSkip
|
|
285
|
-
? `${message} (press Enter to skip): `
|
|
286
|
-
: `${message} `;
|
|
275
|
+
const promptMessage = allowSkip ? `${message} (press Enter to skip): ` : `${message} `;
|
|
287
276
|
|
|
288
277
|
rl.question(promptMessage, (answer) => {
|
|
289
278
|
const trimmedAnswer = answer.trim();
|
|
@@ -317,11 +306,9 @@ export const promptForTaskId = async ({
|
|
|
317
306
|
|
|
318
307
|
// Valid task ID provided
|
|
319
308
|
const normalizedTaskId = trimmedAnswer.toUpperCase();
|
|
320
|
-
logger.debug(
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
{ taskId: normalizedTaskId }
|
|
324
|
-
);
|
|
309
|
+
logger.debug('task-id - promptForTaskId', 'User provided task ID', {
|
|
310
|
+
taskId: normalizedTaskId
|
|
311
|
+
});
|
|
325
312
|
rl.close();
|
|
326
313
|
if (usingTty) {
|
|
327
314
|
input.close();
|
|
@@ -432,8 +419,8 @@ const getExamplesForPattern = (patternName) => {
|
|
|
432
419
|
const examples = {
|
|
433
420
|
'Jira-style': ['IX-123', 'PROJ-456', 'ABC-789'],
|
|
434
421
|
'GitHub issue': ['#123', 'GH-456'],
|
|
435
|
-
|
|
436
|
-
|
|
422
|
+
Linear: ['LIN-123', 'LIN-456'],
|
|
423
|
+
Generic: ['TASK-123', 'BUG-456', 'FEAT-789']
|
|
437
424
|
};
|
|
438
425
|
return examples[patternName] || [];
|
|
439
426
|
};
|
package/lib/utils/telemetry.js
CHANGED
|
@@ -88,11 +88,9 @@ const getCurrentLogFile = () => {
|
|
|
88
88
|
*
|
|
89
89
|
* @returns {boolean} True if telemetry is enabled (default: true)
|
|
90
90
|
*/
|
|
91
|
-
const isTelemetryEnabled = () =>
|
|
91
|
+
const isTelemetryEnabled = () =>
|
|
92
92
|
// Enabled by default - only disabled if explicitly set to false
|
|
93
|
-
config.system?.telemetry !== false
|
|
94
|
-
;
|
|
95
|
-
|
|
93
|
+
config.system?.telemetry !== false;
|
|
96
94
|
/**
|
|
97
95
|
* Ensure telemetry directory exists
|
|
98
96
|
* Why: Create on first use
|
|
@@ -115,7 +113,7 @@ const ensureTelemetryDir = async () => {
|
|
|
115
113
|
const appendEvent = async (event) => {
|
|
116
114
|
try {
|
|
117
115
|
const logFile = getCurrentLogFile();
|
|
118
|
-
const line = `${JSON.stringify(event)
|
|
116
|
+
const line = `${JSON.stringify(event)}\n`;
|
|
119
117
|
|
|
120
118
|
// Append to file (create if doesn't exist)
|
|
121
119
|
await fs.appendFile(logFile, line, 'utf8');
|
|
@@ -240,7 +238,7 @@ const readTelemetryEvents = async (maxDays = 7) => {
|
|
|
240
238
|
const files = await fs.readdir(dir);
|
|
241
239
|
|
|
242
240
|
// Filter to .jsonl files only
|
|
243
|
-
const logFiles = files.filter(f => f.endsWith('.jsonl'));
|
|
241
|
+
const logFiles = files.filter((f) => f.endsWith('.jsonl'));
|
|
244
242
|
|
|
245
243
|
// Sort by date (newest first)
|
|
246
244
|
logFiles.sort().reverse();
|
|
@@ -262,7 +260,11 @@ const readTelemetryEvents = async (maxDays = 7) => {
|
|
|
262
260
|
events.push(JSON.parse(line));
|
|
263
261
|
} catch (parseError) {
|
|
264
262
|
// Skip invalid lines
|
|
265
|
-
logger.debug(
|
|
263
|
+
logger.debug(
|
|
264
|
+
'telemetry - readTelemetryEvents',
|
|
265
|
+
'Invalid JSON line',
|
|
266
|
+
parseError
|
|
267
|
+
);
|
|
266
268
|
}
|
|
267
269
|
}
|
|
268
270
|
}
|
|
@@ -286,7 +288,8 @@ export const getStatistics = async (maxDays = 7) => {
|
|
|
286
288
|
if (!isTelemetryEnabled()) {
|
|
287
289
|
return {
|
|
288
290
|
enabled: false,
|
|
289
|
-
message:
|
|
291
|
+
message:
|
|
292
|
+
'Telemetry is disabled. To enable (default), remove or set "system.telemetry: true" in .claude/config.json'
|
|
290
293
|
};
|
|
291
294
|
}
|
|
292
295
|
|
|
@@ -311,14 +314,15 @@ export const getStatistics = async (maxDays = 7) => {
|
|
|
311
314
|
let totalFilesInFailures = 0;
|
|
312
315
|
let totalFilesInSuccesses = 0;
|
|
313
316
|
|
|
314
|
-
events.forEach(event => {
|
|
317
|
+
events.forEach((event) => {
|
|
315
318
|
if (event.type === 'json_parse_failure') {
|
|
316
319
|
stats.jsonParseFailures++;
|
|
317
320
|
totalFilesInFailures += event.data.fileCount || 0;
|
|
318
321
|
|
|
319
322
|
// Group by batch size
|
|
320
323
|
const batchSize = event.data.batchSize || 0;
|
|
321
|
-
stats.failuresByBatchSize[batchSize] =
|
|
324
|
+
stats.failuresByBatchSize[batchSize] =
|
|
325
|
+
(stats.failuresByBatchSize[batchSize] || 0) + 1;
|
|
322
326
|
|
|
323
327
|
// Group by model
|
|
324
328
|
const model = event.data.model || 'unknown';
|
|
@@ -327,7 +331,6 @@ export const getStatistics = async (maxDays = 7) => {
|
|
|
327
331
|
// Group by hook
|
|
328
332
|
const hook = event.data.hook || 'unknown';
|
|
329
333
|
stats.failuresByHook[hook] = (stats.failuresByHook[hook] || 0) + 1;
|
|
330
|
-
|
|
331
334
|
} else if (event.type === 'batch_success') {
|
|
332
335
|
stats.batchSuccesses++;
|
|
333
336
|
totalFilesInSuccesses += event.data.fileCount || 0;
|
|
@@ -340,16 +343,22 @@ export const getStatistics = async (maxDays = 7) => {
|
|
|
340
343
|
|
|
341
344
|
// Calculate averages
|
|
342
345
|
if (stats.jsonParseFailures > 0) {
|
|
343
|
-
stats.avgFilesPerFailure = parseFloat(
|
|
346
|
+
stats.avgFilesPerFailure = parseFloat(
|
|
347
|
+
(totalFilesInFailures / stats.jsonParseFailures).toFixed(2)
|
|
348
|
+
);
|
|
344
349
|
}
|
|
345
350
|
if (stats.batchSuccesses > 0) {
|
|
346
|
-
stats.avgFilesPerSuccess = parseFloat(
|
|
351
|
+
stats.avgFilesPerSuccess = parseFloat(
|
|
352
|
+
(totalFilesInSuccesses / stats.batchSuccesses).toFixed(2)
|
|
353
|
+
);
|
|
347
354
|
}
|
|
348
355
|
|
|
349
356
|
// Calculate failure rate
|
|
350
357
|
const totalAnalyses = stats.jsonParseFailures + stats.batchSuccesses;
|
|
351
358
|
if (totalAnalyses > 0) {
|
|
352
|
-
stats.failureRate = parseFloat(
|
|
359
|
+
stats.failureRate = parseFloat(
|
|
360
|
+
((stats.jsonParseFailures / totalAnalyses) * 100).toFixed(2)
|
|
361
|
+
);
|
|
353
362
|
}
|
|
354
363
|
|
|
355
364
|
return stats;
|
|
@@ -379,7 +388,7 @@ export const rotateTelemetry = async (maxDays = 30) => {
|
|
|
379
388
|
const files = await fs.readdir(dir);
|
|
380
389
|
|
|
381
390
|
// Filter to .jsonl files
|
|
382
|
-
const logFiles = files.filter(f => f.endsWith('.jsonl'));
|
|
391
|
+
const logFiles = files.filter((f) => f.endsWith('.jsonl'));
|
|
383
392
|
|
|
384
393
|
// Calculate cutoff date
|
|
385
394
|
const cutoffDate = new Date();
|
|
@@ -395,7 +404,10 @@ export const rotateTelemetry = async (maxDays = 30) => {
|
|
|
395
404
|
if (fileDate < cutoffStr) {
|
|
396
405
|
const filePath = path.join(dir, file);
|
|
397
406
|
await fs.unlink(filePath);
|
|
398
|
-
logger.debug(
|
|
407
|
+
logger.debug(
|
|
408
|
+
'telemetry - rotateTelemetry',
|
|
409
|
+
`Deleted old telemetry file: ${file}`
|
|
410
|
+
);
|
|
399
411
|
}
|
|
400
412
|
}
|
|
401
413
|
}
|
|
@@ -420,7 +432,7 @@ export const clearTelemetry = async () => {
|
|
|
420
432
|
|
|
421
433
|
// Delete all .jsonl files
|
|
422
434
|
const files = await fs.readdir(dir);
|
|
423
|
-
const logFiles = files.filter(f => f.endsWith('.jsonl'));
|
|
435
|
+
const logFiles = files.filter((f) => f.endsWith('.jsonl'));
|
|
424
436
|
|
|
425
437
|
for (const file of logFiles) {
|
|
426
438
|
const filePath = path.join(dir, file);
|