aidp 0.1.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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +59 -4
  3. data/bin/aidp +2 -2
  4. data/lib/aidp/analyze/agent_personas.rb +1 -1
  5. data/lib/aidp/analyze/data_retention_manager.rb +2 -2
  6. data/lib/aidp/analyze/database.rb +99 -82
  7. data/lib/aidp/analyze/error_handler.rb +12 -76
  8. data/lib/aidp/analyze/focus_guidance.rb +2 -2
  9. data/lib/aidp/analyze/large_analysis_progress.rb +2 -2
  10. data/lib/aidp/analyze/metrics_storage.rb +336 -0
  11. data/lib/aidp/analyze/prioritizer.rb +4 -4
  12. data/lib/aidp/analyze/repository_chunker.rb +15 -13
  13. data/lib/aidp/analyze/ruby_maat_integration.rb +6 -102
  14. data/lib/aidp/analyze/runner.rb +107 -191
  15. data/lib/aidp/analyze/steps.rb +29 -30
  16. data/lib/aidp/analyze/storage.rb +234 -172
  17. data/lib/aidp/cli/jobs_command.rb +489 -0
  18. data/lib/aidp/cli/terminal_io.rb +52 -0
  19. data/lib/aidp/cli.rb +227 -0
  20. data/lib/aidp/config.rb +33 -0
  21. data/lib/aidp/core_ext/class_attribute.rb +36 -0
  22. data/lib/aidp/database/pg_adapter.rb +148 -0
  23. data/lib/aidp/database_config.rb +69 -0
  24. data/lib/aidp/database_connection.rb +72 -0
  25. data/lib/aidp/database_migration.rb +158 -0
  26. data/lib/aidp/execute/runner.rb +65 -92
  27. data/lib/aidp/execute/steps.rb +81 -82
  28. data/lib/aidp/job_manager.rb +41 -0
  29. data/lib/aidp/jobs/base_job.rb +47 -0
  30. data/lib/aidp/jobs/provider_execution_job.rb +96 -0
  31. data/lib/aidp/project_detector.rb +117 -0
  32. data/lib/aidp/provider_manager.rb +25 -0
  33. data/lib/aidp/providers/agent_supervisor.rb +348 -0
  34. data/lib/aidp/providers/anthropic.rb +187 -0
  35. data/lib/aidp/providers/base.rb +162 -0
  36. data/lib/aidp/providers/cursor.rb +304 -0
  37. data/lib/aidp/providers/gemini.rb +187 -0
  38. data/lib/aidp/providers/macos_ui.rb +24 -0
  39. data/lib/aidp/providers/supervised_base.rb +317 -0
  40. data/lib/aidp/providers/supervised_cursor.rb +22 -0
  41. data/lib/aidp/sync.rb +13 -0
  42. data/lib/aidp/util.rb +39 -0
  43. data/lib/aidp/{shared/version.rb → version.rb} +1 -3
  44. data/lib/aidp/workspace.rb +19 -0
  45. data/lib/aidp.rb +36 -45
  46. data/templates/ANALYZE/01_REPOSITORY_ANALYSIS.md +4 -4
  47. metadata +89 -45
  48. data/lib/aidp/shared/cli.rb +0 -117
  49. data/lib/aidp/shared/config.rb +0 -35
  50. data/lib/aidp/shared/project_detector.rb +0 -119
  51. data/lib/aidp/shared/providers/anthropic.rb +0 -26
  52. data/lib/aidp/shared/providers/base.rb +0 -17
  53. data/lib/aidp/shared/providers/cursor.rb +0 -102
  54. data/lib/aidp/shared/providers/gemini.rb +0 -26
  55. data/lib/aidp/shared/providers/macos_ui.rb +0 -26
  56. data/lib/aidp/shared/sync.rb +0 -15
  57. data/lib/aidp/shared/util.rb +0 -41
  58. data/lib/aidp/shared/workspace.rb +0 -21
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sqlite3"
4
+ require "fileutils"
5
+
6
+ module Aidp
7
+ # Handles database migrations for AIDP
8
+ class DatabaseMigration
9
+ def initialize(project_dir = Dir.pwd)
10
+ @project_dir = project_dir
11
+ @old_db_path = File.join(project_dir, ".aidp-analysis.db")
12
+ @new_db_path = File.join(project_dir, ".aidp.db")
13
+ end
14
+
15
+ # Migrate database from old to new format
16
+ def migrate
17
+ # If neither database exists, create new one directly
18
+ if !File.exist?(@old_db_path) && !File.exist?(@new_db_path)
19
+ create_new_database
20
+ return true
21
+ end
22
+
23
+ # If new database already exists, skip migration
24
+ if File.exist?(@new_db_path)
25
+ puts "Database .aidp.db already exists, skipping migration"
26
+ return false
27
+ end
28
+
29
+ # Rename old database to new name
30
+ FileUtils.mv(@old_db_path, @new_db_path)
31
+
32
+ # Open database connection
33
+ db = SQLite3::Database.new(@new_db_path)
34
+
35
+ # Create new tables for job management
36
+ create_job_tables(db)
37
+
38
+ # Close connection
39
+ db.close
40
+
41
+ true
42
+ rescue => e
43
+ puts "Error during database migration: #{e.message}"
44
+ # Try to restore old database if something went wrong
45
+ if File.exist?(@new_db_path) && !File.exist?(@old_db_path)
46
+ FileUtils.mv(@new_db_path, @old_db_path)
47
+ end
48
+ false
49
+ end
50
+
51
+ private
52
+
53
+ def create_new_database
54
+ db = SQLite3::Database.new(@new_db_path)
55
+
56
+ # Create original tables
57
+ create_original_tables(db)
58
+
59
+ # Create new job tables
60
+ create_job_tables(db)
61
+
62
+ db.close
63
+ end
64
+
65
+ def create_original_tables(db)
66
+ # Create analysis_results table
67
+ db.execute(<<~SQL)
68
+ CREATE TABLE analysis_results (
69
+ step_name TEXT PRIMARY KEY,
70
+ data TEXT NOT NULL,
71
+ metadata TEXT,
72
+ created_at TEXT NOT NULL,
73
+ updated_at TEXT NOT NULL
74
+ )
75
+ SQL
76
+
77
+ # Create analysis_metrics table
78
+ db.execute(<<~SQL)
79
+ CREATE TABLE analysis_metrics (
80
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
81
+ step_name TEXT NOT NULL,
82
+ metric_name TEXT NOT NULL,
83
+ value TEXT NOT NULL,
84
+ recorded_at TEXT NOT NULL,
85
+ UNIQUE(step_name, metric_name, recorded_at)
86
+ )
87
+ SQL
88
+
89
+ # Create embeddings table
90
+ db.execute(<<~SQL)
91
+ CREATE TABLE embeddings (
92
+ step_name TEXT PRIMARY KEY,
93
+ embeddings_data TEXT NOT NULL,
94
+ created_at TEXT NOT NULL
95
+ )
96
+ SQL
97
+
98
+ # Create indexes
99
+ db.execute("CREATE INDEX idx_analysis_metrics_step_name ON analysis_metrics(step_name)")
100
+ db.execute("CREATE INDEX idx_analysis_metrics_recorded_at ON analysis_metrics(recorded_at)")
101
+ db.execute("CREATE INDEX idx_analysis_results_updated_at ON analysis_results(updated_at)")
102
+ end
103
+
104
+ def create_job_tables(db)
105
+ # Create jobs table
106
+ db.execute(<<~SQL)
107
+ CREATE TABLE jobs (
108
+ id INTEGER PRIMARY KEY,
109
+ job_type TEXT NOT NULL,
110
+ provider TEXT NOT NULL,
111
+ status TEXT NOT NULL,
112
+ created_at INTEGER NOT NULL,
113
+ started_at INTEGER,
114
+ completed_at INTEGER,
115
+ error TEXT,
116
+ metadata TEXT
117
+ )
118
+ SQL
119
+
120
+ # Create job_executions table
121
+ db.execute(<<~SQL)
122
+ CREATE TABLE job_executions (
123
+ id INTEGER PRIMARY KEY,
124
+ job_id INTEGER NOT NULL,
125
+ attempt INTEGER NOT NULL,
126
+ status TEXT NOT NULL,
127
+ started_at INTEGER NOT NULL,
128
+ completed_at INTEGER,
129
+ error TEXT,
130
+ FOREIGN KEY (job_id) REFERENCES jobs(id)
131
+ )
132
+ SQL
133
+
134
+ # Create job_logs table
135
+ db.execute(<<~SQL)
136
+ CREATE TABLE job_logs (
137
+ id INTEGER PRIMARY KEY,
138
+ job_id INTEGER NOT NULL,
139
+ execution_id INTEGER NOT NULL,
140
+ timestamp INTEGER NOT NULL,
141
+ message TEXT NOT NULL,
142
+ level TEXT NOT NULL,
143
+ metadata TEXT,
144
+ FOREIGN KEY (job_id) REFERENCES jobs(id),
145
+ FOREIGN KEY (execution_id) REFERENCES job_executions(id)
146
+ )
147
+ SQL
148
+
149
+ # Create indexes for job tables
150
+ db.execute("CREATE INDEX idx_jobs_status ON jobs(status)")
151
+ db.execute("CREATE INDEX idx_jobs_provider ON jobs(provider)")
152
+ db.execute("CREATE INDEX idx_job_executions_job_id ON job_executions(job_id)")
153
+ db.execute("CREATE INDEX idx_job_logs_job_id ON job_logs(job_id)")
154
+ db.execute("CREATE INDEX idx_job_logs_execution_id ON job_logs(execution_id)")
155
+ db.execute("CREATE INDEX idx_job_logs_timestamp ON job_logs(timestamp)")
156
+ end
157
+ end
158
+ end
@@ -1,135 +1,108 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "erb"
4
- require "yaml"
5
- require "json"
6
-
7
3
  module Aidp
