aidp 0.24.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 +165 -1
- data/lib/aidp/harness/config_schema.rb +50 -0
- data/lib/aidp/harness/provider_factory.rb +2 -0
- 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/kilocode.rb +202 -0
- data/lib/aidp/setup/provider_registry.rb +15 -0
- data/lib/aidp/setup/wizard.rb +12 -4
- 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 +66 -16
- 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 +380 -0
- 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 +185 -0
- data/lib/aidp/watch/state_store.rb +53 -0
- data/lib/aidp.rb +1 -0
- metadata +20 -1
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "timeout"
|
|
4
|
+
require_relative "base"
|
|
5
|
+
require_relative "../util"
|
|
6
|
+
require_relative "../debug_mixin"
|
|
7
|
+
|
|
8
|
+
module Aidp
|
|
9
|
+
module Providers
|
|
10
|
+
class Kilocode < Base
|
|
11
|
+
include Aidp::DebugMixin
|
|
12
|
+
|
|
13
|
+
def self.available?
|
|
14
|
+
!!Aidp::Util.which("kilocode")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def name
|
|
18
|
+
"kilocode"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def display_name
|
|
22
|
+
"Kilocode"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def send_message(prompt:, session: nil)
|
|
26
|
+
raise "kilocode not available" unless self.class.available?
|
|
27
|
+
|
|
28
|
+
# Smart timeout calculation
|
|
29
|
+
timeout_seconds = calculate_timeout
|
|
30
|
+
|
|
31
|
+
debug_provider("kilocode", "Starting execution", {timeout: timeout_seconds})
|
|
32
|
+
debug_log("📝 Sending prompt to kilocode (length: #{prompt.length})", level: :info)
|
|
33
|
+
|
|
34
|
+
# Check if streaming mode is enabled
|
|
35
|
+
streaming_enabled = ENV["AIDP_STREAMING"] == "1" || ENV["DEBUG"] == "1"
|
|
36
|
+
if streaming_enabled
|
|
37
|
+
display_message("📺 Display streaming enabled - output buffering reduced", type: :info)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Check if prompt is too large and warn
|
|
41
|
+
if prompt.length > 3000
|
|
42
|
+
debug_log("⚠️ Large prompt detected (#{prompt.length} chars) - this may cause rate limiting", level: :warn)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Set up activity monitoring
|
|
46
|
+
setup_activity_monitoring("kilocode", method(:activity_callback))
|
|
47
|
+
record_activity("Starting kilocode execution")
|
|
48
|
+
|
|
49
|
+
# Create a spinner for activity display
|
|
50
|
+
spinner = TTY::Spinner.new("[:spinner] :title", format: :dots, hide_cursor: true)
|
|
51
|
+
spinner.auto_spin
|
|
52
|
+
|
|
53
|
+
activity_display_thread = Thread.new do
|
|
54
|
+
start_time = Time.now
|
|
55
|
+
loop do
|
|
56
|
+
sleep 0.5 # Update every 500ms to reduce spam
|
|
57
|
+
elapsed = Time.now - start_time
|
|
58
|
+
|
|
59
|
+
# Break if we've been running too long or state changed
|
|
60
|
+
break if elapsed > timeout_seconds || @activity_state == :completed || @activity_state == :failed
|
|
61
|
+
|
|
62
|
+
update_spinner_status(spinner, elapsed, "🔄 kilocode")
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
begin
|
|
67
|
+
# Build kilocode command arguments
|
|
68
|
+
args = ["--auto"]
|
|
69
|
+
|
|
70
|
+
# Add model if specified
|
|
71
|
+
model = ENV["KILOCODE_MODEL"]
|
|
72
|
+
if model
|
|
73
|
+
args.concat(["-m", model])
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Add workspace detection if needed
|
|
77
|
+
if Dir.exist?(".git") && ENV["KILOCODE_WORKSPACE"]
|
|
78
|
+
args.concat(["--workspace", ENV["KILOCODE_WORKSPACE"]])
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Set authentication via environment variable
|
|
82
|
+
env_vars = {}
|
|
83
|
+
if ENV["KILOCODE_TOKEN"]
|
|
84
|
+
env_vars["KILOCODE_TOKEN"] = ENV["KILOCODE_TOKEN"]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Use debug_execute_command for better debugging
|
|
88
|
+
result = debug_execute_command("kilocode", args: args, input: prompt, timeout: timeout_seconds, streaming: streaming_enabled, env: env_vars)
|
|
89
|
+
|
|
90
|
+
# Log the results
|
|
91
|
+
debug_command("kilocode", args: args, input: prompt, output: result.out, error: result.err, exit_code: result.exit_status)
|
|
92
|
+
|
|
93
|
+
if result.exit_status == 0
|
|
94
|
+
spinner.success("✓")
|
|
95
|
+
mark_completed
|
|
96
|
+
result.out
|
|
97
|
+
else
|
|
98
|
+
spinner.error("✗")
|
|
99
|
+
mark_failed("kilocode failed with exit code #{result.exit_status}")
|
|
100
|
+
debug_error(StandardError.new("kilocode failed"), {exit_code: result.exit_status, stderr: result.err})
|
|
101
|
+
raise "kilocode failed with exit code #{result.exit_status}: #{result.err}"
|
|
102
|
+
end
|
|
103
|
+
rescue => e
|
|
104
|
+
spinner&.error("✗")
|
|
105
|
+
mark_failed("kilocode execution failed: #{e.message}")
|
|
106
|
+
debug_error(e, {provider: "kilocode", prompt_length: prompt.length})
|
|
107
|
+
raise
|
|
108
|
+
ensure
|
|
109
|
+
cleanup_activity_display(activity_display_thread, spinner)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
def calculate_timeout
|
|
116
|
+
# Priority order for timeout calculation:
|
|
117
|
+
# 1. Quick mode (for testing)
|
|
118
|
+
# 2. Environment variable override
|
|
119
|
+
# 3. Adaptive timeout based on step type
|
|
120
|
+
# 4. Default timeout
|
|
121
|
+
|
|
122
|
+
if ENV["AIDP_QUICK_MODE"]
|
|
123
|
+
display_message("⚡ Quick mode enabled - #{TIMEOUT_QUICK_MODE / 60} minute timeout", type: :highlight)
|
|
124
|
+
return TIMEOUT_QUICK_MODE
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
if ENV["AIDP_KILOCODE_TIMEOUT"]
|
|
128
|
+
return ENV["AIDP_KILOCODE_TIMEOUT"].to_i
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
if adaptive_timeout
|
|
132
|
+
display_message("🧠 Using adaptive timeout: #{adaptive_timeout} seconds", type: :info)
|
|
133
|
+
return adaptive_timeout
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Default timeout
|
|
137
|
+
display_message("📋 Using default timeout: #{TIMEOUT_DEFAULT / 60} minutes", type: :info)
|
|
138
|
+
TIMEOUT_DEFAULT
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def adaptive_timeout
|
|
142
|
+
@adaptive_timeout ||= begin
|
|
143
|
+
# Timeout recommendations based on step type patterns
|
|
144
|
+
step_name = ENV["AIDP_CURRENT_STEP"] || ""
|
|
145
|
+
|
|
146
|
+
case step_name
|
|
147
|
+
when /REPOSITORY_ANALYSIS/
|
|
148
|
+
TIMEOUT_REPOSITORY_ANALYSIS
|
|
149
|
+
when /ARCHITECTURE_ANALYSIS/
|
|
150
|
+
TIMEOUT_ARCHITECTURE_ANALYSIS
|
|
151
|
+
when /TEST_ANALYSIS/
|
|
152
|
+
TIMEOUT_TEST_ANALYSIS
|
|
153
|
+
when /FUNCTIONALITY_ANALYSIS/
|
|
154
|
+
TIMEOUT_FUNCTIONALITY_ANALYSIS
|
|
155
|
+
when /DOCUMENTATION_ANALYSIS/
|
|
156
|
+
TIMEOUT_DOCUMENTATION_ANALYSIS
|
|
157
|
+
when /STATIC_ANALYSIS/
|
|
158
|
+
TIMEOUT_STATIC_ANALYSIS
|
|
159
|
+
when /REFACTORING_RECOMMENDATIONS/
|
|
160
|
+
TIMEOUT_REFACTORING_RECOMMENDATIONS
|
|
161
|
+
else
|
|
162
|
+
nil # Use default
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def activity_callback(state, message, provider)
|
|
168
|
+
# This is now handled by the animated display thread
|
|
169
|
+
# Only print static messages for state changes
|
|
170
|
+
case state
|
|
171
|
+
when :starting
|
|
172
|
+
display_message("🚀 Starting kilocode execution...", type: :info)
|
|
173
|
+
when :completed
|
|
174
|
+
display_message("✅ kilocode execution completed", type: :success)
|
|
175
|
+
when :failed
|
|
176
|
+
display_message("❌ kilocode execution failed: #{message}", type: :error)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def setup_activity_monitoring(provider_name, callback)
|
|
181
|
+
@activity_callback = callback
|
|
182
|
+
@activity_state = :starting
|
|
183
|
+
@activity_start_time = Time.now
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def record_activity(message)
|
|
187
|
+
@activity_state = :running
|
|
188
|
+
@activity_callback&.call(:running, message, "kilocode")
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def mark_completed
|
|
192
|
+
@activity_state = :completed
|
|
193
|
+
@activity_callback&.call(:completed, "Execution completed", "kilocode")
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def mark_failed(reason)
|
|
197
|
+
@activity_state = :failed
|
|
198
|
+
@activity_callback&.call(:failed, reason, "kilocode")
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
@@ -41,6 +41,21 @@ module Aidp
|
|
|
41
41
|
value: "claude",
|
|
42
42
|
description: "Balanced performance for general-purpose tasks"
|
|
43
43
|
},
|
|
44
|
+
{
|
|
45
|
+
label: "Google Gemini (multimodal)",
|
|
46
|
+
value: "gemini",
|
|
47
|
+
description: "Google's multimodal AI with strong reasoning and vision capabilities"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
label: "Meta Llama (open-source)",
|
|
51
|
+
value: "llama",
|
|
52
|
+
description: "Meta's open-source model family, suitable for self-hosting"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
label: "DeepSeek (efficient reasoning)",
|
|
56
|
+
value: "deepseek",
|
|
57
|
+
description: "Cost-efficient reasoning models with strong performance"
|
|
58
|
+
},
|
|
44
59
|
{
|
|
45
60
|
label: "Mistral (European/open)",
|
|
46
61
|
value: "mistral",
|
data/lib/aidp/setup/wizard.rb
CHANGED
|
@@ -1305,12 +1305,20 @@ module Aidp
|
|
|
1305
1305
|
# Canonicalization helpers ------------------------------------------------
|
|
1306
1306
|
def normalize_model_family(value)
|
|
1307
1307
|
return "auto" if value.nil? || value.to_s.strip.empty?
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1308
|
+
|
|
1309
|
+
normalized_input = value.to_s.strip.downcase
|
|
1310
|
+
|
|
1311
|
+
# Check for exact canonical value match (case-insensitive)
|
|
1312
|
+
canonical_match = ProviderRegistry.model_family_values.find do |v|
|
|
1313
|
+
v.downcase == normalized_input
|
|
1314
|
+
end
|
|
1315
|
+
return canonical_match if canonical_match
|
|
1316
|
+
|
|
1317
|
+
# Try label -> value mapping (case-insensitive)
|
|
1311
1318
|
choices = ProviderRegistry.model_family_choices
|
|
1312
|
-
mapped = choices.find { |label, _| label == value }&.last
|
|
1319
|
+
mapped = choices.find { |label, _| label.downcase == value.to_s.downcase }&.last
|
|
1313
1320
|
return mapped if mapped
|
|
1321
|
+
|
|
1314
1322
|
# Unknown legacy entry -> fallback to auto
|
|
1315
1323
|
"auto"
|
|
1316
1324
|
end
|
data/lib/aidp/skills/composer.rb
CHANGED
|
@@ -82,6 +82,8 @@ module Aidp
|
|
|
82
82
|
def render_template(template, options: {})
|
|
83
83
|
return template if options.empty?
|
|
84
84
|
|
|
85
|
+
# Ensure template is UTF-8 encoded
|
|
86
|
+
template = template.encode("UTF-8", invalid: :replace, undef: :replace) unless template.encoding == Encoding::UTF_8
|
|
85
87
|
rendered = template.dup
|
|
86
88
|
|
|
87
89
|
options.each do |key, value|
|
|
@@ -158,6 +160,8 @@ module Aidp
|
|
|
158
160
|
def extract_placeholders(text)
|
|
159
161
|
return [] if text.nil? || text.empty?
|
|
160
162
|
|
|
163
|
+
# Ensure text is UTF-8 encoded
|
|
164
|
+
text = text.encode("UTF-8", invalid: :replace, undef: :replace) unless text.encoding == Encoding::UTF_8
|
|
161
165
|
scanner = StringScanner.new(text)
|
|
162
166
|
placeholders = []
|
|
163
167
|
|
data/lib/aidp/skills/loader.rb
CHANGED
|
@@ -34,7 +34,7 @@ module Aidp
|
|
|
34
34
|
raise Aidp::Errors::ValidationError, "Skill file not found: #{file_path}"
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
content = File.read(file_path)
|
|
37
|
+
content = File.read(file_path, encoding: "UTF-8")
|
|
38
38
|
load_from_string(content, source_path: file_path, provider: provider)
|
|
39
39
|
end
|
|
40
40
|
|
|
@@ -139,6 +139,8 @@ module Aidp
|
|
|
139
139
|
# @return [Array(Hash, String)] Tuple of [metadata, markdown_content]
|
|
140
140
|
# @raise [Aidp::Errors::ValidationError] if frontmatter is missing or invalid
|
|
141
141
|
def self.parse_frontmatter(content, source_path:)
|
|
142
|
+
# Ensure content is UTF-8 encoded
|
|
143
|
+
content = content.encode("UTF-8", invalid: :replace, undef: :replace) unless content.encoding == Encoding::UTF_8
|
|
142
144
|
lines = content.lines
|
|
143
145
|
|
|
144
146
|
unless lines.first&.strip == "---"
|
data/lib/aidp/version.rb
CHANGED
|
@@ -355,7 +355,7 @@ module Aidp
|
|
|
355
355
|
FileUtils.mkdir_p(File.dirname(target_config))
|
|
356
356
|
|
|
357
357
|
# Only copy when target missing or differs
|
|
358
|
-
if !File.exist?(target_config) || File.read(source_config) != File.read(target_config)
|
|
358
|
+
if !File.exist?(target_config) || File.read(source_config, encoding: "UTF-8") != File.read(target_config, encoding: "UTF-8")
|
|
359
359
|
FileUtils.cp(source_config, target_config)
|
|
360
360
|
end
|
|
361
361
|
rescue => e
|
|
@@ -382,8 +382,8 @@ module Aidp
|
|
|
382
382
|
|
|
383
383
|
# Check if PR should be created based on VCS preferences
|
|
384
384
|
# For watch mode, default to creating PRs (set to false to disable)
|
|
385
|
-
vcs_config =
|
|
386
|
-
auto_create_pr = vcs_config
|
|
385
|
+
vcs_config = config_dig(:work_loop, :version_control) || {}
|
|
386
|
+
auto_create_pr = config_value(vcs_config, :auto_create_pr, true)
|
|
387
387
|
|
|
388
388
|
pr_url = if !changes_committed
|
|
389
389
|
Aidp.log_info(
|
|
@@ -410,12 +410,17 @@ module Aidp
|
|
|
410
410
|
nil
|
|
411
411
|
end
|
|
412
412
|
|
|
413
|
+
# Fetch the user who added the most recent label
|
|
414
|
+
label_actor = @repository_client.most_recent_label_actor(issue[:number])
|
|
415
|
+
|
|
413
416
|
workstream_note = @use_workstreams ? "\n- Workstream: `#{slug}`" : ""
|
|
414
417
|
pr_line = pr_url ? "\n- Pull Request: #{pr_url}" : ""
|
|
418
|
+
actor_tag = label_actor ? "cc @#{label_actor}\n\n" : ""
|
|
415
419
|
|
|
416
420
|
comment = <<~COMMENT
|
|
417
421
|
✅ Implementation complete for ##{issue[:number]}.
|
|
418
|
-
|
|
422
|
+
|
|
423
|
+
#{actor_tag}- Branch: `#{branch_name}`#{workstream_note}#{pr_line}
|
|
419
424
|
|
|
420
425
|
Summary:
|
|
421
426
|
#{plan_value(plan_data, "summary")}
|
|
@@ -448,10 +453,20 @@ module Aidp
|
|
|
448
453
|
questions = result[:clarification_questions] || []
|
|
449
454
|
workstream_note = @use_workstreams ? " The workstream `#{slug}` has been preserved." : " The branch has been preserved."
|
|
450
455
|
|
|
456
|
+
# Fetch the user who added the most recent label
|
|
457
|
+
label_actor = @repository_client.most_recent_label_actor(issue[:number])
|
|
458
|
+
|
|
451
459
|
# Build comment with questions
|
|
452
460
|
comment_parts = []
|
|
453
461
|
comment_parts << "❓ Implementation needs clarification for ##{issue[:number]}."
|
|
454
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
|
+
|
|
455
470
|
comment_parts << "The AI agent needs additional information to proceed with implementation:"
|
|
456
471
|
comment_parts << ""
|
|
457
472
|
questions.each_with_index do |question, index|
|
|
@@ -615,15 +630,15 @@ module Aidp
|
|
|
615
630
|
end
|
|
616
631
|
|
|
617
632
|
def build_commit_message(issue)
|
|
618
|
-
vcs_config =
|
|
633
|
+
vcs_config = config_dig(:work_loop, :version_control) || {}
|
|
619
634
|
|
|
620
635
|
# Base message components
|
|
621
636
|
issue_ref = "##{issue[:number]}"
|
|
622
637
|
title = issue[:title]
|
|
623
638
|
|
|
624
639
|
# Determine commit prefix based on configuration
|
|
625
|
-
prefix = if vcs_config
|
|
626
|
-
commit_style = vcs_config
|
|
640
|
+
prefix = if config_value(vcs_config, :conventional_commits)
|
|
641
|
+
commit_style = config_value(vcs_config, :commit_style, "default")
|
|
627
642
|
emoji = (commit_style == "emoji") ? "✨ " : ""
|
|
628
643
|
scope = (commit_style == "angular") ? "(implementation)" : ""
|
|
629
644
|
"#{emoji}feat#{scope}: "
|
|
@@ -635,7 +650,7 @@ module Aidp
|
|
|
635
650
|
main_message = "#{prefix}implement #{issue_ref} #{title}"
|
|
636
651
|
|
|
637
652
|
# Add co-author attribution if configured
|
|
638
|
-
if vcs_config
|
|
653
|
+
if config_value(vcs_config, :co_author_ai, true)
|
|
639
654
|
provider_name = detect_current_provider || "AI Agent"
|
|
640
655
|
co_author = "\n\nCo-authored-by: #{provider_name} <ai@aidp.dev>"
|
|
641
656
|
main_message + co_author
|
|
@@ -658,30 +673,64 @@ module Aidp
|
|
|
658
673
|
@config ||= begin
|
|
659
674
|
config_manager = Aidp::Harness::ConfigManager.new(@project_dir)
|
|
660
675
|
config_manager.config || {}
|
|
661
|
-
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))
|
|
662
678
|
{}
|
|
663
679
|
end
|
|
664
680
|
end
|
|
665
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
|
+
|
|
666
704
|
def create_pull_request(issue:, branch_name:, base_branch:, working_dir: @project_dir)
|
|
667
705
|
title = "aidp: Resolve ##{issue[:number]} - #{issue[:title]}"
|
|
668
706
|
test_summary = gather_test_summary(working_dir: working_dir)
|
|
669
707
|
body = <<~BODY
|
|
708
|
+
Fixes ##{issue[:number]}
|
|
709
|
+
|
|
670
710
|
## Summary
|
|
671
711
|
- Automated resolution for ##{issue[:number]}
|
|
672
|
-
- Fixes ##{issue[:number]}
|
|
673
712
|
|
|
674
713
|
## Testing
|
|
675
714
|
#{test_summary}
|
|
676
715
|
BODY
|
|
677
716
|
|
|
678
717
|
# Determine if PR should be draft based on VCS preferences
|
|
679
|
-
vcs_config =
|
|
680
|
-
pr_strategy = vcs_config
|
|
718
|
+
vcs_config = config_dig(:work_loop, :version_control) || {}
|
|
719
|
+
pr_strategy = config_value(vcs_config, :pr_strategy, "draft")
|
|
681
720
|
draft = (pr_strategy == "draft")
|
|
682
721
|
|
|
683
|
-
#
|
|
684
|
-
|
|
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
|
+
)
|
|
685
734
|
|
|
686
735
|
output = @repository_client.create_pull_request(
|
|
687
736
|
title: title,
|
|
@@ -700,7 +749,8 @@ module Aidp
|
|
|
700
749
|
issue: issue[:number],
|
|
701
750
|
branch: branch_name,
|
|
702
751
|
base_branch: base_branch,
|
|
703
|
-
pr_url: pr_url
|
|
752
|
+
pr_url: pr_url,
|
|
753
|
+
assignee: assignee
|
|
704
754
|
)
|
|
705
755
|
pr_url
|
|
706
756
|
end
|
|
@@ -710,7 +760,7 @@ module Aidp
|
|
|
710
760
|
log_path = File.join(".aidp", "logs", "test_runner.log")
|
|
711
761
|
return "- Fix-forward harness executed; refer to #{log_path}" unless File.exist?(log_path)
|
|
712
762
|
|
|
713
|
-
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?)
|
|
714
764
|
if recent.empty?
|
|
715
765
|
"- Fix-forward harness executed successfully."
|
|
716
766
|
else
|