aidp 0.9.6 → 0.11.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 (100) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +194 -25
  3. data/lib/aidp/analyze/error_handler.rb +4 -2
  4. data/lib/aidp/{analysis → analyze}/kb_inspector.rb +93 -89
  5. data/lib/aidp/analyze/prioritizer.rb +3 -2
  6. data/lib/aidp/analyze/progress.rb +2 -1
  7. data/lib/aidp/analyze/ruby_maat_integration.rb +7 -3
  8. data/lib/aidp/analyze/runner.rb +73 -11
  9. data/lib/aidp/{analysis → analyze}/seams.rb +1 -1
  10. data/lib/aidp/analyze/steps.rb +10 -8
  11. data/lib/aidp/{analysis → analyze}/tree_sitter_grammar_loader.rb +11 -5
  12. data/lib/aidp/{analysis → analyze}/tree_sitter_scan.rb +21 -15
  13. data/lib/aidp/cli/checkpoint_command.rb +98 -0
  14. data/lib/aidp/cli/first_run_wizard.rb +83 -103
  15. data/lib/aidp/cli/jobs_command.rb +270 -36
  16. data/lib/aidp/cli/terminal_io.rb +3 -3
  17. data/lib/aidp/cli.rb +411 -69
  18. data/lib/aidp/config.rb +5 -8
  19. data/lib/aidp/debug_logger.rb +4 -4
  20. data/lib/aidp/debug_mixin.rb +11 -4
  21. data/lib/aidp/execute/checkpoint.rb +282 -0
  22. data/lib/aidp/execute/checkpoint_display.rb +221 -0
  23. data/lib/aidp/execute/progress.rb +2 -1
  24. data/lib/aidp/execute/prompt_manager.rb +62 -0
  25. data/lib/aidp/execute/runner.rb +67 -20
  26. data/lib/aidp/execute/steps.rb +36 -27
  27. data/lib/aidp/execute/work_loop_runner.rb +308 -0
  28. data/lib/aidp/execute/workflow_selector.rb +50 -26
  29. data/lib/aidp/harness/condition_detector.rb +4 -4
  30. data/lib/aidp/harness/config_schema.rb +40 -0
  31. data/lib/aidp/harness/config_validator.rb +3 -6
  32. data/lib/aidp/harness/configuration.rb +35 -1
  33. data/lib/aidp/harness/enhanced_runner.rb +25 -4
  34. data/lib/aidp/harness/error_handler.rb +103 -28
  35. data/lib/aidp/harness/provider_factory.rb +6 -1
  36. data/lib/aidp/harness/provider_manager.rb +273 -19
  37. data/lib/aidp/harness/runner.rb +14 -6
  38. data/lib/aidp/harness/simple_user_interface.rb +6 -4
  39. data/lib/aidp/harness/status_display.rb +118 -106
  40. data/lib/aidp/harness/test_runner.rb +83 -0
  41. data/lib/aidp/harness/ui/enhanced_tui.rb +7 -5
  42. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +22 -4
  43. data/lib/aidp/harness/ui/error_handler.rb +7 -2
  44. data/lib/aidp/harness/ui/frame_manager.rb +61 -39
  45. data/lib/aidp/harness/ui/job_monitor.rb +2 -0
  46. data/lib/aidp/harness/ui/navigation/main_menu.rb +27 -16
  47. data/lib/aidp/harness/ui/navigation/menu_item.rb +1 -0
  48. data/lib/aidp/harness/ui/navigation/menu_state.rb +1 -0
  49. data/lib/aidp/harness/ui/navigation/submenu.rb +1 -0
  50. data/lib/aidp/harness/ui/navigation/workflow_selector.rb +2 -0
  51. data/lib/aidp/harness/ui/progress_display.rb +26 -7
  52. data/lib/aidp/harness/ui/question_collector.rb +2 -0
  53. data/lib/aidp/harness/ui/spinner_group.rb +2 -0
  54. data/lib/aidp/harness/ui/spinner_helper.rb +1 -1
  55. data/lib/aidp/harness/ui/status_manager.rb +4 -2
  56. data/lib/aidp/harness/ui/status_widget.rb +20 -9
  57. data/lib/aidp/harness/ui/workflow_controller.rb +27 -9
  58. data/lib/aidp/harness/user_interface.rb +338 -330
  59. data/lib/aidp/jobs/background_runner.rb +278 -0
  60. data/lib/aidp/message_display.rb +48 -0
  61. data/lib/aidp/provider_manager.rb +13 -7
  62. data/lib/aidp/providers/anthropic.rb +101 -18
  63. data/lib/aidp/providers/base.rb +51 -1
  64. data/lib/aidp/providers/codex.rb +248 -0
  65. data/lib/aidp/providers/cursor.rb +39 -48
  66. data/lib/aidp/providers/gemini.rb +26 -16
  67. data/lib/aidp/providers/github_copilot.rb +263 -0
  68. data/lib/aidp/providers/opencode.rb +38 -47
  69. data/lib/aidp/version.rb +1 -1
  70. data/lib/aidp/workflows/definitions.rb +357 -0
  71. data/lib/aidp/workflows/selector.rb +171 -0
  72. data/lib/aidp.rb +16 -4
  73. data/templates/planning/generate_llm_style_guide.md +119 -0
  74. metadata +43 -31
  75. data/lib/aidp/analyze/progress_visualizer.rb +0 -314
  76. /data/templates/{ANALYZE/02_ARCHITECTURE_ANALYSIS.md → analysis/analyze_architecture.md} +0 -0
  77. /data/templates/{ANALYZE/05_DOCUMENTATION_ANALYSIS.md → analysis/analyze_documentation.md} +0 -0
  78. /data/templates/{ANALYZE/04_FUNCTIONALITY_ANALYSIS.md → analysis/analyze_functionality.md} +0 -0
  79. /data/templates/{ANALYZE/01_REPOSITORY_ANALYSIS.md → analysis/analyze_repository.md} +0 -0
  80. /data/templates/{ANALYZE/06_STATIC_ANALYSIS.md → analysis/analyze_static_code.md} +0 -0
  81. /data/templates/{ANALYZE/03_TEST_ANALYSIS.md → analysis/analyze_tests.md} +0 -0
  82. /data/templates/{ANALYZE/07_REFACTORING_RECOMMENDATIONS.md → analysis/recommend_refactoring.md} +0 -0
  83. /data/templates/{ANALYZE/06a_tree_sitter_scan.md → analysis/scan_with_tree_sitter.md} +0 -0
  84. /data/templates/{EXECUTE/11_STATIC_ANALYSIS.md → implementation/configure_static_analysis.md} +0 -0
  85. /data/templates/{EXECUTE/14_DOCS_PORTAL.md → implementation/create_documentation_portal.md} +0 -0
  86. /data/templates/{EXECUTE/10_IMPLEMENTATION_AGENT.md → implementation/implement_features.md} +0 -0
  87. /data/templates/{EXECUTE/13_DELIVERY_ROLLOUT.md → implementation/plan_delivery.md} +0 -0
  88. /data/templates/{EXECUTE/15_POST_RELEASE.md → implementation/review_post_release.md} +0 -0
  89. /data/templates/{EXECUTE/09_SCAFFOLDING_DEVEX.md → implementation/setup_scaffolding.md} +0 -0
  90. /data/templates/{EXECUTE/02A_ARCH_GATE_QUESTIONS.md → planning/ask_architecture_questions.md} +0 -0
  91. /data/templates/{EXECUTE/00_PRD.md → planning/create_prd.md} +0 -0
  92. /data/templates/{EXECUTE/08_TASKS.md → planning/create_tasks.md} +0 -0
  93. /data/templates/{EXECUTE/04_DOMAIN_DECOMPOSITION.md → planning/decompose_domain.md} +0 -0
  94. /data/templates/{EXECUTE/01_NFRS.md → planning/define_nfrs.md} +0 -0
  95. /data/templates/{EXECUTE/05_CONTRACTS.md → planning/design_apis.md} +0 -0
  96. /data/templates/{EXECUTE/02_ARCHITECTURE.md → planning/design_architecture.md} +0 -0
  97. /data/templates/{EXECUTE/06_THREAT_MODEL.md → planning/design_data_model.md} +0 -0
  98. /data/templates/{EXECUTE/03_ADR_FACTORY.md → planning/generate_adrs.md} +0 -0
  99. /data/templates/{EXECUTE/12_OBSERVABILITY_SLOS.md → planning/plan_observability.md} +0 -0
  100. /data/templates/{EXECUTE/07_TEST_PLAN.md → planning/plan_testing.md} +0 -0
