aidp 0.32.0 → 0.34.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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -0
  3. data/lib/aidp/analyze/feature_analyzer.rb +322 -320
  4. data/lib/aidp/analyze/tree_sitter_scan.rb +3 -0
  5. data/lib/aidp/auto_update/coordinator.rb +97 -7
  6. data/lib/aidp/auto_update.rb +0 -12
  7. data/lib/aidp/cli/devcontainer_commands.rb +0 -5
  8. data/lib/aidp/cli/eval_command.rb +399 -0
  9. data/lib/aidp/cli/harness_command.rb +1 -1
  10. data/lib/aidp/cli/security_command.rb +416 -0
  11. data/lib/aidp/cli/tools_command.rb +6 -4
  12. data/lib/aidp/cli.rb +172 -4
  13. data/lib/aidp/comment_consolidator.rb +78 -0
  14. data/lib/aidp/concurrency/exec.rb +3 -0
  15. data/lib/aidp/concurrency.rb +0 -3
  16. data/lib/aidp/config.rb +113 -1
  17. data/lib/aidp/config_paths.rb +91 -0
  18. data/lib/aidp/daemon/runner.rb +8 -4
  19. data/lib/aidp/errors.rb +134 -0
  20. data/lib/aidp/evaluations/context_capture.rb +205 -0
  21. data/lib/aidp/evaluations/evaluation_record.rb +114 -0
  22. data/lib/aidp/evaluations/evaluation_storage.rb +250 -0
  23. data/lib/aidp/evaluations.rb +23 -0
  24. data/lib/aidp/execute/async_work_loop_runner.rb +4 -1
  25. data/lib/aidp/execute/interactive_repl.rb +6 -2
  26. data/lib/aidp/execute/prompt_evaluator.rb +359 -0
  27. data/lib/aidp/execute/repl_macros.rb +100 -1
  28. data/lib/aidp/execute/work_loop_runner.rb +719 -58
  29. data/lib/aidp/execute/work_loop_state.rb +4 -1
  30. data/lib/aidp/execute/workflow_selector.rb +3 -0
  31. data/lib/aidp/harness/ai_decision_engine.rb +79 -0
  32. data/lib/aidp/harness/ai_filter_factory.rb +285 -0
  33. data/lib/aidp/harness/capability_registry.rb +2 -0
  34. data/lib/aidp/harness/condition_detector.rb +3 -0
  35. data/lib/aidp/harness/config_loader.rb +3 -0
  36. data/lib/aidp/harness/config_schema.rb +97 -1
  37. data/lib/aidp/harness/config_validator.rb +1 -1
  38. data/lib/aidp/harness/configuration.rb +61 -5
  39. data/lib/aidp/harness/enhanced_runner.rb +14 -11
  40. data/lib/aidp/harness/error_handler.rb +3 -0
  41. data/lib/aidp/harness/filter_definition.rb +212 -0
  42. data/lib/aidp/harness/generated_filter_strategy.rb +197 -0
  43. data/lib/aidp/harness/output_filter.rb +50 -25
  44. data/lib/aidp/harness/output_filter_config.rb +129 -0
  45. data/lib/aidp/harness/provider_factory.rb +3 -0
  46. data/lib/aidp/harness/provider_manager.rb +96 -2
  47. data/lib/aidp/harness/runner.rb +5 -12
  48. data/lib/aidp/harness/state/persistence.rb +3 -0
  49. data/lib/aidp/harness/state_manager.rb +3 -0
  50. data/lib/aidp/harness/status_display.rb +28 -20
  51. data/lib/aidp/harness/test_runner.rb +179 -41
  52. data/lib/aidp/harness/thinking_depth_manager.rb +44 -28
  53. data/lib/aidp/harness/ui/enhanced_tui.rb +4 -0
  54. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +4 -0
  55. data/lib/aidp/harness/ui/error_handler.rb +3 -0
  56. data/lib/aidp/harness/ui/job_monitor.rb +4 -0
  57. data/lib/aidp/harness/ui/navigation/submenu.rb +2 -2
  58. data/lib/aidp/harness/ui/navigation/workflow_selector.rb +6 -0
  59. data/lib/aidp/harness/ui/spinner_helper.rb +3 -0
  60. data/lib/aidp/harness/ui/workflow_controller.rb +3 -0
  61. data/lib/aidp/harness/user_interface.rb +3 -0
  62. data/lib/aidp/loader.rb +195 -0
  63. data/lib/aidp/logger.rb +3 -0
  64. data/lib/aidp/message_display.rb +31 -0
  65. data/lib/aidp/metadata/compiler.rb +29 -17
  66. data/lib/aidp/metadata/query.rb +1 -1
  67. data/lib/aidp/metadata/scanner.rb +8 -1
  68. data/lib/aidp/metadata/tool_metadata.rb +13 -13
  69. data/lib/aidp/metadata/validator.rb +10 -0
  70. data/lib/aidp/metadata.rb +16 -0
  71. data/lib/aidp/pr_worktree_manager.rb +20 -8
  72. data/lib/aidp/provider_manager.rb +4 -7
  73. data/lib/aidp/providers/base.rb +2 -0
  74. data/lib/aidp/security/rule_of_two_enforcer.rb +210 -0
  75. data/lib/aidp/security/secrets_proxy.rb +328 -0
  76. data/lib/aidp/security/secrets_registry.rb +227 -0
  77. data/lib/aidp/security/trifecta_state.rb +220 -0
  78. data/lib/aidp/security/watch_mode_handler.rb +306 -0
  79. data/lib/aidp/security/work_loop_adapter.rb +277 -0
  80. data/lib/aidp/security.rb +56 -0
  81. data/lib/aidp/setup/wizard.rb +283 -11
  82. data/lib/aidp/skills.rb +0 -5
  83. data/lib/aidp/storage/csv_storage.rb +3 -0
  84. data/lib/aidp/style_guide/selector.rb +360 -0
  85. data/lib/aidp/tooling_detector.rb +283 -16
  86. data/lib/aidp/version.rb +1 -1
  87. data/lib/aidp/watch/auto_merger.rb +274 -0
  88. data/lib/aidp/watch/auto_pr_processor.rb +125 -7
  89. data/lib/aidp/watch/build_processor.rb +16 -1
  90. data/lib/aidp/watch/change_request_processor.rb +682 -150
  91. data/lib/aidp/watch/ci_fix_processor.rb +262 -4
  92. data/lib/aidp/watch/feedback_collector.rb +191 -0
  93. data/lib/aidp/watch/hierarchical_pr_strategy.rb +256 -0
  94. data/lib/aidp/watch/implementation_verifier.rb +142 -1
  95. data/lib/aidp/watch/plan_generator.rb +70 -13
  96. data/lib/aidp/watch/plan_processor.rb +12 -5
  97. data/lib/aidp/watch/projects_processor.rb +286 -0
  98. data/lib/aidp/watch/repository_client.rb +871 -22
  99. data/lib/aidp/watch/review_processor.rb +33 -6
  100. data/lib/aidp/watch/runner.rb +80 -29
  101. data/lib/aidp/watch/state_store.rb +233 -0
  102. data/lib/aidp/watch/sub_issue_creator.rb +221 -0
  103. data/lib/aidp/watch.rb +5 -7
  104. data/lib/aidp/workflows/guided_agent.rb +4 -0
  105. data/lib/aidp/workstream_cleanup.rb +0 -2
  106. data/lib/aidp/workstream_executor.rb +3 -4
  107. data/lib/aidp/worktree.rb +61 -12
  108. data/lib/aidp/worktree_branch_manager.rb +347 -101
  109. data/lib/aidp.rb +21 -106
  110. data/templates/implementation/iterative_implementation.md +46 -3
  111. metadata +91 -36
  112. data/lib/aidp/config/paths.rb +0 -131
