aidp 0.17.1 → 0.18.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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +69 -0
  3. data/lib/aidp/cli.rb +43 -2
  4. data/lib/aidp/config.rb +9 -14
  5. data/lib/aidp/execute/prompt_manager.rb +128 -1
  6. data/lib/aidp/execute/repl_macros.rb +555 -0
  7. data/lib/aidp/execute/work_loop_runner.rb +108 -1
  8. data/lib/aidp/harness/ai_decision_engine.rb +376 -0
  9. data/lib/aidp/harness/capability_registry.rb +273 -0
  10. data/lib/aidp/harness/config_schema.rb +305 -1
  11. data/lib/aidp/harness/configuration.rb +452 -0
  12. data/lib/aidp/harness/enhanced_runner.rb +7 -1
  13. data/lib/aidp/harness/provider_factory.rb +0 -2
  14. data/lib/aidp/harness/runner.rb +7 -1
  15. data/lib/aidp/harness/thinking_depth_manager.rb +335 -0
  16. data/lib/aidp/harness/zfc_condition_detector.rb +395 -0
  17. data/lib/aidp/init/devcontainer_generator.rb +274 -0
  18. data/lib/aidp/init/runner.rb +37 -10
  19. data/lib/aidp/init.rb +1 -0
  20. data/lib/aidp/prompt_optimization/context_composer.rb +286 -0
  21. data/lib/aidp/prompt_optimization/optimizer.rb +335 -0
  22. data/lib/aidp/prompt_optimization/prompt_builder.rb +309 -0
  23. data/lib/aidp/prompt_optimization/relevance_scorer.rb +256 -0
  24. data/lib/aidp/prompt_optimization/source_code_fragmenter.rb +308 -0
  25. data/lib/aidp/prompt_optimization/style_guide_indexer.rb +240 -0
  26. data/lib/aidp/prompt_optimization/template_indexer.rb +250 -0
  27. data/lib/aidp/provider_manager.rb +0 -2
  28. data/lib/aidp/providers/anthropic.rb +19 -0
  29. data/lib/aidp/setup/wizard.rb +299 -4
  30. data/lib/aidp/utils/devcontainer_detector.rb +166 -0
  31. data/lib/aidp/version.rb +1 -1
  32. data/lib/aidp/watch/build_processor.rb +72 -6
  33. data/lib/aidp/watch/repository_client.rb +2 -1
  34. data/lib/aidp.rb +0 -1
  35. data/templates/aidp.yml.example +128 -0
  36. metadata +14 -2
  37. data/lib/aidp/providers/macos_ui.rb +0 -102
