cc-reviewer 3.0.0 → 4.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/commands/claude-review.md +92 -0
- package/commands/multi-review.md +2 -2
- package/dist/adapters/claude.d.ts +24 -0
- package/dist/adapters/claude.js +175 -0
- package/dist/adapters/codex.js +2 -1
- package/dist/adapters/gemini.js +2 -1
- package/dist/adapters/index.d.ts +2 -0
- package/dist/adapters/index.js +2 -0
- package/dist/cli/check.js +8 -5
- package/dist/decoders/claude.d.ts +53 -0
- package/dist/decoders/claude.js +106 -0
- package/dist/decoders/index.d.ts +2 -0
- package/dist/decoders/index.js +1 -0
- package/dist/errors.js +8 -4
- package/dist/handoff.js +3 -1
- package/dist/index.js +6 -1
- package/dist/tools/feedback.d.ts +48 -0
- package/dist/tools/feedback.js +27 -1
- package/dist/types.d.ts +2 -1
- package/dist/types.js +5 -0
- package/package.json +1 -1
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Claude Review
|
|
2
|
+
|
|
3
|
+
Get a review from a fresh Claude (Opus) instance with clean context — zero memory of this session.
|
|
4
|
+
|
|
5
|
+
## Arguments
|
|
6
|
+
- `$ARGUMENTS` - Optional: focus area or custom instructions
|
|
7
|
+
|
|
8
|
+
## Claude Strengths
|
|
9
|
+
- **Deep Analysis**: Thorough reasoning across all dimensions
|
|
10
|
+
- **Correctness**: Logic errors, edge cases, subtle bugs
|
|
11
|
+
- **Security**: Vulnerability detection, auth analysis
|
|
12
|
+
- **Architecture**: Design patterns, coupling, abstractions
|
|
13
|
+
- **Clean Context**: No confirmation bias from the current session
|
|
14
|
+
|
|
15
|
+
## Before Calling - PREPARE THE HANDOFF
|
|
16
|
+
|
|
17
|
+
### 1. Summarize What You Did (Brief!)
|
|
18
|
+
```
|
|
19
|
+
"Implemented WebSocket reconnection with exponential backoff
|
|
20
|
+
and added session persistence across reconnects."
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### 2. List Your Uncertainties
|
|
24
|
+
What should Claude verify?
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
UNCERTAINTIES:
|
|
28
|
+
- "Is the backoff strategy correct for this use case?"
|
|
29
|
+
- "Could there be a race condition between reconnect and message send?"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 3. Ask Specific Questions
|
|
33
|
+
```
|
|
34
|
+
QUESTIONS:
|
|
35
|
+
- "Is the session token handling secure during reconnect?"
|
|
36
|
+
- "Should I handle partial message delivery differently?"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Tool Invocation
|
|
40
|
+
|
|
41
|
+
Call `claude_review` with:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"workingDir": "<current directory>",
|
|
46
|
+
"ccOutput": "<structured handoff - see below>",
|
|
47
|
+
"outputType": "analysis",
|
|
48
|
+
"focusAreas": ["<from $ARGUMENTS>"]
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Structure your ccOutput:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
SUMMARY:
|
|
56
|
+
<what you did, 1-3 sentences>
|
|
57
|
+
|
|
58
|
+
UNCERTAINTIES (verify these):
|
|
59
|
+
1. <your uncertainty>
|
|
60
|
+
2. <another uncertainty>
|
|
61
|
+
|
|
62
|
+
QUESTIONS:
|
|
63
|
+
1. <specific question>
|
|
64
|
+
|
|
65
|
+
KEY DECISIONS:
|
|
66
|
+
- <decision>: <rationale>
|
|
67
|
+
|
|
68
|
+
PRIORITY FILES:
|
|
69
|
+
- <file to focus on>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## After Receiving Review
|
|
73
|
+
|
|
74
|
+
1. **Verify file references exist**
|
|
75
|
+
- Check mentioned file:line locations
|
|
76
|
+
- Flag any that don't exist
|
|
77
|
+
|
|
78
|
+
2. **Cross-check findings**
|
|
79
|
+
- Read the actual code
|
|
80
|
+
- Confirm the issue exists
|
|
81
|
+
|
|
82
|
+
3. **Mark confidence:**
|
|
83
|
+
- ✓✓ Verified by you
|
|
84
|
+
- ✓ Plausible, not verified
|
|
85
|
+
- ? Needs investigation
|
|
86
|
+
- ✗ Rejected
|
|
87
|
+
|
|
88
|
+
4. **Apply judgment**
|
|
89
|
+
- You may disagree with findings
|
|
90
|
+
- Make YOUR recommendation
|
|
91
|
+
|
|
92
|
+
$ARGUMENTS
|
package/commands/multi-review.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# Multi Review
|
|
2
2
|
|
|
3
|
-
Get parallel reviews from
|
|
3
|
+
Get parallel reviews from Codex, Gemini, and a fresh Claude (Opus) instance, raw output for manual synthesis.
|
|
4
4
|
|
|
5
5
|
## Arguments
|
|
6
6
|
- `$ARGUMENTS` - Optional: focus area or custom instructions
|
|
7
7
|
|
|
8
8
|
## When to Use
|
|
9
9
|
|
|
10
|
-
Use `/multi-review` when you want parallel reviews from
|
|
10
|
+
Use `/multi-review` when you want parallel reviews from Codex, Gemini, and a fresh Claude (Opus) instance.
|
|
11
11
|
|
|
12
12
|
## Before Calling - PREPARE THE HANDOFF
|
|
13
13
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude CLI Adapter
|
|
3
|
+
*
|
|
4
|
+
* Implements the ReviewerAdapter interface for Anthropic's Claude CLI.
|
|
5
|
+
* Spawns a FRESH Claude Code instance with zero session context.
|
|
6
|
+
* Returns raw text — CC handles interpretation.
|
|
7
|
+
*
|
|
8
|
+
* Read-only enforcement (defense-in-depth):
|
|
9
|
+
* 1. --permission-mode plan (CLI-level read-only)
|
|
10
|
+
* 2. --disallowed-tools (write tools explicitly blocked)
|
|
11
|
+
* 3. Handoff prompt (explicit READ-ONLY instruction)
|
|
12
|
+
*/
|
|
13
|
+
import { ReviewerAdapter, ReviewerCapabilities, ReviewRequest, ReviewResult } from './base.js';
|
|
14
|
+
export declare class ClaudeAdapter implements ReviewerAdapter {
|
|
15
|
+
readonly id = "claude";
|
|
16
|
+
getCapabilities(): ReviewerCapabilities;
|
|
17
|
+
isAvailable(): Promise<boolean>;
|
|
18
|
+
runReview(request: ReviewRequest): Promise<ReviewResult>;
|
|
19
|
+
private runCli;
|
|
20
|
+
private handleException;
|
|
21
|
+
private categorizeError;
|
|
22
|
+
private getSuggestion;
|
|
23
|
+
}
|
|
24
|
+
export declare const claudeAdapter: ClaudeAdapter;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude CLI Adapter
|
|
3
|
+
*
|
|
4
|
+
* Implements the ReviewerAdapter interface for Anthropic's Claude CLI.
|
|
5
|
+
* Spawns a FRESH Claude Code instance with zero session context.
|
|
6
|
+
* Returns raw text — CC handles interpretation.
|
|
7
|
+
*
|
|
8
|
+
* Read-only enforcement (defense-in-depth):
|
|
9
|
+
* 1. --permission-mode plan (CLI-level read-only)
|
|
10
|
+
* 2. --disallowed-tools (write tools explicitly blocked)
|
|
11
|
+
* 3. Handoff prompt (explicit READ-ONLY instruction)
|
|
12
|
+
*/
|
|
13
|
+
import { spawn } from 'child_process';
|
|
14
|
+
import { existsSync } from 'fs';
|
|
15
|
+
import { registerAdapter, } from './base.js';
|
|
16
|
+
import { CliExecutor } from '../executor.js';
|
|
17
|
+
import { ClaudeEventDecoder } from '../decoders/index.js';
|
|
18
|
+
import { buildSimpleHandoff, buildHandoffPrompt, selectRole, } from '../handoff.js';
|
|
19
|
+
// =============================================================================
|
|
20
|
+
// CONFIGURATION
|
|
21
|
+
// =============================================================================
|
|
22
|
+
const INACTIVITY_TIMEOUT_MS = 300_000; // 5 min — Opus has long thinking phases
|
|
23
|
+
const MAX_TIMEOUT_MS = 3_600_000; // 60 min absolute max
|
|
24
|
+
const MAX_BUFFER_SIZE = 1024 * 1024; // 1MB max buffer
|
|
25
|
+
// Write tools explicitly blocked as defense-in-depth
|
|
26
|
+
const DISALLOWED_TOOLS = 'Edit Write NotebookEdit';
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// CLAUDE ADAPTER
|
|
29
|
+
// =============================================================================
|
|
30
|
+
export class ClaudeAdapter {
|
|
31
|
+
id = 'claude';
|
|
32
|
+
getCapabilities() {
|
|
33
|
+
return {
|
|
34
|
+
name: 'Claude',
|
|
35
|
+
description: 'Anthropic Claude (Opus) - fresh instance with clean context, excels at deep analysis across all dimensions',
|
|
36
|
+
strengths: ['correctness', 'security', 'architecture', 'maintainability'],
|
|
37
|
+
weaknesses: [],
|
|
38
|
+
hasFilesystemAccess: true,
|
|
39
|
+
supportsStructuredOutput: false,
|
|
40
|
+
maxContextTokens: 200000,
|
|
41
|
+
reasoningLevels: undefined,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
async isAvailable() {
|
|
45
|
+
return new Promise((resolve) => {
|
|
46
|
+
const proc = spawn('claude', ['--version'], {
|
|
47
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
48
|
+
});
|
|
49
|
+
proc.on('close', (code) => resolve(code === 0));
|
|
50
|
+
proc.on('error', () => resolve(false));
|
|
51
|
+
setTimeout(() => { proc.kill(); resolve(false); }, 5000);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async runReview(request) {
|
|
55
|
+
const startTime = Date.now();
|
|
56
|
+
if (!existsSync(request.workingDir)) {
|
|
57
|
+
return {
|
|
58
|
+
success: false,
|
|
59
|
+
error: { type: 'cli_error', message: `Working directory does not exist: ${request.workingDir}` },
|
|
60
|
+
suggestion: 'Check that the working directory path is correct',
|
|
61
|
+
executionTimeMs: Date.now() - startTime,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const handoff = buildSimpleHandoff(request.workingDir, request.ccOutput, request.analyzedFiles, request.focusAreas, request.customPrompt);
|
|
66
|
+
const role = selectRole(request.focusAreas);
|
|
67
|
+
const prompt = buildHandoffPrompt({ handoff, role });
|
|
68
|
+
const result = await this.runCli(prompt, request.workingDir);
|
|
69
|
+
if (result.exitCode !== 0) {
|
|
70
|
+
const error = this.categorizeError(result.stderr);
|
|
71
|
+
return { success: false, error, suggestion: this.getSuggestion(error), executionTimeMs: Date.now() - startTime };
|
|
72
|
+
}
|
|
73
|
+
if (!result.stdout.trim()) {
|
|
74
|
+
return {
|
|
75
|
+
success: false,
|
|
76
|
+
error: { type: 'cli_error', message: 'Claude returned empty response' },
|
|
77
|
+
suggestion: 'Try again or use /codex-review instead',
|
|
78
|
+
executionTimeMs: Date.now() - startTime,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return { success: true, output: result.stdout, executionTimeMs: Date.now() - startTime };
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
return this.handleException(error, startTime);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async runCli(prompt, workingDir) {
|
|
88
|
+
const args = [
|
|
89
|
+
'-p', // Non-interactive, print and exit
|
|
90
|
+
'--model', 'opus', // Use Opus
|
|
91
|
+
'--permission-mode', 'plan', // Read-only enforcement (layer 1)
|
|
92
|
+
'--verbose', // Required for stream-json
|
|
93
|
+
'--output-format', 'stream-json', // Structured streaming events
|
|
94
|
+
'--no-session-persistence', // Ephemeral — no trace
|
|
95
|
+
'--disable-slash-commands', // No skills — minimal startup
|
|
96
|
+
'--disallowed-tools', DISALLOWED_TOOLS, // Block write tools (layer 2)
|
|
97
|
+
'-', // Read prompt from stdin
|
|
98
|
+
];
|
|
99
|
+
const decoder = new ClaudeEventDecoder();
|
|
100
|
+
const cliStartTime = Date.now();
|
|
101
|
+
console.error('[claude] Running Opus review...');
|
|
102
|
+
decoder.onProgress = (eventType, detail) => {
|
|
103
|
+
const elapsed = Math.round((Date.now() - cliStartTime) / 1000);
|
|
104
|
+
const detailStr = detail ? ` — ${detail}` : '';
|
|
105
|
+
console.error(`[claude] ${eventType}${detailStr} (${elapsed}s)`);
|
|
106
|
+
};
|
|
107
|
+
const executor = new CliExecutor({
|
|
108
|
+
command: 'claude',
|
|
109
|
+
args,
|
|
110
|
+
cwd: workingDir,
|
|
111
|
+
stdin: prompt,
|
|
112
|
+
inactivityTimeoutMs: INACTIVITY_TIMEOUT_MS,
|
|
113
|
+
maxTimeoutMs: MAX_TIMEOUT_MS,
|
|
114
|
+
maxBufferSize: MAX_BUFFER_SIZE,
|
|
115
|
+
onLine: (line) => {
|
|
116
|
+
decoder.processLine(line);
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
const result = await executor.run();
|
|
120
|
+
const elapsed = Math.round((Date.now() - cliStartTime) / 1000);
|
|
121
|
+
console.error(`[claude] ✓ complete (${elapsed}s)`);
|
|
122
|
+
// Check for errors captured from stream events
|
|
123
|
+
const decoderError = decoder.getError();
|
|
124
|
+
if (decoderError) {
|
|
125
|
+
return { stdout: '', stderr: decoderError, exitCode: 1, truncated: false };
|
|
126
|
+
}
|
|
127
|
+
const finalResponse = decoder.getFinalResponse();
|
|
128
|
+
if (!finalResponse && decoder.hasNoOutput()) {
|
|
129
|
+
return { stdout: '', stderr: 'No response from Claude — possible rate limit or auth issue', exitCode: 1, truncated: false };
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
stdout: finalResponse || result.rawStdout,
|
|
133
|
+
stderr: result.stderr,
|
|
134
|
+
exitCode: result.exitCode,
|
|
135
|
+
truncated: result.truncated,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
handleException(error, startTime) {
|
|
139
|
+
const err = error;
|
|
140
|
+
if (err.code === 'ENOENT') {
|
|
141
|
+
return { success: false, error: { type: 'cli_not_found', message: 'Claude CLI not found' },
|
|
142
|
+
suggestion: 'Install Claude Code: https://docs.anthropic.com/en/docs/claude-code', executionTimeMs: Date.now() - startTime };
|
|
143
|
+
}
|
|
144
|
+
if (err.message === 'TIMEOUT') {
|
|
145
|
+
return { success: false, error: { type: 'timeout', message: 'Claude timed out — no events received' },
|
|
146
|
+
suggestion: 'Try a smaller scope or use /codex-review', executionTimeMs: Date.now() - startTime };
|
|
147
|
+
}
|
|
148
|
+
if (err.message === 'MAX_TIMEOUT') {
|
|
149
|
+
return { success: false, error: { type: 'timeout', message: 'Task exceeded 60 minute maximum' },
|
|
150
|
+
suggestion: 'Try a smaller scope', executionTimeMs: Date.now() - startTime };
|
|
151
|
+
}
|
|
152
|
+
return { success: false, error: { type: 'cli_error', message: err.message }, executionTimeMs: Date.now() - startTime };
|
|
153
|
+
}
|
|
154
|
+
categorizeError(stderr) {
|
|
155
|
+
const lower = stderr.toLowerCase();
|
|
156
|
+
if (lower.includes('rate limit') || lower.includes('quota') || lower.includes('no response from claude')) {
|
|
157
|
+
return { type: 'rate_limit', message: 'Claude rate limit — try again later' };
|
|
158
|
+
}
|
|
159
|
+
if (lower.includes('unauthorized') || lower.includes('authentication') || lower.includes('api key') || stderr.includes('401') || stderr.includes('403')) {
|
|
160
|
+
return { type: 'auth_error', message: 'Authentication failed', details: { stderr } };
|
|
161
|
+
}
|
|
162
|
+
return { type: 'cli_error', message: stderr || 'Unknown error' };
|
|
163
|
+
}
|
|
164
|
+
getSuggestion(error) {
|
|
165
|
+
switch (error.type) {
|
|
166
|
+
case 'rate_limit': return 'Wait and retry, or use /codex-review or /gemini-review instead';
|
|
167
|
+
case 'auth_error': return 'Run `claude auth` to authenticate';
|
|
168
|
+
case 'cli_not_found': return 'Install Claude Code: https://docs.anthropic.com/en/docs/claude-code';
|
|
169
|
+
default: return 'Check the error message and try again';
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Register the adapter
|
|
174
|
+
registerAdapter(new ClaudeAdapter());
|
|
175
|
+
export const claudeAdapter = new ClaudeAdapter();
|
package/dist/adapters/codex.js
CHANGED
|
@@ -87,7 +87,8 @@ export class CodexAdapter {
|
|
|
87
87
|
'-m', 'gpt-5.4',
|
|
88
88
|
'-c', `model_reasoning_effort=${reasoningEffort}`,
|
|
89
89
|
'-c', 'model_reasoning_summary_format=experimental',
|
|
90
|
-
'--
|
|
90
|
+
'--full-auto',
|
|
91
|
+
'--sandbox', 'read-only',
|
|
91
92
|
'--skip-git-repo-check',
|
|
92
93
|
'-C', workingDir,
|
|
93
94
|
'-', // Read prompt from stdin
|
package/dist/adapters/gemini.js
CHANGED
package/dist/adapters/index.d.ts
CHANGED
package/dist/adapters/index.js
CHANGED
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
// Import adapters to register them
|
|
7
7
|
import './codex.js';
|
|
8
8
|
import './gemini.js';
|
|
9
|
+
import './claude.js';
|
|
9
10
|
// Re-export everything from base
|
|
10
11
|
export * from './base.js';
|
|
11
12
|
// Export specific adapters
|
|
12
13
|
export { codexAdapter } from './codex.js';
|
|
13
14
|
export { geminiAdapter } from './gemini.js';
|
|
15
|
+
export { claudeAdapter } from './claude.js';
|
package/dist/cli/check.js
CHANGED
|
@@ -22,11 +22,12 @@ async function commandExists(command) {
|
|
|
22
22
|
* Check availability of all supported CLIs
|
|
23
23
|
*/
|
|
24
24
|
export async function checkCliAvailability() {
|
|
25
|
-
const [codex, gemini] = await Promise.all([
|
|
25
|
+
const [codex, gemini, claude] = await Promise.all([
|
|
26
26
|
commandExists('codex'),
|
|
27
|
-
commandExists('gemini')
|
|
27
|
+
commandExists('gemini'),
|
|
28
|
+
commandExists('claude')
|
|
28
29
|
]);
|
|
29
|
-
return { codex, gemini };
|
|
30
|
+
return { codex, gemini, claude };
|
|
30
31
|
}
|
|
31
32
|
/**
|
|
32
33
|
* Check if a specific CLI is available
|
|
@@ -67,9 +68,11 @@ export async function logCliStatus() {
|
|
|
67
68
|
console.error('AI Reviewer CLI Status:');
|
|
68
69
|
console.error(` - Codex: ${status.codex ? '✓ Available' : '✗ Not found'}`);
|
|
69
70
|
console.error(` - Gemini: ${status.gemini ? '✓ Available' : '✗ Not found'}`);
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
console.error(` - Claude: ${status.claude ? '✓ Available' : '✗ Not found'}`);
|
|
72
|
+
if (!status.codex && !status.gemini && !status.claude) {
|
|
73
|
+
console.error('\nWarning: No AI CLIs found. Install at least one:');
|
|
72
74
|
console.error(' npm install -g @openai/codex-cli');
|
|
73
75
|
console.error(' npm install -g @google/gemini-cli');
|
|
76
|
+
console.error(' Claude Code: https://docs.anthropic.com/en/docs/claude-code');
|
|
74
77
|
}
|
|
75
78
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClaudeEventDecoder — Parses Claude CLI stream-json JSONL events.
|
|
3
|
+
*
|
|
4
|
+
* Event stream format (with --output-format stream-json --verbose):
|
|
5
|
+
* {"type":"system","subtype":"init",...}
|
|
6
|
+
* {"type":"assistant","message":{"content":[{"type":"text","text":"..."}],...},...}
|
|
7
|
+
* {"type":"result","subtype":"success","result":"...","duration_ms":...,"usage":{...}}
|
|
8
|
+
*/
|
|
9
|
+
export interface ClaudeEvent {
|
|
10
|
+
type: string;
|
|
11
|
+
subtype?: string;
|
|
12
|
+
session_id?: string;
|
|
13
|
+
model?: string;
|
|
14
|
+
result?: string;
|
|
15
|
+
is_error?: boolean;
|
|
16
|
+
duration_ms?: number;
|
|
17
|
+
message?: {
|
|
18
|
+
content?: Array<{
|
|
19
|
+
type: string;
|
|
20
|
+
text?: string;
|
|
21
|
+
}>;
|
|
22
|
+
usage?: {
|
|
23
|
+
input_tokens: number;
|
|
24
|
+
output_tokens: number;
|
|
25
|
+
cache_read_input_tokens?: number;
|
|
26
|
+
cache_creation_input_tokens?: number;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
usage?: {
|
|
30
|
+
input_tokens: number;
|
|
31
|
+
output_tokens: number;
|
|
32
|
+
cache_read_input_tokens?: number;
|
|
33
|
+
cache_creation_input_tokens?: number;
|
|
34
|
+
};
|
|
35
|
+
tool_use_id?: string;
|
|
36
|
+
tool_name?: string;
|
|
37
|
+
}
|
|
38
|
+
export declare class ClaudeEventDecoder {
|
|
39
|
+
onProgress?: (eventType: string, detail?: string) => void;
|
|
40
|
+
private _finalResponse;
|
|
41
|
+
private _usage;
|
|
42
|
+
private _error;
|
|
43
|
+
private _eventCount;
|
|
44
|
+
private _durationMs;
|
|
45
|
+
processLine(line: string): void;
|
|
46
|
+
getFinalResponse(): string | null;
|
|
47
|
+
getUsage(): ClaudeEvent['usage'] | null;
|
|
48
|
+
getError(): string | null;
|
|
49
|
+
getDurationMs(): number | null;
|
|
50
|
+
hasNoOutput(): boolean;
|
|
51
|
+
private _handleEvent;
|
|
52
|
+
private _describeEvent;
|
|
53
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClaudeEventDecoder — Parses Claude CLI stream-json JSONL events.
|
|
3
|
+
*
|
|
4
|
+
* Event stream format (with --output-format stream-json --verbose):
|
|
5
|
+
* {"type":"system","subtype":"init",...}
|
|
6
|
+
* {"type":"assistant","message":{"content":[{"type":"text","text":"..."}],...},...}
|
|
7
|
+
* {"type":"result","subtype":"success","result":"...","duration_ms":...,"usage":{...}}
|
|
8
|
+
*/
|
|
9
|
+
// =============================================================================
|
|
10
|
+
// DECODER
|
|
11
|
+
// =============================================================================
|
|
12
|
+
export class ClaudeEventDecoder {
|
|
13
|
+
onProgress;
|
|
14
|
+
_finalResponse = null;
|
|
15
|
+
_usage = null;
|
|
16
|
+
_error = null;
|
|
17
|
+
_eventCount = 0;
|
|
18
|
+
_durationMs = null;
|
|
19
|
+
// =============================================================================
|
|
20
|
+
// PUBLIC API
|
|
21
|
+
// =============================================================================
|
|
22
|
+
processLine(line) {
|
|
23
|
+
const trimmed = line.trim();
|
|
24
|
+
if (trimmed.length === 0)
|
|
25
|
+
return;
|
|
26
|
+
let event;
|
|
27
|
+
try {
|
|
28
|
+
const parsed = JSON.parse(trimmed);
|
|
29
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
|
|
30
|
+
return;
|
|
31
|
+
event = parsed;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (!event.type)
|
|
37
|
+
return;
|
|
38
|
+
this._handleEvent(event);
|
|
39
|
+
}
|
|
40
|
+
getFinalResponse() {
|
|
41
|
+
return this._finalResponse;
|
|
42
|
+
}
|
|
43
|
+
getUsage() {
|
|
44
|
+
return this._usage;
|
|
45
|
+
}
|
|
46
|
+
getError() {
|
|
47
|
+
return this._error;
|
|
48
|
+
}
|
|
49
|
+
getDurationMs() {
|
|
50
|
+
return this._durationMs;
|
|
51
|
+
}
|
|
52
|
+
hasNoOutput() {
|
|
53
|
+
return this._eventCount > 0 && this._finalResponse === null;
|
|
54
|
+
}
|
|
55
|
+
// =============================================================================
|
|
56
|
+
// PRIVATE HELPERS
|
|
57
|
+
// =============================================================================
|
|
58
|
+
_handleEvent(event) {
|
|
59
|
+
this._eventCount++;
|
|
60
|
+
switch (event.type) {
|
|
61
|
+
case 'result':
|
|
62
|
+
// The result event contains the final text response
|
|
63
|
+
if (event.subtype === 'success' && typeof event.result === 'string') {
|
|
64
|
+
this._finalResponse = event.result;
|
|
65
|
+
}
|
|
66
|
+
if (event.is_error) {
|
|
67
|
+
this._error = event.result || 'Claude review failed';
|
|
68
|
+
}
|
|
69
|
+
if (event.usage) {
|
|
70
|
+
this._usage = event.usage;
|
|
71
|
+
}
|
|
72
|
+
if (event.duration_ms != null) {
|
|
73
|
+
this._durationMs = event.duration_ms;
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
case 'assistant':
|
|
77
|
+
// Track usage from assistant messages
|
|
78
|
+
if (event.message?.usage) {
|
|
79
|
+
this._usage = event.message.usage;
|
|
80
|
+
}
|
|
81
|
+
break;
|
|
82
|
+
case 'error':
|
|
83
|
+
this._error = event.result || 'Unknown error from Claude CLI';
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
this.onProgress?.(event.type, this._describeEvent(event));
|
|
87
|
+
}
|
|
88
|
+
_describeEvent(event) {
|
|
89
|
+
switch (event.type) {
|
|
90
|
+
case 'system':
|
|
91
|
+
if (event.subtype === 'init')
|
|
92
|
+
return `model: ${event.model || 'opus'}`;
|
|
93
|
+
if (event.subtype)
|
|
94
|
+
return event.subtype;
|
|
95
|
+
return undefined;
|
|
96
|
+
case 'assistant':
|
|
97
|
+
return 'assistant message';
|
|
98
|
+
case 'tool_use':
|
|
99
|
+
return event.tool_name ? `tool: ${event.tool_name}` : 'tool use';
|
|
100
|
+
case 'result':
|
|
101
|
+
return `status: ${event.subtype || 'unknown'}`;
|
|
102
|
+
default:
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
package/dist/decoders/index.d.ts
CHANGED
|
@@ -2,3 +2,5 @@ export { CodexEventDecoder } from './codex.js';
|
|
|
2
2
|
export type { CodexEvent } from './codex.js';
|
|
3
3
|
export { GeminiEventDecoder } from './gemini.js';
|
|
4
4
|
export type { GeminiEvent } from './gemini.js';
|
|
5
|
+
export { ClaudeEventDecoder } from './claude.js';
|
|
6
|
+
export type { ClaudeEvent } from './claude.js';
|
package/dist/decoders/index.js
CHANGED
package/dist/errors.js
CHANGED
|
@@ -6,17 +6,20 @@
|
|
|
6
6
|
// Gemini: https://github.com/google-gemini/gemini-cli
|
|
7
7
|
const INSTALL_COMMANDS = {
|
|
8
8
|
codex: 'npm install -g @openai/codex-cli',
|
|
9
|
-
gemini: 'npm install -g @google/gemini-cli'
|
|
9
|
+
gemini: 'npm install -g @google/gemini-cli',
|
|
10
|
+
claude: 'https://docs.anthropic.com/en/docs/claude-code'
|
|
10
11
|
};
|
|
11
12
|
// Environment variables for API keys
|
|
12
13
|
const ENV_VARS = {
|
|
13
14
|
codex: 'OPENAI_API_KEY',
|
|
14
|
-
gemini: 'GEMINI_API_KEY'
|
|
15
|
+
gemini: 'GEMINI_API_KEY',
|
|
16
|
+
claude: 'ANTHROPIC_API_KEY'
|
|
15
17
|
};
|
|
16
18
|
// Authentication commands
|
|
17
19
|
const AUTH_COMMANDS = {
|
|
18
20
|
codex: 'codex login',
|
|
19
|
-
gemini: 'gemini (follow prompts)'
|
|
21
|
+
gemini: 'gemini (follow prompts)',
|
|
22
|
+
claude: 'claude auth'
|
|
20
23
|
};
|
|
21
24
|
/**
|
|
22
25
|
* Create a CLI not found error
|
|
@@ -83,7 +86,8 @@ export function createCliError(cli, exitCode, stderr) {
|
|
|
83
86
|
* Format an error for user display
|
|
84
87
|
*/
|
|
85
88
|
export function formatErrorForUser(error) {
|
|
86
|
-
const
|
|
89
|
+
const others = ['codex', 'gemini', 'claude'].filter(c => c !== error.cli);
|
|
90
|
+
const otherCli = others[0];
|
|
87
91
|
switch (error.type) {
|
|
88
92
|
case 'cli_not_found':
|
|
89
93
|
return `❌ ${error.cli} CLI not found.
|
package/dist/handoff.js
CHANGED
|
@@ -168,7 +168,9 @@ export function buildHandoffPrompt(options) {
|
|
|
168
168
|
|
|
169
169
|
Review recent work in \`${handoff.workingDir}\`.
|
|
170
170
|
|
|
171
|
-
**Summary:** ${handoff.summary}${handoff.confidence !== undefined && handoff.confidence < 0.9 ? `\n**CC Confidence:** ${Math.round(handoff.confidence * 100)}% — verify weak areas` : ''}
|
|
171
|
+
**Summary:** ${handoff.summary}${handoff.confidence !== undefined && handoff.confidence < 0.9 ? `\n**CC Confidence:** ${Math.round(handoff.confidence * 100)}% — verify weak areas` : ''}
|
|
172
|
+
|
|
173
|
+
**IMPORTANT: This is a READ-ONLY review. Do NOT create, modify, or delete any files. Only read files to verify claims.**`);
|
|
172
174
|
// SECTION 3: CC'S UNCERTAINTIES
|
|
173
175
|
if (handoff.uncertainties && handoff.uncertainties.length > 0) {
|
|
174
176
|
sections.push(`## CC'S UNCERTAINTIES
|
package/dist/index.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
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
|
-
import { handleCodexReview, handleGeminiReview, handleMultiReview, ReviewInputSchema, TOOL_DEFINITIONS } from './tools/feedback.js';
|
|
21
|
+
import { handleCodexReview, handleGeminiReview, handleClaudeReview, handleMultiReview, ReviewInputSchema, TOOL_DEFINITIONS } from './tools/feedback.js';
|
|
22
22
|
import { logCliStatus } from './cli/check.js';
|
|
23
23
|
import { installCommands } from './commands.js';
|
|
24
24
|
// Read version from package.json
|
|
@@ -62,6 +62,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
62
62
|
tools: [
|
|
63
63
|
TOOL_DEFINITIONS.codex_review,
|
|
64
64
|
TOOL_DEFINITIONS.gemini_review,
|
|
65
|
+
TOOL_DEFINITIONS.claude_review,
|
|
65
66
|
TOOL_DEFINITIONS.multi_review,
|
|
66
67
|
],
|
|
67
68
|
};
|
|
@@ -79,6 +80,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
79
80
|
const input = ReviewInputSchema.parse(args);
|
|
80
81
|
return await handleGeminiReview(input);
|
|
81
82
|
}
|
|
83
|
+
case 'claude_review': {
|
|
84
|
+
const input = ReviewInputSchema.parse(args);
|
|
85
|
+
return await handleClaudeReview(input);
|
|
86
|
+
}
|
|
82
87
|
case 'multi_review': {
|
|
83
88
|
const input = ReviewInputSchema.parse(args);
|
|
84
89
|
return await handleMultiReview(input);
|
package/dist/tools/feedback.d.ts
CHANGED
|
@@ -46,6 +46,12 @@ export declare function handleGeminiReview(input: ReviewInput): Promise<{
|
|
|
46
46
|
text: string;
|
|
47
47
|
}>;
|
|
48
48
|
}>;
|
|
49
|
+
export declare function handleClaudeReview(input: ReviewInput): Promise<{
|
|
50
|
+
content: Array<{
|
|
51
|
+
type: 'text';
|
|
52
|
+
text: string;
|
|
53
|
+
}>;
|
|
54
|
+
}>;
|
|
49
55
|
export declare function handleMultiReview(input: ReviewInput): Promise<{
|
|
50
56
|
content: Array<{
|
|
51
57
|
type: 'text';
|
|
@@ -147,6 +153,48 @@ export declare const TOOL_DEFINITIONS: {
|
|
|
147
153
|
required: string[];
|
|
148
154
|
};
|
|
149
155
|
};
|
|
156
|
+
claude_review: {
|
|
157
|
+
name: string;
|
|
158
|
+
description: string;
|
|
159
|
+
inputSchema: {
|
|
160
|
+
type: string;
|
|
161
|
+
properties: {
|
|
162
|
+
workingDir: {
|
|
163
|
+
type: string;
|
|
164
|
+
description: string;
|
|
165
|
+
};
|
|
166
|
+
ccOutput: {
|
|
167
|
+
type: string;
|
|
168
|
+
description: string;
|
|
169
|
+
};
|
|
170
|
+
outputType: {
|
|
171
|
+
type: string;
|
|
172
|
+
enum: string[];
|
|
173
|
+
description: string;
|
|
174
|
+
};
|
|
175
|
+
analyzedFiles: {
|
|
176
|
+
type: string;
|
|
177
|
+
items: {
|
|
178
|
+
type: string;
|
|
179
|
+
};
|
|
180
|
+
description: string;
|
|
181
|
+
};
|
|
182
|
+
focusAreas: {
|
|
183
|
+
type: string;
|
|
184
|
+
items: {
|
|
185
|
+
type: string;
|
|
186
|
+
enum: string[];
|
|
187
|
+
};
|
|
188
|
+
description: string;
|
|
189
|
+
};
|
|
190
|
+
customPrompt: {
|
|
191
|
+
type: string;
|
|
192
|
+
description: string;
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
required: string[];
|
|
196
|
+
};
|
|
197
|
+
};
|
|
150
198
|
multi_review: {
|
|
151
199
|
name: string;
|
|
152
200
|
description: string;
|
package/dist/tools/feedback.js
CHANGED
|
@@ -73,6 +73,16 @@ export async function handleGeminiReview(input) {
|
|
|
73
73
|
const result = await adapter.runReview(toReviewRequest(input));
|
|
74
74
|
return { content: [{ type: 'text', text: formatResult(result, 'Gemini') }] };
|
|
75
75
|
}
|
|
76
|
+
export async function handleClaudeReview(input) {
|
|
77
|
+
const adapter = getAdapter('claude');
|
|
78
|
+
if (!adapter)
|
|
79
|
+
return { content: [{ type: 'text', text: '❌ Claude adapter not registered' }] };
|
|
80
|
+
const available = await adapter.isAvailable();
|
|
81
|
+
if (!available)
|
|
82
|
+
return { content: [{ type: 'text', text: '❌ Claude CLI not found.\n\nInstall Claude Code: https://docs.anthropic.com/en/docs/claude-code\n\nAlternative: Use codex_review or gemini_review instead' }] };
|
|
83
|
+
const result = await adapter.runReview(toReviewRequest(input));
|
|
84
|
+
return { content: [{ type: 'text', text: formatResult(result, 'Claude (Opus)') }] };
|
|
85
|
+
}
|
|
76
86
|
// =============================================================================
|
|
77
87
|
// MULTI-MODEL HANDLER
|
|
78
88
|
// =============================================================================
|
|
@@ -140,9 +150,25 @@ export const TOOL_DEFINITIONS = {
|
|
|
140
150
|
required: ['workingDir', 'ccOutput', 'outputType']
|
|
141
151
|
}
|
|
142
152
|
},
|
|
153
|
+
claude_review: {
|
|
154
|
+
name: 'claude_review',
|
|
155
|
+
description: "ONLY use when user explicitly requests '/claude-review' or 'review with claude'. Get second-opinion from a fresh Claude (Opus) instance with clean context — no memory of this session. Excels at deep analysis across all dimensions. DO NOT use for general 'review' requests.",
|
|
156
|
+
inputSchema: {
|
|
157
|
+
type: 'object',
|
|
158
|
+
properties: {
|
|
159
|
+
workingDir: { type: 'string', description: 'Working directory for the CLI to operate in' },
|
|
160
|
+
ccOutput: { type: 'string', description: "Claude Code's output to review (findings, plan, analysis)" },
|
|
161
|
+
outputType: { type: 'string', enum: ['plan', 'findings', 'analysis', 'proposal'], description: 'Type of output being reviewed' },
|
|
162
|
+
analyzedFiles: { type: 'array', items: { type: 'string' }, description: 'File paths that CC analyzed' },
|
|
163
|
+
focusAreas: { type: 'array', items: { type: 'string', enum: ['security', 'performance', 'architecture', 'correctness', 'maintainability', 'scalability', 'testing', 'documentation'] }, description: 'Areas to focus the review on' },
|
|
164
|
+
customPrompt: { type: 'string', description: 'Custom instructions for the reviewer' },
|
|
165
|
+
},
|
|
166
|
+
required: ['workingDir', 'ccOutput', 'outputType']
|
|
167
|
+
}
|
|
168
|
+
},
|
|
143
169
|
multi_review: {
|
|
144
170
|
name: 'multi_review',
|
|
145
|
-
description: "ONLY use when user explicitly requests '/multi-review' or 'review with
|
|
171
|
+
description: "ONLY use when user explicitly requests '/multi-review' or 'review with all models'. Get parallel second-opinions from Codex, Gemini, and a fresh Claude (Opus) instance. Returns combined reviews for synthesis. DO NOT use for general 'review' requests.",
|
|
146
172
|
inputSchema: {
|
|
147
173
|
type: 'object',
|
|
148
174
|
properties: {
|
package/dist/types.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export type OutputType = 'plan' | 'findings' | 'analysis' | 'proposal';
|
|
5
5
|
export type FocusArea = 'security' | 'performance' | 'architecture' | 'correctness' | 'maintainability' | 'scalability' | 'testing' | 'documentation';
|
|
6
|
-
export type CliType = 'codex' | 'gemini';
|
|
6
|
+
export type CliType = 'codex' | 'gemini' | 'claude';
|
|
7
7
|
export type ReasoningEffort = 'high' | 'xhigh';
|
|
8
8
|
export type ServiceTier = 'default' | 'fast' | 'flex';
|
|
9
9
|
export interface FeedbackRequest {
|
|
@@ -69,6 +69,7 @@ export interface MultiFeedbackResult {
|
|
|
69
69
|
export interface CliStatus {
|
|
70
70
|
codex: boolean;
|
|
71
71
|
gemini: boolean;
|
|
72
|
+
claude: boolean;
|
|
72
73
|
}
|
|
73
74
|
export interface StructuredFeedback {
|
|
74
75
|
agreements: Array<{
|
package/dist/types.js
CHANGED
|
@@ -11,6 +11,11 @@ export const REVIEWER_PERSONAS = {
|
|
|
11
11
|
name: 'Gemini',
|
|
12
12
|
focus: 'design patterns, scalability, tech debt',
|
|
13
13
|
style: 'Think holistically - consider broader context.'
|
|
14
|
+
},
|
|
15
|
+
claude: {
|
|
16
|
+
name: 'Claude',
|
|
17
|
+
focus: 'deep analysis, correctness, security, architecture',
|
|
18
|
+
style: 'Fresh perspective with clean context - challenge assumptions.'
|
|
14
19
|
}
|
|
15
20
|
};
|
|
16
21
|
// Focus area descriptions
|