aidp 0.7.0 → 0.8.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.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +60 -214
  3. data/bin/aidp +1 -1
  4. data/lib/aidp/analysis/kb_inspector.rb +38 -23
  5. data/lib/aidp/analysis/seams.rb +2 -31
  6. data/lib/aidp/analysis/tree_sitter_grammar_loader.rb +0 -13
  7. data/lib/aidp/analysis/tree_sitter_scan.rb +3 -20
  8. data/lib/aidp/analyze/error_handler.rb +2 -75
  9. data/lib/aidp/analyze/json_file_storage.rb +292 -0
  10. data/lib/aidp/analyze/progress.rb +12 -0
  11. data/lib/aidp/analyze/progress_visualizer.rb +12 -17
  12. data/lib/aidp/analyze/ruby_maat_integration.rb +13 -31
  13. data/lib/aidp/analyze/runner.rb +256 -87
  14. data/lib/aidp/cli/jobs_command.rb +100 -432
  15. data/lib/aidp/cli.rb +309 -239
  16. data/lib/aidp/config.rb +298 -10
  17. data/lib/aidp/debug_logger.rb +195 -0
  18. data/lib/aidp/debug_mixin.rb +187 -0
  19. data/lib/aidp/execute/progress.rb +9 -0
  20. data/lib/aidp/execute/runner.rb +221 -40
  21. data/lib/aidp/execute/steps.rb +17 -7
  22. data/lib/aidp/execute/workflow_selector.rb +211 -0
  23. data/lib/aidp/harness/completion_checker.rb +268 -0
  24. data/lib/aidp/harness/condition_detector.rb +1526 -0
  25. data/lib/aidp/harness/config_loader.rb +373 -0
  26. data/lib/aidp/harness/config_manager.rb +382 -0
  27. data/lib/aidp/harness/config_schema.rb +1006 -0
  28. data/lib/aidp/harness/config_validator.rb +355 -0
  29. data/lib/aidp/harness/configuration.rb +477 -0
  30. data/lib/aidp/harness/enhanced_runner.rb +494 -0
  31. data/lib/aidp/harness/error_handler.rb +616 -0
  32. data/lib/aidp/harness/provider_config.rb +423 -0
  33. data/lib/aidp/harness/provider_factory.rb +306 -0
  34. data/lib/aidp/harness/provider_manager.rb +1269 -0
  35. data/lib/aidp/harness/provider_type_checker.rb +88 -0
  36. data/lib/aidp/harness/runner.rb +411 -0
  37. data/lib/aidp/harness/state/errors.rb +28 -0
  38. data/lib/aidp/harness/state/metrics.rb +219 -0
  39. data/lib/aidp/harness/state/persistence.rb +128 -0
  40. data/lib/aidp/harness/state/provider_state.rb +132 -0
  41. data/lib/aidp/harness/state/ui_state.rb +68 -0
  42. data/lib/aidp/harness/state/workflow_state.rb +123 -0
  43. data/lib/aidp/harness/state_manager.rb +586 -0
  44. data/lib/aidp/harness/status_display.rb +888 -0
  45. data/lib/aidp/harness/ui/base.rb +16 -0
  46. data/lib/aidp/harness/ui/enhanced_tui.rb +545 -0
  47. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +252 -0
  48. data/lib/aidp/harness/ui/error_handler.rb +132 -0
  49. data/lib/aidp/harness/ui/frame_manager.rb +361 -0
  50. data/lib/aidp/harness/ui/job_monitor.rb +500 -0
  51. data/lib/aidp/harness/ui/navigation/main_menu.rb +311 -0
  52. data/lib/aidp/harness/ui/navigation/menu_formatter.rb +120 -0
  53. data/lib/aidp/harness/ui/navigation/menu_item.rb +142 -0
  54. data/lib/aidp/harness/ui/navigation/menu_state.rb +139 -0
  55. data/lib/aidp/harness/ui/navigation/submenu.rb +202 -0
  56. data/lib/aidp/harness/ui/navigation/workflow_selector.rb +176 -0
  57. data/lib/aidp/harness/ui/progress_display.rb +280 -0
  58. data/lib/aidp/harness/ui/question_collector.rb +141 -0
  59. data/lib/aidp/harness/ui/spinner_group.rb +184 -0
  60. data/lib/aidp/harness/ui/spinner_helper.rb +152 -0
  61. data/lib/aidp/harness/ui/status_manager.rb +312 -0
  62. data/lib/aidp/harness/ui/status_widget.rb +280 -0
  63. data/lib/aidp/harness/ui/workflow_controller.rb +312 -0
  64. data/lib/aidp/harness/user_interface.rb +2381 -0
  65. data/lib/aidp/provider_manager.rb +131 -7
  66. data/lib/aidp/providers/anthropic.rb +28 -103
  67. data/lib/aidp/providers/base.rb +170 -0
  68. data/lib/aidp/providers/cursor.rb +52 -181
  69. data/lib/aidp/providers/gemini.rb +24 -107
  70. data/lib/aidp/providers/macos_ui.rb +99 -5
  71. data/lib/aidp/providers/opencode.rb +194 -0
  72. data/lib/aidp/storage/csv_storage.rb +172 -0
  73. data/lib/aidp/storage/file_manager.rb +214 -0
  74. data/lib/aidp/storage/json_storage.rb +140 -0
  75. data/lib/aidp/version.rb +1 -1
  76. data/lib/aidp.rb +54 -39
  77. data/templates/COMMON/AGENT_BASE.md +11 -0
  78. data/templates/EXECUTE/00_PRD.md +4 -4
  79. data/templates/EXECUTE/02_ARCHITECTURE.md +5 -4
  80. data/templates/EXECUTE/07_TEST_PLAN.md +4 -1
  81. data/templates/EXECUTE/08_TASKS.md +4 -4
  82. data/templates/EXECUTE/10_IMPLEMENTATION_AGENT.md +4 -4
  83. data/templates/README.md +279 -0
  84. data/templates/aidp-development.yml.example +373 -0
  85. data/templates/aidp-minimal.yml.example +48 -0
  86. data/templates/aidp-production.yml.example +475 -0
  87. data/templates/aidp.yml.example +598 -0
  88. metadata +93 -69
  89. data/lib/aidp/analyze/agent_personas.rb +0 -71
  90. data/lib/aidp/analyze/agent_tool_executor.rb +0 -439
  91. data/lib/aidp/analyze/data_retention_manager.rb +0 -421
  92. data/lib/aidp/analyze/database.rb +0 -260
  93. data/lib/aidp/analyze/dependencies.rb +0 -335
  94. data/lib/aidp/analyze/export_manager.rb +0 -418
  95. data/lib/aidp/analyze/focus_guidance.rb +0 -517
  96. data/lib/aidp/analyze/incremental_analyzer.rb +0 -533
  97. data/lib/aidp/analyze/language_analysis_strategies.rb +0 -897
  98. data/lib/aidp/analyze/large_analysis_progress.rb +0 -499
  99. data/lib/aidp/analyze/memory_manager.rb +0 -339
  100. data/lib/aidp/analyze/metrics_storage.rb +0 -336
  101. data/lib/aidp/analyze/parallel_processor.rb +0 -454
  102. data/lib/aidp/analyze/performance_optimizer.rb +0 -691
  103. data/lib/aidp/analyze/repository_chunker.rb +0 -697
  104. data/lib/aidp/analyze/static_analysis_detector.rb +0 -577
  105. data/lib/aidp/analyze/storage.rb +0 -655
  106. data/lib/aidp/analyze/tool_configuration.rb +0 -441
  107. data/lib/aidp/analyze/tool_modernization.rb +0 -750
  108. data/lib/aidp/database/pg_adapter.rb +0 -148
  109. data/lib/aidp/database_config.rb +0 -69
  110. data/lib/aidp/database_connection.rb +0 -72
  111. data/lib/aidp/job_manager.rb +0 -41
  112. data/lib/aidp/jobs/base_job.rb +0 -45
  113. data/lib/aidp/jobs/provider_execution_job.rb +0 -83
  114. data/lib/aidp/project_detector.rb +0 -117
  115. data/lib/aidp/providers/agent_supervisor.rb +0 -348
  116. data/lib/aidp/providers/supervised_base.rb +0 -317
  117. data/lib/aidp/providers/supervised_cursor.rb +0 -22
  118. data/lib/aidp/sync.rb +0 -13
  119. data/lib/aidp/workspace.rb +0 -19
