aidp 0.7.0 → 0.8.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 (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 +0 -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
@@ -34,57 +34,6 @@ module Aidp
34
34
  end
35
35
 
36
36
  # Handle specific error types with custom logic
37
- def handle_network_error(error, context: {})
38
- case error
39
- when Net::TimeoutError
40
- handle_timeout_error(error, context)
41
- when Net::HTTPError
42
- handle_http_error(error, context)
43
- when SocketError
44
- handle_socket_error(error, context)
45
- else
46
- handle_generic_network_error(error, context)
47
- end
48
- end
49
-
50
- def handle_file_system_error(error, context: {})
51
- case error
52
- when Errno::ENOENT
53
- handle_file_not_found(error, context)
54
- when Errno::EACCES
55
- handle_permission_denied(error, context)
56
- when Errno::ENOSPC
57
- handle_disk_full(error, context)
58
- else
59
- handle_generic_file_error(error, context)
60
- end
61
- end
62
-
63
- def handle_database_error(error, context: {})
64
- case error
65
- when SQLite3::BusyException
66
- handle_database_busy(error, context)
67
- when SQLite3::CorruptException
68
- handle_database_corrupt(error, context)
69
- when SQLite3::ReadOnlyException
70
- handle_database_readonly(error, context)
71
- else
72
- handle_generic_database_error(error, context)
73
- end
74
- end
75
-
76
- def handle_analysis_error(error, context: {})
77
- case error
78
- when AnalysisTimeoutError
79
- handle_analysis_timeout(error, context)
80
- when AnalysisDataError
81
- handle_analysis_data_error(error, context)
82
- when AnalysisToolError
83
- handle_analysis_tool_error(error, context)
84
- else
85
- handle_generic_analysis_error(error, context)
86
- end
87
- end
88
37
 
89
38
  # Recovery strategies
90
39
  def retry_with_backoff(operation, max_retries: 3, base_delay: 1)
@@ -96,7 +45,7 @@ module Aidp
96
45
  if retry_count <= max_retries
97
46
  delay = base_delay * (2**(retry_count - 1))
98
47
  logger.warn("Retrying operation in #{delay} seconds (attempt #{retry_count}/#{max_retries})")
99
- sleep(delay)
48
+ Async::Task.current.sleep(delay)
100
49
  retry
101
50
  else
102
51
  logger.error("Operation failed after #{max_retries} retries: #{e.message}")
@@ -128,28 +77,6 @@ module Aidp
128
77
  }
129
78
  end
130
79
 
131
- def get_error_recommendations
132
- recommendations = []
133
-
134
- if @error_counts[Net::TimeoutError] > 5
135
- recommendations << "Consider increasing timeout values for network operations"
136
- end
137
-
138
- if @error_counts[Errno::ENOSPC] > 0
139
- recommendations << "Check available disk space and implement cleanup procedures"
140
- end
141
-
142
- if @error_counts[SQLite3::BusyException] > 3
143
- recommendations << "Consider implementing database connection pooling"
144
- end
145
-
146
- if @error_counts[AnalysisTimeoutError] > 2
147
- recommendations << "Consider chunking large analysis tasks or increasing timeouts"
148
- end
149
-
150
- recommendations
151
- end
152
-
153
80
  # Cleanup and resource management
154
81
  def cleanup
155
82
  logger.info("Cleaning up error handler resources")
@@ -247,7 +174,7 @@ module Aidp
247
174
  logger.warn("HTTP error: #{error.message}")
248
175
  case error.response&.code
249
176
  when "429" # Rate limited
250
- sleep(60) # Wait 1 minute
177
+ Async::Task.current.sleep(60) # Wait 1 minute
251
178
  retry_with_backoff(-> { context[:operation].call }, max_retries: 2)
252
179
  when "500".."599" # Server errors
253
180
  retry_with_backoff(-> { context[:operation].call }, max_retries: 3)
