aidp 0.27.0 → 0.28.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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +89 -0
  3. data/lib/aidp/cli/models_command.rb +5 -6
  4. data/lib/aidp/cli.rb +10 -8
  5. data/lib/aidp/config.rb +54 -0
  6. data/lib/aidp/debug_mixin.rb +23 -1
  7. data/lib/aidp/execute/agent_signal_parser.rb +22 -0
  8. data/lib/aidp/execute/repl_macros.rb +2 -2
  9. data/lib/aidp/execute/steps.rb +94 -1
  10. data/lib/aidp/execute/work_loop_runner.rb +209 -17
  11. data/lib/aidp/execute/workflow_selector.rb +2 -25
  12. data/lib/aidp/firewall/provider_requirements_collector.rb +262 -0
  13. data/lib/aidp/harness/ai_decision_engine.rb +35 -2
  14. data/lib/aidp/harness/config_manager.rb +0 -5
  15. data/lib/aidp/harness/config_schema.rb +8 -0
  16. data/lib/aidp/harness/configuration.rb +27 -19
  17. data/lib/aidp/harness/enhanced_runner.rb +1 -4
  18. data/lib/aidp/harness/error_handler.rb +1 -72
  19. data/lib/aidp/harness/provider_factory.rb +11 -2
  20. data/lib/aidp/harness/state_manager.rb +0 -7
  21. data/lib/aidp/harness/thinking_depth_manager.rb +47 -68
  22. data/lib/aidp/harness/ui/enhanced_tui.rb +8 -18
  23. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +0 -18
  24. data/lib/aidp/harness/ui/progress_display.rb +6 -2
  25. data/lib/aidp/harness/user_interface.rb +0 -58
  26. data/lib/aidp/init/runner.rb +7 -2
  27. data/lib/aidp/planning/analyzers/feedback_analyzer.rb +365 -0
  28. data/lib/aidp/planning/builders/agile_plan_builder.rb +387 -0
  29. data/lib/aidp/planning/builders/project_plan_builder.rb +193 -0
  30. data/lib/aidp/planning/generators/gantt_generator.rb +190 -0
  31. data/lib/aidp/planning/generators/iteration_plan_generator.rb +392 -0
  32. data/lib/aidp/planning/generators/legacy_research_planner.rb +473 -0
  33. data/lib/aidp/planning/generators/marketing_report_generator.rb +348 -0
  34. data/lib/aidp/planning/generators/mvp_scope_generator.rb +310 -0
  35. data/lib/aidp/planning/generators/user_test_plan_generator.rb +373 -0
  36. data/lib/aidp/planning/generators/wbs_generator.rb +259 -0
  37. data/lib/aidp/planning/mappers/persona_mapper.rb +163 -0
  38. data/lib/aidp/planning/parsers/document_parser.rb +141 -0
  39. data/lib/aidp/planning/parsers/feedback_data_parser.rb +252 -0
  40. data/lib/aidp/provider_manager.rb +8 -32
  41. data/lib/aidp/providers/aider.rb +264 -0
  42. data/lib/aidp/providers/anthropic.rb +74 -2
  43. data/lib/aidp/providers/base.rb +25 -1
  44. data/lib/aidp/providers/codex.rb +26 -3
  45. data/lib/aidp/providers/cursor.rb +16 -0
  46. data/lib/aidp/providers/gemini.rb +13 -0
  47. data/lib/aidp/providers/github_copilot.rb +17 -0
  48. data/lib/aidp/providers/kilocode.rb +11 -0
  49. data/lib/aidp/providers/opencode.rb +11 -0
  50. data/lib/aidp/setup/wizard.rb +249 -39
  51. data/lib/aidp/version.rb +1 -1
  52. data/lib/aidp/watch/build_processor.rb +211 -30
  53. data/lib/aidp/watch/change_request_processor.rb +128 -14
  54. data/lib/aidp/watch/ci_fix_processor.rb +103 -37
  55. data/lib/aidp/watch/ci_log_extractor.rb +258 -0
  56. data/lib/aidp/watch/github_state_extractor.rb +177 -0
  57. data/lib/aidp/watch/implementation_verifier.rb +284 -0
  58. data/lib/aidp/watch/plan_generator.rb +7 -43
  59. data/lib/aidp/watch/plan_processor.rb +7 -6
  60. data/lib/aidp/watch/repository_client.rb +245 -17
  61. data/lib/aidp/watch/review_processor.rb +98 -17
  62. data/lib/aidp/watch/reviewers/base_reviewer.rb +1 -1
  63. data/lib/aidp/watch/runner.rb +181 -29
  64. data/lib/aidp/watch/state_store.rb +22 -1
  65. data/lib/aidp/workflows/definitions.rb +147 -0
  66. data/lib/aidp/workstream_cleanup.rb +245 -0
  67. data/lib/aidp/worktree.rb +19 -0
  68. data/templates/aidp.yml.example +57 -0
  69. data/templates/implementation/generate_tdd_specs.md +213 -0
  70. data/templates/implementation/iterative_implementation.md +122 -0
  71. data/templates/planning/agile/analyze_feedback.md +183 -0
  72. data/templates/planning/agile/generate_iteration_plan.md +179 -0
  73. data/templates/planning/agile/generate_legacy_research_plan.md +171 -0
  74. data/templates/planning/agile/generate_marketing_report.md +162 -0
  75. data/templates/planning/agile/generate_mvp_scope.md +127 -0
  76. data/templates/planning/agile/generate_user_test_plan.md +143 -0
  77. data/templates/planning/agile/ingest_feedback.md +174 -0
  78. data/templates/planning/assemble_project_plan.md +113 -0
  79. data/templates/planning/assign_personas.md +108 -0
  80. data/templates/planning/create_tasks.md +52 -6
  81. data/templates/planning/generate_gantt.md +86 -0
  82. data/templates/planning/generate_wbs.md +85 -0
  83. data/templates/planning/initialize_planning_mode.md +70 -0
  84. data/templates/skills/README.md +2 -2
  85. data/templates/skills/marketing_strategist/SKILL.md +279 -0
  86. data/templates/skills/product_manager/SKILL.md +177 -0
  87. data/templates/skills/ruby_aidp_planning/SKILL.md +497 -0
  88. data/templates/skills/ruby_rspec_tdd/SKILL.md +514 -0
  89. data/templates/skills/ux_researcher/SKILL.md +222 -0
  90. metadata +39 -1
