@@ -77,6 +77,7 @@ module EvalOpsPrLensReview
7777 DEFAULT_MODEL = "claude-opus-4-7"
7878 DEFAULT_MAX_DIFF_BYTES = 180_000
7979 MAX_FINDINGS_PER_COMMENT = 12
80+ MAX_CONTEXT_ITEMS = 25
8081
8182 module_function
8283
@@ -245,7 +246,60 @@ def pr_file_summary(repo:, pr:)
245246 end . join ( "\n " )
246247 end
247248
248- def build_lens_prompt ( repo :, pr :, lens :, pr_json :, file_summary :, changed_files_text :, diff_text :, diff_truncated :)
249+ def short_text ( value , max_bytes : 1_500 )
250+ text = value . to_s . strip
251+ return "" if text . empty?
252+ return text if text . bytesize <= max_bytes
253+
254+ "#{ text . byteslice ( 0 , max_bytes ) } \n ...[truncated]"
255+ end
256+
257+ def list_section ( title , rows )
258+ body = rows . compact . map ( &:strip ) . reject ( &:empty? )
259+ return "#{ title } :\n (none)" if body . empty?
260+
261+ "#{ title } :\n #{ body . first ( MAX_CONTEXT_ITEMS ) . join ( "\n " ) } "
262+ end
263+
264+ def pr_review_context ( repo :, pr :, pr_json :, head_sha :)
265+ issue_comments = gh_api_json ( "repos/#{ repo } /issues/#{ pr } /comments?per_page=100" )
266+ reviews = gh_api_json ( "repos/#{ repo } /pulls/#{ pr } /reviews?per_page=100" )
267+ review_comments = gh_api_json ( "repos/#{ repo } /pulls/#{ pr } /comments?per_page=100" )
268+ check_runs = gh_api_json ( "repos/#{ repo } /commits/#{ head_sha } /check-runs?per_page=100" ) . fetch ( "check_runs" , [ ] )
269+ combined_status = gh_api_json ( "repos/#{ repo } /commits/#{ head_sha } /status" )
270+
271+ comments = issue_comments . last ( MAX_CONTEXT_ITEMS ) . map do |comment |
272+ "- #{ comment . dig ( "user" , "login" ) } at #{ comment . fetch ( "created_at" , "" ) } : #{ short_text ( comment [ "body" ] , max_bytes : 900 ) } "
273+ end
274+ review_rows = reviews . last ( MAX_CONTEXT_ITEMS ) . map do |review |
275+ body = short_text ( review [ "body" ] , max_bytes : 900 )
276+ "- #{ review . dig ( "user" , "login" ) } #{ review . fetch ( "state" , "" ) } at #{ review . fetch ( "submitted_at" , "" ) } : #{ body . empty? ? "(no body)" : body } "
277+ end
278+ inline_rows = review_comments . last ( MAX_CONTEXT_ITEMS ) . map do |comment |
279+ line = comment [ "line" ] || comment [ "original_line" ] || "?"
280+ "- #{ comment . dig ( "user" , "login" ) } #{ comment . fetch ( "path" , "unknown" ) } :#{ line } : #{ short_text ( comment [ "body" ] , max_bytes : 900 ) } "
281+ end
282+ check_rows = check_runs . select do |check |
283+ !%w[ success skipped neutral ] . include? ( check [ "conclusion" ] . to_s . downcase )
284+ end . map do |check |
285+ "- check-run #{ check . fetch ( "name" , "unknown" ) } : status=#{ check . fetch ( "status" , "" ) } conclusion=#{ check [ "conclusion" ] || "pending" } "
286+ end
287+ status_rows = Array ( combined_status [ "statuses" ] ) . select do |status |
288+ status [ "state" ] . to_s != "success"
289+ end . map do |status |
290+ "- status #{ status . fetch ( "context" , "unknown" ) } : state=#{ status . fetch ( "state" , "" ) } description=#{ status [ "description" ] } "
291+ end
292+
293+ [
294+ "Pull request body:\n #{ short_text ( pr_json [ "body" ] , max_bytes : 2_500 ) . empty? ? "(none)" : short_text ( pr_json [ "body" ] , max_bytes : 2_500 ) } " ,
295+ list_section ( "Issue comments" , comments ) ,
296+ list_section ( "PR review bodies" , review_rows ) ,
297+ list_section ( "Inline review comments" , inline_rows ) ,
298+ list_section ( "Non-green checks and statuses" , check_rows + status_rows )
299+ ] . join ( "\n \n " )
300+ end
301+
302+ def build_lens_prompt ( repo :, pr :, lens :, pr_json :, file_summary :, review_context :, changed_files_text :, diff_text :, diff_truncated :)
249303 lens_config = LENSES . fetch ( valid_lens! ( lens ) )
250304 <<~PROMPT
251305 You are reviewing an EvalOps pull request through one narrow lens: #{ lens_config . fetch ( :name ) } .
@@ -265,6 +319,8 @@ def build_lens_prompt(repo:, pr:, lens:, pr_json:, file_summary:, changed_files_
265319 - Report only actionable defects introduced by this PR that fit the lens.
266320 - Prefer no finding over a speculative finding.
267321 - Confidence must reflect direct evidence from the diff or live PR metadata.
322+ - Existing bot or human review comments are evidence, but verify them
323+ against the diff before turning them into a finding.
268324 - Use head-side file paths and line numbers where possible.
269325 - If no high-signal finding exists, return an empty findings array.
270326 - Do not ask for broad architecture redesigns, style-only changes, or unrelated cleanup.
@@ -292,6 +348,9 @@ def build_lens_prompt(repo:, pr:, lens:, pr_json:, file_summary:, changed_files_
292348 Pull request files from GitHub:
293349 #{ file_summary . empty? ? "(no file metadata)" : file_summary }
294350
351+ Pull request context:
352+ #{ review_context . empty? ? "(no PR context)" : review_context }
353+
295354 Changed files from git:
296355 #{ changed_files_text . empty? ? "(no changed files)" : changed_files_text }
297356
@@ -455,6 +514,7 @@ def normalize_lens_review(raw_review, repo:, pr:, lens:, head_sha:)
455514 def run_lens ( repo :, pr :, lens :, workspace :, base_sha :, head_sha :, output :, provider :, model :, max_diff_bytes :)
456515 pr_json = pr_metadata ( repo : repo , pr : pr )
457516 file_summary = pr_file_summary ( repo : repo , pr : pr )
517+ review_context = pr_review_context ( repo : repo , pr : pr , pr_json : pr_json , head_sha : head_sha )
458518 changed_files_text = changed_files ( workspace : workspace , base_sha : base_sha , head_sha : head_sha )
459519 diff_text , diff_truncated = git_diff (
460520 workspace : workspace ,
@@ -468,6 +528,7 @@ def run_lens(repo:, pr:, lens:, workspace:, base_sha:, head_sha:, output:, provi
468528 lens : lens ,
469529 pr_json : pr_json ,
470530 file_summary : file_summary ,
531+ review_context : review_context ,
471532 changed_files_text : changed_files_text ,
472533 diff_text : diff_text ,
473534 diff_truncated : diff_truncated
0 commit comments