@@ -1,9 +1,138 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "harness/provider_factory"
4
+
3
5
  module Aidp
4
6
  class ProviderManager
5
7
  class << self
6
- def get_provider(provider_type)
8
+ def get_provider(provider_type, options = {})
9
+ # Use harness factory if available
10
+ if options[:use_harness] != false
11
+ factory = get_harness_factory
12
+ return factory.create_provider(provider_type, options) if factory
13
+ end
14
+
15
+ # Fallback to legacy method
16
+ create_legacy_provider(provider_type)
17
+ end
18
+
19
+ def load_from_config(config = {}, options = {})
20
+ provider_type = config["provider"] || "cursor"
21
+ get_provider(provider_type, options)
22
+ end
23
+
24
+ # Get harness factory instance
25
+ def get_harness_factory
26
+ @harness_factory ||= begin
27
+ require_relative "harness/config_manager"
28
+ Aidp::Harness::ProviderFactory.new
29
+ rescue LoadError
30
+ nil
31
+ end
32
+ end
33
+
34
+ # Create provider using harness configuration
35
+ def create_harness_provider(provider_name, options = {})
36
+ factory = get_harness_factory
37
+ raise "Harness factory not available" unless factory
38
+
39
+ factory.create_provider(provider_name, options)
40
+ end
41
+
42
+ # Get all configured providers
43
+ def get_all_providers(options = {})
44
+ factory = get_harness_factory
45
+ return [] unless factory
46
+
47
+ factory.create_all_providers(options)
48
+ end
49
+
50
+ # Get providers by priority
51
+ def get_providers_by_priority(options = {})
52
+ factory = get_harness_factory
53
+ return [] unless factory
54
+
55
+ factory.create_providers_by_priority(options)
56
+ end
57
+
58
+ # Get enabled providers
59
+ def get_enabled_providers(options = {})
60
+ factory = get_harness_factory
61
+ return [] unless factory
62
+
63
+ enabled_names = factory.get_enabled_providers(options)
64
+ factory.create_providers(enabled_names, options)
65
+ end
66
+
67
+ # Check if provider is configured
68
+ def provider_configured?(provider_name, options = {})
69
+ factory = get_harness_factory
70
+ return false unless factory
71
+
72
+ factory.get_configured_providers(options).include?(provider_name.to_s)
73
+ end
74
+
75
+ # Check if provider is enabled
76
+ def provider_enabled?(provider_name, options = {})
77
+ factory = get_harness_factory
78
+ return false unless factory
79
+
80
+ factory.get_enabled_providers(options).include?(provider_name.to_s)
81
+ end
82
+
83
+ # Get provider capabilities
84
+ def get_provider_capabilities(provider_name, options = {})
85
+ factory = get_harness_factory
86
+ return [] unless factory
87
+
88
+ factory.get_provider_capabilities(provider_name, options)
89
+ end
90
+
91
+ # Check if provider supports feature
92
+ def provider_supports_feature?(provider_name, feature, options = {})
93
+ factory = get_harness_factory
94
+ return false unless factory
95
+
96
+ factory.provider_supports_feature?(provider_name, feature, options)
97
+ end
98
+
99
+ # Get provider models
100
+ def get_provider_models(provider_name, options = {})
101
+ factory = get_harness_factory
102
+ return [] unless factory
103
+
104
+ factory.get_provider_models(provider_name, options)
105
+ end
106
+
107
+ # Validate provider configuration
108
+ def validate_provider_config(provider_name, options = {})
109
+ factory = get_harness_factory
110
+ return ["Harness factory not available"] unless factory
111
+
112
+ factory.validate_provider_config(provider_name, options)
113
+ end
114
+
115
+ # Validate all provider configurations
116
+ def validate_all_provider_configs(options = {})
117
+ factory = get_harness_factory
118
+ return {} unless factory
119
+
120
+ factory.validate_all_provider_configs(options)
121
+ end
122
+
123
+ # Clear provider cache
124
+ def clear_cache
125
+ @harness_factory&.clear_cache
126
+ end
127
+
128
+ # Reload configuration
129
+ def reload_config
130
+ @harness_factory&.reload_config
131
+ end
132
+
133
+ private
134
+
135
+ def create_legacy_provider(provider_type)
7
136
  case provider_type
