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
@@ -2,12 +2,16 @@
2
2
 
3
3
  require "json"
4
4
  require "tty-box"
5
+ require "tty-prompt"
5
6
 
6
7
  module Aidp
7
- module Analysis
8
+ module Analyze
8
9
  class KBInspector
9
- def initialize(kb_dir = ".aidp/kb")
10
+ include Aidp::MessageDisplay
11
+
12
+ def initialize(kb_dir = ".aidp/kb", prompt: TTY::Prompt.new)
10
13
  @kb_dir = File.expand_path(kb_dir)
14
+ @prompt = prompt
11
15
  @data = load_kb_data
12
16
  end
13
17
 
@@ -28,8 +32,8 @@ module Aidp
28
32
  when "summary"
29
33
  show_summary(format)
30
34
  else
31
- puts "Unknown KB type: #{type}"
32
- puts "Available types: seams, hotspots, cycles, apis, symbols, imports, summary"
35
+ display_message("Unknown KB type: #{type}", type: :error)
36
+ display_message("Available types: seams, hotspots, cycles, apis, symbols, imports, summary", type: :info)
33
37
  end
34
38
  end
35
39
 
@@ -42,8 +46,8 @@ module Aidp
42
46
  when "cycles"
43
47
  generate_cycle_graph(format, output)
44
48
  else
45
- puts "Unknown graph type: #{type}"
46
- puts "Available types: imports, calls, cycles"
49
+ display_message("Unknown graph type: #{type}", type: :error)
50
+ display_message("Available types: imports, calls, cycles", type: :info)
47
51
  end
48
52
  end
49
53
 
@@ -73,7 +77,7 @@ module Aidp
73
77
  border: :thick,
74
78
  padding: [1, 2]
75
79
  )
76
- puts box
80
+ display_message(box)
77
81
  end
78
82
 
79
83
  def load_kb_data
@@ -87,7 +91,7 @@ module Aidp
87
91
  rescue JSON::ParserError => e
88
92
  # Suppress warnings in test mode to avoid CI failures
89
93
  unless ENV["RACK_ENV"] == "test" || defined?(RSpec)
90
- puts "Warning: Could not parse #{file_path}: #{e.message}"
94
+ display_message("Warning: Could not parse #{file_path}: #{e.message}", type: :warn)
91
95
  end
92
96
  data[type.to_sym] = []
93
97
  end
@@ -100,42 +104,42 @@ module Aidp
100
104
  end
101
105
 
102
106
  def show_summary(_format)
103
- puts "\n📊 Knowledge Base Summary"
104
- puts "=" * 50
105
-
106
- puts "📁 KB Directory: #{@kb_dir}"
107
- puts "📄 Files analyzed: #{count_files}"
108
- puts "🏗️ Symbols: #{@data[:symbols]&.length || 0}"
109
- puts "📦 Imports: #{@data[:imports]&.length || 0}"
110
- puts "🔗 Calls: #{@data[:calls]&.length || 0}"
111
- puts "📏 Metrics: #{@data[:metrics]&.length || 0}"
112
- puts "🔧 Seams: #{@data[:seams]&.length || 0}"
113
- puts "🔥 Hotspots: #{@data[:hotspots]&.length || 0}"
114
- puts "🧪 Tests: #{@data[:tests]&.length || 0}"
115
- puts "🔄 Cycles: #{@data[:cycles]&.length || 0}"
107
+ display_message("\n📊 Knowledge Base Summary", type: :highlight)
108
+ display_message("=" * 50, type: :info)
109
+
110
+ display_message("📁 KB Directory: #{@kb_dir}", type: :info)
111
+ display_message("📄 Files analyzed: #{count_files}", type: :info)
112
+ display_message("🏗️ Symbols: #{@data[:symbols]&.length || 0}", type: :info)
113
+ display_message("📦 Imports: #{@data[:imports]&.length || 0}", type: :info)
114
+ display_message("🔗 Calls: #{@data[:calls]&.length || 0}", type: :info)
115
+ display_message("📏 Metrics: #{@data[:metrics]&.length || 0}", type: :info)
116
+ display_message("🔧 Seams: #{@data[:seams]&.length || 0}", type: :info)
117
+ display_message("🔥 Hotspots: #{@data[:hotspots]&.length || 0}", type: :info)
118
+ display_message("🧪 Tests: #{@data[:tests]&.length || 0}", type: :info)
119
+ display_message("🔄 Cycles: #{@data[:cycles]&.length || 0}", type: :info)
116
120
 
