cc-reviewer 4.0.0 → 5.1.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.
@@ -1,13 +1,25 @@
1
1
  # Multi Review
2
2
 
3
- Get parallel reviews from Codex, Gemini, and a fresh Claude (Opus) instance, raw output for manual synthesis.
3
+ Get parallel standard AND adversarial reviews from all available models (Codex, Gemini, Claude Opus).
4
+
5
+ Each model runs twice: once as a standard reviewer (finding bugs, issues, improvements) and once as an adversarial challenger (breaking confidence in the change, questioning assumptions, targeting hidden failure paths). Results are presented in two sections.
6
+
7
+ Use `$ARGUMENTS` to steer the adversarial focus (e.g., "focus the challenge on race conditions and rollback safety").
4
8
 
5
9
  ## Arguments
6
- - `$ARGUMENTS` - Optional: focus area or custom instructions
10
+ - `$ARGUMENTS` - Optional: focus area, custom instructions, or adversarial steering
7
11
 
8
12
  ## When to Use
9
13
 
10
- Use `/multi-review` when you want parallel reviews from Codex, Gemini, and a fresh Claude (Opus) instance.
14
+ Use `/multi-review` when you want thorough parallel reviews from all available models. Every invocation includes both standard and adversarial passes.
15
+
16
+ ## Examples
17
+
18
+ ```
19
+ /multi-review
20
+ /multi-review focus the challenge on race conditions and rollback safety
21
+ /multi-review challenge whether this was the right caching and retry design
22
+ ```
11
23
 
12
24
  ## Before Calling - PREPARE THE HANDOFF
13
25
 
@@ -22,7 +34,6 @@ Added cache invalidation on product updates."
22
34
  UNCERTAINTIES:
23
35
  - "Is the cache TTL appropriate for this data?"
24
36
  - "Does the invalidation handle all update scenarios?"
25
- - "Is the Redis connection pooling configured correctly?"
26
37
  ```
27
38
 
28
39
  ### 3. Ask Specific Questions
@@ -42,7 +53,7 @@ Call `multi_review` with:
42
53
  "ccOutput": "<structured handoff>",
43
54
  "outputType": "analysis",
44
55
  "focusAreas": ["<from $ARGUMENTS>"],
45
- "serviceTier": "<see below>"
56
+ "customPrompt": "<steering text from $ARGUMENTS for adversarial focus>"
46
57
  }
47
58
  ```
48
59
 
@@ -70,21 +81,22 @@ PRIORITY FILES:
70
81
 
71
82
  ## After Receiving Review
72
83
 
73
- You will receive separate reviews from each model.
84
+ You will receive two sections: **Standard Review Findings** and **Challenge Review Findings**.
74
85
 
75
- ### Synthesize Manually
86
+ ### Synthesize
76
87
 
