@@ -66,6 +66,11 @@ async def _promote_uncovered(ctx: StepContext, output: dict,
6666 "'covered by weekly only') is a FINDING wearing a validation "
6767 "stamp — promote it as the pointed question or ask it implies. "
6868 "Rules: "
69+ "A true code fact is not automatically a defect. Require a concrete "
70+ "current trigger and adverse effect or an unmet requirement. Do not "
71+ "promote harmless unused configuration or hypothetical future code "
72+ "changes. Respect REJECTED COMMENTS and their reasons; reopen one "
73+ "only with new evidence that defeats the rejection. "
6974 "(1) ONLY promote what the raw lines already state — no new claims, "
7075 "no re-investigation; quote the source line in `evidence`. (2) A "
7176 "promoted line KEEPS its directive force: a concern naming a "
@@ -84,6 +89,9 @@ async def _promote_uncovered(ctx: StepContext, output: dict,
8489 "comment" )}
8590 for c in comments ], ensure_ascii = False , indent = 1 )
8691 + "\n \n ## RAW FINDINGS\n " + "\n " .join (f"- { x } " for x in findings )
92+ + "\n \n ## REJECTED COMMENTS\n "
93+ + json .dumps (output .get ("_rejected_review_comments" ) or [],
94+ ensure_ascii = False )
8795 + ("\n \n ## BLOCKERS\n " + "\n " .join (f"- { x } " for x in blockers )
8896 if blockers else "" )
8997 + ("\n \n ## ASSUMPTIONS\n " + "\n " .join (f"- { x } " for x in assumptions )
@@ -229,6 +237,11 @@ async def _second_round(ctx: StepContext, output: dict, common: dict,
229237 "You are the SECOND-ROUND reviewer. The first round produced the "
230238 "KEPT COMMENTS in your evidence; your job is ONLY its coverage "
231239 "holes — do not re-litigate or duplicate what is already covered.\n "
240+ "Respect rejected_comments and their reasons. Reopen a rejected "
241+ "concern only with new evidence defeating that reason. A true code "
242+ "fact needs a current trigger and adverse effect or an unmet "
243+ "requirement to become a finding; harmless unused values and "
244+ "hypothetical future changes are not defects.\n "
232245 + (("These diff hunks (file:start-line) have NO comment and NO "
233246 "recorded verification near them. Review each AT MAINTAINER "
234247 "INLINE GRANULARITY: page to the hunk in situ, and either raise "
@@ -271,7 +284,10 @@ async def _second_round(ctx: StepContext, output: dict, common: dict,
271284 "evidence" : {** common ["evidence" ],
272285 "uncovered_hunk_diffs" : hunk_evidence ,
273286 "kept_comments" : json .dumps (
274- kept , ensure_ascii = False )}}
287+ kept , ensure_ascii = False ),
288+ "rejected_comments" : json .dumps (
289+ output .get ("_rejected_review_comments" )
290+ or [], ensure_ascii = False )}}
275291 result , extra = await run_agent_step (
276292 ctx , ** round_kwargs ,
277293 max_iters = ctx .settings .review_second_round_max_iters , ** routing )
@@ -333,13 +349,20 @@ async def _second_round(ctx: StepContext, output: dict, common: dict,
3333492. Claim: is the asserted problem true of the code you just read? If the
334350 comment asserts consumers/callers/tests elsewhere, grep for them and read
335351 the one that decides the claim.
336- 3. Severity: major requires a real defect in the changed code or a required
352+ 3. Actionability: establish a concrete trigger on this PR head and its
353+ adverse effect, or a specific unmet requirement. A code fact being true
354+ does not establish a defect. Unused optional configuration without an
355+ affected consumer, and a problem contingent on hypothetical future code
356+ changes, are not actionable. Check rejected_comments before reopening
357+ a concern; identify new evidence that defeats its rejection.
358+ 4. Severity: major requires a real defect in the changed code or a required
337359 update the diff lacks; "consider adding X" polish is minor at most.
338360
339361Verdicts:
340362- confirmed: you READ the code that makes the claim true. You MUST return
341363 `evidence` as SELF-CONTAINED PROOF a reader with no repo access can check:
342- the decisive code line(s) QUOTED VERBATIM with their file:line, e.g.
364+ the current trigger and adverse effect (or unmet requirement), together
365+ with the decisive code line(s) QUOTED VERBATIM with their file:line, e.g.
343366 'serving_speech.py:3711 `extra_args["tts_local_seed"] = seed` — set for
344367 every model, no qwen3_tts gate'. A narrative like "read the file, claim
345368 holds" is NOT proof and scores as speculation downstream. When the
@@ -352,6 +375,9 @@ async def _second_round(ctx: StepContext, output: dict, common: dict,
352375 the substance, never soften a confirmed defect.
353376- refuted: the code CONTRADICTS the claim (misread, already handled, wrong
354377 file). Refuted is an evidence conclusion, never a budget one.
378+ - not_actionable: the cited facts may be true, but no current defect or
379+ unmet requirement is demonstrated. Explain why the proposed ask is not
380+ needed on this head. This verdict drops the comment.
355381- unverifiable: you could not decide within budget.
356382
357383Confirming from plausibility alone is the one failure this pass exists to
@@ -363,7 +389,7 @@ async def _verify_comments(ctx: StepContext, output: dict,
363389 """Per-comment agentic verification (val-gate lesson: with recall at
364390 parity the arm lost on per-comment grounding). Each draft comment gets
365391 one small tool-loop that must anchor and re-derive the claim on the
366- PR-time tree: refuted comments drop, unverifiable ones demote one
392+ PR-time tree: refuted and non-actionable comments drop, unverifiable ones demote one
367393 severity step, confirmed ones may be tightened in wording/position. A
368394 verification-step failure keeps the comment unchanged — this pass may
369395 only improve precision, never silently delete recall."""
@@ -385,19 +411,23 @@ async def _one(i: int, c: dict):
385411 purpose = "Verify one draft review comment against the "
386412 "PR-time tree." ,
387413 guidance = _VERIFY_GUIDANCE ,
388- expected = "verdict confirmed|refuted|unverifiable, with "
414+ expected = "verdict confirmed|refuted|not_actionable| unverifiable, with "
389415 "optional comment/line/severity corrections" ,
390416 evidence = {"pr_diff" : diff_ev ,
391417 "draft_comment" : json .dumps (
392- probe , ensure_ascii = False )},
418+ probe , ensure_ascii = False ),
419+ "rejected_comments" : json .dumps (
420+ output .get ("_rejected_review_comments" ) or [],
421+ ensure_ascii = False )},
393422 output_extension = {
394- "verdict" : "confirmed|refuted|unverifiable" ,
423+ "verdict" : "confirmed|refuted|not_actionable| unverifiable" ,
395424 "comment" : "optional tightened rewrite" ,
396425 "line" : "optional corrected line number" ,
397426 "severity" : "optional corrected severity" ,
398427 "evidence" : "confirmed only: verbatim-quoted decisive "
399- "code line(s) with file:line — proof a "
400- "repo-less reader can check" },
428+ "code line(s) with file:line, current trigger "
429+ "and adverse effect or unmet requirement — "
430+ "proof a repo-less reader can check" },
401431 extra_tools = common .get ("extra_tools" ),
402432 max_iters = ctx .settings .review_verify_max_iters )
403433 return i , result , (out or {})
@@ -411,10 +441,10 @@ async def _one(i: int, c: dict):
411441 c = dict (comments [i ])
412442 verdict = str (out .get ("verdict" ) or "" ).lower ()
413443 if not result .ok or verdict not in ("confirmed" , "refuted" ,
414- "unverifiable" ):
444+ "not_actionable" , " unverifiable" ):
415445 kept .append (c ) # fail-open: never delete on pass failure
416446 continue
417- if verdict == "refuted" :
447+ if verdict in ( "refuted" , "not_actionable" ) :
418448 n_drop += 1
419449 continue
420450 if verdict == "confirmed" :
0 commit comments