aidp 0.34.0 → 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.
- checksums.yaml +4 -4
- data/README.md +14 -14
- data/lib/aidp/agent_harness_adapter.rb +309 -0
- data/lib/aidp/cli/issue_importer.rb +20 -6
- data/lib/aidp/cli/jobs_command.rb +1 -1
- data/lib/aidp/cli/models_command.rb +9 -16
- data/lib/aidp/cli/prompts_command.rb +253 -0
- data/lib/aidp/cli/security_command.rb +4 -10
- data/lib/aidp/cli/storage_command.rb +277 -0
- data/lib/aidp/cli/temporal_command.rb +460 -0
- data/lib/aidp/cli.rb +24 -2
- data/lib/aidp/config.rb +15 -0
- data/lib/aidp/config_paths.rb +12 -2
- data/lib/aidp/database/migrations.rb +114 -0
- data/lib/aidp/database/repositories/checkpoint_repository.rb +174 -0
- data/lib/aidp/database/repositories/deprecated_models_repository.rb +206 -0
- data/lib/aidp/database/repositories/evaluation_repository.rb +208 -0
- data/lib/aidp/database/repositories/harness_state_repository.rb +140 -0
- data/lib/aidp/database/repositories/job_repository.rb +278 -0
- data/lib/aidp/database/repositories/model_cache_repository.rb +194 -0
- data/lib/aidp/database/repositories/progress_repository.rb +210 -0
- data/lib/aidp/database/repositories/prompt_archive_repository.rb +195 -0
- data/lib/aidp/database/repositories/prompt_feedback_repository.rb +266 -0
- data/lib/aidp/database/repositories/provider_info_cache_repository.rb +208 -0
- data/lib/aidp/database/repositories/provider_metrics_repository.rb +245 -0
- data/lib/aidp/database/repositories/secrets_repository.rb +187 -0
- data/lib/aidp/database/repositories/task_repository.rb +231 -0
- data/lib/aidp/database/repositories/template_version_repository.rb +383 -0
- data/lib/aidp/database/repositories/watch_state_repository.rb +472 -0
- data/lib/aidp/database/repositories/workstream_repository.rb +310 -0
- data/lib/aidp/database/repositories/worktree_repository.rb +240 -0
- data/lib/aidp/database/repository.rb +166 -0
- data/lib/aidp/database/schema.rb +347 -0
- data/lib/aidp/database/storage_migrator.rb +693 -0
- data/lib/aidp/database.rb +139 -0
- data/lib/aidp/debug_mixin.rb +2 -2
- data/lib/aidp/execute/checkpoint.rb +26 -0
- data/lib/aidp/execute/checkpoint_display.rb +16 -0
- data/lib/aidp/execute/prompt_evaluator.rb +30 -57
- data/lib/aidp/execute/work_loop_runner.rb +294 -46
- data/lib/aidp/firewall/provider_requirements_collector.rb +7 -23
- data/lib/aidp/harness/agent_harness_provider_manager.rb +275 -0
- data/lib/aidp/harness/ai_decision_engine.rb +78 -296
- data/lib/aidp/harness/config_schema.rb +59 -0
- data/lib/aidp/harness/configuration.rb +229 -10
- data/lib/aidp/harness/deprecation_cache.rb +53 -11
- data/lib/aidp/harness/enhanced_runner.rb +1 -1
- data/lib/aidp/harness/error_handler.rb +11 -5
- data/lib/aidp/harness/provider_config.rb +36 -0
- data/lib/aidp/harness/provider_factory.rb +26 -24
- data/lib/aidp/harness/provider_info.rb +9 -5
- data/lib/aidp/harness/provider_manager.rb +2 -2
- data/lib/aidp/harness/provider_metrics.rb +103 -1
- data/lib/aidp/harness/rspec_command_optimizer.rb +166 -0
- data/lib/aidp/harness/ruby_llm_registry.rb +97 -8
- data/lib/aidp/harness/runner.rb +7 -2
- data/lib/aidp/harness/state_manager.rb +1 -1
- data/lib/aidp/harness/test_runner.rb +167 -11
- data/lib/aidp/harness/thinking_depth_manager.rb +564 -5
- data/lib/aidp/harness/ui.rb +11 -0
- data/lib/aidp/harness/usage_limit.rb +179 -0
- data/lib/aidp/harness/usage_limit_enforcer.rb +240 -0
- data/lib/aidp/harness/usage_limit_tracker.rb +308 -0
- data/lib/aidp/harness/usage_period.rb +234 -0
- data/lib/aidp/init/agent_instructions_generator.rb +317 -0
- data/lib/aidp/init/runner.rb +25 -4
- data/lib/aidp/interfaces/activity_monitor_interface.rb +234 -0
- data/lib/aidp/interfaces/binary_checker_interface.rb +232 -0
- data/lib/aidp/interfaces/command_executor_interface.rb +216 -0
- data/lib/aidp/interfaces/logger_interface.rb +150 -0
- data/lib/aidp/interfaces/ui_interface.rb +254 -0
- data/lib/aidp/logger.rb +25 -7
- data/lib/aidp/orchestration_adapter.rb +279 -0
- data/lib/aidp/pr_worktree_manager.rb +4 -15
- data/lib/aidp/prompts/feedback_collector.rb +198 -0
- data/lib/aidp/prompts/prompt_template_manager.rb +415 -0
- data/lib/aidp/prompts/template_evolver.rb +305 -0
- data/lib/aidp/prompts/template_version_manager.rb +325 -0
- data/lib/aidp/setup/in_memory_config_adapter.rb +257 -0
- data/lib/aidp/setup/in_memory_config_manager.rb +262 -0
- data/lib/aidp/setup/wizard.rb +616 -176
- data/lib/aidp/shell_executor.rb +58 -0
- data/lib/aidp/style_guide/selector.rb +56 -1
- data/lib/aidp/temporal/activities/analyze_issue_activity.rb +198 -0
- data/lib/aidp/temporal/activities/analyze_sub_task_activity.rb +163 -0
- data/lib/aidp/temporal/activities/base_activity.rb +78 -0
- data/lib/aidp/temporal/activities/create_plan_activity.rb +115 -0
- data/lib/aidp/temporal/activities/create_pr_activity.rb +193 -0
- data/lib/aidp/temporal/activities/create_prompt_activity.rb +118 -0
- data/lib/aidp/temporal/activities/diagnose_failure_activity.rb +228 -0
- data/lib/aidp/temporal/activities/prepare_next_iteration_activity.rb +147 -0
- data/lib/aidp/temporal/activities/record_checkpoint_activity.rb +81 -0
- data/lib/aidp/temporal/activities/run_agent_activity.rb +139 -0
- data/lib/aidp/temporal/activities/run_tests_activity.rb +114 -0
- data/lib/aidp/temporal/activities/run_work_loop_iteration_activity.rb +249 -0
- data/lib/aidp/temporal/configuration.rb +169 -0
- data/lib/aidp/temporal/connection.rb +93 -0
- data/lib/aidp/temporal/worker.rb +119 -0
- data/lib/aidp/temporal/workflow_client.rb +171 -0
- data/lib/aidp/temporal/workflows/base_workflow.rb +73 -0
- data/lib/aidp/temporal/workflows/issue_to_pr_workflow.rb +278 -0
- data/lib/aidp/temporal/workflows/sub_issue_workflow.rb +253 -0
- data/lib/aidp/temporal/workflows/work_loop_workflow.rb +321 -0
- data/lib/aidp/temporal.rb +120 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/base_processor.rb +35 -0
- data/lib/aidp/watch/build_processor.rb +132 -0
- data/lib/aidp/watch/github_state_extractor.rb +0 -8
- data/lib/aidp/watch/rebase_processor.rb +245 -0
- data/lib/aidp/watch/round_robin_scheduler.rb +150 -0
- data/lib/aidp/watch/runner.rb +366 -285
- data/lib/aidp/watch/state_store.rb +80 -0
- data/lib/aidp/watch/work_item.rb +96 -0
- data/lib/aidp/watch/worktree_cleanup_job.rb +226 -0
- data/lib/aidp/watch/worktree_reconciler.rb +643 -0
- data/lib/aidp/workflows/guided_agent.rb +1 -1
- data/lib/aidp/worktree.rb +62 -0
- data/templates/aidp.yml.example +70 -0
- metadata +118 -16
- data/lib/aidp/analyze/json_file_storage.rb +0 -292
- data/lib/aidp/execute/future_work_backlog.rb +0 -411
- data/lib/aidp/providers/adapter.rb +0 -239
- data/lib/aidp/providers/aider.rb +0 -264
- data/lib/aidp/providers/anthropic.rb +0 -614
- data/lib/aidp/providers/base.rb +0 -617
- data/lib/aidp/providers/capability_registry.rb +0 -204
- data/lib/aidp/providers/codex.rb +0 -267
- data/lib/aidp/providers/cursor.rb +0 -304
- data/lib/aidp/providers/error_taxonomy.rb +0 -195
- data/lib/aidp/providers/gemini.rb +0 -110
- data/lib/aidp/providers/github_copilot.rb +0 -227
- data/lib/aidp/providers/kilocode.rb +0 -157
- data/lib/aidp/providers/opencode.rb +0 -139
|
@@ -6,14 +6,36 @@ require_relative "../message_display"
|
|
|
6
6
|
|
|
7
7
|
module Aidp
|
|
8
8
|
module Harness
|
|
9
|
+
# Custom exception for model availability issues
|
|
10
|
+
class NoModelAvailableError < StandardError
|
|
11
|
+
attr_reader :tier, :provider
|
|
12
|
+
|
|
13
|
+
def initialize(tier:, provider:)
|
|
14
|
+
@tier = tier
|
|
15
|
+
@provider = provider
|
|
16
|
+
super("No model available for tier '#{tier}' with provider '#{provider}'. " \
|
|
17
|
+
"Check your aidp.yml configuration or run 'aidp models discover' to find available models.")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
9
21
|
# Manages thinking depth tier selection and escalation
|
|
10
22
|
# Integrates with CapabilityRegistry and Configuration to select appropriate models
|
|
11
23
|
class ThinkingDepthManager
|
|
12
24
|
include Aidp::MessageDisplay
|
|
13
25
|
|
|
26
|
+
# Configuration constants for tier management
|
|
27
|
+
MAX_TIER_HISTORY_SIZE = 100
|
|
28
|
+
MAX_COMMENT_LENGTH = 2000
|
|
29
|
+
MAX_REASONING_DISPLAY_LENGTH = 100
|
|
30
|
+
DEFAULT_CONFIDENCE = 0.7
|
|
31
|
+
|
|
14
32
|
attr_reader :configuration, :registry
|
|
15
33
|
|
|
16
|
-
|
|
34
|
+
# Issue #375: Model attempt tracking for intelligent escalation
|
|
35
|
+
# Structure: { tier => { provider => { model => { attempts: n, failed: bool } } } }
|
|
36
|
+
attr_reader :model_attempts
|
|
37
|
+
|
|
38
|
+
def initialize(configuration, registry: nil, root_dir: nil, autonomous_mode: false)
|
|
17
39
|
@configuration = configuration
|
|
18
40
|
@registry = registry || CapabilityRegistry.new(root_dir: root_dir || configuration.project_dir)
|
|
19
41
|
@current_tier = nil
|
|
@@ -21,9 +43,64 @@ module Aidp
|
|
|
21
43
|
@tier_history = []
|
|
22
44
|
@escalation_count = 0
|
|
23
45
|
|
|
46
|
+
# Issue #375: Track model attempts for intelligent escalation
|
|
47
|
+
@model_attempts = {}
|
|
48
|
+
@total_attempts_in_tier = 0
|
|
49
|
+
@autonomous_mode = autonomous_mode
|
|
50
|
+
@model_denylist = [] # Models to skip (e.g., denylisted by user)
|
|
51
|
+
|
|
24
52
|
Aidp.log_debug("thinking_depth_manager", "Initialized",
|
|
25
53
|
default_tier: default_tier,
|
|
26
|
-
max_tier: max_tier
|
|
54
|
+
max_tier: max_tier,
|
|
55
|
+
autonomous_max_tier: autonomous_max_tier,
|
|
56
|
+
autonomous_mode: autonomous_mode)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Enable autonomous mode (restricts max tier, enables model-level tracking)
|
|
60
|
+
# Should be called when entering watch mode or work loops
|
|
61
|
+
def enable_autonomous_mode
|
|
62
|
+
@autonomous_mode = true
|
|
63
|
+
reset_model_tracking
|
|
64
|
+
|
|
65
|
+
# Cap current tier at autonomous max if needed
|
|
66
|
+
if @registry.compare_tiers(current_tier, autonomous_max_tier) > 0
|
|
67
|
+
old_tier = current_tier
|
|
68
|
+
@current_tier = autonomous_max_tier
|
|
69
|
+
log_tier_change(old_tier, @current_tier, "autonomous_mode_cap")
|
|
70
|
+
|
|
71
|
+
Aidp.log_info("thinking_depth_manager", "Tier capped for autonomous mode",
|
|
72
|
+
old: old_tier,
|
|
73
|
+
new: @current_tier,
|
|
74
|
+
autonomous_max: autonomous_max_tier)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
Aidp.log_debug("thinking_depth_manager", "Autonomous mode enabled",
|
|
78
|
+
max_tier: autonomous_max_tier)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Disable autonomous mode (restores normal max tier)
|
|
82
|
+
def disable_autonomous_mode
|
|
83
|
+
@autonomous_mode = false
|
|
84
|
+
Aidp.log_debug("thinking_depth_manager", "Autonomous mode disabled")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Check if in autonomous mode
|
|
88
|
+
def autonomous_mode?
|
|
89
|
+
@autonomous_mode
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Get maximum tier for autonomous operations (issue #375)
|
|
93
|
+
def autonomous_max_tier
|
|
94
|
+
@session_autonomous_max_tier || configuration.autonomous_max_tier
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Set autonomous max tier for this session
|
|
98
|
+
def autonomous_max_tier=(tier)
|
|
99
|
+
validate_tier!(tier)
|
|
100
|
+
@session_autonomous_max_tier = tier
|
|
101
|
+
|
|
102
|
+
Aidp.log_info("thinking_depth_manager", "Autonomous max tier updated",
|
|
103
|
+
new: tier)
|
|
27
104
|
end
|
|
28
105
|
|
|
29
106
|
# Get current tier (defaults to config default_tier if not set)
|
|
@@ -49,11 +126,33 @@ module Aidp
|
|
|
49
126
|
end
|
|
50
127
|
end
|
|
51
128
|
|
|
52
|
-
# Get maximum
|
|
53
|
-
|
|
129
|
+
# Get the base maximum tier (from session override or config, ignoring autonomous mode)
|
|
130
|
+
# This is the "raw" max tier before autonomous mode restrictions are applied
|
|
131
|
+
def base_max_tier
|
|
54
132
|
@session_max_tier || configuration.max_tier
|
|
55
133
|
end
|
|
56
134
|
|
|
135
|
+
# Get effective maximum tier (applies autonomous mode restrictions)
|
|
136
|
+
# Issue #375: In autonomous mode, respects autonomous_max_tier
|
|
137
|
+
def max_tier
|
|
138
|
+
apply_autonomous_tier_cap(base_max_tier)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Apply autonomous mode tier cap if active
|
|
142
|
+
# Returns the tier capped at autonomous_max_tier when in autonomous mode
|
|
143
|
+
# @param tier [String] The tier to potentially cap
|
|
144
|
+
# @return [String] The effective tier (capped if in autonomous mode)
|
|
145
|
+
def apply_autonomous_tier_cap(tier)
|
|
146
|
+
return tier unless @autonomous_mode
|
|
147
|
+
|
|
148
|
+
auto_max = autonomous_max_tier
|
|
149
|
+
if @registry.compare_tiers(auto_max, tier) < 0
|
|
150
|
+
auto_max
|
|
151
|
+
else
|
|
152
|
+
tier
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
57
156
|
# Set maximum tier for this session (temporary override)
|
|
58
157
|
def max_tier=(tier)
|
|
59
158
|
validate_tier!(tier)
|
|
@@ -147,6 +246,322 @@ module Aidp
|
|
|
147
246
|
prev_tier
|
|
148
247
|
end
|
|
149
248
|
|
|
249
|
+
# ============================================================
|
|
250
|
+
# Issue #375: Intelligent model-level escalation for autonomous mode
|
|
251
|
+
# ============================================================
|
|
252
|
+
|
|
253
|
+
# Record an attempt with a specific model
|
|
254
|
+
# @param provider [String] Provider name
|
|
255
|
+
# @param model [String] Model name
|
|
256
|
+
# @param success [Boolean] Whether the attempt succeeded
|
|
257
|
+
def record_model_attempt(provider:, model:, success:)
|
|
258
|
+
tier = current_tier
|
|
259
|
+
@model_attempts[tier] ||= {}
|
|
260
|
+
@model_attempts[tier][provider] ||= {}
|
|
261
|
+
@model_attempts[tier][provider][model] ||= {attempts: 0, failed: false, last_attempt_at: nil}
|
|
262
|
+
|
|
263
|
+
@model_attempts[tier][provider][model][:attempts] += 1
|
|
264
|
+
@model_attempts[tier][provider][model][:last_attempt_at] = Time.now
|
|
265
|
+
@model_attempts[tier][provider][model][:failed] = !success
|
|
266
|
+
|
|
267
|
+
@total_attempts_in_tier += 1
|
|
268
|
+
|
|
269
|
+
Aidp.log_debug("thinking_depth_manager", "Recorded model attempt",
|
|
270
|
+
tier: tier,
|
|
271
|
+
provider: provider,
|
|
272
|
+
model: model,
|
|
273
|
+
success: success,
|
|
274
|
+
total_attempts: @model_attempts[tier][provider][model][:attempts],
|
|
275
|
+
tier_total: @total_attempts_in_tier)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Get attempts for a specific model
|
|
279
|
+
def model_attempt_count(provider:, model:)
|
|
280
|
+
tier = current_tier
|
|
281
|
+
@model_attempts.dig(tier, provider, model, :attempts) || 0
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Check if a model has been marked as failed
|
|
285
|
+
def model_failed?(provider:, model:)
|
|
286
|
+
tier = current_tier
|
|
287
|
+
@model_attempts.dig(tier, provider, model, :failed) || false
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Add model to denylist
|
|
291
|
+
def denylist_model(model)
|
|
292
|
+
@model_denylist << model unless @model_denylist.include?(model)
|
|
293
|
+
Aidp.log_debug("thinking_depth_manager", "Model denylisted", model: model)
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# Check if model is denylisted
|
|
297
|
+
def model_denylisted?(model)
|
|
298
|
+
@model_denylist.include?(model)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Get all available models for current tier and provider
|
|
302
|
+
# @param provider [String] Provider name
|
|
303
|
+
# @return [Array<String>] List of model names
|
|
304
|
+
def available_models_for_tier(provider:)
|
|
305
|
+
tier = current_tier
|
|
306
|
+
|
|
307
|
+
# First try user-configured models
|
|
308
|
+
configured = configuration.models_for_tier(tier, provider)
|
|
309
|
+
|
|
310
|
+
if configured.any?
|
|
311
|
+
# Filter out denylisted models
|
|
312
|
+
return configured.reject { |m| model_denylisted?(m) }
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# Fall back to catalog models
|
|
316
|
+
model_name, _data = @registry.best_model_for_tier(tier, provider)
|
|
317
|
+
return [] unless model_name
|
|
318
|
+
|
|
319
|
+
[model_name].reject { |m| model_denylisted?(m) }
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Check if any models are configured for the current tier and provider
|
|
323
|
+
# @param provider [String] Provider name
|
|
324
|
+
# @return [Boolean] true if models are available, false if none configured
|
|
325
|
+
def models_configured_for_tier?(provider:)
|
|
326
|
+
available_models_for_tier(provider: provider).any?
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# Select the next model to try in current tier
|
|
330
|
+
# Issue #375: Tries all models before escalating, respects min attempts per model
|
|
331
|
+
# @param provider [String] Provider name
|
|
332
|
+
# @return [String, nil] Model name or nil if should escalate
|
|
333
|
+
def select_next_model(provider:)
|
|
334
|
+
tier = current_tier
|
|
335
|
+
models = available_models_for_tier(provider: provider)
|
|
336
|
+
|
|
337
|
+
if models.empty?
|
|
338
|
+
Aidp.log_debug("thinking_depth_manager", "No models configured for tier",
|
|
339
|
+
tier: tier,
|
|
340
|
+
provider: provider,
|
|
341
|
+
reason: "no_models_configured")
|
|
342
|
+
return nil
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
min_attempts = configuration.min_attempts_per_model
|
|
346
|
+
|
|
347
|
+
# First pass: find any model with under-min-attempts (must reach min before retry)
|
|
348
|
+
# This ensures every model gets minimum attempts before we consider retrying
|
|
349
|
+
models.each do |model|
|
|
350
|
+
attempts = model_attempt_count(provider: provider, model: model)
|
|
351
|
+
if attempts < min_attempts
|
|
352
|
+
Aidp.log_debug("thinking_depth_manager", "Selected under-min-attempts model",
|
|
353
|
+
model: model,
|
|
354
|
+
attempts: attempts,
|
|
355
|
+
min_required: min_attempts)
|
|
356
|
+
return model
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# Second pass: retry models that have met min attempts (if retry enabled)
|
|
361
|
+
# Prioritize non-failed models first, then retry failed models
|
|
362
|
+
if configuration.retry_failed_models?
|
|
363
|
+
# First try non-failed models that have met min attempts
|
|
364
|
+
models.each do |model|
|
|
365
|
+
attempts = model_attempt_count(provider: provider, model: model)
|
|
366
|
+
if attempts >= min_attempts && !model_failed?(provider: provider, model: model)
|
|
367
|
+
Aidp.log_debug("thinking_depth_manager", "Selected non-failed model for retry",
|
|
368
|
+
model: model,
|
|
369
|
+
attempts: attempts)
|
|
370
|
+
return model
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# Then retry previously failed models that have met min attempts
|
|
375
|
+
models.each do |model|
|
|
376
|
+
attempts = model_attempt_count(provider: provider, model: model)
|
|
377
|
+
if attempts >= min_attempts && model_failed?(provider: provider, model: model)
|
|
378
|
+
Aidp.log_debug("thinking_depth_manager", "Retrying previously failed model",
|
|
379
|
+
model: model,
|
|
380
|
+
attempts: attempts)
|
|
381
|
+
return model
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# All models exhausted in this tier
|
|
387
|
+
Aidp.log_debug("thinking_depth_manager", "All models exhausted in tier",
|
|
388
|
+
tier: tier,
|
|
389
|
+
models_tried: models.size)
|
|
390
|
+
nil
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# Check if we should escalate tier based on model exhaustion
|
|
394
|
+
# Issue #375: Requires minimum total attempts and trying all models first
|
|
395
|
+
# @param provider [String] Provider name
|
|
396
|
+
# @return [Hash] {should_escalate: bool, reason: string}
|
|
397
|
+
def should_escalate_tier?(provider:)
|
|
398
|
+
return {should_escalate: false, reason: "not_autonomous"} unless @autonomous_mode
|
|
399
|
+
|
|
400
|
+
min_total = configuration.min_total_attempts_before_escalation
|
|
401
|
+
min_per_model = configuration.min_attempts_per_model
|
|
402
|
+
models = available_models_for_tier(provider: provider)
|
|
403
|
+
|
|
404
|
+
# Check if we have any models below minimum attempts threshold
|
|
405
|
+
models_below_min = models.select do |model|
|
|
406
|
+
model_attempt_count(provider: provider, model: model) < min_per_model
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
if models_below_min.any?
|
|
410
|
+
return {
|
|
411
|
+
should_escalate: false,
|
|
412
|
+
reason: "models_below_min_attempts",
|
|
413
|
+
remaining_count: models_below_min.size
|
|
414
|
+
}
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
# Check minimum total attempts
|
|
418
|
+
# Relax if tier lacks sufficient models (each model needs min 2 tries)
|
|
419
|
+
effective_min_total = [min_total, models.size * min_per_model].min
|
|
420
|
+
|
|
421
|
+
if @total_attempts_in_tier < effective_min_total
|
|
422
|
+
return {
|
|
423
|
+
should_escalate: false,
|
|
424
|
+
reason: "below_min_attempts",
|
|
425
|
+
current: @total_attempts_in_tier,
|
|
426
|
+
required: effective_min_total
|
|
427
|
+
}
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
# Check if all models have failed
|
|
431
|
+
# Only escalate if ALL models have failed - don't escalate just because min attempts reached
|
|
432
|
+
# if some models are still succeeding
|
|
433
|
+
all_failed = models.all? { |m| model_failed?(provider: provider, model: m) }
|
|
434
|
+
|
|
435
|
+
if all_failed
|
|
436
|
+
return {
|
|
437
|
+
should_escalate: true,
|
|
438
|
+
reason: "all_models_failed",
|
|
439
|
+
total_attempts: @total_attempts_in_tier
|
|
440
|
+
}
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
{should_escalate: false, reason: "continue_current_tier"}
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
# Escalate tier with intelligent model tracking (issue #375)
|
|
447
|
+
# Only escalates if all models in current tier have been tried
|
|
448
|
+
# @param provider [String] Provider name
|
|
449
|
+
# @param reason [String, nil] Reason for escalation
|
|
450
|
+
# @return [String, nil] New tier or nil if cannot escalate
|
|
451
|
+
def escalate_tier_intelligent(provider:, reason: nil)
|
|
452
|
+
escalation_check = should_escalate_tier?(provider: provider)
|
|
453
|
+
|
|
454
|
+
unless escalation_check[:should_escalate]
|
|
455
|
+
Aidp.log_debug("thinking_depth_manager", "Intelligent escalation blocked",
|
|
456
|
+
reason: escalation_check[:reason],
|
|
457
|
+
details: escalation_check)
|
|
458
|
+
return nil
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
# Reset model tracking for new tier
|
|
462
|
+
old_tier = current_tier
|
|
463
|
+
new_tier = escalate_tier(reason: reason || escalation_check[:reason])
|
|
464
|
+
|
|
465
|
+
if new_tier
|
|
466
|
+
attempts_before_reset = @total_attempts_in_tier
|
|
467
|
+
reset_model_tracking
|
|
468
|
+
Aidp.log_info("thinking_depth_manager", "Intelligent tier escalation",
|
|
469
|
+
from: old_tier,
|
|
470
|
+
to: new_tier,
|
|
471
|
+
reason: reason || escalation_check[:reason],
|
|
472
|
+
total_attempts_in_old_tier: attempts_before_reset)
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
new_tier
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
# Reset model tracking (call when changing tiers or starting new work)
|
|
479
|
+
# Clears current tier's data for consistency; preserves other tiers' history for analysis
|
|
480
|
+
def reset_model_tracking
|
|
481
|
+
tier = current_tier
|
|
482
|
+
# Ensure hash exists then clear it for consistency with counter reset
|
|
483
|
+
@model_attempts[tier] ||= {}
|
|
484
|
+
@model_attempts[tier].clear
|
|
485
|
+
@total_attempts_in_tier = 0
|
|
486
|
+
|
|
487
|
+
Aidp.log_debug("thinking_depth_manager", "Model tracking reset for tier", tier: tier)
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
# Get summary of model attempts in current tier
|
|
491
|
+
def model_attempts_summary
|
|
492
|
+
tier = current_tier
|
|
493
|
+
tier_attempts = @model_attempts[tier] || {}
|
|
494
|
+
|
|
495
|
+
summary = {
|
|
496
|
+
tier: tier,
|
|
497
|
+
total_attempts: @total_attempts_in_tier,
|
|
498
|
+
providers: {}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
tier_attempts.each do |provider, models|
|
|
502
|
+
summary[:providers][provider] = models.map do |model, data|
|
|
503
|
+
{
|
|
504
|
+
model: model,
|
|
505
|
+
attempts: data[:attempts],
|
|
506
|
+
failed: data[:failed]
|
|
507
|
+
}
|
|
508
|
+
end
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
summary
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
# ============================================================
|
|
515
|
+
# Issue #375: ZFC-based tier determination from issue comments
|
|
516
|
+
# ============================================================
|
|
517
|
+
|
|
518
|
+
# Determine appropriate tier from issue/PR comment content using ZFC
|
|
519
|
+
# @param comment_text [String] The issue or PR comment text
|
|
520
|
+
# @param provider_manager [ProviderManager] Provider manager for AI calls
|
|
521
|
+
# @param labels [Array<String>] Optional labels on the issue/PR
|
|
522
|
+
# @return [Hash] {tier: String, confidence: Float, reasoning: String}
|
|
523
|
+
def determine_tier_from_comment(comment_text:, provider_manager:, labels: [])
|
|
524
|
+
# Check for explicit tier labels first (fast path)
|
|
525
|
+
tier_from_labels = extract_tier_from_labels(labels)
|
|
526
|
+
if tier_from_labels
|
|
527
|
+
tier = tier_from_labels
|
|
528
|
+
reasoning = "Explicit tier label found: #{tier_from_labels}"
|
|
529
|
+
|
|
530
|
+
# In autonomous mode, cap at autonomous_max_tier (same as ZFC path)
|
|
531
|
+
if @autonomous_mode && @registry.compare_tiers(tier, autonomous_max_tier) > 0
|
|
532
|
+
original_tier = tier
|
|
533
|
+
tier = autonomous_max_tier
|
|
534
|
+
reasoning += " (capped from #{original_tier} due to autonomous mode)"
|
|
535
|
+
|
|
536
|
+
Aidp.log_debug("thinking_depth_manager", "Label tier capped for autonomous mode",
|
|
537
|
+
original: original_tier,
|
|
538
|
+
capped_to: tier)
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
tier: tier,
|
|
543
|
+
confidence: 1.0,
|
|
544
|
+
reasoning: reasoning,
|
|
545
|
+
source: "label"
|
|
546
|
+
}
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
# Use ZFC to determine tier from comment content
|
|
550
|
+
determine_tier_via_zfc(comment_text, provider_manager)
|
|
551
|
+
rescue => e
|
|
552
|
+
Aidp.log_warn("thinking_depth_manager", "ZFC tier determination failed, using default",
|
|
553
|
+
error: e.message,
|
|
554
|
+
error_class: e.class.name)
|
|
555
|
+
|
|
556
|
+
# Return conservative default on error
|
|
557
|
+
{
|
|
558
|
+
tier: "mini",
|
|
559
|
+
confidence: 0.5,
|
|
560
|
+
reasoning: "ZFC determination failed, using conservative default",
|
|
561
|
+
source: "fallback"
|
|
562
|
+
}
|
|
563
|
+
end
|
|
564
|
+
|
|
150
565
|
# Select best model for current tier and provider
|
|
151
566
|
# Returns [provider_name, model_name, model_data] or nil
|
|
152
567
|
def select_model_for_tier(tier = nil, provider: nil)
|
|
@@ -432,7 +847,143 @@ module Aidp
|
|
|
432
847
|
@tier_history << entry
|
|
433
848
|
|
|
434
849
|
# Keep history bounded
|
|
435
|
-
@tier_history.shift if @tier_history.size >
|
|
850
|
+
@tier_history.shift if @tier_history.size > MAX_TIER_HISTORY_SIZE
|
|
851
|
+
end
|
|
852
|
+
|
|
853
|
+
# ============================================================
|
|
854
|
+
# Issue #375: ZFC helper methods for tier determination
|
|
855
|
+
# ============================================================
|
|
856
|
+
|
|
857
|
+
# Extract tier from issue labels (fast path, no AI needed)
|
|
858
|
+
# @param labels [Array<String>] Issue labels
|
|
859
|
+
# @return [String, nil] Tier name or nil if no tier label found
|
|
860
|
+
def extract_tier_from_labels(labels)
|
|
861
|
+
return nil unless labels.is_a?(Array)
|
|
862
|
+
|
|
863
|
+
tier_label_patterns = {
|
|
864
|
+
/\btier[:\-_]?mini\b/i => "mini",
|
|
865
|
+
/\btier[:\-_]?standard\b/i => "standard",
|
|
866
|
+
/\btier[:\-_]?thinking\b/i => "thinking",
|
|
867
|
+
/\btier[:\-_]?pro\b/i => "pro",
|
|
868
|
+
/\btier[:\-_]?max\b/i => "max",
|
|
869
|
+
/\bcomplexity[:\-_]?low\b/i => "mini",
|
|
870
|
+
/\bcomplexity[:\-_]?medium\b/i => "standard",
|
|
871
|
+
/\bcomplexity[:\-_]?high\b/i => "pro"
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
labels.each do |label|
|
|
875
|
+
tier_label_patterns.each do |pattern, tier|
|
|
876
|
+
return tier if label.match?(pattern)
|
|
877
|
+
end
|
|
878
|
+
end
|
|
879
|
+
|
|
880
|
+
nil
|
|
881
|
+
end
|
|
882
|
+
|
|
883
|
+
# Use Zero Framework Cognition to determine tier from comment content
|
|
884
|
+
# @param comment_text [String] The comment text to analyze
|
|
885
|
+
# @param provider_manager [ProviderManager] Provider manager for AI calls
|
|
886
|
+
# @return [Hash] Tier determination result
|
|
887
|
+
def determine_tier_via_zfc(comment_text, provider_manager)
|
|
888
|
+
prompt = build_tier_determination_prompt(comment_text)
|
|
889
|
+
|
|
890
|
+
# Use mini tier for the ZFC decision itself (cost efficiency)
|
|
891
|
+
provider_name, model_name, _data = select_model_for_tier("mini", provider: configuration.default_provider)
|
|
892
|
+
|
|
893
|
+
# Handle case where no model is available for tier determination
|
|
894
|
+
if provider_name.nil?
|
|
895
|
+
Aidp.log_warn("thinking_depth_manager", "No model available for ZFC tier determination",
|
|
896
|
+
tier: "mini",
|
|
897
|
+
provider: configuration.default_provider)
|
|
898
|
+
raise NoModelAvailableError.new(tier: "mini", provider: configuration.default_provider)
|
|
899
|
+
end
|
|
900
|
+
|
|
901
|
+
result = provider_manager.execute_with_provider(
|
|
902
|
+
provider_name,
|
|
903
|
+
prompt,
|
|
904
|
+
{
|
|
905
|
+
model: model_name,
|
|
906
|
+
mode: :tier_determination,
|
|
907
|
+
max_tokens: 500 # Keep response short
|
|
908
|
+
}
|
|
909
|
+
)
|
|
910
|
+
|
|
911
|
+
parse_tier_determination_response(result[:output])
|
|
912
|
+
end
|
|
913
|
+
|
|
914
|
+
# Build prompt for ZFC tier determination
|
|
915
|
+
def build_tier_determination_prompt(comment_text)
|
|
916
|
+
max_length = MAX_COMMENT_LENGTH
|
|
917
|
+
truncated = comment_text && comment_text.length > max_length
|
|
918
|
+
truncation_note = truncated ? "\n\n[Note: Comment was truncated from #{comment_text.length} to #{max_length} characters]" : ""
|
|
919
|
+
|
|
920
|
+
<<~PROMPT
|
|
921
|
+
Analyze the following issue/PR comment and determine the appropriate thinking tier for an AI agent to address it.
|
|
922
|
+
|
|
923
|
+
## Available Tiers (lowest to highest capability/cost):
|
|
924
|
+
- **mini**: Simple fixes, typos, minor changes, documentation updates
|
|
925
|
+
- **standard**: Normal features, bug fixes, moderate complexity changes
|
|
926
|
+
- **thinking**: Complex problems requiring extended reasoning, multi-step solutions
|
|
927
|
+
- **pro**: Highly complex tasks, architectural decisions, security-sensitive work
|
|
928
|
+
- **max**: Extreme complexity, requires maximum reasoning capability (rarely needed)
|
|
929
|
+
|
|
930
|
+
## Comment to Analyze:
|
|
931
|
+
```
|
|
932
|
+
#{truncate_string(comment_text, max_length)}
|
|
933
|
+
```#{truncation_note}
|
|
934
|
+
|
|
935
|
+
## Your Task:
|
|
936
|
+
Determine which tier is most appropriate for handling this work.
|
|
937
|
+
Consider:
|
|
938
|
+
1. Task complexity (simple fix vs architectural change)
|
|
939
|
+
2. Reasoning depth required
|
|
940
|
+
3. Risk level (security, data integrity)
|
|
941
|
+
4. Domain expertise needed
|
|
942
|
+
|
|
943
|
+
Respond in this exact format:
|
|
944
|
+
TIER: <mini|standard|thinking|pro|max>
|
|
945
|
+
CONFIDENCE: <0.0-1.0>
|
|
946
|
+
REASONING: <brief explanation>
|
|
947
|
+
PROMPT
|
|
948
|
+
end
|
|
949
|
+
|
|
950
|
+
# Parse the ZFC response for tier determination
|
|
951
|
+
def parse_tier_determination_response(response)
|
|
952
|
+
tier_match = response.match(/TIER:\s*(\w+)/i)
|
|
953
|
+
confidence_match = response.match(/CONFIDENCE:\s*([-\d.]+)/i)
|
|
954
|
+
reasoning_match = response.match(/REASONING:\s*(.+)/mi)
|
|
955
|
+
|
|
956
|
+
tier = tier_match&.[](1)&.downcase || "standard"
|
|
957
|
+
raw_confidence = confidence_match&.[](1)&.to_f || DEFAULT_CONFIDENCE
|
|
958
|
+
# Clamp confidence to valid 0.0-1.0 range
|
|
959
|
+
confidence = raw_confidence.clamp(0.0, 1.0)
|
|
960
|
+
reasoning = reasoning_match&.[](1)&.strip || "No reasoning provided"
|
|
961
|
+
|
|
962
|
+
# Validate tier is in allowed list
|
|
963
|
+
tier = "standard" unless CapabilityRegistry::VALID_TIERS.include?(tier)
|
|
964
|
+
|
|
965
|
+
# In autonomous mode, cap at autonomous_max_tier
|
|
966
|
+
if @autonomous_mode && @registry.compare_tiers(tier, autonomous_max_tier) > 0
|
|
967
|
+
original_tier = tier
|
|
968
|
+
tier = autonomous_max_tier
|
|
969
|
+
reasoning += " (capped from #{original_tier} due to autonomous mode)"
|
|
970
|
+
|
|
971
|
+
Aidp.log_debug("thinking_depth_manager", "ZFC tier capped for autonomous mode",
|
|
972
|
+
original: original_tier,
|
|
973
|
+
capped_to: tier)
|
|
974
|
+
end
|
|
975
|
+
|
|
976
|
+
Aidp.log_info("thinking_depth_manager", "ZFC tier determination",
|
|
977
|
+
tier: tier,
|
|
978
|
+
confidence: confidence,
|
|
979
|
+
reasoning: truncate_string(reasoning, MAX_REASONING_DISPLAY_LENGTH))
|
|
980
|
+
|
|
981
|
+
{
|
|
982
|
+
tier: tier,
|
|
983
|
+
confidence: confidence,
|
|
984
|
+
reasoning: reasoning,
|
|
985
|
+
source: "zfc"
|
|
986
|
+
}
|
|
436
987
|
end
|
|
437
988
|
|
|
438
989
|
# Try to find a model in fallback tiers when requested tier has no models
|
|
@@ -583,6 +1134,14 @@ module Aidp
|
|
|
583
1134
|
display_message(" 2. Run 'aidp models list --tier=#{tier}' to see models for this tier", type: :info)
|
|
584
1135
|
display_message(" 3. Run 'aidp models validate' to check your configuration\n", type: :info)
|
|
585
1136
|
end
|
|
1137
|
+
|
|
1138
|
+
# Truncate string to max_length (plain Ruby replacement for ActiveSupport truncate)
|
|
1139
|
+
def truncate_string(string, max_length)
|
|
1140
|
+
return "" if string.nil?
|
|
1141
|
+
return string if string.length <= max_length
|
|
1142
|
+
|
|
1143
|
+
"#{string[0, max_length - 3]}..."
|
|
1144
|
+
end
|
|
586
1145
|
end
|
|
587
1146
|
end
|
|
588
1147
|
end
|