aidp 0.7.0 → 0.8.1

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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +60 -214
  3. data/bin/aidp +1 -1
  4. data/lib/aidp/analysis/kb_inspector.rb +38 -23
  5. data/lib/aidp/analysis/seams.rb +2 -31
  6. data/lib/aidp/analysis/tree_sitter_grammar_loader.rb +1 -13
  7. data/lib/aidp/analysis/tree_sitter_scan.rb +3 -20
  8. data/lib/aidp/analyze/error_handler.rb +2 -75
  9. data/lib/aidp/analyze/json_file_storage.rb +292 -0
  10. data/lib/aidp/analyze/progress.rb +12 -0
  11. data/lib/aidp/analyze/progress_visualizer.rb +12 -17
  12. data/lib/aidp/analyze/ruby_maat_integration.rb +13 -31
  13. data/lib/aidp/analyze/runner.rb +256 -87
  14. data/lib/aidp/cli/jobs_command.rb +100 -432
  15. data/lib/aidp/cli.rb +309 -239
  16. data/lib/aidp/config.rb +298 -10
  17. data/lib/aidp/debug_logger.rb +195 -0
  18. data/lib/aidp/debug_mixin.rb +187 -0
  19. data/lib/aidp/execute/progress.rb +9 -0
  20. data/lib/aidp/execute/runner.rb +221 -40
  21. data/lib/aidp/execute/steps.rb +17 -7
  22. data/lib/aidp/execute/workflow_selector.rb +211 -0
  23. data/lib/aidp/harness/completion_checker.rb +268 -0
  24. data/lib/aidp/harness/condition_detector.rb +1526 -0
  25. data/lib/aidp/harness/config_loader.rb +373 -0
  26. data/lib/aidp/harness/config_manager.rb +382 -0
  27. data/lib/aidp/harness/config_schema.rb +1006 -0
  28. data/lib/aidp/harness/config_validator.rb +355 -0
  29. data/lib/aidp/harness/configuration.rb +477 -0
  30. data/lib/aidp/harness/enhanced_runner.rb +494 -0
  31. data/lib/aidp/harness/error_handler.rb +616 -0
  32. data/lib/aidp/harness/provider_config.rb +423 -0
  33. data/lib/aidp/harness/provider_factory.rb +306 -0
  34. data/lib/aidp/harness/provider_manager.rb +1269 -0
  35. data/lib/aidp/harness/provider_type_checker.rb +88 -0
  36. data/lib/aidp/harness/runner.rb +411 -0
  37. data/lib/aidp/harness/state/errors.rb +28 -0
  38. data/lib/aidp/harness/state/metrics.rb +219 -0
  39. data/lib/aidp/harness/state/persistence.rb +128 -0
  40. data/lib/aidp/harness/state/provider_state.rb +132 -0
  41. data/lib/aidp/harness/state/ui_state.rb +68 -0
  42. data/lib/aidp/harness/state/workflow_state.rb +123 -0
  43. data/lib/aidp/harness/state_manager.rb +586 -0
  44. data/lib/aidp/harness/status_display.rb +888 -0
  45. data/lib/aidp/harness/ui/base.rb +16 -0
  46. data/lib/aidp/harness/ui/enhanced_tui.rb +545 -0
  47. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +252 -0
  48. data/lib/aidp/harness/ui/error_handler.rb +132 -0
  49. data/lib/aidp/harness/ui/frame_manager.rb +361 -0
  50. data/lib/aidp/harness/ui/job_monitor.rb +500 -0
  51. data/lib/aidp/harness/ui/navigation/main_menu.rb +311 -0
  52. data/lib/aidp/harness/ui/navigation/menu_formatter.rb +120 -0
  53. data/lib/aidp/harness/ui/navigation/menu_item.rb +142 -0
  54. data/lib/aidp/harness/ui/navigation/menu_state.rb +139 -0
  55. data/lib/aidp/harness/ui/navigation/submenu.rb +202 -0
  56. data/lib/aidp/harness/ui/navigation/workflow_selector.rb +176 -0
  57. data/lib/aidp/harness/ui/progress_display.rb +280 -0
  58. data/lib/aidp/harness/ui/question_collector.rb +141 -0
  59. data/lib/aidp/harness/ui/spinner_group.rb +184 -0
  60. data/lib/aidp/harness/ui/spinner_helper.rb +152 -0
  61. data/lib/aidp/harness/ui/status_manager.rb +312 -0
  62. data/lib/aidp/harness/ui/status_widget.rb +280 -0
  63. data/lib/aidp/harness/ui/workflow_controller.rb +312 -0
  64. data/lib/aidp/harness/user_interface.rb +2381 -0
  65. data/lib/aidp/provider_manager.rb +131 -7
  66. data/lib/aidp/providers/anthropic.rb +28 -103
  67. data/lib/aidp/providers/base.rb +170 -0
  68. data/lib/aidp/providers/cursor.rb +52 -181
  69. data/lib/aidp/providers/gemini.rb +24 -107
  70. data/lib/aidp/providers/macos_ui.rb +99 -5
  71. data/lib/aidp/providers/opencode.rb +194 -0
  72. data/lib/aidp/storage/csv_storage.rb +172 -0
  73. data/lib/aidp/storage/file_manager.rb +214 -0
  74. data/lib/aidp/storage/json_storage.rb +140 -0
  75. data/lib/aidp/version.rb +1 -1
  76. data/lib/aidp.rb +54 -39
  77. data/templates/COMMON/AGENT_BASE.md +11 -0
  78. data/templates/EXECUTE/00_PRD.md +4 -4
  79. data/templates/EXECUTE/02_ARCHITECTURE.md +5 -4
  80. data/templates/EXECUTE/07_TEST_PLAN.md +4 -1
  81. data/templates/EXECUTE/08_TASKS.md +4 -4
  82. data/templates/EXECUTE/10_IMPLEMENTATION_AGENT.md +4 -4
  83. data/templates/README.md +279 -0
  84. data/templates/aidp-development.yml.example +373 -0
  85. data/templates/aidp-minimal.yml.example +48 -0
  86. data/templates/aidp-production.yml.example +475 -0
  87. data/templates/aidp.yml.example +598 -0
  88. metadata +93 -69
  89. data/lib/aidp/analyze/agent_personas.rb +0 -71
  90. data/lib/aidp/analyze/agent_tool_executor.rb +0 -439
  91. data/lib/aidp/analyze/data_retention_manager.rb +0 -421
  92. data/lib/aidp/analyze/database.rb +0 -260
  93. data/lib/aidp/analyze/dependencies.rb +0 -335
  94. data/lib/aidp/analyze/export_manager.rb +0 -418
  95. data/lib/aidp/analyze/focus_guidance.rb +0 -517
  96. data/lib/aidp/analyze/incremental_analyzer.rb +0 -533
  97. data/lib/aidp/analyze/language_analysis_strategies.rb +0 -897
  98. data/lib/aidp/analyze/large_analysis_progress.rb +0 -499
  99. data/lib/aidp/analyze/memory_manager.rb +0 -339
  100. data/lib/aidp/analyze/metrics_storage.rb +0 -336
  101. data/lib/aidp/analyze/parallel_processor.rb +0 -454
  102. data/lib/aidp/analyze/performance_optimizer.rb +0 -691
  103. data/lib/aidp/analyze/repository_chunker.rb +0 -697
  104. data/lib/aidp/analyze/static_analysis_detector.rb +0 -577
  105. data/lib/aidp/analyze/storage.rb +0 -655
  106. data/lib/aidp/analyze/tool_configuration.rb +0 -441
  107. data/lib/aidp/analyze/tool_modernization.rb +0 -750
  108. data/lib/aidp/database/pg_adapter.rb +0 -148
  109. data/lib/aidp/database_config.rb +0 -69
  110. data/lib/aidp/database_connection.rb +0 -72
  111. data/lib/aidp/job_manager.rb +0 -41
  112. data/lib/aidp/jobs/base_job.rb +0 -45
  113. data/lib/aidp/jobs/provider_execution_job.rb +0 -83
  114. data/lib/aidp/project_detector.rb +0 -117
  115. data/lib/aidp/providers/agent_supervisor.rb +0 -348
  116. data/lib/aidp/providers/supervised_base.rb +0 -317
  117. data/lib/aidp/providers/supervised_cursor.rb +0 -22
  118. data/lib/aidp/sync.rb +0 -13
  119. data/lib/aidp/workspace.rb +0 -19
