cc-reviewer 1.5.1 → 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.
@@ -138,16 +138,29 @@ export class CodexAdapter {
138
138
  executionTimeMs: Date.now() - startTime,
139
139
  };
140
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);
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,
@@ -236,6 +249,11 @@ export class CodexAdapter {
236
249
  stdio: ['pipe', 'pipe', 'pipe'], // stdin is pipe for prompt delivery
237
250
  env: { ...process.env }
238
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
+ });
239
257
  // Deliver prompt via stdin
240
258
  proc.stdin.write(prompt);
241
259
  proc.stdin.end();
@@ -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 regardless of parse path
140
- if (attempt < MAX_RETRIES) {
141
- const hasMinimalData = output.findings.length === 0 &&
142
- output.agreements.length === 0 &&
143
- output.disagreements.length === 0;
144
- if (hasMinimalData) {
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,
@@ -209,12 +221,18 @@ export class GeminiAdapter {
209
221
  '--yolo',
210
222
  '--output-format', 'json', // Force JSON output
211
223
  '--include-directories', workingDir,
224
+ '-p', '', // Force headless mode; actual prompt delivered via stdin
212
225
  ];
213
226
  const proc = spawn('gemini', args, {
214
227
  cwd: workingDir,
215
228
  stdio: ['pipe', 'pipe', 'pipe'], // stdin is pipe for prompt delivery
216
229
  env: { ...process.env }
217
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
+ });
218
236
  // Deliver prompt via stdin — more stable than args for complex content
219
237
  proc.stdin.write(prompt);
220
238
  proc.stdin.end();
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;
@@ -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`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-reviewer",
3
- "version": "1.5.1",
3
+ "version": "1.5.3",
4
4
  "description": "MCP server for Claude Code - Get second-opinion feedback from Codex/Gemini CLIs",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",