aidp 0.31.0 → 0.33.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/lib/aidp/analyze/feature_analyzer.rb +322 -320
- data/lib/aidp/auto_update/coordinator.rb +97 -7
- data/lib/aidp/auto_update.rb +0 -12
- data/lib/aidp/cli/devcontainer_commands.rb +0 -5
- data/lib/aidp/cli.rb +2 -1
- data/lib/aidp/comment_consolidator.rb +78 -0
- data/lib/aidp/concurrency.rb +0 -3
- data/lib/aidp/config.rb +0 -1
- data/lib/aidp/config_paths.rb +71 -0
- data/lib/aidp/execute/work_loop_runner.rb +394 -15
- data/lib/aidp/harness/ai_filter_factory.rb +285 -0
- data/lib/aidp/harness/config_schema.rb +97 -1
- data/lib/aidp/harness/config_validator.rb +1 -1
- data/lib/aidp/harness/configuration.rb +61 -5
- data/lib/aidp/harness/filter_definition.rb +212 -0
- data/lib/aidp/harness/generated_filter_strategy.rb +197 -0
- data/lib/aidp/harness/output_filter.rb +50 -25
- data/lib/aidp/harness/output_filter_config.rb +129 -0
- data/lib/aidp/harness/provider_manager.rb +128 -2
- data/lib/aidp/harness/provider_metrics.rb +5 -3
- data/lib/aidp/harness/runner.rb +0 -11
- data/lib/aidp/harness/test_runner.rb +179 -41
- data/lib/aidp/harness/thinking_depth_manager.rb +16 -0
- data/lib/aidp/harness/ui/navigation/submenu.rb +0 -2
- data/lib/aidp/loader.rb +195 -0
- data/lib/aidp/metadata/compiler.rb +29 -17
- data/lib/aidp/metadata/query.rb +1 -1
- data/lib/aidp/metadata/scanner.rb +8 -1
- data/lib/aidp/metadata/tool_metadata.rb +13 -13
- data/lib/aidp/metadata/validator.rb +10 -0
- data/lib/aidp/metadata.rb +16 -0
- data/lib/aidp/pr_worktree_manager.rb +582 -0
- data/lib/aidp/provider_manager.rb +1 -7
- data/lib/aidp/setup/wizard.rb +279 -9
- data/lib/aidp/skills.rb +0 -5
- data/lib/aidp/storage/csv_storage.rb +3 -0
- data/lib/aidp/style_guide/selector.rb +360 -0
- data/lib/aidp/tooling_detector.rb +283 -16
- data/lib/aidp/util.rb +11 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/change_request_processor.rb +152 -14
- data/lib/aidp/watch/repository_client.rb +41 -0
- data/lib/aidp/watch/runner.rb +29 -18
- data/lib/aidp/watch.rb +5 -7
- data/lib/aidp/workstream_cleanup.rb +0 -2
- data/lib/aidp/workstream_executor.rb +0 -4
- data/lib/aidp/worktree.rb +0 -1
- data/lib/aidp/worktree_branch_manager.rb +70 -1
- data/lib/aidp.rb +21 -106
- metadata +73 -36
- data/lib/aidp/config/paths.rb +0 -131
|
@@ -38,6 +38,7 @@ module Aidp
|
|
|
38
38
|
@model_fallback_chains = {}
|
|
39
39
|
@model_switching_enabled = true
|
|
40
40
|
@model_weights = {}
|
|
41
|
+
@model_denylist = Hash.new { |h, k| h[k] = [] }
|
|
41
42
|
@unavailable_cache = {}
|
|
42
43
|
@binary_check_cache = {}
|
|
43
44
|
@binary_check_ttl = 300 # seconds
|
|
@@ -387,10 +388,30 @@ module Aidp
|
|
|
387
388
|
# Check if model is configured for provider
|
|
388
389
|
return false unless model_configured?(provider_name, model_name)
|
|
389
390
|
|
|
391
|
+
# Skip models that were explicitly denied (e.g., unsupported by provider)
|
|
392
|
+
return false if model_denied?(provider_name, model_name)
|
|
393
|
+
|
|
390
394
|
# Check if model is not rate limited
|
|
391
395
|
!is_model_rate_limited?(provider_name, model_name)
|
|
392
396
|
end
|
|
393
397
|
|
|
398
|
+
# Check if a model has been denylisted for a provider
|
|
399
|
+
def model_denied?(provider_name, model_name)
|
|
400
|
+
@model_denylist[provider_name]&.include?(model_name)
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
# Add a model to the denylist for a provider (e.g., unsupported model errors)
|
|
404
|
+
def deny_model(provider_name, model_name, error: nil)
|
|
405
|
+
return if provider_name.nil? || model_name.nil?
|
|
406
|
+
return if model_denied?(provider_name, model_name)
|
|
407
|
+
|
|
408
|
+
@model_denylist[provider_name] << model_name
|
|
409
|
+
Aidp.log_debug("provider_manager", "model_denylisted",
|
|
410
|
+
provider: provider_name,
|
|
411
|
+
model: model_name,
|
|
412
|
+
error: error&.message)
|
|
413
|
+
end
|
|
414
|
+
|
|
394
415
|
# Check if model is configured for provider
|
|
395
416
|
def model_configured?(provider_name, model_name)
|
|
396
417
|
models = provider_models(provider_name)
|
|
@@ -1219,6 +1240,11 @@ module Aidp
|
|
|
1219
1240
|
total_tokens: m[:total_tokens] || 0,
|
|
1220
1241
|
last_used: m[:last_used]
|
|
1221
1242
|
}
|
|
1243
|
+
|
|
1244
|
+
# Set reason if currently rate limited (for display in Reason column)
|
|
1245
|
+
if rl && reset_in && reset_in > 0
|
|
1246
|
+
row[:unhealthy_reason] ||= "rate_limited"
|
|
1247
|
+
end
|
|
1222
1248
|
# Incorporate CLI check outcome into reason/availability if failing
|
|
1223
1249
|
unless cli_ok_prefetch
|
|
1224
1250
|
row[:available] = false
|
|
@@ -1279,6 +1305,7 @@ module Aidp
|
|
|
1279
1305
|
@model_health.clear
|
|
1280
1306
|
@model_metrics.clear
|
|
1281
1307
|
@model_fallback_chains.clear
|
|
1308
|
+
@model_denylist.clear
|
|
1282
1309
|
@model_rate_limit_info&.clear
|
|
1283
1310
|
@model_history&.clear
|
|
1284
1311
|
initialize_fallback_chains
|
|
@@ -1394,9 +1421,43 @@ module Aidp
|
|
|
1394
1421
|
|
|
1395
1422
|
# Execute a prompt with a specific provider
|
|
1396
1423
|
def execute_with_provider(provider_type, prompt, options = {})
|
|
1424
|
+
options = options.dup
|
|
1425
|
+
|
|
1397
1426
|
# Extract model from options if provided
|
|
1398
1427
|
model_name = options.delete(:model)
|
|
1399
|
-
retry_on_rate_limit = options.
|
|
1428
|
+
retry_on_rate_limit = if options.key?(:retry_on_rate_limit)
|
|
1429
|
+
options.delete(:retry_on_rate_limit) != false
|
|
1430
|
+
else
|
|
1431
|
+
true
|
|
1432
|
+
end
|
|
1433
|
+
retry_on_unsupported = if options.key?(:retry_on_unsupported)
|
|
1434
|
+
options.delete(:retry_on_unsupported) != false
|
|
1435
|
+
else
|
|
1436
|
+
true
|
|
1437
|
+
end
|
|
1438
|
+
tier = options[:tier]
|
|
1439
|
+
base_options = options.dup
|
|
1440
|
+
|
|
1441
|
+
if model_name && model_denied?(provider_type, model_name)
|
|
1442
|
+
alternate_model = select_alternate_model(provider_type, tier: tier, current_model: model_name)
|
|
1443
|
+
if alternate_model
|
|
1444
|
+
Aidp.logger.warn("provider_manager", "Model is denylisted, selecting alternate",
|
|
1445
|
+
provider: provider_type,
|
|
1446
|
+
model: model_name,
|
|
1447
|
+
alternate_model: alternate_model,
|
|
1448
|
+
tier: tier)
|
|
1449
|
+
|
|
1450
|
+
return execute_with_provider(
|
|
1451
|
+
provider_type,
|
|
1452
|
+
prompt,
|
|
1453
|
+
base_options.merge(
|
|
1454
|
+
model: alternate_model,
|
|
1455
|
+
retry_on_rate_limit: retry_on_rate_limit,
|
|
1456
|
+
retry_on_unsupported: false
|
|
1457
|
+
)
|
|
1458
|
+
)
|
|
1459
|
+
end
|
|
1460
|
+
end
|
|
1400
1461
|
|
|
1401
1462
|
# Create provider factory instance
|
|
1402
1463
|
provider_factory = ProviderFactory.new
|
|
@@ -1418,7 +1479,7 @@ module Aidp
|
|
|
1418
1479
|
prompt_length: prompt.length)
|
|
1419
1480
|
|
|
1420
1481
|
# Execute the prompt with the provider
|
|
1421
|
-
result = provider.send_message(prompt: prompt, session: nil)
|
|
1482
|
+
result = provider.send_message(prompt: prompt, session: nil, options: options)
|
|
1422
1483
|
|
|
1423
1484
|
# Return structured result
|
|
1424
1485
|
{
|
|
@@ -1436,6 +1497,32 @@ module Aidp
|
|
|
1436
1497
|
rescue => e
|
|
1437
1498
|
log_rescue(e, component: "provider_manager", action: "execute_with_provider", fallback: "error_result", provider: provider_type, model: model_name, prompt_length: prompt.length)
|
|
1438
1499
|
|
|
1500
|
+
if unsupported_model_error?(e, model_name)
|
|
1501
|
+
deny_model(provider_type, model_name, error: e)
|
|
1502
|
+
|
|
1503
|
+
if retry_on_unsupported
|
|
1504
|
+
alternate_model = select_alternate_model(provider_type, tier: tier, current_model: model_name)
|
|
1505
|
+
|
|
1506
|
+
if alternate_model
|
|
1507
|
+
Aidp.logger.info("provider_manager", "Retrying with alternate model after unsupported model error",
|
|
1508
|
+
provider: provider_type,
|
|
1509
|
+
original_model: model_name,
|
|
1510
|
+
alternate_model: alternate_model,
|
|
1511
|
+
tier: tier)
|
|
1512
|
+
|
|
1513
|
+
return execute_with_provider(
|
|
1514
|
+
provider_type,
|
|
1515
|
+
prompt,
|
|
1516
|
+
base_options.merge(
|
|
1517
|
+
model: alternate_model,
|
|
1518
|
+
retry_on_rate_limit: retry_on_rate_limit,
|
|
1519
|
+
retry_on_unsupported: false
|
|
1520
|
+
)
|
|
1521
|
+
)
|
|
1522
|
+
end
|
|
1523
|
+
end
|
|
1524
|
+
end
|
|
1525
|
+
|
|
1439
1526
|
# Detect rate limit / quota errors and attempt fallback
|
|
1440
1527
|
error_message = e.message.to_s.downcase
|
|
1441
1528
|
is_rate_limit = error_message.include?("rate limit") ||
|
|
@@ -1583,6 +1670,45 @@ module Aidp
|
|
|
1583
1670
|
((order[a] || 0) >= (order[b] || 0)) ? a : b
|
|
1584
1671
|
end
|
|
1585
1672
|
|
|
1673
|
+
# Detect unsupported/invalid model errors to avoid reusing the model
|
|
1674
|
+
def unsupported_model_error?(error, model_name)
|
|
1675
|
+
return false if model_name.nil?
|
|
1676
|
+
|
|
1677
|
+
message = error&.message.to_s.downcase
|
|
1678
|
+
return false if message.empty?
|
|
1679
|
+
return false unless message.include?(model_name.to_s.downcase)
|
|
1680
|
+
|
|
1681
|
+
(message.include?("unsupported") && message.include?("model")) ||
|
|
1682
|
+
(message.include?("model") && message.include?("not supported")) ||
|
|
1683
|
+
message.include?("invalid model")
|
|
1684
|
+
end
|
|
1685
|
+
|
|
1686
|
+
# Select an alternate model for the provider and tier, excluding denylisted/current models
|
|
1687
|
+
def select_alternate_model(provider_name, tier:, current_model: nil)
|
|
1688
|
+
candidates = []
|
|
1689
|
+
|
|
1690
|
+
if tier && @configuration.respond_to?(:models_for_tier)
|
|
1691
|
+
tier_models = Array(@configuration.models_for_tier(tier, provider_name)).map(&:to_s)
|
|
1692
|
+
candidates.concat(tier_models)
|
|
1693
|
+
end
|
|
1694
|
+
|
|
1695
|
+
if candidates.empty? && @configuration.respond_to?(:provider_models)
|
|
1696
|
+
provider_models = Array(@configuration.provider_models(provider_name)).map(&:to_s)
|
|
1697
|
+
candidates.concat(provider_models)
|
|
1698
|
+
end
|
|
1699
|
+
|
|
1700
|
+
candidates = candidates.uniq
|
|
1701
|
+
return nil if candidates.empty?
|
|
1702
|
+
|
|
1703
|
+
excluded = Array(current_model).compact + Array(@model_denylist[provider_name])
|
|
1704
|
+
candidates.reject! { |model| excluded.include?(model) }
|
|
1705
|
+
candidates.first
|
|
1706
|
+
rescue => e
|
|
1707
|
+
log_rescue(e, component: "provider_manager", action: "select_alternate_model", fallback: nil,
|
|
1708
|
+
provider: provider_name, tier: tier, current_model: current_model)
|
|
1709
|
+
nil
|
|
1710
|
+
end
|
|
1711
|
+
|
|
1586
1712
|
public
|
|
1587
1713
|
|
|
1588
1714
|
# Log provider switch
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "yaml"
|
|
4
4
|
require "fileutils"
|
|
5
5
|
require_relative "../rescue_logging"
|
|
6
|
+
require_relative "../util"
|
|
6
7
|
|
|
7
8
|
module Aidp
|
|
8
9
|
module Harness
|
|
@@ -14,9 +15,10 @@ module Aidp
|
|
|
14
15
|
attr_reader :project_dir, :metrics_file, :rate_limit_file
|
|
15
16
|
|
|
16
17
|
def initialize(project_dir)
|
|
17
|
-
|
|
18
|
-
@
|
|
19
|
-
@
|
|
18
|
+
# Store metrics at the repository root so different worktrees/modes share state
|
|
19
|
+
@project_dir = Aidp::Util.find_project_root(project_dir)
|
|
20
|
+
@metrics_file = File.join(@project_dir, ".aidp", "provider_metrics.yml")
|
|
21
|
+
@rate_limit_file = File.join(@project_dir, ".aidp", "provider_rate_limits.yml")
|
|
20
22
|
ensure_directory
|
|
21
23
|
end
|
|
22
24
|
|
data/lib/aidp/harness/runner.rb
CHANGED
|
@@ -2,16 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require "timeout"
|
|
4
4
|
require "json"
|
|
5
|
-
require_relative "configuration"
|
|
6
|
-
require_relative "state_manager"
|
|
7
|
-
require_relative "condition_detector"
|
|
8
|
-
require_relative "provider_manager"
|
|
9
|
-
require_relative "simple_user_interface"
|
|
10
|
-
require_relative "error_handler"
|
|
11
|
-
require_relative "status_display"
|
|
12
|
-
require_relative "completion_checker"
|
|
13
|
-
require_relative "../concurrency"
|
|
14
|
-
require_relative "../errors"
|
|
15
5
|
|
|
16
6
|
module Aidp
|
|
17
7
|
module Harness
|
|
@@ -61,7 +51,6 @@ module Aidp
|
|
|
61
51
|
# Use ZFC-enabled condition detector
|
|
62
52
|
# ZfcConditionDetector will create its own ProviderFactory if needed
|
|
63
53
|
# Falls back to legacy pattern matching when ZFC is disabled
|
|
64
|
-
require_relative "zfc_condition_detector"
|
|
65
54
|
@condition_detector = ZfcConditionDetector.new(@configuration)
|
|
66
55
|
|
|
67
56
|
@user_interface = SimpleUserInterface.new
|
|
@@ -3,16 +3,19 @@
|
|
|
3
3
|
require "open3"
|
|
4
4
|
require_relative "../tooling_detector"
|
|
5
5
|
require_relative "output_filter"
|
|
6
|
+
require_relative "output_filter_config"
|
|
6
7
|
|
|
7
8
|
module Aidp
|
|
8
9
|
module Harness
|
|
9
10
|
# Executes test and linter commands configured in aidp.yml
|
|
10
11
|
# Returns results with exit status and output
|
|
12
|
+
# Applies intelligent output filtering to reduce token consumption
|
|
11
13
|
class TestRunner
|
|
12
14
|
def initialize(project_dir, config)
|
|
13
15
|
@project_dir = project_dir
|
|
14
16
|
@config = config
|
|
15
17
|
@iteration_count = 0
|
|
18
|
+
@filter_stats = {total_input_bytes: 0, total_output_bytes: 0}
|
|
16
19
|
end
|
|
17
20
|
|
|
18
21
|
# Run all configured tests
|
|
@@ -47,6 +50,29 @@ module Aidp
|
|
|
47
50
|
run_command_category(:documentation, "Documentation")
|
|
48
51
|
end
|
|
49
52
|
|
|
53
|
+
# Preview the commands that will run for each category so callers can log intent
|
|
54
|
+
# Returns a hash of category => array of normalized command entries
|
|
55
|
+
def planned_commands
|
|
56
|
+
{
|
|
57
|
+
tests: resolved_commands(:test),
|
|
58
|
+
lints: resolved_commands(:lint),
|
|
59
|
+
formatters: resolved_commands(:formatter),
|
|
60
|
+
builds: resolved_commands(:build),
|
|
61
|
+
docs: resolved_commands(:documentation)
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Get current iteration count
|
|
66
|
+
attr_reader :iteration_count
|
|
67
|
+
|
|
68
|
+
# Get filtering statistics
|
|
69
|
+
attr_reader :filter_stats
|
|
70
|
+
|
|
71
|
+
# Reset iteration counter (useful for testing)
|
|
72
|
+
def reset_iteration_count
|
|
73
|
+
@iteration_count = 0
|
|
74
|
+
end
|
|
75
|
+
|
|
50
76
|
private
|
|
51
77
|
|
|
52
78
|
# Run commands for a specific category (test, lint, formatter, build, documentation)
|
|
@@ -59,6 +85,12 @@ module Aidp
|
|
|
59
85
|
# Determine output mode based on category
|
|
60
86
|
mode = determine_output_mode(category)
|
|
61
87
|
|
|
88
|
+
Aidp.log_debug("test_runner", "running_category",
|
|
89
|
+
category: category,
|
|
90
|
+
command_count: commands.length,
|
|
91
|
+
iteration: @iteration_count,
|
|
92
|
+
output_mode: mode)
|
|
93
|
+
|
|
62
94
|
# Execute all commands
|
|
63
95
|
results = commands.map do |cmd_config|
|
|
64
96
|
# Handle both string commands (legacy) and hash format (new)
|
|
@@ -71,6 +103,23 @@ module Aidp
|
|
|
71
103
|
end
|
|
72
104
|
end
|
|
73
105
|
|
|
106
|
+
aggregate_results(results, display_name, mode: mode)
|
|
107
|
+
rescue NameError
|
|
108
|
+
# Logging not available
|
|
109
|
+
commands = resolved_commands(category)
|
|
110
|
+
return {success: true, output: "", failures: [], required_failures: []} if commands.empty?
|
|
111
|
+
|
|
112
|
+
mode = determine_output_mode(category)
|
|
113
|
+
results = commands.map do |cmd_config|
|
|
114
|
+
if cmd_config.is_a?(String)
|
|
115
|
+
result = execute_command(cmd_config, category.to_s)
|
|
116
|
+
result.merge(required: true)
|
|
117
|
+
else
|
|
118
|
+
result = execute_command(cmd_config[:command], category.to_s)
|
|
119
|
+
result.merge(required: cmd_config[:required])
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
74
123
|
aggregate_results(results, display_name, mode: mode)
|
|
75
124
|
end
|
|
76
125
|
|
|
@@ -107,6 +156,11 @@ module Aidp
|
|
|
107
156
|
(optional_failures.any? ? "\n" + format_failures(optional_failures, "#{category} - Optional", mode: mode) : "")
|
|
108
157
|
end
|
|
109
158
|
|
|
159
|
+
# Add filtering summary if filtering was applied
|
|
160
|
+
if mode != :full && all_failures.any?
|
|
161
|
+
output += "\n[Output filtered: mode=#{mode}]"
|
|
162
|
+
end
|
|
163
|
+
|
|
110
164
|
{
|
|
111
165
|
success: success,
|
|
112
166
|
output: output,
|
|
@@ -124,9 +178,12 @@ module Aidp
|
|
|
124
178
|
output << "Exit Code: #{failure[:exit_code]}"
|
|
125
179
|
output << "--- Output ---"
|
|
126
180
|
|
|
181
|
+
# Detect framework for filtering
|
|
182
|
+
framework = detect_framework_from_command(failure[:command])
|
|
183
|
+
|
|
127
184
|
# Apply filtering based on mode and framework
|
|
128
|
-
filtered_stdout = filter_output(failure[:stdout], mode,
|
|
129
|
-
filtered_stderr = filter_output(failure[:stderr], mode, :unknown)
|
|
185
|
+
filtered_stdout = filter_output(failure[:stdout], mode, framework, :test)
|
|
186
|
+
filtered_stderr = filter_output(failure[:stderr], mode, :unknown, :lint)
|
|
130
187
|
|
|
131
188
|
output << filtered_stdout unless filtered_stdout.strip.empty?
|
|
132
189
|
output << filtered_stderr unless filtered_stderr.strip.empty?
|
|
@@ -136,67 +193,139 @@ module Aidp
|
|
|
136
193
|
output.join("\n")
|
|
137
194
|
end
|
|
138
195
|
|
|
139
|
-
def filter_output(raw_output, mode, framework)
|
|
196
|
+
def filter_output(raw_output, mode, framework, category)
|
|
140
197
|
return raw_output if mode == :full || raw_output.nil? || raw_output.empty?
|
|
141
198
|
|
|
142
|
-
|
|
199
|
+
# Build filter config from configuration or defaults
|
|
200
|
+
filter_config = build_filter_config(mode, category)
|
|
201
|
+
|
|
202
|
+
# Load AI-generated filter definition if available (AGD pattern)
|
|
203
|
+
filter_definition = load_filter_definition_for(category)
|
|
204
|
+
|
|
205
|
+
# Track input size for stats
|
|
206
|
+
@filter_stats[:total_input_bytes] += raw_output.bytesize
|
|
207
|
+
|
|
208
|
+
filter = OutputFilter.new(filter_config, filter_definition: filter_definition)
|
|
209
|
+
filtered = filter.filter(raw_output, framework: framework)
|
|
210
|
+
|
|
211
|
+
# Track output size for stats
|
|
212
|
+
@filter_stats[:total_output_bytes] += filtered.bytesize
|
|
213
|
+
|
|
214
|
+
Aidp.log_debug("test_runner", "output_filtered",
|
|
215
|
+
framework: framework,
|
|
143
216
|
mode: mode,
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
217
|
+
input_size: raw_output.bytesize,
|
|
218
|
+
output_size: filtered.bytesize,
|
|
219
|
+
has_custom_definition: !filter_definition.nil?)
|
|
148
220
|
|
|
149
|
-
|
|
150
|
-
filter.filter(raw_output, framework: framework)
|
|
221
|
+
filtered
|
|
151
222
|
rescue NameError
|
|
152
223
|
# Logging infrastructure not available
|
|
153
224
|
raw_output
|
|
154
225
|
rescue => e
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
226
|
+
begin
|
|
227
|
+
Aidp.log_warn("test_runner", "filter_failed",
|
|
228
|
+
error: e.message,
|
|
229
|
+
framework: framework)
|
|
230
|
+
rescue NameError
|
|
231
|
+
# Logging not available
|
|
232
|
+
end
|
|
158
233
|
raw_output # Fallback to unfiltered on error
|
|
159
234
|
end
|
|
160
235
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
when
|
|
170
|
-
|
|
171
|
-
else
|
|
172
|
-
:unknown
|
|
236
|
+
# Load AI-generated filter definition for a category
|
|
237
|
+
# @param category [Symbol] :test or :lint
|
|
238
|
+
# @return [FilterDefinition, nil]
|
|
239
|
+
def load_filter_definition_for(category)
|
|
240
|
+
return nil unless @config.respond_to?(:filter_definition_for)
|
|
241
|
+
|
|
242
|
+
# Map category to definition key (matches setup wizard keys)
|
|
243
|
+
definition_key = case category
|
|
244
|
+
when :test then "unit_test"
|
|
245
|
+
when :lint then "lint"
|
|
246
|
+
else category.to_s
|
|
173
247
|
end
|
|
248
|
+
|
|
249
|
+
@config.filter_definition_for(definition_key)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def build_filter_config(mode, category)
|
|
253
|
+
config_hash = {
|
|
254
|
+
mode: mode,
|
|
255
|
+
include_context: output_filtering_include_context,
|
|
256
|
+
context_lines: output_filtering_context_lines,
|
|
257
|
+
max_lines: max_output_lines_for_category(category)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
OutputFilterConfig.from_hash(config_hash)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def detect_framework_from_command(command)
|
|
264
|
+
# Use ToolingDetector's enhanced framework detection
|
|
265
|
+
Aidp::ToolingDetector.framework_from_command(command)
|
|
174
266
|
end
|
|
175
267
|
|
|
176
268
|
def determine_output_mode(category)
|
|
177
269
|
# Check config for category-specific mode
|
|
178
270
|
case category
|
|
179
271
|
when :test
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
:full
|
|
186
|
-
end
|
|
272
|
+
configured_mode = test_output_mode_from_config
|
|
273
|
+
return configured_mode if configured_mode
|
|
274
|
+
|
|
275
|
+
# Default: full on first iteration, failures_only after
|
|
276
|
+
(@iteration_count > 1) ? :failures_only : :full
|
|
187
277
|
when :lint
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
:full
|
|
194
|
-
end
|
|
278
|
+
configured_mode = lint_output_mode_from_config
|
|
279
|
+
return configured_mode if configured_mode
|
|
280
|
+
|
|
281
|
+
# Default: full on first iteration, failures_only after
|
|
282
|
+
(@iteration_count > 1) ? :failures_only : :full
|
|
195
283
|
else
|
|
196
284
|
:full
|
|
197
285
|
end
|
|
198
286
|
end
|
|
199
287
|
|
|
288
|
+
def test_output_mode_from_config
|
|
289
|
+
return nil unless @config.respond_to?(:test_output_mode)
|
|
290
|
+
mode = @config.test_output_mode
|
|
291
|
+
return nil if mode.nil?
|
|
292
|
+
mode.to_sym
|
|
293
|
+
rescue
|
|
294
|
+
nil
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def lint_output_mode_from_config
|
|
298
|
+
return nil unless @config.respond_to?(:lint_output_mode)
|
|
299
|
+
mode = @config.lint_output_mode
|
|
300
|
+
return nil if mode.nil?
|
|
301
|
+
mode.to_sym
|
|
302
|
+
rescue
|
|
303
|
+
nil
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def max_output_lines_for_category(category)
|
|
307
|
+
case category
|
|
308
|
+
when :test
|
|
309
|
+
return @config.test_max_output_lines if @config.respond_to?(:test_max_output_lines) && @config.test_max_output_lines
|
|
310
|
+
OutputFilterConfig::DEFAULT_MAX_LINES
|
|
311
|
+
when :lint
|
|
312
|
+
return @config.lint_max_output_lines if @config.respond_to?(:lint_max_output_lines) && @config.lint_max_output_lines
|
|
313
|
+
300 # Default smaller for lint output
|
|
314
|
+
else
|
|
315
|
+
OutputFilterConfig::DEFAULT_MAX_LINES
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def output_filtering_include_context
|
|
320
|
+
return @config.output_filtering_include_context if @config.respond_to?(:output_filtering_include_context)
|
|
321
|
+
OutputFilterConfig::DEFAULT_INCLUDE_CONTEXT
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def output_filtering_context_lines
|
|
325
|
+
return @config.output_filtering_context_lines if @config.respond_to?(:output_filtering_context_lines)
|
|
326
|
+
OutputFilterConfig::DEFAULT_CONTEXT_LINES
|
|
327
|
+
end
|
|
328
|
+
|
|
200
329
|
# Resolve commands for a specific category
|
|
201
330
|
# Returns normalized command configs (array of {command:, required:} hashes)
|
|
202
331
|
def resolved_commands(category)
|
|
@@ -239,8 +368,17 @@ module Aidp
|
|
|
239
368
|
def detected_tooling
|
|
240
369
|
@detected_tooling ||= Aidp::ToolingDetector.detect(@project_dir)
|
|
241
370
|
rescue => e
|
|
242
|
-
|
|
243
|
-
|
|
371
|
+
begin
|
|
372
|
+
Aidp.log_warn("test_runner", "tooling_detection_failed", error: e.message)
|
|
373
|
+
rescue NameError
|
|
374
|
+
# Logging not available
|
|
375
|
+
end
|
|
376
|
+
Aidp::ToolingDetector::Result.new(
|
|
377
|
+
test_commands: [],
|
|
378
|
+
lint_commands: [],
|
|
379
|
+
formatter_commands: [],
|
|
380
|
+
frameworks: {}
|
|
381
|
+
)
|
|
244
382
|
end
|
|
245
383
|
|
|
246
384
|
def log_fallback(type, commands)
|
|
@@ -152,6 +152,15 @@ module Aidp
|
|
|
152
152
|
def select_model_for_tier(tier = nil, provider: nil)
|
|
153
153
|
tier ||= current_tier
|
|
154
154
|
validate_tier!(tier)
|
|
155
|
+
provider_has_no_tiers = provider && configuration.configured_tiers(provider).empty?
|
|
156
|
+
provider_has_catalog_models = provider && !@registry.models_for_provider(provider).empty?
|
|
157
|
+
|
|
158
|
+
if provider_has_no_tiers && !provider_has_catalog_models
|
|
159
|
+
Aidp.log_info("thinking_depth_manager", "No configured tiers for provider, deferring to provider auto model selection",
|
|
160
|
+
requested_tier: tier,
|
|
161
|
+
provider: provider)
|
|
162
|
+
return [provider, nil, {auto_model: true, reason: "provider_has_no_tiers"}]
|
|
163
|
+
end
|
|
155
164
|
|
|
156
165
|
# First, try to get models from user's configuration for this tier and provider
|
|
157
166
|
if provider
|
|
@@ -270,6 +279,13 @@ module Aidp
|
|
|
270
279
|
|
|
271
280
|
result = try_fallback_tiers(tier, provider)
|
|
272
281
|
|
|
282
|
+
if provider_has_no_tiers && result.nil? && provider
|
|
283
|
+
Aidp.log_info("thinking_depth_manager", "No configured tiers for provider, deferring to provider auto model selection",
|
|
284
|
+
requested_tier: tier,
|
|
285
|
+
provider: provider)
|
|
286
|
+
return [provider, nil, {auto_model: true, reason: "provider_has_no_tiers"}]
|
|
287
|
+
end
|
|
288
|
+
|
|
273
289
|
unless result
|
|
274
290
|
# Enhanced error message with discovery hints
|
|
275
291
|
display_enhanced_tier_error(tier, provider)
|