@@ -1,45 +1,291 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "tty-prompt"
3
4
  require "tty-box"
4
5
  require "pastel"
5
6
  require "io/console"
6
7
  require "json"
7
8
  require_relative "terminal_io"
8
9
  require_relative "../storage/file_manager"
10
+ require_relative "../jobs/background_runner"
9
11
 
10
12
  module Aidp
11
13
  class CLI
12
14
  class JobsCommand
13
- def initialize(input: $stdin, output: $stdout)
14
- @io = TerminalIO.new(input, output)
15
+ include Aidp::MessageDisplay
16
+
17
+ def initialize(input: nil, output: nil, prompt: TTY::Prompt.new)
18
+ @io = TerminalIO.new(input: input, output: output)
19
+ @prompt = prompt
15
20
  @pastel = Pastel.new
16
21
  @running = true
17
22
  @view_mode = :list
18
23
  @selected_job_id = nil
19
- @jobs_displayed = false # Track if we've displayed jobs in interactive mode
24
+ @jobs_displayed = false # Track if we've displayed jobs in interactive mode
20
25
  @file_manager = Aidp::Storage::FileManager.new(File.join(Dir.pwd, ".aidp"))
21
- @screen_width = 80 # Default screen width
26
+ @background_runner = Aidp::Jobs::BackgroundRunner.new(Dir.pwd)
27
+ @screen_width = 80 # Default screen width
22
28
  end
