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
@@ -0,0 +1,257 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aidp
4
+ module Setup
5
+ # Adapter that wraps in-memory configuration from the wizard
6
+ # to provide the same interface as Harness::Configuration.
7
+ #
8
+ # This enables AGD (AI-Generated Determinism) to work during
9
+ # the interactive configuration wizard before the config file
10
+ # is written to disk.
11
+ #
12
+ # @example Usage in wizard
13
+ # adapter = InMemoryConfigAdapter.new(@config, project_dir)
14
+ # factory = AIFilterFactory.new(adapter)
15
+ #
16
+ class InMemoryConfigAdapter
17
+ attr_reader :project_dir
18
+
19
+ # Initialize with wizard's in-memory config hash
20
+ #
21
+ # @param config [Hash] The wizard's @config hash (symbolized keys)
22
+ # @param project_dir [String] Project directory path
23
+ def initialize(config, project_dir)
24
+ @config = config || {}
25
+ @project_dir = project_dir
26
+
27
+ Aidp.log_debug("in_memory_config_adapter", "initialized",
28
+ providers: configured_providers,
29
+ default_provider: default_provider)
30
+ end
31
+
32
+ # Get harness-specific configuration
33
+ def harness_config
34
+ config = @config[:harness] || {}
35
+ Aidp.log_debug("in_memory_config_adapter", "harness_config_retrieved",
36
+ keys: config.keys)
37
+ config
38
+ end
39
+
40
+ # Get provider configuration
41
+ def provider_config(provider_name)
42
+ providers = @config[:providers] || {}
43
+ config = providers[provider_name.to_sym] || providers[provider_name.to_s] || {}
44
+ Aidp.log_debug("in_memory_config_adapter", "provider_config_retrieved",
45
+ provider: provider_name, found: !config.empty?)
46
+ config
47
+ end
48
+
49
+ # Get all configured providers
50
+ def configured_providers
51
+ providers = @config[:providers] || {}
52
+ providers.keys.map(&:to_s)
53
+ end
54
+
55
+ # Get default provider
56
+ def default_provider
57
+ harness_config[:default_provider]
58
+ end
59
+
60
+ # Get fallback providers
61
+ def fallback_providers
62
+ Array(harness_config[:fallback_providers])
63
+ end
64
+
65
+ # Get provider type (usage_based, subscription, passthrough)
66
+ def provider_type(provider_name)
67
+ provider_config(provider_name)[:type] || "unknown"
68
+ end
69
+
70
+ # Get model family for a provider
71
+ def model_family(provider_name)
72
+ provider_config(provider_name)[:model_family] || "auto"
73
+ end
74
+
75
+ # Get thinking tiers configuration for a provider
76
+ def provider_thinking_tiers(provider_name)
77
+ cfg = provider_config(provider_name)
78
+ cfg[:thinking_tiers] || cfg["thinking_tiers"] || {}
79
+ end
80
+
81
+ # Get models configured for a specific tier and provider
82
+ def models_for_tier(tier, provider_name)
83
+ unless provider_name
84
+ Aidp.log_debug("in_memory_config_adapter", "models_for_tier_skipped",
85
+ reason: "no_provider_name")
86
+ return []
87
+ end
88
+
89
+ tier_config = provider_thinking_tiers(provider_name)[tier] ||
90
+ provider_thinking_tiers(provider_name)[tier.to_sym]
91
+ unless tier_config
92
+ Aidp.log_debug("in_memory_config_adapter", "models_for_tier_not_found",
93
+ tier: tier, provider: provider_name)
94
+ return []
95
+ end
96
+
97
+ models = tier_config[:models] || tier_config["models"]
98
+ result = models ? Array(models).map(&:to_s).compact : []
99
+ Aidp.log_debug("in_memory_config_adapter", "models_for_tier_retrieved",
100
+ tier: tier, provider: provider_name, count: result.size)
101
+ result
102
+ end
103
+
104
+ # Get all configured tiers for a provider
105
+ def configured_tiers(provider_name)
106
+ provider_thinking_tiers(provider_name).keys.map(&:to_s)
107
+ end
108
+
109
+ # Check if provider switching for tier is allowed
110
+ def allow_provider_switch_for_tier?
111
+ thinking_config[:allow_provider_switch] != false
112
+ end
113
+
114
+ # Get default thinking tier
115
+ def default_tier
116
+ thinking_config[:default_tier] || "mini"
117
+ end
118
+
119
+ # Get maximum thinking tier
120
+ def max_tier
121
+ thinking_config[:max_tier] || "pro"
122
+ end
123
+
124
+ # Get thinking configuration
125
+ def thinking_config
126
+ @config[:thinking] || default_thinking_config
127
+ end
128
+
129
+ # Get tier override for a skill or template
130
+ def tier_override_for(key)
131
+ thinking_overrides[key] || thinking_overrides[key.to_sym]
132
+ end
133
+
134
+ # Get thinking tier overrides
135
+ def thinking_overrides
136
+ thinking_config[:overrides] || {}
137
+ end
138
+
139
+ # Get permission level for a tier
140
+ def permission_for_tier(tier)
141
+ permissions = thinking_config[:permissions_by_tier] || {}
142
+ permissions[tier] || permissions[tier.to_sym] || "tools"
143
+ end
144
+
145
+ # Get escalation configuration
146
+ def escalation_config
147
+ thinking_config[:escalation] || default_escalation_config
148
+ end
149
+
150
+ # Get fail attempts threshold for escalation
151
+ def escalation_fail_attempts
152
+ escalation_config[:on_fail_attempts] || 2
153
+ end
154
+
155
+ # Get complexity threshold configuration for escalation
156
+ def escalation_complexity_threshold
157
+ escalation_config[:on_complexity_threshold] || {}
158
+ end
159
+
160
+ # Issue #375: Get maximum tier for autonomous contexts (watch mode, work loops)
161
+ def autonomous_max_tier
162
+ thinking_config[:autonomous_max_tier] || default_thinking_config[:autonomous_max_tier]
163
+ end
164
+
165
+ # Issue #375: Get escalation configuration for autonomous contexts
166
+ def autonomous_escalation_config
167
+ thinking_config[:autonomous_escalation] || default_autonomous_escalation_config
168
+ end
169
+
170
+ # Issue #375: Minimum attempts per model before trying next model in tier
171
+ def min_attempts_per_model
172
+ autonomous_escalation_config[:min_attempts_per_model] || 2
173
+ end
174
+
175
+ # Issue #375: Minimum total attempts across all models before tier escalation
176
+ def min_total_attempts_before_escalation
177
+ autonomous_escalation_config[:min_total_attempts] || 10
178
+ end
179
+
180
+ # Issue #375: Whether to retry failed models after exhausting untested models
181
+ def retry_failed_models?
182
+ autonomous_escalation_config[:retry_failed_models] != false
183
+ end
184
+
185
+ # Get provider models configuration
186
+ def provider_models(provider_name)
187
+ provider_config(provider_name)[:models] || []
188
+ end
189
+
190
+ # Get default flags for a provider
191
+ def default_flags(provider_name)
192
+ provider_config(provider_name)[:default_flags] || []
193
+ end
194
+
195
+ # Get maximum tokens for a provider
196
+ def max_tokens(provider_name)
197
+ provider_config(provider_name)[:max_tokens]
198
+ end
199
+
200
+ # Check if provider is configured
201
+ def provider_configured?(provider_name)
202
+ result = configured_providers.include?(provider_name.to_s)
203
+ Aidp.log_debug("in_memory_config_adapter", "provider_configured_check",
204
+ provider: provider_name, configured: result)
205
+ result
206
+ end
207
+
208
+ # Get work loop configuration
209
+ def work_loop_config
210
+ harness_config[:work_loop] || {}
211
+ end
212
+
213
+ # Get output filtering configuration
214
+ def output_filtering_config
215
+ work_loop_config[:output_filtering] || {}
216
+ end
217
+
218
+ # Get filter definitions
219
+ def filter_definitions
220
+ output_filtering_config[:filter_definitions] || {}
221
+ end
222
+
223
+ # Get raw configuration
224
+ def raw_config
225
+ @config.dup
226
+ end
227
+
228
+ private
229
+
230
+ def default_thinking_config
231
+ {
232
+ default_tier: "mini",
233
+ max_tier: "pro",
234
+ autonomous_max_tier: "standard", # Issue #375: restrict autonomous operations
235
+ allow_provider_switch: true,
236
+ autonomous_escalation: default_autonomous_escalation_config
237
+ }
238
+ end
239
+
240
+ def default_escalation_config
241
+ {
242
+ on_fail_attempts: 2,
243
+ on_complexity_threshold: {}
244
+ }
245
+ end
246
+
247
+ # Issue #375: Default autonomous escalation configuration
248
+ def default_autonomous_escalation_config
249
+ {
250
+ min_attempts_per_model: 2,
251
+ min_total_attempts: 10,
252
+ retry_failed_models: true
253
+ }
254
+ end
255
+ end
256
+ end
257
+ end
@@ -0,0 +1,262 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aidp
4
+ module Setup
5
+ # ConfigManager-compatible wrapper for in-memory wizard configuration.
6
+ #
7
+ # This class provides the same interface as Harness::ConfigManager but
8
+ # reads from the wizard's in-memory @config hash instead of from disk.
9
+ # This enables ProviderFactory to work during the wizard before
10
+ # the config file is written.
11
+ #
12
+ # @example Usage
13
+ # config_manager = InMemoryConfigManager.new(@config, project_dir)
14
+ # factory = ProviderFactory.new(config_manager)
15
+ #
16
+ class InMemoryConfigManager
17
+ attr_reader :project_dir
18
+
19
+ # Initialize with wizard's in-memory config hash
20
+ #
21
+ # @param config [Hash] The wizard's @config hash (symbolized keys)
22
+ # @param project_dir [String] Project directory path
23
+ def initialize(config, project_dir)
24
+ @config = config || {}
25
+ @project_dir = project_dir
26
+
27
+ Aidp.log_debug("in_memory_config_manager", "initialized",
28
+ providers: provider_names)
29
+ end
30
+
31
+ # Get complete configuration (returns a copy to prevent mutation)
32
+ def config(_options = {})
33
+ deep_dup(@config)
34
+ end
35
+
36
+ # Get harness configuration
37
+ def harness_config(_options = {})
38
+ @config[:harness] || {}
39
+ end
40
+
41
+ # Get provider configuration
42
+ def provider_config(provider_name, _options = {})
43
+ providers = @config[:providers] || {}
44
+ config = providers[provider_name.to_sym] || providers[provider_name.to_s]
45
+ Aidp.log_debug("in_memory_config_manager", "provider_config_retrieved",
46
+ provider: provider_name, found: !config.nil?)
47
+ config
48
+ end
49
+
50
+ # Get all provider configurations
51
+ def all_providers(_options = {})
52
+ @config[:providers] || {}
53
+ end
54
+
55
+ # Get configured provider names
56
+ def provider_names(_options = {})
57
+ providers = all_providers
58
+ providers.keys.map(&:to_s)
59
+ end
60
+
61
+ # Get default provider
62
+ def default_provider(_options = {})
63
+ harness_config[:default_provider] || harness_config["default_provider"]
64
+ end
65
+
66
+ # Get fallback providers
67
+ def fallback_providers(_options = {})
68
+ fallbacks = harness_config[:fallback_providers] || harness_config["fallback_providers"] || []
69
+ configured = provider_names
70
+ fallbacks.select { |p| configured.include?(p.to_s) }
71
+ end
72
+
73
+ # Check if configuration is valid
74
+ def config_valid?
75
+ result = !!(default_provider && provider_config(default_provider))
76
+ Aidp.log_debug("in_memory_config_manager", "config_valid_check",
77
+ valid: result, default_provider: default_provider)
78
+ result
79
+ end
80
+
81
+ # Get validation errors (empty for in-memory config)
82
+ def validation_errors
83
+ []
84
+ end
85
+
86
+ # Get validation warnings (empty for in-memory config)
87
+ def validation_warnings
88
+ []
89
+ end
90
+
91
+ # Reload configuration (no-op for in-memory)
92
+ def reload_config
93
+ # No-op: in-memory config doesn't need reloading
94
+ end
95
+
96
+ # Get configuration summary
97
+ def config_summary
98
+ {
99
+ providers: provider_names.size,
100
+ default_provider: default_provider,
101
+ fallback_providers: fallback_providers.size
102
+ }
103
+ end
104
+
105
+ # Get provider type
106
+ def provider_type(provider_name, _options = {})
107
+ cfg = provider_config(provider_name)
108
+ return nil unless cfg
109
+ cfg[:type] || cfg["type"]
110
+ end
111
+
112
+ # Get provider models
113
+ def provider_models(provider_name, _options = {})
114
+ cfg = provider_config(provider_name)
115
+ return [] unless cfg
116
+ models = cfg[:models] || cfg["models"] || []
117
+ models.map(&:to_s)
118
+ end
119
+
120
+ # Get provider features
121
+ def provider_features(provider_name, _options = {})
122
+ cfg = provider_config(provider_name)
123
+ return {} unless cfg
124
+ features = cfg[:features] || cfg["features"] || {}
125
+ {
126
+ file_upload: features[:file_upload] == true,
127
+ code_generation: features[:code_generation] != false,
128
+ analysis: features[:analysis] != false,
129
+ vision: features[:vision] == true
130
+ }
131
+ end
132
+
133
+ # Check if provider supports feature
134
+ def provider_supports_feature?(provider_name, feature, options = {})
135
+ features = provider_features(provider_name, options)
136
+ result = features[feature.to_sym] == true
137
+ Aidp.log_debug("in_memory_config_manager", "feature_support_check",
138
+ provider: provider_name, feature: feature, supported: result)
139
+ result
140
+ end
141
+
142
+ # Get provider priority
143
+ def provider_priority(provider_name, _options = {})
144
+ cfg = provider_config(provider_name)
145
+ return 1 unless cfg
146
+ cfg[:priority] || cfg["priority"] || 1
147
+ end
148
+
149
+ # Get retry configuration
150
+ def retry_config(_options = {})
151
+ cfg = harness_config[:retry] || {}
152
+ {
153
+ enabled: cfg[:enabled] != false,
154
+ max_attempts: cfg[:max_attempts] || 3,
155
+ base_delay: cfg[:base_delay] || 1.0,
156
+ max_delay: cfg[:max_delay] || 60.0,
157
+ exponential_base: cfg[:exponential_base] || 2.0,
158
+ jitter: cfg[:jitter] != false
159
+ }
160
+ end
161
+
162
+ # Get circuit breaker configuration
163
+ def circuit_breaker_config(_options = {})
164
+ cfg = harness_config[:circuit_breaker] || {}
165
+ {
166
+ enabled: cfg[:enabled] != false,
167
+ failure_threshold: cfg[:failure_threshold] || 5,
168
+ timeout: cfg[:timeout] || 300,
169
+ half_open_max_calls: cfg[:half_open_max_calls] || 3
170
+ }
171
+ end
172
+
173
+ # Get rate limit configuration
174
+ def rate_limit_config(_options = {})
175
+ cfg = harness_config[:rate_limit] || {}
176
+ {
177
+ enabled: cfg[:enabled] != false,
178
+ default_reset_time: cfg[:default_reset_time] || 3600,
179
+ burst_limit: cfg[:burst_limit] || 10,
180
+ sustained_limit: cfg[:sustained_limit] || 5
181
+ }
182
+ end
183
+
184
+ # Get provider monitoring configuration
185
+ def provider_monitoring_config(provider_name, _options = {})
186
+ cfg = provider_config(provider_name)
187
+ return {} unless cfg
188
+ monitoring = cfg[:monitoring] || cfg["monitoring"] || {}
189
+ {
190
+ enabled: monitoring[:enabled] != false,
191
+ metrics_interval: monitoring[:metrics_interval] || 60
192
+ }
193
+ end
194
+
195
+ # Get provider model weights
196
+ def provider_model_weights(provider_name, _options = {})
197
+ cfg = provider_config(provider_name)
198
+ return {} unless cfg
199
+ weights = cfg[:model_weights] || cfg["model_weights"] || {}
200
+ weights.transform_values { |w| [w.to_i, 1].max }
201
+ end
202
+
203
+ # Get provider max tokens
204
+ def provider_max_tokens(provider_name, _options = {})
205
+ cfg = provider_config(provider_name)
206
+ return nil unless cfg
207
+ cfg[:max_tokens] || cfg["max_tokens"]
208
+ end
209
+
210
+ # Get provider default flags
211
+ def provider_default_flags(provider_name, _options = {})
212
+ cfg = provider_config(provider_name)
213
+ return [] unless cfg
214
+ flags = cfg[:default_flags] || cfg["default_flags"] || []
215
+ flags.map(&:to_s)
216
+ end
217
+
218
+ # Get provider auth configuration (redacts sensitive values)
219
+ def provider_auth_config(provider_name, _options = {})
220
+ cfg = provider_config(provider_name)
221
+ return {} unless cfg
222
+ auth = cfg[:auth] || cfg["auth"] || {}
223
+ {
224
+ # Only expose the env var name, not actual keys
225
+ api_key_env: auth[:api_key_env] || auth["api_key_env"],
226
+ # Redact actual API key - callers should use api_key_env
227
+ api_key: (auth[:api_key] || auth["api_key"]) ? "[REDACTED]" : nil
228
+ }
229
+ end
230
+
231
+ # Get provider endpoints
232
+ def provider_endpoints(provider_name, _options = {})
233
+ cfg = provider_config(provider_name)
234
+ return {} unless cfg
235
+ endpoints = cfg[:endpoints] || cfg["endpoints"] || {}
236
+ {
237
+ default: endpoints[:default] || endpoints["default"]
238
+ }
239
+ end
240
+
241
+ private
242
+
243
+ # Deep duplicate a hash/array structure to prevent mutation
244
+ def deep_dup(obj)
245
+ case obj
246
+ when Hash
247
+ obj.each_with_object({}) { |(k, v), h| h[k] = deep_dup(v) }
248
+ when Array
249
+ obj.map { |v| deep_dup(v) }
250
+ else
251
+ begin
252
+ obj.dup
253
+ rescue TypeError => e
254
+ Aidp.log_debug("in_memory_config_manager", "deep_dup_type_error",
255
+ object_class: obj.class.name, error: e.message)
256
+ obj
257
+ end
258
+ end
259
+ end
260
+ end
261
+ end
262
+ end