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
@@ -122,6 +122,42 @@ module Aidp
122
122
 
123
123
  private
124
124
 
125
+ # Retry a GitHub CLI operation with exponential backoff
126
+ # Rescues network-related RuntimeErrors from gh CLI
127
+ def with_gh_retry(operation_name, max_retries: 3, initial_delay: 1.0)
128
+ retries = 0
129
+ begin
130
+ yield
131
+ rescue RuntimeError => e
132
+ # Only retry on network-related errors from gh CLI
133
+ if e.message.include?("unexpected EOF") ||
134
+ e.message.include?("connection") ||
135
+ e.message.include?("timeout") ||
136
+ e.message.include?("Request to https://api.github.com")
137
+ retries += 1
138
+ if retries <= max_retries
139
+ delay = initial_delay * (2**(retries - 1))
140
+ Aidp.log_warn("repository_client", "gh_retry",
141
+ operation: operation_name,
142
+ attempt: retries,
143
+ max_retries: max_retries,
144
+ delay: delay,
145
+ error: e.message.lines.first&.strip)
146
+ sleep(delay)
147
+ retry
148
+ else
149
+ Aidp.log_error("repository_client", "gh_retry_exhausted",
150
+ operation: operation_name,
151
+ error: e.message)
152
+ raise
153
+ end
154
+ else
155
+ # Non-network errors should not be retried
156
+ raise
157
+ end
158
+ end
159
+ end
160
+
125
161
  def list_issues_via_gh(labels:, state:)
126
162
  json_fields = %w[number title labels updatedAt state url assignees]
127
163
  cmd = ["gh", "issue", "list", "--repo", full_repo, "--state", state, "--json", json_fields.join(",")]
@@ -156,14 +192,16 @@ module Aidp
156
192
  end
157
193
 
158
194
  def fetch_issue_via_gh(number)
159
- fields = %w[number title body comments labels state assignees url updatedAt author]
160
- cmd = ["gh", "issue", "view", number.to_s, "--repo", full_repo, "--json", fields.join(",")]
195
+ with_gh_retry("fetch_issue") do
196
+ fields = %w[number title body comments labels state assignees url updatedAt author]
197
+ cmd = ["gh", "issue", "view", number.to_s, "--repo", full_repo, "--json", fields.join(",")]
161
198
 
162
- stdout, stderr, status = Open3.capture3(*cmd)
163
- raise "GitHub CLI error: #{stderr.strip}" unless status.success?
199
+ stdout, stderr, status = Open3.capture3(*cmd)
200
+ raise "GitHub CLI error: #{stderr.strip}" unless status.success?
164
201
 
165
- data = JSON.parse(stdout)
166
- normalize_issue_detail(data)
202
+ data = JSON.parse(stdout)
203
+ normalize_issue_detail(data)
204
+ end
167
205
  rescue JSON::ParserError => e
168
206
  raise "Failed to parse GitHub CLI issue response: #{e.message}"
169
207
  end
@@ -240,6 +278,33 @@ module Aidp
240
278
  end
241
279
 
242
280
  def create_pull_request_via_gh(title:, body:, head:, base:, issue_number:, draft: false, assignee: nil)