23
29
 
24
- def run
25
- # Simple harness jobs display
26
- jobs = fetch_harness_jobs
30
+ private
31
+
32
+ public
33
+
34
+ def run(subcommand = nil, args = [])
35
+ case subcommand
36
+ when "list", nil
37
+ list_jobs
38
+ when "status"
39
+ job_id = args.shift
40
+ if job_id
41
+ show_job_status(job_id, follow: args.include?("--follow"))
42
+ else
43
+ display_message("Usage: aidp jobs status <job_id> [--follow]", type: :error)
44
+ end
45
+ when "stop"
46
+ job_id = args.shift
47
+ if job_id
48
+ stop_job(job_id)
49
+ else
50
+ display_message("Usage: aidp jobs stop <job_id>", type: :error)
51
+ end
52
+ when "logs"
53
+ job_id = args.shift
54
+ if job_id
55
+ show_job_logs(job_id, tail: args.include?("--tail"), follow: args.include?("--follow"))
56
+ else
57
+ display_message("Usage: aidp jobs logs <job_id> [--tail] [--follow]", type: :error)
58
+ end
59
+ else
60
+ display_message("Unknown jobs subcommand: #{subcommand}", type: :error)
61
+ display_message("Available: list, status, stop, logs", type: :info)
62
+ end
63
+ end
64
+
65
+ def list_jobs
66
+ jobs = @background_runner.list_jobs
27
67
 
28
68
  if jobs.empty?
29
- @io.puts "Harness Jobs"
30
- @io.puts "-" * @screen_width
31
- @io.puts
32
- @io.puts "No harness jobs found"
33
- @io.puts
34
- @io.puts "Harness jobs are background tasks that run during harness mode."
35
- @io.puts "They are stored as JSON files in the .aidp/harness_logs/ directory."
69
+ display_message("Background Jobs", type: :info)
70
+ display_message("-" * @screen_width, type: :muted)
71
+ display_message("")
72
+ display_message("No background jobs found", type: :info)
73
+ display_message("")
74
+ display_message("Start a background job with:", type: :info)
75
+ display_message(" aidp execute --background", type: :info)
76
+ display_message(" aidp analyze --background", type: :info)
77
+ return
78
+ end
79
+
80
+ render_background_jobs(jobs)
81
+ end
82
+
83
+ def show_job_status(job_id, follow: false)
84
+ if follow
85
+ follow_job_status(job_id)
86
+ else
87
+ status = @background_runner.job_status(job_id)
88
+ unless status
89
+ display_message("Job not found: #{job_id}", type: :error)
90
+ return
91
+ end
92
+
93
+ render_job_status(status)
94
+ end
95
+ end
96
+
97
+ def stop_job(job_id)
98
+ result = @background_runner.stop_job(job_id)
99
+
100
+ if result[:success]
101
+ display_message("✓ #{result[:message]}", type: :success)
102
+ else
103
+ display_message("✗ #{result[:message]}", type: :error)
104
+ end
105
+ end
106
+
107
+ def show_job_logs(job_id, tail: false, follow: false)
108
+ if follow
109
+ display_message("Following logs for job #{job_id} (Ctrl+C to exit)...", type: :info)
110
+ @background_runner.follow_job_logs(job_id)
36
111
  else
37
- render_harness_jobs(jobs)
112
+ logs = @background_runner.job_logs(job_id, tail: tail, lines: 50)
113
+ unless logs
114
+ display_message("No logs found for job: #{job_id}", type: :error)
115
+ return
116
+ end
117
+
118
+ display_message("Logs for job #{job_id}:", type: :info)
119
+ display_message("-" * @screen_width, type: :muted)
120
+ puts logs
38
121
  end
