prreview 0.2.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 700fa5c2803277c3047957a7ed81f605baaa2efebacb99c367331e3f88f866c5
4
- data.tar.gz: 1bdf5fe31c583de5a81dd8a0fccbd5f7a5e02640a6c18ba8697c92bf086f8309
3
+ metadata.gz: 714f7802004de97bfd9e4defb9f447ad5b445ea77e12b8f04123218a6684549b
4
+ data.tar.gz: ce62752c1d1d11c13f432e0c74df032873088d75018a13dec4dfc658b5ed6c73
5
5
  SHA512:
6
- metadata.gz: 3e4e769b47652d76c065185661d6593507d1225282ab713e3145a0dcfb6ec6160dec0410a78df1c3ecd7e049a799f673a29651959ff755b95e54f34d5281ce79
7
- data.tar.gz: ab68d24845e6ac8677383bfdc971fea1b957942327ddf7b503f9558dec5593235d7e74391d986ce9698c4102409064e9b505ac6e3c9b7b0426dd9dfe019aef9c
6
+ metadata.gz: 7526eaadf9570e7ad2967a2e003989b3126bff0ae2d0907d4d4056f92b90f8e2e7dff91a17cef9cd8a6cd81625d20a0cf27dcb378a348f6e1f7708124952011f
7
+ data.tar.gz: b9a446c787e170bb3998211173e198d5cb67480512446ddbb9f0a7620a8233ec692f8493bed7c1e1b4a181968c8e6b1ee07f1a4838ddd515c300ef60ef18bddf
data/.rubocop.yml CHANGED
@@ -1,5 +1,6 @@
1
1
  AllCops:
2
2
  NewCops: enable
3
+ SuggestExtensions: false
3
4
 
4
5
  Metrics/AbcSize:
