pmat 3.30.1

PMAT - Zero-config AI context generation and code quality toolkit (CLI, MCP)
Documentation
#![allow(unused)]
#![cfg_attr(coverage_nightly, coverage(off))]
//! Helper functions for provability analysis to reduce complexity

use crate::services::lightweight_provability_analyzer::{FunctionId, ProofSummary};
use anyhow::Result;
use std::collections::HashMap;
use std::fmt::Write;
use std::path::Path;

/// Parse function specification string into `FunctionId`
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn parse_function_spec(spec: &str, project_path: &Path) -> Result<FunctionId> {
    // Parse function specification in format: path/to/file.rs:function_name
    // or just function_name (search all files)
    if let Some((file_part, func_part)) = spec.split_once(':') {
        Ok(FunctionId {
            file_path: project_path.join(file_part).to_string_lossy().to_string(),
            function_name: func_part.to_string(),
            line_number: 0, // Will be populated by analyzer
        })
    } else {
        // Just function name - will search all files
        Ok(FunctionId {
            file_path: String::new(),
            function_name: spec.to_string(),
            line_number: 0,
        })
    }
}

/// Functions among `discovered` that a `--functions` spec names.
///
/// `parse_function_spec` above manufactures a `FunctionId` out of whatever
/// string it is handed — its "will search all files" comment described a search
/// that was never performed. That is why `analyze provability -p <empty dir>
/// --functions ghost` reported "✓ Analyzed 1 functions / Average provability
/// score: 20.0%": a phantom id with an empty file path, scored at the lattice
/// Top default because its source could never be read. Matching against the
/// functions actually discovered in the tree is the only way an answer here can
/// be about the code rather than about the argument.
///
/// A spec is either `function_name` or `path/to/file.rs:function_name`; the
/// file part matches a discovered path exactly or as a trailing path suffix.
#[must_use]
pub fn match_function_spec(spec: &str, discovered: &[FunctionId]) -> Vec<FunctionId> {
    let (file_part, func_part) = match spec.split_once(':') {
        Some((file, func)) => (Some(file.trim_start_matches("./")), func),
        None => (None, spec),
    };

    discovered
        .iter()
        .filter(|id| {
            if id.function_name != func_part {
                return false;
            }
            match file_part {
                None => true,
                Some(wanted) => {
                    let have = id.file_path.trim_start_matches("./");
                    have == wanted || have.ends_with(&format!("/{wanted}"))
                }
            }
        })
        .cloned()
        .collect()
}

/// Extract function name from a line
fn extract_function_name(line: &str) -> Option<String> {
    let line = line.trim();
    let start = line.find("fn ")? + 3;
    let end = line.get(start..)?.find(['(', '<'])?;
    Some(
        line.get(start..start + end)
            .unwrap_or_default()
            .trim()
            .to_string(),
    )
}

/// Filter function summaries based on confidence
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn filter_summaries(
    summaries: &[ProofSummary],
    high_confidence_only: bool,
) -> Vec<&ProofSummary> {
    summaries
        .iter()
        .filter(|s| !high_confidence_only || s.provability_score >= 0.8)
        .collect()
}

fn categorize_scores(summaries: &[ProofSummary]) -> (usize, usize, usize) {
    let high_provability = summaries
        .iter()
        .filter(|s| s.provability_score >= 0.8)
        .count();
    let medium_provability = summaries
        .iter()
        .filter(|s| s.provability_score >= 0.5 && s.provability_score < 0.8)
        .count();
    let low_provability = summaries
        .iter()
        .filter(|s| s.provability_score < 0.5)
        .count();

    (high_provability, medium_provability, low_provability)
}

fn calculate_average_score(summaries: &[ProofSummary]) -> f64 {
    if summaries.is_empty() {
        0.0
    } else {
        summaries.iter().map(|s| s.provability_score).sum::<f64>() / summaries.len() as f64
    }
}

// --- Include split-out implementation files ---

include!("provability_helpers_discovery.rs");
include!("provability_helpers_json.rs");
include!("provability_helpers_summary.rs");
include!("provability_helpers_detailed.rs");
include!("provability_helpers_sarif.rs");