cleo_quality_review 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/lib/cleo_quality_review/changes_diff.rb +16 -13
- data/lib/cleo_quality_review/cli.rb +13 -4
- data/lib/cleo_quality_review/concurrent_executor.rb +125 -0
- data/lib/cleo_quality_review/configuration.rb +32 -1
- data/lib/cleo_quality_review/formatter.rb +18 -3
- data/lib/cleo_quality_review/github_client.rb +114 -0
- data/lib/cleo_quality_review/incremental_base_resolver.rb +153 -0
- data/lib/cleo_quality_review/llm_client.rb +9 -5
- data/lib/cleo_quality_review/llm_providers/open_ai.rb +15 -7
- data/lib/cleo_quality_review/llm_providers/stub.rb +7 -3
- data/lib/cleo_quality_review/options.rb +14 -3
- data/lib/cleo_quality_review/run.rb +8 -0
- data/lib/cleo_quality_review/runner.rb +33 -9
- data/lib/cleo_quality_review/sticky_comment_builder.rb +76 -0
- data/lib/cleo_quality_review/sticky_comment_publisher.rb +143 -0
- data/lib/cleo_quality_review/version.rb +1 -1
- data/prompts/agent.md +7 -13
- data/prompts/configuration.md +47 -0
- data/prompts/github.md +5 -15
- data/prompts/human.md +4 -11
- data/prompts/pr_review.md +33 -32
- metadata +8 -5
- data/lib/cleo_quality_review/diff_map.rb +0 -95
- data/lib/cleo_quality_review/github_review_builder.rb +0 -140
- data/lib/cleo_quality_review/github_review_publisher.rb +0 -150
|
@@ -54,21 +54,25 @@ module CleoQualityReview
|
|
|
54
54
|
##
|
|
55
55
|
# Stub LLM client, mirrors OpenAi::Client interface.
|
|
56
56
|
class Client
|
|
57
|
-
attr_reader :received_prompts
|
|
57
|
+
attr_reader :received_prompts, :received_instructions
|
|
58
58
|
|
|
59
59
|
##
|
|
60
60
|
# @param [Config] config stub configuration
|
|
61
61
|
def initialize(config:)
|
|
62
62
|
@config = config
|
|
63
63
|
@received_prompts = []
|
|
64
|
+
@received_instructions = []
|
|
64
65
|
end
|
|
65
66
|
|
|
66
67
|
##
|
|
67
68
|
# Generate a review by returning the configured response.
|
|
68
|
-
# @param [String] prompt the prompt sent
|
|
69
|
+
# @param [String] prompt the format-specific prompt sent as input
|
|
70
|
+
# @param [String, nil] instructions shared configuration prompt applied
|
|
71
|
+
# to every run
|
|
69
72
|
# @return [String] the configured response
|
|
70
|
-
def generate_review(prompt)
|
|
73
|
+
def generate_review(prompt, instructions: nil)
|
|
71
74
|
received_prompts << prompt
|
|
75
|
+
received_instructions << instructions
|
|
72
76
|
response = config.response
|
|
73
77
|
|
|
74
78
|
case response
|
|
@@ -25,7 +25,9 @@ module CleoQualityReview
|
|
|
25
25
|
# @return [Array<String>] checks to exclude
|
|
26
26
|
# @!attribute [r] changed
|
|
27
27
|
# @return [Boolean] whether to filter to changed files only
|
|
28
|
-
|
|
28
|
+
# @!attribute [r] jobs
|
|
29
|
+
# @return [Integer, nil] max checks to run in parallel, or nil to auto-size
|
|
30
|
+
ParseResult = Struct.new(:format, :checks, :files, :exclude, :changed, :base, :log, :review_id, :review_file, :jobs, keyword_init: true) do
|
|
29
31
|
##
|
|
30
32
|
# @return [String] validated review_id
|
|
31
33
|
# @raise [OptionParser::MissingArgument] if review_id is blank
|
|
@@ -64,6 +66,7 @@ module CleoQualityReview
|
|
|
64
66
|
@log = false
|
|
65
67
|
@review_id = nil
|
|
66
68
|
@review_file = nil
|
|
69
|
+
@jobs = nil
|
|
67
70
|
end
|
|
68
71
|
|
|
69
72
|
##
|
|
@@ -85,17 +88,19 @@ module CleoQualityReview
|
|
|
85
88
|
log: log,
|
|
86
89
|
review_id: review_id,
|
|
87
90
|
review_file: review_file,
|
|
91
|
+
jobs: jobs,
|
|
88
92
|
)
|
|
89
93
|
end
|
|
90
94
|
|
|
91
95
|
private
|
|
92
96
|
|
|
93
|
-
attr_reader :argv, :format, :checks, :files, :exclude, :changed, :base, :log, :review_id, :review_file
|
|
97
|
+
attr_reader :argv, :format, :checks, :files, :exclude, :changed, :base, :log, :review_id, :review_file, :jobs
|
|
94
98
|
|
|
95
99
|
def parser
|
|
96
100
|
OptionParser.new do |opts|
|
|
97
101
|
opts.banner = "Usage: check_quality [options] [files...]"
|
|
98
102
|
register_options(opts)
|
|
103
|
+
register_help_option(opts)
|
|
99
104
|
end
|
|
100
105
|
end
|
|
101
106
|
|
|
@@ -104,7 +109,13 @@ module CleoQualityReview
|
|
|
104
109
|
register_check_options(opts)
|
|
105
110
|
register_target_options(opts)
|
|
106
111
|
register_output_options(opts)
|
|
107
|
-
|
|
112
|
+
register_jobs_option(opts)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def register_jobs_option(opts)
|
|
116
|
+
opts.on("-j", "--jobs N", Integer, "Max checks to run in parallel (default: CPU cores)") do |value|
|
|
117
|
+
@jobs = value
|
|
118
|
+
end
|
|
108
119
|
end
|
|
109
120
|
|
|
110
121
|
def register_format_option(opts)
|
|
@@ -38,6 +38,14 @@ module CleoQualityReview
|
|
|
38
38
|
:log,
|
|
39
39
|
keyword_init: true,
|
|
40
40
|
) do
|
|
41
|
+
##
|
|
42
|
+
# Whether the run has any files to review. Runs with no target files
|
|
43
|
+
# (e.g. a branch that only changes non-Ruby files) have nothing to analyse.
|
|
44
|
+
# @return [Boolean]
|
|
45
|
+
def reviewable?
|
|
46
|
+
!Array(target_files).empty?
|
|
47
|
+
end
|
|
48
|
+
|
|
41
49
|
##
|
|
42
50
|
# Convert the run to a hash representation
|
|
43
51
|
# @return [Hash{Symbol => Object}]
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "digest"
|
|
4
|
+
require "forwardable"
|
|
4
5
|
require "json"
|
|
5
6
|
|
|
6
7
|
require_relative "changes_diff"
|
|
7
8
|
require_relative "checks"
|
|
8
9
|
require_relative "command_runner"
|
|
10
|
+
require_relative "concurrent_executor"
|
|
9
11
|
require_relative "git_diff_base"
|
|
10
12
|
require_relative "run"
|
|
11
13
|
require_relative "run_artifacts"
|
|
@@ -15,6 +17,8 @@ module CleoQualityReview
|
|
|
15
17
|
##
|
|
16
18
|
# Orchestrates a complete quality review run
|
|
17
19
|
class Runner
|
|
20
|
+
extend Forwardable
|
|
21
|
+
|
|
18
22
|
##
|
|
19
23
|
# Grouped values resolved at the start of an analysis run
|
|
20
24
|
AnalysisContext = Struct.new(:timestamp, :base_ref, :target, :changes, :review_id, :check_classes, keyword_init: true) do
|
|
@@ -32,16 +36,28 @@ module CleoQualityReview
|
|
|
32
36
|
end
|
|
33
37
|
end
|
|
34
38
|
|
|
39
|
+
##
|
|
40
|
+
# Runtime collaborators for a quality review run
|
|
41
|
+
Dependencies = Struct.new(:command_runner, :clock, :check_registry, :base_resolver, :executor, keyword_init: true) do
|
|
42
|
+
def self.for(options, overrides)
|
|
43
|
+
new(
|
|
44
|
+
**{
|
|
45
|
+
command_runner: CommandRunner.new,
|
|
46
|
+
clock: Time,
|
|
47
|
+
check_registry: Checks,
|
|
48
|
+
base_resolver: nil,
|
|
49
|
+
executor: ConcurrentExecutor.new(max_workers: options.jobs),
|
|
50
|
+
}.merge(overrides),
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
35
55
|
##
|
|
36
56
|
# @param [Options::ParseResult] options parsed command-line options
|
|
37
|
-
# @param [
|
|
38
|
-
|
|
39
|
-
# @param [CheckRegistry] check_registry registry for resolving check names
|
|
40
|
-
def initialize(options:, command_runner: CommandRunner.new, clock: Time, check_registry: Checks)
|
|
57
|
+
# @param [Hash] dependencies optional runtime collaborators for tests or alternate runners
|
|
58
|
+
def initialize(options:, **dependencies)
|
|
41
59
|
@options = options
|
|
42
|
-
@
|
|
43
|
-
@clock = clock
|
|
44
|
-
@check_registry = check_registry
|
|
60
|
+
@dependencies = Dependencies.for(options, dependencies)
|
|
45
61
|
end
|
|
46
62
|
|
|
47
63
|
##
|
|
@@ -57,7 +73,9 @@ module CleoQualityReview
|
|
|
57
73
|
|
|
58
74
|
private
|
|
59
75
|
|
|
60
|
-
attr_reader :options, :
|
|
76
|
+
attr_reader :options, :dependencies
|
|
77
|
+
def_delegators :dependencies, :command_runner, :clock, :check_registry, :base_resolver, :executor
|
|
78
|
+
private :command_runner, :clock, :check_registry, :base_resolver, :executor
|
|
61
79
|
|
|
62
80
|
def epoch_milliseconds
|
|
63
81
|
(clock.now.to_r * 1_000).to_i
|
|
@@ -127,7 +145,9 @@ module CleoQualityReview
|
|
|
127
145
|
end
|
|
128
146
|
|
|
129
147
|
def run_checks(check_classes, ruby_files, timestamp)
|
|
130
|
-
|
|
148
|
+
return [] if ruby_files.empty?
|
|
149
|
+
|
|
150
|
+
executor.map(check_classes) do |check_class|
|
|
131
151
|
check_class.new(command_runner: command_runner, timestamp: timestamp).run(ruby_files)
|
|
132
152
|
end
|
|
133
153
|
end
|
|
@@ -170,6 +190,10 @@ module CleoQualityReview
|
|
|
170
190
|
end
|
|
171
191
|
|
|
172
192
|
def base_ref
|
|
193
|
+
@base_ref ||= base_resolver&.resolve || default_base_ref
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def default_base_ref
|
|
173
197
|
options.base || GitDiffBase::DEFAULT_BASE_REF
|
|
174
198
|
end
|
|
175
199
|
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
require_relative "llm_errors"
|
|
6
|
+
|
|
7
|
+
module CleoQualityReview
|
|
8
|
+
##
|
|
9
|
+
# Builds the marker and body for the sticky pull request comment from
|
|
10
|
+
# rendered pr_review JSON
|
|
11
|
+
class StickyCommentBuilder
|
|
12
|
+
MARKER_PREFIX = "<!-- cleo-quality-review:"
|
|
13
|
+
MAX_BODY_LENGTH = 3_500
|
|
14
|
+
CLEAN_MESSAGE = "Cleo quality review found no high-confidence issues worth flagging on this change."
|
|
15
|
+
|
|
16
|
+
##
|
|
17
|
+
# @param [String] rendered_review JSON produced by the pr_review formatter
|
|
18
|
+
def initialize(rendered_review:)
|
|
19
|
+
@rendered_review = rendered_review
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
##
|
|
23
|
+
# @param [String] commit_sha head commit reviewed to embed in the marker
|
|
24
|
+
# @return [String] full comment body, including the hidden marker
|
|
25
|
+
def comment_body(commit_sha:)
|
|
26
|
+
[marker(commit_sha), truncate(display_body)].join("\n\n")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
# @return [Boolean] whether the rendered review has anything worth publishing
|
|
31
|
+
def empty?
|
|
32
|
+
body_text.empty?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
attr_reader :rendered_review
|
|
38
|
+
|
|
39
|
+
def marker(commit_sha)
|
|
40
|
+
"#{MARKER_PREFIX} commit=#{commit_sha} -->"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def display_body
|
|
44
|
+
body_text.empty? ? CLEAN_MESSAGE : body_text
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def body_text
|
|
48
|
+
parsed_review.fetch("body", "").to_s.strip
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def parsed_review
|
|
52
|
+
@parsed_review ||= parse_rendered_review
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# A blank rendered review (e.g. the render step produced no output because
|
|
56
|
+
# there were no reviewable changes) means there is nothing to publish, so
|
|
57
|
+
# treat it as an empty review rather than failing to parse it as JSON.
|
|
58
|
+
def parse_rendered_review
|
|
59
|
+
content = rendered_review.to_s.strip
|
|
60
|
+
return {} if content.empty?
|
|
61
|
+
|
|
62
|
+
parsed = JSON.parse(content)
|
|
63
|
+
raise Error, "pr_review JSON must be an object" unless parsed.is_a?(Hash)
|
|
64
|
+
|
|
65
|
+
parsed
|
|
66
|
+
rescue JSON::ParserError => e
|
|
67
|
+
raise Error, "pr_review output was not valid JSON: #{e.message}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def truncate(value)
|
|
71
|
+
return value if value.length <= MAX_BODY_LENGTH
|
|
72
|
+
|
|
73
|
+
"#{value[0, MAX_BODY_LENGTH - 20]}\n\n[truncated]"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
require_relative "github_client"
|
|
6
|
+
require_relative "llm_errors"
|
|
7
|
+
require_relative "sticky_comment_builder"
|
|
8
|
+
|
|
9
|
+
module CleoQualityReview
|
|
10
|
+
##
|
|
11
|
+
# Publishes quality review findings as a single sticky pull request
|
|
12
|
+
# comment, editing it in place on every run rather than posting a new one
|
|
13
|
+
class StickyCommentPublisher
|
|
14
|
+
COMMENTS_PER_PAGE = 100
|
|
15
|
+
MAX_COMMENT_PAGES = 20
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
# @param [Run] run completed quality review run
|
|
19
|
+
# @param [String] rendered_review JSON produced by the pr_review formatter
|
|
20
|
+
# @param [Hash{String => String}] env process environment
|
|
21
|
+
# @param [GitHubClient, nil] client GitHub API client (built from env when omitted)
|
|
22
|
+
def initialize(run:, rendered_review:, env: ENV, client: nil)
|
|
23
|
+
@run = run
|
|
24
|
+
@env = env
|
|
25
|
+
@client = client
|
|
26
|
+
@builder = StickyCommentBuilder.new(rendered_review: rendered_review)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
# Publish the sticky comment, or skip when there is no PR context/findings
|
|
31
|
+
# @return [String] status message
|
|
32
|
+
def publish
|
|
33
|
+
skip_reason = publication_skip_reason
|
|
34
|
+
return skip_reason if skip_reason
|
|
35
|
+
|
|
36
|
+
existing_comment_id ? update_comment : create_comment
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
attr_reader :env, :run, :builder
|
|
42
|
+
|
|
43
|
+
def publication_skip_reason
|
|
44
|
+
return "No pull_request event found; skipping sticky comment publication." unless pull_request_context?
|
|
45
|
+
return "No sticky comment to publish for review ID #{run.review_id}." if builder.empty? && existing_comment_id.nil?
|
|
46
|
+
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def create_comment
|
|
51
|
+
response = client.post(comments_path, { body: comment_body })
|
|
52
|
+
raise Error, "GitHub sticky comment creation failed with status #{response.status_code}: #{response.body}" unless response.success?
|
|
53
|
+
|
|
54
|
+
"Created sticky comment for review ID #{run.review_id}."
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def update_comment
|
|
58
|
+
response = client.patch(comment_path(existing_comment_id), { body: comment_body })
|
|
59
|
+
raise Error, "GitHub sticky comment update failed with status #{response.status_code}: #{response.body}" unless response.success?
|
|
60
|
+
|
|
61
|
+
"Updated sticky comment for review ID #{run.review_id}."
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def comment_body
|
|
65
|
+
builder.comment_body(commit_sha: head_sha)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def existing_comment_id
|
|
69
|
+
return @existing_comment_id if defined?(@existing_comment_id)
|
|
70
|
+
|
|
71
|
+
@existing_comment_id = find_existing_comment_id
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def find_existing_comment_id
|
|
75
|
+
marked = comments.find { |comment| bot_authored?(comment) && marked?(comment) }
|
|
76
|
+
marked && marked.fetch("id")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def comments
|
|
80
|
+
(1..MAX_COMMENT_PAGES).each_with_object([]) do |page, all|
|
|
81
|
+
page_comments = comments_page(page)
|
|
82
|
+
all.concat(page_comments)
|
|
83
|
+
break all if page_comments.length < COMMENTS_PER_PAGE
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def comments_page(page)
|
|
88
|
+
response = client.get("#{comments_path}?per_page=#{COMMENTS_PER_PAGE}&page=#{page}")
|
|
89
|
+
raise Error, "GitHub comment lookup failed with status #{response.status_code}: #{response.body}" unless response.success?
|
|
90
|
+
|
|
91
|
+
parsed = JSON.parse(response.body)
|
|
92
|
+
parsed.is_a?(Array) ? parsed : []
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def bot_authored?(comment)
|
|
96
|
+
comment.dig("user", "type") == "Bot"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def marked?(comment)
|
|
100
|
+
comment.fetch("body") { "" }.to_s.include?(StickyCommentBuilder::MARKER_PREFIX)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def client
|
|
104
|
+
@client ||= GitHubClient.new(token: token, api_url: api_url)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def pull_request_context?
|
|
108
|
+
event.fetch("pull_request", nil).is_a?(Hash)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def comments_path
|
|
112
|
+
"/repos/#{repository}/issues/#{pull_request_number}/comments"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def comment_path(comment_id)
|
|
116
|
+
"/repos/#{repository}/issues/comments/#{comment_id}"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def pull_request_number
|
|
120
|
+
event["number"] || event.fetch("pull_request").fetch("number")
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def head_sha
|
|
124
|
+
event.fetch("pull_request").fetch("head").fetch("sha")
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def repository
|
|
128
|
+
env.fetch("GITHUB_REPOSITORY")
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def api_url
|
|
132
|
+
env.fetch("GITHUB_API_URL", "https://api.github.com")
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def event
|
|
136
|
+
@event ||= JSON.parse(File.read(env.fetch("GITHUB_EVENT_PATH")))
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def token
|
|
140
|
+
env.fetch("GITHUB_TOKEN")
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
data/prompts/agent.md
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
You are reviewing Ruby code quality findings for consumption by AI coding assistants.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- **Flog**: Ignore scores below 40.0
|
|
8
|
-
- **Reek**: Focus on FeatureEnvy, TooManyStatements, DuplicateMethodCall, NestedIterators, LongParameterList
|
|
9
|
-
- **Fasterer**: Include all performance suggestions
|
|
3
|
+
Apply the shared review rules from the configuration prompt provided alongside this one.
|
|
4
|
+
That prompt defines the inputs, tool thresholds, prioritisation, and noise-reduction rules.
|
|
5
|
+
This prompt defines only the output format.
|
|
10
6
|
|
|
11
7
|
## Output Format
|
|
12
8
|
|
|
@@ -43,10 +39,8 @@ Output valid JSON matching this exact schema:
|
|
|
43
39
|
}
|
|
44
40
|
```
|
|
45
41
|
|
|
46
|
-
##
|
|
42
|
+
## Output rules
|
|
47
43
|
|
|
48
|
-
1.
|
|
49
|
-
2.
|
|
50
|
-
3.
|
|
51
|
-
4. Include the raw check outputs in `check_outputs` for reference
|
|
52
|
-
5. Output ONLY valid JSON - no markdown fences, no explanatory text
|
|
44
|
+
1. Write concise `result` descriptions an agent can act on.
|
|
45
|
+
2. Include the raw check outputs in `check_outputs` for reference.
|
|
46
|
+
3. Output ONLY valid JSON - no markdown fences, no explanatory text.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
You are reviewing Ruby code quality findings produced by static analysis tools.
|
|
2
|
+
|
|
3
|
+
These are the standard rules that apply to every review, regardless of the output format.
|
|
4
|
+
The output format is defined separately in the format-specific prompt that accompanies these rules.
|
|
5
|
+
|
|
6
|
+
## Inputs
|
|
7
|
+
|
|
8
|
+
You are given the raw output from a series of code quality tools (including, but not limited to, Reek, Flog, Fasterer, Flay, and Brakeman), together with the git diff for the change under review.
|
|
9
|
+
The combined tool output is noisy.
|
|
10
|
+
Your job is to decide what genuinely matters and to discard the rest.
|
|
11
|
+
The diff is provided so you can map tool findings to the lines that changed.
|
|
12
|
+
|
|
13
|
+
## Excluded files
|
|
14
|
+
|
|
15
|
+
Do not review test files: ignore every tool finding that points to one, and never post a comment on a test file.
|
|
16
|
+
Test files are those inside a `test/` or `spec/` directory, for example `test/models/user_test.rb`.
|
|
17
|
+
A file that merely has `test` in its name but lives in application code, such as an A/B-test model under `app/`, is not a test file.
|
|
18
|
+
Tests in this codebase are intentionally verbose and self-contained, so the smells these tools report on them are expected rather than defects.
|
|
19
|
+
|
|
20
|
+
## Tool thresholds and severity
|
|
21
|
+
|
|
22
|
+
- **Flog**: Ignore scores below 40.0. Treat high-complexity methods as the most important findings because they are the most expensive to maintain.
|
|
23
|
+
- **Reek**: Prefer actionable smells such as FeatureEnvy, DuplicateMethodCall, NestedIterators, and LongParameterList.
|
|
24
|
+
- **Fasterer**: Low severity. Include a performance suggestion only when it clearly applies to code changed by this review and the fix is straightforward.
|
|
25
|
+
|
|
26
|
+
## Rule-specific guidance
|
|
27
|
+
|
|
28
|
+
These notes refine how individual rules should be treated.
|
|
29
|
+
Where a note here conflicts with the general guidance above, the note takes precedence for that rule.
|
|
30
|
+
|
|
31
|
+
### Reek: TooManyStatements
|
|
32
|
+
|
|
33
|
+
- Deprioritise this smell in application code.
|
|
34
|
+
Only surface it when the method is a particularly egregious example, such as a long method that clearly juggles several unrelated responsibilities, and omit it otherwise.
|
|
35
|
+
|
|
36
|
+
## Prioritisation
|
|
37
|
+
|
|
38
|
+
1. Prioritise issues that affect maintainability, correctness, readability, performance, and long-term ownership.
|
|
39
|
+
2. Order findings by impact: high-complexity methods first, then code smells, then performance suggestions.
|
|
40
|
+
3. Filter out low-signal findings.
|
|
41
|
+
|
|
42
|
+
## Noise reduction
|
|
43
|
+
|
|
44
|
+
- Do not comment on the code diff itself unless the comment is directly supported by a tool finding.
|
|
45
|
+
- Do not repeat tool output mechanically. When several findings are of the same kind, highlight a couple of representative examples and then make one general recommendation.
|
|
46
|
+
- If a finding is low value, stale, ambiguous, or a likely false positive, omit it or note it briefly.
|
|
47
|
+
- Keep every finding concise and actionable, specific enough for an engineer or coding agent to act on.
|
data/prompts/github.md
CHANGED
|
@@ -1,22 +1,12 @@
|
|
|
1
1
|
You are the pipeline interface between a series of code reviews for a git diff, and the GitHub Actions automation pipeline.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Apply the shared review rules from the configuration prompt provided alongside this one.
|
|
4
|
+
That prompt defines the inputs, tool thresholds, prioritisation, and noise-reduction rules.
|
|
5
|
+
This prompt defines only the output format.
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
For weighting, consider the following values as guides:
|
|
8
|
-
|
|
9
|
-
Flog:
|
|
10
|
-
Threshold: 40.0
|
|
11
|
-
ThresholdType: GreaterThanOrEqual
|
|
12
|
-
Severity: Medium to High
|
|
13
|
-
|
|
14
|
-
Reek:
|
|
15
|
-
Severity: Low to Medium
|
|
16
|
-
|
|
17
|
-
Fasterer:
|
|
18
|
-
Severity: Low
|
|
7
|
+
You produce useful, meaningful output for the engineer whose PR triggered this flow.
|
|
19
8
|
|
|
9
|
+
## Output Format
|
|
20
10
|
|
|
21
11
|
You MUST NOT return so many items that the feedback is noisy and confusing. Limit yourself to maximum 10 comments.
|
|
22
12
|
|
data/prompts/human.md
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
You are reviewing a local code change for code quality.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
YOU MUST NOT comment on the code diff itself, unless the comment is in relation to an issue reported by a tool.
|
|
8
|
-
|
|
9
|
-
Prioritize issues that are likely to matter to maintainability, correctness, readability, or long-term ownership.
|
|
10
|
-
|
|
11
|
-
Avoid repeating tool output mechanically. If multiple issues of the same sort are reported, it's fine to highlight a couple of examples and then make a general comment for improvement.
|
|
12
|
-
|
|
13
|
-
If a tool finding is low value or likely a false positive, say so briefly or omit it.
|
|
3
|
+
Apply the shared review rules from the configuration prompt provided alongside this one.
|
|
4
|
+
That prompt defines the inputs, tool thresholds, prioritisation, and noise-reduction rules.
|
|
5
|
+
This prompt defines only the output format.
|
|
14
6
|
|
|
7
|
+
## Output Format
|
|
15
8
|
|
|
16
9
|
The output will be printed in a Unix terminal, and so colour-coded feedback is preferable.
|
|
17
10
|
|
data/prompts/pr_review.md
CHANGED
|
@@ -1,61 +1,62 @@
|
|
|
1
|
-
You are the pipeline interface between code quality tools and GitHub pull request
|
|
1
|
+
You are the pipeline interface between code quality tools and a single, standing summary comment on a GitHub pull request.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Apply the shared review rules from the configuration prompt provided alongside this one.
|
|
4
|
+
That prompt defines the inputs, tool thresholds, prioritisation, and noise-reduction rules.
|
|
5
|
+
This prompt defines only the output format.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
This comment is edited in place on every push rather than replaced or added to, and the same PR also receives GitHub Actions annotations pointing at the exact file and line of each finding.
|
|
8
|
+
Do not attempt to recreate that per-line detail here.
|
|
9
|
+
Write a short, consolidated narrative of the main themes across findings, then point the reader at the annotations for specifics.
|
|
6
10
|
|
|
7
|
-
##
|
|
11
|
+
## Summary Selection
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
## Comment Selection
|
|
14
|
-
|
|
15
|
-
1. Limit yourself to ten comments at most.
|
|
16
|
-
2. Prefer findings that map directly to a changed or commentable right-side line in the git diff.
|
|
17
|
-
3. Omit low-value, duplicated, stale, or ambiguous findings.
|
|
18
|
-
4. If a tool finding points to a file or line that is not visible in the provided diff, omit the inline comment.
|
|
19
|
-
5. Keep comments concise and actionable. Mention the tool and check name.
|
|
13
|
+
1. Cover at most the five most important themes.
|
|
14
|
+
Group related findings from the same tool or the same underlying cause into one theme rather than listing them separately.
|
|
15
|
+
2. Mention the tool and check name for each theme, but do not centre the narrative on tool names.
|
|
16
|
+
3. Do not quote line numbers or file paths; the annotations already carry that detail.
|
|
20
17
|
|
|
21
18
|
## Output Format
|
|
22
19
|
|
|
23
|
-
Output ONLY valid JSON.
|
|
20
|
+
Output ONLY valid JSON.
|
|
21
|
+
Do not wrap it in markdown fences.
|
|
22
|
+
Do not include explanatory text before or after the JSON.
|
|
24
23
|
|
|
25
24
|
The JSON MUST match this schema:
|
|
26
25
|
|
|
27
26
|
```json
|
|
28
27
|
{
|
|
29
|
-
"body": "<short markdown summary for the PR
|
|
30
|
-
"comments": [
|
|
31
|
-
{
|
|
32
|
-
"path": "<repository-relative file path>",
|
|
33
|
-
"line": <right-side line number from the diff>,
|
|
34
|
-
"body": "<markdown review comment>"
|
|
35
|
-
}
|
|
36
|
-
]
|
|
28
|
+
"body": "<short markdown narrative summary for the sticky PR comment>"
|
|
37
29
|
}
|
|
38
30
|
```
|
|
39
31
|
|
|
32
|
+
## Comment format
|
|
40
33
|
|
|
41
|
-
|
|
34
|
+
Prioritise readability and actionability.
|
|
35
|
+
Assume the reader is a junior developer, or someone who is not familiar with the language and framework.
|
|
36
|
+
Be helpful, without being overly verbose.
|
|
42
37
|
|
|
43
|
-
|
|
38
|
+
Write one short paragraph per theme.
|
|
39
|
+
Separate each theme's paragraph from the next with a blank line - never merge multiple themes into a single paragraph.
|
|
40
|
+
End each theme's paragraph with its own `(Ref: ...)` tag naming the tool and check.
|
|
44
41
|
|
|
45
42
|
Example format:
|
|
46
43
|
```
|
|
47
|
-
This
|
|
44
|
+
This change introduces a fairly complex method that will be expensive to maintain as it grows further.
|
|
45
|
+
Consider breaking it into smaller, named steps.
|
|
46
|
+
|
|
47
|
+
_(Ref: Flog)_
|
|
48
|
+
|
|
49
|
+
There's a repeated pattern here that could be extracted into a shared helper, which would also make the duplication easier to spot next time it happens.
|
|
48
50
|
|
|
49
|
-
_(Ref: Reek
|
|
51
|
+
_(Ref: Reek DuplicateMethodCall)_
|
|
50
52
|
```
|
|
51
53
|
|
|
52
|
-
## Empty output
|
|
54
|
+
## Empty output
|
|
53
55
|
|
|
54
|
-
If there are no high-confidence
|
|
56
|
+
If there are no high-confidence findings worth reporting, return:
|
|
55
57
|
|
|
56
58
|
```json
|
|
57
59
|
{
|
|
58
|
-
"body": "
|
|
59
|
-
"comments": []
|
|
60
|
+
"body": ""
|
|
60
61
|
}
|
|
61
62
|
```
|