aidp 0.26.0 → 0.28.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.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +89 -0
  3. data/lib/aidp/cli/checkpoint_command.rb +198 -0
  4. data/lib/aidp/cli/config_command.rb +71 -0
  5. data/lib/aidp/cli/enhanced_input.rb +2 -0
  6. data/lib/aidp/cli/first_run_wizard.rb +8 -7
  7. data/lib/aidp/cli/harness_command.rb +102 -0
  8. data/lib/aidp/cli/jobs_command.rb +3 -3
  9. data/lib/aidp/cli/mcp_dashboard.rb +4 -3
  10. data/lib/aidp/cli/models_command.rb +661 -0
  11. data/lib/aidp/cli/providers_command.rb +223 -0
  12. data/lib/aidp/cli.rb +45 -464
  13. data/lib/aidp/config.rb +54 -0
  14. data/lib/aidp/daemon/runner.rb +2 -2
  15. data/lib/aidp/debug_mixin.rb +25 -10
  16. data/lib/aidp/execute/agent_signal_parser.rb +22 -0
  17. data/lib/aidp/execute/async_work_loop_runner.rb +2 -1
  18. data/lib/aidp/execute/checkpoint_display.rb +38 -37
  19. data/lib/aidp/execute/interactive_repl.rb +2 -1
  20. data/lib/aidp/execute/prompt_manager.rb +4 -4
  21. data/lib/aidp/execute/repl_macros.rb +2 -2
  22. data/lib/aidp/execute/steps.rb +94 -1
  23. data/lib/aidp/execute/work_loop_runner.rb +238 -19
  24. data/lib/aidp/execute/workflow_selector.rb +4 -27
  25. data/lib/aidp/firewall/provider_requirements_collector.rb +262 -0
  26. data/lib/aidp/harness/ai_decision_engine.rb +35 -2
  27. data/lib/aidp/harness/config_manager.rb +5 -10
  28. data/lib/aidp/harness/config_schema.rb +8 -0
  29. data/lib/aidp/harness/configuration.rb +40 -2
  30. data/lib/aidp/harness/enhanced_runner.rb +25 -19
  31. data/lib/aidp/harness/error_handler.rb +23 -73
  32. data/lib/aidp/harness/model_cache.rb +269 -0
  33. data/lib/aidp/harness/model_discovery_service.rb +259 -0
  34. data/lib/aidp/harness/model_registry.rb +201 -0
  35. data/lib/aidp/harness/provider_factory.rb +11 -2
  36. data/lib/aidp/harness/runner.rb +5 -0
  37. data/lib/aidp/harness/state_manager.rb +0 -7
  38. data/lib/aidp/harness/thinking_depth_manager.rb +202 -7
  39. data/lib/aidp/harness/ui/enhanced_tui.rb +8 -18
  40. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +0 -18
  41. data/lib/aidp/harness/ui/progress_display.rb +6 -2
  42. data/lib/aidp/harness/user_interface.rb +0 -58
  43. data/lib/aidp/init/runner.rb +7 -2
  44. data/lib/aidp/message_display.rb +0 -46
  45. data/lib/aidp/planning/analyzers/feedback_analyzer.rb +365 -0
  46. data/lib/aidp/planning/builders/agile_plan_builder.rb +387 -0
  47. data/lib/aidp/planning/builders/project_plan_builder.rb +193 -0
  48. data/lib/aidp/planning/generators/gantt_generator.rb +190 -0
  49. data/lib/aidp/planning/generators/iteration_plan_generator.rb +392 -0
  50. data/lib/aidp/planning/generators/legacy_research_planner.rb +473 -0
  51. data/lib/aidp/planning/generators/marketing_report_generator.rb +348 -0
  52. data/lib/aidp/planning/generators/mvp_scope_generator.rb +310 -0
  53. data/lib/aidp/planning/generators/user_test_plan_generator.rb +373 -0
  54. data/lib/aidp/planning/generators/wbs_generator.rb +259 -0
  55. data/lib/aidp/planning/mappers/persona_mapper.rb +163 -0
  56. data/lib/aidp/planning/parsers/document_parser.rb +141 -0
  57. data/lib/aidp/planning/parsers/feedback_data_parser.rb +252 -0
  58. data/lib/aidp/provider_manager.rb +8 -32
  59. data/lib/aidp/providers/adapter.rb +2 -4
  60. data/lib/aidp/providers/aider.rb +264 -0
  61. data/lib/aidp/providers/anthropic.rb +206 -121
  62. data/lib/aidp/providers/base.rb +123 -3
  63. data/lib/aidp/providers/capability_registry.rb +0 -1
  64. data/lib/aidp/providers/codex.rb +75 -70
  65. data/lib/aidp/providers/cursor.rb +87 -59
  66. data/lib/aidp/providers/gemini.rb +57 -60
  67. data/lib/aidp/providers/github_copilot.rb +19 -66
  68. data/lib/aidp/providers/kilocode.rb +35 -80
  69. data/lib/aidp/providers/opencode.rb +35 -80
  70. data/lib/aidp/setup/wizard.rb +555 -8
  71. data/lib/aidp/version.rb +1 -1
  72. data/lib/aidp/watch/build_processor.rb +211 -30
  73. data/lib/aidp/watch/change_request_processor.rb +128 -14
  74. data/lib/aidp/watch/ci_fix_processor.rb +103 -37
  75. data/lib/aidp/watch/ci_log_extractor.rb +258 -0
  76. data/lib/aidp/watch/github_state_extractor.rb +177 -0
  77. data/lib/aidp/watch/implementation_verifier.rb +284 -0
  78. data/lib/aidp/watch/plan_generator.rb +95 -52
  79. data/lib/aidp/watch/plan_processor.rb +7 -6
  80. data/lib/aidp/watch/repository_client.rb +245 -17
  81. data/lib/aidp/watch/review_processor.rb +100 -19
  82. data/lib/aidp/watch/reviewers/base_reviewer.rb +1 -1
  83. data/lib/aidp/watch/runner.rb +181 -29
  84. data/lib/aidp/watch/state_store.rb +22 -1
  85. data/lib/aidp/workflows/definitions.rb +147 -0
  86. data/lib/aidp/workflows/guided_agent.rb +3 -3
  87. data/lib/aidp/workstream_cleanup.rb +245 -0
  88. data/lib/aidp/worktree.rb +19 -0
  89. data/templates/aidp-development.yml.example +2 -2
  90. data/templates/aidp-production.yml.example +3 -3
  91. data/templates/aidp.yml.example +57 -0
  92. data/templates/implementation/generate_tdd_specs.md +213 -0
  93. data/templates/implementation/iterative_implementation.md +122 -0
  94. data/templates/planning/agile/analyze_feedback.md +183 -0
  95. data/templates/planning/agile/generate_iteration_plan.md +179 -0
  96. data/templates/planning/agile/generate_legacy_research_plan.md +171 -0
  97. data/templates/planning/agile/generate_marketing_report.md +162 -0
  98. data/templates/planning/agile/generate_mvp_scope.md +127 -0
  99. data/templates/planning/agile/generate_user_test_plan.md +143 -0
  100. data/templates/planning/agile/ingest_feedback.md +174 -0
  101. data/templates/planning/assemble_project_plan.md +113 -0
  102. data/templates/planning/assign_personas.md +108 -0
  103. data/templates/planning/create_tasks.md +52 -6
  104. data/templates/planning/generate_gantt.md +86 -0
  105. data/templates/planning/generate_wbs.md +85 -0
  106. data/templates/planning/initialize_planning_mode.md +70 -0
  107. data/templates/skills/README.md +2 -2
  108. data/templates/skills/marketing_strategist/SKILL.md +279 -0
  109. data/templates/skills/product_manager/SKILL.md +177 -0
  110. data/templates/skills/ruby_aidp_planning/SKILL.md +497 -0
  111. data/templates/skills/ruby_rspec_tdd/SKILL.md +514 -0
  112. data/templates/skills/ux_researcher/SKILL.md +222 -0
  113. metadata +47 -1
