aidp 0.34.0 → 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/ui.rb +11 -0
- 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 +118 -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,278 @@
|
|
|
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 orchestrates the full issue-to-PR pipeline
|
|
10
|
+
# Handles: issue analysis, planning, implementation, testing, and PR creation
|
|
11
|
+
#
|
|
12
|
+
# State machine:
|
|
13
|
+
# INIT → ANALYZE → PLAN → IMPLEMENT → TEST → {PASS → CREATE_PR | FAIL → IMPLEMENT} → COMPLETE
|
|
14
|
+
class IssueToPrWorkflow < BaseWorkflow
|
|
15
|
+
# Query handlers for workflow state
|
|
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 progress
|
|
28
|
+
{
|
|
29
|
+
state: @state,
|
|
30
|
+
iteration: @iteration,
|
|
31
|
+
max_iterations: @max_iterations,
|
|
32
|
+
issue_number: @issue_number,
|
|
33
|
+
started_at: @started_at,
|
|
34
|
+
last_activity: @last_activity
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Signal handlers for external control
|
|
39
|
+
workflow_signal
|
|
40
|
+
def pause
|
|
41
|
+
@paused = true
|
|
42
|
+
log_workflow("paused")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
workflow_signal
|
|
46
|
+
def resume
|
|
47
|
+
@paused = false
|
|
48
|
+
log_workflow("resumed")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
workflow_signal
|
|
52
|
+
def inject_instruction(instruction)
|
|
53
|
+
@injected_instructions ||= []
|
|
54
|
+
@injected_instructions << instruction
|
|
55
|
+
log_workflow("instruction_injected", instruction_length: instruction.length)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Main workflow execution
|
|
59
|
+
def execute(input)
|
|
60
|
+
initialize_state(input)
|
|
61
|
+
log_workflow("started",
|
|
62
|
+
issue_number: @issue_number,
|
|
63
|
+
project_dir: @project_dir,
|
|
64
|
+
max_iterations: @max_iterations)
|
|
65
|
+
|
|
66
|
+
begin
|
|
67
|
+
# Phase 1: Analyze the issue
|
|
68
|
+
transition_to(:analyzing)
|
|
69
|
+
analysis = run_analysis_phase
|
|
70
|
+
|
|
71
|
+
return build_error_result("Analysis failed") unless analysis[:success]
|
|
72
|
+
|
|
73
|
+
# Phase 2: Create implementation plan
|
|
74
|
+
transition_to(:planning)
|
|
75
|
+
plan = run_planning_phase(analysis)
|
|
76
|
+
|
|
77
|
+
return build_error_result("Planning failed") unless plan[:success]
|
|
78
|
+
|
|
79
|
+
# Phase 3: Implementation loop (fix-forward pattern)
|
|
80
|
+
transition_to(:implementing)
|
|
81
|
+
implementation = run_implementation_loop(plan)
|
|
82
|
+
|
|
83
|
+
return build_error_result("Implementation failed: max iterations reached") unless implementation[:success]
|
|
84
|
+
|
|
85
|
+
# Phase 4: Create PR
|
|
86
|
+
transition_to(:creating_pr)
|
|
87
|
+
pr_result = run_create_pr_phase(implementation)
|
|
88
|
+
|
|
89
|
+
transition_to(:completed)
|
|
90
|
+
build_success_result(pr_result)
|
|
91
|
+
rescue Temporalio::Error::CanceledError
|
|
92
|
+
log_workflow("canceled", state: @state, iteration: @iteration)
|
|
93
|
+
transition_to(:canceled)
|
|
94
|
+
build_canceled_result
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
def initialize_state(input)
|
|
101
|
+
@project_dir = input[:project_dir]
|
|
102
|
+
@issue_number = input[:issue_number]
|
|
103
|
+
@issue_url = input[:issue_url]
|
|
104
|
+
@max_iterations = input[:max_iterations] || 50
|
|
105
|
+
@options = input[:options] || {}
|
|
106
|
+
|
|
107
|
+
@state = :init
|
|
108
|
+
@iteration = 0
|
|
109
|
+
@paused = false
|
|
110
|
+
@started_at = Time.now.iso8601
|
|
111
|
+
@last_activity = nil
|
|
112
|
+
@injected_instructions = []
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def transition_to(new_state)
|
|
116
|
+
old_state = @state
|
|
117
|
+
@state = new_state
|
|
118
|
+
log_workflow("state_transition", from: old_state, to: new_state)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def run_analysis_phase
|
|
122
|
+
@last_activity = :analyze_issue
|
|
123
|
+
|
|
124
|
+
result = Temporalio::Workflow.execute_activity(
|
|
125
|
+
Activities::AnalyzeIssueActivity,
|
|
126
|
+
{
|
|
127
|
+
project_dir: @project_dir,
|
|
128
|
+
issue_number: @issue_number,
|
|
129
|
+
issue_url: @issue_url
|
|
130
|
+
},
|
|
131
|
+
**activity_options(start_to_close_timeout: 300)
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
log_workflow("analysis_complete", success: result[:success])
|
|
135
|
+
result
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def run_planning_phase(analysis)
|
|
139
|
+
@last_activity = :create_plan
|
|
140
|
+
|
|
141
|
+
result = Temporalio::Workflow.execute_activity(
|
|
142
|
+
Activities::CreatePlanActivity,
|
|
143
|
+
{
|
|
144
|
+
project_dir: @project_dir,
|
|
145
|
+
issue_number: @issue_number,
|
|
146
|
+
analysis: analysis[:result]
|
|
147
|
+
},
|
|
148
|
+
**activity_options(start_to_close_timeout: 300)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
log_workflow("planning_complete", success: result[:success])
|
|
152
|
+
result
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def run_implementation_loop(plan)
|
|
156
|
+
loop do
|
|
157
|
+
# Check for pause signal
|
|
158
|
+
while @paused
|
|
159
|
+
workflow_sleep(1)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Check for cancellation
|
|
163
|
+
return build_canceled_result if cancellation_requested?
|
|
164
|
+
|
|
165
|
+
@iteration += 1
|
|
166
|
+
log_workflow("implementation_iteration", iteration: @iteration, max: @max_iterations)
|
|
167
|
+
|
|
168
|
+
if @iteration > @max_iterations
|
|
169
|
+
return {success: false, reason: "max_iterations_exceeded", iteration: @iteration}
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Run single implementation iteration
|
|
173
|
+
@last_activity = :run_work_loop_iteration
|
|
174
|
+
|
|
175
|
+
iteration_input = {
|
|
176
|
+
project_dir: @project_dir,
|
|
177
|
+
issue_number: @issue_number,
|
|
178
|
+
plan: plan[:result],
|
|
179
|
+
iteration: @iteration,
|
|
180
|
+
injected_instructions: drain_injected_instructions
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
result = Temporalio::Workflow.execute_activity(
|
|
184
|
+
Activities::RunWorkLoopIterationActivity,
|
|
185
|
+
iteration_input,
|
|
186
|
+
**activity_options(
|
|
187
|
+
start_to_close_timeout: 900, # 15 minutes per iteration
|
|
188
|
+
heartbeat_timeout: 120 # 2 minute heartbeat
|
|
189
|
+
)
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
log_workflow("iteration_complete",
|
|
193
|
+
iteration: @iteration,
|
|
194
|
+
success: result[:success],
|
|
195
|
+
tests_passing: result[:tests_passing])
|
|
196
|
+
|
|
197
|
+
# Check completion
|
|
198
|
+
if result[:success] && result[:tests_passing]
|
|
199
|
+
return {
|
|
200
|
+
success: true,
|
|
201
|
+
iterations: @iteration,
|
|
202
|
+
result: result[:result]
|
|
203
|
+
}
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Continue to next iteration (fix-forward)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def run_create_pr_phase(implementation)
|
|
211
|
+
@last_activity = :create_pr
|
|
212
|
+
|
|
213
|
+
result = Temporalio::Workflow.execute_activity(
|
|
214
|
+
Activities::CreatePrActivity,
|
|
215
|
+
{
|
|
216
|
+
project_dir: @project_dir,
|
|
217
|
+
issue_number: @issue_number,
|
|
218
|
+
implementation: implementation[:result],
|
|
219
|
+
iterations: implementation[:iterations]
|
|
220
|
+
},
|
|
221
|
+
**activity_options(start_to_close_timeout: 300)
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
log_workflow("pr_created",
|
|
225
|
+
success: result[:success],
|
|
226
|
+
pr_url: result[:pr_url])
|
|
227
|
+
|
|
228
|
+
result
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def drain_injected_instructions
|
|
232
|
+
instructions = @injected_instructions.dup
|
|
233
|
+
@injected_instructions.clear
|
|
234
|
+
instructions
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def activity_options(overrides = {})
|
|
238
|
+
self.class.activity_options(overrides)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def build_success_result(pr_result)
|
|
242
|
+
{
|
|
243
|
+
status: "completed",
|
|
244
|
+
issue_number: @issue_number,
|
|
245
|
+
pr_url: pr_result[:pr_url],
|
|
246
|
+
pr_number: pr_result[:pr_number],
|
|
247
|
+
iterations: @iteration,
|
|
248
|
+
started_at: @started_at,
|
|
249
|
+
completed_at: Time.now.iso8601
|
|
250
|
+
}
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def build_error_result(message)
|
|
254
|
+
{
|
|
255
|
+
status: "error",
|
|
256
|
+
issue_number: @issue_number,
|
|
257
|
+
error: message,
|
|
258
|
+
state: @state,
|
|
259
|
+
iteration: @iteration,
|
|
260
|
+
started_at: @started_at,
|
|
261
|
+
completed_at: Time.now.iso8601
|
|
262
|
+
}
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def build_canceled_result
|
|
266
|
+
{
|
|
267
|
+
status: "canceled",
|
|
268
|
+
issue_number: @issue_number,
|
|
269
|
+
state: @state,
|
|
270
|
+
iteration: @iteration,
|
|
271
|
+
started_at: @started_at,
|
|
272
|
+
completed_at: Time.now.iso8601
|
|
273
|
+
}
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
@@ -0,0 +1,253 @@
|
|
|
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
|
+
# Child workflow for handling decomposed sub-tasks
|
|
10
|
+
# Supports recursive decomposition when a task is too large
|
|
11
|
+
#
|
|
12
|
+
# Used by IssueToPrWorkflow to break down complex issues
|
|
13
|
+
# into smaller, manageable sub-tasks executed in parallel
|
|
14
|
+
class SubIssueWorkflow < BaseWorkflow
|
|
15
|
+
# Maximum depth for recursive decomposition
|
|
16
|
+
MAX_RECURSION_DEPTH = 3
|
|
17
|
+
|
|
18
|
+
# Query handlers
|
|
19
|
+
workflow_query
|
|
20
|
+
def current_state
|
|
21
|
+
@state
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
workflow_query
|
|
25
|
+
def progress
|
|
26
|
+
{
|
|
27
|
+
state: @state,
|
|
28
|
+
sub_issue_id: @sub_issue_id,
|
|
29
|
+
parent_workflow_id: @parent_workflow_id,
|
|
30
|
+
depth: @depth,
|
|
31
|
+
iteration: @iteration,
|
|
32
|
+
started_at: @started_at
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Signal handlers
|
|
37
|
+
workflow_signal
|
|
38
|
+
def pause
|
|
39
|
+
@paused = true
|
|
40
|
+
log_workflow("paused")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
workflow_signal
|
|
44
|
+
def resume
|
|
45
|
+
@paused = false
|
|
46
|
+
log_workflow("resumed")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Main workflow execution
|
|
50
|
+
def execute(input)
|
|
51
|
+
initialize_state(input)
|
|
52
|
+
log_workflow("started",
|
|
53
|
+
sub_issue_id: @sub_issue_id,
|
|
54
|
+
depth: @depth,
|
|
55
|
+
parent: @parent_workflow_id)
|
|
56
|
+
|
|
57
|
+
# Check recursion depth limit
|
|
58
|
+
if @depth >= MAX_RECURSION_DEPTH
|
|
59
|
+
log_workflow("max_depth_reached", depth: @depth)
|
|
60
|
+
return build_error_result("Maximum recursion depth reached")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
begin
|
|
64
|
+
# Analyze the sub-task
|
|
65
|
+
transition_to(:analyzing)
|
|
66
|
+
analysis = analyze_sub_task
|
|
67
|
+
|
|
68
|
+
return build_error_result("Analysis failed") unless analysis[:success]
|
|
69
|
+
|
|
70
|
+
# Check if further decomposition needed
|
|
71
|
+
if needs_decomposition?(analysis)
|
|
72
|
+
transition_to(:decomposing)
|
|
73
|
+
result = execute_child_workflows(analysis)
|
|
74
|
+
else
|
|
75
|
+
# Execute directly
|
|
76
|
+
transition_to(:implementing)
|
|
77
|
+
result = execute_implementation(analysis)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
transition_to(:completed)
|
|
81
|
+
build_success_result(result)
|
|
82
|
+
rescue Temporalio::Error::CanceledError
|
|
83
|
+
log_workflow("canceled", state: @state)
|
|
84
|
+
transition_to(:canceled)
|
|
85
|
+
build_canceled_result
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def initialize_state(input)
|
|
92
|
+
@project_dir = input[:project_dir]
|
|
93
|
+
@sub_issue_id = input[:sub_issue_id]
|
|
94
|
+
@task_description = input[:task_description]
|
|
95
|
+
@context = input[:context] || {}
|
|
96
|
+
@parent_workflow_id = input[:parent_workflow_id]
|
|
97
|
+
@depth = input[:depth] || 0
|
|
98
|
+
@max_iterations = input[:max_iterations] || 20
|
|
99
|
+
|
|
100
|
+
@state = :init
|
|
101
|
+
@iteration = 0
|
|
102
|
+
@paused = false
|
|
103
|
+
@started_at = Time.now.iso8601
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def transition_to(new_state)
|
|
107
|
+
old_state = @state
|
|
108
|
+
@state = new_state
|
|
109
|
+
log_workflow("state_transition", from: old_state, to: new_state)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def analyze_sub_task
|
|
113
|
+
Temporalio::Workflow.execute_activity(
|
|
114
|
+
Activities::AnalyzeSubTaskActivity,
|
|
115
|
+
{
|
|
116
|
+
project_dir: @project_dir,
|
|
117
|
+
sub_issue_id: @sub_issue_id,
|
|
118
|
+
task_description: @task_description,
|
|
119
|
+
context: @context
|
|
120
|
+
},
|
|
121
|
+
**activity_options(start_to_close_timeout: 120)
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def needs_decomposition?(analysis)
|
|
126
|
+
# Heuristics for when to decompose further
|
|
127
|
+
result = analysis[:result] || {}
|
|
128
|
+
|
|
129
|
+
# Check estimated complexity
|
|
130
|
+
estimated_iterations = result[:estimated_iterations] || 0
|
|
131
|
+
return true if estimated_iterations > @max_iterations
|
|
132
|
+
|
|
133
|
+
# Check if multiple independent tasks identified
|
|
134
|
+
sub_tasks = result[:sub_tasks] || []
|
|
135
|
+
return true if sub_tasks.length >= 3
|
|
136
|
+
|
|
137
|
+
false
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def execute_child_workflows(analysis)
|
|
141
|
+
sub_tasks = analysis[:result][:sub_tasks] || []
|
|
142
|
+
|
|
143
|
+
log_workflow("spawning_children",
|
|
144
|
+
count: sub_tasks.length,
|
|
145
|
+
depth: @depth + 1)
|
|
146
|
+
|
|
147
|
+
# Start child workflows in parallel
|
|
148
|
+
child_handles = sub_tasks.map.with_index do |task, idx|
|
|
149
|
+
child_id = "#{@sub_issue_id}_sub_#{idx}"
|
|
150
|
+
|
|
151
|
+
Temporalio::Workflow.execute_child_workflow(
|
|
152
|
+
SubIssueWorkflow,
|
|
153
|
+
{
|
|
154
|
+
project_dir: @project_dir,
|
|
155
|
+
sub_issue_id: child_id,
|
|
156
|
+
task_description: task[:description],
|
|
157
|
+
context: @context.merge(parent_task: @task_description),
|
|
158
|
+
parent_workflow_id: workflow_info.workflow_id,
|
|
159
|
+
depth: @depth + 1,
|
|
160
|
+
max_iterations: task[:estimated_iterations] || 10
|
|
161
|
+
},
|
|
162
|
+
id: child_id,
|
|
163
|
+
**child_workflow_options
|
|
164
|
+
)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Wait for all children to complete
|
|
168
|
+
results = child_handles.map(&:result)
|
|
169
|
+
|
|
170
|
+
# Aggregate results
|
|
171
|
+
{
|
|
172
|
+
strategy: :decomposed,
|
|
173
|
+
child_count: results.length,
|
|
174
|
+
all_successful: results.all? { |r| r[:status] == "completed" },
|
|
175
|
+
child_results: results
|
|
176
|
+
}
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def execute_implementation(analysis)
|
|
180
|
+
# Create work loop for this sub-task
|
|
181
|
+
work_loop_result = Temporalio::Workflow.execute_child_workflow(
|
|
182
|
+
WorkLoopWorkflow,
|
|
183
|
+
{
|
|
184
|
+
project_dir: @project_dir,
|
|
185
|
+
step_name: "sub_issue_#{@sub_issue_id}",
|
|
186
|
+
step_spec: {
|
|
187
|
+
description: @task_description
|
|
188
|
+
},
|
|
189
|
+
context: @context.merge(analysis: analysis[:result]),
|
|
190
|
+
max_iterations: @max_iterations
|
|
191
|
+
},
|
|
192
|
+
id: "workloop_#{@sub_issue_id}",
|
|
193
|
+
**child_workflow_options
|
|
194
|
+
).result
|
|
195
|
+
|
|
196
|
+
{
|
|
197
|
+
strategy: :direct,
|
|
198
|
+
work_loop_result: work_loop_result
|
|
199
|
+
}
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def activity_options(overrides = {})
|
|
203
|
+
self.class.activity_options(overrides)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def child_workflow_options
|
|
207
|
+
{
|
|
208
|
+
task_queue: Temporalio::Workflow.info.task_queue,
|
|
209
|
+
execution_timeout: 3600, # 1 hour per child
|
|
210
|
+
retry_policy: build_retry_policy(
|
|
211
|
+
initial_interval: 1,
|
|
212
|
+
maximum_attempts: 2
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def build_success_result(result)
|
|
218
|
+
{
|
|
219
|
+
status: "completed",
|
|
220
|
+
sub_issue_id: @sub_issue_id,
|
|
221
|
+
depth: @depth,
|
|
222
|
+
result: result,
|
|
223
|
+
started_at: @started_at,
|
|
224
|
+
completed_at: Time.now.iso8601
|
|
225
|
+
}
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def build_error_result(message)
|
|
229
|
+
{
|
|
230
|
+
status: "error",
|
|
231
|
+
sub_issue_id: @sub_issue_id,
|
|
232
|
+
depth: @depth,
|
|
233
|
+
error: message,
|
|
234
|
+
state: @state,
|
|
235
|
+
started_at: @started_at,
|
|
236
|
+
completed_at: Time.now.iso8601
|
|
237
|
+
}
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def build_canceled_result
|
|
241
|
+
{
|
|
242
|
+
status: "canceled",
|
|
243
|
+
sub_issue_id: @sub_issue_id,
|
|
244
|
+
depth: @depth,
|
|
245
|
+
state: @state,
|
|
246
|
+
started_at: @started_at,
|
|
247
|
+
completed_at: Time.now.iso8601
|
|
248
|
+
}
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|