@eldrforge/kodrdriv 1.2.133 ā 1.2.135
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/.cursor/rules/no-local-dependencies.md +6 -0
- package/AI-GUIDE.md +4 -1
- package/LICENSE +1 -1
- package/dist/application.js +34 -42
- package/dist/application.js.map +1 -1
- package/dist/arguments.js +3 -3
- package/dist/arguments.js.map +1 -1
- package/dist/constants.js +5 -7
- package/dist/constants.js.map +1 -1
- package/dist/logging.js +4 -32
- package/dist/logging.js.map +1 -1
- package/dist/types.js +283 -0
- package/dist/types.js.map +1 -0
- package/guide/ai-system.md +3 -0
- package/guide/architecture.md +3 -0
- package/guide/commands.md +3 -0
- package/guide/configuration.md +3 -0
- package/guide/debugging.md +4 -1
- package/guide/development.md +3 -0
- package/guide/index.md +4 -1
- package/guide/integration.md +3 -0
- package/guide/monorepo.md +3 -0
- package/guide/quickstart.md +3 -0
- package/guide/testing.md +3 -0
- package/guide/tree-operations.md +3 -0
- package/guide/usage.md +3 -0
- package/package.json +15 -10
- package/DUPLICATION-CLEANUP.md +0 -104
- package/agentic-reflection-commit-2025-12-27T22-56-06-143Z.md +0 -50
- package/agentic-reflection-commit-2025-12-27T23-01-57-294Z.md +0 -50
- package/agentic-reflection-commit-2025-12-27T23-11-57-811Z.md +0 -50
- package/agentic-reflection-commit-2025-12-27T23-12-50-645Z.md +0 -50
- package/agentic-reflection-commit-2025-12-27T23-13-59-347Z.md +0 -52
- package/agentic-reflection-commit-2025-12-27T23-14-36-001Z.md +0 -50
- package/agentic-reflection-commit-2025-12-27T23-18-59-832Z.md +0 -50
- package/agentic-reflection-commit-2025-12-27T23-25-20-667Z.md +0 -62
- package/dist/commands/audio-commit.js +0 -152
- package/dist/commands/audio-commit.js.map +0 -1
- package/dist/commands/audio-review.js +0 -274
- package/dist/commands/audio-review.js.map +0 -1
- package/dist/commands/clean.js +0 -49
- package/dist/commands/clean.js.map +0 -1
- package/dist/commands/commit.js +0 -680
- package/dist/commands/commit.js.map +0 -1
- package/dist/commands/development.js +0 -467
- package/dist/commands/development.js.map +0 -1
- package/dist/commands/link.js +0 -646
- package/dist/commands/link.js.map +0 -1
- package/dist/commands/precommit.js +0 -99
- package/dist/commands/precommit.js.map +0 -1
- package/dist/commands/publish.js +0 -1432
- package/dist/commands/publish.js.map +0 -1
- package/dist/commands/release.js +0 -376
- package/dist/commands/release.js.map +0 -1
- package/dist/commands/review.js +0 -733
- package/dist/commands/review.js.map +0 -1
- package/dist/commands/select-audio.js +0 -46
- package/dist/commands/select-audio.js.map +0 -1
- package/dist/commands/tree.js +0 -2363
- package/dist/commands/tree.js.map +0 -1
- package/dist/commands/unlink.js +0 -537
- package/dist/commands/unlink.js.map +0 -1
- package/dist/commands/updates.js +0 -211
- package/dist/commands/updates.js.map +0 -1
- package/dist/commands/versions.js +0 -221
- package/dist/commands/versions.js.map +0 -1
- package/dist/content/diff.js +0 -346
- package/dist/content/diff.js.map +0 -1
- package/dist/content/files.js +0 -190
- package/dist/content/files.js.map +0 -1
- package/dist/content/log.js +0 -72
- package/dist/content/log.js.map +0 -1
- package/dist/util/aiAdapter.js +0 -28
- package/dist/util/aiAdapter.js.map +0 -1
- package/dist/util/fileLock.js +0 -241
- package/dist/util/fileLock.js.map +0 -1
- package/dist/util/general.js +0 -379
- package/dist/util/general.js.map +0 -1
- package/dist/util/gitMutex.js +0 -161
- package/dist/util/gitMutex.js.map +0 -1
- package/dist/util/interactive.js +0 -32
- package/dist/util/interactive.js.map +0 -1
- package/dist/util/loggerAdapter.js +0 -41
- package/dist/util/loggerAdapter.js.map +0 -1
- package/dist/util/performance.js +0 -134
- package/dist/util/performance.js.map +0 -1
- package/dist/util/precommitOptimizations.js +0 -310
- package/dist/util/precommitOptimizations.js.map +0 -1
- package/dist/util/stopContext.js +0 -146
- package/dist/util/stopContext.js.map +0 -1
- package/dist/util/storageAdapter.js +0 -31
- package/dist/util/storageAdapter.js.map +0 -1
- package/dist/util/validation.js +0 -45
- package/dist/util/validation.js.map +0 -1
- package/dist/utils/branchState.js +0 -700
- package/dist/utils/branchState.js.map +0 -1
package/dist/commands/review.js
DELETED
|
@@ -1,733 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { Formatter } from '@riotprompt/riotprompt';
|
|
3
|
-
import { ValidationError, FileOperationError, CommandError, createStorage } from '@eldrforge/shared';
|
|
4
|
-
import { getLogger } from '../logging.js';
|
|
5
|
-
import { getUserChoice, createReviewPrompt, createCompletion } from '@eldrforge/ai-service';
|
|
6
|
-
import { toAIConfig } from '../util/aiAdapter.js';
|
|
7
|
-
import { createStorageAdapter } from '../util/storageAdapter.js';
|
|
8
|
-
import { createLoggerAdapter } from '../util/loggerAdapter.js';
|
|
9
|
-
import { create } from '../content/log.js';
|
|
10
|
-
import { getReviewExcludedPatterns, getRecentDiffsForReview } from '../content/diff.js';
|
|
11
|
-
import { handleIssueCreation, getReleaseNotesContent, getIssuesContent } from '@eldrforge/github-tools';
|
|
12
|
-
import { DEFAULT_EXCLUDED_PATTERNS, DEFAULT_OUTPUT_DIRECTORY } from '../constants.js';
|
|
13
|
-
import { getTimestampedReviewNotesFilename, getOutputPath, getTimestampedReviewFilename, getTimestampedResponseFilename, getTimestampedRequestFilename } from '../util/general.js';
|
|
14
|
-
import path__default from 'path';
|
|
15
|
-
import os__default from 'os';
|
|
16
|
-
import { spawn } from 'child_process';
|
|
17
|
-
import fs from 'fs/promises';
|
|
18
|
-
import { filterContent } from '../util/stopContext.js';
|
|
19
|
-
|
|
20
|
-
// Utility function to read a review note from a file
|
|
21
|
-
const readReviewNoteFromFile = async (filePath)=>{
|
|
22
|
-
const logger = getLogger();
|
|
23
|
-
try {
|
|
24
|
-
logger.debug(`Reading review note from file: ${filePath}`);
|
|
25
|
-
const content = await fs.readFile(filePath, 'utf8');
|
|
26
|
-
if (!content.trim()) {
|
|
27
|
-
throw new ValidationError(`Review file is empty: ${filePath}`);
|
|
28
|
-
}
|
|
29
|
-
logger.debug(`Successfully read review note from file: ${filePath} (${content.length} characters)`);
|
|
30
|
-
return content.trim();
|
|
31
|
-
} catch (error) {
|
|
32
|
-
if (error.code === 'ENOENT') {
|
|
33
|
-
throw new FileOperationError(`Review file not found: ${filePath}`, filePath, error);
|
|
34
|
-
}
|
|
35
|
-
if (error instanceof ValidationError) {
|
|
36
|
-
throw error;
|
|
37
|
-
}
|
|
38
|
-
throw new FileOperationError(`Failed to read review file: ${error.message}`, filePath, error);
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
// Utility function to get all review files in a directory
|
|
42
|
-
const getReviewFilesInDirectory = async (directoryPath)=>{
|
|
43
|
-
const logger = getLogger();
|
|
44
|
-
try {
|
|
45
|
-
logger.debug(`Scanning directory for review files: ${directoryPath}`);
|
|
46
|
-
const entries = await fs.readdir(directoryPath, {
|
|
47
|
-
withFileTypes: true
|
|
48
|
-
});
|
|
49
|
-
// Filter for regular files (not directories) and get full paths
|
|
50
|
-
const files = entries.filter((entry)=>entry.isFile()).map((entry)=>path__default.join(directoryPath, entry.name)).sort(); // Sort alphabetically
|
|
51
|
-
logger.debug(`Found ${files.length} files in directory: ${directoryPath}`);
|
|
52
|
-
return files;
|
|
53
|
-
} catch (error) {
|
|
54
|
-
if (error.code === 'ENOENT') {
|
|
55
|
-
throw new FileOperationError(`Directory not found: ${directoryPath}`, directoryPath, error);
|
|
56
|
-
}
|
|
57
|
-
throw new FileOperationError(`Failed to read directory: ${directoryPath}`, directoryPath, error);
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
// New function for file selection phase
|
|
61
|
-
const selectFilesForProcessing = async (reviewFiles, senditMode)=>{
|
|
62
|
-
const logger = getLogger();
|
|
63
|
-
if (senditMode) {
|
|
64
|
-
logger.info(`REVIEW_AUTO_SELECT: Auto-selecting all files for processing | Mode: sendit | File Count: ${reviewFiles.length} | Confirmation: automatic`);
|
|
65
|
-
return reviewFiles;
|
|
66
|
-
}
|
|
67
|
-
// Check if we're in an interactive environment
|
|
68
|
-
if (!isTTYSafe()) {
|
|
69
|
-
logger.warn(`REVIEW_NON_INTERACTIVE_SELECT: Non-interactive environment detected | Action: Selecting all files | Mode: non-interactive`);
|
|
70
|
-
return reviewFiles;
|
|
71
|
-
}
|
|
72
|
-
logger.info(`\nREVIEW_SELECTION_PHASE: Starting file selection phase | File Count: ${reviewFiles.length} | Purpose: Choose files to process`);
|
|
73
|
-
logger.info(`REVIEW_SELECTION_FILES: Found files to review | Count: ${reviewFiles.length} | Action: Select files for processing`);
|
|
74
|
-
logger.info(`REVIEW_SELECTION_OPTIONS: File selection options available | [c]=Confirm | [s]=Skip | [a]=Abort`);
|
|
75
|
-
logger.info(``);
|
|
76
|
-
const selectedFiles = [];
|
|
77
|
-
let shouldAbort = false;
|
|
78
|
-
for(let i = 0; i < reviewFiles.length; i++){
|
|
79
|
-
const filePath = reviewFiles[i];
|
|
80
|
-
logger.info(`REVIEW_SELECTION_FILE: File for review | Progress: ${i + 1}/${reviewFiles.length} | File: ${filePath}`);
|
|
81
|
-
const choice = await getUserChoice(`Select action for this file:`, [
|
|
82
|
-
{
|
|
83
|
-
key: 'c',
|
|
84
|
-
label: 'Confirm and process'
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
key: 's',
|
|
88
|
-
label: 'Skip this file'
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
key: 'a',
|
|
92
|
-
label: 'Abort entire review'
|
|
93
|
-
}
|
|
94
|
-
]);
|
|
95
|
-
if (choice === 'a') {
|
|
96
|
-
logger.info(`REVIEW_ABORTED: User aborted review process | Action: Aborting | Reason: User request`);
|
|
97
|
-
shouldAbort = true;
|
|
98
|
-
break;
|
|
99
|
-
} else if (choice === 'c') {
|
|
100
|
-
selectedFiles.push(filePath);
|
|
101
|
-
logger.info(`REVIEW_FILE_SELECTED: File selected for processing | File: ${filePath} | Action: Will be processed`);
|
|
102
|
-
} else if (choice === 's') {
|
|
103
|
-
logger.info(`REVIEW_FILE_SKIPPED: File skipped during selection | File: ${filePath} | Action: Will not be processed`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
if (shouldAbort) {
|
|
107
|
-
throw new Error('Review process aborted by user');
|
|
108
|
-
}
|
|
109
|
-
if (selectedFiles.length === 0) {
|
|
110
|
-
throw new Error('No files were selected for processing');
|
|
111
|
-
}
|
|
112
|
-
logger.info(`\nš File selection complete. ${selectedFiles.length} files selected for processing:`);
|
|
113
|
-
selectedFiles.forEach((file, index)=>{
|
|
114
|
-
logger.info(` ${index + 1}. ${file}`);
|
|
115
|
-
});
|
|
116
|
-
logger.info(``);
|
|
117
|
-
return selectedFiles;
|
|
118
|
-
};
|
|
119
|
-
// Safe temp file handling with proper permissions and validation
|
|
120
|
-
const createSecureTempFile = async ()=>{
|
|
121
|
-
const logger = getLogger();
|
|
122
|
-
const tmpDir = os__default.tmpdir();
|
|
123
|
-
// Ensure temp directory exists and is writable
|
|
124
|
-
try {
|
|
125
|
-
// Use constant value directly to avoid import restrictions
|
|
126
|
-
const W_OK = 2; // fs.constants.W_OK value
|
|
127
|
-
await fs.access(tmpDir, W_OK);
|
|
128
|
-
} catch (error) {
|
|
129
|
-
logger.error(`TEMP_DIR_NOT_WRITABLE: Temporary directory is not writable | Directory: ${tmpDir} | Impact: Cannot create temp files`);
|
|
130
|
-
throw new FileOperationError(`Temp directory not writable: ${error.message}`, tmpDir, error);
|
|
131
|
-
}
|
|
132
|
-
const tmpFilePath = path__default.join(tmpDir, `kodrdriv_review_${Date.now()}_${Math.random().toString(36).substring(7)}.md`);
|
|
133
|
-
// Create file with restrictive permissions (owner read/write only)
|
|
134
|
-
try {
|
|
135
|
-
const fd = await fs.open(tmpFilePath, 'w', 0o600);
|
|
136
|
-
await fd.close();
|
|
137
|
-
logger.debug(`Created secure temp file: ${tmpFilePath}`);
|
|
138
|
-
return tmpFilePath;
|
|
139
|
-
} catch (error) {
|
|
140
|
-
logger.error(`TEMP_FILE_CREATE_FAILED: Unable to create temporary file | Error: ${error.message} | Impact: Cannot proceed with review`);
|
|
141
|
-
throw new FileOperationError(`Failed to create temp file: ${error.message}`, 'temporary file', error);
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
// Safe file cleanup with proper error handling
|
|
145
|
-
const cleanupTempFile = async (filePath)=>{
|
|
146
|
-
const logger = getLogger();
|
|
147
|
-
try {
|
|
148
|
-
await fs.unlink(filePath);
|
|
149
|
-
logger.debug(`Cleaned up temp file: ${filePath}`);
|
|
150
|
-
} catch (error) {
|
|
151
|
-
// Only ignore ENOENT (file not found) errors, log others
|
|
152
|
-
if (error.code !== 'ENOENT') {
|
|
153
|
-
logger.warn(`TEMP_FILE_CLEANUP_FAILED: Unable to cleanup temporary file | File: ${filePath} | Error: ${error.message} | Impact: File may remain`);
|
|
154
|
-
// Don't throw here to avoid masking the main operation
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
};
|
|
158
|
-
// Editor with optional timeout and proper error handling
|
|
159
|
-
const openEditorWithTimeout = async (editorCmd, filePath, timeoutMs)=>{
|
|
160
|
-
const logger = getLogger();
|
|
161
|
-
return new Promise((resolve, reject)=>{
|
|
162
|
-
if (timeoutMs) {
|
|
163
|
-
logger.debug(`Opening editor: ${editorCmd} ${filePath} (timeout: ${timeoutMs}ms)`);
|
|
164
|
-
} else {
|
|
165
|
-
logger.debug(`Opening editor: ${editorCmd} ${filePath} (no timeout)`);
|
|
166
|
-
}
|
|
167
|
-
const child = spawn(editorCmd, [
|
|
168
|
-
filePath
|
|
169
|
-
], {
|
|
170
|
-
stdio: 'inherit',
|
|
171
|
-
shell: false // Prevent shell injection
|
|
172
|
-
});
|
|
173
|
-
let timeout;
|
|
174
|
-
let timeoutCleared = false;
|
|
175
|
-
const clearTimeoutSafely = ()=>{
|
|
176
|
-
if (timeout && !timeoutCleared) {
|
|
177
|
-
clearTimeout(timeout);
|
|
178
|
-
timeoutCleared = true;
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
if (timeoutMs) {
|
|
182
|
-
timeout = setTimeout(()=>{
|
|
183
|
-
clearTimeoutSafely(); // Clear the timeout immediately when it fires
|
|
184
|
-
logger.warn(`Editor timed out after ${timeoutMs}ms, terminating...`);
|
|
185
|
-
child.kill('SIGTERM');
|
|
186
|
-
// Give it a moment to terminate gracefully, then force kill
|
|
187
|
-
setTimeout(()=>{
|
|
188
|
-
if (!child.killed) {
|
|
189
|
-
logger.warn('Editor did not terminate gracefully, force killing...');
|
|
190
|
-
child.kill('SIGKILL');
|
|
191
|
-
}
|
|
192
|
-
}, 5000);
|
|
193
|
-
reject(new Error(`Editor '${editorCmd}' timed out after ${timeoutMs}ms. Consider using a different editor or increasing the timeout.`));
|
|
194
|
-
}, timeoutMs);
|
|
195
|
-
}
|
|
196
|
-
child.on('exit', (code, signal)=>{
|
|
197
|
-
clearTimeoutSafely();
|
|
198
|
-
logger.debug(`Editor exited with code ${code}, signal ${signal}`);
|
|
199
|
-
if (signal === 'SIGTERM' || signal === 'SIGKILL') {
|
|
200
|
-
reject(new Error(`Editor was terminated (${signal})`));
|
|
201
|
-
} else if (code === 0) {
|
|
202
|
-
resolve();
|
|
203
|
-
} else {
|
|
204
|
-
reject(new Error(`Editor exited with non-zero code: ${code}`));
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
child.on('error', (error)=>{
|
|
208
|
-
clearTimeoutSafely();
|
|
209
|
-
logger.error(`Editor error: ${error.message}`);
|
|
210
|
-
reject(new Error(`Failed to launch editor '${editorCmd}': ${error.message}`));
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
};
|
|
214
|
-
// Validate API response format before use
|
|
215
|
-
const validateReviewResult = (data)=>{
|
|
216
|
-
if (!data || typeof data !== 'object') {
|
|
217
|
-
throw new Error('Invalid API response: expected object, got ' + typeof data);
|
|
218
|
-
}
|
|
219
|
-
if (typeof data.summary !== 'string') {
|
|
220
|
-
throw new Error('Invalid API response: missing or invalid summary field');
|
|
221
|
-
}
|
|
222
|
-
if (typeof data.totalIssues !== 'number' || data.totalIssues < 0) {
|
|
223
|
-
throw new Error('Invalid API response: missing or invalid totalIssues field');
|
|
224
|
-
}
|
|
225
|
-
if (data.issues && !Array.isArray(data.issues)) {
|
|
226
|
-
throw new Error('Invalid API response: issues field must be an array');
|
|
227
|
-
}
|
|
228
|
-
// Validate each issue if present
|
|
229
|
-
if (data.issues) {
|
|
230
|
-
for(let i = 0; i < data.issues.length; i++){
|
|
231
|
-
const issue = data.issues[i];
|
|
232
|
-
if (!issue || typeof issue !== 'object') {
|
|
233
|
-
throw new Error(`Invalid API response: issue ${i} is not an object`);
|
|
234
|
-
}
|
|
235
|
-
if (typeof issue.title !== 'string') {
|
|
236
|
-
throw new Error(`Invalid API response: issue ${i} missing title`);
|
|
237
|
-
}
|
|
238
|
-
if (typeof issue.priority !== 'string') {
|
|
239
|
-
throw new Error(`Invalid API response: issue ${i} missing priority`);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
return data;
|
|
244
|
-
};
|
|
245
|
-
// Enhanced TTY detection with fallback handling
|
|
246
|
-
const isTTYSafe = ()=>{
|
|
247
|
-
try {
|
|
248
|
-
// Primary check
|
|
249
|
-
if (process.stdin.isTTY === false) {
|
|
250
|
-
return false;
|
|
251
|
-
}
|
|
252
|
-
// Additional checks for edge cases
|
|
253
|
-
if (process.stdin.isTTY === true) {
|
|
254
|
-
return true;
|
|
255
|
-
}
|
|
256
|
-
// Handle undefined case (some environments)
|
|
257
|
-
if (process.stdin.isTTY === undefined) {
|
|
258
|
-
// Check if we can reasonably assume interactive mode
|
|
259
|
-
return process.stdout.isTTY === true && process.stderr.isTTY === true;
|
|
260
|
-
}
|
|
261
|
-
return false;
|
|
262
|
-
} catch (error) {
|
|
263
|
-
// If TTY detection fails entirely, assume non-interactive
|
|
264
|
-
getLogger().debug(`TTY detection failed: ${error}, assuming non-interactive`);
|
|
265
|
-
return false;
|
|
266
|
-
}
|
|
267
|
-
};
|
|
268
|
-
// Safe file write with disk space and permission validation
|
|
269
|
-
const safeWriteFile = async (filePath, content, encoding = 'utf-8')=>{
|
|
270
|
-
const logger = getLogger();
|
|
271
|
-
try {
|
|
272
|
-
// Check if parent directory exists and is writable
|
|
273
|
-
const parentDir = path__default.dirname(filePath);
|
|
274
|
-
const W_OK = 2; // fs.constants.W_OK value
|
|
275
|
-
await fs.access(parentDir, W_OK);
|
|
276
|
-
// Check available disk space (basic check by writing a small test)
|
|
277
|
-
const testFile = `${filePath}.test`;
|
|
278
|
-
try {
|
|
279
|
-
await fs.writeFile(testFile, 'test', encoding);
|
|
280
|
-
await fs.unlink(testFile);
|
|
281
|
-
} catch (error) {
|
|
282
|
-
if (error.code === 'ENOSPC') {
|
|
283
|
-
throw new Error(`Insufficient disk space to write file: ${filePath}`);
|
|
284
|
-
}
|
|
285
|
-
throw error;
|
|
286
|
-
}
|
|
287
|
-
// Write the actual file
|
|
288
|
-
await fs.writeFile(filePath, content, encoding);
|
|
289
|
-
logger.debug(`Successfully wrote file: ${filePath} (${content.length} characters)`);
|
|
290
|
-
} catch (error) {
|
|
291
|
-
logger.error(`Failed to write file ${filePath}: ${error.message}`);
|
|
292
|
-
throw new Error(`Failed to write file ${filePath}: ${error.message}`);
|
|
293
|
-
}
|
|
294
|
-
};
|
|
295
|
-
// Helper function to process a single review note
|
|
296
|
-
const processSingleReview = async (reviewNote, runConfig, outputDirectory)=>{
|
|
297
|
-
var _runConfig_review, _runConfig_review1, _runConfig_review2, _runConfig_review3, _runConfig_review_context, _runConfig_review4, _runConfig_review5, _aiConfig_commands_review, _aiConfig_commands, _analysisResult_issues;
|
|
298
|
-
const logger = getLogger();
|
|
299
|
-
// Gather additional context based on configuration with improved error handling
|
|
300
|
-
let logContext = '';
|
|
301
|
-
let diffContext = '';
|
|
302
|
-
let releaseNotesContext = '';
|
|
303
|
-
let issuesContext = '';
|
|
304
|
-
const contextErrors = [];
|
|
305
|
-
// Fetch commit history if enabled
|
|
306
|
-
if ((_runConfig_review = runConfig.review) === null || _runConfig_review === void 0 ? void 0 : _runConfig_review.includeCommitHistory) {
|
|
307
|
-
try {
|
|
308
|
-
logger.debug('Fetching recent commit history...');
|
|
309
|
-
const log = await create({
|
|
310
|
-
limit: runConfig.review.commitHistoryLimit
|
|
311
|
-
});
|
|
312
|
-
const logContent = await log.get();
|
|
313
|
-
if (logContent.trim()) {
|
|
314
|
-
logContext += `\n\n[Recent Commit History]\n${logContent}`;
|
|
315
|
-
logger.debug('Added commit history to context (%d characters)', logContent.length);
|
|
316
|
-
}
|
|
317
|
-
} catch (error) {
|
|
318
|
-
const errorMsg = `Failed to fetch commit history: ${error.message}`;
|
|
319
|
-
logger.warn(errorMsg);
|
|
320
|
-
contextErrors.push(errorMsg);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
// Fetch recent diffs if enabled
|
|
324
|
-
if ((_runConfig_review1 = runConfig.review) === null || _runConfig_review1 === void 0 ? void 0 : _runConfig_review1.includeRecentDiffs) {
|
|
325
|
-
try {
|
|
326
|
-
logger.debug('Fetching recent commit diffs...');
|
|
327
|
-
var _runConfig_excludedPatterns;
|
|
328
|
-
const basePatterns = (_runConfig_excludedPatterns = runConfig.excludedPatterns) !== null && _runConfig_excludedPatterns !== void 0 ? _runConfig_excludedPatterns : DEFAULT_EXCLUDED_PATTERNS;
|
|
329
|
-
const recentDiffs = await getRecentDiffsForReview({
|
|
330
|
-
limit: runConfig.review.diffHistoryLimit,
|
|
331
|
-
baseExcludedPatterns: basePatterns
|
|
332
|
-
});
|
|
333
|
-
diffContext += recentDiffs;
|
|
334
|
-
if (recentDiffs.trim()) {
|
|
335
|
-
logger.debug('Added recent diffs to context (%d characters)', recentDiffs.length);
|
|
336
|
-
}
|
|
337
|
-
} catch (error) {
|
|
338
|
-
const errorMsg = `Failed to fetch recent diffs: ${error.message}`;
|
|
339
|
-
logger.warn(errorMsg);
|
|
340
|
-
contextErrors.push(errorMsg);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
// Fetch release notes if enabled
|
|
344
|
-
if ((_runConfig_review2 = runConfig.review) === null || _runConfig_review2 === void 0 ? void 0 : _runConfig_review2.includeReleaseNotes) {
|
|
345
|
-
try {
|
|
346
|
-
logger.debug('Fetching recent release notes from GitHub...');
|
|
347
|
-
const releaseNotesContent = await getReleaseNotesContent({
|
|
348
|
-
limit: runConfig.review.releaseNotesLimit || 3
|
|
349
|
-
});
|
|
350
|
-
if (releaseNotesContent.trim()) {
|
|
351
|
-
releaseNotesContext += `\n\n[Recent Release Notes]\n${releaseNotesContent}`;
|
|
352
|
-
logger.debug('Added release notes to context (%d characters)', releaseNotesContent.length);
|
|
353
|
-
}
|
|
354
|
-
} catch (error) {
|
|
355
|
-
const errorMsg = `Failed to fetch release notes: ${error.message}`;
|
|
356
|
-
logger.warn(errorMsg);
|
|
357
|
-
contextErrors.push(errorMsg);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
// Fetch GitHub issues if enabled
|
|
361
|
-
if ((_runConfig_review3 = runConfig.review) === null || _runConfig_review3 === void 0 ? void 0 : _runConfig_review3.includeGithubIssues) {
|
|
362
|
-
try {
|
|
363
|
-
logger.debug('Fetching open GitHub issues...');
|
|
364
|
-
issuesContext = await getIssuesContent({
|
|
365
|
-
limit: runConfig.review.githubIssuesLimit || 20
|
|
366
|
-
});
|
|
367
|
-
if (issuesContext.trim()) {
|
|
368
|
-
logger.debug('Added GitHub issues to context (%d characters)', issuesContext.length);
|
|
369
|
-
}
|
|
370
|
-
} catch (error) {
|
|
371
|
-
const errorMsg = `Failed to fetch GitHub issues: ${error.message}`;
|
|
372
|
-
logger.warn(errorMsg);
|
|
373
|
-
contextErrors.push(errorMsg);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
// Report context gathering results
|
|
377
|
-
if (contextErrors.length > 0) {
|
|
378
|
-
var _runConfig_review6;
|
|
379
|
-
logger.warn(`Context gathering completed with ${contextErrors.length} error(s):`);
|
|
380
|
-
contextErrors.forEach((error)=>logger.warn(` - ${error}`));
|
|
381
|
-
// For critical operations, consider failing if too many context sources fail
|
|
382
|
-
const maxContextErrors = ((_runConfig_review6 = runConfig.review) === null || _runConfig_review6 === void 0 ? void 0 : _runConfig_review6.maxContextErrors) || contextErrors.length; // Default: allow all errors
|
|
383
|
-
if (contextErrors.length > maxContextErrors) {
|
|
384
|
-
throw new Error(`Too many context gathering errors (${contextErrors.length}), aborting review. Consider checking your configuration and network connectivity.`);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
// Analyze review note for issues using OpenAI
|
|
388
|
-
logger.info('REVIEW_ANALYSIS_STARTING: Analyzing review note for project issues | Source: review note | Purpose: Identify actionable issues');
|
|
389
|
-
logger.debug('Context summary:');
|
|
390
|
-
logger.debug(' - Review note: %d chars', reviewNote.length);
|
|
391
|
-
logger.debug(' - Log context: %d chars', logContext.length);
|
|
392
|
-
logger.debug(' - Diff context: %d chars', diffContext.length);
|
|
393
|
-
logger.debug(' - Release notes context: %d chars', releaseNotesContext.length);
|
|
394
|
-
logger.debug(' - Issues context: %d chars', issuesContext.length);
|
|
395
|
-
logger.debug(' - User context: %d chars', ((_runConfig_review4 = runConfig.review) === null || _runConfig_review4 === void 0 ? void 0 : (_runConfig_review_context = _runConfig_review4.context) === null || _runConfig_review_context === void 0 ? void 0 : _runConfig_review_context.length) || 0);
|
|
396
|
-
const promptConfig = {
|
|
397
|
-
overridePaths: runConfig.discoveredConfigDirs || [],
|
|
398
|
-
overrides: runConfig.overrides || false
|
|
399
|
-
};
|
|
400
|
-
// Create adapters for ai-service
|
|
401
|
-
const aiConfig = toAIConfig(runConfig);
|
|
402
|
-
const aiStorageAdapter = createStorageAdapter(outputDirectory);
|
|
403
|
-
const aiLogger = createLoggerAdapter(runConfig.dryRun || false);
|
|
404
|
-
const promptContent = {
|
|
405
|
-
notes: reviewNote
|
|
406
|
-
};
|
|
407
|
-
const promptContext = {
|
|
408
|
-
context: (_runConfig_review5 = runConfig.review) === null || _runConfig_review5 === void 0 ? void 0 : _runConfig_review5.context,
|
|
409
|
-
logContext,
|
|
410
|
-
diffContext,
|
|
411
|
-
releaseNotesContext,
|
|
412
|
-
issuesContext
|
|
413
|
-
};
|
|
414
|
-
const prompt = await createReviewPrompt(promptConfig, promptContent, promptContext);
|
|
415
|
-
const modelToUse = ((_aiConfig_commands = aiConfig.commands) === null || _aiConfig_commands === void 0 ? void 0 : (_aiConfig_commands_review = _aiConfig_commands.review) === null || _aiConfig_commands_review === void 0 ? void 0 : _aiConfig_commands_review.model) || aiConfig.model || 'gpt-4o-mini';
|
|
416
|
-
const request = Formatter.create({
|
|
417
|
-
logger
|
|
418
|
-
}).formatPrompt(modelToUse, prompt);
|
|
419
|
-
let analysisResult;
|
|
420
|
-
try {
|
|
421
|
-
var _aiConfig_commands_review1, _aiConfig_commands1, _rawAnalysisResult_issues;
|
|
422
|
-
const rawResult = await createCompletion(request.messages, {
|
|
423
|
-
model: modelToUse,
|
|
424
|
-
openaiReasoning: ((_aiConfig_commands1 = aiConfig.commands) === null || _aiConfig_commands1 === void 0 ? void 0 : (_aiConfig_commands_review1 = _aiConfig_commands1.review) === null || _aiConfig_commands_review1 === void 0 ? void 0 : _aiConfig_commands_review1.reasoning) || aiConfig.reasoning,
|
|
425
|
-
responseFormat: {
|
|
426
|
-
type: 'json_object'
|
|
427
|
-
},
|
|
428
|
-
debug: runConfig.debug,
|
|
429
|
-
debugRequestFile: getOutputPath(outputDirectory, getTimestampedRequestFilename('review-analysis')),
|
|
430
|
-
debugResponseFile: getOutputPath(outputDirectory, getTimestampedResponseFilename('review-analysis')),
|
|
431
|
-
storage: aiStorageAdapter,
|
|
432
|
-
logger: aiLogger
|
|
433
|
-
});
|
|
434
|
-
// Validate the API response before using it
|
|
435
|
-
const rawAnalysisResult = validateReviewResult(rawResult);
|
|
436
|
-
// Apply stop-context filtering to issues
|
|
437
|
-
analysisResult = {
|
|
438
|
-
...rawAnalysisResult,
|
|
439
|
-
summary: filterContent(rawAnalysisResult.summary, runConfig.stopContext).filtered,
|
|
440
|
-
issues: (_rawAnalysisResult_issues = rawAnalysisResult.issues) === null || _rawAnalysisResult_issues === void 0 ? void 0 : _rawAnalysisResult_issues.map((issue)=>({
|
|
441
|
-
...issue,
|
|
442
|
-
title: filterContent(issue.title, runConfig.stopContext).filtered,
|
|
443
|
-
description: filterContent(issue.description || '', runConfig.stopContext).filtered
|
|
444
|
-
}))
|
|
445
|
-
};
|
|
446
|
-
} catch (error) {
|
|
447
|
-
logger.error(`REVIEW_ANALYSIS_FAILED: Unable to analyze review note | Error: ${error.message} | Impact: Cannot identify issues`);
|
|
448
|
-
throw new Error(`Review analysis failed: ${error.message}`);
|
|
449
|
-
}
|
|
450
|
-
logger.info('REVIEW_ANALYSIS_COMPLETE: Review note analysis completed successfully | Status: completed | Next: Issue creation if enabled');
|
|
451
|
-
logger.debug('Analysis result summary: %s', analysisResult.summary);
|
|
452
|
-
logger.debug('Total issues found: %d', analysisResult.totalIssues);
|
|
453
|
-
logger.debug('Issues array length: %d', ((_analysisResult_issues = analysisResult.issues) === null || _analysisResult_issues === void 0 ? void 0 : _analysisResult_issues.length) || 0);
|
|
454
|
-
if (analysisResult.issues && analysisResult.issues.length > 0) {
|
|
455
|
-
analysisResult.issues.forEach((issue, index)=>{
|
|
456
|
-
logger.debug(' Issue %d: [%s] %s', index + 1, issue.priority, issue.title);
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
// Save timestamped copy of analysis result to output directory
|
|
460
|
-
try {
|
|
461
|
-
const reviewFilename = getTimestampedReviewFilename();
|
|
462
|
-
const reviewPath = getOutputPath(outputDirectory, reviewFilename);
|
|
463
|
-
// Format the analysis result as markdown
|
|
464
|
-
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()}*`;
|
|
465
|
-
await safeWriteFile(reviewPath, reviewContent);
|
|
466
|
-
logger.debug('Saved timestamped review analysis: %s', reviewPath);
|
|
467
|
-
} catch (error) {
|
|
468
|
-
logger.warn('Failed to save timestamped review analysis: %s', error.message);
|
|
469
|
-
// Don't fail the entire operation for this
|
|
470
|
-
}
|
|
471
|
-
return analysisResult;
|
|
472
|
-
};
|
|
473
|
-
const executeInternal = async (runConfig)=>{
|
|
474
|
-
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_review15, _runConfig_review16, _runConfig_review17, _runConfig_review18;
|
|
475
|
-
const logger = getLogger();
|
|
476
|
-
const isDryRun = runConfig.dryRun || false;
|
|
477
|
-
// Show configuration even in dry-run mode
|
|
478
|
-
logger.debug('Review context configuration:');
|
|
479
|
-
logger.debug(' Include commit history: %s', (_runConfig_review = runConfig.review) === null || _runConfig_review === void 0 ? void 0 : _runConfig_review.includeCommitHistory);
|
|
480
|
-
logger.debug(' Include recent diffs: %s', (_runConfig_review1 = runConfig.review) === null || _runConfig_review1 === void 0 ? void 0 : _runConfig_review1.includeRecentDiffs);
|
|
481
|
-
logger.debug(' Include release notes: %s', (_runConfig_review2 = runConfig.review) === null || _runConfig_review2 === void 0 ? void 0 : _runConfig_review2.includeReleaseNotes);
|
|
482
|
-
logger.debug(' Include GitHub issues: %s', (_runConfig_review3 = runConfig.review) === null || _runConfig_review3 === void 0 ? void 0 : _runConfig_review3.includeGithubIssues);
|
|
483
|
-
logger.debug(' Commit history limit: %d', (_runConfig_review4 = runConfig.review) === null || _runConfig_review4 === void 0 ? void 0 : _runConfig_review4.commitHistoryLimit);
|
|
484
|
-
logger.debug(' Diff history limit: %d', (_runConfig_review5 = runConfig.review) === null || _runConfig_review5 === void 0 ? void 0 : _runConfig_review5.diffHistoryLimit);
|
|
485
|
-
logger.debug(' Release notes limit: %d', (_runConfig_review6 = runConfig.review) === null || _runConfig_review6 === void 0 ? void 0 : _runConfig_review6.releaseNotesLimit);
|
|
486
|
-
logger.debug(' GitHub issues limit: %d', (_runConfig_review7 = runConfig.review) === null || _runConfig_review7 === void 0 ? void 0 : _runConfig_review7.githubIssuesLimit);
|
|
487
|
-
logger.debug(' Sendit mode (auto-create issues): %s', (_runConfig_review8 = runConfig.review) === null || _runConfig_review8 === void 0 ? void 0 : _runConfig_review8.sendit);
|
|
488
|
-
logger.debug(' File: %s', ((_runConfig_review9 = runConfig.review) === null || _runConfig_review9 === void 0 ? void 0 : _runConfig_review9.file) || 'not specified');
|
|
489
|
-
logger.debug(' Directory: %s', ((_runConfig_review10 = runConfig.review) === null || _runConfig_review10 === void 0 ? void 0 : _runConfig_review10.directory) || 'not specified');
|
|
490
|
-
if (isDryRun) {
|
|
491
|
-
var _runConfig_review19, _runConfig_review20, _runConfig_review21, _runConfig_review22, _runConfig_review23;
|
|
492
|
-
if ((_runConfig_review19 = runConfig.review) === null || _runConfig_review19 === void 0 ? void 0 : _runConfig_review19.file) {
|
|
493
|
-
logger.info('DRY RUN: Would read review note from file: %s', runConfig.review.file);
|
|
494
|
-
} else if ((_runConfig_review20 = runConfig.review) === null || _runConfig_review20 === void 0 ? void 0 : _runConfig_review20.directory) {
|
|
495
|
-
logger.info('DRY RUN: Would process review files in directory: %s', runConfig.review.directory);
|
|
496
|
-
logger.info('DRY RUN: Would first select which files to process, then analyze selected files');
|
|
497
|
-
} else if ((_runConfig_review21 = runConfig.review) === null || _runConfig_review21 === void 0 ? void 0 : _runConfig_review21.note) {
|
|
498
|
-
logger.info('DRY RUN: Would analyze provided note for review');
|
|
499
|
-
} else {
|
|
500
|
-
logger.info('DRY RUN: Would open editor to capture review note');
|
|
501
|
-
}
|
|
502
|
-
logger.info('DRY RUN: Would gather additional context based on configuration above');
|
|
503
|
-
logger.info('DRY RUN: Would analyze note and identify issues');
|
|
504
|
-
if ((_runConfig_review22 = runConfig.review) === null || _runConfig_review22 === void 0 ? void 0 : _runConfig_review22.sendit) {
|
|
505
|
-
logger.info('DRY RUN: Would automatically create GitHub issues (sendit mode enabled)');
|
|
506
|
-
} else {
|
|
507
|
-
logger.info('DRY RUN: Would prompt for confirmation before creating GitHub issues');
|
|
508
|
-
}
|
|
509
|
-
// Show what exclusion patterns would be used in dry-run mode
|
|
510
|
-
if ((_runConfig_review23 = runConfig.review) === null || _runConfig_review23 === void 0 ? void 0 : _runConfig_review23.includeRecentDiffs) {
|
|
511
|
-
var _runConfig_excludedPatterns;
|
|
512
|
-
const basePatterns = (_runConfig_excludedPatterns = runConfig.excludedPatterns) !== null && _runConfig_excludedPatterns !== void 0 ? _runConfig_excludedPatterns : DEFAULT_EXCLUDED_PATTERNS;
|
|
513
|
-
const reviewExcluded = getReviewExcludedPatterns(basePatterns);
|
|
514
|
-
logger.info('DRY RUN: Would use %d exclusion patterns for diff context', reviewExcluded.length);
|
|
515
|
-
logger.debug('DRY RUN: Sample exclusions: %s', reviewExcluded.slice(0, 15).join(', ') + (reviewExcluded.length > 15 ? '...' : ''));
|
|
516
|
-
}
|
|
517
|
-
return 'DRY RUN: Review command would analyze note, gather context, and create GitHub issues';
|
|
518
|
-
}
|
|
519
|
-
// Enhanced TTY check with proper error handling
|
|
520
|
-
const isInteractive = isTTYSafe();
|
|
521
|
-
if (!isInteractive && !((_runConfig_review11 = runConfig.review) === null || _runConfig_review11 === void 0 ? void 0 : _runConfig_review11.sendit)) {
|
|
522
|
-
logger.error('ā STDIN is piped but --sendit flag is not enabled');
|
|
523
|
-
logger.error(' Interactive prompts cannot be used when input is piped');
|
|
524
|
-
logger.error(' Solutions:');
|
|
525
|
-
logger.error(' ⢠Add --sendit flag to auto-create all issues');
|
|
526
|
-
logger.error(' ⢠Use terminal input instead of piping');
|
|
527
|
-
logger.error(' ⢠Example: echo "note" | kodrdriv review --sendit');
|
|
528
|
-
throw new ValidationError('Piped input requires --sendit flag for non-interactive operation');
|
|
529
|
-
}
|
|
530
|
-
// Get the review note from configuration
|
|
531
|
-
let reviewNote = (_runConfig_review12 = runConfig.review) === null || _runConfig_review12 === void 0 ? void 0 : _runConfig_review12.note;
|
|
532
|
-
let reviewFiles = [];
|
|
533
|
-
// Check if we should process a single file
|
|
534
|
-
if ((_runConfig_review13 = runConfig.review) === null || _runConfig_review13 === void 0 ? void 0 : _runConfig_review13.file) {
|
|
535
|
-
logger.info(`š Reading review note from file: ${runConfig.review.file}`);
|
|
536
|
-
reviewNote = await readReviewNoteFromFile(runConfig.review.file);
|
|
537
|
-
reviewFiles = [
|
|
538
|
-
runConfig.review.file
|
|
539
|
-
];
|
|
540
|
-
} else if ((_runConfig_review14 = runConfig.review) === null || _runConfig_review14 === void 0 ? void 0 : _runConfig_review14.directory) {
|
|
541
|
-
var _runConfig_review24;
|
|
542
|
-
logger.info(`š Processing review files in directory: ${runConfig.review.directory}`);
|
|
543
|
-
reviewFiles = await getReviewFilesInDirectory(runConfig.review.directory);
|
|
544
|
-
if (reviewFiles.length === 0) {
|
|
545
|
-
throw new ValidationError(`No review files found in directory: ${runConfig.review.directory}`);
|
|
546
|
-
}
|
|
547
|
-
logger.info(`š Found ${reviewFiles.length} files to process`);
|
|
548
|
-
// Set a dummy reviewNote for directory mode to satisfy validation
|
|
549
|
-
// The actual review notes will be read from each file during processing
|
|
550
|
-
reviewNote = `Processing ${reviewFiles.length} files from directory`;
|
|
551
|
-
// If not in sendit mode, explain the two-phase process
|
|
552
|
-
if (!((_runConfig_review24 = runConfig.review) === null || _runConfig_review24 === void 0 ? void 0 : _runConfig_review24.sendit)) {
|
|
553
|
-
logger.info(`š Interactive mode: You will first select which files to process, then they will be analyzed in order.`);
|
|
554
|
-
logger.info(`š Use --sendit to process all files automatically without confirmation.`);
|
|
555
|
-
}
|
|
556
|
-
} else if ((_runConfig_review15 = runConfig.review) === null || _runConfig_review15 === void 0 ? void 0 : _runConfig_review15.note) {
|
|
557
|
-
reviewNote = runConfig.review.note;
|
|
558
|
-
reviewFiles = [
|
|
559
|
-
'provided note'
|
|
560
|
-
];
|
|
561
|
-
} else {
|
|
562
|
-
// Open editor to capture review note
|
|
563
|
-
const editor = process.env.EDITOR || process.env.VISUAL || 'vi';
|
|
564
|
-
let tmpFilePath = null;
|
|
565
|
-
try {
|
|
566
|
-
var _runConfig_review25;
|
|
567
|
-
// Create secure temporary file
|
|
568
|
-
tmpFilePath = await createSecureTempFile();
|
|
569
|
-
// Pre-populate the file with a helpful header so users know what to do.
|
|
570
|
-
const templateContent = [
|
|
571
|
-
'# Kodrdriv Review Note',
|
|
572
|
-
'',
|
|
573
|
-
'# Please enter your review note below. Lines starting with "#" will be ignored.',
|
|
574
|
-
'# Save and close the editor when you are done.',
|
|
575
|
-
'',
|
|
576
|
-
''
|
|
577
|
-
].join('\n');
|
|
578
|
-
await safeWriteFile(tmpFilePath, templateContent);
|
|
579
|
-
logger.info(`No review note provided ā opening ${editor} to capture input...`);
|
|
580
|
-
// Open the editor with optional timeout protection
|
|
581
|
-
const editorTimeout = (_runConfig_review25 = runConfig.review) === null || _runConfig_review25 === void 0 ? void 0 : _runConfig_review25.editorTimeout; // No default timeout - let user take their time
|
|
582
|
-
await openEditorWithTimeout(editor, tmpFilePath, editorTimeout);
|
|
583
|
-
// Read the file back in, stripping comment lines and whitespace.
|
|
584
|
-
const fileContent = (await fs.readFile(tmpFilePath, 'utf8')).split('\n').filter((line)=>!line.trim().startsWith('#')).join('\n').trim();
|
|
585
|
-
if (!fileContent) {
|
|
586
|
-
throw new ValidationError('Review note is empty ā aborting. Provide a note as an argument, via STDIN, or through the editor.');
|
|
587
|
-
}
|
|
588
|
-
reviewNote = fileContent;
|
|
589
|
-
// If the original runConfig.review object exists, update it so downstream code has the note.
|
|
590
|
-
if (runConfig.review) {
|
|
591
|
-
runConfig.review.note = reviewNote;
|
|
592
|
-
}
|
|
593
|
-
} catch (error) {
|
|
594
|
-
logger.error(`Failed to capture review note via editor: ${error.message}`);
|
|
595
|
-
throw error;
|
|
596
|
-
} finally{
|
|
597
|
-
// Always clean up the temp file
|
|
598
|
-
if (tmpFilePath) {
|
|
599
|
-
await cleanupTempFile(tmpFilePath);
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
reviewFiles = [
|
|
603
|
-
'editor input'
|
|
604
|
-
];
|
|
605
|
-
}
|
|
606
|
-
if (!reviewNote || !reviewNote.trim()) {
|
|
607
|
-
throw new ValidationError('No review note provided or captured');
|
|
608
|
-
}
|
|
609
|
-
logger.info('š Starting review analysis...');
|
|
610
|
-
logger.debug('Review note: %s', reviewNote);
|
|
611
|
-
logger.debug('Review note length: %d characters', reviewNote.length);
|
|
612
|
-
const outputDirectory = runConfig.outputDirectory || DEFAULT_OUTPUT_DIRECTORY;
|
|
613
|
-
const storage = createStorage();
|
|
614
|
-
await storage.ensureDirectory(outputDirectory);
|
|
615
|
-
// Save timestamped copy of review notes to output directory
|
|
616
|
-
try {
|
|
617
|
-
const reviewNotesFilename = getTimestampedReviewNotesFilename();
|
|
618
|
-
const reviewNotesPath = getOutputPath(outputDirectory, reviewNotesFilename);
|
|
619
|
-
const reviewNotesContent = `# Review Notes\n\n${reviewNote}\n\n`;
|
|
620
|
-
await safeWriteFile(reviewNotesPath, reviewNotesContent);
|
|
621
|
-
logger.debug('Saved timestamped review notes: %s', reviewNotesPath);
|
|
622
|
-
} catch (error) {
|
|
623
|
-
logger.warn('Failed to save review notes: %s', error.message);
|
|
624
|
-
}
|
|
625
|
-
// Phase 1: File selection (only for directory mode)
|
|
626
|
-
let selectedFiles;
|
|
627
|
-
if ((_runConfig_review16 = runConfig.review) === null || _runConfig_review16 === void 0 ? void 0 : _runConfig_review16.directory) {
|
|
628
|
-
var _runConfig_review26;
|
|
629
|
-
selectedFiles = await selectFilesForProcessing(reviewFiles, ((_runConfig_review26 = runConfig.review) === null || _runConfig_review26 === void 0 ? void 0 : _runConfig_review26.sendit) || false);
|
|
630
|
-
} else {
|
|
631
|
-
// For single note mode, just use the note directly
|
|
632
|
-
selectedFiles = [
|
|
633
|
-
'single note'
|
|
634
|
-
];
|
|
635
|
-
}
|
|
636
|
-
// Phase 2: Process selected files in order
|
|
637
|
-
logger.info(`\nš Starting analysis phase...`);
|
|
638
|
-
const results = [];
|
|
639
|
-
const processedFiles = [];
|
|
640
|
-
if ((_runConfig_review17 = runConfig.review) === null || _runConfig_review17 === void 0 ? void 0 : _runConfig_review17.directory) {
|
|
641
|
-
// Directory mode: process each selected file
|
|
642
|
-
for(let i = 0; i < selectedFiles.length; i++){
|
|
643
|
-
const filePath = selectedFiles[i];
|
|
644
|
-
try {
|
|
645
|
-
logger.info(`š Processing file ${i + 1}/${selectedFiles.length}: ${filePath}`);
|
|
646
|
-
const fileNote = await readReviewNoteFromFile(filePath);
|
|
647
|
-
const fileResult = await processSingleReview(fileNote, runConfig, outputDirectory);
|
|
648
|
-
results.push(fileResult);
|
|
649
|
-
processedFiles.push(filePath);
|
|
650
|
-
} catch (error) {
|
|
651
|
-
// Check if this is a critical error that should be propagated
|
|
652
|
-
if (error.message.includes('Too many context gathering errors')) {
|
|
653
|
-
throw error; // Propagate critical context errors
|
|
654
|
-
}
|
|
655
|
-
logger.warn(`Failed to process file ${filePath}: ${error.message}`);
|
|
656
|
-
// Continue with other files for non-critical errors
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
} else {
|
|
660
|
-
// Single note mode: process the note directly
|
|
661
|
-
try {
|
|
662
|
-
logger.info(`š Processing single review note`);
|
|
663
|
-
const fileResult = await processSingleReview(reviewNote, runConfig, outputDirectory);
|
|
664
|
-
results.push(fileResult);
|
|
665
|
-
processedFiles.push('single note');
|
|
666
|
-
} catch (error) {
|
|
667
|
-
logger.warn(`Failed to process review note: ${error.message}`);
|
|
668
|
-
throw error; // Re-throw for single note mode since there's only one item
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
if (results.length === 0) {
|
|
672
|
-
throw new ValidationError('No files were processed successfully');
|
|
673
|
-
}
|
|
674
|
-
// Combine results if we processed multiple files
|
|
675
|
-
let analysisResult;
|
|
676
|
-
if (results.length === 1) {
|
|
677
|
-
analysisResult = results[0];
|
|
678
|
-
} else {
|
|
679
|
-
logger.info(`ā
Successfully processed ${results.length} review files`);
|
|
680
|
-
// Create a combined summary
|
|
681
|
-
const totalIssues = results.reduce((sum, result)=>sum + result.totalIssues, 0);
|
|
682
|
-
const allIssues = results.flatMap((result)=>result.issues || []);
|
|
683
|
-
analysisResult = {
|
|
684
|
-
summary: `Combined analysis of ${results.length} review files. Total issues found: ${totalIssues}`,
|
|
685
|
-
totalIssues,
|
|
686
|
-
issues: allIssues
|
|
687
|
-
};
|
|
688
|
-
// Save combined results
|
|
689
|
-
try {
|
|
690
|
-
const combinedFilename = getTimestampedReviewFilename();
|
|
691
|
-
const combinedPath = getOutputPath(outputDirectory, combinedFilename);
|
|
692
|
-
const combinedContent = `# Combined Review Analysis Result\n\n` + `## Summary\n${analysisResult.summary}\n\n` + `## Total Issues Found\n${totalIssues}\n\n` + `## Files Processed\n${processedFiles.join('\n')}\n\n` + `## Issues\n\n${JSON.stringify(allIssues, null, 2)}\n\n` + `---\n\n*Combined analysis completed at ${new Date().toISOString()}*`;
|
|
693
|
-
await safeWriteFile(combinedPath, combinedContent);
|
|
694
|
-
logger.debug('Saved combined review analysis: %s', combinedPath);
|
|
695
|
-
} catch (error) {
|
|
696
|
-
logger.warn('Failed to save combined review analysis: %s', error.message);
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
// Handle GitHub issue creation using the issues module
|
|
700
|
-
const senditMode = ((_runConfig_review18 = runConfig.review) === null || _runConfig_review18 === void 0 ? void 0 : _runConfig_review18.sendit) || false;
|
|
701
|
-
return await handleIssueCreation(analysisResult, senditMode);
|
|
702
|
-
};
|
|
703
|
-
const execute = async (runConfig)=>{
|
|
704
|
-
try {
|
|
705
|
-
return await executeInternal(runConfig);
|
|
706
|
-
} catch (error) {
|
|
707
|
-
const logger = getLogger();
|
|
708
|
-
if (error instanceof ValidationError) {
|
|
709
|
-
logger.error(`review failed: ${error.message}`);
|
|
710
|
-
throw error;
|
|
711
|
-
}
|
|
712
|
-
if (error instanceof FileOperationError) {
|
|
713
|
-
logger.error(`review failed: ${error.message}`);
|
|
714
|
-
if (error.cause && typeof error.cause === 'object' && 'message' in error.cause) {
|
|
715
|
-
logger.debug(`Caused by: ${error.cause.message}`);
|
|
716
|
-
}
|
|
717
|
-
throw error;
|
|
718
|
-
}
|
|
719
|
-
if (error instanceof CommandError) {
|
|
720
|
-
logger.error(`review failed: ${error.message}`);
|
|
721
|
-
if (error.cause && typeof error.cause === 'object' && 'message' in error.cause) {
|
|
722
|
-
logger.debug(`Caused by: ${error.cause.message}`);
|
|
723
|
-
}
|
|
724
|
-
throw error;
|
|
725
|
-
}
|
|
726
|
-
// Unexpected errors
|
|
727
|
-
logger.error(`review encountered unexpected error: ${error.message}`);
|
|
728
|
-
throw error;
|
|
729
|
-
}
|
|
730
|
-
};
|
|
731
|
-
|
|
732
|
-
export { execute };
|
|
733
|
-
//# sourceMappingURL=review.js.map
|