@@ -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 or label-based model_family entries before prompting
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,251 @@ 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
+ has_existing_tiers = all_providers.any? do |provider|
312
+ existing_tiers = get([:providers, provider.to_sym, :thinking_tiers])
313
+ existing_tiers && !existing_tiers.empty?
314
+ end
315
+
316
+ if has_existing_tiers
317
+ prompt.say("📝 Found existing tier configuration")
318
+ unless prompt.yes?("Would you like to update it with discovered models?", default: false)
319
+ return
320
+ end
321
+ elsif !prompt.yes?("Auto-configure thinking tiers with discovered models?", default: true)
322
+ prompt.say("💡 You can run 'aidp models discover' later to see available models")
323
+ return
324
+ end
325
+
326
+ # Run model discovery
327
+ prompt.say("\n🔍 Discovering available models...")
328
+ discovered_models = discover_models_for_providers(all_providers)
329
+
330
+ if discovered_models.empty?
331
+ prompt.warn("⚠️ No models discovered. Ensure provider CLIs are installed.")
332
+ prompt.say("💡 You can configure tiers manually or run 'aidp models discover' later")
333
+ return
334
+ end
335
+
336
+ # Display discovered models
337
+ display_discovered_models(discovered_models)
338
+
339
+ # Generate tier configuration (now provider-specific)
340
+ tier_configs = generate_provider_tier_configurations(discovered_models)
341
+
342
+ # Show preview
343
+ prompt.say("\n📋 Proposed tier configuration:")
344
+ display_provider_tier_preview(tier_configs)
345
+
346
+ # Confirm and save
347
+ if prompt.yes?("\nSave this tier configuration?", default: true)
348
+ # Write to provider-specific paths
349
+ tier_configs.each do |provider, provider_tiers|
350
+ set([:providers, provider.to_sym, :thinking_tiers], provider_tiers)
351
+ end
352
+ prompt.ok("✅ Thinking tiers configured successfully")
353
+ else
354
+ prompt.say("💡 Skipped tier configuration. You can run 'aidp models discover' later")
355
+ end
356
+ end
357
+
358
+ # Trigger background model discovery for a provider
359
+ # Runs asynchronously and caches results without blocking the wizard
360
+ def trigger_background_discovery(provider_name)
361
+ return unless provider_available_for_discovery?(provider_name)
362
+
363
+ # Store reference to display notification later
364
+ @discovery_threads ||= []
365
+
366
+ thread = Thread.new do
367
+ discover_and_cache_models(provider_name)
368
+ rescue => e
369
+ Aidp.log_debug("setup_wizard", "background discovery failed",
370
+ provider: provider_name, error: e.message)
371
+ end
372
+
373
+ @discovery_threads << {thread: thread, provider: provider_name}
374
+ end
375
+
376
+ # Check if provider CLI is available for discovery
377
+ def provider_available_for_discovery?(provider_name)
378
+ provider_class = get_provider_class(provider_name)
379
+ return false unless provider_class
380
+
381
+ provider_class.respond_to?(:available?) && provider_class.available?
382
+ rescue => e
383
+ Aidp.log_debug("setup_wizard", "provider availability check failed",
384
+ provider: provider_name, error: e.message)
385
+ false
386
+ end
387
+
388
+ # Perform model discovery and cache results
389
+ def discover_and_cache_models(provider_name)
390
+ require_relative "../harness/model_discovery_service"
391
+
392
+ service = Aidp::Harness::ModelDiscoveryService.new
393
+ models = service.discover_models(provider_name, use_cache: false)
394
+
395
+ if models.any?
396
+ Aidp.log_info("setup_wizard", "discovered models in background",
397
+ provider: provider_name, count: models.size)
398
+ end
399
+
400
+ models
401
+ rescue => e
402
+ Aidp.log_debug("setup_wizard", "background discovery failed",
403
+ provider: provider_name, error: e.message)
404
+ []
405
+ end
406
+
407
+ # Get provider class for discovery
408
+ def get_provider_class(provider_name)
409
+ class_name = "Aidp::Providers::#{provider_name.capitalize}"
410
+ Object.const_get(class_name)
411
+ rescue NameError
412
+ nil
413
+ end
414
+
415
+ # Wait for background discovery to complete and show notifications
416
+ #
417
+ # @param timeout [Numeric] Maximum seconds to wait per thread (default: 5)
418
+ def finalize_background_discovery(timeout: 5)
419
+ return unless @discovery_threads&.any?
420
+
421
+ @discovery_threads.each do |entry|
422
+ thread = entry[:thread]
423
+ provider = entry[:provider]
424
+
425
+ # Wait up to timeout seconds for discovery to complete
426
+ thread.join(timeout)
427
+
428
+ if thread.alive?
429
+ Aidp.log_debug("setup_wizard", "discovery timeout, killing thread",
430
+ provider: provider)
431
+ # Kill thread to prevent hanging
432
+ thread.kill
433
+ thread.join(0.1) # Brief wait for cleanup
434
+ else
435
+ # Discovery completed - show notification
436
+ begin
437
+ require_relative "../harness/model_cache"
438
+ cache = Aidp::Harness::ModelCache.new
439
+ cached_models = cache.get_cached_models(provider)
440
+
441
+ if cached_models&.any?
442
+ prompt.say(" 💾 Discovered #{cached_models.size} model#{"s" unless cached_models.size == 1} for #{provider}")
443
+ end
444
+ rescue => e
445
+ Aidp.log_debug("setup_wizard", "failed to check cached models",
446
+ provider: provider, error: e.message)
447
+ end
448
+ end
449
+ end
450
+
451
+ @discovery_threads = []
452
+ end
453
+
454
+ def discover_models_for_providers(providers)
455
+ require_relative "../harness/model_discovery_service"
456
+
457
+ service = Aidp::Harness::ModelDiscoveryService.new
458
+ all_models = {}
459
+
460
+ providers.each do |provider|
461
+ models = service.discover_models(provider, use_cache: true)
462
+ all_models[provider] = models if models.any?
463
+ rescue => e
464
+ Aidp.log_debug("setup_wizard", "discovery failed", provider: provider, error: e.message)
465
+ # Continue with other providers
466
+ end
467
+
468
+ all_models
469
+ end
470
+
471
+ def display_discovered_models(discovered_models)
472
+ discovered_models.each do |provider, models|
473
+ prompt.say("\n✓ Found #{models.size} models for #{provider}:")
474
+ by_tier = models.group_by { |m| m[:tier] }
475
+ valid_thinking_tiers.each do |tier|
476
+ tier_models = by_tier[tier] || []
477
+ next if tier_models.empty?
478
+
479
+ prompt.say(" #{tier.capitalize} tier: #{tier_models.size} model#{"s" unless tier_models.size == 1}")
480
+ end
481
+ end
482
+ end
483
+
484
+ def generate_provider_tier_configurations(discovered_models)
485
+ provider_configs = {}
486
+
487
+ # Organize by provider first, then by tier
488
+ discovered_models.each do |provider, models|
489
+ provider_tiers = {}
490
+
491
+ # Configure the three most common tiers: mini, standard, and pro
492
+ DEFAULT_AUTOCONFIG_TIERS.each do |tier|
493
+ tier_models = find_models_for_tier(models, tier)
494
+
495
+ # Add to config if we found any models for this tier
496
+ if tier_models.any?
497
+ provider_tiers[tier.to_sym] = {
498
+ models: tier_models.map { |m| m[:name] }
499
+ }
500
+ end
501
+ end
502
+
503
+ # Only add provider if it has at least one tier configured
504
+ provider_configs[provider] = provider_tiers if provider_tiers.any?
505
+ end
506
+
507
+ provider_configs
508
+ end
509
+
510
+ def find_model_for_tier(models, target_tier)
511
+ return nil unless models
512
+
513
+ models.find { |m| m[:tier] == target_tier }
514
+ end
515
+
516
+ def find_models_for_tier(models, target_tier)
517
+ return [] unless models
518
+
519
+ models.select { |m| m[:tier] == target_tier }
520
+ end
521
+
522
+ def display_provider_tier_preview(provider_configs)
523
+ return if provider_configs.empty?
524
+
525
+ provider_configs.each do |provider, provider_tiers|
526
+ prompt.say(" #{provider}:")
527
+ provider_tiers.each do |tier, tier_config|
528
+ models = tier_config[:models] || []
529
+ prompt.say(" #{tier}:")
530
+ models.each do |model_name|
531
+ prompt.say(" - #{model_name}")
532
+ end
533
+ end
534
+ prompt.say("") # Blank line between providers
535
+ end
536
+ end
537
+
282
538
  # -------------------------------------------
