cc-reviewer 1.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/dist/errors.js ADDED
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Error Handling for AI Reviewer MCP Server
3
+ */
4
+ // CLI installation commands
5
+ // Codex: https://developers.openai.com/codex/cli/
6
+ // Gemini: https://github.com/google-gemini/gemini-cli
7
+ const INSTALL_COMMANDS = {
8
+ codex: 'npm install -g @openai/codex-cli',
9
+ gemini: 'npm install -g @google/gemini-cli'
10
+ };
11
+ // Environment variables for API keys
12
+ const ENV_VARS = {
13
+ codex: 'OPENAI_API_KEY',
14
+ gemini: 'GEMINI_API_KEY'
15
+ };
16
+ // Authentication commands
17
+ const AUTH_COMMANDS = {
18
+ codex: 'codex login',
19
+ gemini: 'gemini (follow prompts)'
20
+ };
21
+ /**
22
+ * Create a CLI not found error
23
+ */
24
+ export function createCliNotFoundError(cli) {
25
+ return {
26
+ type: 'cli_not_found',
27
+ cli,
28
+ installCmd: INSTALL_COMMANDS[cli]
29
+ };
30
+ }
31
+ /**
32
+ * Create a timeout error
33
+ */
34
+ export function createTimeoutError(cli, durationMs) {
35
+ return {
36
+ type: 'timeout',
37
+ cli,
38
+ durationMs
39
+ };
40
+ }
41
+ /**
42
+ * Create a rate limit error
43
+ */
44
+ export function createRateLimitError(cli, retryAfterMs) {
45
+ return {
46
+ type: 'rate_limit',
47
+ cli,
48
+ retryAfterMs
49
+ };
50
+ }
51
+ /**
52
+ * Create an auth error
53
+ */
54
+ export function createAuthError(cli, message) {
55
+ return {
56
+ type: 'auth_error',
57
+ cli,
58
+ message
59
+ };
60
+ }
61
+ /**
62
+ * Create an invalid response error
63
+ */
64
+ export function createInvalidResponseError(cli, rawOutput) {
65
+ return {
66
+ type: 'invalid_response',
67
+ cli,
68
+ rawOutput
69
+ };
70
+ }
71
+ /**
72
+ * Create a CLI crash error
73
+ */
74
+ export function createCliError(cli, exitCode, stderr) {
75
+ return {
76
+ type: 'cli_error',
77
+ cli,
78
+ exitCode,
79
+ stderr
80
+ };
81
+ }
82
+ /**
83
+ * Format an error for user display
84
+ */
85
+ export function formatErrorForUser(error) {
86
+ const otherCli = error.cli === 'codex' ? 'gemini' : 'codex';
87
+ switch (error.type) {
88
+ case 'cli_not_found':
89
+ return `❌ ${error.cli} CLI not found.
90
+
91
+ Install with: ${error.installCmd}
92
+
93
+ Alternative: Use /${otherCli}-review instead`;
94
+ case 'timeout':
95
+ return `⏱️ ${error.cli} timed out after ${Math.round(error.durationMs / 1000)}s.
96
+
97
+ This might happen with complex reviews. Try:
98
+ • Reviewing a smaller scope
99
+ • Using --focus to narrow the review`;
100
+ case 'rate_limit':
101
+ const retryMsg = error.retryAfterMs
102
+ ? `Try again in ${Math.ceil(error.retryAfterMs / 1000)}s`
103
+ : 'Wait a moment and try again';
104
+ return `🚫 ${error.cli} rate limit hit.
105
+
106
+ ${retryMsg}
107
+
108
+ Alternative: Use /${otherCli}-review instead`;
109
+ case 'auth_error':
110
+ return `🔐 ${error.cli} authentication failed.
111
+
112
+ ${error.message}
113
+
114
+ Check your API key: ${ENV_VARS[error.cli]}
115
+ Run: ${AUTH_COMMANDS[error.cli]}`;
116
+ case 'invalid_response':
117
+ return `⚠️ ${error.cli} returned an unusable response.
118
+
119
+ The output couldn't be parsed as valid feedback.
120
+ Raw output (truncated): ${error.rawOutput.slice(0, 200)}...`;
121
+ case 'cli_error':
122
+ return `❌ ${error.cli} crashed (exit code ${error.exitCode}).
123
+
124
+ ${error.stderr}`;
125
+ }
126
+ }
127
+ /**
128
+ * Detect error type from CLI output and error messages
129
+ */
130
+ export function detectErrorType(cli, error, stdout, stderr, exitCode) {
131
+ // CLI not found
132
+ if (error.code === 'ENOENT') {
133
+ return createCliNotFoundError(cli);
134
+ }
135
+ // Rate limit
136
+ if (stderr.toLowerCase().includes('rate limit') ||
137
+ stdout.toLowerCase().includes('rate limit')) {
138
+ const retryAfterMatch = stderr.match(/retry after (\d+)/i);
139
+ const retryAfterMs = retryAfterMatch ? parseInt(retryAfterMatch[1]) * 1000 : undefined;
140
+ return createRateLimitError(cli, retryAfterMs);
141
+ }
142
+ // Auth error
143
+ if (stderr.toLowerCase().includes('unauthorized') ||
144
+ stderr.toLowerCase().includes('authentication') ||
145
+ stderr.toLowerCase().includes('401') ||
146
+ stderr.toLowerCase().includes('403')) {
147
+ return createAuthError(cli, stderr);
148
+ }
149
+ // Generic CLI error
150
+ if (exitCode !== null && exitCode !== 0) {
151
+ return createCliError(cli, exitCode, stderr);
152
+ }
153
+ // Invalid response (fallback)
154
+ return createInvalidResponseError(cli, stdout);
155
+ }
156
+ /**
157
+ * Parse retry-after from error response
158
+ */
159
+ export function parseRetryAfter(errorMessage) {
160
+ const match = errorMessage.match(/retry[- ]?after[:\s]+(\d+)/i);
161
+ if (match) {
162
+ return parseInt(match[1]) * 1000; // Convert to ms
163
+ }
164
+ return undefined;
165
+ }
166
+ /**
167
+ * Generate suggestion based on error type
168
+ */
169
+ export function getSuggestion(error) {
170
+ switch (error.type) {
171
+ case 'cli_not_found':
172
+ return `Install ${error.cli} CLI or use the other model`;
173
+ case 'timeout':
174
+ return 'Try reviewing a smaller scope or using --focus';
175
+ case 'rate_limit':
176
+ return error.retryAfterMs
177
+ ? `Wait ${Math.ceil(error.retryAfterMs / 1000)}s and retry`
178
+ : 'Wait a moment and retry';
179
+ case 'auth_error':
180
+ return `Check your ${ENV_VARS[error.cli]} environment variable`;
181
+ case 'invalid_response':
182
+ return 'Retry with a more specific focus area';
183
+ case 'cli_error':
184
+ return 'Check the CLI documentation for troubleshooting';
185
+ default:
186
+ return undefined;
187
+ }
188
+ }
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AI Reviewer MCP Server
4
+ *
5
+ * Provides tools for getting second-opinion feedback from external AI CLIs
6
+ * (Codex and Gemini) on Claude Code's work.
7
+ */
8
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AI Reviewer MCP Server
4
+ *
5
+ * Provides tools for getting second-opinion feedback from external AI CLIs
6
+ * (Codex and Gemini) on Claude Code's work.
7
+ */
8
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
9
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
10
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
11
+ import { handleCodexFeedback, handleGeminiFeedback, handleMultiFeedback, FeedbackInputSchema, TOOL_DEFINITIONS } from './tools/feedback.js';
12
+ import { logCliStatus } from './cli/check.js';
13
+ // Create the MCP server
14
+ const server = new Server({
15
+ name: 'ai-reviewer',
16
+ version: '1.0.0',
17
+ }, {
18
+ capabilities: {
19
+ tools: {},
20
+ },
21
+ });
22
+ // Handle tool listing
23
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
24
+ return {
25
+ tools: [
26
+ TOOL_DEFINITIONS.codex_feedback,
27
+ TOOL_DEFINITIONS.gemini_feedback,
28
+ TOOL_DEFINITIONS.multi_feedback,
29
+ ],
30
+ };
31
+ });
32
+ // Handle tool calls
33
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
34
+ const { name, arguments: args } = request.params;
35
+ try {
36
+ // Validate input
37
+ const input = FeedbackInputSchema.parse(args);
38
+ switch (name) {
39
+ case 'codex_feedback':
40
+ return await handleCodexFeedback(input);
41
+ case 'gemini_feedback':
42
+ return await handleGeminiFeedback(input);
43
+ case 'multi_feedback':
44
+ return await handleMultiFeedback(input);
45
+ default:
46
+ return {
47
+ content: [{
48
+ type: 'text',
49
+ text: `Unknown tool: ${name}`
50
+ }],
51
+ isError: true
52
+ };
53
+ }
54
+ }
55
+ catch (error) {
56
+ const errorMessage = error instanceof Error ? error.message : String(error);
57
+ return {
58
+ content: [{
59
+ type: 'text',
60
+ text: `Error: ${errorMessage}`
61
+ }],
62
+ isError: true
63
+ };
64
+ }
65
+ });
66
+ // Start the server
67
+ async function main() {
68
+ // Log CLI availability status on startup
69
+ await logCliStatus();
70
+ const transport = new StdioServerTransport();
71
+ await server.connect(transport);
72
+ console.error('AI Reviewer MCP Server running on stdio');
73
+ }
74
+ main().catch((error) => {
75
+ console.error('Fatal error:', error);
76
+ process.exit(1);
77
+ });
@@ -0,0 +1,24 @@
1
+ /**
2
+ * 7-Section Prompt Builder for External CLI Delegation
3
+ */
4
+ import { FeedbackRequest, CliType } from './types.js';
5
+ /**
6
+ * Build the 7-section delegation prompt for external CLIs
7
+ */
8
+ export declare function build7SectionPrompt(request: FeedbackRequest): string;
9
+ /**
10
+ * Build the developer instructions (persona) for a specific CLI
11
+ */
12
+ export declare function buildDeveloperInstructions(cli: CliType): string;
13
+ /**
14
+ * Build a retry prompt that includes previous attempt information
15
+ */
16
+ export declare function buildRetryPrompt(request: FeedbackRequest, attemptNumber: number, previousError: string, previousOutput: string): string;
17
+ /**
18
+ * Detect output type from CC's output content
19
+ */
20
+ export declare function detectOutputType(ccOutput: string): 'findings' | 'plan' | 'proposal' | 'analysis';
21
+ /**
22
+ * Validate that external CLI output follows expected structure
23
+ */
24
+ export declare function isValidFeedbackOutput(output: string): boolean;
package/dist/prompt.js ADDED
@@ -0,0 +1,134 @@
1
+ /**
2
+ * 7-Section Prompt Builder for External CLI Delegation
3
+ */
4
+ import { REVIEWER_PERSONAS, FOCUS_AREA_DESCRIPTIONS } from './types.js';
5
+ /**
6
+ * Build the 7-section delegation prompt for external CLIs
7
+ */
8
+ export function build7SectionPrompt(request) {
9
+ const { workingDir, ccOutput, outputType, analyzedFiles = [], focusAreas = [], customPrompt } = request;
10
+ const focusDescription = focusAreas.length > 0
11
+ ? focusAreas.map(f => `${f} (${FOCUS_AREA_DESCRIPTIONS[f]})`).join(', ')
12
+ : 'General review';
13
+ const filesAnalyzed = analyzedFiles.length > 0
14
+ ? analyzedFiles.join(', ')
15
+ : 'Not specified';
16
+ const customInstructions = customPrompt
17
+ ? `\n Special Instructions: ${customPrompt}`
18
+ : '';
19
+ return `TASK: Review CC's ${outputType} and provide structured feedback.
20
+
21
+ EXPECTED OUTCOME: Identify agreements, disagreements, additions, and alternatives.
22
+
23
+ CONTEXT:
24
+ Working Directory: ${workingDir}
25
+ Output Type: ${outputType}
26
+ Files Analyzed: ${filesAnalyzed}
27
+ CC's Output:
28
+ ${indent(ccOutput, 4)}
29
+
30
+ CONSTRAINTS:
31
+ Focus Areas: ${focusDescription}
32
+ Advisory mode only (no file changes)
33
+ You have filesystem access${customInstructions}
34
+
35
+ MUST DO:
36
+ • Review CC's findings critically
37
+ • Verify claims by reading files yourself
38
+ • Reference specific file:line when relevant
39
+ • Follow OUTPUT FORMAT exactly
40
+
41
+ MUST NOT DO:
42
+ • Make any file changes
43
+ • Hallucinate file paths
44
+ • Return unstructured text
45
+
46
+ OUTPUT FORMAT:
47
+ ## Agreements
48
+ - [Finding]: [Why correct]
49
+
50
+ ## Disagreements
51
+ - [Finding]: [Why wrong] - [Correct assessment]
52
+
53
+ ## Additions
54
+ - [New finding]: [File:line] - [Impact]
55
+
56
+ ## Alternatives
57
+ - [Topic]: [Alternative] - [Tradeoffs]
58
+
59
+ ## Risk Assessment
60
+ [Low/Medium/High] - [Reason]`;
61
+ }
62
+ /**
63
+ * Build the developer instructions (persona) for a specific CLI
64
+ */
65
+ export function buildDeveloperInstructions(cli) {
66
+ const persona = REVIEWER_PERSONAS[cli];
67
+ return `You are a ${persona.name} code review expert.
68
+
69
+ Focus on: ${persona.focus}
70
+
71
+ Style: ${persona.style}
72
+
73
+ You are reviewing another AI assistant's (Claude Code) work on a codebase.
74
+ Your job is to provide a second opinion - validate their findings, challenge
75
+ incorrect assessments, and add anything they missed.
76
+
77
+ Be specific. Reference file paths and line numbers when relevant.
78
+ Use the structured OUTPUT FORMAT provided in the prompt.`;
79
+ }
80
+ /**
81
+ * Build a retry prompt that includes previous attempt information
82
+ */
83
+ export function buildRetryPrompt(request, attemptNumber, previousError, previousOutput) {
84
+ const basePrompt = build7SectionPrompt(request);
85
+ return `TASK: Review CC's ${request.outputType} (RETRY - attempt ${attemptNumber}/3)
86
+
87
+ PREVIOUS ATTEMPT FAILED:
88
+ Error: ${previousError}
89
+ Raw output (truncated): ${previousOutput.slice(0, 300)}...
90
+
91
+ ADDITIONAL INSTRUCTION: Follow the OUTPUT FORMAT exactly. Each section header
92
+ must start with "## " and contain structured content.
93
+
94
+ ${basePrompt.split('\n').slice(1).join('\n')}`;
95
+ }
96
+ /**
97
+ * Helper to indent text
98
+ */
99
+ function indent(text, spaces) {
100
+ const padding = ' '.repeat(spaces);
101
+ return text.split('\n').map(line => padding + line).join('\n');
102
+ }
103
+ /**
104
+ * Detect output type from CC's output content
105
+ */
106
+ export function detectOutputType(ccOutput) {
107
+ if (/\b(vulnerab|injection|XSS|CSRF|SSRF|auth.*issue|security)/i.test(ccOutput)) {
108
+ return 'findings';
109
+ }
110
+ if (/\b(fix|bug|issue|error|proposed fix|solution|patch)/i.test(ccOutput)) {
111
+ return 'proposal';
112
+ }
113
+ if (/\b(architect|design|plan|implement|phase|step \d|structure)/i.test(ccOutput)) {
114
+ return 'plan';
115
+ }
116
+ return 'analysis';
117
+ }
118
+ /**
119
+ * Validate that external CLI output follows expected structure
120
+ */
121
+ export function isValidFeedbackOutput(output) {
122
+ const requiredSections = [
123
+ /^## Agreements$/m,
124
+ /^## Disagreements$/m,
125
+ /^## Additions$/m,
126
+ /^## Alternatives$/m,
127
+ /^## Risk Assessment$/m
128
+ ];
129
+ // Must have all required sections: Agreements, Disagreements, Additions, Alternatives, Risk Assessment
130
+ const hasRequiredSections = requiredSections.every(regex => regex.test(output));
131
+ // Must not be empty or too short
132
+ const hasContent = output.length > 100;
133
+ return hasRequiredSections && hasContent;
134
+ }
@@ -0,0 +1,185 @@
1
+ /**
2
+ * MCP Tool Implementations for AI Reviewer
3
+ */
4
+ import { z } from 'zod';
5
+ export declare const FeedbackInputSchema: z.ZodObject<{
6
+ workingDir: z.ZodString;
7
+ ccOutput: z.ZodString;
8
+ outputType: z.ZodEnum<["plan", "findings", "analysis", "proposal"]>;
9
+ analyzedFiles: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
10
+ focusAreas: z.ZodOptional<z.ZodArray<z.ZodEnum<["security", "performance", "architecture", "correctness", "maintainability", "scalability", "testing", "documentation"]>, "many">>;
11
+ customPrompt: z.ZodOptional<z.ZodString>;
12
+ }, "strip", z.ZodTypeAny, {
13
+ workingDir: string;
14
+ ccOutput: string;
15
+ outputType: "plan" | "findings" | "analysis" | "proposal";
16
+ analyzedFiles?: string[] | undefined;
17
+ focusAreas?: ("security" | "performance" | "architecture" | "correctness" | "maintainability" | "scalability" | "testing" | "documentation")[] | undefined;
18
+ customPrompt?: string | undefined;
19
+ }, {
20
+ workingDir: string;
21
+ ccOutput: string;
22
+ outputType: "plan" | "findings" | "analysis" | "proposal";
23
+ analyzedFiles?: string[] | undefined;
24
+ focusAreas?: ("security" | "performance" | "architecture" | "correctness" | "maintainability" | "scalability" | "testing" | "documentation")[] | undefined;
25
+ customPrompt?: string | undefined;
26
+ }>;
27
+ export type FeedbackInput = z.infer<typeof FeedbackInputSchema>;
28
+ /**
29
+ * Codex feedback tool handler
30
+ */
31
+ export declare function handleCodexFeedback(input: FeedbackInput): Promise<{
32
+ content: Array<{
33
+ type: 'text';
34
+ text: string;
35
+ }>;
36
+ }>;
37
+ /**
38
+ * Gemini feedback tool handler
39
+ */
40
+ export declare function handleGeminiFeedback(input: FeedbackInput): Promise<{
41
+ content: Array<{
42
+ type: 'text';
43
+ text: string;
44
+ }>;
45
+ }>;
46
+ /**
47
+ * Multi-model feedback tool handler (parallel execution)
48
+ */
49
+ export declare function handleMultiFeedback(input: FeedbackInput): Promise<{
50
+ content: Array<{
51
+ type: 'text';
52
+ text: string;
53
+ }>;
54
+ }>;
55
+ /**
56
+ * Tool definitions for MCP registration
57
+ */
58
+ export declare const TOOL_DEFINITIONS: {
59
+ codex_feedback: {
60
+ name: string;
61
+ description: string;
62
+ inputSchema: {
63
+ type: string;
64
+ properties: {
65
+ workingDir: {
66
+ type: string;
67
+ description: string;
68
+ };
69
+ ccOutput: {
70
+ type: string;
71
+ description: string;
72
+ };
73
+ outputType: {
74
+ type: string;
75
+ enum: string[];
76
+ description: string;
77
+ };
78
+ analyzedFiles: {
79
+ type: string;
80
+ items: {
81
+ type: string;
82
+ };
83
+ description: string;
84
+ };
85
+ focusAreas: {
86
+ type: string;
87
+ items: {
88
+ type: string;
89
+ enum: string[];
90
+ };
91
+ description: string;
92
+ };
93
+ customPrompt: {
94
+ type: string;
95
+ description: string;
96
+ };
97
+ };
98
+ required: string[];
99
+ };
100
+ };
101
+ gemini_feedback: {
102
+ name: string;
103
+ description: string;
104
+ inputSchema: {
105
+ type: string;
106
+ properties: {
107
+ workingDir: {
108
+ type: string;
109
+ description: string;
110
+ };
111
+ ccOutput: {
112
+ type: string;
113
+ description: string;
114
+ };
115
+ outputType: {
116
+ type: string;
117
+ enum: string[];
118
+ description: string;
119
+ };
120
+ analyzedFiles: {
121
+ type: string;
122
+ items: {
123
+ type: string;
124
+ };
125
+ description: string;
126
+ };
127
+ focusAreas: {
128
+ type: string;
129
+ items: {
130
+ type: string;
131
+ enum: string[];
132
+ };
133
+ description: string;
134
+ };
135
+ customPrompt: {
136
+ type: string;
137
+ description: string;
138
+ };
139
+ };
140
+ required: string[];
141
+ };
142
+ };
143
+ multi_feedback: {
144
+ name: string;
145
+ description: string;
146
+ inputSchema: {
147
+ type: string;
148
+ properties: {
149
+ workingDir: {
150
+ type: string;
151
+ description: string;
152
+ };
153
+ ccOutput: {
154
+ type: string;
155
+ description: string;
156
+ };
157
+ outputType: {
158
+ type: string;
159
+ enum: string[];
160
+ description: string;
161
+ };
162
+ analyzedFiles: {
163
+ type: string;
164
+ items: {
165
+ type: string;
166
+ };
167
+ description: string;
168
+ };
169
+ focusAreas: {
170
+ type: string;
171
+ items: {
172
+ type: string;
173
+ enum: string[];
174
+ };
175
+ description: string;
176
+ };
177
+ customPrompt: {
178
+ type: string;
179
+ description: string;
180
+ };
181
+ };
182
+ required: string[];
183
+ };
184
+ };
185
+ };