@@ -0,0 +1,292 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "fileutils"
5
+
6
+ module Aidp
7
+ module Analyze
8
+ class JsonFileStorage
9
+ def initialize(project_dir = Dir.pwd, storage_dir = ".aidp/json")
10
+ @project_dir = project_dir
11
+ @storage_dir = File.join(project_dir, storage_dir)
12
+ ensure_storage_directory
13
+ end
14
+
15
+ # Store data in a JSON file
16
+ def store_data(filename, data)
17
+ file_path = get_file_path(filename)
18
+
19
+ # Ensure directory exists
20
+ FileUtils.mkdir_p(File.dirname(file_path))
21
+
22
+ # Write data as pretty JSON
23
+ File.write(file_path, JSON.pretty_generate(data))
24
+
25
+ {
26
+ filename: filename,
27
+ file_path: file_path,
28
+ stored_at: Time.now,
29
+ success: true
30
+ }
31
+ end
32
+
33
+ # Retrieve data from a JSON file
34
+ def get_data(filename)
35
+ file_path = get_file_path(filename)
36
+
37
+ return nil unless File.exist?(file_path)
38
+
39
+ begin
40
+ JSON.parse(File.read(file_path))
41
+ rescue JSON::ParserError => e
42
+ raise "Invalid JSON in file #{filename}: #{e.message}"
43
+ end
44
+ end
45
+
46
+ # Check if a JSON file exists
47
+ def data_exists?(filename)
48
+ File.exist?(get_file_path(filename))
49
+ end
50
+
51
+ # Delete a JSON file
52
+ def delete_data(filename)
53
+ file_path = get_file_path(filename)
54
+
55
+ if File.exist?(file_path)
56
+ File.delete(file_path)
57
+ {
58
+ filename: filename,
59
+ deleted: true,
60
+ deleted_at: Time.now
61
+ }
62
+ else
63
+ {
64
+ filename: filename,
65
+ deleted: false,
66
+ message: "File does not exist"
67
+ }
68
+ end
69
+ end
70
+
71
+ # List all JSON files in the storage directory
72
+ def list_files
73
+ return [] unless Dir.exist?(@storage_dir)
74
+
75
+ Dir.glob(File.join(@storage_dir, "**", "*.json")).map do |file_path|
76
+ relative_path = file_path.sub("#{@storage_dir}/", "")
77
+ {
78
+ filename: relative_path,
79
+ file_path: file_path,
80
+ size: File.size(file_path),
81
+ modified_at: File.mtime(file_path)
82
+ }
83
+ end
84
+ end
85
+
86
+ # Store project configuration
87
+ def store_project_config(config_data)
88
+ store_data("project_config.json", config_data)
89
+ end
90
+
91
+ # Get project configuration
92
+ def get_project_config
93
+ get_data("project_config.json")
94
+ end
95
+
96
+ # Store runtime status
97
+ def store_runtime_status(status_data)
98
+ store_data("runtime_status.json", status_data)
99
+ end
100
+
101
+ # Get runtime status
102
+ def get_runtime_status
103
+ get_data("runtime_status.json")
104
+ end
105
+
106
+ # Store simple metrics
107
+ def store_simple_metrics(metrics_data)
108
+ store_data("simple_metrics.json", metrics_data)
109
+ end
110
+
111
+ # Get simple metrics
112
+ def get_simple_metrics
113
+ get_data("simple_metrics.json")
114
+ end
115
+
116
+ # Store analysis session data
117
+ def store_analysis_session(session_id, session_data)
118
+ store_data("sessions/#{session_id}.json", session_data)
119
+ end
120
+
121
+ # Get analysis session data
122
+ def get_analysis_session(session_id)
123
+ get_data("sessions/#{session_id}.json")
124
+ end
125
+
126
+ # List analysis sessions
127
+ def list_analysis_sessions
128
+ sessions_dir = File.join(@storage_dir, "sessions")
129
+ return [] unless Dir.exist?(sessions_dir)
130
+
131
+ Dir.glob(File.join(sessions_dir, "*.json")).map do |file_path|
132
+ session_id = File.basename(file_path, ".json")
133
+ {
134
+ session_id: session_id,
135
+ file_path: file_path,
136
+ size: File.size(file_path),
137
+ modified_at: File.mtime(file_path)
138
+ }
139
+ end
140
+ end
141
+
142
+ # Store user preferences
143
+ def store_user_preferences(preferences_data)
144
+ store_data("user_preferences.json", preferences_data)
145
+ end
146
+
147
+ # Get user preferences
148
+ def get_user_preferences
149
+ get_data("user_preferences.json")
150
+ end
151
+
152
+ # Store cache data
153
+ def store_cache(cache_key, cache_data, ttl_seconds = nil)
154
+ cache_data_with_ttl = {
155
+ data: cache_data,
156
+ cached_at: Time.now.iso8601,
157
+ ttl_seconds: ttl_seconds
158
+ }
159
+
160
+ store_data("cache/#{cache_key}.json", cache_data_with_ttl)
161
+ end
162
+
163
+ # Get cache data (respects TTL)
164
+ def get_cache(cache_key)
165
+ cache_file_data = get_data("cache/#{cache_key}.json")
166
+ return nil unless cache_file_data
167
+
168
+ # Check TTL if specified
169
+ if cache_file_data["ttl_seconds"]
170
+ cached_at = Time.parse(cache_file_data["cached_at"])
171
+ if Time.now - cached_at > cache_file_data["ttl_seconds"]
172
+ # Cache expired, delete it
173
+ delete_data("cache/#{cache_key}.json")
174
+ return nil
175
+ end
176
+ end
177
+
178
+ cache_file_data["data"]
179
+ end
180
+
181
+ # Clear expired cache entries
182
+ def clear_expired_cache
183
+ cache_dir = File.join(@storage_dir, "cache")
184
+ return 0 unless Dir.exist?(cache_dir)
185
+
186
+ cleared_count = 0
187
+ Dir.glob(File.join(cache_dir, "*.json")).each do |file_path|
188
+ cache_data = JSON.parse(File.read(file_path))
189
+ if cache_data["ttl_seconds"] && cache_data["cached_at"]
190
+ cached_at = Time.parse(cache_data["cached_at"])
191
+ if Time.now - cached_at > cache_data["ttl_seconds"]
192
+ File.delete(file_path)
193
+ cleared_count += 1
194
+ end
195
+ end
196
+ rescue JSON::ParserError
197
+ # Invalid JSON, delete the file
198
+ File.delete(file_path)
199
+ cleared_count += 1
200
+ end
201
+
202
+ cleared_count
203
+ end
204
+
205
+ # Get storage statistics
206
+ def get_storage_statistics
207
+ files = list_files
208
+
209
+ {
210
+ total_files: files.length,
211
+ total_size: files.sum { |f| f[:size] },
212
+ storage_directory: @storage_dir,
213
+ oldest_file: files.min_by { |f| f[:modified_at] }&.dig(:modified_at),
214
+ newest_file: files.max_by { |f| f[:modified_at] }&.dig(:modified_at),
215
+ file_types: files.group_by { |f| File.extname(f[:filename]) }.transform_values(&:count)
216
+ }
217
+ end
218
+
219
+ # Export all data to a single JSON file
220
+ def export_all_data(export_filename = "aidp_data_export.json")
221
+ export_data = {
222
+ "exported_at" => Time.now.iso8601,
223
+ "storage_directory" => @storage_dir,
224
+ "files" => {}
225
+ }
226
+
227
+ files = list_files
228
+ files.each do |file_info|
229
+ data = get_data(file_info[:filename])
230
+ export_data["files"][file_info[:filename]] = {
231
+ "data" => data,
232
+ "metadata" => {
233
+ "size" => file_info[:size],
234
+ "modified_at" => file_info[:modified_at]
235
+ }
236
+ }
237
+ end
238
+
239
+ export_path = File.join(@storage_dir, export_filename)
240
+ File.write(export_path, JSON.pretty_generate(export_data))
241
+
242
+ {
243
+ export_filename: export_filename,
244
+ export_path: export_path,
245
+ files_exported: files.length,
246
+ exported_at: Time.now
247
+ }
248
+ end
249
+
250
+ # Import data from an exported JSON file
251
+ def import_data(import_filename)
252
+ import_path = get_file_path(import_filename)
253
+
254
+ unless File.exist?(import_path)
255
+ raise "Import file does not exist: #{import_filename}"
256
+ end
257
+
258
+ begin
259
+ import_data = JSON.parse(File.read(import_path))
260
+ rescue JSON::ParserError => e
261
+ raise "Invalid JSON in import file: #{e.message}"
262
+ end
263
+
264
+ unless import_data["files"]
265
+ raise "Invalid import file format: missing 'files' key"
266
+ end
267
+
268
+ imported_count = 0
269
+ import_data["files"].each do |filename, file_data|
270
+ store_data(filename, file_data["data"])
271
+ imported_count += 1
272
+ end
273
+
274
+ {
275
+ imported_count: imported_count,
276
+ imported_at: Time.now,
277
+ success: true
278
+ }
279
+ end
280
+
281
+ private
282
+
283
+ def get_file_path(filename)
284
+ File.join(@storage_dir, filename)
285
+ end
286
+
287
+ def ensure_storage_directory
288
+ FileUtils.mkdir_p(@storage_dir)
289
+ end
290
+ end
291
+ end
292
+ end
@@ -60,14 +60,26 @@ module Aidp
60
60
  private