283
539
  # Work loop configuration
284
540
  # -------------------------------------------
@@ -670,8 +926,18 @@ module Aidp
670
926
  prompt.say("\n📋 Non-functional requirements & preferred libraries")
671
927
  prompt.say("-" * 40)
672
928
 
673
- return delete_path([:nfrs]) unless prompt.yes?("Configure NFRs?", default: true)
929
+ # Check existing configuration for previous choice
930
+ existing_configure = @config.dig(:nfrs, :configure)
931
+ default_configure = existing_configure.nil? || existing_configure
932
+
933
+ configure = prompt.yes?("Configure NFRs?", default: default_configure)
674
934
 
935
+ unless configure
936
+ Aidp.log_debug("setup_wizard.nfrs", "opt_out")
937
+ return set([:nfrs, :configure], false)
938
+ end
939
+
940
+ set([:nfrs, :configure], true)
675
941
  categories = %i[performance security reliability accessibility internationalization]
676
942
  categories.each do |category|
677
943
  existing = get([:nfrs, category])
@@ -802,6 +1068,7 @@ module Aidp
802
1068
 
803
1069
  configure_watch_safety
804
1070
  configure_watch_labels
1071
+ configure_watch_label_creation
805
1072
  end
806
1073
 
807
1074
  def configure_watch_safety