5
6
  Enabled: false
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![Gem Version](https://badge.fury.io/rb/prreview.svg)](https://badge.fury.io/rb/prreview)
2
+
1
3
  ## How to use
2
4
 
3
5
  Install:
@@ -44,10 +46,13 @@ However, in the future we might add some optional integrations.
44
46
 
45
47
  ## Tips
46
48
 
49
+ - A well-written PR and linked issue description make a big difference — and are good practice anyway.
47
50
  - Run `prreview` after you've thoroughly reviewed the PR. It works best when you understand the changes well.
48
51
  - Don't hesitate to try different LLMs or refresh the response to see if something new comes up.
49
52
  - Use `--all-content` and other extra options — they can significantly improve results for some PRs.
50
- - After the LLM responds, ask "Anything else?" to potentially uncover more issues.
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.
51
56
 
52
57
  ## Requirements
53
58
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Prreview
4
- VERSION = '0.2.0'
4
+ VERSION = '0.4.0'
5
5
  end
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
- DEFAULT_ISSUES_LIMIT = 10
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,9 @@ module Prreview
36
36
  end
37
37
 
38
38
  def process
39
- fetch_pull_request
40
- fetch_issues
41
39
  load_optional_files
40
+ fetch_pull_request
41
+ fetch_linked_issues
42
42
  build_xml
43
43
  copy_result_to_clipboard
44
44
  end
@@ -48,26 +48,31 @@ module Prreview
48
48
  def parse_options!
49
49
  @prompt = DEFAULT_PROMPT
50
50
  @include_content = false
51
- @issues_limit = DEFAULT_ISSUES_LIMIT
52
- @optional_files = []
51
+ @linked_issues_limit = DEFAULT_LINKED_ISSUES_LIMIT
52
+ @optional_files = []
53
+
54
+ ARGV << '--help' if ARGV.empty?
53
55
 
54
- parser = OptionParser.new do |opts|
55
- opts.banner = "Usage: #{File.basename($PROGRAM_NAME)} -u URL [options]"
56
+ OptionParser.new do |parser|
57
+ parser.banner = "Usage: #{File.basename($PROGRAM_NAME)} -u URL [options]"
56
58
 
57
- opts.on('-u', '--url URL', 'Pull‑request URL (https://github.com/owner/repo/pull/1)') { |v| @url = v }
58
- opts.on('-p', '--prompt PROMPT', 'Custom LLM prompt') { |v| @prompt = v }
59
- opts.on('-a', '--all-content', 'Include full file contents') { @include_content = true }
60
- opts.on('-l', '--limit LIMIT', Integer, "Limit number of issues fetched (default: #{DEFAULT_ISSUES_LIMIT})") { |v| @issues_limit = v }
61
- opts.on('-o', '--optional PATHS', 'Comma‑separated paths to local files (relative or absolute, e.g. docs/description.md,/etc/hosts)') do |v|
59
+ parser.on('-u', '--url URL', 'Pull‑request URL (https://github.com/owner/repo/pull/1)') { |v| @url = v }
60
+ parser.on('-p', '--prompt PROMPT', 'Custom LLM prompt') { |v| @prompt = v }
61
+ parser.on('-a', '--all-content', 'Include full file contents') { @include_content = true }
62
+ parser.on('-l', '--limit LIMIT', Integer, "Limit number of issues fetched (default: #{DEFAULT_LINKED_ISSUES_LIMIT})") { |v| @linked_issues_limit = v }
63
+ parser.on('-o', '--optional PATHS', 'Comma‑separated paths to local files (relative or absolute, e.g. docs/description.md,/etc/hosts)') do |v|
62
64
  @optional_files = v.split(',').map(&:strip)
63
65
  end
64
- opts.on('-h', '--help', 'Show help') do
65
- puts opts
66
+ parser.on_tail('-v', '--version', 'Show version') do
67
+ puts VERSION
68
+ exit
69
+ end
70
+ parser.on_tail('-h', '--help', 'Show help') do
71
+ puts parser
66
72
  exit
67
73
  end
74
+ parser.parse!
68
75
  end
69
-
70
- parser.parse!
71
76
  end
72
77
 
73
78
  def parse_url!
@@ -84,11 +89,11 @@ module Prreview
84
89
 
85
90
  def initialize_client
86
91
  access_token = ENV.fetch('GITHUB_TOKEN', nil)
87
- abort 'Error: GITHUB_TOKEN is not set' if access_token.to_s.empty?
92
+ abort 'Error: GITHUB_TOKEN is not set.' if access_token.to_s.empty?
88
93
 
89
94
  @client = Octokit::Client.new(access_token:, auto_paginate: true)
90
95
  rescue Octokit::Unauthorized
91
- abort 'Error: Invalid GITHUB_TOKEN'
96
+ abort 'Error: Invalid GITHUB_TOKEN.'
92
97
  end
93
98
 
94
99
  def fetch_pull_request
@@ -107,53 +112,50 @@ module Prreview
107
112
  end
108
113
 
109
114
  def fetch_file_content(path)
110
- puts "Fetching file content for #{path}"
115
+ puts "Fetching #{path}"
111
116
 
112
117
  content = @client.contents(@full_repo, path:, ref: @pull_request.head.sha)
113
- Base64.decode64(content[:content])
118
+ decoded = Base64.decode64(content[:content])
119
+ binary?(decoded) ? '(binary file)' : decoded
114
120
  rescue Octokit::NotFound
115
121
  '(file content not found)'
116
122
  end
117
123
 
118
- def fetch_issues
119
- @issues = []
124
+ def fetch_linked_issues
125
+ @linked_issues = []
120
126
 
121
127
  text = [@pull_request.body, *@comments].join("\n")
122
128
  queue = extract_refs(text, URL_REGEX)
123
129
  seen = Set.new
124
130
 
125
- until queue.empty? || @issues.length >= @issues_limit
131
+ until queue.empty? || @linked_issues.length >= @linked_issues_limit
126
132
  ref = queue.shift
127
133
  key = ref[:key]
128
134
  next if seen.include?(key)
129
135
 
130
136
  seen << key
131
137
 
132
- issue = fetch_issue(ref)
138
+ issue = fetch_linked_issue(ref)
133
139
  next unless issue
134
140
 
135
- @issues << issue
141
+ @linked_issues << issue
136
142
 
137
143
  new_text = [issue[:description], *issue[:comments]].join("\n")
138
144
  new_refs = extract_refs(new_text, URL_REGEX).reject { |nref| seen.include?(nref[:key]) }
139
145
  queue.concat(new_refs)
140
146
  end
141
147
 
142
- puts "Fetched #{@issues.length} issues (limit: #{@issues_limit})"
148
+ puts "Fetched #{@linked_issues.length} linked issues (limit: #{@linked_issues_limit})"
143
149
  end
144
150
 
145
151
  def load_optional_files
146
152
  @optional_file_contents = @optional_files.filter_map do |path|
147
- if File.exist?(path)
148
- content = File.read(path)
149
- { filename: path, content: content }
150
- else
151
- warn "File #{path} not found, skipping"
152
- nil
153
- end
153
+ puts "Reading #{path}"
154
+ abort "Optional file #{path} not found." unless File.exist?(path)
155
+ content = File.read(path)
156
+ { filename: path, content: }
154
157
  rescue StandardError => e
155
- warn "Error reading file #{path}: #{e.message}"
156
- nil
158
+ raise "Error reading file #{path}: #{e.message}"
157
159
  end
158
160
  end
159
161
 
@@ -171,11 +173,11 @@ module Prreview
171
173
  end
172
174
  end
173
175
 
174
- def fetch_issue(ref)
176
+ def fetch_linked_issue(ref)
175
177
  full_repo = "#{ref[:owner]}/#{ref[:repo]}"
176
178
  number = ref[:number]
177
179
 
178
- puts "Fetching issue ##{number} for #{full_repo}"
180
+ puts "Fetching linked issue ##{number} for #{full_repo}"
179
181
 
180
182
  issue = @client.issue(full_repo, number)
181
183
  {
@@ -186,7 +188,7 @@ module Prreview
186
188
  comments: @client.issue_comments(full_repo, number).map(&:body)
187
189
  }
188
190
  rescue Octokit::NotFound
189
- puts "Issue #{number} for #{full_repo} not found, skipping"
191
+ warn "Linked issue #{number} for #{full_repo} not found, skipping"
190
192
  nil
191
193
  end
192
194
 
@@ -194,6 +196,7 @@ module Prreview
194
196
  builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |x|
195
197
  x.prompt do
196
198
  x.task @prompt
199
+ x.current_date DateTime.now
197
200
 
198
201
  x.pull_request do
199
202
  x.number @pr_number
@@ -212,8 +215,8 @@ module Prreview
212
215
  end
213
216
  end
214
217
 
215
- @issues.each do |issue|
216
- x.issue do
218
+ @linked_issues.each do |issue|
219
+ x.linked_issue do
217
220
  x.repo issue[:full_repo]
218
221
  x.number issue[:number]
219
222
  x.title issue[:title]
@@ -241,9 +244,13 @@ module Prreview
241
244
  @xml = builder.doc.root.to_xml
242
245
  end
243
246
 
247
+ def binary?(string)
248
+ string.include?("\x00")
249
+ end
250
+
244
251
  def copy_result_to_clipboard
245
252
  Clipboard.copy(@xml)
246
- puts 'XML prompt generated and copied to clipboard.'
253
+ puts 'XML prompt generated and copied to your clipboard.'
247
254
  end
248
255
  end
249
256
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prreview
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evgenii Morozov