cc-reviewer 1.5.3 → 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.
- package/commands/ask-codex.md +35 -0
- package/commands/ask-gemini.md +35 -0
- package/commands/ask-multi.md +38 -0
- package/dist/adapters/base.d.ts +36 -2
- package/dist/adapters/codex.d.ts +3 -1
- package/dist/adapters/codex.js +96 -5
- package/dist/adapters/gemini.d.ts +3 -1
- package/dist/adapters/gemini.js +88 -2
- package/dist/handoff.d.ts +15 -0
- package/dist/handoff.js +122 -0
- package/dist/index.js +17 -0
- package/dist/schema.d.ts +222 -33
- package/dist/schema.js +203 -14
- package/dist/tools/feedback.d.ts +2 -2
- package/dist/tools/feedback.js +1 -1
- package/dist/tools/peer.d.ts +183 -0
- package/dist/tools/peer.js +336 -0
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
|
@@ -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
|
package/dist/adapters/base.d.ts
CHANGED
|
@@ -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
|
package/dist/adapters/codex.d.ts
CHANGED
|
@@ -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;
|
package/dist/adapters/codex.js
CHANGED
|
@@ -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);
|
|
@@ -214,14 +214,105 @@ export class CodexAdapter {
|
|
|
214
214
|
};
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
|
-
|
|
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) {
|
|
218
309
|
return new Promise((resolve, reject) => {
|
|
219
310
|
// Create temp schema file for structured output
|
|
220
311
|
let schemaFile = null;
|
|
221
312
|
try {
|
|
222
313
|
const tempDir = mkdtempSync(join(tmpdir(), 'codex-schema-'));
|
|
223
314
|
schemaFile = join(tempDir, 'schema.json');
|
|
224
|
-
const schema =
|
|
315
|
+
const schema = schemaGetter();
|
|
225
316
|
writeFileSync(schemaFile, JSON.stringify(schema, null, 2), 'utf-8');
|
|
226
317
|
}
|
|
227
318
|
catch (err) {
|
|
@@ -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;
|
package/dist/adapters/gemini.js
CHANGED
|
@@ -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
|
// =============================================================================
|
|
@@ -212,6 +212,92 @@ export class GeminiAdapter {
|
|
|
212
212
|
};
|
|
213
213
|
}
|
|
214
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
|
+
}
|
|
215
301
|
runCli(prompt, workingDir) {
|
|
216
302
|
return new Promise((resolve, reject) => {
|
|
217
303
|
// Gemini CLI uses --yolo for auto-approval, prompt passed via stdin
|
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;
|
package/dist/handoff.js
CHANGED
|
@@ -476,3 +476,125 @@ export function enhanceHandoff(handoff, uncertainties, questions, decisions) {
|
|
|
476
476
|
decisions: decisions || handoff.decisions,
|
|
477
477
|
};
|
|
478
478
|
}
|
|
479
|
+
/**
|
|
480
|
+
* Build a prompt for general-purpose peer assistance (not review).
|
|
481
|
+
* The peer acts as a collaborative coworker, not a critic.
|
|
482
|
+
*/
|
|
483
|
+
export function buildPeerPrompt(options) {
|
|
484
|
+
const { workingDir, prompt, taskType, relevantFiles, context, focusAreas, customInstructions } = options;
|
|
485
|
+
// Select role based on focus areas (reuse existing role selection)
|
|
486
|
+
const role = selectRole(focusAreas);
|
|
487
|
+
const sections = [];
|
|
488
|
+
// SECTION 1: ROLE (adapted from review role)
|
|
489
|
+
sections.push(`# ROLE: ${role.name} — Peer Engineer
|
|
490
|
+
|
|
491
|
+
${role.systemPrompt}
|
|
492
|
+
|
|
493
|
+
You are acting as a collaborative peer engineer, NOT a reviewer.
|
|
494
|
+
Your job is to help Claude Code (CC) with whatever it needs:
|
|
495
|
+
planning, debugging, explaining, fixing, exploring, or answering questions.
|
|
496
|
+
Be direct, specific, and actionable.`);
|
|
497
|
+
// SECTION 2: TASK
|
|
498
|
+
const taskLabel = taskType ? ` [${taskType.toUpperCase()}]` : '';
|
|
499
|
+
sections.push(`
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
# YOUR TASK${taskLabel}
|
|
503
|
+
|
|
504
|
+
**Working Directory:** \`${workingDir}\`
|
|
505
|
+
|
|
506
|
+
**CC's Request:**
|
|
507
|
+
${prompt}
|
|
508
|
+
${context ? `\n**Additional Context:**\n${context}` : ''}`);
|
|
509
|
+
// SECTION 3: RELEVANT FILES
|
|
510
|
+
if (relevantFiles && relevantFiles.length > 0) {
|
|
511
|
+
sections.push(`
|
|
512
|
+
---
|
|
513
|
+
|
|
514
|
+
# RELEVANT FILES
|
|
515
|
+
|
|
516
|
+
CC suggests focusing on these files:
|
|
517
|
+
${relevantFiles.map(f => `- \`${f}\``).join('\n')}
|
|
518
|
+
|
|
519
|
+
Read these files to understand the context. Also explore related files if needed.`);
|
|
520
|
+
}
|
|
521
|
+
// SECTION 4: FOCUS AREAS
|
|
522
|
+
if (focusAreas && focusAreas.length > 0) {
|
|
523
|
+
sections.push(`
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
# FOCUS AREAS
|
|
527
|
+
|
|
528
|
+
Prioritize these aspects: ${focusAreas.join(', ')}`);
|
|
529
|
+
}
|
|
530
|
+
// SECTION 5: CUSTOM INSTRUCTIONS
|
|
531
|
+
if (customInstructions) {
|
|
532
|
+
sections.push(`
|
|
533
|
+
---
|
|
534
|
+
|
|
535
|
+
# ADDITIONAL INSTRUCTIONS
|
|
536
|
+
|
|
537
|
+
${customInstructions}`);
|
|
538
|
+
}
|
|
539
|
+
// SECTION 6: HOW TO WORK
|
|
540
|
+
sections.push(`
|
|
541
|
+
---
|
|
542
|
+
|
|
543
|
+
# HOW TO WORK
|
|
544
|
+
|
|
545
|
+
1. Read the relevant files in the working directory
|
|
546
|
+
2. Use \`git log --oneline -10\` and \`git diff\` if useful
|
|
547
|
+
3. Think through the problem step by step
|
|
548
|
+
4. Provide a clear, actionable answer
|
|
549
|
+
5. Reference specific files and line numbers
|
|
550
|
+
6. Suggest concrete next steps`);
|
|
551
|
+
// SECTION 7: OUTPUT FORMAT
|
|
552
|
+
sections.push(`
|
|
553
|
+
---
|
|
554
|
+
|
|
555
|
+
# OUTPUT FORMAT
|
|
556
|
+
|
|
557
|
+
Respond with valid JSON:
|
|
558
|
+
|
|
559
|
+
\`\`\`json
|
|
560
|
+
{
|
|
561
|
+
"responder": "<your-name>",
|
|
562
|
+
"answer": "Your detailed response in markdown",
|
|
563
|
+
"confidence": 0.0-1.0,
|
|
564
|
+
"key_points": ["Point 1", "Point 2"],
|
|
565
|
+
"suggested_actions": [
|
|
566
|
+
{
|
|
567
|
+
"action": "What to do",
|
|
568
|
+
"priority": "high|medium|low",
|
|
569
|
+
"file": "path/to/file.ts",
|
|
570
|
+
"rationale": "Why"
|
|
571
|
+
}
|
|
572
|
+
],
|
|
573
|
+
"file_references": [
|
|
574
|
+
{
|
|
575
|
+
"path": "path/to/file.ts",
|
|
576
|
+
"lines": "10-25",
|
|
577
|
+
"relevance": "Why this file matters"
|
|
578
|
+
}
|
|
579
|
+
],
|
|
580
|
+
"alternatives": [
|
|
581
|
+
{
|
|
582
|
+
"topic": "The decision point",
|
|
583
|
+
"current_approach": "What exists now",
|
|
584
|
+
"alternative": "Different approach",
|
|
585
|
+
"tradeoffs": { "pros": ["..."], "cons": ["..."] },
|
|
586
|
+
"recommendation": "strongly_prefer|consider|situational|informational"
|
|
587
|
+
}
|
|
588
|
+
],
|
|
589
|
+
"execution_notes": "Any notes about your process"
|
|
590
|
+
}
|
|
591
|
+
\`\`\`
|
|
592
|
+
|
|
593
|
+
**Rules:**
|
|
594
|
+
- Read files before making claims
|
|
595
|
+
- Reference specific file paths and line numbers
|
|
596
|
+
- Be concrete and actionable — no vague suggestions
|
|
597
|
+
- Confidence reflects how sure YOU are about your answer
|
|
598
|
+
- Include alternatives when there are meaningful tradeoffs`);
|
|
599
|
+
return sections.join('\n');
|
|
600
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -19,6 +19,8 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
|
19
19
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
20
20
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
21
21
|
import { handleCodexReview, handleGeminiReview, handleMultiReview, ReviewInputSchema, TOOL_DEFINITIONS } from './tools/feedback.js';
|
|
22
|
+
import { handleAskCodex, handleAskGemini, handleAskMulti, PEER_TOOL_DEFINITIONS, } from './tools/peer.js';
|
|
23
|
+
import { PeerInputSchema } from './schema.js';
|
|
22
24
|
import { logCliStatus } from './cli/check.js';
|
|
23
25
|
import { installCommands } from './commands.js';
|
|
24
26
|
// Handle --setup flag: install commands and exit
|
|
@@ -51,6 +53,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
51
53
|
TOOL_DEFINITIONS.codex_review,
|
|
52
54
|
TOOL_DEFINITIONS.gemini_review,
|
|
53
55
|
TOOL_DEFINITIONS.multi_review,
|
|
56
|
+
PEER_TOOL_DEFINITIONS.ask_codex,
|
|
57
|
+
PEER_TOOL_DEFINITIONS.ask_gemini,
|
|
58
|
+
PEER_TOOL_DEFINITIONS.ask_multi,
|
|
54
59
|
],
|
|
55
60
|
};
|
|
56
61
|
});
|
|
@@ -71,6 +76,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
71
76
|
const input = ReviewInputSchema.parse(args);
|
|
72
77
|
return await handleMultiReview(input);
|
|
73
78
|
}
|
|
79
|
+
case 'ask_codex': {
|
|
80
|
+
const input = PeerInputSchema.parse(args);
|
|
81
|
+
return await handleAskCodex(input);
|
|
82
|
+
}
|
|
83
|
+
case 'ask_gemini': {
|
|
84
|
+
const input = PeerInputSchema.parse(args);
|
|
85
|
+
return await handleAskGemini(input);
|
|
86
|
+
}
|
|
87
|
+
case 'ask_multi': {
|
|
88
|
+
const input = PeerInputSchema.parse(args);
|
|
89
|
+
return await handleAskMulti(input);
|
|
90
|
+
}
|
|
74
91
|
default:
|
|
75
92
|
return {
|
|
76
93
|
content: [{
|