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
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../message_display"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
module Watch
|
|
7
|
+
# Creates sub-issues from a hierarchical plan during watch mode.
|
|
8
|
+
# Links sub-issues to parent, adds them to projects, and sets custom fields.
|
|
9
|
+
class SubIssueCreator
|
|
10
|
+
include Aidp::MessageDisplay
|
|
11
|
+
|
|
12
|
+
attr_reader :repository_client, :state_store, :project_id
|
|
13
|
+
|
|
14
|
+
def initialize(repository_client:, state_store:, project_id: nil)
|
|
15
|
+
@repository_client = repository_client
|
|
16
|
+
@state_store = state_store
|
|
17
|
+
@project_id = project_id
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Creates sub-issues from hierarchical plan data
|
|
21
|
+
# @param parent_issue [Hash] The parent issue data
|
|
22
|
+
# @param sub_issues_data [Array<Hash>] Array of sub-issue specifications
|
|
23
|
+
# @return [Array<Hash>] Created sub-issue details
|
|
24
|
+
def create_sub_issues(parent_issue, sub_issues_data)
|
|
25
|
+
parent_number = parent_issue[:number]
|
|
26
|
+
Aidp.log_debug("sub_issue_creator", "create_sub_issues", parent: parent_number, count: sub_issues_data.size)
|
|
27
|
+
|
|
28
|
+
display_message("🔨 Creating #{sub_issues_data.size} sub-issues for ##{parent_number}", type: :info)
|
|
29
|
+
|
|
30
|
+
created_issues = []
|
|
31
|
+
|
|
32
|
+
sub_issues_data.each_with_index do |sub_data, index|
|
|
33
|
+
issue = create_single_sub_issue(parent_issue, sub_data, index + 1)
|
|
34
|
+
created_issues << issue
|
|
35
|
+
display_message(" ✓ Created sub-issue ##{issue[:number]}: #{sub_data[:title]}", type: :success)
|
|
36
|
+
rescue => e
|
|
37
|
+
Aidp.log_error("sub_issue_creator", "Failed to create sub-issue", parent: parent_number, index: index, error: e.message)
|
|
38
|
+
display_message(" ✗ Failed to create sub-issue #{index + 1}: #{e.message}", type: :error)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Link all created issues to project if project_id is configured
|
|
42
|
+
if @project_id && created_issues.any?
|
|
43
|
+
link_issues_to_project(parent_number, created_issues.map { |i| i[:number] })
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Update state store with parent-child relationships
|
|
47
|
+
@state_store.record_sub_issues(parent_number, created_issues.map { |i| i[:number] })
|
|
48
|
+
|
|
49
|
+
# Post summary comment on parent issue
|
|
50
|
+
post_sub_issues_summary(parent_issue, created_issues)
|
|
51
|
+
|
|
52
|
+
Aidp.log_debug("sub_issue_creator", "create_sub_issues_complete", parent: parent_number, created: created_issues.size)
|
|
53
|
+
created_issues
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def create_single_sub_issue(parent_issue, sub_data, sequence_number)
|
|
59
|
+
parent_number = parent_issue[:number]
|
|
60
|
+
Aidp.log_debug("sub_issue_creator", "create_single_sub_issue", parent: parent_number, sequence: sequence_number)
|
|
61
|
+
|
|
62
|
+
# Build issue title
|
|
63
|
+
title = sub_data[:title]
|
|
64
|
+
title = "#{parent_issue[:title]} - Part #{sequence_number}" if title.to_s.strip.empty?
|
|
65
|
+
|
|
66
|
+
# Build issue body with parent reference
|
|
67
|
+
body = build_sub_issue_body(parent_issue, sub_data, sequence_number)
|
|
68
|
+
|
|
69
|
+
# Determine labels
|
|
70
|
+
labels = ["aidp-auto"]
|
|
71
|
+
labels.concat(Array(sub_data[:labels])) if sub_data[:labels]
|
|
72
|
+
|
|
73
|
+
# Create the issue
|
|
74
|
+
result = @repository_client.create_issue(
|
|
75
|
+
title: title,
|
|
76
|
+
body: body,
|
|
77
|
+
labels: labels,
|
|
78
|
+
assignees: Array(sub_data[:assignees])
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
Aidp.log_debug("sub_issue_creator", "issue_created", parent: parent_number, number: result[:number], url: result[:url])
|
|
82
|
+
|
|
83
|
+
{
|
|
84
|
+
number: result[:number],
|
|
85
|
+
url: result[:url],
|
|
86
|
+
title: title,
|
|
87
|
+
skills: sub_data[:skills],
|
|
88
|
+
personas: sub_data[:personas],
|
|
89
|
+
dependencies: sub_data[:dependencies]
|
|
90
|
+
}
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def build_sub_issue_body(parent_issue, sub_data, sequence_number)
|
|
94
|
+
parts = []
|
|
95
|
+
|
|
96
|
+
# Parent reference
|
|
97
|
+
parts << "## 🔗 Parent Issue"
|
|
98
|
+
parts << ""
|
|
99
|
+
parts << "This is sub-issue #{sequence_number} of #{parent_issue[:url]}"
|
|
100
|
+
parts << ""
|
|
101
|
+
|
|
102
|
+
# Description
|
|
103
|
+
if sub_data[:description] && !sub_data[:description].to_s.strip.empty?
|
|
104
|
+
parts << "## 📋 Description"
|
|
105
|
+
parts << ""
|
|
106
|
+
parts << sub_data[:description]
|
|
107
|
+
parts << ""
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Tasks
|
|
111
|
+
if sub_data[:tasks]&.any?
|
|
112
|
+
parts << "## ✅ Tasks"
|
|
113
|
+
parts << ""
|
|
114
|
+
sub_data[:tasks].each do |task|
|
|
115
|
+
parts << "- [ ] #{task}"
|
|
116
|
+
end
|
|
117
|
+
parts << ""
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Skills required
|
|
121
|
+
if sub_data[:skills]&.any?
|
|
122
|
+
parts << "## 🛠️ Skills Required"
|
|
123
|
+
parts << ""
|
|
124
|
+
parts << sub_data[:skills].map { |s| "- #{s}" }.join("\n")
|
|
125
|
+
parts << ""
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Personas
|
|
129
|
+
if sub_data[:personas]&.any?
|
|
130
|
+
parts << "## 👤 Suggested Personas"
|
|
131
|
+
parts << ""
|
|
132
|
+
parts << sub_data[:personas].map { |p| "- #{p}" }.join("\n")
|
|
133
|
+
parts << ""
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Dependencies
|
|
137
|
+
if sub_data[:dependencies]&.any?
|
|
138
|
+
parts << "## ⚠️ Dependencies"
|
|
139
|
+
parts << ""
|
|
140
|
+
parts << "This issue depends on:"
|
|
141
|
+
parts << sub_data[:dependencies].map { |d| "- #{d}" }.join("\n")
|
|
142
|
+
parts << ""
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Footer
|
|
146
|
+
parts << "---"
|
|
147
|
+
parts << "_This issue was automatically created by AIDP as part of hierarchical project planning._"
|
|
148
|
+
|
|
149
|
+
parts.join("\n")
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def link_issues_to_project(parent_number, sub_issue_numbers)
|
|
153
|
+
Aidp.log_debug("sub_issue_creator", "link_issues_to_project", parent: parent_number, project_id: @project_id, count: sub_issue_numbers.size + 1)
|
|
154
|
+
|
|
155
|
+
display_message("📊 Linking issues to project #{@project_id}", type: :info)
|
|
156
|
+
|
|
157
|
+
# Link parent issue
|
|
158
|
+
begin
|
|
159
|
+
parent_item_id = @repository_client.link_issue_to_project(@project_id, parent_number)
|
|
160
|
+
@state_store.record_project_item_id(parent_number, parent_item_id)
|
|
161
|
+
display_message(" ✓ Linked parent issue ##{parent_number}", type: :success)
|
|
162
|
+
rescue => e
|
|
163
|
+
Aidp.log_error("sub_issue_creator", "Failed to link parent to project", parent: parent_number, error: e.message)
|
|
164
|
+
display_message(" ✗ Failed to link parent issue: #{e.message}", type: :warn)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Link sub-issues
|
|
168
|
+
sub_issue_numbers.each do |number|
|
|
169
|
+
item_id = @repository_client.link_issue_to_project(@project_id, number)
|
|
170
|
+
@state_store.record_project_item_id(number, item_id)
|
|
171
|
+
display_message(" ✓ Linked sub-issue ##{number}", type: :success)
|
|
172
|
+
rescue => e
|
|
173
|
+
Aidp.log_error("sub_issue_creator", "Failed to link sub-issue to project", issue: number, error: e.message)
|
|
174
|
+
display_message(" ✗ Failed to link sub-issue ##{number}: #{e.message}", type: :warn)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def post_sub_issues_summary(parent_issue, created_issues)
|
|
179
|
+
parent_number = parent_issue[:number]
|
|
180
|
+
Aidp.log_debug("sub_issue_creator", "post_sub_issues_summary", parent: parent_number, count: created_issues.size)
|
|
181
|
+
|
|
182
|
+
return if created_issues.empty?
|
|
183
|
+
|
|
184
|
+
parts = []
|
|
185
|
+
parts << "## 🔀 Sub-Issues Created"
|
|
186
|
+
parts << ""
|
|
187
|
+
parts << "AIDP has broken down this issue into #{created_issues.size} sub-issues:"
|
|
188
|
+
parts << ""
|
|
189
|
+
|
|
190
|
+
created_issues.each_with_index do |issue, index|
|
|
191
|
+
parts << "#{index + 1}. ##{issue[:number]} - #{issue[:title]}"
|
|
192
|
+
|
|
193
|
+
metadata = []
|
|
194
|
+
metadata << "**Skills**: #{issue[:skills].join(", ")}" if issue[:skills]&.any?
|
|
195
|
+
metadata << "**Personas**: #{issue[:personas].join(", ")}" if issue[:personas]&.any?
|
|
196
|
+
metadata << "**Depends on**: #{issue[:dependencies].join(", ")}" if issue[:dependencies]&.any?
|
|
197
|
+
|
|
198
|
+
if metadata.any?
|
|
199
|
+
parts << " - #{metadata.join(" | ")}"
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
parts << ""
|
|
204
|
+
parts << "Each sub-issue has been labeled with `aidp-auto` and will be automatically picked up for implementation."
|
|
205
|
+
parts << ""
|
|
206
|
+
parts << "---"
|
|
207
|
+
parts << "_This breakdown was automatically generated by AIDP._"
|
|
208
|
+
|
|
209
|
+
body = parts.join("\n")
|
|
210
|
+
|
|
211
|
+
begin
|
|
212
|
+
@repository_client.post_comment(parent_number, body)
|
|
213
|
+
display_message("💬 Posted sub-issues summary to parent issue ##{parent_number}", type: :success)
|
|
214
|
+
rescue => e
|
|
215
|
+
Aidp.log_error("sub_issue_creator", "Failed to post summary comment", parent: parent_number, error: e.message)
|
|
216
|
+
display_message("⚠️ Failed to post summary comment: #{e.message}", type: :warn)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
data/lib/aidp/watch.rb
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
require_relative "watch/build_processor"
|
|
9
|
-
require_relative "watch/runner"
|
|
3
|
+
module Aidp
|
|
4
|
+
# Watch mode functionality for monitoring GitHub issues and PRs
|
|
5
|
+
module Watch
|
|
6
|
+
end
|
|
7
|
+
end
|
|
@@ -19,6 +19,10 @@ module Aidp
|
|
|
19
19
|
|
|
20
20
|
class ConversationError < StandardError; end
|
|
21
21
|
|
|
22
|
+
# Expose for testability
|
|
23
|
+
attr_reader :project_dir, :conversation_history, :user_input, :config_manager
|
|
24
|
+
attr_writer :provider_manager
|
|
25
|
+
|
|
22
26
|
def initialize(project_dir, prompt: nil, use_enhanced_input: true, verbose: false, config_manager: nil, provider_manager: nil)
|
|
23
27
|
@project_dir = project_dir
|
|
24
28
|
|
|
@@ -2,10 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require "concurrent-ruby"
|
|
4
4
|
require "time"
|
|
5
|
-
require_relative "worktree"
|
|
6
|
-
require_relative "workstream_state"
|
|
7
|
-
require_relative "harness/runner"
|
|
8
|
-
require_relative "message_display"
|
|
9
5
|
|
|
10
6
|
module Aidp
|
|
11
7
|
# Executes multiple workstreams in parallel using concurrent-ruby.
|
|
@@ -13,6 +9,9 @@ module Aidp
|
|
|
13
9
|
class WorkstreamExecutor
|
|
14
10
|
include Aidp::MessageDisplay
|
|
15
11
|
|
|
12
|
+
# Expose for testability
|
|
13
|
+
attr_reader :project_dir, :max_concurrent, :results, :start_times
|
|
14
|
+
|
|
16
15
|
# Result from executing a workstream
|
|
17
16
|
WorkstreamResult = Struct.new(
|
|
18
17
|
:slug,
|
data/lib/aidp/worktree.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require "fileutils"
|
|
4
4
|
require "json"
|
|
5
5
|
require "open3"
|
|
6
|
-
|
|
6
|
+
require "time"
|
|
7
7
|
|
|
8
8
|
module Aidp
|
|
9
9
|
# Manages git worktree operations for parallel workstreams.
|
|
@@ -145,19 +145,43 @@ module Aidp
|
|
|
145
145
|
#
|
|
146
146
|
# @param branch [String] Branch name to search for
|
|
147
147
|
# @param project_dir [String] Project root directory
|
|
148
|
-
# @
|
|
149
|
-
|
|
148
|
+
# @param create_if_not_found [Boolean] Whether to create a worktree if not found
|
|
149
|
+
# @param base_branch [String, nil] Base branch to use if creating a new worktree
|
|
150
|
+
# @return [Hash, nil] Worktree info or nil if not found and create_if_not_found is false
|
|
151
|
+
def find_by_branch(branch:, project_dir: Dir.pwd, create_if_not_found: false, base_branch: nil)
|
|
152
|
+
# First, try exact match in the registry
|
|
150
153
|
registry = load_registry(project_dir)
|
|
151
154
|
slug, data = registry.find { |_slug, info| info["branch"] == branch }
|
|
152
|
-
return nil unless data
|
|
153
155
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
156
|
+
# Check if the existing worktree is still active
|
|
157
|
+
if data
|
|
158
|
+
active = Dir.exist?(data["path"])
|
|
159
|
+
Aidp.log_debug("worktree", active ? "found_existing" : "found_inactive", branch: branch, slug: slug)
|
|
160
|
+
return {
|
|
161
|
+
slug: slug,
|
|
162
|
+
path: data["path"],
|
|
163
|
+
branch: data["branch"],
|
|
164
|
+
created_at: data["created_at"],
|
|
165
|
+
active: active
|
|
166
|
+
}
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# If not found and create_if_not_found is true, create a new worktree
|
|
170
|
+
if create_if_not_found
|
|
171
|
+
# Generate a slug from the branch name, replacing problematic characters
|
|
172
|
+
safe_slug = branch.downcase.gsub(/[^a-z0-9\-_]/, "-")
|
|
173
|
+
|
|
174
|
+
Aidp.log_debug("worktree", "creating_for_branch", branch: branch, slug: safe_slug)
|
|
175
|
+
return create(
|
|
176
|
+
slug: safe_slug,
|
|
177
|
+
project_dir: project_dir,
|
|
178
|
+
branch: branch,
|
|
179
|
+
base_branch: base_branch
|
|
180
|
+
)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# If no existing worktree and not set to create, return nil
|
|
184
|
+
nil
|
|
161
185
|
end
|
|
162
186
|
|
|
163
187
|
private
|
|
@@ -228,11 +252,36 @@ module Aidp
|
|
|
228
252
|
end
|
|
229
253
|
end
|
|
230
254
|
|
|
255
|
+
# Check if a branch exists on the remote (origin)
|
|
256
|
+
# @param project_dir [String] Project root directory
|
|
257
|
+
# @param branch [String] Branch name to check
|
|
258
|
+
# @return [Boolean] True if branch exists on origin
|
|
259
|
+
def remote_branch_exists?(project_dir, branch)
|
|
260
|
+
Dir.chdir(project_dir) do
|
|
261
|
+
system("git", "show-ref", "--verify", "--quiet", "refs/remotes/origin/#{branch}")
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
231
265
|
def run_worktree_add!(project_dir:, branch:, branch_exists:, worktree_path:, base_branch:)
|
|
232
266
|
prune_attempted = false
|
|
233
267
|
|
|
268
|
+
# If branch doesn't exist locally and no base_branch provided,
|
|
269
|
+
# check if it exists on the remote and use that as the base.
|
|
270
|
+
# This handles PRs created by external tools (Claude Code Web, GitHub UI, etc.)
|
|
271
|
+
effective_base_branch = base_branch
|
|
272
|
+
if !branch_exists && base_branch.nil? && remote_branch_exists?(project_dir, branch)
|
|
273
|
+
effective_base_branch = "origin/#{branch}"
|
|
274
|
+
Aidp.log_debug("worktree", "using_remote_branch_as_base",
|
|
275
|
+
branch: branch, remote_ref: effective_base_branch)
|
|
276
|
+
end
|
|
277
|
+
|
|
234
278
|
loop do
|
|
235
|
-
cmd = build_worktree_command(
|
|
279
|
+
cmd = build_worktree_command(
|
|
280
|
+
branch_exists: branch_exists,
|
|
281
|
+
branch: branch,
|
|
282
|
+
worktree_path: worktree_path,
|
|
283
|
+
base_branch: effective_base_branch
|
|
284
|
+
)
|
|
236
285
|
stdout, stderr, status = Dir.chdir(project_dir) { Open3.capture3(*cmd) }
|
|
237
286
|
|
|
238
287
|
return if status.success?
|