cc-reviewer 2.0.0 → 2.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.
@@ -2,22 +2,20 @@
2
2
  * Gemini CLI Adapter
3
3
  *
4
4
  * Implements the ReviewerAdapter interface for Google's Gemini CLI.
5
- * Specializes in architecture, design patterns, and large-context analysis.
5
+ * Returns raw text no JSON parsing or schema enforcement.
6
+ * CC handles interpretation of the reviewer's response.
6
7
  */
7
8
  import { spawn } from 'child_process';
8
9
  import { existsSync } from 'fs';
9
10
  import { registerAdapter, } from './base.js';
10
- import { parseReviewOutput, parseLegacyMarkdownOutput, parsePeerOutput, isSubstantiveReview } from '../schema.js';
11
11
  import { CliExecutor } from '../executor.js';
12
12
  import { GeminiEventDecoder } from '../decoders/index.js';
13
13
  import { buildSimpleHandoff, buildHandoffPrompt, buildPeerPrompt, selectRole, } from '../handoff.js';
14
14
  // =============================================================================
15
15
  // CONFIGURATION
16
16
  // =============================================================================
17
- const COLD_START_TIMEOUT_MS = 300_000; // 5 min — waiting for first JSONL event
18
- const STREAMING_TIMEOUT_MS = 90_000; // 90s — if events stop mid-stream
17
+ const INACTIVITY_TIMEOUT_MS = 300_000; // 5 min — covers reasoning gaps between tool use
19
18
  const MAX_TIMEOUT_MS = 3_600_000; // 60 min absolute max
20
- const MAX_RETRIES = 2;
21
19
  const MAX_BUFFER_SIZE = 1024 * 1024; // 1MB max buffer
22
20
  // =============================================================================
23
21
  // GEMINI ADAPTER
@@ -31,9 +29,9 @@ export class GeminiAdapter {
31
29
  strengths: ['architecture', 'maintainability', 'scalability', 'documentation'],
32
30
  weaknesses: ['security'],
33
31
  hasFilesystemAccess: true,
34
- supportsStructuredOutput: true,
35
- maxContextTokens: 2000000, // Gemini has very large context
36
- reasoningLevels: undefined, // Gemini doesn't have configurable reasoning
32
+ supportsStructuredOutput: false,
33
+ maxContextTokens: 2000000,
34
+ reasoningLevels: undefined,
37
35
  };
38
36
  }
39
37
  async isAvailable() {
@@ -41,174 +39,42 @@ export class GeminiAdapter {
41
39
  const proc = spawn('gemini', ['--version'], {
42
40
  stdio: ['ignore', 'pipe', 'pipe'],
43
41
  });
44
- proc.on('close', (code) => {
45
- resolve(code === 0);
46
- });
47
- proc.on('error', () => {
48
- resolve(false);
49
- });
50
- // Timeout after 5s
51
- setTimeout(() => {
52
- proc.kill();
53
- resolve(false);
54
- }, 5000);
42
+ proc.on('close', (code) => resolve(code === 0));
43
+ proc.on('error', () => resolve(false));
44
+ setTimeout(() => { proc.kill(); resolve(false); }, 5000);
55
45
  });
56
46
  }