117
121
  if @data[:seams]&.any?
118
- puts "\n🔧 Seam Types:"
122
+ display_message("\n🔧 Seam Types:", type: :info)
119
123
  seam_types = @data[:seams].group_by { |s| s[:kind] }
120
124
  seam_types.each do |type, seams|
121
- puts " #{type}: #{seams.length}"
125
+ display_message(" #{type}: #{seams.length}", type: :info)
122
126
  end
123
127
  end
124
128
 
125
129
  if @data[:hotspots]&.any?
126
- puts "\n🔥 Top 5 Hotspots:"
130
+ display_message("\n🔥 Top 5 Hotspots:", type: :info)
127
131
  @data[:hotspots].first(5).each_with_index do |hotspot, i|
128
- puts " #{i + 1}. #{hotspot[:file]}:#{hotspot[:method]} (score: #{hotspot[:score]})"
132
+ display_message(" #{i + 1}. #{hotspot[:file]}:#{hotspot[:method]} (score: #{hotspot[:score]})", type: :info)
129
133
  end
130
134
  end
131
135
  end
132
136
 
133
137
  def show_seams(format)
134
- return puts "No seams data available" unless @data[:seams]&.any?
138
+ return display_message("No seams data available") unless @data[:seams]&.any?
135
139
 
136
140
  case format
137
141
  when "json"
138
- puts JSON.pretty_generate(@data[:seams])
142
+ display_message(JSON.pretty_generate(@data[:seams]))
139
143
  when "table"
140
144
  show_seams_table
141
145
  else
@@ -144,8 +148,8 @@ module Aidp
144
148
  end
145
149
 
146
150
  def show_seams_table
147
- puts "\n🔧 Seams Analysis"
148
- puts "=" * 80
151
+ display_message("\n🔧 Seams Analysis")
152
+ display_message("=" * 80)
149
153
 
