@@ -184,6 +184,10 @@ pub fn cmd_parse_findings(
184184 _register : bool ,
185185 _source : String ,
186186) {
187+ use flowctl_core:: review_protocol:: {
188+ filter_by_confidence, AutofixClass , FindingOwner , ReviewFinding , Severity ,
189+ } ;
190+
187191 // Read input from file or stdin
188192 let text = if file == "-" {
189193 use std:: io:: Read ;
@@ -203,9 +207,10 @@ pub fn cmd_parse_findings(
203207 }
204208 } ;
205209
206- let mut findings: Vec < serde_json :: Value > = Vec :: new ( ) ;
210+ let mut findings: Vec < ReviewFinding > = Vec :: new ( ) ;
207211 let mut warnings: Vec < String > = Vec :: new ( ) ;
208- let required_keys = [ "title" , "severity" , "location" , "recommendation" ] ;
212+ // CE schema required keys
213+ let required_keys = [ "severity" , "file" , "line" , "confidence" , "evidence" ] ;
209214
210215 // Tiered extraction:
211216 // 1. <findings>...</findings> tag
@@ -239,12 +244,11 @@ pub fn cmd_parse_findings(
239244
240245 if let Some ( raw) = raw_json {
241246 // Remove trailing commas before ] or }
242- let cleaned = raw
243- . replace ( ",]" , "]" )
244- . replace ( ",}" , "}" ) ;
247+ let cleaned = raw. replace ( ",]" , "]" ) . replace ( ",}" , "}" ) ;
245248
246249 match serde_json:: from_str :: < serde_json:: Value > ( & cleaned) {
247250 Ok ( serde_json:: Value :: Array ( arr) ) => {
251+ let mut raw_findings: Vec < ReviewFinding > = Vec :: new ( ) ;
248252 for ( i, item) in arr. iter ( ) . enumerate ( ) {
249253 if !item. is_object ( ) {
250254 warnings. push ( format ! ( "Finding {} is not an object, skipping" , i) ) ;
@@ -263,8 +267,90 @@ pub fn cmd_parse_findings(
263267 ) ) ;
264268 continue ;
265269 }
266- findings. push ( item. clone ( ) ) ;
270+
271+ let severity = match item. get ( "severity" ) . and_then ( |v| v. as_str ( ) ) {
272+ Some ( "P0" ) | Some ( "critical" ) => Severity :: P0 ,
273+ Some ( "P1" ) | Some ( "warning" ) => Severity :: P1 ,
274+ Some ( "P2" ) => Severity :: P2 ,
275+ _ => Severity :: P3 ,
276+ } ;
277+ let category = item
278+ . get ( "category" )
279+ . and_then ( |v| v. as_str ( ) )
280+ . unwrap_or ( "general" )
281+ . to_string ( ) ;
282+ let description = item
283+ . get ( "description" )
284+ . or_else ( || item. get ( "title" ) )
285+ . and_then ( |v| v. as_str ( ) )
286+ . unwrap_or ( "" )
287+ . to_string ( ) ;
288+ let file_path = item. get ( "file" ) . and_then ( |v| v. as_str ( ) ) . map ( String :: from) ;
289+ let line = item. get ( "line" ) . and_then ( |v| v. as_u64 ( ) ) . map ( |n| n as u32 ) ;
290+ let confidence = item
291+ . get ( "confidence" )
292+ . and_then ( |v| v. as_f64 ( ) )
293+ . unwrap_or ( 0.8 ) ;
294+ let autofix_class =
295+ match item. get ( "autofix_class" ) . and_then ( |v| v. as_str ( ) ) {
296+ Some ( "safe_auto" ) => AutofixClass :: SafeAuto ,
297+ Some ( "gated_auto" ) => AutofixClass :: GatedAuto ,
298+ Some ( "advisory" ) => AutofixClass :: Advisory ,
299+ _ => AutofixClass :: Manual ,
300+ } ;
301+ let owner = match item. get ( "owner" ) . and_then ( |v| v. as_str ( ) ) {
302+ Some ( "review-fixer" ) => FindingOwner :: ReviewFixer ,
303+ Some ( "downstream-resolver" ) => FindingOwner :: DownstreamResolver ,
304+ Some ( "human" ) => FindingOwner :: Human ,
305+ Some ( "release" ) => FindingOwner :: Release ,
306+ _ => FindingOwner :: ReviewFixer ,
307+ } ;
308+ let evidence = item
309+ . get ( "evidence" )
310+ . and_then ( |v| v. as_array ( ) )
311+ . map ( |arr| {
312+ arr. iter ( )
313+ . filter_map ( |v| v. as_str ( ) . map ( String :: from) )
314+ . collect ( )
315+ } )
316+ . unwrap_or_default ( ) ;
317+ let pre_existing = item
318+ . get ( "pre_existing" )
319+ . and_then ( |v| v. as_bool ( ) )
320+ . unwrap_or ( false ) ;
321+ let requires_verification = item
322+ . get ( "requires_verification" )
323+ . and_then ( |v| v. as_bool ( ) )
324+ . unwrap_or ( false ) ;
325+ let suggested_fix = item
326+ . get ( "suggested_fix" )
327+ . and_then ( |v| v. as_str ( ) )
328+ . map ( String :: from) ;
329+ let why_it_matters = item
330+ . get ( "why_it_matters" )
331+ . and_then ( |v| v. as_str ( ) )
332+ . map ( String :: from) ;
333+
334+ raw_findings. push ( ReviewFinding {
335+ severity,
336+ category,
337+ description,
338+ file : file_path,
339+ line,
340+ confidence,
341+ autofix_class,
342+ owner,
343+ evidence,
344+ pre_existing,
345+ requires_verification,
346+ suggested_fix,
347+ why_it_matters,
348+ } ) ;
267349 }
350+
351+ // Apply confidence filtering
352+ findings = filter_by_confidence ( raw_findings) ;
353+
268354 // Cap at 50
269355 if findings. len ( ) > 50 {
270356 warnings. push ( format ! (
@@ -286,8 +372,12 @@ pub fn cmd_parse_findings(
286372 }
287373
288374 if json_mode {
375+ let findings_json: Vec < serde_json:: Value > = findings
376+ . iter ( )
377+ . map ( |f| serde_json:: to_value ( f) . unwrap_or ( json ! ( { } ) ) )
378+ . collect ( ) ;
289379 json_output ( json ! ( {
290- "findings" : findings ,
380+ "findings" : findings_json ,
291381 "count" : findings. len( ) ,
292382 "registered" : 0 ,
293383 "warnings" : warnings,
@@ -298,10 +388,21 @@ pub fn cmd_parse_findings(
298388 eprintln ! ( " Warning: {}" , w) ;
299389 }
300390 for f in & findings {
301- let sev = f[ "severity" ] . as_str ( ) . unwrap_or ( "unknown" ) ;
302- let title = f[ "title" ] . as_str ( ) . unwrap_or ( "" ) ;
303- let location = f[ "location" ] . as_str ( ) . unwrap_or ( "" ) ;
304- println ! ( " [{}] {} \u{2014} {}" , sev, title, location) ;
391+ let loc = f
392+ . file
393+ . as_deref ( )
394+ . map ( |fp| {
395+ if let Some ( ln) = f. line {
396+ format ! ( "{}:{}" , fp, ln)
397+ } else {
398+ fp. to_string ( )
399+ }
400+ } )
401+ . unwrap_or_default ( ) ;
402+ println ! (
403+ " [{}] {} \u{2014} {} (confidence: {:.0}%)" ,
404+ f. severity, f. description, loc, f. confidence * 100.0
405+ ) ;
305406 }
306407 }
307408}
0 commit comments