aidp 0.34.1 → 0.39.3
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 +14 -14
- data/lib/aidp/agent_harness_adapter.rb +309 -0
- data/lib/aidp/cli/issue_importer.rb +20 -6
- data/lib/aidp/cli/jobs_command.rb +1 -1
- data/lib/aidp/cli/models_command.rb +9 -16
- data/lib/aidp/cli/prompts_command.rb +253 -0
- data/lib/aidp/cli/security_command.rb +4 -10
- data/lib/aidp/cli/storage_command.rb +277 -0
- data/lib/aidp/cli/temporal_command.rb +460 -0
- data/lib/aidp/cli.rb +24 -2
- data/lib/aidp/config.rb +15 -0
- data/lib/aidp/config_paths.rb +12 -2
- data/lib/aidp/database/migrations.rb +114 -0
- data/lib/aidp/database/repositories/checkpoint_repository.rb +174 -0
- data/lib/aidp/database/repositories/deprecated_models_repository.rb +206 -0
- data/lib/aidp/database/repositories/evaluation_repository.rb +208 -0
- data/lib/aidp/database/repositories/harness_state_repository.rb +140 -0
- data/lib/aidp/database/repositories/job_repository.rb +278 -0
- data/lib/aidp/database/repositories/model_cache_repository.rb +194 -0
- data/lib/aidp/database/repositories/progress_repository.rb +210 -0
- data/lib/aidp/database/repositories/prompt_archive_repository.rb +195 -0
- data/lib/aidp/database/repositories/prompt_feedback_repository.rb +266 -0
- data/lib/aidp/database/repositories/provider_info_cache_repository.rb +208 -0
- data/lib/aidp/database/repositories/provider_metrics_repository.rb +245 -0
- data/lib/aidp/database/repositories/secrets_repository.rb +187 -0
- data/lib/aidp/database/repositories/task_repository.rb +231 -0
- data/lib/aidp/database/repositories/template_version_repository.rb +383 -0
- data/lib/aidp/database/repositories/watch_state_repository.rb +472 -0
- data/lib/aidp/database/repositories/workstream_repository.rb +310 -0
- data/lib/aidp/database/repositories/worktree_repository.rb +240 -0
- data/lib/aidp/database/repository.rb +166 -0
- data/lib/aidp/database/schema.rb +347 -0
- data/lib/aidp/database/storage_migrator.rb +693 -0
- data/lib/aidp/database.rb +139 -0
- data/lib/aidp/debug_mixin.rb +2 -2
- data/lib/aidp/execute/checkpoint.rb +26 -0
- data/lib/aidp/execute/checkpoint_display.rb +16 -0
- data/lib/aidp/execute/prompt_evaluator.rb +30 -57
- data/lib/aidp/execute/work_loop_runner.rb +294 -46
- data/lib/aidp/firewall/provider_requirements_collector.rb +7 -23
- data/lib/aidp/harness/agent_harness_provider_manager.rb +275 -0
- data/lib/aidp/harness/ai_decision_engine.rb +78 -296
- data/lib/aidp/harness/config_schema.rb +59 -0
- data/lib/aidp/harness/configuration.rb +229 -10
- data/lib/aidp/harness/deprecation_cache.rb +53 -11
- data/lib/aidp/harness/enhanced_runner.rb +1 -1
- data/lib/aidp/harness/error_handler.rb +11 -5
- data/lib/aidp/harness/provider_config.rb +36 -0
- data/lib/aidp/harness/provider_factory.rb +26 -24
- data/lib/aidp/harness/provider_info.rb +9 -5
- data/lib/aidp/harness/provider_manager.rb +2 -2
- data/lib/aidp/harness/provider_metrics.rb +103 -1
- data/lib/aidp/harness/rspec_command_optimizer.rb +166 -0
- data/lib/aidp/harness/ruby_llm_registry.rb +97 -8
- data/lib/aidp/harness/runner.rb +7 -2
- data/lib/aidp/harness/state_manager.rb +1 -1
- data/lib/aidp/harness/test_runner.rb +167 -11
- data/lib/aidp/harness/thinking_depth_manager.rb +564 -5
- data/lib/aidp/harness/usage_limit.rb +179 -0
- data/lib/aidp/harness/usage_limit_enforcer.rb +240 -0
- data/lib/aidp/harness/usage_limit_tracker.rb +308 -0
- data/lib/aidp/harness/usage_period.rb +234 -0
- data/lib/aidp/init/agent_instructions_generator.rb +317 -0
- data/lib/aidp/init/runner.rb +25 -4
- data/lib/aidp/interfaces/activity_monitor_interface.rb +234 -0
- data/lib/aidp/interfaces/binary_checker_interface.rb +232 -0
- data/lib/aidp/interfaces/command_executor_interface.rb +216 -0
- data/lib/aidp/interfaces/logger_interface.rb +150 -0
- data/lib/aidp/interfaces/ui_interface.rb +254 -0
- data/lib/aidp/logger.rb +25 -7
- data/lib/aidp/orchestration_adapter.rb +279 -0
- data/lib/aidp/pr_worktree_manager.rb +4 -15
- data/lib/aidp/prompts/feedback_collector.rb +198 -0
- data/lib/aidp/prompts/prompt_template_manager.rb +415 -0
- data/lib/aidp/prompts/template_evolver.rb +305 -0
- data/lib/aidp/prompts/template_version_manager.rb +325 -0
- data/lib/aidp/setup/in_memory_config_adapter.rb +257 -0
- data/lib/aidp/setup/in_memory_config_manager.rb +262 -0
- data/lib/aidp/setup/wizard.rb +616 -176
- data/lib/aidp/shell_executor.rb +58 -0
- data/lib/aidp/style_guide/selector.rb +56 -1
- data/lib/aidp/temporal/activities/analyze_issue_activity.rb +198 -0
- data/lib/aidp/temporal/activities/analyze_sub_task_activity.rb +163 -0
- data/lib/aidp/temporal/activities/base_activity.rb +78 -0
- data/lib/aidp/temporal/activities/create_plan_activity.rb +115 -0
- data/lib/aidp/temporal/activities/create_pr_activity.rb +193 -0
- data/lib/aidp/temporal/activities/create_prompt_activity.rb +118 -0
- data/lib/aidp/temporal/activities/diagnose_failure_activity.rb +228 -0
- data/lib/aidp/temporal/activities/prepare_next_iteration_activity.rb +147 -0
- data/lib/aidp/temporal/activities/record_checkpoint_activity.rb +81 -0
- data/lib/aidp/temporal/activities/run_agent_activity.rb +139 -0
- data/lib/aidp/temporal/activities/run_tests_activity.rb +114 -0
- data/lib/aidp/temporal/activities/run_work_loop_iteration_activity.rb +249 -0
- data/lib/aidp/temporal/configuration.rb +169 -0
- data/lib/aidp/temporal/connection.rb +93 -0
- data/lib/aidp/temporal/worker.rb +119 -0
- data/lib/aidp/temporal/workflow_client.rb +171 -0
- data/lib/aidp/temporal/workflows/base_workflow.rb +73 -0
- data/lib/aidp/temporal/workflows/issue_to_pr_workflow.rb +278 -0
- data/lib/aidp/temporal/workflows/sub_issue_workflow.rb +253 -0
- data/lib/aidp/temporal/workflows/work_loop_workflow.rb +321 -0
- data/lib/aidp/temporal.rb +120 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/base_processor.rb +35 -0
- data/lib/aidp/watch/build_processor.rb +132 -0
- data/lib/aidp/watch/github_state_extractor.rb +0 -8
- data/lib/aidp/watch/rebase_processor.rb +245 -0
- data/lib/aidp/watch/round_robin_scheduler.rb +150 -0
- data/lib/aidp/watch/runner.rb +366 -285
- data/lib/aidp/watch/state_store.rb +80 -0
- data/lib/aidp/watch/work_item.rb +96 -0
- data/lib/aidp/watch/worktree_cleanup_job.rb +226 -0
- data/lib/aidp/watch/worktree_reconciler.rb +643 -0
- data/lib/aidp/workflows/guided_agent.rb +1 -1
- data/lib/aidp/worktree.rb +62 -0
- data/templates/aidp.yml.example +70 -0
- metadata +117 -16
- data/lib/aidp/analyze/json_file_storage.rb +0 -292
- data/lib/aidp/execute/future_work_backlog.rb +0 -411
- data/lib/aidp/providers/adapter.rb +0 -239
- data/lib/aidp/providers/aider.rb +0 -264
- data/lib/aidp/providers/anthropic.rb +0 -614
- data/lib/aidp/providers/base.rb +0 -617
- data/lib/aidp/providers/capability_registry.rb +0 -204
- data/lib/aidp/providers/codex.rb +0 -267
- data/lib/aidp/providers/cursor.rb +0 -304
- data/lib/aidp/providers/error_taxonomy.rb +0 -195
- data/lib/aidp/providers/gemini.rb +0 -110
- data/lib/aidp/providers/github_copilot.rb +0 -227
- data/lib/aidp/providers/kilocode.rb +0 -157
- data/lib/aidp/providers/opencode.rb +0 -139
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_activity"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
module Temporal
|
|
7
|
+
module Activities
|
|
8
|
+
# Activity that records a checkpoint for progress tracking
|
|
9
|
+
# Persists state for recovery and observability
|
|
10
|
+
class RecordCheckpointActivity < BaseActivity
|
|
11
|
+
def execute(input)
|
|
12
|
+
with_activity_context do
|
|
13
|
+
project_dir = input[:project_dir]
|
|
14
|
+
step_name = input[:step_name]
|
|
15
|
+
iteration = input[:iteration]
|
|
16
|
+
state = input[:state]
|
|
17
|
+
test_results = input[:test_results]
|
|
18
|
+
|
|
19
|
+
log_activity("recording_checkpoint",
|
|
20
|
+
project_dir: project_dir,
|
|
21
|
+
step_name: step_name,
|
|
22
|
+
iteration: iteration)
|
|
23
|
+
|
|
24
|
+
# Create checkpoint recorder
|
|
25
|
+
checkpoint = Aidp::Execute::Checkpoint.new(project_dir)
|
|
26
|
+
|
|
27
|
+
# Build metrics from test results
|
|
28
|
+
metrics = build_metrics(test_results)
|
|
29
|
+
|
|
30
|
+
# Record the checkpoint
|
|
31
|
+
checkpoint.record_checkpoint(
|
|
32
|
+
step_name,
|
|
33
|
+
iteration,
|
|
34
|
+
{
|
|
35
|
+
state: state,
|
|
36
|
+
test_results_summary: summarize_test_results(test_results),
|
|
37
|
+
metrics: metrics,
|
|
38
|
+
workflow_type: "temporal"
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
success_result(
|
|
43
|
+
step_name: step_name,
|
|
44
|
+
iteration: iteration,
|
|
45
|
+
timestamp: Time.now.iso8601
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def build_metrics(test_results)
|
|
53
|
+
return {} unless test_results
|
|
54
|
+
|
|
55
|
+
results = test_results[:results] || {}
|
|
56
|
+
passing = results.count { |_, r| r[:success] }
|
|
57
|
+
total = results.count
|
|
58
|
+
|
|
59
|
+
{
|
|
60
|
+
checks_passing: passing,
|
|
61
|
+
checks_total: total,
|
|
62
|
+
pass_rate: (total > 0) ? (passing.to_f / total * 100).round(1) : 0
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def summarize_test_results(test_results)
|
|
67
|
+
return {} unless test_results
|
|
68
|
+
|
|
69
|
+
results = test_results[:results] || {}
|
|
70
|
+
|
|
71
|
+
results.transform_values do |result|
|
|
72
|
+
{
|
|
73
|
+
success: result[:success],
|
|
74
|
+
duration: result[:duration]
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_activity"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
module Temporal
|
|
7
|
+
module Activities
|
|
8
|
+
# Activity that executes an AI agent iteration
|
|
9
|
+
# Wraps the existing AIDP agent execution with Temporal durability
|
|
10
|
+
class RunAgentActivity < BaseActivity
|
|
11
|
+
def execute(input)
|
|
12
|
+
with_activity_context do
|
|
13
|
+
project_dir = input[:project_dir]
|
|
14
|
+
step_name = input[:step_name]
|
|
15
|
+
iteration = input[:iteration]
|
|
16
|
+
injected_instructions = input[:injected_instructions] || []
|
|
17
|
+
escalate = input[:escalate] || false
|
|
18
|
+
|
|
19
|
+
log_activity("executing_agent",
|
|
20
|
+
project_dir: project_dir,
|
|
21
|
+
step_name: step_name,
|
|
22
|
+
iteration: iteration,
|
|
23
|
+
instructions_count: injected_instructions.length,
|
|
24
|
+
escalate: escalate)
|
|
25
|
+
|
|
26
|
+
# Load configuration
|
|
27
|
+
config = load_config(project_dir)
|
|
28
|
+
provider_manager = create_provider_manager(project_dir, config)
|
|
29
|
+
|
|
30
|
+
# Get prompt content
|
|
31
|
+
prompt_manager = Aidp::Execute::PromptManager.new(project_dir, config: config)
|
|
32
|
+
prompt_content = prompt_manager.read
|
|
33
|
+
|
|
34
|
+
unless prompt_content
|
|
35
|
+
return error_result("No PROMPT.md found")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Inject any queued instructions
|
|
39
|
+
if injected_instructions.any?
|
|
40
|
+
prompt_content = inject_instructions(prompt_content, injected_instructions)
|
|
41
|
+
prompt_manager.write(prompt_content)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Select model based on escalation status
|
|
45
|
+
model_selector = create_model_selector(config, escalate: escalate)
|
|
46
|
+
provider, model = model_selector.select
|
|
47
|
+
|
|
48
|
+
log_activity("agent_model_selected",
|
|
49
|
+
provider: provider,
|
|
50
|
+
model: model,
|
|
51
|
+
escalate: escalate)
|
|
52
|
+
|
|
53
|
+
# Periodic heartbeat during agent execution
|
|
54
|
+
heartbeat_thread = start_heartbeat_thread(iteration: iteration)
|
|
55
|
+
|
|
56
|
+
begin
|
|
57
|
+
# Execute agent
|
|
58
|
+
result = execute_agent(
|
|
59
|
+
project_dir: project_dir,
|
|
60
|
+
provider_manager: provider_manager,
|
|
61
|
+
provider: provider,
|
|
62
|
+
model: model,
|
|
63
|
+
prompt_content: prompt_content
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
check_cancellation!
|
|
67
|
+
|
|
68
|
+
if result[:success]
|
|
69
|
+
success_result(
|
|
70
|
+
result: result[:output],
|
|
71
|
+
provider: provider,
|
|
72
|
+
model: model,
|
|
73
|
+
iteration: iteration
|
|
74
|
+
)
|
|
75
|
+
else
|
|
76
|
+
error_result(result[:error] || "Agent execution failed",
|
|
77
|
+
provider: provider,
|
|
78
|
+
model: model,
|
|
79
|
+
iteration: iteration)
|
|
80
|
+
end
|
|
81
|
+
ensure
|
|
82
|
+
heartbeat_thread&.kill
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def inject_instructions(prompt_content, instructions)
|
|
90
|
+
injection_text = instructions.map { |i| i[:content] || i }.join("\n\n")
|
|
91
|
+
|
|
92
|
+
<<~PROMPT
|
|
93
|
+
#{prompt_content}
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
## Additional Instructions (Injected)
|
|
97
|
+
|
|
98
|
+
#{injection_text}
|
|
99
|
+
PROMPT
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def create_model_selector(config, escalate:)
|
|
103
|
+
require_relative "../../harness/thinking_depth_manager"
|
|
104
|
+
|
|
105
|
+
manager = Aidp::Harness::ThinkingDepthManager.new(config)
|
|
106
|
+
manager.escalate if escalate
|
|
107
|
+
manager
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def start_heartbeat_thread(iteration:)
|
|
111
|
+
Thread.new do
|
|
112
|
+
loop do
|
|
113
|
+
sleep 30
|
|
114
|
+
heartbeat(iteration: iteration, status: "running")
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def execute_agent(project_dir:, provider_manager:, provider:, model:, prompt_content:)
|
|
120
|
+
# Use the provider manager to execute
|
|
121
|
+
prompt_path = File.join(project_dir, ".aidp", "PROMPT.md")
|
|
122
|
+
|
|
123
|
+
result = provider_manager.execute_with_provider(
|
|
124
|
+
provider,
|
|
125
|
+
model: model,
|
|
126
|
+
prompt_path: prompt_path
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
{success: result[:success], output: result[:output], error: result[:error]}
|
|
130
|
+
rescue => e
|
|
131
|
+
Aidp.log_error("run_agent_activity", "agent_execution_failed",
|
|
132
|
+
error: e.message,
|
|
133
|
+
error_class: e.class.name)
|
|
134
|
+
{success: false, error: e.message}
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_activity"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
module Temporal
|
|
7
|
+
module Activities
|
|
8
|
+
# Activity that runs tests and linters
|
|
9
|
+
# Wraps AIDP test runner with Temporal durability
|
|
10
|
+
class RunTestsActivity < BaseActivity
|
|
11
|
+
def execute(input)
|
|
12
|
+
with_activity_context do
|
|
13
|
+
project_dir = input[:project_dir]
|
|
14
|
+
iteration = input[:iteration]
|
|
15
|
+
phases = input[:phases] || [:test, :lint]
|
|
16
|
+
|
|
17
|
+
log_activity("running_tests",
|
|
18
|
+
project_dir: project_dir,
|
|
19
|
+
iteration: iteration,
|
|
20
|
+
phases: phases)
|
|
21
|
+
|
|
22
|
+
# Load configuration
|
|
23
|
+
config = load_config(project_dir)
|
|
24
|
+
|
|
25
|
+
# Create test runner
|
|
26
|
+
test_runner = Aidp::Harness::TestRunner.new(project_dir, config)
|
|
27
|
+
|
|
28
|
+
# Run tests with periodic heartbeat
|
|
29
|
+
results = {}
|
|
30
|
+
all_passing = true
|
|
31
|
+
partial_pass = false
|
|
32
|
+
|
|
33
|
+
phases.each do |phase|
|
|
34
|
+
heartbeat(phase: phase, iteration: iteration)
|
|
35
|
+
check_cancellation!
|
|
36
|
+
|
|
37
|
+
result = run_phase(test_runner, phase)
|
|
38
|
+
results[phase] = result
|
|
39
|
+
|
|
40
|
+
if result[:success]
|
|
41
|
+
partial_pass = true
|
|
42
|
+
else
|
|
43
|
+
all_passing = false
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
log_activity("phase_complete",
|
|
47
|
+
phase: phase,
|
|
48
|
+
success: result[:success],
|
|
49
|
+
duration: result[:duration])
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
success_result(
|
|
53
|
+
all_passing: all_passing,
|
|
54
|
+
partial_pass: partial_pass,
|
|
55
|
+
results: results,
|
|
56
|
+
iteration: iteration,
|
|
57
|
+
summary: build_summary(results)
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def run_phase(test_runner, phase)
|
|
65
|
+
start_time = Time.now
|
|
66
|
+
|
|
67
|
+
result = case phase
|
|
68
|
+
when :test, "test"
|
|
69
|
+
test_runner.run_tests
|
|
70
|
+
when :lint, "lint"
|
|
71
|
+
test_runner.run_lint
|
|
72
|
+
when :typecheck, "typecheck"
|
|
73
|
+
test_runner.run_typecheck
|
|
74
|
+
when :build, "build"
|
|
75
|
+
test_runner.run_build
|
|
76
|
+
else
|
|
77
|
+
{success: false, output: "Unknown phase: #{phase}"}
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
duration = Time.now - start_time
|
|
81
|
+
|
|
82
|
+
{
|
|
83
|
+
success: result[:success],
|
|
84
|
+
output: result[:output],
|
|
85
|
+
exit_code: result[:exit_code],
|
|
86
|
+
duration: duration.round(2)
|
|
87
|
+
}
|
|
88
|
+
rescue => e
|
|
89
|
+
Aidp.log_error("run_tests_activity", "phase_failed",
|
|
90
|
+
phase: phase,
|
|
91
|
+
error: e.message)
|
|
92
|
+
|
|
93
|
+
{
|
|
94
|
+
success: false,
|
|
95
|
+
output: e.message,
|
|
96
|
+
exit_code: -1,
|
|
97
|
+
duration: (Time.now - start_time).round(2)
|
|
98
|
+
}
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def build_summary(results)
|
|
102
|
+
passing = results.count { |_, r| r[:success] }
|
|
103
|
+
total = results.count
|
|
104
|
+
|
|
105
|
+
{
|
|
106
|
+
passing: passing,
|
|
107
|
+
total: total,
|
|
108
|
+
failed_phases: results.reject { |_, r| r[:success] }.keys
|
|
109
|
+
}
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_activity"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
module Temporal
|
|
7
|
+
module Activities
|
|
8
|
+
# Activity that runs a single work loop iteration
|
|
9
|
+
# Combines agent execution and testing in one activity
|
|
10
|
+
# Used by IssueToPrWorkflow for simplified orchestration
|
|
11
|
+
class RunWorkLoopIterationActivity < BaseActivity
|
|
12
|
+
def execute(input)
|
|
13
|
+
with_activity_context do
|
|
14
|
+
project_dir = input[:project_dir]
|
|
15
|
+
issue_number = input[:issue_number]
|
|
16
|
+
plan = input[:plan]
|
|
17
|
+
iteration = input[:iteration]
|
|
18
|
+
injected_instructions = input[:injected_instructions] || []
|
|
19
|
+
|
|
20
|
+
log_activity("running_work_loop_iteration",
|
|
21
|
+
project_dir: project_dir,
|
|
22
|
+
issue_number: issue_number,
|
|
23
|
+
iteration: iteration)
|
|
24
|
+
|
|
25
|
+
# Start heartbeat thread
|
|
26
|
+
heartbeat_thread = start_heartbeat_thread(iteration: iteration)
|
|
27
|
+
|
|
28
|
+
begin
|
|
29
|
+
# Phase 1: Run agent
|
|
30
|
+
heartbeat(phase: "agent", iteration: iteration)
|
|
31
|
+
agent_result = run_agent(
|
|
32
|
+
project_dir: project_dir,
|
|
33
|
+
issue_number: issue_number,
|
|
34
|
+
plan: plan,
|
|
35
|
+
iteration: iteration,
|
|
36
|
+
injected_instructions: injected_instructions
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
check_cancellation!
|
|
40
|
+
|
|
41
|
+
unless agent_result[:success]
|
|
42
|
+
return error_result("Agent failed: #{agent_result[:error]}",
|
|
43
|
+
iteration: iteration,
|
|
44
|
+
tests_passing: false)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Phase 2: Run tests
|
|
48
|
+
heartbeat(phase: "tests", iteration: iteration)
|
|
49
|
+
test_result = run_tests(project_dir: project_dir)
|
|
50
|
+
|
|
51
|
+
check_cancellation!
|
|
52
|
+
|
|
53
|
+
# Phase 3: Handle result
|
|
54
|
+
if test_result[:all_passing]
|
|
55
|
+
success_result(
|
|
56
|
+
result: {
|
|
57
|
+
agent_output: agent_result[:output],
|
|
58
|
+
test_results: test_result
|
|
59
|
+
},
|
|
60
|
+
iteration: iteration,
|
|
61
|
+
tests_passing: true
|
|
62
|
+
)
|
|
63
|
+
else
|
|
64
|
+
# Update prompt with failures for next iteration
|
|
65
|
+
update_prompt_with_failures(project_dir, test_result)
|
|
66
|
+
|
|
67
|
+
success_result(
|
|
68
|
+
result: {
|
|
69
|
+
agent_output: agent_result[:output],
|
|
70
|
+
test_results: test_result
|
|
71
|
+
},
|
|
72
|
+
iteration: iteration,
|
|
73
|
+
tests_passing: false
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
ensure
|
|
77
|
+
heartbeat_thread&.kill
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def start_heartbeat_thread(iteration:)
|
|
85
|
+
Thread.new do
|
|
86
|
+
loop do
|
|
87
|
+
sleep 30
|
|
88
|
+
heartbeat(iteration: iteration, status: "running")
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def run_agent(project_dir:, issue_number:, plan:, iteration:, injected_instructions:)
|
|
94
|
+
config = load_config(project_dir)
|
|
95
|
+
provider_manager = create_provider_manager(project_dir, config)
|
|
96
|
+
|
|
97
|
+
# Prepare prompt with plan context
|
|
98
|
+
prompt_manager = Aidp::Execute::PromptManager.new(project_dir, config: config)
|
|
99
|
+
current_prompt = prompt_manager.read
|
|
100
|
+
|
|
101
|
+
unless current_prompt
|
|
102
|
+
# Create initial prompt from plan
|
|
103
|
+
current_prompt = build_initial_prompt(plan, issue_number)
|
|
104
|
+
prompt_manager.write(current_prompt)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Inject instructions if any
|
|
108
|
+
if injected_instructions.any?
|
|
109
|
+
current_prompt = inject_instructions(current_prompt, injected_instructions)
|
|
110
|
+
prompt_manager.write(current_prompt)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Select model
|
|
114
|
+
require_relative "../../harness/thinking_depth_manager"
|
|
115
|
+
model_selector = Aidp::Harness::ThinkingDepthManager.new(config)
|
|
116
|
+
provider, model = model_selector.select
|
|
117
|
+
|
|
118
|
+
log_activity("agent_executing",
|
|
119
|
+
provider: provider,
|
|
120
|
+
model: model,
|
|
121
|
+
iteration: iteration)
|
|
122
|
+
|
|
123
|
+
# Execute
|
|
124
|
+
result = provider_manager.execute_with_provider(
|
|
125
|
+
provider,
|
|
126
|
+
model: model,
|
|
127
|
+
prompt_path: prompt_manager.prompt_path
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
{success: result[:success], output: result[:output], error: result[:error]}
|
|
131
|
+
rescue => e
|
|
132
|
+
Aidp.log_error("run_work_loop_iteration_activity", "agent_failed",
|
|
133
|
+
error: e.message,
|
|
134
|
+
iteration: iteration)
|
|
135
|
+
{success: false, error: e.message}
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def run_tests(project_dir:)
|
|
139
|
+
config = load_config(project_dir)
|
|
140
|
+
test_runner = Aidp::Harness::TestRunner.new(project_dir, config)
|
|
141
|
+
|
|
142
|
+
results = {}
|
|
143
|
+
|
|
144
|
+
# Run tests
|
|
145
|
+
test_result = test_runner.run_tests
|
|
146
|
+
results[:test] = test_result
|
|
147
|
+
|
|
148
|
+
# Run lint
|
|
149
|
+
lint_result = test_runner.run_lint
|
|
150
|
+
results[:lint] = lint_result
|
|
151
|
+
|
|
152
|
+
all_passing = results.values.all? { |r| r[:success] }
|
|
153
|
+
|
|
154
|
+
{
|
|
155
|
+
all_passing: all_passing,
|
|
156
|
+
results: results
|
|
157
|
+
}
|
|
158
|
+
rescue => e
|
|
159
|
+
Aidp.log_error("run_work_loop_iteration_activity", "tests_failed",
|
|
160
|
+
error: e.message)
|
|
161
|
+
{all_passing: false, results: {}, error: e.message}
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def build_initial_prompt(plan, issue_number)
|
|
165
|
+
<<~PROMPT
|
|
166
|
+
# Implementation Task: Issue ##{issue_number}
|
|
167
|
+
|
|
168
|
+
## Objective
|
|
169
|
+
|
|
170
|
+
#{plan[:title]}
|
|
171
|
+
|
|
172
|
+
## Requirements
|
|
173
|
+
|
|
174
|
+
#{plan[:requirements]&.map { |r| "- #{r}" }&.join("\n")}
|
|
175
|
+
|
|
176
|
+
## Implementation Steps
|
|
177
|
+
|
|
178
|
+
#{plan[:steps]&.map { |s| "- #{s[:description]}" }&.join("\n")}
|
|
179
|
+
|
|
180
|
+
## Instructions
|
|
181
|
+
|
|
182
|
+
1. Review the existing codebase
|
|
183
|
+
2. Implement the required changes
|
|
184
|
+
3. Ensure tests pass
|
|
185
|
+
4. Follow the project's style guide
|
|
186
|
+
PROMPT
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def inject_instructions(prompt, instructions)
|
|
190
|
+
instruction_text = instructions.map { |i| i[:content] || i }.join("\n\n")
|
|
191
|
+
|
|
192
|
+
<<~PROMPT
|
|
193
|
+
#{prompt}
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
## Additional Instructions
|
|
197
|
+
|
|
198
|
+
#{instruction_text}
|
|
199
|
+
PROMPT
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def update_prompt_with_failures(project_dir, test_result)
|
|
203
|
+
prompt_manager = Aidp::Execute::PromptManager.new(project_dir)
|
|
204
|
+
current_prompt = prompt_manager.read
|
|
205
|
+
|
|
206
|
+
return unless current_prompt
|
|
207
|
+
|
|
208
|
+
failures = extract_failures(test_result)
|
|
209
|
+
|
|
210
|
+
updated_prompt = <<~PROMPT
|
|
211
|
+
#{current_prompt}
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
## Failures to Fix
|
|
215
|
+
|
|
216
|
+
#{failures}
|
|
217
|
+
|
|
218
|
+
Please address the above failures.
|
|
219
|
+
PROMPT
|
|
220
|
+
|
|
221
|
+
prompt_manager.write(updated_prompt)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def extract_failures(test_result)
|
|
225
|
+
results = test_result[:results] || {}
|
|
226
|
+
sections = []
|
|
227
|
+
|
|
228
|
+
results.each do |phase, result|
|
|
229
|
+
next if result[:success]
|
|
230
|
+
|
|
231
|
+
sections << "### #{phase.to_s.capitalize} Failures"
|
|
232
|
+
sections << ""
|
|
233
|
+
sections << "```"
|
|
234
|
+
sections << truncate(result[:output], 1500)
|
|
235
|
+
sections << "```"
|
|
236
|
+
sections << ""
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
sections.join("\n")
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def truncate(text, max_length)
|
|
243
|
+
return "" unless text
|
|
244
|
+
(text.length > max_length) ? "#{text.slice(0, max_length)}\n...(truncated)" : text
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|