mirror of
https://github.com/pacnpal/Claude-code-review.git
synced 2025-12-19 20:01:05 -05:00
Initial commit
This commit is contained in:
31
.github/workflows/release.yml
vendored
Normal file
31
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
# .github/workflows/release.yml
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: dist/*
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -128,3 +128,5 @@ dist
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
.DS_Store
|
||||
*.lockb
|
||||
|
||||
137
README.md
Normal file
137
README.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# Claude Code Review Action
|
||||
|
||||
A GitHub Action that performs automated code reviews using Claude AI.
|
||||
|
||||
## Features
|
||||
- Analyzes code changes in pull requests
|
||||
- Provides detailed feedback on code quality
|
||||
- Identifies potential issues and suggests improvements
|
||||
- Checks for security issues and best practices
|
||||
|
||||
## Usage
|
||||
|
||||
Add this to your GitHub workflow file (e.g. `.github/workflows/review.yml`):
|
||||
|
||||
```yaml
|
||||
name: Code Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
|
||||
jobs:
|
||||
review:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: your-username/claude-code-review-action@v1
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
anthropic-key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
pr-number: ${{ github.event.pull_request.number }}
|
||||
```
|
||||
|
||||
## Setup
|
||||
|
||||
1. Create repository secret `ANTHROPIC_API_KEY` with your Claude API key from Anthropic
|
||||
2. The `GITHUB_TOKEN` is automatically provided by GitHub Actions
|
||||
|
||||
## Inputs
|
||||
|
||||
| Input | Description | Required | Default |
|
||||
|-------|-------------|----------|---------|
|
||||
| `github-token` | GitHub token for API access | Yes | N/A |
|
||||
| `anthropic-key` | Anthropic API key for Claude | Yes | N/A |
|
||||
| `pr-number` | Pull request number to review | Yes | N/A |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Output | Description |
|
||||
|--------|-------------|
|
||||
| `diff_size` | Size of the relevant code changes |
|
||||
| `review` | Generated code review content |
|
||||
|
||||
## Review Format
|
||||
|
||||
The action provides detailed code reviews covering:
|
||||
|
||||
1. Potential conflicts with existing codebase
|
||||
2. Code correctness and potential bugs
|
||||
3. Security vulnerabilities and risks
|
||||
4. Performance implications
|
||||
5. Maintainability and readability issues
|
||||
6. Adherence to best practices
|
||||
7. Suggestions for improvements
|
||||
|
||||
Each issue found includes:
|
||||
- Clear problem explanation
|
||||
- Severity rating (Critical/High/Medium/Low)
|
||||
- Specific recommendations
|
||||
- Code examples where helpful
|
||||
|
||||
## Example Review
|
||||
|
||||
```markdown
|
||||
# Claude Code Review
|
||||
|
||||
1. **Potential conflicts with existing codebase**:
|
||||
- No apparent conflicts identified
|
||||
|
||||
2. **Code correctness and potential bugs**:
|
||||
- **Medium Severity**: Potential null pointer in user handling
|
||||
- Recommendation: Add null check before accessing user object
|
||||
|
||||
3. **Security vulnerabilities and risks**:
|
||||
- **High Severity**: SQL injection vulnerability in query construction
|
||||
- Recommendation: Use parameterized queries
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
1. Clone the repository
|
||||
2. Install dependencies:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
3. Make changes to `action.js`
|
||||
|
||||
4. Build the action:
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
5. Run tests:
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please:
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Run tests
|
||||
5. Submit a pull request
|
||||
|
||||
## License
|
||||
|
||||
MIT License - see the [LICENSE](LICENSE) file for details
|
||||
|
||||
## Support
|
||||
|
||||
- Open an issue for bugs/feature requests
|
||||
- Submit a PR to contribute
|
||||
- Contact maintainers for other questions
|
||||
|
||||
## Changelog
|
||||
|
||||
See [CHANGELOG.md](CHANGELOG.md) for release history
|
||||
211
action.js
Normal file
211
action.js
Normal file
@@ -0,0 +1,211 @@
|
||||
// action.js
|
||||
const core = require('@actions/core');
|
||||
const github = require('@actions/github');
|
||||
const { exec } = require('@actions/exec');
|
||||
|
||||
async function getPRDetails(octokit, context, prNumber) {
|
||||
try {
|
||||
console.log(`Getting details for PR #${prNumber}`);
|
||||
|
||||
// Get PR info
|
||||
const { data: pr } = await octokit.rest.pulls.get({
|
||||
...context.repo,
|
||||
pull_number: parseInt(prNumber)
|
||||
});
|
||||
|
||||
return {
|
||||
number: pr.number,
|
||||
base: {
|
||||
sha: pr.base.sha,
|
||||
ref: pr.base.ref
|
||||
},
|
||||
head: {
|
||||
sha: pr.head.sha,
|
||||
ref: pr.head.ref
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to get PR details: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function setupGitConfig() {
|
||||
// Configure git to fetch PR refs
|
||||
await exec('git', ['config', '--local', '--add', 'remote.origin.fetch', '+refs/pull/*/head:refs/remotes/origin/pr/*']);
|
||||
await exec('git', ['fetch', 'origin']);
|
||||
}
|
||||
|
||||
async function getDiff(baseSha, headSha) {
|
||||
let diffContent = '';
|
||||
|
||||
try {
|
||||
// Get the full diff with context
|
||||
await exec('git', ['diff', '-U10', baseSha, headSha], {
|
||||
listeners: {
|
||||
stdout: (data) => {
|
||||
diffContent += data.toString();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Filter for relevant files
|
||||
const lines = diffContent.split('\n');
|
||||
let filtered = '';
|
||||
let keep = false;
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('diff --git')) {
|
||||
keep = false;
|
||||
// Check if file type should be included
|
||||
if (line.match(/\.(js|ts|py|cpp|h|java|cs)$/) &&
|
||||
!line.match(/(package-lock\.json|yarn\.lock|\.md|\.json)/)) {
|
||||
keep = true;
|
||||
}
|
||||
}
|
||||
if (keep) {
|
||||
filtered += line + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
return filtered;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to generate diff: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function analyzeWithClaude(diffContent, anthropicKey) {
|
||||
if (!diffContent.trim()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const prompt = `You are performing a code review. Please analyze this code diff and provide a thorough review that covers:
|
||||
|
||||
1. Potential conflicts with existing codebase
|
||||
2. Code correctness and potential bugs
|
||||
3. Security vulnerabilities or risks
|
||||
4. Performance implications
|
||||
5. Maintainability and readability issues
|
||||
6. Adherence to best practices and coding standards
|
||||
7. Suggestions for improvements
|
||||
|
||||
For each issue found:
|
||||
- Explain the problem clearly
|
||||
- Rate the severity (Critical/High/Medium/Low)
|
||||
- Provide specific recommendations for fixes
|
||||
- Include code examples where helpful
|
||||
|
||||
If no issues are found in a particular area, explicitly state that.
|
||||
|
||||
Here is the code diff to review:
|
||||
|
||||
\`\`\`
|
||||
${diffContent}
|
||||
\`\`\``;
|
||||
|
||||
try {
|
||||
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-key': anthropicKey,
|
||||
'anthropic-version': '2023-06-01'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'claude-3-sonnet-20240229',
|
||||
max_tokens: 4096,
|
||||
temperature: 0.7,
|
||||
messages: [{
|
||||
role: 'user',
|
||||
content: prompt
|
||||
}]
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (!data.content?.[0]?.text) {
|
||||
throw new Error(`API Error: ${JSON.stringify(data)}`);
|
||||
}
|
||||
|
||||
return data.content[0].text;
|
||||
} catch (error) {
|
||||
throw new Error(`Claude API error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function postReview(octokit, context, review, prNumber) {
|
||||
try {
|
||||
// Escape special characters for proper formatting
|
||||
const escapedReview = review
|
||||
.replace(/(?<=[\s\n])`([^`]+)`(?=[\s\n])/g, '\\`$1\\`')
|
||||
.replace(/```/g, '\\`\\`\\`')
|
||||
.replace(/\${/g, '\\${');
|
||||
|
||||
await octokit.rest.issues.createComment({
|
||||
...context.repo,
|
||||
issue_number: prNumber,
|
||||
body: `# Claude Code Review\n\n${escapedReview}`
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to post review: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
// Get inputs
|
||||
const token = core.getInput('github-token', { required: true });
|
||||
const anthropicKey = core.getInput('anthropic-key', { required: true });
|
||||
|
||||
// Initialize GitHub client
|
||||
const octokit = github.getOctokit(token);
|
||||
const context = github.context;
|
||||
|
||||
// Get PR number from event or input
|
||||
let prNumber;
|
||||
if (context.eventName === 'pull_request') {
|
||||
prNumber = context.payload.pull_request.number;
|
||||
} else {
|
||||
prNumber = core.getInput('pr-number', { required: true });
|
||||
}
|
||||
|
||||
// Set up git configuration
|
||||
await setupGitConfig();
|
||||
|
||||
// Get PR details
|
||||
const pr = await getPRDetails(octokit, context, prNumber);
|
||||
console.log(`Retrieved details for PR #${pr.number}`);
|
||||
|
||||
// Generate diff
|
||||
console.log('Generating diff...');
|
||||
const diff = await getDiff(pr.base.sha, pr.head.sha);
|
||||
|
||||
if (!diff) {
|
||||
console.log('No relevant changes found');
|
||||
core.setOutput('diff_size', '0');
|
||||
return;
|
||||
}
|
||||
|
||||
core.setOutput('diff_size', diff.length.toString());
|
||||
|
||||
// Analyze with Claude
|
||||
console.log('Analyzing with Claude...');
|
||||
const review = await analyzeWithClaude(diff, anthropicKey);
|
||||
|
||||
if (!review) {
|
||||
console.log('No review generated');
|
||||
return;
|
||||
}
|
||||
|
||||
// Post review
|
||||
console.log('Posting review...');
|
||||
await postReview(octokit, context, review, pr.number);
|
||||
|
||||
// Set outputs
|
||||
core.setOutput('review', review);
|
||||
|
||||
} catch (error) {
|
||||
core.setFailed(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
||||
21
action.yml
Normal file
21
action.yml
Normal file
@@ -0,0 +1,21 @@
|
||||
# action.yml
|
||||
name: 'Claude Code Review'
|
||||
description: 'Automated code review using Claude'
|
||||
inputs:
|
||||
github-token:
|
||||
description: 'GitHub token'
|
||||
required: true
|
||||
anthropic-key:
|
||||
description: 'Anthropic API key'
|
||||
required: true
|
||||
pr-number:
|
||||
description: 'Pull request number'
|
||||
required: true
|
||||
outputs:
|
||||
diff_size:
|
||||
description: 'Size of the relevant code changes'
|
||||
review:
|
||||
description: 'Generated code review'
|
||||
runs:
|
||||
using: 'node20'
|
||||
main: 'action.js'
|
||||
27
jsconfig.json
Normal file
27
jsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Enable latest features
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
}
|
||||
}
|
||||
23
package.json
Normal file
23
package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "claude-code-review-action",
|
||||
"version": "1.0.0",
|
||||
"description": "GitHub Action for code review using Claude",
|
||||
"main": "action.js",
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"build": "ncc build action.js -o dist"
|
||||
},
|
||||
"keywords": ["github", "action", "code-review", "claude"],
|
||||
"author": "PacNPal",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/github": "^6.0.0",
|
||||
"@actions/exec": "^1.1.1",
|
||||
"node-fetch": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vercel/ncc": "^0.38.3",
|
||||
"jest": "^29.7.0"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user