281
+ Aidp.log_debug(
282
+ "repository_client",
283
+ "preparing_gh_pr_create",
284
+ repo: full_repo,
285
+ head: head,
286
+ base: base,
287
+ draft: draft,
288
+ assignee: assignee,
289
+ issue_number: issue_number,
290
+ gh_available: gh_available?,
291
+ title_length: title.length,
292
+ body_length: body.length
293
+ )
294
+
295
+ unless gh_available?
296
+ error_msg = "GitHub CLI (gh) is not available - cannot create PR"
297
+ Aidp.log_error(
298
+ "repository_client",
299
+ "gh_cli_not_available",
300
+ repo: full_repo,
301
+ head: head,
302
+ base: base,
303
+ message: error_msg
304
+ )
305
+ raise error_msg
306
+ end
307
+
243
308
  cmd = [
244
309
  "gh", "pr", "create",
245
310
  "--repo", full_repo,
@@ -251,8 +316,56 @@ module Aidp
251
316
  cmd += ["--draft"] if draft
252
317
  cmd += ["--assignee", assignee] if assignee
253
318
 
319
+ Aidp.log_debug(
320
+ "repository_client",
321
+ "executing_gh_pr_create",
322
+ repo: full_repo,
323
+ head: head,
324
+ base: base,
325
+ draft: draft,
326
+ assignee: assignee,
327
+ command: cmd.join(" ")
328
+ )
329
+
254
330
  stdout, stderr, status = Open3.capture3(*cmd)
255
- raise "Failed to create PR via gh: #{stderr.strip}" unless status.success?
331
+
332
+ Aidp.log_debug(
333
+ "repository_client",
334
+ "gh_pr_create_result",
335
+ repo: full_repo,
336
+ success: status.success?,
337
+ exit_code: status.exitstatus,
338
+ stdout_length: stdout.length,
339
+ stderr_length: stderr.length,
340
+ stdout_preview: stdout[0, 200],
341
+ stderr: stderr
342
+ )
343
+
344
+ unless status.success?
345
+ Aidp.log_error(
346
+ "repository_client",
347
+ "gh_pr_create_failed",
348
+ repo: full_repo,
349
+ head: head,
350
+ base: base,
351
+ issue_number: issue_number,
352
+ exit_code: status.exitstatus,
353
+ stderr: stderr,
354
+ stdout: stdout,
355
+ command: cmd.join(" ")
356
+ )
357
+ raise "Failed to create PR via gh: #{stderr.strip}"
358
+ end
359
+
360
+ Aidp.log_info(
361
+ "repository_client",
362
+ "gh_pr_create_success",
363
+ repo: full_repo,
364
+ head: head,
365
+ base: base,
366
+ issue_number: issue_number,
367
+ output_preview: stdout[0, 200]
368
+ )
256
369
 
257
370
  stdout.strip
258
371
  end
@@ -476,14 +589,19 @@ module Aidp
476
589
  cmd = ["gh", "api", "repos/#{full_repo}/commits/#{head_sha}/check-runs", "--jq", "."]
477
590
  stdout, _stderr, status = Open3.capture3(*cmd)
478
591
 
479
- if status.success?
592
+ check_runs = if status.success?
480
593
  data = JSON.parse(stdout)
481
- check_runs = data["check_runs"] || []
482
- normalize_ci_status(check_runs, head_sha)
594
+ data["check_runs"] || []
483
595
  else
484
- # Fallback to status checks
485
- {sha: head_sha, state: "unknown", checks: []}
596
+ Aidp.log_warn("repository_client", "Failed to fetch check runs", sha: head_sha)
597
+ []
486
598
  end
599
+
600
+ # Also fetch commit statuses (for OAuth apps, webhooks, etc.)
601
+ commit_statuses = fetch_commit_statuses_via_gh(head_sha)
602
+
603
+ # Combine and normalize both check runs and commit statuses
604
+ normalize_ci_status_combined(check_runs, commit_statuses, head_sha)
487
605
  rescue => e
488
606
  Aidp.log_warn("repository_client", "Failed to fetch CI status", error: e.message)
489
607
  {sha: nil, state: "unknown", checks: []}
@@ -501,13 +619,19 @@ module Aidp
501
619
  http.request(request)
502
620
  end
503
621
 
504
- if response.code == "200"
622
+ check_runs = if response.code == "200"
505
623
  data = JSON.parse(response.body)
506
- check_runs = data["check_runs"] || []
507
- normalize_ci_status(check_runs, head_sha)
624
+ data["check_runs"] || []
508
625
  else
509
- {sha: head_sha, state: "unknown", checks: []}
626
+ Aidp.log_warn("repository_client", "Failed to fetch check runs via API", sha: head_sha, code: response.code)
627
+ []
510
628
  end
629
+
630
+ # Also fetch commit statuses (for OAuth apps, webhooks, etc.)
631
+ commit_statuses = fetch_commit_statuses_via_api(head_sha)
632
+
633
+ # Combine and normalize both check runs and commit statuses
634
+ normalize_ci_status_combined(check_runs, commit_statuses, head_sha)
511
635
  rescue => e
512
636
  Aidp.log_warn("repository_client", "Failed to fetch CI status", error: e.message)
513
637
  {sha: nil, state: "unknown", checks: []}
@@ -660,6 +784,85 @@ module Aidp
660
784
  }
661
785
  end
662
786
 