39
122
  end
40
123
 
41
124
  private
42
125
 
126
+ def render_background_jobs(jobs)
127
+ require "tty-table"
128
+
129
+ display_message("Background Jobs", type: :info)
130
+ display_message("=" * @screen_width, type: :muted)
131
+ display_message("")
132
+
133
+ headers = ["Job ID", "Mode", "Status", "Started", "Duration"]
134
+ rows = jobs.map do |job|
135
+ [
136
+ job[:job_id][0..15] + "...",
137
+ job[:mode].to_s.capitalize,
138
+ format_job_status(job[:status]),
139
+ format_time(job[:started_at]),
140
+ format_duration_from_start(job[:started_at], job[:completed_at])
141
+ ]
142
+ end
143
+
144
+ table = TTY::Table.new(headers, rows)
145
+ puts table.render(:basic)
146
+
147
+ display_message("")
148
+ display_message("Commands:", type: :info)
149
+ display_message(" aidp jobs status <job_id> - Show detailed status", type: :info)
150
+ display_message(" aidp jobs logs <job_id> --tail - Show recent logs", type: :info)
151
+ display_message(" aidp jobs stop <job_id> - Stop a running job", type: :info)
152
+ end
153
+
154
+ def render_job_status(status)
155
+ display_message("Job Status: #{status[:job_id]}", type: :info)
156
+ display_message("=" * @screen_width, type: :muted)
157
+ display_message("")
158
+ display_message("Mode: #{status[:mode]}", type: :info)
159
+ display_message("Status: #{format_job_status(status[:status])}", type: :info)
160
+ display_message("PID: #{status[:pid] || "N/A"}", type: :info)
161
+ display_message("Running: #{status[:running] ? "Yes" : "No"}", type: :info)
162
+ display_message("Started: #{format_time(status[:started_at])}", type: :info)
163
+
164
+ if status[:completed_at]
165
+ display_message("Completed: #{format_time(status[:completed_at])}", type: :info)
166
+ display_message("Duration: #{format_duration_from_start(status[:started_at], status[:completed_at])}", type: :info)
167
+ end
168
+
169
+ if status[:checkpoint]
170
+ display_message("", type: :info)
171
+ display_message("Latest Checkpoint:", type: :info)
172
+ cp = status[:checkpoint]
173
+ display_message(" Step: #{cp[:step_name]}", type: :info)
174
+ display_message(" Iteration: #{cp[:iteration]}", type: :info)
175
+ display_message(" Updated: #{format_checkpoint_age(cp[:timestamp])}", type: :info)
176
+
177
+ if cp[:metrics]
178
+ display_message(" Metrics:", type: :info)
179
+ display_message(" LOC: #{cp[:metrics][:lines_of_code]}", type: :info)
180
+ display_message(" Coverage: #{cp[:metrics][:test_coverage]}%", type: :info)
181
+ display_message(" Quality: #{cp[:metrics][:code_quality]}%", type: :info)
182
+ end
183
+ end
184
+
185
+ display_message("", type: :info)
186
+ display_message("Log file: #{status[:log_file]}", type: :muted)
187
+ end
188
+
189
+ def follow_job_status(job_id)
190
+ display_message("Following job status for #{job_id} (Ctrl+C to exit)...", type: :info)
191
+ display_message("")
192
+
193
+ begin
194
+ loop do
195
+ # Clear screen
196
+ print "\e[2J\e[H"
197
+
198
+ status = @background_runner.job_status(job_id)
199
+ unless status
200
+ display_message("Job not found: #{job_id}", type: :error)
201
+ break
202
+ end
203
+
204
+ render_job_status(status)
205
+
206
+ # Exit if job is done
207
+ break unless status[:running]
208
+
209
+ # Wait before next update
210
+ sleep 2
211
+ end
212
+ rescue Interrupt
213
+ display_message("\nStopped following job status", type: :info)
214
+ end
215
+ end
216
+
217
+ def format_job_status(status)
218
+ case status.to_s
219
+ when "running"
220
+ @pastel.green("● Running")
221
+ when "completed"
222
+ @pastel.cyan("✓ Completed")
223
+ when "failed"
224
+ @pastel.red("✗ Failed")
225
+ when "stopped"
226
+ @pastel.yellow("⏹ Stopped")
227
+ when "stuck"
228
+ @pastel.magenta("⚠ Stuck")
229
+ else
230
+ @pastel.dim(status.to_s)
231
+ end
232
+ end
233
+
234
+ def format_time(time)
235
+ return "N/A" unless time
236
+
237
+ begin
238
+ time = Time.parse(time.to_s) if time.is_a?(String)
239
+ time.strftime("%Y-%m-%d %H:%M:%S")
240
+ rescue
241
+ time.to_s
242
+ end
243
+ end
244
+
245
+ def format_duration_from_start(started_at, completed_at)
246
+ return "N/A" unless started_at
247
+
248
+ start_time = started_at.is_a?(String) ? Time.parse(started_at) : started_at
249
+ end_time = if completed_at
250
+ completed_at.is_a?(String) ? Time.parse(completed_at) : completed_at
251
+ else
252
+ Time.now
253
+ end
254
+
255
+ duration = end_time - start_time
256
+ format_duration(duration)
257
+ end
258
+
259
+ def format_duration(seconds)
260
+ return "0s" if seconds.nil? || seconds <= 0
261
+
262
+ hours = (seconds / 3600).to_i
263
+ minutes = ((seconds % 3600) / 60).to_i
264
+ secs = (seconds % 60).to_i
265
+
266
+ parts = []
267
+ parts << "#{hours}h" if hours > 0
268
+ parts << "#{minutes}m" if minutes > 0
269
+ parts << "#{secs}s" if secs > 0 || parts.empty?
270
+
271
+ parts.join(" ")
272
+ end
273
+
274
+ def format_checkpoint_age(timestamp)
275
+ return "N/A" unless timestamp
276
+
277
+ time = Time.parse(timestamp.to_s)
278
+ age = Time.now - time
279
+
280
+ if age < 60
281
+ "#{age.to_i}s ago"
282
+ elsif age < 3600
283
+ "#{(age / 60).to_i}m ago"
284
+ else
285
+ "#{(age / 3600).to_i}h ago"
286
+ end
287
+ end
288
+
43
289
  # Fetch harness jobs from file-based storage
