ai_pr_reviewer 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a61d180df947862b3037057a7e44f07f1a59d085fd304fbe1d326c4be43ac600
4
+ data.tar.gz: 3e781555225208fbda19fd0e54cfcc086f96a2e1c107335fda60ff900cb01b62
5
+ SHA512:
6
+ metadata.gz: 2ab25a7a08407add6ec385e8a72924efb82d8e12c94a80cacb067ed5e3c2c27068c4feb3edd7757d37753065708eac52cec1ec9376457ebefd1ef2f1eb19765c
7
+ data.tar.gz: 4840a32311ce1d7595cd85e7a4038e83765cece942aeb99a3e97320af85be9b021c869d45e7ba3d9e8174001e3304cdfc4acb9fcab4fe05092cf450fdc5d5f1d
data/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # AI PR Reviewer
2
+
3
+ An AI-powered Pull Request reviewer that uses the Perplexity API to provide actionable feedback on your pull requests.
4
+
5
+ ## Features
6
+
7
+ - Automatically reviews pull requests using AI
8
+ - Provides actionable feedback on code quality, bugs, security, and performance
9
+ - Adds inline comments for specific code feedback
10
+ - Integrates with GitHub Actions for automated reviews
11
+ - Supports both manual and automated workflows
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ ```ruby
18
+ gem 'ai_pr_reviewer'
19
+ ```
20
+
21
+ And then execute:
22
+ ```bash
23
+ $ bundle install
24
+ ```
25
+
26
+ Or install it globally:
27
+ ```bash
28
+ $ gem install ai_pr_reviewer
29
+ ```
30
+
31
+ ## Configuration
32
+
33
+ 1. For local usage, create a `.env` file in your project root with the following variables:
34
+ ```
35
+ GITHUB_TOKEN=your_github_token_here
36
+ GITHUB_REPOSITORY=owner/repo_name
37
+ PERPLEXITY_API_KEY=your_perplexity_api_key_here
38
+ ```
39
+
40
+ 2. For GitHub Actions, add these secrets to your repository:
41
+ - `GITHUB_TOKEN` (automatically provided by GitHub Actions)
42
+ - `PERPLEXITY_API_KEY` (add this in your repository secrets)
43
+ - Run `ai_pr_review install` to automatically install a GitHub Actions workflow file in your repository's `.github/workflows` directory.
44
+
45
+
46
+ ## Usage
47
+
48
+ ### Command Line
49
+
50
+ Review a specific PR:
51
+ ```bash
52
+ $ ai-pr-review PR_NUMBER
53
+ ```
54
+
55
+ ### GitHub Actions
56
+
57
+ The installed workflow will automatically review pull requests when:
58
+ - A new PR is opened
59
+ - An existing PR is updated
60
+ - A PR is reopened
61
+
62
+ ## Development
63
+
64
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
65
+
66
+ ## Contributing
67
+
68
+ Bug reports and pull requests are welcome on GitHub at https://github.com/brilyuhns/ai_pr_reviewer.
69
+
70
+ ## License
71
+
72
+ The gem is available as open source under the terms of the MIT License.
data/exe/ai-pr-review ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "ai_pr_reviewer"
4
+ require_relative "../lib/pr_reviewer"
5
+
6
+ if ARGV.empty?
7
+ puts "Usage:"
8
+ puts " ai-pr-review <pr_number> # Review a specific PR"
9
+ puts " ai-pr-review install # Install GitHub Actions workflow"
10
+ exit 1
11
+ end
12
+
13
+ if ARGV[0] == "install"
14
+ if AiPrReviewer.install_github_actions
15
+ exit 0
16
+ else
17
+ exit 1
18
+ end
19
+ end
20
+
21
+ reviewer = PRReviewer.new
22
+ reviewer.review_pr(ARGV[0].to_i)
@@ -0,0 +1,3 @@
1
+ module AiPrReviewer
2
+ VERSION = "0.1.4"
3
+ end
@@ -0,0 +1,34 @@
1
+ require "ai_pr_reviewer/version"
2
+ require "fileutils"
3
+
4
+ module AiPrReviewer
5
+ class Error < StandardError; end
6
+
7
+ class << self
8
+ def install_github_actions
9
+ source = File.join(File.dirname(__FILE__), '..', 'templates', 'github', 'workflows', 'ai_pr_review.yml')
10
+ target_dir = File.join(Dir.pwd, '.github', 'workflows')
11
+ target = File.join(target_dir, 'ai_pr_review.yml')
12
+
13
+ FileUtils.mkdir_p(target_dir)
14
+ if File.exist?(target)
15
+ puts "GitHub Actions workflow already exists at #{target}"
16
+ false
17
+ else
18
+ FileUtils.cp(source, target)
19
+ puts "Successfully installed GitHub Actions workflow at #{target}"
20
+ true
21
+ end
22
+ rescue StandardError => e
23
+ puts "Error installing GitHub Actions workflow: #{e.message}"
24
+ false
25
+ end
26
+ end
27
+ end
28
+
29
+ # Add post-install hook
30
+ Gem.post_install do |installer|
31
+ if installer.spec.name == 'ai_pr_reviewer'
32
+ AiPrReviewer.install_github_actions
33
+ end
34
+ end
@@ -0,0 +1,187 @@
1
+ require 'octokit'
2
+ require 'dotenv'
3
+ require 'json'
4
+ require 'httparty'
5
+
6
+ # Load environment variables
7
+ Dotenv.load('.env')
8
+
9
+ class PRReviewer
10
+ def initialize
11
+ @github_client = Octokit::Client.new(access_token: ENV['GITHUB_TOKEN'])
12
+ @repository = ENV['GITHUB_REPOSITORY']
13
+ end
14
+
15
+ def review_pr(pr_number)
16
+ begin
17
+ # Fetch PR details
18
+ pr = @github_client.pull_request(@repository, pr_number)
19
+ puts "Reviewing PR ##{pr_number}: #{pr.title}"
20
+
21
+ # Get PR diff
22
+ diff = @github_client.pull_request(@repository, pr_number, accept: 'application/vnd.github.v3.diff')
23
+
24
+ # Generate summary
25
+ summary = generate_summary(pr, diff)
26
+ puts summary
27
+
28
+ # Get review from Perplexity
29
+ review = get_perplexity_review(pr, diff)
30
+
31
+ # Post comments
32
+ post_review_comments(pr_number, summary, review)
33
+
34
+ puts "Review completed successfully!"
35
+ rescue Octokit::Error => e
36
+ puts "GitHub API Error: #{e.message}"
37
+ rescue StandardError => e
38
+ puts "Error: #{e.message}"
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def generate_summary(pr, diff)
45
+ "## PR Summary\n\n" \
46
+ "**Title:** #{pr.title}\n" \
47
+ "**Author:** #{pr.user.login}\n" \
48
+ "**Changed Files:** #{pr.changed_files}\n" \
49
+ "**Additions:** #{pr.additions}\n" \
50
+ "**Deletions:** #{pr.deletions}\n"
51
+ end
52
+
53
+ def system_prompt
54
+ <<~PROMPT
55
+ You are an experienced code reviewer. Provide concise, constructive, actionable feedback.
56
+ I REQUIRE EXTREME BREVITY - use minimal words and only include actionable feedback.
57
+ As a code reviewer, focus on actionable feedback around the following areas:
58
+ 1. Code quality and best practices: SKIP SECTION IF THE PR DOES NOT IMPACT CODE QUALITY AND BEST PRACTICES OR CREATE NEW CODE QUALITY AND BEST PRACTICES
59
+ 2. Potential bugs or issues: SKIP SECTION IF THE PR DOES NOT IMPACT POTENTIAL BUGS OR ISSUES OR CREATE NEW POTENTIAL BUGS OR ISSUES
60
+ 3. Security concerns: SKIP SECTION IF THE PR DOES NOT IMPACT SECURITY OR CREATE NEW SECURITY CONCERNS
61
+ 4. Performance implications: SKIP SECTION IF THE PR DOES NOT IMPACT PERFORMANCE OR CREATE NEW PERFORMANCE IMPLICATIONS
62
+ 5. Suggestions for improvement: SKIP SECTION IF THE PR DOES NOT IMPACT SUGGESTIONS FOR IMPROVEMENT OR CREATE NEW SUGGESTIONS FOR IMPROVEMENT
63
+ ONLY PROVIDE ACTIONABLE FEEDBACK. Do not provide feedback that is not actionable. If for any of the areas the feedback is not actionable, skip the area.
64
+ CRITICAL INSTRUCTIONS:
65
+ - Please format your response as a JSON object with the following structure. Ensure the feedback is in markdown format:
66
+ {
67
+ "summary": "overall review summary",
68
+ "inline_comments": [
69
+ { "path": "file_path", "line": line_number, "body": "comment text" }
70
+ ],
71
+ "general_feedback": "general feedback text",
72
+ "performance_feedback": "performance feedback text",
73
+ "security_feedback": "security feedback text",
74
+ "testing_recommendation": "testing recommendation text"
75
+ }
76
+ - USE PR description as context if available
77
+ - NEVER include a section if it has no critical, actionable feedback.
78
+ - NEVER include phrases like "No X issues found" - simply omit that section.
79
+ - Use bullet points for all feedback items.
80
+ - Sort feedback by criticality (highest first).
81
+ - ONLY flag issues that are definitively problems, not speculative concerns.
82
+ PROMPT
83
+ end
84
+
85
+ def user_prompt(pr, diff)
86
+ <<~PROMPT
87
+ As a code reviewer, provide feedback for the following PR:
88
+ Pull Request Details:
89
+ Title: #{pr.title}
90
+ Description: #{pr.body}
91
+
92
+ Changes (diff):
93
+ #{diff}
94
+ PROMPT
95
+ end
96
+
97
+ def get_perplexity_review(pr, diff)
98
+ begin
99
+ api_key = ENV['PERPLEXITY_API_KEY']
100
+ raise "Perplexity API key not found in environment variables" if api_key.nil?
101
+
102
+ # Prepare the prompt for the review
103
+ prompt = user_prompt(pr, diff)
104
+
105
+ # Make request to Perplexity API
106
+ response = HTTParty.post(
107
+ 'https://api.perplexity.ai/chat/completions',
108
+ headers: {
109
+ 'Authorization' => "Bearer #{api_key}",
110
+ 'Content-Type' => 'application/json'
111
+ },
112
+ body: {
113
+ model: 'sonar-pro',
114
+ messages: [
115
+ { role: 'system', content: system_prompt },
116
+ { role: 'user', content: prompt }
117
+ ]
118
+ }.to_json
119
+ )
120
+
121
+ if response.success?
122
+ result = JSON.parse(response.body)
123
+ review_text = result['choices'][0]['message']['content']
124
+ # p "## AI Review\n\n#{review_text}"
125
+ else
126
+ raise "Perplexity API Error: #{response.code} - #{response.body}"
127
+ end
128
+ rescue StandardError => e
129
+ puts "Error generating review: #{e.message}"
130
+ "## AI Review\n\nError generating review: #{e.message}"
131
+ end
132
+ end
133
+
134
+ def post_review_comments(pr_number, summary, review)
135
+ begin
136
+ # Parse the review JSON
137
+ review_data = JSON.parse(review)
138
+
139
+ # Create the main review body with general feedback
140
+ main_comment = []
141
+ main_comment << "# AI Review Summary\n\n#{review_data['summary']}" if review_data['summary']
142
+ main_comment << "\n\n## General Feedback\n#{review_data['general_feedback']}" if review_data['general_feedback']
143
+ main_comment << "\n\n## Performance Feedback\n#{review_data['performance_feedback']}" if review_data['performance_feedback']
144
+ main_comment << "\n\n## Security Feedback\n#{review_data['security_feedback']}" if review_data['security_feedback']
145
+ main_comment << "\n\n## Testing Recommendations\n#{review_data['testing_recommendation']}" if review_data['testing_recommendation']
146
+
147
+ # Get the PR's latest commit SHA
148
+ pr_commits = @github_client.pull_request_commits(@repository, pr_number)
149
+ latest_commit_sha = pr_commits.last.sha
150
+
151
+ # Prepare review parameters
152
+ review_params = {
153
+ commit_id: latest_commit_sha,
154
+ body: main_comment.join,
155
+ event: 'COMMENT'
156
+ }
157
+
158
+ # Add inline comments if they exist
159
+ if review_data['inline_comments']&.any?
160
+ review_params[:comments] = review_data['inline_comments'].map do |comment|
161
+ {
162
+ path: comment['path'],
163
+ position: comment['line'],
164
+ body: comment['body']
165
+ }
166
+ end
167
+ end
168
+
169
+ # Create a single review with both body and inline comments
170
+ @github_client.create_pull_request_review(
171
+ @repository,
172
+ pr_number,
173
+ review_params
174
+ )
175
+
176
+ puts "Successfully posted review!"
177
+ rescue JSON::ParserError => e
178
+ puts "Error parsing review JSON: #{e.message}"
179
+ # Fallback to posting the raw review if JSON parsing fails
180
+ @github_client.add_comment(@repository, pr_number, [summary, review].join("\n\n"))
181
+ rescue Octokit::Error => e
182
+ puts "GitHub API Error: #{e.message}"
183
+ rescue StandardError => e
184
+ puts "Error posting review: #{e.message}"
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,67 @@
1
+ name: PR Reviewer
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, synchronize]
6
+ issue_comment:
7
+ types: [created]
8
+ workflow_dispatch:
9
+ inputs:
10
+ pr_number:
11
+ description: 'PR number'
12
+ required: true
13
+ default: ''
14
+
15
+ jobs:
16
+ review:
17
+ if: |
18
+ (github.event_name == 'pull_request') ||
19
+ (github.event_name == 'issue_comment' &&
20
+ github.event.issue.pull_request &&
21
+ contains(github.event.comment.body, '/review'))
22
+ runs-on: ubuntu-latest
23
+ # defaults:
24
+ # run:
25
+ # working-directory: ./ai_pr_reviewer # this is the directory of the pr_reviewer.rb file
26
+ # shell: bash
27
+ permissions:
28
+ contents: read
29
+ pull-requests: write
30
+ issues: write
31
+
32
+ steps:
33
+ - uses: actions/checkout@v4
34
+ with:
35
+ fetch-depth: 0
36
+
37
+ - name: Set up Ruby
38
+ uses: ruby/setup-ruby@v1
39
+ with:
40
+ ruby-version: '3.2.2'
41
+ bundler-cache: false
42
+
43
+ - name: Install dependencies
44
+ run: |
45
+ gem install specific_install
46
+ gem specific_install https://github.com/brilyuhns/ai_pr_reviewer.git
47
+
48
+ # - name: Cache dependencies
49
+ # uses: actions/cache@v3
50
+ # with:
51
+ # path: ai_pr_reviewer/vendor/bundle
52
+ # key: ${{ runner.os }}-gems-${{ hashFiles('ai_pr_reviewer/Gemfile.lock') }}
53
+ # restore-keys: |
54
+ # ${{ runner.os }}-gems-
55
+
56
+ - name: Run PR Review
57
+ env:
58
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
59
+ PERPLEXITY_API_KEY: ${{ secrets.PERPLEXITY_API_KEY }}
60
+ GITHUB_REPOSITORY: ${{ github.repository }}
61
+ run: |
62
+ if [[ "${{ github.event_name }}" == "pull_request" ]]; then
63
+ PR_NUMBER="${{ github.event.pull_request.number }}"
64
+ else
65
+ PR_NUMBER="${{ github.event.issue.number }}"
66
+ fi
67
+ ai-pr-review $PR_NUMBER
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ai_pr_reviewer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.4
5
+ platform: ruby
6
+ authors:
7
+ - Your Name
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-06-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: octokit
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '6.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '6.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: dotenv
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.8'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.8'
41
+ - !ruby/object:Gem::Dependency
42
+ name: json
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.6'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: httparty
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.21'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.21'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '13.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '13.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.21'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.21'
111
+ description: Automatically review Pull Requests using AI to provide actionable feedback,
112
+ inline comments, and suggestions for improvement
113
+ email:
114
+ - your.email@example.com
115
+ executables:
116
+ - ai-pr-review
117
+ extensions: []
118
+ extra_rdoc_files: []
119
+ files:
120
+ - README.md
121
+ - exe/ai-pr-review
122
+ - lib/ai_pr_reviewer.rb
123
+ - lib/ai_pr_reviewer/version.rb
124
+ - lib/pr_reviewer.rb
125
+ - templates/github/workflows/ai_pr_review.yml
126
+ homepage: https://github.com/yourusername/ai_pr_reviewer
127
+ licenses:
128
+ - MIT
129
+ metadata:
130
+ homepage_uri: https://github.com/yourusername/ai_pr_reviewer
131
+ source_code_uri: https://github.com/yourusername/ai_pr_reviewer
132
+ changelog_uri: https://github.com/yourusername/ai_pr_reviewer/blob/main/CHANGELOG.md
133
+ post_install_message:
134
+ rdoc_options: []
135
+ require_paths:
136
+ - lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: 2.7.0
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ requirements: []
148
+ rubygems_version: 3.4.10
149
+ signing_key:
150
+ specification_version: 4
151
+ summary: AI-powered Pull Request reviewer using Perplexity API
152
+ test_files: []