aidp 0.9.6 → 0.11.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 (100) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +194 -25
  3. data/lib/aidp/analyze/error_handler.rb +4 -2
  4. data/lib/aidp/{analysis → analyze}/kb_inspector.rb +93 -89
  5. data/lib/aidp/analyze/prioritizer.rb +3 -2
  6. data/lib/aidp/analyze/progress.rb +2 -1
  7. data/lib/aidp/analyze/ruby_maat_integration.rb +7 -3
  8. data/lib/aidp/analyze/runner.rb +73 -11
  9. data/lib/aidp/{analysis → analyze}/seams.rb +1 -1
  10. data/lib/aidp/analyze/steps.rb +10 -8
  11. data/lib/aidp/{analysis → analyze}/tree_sitter_grammar_loader.rb +11 -5
  12. data/lib/aidp/{analysis → analyze}/tree_sitter_scan.rb +21 -15
  13. data/lib/aidp/cli/checkpoint_command.rb +98 -0
  14. data/lib/aidp/cli/first_run_wizard.rb +83 -103
  15. data/lib/aidp/cli/jobs_command.rb +270 -36
  16. data/lib/aidp/cli/terminal_io.rb +3 -3
  17. data/lib/aidp/cli.rb +411 -69
  18. data/lib/aidp/config.rb +5 -8
  19. data/lib/aidp/debug_logger.rb +4 -4
  20. data/lib/aidp/debug_mixin.rb +11 -4
  21. data/lib/aidp/execute/checkpoint.rb +282 -0
  22. data/lib/aidp/execute/checkpoint_display.rb +221 -0
  23. data/lib/aidp/execute/progress.rb +2 -1
  24. data/lib/aidp/execute/prompt_manager.rb +62 -0
  25. data/lib/aidp/execute/runner.rb +67 -20
  26. data/lib/aidp/execute/steps.rb +36 -27
  27. data/lib/aidp/execute/work_loop_runner.rb +308 -0
  28. data/lib/aidp/execute/workflow_selector.rb +50 -26
  29. data/lib/aidp/harness/condition_detector.rb +4 -4
  30. data/lib/aidp/harness/config_schema.rb +40 -0
  31. data/lib/aidp/harness/config_validator.rb +3 -6
  32. data/lib/aidp/harness/configuration.rb +35 -1
  33. data/lib/aidp/harness/enhanced_runner.rb +25 -4
  34. data/lib/aidp/harness/error_handler.rb +103 -28
  35. data/lib/aidp/harness/provider_factory.rb +6 -1
  36. data/lib/aidp/harness/provider_manager.rb +273 -19
  37. data/lib/aidp/harness/runner.rb +14 -6
  38. data/lib/aidp/harness/simple_user_interface.rb +6 -4
  39. data/lib/aidp/harness/status_display.rb +118 -106
  40. data/lib/aidp/harness/test_runner.rb +83 -0
  41. data/lib/aidp/harness/ui/enhanced_tui.rb +7 -5
  42. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +22 -4
  43. data/lib/aidp/harness/ui/error_handler.rb +7 -2
  44. data/lib/aidp/harness/ui/frame_manager.rb +61 -39
  45. data/lib/aidp/harness/ui/job_monitor.rb +2 -0
  46. data/lib/aidp/harness/ui/navigation/main_menu.rb +27 -16
  47. data/lib/aidp/harness/ui/navigation/menu_item.rb +1 -0
  48. data/lib/aidp/harness/ui/navigation/menu_state.rb +1 -0
  49. data/lib/aidp/harness/ui/navigation/submenu.rb +1 -0
  50. data/lib/aidp/harness/ui/navigation/workflow_selector.rb +2 -0
  51. data/lib/aidp/harness/ui/progress_display.rb +26 -7
  52. data/lib/aidp/harness/ui/question_collector.rb +2 -0
  53. data/lib/aidp/harness/ui/spinner_group.rb +2 -0
  54. data/lib/aidp/harness/ui/spinner_helper.rb +1 -1
  55. data/lib/aidp/harness/ui/status_manager.rb +4 -2
  56. data/lib/aidp/harness/ui/status_widget.rb +20 -9
  57. data/lib/aidp/harness/ui/workflow_controller.rb +27 -9
  58. data/lib/aidp/harness/user_interface.rb +338 -330
  59. data/lib/aidp/jobs/background_runner.rb +278 -0
  60. data/lib/aidp/message_display.rb +48 -0
  61. data/lib/aidp/provider_manager.rb +13 -7
  62. data/lib/aidp/providers/anthropic.rb +101 -18
  63. data/lib/aidp/providers/base.rb +51 -1
  64. data/lib/aidp/providers/codex.rb +248 -0
  65. data/lib/aidp/providers/cursor.rb +39 -48
  66. data/lib/aidp/providers/gemini.rb +26 -16
  67. data/lib/aidp/providers/github_copilot.rb +263 -0
  68. data/lib/aidp/providers/opencode.rb +38 -47
  69. data/lib/aidp/version.rb +1 -1
  70. data/lib/aidp/workflows/definitions.rb +357 -0
  71. data/lib/aidp/workflows/selector.rb +171 -0
  72. data/lib/aidp.rb +16 -4
  73. data/templates/planning/generate_llm_style_guide.md +119 -0
  74. metadata +43 -31
  75. data/lib/aidp/analyze/progress_visualizer.rb +0 -314
  76. /data/templates/{ANALYZE/02_ARCHITECTURE_ANALYSIS.md → analysis/analyze_architecture.md} +0 -0
  77. /data/templates/{ANALYZE/05_DOCUMENTATION_ANALYSIS.md → analysis/analyze_documentation.md} +0 -0
  78. /data/templates/{ANALYZE/04_FUNCTIONALITY_ANALYSIS.md → analysis/analyze_functionality.md} +0 -0
  79. /data/templates/{ANALYZE/01_REPOSITORY_ANALYSIS.md → analysis/analyze_repository.md} +0 -0
  80. /data/templates/{ANALYZE/06_STATIC_ANALYSIS.md → analysis/analyze_static_code.md} +0 -0
  81. /data/templates/{ANALYZE/03_TEST_ANALYSIS.md → analysis/analyze_tests.md} +0 -0
  82. /data/templates/{ANALYZE/07_REFACTORING_RECOMMENDATIONS.md → analysis/recommend_refactoring.md} +0 -0
  83. /data/templates/{ANALYZE/06a_tree_sitter_scan.md → analysis/scan_with_tree_sitter.md} +0 -0
  84. /data/templates/{EXECUTE/11_STATIC_ANALYSIS.md → implementation/configure_static_analysis.md} +0 -0
  85. /data/templates/{EXECUTE/14_DOCS_PORTAL.md → implementation/create_documentation_portal.md} +0 -0
  86. /data/templates/{EXECUTE/10_IMPLEMENTATION_AGENT.md → implementation/implement_features.md} +0 -0
  87. /data/templates/{EXECUTE/13_DELIVERY_ROLLOUT.md → implementation/plan_delivery.md} +0 -0
  88. /data/templates/{EXECUTE/15_POST_RELEASE.md → implementation/review_post_release.md} +0 -0
  89. /data/templates/{EXECUTE/09_SCAFFOLDING_DEVEX.md → implementation/setup_scaffolding.md} +0 -0
  90. /data/templates/{EXECUTE/02A_ARCH_GATE_QUESTIONS.md → planning/ask_architecture_questions.md} +0 -0
  91. /data/templates/{EXECUTE/00_PRD.md → planning/create_prd.md} +0 -0
  92. /data/templates/{EXECUTE/08_TASKS.md → planning/create_tasks.md} +0 -0
  93. /data/templates/{EXECUTE/04_DOMAIN_DECOMPOSITION.md → planning/decompose_domain.md} +0 -0
  94. /data/templates/{EXECUTE/01_NFRS.md → planning/define_nfrs.md} +0 -0
  95. /data/templates/{EXECUTE/05_CONTRACTS.md → planning/design_apis.md} +0 -0
  96. /data/templates/{EXECUTE/02_ARCHITECTURE.md → planning/design_architecture.md} +0 -0
  97. /data/templates/{EXECUTE/06_THREAT_MODEL.md → planning/design_data_model.md} +0 -0
  98. /data/templates/{EXECUTE/03_ADR_FACTORY.md → planning/generate_adrs.md} +0 -0
  99. /data/templates/{EXECUTE/12_OBSERVABILITY_SLOS.md → planning/plan_observability.md} +0 -0
  100. /data/templates/{EXECUTE/07_TEST_PLAN.md → planning/plan_testing.md} +0 -0
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "tty-prompt"
3
4
  require "pastel"
