aidp 0.24.0 → 0.25.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 +27 -1
- 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/harness/config_schema.rb +50 -0
- data/lib/aidp/harness/provider_factory.rb +2 -0
- data/lib/aidp/message_display.rb +10 -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/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/version.rb +1 -1
- data/lib/aidp/watch/build_processor.rb +66 -16
- data/lib/aidp/watch/ci_fix_processor.rb +448 -0
- data/lib/aidp/watch/plan_processor.rb +12 -2
- data/lib/aidp/watch/repository_client.rb +380 -0
- 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 +185 -0
- data/lib/aidp/watch/state_store.rb +53 -0
- data/lib/aidp.rb +1 -0
- metadata +20 -1
|
@@ -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
|
data/lib/aidp/watch/runner.rb
CHANGED
|
@@ -9,6 +9,9 @@ require_relative "state_store"
|
|
|
9
9
|
require_relative "plan_generator"
|
|
10
10
|
require_relative "plan_processor"
|
|
11
11
|
require_relative "build_processor"
|
|
12
|
+
require_relative "../auto_update"
|
|
13
|
+
require_relative "review_processor"
|
|
14
|
+
require_relative "ci_fix_processor"
|
|
12
15
|
|
|
13
16
|
module Aidp
|
|
14
17
|
module Watch
|
|
@@ -26,6 +29,8 @@ module Aidp
|
|
|
26
29
|
@project_dir = project_dir
|
|
27
30
|
@force = force
|
|
28
31
|
@verbose = verbose
|
|
32
|
+
@provider_name = provider_name
|
|
33
|
+
@safety_config = safety_config
|
|
29
34
|
|
|
30
35
|
owner, repo = RepositoryClient.parse_issues_url(issues_url)
|
|
31
36
|
@repository_client = RepositoryClient.new(owner: owner, repo: repo, gh_available: gh_available)
|
|
@@ -52,9 +57,32 @@ module Aidp
|
|
|
52
57
|
verbose: verbose,
|
|
53
58
|
label_config: label_config
|
|
54
59
|
)
|
|
60
|
+
|
|
61
|
+
# Initialize auto-update coordinator
|
|
62
|
+
@auto_update_coordinator = Aidp::AutoUpdate.coordinator(project_dir: project_dir)
|
|
63
|
+
@last_update_check = nil
|
|
64
|
+
@review_processor = ReviewProcessor.new(
|
|
65
|
+
repository_client: @repository_client,
|
|
66
|
+
state_store: @state_store,
|
|
67
|
+
provider_name: provider_name,
|
|
68
|
+
project_dir: project_dir,
|
|
69
|
+
label_config: label_config,
|
|
70
|
+
verbose: verbose
|
|
71
|
+
)
|
|
72
|
+
@ci_fix_processor = CiFixProcessor.new(
|
|
73
|
+
repository_client: @repository_client,
|
|
74
|
+
state_store: @state_store,
|
|
75
|
+
provider_name: provider_name,
|
|
76
|
+
project_dir: project_dir,
|
|
77
|
+
label_config: label_config,
|
|
78
|
+
verbose: verbose
|
|
79
|
+
)
|
|
55
80
|
end
|
|
56
81
|
|
|
57
82
|
def start
|
|
83
|
+
# Check for and restore from checkpoint (after auto-update)
|
|
84
|
+
restore_from_checkpoint_if_exists
|
|
85
|
+
|
|
58
86
|
# Validate safety requirements before starting
|
|
59
87
|
@safety_checker.validate_watch_mode_safety!(force: @force)
|
|
60
88
|
|
|
@@ -91,6 +119,9 @@ module Aidp
|
|
|
91
119
|
def process_cycle
|
|
92
120
|
process_plan_triggers
|
|
93
121
|
process_build_triggers
|
|
122
|
+
check_for_updates_if_due
|
|
123
|
+
process_review_triggers
|
|
124
|
+
process_ci_fix_triggers
|
|
94
125
|
end
|
|
95
126
|
|
|
96
127
|
def process_plan_triggers
|
|
@@ -149,12 +180,166 @@ module Aidp
|
|
|
149
180
|
end
|
|
150
181
|
end
|
|
151
182
|
|
|
183
|
+
def process_review_triggers
|
|
184
|
+
review_label = @review_processor.review_label
|
|
185
|
+
prs = @repository_client.list_pull_requests(labels: [review_label], state: "open")
|
|
186
|
+
Aidp.log_debug("watch_runner", "review_poll", label: review_label, total: prs.size)
|
|
187
|
+
prs.each do |pr|
|
|
188
|
+
unless pr_has_label?(pr, review_label)
|
|
189
|
+
Aidp.log_debug("watch_runner", "review_skip_label_mismatch", pr: pr[:number], labels: pr[:labels])
|
|
190
|
+
next
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
detailed = @repository_client.fetch_pull_request(pr[:number])
|
|
194
|
+
|
|
195
|
+
# Check author authorization before processing
|
|
196
|
+
unless @safety_checker.should_process_issue?(detailed, enforce: false)
|
|
197
|
+
Aidp.log_debug("watch_runner", "review_skip_unauthorized_author", pr: detailed[:number], author: detailed[:author])
|
|
198
|
+
next
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
Aidp.log_debug("watch_runner", "review_process", pr: detailed[:number])
|
|
202
|
+
@review_processor.process(detailed)
|
|
203
|
+
rescue RepositorySafetyChecker::UnauthorizedAuthorError => e
|
|
204
|
+
Aidp.log_warn("watch_runner", "unauthorized PR author", pr: pr[:number], error: e.message)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def process_ci_fix_triggers
|
|
209
|
+
ci_fix_label = @ci_fix_processor.ci_fix_label
|
|
210
|
+
prs = @repository_client.list_pull_requests(labels: [ci_fix_label], state: "open")
|
|
211
|
+
Aidp.log_debug("watch_runner", "ci_fix_poll", label: ci_fix_label, total: prs.size)
|
|
212
|
+
prs.each do |pr|
|
|
213
|
+
unless pr_has_label?(pr, ci_fix_label)
|
|
214
|
+
Aidp.log_debug("watch_runner", "ci_fix_skip_label_mismatch", pr: pr[:number], labels: pr[:labels])
|
|
215
|
+
next
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
detailed = @repository_client.fetch_pull_request(pr[:number])
|
|
219
|
+
|
|
220
|
+
# Check author authorization before processing
|
|
221
|
+
unless @safety_checker.should_process_issue?(detailed, enforce: false)
|
|
222
|
+
Aidp.log_debug("watch_runner", "ci_fix_skip_unauthorized_author", pr: detailed[:number], author: detailed[:author])
|
|
223
|
+
next
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
Aidp.log_debug("watch_runner", "ci_fix_process", pr: detailed[:number])
|
|
227
|
+
@ci_fix_processor.process(detailed)
|
|
228
|
+
rescue RepositorySafetyChecker::UnauthorizedAuthorError => e
|
|
229
|
+
Aidp.log_warn("watch_runner", "unauthorized PR author", pr: pr[:number], error: e.message)
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
152
233
|
def issue_has_label?(issue, label)
|
|
153
234
|
Array(issue[:labels]).any? do |issue_label|
|
|
154
235
|
name = issue_label.is_a?(Hash) ? issue_label["name"] : issue_label.to_s
|
|
155
236
|
name.casecmp(label).zero?
|
|
156
237
|
end
|
|
157
238
|
end
|
|
239
|
+
|
|
240
|
+
# Restore from checkpoint if one exists (after auto-update)
|
|
241
|
+
def restore_from_checkpoint_if_exists
|
|
242
|
+
return unless @auto_update_coordinator.policy.enabled
|
|
243
|
+
|
|
244
|
+
checkpoint = @auto_update_coordinator.restore_from_checkpoint
|
|
245
|
+
return unless checkpoint
|
|
246
|
+
|
|
247
|
+
# Checkpoint exists and was successfully restored
|
|
248
|
+
display_message("✨ Restored from checkpoint after update to v#{Aidp::VERSION}", type: :success)
|
|
249
|
+
|
|
250
|
+
# Override instance variables with checkpoint state
|
|
251
|
+
if checkpoint.watch_mode? && checkpoint.watch_state
|
|
252
|
+
@interval = checkpoint.watch_state[:interval] || @interval
|
|
253
|
+
Aidp.log_info("watch_runner", "checkpoint_restored",
|
|
254
|
+
checkpoint_id: checkpoint.checkpoint_id,
|
|
255
|
+
interval: @interval)
|
|
256
|
+
end
|
|
257
|
+
rescue => e
|
|
258
|
+
# Log but don't fail - continue with fresh start
|
|
259
|
+
Aidp.log_error("watch_runner", "checkpoint_restore_failed",
|
|
260
|
+
error: e.message)
|
|
261
|
+
display_message("⚠️ Checkpoint restore failed, starting fresh: #{e.message}", type: :warning)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Check for updates at appropriate intervals
|
|
265
|
+
def check_for_updates_if_due
|
|
266
|
+
return unless @auto_update_coordinator.policy.enabled
|
|
267
|
+
return unless time_for_update_check?
|
|
268
|
+
|
|
269
|
+
update_check = @auto_update_coordinator.check_for_update
|
|
270
|
+
|
|
271
|
+
if update_check.should_update?
|
|
272
|
+
display_message("🔄 Update available: #{update_check.current_version} → #{update_check.available_version}", type: :highlight)
|
|
273
|
+
display_message(" Saving checkpoint and initiating update...", type: :muted)
|
|
274
|
+
|
|
275
|
+
initiate_update(update_check)
|
|
276
|
+
# Never returns - exits with code 75
|
|
277
|
+
end
|
|
278
|
+
rescue Aidp::AutoUpdate::UpdateLoopError => e
|
|
279
|
+
# Restart loop detected - disable auto-update
|
|
280
|
+
display_message("⚠️ Auto-update disabled: #{e.message}", type: :error)
|
|
281
|
+
Aidp.log_error("watch_runner", "update_loop_detected", error: e.message)
|
|
282
|
+
rescue Aidp::AutoUpdate::UpdateError => e
|
|
283
|
+
# Non-fatal update error - log and continue
|
|
284
|
+
Aidp.log_error("watch_runner", "update_check_failed", error: e.message)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Determine if it's time to check for updates
|
|
288
|
+
# @return [Boolean]
|
|
289
|
+
def time_for_update_check?
|
|
290
|
+
return true if @last_update_check.nil?
|
|
291
|
+
|
|
292
|
+
elapsed = Time.now - @last_update_check
|
|
293
|
+
elapsed >= @auto_update_coordinator.policy.check_interval_seconds
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# Initiate update process: capture state, create checkpoint, exit
|
|
297
|
+
# @param update_check [Aidp::AutoUpdate::UpdateCheck] Update check result
|
|
298
|
+
def initiate_update(update_check)
|
|
299
|
+
current_state = capture_current_state
|
|
300
|
+
|
|
301
|
+
# This will exit with code 75 if successful
|
|
302
|
+
@auto_update_coordinator.initiate_update(current_state)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# Capture current watch mode state for checkpoint
|
|
306
|
+
# @return [Hash] Current state
|
|
307
|
+
def capture_current_state
|
|
308
|
+
{
|
|
309
|
+
mode: "watch",
|
|
310
|
+
watch_state: {
|
|
311
|
+
repository: @repository_client.full_repo,
|
|
312
|
+
interval: @interval,
|
|
313
|
+
provider_name: @provider_name,
|
|
314
|
+
persona: nil,
|
|
315
|
+
safety_config: @safety_config,
|
|
316
|
+
worktree_context: capture_worktree_context,
|
|
317
|
+
state_store_snapshot: @state_store.send(:state).dup
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Capture git worktree context
|
|
323
|
+
# @return [Hash] Worktree information
|
|
324
|
+
def capture_worktree_context
|
|
325
|
+
return {} unless system("git rev-parse --git-dir > /dev/null 2>&1")
|
|
326
|
+
|
|
327
|
+
{
|
|
328
|
+
branch: `git rev-parse --abbrev-ref HEAD`.strip,
|
|
329
|
+
commit_sha: `git rev-parse HEAD`.strip,
|
|
330
|
+
remote_url: `git config --get remote.origin.url`.strip
|
|
331
|
+
}
|
|
332
|
+
rescue => e
|
|
333
|
+
Aidp.log_debug("watch_runner", "worktree_context_unavailable", error: e.message)
|
|
334
|
+
{}
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def pr_has_label?(pr, label)
|
|
338
|
+
Array(pr[:labels]).any? do |pr_label|
|
|
339
|
+
name = pr_label.is_a?(Hash) ? pr_label["name"] : pr_label.to_s
|
|
340
|
+
name.casecmp(label).zero?
|
|
341
|
+
end
|
|
342
|
+
end
|
|
158
343
|
end
|
|
159
344
|
end
|
|
160
345
|
end
|
|
@@ -53,6 +53,49 @@ module Aidp
|
|
|
53
53
|
save!
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
+
# Review tracking methods
|
|
57
|
+
def review_processed?(pr_number)
|
|
58
|
+
reviews.key?(pr_number.to_s)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def review_data(pr_number)
|
|
62
|
+
reviews[pr_number.to_s]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def record_review(pr_number, data)
|
|
66
|
+
payload = {
|
|
67
|
+
"timestamp" => data[:timestamp] || Time.now.utc.iso8601,
|
|
68
|
+
"reviewers" => data[:reviewers],
|
|
69
|
+
"total_findings" => data[:total_findings]
|
|
70
|
+
}.compact
|
|
71
|
+
|
|
72
|
+
reviews[pr_number.to_s] = payload
|
|
73
|
+
save!
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# CI fix tracking methods
|
|
77
|
+
def ci_fix_completed?(pr_number)
|
|
78
|
+
fix_data = ci_fixes[pr_number.to_s]
|
|
79
|
+
fix_data && fix_data["status"] == "completed"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def ci_fix_data(pr_number)
|
|
83
|
+
ci_fixes[pr_number.to_s]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def record_ci_fix(pr_number, data)
|
|
87
|
+
payload = {
|
|
88
|
+
"status" => data[:status],
|
|
89
|
+
"timestamp" => data[:timestamp] || Time.now.utc.iso8601,
|
|
90
|
+
"reason" => data[:reason],
|
|
91
|
+
"root_causes" => data[:root_causes],
|
|
92
|
+
"fixes_count" => data[:fixes_count]
|
|
93
|
+
}.compact
|
|
94
|
+
|
|
95
|
+
ci_fixes[pr_number.to_s] = payload
|
|
96
|
+
save!
|
|
97
|
+
end
|
|
98
|
+
|
|
56
99
|
private
|
|
57
100
|
|
|
58
101
|
def ensure_directory
|
|
@@ -81,6 +124,8 @@ module Aidp
|
|
|
81
124
|
base = load_state
|
|
82
125
|
base["plans"] ||= {}
|
|
83
126
|
base["builds"] ||= {}
|
|
127
|
+
base["reviews"] ||= {}
|
|
128
|
+
base["ci_fixes"] ||= {}
|
|
84
129
|
base
|
|
85
130
|
end
|
|
86
131
|
end
|
|
@@ -93,6 +138,14 @@ module Aidp
|
|
|
93
138
|
state["builds"]
|
|
94
139
|
end
|
|
95
140
|
|
|
141
|
+
def reviews
|
|
142
|
+
state["reviews"]
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def ci_fixes
|
|
146
|
+
state["ci_fixes"]
|
|
147
|
+
end
|
|
148
|
+
|
|
96
149
|
def stringify_keys(hash)
|
|
97
150
|
return {} unless hash
|
|
98
151
|
|
data/lib/aidp.rb
CHANGED
|
@@ -26,6 +26,7 @@ require_relative "aidp/providers/base"
|
|
|
26
26
|
require_relative "aidp/providers/cursor"
|
|
27
27
|
require_relative "aidp/providers/anthropic"
|
|
28
28
|
require_relative "aidp/providers/gemini"
|
|
29
|
+
require_relative "aidp/providers/kilocode"
|
|
29
30
|
# Supervised providers removed - using direct execution model
|
|
30
31
|
require_relative "aidp/provider_manager"
|
|
31
32
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: aidp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.25.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bart Agapinan
|
|
@@ -244,6 +244,18 @@ files:
|
|
|
244
244
|
- lib/aidp/analyze/steps.rb
|
|
245
245
|
- lib/aidp/analyze/tree_sitter_grammar_loader.rb
|
|
246
246
|
- lib/aidp/analyze/tree_sitter_scan.rb
|
|
247
|
+
- lib/aidp/auto_update.rb
|
|
248
|
+
- lib/aidp/auto_update/bundler_adapter.rb
|
|
249
|
+
- lib/aidp/auto_update/checkpoint.rb
|
|
250
|
+
- lib/aidp/auto_update/checkpoint_store.rb
|
|
251
|
+
- lib/aidp/auto_update/coordinator.rb
|
|
252
|
+
- lib/aidp/auto_update/errors.rb
|
|
253
|
+
- lib/aidp/auto_update/failure_tracker.rb
|
|
254
|
+
- lib/aidp/auto_update/rubygems_api_adapter.rb
|
|
255
|
+
- lib/aidp/auto_update/update_check.rb
|
|
256
|
+
- lib/aidp/auto_update/update_logger.rb
|
|
257
|
+
- lib/aidp/auto_update/update_policy.rb
|
|
258
|
+
- lib/aidp/auto_update/version_detector.rb
|
|
247
259
|
- lib/aidp/cli.rb
|
|
248
260
|
- lib/aidp/cli/devcontainer_commands.rb
|
|
249
261
|
- lib/aidp/cli/enhanced_input.rb
|
|
@@ -357,6 +369,7 @@ files:
|
|
|
357
369
|
- lib/aidp/providers/error_taxonomy.rb
|
|
358
370
|
- lib/aidp/providers/gemini.rb
|
|
359
371
|
- lib/aidp/providers/github_copilot.rb
|
|
372
|
+
- lib/aidp/providers/kilocode.rb
|
|
360
373
|
- lib/aidp/providers/opencode.rb
|
|
361
374
|
- lib/aidp/rescue_logging.rb
|
|
362
375
|
- lib/aidp/safe_directory.rb
|
|
@@ -387,10 +400,16 @@ files:
|
|
|
387
400
|
- lib/aidp/version.rb
|
|
388
401
|
- lib/aidp/watch.rb
|
|
389
402
|
- lib/aidp/watch/build_processor.rb
|
|
403
|
+
- lib/aidp/watch/ci_fix_processor.rb
|
|
390
404
|
- lib/aidp/watch/plan_generator.rb
|
|
391
405
|
- lib/aidp/watch/plan_processor.rb
|
|
392
406
|
- lib/aidp/watch/repository_client.rb
|
|
393
407
|
- lib/aidp/watch/repository_safety_checker.rb
|
|
408
|
+
- lib/aidp/watch/review_processor.rb
|
|
409
|
+
- lib/aidp/watch/reviewers/base_reviewer.rb
|
|
410
|
+
- lib/aidp/watch/reviewers/performance_reviewer.rb
|
|
411
|
+
- lib/aidp/watch/reviewers/security_reviewer.rb
|
|
412
|
+
- lib/aidp/watch/reviewers/senior_dev_reviewer.rb
|
|
394
413
|
- lib/aidp/watch/runner.rb
|
|
395
414
|
- lib/aidp/watch/state_store.rb
|
|
396
415
|
- lib/aidp/workflows/definitions.rb
|