cc-reviewer 1.1.6 → 1.2.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/codex.js +73 -4
- package/dist/adapters/gemini.js +35 -1
- package/package.json +1 -1
package/dist/adapters/codex.js
CHANGED
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
* Specializes in correctness, edge cases, and performance analysis.
|
|
6
6
|
*/
|
|
7
7
|
import { spawn } from 'child_process';
|
|
8
|
-
import { existsSync } from 'fs';
|
|
8
|
+
import { existsSync, writeFileSync, unlinkSync, mkdtempSync } from 'fs';
|
|
9
|
+
import { tmpdir } from 'os';
|
|
10
|
+
import { join } from 'path';
|
|
9
11
|
import { registerAdapter, } from './base.js';
|
|
10
|
-
import { parseReviewOutput, parseLegacyMarkdownOutput } from '../schema.js';
|
|
12
|
+
import { parseReviewOutput, parseLegacyMarkdownOutput, getReviewOutputJsonSchema } from '../schema.js';
|
|
11
13
|
import { buildSimpleHandoff, buildHandoffPrompt, selectRole, } from '../handoff.js';
|
|
12
14
|
// =============================================================================
|
|
13
15
|
// CONFIGURATION
|
|
@@ -113,11 +115,13 @@ export class CodexAdapter {
|
|
|
113
115
|
}
|
|
114
116
|
// Parse the output
|
|
115
117
|
let output = parseReviewOutput(result.stdout);
|
|
118
|
+
let usedFallback = false;
|
|
116
119
|
// If JSON parsing fails, try legacy markdown
|
|
117
120
|
if (!output) {
|
|
118
121
|
output = parseLegacyMarkdownOutput(result.stdout, 'codex');
|
|
122
|
+
usedFallback = true;
|
|
119
123
|
}
|
|
120
|
-
// If
|
|
124
|
+
// If no valid output, retry or fail
|
|
121
125
|
if (!output) {
|
|
122
126
|
if (attempt < MAX_RETRIES) {
|
|
123
127
|
return this.runWithRetry(request, attempt + 1, startTime, 'Output did not match expected JSON schema', result.stdout);
|
|
@@ -134,6 +138,17 @@ export class CodexAdapter {
|
|
|
134
138
|
executionTimeMs: Date.now() - startTime,
|
|
135
139
|
};
|
|
136
140
|
}
|
|
141
|
+
// If we used fallback and got minimal data, retry
|
|
142
|
+
if (usedFallback && attempt < MAX_RETRIES) {
|
|
143
|
+
const hasMinimalData = output.findings.length === 0 &&
|
|
144
|
+
output.agreements.length === 0 &&
|
|
145
|
+
output.disagreements.length === 0 &&
|
|
146
|
+
output.risk_assessment.summary === 'Unable to parse structured risk assessment';
|
|
147
|
+
if (hasMinimalData) {
|
|
148
|
+
console.error(`[codex] Received incomplete output (fallback parse with no data), retrying...`);
|
|
149
|
+
return this.runWithRetry(request, attempt + 1, startTime, 'Received markdown output instead of JSON. Please provide valid JSON output.', result.stdout);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
137
152
|
return {
|
|
138
153
|
success: true,
|
|
139
154
|
output,
|
|
@@ -188,6 +203,18 @@ export class CodexAdapter {
|
|
|
188
203
|
}
|
|
189
204
|
runCli(prompt, workingDir, reasoningEffort) {
|
|
190
205
|
return new Promise((resolve, reject) => {
|
|
206
|
+
// Create temp schema file for structured output
|
|
207
|
+
let schemaFile = null;
|
|
208
|
+
try {
|
|
209
|
+
const tempDir = mkdtempSync(join(tmpdir(), 'codex-schema-'));
|
|
210
|
+
schemaFile = join(tempDir, 'schema.json');
|
|
211
|
+
const schema = getReviewOutputJsonSchema();
|
|
212
|
+
writeFileSync(schemaFile, JSON.stringify(schema, null, 2), 'utf-8');
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
console.error('[codex] Warning: Failed to create schema file, continuing without structured output:', err);
|
|
216
|
+
schemaFile = null;
|
|
217
|
+
}
|
|
191
218
|
const args = [
|
|
192
219
|
'exec',
|
|
193
220
|
'-m', 'gpt-5.2-codex',
|
|
@@ -196,8 +223,12 @@ export class CodexAdapter {
|
|
|
196
223
|
'--dangerously-bypass-approvals-and-sandbox',
|
|
197
224
|
'--skip-git-repo-check',
|
|
198
225
|
'-C', workingDir,
|
|
199
|
-
prompt
|
|
200
226
|
];
|
|
227
|
+
// Add schema enforcement if available
|
|
228
|
+
if (schemaFile) {
|
|
229
|
+
args.push('--output-schema', schemaFile);
|
|
230
|
+
}
|
|
231
|
+
args.push(prompt);
|
|
201
232
|
const proc = spawn('codex', args, {
|
|
202
233
|
cwd: workingDir,
|
|
203
234
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -207,6 +238,11 @@ export class CodexAdapter {
|
|
|
207
238
|
let stderr = '';
|
|
208
239
|
let truncated = false;
|
|
209
240
|
let inactivityTimer;
|
|
241
|
+
const cliStartTime = Date.now();
|
|
242
|
+
let lastProgressTime = cliStartTime;
|
|
243
|
+
let dataChunks = 0;
|
|
244
|
+
// Show initial progress message
|
|
245
|
+
console.error(`[codex] Running review with ${reasoningEffort} reasoning...`);
|
|
210
246
|
const maxTimer = setTimeout(() => {
|
|
211
247
|
proc.kill('SIGTERM');
|
|
212
248
|
reject(new Error('MAX_TIMEOUT'));
|
|
@@ -221,6 +257,18 @@ export class CodexAdapter {
|
|
|
221
257
|
resetInactivityTimer();
|
|
222
258
|
proc.stdout.on('data', (data) => {
|
|
223
259
|
resetInactivityTimer();
|
|
260
|
+
dataChunks++;
|
|
261
|
+
// Show progress dot every 5 chunks
|
|
262
|
+
if (dataChunks % 5 === 0) {
|
|
263
|
+
process.stderr.write('.');
|
|
264
|
+
}
|
|
265
|
+
// Show elapsed time every 10 seconds
|
|
266
|
+
const now = Date.now();
|
|
267
|
+
if (now - lastProgressTime > 10000) {
|
|
268
|
+
const elapsed = Math.round((now - cliStartTime) / 1000);
|
|
269
|
+
console.error(` [${elapsed}s]`);
|
|
270
|
+
lastProgressTime = now;
|
|
271
|
+
}
|
|
224
272
|
if (stdout.length < MAX_BUFFER_SIZE) {
|
|
225
273
|
stdout += data.toString();
|
|
226
274
|
if (stdout.length > MAX_BUFFER_SIZE) {
|
|
@@ -241,11 +289,32 @@ export class CodexAdapter {
|
|
|
241
289
|
proc.on('close', (code) => {
|
|
242
290
|
clearTimeout(inactivityTimer);
|
|
243
291
|
clearTimeout(maxTimer);
|
|
292
|
+
const elapsed = Math.round((Date.now() - cliStartTime) / 1000);
|
|
293
|
+
console.error(` ✓ [${elapsed}s]`);
|
|
294
|
+
// Cleanup temp schema file
|
|
295
|
+
if (schemaFile) {
|
|
296
|
+
try {
|
|
297
|
+
unlinkSync(schemaFile);
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
// Ignore cleanup errors
|
|
301
|
+
}
|
|
302
|
+
}
|
|
244
303
|
resolve({ stdout, stderr, exitCode: code ?? -1, truncated });
|
|
245
304
|
});
|
|
246
305
|
proc.on('error', (err) => {
|
|
247
306
|
clearTimeout(inactivityTimer);
|
|
248
307
|
clearTimeout(maxTimer);
|
|
308
|
+
console.error(' ✗');
|
|
309
|
+
// Cleanup temp schema file
|
|
310
|
+
if (schemaFile) {
|
|
311
|
+
try {
|
|
312
|
+
unlinkSync(schemaFile);
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
// Ignore cleanup errors
|
|
316
|
+
}
|
|
317
|
+
}
|
|
249
318
|
reject(err);
|
|
250
319
|
});
|
|
251
320
|
});
|
package/dist/adapters/gemini.js
CHANGED
|
@@ -113,11 +113,13 @@ export class GeminiAdapter {
|
|
|
113
113
|
}
|
|
114
114
|
// Parse the output
|
|
115
115
|
let output = parseReviewOutput(result.stdout);
|
|
116
|
+
let usedFallback = false;
|
|
116
117
|
// If JSON parsing fails, try legacy markdown
|
|
117
118
|
if (!output) {
|
|
118
119
|
output = parseLegacyMarkdownOutput(result.stdout, 'gemini');
|
|
120
|
+
usedFallback = true;
|
|
119
121
|
}
|
|
120
|
-
// If
|
|
122
|
+
// If no valid output, retry or fail
|
|
121
123
|
if (!output) {
|
|
122
124
|
if (attempt < MAX_RETRIES) {
|
|
123
125
|
return this.runWithRetry(request, attempt + 1, startTime, 'Output did not match expected JSON schema', result.stdout);
|
|
@@ -134,6 +136,17 @@ export class GeminiAdapter {
|
|
|
134
136
|
executionTimeMs: Date.now() - startTime,
|
|
135
137
|
};
|
|
136
138
|
}
|
|
139
|
+
// If we used fallback and got minimal data, retry
|
|
140
|
+
if (usedFallback && attempt < MAX_RETRIES) {
|
|
141
|
+
const hasMinimalData = output.findings.length === 0 &&
|
|
142
|
+
output.agreements.length === 0 &&
|
|
143
|
+
output.disagreements.length === 0 &&
|
|
144
|
+
output.risk_assessment.summary === 'Unable to parse structured risk assessment';
|
|
145
|
+
if (hasMinimalData) {
|
|
146
|
+
console.error(`[gemini] Received incomplete output (fallback parse with no data), retrying...`);
|
|
147
|
+
return this.runWithRetry(request, attempt + 1, startTime, 'Received markdown output instead of JSON. Please provide valid JSON output.', result.stdout);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
137
150
|
return {
|
|
138
151
|
success: true,
|
|
139
152
|
output,
|
|
@@ -191,6 +204,7 @@ export class GeminiAdapter {
|
|
|
191
204
|
// Gemini CLI uses positional prompt and --yolo for auto-approval
|
|
192
205
|
const args = [
|
|
193
206
|
'--yolo',
|
|
207
|
+
'--output-format', 'json', // Force JSON output
|
|
194
208
|
'--include-directories', workingDir,
|
|
195
209
|
prompt
|
|
196
210
|
];
|
|
@@ -203,6 +217,11 @@ export class GeminiAdapter {
|
|
|
203
217
|
let stderr = '';
|
|
204
218
|
let truncated = false;
|
|
205
219
|
let inactivityTimer;
|
|
220
|
+
const cliStartTime = Date.now();
|
|
221
|
+
let lastProgressTime = cliStartTime;
|
|
222
|
+
let dataChunks = 0;
|
|
223
|
+
// Show initial progress message
|
|
224
|
+
console.error('[gemini] Running review...');
|
|
206
225
|
const maxTimer = setTimeout(() => {
|
|
207
226
|
proc.kill('SIGTERM');
|
|
208
227
|
reject(new Error('MAX_TIMEOUT'));
|
|
@@ -217,6 +236,18 @@ export class GeminiAdapter {
|
|
|
217
236
|
resetInactivityTimer();
|
|
218
237
|
proc.stdout.on('data', (data) => {
|
|
219
238
|
resetInactivityTimer();
|
|
239
|
+
dataChunks++;
|
|
240
|
+
// Show progress dot every 5 chunks
|
|
241
|
+
if (dataChunks % 5 === 0) {
|
|
242
|
+
process.stderr.write('.');
|
|
243
|
+
}
|
|
244
|
+
// Show elapsed time every 10 seconds
|
|
245
|
+
const now = Date.now();
|
|
246
|
+
if (now - lastProgressTime > 10000) {
|
|
247
|
+
const elapsed = Math.round((now - cliStartTime) / 1000);
|
|
248
|
+
console.error(` [${elapsed}s]`);
|
|
249
|
+
lastProgressTime = now;
|
|
250
|
+
}
|
|
220
251
|
if (stdout.length < MAX_BUFFER_SIZE) {
|
|
221
252
|
stdout += data.toString();
|
|
222
253
|
if (stdout.length > MAX_BUFFER_SIZE) {
|
|
@@ -237,11 +268,14 @@ export class GeminiAdapter {
|
|
|
237
268
|
proc.on('close', (code) => {
|
|
238
269
|
clearTimeout(inactivityTimer);
|
|
239
270
|
clearTimeout(maxTimer);
|
|
271
|
+
const elapsed = Math.round((Date.now() - cliStartTime) / 1000);
|
|
272
|
+
console.error(` ✓ [${elapsed}s]`);
|
|
240
273
|
resolve({ stdout, stderr, exitCode: code ?? -1, truncated });
|
|
241
274
|
});
|
|
242
275
|
proc.on('error', (err) => {
|
|
243
276
|
clearTimeout(inactivityTimer);
|
|
244
277
|
clearTimeout(maxTimer);
|
|
278
|
+
console.error(' ✗');
|
|
245
279
|
reject(err);
|
|
246
280
|
});
|
|
247
281
|
});
|