8
4
  module Execute
9
- # Handles execution logic for execute mode steps
10
5
  class Runner
11
- attr_reader :project_dir, :progress
12
-
13
6
  def initialize(project_dir)
14
7
  @project_dir = project_dir
15
- @progress = Aidp::Execute::Progress.new(project_dir)
16
8
  end
17
9
 
18
- def run_step(step_name, options = {})
19
- raise "Step '#{step_name}' not found in execute mode steps" unless Aidp::Execute::Steps::SPEC.key?(step_name)
10
+ def progress
11
+ @progress ||= Aidp::Execute::Progress.new(@project_dir)
12
+ end
20
13
 
14
+ def run_step(step_name, options = {})
15
+ # Always validate step exists first, even in mock mode
21
16
  step_spec = Aidp::Execute::Steps::SPEC[step_name]
22
- template_name = step_spec["templates"].first
23
-
24
- # Load template
25
- template = find_template(template_name)
26
- raise "Template '#{template_name}' not found" unless template
17
+ raise "Step '#{step_name}' not found" unless step_spec
27
18
 
28
- # Compose prompt
29
- prompt = composed_prompt(template_name, options)
30
-
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
- }
19
+ if should_use_mock_mode?(options)
20
+ return options[:simulate_error] ?
21
+ {status: "error", error: options[:simulate_error]} :
22
+ mock_execution_result
38
23
  end
