second-opinion-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +323 -0
- package/dist/config.d.ts +31 -0
- package/dist/config.js +84 -0
- package/dist/context/bundler.d.ts +51 -0
- package/dist/context/bundler.js +481 -0
- package/dist/context/bundler.test.d.ts +1 -0
- package/dist/context/bundler.test.js +275 -0
- package/dist/context/git.d.ts +21 -0
- package/dist/context/git.js +102 -0
- package/dist/context/imports.d.ts +43 -0
- package/dist/context/imports.js +197 -0
- package/dist/context/imports.test.d.ts +1 -0
- package/dist/context/imports.test.js +147 -0
- package/dist/context/index.d.ts +6 -0
- package/dist/context/index.js +6 -0
- package/dist/context/session.d.ts +35 -0
- package/dist/context/session.js +317 -0
- package/dist/context/tests.d.ts +13 -0
- package/dist/context/tests.js +83 -0
- package/dist/context/types.d.ts +16 -0
- package/dist/context/types.js +100 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -0
- package/dist/output/writer.d.ts +34 -0
- package/dist/output/writer.js +162 -0
- package/dist/output/writer.test.d.ts +1 -0
- package/dist/output/writer.test.js +175 -0
- package/dist/providers/base.d.ts +24 -0
- package/dist/providers/base.js +77 -0
- package/dist/providers/base.test.d.ts +1 -0
- package/dist/providers/base.test.js +91 -0
- package/dist/providers/gemini.d.ts +8 -0
- package/dist/providers/gemini.js +43 -0
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.js +31 -0
- package/dist/providers/openai.d.ts +8 -0
- package/dist/providers/openai.js +39 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +181 -0
- package/dist/test-utils.d.ts +71 -0
- package/dist/test-utils.js +136 -0
- package/dist/tools/review.d.ts +76 -0
- package/dist/tools/review.js +199 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/tokens.d.ts +26 -0
- package/dist/utils/tokens.js +27 -0
- package/package.json +61 -0
- package/scripts/install-config.js +51 -0
- package/second-opinion.skill.md +34 -0
- package/templates/second-opinion.md +54 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { loadConfig, loadReviewInstructions } from "../config.js";
|
|
5
|
+
import { bundleContext, formatBundleAsMarkdown, } from "../context/index.js";
|
|
6
|
+
import { isWithinProject } from "../context/imports.js";
|
|
7
|
+
import { createProvider } from "../providers/index.js";
|
|
8
|
+
import { writeReview, writeEgressManifest, deriveSessionName, } from "../output/writer.js";
|
|
9
|
+
/**
|
|
10
|
+
* Validate that a project path is safe to use
|
|
11
|
+
*/
|
|
12
|
+
function validateProjectPath(projectPath) {
|
|
13
|
+
// Must be absolute
|
|
14
|
+
if (!path.isAbsolute(projectPath)) {
|
|
15
|
+
throw new Error(`projectPath must be absolute, got: ${projectPath}`);
|
|
16
|
+
}
|
|
17
|
+
// Normalize and check for traversal
|
|
18
|
+
const normalized = path.normalize(projectPath);
|
|
19
|
+
if (normalized !== projectPath && projectPath.includes("..")) {
|
|
20
|
+
throw new Error(`projectPath contains path traversal: ${projectPath}`);
|
|
21
|
+
}
|
|
22
|
+
// Must exist
|
|
23
|
+
if (!fs.existsSync(normalized)) {
|
|
24
|
+
throw new Error(`projectPath does not exist: ${normalized}`);
|
|
25
|
+
}
|
|
26
|
+
// Must be a directory
|
|
27
|
+
const stat = fs.statSync(normalized);
|
|
28
|
+
if (!stat.isDirectory()) {
|
|
29
|
+
throw new Error(`projectPath is not a directory: ${normalized}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export const SecondOpinionInputSchema = z.object({
|
|
33
|
+
// Required
|
|
34
|
+
provider: z.enum(["gemini", "openai"]).describe("Which LLM to use for the review"),
|
|
35
|
+
projectPath: z.string().describe("Absolute path to the project being reviewed"),
|
|
36
|
+
// Task specification
|
|
37
|
+
task: z
|
|
38
|
+
.string()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("The task or prompt for the LLM to accomplish. When omitted, defaults to code review."),
|
|
41
|
+
// Context options
|
|
42
|
+
sessionId: z
|
|
43
|
+
.string()
|
|
44
|
+
.optional()
|
|
45
|
+
.describe("Claude Code session ID (defaults to most recent)"),
|
|
46
|
+
includeFiles: z
|
|
47
|
+
.array(z.string())
|
|
48
|
+
.optional()
|
|
49
|
+
.describe("Additional files or folders to include (supports ~ and relative paths)"),
|
|
50
|
+
allowExternalFiles: z
|
|
51
|
+
.boolean()
|
|
52
|
+
.default(false)
|
|
53
|
+
.describe("Allow including files outside the project directory. Required when includeFiles contains paths outside the project. Use with caution as these files will be sent to the external LLM."),
|
|
54
|
+
includeConversation: z
|
|
55
|
+
.boolean()
|
|
56
|
+
.default(true)
|
|
57
|
+
.describe("Include conversation context from Claude session"),
|
|
58
|
+
// Smart context options
|
|
59
|
+
includeDependencies: z
|
|
60
|
+
.boolean()
|
|
61
|
+
.default(true)
|
|
62
|
+
.describe("Include files imported by modified files"),
|
|
63
|
+
includeDependents: z
|
|
64
|
+
.boolean()
|
|
65
|
+
.default(true)
|
|
66
|
+
.describe("Include files that import modified files"),
|
|
67
|
+
includeTests: z
|
|
68
|
+
.boolean()
|
|
69
|
+
.default(true)
|
|
70
|
+
.describe("Include corresponding test files"),
|
|
71
|
+
includeTypes: z
|
|
72
|
+
.boolean()
|
|
73
|
+
.default(true)
|
|
74
|
+
.describe("Include referenced type definitions"),
|
|
75
|
+
maxTokens: z
|
|
76
|
+
.number()
|
|
77
|
+
.default(100000)
|
|
78
|
+
.describe("Maximum tokens for context"),
|
|
79
|
+
// Output options
|
|
80
|
+
sessionName: z
|
|
81
|
+
.string()
|
|
82
|
+
.optional()
|
|
83
|
+
.describe("Name for this output (used in filename)"),
|
|
84
|
+
customPrompt: z
|
|
85
|
+
.string()
|
|
86
|
+
.optional()
|
|
87
|
+
.describe("Additional instructions (deprecated: use task instead)"),
|
|
88
|
+
focusAreas: z
|
|
89
|
+
.array(z.string())
|
|
90
|
+
.optional()
|
|
91
|
+
.describe("Specific areas to focus on (for code reviews)"),
|
|
92
|
+
dryRun: z
|
|
93
|
+
.boolean()
|
|
94
|
+
.default(false)
|
|
95
|
+
.describe("If true, return a preview of what would be sent without calling the external API. Use this for confirmation before sending files to external providers."),
|
|
96
|
+
});
|
|
97
|
+
/**
|
|
98
|
+
* Build egress summary from bundle, categorizing files as project vs external
|
|
99
|
+
*/
|
|
100
|
+
function buildEgressSummary(bundle, projectPath, provider) {
|
|
101
|
+
const projectFilePaths = [];
|
|
102
|
+
const externalFilePaths = [];
|
|
103
|
+
for (const file of bundle.files) {
|
|
104
|
+
if (isWithinProject(file.path, projectPath)) {
|
|
105
|
+
projectFilePaths.push(file.path);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
externalFilePaths.push(file.path);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Get unique parent directories of external files
|
|
112
|
+
const externalLocations = [
|
|
113
|
+
...new Set(externalFilePaths.map((p) => path.dirname(p))),
|
|
114
|
+
];
|
|
115
|
+
return {
|
|
116
|
+
projectFilesSent: projectFilePaths.length,
|
|
117
|
+
projectFilePaths,
|
|
118
|
+
externalFilesSent: externalFilePaths.length,
|
|
119
|
+
externalFilePaths,
|
|
120
|
+
externalLocations,
|
|
121
|
+
blockedFiles: bundle.omittedFiles.map((f) => ({
|
|
122
|
+
path: f.path,
|
|
123
|
+
reason: f.reason,
|
|
124
|
+
})),
|
|
125
|
+
provider,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
export async function executeReview(input) {
|
|
129
|
+
// Validate project path before proceeding
|
|
130
|
+
validateProjectPath(input.projectPath);
|
|
131
|
+
const config = loadConfig();
|
|
132
|
+
// 1. Bundle the context
|
|
133
|
+
const bundle = await bundleContext({
|
|
134
|
+
projectPath: input.projectPath,
|
|
135
|
+
sessionId: input.sessionId,
|
|
136
|
+
includeFiles: input.includeFiles,
|
|
137
|
+
allowExternalFiles: input.allowExternalFiles,
|
|
138
|
+
includeConversation: input.includeConversation,
|
|
139
|
+
includeDependencies: input.includeDependencies,
|
|
140
|
+
includeDependents: input.includeDependents,
|
|
141
|
+
includeTests: input.includeTests,
|
|
142
|
+
includeTypes: input.includeTypes,
|
|
143
|
+
maxTokens: input.maxTokens,
|
|
144
|
+
});
|
|
145
|
+
// Build egress summary (used for both dry run and actual execution)
|
|
146
|
+
const summary = buildEgressSummary(bundle, input.projectPath, input.provider);
|
|
147
|
+
// 2. If dry run, return preview without calling external API
|
|
148
|
+
if (input.dryRun) {
|
|
149
|
+
return {
|
|
150
|
+
dryRun: true,
|
|
151
|
+
provider: input.provider,
|
|
152
|
+
summary,
|
|
153
|
+
totalTokens: bundle.totalTokens,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
// 3. Format as markdown
|
|
157
|
+
const contextMarkdown = formatBundleAsMarkdown(bundle, input.projectPath);
|
|
158
|
+
// 4. Load review instructions
|
|
159
|
+
const instructions = loadReviewInstructions(input.projectPath);
|
|
160
|
+
// 5. Create provider and execute task
|
|
161
|
+
const provider = createProvider(input.provider, config);
|
|
162
|
+
const response = await provider.review({
|
|
163
|
+
instructions,
|
|
164
|
+
context: contextMarkdown,
|
|
165
|
+
task: input.task,
|
|
166
|
+
focusAreas: input.focusAreas,
|
|
167
|
+
customPrompt: input.customPrompt,
|
|
168
|
+
});
|
|
169
|
+
// 6. Derive session name if not provided
|
|
170
|
+
const sessionName = input.sessionName ||
|
|
171
|
+
deriveSessionName(bundle.conversationContext, "code-review");
|
|
172
|
+
// 7. Write the output files
|
|
173
|
+
const timestamp = new Date().toISOString();
|
|
174
|
+
const metadata = {
|
|
175
|
+
sessionName,
|
|
176
|
+
provider: input.provider,
|
|
177
|
+
model: response.model,
|
|
178
|
+
timestamp,
|
|
179
|
+
filesReviewed: bundle.files.map((f) => f.path),
|
|
180
|
+
tokensUsed: response.tokensUsed,
|
|
181
|
+
task: input.task,
|
|
182
|
+
};
|
|
183
|
+
const reviewFile = writeReview(input.projectPath, config.reviewsDir, metadata, response.review);
|
|
184
|
+
// 8. Write egress manifest for audit trail
|
|
185
|
+
const egressManifestFile = writeEgressManifest(input.projectPath, config.reviewsDir, metadata, summary);
|
|
186
|
+
return {
|
|
187
|
+
dryRun: false,
|
|
188
|
+
review: response.review,
|
|
189
|
+
reviewFile,
|
|
190
|
+
egressManifestFile,
|
|
191
|
+
provider: input.provider,
|
|
192
|
+
model: response.model,
|
|
193
|
+
tokensUsed: response.tokensUsed,
|
|
194
|
+
timestamp,
|
|
195
|
+
filesReviewed: bundle.files.length,
|
|
196
|
+
contextTokens: bundle.totalTokens,
|
|
197
|
+
summary,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { estimateTokens, BUDGET_ALLOCATION, type BudgetCategory } from "./tokens.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { estimateTokens, BUDGET_ALLOCATION } from "./tokens.js";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token estimation utilities for context budgeting
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Rough token estimation (4 chars per token is a reasonable approximation for code)
|
|
6
|
+
*
|
|
7
|
+
* This is intentionally simple - for budget estimation we don't need tiktoken's
|
|
8
|
+
* precision, just a ballpark to avoid sending too much context.
|
|
9
|
+
*/
|
|
10
|
+
export declare function estimateTokens(text: string): number;
|
|
11
|
+
/**
|
|
12
|
+
* Budget allocation by category (explicit files get priority, then session, etc.)
|
|
13
|
+
*
|
|
14
|
+
* These percentages determine how the token budget is divided when multiple
|
|
15
|
+
* categories of files compete for space.
|
|
16
|
+
*/
|
|
17
|
+
export declare const BUDGET_ALLOCATION: {
|
|
18
|
+
readonly explicit: 0.15;
|
|
19
|
+
readonly session: 0.3;
|
|
20
|
+
readonly git: 0.1;
|
|
21
|
+
readonly dependency: 0.15;
|
|
22
|
+
readonly dependent: 0.15;
|
|
23
|
+
readonly test: 0.1;
|
|
24
|
+
readonly type: 0.05;
|
|
25
|
+
};
|
|
26
|
+
export type BudgetCategory = keyof typeof BUDGET_ALLOCATION;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token estimation utilities for context budgeting
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Rough token estimation (4 chars per token is a reasonable approximation for code)
|
|
6
|
+
*
|
|
7
|
+
* This is intentionally simple - for budget estimation we don't need tiktoken's
|
|
8
|
+
* precision, just a ballpark to avoid sending too much context.
|
|
9
|
+
*/
|
|
10
|
+
export function estimateTokens(text) {
|
|
11
|
+
return Math.ceil(text.length / 4);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Budget allocation by category (explicit files get priority, then session, etc.)
|
|
15
|
+
*
|
|
16
|
+
* These percentages determine how the token budget is divided when multiple
|
|
17
|
+
* categories of files compete for space.
|
|
18
|
+
*/
|
|
19
|
+
export const BUDGET_ALLOCATION = {
|
|
20
|
+
explicit: 0.15,
|
|
21
|
+
session: 0.3,
|
|
22
|
+
git: 0.1,
|
|
23
|
+
dependency: 0.15,
|
|
24
|
+
dependent: 0.15,
|
|
25
|
+
test: 0.1,
|
|
26
|
+
type: 0.05,
|
|
27
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "second-opinion-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for getting code reviews from Gemini/GPT in Claude Code",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mcp",
|
|
7
|
+
"claude",
|
|
8
|
+
"claude-code",
|
|
9
|
+
"code-review",
|
|
10
|
+
"gemini",
|
|
11
|
+
"openai",
|
|
12
|
+
"gpt"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/noelweichbrodt/second-opinion.git"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/noelweichbrodt/second-opinion#readme",
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/noelweichbrodt/second-opinion/issues"
|
|
22
|
+
},
|
|
23
|
+
"type": "module",
|
|
24
|
+
"main": "dist/index.js",
|
|
25
|
+
"bin": {
|
|
26
|
+
"second-opinion-mcp": "dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"templates",
|
|
31
|
+
"scripts",
|
|
32
|
+
"second-opinion.skill.md"
|
|
33
|
+
],
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=18"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc",
|
|
39
|
+
"start": "node dist/index.js",
|
|
40
|
+
"dev": "tsx src/index.ts",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"test:watch": "vitest",
|
|
43
|
+
"prepublishOnly": "npm run build",
|
|
44
|
+
"postinstall": "node scripts/install-config.js",
|
|
45
|
+
"install-config": "node scripts/install-config.js"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@google/generative-ai": "^0.21.0",
|
|
49
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
50
|
+
"glob": "^11.0.0",
|
|
51
|
+
"openai": "^4.77.0",
|
|
52
|
+
"zod": "^3.24.1"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/node": "^22.10.5",
|
|
56
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
57
|
+
"tsx": "^4.19.2",
|
|
58
|
+
"typescript": "^5.7.2",
|
|
59
|
+
"vitest": "^4.0.18"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import * as os from "os";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const projectRoot = path.join(__dirname, "..");
|
|
10
|
+
|
|
11
|
+
// Paths for review instructions template
|
|
12
|
+
const configDir = path.join(os.homedir(), ".config", "second-opinion");
|
|
13
|
+
const templatePath = path.join(projectRoot, "templates", "second-opinion.md");
|
|
14
|
+
const targetPath = path.join(configDir, "second-opinion.md");
|
|
15
|
+
|
|
16
|
+
// Paths for slash command
|
|
17
|
+
const claudeCommandsDir = path.join(os.homedir(), ".claude", "commands");
|
|
18
|
+
const skillPath = path.join(projectRoot, "second-opinion.skill.md");
|
|
19
|
+
const commandTargetPath = path.join(claudeCommandsDir, "second-opinion.md");
|
|
20
|
+
|
|
21
|
+
// Create config directory if it doesn't exist
|
|
22
|
+
if (!fs.existsSync(configDir)) {
|
|
23
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
24
|
+
console.log(`Created config directory: ${configDir}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Copy template if target doesn't exist
|
|
28
|
+
if (!fs.existsSync(targetPath)) {
|
|
29
|
+
fs.copyFileSync(templatePath, targetPath);
|
|
30
|
+
console.log(`Installed default review instructions to: ${targetPath}`);
|
|
31
|
+
} else {
|
|
32
|
+
console.log(`Review instructions already exist at: ${targetPath}`);
|
|
33
|
+
console.log("Skipping to preserve your customizations.");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Install slash command to ~/.claude/commands/
|
|
37
|
+
if (!fs.existsSync(claudeCommandsDir)) {
|
|
38
|
+
fs.mkdirSync(claudeCommandsDir, { recursive: true });
|
|
39
|
+
console.log(`Created Claude commands directory: ${claudeCommandsDir}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (fs.existsSync(skillPath)) {
|
|
43
|
+
fs.copyFileSync(skillPath, commandTargetPath);
|
|
44
|
+
console.log(`Installed /second-opinion command to: ${commandTargetPath}`);
|
|
45
|
+
} else {
|
|
46
|
+
console.log(`Warning: Skill file not found at ${skillPath}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log("\nSetup complete! Make sure to set your API keys:");
|
|
50
|
+
console.log(" export GEMINI_API_KEY=your-key # For Gemini");
|
|
51
|
+
console.log(" export OPENAI_API_KEY=your-key # For GPT");
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: second-opinion
|
|
3
|
+
description: Get feedback from Gemini or GPT on your current work
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Second Opinion Skill
|
|
8
|
+
|
|
9
|
+
Get a code review or custom feedback from an external LLM (Gemini or GPT).
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
- `/second-opinion` - Default code review from Gemini
|
|
14
|
+
- `/second-opinion [task]` - Custom task for Gemini
|
|
15
|
+
- `/second-opinion openai [task]` - Use GPT instead
|
|
16
|
+
- `/second-opinion gemini [task]` - Explicitly use Gemini
|
|
17
|
+
|
|
18
|
+
## Instructions
|
|
19
|
+
|
|
20
|
+
When invoked, parse the arguments:
|
|
21
|
+
|
|
22
|
+
1. Check if first word is a provider (`gemini` or `openai`). If not, default to `gemini`.
|
|
23
|
+
2. Remaining text becomes the custom task (if any).
|
|
24
|
+
3. Derive a session name from the work done in this conversation (e.g., "add-auth-flow", "fix-login-bug").
|
|
25
|
+
|
|
26
|
+
Call the `mcp__second-opinion__second_opinion` tool with:
|
|
27
|
+
- `provider`: "gemini" or "openai"
|
|
28
|
+
- `projectPath`: The current working directory (absolute path)
|
|
29
|
+
- `sessionName`: Descriptive name based on the work done
|
|
30
|
+
- `customPrompt`: The user's task text (if provided)
|
|
31
|
+
|
|
32
|
+
After the review completes, report:
|
|
33
|
+
- Path to the output file
|
|
34
|
+
- Brief summary of key findings
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Code Review Instructions
|
|
2
|
+
|
|
3
|
+
You are a code reviewer providing a second opinion on code changes made during a Claude Code session.
|
|
4
|
+
|
|
5
|
+
## Your Role
|
|
6
|
+
|
|
7
|
+
- Review the code changes objectively and thoroughly
|
|
8
|
+
- Identify potential issues, bugs, security vulnerabilities, or improvements
|
|
9
|
+
- Be constructive and specific in your feedback
|
|
10
|
+
- Consider the conversation context to understand what was requested
|
|
11
|
+
|
|
12
|
+
## Review Focus
|
|
13
|
+
|
|
14
|
+
1. **Correctness**: Does the code do what it's supposed to do?
|
|
15
|
+
2. **Security**: Are there any security vulnerabilities (injection, XSS, auth issues, etc.)?
|
|
16
|
+
3. **Performance**: Are there obvious performance issues or inefficiencies?
|
|
17
|
+
4. **Maintainability**: Is the code clear, well-organized, and easy to understand?
|
|
18
|
+
5. **Error Handling**: Are errors handled appropriately?
|
|
19
|
+
6. **Edge Cases**: Are edge cases considered?
|
|
20
|
+
|
|
21
|
+
## Output Format
|
|
22
|
+
|
|
23
|
+
Structure your review as follows:
|
|
24
|
+
|
|
25
|
+
### Summary
|
|
26
|
+
2-3 sentences summarizing the changes and your overall assessment.
|
|
27
|
+
|
|
28
|
+
### Critical Issues
|
|
29
|
+
Issues that should be fixed before merging (if any):
|
|
30
|
+
- Issue description
|
|
31
|
+
- Why it matters
|
|
32
|
+
- Suggested fix
|
|
33
|
+
|
|
34
|
+
### Suggestions
|
|
35
|
+
Improvements that would be nice to have:
|
|
36
|
+
- What could be improved
|
|
37
|
+
- Why it would help
|
|
38
|
+
|
|
39
|
+
### Questions
|
|
40
|
+
Things that are unclear or might need clarification:
|
|
41
|
+
- Question about intent or implementation
|
|
42
|
+
|
|
43
|
+
### What's Done Well
|
|
44
|
+
Positive aspects of the implementation:
|
|
45
|
+
- Good practices observed
|
|
46
|
+
- Clever solutions
|
|
47
|
+
|
|
48
|
+
## Guidelines
|
|
49
|
+
|
|
50
|
+
- Be specific: Reference file names and line numbers when possible
|
|
51
|
+
- Be constructive: Don't just point out problems, suggest solutions
|
|
52
|
+
- Be proportionate: Don't nitpick minor style issues if there are bigger concerns
|
|
53
|
+
- Consider context: The conversation shows what was asked for - review against those requirements
|
|
54
|
+
- Be honest: If the code looks good, say so
|