aidp 0.5.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 (122) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +128 -151
  3. data/bin/aidp +1 -1
  4. data/lib/aidp/analysis/kb_inspector.rb +471 -0
  5. data/lib/aidp/analysis/seams.rb +159 -0
  6. data/lib/aidp/analysis/tree_sitter_grammar_loader.rb +480 -0
  7. data/lib/aidp/analysis/tree_sitter_scan.rb +686 -0
  8. data/lib/aidp/analyze/error_handler.rb +2 -78
  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/analyze/steps.rb +6 -0
  15. data/lib/aidp/cli/jobs_command.rb +103 -435
  16. data/lib/aidp/cli.rb +317 -191
  17. data/lib/aidp/config.rb +298 -10
  18. data/lib/aidp/debug_logger.rb +195 -0
  19. data/lib/aidp/debug_mixin.rb +187 -0
  20. data/lib/aidp/execute/progress.rb +9 -0
  21. data/lib/aidp/execute/runner.rb +221 -40
  22. data/lib/aidp/execute/steps.rb +17 -7
  23. data/lib/aidp/execute/workflow_selector.rb +211 -0
  24. data/lib/aidp/harness/completion_checker.rb +268 -0
  25. data/lib/aidp/harness/condition_detector.rb +1526 -0
  26. data/lib/aidp/harness/config_loader.rb +373 -0
  27. data/lib/aidp/harness/config_manager.rb +382 -0
  28. data/lib/aidp/harness/config_schema.rb +1006 -0
  29. data/lib/aidp/harness/config_validator.rb +355 -0
  30. data/lib/aidp/harness/configuration.rb +477 -0
  31. data/lib/aidp/harness/enhanced_runner.rb +494 -0
  32. data/lib/aidp/harness/error_handler.rb +616 -0
  33. data/lib/aidp/harness/provider_config.rb +423 -0
  34. data/lib/aidp/harness/provider_factory.rb +306 -0
  35. data/lib/aidp/harness/provider_manager.rb +1269 -0
  36. data/lib/aidp/harness/provider_type_checker.rb +88 -0
  37. data/lib/aidp/harness/runner.rb +411 -0
  38. data/lib/aidp/harness/state/errors.rb +28 -0
  39. data/lib/aidp/harness/state/metrics.rb +219 -0
  40. data/lib/aidp/harness/state/persistence.rb +128 -0
  41. data/lib/aidp/harness/state/provider_state.rb +132 -0
  42. data/lib/aidp/harness/state/ui_state.rb +68 -0
  43. data/lib/aidp/harness/state/workflow_state.rb +123 -0
  44. data/lib/aidp/harness/state_manager.rb +586 -0
  45. data/lib/aidp/harness/status_display.rb +888 -0
  46. data/lib/aidp/harness/ui/base.rb +16 -0
  47. data/lib/aidp/harness/ui/enhanced_tui.rb +545 -0
  48. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +252 -0
  49. data/lib/aidp/harness/ui/error_handler.rb +132 -0
  50. data/lib/aidp/harness/ui/frame_manager.rb +361 -0
  51. data/lib/aidp/harness/ui/job_monitor.rb +500 -0
  52. data/lib/aidp/harness/ui/navigation/main_menu.rb +311 -0
  53. data/lib/aidp/harness/ui/navigation/menu_formatter.rb +120 -0
  54. data/lib/aidp/harness/ui/navigation/menu_item.rb +142 -0
  55. data/lib/aidp/harness/ui/navigation/menu_state.rb +139 -0
  56. data/lib/aidp/harness/ui/navigation/submenu.rb +202 -0
  57. data/lib/aidp/harness/ui/navigation/workflow_selector.rb +176 -0
  58. data/lib/aidp/harness/ui/progress_display.rb +280 -0
  59. data/lib/aidp/harness/ui/question_collector.rb +141 -0
  60. data/lib/aidp/harness/ui/spinner_group.rb +184 -0
  61. data/lib/aidp/harness/ui/spinner_helper.rb +152 -0
  62. data/lib/aidp/harness/ui/status_manager.rb +312 -0
  63. data/lib/aidp/harness/ui/status_widget.rb +280 -0
  64. data/lib/aidp/harness/ui/workflow_controller.rb +312 -0
  65. data/lib/aidp/harness/user_interface.rb +2381 -0
  66. data/lib/aidp/provider_manager.rb +131 -7
  67. data/lib/aidp/providers/anthropic.rb +28 -109
  68. data/lib/aidp/providers/base.rb +170 -0
  69. data/lib/aidp/providers/cursor.rb +52 -183
  70. data/lib/aidp/providers/gemini.rb +24 -109
  71. data/lib/aidp/providers/macos_ui.rb +99 -5
  72. data/lib/aidp/providers/opencode.rb +194 -0
  73. data/lib/aidp/storage/csv_storage.rb +172 -0
  74. data/lib/aidp/storage/file_manager.rb +214 -0
  75. data/lib/aidp/storage/json_storage.rb +140 -0
  76. data/lib/aidp/version.rb +1 -1
  77. data/lib/aidp.rb +56 -35
  78. data/templates/ANALYZE/06a_tree_sitter_scan.md +217 -0
  79. data/templates/COMMON/AGENT_BASE.md +11 -0
  80. data/templates/EXECUTE/00_PRD.md +4 -4
  81. data/templates/EXECUTE/02_ARCHITECTURE.md +5 -4
  82. data/templates/EXECUTE/07_TEST_PLAN.md +4 -1
  83. data/templates/EXECUTE/08_TASKS.md +4 -4
  84. data/templates/EXECUTE/10_IMPLEMENTATION_AGENT.md +4 -4
  85. data/templates/README.md +279 -0
  86. data/templates/aidp-development.yml.example +373 -0
  87. data/templates/aidp-minimal.yml.example +48 -0
  88. data/templates/aidp-production.yml.example +475 -0
  89. data/templates/aidp.yml.example +598 -0
  90. metadata +106 -64
  91. data/lib/aidp/analyze/agent_personas.rb +0 -71
  92. data/lib/aidp/analyze/agent_tool_executor.rb +0 -445
  93. data/lib/aidp/analyze/data_retention_manager.rb +0 -426
  94. data/lib/aidp/analyze/database.rb +0 -260
  95. data/lib/aidp/analyze/dependencies.rb +0 -335
  96. data/lib/aidp/analyze/export_manager.rb +0 -425
  97. data/lib/aidp/analyze/focus_guidance.rb +0 -517
  98. data/lib/aidp/analyze/incremental_analyzer.rb +0 -543
  99. data/lib/aidp/analyze/language_analysis_strategies.rb +0 -897
  100. data/lib/aidp/analyze/large_analysis_progress.rb +0 -504
  101. data/lib/aidp/analyze/memory_manager.rb +0 -365
  102. data/lib/aidp/analyze/metrics_storage.rb +0 -336
  103. data/lib/aidp/analyze/parallel_processor.rb +0 -460
  104. data/lib/aidp/analyze/performance_optimizer.rb +0 -694
  105. data/lib/aidp/analyze/repository_chunker.rb +0 -704
  106. data/lib/aidp/analyze/static_analysis_detector.rb +0 -577
  107. data/lib/aidp/analyze/storage.rb +0 -662
  108. data/lib/aidp/analyze/tool_configuration.rb +0 -456
  109. data/lib/aidp/analyze/tool_modernization.rb +0 -750
  110. data/lib/aidp/database/pg_adapter.rb +0 -148
  111. data/lib/aidp/database_config.rb +0 -69
  112. data/lib/aidp/database_connection.rb +0 -72
  113. data/lib/aidp/database_migration.rb +0 -158
  114. data/lib/aidp/job_manager.rb +0 -41
  115. data/lib/aidp/jobs/base_job.rb +0 -47
  116. data/lib/aidp/jobs/provider_execution_job.rb +0 -96
  117. data/lib/aidp/project_detector.rb +0 -117
  118. data/lib/aidp/providers/agent_supervisor.rb +0 -348
  119. data/lib/aidp/providers/supervised_base.rb +0 -317
  120. data/lib/aidp/providers/supervised_cursor.rb +0 -22
  121. data/lib/aidp/sync.rb +0 -13
  122. data/lib/aidp/workspace.rb +0 -19
