cleo_quality_review 0.1.0 → 0.3.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/cleo_quality_review.gemspec +0 -1
- data/lib/cleo_quality_review/changes_diff.rb +8 -9
- data/lib/cleo_quality_review/checks.rb +0 -1
- data/lib/cleo_quality_review/git_diff_base.rb +27 -0
- data/lib/cleo_quality_review/options.rb +13 -5
- data/lib/cleo_quality_review/prompt_builder.rb +2 -2
- data/lib/cleo_quality_review/run.rb +11 -0
- data/lib/cleo_quality_review/run_artifacts.rb +2 -0
- data/lib/cleo_quality_review/runner.rb +28 -10
- data/lib/cleo_quality_review/target_resolver.rb +7 -9
- data/lib/cleo_quality_review/version.rb +1 -1
- data/lib/cleo_quality_review.rb +0 -1
- data/prompts/agent.md +2 -3
- data/prompts/github.md +0 -4
- data/prompts/human.md +3 -4
- data/prompts/pr_review.md +1 -2
- metadata +2 -16
- data/lib/cleo_quality_review/checks/debride.rb +0 -65
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2f7c748370590a154e611c6302e8bbfd9e5c3f8dc1b756e938ccba09101b6ee5
|
|
4
|
+
data.tar.gz: 85e837b45af4b31c23e9f59d613653a481a5e172da0581df8edc96945c4f409c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7f5f53a18a2cd9ef9e6e7c3456b416d4b2f0dcab0d36d3c4d75e70c1c97bb408659918c89d57ab284faf05e7111cd06f0fc39f34f1be0d08f611f48945fe9ace
|
|
7
|
+
data.tar.gz: dabda2763af548f1ef2be712d6b3a0ec8ca1ca03330a249ea91b35972176a3f37785677dd39334ff207aae8374d586358a0c1c3a1a402f0e9ac94ab1c4dadff5
|
data/cleo_quality_review.gemspec
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "digest"
|
|
4
4
|
|
|
5
|
-
require_relative "
|
|
5
|
+
require_relative "git_diff_base"
|
|
6
6
|
|
|
7
7
|
module CleoQualityReview
|
|
8
8
|
##
|
|
@@ -11,9 +11,13 @@ module CleoQualityReview
|
|
|
11
11
|
##
|
|
12
12
|
# @param [Array<String>] target_files files included in the review
|
|
13
13
|
# @param [CommandRunner] command_runner for executing git commands
|
|
14
|
-
|
|
14
|
+
# @param [String] base_ref git ref to compare against
|
|
15
|
+
# @param [Boolean] strict_base whether unresolved refs should raise
|
|
16
|
+
def initialize(target_files:, command_runner:, base_ref: GitDiffBase::DEFAULT_BASE_REF, strict_base: false)
|
|
15
17
|
@target_files = target_files
|
|
16
18
|
@command_runner = command_runner
|
|
19
|
+
@base_ref = base_ref || GitDiffBase::DEFAULT_BASE_REF
|
|
20
|
+
@strict_base = strict_base
|
|
17
21
|
end
|
|
18
22
|
|
|
19
23
|
##
|
|
@@ -30,7 +34,7 @@ module CleoQualityReview
|
|
|
30
34
|
|
|
31
35
|
private
|
|
32
36
|
|
|
33
|
-
attr_reader :command_runner, :target_files
|
|
37
|
+
attr_reader :command_runner, :target_files, :base_ref, :strict_base
|
|
34
38
|
|
|
35
39
|
def tracked_changes_diff
|
|
36
40
|
command = ["git", "diff", diff_base]
|
|
@@ -56,12 +60,7 @@ module CleoQualityReview
|
|
|
56
60
|
end
|
|
57
61
|
|
|
58
62
|
def diff_base
|
|
59
|
-
@diff_base ||=
|
|
60
|
-
result = command_runner.run("git", "merge-base", TargetResolver::BASE_REF, "HEAD")
|
|
61
|
-
base = result.stdout.strip
|
|
62
|
-
|
|
63
|
-
result.success? && !base.empty? ? base : TargetResolver::BASE_REF
|
|
64
|
-
end
|
|
63
|
+
@diff_base ||= GitDiffBase.resolve(command_runner: command_runner, base_ref: base_ref, strict: strict_base)
|
|
65
64
|
end
|
|
66
65
|
end
|
|
67
66
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CleoQualityReview
|
|
4
|
+
##
|
|
5
|
+
# Resolves git comparison bases for changed-file and diff capture flows
|
|
6
|
+
module GitDiffBase
|
|
7
|
+
DEFAULT_BASE_REF = "origin/main"
|
|
8
|
+
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
##
|
|
12
|
+
# @param [CommandRunner] command_runner for executing git commands
|
|
13
|
+
# @param [String] base_ref git ref to compare against
|
|
14
|
+
# @param [Boolean] strict whether unresolved refs should raise
|
|
15
|
+
# @return [String] merge-base SHA, or the base ref when non-strict resolution fails
|
|
16
|
+
def resolve(command_runner:, base_ref:, strict:)
|
|
17
|
+
result = command_runner.run("git", "merge-base", base_ref, "HEAD")
|
|
18
|
+
base = result.stdout.strip
|
|
19
|
+
|
|
20
|
+
return base if result.success? && !base.empty?
|
|
21
|
+
|
|
22
|
+
raise ArgumentError, "Could not resolve quality review base ref: #{base_ref}" if strict
|
|
23
|
+
|
|
24
|
+
base_ref
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require "optparse"
|
|
4
4
|
|
|
5
|
+
require_relative "git_diff_base"
|
|
6
|
+
|
|
5
7
|
module CleoQualityReview
|
|
6
8
|
##
|
|
7
9
|
# Parses command-line options for the quality review CLI
|
|
@@ -23,7 +25,7 @@ module CleoQualityReview
|
|
|
23
25
|
# @return [Array<String>] checks to exclude
|
|
24
26
|
# @!attribute [r] changed
|
|
25
27
|
# @return [Boolean] whether to filter to changed files only
|
|
26
|
-
ParseResult = Struct.new(:format, :checks, :files, :exclude, :changed, :log, :review_id, :review_file, keyword_init: true) do
|
|
28
|
+
ParseResult = Struct.new(:format, :checks, :files, :exclude, :changed, :base, :log, :review_id, :review_file, keyword_init: true) do
|
|
27
29
|
##
|
|
28
30
|
# @return [String] validated review_id
|
|
29
31
|
# @raise [OptionParser::MissingArgument] if review_id is blank
|
|
@@ -58,6 +60,7 @@ module CleoQualityReview
|
|
|
58
60
|
@files = []
|
|
59
61
|
@exclude = []
|
|
60
62
|
@changed = false
|
|
63
|
+
@base = GitDiffBase::DEFAULT_BASE_REF
|
|
61
64
|
@log = false
|
|
62
65
|
@review_id = nil
|
|
63
66
|
@review_file = nil
|
|
@@ -78,6 +81,7 @@ module CleoQualityReview
|
|
|
78
81
|
files: files,
|
|
79
82
|
exclude: exclude,
|
|
80
83
|
changed: changed,
|
|
84
|
+
base: base,
|
|
81
85
|
log: log,
|
|
82
86
|
review_id: review_id,
|
|
83
87
|
review_file: review_file,
|
|
@@ -86,7 +90,7 @@ module CleoQualityReview
|
|
|
86
90
|
|
|
87
91
|
private
|
|
88
92
|
|
|
89
|
-
attr_reader :argv, :format, :checks, :files, :exclude, :changed, :log, :review_id, :review_file
|
|
93
|
+
attr_reader :argv, :format, :checks, :files, :exclude, :changed, :base, :log, :review_id, :review_file
|
|
90
94
|
|
|
91
95
|
def parser
|
|
92
96
|
OptionParser.new do |opts|
|
|
@@ -116,7 +120,7 @@ module CleoQualityReview
|
|
|
116
120
|
end
|
|
117
121
|
|
|
118
122
|
def register_checks_option(opts)
|
|
119
|
-
opts.on("-c", "--checks CHECKS", Array, "Checks to run: all, reek, flog, fasterer
|
|
123
|
+
opts.on("-c", "--checks CHECKS", Array, "Checks to run: all, reek, flog, fasterer") { |values| checks.concat(values) }
|
|
120
124
|
end
|
|
121
125
|
|
|
122
126
|
def register_only_option(opts)
|
|
@@ -124,7 +128,7 @@ module CleoQualityReview
|
|
|
124
128
|
end
|
|
125
129
|
|
|
126
130
|
def register_exclude_option(opts)
|
|
127
|
-
opts.on("-x", "--exclude CHECKS", Array, "Checks to exclude: reek, flog, fasterer
|
|
131
|
+
opts.on("-x", "--exclude CHECKS", Array, "Checks to exclude: reek, flog, fasterer") { |values| exclude.concat(values) }
|
|
128
132
|
end
|
|
129
133
|
|
|
130
134
|
def register_target_options(opts)
|
|
@@ -132,9 +136,13 @@ module CleoQualityReview
|
|
|
132
136
|
files.concat(values)
|
|
133
137
|
end
|
|
134
138
|
|
|
135
|
-
opts.on("--changed", "Only check files changed from
|
|
139
|
+
opts.on("--changed", "Only check files changed from the base ref") do
|
|
136
140
|
@changed = true
|
|
137
141
|
end
|
|
142
|
+
|
|
143
|
+
opts.on("--base REF", "Git ref to compare changed files against") do |value|
|
|
144
|
+
@base = value
|
|
145
|
+
end
|
|
138
146
|
end
|
|
139
147
|
|
|
140
148
|
def register_output_options(opts)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "
|
|
3
|
+
require_relative "git_diff_base"
|
|
4
4
|
|
|
5
5
|
module CleoQualityReview
|
|
6
6
|
##
|
|
@@ -46,7 +46,7 @@ module CleoQualityReview
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def diff_section
|
|
49
|
-
fenced("Git diff against #{
|
|
49
|
+
fenced("Git diff against #{run.base_ref || GitDiffBase::DEFAULT_BASE_REF}", "diff", artifacts.changes_diff)
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
def check_outputs_section
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "git_diff_base"
|
|
4
|
+
|
|
3
5
|
module CleoQualityReview
|
|
4
6
|
##
|
|
5
7
|
# Value object representing a quality review run with its configuration and results
|
|
@@ -25,6 +27,7 @@ module CleoQualityReview
|
|
|
25
27
|
Run = Struct.new(
|
|
26
28
|
:timestamp,
|
|
27
29
|
:review_id,
|
|
30
|
+
:base_ref,
|
|
28
31
|
:format,
|
|
29
32
|
:checks,
|
|
30
33
|
:target_files,
|
|
@@ -42,6 +45,7 @@ module CleoQualityReview
|
|
|
42
45
|
{
|
|
43
46
|
timestamp: timestamp,
|
|
44
47
|
review_id: review_id,
|
|
48
|
+
base_ref: comparison_base_ref,
|
|
45
49
|
format: format,
|
|
46
50
|
checks: checks,
|
|
47
51
|
target_files: target_files,
|
|
@@ -68,11 +72,18 @@ module CleoQualityReview
|
|
|
68
72
|
def manifest_data
|
|
69
73
|
{
|
|
70
74
|
review_id: review_id,
|
|
75
|
+
base_ref: comparison_base_ref,
|
|
71
76
|
timestamp: timestamp,
|
|
72
77
|
checks: checks,
|
|
73
78
|
target_files: target_files,
|
|
74
79
|
ruby_files: ruby_files,
|
|
75
80
|
}
|
|
76
81
|
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def comparison_base_ref
|
|
86
|
+
base_ref || GitDiffBase::DEFAULT_BASE_REF
|
|
87
|
+
end
|
|
77
88
|
end
|
|
78
89
|
end
|
|
@@ -5,6 +5,7 @@ require "fileutils"
|
|
|
5
5
|
|
|
6
6
|
require_relative "result"
|
|
7
7
|
require_relative "run"
|
|
8
|
+
require_relative "git_diff_base"
|
|
8
9
|
require_relative "run_artifacts/raw_check_outputs"
|
|
9
10
|
|
|
10
11
|
module CleoQualityReview
|
|
@@ -79,6 +80,7 @@ module CleoQualityReview
|
|
|
79
80
|
Run.new(
|
|
80
81
|
timestamp: manifest.fetch("timestamp"),
|
|
81
82
|
review_id: manifest.fetch("review_id"),
|
|
83
|
+
base_ref: manifest.fetch("base_ref", GitDiffBase::DEFAULT_BASE_REF),
|
|
82
84
|
format: format,
|
|
83
85
|
checks: manifest.fetch("checks", []),
|
|
84
86
|
target_files: target_files,
|
|
@@ -6,6 +6,7 @@ require "json"
|
|
|
6
6
|
require_relative "changes_diff"
|
|
7
7
|
require_relative "checks"
|
|
8
8
|
require_relative "command_runner"
|
|
9
|
+
require_relative "git_diff_base"
|
|
9
10
|
require_relative "run"
|
|
10
11
|
require_relative "run_artifacts"
|
|
11
12
|
require_relative "target_resolver"
|
|
@@ -16,12 +17,13 @@ module CleoQualityReview
|
|
|
16
17
|
class Runner
|
|
17
18
|
##
|
|
18
19
|
# Grouped values resolved at the start of an analysis run
|
|
19
|
-
AnalysisContext = Struct.new(:timestamp, :target, :changes, :review_id, :check_classes, keyword_init: true) do
|
|
20
|
+
AnalysisContext = Struct.new(:timestamp, :base_ref, :target, :changes, :review_id, :check_classes, keyword_init: true) do
|
|
20
21
|
##
|
|
21
22
|
# @return [Hash] run construction attributes derived from this context
|
|
22
23
|
def run_attributes
|
|
23
24
|
{
|
|
24
25
|
timestamp: timestamp,
|
|
26
|
+
base_ref: base_ref,
|
|
25
27
|
review_id: review_id,
|
|
26
28
|
checks: check_classes.map(&:check_name),
|
|
27
29
|
target_files: target.files,
|
|
@@ -69,21 +71,26 @@ module CleoQualityReview
|
|
|
69
71
|
|
|
70
72
|
AnalysisContext.new(
|
|
71
73
|
timestamp: timestamp,
|
|
74
|
+
base_ref: base_ref,
|
|
72
75
|
target: target,
|
|
73
76
|
changes: changes,
|
|
74
|
-
review_id: review_id_for(changes, check_classes),
|
|
77
|
+
review_id: review_id_for(changes, check_classes, base_ref: base_ref),
|
|
75
78
|
check_classes: check_classes,
|
|
76
79
|
)
|
|
77
80
|
end
|
|
78
81
|
|
|
79
82
|
def resolve_target
|
|
80
83
|
files = options.files
|
|
81
|
-
|
|
82
|
-
TargetResolver.new(command_runner: command_runner).resolve(files, changed: changed)
|
|
84
|
+
TargetResolver.new(command_runner: command_runner, base_ref: base_ref).resolve(files, changed: changed_mode?)
|
|
83
85
|
end
|
|
84
86
|
|
|
85
87
|
def changes_diff(target)
|
|
86
|
-
ChangesDiff.new(
|
|
88
|
+
ChangesDiff.new(
|
|
89
|
+
target_files: target.files,
|
|
90
|
+
command_runner: command_runner,
|
|
91
|
+
base_ref: base_ref,
|
|
92
|
+
strict_base: changed_mode?,
|
|
93
|
+
)
|
|
87
94
|
end
|
|
88
95
|
|
|
89
96
|
def prepare_artifacts(context)
|
|
@@ -146,13 +153,24 @@ module CleoQualityReview
|
|
|
146
153
|
artifacts.write_run(run)
|
|
147
154
|
end
|
|
148
155
|
|
|
149
|
-
def review_id_for(changes, check_classes)
|
|
156
|
+
def review_id_for(changes, check_classes, base_ref:)
|
|
157
|
+
payload = {
|
|
158
|
+
diff: changes.to_s,
|
|
159
|
+
checks: check_classes.map(&:check_name).sort,
|
|
160
|
+
}
|
|
161
|
+
payload[:base_ref] = base_ref unless base_ref == GitDiffBase::DEFAULT_BASE_REF
|
|
162
|
+
|
|
150
163
|
Digest::SHA256.hexdigest(
|
|
151
|
-
JSON.generate(
|
|
152
|
-
diff: changes.to_s,
|
|
153
|
-
checks: check_classes.map(&:check_name).sort,
|
|
154
|
-
),
|
|
164
|
+
JSON.generate(payload),
|
|
155
165
|
)
|
|
156
166
|
end
|
|
167
|
+
|
|
168
|
+
def changed_mode?
|
|
169
|
+
options.changed || options.files.empty?
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def base_ref
|
|
173
|
+
options.base || GitDiffBase::DEFAULT_BASE_REF
|
|
174
|
+
end
|
|
157
175
|
end
|
|
158
176
|
end
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "configuration"
|
|
4
|
+
require_relative "git_diff_base"
|
|
4
5
|
|
|
5
6
|
module CleoQualityReview
|
|
6
7
|
##
|
|
7
8
|
# Resolves target files for quality review based on git changes and configuration
|
|
8
9
|
class TargetResolver
|
|
9
|
-
BASE_REF =
|
|
10
|
+
BASE_REF = GitDiffBase::DEFAULT_BASE_REF
|
|
10
11
|
|
|
11
12
|
##
|
|
12
13
|
# Value object containing resolved file lists
|
|
@@ -20,9 +21,11 @@ module CleoQualityReview
|
|
|
20
21
|
##
|
|
21
22
|
# @param [CommandRunner] command_runner for executing git commands
|
|
22
23
|
# @param [Configuration] configuration file filtering configuration
|
|
23
|
-
|
|
24
|
+
# @param [String] base_ref git ref to compare changed files against
|
|
25
|
+
def initialize(command_runner:, configuration: Configuration.load, base_ref: GitDiffBase::DEFAULT_BASE_REF)
|
|
24
26
|
@command_runner = command_runner
|
|
25
27
|
@configuration = configuration
|
|
28
|
+
@base_ref = base_ref || GitDiffBase::DEFAULT_BASE_REF
|
|
26
29
|
end
|
|
27
30
|
|
|
28
31
|
##
|
|
@@ -41,7 +44,7 @@ module CleoQualityReview
|
|
|
41
44
|
|
|
42
45
|
private
|
|
43
46
|
|
|
44
|
-
attr_reader :command_runner, :configuration
|
|
47
|
+
attr_reader :command_runner, :configuration, :base_ref
|
|
45
48
|
|
|
46
49
|
def resolve_target_files(files, changed:)
|
|
47
50
|
candidates = resolve_candidates(files, changed: changed)
|
|
@@ -91,12 +94,7 @@ module CleoQualityReview
|
|
|
91
94
|
end
|
|
92
95
|
|
|
93
96
|
def diff_base
|
|
94
|
-
@diff_base ||=
|
|
95
|
-
result = command_runner.run("git", "merge-base", BASE_REF, "HEAD")
|
|
96
|
-
base = result.stdout.strip
|
|
97
|
-
|
|
98
|
-
result.success? && !base.empty? ? base : BASE_REF
|
|
99
|
-
end
|
|
97
|
+
@diff_base ||= GitDiffBase.resolve(command_runner: command_runner, base_ref: base_ref, strict: true)
|
|
100
98
|
end
|
|
101
99
|
|
|
102
100
|
def expand_target_paths(paths)
|
data/lib/cleo_quality_review.rb
CHANGED
|
@@ -14,7 +14,6 @@ module CleoQualityReview
|
|
|
14
14
|
Checks.register("Reek", Checks::Reek, tool_type: :smell_detection)
|
|
15
15
|
Checks.register("Flog", Checks::Flog, tool_type: :complexity)
|
|
16
16
|
Checks.register("Fasterer", Checks::Fasterer, tool_type: :performance)
|
|
17
|
-
Checks.register("Debride", Checks::Debride, tool_type: :dead_code)
|
|
18
17
|
|
|
19
18
|
##
|
|
20
19
|
# Register all supported LLM APIs for formatting output here
|
data/prompts/agent.md
CHANGED
|
@@ -7,7 +7,6 @@ Analyze the raw tool outputs and git diff provided. Prioritize actionable issues
|
|
|
7
7
|
- **Flog**: Ignore scores below 40.0
|
|
8
8
|
- **Reek**: Focus on FeatureEnvy, TooManyStatements, DuplicateMethodCall, NestedIterators, LongParameterList
|
|
9
9
|
- **Fasterer**: Include all performance suggestions
|
|
10
|
-
- **Debride**: Treat as lower-confidence static dead-code detection. Include only findings that are clearly actionable and avoid recommending deletion without checking dynamic call paths.
|
|
11
10
|
|
|
12
11
|
## Output Format
|
|
13
12
|
|
|
@@ -21,7 +20,7 @@ Output valid JSON matching this exact schema:
|
|
|
21
20
|
"target_files": [<file paths from metadata>],
|
|
22
21
|
"findings": [
|
|
23
22
|
{
|
|
24
|
-
"tool_name": "<reek|flog|fasterer
|
|
23
|
+
"tool_name": "<reek|flog|fasterer>",
|
|
25
24
|
"tool_type": "<smell_detection|complexity|performance|dead_code>",
|
|
26
25
|
"check": "<specific check type>",
|
|
27
26
|
"filepath": "<relative file path>",
|
|
@@ -33,7 +32,7 @@ Output valid JSON matching this exact schema:
|
|
|
33
32
|
"check_outputs": [
|
|
34
33
|
{
|
|
35
34
|
"check_name": "<check name>",
|
|
36
|
-
"tool_name": "<reek|flog|fasterer
|
|
35
|
+
"tool_name": "<reek|flog|fasterer>",
|
|
37
36
|
"tool_type": "<smell_detection|complexity|performance|dead_code>",
|
|
38
37
|
"extension": "<json|txt>",
|
|
39
38
|
"path": "<raw output artifact path>",
|
data/prompts/github.md
CHANGED
|
@@ -17,10 +17,6 @@ Reek:
|
|
|
17
17
|
Fasterer:
|
|
18
18
|
Severity: Low
|
|
19
19
|
|
|
20
|
-
Debride:
|
|
21
|
-
Severity: Low
|
|
22
|
-
Notes: Lower-confidence static dead-code signal. Only report when the finding is specific, actionable, and unlikely to be a dynamic Rails call.
|
|
23
|
-
|
|
24
20
|
|
|
25
21
|
You MUST NOT return so many items that the feedback is noisy and confusing. Limit yourself to maximum 10 comments.
|
|
26
22
|
|
data/prompts/human.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
You are reviewing a local code change for code quality.
|
|
2
2
|
|
|
3
|
-
The files provided include git diffs for local code changes, as well as generated output files from various code quality assessment tools including (but not limited to) Reek, Flog, Fasterer,
|
|
3
|
+
The files provided include git diffs for local code changes, as well as generated output files from various code quality assessment tools including (but not limited to) Reek, Flog, Fasterer, etc.
|
|
4
4
|
|
|
5
5
|
Your task is to parse the static output files generated by these tools, and provide feedback to the human user. The diff provided is to allow you to map tool output to changes in the code.
|
|
6
6
|
|
|
7
|
-
YOU MUST NOT comment on the code diff itself, unless the comment is in
|
|
7
|
+
YOU MUST NOT comment on the code diff itself, unless the comment is in relation to an issue reported by a tool.
|
|
8
8
|
|
|
9
9
|
Prioritize issues that are likely to matter to maintainability, correctness, readability, or long-term ownership.
|
|
10
10
|
|
|
@@ -12,9 +12,8 @@ Avoid repeating tool output mechanically. If multiple issues of the same sort ar
|
|
|
12
12
|
|
|
13
13
|
If a tool finding is low value or likely a false positive, say so briefly or omit it.
|
|
14
14
|
|
|
15
|
-
Debride findings are lower-confidence static dead-code candidates. Do not recommend deleting code unless the finding is clearly supported by the changed code and dynamic call paths have been considered.
|
|
16
15
|
|
|
17
|
-
The output will be printed in a
|
|
16
|
+
The output will be printed in a Unix terminal, and so colour-coded feedback is preferable.
|
|
18
17
|
|
|
19
18
|
1. Highest-impact issues first, with file and line references as clickable links when available.
|
|
20
19
|
2. Suggested changes that are specific enough for an engineer or coding agent to implement.
|
data/prompts/pr_review.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
You are the pipeline interface between code quality tools and GitHub pull request review comments.
|
|
2
2
|
|
|
3
|
-
You will collate data from code quality tools including Reek, Flog,
|
|
3
|
+
You will collate data from code quality tools including Reek, Flog, and Fasterer. The raw output is noisy, so your job is to identify only the most useful comments for the engineer whose PR triggered this flow.
|
|
4
4
|
|
|
5
5
|
You MUST NOT comment on the code diff itself unless the comment is directly supported by a tool finding.
|
|
6
6
|
|
|
@@ -9,7 +9,6 @@ You MUST NOT comment on the code diff itself unless the comment is directly supp
|
|
|
9
9
|
- **Flog**: Ignore scores below 40.0. Prioritize high-complexity methods because they are the most expensive to maintain.
|
|
10
10
|
- **Reek**: Prefer actionable smells such as FeatureEnvy, TooManyStatements, DuplicateMethodCall, NestedIterators, and LongParameterList.
|
|
11
11
|
- **Fasterer**: Low severity. Include only when the finding is clearly on code changed by this PR and the fix is straightforward.
|
|
12
|
-
- **Debride**: Lower-confidence static dead-code signal. Include only when the candidate method is clearly made obsolete by this PR, and do not suggest deletion without noting possible dynamic Rails calls.
|
|
13
12
|
|
|
14
13
|
## Comment Selection
|
|
15
14
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cleo_quality_review
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gavin Morrice
|
|
@@ -9,20 +9,6 @@ bindir: exe
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
-
- !ruby/object:Gem::Dependency
|
|
13
|
-
name: debride
|
|
14
|
-
requirement: !ruby/object:Gem::Requirement
|
|
15
|
-
requirements:
|
|
16
|
-
- - ">="
|
|
17
|
-
- !ruby/object:Gem::Version
|
|
18
|
-
version: '0'
|
|
19
|
-
type: :runtime
|
|
20
|
-
prerelease: false
|
|
21
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
-
requirements:
|
|
23
|
-
- - ">="
|
|
24
|
-
- !ruby/object:Gem::Version
|
|
25
|
-
version: '0'
|
|
26
12
|
- !ruby/object:Gem::Dependency
|
|
27
13
|
name: fasterer
|
|
28
14
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -80,7 +66,6 @@ files:
|
|
|
80
66
|
- lib/cleo_quality_review.rb
|
|
81
67
|
- lib/cleo_quality_review/changes_diff.rb
|
|
82
68
|
- lib/cleo_quality_review/checks.rb
|
|
83
|
-
- lib/cleo_quality_review/checks/debride.rb
|
|
84
69
|
- lib/cleo_quality_review/checks/fasterer.rb
|
|
85
70
|
- lib/cleo_quality_review/checks/flog.rb
|
|
86
71
|
- lib/cleo_quality_review/checks/quality_check.rb
|
|
@@ -92,6 +77,7 @@ files:
|
|
|
92
77
|
- lib/cleo_quality_review/configuration.rb
|
|
93
78
|
- lib/cleo_quality_review/diff_map.rb
|
|
94
79
|
- lib/cleo_quality_review/formatter.rb
|
|
80
|
+
- lib/cleo_quality_review/git_diff_base.rb
|
|
95
81
|
- lib/cleo_quality_review/github_review_builder.rb
|
|
96
82
|
- lib/cleo_quality_review/github_review_publisher.rb
|
|
97
83
|
- lib/cleo_quality_review/llm_client.rb
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "json"
|
|
4
|
-
|
|
5
|
-
require_relative "quality_check"
|
|
6
|
-
|
|
7
|
-
module CleoQualityReview
|
|
8
|
-
module Checks
|
|
9
|
-
##
|
|
10
|
-
# Quality check implementation for Debride unused-code analyzer
|
|
11
|
-
class Debride < QualityCheck
|
|
12
|
-
self.check_name = "dead_code"
|
|
13
|
-
self.tool_name = "debride"
|
|
14
|
-
self.output_extension = "json"
|
|
15
|
-
|
|
16
|
-
private
|
|
17
|
-
|
|
18
|
-
def command(files)
|
|
19
|
-
[ruby_executable, gem_executable("debride", "debride"), "--json", "--rails", *files]
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def parse(stdout, stderr)
|
|
23
|
-
findings = missing_methods(stdout).flat_map do |class_name, methods|
|
|
24
|
-
results_for_class(class_name, methods)
|
|
25
|
-
end
|
|
26
|
-
return findings unless findings.empty? && stderr.to_s.strip != ""
|
|
27
|
-
|
|
28
|
-
[result(check: "Execution error", message: stderr, filepath: nil)]
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def missing_methods(stdout)
|
|
32
|
-
parsed = JSON.parse(stdout.to_s)
|
|
33
|
-
missing = parsed.fetch("missing", {})
|
|
34
|
-
return {} unless missing.is_a?(Hash)
|
|
35
|
-
|
|
36
|
-
missing
|
|
37
|
-
rescue JSON::ParserError
|
|
38
|
-
{}
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def results_for_class(class_name, methods)
|
|
42
|
-
Array(methods).map { |entry| method_to_result(class_name, entry) }
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def method_to_result(class_name, entry)
|
|
46
|
-
method_name, location = Array(entry)
|
|
47
|
-
filepath, line = parse_location(location)
|
|
48
|
-
|
|
49
|
-
result(
|
|
50
|
-
check: "PotentialDeadMethod",
|
|
51
|
-
message: "#{class_name}##{method_name} might not be called",
|
|
52
|
-
filepath: filepath,
|
|
53
|
-
line: line,
|
|
54
|
-
)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def parse_location(location)
|
|
58
|
-
match = location.to_s.match(/\A(?<filepath>.*):(?<line>\d+)(?:-\d+)?\z/)
|
|
59
|
-
return [nil, nil] unless match
|
|
60
|
-
|
|
61
|
-
match.values_at(:filepath, :line)
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
end
|