aidp 0.3.0 → 0.5.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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +59 -4
  3. data/lib/aidp/analyze/agent_personas.rb +1 -1
  4. data/lib/aidp/analyze/database.rb +99 -82
  5. data/lib/aidp/analyze/error_handler.rb +12 -76
  6. data/lib/aidp/analyze/focus_guidance.rb +2 -2
  7. data/lib/aidp/analyze/metrics_storage.rb +336 -0
  8. data/lib/aidp/analyze/prioritizer.rb +2 -2
  9. data/lib/aidp/analyze/ruby_maat_integration.rb +6 -102
  10. data/lib/aidp/analyze/runner.rb +107 -191
  11. data/lib/aidp/analyze/steps.rb +29 -30
  12. data/lib/aidp/analyze/storage.rb +233 -171
  13. data/lib/aidp/cli/jobs_command.rb +489 -0
  14. data/lib/aidp/cli/terminal_io.rb +52 -0
  15. data/lib/aidp/cli.rb +104 -45
  16. data/lib/aidp/core_ext/class_attribute.rb +36 -0
  17. data/lib/aidp/database/pg_adapter.rb +148 -0
  18. data/lib/aidp/database_config.rb +69 -0
  19. data/lib/aidp/database_connection.rb +72 -0
  20. data/lib/aidp/database_migration.rb +158 -0
  21. data/lib/aidp/execute/runner.rb +65 -92
  22. data/lib/aidp/execute/steps.rb +81 -82
  23. data/lib/aidp/job_manager.rb +41 -0
  24. data/lib/aidp/jobs/base_job.rb +47 -0
  25. data/lib/aidp/jobs/provider_execution_job.rb +96 -0
  26. data/lib/aidp/provider_manager.rb +25 -0
  27. data/lib/aidp/providers/agent_supervisor.rb +348 -0
  28. data/lib/aidp/providers/anthropic.rb +166 -3
  29. data/lib/aidp/providers/base.rb +153 -6
  30. data/lib/aidp/providers/cursor.rb +247 -43
  31. data/lib/aidp/providers/gemini.rb +166 -3
  32. data/lib/aidp/providers/supervised_base.rb +317 -0
  33. data/lib/aidp/providers/supervised_cursor.rb +22 -0
  34. data/lib/aidp/version.rb +1 -1
  35. data/lib/aidp.rb +25 -34
  36. data/templates/ANALYZE/01_REPOSITORY_ANALYSIS.md +4 -4
  37. metadata +72 -35
@@ -1,98 +1,125 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "erb"
4
- require "yaml"
5
- require "json"
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 run_step(step_name, options = {})
19
- raise "Step '#{step_name}' not found in analyze mode steps" unless Aidp::Analyze::Steps::SPEC.key?(step_name)
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
- template_name = step_spec["templates"].first
21
+ raise "Step '#{step_name}' not found" unless step_spec
23
22
 
24
- # Load template
25
- template = find_template(template_name)
26
- raise "Template '#{template_name}' not found" unless template
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
- # Compose prompt with agent persona
29
- prompt = composed_prompt(template_name, step_spec["agent"], options)
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
- # Handle error simulation for tests
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
- # Execute step (mock for now)
41
- result = {
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
- result[:focus_areas] = options[:focus].split(",") if options[:focus]
55
-
56
- result[:export_formats] = options[:format].split(",") if options[:format]
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
- # Simulate chunking for large repositories
59
- result[:chunking_used] = true if Dir.glob(File.join(@project_dir, "**", "*")).count > 50
49
+ wait_for_job_completion(job)
50
+ end
60
51
 
61
- # Simulate warnings for network errors
62
- result[:warnings] = ["Network timeout"] if options[:simulate_network_error]
52
+ private
63
53
 
64
- # Simulate tools used for configuration tests
65
- if step_name == "06_STATIC_ANALYSIS"
66
- result[:tools_used] = %w[rubocop reek]
67
- # Check for user config
68
- user_config_file = File.expand_path("~/.aidp-tools.yml")
69
- result[:tools_used] << "eslint" if File.exist?(user_config_file)
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
- # Mark step as completed
73
- @progress.mark_step_completed(step_name)
74
-
75
- # Generate output files
76
- generate_output_files(step_name, step_spec["outs"], result)
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
- result
89
+ def mock_execution_result
90
+ {
91
+ status: "completed",
92
+ provider: "mock",
93
+ message: "Mock execution"
94
+ }
88
95
  end
89
96
 
90
- private
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
- template_file = File.join(path, template_name)
95
- return File.read(template_file) if File.exist?(template_file)
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(template_name, agent_persona, options = {})
111
- template = find_template(template_name)
112
- return template unless template
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
- # Load agent base template if available
115
- agent_base = find_template("AGENT_BASE.md")
116
- template = "#{agent_base}\n\n#{template}" if agent_base
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
- # Add agent persona context
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 placeholders
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
- def generate_output_files(step_name, output_files, result)
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
- Aidp::Analyze::Steps::SPEC.keys.each do |step|
221
- if @progress.step_completed?(step)
222
- db.execute("INSERT INTO analysis_results (step, status, agent, completed_at) VALUES (?, ?, ?, ?)",
223
- [step, "success", Aidp::Analyze::Steps::SPEC[step]["agent"], Time.now.iso8601])
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
@@ -2,50 +2,49 @@
2
2
 
3
3
  module Aidp
4
4
  module Analyze
5
- # Defines the steps, templates, outputs, and associated AI agents for analyze mode
6
- class Steps
5
+ module Steps
7
6
  SPEC = {
8
7
  "01_REPOSITORY_ANALYSIS" => {
9
- "templates" => ["01_REPOSITORY_ANALYSIS.md"],
10
- "outs" => ["01_REPOSITORY_ANALYSIS.md"],
11
- "gate" => false,
12
- "agent" => "Repository Analyst"
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" => ["02_ARCHITECTURE_ANALYSIS.md"],
16
- "outs" => ["02_ARCHITECTURE_ANALYSIS.md"],
17
- "gate" => false,
18
- "agent" => "Architecture Analyst"
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" => ["03_TEST_ANALYSIS.md"],
22
- "outs" => ["03_TEST_ANALYSIS.md"],
23
- "gate" => false,
24
- "agent" => "Test Analyst"
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" => ["04_FUNCTIONALITY_ANALYSIS.md"],
28
- "outs" => ["04_FUNCTIONALITY_ANALYSIS.md"],
29
- "gate" => false,
30
- "agent" => "Functionality Analyst"
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" => ["05_DOCUMENTATION_ANALYSIS.md"],
34
- "outs" => ["05_DOCUMENTATION_ANALYSIS.md"],
35
- "gate" => false,
36
- "agent" => "Documentation Analyst"
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" => ["06_STATIC_ANALYSIS.md"],
40
- "outs" => ["06_STATIC_ANALYSIS.md"],
41
- "gate" => false,
42
- "agent" => "Static Analysis Expert"
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
43
42
  },
44
43
  "07_REFACTORING_RECOMMENDATIONS" => {
45
- "templates" => ["07_REFACTORING_RECOMMENDATIONS.md"],
46
- "outs" => ["07_REFACTORING_RECOMMENDATIONS.md"],
47
- "gate" => false,
48
- "agent" => "Refactoring Specialist"
44
+ "templates" => ["07_refactoring_recommendations.md"],
45
+ "description" => "Provide actionable refactoring guidance",
46
+ "outs" => ["docs/analysis/refactoring_recommendations.md"],
47
+ "gate" => true
49
48
  }
50
49
  }.freeze
51
50
  end