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.
- checksums.yaml +4 -4
- data/README.md +194 -25
- data/lib/aidp/analyze/error_handler.rb +4 -2
- data/lib/aidp/{analysis → analyze}/kb_inspector.rb +93 -89
- data/lib/aidp/analyze/prioritizer.rb +3 -2
- data/lib/aidp/analyze/progress.rb +2 -1
- data/lib/aidp/analyze/ruby_maat_integration.rb +7 -3
- data/lib/aidp/analyze/runner.rb +73 -11
- data/lib/aidp/{analysis → analyze}/seams.rb +1 -1
- data/lib/aidp/analyze/steps.rb +10 -8
- data/lib/aidp/{analysis → analyze}/tree_sitter_grammar_loader.rb +11 -5
- data/lib/aidp/{analysis → analyze}/tree_sitter_scan.rb +21 -15
- data/lib/aidp/cli/checkpoint_command.rb +98 -0
- data/lib/aidp/cli/first_run_wizard.rb +83 -103
- data/lib/aidp/cli/jobs_command.rb +270 -36
- data/lib/aidp/cli/terminal_io.rb +3 -3
- data/lib/aidp/cli.rb +411 -69
- data/lib/aidp/config.rb +5 -8
- data/lib/aidp/debug_logger.rb +4 -4
- data/lib/aidp/debug_mixin.rb +11 -4
- data/lib/aidp/execute/checkpoint.rb +282 -0
- data/lib/aidp/execute/checkpoint_display.rb +221 -0
- data/lib/aidp/execute/progress.rb +2 -1
- data/lib/aidp/execute/prompt_manager.rb +62 -0
- data/lib/aidp/execute/runner.rb +67 -20
- data/lib/aidp/execute/steps.rb +36 -27
- data/lib/aidp/execute/work_loop_runner.rb +308 -0
- data/lib/aidp/execute/workflow_selector.rb +50 -26
- data/lib/aidp/harness/condition_detector.rb +4 -4
- data/lib/aidp/harness/config_schema.rb +40 -0
- data/lib/aidp/harness/config_validator.rb +3 -6
- data/lib/aidp/harness/configuration.rb +35 -1
- data/lib/aidp/harness/enhanced_runner.rb +25 -4
- data/lib/aidp/harness/error_handler.rb +103 -28
- data/lib/aidp/harness/provider_factory.rb +6 -1
- data/lib/aidp/harness/provider_manager.rb +273 -19
- data/lib/aidp/harness/runner.rb +14 -6
- data/lib/aidp/harness/simple_user_interface.rb +6 -4
- data/lib/aidp/harness/status_display.rb +118 -106
- data/lib/aidp/harness/test_runner.rb +83 -0
- data/lib/aidp/harness/ui/enhanced_tui.rb +7 -5
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +22 -4
- data/lib/aidp/harness/ui/error_handler.rb +7 -2
- data/lib/aidp/harness/ui/frame_manager.rb +61 -39
- data/lib/aidp/harness/ui/job_monitor.rb +2 -0
- data/lib/aidp/harness/ui/navigation/main_menu.rb +27 -16
- data/lib/aidp/harness/ui/navigation/menu_item.rb +1 -0
- data/lib/aidp/harness/ui/navigation/menu_state.rb +1 -0
- data/lib/aidp/harness/ui/navigation/submenu.rb +1 -0
- data/lib/aidp/harness/ui/navigation/workflow_selector.rb +2 -0
- data/lib/aidp/harness/ui/progress_display.rb +26 -7
- data/lib/aidp/harness/ui/question_collector.rb +2 -0
- data/lib/aidp/harness/ui/spinner_group.rb +2 -0
- data/lib/aidp/harness/ui/spinner_helper.rb +1 -1
- data/lib/aidp/harness/ui/status_manager.rb +4 -2
- data/lib/aidp/harness/ui/status_widget.rb +20 -9
- data/lib/aidp/harness/ui/workflow_controller.rb +27 -9
- data/lib/aidp/harness/user_interface.rb +338 -330
- data/lib/aidp/jobs/background_runner.rb +278 -0
- data/lib/aidp/message_display.rb +48 -0
- data/lib/aidp/provider_manager.rb +13 -7
- data/lib/aidp/providers/anthropic.rb +101 -18
- data/lib/aidp/providers/base.rb +51 -1
- data/lib/aidp/providers/codex.rb +248 -0
- data/lib/aidp/providers/cursor.rb +39 -48
- data/lib/aidp/providers/gemini.rb +26 -16
- data/lib/aidp/providers/github_copilot.rb +263 -0
- data/lib/aidp/providers/opencode.rb +38 -47
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/workflows/definitions.rb +357 -0
- data/lib/aidp/workflows/selector.rb +171 -0
- data/lib/aidp.rb +16 -4
- data/templates/planning/generate_llm_style_guide.md +119 -0
- metadata +43 -31
- data/lib/aidp/analyze/progress_visualizer.rb +0 -314
- /data/templates/{ANALYZE/02_ARCHITECTURE_ANALYSIS.md → analysis/analyze_architecture.md} +0 -0
- /data/templates/{ANALYZE/05_DOCUMENTATION_ANALYSIS.md → analysis/analyze_documentation.md} +0 -0
- /data/templates/{ANALYZE/04_FUNCTIONALITY_ANALYSIS.md → analysis/analyze_functionality.md} +0 -0
- /data/templates/{ANALYZE/01_REPOSITORY_ANALYSIS.md → analysis/analyze_repository.md} +0 -0
- /data/templates/{ANALYZE/06_STATIC_ANALYSIS.md → analysis/analyze_static_code.md} +0 -0
- /data/templates/{ANALYZE/03_TEST_ANALYSIS.md → analysis/analyze_tests.md} +0 -0
- /data/templates/{ANALYZE/07_REFACTORING_RECOMMENDATIONS.md → analysis/recommend_refactoring.md} +0 -0
- /data/templates/{ANALYZE/06a_tree_sitter_scan.md → analysis/scan_with_tree_sitter.md} +0 -0
- /data/templates/{EXECUTE/11_STATIC_ANALYSIS.md → implementation/configure_static_analysis.md} +0 -0
- /data/templates/{EXECUTE/14_DOCS_PORTAL.md → implementation/create_documentation_portal.md} +0 -0
- /data/templates/{EXECUTE/10_IMPLEMENTATION_AGENT.md → implementation/implement_features.md} +0 -0
- /data/templates/{EXECUTE/13_DELIVERY_ROLLOUT.md → implementation/plan_delivery.md} +0 -0
- /data/templates/{EXECUTE/15_POST_RELEASE.md → implementation/review_post_release.md} +0 -0
- /data/templates/{EXECUTE/09_SCAFFOLDING_DEVEX.md → implementation/setup_scaffolding.md} +0 -0
- /data/templates/{EXECUTE/02A_ARCH_GATE_QUESTIONS.md → planning/ask_architecture_questions.md} +0 -0
- /data/templates/{EXECUTE/00_PRD.md → planning/create_prd.md} +0 -0
- /data/templates/{EXECUTE/08_TASKS.md → planning/create_tasks.md} +0 -0
- /data/templates/{EXECUTE/04_DOMAIN_DECOMPOSITION.md → planning/decompose_domain.md} +0 -0
- /data/templates/{EXECUTE/01_NFRS.md → planning/define_nfrs.md} +0 -0
- /data/templates/{EXECUTE/05_CONTRACTS.md → planning/design_apis.md} +0 -0
- /data/templates/{EXECUTE/02_ARCHITECTURE.md → planning/design_architecture.md} +0 -0
- /data/templates/{EXECUTE/06_THREAT_MODEL.md → planning/design_data_model.md} +0 -0
- /data/templates/{EXECUTE/03_ADR_FACTORY.md → planning/generate_adrs.md} +0 -0
- /data/templates/{EXECUTE/12_OBSERVABILITY_SLOS.md → planning/plan_observability.md} +0 -0
- /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
|
-
|
|
14
|
-
|
|
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
|
|
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
|
-
@
|
|
26
|
+
@background_runner = Aidp::Jobs::BackgroundRunner.new(Dir.pwd)
|
|
27
|
+
@screen_width = 80 # Default screen width
|
|
22
28
|
end
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
data/lib/aidp/cli/terminal_io.rb
CHANGED
|
@@ -5,9 +5,9 @@ require "stringio"
|
|
|
5
5
|
module Aidp
|
|
6
6
|
class CLI
|
|
7
7
|
class TerminalIO
|
|
8
|
-
def initialize(input
|
|
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?
|