aidp 0.33.0 → 0.34.1

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.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -0
  3. data/lib/aidp/analyze/tree_sitter_scan.rb +3 -0
  4. data/lib/aidp/cli/eval_command.rb +399 -0
  5. data/lib/aidp/cli/harness_command.rb +1 -1
  6. data/lib/aidp/cli/security_command.rb +416 -0
  7. data/lib/aidp/cli/tools_command.rb +6 -4
  8. data/lib/aidp/cli.rb +170 -3
  9. data/lib/aidp/concurrency/exec.rb +3 -0
  10. data/lib/aidp/config.rb +113 -0
  11. data/lib/aidp/config_paths.rb +20 -0
  12. data/lib/aidp/daemon/runner.rb +8 -4
  13. data/lib/aidp/errors.rb +134 -0
  14. data/lib/aidp/evaluations/context_capture.rb +205 -0
  15. data/lib/aidp/evaluations/evaluation_record.rb +114 -0
  16. data/lib/aidp/evaluations/evaluation_storage.rb +250 -0
  17. data/lib/aidp/evaluations.rb +23 -0
  18. data/lib/aidp/execute/async_work_loop_runner.rb +4 -1
  19. data/lib/aidp/execute/interactive_repl.rb +6 -2
  20. data/lib/aidp/execute/prompt_evaluator.rb +359 -0
  21. data/lib/aidp/execute/repl_macros.rb +100 -1
  22. data/lib/aidp/execute/work_loop_runner.rb +399 -47
  23. data/lib/aidp/execute/work_loop_state.rb +4 -1
  24. data/lib/aidp/execute/workflow_selector.rb +3 -0
  25. data/lib/aidp/harness/ai_decision_engine.rb +79 -0
  26. data/lib/aidp/harness/capability_registry.rb +2 -0
  27. data/lib/aidp/harness/condition_detector.rb +3 -0
  28. data/lib/aidp/harness/config_loader.rb +3 -0
  29. data/lib/aidp/harness/enhanced_runner.rb +14 -11
  30. data/lib/aidp/harness/error_handler.rb +3 -0
  31. data/lib/aidp/harness/provider_factory.rb +3 -0
  32. data/lib/aidp/harness/provider_manager.rb +6 -0
  33. data/lib/aidp/harness/runner.rb +5 -1
  34. data/lib/aidp/harness/state/persistence.rb +3 -0
  35. data/lib/aidp/harness/state_manager.rb +3 -0
  36. data/lib/aidp/harness/status_display.rb +28 -20
  37. data/lib/aidp/harness/thinking_depth_manager.rb +32 -32
  38. data/lib/aidp/harness/ui/enhanced_tui.rb +4 -0
  39. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +4 -0
  40. data/lib/aidp/harness/ui/error_handler.rb +3 -0
  41. data/lib/aidp/harness/ui/job_monitor.rb +4 -0
  42. data/lib/aidp/harness/ui/navigation/submenu.rb +2 -0
  43. data/lib/aidp/harness/ui/navigation/workflow_selector.rb +6 -0
  44. data/lib/aidp/harness/ui/spinner_helper.rb +3 -0
  45. data/lib/aidp/harness/ui/workflow_controller.rb +3 -0
  46. data/lib/aidp/harness/ui.rb +11 -0
  47. data/lib/aidp/harness/user_interface.rb +3 -0
  48. data/lib/aidp/loader.rb +2 -2
  49. data/lib/aidp/logger.rb +3 -0
  50. data/lib/aidp/message_display.rb +31 -0
  51. data/lib/aidp/pr_worktree_manager.rb +18 -6
  52. data/lib/aidp/provider_manager.rb +3 -0
  53. data/lib/aidp/providers/base.rb +2 -0
  54. data/lib/aidp/security/rule_of_two_enforcer.rb +210 -0
  55. data/lib/aidp/security/secrets_proxy.rb +328 -0
  56. data/lib/aidp/security/secrets_registry.rb +227 -0
  57. data/lib/aidp/security/trifecta_state.rb +220 -0
  58. data/lib/aidp/security/watch_mode_handler.rb +306 -0
  59. data/lib/aidp/security/work_loop_adapter.rb +277 -0
  60. data/lib/aidp/security.rb +56 -0
  61. data/lib/aidp/setup/wizard.rb +4 -2
  62. data/lib/aidp/version.rb +1 -1
  63. data/lib/aidp/watch/auto_merger.rb +274 -0
  64. data/lib/aidp/watch/auto_pr_processor.rb +125 -7
  65. data/lib/aidp/watch/build_processor.rb +16 -1
  66. data/lib/aidp/watch/change_request_processor.rb +680 -286
  67. data/lib/aidp/watch/ci_fix_processor.rb +262 -4
  68. data/lib/aidp/watch/feedback_collector.rb +191 -0
  69. data/lib/aidp/watch/hierarchical_pr_strategy.rb +256 -0
  70. data/lib/aidp/watch/implementation_verifier.rb +142 -1
  71. data/lib/aidp/watch/plan_generator.rb +70 -13
  72. data/lib/aidp/watch/plan_processor.rb +12 -5
  73. data/lib/aidp/watch/projects_processor.rb +286 -0
  74. data/lib/aidp/watch/repository_client.rb +861 -53
  75. data/lib/aidp/watch/review_processor.rb +33 -6
  76. data/lib/aidp/watch/runner.rb +51 -11
  77. data/lib/aidp/watch/state_store.rb +233 -0
  78. data/lib/aidp/watch/sub_issue_creator.rb +221 -0
  79. data/lib/aidp/workflows/guided_agent.rb +4 -0
  80. data/lib/aidp/workstream_executor.rb +3 -0
  81. data/lib/aidp/worktree.rb +61 -11
  82. data/lib/aidp/worktree_branch_manager.rb +347 -101
  83. data/templates/implementation/iterative_implementation.md +46 -3
  84. metadata +21 -1
