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.
- checksums.yaml +4 -4
- data/README.md +89 -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 +661 -0
- data/lib/aidp/cli/providers_command.rb +223 -0
- data/lib/aidp/cli.rb +45 -464
- data/lib/aidp/config.rb +54 -0
- data/lib/aidp/daemon/runner.rb +2 -2
- data/lib/aidp/debug_mixin.rb +25 -10
- data/lib/aidp/execute/agent_signal_parser.rb +22 -0
- 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/repl_macros.rb +2 -2
- data/lib/aidp/execute/steps.rb +94 -1
- data/lib/aidp/execute/work_loop_runner.rb +238 -19
- data/lib/aidp/execute/workflow_selector.rb +4 -27
- data/lib/aidp/firewall/provider_requirements_collector.rb +262 -0
- data/lib/aidp/harness/ai_decision_engine.rb +35 -2
- data/lib/aidp/harness/config_manager.rb +5 -10
- data/lib/aidp/harness/config_schema.rb +8 -0
- data/lib/aidp/harness/configuration.rb +40 -2
- data/lib/aidp/harness/enhanced_runner.rb +25 -19
- data/lib/aidp/harness/error_handler.rb +23 -73
- 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/provider_factory.rb +11 -2
- data/lib/aidp/harness/runner.rb +5 -0
- data/lib/aidp/harness/state_manager.rb +0 -7
- data/lib/aidp/harness/thinking_depth_manager.rb +202 -7
- data/lib/aidp/harness/ui/enhanced_tui.rb +8 -18
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +0 -18
- data/lib/aidp/harness/ui/progress_display.rb +6 -2
- data/lib/aidp/harness/user_interface.rb +0 -58
- data/lib/aidp/init/runner.rb +7 -2
- data/lib/aidp/message_display.rb +0 -46
- data/lib/aidp/planning/analyzers/feedback_analyzer.rb +365 -0
- data/lib/aidp/planning/builders/agile_plan_builder.rb +387 -0
- data/lib/aidp/planning/builders/project_plan_builder.rb +193 -0
- data/lib/aidp/planning/generators/gantt_generator.rb +190 -0
- data/lib/aidp/planning/generators/iteration_plan_generator.rb +392 -0
- data/lib/aidp/planning/generators/legacy_research_planner.rb +473 -0
- data/lib/aidp/planning/generators/marketing_report_generator.rb +348 -0
- data/lib/aidp/planning/generators/mvp_scope_generator.rb +310 -0
- data/lib/aidp/planning/generators/user_test_plan_generator.rb +373 -0
- data/lib/aidp/planning/generators/wbs_generator.rb +259 -0
- data/lib/aidp/planning/mappers/persona_mapper.rb +163 -0
- data/lib/aidp/planning/parsers/document_parser.rb +141 -0
- data/lib/aidp/planning/parsers/feedback_data_parser.rb +252 -0
- data/lib/aidp/provider_manager.rb +8 -32
- data/lib/aidp/providers/adapter.rb +2 -4
- data/lib/aidp/providers/aider.rb +264 -0
- data/lib/aidp/providers/anthropic.rb +206 -121
- data/lib/aidp/providers/base.rb +123 -3
- data/lib/aidp/providers/capability_registry.rb +0 -1
- data/lib/aidp/providers/codex.rb +75 -70
- data/lib/aidp/providers/cursor.rb +87 -59
- data/lib/aidp/providers/gemini.rb +57 -60
- data/lib/aidp/providers/github_copilot.rb +19 -66
- data/lib/aidp/providers/kilocode.rb +35 -80
- data/lib/aidp/providers/opencode.rb +35 -80
- data/lib/aidp/setup/wizard.rb +555 -8
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/build_processor.rb +211 -30
- data/lib/aidp/watch/change_request_processor.rb +128 -14
- data/lib/aidp/watch/ci_fix_processor.rb +103 -37
- data/lib/aidp/watch/ci_log_extractor.rb +258 -0
- data/lib/aidp/watch/github_state_extractor.rb +177 -0
- data/lib/aidp/watch/implementation_verifier.rb +284 -0
- data/lib/aidp/watch/plan_generator.rb +95 -52
- data/lib/aidp/watch/plan_processor.rb +7 -6
- data/lib/aidp/watch/repository_client.rb +245 -17
- data/lib/aidp/watch/review_processor.rb +100 -19
- data/lib/aidp/watch/reviewers/base_reviewer.rb +1 -1
- data/lib/aidp/watch/runner.rb +181 -29
- data/lib/aidp/watch/state_store.rb +22 -1
- data/lib/aidp/workflows/definitions.rb +147 -0
- data/lib/aidp/workflows/guided_agent.rb +3 -3
- data/lib/aidp/workstream_cleanup.rb +245 -0
- data/lib/aidp/worktree.rb +19 -0
- data/templates/aidp-development.yml.example +2 -2
- data/templates/aidp-production.yml.example +3 -3
- data/templates/aidp.yml.example +57 -0
- data/templates/implementation/generate_tdd_specs.md +213 -0
- data/templates/implementation/iterative_implementation.md +122 -0
- data/templates/planning/agile/analyze_feedback.md +183 -0
- data/templates/planning/agile/generate_iteration_plan.md +179 -0
- data/templates/planning/agile/generate_legacy_research_plan.md +171 -0
- data/templates/planning/agile/generate_marketing_report.md +162 -0
- data/templates/planning/agile/generate_mvp_scope.md +127 -0
- data/templates/planning/agile/generate_user_test_plan.md +143 -0
- data/templates/planning/agile/ingest_feedback.md +174 -0
- data/templates/planning/assemble_project_plan.md +113 -0
- data/templates/planning/assign_personas.md +108 -0
- data/templates/planning/create_tasks.md +52 -6
- data/templates/planning/generate_gantt.md +86 -0
- data/templates/planning/generate_wbs.md +85 -0
- data/templates/planning/initialize_planning_mode.md +70 -0
- data/templates/skills/README.md +2 -2
- data/templates/skills/marketing_strategist/SKILL.md +279 -0
- data/templates/skills/product_manager/SKILL.md +177 -0
- data/templates/skills/ruby_aidp_planning/SKILL.md +497 -0
- data/templates/skills/ruby_rspec_tdd/SKILL.md +514 -0
- data/templates/skills/ux_researcher/SKILL.md +222 -0
- metadata +47 -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,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
|
-
|
|
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.
|
|
957
|
-
prompt.say(" 2. Run 'aidp
|
|
958
|
-
prompt.say(" 3. Run 'aidp
|
|
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:
|
|
2035
|
+
default: default_manage
|
|
1489
2036
|
)
|
|
1490
2037
|
|
|
1491
2038
|
unless manage
|
data/lib/aidp/version.rb
CHANGED