cc-reviewer 3.0.1 → 5.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 +28 -16
- package/dist/adapters/base.d.ts +2 -0
- package/dist/adapters/claude.d.ts +24 -0
- package/dist/adapters/claude.js +183 -0
- package/dist/adapters/codex.js +18 -11
- package/dist/adapters/gemini.js +11 -7
- package/dist/adapters/index.d.ts +2 -0
- package/dist/adapters/index.js +2 -0
- package/dist/cli/check.js +8 -5
- package/dist/commands.js +1 -0
- 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.d.ts +10 -2
- package/dist/handoff.js +127 -5
- package/dist/index.js +6 -1
- package/dist/prompt.js +1 -0
- package/dist/tools/feedback.d.ts +48 -0
- package/dist/tools/feedback.js +49 -9
- 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,25 @@
|
|
|
1
1
|
# Multi Review
|
|
2
2
|
|
|
3
|
-
Get parallel reviews from
|
|
3
|
+
Get parallel standard AND adversarial reviews from all available models (Codex, Gemini, Claude Opus).
|
|
4
|
+
|
|
5
|
+
Each model runs twice: once as a standard reviewer (finding bugs, issues, improvements) and once as an adversarial challenger (breaking confidence in the change, questioning assumptions, targeting hidden failure paths). Results are presented in two sections.
|
|
6
|
+
|
|
7
|
+
Use `$ARGUMENTS` to steer the adversarial focus (e.g., "focus the challenge on race conditions and rollback safety").
|
|
4
8
|
|
|
5
9
|
## Arguments
|
|
6
|
-
- `$ARGUMENTS` - Optional: focus area or
|
|
10
|
+
- `$ARGUMENTS` - Optional: focus area, custom instructions, or adversarial steering
|
|
7
11
|
|
|
8
12
|
## When to Use
|
|
9
13
|
|
|
10
|
-
Use `/multi-review` when you want parallel reviews from both
|
|
14
|
+
Use `/multi-review` when you want thorough parallel reviews from all available models. Every invocation includes both standard and adversarial passes.
|
|
15
|
+
|
|
16
|
+
## Examples
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
/multi-review
|
|
20
|
+
/multi-review focus the challenge on race conditions and rollback safety
|
|
21
|
+
/multi-review challenge whether this was the right caching and retry design
|
|
22
|
+
```
|
|
11
23
|
|
|
12
24
|
## Before Calling - PREPARE THE HANDOFF
|
|
13
25
|
|
|
@@ -22,7 +34,6 @@ Added cache invalidation on product updates."
|
|
|
22
34
|
UNCERTAINTIES:
|
|
23
35
|
- "Is the cache TTL appropriate for this data?"
|
|
24
36
|
- "Does the invalidation handle all update scenarios?"
|
|
25
|
-
- "Is the Redis connection pooling configured correctly?"
|
|
26
37
|
```
|
|
27
38
|
|
|
28
39
|
### 3. Ask Specific Questions
|
|
@@ -42,7 +53,7 @@ Call `multi_review` with:
|
|
|
42
53
|
"ccOutput": "<structured handoff>",
|
|
43
54
|
"outputType": "analysis",
|
|
44
55
|
"focusAreas": ["<from $ARGUMENTS>"],
|
|
45
|
-
"
|
|
56
|
+
"customPrompt": "<steering text from $ARGUMENTS for adversarial focus>"
|
|
46
57
|
}
|
|
47
58
|
```
|
|
48
59
|
|
|
@@ -70,21 +81,22 @@ PRIORITY FILES:
|
|
|
70
81
|
|
|
71
82
|
## After Receiving Review
|
|
72
83
|
|
|
73
|
-
You will receive
|
|
84
|
+
You will receive two sections: **Standard Review Findings** and **Challenge Review Findings**.
|
|
74
85
|
|
|
75
|
-
### Synthesize
|
|
86
|
+
### Synthesize
|
|
76
87
|
|
|
77
|
-
1. **
|
|
78
|
-
-
|
|
79
|
-
-
|
|
88
|
+
1. **Standard findings** — bugs, issues, improvements from each model
|
|
89
|
+
- Find agreements across models (higher confidence)
|
|
90
|
+
- Identify conflicts (YOU decide who's right)
|
|
80
91
|
|
|
81
|
-
2. **
|
|
82
|
-
-
|
|
83
|
-
-
|
|
92
|
+
2. **Challenge findings** — adversarial challenges from each model
|
|
93
|
+
- These target assumptions and design decisions, not just bugs
|
|
94
|
+
- Evaluate on merit — some challenges are speculative by design
|
|
95
|
+
- Strong challenges with evidence deserve serious consideration
|
|
84
96
|
|
|
85
|
-
3. **
|
|
86
|
-
-
|
|
87
|
-
-
|
|
97
|
+
3. **Cross-reference** standard vs challenge findings
|
|
98
|
+
- Standard + challenge agreement = high confidence issue
|
|
99
|
+
- Challenge-only finding = investigate further before acting
|
|
88
100
|
|
|
89
101
|
4. **Verify all findings**
|
|
90
102
|
- Check file/line references exist
|
package/dist/adapters/base.d.ts
CHANGED
|
@@ -41,6 +41,8 @@ export interface ReviewRequest {
|
|
|
41
41
|
reasoningEffort?: ReasoningEffort;
|
|
42
42
|
/** Service tier (for models that support it: priority = fast, flex = cheap) */
|
|
43
43
|
serviceTier?: ServiceTier;
|
|
44
|
+
/** Review mode: standard finds bugs, adversarial challenges assumptions */
|
|
45
|
+
reviewMode?: 'standard' | 'adversarial';
|
|
44
46
|
}
|
|
45
47
|
/** @deprecated Use handoff.ts roles instead */
|
|
46
48
|
export interface ExpertRole {
|
|
@@ -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,183 @@
|
|
|
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, buildAdversarialHandoffPrompt, 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 prompt = request.reviewMode === 'adversarial'
|
|
67
|
+
? buildAdversarialHandoffPrompt({ handoff })
|
|
68
|
+
: buildHandoffPrompt({ handoff, role: selectRole(request.focusAreas) });
|
|
69
|
+
const result = await this.runCli(prompt, request.workingDir);
|
|
70
|
+
if (result.exitCode !== 0) {
|
|
71
|
+
const error = this.categorizeError(result.stderr);
|
|
72
|
+
return { success: false, error, suggestion: this.getSuggestion(error), executionTimeMs: Date.now() - startTime };
|
|
73
|
+
}
|
|
74
|
+
if (!result.stdout.trim()) {
|
|
75
|
+
return {
|
|
76
|
+
success: false,
|
|
77
|
+
error: { type: 'cli_error', message: 'Claude returned empty response' },
|
|
78
|
+
suggestion: 'Try again or use /codex-review instead',
|
|
79
|
+
executionTimeMs: Date.now() - startTime,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return { success: true, output: result.stdout, executionTimeMs: Date.now() - startTime };
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
return this.handleException(error, startTime);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async runCli(prompt, workingDir) {
|
|
89
|
+
const args = [
|
|
90
|
+
'-p', // Non-interactive, print and exit
|
|
91
|
+
'--model', 'opus', // Use Opus
|
|
92
|
+
'--bare', // Skip hooks, plugins, CLAUDE.md, auto-memory
|
|
93
|
+
'--permission-mode', 'plan', // Read-only enforcement (layer 1)
|
|
94
|
+
'--verbose', // Required for stream-json
|
|
95
|
+
'--output-format', 'stream-json', // Structured streaming events
|
|
96
|
+
'--no-session-persistence', // Ephemeral — no trace
|
|
97
|
+
'--disable-slash-commands', // No skills — minimal startup
|
|
98
|
+
'--disallowed-tools', DISALLOWED_TOOLS, // Block write tools (layer 2)
|
|
99
|
+
'-', // Read prompt from stdin
|
|
100
|
+
];
|
|
101
|
+
const decoder = new ClaudeEventDecoder();
|
|
102
|
+
const cliStartTime = Date.now();
|
|
103
|
+
console.error('[claude] Running Opus review...');
|
|
104
|
+
decoder.onProgress = (eventType, detail) => {
|
|
105
|
+
const elapsed = Math.round((Date.now() - cliStartTime) / 1000);
|
|
106
|
+
const detailStr = detail ? ` — ${detail}` : '';
|
|
107
|
+
console.error(`[claude] ${eventType}${detailStr} (${elapsed}s)`);
|
|
108
|
+
};
|
|
109
|
+
const executor = new CliExecutor({
|
|
110
|
+
command: 'claude',
|
|
111
|
+
args,
|
|
112
|
+
cwd: workingDir,
|
|
113
|
+
stdin: prompt,
|
|
114
|
+
inactivityTimeoutMs: INACTIVITY_TIMEOUT_MS,
|
|
115
|
+
maxTimeoutMs: MAX_TIMEOUT_MS,
|
|
116
|
+
maxBufferSize: MAX_BUFFER_SIZE,
|
|
117
|
+
onLine: (line) => {
|
|
118
|
+
decoder.processLine(line);
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
const result = await executor.run();
|
|
122
|
+
const elapsed = Math.round((Date.now() - cliStartTime) / 1000);
|
|
123
|
+
console.error(`[claude] ✓ complete (${elapsed}s)`);
|
|
124
|
+
// Check for errors captured from stream events
|
|
125
|
+
const decoderError = decoder.getError();
|
|
126
|
+
if (decoderError) {
|
|
127
|
+
const combined = result.stderr ? `${decoderError}\n\nCLI stderr: ${result.stderr}` : decoderError;
|
|
128
|
+
return { stdout: '', stderr: combined, exitCode: 1, truncated: false };
|
|
129
|
+
}
|
|
130
|
+
const finalResponse = decoder.getFinalResponse();
|
|
131
|
+
if (!finalResponse && decoder.hasNoOutput()) {
|
|
132
|
+
const combined = result.stderr ? `No output from Claude\n\nCLI stderr: ${result.stderr}` : 'No output from Claude';
|
|
133
|
+
return { stdout: '', stderr: combined, exitCode: 1, truncated: false };
|
|
134
|
+
}
|
|
135
|
+
if (!finalResponse) {
|
|
136
|
+
const combined = result.stderr ? `No result event from Claude\n\nCLI stderr: ${result.stderr}` : 'No result event from Claude';
|
|
137
|
+
return { stdout: '', stderr: combined, exitCode: 1, truncated: false };
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
stdout: finalResponse,
|
|
141
|
+
stderr: result.stderr,
|
|
142
|
+
exitCode: result.exitCode,
|
|
143
|
+
truncated: result.truncated,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
handleException(error, startTime) {
|
|
147
|
+
const err = error;
|
|
148
|
+
if (err.code === 'ENOENT') {
|
|
149
|
+
return { success: false, error: { type: 'cli_not_found', message: 'Claude CLI not found' },
|
|
150
|
+
suggestion: 'Install Claude Code: https://docs.anthropic.com/en/docs/claude-code', executionTimeMs: Date.now() - startTime };
|
|
151
|
+
}
|
|
152
|
+
if (err.message === 'TIMEOUT') {
|
|
153
|
+
return { success: false, error: { type: 'timeout', message: 'Claude timed out — no events received' },
|
|
154
|
+
suggestion: 'Try a smaller scope or use /codex-review', executionTimeMs: Date.now() - startTime };
|
|
155
|
+
}
|
|
156
|
+
if (err.message === 'MAX_TIMEOUT') {
|
|
157
|
+
return { success: false, error: { type: 'timeout', message: 'Task exceeded 60 minute maximum' },
|
|
158
|
+
suggestion: 'Try a smaller scope', executionTimeMs: Date.now() - startTime };
|
|
159
|
+
}
|
|
160
|
+
return { success: false, error: { type: 'cli_error', message: err.message }, executionTimeMs: Date.now() - startTime };
|
|
161
|
+
}
|
|
162
|
+
categorizeError(stderr) {
|
|
163
|
+
const lower = stderr.toLowerCase();
|
|
164
|
+
if (lower.includes('rate limit') || lower.includes('rate_limit') || lower.includes('quota')) {
|
|
165
|
+
return { type: 'rate_limit', message: `Claude rate limit: ${stderr.slice(0, 500)}` };
|
|
166
|
+
}
|
|
167
|
+
if (lower.includes('unauthorized') || lower.includes('authentication') || lower.includes('api key') || stderr.includes('401') || stderr.includes('403')) {
|
|
168
|
+
return { type: 'auth_error', message: `Authentication failed: ${stderr.slice(0, 500)}`, details: { stderr } };
|
|
169
|
+
}
|
|
170
|
+
return { type: 'cli_error', message: stderr.slice(0, 500) || 'Unknown error' };
|
|
171
|
+
}
|
|
172
|
+
getSuggestion(error) {
|
|
173
|
+
switch (error.type) {
|
|
174
|
+
case 'rate_limit': return 'Wait and retry, or use /codex-review or /gemini-review instead';
|
|
175
|
+
case 'auth_error': return 'Run `claude auth` to authenticate';
|
|
176
|
+
case 'cli_not_found': return 'Install Claude Code: https://docs.anthropic.com/en/docs/claude-code';
|
|
177
|
+
default: return 'Check the error message and try again';
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Register the adapter
|
|
182
|
+
registerAdapter(new ClaudeAdapter());
|
|
183
|
+
export const claudeAdapter = new ClaudeAdapter();
|
package/dist/adapters/codex.js
CHANGED
|
@@ -10,7 +10,7 @@ import { existsSync } from 'fs';
|
|
|
10
10
|
import { registerAdapter, } from './base.js';
|
|
11
11
|
import { CliExecutor } from '../executor.js';
|
|
12
12
|
import { CodexEventDecoder } from '../decoders/index.js';
|
|
13
|
-
import { buildSimpleHandoff, buildHandoffPrompt, selectRole, } from '../handoff.js';
|
|
13
|
+
import { buildSimpleHandoff, buildHandoffPrompt, buildAdversarialHandoffPrompt, selectRole, } from '../handoff.js';
|
|
14
14
|
// =============================================================================
|
|
15
15
|
// CONFIGURATION
|
|
16
16
|
// =============================================================================
|
|
@@ -59,8 +59,9 @@ export class CodexAdapter {
|
|
|
59
59
|
}
|
|
60
60
|
try {
|
|
61
61
|
const handoff = buildSimpleHandoff(request.workingDir, request.ccOutput, request.analyzedFiles, request.focusAreas, request.customPrompt);
|
|
62
|
-
const
|
|
63
|
-
|
|
62
|
+
const prompt = request.reviewMode === 'adversarial'
|
|
63
|
+
? buildAdversarialHandoffPrompt({ handoff })
|
|
64
|
+
: buildHandoffPrompt({ handoff, role: selectRole(request.focusAreas) });
|
|
64
65
|
const result = await this.runCli(prompt, request.workingDir, request.reasoningEffort || 'high', request.serviceTier);
|
|
65
66
|
if (result.exitCode !== 0) {
|
|
66
67
|
const error = this.categorizeError(result.stderr);
|
|
@@ -123,14 +124,20 @@ export class CodexAdapter {
|
|
|
123
124
|
// Check for errors captured from JSONL events
|
|
124
125
|
const decoderError = decoder.getError();
|
|
125
126
|
if (decoderError) {
|
|
126
|
-
|
|
127
|
+
const combined = result.stderr ? `${decoderError}\n\nCLI stderr: ${result.stderr}` : decoderError;
|
|
128
|
+
return { stdout: '', stderr: combined, exitCode: 1, truncated: false };
|
|
127
129
|
}
|
|
128
130
|
const finalResponse = decoder.getFinalResponse();
|
|
129
131
|
if (!finalResponse && decoder.hasNoOutput()) {
|
|
130
|
-
|
|
132
|
+
const combined = result.stderr ? `No output from Codex\n\nCLI stderr: ${result.stderr}` : 'No output from Codex';
|
|
133
|
+
return { stdout: '', stderr: combined, exitCode: 1, truncated: false };
|
|
134
|
+
}
|
|
135
|
+
if (!finalResponse) {
|
|
136
|
+
const combined = result.stderr ? `No result event from Codex\n\nCLI stderr: ${result.stderr}` : 'No result event from Codex';
|
|
137
|
+
return { stdout: '', stderr: combined, exitCode: 1, truncated: false };
|
|
131
138
|
}
|
|
132
139
|
return {
|
|
133
|
-
stdout: finalResponse
|
|
140
|
+
stdout: finalResponse,
|
|
134
141
|
stderr: result.stderr,
|
|
135
142
|
exitCode: result.exitCode,
|
|
136
143
|
truncated: result.truncated,
|
|
@@ -154,16 +161,16 @@ export class CodexAdapter {
|
|
|
154
161
|
}
|
|
155
162
|
categorizeError(stderr) {
|
|
156
163
|
const lower = stderr.toLowerCase();
|
|
157
|
-
if (lower.includes('rate limit') || lower.includes('
|
|
158
|
-
return { type: 'rate_limit', message:
|
|
164
|
+
if (lower.includes('rate limit') || lower.includes('rate_limit')) {
|
|
165
|
+
return { type: 'rate_limit', message: `Codex rate limit: ${stderr.slice(0, 500)}` };
|
|
159
166
|
}
|
|
160
167
|
if (lower.includes('unauthorized') || lower.includes('authentication') || stderr.includes('401') || stderr.includes('403')) {
|
|
161
|
-
return { type: 'auth_error', message:
|
|
168
|
+
return { type: 'auth_error', message: `Authentication failed: ${stderr.slice(0, 500)}`, details: { stderr } };
|
|
162
169
|
}
|
|
163
170
|
if (lower.includes('invalid_json_schema') || lower.includes('invalid_request_error')) {
|
|
164
|
-
return { type: 'cli_error', message: `API error: ${stderr.slice(0,
|
|
171
|
+
return { type: 'cli_error', message: `API error: ${stderr.slice(0, 500)}` };
|
|
165
172
|
}
|
|
166
|
-
return { type: 'cli_error', message: stderr || 'Unknown error' };
|
|
173
|
+
return { type: 'cli_error', message: stderr.slice(0, 500) || 'Unknown error' };
|
|
167
174
|
}
|
|
168
175
|
getSuggestion(error) {
|
|
169
176
|
switch (error.type) {
|
package/dist/adapters/gemini.js
CHANGED
|
@@ -10,7 +10,7 @@ import { existsSync } from 'fs';
|
|
|
10
10
|
import { registerAdapter, } from './base.js';
|
|
11
11
|
import { CliExecutor } from '../executor.js';
|
|
12
12
|
import { GeminiEventDecoder } from '../decoders/index.js';
|
|
13
|
-
import { buildSimpleHandoff, buildHandoffPrompt, selectRole, } from '../handoff.js';
|
|
13
|
+
import { buildSimpleHandoff, buildHandoffPrompt, buildAdversarialHandoffPrompt, selectRole, } from '../handoff.js';
|
|
14
14
|
// =============================================================================
|
|
15
15
|
// CONFIGURATION
|
|
16
16
|
// =============================================================================
|
|
@@ -56,8 +56,9 @@ export class GeminiAdapter {
|
|
|
56
56
|
}
|
|
57
57
|
try {
|
|
58
58
|
const handoff = buildSimpleHandoff(request.workingDir, request.ccOutput, request.analyzedFiles, request.focusAreas, request.customPrompt);
|
|
59
|
-
const
|
|
60
|
-
|
|
59
|
+
const prompt = request.reviewMode === 'adversarial'
|
|
60
|
+
? buildAdversarialHandoffPrompt({ handoff })
|
|
61
|
+
: buildHandoffPrompt({ handoff, role: selectRole(request.focusAreas) });
|
|
61
62
|
const result = await this.runCli(prompt, request.workingDir);
|
|
62
63
|
if (result.exitCode !== 0) {
|
|
63
64
|
const error = this.categorizeError(result.stderr);
|
|
@@ -109,8 +110,11 @@ export class GeminiAdapter {
|
|
|
109
110
|
const elapsed = Math.round((Date.now() - cliStartTime) / 1000);
|
|
110
111
|
console.error(`[gemini] ✓ complete (${elapsed}s)`);
|
|
111
112
|
const finalResponse = decoder.getFinalResponse();
|
|
113
|
+
if (!finalResponse && result.exitCode === 0) {
|
|
114
|
+
return { stdout: '', stderr: 'Gemini produced no output — review may have failed silently', exitCode: 1, truncated: false };
|
|
115
|
+
}
|
|
112
116
|
return {
|
|
113
|
-
stdout: finalResponse ||
|
|
117
|
+
stdout: finalResponse || '',
|
|
114
118
|
stderr: result.stderr,
|
|
115
119
|
exitCode: result.exitCode,
|
|
116
120
|
truncated: result.truncated,
|
|
@@ -135,12 +139,12 @@ export class GeminiAdapter {
|
|
|
135
139
|
categorizeError(stderr) {
|
|
136
140
|
const lower = stderr.toLowerCase();
|
|
137
141
|
if (lower.includes('rate limit') || lower.includes('quota')) {
|
|
138
|
-
return { type: 'rate_limit', message:
|
|
142
|
+
return { type: 'rate_limit', message: `Rate limit or quota exceeded: ${stderr.slice(0, 500)}` };
|
|
139
143
|
}
|
|
140
144
|
if (lower.includes('unauthorized') || lower.includes('authentication') || lower.includes('api key') || stderr.includes('401') || stderr.includes('403')) {
|
|
141
|
-
return { type: 'auth_error', message:
|
|
145
|
+
return { type: 'auth_error', message: `Authentication failed: ${stderr.slice(0, 500)}`, details: { stderr } };
|
|
142
146
|
}
|
|
143
|
-
return { type: 'cli_error', message: stderr || 'Unknown error' };
|
|
147
|
+
return { type: 'cli_error', message: stderr.slice(0, 500) || 'Unknown error' };
|
|
144
148
|
}
|
|
145
149
|
getSuggestion(error) {
|
|
146
150
|
switch (error.type) {
|
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
|
}
|
package/dist/commands.js
CHANGED
|
@@ -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.d.ts
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* Defines the minimal, targeted information that should flow from CC to reviewers.
|
|
5
5
|
*
|
|
6
6
|
* Philosophy:
|
|
7
|
-
* - Reviewers have filesystem
|
|
7
|
+
* - Reviewers have filesystem access - don't duplicate what they can discover
|
|
8
8
|
* - Pass ONLY what CC uniquely knows: uncertainties, decisions, questions
|
|
9
|
-
* - Let reviewer use their tools (
|
|
9
|
+
* - Let reviewer use their tools (file reading) for actual code
|
|
10
|
+
* - Do NOT assume git — working directory may not be a git repo
|
|
10
11
|
*/
|
|
11
12
|
import { z } from 'zod';
|
|
12
13
|
import { FocusArea } from './types.js';
|
|
@@ -211,6 +212,13 @@ export declare const ROLES: Record<string, ReviewerRole>;
|
|
|
211
212
|
* Select the best role based on focus areas
|
|
212
213
|
*/
|
|
213
214
|
export declare function selectRole(focusAreas?: FocusArea[]): ReviewerRole;
|
|
215
|
+
export declare const ADVERSARIAL_REVIEWER: ReviewerRole;
|
|
216
|
+
/**
|
|
217
|
+
* Build an adversarial handoff prompt with challenge-mode stance sections.
|
|
218
|
+
* Same structure as buildHandoffPrompt but adds adversarial XML sections
|
|
219
|
+
* and uses the ADVERSARIAL_REVIEWER role.
|
|
220
|
+
*/
|
|
221
|
+
export declare function buildAdversarialHandoffPrompt(options: PromptOptions): string;
|
|
214
222
|
export interface PromptOptions {
|
|
215
223
|
handoff: Handoff;
|
|
216
224
|
role?: ReviewerRole;
|
package/dist/handoff.js
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* Defines the minimal, targeted information that should flow from CC to reviewers.
|
|
5
5
|
*
|
|
6
6
|
* Philosophy:
|
|
7
|
-
* - Reviewers have filesystem
|
|
7
|
+
* - Reviewers have filesystem access - don't duplicate what they can discover
|
|
8
8
|
* - Pass ONLY what CC uniquely knows: uncertainties, decisions, questions
|
|
9
|
-
* - Let reviewer use their tools (
|
|
9
|
+
* - Let reviewer use their tools (file reading) for actual code
|
|
10
|
+
* - Do NOT assume git — working directory may not be a git repo
|
|
10
11
|
*/
|
|
11
12
|
import { z } from 'zod';
|
|
12
13
|
// =============================================================================
|
|
@@ -87,7 +88,7 @@ export const CHANGE_FOCUSED_REVIEWER = {
|
|
|
87
88
|
isGeneric: true,
|
|
88
89
|
applicableFocusAreas: [],
|
|
89
90
|
systemPrompt: `Change reviewer. Focus on: goal achievement, regressions, edge cases, side effects.
|
|
90
|
-
Reference specific lines in the
|
|
91
|
+
Reference specific lines in the source files.`,
|
|
91
92
|
};
|
|
92
93
|
/**
|
|
93
94
|
* Specialized roles - when specific focus is requested
|
|
@@ -153,6 +154,121 @@ export function selectRole(focusAreas) {
|
|
|
153
154
|
}
|
|
154
155
|
return CHANGE_FOCUSED_REVIEWER;
|
|
155
156
|
}
|
|
157
|
+
// =============================================================================
|
|
158
|
+
// ADVERSARIAL REVIEWER — Challenge mode for multi_review
|
|
159
|
+
// =============================================================================
|
|
160
|
+
export const ADVERSARIAL_REVIEWER = {
|
|
161
|
+
id: 'adversarial',
|
|
162
|
+
name: 'Adversarial Reviewer',
|
|
163
|
+
description: 'Actively tries to break confidence in the change — challenges assumptions, not just bugs',
|
|
164
|
+
isGeneric: false,
|
|
165
|
+
applicableFocusAreas: [],
|
|
166
|
+
systemPrompt: `Senior staff engineer performing an adversarial review. Your job is to break confidence in the change, not to validate it.`,
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* Build an adversarial handoff prompt with challenge-mode stance sections.
|
|
170
|
+
* Same structure as buildHandoffPrompt but adds adversarial XML sections
|
|
171
|
+
* and uses the ADVERSARIAL_REVIEWER role.
|
|
172
|
+
*/
|
|
173
|
+
export function buildAdversarialHandoffPrompt(options) {
|
|
174
|
+
const { handoff } = options;
|
|
175
|
+
const role = ADVERSARIAL_REVIEWER;
|
|
176
|
+
const sections = [];
|
|
177
|
+
// SECTION 1: ROLE
|
|
178
|
+
sections.push(`# ROLE: ${role.name}\n\n${role.systemPrompt}`);
|
|
179
|
+
// SECTION 2: ADVERSARIAL STANCE
|
|
180
|
+
sections.push(`## ADVERSARIAL STANCE
|
|
181
|
+
|
|
182
|
+
<operating_stance>
|
|
183
|
+
Default to skepticism. Assume the change can fail in subtle, high-cost,
|
|
184
|
+
or user-visible ways until the evidence says otherwise. Do not give credit
|
|
185
|
+
for good intent, partial fixes, or likely follow-up work.
|
|
186
|
+
</operating_stance>
|
|
187
|
+
|
|
188
|
+
<attack_surface>
|
|
189
|
+
Prioritized failure categories:
|
|
190
|
+
1. Auth/permissions bypass
|
|
191
|
+
2. Data loss or corruption
|
|
192
|
+
3. Rollback safety
|
|
193
|
+
4. Race conditions / concurrency
|
|
194
|
+
5. Empty-state / null / timeout handling
|
|
195
|
+
6. Version skew / backwards compatibility
|
|
196
|
+
7. Observability gaps (missing logs, metrics, alerts)
|
|
197
|
+
</attack_surface>
|
|
198
|
+
|
|
199
|
+
<review_method>
|
|
200
|
+
Actively try to disprove the change. Look for violated invariants,
|
|
201
|
+
missing guards, unhandled failure paths. If the user supplied a focus area,
|
|
202
|
+
weight it heavily, but still report any other material issue you can defend.
|
|
203
|
+
</review_method>
|
|
204
|
+
|
|
205
|
+
<finding_bar>
|
|
206
|
+
Material findings only. Each must answer:
|
|
207
|
+
1. What can go wrong?
|
|
208
|
+
2. Why is this code path vulnerable?
|
|
209
|
+
3. What is the likely impact?
|
|
210
|
+
4. What concrete change would reduce the risk?
|
|
211
|
+
</finding_bar>
|
|
212
|
+
|
|
213
|
+
<calibration_rules>
|
|
214
|
+
Prefer one strong finding over several weak ones. If you cannot defend
|
|
215
|
+
a finding from the provided code, drop it.
|
|
216
|
+
</calibration_rules>
|
|
217
|
+
|
|
218
|
+
<grounding_rules>
|
|
219
|
+
Be aggressive, but stay grounded. Every finding must be defensible from
|
|
220
|
+
the repository context. No speculative findings. No "might be an issue"
|
|
221
|
+
without concrete evidence from the code.
|
|
222
|
+
</grounding_rules>`);
|
|
223
|
+
// SECTION 3: TASK (same as standard)
|
|
224
|
+
sections.push(`## YOUR TASK
|
|
225
|
+
|
|
226
|
+
Review code in \`${handoff.workingDir}\`.
|
|
227
|
+
|
|
228
|
+
**Summary:** ${handoff.summary}${handoff.confidence !== undefined && handoff.confidence < 0.9 ? `\n**CC Confidence:** ${Math.round(handoff.confidence * 100)}% — verify weak areas` : ''}
|
|
229
|
+
|
|
230
|
+
**IMPORTANT:**
|
|
231
|
+
- This is a READ-ONLY review. Do NOT create, modify, or delete any files. Only read files to verify claims.
|
|
232
|
+
- Do NOT assume a git repository exists. Do NOT run git commands. Read files directly from the filesystem.`);
|
|
233
|
+
// SECTION 4: CC'S UNCERTAINTIES
|
|
234
|
+
if (handoff.uncertainties && handoff.uncertainties.length > 0) {
|
|
235
|
+
sections.push(`## CC'S UNCERTAINTIES
|
|
236
|
+
|
|
237
|
+
${handoff.uncertainties.map((u, i) => `### ${i + 1}. ${u.topic} ${u.severity === 'critical' ? '⚠️' : ''}
|
|
238
|
+
- **Question:** ${u.question}
|
|
239
|
+
${u.ccAssumption ? `- **CC assumed:** ${u.ccAssumption}` : ''}
|
|
240
|
+
${u.relevantFiles ? `- **Files:** ${u.relevantFiles.join(', ')}` : ''}`).join('\n\n')}`);
|
|
241
|
+
}
|
|
242
|
+
// SECTION 5: SPECIFIC QUESTIONS
|
|
243
|
+
if (handoff.questions && handoff.questions.length > 0) {
|
|
244
|
+
sections.push(`## QUESTIONS FROM CC
|
|
245
|
+
|
|
246
|
+
${handoff.questions.map((q, i) => `${i + 1}. **${q.question}**
|
|
247
|
+
${q.context ? `Context: ${q.context}` : ''}
|
|
248
|
+
${q.ccGuess ? `CC Guess: ${q.ccGuess}` : ''}`).join('\n')}`);
|
|
249
|
+
}
|
|
250
|
+
// SECTION 6: DECISIONS TO EVALUATE
|
|
251
|
+
if (handoff.decisions && handoff.decisions.length > 0) {
|
|
252
|
+
sections.push(`## DECISIONS TO EVALUATE
|
|
253
|
+
|
|
254
|
+
${handoff.decisions.map((d, i) => `${i + 1}. **${d.decision}**
|
|
255
|
+
Rationale: ${d.rationale}
|
|
256
|
+
${d.alternatives ? `Alternatives: ${d.alternatives.join(', ')}` : ''}`).join('\n')}`);
|
|
257
|
+
}
|
|
258
|
+
// SECTION 7: FOCUS AREAS
|
|
259
|
+
if (handoff.focusAreas && handoff.focusAreas.length > 0) {
|
|
260
|
+
sections.push(`## FOCUS AREAS\n\nWeight these areas heavily in your adversarial analysis:\n${handoff.focusAreas.map(f => `- **${f}**`).join('\n')}`);
|
|
261
|
+
}
|
|
262
|
+
// SECTION 8: PRIORITY FILES
|
|
263
|
+
if (handoff.priorityFiles && handoff.priorityFiles.length > 0) {
|
|
264
|
+
sections.push(`## PRIORITY FILES\n\n${handoff.priorityFiles.map(f => `- \`${f}\``).join('\n')}`);
|
|
265
|
+
}
|
|
266
|
+
// SECTION 9: ADVERSARIAL FOCUS (customInstructions steers the challenge)
|
|
267
|
+
if (handoff.customInstructions) {
|
|
268
|
+
sections.push(`## ADVERSARIAL FOCUS\n\n${handoff.customInstructions}`);
|
|
269
|
+
}
|
|
270
|
+
return sections.join('\n\n');
|
|
271
|
+
}
|
|
156
272
|
/**
|
|
157
273
|
* Build the review prompt using minimal, targeted context.
|
|
158
274
|
* No output format constraints — reviewer responds naturally, CC interprets.
|
|
@@ -166,11 +282,13 @@ export function buildHandoffPrompt(options) {
|
|
|
166
282
|
// SECTION 2: TASK
|
|
167
283
|
sections.push(`## YOUR TASK
|
|
168
284
|
|
|
169
|
-
Review
|
|
285
|
+
Review code in \`${handoff.workingDir}\`.
|
|
170
286
|
|
|
171
287
|
**Summary:** ${handoff.summary}${handoff.confidence !== undefined && handoff.confidence < 0.9 ? `\n**CC Confidence:** ${Math.round(handoff.confidence * 100)}% — verify weak areas` : ''}
|
|
172
288
|
|
|
173
|
-
**IMPORTANT
|
|
289
|
+
**IMPORTANT:**
|
|
290
|
+
- This is a READ-ONLY review. Do NOT create, modify, or delete any files. Only read files to verify claims.
|
|
291
|
+
- Do NOT assume a git repository exists. Do NOT run git commands. Read files directly from the filesystem.`);
|
|
174
292
|
// SECTION 3: CC'S UNCERTAINTIES
|
|
175
293
|
if (handoff.uncertainties && handoff.uncertainties.length > 0) {
|
|
176
294
|
sections.push(`## CC'S UNCERTAINTIES
|
|
@@ -200,6 +318,10 @@ ${handoff.decisions.map((d, i) => `${i + 1}. **${d.decision}**
|
|
|
200
318
|
if (handoff.priorityFiles && handoff.priorityFiles.length > 0) {
|
|
201
319
|
sections.push(`## PRIORITY FILES\n\n${handoff.priorityFiles.map(f => `- \`${f}\``).join('\n')}`);
|
|
202
320
|
}
|
|
321
|
+
// SECTION 7: CUSTOM INSTRUCTIONS
|
|
322
|
+
if (handoff.customInstructions) {
|
|
323
|
+
sections.push(`## ADDITIONAL INSTRUCTIONS\n\n${handoff.customInstructions}`);
|
|
324
|
+
}
|
|
203
325
|
return sections.join('\n\n');
|
|
204
326
|
}
|
|
205
327
|
// =============================================================================
|
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/prompt.js
CHANGED
|
@@ -101,6 +101,7 @@ ${request.customPrompt}`);
|
|
|
101
101
|
CONSTRAINTS:
|
|
102
102
|
• You have filesystem access - READ files to verify claims
|
|
103
103
|
• Do NOT modify any files (advisory mode only)
|
|
104
|
+
• Do NOT assume a git repository exists - do NOT run git commands
|
|
104
105
|
• Reference specific file:line when making claims
|
|
105
106
|
• Do NOT hallucinate file paths - verify they exist
|
|
106
107
|
• Be skeptical - verify before agreeing with CC's findings`);
|
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
|
// =============================================================================
|
|
@@ -82,24 +92,38 @@ export async function handleMultiReview(input) {
|
|
|
82
92
|
if (availableAdapters.length === 0) {
|
|
83
93
|
return { content: [{ type: 'text', text: '❌ No AI CLIs found.\n\nInstall at least one:\n - Codex: npm install -g @openai/codex-cli\n - Gemini: npm install -g @google/gemini-cli' }] };
|
|
84
94
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
95
|
+
// Spawn 2 reviews per adapter: standard + adversarial (all in parallel)
|
|
96
|
+
// customPrompt steers the adversarial focus only — strip it from standard pass to avoid bias
|
|
97
|
+
const { customPrompt, ...standardRequest } = request;
|
|
98
|
+
const reviewPromises = availableAdapters.flatMap((adapter) => [
|
|
99
|
+
adapter.runReview({ ...standardRequest }).then(result => ({ adapter, result, mode: 'standard' })),
|
|
100
|
+
adapter.runReview({ ...request, reviewMode: 'adversarial' }).then(result => ({ adapter, result, mode: 'adversarial' })),
|
|
101
|
+
]);
|
|
102
|
+
const results = await Promise.all(reviewPromises);
|
|
103
|
+
const standardResults = results.filter(r => r.mode === 'standard');
|
|
104
|
+
const adversarialResults = results.filter(r => r.mode === 'adversarial');
|
|
90
105
|
const allFailed = results.every(r => !r.result.success);
|
|
91
106
|
const someFailed = results.some(r => !r.result.success);
|
|
107
|
+
const lines = [];
|
|
92
108
|
if (allFailed)
|
|
93
109
|
lines.push('## Multi-Model Review ❌ All Failed\n');
|
|
94
110
|
else if (someFailed)
|
|
95
111
|
lines.push('## Multi-Model Review ⚠️ Partial Success\n');
|
|
96
112
|
else
|
|
97
113
|
lines.push('## Multi-Model Review ✓\n');
|
|
98
|
-
lines.push(`**Models:** ${availableAdapters.map(a => a.id).join(', ')}\n`);
|
|
99
|
-
|
|
114
|
+
lines.push(`**Models:** ${availableAdapters.map(a => a.id).join(', ')} (standard + adversarial)\n`);
|
|
115
|
+
// Standard section
|
|
116
|
+
lines.push('## Standard Review Findings\n');
|
|
117
|
+
for (const { adapter, result } of standardResults) {
|
|
100
118
|
lines.push(formatResult(result, adapter.getCapabilities().name));
|
|
101
119
|
lines.push('');
|
|
102
120
|
}
|
|
121
|
+
// Adversarial section
|
|
122
|
+
lines.push('## Challenge Review Findings\n');
|
|
123
|
+
for (const { adapter, result } of adversarialResults) {
|
|
124
|
+
lines.push(formatResult(result, `${adapter.getCapabilities().name} (Adversarial)`));
|
|
125
|
+
lines.push('');
|
|
126
|
+
}
|
|
103
127
|
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
104
128
|
}
|
|
105
129
|
// =============================================================================
|
|
@@ -140,9 +164,25 @@ export const TOOL_DEFINITIONS = {
|
|
|
140
164
|
required: ['workingDir', 'ccOutput', 'outputType']
|
|
141
165
|
}
|
|
142
166
|
},
|
|
167
|
+
claude_review: {
|
|
168
|
+
name: 'claude_review',
|
|
169
|
+
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.",
|
|
170
|
+
inputSchema: {
|
|
171
|
+
type: 'object',
|
|
172
|
+
properties: {
|
|
173
|
+
workingDir: { type: 'string', description: 'Working directory for the CLI to operate in' },
|
|
174
|
+
ccOutput: { type: 'string', description: "Claude Code's output to review (findings, plan, analysis)" },
|
|
175
|
+
outputType: { type: 'string', enum: ['plan', 'findings', 'analysis', 'proposal'], description: 'Type of output being reviewed' },
|
|
176
|
+
analyzedFiles: { type: 'array', items: { type: 'string' }, description: 'File paths that CC analyzed' },
|
|
177
|
+
focusAreas: { type: 'array', items: { type: 'string', enum: ['security', 'performance', 'architecture', 'correctness', 'maintainability', 'scalability', 'testing', 'documentation'] }, description: 'Areas to focus the review on' },
|
|
178
|
+
customPrompt: { type: 'string', description: 'Custom instructions for the reviewer' },
|
|
179
|
+
},
|
|
180
|
+
required: ['workingDir', 'ccOutput', 'outputType']
|
|
181
|
+
}
|
|
182
|
+
},
|
|
143
183
|
multi_review: {
|
|
144
184
|
name: 'multi_review',
|
|
145
|
-
description: "ONLY use when user explicitly requests '/multi-review' or 'review with
|
|
185
|
+
description: "ONLY use when user explicitly requests '/multi-review' or 'review with all models'. Runs parallel standard AND adversarial reviews from all available models. Each model reviews twice: standard (bugs/issues) + adversarial (challenge assumptions/design decisions). Use customPrompt to steer the adversarial focus. DO NOT use for general 'review' requests.",
|
|
146
186
|
inputSchema: {
|
|
147
187
|
type: 'object',
|
|
148
188
|
properties: {
|
|
@@ -151,7 +191,7 @@ export const TOOL_DEFINITIONS = {
|
|
|
151
191
|
outputType: { type: 'string', enum: ['plan', 'findings', 'analysis', 'proposal'], description: 'Type of output being reviewed' },
|
|
152
192
|
analyzedFiles: { type: 'array', items: { type: 'string' }, description: 'File paths that CC analyzed' },
|
|
153
193
|
focusAreas: { type: 'array', items: { type: 'string', enum: ['security', 'performance', 'architecture', 'correctness', 'maintainability', 'scalability', 'testing', 'documentation'] }, description: 'Areas to focus the review on' },
|
|
154
|
-
customPrompt: { type: 'string', description: 'Custom instructions for
|
|
194
|
+
customPrompt: { type: 'string', description: 'Custom instructions for standard review + adversarial focus steering' },
|
|
155
195
|
serviceTier: { type: 'string', enum: ['default', 'fast', 'flex'], description: 'Codex service tier (fast = priority processing, flex = cheaper/slower). Only applies to Codex.' }
|
|
156
196
|
},
|
|
157
197
|
required: ['workingDir', 'ccOutput', 'outputType']
|
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
|