@@ -835,7 +1102,7 @@ module Aidp
835
1102
 
836
1103
  def configure_watch_labels
837
1104
  prompt.say("\n🏷️ Watch mode label configuration")
838
- prompt.say(" Configure GitHub issue labels that trigger watch mode actions")
1105
+ prompt.say(" Configure GitHub issue and PR labels that trigger watch mode actions")
839
1106
  existing = get([:watch, :labels]) || {}
840
1107
 
841
1108
  plan_trigger = ask_with_default(
@@ -858,14 +1125,239 @@ module Aidp
858
1125
  existing[:build_trigger] || "aidp-build"
859
1126
  )
860
1127
 
1128
+ review_trigger = ask_with_default(
1129
+ "Label to trigger code review",
1130
+ existing[:review_trigger] || "aidp-review"
1131
+ )
1132
+
1133
+ ci_fix_trigger = ask_with_default(
1134
+ "Label to trigger CI remediation",
1135
+ existing[:ci_fix_trigger] || "aidp-fix-ci"
1136
+ )
1137
+
1138
+ change_request_trigger = ask_with_default(
1139
+ "Label to trigger PR change implementation",
1140
+ existing[:change_request_trigger] || "aidp-request-changes"
1141
+ )
1142
+
861
1143
  set([:watch, :labels], {
862
1144
  plan_trigger: plan_trigger,
863
1145
  needs_input: needs_input,
864
1146
  ready_to_build: ready_to_build,
865
- build_trigger: build_trigger
1147
+ build_trigger: build_trigger,
1148
+ review_trigger: review_trigger,
1149
+ ci_fix_trigger: ci_fix_trigger,
1150
+ change_request_trigger: change_request_trigger
866
1151
  })
