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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +72 -7
  3. data/lib/aidp/analyze/error_handler.rb +11 -0
  4. data/lib/aidp/auto_update/bundler_adapter.rb +66 -0
  5. data/lib/aidp/auto_update/checkpoint.rb +178 -0
  6. data/lib/aidp/auto_update/checkpoint_store.rb +182 -0
  7. data/lib/aidp/auto_update/coordinator.rb +204 -0
  8. data/lib/aidp/auto_update/errors.rb +17 -0
  9. data/lib/aidp/auto_update/failure_tracker.rb +162 -0
  10. data/lib/aidp/auto_update/rubygems_api_adapter.rb +95 -0
  11. data/lib/aidp/auto_update/update_check.rb +106 -0
  12. data/lib/aidp/auto_update/update_logger.rb +143 -0
  13. data/lib/aidp/auto_update/update_policy.rb +109 -0
  14. data/lib/aidp/auto_update/version_detector.rb +144 -0
  15. data/lib/aidp/auto_update.rb +52 -0
  16. data/lib/aidp/cli.rb +165 -1
  17. data/lib/aidp/execute/work_loop_runner.rb +225 -55
  18. data/lib/aidp/harness/config_loader.rb +20 -11
  19. data/lib/aidp/harness/config_schema.rb +80 -8
  20. data/lib/aidp/harness/configuration.rb +73 -2
  21. data/lib/aidp/harness/filter_strategy.rb +45 -0
  22. data/lib/aidp/harness/generic_filter_strategy.rb +63 -0
  23. data/lib/aidp/harness/output_filter.rb +136 -0
  24. data/lib/aidp/harness/provider_factory.rb +2 -0
  25. data/lib/aidp/harness/provider_manager.rb +18 -3
  26. data/lib/aidp/harness/rspec_filter_strategy.rb +82 -0
  27. data/lib/aidp/harness/test_runner.rb +165 -27
  28. data/lib/aidp/harness/ui/enhanced_tui.rb +4 -1
  29. data/lib/aidp/logger.rb +35 -5
  30. data/lib/aidp/message_display.rb +56 -2
  31. data/lib/aidp/prompt_optimization/style_guide_indexer.rb +3 -1
  32. data/lib/aidp/provider_manager.rb +2 -0
  33. data/lib/aidp/providers/kilocode.rb +202 -0
  34. data/lib/aidp/safe_directory.rb +10 -3
  35. data/lib/aidp/setup/provider_registry.rb +15 -0
  36. data/lib/aidp/setup/wizard.rb +12 -4
  37. data/lib/aidp/skills/composer.rb +4 -0
  38. data/lib/aidp/skills/loader.rb +3 -1
  39. data/lib/aidp/storage/csv_storage.rb +9 -3
  40. data/lib/aidp/storage/file_manager.rb +8 -2
  41. data/lib/aidp/storage/json_storage.rb +9 -3
  42. data/lib/aidp/version.rb +1 -1
  43. data/lib/aidp/watch/build_processor.rb +106 -17
  44. data/lib/aidp/watch/change_request_processor.rb +659 -0
  45. data/lib/aidp/watch/ci_fix_processor.rb +448 -0
  46. data/lib/aidp/watch/plan_processor.rb +81 -8
  47. data/lib/aidp/watch/repository_client.rb +465 -20
  48. data/lib/aidp/watch/review_processor.rb +266 -0
  49. data/lib/aidp/watch/reviewers/base_reviewer.rb +164 -0
  50. data/lib/aidp/watch/reviewers/performance_reviewer.rb +65 -0
  51. data/lib/aidp/watch/reviewers/security_reviewer.rb +65 -0
  52. data/lib/aidp/watch/reviewers/senior_dev_reviewer.rb +33 -0
  53. data/lib/aidp/watch/runner.rb +222 -0
  54. data/lib/aidp/watch/state_store.rb +99 -1
  55. data/lib/aidp/workstream_executor.rb +5 -2
  56. data/lib/aidp.rb +5 -0
  57. data/templates/aidp.yml.example +53 -0
  58. 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