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.
- checksums.yaml +4 -4
- data/README.md +194 -25
- data/lib/aidp/analyze/error_handler.rb +4 -2
- data/lib/aidp/{analysis → analyze}/kb_inspector.rb +93 -89
- data/lib/aidp/analyze/prioritizer.rb +3 -2
- data/lib/aidp/analyze/progress.rb +2 -1
- data/lib/aidp/analyze/ruby_maat_integration.rb +7 -3
- data/lib/aidp/analyze/runner.rb +73 -11
- data/lib/aidp/{analysis → analyze}/seams.rb +1 -1
- data/lib/aidp/analyze/steps.rb +10 -8
- data/lib/aidp/{analysis → analyze}/tree_sitter_grammar_loader.rb +11 -5
- data/lib/aidp/{analysis → analyze}/tree_sitter_scan.rb +21 -15
- data/lib/aidp/cli/checkpoint_command.rb +98 -0
- data/lib/aidp/cli/first_run_wizard.rb +83 -103
- data/lib/aidp/cli/jobs_command.rb +270 -36
- data/lib/aidp/cli/terminal_io.rb +3 -3
- data/lib/aidp/cli.rb +411 -69
- data/lib/aidp/config.rb +5 -8
- data/lib/aidp/debug_logger.rb +4 -4
- data/lib/aidp/debug_mixin.rb +11 -4
- data/lib/aidp/execute/checkpoint.rb +282 -0
- data/lib/aidp/execute/checkpoint_display.rb +221 -0
- data/lib/aidp/execute/progress.rb +2 -1
- data/lib/aidp/execute/prompt_manager.rb +62 -0
- data/lib/aidp/execute/runner.rb +67 -20
- data/lib/aidp/execute/steps.rb +36 -27
- data/lib/aidp/execute/work_loop_runner.rb +308 -0
- data/lib/aidp/execute/workflow_selector.rb +50 -26
- data/lib/aidp/harness/condition_detector.rb +4 -4
- data/lib/aidp/harness/config_schema.rb +40 -0
- data/lib/aidp/harness/config_validator.rb +3 -6
- data/lib/aidp/harness/configuration.rb +35 -1
- data/lib/aidp/harness/enhanced_runner.rb +25 -4
- data/lib/aidp/harness/error_handler.rb +103 -28
- data/lib/aidp/harness/provider_factory.rb +6 -1
- data/lib/aidp/harness/provider_manager.rb +273 -19
- data/lib/aidp/harness/runner.rb +14 -6
- data/lib/aidp/harness/simple_user_interface.rb +6 -4
- data/lib/aidp/harness/status_display.rb +118 -106
- data/lib/aidp/harness/test_runner.rb +83 -0
- data/lib/aidp/harness/ui/enhanced_tui.rb +7 -5
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +22 -4
- data/lib/aidp/harness/ui/error_handler.rb +7 -2
- data/lib/aidp/harness/ui/frame_manager.rb +61 -39
- data/lib/aidp/harness/ui/job_monitor.rb +2 -0
- data/lib/aidp/harness/ui/navigation/main_menu.rb +27 -16
- data/lib/aidp/harness/ui/navigation/menu_item.rb +1 -0
- data/lib/aidp/harness/ui/navigation/menu_state.rb +1 -0
- data/lib/aidp/harness/ui/navigation/submenu.rb +1 -0
- data/lib/aidp/harness/ui/navigation/workflow_selector.rb +2 -0
- data/lib/aidp/harness/ui/progress_display.rb +26 -7
- data/lib/aidp/harness/ui/question_collector.rb +2 -0
- data/lib/aidp/harness/ui/spinner_group.rb +2 -0
- data/lib/aidp/harness/ui/spinner_helper.rb +1 -1
- data/lib/aidp/harness/ui/status_manager.rb +4 -2
- data/lib/aidp/harness/ui/status_widget.rb +20 -9
- data/lib/aidp/harness/ui/workflow_controller.rb +27 -9
- data/lib/aidp/harness/user_interface.rb +338 -330
- data/lib/aidp/jobs/background_runner.rb +278 -0
- data/lib/aidp/message_display.rb +48 -0
- data/lib/aidp/provider_manager.rb +13 -7
- data/lib/aidp/providers/anthropic.rb +101 -18
- data/lib/aidp/providers/base.rb +51 -1
- data/lib/aidp/providers/codex.rb +248 -0
- data/lib/aidp/providers/cursor.rb +39 -48
- data/lib/aidp/providers/gemini.rb +26 -16
- data/lib/aidp/providers/github_copilot.rb +263 -0
- data/lib/aidp/providers/opencode.rb +38 -47
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/workflows/definitions.rb +357 -0
- data/lib/aidp/workflows/selector.rb +171 -0
- data/lib/aidp.rb +16 -4
- data/templates/planning/generate_llm_style_guide.md +119 -0
- metadata +43 -31
- data/lib/aidp/analyze/progress_visualizer.rb +0 -314
- /data/templates/{ANALYZE/02_ARCHITECTURE_ANALYSIS.md → analysis/analyze_architecture.md} +0 -0
- /data/templates/{ANALYZE/05_DOCUMENTATION_ANALYSIS.md → analysis/analyze_documentation.md} +0 -0
- /data/templates/{ANALYZE/04_FUNCTIONALITY_ANALYSIS.md → analysis/analyze_functionality.md} +0 -0
- /data/templates/{ANALYZE/01_REPOSITORY_ANALYSIS.md → analysis/analyze_repository.md} +0 -0
- /data/templates/{ANALYZE/06_STATIC_ANALYSIS.md → analysis/analyze_static_code.md} +0 -0
- /data/templates/{ANALYZE/03_TEST_ANALYSIS.md → analysis/analyze_tests.md} +0 -0
- /data/templates/{ANALYZE/07_REFACTORING_RECOMMENDATIONS.md → analysis/recommend_refactoring.md} +0 -0
- /data/templates/{ANALYZE/06a_tree_sitter_scan.md → analysis/scan_with_tree_sitter.md} +0 -0
- /data/templates/{EXECUTE/11_STATIC_ANALYSIS.md → implementation/configure_static_analysis.md} +0 -0
- /data/templates/{EXECUTE/14_DOCS_PORTAL.md → implementation/create_documentation_portal.md} +0 -0
- /data/templates/{EXECUTE/10_IMPLEMENTATION_AGENT.md → implementation/implement_features.md} +0 -0
- /data/templates/{EXECUTE/13_DELIVERY_ROLLOUT.md → implementation/plan_delivery.md} +0 -0
- /data/templates/{EXECUTE/15_POST_RELEASE.md → implementation/review_post_release.md} +0 -0
- /data/templates/{EXECUTE/09_SCAFFOLDING_DEVEX.md → implementation/setup_scaffolding.md} +0 -0
- /data/templates/{EXECUTE/02A_ARCH_GATE_QUESTIONS.md → planning/ask_architecture_questions.md} +0 -0
- /data/templates/{EXECUTE/00_PRD.md → planning/create_prd.md} +0 -0
- /data/templates/{EXECUTE/08_TASKS.md → planning/create_tasks.md} +0 -0
- /data/templates/{EXECUTE/04_DOMAIN_DECOMPOSITION.md → planning/decompose_domain.md} +0 -0
- /data/templates/{EXECUTE/01_NFRS.md → planning/define_nfrs.md} +0 -0
- /data/templates/{EXECUTE/05_CONTRACTS.md → planning/design_apis.md} +0 -0
- /data/templates/{EXECUTE/02_ARCHITECTURE.md → planning/design_architecture.md} +0 -0
- /data/templates/{EXECUTE/06_THREAT_MODEL.md → planning/design_data_model.md} +0 -0
- /data/templates/{EXECUTE/03_ADR_FACTORY.md → planning/generate_adrs.md} +0 -0
- /data/templates/{EXECUTE/12_OBSERVABILITY_SLOS.md → planning/plan_observability.md} +0 -0
- /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
|
-
|
|
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
|
|
61
|
+
if block
|
|
47
62
|
content = yield
|
|
48
|
-
|
|
63
|
+
display_message(@frame.frame(formatted_title, content, width: 80))
|
|
49
64
|
else
|
|
50
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|
-
|
|
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
|
-
|
|
129
|
-
|
|
150
|
+
display_message("\n📊 Frame Summary")
|
|
151
|
+
display_message("=" * 50)
|
|
130
152
|
|
|
131
153
|
if @frame_stats[:total_frames] == 0
|
|
132
|
-
|
|
154
|
+
display_message("No frames used")
|
|
133
155
|
return
|
|
134
156
|
end
|
|
135
157
|
|
|
136
|
-
|
|
158
|
+
display_message("Total Frames: #{@frame_stats[:total_frames]}")
|
|
137
159
|
|
|
138
160
|
unless @frame_stats[:frame_types].empty?
|
|
139
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
183
|
+
display_message(" #{status_emoji} #{status.to_s.capitalize}: #{count}")
|
|
162
184
|
end
|
|
163
185
|
end
|
|
164
186
|
|
|
165
|
-
|
|
166
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
241
|
+
formatted_title = @formatter ? @formatter.format_section_title(title) : "📋 #{title}"
|
|
242
|
+
if block
|
|
221
243
|
content = yield
|
|
222
|
-
|
|
244
|
+
display_message(@frame.frame(formatted_title, content, width: 80))
|
|
223
245
|
else
|
|
224
|
-
|
|
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
|
-
|
|
235
|
-
yield if
|
|
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
|
-
|
|
246
|
-
yield if
|
|
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
|
-
|
|
257
|
-
yield if
|
|
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 = []
|
|
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
|
-
|
|
75
|
+
display_message "Use arrow keys to navigate, Enter to select"
|
|
73
76
|
else
|
|
74
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
226
|
+
display_message "Home"
|
|
224
227
|
else
|
|
225
228
|
breadcrumb_text = @formatter.format_breadcrumb(@breadcrumb)
|
|
226
|
-
|
|
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
|
-
|
|
247
|
-
|
|
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
|
-
|
|
264
|
+
display_message(formatted_item)
|
|
254
265
|
end
|
|
255
266
|
end
|
|
256
267
|
|
|
@@ -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]
|
|
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
|
-
@
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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}")
|