aidp 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +60 -214
- data/bin/aidp +1 -1
- data/lib/aidp/analysis/kb_inspector.rb +38 -23
- data/lib/aidp/analysis/seams.rb +2 -31
- data/lib/aidp/analysis/tree_sitter_grammar_loader.rb +1 -13
- data/lib/aidp/analysis/tree_sitter_scan.rb +3 -20
- data/lib/aidp/analyze/error_handler.rb +2 -75
- data/lib/aidp/analyze/json_file_storage.rb +292 -0
- data/lib/aidp/analyze/progress.rb +12 -0
- data/lib/aidp/analyze/progress_visualizer.rb +12 -17
- data/lib/aidp/analyze/ruby_maat_integration.rb +13 -31
- data/lib/aidp/analyze/runner.rb +256 -87
- data/lib/aidp/cli/jobs_command.rb +100 -432
- data/lib/aidp/cli.rb +309 -239
- data/lib/aidp/config.rb +298 -10
- data/lib/aidp/debug_logger.rb +195 -0
- data/lib/aidp/debug_mixin.rb +187 -0
- data/lib/aidp/execute/progress.rb +9 -0
- data/lib/aidp/execute/runner.rb +221 -40
- data/lib/aidp/execute/steps.rb +17 -7
- data/lib/aidp/execute/workflow_selector.rb +211 -0
- data/lib/aidp/harness/completion_checker.rb +268 -0
- data/lib/aidp/harness/condition_detector.rb +1526 -0
- data/lib/aidp/harness/config_loader.rb +373 -0
- data/lib/aidp/harness/config_manager.rb +382 -0
- data/lib/aidp/harness/config_schema.rb +1006 -0
- data/lib/aidp/harness/config_validator.rb +355 -0
- data/lib/aidp/harness/configuration.rb +477 -0
- data/lib/aidp/harness/enhanced_runner.rb +494 -0
- data/lib/aidp/harness/error_handler.rb +616 -0
- data/lib/aidp/harness/provider_config.rb +423 -0
- data/lib/aidp/harness/provider_factory.rb +306 -0
- data/lib/aidp/harness/provider_manager.rb +1269 -0
- data/lib/aidp/harness/provider_type_checker.rb +88 -0
- data/lib/aidp/harness/runner.rb +411 -0
- data/lib/aidp/harness/state/errors.rb +28 -0
- data/lib/aidp/harness/state/metrics.rb +219 -0
- data/lib/aidp/harness/state/persistence.rb +128 -0
- data/lib/aidp/harness/state/provider_state.rb +132 -0
- data/lib/aidp/harness/state/ui_state.rb +68 -0
- data/lib/aidp/harness/state/workflow_state.rb +123 -0
- data/lib/aidp/harness/state_manager.rb +586 -0
- data/lib/aidp/harness/status_display.rb +888 -0
- data/lib/aidp/harness/ui/base.rb +16 -0
- data/lib/aidp/harness/ui/enhanced_tui.rb +545 -0
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +252 -0
- data/lib/aidp/harness/ui/error_handler.rb +132 -0
- data/lib/aidp/harness/ui/frame_manager.rb +361 -0
- data/lib/aidp/harness/ui/job_monitor.rb +500 -0
- data/lib/aidp/harness/ui/navigation/main_menu.rb +311 -0
- data/lib/aidp/harness/ui/navigation/menu_formatter.rb +120 -0
- data/lib/aidp/harness/ui/navigation/menu_item.rb +142 -0
- data/lib/aidp/harness/ui/navigation/menu_state.rb +139 -0
- data/lib/aidp/harness/ui/navigation/submenu.rb +202 -0
- data/lib/aidp/harness/ui/navigation/workflow_selector.rb +176 -0
- data/lib/aidp/harness/ui/progress_display.rb +280 -0
- data/lib/aidp/harness/ui/question_collector.rb +141 -0
- data/lib/aidp/harness/ui/spinner_group.rb +184 -0
- data/lib/aidp/harness/ui/spinner_helper.rb +152 -0
- data/lib/aidp/harness/ui/status_manager.rb +312 -0
- data/lib/aidp/harness/ui/status_widget.rb +280 -0
- data/lib/aidp/harness/ui/workflow_controller.rb +312 -0
- data/lib/aidp/harness/user_interface.rb +2381 -0
- data/lib/aidp/provider_manager.rb +131 -7
- data/lib/aidp/providers/anthropic.rb +28 -103
- data/lib/aidp/providers/base.rb +170 -0
- data/lib/aidp/providers/cursor.rb +52 -181
- data/lib/aidp/providers/gemini.rb +24 -107
- data/lib/aidp/providers/macos_ui.rb +99 -5
- data/lib/aidp/providers/opencode.rb +194 -0
- data/lib/aidp/storage/csv_storage.rb +172 -0
- data/lib/aidp/storage/file_manager.rb +214 -0
- data/lib/aidp/storage/json_storage.rb +140 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp.rb +54 -39
- data/templates/COMMON/AGENT_BASE.md +11 -0
- data/templates/EXECUTE/00_PRD.md +4 -4
- data/templates/EXECUTE/02_ARCHITECTURE.md +5 -4
- data/templates/EXECUTE/07_TEST_PLAN.md +4 -1
- data/templates/EXECUTE/08_TASKS.md +4 -4
- data/templates/EXECUTE/10_IMPLEMENTATION_AGENT.md +4 -4
- data/templates/README.md +279 -0
- data/templates/aidp-development.yml.example +373 -0
- data/templates/aidp-minimal.yml.example +48 -0
- data/templates/aidp-production.yml.example +475 -0
- data/templates/aidp.yml.example +598 -0
- metadata +93 -69
- data/lib/aidp/analyze/agent_personas.rb +0 -71
- data/lib/aidp/analyze/agent_tool_executor.rb +0 -439
- data/lib/aidp/analyze/data_retention_manager.rb +0 -421
- data/lib/aidp/analyze/database.rb +0 -260
- data/lib/aidp/analyze/dependencies.rb +0 -335
- data/lib/aidp/analyze/export_manager.rb +0 -418
- data/lib/aidp/analyze/focus_guidance.rb +0 -517
- data/lib/aidp/analyze/incremental_analyzer.rb +0 -533
- data/lib/aidp/analyze/language_analysis_strategies.rb +0 -897
- data/lib/aidp/analyze/large_analysis_progress.rb +0 -499
- data/lib/aidp/analyze/memory_manager.rb +0 -339
- data/lib/aidp/analyze/metrics_storage.rb +0 -336
- data/lib/aidp/analyze/parallel_processor.rb +0 -454
- data/lib/aidp/analyze/performance_optimizer.rb +0 -691
- data/lib/aidp/analyze/repository_chunker.rb +0 -697
- data/lib/aidp/analyze/static_analysis_detector.rb +0 -577
- data/lib/aidp/analyze/storage.rb +0 -655
- data/lib/aidp/analyze/tool_configuration.rb +0 -441
- data/lib/aidp/analyze/tool_modernization.rb +0 -750
- data/lib/aidp/database/pg_adapter.rb +0 -148
- data/lib/aidp/database_config.rb +0 -69
- data/lib/aidp/database_connection.rb +0 -72
- data/lib/aidp/job_manager.rb +0 -41
- data/lib/aidp/jobs/base_job.rb +0 -45
- data/lib/aidp/jobs/provider_execution_job.rb +0 -83
- data/lib/aidp/project_detector.rb +0 -117
- data/lib/aidp/providers/agent_supervisor.rb +0 -348
- data/lib/aidp/providers/supervised_base.rb +0 -317
- data/lib/aidp/providers/supervised_cursor.rb +0 -22
- data/lib/aidp/sync.rb +0 -13
- 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
|
-
|
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
|
-
|
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
|
-
|
18
|
-
|
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
|
-
|
22
|
-
|
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
|
-
|
26
|
-
|
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
|
-
|
30
|
-
|
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
|