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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -0
  3. data/lib/aidp/analyze/tree_sitter_scan.rb +3 -0
  4. data/lib/aidp/cli/eval_command.rb +399 -0
  5. data/lib/aidp/cli/harness_command.rb +1 -1
  6. data/lib/aidp/cli/security_command.rb +416 -0
  7. data/lib/aidp/cli/tools_command.rb +6 -4
  8. data/lib/aidp/cli.rb +170 -3
  9. data/lib/aidp/concurrency/exec.rb +3 -0
  10. data/lib/aidp/config.rb +113 -0
  11. data/lib/aidp/config_paths.rb +20 -0
  12. data/lib/aidp/daemon/runner.rb +8 -4
  13. data/lib/aidp/errors.rb +134 -0
  14. data/lib/aidp/evaluations/context_capture.rb +205 -0
  15. data/lib/aidp/evaluations/evaluation_record.rb +114 -0
  16. data/lib/aidp/evaluations/evaluation_storage.rb +250 -0
  17. data/lib/aidp/evaluations.rb +23 -0
  18. data/lib/aidp/execute/async_work_loop_runner.rb +4 -1
  19. data/lib/aidp/execute/interactive_repl.rb +6 -2
  20. data/lib/aidp/execute/prompt_evaluator.rb +359 -0
  21. data/lib/aidp/execute/repl_macros.rb +100 -1
  22. data/lib/aidp/execute/work_loop_runner.rb +399 -47
  23. data/lib/aidp/execute/work_loop_state.rb +4 -1
  24. data/lib/aidp/execute/workflow_selector.rb +3 -0
  25. data/lib/aidp/harness/ai_decision_engine.rb +79 -0
  26. data/lib/aidp/harness/capability_registry.rb +2 -0
  27. data/lib/aidp/harness/condition_detector.rb +3 -0
  28. data/lib/aidp/harness/config_loader.rb +3 -0
  29. data/lib/aidp/harness/enhanced_runner.rb +14 -11
  30. data/lib/aidp/harness/error_handler.rb +3 -0
  31. data/lib/aidp/harness/provider_factory.rb +3 -0
  32. data/lib/aidp/harness/provider_manager.rb +6 -0
  33. data/lib/aidp/harness/runner.rb +5 -1
  34. data/lib/aidp/harness/state/persistence.rb +3 -0
  35. data/lib/aidp/harness/state_manager.rb +3 -0
  36. data/lib/aidp/harness/status_display.rb +28 -20
  37. data/lib/aidp/harness/thinking_depth_manager.rb +32 -32
  38. data/lib/aidp/harness/ui/enhanced_tui.rb +4 -0
  39. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +4 -0
  40. data/lib/aidp/harness/ui/error_handler.rb +3 -0
  41. data/lib/aidp/harness/ui/job_monitor.rb +4 -0
  42. data/lib/aidp/harness/ui/navigation/submenu.rb +2 -0
  43. data/lib/aidp/harness/ui/navigation/workflow_selector.rb +6 -0
  44. data/lib/aidp/harness/ui/spinner_helper.rb +3 -0
  45. data/lib/aidp/harness/ui/workflow_controller.rb +3 -0
  46. data/lib/aidp/harness/user_interface.rb +3 -0
  47. data/lib/aidp/loader.rb +2 -2
  48. data/lib/aidp/logger.rb +3 -0
  49. data/lib/aidp/message_display.rb +31 -0
  50. data/lib/aidp/pr_worktree_manager.rb +18 -6
  51. data/lib/aidp/provider_manager.rb +3 -0
  52. data/lib/aidp/providers/base.rb +2 -0
  53. data/lib/aidp/security/rule_of_two_enforcer.rb +210 -0
  54. data/lib/aidp/security/secrets_proxy.rb +328 -0
  55. data/lib/aidp/security/secrets_registry.rb +227 -0
  56. data/lib/aidp/security/trifecta_state.rb +220 -0
  57. data/lib/aidp/security/watch_mode_handler.rb +306 -0
  58. data/lib/aidp/security/work_loop_adapter.rb +277 -0
  59. data/lib/aidp/security.rb +56 -0
  60. data/lib/aidp/setup/wizard.rb +4 -2
  61. data/lib/aidp/version.rb +1 -1
  62. data/lib/aidp/watch/auto_merger.rb +274 -0
  63. data/lib/aidp/watch/auto_pr_processor.rb +125 -7
  64. data/lib/aidp/watch/build_processor.rb +16 -1
  65. data/lib/aidp/watch/change_request_processor.rb +680 -286
  66. data/lib/aidp/watch/ci_fix_processor.rb +262 -4
  67. data/lib/aidp/watch/feedback_collector.rb +191 -0
  68. data/lib/aidp/watch/hierarchical_pr_strategy.rb +256 -0
  69. data/lib/aidp/watch/implementation_verifier.rb +142 -1
  70. data/lib/aidp/watch/plan_generator.rb +70 -13
  71. data/lib/aidp/watch/plan_processor.rb +12 -5
  72. data/lib/aidp/watch/projects_processor.rb +286 -0
  73. data/lib/aidp/watch/repository_client.rb +861 -53
  74. data/lib/aidp/watch/review_processor.rb +33 -6
  75. data/lib/aidp/watch/runner.rb +51 -11
  76. data/lib/aidp/watch/state_store.rb +233 -0
  77. data/lib/aidp/watch/sub_issue_creator.rb +221 -0
  78. data/lib/aidp/workflows/guided_agent.rb +4 -0
  79. data/lib/aidp/workstream_executor.rb +3 -0
  80. data/lib/aidp/worktree.rb +61 -11
  81. data/lib/aidp/worktree_branch_manager.rb +347 -101
  82. data/templates/implementation/iterative_implementation.md +46 -3
  83. 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:, label_config: {}, verbose: false)
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[:state] == "success"
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
- post_completion_comment(pr_number)
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 post_completion_comment(pr_number)
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
- @repository_client.post_comment(issue[:number], comment)
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