aidp 0.10.0 → 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 (95) 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.rb +312 -38
  14. data/lib/aidp/config.rb +5 -8
  15. data/lib/aidp/debug_logger.rb +4 -4
  16. data/lib/aidp/debug_mixin.rb +11 -4
  17. data/lib/aidp/execute/checkpoint.rb +282 -0
  18. data/lib/aidp/execute/checkpoint_display.rb +221 -0
  19. data/lib/aidp/execute/progress.rb +2 -1
  20. data/lib/aidp/execute/prompt_manager.rb +62 -0
  21. data/lib/aidp/execute/runner.rb +53 -24
  22. data/lib/aidp/execute/steps.rb +36 -27
  23. data/lib/aidp/execute/work_loop_runner.rb +308 -0
  24. data/lib/aidp/execute/workflow_selector.rb +26 -17
  25. data/lib/aidp/harness/condition_detector.rb +4 -4
  26. data/lib/aidp/harness/config_schema.rb +40 -0
  27. data/lib/aidp/harness/config_validator.rb +3 -6
  28. data/lib/aidp/harness/configuration.rb +35 -1
  29. data/lib/aidp/harness/enhanced_runner.rb +22 -1
  30. data/lib/aidp/harness/error_handler.rb +103 -28
  31. data/lib/aidp/harness/provider_factory.rb +4 -1
  32. data/lib/aidp/harness/provider_manager.rb +250 -15
  33. data/lib/aidp/harness/runner.rb +3 -14
  34. data/lib/aidp/harness/simple_user_interface.rb +2 -15
  35. data/lib/aidp/harness/status_display.rb +12 -17
  36. data/lib/aidp/harness/test_runner.rb +83 -0
  37. data/lib/aidp/harness/ui/enhanced_tui.rb +2 -0
  38. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +22 -4
  39. data/lib/aidp/harness/ui/error_handler.rb +4 -0
  40. data/lib/aidp/harness/ui/frame_manager.rb +10 -8
  41. data/lib/aidp/harness/ui/job_monitor.rb +2 -0
  42. data/lib/aidp/harness/ui/navigation/main_menu.rb +4 -2
  43. data/lib/aidp/harness/ui/navigation/menu_item.rb +1 -0
  44. data/lib/aidp/harness/ui/navigation/menu_state.rb +1 -0
  45. data/lib/aidp/harness/ui/navigation/submenu.rb +1 -0
  46. data/lib/aidp/harness/ui/navigation/workflow_selector.rb +2 -0
  47. data/lib/aidp/harness/ui/progress_display.rb +8 -12
  48. data/lib/aidp/harness/ui/question_collector.rb +2 -0
  49. data/lib/aidp/harness/ui/spinner_group.rb +2 -0
  50. data/lib/aidp/harness/ui/spinner_helper.rb +1 -1
  51. data/lib/aidp/harness/ui/status_manager.rb +4 -2
  52. data/lib/aidp/harness/ui/status_widget.rb +3 -1
  53. data/lib/aidp/harness/ui/workflow_controller.rb +2 -0
  54. data/lib/aidp/harness/user_interface.rb +12 -17
  55. data/lib/aidp/jobs/background_runner.rb +278 -0
  56. data/lib/aidp/message_display.rb +48 -0
  57. data/lib/aidp/provider_manager.rb +3 -1
  58. data/lib/aidp/providers/anthropic.rb +100 -17
  59. data/lib/aidp/providers/base.rb +42 -11
  60. data/lib/aidp/providers/codex.rb +248 -0
  61. data/lib/aidp/providers/cursor.rb +35 -42
  62. data/lib/aidp/providers/gemini.rb +25 -15
  63. data/lib/aidp/providers/github_copilot.rb +41 -42
  64. data/lib/aidp/providers/opencode.rb +34 -41
  65. data/lib/aidp/version.rb +1 -1
  66. data/lib/aidp/workflows/definitions.rb +357 -0
  67. data/lib/aidp/workflows/selector.rb +171 -0
  68. data/lib/aidp.rb +12 -0
  69. data/templates/planning/generate_llm_style_guide.md +119 -0
  70. metadata +38 -26
  71. /data/templates/{ANALYZE/02_ARCHITECTURE_ANALYSIS.md → analysis/analyze_architecture.md} +0 -0
  72. /data/templates/{ANALYZE/05_DOCUMENTATION_ANALYSIS.md → analysis/analyze_documentation.md} +0 -0
  73. /data/templates/{ANALYZE/04_FUNCTIONALITY_ANALYSIS.md → analysis/analyze_functionality.md} +0 -0
  74. /data/templates/{ANALYZE/01_REPOSITORY_ANALYSIS.md → analysis/analyze_repository.md} +0 -0
  75. /data/templates/{ANALYZE/06_STATIC_ANALYSIS.md → analysis/analyze_static_code.md} +0 -0
  76. /data/templates/{ANALYZE/03_TEST_ANALYSIS.md → analysis/analyze_tests.md} +0 -0
  77. /data/templates/{ANALYZE/07_REFACTORING_RECOMMENDATIONS.md → analysis/recommend_refactoring.md} +0 -0
  78. /data/templates/{ANALYZE/06a_tree_sitter_scan.md → analysis/scan_with_tree_sitter.md} +0 -0
  79. /data/templates/{EXECUTE/11_STATIC_ANALYSIS.md → implementation/configure_static_analysis.md} +0 -0
  80. /data/templates/{EXECUTE/14_DOCS_PORTAL.md → implementation/create_documentation_portal.md} +0 -0
  81. /data/templates/{EXECUTE/10_IMPLEMENTATION_AGENT.md → implementation/implement_features.md} +0 -0
  82. /data/templates/{EXECUTE/13_DELIVERY_ROLLOUT.md → implementation/plan_delivery.md} +0 -0
  83. /data/templates/{EXECUTE/15_POST_RELEASE.md → implementation/review_post_release.md} +0 -0
  84. /data/templates/{EXECUTE/09_SCAFFOLDING_DEVEX.md → implementation/setup_scaffolding.md} +0 -0
  85. /data/templates/{EXECUTE/02A_ARCH_GATE_QUESTIONS.md → planning/ask_architecture_questions.md} +0 -0
  86. /data/templates/{EXECUTE/00_PRD.md → planning/create_prd.md} +0 -0
  87. /data/templates/{EXECUTE/08_TASKS.md → planning/create_tasks.md} +0 -0
  88. /data/templates/{EXECUTE/04_DOMAIN_DECOMPOSITION.md → planning/decompose_domain.md} +0 -0
  89. /data/templates/{EXECUTE/01_NFRS.md → planning/define_nfrs.md} +0 -0
  90. /data/templates/{EXECUTE/05_CONTRACTS.md → planning/design_apis.md} +0 -0
  91. /data/templates/{EXECUTE/02_ARCHITECTURE.md → planning/design_architecture.md} +0 -0
  92. /data/templates/{EXECUTE/06_THREAT_MODEL.md → planning/design_data_model.md} +0 -0
  93. /data/templates/{EXECUTE/03_ADR_FACTORY.md → planning/generate_adrs.md} +0 -0
  94. /data/templates/{EXECUTE/12_OBSERVABILITY_SLOS.md → planning/plan_observability.md} +0 -0
  95. /data/templates/{EXECUTE/07_TEST_PLAN.md → planning/plan_testing.md} +0 -0
