aidp 0.33.0 → 0.34.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 +35 -0
- data/lib/aidp/analyze/tree_sitter_scan.rb +3 -0
- data/lib/aidp/cli/eval_command.rb +399 -0
- data/lib/aidp/cli/harness_command.rb +1 -1
- data/lib/aidp/cli/security_command.rb +416 -0
- data/lib/aidp/cli/tools_command.rb +6 -4
- data/lib/aidp/cli.rb +170 -3
- data/lib/aidp/concurrency/exec.rb +3 -0
- data/lib/aidp/config.rb +113 -0
- data/lib/aidp/config_paths.rb +20 -0
- data/lib/aidp/daemon/runner.rb +8 -4
- data/lib/aidp/errors.rb +134 -0
- data/lib/aidp/evaluations/context_capture.rb +205 -0
- data/lib/aidp/evaluations/evaluation_record.rb +114 -0
- data/lib/aidp/evaluations/evaluation_storage.rb +250 -0
- data/lib/aidp/evaluations.rb +23 -0
- data/lib/aidp/execute/async_work_loop_runner.rb +4 -1
- data/lib/aidp/execute/interactive_repl.rb +6 -2
- data/lib/aidp/execute/prompt_evaluator.rb +359 -0
- data/lib/aidp/execute/repl_macros.rb +100 -1
- data/lib/aidp/execute/work_loop_runner.rb +399 -47
- data/lib/aidp/execute/work_loop_state.rb +4 -1
- data/lib/aidp/execute/workflow_selector.rb +3 -0
- data/lib/aidp/harness/ai_decision_engine.rb +79 -0
- data/lib/aidp/harness/capability_registry.rb +2 -0
- data/lib/aidp/harness/condition_detector.rb +3 -0
- data/lib/aidp/harness/config_loader.rb +3 -0
- data/lib/aidp/harness/enhanced_runner.rb +14 -11
- data/lib/aidp/harness/error_handler.rb +3 -0
- data/lib/aidp/harness/provider_factory.rb +3 -0
- data/lib/aidp/harness/provider_manager.rb +6 -0
- data/lib/aidp/harness/runner.rb +5 -1
- data/lib/aidp/harness/state/persistence.rb +3 -0
- data/lib/aidp/harness/state_manager.rb +3 -0
- data/lib/aidp/harness/status_display.rb +28 -20
- data/lib/aidp/harness/thinking_depth_manager.rb +32 -32
- data/lib/aidp/harness/ui/enhanced_tui.rb +4 -0
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +4 -0
- data/lib/aidp/harness/ui/error_handler.rb +3 -0
- data/lib/aidp/harness/ui/job_monitor.rb +4 -0
- data/lib/aidp/harness/ui/navigation/submenu.rb +2 -0
- data/lib/aidp/harness/ui/navigation/workflow_selector.rb +6 -0
- data/lib/aidp/harness/ui/spinner_helper.rb +3 -0
- data/lib/aidp/harness/ui/workflow_controller.rb +3 -0
- data/lib/aidp/harness/user_interface.rb +3 -0
- data/lib/aidp/loader.rb +2 -2
- data/lib/aidp/logger.rb +3 -0
- data/lib/aidp/message_display.rb +31 -0
- data/lib/aidp/pr_worktree_manager.rb +18 -6
- data/lib/aidp/provider_manager.rb +3 -0
- data/lib/aidp/providers/base.rb +2 -0
- data/lib/aidp/security/rule_of_two_enforcer.rb +210 -0
- data/lib/aidp/security/secrets_proxy.rb +328 -0
- data/lib/aidp/security/secrets_registry.rb +227 -0
- data/lib/aidp/security/trifecta_state.rb +220 -0
- data/lib/aidp/security/watch_mode_handler.rb +306 -0
- data/lib/aidp/security/work_loop_adapter.rb +277 -0
- data/lib/aidp/security.rb +56 -0
- data/lib/aidp/setup/wizard.rb +4 -2
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/auto_merger.rb +274 -0
- data/lib/aidp/watch/auto_pr_processor.rb +125 -7
- data/lib/aidp/watch/build_processor.rb +16 -1
- data/lib/aidp/watch/change_request_processor.rb +680 -286
- data/lib/aidp/watch/ci_fix_processor.rb +262 -4
- data/lib/aidp/watch/feedback_collector.rb +191 -0
- data/lib/aidp/watch/hierarchical_pr_strategy.rb +256 -0
- data/lib/aidp/watch/implementation_verifier.rb +142 -1
- data/lib/aidp/watch/plan_generator.rb +70 -13
- data/lib/aidp/watch/plan_processor.rb +12 -5
- data/lib/aidp/watch/projects_processor.rb +286 -0
- data/lib/aidp/watch/repository_client.rb +861 -53
- data/lib/aidp/watch/review_processor.rb +33 -6
- data/lib/aidp/watch/runner.rb +51 -11
- data/lib/aidp/watch/state_store.rb +233 -0
- data/lib/aidp/watch/sub_issue_creator.rb +221 -0
- data/lib/aidp/workflows/guided_agent.rb +4 -0
- data/lib/aidp/workstream_executor.rb +3 -0
- data/lib/aidp/worktree.rb +61 -11
- data/lib/aidp/worktree_branch_manager.rb +347 -101
- data/templates/implementation/iterative_implementation.md +46 -3
- metadata +20 -1
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../message_display"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
module Watch
|
|
7
|
+
# Automatically merges sub-issue PRs when CI passes and conditions are met.
|
|
8
|
+
# Never auto-merges parent PRs - those require human review.
|
|
9
|
+
class AutoMerger
|
|
10
|
+
include Aidp::MessageDisplay
|
|
11
|
+
|
|
12
|
+
# Labels that indicate PR type
|
|
13
|
+
PARENT_PR_LABEL = "aidp-parent-pr"
|
|
14
|
+
SUB_PR_LABEL = "aidp-sub-pr"
|
|
15
|
+
|
|
16
|
+
# Default configuration
|
|
17
|
+
DEFAULT_CONFIG = {
|
|
18
|
+
enabled: true,
|
|
19
|
+
sub_issue_prs_only: true,
|
|
20
|
+
require_ci_success: true,
|
|
21
|
+
require_reviews: 0,
|
|
22
|
+
merge_method: "squash",
|
|
23
|
+
delete_branch: true
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
attr_reader :repository_client, :state_store
|
|
27
|
+
|
|
28
|
+
def initialize(repository_client:, state_store:, config: {})
|
|
29
|
+
@repository_client = repository_client
|
|
30
|
+
@state_store = state_store
|
|
31
|
+
@config = DEFAULT_CONFIG.merge(config)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Check if a PR can be auto-merged
|
|
35
|
+
# @param pr_number [Integer] The PR number
|
|
36
|
+
# @return [Hash] Result with :can_merge flag and :reason
|
|
37
|
+
def can_auto_merge?(pr_number)
|
|
38
|
+
Aidp.log_debug("auto_merger", "checking_can_auto_merge", pr_number: pr_number)
|
|
39
|
+
|
|
40
|
+
return {can_merge: false, reason: "Auto-merge is disabled"} unless @config[:enabled]
|
|
41
|
+
|
|
42
|
+
# Fetch PR details
|
|
43
|
+
pr = begin
|
|
44
|
+
@repository_client.fetch_pull_request(pr_number)
|
|
45
|
+
rescue => e
|
|
46
|
+
Aidp.log_error("auto_merger", "Failed to fetch PR", pr_number: pr_number, error: e.message)
|
|
47
|
+
return {can_merge: false, reason: "Failed to fetch PR: #{e.message}"}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Check if it's a parent PR (never auto-merge)
|
|
51
|
+
if pr[:labels].include?(PARENT_PR_LABEL)
|
|
52
|
+
Aidp.log_debug("auto_merger", "skipping_parent_pr", pr_number: pr_number)
|
|
53
|
+
return {can_merge: false, reason: "Parent PRs require human review"}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Check if sub-PRs only mode requires the sub-PR label
|
|
57
|
+
if @config[:sub_issue_prs_only] && !pr[:labels].include?(SUB_PR_LABEL)
|
|
58
|
+
Aidp.log_debug("auto_merger", "not_a_sub_pr", pr_number: pr_number)
|
|
59
|
+
return {can_merge: false, reason: "Not a sub-issue PR (missing #{SUB_PR_LABEL} label)"}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Check PR state
|
|
63
|
+
unless pr[:state] == "open" || pr[:state] == "OPEN"
|
|
64
|
+
return {can_merge: false, reason: "PR is not open (state: #{pr[:state]})"}
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Check mergeability
|
|
68
|
+
if pr[:mergeable] == false
|
|
69
|
+
return {can_merge: false, reason: "PR has merge conflicts"}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Check CI status
|
|
73
|
+
if @config[:require_ci_success]
|
|
74
|
+
ci_status = @repository_client.fetch_ci_status(pr_number)
|
|
75
|
+
unless ci_status[:state] == "success"
|
|
76
|
+
Aidp.log_debug("auto_merger", "ci_not_passed",
|
|
77
|
+
pr_number: pr_number, ci_state: ci_status[:state])
|
|
78
|
+
return {can_merge: false, reason: "CI has not passed (status: #{ci_status[:state]})"}
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# All checks passed
|
|
83
|
+
Aidp.log_debug("auto_merger", "can_auto_merge", pr_number: pr_number)
|
|
84
|
+
{can_merge: true, reason: "All merge conditions met"}
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Attempt to merge a PR
|
|
88
|
+
# @param pr_number [Integer] The PR number
|
|
89
|
+
# @return [Hash] Result with :success flag, :reason, and optional :merge_sha
|
|
90
|
+
def merge_pr(pr_number)
|
|
91
|
+
Aidp.log_debug("auto_merger", "attempting_merge", pr_number: pr_number)
|
|
92
|
+
|
|
93
|
+
# Verify can merge
|
|
94
|
+
eligibility = can_auto_merge?(pr_number)
|
|
95
|
+
unless eligibility[:can_merge]
|
|
96
|
+
return {success: false, reason: eligibility[:reason]}
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
begin
|
|
100
|
+
result = @repository_client.merge_pull_request(
|
|
101
|
+
pr_number,
|
|
102
|
+
merge_method: @config[:merge_method]
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
Aidp.log_info("auto_merger", "pr_merged",
|
|
106
|
+
pr_number: pr_number, merge_method: @config[:merge_method])
|
|
107
|
+
display_message("✅ Auto-merged PR ##{pr_number}", type: :success)
|
|
108
|
+
|
|
109
|
+
# Post comment about auto-merge
|
|
110
|
+
post_merge_comment(pr_number)
|
|
111
|
+
|
|
112
|
+
# Update parent issue/PR if this was a sub-issue PR
|
|
113
|
+
update_parent_after_merge(pr_number)
|
|
114
|
+
|
|
115
|
+
{success: true, reason: "Successfully merged", result: result}
|
|
116
|
+
rescue => e
|
|
117
|
+
Aidp.log_error("auto_merger", "merge_failed",
|
|
118
|
+
pr_number: pr_number, error: e.message)
|
|
119
|
+
display_message("❌ Failed to auto-merge PR ##{pr_number}: #{e.message}", type: :error)
|
|
120
|
+
{success: false, reason: "Merge failed: #{e.message}"}
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Process all eligible PRs for auto-merge
|
|
125
|
+
# @param prs [Array<Hash>] Array of PR data with :number keys
|
|
126
|
+
# @return [Hash] Summary with :merged, :skipped, :failed counts
|
|
127
|
+
def process_auto_merge_candidates(prs)
|
|
128
|
+
Aidp.log_debug("auto_merger", "processing_candidates", count: prs.size)
|
|
129
|
+
|
|
130
|
+
merged = 0
|
|
131
|
+
skipped = 0
|
|
132
|
+
failed = 0
|
|
133
|
+
|
|
134
|
+
prs.each do |pr|
|
|
135
|
+
pr_number = pr[:number]
|
|
136
|
+
|
|
137
|
+
eligibility = can_auto_merge?(pr_number)
|
|
138
|
+
unless eligibility[:can_merge]
|
|
139
|
+
Aidp.log_debug("auto_merger", "skipping_pr",
|
|
140
|
+
pr_number: pr_number, reason: eligibility[:reason])
|
|
141
|
+
skipped += 1
|
|
142
|
+
next
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
result = merge_pr(pr_number)
|
|
146
|
+
if result[:success]
|
|
147
|
+
merged += 1
|
|
148
|
+
else
|
|
149
|
+
failed += 1
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
summary = {merged: merged, skipped: skipped, failed: failed}
|
|
154
|
+
Aidp.log_info("auto_merger", "processing_complete", **summary)
|
|
155
|
+
display_message("🔀 Auto-merge: #{merged} merged, #{skipped} skipped, #{failed} failed",
|
|
156
|
+
type: :info)
|
|
157
|
+
summary
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# List all PRs with the sub-PR label that are candidates for auto-merge
|
|
161
|
+
# @return [Array<Hash>] PRs that might be eligible for auto-merge
|
|
162
|
+
def list_sub_pr_candidates
|
|
163
|
+
@repository_client.list_pull_requests(labels: [SUB_PR_LABEL], state: "open")
|
|
164
|
+
rescue => e
|
|
165
|
+
Aidp.log_error("auto_merger", "Failed to list sub-PR candidates", error: e.message)
|
|
166
|
+
[]
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
private
|
|
170
|
+
|
|
171
|
+
def post_merge_comment(pr_number)
|
|
172
|
+
comment = <<~COMMENT
|
|
173
|
+
✅ This PR was automatically merged by AIDP after CI passed.
|
|
174
|
+
|
|
175
|
+
Merge method: `#{@config[:merge_method]}`
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
_Sub-issue PRs are automatically merged when CI passes. Parent PRs always require human review._
|
|
179
|
+
COMMENT
|
|
180
|
+
|
|
181
|
+
begin
|
|
182
|
+
@repository_client.post_comment(pr_number, comment)
|
|
183
|
+
rescue => e
|
|
184
|
+
Aidp.log_warn("auto_merger", "Failed to post merge comment",
|
|
185
|
+
pr_number: pr_number, error: e.message)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def update_parent_after_merge(pr_number)
|
|
190
|
+
# Find the parent issue for this sub-PR
|
|
191
|
+
# The sub-PR should target the parent's branch, so we can identify it
|
|
192
|
+
|
|
193
|
+
# First, check if we have hierarchy data
|
|
194
|
+
build_data = @state_store.find_build_by_pr(pr_number)
|
|
195
|
+
return unless build_data
|
|
196
|
+
|
|
197
|
+
issue_number = build_data[:issue_number]
|
|
198
|
+
parent_number = @state_store.parent_issue(issue_number)
|
|
199
|
+
return unless parent_number
|
|
200
|
+
|
|
201
|
+
Aidp.log_debug("auto_merger", "updating_parent_after_merge",
|
|
202
|
+
sub_issue: issue_number, parent: parent_number)
|
|
203
|
+
|
|
204
|
+
# Check if all sub-issues are now complete
|
|
205
|
+
sub_issues = @state_store.sub_issues(parent_number)
|
|
206
|
+
all_complete = sub_issues.all? do |sub_number|
|
|
207
|
+
sub_build = @state_store.workstream_for_issue(sub_number)
|
|
208
|
+
sub_build && sub_build[:status] == "completed"
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
if all_complete
|
|
212
|
+
notify_parent_ready_for_review(parent_number)
|
|
213
|
+
end
|
|
214
|
+
rescue => e
|
|
215
|
+
Aidp.log_warn("auto_merger", "Failed to update parent after merge",
|
|
216
|
+
pr_number: pr_number, error: e.message)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def notify_parent_ready_for_review(parent_number)
|
|
220
|
+
Aidp.log_info("auto_merger", "all_sub_issues_complete", parent: parent_number)
|
|
221
|
+
|
|
222
|
+
comment = <<~COMMENT
|
|
223
|
+
🎉 All sub-issue PRs have been merged!
|
|
224
|
+
|
|
225
|
+
The parent PR is now ready for final review and merge to main.
|
|
226
|
+
|
|
227
|
+
### Sub-Issues Completed
|
|
228
|
+
#{format_sub_issues_list(parent_number)}
|
|
229
|
+
|
|
230
|
+
**Next Steps:**
|
|
231
|
+
1. Review the combined changes in the parent PR
|
|
232
|
+
2. Ensure all integration tests pass
|
|
233
|
+
3. Merge the parent PR manually
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
_Parent PRs are never auto-merged and require human review._
|
|
237
|
+
COMMENT
|
|
238
|
+
|
|
239
|
+
begin
|
|
240
|
+
@repository_client.post_comment(parent_number, comment)
|
|
241
|
+
display_message("📋 Notified parent issue ##{parent_number} that all sub-PRs are merged",
|
|
242
|
+
type: :success)
|
|
243
|
+
|
|
244
|
+
# Mark the parent PR as ready for review if it's still draft
|
|
245
|
+
parent_build = @state_store.workstream_for_issue(parent_number)
|
|
246
|
+
if parent_build && parent_build[:pr_url]
|
|
247
|
+
pr_number = parent_build[:pr_url].split("/").last.to_i
|
|
248
|
+
begin
|
|
249
|
+
@repository_client.mark_pr_ready_for_review(pr_number)
|
|
250
|
+
display_message("✅ Marked parent PR ##{pr_number} as ready for review", type: :success)
|
|
251
|
+
rescue => e
|
|
252
|
+
Aidp.log_warn("auto_merger", "Failed to mark parent PR ready",
|
|
253
|
+
pr_number: pr_number, error: e.message)
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
rescue => e
|
|
257
|
+
Aidp.log_warn("auto_merger", "Failed to notify parent",
|
|
258
|
+
parent: parent_number, error: e.message)
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def format_sub_issues_list(parent_number)
|
|
263
|
+
sub_issues = @state_store.sub_issues(parent_number)
|
|
264
|
+
return "_No sub-issues found_" if sub_issues.empty?
|
|
265
|
+
|
|
266
|
+
sub_issues.map do |sub_number|
|
|
267
|
+
build = @state_store.workstream_for_issue(sub_number)
|
|
268
|
+
pr_link = build&.dig(:pr_url) || "No PR"
|
|
269
|
+
"- ##{sub_number}: #{pr_link}"
|
|
270
|
+
end.join("\n")
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
@@ -7,19 +7,35 @@ module Aidp
|
|
|
7
7
|
module Watch
|
|
8
8
|
# Handles the aidp-auto label on PRs by chaining review and CI-fix flows
|
|
9
9
|
# until the PR is ready for human review.
|
|
10
|
+
#
|
|
11
|
+
# Completion criteria:
|
|
12
|
+
# - Automated review completed (or already processed)
|
|
13
|
+
# - CI passing (success or skipped states accepted)
|
|
14
|
+
# - Iteration cap not exceeded (default: 20)
|
|
15
|
+
#
|
|
16
|
+
# When complete:
|
|
17
|
+
# - Converts draft PR to ready for review
|
|
18
|
+
# - Requests the label-adder as reviewer
|
|
19
|
+
# - Posts completion comment
|
|
20
|
+
# - Removes aidp-auto label
|
|
10
21
|
class AutoPrProcessor
|
|
11
22
|
include Aidp::MessageDisplay
|
|
12
23
|
|
|
13
24
|
DEFAULT_AUTO_LABEL = "aidp-auto"
|
|
25
|
+
DEFAULT_ITERATION_CAP = 20
|
|
26
|
+
PASSING_CI_STATES = %w[success skipped].freeze
|
|
14
27
|
|
|
15
|
-
def initialize(repository_client:, state_store:, review_processor:, ci_fix_processor:,
|
|
28
|
+
def initialize(repository_client:, state_store:, review_processor:, ci_fix_processor:,
|
|
29
|
+
label_config: {}, iteration_cap: nil, verbose: false, prompt: nil)
|
|
16
30
|
@repository_client = repository_client
|
|
17
31
|
@state_store = state_store
|
|
18
32
|
@review_processor = review_processor
|
|
19
33
|
@ci_fix_processor = ci_fix_processor
|
|
20
34
|
@state_extractor = GitHubStateExtractor.new(repository_client: repository_client)
|
|
21
35
|
@verbose = verbose
|
|
36
|
+
@prompt = prompt
|
|
22
37
|
@auto_label = label_config[:auto_trigger] || label_config["auto_trigger"] || DEFAULT_AUTO_LABEL
|
|
38
|
+
@iteration_cap = iteration_cap || DEFAULT_ITERATION_CAP
|
|
23
39
|
end
|
|
24
40
|
|
|
25
41
|
def process(pr)
|
|
@@ -27,6 +43,19 @@ module Aidp
|
|
|
27
43
|
Aidp.log_debug("auto_pr_processor", "process_started", pr: number, title: pr[:title])
|
|
28
44
|
display_message("🤖 Running autonomous review/CI loop for PR ##{number}", type: :info)
|
|
29
45
|
|
|
46
|
+
# Record this iteration
|
|
47
|
+
iteration = @state_store.record_auto_pr_iteration(number)
|
|
48
|
+
Aidp.log_debug("auto_pr_processor", "iteration_recorded", pr: number, iteration: iteration, cap: @iteration_cap)
|
|
49
|
+
|
|
50
|
+
# Check iteration cap
|
|
51
|
+
if iteration > @iteration_cap
|
|
52
|
+
Aidp.log_warn("auto_pr_processor", "iteration_cap_reached",
|
|
53
|
+
pr: number, iteration: iteration, cap: @iteration_cap)
|
|
54
|
+
display_message("⚠️ PR ##{number} reached iteration cap (#{@iteration_cap}), marking ready", type: :warn)
|
|
55
|
+
finalize_pr(pr_number: number, reason: :iteration_cap_reached)
|
|
56
|
+
return
|
|
57
|
+
end
|
|
58
|
+
|
|
30
59
|
# Run review and CI fix flows. Each processor is responsible for its own guards.
|
|
31
60
|
@review_processor.process(pr)
|
|
32
61
|
@ci_fix_processor.process(pr)
|
|
@@ -37,7 +66,7 @@ module Aidp
|
|
|
37
66
|
display_message("❌ aidp-auto failed on PR ##{pr[:number]}: #{e.message}", type: :error)
|
|
38
67
|
end
|
|
39
68
|
|
|
40
|
-
attr_reader :auto_label
|
|
69
|
+
attr_reader :auto_label, :iteration_cap
|
|
41
70
|
|
|
42
71
|
private
|
|
43
72
|
|
|
@@ -46,25 +75,114 @@ module Aidp
|
|
|
46
75
|
ci_status = @repository_client.fetch_ci_status(pr_number)
|
|
47
76
|
|
|
48
77
|
review_done = @state_extractor.review_completed?(pr_data) || @state_store.review_processed?(pr_number)
|
|
49
|
-
ci_passing = ci_status
|
|
78
|
+
ci_passing = ci_state_passing?(ci_status)
|
|
50
79
|
|
|
51
80
|
Aidp.log_debug("auto_pr_processor", "completion_check",
|
|
52
81
|
pr: pr_number,
|
|
53
82
|
review_done: review_done,
|
|
54
|
-
ci_state: ci_status[:state]
|
|
83
|
+
ci_state: ci_status[:state],
|
|
84
|
+
ci_passing: ci_passing)
|
|
55
85
|
|
|
56
86
|
return unless review_done && ci_passing
|
|
57
87
|
|
|
58
|
-
|
|
88
|
+
finalize_pr(pr_number: pr_number, reason: :completion_criteria_met)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Check if CI state indicates passing (success or skipped)
|
|
92
|
+
# @param ci_status [Hash] CI status from repository_client
|
|
93
|
+
# @return [Boolean] True if CI is considered passing
|
|
94
|
+
def ci_state_passing?(ci_status)
|
|
95
|
+
state = ci_status[:state]
|
|
96
|
+
|
|
97
|
+
# Direct state match
|
|
98
|
+
return true if PASSING_CI_STATES.include?(state)
|
|
99
|
+
|
|
100
|
+
# Check individual checks - all must be success or skipped
|
|
101
|
+
checks = ci_status[:checks] || []
|
|
102
|
+
return false if checks.empty?
|
|
103
|
+
|
|
104
|
+
checks.all? do |check|
|
|
105
|
+
conclusion = check[:conclusion]
|
|
106
|
+
PASSING_CI_STATES.include?(conclusion)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def finalize_pr(pr_number:, reason:)
|
|
111
|
+
Aidp.log_info("auto_pr_processor", "finalizing_pr",
|
|
112
|
+
pr: pr_number, reason: reason)
|
|
113
|
+
|
|
114
|
+
# Convert draft to ready if needed
|
|
115
|
+
convert_draft_to_ready(pr_number)
|
|
116
|
+
|
|
117
|
+
# Request reviewer (label-adder)
|
|
118
|
+
request_label_adder_as_reviewer(pr_number)
|
|
119
|
+
|
|
120
|
+
# Post completion comment
|
|
121
|
+
post_completion_comment(pr_number, reason: reason)
|
|
122
|
+
|
|
123
|
+
# Remove auto label
|
|
59
124
|
remove_auto_label(pr_number)
|
|
125
|
+
|
|
126
|
+
# Mark as completed in state store
|
|
127
|
+
@state_store.complete_auto_pr(pr_number, reason: reason.to_s)
|
|
128
|
+
|
|
129
|
+
display_message("✅ PR ##{pr_number} ready for human review", type: :success)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def convert_draft_to_ready(pr_number)
|
|
133
|
+
# Attempt the conversion and let it fail gracefully if already ready
|
|
134
|
+
# The gh CLI will return an error if PR is not a draft
|
|
135
|
+
success = @repository_client.mark_pr_ready_for_review(pr_number)
|
|
136
|
+
if success
|
|
137
|
+
display_message("📋 Converted PR ##{pr_number} from draft to ready", type: :info)
|
|
138
|
+
else
|
|
139
|
+
Aidp.log_debug("auto_pr_processor", "draft_conversion_skipped",
|
|
140
|
+
pr: pr_number, message: "PR may already be ready for review")
|
|
141
|
+
end
|
|
142
|
+
rescue => e
|
|
143
|
+
Aidp.log_warn("auto_pr_processor", "draft_conversion_failed",
|
|
144
|
+
pr: pr_number, error: e.message)
|
|
60
145
|
end
|
|
61
146
|
|
|
62
|
-
def
|
|
147
|
+
def request_label_adder_as_reviewer(pr_number)
|
|
148
|
+
label_actor = @repository_client.most_recent_pr_label_actor(pr_number)
|
|
149
|
+
|
|
150
|
+
unless label_actor
|
|
151
|
+
Aidp.log_debug("auto_pr_processor", "no_label_actor_found", pr: pr_number)
|
|
152
|
+
return
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
success = @repository_client.request_reviewers(pr_number, reviewers: [label_actor])
|
|
156
|
+
if success
|
|
157
|
+
display_message("👤 Requested @#{label_actor} as reviewer on PR ##{pr_number}", type: :info)
|
|
158
|
+
else
|
|
159
|
+
Aidp.log_warn("auto_pr_processor", "reviewer_request_failed",
|
|
160
|
+
pr: pr_number, reviewer: label_actor)
|
|
161
|
+
end
|
|
162
|
+
rescue => e
|
|
163
|
+
Aidp.log_warn("auto_pr_processor", "reviewer_request_exception",
|
|
164
|
+
pr: pr_number, error: e.message)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def post_completion_comment(pr_number, reason:)
|
|
168
|
+
iteration = @state_store.auto_pr_iteration_count(pr_number)
|
|
169
|
+
|
|
170
|
+
reason_text = case reason
|
|
171
|
+
when :iteration_cap_reached
|
|
172
|
+
"Iteration cap (#{@iteration_cap}) reached"
|
|
173
|
+
when :completion_criteria_met
|
|
174
|
+
"All completion criteria met"
|
|
175
|
+
else
|
|
176
|
+
"Processing complete"
|
|
177
|
+
end
|
|
178
|
+
|
|
63
179
|
comment = <<~COMMENT
|
|
64
180
|
## 🤖 aidp-auto
|
|
65
181
|
|
|
182
|
+
#{reason_text} after #{iteration} iteration(s).
|
|
183
|
+
|
|
66
184
|
- Automated review completed
|
|
67
|
-
- CI is passing
|
|
185
|
+
- CI is passing (success/skipped)
|
|
68
186
|
|
|
69
187
|
Marking this PR ready for human review and removing the `#{@auto_label}` label.
|
|
70
188
|
COMMENT
|
|
@@ -12,6 +12,7 @@ require_relative "../worktree"
|
|
|
12
12
|
require_relative "../execute/progress"
|
|
13
13
|
require_relative "github_state_extractor"
|
|
14
14
|
require_relative "implementation_verifier"
|
|
15
|
+
require_relative "feedback_collector"
|
|
15
16
|
|
|
16
17
|
module Aidp
|
|
17
18
|
module Watch
|
|
@@ -25,6 +26,8 @@ module Aidp
|
|
|
25
26
|
IMPLEMENTATION_STEP = "16_IMPLEMENTATION"
|
|
26
27
|
|
|
27
28
|
attr_reader :build_label, :needs_input_label
|
|
29
|
+
# Expose state for testability
|
|
30
|
+
attr_writer :config, :verifier
|
|
28
31
|
|
|
29
32
|
def initialize(repository_client:, state_store:, project_dir: Dir.pwd, use_workstreams: true, verbose: false, label_config: {})
|
|
30
33
|
@repository_client = repository_client
|
|
@@ -525,7 +528,19 @@ module Aidp
|
|
|
525
528
|
#{plan_value(plan_data, "summary")}
|
|
526
529
|
COMMENT
|
|
527
530
|
|
|
528
|
-
|
|
531
|
+
comment_with_feedback = FeedbackCollector.append_feedback_prompt(comment)
|
|
532
|
+
result = @repository_client.post_comment(issue[:number], comment_with_feedback)
|
|
533
|
+
comment_id = result[:id] if result.is_a?(Hash)
|
|
534
|
+
|
|
535
|
+
# Track comment for feedback collection
|
|
536
|
+
if comment_id
|
|
537
|
+
@state_store.track_comment_for_feedback(
|
|
538
|
+
comment_id: comment_id,
|
|
539
|
+
processor_type: "build",
|
|
540
|
+
number: issue[:number]
|
|
541
|
+
)
|
|
542
|
+
end
|
|
543
|
+
|
|
529
544
|
display_message("🎉 Posted completion comment for issue ##{issue[:number]}", type: :success)
|
|
530
545
|
|
|
531
546
|
# Keep workstream for review - don't auto-cleanup on success
|