cc-reviewer 1.5.1 → 1.7.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.
@@ -0,0 +1,35 @@
1
+ # Ask Codex
2
+
3
+ Ask OpenAI Codex CLI for help as a peer engineer.
4
+
5
+ ## Arguments
6
+ - `$ARGUMENTS` - Your question or request
7
+
8
+ ## Codex Strengths
9
+ - **Correctness**: Logic errors, edge cases, bugs
10
+ - **Performance**: Efficiency, complexity analysis
11
+ - **Security**: Vulnerability detection
12
+
13
+ ## Tool Invocation
14
+
15
+ Call `ask_codex` with:
16
+
17
+ ```json
18
+ {
19
+ "workingDir": "<current directory>",
20
+ "prompt": "<your question or request from $ARGUMENTS>",
21
+ "taskType": "<infer from request: plan|debug|explain|question|fix|explore|general>",
22
+ "relevantFiles": ["<files related to the question>"],
23
+ "context": "<any error messages or prior analysis>"
24
+ }
25
+ ```
26
+
27
+ ## After Receiving Response
28
+
29
+ 1. **Read the answer** and key points
30
+ 2. **Check file references** — verify they exist
31
+ 3. **Evaluate suggested actions** — do they make sense?
32
+ 4. **Apply your judgment** — you may disagree
33
+ 5. **Act on the suggestions** or ask follow-up questions
34
+
35
+ $ARGUMENTS
@@ -0,0 +1,35 @@
1
+ # Ask Gemini
2
+
3
+ Ask Google Gemini CLI for help as a peer engineer.
4
+
5
+ ## Arguments
6
+ - `$ARGUMENTS` - Your question or request
7
+
8
+ ## Gemini Strengths
9
+ - **Architecture**: Design patterns, structure
10
+ - **Scalability**: Load handling, bottlenecks
11
+ - **Maintainability**: Code clarity, tech debt
12
+
13
+ ## Tool Invocation
14
+
15
+ Call `ask_gemini` with:
16
+
17
+ ```json
18
+ {
19
+ "workingDir": "<current directory>",
20
+ "prompt": "<your question or request from $ARGUMENTS>",
21
+ "taskType": "<infer from request: plan|debug|explain|question|fix|explore|general>",
22
+ "relevantFiles": ["<files related to the question>"],
23
+ "context": "<any error messages or prior analysis>"
24
+ }
25
+ ```
26
+
27
+ ## After Receiving Response
28
+
29
+ 1. **Read the answer** and key points
30
+ 2. **Check file references** — verify they exist
31
+ 3. **Evaluate suggested actions** — do they make sense?
32
+ 4. **Apply your judgment** — you may disagree
33
+ 5. **Act on the suggestions** or ask follow-up questions
34
+
35
+ $ARGUMENTS
@@ -0,0 +1,38 @@
1
+ # Ask Multi
2
+
3
+ Ask both Codex and Gemini for help in parallel — get multiple perspectives.
4
+
5
+ ## Arguments
6
+ - `$ARGUMENTS` - Your question or request
7
+
8
+ ## When to Use
9
+
10
+ Use `/ask-multi` when you want perspectives from both models on the same question.
11
+
12
+ ## Tool Invocation
13
+
14
+ Call `ask_multi` with:
15
+
16
+ ```json
17
+ {
18
+ "workingDir": "<current directory>",
19
+ "prompt": "<your question or request from $ARGUMENTS>",
20
+ "taskType": "<infer from request: plan|debug|explain|question|fix|explore|general>",
21
+ "relevantFiles": ["<files related to the question>"],
22
+ "context": "<any error messages or prior analysis>"
23
+ }
24
+ ```
25
+
26
+ ## After Receiving Responses
27
+
28
+ You will receive separate responses from each model.
29
+
30
+ ### Synthesize
31
+
32
+ 1. **Find agreements** — both models say the same thing (higher confidence)
33
+ 2. **Identify conflicts** — they disagree (YOU decide who's right)
34
+ 3. **Note unique insights** — findings only one model provided
35
+ 4. **Verify file references** — check they exist
36
+ 5. **Make YOUR recommendation** — don't just relay, apply judgment
37
+
38
+ $ARGUMENTS
@@ -5,8 +5,8 @@
5
5
  * Makes it easy to add new models (Ollama, Azure, etc.) without
6
6
  * changing the core orchestration logic.
7
7
  */
8
- import { ReviewOutput } from '../schema.js';
9
- import { FocusArea, OutputType, ReasoningEffort } from '../types.js';
8
+ import { ReviewOutput, PeerOutput } from '../schema.js';
9
+ import { FocusArea, OutputType, ReasoningEffort, TaskType } from '../types.js';
10
10
  export interface ReviewerCapabilities {
11
11
  /** Display name for this reviewer */
12
12
  name: string;
@@ -43,6 +43,24 @@ export interface ReviewRequest {
43
43
  /** Expert role configuration (optional override) */
44
44
  expertRole?: ExpertRole;
45
45
  }
46
+ export interface PeerRequest {
47
+ /** Working directory containing the code */
48
+ workingDir: string;
49
+ /** The question or request from CC */
50
+ prompt: string;
51
+ /** Hint about the type of task */
52
+ taskType?: TaskType;
53
+ /** Files the peer should focus on */
54
+ relevantFiles?: string[];
55
+ /** Additional context (error messages, prior analysis) */
56
+ context?: string;
57
+ /** Areas to focus on */
58
+ focusAreas?: FocusArea[];
59
+ /** Custom instructions from the user */
60
+ customPrompt?: string;
61
+ /** Reasoning effort level (for models that support it) */
62
+ reasoningEffort?: ReasoningEffort;
63
+ }
46
64
  export interface ExpertRole {
47
65
  name: string;
48
66
  description: string;
@@ -74,6 +92,20 @@ export interface ReviewError {
74
92
  message: string;
75
93
  details?: Record<string, unknown>;
76
94
  }
95
+ export interface PeerSuccess {
96
+ success: true;
97
+ output: PeerOutput;
98
+ rawOutput?: string;
99
+ executionTimeMs: number;
100
+ }
101
+ export interface PeerFailure {
102
+ success: false;
103
+ error: ReviewError;
104
+ suggestion?: string;
105
+ rawOutput?: string;
106
+ executionTimeMs: number;
107
+ }
108
+ export type PeerResult = PeerSuccess | PeerFailure;
77
109
  /**
78
110
  * Base interface that all reviewer adapters must implement.
79
111
  * This allows easy addition of new AI CLIs without changing orchestration logic.
@@ -87,6 +119,8 @@ export interface ReviewerAdapter {
87
119
  isAvailable(): Promise<boolean>;
88
120
  /** Run a review and return structured output */
89
121
  runReview(request: ReviewRequest): Promise<ReviewResult>;
122
+ /** Run a general-purpose peer request and return structured output */
123
+ runPeerRequest(request: PeerRequest): Promise<PeerResult>;
90
124
  /**
91
125
  * Optional: Run peer review of another model's output
92
126
  * Future capability - not currently implemented by any adapter
@@ -4,13 +4,15 @@
4
4
  * Implements the ReviewerAdapter interface for OpenAI's Codex CLI.
5
5
  * Specializes in correctness, edge cases, and performance analysis.
6
6
  */
7
- import { ReviewerAdapter, ReviewerCapabilities, ReviewRequest, ReviewResult } from './base.js';
7
+ import { ReviewerAdapter, ReviewerCapabilities, ReviewRequest, ReviewResult, PeerRequest, PeerResult } from './base.js';
8
8
  export declare class CodexAdapter implements ReviewerAdapter {
9
9
  readonly id = "codex";
10
10
  getCapabilities(): ReviewerCapabilities;
11
11
  isAvailable(): Promise<boolean>;
12
12
  runReview(request: ReviewRequest): Promise<ReviewResult>;
13
13
  private runWithRetry;
14
+ runPeerRequest(request: PeerRequest): Promise<PeerResult>;
15
+ private runPeerWithRetry;
14
16
  private runCli;
15
17
  private categorizeError;
16
18
  private getSuggestion;
@@ -9,8 +9,8 @@ import { existsSync, writeFileSync, unlinkSync, mkdtempSync } from 'fs';
9
9
  import { tmpdir } from 'os';
10
10
  import { join } from 'path';
11
11
  import { registerAdapter, } from './base.js';
12
- import { parseReviewOutput, parseLegacyMarkdownOutput, getReviewOutputJsonSchema } from '../schema.js';
13
- import { buildSimpleHandoff, buildHandoffPrompt, selectRole, } from '../handoff.js';
12
+ import { parseReviewOutput, parseLegacyMarkdownOutput, getReviewOutputJsonSchema, getPeerOutputJsonSchema, parsePeerOutput } from '../schema.js';
13
+ import { buildSimpleHandoff, buildHandoffPrompt, buildPeerPrompt, selectRole, } from '../handoff.js';
14
14
  // =============================================================================
15
15
  // CONFIGURATION
16
16
  // =============================================================================
@@ -89,7 +89,7 @@ export class CodexAdapter {
89
89
  (previousOutput ? `\nPrevious output (for reference):\n${previousOutput.slice(0, 500)}...` : '');
90
90
  }
91
91
  // Run the CLI
92
- const result = await this.runCli(prompt, request.workingDir, request.reasoningEffort || 'high');
92
+ const result = await this.runCli(prompt, request.workingDir, request.reasoningEffort || 'high', getReviewOutputJsonSchema);
93
93
  // Handle CLI errors
94
94
  if (result.exitCode !== 0) {
95
95
  const error = this.categorizeError(result.stderr);
@@ -138,16 +138,29 @@ export class CodexAdapter {
138
138
  executionTimeMs: Date.now() - startTime,
139
139
  };
140
140
  }
141
- // If we used fallback and got minimal data, retry
142
- if (usedFallback && attempt < MAX_RETRIES) {
143
- const hasMinimalData = output.findings.length === 0 &&
144
- output.agreements.length === 0 &&
145
- output.disagreements.length === 0 &&
146
- output.risk_assessment.summary === 'Unable to parse structured risk assessment';
147
- if (hasMinimalData) {
148
- console.error(`[codex] Received incomplete output (fallback parse with no data), retrying...`);
149
- return this.runWithRetry(request, attempt + 1, startTime, 'Received markdown output instead of JSON. Please provide valid JSON output.', result.stdout);
141
+ // Check for empty/minimal data on any parse path
142
+ const hasMinimalData = output.findings.length === 0 &&
143
+ output.agreements.length === 0 &&
144
+ output.disagreements.length === 0;
145
+ if (hasMinimalData) {
146
+ if (attempt < MAX_RETRIES) {
147
+ console.error(`[codex] Received empty output, retrying...`);
148
+ return this.runWithRetry(request, attempt + 1, startTime, usedFallback
149
+ ? 'Received markdown output instead of JSON. Please provide valid JSON output.'
150
+ : 'Output contained no findings, agreements, or disagreements. Please provide substantive review.', result.stdout);
150
151
  }
152
+ // Final attempt with no data — report failure
153
+ return {
154
+ success: false,
155
+ error: {
156
+ type: 'parse_error',
157
+ message: 'Reviewer returned empty output after retries',
158
+ details: { rawOutput: result.stdout.slice(0, 1000) },
159
+ },
160
+ suggestion: 'The model returned no substantive review. Try a different focus area.',
161
+ rawOutput: result.stdout,
162
+ executionTimeMs: Date.now() - startTime,
163
+ };
151
164
  }
152
165
  return {
153
166
  success: true,
@@ -201,14 +214,105 @@ export class CodexAdapter {
201
214
  };
202
215
  }
203
216
  }
204
- runCli(prompt, workingDir, reasoningEffort) {
217
+ async runPeerRequest(request) {
218
+ const startTime = Date.now();
219
+ if (!existsSync(request.workingDir)) {
220
+ return {
221
+ success: false,
222
+ error: {
223
+ type: 'cli_error',
224
+ message: `Working directory does not exist: ${request.workingDir}`,
225
+ },
226
+ suggestion: 'Check that the working directory path is correct',
227
+ executionTimeMs: Date.now() - startTime,
228
+ };
229
+ }
230
+ return this.runPeerWithRetry(request, 0, startTime);
231
+ }
232
+ async runPeerWithRetry(request, attempt, startTime, previousError, previousOutput) {
233
+ try {
234
+ let prompt = buildPeerPrompt({
235
+ workingDir: request.workingDir,
236
+ prompt: request.prompt,
237
+ taskType: request.taskType,
238
+ relevantFiles: request.relevantFiles,
239
+ context: request.context,
240
+ focusAreas: request.focusAreas,
241
+ customInstructions: request.customPrompt,
242
+ outputFormat: 'json',
243
+ });
244
+ if (attempt > 0) {
245
+ prompt += `\n\n---\n\n# RETRY ATTEMPT ${attempt + 1}\n\n` +
246
+ `Previous output had issues: ${previousError}\n` +
247
+ `Please fix these issues and provide valid JSON output.\n` +
248
+ (previousOutput ? `\nPrevious output (for reference):\n${previousOutput.slice(0, 500)}...` : '');
249
+ }
250
+ const result = await this.runCli(prompt, request.workingDir, request.reasoningEffort || 'high', getPeerOutputJsonSchema);
251
+ if (result.exitCode !== 0) {
252
+ const error = this.categorizeError(result.stderr);
253
+ return {
254
+ success: false,
255
+ error,
256
+ suggestion: this.getSuggestion(error),
257
+ rawOutput: result.stderr,
258
+ executionTimeMs: Date.now() - startTime,
259
+ };
260
+ }
261
+ if (result.truncated) {
262
+ return {
263
+ success: false,
264
+ error: { type: 'cli_error', message: 'Output exceeded maximum buffer size (1MB)' },
265
+ suggestion: 'Try a more focused request',
266
+ executionTimeMs: Date.now() - startTime,
267
+ };
268
+ }
269
+ const output = parsePeerOutput(result.stdout);
270
+ if (!output) {
271
+ if (attempt < MAX_RETRIES) {
272
+ return this.runPeerWithRetry(request, attempt + 1, startTime, 'Output did not match expected JSON schema', result.stdout);
273
+ }
274
+ return {
275
+ success: false,
276
+ error: { type: 'parse_error', message: 'Failed to parse peer output after retries',
277
+ details: { rawOutput: result.stdout.slice(0, 1000) } },
278
+ suggestion: 'The model may not be following the output format.',
279
+ rawOutput: result.stdout,
280
+ executionTimeMs: Date.now() - startTime,
281
+ };
282
+ }
283
+ return {
284
+ success: true,
285
+ output,
286
+ rawOutput: result.stdout,
287
+ executionTimeMs: Date.now() - startTime,
288
+ };
289
+ }
290
+ catch (error) {
291
+ const err = error;
292
+ if (err.code === 'ENOENT') {
293
+ return { success: false, error: { type: 'cli_not_found', message: 'Codex CLI not found' },
294
+ suggestion: 'Install with: npm install -g @openai/codex', executionTimeMs: Date.now() - startTime };
295
+ }
296
+ if (err.message === 'TIMEOUT') {
297
+ return { success: false, error: { type: 'timeout', message: 'No output for 2 minutes' },
298
+ suggestion: 'Try a simpler request', executionTimeMs: Date.now() - startTime };
299
+ }
300
+ if (err.message === 'MAX_TIMEOUT') {
301
+ return { success: false, error: { type: 'timeout', message: 'Task exceeded 60 minute maximum' },
302
+ suggestion: 'Try a smaller scope', executionTimeMs: Date.now() - startTime };
303
+ }
304
+ return { success: false, error: { type: 'cli_error', message: err.message },
305
+ executionTimeMs: Date.now() - startTime };
306
+ }
307
+ }
308
+ runCli(prompt, workingDir, reasoningEffort, schemaGetter) {
205
309
  return new Promise((resolve, reject) => {
206
310
  // Create temp schema file for structured output
207
311
  let schemaFile = null;
208
312
  try {
209
313
  const tempDir = mkdtempSync(join(tmpdir(), 'codex-schema-'));
210
314
  schemaFile = join(tempDir, 'schema.json');
211
- const schema = getReviewOutputJsonSchema();
315
+ const schema = schemaGetter();
212
316
  writeFileSync(schemaFile, JSON.stringify(schema, null, 2), 'utf-8');
213
317
  }
214
318
  catch (err) {
@@ -236,6 +340,11 @@ export class CodexAdapter {
236
340
  stdio: ['pipe', 'pipe', 'pipe'], // stdin is pipe for prompt delivery
237
341
  env: { ...process.env }
238
342
  });
343
+ // Guard against EPIPE if the child exits before consuming stdin.
344
+ // Log but don't reject — let the `close` handler capture the real exit code.
345
+ proc.stdin.on('error', (err) => {
346
+ console.error(`[codex] stdin error (likely EPIPE): ${err.message}`);
347
+ });
239
348
  // Deliver prompt via stdin
240
349
  proc.stdin.write(prompt);
241
350
  proc.stdin.end();
@@ -4,13 +4,15 @@
4
4
  * Implements the ReviewerAdapter interface for Google's Gemini CLI.
5
5
  * Specializes in architecture, design patterns, and large-context analysis.
6
6
  */
7
- import { ReviewerAdapter, ReviewerCapabilities, ReviewRequest, ReviewResult } from './base.js';
7
+ import { ReviewerAdapter, ReviewerCapabilities, ReviewRequest, ReviewResult, PeerRequest, PeerResult } from './base.js';
8
8
  export declare class GeminiAdapter implements ReviewerAdapter {
9
9
  readonly id = "gemini";
10
10
  getCapabilities(): ReviewerCapabilities;
11
11
  isAvailable(): Promise<boolean>;
12
12
  runReview(request: ReviewRequest): Promise<ReviewResult>;
13
13
  private runWithRetry;
14
+ runPeerRequest(request: PeerRequest): Promise<PeerResult>;
15
+ private runPeerWithRetry;
14
16
  private runCli;
15
17
  private categorizeError;
16
18
  private getSuggestion;
@@ -7,8 +7,8 @@
7
7
  import { spawn } from 'child_process';
8
8
  import { existsSync } from 'fs';
9
9
  import { registerAdapter, } from './base.js';
10
- import { parseReviewOutput, parseLegacyMarkdownOutput } from '../schema.js';
11
- import { buildSimpleHandoff, buildHandoffPrompt, selectRole, } from '../handoff.js';
10
+ import { parseReviewOutput, parseLegacyMarkdownOutput, parsePeerOutput } from '../schema.js';
11
+ import { buildSimpleHandoff, buildHandoffPrompt, buildPeerPrompt, selectRole, } from '../handoff.js';
12
12
  // =============================================================================
13
13
  // CONFIGURATION
14
14
  // =============================================================================
@@ -136,17 +136,29 @@ export class GeminiAdapter {
136
136
  executionTimeMs: Date.now() - startTime,
137
137
  };
138
138
  }
139
- // If output has no substantive data, retry regardless of parse path
140
- if (attempt < MAX_RETRIES) {
141
- const hasMinimalData = output.findings.length === 0 &&
142
- output.agreements.length === 0 &&
143
- output.disagreements.length === 0;
144
- if (hasMinimalData) {
139
+ // If output has no substantive data, retry or fail
140
+ const hasMinimalData = output.findings.length === 0 &&
141
+ output.agreements.length === 0 &&
142
+ output.disagreements.length === 0;
143
+ if (hasMinimalData) {
144
+ if (attempt < MAX_RETRIES) {
145
145
  console.error(`[gemini] Received empty output, retrying...`);
146
146
  return this.runWithRetry(request, attempt + 1, startTime, usedFallback
147
147
  ? 'Received markdown output instead of JSON. Please provide valid JSON output.'
148
148
  : 'Output contained no findings, agreements, or disagreements. Please provide substantive review.', result.stdout);
149
149
  }
150
+ // Final attempt with no data — report failure
151
+ return {
152
+ success: false,
153
+ error: {
154
+ type: 'parse_error',
155
+ message: 'Reviewer returned empty output after retries',
156
+ details: { rawOutput: result.stdout.slice(0, 1000) },
157
+ },
158
+ suggestion: 'The model returned no substantive review. Try a different focus area.',
159
+ rawOutput: result.stdout,
160
+ executionTimeMs: Date.now() - startTime,
161
+ };
150
162
  }
151
163
  return {
152
164
  success: true,
@@ -200,6 +212,92 @@ export class GeminiAdapter {
200
212
  };
201
213
  }
202
214
  }
215
+ async runPeerRequest(request) {
216
+ const startTime = Date.now();
217
+ if (!existsSync(request.workingDir)) {
218
+ return {
219
+ success: false,
220
+ error: { type: 'cli_error', message: `Working directory does not exist: ${request.workingDir}` },
221
+ suggestion: 'Check that the working directory path is correct',
222
+ executionTimeMs: Date.now() - startTime,
223
+ };
224
+ }
225
+ return this.runPeerWithRetry(request, 0, startTime);
226
+ }
227
+ async runPeerWithRetry(request, attempt, startTime, previousError, previousOutput) {
228
+ try {
229
+ let prompt = buildPeerPrompt({
230
+ workingDir: request.workingDir,
231
+ prompt: request.prompt,
232
+ taskType: request.taskType,
233
+ relevantFiles: request.relevantFiles,
234
+ context: request.context,
235
+ focusAreas: request.focusAreas,
236
+ customInstructions: request.customPrompt,
237
+ outputFormat: 'json',
238
+ });
239
+ if (attempt > 0) {
240
+ prompt += `\n\n---\n\n# RETRY ATTEMPT ${attempt + 1}\n\n` +
241
+ `Previous output had issues: ${previousError}\n` +
242
+ `Please fix these issues and provide valid JSON output.\n` +
243
+ (previousOutput ? `\nPrevious output (for reference):\n${previousOutput.slice(0, 500)}...` : '');
244
+ }
245
+ const result = await this.runCli(prompt, request.workingDir);
246
+ if (result.exitCode !== 0) {
247
+ const error = this.categorizeError(result.stderr);
248
+ return {
249
+ success: false, error,
250
+ suggestion: this.getSuggestion(error),
251
+ rawOutput: result.stderr,
252
+ executionTimeMs: Date.now() - startTime,
253
+ };
254
+ }
255
+ if (result.truncated) {
256
+ return {
257
+ success: false,
258
+ error: { type: 'cli_error', message: 'Output exceeded maximum buffer size (1MB)' },
259
+ suggestion: 'Try a more focused request',
260
+ executionTimeMs: Date.now() - startTime,
261
+ };
262
+ }
263
+ const output = parsePeerOutput(result.stdout);
264
+ if (!output) {
265
+ if (attempt < MAX_RETRIES) {
266
+ return this.runPeerWithRetry(request, attempt + 1, startTime, 'Output did not match expected JSON schema', result.stdout);
267
+ }
268
+ return {
269
+ success: false,
270
+ error: { type: 'parse_error', message: 'Failed to parse peer output after retries',
271
+ details: { rawOutput: result.stdout.slice(0, 1000) } },
272
+ suggestion: 'The model may not be following the output format.',
273
+ rawOutput: result.stdout,
274
+ executionTimeMs: Date.now() - startTime,
275
+ };
276
+ }
277
+ return {
278
+ success: true, output,
279
+ rawOutput: result.stdout,
280
+ executionTimeMs: Date.now() - startTime,
281
+ };
282
+ }
283
+ catch (error) {
284
+ const err = error;
285
+ if (err.code === 'ENOENT') {
286
+ return { success: false, error: { type: 'cli_not_found', message: 'Gemini CLI not found' },
287
+ suggestion: 'Install with: npm install -g @google/gemini-cli', executionTimeMs: Date.now() - startTime };
288
+ }
289
+ if (err.message === 'TIMEOUT') {
290
+ return { success: false, error: { type: 'timeout', message: 'No output for 10 minutes' },
291
+ suggestion: 'Try a simpler request', executionTimeMs: Date.now() - startTime };
292
+ }
293
+ if (err.message === 'MAX_TIMEOUT') {
294
+ return { success: false, error: { type: 'timeout', message: 'Task exceeded 60 minute maximum' },
295
+ suggestion: 'Try a smaller scope', executionTimeMs: Date.now() - startTime };
296
+ }
297
+ return { success: false, error: { type: 'cli_error', message: err.message },
298
+ executionTimeMs: Date.now() - startTime };
299
+ }
300
+ }
203
301
  runCli(prompt, workingDir) {
204
302
  return new Promise((resolve, reject) => {
205
303
  // Gemini CLI uses --yolo for auto-approval, prompt passed via stdin
@@ -209,12 +307,18 @@ export class GeminiAdapter {
209
307
  '--yolo',
210
308
  '--output-format', 'json', // Force JSON output
211
309
  '--include-directories', workingDir,
310
+ '-p', '', // Force headless mode; actual prompt delivered via stdin
212
311
  ];
213
312
  const proc = spawn('gemini', args, {
214
313
  cwd: workingDir,
215
314
  stdio: ['pipe', 'pipe', 'pipe'], // stdin is pipe for prompt delivery
216
315
  env: { ...process.env }
217
316
  });
317
+ // Guard against EPIPE if the child exits before consuming stdin.
318
+ // Log but don't reject — let the `close` handler capture the real exit code.
319
+ proc.stdin.on('error', (err) => {
320
+ console.error(`[gemini] stdin error (likely EPIPE): ${err.message}`);
321
+ });
218
322
  // Deliver prompt via stdin — more stable than args for complex content
219
323
  proc.stdin.write(prompt);
220
324
  proc.stdin.end();
package/dist/handoff.d.ts CHANGED
@@ -230,3 +230,18 @@ export declare function buildSimpleHandoff(workingDir: string, ccOutput: string,
230
230
  * CC should call this to add its specific concerns
231
231
  */
232
232
  export declare function enhanceHandoff(handoff: Handoff, uncertainties?: Uncertainty[], questions?: Question[], decisions?: Decision[]): Handoff;
233
+ export interface PeerPromptOptions {
234
+ workingDir: string;
235
+ prompt: string;
236
+ taskType?: string;
237
+ relevantFiles?: string[];
238
+ context?: string;
239
+ focusAreas?: FocusArea[];
240
+ customInstructions?: string;
241
+ outputFormat: 'json';
242
+ }
243
+ /**
244
+ * Build a prompt for general-purpose peer assistance (not review).
245
+ * The peer acts as a collaborative coworker, not a critic.
246
+ */
247
+ export declare function buildPeerPrompt(options: PeerPromptOptions): string;