4
5
 
5
6
  module Aidp
@@ -8,14 +9,19 @@ module Aidp
8
9
  # Centralized error handling for UI components
9
10
  class ErrorHandler
10
11
  class UIError < StandardError; end
12
+
11
13
  class ComponentError < UIError; end
14
+
12
15
  class ValidationError < UIError; end
16
+
13
17
  class DisplayError < UIError; end
18
+
14
19
  class InteractionError < UIError; end
15
20
 
16
21
  def initialize(ui_components = {})
17
22
  @logger = ui_components[:logger] || default_logger
18
23
  @formatter = ui_components[:formatter] || ErrorFormatter.new
24
+ @prompt = ui_components[:prompt] || TTY::Prompt.new
19
25
  end
20
26
 
21
27
  def handle_error(error, context = {})
@@ -67,8 +73,7 @@ module Aidp
67
73
  end
68
74
 
69
75
  def display_user_friendly_error(message)
70
- pastel = Pastel.new
71
- puts("#{pastel.red("Error:")} #{message}")
76
+ @prompt.say("Error: #{message}", color: :red)
72
77
  end
73
78
 
74
79
  def display_generic_error(error)
@@ -9,13 +9,16 @@ module Aidp
9
9
  # Handles nested framing using CLI UI frames
10
10
  class FrameManager < Base
