aidp 0.1.0 → 0.3.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/bin/aidp +2 -2
- data/lib/aidp/analyze/data_retention_manager.rb +2 -2
- data/lib/aidp/analyze/large_analysis_progress.rb +2 -2
- data/lib/aidp/analyze/prioritizer.rb +2 -2
- data/lib/aidp/analyze/repository_chunker.rb +15 -13
- data/lib/aidp/analyze/storage.rb +1 -1
- data/lib/aidp/cli.rb +168 -0
- data/lib/aidp/config.rb +33 -0
- data/lib/aidp/project_detector.rb +117 -0
- data/lib/aidp/providers/anthropic.rb +24 -0
- data/lib/aidp/providers/base.rb +15 -0
- data/lib/aidp/providers/cursor.rb +100 -0
- data/lib/aidp/providers/gemini.rb +24 -0
- data/lib/aidp/providers/macos_ui.rb +24 -0
- data/lib/aidp/sync.rb +13 -0
- data/lib/aidp/util.rb +39 -0
- data/lib/aidp/{shared/version.rb → version.rb} +1 -3
- data/lib/aidp/workspace.rb +19 -0
- data/lib/aidp.rb +13 -13
- metadata +27 -20
- data/lib/aidp/shared/cli.rb +0 -117
- data/lib/aidp/shared/config.rb +0 -35
- data/lib/aidp/shared/project_detector.rb +0 -119
- data/lib/aidp/shared/providers/anthropic.rb +0 -26
- data/lib/aidp/shared/providers/base.rb +0 -17
- data/lib/aidp/shared/providers/cursor.rb +0 -102
- data/lib/aidp/shared/providers/gemini.rb +0 -26
- data/lib/aidp/shared/providers/macos_ui.rb +0 -26
- data/lib/aidp/shared/sync.rb +0 -15
- data/lib/aidp/shared/util.rb +0 -41
- data/lib/aidp/shared/workspace.rb +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ceebdfcb742085ac8d9ea6b5db63557d5fe999f498e97699c199fc9651cf4654
|
4
|
+
data.tar.gz: f1b230dcc33692c4c0391464beaf305b76993dbe02964d8c80198a7a50294a5c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d20547fa251feb09a97e6795b04fc09dd13d0f0fd801c5abdc994bb27a20898a604692eb04b7ed8a370ac7619bcbaf89a17af60dc34c844e782e8b68087ef67a
|
7
|
+
data.tar.gz: cb8503f9b0a85d6113d9ab2dada1cb0087b9b9e6ab1abb44894aa5152d3596c782c08f4708e3f03f9b969a94736acb7d882350b6f0cb869787069c8b12ddde85
|
data/bin/aidp
CHANGED
@@ -19,8 +19,8 @@ module Aidp
|
|
19
19
|
# Default retention periods (in days)
|
20
20
|
DEFAULT_RETENTION_PERIODS = {
|
21
21
|
"analysis_results" => 90, # 3 months
|
22
|
-
"execution_logs" => 30,
|
23
|
-
"temporary_data" => 1
|
22
|
+
"execution_logs" => 30, # 1 month
|
23
|
+
"temporary_data" => 1 # 1 day
|
24
24
|
}.freeze
|
25
25
|
|
26
26
|
def initialize(project_dir = Dir.pwd, config = {})
|
@@ -13,7 +13,7 @@ module Aidp
|
|
13
13
|
# Default configuration
|
14
14
|
DEFAULT_CONFIG = {
|
15
15
|
checkpoint_interval: 100, # Save progress every 100 items
|
16
|
-
max_checkpoints: 50,
|
16
|
+
max_checkpoints: 50, # Keep last 50 checkpoints
|
17
17
|
progress_file: ".aidp-large-analysis-progress.yml",
|
18
18
|
auto_save: true,
|
19
19
|
detailed_logging: false
|
@@ -280,7 +280,7 @@ module Aidp
|
|
280
280
|
when "json"
|
281
281
|
JSON.parse(data)
|
282
282
|
when "yaml"
|
283
|
-
YAML.
|
283
|
+
YAML.safe_load(data)
|
284
284
|
else
|
285
285
|
raise "Unsupported import format: #{format}"
|
286
286
|
end
|
@@ -281,9 +281,9 @@ module Aidp
|
|
281
281
|
|
282
282
|
# Adjust for authorship patterns
|
283
283
|
if authorship_file[:author_count] == 1
|
284
|
-
base_score *= 1.5
|
284
|
+
base_score *= 1.5 # Knowledge silo penalty
|
285
285
|
elsif authorship_file[:author_count] > 3
|
286
|
-
base_score *= 1.2
|
286
|
+
base_score *= 1.2 # Coordination complexity penalty
|
287
287
|
end
|
288
288
|
|
289
289
|
base_score
|
@@ -13,15 +13,15 @@ module Aidp
|
|
13
13
|
DEFAULT_CHUNK_CONFIG = {
|
14
14
|
"time_based" => {
|
15
15
|
"chunk_size" => "30d", # 30 days
|
16
|
-
"overlap" => "7d"
|
16
|
+
"overlap" => "7d" # 7 days overlap
|
17
17
|
},
|
18
18
|
"commit_count" => {
|
19
|
-
"chunk_size" => 1000,
|
20
|
-
"overlap" => 100
|
19
|
+
"chunk_size" => 1000, # 1000 commits per chunk
|
20
|
+
"overlap" => 100 # 100 commits overlap
|
21
21
|
},
|
22
22
|
"size_based" => {
|
23
23
|
"chunk_size" => "100MB", # 100MB per chunk
|
24
|
-
"overlap" => "10MB"
|
24
|
+
"overlap" => "10MB" # 10MB overlap
|
25
25
|
},
|
26
26
|
"feature_based" => {
|
27
27
|
"max_files_per_chunk" => 500,
|
@@ -358,14 +358,15 @@ module Aidp
|
|
358
358
|
|
359
359
|
def parse_time_duration(duration_str)
|
360
360
|
# Parse duration strings like "30d", "7d", "1w", etc.
|
361
|
-
|
362
|
-
|
361
|
+
# Use anchored patterns with limited digit repetition to prevent ReDoS
|
362
|
+
case duration_str.to_s.strip
|
363
|
+
when /\A(\d{1,6})d\z/
|
363
364
|
::Regexp.last_match(1).to_i * 24 * 60 * 60
|
364
|
-
when
|
365
|
+
when /\A(\d{1,6})w\z/
|
365
366
|
::Regexp.last_match(1).to_i * 7 * 24 * 60 * 60
|
366
|
-
when
|
367
|
+
when /\A(\d{1,6})m\z/
|
367
368
|
::Regexp.last_match(1).to_i * 30 * 24 * 60 * 60
|
368
|
-
when
|
369
|
+
when /\A(\d{1,6})y\z/
|
369
370
|
::Regexp.last_match(1).to_i * 365 * 24 * 60 * 60
|
370
371
|
else
|
371
372
|
30 * 24 * 60 * 60 # Default to 30 days
|
@@ -374,12 +375,13 @@ module Aidp
|
|
374
375
|
|
375
376
|
def parse_size(size_str)
|
376
377
|
# Parse size strings like "100MB", "1GB", etc.
|
377
|
-
|
378
|
-
|
378
|
+
# Use anchored patterns with limited digit repetition to prevent ReDoS
|
379
|
+
case size_str.to_s.strip
|
380
|
+
when /\A(\d{1,10})KB\z/i
|
379
381
|
::Regexp.last_match(1).to_i * 1024
|
380
|
-
when
|
382
|
+
when /\A(\d{1,10})MB\z/i
|
381
383
|
::Regexp.last_match(1).to_i * 1024 * 1024
|
382
|
-
when
|
384
|
+
when /\A(\d{1,10})GB\z/i
|
383
385
|
::Regexp.last_match(1).to_i * 1024 * 1024 * 1024
|
384
386
|
else
|
385
387
|
100 * 1024 * 1024 # Default to 100MB
|
data/lib/aidp/analyze/storage.rb
CHANGED
data/lib/aidp/cli.rb
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "thor"
|
4
|
+
|
5
|
+
module Aidp
|
6
|
+
# CLI interface for both execute and analyze modes
|
7
|
+
class CLI < Thor
|
8
|
+
desc "execute [STEP]", "Run execute mode step(s)"
|
9
|
+
option :force, type: :boolean, desc: "Force execution even if dependencies are not met"
|
10
|
+
option :rerun, type: :boolean, desc: "Re-run a completed step"
|
11
|
+
def execute(project_dir = Dir.pwd, step_name = nil, custom_options = {})
|
12
|
+
if step_name
|
13
|
+
runner = Aidp::Execute::Runner.new(project_dir)
|
14
|
+
# Merge Thor options with custom options
|
15
|
+
all_options = options.merge(custom_options)
|
16
|
+
runner.run_step(step_name, all_options)
|
17
|
+
else
|
18
|
+
puts "Available execute steps:"
|
19
|
+
Aidp::Execute::Steps::SPEC.keys.each { |step| puts " - #{step}" }
|
20
|
+
progress = Aidp::Execute::Progress.new(project_dir)
|
21
|
+
next_step = progress.next_step
|
22
|
+
{status: "success", message: "Available steps listed", next_step: next_step}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "analyze [STEP]", "Run analyze mode step(s)"
|
27
|
+
long_desc <<~DESC
|
28
|
+
Run analyze mode steps. STEP can be:
|
29
|
+
- A full step name (e.g., 01_REPOSITORY_ANALYSIS)
|
30
|
+
- A step number (e.g., 01, 02, 03)
|
31
|
+
- 'next' to run the next unfinished step
|
32
|
+
- 'current' to run the current step
|
33
|
+
- Empty to list available steps
|
34
|
+
DESC
|
35
|
+
option :force, type: :boolean, desc: "Force execution even if dependencies are not met"
|
36
|
+
option :rerun, type: :boolean, desc: "Re-run a completed step"
|
37
|
+
def analyze(project_dir = Dir.pwd, step_name = nil, custom_options = {})
|
38
|
+
progress = Aidp::Analyze::Progress.new(project_dir)
|
39
|
+
|
40
|
+
if step_name
|
41
|
+
# Resolve the step name
|
42
|
+
resolved_step = resolve_analyze_step(step_name, progress)
|
43
|
+
|
44
|
+
if resolved_step
|
45
|
+
runner = Aidp::Analyze::Runner.new(project_dir)
|
46
|
+
# Merge Thor options with custom options
|
47
|
+
all_options = options.merge(custom_options)
|
48
|
+
runner.run_step(resolved_step, all_options)
|
49
|
+
else
|
50
|
+
puts "❌ Step '#{step_name}' not found or not available"
|
51
|
+
puts "\nAvailable steps:"
|
52
|
+
Aidp::Analyze::Steps::SPEC.keys.each_with_index do |step, index|
|
53
|
+
status = progress.step_completed?(step) ? "✅" : "⏳"
|
54
|
+
puts " #{status} #{sprintf("%02d", index + 1)}: #{step}"
|
55
|
+
end
|
56
|
+
{status: "error", message: "Step not found"}
|
57
|
+
end
|
58
|
+
else
|
59
|
+
puts "Available analyze steps:"
|
60
|
+
Aidp::Analyze::Steps::SPEC.keys.each_with_index do |step, index|
|
61
|
+
status = progress.step_completed?(step) ? "✅" : "⏳"
|
62
|
+
puts " #{status} #{sprintf("%02d", index + 1)}: #{step}"
|
63
|
+
end
|
64
|
+
|
65
|
+
next_step = progress.next_step
|
66
|
+
if next_step
|
67
|
+
puts "\n💡 Run 'aidp analyze next' or 'aidp analyze #{next_step.match(/^(\d+)/)[1]}' to run the next step"
|
68
|
+
end
|
69
|
+
|
70
|
+
{status: "success", message: "Available steps listed", next_step: next_step,
|
71
|
+
completed_steps: progress.completed_steps}
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
desc "analyze-approve STEP", "Approve a completed analyze gate step"
|
76
|
+
def analyze_approve(project_dir = Dir.pwd, step_name = nil)
|
77
|
+
progress = Aidp::Analyze::Progress.new(project_dir)
|
78
|
+
progress.mark_step_completed(step_name)
|
79
|
+
puts "✅ Approved analyze step: #{step_name}"
|
80
|
+
{status: "success", step: step_name}
|
81
|
+
end
|
82
|
+
|
83
|
+
desc "analyze-reset", "Reset analyze mode progress"
|
84
|
+
def analyze_reset(project_dir = Dir.pwd)
|
85
|
+
progress = Aidp::Analyze::Progress.new(project_dir)
|
86
|
+
progress.reset
|
87
|
+
puts "🔄 Reset analyze mode progress"
|
88
|
+
{status: "success", message: "Progress reset"}
|
89
|
+
end
|
90
|
+
|
91
|
+
desc "execute-approve STEP", "Approve a completed execute gate step"
|
92
|
+
def execute_approve(project_dir = Dir.pwd, step_name = nil)
|
93
|
+
progress = Aidp::Execute::Progress.new(project_dir)
|
94
|
+
progress.mark_step_completed(step_name)
|
95
|
+
puts "✅ Approved execute step: #{step_name}"
|
96
|
+
{status: "success", step: step_name}
|
97
|
+
end
|
98
|
+
|
99
|
+
desc "execute-reset", "Reset execute mode progress"
|
100
|
+
def execute_reset(project_dir = Dir.pwd)
|
101
|
+
progress = Aidp::Execute::Progress.new(project_dir)
|
102
|
+
progress.reset
|
103
|
+
puts "🔄 Reset execute mode progress"
|
104
|
+
{status: "success", message: "Progress reset"}
|
105
|
+
end
|
106
|
+
|
107
|
+
# Backward compatibility aliases
|
108
|
+
desc "approve STEP", "Approve a completed execute gate step (alias for execute-approve)"
|
109
|
+
def approve(project_dir = Dir.pwd, step_name = nil)
|
110
|
+
execute_approve(project_dir, step_name)
|
111
|
+
end
|
112
|
+
|
113
|
+
desc "reset", "Reset execute mode progress (alias for execute-reset)"
|
114
|
+
def reset(project_dir = Dir.pwd)
|
115
|
+
execute_reset(project_dir)
|
116
|
+
end
|
117
|
+
|
118
|
+
desc "status", "Show current progress for both modes"
|
119
|
+
def status
|
120
|
+
puts "\n📊 AI Dev Pipeline Status"
|
121
|
+
puts "=" * 50
|
122
|
+
|
123
|
+
# Execute mode status
|
124
|
+
execute_progress = Aidp::Execute::Progress.new(Dir.pwd)
|
125
|
+
puts "\n🔧 Execute Mode:"
|
126
|
+
Aidp::Execute::Steps::SPEC.keys.each do |step|
|
127
|
+
status = execute_progress.step_completed?(step) ? "✅" : "⏳"
|
128
|
+
puts " #{status} #{step}"
|
129
|
+
end
|
130
|
+
|
131
|
+
# Analyze mode status
|
132
|
+
analyze_progress = Aidp::Analyze::Progress.new(Dir.pwd)
|
133
|
+
puts "\n🔍 Analyze Mode:"
|
134
|
+
Aidp::Analyze::Steps::SPEC.keys.each do |step|
|
135
|
+
status = analyze_progress.step_completed?(step) ? "✅" : "⏳"
|
136
|
+
puts " #{status} #{step}"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
desc "version", "Show version information"
|
141
|
+
def version
|
142
|
+
puts "Aidp version #{Aidp::VERSION}"
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
def resolve_analyze_step(step_input, progress)
|
148
|
+
step_input = step_input.to_s.downcase.strip
|
149
|
+
|
150
|
+
case step_input
|
151
|
+
when "next"
|
152
|
+
progress.next_step
|
153
|
+
when "current"
|
154
|
+
progress.current_step || progress.next_step
|
155
|
+
else
|
156
|
+
# Check if it's a step number (e.g., "01", "02", "1", "2")
|
157
|
+
if step_input.match?(/^\d{1,2}$/)
|
158
|
+
step_number = sprintf("%02d", step_input.to_i)
|
159
|
+
# Find step that starts with this number
|
160
|
+
Aidp::Analyze::Steps::SPEC.keys.find { |step| step.start_with?(step_number) }
|
161
|
+
else
|
162
|
+
# Check if it's a full step name (case insensitive)
|
163
|
+
Aidp::Analyze::Steps::SPEC.keys.find { |step| step.downcase == step_input }
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
data/lib/aidp/config.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yaml"
|
4
|
+
|
5
|
+
module Aidp
|
6
|
+
# Configuration management for both execute and analyze modes
|
7
|
+
class Config
|
8
|
+
def self.load(project_dir = Dir.pwd)
|
9
|
+
config_file = File.join(project_dir, ".aidp.yml")
|
10
|
+
if File.exist?(config_file)
|
11
|
+
YAML.load_file(config_file) || {}
|
12
|
+
else
|
13
|
+
{}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.templates_root
|
18
|
+
File.join(Dir.pwd, "templates")
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.analyze_templates_root
|
22
|
+
File.join(Dir.pwd, "templates", "ANALYZE")
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.execute_templates_root
|
26
|
+
File.join(Dir.pwd, "templates", "EXECUTE")
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.common_templates_root
|
30
|
+
File.join(Dir.pwd, "templates", "COMMON")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "yaml"
|
5
|
+
|
6
|
+
module Aidp
|
7
|
+
# Detects project type, language, framework, and other characteristics
|
8
|
+
class ProjectDetector
|
9
|
+
attr_reader :project_dir
|
10
|
+
|
11
|
+
def initialize(project_dir = Dir.pwd)
|
12
|
+
@project_dir = project_dir
|
13
|
+
end
|
14
|
+
|
15
|
+
def detect
|
16
|
+
{
|
17
|
+
language: detect_language,
|
18
|
+
framework: detect_framework,
|
19
|
+
build_system: detect_build_system,
|
20
|
+
package_manager: detect_package_manager,
|
21
|
+
static_analysis_tools: detect_static_analysis_tools,
|
22
|
+
test_framework: detect_test_framework,
|
23
|
+
database: detect_database,
|
24
|
+
deployment: detect_deployment
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def detect_language
|
31
|
+
return "ruby" if File.exist?(File.join(@project_dir, "Gemfile"))
|
32
|
+
return "javascript" if File.exist?(File.join(@project_dir, "package.json"))
|
33
|
+
return "python" if File.exist?(File.join(@project_dir, "requirements.txt")) || File.exist?(File.join(@project_dir, "pyproject.toml"))
|
34
|
+
return "java" if File.exist?(File.join(@project_dir, "pom.xml")) || File.exist?(File.join(@project_dir, "build.gradle"))
|
35
|
+
return "go" if File.exist?(File.join(@project_dir, "go.mod"))
|
36
|
+
return "rust" if File.exist?(File.join(@project_dir, "Cargo.toml"))
|
37
|
+
return "csharp" if File.exist?(File.join(@project_dir, "*.csproj"))
|
38
|
+
"unknown"
|
39
|
+
end
|
40
|
+
|
41
|
+
def detect_framework
|
42
|
+
case detect_language
|
43
|
+
when "ruby"
|
44
|
+
return "rails" if File.exist?(File.join(@project_dir, "config", "application.rb"))
|
45
|
+
return "sinatra" if File.exist?(File.join(@project_dir, "app.rb")) && File.read(File.join(@project_dir, "app.rb")).include?("Sinatra")
|
46
|
+
when "javascript"
|
47
|
+
return "react" if File.exist?(File.join(@project_dir, "package.json")) && File.read(File.join(@project_dir, "package.json")).include?("react")
|
48
|
+
return "vue" if File.exist?(File.join(@project_dir, "package.json")) && File.read(File.join(@project_dir, "package.json")).include?("vue")
|
49
|
+
return "angular" if File.exist?(File.join(@project_dir, "angular.json"))
|
50
|
+
return "express" if File.exist?(File.join(@project_dir, "package.json")) && File.read(File.join(@project_dir, "package.json")).include?("express")
|
51
|
+
when "python"
|
52
|
+
return "django" if File.exist?(File.join(@project_dir, "manage.py"))
|
53
|
+
return "flask" if File.exist?(File.join(@project_dir, "app.py")) && File.read(File.join(@project_dir, "app.py")).include?("Flask")
|
54
|
+
when "java"
|
55
|
+
return "spring" if File.exist?(File.join(@project_dir, "pom.xml")) && File.read(File.join(@project_dir, "pom.xml")).include?("spring-boot")
|
56
|
+
end
|
57
|
+
"unknown"
|
58
|
+
end
|
59
|
+
|
60
|
+
def detect_build_system
|
61
|
+
return "maven" if File.exist?(File.join(@project_dir, "pom.xml"))
|
62
|
+
return "gradle" if File.exist?(File.join(@project_dir, "build.gradle"))
|
63
|
+
return "npm" if File.exist?(File.join(@project_dir, "package.json"))
|
64
|
+
return "bundler" if File.exist?(File.join(@project_dir, "Gemfile"))
|
65
|
+
return "pip" if File.exist?(File.join(@project_dir, "requirements.txt"))
|
66
|
+
return "cargo" if File.exist?(File.join(@project_dir, "Cargo.toml"))
|
67
|
+
return "go" if File.exist?(File.join(@project_dir, "go.mod"))
|
68
|
+
"unknown"
|
69
|
+
end
|
70
|
+
|
71
|
+
def detect_package_manager
|
72
|
+
detect_build_system
|
73
|
+
end
|
74
|
+
|
75
|
+
def detect_static_analysis_tools
|
76
|
+
tools = []
|
77
|
+
tools << "rubocop" if File.exist?(File.join(@project_dir, ".rubocop.yml"))
|
78
|
+
tools << "eslint" if File.exist?(File.join(@project_dir, ".eslintrc"))
|
79
|
+
tools << "flake8" if File.exist?(File.join(@project_dir, ".flake8"))
|
80
|
+
tools << "checkstyle" if File.exist?(File.join(@project_dir, "checkstyle.xml"))
|
81
|
+
tools << "clippy" if File.exist?(File.join(@project_dir, "Cargo.toml"))
|
82
|
+
tools
|
83
|
+
end
|
84
|
+
|
85
|
+
def detect_test_framework
|
86
|
+
case detect_language
|
87
|
+
when "ruby"
|
88
|
+
return "rspec" if File.exist?(File.join(@project_dir, "spec"))
|
89
|
+
return "minitest" if File.exist?(File.join(@project_dir, "test"))
|
90
|
+
when "javascript"
|
91
|
+
return "jest" if File.exist?(File.join(@project_dir, "package.json")) && File.read(File.join(@project_dir, "package.json")).include?("jest")
|
92
|
+
return "mocha" if File.exist?(File.join(@project_dir, "package.json")) && File.read(File.join(@project_dir, "package.json")).include?("mocha")
|
93
|
+
when "python"
|
94
|
+
return "pytest" if File.exist?(File.join(@project_dir, "pytest.ini"))
|
95
|
+
return "unittest" if Dir.exist?(File.join(@project_dir, "tests"))
|
96
|
+
when "java"
|
97
|
+
return "junit" if File.exist?(File.join(@project_dir, "src", "test"))
|
98
|
+
end
|
99
|
+
"unknown"
|
100
|
+
end
|
101
|
+
|
102
|
+
def detect_database
|
103
|
+
return "postgresql" if File.exist?(File.join(@project_dir, "config", "database.yml")) && File.read(File.join(@project_dir, "config", "database.yml")).include?("postgresql")
|
104
|
+
return "mysql" if File.exist?(File.join(@project_dir, "config", "database.yml")) && File.read(File.join(@project_dir, "config", "database.yml")).include?("mysql")
|
105
|
+
return "sqlite" if File.exist?(File.join(@project_dir, "config", "database.yml")) && File.read(File.join(@project_dir, "config", "database.yml")).include?("sqlite")
|
106
|
+
"unknown"
|
107
|
+
end
|
108
|
+
|
109
|
+
def detect_deployment
|
110
|
+
return "docker" if File.exist?(File.join(@project_dir, "Dockerfile"))
|
111
|
+
return "kubernetes" if File.exist?(File.join(@project_dir, "k8s")) || File.exist?(File.join(@project_dir, "kubernetes"))
|
112
|
+
return "heroku" if File.exist?(File.join(@project_dir, "Procfile"))
|
113
|
+
return "aws" if File.exist?(File.join(@project_dir, "serverless.yml")) || File.exist?(File.join(@project_dir, "template.yaml"))
|
114
|
+
"unknown"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module Aidp
|
6
|
+
module Providers
|
7
|
+
class Anthropic < Base
|
8
|
+
def self.available?
|
9
|
+
!!Aidp::Util.which("claude")
|
10
|
+
end
|
11
|
+
|
12
|
+
def name = "anthropic"
|
13
|
+
|
14
|
+
def send(prompt:, session: nil)
|
15
|
+
raise "claude CLI not available" unless self.class.available?
|
16
|
+
|
17
|
+
# Use Claude CLI for non-interactive mode
|
18
|
+
cmd = ["claude", "compose", "--prompt", prompt]
|
19
|
+
system(*cmd)
|
20
|
+
:ok
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aidp
|
4
|
+
module Providers
|
5
|
+
class Base
|
6
|
+
def name = raise(NotImplementedError)
|
7
|
+
|
8
|
+
# Send a composed prompt string to the provider.
|
9
|
+
# Return :ok when command completed successfully,
|
10
|
+
# Return :interactive when starting an interactive session (for gate steps),
|
11
|
+
# or return a string if we captured output and the caller should write to a file.
|
12
|
+
def send(prompt:, session: nil) = raise(NotImplementedError)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "open3"
|
4
|
+
require "timeout"
|
5
|
+
require_relative "base"
|
6
|
+
require_relative "../util"
|
7
|
+
|
8
|
+
module Aidp
|
9
|
+
module Providers
|
10
|
+
class Cursor < Base
|
11
|
+
def self.available?
|
12
|
+
!!Aidp::Util.which("cursor-agent")
|
13
|
+
end
|
14
|
+
|
15
|
+
def name = "cursor"
|
16
|
+
|
17
|
+
def send(prompt:, session: nil)
|
18
|
+
raise "cursor-agent not available" unless self.class.available?
|
19
|
+
|
20
|
+
# Always use non-interactive mode with -p flag
|
21
|
+
cmd = ["cursor-agent", "-p"]
|
22
|
+
puts "📝 Sending prompt to cursor-agent"
|
23
|
+
|
24
|
+
# Enable debug output if requested
|
25
|
+
if ENV["AIDP_DEBUG"]
|
26
|
+
puts "🔍 Debug mode enabled - showing cursor-agent output"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Setup logging if log file is specified
|
30
|
+
log_file = ENV["AIDP_LOG_FILE"]
|
31
|
+
if log_file
|
32
|
+
puts "📝 Logging to: #{log_file}"
|
33
|
+
end
|
34
|
+
|
35
|
+
Open3.popen3(*cmd) do |stdin, stdout, stderr, wait|
|
36
|
+
# Send the prompt to stdin
|
37
|
+
stdin.puts prompt
|
38
|
+
stdin.close
|
39
|
+
|
40
|
+
# Log the prompt if debugging
|
41
|
+
if ENV["AIDP_DEBUG"] || log_file
|
42
|
+
prompt_log = "📝 Sending prompt to cursor-agent:\n#{prompt}"
|
43
|
+
puts prompt_log if ENV["AIDP_DEBUG"]
|
44
|
+
File.write(log_file, "#{Time.now.iso8601} #{prompt_log}\n", mode: "a") if log_file
|
45
|
+
end
|
46
|
+
|
47
|
+
# Handle debug output and logging
|
48
|
+
if ENV["AIDP_DEBUG"] || log_file
|
49
|
+
# Start threads to capture and display output in real-time
|
50
|
+
stdout_thread = Thread.new do
|
51
|
+
stdout.each_line do |line|
|
52
|
+
output = "📤 cursor-agent: #{line.chomp}"
|
53
|
+
puts output if ENV["AIDP_DEBUG"]
|
54
|
+
File.write(log_file, "#{Time.now.iso8601} #{output}\n", mode: "a") if log_file
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
stderr_thread = Thread.new do
|
59
|
+
stderr.each_line do |line|
|
60
|
+
output = "❌ cursor-agent error: #{line.chomp}"
|
61
|
+
puts output if ENV["AIDP_DEBUG"]
|
62
|
+
File.write(log_file, "#{Time.now.iso8601} #{output}\n", mode: "a") if log_file
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Wait for completion with a reasonable timeout
|
68
|
+
begin
|
69
|
+
Timeout.timeout(300) do # 5 minutes timeout
|
70
|
+
result = wait.value
|
71
|
+
|
72
|
+
# Stop debug threads
|
73
|
+
if ENV["AIDP_DEBUG"]
|
74
|
+
stdout_thread&.kill
|
75
|
+
stderr_thread&.kill
|
76
|
+
end
|
77
|
+
|
78
|
+
return :ok if result.success?
|
79
|
+
raise "cursor-agent failed with exit code #{result.exitstatus}"
|
80
|
+
end
|
81
|
+
rescue Timeout::Error
|
82
|
+
# Stop debug threads
|
83
|
+
if ENV["AIDP_DEBUG"]
|
84
|
+
stdout_thread&.kill
|
85
|
+
stderr_thread&.kill
|
86
|
+
end
|
87
|
+
|
88
|
+
# Kill the process if it's taking too long
|
89
|
+
begin
|
90
|
+
Process.kill("TERM", wait.pid)
|
91
|
+
rescue
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
raise Timeout::Error, "cursor-agent timed out after 5 minutes"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module Aidp
|
6
|
+
module Providers
|
7
|
+
class Gemini < Base
|
8
|
+
def self.available?
|
9
|
+
!!Aidp::Util.which("gemini")
|
10
|
+
end
|
11
|
+
|
12
|
+
def name = "gemini"
|
13
|
+
|
14
|
+
def send(prompt:, session: nil)
|
15
|
+
raise "gemini CLI not available" unless self.class.available?
|
16
|
+
|
17
|
+
# Use Gemini CLI for non-interactive mode
|
18
|
+
cmd = ["gemini", "chat", "--prompt", prompt]
|
19
|
+
system(*cmd)
|
20
|
+
:ok
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module Aidp
|
6
|
+
module Providers
|
7
|
+
class MacOSUI < Base
|
8
|
+
def self.available?
|
9
|
+
RUBY_PLATFORM.include?("darwin")
|
10
|
+
end
|
11
|
+
|
12
|
+
def name = "macos"
|
13
|
+
|
14
|
+
def send(prompt:, session: nil)
|
15
|
+
raise "macOS UI not available on this platform" unless self.class.available?
|
16
|
+
|
17
|
+
# Use macOS UI for interactive mode
|
18
|
+
cmd = ["osascript", "-e", "display dialog \"#{prompt}\" with title \"Aidp\" buttons {\"OK\"} default button \"OK\""]
|
19
|
+
system(*cmd)
|
20
|
+
:ok
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|