55from agents .base import BaseAgent
66from app .config import Settings
77from app .logging import get_logger
8- from domain import AgentDecision , AgentRole , Finding , PRContext , Severity
8+ from domain import AgentDecision , AgentRole , Finding , FindingType , PRContext , Severity
99
1010logger = get_logger (__name__ )
1111
@@ -26,6 +26,8 @@ def analyze(self, context: PRContext) -> AgentDecision:
2626
2727 findings = self ._deduplicate_findings (all_findings )
2828 findings = self ._resolve_conflicts (findings )
29+ findings = self ._filter_low_quality (findings )
30+ findings = self ._filter_minor_issues (findings )
2931 findings = self ._apply_nit_limit (findings , self .max_nits )
3032 findings = self ._validate_findings (findings )
3133
@@ -76,3 +78,80 @@ def _resolve_conflicts(self, findings: List[Finding]) -> List[Finding]:
7678
7779 logger .info (f"Resolved conflicts: { len (findings )} -> { len (resolved )} " )
7880 return resolved
81+
82+ def _filter_low_quality (self , findings : List [Finding ]) -> List [Finding ]:
83+ """Filter out low-quality findings to improve precision."""
84+ filtered = []
85+
86+ for f in findings :
87+ # Calculate quality score
88+ quality_score = self ._calculate_quality_score (f )
89+
90+ # Keep if high quality or has patch (actionable)
91+ if quality_score >= 0.6 or f .has_patch :
92+ filtered .append (f )
93+ elif f .severity == Severity .CRITICAL or f .severity == Severity .MAJOR :
94+ # Always keep critical/major even if quality is lower
95+ filtered .append (f )
96+
97+ logger .info (f"Filtered low quality: { len (findings )} -> { len (filtered )} " )
98+ return filtered
99+
100+ def _calculate_quality_score (self , finding : Finding ) -> float :
101+ """Calculate quality score for a finding (0.0 to 1.0)."""
102+ score = 0.0
103+
104+ # Base score from severity
105+ severity_scores = {
106+ Severity .CRITICAL : 1.0 ,
107+ Severity .MAJOR : 0.8 ,
108+ Severity .MINOR : 0.5 ,
109+ Severity .NIT : 0.3 ,
110+ }
111+ score += severity_scores .get (finding .severity , 0.0 ) * 0.4
112+
113+ # Type priority boost
114+ type_scores = {
115+ FindingType .SECURITY : 1.0 ,
116+ FindingType .LOGIC : 0.9 ,
117+ FindingType .PERFORMANCE : 0.8 ,
118+ FindingType .CONSISTENCY : 0.6 ,
119+ FindingType .STYLE : 0.4 ,
120+ FindingType .OTHER : 0.3 ,
121+ }
122+ score += type_scores .get (finding .type , 0.0 ) * 0.3
123+
124+ # Evidence quality (has location and description)
125+ if finding .location and finding .description :
126+ score += 0.2
127+ if finding .evidence and finding .evidence .snippet :
128+ score += 0.1
129+
130+ return min (score , 1.0 )
131+
132+ def _filter_minor_issues (self , findings : List [Finding ]) -> List [Finding ]:
133+ """Aggressively filter minor issues to reduce noise."""
134+ filtered = []
135+ minor_count = 0
136+ max_minor = 3 # Limit minor findings
137+
138+ # Separate by severity
139+ critical_major = [f for f in findings if f .severity in [Severity .CRITICAL , Severity .MAJOR ]]
140+ minor_nit = [f for f in findings if f .severity in [Severity .MINOR , Severity .NIT ]]
141+
142+ # Keep all critical/major
143+ filtered .extend (critical_major )
144+
145+ # Prioritize minor findings with patches or high type priority
146+ minor_sorted = sorted (
147+ minor_nit ,
148+ key = lambda f : (f .has_patch , self .type_priority (f .type ), f .severity == Severity .MINOR ),
149+ reverse = True
150+ )
151+
152+ # Keep top minor findings
153+ filtered .extend (minor_sorted [:max_minor ])
154+ minor_count = len ([f for f in filtered if f .severity == Severity .MINOR ])
155+
156+ logger .info (f"Filtered minor issues: { len (minor_nit )} -> { len (filtered ) - len (critical_major )} " )
157+ return filtered
0 commit comments