@@ -1,17 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "tty-prompt"
4
+ require "tty-cursor"
4
5
 
5
6
  module Aidp
6
7
  module Harness
7
8
  # Real-time status updates and monitoring interface
8
9
  class StatusDisplay
10
+ include Aidp::MessageDisplay
11
+
9
12
  def initialize(provider_manager = nil, metrics_manager = nil, circuit_breaker_manager = nil, error_logger = nil, prompt: TTY::Prompt.new)
10
13
  @provider_manager = provider_manager
11
14
  @metrics_manager = metrics_manager
12
15
  @circuit_breaker_manager = circuit_breaker_manager
13
16
  @error_logger = error_logger
14
17
  @prompt = prompt
18
+ @cursor = TTY::Cursor
15
19
 
16
20
  @start_time = nil
17
21
  @current_step = nil
@@ -41,21 +45,6 @@ module Aidp
41
45
  @display_animator = DisplayAnimator.new
42
46
  end
43
47
 
44
- # Helper method for consistent message display using TTY::Prompt
45
- def display_message(message, type: :info)
46
- color = case type
47
- when :error then :red
48
- when :success then :green
49
- when :warning then :yellow
50
- when :info then :blue
51
- when :highlight then :cyan
52
- when :muted then :bright_black
53
- else :white
54
- end
55
-
56
- @prompt.say(message, color: color)
57
- end
58
-
59
48
  # Start real-time status updates
