aidp 0.22.0 → 0.24.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 +145 -31
- data/lib/aidp/cli.rb +19 -2
- 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 +25 -0
- data/lib/aidp/harness/configuration.rb +69 -6
- data/lib/aidp/harness/error_handler.rb +117 -44
- data/lib/aidp/harness/provider_manager.rb +64 -0
- data/lib/aidp/harness/provider_metrics.rb +138 -0
- data/lib/aidp/harness/runner.rb +110 -35
- 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/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/setup/devcontainer/backup_manager.rb +11 -4
- data/lib/aidp/setup/provider_registry.rb +107 -0
- data/lib/aidp/setup/wizard.rb +189 -31
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/build_processor.rb +357 -27
- data/lib/aidp/watch/plan_generator.rb +16 -1
- data/lib/aidp/watch/plan_processor.rb +54 -3
- data/lib/aidp/watch/repository_client.rb +78 -4
- data/lib/aidp/watch/repository_safety_checker.rb +12 -3
- data/lib/aidp/watch/runner.rb +52 -10
- data/lib/aidp/workflows/guided_agent.rb +53 -0
- data/lib/aidp/worktree.rb +67 -10
- data/templates/work_loop/decide_whats_next.md +21 -0
- data/templates/work_loop/diagnose_failures.md +21 -0
- metadata +10 -3
- /data/{bin → exe}/aidp +0 -0
|
@@ -61,8 +61,22 @@ module Aidp
|
|
|
61
61
|
gh_available? ? post_comment_via_gh(number, body) : post_comment_via_api(number, body)
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
-
def create_pull_request(title:, body:, head:, base:, issue_number:)
|
|
65
|
-
gh_available? ? create_pull_request_via_gh(title: title, body: body, head: head, base: base, issue_number: issue_number) : raise("GitHub CLI not available - cannot create PR")
|
|
64
|
+
def create_pull_request(title:, body:, head:, base:, issue_number:, draft: false, assignee: nil)
|
|
65
|
+
gh_available? ? create_pull_request_via_gh(title: title, body: body, head: head, base: base, issue_number: issue_number, draft: draft, assignee: assignee) : raise("GitHub CLI not available - cannot create PR")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def add_labels(number, *labels)
|
|
69
|
+
gh_available? ? add_labels_via_gh(number, labels.flatten) : add_labels_via_api(number, labels.flatten)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def remove_labels(number, *labels)
|
|
73
|
+
gh_available? ? remove_labels_via_gh(number, labels.flatten) : remove_labels_via_api(number, labels.flatten)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def replace_labels(number, old_labels:, new_labels:)
|
|
77
|
+
# Remove old labels and add new ones atomically where possible
|
|
78
|
+
remove_labels(number, *old_labels) unless old_labels.empty?
|
|
79
|
+
add_labels(number, *new_labels) unless new_labels.empty?
|
|
66
80
|
end
|
|
67
81
|
|
|
68
82
|
private
|
|
@@ -162,7 +176,7 @@ module Aidp
|
|
|
162
176
|
response.body
|
|
163
177
|
end
|
|
164
178
|
|
|
165
|
-
def create_pull_request_via_gh(title:, body:, head:, base:, issue_number:, draft: false)
|
|
179
|
+
def create_pull_request_via_gh(title:, body:, head:, base:, issue_number:, draft: false, assignee: nil)
|
|
166
180
|
cmd = [
|
|
167
181
|
"gh", "pr", "create",
|
|
168
182
|
"--repo", full_repo,
|
|
@@ -171,8 +185,8 @@ module Aidp
|
|
|
171
185
|
"--head", head,
|
|
172
186
|
"--base", base
|
|
173
187
|
]
|
|
174
|
-
cmd += ["--issue", issue_number.to_s] if issue_number
|
|
175
188
|
cmd += ["--draft"] if draft
|
|
189
|
+
cmd += ["--assignee", assignee] if assignee
|
|
176
190
|
|
|
177
191
|
stdout, stderr, status = Open3.capture3(*cmd)
|
|
178
192
|
raise "Failed to create PR via gh: #{stderr.strip}" unless status.success?
|
|
@@ -180,6 +194,66 @@ module Aidp
|
|
|
180
194
|
stdout.strip
|
|
181
195
|
end
|
|
182
196
|
|
|
197
|
+
def add_labels_via_gh(number, labels)
|
|
198
|
+
return if labels.empty?
|
|
199
|
+
|
|
200
|
+
cmd = ["gh", "issue", "edit", number.to_s, "--repo", full_repo]
|
|
201
|
+
labels.each { |label| cmd += ["--add-label", label] }
|
|
202
|
+
|
|
203
|
+
stdout, stderr, status = Open3.capture3(*cmd)
|
|
204
|
+
raise "Failed to add labels via gh: #{stderr.strip}" unless status.success?
|
|
205
|
+
|
|
206
|
+
stdout.strip
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def add_labels_via_api(number, labels)
|
|
210
|
+
return if labels.empty?
|
|
211
|
+
|
|
212
|
+
uri = URI("https://api.github.com/repos/#{full_repo}/issues/#{number}/labels")
|
|
213
|
+
request = Net::HTTP::Post.new(uri)
|
|
214
|
+
request["Content-Type"] = "application/json"
|
|
215
|
+
request.body = JSON.dump({labels: labels})
|
|
216
|
+
|
|
217
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
|
218
|
+
http.request(request)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
raise "Failed to add labels via API (#{response.code})" unless response.code.start_with?("2")
|
|
222
|
+
response.body
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def remove_labels_via_gh(number, labels)
|
|
226
|
+
return if labels.empty?
|
|
227
|
+
|
|
228
|
+
cmd = ["gh", "issue", "edit", number.to_s, "--repo", full_repo]
|
|
229
|
+
labels.each { |label| cmd += ["--remove-label", label] }
|
|
230
|
+
|
|
231
|
+
stdout, stderr, status = Open3.capture3(*cmd)
|
|
232
|
+
raise "Failed to remove labels via gh: #{stderr.strip}" unless status.success?
|
|
233
|
+
|
|
234
|
+
stdout.strip
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def remove_labels_via_api(number, labels)
|
|
238
|
+
return if labels.empty?
|
|
239
|
+
|
|
240
|
+
labels.each do |label|
|
|
241
|
+
# URL encode the label name
|
|
242
|
+
encoded_label = URI.encode_www_form_component(label)
|
|
243
|
+
uri = URI("https://api.github.com/repos/#{full_repo}/issues/#{number}/labels/#{encoded_label}")
|
|
244
|
+
request = Net::HTTP::Delete.new(uri)
|
|
245
|
+
|
|
246
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
|
247
|
+
http.request(request)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# 404 is OK - label didn't exist
|
|
251
|
+
unless response.code.start_with?("2") || response.code == "404"
|
|
252
|
+
raise "Failed to remove label '#{label}' via API (#{response.code})"
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
183
257
|
def normalize_issue(raw)
|
|
184
258
|
{
|
|
185
259
|
number: raw["number"],
|
|
@@ -163,16 +163,25 @@ module Aidp
|
|
|
163
163
|
end
|
|
164
164
|
|
|
165
165
|
def public_repos_allowed?
|
|
166
|
-
|
|
166
|
+
# Support both string and symbol keys
|
|
167
|
+
safety_config = @config[:safety] || @config["safety"] || {}
|
|
168
|
+
(safety_config[:allow_public_repos] || safety_config["allow_public_repos"]) == true
|
|
167
169
|
end
|
|
168
170
|
|
|
169
171
|
def author_allowlist
|
|
170
|
-
|
|
172
|
+
# Support both string and symbol keys
|
|
173
|
+
safety_config = @config[:safety] || @config["safety"] || {}
|
|
174
|
+
@author_allowlist ||= Array(
|
|
175
|
+
safety_config[:author_allowlist] || safety_config["author_allowlist"]
|
|
176
|
+
).compact.map(&:to_s)
|
|
171
177
|
end
|
|
172
178
|
|
|
173
179
|
def safe_environment?
|
|
174
180
|
# Check if running in a container
|
|
175
|
-
|
|
181
|
+
# Support both string and symbol keys
|
|
182
|
+
safety_config = @config[:safety] || @config["safety"] || {}
|
|
183
|
+
require_container = safety_config[:require_container] || safety_config["require_container"]
|
|
184
|
+
in_container? || require_container == false
|
|
176
185
|
end
|
|
177
186
|
|
|
178
187
|
def in_container?
|
data/lib/aidp/watch/runner.rb
CHANGED
|
@@ -19,12 +19,13 @@ module Aidp
|
|
|
19
19
|
|
|
20
20
|
DEFAULT_INTERVAL = 30
|
|
21
21
|
|
|
22
|
-
def initialize(issues_url:, interval: DEFAULT_INTERVAL, provider_name: nil, gh_available: nil, project_dir: Dir.pwd, once: false, use_workstreams: true, prompt: TTY::Prompt.new, safety_config: {}, force: false)
|
|
22
|
+
def initialize(issues_url:, interval: DEFAULT_INTERVAL, provider_name: nil, gh_available: nil, project_dir: Dir.pwd, once: false, use_workstreams: true, prompt: TTY::Prompt.new, safety_config: {}, force: false, verbose: false)
|
|
23
23
|
@prompt = prompt
|
|
24
24
|
@interval = interval
|
|
25
25
|
@once = once
|
|
26
26
|
@project_dir = project_dir
|
|
27
27
|
@force = force
|
|
28
|
+
@verbose = verbose
|
|
28
29
|
|
|
29
30
|
owner, repo = RepositoryClient.parse_issues_url(issues_url)
|
|
30
31
|
@repository_client = RepositoryClient.new(owner: owner, repo: repo, gh_available: gh_available)
|
|
@@ -33,16 +34,23 @@ module Aidp
|
|
|
33
34
|
config: safety_config
|
|
34
35
|
)
|
|
35
36
|
@state_store = StateStore.new(project_dir: project_dir, repository: "#{owner}/#{repo}")
|
|
37
|
+
|
|
38
|
+
# Extract label configuration from safety_config (it's actually the full watch config)
|
|
39
|
+
label_config = safety_config[:labels] || safety_config["labels"] || {}
|
|
40
|
+
|
|
36
41
|
@plan_processor = PlanProcessor.new(
|
|
37
42
|
repository_client: @repository_client,
|
|
38
43
|
state_store: @state_store,
|
|
39
|
-
plan_generator: PlanGenerator.new(provider_name: provider_name)
|
|
44
|
+
plan_generator: PlanGenerator.new(provider_name: provider_name, verbose: verbose),
|
|
45
|
+
label_config: label_config
|
|
40
46
|
)
|
|
41
47
|
@build_processor = BuildProcessor.new(
|
|
42
48
|
repository_client: @repository_client,
|
|
43
49
|
state_store: @state_store,
|
|
44
50
|
project_dir: project_dir,
|
|
45
|
-
use_workstreams: use_workstreams
|
|
51
|
+
use_workstreams: use_workstreams,
|
|
52
|
+
verbose: verbose,
|
|
53
|
+
label_config: label_config
|
|
46
54
|
)
|
|
47
55
|
end
|
|
48
56
|
|
|
@@ -50,12 +58,25 @@ module Aidp
|
|
|
50
58
|
# Validate safety requirements before starting
|
|
51
59
|
@safety_checker.validate_watch_mode_safety!(force: @force)
|
|
52
60
|
|
|
61
|
+
Aidp.log_info(
|
|
62
|
+
"watch_runner",
|
|
63
|
+
"watch_mode_started",
|
|
64
|
+
repo: @repository_client.full_repo,
|
|
65
|
+
interval: @interval,
|
|
66
|
+
once: @once,
|
|
67
|
+
use_workstreams: @use_workstreams,
|
|
68
|
+
verbose: @verbose
|
|
69
|
+
)
|
|
70
|
+
|
|
53
71
|
display_message("👀 Watch mode enabled for #{@repository_client.full_repo}", type: :highlight)
|
|
54
72
|
display_message("Polling every #{@interval} seconds. Press Ctrl+C to stop.", type: :muted)
|
|
55
73
|
|
|
56
74
|
loop do
|
|
75
|
+
Aidp.log_debug("watch_runner", "poll_cycle.begin", repo: @repository_client.full_repo, interval: @interval)
|
|
57
76
|
process_cycle
|
|
77
|
+
Aidp.log_debug("watch_runner", "poll_cycle.complete", once: @once, next_poll_in: @once ? nil : @interval)
|
|
58
78
|
break if @once
|
|
79
|
+
Aidp.log_debug("watch_runner", "poll_cycle.sleep", seconds: @interval)
|
|
59
80
|
sleep @interval
|
|
60
81
|
end
|
|
61
82
|
rescue Interrupt
|
|
@@ -73,15 +94,24 @@ module Aidp
|
|
|
73
94
|
end
|
|
74
95
|
|
|
75
96
|
def process_plan_triggers
|
|
76
|
-
|
|
97
|
+
plan_label = @plan_processor.plan_label
|
|
98
|
+
issues = @repository_client.list_issues(labels: [plan_label], state: "open")
|
|
99
|
+
Aidp.log_debug("watch_runner", "plan_poll", label: plan_label, total: issues.size)
|
|
77
100
|
issues.each do |issue|
|
|
78
|
-
|
|
101
|
+
unless issue_has_label?(issue, plan_label)
|
|
102
|
+
Aidp.log_debug("watch_runner", "plan_skip_label_mismatch", issue: issue[:number], labels: issue[:labels])
|
|
103
|
+
next
|
|
104
|
+
end
|
|
79
105
|
|
|
80
106
|
detailed = @repository_client.fetch_issue(issue[:number])
|
|
81
107
|
|
|
82
108
|
# Check author authorization before processing
|
|
83
|
-
|
|
109
|
+
unless @safety_checker.should_process_issue?(detailed, enforce: false)
|
|
110
|
+
Aidp.log_debug("watch_runner", "plan_skip_unauthorized_author", issue: detailed[:number], author: detailed[:author])
|
|
111
|
+
next
|
|
112
|
+
end
|
|
84
113
|
|
|
114
|
+
Aidp.log_debug("watch_runner", "plan_process", issue: detailed[:number])
|
|
85
115
|
@plan_processor.process(detailed)
|
|
86
116
|
rescue RepositorySafetyChecker::UnauthorizedAuthorError => e
|
|
87
117
|
Aidp.log_warn("watch_runner", "unauthorized issue author", issue: issue[:number], error: e.message)
|
|
@@ -89,18 +119,30 @@ module Aidp
|
|
|
89
119
|
end
|
|
90
120
|
|
|
91
121
|
def process_build_triggers
|
|
92
|
-
|
|
122
|
+
build_label = @build_processor.build_label
|
|
123
|
+
issues = @repository_client.list_issues(labels: [build_label], state: "open")
|
|
124
|
+
Aidp.log_debug("watch_runner", "build_poll", label: build_label, total: issues.size)
|
|
93
125
|
issues.each do |issue|
|
|
94
|
-
|
|
126
|
+
unless issue_has_label?(issue, build_label)
|
|
127
|
+
Aidp.log_debug("watch_runner", "build_skip_label_mismatch", issue: issue[:number], labels: issue[:labels])
|
|
128
|
+
next
|
|
129
|
+
end
|
|
95
130
|
|
|
96
131
|
status = @state_store.build_status(issue[:number])
|
|
97
|
-
|
|
132
|
+
if status["status"] == "completed"
|
|
133
|
+
Aidp.log_debug("watch_runner", "build_skip_completed", issue: issue[:number])
|
|
134
|
+
next
|
|
135
|
+
end
|
|
98
136
|
|
|
99
137
|
detailed = @repository_client.fetch_issue(issue[:number])
|
|
100
138
|
|
|
101
139
|
# Check author authorization before processing
|
|
102
|
-
|
|
140
|
+
unless @safety_checker.should_process_issue?(detailed, enforce: false)
|
|
141
|
+
Aidp.log_debug("watch_runner", "build_skip_unauthorized_author", issue: detailed[:number], author: detailed[:author])
|
|
142
|
+
next
|
|
143
|
+
end
|
|
103
144
|
|
|
145
|
+
Aidp.log_debug("watch_runner", "build_process", issue: detailed[:number])
|
|
104
146
|
@build_processor.process(detailed)
|
|
105
147
|
rescue RepositorySafetyChecker::UnauthorizedAuthorError => e
|
|
106
148
|
Aidp.log_warn("watch_runner", "unauthorized issue author", issue: issue[:number], error: e.message)
|
|
@@ -229,16 +229,34 @@ module Aidp
|
|
|
229
229
|
end
|
|
230
230
|
|
|
231
231
|
combined_prompt = "#{system_prompt}\n\n#{user_prompt}"
|
|
232
|
+
|
|
233
|
+
# Record timing for metrics
|
|
234
|
+
start_time = Time.now
|
|
232
235
|
result = provider.send_message(prompt: combined_prompt)
|
|
236
|
+
duration = Time.now - start_time
|
|
233
237
|
|
|
234
238
|
if result.nil? || result.empty?
|
|
239
|
+
# Record failed metric
|
|
240
|
+
@provider_manager.record_metrics(provider_name, success: false, duration: duration, error: StandardError.new("Empty response")) if @provider_manager.respond_to?(:record_metrics)
|
|
235
241
|
raise ConversationError, "Provider request failed: empty response"
|
|
236
242
|
end
|
|
237
243
|
|
|
244
|
+
# Record successful metric
|
|
245
|
+
@provider_manager.record_metrics(provider_name, success: true, duration: duration) if @provider_manager.respond_to?(:record_metrics)
|
|
246
|
+
|
|
238
247
|
result
|
|
239
248
|
rescue => e
|
|
240
249
|
message = e.message.to_s
|
|
241
250
|
|
|
251
|
+
# Record failed metric (if we haven't already from empty response check)
|
|
252
|
+
if defined?(start_time) && defined?(provider_name) && defined?(duration).nil?
|
|
253
|
+
duration = Time.now - start_time
|
|
254
|
+
@provider_manager.record_metrics(provider_name, success: false, duration: duration, error: e) if @provider_manager.respond_to?(:record_metrics)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Extract rate limit reset time from error message (especially Claude)
|
|
258
|
+
extract_and_save_rate_limit_info(provider_name, message) if defined?(provider_name)
|
|
259
|
+
|
|
242
260
|
# Classify error type for better handling
|
|
243
261
|
classified = if message =~ /resource[_ ]exhausted/i || message =~ /\[resource_exhausted\]/i
|
|
244
262
|
"resource_exhausted"
|
|
@@ -319,6 +337,41 @@ module Aidp
|
|
|
319
337
|
Aidp.logger.warn("guided_agent", "Failed verbose iteration emit", error: e.message)
|
|
320
338
|
end
|
|
321
339
|
|
|
340
|
+
# Extract rate limit reset time from error message and save to provider manager
|
|
341
|
+
def extract_and_save_rate_limit_info(provider_name, error_message)
|
|
342
|
+
return unless @provider_manager.respond_to?(:mark_rate_limited)
|
|
343
|
+
return unless error_message
|
|
344
|
+
|
|
345
|
+
# Claude error format: "please try again in 1m23s" or "retry after X seconds/minutes"
|
|
346
|
+
reset_time = nil
|
|
347
|
+
|
|
348
|
+
if error_message =~ /try again in (\d+)m(\d+)s/i
|
|
349
|
+
minutes = $1.to_i
|
|
350
|
+
seconds = $2.to_i
|
|
351
|
+
reset_time = Time.now + (minutes * 60) + seconds
|
|
352
|
+
elsif error_message =~ /try again in (\d+)s/i
|
|
353
|
+
seconds = $1.to_i
|
|
354
|
+
reset_time = Time.now + seconds
|
|
355
|
+
elsif error_message =~ /try again in (\d+)m/i
|
|
356
|
+
minutes = $1.to_i
|
|
357
|
+
reset_time = Time.now + (minutes * 60)
|
|
358
|
+
elsif error_message =~ /retry after (\d+) seconds?/i
|
|
359
|
+
seconds = $1.to_i
|
|
360
|
+
reset_time = Time.now + seconds
|
|
361
|
+
elsif error_message =~ /retry after (\d+) minutes?/i
|
|
362
|
+
minutes = $1.to_i
|
|
363
|
+
reset_time = Time.now + (minutes * 60)
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
# Mark provider as rate limited with extracted reset time
|
|
367
|
+
if reset_time
|
|
368
|
+
@provider_manager.mark_rate_limited(provider_name, reset_time)
|
|
369
|
+
Aidp.logger.info("guided_agent", "Extracted rate limit reset time", provider: provider_name, reset_at: reset_time.iso8601)
|
|
370
|
+
end
|
|
371
|
+
rescue => e
|
|
372
|
+
Aidp.logger.warn("guided_agent", "Failed to extract rate limit info", error: e.message)
|
|
373
|
+
end
|
|
374
|
+
|
|
322
375
|
def validate_provider_configuration!
|
|
323
376
|
configured = @provider_manager.configured_providers
|
|
324
377
|
if configured.nil? || configured.empty?
|
data/lib/aidp/worktree.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "fileutils"
|
|
4
4
|
require "json"
|
|
5
|
+
require "open3"
|
|
5
6
|
require_relative "workstream_state"
|
|
6
7
|
|
|
7
8
|
module Aidp
|
|
@@ -32,16 +33,14 @@ module Aidp
|
|
|
32
33
|
raise WorktreeExists, "Worktree already exists at #{worktree_path}"
|
|
33
34
|
end
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
end
|
|
44
|
-
end
|
|
36
|
+
branch_exists = branch_exists?(project_dir, branch)
|
|
37
|
+
run_worktree_add!(
|
|
38
|
+
project_dir: project_dir,
|
|
39
|
+
branch: branch,
|
|
40
|
+
branch_exists: branch_exists,
|
|
41
|
+
worktree_path: worktree_path,
|
|
42
|
+
base_branch: base_branch
|
|
43
|
+
)
|
|
45
44
|
|
|
46
45
|
# Initialize .aidp directory in the worktree
|
|
47
46
|
ensure_aidp_dir(worktree_path)
|
|
@@ -203,6 +202,64 @@ module Aidp
|
|
|
203
202
|
def registry_file_path(project_dir)
|
|
204
203
|
File.join(project_dir, ".aidp", "worktrees.json")
|
|
205
204
|
end
|
|
205
|
+
|
|
206
|
+
def branch_exists?(project_dir, branch)
|
|
207
|
+
Dir.chdir(project_dir) do
|
|
208
|
+
system("git", "show-ref", "--verify", "--quiet", "refs/heads/#{branch}")
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def run_worktree_add!(project_dir:, branch:, branch_exists:, worktree_path:, base_branch:)
|
|
213
|
+
prune_attempted = false
|
|
214
|
+
|
|
215
|
+
loop do
|
|
216
|
+
cmd = build_worktree_command(branch_exists: branch_exists, branch: branch, worktree_path: worktree_path, base_branch: base_branch)
|
|
217
|
+
stdout, stderr, status = Dir.chdir(project_dir) { Open3.capture3(*cmd) }
|
|
218
|
+
|
|
219
|
+
return if status.success?
|
|
220
|
+
|
|
221
|
+
error_output = stderr.strip.empty? ? stdout.strip : stderr.strip
|
|
222
|
+
|
|
223
|
+
if !branch_exists && branch_already_exists?(error_output, branch)
|
|
224
|
+
Aidp.log_debug("worktree", "branch_exists_retry", branch: branch)
|
|
225
|
+
branch_exists = true
|
|
226
|
+
next
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
if !prune_attempted && missing_registered_worktree?(error_output)
|
|
230
|
+
Aidp.log_debug("worktree", "prune_missing_worktree", branch: branch, path: worktree_path)
|
|
231
|
+
Dir.chdir(project_dir) { Open3.capture3("git", "worktree", "prune") }
|
|
232
|
+
prune_attempted = true
|
|
233
|
+
next
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
raise Error, "Failed to create worktree (status=#{status.exitstatus}): #{error_output}"
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def build_worktree_command(branch_exists:, branch:, worktree_path:, base_branch:)
|
|
241
|
+
if branch_exists
|
|
242
|
+
["git", "worktree", "add", worktree_path, branch]
|
|
243
|
+
else
|
|
244
|
+
cmd = ["git", "worktree", "add", "-b", branch, worktree_path]
|
|
245
|
+
cmd << base_branch if base_branch
|
|
246
|
+
cmd
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def branch_already_exists?(error_output, branch)
|
|
251
|
+
return false if error_output.nil? || error_output.empty?
|
|
252
|
+
|
|
253
|
+
normalized = error_output.downcase
|
|
254
|
+
normalized.include?("branch '#{branch.downcase}' already exists") ||
|
|
255
|
+
normalized.include?("a branch named '#{branch.downcase}' already exists")
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def missing_registered_worktree?(error_output)
|
|
259
|
+
return false if error_output.nil? || error_output.empty?
|
|
260
|
+
|
|
261
|
+
error_output.downcase.include?("missing but already registered worktree")
|
|
262
|
+
end
|
|
206
263
|
end
|
|
207
264
|
end
|
|
208
265
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Decide the Next Work Loop Unit
|
|
2
|
+
|
|
3
|
+
You are operating inside the Aidp hybrid work loop. Review the recent deterministic outputs, consider the latest agent summary, and choose the next unit by emitting `NEXT_UNIT: <unit_name>` on its own line.
|
|
4
|
+
|
|
5
|
+
## Deterministic Outputs
|
|
6
|
+
|
|
7
|
+
{{DETERMINISTIC_OUTPUTS}}
|
|
8
|
+
|
|
9
|
+
## Previous Agent Summary
|
|
10
|
+
|
|
11
|
+
{{PREVIOUS_AGENT_SUMMARY}}
|
|
12
|
+
|
|
13
|
+
## Guidance
|
|
14
|
+
|
|
15
|
+
- Pick whichever unit will unblock progress the fastest. Options include deterministic unit names (`run_full_tests`, `run_lint`, etc.), `agentic` to resume coding, or `wait_for_github` if the system must await an external event.
|
|
16
|
+
- Cite concrete evidence from the outputs above when selecting a unit so future readers understand the decision.
|
|
17
|
+
- Keep the rationale tight—two or three sentences max.
|
|
18
|
+
|
|
19
|
+
## Rationale
|
|
20
|
+
|
|
21
|
+
Provide the reasoning for your `NEXT_UNIT` decision here.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Diagnose Recent Failures
|
|
2
|
+
|
|
3
|
+
You are operating inside the Aidp work loop. Analyze the most recent deterministic failures and decide how to unblock progress. Finish by emitting `NEXT_UNIT: <unit_name>` on its own line.
|
|
4
|
+
|
|
5
|
+
## Recent Deterministic Outputs
|
|
6
|
+
|
|
7
|
+
{{DETERMINISTIC_OUTPUTS}}
|
|
8
|
+
|
|
9
|
+
## Previous Agent Summary
|
|
10
|
+
|
|
11
|
+
{{PREVIOUS_AGENT_SUMMARY}}
|
|
12
|
+
|
|
13
|
+
## Instructions
|
|
14
|
+
|
|
15
|
+
- Identify the concrete failures and hypothesize root causes.
|
|
16
|
+
- Propose the next best action (run another deterministic unit, resume agentic editing, or wait for GitHub updates).
|
|
17
|
+
- Keep the analysis crisp—focus on evidence and required follow-up work.
|
|
18
|
+
|
|
19
|
+
## Analysis
|
|
20
|
+
|
|
21
|
+
Summarize key findings here, then state your `NEXT_UNIT` decision.
|
metadata
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: aidp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.24.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bart Agapinan
|
|
8
|
-
bindir:
|
|
8
|
+
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
@@ -231,7 +231,7 @@ extra_rdoc_files: []
|
|
|
231
231
|
files:
|
|
232
232
|
- LICENSE
|
|
233
233
|
- README.md
|
|
234
|
-
-
|
|
234
|
+
- exe/aidp
|
|
235
235
|
- lib/aidp.rb
|
|
236
236
|
- lib/aidp/analyze/error_handler.rb
|
|
237
237
|
- lib/aidp/analyze/feature_analyzer.rb
|
|
@@ -297,6 +297,7 @@ files:
|
|
|
297
297
|
- lib/aidp/harness/provider_factory.rb
|
|
298
298
|
- lib/aidp/harness/provider_info.rb
|
|
299
299
|
- lib/aidp/harness/provider_manager.rb
|
|
300
|
+
- lib/aidp/harness/provider_metrics.rb
|
|
300
301
|
- lib/aidp/harness/provider_type_checker.rb
|
|
301
302
|
- lib/aidp/harness/runner.rb
|
|
302
303
|
- lib/aidp/harness/simple_user_interface.rb
|
|
@@ -347,10 +348,13 @@ files:
|
|
|
347
348
|
- lib/aidp/prompt_optimization/style_guide_indexer.rb
|
|
348
349
|
- lib/aidp/prompt_optimization/template_indexer.rb
|
|
349
350
|
- lib/aidp/provider_manager.rb
|
|
351
|
+
- lib/aidp/providers/adapter.rb
|
|
350
352
|
- lib/aidp/providers/anthropic.rb
|
|
351
353
|
- lib/aidp/providers/base.rb
|
|
354
|
+
- lib/aidp/providers/capability_registry.rb
|
|
352
355
|
- lib/aidp/providers/codex.rb
|
|
353
356
|
- lib/aidp/providers/cursor.rb
|
|
357
|
+
- lib/aidp/providers/error_taxonomy.rb
|
|
354
358
|
- lib/aidp/providers/gemini.rb
|
|
355
359
|
- lib/aidp/providers/github_copilot.rb
|
|
356
360
|
- lib/aidp/providers/opencode.rb
|
|
@@ -360,6 +364,7 @@ files:
|
|
|
360
364
|
- lib/aidp/setup/devcontainer/generator.rb
|
|
361
365
|
- lib/aidp/setup/devcontainer/parser.rb
|
|
362
366
|
- lib/aidp/setup/devcontainer/port_manager.rb
|
|
367
|
+
- lib/aidp/setup/provider_registry.rb
|
|
363
368
|
- lib/aidp/setup/wizard.rb
|
|
364
369
|
- lib/aidp/skills.rb
|
|
365
370
|
- lib/aidp/skills/composer.rb
|
|
@@ -438,6 +443,8 @@ files:
|
|
|
438
443
|
- templates/skills/product_strategist/SKILL.md
|
|
439
444
|
- templates/skills/repository_analyst/SKILL.md
|
|
440
445
|
- templates/skills/test_analyzer/SKILL.md
|
|
446
|
+
- templates/work_loop/decide_whats_next.md
|
|
447
|
+
- templates/work_loop/diagnose_failures.md
|
|
441
448
|
homepage: https://github.com/viamin/aidp
|
|
442
449
|
licenses:
|
|
443
450
|
- MIT
|
/data/{bin → exe}/aidp
RENAMED
|
File without changes
|