@@ -12,6 +12,9 @@ require_relative "../execute/prompt_manager"
12
12
  require_relative "../harness/runner"
13
13
  require_relative "../harness/state_manager"
14
14
  require_relative "../harness/test_runner"
15
+ require_relative "../worktree"
16
+ require_relative "github_state_extractor"
17
+ require_relative "implementation_verifier"
15
18
 
16
19
  module Aidp
17
20
  module Watch
@@ -32,10 +35,17 @@ module Aidp
32
35
  def initialize(repository_client:, state_store:, provider_name: nil, project_dir: Dir.pwd, label_config: {}, change_request_config: {}, safety_config: {}, verbose: false)
33
36
  @repository_client = repository_client
34
37
  @state_store = state_store
38
+ @state_extractor = GitHubStateExtractor.new(repository_client: repository_client)
35
39
  @provider_name = provider_name
36
40
  @project_dir = project_dir
37
41
  @verbose = verbose
38
42
 
43
+ # Initialize verifier
44
+ @verifier = ImplementationVerifier.new(
45
+ repository_client: repository_client,
46
+ project_dir: project_dir
47
+ )
48
+
39
49
  # Load label configuration
40
50
  @change_request_label = label_config[:change_request_trigger] || label_config["change_request_trigger"] || DEFAULT_CHANGE_REQUEST_LABEL