61
61
 
62
62
  def load_progress
63
+ # In test mode, only skip file operations if no progress file exists
64
+ if (ENV["RACK_ENV"] == "test" || defined?(RSpec)) && !File.exist?(@progress_file)
65
+ @progress = {}
66
+ return
67
+ end
68
+
63
69
  @progress = if File.exist?(@progress_file)
64
70
  YAML.load_file(@progress_file) || {}
65
71
  else
66
72
  {}
67
73
  end
74
+
75
+ # Ensure @progress is never nil
76
+ @progress = {} if @progress.nil?
68
77
  end
69
78
 
70
79
  def save_progress
80
+ # In test mode, skip file operations to avoid hanging
81
+ return if ENV["RACK_ENV"] == "test" || defined?(RSpec)
82
+
71
83
  File.write(@progress_file, @progress.to_yaml)
72
84
  end
73
85
  end
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "tty-progressbar"
4
3
  require "tty-spinner"
4
+ require "tty-progressbar"
5
5
  require "tty-table"
6
+ require "pastel"
6
7
  require "colorize"
7
8
 
8
9
  module Aidp
@@ -20,6 +21,7 @@ module Aidp
20
21
  @quiet = config[:quiet] || false
21
22
  @style = config[:style] || :default
22
23
  @show_details = config[:show_details] || true