data/lib/aidp/config.rb CHANGED
@@ -5,29 +5,317 @@ require "yaml"
5
5
  module Aidp
6
6
  # Configuration management for both execute and analyze modes
7
7
  class Config
8
+ # Default configuration for harness
9
+ DEFAULT_HARNESS_CONFIG = {
10
+ harness: {
11
+ max_retries: 2,
12
+ default_provider: "cursor",
13
+ fallback_providers: ["cursor"],
14
+ restrict_to_non_byok: false,
15
+ provider_weights: {
16
+ "cursor" => 3,
17
+ "anthropic" => 2,
18
+ "macos" => 1
19
+ },
20
+ circuit_breaker: {
21
+ enabled: true,
22
+ failure_threshold: 5,
23
+ timeout: 300,
24
+ half_open_max_calls: 3
25
+ },
26
+ retry: {
27
+ enabled: true,
28
+ max_attempts: 3,
29
+ base_delay: 1.0,
30
+ max_delay: 60.0,
31
+ exponential_base: 2.0,
32
+ jitter: true
33
+ },
34
+ rate_limit: {
35
+ enabled: true,
36
+ default_reset_time: 3600,
37
+ burst_limit: 10,
38
+ sustained_limit: 5
39
+ },
40
+ load_balancing: {
41
+ enabled: true,
42
+ strategy: "weighted_round_robin",
43
+ health_check_interval: 30,
44
+ unhealthy_threshold: 3
45
+ },
46
+ model_switching: {
47
+ enabled: true,
48
+ auto_switch_on_error: true,
49
+ auto_switch_on_rate_limit: true,
50
+ fallback_strategy: "sequential"
51
+ },
52
+ health_check: {
53
+ enabled: true,
54
+ interval: 60,
55
+ timeout: 10,
56
+ failure_threshold: 3,
57
+ success_threshold: 2
58
+ },
59
+ metrics: {
60
+ enabled: true,
61
+ retention_days: 30,
62
+ aggregation_interval: 300,
63
+ export_interval: 3600
64
+ },
65
+ session: {
66
+ enabled: true,
67
+ timeout: 1800,
68
+ sticky_sessions: true,
69
+ session_affinity: "provider_model"
70
+ }
71
+ },
72
+ providers: {
73
+ cursor: {
74
+ type: "package",
75
+ priority: 1,
76
+ default_flags: [],
77
+ models: ["cursor-default", "cursor-fast", "cursor-precise"],
78
+ model_weights: {
79
+ "cursor-default" => 3,
80
+ "cursor-fast" => 2,
81
+ "cursor-precise" => 1
82
+ },
83
+ models_config: {
84
+ "cursor-default" => {
85
+ flags: [],
86
+ timeout: 600
87
+ },
88
+ "cursor-fast" => {
89
+ flags: ["--fast"],
90
+ timeout: 300
91
+ },
92
+ "cursor-precise" => {
93
+ flags: ["--precise"],
94
+ timeout: 900
95
+ }
96
+ },
97
+ features: {
98
+ file_upload: true,
99
+ code_generation: true,
100
+ analysis: true
101
+ },
102
+ monitoring: {
103
+ enabled: true,
104
+ metrics_interval: 60
105
+ }
106
+ },
107
+ anthropic: {
108
+ type: "usage_based",
109
+ priority: 2,
110
+ max_tokens: 100_000,
111
+ default_flags: ["--dangerously-skip-permissions"],
112
+ models: ["claude-3-5-sonnet-20241022", "claude-3-5-haiku-20241022", "claude-3-opus-20240229"],
113
+ model_weights: {
114
+ "claude-3-5-sonnet-20241022" => 3,
115
+ "claude-3-5-haiku-20241022" => 2,
116
+ "claude-3-opus-20240229" => 1
117
+ },
118
+ models_config: {
119
+ "claude-3-5-sonnet-20241022" => {
120
+ flags: ["--dangerously-skip-permissions"],
121
+ max_tokens: 200_000,
122
+ timeout: 300
123
+ },
124
+ "claude-3-5-haiku-20241022" => {
125
+ flags: ["--dangerously-skip-permissions"],
126
+ max_tokens: 200_000,
127
+ timeout: 180
128
+ },
129
+ "claude-3-opus-20240229" => {
130
+ flags: ["--dangerously-skip-permissions"],
131
+ max_tokens: 200_000,
132
+ timeout: 600
133
+ }
134
+ },
135
+ auth: {
136
+ api_key_env: "ANTHROPIC_API_KEY"
137
+ },
138
+ endpoints: {
139
+ default: "https://api.anthropic.com/v1/messages"
140
+ },
141
+ features: {
142
+ file_upload: true,
143
+ code_generation: true,
144
+ analysis: true,
145
+ vision: true
146
+ },
147
+ monitoring: {
148
+ enabled: true,
149
+ metrics_interval: 60
150
+ }
151
+ },
152
+ macos: {
153
+ type: "passthrough",
154
+ priority: 4,
155
+ underlying_service: "cursor",
156
+ models: ["cursor-chat"],
157
+ features: {
158
+ file_upload: false,
159
+ code_generation: true,
160
+ analysis: true,
161
+ interactive: true
162
+ }
163
+ }
164
+ }
165
+ }.freeze
166
+
8
167
  def self.load(project_dir = Dir.pwd)