41
51
  @needs_input_label = label_config[:needs_input] || label_config["needs_input"] || DEFAULT_NEEDS_INPUT_LABEL
@@ -109,19 +119,14 @@ module Aidp
109
119
  display_message("❌ Change request processing failed: #{e.message}", type: :error)
110
120
  Aidp.log_error("change_request_processor", "Change request failed", pr: pr[:number], error: e.message, backtrace: e.backtrace&.first(10))
111
121
 
112
- # Post error comment
113
- error_comment = <<~COMMENT
114
- #{COMMENT_HEADER}
115
-
116
- ❌ Automated change request processing failed: #{e.message}
117
-
118
- Please review the requested changes manually or retry by re-adding the `#{@change_request_label}` label.
119
- COMMENT
120
- begin
121
- @repository_client.post_comment(pr[:number], error_comment)
122
- rescue
123
- nil
124
- end
122
+ # Record failure state internally but DON'T post error to GitHub
123
+ # (per issue #280 - error messages should never appear on issues)
124
+ @state_store.record_change_request(pr[:number], {
125
+ status: "error",
126
+ error: e.message,
127
+ error_class: e.class.name,
128
+ timestamp: Time.now.utc.iso8601
129
+ })
125
130
  end
126
131
 
127
132
  private
@@ -143,7 +148,7 @@ module Aidp
143
148
 
144
149
  def analyze_change_requests(pr_data:, comments:, diff:)
145
150
  provider_name = @provider_name || detect_default_provider
146
- provider = Aidp::ProviderManager.get_provider(provider_name, use_harness: false)
151
+ provider = Aidp::ProviderManager.get_provider(provider_name)
147
152
 
148
153
  user_prompt = build_analysis_prompt(pr_data: pr_data, comments: comments, diff: diff)
149
154
  full_prompt = "#{change_request_system_prompt}\n\n#{user_prompt}"
@@ -269,6 +274,28 @@ module Aidp
269
274
  end
270
275
  end
271
276
 
277
+ # Check if PR is linked to an issue - if so, verify implementation completeness
278
+ issue_number = @state_extractor.extract_linked_issue(pr[:body])
279
+ if issue_number
280
+ display_message("🔗 Found linked issue ##{issue_number} - verifying implementation...", type: :info)
281
+
282
+ begin
283
+ issue = @repository_client.fetch_issue(issue_number)
284
+ verification_result = @verifier.verify(issue: issue, working_dir: @project_dir)
285
+
286
+ unless verification_result[:verified]
287
+ handle_incomplete_implementation(pr: pr, analysis: analysis, verification_result: verification_result)
288
+ return
289
+ end
290
+
291
+ display_message("✅ Implementation verified complete", type: :success)
292
+ rescue => e
293
+ display_message("⚠️ Verification check failed: #{e.message}", type: :warn)
294
+ Aidp.log_error("change_request_processor", "Verification failed", pr: pr[:number], error: e.message)
295
+ # Continue with commit/push even if verification fails
296
+ end
297
+ end
298
+
272
299
  # Commit and push
273
300
  if commit_and_push(pr, analysis)
274
301
  handle_success(pr: pr, analysis: analysis)
@@ -279,7 +306,29 @@ module Aidp
279
306
 
280
307
  def checkout_pr_branch(pr_data)
281
308
  head_ref = pr_data[:head_ref]
309
+ pr_number = pr_data[:number]
310
+
311
+ # Check if a worktree already exists for this branch
312
+ existing = Aidp::Worktree.find_by_branch(branch: head_ref, project_dir: @project_dir)
313
+
314
+ if existing && existing[:active]
315
+ display_message("🔄 Using existing worktree for branch: #{head_ref}", type: :info)
316
+ Aidp.log_debug("change_request_processor", "worktree_reused", pr_number: pr_number, branch: head_ref, path: existing[:path])
282
317
 