@@ -6,15 +6,7 @@ require "yaml"
6
6
  require "time"
7
7
  require "fileutils"
8
8
  require "json"
9
-
10
- require_relative "../util"
11
- require_relative "../config/paths"
12
- require_relative "../harness/capability_registry"
13
- require_relative "provider_registry"
14
- require_relative "devcontainer/parser"
15
- require_relative "devcontainer/generator"
16
- require_relative "devcontainer/port_manager"
17
- require_relative "devcontainer/backup_manager"
9
+ require "ostruct"
18
10
 
19
11
  module Aidp
20
12
  module Setup
@@ -31,6 +23,8 @@ module Aidp
31
23
  }.freeze
32
24
 
33
25
  attr_reader :project_dir, :prompt, :dry_run
26
+ # Expose state for testability
27
+ attr_reader :warnings, :existing_config, :config, :discovery_threads
34
28
 
35
29
  def initialize(project_dir = Dir.pwd, prompt: nil, dry_run: false)
36
30
  @project_dir = project_dir
@@ -57,6 +51,7 @@ module Aidp
57
51
  configure_artifacts
58
52
  configure_nfrs
59
53
  configure_logging
54
+ configure_auto_update
60
55
  configure_modes
61
56
  configure_devcontainer
62
57
 
@@ -112,8 +107,8 @@ module Aidp
112
107
  providers_dir = File.join(__dir__, "../providers")