9
- config_file = File.join(project_dir, ".aidp.yml")
168
+ # Try new aidp.yml format first, then fall back to .aidp.yml
169
+ config_file = File.join(project_dir, "aidp.yml")
170
+ legacy_config_file = File.join(project_dir, ".aidp.yml")
171
+
10
172
  if File.exist?(config_file)
11
- YAML.load_file(config_file) || {}
173
+ load_yaml_config(config_file)
174
+ elsif File.exist?(legacy_config_file)
175
+ load_yaml_config(legacy_config_file)
12
176
  else
13
177
  {}
14
178
  end
15
179
  end
16
180
 
17
- def self.templates_root
18
- File.join(Dir.pwd, "templates")
181
+ # Load harness configuration with defaults
182
+ def self.load_harness_config(project_dir = Dir.pwd)
183
+ config = load(project_dir)
184
+ merge_harness_defaults(config)
185
+ end
186
+
187
+ # Validate harness configuration
188
+ def self.validate_harness_config(config, project_dir = Dir.pwd)
189
+ errors = []
190
+
191
+ # Validate harness section (check the merged config, not original)
192
+ harness_config = config[:harness] || config["harness"]
193
+ if harness_config
194
+ unless harness_config[:default_provider] || harness_config["default_provider"]
195
+ errors << "Default provider not specified in harness config"
196
+ end
197
+ end
198
+
199
+ # Validate providers section using config_validator
200
+ # Only validate providers that exist in the original YAML file, not merged defaults
201
+ original_config = load(project_dir)
202
+ original_providers = original_config[:providers] || original_config["providers"]
203
+ if original_providers&.any?
204
+ require_relative "harness/config_validator"
205
+ validator = Aidp::Harness::ConfigValidator.new(project_dir)
206
+
207
+ original_providers.each do |provider_name, _provider_config|
208
+ validation_result = validator.validate_provider(provider_name)
209
+ unless validation_result[:valid]
210
+ errors.concat(validation_result[:errors])
211
+ end
212
+ end
213
+ end
214
+
215
+ errors
216
+ end
217
+
218
+ # Get harness configuration
219
+ def self.harness_config(project_dir = Dir.pwd)
220
+ config = load_harness_config(project_dir)
221
+ harness_section = config[:harness] || config["harness"] || {}
222
+
223
+ # Convert string keys to symbols for consistency
224
+ symbolize_keys(harness_section)
225
+ end
226
+
227
+ # Get provider configuration
228
+ def self.provider_config(provider_name, project_dir = Dir.pwd)
229
+ config = load_harness_config(project_dir)
230
+ providers_section = config[:providers] || config["providers"] || {}
231
+ provider_config = providers_section[provider_name.to_s] || providers_section[provider_name.to_sym] || {}
232
+
233
+ symbolize_keys(provider_config)
19
234
  end
