aidp 0.25.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +45 -6
- data/lib/aidp/analyze/error_handler.rb +11 -0
- data/lib/aidp/cli/checkpoint_command.rb +198 -0
- data/lib/aidp/cli/config_command.rb +71 -0
- data/lib/aidp/cli/enhanced_input.rb +2 -0
- data/lib/aidp/cli/first_run_wizard.rb +8 -7
- data/lib/aidp/cli/harness_command.rb +102 -0
- data/lib/aidp/cli/jobs_command.rb +3 -3
- data/lib/aidp/cli/mcp_dashboard.rb +4 -3
- data/lib/aidp/cli/models_command.rb +662 -0
- data/lib/aidp/cli/providers_command.rb +223 -0
- data/lib/aidp/cli.rb +35 -456
- data/lib/aidp/daemon/runner.rb +2 -2
- data/lib/aidp/debug_mixin.rb +2 -9
- data/lib/aidp/execute/async_work_loop_runner.rb +2 -1
- data/lib/aidp/execute/checkpoint_display.rb +38 -37
- data/lib/aidp/execute/interactive_repl.rb +2 -1
- data/lib/aidp/execute/prompt_manager.rb +4 -4
- data/lib/aidp/execute/work_loop_runner.rb +253 -56
- data/lib/aidp/execute/workflow_selector.rb +2 -2
- data/lib/aidp/harness/config_loader.rb +20 -11
- data/lib/aidp/harness/config_manager.rb +5 -5
- data/lib/aidp/harness/config_schema.rb +30 -8
- data/lib/aidp/harness/configuration.rb +105 -4
- data/lib/aidp/harness/enhanced_runner.rb +24 -15
- data/lib/aidp/harness/error_handler.rb +26 -5
- data/lib/aidp/harness/filter_strategy.rb +45 -0
- data/lib/aidp/harness/generic_filter_strategy.rb +63 -0
- data/lib/aidp/harness/model_cache.rb +269 -0
- data/lib/aidp/harness/model_discovery_service.rb +259 -0
- data/lib/aidp/harness/model_registry.rb +201 -0
- data/lib/aidp/harness/output_filter.rb +136 -0
- data/lib/aidp/harness/provider_manager.rb +18 -3
- data/lib/aidp/harness/rspec_filter_strategy.rb +82 -0
- data/lib/aidp/harness/runner.rb +5 -0
- data/lib/aidp/harness/test_runner.rb +165 -27
- data/lib/aidp/harness/thinking_depth_manager.rb +223 -7
- data/lib/aidp/harness/ui/enhanced_tui.rb +4 -1
- data/lib/aidp/logger.rb +35 -5
- data/lib/aidp/providers/adapter.rb +2 -4
- data/lib/aidp/providers/anthropic.rb +141 -128
- data/lib/aidp/providers/base.rb +98 -2
- data/lib/aidp/providers/capability_registry.rb +0 -1
- data/lib/aidp/providers/codex.rb +49 -67
- data/lib/aidp/providers/cursor.rb +71 -59
- data/lib/aidp/providers/gemini.rb +44 -60
- data/lib/aidp/providers/github_copilot.rb +2 -66
- data/lib/aidp/providers/kilocode.rb +24 -80
- data/lib/aidp/providers/opencode.rb +24 -80
- data/lib/aidp/safe_directory.rb +10 -3
- data/lib/aidp/setup/wizard.rb +345 -8
- data/lib/aidp/storage/csv_storage.rb +9 -3
- data/lib/aidp/storage/file_manager.rb +8 -2
- data/lib/aidp/storage/json_storage.rb +9 -3
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/build_processor.rb +40 -1
- data/lib/aidp/watch/change_request_processor.rb +659 -0
- data/lib/aidp/watch/plan_generator.rb +93 -14
- data/lib/aidp/watch/plan_processor.rb +71 -8
- data/lib/aidp/watch/repository_client.rb +85 -20
- data/lib/aidp/watch/review_processor.rb +3 -3
- data/lib/aidp/watch/runner.rb +37 -0
- data/lib/aidp/watch/state_store.rb +46 -1
- data/lib/aidp/workflows/guided_agent.rb +3 -3
- data/lib/aidp/workstream_executor.rb +5 -2
- data/lib/aidp.rb +4 -0
- data/templates/aidp-development.yml.example +2 -2
- data/templates/aidp-production.yml.example +3 -3
- data/templates/aidp.yml.example +53 -0
- metadata +14 -1
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
|
|
@@ -205,12 +205,12 @@ module Aidp
|
|
|
205
205
|
rescue
|
|
206
206
|
File.join(Dir.tmpdir, "aidp_storage")
|
|
207
207
|
end
|
|
208
|
-
|
|
208
|
+
warn_storage("[AIDP Storage] Cannot create base directory #{@base_dir}: #{e.class}: #{e.message}. Using fallback #{fallback}")
|
|
209
209
|
@base_dir = fallback
|
|
210
210
|
begin
|
|
211
211
|
FileUtils.mkdir_p(@base_dir) unless Dir.exist?(@base_dir)
|
|
212
212
|
rescue SystemCallError => e2
|
|
213
|
-
|
|
213
|
+
warn_storage("[AIDP Storage] Fallback directory creation also failed: #{e2.class}: #{e2.message}. Continuing without persistent CSV storage.")
|
|
214
214
|
end
|
|
215
215
|
end
|
|
216
216
|
end
|
|
@@ -225,11 +225,17 @@ module Aidp
|
|
|
225
225
|
rescue
|
|
226
226
|
File.join(Dir.tmpdir, "aidp_storage")
|
|
227
227
|
end
|
|
228
|
-
|
|
228
|
+
warn_storage("[AIDP Storage] Root base_dir detected - using fallback #{fallback} instead of '#{str}'")
|
|
229
229
|
return fallback
|
|
230
230
|
end
|
|
231
231
|
str
|
|
232
232
|
end
|
|
233
|
+
|
|
234
|
+
# Suppress storage warnings in test/CI environments
|
|
235
|
+
def warn_storage(message)
|
|
236
|
+
return if ENV["RSPEC_RUNNING"] || ENV["CI"] || ENV["RAILS_ENV"] == "test" || ENV["RACK_ENV"] == "test"
|
|
237
|
+
Kernel.warn(message)
|
|
238
|
+
end
|
|
233
239
|
end
|
|
234
240
|
end
|
|
235
241
|
end
|
|
@@ -16,7 +16,7 @@ module Aidp
|
|
|
16
16
|
csv_dir = @csv_storage.instance_variable_get(:@base_dir)
|
|
17
17
|
if json_dir != @base_dir || csv_dir != @base_dir
|
|
18
18
|
@base_dir = json_dir # Prefer JSON storage directory
|
|
19
|
-
|
|
19
|
+
warn_storage("[AIDP Storage] Base directory normalized to #{@base_dir} after fallback.")
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
|
|
@@ -229,11 +229,17 @@ module Aidp
|
|
|
229
229
|
rescue
|
|
230
230
|
File.join(Dir.tmpdir, "aidp_storage")
|
|
231
231
|
end
|
|
232
|
-
|
|
232
|
+
warn_storage("[AIDP Storage] Root base_dir detected - using fallback #{fallback} instead of '#{str}'")
|
|
233
233
|
return fallback
|
|
234
234
|
end
|
|
235
235
|
str
|
|
236
236
|
end
|
|
237
|
+
|
|
238
|
+
# Suppress storage warnings in test/CI environments
|
|
239
|
+
def warn_storage(message)
|
|
240
|
+
return if ENV["RSPEC_RUNNING"] || ENV["CI"] || ENV["RAILS_ENV"] == "test" || ENV["RACK_ENV"] == "test"
|
|
241
|
+
Kernel.warn(message)
|
|
242
|
+
end
|
|
237
243
|
end
|
|
238
244
|
end
|
|
239
245
|
end
|
|
@@ -171,12 +171,12 @@ module Aidp
|
|
|
171
171
|
rescue
|
|
172
172
|
File.join(Dir.tmpdir, "aidp_storage")
|
|
173
173
|
end
|
|
174
|
-
|
|
174
|
+
warn_storage("[AIDP Storage] Cannot create base directory #{@base_dir}: #{e.class}: #{e.message}. Using fallback #{fallback}")
|
|
175
175
|
@base_dir = fallback
|
|
176
176
|
begin
|
|
177
177
|
FileUtils.mkdir_p(@base_dir) unless Dir.exist?(@base_dir)
|
|
178
178
|
rescue SystemCallError => e2
|
|
179
|
-
|
|
179
|
+
warn_storage("[AIDP Storage] Fallback directory creation also failed: #{e2.class}: #{e2.message}. Continuing without persistent JSON storage.")
|
|
180
180
|
end
|
|
181
181
|
end
|
|
182
182
|
end
|
|
@@ -192,11 +192,17 @@ module Aidp
|
|
|
192
192
|
rescue
|
|
193
193
|
File.join(Dir.tmpdir, "aidp_storage")
|
|
194
194
|
end
|
|
195
|
-
|
|
195
|
+
warn_storage("[AIDP Storage] Root base_dir detected - using fallback #{fallback} instead of '#{str}'")
|
|
196
196
|
return fallback
|
|
197
197
|
end
|
|
198
198
|
str
|
|
199
199
|
end
|
|
200
|
+
|
|
201
|
+
# Suppress storage warnings in test/CI environments
|
|
202
|
+
def warn_storage(message)
|
|
203
|
+
return if ENV["RSPEC_RUNNING"] || ENV["CI"] || ENV["RAILS_ENV"] == "test" || ENV["RACK_ENV"] == "test"
|
|
204
|
+
Kernel.warn(message)
|
|
205
|
+
end
|
|
200
206
|
end
|
|
201
207
|
end
|
|
202
208
|
end
|
data/lib/aidp/version.rb
CHANGED
|
@@ -228,12 +228,51 @@ module Aidp
|
|
|
228
228
|
relevant.map do |comment|
|
|
229
229
|
author = comment["author"] || "unknown"
|
|
230
230
|
created = comment["createdAt"] ? Time.parse(comment["createdAt"]).utc.iso8601 : "unknown"
|
|
231
|
-
|
|
231
|
+
body = strip_archived_plans(comment["body"])
|
|
232
|
+
"### #{author} (#{created})\n#{body}"
|
|
232
233
|
end.join("\n\n")
|
|
233
234
|
rescue
|
|
234
235
|
"_Unable to parse comment thread._"
|
|
235
236
|
end
|
|
236
237
|
|
|
238
|
+
def strip_archived_plans(content)
|
|
239
|
+
return content unless content
|
|
240
|
+
|
|
241
|
+
# Remove all archived plan sections (wrapped in HTML comments)
|
|
242
|
+
result = content.dup
|
|
243
|
+
|
|
244
|
+
# Remove archived plan blocks
|
|
245
|
+
# Safe string-based approach to avoid ReDoS vulnerabilities
|
|
246
|
+
start_prefix = "<!-- ARCHIVED_PLAN_START"
|
|
247
|
+
end_marker = "<!-- ARCHIVED_PLAN_END -->"
|
|
248
|
+
|
|
249
|
+
loop do
|
|
250
|
+
# Find the start of an archived plan block (may have attributes after ARCHIVED_PLAN_START)
|
|
251
|
+
start_idx = result.index(start_prefix)
|
|
252
|
+
break unless start_idx
|
|
253
|
+
|
|
254
|
+
# Find the closing --> of the start marker
|
|
255
|
+
start_marker_end = result.index("-->", start_idx)
|
|
256
|
+
break unless start_marker_end
|
|
257
|
+
|
|
258
|
+
# Find the corresponding end marker
|
|
259
|
+
end_idx = result.index(end_marker, start_marker_end)
|
|
260
|
+
break unless end_idx
|
|
261
|
+
|
|
262
|
+
# Remove the entire block including markers
|
|
263
|
+
result = result[0...start_idx] + result[(end_idx + end_marker.length)..]
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Remove HTML-commented sections from active plan
|
|
267
|
+
# Keep the content between START and END markers, but strip the markers themselves
|
|
268
|
+
# This preserves the current plan while removing archived content
|
|
269
|
+
result = result.gsub(/<!-- (PLAN_SUMMARY_START|PLAN_TASKS_START|CLARIFYING_QUESTIONS_START) -->/, "")
|
|
270
|
+
result = result.gsub(/<!-- (PLAN_SUMMARY_END|PLAN_TASKS_END|CLARIFYING_QUESTIONS_END) -->/, "")
|
|
271
|
+
|
|
272
|
+
# Clean up any extra blank lines
|
|
273
|
+
result.gsub(/\n{3,}/, "\n\n").strip
|
|
274
|
+
end
|
|
275
|
+
|
|
237
276
|
def write_prompt(content, working_dir: @project_dir)
|
|
238
277
|
prompt_manager = Aidp::Execute::PromptManager.new(working_dir)
|
|
239
278
|
prompt_manager.write(content, step_name: IMPLEMENTATION_STEP)
|