aidp 0.3.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +191 -5
- data/lib/aidp/analysis/kb_inspector.rb +456 -0
- data/lib/aidp/analysis/seams.rb +188 -0
- data/lib/aidp/analysis/tree_sitter_grammar_loader.rb +493 -0
- data/lib/aidp/analysis/tree_sitter_scan.rb +703 -0
- data/lib/aidp/analyze/agent_personas.rb +1 -1
- data/lib/aidp/analyze/agent_tool_executor.rb +5 -11
- data/lib/aidp/analyze/data_retention_manager.rb +0 -5
- data/lib/aidp/analyze/database.rb +99 -82
- data/lib/aidp/analyze/error_handler.rb +12 -79
- data/lib/aidp/analyze/export_manager.rb +0 -7
- data/lib/aidp/analyze/focus_guidance.rb +2 -2
- data/lib/aidp/analyze/incremental_analyzer.rb +1 -11
- data/lib/aidp/analyze/large_analysis_progress.rb +0 -5
- data/lib/aidp/analyze/memory_manager.rb +34 -60
- data/lib/aidp/analyze/metrics_storage.rb +336 -0
- data/lib/aidp/analyze/parallel_processor.rb +0 -6
- data/lib/aidp/analyze/performance_optimizer.rb +0 -3
- data/lib/aidp/analyze/prioritizer.rb +2 -2
- data/lib/aidp/analyze/repository_chunker.rb +14 -21
- data/lib/aidp/analyze/ruby_maat_integration.rb +6 -102
- data/lib/aidp/analyze/runner.rb +107 -191
- data/lib/aidp/analyze/steps.rb +35 -30
- data/lib/aidp/analyze/storage.rb +233 -178
- data/lib/aidp/analyze/tool_configuration.rb +21 -36
- data/lib/aidp/cli/jobs_command.rb +489 -0
- data/lib/aidp/cli/terminal_io.rb +52 -0
- data/lib/aidp/cli.rb +160 -45
- data/lib/aidp/core_ext/class_attribute.rb +36 -0
- data/lib/aidp/database/pg_adapter.rb +148 -0
- data/lib/aidp/database_config.rb +69 -0
- data/lib/aidp/database_connection.rb +72 -0
- data/lib/aidp/execute/runner.rb +65 -92
- data/lib/aidp/execute/steps.rb +81 -82
- data/lib/aidp/job_manager.rb +41 -0
- data/lib/aidp/jobs/base_job.rb +45 -0
- data/lib/aidp/jobs/provider_execution_job.rb +83 -0
- data/lib/aidp/provider_manager.rb +25 -0
- data/lib/aidp/providers/agent_supervisor.rb +348 -0
- data/lib/aidp/providers/anthropic.rb +160 -3
- data/lib/aidp/providers/base.rb +153 -6
- data/lib/aidp/providers/cursor.rb +245 -43
- data/lib/aidp/providers/gemini.rb +164 -3
- data/lib/aidp/providers/supervised_base.rb +317 -0
- data/lib/aidp/providers/supervised_cursor.rb +22 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp.rb +31 -34
- data/templates/ANALYZE/01_REPOSITORY_ANALYSIS.md +4 -4
- data/templates/ANALYZE/06a_tree_sitter_scan.md +217 -0
- metadata +91 -36
data/lib/aidp/analyze/runner.rb
CHANGED
@@ -1,98 +1,125 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
4
|
-
require "
|
5
|
-
require "
|
3
|
+
require "timeout"
|
4
|
+
require "que"
|
5
|
+
require "sequel"
|
6
6
|
|
7
7
|
module Aidp
|
8
8
|
module Analyze
|
9
|
-
# Handles execution logic for analyze mode steps
|
10
9
|
class Runner
|
11
|
-
attr_reader :project_dir, :progress
|
12
|
-
|
13
10
|
def initialize(project_dir)
|
14
11
|
@project_dir = project_dir
|
15
|
-
@progress = Aidp::Analyze::Progress.new(project_dir)
|
16
12
|
end
|
17
13
|
|
18
|
-
def
|
19
|
-
|
14
|
+
def progress
|
15
|
+
@progress ||= Aidp::Analyze::Progress.new(@project_dir)
|
16
|
+
end
|
20
17
|
|
18
|
+
def run_step(step_name, options = {})
|
19
|
+
# Always validate step exists first, even in mock mode
|
21
20
|
step_spec = Aidp::Analyze::Steps::SPEC[step_name]
|
22
|
-
|
21
|
+
raise "Step '#{step_name}' not found" unless step_spec
|
23
22
|
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
if should_use_mock_mode?(options)
|
24
|
+
result = options[:simulate_error] ?
|
25
|
+
{status: "error", error: options[:simulate_error]} :
|
26
|
+
mock_execution_result
|
27
27
|
|
28
|
-
|
29
|
-
|
28
|
+
# Add focus areas and export formats to mock result if provided
|
29
|
+
result[:focus_areas] = options[:focus]&.split(",") if options[:focus]
|
30
|
+
result[:export_formats] = options[:format]&.split(",") if options[:format]
|
30
31
|
|
31
|
-
|
32
|
-
if options[:simulate_error]
|
33
|
-
return {
|
34
|
-
status: "error",
|
35
|
-
error: options[:simulate_error],
|
36
|
-
step: step_name
|
37
|
-
}
|
32
|
+
return result
|
38
33
|
end
|
39
34
|
|
40
|
-
#
|
41
|
-
|
42
|
-
status: "success",
|
43
|
-
step: step_name,
|
44
|
-
output_files: step_spec["outs"],
|
45
|
-
prompt: prompt,
|
46
|
-
agent: step_spec["agent"]
|
47
|
-
}
|
48
|
-
|
49
|
-
# Add test-specific fields based on options
|
50
|
-
result[:force_used] = true if options[:force]
|
51
|
-
|
52
|
-
result[:rerun_used] = true if options[:rerun]
|
35
|
+
# Set up database connection for background jobs
|
36
|
+
setup_database_connection
|
53
37
|
|
54
|
-
|
55
|
-
|
56
|
-
|
38
|
+
puts "🚀 Enqueuing background job for step: #{step_name}"
|
39
|
+
job = Aidp::Jobs::ProviderExecutionJob.enqueue(
|
40
|
+
provider_type: "cursor",
|
41
|
+
prompt: composed_prompt(step_name, options),
|
42
|
+
metadata: {
|
43
|
+
step_name: step_name,
|
44
|
+
project_dir: @project_dir
|
45
|
+
}
|
46
|
+
)
|
47
|
+
puts "✅ Job enqueued with ID: #{job}"
|
57
48
|
|
58
|
-
|
59
|
-
|
49
|
+
wait_for_job_completion(job)
|
50
|
+
end
|
60
51
|
|
61
|
-
|
62
|
-
result[:warnings] = ["Network timeout"] if options[:simulate_network_error]
|
52
|
+
private
|
63
53
|
|
64
|
-
|
65
|
-
if
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
54
|
+
def setup_database_connection
|
55
|
+
# Skip database setup in test mode if we're mocking
|
56
|
+
return if ENV["RACK_ENV"] == "test" && ENV["MOCK_DATABASE"] == "true"
|
57
|
+
|
58
|
+
dbname = (ENV["RACK_ENV"] == "test") ? "aidp_test" : (ENV["AIDP_DB_NAME"] || "aidp")
|
59
|
+
|
60
|
+
# Use Sequel for connection pooling with timeout
|
61
|
+
Timeout.timeout(10) do
|
62
|
+
Que.connection = Sequel.connect(
|
63
|
+
adapter: "postgres",
|
64
|
+
host: ENV["AIDP_DB_HOST"] || "localhost",
|
65
|
+
port: ENV["AIDP_DB_PORT"] || 5432,
|
66
|
+
database: dbname,
|
67
|
+
user: ENV["AIDP_DB_USER"] || ENV["USER"],
|
68
|
+
password: ENV["AIDP_DB_PASSWORD"],
|
69
|
+
max_connections: 10,
|
70
|
+
pool_timeout: 30
|
71
|
+
)
|
72
|
+
|
73
|
+
Que.migrate!(version: Que::Migrations::CURRENT_VERSION)
|
70
74
|
end
|
75
|
+
rescue Timeout::Error
|
76
|
+
puts "Database connection timed out"
|
77
|
+
raise
|
78
|
+
rescue => e
|
79
|
+
puts "Error connecting to database: #{e.message}"
|
80
|
+
raise
|
81
|
+
end
|
71
82
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
# Generate database export for any step
|
79
|
-
generate_database_export
|
80
|
-
|
81
|
-
# Generate tool configuration file for static analysis step
|
82
|
-
generate_tool_configuration if step_name == "06_STATIC_ANALYSIS"
|
83
|
-
|
84
|
-
# Generate summary report if this is the last step
|
85
|
-
generate_summary_report if step_name == "07_REFACTORING_RECOMMENDATIONS"
|
83
|
+
def should_use_mock_mode?(options)
|
84
|
+
return false if options[:background] # Force background jobs if requested
|
85
|
+
# Only use mock mode when explicitly requested or in tests
|
86
|
+
options[:mock_mode] || ENV["AIDP_MOCK_MODE"] == "1" || ENV["RAILS_ENV"] == "test"
|
87
|
+
end
|
86
88
|
|
87
|
-
|
89
|
+
def mock_execution_result
|
90
|
+
{
|
91
|
+
status: "completed",
|
92
|
+
provider: "mock",
|
93
|
+
message: "Mock execution"
|
94
|
+
}
|
88
95
|
end
|
89
96
|
|
90
|
-
|
97
|
+
def wait_for_job_completion(job_id)
|
98
|
+
loop do
|
99
|
+
job = Que.execute("SELECT * FROM que_jobs WHERE id = $1", [job_id]).first
|
100
|
+
return {status: "completed", provider: "test_provider", message: "Analysis completed successfully"} if job && job["finished_at"] && job["error_count"] == 0
|
101
|
+
return {status: "error", error: job["last_error_message"]} if job && job["error_count"] && job["error_count"] > 0
|
102
|
+
|
103
|
+
if job && job["finished_at"].nil? && job["run_at"]
|
104
|
+
duration = Time.now - job["run_at"]
|
105
|
+
minutes = (duration / 60).to_i
|
106
|
+
seconds = (duration % 60).to_i
|
107
|
+
duration_str = (minutes > 0) ? "#{minutes}m #{seconds}s" : "#{seconds}s"
|
108
|
+
print "\r🔄 Job #{job_id} is running (#{duration_str})...".ljust(80)
|
109
|
+
else
|
110
|
+
print "\r⏳ Job #{job_id} is pending...".ljust(80)
|
111
|
+
end
|
112
|
+
$stdout.flush
|
113
|
+
sleep 1
|
114
|
+
end
|
115
|
+
ensure
|
116
|
+
print "\r" + " " * 80 + "\r"
|
117
|
+
end
|
91
118
|
|
92
119
|
def find_template(template_name)
|
93
120
|
template_search_paths.each do |path|
|
94
|
-
|
95
|
-
return
|
121
|
+
template_path = File.join(path, template_name)
|
122
|
+
return template_path if File.exist?(template_path)
|
96
123
|
end
|
97
124
|
nil
|
98
125
|
end
|
@@ -100,26 +127,21 @@ module Aidp
|
|
100
127
|
def template_search_paths
|
101
128
|
[
|
102
129
|
File.join(@project_dir, "templates", "ANALYZE"),
|
103
|
-
File.join(@project_dir, "templates", "COMMON")
|
104
|
-
File.join(@project_dir, "templates"),
|
105
|
-
File.join(File.dirname(__FILE__), "..", "..", "..", "templates", "ANALYZE"),
|
106
|
-
File.join(File.dirname(__FILE__), "..", "..", "..", "templates", "COMMON")
|
130
|
+
File.join(@project_dir, "templates", "COMMON")
|
107
131
|
]
|
108
132
|
end
|
109
133
|
|
110
|
-
def composed_prompt(
|
111
|
-
|
112
|
-
|
134
|
+
def composed_prompt(step_name, options = {})
|
135
|
+
step_spec = Aidp::Analyze::Steps::SPEC[step_name]
|
136
|
+
raise "Step '#{step_name}' not found" unless step_spec
|
113
137
|
|
114
|
-
|
115
|
-
|
116
|
-
|
138
|
+
template_name = step_spec["templates"].first
|
139
|
+
template_path = find_template(template_name)
|
140
|
+
raise "Template not found for step #{step_name}" unless template_path
|
117
141
|
|
118
|
-
|
119
|
-
persona = Aidp::Analyze::AgentPersonas.get_persona(agent_persona)
|
120
|
-
template = "# Agent Persona: #{persona["name"]}\n#{persona["description"]}\n\n#{template}" if persona
|
142
|
+
template = File.read(template_path)
|
121
143
|
|
122
|
-
# Replace
|
144
|
+
# Replace template variables in the format {{key}} with option values
|
123
145
|
options.each do |key, value|
|
124
146
|
template = template.gsub("{{#{key}}}", value.to_s)
|
125
147
|
end
|
@@ -127,118 +149,12 @@ module Aidp
|
|
127
149
|
template
|
128
150
|
end
|
129
151
|
|
130
|
-
|
131
|
-
output_files.each do |output_file|
|
132
|
-
file_path = File.join(@project_dir, output_file)
|
133
|
-
content = generate_output_content(step_name, output_file, result)
|
134
|
-
File.write(file_path, content)
|
135
|
-
end
|
136
|
-
|
137
|
-
# Handle additional export formats if specified
|
138
|
-
return unless result[:export_formats]
|
139
|
-
|
140
|
-
result[:export_formats].each do |format|
|
141
|
-
case format
|
142
|
-
when "json"
|
143
|
-
json_file = File.join(@project_dir, "#{step_name}.json")
|
144
|
-
File.write(json_file, result.to_json)
|
145
|
-
when "csv"
|
146
|
-
csv_file = File.join(@project_dir, "#{step_name}.csv")
|
147
|
-
csv_content = "step,status,agent\n#{step_name},#{result[:status]},#{result[:agent]}"
|
148
|
-
File.write(csv_file, csv_content)
|
149
|
-
end
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
def generate_output_content(step_name, output_file, result)
|
154
|
-
case output_file
|
155
|
-
when /\.md$/
|
156
|
-
# Use the actual template content if available
|
157
|
-
template_name = Aidp::Analyze::Steps::SPEC[step_name]["templates"].first
|
158
|
-
template = find_template(template_name)
|
159
|
-
if template
|
160
|
-
"# #{step_name} Analysis\n\nGenerated on #{Time.now}\n\n## Result\n\n#{result[:status]}\n\n## Agent\n\n#{result[:agent]}\n\n## Template Content\n\n#{template}"
|
161
|
-
else
|
162
|
-
"# #{step_name} Analysis\n\nGenerated on #{Time.now}\n\n## Result\n\n#{result[:status]}\n\n## Agent\n\n#{result[:agent]}"
|
163
|
-
end
|
164
|
-
when /\.json$/
|
165
|
-
result.to_json
|
166
|
-
else
|
167
|
-
"Analysis output for #{step_name}: #{result[:status]}"
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
|
-
def generate_tool_configuration
|
172
|
-
tools_file = File.join(@project_dir, ".aidp-analyze-tools.yml")
|
173
|
-
tools_config = {
|
174
|
-
"preferred_tools" => {
|
175
|
-
"ruby" => %w[rubocop reek],
|
176
|
-
"javascript" => ["eslint"]
|
177
|
-
},
|
178
|
-
"execution_settings" => {
|
179
|
-
"parallel_execution" => true
|
180
|
-
}
|
181
|
-
}
|
182
|
-
File.write(tools_file, tools_config.to_yaml)
|
183
|
-
end
|
184
|
-
|
185
|
-
def generate_summary_report
|
186
|
-
summary_file = File.join(@project_dir, "ANALYSIS_SUMMARY.md")
|
187
|
-
content = "# Analysis Summary\n\n"
|
188
|
-
content += "Generated on #{Time.now}\n\n"
|
189
|
-
|
190
|
-
step_names = {
|
191
|
-
"01_REPOSITORY_ANALYSIS" => "Repository Analysis",
|
192
|
-
"02_ARCHITECTURE_ANALYSIS" => "Architecture Analysis",
|
193
|
-
"03_TEST_ANALYSIS" => "Test Coverage Analysis",
|
194
|
-
"04_FUNCTIONALITY_ANALYSIS" => "Functionality Analysis",
|
195
|
-
"05_DOCUMENTATION_ANALYSIS" => "Documentation Analysis",
|
196
|
-
"06_STATIC_ANALYSIS" => "Static Analysis",
|
197
|
-
"07_REFACTORING_RECOMMENDATIONS" => "Refactoring Recommendations"
|
198
|
-
}
|
199
|
-
|
200
|
-
Aidp::Analyze::Steps::SPEC.keys.each do |step|
|
201
|
-
readable_name = step_names[step] || step
|
202
|
-
content += if @progress.step_completed?(step)
|
203
|
-
"## #{readable_name}\n✅ Completed\n\n"
|
204
|
-
else
|
205
|
-
"## #{readable_name}\n⏳ Pending\n\n"
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
File.write(summary_file, content)
|
210
|
-
end
|
211
|
-
|
212
|
-
def generate_database_export
|
213
|
-
database_file = File.join(@project_dir, ".aidp-analysis.db")
|
214
|
-
require "sqlite3"
|
215
|
-
|
216
|
-
begin
|
217
|
-
db = SQLite3::Database.new(database_file)
|
218
|
-
db.execute("CREATE TABLE IF NOT EXISTS analysis_results (step TEXT, status TEXT, agent TEXT, completed_at TEXT)")
|
152
|
+
private
|
219
153
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
end
|
225
|
-
end
|
226
|
-
rescue SQLite3::BusyException
|
227
|
-
# Retry once after a short delay
|
228
|
-
sleep(0.1)
|
229
|
-
db = SQLite3::Database.new(database_file)
|
230
|
-
db.execute("CREATE TABLE IF NOT EXISTS analysis_results (step TEXT, status TEXT, agent TEXT, completed_at TEXT)")
|
231
|
-
|
232
|
-
Aidp::Analyze::Steps::SPEC.keys.each do |step|
|
233
|
-
if @progress.step_completed?(step)
|
234
|
-
db.execute("INSERT INTO analysis_results (step, status, agent, completed_at) VALUES (?, ?, ?, ?)",
|
235
|
-
[step, "success", Aidp::Analyze::Steps::SPEC[step]["agent"], Time.now.iso8601])
|
236
|
-
end
|
237
|
-
end
|
238
|
-
rescue => e
|
239
|
-
# Log the error but don't fail the analysis
|
240
|
-
puts "Warning: Database export failed: #{e.message}"
|
241
|
-
end
|
154
|
+
def store_execution_metrics(step_name, result, duration)
|
155
|
+
# Store execution metrics in the database for analysis
|
156
|
+
# This is a placeholder implementation
|
157
|
+
# In a real implementation, this would connect to a database and store metrics
|
242
158
|
end
|
243
159
|
end
|
244
160
|
end
|
data/lib/aidp/analyze/steps.rb
CHANGED
@@ -2,50 +2,55 @@
|
|
2
2
|
|
3
3
|
module Aidp
|
4
4
|
module Analyze
|
5
|
-
|
6
|
-
class Steps
|
5
|
+
module Steps
|
7
6
|
SPEC = {
|
8
7
|
"01_REPOSITORY_ANALYSIS" => {
|
9
|
-
"templates" => ["
|
10
|
-
"
|
11
|
-
"
|
12
|
-
"
|
8
|
+
"templates" => ["01_repository_analysis.md"],
|
9
|
+
"description" => "Initial code-maat based repository mining",
|
10
|
+
"outs" => ["docs/analysis/repository_analysis.md"],
|
11
|
+
"gate" => false
|
13
12
|
},
|
14
13
|
"02_ARCHITECTURE_ANALYSIS" => {
|
15
|
-
"templates" => ["
|
16
|
-
"
|
17
|
-
"
|
18
|
-
"
|
14
|
+
"templates" => ["02_architecture_analysis.md"],
|
15
|
+
"description" => "Identify architectural patterns, dependencies, and violations",
|
16
|
+
"outs" => ["docs/analysis/architecture_analysis.md"],
|
17
|
+
"gate" => true
|
19
18
|
},
|
20
19
|
"03_TEST_ANALYSIS" => {
|
21
|
-
"templates" => ["
|
22
|
-
"
|
23
|
-
"
|
24
|
-
"
|
20
|
+
"templates" => ["03_test_analysis.md"],
|
21
|
+
"description" => "Analyze existing test coverage and identify gaps",
|
22
|
+
"outs" => ["docs/analysis/test_analysis.md"],
|
23
|
+
"gate" => false
|
25
24
|
},
|
26
25
|
"04_FUNCTIONALITY_ANALYSIS" => {
|
27
|
-
"templates" => ["
|
28
|
-
"
|
29
|
-
"
|
30
|
-
"
|
26
|
+
"templates" => ["04_functionality_analysis.md"],
|
27
|
+
"description" => "Map features, identify dead code, analyze complexity",
|
28
|
+
"outs" => ["docs/analysis/functionality_analysis.md"],
|
29
|
+
"gate" => false
|
31
30
|
},
|
32
31
|
"05_DOCUMENTATION_ANALYSIS" => {
|
33
|
-
"templates" => ["
|
34
|
-
"
|
35
|
-
"
|
36
|
-
"
|
32
|
+
"templates" => ["05_documentation_analysis.md"],
|
33
|
+
"description" => "Identify missing documentation and generate what's needed",
|
34
|
+
"outs" => ["docs/analysis/documentation_analysis.md"],
|
35
|
+
"gate" => false
|
37
36
|
},
|
38
37
|
"06_STATIC_ANALYSIS" => {
|
39
|
-
"templates" => ["
|
40
|
-
"
|
41
|
-
"
|
42
|
-
"
|
38
|
+
"templates" => ["06_static_analysis.md"],
|
39
|
+
"description" => "Check for existing tools and recommend improvements",
|
40
|
+
"outs" => ["docs/analysis/static_analysis.md"],
|
41
|
+
"gate" => false
|
42
|
+
},
|
43
|
+
"06A_TREE_SITTER_SCAN" => {
|
44
|
+
"templates" => ["06a_tree_sitter_scan.md"],
|
45
|
+
"description" => "Tree-sitter powered static analysis to build knowledge base",
|
46
|
+
"outs" => [".aidp/kb/symbols.json", ".aidp/kb/seams.json", ".aidp/kb/hotspots.json"],
|
47
|
+
"gate" => false
|
43
48
|
},
|
44
49
|
"07_REFACTORING_RECOMMENDATIONS" => {
|
45
|
-
"templates" => ["
|
46
|
-
"
|
47
|
-
"
|
48
|
-
"
|
50
|
+
"templates" => ["07_refactoring_recommendations.md"],
|
51
|
+
"description" => "Provide actionable refactoring guidance",
|
52
|
+
"outs" => ["docs/analysis/refactoring_recommendations.md"],
|
53
|
+
"gate" => true
|
49
54
|
}
|
50
55
|
}.freeze
|
51
56
|
end
|