24
+ @pastel = Pastel.new
23
25
  end
24
26
 
25
27
  # Display analysis progress
@@ -59,15 +61,8 @@ module Aidp
59
61
  def show_spinner(message, options = {})
60
62
  return if @quiet
61
63
 
62
- spinner = TTY::Spinner.new(
63
- "[:spinner] #{message}",
64
- format: :dots,
65
- success_mark: "✓",
66
- error_mark: "✗",
67
- hide_cursor: true
68
- )
69
-
70
- spinner.auto_spin
64
+ spinner = TTY::Spinner.new(message, format: :dots)
65
+ spinner.start
71
66
  spinner
72
67
  end
73
68
 
@@ -228,15 +223,13 @@ module Aidp
228
223
 
229
224
  def create_progress_bar(total, title, options = {})
230
225
  style = options[:style] || @style
231
- progress_style = PROGRESS_STYLES[style] || PROGRESS_STYLES[:default]
226
+ PROGRESS_STYLES[style] || PROGRESS_STYLES[:default]
232
227
 
228
+ # Use TTY progress bar
233
229
  TTY::ProgressBar.new(
234
- "#{title} [:bar] :percent :current/:total",
230
+ "[:bar] :percent% :current/:total",
235
231
  total: total,
236
- width: 50,
237
- complete: progress_style[:complete],
238
- incomplete: progress_style[:incomplete],
239
- head: progress_style[:head]
232
+ width: 30
240
233
  )
241
234
  end
242
235
 
@@ -273,7 +266,9 @@ module Aidp
273
266
  ]
274
267
  end
275
268
 
276
- TTY::Table.new(headers, rows)
269
+ # Use TTY::Table for table display
270
+ table = TTY::Table.new(headers, rows)
271
+ puts table.render(:ascii)
277
272
  end
