aidp 0.32.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/feature_analyzer.rb +322 -320
- data/lib/aidp/analyze/tree_sitter_scan.rb +3 -0
- data/lib/aidp/auto_update/coordinator.rb +97 -7
- data/lib/aidp/auto_update.rb +0 -12
- data/lib/aidp/cli/devcontainer_commands.rb +0 -5
- 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 +172 -4
- data/lib/aidp/comment_consolidator.rb +78 -0
- data/lib/aidp/concurrency/exec.rb +3 -0
- data/lib/aidp/concurrency.rb +0 -3
- data/lib/aidp/config.rb +113 -1
- data/lib/aidp/config_paths.rb +91 -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 +719 -58
- 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/ai_filter_factory.rb +285 -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/config_schema.rb +97 -1
- data/lib/aidp/harness/config_validator.rb +1 -1
- data/lib/aidp/harness/configuration.rb +61 -5
- data/lib/aidp/harness/enhanced_runner.rb +14 -11
- data/lib/aidp/harness/error_handler.rb +3 -0
- data/lib/aidp/harness/filter_definition.rb +212 -0
- data/lib/aidp/harness/generated_filter_strategy.rb +197 -0
- data/lib/aidp/harness/output_filter.rb +50 -25
- data/lib/aidp/harness/output_filter_config.rb +129 -0
- data/lib/aidp/harness/provider_factory.rb +3 -0
- data/lib/aidp/harness/provider_manager.rb +96 -2
- data/lib/aidp/harness/runner.rb +5 -12
- 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/test_runner.rb +179 -41
- data/lib/aidp/harness/thinking_depth_manager.rb +44 -28
- 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 -2
- 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 +195 -0
- data/lib/aidp/logger.rb +3 -0
- data/lib/aidp/message_display.rb +31 -0
- data/lib/aidp/metadata/compiler.rb +29 -17
- data/lib/aidp/metadata/query.rb +1 -1
- data/lib/aidp/metadata/scanner.rb +8 -1
- data/lib/aidp/metadata/tool_metadata.rb +13 -13
- data/lib/aidp/metadata/validator.rb +10 -0
- data/lib/aidp/metadata.rb +16 -0
- data/lib/aidp/pr_worktree_manager.rb +20 -8
- data/lib/aidp/provider_manager.rb +4 -7
- 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 +283 -11
- data/lib/aidp/skills.rb +0 -5
- data/lib/aidp/storage/csv_storage.rb +3 -0
- data/lib/aidp/style_guide/selector.rb +360 -0
- data/lib/aidp/tooling_detector.rb +283 -16
- 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 +682 -150
- 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 +871 -22
- data/lib/aidp/watch/review_processor.rb +33 -6
- data/lib/aidp/watch/runner.rb +80 -29
- data/lib/aidp/watch/state_store.rb +233 -0
- data/lib/aidp/watch/sub_issue_creator.rb +221 -0
- data/lib/aidp/watch.rb +5 -7
- data/lib/aidp/workflows/guided_agent.rb +4 -0
- data/lib/aidp/workstream_cleanup.rb +0 -2
- data/lib/aidp/workstream_executor.rb +3 -4
- data/lib/aidp/worktree.rb +61 -12
- data/lib/aidp/worktree_branch_manager.rb +347 -101
- data/lib/aidp.rb +21 -106
- data/templates/implementation/iterative_implementation.md +46 -3
- metadata +91 -36
- data/lib/aidp/config/paths.rb +0 -131
|
@@ -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,6 +45,10 @@ module Aidp
|
|
|
40
45
|
@project_dir = project_dir
|
|
41
46
|
@verbose = verbose
|
|
42
47
|
|
|
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)
|
|
51
|
+
|
|
43
52
|
# Initialize verifier
|
|
44
53
|
@verifier = ImplementationVerifier.new(
|
|
45
54
|
repository_client: repository_client,
|
|
@@ -58,9 +67,26 @@ module Aidp
|
|
|
58
67
|
commit_message_prefix: "aidp: pr-change",
|
|
59
68
|
require_comment_reference: true,
|
|
60
69
|
max_diff_size: 2000,
|
|
61
|
-
|
|
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
|
|
62
75
|
}.merge(symbolize_keys(change_request_config))
|
|
63
76
|
|
|
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
|
+
)
|
|
89
|
+
|
|
64
90
|
# Load safety configuration
|
|
65
91
|
@safety_config = safety_config
|
|
66
92
|
@author_allowlist = Array(@safety_config[:author_allowlist] || @safety_config["author_allowlist"])
|
|
@@ -69,20 +95,34 @@ module Aidp
|
|
|
69
95
|
def process(pr)
|
|
70
96
|
number = pr[:number]
|
|
71
97
|
|
|
98
|
+
Aidp.log_debug(
|
|
99
|
+
"change_request_processor", "Starting change request processing",
|
|
100
|
+
pr_number: number, pr_title: pr[:title]
|
|
101
|
+
)
|
|
102
|
+
|
|
72
103
|
unless @config[:enabled]
|
|
73
|
-
display_message(
|
|
104
|
+
display_message(
|
|
105
|
+
"ℹ️ PR change requests are disabled in configuration. Skipping PR ##{number}.",
|
|
106
|
+
type: :muted
|
|
107
|
+
)
|
|
74
108
|
return
|
|
75
109
|
end
|
|
76
110
|
|
|
77
111
|
# Check clarification round limit
|
|
78
112
|
existing_data = @state_store.change_request_data(number)
|
|
79
113
|
if existing_data && existing_data["clarification_count"].to_i >= MAX_CLARIFICATION_ROUNDS
|
|
80
|
-
display_message(
|
|
114
|
+
display_message(
|
|
115
|
+
"⚠️ Max clarification rounds (#{MAX_CLARIFICATION_ROUNDS}) reached for PR ##{number}. Skipping.",
|
|
116
|
+
type: :warn
|
|
117
|
+
)
|
|
81
118
|
post_max_rounds_comment(pr)
|
|
82
119
|
return
|
|
83
120
|
end
|
|
84
121
|
|
|
85
|
-
display_message(
|
|
122
|
+
display_message(
|
|
123
|
+
"📝 Processing change request for PR ##{number} (#{pr[:title]})",
|
|
124
|
+
type: :info
|
|
125
|
+
)
|
|
86
126
|
|
|
87
127
|
# Fetch PR details
|
|
88
128
|
pr_data = @repository_client.fetch_pull_request(number)
|
|
@@ -92,29 +132,135 @@ module Aidp
|
|
|
92
132
|
authorized_comments = filter_authorized_comments(comments, pr_data)
|
|
93
133
|
|
|
94
134
|
if authorized_comments.empty?
|
|
95
|
-
display_message(
|
|
135
|
+
display_message(
|
|
136
|
+
"ℹ️ No authorized comments found for PR ##{number}. Skipping.",
|
|
137
|
+
type: :muted
|
|
138
|
+
)
|
|
96
139
|
return
|
|
97
140
|
end
|
|
98
141
|
|
|
99
|
-
#
|
|
100
|
-
# But bypass restriction for worktree-based workflows
|
|
142
|
+
# Fetch diff to check size with enhanced strategy
|
|
101
143
|
diff = @repository_client.fetch_pull_request_diff(number)
|
|
102
144
|
diff_size = diff.lines.count
|
|
103
145
|
|
|
104
|
-
#
|
|
105
|
-
|
|
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
|
+
)
|
|
106
158
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
end
|
|
159
|
+
display_message(
|
|
160
|
+
"⚠️ Large PR detected - applying enhanced worktree handling strategy.",
|
|
161
|
+
type: :info
|
|
162
|
+
)
|
|
112
163
|
|
|
113
|
-
|
|
114
|
-
|
|
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
|
|
115
257
|
|
|
116
258
|
# Analyze change requests
|
|
117
|
-
analysis_result = analyze_change_requests(
|
|
259
|
+
analysis_result = analyze_change_requests(
|
|
260
|
+
pr_data: pr_data,
|
|
261
|
+
comments: authorized_comments,
|
|
262
|
+
diff: diff
|
|
263
|
+
)
|
|
118
264
|
|
|
119
265
|
if analysis_result[:needs_clarification]
|
|
120
266
|
handle_clarification_needed(pr: pr_data, analysis: analysis_result)
|
|
@@ -124,8 +270,17 @@ module Aidp
|
|
|
124
270
|
handle_cannot_implement(pr: pr_data, analysis: analysis_result)
|
|
125
271
|
end
|
|
126
272
|
rescue => e
|
|
127
|
-
display_message(
|
|
128
|
-
|
|
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
|
+
)
|
|
129
284
|
|
|
130
285
|
# Record failure state internally but DON'T post error to GitHub
|
|
131
286
|
# (per issue #280 - error messages should never appear on issues)
|
|
@@ -172,19 +327,41 @@ module Aidp
|
|
|
172
327
|
# Parse JSON response
|
|
173
328
|
parsed = JSON.parse(json_content)
|
|
174
329
|
|
|
175
|
-
|
|
330
|
+
# Additional structured analysis
|
|
331
|
+
result = {
|
|
176
332
|
can_implement: parsed["can_implement"],
|
|
177
333
|
needs_clarification: parsed["needs_clarification"],
|
|
178
334
|
clarifying_questions: parsed["clarifying_questions"] || [],
|
|
179
335
|
reason: parsed["reason"],
|
|
180
|
-
changes:
|
|
336
|
+
changes: []
|
|
181
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
|
|
182
359
|
rescue JSON::ParserError => e
|
|
183
360
|
Aidp.log_error("change_request_processor", "Failed to parse AI response", error: e.message, content: content)
|
|
184
|
-
{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: []}
|
|
185
362
|
rescue => e
|
|
186
363
|
Aidp.log_error("change_request_processor", "AI analysis failed", error: e.message)
|
|
187
|
-
{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: []}
|
|
188
365
|
end
|
|
189
366
|
|
|
190
367
|
def change_request_system_prompt
|
|
@@ -313,129 +490,351 @@ module Aidp
|
|
|
313
490
|
end
|
|
314
491
|
|
|
315
492
|
def checkout_pr_branch(pr_data)
|
|
316
|
-
head_ref = pr_data[:head_ref]
|
|
317
493
|
pr_number = pr_data[:number]
|
|
494
|
+
base_branch = pr_data[:base_ref]
|
|
318
495
|
|
|
319
|
-
|
|
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
|
+
)
|
|
320
502
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
run_git(["checkout", head_ref])
|
|
324
|
-
run_git(%w[pull --ff-only], allow_failure: true)
|
|
325
|
-
end
|
|
503
|
+
# Get PR branch information
|
|
504
|
+
head_ref = @worktree_branch_manager.get_pr_branch(pr_number)
|
|
326
505
|
|
|
327
|
-
|
|
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
|
+
)
|
|
328
512
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
+
}
|
|
332
520
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
521
|
+
# Enhanced logging for worktree preparation
|
|
522
|
+
Aidp.log_info(
|
|
523
|
+
"change_request_processor", "Preparing PR worktree",
|
|
524
|
+
**log_worktree_strategy
|
|
525
|
+
)
|
|
336
526
|
|
|
337
|
-
|
|
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
|
|
338
541
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
+
)
|
|
344
548
|
|
|
345
|
-
|
|
346
|
-
|
|
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
|
+
)
|
|
556
|
+
|
|
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
|
|
585
|
+
|
|
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
|
+
}
|
|
594
|
+
|
|
595
|
+
Aidp.log_debug(
|
|
596
|
+
"change_request_processor", "Validated PR worktree",
|
|
597
|
+
**worktree_details
|
|
598
|
+
)
|
|
347
599
|
|
|
348
|
-
|
|
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
|
+
)
|
|
611
|
+
|
|
612
|
+
# Provide detailed error handling and recovery
|
|
613
|
+
handle_worktree_error(pr_data, e)
|
|
614
|
+
end
|
|
349
615
|
end
|
|
350
616
|
|
|
351
|
-
def
|
|
352
|
-
|
|
353
|
-
|
|
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
|
+
)
|
|
625
|
+
|
|
626
|
+
# Post a comment to GitHub about the failure
|
|
627
|
+
comment_body = <<~COMMENT
|
|
628
|
+
#{COMMENT_HEADER}
|
|
354
629
|
|
|
355
|
-
|
|
356
|
-
linked_issue_numbers << build_match[:issue_number] if build_match
|
|
357
|
-
linked_issue_numbers = linked_issue_numbers.compact.uniq
|
|
630
|
+
❌ Automated worktree preparation failed for this pull request.
|
|
358
631
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
632
|
+
**Error Details:**
|
|
633
|
+
```
|
|
634
|
+
#{error.message}
|
|
635
|
+
```
|
|
362
636
|
|
|
363
|
-
|
|
364
|
-
branch
|
|
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
|
|
365
641
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
if info && info[:active]
|
|
369
|
-
Aidp.log_debug("change_request_processor", "issue_worktree_reused", pr_number: pr_number, issue_number: issue_number, branch: branch, path: info[:path])
|
|
370
|
-
display_message("🔄 Reusing worktree #{slug} for issue ##{issue_number} (PR ##{pr_number})", type: :info)
|
|
371
|
-
return info[:path]
|
|
372
|
-
end
|
|
373
|
-
end
|
|
642
|
+
This may require manual intervention or administrative access.
|
|
643
|
+
COMMENT
|
|
374
644
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
645
|
+
begin
|
|
646
|
+
@repository_client.post_comment(pr_data[:number], comment_body)
|
|
647
|
+
|
|
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
|
+
)
|
|
383
660
|
end
|
|
384
661
|
|
|
385
|
-
|
|
662
|
+
# Re-raise the original error to halt processing
|
|
663
|
+
raise error
|
|
386
664
|
end
|
|
387
665
|
|
|
388
|
-
|
|
389
|
-
body = pr_data[:body].to_s
|
|
390
|
-
issue_matches = body.scan(/(?:Fixes|Resolves|Closes)\s+#(\d+)/i).flatten
|
|
666
|
+
private
|
|
391
667
|
|
|
392
|
-
|
|
393
|
-
|
|
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
|
+
}
|
|
394
686
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
slug = "pr-#{pr_number}-change-requests"
|
|
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"])
|
|
399
690
|
|
|
400
|
-
|
|
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)
|
|
401
698
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
+
}
|
|
405
777
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
+
}
|
|
412
808
|
|
|
413
|
-
|
|
414
|
-
Aidp.log_debug("change_request_processor", "worktree_created", pr_number: pr_number, branch: head_ref, path: worktree_path)
|
|
415
|
-
display_message("✅ Worktree created at #{worktree_path}", type: :success)
|
|
809
|
+
results[:errors] << error_details
|
|
416
810
|
|
|
417
|
-
|
|
418
|
-
|
|
811
|
+
Aidp.log_error("change_request_processor", "Change application failed",
|
|
812
|
+
**error_details)
|
|
419
813
|
|
|
420
|
-
|
|
421
|
-
changes.each do |change|
|
|
422
|
-
file_path = File.join(@project_dir, change["file"])
|
|
423
|
-
|
|
424
|
-
case change["action"]
|
|
425
|
-
when "create", "edit"
|
|
426
|
-
FileUtils.mkdir_p(File.dirname(file_path))
|
|
427
|
-
File.write(file_path, change["content"])
|
|
428
|
-
display_message(" ✓ #{change["action"]} #{change["file"]}", type: :muted) if @verbose
|
|
429
|
-
Aidp.log_debug("change_request_processor", "Applied change", action: change["action"], file: change["file"])
|
|
430
|
-
when "delete"
|
|
431
|
-
File.delete(file_path) if File.exist?(file_path)
|
|
432
|
-
display_message(" ✓ Deleted #{change["file"]}", type: :muted) if @verbose
|
|
433
|
-
Aidp.log_debug("change_request_processor", "Deleted file", file: change["file"])
|
|
434
|
-
else
|
|
435
|
-
display_message(" ⚠️ Unknown action: #{change["action"]} for #{change["file"]}", type: :warn)
|
|
436
|
-
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)
|
|
437
815
|
end
|
|
438
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
|
|
439
838
|
end
|
|
440
839
|
|
|
441
840
|
def run_tests_and_linters
|
|
@@ -466,30 +865,96 @@ module Aidp
|
|
|
466
865
|
|
|
467
866
|
def commit_and_push(pr_data, analysis)
|
|
468
867
|
Dir.chdir(@project_dir) do
|
|
469
|
-
#
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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
|
+
)
|
|
473
875
|
return false
|
|
474
876
|
end
|
|
475
877
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
|
493
958
|
end
|
|
494
959
|
end
|
|
495
960
|
|
|
@@ -774,36 +1239,56 @@ module Aidp
|
|
|
774
1239
|
end
|
|
775
1240
|
|
|
776
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
|
+
|
|
777
1252
|
comment = <<~COMMENT
|
|
778
1253
|
#{COMMENT_HEADER}
|
|
779
1254
|
|
|
780
|
-
⚠️ PR diff is too large for
|
|
1255
|
+
⚠️ PR diff is too large for standard automated change requests.
|
|
781
1256
|
|
|
782
1257
|
**Current size:** #{diff_size} lines
|
|
783
1258
|
**Maximum allowed:** #{@config[:max_diff_size]} lines
|
|
1259
|
+
**Handling strategy:** #{handling_strategy}
|
|
784
1260
|
|
|
785
|
-
|
|
786
|
-
1.
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
4. Increase `max_diff_size` in your configuration
|
|
791
|
-
|
|
792
|
-
The worktree bypass allows processing large PRs by working directly in the branch
|
|
793
|
-
instead of using diff-based changes.
|
|
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
|
|
794
1266
|
COMMENT
|
|
795
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
|
+
|
|
796
1276
|
begin
|
|
797
1277
|
@repository_client.post_comment(pr[:number], comment)
|
|
798
1278
|
@repository_client.remove_labels(pr[:number], @change_request_label)
|
|
799
|
-
rescue
|
|
800
|
-
|
|
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
|
+
)
|
|
801
1285
|
end
|
|
802
1286
|
end
|
|
803
1287
|
|
|
804
1288
|
def run_git(args, allow_failure: false)
|
|
805
|
-
|
|
806
|
-
|
|
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
|
|
807
1292
|
stdout
|
|
808
1293
|
end
|
|
809
1294
|
|
|
@@ -839,6 +1324,53 @@ module Aidp
|
|
|
839
1324
|
end
|
|
840
1325
|
end
|
|
841
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
|
+
|
|
842
1374
|
def symbolize_keys(hash)
|
|
843
1375
|
return {} unless hash
|
|
844
1376
|
|