867
1152
  end
868
1153
 
1154
+ def configure_watch_label_creation
1155
+ prompt.say("\n🏷️ GitHub Label Auto-Creation")
1156
+ prompt.say(" Automatically create GitHub labels for watch mode if they don't exist")
1157
+
1158
+ Aidp.log_debug("setup_wizard.label_creation", "start")
1159
+
1160
+ # Ask if user wants to auto-create labels
1161
+ unless prompt.yes?("Auto-create GitHub labels if missing?", default: true)
1162
+ Aidp.log_debug("setup_wizard.label_creation", "user_declined")
1163
+ return
1164
+ end
1165
+
1166
+ # Check if gh CLI is available
1167
+ unless gh_cli_available?
1168
+ prompt.warn("⚠️ GitHub CLI (gh) not found. Install it to enable label auto-creation.")
1169
+ prompt.say(" Visit: https://cli.github.com/")
1170
+ Aidp.log_debug("setup_wizard.label_creation", "gh_not_available")
1171
+ return
1172
+ end
1173
+
1174
+ # Extract repository info
1175
+ repo_info = extract_repo_info
1176
+ unless repo_info
1177
+ prompt.warn("⚠️ Could not determine GitHub repository from git remote.")
1178
+ prompt.say(" Ensure you're in a git repository with a GitHub remote configured.")
1179
+ Aidp.log_debug("setup_wizard.label_creation", "repo_info_failed")
1180
+ return
1181
+ end
1182
+
1183
+ owner, repo = repo_info
1184
+ Aidp.log_debug("setup_wizard.label_creation", "repo_detected", owner: owner, repo: repo)
1185
+ prompt.say("📦 Repository: #{owner}/#{repo}")
1186
+
1187
+ # Fetch existing labels
1188
+ existing_labels = fetch_existing_labels(owner, repo)
1189
+ unless existing_labels
1190
+ prompt.warn("⚠️ Failed to fetch existing labels. Check your GitHub authentication.")
1191
+ Aidp.log_debug("setup_wizard.label_creation", "fetch_labels_failed")
1192
+ return
1193
+ end
1194
+
1195
+ Aidp.log_debug("setup_wizard.label_creation", "existing_labels_fetched", count: existing_labels.size)
1196
+
1197
+ # Get configured label names
1198
+ labels_config = get([:watch, :labels]) || {}
1199
+ required_labels = collect_required_labels(labels_config)
1200
+
1201
+ # Determine which labels need to be created
1202
+ labels_to_create = required_labels.reject { |label| existing_labels.include?(label[:name]) }
1203
+
1204
+ if labels_to_create.empty?
1205
+ prompt.ok("✅ All required labels already exist!")
1206
+ Aidp.log_debug("setup_wizard.label_creation", "all_labels_exist")
1207
+ return
1208
+ end
1209
+
1210
+ # Show labels to be created
1211
+ prompt.say("\n📝 Labels to create:")
1212
+ labels_to_create.each do |label|
1213
+ prompt.say(" • #{label[:name]} (#{label[:color]})")
1214
+ end
1215
+
1216
+ # Confirm creation
1217
+ unless prompt.yes?("Create these labels?", default: true)
1218
+ Aidp.log_debug("setup_wizard.label_creation", "creation_declined")
1219
+ return
1220
+ end
1221
+
1222
+ # Create labels
1223
+ create_labels(owner, repo, labels_to_create)
1224
+ end
1225
+
1226
+ # Check if gh CLI is available
1227
+ def gh_cli_available?
1228
+ require "open3"
1229
+ _stdout, _stderr, status = Open3.capture3("gh", "--version")
1230
+ Aidp.log_debug("setup_wizard.gh_check", "version_check", success: status.success?)
1231
+ status.success?
1232
+ rescue Errno::ENOENT
1233
+ Aidp.log_debug("setup_wizard.gh_check", "not_found")
1234
+ false
1235
+ end
1236
+
1237
+ # Extract repository owner and name from git remote
1238
+ def extract_repo_info
1239
+ require "open3"
1240
+ stdout, stderr, status = Open3.capture3("git", "remote", "get-url", "origin")
1241
+
1242
+ unless status.success?
1243
+ Aidp.log_debug("setup_wizard.repo_extraction", "git_remote_failed", error: stderr)
1244
+ return nil
1245
+ end
1246
+
1247
+ remote_url = stdout.strip
1248
+ Aidp.log_debug("setup_wizard.repo_extraction", "remote_url_found", url: remote_url)
1249
+
1250
+ # Parse GitHub URL (supports both HTTPS and SSH formats)
1251
+ # HTTPS: https://github.com/owner/repo.git
1252
+ # SSH: git@github.com:owner/repo.git
1253
+ if remote_url =~ %r{github\.com[:/]([^/]+)/(.+?)(?:\.git)?$}
1254
+ owner = Regexp.last_match(1)
1255
+ repo = Regexp.last_match(2)
1256
+ Aidp.log_debug("setup_wizard.repo_extraction", "parsed", owner: owner, repo: repo)
1257
+ [owner, repo]
1258
+ else
1259
+ Aidp.log_debug("setup_wizard.repo_extraction", "parse_failed", url: remote_url)
1260
+ nil
1261
+ end
1262
+ rescue => e
1263
+ Aidp.log_error("setup_wizard.repo_extraction", "exception", error: e.message)
1264
+ nil
1265
+ end
1266
+
1267
+ # Fetch existing labels from GitHub
1268
+ def fetch_existing_labels(owner, repo)
1269
+ require "open3"
1270
+ stdout, stderr, status = Open3.capture3("gh", "label", "list", "-R", "#{owner}/#{repo}", "--json", "name", "--jq", ".[].name")
1271
+
1272
+ unless status.success?
1273
+ Aidp.log_error("setup_wizard.fetch_labels", "gh_failed", error: stderr)
1274
+ return nil
1275
+ end
1276
+
1277
+ labels = stdout.strip.split("\n").map(&:strip).reject(&:empty?)
1278
+ Aidp.log_debug("setup_wizard.fetch_labels", "fetched", count: labels.size)
1279
+ labels
1280
+ rescue => e
1281
+ Aidp.log_error("setup_wizard.fetch_labels", "exception", error: e.message)
1282
+ nil
1283
+ end
1284
+
1285
+ # Collect required labels with their default colors
1286
+ def collect_required_labels(labels_config)
1287
+ default_colors = {
1288
+ plan_trigger: "0E8A16", # Green
1289
+ needs_input: "D93F0B", # Red
1290
+ ready_to_build: "0075CA", # Blue
1291
+ build_trigger: "5319E7", # Purple
1292
+ review_trigger: "FBCA04", # Yellow
1293
+ ci_fix_trigger: "D93F0B", # Red
1294
+ change_request_trigger: "F9D0C4", # Light pink
1295
+ in_progress: "1D76DB" # Dark blue (internal coordination)
1296
+ }
1297
+
1298
+ required = []
1299
+ labels_config.each do |key, name|
1300
+ next if name.nil? || name.to_s.strip.empty?
1301
+
1302
+ color = default_colors[key] || "EDEDED" # Gray fallback
1303
+ required << {name: name, color: color, key: key}
1304
+ end
1305
+
1306
+ # Always include the internal in-progress label for coordination
1307
+ required << {
1308
+ name: "aidp-in-progress",
1309
+ color: default_colors[:in_progress],
1310
+ key: :in_progress,
1311
+ internal: true
1312
+ }
1313
+
1314
+ Aidp.log_debug("setup_wizard.collect_labels", "collected", count: required.size)
1315
+ required
1316
+ end
1317
+
1318
+ # Create labels on GitHub
1319
+ def create_labels(owner, repo, labels)
1320
+ require "open3"
1321
+
1322
+ created = 0
1323
+ failed = 0
1324
+
1325
+ labels.each do |label|
1326
+ Aidp.log_debug("setup_wizard.create_label", "creating", name: label[:name], color: label[:color])
1327
+
1328
+ _stdout, stderr, status = Open3.capture3(
1329
+ "gh", "label", "create", label[:name],
1330
+ "--color", label[:color],
1331
+ "-R", "#{owner}/#{repo}"
1332
+ )
1333
+
1334
+ if status.success?
1335
+ prompt.ok(" ✅ Created: #{label[:name]}")
1336
+ Aidp.log_info("setup_wizard.create_label", "success", name: label[:name])
1337
+ created += 1
1338
+ else
1339
+ prompt.warn(" ⚠️ Failed to create: #{label[:name]} - #{stderr.strip}")
1340
+ Aidp.log_error("setup_wizard.create_label", "failed", name: label[:name], error: stderr.strip)
1341
+ failed += 1
1342
+ end
1343
+ rescue => e
1344
+ prompt.warn(" ⚠️ Error creating #{label[:name]}: #{e.message}")
1345
+ Aidp.log_error("setup_wizard.create_label", "exception", name: label[:name], error: e.message)
1346
+ failed += 1
1347
+ end
1348
+
1349
+ # Summary
1350
+ prompt.say("")
1351
+ if created > 0
1352
+ prompt.ok("✅ Successfully created #{created} label#{"s" unless created == 1}")
1353
+ end
1354
+ if failed > 0
1355
+ prompt.warn("⚠️ Failed to create #{failed} label#{"s" unless failed == 1}")
1356
+ end
1357
+
1358
+ Aidp.log_info("setup_wizard.create_labels", "complete", created: created, failed: failed)
1359
+ end
1360
+
869
1361
  # -------------------------------------------