787
+ def fetch_commit_statuses_via_gh(head_sha)
788
+ cmd = ["gh", "api", "repos/#{full_repo}/commits/#{head_sha}/status", "--jq", "."]
789
+ stdout, _stderr, status = Open3.capture3(*cmd)
790
+
791
+ if status.success?
792
+ data = JSON.parse(stdout)
793
+ data["statuses"] || []
794
+ else
795
+ Aidp.log_debug("repository_client", "No commit statuses found", sha: head_sha)
796
+ []
797
+ end
798
+ rescue => e
799
+ Aidp.log_warn("repository_client", "Failed to fetch commit statuses", error: e.message, sha: head_sha)
800
+ []
801
+ end
802
+
803
+ def fetch_commit_statuses_via_api(head_sha)
804
+ uri = URI("https://api.github.com/repos/#{full_repo}/commits/#{head_sha}/status")
805
+ request = Net::HTTP::Get.new(uri)
806
+ request["Accept"] = "application/vnd.github.v3+json"
807
+
808
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
809
+ http.request(request)
810
+ end
811
+
812
+ if response.code == "200"
813
+ data = JSON.parse(response.body)
814
+ data["statuses"] || []
815
+ else
816
+ Aidp.log_debug("repository_client", "No commit statuses found via API", sha: head_sha, code: response.code)
817
+ []
818
+ end
819
+ rescue => e
820
+ Aidp.log_warn("repository_client", "Failed to fetch commit statuses via API", error: e.message, sha: head_sha)
821
+ []
822
+ end
823
+
824
+ def normalize_ci_status_combined(check_runs, commit_statuses, head_sha)
825
+ # Convert commit statuses to same format as check runs for unified processing
826
+ # normalize_ci_status expects string keys, so we use string keys here
827
+ checks_from_statuses = commit_statuses.map do |status|
828
+ {
829
+ "name" => status["context"],
830
+ "status" => (status["state"] == "pending") ? "in_progress" : "completed",
831
+ "conclusion" => normalize_commit_status_to_conclusion(status["state"]),
832
+ "details_url" => status["target_url"],
833
+ "output" => status["description"] ? {"summary" => status["description"]} : nil
834
+ }
835
+ end
836
+
837
+ # Combine check runs and converted commit statuses
838
+ # check_runs should already have string keys from API/CLI responses
839
+ all_checks = check_runs + checks_from_statuses
840
+
841
+ Aidp.log_debug("repository_client", "combined_ci_checks",
842
+ check_run_count: check_runs.length,
843
+ commit_status_count: commit_statuses.length,
844
+ total_checks: all_checks.length)
845
+
846
+ # Use existing normalize logic
847
+ normalize_ci_status(all_checks, head_sha)
848
+ end
849
+
850
+ def normalize_commit_status_to_conclusion(state)
851
+ # Map commit status states to check run conclusions
852
+ # Commit status states: error, failure, pending, success
853
+ # Check run conclusions: success, failure, neutral, cancelled, timed_out, action_required, skipped, stale
854
+ case state
855
+ when "success"
856
+ "success"
857
+ when "failure"
858
+ "failure"
859
+ when "error"
860
+ "failure" # Treat error as failure for consistency
861
+ when "pending", nil
862
+ nil # pending means not completed yet
863
+ end
864
+ end
865
+
663
866
  def normalize_ci_status(check_runs, head_sha)
664
867
  checks = check_runs.map do |run|
665
868
  {
@@ -671,17 +874,42 @@ module Aidp
671
874
  }
672
875
  end
673
876
 
877
+ Aidp.log_debug("repository_client", "normalize_ci_status",
878
+ check_count: checks.length,
879
+ checks: checks.map { |c| {name: c[:name], status: c[:status], conclusion: c[:conclusion]} })
880
+
674
881
  # Determine overall state
675
- state = if checks.any? { |c| c[:conclusion] == "failure" }
882
+ # CRITICAL: Empty check list should be "unknown", not "success"
883
+ # This guard must come FIRST to prevent vacuous truth from [].all? { condition }
884
+ state = if checks.empty?
885
+ Aidp.log_debug("repository_client", "ci_status_empty", message: "No checks found")
886
+ "unknown"
887
+ elsif checks.any? { |c| c[:conclusion] == "failure" }
888
+ failing_checks = checks.select { |c| c[:conclusion] == "failure" }
889
+ Aidp.log_debug("repository_client", "ci_status_failure",
890
+ failing_count: failing_checks.length,
891
+ failing_checks: failing_checks.map { |c| c[:name] })
676
892
  "failure"
677
893
  elsif checks.any? { |c| c[:status] != "completed" }
894
+ pending_checks = checks.select { |c| c[:status] != "completed" }
895
+ Aidp.log_debug("repository_client", "ci_status_pending",
896
+ pending_count: pending_checks.length,
897
+ pending_checks: pending_checks.map { |c| {name: c[:name], status: c[:status]} })
678
898
  "pending"