77
- 1. **Find agreements** (both models say same thing)
78
- - Higher confidence
79
- - Still verify yourself
88
+ 1. **Standard findings** bugs, issues, improvements from each model
89
+ - Find agreements across models (higher confidence)
90
+ - Identify conflicts (YOU decide who's right)
80
91
 
81
- 2. **Identify conflicts** (they disagree)
82
- - Read the code
83
- - YOU decide who's right
92
+ 2. **Challenge findings** adversarial challenges from each model
93
+ - These target assumptions and design decisions, not just bugs
94
+ - Evaluate on merit — some challenges are speculative by design
95
+ - Strong challenges with evidence deserve serious consideration
84
96
 
85
- 3. **Note unique insights**
86
- - Findings only one model found
87
- - Evaluate on merit
97
+ 3. **Cross-reference** standard vs challenge findings
98
+ - Standard + challenge agreement = high confidence issue
99
+ - Challenge-only finding = investigate further before acting
88
100
 
89
101
  4. **Verify all findings**
90
102
  - Check file/line references exist
@@ -41,6 +41,8 @@ export interface ReviewRequest {
41
41
  reasoningEffort?: ReasoningEffort;
42
42
  /** Service tier (for models that support it: priority = fast, flex = cheap) */
43
43
  serviceTier?: ServiceTier;
44
+ /** Review mode: standard finds bugs, adversarial challenges assumptions */
45
+ reviewMode?: 'standard' | 'adversarial';
44
46
  }
45
47
  /** @deprecated Use handoff.ts roles instead */
46
48
  export interface ExpertRole {
@@ -15,7 +15,7 @@ import { existsSync } from 'fs';
15
15
  import { registerAdapter, } from './base.js';
16
16
  import { CliExecutor } from '../executor.js';
17
17
  import { ClaudeEventDecoder } from '../decoders/index.js';
18
- import { buildSimpleHandoff, buildHandoffPrompt, selectRole, } from '../handoff.js';
18
+ import { buildSimpleHandoff, buildHandoffPrompt, buildAdversarialHandoffPrompt, selectRole, } from '../handoff.js';
19
19
  // =============================================================================
20
20
  // CONFIGURATION
21
21
  // =============================================================================
@@ -63,8 +63,9 @@ export class ClaudeAdapter {
63
63
  }
64
64
  try {
65
65
  const handoff = buildSimpleHandoff(request.workingDir, request.ccOutput, request.analyzedFiles, request.focusAreas, request.customPrompt);
66
- const role = selectRole(request.focusAreas);
67
- const prompt = buildHandoffPrompt({ handoff, role });
66
+ const prompt = request.reviewMode === 'adversarial'
67
+ ? buildAdversarialHandoffPrompt({ handoff })
68
+ : buildHandoffPrompt({ handoff, role: selectRole(request.focusAreas) });
68
69
  const result = await this.runCli(prompt, request.workingDir);
69
70
  if (result.exitCode !== 0) {
70
71
  const error = this.categorizeError(result.stderr);
@@ -88,6 +89,7 @@ export class ClaudeAdapter {
88
89
  const args = [
89
90
  '-p', // Non-interactive, print and exit
90
91
  '--model', 'opus', // Use Opus
92
+ '--bare', // Skip hooks, plugins, CLAUDE.md, auto-memory
91
93
  '--permission-mode', 'plan', // Read-only enforcement (layer 1)
92
94
  '--verbose', // Required for stream-json
93
95
  '--output-format', 'stream-json', // Structured streaming events
@@ -122,14 +124,20 @@ export class ClaudeAdapter {
122
124
  // Check for errors captured from stream events
123
125
  const decoderError = decoder.getError();
124
126
  if (decoderError) {
125
- return { stdout: '', stderr: decoderError, exitCode: 1, truncated: false };
127
+ const combined = result.stderr ? `${decoderError}\n\nCLI stderr: ${result.stderr}` : decoderError;
128
+ return { stdout: '', stderr: combined, exitCode: 1, truncated: false };
126
129
  }
127
130
  const finalResponse = decoder.getFinalResponse();
128
131
  if (!finalResponse && decoder.hasNoOutput()) {
129
- return { stdout: '', stderr: 'No response from Claude possible rate limit or auth issue', exitCode: 1, truncated: false };
132
+ const combined = result.stderr ? `No output from Claude\n\nCLI stderr: ${result.stderr}` : 'No output from Claude';
133
+ return { stdout: '', stderr: combined, exitCode: 1, truncated: false };
134
+ }
135
+ if (!finalResponse) {
136
+ const combined = result.stderr ? `No result event from Claude\n\nCLI stderr: ${result.stderr}` : 'No result event from Claude';
137
+ return { stdout: '', stderr: combined, exitCode: 1, truncated: false };
130
138
  }
131
139
  return {
132
- stdout: finalResponse || result.rawStdout,
140
+ stdout: finalResponse,
133
141
  stderr: result.stderr,
134
142
  exitCode: result.exitCode,
135
143
  truncated: result.truncated,
@@ -153,13 +161,13 @@ export class ClaudeAdapter {
153
161
  }
154
162
  categorizeError(stderr) {
155
163
  const lower = stderr.toLowerCase();
156
- if (lower.includes('rate limit') || lower.includes('quota') || lower.includes('no response from claude')) {
157
- return { type: 'rate_limit', message: 'Claude rate limit try again later' };
164
+ if (lower.includes('rate limit') || lower.includes('rate_limit') || lower.includes('quota')) {
165
+ return { type: 'rate_limit', message: `Claude rate limit: ${stderr.slice(0, 500)}` };
158
166
  }
159
167
  if (lower.includes('unauthorized') || lower.includes('authentication') || lower.includes('api key') || stderr.includes('401') || stderr.includes('403')) {
160
- return { type: 'auth_error', message: 'Authentication failed', details: { stderr } };
168
+ return { type: 'auth_error', message: `Authentication failed: ${stderr.slice(0, 500)}`, details: { stderr } };
161
169
  }
162
- return { type: 'cli_error', message: stderr || 'Unknown error' };
170
+ return { type: 'cli_error', message: stderr.slice(0, 500) || 'Unknown error' };
163
171
  }
164
172
  getSuggestion(error) {
165
173
  switch (error.type) {
@@ -10,7 +10,7 @@ import { existsSync } from 'fs';
10
10
  import { registerAdapter, } from './base.js';
11
11
  import { CliExecutor } from '../executor.js';
12
12
  import { CodexEventDecoder } from '../decoders/index.js';
13
- import { buildSimpleHandoff, buildHandoffPrompt, selectRole, } from '../handoff.js';
13
+ import { buildSimpleHandoff, buildHandoffPrompt, buildAdversarialHandoffPrompt, selectRole, } from '../handoff.js';
14
14
  // =============================================================================
15
15
  // CONFIGURATION
16
16
  // =============================================================================
@@ -59,8 +59,9 @@ export class CodexAdapter {
59
59
  }
60
60
  try {
61
61
  const handoff = buildSimpleHandoff(request.workingDir, request.ccOutput, request.analyzedFiles, request.focusAreas, request.customPrompt);
62
- const role = selectRole(request.focusAreas);
63
- const prompt = buildHandoffPrompt({ handoff, role });
62
+ const prompt = request.reviewMode === 'adversarial'
63
+ ? buildAdversarialHandoffPrompt({ handoff })
64
+ : buildHandoffPrompt({ handoff, role: selectRole(request.focusAreas) });
64
65
  const result = await this.runCli(prompt, request.workingDir, request.reasoningEffort || 'high', request.serviceTier);
65
66
  if (result.exitCode !== 0) {
66
67
  const error = this.categorizeError(result.stderr);
@@ -123,14 +124,20 @@ export class CodexAdapter {
123
124
  // Check for errors captured from JSONL events
124
125
  const decoderError = decoder.getError();
125
126
  if (decoderError) {
126
- return { stdout: '', stderr: decoderError, exitCode: 1, truncated: false };
127
+ const combined = result.stderr ? `${decoderError}\n\nCLI stderr: ${result.stderr}` : decoderError;
128
+ return { stdout: '', stderr: combined, exitCode: 1, truncated: false };
127
129
  }
128
130
  const finalResponse = decoder.getFinalResponse();
129
131
  if (!finalResponse && decoder.hasNoOutput()) {
130
- return { stdout: '', stderr: 'No response from Codex possible rate limit or model rejection', exitCode: 1, truncated: false };
132
+ const combined = result.stderr ? `No output from Codex\n\nCLI stderr: ${result.stderr}` : 'No output from Codex';
133
+ return { stdout: '', stderr: combined, exitCode: 1, truncated: false };
134
+ }
135
+ if (!finalResponse) {
136
+ const combined = result.stderr ? `No result event from Codex\n\nCLI stderr: ${result.stderr}` : 'No result event from Codex';
137
+ return { stdout: '', stderr: combined, exitCode: 1, truncated: false };
131
138
  }
132
139
  return {
133
- stdout: finalResponse || result.rawStdout,
140
+ stdout: finalResponse,
134
141
  stderr: result.stderr,
135
142
  exitCode: result.exitCode,
136
143
  truncated: result.truncated,
@@ -154,16 +161,16 @@ export class CodexAdapter {
154
161
  }
155
162
  categorizeError(stderr) {
156
163
  const lower = stderr.toLowerCase();
157
- if (lower.includes('rate limit') || lower.includes('possible rate limit') || lower.includes('no response from codex')) {
158
- return { type: 'rate_limit', message: 'Codex rate limit no tokens available' };
164
+ if (lower.includes('rate limit') || lower.includes('rate_limit')) {
165
+ return { type: 'rate_limit', message: `Codex rate limit: ${stderr.slice(0, 500)}` };
159
166
  }
160
167
  if (lower.includes('unauthorized') || lower.includes('authentication') || stderr.includes('401') || stderr.includes('403')) {
161
- return { type: 'auth_error', message: 'Authentication failed', details: { stderr } };
168
+ return { type: 'auth_error', message: `Authentication failed: ${stderr.slice(0, 500)}`, details: { stderr } };
162
169
  }
163
170
  if (lower.includes('invalid_json_schema') || lower.includes('invalid_request_error')) {
164
- return { type: 'cli_error', message: `API error: ${stderr.slice(0, 300)}` };
171
+ return { type: 'cli_error', message: `API error: ${stderr.slice(0, 500)}` };
165
172
  }
166
- return { type: 'cli_error', message: stderr || 'Unknown error' };
173
+ return { type: 'cli_error', message: stderr.slice(0, 500) || 'Unknown error' };
167
174
  }
168
175
  getSuggestion(error) {
169
176
  switch (error.type) {
@@ -10,7 +10,7 @@ import { existsSync } from 'fs';
10
10
  import { registerAdapter, } from './base.js';
11
11
  import { CliExecutor } from '../executor.js';
12
12
  import { GeminiEventDecoder } from '../decoders/index.js';
13
- import { buildSimpleHandoff, buildHandoffPrompt, selectRole, } from '../handoff.js';
13
+ import { buildSimpleHandoff, buildHandoffPrompt, buildAdversarialHandoffPrompt, selectRole, } from '../handoff.js';
14
14
  // =============================================================================
15
15
  // CONFIGURATION
16
16
  // =============================================================================
@@ -56,8 +56,9 @@ export class GeminiAdapter {
56
56
  }
57
57
  try {
58
58
  const handoff = buildSimpleHandoff(request.workingDir, request.ccOutput, request.analyzedFiles, request.focusAreas, request.customPrompt);
59
- const role = selectRole(request.focusAreas);
60
- const prompt = buildHandoffPrompt({ handoff, role });
59
+ const prompt = request.reviewMode === 'adversarial'
60
+ ? buildAdversarialHandoffPrompt({ handoff })
61
+ : buildHandoffPrompt({ handoff, role: selectRole(request.focusAreas) });
61
62
  const result = await this.runCli(prompt, request.workingDir);
62
63
  if (result.exitCode !== 0) {
63
64
  const error = this.categorizeError(result.stderr);
@@ -109,8 +110,11 @@ export class GeminiAdapter {
109
110
  const elapsed = Math.round((Date.now() - cliStartTime) / 1000);
110
111
  console.error(`[gemini] ✓ complete (${elapsed}s)`);
111
112
  const finalResponse = decoder.getFinalResponse();
113
+ if (!finalResponse && result.exitCode === 0) {
114
+ return { stdout: '', stderr: 'Gemini produced no output — review may have failed silently', exitCode: 1, truncated: false };
115
+ }
112
116
  return {
113
- stdout: finalResponse || result.rawStdout,
117
+ stdout: finalResponse || '',
114
118
  stderr: result.stderr,
115
119
  exitCode: result.exitCode,
116
120
  truncated: result.truncated,
@@ -135,12 +139,12 @@ export class GeminiAdapter {
135
139
  categorizeError(stderr) {
136
140
  const lower = stderr.toLowerCase();
137
141
  if (lower.includes('rate limit') || lower.includes('quota')) {
138
- return { type: 'rate_limit', message: 'Rate limit or quota exceeded' };
142
+ return { type: 'rate_limit', message: `Rate limit or quota exceeded: ${stderr.slice(0, 500)}` };
139
143
  }
140
144
  if (lower.includes('unauthorized') || lower.includes('authentication') || lower.includes('api key') || stderr.includes('401') || stderr.includes('403')) {
141
- return { type: 'auth_error', message: 'Authentication failed', details: { stderr } };
145
+ return { type: 'auth_error', message: `Authentication failed: ${stderr.slice(0, 500)}`, details: { stderr } };
142
146
  }
143
- return { type: 'cli_error', message: stderr || 'Unknown error' };
147
+ return { type: 'cli_error', message: stderr.slice(0, 500) || 'Unknown error' };
144
148
  }
145
149
  getSuggestion(error) {
146
150
  switch (error.type) {
package/dist/commands.js CHANGED
@@ -18,6 +18,7 @@ const DEPRECATED_COMMANDS = [
18
18
  'ask-codex.md',
19
19
  'ask-gemini.md',
20
20
  'ask-multi.md',
21
+ 'multi-review-adv.md',
21
22
  ];
22
23
  /**
23
24
  * Get source and target paths for command files
package/dist/handoff.d.ts CHANGED
@@ -4,9 +4,10 @@
4
4
  * Defines the minimal, targeted information that should flow from CC to reviewers.
5
5
  *
6
6
  * Philosophy:
7
- * - Reviewers have filesystem + git access - don't duplicate what they can discover
7
+ * - Reviewers have filesystem access - don't duplicate what they can discover
8
8
  * - Pass ONLY what CC uniquely knows: uncertainties, decisions, questions
9
- * - Let reviewer use their tools (git diff, file reading) for actual code
9
+ * - Let reviewer use their tools (file reading) for actual code
10
+ * - Do NOT assume git — working directory may not be a git repo
10
11
  */
11
12
  import { z } from 'zod';
12
13
  import { FocusArea } from './types.js';
@@ -208,9 +209,19 @@ export declare const ARCHITECTURE_REVIEWER: ReviewerRole;
208
209
  export declare const CORRECTNESS_REVIEWER: ReviewerRole;
209
210
  export declare const ROLES: Record<string, ReviewerRole>;
210
211
  /**
211
- * Select the best role based on focus areas
212
+ * Select and compose roles based on focus areas.
213
+ *
214
+ * When multiple focus areas map to different roles (e.g. security + performance),
215
+ * composes them into a single role with merged prompts instead of picking one winner.
212
216
  */
213
217
  export declare function selectRole(focusAreas?: FocusArea[]): ReviewerRole;
218
+ export declare const ADVERSARIAL_REVIEWER: ReviewerRole;
219
+ /**
220
+ * Build an adversarial handoff prompt with challenge-mode stance sections.
221
+ * Same structure as buildHandoffPrompt but adds adversarial XML sections
222
+ * and uses the ADVERSARIAL_REVIEWER role.
223
+ */
224
+ export declare function buildAdversarialHandoffPrompt(options: PromptOptions): string;
214
225
  export interface PromptOptions {
215
226
  handoff: Handoff;
216
227
  role?: ReviewerRole;
@@ -221,7 +232,30 @@ export interface PromptOptions {
221
232
  */
222
233
  export declare function buildHandoffPrompt(options: PromptOptions): string;
223
234
  /**
224
- * Build a handoff from legacy simple inputs
235
+ * Parse structured ccOutput into Handoff fields.
236
+ *
237
+ * The slash commands tell CC to format its output as:
238
+ * SUMMARY:
239
+ * <text>
240
+ *
241
+ * UNCERTAINTIES (verify these):
242
+ * 1. <text>
243
+ *
244
+ * QUESTIONS:
245
+ * 1. <text>
246
+ *
247
+ * PRIORITY FILES:
248
+ * - <file>
249
+ *
250
+ * If no sections detected, returns { summary: ccOutput } (graceful fallback).
251
+ */
252
+ export declare function parseStructuredCcOutput(ccOutput: string): Pick<Handoff, 'summary'> & Partial<Handoff>;
253
+ /**
254
+ * Build a handoff from MCP tool inputs.
255
+ *
256
+ * Parses structured sections (SUMMARY, UNCERTAINTIES, QUESTIONS, PRIORITY FILES)
257
+ * from ccOutput when present, populating typed Handoff fields so reviewers
258
+ * receive machine-usable context instead of a single summary blob.
225
259
  */
226
260
  export declare function buildSimpleHandoff(workingDir: string, ccOutput: string, analyzedFiles?: string[], focusAreas?: string[], customPrompt?: string): Handoff;
227
261
  /**
package/dist/handoff.js CHANGED
@@ -4,9 +4,10 @@
4
4
  * Defines the minimal, targeted information that should flow from CC to reviewers.
5
5
  *
6
6
  * Philosophy:
7
- * - Reviewers have filesystem + git access - don't duplicate what they can discover
7
+ * - Reviewers have filesystem access - don't duplicate what they can discover
8
8
  * - Pass ONLY what CC uniquely knows: uncertainties, decisions, questions
9
- * - Let reviewer use their tools (git diff, file reading) for actual code
9
+ * - Let reviewer use their tools (file reading) for actual code
10
+ * - Do NOT assume git — working directory may not be a git repo
10
11
  */
11
12
  import { z } from 'zod';
12
13
  // =============================================================================
@@ -87,7 +88,7 @@ export const CHANGE_FOCUSED_REVIEWER = {
87
88
  isGeneric: true,
88
89
  applicableFocusAreas: [],
89
90
  systemPrompt: `Change reviewer. Focus on: goal achievement, regressions, edge cases, side effects.
90
- Reference specific lines in the diff.`,
91
+ Reference specific lines in the source files.`,
91
92
  };
92
93
  /**
93
94
  * Specialized roles - when specific focus is requested
@@ -126,7 +127,8 @@ export const CORRECTNESS_REVIEWER = {
126
127
  isGeneric: false,
127
128
  applicableFocusAreas: ['correctness', 'testing'],
128
129
  systemPrompt: `Correctness analyst. Focus on logic errors, edge cases, race conditions, error handling.
129
- Provide triggering inputs and expected vs actual behavior.`,
130
+ Provide triggering inputs and expected vs actual behavior.
131
+ For significant bugs, suggest a concrete regression test (name, inputs, expected output).`,
130
132
  };
131
133
  // All roles indexed by ID
132
134
  export const ROLES = {
@@ -138,21 +140,209 @@ export const ROLES = {
138
140
  correctness: CORRECTNESS_REVIEWER,
139
141
  };
140
142
  /**
141
- * Select the best role based on focus areas
143
+ * Select and compose roles based on focus areas.
144
+ *
145
+ * When multiple focus areas map to different roles (e.g. security + performance),
146
+ * composes them into a single role with merged prompts instead of picking one winner.
142
147
  */
143
148
  export function selectRole(focusAreas) {
144
149
  if (!focusAreas || focusAreas.length === 0) {
145
150
  return COMPREHENSIVE_REVIEWER;
146
151
  }
152
+ // Collect all unique matching roles (preserving insertion order)
153
+ const matched = new Map();
147
154
  for (const focus of focusAreas) {
148
155
  for (const role of Object.values(ROLES)) {
149
156
  if (!role.isGeneric && role.applicableFocusAreas.includes(focus)) {
150
- return role;
157
+ matched.set(role.id, role);
151
158
  }
152
159
  }
153
160
  }
154
- return CHANGE_FOCUSED_REVIEWER;
161
+ if (matched.size === 0)
162
+ return CHANGE_FOCUSED_REVIEWER;
163
+ if (matched.size === 1)
164
+ return [...matched.values()][0];
165
+ // Compose multiple roles into one
166
+ const roles = [...matched.values()];
167
+ return {
168
+ id: roles.map(r => r.id).join('+'),
169
+ name: roles.map(r => r.name).join(' + '),
170
+ description: roles.map(r => r.description).join('; '),
171
+ isGeneric: false,
172
+ applicableFocusAreas: focusAreas,
173
+ systemPrompt: roles.map(r => `**As ${r.name}:** ${r.systemPrompt}`).join('\n'),
174
+ };
155
175
  }
176
+ // =============================================================================
177
+ // ADVERSARIAL REVIEWER — Challenge mode for multi_review
178
+ // =============================================================================
179
+ export const ADVERSARIAL_REVIEWER = {
180
+ id: 'adversarial',
181
+ name: 'Adversarial Reviewer',
182
+ description: 'Actively tries to break confidence in the change — challenges assumptions, not just bugs',
183
+ isGeneric: false,
184
+ applicableFocusAreas: [],
185
+ systemPrompt: `Senior staff engineer performing an adversarial review. Your job is to break confidence in the change, not to validate it.`,
186
+ };
187
+ /**
188
+ * Build an adversarial handoff prompt with challenge-mode stance sections.
189
+ * Same structure as buildHandoffPrompt but adds adversarial XML sections
190
+ * and uses the ADVERSARIAL_REVIEWER role.
191
+ */
192
+ export function buildAdversarialHandoffPrompt(options) {
193
+ const { handoff } = options;
194
+ const role = ADVERSARIAL_REVIEWER;
195
+ const sections = [];
196
+ // SECTION 1: ROLE
197
+ sections.push(`# ROLE: ${role.name}\n\n${role.systemPrompt}`);
198
+ // SECTION 2: ADVERSARIAL STANCE
199
+ sections.push(`## ADVERSARIAL STANCE
200
+
201
+ <operating_stance>
202
+ Default to skepticism. Assume the change can fail in subtle, high-cost,
203
+ or user-visible ways until the evidence says otherwise. Do not give credit
204
+ for good intent, partial fixes, or likely follow-up work.
205
+ </operating_stance>
206
+
207
+ <attack_surface>
208
+ Prioritized failure categories:
209
+ 1. Auth/permissions bypass
210
+ 2. Data loss or corruption
211
+ 3. Rollback safety
212
+ 4. Race conditions / concurrency
213
+ 5. Empty-state / null / timeout handling
214
+ 6. Version skew / backwards compatibility
215
+ 7. Observability gaps (missing logs, metrics, alerts)
216
+ </attack_surface>
217
+
218
+ <review_method>
219
+ Actively try to disprove the change. Look for violated invariants,
220
+ missing guards, unhandled failure paths. If the user supplied a focus area,
221
+ weight it heavily, but still report any other material issue you can defend.
222
+ </review_method>
223
+
224
+ <finding_bar>
225
+ Material findings only. Each must answer:
226
+ 1. What can go wrong?
227
+ 2. Why is this code path vulnerable?
228
+ 3. What is the likely impact?
229
+ 4. What concrete change would reduce the risk?
230
+ </finding_bar>
231
+
232
+ <calibration_rules>
233
+ Prefer one strong finding over several weak ones. If you cannot defend
234
+ a finding from the provided code, drop it.
235
+ </calibration_rules>
236
+
237
+ <grounding_rules>
238
+ Be aggressive, but stay grounded. Every finding must be defensible from
239
+ the repository context. No speculative findings. No "might be an issue"
240
+ without concrete evidence from the code.
241
+ </grounding_rules>`);
242
+ // SECTION 3: TASK (same as standard)
243
+ sections.push(`## YOUR TASK
244
+
245
+ Review code in \`${handoff.workingDir}\`.
246
+
247
+ **Summary:** ${handoff.summary}${handoff.confidence !== undefined && handoff.confidence < 0.9 ? `\n**CC Confidence:** ${Math.round(handoff.confidence * 100)}% — verify weak areas` : ''}
248
+
249
+ **IMPORTANT:**
250
+ - This is a READ-ONLY review. Do NOT create, modify, or delete any files. Only read files to verify claims.
251
+ - Do NOT assume a git repository exists. Do NOT run git commands. Read files directly from the filesystem.`);
252
+ // SECTION 4: CC'S UNCERTAINTIES
253
+ if (handoff.uncertainties && handoff.uncertainties.length > 0) {
254
+ sections.push(`## CC'S UNCERTAINTIES
255
+
256
+ ${handoff.uncertainties.map((u, i) => `### ${i + 1}. ${u.topic} ${u.severity === 'critical' ? '⚠️' : ''}
257
+ - **Question:** ${u.question}
258
+ ${u.ccAssumption ? `- **CC assumed:** ${u.ccAssumption}` : ''}
259
+ ${u.relevantFiles ? `- **Files:** ${u.relevantFiles.join(', ')}` : ''}`).join('\n\n')}`);
260
+ }
261
+ // SECTION 5: SPECIFIC QUESTIONS
262
+ if (handoff.questions && handoff.questions.length > 0) {
263
+ sections.push(`## QUESTIONS FROM CC
264
+
265
+ ${handoff.questions.map((q, i) => `${i + 1}. **${q.question}**
266
+ ${q.context ? `Context: ${q.context}` : ''}
267
+ ${q.ccGuess ? `CC Guess: ${q.ccGuess}` : ''}`).join('\n')}`);
268
+ }
269
+ // SECTION 6: DECISIONS TO EVALUATE
270
+ if (handoff.decisions && handoff.decisions.length > 0) {
271
+ sections.push(`## DECISIONS TO EVALUATE
272
+
273
+ ${handoff.decisions.map((d, i) => `${i + 1}. **${d.decision}**${d.rationale ? `\n Rationale: ${d.rationale}` : ''}${d.alternatives ? `\n Alternatives: ${d.alternatives.join(', ')}` : ''}`).join('\n')}`);
274
+ }
275
+ // SECTION 7: FOCUS AREAS
276
+ if (handoff.focusAreas && handoff.focusAreas.length > 0) {
277
+ sections.push(`## FOCUS AREAS\n\nWeight these areas heavily in your adversarial analysis:\n${handoff.focusAreas.map(f => `- **${f}**`).join('\n')}`);
278
+ }
279
+ // SECTION 8: PRIORITY FILES
280
+ if (handoff.priorityFiles && handoff.priorityFiles.length > 0) {
281
+ sections.push(`## PRIORITY FILES\n\n${handoff.priorityFiles.map(f => `- \`${f}\``).join('\n')}`);
282
+ }
283
+ // SECTION 9: ADVERSARIAL FOCUS (customInstructions steers the challenge)
284
+ if (handoff.customInstructions) {
285
+ sections.push(`## ADVERSARIAL FOCUS\n\n${handoff.customInstructions}`);
286
+ }
287
+ return sections.join('\n\n');
288
+ }
289
+ // =============================================================================
290
+ // FOCUS-AREA CHECKLISTS — Specific patterns to look for (ported from prompt-v2)
291
+ // =============================================================================
292
+ const FOCUS_CHECKLISTS = {
293
+ security: `Check for:
294
+ - Injection vulnerabilities (SQL, NoSQL, Command, XSS)
295
+ - Auth/authorization bypass, session management flaws
296
+ - Sensitive data exposure, insecure storage, missing encryption
297
+ - Input validation gaps (type, range, format)
298
+ - Path traversal, SSRF, unsafe deserialization
299
+ For each: CWE ID if applicable, attack scenario, severity by impact + exploitability.`,
300
+ performance: `Check for:
301
+ - Algorithmic complexity (provide Big-O notation)
302
+ - N+1 queries, missing indexes, unoptimized queries
303
+ - Blocking I/O in async contexts
304
+ - Memory leaks, unbounded allocations, large object retention
305
+ - Missing caching/memoization, repeated expensive operations
306
+ For each: Big-O analysis, estimated impact, concrete optimization.`,
307
+ architecture: `Check for:
308
+ - SOLID violations (SRP, OCP, LSP, ISP, DIP)
309
+ - High coupling between modules, low cohesion within
310
+ - Layering violations, circular dependencies
311
+ - Anti-patterns (god classes, deep nesting, magic numbers, leaky abstractions)
312
+ - Missing or misused design patterns
313
+ For each: specific principle violated, refactoring suggestion, maintainability impact.`,
314
+ correctness: `Check for:
315
+ - Off-by-one errors, incorrect conditionals, wrong operators
316
+ - Null/undefined handling, empty collections, boundary conditions
317
+ - Race conditions, deadlock potential, state inconsistency
318
+ - Uncaught exceptions, silent failures, incorrect error propagation
319
+ For each: triggering input, expected vs actual behavior.
320
+ For significant bugs: suggest a concrete regression test.`,
321
+ testing: `Check for:
322
+ - Missing test coverage for changed code paths
323
+ - Tests that pass for wrong reasons (tautologies, mocked-away logic)
324
+ - Non-deterministic tests (timing, ordering, randomness)
325
+ - Missing edge case tests (null, empty, boundary, error paths)
326
+ For significant gaps: suggest a concrete test (name, inputs, expected output).`,
327
+ scalability: `Check for:
328
+ - Algorithmic complexity that degrades at scale (provide Big-O)
329
+ - Unbounded growth (queues, caches, in-memory collections)
330
+ - Missing pagination, rate limiting, or backpressure
331
+ - Single points of contention (locks, shared state, single-threaded bottlenecks)
332
+ For each: estimated impact at 10x/100x current load.`,
333
+ maintainability: `Check for:
334
+ - God classes, deep nesting (>3 levels), magic numbers
335
+ - Tight coupling between modules, leaky abstractions
336
+ - Code duplication that should be extracted
337
+ - Missing or misleading comments on non-obvious logic
338
+ For each: specific refactoring suggestion with rationale.`,
339
+ documentation: `Check for:
340
+ - Public API functions missing doc comments
341
+ - Outdated or misleading comments that contradict the code
342
+ - Missing README updates for changed behavior
343
+ - Undocumented configuration, environment variables, or flags
344
+ For each: what specifically should be documented and where.`,
345
+ };
156
346
  /**
157
347
  * Build the review prompt using minimal, targeted context.
158
348
  * No output format constraints — reviewer responds naturally, CC interprets.
@@ -163,15 +353,27 @@ export function buildHandoffPrompt(options) {
163
353
  const sections = [];
164
354
  // SECTION 1: ROLE
165
355
  sections.push(`# ROLE: ${role.name}\n\n${role.systemPrompt}`);
166
- // SECTION 2: TASK
356
+ // SECTION 2: REVIEW CHECKLIST (focus-area-specific patterns to look for)
357
+ const focusAreas = handoff.focusAreas;
358
+ if (focusAreas && focusAreas.length > 0) {
359
+ const checklists = focusAreas
360
+ .map(f => FOCUS_CHECKLISTS[f])
361
+ .filter((c) => !!c);
362
+ if (checklists.length > 0) {
363
+ sections.push(`## REVIEW CHECKLIST\n\n${checklists.join('\n\n')}`);
364
+ }
365
+ }
366
+ // SECTION 3: TASK
167
367
  sections.push(`## YOUR TASK
168
368
 
169
- Review recent work in \`${handoff.workingDir}\`.
369
+ Review code in \`${handoff.workingDir}\`.
170
370
 
171
371
  **Summary:** ${handoff.summary}${handoff.confidence !== undefined && handoff.confidence < 0.9 ? `\n**CC Confidence:** ${Math.round(handoff.confidence * 100)}% — verify weak areas` : ''}
172
372
 
173
- **IMPORTANT: This is a READ-ONLY review. Do NOT create, modify, or delete any files. Only read files to verify claims.**`);
174
- // SECTION 3: CC'S UNCERTAINTIES
373
+ **IMPORTANT:**
374
+ - This is a READ-ONLY review. Do NOT create, modify, or delete any files. Only read files to verify claims.
375
+ - Do NOT assume a git repository exists. Do NOT run git commands. Read files directly from the filesystem.`);
376
+ // SECTION 4: CC'S UNCERTAINTIES
175
377
  if (handoff.uncertainties && handoff.uncertainties.length > 0) {
176
378
  sections.push(`## CC'S UNCERTAINTIES
177
379
 
@@ -180,7 +382,7 @@ ${handoff.uncertainties.map((u, i) => `### ${i + 1}. ${u.topic} ${u.severity ===
180
382
  ${u.ccAssumption ? `- **CC assumed:** ${u.ccAssumption}` : ''}
181
383
  ${u.relevantFiles ? `- **Files:** ${u.relevantFiles.join(', ')}` : ''}`).join('\n\n')}`);
182
384
  }
183
- // SECTION 4: SPECIFIC QUESTIONS
385
+ // SECTION 5: SPECIFIC QUESTIONS
184
386
  if (handoff.questions && handoff.questions.length > 0) {
185
387
  sections.push(`## QUESTIONS FROM CC
186
388
 
@@ -188,35 +390,184 @@ ${handoff.questions.map((q, i) => `${i + 1}. **${q.question}**
188
390
  ${q.context ? `Context: ${q.context}` : ''}
189
391
  ${q.ccGuess ? `CC Guess: ${q.ccGuess}` : ''}`).join('\n')}`);
190
392
  }
191
- // SECTION 5: DECISIONS TO EVALUATE
393
+ // SECTION 6: DECISIONS TO EVALUATE
192
394
  if (handoff.decisions && handoff.decisions.length > 0) {
193
395
  sections.push(`## DECISIONS TO EVALUATE
194
396
 
195
- ${handoff.decisions.map((d, i) => `${i + 1}. **${d.decision}**
196
- Rationale: ${d.rationale}
197
- ${d.alternatives ? `Alternatives: ${d.alternatives.join(', ')}` : ''}`).join('\n')}`);
397
+ ${handoff.decisions.map((d, i) => `${i + 1}. **${d.decision}**${d.rationale ? `\n Rationale: ${d.rationale}` : ''}${d.alternatives ? `\n Alternatives: ${d.alternatives.join(', ')}` : ''}`).join('\n')}`);
198
398
  }
199
- // SECTION 6: PRIORITY FILES
399
+ // SECTION 7: PRIORITY FILES
200
400
  if (handoff.priorityFiles && handoff.priorityFiles.length > 0) {
201
401
  sections.push(`## PRIORITY FILES\n\n${handoff.priorityFiles.map(f => `- \`${f}\``).join('\n')}`);
202
402
  }
403
+ // SECTION 8: CUSTOM INSTRUCTIONS
404
+ if (handoff.customInstructions) {
405
+ sections.push(`## ADDITIONAL INSTRUCTIONS\n\n${handoff.customInstructions}`);
406
+ }
203
407
  return sections.join('\n\n');
204
408
  }
205
409
  // =============================================================================
206
- // HELPER: Build handoff from simple inputs (backwards compatibility)
410
+ // STRUCTURED ccOutput PARSER
207
411
  // =============================================================================
208
412
  /**
209
- * Build a handoff from legacy simple inputs
413
+ * Parse structured ccOutput into Handoff fields.
414
+ *
415
+ * The slash commands tell CC to format its output as:
416
+ * SUMMARY:
417
+ * <text>
418
+ *
419
+ * UNCERTAINTIES (verify these):
420
+ * 1. <text>
421
+ *
422
+ * QUESTIONS:
423
+ * 1. <text>
424
+ *
425
+ * PRIORITY FILES:
426
+ * - <file>
427
+ *
428
+ * If no sections detected, returns { summary: ccOutput } (graceful fallback).
429
+ */
430
+ export function parseStructuredCcOutput(ccOutput) {
431
+ // Quick check: does it look structured? Case-SENSITIVE to avoid matching
432
+ // prose like "Summary: I think..." — slash commands produce ALL-CAPS headers.
433
+ if (!/^SUMMARY[^:\n]*:/m.test(ccOutput)) {
434
+ return { summary: ccOutput };
435
+ }
436
+ // Known section headers — case-SENSITIVE (ALL-CAPS only) to prevent
437
+ // header injection from natural prose starting with "Questions:" etc.
438
+ const KNOWN_HEADERS = ['SUMMARY', 'UNCERTAINTIES', 'QUESTIONS', 'PRIORITY FILES', 'DECISIONS'];
439
+ const headerPattern = new RegExp(`^(${KNOWN_HEADERS.join('|')})[^:\\n]*:`, 'gm' // no 'i' flag — case-sensitive
440
+ );
441
+ // Find all header positions
442
+ const headers = [];
443
+ let match;
444
+ while ((match = headerPattern.exec(ccOutput)) !== null) {
445
+ const raw = match[1].trim();
446
+ const name = KNOWN_HEADERS.find(h => raw.startsWith(h)) || raw;
447
+ headers.push({ name, contentStart: match.index + match[0].length });
448
+ }
449
+ if (headers.length === 0) {
450
+ return { summary: ccOutput };
451
+ }
452
+ // Extract content between headers
453
+ const sections = new Map();
454
+ for (let i = 0; i < headers.length; i++) {
455
+ const start = headers[i].contentStart;
456
+ const end = i + 1 < headers.length
457
+ ? ccOutput.lastIndexOf('\n', headers[i + 1].contentStart - headers[i + 1].name.length - 1)
458
+ : ccOutput.length;
459
+ sections.set(headers[i].name, ccOutput.slice(start, end).trim());
460
+ }
461
+ const rawSummary = sections.get('SUMMARY');
462
+ const result = {
463
+ summary: rawSummary && rawSummary.length > 0 ? rawSummary : ccOutput,
464
+ };
465
+ // Parse uncertainties (numbered or bulleted list)
466
+ const uncertText = sections.get('UNCERTAINTIES');
467
+ if (uncertText) {
468
+ const items = parseListItems(uncertText);
469
+ if (items.length > 0) {
470
+ result.uncertainties = items.map(item => ({
471
+ topic: extractTopic(item),
472
+ question: item,
473
+ }));
474
+ }
475
+ }
476
+ // Parse questions (numbered or bulleted list)
477
+ const questionsText = sections.get('QUESTIONS');
478
+ if (questionsText) {
479
+ const items = parseListItems(questionsText);
480
+ if (items.length > 0) {
481
+ result.questions = items.map(item => ({ question: item }));
482
+ }
483
+ }
484
+ // Parse priority files (bullet or numbered list)
485
+ const filesText = sections.get('PRIORITY FILES');
486
+ if (filesText) {
487
+ const items = parseListItems(filesText);
488
+ if (items.length > 0) {
489
+ result.priorityFiles = items;
490
+ }
491
+ }
492
+ // Parse decisions (numbered or bulleted list)
493
+ const decisionsText = sections.get('DECISIONS');
494
+ if (decisionsText) {
495
+ const items = parseListItems(decisionsText);
496
+ if (items.length > 0) {
497
+ result.decisions = items.map(item => ({ decision: item, rationale: '' }));
498
+ }
499
+ }
500
+ return result;
501
+ }
502
+ /**
503
+ * Extract a short topic from an item — uses first sentence/clause up to 60 chars.
504
+ * Avoids redundant rendering where topic === question.
505
+ */
506
+ function extractTopic(item) {
507
+ // Try first clause (up to first comma, period, dash, or question mark)
508
+ const clauseMatch = item.match(/^(.+?)[,.\-?]/);
509
+ const clause = clauseMatch ? clauseMatch[1].trim() : item;
510
+ if (clause.length <= 60)
511
+ return clause;
512
+ return clause.slice(0, 57) + '...';
513
+ }
514
+ /**
515
+ * Parse a list section that may use numbered ("1. foo") or bulleted ("- foo") format.
516
+ * Supports multi-line continuation for both styles.
517
+ */
518
+ function parseListItems(text) {
519
+ const items = [];
520
+ let current = '';
521
+ for (const line of text.split('\n')) {
522
+ // Match numbered: "1. foo", "2) bar"
523
+ const numbered = line.match(/^\d+[.)]\s+(.+)/);
524
+ // Match bulleted: "- foo", "* bar"
525
+ const bulleted = line.match(/^[-*]\s+(.+)/);
526
+ if (numbered || bulleted) {
527
+ if (current)
528
+ items.push(current.trim());
529
+ current = (numbered || bulleted)[1];
530
+ }
531
+ else if (current && line.trim()) {
532
+ // Continuation line for multi-line items
533
+ current += ' ' + line.trim();
534
+ }
535
+ }
536
+ if (current)
537
+ items.push(current.trim());
538
+ return items;
539
+ }
540
+ // =============================================================================
541
+ // HELPER: Build handoff from simple inputs
542
+ // =============================================================================
543
+ /**
544
+ * Build a handoff from MCP tool inputs.
545
+ *
546
+ * Parses structured sections (SUMMARY, UNCERTAINTIES, QUESTIONS, PRIORITY FILES)
547
+ * from ccOutput when present, populating typed Handoff fields so reviewers
548
+ * receive machine-usable context instead of a single summary blob.
210
549
  */
211
550
  export function buildSimpleHandoff(workingDir, ccOutput, analyzedFiles, focusAreas, customPrompt) {
551
+ const parsed = parseStructuredCcOutput(ccOutput);
552
+ // Merge analyzedFiles with any priority files parsed from ccOutput (dedup)
553
+ const mergedFiles = dedupStrings([
554
+ ...(parsed.priorityFiles || []),
555
+ ...(analyzedFiles || []),
556
+ ]);
212
557
  return {
213
558
  workingDir,
214
- summary: ccOutput,
215
- priorityFiles: analyzedFiles,
559
+ summary: parsed.summary,
560
+ uncertainties: parsed.uncertainties,
561
+ questions: parsed.questions,
562
+ decisions: parsed.decisions,
563
+ priorityFiles: mergedFiles.length > 0 ? mergedFiles : undefined,
216
564
  focusAreas,
217
565
  customInstructions: customPrompt,
218
566
  };
219
567
  }
568
+ function dedupStrings(arr) {
569
+ return [...new Set(arr)];
570
+ }
220
571
  /**
221
572
  * Enhance a simple handoff with uncertainties/questions
222
573
  * CC should call this to add its specific concerns
package/dist/prompt.js CHANGED
@@ -101,6 +101,7 @@ ${request.customPrompt}`);
101
101
  CONSTRAINTS:
102
102
  • You have filesystem access - READ files to verify claims
103
103
  • Do NOT modify any files (advisory mode only)
104
+ • Do NOT assume a git repository exists - do NOT run git commands
104
105
  • Reference specific file:line when making claims
105
106
  • Do NOT hallucinate file paths - verify they exist
106
107
  • Be skeptical - verify before agreeing with CC's findings`);
@@ -92,24 +92,38 @@ export async function handleMultiReview(input) {
92
92
  if (availableAdapters.length === 0) {
93
93
  return { content: [{ type: 'text', text: '❌ No AI CLIs found.\n\nInstall at least one:\n - Codex: npm install -g @openai/codex-cli\n - Gemini: npm install -g @google/gemini-cli' }] };
94
94
  }
95
- const results = await Promise.all(availableAdapters.map(async (adapter) => {
96
- const result = await adapter.runReview({ ...request });
97
- return { adapter, result };
98
- }));
99
- const lines = [];
95
+ // Spawn 2 reviews per adapter: standard + adversarial (all in parallel)
96
+ // customPrompt steers the adversarial focus only — strip it from standard pass to avoid bias
97
+ const { customPrompt, ...standardRequest } = request;
98
+ const reviewPromises = availableAdapters.flatMap((adapter) => [
99
+ adapter.runReview({ ...standardRequest }).then(result => ({ adapter, result, mode: 'standard' })),
100
+ adapter.runReview({ ...request, reviewMode: 'adversarial' }).then(result => ({ adapter, result, mode: 'adversarial' })),
101
+ ]);
102
+ const results = await Promise.all(reviewPromises);
103
+ const standardResults = results.filter(r => r.mode === 'standard');
104
+ const adversarialResults = results.filter(r => r.mode === 'adversarial');
100
105
  const allFailed = results.every(r => !r.result.success);
101
106
  const someFailed = results.some(r => !r.result.success);
107
+ const lines = [];
102
108
  if (allFailed)
103
109
  lines.push('## Multi-Model Review ❌ All Failed\n');
104
110
  else if (someFailed)
105
111
  lines.push('## Multi-Model Review ⚠️ Partial Success\n');
106
112
  else
107
113
  lines.push('## Multi-Model Review ✓\n');
108
- lines.push(`**Models:** ${availableAdapters.map(a => a.id).join(', ')}\n`);
109
- for (const { adapter, result } of results) {
114
+ lines.push(`**Models:** ${availableAdapters.map(a => a.id).join(', ')} (standard + adversarial)\n`);
115
+ // Standard section
116
+ lines.push('## Standard Review Findings\n');
117
+ for (const { adapter, result } of standardResults) {
110
118
  lines.push(formatResult(result, adapter.getCapabilities().name));
111
119
  lines.push('');
112
120
  }
121
+ // Adversarial section
122
+ lines.push('## Challenge Review Findings\n');
123
+ for (const { adapter, result } of adversarialResults) {
124
+ lines.push(formatResult(result, `${adapter.getCapabilities().name} (Adversarial)`));
125
+ lines.push('');
126
+ }
113
127
  return { content: [{ type: 'text', text: lines.join('\n') }] };
114
128
  }
115
129
  // =============================================================================
@@ -168,7 +182,7 @@ export const TOOL_DEFINITIONS = {
168
182
  },
169
183
  multi_review: {
170
184
  name: 'multi_review',
171
- description: "ONLY use when user explicitly requests '/multi-review' or 'review with all models'. Get parallel second-opinions from Codex, Gemini, and a fresh Claude (Opus) instance. Returns combined reviews for synthesis. DO NOT use for general 'review' requests.",
185
+ description: "ONLY use when user explicitly requests '/multi-review' or 'review with all models'. Runs parallel standard AND adversarial reviews from all available models. Each model reviews twice: standard (bugs/issues) + adversarial (challenge assumptions/design decisions). Use customPrompt to steer the adversarial focus. DO NOT use for general 'review' requests.",
172
186
  inputSchema: {
173
187
  type: 'object',
174
188
  properties: {
@@ -177,7 +191,7 @@ export const TOOL_DEFINITIONS = {
177
191
  outputType: { type: 'string', enum: ['plan', 'findings', 'analysis', 'proposal'], description: 'Type of output being reviewed' },
178
192
  analyzedFiles: { type: 'array', items: { type: 'string' }, description: 'File paths that CC analyzed' },
179
193
  focusAreas: { type: 'array', items: { type: 'string', enum: ['security', 'performance', 'architecture', 'correctness', 'maintainability', 'scalability', 'testing', 'documentation'] }, description: 'Areas to focus the review on' },
180
- customPrompt: { type: 'string', description: 'Custom instructions for the reviewer' },
194
+ customPrompt: { type: 'string', description: 'Custom instructions for standard review + adversarial focus steering' },
181
195
  serviceTier: { type: 'string', enum: ['default', 'fast', 'flex'], description: 'Codex service tier (fast = priority processing, flex = cheaper/slower). Only applies to Codex.' }
182
196
  },
183
197
  required: ['workingDir', 'ccOutput', 'outputType']
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-reviewer",
3
- "version": "4.0.0",
3
+ "version": "5.1.0",
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",