113
108
  provider_files = Dir.glob("*.rb", base: providers_dir)
114
109
 
115
- # Exclude base classes
116
- excluded_files = ["base.rb"]
110
+ # Exclude base classes and non-provider utility files
111
+ excluded_files = ["base.rb", "adapter.rb", "capability_registry.rb", "error_taxonomy.rb"]
117
112
  provider_files -= excluded_files
118
113
 
119
114
  providers = {}
@@ -493,6 +488,7 @@ module Aidp
493
488
  prompt.say("-" * 40)
494
489
 
495
490
  configure_work_loop_limits
491
+ configure_output_filtering
496
492
  configure_test_commands
497
493
  configure_linting
498
494
  configure_watch_patterns
@@ -515,6 +511,216 @@ module Aidp
515
511
  set([:work_loop, :max_iterations], max_iterations)
516
512
  end
517
513
 
514
+ def configure_output_filtering
515
+ prompt.say("\n🔍 Output filtering configuration")
516
+ prompt.say(" Reduces token consumption by filtering test/lint output")
517
+ existing = get([:work_loop, :output_filtering]) || {}
518
+
519
+ return unless prompt.yes?("Configure output filtering?", default: false)
520
+
521
+ enabled = prompt.yes?(
522
+ "Enable output filtering?",
523
+ default: existing.fetch(:enabled, true)
524
+ )
525
+
526
+ unless enabled
527
+ set([:work_loop, :output_filtering], {enabled: false})
528
+ return
529
+ end
530
+
531
+ # Test output mode
532
+ test_mode_choices = [
533
+ ["Full (no filtering)", "full"],
534
+ ["Failures only (recommended for iterations)", "failures_only"],
535
+ ["Minimal (summary + locations only)", "minimal"]
536
+ ]
537
+ test_mode_default = existing[:test_mode] || "full"
538
+ test_mode_default_label = test_mode_choices.find { |label, value| value == test_mode_default }&.first
539
+
540
+ test_mode = prompt.select("Test output mode:", default: test_mode_default_label) do |menu|
541
+ test_mode_choices.each { |label, value| menu.choice label, value }
542
+ end
543
+
544
+ # Lint output mode
545
+ lint_mode_choices = test_mode_choices
546
+ lint_mode_default = existing[:lint_mode] || "full"
547
+ lint_mode_default_label = lint_mode_choices.find { |label, value| value == lint_mode_default }&.first
548
+
549
+ lint_mode = prompt.select("Lint output mode:", default: lint_mode_default_label) do |menu|
550
+ lint_mode_choices.each { |label, value| menu.choice label, value }
551
+ end
552
+
553
+ # Max output lines
554
+ test_max_lines = ask_with_default(
555
+ "Maximum test output lines",
556
+ (existing[:test_max_lines] || 500).to_s
557
+ ) { |value| value.to_i }
558
+
559
+ lint_max_lines = ask_with_default(
560
+ "Maximum lint output lines",
561
+ (existing[:lint_max_lines] || 300).to_s
562
+ ) { |value| value.to_i }
563
+
564
+ # Context configuration
565
+ include_context = prompt.yes?(
566
+ "Include context lines around failures?",
567
+ default: existing.fetch(:include_context, true)
568
+ )
569
+
570
+ context_lines = if include_context
571
+ ask_with_default(
572
+ "Number of context lines",
573
+ (existing[:context_lines] || 3).to_s
574
+ ) { |value| value.to_i }
575
+ else
576
+ 0
577
+ end
578
+
579
+ set([:work_loop, :output_filtering], {
580
+ enabled: true,
581
+ test_mode: test_mode,
582
+ lint_mode: lint_mode,
583
+ test_max_lines: test_max_lines,
584
+ lint_max_lines: lint_max_lines,
585
+ include_context: include_context,
586
+ context_lines: context_lines
587
+ })
588
+
589
+ prompt.ok("✅ Output filtering configured")
590
+
591
+ # Offer to generate AI-powered filter definitions
592
+ configure_filter_generation
593
+ end
594
+
595
+ def configure_filter_generation
596
+ prompt.say("\n🤖 AI-Generated Filter Definitions")
597
+ prompt.say(" Generate custom filters for your test/lint tools (one-time AI call)")
598
+
599
+ return unless prompt.yes?("Generate filter definitions for your tools?", default: false)
600
+
601
+ # Collect configured commands
602
+ commands_to_filter = collect_commands_for_filtering
603
+ if commands_to_filter.empty?
604
+ prompt.warn("⚠️ No test or lint commands configured. Configure them first.")
605
+ return
606
+ end
607
+
608
+ prompt.say("\n📝 Commands detected:")
609
+ commands_to_filter.each do |cmd|
610
+ prompt.say(" • #{cmd[:name]}: #{cmd[:command]}")
611
+ end
612
+
613
+ # Check if AI provider is configured
614
+ primary_provider = get([:harness, :default_provider])
615
+ unless primary_provider
616
+ prompt.warn("⚠️ No AI provider configured. Configure providers first.")
617
+ return
618
+ end
619
+
620
+ # Let user select which commands to generate filters for
621
+ prompt.say("\n💡 Use ↑/↓ arrows to navigate, SPACE to select/deselect, ENTER to confirm")
622
+ selected = prompt.multi_select("Select commands to generate filters for:") do |menu|
623
+ commands_to_filter.each do |cmd|
624
+ menu.choice "#{cmd[:name]} (#{cmd[:command]})", cmd
625
+ end
626
+ end
627
+
628
+ return if selected.empty?
629
+
630
+ # Generate filter definitions
631
+ filter_definitions = {}
632
+ factory = create_filter_factory
633
+
634
+ selected.each do |cmd|
635
+ prompt.say("\n⏳ Generating filter for #{cmd[:name]}...")
636
+ Aidp.log_info("setup_wizard", "generating_filter_definition",
637
+ tool_name: cmd[:name], command: cmd[:command])
638
+
639
+ begin
640
+ definition = factory.generate_from_command(
641
+ tool_command: cmd[:command],
642
+ project_dir: project_dir,
643
+ tier: "mini"
644
+ )
645
+
646
+ filter_definitions[cmd[:key]] = definition.to_h
647
+ prompt.ok(" ✅ Generated filter for #{cmd[:name]}")
648
+ Aidp.log_info("setup_wizard", "filter_definition_generated",
649
+ tool_name: cmd[:name],
650
+ pattern_count: definition.summary_patterns.size)
651
+ rescue => e
652
+ prompt.warn(" ⚠️ Failed to generate filter for #{cmd[:name]}: #{e.message}")
653
+ Aidp.log_error("setup_wizard", "filter_generation_failed",
654
+ tool_name: cmd[:name], error: e.message)
655
+ end
656
+ end
657
+
658
+ if filter_definitions.any?
659
+ set([:work_loop, :output_filtering, :filter_definitions], filter_definitions)
660
+ prompt.ok("\n✅ Generated #{filter_definitions.size} filter definition(s)")
661
+ prompt.say(" These filters will be applied deterministically (no AI calls at runtime)")
662
+ end
663
+ end
664
+
665
+ def collect_commands_for_filtering
666
+ commands = []
667
+
668
+ # Test commands
669
+ test_config = get([:work_loop, :test]) || {}
670
+ if test_config[:unit] && !test_config[:unit].start_with?("echo")
671
+ commands << {
672
+ key: "unit_test",
673
+ name: "Unit Tests",
674
+ command: test_config[:unit],
675
+ type: :test
676
+ }
677
+ end
678
+ if test_config[:integration] && !test_config[:integration].to_s.empty? && !test_config[:integration].start_with?("echo")
679
+ commands << {
680
+ key: "integration_test",
681
+ name: "Integration Tests",
682
+ command: test_config[:integration],
683
+ type: :test
684
+ }
685
+ end
686
+ if test_config[:e2e] && !test_config[:e2e].to_s.empty? && !test_config[:e2e].start_with?("echo")
687
+ commands << {
688
+ key: "e2e_test",
689
+ name: "E2E Tests",
690
+ command: test_config[:e2e],
691
+ type: :test
692
+ }
693
+ end
694
+
695
+ # Lint commands
696
+ lint_config = get([:work_loop, :lint]) || {}
697
+ if lint_config[:command] && !lint_config[:command].start_with?("echo")
698
+ commands << {
699
+ key: "lint",
700
+ name: "Linter",
701
+ command: lint_config[:command],
702
+ type: :lint
703
+ }
704
+ end
705
+
706
+ commands
707
+ end
708
+
709
+ def create_filter_factory
710
+ # Build a minimal configuration for the factory
711
+ config = build_harness_config_for_factory
712
+ Aidp::Harness::AIFilterFactory.new(config)
713
+ end
714
+
715
+ def build_harness_config_for_factory
716
+ # Create a minimal config object that the factory needs
717
+ OpenStruct.new(
718
+ default_provider: get([:harness, :default_provider]),
719
+ providers: get([:providers]) || {},
720
+ thinking_tiers: get([:thinking, :tiers])
721
+ )
722
+ end
723
+
518
724
  def configure_test_commands