11
11
  class FrameError < StandardError; end
12
+
12
13
  class InvalidFrameError < FrameError; end
14
+
13
15
  class DisplayError < FrameError; end
14
16
 
15
17
  def initialize(ui_components = {})
16
18
  super()
17
19
  @frame = ui_components[:frame] || TTY::Box
18
- @formatter = ui_components[:formatter] || FrameFormatter.new
20
+ @formatter = ui_components[:formatter] || (defined?(FrameFormatter) ? FrameFormatter.new : nil)
21
+ @output = ui_components[:output]
19
22
  @frame_open = false
20
23
  @frame_stack = []
21
24
  @frame_history = []
@@ -26,6 +29,18 @@ module Aidp
26
29
  }
27
30
  end
28
31
 
32
+ private
33
+
34
+ def display_message(message)
35
+ if @output
36
+ @output.say(message)
37
+ else
38
+ puts message
39
+ end
40
+ end
41
+
42
+ public
43
+
29
44
  def open_frame(frame_type, title, frame_data = nil, &block)
30
45
  validate_frame_type(frame_type)
31
46
  validate_title(title)
@@ -43,11 +58,11 @@ module Aidp
43
58
  @frame_stats[:status_counts][frame_data[:status]] += 1
44
59
  end
45
60
 
46
- if block_given?
61
+ if block
47
62
  content = yield
48
- puts TTY::Box.frame(formatted_title, content, width: 80)
63
+ display_message(@frame.frame(formatted_title, content, width: 80))
49
64
  else
50
- puts TTY::Box.frame(formatted_title, width: 80)
65
+ display_message(@frame.frame(formatted_title, width: 80))
51
66
  end
52
67
  rescue InvalidFrameError => e
53
68
  raise e
@@ -60,11 +75,18 @@ module Aidp
60
75
  validate_title(title)
61
76
  raise DisplayError, "No parent frame exists for nesting" if @frame_stack.empty?
62
77
 
63
- formatted_title = @formatter.format_frame_title(frame_type, title, frame_data)
78
+ formatted_title = @formatter ? @formatter.format_frame_title(frame_type, title, frame_data) : title.to_s
64
79
  @frame_stack.push({type: frame_type, title: title, data: frame_data})
65
- puts TTY::Box.frame(formatted_title, width: 80) do
66
- yield if block_given?
80
+
81
+ frame_result = @frame.frame(formatted_title, width: 80) do
82
+ if block
83
+ yield || ""
84
+ else
85
+ ""
86
+ end
67
87
  end