39
24
 
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
- }
25
+ job = Aidp::Jobs::ProviderExecutionJob.enqueue(
26
+ provider_type: "cursor",
27
+ prompt: composed_prompt(step_name, options),
28
+ metadata: {
29
+ step_name: step_name,
30
+ project_dir: @project_dir
31
+ }
32
+ )
47
33
 
48
- # Mark step as completed
49
- @progress.mark_step_completed(step_name)
34
+ wait_for_job_completion(job)
35
+ end
50
36
 
51
- # Generate output files
52
- generate_output_files(step_name, step_spec["outs"], result)
37
+ private
53
38
 
54
- # Generate database export
55
- generate_database_export
39
+ def should_use_mock_mode?(options)
40
+ options[:mock_mode] || ENV["AIDP_MOCK_MODE"] == "1" || ENV["RAILS_ENV"] == "test"
41
+ end
56
42
 
57
- result
43
+ def mock_execution_result
44
+ {
45
+ status: "completed",
46
+ provider: "mock",
47
+ message: "Mock execution",
48
+ output: "Mock execution result"
49
+ }
58
50
  end
59
51
 
60
- private
52
+ def wait_for_job_completion(job_id)
53
+ loop do
54
+ job = Que.execute("SELECT * FROM que_jobs WHERE id = $1", [job_id]).first
55
+ return {status: "completed"} if job && job["finished_at"] && job["error_count"] == 0
56
+ return {status: "failed", error: job["last_error_message"]} if job && job["error_count"] && job["error_count"] > 0
57
+
58
+ if job && job["finished_at"].nil?
59
+ duration = Time.now - job["run_at"]
60
+ minutes = (duration / 60).to_i
61
+ seconds = (duration % 60).to_i
62
+ duration_str = (minutes > 0) ? "#{minutes}m #{seconds}s" : "#{seconds}s"
63
+ print "\r🔄 Job #{job_id} is running (#{duration_str})...".ljust(80)
64
+ else
65
+ print "\r⏳ Job #{job_id} is pending...".ljust(80)
66
+ end
67
+ $stdout.flush
68
+ sleep 1
69
+ end
70
+ ensure
71
+ print "\r" + " " * 80 + "\r"
72
+ end
61
73
 