20
235
 
21
- def self.analyze_templates_root
22
- File.join(Dir.pwd, "templates", "ANALYZE")
236
+ # Get all configured providers
237
+ def self.configured_providers(project_dir = Dir.pwd)
238
+ config = load_harness_config(project_dir)
239
+ providers_section = config[:providers] || config["providers"] || {}
240
+ providers_section.keys.map(&:to_s)
23
241
  end
24
242
 
25
- def self.execute_templates_root
26
- File.join(Dir.pwd, "templates", "EXECUTE")
243
+ # Check if configuration file exists
244
+ def self.config_exists?(project_dir = Dir.pwd)
245
+ File.exist?(File.join(project_dir, "aidp.yml")) ||
246
+ File.exist?(File.join(project_dir, ".aidp.yml"))
27
247
  end
28
248
 
29
- def self.common_templates_root
30
- File.join(Dir.pwd, "templates", "COMMON")
249
+ # Create example configuration file
250
+ def self.create_example_config(project_dir = Dir.pwd)
251
+ config_path = File.join(project_dir, "aidp.yml")
252
+ return false if File.exist?(config_path)
253
+
254
+ example_config = {
255
+ harness: {
256
+ max_retries: 2,
257
+ default_provider: "cursor",
258
+ fallback_providers: ["cursor"],
259
+ restrict_to_non_byok: false
260
+ },
261
+ providers: {
262
+ cursor: {
263
+ type: "package",
264
+ default_flags: []
265
+ },
266
+ claude: {
267
+ type: "api",
268
+ max_tokens: 100_000,
269
+ default_flags: ["--dangerously-skip-permissions"]
270
+ },
271
+ gemini: {
272
+ type: "api",
273
+ max_tokens: 50_000,
274
+ default_flags: []
275
+ }
276
+ }
277
+ }
278
+
279
+ File.write(config_path, YAML.dump(example_config))
280
+ true
281
+ end
282
+
283
+ private_class_method def self.load_yaml_config(config_file)
284
+ YAML.load_file(config_file) || {}
285
+ rescue => e
286
+ warn "Failed to load configuration file #{config_file}: #{e.message}"
287
+ {}
288
+ end
289
+
290
+ private_class_method def self.merge_harness_defaults(config)
291
+ merged = DEFAULT_HARNESS_CONFIG.dup
292
+
293
+ # Deep merge harness config
294
+ if config[:harness] || config["harness"]
295
+ harness_section = config[:harness] || config["harness"]
296
+ merged[:harness] = merged[:harness].merge(symbolize_keys(harness_section))
297
+ end
298
+
299
+ # Deep merge provider configs
300
+ if config[:providers] || config["providers"]
301
+ providers_section = config[:providers] || config["providers"]
302
+ merged[:providers] = merged[:providers].dup
303
+ providers_section.each do |provider, provider_config|
304
+ merged[:providers][provider.to_sym] = (merged[:providers][provider.to_sym] || {}).merge(symbolize_keys(provider_config))
305
+ end
306
+ end
307
+
308
+ merged
309
+ end
310
+
311
+ private_class_method def self.symbolize_keys(hash)
312
+ return hash unless hash.is_a?(Hash)
313
+
314
+ hash.each_with_object({}) do |(key, value), result|
315
+ new_key = key.is_a?(String) ? key.to_sym : key
316
+ new_value = value.is_a?(Hash) ? symbolize_keys(value) : value
317
+ result[new_key] = new_value
318
+ end
31
319
  end