318
+ # Update @project_dir to point to the worktree
319
+ @project_dir = existing[:path]
320
+
321
+ # Pull latest changes in the worktree
322
+ Dir.chdir(@project_dir) do
323
+ run_git(%w[fetch origin], allow_failure: true)
324
+ run_git(["checkout", head_ref])
325
+ run_git(%w[pull --ff-only], allow_failure: true)
326
+ end
327
+
328
+ return
329
+ end
330
+
331
+ # Otherwise, use the main worktree
283
332
  Dir.chdir(@project_dir) do
284
333
  # Fetch latest
285
334
  run_git(%w[fetch origin])
@@ -448,6 +497,71 @@ module Aidp
448
497
  end
449
498
  end
450
499
 
500
+ def handle_incomplete_implementation(pr:, analysis:, verification_result:)
501
+ display_message("⚠️ Implementation incomplete; creating follow-up tasks.", type: :warn)
502
+
503
+ # Create tasks for missing requirements
504
+ if verification_result[:additional_work] && !verification_result[:additional_work].empty?
505
+ create_follow_up_tasks(@project_dir, verification_result[:additional_work])
506
+ end
507
+
508
+ # Record state but do not post a separate comment
509
+ # (verification details will be included in the next summary comment)
510
+ @state_store.record_change_request(pr[:number], {
511
+ status: "incomplete_implementation",
512
+ timestamp: Time.now.utc.iso8601,
513
+ verification_reasons: verification_result[:reasons],
514
+ missing_items: verification_result[:missing_items],
515
+ additional_work: verification_result[:additional_work]
516
+ })
517
+
518
+ display_message("📝 Recorded incomplete implementation status for PR ##{pr[:number]}", type: :info)
519
+
520
+ # Keep the label so the work loop continues (do NOT remove it)
521
+ Aidp.log_info(
522
+ "change_request_processor",
523
+ "incomplete_implementation",
524
+ pr: pr[:number],
525
+ missing_items: verification_result[:missing_items],
526
+ additional_work: verification_result[:additional_work]
527
+ )
528
+ end
529
+
530
+ def create_follow_up_tasks(working_dir, additional_work)
531
+ return if additional_work.nil? || additional_work.empty?
532
+
533
+ tasklist_file = File.join(working_dir, ".aidp", "tasklist.jsonl")
534
+ FileUtils.mkdir_p(File.dirname(tasklist_file))
535
+
536
+ require_relative "../execute/persistent_tasklist"
537
+ tasklist = Aidp::Execute::PersistentTasklist.new(working_dir)
538
+
539
+ additional_work.each do |task_description|
540
+ tasklist.create(
541
+ description: task_description,
542
+ priority: :high,
543
+ source: "verification"
544
+ )
545
+ end
546
+
547
+ display_message("📝 Created #{additional_work.length} follow-up task(s) for continued work", type: :info)
548
+
549
+ Aidp.log_info(
550
+ "change_request_processor",
551
+ "created_follow_up_tasks",
552
+ task_count: additional_work.length,
553
+ working_dir: working_dir
554
+ )
555
+ rescue => e
556
+ display_message("⚠️ Failed to create follow-up tasks: #{e.message}", type: :warn)
557
+ Aidp.log_error(
558
+ "change_request_processor",
559
+ "failed_to_create_follow_up_tasks",
560
+ error: e.message,
561
+ backtrace: e.backtrace&.first(5)
562
+ )
563
+ end
564
+
451
565
  def handle_clarification_needed(pr:, analysis:)
452
566
  existing_data = @state_store.change_request_data(pr[:number])
453
567
  clarification_count = (existing_data&.dig("clarification_count") || 0) + 1
@@ -11,6 +11,9 @@ require_relative "../harness/config_manager"
11
11
  require_relative "../execute/prompt_manager"
12
12
  require_relative "../harness/runner"
13
13
  require_relative "../harness/state_manager"
14
+ require_relative "../worktree"
15
+ require_relative "github_state_extractor"
16
+ require_relative "ci_log_extractor"
14
17
 