62
74
  def find_template(template_name)
63
75
  template_search_paths.each do |path|
64
- template_file = File.join(path, template_name)
65
- return File.read(template_file) if File.exist?(template_file)
76
+ template_path = File.join(path, template_name)
77
+ return template_path if File.exist?(template_path)
66
78
  end
67
79
  nil
68
80
  end
69
81
 
70
82
  def template_search_paths
71
83
  [
72
- File.join(@project_dir, "templates"),
73
- File.join(@project_dir, "templates", "COMMON"),
74
- File.join(File.dirname(__FILE__), "..", "..", "..", "templates", "EXECUTE"),
75
- File.join(File.dirname(__FILE__), "..", "..", "..", "templates", "COMMON")
84
+ File.join(@project_dir, "templates", "EXECUTE"),
85
+ File.join(@project_dir, "templates", "COMMON")
76
86
  ]
77
87
  end
78
88
 
79
- def composed_prompt(template_name, options = {})
80
- template = find_template(template_name)
81
- return template unless template
89
+ def composed_prompt(step_name, options = {})
90
+ step_spec = Aidp::Execute::Steps::SPEC[step_name]
91
+ raise "Step '#{step_name}' not found" unless step_spec
92
+
93
+ template_name = step_spec["templates"].first
94
+ template_path = find_template(template_name)
95
+ raise "Template not found for step #{step_name}" unless template_path
82
96
 
83
- # Load agent base template if available
84
- agent_base = find_template("AGENT_BASE.md")
85
- template = "#{agent_base}\n\n#{template}" if agent_base
97
+ template = File.read(template_path)
86
98
 
87
- # Replace placeholders
99
+ # Replace template variables in the format {{key}} with option values
88
100
  options.each do |key, value|
89
101
  template = template.gsub("{{#{key}}}", value.to_s)
90
102
  end
91
103
 
92
104
  template
93
105
  end
