aidp 0.25.0 → 0.27.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 +45 -6
- data/lib/aidp/analyze/error_handler.rb +11 -0
- data/lib/aidp/cli/checkpoint_command.rb +198 -0
- data/lib/aidp/cli/config_command.rb +71 -0
- data/lib/aidp/cli/enhanced_input.rb +2 -0
- data/lib/aidp/cli/first_run_wizard.rb +8 -7
- data/lib/aidp/cli/harness_command.rb +102 -0
- data/lib/aidp/cli/jobs_command.rb +3 -3
- data/lib/aidp/cli/mcp_dashboard.rb +4 -3
- data/lib/aidp/cli/models_command.rb +662 -0
- data/lib/aidp/cli/providers_command.rb +223 -0
- data/lib/aidp/cli.rb +35 -456
- data/lib/aidp/daemon/runner.rb +2 -2
- data/lib/aidp/debug_mixin.rb +2 -9
- data/lib/aidp/execute/async_work_loop_runner.rb +2 -1
- data/lib/aidp/execute/checkpoint_display.rb +38 -37
- data/lib/aidp/execute/interactive_repl.rb +2 -1
- data/lib/aidp/execute/prompt_manager.rb +4 -4
- data/lib/aidp/execute/work_loop_runner.rb +253 -56
- data/lib/aidp/execute/workflow_selector.rb +2 -2
- data/lib/aidp/harness/config_loader.rb +20 -11
- data/lib/aidp/harness/config_manager.rb +5 -5
- data/lib/aidp/harness/config_schema.rb +30 -8
- data/lib/aidp/harness/configuration.rb +105 -4
- data/lib/aidp/harness/enhanced_runner.rb +24 -15
- data/lib/aidp/harness/error_handler.rb +26 -5
- data/lib/aidp/harness/filter_strategy.rb +45 -0
- data/lib/aidp/harness/generic_filter_strategy.rb +63 -0
- data/lib/aidp/harness/model_cache.rb +269 -0
- data/lib/aidp/harness/model_discovery_service.rb +259 -0
- data/lib/aidp/harness/model_registry.rb +201 -0
- data/lib/aidp/harness/output_filter.rb +136 -0
- data/lib/aidp/harness/provider_manager.rb +18 -3
- data/lib/aidp/harness/rspec_filter_strategy.rb +82 -0
- data/lib/aidp/harness/runner.rb +5 -0
- data/lib/aidp/harness/test_runner.rb +165 -27
- data/lib/aidp/harness/thinking_depth_manager.rb +223 -7
- data/lib/aidp/harness/ui/enhanced_tui.rb +4 -1
- data/lib/aidp/logger.rb +35 -5
- data/lib/aidp/providers/adapter.rb +2 -4
- data/lib/aidp/providers/anthropic.rb +141 -128
- data/lib/aidp/providers/base.rb +98 -2
- data/lib/aidp/providers/capability_registry.rb +0 -1
- data/lib/aidp/providers/codex.rb +49 -67
- data/lib/aidp/providers/cursor.rb +71 -59
- data/lib/aidp/providers/gemini.rb +44 -60
- data/lib/aidp/providers/github_copilot.rb +2 -66
- data/lib/aidp/providers/kilocode.rb +24 -80
- data/lib/aidp/providers/opencode.rb +24 -80
- data/lib/aidp/safe_directory.rb +10 -3
- data/lib/aidp/setup/wizard.rb +345 -8
- data/lib/aidp/storage/csv_storage.rb +9 -3
- data/lib/aidp/storage/file_manager.rb +8 -2
- data/lib/aidp/storage/json_storage.rb +9 -3
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/build_processor.rb +40 -1
- data/lib/aidp/watch/change_request_processor.rb +659 -0
- data/lib/aidp/watch/plan_generator.rb +93 -14
- data/lib/aidp/watch/plan_processor.rb +71 -8
- data/lib/aidp/watch/repository_client.rb +85 -20
- data/lib/aidp/watch/review_processor.rb +3 -3
- data/lib/aidp/watch/runner.rb +37 -0
- data/lib/aidp/watch/state_store.rb +46 -1
- data/lib/aidp/workflows/guided_agent.rb +3 -3
- data/lib/aidp/workstream_executor.rb +5 -2
- data/lib/aidp.rb +4 -0
- data/templates/aidp-development.yml.example +2 -2
- data/templates/aidp-production.yml.example +3 -3
- data/templates/aidp.yml.example +53 -0
- metadata +14 -1
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tty-table"
|
|
4
|
+
require "tty-prompt"
|
|
5
|
+
require "tty-spinner"
|
|
6
|
+
require_relative "../harness/model_registry"
|
|
7
|
+
require_relative "../harness/model_discovery_service"
|
|
8
|
+
|
|
9
|
+
module Aidp
|
|
10
|
+
class CLI
|
|
11
|
+
# Command handler for `aidp models` subcommand group
|
|
12
|
+
#
|
|
13
|
+
# Provides commands for viewing and discovering AI models:
|
|
14
|
+
# - list: Show all available models with tier information
|
|
15
|
+
# - discover: Discover models from configured providers
|
|
16
|
+
# - refresh: Refresh the model cache
|
|
17
|
+
#
|
|
18
|
+
# Usage:
|
|
19
|
+
# aidp models list [--provider=<name>] [--tier=<tier>]
|
|
20
|
+
# aidp models discover [--provider=<name>]
|
|
21
|
+
# aidp models refresh [--provider=<name>]
|
|
22
|
+
class ModelsCommand
|
|
23
|
+
include Aidp::MessageDisplay
|
|
24
|
+
|
|
25
|
+
def initialize(prompt: TTY::Prompt.new, registry: nil, discovery_service: nil)
|
|
26
|
+
@prompt = prompt
|
|
27
|
+
@registry = registry
|
|
28
|
+
@discovery_service = discovery_service
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Main entry point for models subcommand
|
|
32
|
+
def run(args)
|
|
33
|
+
subcommand = args.first if args.first && !args.first.start_with?("--")
|
|
34
|
+
|
|
35
|
+
case subcommand
|
|
36
|
+
when "list", nil
|
|
37
|
+
args.shift if subcommand == "list"
|
|
38
|
+
run_list_command(args)
|
|
39
|
+
when "discover"
|
|
40
|
+
args.shift
|
|
41
|
+
run_discover_command(args)
|
|
42
|
+
when "refresh"
|
|
43
|
+
args.shift
|
|
44
|
+
run_refresh_command(args)
|
|
45
|
+
when "validate"
|
|
46
|
+
args.shift
|
|
47
|
+
run_validate_command(args)
|
|
48
|
+
else
|
|
49
|
+
display_message("Unknown models subcommand: #{subcommand}", type: :error)
|
|
50
|
+
display_help
|
|
51
|
+
1
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def registry
|
|
58
|
+
@registry ||= Aidp::Harness::ModelRegistry.new
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def discovery_service
|
|
62
|
+
@discovery_service ||= Aidp::Harness::ModelDiscoveryService.new
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def display_help
|
|
66
|
+
display_message("\nUsage: aidp models <subcommand> [options]", type: :info)
|
|
67
|
+
display_message("\nSubcommands:", type: :info)
|
|
68
|
+
display_message(" list List all available models with tier information", type: :info)
|
|
69
|
+
display_message(" discover Discover models from configured providers", type: :info)
|
|
70
|
+
display_message(" refresh Refresh the model discovery cache", type: :info)
|
|
71
|
+
display_message(" validate Validate model configuration for all tiers", type: :info)
|
|
72
|
+
display_message("\nOptions:", type: :info)
|
|
73
|
+
display_message(" --provider=<name> Filter/target specific provider", type: :info)
|
|
74
|
+
display_message(" --tier=<tier> Filter by tier (mini, standard, advanced)", type: :info)
|
|
75
|
+
display_message("\nExamples:", type: :info)
|
|
76
|
+
display_message(" aidp models list", type: :info)
|
|
77
|
+
display_message(" aidp models list --tier=mini", type: :info)
|
|
78
|
+
display_message(" aidp models list --provider=anthropic", type: :info)
|
|
79
|
+
display_message(" aidp models discover", type: :info)
|
|
80
|
+
display_message(" aidp models discover --provider=anthropic", type: :info)
|
|
81
|
+
display_message(" aidp models refresh", type: :info)
|
|
82
|
+
display_message(" aidp models validate", type: :info)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def run_list_command(args)
|
|
86
|
+
options = parse_list_options(args)
|
|
87
|
+
|
|
88
|
+
begin
|
|
89
|
+
# Get all model families from registry
|
|
90
|
+
families = registry.all_families
|
|
91
|
+
|
|
92
|
+
# Apply tier filter if specified
|
|
93
|
+
if options[:tier]
|
|
94
|
+
families = families.select { |family|
|
|
95
|
+
info = registry.get_model_info(family)
|
|
96
|
+
info && info["tier"] == options[:tier]
|
|
97
|
+
}
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Build table rows
|
|
101
|
+
rows = []
|
|
102
|
+
families.each do |family|
|
|
103
|
+
info = registry.get_model_info(family)
|
|
104
|
+
next unless info
|
|
105
|
+
|
|
106
|
+
# Get providers that support this family
|
|
107
|
+
providers = find_providers_for_family(family)
|
|
108
|
+
|
|
109
|
+
# Apply provider filter if specified
|
|
110
|
+
next if options[:provider] && !providers.include?(options[:provider])
|
|
111
|
+
|
|
112
|
+
# Add a row for each provider that supports this family
|
|
113
|
+
if providers.empty?
|
|
114
|
+
# No provider support - show as registry-only
|
|
115
|
+
rows << build_table_row(nil, family, info, "registry")
|
|
116
|
+
else
|
|
117
|
+
providers.each do |provider_name|
|
|
118
|
+
rows << build_table_row(provider_name, family, info, "registry")
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Sort rows by tier, then provider, then model name
|
|
124
|
+
tier_order = {"mini" => 0, "standard" => 1, "advanced" => 2}
|
|
125
|
+
rows.sort_by! { |r| [tier_order[r[2]] || 3, r[0] || "", r[1]] }
|
|
126
|
+
|
|
127
|
+
# Display table
|
|
128
|
+
if rows.empty?
|
|
129
|
+
display_message("No models found matching the specified criteria.", type: :info)
|
|
130
|
+
return 0
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
display_message("\n#{build_header(options)}\n", type: :highlight)
|
|
134
|
+
|
|
135
|
+
table = TTY::Table.new(
|
|
136
|
+
header: ["Provider", "Model Family", "Tier", "Capabilities", "Context", "Speed"],
|
|
137
|
+
rows: rows
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Use simple renderer for consistent formatting
|
|
141
|
+
renderer = table.render(:basic, padding: [0, 1])
|
|
142
|
+
display_message(renderer, type: :info)
|
|
143
|
+
|
|
144
|
+
display_message("\n#{build_footer(rows.size)}\n", type: :info)
|
|
145
|
+
0
|
|
146
|
+
rescue Aidp::Harness::ModelRegistry::RegistryError => e
|
|
147
|
+
display_message("Error loading model registry: #{e.message}", type: :error)
|
|
148
|
+
Aidp.log_error("models_command", "registry error", error: e.message)
|
|
149
|
+
1
|
|
150
|
+
rescue => e
|
|
151
|
+
display_message("Error listing models: #{e.message}", type: :error)
|
|
152
|
+
Aidp.log_error("models_command", "unexpected error", error: e.message, backtrace: e.backtrace.first(5))
|
|
153
|
+
1
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def parse_list_options(args)
|
|
158
|
+
options = {}
|
|
159
|
+
|
|
160
|
+
args.each do |arg|
|
|
161
|
+
case arg
|
|
162
|
+
when /^--provider=(.+)$/
|
|
163
|
+
options[:provider] = Regexp.last_match(1)
|
|
164
|
+
when /^--tier=(.+)$/
|
|
165
|
+
tier = Regexp.last_match(1)
|
|
166
|
+
unless Aidp::Harness::ModelRegistry::VALID_TIERS.include?(tier)
|
|
167
|
+
display_message("Invalid tier: #{tier}. Valid tiers: #{Aidp::Harness::ModelRegistry::VALID_TIERS.join(", ")}", type: :error)
|
|
168
|
+
exit 1
|
|
169
|
+
end
|
|
170
|
+
options[:tier] = tier
|
|
171
|
+
when "--help", "-h"
|
|
172
|
+
display_help
|
|
173
|
+
exit 0
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
options
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def build_table_row(provider_name, family, info, source)
|
|
181
|
+
capabilities = (info["capabilities"] || []).join(",")
|
|
182
|
+
context = format_context_window(info["context_window"])
|
|
183
|
+
speed = info["speed"] || "unknown"
|
|
184
|
+
|
|
185
|
+
[
|
|
186
|
+
provider_name || "-",
|
|
187
|
+
family,
|
|
188
|
+
info["tier"] || "unknown",
|
|
189
|
+
capabilities.empty? ? "-" : capabilities,
|
|
190
|
+
context,
|
|
191
|
+
speed
|
|
192
|
+
]
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def format_context_window(tokens)
|
|
196
|
+
return "-" unless tokens
|
|
197
|
+
|
|
198
|
+
if tokens >= 1_000_000
|
|
199
|
+
"#{tokens / 1_000_000}M"
|
|
200
|
+
elsif tokens >= 1_000
|
|
201
|
+
"#{tokens / 1_000}K"
|
|
202
|
+
else
|
|
203
|
+
tokens.to_s
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def build_header(options)
|
|
208
|
+
parts = ["Available Models"]
|
|
209
|
+
parts << "(Provider: #{options[:provider]})" if options[:provider]
|
|
210
|
+
parts << "(Tier: #{options[:tier]})" if options[:tier]
|
|
211
|
+
parts.join(" ")
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def build_footer(count)
|
|
215
|
+
tips = [
|
|
216
|
+
"💡 Showing #{count} model#{"s" unless count == 1} from the static registry",
|
|
217
|
+
"💡 Model families are provider-agnostic (e.g., 'claude-3-5-sonnet' works across providers)"
|
|
218
|
+
]
|
|
219
|
+
tips.join("\n")
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def find_providers_for_family(family_name)
|
|
223
|
+
providers = []
|
|
224
|
+
|
|
225
|
+
# Check each provider adapter for support
|
|
226
|
+
provider_classes = [
|
|
227
|
+
Aidp::Providers::Anthropic,
|
|
228
|
+
Aidp::Providers::Cursor,
|
|
229
|
+
Aidp::Providers::Gemini
|
|
230
|
+
]
|
|
231
|
+
|
|
232
|
+
provider_classes.each do |provider_class|
|
|
233
|
+
next unless provider_class.respond_to?(:supports_model_family?)
|
|
234
|
+
|
|
235
|
+
if provider_class.supports_model_family?(family_name)
|
|
236
|
+
# Get the provider name from an instance (need to instantiate to call name method)
|
|
237
|
+
# Or use a simple name mapping
|
|
238
|
+
provider_name = provider_class.name.split("::").last.downcase
|
|
239
|
+
providers << provider_name
|
|
240
|
+
end
|
|
241
|
+
rescue => e
|
|
242
|
+
# Log but don't fail if provider check fails
|
|
243
|
+
Aidp.log_debug("models_command", "provider check failed", provider: provider_class.name, error: e.message)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
providers
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def run_discover_command(args)
|
|
250
|
+
options = parse_discover_options(args)
|
|
251
|
+
|
|
252
|
+
begin
|
|
253
|
+
display_message("\n🔍 Discovering models from configured providers...\n", type: :highlight)
|
|
254
|
+
|
|
255
|
+
spinner = TTY::Spinner.new("[:spinner] Querying provider APIs...", format: :dots)
|
|
256
|
+
spinner.auto_spin
|
|
257
|
+
|
|
258
|
+
# Discover models
|
|
259
|
+
results = if options[:provider]
|
|
260
|
+
{options[:provider] => discovery_service.discover_models(options[:provider], use_cache: false)}
|
|
261
|
+
else
|
|
262
|
+
discovery_service.discover_all_models(use_cache: false)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
spinner.success("✓")
|
|
266
|
+
|
|
267
|
+
# Display results
|
|
268
|
+
total_models = 0
|
|
269
|
+
results.each do |provider, models|
|
|
270
|
+
next if models.empty?
|
|
271
|
+
|
|
272
|
+
total_models += models.size
|
|
273
|
+
display_message("\n✓ Found #{models.size} models for #{provider}:", type: :success)
|
|
274
|
+
|
|
275
|
+
# Group by tier
|
|
276
|
+
by_tier = models.group_by { |m| m[:tier] }
|
|
277
|
+
%w[mini standard advanced].each do |tier|
|
|
278
|
+
tier_models = by_tier[tier] || []
|
|
279
|
+
next if tier_models.empty?
|
|
280
|
+
|
|
281
|
+
display_message(" #{tier.capitalize} tier:", type: :info)
|
|
282
|
+
tier_models.each do |model|
|
|
283
|
+
display_message(" - #{model[:name]}", type: :info)
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
if total_models == 0
|
|
289
|
+
display_message("\n⚠️ No models discovered. Ensure provider CLIs are installed and configured.", type: :warning)
|
|
290
|
+
return 1
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
display_message("\n✅ Discovered #{total_models} total model#{"s" unless total_models == 1}", type: :success)
|
|
294
|
+
display_message("💾 Models cached for 24 hours\n", type: :info)
|
|
295
|
+
0
|
|
296
|
+
rescue => e
|
|
297
|
+
display_message("Error discovering models: #{e.message}", type: :error)
|
|
298
|
+
Aidp.log_error("models_command", "discovery error", error: e.message, backtrace: e.backtrace.first(5))
|
|
299
|
+
1
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def run_refresh_command(args)
|
|
304
|
+
options = parse_refresh_options(args)
|
|
305
|
+
|
|
306
|
+
begin
|
|
307
|
+
display_message("\n♻️ Refreshing model cache...\n", type: :highlight)
|
|
308
|
+
|
|
309
|
+
spinner = TTY::Spinner.new("[:spinner] Clearing cache and re-discovering...", format: :dots)
|
|
310
|
+
spinner.auto_spin
|
|
311
|
+
|
|
312
|
+
if options[:provider]
|
|
313
|
+
discovery_service.refresh_cache(options[:provider])
|
|
314
|
+
spinner.success("✓")
|
|
315
|
+
display_message("\n✅ Refreshed cache for #{options[:provider]}", type: :success)
|
|
316
|
+
else
|
|
317
|
+
discovery_service.refresh_all_caches
|
|
318
|
+
spinner.success("✓")
|
|
319
|
+
display_message("\n✅ Refreshed cache for all providers", type: :success)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
display_message("💡 Run 'aidp models discover' to see the updated models\n", type: :info)
|
|
323
|
+
0
|
|
324
|
+
rescue => e
|
|
325
|
+
display_message("Error refreshing cache: #{e.message}", type: :error)
|
|
326
|
+
Aidp.log_error("models_command", "refresh error", error: e.message, backtrace: e.backtrace.first(5))
|
|
327
|
+
1
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def parse_discover_options(args)
|
|
332
|
+
options = {}
|
|
333
|
+
|
|
334
|
+
args.each do |arg|
|
|
335
|
+
case arg
|
|
336
|
+
when /^--provider=(.+)$/
|
|
337
|
+
options[:provider] = Regexp.last_match(1)
|
|
338
|
+
when "--help", "-h"
|
|
339
|
+
display_help
|
|
340
|
+
exit 0
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
options
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def parse_refresh_options(args)
|
|
348
|
+
parse_discover_options(args)
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def run_validate_command(args)
|
|
352
|
+
parse_validate_options(args)
|
|
353
|
+
|
|
354
|
+
begin
|
|
355
|
+
# Load configuration
|
|
356
|
+
config = load_configuration
|
|
357
|
+
return 1 unless config
|
|
358
|
+
|
|
359
|
+
display_message("\n🔍 Validating model configuration...\n", type: :highlight)
|
|
360
|
+
|
|
361
|
+
# Collect validation issues
|
|
362
|
+
issues = []
|
|
363
|
+
warnings = []
|
|
364
|
+
|
|
365
|
+
# Validate tier coverage
|
|
366
|
+
tier_issues = validate_tier_coverage(config)
|
|
367
|
+
issues.concat(tier_issues[:errors])
|
|
368
|
+
warnings.concat(tier_issues[:warnings])
|
|
369
|
+
|
|
370
|
+
# Validate provider models
|
|
371
|
+
provider_issues = validate_provider_models(config)
|
|
372
|
+
issues.concat(provider_issues[:errors])
|
|
373
|
+
warnings.concat(provider_issues[:warnings])
|
|
374
|
+
|
|
375
|
+
# Display results
|
|
376
|
+
display_validation_results(issues, warnings)
|
|
377
|
+
|
|
378
|
+
# Return exit code
|
|
379
|
+
issues.empty? ? 0 : 1
|
|
380
|
+
rescue => e
|
|
381
|
+
display_message("Error validating configuration: #{e.message}", type: :error)
|
|
382
|
+
Aidp.log_error("models_command", "validation error",
|
|
383
|
+
error: e.message, backtrace: e.backtrace.first(5))
|
|
384
|
+
1
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def parse_validate_options(args)
|
|
389
|
+
args.each do |arg|
|
|
390
|
+
case arg
|
|
391
|
+
when "--help", "-h"
|
|
392
|
+
display_help
|
|
393
|
+
exit 0
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
def load_configuration
|
|
399
|
+
project_dir = Dir.pwd
|
|
400
|
+
unless Aidp::Config.config_exists?(project_dir)
|
|
401
|
+
display_message("❌ No aidp.yml configuration file found", type: :error)
|
|
402
|
+
display_message("Run 'aidp config --interactive' to create one", type: :info)
|
|
403
|
+
return nil
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
config_data = Aidp::Config.load(project_dir) || {}
|
|
407
|
+
providers_section = config_data[:providers] || config_data["providers"] || {}
|
|
408
|
+
SimpleConfiguration.new(providers_section)
|
|
409
|
+
rescue => e
|
|
410
|
+
display_message("Error validating configuration: #{e.message}", type: :error)
|
|
411
|
+
nil
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def validate_tier_coverage(config)
|
|
415
|
+
errors = []
|
|
416
|
+
warnings = []
|
|
417
|
+
|
|
418
|
+
# Check each tier for model coverage
|
|
419
|
+
Aidp::Harness::ModelRegistry::VALID_TIERS.each do |tier|
|
|
420
|
+
has_model = tier_has_model?(config, tier)
|
|
421
|
+
|
|
422
|
+
unless has_model
|
|
423
|
+
errors << {
|
|
424
|
+
tier: tier,
|
|
425
|
+
message: "No model configured for '#{tier}' tier",
|
|
426
|
+
fix: generate_tier_fix_suggestion(tier, config)
|
|
427
|
+
}
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
{errors: errors, warnings: warnings}
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def tier_has_model?(config, tier)
|
|
435
|
+
# Check if any provider has a model for this tier
|
|
436
|
+
configured_providers = config.configured_providers
|
|
437
|
+
|
|
438
|
+
configured_providers.any? do |provider_name|
|
|
439
|
+
provider_cfg = config.provider_config(provider_name)
|
|
440
|
+
tier_config = provider_cfg.dig(:thinking, :tiers, tier.to_sym) ||
|
|
441
|
+
provider_cfg.dig(:thinking, :tiers, tier)
|
|
442
|
+
|
|
443
|
+
tier_config && tier_config[:models] && !tier_config[:models].empty?
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def validate_provider_models(config)
|
|
448
|
+
errors = []
|
|
449
|
+
warnings = []
|
|
450
|
+
|
|
451
|
+
configured_providers = config.configured_providers
|
|
452
|
+
|
|
453
|
+
configured_providers.each do |provider_name|
|
|
454
|
+
provider_class = get_provider_class(provider_name)
|
|
455
|
+
next unless provider_class
|
|
456
|
+
|
|
457
|
+
provider_cfg = config.provider_config(provider_name)
|
|
458
|
+
thinking_cfg = provider_cfg[:thinking] || {}
|
|
459
|
+
tiers_cfg = thinking_cfg[:tiers] || {}
|
|
460
|
+
|
|
461
|
+
# Validate models in each tier
|
|
462
|
+
tiers_cfg.each do |tier, tier_config|
|
|
463
|
+
next unless tier_config[:models]
|
|
464
|
+
|
|
465
|
+
tier_config[:models].each do |model_entry|
|
|
466
|
+
model_name = model_entry.is_a?(Hash) ? model_entry[:model] : model_entry
|
|
467
|
+
next unless model_name
|
|
468
|
+
|
|
469
|
+
# Check if provider supports this model family
|
|
470
|
+
family = get_model_family(provider_class, model_name)
|
|
471
|
+
next unless family
|
|
472
|
+
|
|
473
|
+
unless provider_supports_model?(provider_class, family)
|
|
474
|
+
errors << {
|
|
475
|
+
provider: provider_name,
|
|
476
|
+
tier: tier,
|
|
477
|
+
model: model_name,
|
|
478
|
+
message: "Model '#{model_name}' not supported by provider '#{provider_name}'",
|
|
479
|
+
fix: suggest_alternative_model(provider_name, tier, model_name)
|
|
480
|
+
}
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
# Check if model exists in registry
|
|
484
|
+
model_info = registry.get_model_info(family)
|
|
485
|
+
unless model_info
|
|
486
|
+
warnings << {
|
|
487
|
+
provider: provider_name,
|
|
488
|
+
tier: tier,
|
|
489
|
+
model: model_name,
|
|
490
|
+
message: "Model family '#{family}' not found in registry (may still work)"
|
|
491
|
+
}
|
|
492
|
+
end
|
|
493
|
+
end
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
{errors: errors, warnings: warnings}
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
def get_provider_class(provider_name)
|
|
501
|
+
class_name = "Aidp::Providers::#{provider_name.capitalize}"
|
|
502
|
+
Object.const_get(class_name)
|
|
503
|
+
rescue NameError
|
|
504
|
+
nil
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
def get_model_family(provider_class, model_name)
|
|
508
|
+
return model_name unless provider_class.respond_to?(:model_family)
|
|
509
|
+
provider_class.model_family(model_name)
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
def provider_supports_model?(provider_class, family)
|
|
513
|
+
return true unless provider_class.respond_to?(:supports_model_family?)
|
|
514
|
+
provider_class.supports_model_family?(family)
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
def generate_tier_fix_suggestion(tier, config)
|
|
518
|
+
# Get a model from registry for this tier
|
|
519
|
+
tier_models = registry.models_for_tier(tier)
|
|
520
|
+
return "Configure a model for this tier in aidp.yml" if tier_models.empty?
|
|
521
|
+
|
|
522
|
+
# Find a model that works with configured providers
|
|
523
|
+
configured_providers = config.configured_providers
|
|
524
|
+
suggested_model = nil
|
|
525
|
+
|
|
526
|
+
tier_models.each do |family|
|
|
527
|
+
configured_providers.each do |provider_name|
|
|
528
|
+
provider_class = get_provider_class(provider_name)
|
|
529
|
+
next unless provider_class
|
|
530
|
+
|
|
531
|
+
if provider_supports_model?(provider_class, family)
|
|
532
|
+
suggested_model = {family: family, provider: provider_name}
|
|
533
|
+
break
|
|
534
|
+
end
|
|
535
|
+
end
|
|
536
|
+
break if suggested_model
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
if suggested_model
|
|
540
|
+
"Add to aidp.yml under providers.#{suggested_model[:provider]}.thinking.tiers.#{tier}.models:\n" \
|
|
541
|
+
" - model: #{suggested_model[:family]}"
|
|
542
|
+
else
|
|
543
|
+
"Configure a model for this tier in aidp.yml"
|
|
544
|
+
end
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
def suggest_alternative_model(provider_name, tier, invalid_model)
|
|
548
|
+
# Get models from registry for this tier and provider
|
|
549
|
+
tier_models = if registry.is_a?(Aidp::Harness::ModelRegistry)
|
|
550
|
+
registry.models_for_tier(tier.to_s)
|
|
551
|
+
else
|
|
552
|
+
[]
|
|
553
|
+
end
|
|
554
|
+
provider_class = get_provider_class(provider_name)
|
|
555
|
+
return "Check model name or use a different provider" unless provider_class
|
|
556
|
+
|
|
557
|
+
# Find valid alternatives
|
|
558
|
+
alternatives = tier_models.select do |family|
|
|
559
|
+
provider_supports_model?(provider_class, family)
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
if alternatives.any?
|
|
563
|
+
"Try using: #{alternatives.first(3).join(", ")}"
|
|
564
|
+
else
|
|
565
|
+
"Provider '#{provider_name}' doesn't support any models for tier '#{tier}'"
|
|
566
|
+
end
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
def display_validation_results(issues, warnings)
|
|
570
|
+
if issues.empty? && warnings.empty?
|
|
571
|
+
display_message("✅ Configuration is valid!\n", type: :success)
|
|
572
|
+
display_message("All tiers have models configured", type: :info)
|
|
573
|
+
display_message("All configured models are valid for their providers\n", type: :info)
|
|
574
|
+
return
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
# Display errors
|
|
578
|
+
if issues.any?
|
|
579
|
+
display_message("❌ Found #{issues.size} configuration error#{"s" unless issues.size == 1}:\n", type: :error)
|
|
580
|
+
|
|
581
|
+
issues.each_with_index do |issue, idx|
|
|
582
|
+
display_message("\n#{idx + 1}. #{issue[:message]}", type: :error)
|
|
583
|
+
|
|
584
|
+
if issue[:tier]
|
|
585
|
+
display_message(" Tier: #{issue[:tier]}", type: :info)
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
if issue[:provider]
|
|
589
|
+
display_message(" Provider: #{issue[:provider]}", type: :info)
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
if issue[:model]
|
|
593
|
+
display_message(" Model: #{issue[:model]}", type: :info)
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
if issue[:fix]
|
|
597
|
+
display_message("\n 💡 Suggested fix:", type: :highlight)
|
|
598
|
+
display_message(" #{issue[:fix]}", type: :info)
|
|
599
|
+
end
|
|
600
|
+
end
|
|
601
|
+
display_message("\n", type: :info)
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
# Display warnings
|
|
605
|
+
if warnings.any?
|
|
606
|
+
display_message("⚠️ Found #{warnings.size} warning#{"s" unless warnings.size == 1}:\n", type: :warning)
|
|
607
|
+
|
|
608
|
+
warnings.each_with_index do |warning, idx|
|
|
609
|
+
display_message("\n#{idx + 1}. #{warning[:message]}", type: :warning)
|
|
610
|
+
|
|
611
|
+
if warning[:provider]
|
|
612
|
+
display_message(" Provider: #{warning[:provider]}", type: :info)
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
if warning[:model]
|
|
616
|
+
display_message(" Model: #{warning[:model]}", type: :info)
|
|
617
|
+
end
|
|
618
|
+
end
|
|
619
|
+
display_message("\n", type: :info)
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
# Display helpful tips
|
|
623
|
+
display_message("💡 Run 'aidp models discover' to see available models", type: :info)
|
|
624
|
+
display_message("💡 Run 'aidp models list --tier=<tier>' to see models for a specific tier\n", type: :info)
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
# Lightweight configuration wrapper for CLI validation
|
|
628
|
+
class SimpleConfiguration
|
|
629
|
+
def initialize(providers_section)
|
|
630
|
+
@providers = (providers_section || {}).each_with_object({}) do |(name, cfg), result|
|
|
631
|
+
result[name.to_s] = deep_symbolize(cfg || {})
|
|
632
|
+
end
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
def configured_providers
|
|
636
|
+
@providers.keys
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
def provider_config(name)
|
|
640
|
+
@providers[name.to_s] || {}
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
private
|
|
644
|
+
|
|
645
|
+
def deep_symbolize(value)
|
|
646
|
+
case value
|
|
647
|
+
when Hash
|
|
648
|
+
value.each_with_object({}) do |(key, val), result|
|
|
649
|
+
result_key = key.is_a?(String) ? key.to_sym : key
|
|
650
|
+
result[result_key] = deep_symbolize(val)
|
|
651
|
+
end
|
|
652
|
+
when Array
|
|
653
|
+
value.map { |item| deep_symbolize(item) }
|
|
654
|
+
else
|
|
655
|
+
value
|
|
656
|
+
end
|
|
657
|
+
end
|
|
658
|
+
end
|
|
659
|
+
private_constant :SimpleConfiguration
|
|
660
|
+
end
|
|
661
|
+
end
|
|
662
|
+
end
|