aidp 0.10.0 → 0.12.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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +194 -25
  3. data/lib/aidp/analyze/kb_inspector.rb +2 -15
  4. data/lib/aidp/analyze/progress.rb +2 -1
  5. data/lib/aidp/analyze/ruby_maat_integration.rb +2 -15
  6. data/lib/aidp/analyze/runner.rb +64 -20
  7. data/lib/aidp/analyze/steps.rb +10 -8
  8. data/lib/aidp/analyze/tree_sitter_grammar_loader.rb +2 -13
  9. data/lib/aidp/analyze/tree_sitter_scan.rb +2 -13
  10. data/lib/aidp/cli/checkpoint_command.rb +98 -0
  11. data/lib/aidp/cli/first_run_wizard.rb +65 -94
  12. data/lib/aidp/cli/jobs_command.rb +249 -34
  13. data/lib/aidp/cli/mcp_dashboard.rb +205 -0
  14. data/lib/aidp/cli.rb +517 -43
  15. data/lib/aidp/config.rb +5 -8
  16. data/lib/aidp/debug_logger.rb +4 -4
  17. data/lib/aidp/debug_mixin.rb +11 -4
  18. data/lib/aidp/execute/checkpoint.rb +282 -0
  19. data/lib/aidp/execute/checkpoint_display.rb +221 -0
  20. data/lib/aidp/execute/progress.rb +2 -1
  21. data/lib/aidp/execute/prompt_manager.rb +62 -0
  22. data/lib/aidp/execute/runner.rb +53 -24
  23. data/lib/aidp/execute/steps.rb +36 -27
  24. data/lib/aidp/execute/work_loop_runner.rb +308 -0
  25. data/lib/aidp/execute/workflow_selector.rb +26 -17
  26. data/lib/aidp/harness/condition_detector.rb +4 -4
  27. data/lib/aidp/harness/config_schema.rb +40 -0
  28. data/lib/aidp/harness/config_validator.rb +3 -6
  29. data/lib/aidp/harness/configuration.rb +35 -1
  30. data/lib/aidp/harness/enhanced_runner.rb +22 -1
  31. data/lib/aidp/harness/error_handler.rb +103 -28
  32. data/lib/aidp/harness/provider_factory.rb +4 -1
  33. data/lib/aidp/harness/provider_info.rb +366 -0
  34. data/lib/aidp/harness/provider_manager.rb +250 -15
  35. data/lib/aidp/harness/runner.rb +3 -14
  36. data/lib/aidp/harness/simple_user_interface.rb +2 -15
  37. data/lib/aidp/harness/status_display.rb +12 -17
  38. data/lib/aidp/harness/test_runner.rb +83 -0
  39. data/lib/aidp/harness/ui/enhanced_tui.rb +2 -0
  40. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +44 -5
  41. data/lib/aidp/harness/ui/error_handler.rb +4 -0
  42. data/lib/aidp/harness/ui/frame_manager.rb +10 -8
  43. data/lib/aidp/harness/ui/job_monitor.rb +2 -0
  44. data/lib/aidp/harness/ui/navigation/main_menu.rb +4 -2
  45. data/lib/aidp/harness/ui/navigation/menu_item.rb +1 -0
  46. data/lib/aidp/harness/ui/navigation/menu_state.rb +1 -0
  47. data/lib/aidp/harness/ui/navigation/submenu.rb +1 -0
  48. data/lib/aidp/harness/ui/navigation/workflow_selector.rb +2 -0
  49. data/lib/aidp/harness/ui/progress_display.rb +8 -12
  50. data/lib/aidp/harness/ui/question_collector.rb +2 -0
  51. data/lib/aidp/harness/ui/spinner_group.rb +2 -0
  52. data/lib/aidp/harness/ui/spinner_helper.rb +1 -1
  53. data/lib/aidp/harness/ui/status_manager.rb +4 -2
  54. data/lib/aidp/harness/ui/status_widget.rb +3 -1
  55. data/lib/aidp/harness/ui/workflow_controller.rb +2 -0
  56. data/lib/aidp/harness/user_interface.rb +12 -17
  57. data/lib/aidp/jobs/background_runner.rb +278 -0
  58. data/lib/aidp/message_display.rb +48 -0
  59. data/lib/aidp/provider_manager.rb +3 -1
  60. data/lib/aidp/providers/anthropic.rb +100 -17
  61. data/lib/aidp/providers/base.rb +42 -11
  62. data/lib/aidp/providers/codex.rb +248 -0
  63. data/lib/aidp/providers/cursor.rb +35 -42
  64. data/lib/aidp/providers/gemini.rb +25 -15
  65. data/lib/aidp/providers/github_copilot.rb +41 -42
  66. data/lib/aidp/providers/opencode.rb +34 -41
  67. data/lib/aidp/version.rb +1 -1
  68. data/lib/aidp/workflows/definitions.rb +357 -0
  69. data/lib/aidp/workflows/guided_agent.rb +400 -0
  70. data/lib/aidp/workflows/selector.rb +171 -0
  71. data/lib/aidp.rb +12 -0
  72. data/templates/planning/generate_llm_style_guide.md +119 -0
  73. metadata +41 -26
  74. /data/templates/{ANALYZE/02_ARCHITECTURE_ANALYSIS.md → analysis/analyze_architecture.md} +0 -0
  75. /data/templates/{ANALYZE/05_DOCUMENTATION_ANALYSIS.md → analysis/analyze_documentation.md} +0 -0
  76. /data/templates/{ANALYZE/04_FUNCTIONALITY_ANALYSIS.md → analysis/analyze_functionality.md} +0 -0
  77. /data/templates/{ANALYZE/01_REPOSITORY_ANALYSIS.md → analysis/analyze_repository.md} +0 -0
  78. /data/templates/{ANALYZE/06_STATIC_ANALYSIS.md → analysis/analyze_static_code.md} +0 -0
  79. /data/templates/{ANALYZE/03_TEST_ANALYSIS.md → analysis/analyze_tests.md} +0 -0
  80. /data/templates/{ANALYZE/07_REFACTORING_RECOMMENDATIONS.md → analysis/recommend_refactoring.md} +0 -0
  81. /data/templates/{ANALYZE/06a_tree_sitter_scan.md → analysis/scan_with_tree_sitter.md} +0 -0
  82. /data/templates/{EXECUTE/11_STATIC_ANALYSIS.md → implementation/configure_static_analysis.md} +0 -0
  83. /data/templates/{EXECUTE/14_DOCS_PORTAL.md → implementation/create_documentation_portal.md} +0 -0
  84. /data/templates/{EXECUTE/10_IMPLEMENTATION_AGENT.md → implementation/implement_features.md} +0 -0
  85. /data/templates/{EXECUTE/13_DELIVERY_ROLLOUT.md → implementation/plan_delivery.md} +0 -0
  86. /data/templates/{EXECUTE/15_POST_RELEASE.md → implementation/review_post_release.md} +0 -0
  87. /data/templates/{EXECUTE/09_SCAFFOLDING_DEVEX.md → implementation/setup_scaffolding.md} +0 -0
  88. /data/templates/{EXECUTE/02A_ARCH_GATE_QUESTIONS.md → planning/ask_architecture_questions.md} +0 -0
  89. /data/templates/{EXECUTE/00_PRD.md → planning/create_prd.md} +0 -0
  90. /data/templates/{EXECUTE/08_TASKS.md → planning/create_tasks.md} +0 -0
  91. /data/templates/{EXECUTE/04_DOMAIN_DECOMPOSITION.md → planning/decompose_domain.md} +0 -0
  92. /data/templates/{EXECUTE/01_NFRS.md → planning/define_nfrs.md} +0 -0
  93. /data/templates/{EXECUTE/05_CONTRACTS.md → planning/design_apis.md} +0 -0
  94. /data/templates/{EXECUTE/02_ARCHITECTURE.md → planning/design_architecture.md} +0 -0
  95. /data/templates/{EXECUTE/06_THREAT_MODEL.md → planning/design_data_model.md} +0 -0
  96. /data/templates/{EXECUTE/03_ADR_FACTORY.md → planning/generate_adrs.md} +0 -0
  97. /data/templates/{EXECUTE/12_OBSERVABILITY_SLOS.md → planning/plan_observability.md} +0 -0
  98. /data/templates/{EXECUTE/07_TEST_PLAN.md → planning/plan_testing.md} +0 -0
