aidp 0.34.1 → 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 (132) 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/usage_limit.rb +179 -0
  61. data/lib/aidp/harness/usage_limit_enforcer.rb +240 -0
  62. data/lib/aidp/harness/usage_limit_tracker.rb +308 -0
  63. data/lib/aidp/harness/usage_period.rb +234 -0
  64. data/lib/aidp/init/agent_instructions_generator.rb +317 -0
  65. data/lib/aidp/init/runner.rb +25 -4
  66. data/lib/aidp/interfaces/activity_monitor_interface.rb +234 -0
  67. data/lib/aidp/interfaces/binary_checker_interface.rb +232 -0
  68. data/lib/aidp/interfaces/command_executor_interface.rb +216 -0
  69. data/lib/aidp/interfaces/logger_interface.rb +150 -0
  70. data/lib/aidp/interfaces/ui_interface.rb +254 -0
  71. data/lib/aidp/logger.rb +25 -7
  72. data/lib/aidp/orchestration_adapter.rb +279 -0
  73. data/lib/aidp/pr_worktree_manager.rb +4 -15
  74. data/lib/aidp/prompts/feedback_collector.rb +198 -0
  75. data/lib/aidp/prompts/prompt_template_manager.rb +415 -0
  76. data/lib/aidp/prompts/template_evolver.rb +305 -0
  77. data/lib/aidp/prompts/template_version_manager.rb +325 -0
  78. data/lib/aidp/setup/in_memory_config_adapter.rb +257 -0
  79. data/lib/aidp/setup/in_memory_config_manager.rb +262 -0
  80. data/lib/aidp/setup/wizard.rb +616 -176
  81. data/lib/aidp/shell_executor.rb +58 -0
  82. data/lib/aidp/style_guide/selector.rb +56 -1
  83. data/lib/aidp/temporal/activities/analyze_issue_activity.rb +198 -0
  84. data/lib/aidp/temporal/activities/analyze_sub_task_activity.rb +163 -0
  85. data/lib/aidp/temporal/activities/base_activity.rb +78 -0
  86. data/lib/aidp/temporal/activities/create_plan_activity.rb +115 -0
  87. data/lib/aidp/temporal/activities/create_pr_activity.rb +193 -0
  88. data/lib/aidp/temporal/activities/create_prompt_activity.rb +118 -0
  89. data/lib/aidp/temporal/activities/diagnose_failure_activity.rb +228 -0
  90. data/lib/aidp/temporal/activities/prepare_next_iteration_activity.rb +147 -0
  91. data/lib/aidp/temporal/activities/record_checkpoint_activity.rb +81 -0
  92. data/lib/aidp/temporal/activities/run_agent_activity.rb +139 -0
  93. data/lib/aidp/temporal/activities/run_tests_activity.rb +114 -0
  94. data/lib/aidp/temporal/activities/run_work_loop_iteration_activity.rb +249 -0
  95. data/lib/aidp/temporal/configuration.rb +169 -0
  96. data/lib/aidp/temporal/connection.rb +93 -0
  97. data/lib/aidp/temporal/worker.rb +119 -0
  98. data/lib/aidp/temporal/workflow_client.rb +171 -0
  99. data/lib/aidp/temporal/workflows/base_workflow.rb +73 -0
  100. data/lib/aidp/temporal/workflows/issue_to_pr_workflow.rb +278 -0
  101. data/lib/aidp/temporal/workflows/sub_issue_workflow.rb +253 -0
  102. data/lib/aidp/temporal/workflows/work_loop_workflow.rb +321 -0
  103. data/lib/aidp/temporal.rb +120 -0
  104. data/lib/aidp/version.rb +1 -1
  105. data/lib/aidp/watch/base_processor.rb +35 -0
  106. data/lib/aidp/watch/build_processor.rb +132 -0
  107. data/lib/aidp/watch/github_state_extractor.rb +0 -8
  108. data/lib/aidp/watch/rebase_processor.rb +245 -0
  109. data/lib/aidp/watch/round_robin_scheduler.rb +150 -0
  110. data/lib/aidp/watch/runner.rb +366 -285
  111. data/lib/aidp/watch/state_store.rb +80 -0
  112. data/lib/aidp/watch/work_item.rb +96 -0
  113. data/lib/aidp/watch/worktree_cleanup_job.rb +226 -0
  114. data/lib/aidp/watch/worktree_reconciler.rb +643 -0
  115. data/lib/aidp/workflows/guided_agent.rb +1 -1
  116. data/lib/aidp/worktree.rb +62 -0
  117. data/templates/aidp.yml.example +70 -0
  118. metadata +117 -16
  119. data/lib/aidp/analyze/json_file_storage.rb +0 -292
  120. data/lib/aidp/execute/future_work_backlog.rb +0 -411
  121. data/lib/aidp/providers/adapter.rb +0 -239
  122. data/lib/aidp/providers/aider.rb +0 -264
  123. data/lib/aidp/providers/anthropic.rb +0 -614
  124. data/lib/aidp/providers/base.rb +0 -617
  125. data/lib/aidp/providers/capability_registry.rb +0 -204
  126. data/lib/aidp/providers/codex.rb +0 -267
  127. data/lib/aidp/providers/cursor.rb +0 -304
  128. data/lib/aidp/providers/error_taxonomy.rb +0 -195
  129. data/lib/aidp/providers/gemini.rb +0 -110
  130. data/lib/aidp/providers/github_copilot.rb +0 -227
  131. data/lib/aidp/providers/kilocode.rb +0 -157
  132. data/lib/aidp/providers/opencode.rb +0 -139