150
154
  create_table(
151
155
  ["Type", "File", "Line", "Symbol", "Suggestion"],
@@ -162,34 +166,34 @@ module Aidp
162
166
  end
163
167
 
164
168
  def show_seams_summary
165
- puts "\n🔧 Seams Analysis"
166
- puts "=" * 50
169
+ display_message("\n🔧 Seams Analysis")
170
+ display_message("=" * 50)
167
171
 
168
172
  seam_types = @data[:seams].group_by { |s| s[:kind] }
169
173
 
170
174
  seam_types.each do |type, seams|
171
- puts "\n📌 #{type.upcase} (#{seams.length} found)"
172
- puts "-" * 30
175
+ display_message("\n📌 #{type.upcase} (#{seams.length} found)")
176
+ display_message("-" * 30)
173
177
 
174
178
  seams.first(10).each do |seam|
175
- puts " #{seam[:file]}:#{seam[:line]}"
176
- puts " Symbol: #{seam[:symbol_id]&.split(":")&.last}"
177
- puts " Suggestion: #{seam[:suggestion]}"
178
- puts
179
+ display_message(" #{seam[:file]}:#{seam[:line]}")
180
+ display_message(" Symbol: #{seam[:symbol_id]&.split(":")&.last}")
181
+ display_message(" Suggestion: #{seam[:suggestion]}")
182
+ display_message("")
179
183
  end
180
184
 
181
185
  if seams.length > 10
182
- puts " ... and #{seams.length - 10} more"
186
+ display_message(" ... and #{seams.length - 10} more")
183
187
  end
184
188
  end
185
189
  end
186
190
 
187
191
  def show_hotspots(format)
188
- return puts "No hotspots data available" unless @data[:hotspots]&.any?
192
+ return display_message("No hotspots data available") unless @data[:hotspots]&.any?
189
193
 
190
194
  case format
191
195
  when "json"
192
- puts JSON.pretty_generate(@data[:hotspots])
196
+ display_message(JSON.pretty_generate(@data[:hotspots]))
193
197
  when "table"
194
198
  show_hotspots_table
195
199
  else
@@ -198,8 +202,8 @@ module Aidp
198
202
  end
199
203
 
200
204
  def show_hotspots_table
201
- puts "\n🔥 Code Hotspots"
202
- puts "=" * 80
205
+ display_message("\n🔥 Code Hotspots")
206
+ display_message("=" * 80)
203
207
 
204
208
  create_table(
205
209
  ["Rank", "File", "Method", "Score", "Complexity", "Touches"],
@@ -217,81 +221,81 @@ module Aidp
217
221
  end
218
222
 
219
223
  def show_hotspots_summary
220
- puts "\n🔥 Code Hotspots (Top 20)"
221
- puts "=" * 50
224
+ display_message("\n🔥 Code Hotspots (Top 20)")
225
+ display_message("=" * 50)
222
226
 
223
227
  @data[:hotspots].each_with_index do |hotspot, i|
224
- puts "#{i + 1}. #{hotspot[:file]}:#{hotspot[:method]}"
225
- puts " Score: #{hotspot[:score]} (Complexity: #{hotspot[:complexity]}, Touches: #{hotspot[:touches]})"
226
- puts
228
+ display_message("#{i + 1}. #{hotspot[:file]}:#{hotspot[:method]}")
229
+ display_message(" Score: #{hotspot[:score]} (Complexity: #{hotspot[:complexity]}, Touches: #{hotspot[:touches]})")
230
+ display_message("")
227
231
  end
228
232
  end
229
233
 
230
234
  def show_cycles(format)
231
- return puts "No cycles data available" unless @data[:cycles]&.any?
235
+ return display_message("No cycles data available") unless @data[:cycles]&.any?
232
236
 
233
237
  case format
234
238
  when "json"
235
- puts JSON.pretty_generate(@data[:cycles])
239
+ display_message(JSON.pretty_generate(@data[:cycles]))
236
240
  else
237
241
  show_cycles_summary
238
242
  end
239
243
  end
240
244
 
241
245
  def show_cycles_summary
242
- puts "\n🔄 Import Cycles"
243
- puts "=" * 50
246
+ display_message("\n🔄 Import Cycles")
247
+ display_message("=" * 50)
244
248
 
245
249
  @data[:cycles].each_with_index do |cycle, i|
246
- puts "Cycle #{i + 1}:"
250
+ display_message("Cycle #{i + 1}:")
247
251
  cycle[:members].each do |member|
248
- puts " - #{member}"
252
+ display_message(" - #{member}")
249
253
  end
250
- puts " Weight: #{cycle[:weight]}" if cycle[:weight]
251
- puts
254
+ display_message(" Weight: #{cycle[:weight]}") if cycle[:weight]
255
+ display_message("")
252
256
  end
253
257
  end
254
258
 
255
259
  def show_apis(format)
256
- return puts "No APIs data available" unless @data[:tests]&.any?
260
+ return display_message("No APIs data available") unless @data[:tests]&.any?
257
261
 
258
262
  untested_apis = @data[:tests].select { |t| t[:tests].empty? }
259
263
 
260
264
  case format
261
265
  when "json"
262
- puts JSON.pretty_generate(untested_apis)
266
+ display_message(JSON.pretty_generate(untested_apis))
263
267
  else
264
268
  show_apis_summary(untested_apis)
265
269
  end
266
270
  end
267
271
 
268
272
  def show_apis_summary(untested_apis)
269
- puts "\n🧪 Untested Public APIs"
270
- puts "=" * 50
273
+ display_message("\n🧪 Untested Public APIs")
274
+ display_message("=" * 50)
271
275
 
272
276
  if untested_apis.empty?
273
- puts "✅ All public APIs have associated tests!"
277
+ display_message("✅ All public APIs have associated tests!")
274
278
  else
275
- puts "Found #{untested_apis.length} untested public APIs:"
276
- puts
279
+ display_message("Found #{untested_apis.length} untested public APIs:")
280
+ display_message("")
277
281
 
278
282
  untested_apis.each do |api|
279
283
  symbol = @data[:symbols]&.find { |s| s[:id] == api[:symbol_id] }
280
284
  if symbol
281
- puts " #{symbol[:file]}:#{symbol[:line]} - #{symbol[:name]}"
282
- puts " Suggestion: Create characterization tests"
283
- puts
285
+ display_message(" #{symbol[:file]}:#{symbol[:line]} - #{symbol[:name]}")
286
+ display_message(" Suggestion: Create characterization tests")
287
+ display_message("")
284
288
  end
285
289
  end
286
290
  end
287
291
  end
288
292
 
289
293
  def show_symbols(format)
290
- return puts "No symbols data available" unless @data[:symbols]&.any?
294
+ return display_message("No symbols data available") unless @data[:symbols]&.any?
291
295
 
292
296
  case format
293
297
  when "json"
294
- puts JSON.pretty_generate(@data[:symbols])
298
+ display_message(JSON.pretty_generate(@data[:symbols]))
295
299
  when "table"
296
300
  show_symbols_table
297
301
  else
@@ -300,8 +304,8 @@ module Aidp
300
304
  end
301
305
 
302
306
  def show_symbols_table
303
- puts "\n🏗️ Symbols"
304
- puts "=" * 80
307
+ display_message("\n🏗️ Symbols")
308
+ display_message("=" * 80)
305
309
 
306
310
  create_table(
307
311
  ["Type", "Name", "File", "Line", "Visibility"],
@@ -318,22 +322,22 @@ module Aidp
318
322
  end
319
323
 
320
324
  def show_symbols_summary
321
- puts "\n🏗️ Symbols Summary"
322
- puts "=" * 50
325
+ display_message("\n🏗️ Symbols Summary")
326
+ display_message("=" * 50)
323
327
 
324
328
  symbol_types = @data[:symbols].group_by { |s| s[:kind] }
325
329
 
326
330
  symbol_types.each do |type, symbols|
327
- puts "#{type.capitalize}: #{symbols.length}"
331
+ display_message("#{type.capitalize}: #{symbols.length}")
328
332
  end
329
333
  end
330
334
 
331
335
  def show_imports(format)
332
- return puts "No imports data available" unless @data[:imports]&.any?
336
+ return display_message("No imports data available") unless @data[:imports]&.any?
333
337
 
334
338
  case format
335
339
  when "json"
336
- puts JSON.pretty_generate(@data[:imports])
340
+ display_message(JSON.pretty_generate(@data[:imports]))
337
341
  when "table"
338
342
  show_imports_table
339
343
  else
@@ -342,8 +346,8 @@ module Aidp
342
346
  end
343
347
 
344
348
  def show_imports_table
345
- puts "\n📦 Imports"
346
- puts "=" * 80
349
+ display_message("\n📦 Imports")
350
+ display_message("=" * 80)
347
351
 
348
352
  create_table(
349
353
  ["Type", "Target", "File", "Line"],
@@ -359,18 +363,18 @@ module Aidp
359
363
  end
360
364
 
361
365
  def show_imports_summary
362
- puts "\n📦 Imports Summary"
363
- puts "=" * 50
366
+ display_message("\n📦 Imports Summary")
367
+ display_message("=" * 50)
364
368
 
365
369
  import_types = @data[:imports].group_by { |i| i[:kind] }
366
370
 
367
371
  import_types.each do |type, imports|
368
- puts "#{type.capitalize}: #{imports.length}"
372
+ display_message("#{type.capitalize}: #{imports.length}")
369
373
  end
370
374
  end
371
375
 
372
376
  def generate_import_graph(format, output)
373
- puts "Generating import graph in #{format} format..."
377
+ display_message("Generating import graph in #{format} format...")
374
378
 
375
379
  case format
376
380
  when "dot"
@@ -380,7 +384,7 @@ module Aidp
380
384
  when "json"
381
385
  generate_json_graph(output)
382
386
  else
383
- puts "Unsupported graph format: #{format}"
387
+ display_message("Unsupported graph format: #{format}")
384
388
  end
385
389
  end
386
390
 
@@ -399,9 +403,9 @@ module Aidp
399
403
 
400
404
  if output
401
405
  File.write(output, content.join("\n"))
402
- puts "Graph written to #{output}"
406
+ display_message("Graph written to #{output}")
403
407
  else
404
- puts content.join("\n")
408
+ display_message(content.join("\n"))
405
409
  end
406
410
  end
407
411
 
@@ -416,9 +420,9 @@ module Aidp
416
420
 
417
421
  if output
418
422
  File.write(output, content.join("\n"))
419
- puts "Graph written to #{output}"
423
+ display_message("Graph written to #{output}")
420
424
  else
421
- puts content.join("\n")
425
+ display_message(content.join("\n"))
422
426
  end
423
427
  end
424
428
 
@@ -447,20 +451,20 @@ module Aidp
447
451
 
448
452
  if output
449
453
  File.write(output, JSON.pretty_generate(graph_data))
450
- puts "Graph written to #{output}"
454
+ display_message("Graph written to #{output}")
451
455
  else
452
- puts JSON.pretty_generate(graph_data)
456
+ display_message(JSON.pretty_generate(graph_data))
453
457
  end
454
458
  end
455
459
 
456
460
  def generate_call_graph(_format, _output)
457
461
  # Similar to import graph but for method calls
458
- puts "Call graph generation not yet implemented"
462
+ display_message("Call graph generation not yet implemented")
459
463
  end
460
464
 
461
465
  def generate_cycle_graph(_format, _output)
462
466
  # Generate graph showing only the cycles
463
- puts "Cycle graph generation not yet implemented"
467
+ display_message("Cycle graph generation not yet implemented")
464
468
  end
465
469
 
466
470
  def count_files
@@ -1,14 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "tty-prompt"
3
4
  require_relative "ruby_maat_integration"
4
5
  require_relative "feature_analyzer"
5
6
 
6
7
  module Aidp
7
8
  module Analyze
8
9
  class Prioritizer
9
- def initialize(project_dir = Dir.pwd)
10
+ def initialize(project_dir = Dir.pwd, prompt: TTY::Prompt.new)
10
11
  @project_dir = project_dir
11
- @code_maat = Aidp::Analyze::RubyMaatIntegration.new(project_dir)
12
+ @code_maat = Aidp::Analyze::RubyMaatIntegration.new(project_dir, prompt: prompt)
12
13
  @feature_analyzer = Aidp::Analyze::FeatureAnalyzer.new(project_dir)
13
14
  end
14
15
 
@@ -11,7 +11,7 @@ module Aidp
11
11
 
12
12
  def initialize(project_dir)
13
13
  @project_dir = project_dir
14
- @progress_file = File.join(project_dir, ".aidp-analyze-progress.yml")
14
+ @progress_file = File.join(project_dir, ".aidp", "progress", "analyze.yml")
15
15
  load_progress
16
16
  end
17
17
 
@@ -80,6 +80,7 @@ module Aidp
80
80
  # In test mode, skip file operations to avoid hanging
81
81
  return if ENV["RACK_ENV"] == "test" || defined?(RSpec)
82
82
 
83
+ FileUtils.mkdir_p(File.dirname(@progress_file))
83
84
  File.write(@progress_file, @progress.to_yaml)
84
85
  end
85
86
  end
@@ -1,14 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "tty-command"
4
+ require "tty-prompt"
4
5
  require "json"
5
6
  require "fileutils"
6
7
 
7
8
  module Aidp
8
9
  module Analyze
9
10
  class RubyMaatIntegration
10
- def initialize(project_dir = Dir.pwd)
11
+ include Aidp::MessageDisplay
12
+
13
+ def initialize(project_dir = Dir.pwd, prompt: TTY::Prompt.new)
11
14
  @project_dir = project_dir
15
+ @prompt = prompt
12
16
  end
13
17
 
14
18
  # Check if RubyMaat gem is available and accessible
@@ -80,7 +84,7 @@ module Aidp
80
84
 
81
85
  # Run analysis on large repositories using chunking
82
86
  def run_chunked_analysis(git_log_file)
83
- puts "Large repository detected. Running chunked analysis..."
87
+ display_message("Large repository detected. Running chunked analysis...", type: :info)
84
88
 
85
89
  # Split analysis into chunks
86
90
  chunks = create_analysis_chunks(git_log_file)
@@ -93,7 +97,7 @@ module Aidp
93
97
  }
94
98
 
95
99
  chunks.each_with_index do |chunk, index|
96
- puts "Processing chunk #{index + 1}/#{chunks.length}..."
100
+ display_message("Processing chunk #{index + 1}/#{chunks.length}...", type: :info)
97
101
 
98
102
  chunk_results = analyze_chunk(chunk)
99
103
 
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "tty-prompt"
3
4
  require_relative "steps"
4
5
  require_relative "progress"
5
6
  require_relative "../storage/file_manager"
@@ -9,18 +10,24 @@ module Aidp
9
10
  module Analyze
10
11
  class Runner
11
12
  include Aidp::DebugMixin
13
+ include Aidp::MessageDisplay
12
14
 
13
- def initialize(project_dir, harness_runner = nil)
15
+ def initialize(project_dir, harness_runner = nil, prompt: TTY::Prompt.new)
14
16
  @project_dir = project_dir
15
17
  @harness_runner = harness_runner
16
18
  @is_harness_mode = !harness_runner.nil?
17
19
  @file_manager = Aidp::Storage::FileManager.new(File.join(project_dir, ".aidp"))
20
+ @prompt = prompt
18
21
  end
19
22
 
20
23
  def progress
21
24
  @progress ||= Aidp::Analyze::Progress.new(@project_dir)
22
25
  end
23
26
 
27
+ private
28
+
29
+ public
30
+
24
31
  def run_step(step_name, options = {})
25
32
  # Always validate step exists first
26
33
  step_spec = Aidp::Analyze::Steps::SPEC[step_name]
@@ -42,7 +49,7 @@ module Aidp
42
49
  # Harness-aware step execution
43
50
  def run_step_with_harness(step_name, options = {})
44
51
  # Get current provider from harness
45
- current_provider = @harness_runner.instance_variable_get(:@current_provider)
52
+ current_provider = get_harness_provider_safely
46
53
  provider_type = current_provider || "cursor"
47
54
 
48
55
  debug_step(step_name, "Harness execution", {
@@ -72,7 +79,7 @@ module Aidp
72
79
 
73
80
  # Standalone step execution (simplified - synchronous)
74
81
  def run_step_standalone(step_name, options = {})
75
- puts "🚀 Running step synchronously: #{step_name}"
82
+ display_message("🚀 Running step synchronously: #{step_name}", type: :info)
76
83
 
77
84
  start_time = Time.now
78
85
  prompt = composed_prompt(step_name, options)
@@ -85,7 +92,7 @@ module Aidp
85
92
  # Store execution metrics
86
93
  @file_manager.record_step_execution(step_name, "cursor", duration, result[:status] == "completed")
87
94
 
88
- puts "✅ Step completed in #{duration.round(2)}s"
95
+ display_message("✅ Step completed in #{duration.round(2)}s", type: :success)
89
96
  result
90
97
  end
91
98
 
@@ -180,7 +187,10 @@ module Aidp
180
187
 
181
188
  def template_search_paths
182
189
  [
183
- File.join(@project_dir, "templates", "ANALYZE"),
190
+ File.join(@project_dir, "templates"), # Root templates folder
191
+ File.join(@project_dir, "templates", "analysis"),
192
+ File.join(@project_dir, "templates", "planning"),
193
+ File.join(@project_dir, "templates", "implementation"),
184
194
  File.join(@project_dir, "templates", "COMMON")
185
195
  ]
186
196
  end
@@ -223,11 +233,11 @@ module Aidp
223
233
  # Add current execution context
224
234
  context_parts << "## Analysis Context"
225
235
  context_parts << "Project Directory: #{@project_dir}"
226
- context_parts << "Current Step: #{@harness_runner.instance_variable_get(:@current_step)}"
227
- context_parts << "Current Provider: #{@harness_runner.instance_variable_get(:@current_provider)}"
236
+ context_parts << "Current Step: #{get_harness_current_step_safely}"
237
+ context_parts << "Current Provider: #{get_harness_provider_safely}"
228
238
 
229
239
  # Add user input context
230
- user_input = @harness_runner.instance_variable_get(:@user_input)
240
+ user_input = get_harness_user_input_safely
231
241
  if user_input && !user_input.empty?
232
242
  context_parts << "\n## Previous User Input"
233
243
  user_input.each do |key, value|
@@ -236,7 +246,7 @@ module Aidp
236
246
  end
237
247
 
238
248
  # Add execution history context
239
- execution_log = @harness_runner.instance_variable_get(:@execution_log)
249
+ execution_log = get_harness_execution_log_safely
240
250
  if execution_log && !execution_log.empty?
241
251
  context_parts << "\n## Analysis History"
242
252
  recent_logs = execution_log.last(5) # Last 5 entries
@@ -250,8 +260,9 @@ module Aidp
250
260
 
251
261
  # Execute step with harness provider management
252
262
  def execute_with_harness_provider(provider_type, prompt, step_name, _options)
253
- # Get provider manager from harness
254
- provider_manager = @harness_runner.instance_variable_get(:@provider_manager)
263
+ # Get provider manager from harness safely
264
+ provider_manager = get_harness_provider_manager_safely
265
+ return {status: "failed", error: "No provider manager available"} unless provider_manager
255
266
 
256
267
  # Execute with provider
257
268
  provider_manager.execute_with_provider(provider_type, prompt, {
@@ -325,6 +336,57 @@ module Aidp
325
336
  })
326
337
  end
327
338
  end
339
+
340
+ # Safely get current provider from harness runner
341
+ def get_harness_provider_safely
342
+ return "cursor" unless @harness_runner
343
+ return "cursor" unless @harness_runner.respond_to?(:current_provider)
344
+
345
+ @harness_runner.current_provider || "cursor"
346
+ rescue => e
347
+ debug_log("⚠️ Failed to get current provider from harness", level: :warn, data: {error: e.message})
348
+ "cursor"
349
+ end
350
+
351
+ def get_harness_current_step_safely
352
+ return "unknown" unless @harness_runner
353
+ return "unknown" unless @harness_runner.respond_to?(:current_step)
354
+
355
+ @harness_runner.current_step || "unknown"
356
+ rescue => e
357
+ debug_log("⚠️ Failed to get current step from harness", level: :warn, data: {error: e.message})
358
+ "unknown"
359
+ end
360
+
361
+ def get_harness_user_input_safely
362
+ return {} unless @harness_runner
363
+ return {} unless @harness_runner.respond_to?(:user_input)
364
+
365
+ @harness_runner.user_input || {}
366
+ rescue => e
367
+ debug_log("⚠️ Failed to get user input from harness", level: :warn, data: {error: e.message})
368
+ {}
369
+ end
370
+
371
+ def get_harness_execution_log_safely
372
+ return [] unless @harness_runner
373
+ return [] unless @harness_runner.respond_to?(:execution_log)
374
+
375
+ @harness_runner.execution_log || []
376
+ rescue => e
377
+ debug_log("⚠️ Failed to get execution log from harness", level: :warn, data: {error: e.message})
378
+ []
379
+ end
380
+
381
+ def get_harness_provider_manager_safely
382
+ return nil unless @harness_runner
383
+ return nil unless @harness_runner.respond_to?(:provider_manager)
384
+
385
+ @harness_runner.provider_manager
386
+ rescue => e
387
+ debug_log("⚠️ Failed to get provider manager from harness", level: :warn, data: {error: e.message})
388
+ nil
389
+ end
328
390
  end
329
391
  end
330
392
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aidp
4
- module Analysis
4
+ module Analyze
5
5
  module Seams
6
6
  # I/O and OS integration patterns
7
7
  IO_PATTERNS = [