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,460 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tty-prompt"
|
|
4
|
+
require "tty-table"
|
|
5
|
+
require "pastel"
|
|
6
|
+
require_relative "terminal_io"
|
|
7
|
+
|
|
8
|
+
module Aidp
|
|
9
|
+
class CLI
|
|
10
|
+
# CLI commands for Temporal workflow management
|
|
11
|
+
# Provides interface for starting, monitoring, and controlling workflows
|
|
12
|
+
class TemporalCommand
|
|
13
|
+
include Aidp::MessageDisplay
|
|
14
|
+
|
|
15
|
+
def initialize(input: nil, output: nil, prompt: TTY::Prompt.new, project_dir: Dir.pwd)
|
|
16
|
+
@io = TerminalIO.new(input: input, output: output)
|
|
17
|
+
@prompt = prompt
|
|
18
|
+
@pastel = Pastel.new
|
|
19
|
+
@project_dir = project_dir
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def run(subcommand = nil, args = [])
|
|
23
|
+
# Check if Temporal is configured
|
|
24
|
+
unless temporal_enabled?
|
|
25
|
+
display_message("Temporal is not enabled. Add 'temporal' section to aidp.yml", type: :warning)
|
|
26
|
+
display_message("See: aidp temporal setup", type: :info)
|
|
27
|
+
return
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
case subcommand
|
|
31
|
+
when "start", nil
|
|
32
|
+
start_workflow(args)
|
|
33
|
+
when "list"
|
|
34
|
+
list_workflows(args)
|
|
35
|
+
when "status"
|
|
36
|
+
workflow_status(args)
|
|
37
|
+
when "signal"
|
|
38
|
+
signal_workflow(args)
|
|
39
|
+
when "cancel"
|
|
40
|
+
cancel_workflow(args)
|
|
41
|
+
when "worker"
|
|
42
|
+
run_worker(args)
|
|
43
|
+
when "setup"
|
|
44
|
+
setup_temporal
|
|
45
|
+
else
|
|
46
|
+
display_message("Unknown temporal subcommand: #{subcommand}", type: :error)
|
|
47
|
+
show_help
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def temporal_enabled?
|
|
54
|
+
require_relative "../temporal"
|
|
55
|
+
Aidp::Temporal.enabled?(@project_dir)
|
|
56
|
+
rescue LoadError
|
|
57
|
+
false
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def show_help
|
|
61
|
+
display_message("")
|
|
62
|
+
display_message("Temporal Workflow Commands:", type: :info)
|
|
63
|
+
display_message(" aidp temporal start [issue_number] - Start issue-to-PR workflow", type: :info)
|
|
64
|
+
display_message(" aidp temporal list - List active workflows", type: :info)
|
|
65
|
+
display_message(" aidp temporal status <workflow_id> - Show workflow status", type: :info)
|
|
66
|
+
display_message(" aidp temporal signal <workflow_id> <signal> - Send signal to workflow", type: :info)
|
|
67
|
+
display_message(" aidp temporal cancel <workflow_id> - Cancel a workflow", type: :info)
|
|
68
|
+
display_message(" aidp temporal worker - Run the Temporal worker", type: :info)
|
|
69
|
+
display_message(" aidp temporal setup - Configure Temporal settings", type: :info)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def start_workflow(args)
|
|
73
|
+
require_relative "../temporal"
|
|
74
|
+
|
|
75
|
+
workflow_type = args.shift || select_workflow_type
|
|
76
|
+
issue_number = args.shift
|
|
77
|
+
|
|
78
|
+
case workflow_type
|
|
79
|
+
when "issue", "issue_to_pr"
|
|
80
|
+
start_issue_to_pr_workflow(issue_number)
|
|
81
|
+
when "workloop", "work_loop"
|
|
82
|
+
start_work_loop_workflow(args)
|
|
83
|
+
else
|
|
84
|
+
display_message("Unknown workflow type: #{workflow_type}", type: :error)
|
|
85
|
+
display_message("Available: issue, workloop", type: :info)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def select_workflow_type
|
|
90
|
+
@prompt.select("Select workflow type:") do |menu|
|
|
91
|
+
menu.choice "Issue to PR (full pipeline)", "issue"
|
|
92
|
+
menu.choice "Work Loop (fix-forward iteration)", "workloop"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def start_issue_to_pr_workflow(issue_number)
|
|
97
|
+
issue_number ||= @prompt.ask("Issue number:") { |q| q.validate(/^\d+$/, "Must be a number") }
|
|
98
|
+
|
|
99
|
+
display_message("Starting issue-to-PR workflow for ##{issue_number}...", type: :info)
|
|
100
|
+
|
|
101
|
+
input = {
|
|
102
|
+
project_dir: @project_dir,
|
|
103
|
+
issue_number: issue_number.to_i,
|
|
104
|
+
max_iterations: 50
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
handle = Aidp::Temporal.start_workflow(
|
|
108
|
+
Aidp::Temporal::Workflows::IssueToPrWorkflow,
|
|
109
|
+
input,
|
|
110
|
+
project_dir: @project_dir
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
display_message("")
|
|
114
|
+
display_message(@pastel.green("Workflow started!"), type: :success)
|
|
115
|
+
display_message(" Workflow ID: #{handle.id}", type: :info)
|
|
116
|
+
display_message(" Issue: ##{issue_number}", type: :info)
|
|
117
|
+
display_message("")
|
|
118
|
+
display_message("Monitor with: aidp temporal status #{handle.id}", type: :info)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def start_work_loop_workflow(args)
|
|
122
|
+
step_name = args.shift || @prompt.ask("Step name:")
|
|
123
|
+
|
|
124
|
+
display_message("Starting work loop workflow: #{step_name}...", type: :info)
|
|
125
|
+
|
|
126
|
+
input = {
|
|
127
|
+
project_dir: @project_dir,
|
|
128
|
+
step_name: step_name,
|
|
129
|
+
step_spec: {},
|
|
130
|
+
max_iterations: 50
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
handle = Aidp::Temporal.start_workflow(
|
|
134
|
+
Aidp::Temporal::Workflows::WorkLoopWorkflow,
|
|
135
|
+
input,
|
|
136
|
+
project_dir: @project_dir
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
display_message("")
|
|
140
|
+
display_message(@pastel.green("Workflow started!"), type: :success)
|
|
141
|
+
display_message(" Workflow ID: #{handle.id}", type: :info)
|
|
142
|
+
display_message(" Step: #{step_name}", type: :info)
|
|
143
|
+
display_message("")
|
|
144
|
+
display_message("Monitor with: aidp temporal status #{handle.id}", type: :info)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def list_workflows(args)
|
|
148
|
+
require_relative "../temporal"
|
|
149
|
+
|
|
150
|
+
query = args.shift # Optional query filter
|
|
151
|
+
|
|
152
|
+
display_message("Active Temporal Workflows", type: :info)
|
|
153
|
+
display_message("=" * 60, type: :muted)
|
|
154
|
+
display_message("")
|
|
155
|
+
|
|
156
|
+
client = Aidp::Temporal.workflow_client(@project_dir)
|
|
157
|
+
workflows = client.list_workflows(query: query)
|
|
158
|
+
|
|
159
|
+
workflow_list = if workflows.respond_to?(:to_a)
|
|
160
|
+
workflows.to_a
|
|
161
|
+
else
|
|
162
|
+
[]
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
if workflow_list.empty?
|
|
166
|
+
display_message("No active workflows found", type: :info)
|
|
167
|
+
return
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
headers = ["Workflow ID", "Type", "Status", "Started"]
|
|
171
|
+
rows = workflow_list.map do |wf|
|
|
172
|
+
[
|
|
173
|
+
truncate(wf.id, 30),
|
|
174
|
+
wf.workflow_type,
|
|
175
|
+
format_status(wf.status),
|
|
176
|
+
format_time(wf.start_time)
|
|
177
|
+
]
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
table = TTY::Table.new(headers, rows)
|
|
181
|
+
puts table.render(:basic)
|
|
182
|
+
rescue => e
|
|
183
|
+
display_message("Failed to list workflows: #{e.message}", type: :error)
|
|
184
|
+
Aidp.log_error("temporal_command", "list_failed", error: e.message)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def workflow_status(args)
|
|
188
|
+
require_relative "../temporal"
|
|
189
|
+
|
|
190
|
+
workflow_id = args.shift
|
|
191
|
+
unless workflow_id
|
|
192
|
+
display_message("Usage: aidp temporal status <workflow_id>", type: :error)
|
|
193
|
+
return
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
follow = args.include?("--follow")
|
|
197
|
+
|
|
198
|
+
if follow
|
|
199
|
+
follow_workflow_status(workflow_id)
|
|
200
|
+
else
|
|
201
|
+
show_workflow_status(workflow_id)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def show_workflow_status(workflow_id)
|
|
206
|
+
client = Aidp::Temporal.workflow_client(@project_dir)
|
|
207
|
+
handle = client.get_workflow(workflow_id)
|
|
208
|
+
desc = handle.describe
|
|
209
|
+
|
|
210
|
+
display_message("Workflow Status: #{workflow_id}", type: :info)
|
|
211
|
+
display_message("=" * 60, type: :muted)
|
|
212
|
+
display_message("")
|
|
213
|
+
display_message("Type: #{desc.workflow_type}", type: :info)
|
|
214
|
+
display_message("Status: #{format_status(desc.status)}", type: :info)
|
|
215
|
+
display_message("Started: #{format_time(desc.start_time)}", type: :info)
|
|
216
|
+
|
|
217
|
+
if desc.close_time
|
|
218
|
+
display_message("Completed: #{format_time(desc.close_time)}", type: :info)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Try to query workflow state
|
|
222
|
+
begin
|
|
223
|
+
progress = handle.query(:progress)
|
|
224
|
+
if progress
|
|
225
|
+
display_message("")
|
|
226
|
+
display_message("Progress:", type: :info)
|
|
227
|
+
display_message(" State: #{progress[:state]}", type: :info)
|
|
228
|
+
display_message(" Iteration: #{progress[:iteration]}", type: :info) if progress[:iteration]
|
|
229
|
+
end
|
|
230
|
+
rescue => e
|
|
231
|
+
Aidp.log_debug("temporal_command", "query_failed", error: e.message)
|
|
232
|
+
end
|
|
233
|
+
rescue Temporalio::Error::WorkflowNotFoundError
|
|
234
|
+
display_message("Workflow not found: #{workflow_id}", type: :error)
|
|
235
|
+
rescue => e
|
|
236
|
+
display_message("Failed to get status: #{e.message}", type: :error)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def follow_workflow_status(workflow_id)
|
|
240
|
+
display_message("Following workflow status (Ctrl+C to stop)...", type: :info)
|
|
241
|
+
display_message("")
|
|
242
|
+
|
|
243
|
+
begin
|
|
244
|
+
loop do
|
|
245
|
+
print "\e[2J\e[H" # Clear screen
|
|
246
|
+
|
|
247
|
+
show_workflow_status(workflow_id)
|
|
248
|
+
|
|
249
|
+
client = Aidp::Temporal.workflow_client(@project_dir)
|
|
250
|
+
handle = client.get_workflow(workflow_id)
|
|
251
|
+
desc = handle.describe
|
|
252
|
+
|
|
253
|
+
break if desc.status != :running
|
|
254
|
+
|
|
255
|
+
sleep 2
|
|
256
|
+
end
|
|
257
|
+
rescue Interrupt
|
|
258
|
+
display_message("\nStopped following", type: :info)
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def signal_workflow(args)
|
|
263
|
+
require_relative "../temporal"
|
|
264
|
+
|
|
265
|
+
workflow_id = args.shift
|
|
266
|
+
signal_name = args.shift
|
|
267
|
+
|
|
268
|
+
unless workflow_id && signal_name
|
|
269
|
+
display_message("Usage: aidp temporal signal <workflow_id> <signal> [args...]", type: :error)
|
|
270
|
+
display_message("Available signals: pause, resume, inject_instruction, escalate_model", type: :info)
|
|
271
|
+
return
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
signal_args = args
|
|
275
|
+
|
|
276
|
+
display_message("Sending signal '#{signal_name}' to #{workflow_id}...", type: :info)
|
|
277
|
+
|
|
278
|
+
Aidp::Temporal.signal_workflow(workflow_id, signal_name, *signal_args, project_dir: @project_dir)
|
|
279
|
+
|
|
280
|
+
display_message(@pastel.green("Signal sent successfully"), type: :success)
|
|
281
|
+
rescue => e
|
|
282
|
+
display_message("Failed to send signal: #{e.message}", type: :error)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def cancel_workflow(args)
|
|
286
|
+
require_relative "../temporal"
|
|
287
|
+
|
|
288
|
+
workflow_id = args.shift
|
|
289
|
+
unless workflow_id
|
|
290
|
+
display_message("Usage: aidp temporal cancel <workflow_id>", type: :error)
|
|
291
|
+
return
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
confirmed = @prompt.yes?("Cancel workflow #{workflow_id}?")
|
|
295
|
+
return unless confirmed
|
|
296
|
+
|
|
297
|
+
display_message("Canceling workflow #{workflow_id}...", type: :info)
|
|
298
|
+
|
|
299
|
+
Aidp::Temporal.cancel_workflow(workflow_id, project_dir: @project_dir)
|
|
300
|
+
|
|
301
|
+
display_message(@pastel.green("Workflow canceled"), type: :success)
|
|
302
|
+
rescue => e
|
|
303
|
+
display_message("Failed to cancel workflow: #{e.message}", type: :error)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def run_worker(args)
|
|
307
|
+
require_relative "../temporal"
|
|
308
|
+
|
|
309
|
+
display_message("Starting Temporal worker...", type: :info)
|
|
310
|
+
display_message("Press Ctrl+C to stop", type: :muted)
|
|
311
|
+
display_message("")
|
|
312
|
+
|
|
313
|
+
config = Aidp::Temporal.configuration(@project_dir)
|
|
314
|
+
connection = config.build_connection
|
|
315
|
+
|
|
316
|
+
display_message("Connecting to: #{config.target_host}", type: :info)
|
|
317
|
+
display_message("Namespace: #{config.namespace}", type: :info)
|
|
318
|
+
display_message("Task Queue: #{config.task_queue}", type: :info)
|
|
319
|
+
display_message("")
|
|
320
|
+
|
|
321
|
+
worker = Aidp::Temporal::Worker.new(
|
|
322
|
+
connection: connection,
|
|
323
|
+
config: config.worker_config
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
# Register all workflows
|
|
327
|
+
worker.register_workflows(
|
|
328
|
+
Aidp::Temporal::Workflows::IssueToPrWorkflow,
|
|
329
|
+
Aidp::Temporal::Workflows::WorkLoopWorkflow,
|
|
330
|
+
Aidp::Temporal::Workflows::SubIssueWorkflow
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
# Register all activities
|
|
334
|
+
worker.register_activities(
|
|
335
|
+
Aidp::Temporal::Activities::RunAgentActivity.new,
|
|
336
|
+
Aidp::Temporal::Activities::RunTestsActivity.new,
|
|
337
|
+
Aidp::Temporal::Activities::AnalyzeIssueActivity.new,
|
|
338
|
+
Aidp::Temporal::Activities::CreatePlanActivity.new,
|
|
339
|
+
Aidp::Temporal::Activities::CreatePromptActivity.new,
|
|
340
|
+
Aidp::Temporal::Activities::DiagnoseFailureActivity.new,
|
|
341
|
+
Aidp::Temporal::Activities::PrepareNextIterationActivity.new,
|
|
342
|
+
Aidp::Temporal::Activities::RecordCheckpointActivity.new,
|
|
343
|
+
Aidp::Temporal::Activities::CreatePrActivity.new,
|
|
344
|
+
Aidp::Temporal::Activities::RunWorkLoopIterationActivity.new,
|
|
345
|
+
Aidp::Temporal::Activities::AnalyzeSubTaskActivity.new
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
display_message(@pastel.green("Worker started"), type: :success)
|
|
349
|
+
display_message("")
|
|
350
|
+
|
|
351
|
+
# Handle shutdown
|
|
352
|
+
trap("INT") do
|
|
353
|
+
display_message("\nShutting down worker...", type: :info)
|
|
354
|
+
worker.shutdown
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
trap("TERM") do
|
|
358
|
+
worker.shutdown
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# Run the worker (blocking)
|
|
362
|
+
worker.run
|
|
363
|
+
|
|
364
|
+
display_message("Worker stopped", type: :info)
|
|
365
|
+
rescue => e
|
|
366
|
+
display_message("Worker error: #{e.message}", type: :error)
|
|
367
|
+
Aidp.log_error("temporal_command", "worker_failed", error: e.message)
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def setup_temporal
|
|
371
|
+
display_message("Temporal Setup", type: :info)
|
|
372
|
+
display_message("=" * 40, type: :muted)
|
|
373
|
+
display_message("")
|
|
374
|
+
|
|
375
|
+
# Check if already configured
|
|
376
|
+
config_path = File.join(@project_dir, ".aidp", "aidp.yml")
|
|
377
|
+
if File.exist?(config_path)
|
|
378
|
+
content = File.read(config_path)
|
|
379
|
+
if content.include?("temporal:")
|
|
380
|
+
display_message("Temporal already configured in aidp.yml", type: :info)
|
|
381
|
+
return unless @prompt.yes?("Reconfigure?")
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
# Gather configuration
|
|
386
|
+
target_host = @prompt.ask("Temporal server address:", default: "localhost:7233")
|
|
387
|
+
namespace = @prompt.ask("Namespace:", default: "default")
|
|
388
|
+
task_queue = @prompt.ask("Task queue:", default: "aidp-workflows")
|
|
389
|
+
|
|
390
|
+
use_tls = @prompt.yes?("Use TLS?", default: false)
|
|
391
|
+
|
|
392
|
+
# Build configuration
|
|
393
|
+
temporal_config = {
|
|
394
|
+
"temporal" => {
|
|
395
|
+
"enabled" => true,
|
|
396
|
+
"target_host" => target_host,
|
|
397
|
+
"namespace" => namespace,
|
|
398
|
+
"task_queue" => task_queue,
|
|
399
|
+
"tls" => use_tls
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
# Append to config file
|
|
404
|
+
FileUtils.mkdir_p(File.dirname(config_path))
|
|
405
|
+
|
|
406
|
+
if File.exist?(config_path)
|
|
407
|
+
existing = YAML.safe_load_file(config_path, permitted_classes: [Date, Time, Symbol], aliases: true) || {}
|
|
408
|
+
existing.merge!(temporal_config)
|
|
409
|
+
File.write(config_path, existing.to_yaml)
|
|
410
|
+
else
|
|
411
|
+
File.write(config_path, temporal_config.to_yaml)
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
display_message("")
|
|
415
|
+
display_message(@pastel.green("Temporal configuration saved!"), type: :success)
|
|
416
|
+
display_message("")
|
|
417
|
+
display_message("Next steps:", type: :info)
|
|
418
|
+
display_message(" 1. Start Temporal server: docker-compose -f docker-compose.temporal.yml up -d", type: :info)
|
|
419
|
+
display_message(" 2. Start worker: aidp temporal worker", type: :info)
|
|
420
|
+
display_message(" 3. Start workflow: aidp temporal start issue <number>", type: :info)
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
def format_status(status)
|
|
424
|
+
case status.to_s
|
|
425
|
+
when "running"
|
|
426
|
+
@pastel.green("● Running")
|
|
427
|
+
when "completed"
|
|
428
|
+
@pastel.cyan("✓ Completed")
|
|
429
|
+
when "failed"
|
|
430
|
+
@pastel.red("✗ Failed")
|
|
431
|
+
when "canceled"
|
|
432
|
+
@pastel.yellow("⊘ Canceled")
|
|
433
|
+
when "terminated"
|
|
434
|
+
@pastel.red("⏹ Terminated")
|
|
435
|
+
when "timed_out"
|
|
436
|
+
@pastel.yellow("⏰ Timed Out")
|
|
437
|
+
else
|
|
438
|
+
@pastel.dim(status.to_s)
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
def format_time(time)
|
|
443
|
+
return "N/A" unless time
|
|
444
|
+
|
|
445
|
+
if time.is_a?(String)
|
|
446
|
+
time = Time.parse(time)
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
time.strftime("%Y-%m-%d %H:%M:%S")
|
|
450
|
+
rescue
|
|
451
|
+
time.to_s
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
def truncate(str, max_length)
|
|
455
|
+
return str unless str && str.length > max_length
|
|
456
|
+
"#{str[0, max_length - 3]}..."
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
end
|
data/lib/aidp/cli.rb
CHANGED
|
@@ -390,7 +390,7 @@ module Aidp
|
|
|
390
390
|
# Determine if the invocation is a subcommand style call
|
|
391
391
|
def subcommand?(args)
|
|
392
392
|
return false if args.nil? || args.empty?
|
|
393
|
-
%w[status jobs kb harness providers checkpoint eval mcp issue config init watch ws work skill settings models tools security].include?(args.first)
|
|
393
|
+
%w[status jobs kb harness providers checkpoint eval mcp issue config init watch ws work skill settings models tools security storage prompts temporal].include?(args.first)
|
|
394
394
|
end
|
|
395
395
|
|
|
396
396
|
def run_subcommand(args)
|
|
@@ -416,6 +416,9 @@ module Aidp
|
|
|
416
416
|
when "models" then run_models_command(args)
|
|
417
417
|
when "tools" then run_tools_command(args)
|
|
418
418
|
when "security" then run_security_command(args)
|
|
419
|
+
when "storage" then run_storage_command(args)
|
|
420
|
+
when "prompts" then run_prompts_command(args)
|
|
421
|
+
when "temporal" then run_temporal_command(args)
|
|
419
422
|
else
|
|
420
423
|
display_message("Unknown command: #{cmd}", type: :info)
|
|
421
424
|
return 1
|
|
@@ -781,6 +784,25 @@ module Aidp
|
|
|
781
784
|
security_cmd.run(args)
|
|
782
785
|
end
|
|
783
786
|
|
|
787
|
+
def run_storage_command(args)
|
|
788
|
+
require_relative "cli/storage_command"
|
|
789
|
+
storage_cmd = Aidp::CLI::StorageCommand.new(project_dir: Dir.pwd, prompt: create_prompt)
|
|
790
|
+
storage_cmd.run(args)
|
|
791
|
+
end
|
|
792
|
+
|
|
793
|
+
def run_prompts_command(args)
|
|
794
|
+
require_relative "cli/prompts_command"
|
|
795
|
+
prompts_cmd = Aidp::CLI::PromptsCommand.new(project_dir: Dir.pwd, prompt: create_prompt)
|
|
796
|
+
prompts_cmd.run(args)
|
|
797
|
+
end
|
|
798
|
+
|
|
799
|
+
def run_temporal_command(args)
|
|
800
|
+
require_relative "cli/temporal_command"
|
|
801
|
+
temporal_cmd = Aidp::CLI::TemporalCommand.new(project_dir: Dir.pwd, prompt: create_prompt)
|
|
802
|
+
subcommand = args.shift
|
|
803
|
+
temporal_cmd.run(subcommand, args)
|
|
804
|
+
end
|
|
805
|
+
|
|
784
806
|
def run_issue_command(args)
|
|
785
807
|
require_relative "cli/issue_importer"
|
|
786
808
|
|
|
@@ -1222,7 +1244,7 @@ module Aidp
|
|
|
1222
1244
|
display_message("", type: :info)
|
|
1223
1245
|
display_message("Git Status:", type: :highlight)
|
|
1224
1246
|
Dir.chdir(ws[:path]) do
|
|
1225
|
-
system("git", "status", "--short")
|
|
1247
|
+
Aidp::ShellExecutor.new.system("git", "status", "--short")
|
|
1226
1248
|
end
|
|
1227
1249
|
end
|
|
1228
1250
|
rescue Aidp::Worktree::Error => e
|
data/lib/aidp/config.rb
CHANGED
|
@@ -227,6 +227,12 @@ module Aidp
|
|
|
227
227
|
require_reviews: 0,
|
|
228
228
|
merge_method: "squash",
|
|
229
229
|
delete_branch: true
|
|
230
|
+
},
|
|
231
|
+
worktree_cleanup: {
|
|
232
|
+
enabled: true,
|
|
233
|
+
frequency: "weekly", # daily or weekly
|
|
234
|
+
base_branch: "main",
|
|
235
|
+
delete_branch: true
|
|
230
236
|
}
|
|
231
237
|
}
|
|
232
238
|
}.freeze
|
|
@@ -377,6 +383,15 @@ module Aidp
|
|
|
377
383
|
proxy_config.fetch(:enabled, true)
|
|
378
384
|
end
|
|
379
385
|
|
|
386
|
+
# Get worktree cleanup configuration
|
|
387
|
+
def self.worktree_cleanup_config(project_dir = Dir.pwd)
|
|
388
|
+
config = load_harness_config(project_dir)
|
|
389
|
+
watch_section = config[:watch] || config["watch"] || {}
|
|
390
|
+
cleanup_section = watch_section[:worktree_cleanup] || watch_section["worktree_cleanup"] || {}
|
|
391
|
+
|
|
392
|
+
symbolize_keys(cleanup_section)
|
|
393
|
+
end
|
|
394
|
+
|
|
380
395
|
# Check if configuration file exists
|
|
381
396
|
def self.config_exists?(project_dir = Dir.pwd)
|
|
382
397
|
ConfigPaths.config_exists?(project_dir)
|
data/lib/aidp/config_paths.rb
CHANGED
|
@@ -26,11 +26,15 @@ module Aidp
|
|
|
26
26
|
def self.evaluations_dir(project_dir = Dir.pwd) = File.join(aidp_dir(project_dir), "evaluations")
|
|
27
27
|
def self.evaluations_index_file(project_dir = Dir.pwd) = File.join(evaluations_dir(project_dir), "index.json")
|
|
28
28
|
|
|
29
|
+
# Prompt template paths (feedback is stored in SQLite database)
|
|
30
|
+
def self.prompts_dir(project_dir = Dir.pwd) = File.join(aidp_dir(project_dir), "prompts")
|
|
31
|
+
|
|
29
32
|
# Security module paths
|
|
30
33
|
def self.security_dir(project_dir = Dir.pwd) = File.join(aidp_dir(project_dir), "security")
|
|
31
34
|
def self.secrets_registry_file(project_dir = Dir.pwd) = File.join(security_dir(project_dir), "secrets_registry.json")
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
|
|
36
|
+
# Database path
|
|
37
|
+
def self.database_file(project_dir = Dir.pwd) = File.join(aidp_dir(project_dir), "aidp.db")
|
|
34
38
|
|
|
35
39
|
def self.config_exists?(project_dir = Dir.pwd)
|
|
36
40
|
File.exist?(config_file(project_dir))
|
|
@@ -87,5 +91,11 @@ module Aidp
|
|
|
87
91
|
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
88
92
|
dir
|
|
89
93
|
end
|
|
94
|
+
|
|
95
|
+
def self.ensure_prompts_dir(project_dir = Dir.pwd)
|
|
96
|
+
dir = prompts_dir(project_dir)
|
|
97
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
98
|
+
dir
|
|
99
|
+
end
|
|
90
100
|
end
|
|
91
101
|
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "schema"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
module Database
|
|
7
|
+
# Migration runner for SQLite database
|
|
8
|
+
# Applies pending schema migrations in order
|
|
9
|
+
module Migrations
|
|
10
|
+
class << self
|
|
11
|
+
# Run all pending migrations
|
|
12
|
+
#
|
|
13
|
+
# @param project_dir [String] Project directory path
|
|
14
|
+
# @return [Array<Integer>] List of applied migration versions
|
|
15
|
+
def run!(project_dir = Dir.pwd)
|
|
16
|
+
db = Database.connection(project_dir)
|
|
17
|
+
applied = []
|
|
18
|
+
|
|
19
|
+
current_version = current_schema_version(db)
|
|
20
|
+
pending = pending_migrations(current_version)
|
|
21
|
+
|
|
22
|
+
return applied if pending.empty?
|
|
23
|
+
|
|
24
|
+
Aidp.log_debug("migrations", "running_migrations",
|
|
25
|
+
current: current_version,
|
|
26
|
+
pending_count: pending.size)
|
|
27
|
+
|
|
28
|
+
pending.each do |version|
|
|
29
|
+
apply_migration!(db, version)
|
|
30
|
+
applied << version
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
Aidp.log_debug("migrations", "migrations_complete",
|
|
34
|
+
applied_count: applied.size,
|
|
35
|
+
new_version: Schema.latest_version)
|
|
36
|
+
|
|
37
|
+
applied
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Check if database needs migrations
|
|
41
|
+
#
|
|
42
|
+
# @param project_dir [String] Project directory path
|
|
43
|
+
# @return [Boolean] True if migrations are pending
|
|
44
|
+
def pending?(project_dir = Dir.pwd)
|
|
45
|
+
db = Database.connection(project_dir)
|
|
46
|
+
current = current_schema_version(db)
|
|
47
|
+
current < Schema.latest_version
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Get list of pending migration versions
|
|
51
|
+
#
|
|
52
|
+
# @param project_dir [String] Project directory path
|
|
53
|
+
# @return [Array<Integer>] Pending migration versions
|
|
54
|
+
def pending_versions(project_dir = Dir.pwd)
|
|
55
|
+
db = Database.connection(project_dir)
|
|
56
|
+
pending_migrations(current_schema_version(db))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
# Get current schema version from database
|
|
62
|
+
def current_schema_version(db)
|
|
63
|
+
# Check if schema_migrations table exists
|
|
64
|
+
table_exists = db.get_first_value(<<~SQL)
|
|
65
|
+
SELECT COUNT(*) FROM sqlite_master
|
|
66
|
+
WHERE type='table' AND name='schema_migrations'
|
|
67
|
+
SQL
|
|
68
|
+
|
|
69
|
+
return 0 unless table_exists.positive?
|
|
70
|
+
|
|
71
|
+
version = db.get_first_value("SELECT MAX(version) FROM schema_migrations")
|
|
72
|
+
version || 0
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Get list of pending migrations
|
|
76
|
+
def pending_migrations(current_version)
|
|
77
|
+
Schema.versions.select { |v| v > current_version }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Apply a single migration
|
|
81
|
+
def apply_migration!(db, version)
|
|
82
|
+
sql = Schema.migration_sql(version)
|
|
83
|
+
|
|
84
|
+
raise MigrationError, "No SQL found for migration version #{version}" unless sql
|
|
85
|
+
|
|
86
|
+
Aidp.log_debug("migrations", "applying_migration", version: version)
|
|
87
|
+
|
|
88
|
+
db.transaction do
|
|
89
|
+
# Execute all statements in the migration
|
|
90
|
+
sql.split(";").each do |statement|
|
|
91
|
+
statement = statement.strip
|
|
92
|
+
next if statement.empty?
|
|
93
|
+
|
|
94
|
+
db.execute(statement)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Record the migration
|
|
98
|
+
db.execute(
|
|
99
|
+
"INSERT INTO schema_migrations (version) VALUES (?)",
|
|
100
|
+
[version]
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
Aidp.log_debug("migrations", "migration_applied", version: version)
|
|
105
|
+
rescue SQLite3::Exception => e
|
|
106
|
+
Aidp.log_debug("migrations", "migration_failed",
|
|
107
|
+
version: version,
|
|
108
|
+
error: e.message)
|
|
109
|
+
raise MigrationError, "Failed to apply migration #{version}: #{e.message}"
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|