cc-reviewer 1.0.3 → 1.0.5

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/cli/codex.js CHANGED
@@ -8,7 +8,9 @@ import { spawn } from 'child_process';
8
8
  import { existsSync } from 'fs';
9
9
  import { build7SectionPrompt, buildDeveloperInstructions, buildRetryPrompt, isValidFeedbackOutput } from '../prompt.js';
10
10
  import { createTimeoutError, createCliNotFoundError, getSuggestion } from '../errors.js';
11
- const TIMEOUT_MS = 180000; // 3 minutes (Codex can be slow)
11
+ // Activity-based timeout: reset on output, kill on silence
12
+ const INACTIVITY_TIMEOUT_MS = 120000; // 2 min of no output = timeout
13
+ const MAX_TIMEOUT_MS = 3600000; // 60 min absolute max (edge case safety)
12
14
  const MAX_RETRIES = 2;
13
15
  const MAX_BUFFER_SIZE = 1024 * 1024; // 1MB max buffer to prevent memory issues
14
16
  /**
@@ -45,7 +47,7 @@ async function runWithRetry(request, attempt, previousError, previousOutput) {
45
47
  // Codex exec doesn't have a separate system instruction flag
46
48
  const fullPrompt = `${developerInstructions}\n\n---\n\n${basePrompt}`;
47
49
  // Run the CLI
48
- const result = await runCodexCli(fullPrompt, request.workingDir);
50
+ const result = await runCodexCli(fullPrompt, request.workingDir, request.reasoningEffort || 'high');
49
51
  // Check for CLI errors
50
52
  if (result.exitCode !== 0) {
51
53
  // Check for specific error patterns in stderr
@@ -136,11 +138,15 @@ async function runWithRetry(request, attempt, previousError, previousOutput) {
136
138
  model: 'codex'
137
139
  };
138
140
  }
139
- if (err.message === 'TIMEOUT') {
141
+ if (err.message === 'TIMEOUT' || err.message === 'MAX_TIMEOUT') {
142
+ const isMaxTimeout = err.message === 'MAX_TIMEOUT';
143
+ const timeoutMs = isMaxTimeout ? MAX_TIMEOUT_MS : INACTIVITY_TIMEOUT_MS;
140
144
  return {
141
145
  success: false,
142
- error: createTimeoutError('codex', TIMEOUT_MS),
143
- suggestion: getSuggestion(createTimeoutError('codex', TIMEOUT_MS)),
146
+ error: createTimeoutError('codex', timeoutMs),
147
+ suggestion: isMaxTimeout
148
+ ? 'Task exceeded 60 minute maximum. Try a smaller scope.'
149
+ : 'No output for 2 minutes. Process may be hung. Try a smaller scope or use --focus.',
144
150
  model: 'codex'
145
151
  };
146
152
  }
@@ -160,17 +166,17 @@ async function runWithRetry(request, attempt, previousError, previousOutput) {
160
166
  /**
161
167
  * Execute the Codex CLI in non-interactive mode
162
168
  *
163
- * Uses: codex exec -m gpt-5.2-codex -c model_reasoning_effort="xhigh" \
169
+ * Uses: codex exec -m gpt-5.2-codex -c model_reasoning_effort="high|xhigh" \
164
170
  * -c model_reasoning_summary_format=experimental \
165
171
  * --dangerously-bypass-approvals-and-sandbox "<prompt>"
166
172
  */
