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/build-state.yml
|
||||||
.yarn/install-state.gz
|
.yarn/install-state.gz
|
||||||
.pnp.*
|
.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