519
725
  existing = get([:work_loop, :test]) || {}
520
726
 
@@ -1007,6 +1213,72 @@ module Aidp
1007
1213
  })
1008
1214
  end
1009
1215
 
1216
+ def configure_auto_update
1217
+ prompt.say("\n♻️ Auto-update configuration")
1218
+ prompt.say("-" * 40)
1219
+
1220
+ existing = get([:auto_update]) || {}
1221
+ enabled = prompt.yes?(
1222
+ "Enable auto-update for watch mode?",
1223
+ default: existing.fetch(:enabled, false)
1224
+ )
1225
+
1226
+ if enabled
1227
+ policy_choices = [
1228
+ ["Off (manual updates)", "off"],
1229
+ ["Patch updates", "patch"],
1230
+ ["Minor updates", "minor"],
1231
+ ["Major updates", "major"],
1232
+ ["Exact version only", "exact"]
1233
+ ]
1234
+ policy_default = existing[:policy] || "minor"
1235
+ policy_default_label = policy_choices.find { |label, value| value == policy_default }&.first
1236
+ policy = prompt.select("Auto-update policy:", default: policy_default_label) do |menu|
1237
+ policy_choices.each { |label, value| menu.choice(label, value) }
1238
+ end
1239
+
1240
+ allow_prerelease = prompt.yes?(
1241
+ "Allow prerelease versions?",
1242
+ default: existing.fetch(:allow_prerelease, false)
1243
+ )
1244
+
1245
+ interval_default = (existing[:check_interval_seconds] || 3600).to_s
1246
+ interval = ask_with_default(
1247
+ "Check interval (seconds, 300-86400)",
1248
+ interval_default
1249
+ ) { |value| value.to_i }
1250
+
1251
+ supervisor_choices = [
1252
+ ["None (manual restart)", "none"],
1253
+ ["supervisord (recommended)", "supervisord"],
1254
+ ["s6", "s6"],
1255
+ ["runit", "runit"]
1256
+ ]
1257
+ supervisor_default = existing[:supervisor] || "none"
1258
+ supervisor_default_label = supervisor_choices.find { |label, value| value == supervisor_default }&.first
1259
+
1260
+ supervisor = prompt.select("Update supervisor:", default: supervisor_default_label) do |menu|
1261
+ supervisor_choices.each { |label, value| menu.choice(label, value) }
1262
+ end
1263
+
1264
+ max_failures = ask_with_default(
1265
+ "Max consecutive update failures before backoff",
1266
+ (existing[:max_consecutive_failures] || 3).to_s
1267
+ ) { |value| value.to_i }
1268
+
1269
+ set([:auto_update], {
1270
+ enabled: true,
1271
+ policy: policy,
1272
+ allow_prerelease: allow_prerelease,
1273
+ check_interval_seconds: interval,
1274
+ supervisor: supervisor,
1275
+ max_consecutive_failures: max_failures
1276
+ })
1277
+ else
1278
+ set([:auto_update], {enabled: false, policy: "off"})
1279
+ end
1280
+ end
1281
+
1010
1282
  def configure_modes
1011
1283
  prompt.say("\n🚀 Operational modes")
1012
1284
  prompt.say("-" * 40)
data/lib/aidp/skills.rb CHANGED
@@ -1,10 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "skills/skill"
4
- require_relative "skills/loader"
5
- require_relative "skills/registry"
6
- require_relative "skills/composer"
7
-
8
3
  module Aidp
9
4
  # Skills subsystem for managing agent personas and capabilities
10
5
  #
@@ -237,5 +237,8 @@ module Aidp
237
237
  Kernel.warn(message)
238
238
  end
239
239
  end
240
+
241
+ # Zeitwerk inflection compatibility
242
+ CSVStorage = CsvStorage
240
243
  end
241
244
  end