@@ -366,6 +366,46 @@ module Aidp
366
366
  enum: ["provider_model", "provider", "model", "none"]
367
367
  }
368
368
  }
369
+ },
370
+ work_loop: {
371
+ type: :hash,
372
+ required: false,
373
+ default: {
374
+ enabled: true,
375
+ max_iterations: 50,
376
+ test_commands: [],
377
+ lint_commands: []
378
+ },
379
+ properties: {
380
+ enabled: {
381
+ type: :boolean,
382
+ required: false,
383
+ default: true
384
+ },
385
+ max_iterations: {
386
+ type: :integer,
387
+ required: false,
388
+ default: 50,
389
+ min: 1,
390
+ max: 200
391
+ },
392
+ test_commands: {
393
+ type: :array,
394
+ required: false,
395
+ default: [],
396
+ items: {
397
+ type: :string
398
+ }
399
+ },
400
+ lint_commands: {
401
+ type: :array,
402
+ required: false,
403
+ default: [],
404
+ items: {
405
+ type: :string
406
+ }
407
+ }
408
+ }
369
409
  }
370
410
  }
371
411
  },
@@ -76,8 +76,9 @@ module Aidp
76
76
  return false if config_exists?
77
77
 
78
78
  example_config = ConfigSchema.generate_example
