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.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -14
  3. data/lib/aidp/agent_harness_adapter.rb +309 -0
  4. data/lib/aidp/cli/issue_importer.rb +20 -6
  5. data/lib/aidp/cli/jobs_command.rb +1 -1
  6. data/lib/aidp/cli/models_command.rb +9 -16
  7. data/lib/aidp/cli/prompts_command.rb +253 -0
  8. data/lib/aidp/cli/security_command.rb +4 -10
  9. data/lib/aidp/cli/storage_command.rb +277 -0
  10. data/lib/aidp/cli/temporal_command.rb +460 -0
  11. data/lib/aidp/cli.rb +24 -2
  12. data/lib/aidp/config.rb +15 -0
  13. data/lib/aidp/config_paths.rb +12 -2
  14. data/lib/aidp/database/migrations.rb +114 -0
  15. data/lib/aidp/database/repositories/checkpoint_repository.rb +174 -0
  16. data/lib/aidp/database/repositories/deprecated_models_repository.rb +206 -0
  17. data/lib/aidp/database/repositories/evaluation_repository.rb +208 -0
  18. data/lib/aidp/database/repositories/harness_state_repository.rb +140 -0
  19. data/lib/aidp/database/repositories/job_repository.rb +278 -0
  20. data/lib/aidp/database/repositories/model_cache_repository.rb +194 -0
  21. data/lib/aidp/database/repositories/progress_repository.rb +210 -0
  22. data/lib/aidp/database/repositories/prompt_archive_repository.rb +195 -0
  23. data/lib/aidp/database/repositories/prompt_feedback_repository.rb +266 -0
  24. data/lib/aidp/database/repositories/provider_info_cache_repository.rb +208 -0
  25. data/lib/aidp/database/repositories/provider_metrics_repository.rb +245 -0
  26. data/lib/aidp/database/repositories/secrets_repository.rb +187 -0
  27. data/lib/aidp/database/repositories/task_repository.rb +231 -0
  28. data/lib/aidp/database/repositories/template_version_repository.rb +383 -0
  29. data/lib/aidp/database/repositories/watch_state_repository.rb +472 -0
  30. data/lib/aidp/database/repositories/workstream_repository.rb +310 -0
  31. data/lib/aidp/database/repositories/worktree_repository.rb +240 -0
  32. data/lib/aidp/database/repository.rb +166 -0
  33. data/lib/aidp/database/schema.rb +347 -0
  34. data/lib/aidp/database/storage_migrator.rb +693 -0
  35. data/lib/aidp/database.rb +139 -0
  36. data/lib/aidp/debug_mixin.rb +2 -2
  37. data/lib/aidp/execute/checkpoint.rb +26 -0
  38. data/lib/aidp/execute/checkpoint_display.rb +16 -0
  39. data/lib/aidp/execute/prompt_evaluator.rb +30 -57
  40. data/lib/aidp/execute/work_loop_runner.rb +294 -46
  41. data/lib/aidp/firewall/provider_requirements_collector.rb +7 -23
  42. data/lib/aidp/harness/agent_harness_provider_manager.rb +275 -0
  43. data/lib/aidp/harness/ai_decision_engine.rb +78 -296
  44. data/lib/aidp/harness/config_schema.rb +59 -0
  45. data/lib/aidp/harness/configuration.rb +229 -10
  46. data/lib/aidp/harness/deprecation_cache.rb +53 -11
  47. data/lib/aidp/harness/enhanced_runner.rb +1 -1
  48. data/lib/aidp/harness/error_handler.rb +11 -5
  49. data/lib/aidp/harness/provider_config.rb +36 -0
  50. data/lib/aidp/harness/provider_factory.rb +26 -24
  51. data/lib/aidp/harness/provider_info.rb +9 -5
  52. data/lib/aidp/harness/provider_manager.rb +2 -2
  53. data/lib/aidp/harness/provider_metrics.rb +103 -1
  54. data/lib/aidp/harness/rspec_command_optimizer.rb +166 -0
  55. data/lib/aidp/harness/ruby_llm_registry.rb +97 -8
  56. data/lib/aidp/harness/runner.rb +7 -2
  57. data/lib/aidp/harness/state_manager.rb +1 -1
  58. data/lib/aidp/harness/test_runner.rb +167 -11
  59. data/lib/aidp/harness/thinking_depth_manager.rb +564 -5
  60. data/lib/aidp/harness/usage_limit.rb +179 -0
  61. data/lib/aidp/harness/usage_limit_enforcer.rb +240 -0
  62. data/lib/aidp/harness/usage_limit_tracker.rb +308 -0
  63. data/lib/aidp/harness/usage_period.rb +234 -0
  64. data/lib/aidp/init/agent_instructions_generator.rb +317 -0
  65. data/lib/aidp/init/runner.rb +25 -4
  66. data/lib/aidp/interfaces/activity_monitor_interface.rb +234 -0
  67. data/lib/aidp/interfaces/binary_checker_interface.rb +232 -0
  68. data/lib/aidp/interfaces/command_executor_interface.rb +216 -0
  69. data/lib/aidp/interfaces/logger_interface.rb +150 -0
  70. data/lib/aidp/interfaces/ui_interface.rb +254 -0
  71. data/lib/aidp/logger.rb +25 -7
  72. data/lib/aidp/orchestration_adapter.rb +279 -0
  73. data/lib/aidp/pr_worktree_manager.rb +4 -15
  74. data/lib/aidp/prompts/feedback_collector.rb +198 -0
  75. data/lib/aidp/prompts/prompt_template_manager.rb +415 -0
  76. data/lib/aidp/prompts/template_evolver.rb +305 -0
  77. data/lib/aidp/prompts/template_version_manager.rb +325 -0
  78. data/lib/aidp/setup/in_memory_config_adapter.rb +257 -0
  79. data/lib/aidp/setup/in_memory_config_manager.rb +262 -0
  80. data/lib/aidp/setup/wizard.rb +616 -176
  81. data/lib/aidp/shell_executor.rb +58 -0
  82. data/lib/aidp/style_guide/selector.rb +56 -1
  83. data/lib/aidp/temporal/activities/analyze_issue_activity.rb +198 -0
  84. data/lib/aidp/temporal/activities/analyze_sub_task_activity.rb +163 -0
  85. data/lib/aidp/temporal/activities/base_activity.rb +78 -0
  86. data/lib/aidp/temporal/activities/create_plan_activity.rb +115 -0
  87. data/lib/aidp/temporal/activities/create_pr_activity.rb +193 -0
  88. data/lib/aidp/temporal/activities/create_prompt_activity.rb +118 -0
  89. data/lib/aidp/temporal/activities/diagnose_failure_activity.rb +228 -0
  90. data/lib/aidp/temporal/activities/prepare_next_iteration_activity.rb +147 -0
  91. data/lib/aidp/temporal/activities/record_checkpoint_activity.rb +81 -0
  92. data/lib/aidp/temporal/activities/run_agent_activity.rb +139 -0
  93. data/lib/aidp/temporal/activities/run_tests_activity.rb +114 -0
  94. data/lib/aidp/temporal/activities/run_work_loop_iteration_activity.rb +249 -0
  95. data/lib/aidp/temporal/configuration.rb +169 -0
  96. data/lib/aidp/temporal/connection.rb +93 -0
  97. data/lib/aidp/temporal/worker.rb +119 -0
  98. data/lib/aidp/temporal/workflow_client.rb +171 -0
  99. data/lib/aidp/temporal/workflows/base_workflow.rb +73 -0
  100. data/lib/aidp/temporal/workflows/issue_to_pr_workflow.rb +278 -0
  101. data/lib/aidp/temporal/workflows/sub_issue_workflow.rb +253 -0
  102. data/lib/aidp/temporal/workflows/work_loop_workflow.rb +321 -0
  103. data/lib/aidp/temporal.rb +120 -0
  104. data/lib/aidp/version.rb +1 -1
  105. data/lib/aidp/watch/base_processor.rb +35 -0
  106. data/lib/aidp/watch/build_processor.rb +132 -0
  107. data/lib/aidp/watch/github_state_extractor.rb +0 -8
  108. data/lib/aidp/watch/rebase_processor.rb +245 -0
  109. data/lib/aidp/watch/round_robin_scheduler.rb +150 -0
  110. data/lib/aidp/watch/runner.rb +366 -285
  111. data/lib/aidp/watch/state_store.rb +80 -0
  112. data/lib/aidp/watch/work_item.rb +96 -0
  113. data/lib/aidp/watch/worktree_cleanup_job.rb +226 -0
  114. data/lib/aidp/watch/worktree_reconciler.rb +643 -0
  115. data/lib/aidp/workflows/guided_agent.rb +1 -1
  116. data/lib/aidp/worktree.rb +62 -0
  117. data/templates/aidp.yml.example +70 -0
  118. metadata +117 -16
  119. data/lib/aidp/analyze/json_file_storage.rb +0 -292
  120. data/lib/aidp/execute/future_work_backlog.rb +0 -411
  121. data/lib/aidp/providers/adapter.rb +0 -239
  122. data/lib/aidp/providers/aider.rb +0 -264
  123. data/lib/aidp/providers/anthropic.rb +0 -614
  124. data/lib/aidp/providers/base.rb +0 -617
  125. data/lib/aidp/providers/capability_registry.rb +0 -204
  126. data/lib/aidp/providers/codex.rb +0 -267
  127. data/lib/aidp/providers/cursor.rb +0 -304
  128. data/lib/aidp/providers/error_taxonomy.rb +0 -195
  129. data/lib/aidp/providers/gemini.rb +0 -110
  130. data/lib/aidp/providers/github_copilot.rb +0 -227
  131. data/lib/aidp/providers/kilocode.rb +0 -157
  132. 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)
@@ -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
- def self.security_audit_log_file(project_dir = Dir.pwd) = File.join(security_dir(project_dir), "audit.jsonl")
33
- def self.mcp_risk_profile_file(project_dir = Dir.pwd) = File.join(security_dir(project_dir), "mcp_risk_profile.yml")
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