Claude Auto Review #5
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Claude PR Review | |
| on: | |
| pull_request: | |
| types: [opened, synchronize] | |
| jobs: | |
| review: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v3 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v3 | |
| with: | |
| node-version: '22' | |
| - name: Install dependencies | |
| run: npm install @anthropic-ai/sdk axios @actions/github @actions/core fs-extra | |
| - name: Run Claude PR Review | |
| uses: actions/github-script@v6 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const axios = require('axios'); | |
| const fs = require('fs-extra'); | |
| const path = require('path'); | |
| const { Anthropic } = require('@anthropic-ai/sdk'); | |
| // 設定ファイルを読み込む | |
| let config = { | |
| systemPrompt: "あなたはSpringFramework, Java, Kotlin に関するバックエンド開発のプロであり、コードレビューの専門家です。PRの内容を詳細に分析し、日本語でフィードバックを提供してください。コードの問題点、改善案、ベストプラクティスについて具体的に指摘してください。特にセキュリティ、パフォーマンス、可読性、保守性の観点から評価してください。コード改善提案は具体的に示してください。最後に、PRの品質を評価し、「APPROVE」、「REQUEST_CHANGES」、「COMMENT」のいずれかで明示的に判断してください。", | |
| reviewOptions: { | |
| maxFilesToReview: 10, | |
| excludeFiles: [ | |
| "*.lock", | |
| "package-lock.json", | |
| "yarn.lock", | |
| "dist/*", | |
| "build/*", | |
| "Podfile.lock", | |
| "pubspec.lock", | |
| "Gemfile.lock", | |
| "Cargo.lock" | |
| ], | |
| includeMergeReviews: false, | |
| autoApprove: true, | |
| approveKeywords: ["APPROVE", "承認"], | |
| requestChangesKeywords: ["REQUEST_CHANGES", "変更依頼"] | |
| }, | |
| formatting: { | |
| title: "## Claude AI Review", | |
| summaryTitle: "### Summary", | |
| issuesTitle: "### Issues", | |
| suggestionsTitle: "### Suggestions", | |
| verdictTitle: "### Verdict", | |
| useSuggestionFormat: true | |
| }, | |
| model: { | |
| name: "claude-3-7-sonnet-20250219", | |
| maxTokens: 4000 | |
| } | |
| }; | |
| const anthropic = new Anthropic({ | |
| apiKey: '${{ secrets.CLAUDE_API_KEY }}', | |
| }); | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const pull_number = context.payload.pull_request.number; | |
| // Get the list of files changed in the PR | |
| const { data: files } = await github.rest.pulls.listFiles({ | |
| owner, | |
| repo, | |
| pull_number, | |
| }); | |
| // 除外ファイルパターンに一致するファイルをフィルタリング | |
| const isExcluded = (filename) => { | |
| return config.reviewOptions.excludeFiles.some(pattern => { | |
| if (pattern.includes('*')) { | |
| const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$'); | |
| return regex.test(filename); | |
| } | |
| return filename === pattern; | |
| }); | |
| }; | |
| const filesToReview = files | |
| .filter(file => !isExcluded(file.filename)) | |
| .slice(0, config.reviewOptions.maxFilesToReview); | |
| // Get the PR description and title | |
| const { data: pullRequest } = await github.rest.pulls.get({ | |
| owner, | |
| repo, | |
| pull_number, | |
| }); | |
| // Prepare a prompt for Claude with the PR information | |
| let promptContent = `Review the following GitHub Pull Request:\n\n`; | |
| promptContent += `Title: ${pullRequest.title}\n`; | |
| promptContent += `Description: ${pullRequest.body || 'No description provided.'}\n\n`; | |
| promptContent += `Changed files:\n\n`; | |
| // For each file, get the content and diff | |
| for (const file of filesToReview) { | |
| promptContent += `File: ${file.filename}\n`; | |
| promptContent += `Status: ${file.status}\n`; | |
| promptContent += `Diff:\n\`\`\`diff\n${file.patch || 'Binary file or too large to display'}\n\`\`\`\n\n`; | |
| } | |
| promptContent += `Please review this PR and provide the following feedback: | |
| 1. A summary of the changes | |
| 2. Any issues, bugs, or concerns you see in the code | |
| 3. Suggestions for improvements with concrete code examples | |
| 4. Overall assessment of the PR quality | |
| Format your response using these section headers: | |
| ${config.formatting.summaryTitle} | |
| ${config.formatting.issuesTitle} | |
| ${config.formatting.suggestionsTitle} | |
| ${config.formatting.verdictTitle} | |
| When suggesting code changes, use this format if it should be applied directly: | |
| FILE: [filename] | |
| \`\`\`suggestion | |
| [your suggested code] | |
| \`\`\` | |
| Very important: In the ${config.formatting.verdictTitle} section, clearly indicate one of these review decisions: | |
| - APPROVE - if the code is good quality and ready to merge | |
| - REQUEST_CHANGES - if there are issues that must be fixed before merging | |
| - COMMENT - if you have minor suggestions that don't block merging`; | |
| // Call Claude API | |
| try { | |
| const response = await anthropic.messages.create({ | |
| model: config.model.name, | |
| max_tokens: config.model.maxTokens, | |
| system: config.systemPrompt, | |
| messages: [ | |
| { | |
| role: 'user', | |
| content: promptContent, | |
| }, | |
| ], | |
| }); | |
| const claudeResponse = response.content[0].text; | |
| // Post Claude's response as a comment on the PR | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: pull_number, | |
| body: `${config.formatting.title}\n\n${claudeResponse}`, | |
| }); | |
| // Determine if PR should be approved or changes requested | |
| let reviewEvent = "COMMENT"; // Default to just comment | |
| if (config.reviewOptions.autoApprove) { | |
| // Check if any approve keywords are in the response | |
| const hasApproveKeyword = config.reviewOptions.approveKeywords.some(keyword => | |
| claudeResponse.includes(keyword) | |
| ); | |
| // Check if any request changes keywords are in the response | |
| const hasRequestChangesKeyword = config.reviewOptions.requestChangesKeywords.some(keyword => | |
| claudeResponse.includes(keyword) | |
| ); | |
| if (hasApproveKeyword && !hasRequestChangesKeyword) { | |
| reviewEvent = "APPROVE"; | |
| } else if (hasRequestChangesKeyword) { | |
| reviewEvent = "REQUEST_CHANGES"; | |
| } | |
| } | |
| // Submit the review with appropriate action | |
| await github.rest.pulls.createReview({ | |
| owner, | |
| repo, | |
| pull_number, | |
| body: `${config.formatting.title}\n\nSee detailed review in comments.`, | |
| event: reviewEvent | |
| }); | |
| console.log(`Claude review posted successfully with event: ${reviewEvent}`); | |
| } catch (error) { | |
| console.error('Error calling Claude API:', error); | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: pull_number, | |
| body: `${config.formatting.title}\n\nSorry, there was an error generating the review: ${error.message}`, | |
| }); | |
| } | |
| env: | |
| CLAUDE_API_KEY: ${{ secrets.CLAUDE_API_KEY }} |