aidp 0.7.0 → 0.8.1
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 +60 -214
- data/bin/aidp +1 -1
- data/lib/aidp/analysis/kb_inspector.rb +38 -23
- data/lib/aidp/analysis/seams.rb +2 -31
- data/lib/aidp/analysis/tree_sitter_grammar_loader.rb +1 -13
- data/lib/aidp/analysis/tree_sitter_scan.rb +3 -20
- data/lib/aidp/analyze/error_handler.rb +2 -75
- 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/cli/jobs_command.rb +100 -432
- data/lib/aidp/cli.rb +309 -239
- 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 -103
- data/lib/aidp/providers/base.rb +170 -0
- data/lib/aidp/providers/cursor.rb +52 -181
- data/lib/aidp/providers/gemini.rb +24 -107
- 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 +54 -39
- 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 +93 -69
- data/lib/aidp/analyze/agent_personas.rb +0 -71
- data/lib/aidp/analyze/agent_tool_executor.rb +0 -439
- data/lib/aidp/analyze/data_retention_manager.rb +0 -421
- data/lib/aidp/analyze/database.rb +0 -260
- data/lib/aidp/analyze/dependencies.rb +0 -335
- data/lib/aidp/analyze/export_manager.rb +0 -418
- data/lib/aidp/analyze/focus_guidance.rb +0 -517
- data/lib/aidp/analyze/incremental_analyzer.rb +0 -533
- data/lib/aidp/analyze/language_analysis_strategies.rb +0 -897
- data/lib/aidp/analyze/large_analysis_progress.rb +0 -499
- data/lib/aidp/analyze/memory_manager.rb +0 -339
- data/lib/aidp/analyze/metrics_storage.rb +0 -336
- data/lib/aidp/analyze/parallel_processor.rb +0 -454
- data/lib/aidp/analyze/performance_optimizer.rb +0 -691
- data/lib/aidp/analyze/repository_chunker.rb +0 -697
- data/lib/aidp/analyze/static_analysis_detector.rb +0 -577
- data/lib/aidp/analyze/storage.rb +0 -655
- data/lib/aidp/analyze/tool_configuration.rb +0 -441
- 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/job_manager.rb +0 -41
- data/lib/aidp/jobs/base_job.rb +0 -45
- data/lib/aidp/jobs/provider_execution_job.rb +0 -83
- 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
|
-
end
|
91
|
-
|
92
|
-
def render_job_list
|
93
|
-
jobs = fetch_jobs
|
25
|
+
# Simple harness jobs display
|
26
|
+
jobs = fetch_harness_jobs
|
94
27
|
|
95
28
|
if jobs.empty?
|
96
|
-
|
97
|
-
@io.puts "Background Jobs"
|
29
|
+
@io.puts "Harness Jobs"
|
98
30
|
@io.puts "-" * @screen_width
|
99
31
|
@io.puts
|
100
|
-
@io.puts "No jobs
|
101
|
-
return :exit
|
102
|
-
else
|
103
|
-
# Clear screen and hide cursor only when we have jobs to display
|
104
|
-
@io.print(@cursor.hide)
|
105
|
-
@io.print(@cursor.clear_screen)
|
106
|
-
@io.print(@cursor.move_to(0, 0))
|
107
|
-
@jobs_displayed = true # Mark that we've displayed jobs
|
108
|
-
|
109
|
-
# Print header
|
110
|
-
@io.puts "Background Jobs"
|
111
|
-
@io.puts "-" * @screen_width
|
32
|
+
@io.puts "No harness jobs found"
|
112
33
|
@io.puts
|
113
|
-
|
114
|
-
|
115
|
-
table = TTY::Table.new(
|
116
|
-
header: ["ID", "Job", "Queue", "Status", "Runtime", "Error"],
|
117
|
-
rows: jobs.map do |job|
|
118
|
-
[
|
119
|
-
job[:id].to_s,
|
120
|
-
job[:job_class]&.split("::")&.last || "Unknown",
|
121
|
-
job[:queue] || "default",
|
122
|
-
job_status(job),
|
123
|
-
format_runtime(job),
|
124
|
-
truncate_error(job[:last_error_message])
|
125
|
-
]
|
126
|
-
end
|
127
|
-
)
|
128
|
-
|
129
|
-
# Render table
|
130
|
-
@io.puts table.render(:unicode, padding: [0, 1], width: @screen_width)
|
131
|
-
end
|
132
|
-
|
133
|
-
@io.puts
|
134
|
-
@io.puts "Commands: (d)etails, (o)utput, (r)etry, (k)ill, (q)uit"
|
135
|
-
:continue
|
136
|
-
end
|
137
|
-
|
138
|
-
def render_job_details
|
139
|
-
return switch_to_list unless @selected_job_id
|
140
|
-
|
141
|
-
job = fetch_job(@selected_job_id)
|
142
|
-
return switch_to_list unless job
|
143
|
-
|
144
|
-
# Clear screen and hide cursor
|
145
|
-
@io.print(@cursor.hide)
|
146
|
-
@io.print(@cursor.clear_screen)
|
147
|
-
@io.print(@cursor.move_to(0, 0))
|
148
|
-
|
149
|
-
# Print header
|
150
|
-
@io.puts "Job Details - ID: #{@selected_job_id}"
|
151
|
-
@io.puts "-" * @screen_width
|
152
|
-
@io.puts
|
153
|
-
|
154
|
-
# Print job details
|
155
|
-
@io.puts "Class: #{job[:job_class]}"
|
156
|
-
@io.puts "Queue: #{job[:queue]}"
|
157
|
-
@io.puts "Status: #{job_status(job)}"
|
158
|
-
@io.puts "Runtime: #{format_runtime(job)}"
|
159
|
-
@io.puts "Started: #{job[:run_at]}"
|
160
|
-
@io.puts "Finished: #{job[:finished_at]}"
|
161
|
-
@io.puts "Attempts: #{job[:error_count]}"
|
162
|
-
@io.puts
|
163
|
-
@io.puts "Error:" if job[:last_error_message]
|
164
|
-
@io.puts job[:last_error_message] if job[:last_error_message]
|
165
|
-
|
166
|
-
@io.puts
|
167
|
-
@io.puts "Commands: (b)ack, (o)utput, (r)etry, (k)ill, (q)uit"
|
168
|
-
end
|
169
|
-
|
170
|
-
def render_job_output
|
171
|
-
return switch_to_list unless @selected_job_id
|
172
|
-
|
173
|
-
job = fetch_job(@selected_job_id)
|
174
|
-
return switch_to_list unless job
|
175
|
-
|
176
|
-
# Clear screen and hide cursor
|
177
|
-
@io.print(@cursor.hide)
|
178
|
-
@io.print(@cursor.clear_screen)
|
179
|
-
@io.print(@cursor.move_to(0, 0))
|
180
|
-
|
181
|
-
# Print header
|
182
|
-
@io.puts "Job Output - ID: #{@selected_job_id}"
|
183
|
-
@io.puts "-" * @screen_width
|
184
|
-
@io.puts
|
185
|
-
|
186
|
-
# Get job output
|
187
|
-
output = get_job_output(@selected_job_id)
|
188
|
-
|
189
|
-
if output.empty?
|
190
|
-
@io.puts "No output available for this job."
|
191
|
-
@io.puts
|
192
|
-
@io.puts "This could mean:"
|
193
|
-
@io.puts "- The job hasn't started yet"
|
194
|
-
@io.puts "- The job is still running but hasn't produced output"
|
195
|
-
@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."
|
196
36
|
else
|
197
|
-
|
198
|
-
@io.puts "-" * 20
|
199
|
-
@io.puts output
|
200
|
-
end
|
201
|
-
|
202
|
-
@io.puts
|
203
|
-
@io.puts "Commands: (b)ack, (r)efresh, (q)uit"
|
204
|
-
end
|
205
|
-
|
206
|
-
def handle_input
|
207
|
-
if @io.ready?
|
208
|
-
char = @io.getch
|
209
|
-
return if char.nil? || char.empty?
|
210
|
-
|
211
|
-
case char.downcase
|
212
|
-
when "q"
|
213
|
-
@running = false
|
214
|
-
when "d"
|
215
|
-
handle_details_command
|
216
|
-
when "o"
|
217
|
-
handle_output_command
|
218
|
-
when "r"
|
219
|
-
handle_retry_command
|
220
|
-
when "k"
|
221
|
-
handle_kill_command
|
222
|
-
when "b"
|
223
|
-
switch_to_list
|
224
|
-
end
|
37
|
+
render_harness_jobs(jobs)
|
225
38
|
end
|
226
39
|
end
|
227
40
|
|
228
|
-
|
229
|
-
return unless @view_mode == :list
|
230
|
-
|
231
|
-
@io.print "Enter job ID: "
|
232
|
-
job_id = @io.gets.chomp
|
233
|
-
if job_exists?(job_id)
|
234
|
-
@selected_job_id = job_id
|
235
|
-
@view_mode = :details
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|
239
|
-
def handle_output_command
|
240
|
-
job_id = (@view_mode == :details) ? @selected_job_id : nil
|
41
|
+
private
|
241
42
|
|
242
|
-
|
243
|
-
|
244
|
-
|
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"]
|
245
68
|
end
|
246
69
|
|
247
|
-
|
248
|
-
|
249
|
-
@view_mode = :output
|
250
|
-
end
|
70
|
+
# Sort by creation time (newest first)
|
71
|
+
jobs.sort_by { |job| job[:created_at] }.reverse
|
251
72
|
end
|
252
73
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
if job[:error_count].to_i > 0
|
264
|
-
Que.execute(
|
265
|
-
<<~SQL,
|
266
|
-
UPDATE que_jobs
|
267
|
-
SET error_count = 0,
|
268
|
-
last_error_message = NULL,
|
269
|
-
finished_at = NULL,
|
270
|
-
expired_at = NULL
|
271
|
-
WHERE id = $1
|
272
|
-
SQL
|
273
|
-
[job_id]
|
274
|
-
)
|
275
|
-
@io.puts "Job #{job_id} has been queued for retry"
|
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"
|
276
84
|
else
|
277
|
-
|
85
|
+
"running"
|
278
86
|
end
|
279
|
-
|
87
|
+
else
|
88
|
+
"unknown"
|
280
89
|
end
|
281
90
|
end
|
282
91
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
job_id = @io.gets.chomp
|
289
|
-
end
|
290
|
-
|
291
|
-
if job_exists?(job_id)
|
292
|
-
job = fetch_job(job_id)
|
293
|
-
|
294
|
-
# Only allow killing running jobs
|
295
|
-
if job_status(job) == "running"
|
296
|
-
@io.print "Are you sure you want to kill job #{job_id}? (y/N): "
|
297
|
-
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
|
298
97
|
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
else
|
308
|
-
@io.puts "Only running jobs can be killed. Job #{job_id} is #{job_status(job)}"
|
309
|
-
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 "❓"
|
310
107
|
end
|
311
|
-
end
|
312
|
-
end
|
313
108
|
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
# For testing, return empty array if no database connection
|
321
|
-
return [] if ENV["RACK_ENV"] == "test" && !Que.connection
|
322
|
-
return [] if ENV["RACK_ENV"] == "test" && ENV["MOCK_DATABASE"] == "true"
|
323
|
-
|
324
|
-
Timeout.timeout(10) do
|
325
|
-
Que.execute(
|
326
|
-
<<~SQL
|
327
|
-
SELECT *
|
328
|
-
FROM que_jobs
|
329
|
-
ORDER BY
|
330
|
-
CASE
|
331
|
-
WHEN finished_at IS NULL AND error_count = 0 THEN 1 -- Running
|
332
|
-
WHEN error_count > 0 THEN 2 -- Failed
|
333
|
-
ELSE 3 -- Completed
|
334
|
-
END,
|
335
|
-
id DESC
|
336
|
-
SQL
|
337
|
-
)
|
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")
|
338
115
|
end
|
339
|
-
rescue Timeout::Error
|
340
|
-
@io.puts "Database query timed out"
|
341
|
-
[]
|
342
|
-
rescue Sequel::DatabaseError => e
|
343
|
-
@io.puts "Error fetching jobs: #{e.message}"
|
344
|
-
[]
|
345
|
-
end
|
346
|
-
|
347
|
-
def fetch_job(job_id)
|
348
|
-
Timeout.timeout(5) do
|
349
|
-
Que.execute("SELECT * FROM que_jobs WHERE id = $1", [job_id]).first
|
350
|
-
end
|
351
|
-
rescue Timeout::Error
|
352
|
-
@io.puts "Database query timed out"
|
353
|
-
nil
|
354
|
-
rescue Sequel::DatabaseError => e
|
355
|
-
@io.puts "Error fetching job #{job_id}: #{e.message}"
|
356
|
-
nil
|
357
|
-
end
|
358
116
|
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
def job_status(job)
|
368
|
-
return "unknown" unless job
|
369
|
-
|
370
|
-
if job[:finished_at]
|
371
|
-
(job[:error_count].to_i > 0) ? "failed" : "completed"
|
372
|
-
else
|
373
|
-
"running"
|
374
|
-
end
|
375
|
-
end
|
376
|
-
|
377
|
-
def format_runtime(job)
|
378
|
-
return "unknown" unless job
|
379
|
-
|
380
|
-
if job[:finished_at] && job[:run_at]
|
381
|
-
finished_at = job[:finished_at].is_a?(Time) ? job[:finished_at] : Time.parse(job[:finished_at])
|
382
|
-
run_at = job[:run_at].is_a?(Time) ? job[:run_at] : Time.parse(job[:run_at])
|
383
|
-
duration = finished_at - run_at
|
384
|
-
minutes = (duration / 60).to_i
|
385
|
-
seconds = (duration % 60).to_i
|
386
|
-
(minutes > 0) ? "#{minutes}m #{seconds}s" : "#{seconds}s"
|
387
|
-
elsif job[:run_at]
|
388
|
-
run_at = job[:run_at].is_a?(Time) ? job[:run_at] : Time.parse(job[:run_at])
|
389
|
-
duration = Time.now - run_at
|
390
|
-
minutes = (duration / 60).to_i
|
391
|
-
seconds = (duration % 60).to_i
|
392
|
-
(minutes > 0) ? "#{minutes}m #{seconds}s" : "#{seconds}s"
|
393
|
-
else
|
394
|
-
"pending"
|
395
|
-
end
|
396
|
-
end
|
397
|
-
|
398
|
-
def truncate_error(error)
|
399
|
-
return nil unless error
|
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
|
400
125
|
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
error
|
406
|
-
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/"
|
407
130
|
end
|
408
131
|
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
# 1. Check if there's a result stored in analysis_results table
|
414
|
-
begin
|
415
|
-
result = Que.execute(
|
416
|
-
"SELECT data FROM analysis_results WHERE step_name = $1",
|
417
|
-
["job_#{job_id}"]
|
418
|
-
).first
|
419
|
-
|
420
|
-
if result && result["data"]
|
421
|
-
data = JSON.parse(result["data"])
|
422
|
-
output << "Result: #{data["output"]}" if data["output"]
|
423
|
-
end
|
424
|
-
rescue Sequel::DatabaseError, PG::Error => e
|
425
|
-
# Database error - table might not exist
|
426
|
-
@io.puts "Warning: Could not fetch job result: #{e.message}" if ENV["AIDP_DEBUG"]
|
427
|
-
rescue JSON::ParserError => e
|
428
|
-
# JSON parse error
|
429
|
-
@io.puts "Warning: Could not parse job result data: #{e.message}" if ENV["AIDP_DEBUG"]
|
430
|
-
end
|
132
|
+
# Format timestamp for display
|
133
|
+
def format_time(timestamp)
|
134
|
+
return "unknown" unless timestamp
|
431
135
|
|
432
|
-
# 2. Check for any recent log entries
|
433
136
|
begin
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
if logs && logs["last_error_message"]
|
440
|
-
output << "Error: #{logs["last_error_message"]}"
|
441
|
-
end
|
442
|
-
rescue Sequel::DatabaseError, PG::Error => e
|
443
|
-
# Database error fetching logs - continue with diagnostic
|
444
|
-
@io.puts "Warning: Could not fetch job logs: #{e.message}" if ENV["AIDP_DEBUG"]
|
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
|
137
|
+
time = Time.parse(timestamp)
|
138
|
+
time.strftime("%Y-%m-%d %H:%M:%S")
|
139
|
+
rescue
|
140
|
+
timestamp
|
457
141
|
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
|