278
273
 
279
274
  def show_step_details(step_details)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "open3"
3
+ require "tty-command"
4
4
  require "json"
5
5
  require "fileutils"
6
6
 
@@ -12,12 +12,6 @@ module Aidp
12
12
  end
13
13
 
14
14
  # Check if RubyMaat gem is available and accessible
15
- def check_prerequisites
16
- {
17
- git_repository: git_repository?,
18
- git_log_available: git_log_available?
19
- }
20
- end
21
15
 
22
16
  # Generate Git log for RubyMaat analysis
23
17
  def generate_git_log(output_file = nil)
@@ -25,18 +19,12 @@ module Aidp
25
19
 
26
20
  raise "Not a Git repository. RubyMaat requires a Git repository for analysis." unless git_repository?
27
21
 
28
- cmd = [
29
- "git", "log",
30
- '--pretty=format:"%h|%an|%ad|%aE|%s"',
31
- "--date=short",
32
- "--numstat"
33
- ]
34
-
35
- stdout, stderr, status = Open3.capture3(*cmd, chdir: @project_dir)
22
+ cmd = TTY::Command.new(printer: :null)
23
+ result = cmd.run("git", "log", '--pretty=format:"%h|%an|%ad|%aE|%s"', "--date=short", "--numstat", chdir: @project_dir)
36
24
 
37
- raise "Failed to generate Git log: #{stderr}" unless status.success?
25
+ raise "Failed to generate Git log: #{result.err}" unless result.success?
38
26
 
39
- File.write(output_file, stdout)
27
+ File.write(output_file, result.out)
40
28
  output_file
41
29
  end
42
30
 
@@ -163,16 +151,15 @@ module Aidp
163
151
  raise "Input file not found: #{input_file}" unless File.exist?(input_file)
164
152
 
165
153
  # Run RubyMaat with the same command-line interface as code-maat
166
- cmd = ["bundle", "exec", "ruby-maat", analysis_type, input_file]
154
+ cmd = TTY::Command.new(printer: :null)
155
+ result = cmd.run("bundle", "exec", "ruby-maat", analysis_type, input_file, chdir: @project_dir)
167
156
 
168
- stdout, stderr, status = Open3.capture3(*cmd, chdir: @project_dir)
169
-
170
- if status.success?
157
+ if result.success?
171
158
  # Write the output to the specified file
172
- File.write(output_file, stdout)
159
+ File.write(output_file, result.out)
173
160
  else
174
161
  # Raise proper error instead of falling back to fake data
175
- error_msg = "RubyMaat analysis failed for #{analysis_type}: #{stderr.strip}"
162
+ error_msg = "RubyMaat analysis failed for #{analysis_type}: #{result.err.strip}"
176
163
  error_msg += "\n\nTo install ruby-maat, run: gem install ruby-maat"
177
164
  error_msg += "\nOr add it to your Gemfile: gem 'ruby-maat'"
178
165
  raise error_msg
@@ -267,11 +254,6 @@ module Aidp
267
254
  end
268
255
 
269
256
  # Clean up chunk files after analysis
270
- def cleanup_chunk_files(git_log_file)
271
- Dir.glob("#{git_log_file}.chunk_*").each do |chunk_file|
272
- File.delete(chunk_file) if File.exist?(chunk_file)
273
- end
274
- end
275
257
 
276
258
  def parse_churn_results(file_path)
277
259
  return {files: []} unless File.exist?(file_path)
@@ -466,10 +448,10 @@ module Aidp
466
448
  def git_log_available?
467
449
  return false unless git_repository?
468
450
 
469
- cmd = ["git", "log", "--oneline", "-1"]
470
- stdout, _, status = Open3.capture3(*cmd, chdir: @project_dir)
451
+ cmd = TTY::Command.new(printer: :null)
452
+ result = cmd.run("git", "log", "--oneline", "-1", chdir: @project_dir)
471
453
 
472
- status.success? && !stdout.strip.empty?
454
+ result.success? && !result.out.strip.empty?
473
455
  end
474
456
  end
475
457
  end