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.
Files changed (133) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -14
  3. data/lib/aidp/agent_harness_adapter.rb +309 -0
  4. data/lib/aidp/cli/issue_importer.rb +20 -6
  5. data/lib/aidp/cli/jobs_command.rb +1 -1
  6. data/lib/aidp/cli/models_command.rb +9 -16
  7. data/lib/aidp/cli/prompts_command.rb +253 -0
  8. data/lib/aidp/cli/security_command.rb +4 -10
  9. data/lib/aidp/cli/storage_command.rb +277 -0
  10. data/lib/aidp/cli/temporal_command.rb +460 -0
  11. data/lib/aidp/cli.rb +24 -2
  12. data/lib/aidp/config.rb +15 -0
  13. data/lib/aidp/config_paths.rb +12 -2
  14. data/lib/aidp/database/migrations.rb +114 -0
  15. data/lib/aidp/database/repositories/checkpoint_repository.rb +174 -0
  16. data/lib/aidp/database/repositories/deprecated_models_repository.rb +206 -0
  17. data/lib/aidp/database/repositories/evaluation_repository.rb +208 -0
  18. data/lib/aidp/database/repositories/harness_state_repository.rb +140 -0
  19. data/lib/aidp/database/repositories/job_repository.rb +278 -0
  20. data/lib/aidp/database/repositories/model_cache_repository.rb +194 -0
  21. data/lib/aidp/database/repositories/progress_repository.rb +210 -0
  22. data/lib/aidp/database/repositories/prompt_archive_repository.rb +195 -0
  23. data/lib/aidp/database/repositories/prompt_feedback_repository.rb +266 -0
  24. data/lib/aidp/database/repositories/provider_info_cache_repository.rb +208 -0
  25. data/lib/aidp/database/repositories/provider_metrics_repository.rb +245 -0
  26. data/lib/aidp/database/repositories/secrets_repository.rb +187 -0
  27. data/lib/aidp/database/repositories/task_repository.rb +231 -0
  28. data/lib/aidp/database/repositories/template_version_repository.rb +383 -0
  29. data/lib/aidp/database/repositories/watch_state_repository.rb +472 -0
  30. data/lib/aidp/database/repositories/workstream_repository.rb +310 -0
  31. data/lib/aidp/database/repositories/worktree_repository.rb +240 -0
  32. data/lib/aidp/database/repository.rb +166 -0
  33. data/lib/aidp/database/schema.rb +347 -0
  34. data/lib/aidp/database/storage_migrator.rb +693 -0
  35. data/lib/aidp/database.rb +139 -0
  36. data/lib/aidp/debug_mixin.rb +2 -2
  37. data/lib/aidp/execute/checkpoint.rb +26 -0
  38. data/lib/aidp/execute/checkpoint_display.rb +16 -0
  39. data/lib/aidp/execute/prompt_evaluator.rb +30 -57
  40. data/lib/aidp/execute/work_loop_runner.rb +294 -46
  41. data/lib/aidp/firewall/provider_requirements_collector.rb +7 -23
  42. data/lib/aidp/harness/agent_harness_provider_manager.rb +275 -0
  43. data/lib/aidp/harness/ai_decision_engine.rb +78 -296
  44. data/lib/aidp/harness/config_schema.rb +59 -0
  45. data/lib/aidp/harness/configuration.rb +229 -10
  46. data/lib/aidp/harness/deprecation_cache.rb +53 -11
  47. data/lib/aidp/harness/enhanced_runner.rb +1 -1
  48. data/lib/aidp/harness/error_handler.rb +11 -5
  49. data/lib/aidp/harness/provider_config.rb +36 -0
  50. data/lib/aidp/harness/provider_factory.rb +26 -24
  51. data/lib/aidp/harness/provider_info.rb +9 -5
  52. data/lib/aidp/harness/provider_manager.rb +2 -2
  53. data/lib/aidp/harness/provider_metrics.rb +103 -1
  54. data/lib/aidp/harness/rspec_command_optimizer.rb +166 -0
  55. data/lib/aidp/harness/ruby_llm_registry.rb +97 -8
  56. data/lib/aidp/harness/runner.rb +7 -2
  57. data/lib/aidp/harness/state_manager.rb +1 -1
  58. data/lib/aidp/harness/test_runner.rb +167 -11
  59. data/lib/aidp/harness/thinking_depth_manager.rb +564 -5
  60. data/lib/aidp/harness/ui.rb +11 -0
  61. data/lib/aidp/harness/usage_limit.rb +179 -0
  62. data/lib/aidp/harness/usage_limit_enforcer.rb +240 -0
  63. data/lib/aidp/harness/usage_limit_tracker.rb +308 -0
  64. data/lib/aidp/harness/usage_period.rb +234 -0
  65. data/lib/aidp/init/agent_instructions_generator.rb +317 -0
  66. data/lib/aidp/init/runner.rb +25 -4
  67. data/lib/aidp/interfaces/activity_monitor_interface.rb +234 -0
  68. data/lib/aidp/interfaces/binary_checker_interface.rb +232 -0
  69. data/lib/aidp/interfaces/command_executor_interface.rb +216 -0
  70. data/lib/aidp/interfaces/logger_interface.rb +150 -0
  71. data/lib/aidp/interfaces/ui_interface.rb +254 -0
  72. data/lib/aidp/logger.rb +25 -7
  73. data/lib/aidp/orchestration_adapter.rb +279 -0
  74. data/lib/aidp/pr_worktree_manager.rb +4 -15
  75. data/lib/aidp/prompts/feedback_collector.rb +198 -0
  76. data/lib/aidp/prompts/prompt_template_manager.rb +415 -0
  77. data/lib/aidp/prompts/template_evolver.rb +305 -0
  78. data/lib/aidp/prompts/template_version_manager.rb +325 -0
  79. data/lib/aidp/setup/in_memory_config_adapter.rb +257 -0
  80. data/lib/aidp/setup/in_memory_config_manager.rb +262 -0
  81. data/lib/aidp/setup/wizard.rb +616 -176
  82. data/lib/aidp/shell_executor.rb +58 -0
  83. data/lib/aidp/style_guide/selector.rb +56 -1
  84. data/lib/aidp/temporal/activities/analyze_issue_activity.rb +198 -0
  85. data/lib/aidp/temporal/activities/analyze_sub_task_activity.rb +163 -0
  86. data/lib/aidp/temporal/activities/base_activity.rb +78 -0
  87. data/lib/aidp/temporal/activities/create_plan_activity.rb +115 -0
  88. data/lib/aidp/temporal/activities/create_pr_activity.rb +193 -0
  89. data/lib/aidp/temporal/activities/create_prompt_activity.rb +118 -0
  90. data/lib/aidp/temporal/activities/diagnose_failure_activity.rb +228 -0
  91. data/lib/aidp/temporal/activities/prepare_next_iteration_activity.rb +147 -0
  92. data/lib/aidp/temporal/activities/record_checkpoint_activity.rb +81 -0
  93. data/lib/aidp/temporal/activities/run_agent_activity.rb +139 -0
  94. data/lib/aidp/temporal/activities/run_tests_activity.rb +114 -0
  95. data/lib/aidp/temporal/activities/run_work_loop_iteration_activity.rb +249 -0
  96. data/lib/aidp/temporal/configuration.rb +169 -0
  97. data/lib/aidp/temporal/connection.rb +93 -0
  98. data/lib/aidp/temporal/worker.rb +119 -0
  99. data/lib/aidp/temporal/workflow_client.rb +171 -0
  100. data/lib/aidp/temporal/workflows/base_workflow.rb +73 -0
  101. data/lib/aidp/temporal/workflows/issue_to_pr_workflow.rb +278 -0
  102. data/lib/aidp/temporal/workflows/sub_issue_workflow.rb +253 -0
  103. data/lib/aidp/temporal/workflows/work_loop_workflow.rb +321 -0
  104. data/lib/aidp/temporal.rb +120 -0
  105. data/lib/aidp/version.rb +1 -1
  106. data/lib/aidp/watch/base_processor.rb +35 -0
  107. data/lib/aidp/watch/build_processor.rb +132 -0
  108. data/lib/aidp/watch/github_state_extractor.rb +0 -8
  109. data/lib/aidp/watch/rebase_processor.rb +245 -0
  110. data/lib/aidp/watch/round_robin_scheduler.rb +150 -0
  111. data/lib/aidp/watch/runner.rb +366 -285
  112. data/lib/aidp/watch/state_store.rb +80 -0
  113. data/lib/aidp/watch/work_item.rb +96 -0
  114. data/lib/aidp/watch/worktree_cleanup_job.rb +226 -0
  115. data/lib/aidp/watch/worktree_reconciler.rb +643 -0
  116. data/lib/aidp/workflows/guided_agent.rb +1 -1
  117. data/lib/aidp/worktree.rb +62 -0
  118. data/templates/aidp.yml.example +70 -0
  119. metadata +118 -16
  120. data/lib/aidp/analyze/json_file_storage.rb +0 -292
  121. data/lib/aidp/execute/future_work_backlog.rb +0 -411
  122. data/lib/aidp/providers/adapter.rb +0 -239
  123. data/lib/aidp/providers/aider.rb +0 -264
  124. data/lib/aidp/providers/anthropic.rb +0 -614
  125. data/lib/aidp/providers/base.rb +0 -617
  126. data/lib/aidp/providers/capability_registry.rb +0 -204
  127. data/lib/aidp/providers/codex.rb +0 -267
  128. data/lib/aidp/providers/cursor.rb +0 -304
  129. data/lib/aidp/providers/error_taxonomy.rb +0 -195
  130. data/lib/aidp/providers/gemini.rb +0 -110
  131. data/lib/aidp/providers/github_copilot.rb +0 -227
  132. data/lib/aidp/providers/kilocode.rb +0 -157
  133. 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
- raw = ENV["AIDP_DEBUG"] || ENV["DEBUG"]
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)