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,321 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "temporalio/workflow"
|
|
4
|
+
require_relative "base_workflow"
|
|
5
|
+
|
|
6
|
+
module Aidp
|
|
7
|
+
module Temporal
|
|
8
|
+
module Workflows
|
|
9
|
+
# Workflow that implements the fix-forward work loop pattern
|
|
10
|
+
# Handles iterative implementation with test validation
|
|
11
|
+
#
|
|
12
|
+
# State machine:
|
|
13
|
+
# READY → APPLY_PATCH → TEST → {PASS → DONE | FAIL → DIAGNOSE → NEXT_PATCH} → READY
|
|
14
|
+
class WorkLoopWorkflow < BaseWorkflow
|
|
15
|
+
# Query handlers
|
|
16
|
+
workflow_query
|
|
17
|
+
def current_state
|
|
18
|
+
@state
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
workflow_query
|
|
22
|
+
def iteration_count
|
|
23
|
+
@iteration
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
workflow_query
|
|
27
|
+
def test_results
|
|
28
|
+
@last_test_results
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
workflow_query
|
|
32
|
+
def progress
|
|
33
|
+
{
|
|
34
|
+
state: @state,
|
|
35
|
+
iteration: @iteration,
|
|
36
|
+
max_iterations: @max_iterations,
|
|
37
|
+
step_name: @step_name,
|
|
38
|
+
consecutive_failures: @consecutive_failures,
|
|
39
|
+
started_at: @started_at
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Signal handlers
|
|
44
|
+
workflow_signal
|
|
45
|
+
def pause
|
|
46
|
+
@paused = true
|
|
47
|
+
log_workflow("paused", iteration: @iteration)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
workflow_signal
|
|
51
|
+
def resume
|
|
52
|
+
@paused = false
|
|
53
|
+
log_workflow("resumed", iteration: @iteration)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
workflow_signal
|
|
57
|
+
def inject_instruction(instruction)
|
|
58
|
+
@instruction_queue ||= []
|
|
59
|
+
@instruction_queue << {
|
|
60
|
+
content: instruction,
|
|
61
|
+
type: :user_input,
|
|
62
|
+
queued_at: Time.now.iso8601
|
|
63
|
+
}
|
|
64
|
+
log_workflow("instruction_queued", queue_size: @instruction_queue.length)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
workflow_signal
|
|
68
|
+
def escalate_model
|
|
69
|
+
@escalate_requested = true
|
|
70
|
+
log_workflow("escalation_requested", current_iteration: @iteration)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Main workflow execution
|
|
74
|
+
def execute(input)
|
|
75
|
+
initialize_state(input)
|
|
76
|
+
log_workflow("started",
|
|
77
|
+
step_name: @step_name,
|
|
78
|
+
max_iterations: @max_iterations,
|
|
79
|
+
project_dir: @project_dir)
|
|
80
|
+
|
|
81
|
+
begin
|
|
82
|
+
result = run_work_loop
|
|
83
|
+
build_result(result)
|
|
84
|
+
rescue Temporalio::Error::CanceledError
|
|
85
|
+
log_workflow("canceled", state: @state, iteration: @iteration)
|
|
86
|
+
build_canceled_result
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def initialize_state(input)
|
|
93
|
+
@project_dir = input[:project_dir]
|
|
94
|
+
@step_name = input[:step_name]
|
|
95
|
+
@step_spec = input[:step_spec]
|
|
96
|
+
@context = input[:context] || {}
|
|
97
|
+
@max_iterations = input[:max_iterations] || 50
|
|
98
|
+
@checkpoint_interval = input[:checkpoint_interval] || 5
|
|
99
|
+
|
|
100
|
+
@state = :ready
|
|
101
|
+
@iteration = 0
|
|
102
|
+
@paused = false
|
|
103
|
+
@escalate_requested = false
|
|
104
|
+
@consecutive_failures = 0
|
|
105
|
+
@last_test_results = nil
|
|
106
|
+
@instruction_queue = []
|
|
107
|
+
@started_at = Time.now.iso8601
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def run_work_loop
|
|
111
|
+
# Create initial prompt
|
|
112
|
+
transition_to(:initializing)
|
|
113
|
+
prompt_result = create_initial_prompt
|
|
114
|
+
|
|
115
|
+
unless prompt_result[:success]
|
|
116
|
+
return {success: false, reason: "prompt_creation_failed", error: prompt_result[:error]}
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
loop do
|
|
120
|
+
# Handle pause signal
|
|
121
|
+
while @paused
|
|
122
|
+
workflow_sleep(1)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Check cancellation
|
|
126
|
+
return {success: false, reason: "canceled"} if cancellation_requested?
|
|
127
|
+
|
|
128
|
+
@iteration += 1
|
|
129
|
+
log_workflow("iteration_start", iteration: @iteration)
|
|
130
|
+
|
|
131
|
+
# Check iteration limit
|
|
132
|
+
if @iteration > @max_iterations
|
|
133
|
+
log_workflow("max_iterations_reached", max: @max_iterations)
|
|
134
|
+
return {success: false, reason: "max_iterations", iterations: @iteration}
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# APPLY_PATCH: Send prompt to agent
|
|
138
|
+
transition_to(:apply_patch)
|
|
139
|
+
agent_result = run_agent_activity
|
|
140
|
+
|
|
141
|
+
unless agent_result[:success]
|
|
142
|
+
@consecutive_failures += 1
|
|
143
|
+
if @consecutive_failures >= 3
|
|
144
|
+
handle_escalation
|
|
145
|
+
end
|
|
146
|
+
# Fix-forward: continue to next iteration
|
|
147
|
+
next
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# TEST: Run validation
|
|
151
|
+
transition_to(:test)
|
|
152
|
+
test_result = run_test_activity
|
|
153
|
+
@last_test_results = test_result
|
|
154
|
+
|
|
155
|
+
if test_result[:all_passing]
|
|
156
|
+
# PASS → DONE
|
|
157
|
+
transition_to(:done)
|
|
158
|
+
log_workflow("completed", iterations: @iteration)
|
|
159
|
+
return {
|
|
160
|
+
success: true,
|
|
161
|
+
iterations: @iteration,
|
|
162
|
+
test_results: test_result
|
|
163
|
+
}
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# FAIL → DIAGNOSE
|
|
167
|
+
transition_to(:diagnose)
|
|
168
|
+
handle_failure(test_result)
|
|
169
|
+
|
|
170
|
+
# Record checkpoint at intervals
|
|
171
|
+
if (@iteration % @checkpoint_interval).zero?
|
|
172
|
+
record_checkpoint
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Reset failure count on progress
|
|
176
|
+
@consecutive_failures = 0 if test_result[:partial_pass]
|
|
177
|
+
|
|
178
|
+
# NEXT_PATCH: Prepare for next iteration
|
|
179
|
+
transition_to(:next_patch)
|
|
180
|
+
prepare_next_iteration(test_result)
|
|
181
|
+
|
|
182
|
+
transition_to(:ready)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def transition_to(new_state)
|
|
187
|
+
old_state = @state
|
|
188
|
+
@state = new_state
|
|
189
|
+
log_workflow("state_transition", from: old_state, to: new_state, iteration: @iteration)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def create_initial_prompt
|
|
193
|
+
Temporalio::Workflow.execute_activity(
|
|
194
|
+
Activities::CreatePromptActivity,
|
|
195
|
+
{
|
|
196
|
+
project_dir: @project_dir,
|
|
197
|
+
step_name: @step_name,
|
|
198
|
+
step_spec: @step_spec,
|
|
199
|
+
context: @context
|
|
200
|
+
},
|
|
201
|
+
**activity_options(start_to_close_timeout: 120)
|
|
202
|
+
)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def run_agent_activity
|
|
206
|
+
instructions = drain_instruction_queue
|
|
207
|
+
|
|
208
|
+
Temporalio::Workflow.execute_activity(
|
|
209
|
+
Activities::RunAgentActivity,
|
|
210
|
+
{
|
|
211
|
+
project_dir: @project_dir,
|
|
212
|
+
step_name: @step_name,
|
|
213
|
+
iteration: @iteration,
|
|
214
|
+
injected_instructions: instructions,
|
|
215
|
+
escalate: @escalate_requested
|
|
216
|
+
},
|
|
217
|
+
**activity_options(
|
|
218
|
+
start_to_close_timeout: 900, # 15 minutes
|
|
219
|
+
heartbeat_timeout: 120 # 2 minutes
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
ensure
|
|
223
|
+
@escalate_requested = false
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def run_test_activity
|
|
227
|
+
Temporalio::Workflow.execute_activity(
|
|
228
|
+
Activities::RunTestsActivity,
|
|
229
|
+
{
|
|
230
|
+
project_dir: @project_dir,
|
|
231
|
+
iteration: @iteration
|
|
232
|
+
},
|
|
233
|
+
**activity_options(
|
|
234
|
+
start_to_close_timeout: 600, # 10 minutes
|
|
235
|
+
heartbeat_timeout: 60
|
|
236
|
+
)
|
|
237
|
+
)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def handle_failure(test_result)
|
|
241
|
+
Temporalio::Workflow.execute_activity(
|
|
242
|
+
Activities::DiagnoseFailureActivity,
|
|
243
|
+
{
|
|
244
|
+
project_dir: @project_dir,
|
|
245
|
+
iteration: @iteration,
|
|
246
|
+
test_result: test_result
|
|
247
|
+
},
|
|
248
|
+
**activity_options(start_to_close_timeout: 120)
|
|
249
|
+
)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def prepare_next_iteration(test_result)
|
|
253
|
+
Temporalio::Workflow.execute_activity(
|
|
254
|
+
Activities::PrepareNextIterationActivity,
|
|
255
|
+
{
|
|
256
|
+
project_dir: @project_dir,
|
|
257
|
+
iteration: @iteration,
|
|
258
|
+
test_result: test_result,
|
|
259
|
+
failures_only: true
|
|
260
|
+
},
|
|
261
|
+
**activity_options(start_to_close_timeout: 60)
|
|
262
|
+
)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def record_checkpoint
|
|
266
|
+
Temporalio::Workflow.execute_activity(
|
|
267
|
+
Activities::RecordCheckpointActivity,
|
|
268
|
+
{
|
|
269
|
+
project_dir: @project_dir,
|
|
270
|
+
step_name: @step_name,
|
|
271
|
+
iteration: @iteration,
|
|
272
|
+
state: @state,
|
|
273
|
+
test_results: @last_test_results
|
|
274
|
+
},
|
|
275
|
+
**activity_options(start_to_close_timeout: 30)
|
|
276
|
+
)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def handle_escalation
|
|
280
|
+
log_workflow("escalating_model", consecutive_failures: @consecutive_failures)
|
|
281
|
+
@escalate_requested = true
|
|
282
|
+
@consecutive_failures = 0
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def drain_instruction_queue
|
|
286
|
+
instructions = @instruction_queue.dup
|
|
287
|
+
@instruction_queue.clear
|
|
288
|
+
instructions
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def activity_options(overrides = {})
|
|
292
|
+
self.class.activity_options(overrides)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def build_result(result)
|
|
296
|
+
{
|
|
297
|
+
status: result[:success] ? "completed" : "failed",
|
|
298
|
+
step_name: @step_name,
|
|
299
|
+
iterations: @iteration,
|
|
300
|
+
success: result[:success],
|
|
301
|
+
reason: result[:reason],
|
|
302
|
+
test_results: result[:test_results],
|
|
303
|
+
started_at: @started_at,
|
|
304
|
+
completed_at: Time.now.iso8601
|
|
305
|
+
}
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def build_canceled_result
|
|
309
|
+
{
|
|
310
|
+
status: "canceled",
|
|
311
|
+
step_name: @step_name,
|
|
312
|
+
iterations: @iteration,
|
|
313
|
+
state: @state,
|
|
314
|
+
started_at: @started_at,
|
|
315
|
+
completed_at: Time.now.iso8601
|
|
316
|
+
}
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Temporal integration for AIDP
|
|
4
|
+
# Provides durable workflow orchestration for issue-to-PR pipelines
|
|
5
|
+
#
|
|
6
|
+
# Components:
|
|
7
|
+
# - Connection: Manages Temporal client connections
|
|
8
|
+
# - Worker: Runs workflows and activities
|
|
9
|
+
# - WorkflowClient: Interface for starting/managing workflows
|
|
10
|
+
# - Configuration: Loads Temporal settings from aidp.yml
|
|
11
|
+
#
|
|
12
|
+
# Workflows:
|
|
13
|
+
# - IssueToPrWorkflow: Full issue analysis → implementation → PR pipeline
|
|
14
|
+
# - WorkLoopWorkflow: Fix-forward iteration pattern
|
|
15
|
+
# - SubIssueWorkflow: Child workflow for decomposed tasks
|
|
16
|
+
#
|
|
17
|
+
# Activities:
|
|
18
|
+
# - RunAgentActivity: Execute AI agent
|
|
19
|
+
# - RunTestsActivity: Run tests/linters
|
|
20
|
+
# - AnalyzeIssueActivity: Analyze GitHub issue
|
|
21
|
+
# - CreatePlanActivity: Generate implementation plan
|
|
22
|
+
# - CreatePromptActivity: Build PROMPT.md
|
|
23
|
+
# - DiagnoseFailureActivity: Analyze failures
|
|
24
|
+
# - PrepareNextIterationActivity: Set up next iteration
|
|
25
|
+
# - RecordCheckpointActivity: Save progress
|
|
26
|
+
# - CreatePrActivity: Create GitHub PR
|
|
27
|
+
|
|
28
|
+
module Aidp
|
|
29
|
+
module Temporal
|
|
30
|
+
class << self
|
|
31
|
+
# Get the default configuration
|
|
32
|
+
def configuration(project_dir = Dir.pwd)
|
|
33
|
+
@configurations ||= {}
|
|
34
|
+
@configurations[project_dir] ||= Configuration.new(project_dir)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Check if Temporal is enabled
|
|
38
|
+
def enabled?(project_dir = Dir.pwd)
|
|
39
|
+
configuration(project_dir).enabled?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Build a connection with project configuration
|
|
43
|
+
def connection(project_dir = Dir.pwd)
|
|
44
|
+
configuration(project_dir).build_connection
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Build a workflow client with project configuration
|
|
48
|
+
def workflow_client(project_dir = Dir.pwd)
|
|
49
|
+
configuration(project_dir).build_workflow_client
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Build a worker with project configuration
|
|
53
|
+
def worker(project_dir = Dir.pwd)
|
|
54
|
+
configuration(project_dir).build_worker
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Start a workflow
|
|
58
|
+
def start_workflow(workflow_class, input, project_dir: Dir.pwd, **options)
|
|
59
|
+
client = workflow_client(project_dir)
|
|
60
|
+
client.start_workflow(workflow_class, input, options)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Execute a workflow synchronously
|
|
64
|
+
def execute_workflow(workflow_class, input, project_dir: Dir.pwd, **options)
|
|
65
|
+
client = workflow_client(project_dir)
|
|
66
|
+
client.execute_workflow(workflow_class, input, options)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Get workflow status
|
|
70
|
+
def workflow_status(workflow_id, project_dir: Dir.pwd)
|
|
71
|
+
client = workflow_client(project_dir)
|
|
72
|
+
handle = client.get_workflow(workflow_id)
|
|
73
|
+
handle.describe
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Signal a workflow
|
|
77
|
+
def signal_workflow(workflow_id, signal_name, *args, project_dir: Dir.pwd)
|
|
78
|
+
client = workflow_client(project_dir)
|
|
79
|
+
client.signal_workflow(workflow_id, signal_name, *args)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Cancel a workflow
|
|
83
|
+
def cancel_workflow(workflow_id, project_dir: Dir.pwd)
|
|
84
|
+
client = workflow_client(project_dir)
|
|
85
|
+
client.cancel_workflow(workflow_id)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Reset configuration cache (useful for testing)
|
|
89
|
+
def reset!
|
|
90
|
+
@configurations = {}
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Require components in dependency order
|
|
97
|
+
require_relative "temporal/configuration"
|
|
98
|
+
require_relative "temporal/connection"
|
|
99
|
+
require_relative "temporal/worker"
|
|
100
|
+
require_relative "temporal/workflow_client"
|
|
101
|
+
|
|
102
|
+
# Require workflows
|
|
103
|
+
require_relative "temporal/workflows/base_workflow"
|
|
104
|
+
require_relative "temporal/workflows/issue_to_pr_workflow"
|
|
105
|
+
require_relative "temporal/workflows/work_loop_workflow"
|
|
106
|
+
require_relative "temporal/workflows/sub_issue_workflow"
|
|
107
|
+
|
|
108
|
+
# Require activities
|
|
109
|
+
require_relative "temporal/activities/base_activity"
|
|
110
|
+
require_relative "temporal/activities/run_agent_activity"
|
|
111
|
+
require_relative "temporal/activities/run_tests_activity"
|
|
112
|
+
require_relative "temporal/activities/analyze_issue_activity"
|
|
113
|
+
require_relative "temporal/activities/create_plan_activity"
|
|
114
|
+
require_relative "temporal/activities/create_prompt_activity"
|
|
115
|
+
require_relative "temporal/activities/diagnose_failure_activity"
|
|
116
|
+
require_relative "temporal/activities/prepare_next_iteration_activity"
|
|
117
|
+
require_relative "temporal/activities/record_checkpoint_activity"
|
|
118
|
+
require_relative "temporal/activities/create_pr_activity"
|
|
119
|
+
require_relative "temporal/activities/run_work_loop_iteration_activity"
|
|
120
|
+
require_relative "temporal/activities/analyze_sub_task_activity"
|
data/lib/aidp/version.rb
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aidp
|
|
4
|
+
module Watch
|
|
5
|
+
# Base class for Watch Mode processors
|
|
6
|
+
class BaseProcessor
|
|
7
|
+
def initialize(repository_client:, state_store: nil, label_config: {}, verbose: false)
|
|
8
|
+
@repository_client = repository_client
|
|
9
|
+
@state_store = state_store
|
|
10
|
+
@verbose = verbose
|
|
11
|
+
|
|
12
|
+
# Only use string or symbol access for label_config
|
|
13
|
+
@rebase_label = label_config[:rebase_trigger] ||
|
|
14
|
+
label_config["rebase_trigger"] ||
|
|
15
|
+
"aidp-rebase"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Checks if this processor can handle the current work item
|
|
19
|
+
# @param work_item [WorkItem] The work item to check
|
|
20
|
+
# @return [Boolean] Whether the work item can be processed
|
|
21
|
+
def can_process?(work_item)
|
|
22
|
+
raise NotImplementedError, "Subclasses must implement can_process?"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Process the work item
|
|
26
|
+
# @param work_item [WorkItem] The work item to process
|
|
27
|
+
# @return [Boolean] Whether the work item was processed successfully
|
|
28
|
+
def process(work_item)
|
|
29
|
+
raise NotImplementedError, "Subclasses must implement process"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
attr_reader :rebase_label
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -67,6 +67,17 @@ module Aidp
|
|
|
67
67
|
|
|
68
68
|
sync_local_aidp_config(working_dir)
|
|
69
69
|
|
|
70
|
+
# Check for stuck work loop: work appears done but protocol wasn't followed
|
|
71
|
+
if work_loop_stuck_but_complete?(working_dir: working_dir, base_branch: base_branch)
|
|
72
|
+
display_message("🔧 Detected stuck work loop - work appears complete, bypassing harness", type: :info)
|
|
73
|
+
Aidp.log_info("build_processor", "stuck_work_loop_detected",
|
|
74
|
+
issue: number, working_dir: working_dir)
|
|
75
|
+
|
|
76
|
+
# Skip harness and go directly to success handling
|
|
77
|
+
handle_success(issue: issue, slug: slug, branch_name: branch_name, base_branch: base_branch, plan_data: plan_data, working_dir: working_dir)
|
|
78
|
+
return
|
|
79
|
+
end
|
|
80
|
+
|
|
70
81
|
prompt_content = build_prompt(issue: issue, plan_data: plan_data)
|
|
71
82
|
write_prompt(prompt_content, working_dir: working_dir)
|
|
72
83
|
|
|
@@ -1023,6 +1034,127 @@ module Aidp
|
|
|
1023
1034
|
string_key = key.to_s
|
|
1024
1035
|
plan_data[symbol_key] || plan_data[string_key]
|
|
1025
1036
|
end
|
|
1037
|
+
|
|
1038
|
+
# Detect if work loop is stuck but work appears complete.
|
|
1039
|
+
# This handles cases where the agent did the work but didn't follow the protocol
|
|
1040
|
+
# (e.g., didn't mark STATUS: COMPLETE in .aidp/PROMPT.md, didn't file tasks).
|
|
1041
|
+
#
|
|
1042
|
+
# Conditions for "stuck but complete":
|
|
1043
|
+
# 1. Has commits ahead of base branch (work was done)
|
|
1044
|
+
# 2. Tests pass (work is valid)
|
|
1045
|
+
# 3. Linters pass (code quality is acceptable)
|
|
1046
|
+
# 4. .aidp/PROMPT.md exists but doesn't have STATUS: COMPLETE (protocol not followed)
|
|
1047
|
+
#
|
|
1048
|
+
# @param working_dir [String] Path to the working directory
|
|
1049
|
+
# @param base_branch [String] Base branch to compare against
|
|
1050
|
+
# @return [Boolean] True if work loop appears stuck but work is complete
|
|
1051
|
+
def work_loop_stuck_but_complete?(working_dir:, base_branch:)
|
|
1052
|
+
Aidp.log_debug("build_processor", "checking_stuck_work_loop", working_dir: working_dir)
|
|
1053
|
+
|
|
1054
|
+
# Check 1: Has commits ahead of base branch
|
|
1055
|
+
unless has_commits_ahead?(working_dir: working_dir, base_branch: base_branch)
|
|
1056
|
+
Aidp.log_debug("build_processor", "stuck_check_no_commits_ahead")
|
|
1057
|
+
return false
|
|
1058
|
+
end
|
|
1059
|
+
|
|
1060
|
+
# Check 2: .aidp/PROMPT.md exists but doesn't have STATUS: COMPLETE
|
|
1061
|
+
prompt_path = File.join(working_dir, ".aidp", "PROMPT.md")
|
|
1062
|
+
if File.exist?(prompt_path)
|
|
1063
|
+
prompt_content = File.read(prompt_path)
|
|
1064
|
+
if prompt_content.match?(/^STATUS:\s*COMPLETE/i)
|
|
1065
|
+
# Protocol was followed, not stuck
|
|
1066
|
+
Aidp.log_debug("build_processor", "stuck_check_already_marked_complete")
|
|
1067
|
+
return false
|
|
1068
|
+
end
|
|
1069
|
+
else
|
|
1070
|
+
# No prompt file means this is a fresh start, not stuck
|
|
1071
|
+
Aidp.log_debug("build_processor", "stuck_check_no_prompt_file")
|
|
1072
|
+
return false
|
|
1073
|
+
end
|
|
1074
|
+
|
|
1075
|
+
# Check 3: Run quick validation (tests + linters)
|
|
1076
|
+
unless quick_validation_passes?(working_dir: working_dir)
|
|
1077
|
+
Aidp.log_debug("build_processor", "stuck_check_validation_failed")
|
|
1078
|
+
return false
|
|
1079
|
+
end
|
|
1080
|
+
|
|
1081
|
+
Aidp.log_info("build_processor", "work_loop_stuck_but_complete",
|
|
1082
|
+
working_dir: working_dir,
|
|
1083
|
+
reason: "has_commits_tests_pass_but_no_status_complete")
|
|
1084
|
+
true
|
|
1085
|
+
rescue => e
|
|
1086
|
+
Aidp.log_warn("build_processor", "stuck_check_error", error: e.message)
|
|
1087
|
+
false
|
|
1088
|
+
end
|
|
1089
|
+
|
|
1090
|
+
# Check if branch has commits ahead of base branch
|
|
1091
|
+
def has_commits_ahead?(working_dir:, base_branch:)
|
|
1092
|
+
Dir.chdir(working_dir) do
|
|
1093
|
+
# Fetch to ensure we have latest refs (use Open3 directly to avoid test mocks)
|
|
1094
|
+
Open3.capture3("git", "fetch", "origin")
|
|
1095
|
+
|
|
1096
|
+
# Count commits ahead of origin/base_branch
|
|
1097
|
+
stdout, _stderr, status = Open3.capture3(
|
|
1098
|
+
"git", "rev-list", "--count", "HEAD", "^origin/#{base_branch}"
|
|
1099
|
+
)
|
|
1100
|
+
return false unless status.success?
|
|
1101
|
+
|
|
1102
|
+
commits_ahead = stdout.strip.to_i
|
|
1103
|
+
Aidp.log_debug("build_processor", "commits_ahead_check",
|
|
1104
|
+
base_branch: base_branch, commits_ahead: commits_ahead)
|
|
1105
|
+
commits_ahead > 0
|
|
1106
|
+
end
|
|
1107
|
+
rescue => e
|
|
1108
|
+
Aidp.log_warn("build_processor", "commits_ahead_check_error", error: e.message)
|
|
1109
|
+
false
|
|
1110
|
+
end
|
|
1111
|
+
|
|
1112
|
+
# Run quick validation to check if tests and linters pass
|
|
1113
|
+
def quick_validation_passes?(working_dir:)
|
|
1114
|
+
Dir.chdir(working_dir) do
|
|
1115
|
+
# Load config to get test/lint commands
|
|
1116
|
+
config_manager = Aidp::Harness::ConfigManager.new(working_dir)
|
|
1117
|
+
harness_config = config_manager.config || {}
|
|
1118
|
+
|
|
1119
|
+
# Try to run tests
|
|
1120
|
+
test_commands = harness_config.dig(:harness, :tests) ||
|
|
1121
|
+
harness_config.dig("harness", "tests") || []
|
|
1122
|
+
|
|
1123
|
+
test_commands.each do |cmd|
|
|
1124
|
+
command = cmd.is_a?(Hash) ? (cmd[:command] || cmd["command"]) : cmd.to_s
|
|
1125
|
+
next if command.nil? || command.empty?
|
|
1126
|
+
|
|
1127
|
+
Aidp.log_debug("build_processor", "quick_validation_running_test", command: command)
|
|
1128
|
+
_stdout, _stderr, status = Open3.capture3(command)
|
|
1129
|
+
unless status.success?
|
|
1130
|
+
Aidp.log_debug("build_processor", "quick_validation_test_failed", command: command)
|
|
1131
|
+
return false
|
|
1132
|
+
end
|
|
1133
|
+
end
|
|
1134
|
+
|
|
1135
|
+
# Try to run linters
|
|
1136
|
+
lint_commands = harness_config.dig(:harness, :linters) ||
|
|
1137
|
+
harness_config.dig("harness", "linters") || []
|
|
1138
|
+
|
|
1139
|
+
lint_commands.each do |cmd|
|
|
1140
|
+
command = cmd.is_a?(Hash) ? (cmd[:command] || cmd["command"]) : cmd.to_s
|
|
1141
|
+
next if command.nil? || command.empty?
|
|
1142
|
+
|
|
1143
|
+
Aidp.log_debug("build_processor", "quick_validation_running_lint", command: command)
|
|
1144
|
+
_stdout, _stderr, status = Open3.capture3(command)
|
|
1145
|
+
unless status.success?
|
|
1146
|
+
Aidp.log_debug("build_processor", "quick_validation_lint_failed", command: command)
|
|
1147
|
+
return false
|
|
1148
|
+
end
|
|
1149
|
+
end
|
|
1150
|
+
|
|
1151
|
+
Aidp.log_debug("build_processor", "quick_validation_passed")
|
|
1152
|
+
true
|
|
1153
|
+
end
|
|
1154
|
+
rescue => e
|
|
1155
|
+
Aidp.log_warn("build_processor", "quick_validation_error", error: e.message)
|
|
1156
|
+
false
|
|
1157
|
+
end
|
|
1026
1158
|
end
|
|
1027
1159
|
end
|
|
1028
1160
|
end
|
|
@@ -17,18 +17,10 @@ module Aidp
|
|
|
17
17
|
# Pattern for detecting plan proposal comments
|
|
18
18
|
PLAN_PROPOSAL_PATTERN = /<!-- PLAN_SUMMARY_START -->/i
|
|
19
19
|
|
|
20
|
-
# Pattern for detecting in-progress label
|
|
21
|
-
IN_PROGRESS_LABEL = "aidp-in-progress"
|
|
22
|
-
|
|
23
20
|
def initialize(repository_client:)
|
|
24
21
|
@repository_client = repository_client
|
|
25
22
|
end
|
|
26
23
|
|
|
27
|
-
# Check if an issue/PR is currently being worked on by another instance
|
|
28
|
-
def in_progress?(item)
|
|
29
|
-
has_label?(item, IN_PROGRESS_LABEL)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
24
|
# Check if build is already completed for an issue
|
|
33
25
|
# Looks for completion comment from AIDP
|
|
34
26
|
def build_completed?(issue)
|