32
320
  end
33
321
  end
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "json"
5
+ require "pastel"
6
+
7
+ module Aidp
8
+ # Debug logger that outputs to both console and a single log file
9
+ class DebugLogger
10
+ LOG_LEVELS = {
11
+ debug: 0,
12
+ info: 1,
13
+ warn: 2,
14
+ error: 3
15
+ }.freeze
16
+
17
+ def initialize(log_dir: nil)
18
+ @log_dir = log_dir || default_log_dir
19
+ @log_file = nil
20
+ @run_started = false
21
+ ensure_log_directory
22
+ log_run_banner
23
+ end
24
+
25
+ def log(message, level: :info, data: nil)
26
+ timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S.%3N")
27
+ level_str = level.to_s.upcase.ljust(5)
28
+
29
+ # Format message with timestamp and level
30
+ formatted_message = "[#{timestamp}] #{level_str} #{message}"
31
+
32
+ # Add data if present
33
+ if data && !data.empty?
34
+ data_str = format_data(data)
35
+ formatted_message += "\n#{data_str}" if data_str
36
+ end
37
+
38
+ # Output to console
39
+ output_to_console(formatted_message, level)
40
+
41
+ # Output to log file
42
+ output_to_file(formatted_message, level, data)
43
+ end
44
+
45
+ def close
46
+ @log_file&.close
47
+ @log_file = nil
48
+ end
49
+
50
+ private
51
+
52
+ def default_log_dir
53
+ File.join(Dir.pwd, ".aidp", "debug_logs")
54
+ end
55
+
56
+ def ensure_log_directory
57
+ FileUtils.mkdir_p(@log_dir) unless Dir.exist?(@log_dir)
58
+ end
59
+
60
+ def log_file_path
61
+ @log_file_path ||= File.join(@log_dir, "aidp_debug.log")
62
+ end
63
+
64
+ def get_log_file
65
+ return @log_file if @log_file && !@log_file.closed?
66
+
67
+ @log_file = File.open(log_file_path, "a")
68
+ @log_file
69
+ end
70
+
71
+ def log_run_banner
72
+ return if @run_started
73
+ @run_started = true
74
+
75
+ timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S.%3N")
76
+ command_line = build_command_line
77
+
78
+ banner = <<~BANNER
79
+
80
+ ================================================================================
81
+ AIDP DEBUG SESSION STARTED
82
+ ================================================================================
83
+ Timestamp: #{timestamp}
84
+ Command: #{command_line}
85
+ Working Directory: #{Dir.pwd}
86
+ Debug Level: #{ENV["DEBUG"] || "0"}
87
+ ================================================================================
88
+ BANNER
89
+
90
+ # Write banner to log file
91
+ file = get_log_file
92
+ file.puts banner
93
+ file.flush
94
+
95
+ # Also output to console if debug is enabled
96
+ if ENV["DEBUG"] && ENV["DEBUG"].to_i > 0
97
+ puts "\e[36m#{banner}\e[0m" # Cyan color
98
+ end
99
+ end
100
+
101
+ def build_command_line
102
+ # Get the command line arguments
103
+ cmd_parts = []
104
+
105
+ # Add the main command (aidp)
106
+ cmd_parts << "aidp"
107
+
108
+ # Add any arguments from ARGV
109
+ if defined?(ARGV) && !ARGV.empty?
110
+ cmd_parts.concat(ARGV)
111
+ end
112
+
113
+ # Add environment variables that affect behavior
114
+ env_vars = []
115
+ env_vars << "DEBUG=#{ENV["DEBUG"]}" if ENV["DEBUG"]
116
+ env_vars << "AIDP_CONFIG=#{ENV["AIDP_CONFIG"]}" if ENV["AIDP_CONFIG"]
117
+
118
+ if env_vars.any?
119
+ cmd_parts << "(" + env_vars.join(" ") + ")"
120
+ end
121
+
122
+ cmd_parts.join(" ")
123
+ end
124
+
125
+ def output_to_console(message, level)
126
+ case level
127
+ when :error
128
+ warn message
129
+ when :warn
130
+ puts "\e[33m#{message}\e[0m" # Yellow
131
+ when :info
132
+ puts "\e[36m#{message}\e[0m" # Cyan
133
+ when :debug
134
+ puts "\e[90m#{message}\e[0m" # Gray
135
+ else
136
+ puts message
137
+ end
138
+ end
139
+
140
+ def output_to_file(message, level, data)
141
+ file = get_log_file
142
+ file.puts message
143
+
144
+ # Add structured data if present
145
+ if data && !data.empty?
146
+ structured_data = {
147
+ timestamp: Time.now.strftime("%Y-%m-%dT%H:%M:%S.%3N%z"),
148
+ level: level.to_s,
149
+ message: message,
150
+ data: data
151
+ }
152
+ file.puts "DATA: #{JSON.generate(structured_data)}"
153
+ end
154
+
155
+ file.flush
156
+ end
157
+
158
+ def format_data(data)
159
+ return nil if data.nil? || data.empty?
160
+
161
+ case data
162
+ when Hash
163
+ format_hash_data(data)
164
+ when Array
165
+ format_array_data(data)
166
+ when String
167
+ (data.length > 200) ? "#{data[0..200]}..." : data
168
+ else
169
+ data.to_s
170
+ end
171
+ end
172
+
173
+ def format_hash_data(hash)
174
+ lines = []
175
+ hash.each do |key, value|
176
+ lines << if value.is_a?(String) && value.length > 100
177
+ " #{key}: #{value[0..100]}..."
178
+ elsif value.is_a?(Hash) || value.is_a?(Array)
179
+ " #{key}: #{JSON.pretty_generate(value).gsub("\n", "\n ")}"
180
+ else
181
+ " #{key}: #{value}"
182
+ end
183
+ end
184
+ lines.join("\n")
185
+ end
186
+
187
+ def format_array_data(array)
188
+ if array.length > 10
189
+ "#{array.first(5).join(", ")}... (#{array.length} total items)"
190
+ else
191
+ array.join(", ")
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "debug_logger"
4
+
5
+ module Aidp
6
+ # Mixin module for easy debug integration across the codebase
7
+ module DebugMixin
8
+ # Debug levels
9
+ DEBUG_OFF = 0
10
+ DEBUG_BASIC = 1 # Commands and stderr
11
+ DEBUG_VERBOSE = 2 # Everything including prompts and stdout
12
+
13
+ def self.included(base)
14
+ base.extend(ClassMethods)
15
+ end
16
+
17
+ module ClassMethods
18
+ # Class-level debug configuration
19
+ def debug_enabled?
20
+ ENV["DEBUG"] && ENV["DEBUG"].to_i > 0
21
+ end
22
+
23
+ def debug_level
24
+ ENV["DEBUG"]&.to_i || DEBUG_OFF
25
+ end
26
+ end
27
+
28
+ # Shared logger instance across all classes using DebugMixin
29
+ def self.shared_logger
30
+ @shared_logger ||= Aidp::DebugLogger.new
31
+ end
32
+
33
+ # Instance-level debug methods
34
+ def debug_enabled?
35
+ self.class.debug_enabled?
36
+ end
37
+
38
+ def debug_level
39
+ self.class.debug_level
40
+ end
41
+
42
+ def debug_basic?
43
+ debug_level >= DEBUG_BASIC
44
+ end
45
+
46
+ def debug_verbose?
47
+ debug_level >= DEBUG_VERBOSE
48
+ end
49
+
50
+ # Get or create debug logger instance (shared across all instances)
51
+ def debug_logger
52
+ Aidp::DebugMixin.shared_logger
53
+ end
54
+
55
+ # Log debug information with automatic level detection
56
+ def debug_log(message, level: :info, data: nil)
57
+ return unless debug_enabled?
58
+
59
+ debug_logger.log(message, level: level, data: data)
60
+ end
61
+
62
+ # Log command execution with debug details
63
+ def debug_command(cmd, args: [], input: nil, output: nil, error: nil, exit_code: nil)
64
+ return unless debug_basic?
65
+
66
+ command_str = [cmd, *args].join(" ")
67
+
68
+ debug_log("🔧 Executing command: #{command_str}", level: :info)
69
+
70
+ if input
71
+ if input.is_a?(String) && input.length > 200
72
+ # If input is long, show first 100 chars and indicate it's truncated
73
+ debug_log("📝 Input (truncated): #{input[0..100]}...", level: :info)
74
+ elsif input.is_a?(String) && File.exist?(input)
75
+ debug_log("📝 Input file: #{input}", level: :info)
76
+ else
77
+ debug_log("📝 Input: #{input}", level: :info)
78
+ end
79
+ end
80
+
81
+ if error && !error.empty?
82
+ debug_log("❌ Error output: #{error}", level: :error)
83
+ end
84
+
85
+ if debug_verbose?
86
+ if output && !output.empty?
87
+ debug_log("📤 Output: #{output}", level: :info)
88
+ end
89
+
90
+ if exit_code
91
+ debug_log("🏁 Exit code: #{exit_code}", level: :info)
92
+ end
93
+ end
94
+ end
95
+
96
+ # Log step execution with context
97
+ def debug_step(step_name, action, details = {})
98
+ return unless debug_basic?
99
+
100
+ message = "🔄 #{action}: #{step_name}"
101
+ if details.any?
102
+ detail_str = details.map { |k, v| "#{k}=#{v}" }.join(", ")
103
+ message += " (#{detail_str})"
104
+ end
105
+
106
+ debug_log(message, level: :info, data: details)
107
+ end
108
+
109
+ # Log provider interaction
110
+ def debug_provider(provider_name, action, details = {})
111
+ return unless debug_basic?
112
+
113
+ message = "🤖 #{provider_name}: #{action}"
114
+ if details.any?
115
+ detail_str = details.map { |k, v| "#{k}=#{v}" }.join(", ")
116
+ message += " (#{detail_str})"
117
+ end
118
+
119
+ debug_log(message, level: :info, data: details)
120
+ end
121
+
122
+ # Log error with debug context
123
+ def debug_error(error, context = {})
124
+ return unless debug_basic?
125
+
126
+ error_message = "💥 Error: #{error.class.name}: #{error.message}"
127
+ debug_log(error_message, level: :error, data: {error: error, context: context})
128
+
129
+ if debug_verbose? && error.backtrace
130
+ debug_log("📍 Backtrace: #{error.backtrace.first(5).join("\n")}", level: :error)
131
+ end
132
+ end
133
+
134
+ # Log timing information
135
+ def debug_timing(operation, duration, details = {})
136
+ return unless debug_verbose?
137
+
138
+ message = "⏱️ #{operation}: #{duration.round(2)}s"
139
+ if details.any?
140
+ detail_str = details.map { |k, v| "#{k}=#{v}" }.join(", ")
141
+ message += " (#{detail_str})"
142
+ end
143
+
144
+ debug_log(message, level: :info, data: {duration: duration, details: details})
145
+ end
146
+
147
+ # Execute command with debug logging
148
+ def debug_execute_command(cmd, args: [], input: nil, timeout: nil, **options)
149
+ require "tty-command"
150
+
151
+ command_str = [cmd, *args].join(" ")
152
+ start_time = Time.now
153
+
154
+ debug_log("🚀 Starting command execution: #{command_str}", level: :info)
155
+
156
+ begin
157
+ cmd_obj = TTY::Command.new(printer: :null) # Disable TTY::Command's own output
158
+
159
+ # Prepare input
160
+ input_data = nil
161
+ if input
162
+ if input.is_a?(String) && File.exist?(input)
163
+ input_data = File.read(input)
164
+ debug_log("📁 Reading input from file: #{input}", level: :info)
165
+ else
166
+ input_data = input
167
+ end
168
+ end
169
+
170
+ # Execute command
171
+ result = cmd_obj.run(cmd, *args, input: input_data, timeout: timeout, **options)
172
+
173
+ duration = Time.now - start_time
174
+
175
+ # Log results
176
+ debug_command(cmd, args: args, input: input, output: result.out, error: result.err, exit_code: result.exit_status)
177
+ debug_timing("Command execution", duration, {exit_code: result.exit_status})
178
+
179
+ result
180
+ rescue => e
181
+ duration = Time.now - start_time
182
+ debug_error(e, {command: command_str, duration: duration})
183
+ raise
184
+ end
185
+ end
186
+ end
187
+ end