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,245 @@
1
+ require "aidp/watch/base_processor"
2
+ require "aidp/worktree"
3
+ require "aidp/pr_worktree_manager"
4
+
5
+ module Aidp
6
+ module Watch
7
+ # Processor for handling 'aidp-rebase' label on GitHub PRs
8
+ # Automatically rebases the PR branch against its target branch
9
+ class RebaseProcessor < BaseProcessor
10
+ DEFAULT_REBASE_LABEL = "aidp-rebase".freeze
11
+
12
+ def initialize(
13
+ repository_client:,
14
+ state_store: nil,
15
+ worktree_manager: nil,
16
+ ai_decision_engine: AIDecisionEngine.new,
17
+ label_config: {},
18
+ verbose: false,
19
+ shell_executor: nil
20
+ )
21
+ super(
22
+ repository_client: repository_client,
23
+ state_store: state_store,
24
+ label_config: label_config,
25
+ verbose: verbose
26
+ )
27
+ @worktree_manager = worktree_manager || Aidp::PRWorktreeManager.new
28
+ @ai_decision_engine = ai_decision_engine
29
+ @shell_executor = shell_executor || Aidp::ShellExecutor.new
30
+ @rebase_label = label_config[:rebase_trigger] ||
31
+ label_config["rebase_trigger"] ||
32
+ DEFAULT_REBASE_LABEL
33
+
34
+ Aidp.log_debug(
35
+ "rebase_processor",
36
+ "initialized",
37
+ rebase_label: @rebase_label
38
+ )
39
+ end
40
+
41
+ def can_process?(work_item)
42
+ return false unless work_item.respond_to?(:pr?) && work_item.pr?
43
+ work_item.labels.include?(@rebase_label)
44
+ end
45
+
46
+ def process(work_item)
47
+ Aidp.log_debug(
48
+ "rebase_processor",
49
+ "processing_work_item",
50
+ pr_number: work_item.number,
51
+ branch: work_item.data[:head][:ref]
52
+ )
53
+
54
+ begin
55
+ # Fetch PR details
56
+ pr_details = @repository_client.get_pull_request(work_item.number)
57
+ base_branch = pr_details[:base][:ref]
58
+ head_branch = pr_details[:head][:ref]
59
+
60
+ # Create worktree for rebasing
61
+ worktree_path = @worktree_manager.create_pr_worktree(
62
+ pr_number: work_item.number,
63
+ base_branch: base_branch,
64
+ head_branch: head_branch
65
+ )
66
+
67
+ # Attempt rebase with intelligent conflict resolution
68
+ rebase_result = perform_rebase(worktree_path, base_branch, head_branch)
69
+
70
+ # Post results to PR
71
+ post_rebase_status(work_item.number, rebase_result)
72
+
73
+ Aidp.log_debug(
74
+ "rebase_processor",
75
+ "rebase_completed",
76
+ pr_number: work_item.number,
77
+ result: rebase_result
78
+ )
79
+
80
+ rebase_result
81
+ rescue => e
82
+ # Handle and log rebase failures
83
+ handle_rebase_error(work_item.number, e)
84
+ false
85
+ ensure
86
+ # Remove rebase label after processing (success or failure)
87
+ @repository_client.remove_labels(
88
+ work_item.number,
89
+ [@rebase_label]
90
+ )
91
+
92
+ # Cleanup: remove temporary worktree
93
+ @worktree_manager.cleanup_pr_worktree(work_item.number)
94
+ end
95
+ end
96
+
97
+ private
98
+
99
+ def with_worktree_context(worktree_path)
100
+ if Dir.exist?(worktree_path)
101
+ Dir.chdir(worktree_path) { yield }
102
+ else
103
+ yield
104
+ end
105
+ end
106
+
107
+ def perform_rebase(worktree_path, base_branch, head_branch)
108
+ Aidp.log_debug(
109
+ "rebase_processor",
110
+ "performing_rebase",
111
+ worktree_path: worktree_path,
112
+ base_branch: base_branch,
113
+ head_branch: head_branch
114
+ )
115
+
116
+ # Use PRWorktreeManager's method to perform rebase and conflict resolution
117
+ with_worktree_context(worktree_path) do
118
+ # Fetch the latest changes
119
+ @shell_executor.system("git fetch origin")
120
+
121
+ # Attempt to rebase
122
+ rebase_command = "git rebase origin/#{base_branch}"
123
+ rebase_output = @shell_executor.system(rebase_command)
124
+
125
+ unless rebase_output
126
+ # Conflict resolution using AI
127
+ conflict_files = detect_conflicting_files(worktree_path)
128
+
129
+ if !conflict_files.empty?
130
+ # Use AI-powered conflict resolution
131
+ resolution = resolve_conflicts(worktree_path, base_branch, conflict_files)
132
+
133
+ # If resolution is successful, continue with the rebase
134
+ if resolution
135
+ # Stage resolved files and continue rebase
136
+ # GIT_EDITOR=true prevents editor from opening during automated rebase
137
+ quoted_files = conflict_files.map { |f| "\"#{f}\"" }.join(" ")
138
+ @shell_executor.system("git add #{quoted_files}")
139
+ @shell_executor.system({"GIT_EDITOR" => "true"}, "git rebase --continue")
140
+ else
141
+ return false
142
+ end
143
+ else
144
+ # No conflicts, but rebase failed
145
+ return false
146
+ end
147
+ end
148
+
149
+ # Push the rebased branch
150
+ @shell_executor.system("git push -f origin #{head_branch}")
151
+ end
152
+
153
+ true
154
+ end
155
+
156
+ def detect_conflicting_files(worktree_path)
157
+ # Use git to list conflicting files
158
+ `cd #{worktree_path} && git diff --name-only --diff-filter=U`.split("\n")
159
+ end
160
+
161
+ def resolve_conflicts(worktree_path, base_branch, conflict_files)
162
+ Aidp.log_debug(
163
+ "rebase_processor",
164
+ "resolving_conflicts",
165
+ worktree_path: worktree_path,
166
+ base_branch: base_branch
167
+ )
168
+
169
+ # Use AIDecisionEngine to analyze and resolve merge conflicts
170
+ conflict_resolution = @ai_decision_engine.resolve_merge_conflict(
171
+ base_branch_path: worktree_path,
172
+ conflict_files: conflict_files
173
+ )
174
+
175
+ apply_conflict_resolution(worktree_path, conflict_resolution)
176
+ end
177
+
178
+ def apply_conflict_resolution(worktree_path, conflict_resolution)
179
+ Aidp.log_debug(
180
+ "rebase_processor",
181
+ "applying_conflict_resolution",
182
+ resolution: conflict_resolution
183
+ )
184
+
185
+ # Apply AI-generated conflict resolution
186
+ conflict_resolution.each do |file, resolution|
187
+ File.write(File.join(worktree_path, file), resolution)
188
+ end
189
+
190
+ # Stage and continue rebase
191
+ # GIT_EDITOR=true prevents editor from opening during automated rebase
192
+ @shell_executor.system("cd #{worktree_path} && git add .")
193
+ @shell_executor.system({"GIT_EDITOR" => "true"}, "cd #{worktree_path} && git rebase --continue")
194
+ end
195
+
196
+ def post_rebase_status(pr_number, rebase_result, error_detail = nil)
197
+ status_method = rebase_result ? :add_success_status : :add_failure_status
198
+ description = if rebase_result
199
+ "PR successfully rebased"
200
+ elsif error_detail
201
+ error_detail
202
+ else
203
+ "Rebase failed"
204
+ end
205
+
206
+ @repository_client.send(
207
+ status_method,
208
+ pr_number,
209
+ context: "aidp/rebase",
210
+ description: description
211
+ )
212
+
213
+ comment_text = if rebase_result
214
+ "✅ PR has been successfully rebased against the target branch."
215
+ elsif error_detail
216
+ "❌ Automatic rebase failed with error: `#{error_detail}`\n\n" \
217
+ "Please check the PR and rebase manually."
218
+ else
219
+ "❌ Automatic rebase failed. Manual intervention required."
220
+ end
221
+
222
+ @repository_client.post_comment(pr_number, comment_text)
223
+ end
224
+
225
+ def handle_rebase_error(pr_number, error)
226
+ Aidp.log_debug(
227
+ "rebase_processor",
228
+ "rebase_error",
229
+ pr_number: pr_number,
230
+ error_message: error.message,
231
+ error_class: error.class
232
+ )
233
+
234
+ # Extract the error message for the status
235
+ error_message = if error.is_a?(StandardError)
236
+ error.message
237
+ else
238
+ "Unknown error"
239
+ end
240
+
241
+ post_rebase_status(pr_number, false, error_message)
242
+ end
243
+ end
244
+ end
245
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "work_item"
4
+
5
+ module Aidp
6
+ module Watch
7
+ # Schedules work items using a round-robin strategy across all tagged
8
+ # issues and PRs. Processes one iteration per work item before rotating
9
+ # to the next, ensuring fair distribution of attention.
10
+ #
11
+ # Priority handling:
12
+ # - aidp-plan items receive higher priority in rotation
13
+ # - Within the same priority, items are processed in queue order
14
+ #
15
+ # Paused items (aidp-needs-input label) are skipped during their turn
16
+ # but remain in the queue for later processing.
17
+ class RoundRobinScheduler
18
+ attr_reader :queue, :last_processed_key
19
+
20
+ def initialize(state_store:)
21
+ @state_store = state_store
22
+ @queue = []
23
+ @last_processed_key = state_store.round_robin_last_key
24
+
25
+ Aidp.log_debug("round_robin_scheduler", "initialized",
26
+ last_key: @last_processed_key)
27
+ end
28
+
29
+ # Refresh the queue with current work items from GitHub.
30
+ # Maintains position in rotation when possible.
31
+ #
32
+ # @param work_items [Array<WorkItem>] Current work items from all processors
33
+ def refresh_queue(work_items)
34
+ Aidp.log_debug("round_robin_scheduler", "refresh_queue.start",
35
+ incoming_count: work_items.size,
36
+ current_queue_size: @queue.size)
37
+
38
+ # Sort by priority (primary) and key (secondary) for deterministic ordering
39
+ # This ensures consistent queue order across refreshes
40
+ @queue = work_items.sort_by { |item| [item.priority, item.key] }
41
+
42
+ Aidp.log_debug("round_robin_scheduler", "refresh_queue.complete",
43
+ queue_size: @queue.size,
44
+ priorities: @queue.map(&:priority).uniq.sort)
45
+ end
46
+
47
+ # Get the next work item to process using round-robin rotation.
48
+ # Skips paused items but keeps them in queue.
49
+ #
50
+ # @param paused_numbers [Array<Integer>] Issue/PR numbers that are paused
51
+ # @return [WorkItem, nil] Next item to process, or nil if none available
52
+ def next_item(paused_numbers: [])
53
+ return nil if @queue.empty?
54
+
55
+ Aidp.log_debug("round_robin_scheduler", "next_item.start",
56
+ queue_size: @queue.size,
57
+ paused_count: paused_numbers.size,
58
+ last_key: @last_processed_key)
59
+
60
+ # Find starting position based on last processed key
61
+ start_index = find_start_index
62
+
63
+ # Iterate through queue starting from rotation point
64
+ @queue.size.times do |offset|
65
+ index = (start_index + offset) % @queue.size
66
+ item = @queue[index]
67
+
68
+ # Skip paused items
69
+ if paused_numbers.include?(item.number)
70
+ Aidp.log_debug("round_robin_scheduler", "next_item.skip_paused",
71
+ key: item.key, number: item.number)
72
+ next
73
+ end
74
+
75
+ Aidp.log_debug("round_robin_scheduler", "next_item.selected",
76
+ key: item.key, number: item.number, processor_type: item.processor_type)
77
+
78
+ return item
79
+ end
80
+
81
+ Aidp.log_debug("round_robin_scheduler", "next_item.all_paused",
82
+ queue_size: @queue.size)
83
+ nil
84
+ end
85
+
86
+ # Mark a work item as processed and persist the rotation state.
87
+ #
88
+ # @param item [WorkItem] The item that was just processed
89
+ def mark_processed(item)
90
+ @last_processed_key = item.key
91
+ @state_store.record_round_robin_position(
92
+ last_key: item.key,
93
+ processed_at: Time.now.utc.iso8601
94
+ )
95
+
96
+ Aidp.log_debug("round_robin_scheduler", "mark_processed",
97
+ key: item.key, number: item.number)
98
+ end
99
+
100
+ # Check if there are any non-paused items in the queue.
101
+ #
102
+ # @param paused_numbers [Array<Integer>] Issue/PR numbers that are paused
103
+ # @return [Boolean] True if there's work to do
104
+ def work?(paused_numbers: [])
105
+ @queue.any? { |item| !paused_numbers.include?(item.number) }
106
+ end
107
+
108
+ # Get queue statistics for debugging/monitoring.
109
+ #
110
+ # @return [Hash] Queue statistics
111
+ def stats
112
+ by_processor = @queue.group_by(&:processor_type)
113
+ by_priority = @queue.group_by(&:priority)
114
+
115
+ {
116
+ total: @queue.size,
117
+ by_processor: by_processor.transform_values(&:size),
118
+ by_priority: by_priority.transform_values(&:size),
119
+ last_processed_key: @last_processed_key
120
+ }
121
+ end
122
+
123
+ private
124
+
125
+ # Find the index to start searching from based on last processed item.
126
+ # Returns the index AFTER the last processed item for round-robin.
127
+ #
128
+ # Empty queue safety: This method returns 0 for empty queues, but the
129
+ # caller (next_item) guards against empty queues before calling this method.
130
+ # The modulo operation is safe because we've verified queue.size > 0.
131
+ #
132
+ # @return [Integer] Starting index for rotation (0 if queue is empty,
133
+ # no last key, or last processed item not found)
134
+ def find_start_index
135
+ return 0 if @last_processed_key.nil?
136
+ return 0 if @queue.empty?
137
+
138
+ # Find the last processed item in the current queue
139
+ last_index = @queue.find_index { |item| item.key == @last_processed_key }
140
+
141
+ # If not found (item was removed), start from beginning
142
+ return 0 unless last_index
143
+
144
+ # Start from the next item (wrap around)
145
+ # Safe: @queue.size > 0 guaranteed by early return on line 136
146
+ (last_index + 1) % @queue.size
147
+ end
148
+ end
149
+ end
150
+ end