94
-
95
- def generate_output_files(step_name, output_files, result)
96
- output_files.each do |output_file|
97
- file_path = File.join(@project_dir, output_file)
98
- content = generate_output_content(step_name, output_file, result)
99
- File.write(file_path, content)
100
- end
101
- end
102
-
103
- def generate_output_content(step_name, output_file, result)
104
- case output_file
105
- when /\.md$/
106
- "# #{step_name} Output\n\nGenerated on #{Time.now}\n\n## Result\n\n#{result[:status]}"
107
- when /\.json$/
108
- result.to_json
109
- else
110
- "Output for #{step_name}: #{result[:status]}"
111
- end
112
- end
113
-
114
- def generate_database_export
115
- database_file = File.join(@project_dir, ".aidp.db")
116
- require "sqlite3"
117
-
118
- begin
119
- db = SQLite3::Database.new(database_file)
120
- db.execute("CREATE TABLE IF NOT EXISTS execute_results (step TEXT, status TEXT, completed_at TEXT)")
121
-
122
- Aidp::Execute::Steps::SPEC.keys.each do |step|
123
- if @progress.step_completed?(step)
124
- db.execute("INSERT INTO execute_results (step, status, completed_at) VALUES (?, ?, ?)",
125
- [step, "success", Time.now.iso8601])
126
- end
127
- end
128
- rescue => e
129
- # Log the error but don't fail the execution
130
- puts "Warning: Database export failed: #{e.message}"
131
- end
132
- end
133
106
  end
134
107
  end
135
108
  end
@@ -2,110 +2,109 @@
2
2
 
3
3
  module Aidp
4
4
  module Execute
