Initial commit

This commit is contained in:
pacnpal
2024-12-10 18:42:08 -05:00
parent ef532feee8
commit 7ba8e02de8
8 changed files with 453 additions and 0 deletions

31
.github/workflows/release.yml vendored Normal file
View 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
View File

@@ -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
View 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
View 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
View 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'

1
index.js Normal file
View File

@@ -0,0 +1 @@
console.log("Hello via Bun!");

27
jsconfig.json Normal file
View 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
View 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"
}
}