aidp 0.5.0 → 0.8.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 +128 -151
- data/bin/aidp +1 -1
- data/lib/aidp/analysis/kb_inspector.rb +471 -0
- data/lib/aidp/analysis/seams.rb +159 -0
- data/lib/aidp/analysis/tree_sitter_grammar_loader.rb +480 -0
- data/lib/aidp/analysis/tree_sitter_scan.rb +686 -0
- data/lib/aidp/analyze/error_handler.rb +2 -78
- data/lib/aidp/analyze/json_file_storage.rb +292 -0
- data/lib/aidp/analyze/progress.rb +12 -0
- data/lib/aidp/analyze/progress_visualizer.rb +12 -17
- data/lib/aidp/analyze/ruby_maat_integration.rb +13 -31
- data/lib/aidp/analyze/runner.rb +256 -87
- data/lib/aidp/analyze/steps.rb +6 -0
- data/lib/aidp/cli/jobs_command.rb +103 -435
- data/lib/aidp/cli.rb +317 -191
- data/lib/aidp/config.rb +298 -10
- data/lib/aidp/debug_logger.rb +195 -0
- data/lib/aidp/debug_mixin.rb +187 -0
- data/lib/aidp/execute/progress.rb +9 -0
- data/lib/aidp/execute/runner.rb +221 -40
- data/lib/aidp/execute/steps.rb +17 -7
- data/lib/aidp/execute/workflow_selector.rb +211 -0
- data/lib/aidp/harness/completion_checker.rb +268 -0
- data/lib/aidp/harness/condition_detector.rb +1526 -0
- data/lib/aidp/harness/config_loader.rb +373 -0
- data/lib/aidp/harness/config_manager.rb +382 -0
- data/lib/aidp/harness/config_schema.rb +1006 -0
- data/lib/aidp/harness/config_validator.rb +355 -0
- data/lib/aidp/harness/configuration.rb +477 -0
- data/lib/aidp/harness/enhanced_runner.rb +494 -0
- data/lib/aidp/harness/error_handler.rb +616 -0
- data/lib/aidp/harness/provider_config.rb +423 -0
- data/lib/aidp/harness/provider_factory.rb +306 -0
- data/lib/aidp/harness/provider_manager.rb +1269 -0
- data/lib/aidp/harness/provider_type_checker.rb +88 -0
- data/lib/aidp/harness/runner.rb +411 -0
- data/lib/aidp/harness/state/errors.rb +28 -0
- data/lib/aidp/harness/state/metrics.rb +219 -0
- data/lib/aidp/harness/state/persistence.rb +128 -0
- data/lib/aidp/harness/state/provider_state.rb +132 -0
- data/lib/aidp/harness/state/ui_state.rb +68 -0
- data/lib/aidp/harness/state/workflow_state.rb +123 -0
- data/lib/aidp/harness/state_manager.rb +586 -0
- data/lib/aidp/harness/status_display.rb +888 -0
- data/lib/aidp/harness/ui/base.rb +16 -0
- data/lib/aidp/harness/ui/enhanced_tui.rb +545 -0
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +252 -0
- data/lib/aidp/harness/ui/error_handler.rb +132 -0
- data/lib/aidp/harness/ui/frame_manager.rb +361 -0
- data/lib/aidp/harness/ui/job_monitor.rb +500 -0
- data/lib/aidp/harness/ui/navigation/main_menu.rb +311 -0
- data/lib/aidp/harness/ui/navigation/menu_formatter.rb +120 -0
- data/lib/aidp/harness/ui/navigation/menu_item.rb +142 -0
- data/lib/aidp/harness/ui/navigation/menu_state.rb +139 -0
- data/lib/aidp/harness/ui/navigation/submenu.rb +202 -0
- data/lib/aidp/harness/ui/navigation/workflow_selector.rb +176 -0
- data/lib/aidp/harness/ui/progress_display.rb +280 -0
- data/lib/aidp/harness/ui/question_collector.rb +141 -0
- data/lib/aidp/harness/ui/spinner_group.rb +184 -0
- data/lib/aidp/harness/ui/spinner_helper.rb +152 -0
- data/lib/aidp/harness/ui/status_manager.rb +312 -0
- data/lib/aidp/harness/ui/status_widget.rb +280 -0
- data/lib/aidp/harness/ui/workflow_controller.rb +312 -0
- data/lib/aidp/harness/user_interface.rb +2381 -0
- data/lib/aidp/provider_manager.rb +131 -7
- data/lib/aidp/providers/anthropic.rb +28 -109
- data/lib/aidp/providers/base.rb +170 -0
- data/lib/aidp/providers/cursor.rb +52 -183
- data/lib/aidp/providers/gemini.rb +24 -109
- data/lib/aidp/providers/macos_ui.rb +99 -5
- data/lib/aidp/providers/opencode.rb +194 -0
- data/lib/aidp/storage/csv_storage.rb +172 -0
- data/lib/aidp/storage/file_manager.rb +214 -0
- data/lib/aidp/storage/json_storage.rb +140 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp.rb +56 -35
- data/templates/ANALYZE/06a_tree_sitter_scan.md +217 -0
- data/templates/COMMON/AGENT_BASE.md +11 -0
- data/templates/EXECUTE/00_PRD.md +4 -4
- data/templates/EXECUTE/02_ARCHITECTURE.md +5 -4
- data/templates/EXECUTE/07_TEST_PLAN.md +4 -1
- data/templates/EXECUTE/08_TASKS.md +4 -4
- data/templates/EXECUTE/10_IMPLEMENTATION_AGENT.md +4 -4
- data/templates/README.md +279 -0
- data/templates/aidp-development.yml.example +373 -0
- data/templates/aidp-minimal.yml.example +48 -0
- data/templates/aidp-production.yml.example +475 -0
- data/templates/aidp.yml.example +598 -0
- metadata +106 -64
- data/lib/aidp/analyze/agent_personas.rb +0 -71
- data/lib/aidp/analyze/agent_tool_executor.rb +0 -445
- data/lib/aidp/analyze/data_retention_manager.rb +0 -426
- data/lib/aidp/analyze/database.rb +0 -260
- data/lib/aidp/analyze/dependencies.rb +0 -335
- data/lib/aidp/analyze/export_manager.rb +0 -425
- data/lib/aidp/analyze/focus_guidance.rb +0 -517
- data/lib/aidp/analyze/incremental_analyzer.rb +0 -543
- data/lib/aidp/analyze/language_analysis_strategies.rb +0 -897
- data/lib/aidp/analyze/large_analysis_progress.rb +0 -504
- data/lib/aidp/analyze/memory_manager.rb +0 -365
- data/lib/aidp/analyze/metrics_storage.rb +0 -336
- data/lib/aidp/analyze/parallel_processor.rb +0 -460
- data/lib/aidp/analyze/performance_optimizer.rb +0 -694
- data/lib/aidp/analyze/repository_chunker.rb +0 -704
- data/lib/aidp/analyze/static_analysis_detector.rb +0 -577
- data/lib/aidp/analyze/storage.rb +0 -662
- data/lib/aidp/analyze/tool_configuration.rb +0 -456
- data/lib/aidp/analyze/tool_modernization.rb +0 -750
- data/lib/aidp/database/pg_adapter.rb +0 -148
- data/lib/aidp/database_config.rb +0 -69
- data/lib/aidp/database_connection.rb +0 -72
- data/lib/aidp/database_migration.rb +0 -158
- data/lib/aidp/job_manager.rb +0 -41
- data/lib/aidp/jobs/base_job.rb +0 -47
- data/lib/aidp/jobs/provider_execution_job.rb +0 -96
- data/lib/aidp/project_detector.rb +0 -117
- data/lib/aidp/providers/agent_supervisor.rb +0 -348
- data/lib/aidp/providers/supervised_base.rb +0 -317
- data/lib/aidp/providers/supervised_cursor.rb +0 -22
- data/lib/aidp/sync.rb +0 -13
- data/lib/aidp/workspace.rb +0 -19
@@ -1,487 +1,155 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "tty-
|
4
|
-
require "
|
5
|
-
require "tty-table"
|
3
|
+
require "tty-box"
|
4
|
+
require "pastel"
|
6
5
|
require "io/console"
|
7
|
-
require "que"
|
8
6
|
require "json"
|
9
7
|
require_relative "terminal_io"
|
8
|
+
require_relative "../storage/file_manager"
|
10
9
|
|
11
10
|
module Aidp
|
12
11
|
class CLI
|
13
12
|
class JobsCommand
|
14
13
|
def initialize(input: $stdin, output: $stdout)
|
15
14
|
@io = TerminalIO.new(input, output)
|
16
|
-
@
|
17
|
-
@screen_width = TTY::Screen.width
|
18
|
-
@screen_height = TTY::Screen.height
|
15
|
+
@pastel = Pastel.new
|
19
16
|
@running = true
|
20
17
|
@view_mode = :list
|
21
18
|
@selected_job_id = nil
|
22
19
|
@jobs_displayed = false # Track if we've displayed jobs in interactive mode
|
20
|
+
@file_manager = Aidp::Storage::FileManager.new(File.join(Dir.pwd, ".aidp"))
|
21
|
+
@screen_width = 80 # Default screen width
|
23
22
|
end
|
24
23
|
|
25
24
|
def run
|
26
|
-
#
|
27
|
-
|
28
|
-
|
29
|
-
# Start the UI loop with timeout
|
30
|
-
Timeout.timeout(60) do
|
31
|
-
while @running
|
32
|
-
case @view_mode
|
33
|
-
when :list
|
34
|
-
result = render_job_list
|
35
|
-
if result == :exit
|
36
|
-
# Exit immediately when no jobs are found
|
37
|
-
break
|
38
|
-
end
|
39
|
-
handle_input
|
40
|
-
sleep_for_refresh unless @running
|
41
|
-
when :details
|
42
|
-
render_job_details
|
43
|
-
handle_input
|
44
|
-
sleep_for_refresh unless @running
|
45
|
-
when :output
|
46
|
-
render_job_output
|
47
|
-
handle_input
|
48
|
-
sleep_for_refresh unless @running
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
rescue Timeout::Error
|
53
|
-
@io.puts "Command timed out"
|
54
|
-
@running = false
|
55
|
-
ensure
|
56
|
-
# Only clear screen and show cursor if we were in interactive mode
|
57
|
-
# (i.e., if we had jobs to display and were in a real terminal)
|
58
|
-
if @view_mode == :list && @jobs_displayed
|
59
|
-
@io.print @cursor.clear_screen
|
60
|
-
@io.print @cursor.show
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
private
|
65
|
-
|
66
|
-
def setup_database_connection
|
67
|
-
# Skip database setup in test mode if we're mocking
|
68
|
-
return if ENV["RACK_ENV"] == "test" && ENV["MOCK_DATABASE"] == "true"
|
69
|
-
|
70
|
-
dbname = (ENV["RACK_ENV"] == "test") ? "aidp_test" : (ENV["AIDP_DB_NAME"] || "aidp")
|
71
|
-
|
72
|
-
# Use Sequel for connection pooling with timeout
|
73
|
-
Timeout.timeout(10) do
|
74
|
-
Que.connection = Sequel.connect(
|
75
|
-
adapter: "postgres",
|
76
|
-
host: ENV["AIDP_DB_HOST"] || "localhost",
|
77
|
-
port: ENV["AIDP_DB_PORT"] || 5432,
|
78
|
-
database: dbname,
|
79
|
-
user: ENV["AIDP_DB_USER"] || ENV["USER"],
|
80
|
-
password: ENV["AIDP_DB_PASSWORD"],
|
81
|
-
max_connections: 10,
|
82
|
-
pool_timeout: 30
|
83
|
-
)
|
84
|
-
|
85
|
-
Que.migrate!(version: Que::Migrations::CURRENT_VERSION)
|
86
|
-
end
|
87
|
-
rescue Timeout::Error
|
88
|
-
@io.puts "Database connection timed out"
|
89
|
-
raise
|
90
|
-
rescue => e
|
91
|
-
@io.puts "Error connecting to database: #{e.message}"
|
92
|
-
raise
|
93
|
-
end
|
94
|
-
|
95
|
-
def render_job_list
|
96
|
-
jobs = fetch_jobs
|
25
|
+
# Simple harness jobs display
|
26
|
+
jobs = fetch_harness_jobs
|
97
27
|
|
98
28
|
if jobs.empty?
|
99
|
-
|
100
|
-
@io.puts "Background Jobs"
|
29
|
+
@io.puts "Harness Jobs"
|
101
30
|
@io.puts "-" * @screen_width
|
102
31
|
@io.puts
|
103
|
-
@io.puts "No jobs
|
104
|
-
return :exit
|
105
|
-
else
|
106
|
-
# Clear screen and hide cursor only when we have jobs to display
|
107
|
-
@io.print(@cursor.hide)
|
108
|
-
@io.print(@cursor.clear_screen)
|
109
|
-
@io.print(@cursor.move_to(0, 0))
|
110
|
-
@jobs_displayed = true # Mark that we've displayed jobs
|
111
|
-
|
112
|
-
# Print header
|
113
|
-
@io.puts "Background Jobs"
|
114
|
-
@io.puts "-" * @screen_width
|
32
|
+
@io.puts "No harness jobs found"
|
115
33
|
@io.puts
|
116
|
-
|
117
|
-
|
118
|
-
table = TTY::Table.new(
|
119
|
-
header: ["ID", "Job", "Queue", "Status", "Runtime", "Error"],
|
120
|
-
rows: jobs.map do |job|
|
121
|
-
[
|
122
|
-
job[:id].to_s,
|
123
|
-
job[:job_class]&.split("::")&.last || "Unknown",
|
124
|
-
job[:queue] || "default",
|
125
|
-
job_status(job),
|
126
|
-
format_runtime(job),
|
127
|
-
truncate_error(job[:last_error_message])
|
128
|
-
]
|
129
|
-
end
|
130
|
-
)
|
131
|
-
|
132
|
-
# Render table
|
133
|
-
@io.puts table.render(:unicode, padding: [0, 1], width: @screen_width)
|
134
|
-
end
|
135
|
-
|
136
|
-
@io.puts
|
137
|
-
@io.puts "Commands: (d)etails, (o)utput, (r)etry, (k)ill, (q)uit"
|
138
|
-
:continue
|
139
|
-
end
|
140
|
-
|
141
|
-
def render_job_details
|
142
|
-
return switch_to_list unless @selected_job_id
|
143
|
-
|
144
|
-
job = fetch_job(@selected_job_id)
|
145
|
-
return switch_to_list unless job
|
146
|
-
|
147
|
-
# Clear screen and hide cursor
|
148
|
-
@io.print(@cursor.hide)
|
149
|
-
@io.print(@cursor.clear_screen)
|
150
|
-
@io.print(@cursor.move_to(0, 0))
|
151
|
-
|
152
|
-
# Print header
|
153
|
-
@io.puts "Job Details - ID: #{@selected_job_id}"
|
154
|
-
@io.puts "-" * @screen_width
|
155
|
-
@io.puts
|
156
|
-
|
157
|
-
# Print job details
|
158
|
-
@io.puts "Class: #{job[:job_class]}"
|
159
|
-
@io.puts "Queue: #{job[:queue]}"
|
160
|
-
@io.puts "Status: #{job_status(job)}"
|
161
|
-
@io.puts "Runtime: #{format_runtime(job)}"
|
162
|
-
@io.puts "Started: #{job[:run_at]}"
|
163
|
-
@io.puts "Finished: #{job[:finished_at]}"
|
164
|
-
@io.puts "Attempts: #{job[:error_count]}"
|
165
|
-
@io.puts
|
166
|
-
@io.puts "Error:" if job[:last_error_message]
|
167
|
-
@io.puts job[:last_error_message] if job[:last_error_message]
|
168
|
-
|
169
|
-
@io.puts
|
170
|
-
@io.puts "Commands: (b)ack, (o)utput, (r)etry, (k)ill, (q)uit"
|
171
|
-
end
|
172
|
-
|
173
|
-
def render_job_output
|
174
|
-
return switch_to_list unless @selected_job_id
|
175
|
-
|
176
|
-
job = fetch_job(@selected_job_id)
|
177
|
-
return switch_to_list unless job
|
178
|
-
|
179
|
-
# Clear screen and hide cursor
|
180
|
-
@io.print(@cursor.hide)
|
181
|
-
@io.print(@cursor.clear_screen)
|
182
|
-
@io.print(@cursor.move_to(0, 0))
|
183
|
-
|
184
|
-
# Print header
|
185
|
-
@io.puts "Job Output - ID: #{@selected_job_id}"
|
186
|
-
@io.puts "-" * @screen_width
|
187
|
-
@io.puts
|
188
|
-
|
189
|
-
# Get job output
|
190
|
-
output = get_job_output(@selected_job_id)
|
191
|
-
|
192
|
-
if output.empty?
|
193
|
-
@io.puts "No output available for this job."
|
194
|
-
@io.puts
|
195
|
-
@io.puts "This could mean:"
|
196
|
-
@io.puts "- The job hasn't started yet"
|
197
|
-
@io.puts "- The job is still running but hasn't produced output"
|
198
|
-
@io.puts "- The job completed without output"
|
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."
|
199
36
|
else
|
200
|
-
|
201
|
-
@io.puts "-" * 20
|
202
|
-
@io.puts output
|
37
|
+
render_harness_jobs(jobs)
|
203
38
|
end
|
204
|
-
|
205
|
-
@io.puts
|
206
|
-
@io.puts "Commands: (b)ack, (r)efresh, (q)uit"
|
207
39
|
end
|
208
40
|
|
209
|
-
|
210
|
-
if @io.ready?
|
211
|
-
char = @io.getch
|
212
|
-
return if char.nil? || char.empty?
|
213
|
-
|
214
|
-
case char.downcase
|
215
|
-
when "q"
|
216
|
-
@running = false
|
217
|
-
when "d"
|
218
|
-
handle_details_command
|
219
|
-
when "o"
|
220
|
-
handle_output_command
|
221
|
-
when "r"
|
222
|
-
handle_retry_command
|
223
|
-
when "k"
|
224
|
-
handle_kill_command
|
225
|
-
when "b"
|
226
|
-
switch_to_list
|
227
|
-
end
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
def handle_details_command
|
232
|
-
return unless @view_mode == :list
|
233
|
-
|
234
|
-
@io.print "Enter job ID: "
|
235
|
-
job_id = @io.gets.chomp
|
236
|
-
if job_exists?(job_id)
|
237
|
-
@selected_job_id = job_id
|
238
|
-
@view_mode = :details
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
|
-
def handle_output_command
|
243
|
-
job_id = (@view_mode == :details) ? @selected_job_id : nil
|
244
|
-
|
245
|
-
unless job_id
|
246
|
-
@io.print "Enter job ID: "
|
247
|
-
job_id = @io.gets.chomp
|
248
|
-
end
|
249
|
-
|
250
|
-
if job_exists?(job_id)
|
251
|
-
@selected_job_id = job_id
|
252
|
-
@view_mode = :output
|
253
|
-
end
|
254
|
-
end
|
255
|
-
|
256
|
-
def handle_retry_command
|
257
|
-
job_id = (@view_mode == :details) ? @selected_job_id : nil
|
258
|
-
|
259
|
-
unless job_id
|
260
|
-
@io.print "Enter job ID: "
|
261
|
-
job_id = @io.gets.chomp
|
262
|
-
end
|
41
|
+
private
|
263
42
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
43
|
+
# Fetch harness jobs from file-based storage
|
44
|
+
def fetch_harness_jobs
|
45
|
+
jobs = []
|
46
|
+
|
47
|
+
# Get all harness log files
|
48
|
+
harness_logs_dir = File.join(Dir.pwd, ".aidp", "harness_logs")
|
49
|
+
return jobs unless Dir.exist?(harness_logs_dir)
|
50
|
+
|
51
|
+
Dir.glob(File.join(harness_logs_dir, "*.json")).each do |log_file|
|
52
|
+
log_data = JSON.parse(File.read(log_file))
|
53
|
+
job_id = File.basename(log_file, ".json")
|
54
|
+
|
55
|
+
# Get job metadata from the log
|
56
|
+
job_info = {
|
57
|
+
id: job_id,
|
58
|
+
status: determine_job_status(log_data),
|
59
|
+
created_at: log_data["created_at"],
|
60
|
+
message: log_data["message"],
|
61
|
+
level: log_data["level"],
|
62
|
+
metadata: log_data["metadata"] || {}
|
63
|
+
}
|
64
|
+
|
65
|
+
jobs << job_info
|
66
|
+
rescue JSON::ParserError => e
|
67
|
+
@io.puts "Warning: Could not parse harness log #{log_file}: #{e.message}" if ENV["AIDP_DEBUG"]
|
68
|
+
end
|
69
|
+
|
70
|
+
# Sort by creation time (newest first)
|
71
|
+
jobs.sort_by { |job| job[:created_at] }.reverse
|
72
|
+
end
|
73
|
+
|
74
|
+
# Determine job status from log data
|
75
|
+
def determine_job_status(log_data)
|
76
|
+
case log_data["level"]
|
77
|
+
when "error"
|
78
|
+
"failed"
|
79
|
+
when "info"
|
80
|
+
if log_data["message"].include?("completed")
|
81
|
+
"completed"
|
82
|
+
elsif log_data["message"].include?("retrying")
|
83
|
+
"retrying"
|
279
84
|
else
|
280
|
-
|
85
|
+
"running"
|
281
86
|
end
|
282
|
-
|
87
|
+
else
|
88
|
+
"unknown"
|
283
89
|
end
|
284
90
|
end
|
285
91
|
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
job_id = @io.gets.chomp
|
292
|
-
end
|
293
|
-
|
294
|
-
if job_exists?(job_id)
|
295
|
-
job = fetch_job(job_id)
|
296
|
-
|
297
|
-
# Only allow killing running jobs
|
298
|
-
if job_status(job) == "running"
|
299
|
-
@io.print "Are you sure you want to kill job #{job_id}? (y/N): "
|
300
|
-
confirmation = @io.gets.chomp.downcase
|
92
|
+
# Render harness jobs in a simple table
|
93
|
+
def render_harness_jobs(jobs)
|
94
|
+
@io.puts "Harness Jobs"
|
95
|
+
@io.puts "-" * @screen_width
|
96
|
+
@io.puts
|
301
97
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
else
|
311
|
-
@io.puts "Only running jobs can be killed. Job #{job_id} is #{job_status(job)}"
|
312
|
-
sleep 2
|
98
|
+
# Create job content for TTY::Box
|
99
|
+
job_content = []
|
100
|
+
jobs.each do |job|
|
101
|
+
status_icon = case job[:status]
|
102
|
+
when "completed" then "✅"
|
103
|
+
when "running" then "🔄"
|
104
|
+
when "failed" then "❌"
|
105
|
+
when "pending" then "⏳"
|
106
|
+
else "❓"
|
313
107
|
end
|
314
|
-
end
|
315
|
-
end
|
316
|
-
|
317
|
-
def switch_to_list
|
318
|
-
@view_mode = :list
|
319
|
-
@selected_job_id = nil
|
320
|
-
end
|
321
|
-
|
322
|
-
def fetch_jobs
|
323
|
-
# For testing, return empty array if no database connection
|
324
|
-
return [] if ENV["RACK_ENV"] == "test" && !Que.connection
|
325
|
-
return [] if ENV["RACK_ENV"] == "test" && ENV["MOCK_DATABASE"] == "true"
|
326
|
-
|
327
|
-
Timeout.timeout(10) do
|
328
|
-
Que.execute(
|
329
|
-
<<~SQL
|
330
|
-
SELECT *
|
331
|
-
FROM que_jobs
|
332
|
-
ORDER BY
|
333
|
-
CASE
|
334
|
-
WHEN finished_at IS NULL AND error_count = 0 THEN 1 -- Running
|
335
|
-
WHEN error_count > 0 THEN 2 -- Failed
|
336
|
-
ELSE 3 -- Completed
|
337
|
-
END,
|
338
|
-
id DESC
|
339
|
-
SQL
|
340
|
-
)
|
341
|
-
end
|
342
|
-
rescue Timeout::Error
|
343
|
-
@io.puts "Database query timed out"
|
344
|
-
[]
|
345
|
-
rescue Sequel::DatabaseError => e
|
346
|
-
@io.puts "Error fetching jobs: #{e.message}"
|
347
|
-
[]
|
348
|
-
end
|
349
|
-
|
350
|
-
def fetch_job(job_id)
|
351
|
-
Timeout.timeout(5) do
|
352
|
-
Que.execute("SELECT * FROM que_jobs WHERE id = $1", [job_id]).first
|
353
|
-
end
|
354
|
-
rescue Timeout::Error
|
355
|
-
@io.puts "Database query timed out"
|
356
|
-
nil
|
357
|
-
rescue Sequel::DatabaseError => e
|
358
|
-
@io.puts "Error fetching job #{job_id}: #{e.message}"
|
359
|
-
nil
|
360
|
-
end
|
361
|
-
|
362
|
-
def job_exists?(job_id)
|
363
|
-
fetch_job(job_id) != nil
|
364
|
-
end
|
365
|
-
|
366
|
-
def sleep_for_refresh
|
367
|
-
sleep 1
|
368
|
-
end
|
369
108
|
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
(job[:
|
375
|
-
|
376
|
-
"running"
|
377
|
-
end
|
378
|
-
end
|
379
|
-
|
380
|
-
def format_runtime(job)
|
381
|
-
return "unknown" unless job
|
382
|
-
|
383
|
-
if job[:finished_at] && job[:run_at]
|
384
|
-
finished_at = job[:finished_at].is_a?(Time) ? job[:finished_at] : Time.parse(job[:finished_at])
|
385
|
-
run_at = job[:run_at].is_a?(Time) ? job[:run_at] : Time.parse(job[:run_at])
|
386
|
-
duration = finished_at - run_at
|
387
|
-
minutes = (duration / 60).to_i
|
388
|
-
seconds = (duration % 60).to_i
|
389
|
-
(minutes > 0) ? "#{minutes}m #{seconds}s" : "#{seconds}s"
|
390
|
-
elsif job[:run_at]
|
391
|
-
run_at = job[:run_at].is_a?(Time) ? job[:run_at] : Time.parse(job[:run_at])
|
392
|
-
duration = Time.now - run_at
|
393
|
-
minutes = (duration / 60).to_i
|
394
|
-
seconds = (duration % 60).to_i
|
395
|
-
(minutes > 0) ? "#{minutes}m #{seconds}s" : "#{seconds}s"
|
396
|
-
else
|
397
|
-
"pending"
|
109
|
+
job_info = []
|
110
|
+
job_info << "#{status_icon} #{job[:id][0..7]}"
|
111
|
+
job_info << "Status: #{@pastel.bold(job[:status])}"
|
112
|
+
job_info << "Created: #{format_time(job[:created_at])}"
|
113
|
+
job_info << "Message: #{truncate_message(job[:message])}"
|
114
|
+
job_content << job_info.join("\n")
|
398
115
|
end
|
399
|
-
rescue => e
|
400
|
-
"error (#{e.message})"
|
401
|
-
end
|
402
116
|
|
403
|
-
|
404
|
-
|
117
|
+
# Create main box with all jobs
|
118
|
+
box = TTY::Box.frame(
|
119
|
+
job_content.join("\n\n"),
|
120
|
+
title: {top_left: "Background Jobs"},
|
121
|
+
border: :thick,
|
122
|
+
padding: [1, 2]
|
123
|
+
)
|
124
|
+
puts box
|
405
125
|
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
error
|
411
|
-
end
|
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/"
|
412
130
|
end
|
413
131
|
|
414
|
-
|
415
|
-
|
416
|
-
|
132
|
+
# Format timestamp for display
|
133
|
+
def format_time(timestamp)
|
134
|
+
return "unknown" unless timestamp
|
417
135
|
|
418
|
-
# 1. Check if there's a result stored in analysis_results table
|
419
136
|
begin
|
420
|
-
|
421
|
-
|
422
|
-
["job_#{job_id}"]
|
423
|
-
).first
|
424
|
-
|
425
|
-
if result && result["data"]
|
426
|
-
data = JSON.parse(result["data"])
|
427
|
-
output << "Result: #{data["output"]}" if data["output"]
|
428
|
-
end
|
137
|
+
time = Time.parse(timestamp)
|
138
|
+
time.strftime("%Y-%m-%d %H:%M:%S")
|
429
139
|
rescue
|
430
|
-
|
140
|
+
timestamp
|
431
141
|
end
|
432
|
-
|
433
|
-
# 2. Check for any recent log entries
|
434
|
-
begin
|
435
|
-
logs = Que.execute(
|
436
|
-
"SELECT message FROM que_jobs WHERE id = $1 AND last_error_message IS NOT NULL",
|
437
|
-
[job_id]
|
438
|
-
).first
|
439
|
-
|
440
|
-
if logs && logs["last_error_message"]
|
441
|
-
output << "Error: #{logs["last_error_message"]}"
|
442
|
-
end
|
443
|
-
rescue
|
444
|
-
# Ignore errors
|
445
|
-
end
|
446
|
-
|
447
|
-
# 3. Check if job appears to be hung
|
448
|
-
job = fetch_job(job_id)
|
449
|
-
if job && job_status(job) == "running"
|
450
|
-
run_at = job[:run_at].is_a?(Time) ? job[:run_at] : Time.parse(job[:run_at])
|
451
|
-
duration = Time.now - run_at
|
452
|
-
|
453
|
-
if duration > 300 # 5 minutes
|
454
|
-
output << "⚠️ WARNING: Job has been running for #{format_duration(duration)}"
|
455
|
-
output << " This job may be hung or stuck."
|
456
|
-
end
|
457
|
-
end
|
458
|
-
|
459
|
-
output.join("\n")
|
460
|
-
end
|
461
|
-
|
462
|
-
def kill_job(job_id)
|
463
|
-
# Mark the job as finished with an error to stop it
|
464
|
-
Que.execute(
|
465
|
-
<<~SQL,
|
466
|
-
UPDATE que_jobs
|
467
|
-
SET finished_at = NOW(),
|
468
|
-
last_error_message = 'Job killed by user',
|
469
|
-
error_count = error_count + 1
|
470
|
-
WHERE id = $1
|
471
|
-
SQL
|
472
|
-
[job_id]
|
473
|
-
)
|
474
142
|
end
|
475
143
|
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
minutes %= 60
|
144
|
+
# Truncate message for table display
|
145
|
+
def truncate_message(message)
|
146
|
+
return "N/A" unless message
|
480
147
|
|
481
|
-
|
482
|
-
|
148
|
+
max_length = @screen_width - 50 # Account for other columns
|
149
|
+
if message.length > max_length
|
150
|
+
"#{message[0..max_length - 4]}..."
|
483
151
|
else
|
484
|
-
|
152
|
+
message
|
485
153
|
end
|
486
154
|
end
|
487
155
|
end
|