15
18
  module Aidp
16
19
  module Watch
@@ -30,6 +33,7 @@ module Aidp
30
33
  def initialize(repository_client:, state_store:, provider_name: nil, project_dir: Dir.pwd, label_config: {}, verbose: false)
31
34
  @repository_client = repository_client
32
35
  @state_store = state_store
36
+ @state_extractor = GitHubStateExtractor.new(repository_client: repository_client)
33
37
  @provider_name = provider_name
34
38
  @project_dir = project_dir
35
39
  @verbose = verbose
@@ -41,9 +45,12 @@ module Aidp
41
45
  def process(pr)
42
46
  number = pr[:number]
43
47
 
44
- # Check if already processed successfully
45
- if @state_store.ci_fix_completed?(number)
48
+ Aidp.log_debug("ci_fix_processor", "process_started", pr_number: number, pr_title: pr[:title])
49
+
50
+ # Check if already processed successfully via GitHub comments
51
+ if @state_extractor.ci_fix_completed?(pr)
46
52
  display_message("ℹ️ CI fix for PR ##{number} already completed. Skipping.", type: :muted)
53
+ Aidp.log_debug("ci_fix_processor", "already_completed", pr_number: number)
47
54
  return
48
55
  end
49
56
 
@@ -53,9 +60,16 @@ module Aidp
53
60
  pr_data = @repository_client.fetch_pull_request(number)
54
61
  ci_status = @repository_client.fetch_ci_status(number)
55
62
 
63
+ Aidp.log_debug("ci_fix_processor", "ci_status_fetched",
64
+ pr_number: number,
65
+ ci_state: ci_status[:state],
66
+ check_count: ci_status[:checks]&.length || 0,
67
+ checks: ci_status[:checks]&.map { |c| {name: c[:name], status: c[:status], conclusion: c[:conclusion]} })
68
+
56
69
  # Check if there are failures
57
70
  if ci_status[:state] == "success"
58
71
  display_message("✅ CI is passing for PR ##{number}. No fixes needed.", type: :success)
72
+ Aidp.log_debug("ci_fix_processor", "ci_passing", pr_number: number)
59
73
  post_success_comment(pr_data)
60
74
  @state_store.record_ci_fix(number, {status: "no_failures", timestamp: Time.now.utc.iso8601})
61
75
  begin
@@ -68,14 +82,25 @@ module Aidp
68
82
 
69
83
  if ci_status[:state] == "pending"
70
84
  display_message("⏳ CI is still running for PR ##{number}. Skipping for now.", type: :muted)
85
+ Aidp.log_debug("ci_fix_processor", "ci_pending", pr_number: number)
71
86
  return
72
87
  end
73
88
 
74
89
  # Get failed checks
75
90
  failed_checks = ci_status[:checks].select { |check| check[:conclusion] == "failure" }
76
91
 
92
+ Aidp.log_debug("ci_fix_processor", "failed_checks_filtered",
93
+ pr_number: number,
94
+ total_checks: ci_status[:checks]&.length || 0,
95
+ failed_count: failed_checks.length,
96
+ failed_checks: failed_checks.map { |c| c[:name] })
97
+
77
98
  if failed_checks.empty?
78
99
  display_message("⚠️ No specific failed checks found for PR ##{number}.", type: :warn)
100
+ Aidp.log_debug("ci_fix_processor", "no_failed_checks",
101
+ pr_number: number,
102
+ ci_state: ci_status[:state],
103
+ all_checks: ci_status[:checks]&.map { |c| {name: c[:name], conclusion: c[:conclusion]} })
79
104
  return
80
105
  end
81
106
 
@@ -99,31 +124,36 @@ module Aidp
99
124
  display_message("❌ CI fix failed: #{e.message}", type: :error)
100
125
  Aidp.log_error("ci_fix_processor", "CI fix failed", pr: pr[:number], error: e.message, backtrace: e.backtrace&.first(10))
101
126
 