@@ -0,0 +1,335 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "capability_registry"
4
+ require_relative "configuration"
5
+
6
+ module Aidp
7
+ module Harness
8
+ # Manages thinking depth tier selection and escalation
9
+ # Integrates with CapabilityRegistry and Configuration to select appropriate models
10
+ class ThinkingDepthManager
11
+ attr_reader :configuration, :registry
12
+
13
+ def initialize(configuration, registry: nil, root_dir: nil)
14
+ @configuration = configuration
15
+ @registry = registry || CapabilityRegistry.new(root_dir: root_dir || configuration.instance_variable_get(:@project_dir))
16
+ @current_tier = nil
17
+ @session_max_tier = nil
18
+ @tier_history = []
19
+ @escalation_count = 0
20
+
21
+ Aidp.log_debug("thinking_depth_manager", "Initialized",
22
+ default_tier: default_tier,
23
+ max_tier: max_tier)
24
+ end
25
+
26
+ # Get current tier (defaults to config default_tier if not set)
27
+ def current_tier
28
+ @current_tier || default_tier
29
+ end
30
+
31
+ # Set current tier (validates against max_tier)
32
+ def current_tier=(tier)
33
+ validate_tier!(tier)
34
+ old_tier = current_tier
35
+ @current_tier = enforce_max_tier(tier)
36
+
37
+ if @current_tier != tier
38
+ Aidp.log_warn("thinking_depth_manager", "Tier capped at max",
39
+ requested: tier,
40
+ applied: @current_tier,
41
+ max: max_tier)
42
+ end
43
+
44
+ if @current_tier != old_tier
45
+ log_tier_change(old_tier, @current_tier, "manual_set")
46
+ end
47
+ end
48
+
49
+ # Get maximum allowed tier (respects session override)
50
+ def max_tier
51
+ @session_max_tier || configuration.max_tier
52
+ end
53
+
54
+ # Set maximum tier for this session (temporary override)
55
+ def max_tier=(tier)
56
+ validate_tier!(tier)
57
+ old_max = max_tier
58
+ @session_max_tier = tier
59
+
60
+ # If current tier exceeds new max, cap it
61
+ if @registry.compare_tiers(current_tier, tier) > 0
62
+ self.current_tier = tier
63
+ end
64
+
65
+ Aidp.log_info("thinking_depth_manager", "Max tier updated",
66
+ old: old_max,
67
+ new: tier,
68
+ current: current_tier)
69
+ end
70
+
71
+ # Get default tier from configuration
72
+ def default_tier
73
+ configuration.default_tier
74
+ end
75
+
76
+ # Reset to default tier
77
+ def reset_to_default
78
+ old_tier = current_tier
79
+ @current_tier = nil
80
+ @session_max_tier = nil
81
+ @escalation_count = 0
82
+
83
+ Aidp.log_info("thinking_depth_manager", "Reset to default",
84
+ old: old_tier,
85
+ new: current_tier)
86
+
87
+ current_tier
88
+ end
89
+
90
+ # Check if we can escalate to next tier
91
+ def can_escalate?
92
+ next_tier = @registry.next_tier(current_tier)
93
+ return false unless next_tier
94
+
95
+ @registry.compare_tiers(next_tier, max_tier) <= 0
96
+ end
97
+
98
+ # Escalate to next higher tier
99
+ # Returns new tier or nil if already at max
100
+ def escalate_tier(reason: nil)
101
+ unless can_escalate?
102
+ Aidp.log_warn("thinking_depth_manager", "Cannot escalate",
103
+ current: current_tier,
104
+ max: max_tier,
105
+ reason: reason)
106
+ return nil
107
+ end
108
+
109
+ old_tier = current_tier
110
+ new_tier = @registry.next_tier(current_tier)
111
+ @current_tier = new_tier
112
+ @escalation_count += 1
113
+
114
+ log_tier_change(old_tier, new_tier, reason || "escalation")
115
+ Aidp.log_info("thinking_depth_manager", "Escalated tier",
116
+ from: old_tier,
117
+ to: new_tier,
118
+ reason: reason,
119
+ count: @escalation_count)
120
+
121
+ new_tier
122
+ end
123
+
124
+ # De-escalate to next lower tier
125
+ # Returns new tier or nil if already at minimum
126
+ def de_escalate_tier(reason: nil)
127
+ prev_tier = @registry.previous_tier(current_tier)
128
+ unless prev_tier
129
+ Aidp.log_debug("thinking_depth_manager", "Cannot de-escalate",
130
+ current: current_tier)
131
+ return nil
132
+ end
133
+
134
+ old_tier = current_tier
135
+ @current_tier = prev_tier
136
+ @escalation_count = [@escalation_count - 1, 0].max
137
+
138
+ log_tier_change(old_tier, prev_tier, reason || "de-escalation")
139
+ Aidp.log_info("thinking_depth_manager", "De-escalated tier",
140
+ from: old_tier,
141
+ to: prev_tier,
142
+ reason: reason)
143
+
144
+ prev_tier
145
+ end
146
+
147
+ # Select best model for current tier and provider
148
+ # Returns [provider_name, model_name, model_data] or nil
149
+ def select_model_for_tier(tier = nil, provider: nil)
150
+ tier ||= current_tier
151
+ validate_tier!(tier)
152
+
153
+ # If provider specified, try to find model for that provider
154
+ if provider
155
+ model_name, model_data = @registry.best_model_for_tier(tier, provider)
156
+ if model_name
157
+ Aidp.log_debug("thinking_depth_manager", "Selected model",
158
+ tier: tier,
159
+ provider: provider,
160
+ model: model_name)
161
+ return [provider, model_name, model_data]
162
+ end
163
+
164
+ # If provider doesn't support tier and switching allowed, try others
165
+ unless configuration.allow_provider_switch_for_tier?
166
+ Aidp.log_warn("thinking_depth_manager", "Provider lacks tier, switching disabled",
167
+ tier: tier,
168
+ provider: provider)
169
+ return nil
170
+ end
171
+ end
172
+
173
+ # Try all providers
174
+ providers_to_try = provider ? [@registry.provider_names - [provider]].flatten : @registry.provider_names
175
+
176
+ providers_to_try.each do |prov_name|
177
+ model_name, model_data = @registry.best_model_for_tier(tier, prov_name)
178
+ if model_name
179
+ Aidp.log_info("thinking_depth_manager", "Selected model from alternate provider",
180
+ tier: tier,
181
+ original_provider: provider,
182
+ selected_provider: prov_name,
183
+ model: model_name)
184
+ return [prov_name, model_name, model_data]
185
+ end
186
+ end
187
+
188
+ Aidp.log_error("thinking_depth_manager", "No model found for tier",
189
+ tier: tier,
190
+ provider: provider)
191
+ nil
192
+ end
193
+
194
+ # Get tier for a specific model
195
+ def tier_for_model(provider, model)
196
+ @registry.tier_for_model(provider, model)
197
+ end
198
+
199
+ # Get information about a specific tier
200
+ def tier_info(tier)
201
+ validate_tier!(tier)
202
+
203
+ {
204
+ tier: tier,
205
+ priority: @registry.tier_priority(tier),
206
+ next_tier: @registry.next_tier(tier),
207
+ previous_tier: @registry.previous_tier(tier),
208
+ available_models: @registry.models_by_tier(tier),
209
+ at_max: tier == max_tier,
210
+ at_min: @registry.previous_tier(tier).nil?,
211
+ can_escalate: can_escalate_to?(tier)
212
+ }
213
+ end
214
+
215
+ # Get tier recommendation based on complexity score (0.0-1.0)
216
+ def recommend_tier_for_complexity(complexity_score)
217
+ tier = @registry.recommend_tier_for_complexity(complexity_score)
218
+
219
+ # Cap at max_tier
220
+ if @registry.compare_tiers(tier, max_tier) > 0
221
+ Aidp.log_debug("thinking_depth_manager", "Recommended tier capped",
222
+ recommended: tier,
223
+ complexity: complexity_score,
224
+ capped_to: max_tier)
225
+ return max_tier
226
+ end
227
+
228
+ Aidp.log_debug("thinking_depth_manager", "Recommended tier",
229
+ tier: tier,
230
+ complexity: complexity_score)
231
+ tier
232
+ end
233
+
234
+ # Check if tier override exists for skill/template
235
+ def tier_override_for(key)
236
+ override = configuration.tier_override_for(key)
237
+ return nil unless override
238
+
239
+ validate_tier!(override)
240
+
241
+ # Cap at max_tier
242
+ if @registry.compare_tiers(override, max_tier) > 0
243
+ Aidp.log_warn("thinking_depth_manager", "Override tier exceeds max",
244
+ key: key,
245
+ override: override,
246
+ max: max_tier)
247
+ return max_tier
248
+ end
249
+
250
+ override
251
+ end
252
+
253
+ # Get permission level for current tier
254
+ def permission_for_current_tier
255
+ configuration.permission_for_tier(current_tier)
256
+ end
257
+
258
+ # Get escalation attempt count
259
+ attr_reader :escalation_count
260
+
261
+ # Get tier change history
262
+ def tier_history
263
+ @tier_history.dup
264
+ end
265
+
266
+ # Check if should escalate based on failure count
267
+ def should_escalate_on_failures?(failure_count)
268
+ threshold = configuration.escalation_fail_attempts
269
+ failure_count >= threshold
270
+ end
271
+
272
+ # Check if should escalate based on complexity thresholds
273
+ def should_escalate_on_complexity?(context)
274
+ thresholds = configuration.escalation_complexity_threshold
275
+ return false if thresholds.empty?
276
+
277
+ files_changed = context[:files_changed] || 0
278
+ modules_touched = context[:modules_touched] || 0
279
+
280
+ exceeds_threshold = false
281
+
282
+ if thresholds[:files_changed] && files_changed >= thresholds[:files_changed]
283
+ exceeds_threshold = true
284
+ end
285
+
286
+ if thresholds[:modules_touched] && modules_touched >= thresholds[:modules_touched]
287
+ exceeds_threshold = true
288
+ end
289
+
290
+ if exceeds_threshold
291
+ Aidp.log_debug("thinking_depth_manager", "Complexity check",
292
+ files: files_changed,
293
+ modules: modules_touched,
294
+ exceeds: exceeds_threshold)
295
+ end
296
+
297
+ exceeds_threshold
298
+ end
299
+
300
+ private
301
+
302
+ def validate_tier!(tier)
303
+ unless @registry.valid_tier?(tier)
304
+ raise ArgumentError, "Invalid tier: #{tier}. Must be one of: #{CapabilityRegistry::VALID_TIERS.join(", ")}"
305
+ end
306
+ end
307
+
308
+ def enforce_max_tier(tier)
309
+ if @registry.compare_tiers(tier, max_tier) > 0
310
+ max_tier
311
+ else
312
+ tier
313
+ end
314
+ end
315
+
316
+ def can_escalate_to?(tier)
317
+ @registry.compare_tiers(tier, max_tier) <= 0
318
+ end
319
+
320
+ def log_tier_change(old_tier, new_tier, reason)
321
+ entry = {
322
+ timestamp: Time.now,
323
+ from: old_tier,
324
+ to: new_tier,
325
+ reason: reason,
326
+ escalation_count: @escalation_count
327
+ }
328
+ @tier_history << entry
329
+
330
+ # Keep history bounded
331
+ @tier_history.shift if @tier_history.size > 100
332
+ end
333
+ end
334
+ end
335
+ end