cc-reviewer 2.0.0 → 3.0.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/README.md +15 -35
- package/commands/{codex-xhigh.md → codex-xhigh-review.md} +3 -3
- package/commands/{multi.md → multi-review.md} +1 -1
- package/dist/adapters/base.d.ts +3 -41
- package/dist/adapters/codex.d.ts +4 -6
- package/dist/adapters/codex.js +68 -322
- package/dist/adapters/gemini.d.ts +4 -6
- package/dist/adapters/gemini.js +46 -276
- package/dist/commands.d.ts +2 -3
- package/dist/commands.js +33 -11
- package/dist/decoders/codex.d.ts +11 -0
- package/dist/decoders/codex.js +29 -0
- package/dist/handoff.d.ts +2 -17
- package/dist/handoff.js +3 -87
- package/dist/index.d.ts +1 -1
- package/dist/index.js +17 -22
- package/dist/schema.d.ts +6 -195
- package/dist/schema.js +1 -190
- package/dist/tools/feedback.d.ts +5 -6
- package/dist/tools/feedback.js +63 -338
- package/dist/types.d.ts +0 -1
- package/package.json +2 -3
- package/commands/ask-codex.md +0 -36
- package/commands/ask-gemini.md +0 -35
- package/commands/ask-multi.md +0 -39
- package/dist/setup.d.ts +0 -7
- package/dist/setup.js +0 -27
- package/dist/tools/peer.d.ts +0 -194
- package/dist/tools/peer.js +0 -347
- /package/commands/{codex.md → codex-review.md} +0 -0
- /package/commands/{gemini.md → gemini-review.md} +0 -0
package/dist/adapters/codex.js
CHANGED
|
@@ -2,27 +2,23 @@
|
|
|
2
2
|
* Codex CLI Adapter
|
|
3
3
|
*
|
|
4
4
|
* Implements the ReviewerAdapter interface for OpenAI's Codex CLI.
|
|
5
|
-
*
|
|
5
|
+
* Returns raw text — no JSON parsing or schema enforcement.
|
|
6
|
+
* CC handles interpretation of the reviewer's response.
|
|
6
7
|
*/
|
|
7
8
|
import { spawn } from 'child_process';
|
|
8
|
-
import { existsSync
|
|
9
|
-
import { tmpdir } from 'os';
|
|
10
|
-
import { join } from 'path';
|
|
9
|
+
import { existsSync } from 'fs';
|
|
11
10
|
import { registerAdapter, } from './base.js';
|
|
12
|
-
import { parseReviewOutput, parseLegacyMarkdownOutput, getReviewOutputJsonSchema, getPeerOutputJsonSchema, parsePeerOutput, isSubstantiveReview } from '../schema.js';
|
|
13
11
|
import { CliExecutor } from '../executor.js';
|
|
14
12
|
import { CodexEventDecoder } from '../decoders/index.js';
|
|
15
|
-
import { buildSimpleHandoff, buildHandoffPrompt,
|
|
13
|
+
import { buildSimpleHandoff, buildHandoffPrompt, selectRole, } from '../handoff.js';
|
|
16
14
|
// =============================================================================
|
|
17
15
|
// CONFIGURATION
|
|
18
16
|
// =============================================================================
|
|
19
|
-
const
|
|
20
|
-
high: 180_000, // 3 min —
|
|
21
|
-
xhigh: 300_000, // 5 min — xhigh
|
|
17
|
+
const INACTIVITY_TIMEOUT_MS = {
|
|
18
|
+
high: 180_000, // 3 min — covers reasoning gaps between tool use bursts
|
|
19
|
+
xhigh: 300_000, // 5 min — xhigh has longer reasoning phases
|
|
22
20
|
};
|
|
23
|
-
const STREAMING_TIMEOUT_MS = 90_000; // 90s — if events stop mid-stream
|
|
24
21
|
const MAX_TIMEOUT_MS = 3_600_000; // 60 min absolute max
|
|
25
|
-
const MAX_RETRIES = 2;
|
|
26
22
|
const MAX_BUFFER_SIZE = 1024 * 1024; // 1MB max buffer
|
|
27
23
|
// =============================================================================
|
|
28
24
|
// CODEX ADAPTER
|
|
@@ -36,7 +32,7 @@ export class CodexAdapter {
|
|
|
36
32
|
strengths: ['correctness', 'performance', 'security', 'testing'],
|
|
37
33
|
weaknesses: ['documentation'],
|
|
38
34
|
hasFilesystemAccess: true,
|
|
39
|
-
supportsStructuredOutput:
|
|
35
|
+
supportsStructuredOutput: false,
|
|
40
36
|
maxContextTokens: 128000,
|
|
41
37
|
reasoningLevels: ['high', 'xhigh'],
|
|
42
38
|
};
|
|
@@ -46,282 +42,45 @@ export class CodexAdapter {
|
|
|
46
42
|
const proc = spawn('codex', ['--version'], {
|
|
47
43
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
48
44
|
});
|
|
49
|
-
proc.on('close', (code) =>
|
|
50
|
-
|
|
51
|
-
});
|
|
52
|
-
proc.on('error', () => {
|
|
53
|
-
resolve(false);
|
|
54
|
-
});
|
|
55
|
-
// Timeout after 5s
|
|
56
|
-
setTimeout(() => {
|
|
57
|
-
proc.kill();
|
|
58
|
-
resolve(false);
|
|
59
|
-
}, 5000);
|
|
45
|
+
proc.on('close', (code) => resolve(code === 0));
|
|
46
|
+
proc.on('error', () => resolve(false));
|
|
47
|
+
setTimeout(() => { proc.kill(); resolve(false); }, 5000);
|
|
60
48
|
});
|
|
61
49
|
}
|
|
62
50
|
async runReview(request) {
|
|
63
51
|
const startTime = Date.now();
|
|
64
|
-
// Validate working directory
|
|
65
52
|
if (!existsSync(request.workingDir)) {
|
|
66
53
|
return {
|
|
67
54
|
success: false,
|
|
68
|
-
error: {
|
|
69
|
-
type: 'cli_error',
|
|
70
|
-
message: `Working directory does not exist: ${request.workingDir}`,
|
|
71
|
-
},
|
|
55
|
+
error: { type: 'cli_error', message: `Working directory does not exist: ${request.workingDir}` },
|
|
72
56
|
suggestion: 'Check that the working directory path is correct',
|
|
73
57
|
executionTimeMs: Date.now() - startTime,
|
|
74
58
|
};
|
|
75
59
|
}
|
|
76
|
-
return this.runWithRetry(request, 0, startTime);
|
|
77
|
-
}
|
|
78
|
-
async runWithRetry(request, attempt, startTime, previousError, previousOutput) {
|
|
79
60
|
try {
|
|
80
|
-
// Build the prompt using handoff protocol
|
|
81
61
|
const handoff = buildSimpleHandoff(request.workingDir, request.ccOutput, request.analyzedFiles, request.focusAreas, request.customPrompt);
|
|
82
|
-
// Select role based on focus areas
|
|
83
62
|
const role = selectRole(request.focusAreas);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
let prompt = buildHandoffPrompt({
|
|
87
|
-
handoff,
|
|
88
|
-
role,
|
|
89
|
-
outputFormat: 'schema-enforced',
|
|
90
|
-
});
|
|
91
|
-
// Add retry context if this is a retry attempt
|
|
92
|
-
if (attempt > 0) {
|
|
93
|
-
prompt += `\n\n---\n\n# RETRY ATTEMPT ${attempt + 1}\n\n` +
|
|
94
|
-
`Previous output had issues: ${previousError}\n` +
|
|
95
|
-
`Please fix these issues and provide valid JSON output.\n` +
|
|
96
|
-
(previousOutput ? `\nPrevious output (for reference):\n${previousOutput.slice(0, 500)}...` : '');
|
|
97
|
-
}
|
|
98
|
-
// Run the CLI
|
|
99
|
-
const result = await this.runCli(prompt, request.workingDir, request.reasoningEffort || 'high', getReviewOutputJsonSchema, request.serviceTier);
|
|
100
|
-
// Handle CLI errors
|
|
101
|
-
if (result.exitCode !== 0) {
|
|
102
|
-
const error = this.categorizeError(result.stderr);
|
|
103
|
-
return {
|
|
104
|
-
success: false,
|
|
105
|
-
error,
|
|
106
|
-
suggestion: this.getSuggestion(error),
|
|
107
|
-
rawOutput: result.stderr,
|
|
108
|
-
executionTimeMs: Date.now() - startTime,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
// Handle buffer truncation
|
|
112
|
-
if (result.truncated) {
|
|
113
|
-
return {
|
|
114
|
-
success: false,
|
|
115
|
-
error: {
|
|
116
|
-
type: 'cli_error',
|
|
117
|
-
message: 'Output exceeded maximum buffer size (1MB) and was truncated',
|
|
118
|
-
},
|
|
119
|
-
suggestion: 'Try reviewing a smaller scope with --focus',
|
|
120
|
-
executionTimeMs: Date.now() - startTime,
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
// Parse the output
|
|
124
|
-
let output = parseReviewOutput(result.stdout);
|
|
125
|
-
let usedFallback = false;
|
|
126
|
-
// If JSON parsing fails, try legacy markdown
|
|
127
|
-
if (!output) {
|
|
128
|
-
output = parseLegacyMarkdownOutput(result.stdout, 'codex');
|
|
129
|
-
usedFallback = true;
|
|
130
|
-
}
|
|
131
|
-
// If no valid output, retry or fail
|
|
132
|
-
if (!output) {
|
|
133
|
-
if (attempt < MAX_RETRIES) {
|
|
134
|
-
return this.runWithRetry(request, attempt + 1, startTime, 'Output did not match expected JSON schema', result.stdout);
|
|
135
|
-
}
|
|
136
|
-
return {
|
|
137
|
-
success: false,
|
|
138
|
-
error: {
|
|
139
|
-
type: 'parse_error',
|
|
140
|
-
message: 'Failed to parse reviewer output after retries',
|
|
141
|
-
details: { rawOutput: result.stdout.slice(0, 1000) },
|
|
142
|
-
},
|
|
143
|
-
suggestion: 'The model may not be following the output format. Try a different focus area.',
|
|
144
|
-
rawOutput: result.stdout,
|
|
145
|
-
executionTimeMs: Date.now() - startTime,
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
// Check for empty/minimal output — centralized substance check
|
|
149
|
-
if (!isSubstantiveReview(output)) {
|
|
150
|
-
if (attempt < MAX_RETRIES) {
|
|
151
|
-
console.error(`[codex] Received empty output, retrying...`);
|
|
152
|
-
return this.runWithRetry(request, attempt + 1, startTime, usedFallback
|
|
153
|
-
? 'Received markdown output instead of JSON. Please provide valid JSON output.'
|
|
154
|
-
: 'Output contained no substantive review content. Please provide findings or analysis.', result.stdout);
|
|
155
|
-
}
|
|
156
|
-
return {
|
|
157
|
-
success: false,
|
|
158
|
-
error: {
|
|
159
|
-
type: 'parse_error',
|
|
160
|
-
message: 'Reviewer returned empty output after retries',
|
|
161
|
-
details: { rawOutput: result.stdout.slice(0, 1000) },
|
|
162
|
-
},
|
|
163
|
-
suggestion: 'The model returned no substantive review. Try a different focus area.',
|
|
164
|
-
rawOutput: result.stdout,
|
|
165
|
-
executionTimeMs: Date.now() - startTime,
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
return {
|
|
169
|
-
success: true,
|
|
170
|
-
output,
|
|
171
|
-
rawOutput: result.stdout,
|
|
172
|
-
executionTimeMs: Date.now() - startTime,
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
catch (error) {
|
|
176
|
-
const err = error;
|
|
177
|
-
if (err.code === 'ENOENT') {
|
|
178
|
-
return {
|
|
179
|
-
success: false,
|
|
180
|
-
error: {
|
|
181
|
-
type: 'cli_not_found',
|
|
182
|
-
message: 'Codex CLI not found',
|
|
183
|
-
},
|
|
184
|
-
suggestion: 'Install with: npm install -g @openai/codex',
|
|
185
|
-
executionTimeMs: Date.now() - startTime,
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
if (err.message === 'TIMEOUT') {
|
|
189
|
-
return {
|
|
190
|
-
success: false,
|
|
191
|
-
error: {
|
|
192
|
-
type: 'timeout',
|
|
193
|
-
message: 'No output for 2 minutes - process may be hung',
|
|
194
|
-
},
|
|
195
|
-
suggestion: 'Try a smaller scope or use --focus',
|
|
196
|
-
executionTimeMs: Date.now() - startTime,
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
if (err.message === 'MAX_TIMEOUT') {
|
|
200
|
-
return {
|
|
201
|
-
success: false,
|
|
202
|
-
error: {
|
|
203
|
-
type: 'timeout',
|
|
204
|
-
message: 'Task exceeded 60 minute maximum',
|
|
205
|
-
},
|
|
206
|
-
suggestion: 'Try a smaller scope',
|
|
207
|
-
executionTimeMs: Date.now() - startTime,
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
return {
|
|
211
|
-
success: false,
|
|
212
|
-
error: {
|
|
213
|
-
type: 'cli_error',
|
|
214
|
-
message: err.message,
|
|
215
|
-
},
|
|
216
|
-
executionTimeMs: Date.now() - startTime,
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
async runPeerRequest(request) {
|
|
221
|
-
const startTime = Date.now();
|
|
222
|
-
if (!existsSync(request.workingDir)) {
|
|
223
|
-
return {
|
|
224
|
-
success: false,
|
|
225
|
-
error: {
|
|
226
|
-
type: 'cli_error',
|
|
227
|
-
message: `Working directory does not exist: ${request.workingDir}`,
|
|
228
|
-
},
|
|
229
|
-
suggestion: 'Check that the working directory path is correct',
|
|
230
|
-
executionTimeMs: Date.now() - startTime,
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
return this.runPeerWithRetry(request, 0, startTime);
|
|
234
|
-
}
|
|
235
|
-
async runPeerWithRetry(request, attempt, startTime, previousError, previousOutput) {
|
|
236
|
-
try {
|
|
237
|
-
// Use 'schema-enforced' since Codex gets --output-schema flag (avoids redundant inline JSON template)
|
|
238
|
-
let prompt = buildPeerPrompt({
|
|
239
|
-
workingDir: request.workingDir,
|
|
240
|
-
prompt: request.prompt,
|
|
241
|
-
taskType: request.taskType,
|
|
242
|
-
relevantFiles: request.relevantFiles,
|
|
243
|
-
context: request.context,
|
|
244
|
-
focusAreas: request.focusAreas,
|
|
245
|
-
customInstructions: request.customPrompt,
|
|
246
|
-
outputFormat: 'schema-enforced',
|
|
247
|
-
});
|
|
248
|
-
if (attempt > 0) {
|
|
249
|
-
prompt += `\n\n---\n\n# RETRY ATTEMPT ${attempt + 1}\n\n` +
|
|
250
|
-
`Previous output had issues: ${previousError}\n` +
|
|
251
|
-
`Please fix these issues and provide valid JSON output.\n` +
|
|
252
|
-
(previousOutput ? `\nPrevious output (for reference):\n${previousOutput.slice(0, 500)}...` : '');
|
|
253
|
-
}
|
|
254
|
-
const result = await this.runCli(prompt, request.workingDir, request.reasoningEffort || 'high', getPeerOutputJsonSchema, request.serviceTier);
|
|
63
|
+
const prompt = buildHandoffPrompt({ handoff, role });
|
|
64
|
+
const result = await this.runCli(prompt, request.workingDir, request.reasoningEffort || 'high', request.serviceTier);
|
|
255
65
|
if (result.exitCode !== 0) {
|
|
256
66
|
const error = this.categorizeError(result.stderr);
|
|
257
|
-
return {
|
|
258
|
-
success: false,
|
|
259
|
-
error,
|
|
260
|
-
suggestion: this.getSuggestion(error),
|
|
261
|
-
rawOutput: result.stderr,
|
|
262
|
-
executionTimeMs: Date.now() - startTime,
|
|
263
|
-
};
|
|
67
|
+
return { success: false, error, suggestion: this.getSuggestion(error), executionTimeMs: Date.now() - startTime };
|
|
264
68
|
}
|
|
265
|
-
if (result.
|
|
69
|
+
if (!result.stdout.trim()) {
|
|
266
70
|
return {
|
|
267
71
|
success: false,
|
|
268
|
-
error: { type: 'cli_error', message: '
|
|
269
|
-
suggestion: 'Try
|
|
72
|
+
error: { type: 'cli_error', message: 'Codex returned empty response' },
|
|
73
|
+
suggestion: 'Try again or use /gemini-review instead',
|
|
270
74
|
executionTimeMs: Date.now() - startTime,
|
|
271
75
|
};
|
|
272
76
|
}
|
|
273
|
-
|
|
274
|
-
if (!output) {
|
|
275
|
-
if (attempt < MAX_RETRIES) {
|
|
276
|
-
return this.runPeerWithRetry(request, attempt + 1, startTime, 'Output did not match expected JSON schema', result.stdout);
|
|
277
|
-
}
|
|
278
|
-
return {
|
|
279
|
-
success: false,
|
|
280
|
-
error: { type: 'parse_error', message: 'Failed to parse peer output after retries',
|
|
281
|
-
details: { rawOutput: result.stdout.slice(0, 1000) } },
|
|
282
|
-
suggestion: 'The model may not be following the output format.',
|
|
283
|
-
rawOutput: result.stdout,
|
|
284
|
-
executionTimeMs: Date.now() - startTime,
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
return {
|
|
288
|
-
success: true,
|
|
289
|
-
output,
|
|
290
|
-
rawOutput: result.stdout,
|
|
291
|
-
executionTimeMs: Date.now() - startTime,
|
|
292
|
-
};
|
|
77
|
+
return { success: true, output: result.stdout, executionTimeMs: Date.now() - startTime };
|
|
293
78
|
}
|
|
294
79
|
catch (error) {
|
|
295
|
-
|
|
296
|
-
if (err.code === 'ENOENT') {
|
|
297
|
-
return { success: false, error: { type: 'cli_not_found', message: 'Codex CLI not found' },
|
|
298
|
-
suggestion: 'Install with: npm install -g @openai/codex', executionTimeMs: Date.now() - startTime };
|
|
299
|
-
}
|
|
300
|
-
if (err.message === 'TIMEOUT') {
|
|
301
|
-
return { success: false, error: { type: 'timeout', message: 'No output for 2 minutes' },
|
|
302
|
-
suggestion: 'Try a simpler request', executionTimeMs: Date.now() - startTime };
|
|
303
|
-
}
|
|
304
|
-
if (err.message === 'MAX_TIMEOUT') {
|
|
305
|
-
return { success: false, error: { type: 'timeout', message: 'Task exceeded 60 minute maximum' },
|
|
306
|
-
suggestion: 'Try a smaller scope', executionTimeMs: Date.now() - startTime };
|
|
307
|
-
}
|
|
308
|
-
return { success: false, error: { type: 'cli_error', message: err.message },
|
|
309
|
-
executionTimeMs: Date.now() - startTime };
|
|
80
|
+
return this.handleException(error, startTime);
|
|
310
81
|
}
|
|
311
82
|
}
|
|
312
|
-
async runCli(prompt, workingDir, reasoningEffort,
|
|
313
|
-
// Create temp schema file for structured output
|
|
314
|
-
let schemaFile = null;
|
|
315
|
-
try {
|
|
316
|
-
const tempDir = mkdtempSync(join(tmpdir(), 'codex-schema-'));
|
|
317
|
-
schemaFile = join(tempDir, 'schema.json');
|
|
318
|
-
const schema = schemaGetter();
|
|
319
|
-
writeFileSync(schemaFile, JSON.stringify(schema, null, 2), 'utf-8');
|
|
320
|
-
}
|
|
321
|
-
catch (err) {
|
|
322
|
-
console.error('[codex] Warning: Failed to create schema file:', err);
|
|
323
|
-
schemaFile = null;
|
|
324
|
-
}
|
|
83
|
+
async runCli(prompt, workingDir, reasoningEffort, serviceTier) {
|
|
325
84
|
const args = [
|
|
326
85
|
'exec',
|
|
327
86
|
'--json', // JSONL streaming events
|
|
@@ -331,19 +90,15 @@ export class CodexAdapter {
|
|
|
331
90
|
'--dangerously-bypass-approvals-and-sandbox',
|
|
332
91
|
'--skip-git-repo-check',
|
|
333
92
|
'-C', workingDir,
|
|
93
|
+
'-', // Read prompt from stdin
|
|
334
94
|
];
|
|
335
95
|
if (serviceTier && serviceTier !== 'default') {
|
|
336
96
|
args.push('-c', `service_tier=${serviceTier}`);
|
|
337
97
|
}
|
|
338
|
-
if (schemaFile) {
|
|
339
|
-
args.push('--output-schema', schemaFile);
|
|
340
|
-
}
|
|
341
|
-
args.push('-'); // Read prompt from stdin
|
|
342
98
|
const decoder = new CodexEventDecoder();
|
|
343
99
|
const cliStartTime = Date.now();
|
|
344
|
-
let firstEventReceived = false;
|
|
345
100
|
const tierLabel = serviceTier && serviceTier !== 'default' ? ` [${serviceTier}]` : '';
|
|
346
|
-
console.error(`[codex] Running
|
|
101
|
+
console.error(`[codex] Running with ${reasoningEffort} reasoning${tierLabel}...`);
|
|
347
102
|
decoder.onProgress = (eventType, detail) => {
|
|
348
103
|
const elapsed = Math.round((Date.now() - cliStartTime) / 1000);
|
|
349
104
|
const detailStr = detail ? ` — ${detail}` : '';
|
|
@@ -354,79 +109,70 @@ export class CodexAdapter {
|
|
|
354
109
|
args,
|
|
355
110
|
cwd: workingDir,
|
|
356
111
|
stdin: prompt,
|
|
357
|
-
inactivityTimeoutMs:
|
|
112
|
+
inactivityTimeoutMs: INACTIVITY_TIMEOUT_MS[reasoningEffort] || INACTIVITY_TIMEOUT_MS.high,
|
|
358
113
|
maxTimeoutMs: MAX_TIMEOUT_MS,
|
|
359
114
|
maxBufferSize: MAX_BUFFER_SIZE,
|
|
360
115
|
onLine: (line) => {
|
|
361
116
|
decoder.processLine(line);
|
|
362
|
-
// Phase transition: tighten timeout after first event
|
|
363
|
-
if (!firstEventReceived) {
|
|
364
|
-
firstEventReceived = true;
|
|
365
|
-
executor.setInactivityTimeout(STREAMING_TIMEOUT_MS);
|
|
366
|
-
}
|
|
367
117
|
},
|
|
368
118
|
});
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
stderr: result.stderr,
|
|
377
|
-
exitCode: result.exitCode,
|
|
378
|
-
truncated: result.truncated,
|
|
379
|
-
};
|
|
119
|
+
const result = await executor.run();
|
|
120
|
+
const elapsed = Math.round((Date.now() - cliStartTime) / 1000);
|
|
121
|
+
console.error(`[codex] ✓ complete (${elapsed}s)`);
|
|
122
|
+
// Check for errors captured from JSONL events
|
|
123
|
+
const decoderError = decoder.getError();
|
|
124
|
+
if (decoderError) {
|
|
125
|
+
return { stdout: '', stderr: decoderError, exitCode: 1, truncated: false };
|
|
380
126
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
127
|
+
const finalResponse = decoder.getFinalResponse();
|
|
128
|
+
if (!finalResponse && decoder.hasNoOutput()) {
|
|
129
|
+
return { stdout: '', stderr: 'No response from Codex — possible rate limit or model rejection', exitCode: 1, truncated: false };
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
stdout: finalResponse || result.rawStdout,
|
|
133
|
+
stderr: result.stderr,
|
|
134
|
+
exitCode: result.exitCode,
|
|
135
|
+
truncated: result.truncated,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
handleException(error, startTime) {
|
|
139
|
+
const err = error;
|
|
140
|
+
if (err.code === 'ENOENT') {
|
|
141
|
+
return { success: false, error: { type: 'cli_not_found', message: 'Codex CLI not found' },
|
|
142
|
+
suggestion: 'Install with: npm install -g @openai/codex-cli', executionTimeMs: Date.now() - startTime };
|
|
143
|
+
}
|
|
144
|
+
if (err.message === 'TIMEOUT') {
|
|
145
|
+
return { success: false, error: { type: 'timeout', message: 'Codex timed out — no events received' },
|
|
146
|
+
suggestion: 'Try a smaller scope or use /gemini-review', executionTimeMs: Date.now() - startTime };
|
|
388
147
|
}
|
|
148
|
+
if (err.message === 'MAX_TIMEOUT') {
|
|
149
|
+
return { success: false, error: { type: 'timeout', message: 'Task exceeded 60 minute maximum' },
|
|
150
|
+
suggestion: 'Try a smaller scope', executionTimeMs: Date.now() - startTime };
|
|
151
|
+
}
|
|
152
|
+
return { success: false, error: { type: 'cli_error', message: err.message }, executionTimeMs: Date.now() - startTime };
|
|
389
153
|
}
|
|
390
154
|
categorizeError(stderr) {
|
|
391
155
|
const lower = stderr.toLowerCase();
|
|
392
|
-
if (lower.includes('rate limit')) {
|
|
393
|
-
return {
|
|
394
|
-
type: 'rate_limit',
|
|
395
|
-
message: 'Rate limit exceeded',
|
|
396
|
-
details: { retryAfterMs: this.parseRetryAfter(stderr) },
|
|
397
|
-
};
|
|
156
|
+
if (lower.includes('rate limit') || lower.includes('possible rate limit') || lower.includes('no response from codex')) {
|
|
157
|
+
return { type: 'rate_limit', message: 'Codex rate limit — no tokens available' };
|
|
398
158
|
}
|
|
399
|
-
if (lower.includes('unauthorized') || lower.includes('authentication') ||
|
|
400
|
-
|
|
401
|
-
return {
|
|
402
|
-
type: 'auth_error',
|
|
403
|
-
message: 'Authentication failed',
|
|
404
|
-
details: { stderr },
|
|
405
|
-
};
|
|
159
|
+
if (lower.includes('unauthorized') || lower.includes('authentication') || stderr.includes('401') || stderr.includes('403')) {
|
|
160
|
+
return { type: 'auth_error', message: 'Authentication failed', details: { stderr } };
|
|
406
161
|
}
|
|
407
|
-
|
|
408
|
-
type: 'cli_error',
|
|
409
|
-
|
|
410
|
-
};
|
|
162
|
+
if (lower.includes('invalid_json_schema') || lower.includes('invalid_request_error')) {
|
|
163
|
+
return { type: 'cli_error', message: `API error: ${stderr.slice(0, 300)}` };
|
|
164
|
+
}
|
|
165
|
+
return { type: 'cli_error', message: stderr || 'Unknown error' };
|
|
411
166
|
}
|
|
412
167
|
getSuggestion(error) {
|
|
413
168
|
switch (error.type) {
|
|
414
|
-
case 'rate_limit':
|
|
415
|
-
|
|
416
|
-
case '
|
|
417
|
-
|
|
418
|
-
case 'cli_not_found':
|
|
419
|
-
return 'Install with: npm install -g @openai/codex';
|
|
420
|
-
default:
|
|
421
|
-
return 'Check the error message and try again';
|
|
169
|
+
case 'rate_limit': return 'Wait and retry, or use /gemini-review instead';
|
|
170
|
+
case 'auth_error': return 'Run `codex login` to authenticate';
|
|
171
|
+
case 'cli_not_found': return 'Install with: npm install -g @openai/codex-cli';
|
|
172
|
+
default: return 'Check the error message and try again';
|
|
422
173
|
}
|
|
423
174
|
}
|
|
424
|
-
parseRetryAfter(errorMessage) {
|
|
425
|
-
const match = errorMessage.match(/retry[- ]?after[:\s]+(\d+)/i);
|
|
426
|
-
return match ? parseInt(match[1]) * 1000 : undefined;
|
|
427
|
-
}
|
|
428
175
|
}
|
|
429
176
|
// Register the adapter
|
|
430
177
|
registerAdapter(new CodexAdapter());
|
|
431
|
-
// Export for direct use
|
|
432
178
|
export const codexAdapter = new CodexAdapter();
|
|
@@ -2,20 +2,18 @@
|
|
|
2
2
|
* Gemini CLI Adapter
|
|
3
3
|
*
|
|
4
4
|
* Implements the ReviewerAdapter interface for Google's Gemini CLI.
|
|
5
|
-
*
|
|
5
|
+
* Returns raw text — no JSON parsing or schema enforcement.
|
|
6
|
+
* CC handles interpretation of the reviewer's response.
|
|
6
7
|
*/
|
|
7
|
-
import { ReviewerAdapter, ReviewerCapabilities, ReviewRequest, ReviewResult
|
|
8
|
+
import { ReviewerAdapter, ReviewerCapabilities, ReviewRequest, ReviewResult } from './base.js';
|
|
8
9
|
export declare class GeminiAdapter implements ReviewerAdapter {
|
|
9
10
|
readonly id = "gemini";
|
|
10
11
|
getCapabilities(): ReviewerCapabilities;
|
|
11
12
|
isAvailable(): Promise<boolean>;
|
|
12
13
|
runReview(request: ReviewRequest): Promise<ReviewResult>;
|
|
13
|
-
private runWithRetry;
|
|
14
|
-
runPeerRequest(request: PeerRequest): Promise<PeerResult>;
|
|
15
|
-
private runPeerWithRetry;
|
|
16
14
|
private runCli;
|
|
15
|
+
private handleException;
|
|
17
16
|
private categorizeError;
|
|
18
17
|
private getSuggestion;
|
|
19
|
-
private parseRetryAfter;
|
|
20
18
|
}
|
|
21
19
|
export declare const geminiAdapter: GeminiAdapter;
|