Skip to main content

the_code_graph_cli/commands/
risk.rs

1use domain::analysis::risk::split_into_segments;
2use domain::error::Result;
3use domain::model::RiskConfig;
4use domain::use_cases::risk::RiskUseCase;
5
6use crate::commands::helpers::open_graph;
7use crate::commands::RiskArgs;
8use crate::config::load_config;
9use crate::output::{print, OutputFormat, RiskScoreDetail};
10
11pub fn run_risk(args: &RiskArgs, output_format: OutputFormat) -> Result<()> {
12    let (store, root) = open_graph()?;
13    let config = load_config(&root)?;
14
15    // Build RiskConfig from defaults + config.toml overrides
16    let mut risk_config = RiskConfig::default();
17
18    if let Some(rc) = &config.risk {
19        // Override weights if specified
20        if let Some(w) = rc.weight_criticality {
21            risk_config.weights.criticality = w;
22        }
23        if let Some(w) = rc.weight_coupling {
24            risk_config.weights.coupling = w;
25        }
26        if let Some(w) = rc.weight_test_gap {
27            risk_config.weights.test_gap = w;
28        }
29        if let Some(w) = rc.weight_sensitivity {
30            risk_config.weights.sensitivity = w;
31        }
32
33        // Merge security patterns: built-in + extra - excluded
34        if let Some(extra) = &rc.extra_security_patterns {
35            risk_config.security_patterns.extend(extra.clone());
36        }
37        if let Some(excluded) = &rc.excluded_security_patterns {
38            risk_config
39                .security_patterns
40                .retain(|p| !excluded.contains(p));
41        }
42    }
43
44    // Normalize weights
45    risk_config.weights = risk_config.weights.normalized();
46
47    let uc = RiskUseCase::new(store);
48
49    if let Some(ref target) = args.target {
50        // Single target mode
51        let score = uc.score_symbol(target, &risk_config)?;
52        // Find which security patterns matched this symbol
53        let segments = split_into_segments(&score.qualified_name);
54        let lower_patterns: Vec<String> = risk_config
55            .security_patterns
56            .iter()
57            .map(|p| p.to_lowercase())
58            .collect();
59        let matched_patterns: Vec<String> = lower_patterns
60            .iter()
61            .filter(|pat| segments.iter().any(|seg| seg.starts_with(pat.as_str())))
62            .cloned()
63            .collect();
64        let detail = RiskScoreDetail {
65            score,
66            matched_patterns,
67            weights: risk_config.weights.clone(),
68        };
69        print(&detail, output_format);
70    } else if args.symbols {
71        // Symbol list mode
72        let analysis = uc.analyze(&risk_config)?;
73        let filtered: Vec<_> = analysis
74            .symbol_scores
75            .into_iter()
76            .filter(|s| s.composite >= args.min_score)
77            .take(args.limit)
78            .collect();
79        print(&filtered, output_format);
80    } else {
81        // Default: file list mode
82        let analysis = uc.analyze(&risk_config)?;
83        let mut filtered_analysis = analysis;
84        filtered_analysis
85            .file_scores
86            .retain(|f| f.composite >= args.min_score);
87        filtered_analysis.file_scores.truncate(args.limit);
88        print(&filtered_analysis, output_format);
89    }
90    Ok(())
91}