aidp 0.26.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/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 +29 -2
- data/lib/aidp/execute/workflow_selector.rb +2 -2
- data/lib/aidp/harness/config_manager.rb +5 -5
- data/lib/aidp/harness/configuration.rb +32 -2
- data/lib/aidp/harness/enhanced_runner.rb +24 -15
- data/lib/aidp/harness/error_handler.rb +26 -5
- 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/runner.rb +5 -0
- data/lib/aidp/harness/thinking_depth_manager.rb +223 -7
- data/lib/aidp/message_display.rb +0 -46
- 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/setup/wizard.rb +345 -8
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/plan_generator.rb +93 -14
- data/lib/aidp/watch/review_processor.rb +3 -3
- data/lib/aidp/workflows/guided_agent.rb +3 -3
- data/templates/aidp-development.yml.example +2 -2
- data/templates/aidp-production.yml.example +3 -3
- metadata +9 -1
data/lib/aidp/setup/wizard.rb
CHANGED
|
@@ -9,6 +9,7 @@ require "json"
|
|
|
9
9
|
|
|
10
10
|
require_relative "../util"
|
|
11
11
|
require_relative "../config/paths"
|
|
12
|
+
require_relative "../harness/capability_registry"
|
|
12
13
|
require_relative "provider_registry"
|
|
13
14
|
require_relative "devcontainer/parser"
|
|
14
15
|
require_relative "devcontainer/generator"
|
|
@@ -24,6 +25,11 @@ module Aidp
|
|
|
24
25
|
SCHEMA_VERSION = 1
|
|
25
26
|
DEVCONTAINER_COMPONENT = "setup_wizard.devcontainer"
|
|
26
27
|
|
|
28
|
+
DEFAULT_AUTOCONFIG_TIERS = %w[mini standard pro].freeze
|
|
29
|
+
LEGACY_TIER_ALIASES = {
|
|
30
|
+
advanced: :pro
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
27
33
|
attr_reader :project_dir, :prompt, :dry_run
|
|
28
34
|
|
|
29
35
|
def initialize(project_dir = Dir.pwd, prompt: nil, dry_run: false)
|
|
@@ -38,11 +44,13 @@ module Aidp
|
|
|
38
44
|
|
|
39
45
|
def run
|
|
40
46
|
display_welcome
|
|
41
|
-
# Normalize any legacy
|
|
47
|
+
# Normalize any legacy tier/model_family entries before prompting
|
|
42
48
|
normalize_existing_model_families!
|
|
49
|
+
normalize_existing_thinking_tiers!
|
|
43
50
|
return @saved if skip_wizard?
|
|
44
51
|
|
|
45
52
|
configure_providers
|
|
53
|
+
configure_thinking_tiers
|
|
46
54
|
configure_work_loop
|
|
47
55
|
configure_branching
|
|
48
56
|
configure_artifacts
|
|
@@ -51,6 +59,9 @@ module Aidp
|
|
|
51
59
|
configure_modes
|
|
52
60
|
configure_devcontainer
|
|
53
61
|
|
|
62
|
+
# Finalize any background model discovery
|
|
63
|
+
finalize_background_discovery
|
|
64
|
+
|
|
54
65
|
yaml_content = generate_yaml
|
|
55
66
|
display_preview(yaml_content)
|
|
56
67
|
display_diff(yaml_content) if @existing_config.any?
|
|
@@ -279,6 +290,249 @@ module Aidp
|
|
|
279
290
|
|
|
280
291
|
# Removed MCP configuration step (MCP now expected to be provider-specific if used)
|
|
281
292
|
|
|
293
|
+
# -------------------------------------------
|
|
294
|
+
# Thinking tier configuration (automated model discovery)
|
|
295
|
+
# -------------------------------------------
|
|
296
|
+
def configure_thinking_tiers
|
|
297
|
+
prompt.say("\n🧠 Thinking Tier Configuration")
|
|
298
|
+
prompt.say("-" * 40)
|
|
299
|
+
|
|
300
|
+
# Get configured providers
|
|
301
|
+
primary_provider = get([:harness, :default_provider])
|
|
302
|
+
fallback_providers = Array(get([:harness, :fallback_providers]))
|
|
303
|
+
all_providers = ([primary_provider] + fallback_providers).compact.uniq
|
|
304
|
+
|
|
305
|
+
if all_providers.empty?
|
|
306
|
+
prompt.warn("⚠️ No providers configured. Skipping tier configuration.")
|
|
307
|
+
return
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Check if user wants to use automated discovery
|
|
311
|
+
existing_tiers = get([:thinking, :tiers])
|
|
312
|
+
if existing_tiers && !existing_tiers.empty?
|
|
313
|
+
prompt.say("📝 Found existing tier configuration")
|
|
314
|
+
unless prompt.yes?("Would you like to update it with discovered models?", default: false)
|
|
315
|
+
return
|
|
316
|
+
end
|
|
317
|
+
elsif !prompt.yes?("Auto-configure thinking tiers with discovered models?", default: true)
|
|
318
|
+
prompt.say("💡 You can run 'aidp models discover' later to see available models")
|
|
319
|
+
return
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Run model discovery
|
|
323
|
+
prompt.say("\n🔍 Discovering available models...")
|
|
324
|
+
discovered_models = discover_models_for_providers(all_providers)
|
|
325
|
+
|
|
326
|
+
if discovered_models.empty?
|
|
327
|
+
prompt.warn("⚠️ No models discovered. Ensure provider CLIs are installed.")
|
|
328
|
+
prompt.say("💡 You can configure tiers manually or run 'aidp models discover' later")
|
|
329
|
+
return
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# Display discovered models
|
|
333
|
+
display_discovered_models(discovered_models)
|
|
334
|
+
|
|
335
|
+
# Generate tier configuration
|
|
336
|
+
tier_config = generate_tier_configuration(discovered_models, primary_provider)
|
|
337
|
+
|
|
338
|
+
# Show preview
|
|
339
|
+
prompt.say("\n📋 Proposed tier configuration:")
|
|
340
|
+
display_tier_preview(tier_config)
|
|
341
|
+
|
|
342
|
+
# Confirm and save
|
|
343
|
+
if prompt.yes?("\nSave this tier configuration?", default: true)
|
|
344
|
+
set([:thinking, :tiers], tier_config)
|
|
345
|
+
prompt.ok("✅ Thinking tiers configured successfully")
|
|
346
|
+
else
|
|
347
|
+
prompt.say("💡 Skipped tier configuration. You can run 'aidp models discover' later")
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# Trigger background model discovery for a provider
|
|
352
|
+
# Runs asynchronously and caches results without blocking the wizard
|
|
353
|
+
def trigger_background_discovery(provider_name)
|
|
354
|
+
return unless provider_available_for_discovery?(provider_name)
|
|
355
|
+
|
|
356
|
+
# Store reference to display notification later
|
|
357
|
+
@discovery_threads ||= []
|
|
358
|
+
|
|
359
|
+
thread = Thread.new do
|
|
360
|
+
discover_and_cache_models(provider_name)
|
|
361
|
+
rescue => e
|
|
362
|
+
Aidp.log_debug("setup_wizard", "background discovery failed",
|
|
363
|
+
provider: provider_name, error: e.message)
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
@discovery_threads << {thread: thread, provider: provider_name}
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
# Check if provider CLI is available for discovery
|
|
370
|
+
def provider_available_for_discovery?(provider_name)
|
|
371
|
+
provider_class = get_provider_class(provider_name)
|
|
372
|
+
return false unless provider_class
|
|
373
|
+
|
|
374
|
+
provider_class.respond_to?(:available?) && provider_class.available?
|
|
375
|
+
rescue => e
|
|
376
|
+
Aidp.log_debug("setup_wizard", "provider availability check failed",
|
|
377
|
+
provider: provider_name, error: e.message)
|
|
378
|
+
false
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
# Perform model discovery and cache results
|
|
382
|
+
def discover_and_cache_models(provider_name)
|
|
383
|
+
require_relative "../harness/model_discovery_service"
|
|
384
|
+
|
|
385
|
+
service = Aidp::Harness::ModelDiscoveryService.new
|
|
386
|
+
models = service.discover_models(provider_name, use_cache: false)
|
|
387
|
+
|
|
388
|
+
if models.any?
|
|
389
|
+
Aidp.log_info("setup_wizard", "discovered models in background",
|
|
390
|
+
provider: provider_name, count: models.size)
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
models
|
|
394
|
+
rescue => e
|
|
395
|
+
Aidp.log_debug("setup_wizard", "background discovery failed",
|
|
396
|
+
provider: provider_name, error: e.message)
|
|
397
|
+
[]
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# Get provider class for discovery
|
|
401
|
+
def get_provider_class(provider_name)
|
|
402
|
+
class_name = "Aidp::Providers::#{provider_name.capitalize}"
|
|
403
|
+
Object.const_get(class_name)
|
|
404
|
+
rescue NameError
|
|
405
|
+
nil
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
# Wait for background discovery to complete and show notifications
|
|
409
|
+
#
|
|
410
|
+
# @param timeout [Numeric] Maximum seconds to wait per thread (default: 5)
|
|
411
|
+
def finalize_background_discovery(timeout: 5)
|
|
412
|
+
return unless @discovery_threads&.any?
|
|
413
|
+
|
|
414
|
+
@discovery_threads.each do |entry|
|
|
415
|
+
thread = entry[:thread]
|
|
416
|
+
provider = entry[:provider]
|
|
417
|
+
|
|
418
|
+
# Wait up to timeout seconds for discovery to complete
|
|
419
|
+
thread.join(timeout)
|
|
420
|
+
|
|
421
|
+
if thread.alive?
|
|
422
|
+
Aidp.log_debug("setup_wizard", "discovery timeout, killing thread",
|
|
423
|
+
provider: provider)
|
|
424
|
+
# Kill thread to prevent hanging
|
|
425
|
+
thread.kill
|
|
426
|
+
thread.join(0.1) # Brief wait for cleanup
|
|
427
|
+
else
|
|
428
|
+
# Discovery completed - show notification
|
|
429
|
+
begin
|
|
430
|
+
require_relative "../harness/model_cache"
|
|
431
|
+
cache = Aidp::Harness::ModelCache.new
|
|
432
|
+
cached_models = cache.get_cached_models(provider)
|
|
433
|
+
|
|
434
|
+
if cached_models&.any?
|
|
435
|
+
prompt.say(" 💾 Discovered #{cached_models.size} model#{"s" unless cached_models.size == 1} for #{provider}")
|
|
436
|
+
end
|
|
437
|
+
rescue => e
|
|
438
|
+
Aidp.log_debug("setup_wizard", "failed to check cached models",
|
|
439
|
+
provider: provider, error: e.message)
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
@discovery_threads = []
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def discover_models_for_providers(providers)
|
|
448
|
+
require_relative "../harness/model_discovery_service"
|
|
449
|
+
|
|
450
|
+
service = Aidp::Harness::ModelDiscoveryService.new
|
|
451
|
+
all_models = {}
|
|
452
|
+
|
|
453
|
+
providers.each do |provider|
|
|
454
|
+
models = service.discover_models(provider, use_cache: true)
|
|
455
|
+
all_models[provider] = models if models.any?
|
|
456
|
+
rescue => e
|
|
457
|
+
Aidp.log_debug("setup_wizard", "discovery failed", provider: provider, error: e.message)
|
|
458
|
+
# Continue with other providers
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
all_models
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def display_discovered_models(discovered_models)
|
|
465
|
+
discovered_models.each do |provider, models|
|
|
466
|
+
prompt.say("\n✓ Found #{models.size} models for #{provider}:")
|
|
467
|
+
by_tier = models.group_by { |m| m[:tier] }
|
|
468
|
+
valid_thinking_tiers.each do |tier|
|
|
469
|
+
tier_models = by_tier[tier] || []
|
|
470
|
+
next if tier_models.empty?
|
|
471
|
+
|
|
472
|
+
prompt.say(" #{tier.capitalize} tier: #{tier_models.size} model#{"s" unless tier_models.size == 1}")
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
def generate_tier_configuration(discovered_models, primary_provider)
|
|
478
|
+
tier_config = {}
|
|
479
|
+
|
|
480
|
+
# Configure the three most common tiers: mini, standard, and pro
|
|
481
|
+
DEFAULT_AUTOCONFIG_TIERS.each do |tier|
|
|
482
|
+
tier_models = []
|
|
483
|
+
|
|
484
|
+
# Collect primary provider models first (if available)
|
|
485
|
+
primary_models = find_models_for_tier(discovered_models[primary_provider], tier)
|
|
486
|
+
tier_models.concat(primary_models) if primary_models&.any?
|
|
487
|
+
|
|
488
|
+
# Add models from other providers
|
|
489
|
+
discovered_models.each do |provider, models|
|
|
490
|
+
next if provider == primary_provider
|
|
491
|
+
provider_models = find_models_for_tier(models, tier)
|
|
492
|
+
tier_models.concat(provider_models) if provider_models&.any?
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
# Add to config if we found any models for this tier
|
|
496
|
+
if tier_models.any?
|
|
497
|
+
tier_config[tier.to_sym] = {
|
|
498
|
+
models: tier_models.map { |m|
|
|
499
|
+
{
|
|
500
|
+
provider: m[:provider],
|
|
501
|
+
model: m[:name]
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
tier_config
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
def find_model_for_tier(models, target_tier)
|
|
512
|
+
return nil unless models
|
|
513
|
+
|
|
514
|
+
models.find { |m| m[:tier] == target_tier }
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
def find_models_for_tier(models, target_tier)
|
|
518
|
+
return [] unless models
|
|
519
|
+
|
|
520
|
+
models.select { |m| m[:tier] == target_tier }
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
def display_tier_preview(tier_config)
|
|
524
|
+
return if tier_config.empty?
|
|
525
|
+
|
|
526
|
+
tier_config.each do |tier, config|
|
|
527
|
+
models = config[:models] || []
|
|
528
|
+
prompt.say(" #{tier}:")
|
|
529
|
+
models.each do |model_entry|
|
|
530
|
+
prompt.say(" - provider: #{model_entry[:provider]}")
|
|
531
|
+
prompt.say(" model: #{model_entry[:model]}")
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
end
|
|
535
|
+
|
|
282
536
|
# -------------------------------------------
|
|
283
537
|
# Work loop configuration
|
|
284
538
|
# -------------------------------------------
|
|
@@ -670,8 +924,18 @@ module Aidp
|
|
|
670
924
|
prompt.say("\n📋 Non-functional requirements & preferred libraries")
|
|
671
925
|
prompt.say("-" * 40)
|
|
672
926
|
|
|
673
|
-
|
|
927
|
+
# Check existing configuration for previous choice
|
|
928
|
+
existing_configure = @config.dig(:nfrs, :configure)
|
|
929
|
+
default_configure = existing_configure.nil? || existing_configure
|
|
930
|
+
|
|
931
|
+
configure = prompt.yes?("Configure NFRs?", default: default_configure)
|
|
932
|
+
|
|
933
|
+
unless configure
|
|
934
|
+
Aidp.log_debug("setup_wizard.nfrs", "opt_out")
|
|
935
|
+
return set([:nfrs, :configure], false)
|
|
936
|
+
end
|
|
674
937
|
|
|
938
|
+
set([:nfrs, :configure], true)
|
|
675
939
|
categories = %i[performance security reliability accessibility internationalization]
|
|
676
940
|
categories.each do |category|
|
|
677
941
|
existing = get([:nfrs, category])
|
|
@@ -835,7 +1099,7 @@ module Aidp
|
|
|
835
1099
|
|
|
836
1100
|
def configure_watch_labels
|
|
837
1101
|
prompt.say("\n🏷️ Watch mode label configuration")
|
|
838
|
-
prompt.say(" Configure GitHub issue labels that trigger watch mode actions")
|
|
1102
|
+
prompt.say(" Configure GitHub issue and PR labels that trigger watch mode actions")
|
|
839
1103
|
existing = get([:watch, :labels]) || {}
|
|
840
1104
|
|
|
841
1105
|
plan_trigger = ask_with_default(
|
|
@@ -858,11 +1122,29 @@ module Aidp
|
|
|
858
1122
|
existing[:build_trigger] || "aidp-build"
|
|
859
1123
|
)
|
|
860
1124
|
|
|
1125
|
+
review_trigger = ask_with_default(
|
|
1126
|
+
"Label to trigger code review",
|
|
1127
|
+
existing[:review_trigger] || "aidp-review"
|
|
1128
|
+
)
|
|
1129
|
+
|
|
1130
|
+
ci_fix_trigger = ask_with_default(
|
|
1131
|
+
"Label to trigger CI remediation",
|
|
1132
|
+
existing[:ci_fix_trigger] || "aidp-fix-ci"
|
|
1133
|
+
)
|
|
1134
|
+
|
|
1135
|
+
change_request_trigger = ask_with_default(
|
|
1136
|
+
"Label to trigger PR change implementation",
|
|
1137
|
+
existing[:change_request_trigger] || "aidp-request-changes"
|
|
1138
|
+
)
|
|
1139
|
+
|
|
861
1140
|
set([:watch, :labels], {
|
|
862
1141
|
plan_trigger: plan_trigger,
|
|
863
1142
|
needs_input: needs_input,
|
|
864
1143
|
ready_to_build: ready_to_build,
|
|
865
|
-
build_trigger: build_trigger
|
|
1144
|
+
build_trigger: build_trigger,
|
|
1145
|
+
review_trigger: review_trigger,
|
|
1146
|
+
ci_fix_trigger: ci_fix_trigger,
|
|
1147
|
+
change_request_trigger: change_request_trigger
|
|
866
1148
|
})
|
|
867
1149
|
end
|
|
868
1150
|
|
|
@@ -953,9 +1235,9 @@ module Aidp
|
|
|
953
1235
|
def show_next_steps
|
|
954
1236
|
prompt.say("\n🎉 Setup complete!")
|
|
955
1237
|
prompt.say("\nNext steps:")
|
|
956
|
-
prompt.say(" 1.
|
|
957
|
-
prompt.say(" 2. Run 'aidp
|
|
958
|
-
prompt.say(" 3. Run 'aidp
|
|
1238
|
+
prompt.say(" 1. Configure provider tools (set required API keys or connections).")
|
|
1239
|
+
prompt.say(" 2. Run 'aidp' to start a work loop.")
|
|
1240
|
+
prompt.say(" 3. Run 'aidp watch <owner/repo>' to enable watch mode automation.")
|
|
959
1241
|
prompt.say("")
|
|
960
1242
|
end
|
|
961
1243
|
|
|
@@ -1225,6 +1507,9 @@ module Aidp
|
|
|
1225
1507
|
# Enhance messaging with display name when available
|
|
1226
1508
|
display_name = discover_available_providers.invert.fetch(provider_name, provider_name)
|
|
1227
1509
|
prompt.say(" • #{action_word.capitalize} provider '#{display_name}' (#{provider_name}) with billing type '#{provider_type}' and model family '#{model_family}'")
|
|
1510
|
+
|
|
1511
|
+
# Trigger background model discovery for newly added/updated provider
|
|
1512
|
+
trigger_background_discovery(provider_name) unless @dry_run
|
|
1228
1513
|
end
|
|
1229
1514
|
|
|
1230
1515
|
def edit_or_remove_provider(provider_name, primary_provider, fallbacks)
|
|
@@ -1335,6 +1620,44 @@ module Aidp
|
|
|
1335
1620
|
end
|
|
1336
1621
|
end
|
|
1337
1622
|
|
|
1623
|
+
def normalize_existing_thinking_tiers!
|
|
1624
|
+
tiers_cfg = @config.dig(:thinking, :tiers)
|
|
1625
|
+
return unless tiers_cfg.is_a?(Hash)
|
|
1626
|
+
|
|
1627
|
+
LEGACY_TIER_ALIASES.each do |legacy, canonical|
|
|
1628
|
+
next unless tiers_cfg.key?(legacy)
|
|
1629
|
+
|
|
1630
|
+
legacy_cfg = tiers_cfg.delete(legacy) || {}
|
|
1631
|
+
canonical_cfg = tiers_cfg[canonical] || {}
|
|
1632
|
+
merged_models = merge_tier_models(canonical_cfg[:models], legacy_cfg[:models])
|
|
1633
|
+
tiers_cfg[canonical] = canonical_cfg.merge(models: merged_models)
|
|
1634
|
+
@warnings << "Normalized thinking tier '#{legacy}' to '#{canonical}'"
|
|
1635
|
+
end
|
|
1636
|
+
|
|
1637
|
+
valid = valid_thinking_tiers
|
|
1638
|
+
tiers_cfg.keys.each do |tier|
|
|
1639
|
+
next if valid.include?(tier.to_s)
|
|
1640
|
+
|
|
1641
|
+
tiers_cfg.delete(tier)
|
|
1642
|
+
@warnings << "Removed unsupported thinking tier '#{tier}' from configuration"
|
|
1643
|
+
end
|
|
1644
|
+
end
|
|
1645
|
+
|
|
1646
|
+
def merge_tier_models(existing_models, new_models)
|
|
1647
|
+
combined = []
|
|
1648
|
+
(Array(existing_models) + Array(new_models)).each do |entry|
|
|
1649
|
+
next unless entry.is_a?(Hash)
|
|
1650
|
+
provider = entry[:provider]
|
|
1651
|
+
model = entry[:model]
|
|
1652
|
+
next unless provider && model
|
|
1653
|
+
|
|
1654
|
+
unless combined.any? { |m| m[:provider] == provider && m[:model] == model }
|
|
1655
|
+
combined << entry
|
|
1656
|
+
end
|
|
1657
|
+
end
|
|
1658
|
+
combined
|
|
1659
|
+
end
|
|
1660
|
+
|
|
1338
1661
|
def load_existing_config
|
|
1339
1662
|
return {} unless File.exist?(config_path)
|
|
1340
1663
|
YAML.safe_load_file(config_path, permitted_classes: [Time]) || {}
|
|
@@ -1468,6 +1791,12 @@ module Aidp
|
|
|
1468
1791
|
File.exist?(File.join(project_dir, relative_path))
|
|
1469
1792
|
end
|
|
1470
1793
|
|
|
1794
|
+
def valid_thinking_tiers
|
|
1795
|
+
Aidp::Harness::CapabilityRegistry::VALID_TIERS
|
|
1796
|
+
rescue NameError
|
|
1797
|
+
%w[mini standard thinking pro max]
|
|
1798
|
+
end
|
|
1799
|
+
|
|
1471
1800
|
def configure_devcontainer
|
|
1472
1801
|
prompt.say("\n🐳 Devcontainer Configuration")
|
|
1473
1802
|
Aidp.log_debug(DEVCONTAINER_COMPONENT, "configure.start")
|
|
@@ -1482,10 +1811,18 @@ module Aidp
|
|
|
1482
1811
|
path: parser.detect)
|
|
1483
1812
|
end
|
|
1484
1813
|
|
|
1814
|
+
# Check existing configuration for previous choice
|
|
1815
|
+
existing_manage = @config.dig(:devcontainer, :manage)
|
|
1816
|
+
default_manage = if existing_manage.nil?
|
|
1817
|
+
existing_devcontainer ? true : false
|
|
1818
|
+
else
|
|
1819
|
+
existing_manage
|
|
1820
|
+
end
|
|
1821
|
+
|
|
1485
1822
|
# Ask if user wants AIDP to manage devcontainer
|
|
1486
1823
|
manage = prompt.yes?(
|
|
1487
1824
|
"Would you like AIDP to manage your devcontainer configuration?",
|
|
1488
|
-
default:
|
|
1825
|
+
default: default_manage
|
|
1489
1826
|
)
|
|
1490
1827
|
|
|
1491
1828
|
unless manage
|
data/lib/aidp/version.rb
CHANGED
|
@@ -29,32 +29,103 @@ module Aidp
|
|
|
29
29
|
def initialize(provider_name: nil, verbose: false)
|
|
30
30
|
@provider_name = provider_name
|
|
31
31
|
@verbose = verbose
|
|
32
|
+
@providers_attempted = []
|
|
32
33
|
end
|
|
33
34
|
|
|
34
35
|
def generate(issue)
|
|
35
|
-
provider
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
Aidp.log_debug("plan_generator", "generate.start", provider: @provider_name, issue: issue[:number])
|
|
37
|
+
|
|
38
|
+
# Try providers in fallback chain order
|
|
39
|
+
providers_to_try = build_provider_fallback_chain
|
|
40
|
+
Aidp.log_debug("plan_generator", "fallback_chain", providers: providers_to_try, count: providers_to_try.size)
|
|
41
|
+
|
|
42
|
+
providers_to_try.each do |provider_name|
|
|
43
|
+
next if @providers_attempted.include?(provider_name)
|
|
44
|
+
|
|
45
|
+
Aidp.log_debug("plan_generator", "trying_provider", provider: provider_name, attempted: @providers_attempted)
|
|
46
|
+
|
|
47
|
+
provider = resolve_provider(provider_name)
|
|
48
|
+
unless provider
|
|
49
|
+
Aidp.log_debug("plan_generator", "provider_unavailable", provider: provider_name, reason: "not resolved")
|
|
50
|
+
@providers_attempted << provider_name
|
|
51
|
+
next
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
begin
|
|
55
|
+
Aidp.log_info("plan_generator", "generate_with_provider", provider: provider_name, issue: issue[:number])
|
|
56
|
+
result = generate_with_provider(provider, issue, provider_name)
|
|
57
|
+
if result
|
|
58
|
+
Aidp.log_info("plan_generator", "generation_success", provider: provider_name, issue: issue[:number])
|
|
59
|
+
return result
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Provider returned nil - try next provider
|
|
63
|
+
Aidp.log_warn("plan_generator", "provider_returned_nil", provider: provider_name)
|
|
64
|
+
@providers_attempted << provider_name
|
|
65
|
+
rescue => e
|
|
66
|
+
# Log error and try next provider in chain
|
|
67
|
+
Aidp.log_warn("plan_generator", "provider_failed", provider: provider_name, error: e.message, error_class: e.class.name)
|
|
68
|
+
@providers_attempted << provider_name
|
|
69
|
+
end
|
|
41
70
|
end
|
|
71
|
+
|
|
72
|
+
# All providers exhausted, fall back to heuristic
|
|
73
|
+
Aidp.log_warn("plan_generator", "all_providers_exhausted", attempted: @providers_attempted, falling_back: "heuristic")
|
|
74
|
+
display_message("⚠️ All providers unavailable or failed. Falling back to heuristic plan.", type: :warn)
|
|
75
|
+
heuristic_plan(issue)
|
|
42
76
|
rescue => e
|
|
43
|
-
|
|
77
|
+
Aidp.log_error("plan_generator", "generation_failed_unexpectedly", error: e.message, backtrace: e.backtrace&.first(3))
|
|
78
|
+
display_message("⚠️ Plan generation failed unexpectedly (#{e.message}). Using heuristic.", type: :warn)
|
|
44
79
|
heuristic_plan(issue)
|
|
45
80
|
end
|
|
46
81
|
|
|
47
82
|
private
|
|
48
83
|
|
|
49
|
-
def
|
|
50
|
-
|
|
84
|
+
def build_provider_fallback_chain
|
|
85
|
+
# Start with specified provider or default
|
|
86
|
+
primary_provider = @provider_name || detect_default_provider
|
|
87
|
+
providers = []
|
|
88
|
+
|
|
89
|
+
# Add primary provider first
|
|
90
|
+
providers << primary_provider if primary_provider
|
|
91
|
+
|
|
92
|
+
# Try to get fallback chain from config
|
|
93
|
+
begin
|
|
94
|
+
config_manager = Aidp::Harness::ConfigManager.new(Dir.pwd)
|
|
95
|
+
fallback_providers = config_manager.fallback_providers || []
|
|
96
|
+
|
|
97
|
+
# Add fallback providers that aren't already in the list
|
|
98
|
+
fallback_providers.each do |fallback|
|
|
99
|
+
providers << fallback unless providers.include?(fallback)
|
|
100
|
+
end
|
|
101
|
+
rescue => e
|
|
102
|
+
Aidp.log_debug("plan_generator", "config_fallback_unavailable", error: e.message)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# If we still have no providers, add cursor as last resort
|
|
106
|
+
providers << "cursor" if providers.empty?
|
|
107
|
+
|
|
108
|
+
# Remove duplicates while preserving order
|
|
109
|
+
providers.uniq
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def resolve_provider(provider_name = nil)
|
|
113
|
+
provider_name ||= @provider_name || detect_default_provider
|
|
51
114
|
return nil unless provider_name
|
|
52
115
|
|
|
116
|
+
Aidp.log_debug("plan_generator", "resolve_provider", provider: provider_name)
|
|
117
|
+
|
|
53
118
|
provider = Aidp::ProviderManager.get_provider(provider_name, use_harness: false)
|
|
54
|
-
return provider if provider&.available?
|
|
55
119
|
|
|
120
|
+
if provider&.available?
|
|
121
|
+
Aidp.log_debug("plan_generator", "provider_resolved", provider: provider_name, available: true)
|
|
122
|
+
return provider
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
Aidp.log_debug("plan_generator", "provider_not_available", provider: provider_name, available: provider&.available?)
|
|
56
126
|
nil
|
|
57
127
|
rescue => e
|
|
128
|
+
Aidp.log_warn("plan_generator", "resolve_provider_failed", provider: provider_name, error: e.message)
|
|
58
129
|
display_message("⚠️ Failed to resolve provider #{provider_name}: #{e.message}", type: :warn)
|
|
59
130
|
nil
|
|
60
131
|
end
|
|
@@ -66,9 +137,11 @@ module Aidp
|
|
|
66
137
|
"cursor"
|
|
67
138
|
end
|
|
68
139
|
|
|
69
|
-
def generate_with_provider(provider, issue)
|
|
140
|
+
def generate_with_provider(provider, issue, provider_name = "unknown")
|
|
70
141
|
payload = build_prompt(issue)
|
|
71
142
|
|
|
143
|
+
Aidp.log_debug("plan_generator", "sending_to_provider", provider: provider_name, prompt_length: payload.length)
|
|
144
|
+
|
|
72
145
|
if @verbose
|
|
73
146
|
display_message("\n--- Plan Generation Prompt ---", type: :muted)
|
|
74
147
|
display_message(payload.strip, type: :muted)
|
|
@@ -77,6 +150,8 @@ module Aidp
|
|
|
77
150
|
|
|
78
151
|
response = provider.send_message(prompt: payload)
|
|
79
152
|
|
|
153
|
+
Aidp.log_debug("plan_generator", "provider_response_received", provider: provider_name, response_length: response&.length || 0)
|
|
154
|
+
|
|
80
155
|
if @verbose
|
|
81
156
|
display_message("\n--- Provider Response ---", type: :muted)
|
|
82
157
|
display_message(response.strip, type: :muted)
|
|
@@ -85,10 +160,14 @@ module Aidp
|
|
|
85
160
|
|
|
86
161
|
parsed = parse_structured_response(response)
|
|
87
162
|
|
|
88
|
-
|
|
163
|
+
if parsed
|
|
164
|
+
Aidp.log_debug("plan_generator", "response_parsed", provider: provider_name, has_summary: !parsed[:summary].to_s.empty?, tasks_count: parsed[:tasks]&.size || 0)
|
|
165
|
+
return parsed
|
|
166
|
+
end
|
|
89
167
|
|
|
90
|
-
|
|
91
|
-
|
|
168
|
+
Aidp.log_warn("plan_generator", "parse_failed", provider: provider_name)
|
|
169
|
+
display_message("⚠️ Unable to parse #{provider_name} response. Trying next provider.", type: :warn)
|
|
170
|
+
nil
|
|
92
171
|
end
|
|
93
172
|
|
|
94
173
|
def build_prompt(issue)
|
|
@@ -23,7 +23,7 @@ module Aidp
|
|
|
23
23
|
|
|
24
24
|
attr_reader :review_label
|
|
25
25
|
|
|
26
|
-
def initialize(repository_client:, state_store:, provider_name: nil, project_dir: Dir.pwd, label_config: {}, verbose: false)
|
|
26
|
+
def initialize(repository_client:, state_store:, provider_name: nil, project_dir: Dir.pwd, label_config: {}, verbose: false, reviewers: nil)
|
|
27
27
|
@repository_client = repository_client
|
|
28
28
|
@state_store = state_store
|
|
29
29
|
@provider_name = provider_name
|
|
@@ -33,8 +33,8 @@ module Aidp
|
|
|
33
33
|
# Load label configuration
|
|
34
34
|
@review_label = label_config[:review_trigger] || label_config["review_trigger"] || DEFAULT_REVIEW_LABEL
|
|
35
35
|
|
|
36
|
-
# Initialize reviewers
|
|
37
|
-
@reviewers = [
|
|
36
|
+
# Initialize reviewers (allow dependency injection for testing)
|
|
37
|
+
@reviewers = reviewers || [
|
|
38
38
|
Reviewers::SeniorDevReviewer.new(provider_name: provider_name),
|
|
39
39
|
Reviewers::SecurityReviewer.new(provider_name: provider_name),
|
|
40
40
|
Reviewers::PerformanceReviewer.new(provider_name: provider_name)
|
|
@@ -19,7 +19,7 @@ module Aidp
|
|
|
19
19
|
|
|
20
20
|
class ConversationError < StandardError; end
|
|
21
21
|
|
|
22
|
-
def initialize(project_dir, prompt: nil, use_enhanced_input: true, verbose: false)
|
|
22
|
+
def initialize(project_dir, prompt: nil, use_enhanced_input: true, verbose: false, config_manager: nil, provider_manager: nil)
|
|
23
23
|
@project_dir = project_dir
|
|
24
24
|
|
|
25
25
|
# Use EnhancedInput with Reline for full readline-style key bindings
|
|
@@ -29,8 +29,8 @@ module Aidp
|
|
|
29
29
|
prompt || TTY::Prompt.new
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
@config_manager = Aidp::Harness::ConfigManager.new(project_dir)
|
|
33
|
-
@provider_manager = Aidp::Harness::ProviderManager.new(@config_manager, prompt: @prompt)
|
|
32
|
+
@config_manager = config_manager || Aidp::Harness::ConfigManager.new(project_dir)
|
|
33
|
+
@provider_manager = provider_manager || Aidp::Harness::ProviderManager.new(@config_manager, prompt: @prompt)
|
|
34
34
|
@conversation_history = []
|
|
35
35
|
@user_input = {}
|
|
36
36
|
@verbose = verbose
|
|
@@ -137,7 +137,7 @@ providers:
|
|
|
137
137
|
file_upload: true
|
|
138
138
|
code_generation: true
|
|
139
139
|
analysis: true
|
|
140
|
-
|
|
140
|
+
supports_json_mode: true
|
|
141
141
|
|
|
142
142
|
# Monitoring configuration (enhanced for development)
|
|
143
143
|
monitoring:
|
|
@@ -243,7 +243,7 @@ providers:
|
|
|
243
243
|
code_generation: true
|
|
244
244
|
analysis: true
|
|
245
245
|
vision: true
|
|
246
|
-
|
|
246
|
+
supports_json_mode: true
|
|
247
247
|
function_calling: true
|
|
248
248
|
tool_use: true
|
|
249
249
|
|