79
- config_path = File.join(@project_dir, "aidp.yml")
79
+ config_path = File.join(@project_dir, ".aidp", "aidp.yml")
80
80
 
81
+ FileUtils.mkdir_p(File.dirname(config_path))
81
82
  File.write(config_path, YAML.dump(example_config))
82
83
  true
83
84
  end
@@ -243,14 +244,10 @@ module Aidp
243
244
  private
244
245
 
245
246
  def find_config_file
246
- # Try new aidp.yml format first, then fall back to .aidp.yml
247
- config_file = File.join(@project_dir, "aidp.yml")
248
- legacy_config_file = File.join(@project_dir, ".aidp.yml")
247
+ config_file = File.join(@project_dir, ".aidp", "aidp.yml")
249
248
 
250
249
  if File.exist?(config_file)
251
250
  config_file
252
- elsif File.exist?(legacy_config_file)
253
- legacy_config_file
254
251
  end
255
252
  end
256
253
 
@@ -144,6 +144,31 @@ module Aidp
144
144
  harness_config[:session] || get_default_session_config
145
145
  end
146
146
 
147
+ # Get work loop configuration
148
+ def work_loop_config
149
+ harness_config[:work_loop] || get_default_work_loop_config
150
+ end
151
+
152
+ # Check if work loops are enabled
153
+ def work_loop_enabled?
154
+ work_loop_config[:enabled]
155
+ end
156
+
157
+ # Get maximum iterations for work loops
158
+ def work_loop_max_iterations
159
+ work_loop_config[:max_iterations]
160
+ end
161
+
162
+ # Get test commands
163
+ def test_commands
164
+ work_loop_config[:test_commands] || []
165
+ end
166
+
167
+ # Get lint commands
168
+ def lint_commands
169
+ work_loop_config[:lint_commands] || []
170
+ end
171
+
147
172
  # Get provider priority
148
173
  def provider_priority(provider_name)
149
174
  provider_config(provider_name)[:priority] || 0
@@ -186,7 +211,7 @@ module Aidp
186
211
 
187
212
  # Get configuration path
188
213
  def config_path
189
- File.join(@project_dir, "aidp.yml")
214
+ File.join(@project_dir, ".aidp", "aidp.yml")
190
215
  end
191
216
 
192
217
  # Get logging configuration
@@ -416,6 +441,15 @@ module Aidp
416
441
  }
417
442
  end
418
443
 
444
+ def get_default_work_loop_config
445
+ {
446
+ enabled: true,
447
+ max_iterations: 50,
448
+ test_commands: [],
449
+ lint_commands: []
450
+ }
451
+ end
452
+
419
453
  def get_default_logging_config
