aidp 0.17.0 → 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.
- checksums.yaml +4 -4
- data/README.md +69 -0
- data/lib/aidp/analyze/kb_inspector.rb +2 -3
- data/lib/aidp/analyze/progress.rb +5 -10
- data/lib/aidp/cli/mcp_dashboard.rb +1 -1
- data/lib/aidp/cli.rb +64 -29
- data/lib/aidp/config.rb +9 -14
- data/lib/aidp/execute/progress.rb +5 -8
- data/lib/aidp/execute/prompt_manager.rb +128 -1
- data/lib/aidp/execute/repl_macros.rb +555 -0
- data/lib/aidp/execute/work_loop_runner.rb +108 -1
- data/lib/aidp/harness/ai_decision_engine.rb +376 -0
- data/lib/aidp/harness/capability_registry.rb +273 -0
- data/lib/aidp/harness/config_loader.rb +2 -2
- data/lib/aidp/harness/config_schema.rb +305 -1
- data/lib/aidp/harness/configuration.rb +452 -0
- data/lib/aidp/harness/enhanced_runner.rb +23 -8
- data/lib/aidp/harness/error_handler.rb +12 -5
- data/lib/aidp/harness/provider_factory.rb +0 -2
- data/lib/aidp/harness/provider_manager.rb +4 -19
- data/lib/aidp/harness/runner.rb +9 -3
- data/lib/aidp/harness/state/persistence.rb +9 -10
- data/lib/aidp/harness/state/workflow_state.rb +3 -2
- data/lib/aidp/harness/state_manager.rb +33 -97
- data/lib/aidp/harness/status_display.rb +22 -12
- data/lib/aidp/harness/thinking_depth_manager.rb +335 -0
- data/lib/aidp/harness/ui/enhanced_tui.rb +3 -4
- data/lib/aidp/harness/user_interface.rb +11 -6
- data/lib/aidp/harness/zfc_condition_detector.rb +395 -0
- data/lib/aidp/init/devcontainer_generator.rb +274 -0
- data/lib/aidp/init/runner.rb +37 -10
- data/lib/aidp/init.rb +1 -0
- data/lib/aidp/jobs/background_runner.rb +7 -1
- data/lib/aidp/logger.rb +1 -1
- data/lib/aidp/message_display.rb +9 -2
- data/lib/aidp/prompt_optimization/context_composer.rb +286 -0
- data/lib/aidp/prompt_optimization/optimizer.rb +335 -0
- data/lib/aidp/prompt_optimization/prompt_builder.rb +309 -0
- data/lib/aidp/prompt_optimization/relevance_scorer.rb +256 -0
- data/lib/aidp/prompt_optimization/source_code_fragmenter.rb +308 -0
- data/lib/aidp/prompt_optimization/style_guide_indexer.rb +240 -0
- data/lib/aidp/prompt_optimization/template_indexer.rb +250 -0
- data/lib/aidp/provider_manager.rb +0 -2
- data/lib/aidp/providers/anthropic.rb +20 -1
- data/lib/aidp/providers/base.rb +4 -4
- data/lib/aidp/providers/codex.rb +1 -1
- data/lib/aidp/providers/cursor.rb +1 -1
- data/lib/aidp/providers/gemini.rb +1 -1
- data/lib/aidp/providers/github_copilot.rb +1 -1
- data/lib/aidp/providers/opencode.rb +1 -1
- data/lib/aidp/setup/wizard.rb +299 -4
- data/lib/aidp/skills/wizard/prompter.rb +2 -2
- data/lib/aidp/utils/devcontainer_detector.rb +166 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/build_processor.rb +72 -6
- data/lib/aidp/watch/plan_generator.rb +1 -1
- data/lib/aidp/watch/repository_client.rb +15 -10
- data/lib/aidp/workflows/guided_agent.rb +2 -312
- data/lib/aidp/workstream_executor.rb +8 -2
- data/lib/aidp.rb +0 -1
- data/templates/aidp.yml.example +128 -0
- metadata +14 -2
- 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
|
|
@@ -20,16 +20,15 @@ module Aidp
|
|
|
20
20
|
|
|
21
21
|
class DisplayError < TUIError; end
|
|
22
22
|
|
|
23
|
-
def initialize(prompt: TTY::Prompt.new)
|
|
23
|
+
def initialize(prompt: TTY::Prompt.new, tty: $stdin)
|
|
24
24
|
@cursor = TTY::Cursor
|
|
25
25
|
@screen = TTY::Screen
|
|
26
26
|
@pastel = Pastel.new
|
|
27
27
|
@prompt = prompt
|
|
28
28
|
|
|
29
29
|
# Headless (non-interactive) detection for test/CI environments:
|
|
30
|
-
# -
|
|
31
|
-
|
|
32
|
-
@headless = !!(defined?(RSpec) || ENV["RSPEC_RUNNING"] || $stdin.nil? || !$stdin.tty?)
|
|
30
|
+
# - STDIN not a TTY (captured by PTY/tmux harness or test environment)
|
|
31
|
+
@headless = !!(tty.nil? || !tty.tty?)
|
|
33
32
|
|
|
34
33
|
@current_mode = nil
|
|
35
34
|
@workflow_active = false
|
|
@@ -2052,17 +2052,22 @@ module Aidp
|
|
|
2052
2052
|
# ============================================================================
|
|
2053
2053
|
|
|
2054
2054
|
# Start the control interface
|
|
2055
|
-
def start_control_interface
|
|
2055
|
+
def start_control_interface(async_control: true)
|
|
2056
2056
|
return unless @control_interface_enabled
|
|
2057
2057
|
|
|
2058
2058
|
@control_mutex.synchronize do
|
|
2059
2059
|
return if @control_future&.pending?
|
|
2060
2060
|
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2061
|
+
if async_control
|
|
2062
|
+
begin
|
|
2063
|
+
require "concurrent"
|
|
2064
|
+
@control_future = Concurrent::Future.execute { control_interface_loop }
|
|
2065
|
+
rescue LoadError
|
|
2066
|
+
# Fallback: run a single synchronous loop iteration if concurrent not available
|
|
2067
|
+
control_interface_loop_iteration
|
|
2068
|
+
end
|
|
2069
|
+
else
|
|
2070
|
+
control_interface_loop_iteration
|
|
2066
2071
|
end
|
|
2067
2072
|
end
|
|
2068
2073
|
|