@@ -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", branch: branch)
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
- # 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)
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
- # 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)
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
- # 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)
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
- # First, check the PR-specific registry
52
- pr_registry = read_pr_registry
53
- pr_worktree = pr_registry.find { |w| w["pr_number"] == pr_number }
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
- # 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
139
+ # Ensure .worktrees directory exists
140
+ FileUtils.mkdir_p(File.join(@project_dir, ".worktrees"))
60
141
 
61
- # Attempt to find worktree by branch name
62
- existing_worktree = find_worktree(branch: head_branch)
63
- return existing_worktree if existing_worktree
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
- # If no existing worktree, create a new one
66
- worktree_path = create_worktree(branch: head_branch, base_branch: base_branch)
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
- # Update PR-specific registry
69
- update_pr_registry(pr_number, head_branch, worktree_path, base_branch)
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
- 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
246
+ raise WorktreeCreationError,
247
+ "Failed to create worktree for branch #{branch}: #{creation_error&.message}"
76
248
  end
77
249
 
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}"
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
- system({"GIT_DIR" => File.join(@project_dir, ".git")}, "cd #{@project_dir} && #{base_exists_cmd}")
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
- # 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}")
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
- # 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"))
280
+ # Determine unique worktree name for PR-specific worktrees
281
+ unique_suffix = "pr-#{pr_number}"
107
282
 
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}")
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
- 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}"
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
- # Update registry
119
- update_registry(branch, worktree_path)
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
- worktree_path
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
- output = `#{cmd} 2>&1`
141
- raise StandardError, output unless $?.success?
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
- # Update the PR-specific worktree registry
194
- def update_pr_registry(pr_number, head_branch, worktree_path, base_branch)
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(@pr_worktree_registry_path))
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
- registry = read_pr_registry
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
- registry.reject! { |w| w["pr_number"] == pr_number }
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
- registry << {
430
+ pr_registry << {
205
431
  "pr_number" => pr_number,
206
432
  "head_branch" => head_branch,
207
433
  "base_branch" => base_branch,
208
- "path" => worktree_path,
434
+ "path" => path,
209
435
  "created_at" => Time.now.to_i
210
436
  }
211
437
 
212
- # Write updated registry
213
- File.write(@pr_worktree_registry_path, JSON.pretty_generate(registry))
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
@@ -6,11 +6,54 @@ You are implementing a feature or fix within the AIDP work loop using an iterati
6
6
 
7
7
  {{task_description}}
8
8
 
9
- ## Important Instructions
9
+ ## ⚠️ CRITICAL: Task Filing Required
10
10
 
11
- ### 1. Break Down the Work
11
+ **You MUST file tasks BEFORE beginning implementation.** The work loop requires at least one task to be created and completed for the work to be considered done.
12
12
 
13
- If this is a multi-step feature, **break it into concrete subtasks** using the persistent tasklist:
13
+ ### Why Tasks Are Required
14
+
15
+ 1. **Prevents premature completion** - Tasks ensure all requirements are tracked
16
+ 2. **Enables progress tracking** - Each task represents verifiable progress
17
+ 3. **Supports iteration** - If tests fail, tasks show what remains
18
+ 4. **Audit trail** - Tasks document what was actually implemented
19
+
20
+ ### First Action: File Your Tasks
21
+
22
+ **IMMEDIATELY** in your first iteration, file tasks for this work:
23
+
24
+ ```text
25
+ File task: "description" priority: high|medium|low tags: tag1,tag2
26
+ ```
27
+
28
+ **Example - If implementing a feature:**
29
+
30
+ ```text
31
+ File task: "Create core implementation for [feature name]" priority: high tags: implementation
32
+ File task: "Add unit tests for [feature name]" priority: high tags: testing
33
+ File task: "Add integration tests if needed" priority: medium tags: testing
34
+ File task: "Update documentation" priority: low tags: docs
35
+ ```
36
+
37
+ **Example - If fixing a bug:**
38
+
39
+ ```text
40
+ File task: "Identify root cause of [bug description]" priority: high tags: investigation
41
+ File task: "Implement fix for [bug description]" priority: high tags: bugfix
42
+ File task: "Add regression test" priority: high tags: testing
43
+ ```
44
+
45
+ ### Task Filing Guidelines
46
+
47
+ - **At least one task is required** - You cannot complete without tasks
48
+ - **Tasks should be specific** - "Implement user auth" not "Do the work"
49
+ - **Include testing tasks** - Every implementation needs tests
50
+ - **Cover the full scope** - Review the requirements and ensure all are covered by tasks
51
+
52
+ ## Implementation Process
53
+
54
+ ### 1. File Tasks First (REQUIRED)
55
+
56
+ Break down the work into concrete subtasks using the persistent tasklist:
14
57
 
15
58
  ```text
16
59
  File task: "Subtask description here" priority: high|medium|low tags: tag1,tag2