aidp 0.30.0 → 0.32.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/lib/aidp/config.rb +2 -8
- data/lib/aidp/execute/work_loop_runner.rb +70 -0
- data/lib/aidp/harness/configuration.rb +1 -1
- data/lib/aidp/harness/provider_manager.rb +74 -5
- data/lib/aidp/harness/provider_metrics.rb +5 -3
- data/lib/aidp/pr_worktree_manager.rb +582 -0
- data/lib/aidp/providers/anthropic.rb +17 -14
- data/lib/aidp/providers/cursor.rb +7 -1
- data/lib/aidp/setup/wizard.rb +65 -0
- data/lib/aidp/util.rb +11 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/auto_pr_processor.rb +86 -0
- data/lib/aidp/watch/auto_processor.rb +78 -0
- data/lib/aidp/watch/change_request_processor.rb +105 -27
- data/lib/aidp/watch/runner.rb +104 -0
- data/lib/aidp/watch/state_store.rb +37 -0
- data/lib/aidp/worktree_branch_manager.rb +216 -0
- metadata +5 -1
|
@@ -65,6 +65,43 @@ module Aidp
|
|
|
65
65
|
save!
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
+
# Retrieve workstream metadata for a given issue
|
|
69
|
+
# @return [Hash, nil] {issue_number:, branch:, workstream:, pr_url:, status:}
|
|
70
|
+
def workstream_for_issue(issue_number)
|
|
71
|
+
data = build_status(issue_number)
|
|
72
|
+
return nil if data.nil? || data.empty?
|
|
73
|
+
|
|
74
|
+
{
|
|
75
|
+
issue_number: issue_number.to_i,
|
|
76
|
+
branch: data["branch"],
|
|
77
|
+
workstream: data["workstream"],
|
|
78
|
+
pr_url: data["pr_url"],
|
|
79
|
+
status: data["status"]
|
|
80
|
+
}
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Find the build/workstream metadata associated with a PR URL
|
|
84
|
+
# This is used to map change-request PRs back to their originating issues/worktrees.
|
|
85
|
+
# @return [Hash, nil] {issue_number:, branch:, workstream:, pr_url:, status:}
|
|
86
|
+
def find_build_by_pr(pr_number)
|
|
87
|
+
builds.each do |issue_number, data|
|
|
88
|
+
pr_url = data["pr_url"]
|
|
89
|
+
next unless pr_url
|
|
90
|
+
|
|
91
|
+
if pr_url.match?(%r{/pull/#{pr_number}\b})
|
|
92
|
+
return {
|
|
93
|
+
issue_number: issue_number.to_i,
|
|
94
|
+
branch: data["branch"],
|
|
95
|
+
workstream: data["workstream"],
|
|
96
|
+
pr_url: pr_url,
|
|
97
|
+
status: data["status"]
|
|
98
|
+
}
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
nil
|
|
103
|
+
end
|
|
104
|
+
|
|
68
105
|
# Review tracking methods
|
|
69
106
|
def review_processed?(pr_number)
|
|
70
107
|
reviews.key?(pr_number.to_s)
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "fileutils"
|
|
3
|
+
|
|
4
|
+
module Aidp
|
|
5
|
+
# Manages git worktrees for pull request branches
|
|
6
|
+
class WorktreeBranchManager
|
|
7
|
+
class WorktreeCreationError < StandardError; end
|
|
8
|
+
class WorktreeLookupError < StandardError; end
|
|
9
|
+
|
|
10
|
+
# Initialize with a project directory and optional logger
|
|
11
|
+
def initialize(project_dir:, logger: Aidp.logger)
|
|
12
|
+
@project_dir = project_dir
|
|
13
|
+
@logger = logger
|
|
14
|
+
@worktree_registry_path = File.join(project_dir, ".aidp", "worktrees.json")
|
|
15
|
+
@pr_worktree_registry_path = File.join(project_dir, ".aidp", "pr_worktrees.json")
|
|
16
|
+
end
|
|
17
|
+
|
|
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", branch: branch)
|
|
21
|
+
|
|
22
|
+
raise WorktreeLookupError, "Invalid git repository: #{@project_dir}" unless git_repository?
|
|
23
|
+
|
|
24
|
+
# First, check registry first for exact branch match
|
|
25
|
+
worktree_info = read_registry.find { |w| w["branch"] == branch }
|
|
26
|
+
|
|
27
|
+
if worktree_info
|
|
28
|
+
worktree_path = worktree_info["path"]
|
|
29
|
+
return worktree_path if File.directory?(worktree_path)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Fallback: Use git worktree list to find the worktree
|
|
33
|
+
worktree_list_output = run_git_command("git worktree list")
|
|
34
|
+
worktree_list_output.split("\n").each do |line|
|
|
35
|
+
path, branch_info = line.split(" ", 2)
|
|
36
|
+
return path if branch_info&.include?(branch)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
nil
|
|
40
|
+
rescue => e
|
|
41
|
+
Aidp.log_error("worktree_branch_manager", "worktree_lookup_failed",
|
|
42
|
+
error: e.message, branch: branch)
|
|
43
|
+
raise
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Find or create a worktree for a PR, with advanced lookup strategies
|
|
47
|
+
def find_or_create_pr_worktree(pr_number:, head_branch:, base_branch: "main")
|
|
48
|
+
Aidp.log_debug("worktree_branch_manager", "finding_or_creating_pr_worktree",
|
|
49
|
+
pr_number: pr_number, head_branch: head_branch, base_branch: base_branch)
|
|
50
|
+
|
|
51
|
+
# First, check the PR-specific registry
|
|
52
|
+
pr_registry = read_pr_registry
|
|
53
|
+
pr_worktree = pr_registry.find { |w| w["pr_number"] == pr_number }
|
|
54
|
+
|
|
55
|
+
# If a valid worktree exists in the registry, return it
|
|
56
|
+
if pr_worktree
|
|
57
|
+
worktree_path = pr_worktree["path"]
|
|
58
|
+
return worktree_path if File.directory?(worktree_path)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Attempt to find worktree by branch name
|
|
62
|
+
existing_worktree = find_worktree(branch: head_branch)
|
|
63
|
+
return existing_worktree if existing_worktree
|
|
64
|
+
|
|
65
|
+
# If no existing worktree, create a new one
|
|
66
|
+
worktree_path = create_worktree(branch: head_branch, base_branch: base_branch)
|
|
67
|
+
|
|
68
|
+
# Update PR-specific registry
|
|
69
|
+
update_pr_registry(pr_number, head_branch, worktree_path, base_branch)
|
|
70
|
+
|
|
71
|
+
worktree_path
|
|
72
|
+
rescue => e
|
|
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
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Create a new worktree for a branch
|
|
79
|
+
def create_worktree(branch:, base_branch: "main")
|
|
80
|
+
Aidp.log_debug("worktree_branch_manager", "creating_worktree",
|
|
81
|
+
branch: branch, base_branch: base_branch)
|
|
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}"
|
|
93
|
+
|
|
94
|
+
system({"GIT_DIR" => File.join(@project_dir, ".git")}, "cd #{@project_dir} && #{base_exists_cmd}")
|
|
95
|
+
|
|
96
|
+
# If base branch doesn't exist locally, create it
|
|
97
|
+
unless $?.success?
|
|
98
|
+
system({"GIT_DIR" => File.join(@project_dir, ".git")}, "cd #{@project_dir} && git checkout -b #{base_branch}")
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Create worktree directory
|
|
102
|
+
worktree_name = branch.tr("/", "_")
|
|
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"))
|
|
107
|
+
|
|
108
|
+
# Create the worktree
|
|
109
|
+
cmd = "git worktree add -b #{branch} #{worktree_path} #{base_branch}"
|
|
110
|
+
result = system({"GIT_DIR" => File.join(@project_dir, ".git")}, "cd #{@project_dir} && #{cmd}")
|
|
111
|
+
|
|
112
|
+
unless result
|
|
113
|
+
Aidp.log_error("worktree_branch_manager", "worktree_creation_failed",
|
|
114
|
+
branch: branch, base_branch: base_branch)
|
|
115
|
+
raise WorktreeCreationError, "Failed to create worktree for branch #{branch}"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Update registry
|
|
119
|
+
update_registry(branch, worktree_path)
|
|
120
|
+
|
|
121
|
+
worktree_path
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
private
|
|
125
|
+
|
|
126
|
+
def git_repository?
|
|
127
|
+
File.directory?(File.join(@project_dir, ".git"))
|
|
128
|
+
rescue
|
|
129
|
+
false
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def validate_branch_name!(branch)
|
|
133
|
+
if branch.include?("..") || branch.start_with?("/")
|
|
134
|
+
raise WorktreeCreationError, "Invalid branch name: #{branch}"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def run_git_command(cmd)
|
|
139
|
+
Dir.chdir(@project_dir) do
|
|
140
|
+
output = `#{cmd} 2>&1`
|
|
141
|
+
raise StandardError, output unless $?.success?
|
|
142
|
+
output
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Read the worktree registry
|
|
147
|
+
def read_registry
|
|
148
|
+
return [] unless File.exist?(@worktree_registry_path)
|
|
149
|
+
|
|
150
|
+
begin
|
|
151
|
+
JSON.parse(File.read(@worktree_registry_path))
|
|
152
|
+
rescue JSON::ParserError
|
|
153
|
+
Aidp.log_warn("worktree_branch_manager", "invalid_registry",
|
|
154
|
+
path: @worktree_registry_path)
|
|
155
|
+
[]
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
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
|
+
# Update the worktree registry
|
|
173
|
+
def update_registry(branch, path)
|
|
174
|
+
# Ensure .aidp directory exists
|
|
175
|
+
FileUtils.mkdir_p(File.dirname(@worktree_registry_path))
|
|
176
|
+
|
|
177
|
+
registry = read_registry
|
|
178
|
+
|
|
179
|
+
# Remove existing entries for the same branch
|
|
180
|
+
registry.reject! { |w| w["branch"] == branch }
|
|
181
|
+
|
|
182
|
+
# Add new entry
|
|
183
|
+
registry << {
|
|
184
|
+
"branch" => branch,
|
|
185
|
+
"path" => path,
|
|
186
|
+
"created_at" => Time.now.to_i
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
# Write updated registry
|
|
190
|
+
File.write(@worktree_registry_path, JSON.pretty_generate(registry))
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Update the PR-specific worktree registry
|
|
194
|
+
def update_pr_registry(pr_number, head_branch, worktree_path, base_branch)
|
|
195
|
+
# Ensure .aidp directory exists
|
|
196
|
+
FileUtils.mkdir_p(File.dirname(@pr_worktree_registry_path))
|
|
197
|
+
|
|
198
|
+
registry = read_pr_registry
|
|
199
|
+
|
|
200
|
+
# Remove existing entries for the same PR
|
|
201
|
+
registry.reject! { |w| w["pr_number"] == pr_number }
|
|
202
|
+
|
|
203
|
+
# Add new entry
|
|
204
|
+
registry << {
|
|
205
|
+
"pr_number" => pr_number,
|
|
206
|
+
"head_branch" => head_branch,
|
|
207
|
+
"base_branch" => base_branch,
|
|
208
|
+
"path" => worktree_path,
|
|
209
|
+
"created_at" => Time.now.to_i
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
# Write updated registry
|
|
213
|
+
File.write(@pr_worktree_registry_path, JSON.pretty_generate(registry))
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: aidp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.32.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bart Agapinan
|
|
@@ -401,6 +401,7 @@ files:
|
|
|
401
401
|
- lib/aidp/planning/mappers/persona_mapper.rb
|
|
402
402
|
- lib/aidp/planning/parsers/document_parser.rb
|
|
403
403
|
- lib/aidp/planning/parsers/feedback_data_parser.rb
|
|
404
|
+
- lib/aidp/pr_worktree_manager.rb
|
|
404
405
|
- lib/aidp/prompt_optimization/context_composer.rb
|
|
405
406
|
- lib/aidp/prompt_optimization/optimizer.rb
|
|
406
407
|
- lib/aidp/prompt_optimization/prompt_builder.rb
|
|
@@ -449,6 +450,8 @@ files:
|
|
|
449
450
|
- lib/aidp/utils/devcontainer_detector.rb
|
|
450
451
|
- lib/aidp/version.rb
|
|
451
452
|
- lib/aidp/watch.rb
|
|
453
|
+
- lib/aidp/watch/auto_pr_processor.rb
|
|
454
|
+
- lib/aidp/watch/auto_processor.rb
|
|
452
455
|
- lib/aidp/watch/build_processor.rb
|
|
453
456
|
- lib/aidp/watch/change_request_processor.rb
|
|
454
457
|
- lib/aidp/watch/ci_fix_processor.rb
|
|
@@ -473,6 +476,7 @@ files:
|
|
|
473
476
|
- lib/aidp/workstream_executor.rb
|
|
474
477
|
- lib/aidp/workstream_state.rb
|
|
475
478
|
- lib/aidp/worktree.rb
|
|
479
|
+
- lib/aidp/worktree_branch_manager.rb
|
|
476
480
|
- templates/COMMON/AGENT_BASE.md
|
|
477
481
|
- templates/COMMON/CONVENTIONS.md
|
|
478
482
|
- templates/COMMON/TEMPLATES/ADR_TEMPLATE.md
|