420
454
  {
421
455
  log_level: :info,
@@ -31,7 +31,7 @@ module Aidp
31
31
  @current_step = nil
32
32
  @current_provider = nil
33
33
  @user_input = options[:user_input] || {}
34
- @user_input = {} if @user_input.nil? # Ensure it's never nil
34
+ @user_input = {} if @user_input.nil? # Ensure it's never nil
35
35
  @execution_log = []
36
36
 
37
37
  # Store workflow configuration
@@ -55,6 +55,27 @@ module Aidp
55
55
  @completion_checker = CompletionChecker.new(@project_dir, @workflow_type)
56
56
  end
57
57
 
58
+ # Get current provider (delegate to provider manager)
59
+ def current_provider
60
+ @current_provider || @provider_manager&.current_provider || "unknown"
61
+ end
62
+
63
+ # Get current step
64
+ attr_reader :current_step
65
+
66
+ # Get user input
67
+ def user_input
68
+ @user_input || {}
69
+ end
70
+
71
+ # Get execution log
72
+ def execution_log
73
+ @execution_log || []
74
+ end
75
+
76
+ # Get provider manager
77
+ attr_reader :provider_manager
78
+
58
79
  # Main execution method with enhanced TUI
59
80
  def run
60
81
  @state = STATES[:running]
@@ -69,6 +69,17 @@ module Aidp
69
69
  error_type: error_info[:error_type],
70
70
  reason: "Retry not applicable or exhausted"
71
71
  })
72
+ if [:authentication, :permission_denied].include?(error_info[:error_type].to_sym)
73
+ # Mark provider unhealthy to avoid immediate re-selection
74
+ begin
75
+ if @provider_manager.respond_to?(:mark_provider_auth_failure)
76
+ @provider_manager.mark_provider_auth_failure(error_info[:provider])
77
+ debug_log("🔐 Marked provider #{error_info[:provider]} unhealthy due to auth error", level: :warn)
78
+ end
79
+ rescue => e
80
+ debug_log("⚠️ Failed to mark provider unhealthy after auth error", level: :warn, data: {error: e.message})
81
+ end
82
+ end
72
83
  attempt_recovery(error_info, context)
73
84
 
74
85
  end
@@ -76,35 +87,75 @@ module Aidp
76
87
 
77
88
  # Execute a block with retry logic
78
89
  def execute_with_retry(&block)
79
- max_attempts = @configuration.max_retries + 1
80
- attempt = 0
81
-
82
- begin
83
- attempt += 1
84
- yield
85
- rescue => error
86
- if attempt < max_attempts
87
- error_info = {
88
- error: error,
89
- provider: @provider_manager.current_provider,
90
- model: @provider_manager.current_model,
91
- error_type: @error_classifier.classify_error(error)
92
- }
90
+ providers_tried = []
91
+
92
+ loop do
93
+ max_attempts = @configuration.max_retries + 1
94
+ attempt = 0
95
+
96
+ begin
97
+ attempt += 1
98
+ return yield
99
+ rescue => error
100
+ current_provider = get_current_provider_safely
101
+
102
+ if attempt < max_attempts
103
+ error_info = {
104
+ error: error,
105
+ provider: current_provider,
106
+ model: get_current_model_safely,
107
+ error_type: @error_classifier.classify_error(error)
108
+ }
109
+
110
+ strategy = get_retry_strategy(error_info[:error_type])
111
+ if should_retry?(error_info, strategy)
112
+ delay = @backoff_calculator.calculate_delay(attempt, strategy[:backoff_strategy] || :exponential, 1, 10)
113
+ debug_log("🔁 Retry attempt #{attempt} for #{current_provider}", level: :info, data: {delay: delay, error_type: error_info[:error_type]})
114
+ sleep(delay) if delay > 0
115
+ retry
116
+ end
117
+ end
93
118
 
94
- strategy = get_retry_strategy(error_info[:error_type])
95
- if should_retry?(error_info, strategy)
96
- delay = @backoff_calculator.calculate_delay(attempt, strategy[:backoff_strategy] || :exponential, 1, 10)
97
- # Use regular sleep for now (async not needed in this context)
98
- sleep(delay)
99
- retry
119
+ # Provider exhausted – attempt recovery (may switch provider)
120
+ debug_log("🚫 Exhausted retries for provider, attempting recovery", level: :warn, data: {provider: current_provider, attempt: attempt, max_attempts: max_attempts})
121
+ handle_error(error, {
122
+ provider: current_provider,
123
+ model: get_current_model_safely,
124
+ exhausted_retries: true
125
+ })
126
+
127
+ new_provider = get_current_provider_safely
128
+ if new_provider != current_provider && !providers_tried.include?(new_provider)
129
+ providers_tried << current_provider
130
+ # Reset retry counts for the new provider
131
+ begin
132
+ reset_retry_counts(new_provider)
133
+ rescue => e
134
+ debug_log("⚠️ Failed to reset retry counts for new provider", level: :warn, data: {error: e.message})
135
+ end
136
+ debug_log("🔀 Switched provider after failure – re-executing block", level: :info, data: {from: current_provider, to: new_provider})
137
+ # Start retry loop fresh for new provider
138
+ next
100
139
  end
