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
data/lib/aidp/watch/runner.rb
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
require "tty-prompt"
|
|
4
4
|
require_relative "feedback_collector"
|
|
5
5
|
require_relative "github_state_extractor"
|
|
6
|
+
require_relative "round_robin_scheduler"
|
|
7
|
+
require_relative "work_item"
|
|
8
|
+
require_relative "worktree_cleanup_job"
|
|
9
|
+
require_relative "worktree_reconciler"
|
|
6
10
|
|
|
7
11
|
module Aidp
|
|
8
12
|
module Watch
|
|
@@ -115,6 +119,30 @@ module Aidp
|
|
|
115
119
|
state_store: @state_store,
|
|
116
120
|
project_dir: project_dir
|
|
117
121
|
)
|
|
122
|
+
|
|
123
|
+
# Initialize worktree cleanup job (issue #367)
|
|
124
|
+
cleanup_config = Aidp::Config.worktree_cleanup_config(project_dir)
|
|
125
|
+
@worktree_cleanup_job = WorktreeCleanupJob.new(
|
|
126
|
+
project_dir: project_dir,
|
|
127
|
+
config: cleanup_config
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Initialize worktree reconciler for dirty worktree handling
|
|
131
|
+
reconciler_config = safety_config[:worktree_reconciliation] || safety_config["worktree_reconciliation"] || {}
|
|
132
|
+
@worktree_reconciler = WorktreeReconciler.new(
|
|
133
|
+
project_dir: project_dir,
|
|
134
|
+
repository_client: @repository_client,
|
|
135
|
+
build_processor: @build_processor,
|
|
136
|
+
state_store: @state_store,
|
|
137
|
+
config: reconciler_config
|
|
138
|
+
)
|
|
139
|
+
@last_reconcile_at = nil
|
|
140
|
+
|
|
141
|
+
# Initialize round-robin scheduler (issue #434)
|
|
142
|
+
@round_robin_scheduler = RoundRobinScheduler.new(
|
|
143
|
+
state_store: @state_store
|
|
144
|
+
)
|
|
145
|
+
@needs_input_label = label_config[:needs_input] || label_config["needs_input"] || "aidp-needs-input"
|
|
118
146
|
end
|
|
119
147
|
|
|
120
148
|
def start
|
|
@@ -156,343 +184,342 @@ module Aidp
|
|
|
156
184
|
private
|
|
157
185
|
|
|
158
186
|
def process_cycle
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
187
|
+
# Collect all work items and refresh the scheduler queue
|
|
188
|
+
work_items = collect_all_work_items
|
|
189
|
+
@round_robin_scheduler.refresh_queue(work_items)
|
|
190
|
+
|
|
191
|
+
# Get paused items (aidp-needs-input label)
|
|
192
|
+
paused_numbers = fetch_paused_item_numbers
|
|
193
|
+
|
|
194
|
+
# Log queue status
|
|
195
|
+
stats = @round_robin_scheduler.stats
|
|
196
|
+
Aidp.log_debug("watch_runner", "round_robin.queue_status",
|
|
197
|
+
total: stats[:total],
|
|
198
|
+
by_processor: stats[:by_processor],
|
|
199
|
+
paused_count: paused_numbers.size)
|
|
200
|
+
|
|
201
|
+
# Process one work item using round-robin
|
|
202
|
+
if @round_robin_scheduler.work?(paused_numbers: paused_numbers)
|
|
203
|
+
process_next_work_item(paused_numbers: paused_numbers)
|
|
204
|
+
else
|
|
205
|
+
Aidp.log_debug("watch_runner", "round_robin.no_work",
|
|
206
|
+
queue_size: stats[:total], paused_count: paused_numbers.size)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Run maintenance tasks (these run every cycle regardless)
|
|
162
210
|
check_for_updates_if_due
|
|
163
|
-
process_review_triggers
|
|
164
|
-
process_ci_fix_triggers
|
|
165
|
-
process_auto_pr_triggers
|
|
166
|
-
process_change_request_triggers
|
|
167
211
|
collect_feedback
|
|
212
|
+
process_worktree_cleanup
|
|
213
|
+
process_worktree_reconciliation
|
|
168
214
|
end
|
|
169
215
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
Aidp.log_error("watch_runner", "plan_poll_failed", label: plan_label, error: e.message)
|
|
176
|
-
return # Skip this cycle, continue watch loop
|
|
177
|
-
end
|
|
178
|
-
Aidp.log_debug("watch_runner", "plan_poll", label: plan_label, total: issues.size)
|
|
179
|
-
issues.each do |issue|
|
|
180
|
-
unless issue_has_label?(issue, plan_label)
|
|
181
|
-
Aidp.log_debug("watch_runner", "plan_skip_label_mismatch", issue: issue[:number], labels: issue[:labels])
|
|
182
|
-
next
|
|
183
|
-
end
|
|
216
|
+
# Process the next work item from the round-robin queue.
|
|
217
|
+
# @param paused_numbers [Array<Integer>] Numbers of paused items to skip
|
|
218
|
+
def process_next_work_item(paused_numbers:)
|
|
219
|
+
item = @round_robin_scheduler.next_item(paused_numbers: paused_numbers)
|
|
220
|
+
return unless item
|
|
184
221
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
rescue => e
|
|
188
|
-
Aidp.log_error("watch_runner", "fetch_issue_failed", issue: issue[:number], error: e.message)
|
|
189
|
-
next # Skip this issue, continue with others
|
|
190
|
-
end
|
|
222
|
+
Aidp.log_info("watch_runner", "round_robin.processing",
|
|
223
|
+
key: item.key, number: item.number, processor_type: item.processor_type)
|
|
191
224
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
Aidp.log_debug("watch_runner", "plan_skip_in_progress", issue: detailed[:number])
|
|
195
|
-
next
|
|
196
|
-
end
|
|
225
|
+
# Dispatch to appropriate processor - returns true if item was actually processed
|
|
226
|
+
was_processed = dispatch_work_item(item)
|
|
197
227
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
228
|
+
# Only mark as processed if work was actually done (not skipped), so skipped
|
|
229
|
+
# items remain in the queue and will be re-checked on the next cycle.
|
|
230
|
+
@round_robin_scheduler.mark_processed(item) if was_processed
|
|
231
|
+
rescue => e
|
|
232
|
+
Aidp.log_error("watch_runner", "round_robin.process_failed",
|
|
233
|
+
key: item&.key, error: e.message, error_class: e.class.name)
|
|
234
|
+
# Mark as processed on error to prevent infinite retry loops within the same
|
|
235
|
+
# rotation. Unlike authorization/build_completed skips (which return false to
|
|
236
|
+
# allow immediate retry since their state may change), errors are assumed to be
|
|
237
|
+
# transient issues that will resolve by the next full queue refresh cycle.
|
|
238
|
+
# The item will be re-added to the queue on the next refresh if still active.
|
|
239
|
+
@round_robin_scheduler.mark_processed(item) if item
|
|
240
|
+
end
|
|
204
241
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
242
|
+
# Dispatch a work item to its appropriate processor.
|
|
243
|
+
#
|
|
244
|
+
# Design note: Items skipped due to authorization or build_completed checks
|
|
245
|
+
# return false and are NOT marked as processed. This means they will be
|
|
246
|
+
# re-checked on the next cycle. This is intentional because:
|
|
247
|
+
# - Authorization: An author could be added to the allowlist between cycles
|
|
248
|
+
# - Build completed: New commits could be pushed, requiring more work
|
|
249
|
+
#
|
|
250
|
+
# The trade-off is some redundant API calls, but this ensures we don't
|
|
251
|
+
# permanently skip items whose state could change.
|
|
252
|
+
#
|
|
253
|
+
# @param item [WorkItem] The work item to process
|
|
254
|
+
# @return [Boolean] True if item was actually processed, false if skipped
|
|
255
|
+
def dispatch_work_item(item)
|
|
256
|
+
detailed = fetch_detailed_item(item)
|
|
257
|
+
return false unless detailed
|
|
258
|
+
|
|
259
|
+
# Check authorization
|
|
260
|
+
unless @safety_checker.should_process_issue?(detailed, enforce: false)
|
|
261
|
+
Aidp.log_debug("watch_runner", "round_robin.skip_unauthorized",
|
|
262
|
+
key: item.key, author: detailed[:author])
|
|
263
|
+
return false
|
|
264
|
+
end
|
|
209
265
|
|
|
210
|
-
|
|
266
|
+
# Post detection comment if not already posted
|
|
267
|
+
post_detection_comment_for_item(item, detailed)
|
|
268
|
+
|
|
269
|
+
# Dispatch to processor
|
|
270
|
+
case item.processor_type
|
|
271
|
+
when :plan
|
|
211
272
|
@plan_processor.process(detailed)
|
|
212
|
-
|
|
213
|
-
|
|
273
|
+
when :build
|
|
274
|
+
# Check build completion at dispatch time (moved from collection for API efficiency)
|
|
275
|
+
if @state_extractor.build_completed?(detailed)
|
|
276
|
+
Aidp.log_debug("watch_runner", "round_robin.skip_build_completed",
|
|
277
|
+
key: item.key, number: item.number)
|
|
278
|
+
return false
|
|
279
|
+
end
|
|
280
|
+
@build_processor.process(detailed)
|
|
281
|
+
when :auto_issue
|
|
282
|
+
@auto_processor.process(detailed)
|
|
283
|
+
when :review
|
|
284
|
+
@review_processor.process(detailed)
|
|
285
|
+
when :ci_fix
|
|
286
|
+
@ci_fix_processor.process(detailed)
|
|
287
|
+
when :auto_pr
|
|
288
|
+
@auto_pr_processor.process(detailed)
|
|
289
|
+
when :change_request
|
|
290
|
+
@change_request_processor.process(detailed)
|
|
291
|
+
else
|
|
292
|
+
Aidp.log_warn("watch_runner", "round_robin.unknown_processor",
|
|
293
|
+
processor_type: item.processor_type)
|
|
294
|
+
return false
|
|
214
295
|
end
|
|
296
|
+
|
|
297
|
+
true # Item was processed
|
|
298
|
+
rescue RepositorySafetyChecker::UnauthorizedAuthorError => e
|
|
299
|
+
Aidp.log_warn("watch_runner", "round_robin.unauthorized_author",
|
|
300
|
+
key: item.key, error: e.message)
|
|
301
|
+
false
|
|
215
302
|
end
|
|
216
303
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
Aidp.log_debug("watch_runner", "build_poll", label: build_label, total: issues.size)
|
|
226
|
-
issues.each do |issue|
|
|
227
|
-
detailed = nil
|
|
228
|
-
in_progress_added = false
|
|
229
|
-
|
|
230
|
-
begin
|
|
231
|
-
unless issue_has_label?(issue, build_label)
|
|
232
|
-
Aidp.log_debug("watch_runner", "build_skip_label_mismatch", issue: issue[:number], labels: issue[:labels])
|
|
233
|
-
next
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
begin
|
|
237
|
-
detailed = @repository_client.fetch_issue(issue[:number])
|
|
238
|
-
rescue => e
|
|
239
|
-
Aidp.log_error("watch_runner", "fetch_issue_failed", issue: issue[:number], error: e.message)
|
|
240
|
-
next # Skip this issue, continue with others
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
# Check if already completed (via GitHub comments)
|
|
244
|
-
if @state_extractor.build_completed?(detailed)
|
|
245
|
-
Aidp.log_debug("watch_runner", "build_skip_completed", issue: detailed[:number])
|
|
246
|
-
next
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
# Check if already in progress by another instance
|
|
250
|
-
if @state_extractor.in_progress?(detailed)
|
|
251
|
-
Aidp.log_debug("watch_runner", "build_skip_in_progress", issue: detailed[:number])
|
|
252
|
-
next
|
|
253
|
-
end
|
|
254
|
-
|
|
255
|
-
# Check author authorization before processing
|
|
256
|
-
unless @safety_checker.should_process_issue?(detailed, enforce: false)
|
|
257
|
-
Aidp.log_debug("watch_runner", "build_skip_unauthorized_author", issue: detailed[:number],
|
|
258
|
-
author: detailed[:author])
|
|
259
|
-
next
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
# Check if detection comment already posted (deduplication)
|
|
263
|
-
unless @state_extractor.detection_comment_posted?(detailed, build_label)
|
|
264
|
-
post_detection_comment(item_type: :issue, number: detailed[:number], label: build_label)
|
|
265
|
-
end
|
|
266
|
-
|
|
267
|
-
# Add in-progress label before processing
|
|
268
|
-
@repository_client.add_labels(detailed[:number], GitHubStateExtractor::IN_PROGRESS_LABEL)
|
|
269
|
-
in_progress_added = true
|
|
270
|
-
|
|
271
|
-
Aidp.log_debug("watch_runner", "build_process", issue: detailed[:number])
|
|
272
|
-
@build_processor.process(detailed)
|
|
273
|
-
rescue RepositorySafetyChecker::UnauthorizedAuthorError => e
|
|
274
|
-
Aidp.log_warn("watch_runner", "unauthorized issue author", issue: issue[:number], error: e.message)
|
|
275
|
-
ensure
|
|
276
|
-
# Remove in-progress label when done (only if we added it)
|
|
277
|
-
if in_progress_added && detailed
|
|
278
|
-
begin
|
|
279
|
-
@repository_client.remove_labels(detailed[:number], GitHubStateExtractor::IN_PROGRESS_LABEL)
|
|
280
|
-
rescue => e
|
|
281
|
-
Aidp.log_warn("watch_runner", "failed_to_remove_in_progress_label", issue: detailed[:number],
|
|
282
|
-
error: e.message)
|
|
283
|
-
end
|
|
284
|
-
end
|
|
285
|
-
end
|
|
304
|
+
# Fetch detailed issue or PR data for a work item.
|
|
305
|
+
# @param item [WorkItem] Work item to fetch details for
|
|
306
|
+
# @return [Hash, nil] Detailed item data or nil on error
|
|
307
|
+
def fetch_detailed_item(item)
|
|
308
|
+
if item.issue?
|
|
309
|
+
@repository_client.fetch_issue(item.number)
|
|
310
|
+
else
|
|
311
|
+
@repository_client.fetch_pull_request(item.number)
|
|
286
312
|
end
|
|
313
|
+
rescue => e
|
|
314
|
+
Aidp.log_error("watch_runner", "round_robin.fetch_failed",
|
|
315
|
+
key: item.key, error: e.message)
|
|
316
|
+
nil
|
|
287
317
|
end
|
|
288
318
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
return
|
|
296
|
-
end
|
|
319
|
+
# Post detection comment for a work item if not already posted.
|
|
320
|
+
# @param item [WorkItem] Work item
|
|
321
|
+
# @param detailed [Hash] Detailed issue/PR data
|
|
322
|
+
def post_detection_comment_for_item(item, detailed)
|
|
323
|
+
return unless @post_detection_comments
|
|
324
|
+
return if @state_extractor.detection_comment_posted?(detailed, item.label)
|
|
297
325
|
|
|
298
|
-
|
|
326
|
+
post_detection_comment(
|
|
327
|
+
item_type: item.item_type,
|
|
328
|
+
number: item.number,
|
|
329
|
+
label: item.label
|
|
330
|
+
)
|
|
331
|
+
end
|
|
299
332
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
next
|
|
305
|
-
end
|
|
333
|
+
# Collect all work items from all processors.
|
|
334
|
+
# @return [Array<WorkItem>] List of all work items
|
|
335
|
+
def collect_all_work_items
|
|
336
|
+
items = []
|
|
306
337
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
338
|
+
items.concat(collect_plan_work_items)
|
|
339
|
+
items.concat(collect_build_work_items)
|
|
340
|
+
items.concat(collect_auto_issue_work_items)
|
|
341
|
+
items.concat(collect_review_work_items)
|
|
342
|
+
items.concat(collect_ci_fix_work_items)
|
|
343
|
+
items.concat(collect_auto_pr_work_items)
|
|
344
|
+
items.concat(collect_change_request_work_items)
|
|
313
345
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
Aidp.log_debug("watch_runner", "auto_issue_skip_in_progress", issue: detailed[:number])
|
|
317
|
-
next
|
|
318
|
-
end
|
|
346
|
+
Aidp.log_debug("watch_runner", "collect_all_work_items.complete",
|
|
347
|
+
total: items.size)
|
|
319
348
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
Aidp.log_debug("watch_runner", "auto_issue_skip_unauthorized_author", issue: detailed[:number],
|
|
323
|
-
author: detailed[:author])
|
|
324
|
-
next
|
|
325
|
-
end
|
|
349
|
+
items
|
|
350
|
+
end
|
|
326
351
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
352
|
+
# Collect work items for plan triggers.
|
|
353
|
+
# @return [Array<WorkItem>]
|
|
354
|
+
def collect_plan_work_items
|
|
355
|
+
label = @plan_processor.plan_label
|
|
356
|
+
issues = @repository_client.list_issues(labels: [label], state: "open")
|
|
331
357
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
358
|
+
issues.filter_map do |issue|
|
|
359
|
+
next unless issue_has_label?(issue, label)
|
|
360
|
+
|
|
361
|
+
WorkItem.new(
|
|
362
|
+
number: issue[:number],
|
|
363
|
+
item_type: :issue,
|
|
364
|
+
processor_type: :plan,
|
|
365
|
+
label: label,
|
|
366
|
+
data: issue
|
|
367
|
+
)
|
|
336
368
|
end
|
|
369
|
+
rescue => e
|
|
370
|
+
Aidp.log_error("watch_runner", "collect_plan_items_failed", error: e.message)
|
|
371
|
+
[]
|
|
337
372
|
end
|
|
338
373
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
374
|
+
# Collect work items for build triggers.
|
|
375
|
+
# @return [Array<WorkItem>]
|
|
376
|
+
def collect_build_work_items
|
|
377
|
+
label = @build_processor.build_label
|
|
378
|
+
issues = @repository_client.list_issues(labels: [label], state: "open")
|
|
379
|
+
|
|
380
|
+
issues.filter_map do |issue|
|
|
381
|
+
next unless issue_has_label?(issue, label)
|
|
382
|
+
|
|
383
|
+
# Note: build_completed check moved to dispatch phase to avoid
|
|
384
|
+
# API calls during collection (addresses rate limiting concerns)
|
|
385
|
+
WorkItem.new(
|
|
386
|
+
number: issue[:number],
|
|
387
|
+
item_type: :issue,
|
|
388
|
+
processor_type: :build,
|
|
389
|
+
label: label,
|
|
390
|
+
data: issue
|
|
391
|
+
)
|
|
346
392
|
end
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
next
|
|
352
|
-
end
|
|
353
|
-
|
|
354
|
-
begin
|
|
355
|
-
detailed = @repository_client.fetch_pull_request(pr[:number])
|
|
356
|
-
rescue => e
|
|
357
|
-
Aidp.log_error("watch_runner", "fetch_pr_failed", pr: pr[:number], error: e.message)
|
|
358
|
-
next # Skip this PR, continue with others
|
|
359
|
-
end
|
|
393
|
+
rescue => e
|
|
394
|
+
Aidp.log_error("watch_runner", "collect_build_items_failed", error: e.message)
|
|
395
|
+
[]
|
|
396
|
+
end
|
|
360
397
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
398
|
+
# Collect work items for auto issue triggers.
|
|
399
|
+
# @return [Array<WorkItem>]
|
|
400
|
+
def collect_auto_issue_work_items
|
|
401
|
+
label = @auto_processor.auto_label
|
|
402
|
+
issues = @repository_client.list_issues(labels: [label], state: "open")
|
|
366
403
|
|
|
367
|
-
|
|
368
|
-
unless
|
|
369
|
-
Aidp.log_debug("watch_runner", "review_skip_unauthorized_author", pr: detailed[:number],
|
|
370
|
-
author: detailed[:author])
|
|
371
|
-
next
|
|
372
|
-
end
|
|
404
|
+
issues.filter_map do |issue|
|
|
405
|
+
next unless issue_has_label?(issue, label)
|
|
373
406
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
rescue RepositorySafetyChecker::UnauthorizedAuthorError => e
|
|
382
|
-
Aidp.log_warn("watch_runner", "unauthorized PR author", pr: pr[:number], error: e.message)
|
|
407
|
+
WorkItem.new(
|
|
408
|
+
number: issue[:number],
|
|
409
|
+
item_type: :issue,
|
|
410
|
+
processor_type: :auto_issue,
|
|
411
|
+
label: label,
|
|
412
|
+
data: issue
|
|
413
|
+
)
|
|
383
414
|
end
|
|
415
|
+
rescue => e
|
|
416
|
+
Aidp.log_error("watch_runner", "collect_auto_issue_items_failed", error: e.message)
|
|
417
|
+
[]
|
|
384
418
|
end
|
|
385
419
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
420
|
+
# Collect work items for review triggers.
|
|
421
|
+
# @return [Array<WorkItem>]
|
|
422
|
+
def collect_review_work_items
|
|
423
|
+
label = @review_processor.review_label
|
|
424
|
+
prs = @repository_client.list_pull_requests(labels: [label], state: "open")
|
|
390
425
|
|
|
391
|
-
prs.
|
|
392
|
-
unless pr_has_label?(pr,
|
|
393
|
-
Aidp.log_debug("watch_runner", "auto_pr_skip_label_mismatch", pr: pr[:number], labels: pr[:labels])
|
|
394
|
-
next
|
|
395
|
-
end
|
|
396
|
-
|
|
397
|
-
detailed = @repository_client.fetch_pull_request(pr[:number])
|
|
426
|
+
prs.filter_map do |pr|
|
|
427
|
+
next unless pr_has_label?(pr, label)
|
|
398
428
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
429
|
+
WorkItem.new(
|
|
430
|
+
number: pr[:number],
|
|
431
|
+
item_type: :pr,
|
|
432
|
+
processor_type: :review,
|
|
433
|
+
label: label,
|
|
434
|
+
data: pr
|
|
435
|
+
)
|
|
436
|
+
end
|
|
437
|
+
rescue => e
|
|
438
|
+
Aidp.log_error("watch_runner", "collect_review_items_failed", error: e.message)
|
|
439
|
+
[]
|
|
440
|
+
end
|
|
404
441
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
end
|
|
442
|
+
# Collect work items for CI fix triggers.
|
|
443
|
+
# @return [Array<WorkItem>]
|
|
444
|
+
def collect_ci_fix_work_items
|
|
445
|
+
label = @ci_fix_processor.ci_fix_label
|
|
446
|
+
prs = @repository_client.list_pull_requests(labels: [label], state: "open")
|
|
411
447
|
|
|
412
|
-
|
|
413
|
-
unless
|
|
414
|
-
post_detection_comment(item_type: :pr, number: detailed[:number], label: auto_label)
|
|
415
|
-
end
|
|
448
|
+
prs.filter_map do |pr|
|
|
449
|
+
next unless pr_has_label?(pr, label)
|
|
416
450
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
451
|
+
WorkItem.new(
|
|
452
|
+
number: pr[:number],
|
|
453
|
+
item_type: :pr,
|
|
454
|
+
processor_type: :ci_fix,
|
|
455
|
+
label: label,
|
|
456
|
+
data: pr
|
|
457
|
+
)
|
|
421
458
|
end
|
|
459
|
+
rescue => e
|
|
460
|
+
Aidp.log_error("watch_runner", "collect_ci_fix_items_failed", error: e.message)
|
|
461
|
+
[]
|
|
422
462
|
end
|
|
423
463
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
prs.
|
|
429
|
-
unless pr_has_label?(pr, ci_fix_label)
|
|
430
|
-
Aidp.log_debug("watch_runner", "ci_fix_skip_label_mismatch", pr: pr[:number], labels: pr[:labels])
|
|
431
|
-
next
|
|
432
|
-
end
|
|
464
|
+
# Collect work items for auto PR triggers.
|
|
465
|
+
# @return [Array<WorkItem>]
|
|
466
|
+
def collect_auto_pr_work_items
|
|
467
|
+
label = @auto_pr_processor.auto_label
|
|
468
|
+
prs = @repository_client.list_pull_requests(labels: [label], state: "open")
|
|
433
469
|
|
|
434
|
-
|
|
470
|
+
prs.filter_map do |pr|
|
|
471
|
+
next unless pr_has_label?(pr, label)
|
|
435
472
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
473
|
+
WorkItem.new(
|
|
474
|
+
number: pr[:number],
|
|
475
|
+
item_type: :pr,
|
|
476
|
+
processor_type: :auto_pr,
|
|
477
|
+
label: label,
|
|
478
|
+
data: pr
|
|
479
|
+
)
|
|
480
|
+
end
|
|
481
|
+
rescue => e
|
|
482
|
+
Aidp.log_error("watch_runner", "collect_auto_pr_items_failed", error: e.message)
|
|
483
|
+
[]
|
|
484
|
+
end
|
|
441
485
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
end
|
|
486
|
+
# Collect work items for change request triggers.
|
|
487
|
+
# @return [Array<WorkItem>]
|
|
488
|
+
def collect_change_request_work_items
|
|
489
|
+
label = @change_request_processor.change_request_label
|
|
490
|
+
prs = @repository_client.list_pull_requests(labels: [label], state: "open")
|
|
448
491
|
|
|
449
|
-
|
|
450
|
-
unless
|
|
451
|
-
post_detection_comment(item_type: :pr, number: detailed[:number], label: ci_fix_label)
|
|
452
|
-
end
|
|
492
|
+
prs.filter_map do |pr|
|
|
493
|
+
next unless pr_has_label?(pr, label)
|
|
453
494
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
495
|
+
WorkItem.new(
|
|
496
|
+
number: pr[:number],
|
|
497
|
+
item_type: :pr,
|
|
498
|
+
processor_type: :change_request,
|
|
499
|
+
label: label,
|
|
500
|
+
data: pr
|
|
501
|
+
)
|
|
458
502
|
end
|
|
503
|
+
rescue => e
|
|
504
|
+
Aidp.log_error("watch_runner", "collect_change_request_items_failed", error: e.message)
|
|
505
|
+
[]
|
|
459
506
|
end
|
|
460
507
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
prs.
|
|
466
|
-
unless pr_has_label?(pr, change_request_label)
|
|
467
|
-
Aidp.log_debug("watch_runner", "change_request_skip_label_mismatch", pr: pr[:number], labels: pr[:labels])
|
|
468
|
-
next
|
|
469
|
-
end
|
|
508
|
+
# Fetch the numbers of all paused items (with aidp-needs-input label).
|
|
509
|
+
# @return [Array<Integer>] Issue/PR numbers that are paused
|
|
510
|
+
def fetch_paused_item_numbers
|
|
511
|
+
issues = @repository_client.list_issues(labels: [@needs_input_label], state: "open")
|
|
512
|
+
prs = @repository_client.list_pull_requests(labels: [@needs_input_label], state: "open")
|
|
470
513
|
|
|
471
|
-
|
|
514
|
+
paused = issues.map { |i| i[:number] } + prs.map { |p| p[:number] }
|
|
472
515
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
Aidp.log_debug("watch_runner", "change_request_skip_in_progress", pr: detailed[:number])
|
|
476
|
-
next
|
|
477
|
-
end
|
|
516
|
+
Aidp.log_debug("watch_runner", "fetch_paused_items",
|
|
517
|
+
count: paused.size, numbers: paused)
|
|
478
518
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
next
|
|
484
|
-
end
|
|
485
|
-
|
|
486
|
-
# Check if detection comment already posted (deduplication)
|
|
487
|
-
unless @state_extractor.detection_comment_posted?(detailed, change_request_label)
|
|
488
|
-
post_detection_comment(item_type: :pr, number: detailed[:number], label: change_request_label)
|
|
489
|
-
end
|
|
490
|
-
|
|
491
|
-
Aidp.log_debug("watch_runner", "change_request_process", pr: detailed[:number])
|
|
492
|
-
@change_request_processor.process(detailed)
|
|
493
|
-
rescue RepositorySafetyChecker::UnauthorizedAuthorError => e
|
|
494
|
-
Aidp.log_warn("watch_runner", "unauthorized PR author", pr: pr[:number], error: e.message)
|
|
495
|
-
end
|
|
519
|
+
paused
|
|
520
|
+
rescue => e
|
|
521
|
+
Aidp.log_error("watch_runner", "fetch_paused_items_failed", error: e.message)
|
|
522
|
+
[]
|
|
496
523
|
end
|
|
497
524
|
|
|
498
525
|
def issue_has_label?(issue, label)
|
|
@@ -684,6 +711,60 @@ module Aidp
|
|
|
684
711
|
Aidp.log_error("watch_runner", "feedback_collection_failed", error: e.message)
|
|
685
712
|
display_message("⚠️ Feedback collection failed: #{e.message}", type: :warn) if @verbose
|
|
686
713
|
end
|
|
714
|
+
|
|
715
|
+
# Process worktree cleanup if due (issue #367)
|
|
716
|
+
# Runs periodically based on configured frequency (daily/weekly)
|
|
717
|
+
def process_worktree_cleanup
|
|
718
|
+
return unless @worktree_cleanup_job.enabled?
|
|
719
|
+
|
|
720
|
+
last_cleanup = @state_store.last_worktree_cleanup
|
|
721
|
+
return unless @worktree_cleanup_job.cleanup_due?(last_cleanup)
|
|
722
|
+
|
|
723
|
+
Aidp.log_debug("watch_runner", "worktree_cleanup.checking",
|
|
724
|
+
last_cleanup: last_cleanup&.iso8601,
|
|
725
|
+
frequency: @worktree_cleanup_job.cleanup_interval_seconds)
|
|
726
|
+
|
|
727
|
+
result = @worktree_cleanup_job.execute
|
|
728
|
+
|
|
729
|
+
@state_store.record_worktree_cleanup(
|
|
730
|
+
cleaned: result[:cleaned],
|
|
731
|
+
skipped: result[:skipped],
|
|
732
|
+
errors: result[:errors]
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
if result[:cleaned] > 0 || @verbose
|
|
736
|
+
display_message("🧹 Worktree cleanup: #{result[:cleaned]} cleaned, #{result[:skipped]} skipped",
|
|
737
|
+
type: :info)
|
|
738
|
+
end
|
|
739
|
+
rescue => e
|
|
740
|
+
Aidp.log_error("watch_runner", "worktree_cleanup_failed", error: e.message)
|
|
741
|
+
display_message("⚠️ Worktree cleanup failed: #{e.message}", type: :warn) if @verbose
|
|
742
|
+
end
|
|
743
|
+
|
|
744
|
+
# Process worktree reconciliation for dirty worktrees
|
|
745
|
+
# Resumes interrupted work or reconciles merged PRs
|
|
746
|
+
def process_worktree_reconciliation
|
|
747
|
+
return unless @worktree_reconciler.enabled?
|
|
748
|
+
return unless @worktree_reconciler.reconciliation_due?(@last_reconcile_at)
|
|
749
|
+
|
|
750
|
+
Aidp.log_debug("watch_runner", "worktree_reconciliation.checking",
|
|
751
|
+
last_reconcile: @last_reconcile_at&.iso8601)
|
|
752
|
+
|
|
753
|
+
result = @worktree_reconciler.execute
|
|
754
|
+
@last_reconcile_at = Time.now
|
|
755
|
+
|
|
756
|
+
total_actions = result[:resumed] + result[:reconciled] + result[:cleaned]
|
|
757
|
+
if total_actions > 0 || @verbose
|
|
758
|
+
display_message(
|
|
759
|
+
"🔄 Worktree reconciliation: #{result[:resumed]} resumed, " \
|
|
760
|
+
"#{result[:reconciled]} reconciled, #{result[:cleaned]} cleaned",
|
|
761
|
+
type: :info
|
|
762
|
+
)
|
|
763
|
+
end
|
|
764
|
+
rescue => e
|
|
765
|
+
Aidp.log_error("watch_runner", "worktree_reconciliation_failed", error: e.message)
|
|
766
|
+
display_message("⚠️ Worktree reconciliation failed: #{e.message}", type: :warn) if @verbose
|
|
767
|
+
end
|
|
687
768
|
end
|
|
688
769
|
end
|
|
689
770
|
end
|