cc-reviewer 2.0.0 → 2.1.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/dist/adapters/base.d.ts +3 -6
- package/dist/adapters/codex.d.ts +3 -4
- package/dist/adapters/codex.js +76 -291
- package/dist/adapters/gemini.d.ts +3 -4
- package/dist/adapters/gemini.js +52 -243
- package/dist/decoders/codex.d.ts +11 -0
- package/dist/decoders/codex.js +29 -0
- package/dist/handoff.d.ts +3 -4
- package/dist/handoff.js +5 -57
- package/dist/schema.d.ts +6 -6
- package/dist/schema.js +1 -1
- package/dist/tools/feedback.d.ts +5 -6
- package/dist/tools/feedback.js +60 -335
- package/dist/tools/peer.d.ts +0 -2
- package/dist/tools/peer.js +19 -102
- package/package.json +1 -1
package/dist/adapters/gemini.js
CHANGED
|
@@ -2,22 +2,20 @@
|
|
|
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
8
|
import { spawn } from 'child_process';
|
|
8
9
|
import { existsSync } from 'fs';
|
|
9
10
|
import { registerAdapter, } from './base.js';
|
|
10
|
-
import { parseReviewOutput, parseLegacyMarkdownOutput, parsePeerOutput, isSubstantiveReview } from '../schema.js';
|
|
11
11
|
import { CliExecutor } from '../executor.js';
|
|
12
12
|
import { GeminiEventDecoder } from '../decoders/index.js';
|
|
13
13
|
import { buildSimpleHandoff, buildHandoffPrompt, buildPeerPrompt, selectRole, } from '../handoff.js';
|
|
14
14
|
// =============================================================================
|
|
15
15
|
// CONFIGURATION
|
|
16
16
|
// =============================================================================
|
|
17
|
-
const
|
|
18
|
-
const STREAMING_TIMEOUT_MS = 90_000; // 90s — if events stop mid-stream
|
|
17
|
+
const INACTIVITY_TIMEOUT_MS = 300_000; // 5 min — covers reasoning gaps between tool use
|
|
19
18
|
const MAX_TIMEOUT_MS = 3_600_000; // 60 min absolute max
|
|
20
|
-
const MAX_RETRIES = 2;
|
|
21
19
|
const MAX_BUFFER_SIZE = 1024 * 1024; // 1MB max buffer
|
|
22
20
|
// =============================================================================
|
|
23
21
|
// GEMINI ADAPTER
|
|
@@ -31,9 +29,9 @@ export class GeminiAdapter {
|
|
|
31
29
|
strengths: ['architecture', 'maintainability', 'scalability', 'documentation'],
|
|
32
30
|
weaknesses: ['security'],
|
|
33
31
|
hasFilesystemAccess: true,
|
|
34
|
-
supportsStructuredOutput:
|
|
35
|
-
maxContextTokens: 2000000,
|
|
36
|
-
reasoningLevels: undefined,
|
|
32
|
+
supportsStructuredOutput: false,
|
|
33
|
+
maxContextTokens: 2000000,
|
|
34
|
+
reasoningLevels: undefined,
|
|
37
35
|
};
|
|
38
36
|
}
|
|
39
37
|
async isAvailable() {
|
|
@@ -41,174 +39,42 @@ export class GeminiAdapter {
|
|
|
41
39
|
const proc = spawn('gemini', ['--version'], {
|
|
42
40
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
43
41
|
});
|
|
44
|
-
proc.on('close', (code) =>
|
|
45
|
-
|
|
46
|
-
});
|
|
47
|
-
proc.on('error', () => {
|
|
48
|
-
resolve(false);
|
|
49
|
-
});
|
|
50
|
-
// Timeout after 5s
|
|
51
|
-
setTimeout(() => {
|
|
52
|
-
proc.kill();
|
|
53
|
-
resolve(false);
|
|
54
|
-
}, 5000);
|
|
42
|
+
proc.on('close', (code) => resolve(code === 0));
|
|
43
|
+
proc.on('error', () => resolve(false));
|
|
44
|
+
setTimeout(() => { proc.kill(); resolve(false); }, 5000);
|
|
55
45
|
});
|
|
56
46
|
}
|
|
57
47
|
async runReview(request) {
|
|
58
48
|
const startTime = Date.now();
|
|
59
|
-
// Validate working directory
|
|
60
49
|
if (!existsSync(request.workingDir)) {
|
|
61
50
|
return {
|
|
62
51
|
success: false,
|
|
63
|
-
error: {
|
|
64
|
-
type: 'cli_error',
|
|
65
|
-
message: `Working directory does not exist: ${request.workingDir}`,
|
|
66
|
-
},
|
|
52
|
+
error: { type: 'cli_error', message: `Working directory does not exist: ${request.workingDir}` },
|
|
67
53
|
suggestion: 'Check that the working directory path is correct',
|
|
68
54
|
executionTimeMs: Date.now() - startTime,
|
|
69
55
|
};
|
|
70
56
|
}
|
|
71
|
-
return this.runWithRetry(request, 0, startTime);
|
|
72
|
-
}
|
|
73
|
-
async runWithRetry(request, attempt, startTime, previousError, previousOutput) {
|
|
74
57
|
try {
|
|
75
|
-
// Build the prompt using handoff protocol
|
|
76
58
|
const handoff = buildSimpleHandoff(request.workingDir, request.ccOutput, request.analyzedFiles, request.focusAreas, request.customPrompt);
|
|
77
|
-
// Select role based on focus areas (Gemini defaults to architect)
|
|
78
59
|
const role = selectRole(request.focusAreas);
|
|
79
|
-
|
|
80
|
-
let prompt = buildHandoffPrompt({
|
|
81
|
-
handoff,
|
|
82
|
-
role,
|
|
83
|
-
outputFormat: 'json',
|
|
84
|
-
});
|
|
85
|
-
// Add retry context if this is a retry attempt
|
|
86
|
-
if (attempt > 0) {
|
|
87
|
-
prompt += `\n\n---\n\n# RETRY ATTEMPT ${attempt + 1}\n\n` +
|
|
88
|
-
`Previous output had issues: ${previousError}\n` +
|
|
89
|
-
`Please fix these issues and provide valid JSON output.\n` +
|
|
90
|
-
(previousOutput ? `\nPrevious output (for reference):\n${previousOutput.slice(0, 500)}...` : '');
|
|
91
|
-
}
|
|
92
|
-
// Run the CLI
|
|
60
|
+
const prompt = buildHandoffPrompt({ handoff, role });
|
|
93
61
|
const result = await this.runCli(prompt, request.workingDir);
|
|
94
|
-
// Handle CLI errors
|
|
95
62
|
if (result.exitCode !== 0) {
|
|
96
63
|
const error = this.categorizeError(result.stderr);
|
|
97
|
-
return {
|
|
98
|
-
success: false,
|
|
99
|
-
error,
|
|
100
|
-
suggestion: this.getSuggestion(error),
|
|
101
|
-
rawOutput: result.stderr,
|
|
102
|
-
executionTimeMs: Date.now() - startTime,
|
|
103
|
-
};
|
|
64
|
+
return { success: false, error, suggestion: this.getSuggestion(error), executionTimeMs: Date.now() - startTime };
|
|
104
65
|
}
|
|
105
|
-
|
|
106
|
-
if (result.truncated) {
|
|
66
|
+
if (!result.stdout.trim()) {
|
|
107
67
|
return {
|
|
108
68
|
success: false,
|
|
109
|
-
error: {
|
|
110
|
-
|
|
111
|
-
message: 'Output exceeded maximum buffer size (1MB) and was truncated',
|
|
112
|
-
},
|
|
113
|
-
suggestion: 'Try reviewing a smaller scope with --focus',
|
|
69
|
+
error: { type: 'cli_error', message: 'Gemini returned empty response' },
|
|
70
|
+
suggestion: 'Try again or use /codex instead',
|
|
114
71
|
executionTimeMs: Date.now() - startTime,
|
|
115
72
|
};
|
|
116
73
|
}
|
|
117
|
-
|
|
118
|
-
let output = parseReviewOutput(result.stdout);
|
|
119
|
-
let usedFallback = false;
|
|
120
|
-
// If JSON parsing fails, try legacy markdown
|
|
121
|
-
if (!output) {
|
|
122
|
-
output = parseLegacyMarkdownOutput(result.stdout, 'gemini');
|
|
123
|
-
usedFallback = true;
|
|
124
|
-
}
|
|
125
|
-
// If no valid output, retry or fail
|
|
126
|
-
if (!output) {
|
|
127
|
-
if (attempt < MAX_RETRIES) {
|
|
128
|
-
return this.runWithRetry(request, attempt + 1, startTime, 'Output did not match expected JSON schema', result.stdout);
|
|
129
|
-
}
|
|
130
|
-
return {
|
|
131
|
-
success: false,
|
|
132
|
-
error: {
|
|
133
|
-
type: 'parse_error',
|
|
134
|
-
message: 'Failed to parse reviewer output after retries',
|
|
135
|
-
details: { rawOutput: result.stdout.slice(0, 1000) },
|
|
136
|
-
},
|
|
137
|
-
suggestion: 'The model may not be following the output format. Try a different focus area.',
|
|
138
|
-
rawOutput: result.stdout,
|
|
139
|
-
executionTimeMs: Date.now() - startTime,
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
// Check for empty/minimal output — centralized substance check
|
|
143
|
-
if (!isSubstantiveReview(output)) {
|
|
144
|
-
if (attempt < MAX_RETRIES) {
|
|
145
|
-
console.error(`[gemini] Received empty output, retrying...`);
|
|
146
|
-
return this.runWithRetry(request, attempt + 1, startTime, usedFallback
|
|
147
|
-
? 'Received markdown output instead of JSON. Please provide valid JSON output.'
|
|
148
|
-
: 'Output contained no substantive review content. Please provide findings or analysis.', result.stdout);
|
|
149
|
-
}
|
|
150
|
-
return {
|
|
151
|
-
success: false,
|
|
152
|
-
error: {
|
|
153
|
-
type: 'parse_error',
|
|
154
|
-
message: 'Reviewer returned empty output after retries',
|
|
155
|
-
details: { rawOutput: result.stdout.slice(0, 1000) },
|
|
156
|
-
},
|
|
157
|
-
suggestion: 'The model returned no substantive review. Try a different focus area.',
|
|
158
|
-
rawOutput: result.stdout,
|
|
159
|
-
executionTimeMs: Date.now() - startTime,
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
return {
|
|
163
|
-
success: true,
|
|
164
|
-
output,
|
|
165
|
-
rawOutput: result.stdout,
|
|
166
|
-
executionTimeMs: Date.now() - startTime,
|
|
167
|
-
};
|
|
74
|
+
return { success: true, output: result.stdout, executionTimeMs: Date.now() - startTime };
|
|
168
75
|
}
|
|
169
76
|
catch (error) {
|
|
170
|
-
|
|
171
|
-
if (err.code === 'ENOENT') {
|
|
172
|
-
return {
|
|
173
|
-
success: false,
|
|
174
|
-
error: {
|
|
175
|
-
type: 'cli_not_found',
|
|
176
|
-
message: 'Gemini CLI not found',
|
|
177
|
-
},
|
|
178
|
-
suggestion: 'Install with: npm install -g @google/gemini-cli',
|
|
179
|
-
executionTimeMs: Date.now() - startTime,
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
if (err.message === 'TIMEOUT') {
|
|
183
|
-
return {
|
|
184
|
-
success: false,
|
|
185
|
-
error: {
|
|
186
|
-
type: 'timeout',
|
|
187
|
-
message: 'No output for 10 minutes - process may be hung',
|
|
188
|
-
},
|
|
189
|
-
suggestion: 'Try a smaller scope or use --focus',
|
|
190
|
-
executionTimeMs: Date.now() - startTime,
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
if (err.message === 'MAX_TIMEOUT') {
|
|
194
|
-
return {
|
|
195
|
-
success: false,
|
|
196
|
-
error: {
|
|
197
|
-
type: 'timeout',
|
|
198
|
-
message: 'Task exceeded 60 minute maximum',
|
|
199
|
-
},
|
|
200
|
-
suggestion: 'Try a smaller scope',
|
|
201
|
-
executionTimeMs: Date.now() - startTime,
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
return {
|
|
205
|
-
success: false,
|
|
206
|
-
error: {
|
|
207
|
-
type: 'cli_error',
|
|
208
|
-
message: err.message,
|
|
209
|
-
},
|
|
210
|
-
executionTimeMs: Date.now() - startTime,
|
|
211
|
-
};
|
|
77
|
+
return this.handleException(error, startTime);
|
|
212
78
|
}
|
|
213
79
|
}
|
|
214
80
|
async runPeerRequest(request) {
|
|
@@ -221,11 +87,8 @@ export class GeminiAdapter {
|
|
|
221
87
|
executionTimeMs: Date.now() - startTime,
|
|
222
88
|
};
|
|
223
89
|
}
|
|
224
|
-
return this.runPeerWithRetry(request, 0, startTime);
|
|
225
|
-
}
|
|
226
|
-
async runPeerWithRetry(request, attempt, startTime, previousError, previousOutput) {
|
|
227
90
|
try {
|
|
228
|
-
|
|
91
|
+
const prompt = buildPeerPrompt({
|
|
229
92
|
workingDir: request.workingDir,
|
|
230
93
|
prompt: request.prompt,
|
|
231
94
|
taskType: request.taskType,
|
|
@@ -233,81 +96,36 @@ export class GeminiAdapter {
|
|
|
233
96
|
context: request.context,
|
|
234
97
|
focusAreas: request.focusAreas,
|
|
235
98
|
customInstructions: request.customPrompt,
|
|
236
|
-
outputFormat: 'json',
|
|
237
99
|
});
|
|
238
|
-
if (attempt > 0) {
|
|
239
|
-
prompt += `\n\n---\n\n# RETRY ATTEMPT ${attempt + 1}\n\n` +
|
|
240
|
-
`Previous output had issues: ${previousError}\n` +
|
|
241
|
-
`Please fix these issues and provide valid JSON output.\n` +
|
|
242
|
-
(previousOutput ? `\nPrevious output (for reference):\n${previousOutput.slice(0, 500)}...` : '');
|
|
243
|
-
}
|
|
244
100
|
const result = await this.runCli(prompt, request.workingDir);
|
|
245
101
|
if (result.exitCode !== 0) {
|
|
246
102
|
const error = this.categorizeError(result.stderr);
|
|
247
|
-
return {
|
|
248
|
-
success: false, error,
|
|
249
|
-
suggestion: this.getSuggestion(error),
|
|
250
|
-
rawOutput: result.stderr,
|
|
251
|
-
executionTimeMs: Date.now() - startTime,
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
if (result.truncated) {
|
|
255
|
-
return {
|
|
256
|
-
success: false,
|
|
257
|
-
error: { type: 'cli_error', message: 'Output exceeded maximum buffer size (1MB)' },
|
|
258
|
-
suggestion: 'Try a more focused request',
|
|
259
|
-
executionTimeMs: Date.now() - startTime,
|
|
260
|
-
};
|
|
103
|
+
return { success: false, error, suggestion: this.getSuggestion(error), executionTimeMs: Date.now() - startTime };
|
|
261
104
|
}
|
|
262
|
-
|
|
263
|
-
if (!output) {
|
|
264
|
-
if (attempt < MAX_RETRIES) {
|
|
265
|
-
return this.runPeerWithRetry(request, attempt + 1, startTime, 'Output did not match expected JSON schema', result.stdout);
|
|
266
|
-
}
|
|
105
|
+
if (!result.stdout.trim()) {
|
|
267
106
|
return {
|
|
268
107
|
success: false,
|
|
269
|
-
error: { type: '
|
|
270
|
-
|
|
271
|
-
suggestion: 'The model may not be following the output format.',
|
|
272
|
-
rawOutput: result.stdout,
|
|
108
|
+
error: { type: 'cli_error', message: 'Gemini returned empty response' },
|
|
109
|
+
suggestion: 'Try again or use /ask-codex instead',
|
|
273
110
|
executionTimeMs: Date.now() - startTime,
|
|
274
111
|
};
|
|
275
112
|
}
|
|
276
|
-
return {
|
|
277
|
-
success: true, output,
|
|
278
|
-
rawOutput: result.stdout,
|
|
279
|
-
executionTimeMs: Date.now() - startTime,
|
|
280
|
-
};
|
|
113
|
+
return { success: true, output: result.stdout, executionTimeMs: Date.now() - startTime };
|
|
281
114
|
}
|
|
282
115
|
catch (error) {
|
|
283
|
-
|
|
284
|
-
if (err.code === 'ENOENT') {
|
|
285
|
-
return { success: false, error: { type: 'cli_not_found', message: 'Gemini CLI not found' },
|
|
286
|
-
suggestion: 'Install with: npm install -g @google/gemini-cli', executionTimeMs: Date.now() - startTime };
|
|
287
|
-
}
|
|
288
|
-
if (err.message === 'TIMEOUT') {
|
|
289
|
-
return { success: false, error: { type: 'timeout', message: 'No output for 10 minutes' },
|
|
290
|
-
suggestion: 'Try a simpler request', executionTimeMs: Date.now() - startTime };
|
|
291
|
-
}
|
|
292
|
-
if (err.message === 'MAX_TIMEOUT') {
|
|
293
|
-
return { success: false, error: { type: 'timeout', message: 'Task exceeded 60 minute maximum' },
|
|
294
|
-
suggestion: 'Try a smaller scope', executionTimeMs: Date.now() - startTime };
|
|
295
|
-
}
|
|
296
|
-
return { success: false, error: { type: 'cli_error', message: err.message },
|
|
297
|
-
executionTimeMs: Date.now() - startTime };
|
|
116
|
+
return this.handleException(error, startTime);
|
|
298
117
|
}
|
|
299
118
|
}
|
|
300
119
|
async runCli(prompt, workingDir) {
|
|
301
120
|
const args = [
|
|
302
121
|
'--yolo',
|
|
303
|
-
'--output-format', 'stream-json',
|
|
122
|
+
'--output-format', 'stream-json',
|
|
304
123
|
'--include-directories', workingDir,
|
|
305
|
-
'-p', '',
|
|
124
|
+
'-p', '',
|
|
306
125
|
];
|
|
307
126
|
const decoder = new GeminiEventDecoder();
|
|
308
127
|
const cliStartTime = Date.now();
|
|
309
|
-
|
|
310
|
-
console.error('[gemini] Running review...');
|
|
128
|
+
console.error('[gemini] Running...');
|
|
311
129
|
decoder.onProgress = (eventType, detail) => {
|
|
312
130
|
const elapsed = Math.round((Date.now() - cliStartTime) / 1000);
|
|
313
131
|
const detailStr = detail ? ` — ${detail}` : '';
|
|
@@ -318,15 +136,11 @@ export class GeminiAdapter {
|
|
|
318
136
|
args,
|
|
319
137
|
cwd: workingDir,
|
|
320
138
|
stdin: prompt,
|
|
321
|
-
inactivityTimeoutMs:
|
|
139
|
+
inactivityTimeoutMs: INACTIVITY_TIMEOUT_MS,
|
|
322
140
|
maxTimeoutMs: MAX_TIMEOUT_MS,
|
|
323
141
|
maxBufferSize: MAX_BUFFER_SIZE,
|
|
324
142
|
onLine: (line) => {
|
|
325
143
|
decoder.processLine(line);
|
|
326
|
-
if (!firstEventReceived) {
|
|
327
|
-
firstEventReceived = true;
|
|
328
|
-
executor.setInactivityTimeout(STREAMING_TIMEOUT_MS);
|
|
329
|
-
}
|
|
330
144
|
},
|
|
331
145
|
});
|
|
332
146
|
const result = await executor.run();
|
|
@@ -340,46 +154,41 @@ export class GeminiAdapter {
|
|
|
340
154
|
truncated: result.truncated,
|
|
341
155
|
};
|
|
342
156
|
}
|
|
157
|
+
handleException(error, startTime) {
|
|
158
|
+
const err = error;
|
|
159
|
+
if (err.code === 'ENOENT') {
|
|
160
|
+
return { success: false, error: { type: 'cli_not_found', message: 'Gemini CLI not found' },
|
|
161
|
+
suggestion: 'Install with: npm install -g @google/gemini-cli', executionTimeMs: Date.now() - startTime };
|
|
162
|
+
}
|
|
163
|
+
if (err.message === 'TIMEOUT') {
|
|
164
|
+
return { success: false, error: { type: 'timeout', message: 'Gemini timed out — no events received' },
|
|
165
|
+
suggestion: 'Try a smaller scope or use /codex', executionTimeMs: Date.now() - startTime };
|
|
166
|
+
}
|
|
167
|
+
if (err.message === 'MAX_TIMEOUT') {
|
|
168
|
+
return { success: false, error: { type: 'timeout', message: 'Task exceeded 60 minute maximum' },
|
|
169
|
+
suggestion: 'Try a smaller scope', executionTimeMs: Date.now() - startTime };
|
|
170
|
+
}
|
|
171
|
+
return { success: false, error: { type: 'cli_error', message: err.message }, executionTimeMs: Date.now() - startTime };
|
|
172
|
+
}
|
|
343
173
|
categorizeError(stderr) {
|
|
344
174
|
const lower = stderr.toLowerCase();
|
|
345
175
|
if (lower.includes('rate limit') || lower.includes('quota')) {
|
|
346
|
-
return {
|
|
347
|
-
type: 'rate_limit',
|
|
348
|
-
message: 'Rate limit or quota exceeded',
|
|
349
|
-
details: { retryAfterMs: this.parseRetryAfter(stderr) },
|
|
350
|
-
};
|
|
176
|
+
return { type: 'rate_limit', message: 'Rate limit or quota exceeded' };
|
|
351
177
|
}
|
|
352
|
-
if (lower.includes('unauthorized') || lower.includes('authentication') ||
|
|
353
|
-
|
|
354
|
-
return {
|
|
355
|
-
type: 'auth_error',
|
|
356
|
-
message: 'Authentication failed',
|
|
357
|
-
details: { stderr },
|
|
358
|
-
};
|
|
178
|
+
if (lower.includes('unauthorized') || lower.includes('authentication') || lower.includes('api key') || stderr.includes('401') || stderr.includes('403')) {
|
|
179
|
+
return { type: 'auth_error', message: 'Authentication failed', details: { stderr } };
|
|
359
180
|
}
|
|
360
|
-
return {
|
|
361
|
-
type: 'cli_error',
|
|
362
|
-
message: stderr || 'Unknown error',
|
|
363
|
-
};
|
|
181
|
+
return { type: 'cli_error', message: stderr || 'Unknown error' };
|
|
364
182
|
}
|
|
365
183
|
getSuggestion(error) {
|
|
366
184
|
switch (error.type) {
|
|
367
|
-
case 'rate_limit':
|
|
368
|
-
|
|
369
|
-
case '
|
|
370
|
-
|
|
371
|
-
case 'cli_not_found':
|
|
372
|
-
return 'Install with: npm install -g @google/gemini-cli';
|
|
373
|
-
default:
|
|
374
|
-
return 'Check the error message and try again';
|
|
185
|
+
case 'rate_limit': return 'Wait and retry, or use /codex instead';
|
|
186
|
+
case 'auth_error': return 'Run `gemini` and follow auth prompts, or set GEMINI_API_KEY';
|
|
187
|
+
case 'cli_not_found': return 'Install with: npm install -g @google/gemini-cli';
|
|
188
|
+
default: return 'Check the error message and try again';
|
|
375
189
|
}
|
|
376
190
|
}
|
|
377
|
-
parseRetryAfter(errorMessage) {
|
|
378
|
-
const match = errorMessage.match(/retry[- ]?after[:\s]+(\d+)/i);
|
|
379
|
-
return match ? parseInt(match[1]) * 1000 : undefined;
|
|
380
|
-
}
|
|
381
191
|
}
|
|
382
192
|
// Register the adapter
|
|
383
193
|
registerAdapter(new GeminiAdapter());
|
|
384
|
-
// Export for direct use
|
|
385
194
|
export const geminiAdapter = new GeminiAdapter();
|
package/dist/decoders/codex.d.ts
CHANGED
|
@@ -42,6 +42,8 @@ export declare class CodexEventDecoder {
|
|
|
42
42
|
onProgress?: (eventType: string, detail?: string) => void;
|
|
43
43
|
private _finalResponse;
|
|
44
44
|
private _usage;
|
|
45
|
+
private _error;
|
|
46
|
+
private _eventCount;
|
|
45
47
|
/**
|
|
46
48
|
* Parse a single JSONL line. Silently skips malformed or empty input.
|
|
47
49
|
*/
|
|
@@ -56,5 +58,14 @@ export declare class CodexEventDecoder {
|
|
|
56
58
|
* `null` if no such event has been seen.
|
|
57
59
|
*/
|
|
58
60
|
getUsage(): CodexEvent['usage'] | null;
|
|
61
|
+
/**
|
|
62
|
+
* Returns the error message from `error` or `turn.failed` events, or `null`.
|
|
63
|
+
*/
|
|
64
|
+
getError(): string | null;
|
|
65
|
+
/**
|
|
66
|
+
* Returns true if events were received but no agent_message was produced.
|
|
67
|
+
* Combined with a fast exit, this indicates rate limiting or instant rejection.
|
|
68
|
+
*/
|
|
69
|
+
hasNoOutput(): boolean;
|
|
59
70
|
private _handleEvent;
|
|
60
71
|
}
|
package/dist/decoders/codex.js
CHANGED
|
@@ -25,6 +25,10 @@ export class CodexEventDecoder {
|
|
|
25
25
|
_finalResponse = null;
|
|
26
26
|
// Token usage from the most recently seen turn.completed
|
|
27
27
|
_usage = null;
|
|
28
|
+
// Error message from error/turn.failed events
|
|
29
|
+
_error = null;
|
|
30
|
+
// Count of events received (0 = possible rate limit / instant rejection)
|
|
31
|
+
_eventCount = 0;
|
|
28
32
|
// =============================================================================
|
|
29
33
|
// PUBLIC API
|
|
30
34
|
// =============================================================================
|
|
@@ -63,10 +67,24 @@ export class CodexEventDecoder {
|
|
|
63
67
|
getUsage() {
|
|
64
68
|
return this._usage;
|
|
65
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Returns the error message from `error` or `turn.failed` events, or `null`.
|
|
72
|
+
*/
|
|
73
|
+
getError() {
|
|
74
|
+
return this._error;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Returns true if events were received but no agent_message was produced.
|
|
78
|
+
* Combined with a fast exit, this indicates rate limiting or instant rejection.
|
|
79
|
+
*/
|
|
80
|
+
hasNoOutput() {
|
|
81
|
+
return this._eventCount > 0 && this._finalResponse === null;
|
|
82
|
+
}
|
|
66
83
|
// =============================================================================
|
|
67
84
|
// PRIVATE HELPERS
|
|
68
85
|
// =============================================================================
|
|
69
86
|
_handleEvent(event) {
|
|
87
|
+
this._eventCount++;
|
|
70
88
|
// Track the last agent_message text
|
|
71
89
|
if (event.type === 'item.completed' &&
|
|
72
90
|
event.item?.type === 'agent_message' &&
|
|
@@ -77,6 +95,17 @@ export class CodexEventDecoder {
|
|
|
77
95
|
if (event.type === 'turn.completed' && event.usage != null) {
|
|
78
96
|
this._usage = event.usage;
|
|
79
97
|
}
|
|
98
|
+
// Capture errors from error/turn.failed events
|
|
99
|
+
if (event.type === 'error') {
|
|
100
|
+
this._error = event.message || 'Unknown error from Codex';
|
|
101
|
+
}
|
|
102
|
+
if (event.type === 'turn.failed') {
|
|
103
|
+
this._error = event.error?.message || 'Turn failed';
|
|
104
|
+
}
|
|
105
|
+
// Capture error items (e.g. model errors reported as item.completed with type=error)
|
|
106
|
+
if (event.type === 'item.completed' && event.item?.type === 'error') {
|
|
107
|
+
this._error = event.item.message || event.item.text || 'Model error';
|
|
108
|
+
}
|
|
80
109
|
// Notify caller
|
|
81
110
|
this.onProgress?.(event.type, describeEvent(event));
|
|
82
111
|
}
|
package/dist/handoff.d.ts
CHANGED
|
@@ -214,10 +214,10 @@ export declare function selectRole(focusAreas?: FocusArea[]): ReviewerRole;
|
|
|
214
214
|
export interface PromptOptions {
|
|
215
215
|
handoff: Handoff;
|
|
216
216
|
role?: ReviewerRole;
|
|
217
|
-
outputFormat: 'json' | 'markdown' | 'schema-enforced';
|
|
218
217
|
}
|
|
219
218
|
/**
|
|
220
|
-
* Build the review prompt using minimal, targeted context
|
|
219
|
+
* Build the review prompt using minimal, targeted context.
|
|
220
|
+
* No output format constraints — reviewer responds naturally, CC interprets.
|
|
221
221
|
*/
|
|
222
222
|
export declare function buildHandoffPrompt(options: PromptOptions): string;
|
|
223
223
|
/**
|
|
@@ -237,10 +237,9 @@ export interface PeerPromptOptions {
|
|
|
237
237
|
context?: string;
|
|
238
238
|
focusAreas?: FocusArea[];
|
|
239
239
|
customInstructions?: string;
|
|
240
|
-
outputFormat: 'json' | 'schema-enforced';
|
|
241
240
|
}
|
|
242
241
|
/**
|
|
243
242
|
* Build a prompt for general-purpose peer assistance (not review).
|
|
244
|
-
*
|
|
243
|
+
* No output format constraints — peer responds naturally, CC interprets.
|
|
245
244
|
*/
|
|
246
245
|
export declare function buildPeerPrompt(options: PeerPromptOptions): string;
|
package/dist/handoff.js
CHANGED
|
@@ -154,10 +154,11 @@ export function selectRole(focusAreas) {
|
|
|
154
154
|
return CHANGE_FOCUSED_REVIEWER;
|
|
155
155
|
}
|
|
156
156
|
/**
|
|
157
|
-
* Build the review prompt using minimal, targeted context
|
|
157
|
+
* Build the review prompt using minimal, targeted context.
|
|
158
|
+
* No output format constraints — reviewer responds naturally, CC interprets.
|
|
158
159
|
*/
|
|
159
160
|
export function buildHandoffPrompt(options) {
|
|
160
|
-
const { handoff
|
|
161
|
+
const { handoff } = options;
|
|
161
162
|
const role = options.role || selectRole(handoff.focusAreas);
|
|
162
163
|
const sections = [];
|
|
163
164
|
// SECTION 1: ROLE
|
|
@@ -197,38 +198,6 @@ ${handoff.decisions.map((d, i) => `${i + 1}. **${d.decision}**
|
|
|
197
198
|
if (handoff.priorityFiles && handoff.priorityFiles.length > 0) {
|
|
198
199
|
sections.push(`## PRIORITY FILES\n\n${handoff.priorityFiles.map(f => `- \`${f}\``).join('\n')}`);
|
|
199
200
|
}
|
|
200
|
-
// SECTION 7: OUTPUT FORMAT
|
|
201
|
-
if (outputFormat === 'schema-enforced') {
|
|
202
|
-
sections.push(`## OUTPUT FORMAT
|
|
203
|
-
Respond with valid JSON matching the schema. Confidence reflects YOUR certainty.`);
|
|
204
|
-
}
|
|
205
|
-
else if (outputFormat === 'json') {
|
|
206
|
-
sections.push(`## OUTPUT FORMAT
|
|
207
|
-
Respond with valid JSON:
|
|
208
|
-
\`\`\`json
|
|
209
|
-
{
|
|
210
|
-
"findings": [{
|
|
211
|
-
"id": "string",
|
|
212
|
-
"category": "security|performance|correctness|architecture|other",
|
|
213
|
-
"severity": "critical|high|medium|low|info",
|
|
214
|
-
"confidence": 0.0-1.0,
|
|
215
|
-
"title": "string",
|
|
216
|
-
"description": "string",
|
|
217
|
-
"location": { "file": "string", "line_start": 0 },
|
|
218
|
-
"evidence": "code snippet",
|
|
219
|
-
"suggestion": "string"
|
|
220
|
-
}],
|
|
221
|
-
"uncertainty_responses": [{"uncertainty_index": 0, "verified": true, "finding": "string", "recommendation": "string"}],
|
|
222
|
-
"question_answers": [{"question_index": 0, "answer": "string", "confidence": 0.0-1.0}],
|
|
223
|
-
"agreements": ["string"],
|
|
224
|
-
"risk_assessment": { "level": "critical|high|medium|low|minimal", "summary": "string" }
|
|
225
|
-
}
|
|
226
|
-
\`\`\``);
|
|
227
|
-
}
|
|
228
|
-
else {
|
|
229
|
-
sections.push(`## OUTPUT FORMAT
|
|
230
|
-
Structure: ## Findings, ## Uncertainty Responses, ## Question Answers, ## Agreements, ## Risk Assessment.`);
|
|
231
|
-
}
|
|
232
201
|
return sections.join('\n\n');
|
|
233
202
|
}
|
|
234
203
|
// =============================================================================
|
|
@@ -260,10 +229,10 @@ export function enhanceHandoff(handoff, uncertainties, questions, decisions) {
|
|
|
260
229
|
}
|
|
261
230
|
/**
|
|
262
231
|
* Build a prompt for general-purpose peer assistance (not review).
|
|
263
|
-
*
|
|
232
|
+
* No output format constraints — peer responds naturally, CC interprets.
|
|
264
233
|
*/
|
|
265
234
|
export function buildPeerPrompt(options) {
|
|
266
|
-
const { workingDir, prompt, taskType, relevantFiles, context, focusAreas, customInstructions
|
|
235
|
+
const { workingDir, prompt, taskType, relevantFiles, context, focusAreas, customInstructions } = options;
|
|
267
236
|
const role = selectRole(focusAreas);
|
|
268
237
|
const sections = [];
|
|
269
238
|
// SECTION 1: ROLE
|
|
@@ -288,26 +257,5 @@ Be direct and actionable.`);
|
|
|
288
257
|
if (customInstructions) {
|
|
289
258
|
sections.push(`## ADDITIONAL INSTRUCTIONS\n\n${customInstructions}`);
|
|
290
259
|
}
|
|
291
|
-
// SECTION 6: OUTPUT FORMAT
|
|
292
|
-
if (outputFormat === 'schema-enforced') {
|
|
293
|
-
sections.push(`## OUTPUT FORMAT
|
|
294
|
-
Respond with valid JSON matching the schema. Confidence reflects YOUR certainty.`);
|
|
295
|
-
}
|
|
296
|
-
else {
|
|
297
|
-
sections.push(`## OUTPUT FORMAT
|
|
298
|
-
Respond with valid JSON:
|
|
299
|
-
\`\`\`json
|
|
300
|
-
{
|
|
301
|
-
"responder": "string",
|
|
302
|
-
"answer": "markdown",
|
|
303
|
-
"confidence": 0.0-1.0,
|
|
304
|
-
"key_points": ["string"],
|
|
305
|
-
"suggested_actions": [{ "action": "string", "priority": "high|medium|low", "file": "string", "rationale": "string" }],
|
|
306
|
-
"file_references": [{ "path": "string", "lines": "string", "relevance": "string" }],
|
|
307
|
-
"alternatives": [{ "topic": "string", "current_approach": "string", "alternative": "string", "tradeoffs": { "pros": [], "cons": [] }, "recommendation": "string" }],
|
|
308
|
-
"execution_notes": "string"
|
|
309
|
-
}
|
|
310
|
-
\`\`\``);
|
|
311
|
-
}
|
|
312
260
|
return sections.join('\n\n');
|
|
313
261
|
}
|
package/dist/schema.d.ts
CHANGED
|
@@ -76,8 +76,8 @@ export declare const ReviewFinding: z.ZodObject<{
|
|
|
76
76
|
column_start?: number | undefined;
|
|
77
77
|
column_end?: number | undefined;
|
|
78
78
|
} | undefined;
|
|
79
|
-
evidence?: string | undefined;
|
|
80
79
|
suggestion?: string | undefined;
|
|
80
|
+
evidence?: string | undefined;
|
|
81
81
|
cwe_id?: string | undefined;
|
|
82
82
|
owasp_category?: string | undefined;
|
|
83
83
|
tags?: string[] | undefined;
|
|
@@ -95,8 +95,8 @@ export declare const ReviewFinding: z.ZodObject<{
|
|
|
95
95
|
column_start?: number | undefined;
|
|
96
96
|
column_end?: number | undefined;
|
|
97
97
|
} | undefined;
|
|
98
|
-
evidence?: string | undefined;
|
|
99
98
|
suggestion?: string | undefined;
|
|
99
|
+
evidence?: string | undefined;
|
|
100
100
|
cwe_id?: string | undefined;
|
|
101
101
|
owasp_category?: string | undefined;
|
|
102
102
|
tags?: string[] | undefined;
|
|
@@ -279,8 +279,8 @@ export declare const ReviewOutput: z.ZodObject<{
|
|
|
279
279
|
column_start?: number | undefined;
|
|
280
280
|
column_end?: number | undefined;
|
|
281
281
|
} | undefined;
|
|
282
|
-
evidence?: string | undefined;
|
|
283
282
|
suggestion?: string | undefined;
|
|
283
|
+
evidence?: string | undefined;
|
|
284
284
|
cwe_id?: string | undefined;
|
|
285
285
|
owasp_category?: string | undefined;
|
|
286
286
|
tags?: string[] | undefined;
|
|
@@ -298,8 +298,8 @@ export declare const ReviewOutput: z.ZodObject<{
|
|
|
298
298
|
column_start?: number | undefined;
|
|
299
299
|
column_end?: number | undefined;
|
|
300
300
|
} | undefined;
|
|
301
|
-
evidence?: string | undefined;
|
|
302
301
|
suggestion?: string | undefined;
|
|
302
|
+
evidence?: string | undefined;
|
|
303
303
|
cwe_id?: string | undefined;
|
|
304
304
|
owasp_category?: string | undefined;
|
|
305
305
|
tags?: string[] | undefined;
|
|
@@ -444,8 +444,8 @@ export declare const ReviewOutput: z.ZodObject<{
|
|
|
444
444
|
column_start?: number | undefined;
|
|
445
445
|
column_end?: number | undefined;
|
|
446
446
|
} | undefined;
|
|
447
|
-
evidence?: string | undefined;
|
|
448
447
|
suggestion?: string | undefined;
|
|
448
|
+
evidence?: string | undefined;
|
|
449
449
|
cwe_id?: string | undefined;
|
|
450
450
|
owasp_category?: string | undefined;
|
|
451
451
|
tags?: string[] | undefined;
|
|
@@ -512,8 +512,8 @@ export declare const ReviewOutput: z.ZodObject<{
|
|
|
512
512
|
column_start?: number | undefined;
|
|
513
513
|
column_end?: number | undefined;
|
|
514
514
|
} | undefined;
|
|
515
|
-
evidence?: string | undefined;
|
|
516
515
|
suggestion?: string | undefined;
|
|
516
|
+
evidence?: string | undefined;
|
|
517
517
|
cwe_id?: string | undefined;
|
|
518
518
|
owasp_category?: string | undefined;
|
|
519
519
|
tags?: string[] | undefined;
|