102
- # Post error comment
103
- error_comment = <<~COMMENT
104
- #{COMMENT_HEADER}
105
-
106
- ❌ Automated CI fix failed: #{e.message}
107
-
108
- Please investigate the CI failures manually or retry by re-adding the `#{@ci_fix_label}` label.
109
- COMMENT
110
- begin
111
- @repository_client.post_comment(pr[:number], error_comment)
112
- rescue
113
- nil
114
- end
127
+ # Record failure state internally but DON'T post error to GitHub
128
+ # (per issue #280 - error messages should never appear on issues)
129
+ @state_store.record_ci_fix(pr[:number], {
130
+ status: "error",
131
+ error: e.message,
132
+ error_class: e.class.name,
133
+ timestamp: Time.now.utc.iso8601
134
+ })
115
135
  end
116
136
 
117
137
  private
118
138
 
119
139
  def analyze_and_fix(pr_data:, ci_status:, failed_checks:)
120
- # Fetch logs for failed checks (if available)
140
+ # Extract concise failure information to reduce token usage
141
+ provider = detect_default_provider
142
+ provider_manager = Aidp::ProviderManager.get_provider(provider)
143
+ log_extractor = CiLogExtractor.new(provider_manager: provider_manager)
144
+
121
145
  failure_details = failed_checks.map do |check|
146
+ Aidp.log_debug("ci_fix_processor", "extracting_logs", check_name: check[:name])
147
+ extracted = log_extractor.extract_failure_info(
148
+ check: check,
149
+ check_run_url: check[:details_url]
150
+ )
151
+
122
152
  {
123
153
  name: check[:name],
124
- conclusion: check[:conclusion],
125
- output: check[:output],
126
- details_url: check[:details_url]
154
+ summary: extracted[:summary],
155
+ details: extracted[:details],
156
+ extraction_method: extracted[:extraction_method]
127
157
  }
128
158
  end
129
159
 
@@ -131,14 +161,14 @@ module Aidp
131
161
  analysis = analyze_failures_with_ai(pr_data: pr_data, failures: failure_details)
132
162
 
133
163
  if analysis[:can_fix]
134
- # Checkout the PR branch and apply fixes
135
- checkout_pr_branch(pr_data)
164
+ # Setup worktree for the PR branch
165
+ working_dir = setup_pr_worktree(pr_data)
136
166
 
137
167
  # Apply the proposed fixes
138
- apply_fixes(analysis[:fixes])
168
+ apply_fixes(analysis[:fixes], working_dir: working_dir)
139
169
 
140
170
  # Commit and push
141
- if commit_and_push(pr_data, analysis)
171
+ if commit_and_push(pr_data, analysis, working_dir: working_dir)
142
172
  {success: true, analysis: analysis, commit_created: true}
143
173
  else
144
174
  {success: false, analysis: analysis, reason: "No changes to commit"}
@@ -152,7 +182,7 @@ module Aidp
152
182
 
153
183
  def analyze_failures_with_ai(pr_data:, failures:)
154
184
  provider_name = @provider_name || detect_default_provider
155
- provider = Aidp::ProviderManager.get_provider(provider_name, use_harness: false)
185
+ provider = Aidp::ProviderManager.get_provider(provider_name)
156
186
 
157
187
  user_prompt = build_ci_analysis_prompt(pr_data: pr_data, failures: failures)
158
188
  full_prompt = "#{ci_fix_system_prompt}\n\n#{user_prompt}"
@@ -222,12 +252,20 @@ module Aidp
222
252
  #{pr_data[:body]}
223
253
 
224
254
  **Failed Checks:**
225
- #{failures.map { |f| "- #{f[:name]}: #{f[:conclusion]}\n Output: #{f[:output].inspect}" }.join("\n")}
255
+ #{failures.map { |f| format_failure_for_prompt(f) }.join("\n\n")}
226
256
 
227
257
  Please analyze these failures and propose fixes if possible.
