diff --git a/.github/workflows/claude-code.yml b/.github/workflows/claude-code.yml new file mode 100644 index 00000000..62ca6f65 --- /dev/null +++ b/.github/workflows/claude-code.yml @@ -0,0 +1,79 @@ +name: Claude Code Review + +on: + pull_request: + branches: + - main + push: + branches: + - main + +jobs: + review: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + issues: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch full history for better diff analysis + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install Claude Code CLI + run: | + npm install -g @anthropic-ai/claude-code-cli 2>/dev/null || echo "Claude Code CLI not available, using SDK" + + - name: Install dependencies + run: | + npm install -g @anthropic-ai/sdk + npm install --save-dev @actions/core @actions/github + + - name: Get changed files + id: changed-files + run: | + if [ "${{ github.event_name }}" == "pull_request" ]; then + git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} > changed_files.txt + else + git diff --name-only HEAD^ HEAD > changed_files.txt + fi + echo "files=$(cat changed_files.txt | tr '\n' ' ')" >> $GITHUB_OUTPUT + + - name: Run Claude Code Review + id: claude-review + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + run: | + node scripts/claude-review.js + continue-on-error: true + + - name: Post PR Comment + if: github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const fs = require('fs'); + const reviewOutput = fs.readFileSync('claude-review-output.md', 'utf8'); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: reviewOutput + }); + + - name: Upload Review Artifact + uses: actions/upload-artifact@v4 + if: always() + with: + name: claude-review-results + path: claude-review-output.md + retention-days: 30 \ No newline at end of file diff --git a/package.json b/package.json index 00e2271e..b70bbeab 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,8 @@ "devDependencies": { "@typescript-eslint/eslint-plugin": "^8.30.1", "@typescript-eslint/parser": "^8.30.1", - "eslint": "^9.24.0" + "eslint": "^9.24.0", + "@actions/core": "^1.10.1", + "@actions/github": "^6.0.0" } -} +} \ No newline at end of file diff --git a/scripts/claude-review.js b/scripts/claude-review.js new file mode 100644 index 00000000..094591c7 --- /dev/null +++ b/scripts/claude-review.js @@ -0,0 +1,90 @@ +const Anthropic = require('@anthropic-ai/sdk'); +const fs = require('fs'); +const { execSync } = require('child_process'); + +const anthropic = new Anthropic({ + apiKey: process.env.ANTHROPIC_API_KEY, +}); + +async function analyzeCode() { + try { + // Get the diff + const diff = execSync('git diff HEAD^ HEAD', { encoding: 'utf-8' }); + + if (!diff.trim()) { + console.log('No changes detected.'); + fs.writeFileSync('claude-review-output.md', '## 🤖 Claude Code Review\n\nNo changes to review.'); + return; + } + + // Get changed files + const changedFiles = fs.readFileSync('changed_files.txt', 'utf-8').trim(); + + const prompt = `You are an expert code reviewer. Please analyze the following code changes and provide: + +1. **Security Issues** - Any potential security vulnerabilities +2. **Bug Detection** - Potential bugs or logic errors +3. **Code Quality** - Suggestions for improvements +4. **Best Practices** - Violations of coding standards +5. **Documentation** - Missing or inadequate documentation + +Format your response in Markdown with clear sections and severity levels (🔴 Critical, 🟡 Warning, 🟢 Info). + +Changed files: ${changedFiles} + +Git Diff: +\`\`\`diff +${diff} +\`\`\``; + + console.log('Requesting Claude code review...'); + + const message = await anthropic.messages.create({ + model: 'claude-sonnet-4-20250514', + max_tokens: 4096, + messages: [ + { + role: 'user', + content: prompt, + }, + ], + }); + + const reviewContent = message.content[0].text; + + // Format output + const output = `## 🤖 Claude Code Review + +**Model:** ${message.model} +**Analyzed Files:** ${changedFiles.split(' ').length} file(s) + +--- + +${reviewContent} + +--- + +*Review generated on ${new Date().toISOString()}* +`; + + fs.writeFileSync('claude-review-output.md', output); + console.log('✅ Claude code review completed successfully!'); + console.log(output); + + } catch (error) { + console.error('❌ Error during code review:', error.message); + + const errorOutput = `## 🤖 Claude Code Review - Error + +**Status:** Failed +**Error:** ${error.message} + +Please check the workflow logs for more details. +`; + + fs.writeFileSync('claude-review-output.md', errorOutput); + process.exit(1); + } +} + +analyzeCode(); diff --git a/scripts/format-claude-output.js b/scripts/format-claude-output.js new file mode 100644 index 00000000..05050bf4 --- /dev/null +++ b/scripts/format-claude-output.js @@ -0,0 +1,10 @@ +module.exports = function formatClaudeOutput(rawOutput) { + // Assuming rawOutput is a string containing the output from Claude Code + const lines = rawOutput.split('\n'); + const formattedOutput = lines.map(line => { + // Example formatting: trim whitespace and add a bullet point + return line.trim() ? `• ${line.trim()}` : ''; + }).filter(line => line !== '').join('\n'); + + return formattedOutput; +}; \ No newline at end of file