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,448 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "json"
|
|
6
|
+
require "time"
|
|
7
|
+
|
|
8
|
+
require_relative "../message_display"
|
|
9
|
+
require_relative "../provider_manager"
|
|
10
|
+
require_relative "../harness/config_manager"
|
|
11
|
+
require_relative "../execute/prompt_manager"
|
|
12
|
+
require_relative "../harness/runner"
|
|
13
|
+
require_relative "../harness/state_manager"
|
|
14
|
+
|
|
15
|
+
module Aidp
|
|
16
|
+
module Watch
|
|
17
|
+
# Handles the aidp-fix-ci label trigger by analyzing CI failures
|
|
18
|
+
# and automatically fixing them with commits pushed to the PR branch.
|
|
19
|
+
class CiFixProcessor
|
|
20
|
+
include Aidp::MessageDisplay
|
|
21
|
+
|
|
22
|
+
# Default label names
|
|
23
|
+
DEFAULT_CI_FIX_LABEL = "aidp-fix-ci"
|
|
24
|
+
|
|
25
|
+
COMMENT_HEADER = "## đ¤ AIDP CI Fix"
|
|
26
|
+
MAX_FIX_ATTEMPTS = 3
|
|
27
|
+
|
|
28
|
+
attr_reader :ci_fix_label
|
|
29
|
+
|
|
30
|
+
def initialize(repository_client:, state_store:, provider_name: nil, project_dir: Dir.pwd, label_config: {}, verbose: false)
|
|
31
|
+
@repository_client = repository_client
|
|
32
|
+
@state_store = state_store
|
|
33
|
+
@provider_name = provider_name
|
|
34
|
+
@project_dir = project_dir
|
|
35
|
+
@verbose = verbose
|
|
36
|
+
|
|
37
|
+
# Load label configuration
|
|
38
|
+
@ci_fix_label = label_config[:ci_fix_trigger] || label_config["ci_fix_trigger"] || DEFAULT_CI_FIX_LABEL
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def process(pr)
|
|
42
|
+
number = pr[:number]
|
|
43
|
+
|
|
44
|
+
# Check if already processed successfully
|
|
45
|
+
if @state_store.ci_fix_completed?(number)
|
|
46
|
+
display_message("âšī¸ CI fix for PR ##{number} already completed. Skipping.", type: :muted)
|
|
47
|
+
return
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
display_message("đ§ Analyzing CI failures for PR ##{number} (#{pr[:title]})", type: :info)
|
|
51
|
+
|
|
52
|
+
# Fetch PR details
|
|
53
|
+
pr_data = @repository_client.fetch_pull_request(number)
|
|
54
|
+
ci_status = @repository_client.fetch_ci_status(number)
|
|
55
|
+
|
|
56
|
+
# Check if there are failures
|
|
57
|
+
if ci_status[:state] == "success"
|
|
58
|
+
display_message("â
CI is passing for PR ##{number}. No fixes needed.", type: :success)
|
|
59
|
+
post_success_comment(pr_data)
|
|
60
|
+
@state_store.record_ci_fix(number, {status: "no_failures", timestamp: Time.now.utc.iso8601})
|
|
61
|
+
begin
|
|
62
|
+
@repository_client.remove_labels(number, @ci_fix_label)
|
|
63
|
+
rescue
|
|
64
|
+
nil
|
|
65
|
+
end
|
|
66
|
+
return
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
if ci_status[:state] == "pending"
|
|
70
|
+
display_message("âŗ CI is still running for PR ##{number}. Skipping for now.", type: :muted)
|
|
71
|
+
return
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Get failed checks
|
|
75
|
+
failed_checks = ci_status[:checks].select { |check| check[:conclusion] == "failure" }
|
|
76
|
+
|
|
77
|
+
if failed_checks.empty?
|
|
78
|
+
display_message("â ī¸ No specific failed checks found for PR ##{number}.", type: :warn)
|
|
79
|
+
return
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
display_message("Found #{failed_checks.length} failed check(s):", type: :info)
|
|
83
|
+
failed_checks.each do |check|
|
|
84
|
+
display_message(" - #{check[:name]}", type: :muted)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Analyze failures and generate fixes
|
|
88
|
+
fix_result = analyze_and_fix(pr_data: pr_data, ci_status: ci_status, failed_checks: failed_checks)
|
|
89
|
+
|
|
90
|
+
# Log the fix attempt
|
|
91
|
+
log_ci_fix(number, fix_result)
|
|
92
|
+
|
|
93
|
+
if fix_result[:success]
|
|
94
|
+
handle_success(pr: pr_data, fix_result: fix_result)
|
|
95
|
+
else
|
|
96
|
+
handle_failure(pr: pr_data, fix_result: fix_result)
|
|
97
|
+
end
|
|
98
|
+
rescue => e
|
|
99
|
+
display_message("â CI fix failed: #{e.message}", type: :error)
|
|
100
|
+
Aidp.log_error("ci_fix_processor", "CI fix failed", pr: pr[:number], error: e.message, backtrace: e.backtrace&.first(10))
|
|
101
|
+
|
|
102
|
+
# Post error comment
|
|
103
|
+
error_comment = <<~COMMENT
|
|
104
|
+
#{COMMENT_HEADER}
|
|
105
|
+
|
|
106
|
+
â Automated CI fix failed: #{e.message}
|
|
107
|
+
|
|
108
|
+
Please investigate the CI failures manually or retry by re-adding the `#{@ci_fix_label}` label.
|
|
109
|
+
COMMENT
|
|
110
|
+
begin
|
|
111
|
+
@repository_client.post_comment(pr[:number], error_comment)
|
|
112
|
+
rescue
|
|
113
|
+
nil
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
|
|
119
|
+
def analyze_and_fix(pr_data:, ci_status:, failed_checks:)
|
|
120
|
+
# Fetch logs for failed checks (if available)
|
|
121
|
+
failure_details = failed_checks.map do |check|
|
|
122
|
+
{
|
|
123
|
+
name: check[:name],
|
|
124
|
+
conclusion: check[:conclusion],
|
|
125
|
+
output: check[:output],
|
|
126
|
+
details_url: check[:details_url]
|
|
127
|
+
}
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Use AI to analyze failures and propose fixes
|
|
131
|
+
analysis = analyze_failures_with_ai(pr_data: pr_data, failures: failure_details)
|
|
132
|
+
|
|
133
|
+
if analysis[:can_fix]
|
|
134
|
+
# Checkout the PR branch and apply fixes
|
|
135
|
+
checkout_pr_branch(pr_data)
|
|
136
|
+
|
|
137
|
+
# Apply the proposed fixes
|
|
138
|
+
apply_fixes(analysis[:fixes])
|
|
139
|
+
|
|
140
|
+
# Commit and push
|
|
141
|
+
if commit_and_push(pr_data, analysis)
|
|
142
|
+
{success: true, analysis: analysis, commit_created: true}
|
|
143
|
+
else
|
|
144
|
+
{success: false, analysis: analysis, reason: "No changes to commit"}
|
|
145
|
+
end
|
|
146
|
+
else
|
|
147
|
+
{success: false, analysis: analysis, reason: analysis[:reason] || "Cannot automatically fix"}
|
|
148
|
+
end
|
|
149
|
+
rescue => e
|
|
150
|
+
{success: false, error: e.message, backtrace: e.backtrace&.first(5)}
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def analyze_failures_with_ai(pr_data:, failures:)
|
|
154
|
+
provider_name = @provider_name || detect_default_provider
|
|
155
|
+
provider = Aidp::ProviderManager.get_provider(provider_name, use_harness: false)
|
|
156
|
+
|
|
157
|
+
user_prompt = build_ci_analysis_prompt(pr_data: pr_data, failures: failures)
|
|
158
|
+
full_prompt = "#{ci_fix_system_prompt}\n\n#{user_prompt}"
|
|
159
|
+
|
|
160
|
+
response = provider.send_message(prompt: full_prompt)
|
|
161
|
+
content = response.to_s.strip
|
|
162
|
+
|
|
163
|
+
# Extract JSON from response (handle code fences)
|
|
164
|
+
json_content = extract_json(content)
|
|
165
|
+
|
|
166
|
+
# Parse JSON response
|
|
167
|
+
parsed = JSON.parse(json_content)
|
|
168
|
+
|
|
169
|
+
{
|
|
170
|
+
can_fix: parsed["can_fix"],
|
|
171
|
+
reason: parsed["reason"],
|
|
172
|
+
root_causes: parsed["root_causes"] || [],
|
|
173
|
+
fixes: parsed["fixes"] || []
|
|
174
|
+
}
|
|
175
|
+
rescue JSON::ParserError => e
|
|
176
|
+
Aidp.log_error("ci_fix_processor", "Failed to parse AI response", error: e.message, content: content)
|
|
177
|
+
{can_fix: false, reason: "Failed to parse AI analysis"}
|
|
178
|
+
rescue => e
|
|
179
|
+
Aidp.log_error("ci_fix_processor", "AI analysis failed", error: e.message)
|
|
180
|
+
{can_fix: false, reason: "AI analysis error: #{e.message}"}
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def ci_fix_system_prompt
|
|
184
|
+
<<~PROMPT
|
|
185
|
+
You are an expert CI/CD troubleshooter. Your task is to analyze CI failures and propose fixes.
|
|
186
|
+
|
|
187
|
+
Analyze the provided CI failure information and respond in JSON format:
|
|
188
|
+
{
|
|
189
|
+
"can_fix": true/false,
|
|
190
|
+
"reason": "Brief explanation of why you can or cannot fix this",
|
|
191
|
+
"root_causes": ["List of identified root causes"],
|
|
192
|
+
"fixes": [
|
|
193
|
+
{
|
|
194
|
+
"file": "path/to/file",
|
|
195
|
+
"action": "edit|create|delete",
|
|
196
|
+
"content": "Full file content after fix (for create/edit)",
|
|
197
|
+
"description": "What this fix does"
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
Only propose fixes if you are confident they will resolve the issue.
|
|
203
|
+
Common CI failures you can fix:
|
|
204
|
+
- Linting errors (formatting, style violations)
|
|
205
|
+
- Simple test failures (typos, missing imports, incorrect assertions)
|
|
206
|
+
- Dependency issues (missing packages in manifest)
|
|
207
|
+
- Configuration errors (incorrect paths, missing env vars)
|
|
208
|
+
|
|
209
|
+
DO NOT attempt to fix:
|
|
210
|
+
- Complex logic errors requiring domain knowledge
|
|
211
|
+
- Failing integration tests that may indicate real bugs
|
|
212
|
+
- Security scan failures
|
|
213
|
+
- Performance regression issues
|
|
214
|
+
PROMPT
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def build_ci_analysis_prompt(pr_data:, failures:)
|
|
218
|
+
<<~PROMPT
|
|
219
|
+
Analyze these CI failures for PR ##{pr_data[:number]}: #{pr_data[:title]}
|
|
220
|
+
|
|
221
|
+
**PR Description:**
|
|
222
|
+
#{pr_data[:body]}
|
|
223
|
+
|
|
224
|
+
**Failed Checks:**
|
|
225
|
+
#{failures.map { |f| "- #{f[:name]}: #{f[:conclusion]}\n Output: #{f[:output].inspect}" }.join("\n")}
|
|
226
|
+
|
|
227
|
+
Please analyze these failures and propose fixes if possible.
|
|
228
|
+
PROMPT
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def detect_default_provider
|
|
232
|
+
config_manager = Aidp::Harness::ConfigManager.new(@project_dir)
|
|
233
|
+
config_manager.default_provider || "anthropic"
|
|
234
|
+
rescue
|
|
235
|
+
"anthropic"
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def extract_json(text)
|
|
239
|
+
# Try to extract JSON from code fences or find JSON object
|
|
240
|
+
# Avoid regex to prevent ReDoS - use simple string operations
|
|
241
|
+
return text if text.start_with?("{") && text.end_with?("}")
|
|
242
|
+
|
|
243
|
+
# Extract from code fence using string operations
|
|
244
|
+
fence_start = text.index("```json")
|
|
245
|
+
if fence_start
|
|
246
|
+
json_start = text.index("{", fence_start)
|
|
247
|
+
fence_end = text.index("```", fence_start + 7)
|
|
248
|
+
if json_start && fence_end && json_start < fence_end
|
|
249
|
+
json_end = text.rindex("}", fence_end - 1)
|
|
250
|
+
return text[json_start..json_end] if json_end && json_end > json_start
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Find JSON object using string operations
|
|
255
|
+
first_brace = text.index("{")
|
|
256
|
+
last_brace = text.rindex("}")
|
|
257
|
+
if first_brace && last_brace && last_brace > first_brace
|
|
258
|
+
text[first_brace..last_brace]
|
|
259
|
+
else
|
|
260
|
+
text
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def checkout_pr_branch(pr_data)
|
|
265
|
+
head_ref = pr_data[:head_ref]
|
|
266
|
+
|
|
267
|
+
Dir.chdir(@project_dir) do
|
|
268
|
+
# Fetch latest
|
|
269
|
+
run_git(%w[fetch origin])
|
|
270
|
+
|
|
271
|
+
# Checkout the PR branch
|
|
272
|
+
run_git(["checkout", head_ref])
|
|
273
|
+
|
|
274
|
+
# Pull latest changes
|
|
275
|
+
run_git(%w[pull --ff-only], allow_failure: true)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
display_message("đŋ Checked out branch: #{head_ref}", type: :info)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def apply_fixes(fixes)
|
|
282
|
+
fixes.each do |fix|
|
|
283
|
+
file_path = File.join(@project_dir, fix["file"])
|
|
284
|
+
|
|
285
|
+
case fix["action"]
|
|
286
|
+
when "create", "edit"
|
|
287
|
+
FileUtils.mkdir_p(File.dirname(file_path))
|
|
288
|
+
File.write(file_path, fix["content"])
|
|
289
|
+
display_message(" â #{fix["action"]} #{fix["file"]}", type: :muted) if @verbose
|
|
290
|
+
when "delete"
|
|
291
|
+
File.delete(file_path) if File.exist?(file_path)
|
|
292
|
+
display_message(" â Deleted #{fix["file"]}", type: :muted) if @verbose
|
|
293
|
+
else
|
|
294
|
+
display_message(" â ī¸ Unknown action: #{fix["action"]} for #{fix["file"]}", type: :warn)
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def commit_and_push(pr_data, analysis)
|
|
300
|
+
Dir.chdir(@project_dir) do
|
|
301
|
+
# Check if there are changes
|
|
302
|
+
status_output = run_git(%w[status --porcelain])
|
|
303
|
+
if status_output.strip.empty?
|
|
304
|
+
display_message("âšī¸ No changes to commit after applying fixes.", type: :muted)
|
|
305
|
+
return false
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# Stage all changes
|
|
309
|
+
run_git(%w[add -A])
|
|
310
|
+
|
|
311
|
+
# Create commit
|
|
312
|
+
commit_message = build_commit_message(pr_data, analysis)
|
|
313
|
+
run_git(["commit", "-m", commit_message])
|
|
314
|
+
|
|
315
|
+
display_message("đž Created commit: #{commit_message.lines.first.strip}", type: :info)
|
|
316
|
+
|
|
317
|
+
# Push to origin
|
|
318
|
+
head_ref = pr_data[:head_ref]
|
|
319
|
+
run_git(["push", "origin", head_ref])
|
|
320
|
+
|
|
321
|
+
display_message("âŦī¸ Pushed fixes to #{head_ref}", type: :success)
|
|
322
|
+
true
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def build_commit_message(pr_data, analysis)
|
|
327
|
+
root_causes = analysis[:root_causes] || []
|
|
328
|
+
fixes_description = analysis[:fixes]&.map { |f| f["description"] }&.join(", ") || "CI failures"
|
|
329
|
+
|
|
330
|
+
message = "fix: resolve CI failures for PR ##{pr_data[:number]}\n\n"
|
|
331
|
+
message += "Root causes:\n"
|
|
332
|
+
root_causes.each { |cause| message += "- #{cause}\n" }
|
|
333
|
+
message += "\nFixes: #{fixes_description}\n"
|
|
334
|
+
message += "\nCo-authored-by: AIDP CI Fixer <ai@aidp.dev>"
|
|
335
|
+
|
|
336
|
+
message
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def handle_success(pr:, fix_result:)
|
|
340
|
+
comment = <<~COMMENT
|
|
341
|
+
#{COMMENT_HEADER}
|
|
342
|
+
|
|
343
|
+
â
Successfully analyzed and fixed CI failures!
|
|
344
|
+
|
|
345
|
+
**Root Causes:**
|
|
346
|
+
#{fix_result[:analysis][:root_causes].map { |c| "- #{c}" }.join("\n")}
|
|
347
|
+
|
|
348
|
+
**Applied Fixes:**
|
|
349
|
+
#{fix_result[:analysis][:fixes].map { |f| "- #{f["file"]}: #{f["description"]}" }.join("\n")}
|
|
350
|
+
|
|
351
|
+
The fixes have been committed and pushed to this PR. CI should re-run automatically.
|
|
352
|
+
COMMENT
|
|
353
|
+
|
|
354
|
+
@repository_client.post_comment(pr[:number], comment)
|
|
355
|
+
@state_store.record_ci_fix(pr[:number], {
|
|
356
|
+
status: "completed",
|
|
357
|
+
timestamp: Time.now.utc.iso8601,
|
|
358
|
+
root_causes: fix_result[:analysis][:root_causes],
|
|
359
|
+
fixes_count: fix_result[:analysis][:fixes].length
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
display_message("đ Posted success comment for PR ##{pr[:number]}", type: :success)
|
|
363
|
+
|
|
364
|
+
# Remove label after successful fix
|
|
365
|
+
begin
|
|
366
|
+
@repository_client.remove_labels(pr[:number], @ci_fix_label)
|
|
367
|
+
display_message("đˇī¸ Removed '#{@ci_fix_label}' label after successful fix", type: :info)
|
|
368
|
+
rescue => e
|
|
369
|
+
display_message("â ī¸ Failed to remove CI fix label: #{e.message}", type: :warn)
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def handle_failure(pr:, fix_result:)
|
|
374
|
+
reason = fix_result[:reason] || fix_result[:error] || "Unknown error"
|
|
375
|
+
|
|
376
|
+
analysis_section = if fix_result[:analysis]
|
|
377
|
+
"**Analysis:**\n#{fix_result[:analysis][:root_causes]&.map { |c| "- #{c}" }&.join("\n")}"
|
|
378
|
+
else
|
|
379
|
+
""
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
comment = <<~COMMENT
|
|
383
|
+
#{COMMENT_HEADER}
|
|
384
|
+
|
|
385
|
+
â ī¸ Could not automatically fix CI failures.
|
|
386
|
+
|
|
387
|
+
**Reason:** #{reason}
|
|
388
|
+
|
|
389
|
+
#{analysis_section}
|
|
390
|
+
|
|
391
|
+
Please review the CI failures manually. You may need to:
|
|
392
|
+
1. Check the full CI logs for more context
|
|
393
|
+
2. Run tests locally to reproduce the issue
|
|
394
|
+
3. Consult with your team if the failures indicate a deeper problem
|
|
395
|
+
|
|
396
|
+
You can retry the automated fix by re-adding the `#{@ci_fix_label}` label after making changes.
|
|
397
|
+
COMMENT
|
|
398
|
+
|
|
399
|
+
@repository_client.post_comment(pr[:number], comment)
|
|
400
|
+
@state_store.record_ci_fix(pr[:number], {
|
|
401
|
+
status: "failed",
|
|
402
|
+
timestamp: Time.now.utc.iso8601,
|
|
403
|
+
reason: reason
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
display_message("â ī¸ Posted failure comment for PR ##{pr[:number]}", type: :warn)
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def post_success_comment(pr_data)
|
|
410
|
+
comment = <<~COMMENT
|
|
411
|
+
#{COMMENT_HEADER}
|
|
412
|
+
|
|
413
|
+
â
CI is already passing! No fixes needed.
|
|
414
|
+
|
|
415
|
+
All checks are green for this PR.
|
|
416
|
+
COMMENT
|
|
417
|
+
|
|
418
|
+
@repository_client.post_comment(pr_data[:number], comment)
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
def run_git(args, allow_failure: false)
|
|
422
|
+
stdout, stderr, status = Open3.capture3("git", *Array(args))
|
|
423
|
+
raise "git #{args.join(" ")} failed: #{stderr.strip}" unless status.success? || allow_failure
|
|
424
|
+
stdout
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def log_ci_fix(pr_number, fix_result)
|
|
428
|
+
log_dir = File.join(@project_dir, ".aidp", "logs", "pr_reviews")
|
|
429
|
+
FileUtils.mkdir_p(log_dir)
|
|
430
|
+
|
|
431
|
+
log_file = File.join(log_dir, "ci_fix_#{pr_number}_#{Time.now.utc.strftime("%Y%m%d_%H%M%S")}.json")
|
|
432
|
+
|
|
433
|
+
log_data = {
|
|
434
|
+
pr_number: pr_number,
|
|
435
|
+
timestamp: Time.now.utc.iso8601,
|
|
436
|
+
success: fix_result[:success],
|
|
437
|
+
analysis: fix_result[:analysis],
|
|
438
|
+
error: fix_result[:error]
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
File.write(log_file, JSON.pretty_generate(log_data))
|
|
442
|
+
display_message("đ CI fix log saved to #{log_file}", type: :muted) if @verbose
|
|
443
|
+
rescue => e
|
|
444
|
+
display_message("â ī¸ Failed to save CI fix log: #{e.message}", type: :warn)
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
end
|
|
@@ -49,7 +49,10 @@ module Aidp
|
|
|
49
49
|
display_message("đ§ Generating plan for issue ##{number} (#{issue[:title]})", type: :info)
|
|
50
50
|
plan_data = @plan_generator.generate(issue)
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
# Fetch the user who added the most recent label
|
|
53
|
+
label_actor = @repository_client.most_recent_label_actor(number)
|
|
54
|
+
|
|
55
|
+
comment_body = build_comment(issue: issue, plan: plan_data, label_actor: label_actor)
|
|
53
56
|
@repository_client.post_comment(number, comment_body)
|
|
54
57
|
|
|
55
58
|
display_message("đŦ Posted plan comment for issue ##{number}", type: :success)
|
|
@@ -82,7 +85,7 @@ module Aidp
|
|
|
82
85
|
end
|
|
83
86
|
end
|
|
84
87
|
|
|
85
|
-
def build_comment(issue:, plan:)
|
|
88
|
+
def build_comment(issue:, plan:, label_actor: nil)
|
|
86
89
|
summary = plan[:summary].to_s.strip
|
|
87
90
|
tasks = Array(plan[:tasks])
|
|
88
91
|
questions = Array(plan[:questions])
|
|
@@ -91,6 +94,13 @@ module Aidp
|
|
|
91
94
|
parts = []
|
|
92
95
|
parts << COMMENT_HEADER
|
|
93
96
|
parts << ""
|
|
97
|
+
|
|
98
|
+
# Tag the label actor if available
|
|
99
|
+
if label_actor
|
|
100
|
+
parts << "cc @#{label_actor}"
|
|
101
|
+
parts << ""
|
|
102
|
+
end
|
|
103
|
+
|
|
94
104
|
parts << "**Issue**: [##{issue[:number]}](#{issue[:url]})"
|
|
95
105
|
parts << "**Title**: #{issue[:title]}"
|
|
96
106
|
parts << ""
|