5
- # Defines the steps, templates, outputs, and associated AI agents for execute mode
6
- class Steps
5
+ module Steps
7
6
  SPEC = {
8
7
  "00_PRD" => {
9
- "templates" => ["00_PRD.md"],
10
- "outs" => ["00_PRD.md"],
11
- "gate" => false,
12
- "agent" => "Product Manager"
8
+ "templates" => ["prd.md"],
9
+ "description" => "Generate Product Requirements Document",
10
+ "outs" => ["docs/prd.md"],
11
+ "gate" => true
13
12
  },
14
13
  "01_NFRS" => {
15
- "templates" => ["01_NFRS.md"],
16
- "outs" => ["01_NFRS.md"],
17
- "gate" => false,
18
- "agent" => "Architect"
14
+ "templates" => ["nfrs.md"],
15
+ "description" => "Define Non-Functional Requirements",
16
+ "outs" => ["docs/nfrs.md"],
17
+ "gate" => true
19
18
  },
20
19
  "02_ARCHITECTURE" => {
21
- "templates" => ["02_ARCHITECTURE.md"],
22
- "outs" => ["02_ARCHITECTURE.md"],
23
- "gate" => false,
24
- "agent" => "Architect"
20
+ "templates" => ["architecture.md"],
21
+ "description" => "Design System Architecture",
22
+ "outs" => ["docs/architecture.md"],
23
+ "gate" => true
25
24
  },
26
25
  "02A_ARCH_GATE_QUESTIONS" => {
27
- "templates" => ["02A_ARCH_GATE_QUESTIONS.md"],
28
- "outs" => ["02A_ARCH_GATE_QUESTIONS.md"],
29
- "gate" => true,
30
- "agent" => "Architect"
26
+ "templates" => ["arch_gate_questions.md"],
27
+ "description" => "Architecture Gate Questions",
28
+ "outs" => ["docs/arch_gate_questions.md"],
29
+ "gate" => true
31
30
  },
32
31
  "03_ADR_FACTORY" => {
33
- "templates" => ["03_ADR_FACTORY.md"],
34
- "outs" => ["03_ADR_FACTORY.md"],
35
- "gate" => false,
36
- "agent" => "Architect"
32
+ "templates" => ["adr_factory.md"],
33
+ "description" => "Generate Architecture Decision Records",
34
+ "outs" => ["docs/adr/*.md"],
35
+ "gate" => false
37
36
  },
38
37
  "04_DOMAIN_DECOMPOSITION" => {
39
- "templates" => ["04_DOMAIN_DECOMPOSITION.md"],
40
- "outs" => ["04_DOMAIN_DECOMPOSITION.md"],
41
- "gate" => false,
42
- "agent" => "Architect"
43
- },
44
- "05_CONTRACTS" => {
45
- "templates" => ["05_CONTRACTS.md"],
46
- "outs" => ["05_CONTRACTS.md"],
47
- "gate" => false,
48
- "agent" => "Architect"
49
- },
50
- "06_THREAT_MODEL" => {
51
- "templates" => ["06_THREAT_MODEL.md"],
52
- "outs" => ["06_THREAT_MODEL.md"],
53
- "gate" => false,
54
- "agent" => "Security Expert"
55
- },
56
- "07_TEST_PLAN" => {
57
- "templates" => ["07_TEST_PLAN.md"],
58
- "outs" => ["07_TEST_PLAN.md"],
59
- "gate" => false,
60
- "agent" => "Test Engineer"
61
- },
62
- "08_TASKS" => {
63
- "templates" => ["08_TASKS.md"],
64
- "outs" => ["08_TASKS.md"],
65
- "gate" => false,
66
- "agent" => "Project Manager"
67
- },
68
- "09_SCAFFOLDING_DEVEX" => {
69
- "templates" => ["09_SCAFFOLDING_DEVEX.md"],
70
- "outs" => ["09_SCAFFOLDING_DEVEX.md"],
71
- "gate" => false,
72
- "agent" => "DevOps Engineer"
73
- },
74
- "10_IMPLEMENTATION_AGENT" => {
75
- "templates" => ["10_IMPLEMENTATION_AGENT.md"],
76
- "outs" => ["10_IMPLEMENTATION_AGENT.md"],
77
- "gate" => false,
78
- "agent" => "Implementation Specialist"
38
+ "templates" => ["domain_decomposition.md"],
39
+ "description" => "Decompose Domain into Components",
40
+ "outs" => ["docs/domain_decomposition.md"],
41
+ "gate" => true
42
+ },
43
+ "05_API_DESIGN" => {
44
+ "templates" => ["api_design.md"],
45
+ "description" => "Design APIs and Interfaces",
46
+ "outs" => ["docs/api_design.md"],
47
+ "gate" => true
48
+ },
49
+ "06_DATA_MODEL" => {
50
+ "templates" => ["data_model.md"],
51
+ "description" => "Design Data Model",
52
+ "outs" => ["docs/data_model.md"],
53
+ "gate" => true
54
+ },
55
+ "07_SECURITY_REVIEW" => {
56
+ "templates" => ["security_review.md"],
57
+ "description" => "Security Review and Threat Model",
58
+ "outs" => ["docs/security_review.md"],
59
+ "gate" => true
60
+ },
61
+ "08_PERFORMANCE_REVIEW" => {
62
+ "templates" => ["performance_review.md"],
63
+ "description" => "Performance Review and Optimization",
64
+ "outs" => ["docs/performance_review.md"],
65
+ "gate" => true
66
+ },
67
+ "09_RELIABILITY_REVIEW" => {
68
+ "templates" => ["reliability_review.md"],
69
+ "description" => "Reliability Review and SLOs",
70
+ "outs" => ["docs/reliability_review.md"],
71
+ "gate" => true
72
+ },
73
+ "10_TESTING_STRATEGY" => {
74
+ "templates" => ["testing_strategy.md"],
75
+ "description" => "Define Testing Strategy",
76
+ "outs" => ["docs/testing_strategy.md"],
77
+ "gate" => true
79
78
  },
80
79
  "11_STATIC_ANALYSIS" => {
81
- "templates" => ["11_STATIC_ANALYSIS.md"],
82
- "outs" => ["11_STATIC_ANALYSIS.md"],
83
- "gate" => false,
84
- "agent" => "Code Quality Expert"
80
+ "templates" => ["static_analysis.md"],
81
+ "description" => "Static Code Analysis",
82
+ "outs" => ["docs/static_analysis.md"],
83
+ "gate" => false
85
84
  },
86
85
  "12_OBSERVABILITY_SLOS" => {
87
- "templates" => ["12_OBSERVABILITY_SLOS.md"],
88
- "outs" => ["12_OBSERVABILITY_SLOS.md"],
89
- "gate" => false,
90
- "agent" => "SRE Engineer"
86
+ "templates" => ["observability_slos.md"],
87
+ "description" => "Define Observability and SLOs",
88
+ "outs" => ["docs/observability_slos.md"],
89
+ "gate" => true
91
90
  },
92
91
  "13_DELIVERY_ROLLOUT" => {
93
- "templates" => ["13_DELIVERY_ROLLOUT.md"],
94
- "outs" => ["13_DELIVERY_ROLLOUT.md"],
95
- "gate" => false,
96
- "agent" => "DevOps Engineer"
92
+ "templates" => ["delivery_rollout.md"],
93
+ "description" => "Plan Delivery and Rollout",
94
+ "outs" => ["docs/delivery_rollout.md"],
95
+ "gate" => true
97
96
  },
98
97
  "14_DOCS_PORTAL" => {
99
- "templates" => ["14_DOCS_PORTAL.md"],
100
- "outs" => ["14_DOCS_PORTAL.md"],
101
- "gate" => false,
102
- "agent" => "Technical Writer"
98
+ "templates" => ["docs_portal.md"],
99
+ "description" => "Documentation Portal",
100
+ "outs" => ["docs/docs_portal.md"],
101
+ "gate" => false
103
102
  },
104
103
  "15_POST_RELEASE" => {
105
- "templates" => ["15_POST_RELEASE.md"],
106
- "outs" => ["15_POST_RELEASE.md"],
107
- "gate" => false,
108
- "agent" => "Project Manager"
104
+ "templates" => ["post_release.md"],
105
+ "description" => "Post-Release Review",
106
+ "outs" => ["docs/post_release.md"],
107
+ "gate" => true
109
108
  }
110
109
  }.freeze