60
49
  def start_status_updates(display_mode = :compact)
61
50
  return if @running
@@ -755,8 +744,14 @@ module Aidp
755
744
  end
756
745
 
757
746
  def clear_display
758
- # Clear the current line and move cursor to beginning
759
- print "\r" + " " * 80 + "\r"
747
+ # Clear the current line using TTY::Cursor
748
+ print_to_stderr(@cursor.clear_line, @cursor.column(1))
749
+ end
750
+
751
+ # Helper method to print to stderr with flush
752
+ def print_to_stderr(*parts)
753
+ parts.each { |part| $stderr.print part }
754
+ $stderr.flush
760
755
  end
761
756
 
762
757
  def format_duration(seconds)
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+
5
+ module Aidp
6
+ module Harness
7
+ # Executes test and linter commands configured in aidp.yml
8
+ # Returns results with exit status and output
9
+ class TestRunner
10
+ def initialize(project_dir, config)
11
+ @project_dir = project_dir
12
+ @config = config
13
+ end
14
+
15
+ # Run all configured tests
16
+ # Returns: { success: boolean, output: string, failures: array }
17
+ def run_tests
18
+ test_commands = @config.test_commands || []
19
+ return {success: true, output: "", failures: []} if test_commands.empty?
20
+
21
+ results = test_commands.map { |cmd| execute_command(cmd, "test") }
22
+ aggregate_results(results, "Tests")
23
+ end
24
+
25
+ # Run all configured linters
26
+ # Returns: { success: boolean, output: string, failures: array }
27
+ def run_linters
28
+ lint_commands = @config.lint_commands || []
29
+ return {success: true, output: "", failures: []} if lint_commands.empty?
30
+
31
+ results = lint_commands.map { |cmd| execute_command(cmd, "linter") }
32
+ aggregate_results(results, "Linters")
33
+ end
34
+
35
+ private
36
+
37
+ def execute_command(command, type)
38
+ stdout, stderr, status = Open3.capture3(command, chdir: @project_dir)
39
+
40
+ {
41
+ command: command,
42
+ type: type,
43
+ success: status.success?,
44
+ exit_code: status.exitstatus,
45
+ stdout: stdout,
46
+ stderr: stderr
47
+ }
48
+ end
49
+
50
+ def aggregate_results(results, category)
51
+ failures = results.reject { |r| r[:success] }
52
+ success = failures.empty?
53
+
54
+ output = if success
55
+ "#{category}: All passed"
56
+ else
57
+ format_failures(failures, category)
58
+ end
59
+
60
+ {
61
+ success: success,
62
+ output: output,
63
+ failures: failures
64
+ }
65
+ end
66
+
67
+ def format_failures(failures, category)
68
+ output = ["#{category} Failures:", ""]
69
+
70
+ failures.each do |failure|
71
+ output << "Command: #{failure[:command]}"
72
+ output << "Exit Code: #{failure[:exit_code]}"
73
+ output << "--- Output ---"
74
+ output << failure[:stdout] unless failure[:stdout].strip.empty?
75
+ output << failure[:stderr] unless failure[:stderr].strip.empty?
76
+ output << ""
77
+ end
78
+
79
+ output.join("\n")
80
+ end
81
+ end
82
+ end
83
+ end
@@ -15,7 +15,9 @@ module Aidp
15
15
  # Enhanced TUI system using TTY libraries, inspired by Claude Code and modern LLM agents