870
1362
  # Preview & persistence
871
1363
  # -------------------------------------------
@@ -953,9 +1445,9 @@ module Aidp
953
1445
  def show_next_steps
954
1446
  prompt.say("\n🎉 Setup complete!")
955
1447
  prompt.say("\nNext steps:")
956
- prompt.say(" 1. Export provider API keys as environment variables.")
957
- prompt.say(" 2. Run 'aidp init' to analyze the project.")
958
- prompt.say(" 3. Run 'aidp execute' to start a work loop.")
1448
+ prompt.say(" 1. Configure provider tools (set required API keys or connections).")
1449
+ prompt.say(" 2. Run 'aidp' to start a work loop.")
1450
+ prompt.say(" 3. Run 'aidp watch <owner/repo>' to enable watch mode automation.")
959
1451
  prompt.say("")
960
1452
  end
961
1453
 
@@ -1225,6 +1717,9 @@ module Aidp
1225
1717
  # Enhance messaging with display name when available
1226
1718
  display_name = discover_available_providers.invert.fetch(provider_name, provider_name)
1227
1719
  prompt.say(" • #{action_word.capitalize} provider '#{display_name}' (#{provider_name}) with billing type '#{provider_type}' and model family '#{model_family}'")
1720
+
1721
+ # Trigger background model discovery for newly added/updated provider
1722
+ trigger_background_discovery(provider_name) unless @dry_run
1228
1723
  end
