aidp 0.32.0 → 0.34.0
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 +35 -0
- data/lib/aidp/analyze/feature_analyzer.rb +322 -320
- data/lib/aidp/analyze/tree_sitter_scan.rb +3 -0
- data/lib/aidp/auto_update/coordinator.rb +97 -7
- data/lib/aidp/auto_update.rb +0 -12
- data/lib/aidp/cli/devcontainer_commands.rb +0 -5
- data/lib/aidp/cli/eval_command.rb +399 -0
- data/lib/aidp/cli/harness_command.rb +1 -1
- data/lib/aidp/cli/security_command.rb +416 -0
- data/lib/aidp/cli/tools_command.rb +6 -4
- data/lib/aidp/cli.rb +172 -4
- data/lib/aidp/comment_consolidator.rb +78 -0
- data/lib/aidp/concurrency/exec.rb +3 -0
- data/lib/aidp/concurrency.rb +0 -3
- data/lib/aidp/config.rb +113 -1
- data/lib/aidp/config_paths.rb +91 -0
- data/lib/aidp/daemon/runner.rb +8 -4
- data/lib/aidp/errors.rb +134 -0
- data/lib/aidp/evaluations/context_capture.rb +205 -0
- data/lib/aidp/evaluations/evaluation_record.rb +114 -0
- data/lib/aidp/evaluations/evaluation_storage.rb +250 -0
- data/lib/aidp/evaluations.rb +23 -0
- data/lib/aidp/execute/async_work_loop_runner.rb +4 -1
- data/lib/aidp/execute/interactive_repl.rb +6 -2
- data/lib/aidp/execute/prompt_evaluator.rb +359 -0
- data/lib/aidp/execute/repl_macros.rb +100 -1
- data/lib/aidp/execute/work_loop_runner.rb +719 -58
- data/lib/aidp/execute/work_loop_state.rb +4 -1
- data/lib/aidp/execute/workflow_selector.rb +3 -0
- data/lib/aidp/harness/ai_decision_engine.rb +79 -0
- data/lib/aidp/harness/ai_filter_factory.rb +285 -0
- data/lib/aidp/harness/capability_registry.rb +2 -0
- data/lib/aidp/harness/condition_detector.rb +3 -0
- data/lib/aidp/harness/config_loader.rb +3 -0
- data/lib/aidp/harness/config_schema.rb +97 -1
- data/lib/aidp/harness/config_validator.rb +1 -1
- data/lib/aidp/harness/configuration.rb +61 -5
- data/lib/aidp/harness/enhanced_runner.rb +14 -11
- data/lib/aidp/harness/error_handler.rb +3 -0
- data/lib/aidp/harness/filter_definition.rb +212 -0
- data/lib/aidp/harness/generated_filter_strategy.rb +197 -0
- data/lib/aidp/harness/output_filter.rb +50 -25
- data/lib/aidp/harness/output_filter_config.rb +129 -0
- data/lib/aidp/harness/provider_factory.rb +3 -0
- data/lib/aidp/harness/provider_manager.rb +96 -2
- data/lib/aidp/harness/runner.rb +5 -12
- data/lib/aidp/harness/state/persistence.rb +3 -0
- data/lib/aidp/harness/state_manager.rb +3 -0
- data/lib/aidp/harness/status_display.rb +28 -20
- data/lib/aidp/harness/test_runner.rb +179 -41
- data/lib/aidp/harness/thinking_depth_manager.rb +44 -28
- data/lib/aidp/harness/ui/enhanced_tui.rb +4 -0
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +4 -0
- data/lib/aidp/harness/ui/error_handler.rb +3 -0
- data/lib/aidp/harness/ui/job_monitor.rb +4 -0
- data/lib/aidp/harness/ui/navigation/submenu.rb +2 -2
- data/lib/aidp/harness/ui/navigation/workflow_selector.rb +6 -0
- data/lib/aidp/harness/ui/spinner_helper.rb +3 -0
- data/lib/aidp/harness/ui/workflow_controller.rb +3 -0
- data/lib/aidp/harness/user_interface.rb +3 -0
- data/lib/aidp/loader.rb +195 -0
- data/lib/aidp/logger.rb +3 -0
- data/lib/aidp/message_display.rb +31 -0
- data/lib/aidp/metadata/compiler.rb +29 -17
- data/lib/aidp/metadata/query.rb +1 -1
- data/lib/aidp/metadata/scanner.rb +8 -1
- data/lib/aidp/metadata/tool_metadata.rb +13 -13
- data/lib/aidp/metadata/validator.rb +10 -0
- data/lib/aidp/metadata.rb +16 -0
- data/lib/aidp/pr_worktree_manager.rb +20 -8
- data/lib/aidp/provider_manager.rb +4 -7
- data/lib/aidp/providers/base.rb +2 -0
- data/lib/aidp/security/rule_of_two_enforcer.rb +210 -0
- data/lib/aidp/security/secrets_proxy.rb +328 -0
- data/lib/aidp/security/secrets_registry.rb +227 -0
- data/lib/aidp/security/trifecta_state.rb +220 -0
- data/lib/aidp/security/watch_mode_handler.rb +306 -0
- data/lib/aidp/security/work_loop_adapter.rb +277 -0
- data/lib/aidp/security.rb +56 -0
- data/lib/aidp/setup/wizard.rb +283 -11
- data/lib/aidp/skills.rb +0 -5
- data/lib/aidp/storage/csv_storage.rb +3 -0
- data/lib/aidp/style_guide/selector.rb +360 -0
- data/lib/aidp/tooling_detector.rb +283 -16
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/auto_merger.rb +274 -0
- data/lib/aidp/watch/auto_pr_processor.rb +125 -7
- data/lib/aidp/watch/build_processor.rb +16 -1
- data/lib/aidp/watch/change_request_processor.rb +682 -150
- data/lib/aidp/watch/ci_fix_processor.rb +262 -4
- data/lib/aidp/watch/feedback_collector.rb +191 -0
- data/lib/aidp/watch/hierarchical_pr_strategy.rb +256 -0
- data/lib/aidp/watch/implementation_verifier.rb +142 -1
- data/lib/aidp/watch/plan_generator.rb +70 -13
- data/lib/aidp/watch/plan_processor.rb +12 -5
- data/lib/aidp/watch/projects_processor.rb +286 -0
- data/lib/aidp/watch/repository_client.rb +871 -22
- data/lib/aidp/watch/review_processor.rb +33 -6
- data/lib/aidp/watch/runner.rb +80 -29
- data/lib/aidp/watch/state_store.rb +233 -0
- data/lib/aidp/watch/sub_issue_creator.rb +221 -0
- data/lib/aidp/watch.rb +5 -7
- data/lib/aidp/workflows/guided_agent.rb +4 -0
- data/lib/aidp/workstream_cleanup.rb +0 -2
- data/lib/aidp/workstream_executor.rb +3 -4
- data/lib/aidp/worktree.rb +61 -12
- data/lib/aidp/worktree_branch_manager.rb +347 -101
- data/lib/aidp.rb +21 -106
- data/templates/implementation/iterative_implementation.md +46 -3
- metadata +91 -36
- data/lib/aidp/config/paths.rb +0 -131
|
@@ -1,124 +1,341 @@
|
|
|
1
1
|
require "json"
|
|
2
2
|
require "fileutils"
|
|
3
|
+
require "open3"
|
|
3
4
|
|
|
4
5
|
module Aidp
|
|
5
6
|
# Manages git worktrees for pull request branches
|
|
6
7
|
class WorktreeBranchManager
|
|
7
8
|
class WorktreeCreationError < StandardError; end
|
|
8
9
|
class WorktreeLookupError < StandardError; end
|
|
10
|
+
class PullRequestBranchExtractionError < StandardError; end
|
|
9
11
|
|
|
10
12
|
# Initialize with a project directory and optional logger
|
|
11
13
|
def initialize(project_dir:, logger: Aidp.logger)
|
|
12
14
|
@project_dir = project_dir
|
|
13
15
|
@logger = logger
|
|
14
16
|
@worktree_registry_path = File.join(project_dir, ".aidp", "worktrees.json")
|
|
15
|
-
@pr_worktree_registry_path = File.join(project_dir, ".aidp", "pr_worktrees.json")
|
|
16
17
|
end
|
|
17
18
|
|
|
18
|
-
# Find an existing worktree for a given branch or PR
|
|
19
|
-
def find_worktree(branch:)
|
|
20
|
-
Aidp.log_debug("worktree_branch_manager", "finding_worktree",
|
|
19
|
+
# Find an existing worktree for a given branch or PR number
|
|
20
|
+
def find_worktree(branch: nil, pr_number: nil)
|
|
21
|
+
Aidp.log_debug("worktree_branch_manager", "finding_worktree",
|
|
22
|
+
branch: branch, pr_number: pr_number,
|
|
23
|
+
caller: caller(1..1).first)
|
|
21
24
|
|
|
25
|
+
# Validate inputs
|
|
22
26
|
raise WorktreeLookupError, "Invalid git repository: #{@project_dir}" unless git_repository?
|
|
23
27
|
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
# Handle PR number by extracting branch first
|
|
29
|
+
if pr_number
|
|
30
|
+
begin
|
|
31
|
+
branch = get_pr_branch(pr_number)
|
|
32
|
+
rescue => e
|
|
33
|
+
Aidp.log_warn("worktree_branch_manager", "pr_branch_extraction_failed",
|
|
34
|
+
pr_number: pr_number, error: e.message)
|
|
35
|
+
branch = nil
|
|
36
|
+
end
|
|
30
37
|
end
|
|
31
38
|
|
|
32
|
-
#
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
# Validate branch input after extraction attempt
|
|
40
|
+
raise WorktreeLookupError, "Branch or PR number must be provided" if branch.nil?
|
|
41
|
+
|
|
42
|
+
# Comprehensive lookup strategies
|
|
43
|
+
lookup_strategies = [
|
|
44
|
+
# 1. Check in-memory registry first
|
|
45
|
+
-> {
|
|
46
|
+
registry = read_registry
|
|
47
|
+
worktree_info = registry.find { |w| w["branch"] == branch }
|
|
48
|
+
|
|
49
|
+
if worktree_info
|
|
50
|
+
worktree_path = worktree_info["path"]
|
|
51
|
+
return worktree_path if File.directory?(worktree_path)
|
|
52
|
+
end
|
|
53
|
+
nil
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
# 2. Check PR-specific registry
|
|
57
|
+
-> {
|
|
58
|
+
pr_registry = read_pr_registry
|
|
59
|
+
pr_entry = pr_registry.find { |entry| entry["head_branch"] == branch }
|
|
60
|
+
|
|
61
|
+
if pr_entry
|
|
62
|
+
worktree_path = pr_entry["path"]
|
|
63
|
+
return worktree_path if File.directory?(worktree_path)
|
|
64
|
+
end
|
|
65
|
+
nil
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
# 3. Use git worktree list for broader search
|
|
69
|
+
-> {
|
|
70
|
+
begin
|
|
71
|
+
worktree_list_output = run_git_command(["git", "worktree", "list"])
|
|
72
|
+
worktree_list_output.split("\n").each do |line|
|
|
73
|
+
path, branch_info = line.split(" ", 2)
|
|
74
|
+
return path if branch_info&.include?(branch)
|
|
75
|
+
end
|
|
76
|
+
rescue => e
|
|
77
|
+
Aidp.log_warn("worktree_branch_manager", "git_worktree_list_fallback_failed",
|
|
78
|
+
error: e.message)
|
|
79
|
+
end
|
|
80
|
+
nil
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
# 4. Scan .worktrees directory manually
|
|
84
|
+
-> {
|
|
85
|
+
worktree_base_path = File.join(@project_dir, ".worktrees")
|
|
86
|
+
candidates = Dir.glob(File.join(worktree_base_path, "**")).select do |path|
|
|
87
|
+
File.directory?(path) && path.end_with?(branch.tr("/", "_"))
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
return candidates.first unless candidates.empty?
|
|
91
|
+
nil
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
# Execute lookup strategies
|
|
96
|
+
lookup_strategies.each do |strategy|
|
|
97
|
+
result = strategy.call
|
|
98
|
+
return result if result
|
|
37
99
|
end
|
|
38
100
|
|
|
101
|
+
# If no worktree found
|
|
102
|
+
Aidp.log_warn("worktree_branch_manager", "no_worktree_found",
|
|
103
|
+
branch: branch, pr_number: pr_number)
|
|
39
104
|
nil
|
|
40
105
|
rescue => e
|
|
41
106
|
Aidp.log_error("worktree_branch_manager", "worktree_lookup_failed",
|
|
42
|
-
error: e.message, branch: branch)
|
|
107
|
+
error: e.message, branch: branch, pr_number: pr_number)
|
|
43
108
|
raise
|
|
44
109
|
end
|
|
45
110
|
|
|
46
|
-
#
|
|
47
|
-
def
|
|
48
|
-
|
|
49
|
-
|
|
111
|
+
# Create a new worktree for a branch, with advanced PR-aware handling
|
|
112
|
+
def create_worktree(
|
|
113
|
+
branch: nil,
|
|
114
|
+
pr_number: nil,
|
|
115
|
+
base_branch: "main",
|
|
116
|
+
force_recreate: false,
|
|
117
|
+
unique_suffix: nil
|
|
118
|
+
)
|
|
119
|
+
# Log detailed input for debugging
|
|
120
|
+
Aidp.log_debug("worktree_branch_manager", "create_worktree_start",
|
|
121
|
+
input_branch: branch,
|
|
122
|
+
pr_number: pr_number,
|
|
123
|
+
base_branch: base_branch,
|
|
124
|
+
force_recreate: force_recreate,
|
|
125
|
+
caller: caller(1..1).first)
|
|
126
|
+
|
|
127
|
+
# Normalize branch input
|
|
128
|
+
branch ||= get_pr_branch(pr_number) if pr_number
|
|
129
|
+
|
|
130
|
+
# Validate inputs
|
|
131
|
+
validate_branch_name!(branch)
|
|
132
|
+
raise ArgumentError, "Branch must be provided" if branch.nil? || branch.empty?
|
|
50
133
|
|
|
51
|
-
#
|
|
52
|
-
|
|
53
|
-
|
|
134
|
+
# Comprehensive worktree name generation
|
|
135
|
+
worktree_name = branch.tr("/", "_")
|
|
136
|
+
worktree_name += "-#{unique_suffix}" if unique_suffix
|
|
137
|
+
worktree_path = File.join(@project_dir, ".worktrees", worktree_name)
|
|
54
138
|
|
|
55
|
-
#
|
|
56
|
-
|
|
57
|
-
worktree_path = pr_worktree["path"]
|
|
58
|
-
return worktree_path if File.directory?(worktree_path)
|
|
59
|
-
end
|
|
139
|
+
# Ensure .worktrees directory exists
|
|
140
|
+
FileUtils.mkdir_p(File.join(@project_dir, ".worktrees"))
|
|
60
141
|
|
|
61
|
-
#
|
|
62
|
-
|
|
63
|
-
|
|
142
|
+
# Existing worktree handling - only for non-PR-specific worktrees
|
|
143
|
+
# For PR-specific worktrees, we always create a new one with unique naming
|
|
144
|
+
unless pr_number
|
|
145
|
+
existing_worktree = find_worktree(branch: branch)
|
|
146
|
+
if existing_worktree
|
|
147
|
+
# Log existing worktree details
|
|
148
|
+
Aidp.log_debug("worktree_branch_manager", "existing_worktree_found",
|
|
149
|
+
existing_path: existing_worktree,
|
|
150
|
+
force_recreate: force_recreate)
|
|
151
|
+
|
|
152
|
+
if force_recreate
|
|
153
|
+
# Attempt to remove existing worktree
|
|
154
|
+
begin
|
|
155
|
+
run_git_command(["git", "worktree", "remove", existing_worktree])
|
|
156
|
+
Aidp.log_debug("worktree_branch_manager", "existing_worktree_removed",
|
|
157
|
+
path: existing_worktree)
|
|
158
|
+
rescue => e
|
|
159
|
+
Aidp.log_warn("worktree_branch_manager", "worktree_removal_failed",
|
|
160
|
+
path: existing_worktree,
|
|
161
|
+
error: e.message)
|
|
162
|
+
end
|
|
163
|
+
else
|
|
164
|
+
return existing_worktree
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
64
168
|
|
|
65
|
-
#
|
|
66
|
-
|
|
169
|
+
# Resolve base branch with multiple strategies
|
|
170
|
+
resolved_base_branch =
|
|
171
|
+
begin
|
|
172
|
+
resolve_base_branch(branch, base_branch, pr_number)
|
|
173
|
+
rescue => e
|
|
174
|
+
Aidp.log_warn("worktree_branch_manager", "base_branch_resolution_fallback",
|
|
175
|
+
error: e.message,
|
|
176
|
+
fallback_base: base_branch)
|
|
177
|
+
base_branch
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# For PR-specific worktrees, create unique branch name to avoid conflicts
|
|
181
|
+
effective_branch = pr_number ? "#{branch}-pr-#{pr_number}" : branch
|
|
182
|
+
|
|
183
|
+
# Comprehensive worktree creation strategies
|
|
184
|
+
creation_strategies = [
|
|
185
|
+
# Strategy 1: Create with remote tracking
|
|
186
|
+
-> {
|
|
187
|
+
Aidp.log_debug("worktree_branch_manager", "create_strategy_remote_tracking",
|
|
188
|
+
branch: effective_branch,
|
|
189
|
+
base_branch: resolved_base_branch)
|
|
190
|
+
run_git_command(["git", "fetch", "origin", resolved_base_branch])
|
|
191
|
+
run_git_command(["git", "worktree", "add", "-b", effective_branch, worktree_path, "origin/#{resolved_base_branch}"])
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
# Strategy 2: Create without remote tracking
|
|
195
|
+
-> {
|
|
196
|
+
Aidp.log_debug("worktree_branch_manager", "create_strategy_local_branch",
|
|
197
|
+
branch: effective_branch,
|
|
198
|
+
base_branch: resolved_base_branch)
|
|
199
|
+
run_git_command(["git", "worktree", "add", "-b", effective_branch, worktree_path, resolved_base_branch])
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
# Strategy 3: Checkout existing branch (for when branch already exists)
|
|
203
|
+
-> {
|
|
204
|
+
Aidp.log_debug("worktree_branch_manager", "create_strategy_existing_branch",
|
|
205
|
+
branch: effective_branch,
|
|
206
|
+
base_branch: resolved_base_branch)
|
|
207
|
+
run_git_command(["git", "worktree", "add", worktree_path, effective_branch])
|
|
208
|
+
}
|
|
209
|
+
]
|
|
210
|
+
|
|
211
|
+
# Attempt worktree creation with multiple strategies
|
|
212
|
+
creation_error = nil
|
|
213
|
+
creation_strategies.each do |strategy|
|
|
214
|
+
strategy.call
|
|
215
|
+
|
|
216
|
+
# Validate worktree creation
|
|
217
|
+
unless File.directory?(worktree_path)
|
|
218
|
+
raise WorktreeCreationError, "Worktree directory not created"
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Update registries
|
|
222
|
+
update_registry(branch, worktree_path)
|
|
223
|
+
|
|
224
|
+
# Success logging
|
|
225
|
+
Aidp.log_debug("worktree_branch_manager", "worktree_created",
|
|
226
|
+
branch: branch,
|
|
227
|
+
path: worktree_path,
|
|
228
|
+
strategy: strategy.source_location&.first)
|
|
229
|
+
|
|
230
|
+
return worktree_path
|
|
231
|
+
rescue => e
|
|
232
|
+
# Log strategy failure
|
|
233
|
+
Aidp.log_warn("worktree_branch_manager", "worktree_creation_strategy_failed",
|
|
234
|
+
error: e.message,
|
|
235
|
+
strategy: strategy.source_location&.first)
|
|
236
|
+
creation_error = e
|
|
237
|
+
end
|
|
67
238
|
|
|
68
|
-
#
|
|
69
|
-
|
|
239
|
+
# If all strategies fail, raise comprehensive error
|
|
240
|
+
Aidp.log_error("worktree_branch_manager", "worktree_creation_failed",
|
|
241
|
+
branch: branch,
|
|
242
|
+
base_branch: resolved_base_branch,
|
|
243
|
+
pr_number: pr_number,
|
|
244
|
+
error: creation_error&.message)
|
|
70
245
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
Aidp.log_error("worktree_branch_manager", "pr_worktree_creation_failed",
|
|
74
|
-
error: e.message, pr_number: pr_number, head_branch: head_branch)
|
|
75
|
-
raise
|
|
246
|
+
raise WorktreeCreationError,
|
|
247
|
+
"Failed to create worktree for branch #{branch}: #{creation_error&.message}"
|
|
76
248
|
end
|
|
77
249
|
|
|
78
|
-
#
|
|
79
|
-
def
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
# Validate branch name to prevent path traversal
|
|
84
|
-
validate_branch_name!(branch)
|
|
85
|
-
|
|
86
|
-
# Check if worktree already exists
|
|
87
|
-
existing_worktree = find_worktree(branch: branch)
|
|
88
|
-
return existing_worktree if existing_worktree
|
|
89
|
-
|
|
90
|
-
# Ensure base branch exists
|
|
91
|
-
base_ref = (branch == "main") ? "main" : "refs/heads/#{base_branch}"
|
|
92
|
-
base_exists_cmd = "git show-ref --verify --quiet #{base_ref}"
|
|
250
|
+
# Find or create a worktree specifically for a PR branch
|
|
251
|
+
def find_or_create_pr_worktree(pr_number:, head_branch:, base_branch: "main")
|
|
252
|
+
# Validate required parameters
|
|
253
|
+
raise ArgumentError, "PR number is required" if pr_number.nil?
|
|
254
|
+
raise WorktreeCreationError, "Head branch is required" if head_branch.nil? || head_branch.empty?
|
|
93
255
|
|
|
94
|
-
|
|
256
|
+
# Comprehensive logging of input parameters
|
|
257
|
+
Aidp.log_debug("worktree_branch_manager", "finding_or_creating_pr_worktree",
|
|
258
|
+
pr_number: pr_number,
|
|
259
|
+
head_branch: head_branch,
|
|
260
|
+
base_branch: base_branch)
|
|
95
261
|
|
|
96
|
-
#
|
|
97
|
-
|
|
98
|
-
|
|
262
|
+
# Check PR-specific registry first
|
|
263
|
+
pr_registry = read_pr_registry
|
|
264
|
+
existing_pr_entry = pr_registry.find { |entry| entry["pr_number"] == pr_number }
|
|
265
|
+
|
|
266
|
+
# Handle existing PR worktree
|
|
267
|
+
if existing_pr_entry
|
|
268
|
+
existing_path = existing_pr_entry["path"]
|
|
269
|
+
if File.directory?(existing_path)
|
|
270
|
+
Aidp.log_debug("worktree_branch_manager", "found_existing_pr_worktree",
|
|
271
|
+
pr_number: pr_number, path: existing_path)
|
|
272
|
+
return existing_path
|
|
273
|
+
else
|
|
274
|
+
# Clean up invalid entry
|
|
275
|
+
pr_registry.reject! { |entry| entry["pr_number"] == pr_number }
|
|
276
|
+
write_pr_registry(pr_registry)
|
|
277
|
+
end
|
|
99
278
|
end
|
|
100
279
|
|
|
101
|
-
#
|
|
102
|
-
|
|
103
|
-
worktree_path = File.join(@project_dir, ".worktrees", worktree_name)
|
|
104
|
-
|
|
105
|
-
# Ensure .worktrees directory exists
|
|
106
|
-
FileUtils.mkdir_p(File.join(@project_dir, ".worktrees"))
|
|
280
|
+
# Determine unique worktree name for PR-specific worktrees
|
|
281
|
+
unique_suffix = "pr-#{pr_number}"
|
|
107
282
|
|
|
108
|
-
# Create
|
|
109
|
-
|
|
110
|
-
|
|
283
|
+
# Create worktree using advanced method
|
|
284
|
+
begin
|
|
285
|
+
worktree_path = create_worktree(
|
|
286
|
+
branch: head_branch,
|
|
287
|
+
pr_number: pr_number,
|
|
288
|
+
base_branch: base_branch,
|
|
289
|
+
unique_suffix: unique_suffix
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
# Update PR-specific registry with complete metadata
|
|
293
|
+
update_pr_registry(pr_number, head_branch, base_branch, worktree_path)
|
|
294
|
+
|
|
295
|
+
Aidp.log_debug("worktree_branch_manager", "pr_worktree_created_complete",
|
|
296
|
+
pr_number: pr_number,
|
|
297
|
+
head_branch: head_branch,
|
|
298
|
+
path: worktree_path)
|
|
299
|
+
|
|
300
|
+
worktree_path
|
|
301
|
+
rescue => e
|
|
302
|
+
# Log comprehensive error details
|
|
303
|
+
Aidp.log_error("worktree_branch_manager", "pr_worktree_creation_failed",
|
|
304
|
+
pr_number: pr_number,
|
|
305
|
+
head_branch: head_branch,
|
|
306
|
+
base_branch: base_branch,
|
|
307
|
+
error: e.message,
|
|
308
|
+
backtrace: e.backtrace.first(5))
|
|
309
|
+
|
|
310
|
+
raise WorktreeCreationError,
|
|
311
|
+
"Failed to create PR worktree for PR ##{pr_number}: #{e.message}"
|
|
312
|
+
end
|
|
313
|
+
end
|
|
111
314
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
315
|
+
# Resolve the best base branch for a given branch or PR
|
|
316
|
+
def resolve_base_branch(branch, default_base_branch, pr_number = nil)
|
|
317
|
+
# If PR number is provided, try to get base branch from GitHub
|
|
318
|
+
if pr_number
|
|
319
|
+
begin
|
|
320
|
+
pr_info_output = run_git_command(["gh", "pr", "view", pr_number.to_s, "--json", "baseRefName"])
|
|
321
|
+
pr_base_branch = JSON.parse(pr_info_output)["baseRefName"]
|
|
322
|
+
return pr_base_branch if pr_base_branch && !pr_base_branch.empty?
|
|
323
|
+
rescue => e
|
|
324
|
+
Aidp.log_warn("worktree_branch_manager", "pr_base_branch_extraction_failed",
|
|
325
|
+
pr_number: pr_number, error: e.message)
|
|
326
|
+
end
|
|
116
327
|
end
|
|
117
328
|
|
|
118
|
-
#
|
|
119
|
-
|
|
329
|
+
# Try to fetch origin branches
|
|
330
|
+
begin
|
|
331
|
+
run_git_command(["git", "fetch", "origin", default_base_branch])
|
|
332
|
+
rescue => e
|
|
333
|
+
Aidp.log_debug("worktree_branch_manager", "base_branch_fetch_failed",
|
|
334
|
+
base_branch: default_base_branch, error: e.message)
|
|
335
|
+
end
|
|
120
336
|
|
|
121
|
-
|
|
337
|
+
# Return fallback base branch
|
|
338
|
+
default_base_branch
|
|
122
339
|
end
|
|
123
340
|
|
|
124
341
|
private
|
|
@@ -137,8 +354,10 @@ module Aidp
|
|
|
137
354
|
|
|
138
355
|
def run_git_command(cmd)
|
|
139
356
|
Dir.chdir(@project_dir) do
|
|
140
|
-
|
|
141
|
-
|
|
357
|
+
# Convert string command to array for safe execution (no shell interpolation)
|
|
358
|
+
args = cmd.is_a?(Array) ? cmd : cmd.split
|
|
359
|
+
output, status = Open3.capture2e(*args)
|
|
360
|
+
raise StandardError, output unless status.success?
|
|
142
361
|
output
|
|
143
362
|
end
|
|
144
363
|
end
|
|
@@ -156,19 +375,6 @@ module Aidp
|
|
|
156
375
|
end
|
|
157
376
|
end
|
|
158
377
|
|
|
159
|
-
# Read the PR-specific worktree registry
|
|
160
|
-
def read_pr_registry
|
|
161
|
-
return [] unless File.exist?(@pr_worktree_registry_path)
|
|
162
|
-
|
|
163
|
-
begin
|
|
164
|
-
JSON.parse(File.read(@pr_worktree_registry_path))
|
|
165
|
-
rescue JSON::ParserError
|
|
166
|
-
Aidp.log_warn("worktree_branch_manager", "invalid_pr_registry",
|
|
167
|
-
path: @pr_worktree_registry_path)
|
|
168
|
-
[]
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
|
|
172
378
|
# Update the worktree registry
|
|
173
379
|
def update_registry(branch, path)
|
|
174
380
|
# Ensure .aidp directory exists
|
|
@@ -186,31 +392,71 @@ module Aidp
|
|
|
186
392
|
"created_at" => Time.now.to_i
|
|
187
393
|
}
|
|
188
394
|
|
|
189
|
-
# Write updated registry
|
|
190
395
|
File.write(@worktree_registry_path, JSON.pretty_generate(registry))
|
|
191
396
|
end
|
|
192
397
|
|
|
193
|
-
#
|
|
194
|
-
def
|
|
398
|
+
# Read the PR-specific worktree registry
|
|
399
|
+
def read_pr_registry
|
|
400
|
+
pr_registry_path = File.join(@project_dir, ".aidp", "pr_worktrees.json")
|
|
401
|
+
return [] unless File.exist?(pr_registry_path)
|
|
402
|
+
|
|
403
|
+
begin
|
|
404
|
+
JSON.parse(File.read(pr_registry_path))
|
|
405
|
+
rescue JSON::ParserError
|
|
406
|
+
Aidp.log_warn("worktree_branch_manager", "invalid_pr_registry",
|
|
407
|
+
path: pr_registry_path)
|
|
408
|
+
[]
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# Write the PR-specific worktree registry
|
|
413
|
+
def write_pr_registry(registry)
|
|
414
|
+
pr_registry_path = File.join(@project_dir, ".aidp", "pr_worktrees.json")
|
|
415
|
+
|
|
195
416
|
# Ensure .aidp directory exists
|
|
196
|
-
FileUtils.mkdir_p(File.dirname(
|
|
417
|
+
FileUtils.mkdir_p(File.dirname(pr_registry_path))
|
|
418
|
+
|
|
419
|
+
File.write(pr_registry_path, JSON.pretty_generate(registry))
|
|
420
|
+
end
|
|
197
421
|
|
|
198
|
-
|
|
422
|
+
# Update the PR-specific worktree registry
|
|
423
|
+
def update_pr_registry(pr_number, head_branch, base_branch, path)
|
|
424
|
+
pr_registry = read_pr_registry
|
|
199
425
|
|
|
200
|
-
# Remove existing entries for the same PR
|
|
201
|
-
|
|
426
|
+
# Remove existing entries for the same PR number
|
|
427
|
+
pr_registry.reject! { |entry| entry["pr_number"] == pr_number }
|
|
202
428
|
|
|
203
429
|
# Add new entry
|
|
204
|
-
|
|
430
|
+
pr_registry << {
|
|
205
431
|
"pr_number" => pr_number,
|
|
206
432
|
"head_branch" => head_branch,
|
|
207
433
|
"base_branch" => base_branch,
|
|
208
|
-
"path" =>
|
|
434
|
+
"path" => path,
|
|
209
435
|
"created_at" => Time.now.to_i
|
|
210
436
|
}
|
|
211
437
|
|
|
212
|
-
|
|
213
|
-
|
|
438
|
+
write_pr_registry(pr_registry)
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
# Extract the PR branch from a GitHub Pull Request number
|
|
442
|
+
public def get_pr_branch(pr_number)
|
|
443
|
+
Aidp.log_debug("worktree_branch_manager", "extracting_pr_branch", pr_number: pr_number)
|
|
444
|
+
|
|
445
|
+
# Fetch pull request information from GitHub
|
|
446
|
+
begin
|
|
447
|
+
pr_info_output = run_git_command(["gh", "pr", "view", pr_number.to_s, "--json", "headRefName"])
|
|
448
|
+
pr_branch = JSON.parse(pr_info_output)["headRefName"]
|
|
449
|
+
|
|
450
|
+
if pr_branch.nil? || pr_branch.empty?
|
|
451
|
+
raise PullRequestBranchExtractionError, "Could not extract branch for PR #{pr_number}"
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
pr_branch
|
|
455
|
+
rescue => e
|
|
456
|
+
Aidp.log_error("worktree_branch_manager", "pr_branch_extraction_failed",
|
|
457
|
+
pr_number: pr_number, error: e.message)
|
|
458
|
+
raise PullRequestBranchExtractionError, "Failed to extract branch for PR #{pr_number}: #{e.message}"
|
|
459
|
+
end
|
|
214
460
|
end
|
|
215
461
|
end
|
|
216
462
|
end
|
data/lib/aidp.rb
CHANGED
|
@@ -1,115 +1,30 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
|
|
3
|
+
# Bootstrap: Load essential files before Zeitwerk
|
|
4
|
+
# These files must be loaded first and are excluded from autoloading:
|
|
5
|
+
# - version.rb: Needed for version checks during load
|
|
6
|
+
# - core_ext: Ruby core extensions that need to be available globally
|
|
7
|
+
# - logger.rb: Logging infrastructure used throughout loading
|
|
5
8
|
|
|
6
|
-
# Shared modules
|
|
7
9
|
require_relative "aidp/version"
|
|
8
|
-
require_relative "aidp/
|
|
9
|
-
require_relative "aidp/util"
|
|
10
|
-
require_relative "aidp/rescue_logging"
|
|
11
|
-
require_relative "aidp/message_display"
|
|
12
|
-
require_relative "aidp/concurrency"
|
|
13
|
-
require_relative "aidp/setup/wizard"
|
|
14
|
-
require_relative "aidp/init"
|
|
15
|
-
require_relative "aidp/watch"
|
|
16
|
-
require_relative "aidp/cli"
|
|
17
|
-
|
|
18
|
-
# Jobs and background execution
|
|
19
|
-
require_relative "aidp/jobs/background_runner"
|
|
20
|
-
|
|
21
|
-
# CLI commands
|
|
22
|
-
require_relative "aidp/cli/jobs_command"
|
|
23
|
-
|
|
24
|
-
# Providers
|
|
25
|
-
require_relative "aidp/providers/base"
|
|
26
|
-
require_relative "aidp/providers/cursor"
|
|
27
|
-
require_relative "aidp/providers/anthropic"
|
|
28
|
-
require_relative "aidp/providers/gemini"
|
|
29
|
-
require_relative "aidp/providers/kilocode"
|
|
30
|
-
# Supervised providers removed - using direct execution model
|
|
31
|
-
require_relative "aidp/provider_manager"
|
|
32
|
-
|
|
33
|
-
# Simple file-based storage
|
|
34
|
-
require_relative "aidp/storage/json_storage"
|
|
35
|
-
require_relative "aidp/storage/csv_storage"
|
|
36
|
-
require_relative "aidp/storage/file_manager"
|
|
37
|
-
|
|
38
|
-
# Analyze mode (simplified - file-based storage only)
|
|
39
|
-
require_relative "aidp/analyze/json_file_storage"
|
|
40
|
-
require_relative "aidp/analyze/error_handler"
|
|
41
|
-
require_relative "aidp/analyze/ruby_maat_integration"
|
|
42
|
-
require_relative "aidp/analyze/runner"
|
|
43
|
-
require_relative "aidp/analyze/steps"
|
|
44
|
-
require_relative "aidp/analyze/progress"
|
|
45
|
-
|
|
46
|
-
# Tree-sitter analysis
|
|
47
|
-
require_relative "aidp/analyze/tree_sitter_grammar_loader"
|
|
48
|
-
require_relative "aidp/analyze/seams"
|
|
49
|
-
require_relative "aidp/analyze/tree_sitter_scan"
|
|
50
|
-
require_relative "aidp/analyze/kb_inspector"
|
|
51
|
-
|
|
52
|
-
# Metadata system
|
|
53
|
-
require_relative "aidp/metadata/tool_metadata"
|
|
54
|
-
require_relative "aidp/metadata/validator"
|
|
55
|
-
require_relative "aidp/metadata/parser"
|
|
56
|
-
require_relative "aidp/metadata/scanner"
|
|
57
|
-
require_relative "aidp/metadata/compiler"
|
|
58
|
-
require_relative "aidp/metadata/query"
|
|
59
|
-
require_relative "aidp/metadata/cache"
|
|
60
|
-
|
|
61
|
-
# Workflows
|
|
62
|
-
require_relative "aidp/workflows/definitions"
|
|
63
|
-
require_relative "aidp/workflows/selector"
|
|
64
|
-
|
|
65
|
-
# Execute mode
|
|
66
|
-
require_relative "aidp/execute/steps"
|
|
67
|
-
require_relative "aidp/execute/runner"
|
|
68
|
-
require_relative "aidp/execute/progress"
|
|
69
|
-
require_relative "aidp/execute/checkpoint"
|
|
70
|
-
require_relative "aidp/execute/checkpoint_display"
|
|
71
|
-
require_relative "aidp/execute/work_loop_state"
|
|
72
|
-
require_relative "aidp/execute/instruction_queue"
|
|
73
|
-
require_relative "aidp/execute/persistent_tasklist"
|
|
74
|
-
require_relative "aidp/execute/async_work_loop_runner"
|
|
75
|
-
require_relative "aidp/execute/interactive_repl"
|
|
76
|
-
|
|
77
|
-
# Logging
|
|
10
|
+
require_relative "aidp/core_ext/class_attribute"
|
|
78
11
|
require_relative "aidp/logger"
|
|
79
12
|
|
|
80
|
-
#
|
|
81
|
-
require_relative "aidp/
|
|
82
|
-
require_relative "aidp/daemon/runner"
|
|
83
|
-
|
|
84
|
-
# Workstream/worktree management
|
|
85
|
-
require_relative "aidp/worktree"
|
|
86
|
-
require_relative "aidp/workstream_state"
|
|
87
|
-
require_relative "aidp/workstream_executor"
|
|
13
|
+
# Now set up Zeitwerk autoloader for the rest of the codebase
|
|
14
|
+
require_relative "aidp/loader"
|
|
88
15
|
|
|
89
|
-
#
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
require_relative "aidp/harness/config_manager"
|
|
95
|
-
require_relative "aidp/harness/ruby_llm_registry"
|
|
96
|
-
require_relative "aidp/harness/model_registry"
|
|
97
|
-
require_relative "aidp/harness/condition_detector"
|
|
98
|
-
require_relative "aidp/harness/user_interface"
|
|
99
|
-
require_relative "aidp/harness/simple_user_interface"
|
|
100
|
-
require_relative "aidp/harness/provider_manager"
|
|
101
|
-
require_relative "aidp/harness/provider_config"
|
|
102
|
-
require_relative "aidp/harness/provider_factory"
|
|
103
|
-
require_relative "aidp/harness/state_manager"
|
|
104
|
-
require_relative "aidp/harness/error_handler"
|
|
105
|
-
require_relative "aidp/harness/status_display"
|
|
106
|
-
require_relative "aidp/harness/runner"
|
|
107
|
-
require_relative "aidp/harness/filter_strategy"
|
|
108
|
-
require_relative "aidp/harness/generic_filter_strategy"
|
|
109
|
-
require_relative "aidp/harness/rspec_filter_strategy"
|
|
110
|
-
require_relative "aidp/harness/output_filter"
|
|
16
|
+
# Configure Zeitwerk based on environment
|
|
17
|
+
# In watch mode or development, enable reloading for hot code updates
|
|
18
|
+
# In production, disable reloading and eager load for performance
|
|
19
|
+
reloading_enabled = ENV["AIDP_ENABLE_RELOADING"] == "1" ||
|
|
20
|
+
ENV["AIDP_WATCH_MODE"] == "1"
|
|
111
21
|
|
|
112
|
-
|
|
113
|
-
|
|
22
|
+
Aidp::Loader.setup(
|
|
23
|
+
enable_reloading: reloading_enabled,
|
|
24
|
+
eager_load: !reloading_enabled && ENV["AIDP_EAGER_LOAD"] == "1"
|
|
25
|
+
)
|
|
114
26
|
|
|
115
|
-
#
|
|
27
|
+
# Manually require files that contain multiple constants (not autoloadable by Zeitwerk)
|
|
28
|
+
require_relative "aidp/errors"
|
|
29
|
+
require_relative "aidp/auto_update/errors"
|
|
30
|
+
require_relative "aidp/harness/state/errors"
|