16
16
  class EnhancedTUI
17
17
  class TUIError < StandardError; end
18
+
18
19
  class InputError < TUIError; end
20
+
19
21
  class DisplayError < TUIError; end
20
22
 
21
23
  def initialize(prompt: TTY::Prompt.new)
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "enhanced_tui"
4
+ require_relative "../../workflows/selector"
4
5
 
5
6
  module Aidp
6
7
  module Harness
@@ -12,6 +13,7 @@ module Aidp
12
13
  def initialize(tui = nil)
13
14
  @tui = tui || EnhancedTUI.new
14
15
  @user_input = {}
16
+ @workflow_selector = Aidp::Workflows::Selector.new
15
17
  end
16
18
 
17
19
  def select_workflow(harness_mode: false, mode: :analyze)
@@ -29,7 +31,7 @@ module Aidp
29
31
  when :analyze
30
32
  select_analyze_workflow_interactive
31
33
  when :execute
32
- select_execute_workflow_interactive
34
+ select_execute_workflow_interactive_new(mode)
33
35
  else
34
36
  raise ArgumentError, "Unknown mode: #{mode}"
35
37
  end
@@ -65,6 +67,22 @@ module Aidp
65
67
  }
66
68
  end
67
69
 
70
+ def select_execute_workflow_interactive_new(mode)
71
+ # Step 1: Collect project information
72
+ collect_project_info_interactive
73
+
74
+ # Step 2: Use new workflow selector
75
+ result = @workflow_selector.select_workflow(mode)
76
+
77
+ {
78
+ workflow_type: result[:workflow_key],
79
+ steps: result[:steps],
80
+ user_input: @user_input,
81
+ workflow: result[:workflow]
82
+ }
83
+ end
84
+
85
+ # Legacy method - kept for backward compatibility if needed
68
86
  def select_execute_workflow_interactive
69
87
  # Step 1: Collect project information
70
88
  collect_project_info_interactive
@@ -160,10 +178,10 @@ module Aidp
160
178
 
161
179
  def generate_exploration_steps
162
180
  [
163
- "00_PRD", # Generate PRD from user input (no manual gate)
181
+ "00_PRD", # Generate PRD from user input (no manual gate)
164
182
  "10_TESTING_STRATEGY", # Ensure we have tests
165
- "11_STATIC_ANALYSIS", # Code quality
166
- "16_IMPLEMENTATION" # Special step for actual development work
183
+ "11_STATIC_ANALYSIS", # Code quality
184
+ "16_IMPLEMENTATION" # Special step for actual development work
167
185
  ]
168
186
  end
169
187
 
@@ -9,9 +9,13 @@ module Aidp
9
9
  # Centralized error handling for UI components
10
10
  class ErrorHandler
11
11
  class UIError < StandardError; end
12
+
12
13
  class ComponentError < UIError; end
14
+
13
15
  class ValidationError < UIError; end
16
+
14
17
  class DisplayError < UIError; end
18
+
15
19
  class InteractionError < UIError; end
16
20
 
17
21
  def initialize(ui_components = {})
@@ -9,7 +9,9 @@ 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 = {})
@@ -56,7 +58,7 @@ module Aidp
56
58
  @frame_stats[:status_counts][frame_data[:status]] += 1
57
59
  end
58
60
 
59
- if block_given?
61
+ if block
60
62
  content = yield
61
63
  display_message(@frame.frame(formatted_title, content, width: 80))
62
64
  else
@@ -77,7 +79,7 @@ module Aidp
77
79
  @frame_stack.push({type: frame_type, title: title, data: frame_data})
78
80
 
79
81
  frame_result = @frame.frame(formatted_title, width: 80) do
80
- if block_given?
82
+ if block
81
83
  yield || ""
82
84
  else
83
85
  ""
@@ -200,7 +202,7 @@ module Aidp
200
202
  def frame_with_block(frame_type, title, frame_data = nil, &block)
201
203
  validate_frame_type(frame_type)
202
204
  validate_title(title)
203
- raise ArgumentError, "Block required for frame_with_block" unless block_given?
205
+ raise ArgumentError, "Block required for frame_with_block" unless block
204
206
 