@@ -0,0 +1,494 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ui/enhanced_tui"
4
+ require_relative "ui/enhanced_workflow_selector"
5
+ require_relative "ui/job_monitor"
6
+ require_relative "ui/workflow_controller"
7
+ require_relative "ui/progress_display"
8
+ require_relative "ui/status_widget"
9
+
10
+ module Aidp
11
+ module Harness
12
+ # Enhanced harness runner with modern TTY-based TUI
13
+ class EnhancedRunner
14
+ STATES = {
15
+ idle: "idle",
16
+ running: "running",
17
+ paused: "paused",
18
+ waiting_for_user: "waiting_for_user",
19
+ waiting_for_rate_limit: "waiting_for_rate_limit",
20
+ stopped: "stopped",
21
+ completed: "completed",
22
+ error: "error"
23
+ }.freeze
24
+
25
+ def initialize(project_dir, mode = :analyze, options = {})
26
+ @project_dir = project_dir
27
+ @mode = mode.to_sym
28
+ @options = options
29
+ @state = STATES[:idle]
30
+ @start_time = nil
31
+ @current_step = nil
32
+ @current_provider = nil
33
+ @user_input = options[:user_input] || {}
34
+ @user_input = {} if @user_input.nil? # Ensure it's never nil
35
+ @execution_log = []
36
+
37
+ # Store workflow configuration
38
+ @selected_steps = options[:selected_steps] || []
39
+ @workflow_type = options[:workflow_type] || :default
40
+
41
+ # Initialize enhanced TUI components
42
+ @tui = UI::EnhancedTUI.new
43
+ @workflow_selector = UI::EnhancedWorkflowSelector.new(@tui)
44
+ @job_monitor = UI::JobMonitor.new
45
+ @workflow_controller = UI::WorkflowController.new
46
+ @progress_display = UI::ProgressDisplay.new
47
+ @status_widget = UI::StatusWidget.new
48
+
49
+ # Initialize other components
50
+ @configuration = Configuration.new(project_dir)
51
+ @state_manager = StateManager.new(project_dir, @mode)
52
+ @condition_detector = ConditionDetector.new
53
+ @provider_manager = ProviderManager.new(@configuration)
54
+ @error_handler = ErrorHandler.new(@provider_manager, @configuration)
55
+ @completion_checker = CompletionChecker.new(@project_dir, @workflow_type)
56
+ end
57
+
58
+ # Main execution method with enhanced TUI
59
+ def run
60
+ @state = STATES[:running]
61
+ @start_time = Time.now
62
+
63
+ @tui.show_message("🚀 Starting #{@mode.to_s.capitalize} Mode", :info)
64
+
65
+ begin
66
+ # Start TUI display loop
67
+ @tui.start_display_loop
68
+
69
+ # Load existing state if resuming
70
+ # Temporarily disabled to test
71
+ # load_state if @state_manager.has_state?
72
+
73
+ # Get the appropriate runner for the mode
74
+ runner = get_mode_runner
75
+
76
+ # Register main workflow job
77
+ register_workflow_job
78
+
79
+ # Show initial workflow status
80
+ show_workflow_status(runner)
81
+
82
+ # Show mode-specific feedback
83
+ show_mode_specific_feedback
84
+
85
+ # Main execution loop
86
+ loop do
87
+ break if should_stop?
88
+
89
+ # Check for pause conditions
90
+ if should_pause?
91
+ handle_pause_condition
92
+ next
93
+ end
94
+
95
+ # Get next step to execute with spinner
96
+ next_step = show_step_spinner("Finding next step to execute...") do
97
+ get_next_step(runner)
98
+ end
99
+ break unless next_step
100
+
101
+ # Execute the step with enhanced TUI integration
102
+ execute_step_with_enhanced_tui(runner, next_step)
103
+
104
+ # Update state
105
+ update_state
106
+ end
107
+
108
+ # Mark workflow as completed
109
+ complete_workflow_job
110
+
111
+ # Check completion criteria
112
+ if all_steps_completed?(runner)
113
+ completion_status = @completion_checker.completion_status
114
+ if completion_status[:all_complete]
115
+ @state = STATES[:completed]
116
+ @workflow_controller.complete_workflow("All steps completed successfully")
117
+ @tui.show_message("🎉 Harness completed successfully - all criteria met", :success)
118
+ else
119
+ @tui.show_message("⚠️ Steps completed but completion criteria not met", :warning)
120
+ handle_completion_criteria_not_met(completion_status)
121
+ end
122
+ end
123
+ rescue => e
124
+ @state = STATES[:error]
125
+ # Single error message - don't duplicate
126
+ @tui.show_message("❌ Error: #{e.message}", :error)
127
+ ensure
128
+ # Save state before exiting
129
+ save_state
130
+ @tui.stop_display_loop
131
+ cleanup
132
+ end
133
+
134
+ {status: @state, message: get_completion_message}
135
+ end
136
+
137
+ # Enhanced step execution with TUI integration
138
+ def execute_step_with_enhanced_tui(runner, step_name)
139
+ @current_step = step_name
140
+ @tui.show_message("🔄 Executing step: #{step_name}", :info)
141
+
142
+ # Register step as a job
143
+ step_job_id = "step_#{step_name}"
144
+ step_job_data = {
145
+ name: step_name,
146
+ status: :running,
147
+ progress: 0,
148
+ provider: @current_provider || "unknown",
149
+ message: "Starting execution..."
150
+ }
151
+ @tui.add_job(step_job_id, step_job_data)
152
+
153
+ # Show step execution display
154
+ @tui.show_step_execution(step_name, :starting, {provider: @current_provider})
155
+
156
+ # Mark step as in progress
157
+ runner.mark_step_in_progress(step_name)
158
+
159
+ # Get current provider
160
+ @current_provider = @provider_manager.current_provider
161
+
162
+ # Execute the step with error handling and spinner
163
+ start_time = Time.now
164
+
165
+ # Show spinner while executing the step
166
+ spinner_message = "Executing #{step_name}..."
167
+ result = show_step_spinner(spinner_message) do
168
+ @error_handler.execute_with_retry do
169
+ step_options = @options.merge(user_input: @user_input)
170
+ runner.run_step(step_name, step_options)
171
+ end
172
+ end
173
+ duration = Time.now - start_time
174
+
175
+ # Update step job status
176
+ if result && result[:status] == "completed"
177
+ @tui.update_job(step_job_id, {
178
+ status: :completed,
179
+ progress: 100,
180
+ message: "Completed successfully"
181
+ })
182
+ @tui.show_step_execution(step_name, :completed, {duration: duration})
183
+ runner.mark_step_completed(step_name)
184
+ else
185
+ @tui.update_job(step_job_id, {
186
+ status: :failed,
187
+ message: result&.dig(:error) || "Step execution failed"
188
+ })
189
+ @tui.show_step_execution(step_name, :failed, {
190
+ error: result&.dig(:error) || "Unknown error"
191
+ })
192
+ end
193
+
194
+ # Check for conditions that require user interaction
195
+ if @condition_detector.needs_user_feedback?(result)
196
+ handle_user_feedback_request_with_tui(result)
197
+ end
198
+
199
+ # Check for rate limiting
200
+ if @condition_detector.is_rate_limited?(result)
201
+ handle_rate_limit(result)
202
+ end
203
+
204
+ # Remove job after a delay to show completion
205
+ Thread.new do
206
+ sleep 2
207
+ @tui.remove_job(step_job_id)
208
+ end
209
+
210
+ result
211
+ end
212
+
213
+ # Enhanced user feedback handling
214
+ def handle_user_feedback_request_with_tui(result)
215
+ @state = STATES[:waiting_for_user]
216
+ @workflow_controller.pause_workflow("Waiting for user feedback")
217
+ @tui.show_message("⏸️ Waiting for user feedback", :warning)
218
+
219
+ # Extract questions from result
220
+ questions = @condition_detector.extract_questions(result)
221
+
222
+ # Show input area
223
+ @tui.show_input_area("Please provide feedback:")
224
+
225
+ # Collect user input using enhanced TUI
226
+ user_responses = {}
227
+ questions.each_with_index do |question_data, index|
228
+ question_number = question_data[:number] || (index + 1)
229
+ prompt = "Question #{question_number}: #{question_data[:question]}"
230
+
231
+ response = @tui.get_user_input(prompt)
232
+ user_responses["question_#{question_number}"] = response
233
+ end
234
+
235
+ # Store user input
236
+ @user_input.merge!(user_responses)
237
+ user_responses.each do |key, value|
238
+ @state_manager.add_user_input(key, value)
239
+ end
240
+
241
+ @state = STATES[:running]
242
+ @workflow_controller.resume_workflow("User feedback collected")
243
+ @tui.show_message("✅ User feedback collected", :success)
244
+ end
245
+
246
+ # Enhanced workflow status display
247
+ def show_workflow_status(runner)
248
+ workflow_data = {
249
+ workflow_type: @workflow_type,
250
+ steps: @selected_steps || runner.all_steps,
251
+ completed_steps: runner.progress.completed_steps.size,
252
+ current_step: runner.progress.current_step,
253
+ progress_percentage: calculate_progress_percentage(runner)
254
+ }
255
+
256
+ @tui.show_workflow_status(workflow_data)
257
+ end
258
+
259
+ # Job monitoring integration
260
+ def register_workflow_job
261
+ job_data = {
262
+ name: "Main Workflow",
263
+ status: :running,
264
+ progress: 0,
265
+ provider: @current_provider || "unknown",
266
+ message: "Starting workflow execution..."
267
+ }
268
+
269
+ @tui.add_job("main_workflow", job_data)
270
+ end
271
+
272
+ def complete_workflow_job
273
+ @tui.update_job("main_workflow", {
274
+ status: :completed,
275
+ progress: 100,
276
+ message: "Workflow completed"
277
+ })
278
+ end
279
+
280
+ # Control methods
281
+
282
+ def stop
283
+ @state = STATES[:stopped]
284
+ @workflow_controller.stop_workflow("User requested stop")
285
+ @tui.show_message("⏹️ Harness stopped by user", :warning)
286
+ end
287
+
288
+ # Status methods
289
+ def status
290
+ {
291
+ state: @state,
292
+ mode: @mode,
293
+ current_step: @current_step,
294
+ current_provider: @current_provider,
295
+ start_time: @start_time,
296
+ duration: @start_time ? Time.now - @start_time : 0,
297
+ user_input_count: @user_input.size,
298
+ execution_log_count: @execution_log.size,
299
+ jobs_count: @tui.instance_variable_get(:@jobs).size
300
+ }
301
+ end
302
+
303
+ private
304
+
305
+ def show_step_spinner(message)
306
+ # Use the unified spinner helper
307
+ Aidp::Harness::UI.with_processing_spinner(message) do
308
+ yield
309
+ end
310
+ end
311
+
312
+ def get_mode_runner
313
+ case @mode
314
+ when :analyze
315
+ Aidp::Analyze::Runner.new(@project_dir, self)
316
+ when :execute
317
+ Aidp::Execute::Runner.new(@project_dir, self)
318
+ else
319
+ raise ArgumentError, "Unsupported mode: #{@mode}"
320
+ end
321
+ end
322
+
323
+ def get_next_step(runner)
324
+ runner.next_step
325
+ end
326
+
327
+ def should_stop?
328
+ @state == STATES[:stopped] ||
329
+ @state == STATES[:completed] ||
330
+ @state == STATES[:error]
331
+ end
332
+
333
+ def should_pause?
334
+ @state == STATES[:paused] ||
335
+ @state == STATES[:waiting_for_user] ||
336
+ @state == STATES[:waiting_for_rate_limit]
337
+ end
338
+
339
+ def handle_pause_condition
340
+ case @state
341
+ when STATES[:paused]
342
+ sleep(1)
343
+ when STATES[:waiting_for_user]
344
+ # User interface handles this
345
+ nil
346
+ when STATES[:waiting_for_rate_limit]
347
+ # Rate limit handling
348
+ nil
349
+ end
350
+ end
351
+
352
+ def all_steps_completed?(runner)
353
+ runner.all_steps_completed?
354
+ end
355
+
356
+ def calculate_progress_percentage(runner)
357
+ total_steps = runner.all_steps.size
358
+ completed_steps = runner.progress.completed_steps.size
359
+ return 0 if total_steps == 0
360
+ (completed_steps.to_f / total_steps * 100).round(2)
361
+ end
362
+
363
+ def update_state
364
+ @state_manager.update_state({
365
+ state: @state,
366
+ current_step: @current_step,
367
+ current_provider: @current_provider,
368
+ user_input: @user_input,
369
+ last_updated: Time.now
370
+ })
371
+ end
372
+
373
+ def load_state
374
+ if @state_manager.has_state?
375
+ state = @state_manager.load_state
376
+ # Ensure state is not nil before accessing it
377
+ if state.is_a?(Hash)
378
+ @user_input.merge!(state[:user_input] || {})
379
+ end
380
+ end
381
+ end
382
+
383
+ def save_state
384
+ @state_manager.save_state({
385
+ state: @state,
386
+ current_step: @current_step,
387
+ current_provider: @current_provider,
388
+ user_input: @user_input,
389
+ last_updated: Time.now
390
+ })
391
+ end
392
+
393
+ def show_mode_specific_feedback
394
+ case @mode
395
+ when :analyze
396
+ @tui.show_message("🔬 Starting codebase analysis...", :info)
397
+ @tui.show_message("Press 'j' to view background jobs, 'h' for help", :info)
398
+ when :execute
399
+ @tui.show_message("🏗️ Starting development workflow...", :info)
400
+ @tui.show_message("Press 'j' to view background jobs, 'h' for help", :info)
401
+ end
402
+ end
403
+
404
+ def handle_error(error)
405
+ # Single comprehensive error report
406
+ @tui.show_message("❌ Harness error: #{error.message}", :error)
407
+ @tui.show_message("Error type: #{error.class.name}", :error)
408
+
409
+ # Log error details for debugging
410
+ @execution_log << {
411
+ timestamp: Time.now,
412
+ level: :error,
413
+ message: error.message,
414
+ backtrace: error.backtrace&.first(5)
415
+ }
416
+
417
+ # Show backtrace in debug mode only
418
+ if ENV["DEBUG"]
419
+ @tui.show_message("Backtrace: #{error.backtrace&.first(3)&.join("\n")}", :error)
420
+ end
421
+ end
422
+
423
+ def handle_completion_criteria_not_met(completion_status)
424
+ @tui.show_message("Completion criteria not met: #{completion_status[:summary]}", :warning)
425
+
426
+ if @tui.get_confirmation("Continue anyway? This may indicate issues that should be addressed.", default: false)
427
+ @state = STATES[:completed]
428
+ @workflow_controller.complete_workflow("Completed with user override")
429
+ @tui.show_message("✅ Harness completed with user override", :success)
430
+ else
431
+ @state = STATES[:error]
432
+ @workflow_controller.stop_workflow("Completion criteria not met")
433
+ @tui.show_message("❌ Harness stopped due to unmet completion criteria", :error)
434
+ end
435
+ end
436
+
437
+ def handle_rate_limit(_result)
438
+ @state = STATES[:waiting_for_rate_limit]
439
+ @tui.show_message("⏳ Rate limit detected, switching provider", :warning)
440
+
441
+ @provider_manager.mark_rate_limited(@current_provider)
442
+ next_provider = @provider_manager.switch_provider
443
+ @current_provider = next_provider
444
+
445
+ if next_provider
446
+ @state = STATES[:running]
447
+ @tui.show_message("🔄 Switched to provider: #{next_provider}", :info)
448
+ else
449
+ wait_for_rate_limit_reset
450
+ end
451
+ end
452
+
453
+ def wait_for_rate_limit_reset
454
+ reset_time = @provider_manager.next_reset_time
455
+ if reset_time
456
+ @tui.show_message("⏰ Waiting for rate limit reset at #{reset_time}", :warning)
457
+ sleep_until_reset(reset_time)
458
+ @state = STATES[:running]
459
+ else
460
+ @state = STATES[:error]
461
+ raise "All providers rate limited with no reset time available"
462
+ end
463
+ end
464
+
465
+ def sleep_until_reset(reset_time)
466
+ while Time.now < reset_time && @state == STATES[:waiting_for_rate_limit]
467
+ remaining = reset_time - Time.now
468
+ @tui.show_message("⏳ Rate limit reset in #{remaining.to_i} seconds", :info)
469
+ sleep(1)
470
+ end
471
+ end
472
+
473
+ def cleanup
474
+ # Cleanup any remaining jobs
475
+ @tui.instance_variable_get(:@jobs).keys.each do |job_id|
476
+ @tui.remove_job(job_id)
477
+ end
478
+ end
479
+
480
+ def get_completion_message
481
+ case @state
482
+ when STATES[:completed]
483
+ "Harness completed successfully"
484
+ when STATES[:stopped]
485
+ "Harness stopped by user"
486
+ when STATES[:error]
487
+ "Harness encountered an error"
488
+ else
489
+ "Harness finished"
490
+ end
491
+ end
492
+ end
493
+ end
494
+ end