228
258
  PROMPT
229
259
  end
230
260
 
261
+ def format_failure_for_prompt(failure)
262
+ output = "**Check: #{failure[:name]}**\n"
263
+ output += "Summary: #{failure[:summary]}\n" if failure[:summary]
264
+ output += "\nDetails:\n```\n#{failure[:details]}\n```" if failure[:details]
265
+ output += "\n(Logs extracted using: #{failure[:extraction_method]})" if failure[:extraction_method]
266
+ output
267
+ end
268
+
231
269
  def detect_default_provider
232
270
  config_manager = Aidp::Harness::ConfigManager.new(@project_dir)
233
271
  config_manager.default_provider || "anthropic"
@@ -261,26 +299,54 @@ module Aidp
261
299
  end
262
300
  end
263
301
 
264
- def checkout_pr_branch(pr_data)
302
+ def setup_pr_worktree(pr_data)
265
303
  head_ref = pr_data[:head_ref]
304
+ pr_number = pr_data[:number]
305
+
306
+ # Check if a worktree already exists for this branch
307
+ existing = Aidp::Worktree.find_by_branch(branch: head_ref, project_dir: @project_dir)
308
+
309
+ if existing && existing[:active]
310
+ display_message("🔄 Reusing existing worktree for branch: #{head_ref}", type: :info)
311
+ Aidp.log_debug("ci_fix_processor", "worktree_reused", pr_number: pr_number, branch: head_ref, path: existing[:path])
312
+
313
+ # Pull latest changes in the worktree
314
+ Dir.chdir(existing[:path]) do
315
+ run_git(%w[fetch origin], allow_failure: true)
316
+ run_git(["checkout", head_ref])
317
+ run_git(%w[pull --ff-only], allow_failure: true)
318
+ end
266
319
 
320
+ return existing[:path]
321
+ end
322
+
323
+ # Create a new worktree for this PR
324
+ slug = "pr-#{pr_number}-ci-fix"
325
+ display_message("🌿 Creating worktree for PR ##{pr_number}: #{head_ref}", type: :info)
326
+
327
+ # Fetch the branch first
267
328
  Dir.chdir(@project_dir) do
268
- # Fetch latest
269
329
  run_git(%w[fetch origin])
330
+ end
270
331
 
271
- # Checkout the PR branch
272
- run_git(["checkout", head_ref])
332
+ # Create worktree
333
+ result = Aidp::Worktree.create(
334
+ slug: slug,
335
+ project_dir: @project_dir,
336
+ branch: head_ref,
337
+ base_branch: nil # Branch already exists, no base needed
338
+ )
273
339
 
274
- # Pull latest changes
275
- run_git(%w[pull --ff-only], allow_failure: true)
276
- end
340
+ worktree_path = result[:path]
341
+ Aidp.log_debug("ci_fix_processor", "worktree_created", pr_number: pr_number, branch: head_ref, path: worktree_path)
342
+ display_message("✅ Worktree created at #{worktree_path}", type: :success)
277
343
 
278
- display_message("🌿 Checked out branch: #{head_ref}", type: :info)
344
+ worktree_path
279
345
  end
280
346
 
281
- def apply_fixes(fixes)
347
+ def apply_fixes(fixes, working_dir:)
282
348
  fixes.each do |fix|
283
- file_path = File.join(@project_dir, fix["file"])
349
+ file_path = File.join(working_dir, fix["file"])
284
350
 
285
351
  case fix["action"]
286
352
  when "create", "edit"
@@ -296,8 +362,8 @@ module Aidp
296
362
  end
297
363
  end
298
364
 
299
- def commit_and_push(pr_data, analysis)
300
- Dir.chdir(@project_dir) do
365
+ def commit_and_push(pr_data, analysis, working_dir:)
366
+ Dir.chdir(working_dir) do
301
367
  # Check if there are changes
302
368
  status_output = run_git(%w[status --porcelain])
303
369
  if status_output.strip.empty?