679
899
  elsif checks.all? { |c| c[:conclusion] == "success" }
900
+ Aidp.log_debug("repository_client", "ci_status_success",
901
+ success_count: checks.length)
680
902
  "success"
681
903
  else
904
+ non_success_checks = checks.reject { |c| c[:conclusion] == "success" }
905
+ Aidp.log_debug("repository_client", "ci_status_unknown",
906
+ non_success_count: non_success_checks.length,
907
+ non_success_checks: non_success_checks.map { |c| {name: c[:name], conclusion: c[:conclusion]} })
682
908
  "unknown"
683
909
  end
684
910
 
911
+ Aidp.log_debug("repository_client", "ci_status_determined", sha: head_sha, state: state)
912
+
685
913
  {
686
914
  sha: head_sha,
687
915
  state: state,
@@ -5,6 +5,8 @@ require "json"
5
5
  require "time"
6
6
 
7
7
  require_relative "../message_display"
8
+ require_relative "github_state_extractor"
9
+ require_relative "implementation_verifier"
8
10
  require_relative "reviewers/senior_dev_reviewer"
9
11
  require_relative "reviewers/security_reviewer"
10
12
  require_relative "reviewers/performance_reviewer"
@@ -23,9 +25,10 @@ module Aidp
23
25
 
24
26
  attr_reader :review_label
25
27
 
26
- def initialize(repository_client:, state_store:, provider_name: nil, project_dir: Dir.pwd, label_config: {}, verbose: false, reviewers: nil)
28
+ def initialize(repository_client:, state_store:, provider_name: nil, project_dir: Dir.pwd, label_config: {}, verbose: false, reviewers: nil, verifier: nil)
27
29
  @repository_client = repository_client
28
30
  @state_store = state_store
31
+ @state_extractor = GitHubStateExtractor.new(repository_client: repository_client)
29
32
  @provider_name = provider_name
30
33
  @project_dir = project_dir
31
34
  @verbose = verbose
@@ -33,6 +36,12 @@ module Aidp
33
36
  # Load label configuration
34
37
  @review_label = label_config[:review_trigger] || label_config["review_trigger"] || DEFAULT_REVIEW_LABEL
35
38
 
39
+ # Initialize verifier (allow dependency injection for testing)
40
+ @verifier = verifier || ImplementationVerifier.new(
41
+ repository_client: repository_client,
42
+ project_dir: project_dir
43
+ )
44
+
36
45
  # Initialize reviewers (allow dependency injection for testing)
37
46
  @reviewers = reviewers || [
38
47
  Reviewers::SeniorDevReviewer.new(provider_name: provider_name),
@@ -44,7 +53,8 @@ module Aidp
44
53
  def process(pr)
45
54
  number = pr[:number]
46
55
 
47
- if @state_store.review_processed?(number)
56
+ # Check if review already completed via GitHub comments
57
+ if @state_extractor.review_completed?(pr)
48
58
  display_message("ℹ️ Review for PR ##{number} already posted. Skipping.", type: :muted)
49
59
  return
50
60
  end
@@ -56,6 +66,9 @@ module Aidp
56
66
  files = @repository_client.fetch_pull_request_files(number)
57
67
  diff = @repository_client.fetch_pull_request_diff(number)
58
68
 
69
+ # Check if PR is linked to an issue - if so, run implementation verification
70
+ verification_result = check_implementation_completeness(pr_data)
71
+
59
72
  # Run reviews in parallel (conceptually - actual implementation is sequential)
60
73
  review_results = run_reviews(pr_data: pr_data, files: files, diff: diff)
61
74
 
@@ -63,7 +76,11 @@ module Aidp
63
76
  log_review(number, review_results)
64
77
 
65
78
  # Format and post comment
66
- comment_body = format_review_comment(pr: pr_data, review_results: review_results)
79
+ comment_body = format_review_comment(
80
+ pr: pr_data,
81
+ review_results: review_results,
82
+ verification_result: verification_result
83
+ )
67
84
  @repository_client.post_comment(number, comment_body)
68
85
 
69
86
  display_message("💬 Posted review comment for PR ##{number}", type: :success)
@@ -84,19 +101,14 @@ module Aidp
84
101
  display_message("❌ Review failed: #{e.message}", type: :error)
85
102
  Aidp.log_error("review_processor", "Review failed", pr: number, error: e.message, backtrace: e.backtrace&.first(10))
86
103
 
87
- # Post error comment
88
- error_comment = <<~COMMENT
89
- #{COMMENT_HEADER}
90
-
91
- ❌ Automated review failed: #{e.message}
92
-
93
- Please review manually or retry by re-adding the `#{@review_label}` label.
94
- COMMENT
95
- begin
96
- @repository_client.post_comment(number, error_comment)
97
- rescue
98
- nil
99
- end
104
+ # Record failure state internally but DON'T post error to GitHub
105
+ # (per issue #280 - error messages should never appear on issues)
106
+ @state_store.record_review(number, {
107
+ status: "error",
108
+ error: e.message,
109
+ error_class: e.class.name,
110
+ timestamp: Time.now.utc.iso8601
111
+ })
100
112
  end
101
113
 
102
114
  private
@@ -123,13 +135,82 @@ module Aidp
123
135
  results
124
136
  end
125
137
 
126
- def format_review_comment(pr:, review_results:)
138
+ def check_implementation_completeness(pr_data)
139
+ # Extract linked issue from PR description
140
+ issue_number = @state_extractor.extract_linked_issue(pr_data[:body])
141
+
142
+ unless issue_number
143
+ display_message(" ℹ️ No linked issue found - skipping implementation verification", type: :muted) if @verbose
144
+ return nil
145
+ end
146
+
147
+ display_message(" 🔗 Found linked issue ##{issue_number} - verifying implementation...", type: :info) if @verbose
148
+
149
+ begin
150
+ # Fetch the linked issue
151
+ issue = @repository_client.fetch_issue(issue_number)
152
+
153
+ # Check if a worktree exists for this PR branch
154
+ working_dir = find_or_use_worktree(pr_data[:head_ref])
155
+
156
+ # Run verification
157
+ result = @verifier.verify(issue: issue, working_dir: working_dir)
158
+
159
+ if result[:verified]
160
+ display_message(" ✅ Implementation verified complete", type: :success) if @verbose
161
+ else
162
+ display_message(" ⚠️ Implementation appears incomplete", type: :warn)
163
+ end
164
+
165
+ result
166
+ rescue => e
167
+ display_message(" ⚠️ Verification check failed: #{e.message}", type: :warn)
168
+ Aidp.log_error("review_processor", "Verification failed", issue: issue_number, error: e.message)
169
+ nil
170
+ end
171
+ end
172
+
173
+ def find_or_use_worktree(branch)
174
+ # Check if a worktree already exists for this branch
175
+ existing = Aidp::Worktree.find_by_branch(branch: branch, project_dir: @project_dir)
176
+
177
+ if existing && existing[:active]
178
+ display_message(" 🔄 Using existing worktree for branch: #{branch}", type: :muted) if @verbose
179
+ return existing[:path]
180
+ end
181
+
182
+ # Otherwise, use the main project directory
183
+ # (assuming the branch is checked out in the main directory)
184
+ @project_dir
185
+ end
186
+
187
+ def format_review_comment(pr:, review_results:, verification_result: nil)
127
188
  parts = []
128
189
  parts << COMMENT_HEADER
129
190
  parts << ""
130
191
  parts << "Automated multi-persona code review for PR ##{pr[:number]}"
131
192
  parts << ""
132
193
 
194
+ # Add verification results if present
195
+ if verification_result
196
+ if verification_result[:verified]
197
+ parts << "### ✅ Implementation Verification"
198
+ parts << ""
199
+ parts << "_Implementation successfully verified against linked issue requirements._"
200
+ else
201
+ parts << "### ⚠️ Implementation Incomplete"
202
+ parts << ""
203
+ parts << "**This PR appears to be incomplete based on the linked issue requirements:**"
204
+ parts << ""
205
+ verification_result[:reasons]&.each do |reason|
206
+ parts << "- #{reason}"
207
+ end
208
+ parts << ""
209
+ parts << "**Suggested Action:** Add the `aidp-request-changes` label if you'd like AIDP to help complete the implementation."
210
+ end
211
+ parts << ""
212
+ end
213
+
133
214
  # Collect all findings by severity
134
215
  all_findings = collect_findings_by_severity(review_results)
135
216
 
@@ -43,7 +43,7 @@ module Aidp
43
43
  def provider
44
44
  @provider ||= begin
45
45
  provider_name = @provider_name || detect_default_provider
46
- Aidp::ProviderManager.get_provider(provider_name, use_harness: false)
46
+ Aidp::ProviderManager.get_provider(provider_name)
47
47
  end
48
48
  end
49
49