44
290
  def fetch_harness_jobs
45
291
  jobs = []
@@ -64,7 +310,7 @@ module Aidp
64
310
 
65
311
  jobs << job_info
66
312
  rescue JSON::ParserError => e
67
- @io.puts "Warning: Could not parse harness log #{log_file}: #{e.message}" if ENV["AIDP_DEBUG"]
313
+ display_message("Warning: Could not parse harness log #{log_file}: #{e.message}", type: :warning) if ENV["AIDP_DEBUG"]
68
314
  end
69
315
 
70
316
  # Sort by creation time (newest first)
@@ -91,9 +337,9 @@ module Aidp
91
337
 
92
338
  # Render harness jobs in a simple table
93
339
  def render_harness_jobs(jobs)
94
- @io.puts "Harness Jobs"
95
- @io.puts "-" * @screen_width
96
- @io.puts
340
+ display_message("Harness Jobs", type: :info)
341
+ display_message("-" * @screen_width, type: :muted)
342
+ display_message("")
97
343
 
98
344
  # Create job content for TTY::Box
99
345
  job_content = []
@@ -121,24 +367,12 @@ module Aidp
121
367
  border: :thick,
122
368
  padding: [1, 2]
123
369
  )
124
- puts box
125
-
126
- @io.puts
127
- @io.puts "Total: #{jobs.length} harness job(s)"
128
- @io.puts
129
- @io.puts "Note: Harness jobs are stored as JSON files in .aidp/harness_logs/"
130
- end
370
+ display_message(box)
131
371
 
132
- # Format timestamp for display
133
- def format_time(timestamp)
134
- return "unknown" unless timestamp
135
-
136
- begin
137
- time = Time.parse(timestamp)
138
- time.strftime("%Y-%m-%d %H:%M:%S")
139
- rescue
140
- timestamp
141
- end
372
+ display_message("")
373
+ display_message("Total: #{jobs.length} harness job(s)", type: :info)
374
+ display_message("")
375
+ display_message("Note: Harness jobs are stored as JSON files in .aidp/harness_logs/", type: :muted)
142
376
  end
143
377
 
144
378
  # Truncate message for table display
@@ -5,9 +5,9 @@ require "stringio"
5
5
  module Aidp
6
6
  class CLI
7
7
  class TerminalIO
8
- def initialize(input = $stdin, output = $stdout)
9
- @input = input
10
- @output = output
8
+ def initialize(input: nil, output: nil)
9
+ @input = input || $stdin
10
+ @output = output || $stdout
11
11
  end
12
12
 
13
13
  def ready?