101
- end
102
140
 
103
- # If we get here, all retries failed
104
- handle_error(error, {
105
- provider: @provider_manager.current_provider,
106
- model: @provider_manager.current_model
107
- })
141
+ # No new provider (or already tried) – return structured failure
142
+ debug_log("❌ No fallback provider available or all tried", level: :error, data: {providers_tried: providers_tried})
143
+ begin
144
+ if @provider_manager.respond_to?(:mark_provider_failure_exhausted)
145
+ @provider_manager.mark_provider_failure_exhausted(current_provider)
146
+ debug_log("🛑 Marked provider #{current_provider} unhealthy due to exhausted retries", level: :warn)
147
+ end
148
+ rescue => e
149
+ debug_log("⚠️ Failed to mark provider failure-exhausted", level: :warn, data: {error: e.message})
150
+ end
151
+ return {
152
+ status: "failed",
153
+ error: error,
154
+ message: error.message,
155
+ provider: current_provider,
156
+ providers_tried: providers_tried.dup
157
+ }
158
+ end
108
159
  end
109
160
  end
110
161
 
@@ -579,9 +630,12 @@ module Aidp
579
630
  priority: :high
580
631
  }
581
632
  when :authentication, :permission_denied
633
+ # Previously we escalated immediately. Instead, attempt a provider switch
634
+ # so workflows can continue with alternate providers (e.g., Gemini, Cursor)
635
+ # while the user resolves credentials for the failing provider.
582
636
  {
583
- action: :escalate,
584
- reason: "Authentication or permission issue requires manual intervention",
637
+ action: :switch_provider,
638
+ reason: "Authentication/permission issue switching provider to continue",
585
639
  priority: :critical
586
640
  }
587
641
  when :timeout
@@ -611,6 +665,27 @@ module Aidp
611
665
  end
612
666
  end
613
667
  end
668
+
669
+ # Safe access to provider manager methods that may not exist
670
+ def get_current_provider_safely
671
+ return "unknown" unless @provider_manager
672
+ return "unknown" unless @provider_manager.respond_to?(:current_provider)
673
+
674
+ @provider_manager.current_provider || "unknown"
675
+ rescue => e
676
+ debug_log("⚠️ Failed to get current provider", level: :warn, data: {error: e.message})
677
+ "unknown"
678
+ end
679
+
680
+ def get_current_model_safely
681
+ return "unknown" unless @provider_manager
682
+ return "unknown" unless @provider_manager.respond_to?(:current_model)
683
+
684
+ @provider_manager.current_model || "unknown"
685
+ rescue => e
686
+ debug_log("⚠️ Failed to get current model", level: :warn, data: {error: e.message})
687
+ "unknown"
688
+ end
614
689
  end
615
690
  end
616
691
  end
@@ -8,6 +8,7 @@ require_relative "../providers/gemini"
8
8
  require_relative "../providers/macos_ui"
9
9
  require_relative "../providers/opencode"
10
10
  require_relative "../providers/github_copilot"
11
+ require_relative "../providers/codex"
11
12
 
12
13
  module Aidp
13
14
  module Harness
@@ -16,10 +17,12 @@ module Aidp
16
17
  PROVIDER_CLASSES = {
17
18
  "cursor" => Aidp::Providers::Cursor,
18
19
  "anthropic" => Aidp::Providers::Anthropic,
20
+ "claude" => Aidp::Providers::Anthropic,
19
21
  "gemini" => Aidp::Providers::Gemini,
20
22
  "macos" => Aidp::Providers::MacOSUI,
21
23
  "opencode" => Aidp::Providers::Opencode,
22
- "github_copilot" => Aidp::Providers::GithubCopilot
24
+ "github_copilot" => Aidp::Providers::GithubCopilot,
25
+ "codex" => Aidp::Providers::Codex
23
26
  }.freeze
24
27
 
25
28
  def initialize(config_manager = nil)