aidp 0.23.0 → 0.25.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 +27 -1
- data/lib/aidp/auto_update/bundler_adapter.rb +66 -0
- data/lib/aidp/auto_update/checkpoint.rb +178 -0
- data/lib/aidp/auto_update/checkpoint_store.rb +182 -0
- data/lib/aidp/auto_update/coordinator.rb +204 -0
- data/lib/aidp/auto_update/errors.rb +17 -0
- data/lib/aidp/auto_update/failure_tracker.rb +162 -0
- data/lib/aidp/auto_update/rubygems_api_adapter.rb +95 -0
- data/lib/aidp/auto_update/update_check.rb +106 -0
- data/lib/aidp/auto_update/update_logger.rb +143 -0
- data/lib/aidp/auto_update/update_policy.rb +109 -0
- data/lib/aidp/auto_update/version_detector.rb +144 -0
- data/lib/aidp/auto_update.rb +52 -0
- data/lib/aidp/cli.rb +168 -1
- data/lib/aidp/execute/work_loop_runner.rb +252 -45
- data/lib/aidp/execute/work_loop_unit_scheduler.rb +27 -2
- data/lib/aidp/harness/condition_detector.rb +42 -8
- data/lib/aidp/harness/config_manager.rb +7 -0
- data/lib/aidp/harness/config_schema.rb +75 -0
- data/lib/aidp/harness/configuration.rb +69 -6
- data/lib/aidp/harness/error_handler.rb +117 -44
- data/lib/aidp/harness/provider_factory.rb +2 -0
- data/lib/aidp/harness/provider_manager.rb +64 -0
- data/lib/aidp/harness/provider_metrics.rb +138 -0
- data/lib/aidp/harness/runner.rb +90 -29
- data/lib/aidp/harness/simple_user_interface.rb +4 -0
- data/lib/aidp/harness/state/ui_state.rb +0 -10
- data/lib/aidp/harness/state_manager.rb +1 -15
- data/lib/aidp/harness/test_runner.rb +39 -2
- data/lib/aidp/logger.rb +34 -4
- data/lib/aidp/message_display.rb +10 -2
- data/lib/aidp/prompt_optimization/style_guide_indexer.rb +3 -1
- data/lib/aidp/provider_manager.rb +2 -0
- data/lib/aidp/providers/adapter.rb +241 -0
- data/lib/aidp/providers/anthropic.rb +75 -7
- data/lib/aidp/providers/base.rb +29 -1
- data/lib/aidp/providers/capability_registry.rb +205 -0
- data/lib/aidp/providers/codex.rb +14 -0
- data/lib/aidp/providers/error_taxonomy.rb +195 -0
- data/lib/aidp/providers/gemini.rb +3 -2
- data/lib/aidp/providers/kilocode.rb +202 -0
- data/lib/aidp/setup/provider_registry.rb +122 -0
- data/lib/aidp/setup/wizard.rb +125 -33
- data/lib/aidp/skills/composer.rb +4 -0
- data/lib/aidp/skills/loader.rb +3 -1
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/build_processor.rb +323 -33
- data/lib/aidp/watch/ci_fix_processor.rb +448 -0
- data/lib/aidp/watch/plan_processor.rb +12 -2
- data/lib/aidp/watch/repository_client.rb +384 -4
- data/lib/aidp/watch/review_processor.rb +266 -0
- data/lib/aidp/watch/reviewers/base_reviewer.rb +164 -0
- data/lib/aidp/watch/reviewers/performance_reviewer.rb +65 -0
- data/lib/aidp/watch/reviewers/security_reviewer.rb +65 -0
- data/lib/aidp/watch/reviewers/senior_dev_reviewer.rb +33 -0
- data/lib/aidp/watch/runner.rb +222 -5
- data/lib/aidp/watch/state_store.rb +53 -0
- data/lib/aidp/workflows/guided_agent.rb +53 -0
- data/lib/aidp/worktree.rb +67 -10
- data/lib/aidp.rb +1 -0
- data/templates/work_loop/decide_whats_next.md +21 -0
- data/templates/work_loop/diagnose_failures.md +21 -0
- metadata +29 -3
- /data/{bin → exe}/aidp +0 -0
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
require "open3"
|
|
4
4
|
require "time"
|
|
5
|
+
require "fileutils"
|
|
5
6
|
|
|
6
7
|
require_relative "../message_display"
|
|
7
8
|
require_relative "../execute/prompt_manager"
|
|
8
9
|
require_relative "../harness/runner"
|
|
10
|
+
require_relative "../harness/state_manager"
|
|
9
11
|
require_relative "../worktree"
|
|
12
|
+
require_relative "../execute/progress"
|
|
10
13
|
|
|
11
14
|
module Aidp
|
|
12
15
|
module Watch
|
|
@@ -55,6 +58,8 @@ module Aidp
|
|
|
55
58
|
working_dir = @project_dir
|
|
56
59
|
end
|
|
57
60
|
|
|
61
|
+
sync_local_aidp_config(working_dir)
|
|
62
|
+
|
|
58
63
|
prompt_content = build_prompt(issue: issue, plan_data: plan_data)
|
|
59
64
|
write_prompt(prompt_content, working_dir: working_dir)
|
|
60
65
|
|
|
@@ -65,14 +70,36 @@ module Aidp
|
|
|
65
70
|
handle_success(issue: issue, slug: slug, branch_name: branch_name, base_branch: base_branch, plan_data: plan_data, working_dir: working_dir)
|
|
66
71
|
elsif result[:status] == "needs_clarification"
|
|
67
72
|
handle_clarification_request(issue: issue, slug: slug, result: result)
|
|
73
|
+
elsif result[:reason] == :completion_criteria
|
|
74
|
+
handle_incomplete_criteria(issue: issue, slug: slug, branch_name: branch_name, working_dir: working_dir, metadata: result[:failure_metadata])
|
|
68
75
|
else
|
|
69
76
|
handle_failure(issue: issue, slug: slug, result: result)
|
|
70
77
|
end
|
|
71
78
|
rescue => e
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
79
|
+
# Don't re-raise - handle gracefully for fix-forward pattern
|
|
80
|
+
display_message("❌ Implementation failed with exception: #{e.message}", type: :error)
|
|
81
|
+
Aidp.log_error(
|
|
82
|
+
"build_processor",
|
|
83
|
+
"Implementation failed with exception",
|
|
84
|
+
issue: issue[:number],
|
|
85
|
+
error: e.message,
|
|
86
|
+
error_class: e.class.name,
|
|
87
|
+
backtrace: e.backtrace&.first(10)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Create error result to pass to handle_failure
|
|
91
|
+
error_result = {
|
|
92
|
+
status: "error",
|
|
93
|
+
error: e.message,
|
|
94
|
+
error_class: e.class.name,
|
|
95
|
+
message: "Exception during harness execution: #{e.message}"
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# Handle as failure (posts comment, updates state) but DON'T re-raise
|
|
99
|
+
handle_failure(issue: issue, slug: slug, result: error_result)
|
|
100
|
+
|
|
101
|
+
# Note: We intentionally DON'T re-raise here to allow watch mode to continue
|
|
102
|
+
# The error has been logged, recorded, and reported to GitHub
|
|
76
103
|
end
|
|
77
104
|
|
|
78
105
|
private
|
|
@@ -147,20 +174,18 @@ module Aidp
|
|
|
147
174
|
base_branch: base_branch
|
|
148
175
|
)
|
|
149
176
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
else
|
|
154
|
-
raise "Failed to create workstream: #{result[:message]}"
|
|
155
|
-
end
|
|
177
|
+
worktree_path = worktree_path_from_result(result)
|
|
178
|
+
display_message("✅ Workstream created at #{worktree_path}", type: :success)
|
|
179
|
+
worktree_path
|
|
156
180
|
end
|
|
157
181
|
|
|
158
182
|
def cleanup_workstream(slug)
|
|
159
183
|
return unless slug
|
|
160
184
|
|
|
161
185
|
display_message("🧹 Cleaning up workstream: #{slug}", type: :info)
|
|
162
|
-
result = Aidp::Worktree.remove(slug: slug, project_dir: @project_dir,
|
|
163
|
-
|
|
186
|
+
result = Aidp::Worktree.remove(slug: slug, project_dir: @project_dir, delete_branch: true)
|
|
187
|
+
removed = (result == true) || (result.respond_to?(:[]) && result[:success])
|
|
188
|
+
if removed
|
|
164
189
|
display_message("✅ Workstream removed", type: :success)
|
|
165
190
|
else
|
|
166
191
|
display_message("⚠️ Failed to remove workstream: #{result[:message]}", type: :warn)
|
|
@@ -211,7 +236,7 @@ module Aidp
|
|
|
211
236
|
|
|
212
237
|
def write_prompt(content, working_dir: @project_dir)
|
|
213
238
|
prompt_manager = Aidp::Execute::PromptManager.new(working_dir)
|
|
214
|
-
prompt_manager.write(content)
|
|
239
|
+
prompt_manager.write(content, step_name: IMPLEMENTATION_STEP)
|
|
215
240
|
display_message("📝 Wrote PROMPT.md with implementation contract", type: :info)
|
|
216
241
|
|
|
217
242
|
if @verbose
|
|
@@ -243,10 +268,21 @@ module Aidp
|
|
|
243
268
|
end
|
|
244
269
|
|
|
245
270
|
def run_harness(user_input:, working_dir: @project_dir)
|
|
271
|
+
reset_work_loop_state(working_dir)
|
|
272
|
+
|
|
273
|
+
Aidp.log_info(
|
|
274
|
+
"build_processor",
|
|
275
|
+
"starting_harness",
|
|
276
|
+
issue_dir: working_dir,
|
|
277
|
+
workflow_type: :watch_mode,
|
|
278
|
+
selected_steps: [IMPLEMENTATION_STEP]
|
|
279
|
+
)
|
|
280
|
+
|
|
246
281
|
options = {
|
|
247
282
|
selected_steps: [IMPLEMENTATION_STEP],
|
|
248
283
|
workflow_type: :watch_mode,
|
|
249
|
-
user_input: user_input
|
|
284
|
+
user_input: user_input,
|
|
285
|
+
non_interactive: true
|
|
250
286
|
}
|
|
251
287
|
|
|
252
288
|
display_message("🚀 Running harness in execute mode...", type: :info) if @verbose
|
|
@@ -258,32 +294,133 @@ module Aidp
|
|
|
258
294
|
display_message("\n--- Harness Result ---", type: :muted)
|
|
259
295
|
display_message("Status: #{result[:status]}", type: :muted)
|
|
260
296
|
display_message("Message: #{result[:message]}", type: :muted) if result[:message]
|
|
297
|
+
if result[:error]
|
|
298
|
+
display_message("Error: #{result[:error]}", type: :muted)
|
|
299
|
+
display_message("Error Details: #{result[:error_details]}", type: :muted) if result[:error_details]
|
|
300
|
+
end
|
|
261
301
|
display_message("--- End Result ---\n", type: :muted)
|
|
262
302
|
end
|
|
263
303
|
|
|
304
|
+
Aidp.log_info(
|
|
305
|
+
"build_processor",
|
|
306
|
+
"harness_result",
|
|
307
|
+
status: result[:status],
|
|
308
|
+
message: result[:message],
|
|
309
|
+
error: result[:error],
|
|
310
|
+
error_class: result[:error_class]
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
# Log errors to aidp.log
|
|
314
|
+
if result[:status] == "error"
|
|
315
|
+
error_msg = result[:message] || "Unknown error"
|
|
316
|
+
error_details = {
|
|
317
|
+
status: result[:status],
|
|
318
|
+
message: error_msg,
|
|
319
|
+
error: result[:error]&.to_s,
|
|
320
|
+
error_class: result[:error]&.class&.name,
|
|
321
|
+
backtrace: result[:backtrace]&.first(5)
|
|
322
|
+
}.compact
|
|
323
|
+
Aidp.log_error("build_processor", "Harness execution failed", **error_details)
|
|
324
|
+
end
|
|
325
|
+
|
|
264
326
|
result
|
|
265
327
|
end
|
|
266
328
|
|
|
329
|
+
def reset_work_loop_state(working_dir)
|
|
330
|
+
state_manager = Aidp::Harness::StateManager.new(working_dir, :execute)
|
|
331
|
+
state_manager.clear_state
|
|
332
|
+
Aidp::Execute::Progress.new(working_dir).reset
|
|
333
|
+
rescue => e
|
|
334
|
+
display_message("⚠️ Failed to reset work loop state before execution: #{e.message}", type: :warn)
|
|
335
|
+
Aidp.log_warn("build_processor", "failed_to_reset_work_loop_state", error: e.message, working_dir: working_dir)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def enqueue_decider_followup(target_dir)
|
|
339
|
+
work_loop_dir = File.join(target_dir, ".aidp", "work_loop")
|
|
340
|
+
FileUtils.mkdir_p(work_loop_dir)
|
|
341
|
+
request_path = File.join(work_loop_dir, "initial_units.txt")
|
|
342
|
+
File.open(request_path, "a") { |file| file.puts("decide_whats_next") }
|
|
343
|
+
Aidp.log_info("build_processor", "scheduled_decider_followup", request_path: request_path)
|
|
344
|
+
rescue => e
|
|
345
|
+
Aidp.log_warn("build_processor", "failed_to_schedule_decider", error: e.message)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def sync_local_aidp_config(target_dir)
|
|
349
|
+
return if target_dir.nil? || target_dir == @project_dir
|
|
350
|
+
|
|
351
|
+
source_config = File.join(@project_dir, ".aidp", "aidp.yml")
|
|
352
|
+
return unless File.exist?(source_config)
|
|
353
|
+
|
|
354
|
+
target_config = File.join(target_dir, ".aidp", "aidp.yml")
|
|
355
|
+
FileUtils.mkdir_p(File.dirname(target_config))
|
|
356
|
+
|
|
357
|
+
# Only copy when target missing or differs
|
|
358
|
+
if !File.exist?(target_config) || File.read(source_config, encoding: "UTF-8") != File.read(target_config, encoding: "UTF-8")
|
|
359
|
+
FileUtils.cp(source_config, target_config)
|
|
360
|
+
end
|
|
361
|
+
rescue => e
|
|
362
|
+
display_message("⚠️ Failed to sync AIDP config to workstream: #{e.message}", type: :warn)
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def worktree_path_from_result(result)
|
|
366
|
+
return result if result.is_a?(String)
|
|
367
|
+
|
|
368
|
+
path = result[:path] || result["path"]
|
|
369
|
+
return path if path
|
|
370
|
+
|
|
371
|
+
message = result[:message] || "unknown error"
|
|
372
|
+
raise "Failed to create workstream: #{message}"
|
|
373
|
+
end
|
|
374
|
+
|
|
267
375
|
def handle_success(issue:, slug:, branch_name:, base_branch:, plan_data:, working_dir:)
|
|
268
|
-
stage_and_commit(issue, working_dir: working_dir)
|
|
376
|
+
changes_committed = stage_and_commit(issue, working_dir: working_dir)
|
|
269
377
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
378
|
+
unless changes_committed
|
|
379
|
+
handle_no_changes(issue: issue, slug: slug, branch_name: branch_name, working_dir: working_dir)
|
|
380
|
+
return
|
|
381
|
+
end
|
|
273
382
|
|
|
274
|
-
|
|
383
|
+
# Check if PR should be created based on VCS preferences
|
|
384
|
+
# For watch mode, default to creating PRs (set to false to disable)
|
|
385
|
+
vcs_config = config_dig(:work_loop, :version_control) || {}
|
|
386
|
+
auto_create_pr = config_value(vcs_config, :auto_create_pr, true)
|
|
387
|
+
|
|
388
|
+
pr_url = if !changes_committed
|
|
389
|
+
Aidp.log_info(
|
|
390
|
+
"build_processor",
|
|
391
|
+
"skipping_pr_no_commits",
|
|
392
|
+
issue: issue[:number],
|
|
393
|
+
branch: branch_name,
|
|
394
|
+
working_dir: working_dir
|
|
395
|
+
)
|
|
396
|
+
display_message("ℹ️ Skipping PR creation because there are no commits on #{branch_name}.", type: :muted)
|
|
397
|
+
nil
|
|
398
|
+
elsif auto_create_pr
|
|
399
|
+
Aidp.log_info(
|
|
400
|
+
"build_processor",
|
|
401
|
+
"creating_pull_request",
|
|
402
|
+
issue: issue[:number],
|
|
403
|
+
branch: branch_name,
|
|
404
|
+
base_branch: base_branch,
|
|
405
|
+
working_dir: working_dir
|
|
406
|
+
)
|
|
275
407
|
create_pull_request(issue: issue, branch_name: branch_name, base_branch: base_branch, working_dir: working_dir)
|
|
276
408
|
else
|
|
277
409
|
display_message("ℹ️ Skipping PR creation (disabled in VCS preferences)", type: :muted)
|
|
278
410
|
nil
|
|
279
411
|
end
|
|
280
412
|
|
|
413
|
+
# Fetch the user who added the most recent label
|
|
414
|
+
label_actor = @repository_client.most_recent_label_actor(issue[:number])
|
|
415
|
+
|
|
281
416
|
workstream_note = @use_workstreams ? "\n- Workstream: `#{slug}`" : ""
|
|
282
417
|
pr_line = pr_url ? "\n- Pull Request: #{pr_url}" : ""
|
|
418
|
+
actor_tag = label_actor ? "cc @#{label_actor}\n\n" : ""
|
|
283
419
|
|
|
284
420
|
comment = <<~COMMENT
|
|
285
421
|
✅ Implementation complete for ##{issue[:number]}.
|
|
286
|
-
|
|
422
|
+
|
|
423
|
+
#{actor_tag}- Branch: `#{branch_name}`#{workstream_note}#{pr_line}
|
|
287
424
|
|
|
288
425
|
Summary:
|
|
289
426
|
#{plan_value(plan_data, "summary")}
|
|
@@ -316,10 +453,20 @@ module Aidp
|
|
|
316
453
|
questions = result[:clarification_questions] || []
|
|
317
454
|
workstream_note = @use_workstreams ? " The workstream `#{slug}` has been preserved." : " The branch has been preserved."
|
|
318
455
|
|
|
456
|
+
# Fetch the user who added the most recent label
|
|
457
|
+
label_actor = @repository_client.most_recent_label_actor(issue[:number])
|
|
458
|
+
|
|
319
459
|
# Build comment with questions
|
|
320
460
|
comment_parts = []
|
|
321
461
|
comment_parts << "❓ Implementation needs clarification for ##{issue[:number]}."
|
|
322
462
|
comment_parts << ""
|
|
463
|
+
|
|
464
|
+
# Tag the label actor if available
|
|
465
|
+
if label_actor
|
|
466
|
+
comment_parts << "cc @#{label_actor}"
|
|
467
|
+
comment_parts << ""
|
|
468
|
+
end
|
|
469
|
+
|
|
323
470
|
comment_parts << "The AI agent needs additional information to proceed with implementation:"
|
|
324
471
|
comment_parts << ""
|
|
325
472
|
questions.each_with_index do |question, index|
|
|
@@ -355,49 +502,143 @@ module Aidp
|
|
|
355
502
|
|
|
356
503
|
def handle_failure(issue:, slug:, result:)
|
|
357
504
|
message = result[:message] || "Unknown failure"
|
|
505
|
+
error_info = result[:error] || result[:error_details]
|
|
358
506
|
workstream_note = @use_workstreams ? " The workstream `#{slug}` has been left intact for debugging." : " The branch has been left intact for debugging."
|
|
507
|
+
|
|
508
|
+
# Build detailed error message for the comment
|
|
509
|
+
error_details_section = if error_info
|
|
510
|
+
"\nError: #{error_info}"
|
|
511
|
+
else
|
|
512
|
+
""
|
|
513
|
+
end
|
|
514
|
+
|
|
359
515
|
comment = <<~COMMENT
|
|
360
516
|
❌ Implementation attempt for ##{issue[:number]} failed.
|
|
361
517
|
|
|
362
518
|
Status: #{result[:status]}
|
|
363
|
-
Details: #{message}
|
|
519
|
+
Details: #{message}#{error_details_section}
|
|
364
520
|
|
|
365
521
|
Please review the repository for partial changes.#{workstream_note}
|
|
366
522
|
COMMENT
|
|
367
523
|
@repository_client.post_comment(issue[:number], comment)
|
|
524
|
+
|
|
525
|
+
# Log the failure with full details
|
|
526
|
+
Aidp.log_error(
|
|
527
|
+
"build_processor",
|
|
528
|
+
"Build failed for issue ##{issue[:number]}",
|
|
529
|
+
status: result[:status],
|
|
530
|
+
message: message,
|
|
531
|
+
error: error_info&.to_s,
|
|
532
|
+
workstream: slug
|
|
533
|
+
)
|
|
534
|
+
|
|
368
535
|
@state_store.record_build_status(
|
|
369
536
|
issue[:number],
|
|
370
537
|
status: "failed",
|
|
371
|
-
details: {message: message, workstream: slug}
|
|
538
|
+
details: {message: message, error: error_info&.to_s, workstream: slug}
|
|
372
539
|
)
|
|
373
540
|
display_message("⚠️ Build failure recorded for issue ##{issue[:number]}", type: :warn)
|
|
374
541
|
end
|
|
375
542
|
|
|
543
|
+
def handle_no_changes(issue:, slug:, branch_name:, working_dir:)
|
|
544
|
+
location_note = if @use_workstreams
|
|
545
|
+
"The workstream `#{slug}` has been preserved for review."
|
|
546
|
+
else
|
|
547
|
+
"Branch `#{branch_name}` remains checked out for inspection."
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
@state_store.record_build_status(
|
|
551
|
+
issue[:number],
|
|
552
|
+
status: "no_changes",
|
|
553
|
+
details: {branch: branch_name, workstream: slug}
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
Aidp.log_warn(
|
|
557
|
+
"build_processor",
|
|
558
|
+
"noop_build_result",
|
|
559
|
+
issue: issue[:number],
|
|
560
|
+
branch: branch_name,
|
|
561
|
+
workstream: slug
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
display_message("⚠️ Implementation produced no changes; labels remain untouched. #{location_note}", type: :warn)
|
|
565
|
+
enqueue_decider_followup(working_dir)
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
def handle_incomplete_criteria(issue:, slug:, branch_name:, working_dir:, metadata:)
|
|
569
|
+
display_message("⚠️ Completion criteria unmet; scheduling additional fix-forward iteration.", type: :warn)
|
|
570
|
+
enqueue_decider_followup(working_dir)
|
|
571
|
+
|
|
572
|
+
@state_store.record_build_status(
|
|
573
|
+
issue[:number],
|
|
574
|
+
status: "pending_fix_forward",
|
|
575
|
+
details: {branch: branch_name, workstream: slug, criteria: metadata}
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
Aidp.log_info(
|
|
579
|
+
"build_processor",
|
|
580
|
+
"pending_fix_forward",
|
|
581
|
+
issue: issue[:number],
|
|
582
|
+
branch: branch_name,
|
|
583
|
+
workstream: slug,
|
|
584
|
+
criteria: metadata
|
|
585
|
+
)
|
|
586
|
+
end
|
|
587
|
+
|
|
376
588
|
def stage_and_commit(issue, working_dir: @project_dir)
|
|
589
|
+
commit_created = false
|
|
590
|
+
|
|
377
591
|
Dir.chdir(working_dir) do
|
|
378
592
|
status_output = run_git(%w[status --porcelain])
|
|
379
593
|
if status_output.strip.empty?
|
|
380
594
|
display_message("ℹ️ No file changes detected after work loop.", type: :muted)
|
|
381
|
-
|
|
595
|
+
Aidp.log_info("build_processor", "no_changes_after_work_loop", issue: issue[:number], working_dir: working_dir)
|
|
596
|
+
return commit_created
|
|
382
597
|
end
|
|
383
598
|
|
|
599
|
+
changed_entries = status_output.lines.map(&:strip).reject(&:empty?)
|
|
600
|
+
Aidp.log_info(
|
|
601
|
+
"build_processor",
|
|
602
|
+
"changes_detected_after_work_loop",
|
|
603
|
+
issue: issue[:number],
|
|
604
|
+
working_dir: working_dir,
|
|
605
|
+
changed_file_count: changed_entries.length,
|
|
606
|
+
changed_files_sample: changed_entries.first(10)
|
|
607
|
+
)
|
|
608
|
+
|
|
384
609
|
run_git(%w[add -A])
|
|
385
610
|
commit_message = build_commit_message(issue)
|
|
386
611
|
run_git(["commit", "-m", commit_message])
|
|
387
612
|
display_message("💾 Created commit: #{commit_message.lines.first.strip}", type: :info)
|
|
613
|
+
Aidp.log_info(
|
|
614
|
+
"build_processor",
|
|
615
|
+
"commit_created",
|
|
616
|
+
working_dir: working_dir,
|
|
617
|
+
issue: issue[:number],
|
|
618
|
+
commit_summary: commit_message.lines.first.strip
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
# Push the branch to remote
|
|
622
|
+
current_branch = run_git(%w[branch --show-current]).strip
|
|
623
|
+
run_git(["push", "-u", "origin", current_branch])
|
|
624
|
+
display_message("⬆️ Pushed branch '#{current_branch}' to remote", type: :info)
|
|
625
|
+
Aidp.log_info("build_processor", "branch_pushed", branch: current_branch, working_dir: working_dir)
|
|
626
|
+
commit_created = true
|
|
388
627
|
end
|
|
628
|
+
|
|
629
|
+
commit_created
|
|
389
630
|
end
|
|
390
631
|
|
|
391
632
|
def build_commit_message(issue)
|
|
392
|
-
vcs_config =
|
|
633
|
+
vcs_config = config_dig(:work_loop, :version_control) || {}
|
|
393
634
|
|
|
394
635
|
# Base message components
|
|
395
636
|
issue_ref = "##{issue[:number]}"
|
|
396
637
|
title = issue[:title]
|
|
397
638
|
|
|
398
639
|
# Determine commit prefix based on configuration
|
|
399
|
-
prefix = if vcs_config
|
|
400
|
-
commit_style = vcs_config
|
|
640
|
+
prefix = if config_value(vcs_config, :conventional_commits)
|
|
641
|
+
commit_style = config_value(vcs_config, :commit_style, "default")
|
|
401
642
|
emoji = (commit_style == "emoji") ? "✨ " : ""
|
|
402
643
|
scope = (commit_style == "angular") ? "(implementation)" : ""
|
|
403
644
|
"#{emoji}feat#{scope}: "
|
|
@@ -409,7 +650,7 @@ module Aidp
|
|
|
409
650
|
main_message = "#{prefix}implement #{issue_ref} #{title}"
|
|
410
651
|
|
|
411
652
|
# Add co-author attribution if configured
|
|
412
|
-
if vcs_config
|
|
653
|
+
if config_value(vcs_config, :co_author_ai, true)
|
|
413
654
|
provider_name = detect_current_provider || "AI Agent"
|
|
414
655
|
co_author = "\n\nCo-authored-by: #{provider_name} <ai@aidp.dev>"
|
|
415
656
|
main_message + co_author
|
|
@@ -432,15 +673,40 @@ module Aidp
|
|
|
432
673
|
@config ||= begin
|
|
433
674
|
config_manager = Aidp::Harness::ConfigManager.new(@project_dir)
|
|
434
675
|
config_manager.config || {}
|
|
435
|
-
rescue
|
|
676
|
+
rescue => e
|
|
677
|
+
Aidp.log_error("build_processor", "config_load_exception", project_dir: @project_dir, error: e.message, backtrace: e.backtrace&.first(5))
|
|
436
678
|
{}
|
|
437
679
|
end
|
|
438
680
|
end
|
|
439
681
|
|
|
682
|
+
# Helper to safely dig into config with both string and symbol keys
|
|
683
|
+
def config_dig(*keys)
|
|
684
|
+
value = config
|
|
685
|
+
keys.each do |key|
|
|
686
|
+
return nil unless value.is_a?(Hash)
|
|
687
|
+
# Try both symbol and string versions of the key
|
|
688
|
+
value = value[key] || value[key.to_s] || value[key.to_sym]
|
|
689
|
+
return nil if value.nil?
|
|
690
|
+
end
|
|
691
|
+
value
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
# Helper to get config value with both string and symbol key support
|
|
695
|
+
def config_value(hash, key, default = nil)
|
|
696
|
+
return default unless hash.is_a?(Hash)
|
|
697
|
+
# Check each key variation explicitly to handle false/nil values correctly
|
|
698
|
+
return hash[key] if hash.key?(key)
|
|
699
|
+
return hash[key.to_s] if hash.key?(key.to_s)
|
|
700
|
+
return hash[key.to_sym] if hash.key?(key.to_sym)
|
|
701
|
+
default
|
|
702
|
+
end
|
|
703
|
+
|
|
440
704
|
def create_pull_request(issue:, branch_name:, base_branch:, working_dir: @project_dir)
|
|
441
705
|
title = "aidp: Resolve ##{issue[:number]} - #{issue[:title]}"
|
|
442
706
|
test_summary = gather_test_summary(working_dir: working_dir)
|
|
443
707
|
body = <<~BODY
|
|
708
|
+
Fixes ##{issue[:number]}
|
|
709
|
+
|
|
444
710
|
## Summary
|
|
445
711
|
- Automated resolution for ##{issue[:number]}
|
|
446
712
|
|
|
@@ -449,20 +715,44 @@ module Aidp
|
|
|
449
715
|
BODY
|
|
450
716
|
|
|
451
717
|
# Determine if PR should be draft based on VCS preferences
|
|
452
|
-
vcs_config =
|
|
453
|
-
pr_strategy = vcs_config
|
|
718
|
+
vcs_config = config_dig(:work_loop, :version_control) || {}
|
|
719
|
+
pr_strategy = config_value(vcs_config, :pr_strategy, "draft")
|
|
454
720
|
draft = (pr_strategy == "draft")
|
|
455
721
|
|
|
722
|
+
# Fetch the user who added the most recent label to assign the PR
|
|
723
|
+
label_actor = @repository_client.most_recent_label_actor(issue[:number])
|
|
724
|
+
assignee = label_actor || issue[:author]
|
|
725
|
+
|
|
726
|
+
Aidp.log_info(
|
|
727
|
+
"build_processor",
|
|
728
|
+
"assigning_pr",
|
|
729
|
+
issue: issue[:number],
|
|
730
|
+
assignee: assignee,
|
|
731
|
+
label_actor: label_actor,
|
|
732
|
+
fallback_to_author: label_actor.nil?
|
|
733
|
+
)
|
|
734
|
+
|
|
456
735
|
output = @repository_client.create_pull_request(
|
|
457
736
|
title: title,
|
|
458
737
|
body: body,
|
|
459
738
|
head: branch_name,
|
|
460
739
|
base: base_branch,
|
|
461
740
|
issue_number: issue[:number],
|
|
462
|
-
draft: draft
|
|
741
|
+
draft: draft,
|
|
742
|
+
assignee: assignee
|
|
463
743
|
)
|
|
464
744
|
|
|
465
|
-
extract_pr_url(output)
|
|
745
|
+
pr_url = extract_pr_url(output)
|
|
746
|
+
Aidp.log_info(
|
|
747
|
+
"build_processor",
|
|
748
|
+
"pull_request_created",
|
|
749
|
+
issue: issue[:number],
|
|
750
|
+
branch: branch_name,
|
|
751
|
+
base_branch: base_branch,
|
|
752
|
+
pr_url: pr_url,
|
|
753
|
+
assignee: assignee
|
|
754
|
+
)
|
|
755
|
+
pr_url
|
|
466
756
|
end
|
|
467
757
|
|
|
468
758
|
def gather_test_summary(working_dir: @project_dir)
|
|
@@ -470,7 +760,7 @@ module Aidp
|
|
|
470
760
|
log_path = File.join(".aidp", "logs", "test_runner.log")
|
|
471
761
|
return "- Fix-forward harness executed; refer to #{log_path}" unless File.exist?(log_path)
|
|
472
762
|
|
|
473
|
-
recent = File.readlines(log_path).last(20).map(&:strip).reject(&:empty?)
|
|
763
|
+
recent = File.readlines(log_path, encoding: "UTF-8").last(20).map(&:strip).reject(&:empty?)
|
|
474
764
|
if recent.empty?
|
|
475
765
|
"- Fix-forward harness executed successfully."
|
|
476
766
|
else
|