aidp 0.24.0 → 0.26.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/README.md +72 -7
- data/lib/aidp/analyze/error_handler.rb +11 -0
- data/lib/aidp/auto_update/bundler_adapter.rb +66 -0
- data/lib/aidp/auto_update/checkpoint.rb +178 -0
- data/lib/aidp/auto_update/checkpoint_store.rb +182 -0
- data/lib/aidp/auto_update/coordinator.rb +204 -0
- data/lib/aidp/auto_update/errors.rb +17 -0
- data/lib/aidp/auto_update/failure_tracker.rb +162 -0
- data/lib/aidp/auto_update/rubygems_api_adapter.rb +95 -0
- data/lib/aidp/auto_update/update_check.rb +106 -0
- data/lib/aidp/auto_update/update_logger.rb +143 -0
- data/lib/aidp/auto_update/update_policy.rb +109 -0
- data/lib/aidp/auto_update/version_detector.rb +144 -0
- data/lib/aidp/auto_update.rb +52 -0
- data/lib/aidp/cli.rb +165 -1
- data/lib/aidp/execute/work_loop_runner.rb +225 -55
- data/lib/aidp/harness/config_loader.rb +20 -11
- data/lib/aidp/harness/config_schema.rb +80 -8
- data/lib/aidp/harness/configuration.rb +73 -2
- data/lib/aidp/harness/filter_strategy.rb +45 -0
- data/lib/aidp/harness/generic_filter_strategy.rb +63 -0
- data/lib/aidp/harness/output_filter.rb +136 -0
- data/lib/aidp/harness/provider_factory.rb +2 -0
- data/lib/aidp/harness/provider_manager.rb +18 -3
- data/lib/aidp/harness/rspec_filter_strategy.rb +82 -0
- data/lib/aidp/harness/test_runner.rb +165 -27
- data/lib/aidp/harness/ui/enhanced_tui.rb +4 -1
- data/lib/aidp/logger.rb +35 -5
- data/lib/aidp/message_display.rb +56 -2
- data/lib/aidp/prompt_optimization/style_guide_indexer.rb +3 -1
- data/lib/aidp/provider_manager.rb +2 -0
- data/lib/aidp/providers/kilocode.rb +202 -0
- data/lib/aidp/safe_directory.rb +10 -3
- data/lib/aidp/setup/provider_registry.rb +15 -0
- data/lib/aidp/setup/wizard.rb +12 -4
- data/lib/aidp/skills/composer.rb +4 -0
- data/lib/aidp/skills/loader.rb +3 -1
- data/lib/aidp/storage/csv_storage.rb +9 -3
- data/lib/aidp/storage/file_manager.rb +8 -2
- data/lib/aidp/storage/json_storage.rb +9 -3
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/build_processor.rb +106 -17
- data/lib/aidp/watch/change_request_processor.rb +659 -0
- data/lib/aidp/watch/ci_fix_processor.rb +448 -0
- data/lib/aidp/watch/plan_processor.rb +81 -8
- data/lib/aidp/watch/repository_client.rb +465 -20
- data/lib/aidp/watch/review_processor.rb +266 -0
- data/lib/aidp/watch/reviewers/base_reviewer.rb +164 -0
- data/lib/aidp/watch/reviewers/performance_reviewer.rb +65 -0
- data/lib/aidp/watch/reviewers/security_reviewer.rb +65 -0
- data/lib/aidp/watch/reviewers/senior_dev_reviewer.rb +33 -0
- data/lib/aidp/watch/runner.rb +222 -0
- data/lib/aidp/watch/state_store.rb +99 -1
- data/lib/aidp/workstream_executor.rb +5 -2
- data/lib/aidp.rb +5 -0
- data/templates/aidp.yml.example +53 -0
- metadata +25 -1
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "json"
|
|
5
|
+
require "time"
|
|
6
|
+
|
|
7
|
+
require_relative "../message_display"
|
|
8
|
+
require_relative "reviewers/senior_dev_reviewer"
|
|
9
|
+
require_relative "reviewers/security_reviewer"
|
|
10
|
+
require_relative "reviewers/performance_reviewer"
|
|
11
|
+
|
|
12
|
+
module Aidp
|
|
13
|
+
module Watch
|
|
14
|
+
# Handles the aidp-review label trigger by performing multi-persona code review
|
|
15
|
+
# and posting categorized findings back to the PR.
|
|
16
|
+
class ReviewProcessor
|
|
17
|
+
include Aidp::MessageDisplay
|
|
18
|
+
|
|
19
|
+
# Default label names
|
|
20
|
+
DEFAULT_REVIEW_LABEL = "aidp-review"
|
|
21
|
+
|
|
22
|
+
COMMENT_HEADER = "## 🤖 AIDP Code Review"
|
|
23
|
+
|
|
24
|
+
attr_reader :review_label
|
|
25
|
+
|
|
26
|
+
def initialize(repository_client:, state_store:, provider_name: nil, project_dir: Dir.pwd, label_config: {}, verbose: false)
|
|
27
|
+
@repository_client = repository_client
|
|
28
|
+
@state_store = state_store
|
|
29
|
+
@provider_name = provider_name
|
|
30
|
+
@project_dir = project_dir
|
|
31
|
+
@verbose = verbose
|
|
32
|
+
|
|
33
|
+
# Load label configuration
|
|
34
|
+
@review_label = label_config[:review_trigger] || label_config["review_trigger"] || DEFAULT_REVIEW_LABEL
|
|
35
|
+
|
|
36
|
+
# Initialize reviewers
|
|
37
|
+
@reviewers = [
|
|
38
|
+
Reviewers::SeniorDevReviewer.new(provider_name: provider_name),
|
|
39
|
+
Reviewers::SecurityReviewer.new(provider_name: provider_name),
|
|
40
|
+
Reviewers::PerformanceReviewer.new(provider_name: provider_name)
|
|
41
|
+
]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def process(pr)
|
|
45
|
+
number = pr[:number]
|
|
46
|
+
|
|
47
|
+
if @state_store.review_processed?(number)
|
|
48
|
+
display_message("ℹ️ Review for PR ##{number} already posted. Skipping.", type: :muted)
|
|
49
|
+
return
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
display_message("🔍 Reviewing PR ##{number} (#{pr[:title]})", type: :info)
|
|
53
|
+
|
|
54
|
+
# Fetch PR details
|
|
55
|
+
pr_data = @repository_client.fetch_pull_request(number)
|
|
56
|
+
files = @repository_client.fetch_pull_request_files(number)
|
|
57
|
+
diff = @repository_client.fetch_pull_request_diff(number)
|
|
58
|
+
|
|
59
|
+
# Run reviews in parallel (conceptually - actual implementation is sequential)
|
|
60
|
+
review_results = run_reviews(pr_data: pr_data, files: files, diff: diff)
|
|
61
|
+
|
|
62
|
+
# Log review results
|
|
63
|
+
log_review(number, review_results)
|
|
64
|
+
|
|
65
|
+
# Format and post comment
|
|
66
|
+
comment_body = format_review_comment(pr: pr_data, review_results: review_results)
|
|
67
|
+
@repository_client.post_comment(number, comment_body)
|
|
68
|
+
|
|
69
|
+
display_message("💬 Posted review comment for PR ##{number}", type: :success)
|
|
70
|
+
@state_store.record_review(number, {
|
|
71
|
+
timestamp: Time.now.utc.iso8601,
|
|
72
|
+
reviewers: review_results.map { |r| r[:persona] },
|
|
73
|
+
total_findings: review_results.sum { |r| r[:findings].length }
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
# Remove review label after processing
|
|
77
|
+
begin
|
|
78
|
+
@repository_client.remove_labels(number, @review_label)
|
|
79
|
+
display_message("🏷️ Removed '#{@review_label}' label after review", type: :info)
|
|
80
|
+
rescue => e
|
|
81
|
+
display_message("⚠️ Failed to remove review label: #{e.message}", type: :warn)
|
|
82
|
+
end
|
|
83
|
+
rescue => e
|
|
84
|
+
display_message("❌ Review failed: #{e.message}", type: :error)
|
|
85
|
+
Aidp.log_error("review_processor", "Review failed", pr: number, error: e.message, backtrace: e.backtrace&.first(10))
|
|
86
|
+
|
|
87
|
+
# Post error comment
|
|
88
|
+
error_comment = <<~COMMENT
|
|
89
|
+
#{COMMENT_HEADER}
|
|
90
|
+
|
|
91
|
+
❌ Automated review failed: #{e.message}
|
|
92
|
+
|
|
93
|
+
Please review manually or retry by re-adding the `#{@review_label}` label.
|
|
94
|
+
COMMENT
|
|
95
|
+
begin
|
|
96
|
+
@repository_client.post_comment(number, error_comment)
|
|
97
|
+
rescue
|
|
98
|
+
nil
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
def run_reviews(pr_data:, files:, diff:)
|
|
105
|
+
results = []
|
|
106
|
+
|
|
107
|
+
@reviewers.each do |reviewer|
|
|
108
|
+
display_message(" Running #{reviewer.persona_name} review...", type: :muted) if @verbose
|
|
109
|
+
|
|
110
|
+
begin
|
|
111
|
+
result = reviewer.review(pr_data: pr_data, files: files, diff: diff)
|
|
112
|
+
results << result
|
|
113
|
+
|
|
114
|
+
findings_count = result[:findings].length
|
|
115
|
+
display_message(" ✓ #{reviewer.persona_name}: #{findings_count} findings", type: :muted) if @verbose
|
|
116
|
+
rescue => e
|
|
117
|
+
display_message(" ✗ #{reviewer.persona_name} failed: #{e.message}", type: :warn)
|
|
118
|
+
Aidp.log_error("review_processor", "Reviewer failed", reviewer: reviewer.persona_name, error: e.message)
|
|
119
|
+
# Continue with other reviewers
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
results
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def format_review_comment(pr:, review_results:)
|
|
127
|
+
parts = []
|
|
128
|
+
parts << COMMENT_HEADER
|
|
129
|
+
parts << ""
|
|
130
|
+
parts << "Automated multi-persona code review for PR ##{pr[:number]}"
|
|
131
|
+
parts << ""
|
|
132
|
+
|
|
133
|
+
# Collect all findings by severity
|
|
134
|
+
all_findings = collect_findings_by_severity(review_results)
|
|
135
|
+
|
|
136
|
+
if all_findings.empty?
|
|
137
|
+
parts << "✅ **No issues found!** All reviewers approved the changes."
|
|
138
|
+
parts << ""
|
|
139
|
+
parts << "_The code looks good from architecture, security, and performance perspectives._"
|
|
140
|
+
else
|
|
141
|
+
parts << "### Summary"
|
|
142
|
+
parts << ""
|
|
143
|
+
parts << "| Severity | Count |"
|
|
144
|
+
parts << "|----------|-------|"
|
|
145
|
+
parts << "| 🔴 High Priority | #{all_findings[:high].length} |"
|
|
146
|
+
parts << "| 🟠 Major | #{all_findings[:major].length} |"
|
|
147
|
+
parts << "| 🟡 Minor | #{all_findings[:minor].length} |"
|
|
148
|
+
parts << "| ⚪ Nit | #{all_findings[:nit].length} |"
|
|
149
|
+
parts << ""
|
|
150
|
+
|
|
151
|
+
# Add findings by severity
|
|
152
|
+
if all_findings[:high].any?
|
|
153
|
+
parts << "### 🔴 High Priority Issues"
|
|
154
|
+
parts << ""
|
|
155
|
+
parts << format_findings(all_findings[:high])
|
|
156
|
+
parts << ""
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
if all_findings[:major].any?
|
|
160
|
+
parts << "### 🟠 Major Issues"
|
|
161
|
+
parts << ""
|
|
162
|
+
parts << format_findings(all_findings[:major])
|
|
163
|
+
parts << ""
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
if all_findings[:minor].any?
|
|
167
|
+
parts << "### 🟡 Minor Improvements"
|
|
168
|
+
parts << ""
|
|
169
|
+
parts << format_findings(all_findings[:minor])
|
|
170
|
+
parts << ""
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
if all_findings[:nit].any?
|
|
174
|
+
parts << "<details>"
|
|
175
|
+
parts << "<summary>⚪ Nit-picks (click to expand)</summary>"
|
|
176
|
+
parts << ""
|
|
177
|
+
parts << format_findings(all_findings[:nit])
|
|
178
|
+
parts << ""
|
|
179
|
+
parts << "</details>"
|
|
180
|
+
parts << ""
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Add reviewer attribution
|
|
185
|
+
parts << "---"
|
|
186
|
+
parts << "_Reviewed by: #{review_results.map { |r| r[:persona] }.join(", ")}_"
|
|
187
|
+
|
|
188
|
+
parts.join("\n")
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def collect_findings_by_severity(review_results)
|
|
192
|
+
findings = {high: [], major: [], minor: [], nit: []}
|
|
193
|
+
|
|
194
|
+
review_results.each do |result|
|
|
195
|
+
persona = result[:persona]
|
|
196
|
+
result[:findings].each do |finding|
|
|
197
|
+
severity = finding["severity"]&.to_sym || :minor
|
|
198
|
+
findings[severity] << finding.merge("reviewer" => persona)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
findings
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def format_findings(findings)
|
|
206
|
+
findings.map do |finding|
|
|
207
|
+
parts = []
|
|
208
|
+
|
|
209
|
+
# Header with category and reviewer
|
|
210
|
+
header = "**#{finding["category"]}**"
|
|
211
|
+
header += " (#{finding["reviewer"]})" if finding["reviewer"]
|
|
212
|
+
parts << header
|
|
213
|
+
|
|
214
|
+
# Location if available
|
|
215
|
+
if finding["file"]
|
|
216
|
+
location = "`#{finding["file"]}"
|
|
217
|
+
location += ":#{finding["line"]}" if finding["line"]
|
|
218
|
+
location += "`"
|
|
219
|
+
parts << location
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Message
|
|
223
|
+
parts << finding["message"]
|
|
224
|
+
|
|
225
|
+
# Suggestion if available
|
|
226
|
+
if finding["suggestion"]
|
|
227
|
+
parts << ""
|
|
228
|
+
parts << "<details>"
|
|
229
|
+
parts << "<summary>💡 Suggested fix</summary>"
|
|
230
|
+
parts << ""
|
|
231
|
+
parts << "```suggestion"
|
|
232
|
+
parts << finding["suggestion"]
|
|
233
|
+
parts << "```"
|
|
234
|
+
parts << "</details>"
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
parts.join("\n")
|
|
238
|
+
end.join("\n\n")
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def log_review(pr_number, review_results)
|
|
242
|
+
log_dir = File.join(@project_dir, ".aidp", "logs", "pr_reviews")
|
|
243
|
+
FileUtils.mkdir_p(log_dir)
|
|
244
|
+
|
|
245
|
+
log_file = File.join(log_dir, "pr_#{pr_number}_#{Time.now.utc.strftime("%Y%m%d_%H%M%S")}.json")
|
|
246
|
+
|
|
247
|
+
log_data = {
|
|
248
|
+
pr_number: pr_number,
|
|
249
|
+
timestamp: Time.now.utc.iso8601,
|
|
250
|
+
reviews: review_results.map do |result|
|
|
251
|
+
{
|
|
252
|
+
persona: result[:persona],
|
|
253
|
+
findings_count: result[:findings].length,
|
|
254
|
+
findings: result[:findings]
|
|
255
|
+
}
|
|
256
|
+
end
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
File.write(log_file, JSON.pretty_generate(log_data))
|
|
260
|
+
display_message("📝 Review log saved to #{log_file}", type: :muted) if @verbose
|
|
261
|
+
rescue => e
|
|
262
|
+
display_message("⚠️ Failed to save review log: #{e.message}", type: :warn)
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../provider_manager"
|
|
4
|
+
require_relative "../../harness/config_manager"
|
|
5
|
+
|
|
6
|
+
module Aidp
|
|
7
|
+
module Watch
|
|
8
|
+
module Reviewers
|
|
9
|
+
# Base class for all PR reviewers
|
|
10
|
+
class BaseReviewer
|
|
11
|
+
attr_reader :provider_name, :persona_name, :focus_areas
|
|
12
|
+
|
|
13
|
+
def initialize(provider_name: nil)
|
|
14
|
+
@provider_name = provider_name
|
|
15
|
+
@persona_name = self.class::PERSONA_NAME
|
|
16
|
+
@focus_areas = self.class::FOCUS_AREAS
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Review the PR and return findings
|
|
20
|
+
# @param pr_data [Hash] PR metadata (number, title, body, etc.)
|
|
21
|
+
# @param files [Array<Hash>] Changed files with patches
|
|
22
|
+
# @param diff [String] Full diff content
|
|
23
|
+
# @return [Hash] Review findings with structure:
|
|
24
|
+
# {
|
|
25
|
+
# persona: String,
|
|
26
|
+
# findings: [
|
|
27
|
+
# {
|
|
28
|
+
# severity: "high|major|minor|nit",
|
|
29
|
+
# category: String,
|
|
30
|
+
# message: String,
|
|
31
|
+
# file: String (optional),
|
|
32
|
+
# line: Integer (optional),
|
|
33
|
+
# suggestion: String (optional)
|
|
34
|
+
# }
|
|
35
|
+
# ]
|
|
36
|
+
# }
|
|
37
|
+
def review(pr_data:, files:, diff:)
|
|
38
|
+
raise NotImplementedError, "Subclasses must implement #review"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
protected
|
|
42
|
+
|
|
43
|
+
def provider
|
|
44
|
+
@provider ||= begin
|
|
45
|
+
provider_name = @provider_name || detect_default_provider
|
|
46
|
+
Aidp::ProviderManager.get_provider(provider_name, use_harness: false)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def detect_default_provider
|
|
51
|
+
config_manager = Aidp::Harness::ConfigManager.new(Dir.pwd)
|
|
52
|
+
config_manager.default_provider || "anthropic"
|
|
53
|
+
rescue
|
|
54
|
+
"anthropic"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def system_prompt
|
|
58
|
+
<<~PROMPT
|
|
59
|
+
You are #{@persona_name}, reviewing a pull request.
|
|
60
|
+
|
|
61
|
+
Your focus areas are:
|
|
62
|
+
#{@focus_areas.map { |area| "- #{area}" }.join("\n")}
|
|
63
|
+
|
|
64
|
+
Review the code changes and provide structured feedback in the following JSON format:
|
|
65
|
+
{
|
|
66
|
+
"findings": [
|
|
67
|
+
{
|
|
68
|
+
"severity": "high|major|minor|nit",
|
|
69
|
+
"category": "Brief category (e.g., 'Security', 'Performance', 'Logic Error')",
|
|
70
|
+
"message": "Detailed explanation of the issue",
|
|
71
|
+
"file": "path/to/file.rb",
|
|
72
|
+
"line": 42,
|
|
73
|
+
"suggestion": "Optional: Suggested fix or improvement"
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
Severity levels:
|
|
79
|
+
- high: Critical issues that must be fixed (security vulnerabilities, data loss, crashes)
|
|
80
|
+
- major: Significant problems that should be addressed (incorrect logic, performance issues)
|
|
81
|
+
- minor: Improvements that would be good to have (code quality, maintainability)
|
|
82
|
+
- nit: Stylistic or trivial suggestions (formatting, naming)
|
|
83
|
+
|
|
84
|
+
Focus on actionable, specific feedback. If no issues are found, return an empty findings array.
|
|
85
|
+
PROMPT
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def analyze_with_provider(user_prompt)
|
|
89
|
+
full_prompt = "#{system_prompt}\n\n#{user_prompt}"
|
|
90
|
+
response = provider.send_message(prompt: full_prompt)
|
|
91
|
+
content = response.to_s.strip
|
|
92
|
+
|
|
93
|
+
# Extract JSON from response (handle code fences)
|
|
94
|
+
json_content = extract_json(content)
|
|
95
|
+
|
|
96
|
+
# Parse JSON response
|
|
97
|
+
parsed = JSON.parse(json_content)
|
|
98
|
+
parsed["findings"] || []
|
|
99
|
+
rescue JSON::ParserError => e
|
|
100
|
+
Aidp.log_error("reviewer", "Failed to parse provider response", persona: @persona_name, error: e.message, content: content)
|
|
101
|
+
[]
|
|
102
|
+
rescue => e
|
|
103
|
+
Aidp.log_error("reviewer", "Review failed", persona: @persona_name, error: e.message)
|
|
104
|
+
[]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def extract_json(text)
|
|
108
|
+
# Try to extract JSON from code fences or find JSON object
|
|
109
|
+
# Avoid regex to prevent ReDoS - use simple string operations
|
|
110
|
+
return text if text.start_with?("{") && text.end_with?("}")
|
|
111
|
+
|
|
112
|
+
# Extract from code fence using string operations
|
|
113
|
+
fence_start = text.index("```json")
|
|
114
|
+
if fence_start
|
|
115
|
+
json_start = text.index("{", fence_start)
|
|
116
|
+
fence_end = text.index("```", fence_start + 7)
|
|
117
|
+
if json_start && fence_end && json_start < fence_end
|
|
118
|
+
json_end = text.rindex("}", fence_end - 1)
|
|
119
|
+
return text[json_start..json_end] if json_end && json_end > json_start
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Find JSON object using string operations
|
|
124
|
+
first_brace = text.index("{")
|
|
125
|
+
last_brace = text.rindex("}")
|
|
126
|
+
if first_brace && last_brace && last_brace > first_brace
|
|
127
|
+
text[first_brace..last_brace]
|
|
128
|
+
else
|
|
129
|
+
text
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def build_review_prompt(pr_data:, files:, diff:)
|
|
134
|
+
<<~PROMPT
|
|
135
|
+
Review this pull request from your expertise perspective:
|
|
136
|
+
|
|
137
|
+
**PR ##{pr_data[:number]}: #{pr_data[:title]}**
|
|
138
|
+
|
|
139
|
+
#{pr_data[:body]}
|
|
140
|
+
|
|
141
|
+
**Changed Files (#{files.length}):**
|
|
142
|
+
#{files.map { |f| "- #{f[:filename]} (+#{f[:additions]}/-#{f[:deletions]})" }.join("\n")}
|
|
143
|
+
|
|
144
|
+
**Diff:**
|
|
145
|
+
```diff
|
|
146
|
+
#{truncate_diff(diff, max_lines: 500)}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Please review the changes and provide your findings in JSON format.
|
|
150
|
+
PROMPT
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def truncate_diff(diff, max_lines: 500)
|
|
154
|
+
lines = diff.lines
|
|
155
|
+
if lines.length > max_lines
|
|
156
|
+
lines.first(max_lines).join + "\n... (diff truncated, #{lines.length - max_lines} more lines)"
|
|
157
|
+
else
|
|
158
|
+
diff
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_reviewer"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
module Watch
|
|
7
|
+
module Reviewers
|
|
8
|
+
# Performance Reviewer - focuses on performance issues and optimizations
|
|
9
|
+
class PerformanceReviewer < BaseReviewer
|
|
10
|
+
PERSONA_NAME = "Performance Analyst"
|
|
11
|
+
FOCUS_AREAS = [
|
|
12
|
+
"Algorithm complexity (O(n) vs O(n²), etc.)",
|
|
13
|
+
"Database query optimization (N+1 queries, missing indexes)",
|
|
14
|
+
"Memory allocation and garbage collection pressure",
|
|
15
|
+
"Blocking I/O operations",
|
|
16
|
+
"Inefficient data structures",
|
|
17
|
+
"Unnecessary computations or redundant work",
|
|
18
|
+
"Caching opportunities",
|
|
19
|
+
"Resource leaks (connections, file handles, etc.)",
|
|
20
|
+
"Concurrent access patterns",
|
|
21
|
+
"Network round-trips and latency"
|
|
22
|
+
].freeze
|
|
23
|
+
|
|
24
|
+
def review(pr_data:, files:, diff:)
|
|
25
|
+
user_prompt = build_performance_prompt(pr_data: pr_data, files: files, diff: diff)
|
|
26
|
+
findings = analyze_with_provider(user_prompt)
|
|
27
|
+
|
|
28
|
+
{
|
|
29
|
+
persona: PERSONA_NAME,
|
|
30
|
+
findings: findings
|
|
31
|
+
}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def build_performance_prompt(pr_data:, files:, diff:)
|
|
37
|
+
base_prompt = build_review_prompt(pr_data: pr_data, files: files, diff: diff)
|
|
38
|
+
|
|
39
|
+
<<~PROMPT
|
|
40
|
+
#{base_prompt}
|
|
41
|
+
|
|
42
|
+
**Additional Performance Focus:**
|
|
43
|
+
Pay special attention to:
|
|
44
|
+
1. Loop complexity and nested iterations
|
|
45
|
+
2. Database queries (look for N+1 patterns, missing eager loading)
|
|
46
|
+
3. Memory allocations in hot paths
|
|
47
|
+
4. I/O operations (file, network, database) - are they batched?
|
|
48
|
+
5. Synchronous operations that could be asynchronous
|
|
49
|
+
6. Large data structures being copied or traversed repeatedly
|
|
50
|
+
7. Missing caching opportunities
|
|
51
|
+
8. Resource pooling and connection management
|
|
52
|
+
9. Lazy loading vs eager loading trade-offs
|
|
53
|
+
10. Potential bottlenecks under high load
|
|
54
|
+
|
|
55
|
+
Mark performance issues as:
|
|
56
|
+
- "high" if they could cause timeouts, out-of-memory errors, or severe degradation
|
|
57
|
+
- "major" if they significantly impact response time or resource usage
|
|
58
|
+
- "minor" for optimizations that would improve efficiency
|
|
59
|
+
- "nit" for micro-optimizations with negligible impact
|
|
60
|
+
PROMPT
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_reviewer"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
module Watch
|
|
7
|
+
module Reviewers
|
|
8
|
+
# Security Reviewer - focuses on security vulnerabilities and risks
|
|
9
|
+
class SecurityReviewer < BaseReviewer
|
|
10
|
+
PERSONA_NAME = "Security Specialist"
|
|
11
|
+
FOCUS_AREAS = [
|
|
12
|
+
"Injection vulnerabilities (SQL, command, XSS, etc.)",
|
|
13
|
+
"Authentication and authorization flaws",
|
|
14
|
+
"Sensitive data exposure",
|
|
15
|
+
"Insecure deserialization",
|
|
16
|
+
"Security misconfiguration",
|
|
17
|
+
"Insufficient logging and monitoring",
|
|
18
|
+
"Insecure dependencies",
|
|
19
|
+
"Secrets and credentials in code",
|
|
20
|
+
"Input validation and sanitization",
|
|
21
|
+
"OWASP Top 10 vulnerabilities"
|
|
22
|
+
].freeze
|
|
23
|
+
|
|
24
|
+
def review(pr_data:, files:, diff:)
|
|
25
|
+
user_prompt = build_security_prompt(pr_data: pr_data, files: files, diff: diff)
|
|
26
|
+
findings = analyze_with_provider(user_prompt)
|
|
27
|
+
|
|
28
|
+
{
|
|
29
|
+
persona: PERSONA_NAME,
|
|
30
|
+
findings: findings
|
|
31
|
+
}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def build_security_prompt(pr_data:, files:, diff:)
|
|
37
|
+
base_prompt = build_review_prompt(pr_data: pr_data, files: files, diff: diff)
|
|
38
|
+
|
|
39
|
+
<<~PROMPT
|
|
40
|
+
#{base_prompt}
|
|
41
|
+
|
|
42
|
+
**Additional Security Focus:**
|
|
43
|
+
Pay special attention to:
|
|
44
|
+
1. User input handling and validation
|
|
45
|
+
2. Database queries and potential SQL injection
|
|
46
|
+
3. File operations and path traversal risks
|
|
47
|
+
4. Authentication and session management
|
|
48
|
+
5. Cryptographic operations and key management
|
|
49
|
+
6. API endpoints and their access controls
|
|
50
|
+
7. Third-party dependencies and their security posture
|
|
51
|
+
8. Environment variables and configuration
|
|
52
|
+
9. Logging of sensitive information
|
|
53
|
+
10. Command execution and shell injection risks
|
|
54
|
+
|
|
55
|
+
Mark any security issues as "high" severity if they could lead to:
|
|
56
|
+
- Unauthorized access or privilege escalation
|
|
57
|
+
- Data breach or exposure of sensitive information
|
|
58
|
+
- Remote code execution
|
|
59
|
+
- Denial of service
|
|
60
|
+
PROMPT
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_reviewer"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
module Watch
|
|
7
|
+
module Reviewers
|
|
8
|
+
# Senior Developer Reviewer - focuses on correctness, architecture, and best practices
|
|
9
|
+
class SeniorDevReviewer < BaseReviewer
|
|
10
|
+
PERSONA_NAME = "Senior Developer"
|
|
11
|
+
FOCUS_AREAS = [
|
|
12
|
+
"Code correctness and logic errors",
|
|
13
|
+
"Architecture and design patterns",
|
|
14
|
+
"API design and consistency",
|
|
15
|
+
"Error handling and edge cases",
|
|
16
|
+
"Code maintainability and readability",
|
|
17
|
+
"Testing coverage and quality",
|
|
18
|
+
"Documentation completeness"
|
|
19
|
+
].freeze
|
|
20
|
+
|
|
21
|
+
def review(pr_data:, files:, diff:)
|
|
22
|
+
user_prompt = build_review_prompt(pr_data: pr_data, files: files, diff: diff)
|
|
23
|
+
findings = analyze_with_provider(user_prompt)
|
|
24
|
+
|
|
25
|
+
{
|
|
26
|
+
persona: PERSONA_NAME,
|
|
27
|
+
findings: findings
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|