8
137
  when "cursor"
9
138
  Aidp::Providers::Cursor.new
@@ -12,14 +141,9 @@ module Aidp
12
141
  when "gemini"
13
142
  Aidp::Providers::Gemini.new
14
143
  when "macos_ui"
15
- Aidp::Providers::MacosUI.new
144
+ Aidp::Providers::MacOSUI.new
16
145
  end
17
146
  end
18
-
19
- def load_from_config(config = {})
20
- provider_type = config["provider"] || "cursor"
21
- get_provider(provider_type)
22
- end
23
147
  end
24
148
  end
25
149
  end
@@ -1,111 +1,50 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "base"
4
+ require_relative "../debug_mixin"
4
5
 
5
6
  module Aidp
6
7
  module Providers
7
8
  class Anthropic < Base
9
+ include Aidp::DebugMixin
10
+
8
11
  def self.available?
9
12
  !!Aidp::Util.which("claude")
10
13
  end
11
14
 
12
- def name = "anthropic"
15
+ def name
16
+ "anthropic"
17
+ end
18
+
19
+ def available?
20
+ self.class.available?
21
+ end
13
22
 
14
23
  def send(prompt:, session: nil)
15
24
  raise "claude CLI not available" unless self.class.available?
16
25
 
17
- require "open3"
18
-
19
- # Use Claude CLI for non-interactive mode
20
- cmd = ["claude", "--print"]
21
-
22
- puts "📝 Sending prompt to claude..."
23
-
24
26
  # Smart timeout calculation