111
110
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aidp
4
+ class JobManager
5
+ def initialize(project_dir)
6
+ @project_dir = project_dir
7
+ end
8
+
9
+ def create_job(job_class, args = {})
10
+ # Create a new job and return job ID
11
+ # This is a placeholder implementation
12
+ job_id = rand(1000..9999)
13
+
14
+ # Store job metadata for testing
15
+ @jobs ||= {}
16
+ @jobs[job_id] = {
17
+ id: job_id,
18
+ job_class: job_class,
19
+ args: args,
20
+ status: "queued",
21
+ created_at: Time.now
22
+ }
23
+
24
+ job_id
25
+ end
26
+
27
+ def get_job(job_id)
28
+ @jobs ||= {}
29
+ @jobs[job_id]
30
+ end
31
+
32
+ def update_job_status(job_id, status, error: nil)
33
+ @jobs ||= {}
34
+ return unless @jobs[job_id]
35
+
36
+ @jobs[job_id][:status] = status
37
+ @jobs[job_id][:error] = error if error
38
+ @jobs[job_id][:updated_at] = Time.now
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "que"
4
+
5
+ module Aidp
6
+ module Jobs
7
+ class BaseJob < Que::Job
8
+ # Default settings
9
+ self.retry_interval = 30.0 # 30 seconds between retries
10
+ self.maximum_retry_count = 3
11
+
12
+ # Error tracking
13
+ class_attribute :error_handlers
14
+ self.error_handlers = []
15
+
16
+ def self.on_error(&block)
17
+ error_handlers << block
18
+ end
19
+
20
+ protected
21
+
22
+ def log_info(message)
23
+ Que.logger.info "[#{self.class.name}] #{message}"
24
+ end
25
+
26
+ def log_error(message)
27
+ Que.logger.error "[#{self.class.name}] #{message}"
28
+ end
29
+
30
+ def handle_error(error)
31
+ self.class.error_handlers.each do |handler|
32
+ handler.call(error, self)
33
+ rescue => e
34
+ log_error "Error handler failed: #{e.message}"
35
+ end
36
+ end
37
+
38
+ # Override run to add error handling
39
+ def run(*args)
40
+ raise NotImplementedError, "#{self.class} must implement #run"
41
+ rescue => error
42
+ handle_error(error)
43
+ raise # Re-raise to trigger Que's retry mechanism
44
+ end
45
+ end
46
+ end
47
+ end