57
47
  async runReview(request) {
58
48
  const startTime = Date.now();
59
- // Validate working directory
60
49
  if (!existsSync(request.workingDir)) {
61
50
  return {
62
51
  success: false,
63
- error: {
64
- type: 'cli_error',
65
- message: `Working directory does not exist: ${request.workingDir}`,
66
- },
52
+ error: { type: 'cli_error', message: `Working directory does not exist: ${request.workingDir}` },
67
53
  suggestion: 'Check that the working directory path is correct',
68
54
  executionTimeMs: Date.now() - startTime,
69
55
  };
70
56
  }
71
- return this.runWithRetry(request, 0, startTime);
72
- }
73
- async runWithRetry(request, attempt, startTime, previousError, previousOutput) {
74
57
  try {
75
- // Build the prompt using handoff protocol
76
58
  const handoff = buildSimpleHandoff(request.workingDir, request.ccOutput, request.analyzedFiles, request.focusAreas, request.customPrompt);
77
- // Select role based on focus areas (Gemini defaults to architect)
78
59
  const role = selectRole(request.focusAreas);
79
- // Build prompt with retry context if needed
80
- let prompt = buildHandoffPrompt({
81
- handoff,
82
- role,
83
- outputFormat: 'json',
84
- });
85
- // Add retry context if this is a retry attempt
86
- if (attempt > 0) {
87
- prompt += `\n\n---\n\n# RETRY ATTEMPT ${attempt + 1}\n\n` +
88
- `Previous output had issues: ${previousError}\n` +
89
- `Please fix these issues and provide valid JSON output.\n` +
90
- (previousOutput ? `\nPrevious output (for reference):\n${previousOutput.slice(0, 500)}...` : '');
91
- }
92
- // Run the CLI
60
+ const prompt = buildHandoffPrompt({ handoff, role });
93
61
  const result = await this.runCli(prompt, request.workingDir);
94
- // Handle CLI errors
95
62
  if (result.exitCode !== 0) {
96
63
  const error = this.categorizeError(result.stderr);
97
- return {
98
- success: false,
99
- error,
100
- suggestion: this.getSuggestion(error),
101
- rawOutput: result.stderr,
102
- executionTimeMs: Date.now() - startTime,
103
- };
64
+ return { success: false, error, suggestion: this.getSuggestion(error), executionTimeMs: Date.now() - startTime };
104
65
  }
105
- // Handle buffer truncation
106
- if (result.truncated) {
66
+ if (!result.stdout.trim()) {
107
67
  return {
108
68
  success: false,
109
- error: {
110
- type: 'cli_error',
111
- message: 'Output exceeded maximum buffer size (1MB) and was truncated',
112
- },
113
- suggestion: 'Try reviewing a smaller scope with --focus',
69
+ error: { type: 'cli_error', message: 'Gemini returned empty response' },
70
+ suggestion: 'Try again or use /codex instead',
114
71
  executionTimeMs: Date.now() - startTime,
115
72
  };
116
73
  }
117
- // Parse the output
118
- let output = parseReviewOutput(result.stdout);
119
- let usedFallback = false;
120
- // If JSON parsing fails, try legacy markdown
121
- if (!output) {
122
- output = parseLegacyMarkdownOutput(result.stdout, 'gemini');
123
- usedFallback = true;
124
- }
125
- // If no valid output, retry or fail
126
- if (!output) {
127
- if (attempt < MAX_RETRIES) {
128
- return this.runWithRetry(request, attempt + 1, startTime, 'Output did not match expected JSON schema', result.stdout);
129
- }
130
- return {
131
- success: false,
132
- error: {
133
- type: 'parse_error',
134
- message: 'Failed to parse reviewer output after retries',
135
- details: { rawOutput: result.stdout.slice(0, 1000) },
136
- },
137
- suggestion: 'The model may not be following the output format. Try a different focus area.',
138
- rawOutput: result.stdout,
139
- executionTimeMs: Date.now() - startTime,
140
- };
141
- }
142
- // Check for empty/minimal output — centralized substance check
143
- if (!isSubstantiveReview(output)) {
144
- if (attempt < MAX_RETRIES) {
145
- console.error(`[gemini] Received empty output, retrying...`);
146
- return this.runWithRetry(request, attempt + 1, startTime, usedFallback
147
- ? 'Received markdown output instead of JSON. Please provide valid JSON output.'
148
- : 'Output contained no substantive review content. Please provide findings or analysis.', result.stdout);
149
- }
150
- return {
151
- success: false,
152
- error: {
153
- type: 'parse_error',
154
- message: 'Reviewer returned empty output after retries',
155
- details: { rawOutput: result.stdout.slice(0, 1000) },
156
- },
157
- suggestion: 'The model returned no substantive review. Try a different focus area.',
158
- rawOutput: result.stdout,
159
- executionTimeMs: Date.now() - startTime,
160
- };
161
- }
162
- return {
163
- success: true,
164
- output,
165
- rawOutput: result.stdout,
166
- executionTimeMs: Date.now() - startTime,
167
- };
74
+ return { success: true, output: result.stdout, executionTimeMs: Date.now() - startTime };
168
75
  }
169
76
  catch (error) {
170
- const err = error;
171
- if (err.code === 'ENOENT') {
172
- return {
173
- success: false,
174
- error: {
175
- type: 'cli_not_found',
176
- message: 'Gemini CLI not found',
177
- },
178
- suggestion: 'Install with: npm install -g @google/gemini-cli',
179
- executionTimeMs: Date.now() - startTime,
180
- };
181
- }
182
- if (err.message === 'TIMEOUT') {
183
- return {
184
- success: false,
185
- error: {
186
- type: 'timeout',
187
- message: 'No output for 10 minutes - process may be hung',
188
- },
189
- suggestion: 'Try a smaller scope or use --focus',
190
- executionTimeMs: Date.now() - startTime,
191
- };
192
- }
193
- if (err.message === 'MAX_TIMEOUT') {
194
- return {
195
- success: false,
196
- error: {
197
- type: 'timeout',
198
- message: 'Task exceeded 60 minute maximum',
199
- },
200
- suggestion: 'Try a smaller scope',
201
- executionTimeMs: Date.now() - startTime,
202
- };
203
- }
204
- return {
205
- success: false,
206
- error: {
207
- type: 'cli_error',
208
- message: err.message,
209
- },
210
- executionTimeMs: Date.now() - startTime,
211
- };
77
+ return this.handleException(error, startTime);
212
78
  }
213
79
  }
214
80
  async runPeerRequest(request) {
@@ -221,11 +87,8 @@ export class GeminiAdapter {
221
87
  executionTimeMs: Date.now() - startTime,
222
88
  };
223
89
  }
224
- return this.runPeerWithRetry(request, 0, startTime);
225
- }
226
- async runPeerWithRetry(request, attempt, startTime, previousError, previousOutput) {
227
90
  try {
228
- let prompt = buildPeerPrompt({
91
+ const prompt = buildPeerPrompt({
229
92
  workingDir: request.workingDir,
230
93
  prompt: request.prompt,
231
94
  taskType: request.taskType,
@@ -233,81 +96,36 @@ export class GeminiAdapter {
233
96
  context: request.context,
234
97
  focusAreas: request.focusAreas,
235
98
  customInstructions: request.customPrompt,
236
- outputFormat: 'json',
237
99
  });
238
- if (attempt > 0) {
239
- prompt += `\n\n---\n\n# RETRY ATTEMPT ${attempt + 1}\n\n` +
240
- `Previous output had issues: ${previousError}\n` +
241
- `Please fix these issues and provide valid JSON output.\n` +
242
- (previousOutput ? `\nPrevious output (for reference):\n${previousOutput.slice(0, 500)}...` : '');
243
- }
244
100
  const result = await this.runCli(prompt, request.workingDir);
245
101
  if (result.exitCode !== 0) {
246
102
  const error = this.categorizeError(result.stderr);
247
- return {
248
- success: false, error,
249
- suggestion: this.getSuggestion(error),
250
- rawOutput: result.stderr,
251
- executionTimeMs: Date.now() - startTime,
252
- };
253
- }
254
- if (result.truncated) {
255
- return {
256
- success: false,
257
- error: { type: 'cli_error', message: 'Output exceeded maximum buffer size (1MB)' },
258
- suggestion: 'Try a more focused request',
259
- executionTimeMs: Date.now() - startTime,
260
- };
103
+ return { success: false, error, suggestion: this.getSuggestion(error), executionTimeMs: Date.now() - startTime };
261
104
  }
262
- const output = parsePeerOutput(result.stdout);
263
- if (!output) {
264
- if (attempt < MAX_RETRIES) {
265
- return this.runPeerWithRetry(request, attempt + 1, startTime, 'Output did not match expected JSON schema', result.stdout);
266
- }
105
+ if (!result.stdout.trim()) {
267
106
  return {
268
107
  success: false,
269
- error: { type: 'parse_error', message: 'Failed to parse peer output after retries',
270
- details: { rawOutput: result.stdout.slice(0, 1000) } },
271
- suggestion: 'The model may not be following the output format.',
272
- rawOutput: result.stdout,
108
+ error: { type: 'cli_error', message: 'Gemini returned empty response' },
109
+ suggestion: 'Try again or use /ask-codex instead',
273
110
  executionTimeMs: Date.now() - startTime,
274
111
  };
275
112
  }
276
- return {
277
- success: true, output,
278
- rawOutput: result.stdout,
279
- executionTimeMs: Date.now() - startTime,
280
- };
113
+ return { success: true, output: result.stdout, executionTimeMs: Date.now() - startTime };
281
114
  }
282
115
  catch (error) {
283
- const err = error;
284
- if (err.code === 'ENOENT') {
285
- return { success: false, error: { type: 'cli_not_found', message: 'Gemini CLI not found' },
286
- suggestion: 'Install with: npm install -g @google/gemini-cli', executionTimeMs: Date.now() - startTime };
287
- }
288
- if (err.message === 'TIMEOUT') {
289
- return { success: false, error: { type: 'timeout', message: 'No output for 10 minutes' },
290
- suggestion: 'Try a simpler request', executionTimeMs: Date.now() - startTime };
291
- }
292
- if (err.message === 'MAX_TIMEOUT') {
293
- return { success: false, error: { type: 'timeout', message: 'Task exceeded 60 minute maximum' },
294
- suggestion: 'Try a smaller scope', executionTimeMs: Date.now() - startTime };
295
- }
296
- return { success: false, error: { type: 'cli_error', message: err.message },
297
- executionTimeMs: Date.now() - startTime };
116
+ return this.handleException(error, startTime);
298
117
  }
299
118
  }
300
119
  async runCli(prompt, workingDir) {
301
120
  const args = [
302
121
  '--yolo',
303
- '--output-format', 'stream-json', // JSONL streaming events (was: json)
122
+ '--output-format', 'stream-json',
304
123
  '--include-directories', workingDir,
305
- '-p', '', // Headless mode; prompt via stdin
124
+ '-p', '',
306
125
  ];
307
126
  const decoder = new GeminiEventDecoder();
308
127
  const cliStartTime = Date.now();
309
- let firstEventReceived = false;
310
- console.error('[gemini] Running review...');
128
+ console.error('[gemini] Running...');
311
129
  decoder.onProgress = (eventType, detail) => {
312
130
  const elapsed = Math.round((Date.now() - cliStartTime) / 1000);
313
131
  const detailStr = detail ? ` — ${detail}` : '';
@@ -318,15 +136,11 @@ export class GeminiAdapter {
318
136
  args,
319
137
  cwd: workingDir,
320
138
  stdin: prompt,
321
- inactivityTimeoutMs: COLD_START_TIMEOUT_MS,
139
+ inactivityTimeoutMs: INACTIVITY_TIMEOUT_MS,
322
140
  maxTimeoutMs: MAX_TIMEOUT_MS,
323
141
  maxBufferSize: MAX_BUFFER_SIZE,
324
142
  onLine: (line) => {
325
143
  decoder.processLine(line);
326
- if (!firstEventReceived) {
327
- firstEventReceived = true;
328
- executor.setInactivityTimeout(STREAMING_TIMEOUT_MS);
329
- }
330
144
  },
331
145
  });
332
146
  const result = await executor.run();
@@ -340,46 +154,41 @@ export class GeminiAdapter {
340
154
  truncated: result.truncated,
341
155
  };
342
156
  }
157
+ handleException(error, startTime) {
158
+ const err = error;
159
+ if (err.code === 'ENOENT') {
160
+ return { success: false, error: { type: 'cli_not_found', message: 'Gemini CLI not found' },
161
+ suggestion: 'Install with: npm install -g @google/gemini-cli', executionTimeMs: Date.now() - startTime };
162
+ }
163
+ if (err.message === 'TIMEOUT') {
164
+ return { success: false, error: { type: 'timeout', message: 'Gemini timed out — no events received' },
165
+ suggestion: 'Try a smaller scope or use /codex', executionTimeMs: Date.now() - startTime };
166
+ }
167
+ if (err.message === 'MAX_TIMEOUT') {
168
+ return { success: false, error: { type: 'timeout', message: 'Task exceeded 60 minute maximum' },
169
+ suggestion: 'Try a smaller scope', executionTimeMs: Date.now() - startTime };
170
+ }
171
+ return { success: false, error: { type: 'cli_error', message: err.message }, executionTimeMs: Date.now() - startTime };
172
+ }
343
173
  categorizeError(stderr) {
344
174
  const lower = stderr.toLowerCase();
345
175
  if (lower.includes('rate limit') || lower.includes('quota')) {
346
- return {
347
- type: 'rate_limit',
348
- message: 'Rate limit or quota exceeded',
349
- details: { retryAfterMs: this.parseRetryAfter(stderr) },
350
- };
176
+ return { type: 'rate_limit', message: 'Rate limit or quota exceeded' };
351
177
  }
352
- if (lower.includes('unauthorized') || lower.includes('authentication') ||
353
- lower.includes('api key') || stderr.includes('401') || stderr.includes('403')) {
354
- return {
355
- type: 'auth_error',
356
- message: 'Authentication failed',
357
- details: { stderr },
358
- };
178
+ if (lower.includes('unauthorized') || lower.includes('authentication') || lower.includes('api key') || stderr.includes('401') || stderr.includes('403')) {
179
+ return { type: 'auth_error', message: 'Authentication failed', details: { stderr } };
359
180
  }
360
- return {
361
- type: 'cli_error',
362
- message: stderr || 'Unknown error',
363
- };
181
+ return { type: 'cli_error', message: stderr || 'Unknown error' };
364
182
  }
365
183
  getSuggestion(error) {
366
184
  switch (error.type) {
367
- case 'rate_limit':
368
- return 'Wait and retry, or use /codex instead';
369
- case 'auth_error':
370
- return 'Run `gemini` and follow auth prompts, or set GEMINI_API_KEY';
371
- case 'cli_not_found':
372
- return 'Install with: npm install -g @google/gemini-cli';
373
- default:
374
- return 'Check the error message and try again';
185
+ case 'rate_limit': return 'Wait and retry, or use /codex instead';
186
+ case 'auth_error': return 'Run `gemini` and follow auth prompts, or set GEMINI_API_KEY';
187
+ case 'cli_not_found': return 'Install with: npm install -g @google/gemini-cli';
188
+ default: return 'Check the error message and try again';
375
189
  }
376
190
  }
377
- parseRetryAfter(errorMessage) {
378
- const match = errorMessage.match(/retry[- ]?after[:\s]+(\d+)/i);
379
- return match ? parseInt(match[1]) * 1000 : undefined;
380
- }
381
191
  }
382
192
  // Register the adapter
383
193
  registerAdapter(new GeminiAdapter());
384
- // Export for direct use
385
194
  export const geminiAdapter = new GeminiAdapter();
@@ -42,6 +42,8 @@ export declare class CodexEventDecoder {
42
42
  onProgress?: (eventType: string, detail?: string) => void;
43
43
  private _finalResponse;
44
44
  private _usage;
45
+ private _error;
46
+ private _eventCount;
45
47
  /**
46
48
  * Parse a single JSONL line. Silently skips malformed or empty input.
47
49
  */
@@ -56,5 +58,14 @@ export declare class CodexEventDecoder {
56
58
  * `null` if no such event has been seen.
57
59
  */
58
60
  getUsage(): CodexEvent['usage'] | null;
61
+ /**
62
+ * Returns the error message from `error` or `turn.failed` events, or `null`.
63
+ */
64
+ getError(): string | null;
65
+ /**
66
+ * Returns true if events were received but no agent_message was produced.
67
+ * Combined with a fast exit, this indicates rate limiting or instant rejection.
68
+ */
69
+ hasNoOutput(): boolean;
59
70
  private _handleEvent;
60
71
  }
@@ -25,6 +25,10 @@ export class CodexEventDecoder {
25
25
  _finalResponse = null;
26
26
  // Token usage from the most recently seen turn.completed
27
27
  _usage = null;
28
+ // Error message from error/turn.failed events
29
+ _error = null;
30
+ // Count of events received (0 = possible rate limit / instant rejection)
31
+ _eventCount = 0;
28
32
  // =============================================================================
29
33
  // PUBLIC API
30
34
  // =============================================================================
@@ -63,10 +67,24 @@ export class CodexEventDecoder {
63
67
  getUsage() {
64
68
  return this._usage;
65
69
  }
70
+ /**
71
+ * Returns the error message from `error` or `turn.failed` events, or `null`.
72
+ */
73
+ getError() {
74
+ return this._error;
75
+ }
76
+ /**
77
+ * Returns true if events were received but no agent_message was produced.
78
+ * Combined with a fast exit, this indicates rate limiting or instant rejection.
79
+ */
80
+ hasNoOutput() {
81
+ return this._eventCount > 0 && this._finalResponse === null;
82
+ }
66
83
  // =============================================================================
67
84
  // PRIVATE HELPERS
68
85
  // =============================================================================
69
86
  _handleEvent(event) {
87
+ this._eventCount++;
70
88
  // Track the last agent_message text
71
89
  if (event.type === 'item.completed' &&
72
90
  event.item?.type === 'agent_message' &&
@@ -77,6 +95,17 @@ export class CodexEventDecoder {
77
95
  if (event.type === 'turn.completed' && event.usage != null) {
78
96
  this._usage = event.usage;
79
97
  }
98
+ // Capture errors from error/turn.failed events
99
+ if (event.type === 'error') {
100
+ this._error = event.message || 'Unknown error from Codex';
101
+ }
102
+ if (event.type === 'turn.failed') {
103
+ this._error = event.error?.message || 'Turn failed';
104
+ }
105
+ // Capture error items (e.g. model errors reported as item.completed with type=error)
106
+ if (event.type === 'item.completed' && event.item?.type === 'error') {
107
+ this._error = event.item.message || event.item.text || 'Model error';
108
+ }
80
109
  // Notify caller
81
110
  this.onProgress?.(event.type, describeEvent(event));
82
111
  }
package/dist/handoff.d.ts CHANGED
@@ -214,10 +214,10 @@ export declare function selectRole(focusAreas?: FocusArea[]): ReviewerRole;
214
214
  export interface PromptOptions {
215
215
  handoff: Handoff;
216
216
  role?: ReviewerRole;
217
- outputFormat: 'json' | 'markdown' | 'schema-enforced';
218
217
  }
219
218
  /**
220
- * Build the review prompt using minimal, targeted context
219
+ * Build the review prompt using minimal, targeted context.
220
+ * No output format constraints — reviewer responds naturally, CC interprets.
221
221
  */
222
222
  export declare function buildHandoffPrompt(options: PromptOptions): string;
223
223
  /**
@@ -237,10 +237,9 @@ export interface PeerPromptOptions {
237
237
  context?: string;
238
238
  focusAreas?: FocusArea[];
239
239
  customInstructions?: string;
240
- outputFormat: 'json' | 'schema-enforced';
241
240
  }
242
241
  /**
243
242
  * Build a prompt for general-purpose peer assistance (not review).
244
- * The peer acts as a collaborative coworker, not a critic.
243
+ * No output format constraints peer responds naturally, CC interprets.
245
244
  */
246
245
  export declare function buildPeerPrompt(options: PeerPromptOptions): string;
package/dist/handoff.js CHANGED
@@ -154,10 +154,11 @@ export function selectRole(focusAreas) {
154
154
  return CHANGE_FOCUSED_REVIEWER;
155
155
  }
156
156
  /**
157
- * Build the review prompt using minimal, targeted context
157
+ * Build the review prompt using minimal, targeted context.
158
+ * No output format constraints — reviewer responds naturally, CC interprets.
158
159
  */
159
160
  export function buildHandoffPrompt(options) {
160
- const { handoff, outputFormat } = options;
161
+ const { handoff } = options;
161
162
  const role = options.role || selectRole(handoff.focusAreas);
162
163
  const sections = [];
163
164
  // SECTION 1: ROLE
@@ -197,38 +198,6 @@ ${handoff.decisions.map((d, i) => `${i + 1}. **${d.decision}**
197
198
  if (handoff.priorityFiles && handoff.priorityFiles.length > 0) {
198
199
  sections.push(`## PRIORITY FILES\n\n${handoff.priorityFiles.map(f => `- \`${f}\``).join('\n')}`);
199
200
  }
200
- // SECTION 7: OUTPUT FORMAT
201
- if (outputFormat === 'schema-enforced') {
202
- sections.push(`## OUTPUT FORMAT
203
- Respond with valid JSON matching the schema. Confidence reflects YOUR certainty.`);
204
- }
205
- else if (outputFormat === 'json') {
206
- sections.push(`## OUTPUT FORMAT
207
- Respond with valid JSON:
208
- \`\`\`json
209
- {
210
- "findings": [{
211
- "id": "string",
212
- "category": "security|performance|correctness|architecture|other",
213
- "severity": "critical|high|medium|low|info",
214
- "confidence": 0.0-1.0,
215
- "title": "string",
216
- "description": "string",
217
- "location": { "file": "string", "line_start": 0 },
218
- "evidence": "code snippet",
219
- "suggestion": "string"
220
- }],
221
- "uncertainty_responses": [{"uncertainty_index": 0, "verified": true, "finding": "string", "recommendation": "string"}],
222
- "question_answers": [{"question_index": 0, "answer": "string", "confidence": 0.0-1.0}],
223
- "agreements": ["string"],
224
- "risk_assessment": { "level": "critical|high|medium|low|minimal", "summary": "string" }
225
- }
226
- \`\`\``);
227
- }
228
- else {
229
- sections.push(`## OUTPUT FORMAT
230
- Structure: ## Findings, ## Uncertainty Responses, ## Question Answers, ## Agreements, ## Risk Assessment.`);
231
- }
232
201
  return sections.join('\n\n');
233
202
  }
234
203
  // =============================================================================
@@ -260,10 +229,10 @@ export function enhanceHandoff(handoff, uncertainties, questions, decisions) {
260
229
  }
261
230
  /**
262
231
  * Build a prompt for general-purpose peer assistance (not review).
263
- * The peer acts as a collaborative coworker, not a critic.
232
+ * No output format constraints peer responds naturally, CC interprets.
264
233
  */
265
234
  export function buildPeerPrompt(options) {
266
- const { workingDir, prompt, taskType, relevantFiles, context, focusAreas, customInstructions, outputFormat } = options;
235
+ const { workingDir, prompt, taskType, relevantFiles, context, focusAreas, customInstructions } = options;
267
236
  const role = selectRole(focusAreas);
268
237
  const sections = [];
269
238
  // SECTION 1: ROLE
@@ -288,26 +257,5 @@ Be direct and actionable.`);
288
257
  if (customInstructions) {
289
258
  sections.push(`## ADDITIONAL INSTRUCTIONS\n\n${customInstructions}`);
290
259
  }
291
- // SECTION 6: OUTPUT FORMAT
292
- if (outputFormat === 'schema-enforced') {
293
- sections.push(`## OUTPUT FORMAT
294
- Respond with valid JSON matching the schema. Confidence reflects YOUR certainty.`);
295
- }
296
- else {
297
- sections.push(`## OUTPUT FORMAT
298
- Respond with valid JSON:
299
- \`\`\`json
300
- {
301
- "responder": "string",
302
- "answer": "markdown",
303
- "confidence": 0.0-1.0,
304
- "key_points": ["string"],
305
- "suggested_actions": [{ "action": "string", "priority": "high|medium|low", "file": "string", "rationale": "string" }],
306
- "file_references": [{ "path": "string", "lines": "string", "relevance": "string" }],
307
- "alternatives": [{ "topic": "string", "current_approach": "string", "alternative": "string", "tradeoffs": { "pros": [], "cons": [] }, "recommendation": "string" }],
308
- "execution_notes": "string"
309
- }
310
- \`\`\``);
311
- }
312
260
  return sections.join('\n\n');
313
261
  }
package/dist/schema.d.ts CHANGED
@@ -76,8 +76,8 @@ export declare const ReviewFinding: z.ZodObject<{
76
76
  column_start?: number | undefined;
77
77
  column_end?: number | undefined;
78
78
  } | undefined;
79
- evidence?: string | undefined;
80
79
  suggestion?: string | undefined;
80
+ evidence?: string | undefined;
81
81
  cwe_id?: string | undefined;
82
82
  owasp_category?: string | undefined;
83
83
  tags?: string[] | undefined;
@@ -95,8 +95,8 @@ export declare const ReviewFinding: z.ZodObject<{
95
95
  column_start?: number | undefined;
96
96
  column_end?: number | undefined;
97
97
  } | undefined;
98
- evidence?: string | undefined;
99
98
  suggestion?: string | undefined;
99
+ evidence?: string | undefined;
100
100
  cwe_id?: string | undefined;
101
101
  owasp_category?: string | undefined;
102
102
  tags?: string[] | undefined;
@@ -279,8 +279,8 @@ export declare const ReviewOutput: z.ZodObject<{
279
279
  column_start?: number | undefined;
280
280
  column_end?: number | undefined;
281
281
  } | undefined;
282
- evidence?: string | undefined;
283
282
  suggestion?: string | undefined;
283
+ evidence?: string | undefined;
284
284
  cwe_id?: string | undefined;
285
285
  owasp_category?: string | undefined;
286
286
  tags?: string[] | undefined;
@@ -298,8 +298,8 @@ export declare const ReviewOutput: z.ZodObject<{
298
298
  column_start?: number | undefined;
299
299
  column_end?: number | undefined;
300
300
  } | undefined;
301
- evidence?: string | undefined;
302
301
  suggestion?: string | undefined;
302
+ evidence?: string | undefined;
303
303
  cwe_id?: string | undefined;
304
304
  owasp_category?: string | undefined;
305
305
  tags?: string[] | undefined;
@@ -444,8 +444,8 @@ export declare const ReviewOutput: z.ZodObject<{
444
444
  column_start?: number | undefined;
445
445
  column_end?: number | undefined;
446
446
  } | undefined;
447
- evidence?: string | undefined;
448
447
  suggestion?: string | undefined;
448
+ evidence?: string | undefined;
449
449
  cwe_id?: string | undefined;
450
450
  owasp_category?: string | undefined;
451
451
  tags?: string[] | undefined;
@@ -512,8 +512,8 @@ export declare const ReviewOutput: z.ZodObject<{
512
512
  column_start?: number | undefined;
513
513
  column_end?: number | undefined;
514
514
  } | undefined;
515
- evidence?: string | undefined;
516
515
  suggestion?: string | undefined;
516
+ evidence?: string | undefined;
517
517
  cwe_id?: string | undefined;
518
518
  owasp_category?: string | undefined;
519
519
  tags?: string[] | undefined;