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
|
@@ -12,13 +12,14 @@ module Aidp
|
|
|
12
12
|
class ProviderMetrics
|
|
13
13
|
include Aidp::RescueLogging
|
|
14
14
|
|
|
15
|
-
attr_reader :project_dir, :metrics_file, :rate_limit_file
|
|
15
|
+
attr_reader :project_dir, :metrics_file, :rate_limit_file, :usage_tracking_dir
|
|
16
16
|
|
|
17
17
|
def initialize(project_dir)
|
|
18
18
|
# Store metrics at the repository root so different worktrees/modes share state
|
|
19
19
|
@project_dir = Aidp::Util.find_project_root(project_dir)
|
|
20
20
|
@metrics_file = File.join(@project_dir, ".aidp", "provider_metrics.yml")
|
|
21
21
|
@rate_limit_file = File.join(@project_dir, ".aidp", "provider_rate_limits.yml")
|
|
22
|
+
@usage_tracking_dir = File.join(@project_dir, ".aidp", "usage_tracking")
|
|
22
23
|
ensure_directory
|
|
23
24
|
end
|
|
24
25
|
|
|
@@ -78,6 +79,76 @@ module Aidp
|
|
|
78
79
|
def clear
|
|
79
80
|
File.delete(@metrics_file) if File.exist?(@metrics_file)
|
|
80
81
|
File.delete(@rate_limit_file) if File.exist?(@rate_limit_file)
|
|
82
|
+
clear_usage_tracking
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Save usage tracking data for a provider
|
|
86
|
+
#
|
|
87
|
+
# @param provider_name [String] Name of the provider
|
|
88
|
+
# @param usage_data [Hash] Usage tracking data
|
|
89
|
+
def save_usage_tracking(provider_name, usage_data)
|
|
90
|
+
return if usage_data.nil? || usage_data.empty?
|
|
91
|
+
|
|
92
|
+
ensure_usage_tracking_directory
|
|
93
|
+
usage_file = usage_tracking_file(provider_name)
|
|
94
|
+
|
|
95
|
+
# Serialize Time objects for YAML
|
|
96
|
+
serializable_data = serialize_usage_tracking(usage_data)
|
|
97
|
+
|
|
98
|
+
# Write atomically using temp file + rename
|
|
99
|
+
temp_file = "#{usage_file}.tmp"
|
|
100
|
+
File.write(temp_file, YAML.dump(serializable_data))
|
|
101
|
+
File.rename(temp_file, usage_file)
|
|
102
|
+
rescue => e
|
|
103
|
+
log_rescue(e, component: "provider_metrics", action: "save_usage_tracking", fallback: nil)
|
|
104
|
+
File.delete(temp_file) if defined?(temp_file) && File.exist?(temp_file)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Load usage tracking data for a provider
|
|
108
|
+
#
|
|
109
|
+
# @param provider_name [String] Name of the provider
|
|
110
|
+
# @return [Hash] Usage tracking data or empty hash
|
|
111
|
+
def load_usage_tracking(provider_name)
|
|
112
|
+
usage_file = usage_tracking_file(provider_name)
|
|
113
|
+
return {} unless File.exist?(usage_file)
|
|
114
|
+
|
|
115
|
+
data = YAML.safe_load_file(usage_file, permitted_classes: [Time, Date, Symbol], aliases: true)
|
|
116
|
+
return {} unless data.is_a?(Hash)
|
|
117
|
+
|
|
118
|
+
deserialize_usage_tracking(data)
|
|
119
|
+
rescue => e
|
|
120
|
+
log_rescue(e, component: "provider_metrics", action: "load_usage_tracking", fallback: {})
|
|
121
|
+
{}
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Clear usage tracking data for a specific provider
|
|
125
|
+
#
|
|
126
|
+
# @param provider_name [String] Name of the provider (optional, clears all if nil)
|
|
127
|
+
def clear_usage_tracking(provider_name = nil)
|
|
128
|
+
if provider_name
|
|
129
|
+
usage_file = usage_tracking_file(provider_name)
|
|
130
|
+
File.delete(usage_file) if File.exist?(usage_file)
|
|
131
|
+
else
|
|
132
|
+
# Clear all usage tracking files
|
|
133
|
+
return unless File.directory?(@usage_tracking_dir)
|
|
134
|
+
|
|
135
|
+
Dir.glob(File.join(@usage_tracking_dir, "*.yml")).each do |file|
|
|
136
|
+
File.delete(file)
|
|
137
|
+
rescue => e
|
|
138
|
+
log_rescue(e, component: "provider_metrics", action: "clear_usage_file", fallback: nil)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# List all providers with usage tracking data
|
|
144
|
+
#
|
|
145
|
+
# @return [Array<String>] Array of provider names
|
|
146
|
+
def providers_with_usage_tracking
|
|
147
|
+
return [] unless File.directory?(@usage_tracking_dir)
|
|
148
|
+
|
|
149
|
+
Dir.glob(File.join(@usage_tracking_dir, "*.yml")).map do |file|
|
|
150
|
+
File.basename(file, ".yml")
|
|
151
|
+
end
|
|
81
152
|
end
|
|
82
153
|
|
|
83
154
|
private
|
|
@@ -87,6 +158,37 @@ module Aidp
|
|
|
87
158
|
FileUtils.mkdir_p(aidp_dir) unless File.directory?(aidp_dir)
|
|
88
159
|
end
|
|
89
160
|
|
|
161
|
+
def ensure_usage_tracking_directory
|
|
162
|
+
FileUtils.mkdir_p(@usage_tracking_dir) unless File.directory?(@usage_tracking_dir)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def usage_tracking_file(provider_name)
|
|
166
|
+
File.join(@usage_tracking_dir, "#{provider_name}.yml")
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def serialize_usage_tracking(data)
|
|
170
|
+
deep_transform_values(data) do |value|
|
|
171
|
+
value.is_a?(Time) ? value.iso8601 : value
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def deserialize_usage_tracking(data)
|
|
176
|
+
deep_transform_values(data) do |value|
|
|
177
|
+
parse_time_if_string(value)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def deep_transform_values(obj, &block)
|
|
182
|
+
case obj
|
|
183
|
+
when Hash
|
|
184
|
+
obj.transform_values { |v| deep_transform_values(v, &block) }
|
|
185
|
+
when Array
|
|
186
|
+
obj.map { |v| deep_transform_values(v, &block) }
|
|
187
|
+
else
|
|
188
|
+
yield(obj)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
90
192
|
def serialize_metrics(metrics_hash)
|
|
91
193
|
metrics_hash.transform_values do |provider_metrics|
|
|
92
194
|
next provider_metrics unless provider_metrics.is_a?(Hash)
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aidp
|
|
4
|
+
module Harness
|
|
5
|
+
# Optimizes RSpec command execution by using --only-failures on subsequent runs.
|
|
6
|
+
# This dramatically reduces both execution time and output volume when fixing
|
|
7
|
+
# failing tests during work loops.
|
|
8
|
+
#
|
|
9
|
+
# Requirements:
|
|
10
|
+
# - RSpec must be configured with example_status_persistence_file_path in spec_helper.rb
|
|
11
|
+
# - A .rspec_status file (or configured path) must exist from a previous run
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# optimizer = RSpecCommandOptimizer.new("/path/to/project")
|
|
15
|
+
# result = optimizer.optimize_command("bundle exec rspec", iteration: 2, had_failures: true)
|
|
16
|
+
# # => "bundle exec rspec --only-failures"
|
|
17
|
+
#
|
|
18
|
+
class RSpecCommandOptimizer
|
|
19
|
+
# Default paths where RSpec stores example status
|
|
20
|
+
DEFAULT_STATUS_PATHS = [
|
|
21
|
+
".rspec_status",
|
|
22
|
+
"tmp/.rspec_status",
|
|
23
|
+
"spec/.rspec_status"
|
|
24
|
+
].freeze
|
|
25
|
+
|
|
26
|
+
# Common spec_helper paths to check for persistence configuration
|
|
27
|
+
SPEC_HELPER_PATHS = [
|
|
28
|
+
"spec/spec_helper.rb",
|
|
29
|
+
"spec/rails_helper.rb"
|
|
30
|
+
].freeze
|
|
31
|
+
|
|
32
|
+
attr_reader :project_dir
|
|
33
|
+
|
|
34
|
+
def initialize(project_dir)
|
|
35
|
+
@project_dir = project_dir
|
|
36
|
+
@status_file_cache = nil
|
|
37
|
+
@persistence_configured_cache = nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Optimize an RSpec command based on iteration context
|
|
41
|
+
#
|
|
42
|
+
# @param command [String] The original RSpec command
|
|
43
|
+
# @param iteration [Integer] Current iteration number (1-indexed)
|
|
44
|
+
# @param had_failures [Boolean] Whether the previous iteration had test failures
|
|
45
|
+
# @return [Hash] { command: String, optimized: Boolean, reason: String }
|
|
46
|
+
def optimize_command(command, iteration:, had_failures:)
|
|
47
|
+
return skip_optimization(command, "not an RSpec command") unless rspec_command?(command)
|
|
48
|
+
return skip_optimization(command, "first iteration") if iteration <= 1
|
|
49
|
+
return skip_optimization(command, "no previous failures") unless had_failures
|
|
50
|
+
return skip_optimization(command, "already has --only-failures") if command.include?("--only-failures")
|
|
51
|
+
|
|
52
|
+
status_file = find_status_file
|
|
53
|
+
unless status_file
|
|
54
|
+
Aidp.log_debug("rspec_optimizer", "no_status_file",
|
|
55
|
+
project_dir: @project_dir,
|
|
56
|
+
checked_paths: DEFAULT_STATUS_PATHS)
|
|
57
|
+
|
|
58
|
+
return skip_optimization(command, "no .rspec_status file found - see setup instructions")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
optimized_command = add_only_failures_flag(command)
|
|
62
|
+
|
|
63
|
+
Aidp.log_info("rspec_optimizer", "command_optimized",
|
|
64
|
+
original: command,
|
|
65
|
+
optimized: optimized_command,
|
|
66
|
+
iteration: iteration,
|
|
67
|
+
status_file: status_file)
|
|
68
|
+
|
|
69
|
+
{
|
|
70
|
+
command: optimized_command,
|
|
71
|
+
optimized: true,
|
|
72
|
+
reason: "using --only-failures (iteration #{iteration})",
|
|
73
|
+
status_file: status_file
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Check if RSpec persistence is properly configured in this project
|
|
78
|
+
#
|
|
79
|
+
# @return [Hash] { configured: Boolean, file_path: String, message: String }
|
|
80
|
+
def check_persistence_configuration
|
|
81
|
+
return @persistence_configured_cache if @persistence_configured_cache
|
|
82
|
+
|
|
83
|
+
@persistence_configured_cache = begin
|
|
84
|
+
# Check if any spec_helper configures persistence
|
|
85
|
+
configured_in_spec_helper = SPEC_HELPER_PATHS.any? do |path|
|
|
86
|
+
full_path = File.join(@project_dir, path)
|
|
87
|
+
next false unless File.exist?(full_path)
|
|
88
|
+
|
|
89
|
+
content = File.read(full_path, encoding: "UTF-8")
|
|
90
|
+
content.include?("example_status_persistence_file_path")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
if configured_in_spec_helper
|
|
94
|
+
status_file = find_status_file
|
|
95
|
+
{
|
|
96
|
+
configured: true,
|
|
97
|
+
file_path: status_file,
|
|
98
|
+
message: "RSpec persistence configured" + (status_file ? " (#{status_file} exists)" : " (waiting for first run)")
|
|
99
|
+
}
|
|
100
|
+
else
|
|
101
|
+
{
|
|
102
|
+
configured: false,
|
|
103
|
+
file_path: nil,
|
|
104
|
+
message: "RSpec persistence not configured. Add to spec_helper.rb:\n" \
|
|
105
|
+
" config.example_status_persistence_file_path = '.rspec_status'"
|
|
106
|
+
}
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Find the RSpec status file if it exists
|
|
112
|
+
#
|
|
113
|
+
# @return [String, nil] Path to status file or nil if not found
|
|
114
|
+
def find_status_file
|
|
115
|
+
return @status_file_cache if @status_file_cache
|
|
116
|
+
|
|
117
|
+
@status_file_cache = DEFAULT_STATUS_PATHS.find do |path|
|
|
118
|
+
full_path = File.join(@project_dir, path)
|
|
119
|
+
File.exist?(full_path)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Check if command is an RSpec command
|
|
124
|
+
#
|
|
125
|
+
# @param command [String] Command to check
|
|
126
|
+
# @return [Boolean]
|
|
127
|
+
def rspec_command?(command)
|
|
128
|
+
return false unless command.is_a?(String)
|
|
129
|
+
command.downcase.include?("rspec")
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Reset caches (useful after test runs that may create status file)
|
|
133
|
+
def reset_caches!
|
|
134
|
+
@status_file_cache = nil
|
|
135
|
+
@persistence_configured_cache = nil
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
private
|
|
139
|
+
|
|
140
|
+
def skip_optimization(command, reason)
|
|
141
|
+
{
|
|
142
|
+
command: command,
|
|
143
|
+
optimized: false,
|
|
144
|
+
reason: reason
|
|
145
|
+
}
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def add_only_failures_flag(command)
|
|
149
|
+
# Insert --only-failures before any file arguments
|
|
150
|
+
# "bundle exec rspec" -> "bundle exec rspec --only-failures"
|
|
151
|
+
# "bundle exec rspec spec/foo_spec.rb" -> "bundle exec rspec --only-failures spec/foo_spec.rb"
|
|
152
|
+
parts = command.split(/\s+/)
|
|
153
|
+
rspec_index = parts.index { |p| p.downcase == "rspec" }
|
|
154
|
+
|
|
155
|
+
if rspec_index
|
|
156
|
+
# Insert after "rspec" but before any file arguments
|
|
157
|
+
parts.insert(rspec_index + 1, "--only-failures")
|
|
158
|
+
parts.join(" ")
|
|
159
|
+
else
|
|
160
|
+
# Fallback: append to end
|
|
161
|
+
"#{command} --only-failures"
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -12,6 +12,9 @@ module Aidp
|
|
|
12
12
|
class RegistryError < StandardError; end
|
|
13
13
|
class ModelNotFound < RegistryError; end
|
|
14
14
|
|
|
15
|
+
ENCODING_MUTEX = Mutex.new
|
|
16
|
+
private_constant :ENCODING_MUTEX
|
|
17
|
+
|
|
15
18
|
# Map AIDP provider names to RubyLLM provider names
|
|
16
19
|
# Some AIDP providers use different names than the upstream APIs
|
|
17
20
|
PROVIDER_NAME_MAPPING = {
|
|
@@ -67,8 +70,8 @@ module Aidp
|
|
|
67
70
|
|
|
68
71
|
def initialize(deprecation_cache: nil)
|
|
69
72
|
@deprecation_cache = deprecation_cache
|
|
70
|
-
@models =
|
|
71
|
-
@index_by_id = @models
|
|
73
|
+
@models = load_models_with_utf8_fallback
|
|
74
|
+
@index_by_id = build_id_index(@models)
|
|
72
75
|
|
|
73
76
|
# Build family index for mapping versioned names to families
|
|
74
77
|
@family_index = build_family_index
|
|
@@ -125,9 +128,12 @@ module Aidp
|
|
|
125
128
|
# Get model information
|
|
126
129
|
#
|
|
127
130
|
# @param model_id [String] The model ID
|
|
131
|
+
# @param provider [String, nil] Optional AIDP provider name. When given,
|
|
132
|
+
# returns info for that provider's entry (useful when the same model ID
|
|
133
|
+
# exists under multiple providers, e.g. anthropic and azure).
|
|
128
134
|
# @return [Hash, nil] Model information hash or nil if not found
|
|
129
|
-
def get_model_info(model_id)
|
|
130
|
-
model =
|
|
135
|
+
def get_model_info(model_id, provider: nil)
|
|
136
|
+
model = find_model_entry(model_id, provider: provider)
|
|
131
137
|
return nil unless model
|
|
132
138
|
|
|
133
139
|
{
|
|
@@ -212,8 +218,8 @@ module Aidp
|
|
|
212
218
|
# Refresh the model registry from ruby_llm
|
|
213
219
|
def refresh!
|
|
214
220
|
RubyLLM::Models.refresh!
|
|
215
|
-
@models =
|
|
216
|
-
@index_by_id = @models
|
|
221
|
+
@models = load_models_with_utf8_fallback
|
|
222
|
+
@index_by_id = build_id_index(@models)
|
|
217
223
|
@family_index = build_family_index
|
|
218
224
|
Aidp.log_info("ruby_llm_registry", "refreshed", models: @models.size)
|
|
219
225
|
end
|
|
@@ -229,7 +235,8 @@ module Aidp
|
|
|
229
235
|
end
|
|
230
236
|
|
|
231
237
|
# Find replacement for a deprecated model
|
|
232
|
-
# Returns the
|
|
238
|
+
# Returns the cached replacement if available, otherwise finds the latest
|
|
239
|
+
# non-deprecated model in the same family/tier
|
|
233
240
|
# @param deprecated_model [String] The deprecated model ID
|
|
234
241
|
# @param provider [String, nil] The provider name (AIDP format)
|
|
235
242
|
# @return [String, nil] Replacement model ID or nil
|
|
@@ -237,7 +244,16 @@ module Aidp
|
|
|
237
244
|
registry_provider = provider ? PROVIDER_NAME_MAPPING[provider] : nil
|
|
238
245
|
return nil unless registry_provider
|
|
239
246
|
|
|
240
|
-
#
|
|
247
|
+
# First check the deprecation cache for a stored replacement
|
|
248
|
+
cached_replacement = deprecation_cache.replacement_for(provider: registry_provider, model_id: deprecated_model)
|
|
249
|
+
if cached_replacement
|
|
250
|
+
Aidp.log_info("ruby_llm_registry", "found cached replacement",
|
|
251
|
+
deprecated: deprecated_model,
|
|
252
|
+
replacement: cached_replacement)
|
|
253
|
+
return cached_replacement
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Determine tier of deprecated model for dynamic lookup
|
|
241
257
|
deprecated_info = @index_by_id[deprecated_model]
|
|
242
258
|
return nil unless deprecated_info
|
|
243
259
|
|
|
@@ -265,6 +281,79 @@ module Aidp
|
|
|
265
281
|
|
|
266
282
|
private
|
|
267
283
|
|
|
284
|
+
# Load models from RubyLLM, working around encoding issues in the
|
|
285
|
+
# upstream gem when the process locale is US-ASCII.
|
|
286
|
+
def load_models_with_utf8_fallback
|
|
287
|
+
RubyLLM::Models.instance.instance_variable_get(:@models)
|
|
288
|
+
rescue Encoding::InvalidByteSequenceError => e
|
|
289
|
+
# Only switch encoding when the current default is US-ASCII, which is
|
|
290
|
+
# the known problematic locale for RubyLLM's UTF-8 model JSON.
|
|
291
|
+
# For other non-UTF-8 encodings, propagate the error rather than
|
|
292
|
+
# silently clobbering the process-wide setting.
|
|
293
|
+
ENCODING_MUTEX.synchronize do
|
|
294
|
+
unless Encoding.default_external == Encoding::US_ASCII
|
|
295
|
+
raise e
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
Aidp.log_debug(
|
|
299
|
+
"ruby_llm_registry",
|
|
300
|
+
"setting default external encoding from US-ASCII to UTF-8 for RubyLLM model load"
|
|
301
|
+
)
|
|
302
|
+
Encoding.default_external = Encoding::UTF_8
|
|
303
|
+
|
|
304
|
+
# RubyLLM::Models uses a singleton @instance that caches the parsed
|
|
305
|
+
# model list. We reset it so the retry re-reads the JSON under
|
|
306
|
+
# UTF-8 encoding. This relies on an internal implementation detail;
|
|
307
|
+
# if upstream adds a public reset API we should prefer that.
|
|
308
|
+
RubyLLM::Models.instance_variable_set(:@instance, nil)
|
|
309
|
+
RubyLLM::Models.instance.instance_variable_get(:@models)
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Find the model entry for a given ID, optionally scoped to a provider.
|
|
314
|
+
# When a provider is given, only returns entries for that provider;
|
|
315
|
+
# returns nil if the provider is unsupported or no match is found.
|
|
316
|
+
# Falls back to the flat index only when no provider is given.
|
|
317
|
+
def find_model_entry(model_id, provider: nil)
|
|
318
|
+
if provider
|
|
319
|
+
registry_provider = PROVIDER_NAME_MAPPING[provider]
|
|
320
|
+
unless registry_provider
|
|
321
|
+
Aidp.log_debug("ruby_llm_registry", "unsupported provider for model lookup",
|
|
322
|
+
provider: provider, model_id: model_id)
|
|
323
|
+
return nil
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
match = @models.find { |m| m.id == model_id && m.provider.to_s == registry_provider }
|
|
327
|
+
unless match
|
|
328
|
+
Aidp.log_debug("ruby_llm_registry", "model not found for provider",
|
|
329
|
+
model_id: model_id, provider: provider, registry_provider: registry_provider)
|
|
330
|
+
end
|
|
331
|
+
return match
|
|
332
|
+
end
|
|
333
|
+
@index_by_id[model_id]
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# Build an index of model ID to model object, preferring primary
|
|
337
|
+
# providers (anthropic, openai, gemini) over resellers (azure, bedrock)
|
|
338
|
+
# when the same model ID appears under multiple providers.
|
|
339
|
+
#
|
|
340
|
+
# NOTE: This flat index stores one entry per model ID. Callers that need
|
|
341
|
+
# provider-specific results (e.g. models_for_provider) should filter
|
|
342
|
+
# @models directly rather than relying on this index.
|
|
343
|
+
PRIMARY_PROVIDERS = %w[anthropic openai gemini deepseek mistral].freeze
|
|
344
|
+
|
|
345
|
+
def build_id_index(models)
|
|
346
|
+
index = {}
|
|
347
|
+
models.each do |m|
|
|
348
|
+
existing = index[m.id]
|
|
349
|
+
if existing.nil? || (!PRIMARY_PROVIDERS.include?(existing.provider.to_s) &&
|
|
350
|
+
PRIMARY_PROVIDERS.include?(m.provider.to_s))
|
|
351
|
+
index[m.id] = m
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
index
|
|
355
|
+
end
|
|
356
|
+
|
|
268
357
|
# Build an index mapping family names to model objects
|
|
269
358
|
# Family name is model ID with version suffix removed
|
|
270
359
|
def build_family_index
|
data/lib/aidp/harness/runner.rb
CHANGED
|
@@ -50,7 +50,7 @@ module Aidp
|
|
|
50
50
|
# Initialize components
|
|
51
51
|
@configuration = Configuration.new(project_dir)
|
|
52
52
|
@state_manager = StateManager.new(project_dir, @mode)
|
|
53
|
-
@provider_manager =
|
|
53
|
+
@provider_manager = create_provider_manager(options)
|
|
54
54
|
|
|
55
55
|
# Use ZFC-enabled condition detector
|
|
56
56
|
# ZfcConditionDetector will create its own ProviderFactory if needed
|
|
@@ -224,6 +224,11 @@ module Aidp
|
|
|
224
224
|
|
|
225
225
|
private
|
|
226
226
|
|
|
227
|
+
def create_provider_manager(_options)
|
|
228
|
+
require_relative "agent_harness_provider_manager"
|
|
229
|
+
AgentHarnessProviderManager.new(@configuration, prompt: @prompt)
|
|
230
|
+
end
|
|
231
|
+
|
|
227
232
|
def get_mode_runner
|
|
228
233
|
case @mode
|
|
229
234
|
when :analyze
|
|
@@ -459,7 +464,7 @@ module Aidp
|
|
|
459
464
|
**data.slice(:error, :error_class, :criteria, :all_complete, :summary).compact)
|
|
460
465
|
|
|
461
466
|
# Also log to standard output in debug mode
|
|
462
|
-
puts "[#{Time.now.strftime("%H:%M:%S")}] #{message}" if
|
|
467
|
+
puts "[#{Time.now.strftime("%H:%M:%S")}] #{message}" if Aidp.debug_env_level >= 1
|
|
463
468
|
end
|
|
464
469
|
|
|
465
470
|
def get_completion_message
|
|
@@ -616,7 +616,7 @@ module Aidp
|
|
|
616
616
|
end
|
|
617
617
|
rescue => e
|
|
618
618
|
# Ignore errors when cleaning up stale locks
|
|
619
|
-
warn "Failed to cleanup stale lock: #{e.message}" if
|
|
619
|
+
warn "Failed to cleanup stale lock: #{e.message}" if Aidp.debug_env_enabled?
|
|
620
620
|
end
|
|
621
621
|
end
|
|
622
622
|
end
|