25
27
  timeout_seconds = calculate_timeout
26
28
 
27
- Open3.popen3(*cmd) do |stdin, stdout, stderr, wait|
28
- # Send the prompt to stdin
29
- stdin.puts prompt
30
- stdin.close
31
-
32
- # Start stuck detection thread
33
- stuck_detection_thread = Thread.new do
34
- loop do
35
- sleep 10 # Check every 10 seconds
36
-
37
- if stuck?
38
- puts "⚠️ claude appears stuck (no activity for #{stuck_timeout} seconds)"
39
- puts " You can:"
40
- puts " 1. Wait longer (press Enter)"
41
- puts " 2. Abort (Ctrl+C)"
42
-
43
- # Give user a chance to respond
44
- begin
45
- Timeout.timeout(30) do
46
- gets
47
- puts "🔄 Continuing to wait..."
48
- end
49
- rescue Timeout::Error
50
- puts "⏰ No response received, continuing to wait..."
51
- rescue Interrupt
52
- puts "🛑 Aborting claude..."
53
- Process.kill("TERM", wait.pid)
54
- raise Interrupt, "User aborted claude execution"
55
- end
56
- end
57
-
58
- # Stop checking if the process is done
59
- break if wait.value
60
- end
61
- end
29
+ debug_provider("claude", "Starting execution", {timeout: timeout_seconds})
30
+ debug_log("📝 Sending prompt to claude...", level: :info)
31
+
32
+ begin
33
+ # Use debug_execute_command for better debugging
34
+ result = debug_execute_command("claude", args: ["--print"], input: prompt, timeout: timeout_seconds)
62
35
 
63
- # Wait for completion with timeout
64
- begin
65
- Timeout.timeout(timeout_seconds) do
66
- result = wait.value
67
-
68
- # Stop stuck detection thread
69
- stuck_detection_thread&.kill
70
-
71
- if result.success?
72
- output = stdout.read
73
- puts "✅ Claude analysis completed"
74
- mark_completed
75
- return output.empty? ? :ok : output
76
- else
77
- error_output = stderr.read
78
- mark_failed("claude failed with exit code #{result.exitstatus}: #{error_output}")
79
- raise "claude failed with exit code #{result.exitstatus}: #{error_output}"
80
- end
81
- end
82
- rescue Timeout::Error
83
- # Stop stuck detection thread
84
- stuck_detection_thread&.kill
85
-
86
- # Kill the process if it's taking too long
87
- begin
88
- Process.kill("TERM", wait.pid)
89
- rescue Errno::ESRCH
90
- # Process already terminated
91
- end
92
-
93
- mark_failed("claude timed out after #{timeout_seconds} seconds")
94
- raise Timeout::Error, "claude timed out after #{timeout_seconds} seconds"
95
- rescue Interrupt
96
- # Stop stuck detection thread
97
- stuck_detection_thread&.kill
98
-
99
- # Kill the process
100
- begin
101
- Process.kill("TERM", wait.pid)
102
- rescue Errno::ESRCH
103
- # Process already terminated
104
- end
105
-
106
- mark_failed("claude execution was interrupted")
107
- raise
36
+ # Log the results
37
+ debug_command("claude", args: ["--print"], input: prompt, output: result.out, error: result.err, exit_code: result.exit_status)
38
+
39
+ if result.exit_status == 0
40
+ result.out
41
+ else
42
+ debug_error(StandardError.new("claude failed"), {exit_code: result.exit_status, stderr: result.err})
43
+ raise "claude failed with exit code #{result.exit_status}: #{result.err}"
108
44
  end
