@eldrforge/kodrdriv 0.0.33 → 0.0.37
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/README.md +46 -69
- package/dist/application.js +146 -0
- package/dist/application.js.map +1 -0
- package/dist/arguments.js +22 -21
- package/dist/arguments.js.map +1 -1
- package/dist/commands/audio-commit.js +43 -21
- package/dist/commands/audio-commit.js.map +1 -1
- package/dist/commands/audio-review.js +46 -38
- package/dist/commands/audio-review.js.map +1 -1
- package/dist/commands/clean.js +28 -12
- package/dist/commands/clean.js.map +1 -1
- package/dist/commands/commit.js +132 -39
- package/dist/commands/commit.js.map +1 -1
- package/dist/commands/link.js +177 -159
- package/dist/commands/link.js.map +1 -1
- package/dist/commands/publish-tree.js +19 -6
- package/dist/commands/publish-tree.js.map +1 -1
- package/dist/commands/publish.js +152 -82
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/release.js +21 -16
- package/dist/commands/release.js.map +1 -1
- package/dist/commands/review.js +286 -60
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/select-audio.js +25 -8
- package/dist/commands/select-audio.js.map +1 -1
- package/dist/commands/unlink.js +349 -159
- package/dist/commands/unlink.js.map +1 -1
- package/dist/constants.js +14 -5
- package/dist/constants.js.map +1 -1
- package/dist/content/diff.js +7 -5
- package/dist/content/diff.js.map +1 -1
- package/dist/content/log.js +4 -1
- package/dist/content/log.js.map +1 -1
- package/dist/error/CancellationError.js +9 -0
- package/dist/error/CancellationError.js.map +1 -0
- package/dist/error/CommandErrors.js +120 -0
- package/dist/error/CommandErrors.js.map +1 -0
- package/dist/logging.js +55 -12
- package/dist/logging.js.map +1 -1
- package/dist/main.js +6 -131
- package/dist/main.js.map +1 -1
- package/dist/prompt/commit.js +4 -0
- package/dist/prompt/commit.js.map +1 -1
- package/dist/prompt/instructions/commit.md +33 -24
- package/dist/prompt/instructions/release.md +39 -5
- package/dist/prompt/release.js +41 -1
- package/dist/prompt/release.js.map +1 -1
- package/dist/types.js +9 -2
- package/dist/types.js.map +1 -1
- package/dist/util/github.js +71 -4
- package/dist/util/github.js.map +1 -1
- package/dist/util/npmOptimizations.js +174 -0
- package/dist/util/npmOptimizations.js.map +1 -0
- package/dist/util/openai.js +4 -2
- package/dist/util/openai.js.map +1 -1
- package/dist/util/performance.js +202 -0
- package/dist/util/performance.js.map +1 -0
- package/dist/util/safety.js +166 -0
- package/dist/util/safety.js.map +1 -0
- package/dist/util/storage.js +10 -0
- package/dist/util/storage.js.map +1 -1
- package/dist/util/validation.js +81 -0
- package/dist/util/validation.js.map +1 -0
- package/package.json +19 -18
- package/packages/components/package.json +4 -0
- package/packages/tools/package.json +4 -0
- package/packages/utils/package.json +4 -0
- package/scripts/pre-commit-hook.sh +52 -0
- package/test-project/package.json +1 -0
package/dist/commands/review.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Formatter } from '@riotprompt/riotprompt';
|
|
3
|
+
import { ValidationError, FileOperationError, CommandError } from '../error/CommandErrors.js';
|
|
3
4
|
import { getLogger } from '../logging.js';
|
|
4
5
|
import { createCompletion } from '../util/openai.js';
|
|
5
6
|
import { createPrompt } from '../prompt/review.js';
|
|
@@ -12,10 +13,171 @@ import { getTimestampedReviewNotesFilename, getOutputPath, getTimestampedRespons
|
|
|
12
13
|
import { create as create$1 } from '../util/storage.js';
|
|
13
14
|
import path from 'path';
|
|
14
15
|
import os from 'os';
|
|
15
|
-
import {
|
|
16
|
+
import { spawn } from 'child_process';
|
|
16
17
|
import fs from 'fs/promises';
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
// Safe temp file handling with proper permissions and validation
|
|
20
|
+
const createSecureTempFile = async ()=>{
|
|
21
|
+
const logger = getLogger();
|
|
22
|
+
const tmpDir = os.tmpdir();
|
|
23
|
+
// Ensure temp directory exists and is writable
|
|
24
|
+
try {
|
|
25
|
+
// Use constant value directly to avoid import restrictions
|
|
26
|
+
const W_OK = 2; // fs.constants.W_OK value
|
|
27
|
+
await fs.access(tmpDir, W_OK);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
logger.error(`Temp directory not writable: ${tmpDir}`);
|
|
30
|
+
throw new FileOperationError(`Temp directory not writable: ${error.message}`, tmpDir, error);
|
|
31
|
+
}
|
|
32
|
+
const tmpFilePath = path.join(tmpDir, `kodrdriv_review_${Date.now()}_${Math.random().toString(36).substring(7)}.md`);
|
|
33
|
+
// Create file with restrictive permissions (owner read/write only)
|
|
34
|
+
try {
|
|
35
|
+
const fd = await fs.open(tmpFilePath, 'w', 0o600);
|
|
36
|
+
await fd.close();
|
|
37
|
+
logger.debug(`Created secure temp file: ${tmpFilePath}`);
|
|
38
|
+
return tmpFilePath;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
logger.error(`Failed to create temp file: ${error.message}`);
|
|
41
|
+
throw new FileOperationError(`Failed to create temp file: ${error.message}`, 'temporary file', error);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
// Safe file cleanup with proper error handling
|
|
45
|
+
const cleanupTempFile = async (filePath)=>{
|
|
46
|
+
const logger = getLogger();
|
|
47
|
+
try {
|
|
48
|
+
await fs.unlink(filePath);
|
|
49
|
+
logger.debug(`Cleaned up temp file: ${filePath}`);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
// Only ignore ENOENT (file not found) errors, log others
|
|
52
|
+
if (error.code !== 'ENOENT') {
|
|
53
|
+
logger.warn(`Failed to cleanup temp file ${filePath}: ${error.message}`);
|
|
54
|
+
// Don't throw here to avoid masking the main operation
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
// Editor with timeout and proper error handling
|
|
59
|
+
const openEditorWithTimeout = async (editorCmd, filePath, timeoutMs = 300000)=>{
|
|
60
|
+
const logger = getLogger();
|
|
61
|
+
return new Promise((resolve, reject)=>{
|
|
62
|
+
logger.debug(`Opening editor: ${editorCmd} ${filePath} (timeout: ${timeoutMs}ms)`);
|
|
63
|
+
const child = spawn(editorCmd, [
|
|
64
|
+
filePath
|
|
65
|
+
], {
|
|
66
|
+
stdio: 'inherit',
|
|
67
|
+
shell: false // Prevent shell injection
|
|
68
|
+
});
|
|
69
|
+
const timeout = setTimeout(()=>{
|
|
70
|
+
logger.warn(`Editor timed out after ${timeoutMs}ms, terminating...`);
|
|
71
|
+
child.kill('SIGTERM');
|
|
72
|
+
// Give it a moment to terminate gracefully, then force kill
|
|
73
|
+
setTimeout(()=>{
|
|
74
|
+
if (!child.killed) {
|
|
75
|
+
logger.warn('Editor did not terminate gracefully, force killing...');
|
|
76
|
+
child.kill('SIGKILL');
|
|
77
|
+
}
|
|
78
|
+
}, 5000);
|
|
79
|
+
reject(new Error(`Editor '${editorCmd}' timed out after ${timeoutMs}ms. Consider using a different editor or increasing the timeout.`));
|
|
80
|
+
}, timeoutMs);
|
|
81
|
+
child.on('exit', (code, signal)=>{
|
|
82
|
+
clearTimeout(timeout);
|
|
83
|
+
logger.debug(`Editor exited with code ${code}, signal ${signal}`);
|
|
84
|
+
if (signal === 'SIGTERM' || signal === 'SIGKILL') {
|
|
85
|
+
reject(new Error(`Editor was terminated (${signal})`));
|
|
86
|
+
} else if (code === 0) {
|
|
87
|
+
resolve();
|
|
88
|
+
} else {
|
|
89
|
+
reject(new Error(`Editor exited with non-zero code: ${code}`));
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
child.on('error', (error)=>{
|
|
93
|
+
clearTimeout(timeout);
|
|
94
|
+
logger.error(`Editor error: ${error.message}`);
|
|
95
|
+
reject(new Error(`Failed to launch editor '${editorCmd}': ${error.message}`));
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
// Validate API response format before use
|
|
100
|
+
const validateReviewResult = (data)=>{
|
|
101
|
+
if (!data || typeof data !== 'object') {
|
|
102
|
+
throw new Error('Invalid API response: expected object, got ' + typeof data);
|
|
103
|
+
}
|
|
104
|
+
if (typeof data.summary !== 'string') {
|
|
105
|
+
throw new Error('Invalid API response: missing or invalid summary field');
|
|
106
|
+
}
|
|
107
|
+
if (typeof data.totalIssues !== 'number' || data.totalIssues < 0) {
|
|
108
|
+
throw new Error('Invalid API response: missing or invalid totalIssues field');
|
|
109
|
+
}
|
|
110
|
+
if (data.issues && !Array.isArray(data.issues)) {
|
|
111
|
+
throw new Error('Invalid API response: issues field must be an array');
|
|
112
|
+
}
|
|
113
|
+
// Validate each issue if present
|
|
114
|
+
if (data.issues) {
|
|
115
|
+
for(let i = 0; i < data.issues.length; i++){
|
|
116
|
+
const issue = data.issues[i];
|
|
117
|
+
if (!issue || typeof issue !== 'object') {
|
|
118
|
+
throw new Error(`Invalid API response: issue ${i} is not an object`);
|
|
119
|
+
}
|
|
120
|
+
if (typeof issue.title !== 'string') {
|
|
121
|
+
throw new Error(`Invalid API response: issue ${i} missing title`);
|
|
122
|
+
}
|
|
123
|
+
if (typeof issue.priority !== 'string') {
|
|
124
|
+
throw new Error(`Invalid API response: issue ${i} missing priority`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return data;
|
|
129
|
+
};
|
|
130
|
+
// Enhanced TTY detection with fallback handling
|
|
131
|
+
const isTTYSafe = ()=>{
|
|
132
|
+
try {
|
|
133
|
+
// Primary check
|
|
134
|
+
if (process.stdin.isTTY === false) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
// Additional checks for edge cases
|
|
138
|
+
if (process.stdin.isTTY === true) {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
// Handle undefined case (some environments)
|
|
142
|
+
if (process.stdin.isTTY === undefined) {
|
|
143
|
+
// Check if we can reasonably assume interactive mode
|
|
144
|
+
return process.stdout.isTTY === true && process.stderr.isTTY === true;
|
|
145
|
+
}
|
|
146
|
+
return false;
|
|
147
|
+
} catch (error) {
|
|
148
|
+
// If TTY detection fails entirely, assume non-interactive
|
|
149
|
+
getLogger().debug(`TTY detection failed: ${error}, assuming non-interactive`);
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
// Safe file write with disk space and permission validation
|
|
154
|
+
const safeWriteFile = async (filePath, content, encoding = 'utf-8')=>{
|
|
155
|
+
const logger = getLogger();
|
|
156
|
+
try {
|
|
157
|
+
// Check if parent directory exists and is writable
|
|
158
|
+
const parentDir = path.dirname(filePath);
|
|
159
|
+
const W_OK = 2; // fs.constants.W_OK value
|
|
160
|
+
await fs.access(parentDir, W_OK);
|
|
161
|
+
// Check available disk space (basic check by writing a small test)
|
|
162
|
+
const testFile = `${filePath}.test`;
|
|
163
|
+
try {
|
|
164
|
+
await fs.writeFile(testFile, 'test', encoding);
|
|
165
|
+
await fs.unlink(testFile);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
if (error.code === 'ENOSPC') {
|
|
168
|
+
throw new Error(`Insufficient disk space to write file: ${filePath}`);
|
|
169
|
+
}
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
// Write the actual file
|
|
173
|
+
await fs.writeFile(filePath, content, encoding);
|
|
174
|
+
logger.debug(`Successfully wrote file: ${filePath} (${content.length} characters)`);
|
|
175
|
+
} catch (error) {
|
|
176
|
+
logger.error(`Failed to write file ${filePath}: ${error.message}`);
|
|
177
|
+
throw new Error(`Failed to write file ${filePath}: ${error.message}`);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
const executeInternal = async (runConfig)=>{
|
|
19
181
|
var _runConfig_review, _runConfig_review1, _runConfig_review2, _runConfig_review3, _runConfig_review4, _runConfig_review5, _runConfig_review6, _runConfig_review7, _runConfig_review8, _runConfig_review9, _runConfig_review10, _runConfig_review11, _runConfig_review12, _runConfig_review13, _runConfig_review14, _runConfig_review_context, _runConfig_review15, _runConfig_review16, _analysisResult_issues, _runConfig_review17;
|
|
20
182
|
const logger = getLogger();
|
|
21
183
|
const isDryRun = runConfig.dryRun || false;
|
|
@@ -50,68 +212,70 @@ const execute = async (runConfig)=>{
|
|
|
50
212
|
}
|
|
51
213
|
return 'DRY RUN: Review command would analyze note, gather context, and create GitHub issues';
|
|
52
214
|
}
|
|
53
|
-
//
|
|
54
|
-
|
|
215
|
+
// Enhanced TTY check with proper error handling
|
|
216
|
+
const isInteractive = isTTYSafe();
|
|
217
|
+
if (!isInteractive && !((_runConfig_review9 = runConfig.review) === null || _runConfig_review9 === void 0 ? void 0 : _runConfig_review9.sendit)) {
|
|
55
218
|
logger.error('❌ STDIN is piped but --sendit flag is not enabled');
|
|
56
219
|
logger.error(' Interactive prompts cannot be used when input is piped');
|
|
57
220
|
logger.error(' Solutions:');
|
|
58
221
|
logger.error(' • Add --sendit flag to auto-create all issues');
|
|
59
222
|
logger.error(' • Use terminal input instead of piping');
|
|
60
223
|
logger.error(' • Example: echo "note" | kodrdriv review --sendit');
|
|
61
|
-
throw new
|
|
224
|
+
throw new ValidationError('Piped input requires --sendit flag for non-interactive operation');
|
|
62
225
|
}
|
|
63
226
|
// Get the review note from configuration
|
|
64
227
|
let reviewNote = (_runConfig_review10 = runConfig.review) === null || _runConfig_review10 === void 0 ? void 0 : _runConfig_review10.note;
|
|
65
228
|
// If no review note was provided via CLI arg or STDIN, open the user's editor to capture it.
|
|
66
229
|
if (!reviewNote || !reviewNote.trim()) {
|
|
67
230
|
const editor = process.env.EDITOR || process.env.VISUAL || 'vi';
|
|
68
|
-
|
|
69
|
-
const tmpDir = os.tmpdir();
|
|
70
|
-
const tmpFilePath = path.join(tmpDir, `kodrdriv_review_${Date.now()}.md`);
|
|
71
|
-
// Pre-populate the file with a helpful header so users know what to do.
|
|
72
|
-
const templateContent = [
|
|
73
|
-
'# Kodrdriv Review Note',
|
|
74
|
-
'',
|
|
75
|
-
'# Please enter your review note below. Lines starting with "#" will be ignored.',
|
|
76
|
-
'# Save and close the editor when you are done.',
|
|
77
|
-
'',
|
|
78
|
-
''
|
|
79
|
-
].join('\n');
|
|
80
|
-
await fs.writeFile(tmpFilePath, templateContent, 'utf8');
|
|
81
|
-
logger.info(`No review note provided – opening ${editor} to capture input...`);
|
|
82
|
-
// Open the editor synchronously so execution resumes after the user closes it.
|
|
83
|
-
const result = spawnSync(editor, [
|
|
84
|
-
tmpFilePath
|
|
85
|
-
], {
|
|
86
|
-
stdio: 'inherit'
|
|
87
|
-
});
|
|
88
|
-
if (result.error) {
|
|
89
|
-
throw new Error(`Failed to launch editor '${editor}': ${result.error.message}`);
|
|
90
|
-
}
|
|
91
|
-
// Read the file back in, stripping comment lines and whitespace.
|
|
92
|
-
const fileContent = (await fs.readFile(tmpFilePath, 'utf8')).split('\n').filter((line)=>!line.trim().startsWith('#')).join('\n').trim();
|
|
93
|
-
// Clean up the temporary file (best-effort – ignore errors).
|
|
231
|
+
let tmpFilePath = null;
|
|
94
232
|
try {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
233
|
+
var _runConfig_review20;
|
|
234
|
+
// Create secure temporary file
|
|
235
|
+
tmpFilePath = await createSecureTempFile();
|
|
236
|
+
// Pre-populate the file with a helpful header so users know what to do.
|
|
237
|
+
const templateContent = [
|
|
238
|
+
'# Kodrdriv Review Note',
|
|
239
|
+
'',
|
|
240
|
+
'# Please enter your review note below. Lines starting with "#" will be ignored.',
|
|
241
|
+
'# Save and close the editor when you are done.',
|
|
242
|
+
'',
|
|
243
|
+
''
|
|
244
|
+
].join('\n');
|
|
245
|
+
await safeWriteFile(tmpFilePath, templateContent);
|
|
246
|
+
logger.info(`No review note provided – opening ${editor} to capture input...`);
|
|
247
|
+
// Open the editor with timeout protection
|
|
248
|
+
const editorTimeout = ((_runConfig_review20 = runConfig.review) === null || _runConfig_review20 === void 0 ? void 0 : _runConfig_review20.editorTimeout) || 300000; // 5 minutes default
|
|
249
|
+
await openEditorWithTimeout(editor, tmpFilePath, editorTimeout);
|
|
250
|
+
// Read the file back in, stripping comment lines and whitespace.
|
|
251
|
+
const fileContent = (await fs.readFile(tmpFilePath, 'utf8')).split('\n').filter((line)=>!line.trim().startsWith('#')).join('\n').trim();
|
|
252
|
+
if (!fileContent) {
|
|
253
|
+
throw new ValidationError('Review note is empty – aborting. Provide a note as an argument, via STDIN, or through the editor.');
|
|
254
|
+
}
|
|
255
|
+
reviewNote = fileContent;
|
|
256
|
+
// If the original runConfig.review object exists, update it so downstream code has the note.
|
|
257
|
+
if (runConfig.review) {
|
|
258
|
+
runConfig.review.note = reviewNote;
|
|
259
|
+
}
|
|
260
|
+
} catch (error) {
|
|
261
|
+
logger.error(`Failed to capture review note via editor: ${error.message}`);
|
|
262
|
+
throw error;
|
|
263
|
+
} finally{
|
|
264
|
+
// Always clean up the temp file
|
|
265
|
+
if (tmpFilePath) {
|
|
266
|
+
await cleanupTempFile(tmpFilePath);
|
|
267
|
+
}
|
|
105
268
|
}
|
|
106
269
|
}
|
|
107
270
|
logger.info('📝 Starting review analysis...');
|
|
108
271
|
logger.debug('Review note: %s', reviewNote);
|
|
109
272
|
logger.debug('Review note length: %d characters', reviewNote.length);
|
|
110
|
-
// Gather additional context based on configuration
|
|
273
|
+
// Gather additional context based on configuration with improved error handling
|
|
111
274
|
let logContext = '';
|
|
112
275
|
let diffContext = '';
|
|
113
276
|
let releaseNotesContext = '';
|
|
114
277
|
let issuesContext = '';
|
|
278
|
+
const contextErrors = [];
|
|
115
279
|
// Fetch commit history if enabled
|
|
116
280
|
if ((_runConfig_review11 = runConfig.review) === null || _runConfig_review11 === void 0 ? void 0 : _runConfig_review11.includeCommitHistory) {
|
|
117
281
|
try {
|
|
@@ -125,7 +289,9 @@ const execute = async (runConfig)=>{
|
|
|
125
289
|
logger.debug('Added commit history to context (%d characters)', logContent.length);
|
|
126
290
|
}
|
|
127
291
|
} catch (error) {
|
|
128
|
-
|
|
292
|
+
const errorMsg = `Failed to fetch commit history: ${error.message}`;
|
|
293
|
+
logger.warn(errorMsg);
|
|
294
|
+
contextErrors.push(errorMsg);
|
|
129
295
|
}
|
|
130
296
|
}
|
|
131
297
|
// Fetch recent diffs if enabled
|
|
@@ -139,8 +305,13 @@ const execute = async (runConfig)=>{
|
|
|
139
305
|
baseExcludedPatterns: basePatterns
|
|
140
306
|
});
|
|
141
307
|
diffContext += recentDiffs;
|
|
308
|
+
if (recentDiffs.trim()) {
|
|
309
|
+
logger.debug('Added recent diffs to context (%d characters)', recentDiffs.length);
|
|
310
|
+
}
|
|
142
311
|
} catch (error) {
|
|
143
|
-
|
|
312
|
+
const errorMsg = `Failed to fetch recent diffs: ${error.message}`;
|
|
313
|
+
logger.warn(errorMsg);
|
|
314
|
+
contextErrors.push(errorMsg);
|
|
144
315
|
}
|
|
145
316
|
}
|
|
146
317
|
// Fetch release notes if enabled
|
|
@@ -155,7 +326,9 @@ const execute = async (runConfig)=>{
|
|
|
155
326
|
logger.debug('Added release notes to context (%d characters)', releaseNotesContent.length);
|
|
156
327
|
}
|
|
157
328
|
} catch (error) {
|
|
158
|
-
|
|
329
|
+
const errorMsg = `Failed to fetch release notes: ${error.message}`;
|
|
330
|
+
logger.warn(errorMsg);
|
|
331
|
+
contextErrors.push(errorMsg);
|
|
159
332
|
}
|
|
160
333
|
}
|
|
161
334
|
// Fetch GitHub issues if enabled
|
|
@@ -165,9 +338,24 @@ const execute = async (runConfig)=>{
|
|
|
165
338
|
issuesContext = await get$1({
|
|
166
339
|
limit: runConfig.review.githubIssuesLimit || 20
|
|
167
340
|
});
|
|
168
|
-
|
|
341
|
+
if (issuesContext.trim()) {
|
|
342
|
+
logger.debug('Added GitHub issues to context (%d characters)', issuesContext.length);
|
|
343
|
+
}
|
|
169
344
|
} catch (error) {
|
|
170
|
-
|
|
345
|
+
const errorMsg = `Failed to fetch GitHub issues: ${error.message}`;
|
|
346
|
+
logger.warn(errorMsg);
|
|
347
|
+
contextErrors.push(errorMsg);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// Report context gathering results
|
|
351
|
+
if (contextErrors.length > 0) {
|
|
352
|
+
var _runConfig_review21;
|
|
353
|
+
logger.warn(`Context gathering completed with ${contextErrors.length} error(s):`);
|
|
354
|
+
contextErrors.forEach((error)=>logger.warn(` - ${error}`));
|
|
355
|
+
// For critical operations, consider failing if too many context sources fail
|
|
356
|
+
const maxContextErrors = ((_runConfig_review21 = runConfig.review) === null || _runConfig_review21 === void 0 ? void 0 : _runConfig_review21.maxContextErrors) || contextErrors.length; // Default: allow all errors
|
|
357
|
+
if (contextErrors.length > maxContextErrors) {
|
|
358
|
+
throw new Error(`Too many context gathering errors (${contextErrors.length}), aborting review. Consider checking your configuration and network connectivity.`);
|
|
171
359
|
}
|
|
172
360
|
}
|
|
173
361
|
// Analyze review note for issues using OpenAI
|
|
@@ -201,7 +389,7 @@ const execute = async (runConfig)=>{
|
|
|
201
389
|
await storage.ensureDirectory(outputDirectory);
|
|
202
390
|
// Save timestamped copy of review notes and context to output directory
|
|
203
391
|
try {
|
|
204
|
-
var _runConfig_review_context1,
|
|
392
|
+
var _runConfig_review_context1, _runConfig_review22;
|
|
205
393
|
// Save the original review note
|
|
206
394
|
const reviewNotesFilename = getTimestampedReviewNotesFilename();
|
|
207
395
|
const reviewNotesPath = getOutputPath(outputDirectory, reviewNotesFilename);
|
|
@@ -219,26 +407,35 @@ const execute = async (runConfig)=>{
|
|
|
219
407
|
if (issuesContext.trim()) {
|
|
220
408
|
reviewNotesContent += `# GitHub Issues Context\n\n${issuesContext}\n\n`;
|
|
221
409
|
}
|
|
222
|
-
if ((
|
|
410
|
+
if ((_runConfig_review22 = runConfig.review) === null || _runConfig_review22 === void 0 ? void 0 : (_runConfig_review_context1 = _runConfig_review22.context) === null || _runConfig_review_context1 === void 0 ? void 0 : _runConfig_review_context1.trim()) {
|
|
223
411
|
reviewNotesContent += `# User Context\n\n${runConfig.review.context}\n\n`;
|
|
224
412
|
}
|
|
225
|
-
await
|
|
413
|
+
await safeWriteFile(reviewNotesPath, reviewNotesContent);
|
|
226
414
|
logger.debug('Saved timestamped review notes and context: %s', reviewNotesPath);
|
|
227
415
|
} catch (error) {
|
|
228
416
|
logger.warn('Failed to save timestamped review notes: %s', error.message);
|
|
417
|
+
// Don't fail the entire operation for this
|
|
229
418
|
}
|
|
230
419
|
const request = Formatter.create({
|
|
231
420
|
logger
|
|
232
421
|
}).formatPrompt(runConfig.model, prompt);
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
422
|
+
let analysisResult;
|
|
423
|
+
try {
|
|
424
|
+
const rawResult = await createCompletion(request.messages, {
|
|
425
|
+
model: runConfig.model,
|
|
426
|
+
responseFormat: {
|
|
427
|
+
type: 'json_object'
|
|
428
|
+
},
|
|
429
|
+
debug: runConfig.debug,
|
|
430
|
+
debugRequestFile: getOutputPath(outputDirectory, getTimestampedRequestFilename('review-analysis')),
|
|
431
|
+
debugResponseFile: getOutputPath(outputDirectory, getTimestampedResponseFilename('review-analysis'))
|
|
432
|
+
});
|
|
433
|
+
// Validate the API response before using it
|
|
434
|
+
analysisResult = validateReviewResult(rawResult);
|
|
435
|
+
} catch (error) {
|
|
436
|
+
logger.error(`Failed to analyze review note: ${error.message}`);
|
|
437
|
+
throw new Error(`Review analysis failed: ${error.message}`);
|
|
438
|
+
}
|
|
242
439
|
logger.info('✅ Analysis completed');
|
|
243
440
|
logger.debug('Analysis result summary: %s', analysisResult.summary);
|
|
244
441
|
logger.debug('Total issues found: %d', analysisResult.totalIssues);
|
|
@@ -254,15 +451,44 @@ const execute = async (runConfig)=>{
|
|
|
254
451
|
const reviewPath = getOutputPath(outputDirectory, reviewFilename);
|
|
255
452
|
// Format the analysis result as markdown
|
|
256
453
|
const reviewContent = `# Review Analysis Result\n\n` + `## Summary\n${analysisResult.summary}\n\n` + `## Total Issues Found\n${analysisResult.totalIssues}\n\n` + `## Issues\n\n${JSON.stringify(analysisResult.issues, null, 2)}\n\n` + `---\n\n*Analysis completed at ${new Date().toISOString()}*`;
|
|
257
|
-
await
|
|
454
|
+
await safeWriteFile(reviewPath, reviewContent);
|
|
258
455
|
logger.debug('Saved timestamped review analysis: %s', reviewPath);
|
|
259
456
|
} catch (error) {
|
|
260
457
|
logger.warn('Failed to save timestamped review analysis: %s', error.message);
|
|
458
|
+
// Don't fail the entire operation for this
|
|
261
459
|
}
|
|
262
460
|
// Handle GitHub issue creation using the issues module
|
|
263
461
|
const senditMode = ((_runConfig_review17 = runConfig.review) === null || _runConfig_review17 === void 0 ? void 0 : _runConfig_review17.sendit) || false;
|
|
264
462
|
return await handleIssueCreation(analysisResult, senditMode);
|
|
265
463
|
};
|
|
464
|
+
const execute = async (runConfig)=>{
|
|
465
|
+
try {
|
|
466
|
+
return await executeInternal(runConfig);
|
|
467
|
+
} catch (error) {
|
|
468
|
+
const logger = getLogger();
|
|
469
|
+
if (error instanceof ValidationError) {
|
|
470
|
+
logger.error(`review failed: ${error.message}`);
|
|
471
|
+
process.exit(1);
|
|
472
|
+
}
|
|
473
|
+
if (error instanceof FileOperationError) {
|
|
474
|
+
logger.error(`review failed: ${error.message}`);
|
|
475
|
+
if (error.cause) {
|
|
476
|
+
logger.debug(`Caused by: ${error.cause.message}`);
|
|
477
|
+
}
|
|
478
|
+
process.exit(1);
|
|
479
|
+
}
|
|
480
|
+
if (error instanceof CommandError) {
|
|
481
|
+
logger.error(`review failed: ${error.message}`);
|
|
482
|
+
if (error.cause) {
|
|
483
|
+
logger.debug(`Caused by: ${error.cause.message}`);
|
|
484
|
+
}
|
|
485
|
+
process.exit(1);
|
|
486
|
+
}
|
|
487
|
+
// Unexpected errors
|
|
488
|
+
logger.error(`review encountered unexpected error: ${error.message}`);
|
|
489
|
+
process.exit(1);
|
|
490
|
+
}
|
|
491
|
+
};
|
|
266
492
|
|
|
267
493
|
export { execute };
|
|
268
494
|
//# sourceMappingURL=review.js.map
|