aidp 0.15.1 → 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 +853 -6
- 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 +20 -16
- data/lib/aidp/harness/provider_manager.rb +56 -49
- 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/init/doc_generator.rb +75 -10
- data/lib/aidp/init/project_analyzer.rb +154 -26
- data/lib/aidp/init/runner.rb +263 -10
- data/lib/aidp/jobs/background_runner.rb +15 -5
- data/lib/aidp/logger.rb +11 -0
- 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 -7
- data/lib/aidp/analyze/prioritizer.rb +0 -403
- data/lib/aidp/analyze/report_generator.rb +0 -582
- data/lib/aidp/cli/checkpoint_command.rb +0 -98
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
|
|
|
@@ -283,7 +287,7 @@ module Aidp
|
|
|
283
287
|
when "codex"
|
|
284
288
|
"codex"
|
|
285
289
|
when "github_copilot"
|
|
286
|
-
"
|
|
290
|
+
"copilot"
|
|
287
291
|
when "opencode"
|
|
288
292
|
"opencode"
|
|
289
293
|
else
|
|
@@ -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
|
|
@@ -71,19 +72,20 @@ module Aidp
|
|
|
71
72
|
|
|
72
73
|
# Switch to next available provider with sophisticated fallback logic
|
|
73
74
|
def switch_provider(reason = "manual_switch", context = {})
|
|
74
|
-
|
|
75
|
+
old_provider = current_provider
|
|
76
|
+
Aidp.logger.info("provider_manager", "Attempting provider switch", reason: reason, current: old_provider, **context)
|
|
75
77
|
|
|
76
78
|
# Get fallback chain for current provider
|
|
77
|
-
provider_fallback_chain = fallback_chain(
|
|
79
|
+
provider_fallback_chain = fallback_chain(old_provider)
|
|
78
80
|
|
|
79
81
|
# Find next healthy provider in fallback chain
|
|
80
|
-
next_provider = find_next_healthy_provider(provider_fallback_chain,
|
|
82
|
+
next_provider = find_next_healthy_provider(provider_fallback_chain, old_provider)
|
|
81
83
|
|
|
82
84
|
if next_provider
|
|
83
85
|
success = set_current_provider(next_provider, reason, context)
|
|
84
86
|
if success
|
|
85
|
-
log_provider_switch(
|
|
86
|
-
Aidp.logger.info("provider_manager", "Provider switched successfully", from:
|
|
87
|
+
log_provider_switch(old_provider, next_provider, reason, context)
|
|
88
|
+
Aidp.logger.info("provider_manager", "Provider switched successfully", from: old_provider, to: next_provider, reason: reason)
|
|
87
89
|
return next_provider
|
|
88
90
|
else
|
|
89
91
|
Aidp.logger.warn("provider_manager", "Failed to switch to provider", provider: next_provider, reason: reason)
|
|
@@ -96,7 +98,7 @@ module Aidp
|
|
|
96
98
|
if next_provider
|
|
97
99
|
success = set_current_provider(next_provider, reason, context)
|
|
98
100
|
if success
|
|
99
|
-
log_provider_switch(
|
|
101
|
+
log_provider_switch(old_provider, next_provider, reason, context)
|
|
100
102
|
return next_provider
|
|
101
103
|
end
|
|
102
104
|
end
|
|
@@ -107,14 +109,14 @@ module Aidp
|
|
|
107
109
|
if next_provider
|
|
108
110
|
success = set_current_provider(next_provider, reason, context)
|
|
109
111
|
if success
|
|
110
|
-
log_provider_switch(
|
|
112
|
+
log_provider_switch(old_provider, next_provider, reason, context)
|
|
111
113
|
return next_provider
|
|
112
114
|
end
|
|
113
115
|
end
|
|
114
116
|
|
|
115
117
|
# No providers available
|
|
116
118
|
log_no_providers_available(reason, context)
|
|
117
|
-
Aidp.logger.error("provider_manager", "No providers available for fallback", reason: reason,
|
|
119
|
+
Aidp.logger.error("provider_manager", "No providers available for fallback", reason: reason, provider: old_provider)
|
|
118
120
|
nil
|
|
119
121
|
end
|
|
120
122
|
|
|
@@ -144,26 +146,27 @@ module Aidp
|
|
|
144
146
|
|
|
145
147
|
# Switch provider with retry logic
|
|
146
148
|
def switch_provider_with_retry(reason = "retry", max_retries = @max_retries)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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})
|
|
151
160
|
|
|
152
161
|
if next_provider
|
|
153
162
|
return next_provider
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
retry_count += 1
|
|
157
|
-
|
|
158
|
-
# Wait before retrying
|
|
159
|
-
delay = calculate_retry_delay(retry_count)
|
|
160
|
-
if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
|
161
|
-
sleep(delay)
|
|
162
163
|
else
|
|
163
|
-
|
|
164
|
+
# Raise to trigger retry
|
|
165
|
+
raise "Provider switch failed on attempt #{attempt_count}"
|
|
164
166
|
end
|
|
165
167
|
end
|
|
166
|
-
|
|
168
|
+
rescue Aidp::Concurrency::MaxAttemptsError
|
|
169
|
+
# All retries exhausted
|
|
167
170
|
nil
|
|
168
171
|
end
|
|
169
172
|
|
|
@@ -234,26 +237,27 @@ module Aidp
|
|
|
234
237
|
def switch_model_with_retry(reason = "retry", max_retries = @max_retries)
|
|
235
238
|
return nil unless @model_switching_enabled
|
|
236
239
|
|
|
237
|
-
|
|
240
|
+
attempt_count = 0
|
|
238
241
|
|
|
239
|
-
|
|
240
|
-
|
|
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})
|
|
241
251
|
|
|
242
252
|
if next_model
|
|
243
253
|
return next_model
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
retry_count += 1
|
|
247
|
-
|
|
248
|
-
# Wait before retrying
|
|
249
|
-
delay = calculate_retry_delay(retry_count)
|
|
250
|
-
if ENV["RACK_ENV"] == "test" || defined?(RSpec)
|
|
251
|
-
sleep(delay)
|
|
252
254
|
else
|
|
253
|
-
|
|
255
|
+
# Raise to trigger retry
|
|
256
|
+
raise "Model switch failed on attempt #{attempt_count}"
|
|
254
257
|
end
|
|
255
258
|
end
|
|
256
|
-
|
|
259
|
+
rescue Aidp::Concurrency::MaxAttemptsError
|
|
260
|
+
# All retries exhausted
|
|
257
261
|
nil
|
|
258
262
|
end
|
|
259
263
|
|
|
@@ -1111,28 +1115,31 @@ module Aidp
|
|
|
1111
1115
|
r, w = IO.pipe
|
|
1112
1116
|
pid = Process.spawn(binary, "--version", out: w, err: w)
|
|
1113
1117
|
w.close
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
sleep 0.05
|
|
1120
|
-
end
|
|
1121
|
-
unless status
|
|
1122
|
-
# 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
|
|
1123
1123
|
begin
|
|
1124
1124
|
Process.kill("TERM", pid)
|
|
1125
1125
|
rescue => e
|
|
1126
1126
|
log_rescue(e, component: "provider_manager", action: "kill_timeout_process_term", fallback: nil, binary: binary, pid: pid)
|
|
1127
1127
|
nil
|
|
1128
1128
|
end
|
|
1129
|
-
|
|
1129
|
+
|
|
1130
|
+
# Brief wait for TERM to take effect
|
|
1130
1131
|
begin
|
|
1131
|
-
|
|
1132
|
-
rescue
|
|
1133
|
-
|
|
1134
|
-
|
|
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
|
|
1135
1141
|
end
|
|
1142
|
+
|
|
1136
1143
|
ok = false
|
|
1137
1144
|
reason = "binary_timeout"
|
|
1138
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
|
|