aidp 0.30.0 → 0.32.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/lib/aidp/config.rb +2 -8
- data/lib/aidp/execute/work_loop_runner.rb +70 -0
- data/lib/aidp/harness/configuration.rb +1 -1
- data/lib/aidp/harness/provider_manager.rb +74 -5
- data/lib/aidp/harness/provider_metrics.rb +5 -3
- data/lib/aidp/pr_worktree_manager.rb +582 -0
- data/lib/aidp/providers/anthropic.rb +17 -14
- data/lib/aidp/providers/cursor.rb +7 -1
- data/lib/aidp/setup/wizard.rb +65 -0
- data/lib/aidp/util.rb +11 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/auto_pr_processor.rb +86 -0
- data/lib/aidp/watch/auto_processor.rb +78 -0
- data/lib/aidp/watch/change_request_processor.rb +105 -27
- data/lib/aidp/watch/runner.rb +104 -0
- data/lib/aidp/watch/state_store.rb +37 -0
- data/lib/aidp/worktree_branch_manager.rb +216 -0
- metadata +5 -1
data/lib/aidp/setup/wizard.rb
CHANGED
|
@@ -50,6 +50,7 @@ module Aidp
|
|
|
50
50
|
return @saved if skip_wizard?
|
|
51
51
|
|
|
52
52
|
configure_providers
|
|
53
|
+
configure_harness_settings
|
|
53
54
|
configure_thinking_tiers
|
|
54
55
|
configure_work_loop
|
|
55
56
|
configure_branching
|
|
@@ -285,6 +286,24 @@ module Aidp
|
|
|
285
286
|
show_provider_summary(provider_choice, cleaned_fallbacks) unless provider_choice == "custom"
|
|
286
287
|
end
|
|
287
288
|
|
|
289
|
+
# -------------------------------------------
|
|
290
|
+
# Harness settings (retries, limits, etc.)
|
|
291
|
+
# -------------------------------------------
|
|
292
|
+
def configure_harness_settings
|
|
293
|
+
prompt.say("\n⚙️ Harness Configuration")
|
|
294
|
+
prompt.say(" Advanced settings for provider behavior")
|
|
295
|
+
existing = get([:harness]) || {}
|
|
296
|
+
|
|
297
|
+
return unless prompt.yes?("Configure advanced harness settings?", default: false)
|
|
298
|
+
|
|
299
|
+
max_retries = ask_with_default(
|
|
300
|
+
"Maximum retry attempts for failed LLM calls",
|
|
301
|
+
(existing[:max_retries] || 2).to_s
|
|
302
|
+
) { |value| value.to_i }
|
|
303
|
+
|
|
304
|
+
set([:harness, :max_retries], max_retries)
|
|
305
|
+
end
|
|
306
|
+
|
|
288
307
|
# Removed MCP configuration step (MCP now expected to be provider-specific if used)
|
|
289
308
|
|
|
290
309
|
# -------------------------------------------
|
|
@@ -473,6 +492,7 @@ module Aidp
|
|
|
473
492
|
prompt.say("\n⚙️ Work loop configuration")
|
|
474
493
|
prompt.say("-" * 40)
|
|
475
494
|
|
|
495
|
+
configure_work_loop_limits
|
|
476
496
|
configure_test_commands
|
|
477
497
|
configure_linting
|
|
478
498
|
configure_watch_patterns
|
|
@@ -482,6 +502,19 @@ module Aidp
|
|
|
482
502
|
configure_vcs_behavior
|
|
483
503
|
end
|
|
484
504
|
|
|
505
|
+
def configure_work_loop_limits
|
|
506
|
+
existing = get([:work_loop]) || {}
|
|
507
|
+
|
|
508
|
+
return unless prompt.yes?("Configure work loop limits?", default: false)
|
|
509
|
+
|
|
510
|
+
max_iterations = ask_with_default(
|
|
511
|
+
"Maximum work loop iterations",
|
|
512
|
+
(existing[:max_iterations] || 50).to_s
|
|
513
|
+
) { |value| value.to_i }
|
|
514
|
+
|
|
515
|
+
set([:work_loop, :max_iterations], max_iterations)
|
|
516
|
+
end
|
|
517
|
+
|
|
485
518
|
def configure_test_commands
|
|
486
519
|
existing = get([:work_loop, :test]) || {}
|
|
487
520
|
|
|
@@ -999,6 +1032,7 @@ module Aidp
|
|
|
999
1032
|
|
|
1000
1033
|
configure_watch_safety
|
|
1001
1034
|
configure_watch_labels
|
|
1035
|
+
configure_watch_change_requests
|
|
1002
1036
|
configure_watch_label_creation
|
|
1003
1037
|
end
|
|
1004
1038
|
|
|
@@ -1066,6 +1100,11 @@ module Aidp
|
|
|
1066
1100
|
existing[:ci_fix_trigger] || "aidp-fix-ci"
|
|
1067
1101
|
)
|
|
1068
1102
|
|
|
1103
|
+
auto_trigger = ask_with_default(
|
|
1104
|
+
"Label to trigger fully autonomous build+review+CI",
|
|
1105
|
+
existing[:auto_trigger] || "aidp-auto"
|
|
1106
|
+
)
|
|
1107
|
+
|
|
1069
1108
|
change_request_trigger = ask_with_default(
|
|
1070
1109
|
"Label to trigger PR change implementation",
|
|
1071
1110
|
existing[:change_request_trigger] || "aidp-request-changes"
|
|
@@ -1078,10 +1117,35 @@ module Aidp
|
|
|
1078
1117
|
build_trigger: build_trigger,
|
|
1079
1118
|
review_trigger: review_trigger,
|
|
1080
1119
|
ci_fix_trigger: ci_fix_trigger,
|
|
1120
|
+
auto_trigger: auto_trigger,
|
|
1081
1121
|
change_request_trigger: change_request_trigger
|
|
1082
1122
|
})
|
|
1083
1123
|
end
|
|
1084
1124
|
|
|
1125
|
+
def configure_watch_change_requests
|
|
1126
|
+
prompt.say("\n📝 PR Change Request Configuration")
|
|
1127
|
+
prompt.say(" Configure how AIDP handles automated PR change requests")
|
|
1128
|
+
existing = get([:watch, :change_requests]) || {}
|
|
1129
|
+
|
|
1130
|
+
max_diff_size = ask_with_default(
|
|
1131
|
+
"Maximum PR diff size (lines) for change requests",
|
|
1132
|
+
(existing[:max_diff_size] || 5000).to_s
|
|
1133
|
+
) { |value| value.to_i }
|
|
1134
|
+
|
|
1135
|
+
post_comments = prompt.yes?(
|
|
1136
|
+
"Post detection comments when work is detected?",
|
|
1137
|
+
default: existing.fetch(:post_detection_comments, true)
|
|
1138
|
+
)
|
|
1139
|
+
|
|
1140
|
+
set([:watch, :change_requests], {
|
|
1141
|
+
max_diff_size: max_diff_size
|
|
1142
|
+
})
|
|
1143
|
+
|
|
1144
|
+
set([:watch], {
|
|
1145
|
+
post_detection_comments: post_comments
|
|
1146
|
+
}.merge(get([:watch]) || {}))
|
|
1147
|
+
end
|
|
1148
|
+
|
|
1085
1149
|
def configure_watch_label_creation
|
|
1086
1150
|
prompt.say("\n🏷️ GitHub Label Auto-Creation")
|
|
1087
1151
|
prompt.say(" Automatically create GitHub labels for watch mode if they don't exist")
|
|
@@ -1222,6 +1286,7 @@ module Aidp
|
|
|
1222
1286
|
build_trigger: "5319E7", # Purple
|
|
1223
1287
|
review_trigger: "FBCA04", # Yellow
|
|
1224
1288
|
ci_fix_trigger: "D93F0B", # Red
|
|
1289
|
+
auto_trigger: "0C8BD6", # Blue (distinct from build)
|
|
1225
1290
|
change_request_trigger: "F9D0C4", # Light pink
|
|
1226
1291
|
in_progress: "1D76DB" # Dark blue (internal coordination)
|
|
1227
1292
|
}
|
data/lib/aidp/util.rb
CHANGED
|
@@ -28,6 +28,17 @@ module Aidp
|
|
|
28
28
|
File.write(path, content)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
# Walk upward to find the nearest project root (git/package manager markers)
|
|
32
|
+
def self.find_project_root(start_dir = Dir.pwd)
|
|
33
|
+
dir = File.expand_path(start_dir)
|
|
34
|
+
until dir == File.dirname(dir)
|
|
35
|
+
return dir if project_root?(dir)
|
|
36
|
+
dir = File.dirname(dir)
|
|
37
|
+
end
|
|
38
|
+
# Fall back to the original directory when no markers were found
|
|
39
|
+
File.expand_path(start_dir)
|
|
40
|
+
end
|
|
41
|
+
|
|
31
42
|
def self.project_root?(dir = Dir.pwd)
|
|
32
43
|
File.exist?(File.join(dir, ".git")) ||
|
|
33
44
|
File.exist?(File.join(dir, "package.json")) ||
|
data/lib/aidp/version.rb
CHANGED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../message_display"
|
|
4
|
+
require_relative "github_state_extractor"
|
|
5
|
+
|
|
6
|
+
module Aidp
|
|
7
|
+
module Watch
|
|
8
|
+
# Handles the aidp-auto label on PRs by chaining review and CI-fix flows
|
|
9
|
+
# until the PR is ready for human review.
|
|
10
|
+
class AutoPrProcessor
|
|
11
|
+
include Aidp::MessageDisplay
|
|
12
|
+
|
|
13
|
+
DEFAULT_AUTO_LABEL = "aidp-auto"
|
|
14
|
+
|
|
15
|
+
def initialize(repository_client:, state_store:, review_processor:, ci_fix_processor:, label_config: {}, verbose: false)
|
|
16
|
+
@repository_client = repository_client
|
|
17
|
+
@state_store = state_store
|
|
18
|
+
@review_processor = review_processor
|
|
19
|
+
@ci_fix_processor = ci_fix_processor
|
|
20
|
+
@state_extractor = GitHubStateExtractor.new(repository_client: repository_client)
|
|
21
|
+
@verbose = verbose
|
|
22
|
+
@auto_label = label_config[:auto_trigger] || label_config["auto_trigger"] || DEFAULT_AUTO_LABEL
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def process(pr)
|
|
26
|
+
number = pr[:number]
|
|
27
|
+
Aidp.log_debug("auto_pr_processor", "process_started", pr: number, title: pr[:title])
|
|
28
|
+
display_message("🤖 Running autonomous review/CI loop for PR ##{number}", type: :info)
|
|
29
|
+
|
|
30
|
+
# Run review and CI fix flows. Each processor is responsible for its own guards.
|
|
31
|
+
@review_processor.process(pr)
|
|
32
|
+
@ci_fix_processor.process(pr)
|
|
33
|
+
|
|
34
|
+
finalize_if_ready(pr_number: number)
|
|
35
|
+
rescue => e
|
|
36
|
+
Aidp.log_error("auto_pr_processor", "process_failed", pr: pr[:number], error: e.message, error_class: e.class.name)
|
|
37
|
+
display_message("❌ aidp-auto failed on PR ##{pr[:number]}: #{e.message}", type: :error)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
attr_reader :auto_label
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def finalize_if_ready(pr_number:)
|
|
45
|
+
pr_data = @repository_client.fetch_pull_request(pr_number)
|
|
46
|
+
ci_status = @repository_client.fetch_ci_status(pr_number)
|
|
47
|
+
|
|
48
|
+
review_done = @state_extractor.review_completed?(pr_data) || @state_store.review_processed?(pr_number)
|
|
49
|
+
ci_passing = ci_status[:state] == "success"
|
|
50
|
+
|
|
51
|
+
Aidp.log_debug("auto_pr_processor", "completion_check",
|
|
52
|
+
pr: pr_number,
|
|
53
|
+
review_done: review_done,
|
|
54
|
+
ci_state: ci_status[:state])
|
|
55
|
+
|
|
56
|
+
return unless review_done && ci_passing
|
|
57
|
+
|
|
58
|
+
post_completion_comment(pr_number)
|
|
59
|
+
remove_auto_label(pr_number)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def post_completion_comment(pr_number)
|
|
63
|
+
comment = <<~COMMENT
|
|
64
|
+
## 🤖 aidp-auto
|
|
65
|
+
|
|
66
|
+
- Automated review completed
|
|
67
|
+
- CI is passing
|
|
68
|
+
|
|
69
|
+
Marking this PR ready for human review and removing the `#{@auto_label}` label.
|
|
70
|
+
COMMENT
|
|
71
|
+
|
|
72
|
+
@repository_client.post_comment(pr_number, comment)
|
|
73
|
+
display_message("💬 Posted aidp-auto completion comment on PR ##{pr_number}", type: :success)
|
|
74
|
+
rescue => e
|
|
75
|
+
Aidp.log_warn("auto_pr_processor", "comment_failed", pr: pr_number, error: e.message)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def remove_auto_label(pr_number)
|
|
79
|
+
@repository_client.remove_labels(pr_number, @auto_label)
|
|
80
|
+
display_message("🏷️ Removed '#{@auto_label}' from PR ##{pr_number}", type: :info)
|
|
81
|
+
rescue => e
|
|
82
|
+
Aidp.log_warn("auto_pr_processor", "remove_label_failed", pr: pr_number, error: e.message)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../message_display"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
module Watch
|
|
7
|
+
# Handles the aidp-auto label on issues by delegating to the BuildProcessor
|
|
8
|
+
# and transferring the label to the created PR once work completes.
|
|
9
|
+
class AutoProcessor
|
|
10
|
+
include Aidp::MessageDisplay
|
|
11
|
+
|
|
12
|
+
DEFAULT_AUTO_LABEL = "aidp-auto"
|
|
13
|
+
|
|
14
|
+
attr_reader :auto_label
|
|
15
|
+
|
|
16
|
+
def initialize(repository_client:, state_store:, build_processor:, label_config: {}, verbose: false)
|
|
17
|
+
@repository_client = repository_client
|
|
18
|
+
@state_store = state_store
|
|
19
|
+
@build_processor = build_processor
|
|
20
|
+
@verbose = verbose
|
|
21
|
+
|
|
22
|
+
# Allow overrides from watch config
|
|
23
|
+
@auto_label = label_config[:auto_trigger] || label_config["auto_trigger"] || DEFAULT_AUTO_LABEL
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def process(issue)
|
|
27
|
+
number = issue[:number]
|
|
28
|
+
Aidp.log_debug("auto_processor", "process_started", issue: number, title: issue[:title])
|
|
29
|
+
display_message("🤖 Starting autonomous build for issue ##{number}", type: :info)
|
|
30
|
+
|
|
31
|
+
@build_processor.process(issue)
|
|
32
|
+
|
|
33
|
+
status = @state_store.build_status(number)
|
|
34
|
+
pr_url = status["pr_url"]
|
|
35
|
+
pr_number = extract_pr_number(pr_url)
|
|
36
|
+
|
|
37
|
+
unless status["status"] == "completed" && pr_number
|
|
38
|
+
Aidp.log_debug("auto_processor", "no_pr_to_transfer", issue: number, status: status["status"], pr_url: pr_url)
|
|
39
|
+
return
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
transfer_label_to_pr(issue_number: number, pr_number: pr_number)
|
|
43
|
+
rescue => e
|
|
44
|
+
Aidp.log_error("auto_processor", "process_failed", issue: issue[:number], error: e.message, error_class: e.class.name)
|
|
45
|
+
display_message("❌ aidp-auto failed for issue ##{issue[:number]}: #{e.message}", type: :error)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def extract_pr_number(pr_url)
|
|
51
|
+
return nil unless pr_url
|
|
52
|
+
|
|
53
|
+
match = pr_url.match(%r{/pull/(\d+)}i)
|
|
54
|
+
match && match[1].to_i
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def transfer_label_to_pr(issue_number:, pr_number:)
|
|
58
|
+
Aidp.log_debug("auto_processor", "transferring_label", issue: issue_number, pr: pr_number, label: @auto_label)
|
|
59
|
+
|
|
60
|
+
begin
|
|
61
|
+
@repository_client.add_labels(pr_number, @auto_label)
|
|
62
|
+
display_message("🏷️ Added '#{@auto_label}' to PR ##{pr_number}", type: :info)
|
|
63
|
+
rescue => e
|
|
64
|
+
Aidp.log_warn("auto_processor", "add_label_failed", pr: pr_number, label: @auto_label, error: e.message)
|
|
65
|
+
display_message("⚠️ Failed to add '#{@auto_label}' to PR ##{pr_number}: #{e.message}", type: :warn)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
begin
|
|
69
|
+
@repository_client.remove_labels(issue_number, @auto_label)
|
|
70
|
+
display_message("🏷️ Removed '#{@auto_label}' from issue ##{issue_number}", type: :muted)
|
|
71
|
+
rescue => e
|
|
72
|
+
Aidp.log_warn("auto_processor", "remove_label_failed", issue: issue_number, label: @auto_label, error: e.message)
|
|
73
|
+
display_message("⚠️ Failed to remove '#{@auto_label}' from issue ##{issue_number}: #{e.message}", type: :warn)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -57,7 +57,8 @@ module Aidp
|
|
|
57
57
|
run_tests_before_push: true,
|
|
58
58
|
commit_message_prefix: "aidp: pr-change",
|
|
59
59
|
require_comment_reference: true,
|
|
60
|
-
max_diff_size: 2000
|
|
60
|
+
max_diff_size: 2000,
|
|
61
|
+
allow_large_pr_worktree_bypass: true # Default to always using worktree for large PRs
|
|
61
62
|
}.merge(symbolize_keys(change_request_config))
|
|
62
63
|
|
|
63
64
|
# Load safety configuration
|
|
@@ -95,16 +96,23 @@ module Aidp
|
|
|
95
96
|
return
|
|
96
97
|
end
|
|
97
98
|
|
|
98
|
-
#
|
|
99
|
+
# If max_diff_size is set, attempt to fetch and check diff
|
|
100
|
+
# But bypass restriction for worktree-based workflows
|
|
99
101
|
diff = @repository_client.fetch_pull_request_diff(number)
|
|
100
102
|
diff_size = diff.lines.count
|
|
101
103
|
|
|
102
|
-
if
|
|
104
|
+
# Check if we want to use the worktree bypass
|
|
105
|
+
use_worktree_bypass = @config[:allow_large_pr_worktree_bypass] || @config[:allow_large_pr_worktree_bypass].nil?
|
|
106
|
+
|
|
107
|
+
if diff_size > @config[:max_diff_size] && !use_worktree_bypass
|
|
103
108
|
display_message("⚠️ PR ##{number} diff too large (#{diff_size} lines > #{@config[:max_diff_size]}). Skipping.", type: :warn)
|
|
104
109
|
post_diff_too_large_comment(pr, diff_size)
|
|
105
110
|
return
|
|
106
111
|
end
|
|
107
112
|
|
|
113
|
+
# Log the diff size for observability
|
|
114
|
+
Aidp.log_debug("change_request_processor", "PR diff size", number: number, size: diff_size, max_allowed: @config[:max_diff_size], worktree_bypass: use_worktree_bypass)
|
|
115
|
+
|
|
108
116
|
# Analyze change requests
|
|
109
117
|
analysis_result = analyze_change_requests(pr_data: pr_data, comments: authorized_comments, diff: diff)
|
|
110
118
|
|
|
@@ -308,40 +316,105 @@ module Aidp
|
|
|
308
316
|
head_ref = pr_data[:head_ref]
|
|
309
317
|
pr_number = pr_data[:number]
|
|
310
318
|
|
|
311
|
-
|
|
319
|
+
worktree_path = resolve_worktree_for_pr(pr_data)
|
|
320
|
+
|
|
321
|
+
Dir.chdir(worktree_path) do
|
|
322
|
+
run_git(%w[fetch origin], allow_failure: true)
|
|
323
|
+
run_git(["checkout", head_ref])
|
|
324
|
+
run_git(%w[pull --ff-only], allow_failure: true)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
@project_dir = worktree_path
|
|
328
|
+
|
|
329
|
+
Aidp.log_debug("change_request_processor", "Checked out PR branch", branch: head_ref, worktree: worktree_path)
|
|
330
|
+
display_message("🌿 Using worktree for PR ##{pr_number}: #{head_ref}", type: :info)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def resolve_worktree_for_pr(pr_data)
|
|
334
|
+
head_ref = pr_data[:head_ref]
|
|
335
|
+
pr_number = pr_data[:number]
|
|
336
|
+
|
|
312
337
|
existing = Aidp::Worktree.find_by_branch(branch: head_ref, project_dir: @project_dir)
|
|
313
338
|
|
|
314
339
|
if existing && existing[:active]
|
|
315
340
|
display_message("🔄 Using existing worktree for branch: #{head_ref}", type: :info)
|
|
316
341
|
Aidp.log_debug("change_request_processor", "worktree_reused", pr_number: pr_number, branch: head_ref, path: existing[:path])
|
|
342
|
+
return existing[:path]
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
issue_worktree = find_issue_worktree_for_pr(pr_data)
|
|
346
|
+
return issue_worktree if issue_worktree
|
|
347
|
+
|
|
348
|
+
create_worktree_for_pr(pr_data)
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def find_issue_worktree_for_pr(pr_data)
|
|
352
|
+
pr_number = pr_data[:number]
|
|
353
|
+
linked_issue_numbers = extract_issue_numbers_from_pr(pr_data)
|
|
354
|
+
|
|
355
|
+
build_match = @state_store.find_build_by_pr(pr_number)
|
|
356
|
+
linked_issue_numbers << build_match[:issue_number] if build_match
|
|
357
|
+
linked_issue_numbers = linked_issue_numbers.compact.uniq
|
|
317
358
|
|
|
318
|
-
|
|
319
|
-
|
|
359
|
+
linked_issue_numbers.each do |issue_number|
|
|
360
|
+
workstream = @state_store.workstream_for_issue(issue_number)
|
|
361
|
+
next unless workstream
|
|
320
362
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
363
|
+
slug = workstream[:workstream]
|
|
364
|
+
branch = workstream[:branch]
|
|
365
|
+
|
|
366
|
+
if slug
|
|
367
|
+
info = Aidp::Worktree.info(slug: slug, project_dir: @project_dir)
|
|
368
|
+
if info && info[:active]
|
|
369
|
+
Aidp.log_debug("change_request_processor", "issue_worktree_reused", pr_number: pr_number, issue_number: issue_number, branch: branch, path: info[:path])
|
|
370
|
+
display_message("🔄 Reusing worktree #{slug} for issue ##{issue_number} (PR ##{pr_number})", type: :info)
|
|
371
|
+
return info[:path]
|
|
372
|
+
end
|
|
326
373
|
end
|
|
327
374
|
|
|
328
|
-
|
|
375
|
+
if branch
|
|
376
|
+
existing = Aidp::Worktree.find_by_branch(branch: branch, project_dir: @project_dir)
|
|
377
|
+
if existing && existing[:active]
|
|
378
|
+
Aidp.log_debug("change_request_processor", "issue_branch_worktree_reused", pr_number: pr_number, issue_number: issue_number, branch: branch, path: existing[:path])
|
|
379
|
+
display_message("🔄 Reusing branch worktree for issue ##{issue_number}: #{branch}", type: :info)
|
|
380
|
+
return existing[:path]
|
|
381
|
+
end
|
|
382
|
+
end
|
|
329
383
|
end
|
|
330
384
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
# Fetch latest
|
|
334
|
-
run_git(%w[fetch origin])
|
|
385
|
+
nil
|
|
386
|
+
end
|
|
335
387
|
|
|
336
|
-
|
|
337
|
-
|
|
388
|
+
def extract_issue_numbers_from_pr(pr_data)
|
|
389
|
+
body = pr_data[:body].to_s
|
|
390
|
+
issue_matches = body.scan(/(?:Fixes|Resolves|Closes)\s+#(\d+)/i).flatten
|
|
338
391
|
|
|
339
|
-
|
|
340
|
-
|
|
392
|
+
issue_matches.map { |num| num.to_i }.uniq
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def create_worktree_for_pr(pr_data)
|
|
396
|
+
head_ref = pr_data[:head_ref]
|
|
397
|
+
pr_number = pr_data[:number]
|
|
398
|
+
slug = "pr-#{pr_number}-change-requests"
|
|
399
|
+
|
|
400
|
+
display_message("🌿 Creating worktree for PR ##{pr_number}: #{head_ref}", type: :info)
|
|
401
|
+
|
|
402
|
+
Dir.chdir(@project_dir) do
|
|
403
|
+
run_git(%w[fetch origin], allow_failure: true)
|
|
341
404
|
end
|
|
342
405
|
|
|
343
|
-
Aidp.
|
|
344
|
-
|
|
406
|
+
result = Aidp::Worktree.create(
|
|
407
|
+
slug: slug,
|
|
408
|
+
project_dir: @project_dir,
|
|
409
|
+
branch: head_ref,
|
|
410
|
+
base_branch: pr_data[:base_ref]
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
worktree_path = result[:path]
|
|
414
|
+
Aidp.log_debug("change_request_processor", "worktree_created", pr_number: pr_number, branch: head_ref, path: worktree_path)
|
|
415
|
+
display_message("✅ Worktree created at #{worktree_path}", type: :success)
|
|
416
|
+
|
|
417
|
+
worktree_path
|
|
345
418
|
end
|
|
346
419
|
|
|
347
420
|
def apply_changes(changes)
|
|
@@ -704,15 +777,20 @@ module Aidp
|
|
|
704
777
|
comment = <<~COMMENT
|
|
705
778
|
#{COMMENT_HEADER}
|
|
706
779
|
|
|
707
|
-
⚠️ PR diff is too large for
|
|
780
|
+
⚠️ PR diff is too large for default change requests.
|
|
708
781
|
|
|
709
782
|
**Current size:** #{diff_size} lines
|
|
710
783
|
**Maximum allowed:** #{@config[:max_diff_size]} lines
|
|
711
784
|
|
|
712
|
-
For large PRs,
|
|
713
|
-
1.
|
|
714
|
-
|
|
715
|
-
|
|
785
|
+
For large PRs, you have several options:
|
|
786
|
+
1. Enable worktree-based large PR handling:
|
|
787
|
+
Set `allow_large_pr_worktree_bypass: true` in your `aidp.yml`
|
|
788
|
+
2. Break the PR into smaller chunks
|
|
789
|
+
3. Implement changes manually
|
|
790
|
+
4. Increase `max_diff_size` in your configuration
|
|
791
|
+
|
|
792
|
+
The worktree bypass allows processing large PRs by working directly in the branch
|
|
793
|
+
instead of using diff-based changes.
|
|
716
794
|
COMMENT
|
|
717
795
|
|
|
718
796
|
begin
|
data/lib/aidp/watch/runner.rb
CHANGED
|
@@ -14,6 +14,8 @@ require_relative "../auto_update"
|
|
|
14
14
|
require_relative "review_processor"
|
|
15
15
|
require_relative "ci_fix_processor"
|
|
16
16
|
require_relative "change_request_processor"
|
|
17
|
+
require_relative "auto_processor"
|
|
18
|
+
require_relative "auto_pr_processor"
|
|
17
19
|
|
|
18
20
|
module Aidp
|
|
19
21
|
module Watch
|
|
@@ -70,6 +72,13 @@ module Aidp
|
|
|
70
72
|
verbose: verbose,
|
|
71
73
|
label_config: label_config
|
|
72
74
|
)
|
|
75
|
+
@auto_processor = AutoProcessor.new(
|
|
76
|
+
repository_client: @repository_client,
|
|
77
|
+
state_store: @state_store,
|
|
78
|
+
build_processor: @build_processor,
|
|
79
|
+
label_config: label_config,
|
|
80
|
+
verbose: verbose
|
|
81
|
+
)
|
|
73
82
|
|
|
74
83
|
# Initialize auto-update coordinator
|
|
75
84
|
@auto_update_coordinator = Aidp::AutoUpdate.coordinator(project_dir: project_dir)
|
|
@@ -90,6 +99,14 @@ module Aidp
|
|
|
90
99
|
label_config: label_config,
|
|
91
100
|
verbose: verbose
|
|
92
101
|
)
|
|
102
|
+
@auto_pr_processor = AutoPrProcessor.new(
|
|
103
|
+
repository_client: @repository_client,
|
|
104
|
+
state_store: @state_store,
|
|
105
|
+
review_processor: @review_processor,
|
|
106
|
+
ci_fix_processor: @ci_fix_processor,
|
|
107
|
+
label_config: label_config,
|
|
108
|
+
verbose: verbose
|
|
109
|
+
)
|
|
93
110
|
@change_request_processor = ChangeRequestProcessor.new(
|
|
94
111
|
repository_client: @repository_client,
|
|
95
112
|
state_store: @state_store,
|
|
@@ -142,9 +159,11 @@ module Aidp
|
|
|
142
159
|
def process_cycle
|
|
143
160
|
process_plan_triggers
|
|
144
161
|
process_build_triggers
|
|
162
|
+
process_auto_issue_triggers
|
|
145
163
|
check_for_updates_if_due
|
|
146
164
|
process_review_triggers
|
|
147
165
|
process_ci_fix_triggers
|
|
166
|
+
process_auto_pr_triggers
|
|
148
167
|
process_change_request_triggers
|
|
149
168
|
end
|
|
150
169
|
|
|
@@ -264,6 +283,54 @@ module Aidp
|
|
|
264
283
|
end
|
|
265
284
|
end
|
|
266
285
|
|
|
286
|
+
def process_auto_issue_triggers
|
|
287
|
+
auto_label = @auto_processor.auto_label
|
|
288
|
+
begin
|
|
289
|
+
issues = @repository_client.list_issues(labels: [auto_label], state: "open")
|
|
290
|
+
rescue => e
|
|
291
|
+
Aidp.log_error("watch_runner", "auto_issue_poll_failed", label: auto_label, error: e.message)
|
|
292
|
+
return
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
Aidp.log_debug("watch_runner", "auto_issue_poll", label: auto_label, total: issues.size)
|
|
296
|
+
|
|
297
|
+
issues.each do |issue|
|
|
298
|
+
unless issue_has_label?(issue, auto_label)
|
|
299
|
+
Aidp.log_debug("watch_runner", "auto_issue_skip_label_mismatch", issue: issue[:number], labels: issue[:labels])
|
|
300
|
+
next
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
begin
|
|
304
|
+
detailed = @repository_client.fetch_issue(issue[:number])
|
|
305
|
+
rescue => e
|
|
306
|
+
Aidp.log_error("watch_runner", "auto_issue_fetch_failed", issue: issue[:number], error: e.message)
|
|
307
|
+
next
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Check if already in progress by another instance
|
|
311
|
+
if @state_extractor.in_progress?(detailed)
|
|
312
|
+
Aidp.log_debug("watch_runner", "auto_issue_skip_in_progress", issue: detailed[:number])
|
|
313
|
+
next
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# Check author authorization before processing
|
|
317
|
+
unless @safety_checker.should_process_issue?(detailed, enforce: false)
|
|
318
|
+
Aidp.log_debug("watch_runner", "auto_issue_skip_unauthorized_author", issue: detailed[:number], author: detailed[:author])
|
|
319
|
+
next
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Check if detection comment already posted (deduplication)
|
|
323
|
+
unless @state_extractor.detection_comment_posted?(detailed, auto_label)
|
|
324
|
+
post_detection_comment(item_type: :issue, number: detailed[:number], label: auto_label)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
Aidp.log_debug("watch_runner", "auto_issue_process", issue: detailed[:number])
|
|
328
|
+
@auto_processor.process(detailed)
|
|
329
|
+
rescue RepositorySafetyChecker::UnauthorizedAuthorError => e
|
|
330
|
+
Aidp.log_warn("watch_runner", "unauthorized_issue_author_auto", issue: issue[:number], error: e.message)
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
267
334
|
def process_review_triggers
|
|
268
335
|
review_label = @review_processor.review_label
|
|
269
336
|
begin
|
|
@@ -310,6 +377,43 @@ module Aidp
|
|
|
310
377
|
end
|
|
311
378
|
end
|
|
312
379
|
|
|
380
|
+
def process_auto_pr_triggers
|
|
381
|
+
auto_label = @auto_pr_processor.auto_label
|
|
382
|
+
prs = @repository_client.list_pull_requests(labels: [auto_label], state: "open")
|
|
383
|
+
Aidp.log_debug("watch_runner", "auto_pr_poll", label: auto_label, total: prs.size)
|
|
384
|
+
|
|
385
|
+
prs.each do |pr|
|
|
386
|
+
unless pr_has_label?(pr, auto_label)
|
|
387
|
+
Aidp.log_debug("watch_runner", "auto_pr_skip_label_mismatch", pr: pr[:number], labels: pr[:labels])
|
|
388
|
+
next
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
detailed = @repository_client.fetch_pull_request(pr[:number])
|
|
392
|
+
|
|
393
|
+
# Check if already in progress by another instance
|
|
394
|
+
if @state_extractor.in_progress?(detailed)
|
|
395
|
+
Aidp.log_debug("watch_runner", "auto_pr_skip_in_progress", pr: detailed[:number])
|
|
396
|
+
next
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# Check author authorization before processing
|
|
400
|
+
unless @safety_checker.should_process_issue?(detailed, enforce: false)
|
|
401
|
+
Aidp.log_debug("watch_runner", "auto_pr_skip_unauthorized_author", pr: detailed[:number], author: detailed[:author])
|
|
402
|
+
next
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
# Check if detection comment already posted (deduplication)
|
|
406
|
+
unless @state_extractor.detection_comment_posted?(detailed, auto_label)
|
|
407
|
+
post_detection_comment(item_type: :pr, number: detailed[:number], label: auto_label)
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
Aidp.log_debug("watch_runner", "auto_pr_process", pr: detailed[:number])
|
|
411
|
+
@auto_pr_processor.process(detailed)
|
|
412
|
+
rescue RepositorySafetyChecker::UnauthorizedAuthorError => e
|
|
413
|
+
Aidp.log_warn("watch_runner", "unauthorized_pr_author_auto", pr: pr[:number], error: e.message)
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
|
|
313
417
|
def process_ci_fix_triggers
|
|
314
418
|
ci_fix_label = @ci_fix_processor.ci_fix_label
|
|
315
419
|
prs = @repository_client.list_pull_requests(labels: [ci_fix_label], state: "open")
|