88
+
89
+ display_message(frame_result)
68
90
  rescue InvalidFrameError => e
69
91
  raise e
70
92
  rescue => e
@@ -98,7 +120,7 @@ module Aidp
98
120
  when :failed then "Failed"
99
121
  else status.to_s.capitalize
100
122
  end
101
- puts "Status: #{status_text}"
123
+ display_message("Status: #{status_text}")
102
124
  end
103
125
 
104
126
  def current_frame_status
@@ -125,18 +147,18 @@ module Aidp
125
147
  end
126
148
 
127
149
  def display_frame_summary
128
- puts "\n📊 Frame Summary"
129
- puts "=" * 50
150
+ display_message("\n📊 Frame Summary")
151
+ display_message("=" * 50)
130
152
 
131
153
  if @frame_stats[:total_frames] == 0
132
- puts "No frames used"
154
+ display_message("No frames used")
133
155
  return
134
156
  end
135
157
 
136
- puts "Total Frames: #{@frame_stats[:total_frames]}"
158
+ display_message("Total Frames: #{@frame_stats[:total_frames]}")
137
159
 
138
160
  unless @frame_stats[:frame_types].empty?
139
- puts "\nFrame Types:"
161
+ display_message("\nFrame Types:")
140
162
  @frame_stats[:frame_types].each do |type, count|
141
163
  emoji = case type
142
164
  when :section then "📋"
@@ -145,12 +167,12 @@ module Aidp
145
167
  when :step then "🔧"
146
168
  else "📋"
147
169
  end
148
- puts " #{emoji} #{type.to_s.capitalize}: #{count}"
170
+ display_message(" #{emoji} #{type.to_s.capitalize}: #{count}")
149
171
  end
150
172
  end
151
173
 
152
174
  unless @frame_stats[:status_counts].empty?
153
- puts "\nStatus Counts:"
175
+ display_message("\nStatus Counts:")
154
176
  @frame_stats[:status_counts].each do |status, count|
155
177
  status_emoji = case status
156
178
  when :running then "🔄"
@@ -158,12 +180,12 @@ module Aidp
158
180
  when :failed then "❌"
159
181
  else "❓"
160
182
  end
161
- puts " #{status_emoji} #{status.to_s.capitalize}: #{count}"
183
+ display_message(" #{status_emoji} #{status.to_s.capitalize}: #{count}")
162
184
  end
163
185
  end
164
186
 
165
- puts "\nCurrent Frame Depth: #{@frame_depth}"
166
- puts "Frames in History: #{@frame_history.length}"
187
+ display_message("\nCurrent Frame Depth: #{@frame_depth}")
188
+ display_message("Frames in History: #{@frame_history.length}")
167
189
  end
168
190
 
169
191
  def clear_frame_history
@@ -180,15 +202,15 @@ module Aidp
180
202
  def frame_with_block(frame_type, title, frame_data = nil, &block)
181
203
  validate_frame_type(frame_type)
182
204
  validate_title(title)
183
- raise ArgumentError, "Block required for frame_with_block" unless block_given?
205
+ raise ArgumentError, "Block required for frame_with_block" unless block
184
206
 
185
- formatted_title = @formatter.format_frame_title(frame_type, title, frame_data)
207
+ formatted_title = @formatter ? @formatter.format_frame_title(frame_type, title, frame_data) : title.to_s
186
208
  @frame_open = true
187
209
  @frame_stack.push({type: frame_type, title: title, data: frame_data})
188
210
 
189
211
  begin
190
212
  content = yield
191
- puts TTY::Box.frame(formatted_title, content, width: 80)
213
+ display_message(@frame.frame(formatted_title, content, width: 80))
192
214
 
193
215
  @frame_open = false
194
216
  @frame_stack.pop unless @frame_stack.empty?
@@ -200,14 +222,14 @@ module Aidp
200
222
  rescue => e
201
223
  @frame_open = false
202
224
  @frame_stack.pop unless @frame_stack.empty?
203
- raise e # Re-raise the original exception
225
+ raise e # Re-raise the original exception
204
226
  end