45
+ rescue => e
46
+ debug_error(e, {provider: "claude", prompt_length: prompt.length})
47
+ raise
109
48
  end
110
49
  end
111
50
 
@@ -140,21 +79,7 @@ module Aidp
140
79
  end
141
80
 
142
81
  def get_adaptive_timeout
143
- # Try to get timeout recommendations from metrics storage
144
- require_relative "../analyze/metrics_storage"
145
- storage = Aidp::Analyze::MetricsStorage.new(Dir.pwd)
146
- recommendations = storage.calculate_timeout_recommendations
147
-
148
- # Get current step name from environment or context
149
- step_name = ENV["AIDP_CURRENT_STEP"] || "unknown"
150
-
151
- if recommendations[step_name]
152
- recommended = recommendations[step_name][:recommended_timeout]
153
- # Add 20% buffer for safety
154
- return (recommended * 1.2).ceil
155
- end
156
-
157
- # Fallback timeouts based on step type patterns
82
+ # Timeout recommendations based on step type patterns
158
83
  step_name = ENV["AIDP_CURRENT_STEP"] || ""
159
84
 
160
85
  case step_name
@@ -27,6 +27,17 @@ module Aidp
27
27
  @output_count = 0
28
28
  @last_output_time = Time.now
29
29
  @job_context = nil
30
+ @harness_context = nil
31
+ @harness_metrics = {
32
+ total_requests: 0,
33
+ successful_requests: 0,
34
+ failed_requests: 0,
35
+ rate_limited_requests: 0,
36
+ total_tokens_used: 0,
37
+ total_cost: 0.0,
38
+ average_response_time: 0.0,
39
+ last_request_time: nil
40
+ }
30
41
  end
31
42
 
32
43
  def name
@@ -135,6 +146,141 @@ module Aidp
135
146
  # Get stuck timeout for this provider
136
147
  attr_reader :stuck_timeout
137
148
 
