cc-reviewer 5.3.0 → 6.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.
- package/README.md +13 -17
- package/commands/multi-consult.md +80 -0
- package/dist/adapters/base.d.ts +17 -0
- package/dist/adapters/claude.d.ts +2 -1
- package/dist/adapters/claude.js +35 -3
- package/dist/adapters/codex.d.ts +2 -1
- package/dist/adapters/codex.js +41 -3
- package/dist/adapters/gemini.d.ts +2 -1
- package/dist/adapters/gemini.js +35 -3
- package/dist/commands.d.ts +5 -1
- package/dist/commands.js +21 -5
- package/dist/config.d.ts +22 -0
- package/dist/config.js +6 -1
- package/dist/consult-prompt.d.ts +10 -0
- package/dist/consult-prompt.js +72 -0
- package/dist/context.d.ts +74 -74
- package/dist/handoff.d.ts +14 -14
- package/dist/index.js +7 -16
- package/dist/schema.d.ts +6 -6
- package/dist/tools/consult.d.ts +104 -0
- package/dist/tools/consult.js +220 -0
- package/dist/tools/feedback.d.ts +6 -160
- package/dist/tools/feedback.js +2 -85
- package/package.json +1 -1
- package/commands/claude-review.md +0 -92
- package/commands/codex-review.md +0 -93
- package/commands/codex-xhigh-review.md +0 -55
- package/commands/gemini-review.md +0 -91
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ claude mcp add -s user cc-reviewer -- npx -y cc-reviewer
|
|
|
11
11
|
|
|
12
12
|
**Step 2: Restart Claude Code**
|
|
13
13
|
|
|
14
|
-
The MCP tools and slash commands (`/
|
|
14
|
+
The MCP tools and slash commands (`/multi-review`, `/multi-consult`) are automatically installed.
|
|
15
15
|
|
|
16
16
|
**Manual command install** (if needed):
|
|
17
17
|
```bash
|
|
@@ -51,11 +51,9 @@ gemini # follow auth prompts
|
|
|
51
51
|
|
|
52
52
|
These tools provide **external second-opinion reviews** from Codex and Gemini CLIs. They are designed to complement Claude Code's native review capabilities, not replace them.
|
|
53
53
|
|
|
54
|
-
**
|
|
55
|
-
- `/
|
|
56
|
-
- `/
|
|
57
|
-
- `/gemini-review` or "review with gemini" - Get external Gemini review
|
|
58
|
-
- `/multi-review` - Get parallel reviews from both CLIs
|
|
54
|
+
**Slash commands:**
|
|
55
|
+
- `/multi-review` - Parallel standard + adversarial reviews from all available CLIs (Codex, Gemini, Claude). For reviewing CC-produced work (plan, findings, code).
|
|
56
|
+
- `/multi-consult` - Ask all CLIs the same question and synthesize their answers. For consultation/Q&A — what's the best approach, how to solve X.
|
|
59
57
|
|
|
60
58
|
**For regular reviews:** Just say "review" and Claude Code will use its native capabilities. These external tools are only invoked when explicitly requested.
|
|
61
59
|
|
|
@@ -64,18 +62,17 @@ These tools provide **external second-opinion reviews** from Codex and Gemini CL
|
|
|
64
62
|
These commands are available after restart:
|
|
65
63
|
|
|
66
64
|
```bash
|
|
67
|
-
/
|
|
68
|
-
/
|
|
69
|
-
/
|
|
70
|
-
/
|
|
71
|
-
/gemini-review architecture # Focus on architecture
|
|
72
|
-
/multi-review # Both models in parallel
|
|
65
|
+
/multi-review # Parallel standard + adversarial reviews from all CLIs
|
|
66
|
+
/multi-review focus on race conditions # Steer the adversarial focus
|
|
67
|
+
/multi-consult <question> # Ask all CLIs and synthesize
|
|
68
|
+
/multi-consult <question> [flex] # Use Codex flex tier (cheaper/slower)
|
|
73
69
|
```
|
|
74
70
|
|
|
75
71
|
## How It Works
|
|
76
72
|
|
|
77
73
|
```
|
|
78
|
-
CC does work → User: /
|
|
74
|
+
CC does work → User: /multi-review → External CLIs review → CC synthesizes → Final output
|
|
75
|
+
User has a question → User: /multi-consult → External CLIs answer → CC synthesizes → Consolidated answer
|
|
79
76
|
```
|
|
80
77
|
|
|
81
78
|
**Key Principles:**
|
|
@@ -98,13 +95,12 @@ CC does work → User: /codex-review → External CLI reviews → CC synthesizes
|
|
|
98
95
|
|
|
99
96
|
## MCP Tools
|
|
100
97
|
|
|
101
|
-
The plugin exposes
|
|
98
|
+
The plugin exposes two MCP tools:
|
|
102
99
|
|
|
103
100
|
| Tool | Description |
|
|
104
101
|
|------|-------------|
|
|
105
|
-
| `
|
|
106
|
-
| `
|
|
107
|
-
| `multi_review` | Parallel review from both models |
|
|
102
|
+
| `multi_review` | Parallel standard + adversarial review from all available CLIs (Codex, Gemini, Claude). Requires `ccOutput`. |
|
|
103
|
+
| `multi_consult` | Ask all available CLIs the same question and receive a 5-section structured response per model. For consultation/Q&A. |
|
|
108
104
|
|
|
109
105
|
## Output Format
|
|
110
106
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Multi Consult
|
|
2
|
+
|
|
3
|
+
Ask Codex, Gemini, and Claude (Opus, fresh context) the same question in parallel and synthesize their answers. Use this for **consultation** — finding the best approach, weighing alternatives, getting a panel's take. NOT for reviewing work CC has already done (use `/multi-review` for that).
|
|
4
|
+
|
|
5
|
+
## Arguments
|
|
6
|
+
- `$ARGUMENTS` — the question itself, optional steering, or both
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
|
|
10
|
+
Use `/multi-consult` when you have a question or problem and want a synthesized panel opinion. The panel responds in a fixed 5-section structure (Recommendation / Reasoning / Tradeoffs / Risks / Open questions). CC reads all three responses and presents one consolidated answer with a "Models said:" provenance footer.
|
|
11
|
+
|
|
12
|
+
## Examples
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
/multi-consult Should we use Postgres or DynamoDB for a write-heavy timeseries workload?
|
|
16
|
+
/multi-consult How should I refactor the auth middleware? Focus on rollback safety.
|
|
17
|
+
/multi-consult What's the cleanest way to memoize this expensive selector? [flex]
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Before Calling - PREPARE THE HANDOFF
|
|
21
|
+
|
|
22
|
+
### 1. Pre-compose the question
|
|
23
|
+
|
|
24
|
+
**`$ARGUMENTS` parsing rule (pinned):**
|
|
25
|
+
|
|
26
|
+
- **If conversation context already contains the question CC has been working on:** compose `question` from that context. `$ARGUMENTS` is treated as pure steering — extract reserved tokens (see below) into schema fields; remainder goes into `customPrompt`.
|
|
27
|
+
- **Otherwise — `$ARGUMENTS` IS the literal question.** Set `customPrompt` to empty. Reserved tokens are extracted *only* when they appear at the *end* of `$ARGUMENTS` inside brackets or parens — e.g., `... [flex]`, `... (high reasoning)`. A bare occurrence of `flex` / `cheap` / `default tier` inside the prose is treated as part of the question, NOT a flag, to avoid corrupting questions like *"Should we offer a flex tier or default tier for customers?"*.
|
|
28
|
+
|
|
29
|
+
### 2. Triage code-grounded questions
|
|
30
|
+
|
|
31
|
+
If the question references the codebase, populate `relevantFiles` with the minimal subset (3-10 files typically) the panel needs. For purely general questions ("Postgres vs Mongo for X workload?"), omit `relevantFiles` — the panel will answer from expertise without trawling the filesystem.
|
|
32
|
+
|
|
33
|
+
### 3. Refuse sensitive working directories
|
|
34
|
+
|
|
35
|
+
If the current working directory is `/etc`, `~`, `~/.ssh`, or any other clearly sensitive system path, **refuse**. Tell the user: "Please invoke `/multi-consult` from a project root — `<cwd>` looks sensitive." Do not call the tool.
|
|
36
|
+
|
|
37
|
+
## Tool Invocation
|
|
38
|
+
|
|
39
|
+
Call `multi_consult` with:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"workingDir": "<current directory>",
|
|
44
|
+
"question": "<CC-composed question OR literal $ARGUMENTS minus end-bracket reserved tokens>",
|
|
45
|
+
"relevantFiles": ["<file1>", "<file2>"],
|
|
46
|
+
"customPrompt": "<steering text or empty>"
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Reserved-token mappings (only when bracketed at end of $ARGUMENTS)
|
|
51
|
+
|
|
52
|
+
- `[flex]` / `[cheap]` / `[budget]` → `serviceTier: "flex"`
|
|
53
|
+
- `[default tier]` / `[standard tier]` → `serviceTier: "default"`
|
|
54
|
+
- `[high reasoning]` → `reasoningEffort: "high"` (overrides default `xhigh`)
|
|
55
|
+
|
|
56
|
+
If the user types one of these mid-question (not in brackets), leave it in the question.
|
|
57
|
+
|
|
58
|
+
## After Receiving the Panel
|
|
59
|
+
|
|
60
|
+
You will receive each model's structured 5-section response. Some may carry a `⚠️ Format drift: missing sections [...]` marker — degrade synthesis confidence accordingly for that model.
|
|
61
|
+
|
|
62
|
+
### Synthesize
|
|
63
|
+
|
|
64
|
+
1. **Cross-compare Recommendations.** Agreement across all three → high confidence. 2-vs-1 split → take a side and *surface the dissent explicitly* in your answer (don't flatten it). All three disagree → present the tradeoff space honestly and pick.
|
|
65
|
+
2. **Mine Tradeoffs and Risks.** Even when models agree on the recommendation, the *reasons* and *risks* often diverge — surface the union, not just the intersection. If a single model raised a Risk the others missed, surface it as "1 model raised: …" — *do not silently drop it.*
|
|
66
|
+
3. **Forward Open questions** to the user only if material — do not dump every "what's your scale?" clarifier.
|
|
67
|
+
4. **Apply your own judgment.** You have full conversation context the panel does not; you may dismiss panel suggestions that miss the user's actual constraint, but say so explicitly when overriding.
|
|
68
|
+
5. **Respond with one consolidated answer**, structured as: **Recommendation** (what to do) → **Why** (synthesis of reasoning) → **Watch out for** (consolidated risks, including any single-model-only risks) → optional **Open question for you** if a real ambiguity blocks the answer.
|
|
69
|
+
6. **Append a "Models said:" provenance footer** — a single line per model with the recommendation in <80 chars. Example:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
---
|
|
73
|
+
**Models said:** Codex → Postgres + read replicas. Gemini → Postgres + Citus. Claude → DynamoDB w/ caveat on cost at scale.
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
This is **non-negotiable**. The footer is the audit trail; without it, synthesis-only is opaque.
|
|
77
|
+
7. **Do NOT paste full raw model outputs to the user** unless they explicitly ask ("show me what each model said", "raw").
|
|
78
|
+
8. **All-failed special case:** if the header is `❌ All Failed`, surface the failure types and **ASK** the user *"Panel unavailable — want my solo answer instead?"*. **Do NOT silently substitute** your own answer for the panel's.
|
|
79
|
+
|
|
80
|
+
$ARGUMENTS
|
package/dist/adapters/base.d.ts
CHANGED
|
@@ -44,6 +44,21 @@ export interface ReviewRequest {
|
|
|
44
44
|
/** Review mode: standard finds bugs, adversarial challenges assumptions */
|
|
45
45
|
reviewMode?: 'standard' | 'adversarial';
|
|
46
46
|
}
|
|
47
|
+
export interface ConsultRequest {
|
|
48
|
+
/** Working directory containing the code (always passed) */
|
|
49
|
+
workingDir: string;
|
|
50
|
+
/** CC-composed, self-contained question for the panel */
|
|
51
|
+
question: string;
|
|
52
|
+
/** CC-triaged file subset for code-grounded questions; omitted on general questions */
|
|
53
|
+
relevantFiles?: string[];
|
|
54
|
+
/** Free-form steering from $ARGUMENTS */
|
|
55
|
+
customPrompt?: string;
|
|
56
|
+
/** Reasoning effort (Codex). Default 'xhigh' for consult (deeper questions). */
|
|
57
|
+
reasoningEffort?: ReasoningEffort;
|
|
58
|
+
/** Service tier (Codex). Same defaulting rules as ReviewRequest. */
|
|
59
|
+
serviceTier?: ServiceTier;
|
|
60
|
+
}
|
|
61
|
+
export type ConsultResult = ReviewResult;
|
|
47
62
|
/** @deprecated Use handoff.ts roles instead */
|
|
48
63
|
export interface ExpertRole {
|
|
49
64
|
name: string;
|
|
@@ -87,6 +102,8 @@ export interface ReviewerAdapter {
|
|
|
87
102
|
isAvailable(): Promise<boolean>;
|
|
88
103
|
/** Run a review and return structured output */
|
|
89
104
|
runReview(request: ReviewRequest): Promise<ReviewResult>;
|
|
105
|
+
/** Run a consultation (Q&A) — required on every adapter. */
|
|
106
|
+
runConsult(request: ConsultRequest): Promise<ConsultResult>;
|
|
90
107
|
/**
|
|
91
108
|
* Optional: Run peer review of another model's output
|
|
92
109
|
* Future capability - not currently implemented by any adapter
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* 2. --disallowed-tools (write tools explicitly blocked)
|
|
11
11
|
* 3. Handoff prompt (explicit READ-ONLY instruction)
|
|
12
12
|
*/
|
|
13
|
-
import { ReviewerAdapter, ReviewerCapabilities, ReviewRequest, ReviewResult } from './base.js';
|
|
13
|
+
import { ReviewerAdapter, ReviewerCapabilities, ReviewRequest, ReviewResult, ConsultRequest, ConsultResult } from './base.js';
|
|
14
14
|
export declare class ClaudeAdapter implements ReviewerAdapter {
|
|
15
15
|
readonly id = "claude";
|
|
16
16
|
getCapabilities(): ReviewerCapabilities;
|
|
@@ -20,5 +20,6 @@ export declare class ClaudeAdapter implements ReviewerAdapter {
|
|
|
20
20
|
private handleException;
|
|
21
21
|
private categorizeError;
|
|
22
22
|
private getSuggestion;
|
|
23
|
+
runConsult(request: ConsultRequest): Promise<ConsultResult>;
|
|
23
24
|
}
|
|
24
25
|
export declare const claudeAdapter: ClaudeAdapter;
|
package/dist/adapters/claude.js
CHANGED
|
@@ -16,6 +16,7 @@ import { registerAdapter, } from './base.js';
|
|
|
16
16
|
import { CliExecutor } from '../executor.js';
|
|
17
17
|
import { ClaudeEventDecoder } from '../decoders/index.js';
|
|
18
18
|
import { buildSimpleHandoff, buildHandoffPrompt, buildAdversarialHandoffPrompt, selectRole, } from '../handoff.js';
|
|
19
|
+
import { buildConsultPrompt } from '../consult-prompt.js';
|
|
19
20
|
import { getConfig } from '../config.js';
|
|
20
21
|
// Write tools explicitly blocked as defense-in-depth
|
|
21
22
|
const DISALLOWED_TOOLS = 'Edit Write NotebookEdit';
|
|
@@ -76,7 +77,7 @@ export class ClaudeAdapter {
|
|
|
76
77
|
return {
|
|
77
78
|
success: false,
|
|
78
79
|
error: { type: 'cli_error', message: 'Claude returned empty response' },
|
|
79
|
-
suggestion: 'Try again or use /
|
|
80
|
+
suggestion: 'Try again or use /multi-review instead',
|
|
80
81
|
executionTimeMs: Date.now() - startTime,
|
|
81
82
|
};
|
|
82
83
|
}
|
|
@@ -153,7 +154,7 @@ export class ClaudeAdapter {
|
|
|
153
154
|
}
|
|
154
155
|
if (err.message === 'TIMEOUT') {
|
|
155
156
|
return { success: false, error: { type: 'timeout', message: 'Claude timed out — no events received' },
|
|
156
|
-
suggestion: 'Try a smaller scope or use /
|
|
157
|
+
suggestion: 'Try a smaller scope or use /multi-review', executionTimeMs: Date.now() - startTime };
|
|
157
158
|
}
|
|
158
159
|
if (err.message === 'MAX_TIMEOUT') {
|
|
159
160
|
return { success: false, error: { type: 'timeout', message: 'Task exceeded 60 minute maximum' },
|
|
@@ -173,12 +174,43 @@ export class ClaudeAdapter {
|
|
|
173
174
|
}
|
|
174
175
|
getSuggestion(error) {
|
|
175
176
|
switch (error.type) {
|
|
176
|
-
case 'rate_limit': return 'Wait and retry, or use /
|
|
177
|
+
case 'rate_limit': return 'Wait and retry, or use /multi-review instead';
|
|
177
178
|
case 'auth_error': return 'Run `claude auth` to authenticate';
|
|
178
179
|
case 'cli_not_found': return 'Install Claude Code: https://docs.anthropic.com/en/docs/claude-code';
|
|
179
180
|
default: return 'Check the error message and try again';
|
|
180
181
|
}
|
|
181
182
|
}
|
|
183
|
+
async runConsult(request) {
|
|
184
|
+
const startTime = Date.now();
|
|
185
|
+
if (!existsSync(request.workingDir)) {
|
|
186
|
+
return {
|
|
187
|
+
success: false,
|
|
188
|
+
error: { type: 'cli_error', message: `Working directory does not exist: ${request.workingDir}` },
|
|
189
|
+
suggestion: 'Check that the working directory path is correct',
|
|
190
|
+
executionTimeMs: Date.now() - startTime,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
const prompt = buildConsultPrompt(request);
|
|
195
|
+
const result = await this.runCli(prompt, request.workingDir);
|
|
196
|
+
if (result.exitCode !== 0) {
|
|
197
|
+
const error = this.categorizeError(result.stderr);
|
|
198
|
+
return { success: false, error, suggestion: this.getSuggestion(error), executionTimeMs: Date.now() - startTime };
|
|
199
|
+
}
|
|
200
|
+
if (!result.stdout.trim()) {
|
|
201
|
+
return {
|
|
202
|
+
success: false,
|
|
203
|
+
error: { type: 'cli_error', message: 'Claude returned empty response' },
|
|
204
|
+
suggestion: 'Try again or use /multi-review instead',
|
|
205
|
+
executionTimeMs: Date.now() - startTime,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
return { success: true, output: result.stdout, executionTimeMs: Date.now() - startTime };
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
return this.handleException(error, startTime);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
182
214
|
}
|
|
183
215
|
// Register the adapter
|
|
184
216
|
registerAdapter(new ClaudeAdapter());
|
package/dist/adapters/codex.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Returns raw text — no JSON parsing or schema enforcement.
|
|
6
6
|
* CC handles interpretation of the reviewer's response.
|
|
7
7
|
*/
|
|
8
|
-
import { ReviewerAdapter, ReviewerCapabilities, ReviewRequest, ReviewResult } from './base.js';
|
|
8
|
+
import { ReviewerAdapter, ReviewerCapabilities, ReviewRequest, ReviewResult, ConsultRequest, ConsultResult } from './base.js';
|
|
9
9
|
export declare class CodexAdapter implements ReviewerAdapter {
|
|
10
10
|
readonly id = "codex";
|
|
11
11
|
getCapabilities(): ReviewerCapabilities;
|
|
@@ -15,5 +15,6 @@ export declare class CodexAdapter implements ReviewerAdapter {
|
|
|
15
15
|
private handleException;
|
|
16
16
|
private categorizeError;
|
|
17
17
|
private getSuggestion;
|
|
18
|
+
runConsult(request: ConsultRequest): Promise<ConsultResult>;
|
|
18
19
|
}
|
|
19
20
|
export declare const codexAdapter: CodexAdapter;
|
package/dist/adapters/codex.js
CHANGED
|
@@ -11,6 +11,7 @@ import { registerAdapter, } from './base.js';
|
|
|
11
11
|
import { CliExecutor } from '../executor.js';
|
|
12
12
|
import { CodexEventDecoder } from '../decoders/index.js';
|
|
13
13
|
import { buildSimpleHandoff, buildHandoffPrompt, buildAdversarialHandoffPrompt, selectRole, } from '../handoff.js';
|
|
14
|
+
import { buildConsultPrompt } from '../consult-prompt.js';
|
|
14
15
|
import { getConfig } from '../config.js';
|
|
15
16
|
// =============================================================================
|
|
16
17
|
// CODEX ADAPTER
|
|
@@ -70,7 +71,7 @@ export class CodexAdapter {
|
|
|
70
71
|
return {
|
|
71
72
|
success: false,
|
|
72
73
|
error: { type: 'cli_error', message: 'Codex returned empty response' },
|
|
73
|
-
suggestion: 'Try again or use /
|
|
74
|
+
suggestion: 'Try again or use /multi-review instead',
|
|
74
75
|
executionTimeMs: Date.now() - startTime,
|
|
75
76
|
};
|
|
76
77
|
}
|
|
@@ -154,7 +155,7 @@ export class CodexAdapter {
|
|
|
154
155
|
}
|
|
155
156
|
if (err.message === 'TIMEOUT') {
|
|
156
157
|
return { success: false, error: { type: 'timeout', message: 'Codex timed out — no events received' },
|
|
157
|
-
suggestion: 'Try a smaller scope or use /
|
|
158
|
+
suggestion: 'Try a smaller scope or use /multi-review', executionTimeMs: Date.now() - startTime };
|
|
158
159
|
}
|
|
159
160
|
if (err.message === 'MAX_TIMEOUT') {
|
|
160
161
|
return { success: false, error: { type: 'timeout', message: 'Task exceeded 60 minute maximum' },
|
|
@@ -177,12 +178,49 @@ export class CodexAdapter {
|
|
|
177
178
|
}
|
|
178
179
|
getSuggestion(error) {
|
|
179
180
|
switch (error.type) {
|
|
180
|
-
case 'rate_limit': return 'Wait and retry, or use /
|
|
181
|
+
case 'rate_limit': return 'Wait and retry, or use /multi-review instead';
|
|
181
182
|
case 'auth_error': return 'Run `codex login` to authenticate';
|
|
182
183
|
case 'cli_not_found': return 'Install with: npm install -g @openai/codex-cli';
|
|
183
184
|
default: return 'Check the error message and try again';
|
|
184
185
|
}
|
|
185
186
|
}
|
|
187
|
+
async runConsult(request) {
|
|
188
|
+
const startTime = Date.now();
|
|
189
|
+
if (!existsSync(request.workingDir)) {
|
|
190
|
+
return {
|
|
191
|
+
success: false,
|
|
192
|
+
error: { type: 'cli_error', message: `Working directory does not exist: ${request.workingDir}` },
|
|
193
|
+
suggestion: 'Check that the working directory path is correct',
|
|
194
|
+
executionTimeMs: Date.now() - startTime,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
const prompt = buildConsultPrompt(request);
|
|
199
|
+
// Consult-specific defaults live in config (Zod defaults to xhigh + fast).
|
|
200
|
+
// Request value > config value > Zod default. Users who want to cap cost
|
|
201
|
+
// can set codex.consultServiceTier: "flex" without touching review.
|
|
202
|
+
const cfg = getConfig().codex;
|
|
203
|
+
const reasoningEffort = request.reasoningEffort ?? cfg.consultReasoningEffort;
|
|
204
|
+
const serviceTier = request.serviceTier ?? cfg.consultServiceTier;
|
|
205
|
+
const result = await this.runCli(prompt, request.workingDir, reasoningEffort, serviceTier);
|
|
206
|
+
if (result.exitCode !== 0) {
|
|
207
|
+
const error = this.categorizeError(result.stderr);
|
|
208
|
+
return { success: false, error, suggestion: this.getSuggestion(error), executionTimeMs: Date.now() - startTime };
|
|
209
|
+
}
|
|
210
|
+
if (!result.stdout.trim()) {
|
|
211
|
+
return {
|
|
212
|
+
success: false,
|
|
213
|
+
error: { type: 'cli_error', message: 'Codex returned empty response' },
|
|
214
|
+
suggestion: 'Try again or use /multi-review instead',
|
|
215
|
+
executionTimeMs: Date.now() - startTime,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
return { success: true, output: result.stdout, executionTimeMs: Date.now() - startTime };
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
return this.handleException(error, startTime);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
186
224
|
}
|
|
187
225
|
// Register the adapter
|
|
188
226
|
registerAdapter(new CodexAdapter());
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Returns raw text — no JSON parsing or schema enforcement.
|
|
6
6
|
* CC handles interpretation of the reviewer's response.
|
|
7
7
|
*/
|
|
8
|
-
import { ReviewerAdapter, ReviewerCapabilities, ReviewRequest, ReviewResult } from './base.js';
|
|
8
|
+
import { ReviewerAdapter, ReviewerCapabilities, ReviewRequest, ReviewResult, ConsultRequest, ConsultResult } from './base.js';
|
|
9
9
|
export declare class GeminiAdapter implements ReviewerAdapter {
|
|
10
10
|
readonly id = "gemini";
|
|
11
11
|
getCapabilities(): ReviewerCapabilities;
|
|
@@ -15,5 +15,6 @@ export declare class GeminiAdapter implements ReviewerAdapter {
|
|
|
15
15
|
private handleException;
|
|
16
16
|
private categorizeError;
|
|
17
17
|
private getSuggestion;
|
|
18
|
+
runConsult(request: ConsultRequest): Promise<ConsultResult>;
|
|
18
19
|
}
|
|
19
20
|
export declare const geminiAdapter: GeminiAdapter;
|
package/dist/adapters/gemini.js
CHANGED
|
@@ -11,6 +11,7 @@ import { registerAdapter, } from './base.js';
|
|
|
11
11
|
import { CliExecutor } from '../executor.js';
|
|
12
12
|
import { GeminiEventDecoder } from '../decoders/index.js';
|
|
13
13
|
import { buildSimpleHandoff, buildHandoffPrompt, buildAdversarialHandoffPrompt, selectRole, } from '../handoff.js';
|
|
14
|
+
import { buildConsultPrompt } from '../consult-prompt.js';
|
|
14
15
|
import { getConfig } from '../config.js';
|
|
15
16
|
// =============================================================================
|
|
16
17
|
// GEMINI ADAPTER
|
|
@@ -69,7 +70,7 @@ export class GeminiAdapter {
|
|
|
69
70
|
return {
|
|
70
71
|
success: false,
|
|
71
72
|
error: { type: 'cli_error', message: 'Gemini returned empty response' },
|
|
72
|
-
suggestion: 'Try again or use /
|
|
73
|
+
suggestion: 'Try again or use /multi-review instead',
|
|
73
74
|
executionTimeMs: Date.now() - startTime,
|
|
74
75
|
};
|
|
75
76
|
}
|
|
@@ -133,7 +134,7 @@ export class GeminiAdapter {
|
|
|
133
134
|
}
|
|
134
135
|
if (err.message === 'TIMEOUT') {
|
|
135
136
|
return { success: false, error: { type: 'timeout', message: 'Gemini timed out — no events received' },
|
|
136
|
-
suggestion: 'Try a smaller scope or use /
|
|
137
|
+
suggestion: 'Try a smaller scope or use /multi-review', executionTimeMs: Date.now() - startTime };
|
|
137
138
|
}
|
|
138
139
|
if (err.message === 'MAX_TIMEOUT') {
|
|
139
140
|
return { success: false, error: { type: 'timeout', message: 'Task exceeded 60 minute maximum' },
|
|
@@ -153,12 +154,43 @@ export class GeminiAdapter {
|
|
|
153
154
|
}
|
|
154
155
|
getSuggestion(error) {
|
|
155
156
|
switch (error.type) {
|
|
156
|
-
case 'rate_limit': return 'Wait and retry, or use /
|
|
157
|
+
case 'rate_limit': return 'Wait and retry, or use /multi-review instead';
|
|
157
158
|
case 'auth_error': return 'Run `gemini` and follow auth prompts, or set GEMINI_API_KEY';
|
|
158
159
|
case 'cli_not_found': return 'Install with: npm install -g @google/gemini-cli';
|
|
159
160
|
default: return 'Check the error message and try again';
|
|
160
161
|
}
|
|
161
162
|
}
|
|
163
|
+
async runConsult(request) {
|
|
164
|
+
const startTime = Date.now();
|
|
165
|
+
if (!existsSync(request.workingDir)) {
|
|
166
|
+
return {
|
|
167
|
+
success: false,
|
|
168
|
+
error: { type: 'cli_error', message: `Working directory does not exist: ${request.workingDir}` },
|
|
169
|
+
suggestion: 'Check that the working directory path is correct',
|
|
170
|
+
executionTimeMs: Date.now() - startTime,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
const prompt = buildConsultPrompt(request);
|
|
175
|
+
const result = await this.runCli(prompt, request.workingDir);
|
|
176
|
+
if (result.exitCode !== 0) {
|
|
177
|
+
const error = this.categorizeError(result.stderr);
|
|
178
|
+
return { success: false, error, suggestion: this.getSuggestion(error), executionTimeMs: Date.now() - startTime };
|
|
179
|
+
}
|
|
180
|
+
if (!result.stdout.trim()) {
|
|
181
|
+
return {
|
|
182
|
+
success: false,
|
|
183
|
+
error: { type: 'cli_error', message: 'Gemini returned empty response' },
|
|
184
|
+
suggestion: 'Try again or use /multi-review instead',
|
|
185
|
+
executionTimeMs: Date.now() - startTime,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
return { success: true, output: result.stdout, executionTimeMs: Date.now() - startTime };
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
return this.handleException(error, startTime);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
162
194
|
}
|
|
163
195
|
// Register the adapter
|
|
164
196
|
registerAdapter(new GeminiAdapter());
|
package/dist/commands.d.ts
CHANGED
|
@@ -19,6 +19,10 @@ export declare function getCommandPaths(): {
|
|
|
19
19
|
/**
|
|
20
20
|
* Install slash commands to ~/.claude/commands/
|
|
21
21
|
*
|
|
22
|
+
* @param overrides Test-only path overrides; production callers pass nothing.
|
|
22
23
|
* @returns Result object with success status and installed commands
|
|
23
24
|
*/
|
|
24
|
-
export declare function installCommands(
|
|
25
|
+
export declare function installCommands(overrides?: Partial<{
|
|
26
|
+
source: string;
|
|
27
|
+
target: string;
|
|
28
|
+
}>): InstallResult;
|
package/dist/commands.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Used by index.ts (auto-install on MCP server startup and `update` subcommand)
|
|
5
5
|
*/
|
|
6
|
-
import { existsSync, mkdirSync, copyFileSync, readdirSync,
|
|
6
|
+
import { existsSync, mkdirSync, copyFileSync, readdirSync, renameSync, statSync } from 'fs';
|
|
7
7
|
import { join, dirname } from 'path';
|
|
8
8
|
import { homedir } from 'os';
|
|
9
9
|
import { fileURLToPath } from 'url';
|
|
@@ -19,6 +19,11 @@ const DEPRECATED_COMMANDS = [
|
|
|
19
19
|
'ask-gemini.md',
|
|
20
20
|
'ask-multi.md',
|
|
21
21
|
'multi-review-adv.md',
|
|
22
|
+
// Removed in favor of /multi-review and /multi-consult only:
|
|
23
|
+
'codex-review.md',
|
|
24
|
+
'codex-xhigh-review.md',
|
|
25
|
+
'gemini-review.md',
|
|
26
|
+
'claude-review.md',
|
|
22
27
|
];
|
|
23
28
|
/**
|
|
24
29
|
* Get source and target paths for command files
|
|
@@ -32,10 +37,13 @@ export function getCommandPaths() {
|
|
|
32
37
|
/**
|
|
33
38
|
* Install slash commands to ~/.claude/commands/
|
|
34
39
|
*
|
|
40
|
+
* @param overrides Test-only path overrides; production callers pass nothing.
|
|
35
41
|
* @returns Result object with success status and installed commands
|
|
36
42
|
*/
|
|
37
|
-
export function installCommands() {
|
|
38
|
-
const
|
|
43
|
+
export function installCommands(overrides) {
|
|
44
|
+
const defaults = getCommandPaths();
|
|
45
|
+
const source = overrides?.source ?? defaults.source;
|
|
46
|
+
const target = overrides?.target ?? defaults.target;
|
|
39
47
|
// Check source exists
|
|
40
48
|
if (!existsSync(source)) {
|
|
41
49
|
return { success: false, installed: [], removed: [], error: 'Commands directory not found' };
|
|
@@ -59,13 +67,21 @@ export function installCommands() {
|
|
|
59
67
|
if (files.length === 0) {
|
|
60
68
|
return { success: false, installed: [], removed: [], error: 'No command files found' };
|
|
61
69
|
}
|
|
62
|
-
// Prune deprecated commands from target
|
|
70
|
+
// Prune deprecated commands from target by renaming to .deprecated.bak
|
|
71
|
+
// (lossless — preserves any user edits the operator may have made). If the
|
|
72
|
+
// backup already exists from a previous upgrade, leave it alone.
|
|
63
73
|
const removed = [];
|
|
64
74
|
for (const oldFile of DEPRECATED_COMMANDS) {
|
|
65
75
|
const oldPath = join(target, oldFile);
|
|
66
76
|
if (existsSync(oldPath)) {
|
|
77
|
+
const backupPath = `${oldPath}.deprecated.bak`;
|
|
67
78
|
try {
|
|
68
|
-
|
|
79
|
+
if (!existsSync(backupPath)) {
|
|
80
|
+
renameSync(oldPath, backupPath);
|
|
81
|
+
}
|
|
82
|
+
// If the backup already exists, the original was already moved on a
|
|
83
|
+
// prior install. The file we see now must be a recreation; leave it
|
|
84
|
+
// alone — the user clearly wants it.
|
|
69
85
|
removed.push(oldFile.replace('.md', ''));
|
|
70
86
|
}
|
|
71
87
|
catch {
|
package/dist/config.d.ts
CHANGED
|
@@ -17,6 +17,11 @@ export declare const CodexConfigSchema: z.ZodDefault<z.ZodObject<{
|
|
|
17
17
|
model: z.ZodDefault<z.ZodString>;
|
|
18
18
|
reasoningEffort: z.ZodDefault<z.ZodEnum<["high", "xhigh"]>>;
|
|
19
19
|
serviceTier: z.ZodDefault<z.ZodEnum<["default", "fast", "flex"]>>;
|
|
20
|
+
/** Consult-specific defaults — separate from review knobs because consult
|
|
21
|
+
* questions are deeper and warrant more reasoning. Users can override
|
|
22
|
+
* these to cap cost without affecting review behavior. */
|
|
23
|
+
consultReasoningEffort: z.ZodDefault<z.ZodEnum<["high", "xhigh"]>>;
|
|
24
|
+
consultServiceTier: z.ZodDefault<z.ZodEnum<["default", "fast", "flex"]>>;
|
|
20
25
|
inactivityTimeoutMs: z.ZodDefault<z.ZodObject<{
|
|
21
26
|
high: z.ZodDefault<z.ZodNumber>;
|
|
22
27
|
xhigh: z.ZodDefault<z.ZodNumber>;
|
|
@@ -33,6 +38,8 @@ export declare const CodexConfigSchema: z.ZodDefault<z.ZodObject<{
|
|
|
33
38
|
model: string;
|
|
34
39
|
reasoningEffort: "high" | "xhigh";
|
|
35
40
|
serviceTier: "default" | "fast" | "flex";
|
|
41
|
+
consultReasoningEffort: "high" | "xhigh";
|
|
42
|
+
consultServiceTier: "default" | "fast" | "flex";
|
|
36
43
|
inactivityTimeoutMs: {
|
|
37
44
|
high: number;
|
|
38
45
|
xhigh: number;
|
|
@@ -43,6 +50,8 @@ export declare const CodexConfigSchema: z.ZodDefault<z.ZodObject<{
|
|
|
43
50
|
model?: string | undefined;
|
|
44
51
|
reasoningEffort?: "high" | "xhigh" | undefined;
|
|
45
52
|
serviceTier?: "default" | "fast" | "flex" | undefined;
|
|
53
|
+
consultReasoningEffort?: "high" | "xhigh" | undefined;
|
|
54
|
+
consultServiceTier?: "default" | "fast" | "flex" | undefined;
|
|
46
55
|
inactivityTimeoutMs?: {
|
|
47
56
|
high?: number | undefined;
|
|
48
57
|
xhigh?: number | undefined;
|
|
@@ -87,6 +96,11 @@ export declare const ConfigSchema: z.ZodDefault<z.ZodObject<{
|
|
|
87
96
|
model: z.ZodDefault<z.ZodString>;
|
|
88
97
|
reasoningEffort: z.ZodDefault<z.ZodEnum<["high", "xhigh"]>>;
|
|
89
98
|
serviceTier: z.ZodDefault<z.ZodEnum<["default", "fast", "flex"]>>;
|
|
99
|
+
/** Consult-specific defaults — separate from review knobs because consult
|
|
100
|
+
* questions are deeper and warrant more reasoning. Users can override
|
|
101
|
+
* these to cap cost without affecting review behavior. */
|
|
102
|
+
consultReasoningEffort: z.ZodDefault<z.ZodEnum<["high", "xhigh"]>>;
|
|
103
|
+
consultServiceTier: z.ZodDefault<z.ZodEnum<["default", "fast", "flex"]>>;
|
|
90
104
|
inactivityTimeoutMs: z.ZodDefault<z.ZodObject<{
|
|
91
105
|
high: z.ZodDefault<z.ZodNumber>;
|
|
92
106
|
xhigh: z.ZodDefault<z.ZodNumber>;
|
|
@@ -103,6 +117,8 @@ export declare const ConfigSchema: z.ZodDefault<z.ZodObject<{
|
|
|
103
117
|
model: string;
|
|
104
118
|
reasoningEffort: "high" | "xhigh";
|
|
105
119
|
serviceTier: "default" | "fast" | "flex";
|
|
120
|
+
consultReasoningEffort: "high" | "xhigh";
|
|
121
|
+
consultServiceTier: "default" | "fast" | "flex";
|
|
106
122
|
inactivityTimeoutMs: {
|
|
107
123
|
high: number;
|
|
108
124
|
xhigh: number;
|
|
@@ -113,6 +129,8 @@ export declare const ConfigSchema: z.ZodDefault<z.ZodObject<{
|
|
|
113
129
|
model?: string | undefined;
|
|
114
130
|
reasoningEffort?: "high" | "xhigh" | undefined;
|
|
115
131
|
serviceTier?: "default" | "fast" | "flex" | undefined;
|
|
132
|
+
consultReasoningEffort?: "high" | "xhigh" | undefined;
|
|
133
|
+
consultServiceTier?: "default" | "fast" | "flex" | undefined;
|
|
116
134
|
inactivityTimeoutMs?: {
|
|
117
135
|
high?: number | undefined;
|
|
118
136
|
xhigh?: number | undefined;
|
|
@@ -157,6 +175,8 @@ export declare const ConfigSchema: z.ZodDefault<z.ZodObject<{
|
|
|
157
175
|
model: string;
|
|
158
176
|
reasoningEffort: "high" | "xhigh";
|
|
159
177
|
serviceTier: "default" | "fast" | "flex";
|
|
178
|
+
consultReasoningEffort: "high" | "xhigh";
|
|
179
|
+
consultServiceTier: "default" | "fast" | "flex";
|
|
160
180
|
inactivityTimeoutMs: {
|
|
161
181
|
high: number;
|
|
162
182
|
xhigh: number;
|
|
@@ -181,6 +201,8 @@ export declare const ConfigSchema: z.ZodDefault<z.ZodObject<{
|
|
|
181
201
|
model?: string | undefined;
|
|
182
202
|
reasoningEffort?: "high" | "xhigh" | undefined;
|
|
183
203
|
serviceTier?: "default" | "fast" | "flex" | undefined;
|
|
204
|
+
consultReasoningEffort?: "high" | "xhigh" | undefined;
|
|
205
|
+
consultServiceTier?: "default" | "fast" | "flex" | undefined;
|
|
184
206
|
inactivityTimeoutMs?: {
|
|
185
207
|
high?: number | undefined;
|
|
186
208
|
xhigh?: number | undefined;
|
package/dist/config.js
CHANGED
|
@@ -21,9 +21,14 @@ import { homedir } from 'os';
|
|
|
21
21
|
// =============================================================================
|
|
22
22
|
export const CodexConfigSchema = z
|
|
23
23
|
.object({
|
|
24
|
-
model: z.string().default('gpt-5.
|
|
24
|
+
model: z.string().default('gpt-5.5'),
|
|
25
25
|
reasoningEffort: z.enum(['high', 'xhigh']).default('high'),
|
|
26
26
|
serviceTier: z.enum(['default', 'fast', 'flex']).default('fast'),
|
|
27
|
+
/** Consult-specific defaults — separate from review knobs because consult
|
|
28
|
+
* questions are deeper and warrant more reasoning. Users can override
|
|
29
|
+
* these to cap cost without affecting review behavior. */
|
|
30
|
+
consultReasoningEffort: z.enum(['high', 'xhigh']).default('xhigh'),
|
|
31
|
+
consultServiceTier: z.enum(['default', 'fast', 'flex']).default('fast'),
|
|
27
32
|
inactivityTimeoutMs: z
|
|
28
33
|
.object({
|
|
29
34
|
high: z.number().int().positive().default(180_000),
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consult Prompt Builder
|
|
3
|
+
*
|
|
4
|
+
* Produces the prompt sent to each model when CC consults the panel via
|
|
5
|
+
* /multi-consult. One identical template for all three adapters — no per-model
|
|
6
|
+
* role lean. The 5-section response structure is enforced by the prompt
|
|
7
|
+
* (lightly validated post-hoc in tools/consult.ts).
|
|
8
|
+
*/
|
|
9
|
+
import { ConsultRequest } from './adapters/base.js';
|
|
10
|
+
export declare function buildConsultPrompt(request: ConsultRequest): string;
|