1229
1724
 
1230
1725
  def edit_or_remove_provider(provider_name, primary_provider, fallbacks)
@@ -1335,6 +1830,44 @@ module Aidp
1335
1830
  end
1336
1831
  end
1337
1832
 
1833
+ def normalize_existing_thinking_tiers!
1834
+ tiers_cfg = @config.dig(:thinking, :tiers)
1835
+ return unless tiers_cfg.is_a?(Hash)
1836
+
1837
+ LEGACY_TIER_ALIASES.each do |legacy, canonical|
1838
+ next unless tiers_cfg.key?(legacy)
1839
+
1840
+ legacy_cfg = tiers_cfg.delete(legacy) || {}
1841
+ canonical_cfg = tiers_cfg[canonical] || {}
1842
+ merged_models = merge_tier_models(canonical_cfg[:models], legacy_cfg[:models])
1843
+ tiers_cfg[canonical] = canonical_cfg.merge(models: merged_models)
1844
+ @warnings << "Normalized thinking tier '#{legacy}' to '#{canonical}'"
1845
+ end
1846
+
1847
+ valid = valid_thinking_tiers
1848
+ tiers_cfg.keys.each do |tier|
1849
+ next if valid.include?(tier.to_s)
1850
+
1851
+ tiers_cfg.delete(tier)
1852
+ @warnings << "Removed unsupported thinking tier '#{tier}' from configuration"
1853
+ end
1854
+ end
1855
+
1856
+ def merge_tier_models(existing_models, new_models)
1857
+ combined = []
1858
+ (Array(existing_models) + Array(new_models)).each do |entry|
1859
+ next unless entry.is_a?(Hash)
1860
+ provider = entry[:provider]
1861
+ model = entry[:model]
1862
+ next unless provider && model
1863
+
1864
+ unless combined.any? { |m| m[:provider] == provider && m[:model] == model }
1865
+ combined << entry
1866
+ end
1867
+ end
1868
+ combined
1869
+ end
1870
+
1338
1871
  def load_existing_config
