cc-reviewer 4.0.0 → 5.0.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';
@@ -211,6 +212,13 @@ export declare const ROLES: Record<string, ReviewerRole>;
211
212
  * Select the best role based on focus areas
212
213
  */
213
214
  export declare function selectRole(focusAreas?: FocusArea[]): ReviewerRole;
215
+ export declare const ADVERSARIAL_REVIEWER: ReviewerRole;
216
+ /**
217
+ * Build an adversarial handoff prompt with challenge-mode stance sections.
218
+ * Same structure as buildHandoffPrompt but adds adversarial XML sections
219
+ * and uses the ADVERSARIAL_REVIEWER role.
220
+ */
221
+ export declare function buildAdversarialHandoffPrompt(options: PromptOptions): string;
214
222
  export interface PromptOptions {
215
223
  handoff: Handoff;
216
224
  role?: ReviewerRole;
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
@@ -153,6 +154,121 @@ export function selectRole(focusAreas) {
153
154
  }
154
155
  return CHANGE_FOCUSED_REVIEWER;
155
156
  }
157
+ // =============================================================================
158
+ // ADVERSARIAL REVIEWER — Challenge mode for multi_review
159
+ // =============================================================================
160
+ export const ADVERSARIAL_REVIEWER = {
161
+ id: 'adversarial',
162
+ name: 'Adversarial Reviewer',
163
+ description: 'Actively tries to break confidence in the change — challenges assumptions, not just bugs',
164
+ isGeneric: false,
165
+ applicableFocusAreas: [],
166
+ systemPrompt: `Senior staff engineer performing an adversarial review. Your job is to break confidence in the change, not to validate it.`,
167
+ };
168
+ /**
169
+ * Build an adversarial handoff prompt with challenge-mode stance sections.
170
+ * Same structure as buildHandoffPrompt but adds adversarial XML sections
171
+ * and uses the ADVERSARIAL_REVIEWER role.
172
+ */
173
+ export function buildAdversarialHandoffPrompt(options) {
174
+ const { handoff } = options;
175
+ const role = ADVERSARIAL_REVIEWER;
176
+ const sections = [];
177
+ // SECTION 1: ROLE
178
+ sections.push(`# ROLE: ${role.name}\n\n${role.systemPrompt}`);
179
+ // SECTION 2: ADVERSARIAL STANCE
180
+ sections.push(`## ADVERSARIAL STANCE
181
+
182
+ <operating_stance>
183
+ Default to skepticism. Assume the change can fail in subtle, high-cost,
184
+ or user-visible ways until the evidence says otherwise. Do not give credit
185
+ for good intent, partial fixes, or likely follow-up work.
186
+ </operating_stance>
187
+
188
+ <attack_surface>
189
+ Prioritized failure categories:
190
+ 1. Auth/permissions bypass
191
+ 2. Data loss or corruption
192
+ 3. Rollback safety
193
+ 4. Race conditions / concurrency
194
+ 5. Empty-state / null / timeout handling
195
+ 6. Version skew / backwards compatibility
196
+ 7. Observability gaps (missing logs, metrics, alerts)
197
+ </attack_surface>
198
+
199
+ <review_method>
200
+ Actively try to disprove the change. Look for violated invariants,
201
+ missing guards, unhandled failure paths. If the user supplied a focus area,
202
+ weight it heavily, but still report any other material issue you can defend.
203
+ </review_method>
204
+
205
+ <finding_bar>
206
+ Material findings only. Each must answer:
207
+ 1. What can go wrong?
208
+ 2. Why is this code path vulnerable?
209
+ 3. What is the likely impact?
210
+ 4. What concrete change would reduce the risk?
211
+ </finding_bar>
212
+
213
+ <calibration_rules>
214
+ Prefer one strong finding over several weak ones. If you cannot defend
215
+ a finding from the provided code, drop it.
216
+ </calibration_rules>
217
+
218
+ <grounding_rules>
219
+ Be aggressive, but stay grounded. Every finding must be defensible from
220
+ the repository context. No speculative findings. No "might be an issue"
221
+ without concrete evidence from the code.
222
+ </grounding_rules>`);
223
+ // SECTION 3: TASK (same as standard)
224
+ sections.push(`## YOUR TASK
225
+
226
+ Review code in \`${handoff.workingDir}\`.
227
+
228
+ **Summary:** ${handoff.summary}${handoff.confidence !== undefined && handoff.confidence < 0.9 ? `\n**CC Confidence:** ${Math.round(handoff.confidence * 100)}% — verify weak areas` : ''}
229
+
230
+ **IMPORTANT:**
231
+ - This is a READ-ONLY review. Do NOT create, modify, or delete any files. Only read files to verify claims.
232
+ - Do NOT assume a git repository exists. Do NOT run git commands. Read files directly from the filesystem.`);
233
+ // SECTION 4: CC'S UNCERTAINTIES
234
+ if (handoff.uncertainties && handoff.uncertainties.length > 0) {
235
+ sections.push(`## CC'S UNCERTAINTIES
236
+
237
+ ${handoff.uncertainties.map((u, i) => `### ${i + 1}. ${u.topic} ${u.severity === 'critical' ? '⚠️' : ''}
238
+ - **Question:** ${u.question}
239
+ ${u.ccAssumption ? `- **CC assumed:** ${u.ccAssumption}` : ''}
240
+ ${u.relevantFiles ? `- **Files:** ${u.relevantFiles.join(', ')}` : ''}`).join('\n\n')}`);
241
+ }
242
+ // SECTION 5: SPECIFIC QUESTIONS
243
+ if (handoff.questions && handoff.questions.length > 0) {
244
+ sections.push(`## QUESTIONS FROM CC
245
+
246
+ ${handoff.questions.map((q, i) => `${i + 1}. **${q.question}**
247
+ ${q.context ? `Context: ${q.context}` : ''}
248
+ ${q.ccGuess ? `CC Guess: ${q.ccGuess}` : ''}`).join('\n')}`);
249
+ }
250
+ // SECTION 6: DECISIONS TO EVALUATE
251
+ if (handoff.decisions && handoff.decisions.length > 0) {
252
+ sections.push(`## DECISIONS TO EVALUATE
253
+
254
+ ${handoff.decisions.map((d, i) => `${i + 1}. **${d.decision}**
255
+ Rationale: ${d.rationale}
256
+ ${d.alternatives ? `Alternatives: ${d.alternatives.join(', ')}` : ''}`).join('\n')}`);
257
+ }
258
+ // SECTION 7: FOCUS AREAS
259
+ if (handoff.focusAreas && handoff.focusAreas.length > 0) {
260
+ sections.push(`## FOCUS AREAS\n\nWeight these areas heavily in your adversarial analysis:\n${handoff.focusAreas.map(f => `- **${f}**`).join('\n')}`);
261
+ }
262
+ // SECTION 8: PRIORITY FILES
263
+ if (handoff.priorityFiles && handoff.priorityFiles.length > 0) {
264
+ sections.push(`## PRIORITY FILES\n\n${handoff.priorityFiles.map(f => `- \`${f}\``).join('\n')}`);
265
+ }
266
+ // SECTION 9: ADVERSARIAL FOCUS (customInstructions steers the challenge)
267
+ if (handoff.customInstructions) {
268
+ sections.push(`## ADVERSARIAL FOCUS\n\n${handoff.customInstructions}`);
269
+ }
270
+ return sections.join('\n\n');
271
+ }
156
272
  /**
157
273
  * Build the review prompt using minimal, targeted context.
158
274
  * No output format constraints — reviewer responds naturally, CC interprets.
@@ -166,11 +282,13 @@ export function buildHandoffPrompt(options) {
166
282
  // SECTION 2: TASK
167
283
  sections.push(`## YOUR TASK
168
284
 
169
- Review recent work in \`${handoff.workingDir}\`.
285
+ Review code in \`${handoff.workingDir}\`.
170
286
 
171
287
  **Summary:** ${handoff.summary}${handoff.confidence !== undefined && handoff.confidence < 0.9 ? `\n**CC Confidence:** ${Math.round(handoff.confidence * 100)}% — verify weak areas` : ''}
172
288
 
173
- **IMPORTANT: This is a READ-ONLY review. Do NOT create, modify, or delete any files. Only read files to verify claims.**`);
289
+ **IMPORTANT:**
290
+ - This is a READ-ONLY review. Do NOT create, modify, or delete any files. Only read files to verify claims.
291
+ - Do NOT assume a git repository exists. Do NOT run git commands. Read files directly from the filesystem.`);
174
292
  // SECTION 3: CC'S UNCERTAINTIES
175
293
  if (handoff.uncertainties && handoff.uncertainties.length > 0) {
176
294
  sections.push(`## CC'S UNCERTAINTIES
@@ -200,6 +318,10 @@ ${handoff.decisions.map((d, i) => `${i + 1}. **${d.decision}**
200
318
  if (handoff.priorityFiles && handoff.priorityFiles.length > 0) {
201
319
  sections.push(`## PRIORITY FILES\n\n${handoff.priorityFiles.map(f => `- \`${f}\``).join('\n')}`);
202
320
  }
321
+ // SECTION 7: CUSTOM INSTRUCTIONS
322
+ if (handoff.customInstructions) {
323
+ sections.push(`## ADDITIONAL INSTRUCTIONS\n\n${handoff.customInstructions}`);
324
+ }
203
325
  return sections.join('\n\n');
204
326
  }
205
327
  // =============================================================================
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.0.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",