prreview 0.3.0 → 0.5.0
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 +4 -4
- data/.rubocop.yml +1 -0
- data/README.md +9 -4
- data/lib/prreview/version.rb +1 -1
- data/lib/prreview.rb +51 -40
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 30200431f3796fe13f702beef779421ce9eb7cbb759bf05a781374d39dc4082b
|
4
|
+
data.tar.gz: cc6bbba926af7d7632a97d17551c045ead9b95b5e6682d988de20eef46d1f06b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 44622bc990c7059044df282588707f489814ab86c6b47eff1d64379809430061de7851d7f6455212a0f5155b76331538d0fc8fe366f9f18a56cf913e2487f9a0
|
7
|
+
data.tar.gz: e5281db6c13f7b36532e663cda11eeddc0bd540a598327e7e8e4a256b24addb1879f9758f4b2150c18a86e172a3e28e4c4989c31277c4297a3577dcc40e280bf
|
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
[](https://badge.fury.io/rb/prreview)
|
2
|
+
|
1
3
|
## How to use
|
2
4
|
|
3
5
|
Install:
|
@@ -9,14 +11,14 @@ gem install prreview
|
|
9
11
|
Run this command with a PR URL:
|
10
12
|
|
11
13
|
```sh
|
12
|
-
prreview
|
14
|
+
prreview https://github.com/owner/repo/pull/123
|
13
15
|
```
|
14
16
|
|
15
17
|
Or use more options:
|
16
18
|
|
17
19
|
```
|
18
20
|
prreview --help
|
19
|
-
prreview
|
21
|
+
prreview https://github.com/owner/repo/pull/123 --all-content --prompt "Are there any security issues?"
|
20
22
|
```
|
21
23
|
|
22
24
|
Now, just paste the PR details into ChatGPT, Claude, or any LLM for review.
|
@@ -48,12 +50,15 @@ However, in the future we might add some optional integrations.
|
|
48
50
|
- Run `prreview` after you've thoroughly reviewed the PR. It works best when you understand the changes well.
|
49
51
|
- Don't hesitate to try different LLMs or refresh the response to see if something new comes up.
|
50
52
|
- Use `--all-content` and other extra options — they can significantly improve results for some PRs.
|
51
|
-
- After the LLM responds,
|
53
|
+
- After the LLM responds, to potentially uncover more issues:
|
54
|
+
- Ask "Anything else?".
|
55
|
+
- If the response has a list of problems, try responding to each of them, giving the LLM more context.
|
52
56
|
|
53
57
|
## Requirements
|
54
58
|
|
55
59
|
- Ruby
|
56
|
-
- `GITHUB_TOKEN` environment
|
60
|
+
- `GITHUB_TOKEN` environment
|
61
|
+
variable. [GitHub Docs](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)
|
57
62
|
|
58
63
|
## License
|
59
64
|
|
data/lib/prreview/version.rb
CHANGED
data/lib/prreview.rb
CHANGED
@@ -11,7 +11,7 @@ require 'optparse'
|
|
11
11
|
module Prreview
|
12
12
|
class CLI
|
13
13
|
DEFAULT_PROMPT = 'Your task is to review this pull request. Do you see any problems there?'
|
14
|
-
|
14
|
+
DEFAULT_LINKED_ISSUES_LIMIT = 5
|
15
15
|
|
16
16
|
# url or owner/repo#123 or #123
|
17
17
|
URL_REGEX = %r{
|
@@ -36,9 +36,15 @@ module Prreview
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def process
|
39
|
-
fetch_pull_request
|
40
|
-
fetch_issues
|
41
39
|
load_optional_files
|
40
|
+
begin
|
41
|
+
fetch_pull_request
|
42
|
+
fetch_linked_issues
|
43
|
+
rescue Octokit::Unauthorized
|
44
|
+
abort 'Error: Invalid GITHUB_TOKEN.'
|
45
|
+
rescue Octokit::NotFound
|
46
|
+
abort 'Error: Pull request not found.'
|
47
|
+
end
|
42
48
|
build_xml
|
43
49
|
copy_result_to_clipboard
|
44
50
|
end
|
@@ -48,30 +54,41 @@ module Prreview
|
|
48
54
|
def parse_options!
|
49
55
|
@prompt = DEFAULT_PROMPT
|
50
56
|
@include_content = false
|
51
|
-
@
|
57
|
+
@linked_issues_limit = DEFAULT_LINKED_ISSUES_LIMIT
|
52
58
|
@optional_files = []
|
53
59
|
|
54
|
-
|
55
|
-
|
60
|
+
ARGV << '--help' if ARGV.empty?
|
61
|
+
|
62
|
+
OptionParser.new do |parser|
|
63
|
+
parser.banner = <<~BAN
|
64
|
+
Usage: #{File.basename($PROGRAM_NAME)} URL [options]
|
65
|
+
|
66
|
+
Pull request URL example: https://github.com/owner/repo/pull/1
|
67
|
+
|
68
|
+
BAN
|
56
69
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
opts.on('-o', '--optional PATHS', 'Comma‑separated paths to local files (relative or absolute, e.g. docs/description.md,/etc/hosts)') do |v|
|
70
|
+
parser.on('-p', '--prompt PROMPT', 'Custom LLM prompt') { |v| @prompt = v }
|
71
|
+
parser.on('-a', '--all-content', 'Include full file contents') { @include_content = true }
|
72
|
+
parser.on('-l', '--limit LIMIT', Integer, "Limit number of issues fetched (default: #{DEFAULT_LINKED_ISSUES_LIMIT})") { |v| @linked_issues_limit = v }
|
73
|
+
parser.on('-o', '--optional PATHS', 'Comma‑separated paths to local files (relative or absolute, e.g. docs/description.md,/etc/hosts)') do |v|
|
62
74
|
@optional_files = v.split(',').map(&:strip)
|
63
75
|
end
|
64
|
-
|
65
|
-
puts
|
76
|
+
parser.on_tail('-v', '--version', 'Show version') do
|
77
|
+
puts VERSION
|
66
78
|
exit
|
67
79
|
end
|
80
|
+
parser.on_tail('-h', '--help', 'Show help') do
|
81
|
+
puts parser
|
82
|
+
exit
|
83
|
+
end
|
84
|
+
parser.parse!
|
68
85
|
end
|
69
86
|
|
70
|
-
|
87
|
+
@url = ARGV.first
|
71
88
|
end
|
72
89
|
|
73
90
|
def parse_url!
|
74
|
-
abort 'Error: Pull
|
91
|
+
abort 'Error: Pull request URL missing.' if @url.to_s.empty?
|
75
92
|
|
76
93
|
match = @url.match(URL_REGEX)
|
77
94
|
abort 'Error: Invalid URL format. See --help for usage.' unless match
|
@@ -84,11 +101,9 @@ module Prreview
|
|
84
101
|
|
85
102
|
def initialize_client
|
86
103
|
access_token = ENV.fetch('GITHUB_TOKEN', nil)
|
87
|
-
abort 'Error: GITHUB_TOKEN is not set' if access_token.to_s.empty?
|
104
|
+
abort 'Error: GITHUB_TOKEN is not set.' if access_token.to_s.empty?
|
88
105
|
|
89
106
|
@client = Octokit::Client.new(access_token:, auto_paginate: true)
|
90
|
-
rescue Octokit::Unauthorized
|
91
|
-
abort 'Error: Invalid GITHUB_TOKEN'
|
92
107
|
end
|
93
108
|
|
94
109
|
def fetch_pull_request
|
@@ -107,7 +122,7 @@ module Prreview
|
|
107
122
|
end
|
108
123
|
|
109
124
|
def fetch_file_content(path)
|
110
|
-
puts "Fetching
|
125
|
+
puts "Fetching #{path}"
|
111
126
|
|
112
127
|
content = @client.contents(@full_repo, path:, ref: @pull_request.head.sha)
|
113
128
|
decoded = Base64.decode64(content[:content])
|
@@ -116,45 +131,41 @@ module Prreview
|
|
116
131
|
'(file content not found)'
|
117
132
|
end
|
118
133
|
|
119
|
-
def
|
120
|
-
@
|
134
|
+
def fetch_linked_issues
|
135
|
+
@linked_issues = []
|
121
136
|
|
122
137
|
text = [@pull_request.body, *@comments].join("\n")
|
123
138
|
queue = extract_refs(text, URL_REGEX)
|
124
139
|
seen = Set.new
|
125
140
|
|
126
|
-
until queue.empty? || @
|
141
|
+
until queue.empty? || @linked_issues.length >= @linked_issues_limit
|
127
142
|
ref = queue.shift
|
128
143
|
key = ref[:key]
|
129
144
|
next if seen.include?(key)
|
130
145
|
|
131
146
|
seen << key
|
132
147
|
|
133
|
-
issue =
|
148
|
+
issue = fetch_linked_issue(ref)
|
134
149
|
next unless issue
|
135
150
|
|
136
|
-
@
|
151
|
+
@linked_issues << issue
|
137
152
|
|
138
153
|
new_text = [issue[:description], *issue[:comments]].join("\n")
|
139
154
|
new_refs = extract_refs(new_text, URL_REGEX).reject { |nref| seen.include?(nref[:key]) }
|
140
155
|
queue.concat(new_refs)
|
141
156
|
end
|
142
157
|
|
143
|
-
puts "Fetched #{@
|
158
|
+
puts "Fetched #{@linked_issues.length} linked issues (limit: #{@linked_issues_limit})"
|
144
159
|
end
|
145
160
|
|
146
161
|
def load_optional_files
|
147
162
|
@optional_file_contents = @optional_files.filter_map do |path|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
warn "File #{path} not found, skipping"
|
153
|
-
nil
|
154
|
-
end
|
163
|
+
puts "Reading #{path}"
|
164
|
+
abort "Optional file #{path} not found." unless File.exist?(path)
|
165
|
+
content = File.read(path)
|
166
|
+
{ filename: path, content: }
|
155
167
|
rescue StandardError => e
|
156
|
-
|
157
|
-
nil
|
168
|
+
raise "Error reading file #{path}: #{e.message}"
|
158
169
|
end
|
159
170
|
end
|
160
171
|
|
@@ -172,11 +183,11 @@ module Prreview
|
|
172
183
|
end
|
173
184
|
end
|
174
185
|
|
175
|
-
def
|
186
|
+
def fetch_linked_issue(ref)
|
176
187
|
full_repo = "#{ref[:owner]}/#{ref[:repo]}"
|
177
188
|
number = ref[:number]
|
178
189
|
|
179
|
-
puts "Fetching issue ##{number} for #{full_repo}"
|
190
|
+
puts "Fetching linked issue ##{number} for #{full_repo}"
|
180
191
|
|
181
192
|
issue = @client.issue(full_repo, number)
|
182
193
|
{
|
@@ -187,7 +198,7 @@ module Prreview
|
|
187
198
|
comments: @client.issue_comments(full_repo, number).map(&:body)
|
188
199
|
}
|
189
200
|
rescue Octokit::NotFound
|
190
|
-
|
201
|
+
warn "Linked issue #{number} for #{full_repo} not found, skipping"
|
191
202
|
nil
|
192
203
|
end
|
193
204
|
|
@@ -214,8 +225,8 @@ module Prreview
|
|
214
225
|
end
|
215
226
|
end
|
216
227
|
|
217
|
-
@
|
218
|
-
x.
|
228
|
+
@linked_issues.each do |issue|
|
229
|
+
x.linked_issue do
|
219
230
|
x.repo issue[:full_repo]
|
220
231
|
x.number issue[:number]
|
221
232
|
x.title issue[:title]
|
@@ -249,7 +260,7 @@ module Prreview
|
|
249
260
|
|
250
261
|
def copy_result_to_clipboard
|
251
262
|
Clipboard.copy(@xml)
|
252
|
-
puts 'XML prompt generated and copied to clipboard.'
|
263
|
+
puts 'XML prompt generated and copied to your clipboard.'
|
253
264
|
end
|
254
265
|
end
|
255
266
|
end
|