149
+ # Harness integration methods
150
+
151
+ # Set harness context for provider
152
+ def set_harness_context(harness_runner)
153
+ @harness_context = harness_runner
154
+ end
155
+
156
+ # Check if provider is operating in harness mode
157
+ def harness_mode?
158
+ !@harness_context.nil?
159
+ end
160
+
161
+ # Get harness metrics
162
+ def harness_metrics
163
+ @harness_metrics.dup
164
+ end
165
+
166
+ # Record harness request metrics
167
+ def record_harness_request(success:, tokens_used: 0, cost: 0.0, response_time: 0.0, rate_limited: false)
168
+ @harness_metrics[:total_requests] += 1
169
+ @harness_metrics[:last_request_time] = Time.now
170
+
171
+ if success
172
+ @harness_metrics[:successful_requests] += 1
173
+ else
174
+ @harness_metrics[:failed_requests] += 1
175
+ end
176
+
177
+ if rate_limited
178
+ @harness_metrics[:rate_limited_requests] += 1
179
+ end
180
+
181
+ @harness_metrics[:total_tokens_used] += tokens_used
182
+ @harness_metrics[:total_cost] += cost
183
+
184
+ # Update average response time
185
+ total_time = @harness_metrics[:average_response_time] * (@harness_metrics[:total_requests] - 1) + response_time
186
+ @harness_metrics[:average_response_time] = total_time / @harness_metrics[:total_requests]
187
+
188
+ # Notify harness context if available
189
+ @harness_context&.record_provider_metrics(name, @harness_metrics)
190
+ end
191
+
192
+ # Get provider health status for harness
193
+ def harness_health_status
194
+ {
195
+ provider: name,
196
+ activity_state: @activity_state,
197
+ stuck: stuck?,
198
+ success_rate: calculate_success_rate,
199
+ average_response_time: @harness_metrics[:average_response_time],
200
+ total_requests: @harness_metrics[:total_requests],
201
+ rate_limit_ratio: calculate_rate_limit_ratio,
202
+ last_activity: @last_activity_time,
203
+ health_score: calculate_health_score
204
+ }
205
+ end
206
+
207
+ # Check if provider is healthy for harness use
208
+ def harness_healthy?
209
+ return false if stuck?
210
+ return false if @harness_metrics[:total_requests] > 0 && calculate_success_rate < 0.5
211
+ return false if calculate_rate_limit_ratio > 0.3
212
+
213
+ true
214
+ end
215
+
216
+ # Get provider configuration for harness
217
+ def harness_config
218
+ {
219
+ name: name,
220
+ supports_activity_monitoring: supports_activity_monitoring?,
221
+ default_timeout: @stuck_timeout,
222
+ available: available?,
223
+ health_status: harness_health_status
224
+ }
225
+ end
226
+
227
+ # Check if provider is available (override in subclasses)
228
+ def available?
229
+ true # Default to true, override in subclasses
230
+ end
231
+
232
+ # Enhanced send method that integrates with harness
233
+ def send_with_harness(prompt:, session: nil, _options: {})
234
+ start_time = Time.now
235
+ success = false
236
+ rate_limited = false
237
+ tokens_used = 0
238
+ cost = 0.0
239
+ error_message = nil
240
+
241
+ begin
242
+ # Call the original send method
243
+ result = send(prompt: prompt, session: session)
244
+ success = true
245
+
246
+ # Extract token usage and cost if available
247
+ if result.is_a?(Hash) && result[:token_usage]
248
+ tokens_used = result[:token_usage][:total] || 0
249
+ cost = result[:token_usage][:cost] || 0.0
250
+ end
251
+
252
+ # Check for rate limiting in result
253
+ if result.is_a?(Hash) && result[:rate_limited]
254
+ rate_limited = true
255
+ end
256
+
257
+ result
258
+ rescue => e
259
+ error_message = e.message
260
+
261
+ # Check if error is rate limiting
262
+ if e.message.match?(/rate.?limit/i) || e.message.match?(/quota/i)
263
+ rate_limited = true
264
+ end
265
+
266
+ raise e
267
+ ensure
268
+ response_time = Time.now - start_time
269
+ record_harness_request(
270
+ success: success,
271
+ tokens_used: tokens_used,
272
+ cost: cost,
273
+ response_time: response_time,
274
+ rate_limited: rate_limited
275
+ )
276
+
277
+ # Log to harness context if available
278
+ if @harness_context && error_message
279
+ @harness_context.record_provider_error(name, error_message, rate_limited)
280
+ end
281
+ end
282
+ end
283
+
138
284
  protected
139
285
 
140
286
  # Log message to job if in background mode
@@ -157,6 +303,30 @@ module Aidp
157
303
  metadata
158
304
  )
159
305
  end
306
+
307
+ # Calculate success rate for harness metrics
308
+ def calculate_success_rate
309
+ return 1.0 if @harness_metrics[:total_requests] == 0
310
+ @harness_metrics[:successful_requests].to_f / @harness_metrics[:total_requests]
311
+ end
312
+
313
+ # Calculate rate limit ratio for harness metrics
314
+ def calculate_rate_limit_ratio
315
+ return 0.0 if @harness_metrics[:total_requests] == 0
316
+ @harness_metrics[:rate_limited_requests].to_f / @harness_metrics[:total_requests]
317
+ end
318
+
319
+ # Calculate overall health score for harness
320
+ def calculate_health_score
321
+ return 100.0 if @harness_metrics[:total_requests] == 0
322
+
323
+ success_rate = calculate_success_rate
324
+ rate_limit_ratio = calculate_rate_limit_ratio
325
+ response_time_score = [100 - (@harness_metrics[:average_response_time] * 10), 0].max
326
+
327
+ # Weighted health score
328
+ (success_rate * 50) + ((1 - rate_limit_ratio) * 30) + (response_time_score * 0.2)
329
+ end
160
330
  end
161
331
  end
162
332
  end