aidp 0.15.2 → 0.16.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 +47 -0
- data/lib/aidp/analyze/error_handler.rb +14 -15
- data/lib/aidp/analyze/runner.rb +27 -5
- data/lib/aidp/analyze/steps.rb +4 -0
- data/lib/aidp/cli/jobs_command.rb +2 -1
- data/lib/aidp/cli.rb +812 -3
- data/lib/aidp/concurrency/backoff.rb +148 -0
- data/lib/aidp/concurrency/exec.rb +192 -0
- data/lib/aidp/concurrency/wait.rb +148 -0
- data/lib/aidp/concurrency.rb +71 -0
- data/lib/aidp/config.rb +20 -0
- data/lib/aidp/daemon/runner.rb +9 -8
- data/lib/aidp/debug_mixin.rb +1 -0
- data/lib/aidp/errors.rb +12 -0
- data/lib/aidp/execute/interactive_repl.rb +102 -11
- data/lib/aidp/execute/repl_macros.rb +776 -2
- data/lib/aidp/execute/runner.rb +27 -5
- data/lib/aidp/execute/steps.rb +2 -0
- data/lib/aidp/harness/config_loader.rb +24 -2
- data/lib/aidp/harness/enhanced_runner.rb +16 -2
- data/lib/aidp/harness/error_handler.rb +1 -1
- data/lib/aidp/harness/provider_info.rb +19 -15
- data/lib/aidp/harness/provider_manager.rb +47 -41
- data/lib/aidp/harness/runner.rb +3 -11
- data/lib/aidp/harness/state/persistence.rb +1 -6
- data/lib/aidp/harness/state_manager.rb +115 -7
- data/lib/aidp/harness/status_display.rb +11 -18
- data/lib/aidp/harness/ui/navigation/submenu.rb +1 -0
- data/lib/aidp/harness/ui/workflow_controller.rb +1 -1
- data/lib/aidp/harness/user_interface.rb +12 -15
- data/lib/aidp/jobs/background_runner.rb +15 -5
- data/lib/aidp/providers/codex.rb +0 -1
- data/lib/aidp/providers/cursor.rb +0 -1
- data/lib/aidp/providers/github_copilot.rb +0 -1
- data/lib/aidp/providers/opencode.rb +0 -1
- data/lib/aidp/skills/composer.rb +178 -0
- data/lib/aidp/skills/loader.rb +205 -0
- data/lib/aidp/skills/registry.rb +220 -0
- data/lib/aidp/skills/skill.rb +174 -0
- data/lib/aidp/skills.rb +30 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/build_processor.rb +93 -28
- data/lib/aidp/watch/runner.rb +3 -2
- data/lib/aidp/workstream_executor.rb +244 -0
- data/lib/aidp/workstream_state.rb +212 -0
- data/lib/aidp/worktree.rb +208 -0
- data/lib/aidp.rb +6 -0
- metadata +17 -4
data/lib/aidp/execute/runner.rb
CHANGED
@@ -5,6 +5,7 @@ require_relative "steps"
|
|
5
5
|
require_relative "progress"
|
6
6
|
require_relative "work_loop_runner"
|
7
7
|
require_relative "../storage/file_manager"
|
8
|
+
require_relative "../skills"
|
8
9
|
|
9
10
|
module Aidp
|
10
11
|
module Execute
|
@@ -17,6 +18,8 @@ module Aidp
|
|
17
18
|
@is_harness_mode = !harness_runner.nil?
|
18
19
|
@file_manager = Aidp::Storage::FileManager.new(File.join(project_dir, ".aidp"))
|
19
20
|
@prompt = prompt
|
21
|
+
@skills_registry = nil # Lazy-loaded
|
22
|
+
@skills_composer = Aidp::Skills::Composer.new
|
20
23
|
end
|
21
24
|
|
22
25
|
def progress
|
@@ -318,18 +321,37 @@ module Aidp
|
|
318
321
|
step_spec = Aidp::Execute::Steps::SPEC[step_name]
|
319
322
|
raise "Step '#{step_name}' not found" unless step_spec
|
320
323
|
|
324
|
+
# Load template
|
321
325
|
template_name = step_spec["templates"].first
|
322
326
|
template_path = find_template(template_name)
|
323
327
|
raise "Template not found for step #{step_name}" unless template_path
|
324
|
-
|
325
328
|
template = File.read(template_path)
|
326
329
|
|
327
|
-
#
|
328
|
-
|
329
|
-
|
330
|
+
# Load skill if specified
|
331
|
+
skill = nil
|
332
|
+
if step_spec["skill"]
|
333
|
+
skill = skills_registry.find(step_spec["skill"])
|
334
|
+
if skill.nil?
|
335
|
+
Aidp.log_warn(
|
336
|
+
"skills",
|
337
|
+
"Skill not found for step",
|
338
|
+
step: step_name,
|
339
|
+
skill_id: step_spec["skill"]
|
340
|
+
)
|
341
|
+
end
|
330
342
|
end
|
331
343
|
|
332
|
-
template
|
344
|
+
# Compose skill + template
|
345
|
+
@skills_composer.compose(skill: skill, template: template, options: options)
|
346
|
+
end
|
347
|
+
|
348
|
+
def skills_registry
|
349
|
+
@skills_registry ||= begin
|
350
|
+
provider = @is_harness_mode ? @harness_runner.current_provider : nil
|
351
|
+
registry = Aidp::Skills::Registry.new(project_dir: @project_dir, provider: provider)
|
352
|
+
registry.load_skills
|
353
|
+
registry
|
354
|
+
end
|
333
355
|
end
|
334
356
|
end
|
335
357
|
end
|
data/lib/aidp/execute/steps.rb
CHANGED
@@ -6,6 +6,7 @@ module Aidp
|
|
6
6
|
# Simplified step specifications with fewer gates
|
7
7
|
# Templates are now organized by purpose (planning/, analysis/, implementation/)
|
8
8
|
# and named with action verbs for clarity
|
9
|
+
# Skills define WHO the agent is, templates define WHAT task to do
|
9
10
|
SPEC = {
|
10
11
|
"00_LLM_STYLE_GUIDE" => {
|
11
12
|
"templates" => ["planning/generate_llm_style_guide.md"],
|
@@ -15,6 +16,7 @@ module Aidp
|
|
15
16
|
"interactive" => false
|
16
17
|
},
|
17
18
|
"00_PRD" => {
|
19
|
+
"skill" => "product_strategist",
|
18
20
|
"templates" => ["planning/create_prd.md"],
|
19
21
|
"description" => "Generate Product Requirements Document",
|
20
22
|
"outs" => ["docs/prd.md"],
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require "yaml"
|
4
4
|
require_relative "config_schema"
|
5
5
|
require_relative "config_validator"
|
6
|
+
require "digest"
|
6
7
|
|
7
8
|
module Aidp
|
8
9
|
module Harness
|
@@ -13,6 +14,7 @@ module Aidp
|
|
13
14
|
@validator = ConfigValidator.new(project_dir)
|
14
15
|
@config_cache = nil
|
15
16
|
@last_loaded = nil
|
17
|
+
@last_signature = nil # stores {mtime:, size:, hash:}
|
16
18
|
end
|
17
19
|
|
18
20
|
# Load and validate configuration with caching
|
@@ -25,6 +27,7 @@ module Aidp
|
|
25
27
|
if validation_result[:valid]
|
26
28
|
@config_cache = @validator.validated_config
|
27
29
|
@last_loaded = Time.now
|
30
|
+
@last_signature = current_file_signature
|
28
31
|
|
29
32
|
# Log warnings if any
|
30
33
|
unless validation_result[:warnings].empty?
|
@@ -284,13 +287,32 @@ module Aidp
|
|
284
287
|
private
|
285
288
|
|
286
289
|
def config_file_changed?
|
287
|
-
return true unless @
|
290
|
+
return true unless @last_signature && @validator.config_file_path && File.exist?(@validator.config_file_path)
|
288
291
|
|
289
|
-
|
292
|
+
sig = current_file_signature
|
293
|
+
return true unless sig
|
294
|
+
|
295
|
+
# Detect any difference (mtime OR size OR content hash)
|
296
|
+
sig[:mtime] != @last_signature[:mtime] ||
|
297
|
+
sig[:size] != @last_signature[:size] ||
|
298
|
+
sig[:hash] != @last_signature[:hash]
|
290
299
|
rescue
|
291
300
|
true
|
292
301
|
end
|
293
302
|
|
303
|
+
def current_file_signature
|
304
|
+
path = @validator.config_file_path
|
305
|
+
return nil unless path && File.exist?(path)
|
306
|
+
stat = File.stat(path)
|
307
|
+
{
|
308
|
+
mtime: stat.mtime,
|
309
|
+
size: stat.size,
|
310
|
+
hash: Digest::SHA256.file(path).hexdigest
|
311
|
+
}
|
312
|
+
rescue
|
313
|
+
nil
|
314
|
+
end
|
315
|
+
|
294
316
|
def handle_validation_errors(errors)
|
295
317
|
error_message = "Configuration validation failed:\n" + errors.join("\n")
|
296
318
|
|
@@ -190,7 +190,20 @@ module Aidp
|
|
190
190
|
result = show_step_spinner(spinner_message) do
|
191
191
|
@error_handler.execute_with_retry do
|
192
192
|
step_options = @options.merge(user_input: @user_input)
|
193
|
-
|
193
|
+
# Determine execution directory (workstream path if set)
|
194
|
+
exec_dir = begin
|
195
|
+
if @state_manager.respond_to?(:current_workstream_path)
|
196
|
+
@state_manager.current_workstream_path
|
197
|
+
else
|
198
|
+
@project_dir
|
199
|
+
end
|
200
|
+
rescue
|
201
|
+
@project_dir
|
202
|
+
end
|
203
|
+
# Execute step within the chosen directory for proper isolation
|
204
|
+
Dir.chdir(exec_dir) do
|
205
|
+
runner.run_step(step_name, step_options)
|
206
|
+
end
|
194
207
|
end
|
195
208
|
end
|
196
209
|
duration = Time.now - start_time
|
@@ -225,8 +238,9 @@ module Aidp
|
|
225
238
|
end
|
226
239
|
|
227
240
|
# Remove job after a delay to show completion
|
241
|
+
# UI delay to let user see completion status before removal
|
228
242
|
Thread.new do
|
229
|
-
sleep 2
|
243
|
+
sleep 2 # Acceptable for UI timing
|
230
244
|
@tui.remove_job(step_job_id)
|
231
245
|
end
|
232
246
|
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "net/http"
|
4
4
|
require_relative "../debug_mixin"
|
5
|
+
require_relative "../concurrency"
|
5
6
|
|
6
7
|
module Aidp
|
7
8
|
module Harness
|
@@ -191,7 +192,6 @@ module Aidp
|
|
191
192
|
|
192
193
|
# Wait for backoff delay
|
193
194
|
if delay > 0
|
194
|
-
# Use regular sleep for now (async not needed in this context)
|
195
195
|
sleep(delay)
|
196
196
|
end
|
197
197
|
|
@@ -4,6 +4,7 @@ require "json"
|
|
4
4
|
require "yaml"
|
5
5
|
require "fileutils"
|
6
6
|
require_relative "../rescue_logging"
|
7
|
+
require_relative "../concurrency"
|
7
8
|
|
8
9
|
module Aidp
|
9
10
|
module Harness
|
@@ -175,25 +176,28 @@ module Aidp
|
|
175
176
|
pid = Process.spawn(binary_name, *args, out: w, err: w)
|
176
177
|
w.close
|
177
178
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
break if pid_done
|
184
|
-
sleep 0.05
|
185
|
-
end
|
186
|
-
|
187
|
-
# Kill if timed out
|
188
|
-
unless status
|
179
|
+
begin
|
180
|
+
Aidp::Concurrency::Wait.for_process_exit(pid, timeout: 5, interval: 0.05)
|
181
|
+
# Process exited normally (status available for future diagnostics)
|
182
|
+
rescue Aidp::Concurrency::TimeoutError
|
183
|
+
# Timeout - kill process
|
189
184
|
begin
|
190
185
|
Process.kill("TERM", pid)
|
191
|
-
sleep 0.1
|
192
|
-
Process.kill("KILL", pid)
|
193
186
|
rescue => e
|
194
|
-
log_rescue(e, component: "provider_info", action: "
|
195
|
-
|
187
|
+
log_rescue(e, component: "provider_info", action: "kill_timeout_provider_command_term", fallback: nil, provider: @provider_name, binary: binary_name, pid: pid)
|
188
|
+
end
|
189
|
+
|
190
|
+
begin
|
191
|
+
Aidp::Concurrency::Wait.for_process_exit(pid, timeout: 0.1, interval: 0.02)
|
192
|
+
rescue Aidp::Concurrency::TimeoutError
|
193
|
+
# TERM didn't work, use KILL
|
194
|
+
begin
|
195
|
+
Process.kill("KILL", pid)
|
196
|
+
rescue => e
|
197
|
+
log_rescue(e, component: "provider_info", action: "kill_timeout_provider_command_kill", fallback: nil, provider: @provider_name, binary: binary_name, pid: pid)
|
198
|
+
end
|
196
199
|
end
|
200
|
+
|
197
201
|
return nil
|
198
202
|
end
|
199
203
|
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require "tty-prompt"
|
4
4
|
require_relative "provider_factory"
|
5
5
|
require_relative "../rescue_logging"
|
6
|
+
require_relative "../concurrency"
|
6
7
|
|
7
8
|
module Aidp
|
8
9
|
module Harness
|
@@ -145,26 +146,27 @@ module Aidp
|
|
145
146
|
|
146
147
|
# Switch provider with retry logic
|
147
148
|
def switch_provider_with_retry(reason = "retry", max_retries = @max_retries)
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
149
|
+
attempt_count = 0
|
150
|
+
|
151
|
+
Aidp::Concurrency::Backoff.retry(
|
152
|
+
max_attempts: max_retries,
|
153
|
+
base: 0.5,
|
154
|
+
strategy: :exponential,
|
155
|
+
jitter: 0.2,
|
156
|
+
on: [StandardError]
|
157
|
+
) do
|
158
|
+
attempt_count += 1
|
159
|
+
next_provider = switch_provider(reason, {retry_count: attempt_count - 1})
|
152
160
|
|
153
161
|
if next_provider
|
154
162
|
return next_provider
|
155
|
-
end
|
156
|
-
|
157
|
-
retry_count += 1
|
158
|
-
|
159
|
-
# Wait before retrying
|
160
|
-
delay = calculate_retry_delay(retry_count)
|
161
|
-
if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
162
|
-
sleep(delay)
|
163
163
|
else
|
164
|
-
|
164
|
+
# Raise to trigger retry
|
165
|
+
raise "Provider switch failed on attempt #{attempt_count}"
|
165
166
|
end
|
166
167
|
end
|
167
|
-
|
168
|
+
rescue Aidp::Concurrency::MaxAttemptsError
|
169
|
+
# All retries exhausted
|
168
170
|
nil
|
169
171
|
end
|
170
172
|
|
@@ -235,26 +237,27 @@ module Aidp
|
|
235
237
|
def switch_model_with_retry(reason = "retry", max_retries = @max_retries)
|
236
238
|
return nil unless @model_switching_enabled
|
237
239
|
|
238
|
-
|
240
|
+
attempt_count = 0
|
239
241
|
|
240
|
-
|
241
|
-
|
242
|
+
Aidp::Concurrency::Backoff.retry(
|
243
|
+
max_attempts: max_retries,
|
244
|
+
base: 0.5,
|
245
|
+
strategy: :exponential,
|
246
|
+
jitter: 0.2,
|
247
|
+
on: [StandardError]
|
248
|
+
) do
|
249
|
+
attempt_count += 1
|
250
|
+
next_model = switch_model(reason, {retry_count: attempt_count - 1})
|
242
251
|
|
243
252
|
if next_model
|
244
253
|
return next_model
|
245
|
-
end
|
246
|
-
|
247
|
-
retry_count += 1
|
248
|
-
|
249
|
-
# Wait before retrying
|
250
|
-
delay = calculate_retry_delay(retry_count)
|
251
|
-
if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
252
|
-
sleep(delay)
|
253
254
|
else
|
254
|
-
|
255
|
+
# Raise to trigger retry
|
256
|
+
raise "Model switch failed on attempt #{attempt_count}"
|
255
257
|
end
|
256
258
|
end
|
257
|
-
|
259
|
+
rescue Aidp::Concurrency::MaxAttemptsError
|
260
|
+
# All retries exhausted
|
258
261
|
nil
|
259
262
|
end
|
260
263
|
|
@@ -1112,28 +1115,31 @@ module Aidp
|
|
1112
1115
|
r, w = IO.pipe
|
1113
1116
|
pid = Process.spawn(binary, "--version", out: w, err: w)
|
1114
1117
|
w.close
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
1120
|
-
sleep 0.05
|
1121
|
-
end
|
1122
|
-
unless status
|
1123
|
-
# Timeout -> kill
|
1118
|
+
# Wait for process to exit with timeout
|
1119
|
+
begin
|
1120
|
+
Aidp::Concurrency::Wait.for_process_exit(pid, timeout: 3, interval: 0.05)
|
1121
|
+
rescue Aidp::Concurrency::TimeoutError
|
1122
|
+
# Timeout -> kill process
|
1124
1123
|
begin
|
1125
1124
|
Process.kill("TERM", pid)
|
1126
1125
|
rescue => e
|
1127
1126
|
log_rescue(e, component: "provider_manager", action: "kill_timeout_process_term", fallback: nil, binary: binary, pid: pid)
|
1128
1127
|
nil
|
1129
1128
|
end
|
1130
|
-
|
1129
|
+
|
1130
|
+
# Brief wait for TERM to take effect
|
1131
1131
|
begin
|
1132
|
-
|
1133
|
-
rescue
|
1134
|
-
|
1135
|
-
|
1132
|
+
Aidp::Concurrency::Wait.for_process_exit(pid, timeout: 0.1, interval: 0.02)
|
1133
|
+
rescue Aidp::Concurrency::TimeoutError
|
1134
|
+
# TERM didn't work, use KILL
|
1135
|
+
begin
|
1136
|
+
Process.kill("KILL", pid)
|
1137
|
+
rescue => e
|
1138
|
+
log_rescue(e, component: "provider_manager", action: "kill_timeout_process_kill", fallback: nil, binary: binary, pid: pid)
|
1139
|
+
nil
|
1140
|
+
end
|
1136
1141
|
end
|
1142
|
+
|
1137
1143
|
ok = false
|
1138
1144
|
reason = "binary_timeout"
|
1139
1145
|
end
|
data/lib/aidp/harness/runner.rb
CHANGED
@@ -10,6 +10,7 @@ require_relative "simple_user_interface"
|
|
10
10
|
require_relative "error_handler"
|
11
11
|
require_relative "status_display"
|
12
12
|
require_relative "completion_checker"
|
13
|
+
require_relative "../concurrency"
|
13
14
|
|
14
15
|
module Aidp
|
15
16
|
module Harness
|
@@ -296,11 +297,7 @@ module Aidp
|
|
296
297
|
while Time.now < reset_time && @state == STATES[:waiting_for_rate_limit]
|
297
298
|
remaining = reset_time - Time.now
|
298
299
|
@status_display.update_rate_limit_countdown(remaining)
|
299
|
-
|
300
|
-
sleep(1)
|
301
|
-
else
|
302
|
-
Async::Task.current.sleep(1)
|
303
|
-
end
|
300
|
+
sleep(1)
|
304
301
|
end
|
305
302
|
end
|
306
303
|
|
@@ -319,12 +316,7 @@ module Aidp
|
|
319
316
|
def handle_pause_condition
|
320
317
|
case @state
|
321
318
|
when STATES[:paused]
|
322
|
-
|
323
|
-
if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
324
|
-
sleep(1)
|
325
|
-
else
|
326
|
-
Async::Task.current.sleep(1)
|
327
|
-
end
|
319
|
+
sleep(1)
|
328
320
|
when STATES[:waiting_for_user]
|
329
321
|
# User interface handles this
|
330
322
|
nil
|
@@ -216,6 +216,21 @@ module Aidp
|
|
216
216
|
|
217
217
|
# Export state for debugging
|
218
218
|
def export_state
|
219
|
+
# In test mode, include test variables
|
220
|
+
if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
221
|
+
test_state = {
|
222
|
+
current_workstream: @test_workstream,
|
223
|
+
workstream_path: @test_workstream_path,
|
224
|
+
workstream_branch: @test_workstream_branch
|
225
|
+
}
|
226
|
+
return {
|
227
|
+
state_file: @state_file,
|
228
|
+
has_state: false,
|
229
|
+
metadata: {},
|
230
|
+
state: test_state
|
231
|
+
}
|
232
|
+
end
|
233
|
+
|
219
234
|
{
|
220
235
|
state_file: @state_file,
|
221
236
|
has_state: has_state?,
|
@@ -249,6 +264,15 @@ module Aidp
|
|
249
264
|
@progress_tracker.mark_step_completed(step_name)
|
250
265
|
# Also update harness state
|
251
266
|
update_state(current_step: nil, last_step_completed: step_name)
|
267
|
+
# Increment iteration counter for current workstream if present
|
268
|
+
ws_slug = current_workstream
|
269
|
+
if ws_slug
|
270
|
+
# File layout: this file is in lib/aidp/harness/state_manager.rb
|
271
|
+
# workstream_state.rb lives at lib/aidp/workstream_state.rb
|
272
|
+
# Correct relative path from here is ../workstream_state
|
273
|
+
require_relative "../workstream_state"
|
274
|
+
Aidp::WorkstreamState.increment_iteration(slug: ws_slug, project_dir: @project_dir)
|
275
|
+
end
|
252
276
|
end
|
253
277
|
|
254
278
|
# Mark step as in progress
|
@@ -284,6 +308,12 @@ module Aidp
|
|
284
308
|
def reset_all
|
285
309
|
@progress_tracker.reset
|
286
310
|
clear_state
|
311
|
+
# Also clear test workstream variables
|
312
|
+
if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
313
|
+
@test_workstream = nil
|
314
|
+
@test_workstream_path = nil
|
315
|
+
@test_workstream_branch = nil
|
316
|
+
end
|
287
317
|
end
|
288
318
|
|
289
319
|
# Get progress summary
|
@@ -299,7 +329,8 @@ module Aidp
|
|
299
329
|
harness_state: has_state? ? load_state : {},
|
300
330
|
progress_percentage: progress_percentage,
|
301
331
|
session_duration: session_duration,
|
302
|
-
harness_metrics: harness_metrics
|
332
|
+
harness_metrics: harness_metrics,
|
333
|
+
workstream: workstream_metadata
|
303
334
|
}
|
304
335
|
end
|
305
336
|
|
@@ -437,6 +468,88 @@ module Aidp
|
|
437
468
|
}
|
438
469
|
end
|
439
470
|
|
471
|
+
# Workstream management methods
|
472
|
+
|
473
|
+
# Get current workstream slug
|
474
|
+
def current_workstream
|
475
|
+
# In test mode, use instance variable
|
476
|
+
if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
477
|
+
return @test_workstream
|
478
|
+
end
|
479
|
+
|
480
|
+
state = load_state
|
481
|
+
state[:current_workstream]
|
482
|
+
end
|
483
|
+
|
484
|
+
# Get current workstream path (or project_dir if none)
|
485
|
+
def current_workstream_path
|
486
|
+
slug = current_workstream
|
487
|
+
return @project_dir unless slug
|
488
|
+
|
489
|
+
require_relative "../worktree"
|
490
|
+
ws = Aidp::Worktree.info(slug: slug, project_dir: @project_dir)
|
491
|
+
ws ? ws[:path] : @project_dir
|
492
|
+
end
|
493
|
+
|
494
|
+
# Set current workstream
|
495
|
+
def set_workstream(slug)
|
496
|
+
require_relative "../worktree"
|
497
|
+
# Verify workstream exists
|
498
|
+
ws = Aidp::Worktree.info(slug: slug, project_dir: @project_dir)
|
499
|
+
return false unless ws
|
500
|
+
|
501
|
+
# In test mode, use instance variables
|
502
|
+
if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
503
|
+
@test_workstream = slug
|
504
|
+
@test_workstream_path = ws[:path]
|
505
|
+
@test_workstream_branch = ws[:branch]
|
506
|
+
return true
|
507
|
+
end
|
508
|
+
|
509
|
+
update_state(
|
510
|
+
current_workstream: slug,
|
511
|
+
workstream_path: ws[:path],
|
512
|
+
workstream_branch: ws[:branch]
|
513
|
+
)
|
514
|
+
true
|
515
|
+
end
|
516
|
+
|
517
|
+
# Clear current workstream (switch back to main project)
|
518
|
+
def clear_workstream
|
519
|
+
# In test mode, use instance variables
|
520
|
+
if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
521
|
+
@test_workstream = nil
|
522
|
+
@test_workstream_path = nil
|
523
|
+
@test_workstream_branch = nil
|
524
|
+
return
|
525
|
+
end
|
526
|
+
|
527
|
+
update_state(
|
528
|
+
current_workstream: nil,
|
529
|
+
workstream_path: nil,
|
530
|
+
workstream_branch: nil
|
531
|
+
)
|
532
|
+
end
|
533
|
+
|
534
|
+
# Get workstream metadata
|
535
|
+
def workstream_metadata
|
536
|
+
# In test mode, use instance variables
|
537
|
+
if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
538
|
+
return {
|
539
|
+
slug: @test_workstream,
|
540
|
+
path: @test_workstream_path,
|
541
|
+
branch: @test_workstream_branch
|
542
|
+
}
|
543
|
+
end
|
544
|
+
|
545
|
+
state = load_state
|
546
|
+
{
|
547
|
+
slug: state[:current_workstream],
|
548
|
+
path: state[:workstream_path],
|
549
|
+
branch: state[:workstream_branch]
|
550
|
+
}
|
551
|
+
end
|
552
|
+
|
440
553
|
def get_performance_metrics
|
441
554
|
{
|
442
555
|
efficiency: calculate_efficiency_metrics,
|
@@ -550,12 +663,7 @@ module Aidp
|
|
550
663
|
end
|
551
664
|
rescue Errno::EEXIST
|
552
665
|
# Lock file exists, wait briefly and retry
|
553
|
-
|
554
|
-
if Async::Task.current?
|
555
|
-
Async::Task.current.sleep(0.1)
|
556
|
-
else
|
557
|
-
sleep(0.1)
|
558
|
-
end
|
666
|
+
sleep(0.1)
|
559
667
|
end
|
560
668
|
end
|
561
669
|
|
@@ -54,24 +54,17 @@ module Aidp
|
|
54
54
|
@display_mode = display_mode
|
55
55
|
@last_update = Time.now
|
56
56
|
|
57
|
-
# Start status display using Async (skip in test mode)
|
58
57
|
unless ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
59
|
-
require "
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
else
|
70
|
-
Async::Task.current.sleep(@update_interval)
|
71
|
-
end
|
72
|
-
rescue => e
|
73
|
-
handle_display_error(e)
|
74
|
-
end
|
58
|
+
require "concurrent"
|
59
|
+
@status_future = Concurrent::Future.execute do
|
60
|
+
while @running
|
61
|
+
begin
|
62
|
+
collect_status_data
|
63
|
+
display_status
|
64
|
+
check_alerts
|
65
|
+
sleep(@update_interval)
|
66
|
+
rescue => e
|
67
|
+
handle_display_error(e)
|
75
68
|
end
|
76
69
|
end
|
77
70
|
end
|
@@ -81,7 +74,7 @@ module Aidp
|
|
81
74
|
# Stop status updates
|
82
75
|
def stop_status_updates
|
83
76
|
@running = false
|
84
|
-
@
|
77
|
+
@status_future&.wait(5)
|
85
78
|
clear_display
|
86
79
|
end
|
87
80
|
|
@@ -265,7 +265,7 @@ module Aidp
|
|
265
265
|
def control_interface_loop
|
266
266
|
loop do
|
267
267
|
handle_control_input
|
268
|
-
sleep(0.1)
|
268
|
+
sleep(0.1)
|
269
269
|
rescue => e
|
270
270
|
@status_manager.show_error_status("Control interface error: #{e.message}")
|
271
271
|
end
|