cc-reviewer 1.3.5 → 1.5.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/gemini.js +9 -8
- package/dist/schema.js +85 -2
- package/package.json +1 -1
package/dist/adapters/gemini.js
CHANGED
|
@@ -12,7 +12,7 @@ import { buildSimpleHandoff, buildHandoffPrompt, selectRole, } from '../handoff.
|
|
|
12
12
|
// =============================================================================
|
|
13
13
|
// CONFIGURATION
|
|
14
14
|
// =============================================================================
|
|
15
|
-
const INACTIVITY_TIMEOUT_MS =
|
|
15
|
+
const INACTIVITY_TIMEOUT_MS = 600000; // 10 min of no output = timeout (Gemini buffers entire response with --output-format json)
|
|
16
16
|
const MAX_TIMEOUT_MS = 3600000; // 60 min absolute max
|
|
17
17
|
const MAX_RETRIES = 2;
|
|
18
18
|
const MAX_BUFFER_SIZE = 1024 * 1024; // 1MB max buffer
|
|
@@ -136,15 +136,16 @@ export class GeminiAdapter {
|
|
|
136
136
|
executionTimeMs: Date.now() - startTime,
|
|
137
137
|
};
|
|
138
138
|
}
|
|
139
|
-
// If
|
|
140
|
-
if (
|
|
139
|
+
// If output has no substantive data, retry regardless of parse path
|
|
140
|
+
if (attempt < MAX_RETRIES) {
|
|
141
141
|
const hasMinimalData = output.findings.length === 0 &&
|
|
142
142
|
output.agreements.length === 0 &&
|
|
143
|
-
output.disagreements.length === 0
|
|
144
|
-
output.risk_assessment.summary === 'Unable to parse structured risk assessment';
|
|
143
|
+
output.disagreements.length === 0;
|
|
145
144
|
if (hasMinimalData) {
|
|
146
|
-
console.error(`[gemini] Received
|
|
147
|
-
return this.runWithRetry(request, attempt + 1, startTime,
|
|
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 findings, agreements, or disagreements. Please provide substantive review.', result.stdout);
|
|
148
149
|
}
|
|
149
150
|
}
|
|
150
151
|
return {
|
|
@@ -172,7 +173,7 @@ export class GeminiAdapter {
|
|
|
172
173
|
success: false,
|
|
173
174
|
error: {
|
|
174
175
|
type: 'timeout',
|
|
175
|
-
message: 'No output for
|
|
176
|
+
message: 'No output for 10 minutes - process may be hung',
|
|
176
177
|
},
|
|
177
178
|
suggestion: 'Try a smaller scope or use --focus',
|
|
178
179
|
executionTimeMs: Date.now() - startTime,
|
package/dist/schema.js
CHANGED
|
@@ -225,6 +225,59 @@ export function getReviewOutputJsonSchema() {
|
|
|
225
225
|
}
|
|
226
226
|
};
|
|
227
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Normalize reviewer output that deviates from the strict schema.
|
|
230
|
+
* Handles common patterns from external CLIs (e.g. Gemini returning
|
|
231
|
+
* agreements as strings instead of objects, missing required fields).
|
|
232
|
+
*/
|
|
233
|
+
function normalizeReviewOutput(parsed) {
|
|
234
|
+
const normalized = { ...parsed };
|
|
235
|
+
// Default reviewer if missing
|
|
236
|
+
if (!normalized.reviewer) {
|
|
237
|
+
normalized.reviewer = 'external';
|
|
238
|
+
}
|
|
239
|
+
// Normalize agreements: string[] -> Agreement[]
|
|
240
|
+
if (Array.isArray(normalized.agreements)) {
|
|
241
|
+
normalized.agreements = normalized.agreements.map((a) => {
|
|
242
|
+
if (typeof a === 'string') {
|
|
243
|
+
return { original_claim: a, assessment: 'correct', confidence: 0.7 };
|
|
244
|
+
}
|
|
245
|
+
return a;
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
normalized.agreements = normalized.agreements ?? [];
|
|
250
|
+
}
|
|
251
|
+
// Default missing arrays
|
|
252
|
+
normalized.disagreements = normalized.disagreements ?? [];
|
|
253
|
+
normalized.alternatives = normalized.alternatives ?? [];
|
|
254
|
+
normalized.findings = normalized.findings ?? [];
|
|
255
|
+
// Normalize risk_assessment from simplified formats
|
|
256
|
+
if (!normalized.risk_assessment) {
|
|
257
|
+
const ra = normalized.risk_assessment;
|
|
258
|
+
normalized.risk_assessment = {
|
|
259
|
+
overall_level: 'medium',
|
|
260
|
+
score: 50,
|
|
261
|
+
summary: 'Risk assessment not provided by reviewer',
|
|
262
|
+
top_concerns: [],
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
else if (typeof normalized.risk_assessment === 'object') {
|
|
266
|
+
const ra = normalized.risk_assessment;
|
|
267
|
+
// Handle "level" instead of "overall_level", with case normalization
|
|
268
|
+
if (ra.level && !ra.overall_level) {
|
|
269
|
+
ra.overall_level = typeof ra.level === 'string' ? ra.level.toLowerCase() : ra.level;
|
|
270
|
+
}
|
|
271
|
+
else if (typeof ra.overall_level === 'string') {
|
|
272
|
+
ra.overall_level = ra.overall_level.toLowerCase();
|
|
273
|
+
}
|
|
274
|
+
// Default missing fields
|
|
275
|
+
ra.score = ra.score ?? 50;
|
|
276
|
+
ra.summary = ra.summary ?? 'No summary provided';
|
|
277
|
+
ra.top_concerns = ra.top_concerns ?? [];
|
|
278
|
+
}
|
|
279
|
+
return normalized;
|
|
280
|
+
}
|
|
228
281
|
/**
|
|
229
282
|
* Attempt to parse and validate reviewer output.
|
|
230
283
|
* Returns the validated output or null if invalid.
|
|
@@ -233,8 +286,20 @@ export function parseReviewOutput(rawOutput) {
|
|
|
233
286
|
try {
|
|
234
287
|
// Try to extract JSON from the output (may be wrapped in markdown code blocks)
|
|
235
288
|
let jsonStr = rawOutput;
|
|
289
|
+
// Gemini CLI with --output-format json wraps the response in an envelope:
|
|
290
|
+
// { "session_id": "...", "response": "```json\n{...}\n```" }
|
|
291
|
+
// Try to unwrap this envelope first, but only if it matches the envelope shape.
|
|
292
|
+
try {
|
|
293
|
+
const envelope = JSON.parse(rawOutput);
|
|
294
|
+
if (envelope && typeof envelope.session_id === 'string' && typeof envelope.response === 'string') {
|
|
295
|
+
jsonStr = envelope.response;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
// Not a valid JSON envelope, continue with raw output
|
|
300
|
+
}
|
|
236
301
|
// Extract from ```json ... ``` blocks
|
|
237
|
-
const jsonBlockMatch =
|
|
302
|
+
const jsonBlockMatch = jsonStr.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
238
303
|
if (jsonBlockMatch) {
|
|
239
304
|
jsonStr = jsonBlockMatch[1].trim();
|
|
240
305
|
}
|
|
@@ -245,7 +310,25 @@ export function parseReviewOutput(rawOutput) {
|
|
|
245
310
|
jsonStr = jsonStr.slice(jsonStart, jsonEnd + 1);
|
|
246
311
|
}
|
|
247
312
|
const parsed = JSON.parse(jsonStr);
|
|
248
|
-
|
|
313
|
+
// Try direct parse first
|
|
314
|
+
const result = ReviewOutput.safeParse(parsed);
|
|
315
|
+
if (result.success) {
|
|
316
|
+
return result.data;
|
|
317
|
+
}
|
|
318
|
+
// Normalize common deviations from external CLIs (e.g. Gemini)
|
|
319
|
+
// Only attempt if parsed object has at least one recognizable review field
|
|
320
|
+
const recognizedFields = ['findings', 'agreements', 'disagreements', 'alternatives', 'risk_assessment', 'reviewer'];
|
|
321
|
+
const hasRecognizedField = typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed) &&
|
|
322
|
+
recognizedFields.some(f => f in parsed);
|
|
323
|
+
if (!hasRecognizedField) {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
const normalized = normalizeReviewOutput(parsed);
|
|
327
|
+
const retryResult = ReviewOutput.safeParse(normalized);
|
|
328
|
+
if (retryResult.success) {
|
|
329
|
+
return retryResult.data;
|
|
330
|
+
}
|
|
331
|
+
return null;
|
|
249
332
|
}
|
|
250
333
|
catch {
|
|
251
334
|
return null;
|