@@ -1,617 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "tty-prompt"
4
- require "tty-spinner"
5
- require_relative "adapter"
6
-
7
- module Aidp
8
- module Providers
9
- class ProviderUnavailableError < StandardError; end
10
-
11
- class Base
12
- include Aidp::MessageDisplay
13
- include Aidp::Providers::Adapter
14
-
15
- # Activity indicator states
16
- ACTIVITY_STATES = {
17
- idle: "⏳",
18
- working: "🔄",
19
- stuck: "⚠️",
20
- completed: "✅",
21
- failed: "❌"
22
- }.freeze
23
-
24
- # Default timeout for stuck detection (2 minutes)
25
- DEFAULT_STUCK_TIMEOUT = 120
26
-
27
- # Configurable timeout values (can be overridden via environment or config)
28
- # These defaults provide reasonable values for different execution scenarios
29
- TIMEOUT_QUICK_MODE = 120 # 2 minutes - for quick testing
30
- TIMEOUT_DEFAULT = 300 # 5 minutes - standard interactive timeout
31
- TIMEOUT_REPOSITORY_ANALYSIS = 180 # 3 minutes - repository analysis
32
- TIMEOUT_ARCHITECTURE_ANALYSIS = 600 # 10 minutes - architecture analysis
33
- TIMEOUT_TEST_ANALYSIS = 300 # 5 minutes - test analysis
34
- TIMEOUT_FUNCTIONALITY_ANALYSIS = 600 # 10 minutes - functionality analysis
35
- TIMEOUT_DOCUMENTATION_ANALYSIS = 300 # 5 minutes - documentation analysis
36
- TIMEOUT_STATIC_ANALYSIS = 450 # 7.5 minutes - static analysis
37
- TIMEOUT_REFACTORING_RECOMMENDATIONS = 600 # 10 minutes - refactoring
38
- TIMEOUT_IMPLEMENTATION = 900 # 15 minutes - implementation (write files, run tests, fix issues)
39
-
40
- # Tier-based timeout multipliers (applied on top of base timeouts)
41
- # Higher tiers need more time for deeper reasoning
42
- TIER_TIMEOUT_MULTIPLIERS = {
43
- "mini" => 1.0, # 5 minutes default (300s base)
44
- "standard" => 2.0, # 10 minutes default (600s base)
45
- "thinking" => 6.0, # 30 minutes default (1800s base)
46
- "pro" => 6.0, # 30 minutes default (1800s base)
47
- "max" => 12.0 # 60 minutes default (3600s base)
48
- }.freeze
49
-
50
- attr_reader :activity_state, :last_activity_time, :start_time, :step_name, :model
51
- # Expose for testability
52
- attr_writer :harness_context
53
-
54
- def initialize(output: nil, prompt: TTY::Prompt.new)
55
- @activity_state = :idle
56
- @last_activity_time = Time.now
57
- @start_time = nil
58
- @step_name = nil
59
- @activity_callback = nil
60
- @stuck_timeout = DEFAULT_STUCK_TIMEOUT
61
- @output_count = 0
62
- @last_output_time = Time.now
63
- @job_context = nil
64
- @harness_context = nil
65
- @output = output
66
- @prompt = prompt
67
- @model = nil
68
- @harness_metrics = {
69
- total_requests: 0,
70
- successful_requests: 0,
71
- failed_requests: 0,
72
- rate_limited_requests: 0,
73
- total_tokens_used: 0,
74
- total_cost: 0.0,
75
- average_response_time: 0.0,
76
- last_request_time: nil
77
- }
78
- end
79
-
80
- def name
81
- raise NotImplementedError, "#{self.class} must implement #name"
82
- end
83
-
84
- # Human-friendly display name for UI
85
- # Override in subclasses to provide a better display name
86
- def display_name
87
- name
88
- end
89
-
90
- # Configure the provider with options
91
- # @param config [Hash] Configuration options, may include :model
92
- def configure(config)
93
- if config[:model]
94
- @model = resolve_model_name(config[:model].to_s)
95
- end
96
- end
97
-
98
- def send_message(prompt:, session: nil, options: {})
99
- raise NotImplementedError, "#{self.class} must implement #send_message"
100
- end
101
-
102
- # Fetch MCP servers configured for this provider
103
- # Returns an array of server hashes with keys: :name, :status, :description, :enabled
104
- # Override in subclasses to provide provider-specific MCP server detection
105
- def fetch_mcp_servers
106
- []
107
- end
108
-
109
- # Check if this provider supports MCP servers
110
- # Override in subclasses to provide accurate MCP support detection
111
- def supports_mcp?
112
- false
113
- end
114
-
115
- # Set job context for background execution
116
- def set_job_context(job_id:, execution_id:, job_manager:)
117
- @job_context = {
118
- job_id: job_id,
119
- execution_id: execution_id,
120
- job_manager: job_manager
121
- }
122
- end
123
-
124
- # Set up activity monitoring for a step
125
- def setup_activity_monitoring(step_name, activity_callback = nil, stuck_timeout = nil)
126
- @step_name = step_name
127
- @activity_callback = activity_callback
128
- @stuck_timeout = stuck_timeout || DEFAULT_STUCK_TIMEOUT
129
- @start_time = Time.now
130
- @last_activity_time = @start_time
131
- @output_count = 0
132
- @last_output_time = @start_time
133
- update_activity_state(:working)
134
- end
135
-
136
- # Update activity state and notify callback
137
- def update_activity_state(state, message = nil)
138
- @activity_state = state
139
- @last_activity_time = Time.now if state == :working
140
-
141
- # Log state change to job if in background mode
142
- if @job_context
143
- level = case state
144
- when :completed then "info"
145
- when :failed then "error"
146
- else "debug"
147
- end
148
-
149
- log_to_job(message || "Provider state changed to #{state}", level)
150
- end
151
-
152
- @activity_callback&.call(state, message, self)
153
- end
154
-
155
- # Check if provider appears to be stuck
156
- def stuck?
157
- return false unless @activity_state == :working
158
-
159
- time_since_activity = Time.now - @last_activity_time
160
- time_since_activity > @stuck_timeout
161
- end
162
-
163
- # Get current execution time
164
- def execution_time
165
- return 0 unless @start_time
166
- Time.now - @start_time
167
- end
168
-
169
- # Get time since last activity
170
- def time_since_last_activity
171
- Time.now - @last_activity_time
172
- end
173
-
174
- # Record activity (called when provider produces output)
175
- def record_activity(message = nil)
176
- @output_count += 1
177
- @last_output_time = Time.now
178
- update_activity_state(:working, message)
179
- end
180
-
181
- # Mark as completed
182
- def mark_completed
183
- update_activity_state(:completed)
184
- end
185
-
186
- # Mark as failed
187
- def mark_failed(error_message = nil)
188
- update_activity_state(:failed, error_message)
189
- end
190
-
191
- # Get activity summary for metrics
192
- def activity_summary
193
- {
194
- provider: name,
195
- step_name: @step_name,
196
- start_time: @start_time&.iso8601,
197
- end_time: Time.now.iso8601,
198
- duration: execution_time,
199
- final_state: @activity_state,
200
- stuck_detected: stuck?,
201
- output_count: @output_count
202
- }
203
- end
204
-
205
- # Check if provider supports activity monitoring
206
- def supports_activity_monitoring?
207
- true # Default to true, override in subclasses if needed
208
- end
209
-
210
- # Get stuck timeout for this provider
211
- attr_reader :stuck_timeout
212
-
213
- # Harness integration methods
214
-
215
- # Set harness context for provider
216
- def set_harness_context(harness_runner)
217
- @harness_context = harness_runner
218
- end
219
-
220
- # Check if provider is operating in harness mode
221
- def harness_mode?
222
- !@harness_context.nil?
223
- end
224
-
225
- # Get harness metrics
226
- def harness_metrics
227
- @harness_metrics.dup
228
- end
229
-
230
- # Record harness request metrics
231
- def record_harness_request(success:, tokens_used: 0, cost: 0.0, response_time: 0.0, rate_limited: false)
232
- @harness_metrics[:total_requests] += 1
233
- @harness_metrics[:last_request_time] = Time.now
234
-
235
- if success
236
- @harness_metrics[:successful_requests] += 1
237
- else
238
- @harness_metrics[:failed_requests] += 1
239
- end
240
-
241
- if rate_limited
242
- @harness_metrics[:rate_limited_requests] += 1
243
- end
244
-
245
- @harness_metrics[:total_tokens_used] += tokens_used
246
- @harness_metrics[:total_cost] += cost
247
-
248
- # Update average response time
249
- total_time = @harness_metrics[:average_response_time] * (@harness_metrics[:total_requests] - 1) + response_time
250
- @harness_metrics[:average_response_time] = total_time / @harness_metrics[:total_requests]
251
-
252
- # Notify harness context if available
253
- @harness_context&.record_provider_metrics(name, @harness_metrics)
254
- end
255
-
256
- # Get provider health status for harness
257
- def harness_health_status
258
- {
259
- provider: name,
260
- activity_state: @activity_state,
261
- stuck: stuck?,
262
- success_rate: calculate_success_rate,
263
- average_response_time: @harness_metrics[:average_response_time],
264
- total_requests: @harness_metrics[:total_requests],
265
- rate_limit_ratio: calculate_rate_limit_ratio,
266
- last_activity: @last_activity_time,
267
- health_score: calculate_health_score
268
- }
269
- end
270
-
271
- # Check if provider is healthy for harness use
272
- def harness_healthy?
273
- return false if stuck?
274
- return false if @harness_metrics[:total_requests] > 0 && calculate_success_rate < 0.5
275
- return false if calculate_rate_limit_ratio > 0.3
276
-
277
- true
278
- end
279
-
280
- # Get provider configuration for harness
281
- def harness_config
282
- {
283
- name: name,
284
- supports_activity_monitoring: supports_activity_monitoring?,
285
- default_timeout: @stuck_timeout,
286
- available: available?,
287
- health_status: harness_health_status
288
- }
289
- end
290
-
291
- # Check if provider is available (override in subclasses)
292
- def available?
293
- true # Default to true, override in subclasses
294
- end
295
-
296
- # Enhanced send method that integrates with harness
297
- def send_with_harness(prompt:, session: nil, _options: {})
298
- start_time = Time.now
299
- success = false
300
- rate_limited = false
301
- tokens_used = 0
302
- cost = 0.0
303
- error_message = nil
304
-
305
- begin
306
- # Call the original send_message method
307
- result = send_message(prompt: prompt, session: session)
308
- success = true
309
-
310
- # Extract token usage and cost if available
311
- if result.is_a?(Hash) && result[:token_usage]
312
- tokens_used = result[:token_usage][:total] || 0
313
- cost = result[:token_usage][:cost] || 0.0
314
- end
315
-
316
- # Check for rate limiting in result
317
- if result.is_a?(Hash) && result[:rate_limited]
318
- rate_limited = true
319
- end
320
-
321
- result
322
- rescue => e
323
- error_message = e.message
324
-
325
- # Check if error is rate limiting
326
- if e.message.match?(/rate.?limit/i) || e.message.match?(/quota/i) || e.message.match?(/session limit/i)
327
- rate_limited = true
328
- end
329
-
330
- raise e
331
- ensure
332
- response_time = Time.now - start_time
333
- record_harness_request(
334
- success: success,
335
- tokens_used: tokens_used,
336
- cost: cost,
337
- response_time: response_time,
338
- rate_limited: rate_limited
339
- )
340
-
341
- # Log to harness context if available
342
- if @harness_context && error_message
343
- @harness_context.record_provider_error(name, error_message, rate_limited)
344
- end
345
- end
346
- end
347
-
348
- # Helper method for registry-based model discovery
349
- #
350
- # Providers that use the model registry can call this method to discover models
351
- # based on a model family pattern.
352
- #
353
- # @param model_pattern [Regexp] Pattern to match model families
354
- # @param provider_name [String] Name of the provider
355
- # @return [Array<Hash>] Array of discovered models
356
- def self.discover_models_from_registry(model_pattern, provider_name)
357
- require_relative "../harness/model_registry"
358
- registry = Aidp::Harness::ModelRegistry.new
359
-
360
- # Get all models from registry that match the pattern
361
- models = registry.all_families.filter_map do |family|
362
- next unless model_pattern.match?(family)
363
-
364
- info = registry.get_model_info(family)
365
- next unless info
366
-
367
- {
368
- name: family,
369
- family: family,
370
- tier: info["tier"],
371
- capabilities: info["capabilities"] || [],
372
- context_window: info["context_window"],
373
- provider: provider_name
374
- }
375
- end
376
-
377
- Aidp.log_info("#{provider_name}_provider", "using registry models", count: models.size)
378
- models
379
- rescue => e
380
- Aidp.log_debug("#{provider_name}_provider", "discovery failed", error: e.message)
381
- []
382
- end
383
-
384
- # Get firewall requirements for this provider
385
- #
386
- # Returns domains and IP ranges that need to be accessible for this provider
387
- # to function properly. Used by devcontainer firewall configuration.
388
- #
389
- # @return [Hash] Firewall requirements with :domains and :ip_ranges keys
390
- # - domains: Array of domain strings
391
- # - ip_ranges: Array of CIDR strings
392
- #
393
- # Override in subclasses to provide provider-specific requirements
394
- def self.firewall_requirements
395
- {
396
- domains: [],
397
- ip_ranges: []
398
- }
399
- end
400
-
401
- protected
402
-
403
- # Log message to job if in background mode
404
- def log_to_job(message, level = "info", metadata = {})
405
- return unless @job_context && @job_context[:job_manager]
406
-
407
- metadata = metadata.merge(
408
- provider: name,
409
- step_name: @step_name,
410
- activity_state: @activity_state,
411
- execution_time: execution_time,
412
- output_count: @output_count
413
- )
414
-
415
- @job_context[:job_manager].log_message(
416
- @job_context[:job_id],
417
- @job_context[:execution_id],
418
- message,
419
- level,
420
- metadata
421
- )
422
- end
423
-
424
- # Calculate success rate for harness metrics
425
- def calculate_success_rate
426
- return 1.0 if @harness_metrics[:total_requests] == 0
427
- @harness_metrics[:successful_requests].to_f / @harness_metrics[:total_requests]
428
- end
429
-
430
- # Calculate rate limit ratio for harness metrics
431
- def calculate_rate_limit_ratio
432
- return 0.0 if @harness_metrics[:total_requests] == 0
433
- @harness_metrics[:rate_limited_requests].to_f / @harness_metrics[:total_requests]
434
- end
435
-
436
- # Calculate overall health score for harness
437
- def calculate_health_score
438
- return 100.0 if @harness_metrics[:total_requests] == 0
439
-
440
- success_rate = calculate_success_rate
441
- rate_limit_ratio = calculate_rate_limit_ratio
442
- response_time_score = [100 - (@harness_metrics[:average_response_time] * 10), 0].max
443
-
444
- # Weighted health score
445
- (success_rate * 50) + ((1 - rate_limit_ratio) * 30) + (response_time_score * 0.2)
446
- end
447
-
448
- # Update spinner status with elapsed time
449
- # This is a shared method used by all providers to display progress
450
- def update_spinner_status(spinner, elapsed, provider_name)
451
- minutes = (elapsed / 60).to_i
452
- seconds = (elapsed % 60).to_i
453
-
454
- if minutes > 0
455
- spinner.update(title: "#{provider_name} is running... (#{minutes}m #{seconds}s)")
456
- else
457
- spinner.update(title: "#{provider_name} is running... (#{seconds}s)")
458
- end
459
- end
460
-
461
- # Clean up activity display thread and spinner
462
- # Used by providers to ensure proper cleanup in both success and error paths
463
- def cleanup_activity_display(activity_display_thread, spinner)
464
- activity_display_thread.kill if activity_display_thread&.alive?
465
- activity_display_thread&.join(0.1) # Give it 100ms to finish
466
- spinner&.stop
467
- end
468
-
469
- # Check if we should skip permissions based on devcontainer/codespace environment
470
- # This enables providers to run with elevated permissions in safe development environments
471
- # Returns true if running in a devcontainer or GitHub Codespace
472
- def in_devcontainer_or_codespace?
473
- ENV["REMOTE_CONTAINERS"] == "true" || ENV["CODESPACES"] == "true"
474
- end
475
-
476
- # Check if provider should skip sandbox permissions
477
- # Providers can override this to add additional logic beyond environment detection
478
- def should_skip_permissions?
479
- # First, check for devcontainer/codespace environment (most reliable)
480
- return true if in_devcontainer_or_codespace?
481
-
482
- # Fallback: Check if harness context is available and has configuration
483
- return false unless @harness_context
484
-
485
- # Get configuration from harness
486
- config = @harness_context.config
487
- return false unless config
488
-
489
- # Use configuration method to determine if full permissions should be used
490
- # Provider subclasses should pass their provider name
491
- false # Base implementation returns false, subclasses should override
492
- end
493
-
494
- # Calculate timeout for provider operations
495
- #
496
- # Priority order:
497
- # 1. Quick mode (for testing)
498
- # 2. Provider-specific environment variable override
499
- # 3. Adaptive timeout based on step type and thinking tier
500
- # 4. Default timeout (with tier multiplier)
501
- #
502
- # Override provider_env_var to customize the environment variable name
503
- # @param options [Hash] Options hash that may include :tier
504
- def calculate_timeout(options = {})
505
- if ENV["AIDP_QUICK_MODE"]
506
- display_message("⚡ Quick mode enabled - #{TIMEOUT_QUICK_MODE / 60} minute timeout", type: :highlight)
507
- return TIMEOUT_QUICK_MODE
508
- end
509
-
510
- provider_env_var = "AIDP_#{name.upcase}_TIMEOUT"
511
- return ENV[provider_env_var].to_i if ENV[provider_env_var]
512
-
513
- tier = options[:tier]&.to_s
514
- step_timeout = adaptive_timeout(tier)
515
- if step_timeout
516
- tier_label = tier ? " (tier: #{tier})" : ""
517
- display_message("🧠 Using adaptive timeout: #{step_timeout} seconds#{tier_label}", type: :info)
518
- return step_timeout
519
- end
520
-
521
- # Default timeout with tier multiplier
522
- base_timeout = TIMEOUT_DEFAULT
523
- final_timeout = apply_tier_multiplier(base_timeout, tier)
524
- tier_label = tier ? " (tier: #{tier})" : ""
525
- display_message("📋 Using default timeout: #{final_timeout / 60} minutes#{tier_label}", type: :info)
526
- final_timeout
527
- end
528
-
529
- # Get adaptive timeout based on step type and thinking tier
530
- #
531
- # This method returns different timeout values based on the type of operation
532
- # being performed, as indicated by the AIDP_CURRENT_STEP environment variable,
533
- # and applies a multiplier based on the thinking tier (mini/standard/thinking/pro/max).
534
- # Returns nil for unknown steps to allow calculate_timeout to use the default.
535
- #
536
- # @param tier [String, nil] The thinking tier (mini, standard, thinking, pro, max)
537
- def adaptive_timeout(tier = nil)
538
- # Don't cache - tier may change between calls
539
- step_name = ENV["AIDP_CURRENT_STEP"] || ""
540
-
541
- base_timeout = case step_name
542
- when /REPOSITORY_ANALYSIS/
543
- TIMEOUT_REPOSITORY_ANALYSIS
544
- when /ARCHITECTURE_ANALYSIS/
545
- TIMEOUT_ARCHITECTURE_ANALYSIS
546
- when /TEST_ANALYSIS/
547
- TIMEOUT_TEST_ANALYSIS
548
- when /FUNCTIONALITY_ANALYSIS/
549
- TIMEOUT_FUNCTIONALITY_ANALYSIS
550
- when /DOCUMENTATION_ANALYSIS/
551
- TIMEOUT_DOCUMENTATION_ANALYSIS
552
- when /STATIC_ANALYSIS/
553
- TIMEOUT_STATIC_ANALYSIS
554
- when /REFACTORING_RECOMMENDATIONS/
555
- TIMEOUT_REFACTORING_RECOMMENDATIONS
556
- when /IMPLEMENTATION/
557
- TIMEOUT_IMPLEMENTATION
558
- else
559
- nil # Return nil for unknown steps
560
- end
561
-
562
- return nil unless base_timeout
563
-
564
- apply_tier_multiplier(base_timeout, tier)
565
- end
566
-
567
- # Apply tier-based multiplier to a base timeout
568
- #
569
- # @param base_timeout [Integer] The base timeout in seconds
570
- # @param tier [String, nil] The thinking tier (mini, standard, thinking, pro, max)
571
- # @return [Integer] The adjusted timeout with tier multiplier applied
572
- def apply_tier_multiplier(base_timeout, tier)
573
- return base_timeout unless tier
574
-
575
- multiplier = TIER_TIMEOUT_MULTIPLIERS[tier.to_s] || 1.0
576
- (base_timeout * multiplier).to_i
577
- end
578
-
579
- # Resolve a model name using the RubyLLM registry
580
- #
581
- # Attempts to resolve a model family name (e.g., "claude-3-5-haiku") to a
582
- # versioned model name (e.g., "claude-3-5-haiku-20241022") using the RubyLLM
583
- # registry. Falls back to using the name as-is if resolution fails.
584
- #
585
- # @param model_name [String] The model family name or versioned name
586
- # @return [String] The resolved model name (versioned if found, original if not)
587
- def resolve_model_name(model_name)
588
- require_relative "../harness/ruby_llm_registry" unless defined?(Aidp::Harness::RubyLLMRegistry)
589
-
590
- begin
591
- registry = Aidp::Harness::RubyLLMRegistry.new
592
- resolved = registry.resolve_model(model_name, provider: name)
593
-
594
- if resolved
595
- Aidp.log_debug(name, "Resolved model using registry",
596
- requested: model_name,
597
- resolved: resolved)
598
- resolved
599
- else
600
- # Fall back to using the name as-is
601
- Aidp.log_warn(name, "Model not found in registry, using as-is",
602
- model: model_name)
603
- model_name
604
- end
605
- rescue => e
606
- # If registry fails, fall back to using the name as-is
607
- Aidp.log_error(name, "Registry lookup failed, using model name as-is",
608
- model: model_name,
609
- error: e.message)
610
- model_name
611
- end
612
- end
613
-
614
- private
615
- end
616
- end
617
- end