aidp 0.34.0 → 0.39.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +14 -14
- data/lib/aidp/agent_harness_adapter.rb +309 -0
- data/lib/aidp/cli/issue_importer.rb +20 -6
- data/lib/aidp/cli/jobs_command.rb +1 -1
- data/lib/aidp/cli/models_command.rb +9 -16
- data/lib/aidp/cli/prompts_command.rb +253 -0
- data/lib/aidp/cli/security_command.rb +4 -10
- data/lib/aidp/cli/storage_command.rb +277 -0
- data/lib/aidp/cli/temporal_command.rb +460 -0
- data/lib/aidp/cli.rb +24 -2
- data/lib/aidp/config.rb +15 -0
- data/lib/aidp/config_paths.rb +12 -2
- data/lib/aidp/database/migrations.rb +114 -0
- data/lib/aidp/database/repositories/checkpoint_repository.rb +174 -0
- data/lib/aidp/database/repositories/deprecated_models_repository.rb +206 -0
- data/lib/aidp/database/repositories/evaluation_repository.rb +208 -0
- data/lib/aidp/database/repositories/harness_state_repository.rb +140 -0
- data/lib/aidp/database/repositories/job_repository.rb +278 -0
- data/lib/aidp/database/repositories/model_cache_repository.rb +194 -0
- data/lib/aidp/database/repositories/progress_repository.rb +210 -0
- data/lib/aidp/database/repositories/prompt_archive_repository.rb +195 -0
- data/lib/aidp/database/repositories/prompt_feedback_repository.rb +266 -0
- data/lib/aidp/database/repositories/provider_info_cache_repository.rb +208 -0
- data/lib/aidp/database/repositories/provider_metrics_repository.rb +245 -0
- data/lib/aidp/database/repositories/secrets_repository.rb +187 -0
- data/lib/aidp/database/repositories/task_repository.rb +231 -0
- data/lib/aidp/database/repositories/template_version_repository.rb +383 -0
- data/lib/aidp/database/repositories/watch_state_repository.rb +472 -0
- data/lib/aidp/database/repositories/workstream_repository.rb +310 -0
- data/lib/aidp/database/repositories/worktree_repository.rb +240 -0
- data/lib/aidp/database/repository.rb +166 -0
- data/lib/aidp/database/schema.rb +347 -0
- data/lib/aidp/database/storage_migrator.rb +693 -0
- data/lib/aidp/database.rb +139 -0
- data/lib/aidp/debug_mixin.rb +2 -2
- data/lib/aidp/execute/checkpoint.rb +26 -0
- data/lib/aidp/execute/checkpoint_display.rb +16 -0
- data/lib/aidp/execute/prompt_evaluator.rb +30 -57
- data/lib/aidp/execute/work_loop_runner.rb +294 -46
- data/lib/aidp/firewall/provider_requirements_collector.rb +7 -23
- data/lib/aidp/harness/agent_harness_provider_manager.rb +275 -0
- data/lib/aidp/harness/ai_decision_engine.rb +78 -296
- data/lib/aidp/harness/config_schema.rb +59 -0
- data/lib/aidp/harness/configuration.rb +229 -10
- data/lib/aidp/harness/deprecation_cache.rb +53 -11
- data/lib/aidp/harness/enhanced_runner.rb +1 -1
- data/lib/aidp/harness/error_handler.rb +11 -5
- data/lib/aidp/harness/provider_config.rb +36 -0
- data/lib/aidp/harness/provider_factory.rb +26 -24
- data/lib/aidp/harness/provider_info.rb +9 -5
- data/lib/aidp/harness/provider_manager.rb +2 -2
- data/lib/aidp/harness/provider_metrics.rb +103 -1
- data/lib/aidp/harness/rspec_command_optimizer.rb +166 -0
- data/lib/aidp/harness/ruby_llm_registry.rb +97 -8
- data/lib/aidp/harness/runner.rb +7 -2
- data/lib/aidp/harness/state_manager.rb +1 -1
- data/lib/aidp/harness/test_runner.rb +167 -11
- data/lib/aidp/harness/thinking_depth_manager.rb +564 -5
- data/lib/aidp/harness/ui.rb +11 -0
- data/lib/aidp/harness/usage_limit.rb +179 -0
- data/lib/aidp/harness/usage_limit_enforcer.rb +240 -0
- data/lib/aidp/harness/usage_limit_tracker.rb +308 -0
- data/lib/aidp/harness/usage_period.rb +234 -0
- data/lib/aidp/init/agent_instructions_generator.rb +317 -0
- data/lib/aidp/init/runner.rb +25 -4
- data/lib/aidp/interfaces/activity_monitor_interface.rb +234 -0
- data/lib/aidp/interfaces/binary_checker_interface.rb +232 -0
- data/lib/aidp/interfaces/command_executor_interface.rb +216 -0
- data/lib/aidp/interfaces/logger_interface.rb +150 -0
- data/lib/aidp/interfaces/ui_interface.rb +254 -0
- data/lib/aidp/logger.rb +25 -7
- data/lib/aidp/orchestration_adapter.rb +279 -0
- data/lib/aidp/pr_worktree_manager.rb +4 -15
- data/lib/aidp/prompts/feedback_collector.rb +198 -0
- data/lib/aidp/prompts/prompt_template_manager.rb +415 -0
- data/lib/aidp/prompts/template_evolver.rb +305 -0
- data/lib/aidp/prompts/template_version_manager.rb +325 -0
- data/lib/aidp/setup/in_memory_config_adapter.rb +257 -0
- data/lib/aidp/setup/in_memory_config_manager.rb +262 -0
- data/lib/aidp/setup/wizard.rb +616 -176
- data/lib/aidp/shell_executor.rb +58 -0
- data/lib/aidp/style_guide/selector.rb +56 -1
- data/lib/aidp/temporal/activities/analyze_issue_activity.rb +198 -0
- data/lib/aidp/temporal/activities/analyze_sub_task_activity.rb +163 -0
- data/lib/aidp/temporal/activities/base_activity.rb +78 -0
- data/lib/aidp/temporal/activities/create_plan_activity.rb +115 -0
- data/lib/aidp/temporal/activities/create_pr_activity.rb +193 -0
- data/lib/aidp/temporal/activities/create_prompt_activity.rb +118 -0
- data/lib/aidp/temporal/activities/diagnose_failure_activity.rb +228 -0
- data/lib/aidp/temporal/activities/prepare_next_iteration_activity.rb +147 -0
- data/lib/aidp/temporal/activities/record_checkpoint_activity.rb +81 -0
- data/lib/aidp/temporal/activities/run_agent_activity.rb +139 -0
- data/lib/aidp/temporal/activities/run_tests_activity.rb +114 -0
- data/lib/aidp/temporal/activities/run_work_loop_iteration_activity.rb +249 -0
- data/lib/aidp/temporal/configuration.rb +169 -0
- data/lib/aidp/temporal/connection.rb +93 -0
- data/lib/aidp/temporal/worker.rb +119 -0
- data/lib/aidp/temporal/workflow_client.rb +171 -0
- data/lib/aidp/temporal/workflows/base_workflow.rb +73 -0
- data/lib/aidp/temporal/workflows/issue_to_pr_workflow.rb +278 -0
- data/lib/aidp/temporal/workflows/sub_issue_workflow.rb +253 -0
- data/lib/aidp/temporal/workflows/work_loop_workflow.rb +321 -0
- data/lib/aidp/temporal.rb +120 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/base_processor.rb +35 -0
- data/lib/aidp/watch/build_processor.rb +132 -0
- data/lib/aidp/watch/github_state_extractor.rb +0 -8
- data/lib/aidp/watch/rebase_processor.rb +245 -0
- data/lib/aidp/watch/round_robin_scheduler.rb +150 -0
- data/lib/aidp/watch/runner.rb +366 -285
- data/lib/aidp/watch/state_store.rb +80 -0
- data/lib/aidp/watch/work_item.rb +96 -0
- data/lib/aidp/watch/worktree_cleanup_job.rb +226 -0
- data/lib/aidp/watch/worktree_reconciler.rb +643 -0
- data/lib/aidp/workflows/guided_agent.rb +1 -1
- data/lib/aidp/worktree.rb +62 -0
- data/templates/aidp.yml.example +70 -0
- metadata +118 -16
- data/lib/aidp/analyze/json_file_storage.rb +0 -292
- data/lib/aidp/execute/future_work_backlog.rb +0 -411
- data/lib/aidp/providers/adapter.rb +0 -239
- data/lib/aidp/providers/aider.rb +0 -264
- data/lib/aidp/providers/anthropic.rb +0 -614
- data/lib/aidp/providers/base.rb +0 -617
- data/lib/aidp/providers/capability_registry.rb +0 -204
- data/lib/aidp/providers/codex.rb +0 -267
- data/lib/aidp/providers/cursor.rb +0 -304
- data/lib/aidp/providers/error_taxonomy.rb +0 -195
- data/lib/aidp/providers/gemini.rb +0 -110
- data/lib/aidp/providers/github_copilot.rb +0 -227
- data/lib/aidp/providers/kilocode.rb +0 -157
- data/lib/aidp/providers/opencode.rb +0 -139
|
@@ -397,8 +397,88 @@ module Aidp
|
|
|
397
397
|
}
|
|
398
398
|
end
|
|
399
399
|
|
|
400
|
+
# Worktree cleanup tracking methods (issue #367)
|
|
401
|
+
|
|
402
|
+
# Get the timestamp of last worktree cleanup
|
|
403
|
+
# @return [Time, nil] Time of last cleanup or nil if never run
|
|
404
|
+
def last_worktree_cleanup
|
|
405
|
+
timestamp = worktree_cleanup_state["last_cleanup_at"]
|
|
406
|
+
return nil unless timestamp
|
|
407
|
+
|
|
408
|
+
Time.parse(timestamp)
|
|
409
|
+
rescue ArgumentError
|
|
410
|
+
nil
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# Record a worktree cleanup run
|
|
414
|
+
# @param cleaned [Integer] Number of worktrees cleaned
|
|
415
|
+
# @param skipped [Integer] Number of worktrees skipped
|
|
416
|
+
# @param errors [Array<Hash>] List of errors encountered
|
|
417
|
+
def record_worktree_cleanup(cleaned:, skipped:, errors: [])
|
|
418
|
+
state["worktree_cleanup"] = {
|
|
419
|
+
"last_cleanup_at" => Time.now.utc.iso8601,
|
|
420
|
+
"last_cleaned_count" => cleaned,
|
|
421
|
+
"last_skipped_count" => skipped,
|
|
422
|
+
"last_errors" => errors.map { |e| stringify_keys(e) }
|
|
423
|
+
}
|
|
424
|
+
save!
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
# Get the full worktree cleanup state
|
|
428
|
+
# @return [Hash] Cleanup state data
|
|
429
|
+
def worktree_cleanup_data
|
|
430
|
+
worktree_cleanup_state.dup
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
# Round-robin scheduling state methods (issue #434)
|
|
434
|
+
|
|
435
|
+
# Get the last processed work item key for round-robin rotation
|
|
436
|
+
# @return [String, nil] Last processed key or nil if never processed
|
|
437
|
+
def round_robin_last_key
|
|
438
|
+
round_robin_state["last_key"]
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
# Get the timestamp of the last round-robin processing
|
|
442
|
+
# @return [Time, nil] Time of last processing or nil
|
|
443
|
+
def round_robin_last_processed_at
|
|
444
|
+
timestamp = round_robin_state["processed_at"]
|
|
445
|
+
return nil unless timestamp
|
|
446
|
+
|
|
447
|
+
Time.parse(timestamp)
|
|
448
|
+
rescue ArgumentError
|
|
449
|
+
nil
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
# Record the round-robin rotation position
|
|
453
|
+
# @param last_key [String] Key of the last processed work item
|
|
454
|
+
# @param processed_at [String] ISO8601 timestamp of processing
|
|
455
|
+
def record_round_robin_position(last_key:, processed_at:)
|
|
456
|
+
state["round_robin"] = {
|
|
457
|
+
"last_key" => last_key,
|
|
458
|
+
"processed_at" => processed_at
|
|
459
|
+
}
|
|
460
|
+
save!
|
|
461
|
+
|
|
462
|
+
Aidp.log_debug("state_store", "round_robin_position_recorded",
|
|
463
|
+
last_key: last_key, processed_at: processed_at)
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
# Get the full round-robin state
|
|
467
|
+
# @return [Hash] Round-robin state data
|
|
468
|
+
def round_robin_data
|
|
469
|
+
round_robin_state.dup
|
|
470
|
+
end
|
|
471
|
+
|
|
400
472
|
private
|
|
401
473
|
|
|
474
|
+
def round_robin_state
|
|
475
|
+
state["round_robin"] ||= {}
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def worktree_cleanup_state
|
|
479
|
+
state["worktree_cleanup"] ||= {}
|
|
480
|
+
end
|
|
481
|
+
|
|
402
482
|
def ensure_directory
|
|
403
483
|
FileUtils.mkdir_p(File.dirname(@path))
|
|
404
484
|
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aidp
|
|
4
|
+
module Watch
|
|
5
|
+
# Value object representing a work item in the round-robin queue.
|
|
6
|
+
# Encapsulates issue/PR data with metadata needed for scheduling.
|
|
7
|
+
class WorkItem
|
|
8
|
+
include Comparable
|
|
9
|
+
|
|
10
|
+
ITEM_TYPES = %i[issue pr].freeze
|
|
11
|
+
PROCESSOR_TYPES = %i[plan build auto_issue review ci_fix auto_pr change_request].freeze
|
|
12
|
+
|
|
13
|
+
# Priority levels for scheduling (lower = higher priority)
|
|
14
|
+
PRIORITY_PLAN = 1
|
|
15
|
+
PRIORITY_NORMAL = 2
|
|
16
|
+
|
|
17
|
+
attr_reader :number, :item_type, :processor_type, :label, :data, :priority
|
|
18
|
+
|
|
19
|
+
def initialize(number:, item_type:, processor_type:, label:, data:, priority: nil)
|
|
20
|
+
validate_item_type!(item_type)
|
|
21
|
+
validate_processor_type!(processor_type)
|
|
22
|
+
|
|
23
|
+
@number = number
|
|
24
|
+
@item_type = item_type
|
|
25
|
+
@processor_type = processor_type
|
|
26
|
+
@label = label
|
|
27
|
+
@data = data
|
|
28
|
+
@priority = priority || default_priority
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Unique key for this work item (used for queue tracking)
|
|
32
|
+
# @return [String] Unique identifier
|
|
33
|
+
def key
|
|
34
|
+
"#{item_type}_#{number}_#{processor_type}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Check if this work item is for the same issue/PR
|
|
38
|
+
# @param other [WorkItem] Other work item to compare
|
|
39
|
+
# @return [Boolean] True if same entity
|
|
40
|
+
def same_entity?(other)
|
|
41
|
+
item_type == other.item_type && number == other.number
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Check if item is an issue (vs PR)
|
|
45
|
+
# @return [Boolean]
|
|
46
|
+
def issue?
|
|
47
|
+
item_type == :issue
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Check if item is a PR
|
|
51
|
+
# @return [Boolean]
|
|
52
|
+
def pr?
|
|
53
|
+
item_type == :pr
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Check if this is a high-priority plan item
|
|
57
|
+
# @return [Boolean]
|
|
58
|
+
def plan?
|
|
59
|
+
processor_type == :plan
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def to_h
|
|
63
|
+
{
|
|
64
|
+
number: number,
|
|
65
|
+
item_type: item_type,
|
|
66
|
+
processor_type: processor_type,
|
|
67
|
+
label: label,
|
|
68
|
+
priority: priority
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Comparison for sorting by priority
|
|
73
|
+
def <=>(other)
|
|
74
|
+
priority <=> other.priority
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def default_priority
|
|
80
|
+
plan? ? PRIORITY_PLAN : PRIORITY_NORMAL
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def validate_item_type!(type)
|
|
84
|
+
return if ITEM_TYPES.include?(type)
|
|
85
|
+
|
|
86
|
+
raise ArgumentError, "Invalid item_type: #{type}. Must be one of: #{ITEM_TYPES.join(", ")}"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def validate_processor_type!(type)
|
|
90
|
+
return if PROCESSOR_TYPES.include?(type)
|
|
91
|
+
|
|
92
|
+
raise ArgumentError, "Invalid processor_type: #{type}. Must be one of: #{PROCESSOR_TYPES.join(", ")}"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
require "time"
|
|
5
|
+
|
|
6
|
+
module Aidp
|
|
7
|
+
module Watch
|
|
8
|
+
# Handles automatic cleanup of worktrees whose branches have been merged into main.
|
|
9
|
+
# Runs periodically during watch mode according to configured frequency.
|
|
10
|
+
#
|
|
11
|
+
# Requirements (from issue #367):
|
|
12
|
+
# - Skip worktrees that are not "clean" (have uncommitted changes)
|
|
13
|
+
# - Run silently with log entries
|
|
14
|
+
# - Skip failed worktrees and retry later
|
|
15
|
+
# - Default to weekly cleanup frequency
|
|
16
|
+
class WorktreeCleanupJob
|
|
17
|
+
SECONDS_PER_DAY = 86_400
|
|
18
|
+
SECONDS_PER_WEEK = 604_800
|
|
19
|
+
|
|
20
|
+
# @param project_dir [String] Project root directory
|
|
21
|
+
# @param config [Hash] Cleanup configuration from aidp.yml
|
|
22
|
+
def initialize(project_dir:, config: {})
|
|
23
|
+
@project_dir = project_dir
|
|
24
|
+
@config = normalize_config(config)
|
|
25
|
+
Aidp.log_debug("worktree_cleanup_job", "initialized",
|
|
26
|
+
project_dir: project_dir,
|
|
27
|
+
config: @config)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Check if cleanup is due based on last cleanup time and configured frequency
|
|
31
|
+
#
|
|
32
|
+
# @param last_cleanup_at [Time, nil] Time of last cleanup run
|
|
33
|
+
# @return [Boolean] True if cleanup should run
|
|
34
|
+
def cleanup_due?(last_cleanup_at)
|
|
35
|
+
return true unless enabled?
|
|
36
|
+
return true if last_cleanup_at.nil?
|
|
37
|
+
|
|
38
|
+
elapsed = Time.now - last_cleanup_at
|
|
39
|
+
elapsed >= cleanup_interval_seconds
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Execute cleanup of merged worktrees
|
|
43
|
+
#
|
|
44
|
+
# @return [Hash] Result containing cleaned count, skipped count, and errors
|
|
45
|
+
def execute
|
|
46
|
+
Aidp.log_info("worktree_cleanup_job", "cleanup_started",
|
|
47
|
+
base_branch: base_branch,
|
|
48
|
+
delete_branch: delete_branch?)
|
|
49
|
+
|
|
50
|
+
return {cleaned: 0, skipped: 0, errors: []} unless enabled?
|
|
51
|
+
|
|
52
|
+
worktrees = list_worktrees
|
|
53
|
+
Aidp.log_debug("worktree_cleanup_job", "worktrees_found", count: worktrees.size)
|
|
54
|
+
|
|
55
|
+
cleaned = 0
|
|
56
|
+
skipped = 0
|
|
57
|
+
errors = []
|
|
58
|
+
|
|
59
|
+
worktrees.each do |worktree|
|
|
60
|
+
result = process_worktree(worktree)
|
|
61
|
+
case result[:status]
|
|
62
|
+
when :cleaned
|
|
63
|
+
cleaned += 1
|
|
64
|
+
when :skipped
|
|
65
|
+
skipped += 1
|
|
66
|
+
when :error
|
|
67
|
+
errors << {slug: worktree[:slug], error: result[:error]}
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
Aidp.log_info("worktree_cleanup_job", "cleanup_completed",
|
|
72
|
+
cleaned: cleaned,
|
|
73
|
+
skipped: skipped,
|
|
74
|
+
errors_count: errors.size)
|
|
75
|
+
|
|
76
|
+
{cleaned: cleaned, skipped: skipped, errors: errors}
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Check if cleanup is enabled in configuration
|
|
80
|
+
#
|
|
81
|
+
# @return [Boolean]
|
|
82
|
+
def enabled?
|
|
83
|
+
@config[:enabled]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Get the configured cleanup interval in seconds
|
|
87
|
+
#
|
|
88
|
+
# @return [Integer]
|
|
89
|
+
def cleanup_interval_seconds
|
|
90
|
+
case @config[:frequency]
|
|
91
|
+
when "daily"
|
|
92
|
+
SECONDS_PER_DAY
|
|
93
|
+
when "weekly"
|
|
94
|
+
SECONDS_PER_WEEK
|
|
95
|
+
else
|
|
96
|
+
SECONDS_PER_WEEK
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
def normalize_config(config)
|
|
103
|
+
{
|
|
104
|
+
enabled: config.fetch(:enabled, config.fetch("enabled", true)),
|
|
105
|
+
frequency: config.fetch(:frequency, config.fetch("frequency", "weekly")),
|
|
106
|
+
base_branch: config.fetch(:base_branch, config.fetch("base_branch", "main")),
|
|
107
|
+
delete_branch: config.fetch(:delete_branch, config.fetch("delete_branch", true))
|
|
108
|
+
}
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def base_branch
|
|
112
|
+
@config[:base_branch]
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def delete_branch?
|
|
116
|
+
@config[:delete_branch]
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def list_worktrees
|
|
120
|
+
Aidp::Worktree.list(project_dir: @project_dir)
|
|
121
|
+
rescue => e
|
|
122
|
+
Aidp.log_error("worktree_cleanup_job", "list_worktrees_failed", error: e.message)
|
|
123
|
+
[]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def process_worktree(worktree)
|
|
127
|
+
slug = worktree[:slug]
|
|
128
|
+
branch = worktree[:branch]
|
|
129
|
+
path = worktree[:path]
|
|
130
|
+
|
|
131
|
+
Aidp.log_debug("worktree_cleanup_job", "processing_worktree",
|
|
132
|
+
slug: slug,
|
|
133
|
+
branch: branch,
|
|
134
|
+
active: worktree[:active])
|
|
135
|
+
|
|
136
|
+
# Skip inactive worktrees (directory doesn't exist)
|
|
137
|
+
unless worktree[:active]
|
|
138
|
+
Aidp.log_debug("worktree_cleanup_job", "skipping_inactive", slug: slug)
|
|
139
|
+
return {status: :skipped, reason: "inactive"}
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Check if worktree is clean (no uncommitted changes)
|
|
143
|
+
unless worktree_clean?(path)
|
|
144
|
+
Aidp.log_debug("worktree_cleanup_job", "skipping_dirty", slug: slug)
|
|
145
|
+
return {status: :skipped, reason: "uncommitted_changes"}
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Check if branch is merged into base branch
|
|
149
|
+
unless branch_merged?(branch)
|
|
150
|
+
Aidp.log_debug("worktree_cleanup_job", "skipping_unmerged",
|
|
151
|
+
slug: slug,
|
|
152
|
+
branch: branch,
|
|
153
|
+
base_branch: base_branch)
|
|
154
|
+
return {status: :skipped, reason: "not_merged"}
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Remove the worktree
|
|
158
|
+
remove_worktree(slug)
|
|
159
|
+
rescue => e
|
|
160
|
+
Aidp.log_error("worktree_cleanup_job", "process_worktree_error",
|
|
161
|
+
slug: slug,
|
|
162
|
+
error: e.message)
|
|
163
|
+
{status: :error, error: e.message}
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def worktree_clean?(path)
|
|
167
|
+
return false unless Dir.exist?(path)
|
|
168
|
+
|
|
169
|
+
stdout, _stderr, status = Open3.capture3(
|
|
170
|
+
"git", "status", "--porcelain",
|
|
171
|
+
chdir: path
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
status.success? && stdout.strip.empty?
|
|
175
|
+
rescue => e
|
|
176
|
+
Aidp.log_error("worktree_cleanup_job", "check_clean_failed",
|
|
177
|
+
path: path,
|
|
178
|
+
error: e.message)
|
|
179
|
+
false
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def branch_merged?(branch)
|
|
183
|
+
stdout, _stderr, status = Open3.capture3(
|
|
184
|
+
"git", "branch", "--merged", base_branch,
|
|
185
|
+
chdir: @project_dir
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
return false unless status.success?
|
|
189
|
+
|
|
190
|
+
# Parse branches that are merged (one per line, with possible leading whitespace or *)
|
|
191
|
+
merged_branches = stdout.lines.map { |line| line.strip.sub(/^\*\s*/, "") }
|
|
192
|
+
merged_branches.include?(branch)
|
|
193
|
+
rescue => e
|
|
194
|
+
Aidp.log_error("worktree_cleanup_job", "check_merged_failed",
|
|
195
|
+
branch: branch,
|
|
196
|
+
error: e.message)
|
|
197
|
+
false
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def remove_worktree(slug)
|
|
201
|
+
Aidp.log_info("worktree_cleanup_job", "removing_worktree",
|
|
202
|
+
slug: slug,
|
|
203
|
+
delete_branch: delete_branch?)
|
|
204
|
+
|
|
205
|
+
Aidp::Worktree.remove(
|
|
206
|
+
slug: slug,
|
|
207
|
+
project_dir: @project_dir,
|
|
208
|
+
delete_branch: delete_branch?
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
Aidp.log_info("worktree_cleanup_job", "worktree_removed", slug: slug)
|
|
212
|
+
{status: :cleaned}
|
|
213
|
+
rescue Aidp::Worktree::WorktreeNotFound => e
|
|
214
|
+
Aidp.log_warn("worktree_cleanup_job", "worktree_not_found",
|
|
215
|
+
slug: slug,
|
|
216
|
+
error: e.message)
|
|
217
|
+
{status: :skipped, reason: "not_found"}
|
|
218
|
+
rescue => e
|
|
219
|
+
Aidp.log_error("worktree_cleanup_job", "remove_failed",
|
|
220
|
+
slug: slug,
|
|
221
|
+
error: e.message)
|
|
222
|
+
{status: :error, error: e.message}
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|