205
227
  end
206
228
 
207
229
  def divider(text)
208
230
  validate_text(text)
209
231
 
210
- formatted_text = @formatter.format_divider_text(text)
232
+ formatted_text = @formatter ? @formatter.format_divider_text(text) : text
211
233
  @frame.divider(formatted_text)
212
234
  rescue => e
213
235
  raise DisplayError, "Failed to create divider: #{e.message}"
@@ -216,12 +238,12 @@ module Aidp
216
238
  def section(title, &block)
217
239
  validate_title(title)
218
240
 
219
- formatted_title = @formatter.format_section_title(title)
220
- if block_given?
241
+ formatted_title = @formatter ? @formatter.format_section_title(title) : "📋 #{title}"
242
+ if block
221
243
  content = yield
222
- puts TTY::Box.frame(formatted_title, content, width: 80)
244
+ display_message(@frame.frame(formatted_title, content, width: 80))
223
245
  else
224
- puts TTY::Box.frame(formatted_title, width: 80)
246
+ display_message(@frame.frame(formatted_title, width: 80))
225
247
  end
226
248
  rescue => e
227
249
  raise DisplayError, "Failed to create section: #{e.message}"
@@ -230,10 +252,10 @@ module Aidp
230
252
  def subsection(title, &block)
231
253
  validate_title(title)
232
254
 
233
- formatted_title = @formatter.format_subsection_title(title)
234
- puts TTY::Box.frame(formatted_title, width: 80) do
235
- yield if block_given?
236
- end
255
+ formatted_title = @formatter ? @formatter.format_subsection_title(title) : "📝 #{title}"
256
+ display_message(@frame.frame(formatted_title, width: 80) do
257
+ yield if block
258
+ end)
237
259
  rescue => e
238
260
  raise DisplayError, "Failed to create subsection: #{e.message}"
239
261
  end
@@ -241,10 +263,10 @@ module Aidp
241
263
  def workflow_frame(workflow_name, &block)
242
264
  validate_workflow_name(workflow_name)
243
265
 
244
- formatted_title = @formatter.format_workflow_title(workflow_name)
245
- puts TTY::Box.frame(formatted_title, width: 80) do
246
- yield if block_given?
247
- end
266
+ formatted_title = @formatter ? @formatter.format_workflow_title(workflow_name) : "⚙️ #{workflow_name}"
267
+ display_message(@frame.frame(formatted_title, width: 80) do
268
+ yield if block
269
+ end)
248
270
  rescue => e
249
271
  raise DisplayError, "Failed to create workflow frame: #{e.message}"
250
272
  end
@@ -252,10 +274,10 @@ module Aidp
252
274
  def step_frame(step_name, step_number, total_steps, &block)
253
275
  validate_step_inputs(step_name, step_number, total_steps)
254
276
 
255
- formatted_title = @formatter.format_step_title(step_name, step_number, total_steps)
256
- puts TTY::Box.frame(formatted_title, width: 80) do
257
- yield if block_given?
258
- end
277
+ formatted_title = @formatter ? @formatter.format_step_title(step_name, step_number, total_steps) : "🔧 #{step_name} (#{step_number}/#{total_steps})"
278
+ display_message(@frame.frame(formatted_title, width: 80) do
279
+ yield if block
280
+ end)
259
281
  rescue => e
260
282
  raise DisplayError, "Failed to create step frame: #{e.message}"
261
283
  end
@@ -12,7 +12,9 @@ module Aidp
12
12
  # Real-time job monitoring and status tracking
13
13
  class JobMonitor < Base
14
14
  class JobMonitorError < StandardError; end
15
+
15
16
  class JobNotFoundError < JobMonitorError; end
17
+
16
18
  class MonitorError < JobMonitorError; end
17
19
 
