repotoire 0.8.1

Graph-powered code analysis CLI. 110 detectors for security, architecture, bus factor, and code quality.
Documentation
//! Stage 7: Finding transforms (pure — no caching, no I/O, no presentation).

use crate::config::ProjectConfig;
use crate::detectors::FileContentCache;
use crate::graph::GraphQuery;
use crate::models::Finding;
use anyhow::Result;
use std::collections::HashSet;
use std::path::{Path, PathBuf};
use std::sync::Arc;

/// Input for the postprocess stage. Takes ownership of findings.
pub struct PostprocessInput<'a> {
    pub findings: Vec<Finding>,
    pub project_config: &'a ProjectConfig,
    pub graph: &'a dyn GraphQuery,
    pub all_files: &'a [PathBuf],
    pub repo_path: &'a Path,
    pub verify: bool,
    /// Detector names that opt out of GBDT postprocessor filtering.
    pub bypass_set: HashSet<String>,
    /// Optional file content + parse-tree cache shared with the detect
    /// stage. Postprocess passes that need an AST (e.g. the param-gated
    /// security demotion) hit warm cache here for files the detectors
    /// already parsed. `None` is fine — the relevant pass falls back to a
    /// throwaway local cache.
    pub file_cache: Option<Arc<FileContentCache>>,
}

/// Statistics from the postprocess stage.
pub struct PostprocessStats {
    pub input_count: usize,
    pub output_count: usize,
    pub suppressed: usize,
    pub excluded: usize,
    pub deduped: usize,
    pub fp_filtered: usize,
    pub security_downgraded: usize,
}

/// Output from the postprocess stage.
pub struct PostprocessOutput {
    pub findings: Vec<Finding>,
    pub stats: PostprocessStats,
}

/// Run the postprocessing pipeline on findings.
///
/// Takes ownership of findings (every sub-step mutates the Vec).
pub fn postprocess_stage(input: PostprocessInput) -> Result<PostprocessOutput> {
    let input_count = input.findings.len();
    let mut findings = input.findings;

    let params = crate::cli::analyze::postprocess::PostprocessParams {
        project_config: input.project_config,
        all_files: input.all_files,
        graph: input.graph,
        repo_path: input.repo_path,
        bypass_set: &input.bypass_set,
        verify: input.verify,
        file_cache: input.file_cache.as_ref(),
    };
    let summary = crate::cli::analyze::postprocess::postprocess_findings(&mut findings, &params);

    let output_count = findings.len();
    let total_removed = input_count.saturating_sub(output_count);

    Ok(PostprocessOutput {
        findings,
        stats: PostprocessStats {
            input_count,
            output_count,
            suppressed: 0, // Individual breakdowns not available from existing function
            excluded: 0,
            deduped: 0,
            fp_filtered: total_removed,
            security_downgraded: summary.security_downgraded,
        },
    })
}