205
207
  formatted_title = @formatter ? @formatter.format_frame_title(frame_type, title, frame_data) : title.to_s
206
208
  @frame_open = true
@@ -220,7 +222,7 @@ module Aidp
220
222
  rescue => e
221
223
  @frame_open = false
222
224
  @frame_stack.pop unless @frame_stack.empty?
223
- raise e # Re-raise the original exception
225
+ raise e # Re-raise the original exception
224
226
  end
225
227
  end
226
228
 
@@ -237,7 +239,7 @@ module Aidp
237
239
  validate_title(title)
238
240
 
239
241
  formatted_title = @formatter ? @formatter.format_section_title(title) : "📋 #{title}"
240
- if block_given?
242
+ if block
241
243
  content = yield
242
244
  display_message(@frame.frame(formatted_title, content, width: 80))
243
245
  else
@@ -252,7 +254,7 @@ module Aidp
252
254
 
253
255
  formatted_title = @formatter ? @formatter.format_subsection_title(title) : "📝 #{title}"
254
256
  display_message(@frame.frame(formatted_title, width: 80) do
255
- yield if block_given?
257
+ yield if block
256
258
  end)
257
259
  rescue => e
258
260
  raise DisplayError, "Failed to create subsection: #{e.message}"
@@ -263,7 +265,7 @@ module Aidp
263
265
 
264
266
  formatted_title = @formatter ? @formatter.format_workflow_title(workflow_name) : "⚙️ #{workflow_name}"
265
267
  display_message(@frame.frame(formatted_title, width: 80) do
266
- yield if block_given?
268
+ yield if block
267
269
  end)
268
270
  rescue => e
269
271
  raise DisplayError, "Failed to create workflow frame: #{e.message}"
@@ -274,7 +276,7 @@ module Aidp
274
276
 
275
277
  formatted_title = @formatter ? @formatter.format_step_title(step_name, step_number, total_steps) : "🔧 #{step_name} (#{step_number}/#{total_steps})"
276
278
  display_message(@frame.frame(formatted_title, width: 80) do
277
- yield if block_given?
279
+ yield if block
278
280
  end)
279
281
  rescue => e
280
282
  raise DisplayError, "Failed to create step frame: #{e.message}"
@@ -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)
@@ -27,7 +29,7 @@ module Aidp
27
29
  @menu_items = []
28
30
  @current_level = 0
29
31
  @breadcrumb = []
30
- @navigation_history = [] # Track all navigation actions
32
+ @navigation_history = [] # Track all navigation actions
31
33
  end
32
34
 
33
35
  def add_menu_item(item)
@@ -113,7 +115,7 @@ module Aidp
113
115
  @menu_items = menu_items
114
116
  display_menu_items
115
117
 
116
- max_attempts = 10 # Prevent infinite loops
118
+ max_attempts = 10 # Prevent infinite loops
117
119
  attempts = 0
118
120
 
119
121
  loop do
@@ -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 = {
@@ -10,8 +10,12 @@ module Aidp
10
10
  module UI
11
11
  # Handles progress display using CLI UI progress bars
12
12
  class ProgressDisplay < Base
13
+ include Aidp::MessageDisplay
14
+
13
15
  class ProgressError < StandardError; end
16
+
14
17
  class InvalidProgressError < ProgressError; end
18
+
15
19
  class DisplayError < ProgressError; end
16
20
 
17
21
  attr_reader :refresh_interval
@@ -178,7 +182,7 @@ module Aidp
178
182
 
179
183
  def execute_progress_steps(bar, total_steps, &block)
180
184
  total_steps.times do
181
- yield(bar) if block_given?
185
+ yield(bar) if block
182
186
  bar.tick
183
187
  end
184
188
  end
@@ -188,7 +192,7 @@ module Aidp
188
192
  total_substeps.times do |index|
189
193
  substep_title = @formatter.format_substep_title(title, index + 1, total_substeps)
190
194
  bar.update_title(substep_title)
191
- yield(bar, index) if block_given?
195
+ yield(bar, index) if block
192
196
  bar.tick
193
197
  end
194
198
  end
@@ -256,18 +260,10 @@ module Aidp
256
260
 
257
261
  private
258
262
 
263
+ # Use mixin display_message; fallback to stdout if no prompt
259
264
  def display_message(message, type: :info)
260
265
  if @prompt
261
- color = case type
262
- when :error then :red
263
- when :success then :green
264
- when :warning then :yellow
265
- when :info then :blue
266
- when :highlight then :cyan
267
- when :muted then :bright_black
268
- else :white
269
- end
270
- @prompt.say(message, color: color)
266
+ super
271
267
  elsif @output
272
268
  @output.puts(message)
273
269
  else
@@ -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}")
@@ -10,7 +10,9 @@ module Aidp
10
10
  # Handles status display using CLI UI spinners
