aidp 0.33.0 → 0.34.1
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/ui.rb +11 -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 +21 -1
|
@@ -13,6 +13,8 @@ require_relative "../harness/runner"
|
|
|
13
13
|
require_relative "../harness/state_manager"
|
|
14
14
|
require_relative "../harness/test_runner"
|
|
15
15
|
require_relative "../worktree"
|
|
16
|
+
require_relative "../pr_worktree_manager"
|
|
17
|
+
require_relative "../worktree_branch_manager"
|
|
16
18
|
require_relative "github_state_extractor"
|
|
17
19
|
require_relative "implementation_verifier"
|
|
18
20
|
|
|
@@ -32,6 +34,9 @@ module Aidp
|
|
|
32
34
|
|
|
33
35
|
attr_reader :change_request_label, :needs_input_label
|
|
34
36
|
|
|
37
|
+
# Expose state for testability
|
|
38
|
+
attr_accessor :project_dir, :worktree_branch_manager
|
|
39
|
+
|
|
35
40
|
def initialize(repository_client:, state_store:, provider_name: nil, project_dir: Dir.pwd, label_config: {}, change_request_config: {}, safety_config: {}, verbose: false)
|
|
36
41
|
@repository_client = repository_client
|
|
37
42
|
@state_store = state_store
|
|
@@ -40,11 +45,9 @@ module Aidp
|
|
|
40
45
|
@project_dir = project_dir
|
|
41
46
|
@verbose = verbose
|
|
42
47
|
|
|
43
|
-
#
|
|
44
|
-
Aidp.
|
|
45
|
-
|
|
46
|
-
project_dir: project_dir,
|
|
47
|
-
verbose: verbose)
|
|
48
|
+
# Initialize worktree managers
|
|
49
|
+
@pr_worktree_manager = Aidp::PRWorktreeManager.new(project_dir: project_dir)
|
|
50
|
+
@worktree_branch_manager = Aidp::WorktreeBranchManager.new(project_dir: project_dir)
|
|
48
51
|
|
|
49
52
|
# Initialize verifier
|
|
50
53
|
@verifier = ImplementationVerifier.new(
|
|
@@ -56,11 +59,6 @@ module Aidp
|
|
|
56
59
|
@change_request_label = label_config[:change_request_trigger] || label_config["change_request_trigger"] || DEFAULT_CHANGE_REQUEST_LABEL
|
|
57
60
|
@needs_input_label = label_config[:needs_input] || label_config["needs_input"] || DEFAULT_NEEDS_INPUT_LABEL
|
|
58
61
|
|
|
59
|
-
# Log label details
|
|
60
|
-
Aidp.log_debug("change_request_processor", "label_configuration",
|
|
61
|
-
change_request_label: @change_request_label,
|
|
62
|
-
needs_input_label: @needs_input_label)
|
|
63
|
-
|
|
64
62
|
# Load change request configuration
|
|
65
63
|
@config = {
|
|
66
64
|
enabled: true,
|
|
@@ -69,40 +67,62 @@ module Aidp
|
|
|
69
67
|
commit_message_prefix: "aidp: pr-change",
|
|
70
68
|
require_comment_reference: true,
|
|
71
69
|
max_diff_size: 2000,
|
|
72
|
-
|
|
70
|
+
large_pr_strategy: "create_worktree", # Options: create_worktree, manual, skip
|
|
71
|
+
worktree_strategy: "auto", # Options: auto, always_create, reuse_only
|
|
72
|
+
worktree_max_age: 7 * 24 * 60 * 60, # 7 days in seconds
|
|
73
|
+
worktree_cleanup_on_success: true,
|
|
74
|
+
worktree_cleanup_on_failure: false
|
|
73
75
|
}.merge(symbolize_keys(change_request_config))
|
|
74
76
|
|
|
75
|
-
# Log configuration
|
|
76
|
-
Aidp.log_debug(
|
|
77
|
-
|
|
77
|
+
# Log the configuration, especially the large PR strategy
|
|
78
|
+
Aidp.log_debug(
|
|
79
|
+
"change_request_processor", "Initialized with config",
|
|
80
|
+
max_diff_size: @config[:max_diff_size],
|
|
81
|
+
large_pr_strategy: @config[:large_pr_strategy],
|
|
82
|
+
run_tests_before_push: @config[:run_tests_before_push],
|
|
83
|
+
enabled: @config[:enabled],
|
|
84
|
+
worktree_strategy: @config[:worktree_strategy],
|
|
85
|
+
worktree_max_age: @config[:worktree_max_age],
|
|
86
|
+
worktree_cleanup_on_success: @config[:worktree_cleanup_on_success],
|
|
87
|
+
worktree_cleanup_on_failure: @config[:worktree_cleanup_on_failure]
|
|
88
|
+
)
|
|
78
89
|
|
|
79
90
|
# Load safety configuration
|
|
80
91
|
@safety_config = safety_config
|
|
81
92
|
@author_allowlist = Array(@safety_config[:author_allowlist] || @safety_config["author_allowlist"])
|
|
82
|
-
|
|
83
|
-
# Log safety configuration
|
|
84
|
-
Aidp.log_debug("change_request_processor", "safety_configuration",
|
|
85
|
-
author_allowlist: @author_allowlist,
|
|
86
|
-
allowlist_count: @author_allowlist.length)
|
|
87
93
|
end
|
|
88
94
|
|
|
89
95
|
def process(pr)
|
|
90
96
|
number = pr[:number]
|
|
91
97
|
|
|
98
|
+
Aidp.log_debug(
|
|
99
|
+
"change_request_processor", "Starting change request processing",
|
|
100
|
+
pr_number: number, pr_title: pr[:title]
|
|
101
|
+
)
|
|
102
|
+
|
|
92
103
|
unless @config[:enabled]
|
|
93
|
-
display_message(
|
|
104
|
+
display_message(
|
|
105
|
+
"ℹ️ PR change requests are disabled in configuration. Skipping PR ##{number}.",
|
|
106
|
+
type: :muted
|
|
107
|
+
)
|
|
94
108
|
return
|
|
95
109
|
end
|
|
96
110
|
|
|
97
111
|
# Check clarification round limit
|
|
98
112
|
existing_data = @state_store.change_request_data(number)
|
|
99
113
|
if existing_data && existing_data["clarification_count"].to_i >= MAX_CLARIFICATION_ROUNDS
|
|
100
|
-
display_message(
|
|
114
|
+
display_message(
|
|
115
|
+
"⚠️ Max clarification rounds (#{MAX_CLARIFICATION_ROUNDS}) reached for PR ##{number}. Skipping.",
|
|
116
|
+
type: :warn
|
|
117
|
+
)
|
|
101
118
|
post_max_rounds_comment(pr)
|
|
102
119
|
return
|
|
103
120
|
end
|
|
104
121
|
|
|
105
|
-
display_message(
|
|
122
|
+
display_message(
|
|
123
|
+
"📝 Processing change request for PR ##{number} (#{pr[:title]})",
|
|
124
|
+
type: :info
|
|
125
|
+
)
|
|
106
126
|
|
|
107
127
|
# Fetch PR details
|
|
108
128
|
pr_data = @repository_client.fetch_pull_request(number)
|
|
@@ -112,29 +132,135 @@ module Aidp
|
|
|
112
132
|
authorized_comments = filter_authorized_comments(comments, pr_data)
|
|
113
133
|
|
|
114
134
|
if authorized_comments.empty?
|
|
115
|
-
display_message(
|
|
135
|
+
display_message(
|
|
136
|
+
"ℹ️ No authorized comments found for PR ##{number}. Skipping.",
|
|
137
|
+
type: :muted
|
|
138
|
+
)
|
|
116
139
|
return
|
|
117
140
|
end
|
|
118
141
|
|
|
119
|
-
#
|
|
120
|
-
# But bypass restriction for worktree-based workflows
|
|
142
|
+
# Fetch diff to check size with enhanced strategy
|
|
121
143
|
diff = @repository_client.fetch_pull_request_diff(number)
|
|
122
144
|
diff_size = diff.lines.count
|
|
123
145
|
|
|
124
|
-
#
|
|
125
|
-
|
|
146
|
+
# Enhanced diff size and worktree handling
|
|
147
|
+
large_pr = diff_size > @config[:max_diff_size]
|
|
148
|
+
|
|
149
|
+
if large_pr
|
|
150
|
+
# Comprehensive logging for large PR detection
|
|
151
|
+
Aidp.log_debug(
|
|
152
|
+
"change_request_processor", "Large PR detected",
|
|
153
|
+
pr_number: number,
|
|
154
|
+
diff_size: diff_size,
|
|
155
|
+
max_diff_size: @config[:max_diff_size],
|
|
156
|
+
large_pr_strategy: @config[:large_pr_strategy]
|
|
157
|
+
)
|
|
126
158
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
end
|
|
159
|
+
display_message(
|
|
160
|
+
"⚠️ Large PR detected - applying enhanced worktree handling strategy.",
|
|
161
|
+
type: :info
|
|
162
|
+
)
|
|
132
163
|
|
|
133
|
-
|
|
134
|
-
|
|
164
|
+
# Handle different strategies for large PRs
|
|
165
|
+
case @config[:large_pr_strategy]
|
|
166
|
+
when "skip"
|
|
167
|
+
Aidp.log_debug(
|
|
168
|
+
"change_request_processor", "Skipping large PR processing",
|
|
169
|
+
pr_number: number
|
|
170
|
+
)
|
|
171
|
+
post_diff_too_large_comment(pr_data, diff_size)
|
|
172
|
+
return
|
|
173
|
+
when "manual"
|
|
174
|
+
post_diff_too_large_comment(pr_data, diff_size)
|
|
175
|
+
display_message("❌ Change request processing failed: Large PR requires manual processing. See comment for details.", type: :error)
|
|
176
|
+
@state_store.record_change_request(pr_data[:number], {
|
|
177
|
+
status: "manual_processing_required",
|
|
178
|
+
timestamp: Time.now.utc.iso8601,
|
|
179
|
+
diff_size: diff_size,
|
|
180
|
+
max_diff_size: @config[:max_diff_size]
|
|
181
|
+
})
|
|
182
|
+
raise "Large PR requires manual processing. See comment for details."
|
|
183
|
+
when "create_worktree"
|
|
184
|
+
# Use our enhanced WorktreeBranchManager to handle PR worktrees
|
|
185
|
+
begin
|
|
186
|
+
# Get PR branch information
|
|
187
|
+
head_ref = @worktree_branch_manager.get_pr_branch(number)
|
|
188
|
+
|
|
189
|
+
# Check if a PR-specific worktree already exists
|
|
190
|
+
Aidp.log_debug(
|
|
191
|
+
"change_request_processor", "Checking for existing PR worktree",
|
|
192
|
+
pr_number: number,
|
|
193
|
+
head_branch: head_ref
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# Try to find existing worktree first
|
|
197
|
+
existing_worktree = @worktree_branch_manager.find_worktree(
|
|
198
|
+
branch: head_ref,
|
|
199
|
+
pr_number: number
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
# Create new worktree if none exists
|
|
203
|
+
if existing_worktree.nil?
|
|
204
|
+
Aidp.log_info(
|
|
205
|
+
"change_request_processor", "Creating PR-specific worktree",
|
|
206
|
+
pr_number: number,
|
|
207
|
+
head_branch: head_ref,
|
|
208
|
+
base_branch: pr_data[:base_ref],
|
|
209
|
+
strategy: "create_worktree"
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# Use find_or_create_pr_worktree for PR-specific handling
|
|
213
|
+
@worktree_branch_manager.find_or_create_pr_worktree(
|
|
214
|
+
pr_number: number,
|
|
215
|
+
head_branch: head_ref,
|
|
216
|
+
base_branch: pr_data[:base_ref]
|
|
217
|
+
)
|
|
218
|
+
else
|
|
219
|
+
Aidp.log_debug(
|
|
220
|
+
"change_request_processor", "Using existing PR worktree",
|
|
221
|
+
pr_number: number,
|
|
222
|
+
worktree_path: existing_worktree
|
|
223
|
+
)
|
|
224
|
+
end
|
|
225
|
+
rescue => e
|
|
226
|
+
Aidp.log_error(
|
|
227
|
+
"change_request_processor", "Large PR worktree handling failed",
|
|
228
|
+
pr_number: number,
|
|
229
|
+
error: e.message,
|
|
230
|
+
strategy: @config[:large_pr_strategy]
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
# Fallback error handling
|
|
234
|
+
post_diff_too_large_comment(pr_data, diff_size)
|
|
235
|
+
raise "Failed to handle large PR: #{e.message}"
|
|
236
|
+
end
|
|
237
|
+
else
|
|
238
|
+
# Default fallback
|
|
239
|
+
Aidp.log_warn(
|
|
240
|
+
"change_request_processor", "Unknown large_pr_strategy",
|
|
241
|
+
strategy: @config[:large_pr_strategy],
|
|
242
|
+
fallback: "skip"
|
|
243
|
+
)
|
|
244
|
+
post_diff_too_large_comment(pr_data, diff_size)
|
|
245
|
+
return
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Provide additional context via debug log
|
|
249
|
+
Aidp.log_info(
|
|
250
|
+
"change_request_processor", "Large PR worktree strategy applied",
|
|
251
|
+
pr_number: number,
|
|
252
|
+
diff_size: diff_size,
|
|
253
|
+
max_diff_size: @config[:max_diff_size],
|
|
254
|
+
strategy: @config[:large_pr_strategy]
|
|
255
|
+
)
|
|
256
|
+
end
|
|
135
257
|
|
|
136
258
|
# Analyze change requests
|
|
137
|
-
analysis_result = analyze_change_requests(
|
|
259
|
+
analysis_result = analyze_change_requests(
|
|
260
|
+
pr_data: pr_data,
|
|
261
|
+
comments: authorized_comments,
|
|
262
|
+
diff: diff
|
|
263
|
+
)
|
|
138
264
|
|
|
139
265
|
if analysis_result[:needs_clarification]
|
|
140
266
|
handle_clarification_needed(pr: pr_data, analysis: analysis_result)
|
|
@@ -144,8 +270,17 @@ module Aidp
|
|
|
144
270
|
handle_cannot_implement(pr: pr_data, analysis: analysis_result)
|
|
145
271
|
end
|
|
146
272
|
rescue => e
|
|
147
|
-
display_message(
|
|
148
|
-
|
|
273
|
+
display_message(
|
|
274
|
+
"❌ Change request processing failed: #{e.message}",
|
|
275
|
+
type: :error
|
|
276
|
+
)
|
|
277
|
+
Aidp.log_error(
|
|
278
|
+
"change_request_processor", "Change request failed",
|
|
279
|
+
pr: pr[:number],
|
|
280
|
+
error: e.message,
|
|
281
|
+
backtrace: e.backtrace&.first(10),
|
|
282
|
+
error_class: e.class.name
|
|
283
|
+
)
|
|
149
284
|
|
|
150
285
|
# Record failure state internally but DON'T post error to GitHub
|
|
151
286
|
# (per issue #280 - error messages should never appear on issues)
|
|
@@ -162,29 +297,15 @@ module Aidp
|
|
|
162
297
|
def filter_authorized_comments(comments, pr_data)
|
|
163
298
|
# If allowlist is empty (for private repos), consider PR author and all commenters
|
|
164
299
|
# For public repos, enforce allowlist
|
|
165
|
-
Aidp.log_debug("change_request_processor", "filtering_authorized_comments",
|
|
166
|
-
total_comments: comments.length,
|
|
167
|
-
allowlist_count: @author_allowlist.length,
|
|
168
|
-
is_private_repo: @author_allowlist.empty?)
|
|
169
|
-
|
|
170
300
|
if @author_allowlist.empty?
|
|
171
301
|
# Private repo: trust all comments from PR participants
|
|
172
|
-
Aidp.log_debug("change_request_processor", "private_repo_comments_allowed",
|
|
173
|
-
comments_allowed: comments.length)
|
|
174
302
|
comments
|
|
175
303
|
else
|
|
176
304
|
# Public repo: only allow comments from allowlisted users
|
|
177
|
-
|
|
305
|
+
comments.select do |comment|
|
|
178
306
|
author = comment[:author]
|
|
179
307
|
@author_allowlist.include?(author)
|
|
180
308
|
end
|
|
181
|
-
|
|
182
|
-
Aidp.log_debug("change_request_processor", "public_repo_comment_filtering",
|
|
183
|
-
total_comments: comments.length,
|
|
184
|
-
authorized_comments: authorized_comments.length,
|
|
185
|
-
allowed_authors: authorized_comments.map { |c| c[:author] })
|
|
186
|
-
|
|
187
|
-
authorized_comments
|
|
188
309
|
end
|
|
189
310
|
end
|
|
190
311
|
|
|
@@ -206,19 +327,41 @@ module Aidp
|
|
|
206
327
|
# Parse JSON response
|
|
207
328
|
parsed = JSON.parse(json_content)
|
|
208
329
|
|
|
209
|
-
|
|
330
|
+
# Additional structured analysis
|
|
331
|
+
result = {
|
|
210
332
|
can_implement: parsed["can_implement"],
|
|
211
333
|
needs_clarification: parsed["needs_clarification"],
|
|
212
334
|
clarifying_questions: parsed["clarifying_questions"] || [],
|
|
213
335
|
reason: parsed["reason"],
|
|
214
|
-
changes:
|
|
336
|
+
changes: []
|
|
215
337
|
}
|
|
338
|
+
|
|
339
|
+
# Enhanced change parsing
|
|
340
|
+
begin
|
|
341
|
+
result[:changes] = parse_ai_changes(
|
|
342
|
+
{changes: parsed["changes"]},
|
|
343
|
+
pr_data,
|
|
344
|
+
comments
|
|
345
|
+
)
|
|
346
|
+
rescue => e
|
|
347
|
+
Aidp.log_warn("change_request_processor", "Change parsing failed",
|
|
348
|
+
pr_number: pr_data[:number],
|
|
349
|
+
error: e.message)
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
Aidp.log_debug("change_request_processor", "Change request analysis result",
|
|
353
|
+
pr_number: pr_data[:number],
|
|
354
|
+
can_implement: result[:can_implement],
|
|
355
|
+
needs_clarification: result[:needs_clarification],
|
|
356
|
+
changes_count: result[:changes].length)
|
|
357
|
+
|
|
358
|
+
result
|
|
216
359
|
rescue JSON::ParserError => e
|
|
217
360
|
Aidp.log_error("change_request_processor", "Failed to parse AI response", error: e.message, content: content)
|
|
218
|
-
{can_implement: false, needs_clarification: false, reason: "Failed to parse AI analysis"}
|
|
361
|
+
{can_implement: false, needs_clarification: false, reason: "Failed to parse AI analysis", changes: []}
|
|
219
362
|
rescue => e
|
|
220
363
|
Aidp.log_error("change_request_processor", "AI analysis failed", error: e.message)
|
|
221
|
-
{can_implement: false, needs_clarification: false, reason: "AI analysis error: #{e.message}"}
|
|
364
|
+
{can_implement: false, needs_clarification: false, reason: "AI analysis error: #{e.message}", changes: []}
|
|
222
365
|
end
|
|
223
366
|
|
|
224
367
|
def change_request_system_prompt
|
|
@@ -347,194 +490,351 @@ module Aidp
|
|
|
347
490
|
end
|
|
348
491
|
|
|
349
492
|
def checkout_pr_branch(pr_data)
|
|
350
|
-
head_ref = pr_data[:head_ref]
|
|
351
493
|
pr_number = pr_data[:number]
|
|
494
|
+
base_branch = pr_data[:base_ref]
|
|
352
495
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
end
|
|
360
|
-
|
|
361
|
-
@project_dir = worktree_path
|
|
496
|
+
Aidp.log_debug(
|
|
497
|
+
"change_request_processor", "Starting PR branch checkout process",
|
|
498
|
+
pr_number: pr_number,
|
|
499
|
+
base_branch: base_branch,
|
|
500
|
+
project_dir: @project_dir
|
|
501
|
+
)
|
|
362
502
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
end
|
|
503
|
+
# Get PR branch information
|
|
504
|
+
head_ref = @worktree_branch_manager.get_pr_branch(pr_number)
|
|
366
505
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
506
|
+
begin
|
|
507
|
+
# Advanced worktree strategy with more detailed lookup
|
|
508
|
+
existing_worktree = @worktree_branch_manager.find_worktree(
|
|
509
|
+
branch: head_ref,
|
|
510
|
+
pr_number: pr_number
|
|
511
|
+
)
|
|
370
512
|
|
|
371
|
-
|
|
513
|
+
# Logging: Detailed worktree search strategy
|
|
514
|
+
log_worktree_strategy = {
|
|
515
|
+
pr_number: pr_number,
|
|
516
|
+
head_branch: head_ref,
|
|
517
|
+
existing_worktree: !!existing_worktree,
|
|
518
|
+
worktree_creation_strategy: @config.fetch(:worktree_strategy, "auto")
|
|
519
|
+
}
|
|
372
520
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
521
|
+
# Enhanced logging for worktree preparation
|
|
522
|
+
Aidp.log_info(
|
|
523
|
+
"change_request_processor", "Preparing PR worktree",
|
|
524
|
+
**log_worktree_strategy
|
|
525
|
+
)
|
|
378
526
|
|
|
379
|
-
|
|
380
|
-
|
|
527
|
+
# Worktree creation or reuse strategy
|
|
528
|
+
case @config.fetch(:worktree_strategy, "auto")
|
|
529
|
+
when "always_create"
|
|
530
|
+
# Force new worktree creation, useful for complex/large PRs
|
|
531
|
+
Aidp.log_info("change_request_processor", "Forcing new worktree creation", pr: pr_number)
|
|
532
|
+
existing_worktree = nil
|
|
533
|
+
when "reuse_only"
|
|
534
|
+
# Use only existing worktrees, error if not found
|
|
535
|
+
unless existing_worktree
|
|
536
|
+
raise "No existing worktree found for PR ##{pr_number}"
|
|
537
|
+
end
|
|
538
|
+
else # "auto" or default
|
|
539
|
+
# Existing default behavior: find or create
|
|
540
|
+
end
|
|
381
541
|
|
|
382
|
-
|
|
383
|
-
|
|
542
|
+
# Use the enhanced find_or_create_pr_worktree method for PR-specific worktree handling
|
|
543
|
+
worktree_path = @worktree_branch_manager.find_or_create_pr_worktree(
|
|
544
|
+
pr_number: pr_number,
|
|
545
|
+
head_branch: head_ref,
|
|
546
|
+
base_branch: base_branch
|
|
547
|
+
)
|
|
384
548
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
549
|
+
# Detailed logging of worktree path and strategy
|
|
550
|
+
Aidp.log_debug(
|
|
551
|
+
"change_request_processor", "PR worktree determined",
|
|
552
|
+
worktree_path: worktree_path,
|
|
553
|
+
creation_strategy: existing_worktree ? "reused" : "created",
|
|
554
|
+
pr_number: pr_number
|
|
555
|
+
)
|
|
388
556
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
557
|
+
display_message("🔄 Using PR-specific worktree for branch: #{head_ref}", type: :info)
|
|
558
|
+
|
|
559
|
+
# Update project directory to use the worktree
|
|
560
|
+
@project_dir = worktree_path
|
|
561
|
+
|
|
562
|
+
# Ensure the branch is up-to-date with remote
|
|
563
|
+
Dir.chdir(@project_dir) do
|
|
564
|
+
run_git(["fetch", "origin", base_branch], allow_failure: true)
|
|
565
|
+
run_git(["fetch", "origin", head_ref], allow_failure: true)
|
|
566
|
+
|
|
567
|
+
# Checkout branch with more detailed tracking
|
|
568
|
+
checkout_result = run_git(["checkout", head_ref])
|
|
569
|
+
|
|
570
|
+
# Pull with fast-forward preference for cleaner history
|
|
571
|
+
pull_result = run_git(
|
|
572
|
+
["pull", "--ff-only", "origin", head_ref],
|
|
573
|
+
allow_failure: true
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
# Enhanced branch state logging
|
|
577
|
+
Aidp.log_info(
|
|
578
|
+
"change_request_processor", "Branch synchronization complete",
|
|
579
|
+
pr_number: pr_number,
|
|
580
|
+
checkout_status: checkout_result.strip,
|
|
581
|
+
pull_status: pull_result.strip,
|
|
582
|
+
current_branch: run_git(["rev-parse", "--abbrev-ref", "HEAD"]).strip
|
|
583
|
+
)
|
|
584
|
+
end
|
|
392
585
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
586
|
+
# Additional validation and logging
|
|
587
|
+
worktree_details = {
|
|
588
|
+
path: worktree_path,
|
|
589
|
+
branch: head_ref,
|
|
590
|
+
base_branch: base_branch,
|
|
591
|
+
pr_number: pr_number,
|
|
592
|
+
timestamp: Time.now.utc.iso8601
|
|
593
|
+
}
|
|
396
594
|
|
|
397
|
-
|
|
398
|
-
|
|
595
|
+
Aidp.log_debug(
|
|
596
|
+
"change_request_processor", "Validated PR worktree",
|
|
597
|
+
**worktree_details
|
|
598
|
+
)
|
|
399
599
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
600
|
+
worktree_path
|
|
601
|
+
rescue => e
|
|
602
|
+
Aidp.log_error(
|
|
603
|
+
"change_request_processor", "Critical worktree preparation failure",
|
|
604
|
+
pr_number: pr_number,
|
|
605
|
+
base_branch: base_branch,
|
|
606
|
+
head_branch: head_ref,
|
|
607
|
+
error: e.message,
|
|
608
|
+
error_class: e.class.name,
|
|
609
|
+
backtrace: e.backtrace.first(10)
|
|
610
|
+
)
|
|
408
611
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
if existing && existing[:active]
|
|
412
|
-
Aidp.log_debug("change_request_processor", "issue_branch_worktree_reused", pr_number: pr_number, issue_number: issue_number, branch: branch, path: existing[:path])
|
|
413
|
-
display_message("🔄 Reusing branch worktree for issue ##{issue_number}: #{branch}", type: :info)
|
|
414
|
-
return existing[:path]
|
|
415
|
-
end
|
|
416
|
-
end
|
|
612
|
+
# Provide detailed error handling and recovery
|
|
613
|
+
handle_worktree_error(pr_data, e)
|
|
417
614
|
end
|
|
418
|
-
|
|
419
|
-
nil
|
|
420
615
|
end
|
|
421
616
|
|
|
422
|
-
def
|
|
423
|
-
|
|
424
|
-
|
|
617
|
+
def handle_worktree_error(pr_data, error)
|
|
618
|
+
# Log the detailed error
|
|
619
|
+
Aidp.log_error(
|
|
620
|
+
"change_request_processor", "Worktree preparation critical error",
|
|
621
|
+
pr_number: pr_data[:number],
|
|
622
|
+
error_message: error.message,
|
|
623
|
+
error_class: error.class.name
|
|
624
|
+
)
|
|
425
625
|
|
|
426
|
-
|
|
427
|
-
|
|
626
|
+
# Post a comment to GitHub about the failure
|
|
627
|
+
comment_body = <<~COMMENT
|
|
628
|
+
#{COMMENT_HEADER}
|
|
428
629
|
|
|
429
|
-
|
|
430
|
-
head_ref = pr_data[:head_ref]
|
|
431
|
-
pr_number = pr_data[:number]
|
|
630
|
+
❌ Automated worktree preparation failed for this pull request.
|
|
432
631
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
632
|
+
**Error Details:**
|
|
633
|
+
```
|
|
634
|
+
#{error.message}
|
|
635
|
+
```
|
|
436
636
|
|
|
437
|
-
|
|
637
|
+
**Possible Actions:**
|
|
638
|
+
1. Review the PR branch and its configuration
|
|
639
|
+
2. Check if the base repository is accessible
|
|
640
|
+
3. Try re-adding the `#{@change_request_label}` label
|
|
438
641
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
run_git(%w[fetch origin], allow_failure: true)
|
|
442
|
-
end
|
|
642
|
+
This may require manual intervention or administrative access.
|
|
643
|
+
COMMENT
|
|
443
644
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
case strategy
|
|
447
|
-
when "always_create"
|
|
448
|
-
create_fresh_worktree(pr_data, slug)
|
|
449
|
-
when "reuse_only"
|
|
450
|
-
find_existing_worktree(pr_data, slug)
|
|
451
|
-
else # 'auto' or default
|
|
452
|
-
find_existing_worktree(pr_data, slug) || create_fresh_worktree(pr_data, slug)
|
|
453
|
-
end
|
|
645
|
+
begin
|
|
646
|
+
@repository_client.post_comment(pr_data[:number], comment_body)
|
|
454
647
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
648
|
+
# Optionally remove or modify the label to indicate a problem
|
|
649
|
+
@repository_client.replace_labels(
|
|
650
|
+
pr_data[:number],
|
|
651
|
+
old_labels: [@change_request_label],
|
|
652
|
+
new_labels: ["aidp-worktree-error"]
|
|
653
|
+
)
|
|
654
|
+
rescue => comment_error
|
|
655
|
+
Aidp.log_warn(
|
|
656
|
+
"change_request_processor", "Failed to post error comment",
|
|
657
|
+
pr_number: pr_data[:number],
|
|
658
|
+
error: comment_error.message
|
|
659
|
+
)
|
|
660
|
+
end
|
|
463
661
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
rescue => e
|
|
467
|
-
Aidp.log_error(
|
|
468
|
-
"change_request_processor",
|
|
469
|
-
"worktree_creation_failed",
|
|
470
|
-
pr_number: pr_number,
|
|
471
|
-
error: e.message,
|
|
472
|
-
backtrace: e.backtrace&.first(5)
|
|
473
|
-
)
|
|
474
|
-
display_message("❌ Failed to create worktree: #{e.message}", type: :error)
|
|
475
|
-
raise
|
|
662
|
+
# Re-raise the original error to halt processing
|
|
663
|
+
raise error
|
|
476
664
|
end
|
|
477
665
|
|
|
478
666
|
private
|
|
479
667
|
|
|
480
|
-
def
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
668
|
+
def apply_changes(changes)
|
|
669
|
+
# Validate we're in a worktree for enhanced change handling
|
|
670
|
+
in_worktree = @project_dir.include?(".worktrees")
|
|
671
|
+
|
|
672
|
+
Aidp.log_debug("change_request_processor", "Starting change application",
|
|
673
|
+
total_changes: changes.length,
|
|
674
|
+
in_worktree: in_worktree,
|
|
675
|
+
working_dir: @project_dir)
|
|
676
|
+
|
|
677
|
+
# Track overall change application results
|
|
678
|
+
results = {
|
|
679
|
+
total_changes: changes.length,
|
|
680
|
+
successful_changes: 0,
|
|
681
|
+
failed_changes: 0,
|
|
682
|
+
skipped_changes: 0,
|
|
683
|
+
errors: [],
|
|
684
|
+
worktree_context: in_worktree
|
|
685
|
+
}
|
|
487
686
|
|
|
488
|
-
#
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
w[:slug]&.include?("pr-#{pr_number}")
|
|
492
|
-
end
|
|
687
|
+
# Enhanced change application with worktree context awareness
|
|
688
|
+
changes.each_with_index do |change, index|
|
|
689
|
+
file_path = change["file"].start_with?(@project_dir) ? change["file"] : File.join(@project_dir, change["file"])
|
|
493
690
|
|
|
494
|
-
|
|
495
|
-
|
|
691
|
+
Aidp.log_debug("change_request_processor", "Preparing to apply change",
|
|
692
|
+
index: index + 1,
|
|
693
|
+
total: changes.length,
|
|
694
|
+
action: change["action"],
|
|
695
|
+
file: change["file"],
|
|
696
|
+
content_length: change["content"]&.length || 0,
|
|
697
|
+
in_worktree: in_worktree)
|
|
496
698
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
699
|
+
begin
|
|
700
|
+
case change["action"]
|
|
701
|
+
when "create", "edit"
|
|
702
|
+
# Enhanced file change strategy with worktree awareness
|
|
703
|
+
unless change["content"]
|
|
704
|
+
results[:skipped_changes] += 1
|
|
705
|
+
Aidp.log_warn("change_request_processor", "Skipping change with empty content",
|
|
706
|
+
file: change["file"], action: change["action"], in_worktree: in_worktree)
|
|
707
|
+
next
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
# Enhanced directory creation with worktree validation
|
|
711
|
+
target_directory = File.dirname(file_path)
|
|
712
|
+
unless File.directory?(target_directory)
|
|
713
|
+
FileUtils.mkdir_p(target_directory)
|
|
714
|
+
Aidp.log_debug("change_request_processor", "Created directory structure",
|
|
715
|
+
directory: target_directory, in_worktree: in_worktree)
|
|
716
|
+
end
|
|
717
|
+
|
|
718
|
+
# Preserve file permissions if file already exists
|
|
719
|
+
old_permissions = File.exist?(file_path) ? File.stat(file_path).mode : 0o644
|
|
720
|
+
|
|
721
|
+
# Enhanced content writing with validation
|
|
722
|
+
File.write(file_path, change["content"])
|
|
723
|
+
File.chmod(old_permissions, file_path)
|
|
724
|
+
|
|
725
|
+
# Validate file was written correctly
|
|
726
|
+
unless File.exist?(file_path) && File.readable?(file_path)
|
|
727
|
+
raise "File creation validation failed"
|
|
728
|
+
end
|
|
729
|
+
|
|
730
|
+
display_message(" ✓ #{change["action"]} #{change["file"]}", type: :muted) if @verbose
|
|
731
|
+
Aidp.log_debug("change_request_processor", "Applied file change",
|
|
732
|
+
action: change["action"],
|
|
733
|
+
file: change["file"],
|
|
734
|
+
content_preview: change["content"]&.slice(0, 100),
|
|
735
|
+
file_size: File.size(file_path),
|
|
736
|
+
original_source_comments: change["source_comment_urls"],
|
|
737
|
+
in_worktree: in_worktree)
|
|
738
|
+
|
|
739
|
+
results[:successful_changes] += 1
|
|
740
|
+
|
|
741
|
+
when "delete"
|
|
742
|
+
# Enhanced delete strategy with worktree context
|
|
743
|
+
if File.exist?(file_path)
|
|
744
|
+
# Additional validation for worktree safety
|
|
745
|
+
if in_worktree
|
|
746
|
+
canonical_path = File.expand_path(file_path)
|
|
747
|
+
worktree_canonical = File.expand_path(@project_dir)
|
|
748
|
+
unless canonical_path.start_with?(worktree_canonical)
|
|
749
|
+
raise SecurityError, "Attempted to delete file outside worktree"
|
|
750
|
+
end
|
|
751
|
+
end
|
|
752
|
+
|
|
753
|
+
File.delete(file_path)
|
|
754
|
+
display_message(" ✓ Deleted #{change["file"]}", type: :muted) if @verbose
|
|
755
|
+
Aidp.log_debug("change_request_processor", "Deleted file",
|
|
756
|
+
file: change["file"],
|
|
757
|
+
original_source_comments: change["source_comment_urls"],
|
|
758
|
+
in_worktree: in_worktree)
|
|
759
|
+
results[:successful_changes] += 1
|
|
760
|
+
else
|
|
761
|
+
results[:skipped_changes] += 1
|
|
762
|
+
Aidp.log_warn("change_request_processor", "File to delete does not exist",
|
|
763
|
+
file: change["file"], in_worktree: in_worktree)
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
else
|
|
767
|
+
results[:skipped_changes] += 1
|
|
768
|
+
error_msg = "Unknown change action: #{change["action"]}"
|
|
769
|
+
display_message(" ⚠️ #{error_msg} for #{change["file"]}", type: :warn)
|
|
770
|
+
|
|
771
|
+
results[:errors] << {
|
|
772
|
+
file: change["file"],
|
|
773
|
+
action: change["action"],
|
|
774
|
+
error: error_msg,
|
|
775
|
+
worktree_context: in_worktree
|
|
776
|
+
}
|
|
500
777
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
778
|
+
Aidp.log_warn("change_request_processor", "Unhandled change action",
|
|
779
|
+
action: change["action"],
|
|
780
|
+
file: change["file"],
|
|
781
|
+
in_worktree: in_worktree)
|
|
782
|
+
end
|
|
783
|
+
rescue SecurityError => e
|
|
784
|
+
results[:failed_changes] += 1
|
|
785
|
+
error_details = {
|
|
786
|
+
file: change["file"],
|
|
787
|
+
action: change["action"],
|
|
788
|
+
error: e.message,
|
|
789
|
+
error_type: "security_violation",
|
|
790
|
+
worktree_context: in_worktree
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
results[:errors] << error_details
|
|
794
|
+
|
|
795
|
+
Aidp.log_error("change_request_processor", "Security violation during change application",
|
|
796
|
+
**error_details)
|
|
797
|
+
|
|
798
|
+
display_message(" 🔒 Security error applying change to #{change["file"]}: #{e.message}", type: :error)
|
|
799
|
+
rescue => e
|
|
800
|
+
results[:failed_changes] += 1
|
|
801
|
+
error_details = {
|
|
802
|
+
file: change["file"],
|
|
803
|
+
action: change["action"],
|
|
804
|
+
error: e.message,
|
|
805
|
+
backtrace: e.backtrace&.first(3),
|
|
806
|
+
worktree_context: in_worktree
|
|
807
|
+
}
|
|
508
808
|
|
|
509
|
-
|
|
510
|
-
slug: slug,
|
|
511
|
-
project_dir: @project_dir,
|
|
512
|
-
branch: head_ref,
|
|
513
|
-
base_branch: pr_data[:base_ref]
|
|
514
|
-
)
|
|
809
|
+
results[:errors] << error_details
|
|
515
810
|
|
|
516
|
-
|
|
517
|
-
|
|
811
|
+
Aidp.log_error("change_request_processor", "Change application failed",
|
|
812
|
+
**error_details)
|
|
518
813
|
|
|
519
|
-
|
|
520
|
-
changes.each do |change|
|
|
521
|
-
file_path = File.join(@project_dir, change["file"])
|
|
522
|
-
|
|
523
|
-
case change["action"]
|
|
524
|
-
when "create", "edit"
|
|
525
|
-
FileUtils.mkdir_p(File.dirname(file_path))
|
|
526
|
-
File.write(file_path, change["content"])
|
|
527
|
-
display_message(" ✓ #{change["action"]} #{change["file"]}", type: :muted) if @verbose
|
|
528
|
-
Aidp.log_debug("change_request_processor", "Applied change", action: change["action"], file: change["file"])
|
|
529
|
-
when "delete"
|
|
530
|
-
File.delete(file_path) if File.exist?(file_path)
|
|
531
|
-
display_message(" ✓ Deleted #{change["file"]}", type: :muted) if @verbose
|
|
532
|
-
Aidp.log_debug("change_request_processor", "Deleted file", file: change["file"])
|
|
533
|
-
else
|
|
534
|
-
display_message(" ⚠️ Unknown action: #{change["action"]} for #{change["file"]}", type: :warn)
|
|
535
|
-
Aidp.log_warn("change_request_processor", "Unknown change action", action: change["action"], file: change["file"])
|
|
814
|
+
display_message(" ❌ Failed to apply change to #{change["file"]}: #{e.message}", type: :error)
|
|
536
815
|
end
|
|
537
816
|
end
|
|
817
|
+
|
|
818
|
+
# Enhanced logging of overall change application results
|
|
819
|
+
Aidp.log_info("change_request_processor", "Change application summary",
|
|
820
|
+
total_changes: results[:total_changes],
|
|
821
|
+
successful_changes: results[:successful_changes],
|
|
822
|
+
skipped_changes: results[:skipped_changes],
|
|
823
|
+
failed_changes: results[:failed_changes],
|
|
824
|
+
errors_count: results[:errors].length,
|
|
825
|
+
success_rate: (results[:successful_changes].to_f / results[:total_changes] * 100).round(2),
|
|
826
|
+
in_worktree: in_worktree,
|
|
827
|
+
working_directory: @project_dir)
|
|
828
|
+
|
|
829
|
+
# Additional worktree-specific logging
|
|
830
|
+
if in_worktree && results[:successful_changes] > 0
|
|
831
|
+
Aidp.log_info("change_request_processor", "Worktree changes applied successfully",
|
|
832
|
+
worktree_path: @project_dir,
|
|
833
|
+
files_modified: results[:successful_changes])
|
|
834
|
+
end
|
|
835
|
+
|
|
836
|
+
# Return enhanced results for potential additional handling
|
|
837
|
+
results
|
|
538
838
|
end
|
|
539
839
|
|
|
540
840
|
def run_tests_and_linters
|
|
@@ -565,30 +865,96 @@ module Aidp
|
|
|
565
865
|
|
|
566
866
|
def commit_and_push(pr_data, analysis)
|
|
567
867
|
Dir.chdir(@project_dir) do
|
|
568
|
-
#
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
868
|
+
# Validate we're in a worktree
|
|
869
|
+
unless @project_dir.include?(".worktrees")
|
|
870
|
+
Aidp.log_error(
|
|
871
|
+
"change_request_processor", "Invalid project directory for commit_and_push",
|
|
872
|
+
pr_number: pr_data[:number],
|
|
873
|
+
project_dir: @project_dir
|
|
874
|
+
)
|
|
572
875
|
return false
|
|
573
876
|
end
|
|
574
877
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
878
|
+
begin
|
|
879
|
+
# Check for changes
|
|
880
|
+
status_result = run_git(%w[status --porcelain])
|
|
881
|
+
return false if status_result.strip.empty?
|
|
882
|
+
|
|
883
|
+
modified_files = status_result.split("\n").map { |l| l.strip.split(" ", 2).last }
|
|
884
|
+
|
|
885
|
+
# Stage all changes
|
|
886
|
+
run_git(%w[add -A])
|
|
887
|
+
Aidp.log_debug("change_request_processor", "Staged changes",
|
|
888
|
+
pr_number: pr_data[:number],
|
|
889
|
+
staged_files: modified_files)
|
|
890
|
+
|
|
891
|
+
# Create commit
|
|
892
|
+
commit_message = build_commit_message(pr_data, analysis)
|
|
893
|
+
commit_result = run_git(["commit", "-m", commit_message])
|
|
894
|
+
first_line = commit_message.lines.first.strip
|
|
895
|
+
Aidp.log_info(
|
|
896
|
+
"change_request_processor", "Created commit",
|
|
897
|
+
pr: pr_data[:number],
|
|
898
|
+
commit_message: first_line,
|
|
899
|
+
files_changed: modified_files,
|
|
900
|
+
commit_result: commit_result.strip
|
|
901
|
+
)
|
|
902
|
+
display_message("💾 Created commit: #{first_line}", type: :info)
|
|
903
|
+
|
|
904
|
+
# Push changes
|
|
905
|
+
head_ref = pr_data[:head_ref]
|
|
906
|
+
begin
|
|
907
|
+
push_result = run_git(["push", "origin", head_ref], allow_failure: false)
|
|
908
|
+
Aidp.log_info("change_request_processor", "Pushed changes",
|
|
909
|
+
pr: pr_data[:number],
|
|
910
|
+
branch: head_ref,
|
|
911
|
+
push_result: push_result.strip)
|
|
912
|
+
display_message("⬆️ Pushed changes to #{head_ref}", type: :success)
|
|
913
|
+
return true
|
|
914
|
+
rescue => push_error
|
|
915
|
+
# Push failed, but commit was successful
|
|
916
|
+
Aidp.log_warn("change_request_processor", "Push failed, but commit was successful",
|
|
917
|
+
pr_number: pr_data[:number],
|
|
918
|
+
error: push_error.message)
|
|
919
|
+
|
|
920
|
+
# Post a detailed comment about the push failure
|
|
921
|
+
comment_body = <<~COMMENT
|
|
922
|
+
#{COMMENT_HEADER}
|
|
923
|
+
|
|
924
|
+
⚠️ Automated changes were committed successfully, but pushing to the branch failed.
|
|
925
|
+
|
|
926
|
+
**Error Details:**
|
|
927
|
+
```
|
|
928
|
+
#{push_error.message}
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
**Suggested Actions:**
|
|
932
|
+
1. Check branch permissions
|
|
933
|
+
2. Verify remote repository configuration
|
|
934
|
+
3. Manually push the changes
|
|
935
|
+
4. Contact repository administrator
|
|
936
|
+
COMMENT
|
|
937
|
+
|
|
938
|
+
begin
|
|
939
|
+
@repository_client.post_comment(pr_data[:number], comment_body)
|
|
940
|
+
rescue => comment_error
|
|
941
|
+
Aidp.log_warn("change_request_processor", "Failed to post push error comment",
|
|
942
|
+
pr_number: pr_data[:number],
|
|
943
|
+
error: comment_error.message)
|
|
944
|
+
end
|
|
945
|
+
|
|
946
|
+
return false
|
|
947
|
+
end
|
|
948
|
+
rescue => error
|
|
949
|
+
# Catch any other unexpected errors during the entire process
|
|
950
|
+
Aidp.log_error(
|
|
951
|
+
"change_request_processor", "Unexpected error in commit_and_push",
|
|
952
|
+
pr_number: pr_data[:number],
|
|
953
|
+
error: error.message,
|
|
954
|
+
backtrace: error.backtrace.first(5)
|
|
955
|
+
)
|
|
956
|
+
return false
|
|
957
|
+
end
|
|
592
958
|
end
|
|
593
959
|
end
|
|
594
960
|
|
|
@@ -670,42 +1036,22 @@ module Aidp
|
|
|
670
1036
|
end
|
|
671
1037
|
|
|
672
1038
|
def handle_incomplete_implementation(pr:, analysis:, verification_result:)
|
|
673
|
-
Aidp.log_debug("change_request_processor", "start_incomplete_implementation_handling",
|
|
674
|
-
pr_number: pr[:number],
|
|
675
|
-
verification_result: {
|
|
676
|
-
missing_items_count: verification_result[:missing_items]&.length || 0,
|
|
677
|
-
additional_work_count: verification_result[:additional_work]&.length || 0
|
|
678
|
-
})
|
|
679
|
-
|
|
680
1039
|
display_message("⚠️ Implementation incomplete; creating follow-up tasks.", type: :warn)
|
|
681
1040
|
|
|
682
1041
|
# Create tasks for missing requirements
|
|
683
1042
|
if verification_result[:additional_work] && !verification_result[:additional_work].empty?
|
|
684
|
-
Aidp.log_debug("change_request_processor", "preparing_follow_up_tasks",
|
|
685
|
-
pr_number: pr[:number],
|
|
686
|
-
additional_work_tasks_count: verification_result[:additional_work].length)
|
|
687
1043
|
create_follow_up_tasks(@project_dir, verification_result[:additional_work])
|
|
688
1044
|
end
|
|
689
1045
|
|
|
690
1046
|
# Record state but do not post a separate comment
|
|
691
1047
|
# (verification details will be included in the next summary comment)
|
|
692
|
-
|
|
1048
|
+
@state_store.record_change_request(pr[:number], {
|
|
693
1049
|
status: "incomplete_implementation",
|
|
694
1050
|
timestamp: Time.now.utc.iso8601,
|
|
695
1051
|
verification_reasons: verification_result[:reasons],
|
|
696
1052
|
missing_items: verification_result[:missing_items],
|
|
697
1053
|
additional_work: verification_result[:additional_work]
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
# Log the details of the state record before storing
|
|
701
|
-
Aidp.log_debug("change_request_processor", "recording_incomplete_implementation_state",
|
|
702
|
-
pr_number: pr[:number],
|
|
703
|
-
status: state_record[:status],
|
|
704
|
-
verification_reasons_count: state_record[:verification_reasons]&.length || 0,
|
|
705
|
-
missing_items_count: state_record[:missing_items]&.length || 0,
|
|
706
|
-
additional_work_count: state_record[:additional_work]&.length || 0)
|
|
707
|
-
|
|
708
|
-
@state_store.record_change_request(pr[:number], state_record)
|
|
1054
|
+
})
|
|
709
1055
|
|
|
710
1056
|
display_message("📝 Recorded incomplete implementation status for PR ##{pr[:number]}", type: :info)
|
|
711
1057
|
|
|
@@ -722,55 +1068,36 @@ module Aidp
|
|
|
722
1068
|
def create_follow_up_tasks(working_dir, additional_work)
|
|
723
1069
|
return if additional_work.nil? || additional_work.empty?
|
|
724
1070
|
|
|
725
|
-
Aidp.log_debug("change_request_processor", "start_creating_follow_up_tasks",
|
|
726
|
-
working_dir: working_dir,
|
|
727
|
-
additional_work_tasks_count: additional_work.length)
|
|
728
|
-
|
|
729
1071
|
tasklist_file = File.join(working_dir, ".aidp", "tasklist.jsonl")
|
|
730
1072
|
FileUtils.mkdir_p(File.dirname(tasklist_file))
|
|
731
1073
|
|
|
732
1074
|
require_relative "../execute/persistent_tasklist"
|
|
733
1075
|
tasklist = Aidp::Execute::PersistentTasklist.new(working_dir)
|
|
734
1076
|
|
|
735
|
-
tasks_created = []
|
|
736
1077
|
additional_work.each do |task_description|
|
|
737
|
-
|
|
1078
|
+
tasklist.create(
|
|
738
1079
|
description: task_description,
|
|
739
1080
|
priority: :high,
|
|
740
1081
|
source: "verification"
|
|
741
1082
|
)
|
|
742
|
-
tasks_created << task
|
|
743
1083
|
end
|
|
744
1084
|
|
|
745
1085
|
display_message("📝 Created #{additional_work.length} follow-up task(s) for continued work", type: :info)
|
|
746
1086
|
|
|
747
|
-
Aidp.log_debug("change_request_processor", "follow_up_tasks_details",
|
|
748
|
-
task_count: tasks_created.length,
|
|
749
|
-
working_dir: working_dir,
|
|
750
|
-
task_descriptions: tasks_created.map(&:description))
|
|
751
|
-
|
|
752
1087
|
Aidp.log_info(
|
|
753
1088
|
"change_request_processor",
|
|
754
1089
|
"created_follow_up_tasks",
|
|
755
|
-
task_count:
|
|
1090
|
+
task_count: additional_work.length,
|
|
756
1091
|
working_dir: working_dir
|
|
757
1092
|
)
|
|
758
|
-
|
|
759
|
-
tasks_created
|
|
760
1093
|
rescue => e
|
|
1094
|
+
display_message("⚠️ Failed to create follow-up tasks: #{e.message}", type: :warn)
|
|
761
1095
|
Aidp.log_error(
|
|
762
1096
|
"change_request_processor",
|
|
763
1097
|
"failed_to_create_follow_up_tasks",
|
|
764
1098
|
error: e.message,
|
|
765
|
-
|
|
766
|
-
backtrace: e.backtrace&.first(5),
|
|
767
|
-
working_dir: working_dir
|
|
1099
|
+
backtrace: e.backtrace&.first(5)
|
|
768
1100
|
)
|
|
769
|
-
|
|
770
|
-
display_message("⚠️ Failed to create follow-up tasks: #{e.message}", type: :warn)
|
|
771
|
-
|
|
772
|
-
# Return an empty array to indicate failure
|
|
773
|
-
[]
|
|
774
1101
|
end
|
|
775
1102
|
|
|
776
1103
|
def handle_clarification_needed(pr:, analysis:)
|
|
@@ -912,36 +1239,56 @@ module Aidp
|
|
|
912
1239
|
end
|
|
913
1240
|
|
|
914
1241
|
def post_diff_too_large_comment(pr, diff_size)
|
|
1242
|
+
# Configure handling based on repository/project config
|
|
1243
|
+
handling_strategy = case @config[:large_pr_strategy]
|
|
1244
|
+
when "create_worktree"
|
|
1245
|
+
"Creating a dedicated git worktree"
|
|
1246
|
+
when "manual"
|
|
1247
|
+
"Requiring manual intervention"
|
|
1248
|
+
else
|
|
1249
|
+
"Skipping processing"
|
|
1250
|
+
end
|
|
1251
|
+
|
|
915
1252
|
comment = <<~COMMENT
|
|
916
1253
|
#{COMMENT_HEADER}
|
|
917
1254
|
|
|
918
|
-
⚠️ PR diff is too large for
|
|
1255
|
+
⚠️ PR diff is too large for standard automated change requests.
|
|
919
1256
|
|
|
920
1257
|
**Current size:** #{diff_size} lines
|
|
921
1258
|
**Maximum allowed:** #{@config[:max_diff_size]} lines
|
|
1259
|
+
**Handling strategy:** #{handling_strategy}
|
|
922
1260
|
|
|
923
|
-
|
|
924
|
-
1.
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
4. Increase `max_diff_size` in your configuration
|
|
929
|
-
|
|
930
|
-
The worktree bypass allows processing large PRs by working directly in the branch
|
|
931
|
-
instead of using diff-based changes.
|
|
1261
|
+
Options:
|
|
1262
|
+
1. Break the PR into smaller chunks
|
|
1263
|
+
2. Implement changes manually
|
|
1264
|
+
3. Increase `max_diff_size` in your `aidp.yml` configuration
|
|
1265
|
+
4. Configure `large_pr_strategy` to customize processing
|
|
932
1266
|
COMMENT
|
|
933
1267
|
|
|
1268
|
+
Aidp.log_debug(
|
|
1269
|
+
"change_request_processor", "Large PR detected",
|
|
1270
|
+
pr_number: pr[:number],
|
|
1271
|
+
diff_size: diff_size,
|
|
1272
|
+
max_diff_size: @config[:max_diff_size],
|
|
1273
|
+
strategy: handling_strategy
|
|
1274
|
+
)
|
|
1275
|
+
|
|
934
1276
|
begin
|
|
935
1277
|
@repository_client.post_comment(pr[:number], comment)
|
|
936
1278
|
@repository_client.remove_labels(pr[:number], @change_request_label)
|
|
937
|
-
rescue
|
|
938
|
-
|
|
1279
|
+
rescue => e
|
|
1280
|
+
Aidp.log_warn(
|
|
1281
|
+
"change_request_processor", "Failed to post large PR comment",
|
|
1282
|
+
pr_number: pr[:number],
|
|
1283
|
+
error: e.message
|
|
1284
|
+
)
|
|
939
1285
|
end
|
|
940
1286
|
end
|
|
941
1287
|
|
|
942
1288
|
def run_git(args, allow_failure: false)
|
|
943
|
-
|
|
944
|
-
|
|
1289
|
+
args_array = Array(args)
|
|
1290
|
+
stdout, stderr, status = Open3.capture3("git", *args_array)
|
|
1291
|
+
raise "git #{args_array.join(" ")} failed: #{stderr.strip}" unless status.success? || allow_failure
|
|
945
1292
|
stdout
|
|
946
1293
|
end
|
|
947
1294
|
|
|
@@ -977,6 +1324,53 @@ module Aidp
|
|
|
977
1324
|
end
|
|
978
1325
|
end
|
|
979
1326
|
|
|
1327
|
+
def parse_ai_changes(ai_response, pr_data, comments)
|
|
1328
|
+
changes = ai_response[:changes] || []
|
|
1329
|
+
|
|
1330
|
+
# Log raw changes for debugging
|
|
1331
|
+
Aidp.log_debug(
|
|
1332
|
+
"change_request_processor", "Extracted changes",
|
|
1333
|
+
pr_number: pr_data[:number],
|
|
1334
|
+
changes_count: changes.length
|
|
1335
|
+
)
|
|
1336
|
+
|
|
1337
|
+
# Validate and sanitize changes
|
|
1338
|
+
validated_changes = changes.map do |change|
|
|
1339
|
+
# Sanitize file path
|
|
1340
|
+
file_path = change["file"].to_s.gsub(%r{^/|\.\.}, "")
|
|
1341
|
+
file_path = File.join(@project_dir, file_path) unless file_path.start_with?(@project_dir)
|
|
1342
|
+
|
|
1343
|
+
# Validate change structure
|
|
1344
|
+
{
|
|
1345
|
+
"file" => file_path,
|
|
1346
|
+
"action" => %w[create edit delete].include?(change["action"]) ? change["action"] : "edit",
|
|
1347
|
+
"content" => change["content"].to_s,
|
|
1348
|
+
"description" => change["description"].to_s.slice(0, 500), # Limit description length
|
|
1349
|
+
"line_start" => change["line_start"]&.to_i,
|
|
1350
|
+
"line_end" => change["line_end"]&.to_i
|
|
1351
|
+
}
|
|
1352
|
+
end.select do |change|
|
|
1353
|
+
# Filter out invalid or empty changes
|
|
1354
|
+
change["file"].present? &&
|
|
1355
|
+
(change["action"] == "delete" || change["content"].present?)
|
|
1356
|
+
end
|
|
1357
|
+
|
|
1358
|
+
# Add source reference for traceability
|
|
1359
|
+
validated_changes.each do |change|
|
|
1360
|
+
change["source_comment_urls"] = comments
|
|
1361
|
+
.select { |c| c[:body].include?(change["description"]) }
|
|
1362
|
+
.map { |c| c[:url] }
|
|
1363
|
+
end
|
|
1364
|
+
|
|
1365
|
+
Aidp.log_debug(
|
|
1366
|
+
"change_request_processor", "Validated changes",
|
|
1367
|
+
pr_number: pr_data[:number],
|
|
1368
|
+
validated_changes_count: validated_changes.length
|
|
1369
|
+
)
|
|
1370
|
+
|
|
1371
|
+
validated_changes
|
|
1372
|
+
end
|
|
1373
|
+
|
|
980
1374
|
def symbolize_keys(hash)
|
|
981
1375
|
return {} unless hash
|
|
982
1376
|
|