cc-reviewer 1.5.0 → 1.5.3
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 +34 -11
- package/dist/adapters/gemini.js +31 -9
- package/dist/schema.d.ts +82 -0
- package/dist/schema.js +51 -0
- package/dist/tools/feedback.js +21 -0
- package/package.json +1 -1
package/dist/adapters/codex.js
CHANGED
|
@@ -138,16 +138,29 @@ export class CodexAdapter {
|
|
|
138
138
|
executionTimeMs: Date.now() - startTime,
|
|
139
139
|
};
|
|
140
140
|
}
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
141
|
+
// Check for empty/minimal data on any parse path
|
|
142
|
+
const hasMinimalData = output.findings.length === 0 &&
|
|
143
|
+
output.agreements.length === 0 &&
|
|
144
|
+
output.disagreements.length === 0;
|
|
145
|
+
if (hasMinimalData) {
|
|
146
|
+
if (attempt < MAX_RETRIES) {
|
|
147
|
+
console.error(`[codex] Received empty output, retrying...`);
|
|
148
|
+
return this.runWithRetry(request, attempt + 1, startTime, usedFallback
|
|
149
|
+
? 'Received markdown output instead of JSON. Please provide valid JSON output.'
|
|
150
|
+
: 'Output contained no findings, agreements, or disagreements. Please provide substantive review.', result.stdout);
|
|
150
151
|
}
|
|
152
|
+
// Final attempt with no data — report failure
|
|
153
|
+
return {
|
|
154
|
+
success: false,
|
|
155
|
+
error: {
|
|
156
|
+
type: 'parse_error',
|
|
157
|
+
message: 'Reviewer returned empty output after retries',
|
|
158
|
+
details: { rawOutput: result.stdout.slice(0, 1000) },
|
|
159
|
+
},
|
|
160
|
+
suggestion: 'The model returned no substantive review. Try a different focus area.',
|
|
161
|
+
rawOutput: result.stdout,
|
|
162
|
+
executionTimeMs: Date.now() - startTime,
|
|
163
|
+
};
|
|
151
164
|
}
|
|
152
165
|
return {
|
|
153
166
|
success: true,
|
|
@@ -228,12 +241,22 @@ export class CodexAdapter {
|
|
|
228
241
|
if (schemaFile) {
|
|
229
242
|
args.push('--output-schema', schemaFile);
|
|
230
243
|
}
|
|
231
|
-
|
|
244
|
+
// Use '-' to read prompt from stdin — more stable for complex prompts
|
|
245
|
+
// with newlines, backticks, JSON templates, etc.
|
|
246
|
+
args.push('-');
|
|
232
247
|
const proc = spawn('codex', args, {
|
|
233
248
|
cwd: workingDir,
|
|
234
|
-
stdio: ['
|
|
249
|
+
stdio: ['pipe', 'pipe', 'pipe'], // stdin is pipe for prompt delivery
|
|
235
250
|
env: { ...process.env }
|
|
236
251
|
});
|
|
252
|
+
// Guard against EPIPE if the child exits before consuming stdin.
|
|
253
|
+
// Log but don't reject — let the `close` handler capture the real exit code.
|
|
254
|
+
proc.stdin.on('error', (err) => {
|
|
255
|
+
console.error(`[codex] stdin error (likely EPIPE): ${err.message}`);
|
|
256
|
+
});
|
|
257
|
+
// Deliver prompt via stdin
|
|
258
|
+
proc.stdin.write(prompt);
|
|
259
|
+
proc.stdin.end();
|
|
237
260
|
let stdout = '';
|
|
238
261
|
let stderr = '';
|
|
239
262
|
let truncated = false;
|
package/dist/adapters/gemini.js
CHANGED
|
@@ -136,17 +136,29 @@ export class GeminiAdapter {
|
|
|
136
136
|
executionTimeMs: Date.now() - startTime,
|
|
137
137
|
};
|
|
138
138
|
}
|
|
139
|
-
// If output has no substantive data, retry
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (
|
|
139
|
+
// If output has no substantive data, retry or fail
|
|
140
|
+
const hasMinimalData = output.findings.length === 0 &&
|
|
141
|
+
output.agreements.length === 0 &&
|
|
142
|
+
output.disagreements.length === 0;
|
|
143
|
+
if (hasMinimalData) {
|
|
144
|
+
if (attempt < MAX_RETRIES) {
|
|
145
145
|
console.error(`[gemini] Received empty output, retrying...`);
|
|
146
146
|
return this.runWithRetry(request, attempt + 1, startTime, usedFallback
|
|
147
147
|
? 'Received markdown output instead of JSON. Please provide valid JSON output.'
|
|
148
148
|
: 'Output contained no findings, agreements, or disagreements. Please provide substantive review.', result.stdout);
|
|
149
149
|
}
|
|
150
|
+
// Final attempt with no data — report failure
|
|
151
|
+
return {
|
|
152
|
+
success: false,
|
|
153
|
+
error: {
|
|
154
|
+
type: 'parse_error',
|
|
155
|
+
message: 'Reviewer returned empty output after retries',
|
|
156
|
+
details: { rawOutput: result.stdout.slice(0, 1000) },
|
|
157
|
+
},
|
|
158
|
+
suggestion: 'The model returned no substantive review. Try a different focus area.',
|
|
159
|
+
rawOutput: result.stdout,
|
|
160
|
+
executionTimeMs: Date.now() - startTime,
|
|
161
|
+
};
|
|
150
162
|
}
|
|
151
163
|
return {
|
|
152
164
|
success: true,
|
|
@@ -202,18 +214,28 @@ export class GeminiAdapter {
|
|
|
202
214
|
}
|
|
203
215
|
runCli(prompt, workingDir) {
|
|
204
216
|
return new Promise((resolve, reject) => {
|
|
205
|
-
// Gemini CLI uses
|
|
217
|
+
// Gemini CLI uses --yolo for auto-approval, prompt passed via stdin
|
|
218
|
+
// to avoid escaping issues with complex prompts containing newlines,
|
|
219
|
+
// backticks, JSON templates, etc.
|
|
206
220
|
const args = [
|
|
207
221
|
'--yolo',
|
|
208
222
|
'--output-format', 'json', // Force JSON output
|
|
209
223
|
'--include-directories', workingDir,
|
|
210
|
-
prompt
|
|
224
|
+
'-p', '', // Force headless mode; actual prompt delivered via stdin
|
|
211
225
|
];
|
|
212
226
|
const proc = spawn('gemini', args, {
|
|
213
227
|
cwd: workingDir,
|
|
214
|
-
stdio: ['
|
|
228
|
+
stdio: ['pipe', 'pipe', 'pipe'], // stdin is pipe for prompt delivery
|
|
215
229
|
env: { ...process.env }
|
|
216
230
|
});
|
|
231
|
+
// Guard against EPIPE if the child exits before consuming stdin.
|
|
232
|
+
// Log but don't reject — let the `close` handler capture the real exit code.
|
|
233
|
+
proc.stdin.on('error', (err) => {
|
|
234
|
+
console.error(`[gemini] stdin error (likely EPIPE): ${err.message}`);
|
|
235
|
+
});
|
|
236
|
+
// Deliver prompt via stdin — more stable than args for complex content
|
|
237
|
+
proc.stdin.write(prompt);
|
|
238
|
+
proc.stdin.end();
|
|
217
239
|
let stdout = '';
|
|
218
240
|
let stderr = '';
|
|
219
241
|
let truncated = false;
|
package/dist/schema.d.ts
CHANGED
|
@@ -200,6 +200,37 @@ export declare const RiskAssessment: z.ZodObject<{
|
|
|
200
200
|
mitigations?: string[] | undefined;
|
|
201
201
|
}>;
|
|
202
202
|
export type RiskAssessment = z.infer<typeof RiskAssessment>;
|
|
203
|
+
export declare const UncertaintyResponse: z.ZodObject<{
|
|
204
|
+
uncertainty_index: z.ZodNumber;
|
|
205
|
+
verified: z.ZodBoolean;
|
|
206
|
+
finding: z.ZodString;
|
|
207
|
+
recommendation: z.ZodOptional<z.ZodString>;
|
|
208
|
+
}, "strip", z.ZodTypeAny, {
|
|
209
|
+
verified: boolean;
|
|
210
|
+
uncertainty_index: number;
|
|
211
|
+
finding: string;
|
|
212
|
+
recommendation?: string | undefined;
|
|
213
|
+
}, {
|
|
214
|
+
verified: boolean;
|
|
215
|
+
uncertainty_index: number;
|
|
216
|
+
finding: string;
|
|
217
|
+
recommendation?: string | undefined;
|
|
218
|
+
}>;
|
|
219
|
+
export type UncertaintyResponse = z.infer<typeof UncertaintyResponse>;
|
|
220
|
+
export declare const QuestionAnswer: z.ZodObject<{
|
|
221
|
+
question_index: z.ZodNumber;
|
|
222
|
+
answer: z.ZodString;
|
|
223
|
+
confidence: z.ZodOptional<z.ZodNumber>;
|
|
224
|
+
}, "strip", z.ZodTypeAny, {
|
|
225
|
+
question_index: number;
|
|
226
|
+
answer: string;
|
|
227
|
+
confidence?: number | undefined;
|
|
228
|
+
}, {
|
|
229
|
+
question_index: number;
|
|
230
|
+
answer: string;
|
|
231
|
+
confidence?: number | undefined;
|
|
232
|
+
}>;
|
|
233
|
+
export type QuestionAnswer = z.infer<typeof QuestionAnswer>;
|
|
203
234
|
export declare const ReviewOutput: z.ZodObject<{
|
|
204
235
|
reviewer: z.ZodString;
|
|
205
236
|
timestamp: z.ZodOptional<z.ZodString>;
|
|
@@ -348,6 +379,35 @@ export declare const ReviewOutput: z.ZodObject<{
|
|
|
348
379
|
alternative: string;
|
|
349
380
|
recommendation: "strongly_prefer" | "consider" | "situational" | "informational";
|
|
350
381
|
}>, "many">;
|
|
382
|
+
uncertainty_responses: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
383
|
+
uncertainty_index: z.ZodNumber;
|
|
384
|
+
verified: z.ZodBoolean;
|
|
385
|
+
finding: z.ZodString;
|
|
386
|
+
recommendation: z.ZodOptional<z.ZodString>;
|
|
387
|
+
}, "strip", z.ZodTypeAny, {
|
|
388
|
+
verified: boolean;
|
|
389
|
+
uncertainty_index: number;
|
|
390
|
+
finding: string;
|
|
391
|
+
recommendation?: string | undefined;
|
|
392
|
+
}, {
|
|
393
|
+
verified: boolean;
|
|
394
|
+
uncertainty_index: number;
|
|
395
|
+
finding: string;
|
|
396
|
+
recommendation?: string | undefined;
|
|
397
|
+
}>, "many">>;
|
|
398
|
+
question_answers: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
399
|
+
question_index: z.ZodNumber;
|
|
400
|
+
answer: z.ZodString;
|
|
401
|
+
confidence: z.ZodOptional<z.ZodNumber>;
|
|
402
|
+
}, "strip", z.ZodTypeAny, {
|
|
403
|
+
question_index: number;
|
|
404
|
+
answer: string;
|
|
405
|
+
confidence?: number | undefined;
|
|
406
|
+
}, {
|
|
407
|
+
question_index: number;
|
|
408
|
+
answer: string;
|
|
409
|
+
confidence?: number | undefined;
|
|
410
|
+
}>, "many">>;
|
|
351
411
|
risk_assessment: z.ZodObject<{
|
|
352
412
|
overall_level: z.ZodEnum<["critical", "high", "medium", "low", "minimal"]>;
|
|
353
413
|
score: z.ZodNumber;
|
|
@@ -424,6 +484,17 @@ export declare const ReviewOutput: z.ZodObject<{
|
|
|
424
484
|
mitigations?: string[] | undefined;
|
|
425
485
|
};
|
|
426
486
|
timestamp?: string | undefined;
|
|
487
|
+
uncertainty_responses?: {
|
|
488
|
+
verified: boolean;
|
|
489
|
+
uncertainty_index: number;
|
|
490
|
+
finding: string;
|
|
491
|
+
recommendation?: string | undefined;
|
|
492
|
+
}[] | undefined;
|
|
493
|
+
question_answers?: {
|
|
494
|
+
question_index: number;
|
|
495
|
+
answer: string;
|
|
496
|
+
confidence?: number | undefined;
|
|
497
|
+
}[] | undefined;
|
|
427
498
|
files_examined?: string[] | undefined;
|
|
428
499
|
execution_notes?: string | undefined;
|
|
429
500
|
}, {
|
|
@@ -481,6 +552,17 @@ export declare const ReviewOutput: z.ZodObject<{
|
|
|
481
552
|
mitigations?: string[] | undefined;
|
|
482
553
|
};
|
|
483
554
|
timestamp?: string | undefined;
|
|
555
|
+
uncertainty_responses?: {
|
|
556
|
+
verified: boolean;
|
|
557
|
+
uncertainty_index: number;
|
|
558
|
+
finding: string;
|
|
559
|
+
recommendation?: string | undefined;
|
|
560
|
+
}[] | undefined;
|
|
561
|
+
question_answers?: {
|
|
562
|
+
question_index: number;
|
|
563
|
+
answer: string;
|
|
564
|
+
confidence?: number | undefined;
|
|
565
|
+
}[] | undefined;
|
|
484
566
|
files_examined?: string[] | undefined;
|
|
485
567
|
execution_notes?: string | undefined;
|
|
486
568
|
}>;
|
package/dist/schema.js
CHANGED
|
@@ -94,6 +94,20 @@ export const RiskAssessment = z.object({
|
|
|
94
94
|
mitigations: z.array(z.string()).optional().describe('Suggested mitigations'),
|
|
95
95
|
});
|
|
96
96
|
// =============================================================================
|
|
97
|
+
// UNCERTAINTY & QUESTION RESPONSES
|
|
98
|
+
// =============================================================================
|
|
99
|
+
export const UncertaintyResponse = z.object({
|
|
100
|
+
uncertainty_index: z.number().int().positive().describe('1-based index of the uncertainty being addressed'),
|
|
101
|
+
verified: z.boolean().describe('Whether the uncertainty was verified'),
|
|
102
|
+
finding: z.string().describe('What the reviewer found'),
|
|
103
|
+
recommendation: z.string().optional().describe('What CC should do'),
|
|
104
|
+
});
|
|
105
|
+
export const QuestionAnswer = z.object({
|
|
106
|
+
question_index: z.number().int().positive().describe('1-based index of the question being answered'),
|
|
107
|
+
answer: z.string().describe('The reviewer answer'),
|
|
108
|
+
confidence: ConfidenceScore.optional().describe('Confidence in the answer (0-1)'),
|
|
109
|
+
});
|
|
110
|
+
// =============================================================================
|
|
97
111
|
// COMPLETE REVIEW OUTPUT (Single Reviewer)
|
|
98
112
|
// =============================================================================
|
|
99
113
|
export const ReviewOutput = z.object({
|
|
@@ -104,6 +118,9 @@ export const ReviewOutput = z.object({
|
|
|
104
118
|
agreements: z.array(Agreement).describe("Validation of CC's correct assessments"),
|
|
105
119
|
disagreements: z.array(Disagreement).describe("Challenges to CC's claims"),
|
|
106
120
|
alternatives: z.array(Alternative).describe('Alternative approaches to consider'),
|
|
121
|
+
// Responses to CC's uncertainties and questions
|
|
122
|
+
uncertainty_responses: z.array(UncertaintyResponse).optional().describe('Responses to CC uncertainties'),
|
|
123
|
+
question_answers: z.array(QuestionAnswer).optional().describe('Answers to CC questions'),
|
|
107
124
|
// Summary
|
|
108
125
|
risk_assessment: RiskAssessment,
|
|
109
126
|
// Metadata
|
|
@@ -211,6 +228,33 @@ export function getReviewOutputJsonSchema() {
|
|
|
211
228
|
}
|
|
212
229
|
}
|
|
213
230
|
},
|
|
231
|
+
uncertainty_responses: {
|
|
232
|
+
type: 'array',
|
|
233
|
+
items: {
|
|
234
|
+
type: 'object',
|
|
235
|
+
additionalProperties: false,
|
|
236
|
+
required: ['uncertainty_index', 'verified', 'finding'],
|
|
237
|
+
properties: {
|
|
238
|
+
uncertainty_index: { type: 'integer', minimum: 1 },
|
|
239
|
+
verified: { type: 'boolean' },
|
|
240
|
+
finding: { type: 'string' },
|
|
241
|
+
recommendation: { type: 'string' }
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
question_answers: {
|
|
246
|
+
type: 'array',
|
|
247
|
+
items: {
|
|
248
|
+
type: 'object',
|
|
249
|
+
additionalProperties: false,
|
|
250
|
+
required: ['question_index', 'answer'],
|
|
251
|
+
properties: {
|
|
252
|
+
question_index: { type: 'integer', minimum: 1 },
|
|
253
|
+
answer: { type: 'string' },
|
|
254
|
+
confidence: { type: 'number', minimum: 0, maximum: 1 }
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
},
|
|
214
258
|
risk_assessment: {
|
|
215
259
|
type: 'object',
|
|
216
260
|
additionalProperties: false,
|
|
@@ -252,6 +296,13 @@ function normalizeReviewOutput(parsed) {
|
|
|
252
296
|
normalized.disagreements = normalized.disagreements ?? [];
|
|
253
297
|
normalized.alternatives = normalized.alternatives ?? [];
|
|
254
298
|
normalized.findings = normalized.findings ?? [];
|
|
299
|
+
// Normalize optional response arrays — drop non-array values
|
|
300
|
+
if (normalized.uncertainty_responses !== undefined && !Array.isArray(normalized.uncertainty_responses)) {
|
|
301
|
+
delete normalized.uncertainty_responses;
|
|
302
|
+
}
|
|
303
|
+
if (normalized.question_answers !== undefined && !Array.isArray(normalized.question_answers)) {
|
|
304
|
+
delete normalized.question_answers;
|
|
305
|
+
}
|
|
255
306
|
// Normalize risk_assessment from simplified formats
|
|
256
307
|
if (!normalized.risk_assessment) {
|
|
257
308
|
const ra = normalized.risk_assessment;
|
package/dist/tools/feedback.js
CHANGED
|
@@ -77,6 +77,27 @@ function formatSingleReviewResponse(result, modelName) {
|
|
|
77
77
|
lines.push('');
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
+
// Uncertainty Responses
|
|
81
|
+
if (output.uncertainty_responses && output.uncertainty_responses.length > 0) {
|
|
82
|
+
lines.push(`### Uncertainty Responses (${output.uncertainty_responses.length})\n`);
|
|
83
|
+
for (const ur of output.uncertainty_responses) {
|
|
84
|
+
const icon = ur.verified ? '✓' : '✗';
|
|
85
|
+
lines.push(`${icon} **Uncertainty #${ur.uncertainty_index}**: ${ur.finding}`);
|
|
86
|
+
if (ur.recommendation) {
|
|
87
|
+
lines.push(` → ${ur.recommendation}`);
|
|
88
|
+
}
|
|
89
|
+
lines.push('');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Question Answers
|
|
93
|
+
if (output.question_answers && output.question_answers.length > 0) {
|
|
94
|
+
lines.push(`### Question Answers (${output.question_answers.length})\n`);
|
|
95
|
+
for (const qa of output.question_answers) {
|
|
96
|
+
const confidence = qa.confidence !== undefined ? ` [${Math.round(qa.confidence * 100)}%]` : '';
|
|
97
|
+
lines.push(`**Q${qa.question_index}**${confidence}: ${qa.answer}`);
|
|
98
|
+
lines.push('');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
80
101
|
// Agreements
|
|
81
102
|
if (output.agreements.length > 0) {
|
|
82
103
|
lines.push(`### Agreements (${output.agreements.length})\n`);
|