aidp 0.34.0 → 0.39.3
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 +14 -14
- data/lib/aidp/agent_harness_adapter.rb +309 -0
- data/lib/aidp/cli/issue_importer.rb +20 -6
- data/lib/aidp/cli/jobs_command.rb +1 -1
- data/lib/aidp/cli/models_command.rb +9 -16
- data/lib/aidp/cli/prompts_command.rb +253 -0
- data/lib/aidp/cli/security_command.rb +4 -10
- data/lib/aidp/cli/storage_command.rb +277 -0
- data/lib/aidp/cli/temporal_command.rb +460 -0
- data/lib/aidp/cli.rb +24 -2
- data/lib/aidp/config.rb +15 -0
- data/lib/aidp/config_paths.rb +12 -2
- data/lib/aidp/database/migrations.rb +114 -0
- data/lib/aidp/database/repositories/checkpoint_repository.rb +174 -0
- data/lib/aidp/database/repositories/deprecated_models_repository.rb +206 -0
- data/lib/aidp/database/repositories/evaluation_repository.rb +208 -0
- data/lib/aidp/database/repositories/harness_state_repository.rb +140 -0
- data/lib/aidp/database/repositories/job_repository.rb +278 -0
- data/lib/aidp/database/repositories/model_cache_repository.rb +194 -0
- data/lib/aidp/database/repositories/progress_repository.rb +210 -0
- data/lib/aidp/database/repositories/prompt_archive_repository.rb +195 -0
- data/lib/aidp/database/repositories/prompt_feedback_repository.rb +266 -0
- data/lib/aidp/database/repositories/provider_info_cache_repository.rb +208 -0
- data/lib/aidp/database/repositories/provider_metrics_repository.rb +245 -0
- data/lib/aidp/database/repositories/secrets_repository.rb +187 -0
- data/lib/aidp/database/repositories/task_repository.rb +231 -0
- data/lib/aidp/database/repositories/template_version_repository.rb +383 -0
- data/lib/aidp/database/repositories/watch_state_repository.rb +472 -0
- data/lib/aidp/database/repositories/workstream_repository.rb +310 -0
- data/lib/aidp/database/repositories/worktree_repository.rb +240 -0
- data/lib/aidp/database/repository.rb +166 -0
- data/lib/aidp/database/schema.rb +347 -0
- data/lib/aidp/database/storage_migrator.rb +693 -0
- data/lib/aidp/database.rb +139 -0
- data/lib/aidp/debug_mixin.rb +2 -2
- data/lib/aidp/execute/checkpoint.rb +26 -0
- data/lib/aidp/execute/checkpoint_display.rb +16 -0
- data/lib/aidp/execute/prompt_evaluator.rb +30 -57
- data/lib/aidp/execute/work_loop_runner.rb +294 -46
- data/lib/aidp/firewall/provider_requirements_collector.rb +7 -23
- data/lib/aidp/harness/agent_harness_provider_manager.rb +275 -0
- data/lib/aidp/harness/ai_decision_engine.rb +78 -296
- data/lib/aidp/harness/config_schema.rb +59 -0
- data/lib/aidp/harness/configuration.rb +229 -10
- data/lib/aidp/harness/deprecation_cache.rb +53 -11
- data/lib/aidp/harness/enhanced_runner.rb +1 -1
- data/lib/aidp/harness/error_handler.rb +11 -5
- data/lib/aidp/harness/provider_config.rb +36 -0
- data/lib/aidp/harness/provider_factory.rb +26 -24
- data/lib/aidp/harness/provider_info.rb +9 -5
- data/lib/aidp/harness/provider_manager.rb +2 -2
- data/lib/aidp/harness/provider_metrics.rb +103 -1
- data/lib/aidp/harness/rspec_command_optimizer.rb +166 -0
- data/lib/aidp/harness/ruby_llm_registry.rb +97 -8
- data/lib/aidp/harness/runner.rb +7 -2
- data/lib/aidp/harness/state_manager.rb +1 -1
- data/lib/aidp/harness/test_runner.rb +167 -11
- data/lib/aidp/harness/thinking_depth_manager.rb +564 -5
- data/lib/aidp/harness/ui.rb +11 -0
- data/lib/aidp/harness/usage_limit.rb +179 -0
- data/lib/aidp/harness/usage_limit_enforcer.rb +240 -0
- data/lib/aidp/harness/usage_limit_tracker.rb +308 -0
- data/lib/aidp/harness/usage_period.rb +234 -0
- data/lib/aidp/init/agent_instructions_generator.rb +317 -0
- data/lib/aidp/init/runner.rb +25 -4
- data/lib/aidp/interfaces/activity_monitor_interface.rb +234 -0
- data/lib/aidp/interfaces/binary_checker_interface.rb +232 -0
- data/lib/aidp/interfaces/command_executor_interface.rb +216 -0
- data/lib/aidp/interfaces/logger_interface.rb +150 -0
- data/lib/aidp/interfaces/ui_interface.rb +254 -0
- data/lib/aidp/logger.rb +25 -7
- data/lib/aidp/orchestration_adapter.rb +279 -0
- data/lib/aidp/pr_worktree_manager.rb +4 -15
- data/lib/aidp/prompts/feedback_collector.rb +198 -0
- data/lib/aidp/prompts/prompt_template_manager.rb +415 -0
- data/lib/aidp/prompts/template_evolver.rb +305 -0
- data/lib/aidp/prompts/template_version_manager.rb +325 -0
- data/lib/aidp/setup/in_memory_config_adapter.rb +257 -0
- data/lib/aidp/setup/in_memory_config_manager.rb +262 -0
- data/lib/aidp/setup/wizard.rb +616 -176
- data/lib/aidp/shell_executor.rb +58 -0
- data/lib/aidp/style_guide/selector.rb +56 -1
- data/lib/aidp/temporal/activities/analyze_issue_activity.rb +198 -0
- data/lib/aidp/temporal/activities/analyze_sub_task_activity.rb +163 -0
- data/lib/aidp/temporal/activities/base_activity.rb +78 -0
- data/lib/aidp/temporal/activities/create_plan_activity.rb +115 -0
- data/lib/aidp/temporal/activities/create_pr_activity.rb +193 -0
- data/lib/aidp/temporal/activities/create_prompt_activity.rb +118 -0
- data/lib/aidp/temporal/activities/diagnose_failure_activity.rb +228 -0
- data/lib/aidp/temporal/activities/prepare_next_iteration_activity.rb +147 -0
- data/lib/aidp/temporal/activities/record_checkpoint_activity.rb +81 -0
- data/lib/aidp/temporal/activities/run_agent_activity.rb +139 -0
- data/lib/aidp/temporal/activities/run_tests_activity.rb +114 -0
- data/lib/aidp/temporal/activities/run_work_loop_iteration_activity.rb +249 -0
- data/lib/aidp/temporal/configuration.rb +169 -0
- data/lib/aidp/temporal/connection.rb +93 -0
- data/lib/aidp/temporal/worker.rb +119 -0
- data/lib/aidp/temporal/workflow_client.rb +171 -0
- data/lib/aidp/temporal/workflows/base_workflow.rb +73 -0
- data/lib/aidp/temporal/workflows/issue_to_pr_workflow.rb +278 -0
- data/lib/aidp/temporal/workflows/sub_issue_workflow.rb +253 -0
- data/lib/aidp/temporal/workflows/work_loop_workflow.rb +321 -0
- data/lib/aidp/temporal.rb +120 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/base_processor.rb +35 -0
- data/lib/aidp/watch/build_processor.rb +132 -0
- data/lib/aidp/watch/github_state_extractor.rb +0 -8
- data/lib/aidp/watch/rebase_processor.rb +245 -0
- data/lib/aidp/watch/round_robin_scheduler.rb +150 -0
- data/lib/aidp/watch/runner.rb +366 -285
- data/lib/aidp/watch/state_store.rb +80 -0
- data/lib/aidp/watch/work_item.rb +96 -0
- data/lib/aidp/watch/worktree_cleanup_job.rb +226 -0
- data/lib/aidp/watch/worktree_reconciler.rb +643 -0
- data/lib/aidp/workflows/guided_agent.rb +1 -1
- data/lib/aidp/worktree.rb +62 -0
- data/templates/aidp.yml.example +70 -0
- metadata +118 -16
- data/lib/aidp/analyze/json_file_storage.rb +0 -292
- data/lib/aidp/execute/future_work_backlog.rb +0 -411
- data/lib/aidp/providers/adapter.rb +0 -239
- data/lib/aidp/providers/aider.rb +0 -264
- data/lib/aidp/providers/anthropic.rb +0 -614
- data/lib/aidp/providers/base.rb +0 -617
- data/lib/aidp/providers/capability_registry.rb +0 -204
- data/lib/aidp/providers/codex.rb +0 -267
- data/lib/aidp/providers/cursor.rb +0 -304
- data/lib/aidp/providers/error_taxonomy.rb +0 -195
- data/lib/aidp/providers/gemini.rb +0 -110
- data/lib/aidp/providers/github_copilot.rb +0 -227
- data/lib/aidp/providers/kilocode.rb +0 -157
- data/lib/aidp/providers/opencode.rb +0 -139
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aidp
|
|
4
|
+
module Interfaces
|
|
5
|
+
# UiInterface defines the contract for user interface operations.
|
|
6
|
+
# This interface abstracts TTY-based UI components for extraction
|
|
7
|
+
# into the agent-harness gem, allowing different UI implementations.
|
|
8
|
+
#
|
|
9
|
+
# The interface covers two main areas:
|
|
10
|
+
# 1. Message display - showing status messages to users
|
|
11
|
+
# 2. Spinner/progress - showing activity indicators
|
|
12
|
+
#
|
|
13
|
+
# @example Implementing the interface
|
|
14
|
+
# class MyUI
|
|
15
|
+
# include Aidp::Interfaces::UiInterface
|
|
16
|
+
#
|
|
17
|
+
# def say(message, type: :info)
|
|
18
|
+
# puts "[#{type.upcase}] #{message}"
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# def with_spinner(title:, &block)
|
|
22
|
+
# puts "Starting: #{title}"
|
|
23
|
+
# result = yield
|
|
24
|
+
# puts "Done: #{title}"
|
|
25
|
+
# result
|
|
26
|
+
# end
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# @example Using an injected UI
|
|
30
|
+
# class Provider
|
|
31
|
+
# def initialize(ui: NullUI.new)
|
|
32
|
+
# @ui = ui
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
# def process
|
|
36
|
+
# @ui.say("Starting process", type: :info)
|
|
37
|
+
# @ui.with_spinner(title: "Processing") do
|
|
38
|
+
# # do work
|
|
39
|
+
# end
|
|
40
|
+
# end
|
|
41
|
+
# end
|
|
42
|
+
#
|
|
43
|
+
module UiInterface
|
|
44
|
+
# Message types for display_message
|
|
45
|
+
MESSAGE_TYPES = [:info, :success, :warning, :error, :highlight, :muted].freeze
|
|
46
|
+
|
|
47
|
+
# Display a message to the user.
|
|
48
|
+
#
|
|
49
|
+
# @param message [String] the message to display
|
|
50
|
+
# @param type [Symbol] one of MESSAGE_TYPES (:info, :success, :warning, :error, :highlight, :muted)
|
|
51
|
+
# @return [void]
|
|
52
|
+
def say(message, type: :info)
|
|
53
|
+
raise NotImplementedError, "#{self.class} must implement #say"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Execute a block with a spinner indicator.
|
|
57
|
+
#
|
|
58
|
+
# @param title [String] the spinner title
|
|
59
|
+
# @yield the block to execute
|
|
60
|
+
# @return [Object] the result of the block
|
|
61
|
+
def with_spinner(title:)
|
|
62
|
+
raise NotImplementedError, "#{self.class} must implement #with_spinner"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Create a spinner for manual control.
|
|
66
|
+
# Returns an object that responds to :auto_spin, :success, :error, and :update_title
|
|
67
|
+
#
|
|
68
|
+
# @param title [String] initial spinner title
|
|
69
|
+
# @return [SpinnerInterface] a spinner object
|
|
70
|
+
def spinner(title:)
|
|
71
|
+
raise NotImplementedError, "#{self.class} must implement #spinner"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Check if we're in quiet mode (suppress non-critical messages).
|
|
75
|
+
#
|
|
76
|
+
# @return [Boolean] true if non-critical messages should be suppressed
|
|
77
|
+
def quiet?
|
|
78
|
+
false
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# SpinnerInterface defines the contract for spinner objects.
|
|
83
|
+
module SpinnerInterface
|
|
84
|
+
# Start the spinner animation.
|
|
85
|
+
# @return [void]
|
|
86
|
+
def auto_spin
|
|
87
|
+
raise NotImplementedError, "#{self.class} must implement #auto_spin"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Stop the spinner with a success indicator.
|
|
91
|
+
# @param message [String, nil] optional success message
|
|
92
|
+
# @return [void]
|
|
93
|
+
def success(message = nil)
|
|
94
|
+
raise NotImplementedError, "#{self.class} must implement #success"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Stop the spinner with an error indicator.
|
|
98
|
+
# @param message [String, nil] optional error message
|
|
99
|
+
# @return [void]
|
|
100
|
+
def error(message = nil)
|
|
101
|
+
raise NotImplementedError, "#{self.class} must implement #error"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Update the spinner title.
|
|
105
|
+
# @param title [String] new title
|
|
106
|
+
# @return [void]
|
|
107
|
+
def update_title(title)
|
|
108
|
+
raise NotImplementedError, "#{self.class} must implement #update_title"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# NullUI is a no-op implementation.
|
|
113
|
+
# Useful for testing or non-interactive environments.
|
|
114
|
+
#
|
|
115
|
+
class NullUI
|
|
116
|
+
include UiInterface
|
|
117
|
+
|
|
118
|
+
def say(message, type: :info)
|
|
119
|
+
# no-op
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def with_spinner(title:)
|
|
123
|
+
yield
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def spinner(title:)
|
|
127
|
+
NullSpinner.new
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def quiet?
|
|
131
|
+
true
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# NullSpinner is a no-op spinner implementation.
|
|
136
|
+
#
|
|
137
|
+
class NullSpinner
|
|
138
|
+
include SpinnerInterface
|
|
139
|
+
|
|
140
|
+
def auto_spin
|
|
141
|
+
# no-op
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def success(message = nil)
|
|
145
|
+
# no-op
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def error(message = nil)
|
|
149
|
+
# no-op
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def update_title(title)
|
|
153
|
+
# no-op
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# TtyUI wraps TTY::Prompt and TTY::Spinner for UI operations.
|
|
158
|
+
# This is the standard implementation used by AIDP.
|
|
159
|
+
#
|
|
160
|
+
# @example Creating a TtyUI
|
|
161
|
+
# ui = TtyUI.new
|
|
162
|
+
# ui.say("Hello", type: :success)
|
|
163
|
+
# ui.with_spinner(title: "Working") { sleep(1) }
|
|
164
|
+
#
|
|
165
|
+
class TtyUI
|
|
166
|
+
include UiInterface
|
|
167
|
+
|
|
168
|
+
COLOR_MAP = {
|
|
169
|
+
info: :blue,
|
|
170
|
+
success: :green,
|
|
171
|
+
warning: :yellow,
|
|
172
|
+
error: :red,
|
|
173
|
+
highlight: :cyan,
|
|
174
|
+
muted: :bright_black
|
|
175
|
+
}.freeze
|
|
176
|
+
|
|
177
|
+
CRITICAL_TYPES = [:error, :warning, :success].freeze
|
|
178
|
+
|
|
179
|
+
# @param prompt [TTY::Prompt, nil] optional pre-configured prompt
|
|
180
|
+
# @param quiet [Boolean] whether to suppress non-critical messages
|
|
181
|
+
def initialize(prompt: nil, quiet: false)
|
|
182
|
+
@prompt = prompt
|
|
183
|
+
@quiet = quiet
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def say(message, type: :info)
|
|
187
|
+
return if @quiet && !CRITICAL_TYPES.include?(type)
|
|
188
|
+
|
|
189
|
+
prompt.say(message.to_s, color: COLOR_MAP.fetch(type, :white))
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def with_spinner(title:)
|
|
193
|
+
require "tty-spinner"
|
|
194
|
+
spin = TTY::Spinner.new("[:spinner] #{title}", format: :dots, hide_cursor: true)
|
|
195
|
+
spin.auto_spin
|
|
196
|
+
|
|
197
|
+
begin
|
|
198
|
+
result = yield
|
|
199
|
+
spin.success("done")
|
|
200
|
+
result
|
|
201
|
+
rescue
|
|
202
|
+
spin.error("failed")
|
|
203
|
+
raise
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def spinner(title:)
|
|
208
|
+
require "tty-spinner"
|
|
209
|
+
TtySpinnerWrapper.new(title: title)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def quiet?
|
|
213
|
+
@quiet
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
private
|
|
217
|
+
|
|
218
|
+
def prompt
|
|
219
|
+
@prompt ||= begin
|
|
220
|
+
require "tty-prompt"
|
|
221
|
+
TTY::Prompt.new
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# TtySpinnerWrapper wraps TTY::Spinner to implement SpinnerInterface.
|
|
227
|
+
#
|
|
228
|
+
class TtySpinnerWrapper
|
|
229
|
+
include SpinnerInterface
|
|
230
|
+
|
|
231
|
+
def initialize(title:)
|
|
232
|
+
require "tty-spinner"
|
|
233
|
+
@spinner = TTY::Spinner.new("[:spinner] :title", format: :dots, hide_cursor: true)
|
|
234
|
+
@spinner.update(title: title)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def auto_spin
|
|
238
|
+
@spinner.auto_spin
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def success(message = nil)
|
|
242
|
+
@spinner.success(message || "done")
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def error(message = nil)
|
|
246
|
+
@spinner.error(message || "failed")
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def update_title(title)
|
|
250
|
+
@spinner.update(title: title)
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
data/lib/aidp/logger.rb
CHANGED
|
@@ -121,13 +121,7 @@ module Aidp
|
|
|
121
121
|
end
|
|
122
122
|
|
|
123
123
|
def debug_env_enabled?
|
|
124
|
-
|
|
125
|
-
return false if raw.nil?
|
|
126
|
-
|
|
127
|
-
normalized = raw.to_s.strip.downcase
|
|
128
|
-
return true if %w[true on yes debug].include?(normalized)
|
|
129
|
-
|
|
130
|
-
/\A\d+\z/.match?(normalized) ? normalized.to_i.positive? : false
|
|
124
|
+
Aidp.debug_env_enabled?
|
|
131
125
|
end
|
|
132
126
|
|
|
133
127
|
def should_log?(level)
|
|
@@ -320,6 +314,30 @@ module Aidp
|
|
|
320
314
|
@logger ||= Logger.new
|
|
321
315
|
end
|
|
322
316
|
|
|
317
|
+
def debug_env_value
|
|
318
|
+
ENV["AIDP_DEBUG"] || ENV["DEBUG"]
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def debug_env_enabled?
|
|
322
|
+
raw = debug_env_value
|
|
323
|
+
return false if raw.nil?
|
|
324
|
+
|
|
325
|
+
normalized = raw.to_s.strip.downcase
|
|
326
|
+
return true if %w[true on yes debug].include?(normalized)
|
|
327
|
+
|
|
328
|
+
/\A\d+\z/.match?(normalized) ? normalized.to_i.positive? : false
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def debug_env_level
|
|
332
|
+
raw = debug_env_value
|
|
333
|
+
return 0 if raw.nil?
|
|
334
|
+
|
|
335
|
+
normalized = raw.to_s.strip.downcase
|
|
336
|
+
return 1 if %w[true on yes debug].include?(normalized)
|
|
337
|
+
|
|
338
|
+
/\A\d+\z/.match?(normalized) ? normalized.to_i : 0
|
|
339
|
+
end
|
|
340
|
+
|
|
323
341
|
# Convenience logging methods
|
|
324
342
|
def log_info(component, message, **metadata)
|
|
325
343
|
logger.info(component, message, **metadata)
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aidp
|
|
4
|
+
# Adapter for orchestration execution
|
|
5
|
+
# Routes to Temporal-based or legacy orchestration based on configuration
|
|
6
|
+
#
|
|
7
|
+
# When Temporal is enabled:
|
|
8
|
+
# - Workflows execute through Temporal for durability and observability
|
|
9
|
+
# - Activities wrap existing AIDP functionality
|
|
10
|
+
# - State is persisted in Temporal's history
|
|
11
|
+
#
|
|
12
|
+
# When Temporal is disabled (legacy mode):
|
|
13
|
+
# - Falls back to existing AsyncWorkLoopRunner and BackgroundRunner
|
|
14
|
+
# - State is tracked in file-based storage
|
|
15
|
+
#
|
|
16
|
+
# Migration strategy:
|
|
17
|
+
# 1. Enable Temporal in aidp.yml
|
|
18
|
+
# 2. Start Temporal worker: `aidp temporal worker`
|
|
19
|
+
# 3. Use `aidp temporal start` for new workflows
|
|
20
|
+
# 4. Existing jobs continue until complete
|
|
21
|
+
class OrchestrationAdapter
|
|
22
|
+
include Aidp::MessageDisplay
|
|
23
|
+
|
|
24
|
+
def initialize(project_dir: Dir.pwd)
|
|
25
|
+
@project_dir = project_dir
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Check if Temporal orchestration is enabled
|
|
29
|
+
def temporal_enabled?
|
|
30
|
+
return @temporal_enabled if defined?(@temporal_enabled)
|
|
31
|
+
|
|
32
|
+
begin
|
|
33
|
+
require_relative "temporal"
|
|
34
|
+
@temporal_enabled = Aidp::Temporal.enabled?(@project_dir)
|
|
35
|
+
rescue LoadError
|
|
36
|
+
@temporal_enabled = false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
@temporal_enabled
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Start an issue-to-PR workflow
|
|
43
|
+
# Routes to Temporal or legacy based on configuration
|
|
44
|
+
def start_issue_workflow(issue_number, options = {})
|
|
45
|
+
if temporal_enabled?
|
|
46
|
+
start_temporal_issue_workflow(issue_number, options)
|
|
47
|
+
else
|
|
48
|
+
start_legacy_issue_workflow(issue_number, options)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Start a work loop
|
|
53
|
+
# Routes to Temporal or legacy based on configuration
|
|
54
|
+
def start_work_loop(step_name, step_spec, context = {}, options = {})
|
|
55
|
+
if temporal_enabled?
|
|
56
|
+
start_temporal_work_loop(step_name, step_spec, context, options)
|
|
57
|
+
else
|
|
58
|
+
start_legacy_work_loop(step_name, step_spec, context, options)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Get workflow status
|
|
63
|
+
def workflow_status(workflow_id)
|
|
64
|
+
if temporal_enabled?
|
|
65
|
+
temporal_workflow_status(workflow_id)
|
|
66
|
+
else
|
|
67
|
+
legacy_job_status(workflow_id)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Cancel a workflow
|
|
72
|
+
def cancel_workflow(workflow_id)
|
|
73
|
+
if temporal_enabled?
|
|
74
|
+
cancel_temporal_workflow(workflow_id)
|
|
75
|
+
else
|
|
76
|
+
cancel_legacy_job(workflow_id)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# List active workflows
|
|
81
|
+
def list_workflows
|
|
82
|
+
if temporal_enabled?
|
|
83
|
+
list_temporal_workflows
|
|
84
|
+
else
|
|
85
|
+
list_legacy_jobs
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
# Temporal-based implementations
|
|
92
|
+
def start_temporal_issue_workflow(issue_number, options)
|
|
93
|
+
Aidp.log_info("orchestration_adapter", "starting_temporal_issue_workflow",
|
|
94
|
+
issue_number: issue_number)
|
|
95
|
+
|
|
96
|
+
input = {
|
|
97
|
+
project_dir: @project_dir,
|
|
98
|
+
issue_number: issue_number.to_i,
|
|
99
|
+
max_iterations: options[:max_iterations] || 50,
|
|
100
|
+
options: options
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
handle = Aidp::Temporal.start_workflow(
|
|
104
|
+
Aidp::Temporal::Workflows::IssueToPrWorkflow,
|
|
105
|
+
input,
|
|
106
|
+
project_dir: @project_dir,
|
|
107
|
+
**extract_temporal_options(options)
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
{
|
|
111
|
+
type: :temporal,
|
|
112
|
+
workflow_id: handle.id,
|
|
113
|
+
issue_number: issue_number,
|
|
114
|
+
status: "started"
|
|
115
|
+
}
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def start_temporal_work_loop(step_name, step_spec, context, options)
|
|
119
|
+
Aidp.log_info("orchestration_adapter", "starting_temporal_work_loop",
|
|
120
|
+
step_name: step_name)
|
|
121
|
+
|
|
122
|
+
input = {
|
|
123
|
+
project_dir: @project_dir,
|
|
124
|
+
step_name: step_name,
|
|
125
|
+
step_spec: step_spec,
|
|
126
|
+
context: context,
|
|
127
|
+
max_iterations: options[:max_iterations] || 50
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
handle = Aidp::Temporal.start_workflow(
|
|
131
|
+
Aidp::Temporal::Workflows::WorkLoopWorkflow,
|
|
132
|
+
input,
|
|
133
|
+
project_dir: @project_dir,
|
|
134
|
+
**extract_temporal_options(options)
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
{
|
|
138
|
+
type: :temporal,
|
|
139
|
+
workflow_id: handle.id,
|
|
140
|
+
step_name: step_name,
|
|
141
|
+
status: "started"
|
|
142
|
+
}
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def temporal_workflow_status(workflow_id)
|
|
146
|
+
desc = Aidp::Temporal.workflow_status(workflow_id, project_dir: @project_dir)
|
|
147
|
+
|
|
148
|
+
{
|
|
149
|
+
type: :temporal,
|
|
150
|
+
workflow_id: workflow_id,
|
|
151
|
+
status: desc.status.to_s,
|
|
152
|
+
workflow_type: desc.workflow_type,
|
|
153
|
+
start_time: desc.start_time,
|
|
154
|
+
close_time: desc.close_time
|
|
155
|
+
}
|
|
156
|
+
rescue Temporalio::Error::RPCError => e
|
|
157
|
+
if e.code == Temporalio::Error::RPCError::Code::NOT_FOUND
|
|
158
|
+
{type: :temporal, workflow_id: workflow_id, status: "not_found"}
|
|
159
|
+
else
|
|
160
|
+
raise
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def cancel_temporal_workflow(workflow_id)
|
|
165
|
+
Aidp::Temporal.cancel_workflow(workflow_id, project_dir: @project_dir)
|
|
166
|
+
{type: :temporal, workflow_id: workflow_id, status: "canceled"}
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def list_temporal_workflows
|
|
170
|
+
client = Aidp::Temporal.workflow_client(@project_dir)
|
|
171
|
+
workflows = client.list_workflows
|
|
172
|
+
|
|
173
|
+
workflows.map do |wf|
|
|
174
|
+
{
|
|
175
|
+
type: :temporal,
|
|
176
|
+
workflow_id: wf.id,
|
|
177
|
+
workflow_type: wf.workflow_type,
|
|
178
|
+
status: wf.status.to_s,
|
|
179
|
+
start_time: wf.start_time
|
|
180
|
+
}
|
|
181
|
+
end
|
|
182
|
+
rescue => e
|
|
183
|
+
Aidp.log_error("orchestration_adapter", "list_temporal_failed", error: e.message)
|
|
184
|
+
[]
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def extract_temporal_options(options)
|
|
188
|
+
temporal_opts = {}
|
|
189
|
+
temporal_opts[:workflow_id] = options[:workflow_id] if options[:workflow_id]
|
|
190
|
+
temporal_opts[:task_queue] = options[:task_queue] if options[:task_queue]
|
|
191
|
+
temporal_opts
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Legacy implementations
|
|
195
|
+
def start_legacy_issue_workflow(issue_number, options)
|
|
196
|
+
Aidp.log_info("orchestration_adapter", "starting_legacy_issue_workflow",
|
|
197
|
+
issue_number: issue_number)
|
|
198
|
+
|
|
199
|
+
require_relative "jobs/background_runner"
|
|
200
|
+
|
|
201
|
+
runner = Aidp::Jobs::BackgroundRunner.new(@project_dir)
|
|
202
|
+
job_id = runner.start(:execute, options.merge(issue_number: issue_number))
|
|
203
|
+
|
|
204
|
+
{
|
|
205
|
+
type: :legacy,
|
|
206
|
+
job_id: job_id,
|
|
207
|
+
issue_number: issue_number,
|
|
208
|
+
status: "started"
|
|
209
|
+
}
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def start_legacy_work_loop(step_name, step_spec, context, options)
|
|
213
|
+
Aidp.log_info("orchestration_adapter", "starting_legacy_work_loop",
|
|
214
|
+
step_name: step_name)
|
|
215
|
+
|
|
216
|
+
require_relative "jobs/background_runner"
|
|
217
|
+
|
|
218
|
+
runner = Aidp::Jobs::BackgroundRunner.new(@project_dir)
|
|
219
|
+
job_id = runner.start(:execute, options.merge(
|
|
220
|
+
step_name: step_name,
|
|
221
|
+
step_spec: step_spec,
|
|
222
|
+
context: context
|
|
223
|
+
))
|
|
224
|
+
|
|
225
|
+
{
|
|
226
|
+
type: :legacy,
|
|
227
|
+
job_id: job_id,
|
|
228
|
+
step_name: step_name,
|
|
229
|
+
status: "started"
|
|
230
|
+
}
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def legacy_job_status(job_id)
|
|
234
|
+
require_relative "jobs/background_runner"
|
|
235
|
+
|
|
236
|
+
runner = Aidp::Jobs::BackgroundRunner.new(@project_dir)
|
|
237
|
+
status = runner.job_status(job_id)
|
|
238
|
+
|
|
239
|
+
return {type: :legacy, job_id: job_id, status: "not_found"} unless status
|
|
240
|
+
|
|
241
|
+
{
|
|
242
|
+
type: :legacy,
|
|
243
|
+
job_id: job_id,
|
|
244
|
+
status: status[:status],
|
|
245
|
+
running: status[:running],
|
|
246
|
+
started_at: status[:started_at],
|
|
247
|
+
completed_at: status[:completed_at]
|
|
248
|
+
}
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def cancel_legacy_job(job_id)
|
|
252
|
+
require_relative "jobs/background_runner"
|
|
253
|
+
|
|
254
|
+
runner = Aidp::Jobs::BackgroundRunner.new(@project_dir)
|
|
255
|
+
result = runner.stop_job(job_id)
|
|
256
|
+
|
|
257
|
+
{
|
|
258
|
+
type: :legacy,
|
|
259
|
+
job_id: job_id,
|
|
260
|
+
status: result[:success] ? "canceled" : "failed",
|
|
261
|
+
message: result[:message]
|
|
262
|
+
}
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def list_legacy_jobs
|
|
266
|
+
require_relative "jobs/background_runner"
|
|
267
|
+
|
|
268
|
+
runner = Aidp::Jobs::BackgroundRunner.new(@project_dir)
|
|
269
|
+
runner.list_jobs.map do |job|
|
|
270
|
+
{
|
|
271
|
+
type: :legacy,
|
|
272
|
+
job_id: job[:job_id],
|
|
273
|
+
status: job[:status],
|
|
274
|
+
started_at: job[:started_at]
|
|
275
|
+
}
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
@@ -3,17 +3,6 @@ require "fileutils"
|
|
|
3
3
|
require "shellwords"
|
|
4
4
|
|
|
5
5
|
module Aidp
|
|
6
|
-
# Simple shell command executor wrapper for testability
|
|
7
|
-
class ShellExecutor
|
|
8
|
-
def run(command)
|
|
9
|
-
`#{command}`
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def success?
|
|
13
|
-
$?.success?
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
|
|
17
6
|
# Manages worktrees specifically for Pull Request branches
|
|
18
7
|
class PRWorktreeManager
|
|
19
8
|
def initialize(base_repo_path: nil, project_dir: nil, worktree_registry_path: nil, shell_executor: nil)
|
|
@@ -172,7 +161,7 @@ module Aidp
|
|
|
172
161
|
]
|
|
173
162
|
|
|
174
163
|
fetch_commands.each do |fetch_cmd|
|
|
175
|
-
system(fetch_cmd)
|
|
164
|
+
@shell_executor.system(fetch_cmd)
|
|
176
165
|
branch_list_output = `git branch -a`.split("\n").map(&:strip)
|
|
177
166
|
base_branch_exists = branch_list_output.any? do |branch|
|
|
178
167
|
branch.end_with?("/#{base_branch}", "remotes/origin/#{base_branch}") ||
|
|
@@ -187,7 +176,7 @@ module Aidp
|
|
|
187
176
|
|
|
188
177
|
# Robust worktree creation with enhanced error handling and logging
|
|
189
178
|
worktree_create_command = "git worktree add #{Shellwords.escape(worktree_path)} -b #{Shellwords.escape(head_branch)} #{Shellwords.escape(base_branch)}"
|
|
190
|
-
unless system(worktree_create_command)
|
|
179
|
+
unless @shell_executor.system(worktree_create_command)
|
|
191
180
|
error_details = {
|
|
192
181
|
pr_number: pr_number,
|
|
193
182
|
base_branch: base_branch,
|
|
@@ -390,7 +379,7 @@ module Aidp
|
|
|
390
379
|
|
|
391
380
|
# Stage only successfully modified files
|
|
392
381
|
unless successful_files.empty?
|
|
393
|
-
system("git add #{successful_files.map { |f| Shellwords.escape(f) }.join(" ")}")
|
|
382
|
+
@shell_executor.system("git add #{successful_files.map { |f| Shellwords.escape(f) }.join(" ")}")
|
|
394
383
|
end
|
|
395
384
|
end
|
|
396
385
|
|
|
@@ -508,7 +497,7 @@ module Aidp
|
|
|
508
497
|
return false unless existing_worktree
|
|
509
498
|
|
|
510
499
|
# Remove git worktree
|
|
511
|
-
system("git worktree remove #{existing_worktree["path"]}") if File.exist?(existing_worktree["path"])
|
|
500
|
+
@shell_executor.system("git worktree remove #{existing_worktree["path"]}") if File.exist?(existing_worktree["path"])
|
|
512
501
|
|
|
513
502
|
# Remove from registry and save
|
|
514
503
|
@worktrees.delete(pr_number.to_s)
|