18
20
  JOB_STATUSES = {
@@ -14,7 +14,9 @@ module Aidp
14
14
  # Main hierarchical navigation menu system
15
15
  class MainMenu < Base
16
16
  class MenuError < StandardError; end
17
+
17
18
  class InvalidMenuError < MenuError; end
19
+
18
20
  class NavigationError < MenuError; end
19
21
 
20
22
  def initialize(ui_components = {}, prompt: nil)
@@ -23,10 +25,11 @@ module Aidp
23
25
  @pastel = Pastel.new
24
26
  @formatter = ui_components[:formatter] || MenuFormatter.new
25
27
  @state_manager = ui_components[:state_manager] || MenuState.new
28
+ @output = ui_components[:output]
26
29
  @menu_items = []
27
30
  @current_level = 0
28
31
  @breadcrumb = []
29
- @navigation_history = [] # Track all navigation actions
32
+ @navigation_history = [] # Track all navigation actions
30
33
  end
31
34
 
32
35
  def add_menu_item(item)
@@ -69,9 +72,9 @@ module Aidp
69
72
 
70
73
  # Add navigation instructions if items exist
71
74
  if menu_items.any?
72
- puts "Use arrow keys to navigate, Enter to select"
75
+ display_message "Use arrow keys to navigate, Enter to select"
73
76
  else
74
- puts "No options available"
77
+ display_message "No options available"
75
78
  end
76
79
  rescue => e
77
80
  raise NavigationError, "Failed to display menu: #{e.message}"
@@ -112,7 +115,7 @@ module Aidp
112
115
  @menu_items = menu_items
113
116
  display_menu_items
114
117
 
115
- max_attempts = 10 # Prevent infinite loops
118
+ max_attempts = 10 # Prevent infinite loops
116
119
  attempts = 0
117
120
 
118
121
  loop do
@@ -133,7 +136,7 @@ module Aidp
133
136
  if index >= 0 && index < menu_items.length
134
137
  return menu_items[index]
135
138
  else
136
- puts "Invalid selection"
139
+ display_message "Invalid selection"
137
140
  next
138
141
  end
139
142
  else
@@ -142,14 +145,14 @@ module Aidp
142
145
  if selected_item
143
146
  return selected_item
144
147
  else
145
- puts "Invalid selection"
148
+ display_message "Invalid selection"
146
149
  next
147
150
  end
148
151
  end
149
152
  end
150
153
 
151
154
  # If we get here, we've exceeded max attempts
152
- puts "Too many invalid attempts. Returning first option."
155
+ display_message "Too many invalid attempts. Returning first option."
153
156
  menu_items.first
154
157
  end
155
158
 
@@ -194,10 +197,10 @@ module Aidp
194
197
  end
195
198
 
196
199
  def display_navigation_help
197
- puts "Navigation Help"
198
- puts "Use arrow keys to navigate"
199
- puts "Press Enter to select"
200
- puts "Press Escape to go back"
200
+ display_message "Navigation Help"
201
+ display_message "Use arrow keys to navigate"
202
+ display_message "Press Enter to select"
203
+ display_message "Press Escape to go back"
201
204
  end
202
205
 
203
206
  # Additional methods expected by tests
@@ -220,15 +223,23 @@ module Aidp
220
223
  # Make display_breadcrumb public for testing
221
224
  def display_breadcrumb
222
225
  if @breadcrumb.empty?
223
- puts "Home"
226
+ display_message "Home"
224
227
  else
225
228
  breadcrumb_text = @formatter.format_breadcrumb(@breadcrumb)
226
- puts(breadcrumb_text)
229
+ display_message(breadcrumb_text)
227
230
  end
228
231
  end
229
232
 
230
233
  private
231
234
 
235
+ def display_message(message)
236
+ if @output
237
+ @output.say(message)
238
+ else
239
+ puts message
240
+ end
241
+ end
242
+
232
243
  def validate_menu_item(item)
233
244
  raise InvalidMenuError, "Menu item must be a MenuItem" unless item.is_a?(MenuItem)
234
245
  end
@@ -243,14 +254,14 @@ module Aidp
243
254
 
244
255
  def display_menu_header(title)
245
256
  formatted_title = @formatter.format_menu_title(title)
246
- puts(formatted_title)
247
- puts(@formatter.format_separator)
257
+ display_message(formatted_title)
258
+ display_message(@formatter.format_separator)
248
259
  end
249
260
 
250
261
  def display_menu_items
251
262
  @menu_items.each_with_index do |item, index|
252
263
  formatted_item = @formatter.format_menu_item(item, index + 1)
253
- puts(formatted_item)
264
+ display_message(formatted_item)
254
265
  end
255
266
  end
256
267
 
@@ -7,6 +7,7 @@ module Aidp
7
7
  # Represents a single menu item in the navigation system
8
8
  class MenuItem
9
9
  class MenuItemError < StandardError; end
10
+
10
11
  class InvalidTypeError < MenuItemError; end
11
12
 
12
13
  VALID_TYPES = [:action, :submenu, :workflow, :separator].freeze
@@ -7,6 +7,7 @@ module Aidp
7
7
  # Manages navigation state and history
8
8
  class MenuState
9
9
  class StateError < StandardError; end
10
+
10
11
  class InvalidStateError < StateError; end
11
12
 
12
13
  def initialize
@@ -9,6 +9,7 @@ module Aidp
9
9
  # Specialized submenu for drill-down functionality
10
10
  class SubMenu < MainMenu
11
11
  class SubMenuError < MenuError; end
12
+
12
13
  class InvalidSubMenuError < SubMenuError; end
13
14
 
14
15
  def initialize(title, parent_menu = nil, ui_components = {})
@@ -12,7 +12,9 @@ module Aidp
12
12
  # Handles workflow mode selection (simple vs advanced)
13
13
  class WorkflowSelector < Base
14
14
  class WorkflowError < StandardError; end
15
+
15
16
  class InvalidModeError < WorkflowError; end
17
+
16
18
  class SelectionError < WorkflowError; end
17
19
 
18
20
  WORKFLOW_MODES = {
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "tty-progressbar"
4
+ require "tty-prompt"
4
5
  require "pastel"
5
6
  require_relative "base"
6
7
 
@@ -9,8 +10,12 @@ module Aidp
9
10
  module UI
10
11
  # Handles progress display using CLI UI progress bars
11
12
  class ProgressDisplay < Base
13
+ include Aidp::MessageDisplay
14
+
12
15
  class ProgressError < StandardError; end
16
+
13
17
  class InvalidProgressError < ProgressError; end
18
+
14
19
  class DisplayError < ProgressError; end
15
20
 
16
21
  attr_reader :refresh_interval
@@ -24,7 +29,8 @@ module Aidp
24
29
  @auto_refresh_enabled = false
25
30
  @refresh_interval = 1.0
26
31
  @refresh_thread = nil
27
- @output = ui_components[:output] || $stdout
32
+ @output = ui_components[:output]
33
+ @prompt = ui_components[:prompt] || TTY::Prompt.new
28
34
  @spinner_class = begin
29
35
  ui_components[:spinner] || TTY::Spinner
30
36
  rescue
@@ -115,7 +121,7 @@ module Aidp
115
121
  raise ArgumentError, "Progress items must be an array" unless progress_items.is_a?(Array)
116
122
 
117
123
  if progress_items.empty?
118
- @output.puts @pastel.dim("No progress items to display.")
124
+ display_message(@pastel.dim("No progress items to display."), type: :muted)
119
125
  return
120
126
  end
121
127
 
@@ -176,7 +182,7 @@ module Aidp
176
182
 
177
183
  def execute_progress_steps(bar, total_steps, &block)
178
184
  total_steps.times do
179
- yield(bar) if block_given?
185
+ yield(bar) if block
180
186
  bar.tick
181
187
  end
182
188
  end
@@ -186,7 +192,7 @@ module Aidp
186
192
  total_substeps.times do |index|
187
193
  substep_title = @formatter.format_substep_title(title, index + 1, total_substeps)
188
194
  bar.update_title(substep_title)
189
- yield(bar, index) if block_given?
195
+ yield(bar, index) if block
190
196
  bar.tick
191
197
  end
192
198
  end
@@ -199,7 +205,7 @@ module Aidp
199
205
  " (Step: #{progress_data[:current_step]})"
200
206
  task_id = progress_data[:id] ? "[#{progress_data[:id]}] " : ""
201
207
 
202
- @output.puts "#{task_id}#{progress}% #{message}#{step_info}"
208
+ display_message("#{task_id}#{progress}% #{message}#{step_info}", type: :info)
203
209
  end
204
210
 
205
211
  def display_detailed_progress(progress_data)
@@ -210,13 +216,13 @@ module Aidp
210
216
  started_at = progress_data[:started_at] ? progress_data[:started_at].strftime("%H:%M:%S") : "N/A"
211
217
  eta = progress_data[:eta] || "N/A"
212
218
 
213
- @output.puts "Progress: #{progress}% - #{message} (Step: #{current_step}/#{total_steps}, Started: #{started_at}, ETA: #{eta})"
219
+ display_message("Progress: #{progress}% - #{message} (Step: #{current_step}/#{total_steps}, Started: #{started_at}, ETA: #{eta})", type: :info)
214
220
  end
215
221
 
216
222
  def display_minimal_progress(progress_data)
217
223
  progress = progress_data[:progress] || 0
218
224
  message = progress_data[:message] || "Processing..."
219
- @output.puts "#{@pastel.blue("Progress:")} #{progress}% - #{message}"
225
+ display_message("Progress: #{progress}% - #{message}", type: :info)
220
226
  end
221
227
 
222
228
  def create_progress_bar(progress)
@@ -251,6 +257,19 @@ module Aidp
251
257
  timestamp: Time.now
252
258
  }
253
259
  end
260
+
261
+ private
262
+
263
+ # Use mixin display_message; fallback to stdout if no prompt
264
+ def display_message(message, type: :info)
265
+ if @prompt
266
+ super
267
+ elsif @output
268
+ @output.puts(message)
269
+ else
270
+ puts(message)
271
+ end
272
+ end
254
273
  end
255
274
 
256
275
  # Formats progress display text
@@ -9,7 +9,9 @@ module Aidp
9
9
  # Handles interactive question collection using CLI UI prompts
10
10
  class QuestionCollector < Base
11
11
  class QuestionError < StandardError; end
12
+
12
13
  class ValidationError < QuestionError; end
14
+
13
15
  class CollectionError < QuestionError; end
14
16
 
15
17
  def initialize(ui_components = {}, prompt: nil)
@@ -9,7 +9,9 @@ module Aidp
9
9
  # Handles concurrent operations using CLI UI spinner groups
10
10
  class SpinnerGroup < Base
11
11
  class SpinnerGroupError < StandardError; end
12
+
12
13
  class InvalidOperationError < SpinnerGroupError; end
14
+
13
15
  class ExecutionError < SpinnerGroupError; end
14
16
 
15
17
  def initialize(ui_components = {})
@@ -18,7 +18,7 @@ module Aidp
18
18
 
19
19
  # Main method: automatically manages spinner around a block
20
20
  def with_spinner(message, format: :dots, success_message: nil, error_message: nil, &block)
21
- raise ArgumentError, "Block required for with_spinner" unless block_given?
21
+ raise ArgumentError, "Block required for with_spinner" unless block
22
22
 
23
23
  spinner = create_spinner(message, format)
24
24
  start_spinner(spinner)
@@ -12,7 +12,9 @@ module Aidp
12
12
  # Real-time status updates using CLI UI spinners
13
13
  class StatusManager < Base
14
14
  class StatusError < StandardError; end
15
+
15
16
  class InvalidStatusError < StatusError; end
17
+
16
18
  class UpdateError < StatusError; end
17
19
 
18
20
  def initialize(ui_components = {})
@@ -164,7 +166,7 @@ module Aidp
164
166
  end
165
167
 
166
168
  def track_workflow_status(workflow_name, spinner, &block) # Will be updated dynamically
167
- yield(spinner) if block_given?
169
+ yield(spinner) if block
168
170
  @status_widget.show_success_status("Completed #{workflow_name}")
169
171
  rescue => e
170
172
  @status_widget.show_error_status("Failed #{workflow_name}: #{e.message}")
@@ -172,7 +174,7 @@ module Aidp
172
174
  end
173
175
 
174
176
  def track_step_status(step_name, spinner, &block)
175
- yield(spinner) if block_given?
177
+ yield(spinner) if block
176
178
  @status_widget.show_success_status("Completed #{step_name}")
177
179
  rescue => e
178
180
  @status_widget.show_error_status("Failed #{step_name}: #{e.message}")