1339
1872
  return {} unless File.exist?(config_path)
1340
1873
  YAML.safe_load_file(config_path, permitted_classes: [Time]) || {}
@@ -1468,6 +2001,12 @@ module Aidp
1468
2001
  File.exist?(File.join(project_dir, relative_path))
1469
2002
  end
1470
2003
 
2004
+ def valid_thinking_tiers
2005
+ Aidp::Harness::CapabilityRegistry::VALID_TIERS
2006
+ rescue NameError
2007
+ %w[mini standard thinking pro max]
2008
+ end
2009
+
1471
2010
  def configure_devcontainer
1472
2011
  prompt.say("\n🐳 Devcontainer Configuration")
1473
2012
  Aidp.log_debug(DEVCONTAINER_COMPONENT, "configure.start")
@@ -1482,10 +2021,18 @@ module Aidp
1482
2021
  path: parser.detect)
1483
2022
  end
1484
2023
 
2024
+ # Check existing configuration for previous choice
2025
+ existing_manage = @config.dig(:devcontainer, :manage)
2026
+ default_manage = if existing_manage.nil?
2027
+ existing_devcontainer ? true : false
2028
+ else
2029
+ existing_manage
2030
+ end
2031
+
1485
2032
  # Ask if user wants AIDP to manage devcontainer
1486
2033
  manage = prompt.yes?(
1487
2034
  "Would you like AIDP to manage your devcontainer configuration?",
1488
- default: (@config.dig(:devcontainer, :manage) || existing_devcontainer) ? true : false
2035
+ default: default_manage
1489
2036
  )
1490
2037
 
1491
2038
  unless manage
data/lib/aidp/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aidp
4
- VERSION = "0.26.0"
4
+ VERSION = "0.28.0"
5
5
  end