167
- function runCodexCli(prompt, workingDir) {
173
+ function runCodexCli(prompt, workingDir, reasoningEffort = 'high') {
168
174
  return new Promise((resolve, reject) => {
169
175
  // Build CLI arguments for non-interactive execution
170
176
  const args = [
171
177
  'exec',
172
178
  '-m', 'gpt-5.2-codex',
173
- '-c', 'model_reasoning_effort=xhigh',
179
+ '-c', `model_reasoning_effort=${reasoningEffort}`,
174
180
  '-c', 'model_reasoning_summary_format=experimental',
175
181
  '--dangerously-bypass-approvals-and-sandbox',
176
182
  '--skip-git-repo-check',
@@ -185,7 +191,24 @@ function runCodexCli(prompt, workingDir) {
185
191
  let stdout = '';
186
192
  let stderr = '';
187
193
  let truncated = false;
194
+ let inactivityTimer;
195
+ // Max timeout - absolute cap (60 min)
196
+ const maxTimer = setTimeout(() => {
197
+ proc.kill('SIGTERM');
198
+ reject(new Error('MAX_TIMEOUT'));
199
+ }, MAX_TIMEOUT_MS);
200
+ // Activity-based timeout - reset on any output
201
+ const resetInactivityTimer = () => {
202
+ clearTimeout(inactivityTimer);
203
+ inactivityTimer = setTimeout(() => {
204
+ proc.kill('SIGTERM');
205
+ reject(new Error('TIMEOUT'));
206
+ }, INACTIVITY_TIMEOUT_MS);
207
+ };
208
+ // Start inactivity timer
209
+ resetInactivityTimer();
188
210
  proc.stdout.on('data', (data) => {
211
+ resetInactivityTimer(); // Still streaming = reset timer
189
212
  if (stdout.length < MAX_BUFFER_SIZE) {
190
213
  stdout += data.toString();
191
214
  if (stdout.length > MAX_BUFFER_SIZE) {
@@ -195,6 +218,7 @@ function runCodexCli(prompt, workingDir) {
195
218
  }
196
219
  });
197
220
  proc.stderr.on('data', (data) => {
221
+ resetInactivityTimer(); // Still streaming = reset timer
198
222
  if (stderr.length < MAX_BUFFER_SIZE) {
199
223
  stderr += data.toString();
200
224
  if (stderr.length > MAX_BUFFER_SIZE) {
@@ -202,13 +226,9 @@ function runCodexCli(prompt, workingDir) {
202
226
  }
203
227
  }
204
228
  });
205
- // Timeout handling
206
- const timeout = setTimeout(() => {
207
- proc.kill('SIGTERM');
208
- reject(new Error('TIMEOUT'));
209
- }, TIMEOUT_MS);
210
229
  proc.on('close', (code) => {
211
- clearTimeout(timeout);
230
+ clearTimeout(inactivityTimer);
231
+ clearTimeout(maxTimer);
212
232
  resolve({
213
233
  stdout,
214
234
  stderr,
@@ -217,7 +237,8 @@ function runCodexCli(prompt, workingDir) {
217
237
  });
218
238
  });
219
239
  proc.on('error', (err) => {
220
- clearTimeout(timeout);
240
+ clearTimeout(inactivityTimer);
241
+ clearTimeout(maxTimer);
221
242
  reject(err);
222
243
  });
223
244
  });
@@ -9,7 +9,9 @@ import { spawn } from 'child_process';
9
9
  import { existsSync } from 'fs';
10
10
  import { build7SectionPrompt, buildDeveloperInstructions, buildRetryPrompt, isValidFeedbackOutput } from '../prompt.js';
11
11
  import { createTimeoutError, createCliNotFoundError, getSuggestion } from '../errors.js';
12
- const TIMEOUT_MS = 180000; // 3 minutes
12
+ // Activity-based timeout: reset on output, kill on silence
13
+ const INACTIVITY_TIMEOUT_MS = 120000; // 2 min of no output = timeout
14
+ const MAX_TIMEOUT_MS = 3600000; // 60 min absolute max (edge case safety)
13
15
  const MAX_RETRIES = 2;
14
16
  const MAX_BUFFER_SIZE = 1024 * 1024; // 1MB max buffer to prevent memory issues
15
17
  /**
@@ -139,11 +141,15 @@ async function runWithRetry(request, attempt, previousError, previousOutput) {
139
141
  model: 'gemini'
140
142
  };
141
143
  }
142
- if (err.message === 'TIMEOUT') {
144
+ if (err.message === 'TIMEOUT' || err.message === 'MAX_TIMEOUT') {
145
+ const isMaxTimeout = err.message === 'MAX_TIMEOUT';
146
+ const timeoutMs = isMaxTimeout ? MAX_TIMEOUT_MS : INACTIVITY_TIMEOUT_MS;
143
147
  return {
144
148
  success: false,
145
- error: createTimeoutError('gemini', TIMEOUT_MS),
146
- suggestion: getSuggestion(createTimeoutError('gemini', TIMEOUT_MS)),
149
+ error: createTimeoutError('gemini', timeoutMs),
150
+ suggestion: isMaxTimeout
151
+ ? 'Task exceeded 60 minute maximum. Try a smaller scope.'
152
+ : 'No output for 2 minutes. Process may be hung. Try a smaller scope or use --focus.',
147
153
  model: 'gemini'
148
154
  };
149
155
  }
@@ -182,7 +188,24 @@ function runGeminiCli(prompt, workingDir) {
182
188
  let stdout = '';
183
189
  let stderr = '';
184
190
  let truncated = false;
191
+ let inactivityTimer;
192
+ // Max timeout - absolute cap (60 min)
193
+ const maxTimer = setTimeout(() => {
194
+ proc.kill('SIGTERM');
195
+ reject(new Error('MAX_TIMEOUT'));
196
+ }, MAX_TIMEOUT_MS);
197
+ // Activity-based timeout - reset on any output
198
+ const resetInactivityTimer = () => {
199
+ clearTimeout(inactivityTimer);
200
+ inactivityTimer = setTimeout(() => {
201
+ proc.kill('SIGTERM');
202
+ reject(new Error('TIMEOUT'));
203
+ }, INACTIVITY_TIMEOUT_MS);
204
+ };
205
+ // Start inactivity timer
206
+ resetInactivityTimer();
185
207
  proc.stdout.on('data', (data) => {
208
+ resetInactivityTimer(); // Still streaming = reset timer
186
209
  if (stdout.length < MAX_BUFFER_SIZE) {
187
210
  stdout += data.toString();
188
211
  if (stdout.length > MAX_BUFFER_SIZE) {
@@ -192,6 +215,7 @@ function runGeminiCli(prompt, workingDir) {
192
215
  }
193
216
  });
194
217
  proc.stderr.on('data', (data) => {
218
+ resetInactivityTimer(); // Still streaming = reset timer
195
219
  if (stderr.length < MAX_BUFFER_SIZE) {
196
220
  stderr += data.toString();
197
221
  if (stderr.length > MAX_BUFFER_SIZE) {
@@ -199,13 +223,9 @@ function runGeminiCli(prompt, workingDir) {
199
223
  }
200
224
  }
201
225
  });
202
- // Timeout handling
203
- const timeout = setTimeout(() => {
204
- proc.kill('SIGTERM');
205
- reject(new Error('TIMEOUT'));
206
- }, TIMEOUT_MS);
207
226
  proc.on('close', (code) => {
208
- clearTimeout(timeout);
227
+ clearTimeout(inactivityTimer);
228
+ clearTimeout(maxTimer);
209
229
  resolve({
210
230
  stdout,
211
231
  stderr,
@@ -214,7 +234,8 @@ function runGeminiCli(prompt, workingDir) {
214
234
  });
215
235
  });
216
236
  proc.on('error', (err) => {
217
- clearTimeout(timeout);
237
+ clearTimeout(inactivityTimer);
238
+ clearTimeout(maxTimer);
218
239
  reject(err);
219
240
  });
220
241
  });
@@ -9,6 +9,7 @@ export declare const FeedbackInputSchema: z.ZodObject<{
9
9
  analyzedFiles: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
10
10
  focusAreas: z.ZodOptional<z.ZodArray<z.ZodEnum<["security", "performance", "architecture", "correctness", "maintainability", "scalability", "testing", "documentation"]>, "many">>;
11
11
  customPrompt: z.ZodOptional<z.ZodString>;
12
+ reasoningEffort: z.ZodOptional<z.ZodEnum<["high", "xhigh"]>>;
12
13
  }, "strip", z.ZodTypeAny, {
13
14
  workingDir: string;
14
15
  ccOutput: string;
@@ -16,6 +17,7 @@ export declare const FeedbackInputSchema: z.ZodObject<{
16
17
  analyzedFiles?: string[] | undefined;
17
18
  focusAreas?: ("security" | "performance" | "architecture" | "correctness" | "maintainability" | "scalability" | "testing" | "documentation")[] | undefined;
18
19
  customPrompt?: string | undefined;
20
+ reasoningEffort?: "high" | "xhigh" | undefined;
19
21
  }, {
20
22
  workingDir: string;
21
23
  ccOutput: string;
@@ -23,6 +25,7 @@ export declare const FeedbackInputSchema: z.ZodObject<{
23
25
  analyzedFiles?: string[] | undefined;
24
26
  focusAreas?: ("security" | "performance" | "architecture" | "correctness" | "maintainability" | "scalability" | "testing" | "documentation")[] | undefined;
25
27
  customPrompt?: string | undefined;
28
+ reasoningEffort?: "high" | "xhigh" | undefined;
26
29
  }>;
27
30
  export type FeedbackInput = z.infer<typeof FeedbackInputSchema>;
28
31
  /**
@@ -16,7 +16,8 @@ export const FeedbackInputSchema = z.object({
16
16
  'security', 'performance', 'architecture', 'correctness',
17
17
  'maintainability', 'scalability', 'testing', 'documentation'
18
18
  ])).optional().describe('Areas to focus the review on'),
19
- customPrompt: z.string().optional().describe('Custom instructions for the reviewer')
19
+ customPrompt: z.string().optional().describe('Custom instructions for the reviewer'),
20
+ reasoningEffort: z.enum(['high', 'xhigh']).optional().describe('Codex reasoning effort level (default: high, use xhigh for deeper analysis)')
20
21
  });
21
22
  /**
22
23
  * Convert tool input to FeedbackRequest
@@ -28,7 +29,8 @@ function toFeedbackRequest(input) {
28
29
  outputType: input.outputType,
29
30
  analyzedFiles: input.analyzedFiles,
30
31
  focusAreas: input.focusAreas,
31
- customPrompt: input.customPrompt
32
+ customPrompt: input.customPrompt,
33
+ reasoningEffort: input.reasoningEffort
32
34
  };
33
35
  }
34
36
  /**
package/dist/types.d.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  export type OutputType = 'plan' | 'findings' | 'analysis' | 'proposal';
5
5
  export type FocusArea = 'security' | 'performance' | 'architecture' | 'correctness' | 'maintainability' | 'scalability' | 'testing' | 'documentation';
6
6
  export type CliType = 'codex' | 'gemini';
7
+ export type ReasoningEffort = 'high' | 'xhigh';
7
8
  export interface FeedbackRequest {
8
9
  workingDir: string;
9
10
  ccOutput: string;
@@ -11,6 +12,7 @@ export interface FeedbackRequest {
11
12
  analyzedFiles?: string[];
12
13
  focusAreas?: FocusArea[];
13
14
  customPrompt?: string;
15
+ reasoningEffort?: ReasoningEffort;
14
16
  }
15
17
  export interface FeedbackSuccess {
16
18
  success: true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-reviewer",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
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",