11
11
  class StatusWidget < Base
12
12
  class StatusError < StandardError; end
13
+
13
14
  class InvalidStatusError < StatusError; end
15
+
14
16
  class DisplayError < StatusError; end
15
17
 
16
18
  def initialize(ui_components = {})
@@ -29,7 +31,7 @@ module Aidp
29
31
 
30
32
  formatted_message = @formatter.format_status_message(message)
31
33
  @spinner.spin(formatted_message) do |spinner|
32
- yield(spinner) if block_given?
34
+ yield(spinner) if block
33
35
  end
34
36
  rescue => e
35
37
  raise DisplayError, "Failed to show status: #{e.message}"
@@ -11,7 +11,9 @@ module Aidp
11
11
  # Workflow control interface for pause/resume/cancel/stop
12
12
  class WorkflowController < Base
13
13
  class WorkflowError < StandardError; end
14
+
14
15
  class InvalidStateError < WorkflowError; end
16
+
15
17
  class ControlError < WorkflowError; end
16
18
 
17
19
  WORKFLOW_STATES = {
@@ -1,15 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "tty-prompt"
4
+ require "tty-cursor"
4
5
 
5
6
  module Aidp
6
7
  module Harness
7
8
  # Handles user interaction and feedback collection
8
9
  class UserInterface
10
+ include Aidp::MessageDisplay
11
+
9
12
  def initialize(prompt: TTY::Prompt.new)
10
13
  @input_history = []
11
14
  @file_selection_enabled = false
12
15
  @prompt = prompt
16
+ @cursor = TTY::Cursor
13
17
  @control_mutex = Mutex.new
14
18
  @pause_requested = false
15
19
  @stop_requested = false
@@ -20,21 +24,6 @@ module Aidp
20
24
 
21
25
  private
22
26
 
23
- # Helper method for consistent message display using TTY::Prompt
24
- def display_message(message, type: :info)
25
- color = case type
26
- when :error then :red
27
- when :success then :green
28
- when :warning then :yellow
29
- when :info then :blue
30
- when :highlight then :cyan
31
- when :muted then :bright_black
32
- else :white
33
- end
34
-
35
- @prompt.say(message, color: color)
36
- end
37
-
38
27
  # Helper method to handle input consistently with TTY::Prompt
39
28
  # Fixed to avoid keystroke loss issues with TTY::Prompt's required validation
40
29
  def get_input_with_prompt(message, required: false, default: nil)
@@ -1835,12 +1824,18 @@ module Aidp
1835
1824
 
1836
1825
  # Display progress message
1837
1826
  def show_progress(message)
1838
- print "\r#{message}".ljust(80)
1827
+ print_to_stderr(@cursor.clear_line, @cursor.column(1), message)
1839
1828
  end
1840
1829
 
1841
1830
  # Clear progress message
1842
1831
  def clear_progress
1843
- print "\r" + " " * 80 + "\r"
1832
+ print_to_stderr(@cursor.clear_line, @cursor.column(1))
1833
+ end
1834
+
1835
+ # Helper method to print to stderr with flush
1836
+ def print_to_stderr(*parts)
1837
+ parts.each { |part| $stderr.print part }
1838
+ $stderr.flush
1844
1839
  end
1845
1840
 
1846
1841
  # Get input history